Merge "Add a failsafe to update surface position after activity-level transit" into udc-dev am: 321ed901a3 am: 58f2edece5

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/23925538

Change-Id: Ibba4b5b6922baeadd93d73814fffc76b00131697
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/.clang-format b/.clang-format
index d60d33c..0f6b166 100644
--- a/.clang-format
+++ b/.clang-format
@@ -2,7 +2,7 @@
 
 AccessModifierOffset: -4
 AlignOperands: false
-AllowShortFunctionsOnASingleLine: Inline
+AllowShortFunctionsOnASingleLine: Empty
 AlwaysBreakBeforeMultilineStrings: false
 ColumnLimit: 100
 CommentPragmas: NOLINT:.*
diff --git a/Android.bp b/Android.bp
index 4473d94..be589b2 100644
--- a/Android.bp
+++ b/Android.bp
@@ -100,6 +100,10 @@
         ":android.hardware.gnss-V2-java-source",
         ":android.hardware.graphics.common-V3-java-source",
         ":android.hardware.keymaster-V4-java-source",
+        ":android.hardware.radio-V3-java-source",
+        ":android.hardware.radio.data-V3-java-source",
+        ":android.hardware.radio.network-V3-java-source",
+        ":android.hardware.radio.voice-V3-java-source",
         ":android.hardware.security.keymint-V3-java-source",
         ":android.hardware.security.secureclock-V1-java-source",
         ":android.hardware.thermal-V1-java-source",
@@ -208,24 +212,19 @@
         "android.hardware.contexthub-V1.0-java",
         "android.hardware.contexthub-V1.1-java",
         "android.hardware.contexthub-V1.2-java",
-        "android.hardware.contexthub-V2-java",
+        "android.hardware.contexthub-V3-java",
         "android.hardware.gnss-V1.0-java",
         "android.hardware.gnss-V2.1-java",
         "android.hardware.health-V1.0-java-constants",
-        "android.hardware.radio-V1.0-java",
-        "android.hardware.radio-V1.1-java",
-        "android.hardware.radio-V1.2-java",
-        "android.hardware.radio-V1.3-java",
-        "android.hardware.radio-V1.4-java",
-        "android.hardware.radio-V1.5-java",
         "android.hardware.radio-V1.6-java",
-        "android.hardware.radio.data-V2-java",
-        "android.hardware.radio.ims-V1-java",
-        "android.hardware.radio.messaging-V2-java",
-        "android.hardware.radio.modem-V2-java",
-        "android.hardware.radio.network-V2-java",
-        "android.hardware.radio.sim-V2-java",
-        "android.hardware.radio.voice-V2-java",
+        "android.hardware.radio.data-V3-java",
+        "android.hardware.radio.ims-V2-java",
+        "android.hardware.radio.messaging-V3-java",
+        "android.hardware.radio.modem-V3-java",
+        "android.hardware.radio.network-V3-java",
+        "android.hardware.radio.satellite-V1-java",
+        "android.hardware.radio.sim-V3-java",
+        "android.hardware.radio.voice-V3-java",
         "android.hardware.thermal-V1.0-java-constants",
         "android.hardware.thermal-V1.0-java",
         "android.hardware.thermal-V1.1-java",
@@ -247,7 +246,6 @@
         "android.system.suspend.control.internal-java",
         "devicepolicyprotosnano",
 
-        "com.android.sysprop.apex",
         "com.android.sysprop.init",
         "com.android.sysprop.localization",
         "PlatformProperties",
@@ -369,17 +367,12 @@
         // TODO(b/120066492): remove default_television.xml when the build system
         // propagates "required" properly.
         "default_television.xml",
-        "framework-platform-compat-config",
         // TODO(b/120066492): remove gps_debug and protolog.conf.json when the build
         // system propagates "required" properly.
         "gps_debug.conf",
-        "icu4j-platform-compat-config",
         "protolog.conf.json.gz",
-        "services-platform-compat-config",
-        "TeleService-platform-compat-config",
-        "documents-ui-compat-config",
-        "calendar-provider-compat-config",
-        "contacts-provider-platform-compat-config",
+        // any install dependencies should go into framework-minus-apex-install-dependencies
+        // rather than here to avoid bloating incremental build time
     ],
     libs: [
         "androidx.annotation_annotation",
@@ -487,6 +480,20 @@
     apex_available: ["//apex_available:platform"],
 }
 
+java_library {
+    name: "framework-minus-apex-install-dependencies",
+    required: [
+        "framework-minus-apex",
+        "framework-platform-compat-config",
+        "services-platform-compat-config",
+        "icu4j-platform-compat-config",
+        "TeleService-platform-compat-config",
+        "documents-ui-compat-config",
+        "calendar-provider-compat-config",
+        "contacts-provider-platform-compat-config",
+    ],
+}
+
 platform_compat_config {
     name: "framework-platform-compat-config",
     src: ":framework-minus-apex",
@@ -504,6 +511,13 @@
 }
 
 filegroup {
+    name: "framework-android-os-unit-testable-src",
+    srcs: [
+        "core/java/android/os/DdmSyncState.java",
+    ],
+}
+
+filegroup {
     name: "framework-networkstack-shared-srcs",
     srcs: [
         // TODO: remove these annotations as soon as we can use andoid.support.annotations.*
@@ -579,7 +593,6 @@
     "--hide RequiresPermission " +
     "--hide SdkConstant " +
     "--hide Todo " +
-    "--hide Typo " +
     "--hide UnavailableSymbol " +
     "--manifest $(location :frameworks-base-core-AndroidManifest.xml) "
 
@@ -642,8 +655,6 @@
     libs: [
         "android.hardware.cas-V1.2-java",
         "android.hardware.health-V1.0-java-constants",
-        "android.hardware.radio-V1.5-java",
-        "android.hardware.radio-V1.6-java",
         "android.hardware.thermal-V1.0-java-constants",
         "android.hardware.thermal-V2.0-java",
         "android.hardware.tv.input-V1.0-java-constants",
diff --git a/INTENT_OWNERS b/INTENT_OWNERS
new file mode 100644
index 0000000..58b5f2a
--- /dev/null
+++ b/INTENT_OWNERS
@@ -0,0 +1,3 @@
+include /PACKAGE_MANAGER_OWNERS
+include /services/core/java/com/android/server/wm/OWNERS
+include /services/core/java/com/android/server/am/OWNERS
diff --git a/OWNERS b/OWNERS
index dad8bfe..4860acc 100644
--- a/OWNERS
+++ b/OWNERS
@@ -38,3 +38,4 @@
 per-file TestProtoLibraries.bp = file:platform/tools/tradefederation:/OWNERS
 
 per-file ZYGOTE_OWNERS = file:/ZYGOTE_OWNERS
+per-file SQLITE_OWNERS = file:/SQLITE_OWNERS
diff --git a/ProtoLibraries.bp b/ProtoLibraries.bp
index fcff581..45bb161 100644
--- a/ProtoLibraries.bp
+++ b/ProtoLibraries.bp
@@ -21,10 +21,6 @@
         "soong_zip",
     ],
 
-    tool_files: [
-        ":libprotobuf-internal-protos",
-    ],
-
     cmd: "mkdir -p $(genDir)/$(in) " +
         "&& $(location aprotoc) " +
         "  --plugin=$(location protoc-gen-javastream) " +
@@ -42,6 +38,11 @@
         "core/proto/**/*.proto",
         "libs/incident/**/*.proto",
     ],
+
+    data: [
+        ":libprotobuf-internal-protos",
+    ],
+
     output_extension: "srcjar",
 }
 
@@ -53,10 +54,6 @@
         "protoc-gen-cppstream",
     ],
 
-    tool_files: [
-        ":libprotobuf-internal-protos",
-    ],
-
     cmd: "mkdir -p $(genDir) " +
         "&& $(location aprotoc) " +
         "  --plugin=$(location protoc-gen-cppstream) " +
@@ -73,6 +70,10 @@
         "libs/incident/**/*.proto",
     ],
 
+    data: [
+        ":libprotobuf-internal-protos",
+    ],
+
     output_extension: "proto.h",
 }
 
diff --git a/SQLITE_OWNERS b/SQLITE_OWNERS
new file mode 100644
index 0000000..1ff72e7
--- /dev/null
+++ b/SQLITE_OWNERS
@@ -0,0 +1,2 @@
+shayba@google.com
+shombert@google.com
diff --git a/apct-tests/perftests/core/OWNERS b/apct-tests/perftests/core/OWNERS
index 6abab6e..f8fe51c 100644
--- a/apct-tests/perftests/core/OWNERS
+++ b/apct-tests/perftests/core/OWNERS
@@ -12,3 +12,5 @@
 per-file /apct-tests/perftests/core/src/android/content/om/* = felkachang@google.com
 per-file /apct-tests/perftests/core/src/android/content/om/* = file:/core/java/android/content/om/OWNERS
 
+# Bug component: 44215
+per-file **Accessibility* = file:/core/java/android/view/accessibility/OWNERS
\ No newline at end of file
diff --git a/apct-tests/perftests/core/src/android/accessibility/AccessibilityPerfTest.java b/apct-tests/perftests/core/src/android/accessibility/AccessibilityPerfTest.java
new file mode 100644
index 0000000..7927aa9
--- /dev/null
+++ b/apct-tests/perftests/core/src/android/accessibility/AccessibilityPerfTest.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.accessibility;
+
+import static junit.framework.Assert.assertTrue;
+
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.app.UiAutomation;
+import android.perftests.utils.PerfTestActivity;
+import android.platform.test.annotations.LargeTest;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import androidx.benchmark.BenchmarkState;
+import androidx.benchmark.junit4.BenchmarkRule;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.rule.ActivityTestRule;
+
+import com.android.compatibility.common.util.TestUtils;
+
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.RuleChain;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@LargeTest
+public class AccessibilityPerfTest {
+
+    private static final String TEXT_KEY = "Child";
+
+    BenchmarkRule mBenchmarkRule = new BenchmarkRule();
+    ActivityTestRule<PerfTestActivity> mActivityTestRule =
+            new ActivityTestRule(PerfTestActivity.class);
+
+    @Rule
+    public RuleChain rules =
+            RuleChain.outerRule(mBenchmarkRule).around(mActivityTestRule);
+
+    private static Instrumentation sInstrumentation;
+
+    private Activity mActivity;
+
+    private ViewGroup createTestViewGroup(int children) {
+        ViewGroup group = new LinearLayout(mActivity.getBaseContext());
+        sInstrumentation.runOnMainSync(() -> {
+            mActivity.setContentView(group);
+            for (int i = 0; i < children; i++) {
+                TextView text = new TextView(mActivity.getBaseContext());
+                text.setText(TEXT_KEY);
+                group.addView(text);
+            }
+        });
+
+        return group;
+    }
+
+    @BeforeClass
+    public static void setUpClass() {
+        sInstrumentation = InstrumentationRegistry.getInstrumentation();
+    }
+
+    @Before
+    public void setUp() {
+        mActivity = mActivityTestRule.getActivity();
+    }
+
+    @Test
+    public void testCreateAccessibilityNodeInfo() {
+        final BenchmarkState state = mBenchmarkRule.getState();
+        View view = new View(mActivity.getBaseContext());
+
+        while (state.keepRunning()) {
+            view.createAccessibilityNodeInfo();
+        }
+    }
+
+    @Test
+    public void testCreateViewGroupAccessibilityNodeInfo() {
+        final BenchmarkState state = mBenchmarkRule.getState();
+        ViewGroup group = createTestViewGroup(10);
+
+        while (state.keepRunning()) {
+            group.createAccessibilityNodeInfo();
+        }
+    }
+
+    @Test
+    public void testCreateAccessibilityEvent() {
+        final BenchmarkState state = mBenchmarkRule.getState();
+        View view = new View(mActivity.getBaseContext());
+
+        while (state.keepRunning()) {
+            view.onInitializeAccessibilityEvent(
+                    new AccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED));
+        }
+    }
+
+    @Test
+    public void testPrefetching() throws Exception {
+        final BenchmarkState state = mBenchmarkRule.getState();
+        createTestViewGroup(AccessibilityNodeInfo.MAX_NUMBER_OF_PREFETCHED_NODES);
+        UiAutomation uiAutomation = sInstrumentation.getUiAutomation();
+
+        while (state.keepRunning()) {
+            state.pauseTiming();
+            uiAutomation.clearCache();
+            CountDownLatch latch = new CountDownLatch(
+                    AccessibilityNodeInfo.MAX_NUMBER_OF_PREFETCHED_NODES);
+            uiAutomation.getCache().registerOnNodeAddedListener(
+                    (node) -> {
+                        latch.countDown();
+                    });
+            state.resumeTiming();
+            // Get the root node, and await for the latch to have seen the expected max number
+            // of prefetched nodes.
+            uiAutomation.getRootInActiveWindow(
+                    AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS_HYBRID
+                            | AccessibilityNodeInfo.FLAG_PREFETCH_UNINTERRUPTIBLE);
+            assertTrue(latch.await(100, TimeUnit.MILLISECONDS));
+        }
+    }
+
+    @Test
+    public void testConnectUiAutomation() throws Exception {
+        final BenchmarkState state = mBenchmarkRule.getState();
+        while (state.keepRunning()) {
+            UiAutomation uiAutomation = sInstrumentation.getUiAutomation();
+            state.pauseTiming();
+            uiAutomation.destroy();
+            TestUtils.waitUntil(
+                    "UiAutomation did not disconnect.", 10,
+                    () -> uiAutomation.isDestroyed()
+            );
+            state.resumeTiming();
+        }
+        // We currently run into an exception
+        // if a test ends with UiAutomation explicitly disconnected,
+        // which seems to be the result of some commands being run by benchmarking.
+        sInstrumentation.getUiAutomation();
+    }
+}
diff --git a/apct-tests/perftests/core/src/android/input/VelocityTrackerBenchmarkTest.kt b/apct-tests/perftests/core/src/android/input/VelocityTrackerBenchmarkTest.kt
new file mode 100644
index 0000000..530ca7b
--- /dev/null
+++ b/apct-tests/perftests/core/src/android/input/VelocityTrackerBenchmarkTest.kt
@@ -0,0 +1,258 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.input
+
+import android.perftests.utils.PerfStatusReporter
+import android.view.InputDevice
+import android.view.MotionEvent
+import android.view.VelocityTracker
+
+import androidx.test.filters.LargeTest
+import androidx.test.runner.AndroidJUnit4
+
+import java.time.Duration
+
+import org.junit.Assert
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Helper class to maintain [MotionEvent]s for tests.
+ *
+ * This is primarily used to create [MotionEvent]s for tests, in a way where a sequence of
+ * [MotionEvent]s created in multiple test runs are exactly the same, as long as [reset] is called
+ * between consecutive sequences of [MotionEvent]s.
+ *
+ * Furthermore, it also contains convenience methods to run any queries/verifications of the
+ * generated [MotionEvent]s.
+ */
+abstract class MotionState {
+    /** Current time, in ms. */
+    protected var currentTime = START_TIME
+
+    /** Resets the state of this instance. */
+    open fun reset() {
+        currentTime = START_TIME
+    }
+
+    /** Creates a [MotionEvent]. */
+    abstract fun createMotionEvent(): MotionEvent
+
+    /** Asserts that the current velocity is not zero, just for verifying there's motion. */
+    abstract fun assertNonZeroVelocity(velocityTracker: VelocityTracker)
+
+    companion object {
+        /** Arbitrarily chosen start time. */
+        val START_TIME = Duration.ofMillis(100)
+        /**
+         * A small enough time jump, which won't be considered by the tracker as big enough to
+         * deduce that a pointer has stopped.
+         */
+        val DEFAULT_TIME_JUMP = Duration.ofMillis(2)
+    }
+}
+
+/** An implementation of [MotionState] for [MotionEvent.AXIS_SCROLL]. */
+private class ScrollMotionState : MotionState() {
+    override fun createMotionEvent(): MotionEvent {
+        val props = MotionEvent.PointerProperties()
+        props.id = 0
+        val coords = MotionEvent.PointerCoords()
+        coords.setAxisValue(MotionEvent.AXIS_SCROLL, DEFAULT_SCROLL_AMOUNT)
+        val motionEvent = MotionEvent.obtain(
+            /*downTime=*/0,
+            currentTime.toMillis(),
+            MotionEvent.ACTION_SCROLL,
+            /*pointerCount=*/1,
+            arrayOf(props),
+            arrayOf(coords),
+            /*metaState=*/0,
+            /*buttonState=*/0,
+            /*xPrecision=*/0f,
+            /*yPrecision=*/0f,
+            /*deviceId=*/1,
+            /*edgeFlags=*/0,
+            InputDevice.SOURCE_ROTARY_ENCODER,
+            /*flags=*/0
+        )
+
+        currentTime = currentTime.plus(DEFAULT_TIME_JUMP)
+
+        return motionEvent
+    }
+
+    override fun assertNonZeroVelocity(velocityTracker: VelocityTracker) {
+        Assert.assertTrue(velocityTracker.getAxisVelocity(MotionEvent.AXIS_SCROLL) != 0f)
+    }
+
+    companion object {
+        private val DEFAULT_SCROLL_AMOUNT: Float = 30f
+    }
+}
+
+/** An implementation of [MotionState] for [MotionEvent.AXIS_X] and [MotionEvent.AXIS_Y]. */
+private class PlanarMotionState : MotionState() {
+    private var x: Float = DEFAULT_X
+    private var y: Float = DEFAULT_Y
+    private var downEventCreated = false
+
+    override fun createMotionEvent(): MotionEvent {
+        val action: Int = if (downEventCreated) MotionEvent.ACTION_MOVE else MotionEvent.ACTION_DOWN
+        val motionEvent = MotionEvent.obtain(
+            /*downTime=*/START_TIME.toMillis(),
+            currentTime.toMillis(),
+            action,
+            x,
+            y,
+            /*metaState=*/0)
+
+        if (downEventCreated) {
+            x += INCREMENT
+            y += INCREMENT
+        } else {
+            downEventCreated = true
+        }
+        currentTime = currentTime.plus(DEFAULT_TIME_JUMP)
+
+        return motionEvent
+    }
+
+    override fun assertNonZeroVelocity(velocityTracker: VelocityTracker) {
+        Assert.assertTrue(velocityTracker.getAxisVelocity(MotionEvent.AXIS_X) != 0f)
+        Assert.assertTrue(velocityTracker.getAxisVelocity(MotionEvent.AXIS_Y) != 0f)
+    }
+
+    override fun reset() {
+        super.reset()
+        x = DEFAULT_X
+        y = DEFAULT_Y
+        downEventCreated = false
+    }
+
+    companion object {
+        /** Arbitrarily chosen constants. No need to have varying velocity for now. */
+        private val DEFAULT_X: Float = 2f
+        private val DEFAULT_Y: Float = 4f
+        private val INCREMENT: Float = 0.7f
+    }
+}
+
+/**
+ * Benchmark tests for [VelocityTracker]
+ *
+ * Build/Install/Run:
+ * atest VelocityTrackerBenchmarkTest
+ */
+@LargeTest
+@RunWith(AndroidJUnit4::class)
+class VelocityTrackerBenchmarkTest {
+    @get:Rule
+    val perfStatusReporter: PerfStatusReporter = PerfStatusReporter()
+
+    private val velocityTracker = VelocityTracker.obtain()
+    @Before
+    fun setup() {
+        velocityTracker.clear()
+    }
+
+    @Test
+    fun addMovement_axisScroll() {
+        testAddMovement(ScrollMotionState())
+    }
+
+    @Test
+    fun computeCurrentVelocity_computeAfterAllAdditions_axisScroll() {
+        testComputeCurrentVelocity_computeAfterAllAdditions(ScrollMotionState())
+    }
+
+    @Test
+    fun computeCurrentVelocity_computeAfterEachAdd_axisScroll() {
+        testComputeCurrentVelocity_computeAfterEachAdd(ScrollMotionState())
+    }
+
+    @Test
+    fun addMovement_planarAxes() {
+        testAddMovement(PlanarMotionState())
+    }
+
+    @Test
+    fun computeCurrentVelocity_computeAfterAllAdditions_planarAxes() {
+        testComputeCurrentVelocity_computeAfterAllAdditions(PlanarMotionState())
+    }
+
+    private fun testAddMovement(motionState: MotionState) {
+        val state = perfStatusReporter.getBenchmarkState()
+        while (state.keepRunning()) {
+            state.pauseTiming()
+            for (i in 0 until TEST_NUM_DATAPOINTS) {
+                val motionEvent = motionState.createMotionEvent()
+                state.resumeTiming()
+                velocityTracker.addMovement(motionEvent)
+                state.pauseTiming()
+            }
+            velocityTracker.computeCurrentVelocity(1000)
+            motionState.assertNonZeroVelocity(velocityTracker)
+            // Clear the tracker for the next run
+            velocityTracker.clear()
+            motionState.reset()
+            state.resumeTiming()
+        }
+    }
+
+    private fun testComputeCurrentVelocity_computeAfterAllAdditions(motionState: MotionState) {
+        val state = perfStatusReporter.getBenchmarkState()
+        while (state.keepRunning()) {
+            // Add the data points
+            state.pauseTiming()
+            for (i in 0 until TEST_NUM_DATAPOINTS) {
+                velocityTracker.addMovement(motionState.createMotionEvent())
+            }
+
+            // Do the velocity computation
+            state.resumeTiming()
+            velocityTracker.computeCurrentVelocity(1000)
+
+            state.pauseTiming()
+            motionState.assertNonZeroVelocity(velocityTracker)
+            // Clear the tracker for the next run
+            velocityTracker.clear()
+            state.resumeTiming()
+        }
+    }
+
+    private fun testComputeCurrentVelocity_computeAfterEachAdd(motionState: MotionState) {
+        val state = perfStatusReporter.getBenchmarkState()
+        while (state.keepRunning()) {
+            state.pauseTiming()
+            for (i in 0 until TEST_NUM_DATAPOINTS) {
+                velocityTracker.addMovement(motionState.createMotionEvent())
+                state.resumeTiming()
+                velocityTracker.computeCurrentVelocity(1000)
+                state.pauseTiming()
+            }
+            motionState.assertNonZeroVelocity(velocityTracker)
+            // Clear the tracker for the next run
+            velocityTracker.clear()
+            state.resumeTiming()
+        }
+    }
+
+    companion object {
+        private const val TEST_NUM_DATAPOINTS = 100
+    }
+}
\ No newline at end of file
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/ExpensiveObjectsPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/ExpensiveObjectsPerfTest.java
index b995b06..7712aee 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/ExpensiveObjectsPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/ExpensiveObjectsPerfTest.java
@@ -52,7 +52,7 @@
         }
     }
 
-    @Test
+    @Test(timeout = 900000)
     public void timeClonedDateFormatTimeInstance() {
         DateFormat df = DateFormat.getTimeInstance(DateFormat.SHORT);
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
@@ -72,7 +72,7 @@
         }
     }
 
-    @Test
+    @Test(timeout = 900000)
     public void timeNewCollator() {
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
diff --git a/apct-tests/perftests/core/src/android/view/ViewShowHidePerfTest.java b/apct-tests/perftests/core/src/android/view/ViewShowHidePerfTest.java
index a69d3ff..818e11b 100644
--- a/apct-tests/perftests/core/src/android/view/ViewShowHidePerfTest.java
+++ b/apct-tests/perftests/core/src/android/view/ViewShowHidePerfTest.java
@@ -18,6 +18,7 @@
 
 import static org.junit.Assert.assertTrue;
 
+import android.app.UiAutomation;
 import android.content.Context;
 import android.graphics.Color;
 import android.graphics.drawable.ColorDrawable;
@@ -29,8 +30,8 @@
 
 import androidx.benchmark.BenchmarkState;
 import androidx.benchmark.junit4.BenchmarkRule;
-import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.LargeTest;
+import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.rule.ActivityTestRule;
 
 import org.junit.Rule;
@@ -142,6 +143,10 @@
     }
 
     private void testParentWithChild(TestCallback callback) throws Throwable {
+        // Make sure that a11y is disabled to prevent the test affected by accessibility events.
+        InstrumentationRegistry.getInstrumentation()
+                .getUiAutomation(UiAutomation.FLAG_DONT_USE_ACCESSIBILITY);
+
         mActivityRule.runOnUiThread(() -> {
             final BenchmarkState state = mBenchmarkRule.getState();
 
diff --git a/apct-tests/perftests/inputmethod/OWNERS b/apct-tests/perftests/inputmethod/OWNERS
index 5deb2ce..cbd94ba 100644
--- a/apct-tests/perftests/inputmethod/OWNERS
+++ b/apct-tests/perftests/inputmethod/OWNERS
@@ -1 +1,2 @@
+# Bug component: 34867
 include /core/java/android/view/inputmethod/OWNERS
diff --git a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
index 6dba5b3..da700aa 100644
--- a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
+++ b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
@@ -1557,6 +1557,7 @@
     }
 
     private void waitCoolDownPeriod() {
+        // Heuristic value based on local tests. Stability increased compared to no waiting.
         final int tenSeconds = 1000 * 10;
         waitForBroadcastIdle();
         sleep(tenSeconds);
diff --git a/apct-tests/perftests/surfaceflinger/AndroidTest.xml b/apct-tests/perftests/surfaceflinger/AndroidTest.xml
index 11d1106..58cf58b 100644
--- a/apct-tests/perftests/surfaceflinger/AndroidTest.xml
+++ b/apct-tests/perftests/surfaceflinger/AndroidTest.xml
@@ -58,8 +58,8 @@
         <option name="instrumentation-arg" key="report" value="true" />
         <option name="instrumentation-arg" key="arguments" value="-g" />
         <option name="instrumentation-arg" key="events_to_record" value="instructions,cpu-cycles,raw-l3d-cache-refill,sched:sched_waking" />
-        <option name="instrumentation-arg" key="processes_to_record" value="surfaceflinger" />
-        <option name="instrumentation-arg" key="symbols_to_report" value="&quot;commit;android::SurfaceFlinger::commit(;composite;android::SurfaceFlinger::composite(&quot;" />
+        <option name="instrumentation-arg" key="processes_to_record" value="surfaceflinger,android.perftests.surfaceflinger" />
+        <option name="instrumentation-arg" key="symbols_to_report" value="&quot;commit;android::SurfaceFlinger::commit(;composite;android::SurfaceFlinger::composite(;outbound;android::SurfaceComposerClient::Transaction::apply(;inbound;android::BnTransactionCompletedListener::onTransact(&quot;"/>
 
         <!-- should match profiling-iterations -->
         <option name="instrumentation-arg" key="test_iterations" value="525" />
diff --git a/apct-tests/perftests/windowmanager/src/android/wm/RecentsAnimationPerfTest.java b/apct-tests/perftests/windowmanager/src/android/wm/RecentsAnimationPerfTest.java
deleted file mode 100644
index 42fb24f..0000000
--- a/apct-tests/perftests/windowmanager/src/android/wm/RecentsAnimationPerfTest.java
+++ /dev/null
@@ -1,253 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.wm;
-
-import static android.perftests.utils.ManualBenchmarkState.StatsReport;
-
-import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
-
-import static org.hamcrest.core.AnyOf.anyOf;
-import static org.hamcrest.core.Is.is;
-
-import android.app.ActivityManager;
-import android.app.ActivityTaskManager;
-import android.app.IActivityTaskManager;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.graphics.Rect;
-import android.os.RemoteException;
-import android.os.SystemClock;
-import android.perftests.utils.ManualBenchmarkState;
-import android.perftests.utils.ManualBenchmarkState.ManualBenchmarkTest;
-import android.perftests.utils.PerfManualStatusReporter;
-import android.util.Pair;
-import android.view.IRecentsAnimationController;
-import android.view.IRecentsAnimationRunner;
-import android.view.RemoteAnimationTarget;
-import android.window.TaskSnapshot;
-
-import androidx.test.filters.LargeTest;
-import androidx.test.runner.lifecycle.Stage;
-
-import org.junit.AfterClass;
-import org.junit.Assume;
-import org.junit.Before;
-import org.junit.BeforeClass;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.concurrent.Semaphore;
-import java.util.concurrent.TimeUnit;
-
-@RunWith(Parameterized.class)
-@LargeTest
-public class RecentsAnimationPerfTest extends WindowManagerPerfTestBase
-        implements ManualBenchmarkState.CustomizedIterationListener {
-    private static Intent sRecentsIntent;
-
-    @Rule
-    public final PerfManualStatusReporter mPerfStatusReporter = new PerfManualStatusReporter();
-
-    @Rule
-    public final PerfTestActivityRule mActivityRule =
-            new PerfTestActivityRule(true /* launchActivity */);
-
-    private long mMeasuredTimeNs;
-
-    /**
-     * Used to skip each test method if there is error. It cannot be raised in static setup because
-     * that will break the amount of target method count.
-     */
-    private static Exception sSetUpClassException;
-
-    @Parameterized.Parameter(0)
-    public int intervalBetweenOperations;
-
-    @Parameterized.Parameters(name = "interval{0}ms")
-    public static Collection<Object[]> getParameters() {
-        return Arrays.asList(new Object[][] {
-                { 0 },
-                { 100 },
-                { 300 },
-        });
-    }
-
-    @BeforeClass
-    public static void setUpClass() {
-        // Get the permission to invoke startRecentsActivity.
-        getUiAutomation().adoptShellPermissionIdentity();
-
-        final Context context = getInstrumentation().getContext();
-        final PackageManager pm = context.getPackageManager();
-        final ComponentName defaultHome = pm.getHomeActivities(new ArrayList<>());
-
-        try {
-            final ComponentName recentsComponent =
-                    ComponentName.unflattenFromString(context.getResources().getString(
-                            com.android.internal.R.string.config_recentsComponentName));
-            final int enabledState = pm.getComponentEnabledSetting(recentsComponent);
-            Assume.assumeThat(enabledState, anyOf(
-                    is(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT),
-                    is(PackageManager.COMPONENT_ENABLED_STATE_ENABLED)));
-
-            final boolean homeIsRecents =
-                    recentsComponent.getPackageName().equals(defaultHome.getPackageName());
-            sRecentsIntent =
-                    new Intent().setComponent(homeIsRecents ? defaultHome : recentsComponent);
-        } catch (Exception e) {
-            sSetUpClassException = e;
-        }
-    }
-
-    @AfterClass
-    public static void tearDownClass() {
-        sSetUpClassException = null;
-        try {
-            // Recents activity may stop app switches. Restore the state to avoid affecting
-            // the next test.
-            ActivityManager.resumeAppSwitches();
-        } catch (RemoteException ignored) {
-        }
-        getUiAutomation().dropShellPermissionIdentity();
-    }
-
-    @Before
-    public void setUp() {
-        Assume.assumeNoException(sSetUpClassException);
-    }
-
-    /** Simulate the timing of touch. */
-    private void makeInterval() {
-        SystemClock.sleep(intervalBetweenOperations);
-    }
-
-    /**
-     * <pre>
-     * Steps:
-     * (1) Start recents activity (only make it visible).
-     * (2) Finish animation, take turns to execute (a), (b).
-     *     (a) Move recents activity to top.
-     * ({@link com.android.server.wm.RecentsAnimationController#REORDER_MOVE_TO_TOP})
-     *         Move test app to top by startActivityFromRecents.
-     *     (b) Cancel (it is similar to swipe a little distance and give up to enter recents).
-     * ({@link com.android.server.wm.RecentsAnimationController#REORDER_MOVE_TO_ORIGINAL_POSITION})
-     * (3) Loop (1).
-     * </pre>
-     */
-    @Test
-    @ManualBenchmarkTest(
-            warmupDurationNs = TIME_1_S_IN_NS,
-            targetTestDurationNs = TIME_5_S_IN_NS,
-            statsReport = @StatsReport(flags = StatsReport.FLAG_ITERATION | StatsReport.FLAG_MEAN
-                    | StatsReport.FLAG_COEFFICIENT_VAR))
-    public void testRecentsAnimation() throws Throwable {
-        final ManualBenchmarkState state = mPerfStatusReporter.getBenchmarkState();
-        state.setCustomizedIterations(getProfilingIterations(), this);
-        final IActivityTaskManager atm = ActivityTaskManager.getService();
-
-        final ArrayList<Pair<String, Boolean>> finishCases = new ArrayList<>();
-        // Real launch the recents activity.
-        finishCases.add(new Pair<>("finishMoveToTop", true));
-        // Return to the original top.
-        finishCases.add(new Pair<>("finishCancel", false));
-
-        // Ensure startRecentsActivity won't be called before finishing the animation.
-        final Semaphore recentsSemaphore = new Semaphore(1);
-
-        final int testActivityTaskId = mActivityRule.getActivity().getTaskId();
-        final IRecentsAnimationRunner.Stub anim = new IRecentsAnimationRunner.Stub() {
-            int mIteration;
-
-            @Override
-            public void onAnimationStart(IRecentsAnimationController controller,
-                    RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers,
-                    Rect homeContentInsets, Rect minimizedHomeBounds) throws RemoteException {
-                final Pair<String, Boolean> finishCase = finishCases.get(mIteration++ % 2);
-                final boolean moveRecentsToTop = finishCase.second;
-                makeInterval();
-
-                long startTime = SystemClock.elapsedRealtimeNanos();
-                controller.finish(moveRecentsToTop, false /* sendUserLeaveHint */);
-                final long elapsedTimeNsOfFinish = SystemClock.elapsedRealtimeNanos() - startTime;
-                mMeasuredTimeNs += elapsedTimeNsOfFinish;
-                state.addExtraResult(finishCase.first, elapsedTimeNsOfFinish);
-
-                if (moveRecentsToTop) {
-                    mActivityRule.waitForIdleSync(Stage.STOPPED);
-
-                    startTime = SystemClock.elapsedRealtimeNanos();
-                    atm.startActivityFromRecents(testActivityTaskId, null /* options */);
-                    final long elapsedTimeNs = SystemClock.elapsedRealtimeNanos() - startTime;
-                    mMeasuredTimeNs += elapsedTimeNs;
-                    state.addExtraResult("startFromRecents", elapsedTimeNs);
-
-                    mActivityRule.waitForIdleSync(Stage.RESUMED);
-                }
-
-                makeInterval();
-                recentsSemaphore.release();
-            }
-
-            @Override
-            public void onAnimationCanceled(int[] taskIds, TaskSnapshot[] taskSnapshots)
-                    throws RemoteException {
-                Assume.assumeNoException(
-                        new AssertionError("onAnimationCanceled should not be called"));
-            }
-
-            @Override
-            public void onTasksAppeared(RemoteAnimationTarget[] app) throws RemoteException {
-                /* no-op */
-            }
-        };
-
-        recentsSemaphore.tryAcquire();
-        while (state.keepRunning(mMeasuredTimeNs)) {
-            mMeasuredTimeNs = 0;
-
-            final long startTime = SystemClock.elapsedRealtimeNanos();
-            atm.startRecentsActivity(sRecentsIntent, 0 /* eventTime */, anim);
-            final long elapsedTimeNsOfStart = SystemClock.elapsedRealtimeNanos() - startTime;
-            mMeasuredTimeNs += elapsedTimeNsOfStart;
-            state.addExtraResult("start", elapsedTimeNsOfStart);
-
-            // Ensure the animation callback is done.
-            Assume.assumeTrue(recentsSemaphore.tryAcquire(
-                    sIsProfilingMethod() ? 10 * TIME_5_S_IN_NS : TIME_5_S_IN_NS,
-                    TimeUnit.NANOSECONDS));
-        }
-    }
-
-    @Override
-    public void onStart(int iteration) {
-        startProfiling(RecentsAnimationPerfTest.class.getSimpleName()
-                + "_interval_" + intervalBetweenOperations
-                + "_MethodTracing_" + iteration + ".trace");
-    }
-
-    @Override
-    public void onFinished(int iteration) {
-        stopProfiling();
-    }
-}
diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java b/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java
index d5315da..8494326 100644
--- a/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java
@@ -52,9 +52,9 @@
 import android.content.res.Resources;
 import android.os.Binder;
 import android.os.ParcelFileDescriptor;
+import android.os.Process;
 import android.os.RevocableFileDescriptor;
 import android.os.UserHandle;
-import android.permission.PermissionManager;
 import android.system.ErrnoException;
 import android.system.Os;
 import android.util.ArrayMap;
@@ -298,8 +298,8 @@
                 }
             }
 
-            final boolean canCallerAccessBlobsAcrossUsers = checkCallerCanAccessBlobsAcrossUsers(
-                    callingPackage, callingUserId);
+            final boolean canCallerAccessBlobsAcrossUsers =
+                    checkCallerCanAccessBlobsAcrossUsers(callingUid);
             if (!canCallerAccessBlobsAcrossUsers) {
                 return false;
             }
@@ -325,12 +325,11 @@
         return false;
     }
 
-    private static boolean checkCallerCanAccessBlobsAcrossUsers(
-            String callingPackage, int callingUserId) {
+    private boolean checkCallerCanAccessBlobsAcrossUsers(int callingUid) {
         final long token = Binder.clearCallingIdentity();
         try {
-            return PermissionManager.checkPackageNamePermission(ACCESS_BLOBS_ACROSS_USERS,
-                    callingPackage, callingUserId) == PackageManager.PERMISSION_GRANTED;
+            return mContext.checkPermission(ACCESS_BLOBS_ACROSS_USERS,
+                    Process.INVALID_PID, callingUid) == PackageManager.PERMISSION_GRANTED;
         } finally {
             Binder.restoreCallingIdentity(token);
         }
diff --git a/apex/jobscheduler/framework/java/android/app/IAlarmManager.aidl b/apex/jobscheduler/framework/java/android/app/IAlarmManager.aidl
index a46e697..80d2c5d 100644
--- a/apex/jobscheduler/framework/java/android/app/IAlarmManager.aidl
+++ b/apex/jobscheduler/framework/java/android/app/IAlarmManager.aidl
@@ -33,8 +33,10 @@
     void set(String callingPackage, int type, long triggerAtTime, long windowLength,
             long interval, int flags, in PendingIntent operation, in IAlarmListener listener,
             String listenerTag, in WorkSource workSource, in AlarmManager.AlarmClockInfo alarmClock);
+    @EnforcePermission("SET_TIME")
     @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
     boolean setTime(long millis);
+    @EnforcePermission("SET_TIME_ZONE")
     void setTimeZone(String zone);
     void remove(in PendingIntent operation, in IAlarmListener listener);
     void removeAll(String packageName);
@@ -43,5 +45,6 @@
     AlarmManager.AlarmClockInfo getNextAlarmClock(int userId);
     boolean canScheduleExactAlarms(String packageName);
     boolean hasScheduleExactAlarm(String packageName, int userId);
+    @EnforcePermission("DUMP")
     int getConfigVersion();
 }
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
index 526e63c..3a05323 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
@@ -1817,6 +1817,8 @@
         /**
          * Specify that this job should recur with the provided interval and flex. The job can
          * execute at any time in a window of flex length at the end of the period.
+         * If the constraints are not satisfied within the window,
+         * the job will wait until the constraints are satisfied.
          * @param intervalMillis Millisecond interval for which this job will repeat. A minimum
          *                       value of {@link #getMinPeriodMillis()} is enforced.
          * @param flexMillis Millisecond flex for this job. Flex is clamped to be at least
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobParameters.java b/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
index bf4f9a8..f1403bd5 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
@@ -438,7 +438,10 @@
      * For jobs with {@link android.app.job.JobInfo.Builder#setOverrideDeadline(long)} set, this
      * provides an easy way to tell whether the job is being executed due to the deadline
      * expiring. Note: If the job is running because its deadline expired, it implies that its
-     * constraints will not be met.
+     * constraints will not be met. However,
+     * {@link android.app.job.JobInfo.Builder#setPeriodic(boolean) periodic jobs} will only ever
+     * run when their constraints are satisfied, therefore, the constraints will still be satisfied
+     * for a periodic job even if the deadline has expired.
      */
     public boolean isOverrideDeadlineExpired() {
         return overrideDeadlineExpired;
diff --git a/apex/jobscheduler/framework/java/android/os/IDeviceIdleController.aidl b/apex/jobscheduler/framework/java/android/os/IDeviceIdleController.aidl
index 9d18dfe..256b68f 100644
--- a/apex/jobscheduler/framework/java/android/os/IDeviceIdleController.aidl
+++ b/apex/jobscheduler/framework/java/android/os/IDeviceIdleController.aidl
@@ -47,7 +47,6 @@
     long addPowerSaveTempWhitelistAppForMms(String name, int userId, int reasonCode, String reason);
     long addPowerSaveTempWhitelistAppForSms(String name, int userId, int reasonCode, String reason);
     long whitelistAppTemporarily(String name, int userId, int reasonCode, String reason);
+    @EnforcePermission("DEVICE_POWER")
     void exitIdle(String reason);
-    int setPreIdleTimeoutMode(int Mode);
-    void resetPreIdleTimeoutMode();
 }
diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
index 8316a26..76d1935 100644
--- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
+++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
@@ -23,6 +23,7 @@
 import static android.os.Process.INVALID_UID;
 
 import android.Manifest;
+import android.annotation.EnforcePermission;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SuppressLint;
@@ -369,6 +370,13 @@
     private Location mLastGenericLocation;
     @GuardedBy("this")
     private Location mLastGpsLocation;
+    @GuardedBy("this")
+    private boolean mBatterySaverEnabled;
+    @GuardedBy("this")
+    private boolean mIsOffBody;
+    @GuardedBy("this")
+    private boolean mForceBodyState;
+    private Sensor mOffBodySensor;
 
     /** Time in the elapsed realtime timebase when this listener last received a motion event. */
     @GuardedBy("this")
@@ -427,20 +435,7 @@
     private static final int ACTIVE_REASON_FORCED = 6;
     private static final int ACTIVE_REASON_ALARM = 7;
     private static final int ACTIVE_REASON_EMERGENCY_CALL = 8;
-    @VisibleForTesting
-    static final int SET_IDLE_FACTOR_RESULT_UNINIT = -1;
-    @VisibleForTesting
-    static final int SET_IDLE_FACTOR_RESULT_IGNORED = 0;
-    @VisibleForTesting
-    static final int SET_IDLE_FACTOR_RESULT_OK = 1;
-    @VisibleForTesting
-    static final int SET_IDLE_FACTOR_RESULT_NOT_SUPPORT = 2;
-    @VisibleForTesting
-    static final int SET_IDLE_FACTOR_RESULT_INVALID = 3;
-    @VisibleForTesting
-    static final long MIN_STATE_STEP_ALARM_CHANGE = 60 * 1000;
-    @VisibleForTesting
-    static final float MIN_PRE_IDLE_FACTOR_CHANGE = 0.05f;
+    private static final int ACTIVE_REASON_ONBODY = 9;
 
     @VisibleForTesting
     static String stateToString(int state) {
@@ -523,8 +518,6 @@
      */
     @GuardedBy("this")
     private long mMaintenanceStartTime;
-    @GuardedBy("this")
-    private long mIdleStartTime;
 
     @GuardedBy("this")
     private int mActiveIdleOpCount;
@@ -537,17 +530,6 @@
     @GuardedBy("this")
     private boolean mAlarmsActive;
 
-    /* Factor to apply to INACTIVE_TIMEOUT and IDLE_AFTER_INACTIVE_TIMEOUT in order to enter
-     * STATE_IDLE faster or slower. Don't apply this to SENSING_TIMEOUT or LOCATING_TIMEOUT because:
-     *   - Both of them are shorter
-     *   - Device sensor might take time be to become be stabilized
-     * Also don't apply the factor if the device is in motion because device motion provides a
-     * stronger signal than a prediction algorithm.
-     */
-    @GuardedBy("this")
-    private float mPreIdleFactor;
-    @GuardedBy("this")
-    private float mLastPreIdleFactor;
     @GuardedBy("this")
     private int mActiveReason;
 
@@ -850,6 +832,65 @@
         }
     }
 
+    /**
+     * LowLatencyOffBodyListener monitors if a device is on body or off body.
+     */
+    @VisibleForTesting
+    final class LowLatencyOffBodyListener implements SensorEventListener {
+        @Override
+        public void onSensorChanged(SensorEvent event) {
+            if (DEBUG) {
+                Slog.d(TAG, "LowLatencyOffBodyListener detects onSensorChanged event, values are: "
+                        + Arrays.toString(event.values));
+            }
+            if (event.values == null || event.values.length == 0) {
+                // The event returned should contain a single value to indicate off-body state.
+                // No value indicates something went wrong. Take no action and log an error.
+                Slog.e(TAG,
+                        "LowLatencyOffBodyListener detects onSensorChanged event but no event "
+                                + "value returns.");
+                return;
+            }
+            synchronized (DeviceIdleController.this) {
+                final boolean isOffBody = (event.values[0] == 0);
+                if (!mForceBodyState && mIsOffBody != isOffBody) {
+                    // Only consider the sensor value change when mForceBodyState is false, which
+                    // is used to enforce the mIsOffBody to be set by the adb shell command.
+                    mIsOffBody = isOffBody;
+                    onOffBodyChangedLocked();
+                }
+            }
+        }
+
+        @GuardedBy("DeviceIdleController.this")
+        public void onOffBodyChangedLocked() {
+            // Get into quick doze faster when the device is off body instead of taking
+            // traditional multi-stage approach.
+            updateQuickDozeFlagLocked();
+            if (!mIsOffBody && !mBatterySaverEnabled) {
+                mActiveReason = ACTIVE_REASON_ONBODY;
+                becomeActiveLocked("onbody", Process.myUid());
+            }
+        }
+
+        @Override
+        public void onAccuracyChanged(Sensor sensor, int accuracy) {}
+
+        public void registerLocked() {
+            mOffBodySensor =
+                    mSensorManager.getDefaultSensor(Sensor.TYPE_LOW_LATENCY_OFFBODY_DETECT, true);
+            if (mOffBodySensor == null) {
+                Slog.w(TAG, "Body sensor is NULL, unable to register mOffBodySensor.");
+                return;
+            }
+            mSensorManager.registerListener(this, mOffBodySensor,
+                    SensorManager.SENSOR_DELAY_NORMAL);
+        }
+    }
+
+    @VisibleForTesting
+    final LowLatencyOffBodyListener mLowLatencyOffBodyListener = new LowLatencyOffBodyListener();
+
     @VisibleForTesting
     final class MotionListener extends TriggerEventListener
             implements SensorEventListener {
@@ -1010,11 +1051,8 @@
          * exit doze. Default = true
          */
         private static final String KEY_WAIT_FOR_UNLOCK = "wait_for_unlock";
-        private static final String KEY_PRE_IDLE_FACTOR_LONG =
-                "pre_idle_factor_long";
-        private static final String KEY_PRE_IDLE_FACTOR_SHORT =
-                "pre_idle_factor_short";
         private static final String KEY_USE_WINDOW_ALARMS = "use_window_alarms";
+        private static final String KEY_USE_BODY_SENSOR = "use_body_sensor";
 
         private long mDefaultFlexTimeShort =
                 !COMPRESS_TIME ? 60 * 1000L : 5 * 1000L;
@@ -1073,9 +1111,8 @@
         private long mDefaultSmsTempAppAllowlistDurationMs = 20 * 1000L;
         private long mDefaultNotificationAllowlistDurationMs = 30 * 1000L;
         private boolean mDefaultWaitForUnlock = true;
-        private float mDefaultPreIdleFactorLong = 1.67f;
-        private float mDefaultPreIdleFactorShort = .33f;
         private boolean mDefaultUseWindowAlarms = true;
+        private boolean mDefaultUseBodySensor = false;
 
         /**
          * A somewhat short alarm window size that we will tolerate for various alarm timings.
@@ -1308,16 +1345,6 @@
          */
         public long NOTIFICATION_ALLOWLIST_DURATION_MS = mDefaultNotificationAllowlistDurationMs;
 
-        /**
-         * Pre idle time factor use to make idle delay longer
-         */
-        public float PRE_IDLE_FACTOR_LONG = mDefaultPreIdleFactorLong;
-
-        /**
-         * Pre idle time factor use to make idle delay shorter
-         */
-        public float PRE_IDLE_FACTOR_SHORT = mDefaultPreIdleFactorShort;
-
         public boolean WAIT_FOR_UNLOCK = mDefaultWaitForUnlock;
 
         /**
@@ -1326,6 +1353,11 @@
          */
         public boolean USE_WINDOW_ALARMS = mDefaultUseWindowAlarms;
 
+        /**
+         * Whether to use an on/off body signal to affect state transition policy.
+         */
+        public boolean USE_BODY_SENSOR = mDefaultUseBodySensor;
+
         private final boolean mSmallBatteryDevice;
 
         public Constants() {
@@ -1430,12 +1462,10 @@
                     com.android.internal.R.integer.device_idle_notification_allowlist_duration_ms);
             mDefaultWaitForUnlock = res.getBoolean(
                     com.android.internal.R.bool.device_idle_wait_for_unlock);
-            mDefaultPreIdleFactorLong = res.getFloat(
-                    com.android.internal.R.integer.device_idle_pre_idle_factor_long);
-            mDefaultPreIdleFactorShort = res.getFloat(
-                    com.android.internal.R.integer.device_idle_pre_idle_factor_short);
             mDefaultUseWindowAlarms = res.getBoolean(
                     com.android.internal.R.bool.device_idle_use_window_alarms);
+            mDefaultUseBodySensor = res.getBoolean(
+                    com.android.internal.R.bool.device_idle_use_body_sensor);
 
             FLEX_TIME_SHORT = mDefaultFlexTimeShort;
             LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT = mDefaultLightIdleAfterInactiveTimeout;
@@ -1468,9 +1498,8 @@
             SMS_TEMP_APP_ALLOWLIST_DURATION_MS = mDefaultSmsTempAppAllowlistDurationMs;
             NOTIFICATION_ALLOWLIST_DURATION_MS = mDefaultNotificationAllowlistDurationMs;
             WAIT_FOR_UNLOCK = mDefaultWaitForUnlock;
-            PRE_IDLE_FACTOR_LONG = mDefaultPreIdleFactorLong;
-            PRE_IDLE_FACTOR_SHORT = mDefaultPreIdleFactorShort;
             USE_WINDOW_ALARMS = mDefaultUseWindowAlarms;
+            USE_BODY_SENSOR = mDefaultUseBodySensor;
         }
 
         private long getTimeout(long defTimeout, long compTimeout) {
@@ -1628,18 +1657,14 @@
                             WAIT_FOR_UNLOCK = properties.getBoolean(
                                     KEY_WAIT_FOR_UNLOCK, mDefaultWaitForUnlock);
                             break;
-                        case KEY_PRE_IDLE_FACTOR_LONG:
-                            PRE_IDLE_FACTOR_LONG = properties.getFloat(
-                                    KEY_PRE_IDLE_FACTOR_LONG, mDefaultPreIdleFactorLong);
-                            break;
-                        case KEY_PRE_IDLE_FACTOR_SHORT:
-                            PRE_IDLE_FACTOR_SHORT = properties.getFloat(
-                                    KEY_PRE_IDLE_FACTOR_SHORT, mDefaultPreIdleFactorShort);
-                            break;
                         case KEY_USE_WINDOW_ALARMS:
                             USE_WINDOW_ALARMS = properties.getBoolean(
                                     KEY_USE_WINDOW_ALARMS, mDefaultUseWindowAlarms);
                             break;
+                        case KEY_USE_BODY_SENSOR:
+                            USE_BODY_SENSOR = properties.getBoolean(
+                                    KEY_USE_BODY_SENSOR, mDefaultUseBodySensor);
+                            break;
                         default:
                             Slog.e(TAG, "Unknown configuration key: " + name);
                             break;
@@ -1774,14 +1799,11 @@
             pw.print("    "); pw.print(KEY_WAIT_FOR_UNLOCK); pw.print("=");
             pw.println(WAIT_FOR_UNLOCK);
 
-            pw.print("    "); pw.print(KEY_PRE_IDLE_FACTOR_LONG); pw.print("=");
-            pw.println(PRE_IDLE_FACTOR_LONG);
-
-            pw.print("    "); pw.print(KEY_PRE_IDLE_FACTOR_SHORT); pw.print("=");
-            pw.println(PRE_IDLE_FACTOR_SHORT);
-
             pw.print("    "); pw.print(KEY_USE_WINDOW_ALARMS); pw.print("=");
             pw.println(USE_WINDOW_ALARMS);
+
+            pw.print("    "); pw.print(KEY_USE_BODY_SENSOR); pw.print("=");
+            pw.println(USE_BODY_SENSOR);
         }
     }
 
@@ -1824,10 +1846,6 @@
     static final int MSG_REPORT_STATIONARY_STATUS = 7;
     private static final int MSG_FINISH_IDLE_OP = 8;
     private static final int MSG_SEND_CONSTRAINT_MONITORING = 10;
-    @VisibleForTesting
-    static final int MSG_UPDATE_PRE_IDLE_TIMEOUT_FACTOR = 11;
-    @VisibleForTesting
-    static final int MSG_RESET_PRE_IDLE_TIMEOUT_FACTOR = 12;
     private static final int MSG_REPORT_TEMP_APP_WHITELIST_CHANGED = 13;
     private static final int MSG_REPORT_TEMP_APP_WHITELIST_ADDED_TO_NPMS = 14;
     private static final int MSG_REPORT_TEMP_APP_WHITELIST_REMOVED_TO_NPMS = 15;
@@ -1974,13 +1992,6 @@
                         constraint.stopMonitoring();
                     }
                 } break;
-                case MSG_UPDATE_PRE_IDLE_TIMEOUT_FACTOR: {
-                    updatePreIdleFactor();
-                } break;
-                case MSG_RESET_PRE_IDLE_TIMEOUT_FACTOR: {
-                    updatePreIdleFactor();
-                    maybeDoImmediateMaintenance("idle factor");
-                } break;
                 case MSG_REPORT_STATIONARY_STATUS: {
                     final DeviceIdleInternal.StationaryListener newListener =
                             (DeviceIdleInternal.StationaryListener) msg.obj;
@@ -2178,9 +2189,9 @@
             return durationMs;
         }
 
+        @EnforcePermission(android.Manifest.permission.DEVICE_POWER)
         @Override public void exitIdle(String reason) {
-            getContext().enforceCallingOrSelfPermission(Manifest.permission.DEVICE_POWER,
-                    null);
+            exitIdle_enforcePermission();
             final long ident = Binder.clearCallingIdentity();
             try {
                 exitIdleInternal(reason);
@@ -2189,28 +2200,6 @@
             }
         }
 
-        @Override public int setPreIdleTimeoutMode(int mode) {
-            getContext().enforceCallingOrSelfPermission(Manifest.permission.DEVICE_POWER,
-                    null);
-            final long ident = Binder.clearCallingIdentity();
-            try {
-                return DeviceIdleController.this.setPreIdleTimeoutMode(mode);
-            } finally {
-                Binder.restoreCallingIdentity(ident);
-            }
-        }
-
-        @Override public void resetPreIdleTimeoutMode() {
-            getContext().enforceCallingOrSelfPermission(Manifest.permission.DEVICE_POWER,
-                    null);
-            final long ident = Binder.clearCallingIdentity();
-            try {
-                DeviceIdleController.this.resetPreIdleTimeoutMode();
-            } finally {
-                Binder.restoreCallingIdentity(ident);
-            }
-        }
-
         @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
             DeviceIdleController.this.dump(fd, pw, args);
         }
@@ -2579,8 +2568,6 @@
             moveToStateLocked(STATE_ACTIVE, "boot");
             moveToLightStateLocked(LIGHT_STATE_ACTIVE, "boot");
             mInactiveTimeout = mConstants.INACTIVE_TIMEOUT;
-            mPreIdleFactor = 1.0f;
-            mLastPreIdleFactor = 1.0f;
         }
 
         mBinderService = new BinderService();
@@ -2681,15 +2668,19 @@
                         mPowerSaveWhitelistAllAppIdArray, mPowerSaveWhitelistExceptIdleAppIdArray);
                 mLocalPowerManager.setDeviceIdleWhitelist(mPowerSaveWhitelistAllAppIdArray);
 
+                if (mConstants.USE_BODY_SENSOR) {
+                    mLowLatencyOffBodyListener.registerLocked();
+                }
                 mLocalPowerManager.registerLowPowerModeObserver(ServiceType.QUICK_DOZE,
                         state -> {
                             synchronized (DeviceIdleController.this) {
-                                updateQuickDozeFlagLocked(state.batterySaverEnabled);
+                                mBatterySaverEnabled = state.batterySaverEnabled;
+                                updateQuickDozeFlagLocked();
                             }
                         });
-                updateQuickDozeFlagLocked(
-                        mLocalPowerManager.getLowPowerState(
-                                ServiceType.QUICK_DOZE).batterySaverEnabled);
+                mBatterySaverEnabled = mLocalPowerManager.getLowPowerState(
+                        ServiceType.QUICK_DOZE).batterySaverEnabled;
+                updateQuickDozeFlagLocked();
 
                 mLocalActivityTaskManager.registerScreenObserver(mScreenObserver);
 
@@ -3380,6 +3371,17 @@
         }
     }
 
+    /** Calls to {@link #updateQuickDozeFlagLocked(boolean)} by considering appropriate signals. */
+    @GuardedBy("this")
+    private void updateQuickDozeFlagLocked() {
+        if (mConstants.USE_BODY_SENSOR) {
+            // Only disable the quick doze flag when the device is on body and battery saver is off.
+            updateQuickDozeFlagLocked(mIsOffBody || mBatterySaverEnabled);
+        } else {
+            updateQuickDozeFlagLocked(mBatterySaverEnabled);
+        }
+    }
+
     /** Updates the quick doze flag and enters deep doze if appropriate. */
     @VisibleForTesting
     @GuardedBy("this")
@@ -3543,9 +3545,6 @@
                 moveToStateLocked(STATE_INACTIVE, "no activity");
                 resetIdleManagementLocked();
                 long delay = mInactiveTimeout;
-                if (shouldUseIdleTimeoutFactorLocked()) {
-                    delay = (long) (mPreIdleFactor * delay);
-                }
                 if (isUpcomingAlarmClock()) {
                     // If there's an upcoming AlarmClock alarm, we won't go into idle, so
                     // setting a wakeup alarm before the upcoming alarm is futile. Set the idle
@@ -3570,7 +3569,6 @@
     private void resetIdleManagementLocked() {
         mNextIdlePendingDelay = 0;
         mNextIdleDelay = 0;
-        mIdleStartTime = 0;
         mQuickDozeActivatedWhileIdling = false;
         cancelAlarmLocked();
         cancelSensingTimeoutAlarmLocked();
@@ -3766,9 +3764,6 @@
                 // for motion and sleep some more while doing so.
                 startMonitoringMotionLocked();
                 long delay = mConstants.IDLE_AFTER_INACTIVE_TIMEOUT;
-                if (shouldUseIdleTimeoutFactorLocked()) {
-                    delay = (long) (mPreIdleFactor * delay);
-                }
                 scheduleAlarmLocked(delay);
                 moveToStateLocked(STATE_IDLE_PENDING, reason);
                 break;
@@ -3848,7 +3843,6 @@
                         " ms.");
                 mNextIdleDelay = (long)(mNextIdleDelay * mConstants.IDLE_FACTOR);
                 if (DEBUG) Slog.d(TAG, "Setting mNextIdleDelay = " + mNextIdleDelay);
-                mIdleStartTime = SystemClock.elapsedRealtime();
                 mNextIdleDelay = Math.min(mNextIdleDelay, mConstants.MAX_IDLE_TIMEOUT);
                 if (mNextIdleDelay < mConstants.IDLE_TIMEOUT) {
                     mNextIdleDelay = mConstants.IDLE_TIMEOUT;
@@ -3949,130 +3943,6 @@
     }
 
     @VisibleForTesting
-    int setPreIdleTimeoutMode(int mode) {
-        return setPreIdleTimeoutFactor(getPreIdleTimeoutByMode(mode));
-    }
-
-    @VisibleForTesting
-    float getPreIdleTimeoutByMode(int mode) {
-        switch (mode) {
-            case PowerManager.PRE_IDLE_TIMEOUT_MODE_LONG: {
-                return mConstants.PRE_IDLE_FACTOR_LONG;
-            }
-            case PowerManager.PRE_IDLE_TIMEOUT_MODE_SHORT: {
-                return mConstants.PRE_IDLE_FACTOR_SHORT;
-            }
-            case PowerManager.PRE_IDLE_TIMEOUT_MODE_NORMAL: {
-                return 1.0f;
-            }
-            default: {
-                Slog.w(TAG, "Invalid time out factor mode: " + mode);
-                return 1.0f;
-            }
-        }
-    }
-
-    @VisibleForTesting
-    float getPreIdleTimeoutFactor() {
-        synchronized (this) {
-            return mPreIdleFactor;
-        }
-    }
-
-    @VisibleForTesting
-    int setPreIdleTimeoutFactor(float ratio) {
-        synchronized (this) {
-            if (!mDeepEnabled) {
-                if (DEBUG) Slog.d(TAG, "setPreIdleTimeoutFactor: Deep Idle disable");
-                return SET_IDLE_FACTOR_RESULT_NOT_SUPPORT;
-            } else if (ratio <= MIN_PRE_IDLE_FACTOR_CHANGE) {
-                if (DEBUG) Slog.d(TAG, "setPreIdleTimeoutFactor: Invalid input");
-                return SET_IDLE_FACTOR_RESULT_INVALID;
-            } else if (Math.abs(ratio - mPreIdleFactor) < MIN_PRE_IDLE_FACTOR_CHANGE) {
-                if (DEBUG) {
-                    Slog.d(TAG, "setPreIdleTimeoutFactor: New factor same as previous factor");
-                }
-                return SET_IDLE_FACTOR_RESULT_IGNORED;
-            }
-            mLastPreIdleFactor = mPreIdleFactor;
-            mPreIdleFactor = ratio;
-        }
-        if (DEBUG) Slog.d(TAG, "setPreIdleTimeoutFactor: " + ratio);
-        postUpdatePreIdleFactor();
-        return SET_IDLE_FACTOR_RESULT_OK;
-    }
-
-    @VisibleForTesting
-    void resetPreIdleTimeoutMode() {
-        synchronized (this) {
-            mLastPreIdleFactor = mPreIdleFactor;
-            mPreIdleFactor = 1.0f;
-        }
-        if (DEBUG) Slog.d(TAG, "resetPreIdleTimeoutMode to 1.0");
-        postResetPreIdleTimeoutFactor();
-    }
-
-    private void postUpdatePreIdleFactor() {
-        mHandler.sendEmptyMessage(MSG_UPDATE_PRE_IDLE_TIMEOUT_FACTOR);
-    }
-
-    private void postResetPreIdleTimeoutFactor() {
-        mHandler.sendEmptyMessage(MSG_RESET_PRE_IDLE_TIMEOUT_FACTOR);
-    }
-
-    private void updatePreIdleFactor() {
-        synchronized (this) {
-            if (!shouldUseIdleTimeoutFactorLocked()) {
-                return;
-            }
-            if (mState == STATE_INACTIVE || mState == STATE_IDLE_PENDING) {
-                if (mNextAlarmTime == 0) {
-                    return;
-                }
-                long delay = mNextAlarmTime - SystemClock.elapsedRealtime();
-                if (delay < MIN_STATE_STEP_ALARM_CHANGE) {
-                    return;
-                }
-                long newDelay = (long) (delay / mLastPreIdleFactor * mPreIdleFactor);
-                if (Math.abs(delay - newDelay) < MIN_STATE_STEP_ALARM_CHANGE) {
-                    return;
-                }
-                scheduleAlarmLocked(newDelay);
-            }
-        }
-    }
-
-    private void maybeDoImmediateMaintenance(String reason) {
-        synchronized (this) {
-            if (mState == STATE_IDLE) {
-                long duration = SystemClock.elapsedRealtime() - mIdleStartTime;
-                // Trigger an immediate maintenance window if it has been IDLE for long enough.
-                if (duration > mConstants.IDLE_TIMEOUT) {
-                    stepIdleStateLocked(reason);
-                }
-            }
-        }
-    }
-
-    @GuardedBy("this")
-    private boolean shouldUseIdleTimeoutFactorLocked() {
-        // exclude ACTIVE_REASON_MOTION, for exclude device in pocket case
-        if (mActiveReason == ACTIVE_REASON_MOTION) {
-            return false;
-        }
-        return true;
-    }
-
-    /** Must only be used in tests. */
-    @VisibleForTesting
-    void setIdleStartTimeForTest(long idleStartTime) {
-        synchronized (this) {
-            mIdleStartTime = idleStartTime;
-            maybeDoImmediateMaintenance("testing");
-        }
-    }
-
-    @VisibleForTesting
     long getNextAlarmTime() {
         synchronized (this) {
             return mNextAlarmTime;
@@ -4610,8 +4480,10 @@
         pw.println("  force-inactive");
         pw.println("    Force to be inactive, ready to freely step idle states.");
         pw.println("  unforce");
-        pw.println("    Resume normal functioning after force-idle or force-inactive.");
-        pw.println("  get [light|deep|force|screen|charging|network]");
+        pw.println(
+                "    Resume normal functioning after force-idle or force-inactive or "
+                        + "force-offbody or force-onbody.");
+        pw.println("  get [light|deep|force|screen|charging|network|offbody|forcebodystate]");
         pw.println("    Retrieve the current given state.");
         pw.println("  disable [light|deep|all]");
         pw.println("    Completely disable device idle mode.");
@@ -4645,11 +4517,14 @@
                 + "and any [-d] is ignored");
         pw.println("  motion");
         pw.println("    Simulate a motion event to bring the device out of deep doze");
-        pw.println("  pre-idle-factor [0|1|2]");
-        pw.println("    Set a new factor to idle time before step to idle"
-                + "(inactive_to and idle_after_inactive_to)");
-        pw.println("  reset-pre-idle-factor");
-        pw.println("    Reset factor to idle time to default");
+        pw.println("  force-offbody");
+        pw.println(
+                "    Simulate a low latency body sensor detecting a device is offbody. "
+                        + "mForceBodyState will be set to true to ignore body sensor reading.");
+        pw.println("  force-onbody");
+        pw.println(
+                "    Simulate a low latency body sensor detecting a device is onbody. "
+                        + "mForceBodyState will be set to true to ignore body sensor reading.");
     }
 
     class Shell extends ShellCommand {
@@ -4781,6 +4656,8 @@
                     pw.print(lightStateToString(mLightState));
                     pw.print(", deep state: ");
                     pw.println(stateToString(mState));
+                    mForceBodyState = false;
+                    pw.println("mForceBodyState: " + mForceBodyState);
                 } finally {
                     Binder.restoreCallingIdentity(token);
                 }
@@ -4801,6 +4678,8 @@
                             case "screen": pw.println(mScreenOn); break;
                             case "charging": pw.println(mCharging); break;
                             case "network": pw.println(mNetworkConnected); break;
+                            case "offbody": pw.println(mIsOffBody); break;
+                            case "forcebodystate": pw.println(mForceBodyState); break;
                             default: pw.println("Unknown get option: " + arg); break;
                         }
                     } finally {
@@ -5097,48 +4976,32 @@
                     Binder.restoreCallingIdentity(token);
                 }
             }
-        } else if ("pre-idle-factor".equals(cmd)) {
+        } else if ("force-offbody".equals(cmd)) {
             getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER,
                     null);
-            synchronized (this) {
+            synchronized (DeviceIdleController.this) {
                 final long token = Binder.clearCallingIdentity();
-                int ret  = SET_IDLE_FACTOR_RESULT_UNINIT;
                 try {
-                    String arg = shell.getNextArg();
-                    boolean valid = false;
-                    int mode = 0;
-                    if (arg != null) {
-                        mode = Integer.parseInt(arg);
-                        ret = setPreIdleTimeoutMode(mode);
-                        if (ret == SET_IDLE_FACTOR_RESULT_OK) {
-                            pw.println("pre-idle-factor: " + mode);
-                            valid = true;
-                        } else if (ret == SET_IDLE_FACTOR_RESULT_NOT_SUPPORT) {
-                            valid = true;
-                            pw.println("Deep idle not supported");
-                        } else if (ret == SET_IDLE_FACTOR_RESULT_IGNORED) {
-                            valid = true;
-                            pw.println("Idle timeout factor not changed");
-                        }
-                    }
-                    if (!valid) {
-                        pw.println("Unknown idle timeout factor: " + arg
-                                + ",(error code: " + ret + ")");
-                    }
-                } catch (NumberFormatException e) {
-                    pw.println("Unknown idle timeout factor"
-                            + ",(error code: " + ret + ")");
+                    mForceBodyState = true;
+                    pw.println("mForceBodyState: " + mForceBodyState);
+                    mIsOffBody = true;
+                    pw.println("mIsOffBody: " + mIsOffBody);
+                    mLowLatencyOffBodyListener.onOffBodyChangedLocked();
                 } finally {
                     Binder.restoreCallingIdentity(token);
                 }
             }
-        } else if ("reset-pre-idle-factor".equals(cmd)) {
+        } else if ("force-onbody".equals(cmd)) {
             getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER,
                     null);
-            synchronized (this) {
+            synchronized (DeviceIdleController.this) {
                 final long token = Binder.clearCallingIdentity();
                 try {
-                    resetPreIdleTimeoutMode();
+                    mForceBodyState = true;
+                    pw.println("mForceBodyState: " + mForceBodyState);
+                    mIsOffBody = false;
+                    pw.println("mIsOffBody: " + mIsOffBody);
+                    mLowLatencyOffBodyListener.onOffBodyChangedLocked();
                 } finally {
                     Binder.restoreCallingIdentity(token);
                 }
@@ -5394,9 +5257,6 @@
             if (mAlarmsActive) {
                 pw.print("  mAlarmsActive="); pw.println(mAlarmsActive);
             }
-            if (Math.abs(mPreIdleFactor - 1.0f) > MIN_PRE_IDLE_FACTOR_CHANGE) {
-                pw.print("  mPreIdleFactor="); pw.println(mPreIdleFactor);
-            }
         }
     }
 
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 4d646de..a720baf 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/Alarm.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/Alarm.java
@@ -21,6 +21,7 @@
 import static android.app.AlarmManager.RTC;
 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 android.app.AlarmManager;
@@ -128,8 +129,9 @@
     /** The ultimate delivery time to be used for this alarm */
     private long mWhenElapsed;
     private long mMaxWhenElapsed;
-    public int mExactAllowReason;
-    public AlarmManagerService.PriorityClass priorityClass;
+    public int exactAllowReason;
+    @AlarmManagerService.DispatchPriority
+    public int priorityClass;
     /** Broadcast options to use when delivering this alarm */
     public Bundle mIdleOptions;
     public boolean mUsingReserveQuota;
@@ -158,10 +160,11 @@
         this.uid = uid;
         packageName = pkgName;
         mIdleOptions = idleOptions;
-        mExactAllowReason = exactAllowReason;
+        this.exactAllowReason = exactAllowReason;
         sourcePackage = (operation != null) ? operation.getCreatorPackage() : packageName;
         creatorUid = (operation != null) ? operation.getCreatorUid() : this.uid;
         mUsingReserveQuota = false;
+        priorityClass = PRIORITY_NORMAL;
     }
 
     public static String makeTag(PendingIntent pi, String tag, int type) {
@@ -333,9 +336,9 @@
         }
         ipw.print(" window=");
         TimeUtils.formatDuration(windowLength, ipw);
-        if (mExactAllowReason != EXACT_ALLOW_REASON_NOT_APPLICABLE) {
+        if (exactAllowReason != EXACT_ALLOW_REASON_NOT_APPLICABLE) {
             ipw.print(" exactAllowReason=");
-            ipw.print(exactReasonToString(mExactAllowReason));
+            ipw.print(exactReasonToString(exactAllowReason));
         }
         ipw.print(" repeatInterval=");
         ipw.print(repeatInterval);
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 df1b666..2557c6a 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -45,7 +45,6 @@
 
 import static com.android.server.SystemClockTime.TIME_CONFIDENCE_HIGH;
 import static com.android.server.SystemTimeZone.TIME_ZONE_CONFIDENCE_HIGH;
-import static com.android.server.SystemTimeZone.getTimeZoneId;
 import static com.android.server.alarm.Alarm.APP_STANDBY_POLICY_INDEX;
 import static com.android.server.alarm.Alarm.BATTERY_SAVER_POLICY_INDEX;
 import static com.android.server.alarm.Alarm.DEVICE_IDLE_POLICY_INDEX;
@@ -70,7 +69,10 @@
 import android.Manifest;
 import android.annotation.CurrentTimeMillisLong;
 import android.annotation.ElapsedRealtimeLong;
+import android.annotation.EnforcePermission;
+import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.SuppressLint;
 import android.annotation.UserIdInt;
 import android.app.Activity;
 import android.app.ActivityManagerInternal;
@@ -139,7 +141,6 @@
 import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.Keep;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.IAppOpsCallback;
 import com.android.internal.app.IAppOpsService;
@@ -176,6 +177,8 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -183,7 +186,6 @@
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.Date;
-import java.util.HashMap;
 import java.util.Locale;
 import java.util.Set;
 import java.util.TimeZone;
@@ -229,19 +231,6 @@
 
     private static final long TEMPORARY_QUOTA_DURATION = INTERVAL_DAY;
 
-    /*
-     * b/246256335: This compile-time constant controls whether Android attempts to sync the Kernel
-     * time zone offset via settimeofday(null, tz). For <= Android T behavior is the same as
-     * {@code true}, the state for future releases is the same as {@code false}.
-     * It is unlikely anything depends on this, but a compile-time constant has been used to limit
-     * the size of the revert if this proves to be invorrect. The guarded code and associated
-     * methods / native code can be removed after release testing has proved that removing the
-     * behavior doesn't break anything.
-     * TODO(b/246256335): After this change has soaked for a release, remove this constant and
-     * everything it affects.
-     */
-    private static final boolean KERNEL_TIME_ZONE_SYNC_ENABLED = false;
-
     private final Intent mBackgroundIntent
             = new Intent().addFlags(Intent.FLAG_FROM_BACKGROUND);
 
@@ -744,9 +733,6 @@
         @VisibleForTesting
         static final String KEY_MAX_DEVICE_IDLE_FUZZ = "max_device_idle_fuzz";
         @VisibleForTesting
-        static final String KEY_KILL_ON_SCHEDULE_EXACT_ALARM_REVOKED =
-                "kill_on_schedule_exact_alarm_revoked";
-        @VisibleForTesting
         static final String KEY_TEMPORARY_QUOTA_BUMP = "temporary_quota_bump";
         @VisibleForTesting
         static final String KEY_CACHED_LISTENER_REMOVAL_DELAY = "cached_listener_removal_delay";
@@ -789,8 +775,6 @@
         private static final long DEFAULT_MIN_DEVICE_IDLE_FUZZ = 2 * 60_000;
         private static final long DEFAULT_MAX_DEVICE_IDLE_FUZZ = 15 * 60_000;
 
-        private static final boolean DEFAULT_KILL_ON_SCHEDULE_EXACT_ALARM_REVOKED = true;
-
         private static final int DEFAULT_TEMPORARY_QUOTA_BUMP = 0;
 
         private static final boolean DEFAULT_DELAY_NONWAKEUP_ALARMS_WHILE_SCREEN_OFF = true;
@@ -869,13 +853,6 @@
          */
         public long MAX_DEVICE_IDLE_FUZZ = DEFAULT_MAX_DEVICE_IDLE_FUZZ;
 
-        /**
-         * Whether or not to kill app when the permission
-         * {@link Manifest.permission#SCHEDULE_EXACT_ALARM} is revoked.
-         */
-        public boolean KILL_ON_SCHEDULE_EXACT_ALARM_REVOKED =
-                DEFAULT_KILL_ON_SCHEDULE_EXACT_ALARM_REVOKED;
-
         public int USE_TARE_POLICY = EconomyManager.DEFAULT_ENABLE_POLICY_ALARM
                 ? EconomyManager.DEFAULT_ENABLE_TARE_MODE : EconomyManager.ENABLED_MODE_OFF;
 
@@ -927,6 +904,7 @@
                     economyManagerInternal.getEnabledMode(AlarmManagerEconomicPolicy.POLICY_ALARM));
         }
 
+        @SuppressLint("MissingPermission")
         public void updateAllowWhileIdleWhitelistDurationLocked() {
             if (mLastAllowWhileIdleWhitelistDuration != ALLOW_WHILE_IDLE_WHITELIST_DURATION) {
                 mLastAllowWhileIdleWhitelistDuration = ALLOW_WHILE_IDLE_WHITELIST_DURATION;
@@ -1069,11 +1047,6 @@
                                 deviceIdleFuzzBoundariesUpdated = true;
                             }
                             break;
-                        case KEY_KILL_ON_SCHEDULE_EXACT_ALARM_REVOKED:
-                            KILL_ON_SCHEDULE_EXACT_ALARM_REVOKED = properties.getBoolean(
-                                    KEY_KILL_ON_SCHEDULE_EXACT_ALARM_REVOKED,
-                                    DEFAULT_KILL_ON_SCHEDULE_EXACT_ALARM_REVOKED);
-                            break;
                         case KEY_TEMPORARY_QUOTA_BUMP:
                             TEMPORARY_QUOTA_BUMP = properties.getInt(KEY_TEMPORARY_QUOTA_BUMP,
                                     DEFAULT_TEMPORARY_QUOTA_BUMP);
@@ -1317,10 +1290,6 @@
             TimeUtils.formatDuration(MAX_DEVICE_IDLE_FUZZ, pw);
             pw.println();
 
-            pw.print(KEY_KILL_ON_SCHEDULE_EXACT_ALARM_REVOKED,
-                    KILL_ON_SCHEDULE_EXACT_ALARM_REVOKED);
-            pw.println();
-
             pw.print(Settings.Global.ENABLE_TARE,
                     EconomyManager.enabledModeToString(USE_TARE_POLICY));
             pw.println();
@@ -1356,87 +1325,91 @@
 
     Constants mConstants;
 
-    // Alarm delivery ordering bookkeeping
-    static final int PRIO_TICK = 0;
-    static final int PRIO_WAKEUP = 1;
-    static final int PRIO_NORMAL = 2;
+    /** Dispatch priority to use for system alarms. */
+    static final int PRIORITY_SYSTEM = 0;
+    /** Dispatch priority to use for a package that has any wakeup alarm in the pending batch. */
+    static final int PRIORITY_WAKEUP = 1;
+    /** The default dispatch priority to use when no special conditions apply. */
+    static final int PRIORITY_NORMAL = 2;
 
-    final class PriorityClass {
-        int seq;
-        int priority;
+    /**
+     * Priority to assign to alarms in an outgoing batch. Higher priority alarms are considered more
+     * urgent than lower priority ones (smaller value is higher priority). Priorities are assigned
+     * per package, so all alarms in one package will share the same priority in an outgoing batch -
+     * which should be the highest (minimum) priority computed over all its alarms.
+     * This is done to ensure that alarms in the same package preserve their relative ordering.
+     */
+    @IntDef(prefix = "PRIORITY_", value = {
+            PRIORITY_SYSTEM,
+            PRIORITY_WAKEUP,
+            PRIORITY_NORMAL,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface DispatchPriority {}
 
-        PriorityClass() {
-            seq = mCurrentSeq - 1;
-            priority = PRIO_NORMAL;
+    final Comparator<Alarm> mAlarmDispatchComparator = (lhs, rhs) -> {
+
+        // Alarm to exit device_idle should go out first.
+        final boolean idleUntil1 = (lhs.flags & FLAG_IDLE_UNTIL) != 0;
+        final boolean idleUntil2 = (rhs.flags & FLAG_IDLE_UNTIL) != 0;
+        if (idleUntil1 != idleUntil2) {
+            return idleUntil1 ? -1 : 1;
         }
-    }
 
-    final HashMap<String, PriorityClass> mPriorities = new HashMap<>();
-    int mCurrentSeq = 0;
-
-    final Comparator<Alarm> mAlarmDispatchComparator = new Comparator<Alarm>() {
-        @Override
-        public int compare(Alarm lhs, Alarm rhs) {
-
-            // Alarm to exit device_idle should go out first.
-            final boolean lhsIdleUntil = (lhs.flags & FLAG_IDLE_UNTIL) != 0;
-            final boolean rhsIdleUntil = (rhs.flags & FLAG_IDLE_UNTIL) != 0;
-            if (lhsIdleUntil != rhsIdleUntil) {
-                return lhsIdleUntil ? -1 : 1;
-            }
-
-            // Then, priority class trumps everything.  TICK < WAKEUP < NORMAL
-            if (lhs.priorityClass.priority < rhs.priorityClass.priority) {
-                return -1;
-            } else if (lhs.priorityClass.priority > rhs.priorityClass.priority) {
-                return 1;
-            }
-
-            // within each class, sort by requested delivery time
-            if (lhs.getRequestedElapsed() < rhs.getRequestedElapsed()) {
-                return -1;
-            } else if (lhs.getRequestedElapsed() > rhs.getRequestedElapsed()) {
-                return 1;
-            }
-
-            return 0;
+        // Then, priority class trumps everything.  SYSTEM < WAKEUP < NORMAL
+        if (lhs.priorityClass < rhs.priorityClass) {
+            return -1;
+        } else if (lhs.priorityClass > rhs.priorityClass) {
+            return 1;
         }
+
+        // Within all system alarms, pulling time_tick to the front, as it is time-sensitive.
+        final boolean timeTick1 = (lhs.listener == mTimeTickTrigger);
+        final boolean timeTick2 = (rhs.listener == mTimeTickTrigger);
+        if (timeTick1 != timeTick2) {
+            return timeTick1 ? -1 : 1;
+        }
+
+        // Within each class, sort by requested delivery time
+        if (lhs.getRequestedElapsed() < rhs.getRequestedElapsed()) {
+            return -1;
+        } else if (lhs.getRequestedElapsed() > rhs.getRequestedElapsed()) {
+            return 1;
+        }
+
+        return 0;
     };
 
+    /**
+     * Assigns a {@link DispatchPriority} to each alarm that will be used to order alarms in an
+     * outgoing batch.
+     *
+     * @param alarms The batch of alarms about to be sent.
+     */
     void calculateDeliveryPriorities(ArrayList<Alarm> alarms) {
         final int N = alarms.size();
+
+        // Batches are expected to be small (generally N < 10). So an additional iteration to
+        // collect a small set of UserPackage objects should be unnoticeable.
+        final ArraySet<UserPackage> wakeupPackages = new ArraySet<>(4);
         for (int i = 0; i < N; i++) {
-            Alarm a = alarms.get(i);
-
-            final int alarmPrio;
-            if (a.listener == mTimeTickTrigger) {
-                alarmPrio = PRIO_TICK;
-            } else if (a.wakeup) {
-                alarmPrio = PRIO_WAKEUP;
-            } else {
-                alarmPrio = PRIO_NORMAL;
+            final Alarm a = alarms.get(i);
+            if (a.wakeup) {
+                wakeupPackages.add(
+                        UserPackage.of(UserHandle.getUserId(a.creatorUid), a.sourcePackage));
             }
+        }
 
-            PriorityClass packagePrio = a.priorityClass;
-            String alarmPackage = a.sourcePackage;
-            if (packagePrio == null) packagePrio = mPriorities.get(alarmPackage);
-            if (packagePrio == null) {
-                packagePrio = a.priorityClass = new PriorityClass(); // lowest prio & stale sequence
-                mPriorities.put(alarmPackage, packagePrio);
-            }
-            a.priorityClass = packagePrio;
+        for (int i = 0; i < N; i++) {
+            final Alarm a = alarms.get(i);
 
-            if (packagePrio.seq != mCurrentSeq) {
-                // first alarm we've seen in the current delivery generation from this package
-                packagePrio.priority = alarmPrio;
-                packagePrio.seq = mCurrentSeq;
+            if (a.creatorUid == Process.SYSTEM_UID && "android".equals(a.sourcePackage)) {
+                a.priorityClass = PRIORITY_SYSTEM;
+            } else if (wakeupPackages.contains(
+                    UserPackage.of(UserHandle.getUserId(a.creatorUid), a.sourcePackage))) {
+                a.priorityClass = PRIORITY_WAKEUP;
             } else {
-                // Multiple alarms from this package being delivered in this generation;
-                // bump the package's delivery class if it's warranted.
-                // TICK < WAKEUP < NORMAL
-                if (alarmPrio < packagePrio.priority) {
-                    packagePrio.priority = alarmPrio;
-                }
+                a.priorityClass = PRIORITY_NORMAL;
             }
         }
     }
@@ -1741,6 +1714,7 @@
         final BroadcastStats mBroadcastStats;
         final FilterStats mFilterStats;
         final int mAlarmType;
+        final int mPriorityClass;
 
         InFlight(AlarmManagerService service, Alarm alarm, long nowELAPSED) {
             mPendingIntent = alarm.operation;
@@ -1761,6 +1735,7 @@
             fs.lastTime = nowELAPSED;
             mFilterStats = fs;
             mAlarmType = alarm.type;
+            mPriorityClass = alarm.priorityClass;
         }
 
         boolean isBroadcast() {
@@ -1779,6 +1754,7 @@
                     + ", broadcastStats=" + mBroadcastStats
                     + ", filterStats=" + mFilterStats
                     + ", alarmType=" + mAlarmType
+                    + ", priorityClass=" + mPriorityClass
                     + "}";
         }
 
@@ -1925,6 +1901,7 @@
         mActivityOptsRestrictBal.setPendingIntentBackgroundActivityLaunchAllowed(false);
         mBroadcastOptsRestrictBal.setPendingIntentBackgroundActivityLaunchAllowed(false);
         mMetricsHelper = new MetricsHelper(getContext(), mLock);
+        mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
 
         mListenerDeathRecipient = new IBinder.DeathRecipient() {
             @Override
@@ -1955,13 +1932,6 @@
 
             mNextWakeup = mNextNonWakeup = 0;
 
-            if (KERNEL_TIME_ZONE_SYNC_ENABLED) {
-                // We set the current offset in kernel because the kernel doesn't keep this after a
-                // reboot. Keeping the kernel time zone in sync is "best effort" and can be wrong
-                // for a period after daylight savings transitions.
-                mInjector.syncKernelTimeZoneOffset();
-            }
-
             // Ensure that we're booting with a halfway sensible current time.
             mInjector.initializeTimeIfRequired();
 
@@ -2024,7 +1994,6 @@
                 Slog.w(TAG, "Failed to open alarm driver. Falling back to a handler.");
             }
         }
-        mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
         publishLocalService(AlarmManagerInternal.class, new LocalService());
         publishBinderService(Context.ALARM_SERVICE, mService);
     }
@@ -2196,20 +2165,8 @@
             @CurrentTimeMillisLong long newSystemClockTimeMillis, @TimeConfidence int confidence,
             @NonNull String logMsg) {
         synchronized (mLock) {
-            final long oldSystemClockTimeMillis = mInjector.getCurrentTimeMillis();
             mInjector.setCurrentTimeMillis(newSystemClockTimeMillis, confidence, logMsg);
 
-            if (KERNEL_TIME_ZONE_SYNC_ENABLED) {
-                // Changing the time may cross a DST transition; sync the kernel offset if needed.
-                final TimeZone timeZone = TimeZone.getTimeZone(SystemTimeZone.getTimeZoneId());
-                final int currentTzOffset = timeZone.getOffset(oldSystemClockTimeMillis);
-                final int newTzOffset = timeZone.getOffset(newSystemClockTimeMillis);
-                if (currentTzOffset != newTzOffset) {
-                    Slog.i(TAG, "Timezone offset has changed, updating kernel timezone");
-                    mInjector.setKernelTimeZoneOffset(newTzOffset);
-                }
-            }
-
             // The native implementation of setKernelTime can return -1 even when the kernel
             // time was set correctly, so assume setting kernel time was successful and always
             // return true.
@@ -2231,12 +2188,6 @@
             // "GMT" if the ID is unrecognized). The parameter ID is used here rather than
             // newZone.getId(). It will be rejected if it is invalid.
             timeZoneWasChanged = SystemTimeZone.setTimeZoneId(tzId, confidence, logInfo);
-
-            if (KERNEL_TIME_ZONE_SYNC_ENABLED) {
-                // Update the kernel timezone information
-                int utcOffsetMillis = newZone.getOffset(mInjector.getCurrentTimeMillis());
-                mInjector.setKernelTimeZoneOffset(utcOffsetMillis);
-            }
         }
 
         // Clear the default time zone in the system server process. This forces the next call
@@ -3040,11 +2991,10 @@
             return (uid > 0) ? hasScheduleExactAlarmInternal(packageName, uid) : false;
         }
 
+        @EnforcePermission(android.Manifest.permission.SET_TIME)
         @Override
         public boolean setTime(@CurrentTimeMillisLong long millis) {
-            getContext().enforceCallingOrSelfPermission(
-                    "android.permission.SET_TIME",
-                    "setTime");
+            setTime_enforcePermission();
 
             // The public API (and the shell command that also uses this method) have no concept
             // of confidence, but since the time should come either from apps working on behalf of
@@ -3053,11 +3003,10 @@
             return setTimeImpl(millis, timeConfidence, "AlarmManager.setTime() called");
         }
 
+        @EnforcePermission(android.Manifest.permission.SET_TIME_ZONE)
         @Override
         public void setTimeZone(String tz) {
-            getContext().enforceCallingOrSelfPermission(
-                    "android.permission.SET_TIME_ZONE",
-                    "setTimeZone");
+            setTimeZone_enforcePermission();
 
             final long oldId = Binder.clearCallingIdentity();
             try {
@@ -3115,10 +3064,10 @@
             return getNextAlarmClockImpl(userId);
         }
 
+        @EnforcePermission(android.Manifest.permission.DUMP)
         @Override
         public int getConfigVersion() {
-            getContext().enforceCallingOrSelfPermission(Manifest.permission.DUMP,
-                    "getConfigVersion");
+            getConfigVersion_enforcePermission();
             return mConstants.getVersion();
         }
 
@@ -4121,7 +4070,7 @@
             removeAlarmsInternalLocked(whichAlarms, REMOVE_REASON_EXACT_PERMISSION_REVOKED);
         }
 
-        if (killUid && mConstants.KILL_ON_SCHEDULE_EXACT_ALARM_REVOKED) {
+        if (killUid) {
             PermissionManagerService.killUid(UserHandle.getAppId(uid), UserHandle.getUserId(uid),
                     "schedule_exact_alarm revoked");
         }
@@ -4386,16 +4335,6 @@
     private static native void close(long nativeData);
     private static native int set(long nativeData, int type, long seconds, long nanoseconds);
     private static native int waitForAlarm(long nativeData);
-
-    /*
-     * b/246256335: The @Keep ensures that the native definition is kept even when the optimizer can
-     * tell no calls will be made due to a compile-time constant. Allowing this definition to be
-     * optimized away breaks loadLibrary("alarm_jni") at boot time.
-     * TODO(b/246256335): Remove this native method and the associated native code when it is no
-     * longer needed.
-     */
-    @Keep
-    private static native int setKernelTimezone(long nativeData, int minuteswest);
     private static native long getNextAlarm(long nativeData, int type);
 
     @GuardedBy("mLock")
@@ -4470,9 +4409,6 @@
             }
         }
 
-        // This is a new alarm delivery set; bump the sequence number to indicate that
-        // all apps' alarm delivery classes should be recalculated.
-        mCurrentSeq++;
         calculateDeliveryPriorities(triggerList);
         Collections.sort(triggerList, mAlarmDispatchComparator);
 
@@ -4664,20 +4600,6 @@
             return AlarmManagerService.getNextAlarm(mNativeData, type);
         }
 
-        void setKernelTimeZoneOffset(int utcOffsetMillis) {
-            // Kernel tracks time offsets as 'minutes west of GMT'
-            AlarmManagerService.setKernelTimezone(mNativeData, -(utcOffsetMillis / 60000));
-        }
-
-        void syncKernelTimeZoneOffset() {
-            long currentTimeMillis = getCurrentTimeMillis();
-            TimeZone currentTimeZone = TimeZone.getTimeZone(getTimeZoneId());
-            // If the time zone ID is invalid, GMT will be returned and this will set a kernel
-            // offset of zero.
-            int utcOffsetMillis = currentTimeZone.getOffset(currentTimeMillis);
-            setKernelTimeZoneOffset(utcOffsetMillis);
-        }
-
         void initializeTimeIfRequired() {
             SystemClockTime.initializeIfRequired();
         }
@@ -5199,12 +5121,6 @@
         @Override
         public void onReceive(Context context, Intent intent) {
             if (intent.getAction().equals(Intent.ACTION_DATE_CHANGED)) {
-                if (KERNEL_TIME_ZONE_SYNC_ENABLED) {
-                    // Since the kernel does not keep track of DST, we reset the TZ information at
-                    // the beginning of each day. This may miss a DST transition, but it will
-                    // correct itself within 24 hours.
-                    mInjector.syncKernelTimeZoneOffset();
-                }
                 scheduleDateChangedEvent();
             }
         }
@@ -5362,7 +5278,6 @@
                             // external-applications-unavailable case
                             removeLocked(pkg, REMOVE_REASON_UNDEFINED);
                         }
-                        mPriorities.remove(pkg);
                         for (int i = mBroadcastStats.size() - 1; i >= 0; i--) {
                             ArrayMap<String, BroadcastStats> uidStats = mBroadcastStats.valueAt(i);
                             if (uidStats.remove(pkg) != null) {
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/MetricsHelper.java b/apex/jobscheduler/service/java/com/android/server/alarm/MetricsHelper.java
index eb1848d..90a6455a 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/MetricsHelper.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/MetricsHelper.java
@@ -112,7 +112,7 @@
                 (a.flags & AlarmManager.FLAG_ALLOW_WHILE_IDLE) != 0,
                 a.alarmClock != null,
                 a.repeatInterval != 0,
-                reasonToStatsReason(a.mExactAllowReason),
+                reasonToStatsReason(a.exactAllowReason),
                 AlarmManagerService.isRtc(a.type),
                 ActivityManager.processStateAmToProto(callerProcState));
     }
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 0a7bffc..098b2fb 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
@@ -1184,6 +1184,12 @@
             }
         }
 
+        /** Returns the {@link String#intern() interned} String if it's not null. */
+        @Nullable
+        private static String intern(@Nullable String val) {
+            return val == null ? null : val.intern();
+        }
+
         private List<JobStatus> readJobMapImpl(InputStream fis, boolean rtcIsGood, long nowElapsed)
                 throws XmlPullParserException, IOException {
             TypedXmlPullParser parser = Xml.resolvePullParser(fis);
@@ -1298,8 +1304,8 @@
             }
 
             String sourcePackageName = parser.getAttributeValue(null, "sourcePackageName");
-            final String namespace = parser.getAttributeValue(null, "namespace");
-            final String sourceTag = parser.getAttributeValue(null, "sourceTag");
+            final String namespace = intern(parser.getAttributeValue(null, "namespace"));
+            final String sourceTag = intern(parser.getAttributeValue(null, "sourceTag"));
 
             int eventType;
             // Read out constraints tag.
@@ -1461,7 +1467,7 @@
             final int appBucket = JobSchedulerService.standbyBucketForPackage(sourcePackageName,
                     sourceUserId, nowElapsed);
             JobStatus js = new JobStatus(
-                    builtJob, uid, sourcePackageName, sourceUserId,
+                    builtJob, uid, intern(sourcePackageName), sourceUserId,
                     appBucket, namespace, sourceTag,
                     elapsedRuntimes.first, elapsedRuntimes.second,
                     lastSuccessfulRunTime, lastFailedRunTime, cumulativeExecutionTime,
@@ -1478,8 +1484,8 @@
                 throws XmlPullParserException {
             // Pull out required fields from <job> attributes.
             int jobId = parser.getAttributeInt(null, "jobid");
-            String packageName = parser.getAttributeValue(null, "package");
-            String className = parser.getAttributeValue(null, "class");
+            String packageName = intern(parser.getAttributeValue(null, "package"));
+            String className = intern(parser.getAttributeValue(null, "class"));
             ComponentName cname = new ComponentName(packageName, className);
 
             return new JobInfo.Builder(jobId, cname);
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 13903ac..f429966 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
@@ -56,6 +56,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.FrameworkStatsLog;
+import com.android.modules.expresslog.Counter;
 import com.android.server.LocalServices;
 import com.android.server.job.GrantedUriPermissions;
 import com.android.server.job.JobSchedulerInternal;
@@ -161,6 +162,9 @@
     /** If the job is going to be passed an unmetered network. */
     private boolean mHasAccessToUnmetered;
 
+    /** If the effective bucket has been downgraded once due to being buggy. */
+    private boolean mIsDowngradedDueToBuggyApp;
+
     /**
      * The additional set of dynamic constraints that must be met if this is an expedited job that
      * had a long enough run while the device was Dozing or in battery saver.
@@ -1173,18 +1177,32 @@
             // like other ACTIVE apps.
             return ACTIVE_INDEX;
         }
+
+        final int bucketWithMediaExemption;
+        if (actualBucket != RESTRICTED_INDEX && actualBucket != NEVER_INDEX
+                && mHasMediaBackupExemption) {
+            // Treat it as if it's at most WORKING_INDEX (lower index grants higher quota) since
+            // media backup jobs are important to the user, and the source package may not have
+            // been used directly in a while.
+            bucketWithMediaExemption = Math.min(WORKING_INDEX, actualBucket);
+        } else {
+            bucketWithMediaExemption = actualBucket;
+        }
+
         // If the app is considered buggy, but hasn't yet been put in the RESTRICTED bucket
         // (potentially because it's used frequently by the user), limit its effective bucket
         // so that it doesn't get to run as much as a normal ACTIVE app.
-        final int highestBucket = isBuggy ? WORKING_INDEX : ACTIVE_INDEX;
-        if (actualBucket != RESTRICTED_INDEX && actualBucket != NEVER_INDEX
-                && mHasMediaBackupExemption) {
-            // Treat it as if it's at least WORKING_INDEX since media backup jobs are important
-            // to the user, and the
-            // source package may not have been used directly in a while.
-            return Math.max(highestBucket, Math.min(WORKING_INDEX, actualBucket));
+        if (isBuggy && bucketWithMediaExemption < WORKING_INDEX) {
+            if (!mIsDowngradedDueToBuggyApp) {
+                // Safety check to avoid logging multiple times for the same job.
+                Counter.logIncrementWithUid(
+                        "job_scheduler.value_job_quota_reduced_due_to_buggy_uid",
+                        getTimeoutBlameUid());
+                mIsDowngradedDueToBuggyApp = true;
+            }
+            return WORKING_INDEX;
         }
-        return Math.max(highestBucket, actualBucket);
+        return bucketWithMediaExemption;
     }
 
     /** Returns the real standby bucket of the job. */
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/TimeController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/TimeController.java
index c272af0..be4b720 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/TimeController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/TimeController.java
@@ -32,6 +32,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.modules.expresslog.Counter;
+import com.android.server.AppSchedulingModuleThread;
 import com.android.server.job.JobSchedulerService;
 import com.android.server.job.StateControllerProto;
 
@@ -392,7 +393,8 @@
                 Slog.d(TAG, "Setting " + tag + " for: " + alarmTimeElapsed);
             }
             mAlarmService.set(alarmType, alarmTimeElapsed,
-                    AlarmManager.WINDOW_HEURISTIC, 0, tag, listener, null, ws);
+                    AlarmManager.WINDOW_HEURISTIC, 0, tag, listener,
+                    AppSchedulingModuleThread.getHandler(), ws);
         }
     }
 
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
index 2550a27..f5487dc7 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
@@ -1877,6 +1877,7 @@
                 pw.print(" None");
             }
             pw.decreaseIndent();
+            pw.println();
         }
     }
 }
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/Ledger.java b/apex/jobscheduler/service/java/com/android/server/tare/Ledger.java
index a68170c..92b21e1 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/Ledger.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/Ledger.java
@@ -25,7 +25,9 @@
 import android.annotation.CurrentTimeMillisLong;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.os.Build;
 import android.util.IndentingPrintWriter;
+import android.util.Log;
 import android.util.SparseLongArray;
 import android.util.TimeUtils;
 
@@ -38,6 +40,10 @@
  * Ledger to track the last recorded balance and recent activities of an app.
  */
 class Ledger {
+    private static final String TAG = "TARE-" + Ledger.class.getSimpleName();
+    private static final boolean DEBUG = InternalResourceService.DEBUG
+            || Log.isLoggable(TAG, Log.DEBUG);
+
     /** The window size within which rewards will be counted and used towards reward limiting. */
     private static final long TOTAL_REWARD_WINDOW_MS = 24 * HOUR_IN_MILLIS;
     /** The number of buckets to split {@link #TOTAL_REWARD_WINDOW_MS} into. */
@@ -51,7 +57,7 @@
             TOTAL_REWARD_WINDOW_MS / NUM_REWARD_BUCKET_WINDOWS;
     /** The maximum number of transactions to retain in memory at any one time. */
     @VisibleForTesting
-    static final int MAX_TRANSACTION_COUNT = 50;
+    static final int MAX_TRANSACTION_COUNT = Build.IS_ENG || Build.IS_USERDEBUG || DEBUG ? 32 : 4;
 
     static class Transaction {
         public final long startTimeMs;
@@ -67,7 +73,7 @@
             this.startTimeMs = startTimeMs;
             this.endTimeMs = endTimeMs;
             this.eventId = eventId;
-            this.tag = tag;
+            this.tag = tag == null ? null : tag.intern();
             this.delta = delta;
             this.ctp = ctp;
         }
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/Scribe.java b/apex/jobscheduler/service/java/com/android/server/tare/Scribe.java
index 08439f3..87e1249 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/Scribe.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/Scribe.java
@@ -433,6 +433,12 @@
         }
     }
 
+    /** Returns the {@link String#intern() interned} String if it's not null. */
+    @Nullable
+    private static String intern(@Nullable String val) {
+        return val == null ? null : val.intern();
+    }
+
     /**
      * @param parser Xml parser at the beginning of a "<ledger/>" tag. The next "parser.next()" call
      *               will take the parser into the body of the ledger tag.
@@ -448,7 +454,7 @@
         final List<Ledger.Transaction> transactions = new ArrayList<>();
         final List<Ledger.RewardBucket> rewardBuckets = new ArrayList<>();
 
-        pkgName = parser.getAttributeValue(null, XML_ATTR_PACKAGE_NAME);
+        pkgName = intern(parser.getAttributeValue(null, XML_ATTR_PACKAGE_NAME));
         curBalance = parser.getAttributeLong(null, XML_ATTR_CURRENT_BALANCE);
 
         final boolean isInstalled = validPackages.contains(pkgName);
@@ -487,7 +493,7 @@
                         }
                         continue;
                     }
-                    final String tag = parser.getAttributeValue(null, XML_ATTR_TAG);
+                    final String tag = intern(parser.getAttributeValue(null, XML_ATTR_TAG));
                     final long startTime = parser.getAttributeLong(null, XML_ATTR_START_TIME);
                     final int eventId = parser.getAttributeInt(null, XML_ATTR_EVENT_ID);
                     final long delta = parser.getAttributeLong(null, XML_ATTR_DELTA);
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java b/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java
index fb342b9..913a76a 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java
@@ -61,6 +61,7 @@
 import java.io.BufferedReader;
 import java.io.File;
 import java.io.FileInputStream;
+import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.FileReader;
 import java.io.IOException;
@@ -802,6 +803,9 @@
                     }
                 }
             }
+        } catch (FileNotFoundException e) {
+            // Expected on first boot
+            Slog.d(TAG, "App idle file for user " + userId + " does not exist");
         } catch (IOException | XmlPullParserException e) {
             Slog.e(TAG, "Unable to read app idle file for user " + userId, e);
         } finally {
diff --git a/apex/jobscheduler/service/jni/com_android_server_alarm_AlarmManagerService.cpp b/apex/jobscheduler/service/jni/com_android_server_alarm_AlarmManagerService.cpp
index b2ed4d4..3247da7 100644
--- a/apex/jobscheduler/service/jni/com_android_server_alarm_AlarmManagerService.cpp
+++ b/apex/jobscheduler/service/jni/com_android_server_alarm_AlarmManagerService.cpp
@@ -159,24 +159,6 @@
     return result;
 }
 
-static jint android_server_alarm_AlarmManagerService_setKernelTimezone(JNIEnv*, jobject, jlong, jint minswest)
-{
-    struct timezone tz;
-
-    tz.tz_minuteswest = minswest;
-    tz.tz_dsttime = 0;
-
-    int result = settimeofday(NULL, &tz);
-    if (result < 0) {
-        ALOGE("Unable to set kernel timezone to %d: %s\n", minswest, strerror(errno));
-        return -1;
-    } else {
-        ALOGD("Kernel timezone updated to %d minutes west of GMT\n", minswest);
-    }
-
-    return 0;
-}
-
 static void log_timerfd_create_error(clockid_t id)
 {
     if (errno == EINVAL) {
@@ -319,7 +301,6 @@
     {"close", "(J)V", (void*)android_server_alarm_AlarmManagerService_close},
     {"set", "(JIJJ)I", (void*)android_server_alarm_AlarmManagerService_set},
     {"waitForAlarm", "(J)I", (void*)android_server_alarm_AlarmManagerService_waitForAlarm},
-    {"setKernelTimezone", "(JI)I", (void*)android_server_alarm_AlarmManagerService_setKernelTimezone},
     {"getNextAlarm", "(JI)J", (void*)android_server_alarm_AlarmManagerService_getNextAlarm},
 };
 
diff --git a/api/Android.bp b/api/Android.bp
index f5bafe8..c16bce5 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -60,14 +60,14 @@
 metalava_cmd = "$(location metalava)"
 // Silence reflection warnings. See b/168689341
 metalava_cmd += " -J--add-opens=java.base/java.util=ALL-UNNAMED "
-metalava_cmd += " --quiet --no-banner --format=v2 "
+metalava_cmd += " --quiet "
 
 genrule {
     name: "current-api-xml",
     tools: ["metalava"],
     srcs: [":frameworks-base-api-current.txt"],
     out: ["current.api"],
-    cmd: metalava_cmd + "-convert2xmlnostrip $(in) $(out)",
+    cmd: metalava_cmd + "signature-to-jdiff $(in) $(out)",
     visibility: ["//visibility:public"],
 }
 
diff --git a/api/StubLibraries.bp b/api/StubLibraries.bp
index f08745b..9a0053f 100644
--- a/api/StubLibraries.bp
+++ b/api/StubLibraries.bp
@@ -213,30 +213,106 @@
     system_modules: "none",
     java_version: "1.8",
     compile_dex: true,
-    defaults_visibility: ["//visibility:private"],
     visibility: ["//visibility:public"],
 }
 
 java_defaults {
-    name: "android-non-updatable_defaults_stubs_current",
-    libs: ["stub-annotations"],
-    static_libs: ["framework-res-package-jar"], // Export package of framework-res
+    name: "android-non-updatable_defaults",
     sdk_version: "none",
     system_modules: "none",
     java_version: "1.8",
     compile_dex: true,
+}
+
+java_defaults {
+    name: "android-non-updatable_from_source_defaults",
+    libs: ["stub-annotations"],
+    static_libs: ["framework-res-package-jar"], // Export package of framework-res
     dist: {
         targets: ["sdk"],
         tag: ".jar",
         dest: "android-non-updatable.jar",
     },
-    defaults_visibility: ["//visibility:private"],
-    visibility: ["//visibility:private"],
 }
 
 java_library {
     name: "android-non-updatable.stubs",
-    defaults: ["android-non-updatable_defaults_stubs_current"],
+    defaults: ["android-non-updatable_defaults"],
+    static_libs: [
+        "android-non-updatable.stubs.from-source",
+    ],
+    product_variables: {
+        build_from_text_stub: {
+            static_libs: [
+                "android-non-updatable.stubs.from-text",
+            ],
+            exclude_static_libs: [
+                "android-non-updatable.stubs.from-source",
+            ],
+        },
+    },
+}
+
+java_library {
+    name: "android-non-updatable.stubs.system",
+    defaults: ["android-non-updatable_defaults"],
+    static_libs: [
+        "android-non-updatable.stubs.system.from-source",
+    ],
+    product_variables: {
+        build_from_text_stub: {
+            static_libs: [
+                "android-non-updatable.stubs.system.from-text",
+            ],
+            exclude_static_libs: [
+                "android-non-updatable.stubs.system.from-source",
+            ],
+        },
+    },
+}
+
+java_library {
+    name: "android-non-updatable.stubs.module_lib",
+    defaults: ["android-non-updatable_defaults"],
+    static_libs: [
+        "android-non-updatable.stubs.module_lib.from-source",
+    ],
+    product_variables: {
+        build_from_text_stub: {
+            static_libs: [
+                "android-non-updatable.stubs.module_lib.from-text",
+            ],
+            exclude_static_libs: [
+                "android-non-updatable.stubs.module_lib.from-source",
+            ],
+        },
+    },
+}
+
+java_library {
+    name: "android-non-updatable.stubs.test",
+    defaults: ["android-non-updatable_defaults"],
+    static_libs: [
+        "android-non-updatable.stubs.test.from-source",
+    ],
+    product_variables: {
+        build_from_text_stub: {
+            static_libs: [
+                "android-non-updatable.stubs.test.from-text",
+            ],
+            exclude_static_libs: [
+                "android-non-updatable.stubs.test.from-source",
+            ],
+        },
+    },
+}
+
+java_library {
+    name: "android-non-updatable.stubs.from-source",
+    defaults: [
+        "android-non-updatable_defaults",
+        "android-non-updatable_from_source_defaults",
+    ],
     srcs: [":api-stubs-docs-non-updatable"],
     libs: ["all-modules-public-stubs"],
     dist: {
@@ -245,8 +321,11 @@
 }
 
 java_library {
-    name: "android-non-updatable.stubs.system",
-    defaults: ["android-non-updatable_defaults_stubs_current"],
+    name: "android-non-updatable.stubs.system.from-source",
+    defaults: [
+        "android-non-updatable_defaults",
+        "android-non-updatable_from_source_defaults",
+    ],
     srcs: [":system-api-stubs-docs-non-updatable"],
     libs: ["all-modules-system-stubs"],
     dist: {
@@ -255,8 +334,11 @@
 }
 
 java_library {
-    name: "android-non-updatable.stubs.module_lib",
-    defaults: ["android-non-updatable_defaults_stubs_current"],
+    name: "android-non-updatable.stubs.module_lib.from-source",
+    defaults: [
+        "android-non-updatable_defaults",
+        "android-non-updatable_from_source_defaults",
+    ],
     srcs: [":module-lib-api-stubs-docs-non-updatable"],
     libs: [
         "sdk_module-lib_current_framework-tethering",
@@ -273,8 +355,11 @@
 }
 
 java_library {
-    name: "android-non-updatable.stubs.test",
-    defaults: ["android-non-updatable_defaults_stubs_current"],
+    name: "android-non-updatable.stubs.test.from-source",
+    defaults: [
+        "android-non-updatable_defaults",
+        "android-non-updatable_from_source_defaults",
+    ],
     srcs: [":test-api-stubs-docs-non-updatable"],
     libs: ["all-modules-system-stubs"],
     dist: {
@@ -283,6 +368,57 @@
 }
 
 java_defaults {
+    name: "android-non-updatable_from_text_defaults",
+    static_libs: ["framework-res-package-jar"],
+    libs: ["stub-annotations"],
+}
+
+java_api_library {
+    name: "android-non-updatable.stubs.from-text",
+    api_surface: "public",
+    api_files: [
+        ":non-updatable-current.txt",
+    ],
+    defaults: ["android-non-updatable_from_text_defaults"],
+    full_api_surface_stub: "android_stubs_current.from-text",
+}
+
+java_api_library {
+    name: "android-non-updatable.stubs.system.from-text",
+    api_surface: "system",
+    api_files: [
+        ":non-updatable-current.txt",
+        ":non-updatable-system-current.txt",
+    ],
+    defaults: ["android-non-updatable_from_text_defaults"],
+    full_api_surface_stub: "android_system_stubs_current.from-text",
+}
+
+java_api_library {
+    name: "android-non-updatable.stubs.test.from-text",
+    api_surface: "test",
+    api_files: [
+        ":non-updatable-current.txt",
+        ":non-updatable-system-current.txt",
+        ":non-updatable-test-current.txt",
+    ],
+    defaults: ["android-non-updatable_from_text_defaults"],
+    full_api_surface_stub: "android_test_stubs_current.from-text",
+}
+
+java_api_library {
+    name: "android-non-updatable.stubs.module_lib.from-text",
+    api_surface: "module_lib",
+    api_files: [
+        ":non-updatable-current.txt",
+        ":non-updatable-system-current.txt",
+        ":non-updatable-module-lib-current.txt",
+    ],
+    defaults: ["android-non-updatable_from_text_defaults"],
+    full_api_surface_stub: "android_module_lib_stubs_current_full.from-text",
+}
+
+java_defaults {
     name: "android_stubs_dists_default",
     dist: {
         targets: ["sdk"],
@@ -293,7 +429,7 @@
 }
 
 java_library {
-    name: "android_stubs_current",
+    name: "android_stubs_current.from-source",
     static_libs: [
         "all-modules-public-stubs",
         "android-non-updatable.stubs",
@@ -303,7 +439,7 @@
 }
 
 java_library {
-    name: "android_system_stubs_current",
+    name: "android_system_stubs_current.from-source",
     static_libs: [
         "all-modules-system-stubs",
         "android-non-updatable.stubs.system",
@@ -327,7 +463,7 @@
 }
 
 java_library {
-    name: "android_test_stubs_current",
+    name: "android_test_stubs_current.from-source",
     static_libs: [
         // Updatable modules do not have test APIs, but we want to include their SystemApis, like we
         // include the SystemApi of framework-non-updatable-sources.
@@ -347,7 +483,7 @@
 }
 
 java_library {
-    name: "android_module_lib_stubs_current",
+    name: "android_module_lib_stubs_current.from-source",
     defaults: [
         "android.jar_defaults",
         "android_stubs_dists_default",
@@ -363,7 +499,7 @@
 }
 
 java_library {
-    name: "android_system_server_stubs_current",
+    name: "android_system_server_stubs_current.from-source",
     defaults: [
         "android.jar_defaults",
         "android_stubs_dists_default",
@@ -371,7 +507,7 @@
     srcs: [":services-non-updatable-stubs"],
     installable: false,
     static_libs: [
-        "android_module_lib_stubs_current",
+        "android_module_lib_stubs_current.from-source",
     ],
     dist: {
         dir: "apistubs/android/system-server",
@@ -402,7 +538,6 @@
 
 java_genrule {
     name: "android_stubs_private_hjar",
-    visibility: ["//visibility:private"],
     srcs: [":android_stubs_private_jar{.hjar}"],
     out: ["android_stubs_private.jar"],
     cmd: "cp $(in) $(out)",
@@ -411,7 +546,6 @@
 java_library {
     name: "android_stubs_private",
     defaults: ["android_stubs_dists_default"],
-    visibility: ["//visibility:private"],
     sdk_version: "none",
     system_modules: "none",
     static_libs: ["android_stubs_private_hjar"],
@@ -422,7 +556,6 @@
 
 java_genrule {
     name: "android_stubs_private_framework_aidl",
-    visibility: ["//visibility:private"],
     tools: ["sdkparcelables"],
     srcs: [":android_stubs_private"],
     out: ["framework.aidl"],
@@ -586,7 +719,6 @@
         "metalava-manual",
     ],
     args: priv_apps,
-    visibility: ["//visibility:private"],
 }
 
 java_library {
diff --git a/api/api.go b/api/api.go
index 09c2383..c568a45 100644
--- a/api/api.go
+++ b/api/api.go
@@ -15,7 +15,9 @@
 package api
 
 import (
+	"fmt"
 	"sort"
+	"strings"
 
 	"github.com/google/blueprint/proptools"
 
@@ -94,6 +96,7 @@
 	Sdk_version *string
 	Static_libs []string
 	Visibility  []string
+	Defaults    []string
 }
 
 type fgProps struct {
@@ -102,6 +105,13 @@
 	Visibility []string
 }
 
+type defaultsProps struct {
+	Name                *string
+	Api_surface         *string
+	Api_contributions   []string
+	Defaults_visibility []string
+}
+
 type Bazel_module struct {
 	Bp2build_available *bool
 }
@@ -164,26 +174,26 @@
 }
 
 func createMergedAnnotationsFilegroups(ctx android.LoadHookContext, modules, system_server_modules []string) {
-	for _, i := range []struct{
+	for _, i := range []struct {
 		name    string
 		tag     string
 		modules []string
 	}{
 		{
-			name: "all-modules-public-annotations",
-			tag:  "{.public.annotations.zip}",
+			name:    "all-modules-public-annotations",
+			tag:     "{.public.annotations.zip}",
 			modules: modules,
 		}, {
-			name: "all-modules-system-annotations",
-			tag:  "{.system.annotations.zip}",
+			name:    "all-modules-system-annotations",
+			tag:     "{.system.annotations.zip}",
 			modules: modules,
 		}, {
-			name: "all-modules-module-lib-annotations",
-			tag:  "{.module-lib.annotations.zip}",
+			name:    "all-modules-module-lib-annotations",
+			tag:     "{.module-lib.annotations.zip}",
 			modules: modules,
 		}, {
-			name: "all-modules-system-server-annotations",
-			tag:  "{.system-server.annotations.zip}",
+			name:    "all-modules-system-server-annotations",
+			tag:     "{.system-server.annotations.zip}",
 			modules: system_server_modules,
 		},
 	} {
@@ -329,6 +339,54 @@
 	}
 }
 
+func createApiContributionDefaults(ctx android.LoadHookContext, modules []string) {
+	defaultsSdkKinds := []android.SdkKind{
+		android.SdkPublic, android.SdkSystem, android.SdkModule,
+	}
+	for _, sdkKind := range defaultsSdkKinds {
+		props := defaultsProps{}
+		props.Name = proptools.StringPtr(
+			sdkKind.DefaultJavaLibraryName() + "_contributions")
+		if sdkKind == android.SdkModule {
+			props.Name = proptools.StringPtr(
+				sdkKind.DefaultJavaLibraryName() + "_contributions_full")
+		}
+		props.Api_surface = proptools.StringPtr(sdkKind.String())
+		apiSuffix := ""
+		if sdkKind != android.SdkPublic {
+			apiSuffix = "." + strings.ReplaceAll(sdkKind.String(), "-", "_")
+		}
+		props.Api_contributions = transformArray(
+			modules, "", fmt.Sprintf(".stubs.source%s.api.contribution", apiSuffix))
+		props.Defaults_visibility = []string{"//visibility:public"}
+		ctx.CreateModule(java.DefaultsFactory, &props)
+	}
+}
+
+func createFullApiLibraries(ctx android.LoadHookContext) {
+	javaLibraryNames := []string{
+		"android_stubs_current",
+		"android_system_stubs_current",
+		"android_test_stubs_current",
+		"android_module_lib_stubs_current",
+		"android_system_server_stubs_current",
+	}
+
+	for _, libraryName := range javaLibraryNames {
+		props := libraryProps{}
+		props.Name = proptools.StringPtr(libraryName)
+		staticLib := libraryName + ".from-source"
+		if ctx.Config().BuildFromTextStub() {
+			staticLib = libraryName + ".from-text"
+		}
+		props.Static_libs = []string{staticLib}
+		props.Defaults = []string{"android.jar_defaults"}
+		props.Visibility = []string{"//visibility:public"}
+
+		ctx.CreateModule(java.LibraryFactory, &props)
+	}
+}
+
 func (a *CombinedApis) createInternalModules(ctx android.LoadHookContext) {
 	bootclasspath := a.properties.Bootclasspath
 	system_server_classpath := a.properties.System_server_classpath
@@ -347,6 +405,10 @@
 	createMergedAnnotationsFilegroups(ctx, bootclasspath, system_server_classpath)
 
 	createPublicStubsSourceFilegroup(ctx, bootclasspath)
+
+	createApiContributionDefaults(ctx, bootclasspath)
+
+	createFullApiLibraries(ctx)
 }
 
 func combinedApisModuleFactory() android.Module {
@@ -374,7 +436,7 @@
 		"system-server": "-system-server-current.txt",
 	}
 
-	for scopeName, suffix := range scopeToSuffix{
+	for scopeName, suffix := range scopeToSuffix {
 		name := a.Name() + suffix
 
 		var scope bazel.StringAttribute
diff --git a/api/api_test.go b/api/api_test.go
index 15b695c..1f4c2af 100644
--- a/api/api_test.go
+++ b/api/api_test.go
@@ -19,6 +19,7 @@
 
 	"android/soong/android"
 	"android/soong/bp2build"
+	"android/soong/java"
 )
 
 func runCombinedApisTestCaseWithRegistrationCtxFunc(t *testing.T, tc bp2build.Bp2buildTestCase, registrationCtxFunc func(ctx android.RegistrationContext)) {
@@ -30,7 +31,9 @@
 
 func runCombinedApisTestCase(t *testing.T, tc bp2build.Bp2buildTestCase) {
 	t.Helper()
-	runCombinedApisTestCaseWithRegistrationCtxFunc(t, tc, func(ctx android.RegistrationContext) {})
+	runCombinedApisTestCaseWithRegistrationCtxFunc(t, tc, func(ctx android.RegistrationContext) {
+		ctx.RegisterModuleType("java_defaults", java.DefaultsFactory)
+	})
 }
 
 func TestCombinedApisGeneral(t *testing.T) {
@@ -42,6 +45,13 @@
     system_server_classpath: ["ssc"],
 }
 `,
+		Filesystem: map[string]string{
+			"a/Android.bp": `
+			java_defaults {
+				name: "android.jar_defaults",
+			}
+			`,
+		},
 		ExpectedBazelTargets: []string{
 			bp2build.MakeBazelTargetNoRestrictions("merged_txts", "foo-current.txt", bp2build.AttrNameToString{
 				"scope": `"public"`,
diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java
index b8d24e3..d79131c 100644
--- a/cmds/am/src/com/android/commands/am/Am.java
+++ b/cmds/am/src/com/android/commands/am/Am.java
@@ -193,6 +193,9 @@
                 instrument.alwaysCheckSignature = true;
             } else if (opt.equals("--instrument-sdk-sandbox")) {
                 instrument.instrumentSdkSandbox = true;
+            } else if (opt.equals("--instrument-sdk-in-sandbox")) {
+                instrument.instrumentSdkSandbox = true;
+                instrument.instrumentSdkInSandbox = true;
             } else {
                 System.err.println("Error: Unknown option: " + opt);
                 return;
diff --git a/cmds/am/src/com/android/commands/am/Instrument.java b/cmds/am/src/com/android/commands/am/Instrument.java
index 2604497..e60593e 100644
--- a/cmds/am/src/com/android/commands/am/Instrument.java
+++ b/cmds/am/src/com/android/commands/am/Instrument.java
@@ -20,6 +20,7 @@
 import static android.app.ActivityManager.INSTR_FLAG_DISABLE_HIDDEN_API_CHECKS;
 import static android.app.ActivityManager.INSTR_FLAG_DISABLE_ISOLATED_STORAGE;
 import static android.app.ActivityManager.INSTR_FLAG_DISABLE_TEST_API_CHECKS;
+import static android.app.ActivityManager.INSTR_FLAG_INSTRUMENT_SDK_IN_SANDBOX;
 import static android.app.ActivityManager.INSTR_FLAG_INSTRUMENT_SDK_SANDBOX;
 import static android.app.ActivityManager.INSTR_FLAG_NO_RESTART;
 
@@ -99,6 +100,7 @@
     public String componentNameArg;
     public boolean alwaysCheckSignature = false;
     public boolean instrumentSdkSandbox = false;
+    public boolean instrumentSdkInSandbox = false;
 
     /**
      * Construct the instrument command runner.
@@ -530,6 +532,9 @@
             if (instrumentSdkSandbox) {
                 flags |= INSTR_FLAG_INSTRUMENT_SDK_SANDBOX;
             }
+            if (instrumentSdkInSandbox) {
+                flags |= INSTR_FLAG_INSTRUMENT_SDK_IN_SANDBOX;
+            }
             if (!mAm.startInstrumentation(cn, profileFile, flags, args, watcher, connection, userId,
                         abi)) {
                 throw new AndroidException("INSTRUMENTATION_FAILED: " + cn.flattenToString());
diff --git a/cmds/appops/OWNERS b/cmds/appops/OWNERS
index 999ea0e..2fe78c5 100644
--- a/cmds/appops/OWNERS
+++ b/cmds/appops/OWNERS
@@ -1 +1,2 @@
+#Bug component: 137825
 include /core/java/android/permission/OWNERS
diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp
index a8b6c0b..c216d16 100644
--- a/cmds/bootanimation/BootAnimation.cpp
+++ b/cmds/bootanimation/BootAnimation.cpp
@@ -16,6 +16,7 @@
 
 #define LOG_NDEBUG 0
 #define LOG_TAG "BootAnimation"
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
 
 #include <vector>
 
@@ -28,6 +29,7 @@
 #include <math.h>
 #include <fcntl.h>
 #include <utils/misc.h>
+#include <utils/Trace.h>
 #include <signal.h>
 #include <time.h>
 
@@ -200,6 +202,7 @@
 BootAnimation::BootAnimation(sp<Callbacks> callbacks)
         : Thread(false), mLooper(new Looper(false)), mClockEnabled(true), mTimeIsAccurate(false),
         mTimeFormat12Hour(false), mTimeCheckThread(nullptr), mCallbacks(callbacks) {
+    ATRACE_CALL();
     mSession = new SurfaceComposerClient();
 
     std::string powerCtl = android::base::GetProperty("sys.powerctl", "");
@@ -213,6 +216,7 @@
 }
 
 BootAnimation::~BootAnimation() {
+    ATRACE_CALL();
     if (mAnimation != nullptr) {
         releaseAnimation(mAnimation);
         mAnimation = nullptr;
@@ -222,6 +226,7 @@
 }
 
 void BootAnimation::onFirstRef() {
+    ATRACE_CALL();
     status_t err = mSession->linkToComposerDeath(this);
     SLOGE_IF(err, "linkToComposerDeath failed (%s) ", strerror(-err));
     if (err == NO_ERROR) {
@@ -240,6 +245,7 @@
 }
 
 void BootAnimation::binderDied(const wp<IBinder>&) {
+    ATRACE_CALL();
     // woah, surfaceflinger died!
     SLOGD("SurfaceFlinger died, exiting...");
 
@@ -251,6 +257,7 @@
 
 static void* decodeImage(const void* encodedData, size_t dataLength, AndroidBitmapInfo* outInfo,
     bool premultiplyAlpha) {
+    ATRACE_CALL();
     AImageDecoder* decoder = nullptr;
     AImageDecoder_createFromBuffer(encodedData, dataLength, &decoder);
     if (!decoder) {
@@ -282,6 +289,7 @@
 
 status_t BootAnimation::initTexture(Texture* texture, AssetManager& assets,
         const char* name, bool premultiplyAlpha) {
+    ATRACE_CALL();
     Asset* asset = assets.open(name, Asset::ACCESS_BUFFER);
     if (asset == nullptr)
         return NO_INIT;
@@ -338,6 +346,7 @@
 
 status_t BootAnimation::initTexture(FileMap* map, int* width, int* height,
     bool premultiplyAlpha) {
+    ATRACE_CALL();
     AndroidBitmapInfo bitmapInfo;
     void* pixels = decodeImage(map->getDataPtr(), map->getDataLength(), &bitmapInfo,
         premultiplyAlpha);
@@ -404,10 +413,12 @@
 
 public:
     DisplayEventCallback(BootAnimation* bootAnimation) {
+        ATRACE_CALL();
         mBootAnimation = bootAnimation;
     }
 
     int handleEvent(int /* fd */, int events, void* /* data */) {
+        ATRACE_CALL();
         if (events & (Looper::EVENT_ERROR | Looper::EVENT_HANGUP)) {
             ALOGE("Display event receiver pipe was closed or an error occurred. events=0x%x",
                     events);
@@ -492,6 +503,7 @@
 }
 
 status_t BootAnimation::readyToRun() {
+    ATRACE_CALL();
     mAssets.addDefaultAssets();
 
     const std::vector<PhysicalDisplayId> ids = SurfaceComposerClient::getPhysicalDisplayIds();
@@ -628,6 +640,7 @@
 }
 
 void BootAnimation::rotateAwayFromNaturalOrientationIfNeeded() {
+    ATRACE_CALL();
     const auto orientation = parseOrientationProperty();
 
     if (orientation == ui::ROTATION_0) {
@@ -650,6 +663,7 @@
 }
 
 ui::Rotation BootAnimation::parseOrientationProperty() {
+    ATRACE_CALL();
     const auto displayIds = SurfaceComposerClient::getPhysicalDisplayIds();
     if (displayIds.size() == 0) {
         return ui::ROTATION_0;
@@ -672,11 +686,13 @@
 }
 
 void BootAnimation::projectSceneToWindow() {
+    ATRACE_CALL();
     glViewport(0, 0, mWidth, mHeight);
     glScissor(0, 0, mWidth, mHeight);
 }
 
 void BootAnimation::resizeSurface(int newWidth, int newHeight) {
+    ATRACE_CALL();
     // We assume this function is called on the animation thread.
     if (newWidth == mWidth && newHeight == mHeight) {
         return;
@@ -704,6 +720,7 @@
 }
 
 bool BootAnimation::preloadAnimation() {
+    ATRACE_CALL();
     findBootAnimationFile();
     if (!mZipFileName.isEmpty()) {
         mAnimation = loadAnimation(mZipFileName);
@@ -714,6 +731,7 @@
 }
 
 bool BootAnimation::findBootAnimationFileInternal(const std::vector<std::string> &files) {
+    ATRACE_CALL();
     for (const std::string& f : files) {
         if (access(f.c_str(), R_OK) == 0) {
             mZipFileName = f.c_str();
@@ -724,6 +742,7 @@
 }
 
 void BootAnimation::findBootAnimationFile() {
+    ATRACE_CALL();
     const bool playDarkAnim = android::base::GetIntProperty("ro.boot.theme", 0) == 1;
     static const std::vector<std::string> bootFiles = {
         APEX_BOOTANIMATION_FILE, playDarkAnim ? PRODUCT_BOOTANIMATION_DARK_FILE : PRODUCT_BOOTANIMATION_FILE,
@@ -747,6 +766,7 @@
 }
 
 GLuint compileShader(GLenum shaderType, const GLchar *source) {
+    ATRACE_CALL();
     GLuint shader = glCreateShader(shaderType);
     glShaderSource(shader, 1, &source, 0);
     glCompileShader(shader);
@@ -765,6 +785,7 @@
 }
 
 GLuint linkShader(GLuint vertexShader, GLuint fragmentShader) {
+    ATRACE_CALL();
     GLuint program = glCreateProgram();
     glAttachShader(program, vertexShader);
     glAttachShader(program, fragmentShader);
@@ -780,6 +801,7 @@
 }
 
 void BootAnimation::initShaders() {
+    ATRACE_CALL();
     bool dynamicColoringEnabled = mAnimation != nullptr && mAnimation->dynamicColoringEnabled;
     GLuint vertexShader = compileShader(GL_VERTEX_SHADER, (const GLchar *)VERTEX_SHADER_SOURCE);
     GLuint imageFragmentShader =
@@ -813,6 +835,7 @@
 }
 
 bool BootAnimation::threadLoop() {
+    ATRACE_CALL();
     bool result;
     initShaders();
 
@@ -838,6 +861,7 @@
 }
 
 bool BootAnimation::android() {
+    ATRACE_CALL();
     glActiveTexture(GL_TEXTURE0);
 
     SLOGD("%sAnimationShownTiming start time: %" PRId64 "ms", mShuttingDown ? "Shutdown" : "Boot",
@@ -905,6 +929,7 @@
 }
 
 void BootAnimation::checkExit() {
+    ATRACE_CALL();
     // Allow surface flinger to gracefully request shutdown
     char value[PROPERTY_VALUE_MAX];
     property_get(EXIT_PROP_NAME, value, "0");
@@ -915,10 +940,12 @@
 }
 
 bool BootAnimation::validClock(const Animation::Part& part) {
+    ATRACE_CALL();
     return part.clockPosX != TEXT_MISSING_VALUE && part.clockPosY != TEXT_MISSING_VALUE;
 }
 
 bool parseTextCoord(const char* str, int* dest) {
+    ATRACE_CALL();
     if (strcmp("c", str) == 0) {
         *dest = TEXT_CENTER_VALUE;
         return true;
@@ -935,6 +962,7 @@
 
 // Parse two position coordinates. If only string is non-empty, treat it as the y value.
 void parsePosition(const char* str1, const char* str2, int* x, int* y) {
+    ATRACE_CALL();
     bool success = false;
     if (strlen(str1) == 0) {  // No values were specified
         // success = false
@@ -963,6 +991,7 @@
 // If the input string isn't valid, parseColor returns false and color is
 // left unchanged.
 static bool parseColor(const char str[7], float color[3]) {
+    ATRACE_CALL();
     float tmpColor[3];
     for (int i = 0; i < 3; i++) {
         int val = 0;
@@ -985,6 +1014,7 @@
 // If the input color string is empty, set color with values in defaultColor.
 static void parseColorDecimalString(const std::string& colorString,
     float color[3], float defaultColor[3]) {
+    ATRACE_CALL();
     if (colorString == "") {
         memcpy(color, defaultColor, sizeof(float) * 3);
         return;
@@ -996,6 +1026,7 @@
 }
 
 static bool readFile(ZipFileRO* zip, const char* name, String8& outString) {
+    ATRACE_CALL();
     ZipEntryRO entry = zip->findEntryByName(name);
     SLOGE_IF(!entry, "couldn't find %s", name);
     if (!entry) {
@@ -1018,6 +1049,7 @@
 // columns are the printable ASCII characters 0x20 - 0x7f.  The
 // top row is regular text; the bottom row is bold.
 status_t BootAnimation::initFont(Font* font, const char* fallback) {
+    ATRACE_CALL();
     status_t status = NO_ERROR;
 
     if (font->map != nullptr) {
@@ -1045,6 +1077,7 @@
 }
 
 void BootAnimation::drawText(const char* str, const Font& font, bool bold, int* x, int* y) {
+    ATRACE_CALL();
     glEnable(GL_BLEND);  // Allow us to draw on top of the animation
     glBindTexture(GL_TEXTURE_2D, font.texture.name);
     glUseProgram(mTextShader);
@@ -1092,6 +1125,7 @@
 
 // We render 12 or 24 hour time.
 void BootAnimation::drawClock(const Font& font, const int xPos, const int yPos) {
+    ATRACE_CALL();
     static constexpr char TIME_FORMAT_12[] = "%l:%M";
     static constexpr char TIME_FORMAT_24[] = "%H:%M";
     static constexpr int TIME_LENGTH = 6;
@@ -1117,6 +1151,7 @@
 }
 
 void BootAnimation::drawProgress(int percent, const Font& font, const int xPos, const int yPos) {
+    ATRACE_CALL();
     static constexpr int PERCENT_LENGTH = 5;
 
     char percentBuff[PERCENT_LENGTH];
@@ -1129,6 +1164,7 @@
 }
 
 bool BootAnimation::parseAnimationDesc(Animation& animation)  {
+    ATRACE_CALL();
     String8 desString;
 
     if (!readFile(animation.zip, "desc.txt", desString)) {
@@ -1252,6 +1288,7 @@
 }
 
 bool BootAnimation::preloadZip(Animation& animation) {
+    ATRACE_CALL();
     // read all the data structures
     const size_t pcount = animation.parts.size();
     void *cookie = nullptr;
@@ -1357,6 +1394,7 @@
 }
 
 bool BootAnimation::movie() {
+    ATRACE_CALL();
     if (mAnimation == nullptr) {
         mAnimation = loadAnimation(mZipFileName);
     }
@@ -1450,6 +1488,7 @@
 bool BootAnimation::shouldStopPlayingPart(const Animation::Part& part,
                                           const int fadedFramesCount,
                                           const int lastDisplayedProgress) {
+    ATRACE_CALL();
     // stop playing only if it is time to exit and it's a partial part which has been faded out
     return exitPending() && !part.playUntilComplete && fadedFramesCount >= part.framesToFadeCount &&
         (lastDisplayedProgress == 0 || lastDisplayedProgress == 100);
@@ -1461,6 +1500,7 @@
 }
 
 void BootAnimation::drawTexturedQuad(float xStart, float yStart, float width, float height) {
+    ATRACE_CALL();
     // Map coordinates from screen space to world space.
     float x0 = mapLinear(xStart, 0, mWidth, -1, 1);
     float y0 = mapLinear(yStart, 0, mHeight, -1, 1);
@@ -1484,6 +1524,7 @@
 }
 
 void BootAnimation::initDynamicColors() {
+    ATRACE_CALL();
     for (int i = 0; i < DYNAMIC_COLOR_COUNT; i++) {
         const auto syspropName = "persist.bootanim.color" + std::to_string(i + 1);
         const auto syspropValue = android::base::GetProperty(syspropName, "");
@@ -1510,6 +1551,7 @@
 }
 
 bool BootAnimation::playAnimation(const Animation& animation) {
+    ATRACE_CALL();
     const size_t pcount = animation.parts.size();
     nsecs_t frameDuration = s2ns(1) / animation.fps;
 
@@ -1720,12 +1762,14 @@
 }
 
 void BootAnimation::processDisplayEvents() {
+    ATRACE_CALL();
     // This will poll mDisplayEventReceiver and if there are new events it'll call
     // displayEventCallback synchronously.
     mLooper->pollOnce(0);
 }
 
 void BootAnimation::handleViewport(nsecs_t timestep) {
+    ATRACE_CALL();
     if (mShuttingDown || !mFlingerSurfaceControl || mTargetInset == 0) {
         return;
     }
@@ -1768,6 +1812,7 @@
 }
 
 void BootAnimation::releaseAnimation(Animation* animation) const {
+    ATRACE_CALL();
     for (Vector<Animation::Part>::iterator it = animation->parts.begin(),
          e = animation->parts.end(); it != e; ++it) {
         if (it->animation)
@@ -1779,6 +1824,7 @@
 }
 
 BootAnimation::Animation* BootAnimation::loadAnimation(const String8& fn) {
+    ATRACE_CALL();
     if (mLoadedFiles.indexOf(fn) >= 0) {
         SLOGE("File \"%s\" is already loaded. Cyclic ref is not allowed",
             fn.string());
@@ -1810,6 +1856,7 @@
 }
 
 bool BootAnimation::updateIsTimeAccurate() {
+    ATRACE_CALL();
     static constexpr long long MAX_TIME_IN_PAST =   60000LL * 60LL * 24LL * 30LL;  // 30 days
     static constexpr long long MAX_TIME_IN_FUTURE = 60000LL * 90LL;  // 90 minutes
 
@@ -1853,11 +1900,13 @@
     mInotifyFd(-1), mBootAnimWd(-1), mTimeWd(-1), mBootAnimation(bootAnimation) {}
 
 BootAnimation::TimeCheckThread::~TimeCheckThread() {
+    ATRACE_CALL();
     // mInotifyFd may be -1 but that's ok since we're not at risk of attempting to close a valid FD.
     close(mInotifyFd);
 }
 
 bool BootAnimation::TimeCheckThread::threadLoop() {
+    ATRACE_CALL();
     bool shouldLoop = doThreadLoop() && !mBootAnimation->mTimeIsAccurate
         && mBootAnimation->mClockEnabled;
     if (!shouldLoop) {
@@ -1868,6 +1917,7 @@
 }
 
 bool BootAnimation::TimeCheckThread::doThreadLoop() {
+    ATRACE_CALL();
     static constexpr int BUFF_LEN (10 * (sizeof(struct inotify_event) + NAME_MAX + 1));
 
     // Poll instead of doing a blocking read so the Thread can exit if requested.
@@ -1905,6 +1955,7 @@
 }
 
 void BootAnimation::TimeCheckThread::addTimeDirWatch() {
+        ATRACE_CALL();
         mTimeWd = inotify_add_watch(mInotifyFd, BOOTANIM_TIME_DIR_PATH,
                 IN_CLOSE_WRITE | IN_MOVED_TO | IN_ATTRIB);
         if (mTimeWd > 0) {
@@ -1915,6 +1966,7 @@
 }
 
 status_t BootAnimation::TimeCheckThread::readyToRun() {
+    ATRACE_CALL();
     mInotifyFd = inotify_init();
     if (mInotifyFd < 0) {
         SLOGE("Could not initialize inotify fd");
diff --git a/cmds/bootanimation/FORMAT.md b/cmds/bootanimation/FORMAT.md
index 988685e..01e8fe1 100644
--- a/cmds/bootanimation/FORMAT.md
+++ b/cmds/bootanimation/FORMAT.md
@@ -152,14 +152,14 @@
 
 To enable it, add the following line as the second line of desc.txt:
 
-    dynamic_colors PATH #RGBHEX0 #RGBHEX1 #RGBHEX2 #RGBHEX3
+    dynamic_colors PATH #RGBHEX1 #RGBHEX2 #RGBHEX3 #RGBHEX4
 
   * **PATH:** file path of the part to apply dynamic color transition to.
     Any part before this part will be rendered in the start colors.
     Any part after will be rendered in the end colors.
   * **RGBHEX1:** the first start color (masked by the R channel), specified as `#RRGGBB`.
   * **RGBHEX2:** the second start color (masked by the G channel), specified as `#RRGGBB`.
-  * **RGBHEX3:** the thrid start color (masked by the B channel), specified as `#RRGGBB`.
+  * **RGBHEX3:** the third start color (masked by the B channel), specified as `#RRGGBB`.
   * **RGBHEX4:** the forth start color (masked by the A channel), specified as `#RRGGBB`.
 
 The end colors will be read from the following system properties:
diff --git a/cmds/bootanimation/audioplay.cpp b/cmds/bootanimation/audioplay.cpp
index c5e16c6..9b95b04 100644
--- a/cmds/bootanimation/audioplay.cpp
+++ b/cmds/bootanimation/audioplay.cpp
@@ -20,6 +20,8 @@
 #define CHATTY ALOGD
 #define LOG_TAG "audioplay"
 
+#include <binder/IServiceManager.h>
+
 #include "audioplay.h"
 
 #include <string.h>
@@ -316,8 +318,13 @@
         : Thread(false),
           mExampleAudioData(exampleAudioData),
           mExampleAudioLength(exampleAudioLength) {}
+
 private:
     virtual bool threadLoop() {
+        if (defaultServiceManager()->checkService(String16("audio")) == nullptr) {
+            ALOGW("Audio service is not ready yet, ignore creating playback engine");
+            return false;
+        }
         audioplay::create(mExampleAudioData, mExampleAudioLength);
         // Exit immediately
         return false;
@@ -334,6 +341,11 @@
 public:
     void init(const Vector<Animation::Part>& parts) override {
         const Animation::Part* partWithAudio = nullptr;
+
+        if (!playSoundsAllowed()) {
+            return;
+        }
+
         for (const Animation::Part& part : parts) {
             if (part.audioData != nullptr) {
                 partWithAudio = &part;
@@ -401,14 +413,14 @@
 }
 
 bool playClip(const uint8_t* buf, int size) {
-    // Parse the WAV header
-    const ChunkFormat* chunkFormat;
-    if (!parseClipBuf(buf, size, &chunkFormat, &nextBuffer, &nextSize)) {
+    if (!hasPlayer()) {
+        ALOGE("cannot play clip %p without a player", buf);
         return false;
     }
 
-    if (!hasPlayer()) {
-        ALOGD("cannot play clip %p without a player", buf);
+    // Parse the WAV header
+    const ChunkFormat* chunkFormat;
+    if (!parseClipBuf(buf, size, &chunkFormat, &nextBuffer, &nextSize)) {
         return false;
     }
 
@@ -433,11 +445,9 @@
 void setPlaying(bool isPlaying) {
     if (!hasPlayer()) return;
 
-    SLresult result;
-
     if (nullptr != bqPlayerPlay) {
         // set the player's state
-        result = (*bqPlayerPlay)->SetPlayState(bqPlayerPlay,
+        (*bqPlayerPlay)->SetPlayState(bqPlayerPlay,
             isPlaying ? SL_PLAYSTATE_PLAYING : SL_PLAYSTATE_STOPPED);
     }
 
diff --git a/cmds/gpu_counter_producer/Android.bp b/cmds/gpu_counter_producer/Android.bp
index 5b118ce..2232345 100644
--- a/cmds/gpu_counter_producer/Android.bp
+++ b/cmds/gpu_counter_producer/Android.bp
@@ -18,8 +18,6 @@
         "-Werror",
         "-Wunused",
         "-Wunreachable-code",
-        "-fPIE",
-        "-pie",
     ],
 
     soc_specific: true,
diff --git a/cmds/idmap2/Android.bp b/cmds/idmap2/Android.bp
index 5f06c97..55ec7da 100644
--- a/cmds/idmap2/Android.bp
+++ b/cmds/idmap2/Android.bp
@@ -28,11 +28,14 @@
     tidy_checks: [
         "modernize-*",
         "-modernize-avoid-c-arrays",
+        "-modernize-use-nodiscard",
         "-modernize-use-trailing-return-type",
         "android-*",
         "misc-*",
+        "-misc-const-correctness",
         "readability-*",
         "-readability-identifier-length",
+        "-readability-implicit-bool-conversion",
     ],
     tidy_checks_as_errors: [
         "modernize-*",
@@ -56,6 +59,7 @@
         "-readability-const-return-type",
         "-readability-convert-member-functions-to-static",
         "-readability-duplicate-include",
+        "-readability-implicit-bool-conversion",
         "-readability-else-after-return",
         "-readability-named-parameter",
         "-readability-redundant-access-specifiers",
@@ -80,13 +84,13 @@
                 enabled: false,
             },
             static_libs: [
+                "libidmap2_policies",
                 "libidmap2_protos",
             ],
             shared_libs: [
                 "libandroidfw",
                 "libbase",
                 "libcutils",
-                "libidmap2_policies",
                 "libprotobuf-cpp-lite",
                 "libutils",
                 "libz",
@@ -125,7 +129,7 @@
     },
 }
 
-cc_library {
+cc_library_static {
     name: "libidmap2_policies",
     defaults: [
         "idmap2_defaults",
@@ -142,9 +146,6 @@
             ],
         },
         host: {
-            shared: {
-                enabled: false,
-            },
             static_libs: [
                 "libandroidfw",
             ],
@@ -191,7 +192,6 @@
                 "libandroidfw",
                 "libbase",
                 "libidmap2",
-                "libidmap2_policies",
                 "liblog",
                 "libprotobuf-cpp-lite",
                 "libutils",
@@ -199,6 +199,9 @@
                 "libz",
                 "libziparchive",
             ],
+            static_libs: [
+                "libidmap2_policies",
+            ],
         },
         host: {
             static_libs: [
@@ -255,12 +258,14 @@
                 "libbase",
                 "libcutils",
                 "libidmap2",
-                "libidmap2_policies",
                 "libprotobuf-cpp-lite",
                 "libutils",
                 "libz",
                 "libziparchive",
             ],
+            static_libs: [
+                "libidmap2_policies",
+            ],
         },
         host: {
             static_libs: [
@@ -298,13 +303,13 @@
         "libbinder",
         "libcutils",
         "libidmap2",
-        "libidmap2_policies",
         "libprotobuf-cpp-lite",
         "libutils",
         "libziparchive",
     ],
     static_libs: [
         "libc++fs",
+        "libidmap2_policies",
         "libidmap2_protos",
         "libidmap2daidl",
     ],
diff --git a/cmds/idmap2/idmap2/CreateMultiple.cpp b/cmds/idmap2/idmap2/CreateMultiple.cpp
index 953d99f..68800cd 100644
--- a/cmds/idmap2/idmap2/CreateMultiple.cpp
+++ b/cmds/idmap2/idmap2/CreateMultiple.cpp
@@ -18,8 +18,8 @@
 #include <sys/types.h>  // umask
 
 #include <fstream>
+#include <iostream>
 #include <memory>
-#include <ostream>
 #include <string>
 #include <vector>
 
@@ -51,7 +51,7 @@
 Result<Unit> CreateMultiple(const std::vector<std::string>& args) {
   SYSTRACE << "CreateMultiple " << args;
   std::string target_apk_path;
-  std::string idmap_dir = kIdmapCacheDir;
+  std::string idmap_dir{kIdmapCacheDir};
   std::vector<std::string> overlay_apk_paths;
   std::vector<std::string> policies;
   bool ignore_overlayable = false;
@@ -67,7 +67,7 @@
           .OptionalOption("--idmap-dir",
                           StringPrintf("output: path to the directory in which to write idmap file"
                                        " (defaults to %s)",
-                                       kIdmapCacheDir),
+                                       kIdmapCacheDir.data()),
                           &idmap_dir)
           .OptionalOption("--policy",
                           "input: an overlayable policy this overlay fulfills"
@@ -142,7 +142,7 @@
   }
 
   for (const std::string& idmap_path : idmap_paths) {
-    std::cout << idmap_path << std::endl;
+    std::cout << idmap_path << '\n';
   }
 
   return Unit{};
diff --git a/cmds/idmap2/idmap2/Lookup.cpp b/cmds/idmap2/idmap2/Lookup.cpp
index f41e57c..3704b4a 100644
--- a/cmds/idmap2/idmap2/Lookup.cpp
+++ b/cmds/idmap2/idmap2/Lookup.cpp
@@ -16,9 +16,9 @@
 
 #include <algorithm>
 #include <fstream>
+#include <iostream>
 #include <iterator>
 #include <memory>
-#include <ostream>
 #include <string>
 #include <utility>
 #include <vector>
@@ -174,7 +174,7 @@
     return Error("failed to parse config");
   }
 
-  std::vector<std::unique_ptr<const ApkAssets>> apk_assets;
+  std::vector<AssetManager2::ApkAssetsPtr> apk_assets;
   std::string target_path;
   std::string target_package_name;
   for (size_t i = 0; i < idmap_paths.size(); i++) {
@@ -217,24 +217,21 @@
     apk_assets.push_back(std::move(overlay_apk));
   }
 
-  // AssetManager2::SetApkAssets requires raw ApkAssets pointers, not unique_ptrs
-  std::vector<const ApkAssets*> raw_pointer_apk_assets;
-  std::transform(apk_assets.cbegin(), apk_assets.cend(), std::back_inserter(raw_pointer_apk_assets),
-                 [](const auto& p) -> const ApkAssets* { return p.get(); });
-  AssetManager2 am;
-  am.SetApkAssets(raw_pointer_apk_assets);
-  am.SetConfiguration(config);
+  {
+    // Make sure |apk_assets| vector outlives the asset manager as it doesn't own the assets.
+    AssetManager2 am(apk_assets, config);
 
-  const Result<ResourceId> resid = ParseResReference(am, resid_str, target_package_name);
-  if (!resid) {
-    return Error(resid.GetError(), "failed to parse resource ID");
-  }
+    const Result<ResourceId> resid = ParseResReference(am, resid_str, target_package_name);
+    if (!resid) {
+      return Error(resid.GetError(), "failed to parse resource ID");
+    }
 
-  const Result<std::string> value = GetValue(&am, *resid);
-  if (!value) {
-    return Error(value.GetError(), "resource 0x%08x not found", *resid);
+    const Result<std::string> value = GetValue(&am, *resid);
+    if (!value) {
+      return Error(value.GetError(), "resource 0x%08x not found", *resid);
+    }
+    std::cout << *value << '\n';
   }
-  std::cout << *value << std::endl;
 
   return Unit{};
 }
diff --git a/cmds/idmap2/idmap2/Main.cpp b/cmds/idmap2/idmap2/Main.cpp
index aa6d0e7..5ef15a6 100644
--- a/cmds/idmap2/idmap2/Main.cpp
+++ b/cmds/idmap2/idmap2/Main.cpp
@@ -45,7 +45,7 @@
     }
     out << iter->first;
   }
-  out << "]" << std::endl;
+  out << "]" << '\n';
 }
 
 }  // namespace
@@ -65,18 +65,18 @@
   const std::unique_ptr<std::vector<std::string>> args =
       CommandLineOptions::ConvertArgvToVector(argc - 1, const_cast<const char**>(argv + 1));
   if (!args) {
-    std::cerr << "error: failed to parse command line options" << std::endl;
+    std::cerr << "error: failed to parse command line options" << '\n';
     return EXIT_FAILURE;
   }
   const auto iter = commands.find(argv[1]);
   if (iter == commands.end()) {
-    std::cerr << argv[1] << ": command not found" << std::endl;
+    std::cerr << argv[1] << ": command not found" << '\n';
     PrintUsage(commands, std::cerr);
     return EXIT_FAILURE;
   }
   const auto result = iter->second(*args);
   if (!result) {
-    std::cerr << "error: " << result.GetErrorMessage() << std::endl;
+    std::cerr << "error: " << result.GetErrorMessage() << '\n';
     return EXIT_FAILURE;
   }
   return EXIT_SUCCESS;
diff --git a/cmds/idmap2/idmap2d/Idmap2Service.cpp b/cmds/idmap2/idmap2d/Idmap2Service.cpp
index 10947dc..b94b3b4 100644
--- a/cmds/idmap2/idmap2d/Idmap2Service.cpp
+++ b/cmds/idmap2/idmap2d/Idmap2Service.cpp
@@ -59,7 +59,7 @@
 
 namespace {
 
-constexpr const char* kFrameworkPath = "/system/framework/framework-res.apk";
+constexpr std::string_view kFrameworkPath = "/system/framework/framework-res.apk";
 
 Status ok() {
   return Status::ok();
@@ -207,23 +207,47 @@
 
 idmap2::Result<Idmap2Service::TargetResourceContainerPtr> Idmap2Service::GetTargetContainer(
     const std::string& target_path) {
-  if (target_path == kFrameworkPath) {
-    if (framework_apk_cache_ == nullptr) {
-      // Initialize the framework APK cache.
-      auto target = TargetResourceContainer::FromPath(target_path);
-      if (!target) {
-        return target.GetError();
+  const bool is_framework = target_path == kFrameworkPath;
+  bool use_cache;
+  struct stat st = {};
+  if (is_framework || !::stat(target_path.c_str(), &st)) {
+    use_cache = true;
+  } else {
+    LOG(WARNING) << "failed to stat target path '" << target_path << "' for the cache";
+    use_cache = false;
+  }
+
+  if (use_cache) {
+    std::lock_guard lock(container_cache_mutex_);
+    if (auto cache_it = container_cache_.find(target_path); cache_it != container_cache_.end()) {
+      const auto& item = cache_it->second;
+      if (is_framework ||
+        (item.dev == st.st_dev && item.inode == st.st_ino && item.size == st.st_size
+          && item.mtime.tv_sec == st.st_mtim.tv_sec && item.mtime.tv_nsec == st.st_mtim.tv_nsec)) {
+        return {item.apk.get()};
       }
-      framework_apk_cache_ = std::move(*target);
+      container_cache_.erase(cache_it);
     }
-    return {framework_apk_cache_.get()};
   }
 
   auto target = TargetResourceContainer::FromPath(target_path);
   if (!target) {
     return target.GetError();
   }
-  return {std::move(*target)};
+  if (!use_cache) {
+    return {std::move(*target)};
+  }
+
+  const auto res = target->get();
+  std::lock_guard lock(container_cache_mutex_);
+  container_cache_.emplace(target_path, CachedContainer {
+    .dev = dev_t(st.st_dev),
+    .inode = ino_t(st.st_ino),
+    .size = st.st_size,
+    .mtime = st.st_mtim,
+    .apk = std::move(*target)
+  });
+  return {res};
 }
 
 Status Idmap2Service::createFabricatedOverlay(
@@ -257,7 +281,7 @@
     const std::string random_suffix = RandomStringForPath(kSuffixLength);
     file_name = StringPrintf("%s-%s-%s.frro", overlay.packageName.c_str(),
                              overlay.overlayName.c_str(), random_suffix.c_str());
-    path = StringPrintf("%s/%s", kIdmapCacheDir, file_name.c_str());
+    path = StringPrintf("%s/%s", kIdmapCacheDir.data(), file_name.c_str());
 
     // Invoking std::filesystem::exists with a file name greater than 255 characters will cause this
     // process to abort since the name exceeds the maximum file name size.
diff --git a/cmds/idmap2/idmap2d/Idmap2Service.h b/cmds/idmap2/idmap2d/Idmap2Service.h
index cc8cc5f..a69fa61 100644
--- a/cmds/idmap2/idmap2d/Idmap2Service.h
+++ b/cmds/idmap2/idmap2d/Idmap2Service.h
@@ -75,7 +75,20 @@
  private:
   // idmap2d is killed after a period of inactivity, so any information stored on this class should
   // be able to be recalculated if idmap2 dies and restarts.
-  std::unique_ptr<idmap2::TargetResourceContainer> framework_apk_cache_;
+
+  // A cache item for the resource containers (apks or frros), with all information needed to
+  // detect if it has changed since it was parsed:
+  //  - (dev, inode) pair uniquely identifies a file on a particular device partition (see stat(2)).
+  //  - (mtime, size) ensure the file data hasn't changed inside that file.
+  struct CachedContainer {
+    dev_t dev;
+    ino_t inode;
+    int64_t size;
+    struct timespec mtime;
+    std::unique_ptr<idmap2::TargetResourceContainer> apk;
+  };
+  std::unordered_map<std::string, CachedContainer> container_cache_;
+  std::mutex container_cache_mutex_;
 
   int32_t frro_iter_id_ = 0;
   std::optional<std::filesystem::directory_iterator> frro_iter_;
diff --git a/cmds/idmap2/include/idmap2/BinaryStreamVisitor.h b/cmds/idmap2/include/idmap2/BinaryStreamVisitor.h
index 7b38bd1..57af1b6 100644
--- a/cmds/idmap2/include/idmap2/BinaryStreamVisitor.h
+++ b/cmds/idmap2/include/idmap2/BinaryStreamVisitor.h
@@ -18,7 +18,7 @@
 #define IDMAP2_INCLUDE_IDMAP2_BINARYSTREAMVISITOR_H_
 
 #include <cstdint>
-#include <iostream>
+#include <ostream>
 #include <string>
 
 #include "idmap2/Idmap.h"
diff --git a/cmds/idmap2/include/idmap2/FabricatedOverlay.h b/cmds/idmap2/include/idmap2/FabricatedOverlay.h
index 9f57710..a29fa8f 100644
--- a/cmds/idmap2/include/idmap2/FabricatedOverlay.h
+++ b/cmds/idmap2/include/idmap2/FabricatedOverlay.h
@@ -19,9 +19,10 @@
 
 #include <libidmap2/proto/fabricated_v1.pb.h>
 
-#include <iostream>
+#include <istream>
 #include <map>
 #include <memory>
+#include <ostream>
 #include <string>
 #include <unordered_map>
 #include <vector>
diff --git a/cmds/idmap2/include/idmap2/FileUtils.h b/cmds/idmap2/include/idmap2/FileUtils.h
index bc0bb47..3e99981 100644
--- a/cmds/idmap2/include/idmap2/FileUtils.h
+++ b/cmds/idmap2/include/idmap2/FileUtils.h
@@ -19,12 +19,12 @@
 
 #include <sys/types.h>
 
-#include <random>
 #include <string>
+#include <string_view>
 
 namespace android::idmap2::utils {
 
-constexpr const char* kIdmapCacheDir = "/data/resource-cache";
+constexpr std::string_view kIdmapCacheDir = "/data/resource-cache";
 constexpr const mode_t kIdmapFilePermissionMask = 0133;  // u=rw,g=r,o=r
 
 bool UidHasWriteAccessToPath(uid_t uid, const std::string& path);
diff --git a/cmds/idmap2/include/idmap2/Idmap.h b/cmds/idmap2/include/idmap2/Idmap.h
index 03e714a..e86f814 100644
--- a/cmds/idmap2/include/idmap2/Idmap.h
+++ b/cmds/idmap2/include/idmap2/Idmap.h
@@ -71,9 +71,10 @@
 #ifndef IDMAP2_INCLUDE_IDMAP2_IDMAP_H_
 #define IDMAP2_INCLUDE_IDMAP2_IDMAP_H_
 
-#include <iostream>
+#include <istream>
 #include <memory>
 #include <string>
+#include <string_view>
 #include <vector>
 
 #include "android-base/macros.h"
@@ -272,8 +273,8 @@
 
 class Idmap {
  public:
-  static std::string CanonicalIdmapPathFor(const std::string& absolute_dir,
-                                           const std::string& absolute_apk_path);
+  static std::string CanonicalIdmapPathFor(std::string_view absolute_dir,
+                                           std::string_view absolute_apk_path);
 
   static Result<std::unique_ptr<const Idmap>> FromBinaryStream(std::istream& stream);
 
diff --git a/cmds/idmap2/include/idmap2/LogInfo.h b/cmds/idmap2/include/idmap2/LogInfo.h
index a6237e6..b576152 100644
--- a/cmds/idmap2/include/idmap2/LogInfo.h
+++ b/cmds/idmap2/include/idmap2/LogInfo.h
@@ -61,7 +61,7 @@
 #ifdef __ANDROID__
     LOG(WARNING) << msg.GetString();
 #else
-    std::cerr << "W " << msg.GetString() << std::endl;
+    std::cerr << "W " << msg.GetString() << '\n';
 #endif
     lines_.push_back("W " + msg.GetString());
   }
diff --git a/cmds/idmap2/include/idmap2/PrettyPrintVisitor.h b/cmds/idmap2/include/idmap2/PrettyPrintVisitor.h
index 4464201..ed18d9c 100644
--- a/cmds/idmap2/include/idmap2/PrettyPrintVisitor.h
+++ b/cmds/idmap2/include/idmap2/PrettyPrintVisitor.h
@@ -17,7 +17,7 @@
 #ifndef IDMAP2_INCLUDE_IDMAP2_PRETTYPRINTVISITOR_H_
 #define IDMAP2_INCLUDE_IDMAP2_PRETTYPRINTVISITOR_H_
 
-#include <iostream>
+#include <ostream>
 #include <memory>
 
 #include "androidfw/AssetManager2.h"
diff --git a/cmds/idmap2/include/idmap2/RawPrintVisitor.h b/cmds/idmap2/include/idmap2/RawPrintVisitor.h
index ebd0d1e..849ba11 100644
--- a/cmds/idmap2/include/idmap2/RawPrintVisitor.h
+++ b/cmds/idmap2/include/idmap2/RawPrintVisitor.h
@@ -17,7 +17,7 @@
 #ifndef IDMAP2_INCLUDE_IDMAP2_RAWPRINTVISITOR_H_
 #define IDMAP2_INCLUDE_IDMAP2_RAWPRINTVISITOR_H_
 
-#include <iostream>
+#include <ostream>
 #include <memory>
 #include <string>
 
diff --git a/cmds/idmap2/include/idmap2/XmlParser.h b/cmds/idmap2/include/idmap2/XmlParser.h
index c968a5e..c93b067 100644
--- a/cmds/idmap2/include/idmap2/XmlParser.h
+++ b/cmds/idmap2/include/idmap2/XmlParser.h
@@ -17,8 +17,6 @@
 #ifndef IDMAP2_INCLUDE_IDMAP2_XMLPARSER_H_
 #define IDMAP2_INCLUDE_IDMAP2_XMLPARSER_H_
 
-#include <iostream>
-#include <map>
 #include <memory>
 #include <string>
 
diff --git a/cmds/idmap2/libidmap2/CommandLineOptions.cpp b/cmds/idmap2/libidmap2/CommandLineOptions.cpp
index 8129d99..888b3a5 100644
--- a/cmds/idmap2/libidmap2/CommandLineOptions.cpp
+++ b/cmds/idmap2/libidmap2/CommandLineOptions.cpp
@@ -19,8 +19,8 @@
 #include <algorithm>
 #include <cassert>
 #include <iomanip>
-#include <iostream>
 #include <memory>
+#include <ostream>
 #include <set>
 #include <sstream>
 #include <string>
@@ -131,7 +131,7 @@
       separator = true;
       stream << opt << ": missing mandatory option";
     }
-    stream << std::endl;
+    stream << '\n';
     Usage(stream);
     return Error("%s", stream.str().c_str());
   }
@@ -168,7 +168,7 @@
       out << " [" << opt.name << " arg [..]]";
     }
   }
-  out << std::endl << std::endl;
+  out << "\n\n";
   for (const Option& opt : options_) {
     out << std::left << std::setw(maxLength);
     if (opt.argument) {
@@ -181,7 +181,7 @@
         opt.count == Option::COUNT_OPTIONAL_ONCE_OR_MORE) {
       out << " (can be provided multiple times)";
     }
-    out << std::endl;
+    out << '\n';
   }
 }
 
diff --git a/cmds/idmap2/libidmap2/FileUtils.cpp b/cmds/idmap2/libidmap2/FileUtils.cpp
index 98a4cea..bc5654a 100644
--- a/cmds/idmap2/libidmap2/FileUtils.cpp
+++ b/cmds/idmap2/libidmap2/FileUtils.cpp
@@ -16,11 +16,13 @@
 
 #include "idmap2/FileUtils.h"
 
+#include <random>
 #include <string>
+#include <string_view>
 
 #include "android-base/file.h"
 #include "android-base/macros.h"
-#include "android-base/stringprintf.h"
+#include "android-base/strings.h"
 #include "private/android_filesystem_config.h"
 
 namespace android::idmap2::utils {
@@ -33,9 +35,9 @@
     return false;
   }
 
-  const std::string cache_subdir = base::StringPrintf("%s/", kIdmapCacheDir);
-  if (canonical_path == kIdmapCacheDir ||
-      canonical_path.compare(0, cache_subdir.size(), cache_subdir) == 0) {
+  if (base::StartsWith(canonical_path, kIdmapCacheDir) &&
+      (canonical_path.size() == kIdmapCacheDir.size() ||
+       canonical_path[kIdmapCacheDir.size()] == '/')) {
     // limit access to /data/resource-cache to root and system
     return uid == AID_ROOT || uid == AID_SYSTEM;
   }
@@ -47,17 +49,17 @@
 }
 #endif
 
-std::string RandomStringForPath(const size_t length) {
-  constexpr char kChars[] = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
-  constexpr size_t kCharLastIndex = sizeof(kChars) - 1;
+std::string RandomStringForPath(size_t length) {
+  constexpr std::string_view kChars =
+      "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
 
   std::string out_rand;
-  out_rand.reserve(length);
+  out_rand.resize(length);
 
-  std::random_device rd;
-  std::uniform_int_distribution<int> dist(0, kCharLastIndex);
+  static thread_local std::random_device rd;
+  std::uniform_int_distribution<int> dist(0, kChars.size() - 1);
   for (size_t i = 0; i < length; i++) {
-    out_rand[i] = kChars[dist(rd) % (kCharLastIndex)];
+    out_rand[i] = kChars[dist(rd)];
   }
   return out_rand;
 }
diff --git a/cmds/idmap2/libidmap2/Idmap.cpp b/cmds/idmap2/libidmap2/Idmap.cpp
index 7c0b937..12d9dd9 100644
--- a/cmds/idmap2/libidmap2/Idmap.cpp
+++ b/cmds/idmap2/libidmap2/Idmap.cpp
@@ -18,13 +18,14 @@
 
 #include <algorithm>
 #include <cassert>
-#include <iostream>
+#include <istream>
 #include <iterator>
 #include <limits>
 #include <memory>
 #include <string>
 #include <utility>
 
+#include "android-base/format.h"
 #include "android-base/macros.h"
 #include "androidfw/AssetManager2.h"
 #include "idmap2/ResourceMapping.h"
@@ -80,7 +81,7 @@
   if (padding_size != 0 && !stream.seekg(padding_size, std::ios_base::cur)) {
     return false;
   }
-  *out = buf;
+  *out = std::move(buf);
   return true;
 }
 
@@ -279,13 +280,13 @@
   return std::move(data);
 }
 
-std::string Idmap::CanonicalIdmapPathFor(const std::string& absolute_dir,
-                                         const std::string& absolute_apk_path) {
+std::string Idmap::CanonicalIdmapPathFor(std::string_view absolute_dir,
+                                         std::string_view absolute_apk_path) {
   assert(absolute_dir.size() > 0 && absolute_dir[0] == "/");
   assert(absolute_apk_path.size() > 0 && absolute_apk_path[0] == "/");
-  std::string copy(++absolute_apk_path.cbegin(), absolute_apk_path.cend());
+  std::string copy(absolute_apk_path.begin() + 1, absolute_apk_path.end());
   replace(copy.begin(), copy.end(), '/', '@');
-  return absolute_dir + "/" + copy + "@idmap";
+  return fmt::format("{}/{}@idmap", absolute_dir, copy);
 }
 
 Result<std::unique_ptr<const Idmap>> Idmap::FromBinaryStream(std::istream& stream) {
@@ -332,7 +333,7 @@
         values[cd] = value;
         inline_value_count++;
       }
-      data->target_inline_entries_.push_back({mapping.first, values});
+      data->target_inline_entries_.push_back({mapping.first, std::move(values)});
     }
   }
 
diff --git a/cmds/idmap2/libidmap2/PrettyPrintVisitor.cpp b/cmds/idmap2/libidmap2/PrettyPrintVisitor.cpp
index a44fa75..eb94582 100644
--- a/cmds/idmap2/libidmap2/PrettyPrintVisitor.cpp
+++ b/cmds/idmap2/libidmap2/PrettyPrintVisitor.cpp
@@ -34,21 +34,21 @@
 }
 
 void PrettyPrintVisitor::visit(const IdmapHeader& header) {
-  stream_ << "Paths:" << std::endl
-          << TAB "target path  : " << header.GetTargetPath() << std::endl
-          << TAB "overlay path : " << header.GetOverlayPath() << std::endl;
+  stream_ << "Paths:" << '\n'
+          << TAB "target path  : " << header.GetTargetPath() << '\n'
+          << TAB "overlay path : " << header.GetOverlayPath() << '\n';
 
   if (!header.GetOverlayName().empty()) {
-    stream_ << "Overlay name: " << header.GetOverlayName() << std::endl;
+    stream_ << "Overlay name: " << header.GetOverlayName() << '\n';
   }
 
   const std::string& debug = header.GetDebugInfo();
   if (!debug.empty()) {
     std::istringstream debug_stream(debug);
     std::string line;
-    stream_ << "Debug info:" << std::endl;
+    stream_ << "Debug info:" << '\n';
     while (std::getline(debug_stream, line)) {
-      stream_ << TAB << line << std::endl;
+      stream_ << TAB << line << '\n';
     }
   }
 
@@ -59,7 +59,7 @@
     overlay_ = std::move(*overlay);
   }
 
-  stream_ << "Mapping:" << std::endl;
+  stream_ << "Mapping:" << '\n';
 }
 
 void PrettyPrintVisitor::visit(const IdmapData::Header& header ATTRIBUTE_UNUSED) {
@@ -90,7 +90,7 @@
             << base::StringPrintf("0x%08x -> 0x%08x (%s -> %s)", target_entry.target_id,
                                   target_entry.overlay_id, target_name.c_str(),
                                   overlay_name.c_str())
-            << std::endl;
+            << '\n';
   }
 
   for (auto& target_entry : data.GetTargetInlineEntries()) {
@@ -114,7 +114,7 @@
       }
     }
 
-    stream_ << " (" << target_name << ")" << std::endl;
+    stream_ << " (" << target_name << ")" << '\n';
   }
 }
 
diff --git a/cmds/idmap2/libidmap2/RawPrintVisitor.cpp b/cmds/idmap2/libidmap2/RawPrintVisitor.cpp
index 3531cd7..174d85c 100644
--- a/cmds/idmap2/libidmap2/RawPrintVisitor.cpp
+++ b/cmds/idmap2/libidmap2/RawPrintVisitor.cpp
@@ -161,7 +161,7 @@
   va_end(ap);
 
   stream_ << base::StringPrintf("%08zx:       %02x", offset_, value) << "  " << comment
-          << std::endl;
+          << '\n';
   offset_ += sizeof(uint8_t);
 }
 
@@ -173,7 +173,7 @@
   base::StringAppendV(&comment, fmt, ap);
   va_end(ap);
 
-  stream_ << base::StringPrintf("%08zx:     %04x", offset_, value) << "  " << comment << std::endl;
+  stream_ << base::StringPrintf("%08zx:     %04x", offset_, value) << "  " << comment << '\n';
   offset_ += sizeof(uint16_t);
 }
 
@@ -185,7 +185,7 @@
   base::StringAppendV(&comment, fmt, ap);
   va_end(ap);
 
-  stream_ << base::StringPrintf("%08zx: %08x", offset_, value) << "  " << comment << std::endl;
+  stream_ << base::StringPrintf("%08zx: %08x", offset_, value) << "  " << comment << '\n';
   offset_ += sizeof(uint32_t);
 }
 
@@ -198,7 +198,7 @@
   va_end(ap);
 
   stream_ << base::StringPrintf("%08zx: %08x", offset_, (uint32_t)value.size()) << "  " << comment
-          << " size" << std::endl;
+          << " size" << '\n';
   offset_ += sizeof(uint32_t);
 
   stream_ << base::StringPrintf("%08zx: ", offset_) << "........  " << comment;
@@ -207,7 +207,7 @@
   if (print_value) {
     stream_ << ": " << value;
   }
-  stream_ << std::endl;
+  stream_ << '\n';
 }
 
 void RawPrintVisitor::align() {
diff --git a/cmds/idmap2/libidmap2/ResourceContainer.cpp b/cmds/idmap2/libidmap2/ResourceContainer.cpp
index 0e35904..7869fbd 100644
--- a/cmds/idmap2/libidmap2/ResourceContainer.cpp
+++ b/cmds/idmap2/libidmap2/ResourceContainer.cpp
@@ -262,7 +262,7 @@
 }
 
 struct ResState {
-  std::unique_ptr<ApkAssets> apk_assets;
+  AssetManager2::ApkAssetsPtr apk_assets;
   const LoadedArsc* arsc;
   const LoadedPackage* package;
   std::unique_ptr<AssetManager2> am;
@@ -284,7 +284,7 @@
     }
 
     state.am = std::make_unique<AssetManager2>();
-    if (!state.am->SetApkAssets({state.apk_assets.get()})) {
+    if (!state.am->SetApkAssets({state.apk_assets})) {
       return Error("failed to create asset manager");
     }
 
diff --git a/cmds/idmap2/libidmap2/XmlParser.cpp b/cmds/idmap2/libidmap2/XmlParser.cpp
index 70822c8..766ca56 100644
--- a/cmds/idmap2/libidmap2/XmlParser.cpp
+++ b/cmds/idmap2/libidmap2/XmlParser.cpp
@@ -16,8 +16,6 @@
 
 #include "idmap2/XmlParser.h"
 
-#include <iostream>
-#include <map>
 #include <memory>
 #include <string>
 #include <utility>
diff --git a/cmds/idmap2/tests/FileUtilsTests.cpp b/cmds/idmap2/tests/FileUtilsTests.cpp
index 5750ca1..b160e8e 100644
--- a/cmds/idmap2/tests/FileUtilsTests.cpp
+++ b/cmds/idmap2/tests/FileUtilsTests.cpp
@@ -27,8 +27,9 @@
 #ifdef __ANDROID__
 TEST(FileUtilsTests, UidHasWriteAccessToPath) {
   constexpr const char* tmp_path = "/data/local/tmp/test@idmap";
-  const std::string cache_path(base::StringPrintf("%s/test@idmap", kIdmapCacheDir));
-  const std::string sneaky_cache_path(base::StringPrintf("/data/../%s/test@idmap", kIdmapCacheDir));
+  const std::string cache_path(base::StringPrintf("%s/test@idmap", kIdmapCacheDir.data()));
+  const std::string sneaky_cache_path(
+      base::StringPrintf("/data/../%s/test@idmap", kIdmapCacheDir.data()));
 
   ASSERT_TRUE(UidHasWriteAccessToPath(AID_ROOT, tmp_path));
   ASSERT_TRUE(UidHasWriteAccessToPath(AID_ROOT, cache_path));
diff --git a/cmds/idmap2/tests/IdmapTests.cpp b/cmds/idmap2/tests/IdmapTests.cpp
index b473f26..f6e48ba 100644
--- a/cmds/idmap2/tests/IdmapTests.cpp
+++ b/cmds/idmap2/tests/IdmapTests.cpp
@@ -613,19 +613,19 @@
   }
 
   void visit(const Idmap& idmap ATTRIBUTE_UNUSED) override {
-    stream_ << "TestVisitor::visit(Idmap)" << std::endl;
+    stream_ << "TestVisitor::visit(Idmap)" << '\n';
   }
 
   void visit(const IdmapHeader& idmap ATTRIBUTE_UNUSED) override {
-    stream_ << "TestVisitor::visit(IdmapHeader)" << std::endl;
+    stream_ << "TestVisitor::visit(IdmapHeader)" << '\n';
   }
 
   void visit(const IdmapData& idmap ATTRIBUTE_UNUSED) override {
-    stream_ << "TestVisitor::visit(IdmapData)" << std::endl;
+    stream_ << "TestVisitor::visit(IdmapData)" << '\n';
   }
 
   void visit(const IdmapData::Header& idmap ATTRIBUTE_UNUSED) override {
-    stream_ << "TestVisitor::visit(IdmapData::Header)" << std::endl;
+    stream_ << "TestVisitor::visit(IdmapData::Header)" << '\n';
   }
 
  private:
diff --git a/cmds/idmap2/tests/ResourceUtilsTests.cpp b/cmds/idmap2/tests/ResourceUtilsTests.cpp
index 6914208..011040b 100644
--- a/cmds/idmap2/tests/ResourceUtilsTests.cpp
+++ b/cmds/idmap2/tests/ResourceUtilsTests.cpp
@@ -38,7 +38,7 @@
     apk_assets_ = ApkAssets::Load(GetTargetApkPath());
     ASSERT_THAT(apk_assets_, NotNull());
 
-    am_.SetApkAssets({apk_assets_.get()});
+    am_.SetApkAssets({apk_assets_});
   }
 
   const AssetManager2& GetAssetManager() {
@@ -47,7 +47,7 @@
 
  private:
   AssetManager2 am_;
-  std::unique_ptr<const ApkAssets> apk_assets_;
+  AssetManager2::ApkAssetsPtr apk_assets_;
 };
 
 TEST_F(ResourceUtilsTests, ResToTypeEntryName) {
diff --git a/cmds/incidentd/Android.bp b/cmds/incidentd/Android.bp
index b0b23f5..bd30404 100644
--- a/cmds/incidentd/Android.bp
+++ b/cmds/incidentd/Android.bp
@@ -82,17 +82,11 @@
         },
     },
 
-
     init_rc: ["incidentd.rc"],
 }
 
-// ==============
-// incidentd_test
-// ==============
-
-cc_test {
-    name: "incidentd_test",
-    test_suites: ["device-tests"],
+cc_defaults {
+    name: "incidentd_test_defaults",
 
     cflags: [
         "-Werror",
@@ -110,8 +104,6 @@
     generated_headers: ["framework-cppstream-protos"],
 
     srcs: [
-        "tests/**/*.cpp",
-        "tests/**/*.proto",
         "src/FdBuffer.cpp",
         "src/Privacy.cpp",
         "src/PrivacyFilter.cpp",
@@ -125,13 +117,11 @@
         "src/**/*.proto",
     ],
 
-    data: ["testdata/**/*"],
-
     static_libs: [
-        "libgmock",
         "libincidentcompanion",
         "libplatformprotos-test",
     ],
+
     shared_libs: [
         "libbase",
         "libbinder",
@@ -144,6 +134,30 @@
         "libservices",
         "libutils",
     ],
+}
+
+// ==============
+// incidentd_test
+// ==============
+cc_test {
+    name: "incidentd_test",
+
+    defaults: [
+        "incidentd_test_defaults",
+    ],
+
+    test_suites: ["device-tests"],
+
+    srcs: [
+        "tests/**/*.cpp",
+        "tests/**/*.proto",
+    ],
+
+    data: ["testdata/**/*"],
+
+    static_libs: [
+        "libgmock",
+    ],
 
     target: {
         android: {
@@ -160,3 +174,27 @@
     out: ["section_list.cpp"],
     cmd: "$(location incident-section-gen) incidentd > $(out)",
 }
+
+cc_fuzz {
+    name: "incidentd_service_fuzzer",
+    defaults: [
+        "service_fuzzer_defaults",
+        "fuzzer_disable_leaks",
+        "incidentd_test_defaults",
+    ],
+
+    fuzz_config: {
+        cc: [
+            "yaochen@google.com",
+            "yanmin@google.com",
+        ],
+        triage_assignee: "waghpawan@google.com",
+    },
+
+    srcs: [
+        "fuzzers/IncidentServiceFuzzer.cpp",
+        "src/IncidentService.cpp",
+        "src/Broadcaster.cpp",
+        ":incidentd_section_list",
+    ],
+}
diff --git a/cmds/incidentd/fuzzers/IncidentServiceFuzzer.cpp b/cmds/incidentd/fuzzers/IncidentServiceFuzzer.cpp
new file mode 100644
index 0000000..14c969b
--- /dev/null
+++ b/cmds/incidentd/fuzzers/IncidentServiceFuzzer.cpp
@@ -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.
+ */
+#include <fuzzbinder/libbinder_driver.h>
+#include <utils/Looper.h>
+
+#include "IncidentService.h"
+
+using ::android::fuzzService;
+using ::android::os::incidentd::IncidentService;
+using ::android::Looper;
+using ::android::sp;
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+    sp<Looper> looper(Looper::prepare(0));
+    sp<IncidentService> service = sp<IncidentService>::make(looper);
+    fuzzService(service, FuzzedDataProvider(data, size));
+    return 0;
+}
diff --git a/cmds/incidentd/src/IncidentService.cpp b/cmds/incidentd/src/IncidentService.cpp
index 5af02f4..4f9059f 100644
--- a/cmds/incidentd/src/IncidentService.cpp
+++ b/cmds/incidentd/src/IncidentService.cpp
@@ -502,9 +502,13 @@
 
     switch (code) {
         case SHELL_COMMAND_TRANSACTION: {
-            int in = data.readFileDescriptor();
-            int out = data.readFileDescriptor();
-            int err = data.readFileDescriptor();
+            unique_fd in, out, err;
+            if (status_t status = data.readUniqueFileDescriptor(&in); status != OK) return status;
+
+            if (status_t status = data.readUniqueFileDescriptor(&out); status != OK) return status;
+
+            if (status_t status = data.readUniqueFileDescriptor(&err); status != OK) return status;
+
             int argc = data.readInt32();
             Vector<String8> args;
             for (int i = 0; i < argc && data.dataAvail() > 0; i++) {
@@ -513,16 +517,19 @@
             sp<IShellCallback> shellCallback = IShellCallback::asInterface(data.readStrongBinder());
             sp<IResultReceiver> resultReceiver =
                     IResultReceiver::asInterface(data.readStrongBinder());
+            if (resultReceiver == nullptr) {
+                return BAD_VALUE;
+            }
 
-            FILE* fin = fdopen(in, "r");
-            FILE* fout = fdopen(out, "w");
-            FILE* ferr = fdopen(err, "w");
+            FILE* fin = fdopen(in.release(), "r");
+            FILE* fout = fdopen(out.release(), "w");
+            FILE* ferr = fdopen(err.release(), "w");
 
             if (fin == NULL || fout == NULL || ferr == NULL) {
                 resultReceiver->send(NO_MEMORY);
             } else {
-                err = command(fin, fout, ferr, args);
-                resultReceiver->send(err);
+                status_t result = command(fin, fout, ferr, args);
+                resultReceiver->send(result);
             }
 
             if (fin != NULL) {
@@ -533,7 +540,7 @@
                 fflush(fout);
                 fclose(fout);
             }
-            if (fout != NULL) {
+            if (ferr != NULL) {
                 fflush(ferr);
                 fclose(ferr);
             }
diff --git a/cmds/locksettings/Android.bp b/cmds/locksettings/Android.bp
index 5ee5824..ee31aed 100644
--- a/cmds/locksettings/Android.bp
+++ b/cmds/locksettings/Android.bp
@@ -21,8 +21,7 @@
     default_applicable_licenses: ["frameworks_base_license"],
 }
 
-java_binary {
+sh_binary {
     name: "locksettings",
-    wrapper: "locksettings.sh",
-    srcs: ["**/*.java"],
+    src: "locksettings.sh",
 }
diff --git a/cmds/locksettings/locksettings.sh b/cmds/locksettings/locksettings.sh
index 0ef4fa9..2f8d868 100755
--- a/cmds/locksettings/locksettings.sh
+++ b/cmds/locksettings/locksettings.sh
@@ -1,6 +1,2 @@
 #!/system/bin/sh
-# Script to start "locksettings" on the device
-#
-base=/system
-export CLASSPATH=$base/framework/locksettings.jar
-exec app_process $base/bin com.android.commands.locksettings.LockSettingsCmd "$@"
+cmd lock_settings "$@"
diff --git a/cmds/locksettings/src/com/android/commands/locksettings/LockSettingsCmd.java b/cmds/locksettings/src/com/android/commands/locksettings/LockSettingsCmd.java
deleted file mode 100644
index 7d9260a..0000000
--- a/cmds/locksettings/src/com/android/commands/locksettings/LockSettingsCmd.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * 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.commands.locksettings;
-
-import android.os.ResultReceiver;
-import android.os.ServiceManager;
-import android.os.ShellCallback;
-
-import com.android.internal.os.BaseCommand;
-import com.android.internal.widget.ILockSettings;
-
-import java.io.FileDescriptor;
-import java.io.PrintStream;
-
-public final class LockSettingsCmd extends BaseCommand {
-
-    public static void main(String[] args) {
-        (new LockSettingsCmd()).run(args);
-    }
-
-    @Override
-    public void onShowUsage(PrintStream out) {
-        main(new String[] { "help" });
-    }
-
-    @Override
-    public void onRun() throws Exception {
-        ILockSettings lockSettings = ILockSettings.Stub.asInterface(
-                ServiceManager.getService("lock_settings"));
-        lockSettings.asBinder().shellCommand(FileDescriptor.in, FileDescriptor.out,
-                FileDescriptor.err, getRawArgs(), new ShellCallback(), new ResultReceiver(null) {});
-    }
-}
diff --git a/cmds/screencap/screencap.cpp b/cmds/screencap/screencap.cpp
index 863efff..c18b7ef 100644
--- a/cmds/screencap/screencap.cpp
+++ b/cmds/screencap/screencap.cpp
@@ -196,9 +196,6 @@
         return 1;
     }
 
-    void const* mapbase = MAP_FAILED;
-    ssize_t mapsize = -1;
-
     void* base = NULL;
 
     // setThreadPoolMaxThreadCount(0) actually tells the kernel it's
@@ -278,9 +275,6 @@
         }
     }
     close(fd);
-    if (mapbase != MAP_FAILED) {
-        munmap((void *)mapbase, mapsize);
-    }
 
     return 0;
 }
diff --git a/cmds/uiautomator/library/testrunner-src/com/android/uiautomator/core/UiAutomationShellWrapper.java b/cmds/uiautomator/library/testrunner-src/com/android/uiautomator/core/UiAutomationShellWrapper.java
index 39248730..7489ce3 100644
--- a/cmds/uiautomator/library/testrunner-src/com/android/uiautomator/core/UiAutomationShellWrapper.java
+++ b/cmds/uiautomator/library/testrunner-src/com/android/uiautomator/core/UiAutomationShellWrapper.java
@@ -8,6 +8,7 @@
 import android.app.UiAutomationConnection;
 import android.content.Intent;
 import android.os.HandlerThread;
+import android.os.Looper;
 import android.os.RemoteException;
 
 /**
@@ -26,6 +27,10 @@
             throw new IllegalStateException("Already connected!");
         }
         mHandlerThread.start();
+        // The AccessibilityInteractionClient used by UiAutomation expects the main looper to
+        // be prepared. In most contexts this is normally done automatically, but must be called
+        // explicitly here because this is a shell tool.
+        Looper.prepareMainLooper();
         mUiAutomation = new UiAutomation(mHandlerThread.getLooper(),
                 new UiAutomationConnection());
         mUiAutomation.connect();
diff --git a/core/api/current.txt b/core/api/current.txt
index 288ab47..eaefe84 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -3286,8 +3286,10 @@
 
   public abstract class AccessibilityService extends android.app.Service {
     ctor public AccessibilityService();
-    method public void attachAccessibilityOverlayToDisplay(int, @NonNull android.view.SurfaceControl);
-    method public void attachAccessibilityOverlayToWindow(int, @NonNull android.view.SurfaceControl);
+    method @Deprecated public void attachAccessibilityOverlayToDisplay(int, @NonNull android.view.SurfaceControl);
+    method public void attachAccessibilityOverlayToDisplay(int, @NonNull android.view.SurfaceControl, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.IntConsumer);
+    method @Deprecated public void attachAccessibilityOverlayToWindow(int, @NonNull android.view.SurfaceControl);
+    method public void attachAccessibilityOverlayToWindow(int, @NonNull android.view.SurfaceControl, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.IntConsumer);
     method public boolean clearCache();
     method public boolean clearCachedSubtree(@NonNull android.view.accessibility.AccessibilityNodeInfo);
     method public final void disableSelf();
@@ -3399,6 +3401,9 @@
     field public static final int GLOBAL_ACTION_RECENTS = 3; // 0x3
     field public static final int GLOBAL_ACTION_TAKE_SCREENSHOT = 9; // 0x9
     field public static final int GLOBAL_ACTION_TOGGLE_SPLIT_SCREEN = 7; // 0x7
+    field public static final int OVERLAY_RESULT_INTERNAL_ERROR = 1; // 0x1
+    field public static final int OVERLAY_RESULT_INVALID = 2; // 0x2
+    field public static final int OVERLAY_RESULT_SUCCESS = 0; // 0x0
     field public static final String SERVICE_INTERFACE = "android.accessibilityservice.AccessibilityService";
     field public static final String SERVICE_META_DATA = "android.accessibilityservice";
     field public static final int SHOW_MODE_AUTO = 0; // 0x0
@@ -3662,13 +3667,13 @@
   }
 
   public class Account implements android.os.Parcelable {
-    ctor public Account(String, String);
+    ctor public Account(@NonNull String, @NonNull String);
     ctor public Account(android.os.Parcel);
     method public int describeContents();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.accounts.Account> CREATOR;
-    field public final String name;
-    field public final String type;
+    field @NonNull public final String name;
+    field @NonNull public final String type;
   }
 
   @Deprecated public class AccountAuthenticatorActivity extends android.app.Activity {
@@ -4458,6 +4463,7 @@
     method public void onProvideAssistData(android.os.Bundle);
     method public android.net.Uri onProvideReferrer();
     method public void onRequestPermissionsResult(int, @NonNull String[], @NonNull int[]);
+    method public void onRequestPermissionsResult(int, @NonNull String[], @NonNull int[], int);
     method @CallSuper protected void onRestart();
     method protected void onRestoreInstanceState(@NonNull android.os.Bundle);
     method public void onRestoreInstanceState(@Nullable android.os.Bundle, @Nullable android.os.PersistableBundle);
@@ -4499,12 +4505,14 @@
     method public android.view.DragAndDropPermissions requestDragAndDropPermissions(android.view.DragEvent);
     method public void requestFullscreenMode(int, @Nullable android.os.OutcomeReceiver<java.lang.Void,java.lang.Throwable>);
     method public final void requestPermissions(@NonNull String[], int);
+    method public final void requestPermissions(@NonNull String[], int, int);
     method public final void requestShowKeyboardShortcuts();
     method @Deprecated public boolean requestVisibleBehind(boolean);
     method public final boolean requestWindowFeature(int);
     method @NonNull public final <T extends android.view.View> T requireViewById(@IdRes int);
     method public final void runOnUiThread(Runnable);
     method public void setActionBar(@Nullable android.widget.Toolbar);
+    method public void setAllowCrossUidActivitySwitchFromBelow(boolean);
     method public void setContentTransitionManager(android.transition.TransitionManager);
     method public void setContentView(@LayoutRes int);
     method public void setContentView(android.view.View);
@@ -4545,6 +4553,7 @@
     method public void setVrModeEnabled(boolean, @NonNull android.content.ComponentName) throws android.content.pm.PackageManager.NameNotFoundException;
     method public boolean shouldDockBigOverlays();
     method public boolean shouldShowRequestPermissionRationale(@NonNull String);
+    method public boolean shouldShowRequestPermissionRationale(@NonNull String, int);
     method public boolean shouldUpRecreateTask(android.content.Intent);
     method public boolean showAssist(android.os.Bundle);
     method @Deprecated public final void showDialog(int);
@@ -6658,6 +6667,21 @@
     field protected android.app.Notification.Builder mBuilder;
   }
 
+  public static final class Notification.TvExtender implements android.app.Notification.Extender {
+    ctor public Notification.TvExtender();
+    ctor public Notification.TvExtender(@NonNull android.app.Notification);
+    method @NonNull public android.app.Notification.Builder extend(@NonNull android.app.Notification.Builder);
+    method @Nullable public String getChannelId();
+    method @Nullable public android.app.PendingIntent getContentIntent();
+    method @Nullable public android.app.PendingIntent getDeleteIntent();
+    method public boolean isAvailableOnTv();
+    method public boolean isSuppressShowOverApps();
+    method @NonNull public android.app.Notification.TvExtender setChannelId(@Nullable String);
+    method @NonNull public android.app.Notification.TvExtender setContentIntent(@Nullable android.app.PendingIntent);
+    method @NonNull public android.app.Notification.TvExtender setDeleteIntent(@Nullable android.app.PendingIntent);
+    method @NonNull public android.app.Notification.TvExtender setSuppressShowOverApps(boolean);
+  }
+
   public static final class Notification.WearableExtender implements android.app.Notification.Extender {
     ctor public Notification.WearableExtender();
     ctor public Notification.WearableExtender(android.app.Notification);
@@ -9593,6 +9617,7 @@
     method public int describeContents();
     method public int getDeviceId();
     method @Nullable public String getName();
+    method @Nullable public String getPersistentDeviceId();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.companion.virtual.VirtualDevice> CREATOR;
   }
@@ -9669,6 +9694,7 @@
     method public int describeContents();
     method public void enforceCallingUid();
     method @Nullable public String getAttributionTag();
+    method public int getDeviceId();
     method @Nullable public android.content.AttributionSource getNext();
     method @Nullable public String getPackageName();
     method public int getPid();
@@ -9684,7 +9710,9 @@
     ctor public AttributionSource.Builder(@NonNull android.content.AttributionSource);
     method @NonNull public android.content.AttributionSource build();
     method @NonNull public android.content.AttributionSource.Builder setAttributionTag(@Nullable String);
-    method @NonNull public android.content.AttributionSource.Builder setNext(@Nullable android.content.AttributionSource);
+    method @NonNull public android.content.AttributionSource.Builder setDeviceId(int);
+    method @Deprecated @NonNull public android.content.AttributionSource.Builder setNext(@Nullable android.content.AttributionSource);
+    method @NonNull public android.content.AttributionSource.Builder setNextAttributionSource(@NonNull android.content.AttributionSource);
     method @NonNull public android.content.AttributionSource.Builder setPackageName(@Nullable String);
     method @NonNull public android.content.AttributionSource.Builder setPid(int);
   }
@@ -12164,34 +12192,34 @@
     field public static final int REQUESTED_PERMISSION_GRANTED = 2; // 0x2
     field public static final int REQUESTED_PERMISSION_IMPLICIT = 4; // 0x4
     field public static final int REQUESTED_PERMISSION_NEVER_FOR_LOCATION = 65536; // 0x10000
-    field public android.content.pm.ActivityInfo[] activities;
-    field public android.content.pm.ApplicationInfo applicationInfo;
+    field @Nullable public android.content.pm.ActivityInfo[] activities;
+    field @Nullable public android.content.pm.ApplicationInfo applicationInfo;
     field @Nullable public android.content.pm.Attribution[] attributions;
     field public int baseRevisionCode;
-    field public android.content.pm.ConfigurationInfo[] configPreferences;
-    field public android.content.pm.FeatureGroupInfo[] featureGroups;
+    field @Nullable public android.content.pm.ConfigurationInfo[] configPreferences;
+    field @Nullable public android.content.pm.FeatureGroupInfo[] featureGroups;
     field public long firstInstallTime;
-    field public int[] gids;
+    field @Nullable public int[] gids;
     field public int installLocation;
-    field public android.content.pm.InstrumentationInfo[] instrumentation;
+    field @Nullable public android.content.pm.InstrumentationInfo[] instrumentation;
     field public boolean isApex;
     field public long lastUpdateTime;
-    field public String packageName;
-    field public android.content.pm.PermissionInfo[] permissions;
-    field public android.content.pm.ProviderInfo[] providers;
-    field public android.content.pm.ActivityInfo[] receivers;
-    field public android.content.pm.FeatureInfo[] reqFeatures;
-    field public String[] requestedPermissions;
-    field public int[] requestedPermissionsFlags;
-    field public android.content.pm.ServiceInfo[] services;
-    field public String sharedUserId;
+    field @NonNull public String packageName;
+    field @Nullable public android.content.pm.PermissionInfo[] permissions;
+    field @Nullable public android.content.pm.ProviderInfo[] providers;
+    field @Nullable public android.content.pm.ActivityInfo[] receivers;
+    field @Nullable public android.content.pm.FeatureInfo[] reqFeatures;
+    field @Nullable public String[] requestedPermissions;
+    field @Nullable public int[] requestedPermissionsFlags;
+    field @Nullable public android.content.pm.ServiceInfo[] services;
+    field @Nullable public String sharedUserId;
     field public int sharedUserLabel;
-    field @Deprecated public android.content.pm.Signature[] signatures;
-    field public android.content.pm.SigningInfo signingInfo;
-    field public String[] splitNames;
-    field public int[] splitRevisionCodes;
+    field @Deprecated @Nullable public android.content.pm.Signature[] signatures;
+    field @Nullable public android.content.pm.SigningInfo signingInfo;
+    field @NonNull public String[] splitNames;
+    field @NonNull public int[] splitRevisionCodes;
     field @Deprecated public int versionCode;
-    field public String versionName;
+    field @Nullable public String versionName;
   }
 
   public class PackageInstaller {
@@ -12986,7 +13014,7 @@
     method public final int getIconResource();
     method public boolean isCrossProfileIntentForwarderActivity();
     method public android.graphics.drawable.Drawable loadIcon(android.content.pm.PackageManager);
-    method public CharSequence loadLabel(android.content.pm.PackageManager);
+    method @NonNull public CharSequence loadLabel(@NonNull android.content.pm.PackageManager);
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.ResolveInfo> CREATOR;
     field public android.content.pm.ActivityInfo activityInfo;
@@ -14238,11 +14266,14 @@
   public final class SQLiteDatabase extends android.database.sqlite.SQLiteClosable {
     method public void beginTransaction();
     method public void beginTransactionNonExclusive();
-    method public void beginTransactionWithListener(android.database.sqlite.SQLiteTransactionListener);
-    method public void beginTransactionWithListenerNonExclusive(android.database.sqlite.SQLiteTransactionListener);
+    method public void beginTransactionReadOnly();
+    method public void beginTransactionWithListener(@Nullable android.database.sqlite.SQLiteTransactionListener);
+    method public void beginTransactionWithListenerNonExclusive(@Nullable android.database.sqlite.SQLiteTransactionListener);
+    method public void beginTransactionWithListenerReadOnly(@Nullable android.database.sqlite.SQLiteTransactionListener);
     method public android.database.sqlite.SQLiteStatement compileStatement(String) throws android.database.SQLException;
     method @NonNull public static android.database.sqlite.SQLiteDatabase create(@Nullable android.database.sqlite.SQLiteDatabase.CursorFactory);
     method @NonNull public static android.database.sqlite.SQLiteDatabase createInMemory(@NonNull android.database.sqlite.SQLiteDatabase.OpenParams);
+    method @NonNull public android.database.sqlite.SQLiteRawStatement createRawStatement(@NonNull String);
     method public int delete(String, String, String[]);
     method public static boolean deleteDatabase(@NonNull java.io.File);
     method public void disableWriteAheadLogging();
@@ -14253,6 +14284,7 @@
     method public void execSQL(String, Object[]) throws android.database.SQLException;
     method public static String findEditTable(String);
     method public java.util.List<android.util.Pair<java.lang.String,java.lang.String>> getAttachedDbs();
+    method public long getLastInsertRowId();
     method public long getMaximumSize();
     method public long getPageSize();
     method public String getPath();
@@ -14478,6 +14510,39 @@
     method public int update(@NonNull android.database.sqlite.SQLiteDatabase, @NonNull android.content.ContentValues, @Nullable String, @Nullable String[]);
   }
 
+  public final class SQLiteRawStatement implements java.io.Closeable {
+    method public void bindBlob(int, @NonNull byte[]) throws android.database.sqlite.SQLiteException;
+    method public void bindBlob(int, @NonNull byte[], int, int) throws android.database.sqlite.SQLiteException;
+    method public void bindDouble(int, double) throws android.database.sqlite.SQLiteException;
+    method public void bindInt(int, int) throws android.database.sqlite.SQLiteException;
+    method public void bindLong(int, long) throws android.database.sqlite.SQLiteException;
+    method public void bindNull(int) throws android.database.sqlite.SQLiteException;
+    method public void bindText(int, @NonNull String) throws android.database.sqlite.SQLiteException;
+    method public void clearBindings();
+    method public void close();
+    method @Nullable public byte[] getColumnBlob(int) throws android.database.sqlite.SQLiteException;
+    method public double getColumnDouble(int) throws android.database.sqlite.SQLiteException;
+    method public int getColumnInt(int) throws android.database.sqlite.SQLiteException;
+    method public int getColumnLength(int) throws android.database.sqlite.SQLiteException;
+    method public long getColumnLong(int) throws android.database.sqlite.SQLiteException;
+    method @NonNull public String getColumnName(int) throws android.database.sqlite.SQLiteException;
+    method @NonNull public String getColumnText(int) throws android.database.sqlite.SQLiteException;
+    method public int getColumnType(int) throws android.database.sqlite.SQLiteException;
+    method public int getParameterCount();
+    method public int getParameterIndex(@NonNull String);
+    method @Nullable public String getParameterName(int);
+    method public int getResultColumnCount();
+    method public boolean isOpen();
+    method public int readColumnBlob(int, @NonNull byte[], int, int, int) throws android.database.sqlite.SQLiteException;
+    method public void reset();
+    method public boolean step() throws android.database.sqlite.SQLiteException;
+    field public static final int SQLITE_DATA_TYPE_BLOB = 4; // 0x4
+    field public static final int SQLITE_DATA_TYPE_FLOAT = 2; // 0x2
+    field public static final int SQLITE_DATA_TYPE_INTEGER = 1; // 0x1
+    field public static final int SQLITE_DATA_TYPE_NULL = 5; // 0x5
+    field public static final int SQLITE_DATA_TYPE_TEXT = 3; // 0x3
+  }
+
   public class SQLiteReadOnlyDatabaseException extends android.database.sqlite.SQLiteException {
     ctor public SQLiteReadOnlyDatabaseException();
     ctor public SQLiteReadOnlyDatabaseException(String);
@@ -17414,6 +17479,7 @@
     ctor public FontFamily.Builder(@NonNull android.graphics.fonts.Font);
     method @NonNull public android.graphics.fonts.FontFamily.Builder addFont(@NonNull android.graphics.fonts.Font);
     method @NonNull public android.graphics.fonts.FontFamily build();
+    method @Nullable public android.graphics.fonts.FontFamily buildVariableFamily();
   }
 
   public final class FontStyle {
@@ -17505,17 +17571,21 @@
   public final class LineBreakConfig {
     method public int getLineBreakStyle();
     method public int getLineBreakWordStyle();
+    method @NonNull public android.graphics.text.LineBreakConfig merge(@NonNull android.graphics.text.LineBreakConfig);
     field public static final int LINE_BREAK_STYLE_LOOSE = 1; // 0x1
     field public static final int LINE_BREAK_STYLE_NONE = 0; // 0x0
     field public static final int LINE_BREAK_STYLE_NORMAL = 2; // 0x2
     field public static final int LINE_BREAK_STYLE_STRICT = 3; // 0x3
+    field public static final int LINE_BREAK_STYLE_UNSPECIFIED = -1; // 0xffffffff
     field public static final int LINE_BREAK_WORD_STYLE_NONE = 0; // 0x0
     field public static final int LINE_BREAK_WORD_STYLE_PHRASE = 1; // 0x1
+    field public static final int LINE_BREAK_WORD_STYLE_UNSPECIFIED = -1; // 0xffffffff
   }
 
   public static final class LineBreakConfig.Builder {
     ctor public LineBreakConfig.Builder();
     method @NonNull public android.graphics.text.LineBreakConfig build();
+    method @NonNull public android.graphics.text.LineBreakConfig.Builder merge(@NonNull android.graphics.text.LineBreakConfig);
     method @NonNull public android.graphics.text.LineBreakConfig.Builder setLineBreakStyle(int);
     method @NonNull public android.graphics.text.LineBreakConfig.Builder setLineBreakWordStyle(int);
   }
@@ -17590,13 +17660,18 @@
     method public float getAdvance();
     method public float getAscent();
     method public float getDescent();
+    method public boolean getFakeBold(@IntRange(from=0) int);
+    method public boolean getFakeItalic(@IntRange(from=0) int);
     method @NonNull public android.graphics.fonts.Font getFont(@IntRange(from=0) int);
     method @IntRange(from=0) public int getGlyphId(@IntRange(from=0) int);
     method public float getGlyphX(@IntRange(from=0) int);
     method public float getGlyphY(@IntRange(from=0) int);
+    method public float getItalicOverride(@IntRange(from=0) int);
     method public float getOffsetX();
     method public float getOffsetY();
+    method public float getWeightOverride(@IntRange(from=0) int);
     method @IntRange(from=0) public int glyphCount();
+    field public static final float NO_OVERRIDE = 1.4E-45f;
   }
 
   public class TextRunShaper {
@@ -24314,7 +24389,14 @@
     field public static final int TYPE_HDMI = 9; // 0x9
     field public static final int TYPE_HEARING_AID = 23; // 0x17
     field public static final int TYPE_REMOTE_AUDIO_VIDEO_RECEIVER = 1003; // 0x3eb
+    field public static final int TYPE_REMOTE_CAR = 1008; // 0x3f0
+    field public static final int TYPE_REMOTE_COMPUTER = 1006; // 0x3ee
+    field public static final int TYPE_REMOTE_GAME_CONSOLE = 1007; // 0x3ef
+    field public static final int TYPE_REMOTE_SMARTPHONE = 1010; // 0x3f2
+    field public static final int TYPE_REMOTE_SMARTWATCH = 1009; // 0x3f1
     field public static final int TYPE_REMOTE_SPEAKER = 1002; // 0x3ea
+    field public static final int TYPE_REMOTE_TABLET = 1004; // 0x3ec
+    field public static final int TYPE_REMOTE_TABLET_DOCKED = 1005; // 0x3ed
     field public static final int TYPE_REMOTE_TV = 1001; // 0x3e9
     field public static final int TYPE_UNKNOWN = 0; // 0x0
     field public static final int TYPE_USB_ACCESSORY = 12; // 0xc
@@ -24805,7 +24887,6 @@
   }
 
   public class Ringtone {
-    method protected void finalize();
     method public android.media.AudioAttributes getAudioAttributes();
     method @Deprecated public int getStreamType();
     method public String getTitle(android.content.Context);
@@ -32653,6 +32734,7 @@
     field public static final int S_V2 = 32; // 0x20
     field public static final int TIRAMISU = 33; // 0x21
     field public static final int UPSIDE_DOWN_CAKE = 34; // 0x22
+    field public static final int VANILLA_ICE_CREAM = 10000; // 0x2710
   }
 
   public final class Bundle extends android.os.BaseBundle implements java.lang.Cloneable android.os.Parcelable {
@@ -37093,6 +37175,7 @@
     field public static final String ACTION_CAST_SETTINGS = "android.settings.CAST_SETTINGS";
     field public static final String ACTION_CHANNEL_NOTIFICATION_SETTINGS = "android.settings.CHANNEL_NOTIFICATION_SETTINGS";
     field public static final String ACTION_CONDITION_PROVIDER_SETTINGS = "android.settings.ACTION_CONDITION_PROVIDER_SETTINGS";
+    field public static final String ACTION_CREDENTIAL_PROVIDER = "android.settings.CREDENTIAL_PROVIDER";
     field public static final String ACTION_DATA_ROAMING_SETTINGS = "android.settings.DATA_ROAMING_SETTINGS";
     field public static final String ACTION_DATA_USAGE_SETTINGS = "android.settings.DATA_USAGE_SETTINGS";
     field public static final String ACTION_DATE_SETTINGS = "android.settings.DATE_SETTINGS";
@@ -43413,6 +43496,7 @@
     field public static final String KEY_CARRIER_SERVICE_NUMBER_STRING_ARRAY = "carrier_service_number_array";
     field public static final String KEY_CARRIER_SETTINGS_ACTIVITY_COMPONENT_NAME_STRING = "carrier_settings_activity_component_name_string";
     field public static final String KEY_CARRIER_SETTINGS_ENABLE_BOOL = "carrier_settings_enable_bool";
+    field public static final String KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE = "carrier_supported_satellite_services_per_provider_bundle";
     field public static final String KEY_CARRIER_SUPPORTS_OPP_DATA_AUTO_PROVISIONING_BOOL = "carrier_supports_opp_data_auto_provisioning_bool";
     field public static final String KEY_CARRIER_SUPPORTS_SS_OVER_UT_BOOL = "carrier_supports_ss_over_ut_bool";
     field public static final String KEY_CARRIER_SUPPORTS_TETHERING_BOOL = "carrier_supports_tethering_bool";
@@ -44824,6 +44908,7 @@
     method public boolean isNetworkRegistered();
     method public boolean isNetworkRoaming();
     method public boolean isNetworkSearching();
+    method public boolean isNonTerrestrialNetwork();
     method @Deprecated public boolean isRegistered();
     method @Deprecated public boolean isRoaming();
     method @Deprecated public boolean isSearching();
@@ -44918,6 +45003,7 @@
     method public static final boolean isStartsPostDial(char);
     method public static boolean isVoiceMailNumber(String);
     method public static boolean isWellFormedSmsAddress(String);
+    method public static boolean isWpsCallNumber(@Nullable String);
     method public static byte[] networkPortionToCalledPartyBCD(String);
     method public static byte[] networkPortionToCalledPartyBCDWithLength(String);
     method public static String normalizeNumber(String);
@@ -45052,6 +45138,7 @@
     method public boolean getRoaming();
     method public int getState();
     method public boolean isSearching();
+    method public boolean isUsingNonTerrestrialNetwork();
     method public void setIsManualSelection(boolean);
     method public void setOperatorName(String, String, String);
     method public void setRoaming(boolean);
@@ -45933,6 +46020,7 @@
     field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_THROTTLED = 2; // 0x2
     field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_TIMEOUT = 9; // 0x9
     field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_CANCELED = 6; // 0x6
+    field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_DISABLED = 16; // 0x10
     field public static final int SET_OPPORTUNISTIC_SUB_INACTIVE_SUBSCRIPTION = 2; // 0x2
     field public static final int SET_OPPORTUNISTIC_SUB_NO_OPPORTUNISTIC_SUB_AVAILABLE = 3; // 0x3
     field public static final int SET_OPPORTUNISTIC_SUB_REMOTE_SERVICE_EXCEPTION = 4; // 0x4
@@ -47145,6 +47233,7 @@
     method public int getBottomPadding();
     method public int getEllipsisCount(int);
     method public int getEllipsisStart(int);
+    method @NonNull public android.graphics.text.LineBreakConfig getLineBreakConfig();
     method public boolean getLineContainsTab(int);
     method public int getLineCount();
     method public int getLineDescent(int);
@@ -47166,6 +47255,7 @@
     method @NonNull public android.text.DynamicLayout.Builder setHyphenationFrequency(int);
     method @NonNull public android.text.DynamicLayout.Builder setIncludePad(boolean);
     method @NonNull public android.text.DynamicLayout.Builder setJustificationMode(int);
+    method @NonNull public android.text.DynamicLayout.Builder setLineBreakConfig(@NonNull android.graphics.text.LineBreakConfig);
     method @NonNull public android.text.DynamicLayout.Builder setLineSpacing(float, @FloatRange(from=0.0) float);
     method @NonNull public android.text.DynamicLayout.Builder setTextDirection(@NonNull android.text.TextDirectionHeuristic);
     method @NonNull public android.text.DynamicLayout.Builder setUseLineSpacingFromFallbacks(boolean);
@@ -48363,6 +48453,11 @@
     method public void writeToParcel(@NonNull android.os.Parcel, int);
   }
 
+  public class LineBreakConfigSpan {
+    ctor public LineBreakConfigSpan(@NonNull android.graphics.text.LineBreakConfig);
+    method @NonNull public android.graphics.text.LineBreakConfig getLineBreakConfig();
+  }
+
   public interface LineHeightSpan extends android.text.style.ParagraphStyle android.text.style.WrapTogetherSpan {
     method public void chooseHeight(CharSequence, int, int, int, int, android.graphics.Paint.FontMetricsInt);
   }
@@ -49031,7 +49126,7 @@
     method public abstract void captureEndValues(android.transition.TransitionValues);
     method public abstract void captureStartValues(android.transition.TransitionValues);
     method public android.transition.Transition clone();
-    method public android.animation.Animator createAnimator(android.view.ViewGroup, android.transition.TransitionValues, android.transition.TransitionValues);
+    method @Nullable public android.animation.Animator createAnimator(@NonNull android.view.ViewGroup, @Nullable android.transition.TransitionValues, @Nullable android.transition.TransitionValues);
     method public android.transition.Transition excludeChildren(int, boolean);
     method public android.transition.Transition excludeChildren(android.view.View, boolean);
     method public android.transition.Transition excludeChildren(Class, boolean);
@@ -51594,7 +51689,7 @@
     field public static final int TYPE_CONTEXT_MENU = 1001; // 0x3e9
     field public static final int TYPE_COPY = 1011; // 0x3f3
     field public static final int TYPE_CROSSHAIR = 1007; // 0x3ef
-    field public static final int TYPE_DEFAULT = 1000; // 0x3e8
+    field @Deprecated public static final int TYPE_DEFAULT = 1000; // 0x3e8
     field public static final int TYPE_GRAB = 1020; // 0x3fc
     field public static final int TYPE_GRABBING = 1021; // 0x3fd
     field public static final int TYPE_HAND = 1002; // 0x3ea
@@ -54340,6 +54435,7 @@
     method public boolean isEnabled();
     method public boolean isFocusable();
     method public boolean isFocused();
+    method public boolean isGranularScrollingSupported();
     method public boolean isHeading();
     method public boolean isImportantForAccessibility();
     method public boolean isLongClickable();
@@ -54389,6 +54485,7 @@
     method public void setError(CharSequence);
     method public void setFocusable(boolean);
     method public void setFocused(boolean);
+    method public void setGranularScrollingSupported(boolean);
     method public void setHeading(boolean);
     method public void setHintText(CharSequence);
     method public void setImportantForAccessibility(boolean);
@@ -54443,6 +54540,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 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";
@@ -54545,7 +54643,10 @@
   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 public int getRowCount();
     method public int getSelectionMode();
     method public boolean isHierarchical();
@@ -54554,6 +54655,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
+  }
+
+  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);
   }
 
   public static final class AccessibilityNodeInfo.CollectionItemInfo {
@@ -54874,6 +54987,8 @@
   public class AnimationUtils {
     ctor public AnimationUtils();
     method public static long currentAnimationTimeMillis();
+    method public static long getExpectedPresentationTimeMillis();
+    method public static long getExpectedPresentationTimeNanos();
     method public static android.view.animation.Animation loadAnimation(android.content.Context, @AnimRes int) throws android.content.res.Resources.NotFoundException;
     method public static android.view.animation.Interpolator loadInterpolator(android.content.Context, @AnimRes @InterpolatorRes int) throws android.content.res.Resources.NotFoundException;
     method public static android.view.animation.LayoutAnimationController loadLayoutAnimation(android.content.Context, @AnimRes int) throws android.content.res.Resources.NotFoundException;
@@ -55062,6 +55177,7 @@
 
   public final class AutofillManager {
     method public void cancel();
+    method public void clearAutofillRequestCallback();
     method public void commit();
     method public void disableAutofillServices();
     method @Nullable public android.content.ComponentName getAutofillServiceComponentName();
@@ -55088,6 +55204,7 @@
     method public void registerCallback(@Nullable android.view.autofill.AutofillManager.AutofillCallback);
     method public void requestAutofill(@NonNull android.view.View);
     method public void requestAutofill(@NonNull android.view.View, int, @NonNull android.graphics.Rect);
+    method @RequiresPermission(android.Manifest.permission.PROVIDE_OWN_AUTOFILL_SUGGESTIONS) public void setAutofillRequestCallback(@NonNull java.util.concurrent.Executor, @NonNull android.view.autofill.AutofillRequestCallback);
     method public void setUserData(@Nullable android.service.autofill.UserData);
     method public boolean showAutofillDialog(@NonNull android.view.View);
     method public boolean showAutofillDialog(@NonNull android.view.View, int);
@@ -55108,6 +55225,10 @@
     field public static final int EVENT_INPUT_UNAVAILABLE = 3; // 0x3
   }
 
+  public interface AutofillRequestCallback {
+    method public void onFillRequest(@Nullable android.view.inputmethod.InlineSuggestionsRequest, @NonNull android.os.CancellationSignal, @NonNull android.service.autofill.FillCallback);
+  }
+
   public final class AutofillValue implements android.os.Parcelable {
     method public int describeContents();
     method public static android.view.autofill.AutofillValue forDate(long);
@@ -55557,10 +55678,12 @@
     ctor public InlineSuggestionsRequest.Builder(@NonNull java.util.List<android.widget.inline.InlinePresentationSpec>);
     method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder addInlinePresentationSpecs(@NonNull android.widget.inline.InlinePresentationSpec);
     method @NonNull public android.view.inputmethod.InlineSuggestionsRequest build();
+    method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder setClientSupported(boolean);
     method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder setExtras(@NonNull android.os.Bundle);
     method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder setInlinePresentationSpecs(@NonNull java.util.List<android.widget.inline.InlinePresentationSpec>);
     method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder setInlineTooltipPresentationSpec(@NonNull android.widget.inline.InlinePresentationSpec);
     method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder setMaxSuggestionCount(int);
+    method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder setServiceSupported(boolean);
     method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder setSupportedLocales(@NonNull android.os.LocaleList);
   }
 
@@ -55699,7 +55822,7 @@
     field public static final String SERVICE_INTERFACE = "android.view.InputMethod";
     field public static final String SERVICE_META_DATA = "android.view.im";
     field public static final int SHOW_EXPLICIT = 1; // 0x1
-    field public static final int SHOW_FORCED = 2; // 0x2
+    field @Deprecated public static final int SHOW_FORCED = 2; // 0x2
   }
 
   public static interface InputMethod.SessionCallback {
@@ -59597,7 +59720,7 @@
     method public void setRadioGroupChecked(@IdRes int, @IdRes int);
     method public void setRelativeScrollPosition(@IdRes int, int);
     method @Deprecated public void setRemoteAdapter(int, @IdRes int, android.content.Intent);
-    method public void setRemoteAdapter(@IdRes int, android.content.Intent);
+    method @Deprecated public void setRemoteAdapter(@IdRes int, android.content.Intent);
     method public void setRemoteAdapter(@IdRes int, @NonNull android.widget.RemoteViews.RemoteCollectionItems);
     method public void setScrollPosition(@IdRes int, int);
     method public void setShort(@IdRes int, String, short);
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index ace7d59..b22884f 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -157,6 +157,7 @@
     field public static final String KILL_ALL_BACKGROUND_PROCESSES = "android.permission.KILL_ALL_BACKGROUND_PROCESSES";
     field public static final String KILL_UID = "android.permission.KILL_UID";
     field public static final String LAUNCH_DEVICE_MANAGER_SETUP = "android.permission.LAUNCH_DEVICE_MANAGER_SETUP";
+    field public static final String LAUNCH_PERMISSION_SETTINGS = "android.permission.LAUNCH_PERMISSION_SETTINGS";
     field public static final String LOCAL_MAC_ADDRESS = "android.permission.LOCAL_MAC_ADDRESS";
     field public static final String LOCATION_BYPASS = "android.permission.LOCATION_BYPASS";
     field public static final String LOCK_DEVICE = "android.permission.LOCK_DEVICE";
@@ -271,6 +272,7 @@
     field public static final String READ_DEVICE_CONFIG = "android.permission.READ_DEVICE_CONFIG";
     field public static final String READ_DREAM_STATE = "android.permission.READ_DREAM_STATE";
     field public static final String READ_GLOBAL_APP_SEARCH_DATA = "android.permission.READ_GLOBAL_APP_SEARCH_DATA";
+    field public static final String READ_INSTALLED_SESSION_PATHS = "android.permission.READ_INSTALLED_SESSION_PATHS";
     field public static final String READ_INSTALL_SESSIONS = "android.permission.READ_INSTALL_SESSIONS";
     field public static final String READ_NETWORK_USAGE_HISTORY = "android.permission.READ_NETWORK_USAGE_HISTORY";
     field public static final String READ_OEM_UNLOCK_STATE = "android.permission.READ_OEM_UNLOCK_STATE";
@@ -453,6 +455,7 @@
     field public static final int config_defaultCallScreening = 17039398; // 0x1040026
     field public static final int config_defaultDialer = 17039395; // 0x1040023
     field public static final int config_defaultNotes = 17039429; // 0x1040045
+    field public static final int config_defaultRetailDemo;
     field public static final int config_defaultSms = 17039396; // 0x1040024
     field public static final int config_devicePolicyManagement = 17039421; // 0x104003d
     field public static final int config_feedbackIntentExtraKey = 17039391; // 0x104001f
@@ -969,19 +972,8 @@
   }
 
   public static final class Notification.TvExtender implements android.app.Notification.Extender {
-    ctor public Notification.TvExtender();
-    ctor public Notification.TvExtender(android.app.Notification);
-    method public android.app.Notification.Builder extend(android.app.Notification.Builder);
-    method public String getChannelId();
-    method public android.app.PendingIntent getContentIntent();
-    method public android.app.PendingIntent getDeleteIntent();
     method public boolean getSuppressShowOverApps();
-    method public boolean isAvailableOnTv();
     method public android.app.Notification.TvExtender setChannel(String);
-    method public android.app.Notification.TvExtender setChannelId(String);
-    method public android.app.Notification.TvExtender setContentIntent(android.app.PendingIntent);
-    method public android.app.Notification.TvExtender setDeleteIntent(android.app.PendingIntent);
-    method public android.app.Notification.TvExtender setSuppressShowOverApps(boolean);
   }
 
   public final class NotificationChannel implements android.os.Parcelable {
@@ -3209,12 +3201,13 @@
     method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualTouchscreen createVirtualTouchscreen(@NonNull android.hardware.input.VirtualTouchscreenConfig);
     method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualTouchscreen createVirtualTouchscreen(@NonNull android.hardware.display.VirtualDisplay, @NonNull String, int, int);
     method public int getDeviceId();
+    method @Nullable public String getPersistentDeviceId();
     method @NonNull public java.util.List<android.companion.virtual.sensor.VirtualSensor> getVirtualSensorList();
     method public void launchPendingIntent(int, @NonNull android.app.PendingIntent, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.IntConsumer);
     method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void registerIntentInterceptor(@NonNull android.content.IntentFilter, @NonNull java.util.concurrent.Executor, @NonNull android.companion.virtual.VirtualDeviceManager.IntentInterceptorCallback);
     method public void removeActivityListener(@NonNull android.companion.virtual.VirtualDeviceManager.ActivityListener);
     method public void removeSoundEffectListener(@NonNull android.companion.virtual.VirtualDeviceManager.SoundEffectListener);
-    method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void setShowPointerIcon(boolean);
+    method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void setShowPointerIcon(boolean);
     method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void unregisterIntentInterceptor(@NonNull android.companion.virtual.VirtualDeviceManager.IntentInterceptorCallback);
   }
 
@@ -3524,7 +3517,7 @@
     field @Deprecated public static final String ACTION_INTENT_FILTER_NEEDS_VERIFICATION = "android.intent.action.INTENT_FILTER_NEEDS_VERIFICATION";
     field public static final String ACTION_LOAD_DATA = "android.intent.action.LOAD_DATA";
     field @RequiresPermission(android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS) public static final String ACTION_MANAGE_APP_PERMISSION = "android.intent.action.MANAGE_APP_PERMISSION";
-    field public static final String ACTION_MANAGE_APP_PERMISSIONS = "android.intent.action.MANAGE_APP_PERMISSIONS";
+    field @Deprecated public static final String ACTION_MANAGE_APP_PERMISSIONS = "android.intent.action.MANAGE_APP_PERMISSIONS";
     field @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) public static final String ACTION_MANAGE_DEFAULT_APP = "android.intent.action.MANAGE_DEFAULT_APP";
     field public static final String ACTION_MANAGE_PERMISSIONS = "android.intent.action.MANAGE_PERMISSIONS";
     field public static final String ACTION_MANAGE_PERMISSION_APPS = "android.intent.action.MANAGE_PERMISSION_APPS";
@@ -3796,6 +3789,7 @@
 
   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 @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";
@@ -3816,6 +3810,7 @@
 
   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 public int getInstallLocation();
     method @NonNull public String getPackageName();
   }
@@ -3842,6 +3837,7 @@
     method public boolean getInstallAsVirtualPreload();
     method public int getPendingUserActionReason();
     method public boolean getRequestDowngrade();
+    method @Nullable @RequiresPermission(android.Manifest.permission.READ_INSTALLED_SESSION_PATHS) public String getResolvedBaseApkPath();
     method public int getRollbackDataPolicy();
     method @NonNull public java.util.Set<java.lang.String> getWhitelistedRestrictedPermissions();
   }
@@ -3912,7 +3908,7 @@
     method @NonNull @RequiresPermission(android.Manifest.permission.SUSPEND_APPS) public String[] setDistractingPackageRestrictions(@NonNull String[], int);
     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(android.Manifest.permission.SUSPEND_APPS) public String[] setPackagesSuspended(@Nullable String[], boolean, @Nullable android.os.PersistableBundle, @Nullable android.os.PersistableBundle, @Nullable android.content.pm.SuspendDialogInfo);
+    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 @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);
@@ -3929,6 +3925,7 @@
     field public static final int DELETE_FAILED_OWNER_BLOCKED = -4; // 0xfffffffc
     field public static final int DELETE_KEEP_DATA = 1; // 0x1
     field public static final int DELETE_SUCCEEDED = 1; // 0x1
+    field public static final String EXTRA_REQUEST_PERMISSIONS_DEVICE_ID = "android.content.pm.extra.REQUEST_PERMISSIONS_DEVICE_ID";
     field public static final String EXTRA_REQUEST_PERMISSIONS_LEGACY_ACCESS_PERMISSION_NAMES = "android.content.pm.extra.REQUEST_PERMISSIONS_LEGACY_ACCESS_PERMISSION_NAMES";
     field public static final String EXTRA_REQUEST_PERMISSIONS_NAMES = "android.content.pm.extra.REQUEST_PERMISSIONS_NAMES";
     field public static final String EXTRA_REQUEST_PERMISSIONS_RESULTS = "android.content.pm.extra.REQUEST_PERMISSIONS_RESULTS";
@@ -10122,6 +10119,7 @@
     method public int getDeviceType();
     method @NonNull public android.os.Bundle getExtras();
     method @NonNull public String getModelName();
+    method public boolean isBatteryCharging();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.sharedconnectivity.app.NetworkProviderInfo> CREATOR;
     field public static final int DEVICE_TYPE_AUTO = 5; // 0x5
@@ -10135,6 +10133,7 @@
   public static final class NetworkProviderInfo.Builder {
     ctor public NetworkProviderInfo.Builder(@NonNull String, @NonNull String);
     method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo build();
+    method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setBatteryCharging(boolean);
     method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setBatteryPercentage(@IntRange(from=0, to=100) int);
     method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setConnectionStrength(@IntRange(from=0, to=4) int);
     method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setDeviceName(@NonNull String);
@@ -10992,7 +10991,7 @@
     method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public boolean isPrimaryUser();
     method public static boolean isRemoveResultSuccessful(int);
     method public boolean isRestrictedProfile();
-    method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}, conditional=true) public boolean isRestrictedProfile(@NonNull android.os.UserHandle);
+    method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}, conditional=true) public boolean isRestrictedProfile(@NonNull android.os.UserHandle);
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.QUERY_USERS}) public boolean isSameProfileGroup(@NonNull android.os.UserHandle, @NonNull android.os.UserHandle);
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS, android.Manifest.permission.GET_ACCOUNTS_PRIVILEGED}) public boolean isUserNameSet();
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public boolean isUserOfType(@NonNull String);
@@ -11234,7 +11233,7 @@
 
   public abstract class PermissionControllerService extends android.app.Service {
     ctor public PermissionControllerService();
-    method @NonNull @RequiresPermission("android.permission.MANAGE_COMPANION_DEVICES") public String getPrivilegesDescriptionStringForProfile(@NonNull String);
+    method @Deprecated @NonNull @RequiresPermission("android.permission.MANAGE_COMPANION_DEVICES") public String getPrivilegesDescriptionStringForProfile(@NonNull String);
     method @BinderThread public void onApplyStagedRuntimePermissionBackup(@NonNull String, @NonNull android.os.UserHandle, @NonNull java.util.function.Consumer<java.lang.Boolean>);
     method @NonNull public final android.os.IBinder onBind(android.content.Intent);
     method @BinderThread public abstract void onCountPermissionApps(@NonNull java.util.List<java.lang.String>, int, @NonNull java.util.function.IntConsumer);
@@ -11594,6 +11593,7 @@
     method @Deprecated public static boolean checkAndNoteWriteSettingsOperation(@NonNull android.content.Context, int, @NonNull String, boolean);
     method public static boolean checkAndNoteWriteSettingsOperation(@NonNull android.content.Context, int, @NonNull String, @Nullable String, boolean);
     field public static final String ACTION_ACCESSIBILITY_DETAILS_SETTINGS = "android.settings.ACCESSIBILITY_DETAILS_SETTINGS";
+    field @RequiresPermission(android.Manifest.permission.LAUNCH_PERMISSION_SETTINGS) public static final String ACTION_APP_PERMISSIONS_SETTINGS = "android.settings.APP_PERMISSIONS_SETTINGS";
     field public static final String ACTION_BEDTIME_SETTINGS = "android.settings.BEDTIME_SETTINGS";
     field public static final String ACTION_BUGREPORT_HANDLER_SETTINGS = "android.settings.BUGREPORT_HANDLER_SETTINGS";
     field public static final String ACTION_ENTERPRISE_PRIVACY_SETTINGS = "android.settings.ENTERPRISE_PRIVACY_SETTINGS";
@@ -11677,6 +11677,8 @@
 
   public static final class Settings.System extends android.provider.Settings.NameValueTable {
     method @RequiresPermission(android.Manifest.permission.MODIFY_SETTINGS_OVERRIDEABLE_BY_RESTORE) public static boolean putString(@NonNull android.content.ContentResolver, @NonNull String, @Nullable String, boolean);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_SETTINGS_OVERRIDEABLE_BY_RESTORE) public static boolean putString(@NonNull android.content.ContentResolver, @NonNull String, @Nullable String, boolean, boolean);
+    method public static void resetToDefaults(@NonNull android.content.ContentResolver, @Nullable String);
   }
 
   public static final class SimPhonebookContract.SimRecords {
@@ -14208,6 +14210,7 @@
     method @NonNull public android.telephony.NetworkRegistrationInfo.Builder setCellIdentity(@Nullable android.telephony.CellIdentity);
     method @NonNull public android.telephony.NetworkRegistrationInfo.Builder setDomain(int);
     method @NonNull public android.telephony.NetworkRegistrationInfo.Builder setEmergencyOnly(boolean);
+    method @NonNull public android.telephony.NetworkRegistrationInfo.Builder setIsNonTerrestrialNetwork(boolean);
     method @NonNull public android.telephony.NetworkRegistrationInfo.Builder setRegisteredPlmn(@Nullable String);
     method @NonNull public android.telephony.NetworkRegistrationInfo.Builder setRegistrationState(int);
     method @NonNull public android.telephony.NetworkRegistrationInfo.Builder setRejectCause(int);
@@ -15222,7 +15225,7 @@
     method @NonNull public android.telephony.data.DataCallResponse.Builder setGatewayAddresses(@NonNull java.util.List<java.net.InetAddress>);
     method @NonNull public android.telephony.data.DataCallResponse.Builder setHandoverFailureMode(int);
     method @NonNull public android.telephony.data.DataCallResponse.Builder setId(int);
-    method @NonNull public android.telephony.data.DataCallResponse.Builder setInterfaceName(@NonNull String);
+    method @NonNull public android.telephony.data.DataCallResponse.Builder setInterfaceName(@Nullable String);
     method @NonNull public android.telephony.data.DataCallResponse.Builder setLinkStatus(int);
     method @Deprecated @NonNull public android.telephony.data.DataCallResponse.Builder setMtu(int);
     method @NonNull public android.telephony.data.DataCallResponse.Builder setMtuV4(int);
diff --git a/core/api/system-lint-baseline.txt b/core/api/system-lint-baseline.txt
index 025e862..0100f0e 100644
--- a/core/api/system-lint-baseline.txt
+++ b/core/api/system-lint-baseline.txt
@@ -143,12 +143,16 @@
     SAM-compatible parameters (such as parameter 1, "listener", in android.media.AudioFocusRequest.Builder.setOnAudioFocusChangeListener) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
 SamShouldBeLast: android.media.AudioManager#requestAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, int, int):
     SAM-compatible parameters (such as parameter 1, "l", in android.media.AudioManager.requestAudioFocus) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+SamShouldBeLast: android.media.AudioRecord#addOnRoutingChangedListener(android.media.AudioRecord.OnRoutingChangedListener, android.os.Handler):
+    SAM-compatible parameters (such as parameter 1, "listener", in android.media.AudioRecord.addOnRoutingChangedListener) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
 SamShouldBeLast: android.media.AudioRecord#addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler):
     SAM-compatible parameters (such as parameter 1, "listener", in android.media.AudioRecord.addOnRoutingChangedListener) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
 SamShouldBeLast: android.media.AudioRouting#addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler):
     SAM-compatible parameters (such as parameter 1, "listener", in android.media.AudioRouting.addOnRoutingChangedListener) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
 SamShouldBeLast: android.media.AudioTrack#addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler):
     SAM-compatible parameters (such as parameter 1, "listener", in android.media.AudioTrack.addOnRoutingChangedListener) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+SamShouldBeLast: android.media.AudioTrack#addOnRoutingChangedListener(android.media.AudioTrack.OnRoutingChangedListener, android.os.Handler):
+    SAM-compatible parameters (such as parameter 1, "listener", in android.media.AudioTrack.addOnRoutingChangedListener) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
 SamShouldBeLast: android.media.MediaCodec#setOnFrameRenderedListener(android.media.MediaCodec.OnFrameRenderedListener, android.os.Handler):
     SAM-compatible parameters (such as parameter 1, "listener", in android.media.MediaCodec.setOnFrameRenderedListener) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
 SamShouldBeLast: android.media.MediaPlayer#addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler):
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 0c9569f..083c445 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -540,6 +540,7 @@
     field public static final String PERMITTED_INPUT_METHODS_POLICY = "permittedInputMethods";
     field public static final String PERSONAL_APPS_SUSPENDED_POLICY = "personalAppsSuspended";
     field public static final String SCREEN_CAPTURE_DISABLED_POLICY = "screenCaptureDisabled";
+    field public static final String USB_DATA_SIGNALING_POLICY = "usbDataSignaling";
   }
 
   public class DevicePolicyManager {
@@ -848,6 +849,8 @@
     ctor public AttributionSource(int, @Nullable String, @Nullable String);
     ctor public AttributionSource(int, @Nullable String, @Nullable String, @NonNull android.os.IBinder);
     ctor public AttributionSource(int, @Nullable String, @Nullable String, @Nullable java.util.Set<java.lang.String>, @Nullable android.content.AttributionSource);
+    ctor public AttributionSource(int, int, @Nullable String, @Nullable String, @NonNull android.os.IBinder, @Nullable String[], @Nullable android.content.AttributionSource);
+    ctor public AttributionSource(int, int, @Nullable String, @Nullable String, @NonNull android.os.IBinder, @Nullable String[], int, @Nullable android.content.AttributionSource);
     method public void enforceCallingPid();
   }
 
@@ -1048,6 +1051,7 @@
     method public android.os.UserHandle getUserHandle();
     method public boolean isAdmin();
     method public boolean isCloneProfile();
+    method public boolean isCommunalProfile();
     method public boolean isDemo();
     method public boolean isEnabled();
     method public boolean isEphemeral();
@@ -1058,6 +1062,7 @@
     method public boolean isMain();
     method public boolean isManagedProfile();
     method @Deprecated public boolean isPrimary();
+    method public boolean isPrivateProfile();
     method public boolean isProfile();
     method public boolean isQuietModeEnabled();
     method public boolean isRestricted();
@@ -1970,6 +1975,39 @@
     method public android.media.PlaybackParams setAudioStretchMode(int);
   }
 
+  public final class RingtoneSelection {
+    method @NonNull public static android.media.RingtoneSelection fromUri(@Nullable android.net.Uri, int);
+    method public int getSoundSource();
+    method @Nullable public android.net.Uri getSoundUri();
+    method public int getVibrationSource();
+    method @Nullable public android.net.Uri getVibrationUri();
+    method public static boolean isRingtoneSelectionUri(@Nullable android.net.Uri);
+    method @NonNull public android.net.Uri toUri();
+    field public static final String DEFAULT_SELECTION_URI_STRING = "content://media/ringtone";
+    field public static final int FROM_URI_RINGTONE_SELECTION_ONLY = 3; // 0x3
+    field public static final int FROM_URI_RINGTONE_SELECTION_OR_SOUND = 1; // 0x1
+    field public static final int FROM_URI_RINGTONE_SELECTION_OR_VIBRATION = 2; // 0x2
+    field public static final int SOUND_SOURCE_DEFAULT = 0; // 0x0
+    field public static final int SOUND_SOURCE_OFF = 1; // 0x1
+    field public static final int SOUND_SOURCE_URI = 2; // 0x2
+    field public static final int VIBRATION_SOURCE_APPLICATION_PROVIDED = 3; // 0x3
+    field public static final int VIBRATION_SOURCE_AUDIO_CHANNEL = 10; // 0xa
+    field public static final int VIBRATION_SOURCE_DEFAULT = 0; // 0x0
+    field public static final int VIBRATION_SOURCE_HAPTIC_GENERATOR = 11; // 0xb
+    field public static final int VIBRATION_SOURCE_OFF = 1; // 0x1
+    field public static final int VIBRATION_SOURCE_URI = 2; // 0x2
+  }
+
+  public static final class RingtoneSelection.Builder {
+    ctor public RingtoneSelection.Builder();
+    ctor public RingtoneSelection.Builder(@NonNull android.media.RingtoneSelection);
+    method @NonNull public android.media.RingtoneSelection build();
+    method @NonNull public android.media.RingtoneSelection.Builder setSoundSource(int);
+    method @NonNull public android.media.RingtoneSelection.Builder setSoundSource(@NonNull android.net.Uri);
+    method @NonNull public android.media.RingtoneSelection.Builder setVibrationSource(int);
+    method @NonNull public android.media.RingtoneSelection.Builder setVibrationSource(@NonNull android.net.Uri);
+  }
+
   public static final class VolumeShaper.Configuration.Builder {
     method @NonNull public android.media.VolumeShaper.Configuration.Builder setOptionFlags(int);
   }
@@ -2167,6 +2205,10 @@
 
 package android.os {
 
+  public class BatteryManager {
+    field public static final int BATTERY_PLUGGED_ANY = 15; // 0xf
+  }
+
   public final class BatteryStatsManager {
     method @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public void resetBattery(boolean);
     method @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public void setBatteryLevel(int, boolean);
@@ -2632,6 +2674,21 @@
 
 }
 
+package android.os.vibrator.persistence {
+
+  public final class VibrationXmlParser {
+    method @Nullable public static android.os.VibrationEffect parse(@NonNull java.io.Reader) throws java.io.IOException;
+  }
+
+  public final class VibrationXmlSerializer {
+    method public static void serialize(@NonNull android.os.VibrationEffect, @NonNull java.io.Writer) throws java.io.IOException, android.os.vibrator.persistence.VibrationXmlSerializer.SerializationFailedException;
+  }
+
+  public static final class VibrationXmlSerializer.SerializationFailedException extends java.lang.RuntimeException {
+  }
+
+}
+
 package android.permission {
 
   public final class PermissionControllerManager {
@@ -2958,6 +3015,10 @@
     method @Deprecated public boolean isBound();
   }
 
+  public class NotificationRankingUpdate implements android.os.Parcelable {
+    method public final boolean isFdNotNullAndClosed();
+  }
+
 }
 
 package android.service.quickaccesswallet {
@@ -3187,6 +3248,7 @@
     field public static final int HAL_SERVICE_MESSAGING = 2; // 0x2
     field public static final int HAL_SERVICE_MODEM = 3; // 0x3
     field public static final int HAL_SERVICE_NETWORK = 4; // 0x4
+    field public static final int HAL_SERVICE_SATELLITE = 8; // 0x8
     field public static final int HAL_SERVICE_SIM = 5; // 0x5
     field public static final int HAL_SERVICE_VOICE = 6; // 0x6
     field public static final android.util.Pair HAL_VERSION_UNKNOWN;
@@ -3275,6 +3337,15 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.text.FontConfig.NamedFamilyList> CREATOR;
   }
 
+  public class MeasuredParagraph {
+    method @NonNull public static android.text.MeasuredParagraph buildForStaticLayoutTest(@NonNull android.text.TextPaint, @Nullable android.graphics.text.LineBreakConfig, @NonNull CharSequence, @IntRange(from=0) int, @IntRange(from=0) int, @NonNull android.text.TextDirectionHeuristic, int, boolean, @Nullable android.text.MeasuredParagraph.StyleRunCallback);
+  }
+
+  public static interface MeasuredParagraph.StyleRunCallback {
+    method public void onAppendReplacementRun(@NonNull android.graphics.Paint, @IntRange(from=0) int, @FloatRange(from=0) @Px float);
+    method public void onAppendStyleRun(@NonNull android.graphics.Paint, @Nullable android.graphics.text.LineBreakConfig, @IntRange(from=0) int, boolean);
+  }
+
   public static final class Selection.MemoryTextWatcher implements android.text.TextWatcher {
     ctor public Selection.MemoryTextWatcher();
     method public void afterTextChanged(android.text.Editable);
@@ -3394,6 +3465,7 @@
 
   public final class Choreographer {
     method public static long getFrameDelay();
+    method public long getFrameTimeNanos();
     method public void postCallback(int, Runnable, Object);
     method public void postCallbackDelayed(int, Runnable, Object, long);
     method public void removeCallbacks(int, Runnable, Object);
@@ -3551,6 +3623,8 @@
     method public default void holdLock(android.os.IBinder, int);
     method public default boolean isGlobalKey(int);
     method public default boolean isTaskSnapshotSupported();
+    method @RequiresPermission(android.Manifest.permission.ACCESS_SURFACE_FLINGER) public default boolean replaceContentOnDisplayWithMirror(int, @NonNull android.view.Window);
+    method @RequiresPermission(android.Manifest.permission.ACCESS_SURFACE_FLINGER) public default boolean replaceContentOnDisplayWithSc(int, @NonNull android.view.SurfaceControl);
     method public default void setDisplayImePolicy(int, int);
     method public default void setShouldShowSystemDecors(int, boolean);
     method public default void setShouldShowWithInsecureKeyguard(int, boolean);
@@ -3599,6 +3673,7 @@
 
   public final class AccessibilityWindowInfo implements android.os.Parcelable {
     method public static void setNumInstancesInUseCounter(java.util.concurrent.atomic.AtomicInteger);
+    field public static final int UNDEFINED_WINDOW_ID = -1; // 0xffffffff
   }
 
 }
@@ -3606,7 +3681,7 @@
 package android.view.animation {
 
   public class AnimationUtils {
-    method public static void lockAnimationClock(long);
+    method public static void lockAnimationClock(long, long);
     method public static void unlockAnimationClock();
   }
 
@@ -4161,6 +4236,7 @@
     field public final boolean isTrustedOverlay;
     field public final boolean isVisible;
     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 cf02643..4a97280 100644
--- a/core/api/test-lint-baseline.txt
+++ b/core/api/test-lint-baseline.txt
@@ -183,10 +183,6 @@
     android.telecom.ConnectionRequest does not declare a `shouldShowIncomingCallUi()` method matching method android.telecom.ConnectionRequest.Builder.setShouldShowIncomingCallUi(boolean)
 MissingGetterMatchingBuilder: android.view.Display.Mode.Builder#setResolution(int, int):
     android.view.Display.Mode does not declare a `getResolution()` method matching method android.view.Display.Mode.Builder.setResolution(int,int)
-MissingGetterMatchingBuilder: android.view.inputmethod.InlineSuggestionsRequest.Builder#setClientSupported(boolean):
-    android.view.inputmethod.InlineSuggestionsRequest does not declare a `isClientSupported()` method matching method android.view.inputmethod.InlineSuggestionsRequest.Builder.setClientSupported(boolean)
-MissingGetterMatchingBuilder: android.view.inputmethod.InlineSuggestionsRequest.Builder#setServiceSupported(boolean):
-    android.view.inputmethod.InlineSuggestionsRequest does not declare a `isServiceSupported()` method matching method android.view.inputmethod.InlineSuggestionsRequest.Builder.setServiceSupported(boolean)
 
 
 MissingNullability: android.app.Activity#onMovedToDisplay(int, android.content.res.Configuration) parameter #1:
diff --git a/core/java/Android.bp b/core/java/Android.bp
index 02b14ad..70cb597 100644
--- a/core/java/Android.bp
+++ b/core/java/Android.bp
@@ -23,9 +23,14 @@
     srcs: ["android/security/keymaster/IKeyAttestationApplicationIdProvider.aidl"],
 }
 
-filegroup {
-    name: "IDropBoxManagerService.aidl",
-    srcs: ["com/android/internal/os/IDropBoxManagerService.aidl"],
+aidl_library {
+    name: "IDropBoxManagerService_aidl",
+    srcs: [
+        "com/android/internal/os/IDropBoxManagerService.aidl",
+    ],
+    hdrs: [
+        "android/os/DropBoxManager.aidl",
+    ],
 }
 
 filegroup {
@@ -138,7 +143,7 @@
     ],
 }
 
-filegroup {
+aidl_library {
     name: "ILogcatManagerService_aidl",
     srcs: ["android/os/logcat/ILogcatManagerService.aidl"],
 }
@@ -510,6 +515,17 @@
     ],
 }
 
+// common protolog sources without classes that rely on Android SDK
+filegroup {
+    name: "protolog-common-no-android-src",
+    srcs: [
+        ":protolog-common-src",
+    ],
+    exclude_srcs: [
+        "com/android/internal/protolog/common/ProtoLog.java",
+    ],
+}
+
 java_library {
     name: "protolog-lib",
     platform_apis: true,
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index 3d4b6bf..e40ea09 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -81,6 +81,7 @@
 import java.util.List;
 import java.util.concurrent.Executor;
 import java.util.function.Consumer;
+import java.util.function.IntConsumer;
 
 /**
  * Accessibility services should only be used to assist users with disabilities in using
@@ -785,6 +786,39 @@
     public static final String KEY_ACCESSIBILITY_SCREENSHOT_TIMESTAMP =
             "screenshot_timestamp";
 
+
+    /**
+     * Annotations for result codes of attaching accessibility overlays.
+     *
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(
+            prefix = {"OVERLAY_RESULT_"},
+            value = {
+                OVERLAY_RESULT_SUCCESS,
+                OVERLAY_RESULT_INTERNAL_ERROR,
+                OVERLAY_RESULT_INVALID,
+            })
+    public @interface AttachOverlayResult {}
+
+    /** Result code indicating the overlay was successfully attached. */
+    public static final int OVERLAY_RESULT_SUCCESS = 0;
+
+    /**
+     * Result code indicating the overlay could not be attached due to an internal
+     * error and not
+     * because of problems with the input.
+     */
+    public static final int OVERLAY_RESULT_INTERNAL_ERROR = 1;
+
+    /**
+     * Result code indicating the overlay could not be attached because the
+     * specified display or
+     * window id was invalid.
+     */
+    public static final int OVERLAY_RESULT_INVALID = 2;
+
     private int mConnectionId = AccessibilityInteractionClient.NO_ID;
 
     @UnsupportedAppUsage
@@ -1204,7 +1238,21 @@
      * property in its meta-data. For more information, see
      * {@link #SERVICE_META_DATA}.
      * </p>
+     * <p>Since many apps do not appropriately support {@link AccessibilityAction#ACTION_CLICK},
+     * if this action fails on an element that should be clickable, a service that is not a screen
+     * reader may send a tap directly to the element as a fallback. The example below
+     * demonstrates this fallback using the gesture dispatch APIs:
      *
+     * <pre class="prettyprint"><code>
+     *     private void tap(PointF point) {
+     *         StrokeDescription tap =  new StrokeDescription(path(point), 0,
+     *         ViewConfiguration.getTapTimeout());
+     *         GestureDescription.Builder builder = new GestureDescription.Builder();
+     *         builder.addStroke(tap);
+     *         dispatchGesture(builder.build(), null, null);
+     *     }
+     *</code>
+     * </pre>
      * @param gesture The gesture to dispatch
      * @param callback The object to call back when the status of the gesture is known. If
      * {@code null}, no status is reported.
@@ -2580,10 +2628,6 @@
         IAccessibilityServiceConnection connection =
                 AccessibilityInteractionClient.getInstance(this).getConnection(mConnectionId);
         if (mInfo != null && connection != null) {
-            if (!mInfo.isWithinParcelableSize()) {
-                throw new IllegalStateException(
-                        "Cannot update service info: size is larger than safe parcelable limits.");
-            }
             try {
                 connection.setServiceInfo(mInfo);
                 mInfo = null;
@@ -3439,75 +3483,154 @@
     }
 
     /**
-     * <p>Attaches a {@link android.view.SurfaceControl} containing an accessibility
-     * overlay to the
-     * specified display. This type of overlay should be used for content that does
-     * not need to
-     * track the location and size of Views in the currently active app e.g. service
-     * configuration
-     * or general service UI.</p>
-     * <p>Generally speaking, an accessibility overlay  will be  a {@link android.view.View}.
-     * To embed the View into a {@link android.view.SurfaceControl}, create a
-     * {@link android.view.SurfaceControlViewHost} and attach the View using
-     * {@link android.view.SurfaceControlViewHost#setView}. Then obtain the SurfaceControl by
-     * calling <code> viewHost.getSurfacePackage().getSurfaceControl()</code>.</p>
-     * <p>To remove this overlay and free the associated
-     * resources, use
-     * <code> new SurfaceControl.Transaction().reparent(sc, null).apply();</code>.</p>
-     * <p>If the specified overlay has already been attached to the specified display
-     * this method does nothing.
-     * If the specified overlay has already been attached to a previous display this
-     * function will transfer the overlay to the new display.
-     * Services can attach multiple overlays. Use
-     * <code> new SurfaceControl.Transaction().setLayer(sc, layer).apply();</code>.
-     * to coordinate the order of the overlays on screen.</p>
+     * Attaches a {@link android.view.SurfaceControl} containing an accessibility overlay to the
+     * specified display. This type of overlay should be used for content that does not need to
+     * track the location and size of Views in the currently active app e.g. service configuration
+     * or general service UI.
+     *
+     * <p>Generally speaking, an accessibility overlay will be a {@link android.view.View}. To embed
+     * the View into a {@link android.view.SurfaceControl}, create a {@link
+     * android.view.SurfaceControlViewHost} and attach the View using {@link
+     * android.view.SurfaceControlViewHost#setView}. Then obtain the SurfaceControl by calling
+     * <code> viewHost.getSurfacePackage().getSurfaceControl()</code>.
+     *
+     * <p>To remove this overlay and free the associated resources, use <code>
+     *  new SurfaceControl.Transaction().reparent(sc, null).apply();</code>.
+     *
+     * <p>If the specified overlay has already been attached to the specified display this method
+     * does nothing. If the specified overlay has already been attached to a previous display this
+     * function will transfer the overlay to the new display. Services can attach multiple overlays.
+     * Use <code> new SurfaceControl.Transaction().setLayer(sc, layer).apply();</code>. to
+     * coordinate the order of the overlays on screen.
      *
      * @param displayId the display to which the SurfaceControl should be attached.
-     * @param sc        the SurfaceControl containing the overlay content
+     * @param sc the SurfaceControl containing the overlay content
+     *
+     * @deprecated Use
+     * {@link #attachAccessibilityOverlayToDisplay(int, SurfaceControl, Executor, IntConsumer)}
+     * instead.
      */
+    @Deprecated
     public void attachAccessibilityOverlayToDisplay(int displayId, @NonNull SurfaceControl sc) {
         Preconditions.checkNotNull(sc, "SurfaceControl cannot be null");
-        final IAccessibilityServiceConnection connection =
-                AccessibilityInteractionClient.getConnection(mConnectionId);
-        if (connection == null) {
-            return;
-        }
-        try {
-            connection.attachAccessibilityOverlayToDisplay(displayId, sc);
-        } catch (RemoteException re) {
-            re.rethrowFromSystemServer();
-        }
+        AccessibilityInteractionClient.getInstance(this)
+                .attachAccessibilityOverlayToDisplay(mConnectionId, displayId, sc, null, null);
     }
 
     /**
-     * <p>Attaches an accessibility overlay {@link android.view.SurfaceControl} to the
-     * specified
-     * window. This method should be used when you want the overlay to move and
-     * resize as the parent window moves and resizes.</p>
-     * <p>Generally speaking, an accessibility overlay  will be  a {@link android.view.View}.
-     * To embed the View into a {@link android.view.SurfaceControl}, create a
-     * {@link android.view.SurfaceControlViewHost} and attach the View using
-     * {@link android.view.SurfaceControlViewHost#setView}. Then obtain the SurfaceControl by
-     * calling <code> viewHost.getSurfacePackage().getSurfaceControl()</code>.</p>
-     * <p>To remove this overlay and free the associated resources, use
-     * <code> new SurfaceControl.Transaction().reparent(sc, null).apply();</code>.</p>
-     * <p>If the specified overlay has already been attached to the specified window
-     * this method does nothing.
-     * If the specified overlay has already been attached to a previous window this
-     * function will transfer the overlay to the new window.
-     * Services can attach multiple overlays. Use
-     * <code> new SurfaceControl.Transaction().setLayer(sc, layer).apply();</code>.
-     * to coordinate the order of the overlays on screen.</p>
+     * Attaches a {@link android.view.SurfaceControl} containing an accessibility overlay to the
+     * specified display. This type of overlay should be used for content that does not need to
+     * track the location and size of Views in the currently active app e.g. service configuration
+     * or general service UI.
      *
-     * @param accessibilityWindowId The window id, from
-     *                              {@link AccessibilityWindowInfo#getId()}.
-     * @param sc                    the SurfaceControl containing the overlay
-     *                              content
+     * <p>Generally speaking, an accessibility overlay will be a {@link android.view.View}. To embed
+     * the View into a {@link android.view.SurfaceControl}, create a {@link
+     * android.view.SurfaceControlViewHost} and attach the View using {@link
+     * android.view.SurfaceControlViewHost#setView}. Then obtain the SurfaceControl by calling
+     * <code> viewHost.getSurfacePackage().getSurfaceControl()</code>.
+     *
+     * <p>To remove this overlay and free the associated resources, use <code>
+     *  new SurfaceControl.Transaction().reparent(sc, null).apply();</code>.
+     *
+     * <p>If the specified overlay has already been attached to the specified display this method
+     * does nothing. If the specified overlay has already been attached to a previous display this
+     * function will transfer the overlay to the new display. Services can attach multiple overlays.
+     * Use <code> new SurfaceControl.Transaction().setLayer(sc, layer).apply();</code>. to
+     * coordinate the order of the overlays on screen.
+     *
+     * @param displayId the display to which the SurfaceControl should be attached.
+     * @param sc the SurfaceControl containing the overlay content
+     * @param executor Executor on which to run the callback.
+     * @param callback The callback invoked when attaching the overlay has succeeded or failed. The
+     *     callback is a {@link java.util.function.IntConsumer} of the result status code.
+     * @see OVERLAY_RESULT_SUCCESS
+     * @see OVERLAY_RESULT_INVALID
+     * @see OVERLAY_RESULT_INTERNAL_ERROR
      */
+    public void attachAccessibilityOverlayToDisplay(
+            int displayId,
+            @NonNull SurfaceControl sc,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull IntConsumer callback) {
+        Preconditions.checkNotNull(sc, "SurfaceControl cannot be null");
+        AccessibilityInteractionClient.getInstance(this)
+                .attachAccessibilityOverlayToDisplay(
+                        mConnectionId, displayId, sc, executor, callback);
+    }
+
+    /**
+     * Attaches an accessibility overlay {@link android.view.SurfaceControl} to the specified
+     * window. This method should be used when you want the overlay to move and resize as the parent
+     * window moves and resizes.
+     *
+     * <p>Generally speaking, an accessibility overlay will be a {@link android.view.View}. To embed
+     * the View into a {@link android.view.SurfaceControl}, create a {@link
+     * android.view.SurfaceControlViewHost} and attach the View using {@link
+     * android.view.SurfaceControlViewHost#setView}. Then obtain the SurfaceControl by calling
+     * <code> viewHost.getSurfacePackage().getSurfaceControl()</code>.
+     *
+     * <p>To remove this overlay and free the associated resources, use <code>
+     *  new SurfaceControl.Transaction().reparent(sc, null).apply();</code>.
+     *
+     * <p>If the specified overlay has already been attached to the specified window this method
+     * does nothing. If the specified overlay has already been attached to a previous window this
+     * function will transfer the overlay to the new window. Services can attach multiple overlays.
+     * Use <code> new SurfaceControl.Transaction().setLayer(sc, layer).apply();</code>. to
+     * coordinate the order of the overlays on screen.
+     *
+     * @param accessibilityWindowId The window id, from {@link AccessibilityWindowInfo#getId()}.
+     * @param sc the SurfaceControl containing the overlay content
+     *
+     * @deprecated Use
+     * {@link #attachAccessibilityOverlayToWindow(int, SurfaceControl, Executor,IntConsumer)}
+     * instead.
+     */
+    @Deprecated
     public void attachAccessibilityOverlayToWindow(
             int accessibilityWindowId, @NonNull SurfaceControl sc) {
         Preconditions.checkNotNull(sc, "SurfaceControl cannot be null");
         AccessibilityInteractionClient.getInstance(this)
-                .attachAccessibilityOverlayToWindow(mConnectionId, accessibilityWindowId, sc);
+                .attachAccessibilityOverlayToWindow(
+                        mConnectionId, accessibilityWindowId, sc, null, null);
+    }
+
+    /**
+     * Attaches an accessibility overlay {@link android.view.SurfaceControl} to the specified
+     * window. This method should be used when you want the overlay to move and resize as the parent
+     * window moves and resizes.
+     *
+     * <p>Generally speaking, an accessibility overlay will be a {@link android.view.View}. To embed
+     * the View into a {@link android.view.SurfaceControl}, create a {@link
+     * android.view.SurfaceControlViewHost} and attach the View using {@link
+     * android.view.SurfaceControlViewHost#setView}. Then obtain the SurfaceControl by calling
+     * <code> viewHost.getSurfacePackage().getSurfaceControl()</code>.
+     *
+     * <p>To remove this overlay and free the associated resources, use <code>
+     *  new SurfaceControl.Transaction().reparent(sc, null).apply();</code>.
+     *
+     * <p>If the specified overlay has already been attached to the specified window this method
+     * does nothing. If the specified overlay has already been attached to a previous window this
+     * function will transfer the overlay to the new window. Services can attach multiple overlays.
+     * Use <code> new SurfaceControl.Transaction().setLayer(sc, layer).apply();</code>. to
+     * coordinate the order of the overlays on screen.
+     *
+     * @param accessibilityWindowId The window id, from {@link AccessibilityWindowInfo#getId()}.
+     * @param sc the SurfaceControl containing the overlay content
+     * @param executor Executor on which to run the callback.
+     * @param callback The callback invoked when attaching the overlay has succeeded or failed. The
+     *     callback is a {@link java.util.function.IntConsumer} of the result status code.
+     * @see OVERLAY_RESULT_SUCCESS
+     * @see OVERLAY_RESULT_INVALID
+     * @see OVERLAY_RESULT_INTERNAL_ERROR
+     */
+    public void attachAccessibilityOverlayToWindow(
+            int accessibilityWindowId,
+            @NonNull SurfaceControl sc,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull IntConsumer callback) {
+        Preconditions.checkNotNull(sc, "SurfaceControl cannot be null");
+        AccessibilityInteractionClient.getInstance(this)
+                .attachAccessibilityOverlayToWindow(
+                        mConnectionId, accessibilityWindowId, sc, executor, callback);
     }
 }
diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
index d4a96b4..6550f30 100644
--- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
+++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
@@ -107,81 +107,82 @@
      * Capability: This accessibility service can retrieve the active window content.
      * @see android.R.styleable#AccessibilityService_canRetrieveWindowContent
      */
-    public static final int CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT = 0x00000001;
+    public static final int CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT = 1 /* << 0 */;
 
     /**
      * Capability: This accessibility service can request touch exploration mode in which
      * touched items are spoken aloud and the UI can be explored via gestures.
      * @see android.R.styleable#AccessibilityService_canRequestTouchExplorationMode
      */
-    public static final int CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION = 0x00000002;
+    public static final int CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION = 1 << 1;
 
     /**
      * @deprecated No longer used
      */
-    public static final int CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY = 0x00000004;
+    @Deprecated
+    public static final int CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY = 1 << 2;
 
     /**
      * Capability: This accessibility service can request to filter the key event stream.
      * @see android.R.styleable#AccessibilityService_canRequestFilterKeyEvents
      */
-    public static final int CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS = 0x00000008;
+    public static final int CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS = 1 << 3;
 
     /**
      * Capability: This accessibility service can control display magnification.
      * @see android.R.styleable#AccessibilityService_canControlMagnification
      */
-    public static final int CAPABILITY_CAN_CONTROL_MAGNIFICATION = 0x00000010;
+    public static final int CAPABILITY_CAN_CONTROL_MAGNIFICATION = 1 << 4;
 
     /**
      * Capability: This accessibility service can perform gestures.
      * @see android.R.styleable#AccessibilityService_canPerformGestures
      */
-    public static final int CAPABILITY_CAN_PERFORM_GESTURES = 0x00000020;
+    public static final int CAPABILITY_CAN_PERFORM_GESTURES = 1 << 5;
 
     /**
      * Capability: This accessibility service can capture gestures from the fingerprint sensor
      * @see android.R.styleable#AccessibilityService_canRequestFingerprintGestures
      */
-    public static final int CAPABILITY_CAN_REQUEST_FINGERPRINT_GESTURES = 0x00000040;
+    public static final int CAPABILITY_CAN_REQUEST_FINGERPRINT_GESTURES = 1 << 6;
 
     /**
      * Capability: This accessibility service can take screenshot.
      * @see android.R.styleable#AccessibilityService_canTakeScreenshot
      */
-    public static final int CAPABILITY_CAN_TAKE_SCREENSHOT = 0x00000080;
+    public static final int CAPABILITY_CAN_TAKE_SCREENSHOT = 1 << 7;
 
     private static SparseArray<CapabilityInfo> sAvailableCapabilityInfos;
 
     /**
      * Denotes spoken feedback.
      */
-    public static final int FEEDBACK_SPOKEN = 0x0000001;
+    public static final int FEEDBACK_SPOKEN = 1 /* << 0 */;
 
     /**
      * Denotes haptic feedback.
      */
-    public static final int FEEDBACK_HAPTIC =  0x0000002;
+    public static final int FEEDBACK_HAPTIC =  1 << 1;
 
     /**
      * Denotes audible (not spoken) feedback.
      */
-    public static final int FEEDBACK_AUDIBLE = 0x0000004;
+    public static final int FEEDBACK_AUDIBLE = 1 << 2;
 
     /**
      * Denotes visual feedback.
      */
-    public static final int FEEDBACK_VISUAL = 0x0000008;
+    public static final int FEEDBACK_VISUAL = 1 << 3;
 
     /**
      * Denotes generic feedback.
      */
-    public static final int FEEDBACK_GENERIC = 0x0000010;
+    public static final int FEEDBACK_GENERIC = 1 << 4;
 
     /**
      * Denotes braille feedback.
      */
-    public static final int FEEDBACK_BRAILLE = 0x0000020;
+    public static final int FEEDBACK_BRAILLE = 1 << 5;
 
     /**
      * Mask for all feedback types.
@@ -200,7 +201,7 @@
      * Default service is invoked only if no package specific one exists. In case of
      * more than one package specific service only the earlier registered is notified.
      */
-    public static final int DEFAULT = 0x0000001;
+    public static final int DEFAULT = 1 /* << 0 */;
 
     /**
      * If this flag is set the system will regard views that are not important
@@ -230,7 +231,7 @@
      * elements.
      * </p>
      */
-    public static final int FLAG_INCLUDE_NOT_IMPORTANT_VIEWS = 0x0000002;
+    public static final int FLAG_INCLUDE_NOT_IMPORTANT_VIEWS = 1 << 1;
 
     /**
      * This flag requests that the system gets into touch exploration mode.
@@ -258,12 +259,13 @@
      * </p>
      * @see android.R.styleable#AccessibilityService_canRequestTouchExplorationMode
      */
-    public static final int FLAG_REQUEST_TOUCH_EXPLORATION_MODE = 0x0000004;
+    public static final int FLAG_REQUEST_TOUCH_EXPLORATION_MODE = 1 << 2;
 
     /**
      * @deprecated No longer used
      */
-    public static final int FLAG_REQUEST_ENHANCED_WEB_ACCESSIBILITY = 0x00000008;
+    @Deprecated
+    public static final int FLAG_REQUEST_ENHANCED_WEB_ACCESSIBILITY = 1 << 3;
 
     /**
      * This flag requests that the {@link AccessibilityNodeInfo}s obtained
@@ -272,7 +274,7 @@
      * form "package:id/name", for example "foo.bar:id/my_list", and it is
      * useful for UI test automation. This flag is not set by default.
      */
-    public static final int FLAG_REPORT_VIEW_IDS = 0x00000010;
+    public static final int FLAG_REPORT_VIEW_IDS = 1 << 4;
 
     /**
      * This flag requests from the system to filter key events. If this flag
@@ -287,7 +289,7 @@
      * </p>
      * @see android.R.styleable#AccessibilityService_canRequestFilterKeyEvents
      */
-    public static final int FLAG_REQUEST_FILTER_KEY_EVENTS = 0x00000020;
+    public static final int FLAG_REQUEST_FILTER_KEY_EVENTS = 1 << 5;
 
     /**
      * This flag indicates to the system that the accessibility service wants
@@ -308,14 +310,14 @@
      * </p>
      * @see android.R.styleable#AccessibilityService_canRetrieveWindowContent
      */
-    public static final int FLAG_RETRIEVE_INTERACTIVE_WINDOWS = 0x00000040;
+    public static final int FLAG_RETRIEVE_INTERACTIVE_WINDOWS = 1 << 6;
 
     /**
      * This flag requests that all audio tracks system-wide with
      * {@link android.media.AudioAttributes#USAGE_ASSISTANCE_ACCESSIBILITY} be controlled by the
      * {@link android.media.AudioManager#STREAM_ACCESSIBILITY} volume.
      */
-    public static final int FLAG_ENABLE_ACCESSIBILITY_VOLUME = 0x00000080;
+    public static final int FLAG_ENABLE_ACCESSIBILITY_VOLUME = 1 << 7;
 
      /**
      * This flag indicates to the system that the accessibility service requests that an
@@ -326,7 +328,7 @@
       *   accessibility service metadata file. Otherwise, it will be ignored.
       * </p>
      */
-    public static final int FLAG_REQUEST_ACCESSIBILITY_BUTTON = 0x00000100;
+    public static final int FLAG_REQUEST_ACCESSIBILITY_BUTTON = 1 << 8;
 
     /**
      * This flag requests that all fingerprint gestures be sent to the accessibility service.
@@ -341,13 +343,13 @@
      * @see android.R.styleable#AccessibilityService_canRequestFingerprintGestures
      * @see AccessibilityService#getFingerprintGestureController()
      */
-    public static final int FLAG_REQUEST_FINGERPRINT_GESTURES = 0x00000200;
+    public static final int FLAG_REQUEST_FINGERPRINT_GESTURES = 1 << 9;
 
     /**
      * This flag requests that accessibility shortcut warning dialog has spoken feedback when
      * dialog is shown.
      */
-    public static final int FLAG_REQUEST_SHORTCUT_WARNING_DIALOG_SPOKEN_FEEDBACK = 0x00000400;
+    public static final int FLAG_REQUEST_SHORTCUT_WARNING_DIALOG_SPOKEN_FEEDBACK = 1 << 10;
 
     /**
      * This flag requests that when {@link #FLAG_REQUEST_TOUCH_EXPLORATION_MODE} is enabled,
@@ -357,7 +359,7 @@
      *
      * @see #FLAG_REQUEST_TOUCH_EXPLORATION_MODE
      */
-    public static final int FLAG_SERVICE_HANDLES_DOUBLE_TAP = 0x0000800;
+    public static final int FLAG_SERVICE_HANDLES_DOUBLE_TAP = 1 << 11;
 
     /**
      * This flag requests that when when {@link #FLAG_REQUEST_TOUCH_EXPLORATION_MODE} is enabled,
@@ -367,7 +369,7 @@
      *
      * @see #FLAG_REQUEST_TOUCH_EXPLORATION_MODE
      */
-    public static final int FLAG_REQUEST_MULTI_FINGER_GESTURES = 0x0001000;
+    public static final int FLAG_REQUEST_MULTI_FINGER_GESTURES = 1 << 12;
 
     /**
      * This flag requests that when when {@link #FLAG_REQUEST_MULTI_FINGER_GESTURES} is enabled,
@@ -378,7 +380,7 @@
      *
      * @see #FLAG_REQUEST_TOUCH_EXPLORATION_MODE
      */
-    public static final int FLAG_REQUEST_2_FINGER_PASSTHROUGH = 0x0002000;
+    public static final int FLAG_REQUEST_2_FINGER_PASSTHROUGH = 1 << 13;
 
     /**
      * This flag requests that when when {@link #FLAG_REQUEST_TOUCH_EXPLORATION_MODE} is enabled, a
@@ -392,7 +394,7 @@
      *
      * @see #FLAG_REQUEST_TOUCH_EXPLORATION_MODE
      */
-    public static final int FLAG_SEND_MOTION_EVENTS = 0x0004000;
+    public static final int FLAG_SEND_MOTION_EVENTS = 1 << 14;
 
     /**
      * This flag makes the AccessibilityService an input method editor with a subset of input
@@ -401,10 +403,10 @@
      *
      * @see AccessibilityService#getInputMethod()
      */
-    public static final int FLAG_INPUT_METHOD_EDITOR = 0x0008000;
+    public static final int FLAG_INPUT_METHOD_EDITOR = 1 << 15;
 
     /** {@hide} */
-    public static final int FLAG_FORCE_DIRECT_BOOT_AWARE = 0x00010000;
+    public static final int FLAG_FORCE_DIRECT_BOOT_AWARE = 1 << 16;
 
     /**
      * The event types an {@link AccessibilityService} is interested in.
diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
index e99932d..96716db 100644
--- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
+++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
@@ -157,7 +157,7 @@
     void setInstalledAndEnabledServices(in List<AccessibilityServiceInfo> infos);
 
     List<AccessibilityServiceInfo> getInstalledAndEnabledServices();
-    void attachAccessibilityOverlayToDisplay(int displayId, in SurfaceControl sc);
+    void attachAccessibilityOverlayToDisplay(int interactionId, int displayId, in SurfaceControl sc, IAccessibilityInteractionConnectionCallback callback);
 
-    void attachAccessibilityOverlayToWindow(int accessibilityWindowId, in SurfaceControl sc);
+    void attachAccessibilityOverlayToWindow(int interactionId, int accessibilityWindowId, in SurfaceControl sc, IAccessibilityInteractionConnectionCallback callback);
 }
\ No newline at end of file
diff --git a/core/java/android/accessibilityservice/TEST_MAPPING b/core/java/android/accessibilityservice/TEST_MAPPING
index df85b61..1c67399 100644
--- a/core/java/android/accessibilityservice/TEST_MAPPING
+++ b/core/java/android/accessibilityservice/TEST_MAPPING
@@ -1,26 +1,7 @@
 {
-  "presubmit": [
+  "imports": [
     {
-      "name": "CtsAccessibilityServiceTestCases",
-      "options": [
-        {
-          "include-annotation": "android.platform.test.annotations.Presubmit"
-        },
-        {
-          "exclude-annotation": "android.support.test.filters.FlakyTest"
-        }
-      ]
-    }
-  ],
-  "postsubmit": [
-    {
-      "name": "CtsAccessibilityServiceSdk29TestCases"
-    },
-    {
-      "name": "CtsAccessibilityServiceTestCases"
-    },
-    {
-      "name": "CtsAccessibilityTestCases"
+      "path": "frameworks/base/services/accessibility/TEST_MAPPING"
     }
   ]
 }
diff --git a/core/java/android/accounts/Account.java b/core/java/android/accounts/Account.java
index 0d6a079..c376eae 100644
--- a/core/java/android/accounts/Account.java
+++ b/core/java/android/accounts/Account.java
@@ -45,8 +45,8 @@
     @GuardedBy("sAccessedAccounts")
     private static final Set<Account> sAccessedAccounts = new ArraySet<>();
 
-    public final String name;
-    public final String type;
+    public final @NonNull String name;
+    public final @NonNull String type;
     private String mSafeName;
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     private final @Nullable String accessId;
@@ -65,7 +65,7 @@
         return result;
     }
 
-    public Account(String name, String type) {
+    public Account(@NonNull String name, @NonNull String type) {
         this(name, type, null);
     }
 
@@ -79,7 +79,7 @@
     /**
      * @hide
      */
-    public Account(String name, String type, String accessId) {
+    public Account(@NonNull String name, @NonNull String type, String accessId) {
         if (TextUtils.isEmpty(name)) {
             throw new IllegalArgumentException("the name must not be empty: " + name);
         }
diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java
index 821a23c..7ee3413 100644
--- a/core/java/android/accounts/AccountManager.java
+++ b/core/java/android/accounts/AccountManager.java
@@ -2384,12 +2384,8 @@
                 } else {
                     return get(timeout, unit);
                 }
-            } catch (CancellationException e) {
-                throw new OperationCanceledException();
-            } catch (TimeoutException e) {
-                // fall through and cancel
-            } catch (InterruptedException e) {
-                // fall through and cancel
+            } catch (CancellationException | TimeoutException | InterruptedException e) {
+                throw new OperationCanceledException(e);
             } catch (ExecutionException e) {
                 final Throwable cause = e.getCause();
                 if (cause instanceof IOException) {
@@ -2408,7 +2404,6 @@
             } finally {
                 cancel(true /* interrupt if running */);
             }
-            throw new OperationCanceledException();
         }
 
         @Override
diff --git a/core/java/android/animation/AnimatorSet.java b/core/java/android/animation/AnimatorSet.java
index 70c3d7a..b4a6955 100644
--- a/core/java/android/animation/AnimatorSet.java
+++ b/core/java/android/animation/AnimatorSet.java
@@ -1346,8 +1346,26 @@
         }
         // Set the child animators to the right end:
         if (mShouldResetValuesAtStart) {
-            initChildren();
-            skipToEndValue(!mReversing);
+            if (isInitialized()) {
+                skipToEndValue(!mReversing);
+            } else if (mReversing) {
+                // Reversing but haven't initialized all the children yet.
+                initChildren();
+                skipToEndValue(!mReversing);
+            } else {
+                // If not all children are initialized and play direction is forward
+                for (int i = mEvents.size() - 1; i >= 0; i--) {
+                    if (mEvents.get(i).mEvent == AnimationEvent.ANIMATION_DELAY_ENDED) {
+                        Animator anim = mEvents.get(i).mNode.mAnimation;
+                        // Only reset the animations that have been initialized to start value,
+                        // so that if they are defined without a start value, they will get the
+                        // values set at the right time (i.e. the next animation run)
+                        if (anim.isInitialized()) {
+                            anim.skipToEndValue(true);
+                        }
+                    }
+                }
+            }
         }
 
         if (mReversing || mStartDelay == 0 || mSeekState.isActive()) {
@@ -1691,7 +1709,7 @@
                     return 1;
                 }
                 // When neither event happens at INFINITE time:
-                return (int) (t1 - t2);
+                return t1 - t2 > 0 ? 1 : -1;
             }
         });
 
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 8021ce0..a5acf03 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -966,7 +966,7 @@
     private int mDefaultKeyMode = DEFAULT_KEYS_DISABLE;
     private SpannableStringBuilder mDefaultKeySsb = null;
 
-    private ActivityManager.TaskDescription mTaskDescription =
+    private final ActivityManager.TaskDescription mTaskDescription =
             new ActivityManager.TaskDescription();
 
     protected static final int[] FOCUSED_STATE_SET = {com.android.internal.R.attr.state_focused};
@@ -977,7 +977,7 @@
     private Thread mUiThread;
 
     @UnsupportedAppUsage
-    ActivityTransitionState mActivityTransitionState = new ActivityTransitionState();
+    final ActivityTransitionState mActivityTransitionState = new ActivityTransitionState();
     SharedElementCallback mEnterTransitionListener = SharedElementCallback.NULL_CALLBACK;
     SharedElementCallback mExitTransitionListener = SharedElementCallback.NULL_CALLBACK;
 
@@ -1084,6 +1084,16 @@
         }
 
         /**
+         * Update the forced status bar appearance.
+         * @hide
+         */
+        @Override
+        public void updateStatusBarAppearance(int appearance) {
+            mTaskDescription.setStatusBarAppearance(appearance);
+            setTaskDescription(mTaskDescription);
+        }
+
+        /**
          * Update the forced navigation bar color.
          * @hide
          */
@@ -1883,7 +1893,7 @@
         final int numDialogs = ids.length;
         mManagedDialogs = new SparseArray<ManagedDialog>(numDialogs);
         for (int i = 0; i < numDialogs; i++) {
-            final Integer dialogId = ids[i];
+            final int dialogId = ids[i];
             Bundle dialogState = b.getBundle(savedDialogKeyFor(dialogId));
             if (dialogState != null) {
                 // Calling onRestoreInstanceState() below will invoke dispatchOnCreate
@@ -1953,7 +1963,7 @@
      * <code>persistAcrossReboots</code>.
      *
      * @param savedInstanceState The data most recently supplied in {@link #onSaveInstanceState}
-     * @param persistentState The data caming from the PersistableBundle first
+     * @param persistentState The data coming from the PersistableBundle first
      * saved in {@link #onSaveInstanceState(Bundle, PersistableBundle)}.
      *
      * @see #onCreate
@@ -2258,9 +2268,9 @@
      *
      * <p>An activity can never receive a new intent in the resumed state. You can count on
      * {@link #onResume} being called after this method, though not necessarily immediately after
-     * the completion this callback. If the activity was resumed, it will be paused and new intent
-     * will be delivered, followed by {@link #onResume}. If the activity wasn't in the resumed
-     * state, then new intent can be delivered immediately, with {@link #onResume()} called
+     * the completion of this callback. If the activity was resumed, it will be paused and new
+     * intent will be delivered, followed by {@link #onResume}. If the activity wasn't in the
+     * resumed state, then new intent can be delivered immediately, with {@link #onResume()} called
      * sometime later when activity becomes active again.
      *
      * <p>Note that {@link #getIntent} still returns the original Intent.  You
@@ -3146,13 +3156,17 @@
 
     /**
      * Called by the system when the device configuration changes while your
-     * activity is running.  Note that this will <em>only</em> be called if
-     * you have selected configurations you would like to handle with the
+     * activity is running.  Note that this will only be called if you have
+     * selected configurations you would like to handle with the
      * {@link android.R.attr#configChanges} attribute in your manifest.  If
      * any configuration change occurs that is not selected to be reported
      * by that attribute, then instead of reporting it the system will stop
      * and restart the activity (to have it launched with the new
-     * configuration).
+     * configuration). The only exception is if a size-based configuration
+     * is not large enough to be considered significant, in which case the
+     * system will not recreate the activity and will instead call this
+     * method. For details on this see the documentation on
+     * <a href="{@docRoot}guide/topics/resources/runtime-changes.html">size-based config change</a>.
      *
      * <p>At the time that this function has been called, your Resources
      * object will have been updated to return resource values matching the
@@ -3882,7 +3896,9 @@
      * it will set up the dispatch to call {@link #onKeyUp} where the action
      * will be performed; for earlier applications, it will perform the
      * action immediately in on-down, as those versions of the platform
-     * behaved.
+     * behaved. This implementation will also take care of {@link KeyEvent#KEYCODE_ESCAPE}
+     * by finishing the activity if it would be closed by touching outside
+     * of it.
      *
      * <p>Other additional default key handling may be performed
      * if configured with {@link #setDefaultKeyMode}.
@@ -3904,6 +3920,11 @@
             return true;
         }
 
+        if (keyCode == KeyEvent.KEYCODE_ESCAPE && mWindow.shouldCloseOnTouchOutside()) {
+            event.startTracking();
+            return true;
+        }
+
         if (mDefaultKeyMode == DEFAULT_KEYS_DISABLE) {
             return false;
         } else if (mDefaultKeyMode == DEFAULT_KEYS_SHORTCUT) {
@@ -3999,6 +4020,15 @@
                 return true;
             }
         }
+
+        if (keyCode == KeyEvent.KEYCODE_ESCAPE
+                && mWindow.shouldCloseOnTouchOutside()
+                && event.isTracking()
+                && !event.isCanceled()) {
+            finish();
+            return true;
+        }
+
         return false;
     }
 
@@ -5141,7 +5171,7 @@
      * This hook is called when the user signals the desire to start a search.
      *
      * <p>You can use this function as a simple way to launch the search UI, in response to a
-     * menu item, search button, or other widgets within your activity. Unless overidden,
+     * menu item, search button, or other widgets within your activity. Unless overridden,
      * calling this function is the same as calling
      * {@link #startSearch startSearch(null, false, null, false)}, which launches
      * search for the current activity as specified in its manifest, see {@link SearchManager}.
@@ -5419,14 +5449,14 @@
      * the signature of the app declaring the permissions.
      * </p>
      * <p>
-     * Call {@link #shouldShowRequestPermissionRationale(String)} before calling this API to
+     * Call {@link #shouldShowRequestPermissionRationale} before calling this API to
      * check if the system recommends to show a rationale UI before asking for a permission.
      * </p>
      * <p>
      * If your app does not have the requested permissions the user will be presented
      * with UI for accepting them. After the user has accepted or rejected the
      * requested permissions you will receive a callback on {@link
-     * #onRequestPermissionsResult(int, String[], int[])} reporting whether the
+     * #onRequestPermissionsResult} reporting whether the
      * permissions were granted or not.
      * </p>
      * <p>
@@ -5438,8 +5468,7 @@
      * to grant and which to reject. Hence, you should be prepared that your activity
      * may be paused and resumed. Further, granting some permissions may require
      * a restart of you application. In such a case, the system will recreate the
-     * activity stack before delivering the result to {@link
-     * #onRequestPermissionsResult(int, String[], int[])}.
+     * activity stack before delivering the result to {@link #onRequestPermissionsResult}.
      * </p>
      * <p>
      * When checking whether you have a permission you should use {@link
@@ -5449,26 +5478,97 @@
      * You cannot request a permission if your activity sets {@link
      * android.R.styleable#AndroidManifestActivity_noHistory noHistory} to
      * <code>true</code> because in this case the activity would not receive
-     * result callbacks including {@link #onRequestPermissionsResult(int, String[], int[])}.
+     * result callbacks including {@link #onRequestPermissionsResult}.
      * </p>
      * <p>
-     * The <a href="https://github.com/android/permissions-samples">
-     * RuntimePermissions</a> sample apps demonstrate how to use this method to
+     * The <a href="https://github.com/android/platform-samples/tree/main/samples/privacy/permissions">
+     * permissions samples</a> repo demonstrates how to use this method to
      * request permissions at run time.
      * </p>
      *
-     * @param permissions The requested permissions. Must me non-null and not empty.
+     * @param permissions The requested permissions. Must be non-null and not empty.
      * @param requestCode Application specific request code to match with a result
-     *    reported to {@link #onRequestPermissionsResult(int, String[], int[])}.
-     *    Should be >= 0.
+     *                    reported to {@link #onRequestPermissionsResult}.
+     *                    Should be >= 0.
      *
      * @throws IllegalArgumentException if requestCode is negative.
      *
-     * @see #onRequestPermissionsResult(int, String[], int[])
-     * @see #checkSelfPermission(String)
-     * @see #shouldShowRequestPermissionRationale(String)
+     * @see #onRequestPermissionsResult
+     * @see #checkSelfPermission
+     * @see #shouldShowRequestPermissionRationale
      */
     public final void requestPermissions(@NonNull String[] permissions, int requestCode) {
+        requestPermissions(permissions, requestCode, getDeviceId());
+    }
+
+    /**
+     * Requests permissions to be granted to this application. These permissions
+     * must be requested in your manifest, they should not be granted to your app,
+     * and they should have protection level {@link
+     * android.content.pm.PermissionInfo#PROTECTION_DANGEROUS dangerous}, regardless
+     * whether they are declared by the platform or a third-party app.
+     * <p>
+     * Normal permissions {@link android.content.pm.PermissionInfo#PROTECTION_NORMAL}
+     * are granted at install time if requested in the manifest. Signature permissions
+     * {@link android.content.pm.PermissionInfo#PROTECTION_SIGNATURE} are granted at
+     * install time if requested in the manifest and the signature of your app matches
+     * the signature of the app declaring the permissions.
+     * </p>
+     * <p>
+     * Call {@link #shouldShowRequestPermissionRationale} before calling this API to
+     * check if the system recommends to show a rationale UI before asking for a permission.
+     * </p>
+     * <p>
+     * If your app does not have the requested permissions the user will be presented
+     * with UI for accepting them. After the user has accepted or rejected the
+     * requested permissions you will receive a callback on {@link #onRequestPermissionsResult}
+     * reporting whether the permissions were granted or not.
+     * </p>
+     * <p>
+     * Note that requesting a permission does not guarantee it will be granted and
+     * your app should be able to run without having this permission.
+     * </p>
+     * <p>
+     * This method may start an activity allowing the user to choose which permissions
+     * to grant and which to reject. Hence, you should be prepared that your activity
+     * may be paused and resumed. Further, granting some permissions may require
+     * a restart of you application. In such a case, the system will recreate the
+     * activity stack before delivering the result to {@link #onRequestPermissionsResult}.
+     * </p>
+     * <p>
+     * When checking whether you have a permission you should use {@link
+     * #checkSelfPermission(String)}.
+     * </p>
+     * <p>
+     * You cannot request a permission if your activity sets {@link
+     * android.R.styleable#AndroidManifestActivity_noHistory noHistory} to
+     * <code>true</code> because in this case the activity would not receive
+     * result callbacks including {@link #onRequestPermissionsResult}.
+     * </p>
+     * <p>
+     * The <a href="https://github.com/android/platform-samples/tree/main/samples/privacy/permissions">
+     * permissions samples</a> repo demonstrates how to use this method to
+     * request permissions at run time.
+     * </p>
+     *
+     * @param permissions The requested permissions. Must be non-null and not empty.
+     * @param requestCode Application specific request code to match with a result
+     *                    reported to {@link #onRequestPermissionsResult}.
+     *                    Should be >= 0.
+     * @param deviceId The app is requesting permissions for this device. The primary/physical
+     *                 device is assigned {@link Context#DEVICE_ID_DEFAULT}, and {@link
+     *                 android.companion.virtual.VirtualDeviceManager.VirtualDevice virtual devices}
+     *                 are assigned unique device Ids.
+     *
+     * @throws IllegalArgumentException if requestCode is negative.
+     *
+     * @see #onRequestPermissionsResult
+     * @see #checkSelfPermission
+     * @see #shouldShowRequestPermissionRationale
+     * @see Context#DEVICE_ID_DEFAULT
+     */
+    public final void requestPermissions(@NonNull String[] permissions, int requestCode,
+            int deviceId) {
         if (requestCode < 0) {
             throw new IllegalArgumentException("requestCode should be >= 0");
         }
@@ -5476,7 +5576,7 @@
         if (mHasCurrentPermissionsRequest) {
             Log.w(TAG, "Can request only one set of permissions at a time");
             // Dispatch the callback with empty arrays which means a cancellation.
-            onRequestPermissionsResult(requestCode, new String[0], new int[0]);
+            onRequestPermissionsResult(requestCode, new String[0], new int[0], deviceId);
             return;
         }
 
@@ -5490,27 +5590,29 @@
             }
         }
 
-        final Intent intent = getPackageManager().buildRequestPermissionsIntent(permissions);
+        PackageManager packageManager = getDeviceId() == deviceId ? getPackageManager()
+                : createDeviceContext(deviceId).getPackageManager();
+        final Intent intent = packageManager.buildRequestPermissionsIntent(permissions);
         startActivityForResult(REQUEST_PERMISSIONS_WHO_PREFIX, intent, requestCode, null);
         mHasCurrentPermissionsRequest = true;
     }
 
     /**
      * Callback for the result from requesting permissions. This method
-     * is invoked for every call on {@link #requestPermissions(String[], int)}.
+     * is invoked for every call on {@link #requestPermissions}
      * <p>
      * <strong>Note:</strong> It is possible that the permissions request interaction
      * with the user is interrupted. In this case you will receive empty permissions
      * and results arrays which should be treated as a cancellation.
      * </p>
      *
-     * @param requestCode The request code passed in {@link #requestPermissions(String[], int)}.
+     * @param requestCode The request code passed in {@link #requestPermissions}.
      * @param permissions The requested permissions. Never null.
-     * @param grantResults The grant results for the corresponding permissions
-     *     which is either {@link android.content.pm.PackageManager#PERMISSION_GRANTED}
-     *     or {@link android.content.pm.PackageManager#PERMISSION_DENIED}. Never null.
+     * @param grantResults The grant results for the corresponding permissions which is either
+     *                     {@link android.content.pm.PackageManager#PERMISSION_GRANTED} or
+     *                     {@link android.content.pm.PackageManager#PERMISSION_DENIED}. Never null.
      *
-     * @see #requestPermissions(String[], int)
+     * @see #requestPermissions
      */
     public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
             @NonNull int[] grantResults) {
@@ -5518,20 +5620,66 @@
     }
 
     /**
+     * Callback for the result from requesting permissions. This method
+     * is invoked for every call on {@link #requestPermissions}.
+     * <p>
+     * <strong>Note:</strong> It is possible that the permissions request interaction
+     * with the user is interrupted. In this case you will receive empty permissions
+     * and results arrays which should be treated as a cancellation.
+     * </p>
+     *
+     * @param requestCode The request code passed in {@link #requestPermissions}.
+     * @param permissions The requested permissions. Never null.
+     * @param grantResults The grant results for the corresponding permissions which is either
+     *                     {@link android.content.pm.PackageManager#PERMISSION_GRANTED} or
+     *                     {@link android.content.pm.PackageManager#PERMISSION_DENIED}. Never null.
+     * @param deviceId The deviceId for which permissions were requested. The primary/physical
+     *                 device is assigned {@link Context#DEVICE_ID_DEFAULT}, and {@link
+     *                 android.companion.virtual.VirtualDeviceManager.VirtualDevice virtual devices}
+     *                 are assigned unique device Ids.
+     *
+     * @see #requestPermissions
+     */
+    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
+            @NonNull int[] grantResults, int deviceId) {
+        onRequestPermissionsResult(requestCode, permissions, grantResults);
+    }
+
+    /**
      * Gets whether you should show UI with rationale before requesting a permission.
      *
      * @param permission A permission your app wants to request.
      * @return Whether you should show permission rationale UI.
      *
-     * @see #checkSelfPermission(String)
-     * @see #requestPermissions(String[], int)
-     * @see #onRequestPermissionsResult(int, String[], int[])
+     * @see #checkSelfPermission
+     * @see #requestPermissions
+     * @see #onRequestPermissionsResult
      */
     public boolean shouldShowRequestPermissionRationale(@NonNull String permission) {
         return getPackageManager().shouldShowRequestPermissionRationale(permission);
     }
 
     /**
+     * Gets whether you should show UI with rationale before requesting a permission.
+     *
+     * @param permission A permission your app wants to request.
+     * @param deviceId The app is requesting permissions for this device. The primary/physical
+     *                 device is assigned {@link Context#DEVICE_ID_DEFAULT}, and {@link
+     *                 android.companion.virtual.VirtualDeviceManager.VirtualDevice virtual devices}
+     *                 are assigned unique device Ids.
+     * @return Whether you should show permission rationale UI.
+     *
+     * @see #checkSelfPermission
+     * @see #requestPermissions
+     * @see #onRequestPermissionsResult
+     */
+    public boolean shouldShowRequestPermissionRationale(@NonNull String permission, int deviceId) {
+        final PackageManager packageManager = getDeviceId() == deviceId ? getPackageManager()
+                : createDeviceContext(deviceId).getPackageManager();
+        return packageManager.shouldShowRequestPermissionRationale(permission);
+    }
+
+    /**
      * Same as calling {@link #startActivityForResult(Intent, int, Bundle)}
      * with no options.
      *
@@ -9076,17 +9224,20 @@
          * @see Activity#convertFromTranslucent()
          * @see Activity#convertToTranslucent(TranslucentConversionListener, ActivityOptions)
          */
-        public void onTranslucentConversionComplete(boolean drawComplete);
+        void onTranslucentConversionComplete(boolean drawComplete);
     }
 
     private void dispatchRequestPermissionsResult(int requestCode, Intent data) {
         mHasCurrentPermissionsRequest = false;
-        // If the package installer crashed we may have not data - best effort.
+        // If the package installer crashed we may have no data - best effort.
         String[] permissions = (data != null) ? data.getStringArrayExtra(
                 PackageManager.EXTRA_REQUEST_PERMISSIONS_NAMES) : new String[0];
         final int[] grantResults = (data != null) ? data.getIntArrayExtra(
                 PackageManager.EXTRA_REQUEST_PERMISSIONS_RESULTS) : new int[0];
-        onRequestPermissionsResult(requestCode, permissions, grantResults);
+        final int deviceId = (data != null) ? data.getIntExtra(
+                PackageManager.EXTRA_REQUEST_PERMISSIONS_DEVICE_ID, Context.DEVICE_ID_DEFAULT
+        ) : Context.DEVICE_ID_DEFAULT;
+        onRequestPermissionsResult(requestCode, permissions, grantResults, deviceId);
     }
 
     private void dispatchRequestPermissionsResultToFragment(int requestCode, Intent data,
@@ -9209,7 +9360,6 @@
      *
      * @param allowed {@code true} to disable the UID restrictions; {@code false} to revert back to
      *                            the default behaviour
-     * @hide
      */
     public void setAllowCrossUidActivitySwitchFromBelow(boolean allowed) {
         ActivityClient.getInstance().setAllowCrossUidActivitySwitchFromBelow(mToken, allowed);
@@ -9218,7 +9368,7 @@
     /**
      * Registers remote animations per transition type for this activity.
      *
-     * @param definition The remote animation definition that defines which transition whould run
+     * @param definition The remote animation definition that defines which transition would run
      *                   which remote animation.
      * @hide
      */
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index b5ee895..37a1e62 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -81,6 +81,7 @@
 import android.util.DisplayMetrics;
 import android.util.Singleton;
 import android.util.Size;
+import android.view.WindowInsetsController.Appearance;
 import android.window.TaskSnapshot;
 
 import com.android.internal.app.LocalePicker;
@@ -192,6 +193,11 @@
      * @hide
      */
     public static final int INSTR_FLAG_INSTRUMENT_SDK_SANDBOX = 1 << 5;
+    /**
+     * Instrument an Sdk Sandbox process corresponding to an Sdk running inside the sandbox.
+     * @hide
+     */
+    public static final int INSTR_FLAG_INSTRUMENT_SDK_IN_SANDBOX = 1 << 6;
 
     static final class MyUidObserver extends UidObserver {
         final OnUidImportanceListener mListener;
@@ -1553,6 +1559,8 @@
         private int mColorBackgroundFloating;
         private int mStatusBarColor;
         private int mNavigationBarColor;
+        @Appearance
+        private int mStatusBarAppearance;
         private boolean mEnsureStatusBarContrastWhenTransparent;
         private boolean mEnsureNavigationBarContrastWhenTransparent;
         private int mResizeMode;
@@ -1653,8 +1661,8 @@
                 final Icon icon = mIconRes == Resources.ID_NULL ? null :
                         Icon.createWithResource(ActivityThread.currentPackageName(), mIconRes);
                 return new TaskDescription(mLabel, icon, mPrimaryColor, mBackgroundColor,
-                        mStatusBarColor, mNavigationBarColor, false, false, RESIZE_MODE_RESIZEABLE,
-                        -1, -1, 0);
+                        mStatusBarColor, mNavigationBarColor, 0, false, false,
+                        RESIZE_MODE_RESIZEABLE, -1, -1, 0);
             }
         }
 
@@ -1672,7 +1680,7 @@
         @Deprecated
         public TaskDescription(String label, @DrawableRes int iconRes, int colorPrimary) {
             this(label, Icon.createWithResource(ActivityThread.currentPackageName(), iconRes),
-                    colorPrimary, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
+                    colorPrimary, 0, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
             if ((colorPrimary != 0) && (Color.alpha(colorPrimary) != 255)) {
                 throw new RuntimeException("A TaskDescription's primary color should be opaque");
             }
@@ -1690,7 +1698,7 @@
         @Deprecated
         public TaskDescription(String label, @DrawableRes int iconRes) {
             this(label, Icon.createWithResource(ActivityThread.currentPackageName(), iconRes),
-                    0, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
+                    0, 0, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
         }
 
         /**
@@ -1702,7 +1710,7 @@
          */
         @Deprecated
         public TaskDescription(String label) {
-            this(label, null, 0, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
+            this(label, null, 0, 0, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
         }
 
         /**
@@ -1712,7 +1720,7 @@
          */
         @Deprecated
         public TaskDescription() {
-            this(null, null, 0, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
+            this(null, null, 0, 0, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
         }
 
         /**
@@ -1728,7 +1736,7 @@
         @Deprecated
         public TaskDescription(String label, Bitmap icon, int colorPrimary) {
             this(label, icon != null ? Icon.createWithBitmap(icon) : null, colorPrimary, 0, 0, 0,
-                    false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
+                    0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
             if ((colorPrimary != 0) && (Color.alpha(colorPrimary) != 255)) {
                 throw new RuntimeException("A TaskDescription's primary color should be opaque");
             }
@@ -1744,14 +1752,15 @@
          */
         @Deprecated
         public TaskDescription(String label, Bitmap icon) {
-            this(label, icon != null ? Icon.createWithBitmap(icon) : null, 0, 0, 0, 0, false, false,
-                    RESIZE_MODE_RESIZEABLE, -1, -1, 0);
+            this(label, icon != null ? Icon.createWithBitmap(icon) : null, 0, 0, 0, 0, 0, false,
+                    false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
         }
 
         /** @hide */
         public TaskDescription(@Nullable String label, @Nullable Icon icon,
                 int colorPrimary, int colorBackground,
                 int statusBarColor, int navigationBarColor,
+                @Appearance int statusBarAppearance,
                 boolean ensureStatusBarContrastWhenTransparent,
                 boolean ensureNavigationBarContrastWhenTransparent, int resizeMode, int minWidth,
                 int minHeight, int colorBackgroundFloating) {
@@ -1761,6 +1770,7 @@
             mColorBackground = colorBackground;
             mStatusBarColor = statusBarColor;
             mNavigationBarColor = navigationBarColor;
+            mStatusBarAppearance = statusBarAppearance;
             mEnsureStatusBarContrastWhenTransparent = ensureStatusBarContrastWhenTransparent;
             mEnsureNavigationBarContrastWhenTransparent =
                     ensureNavigationBarContrastWhenTransparent;
@@ -1789,6 +1799,7 @@
             mColorBackground = other.mColorBackground;
             mStatusBarColor = other.mStatusBarColor;
             mNavigationBarColor = other.mNavigationBarColor;
+            mStatusBarAppearance = other.mStatusBarAppearance;
             mEnsureStatusBarContrastWhenTransparent = other.mEnsureStatusBarContrastWhenTransparent;
             mEnsureNavigationBarContrastWhenTransparent =
                     other.mEnsureNavigationBarContrastWhenTransparent;
@@ -1818,6 +1829,9 @@
             if (other.mNavigationBarColor != 0) {
                 mNavigationBarColor = other.mNavigationBarColor;
             }
+            if (other.mStatusBarAppearance != 0) {
+                mStatusBarAppearance = other.mStatusBarAppearance;
+            }
 
             mEnsureStatusBarContrastWhenTransparent = other.mEnsureStatusBarContrastWhenTransparent;
             mEnsureNavigationBarContrastWhenTransparent =
@@ -2089,6 +2103,14 @@
         /**
          * @hide
          */
+        @Appearance
+        public int getStatusBarAppearance() {
+            return mStatusBarAppearance;
+        }
+
+        /**
+         * @hide
+         */
         public void setEnsureStatusBarContrastWhenTransparent(
                 boolean ensureStatusBarContrastWhenTransparent) {
             mEnsureStatusBarContrastWhenTransparent = ensureStatusBarContrastWhenTransparent;
@@ -2097,6 +2119,13 @@
         /**
          * @hide
          */
+        public void setStatusBarAppearance(@Appearance int statusBarAppearance) {
+            mStatusBarAppearance = statusBarAppearance;
+        }
+
+        /**
+         * @hide
+         */
         public boolean getEnsureNavigationBarContrastWhenTransparent() {
             return mEnsureNavigationBarContrastWhenTransparent;
         }
@@ -2218,6 +2247,7 @@
             dest.writeInt(mColorBackground);
             dest.writeInt(mStatusBarColor);
             dest.writeInt(mNavigationBarColor);
+            dest.writeInt(mStatusBarAppearance);
             dest.writeBoolean(mEnsureStatusBarContrastWhenTransparent);
             dest.writeBoolean(mEnsureNavigationBarContrastWhenTransparent);
             dest.writeInt(mResizeMode);
@@ -2241,6 +2271,7 @@
             mColorBackground = source.readInt();
             mStatusBarColor = source.readInt();
             mNavigationBarColor = source.readInt();
+            mStatusBarAppearance = source.readInt();
             mEnsureStatusBarContrastWhenTransparent = source.readBoolean();
             mEnsureNavigationBarContrastWhenTransparent = source.readBoolean();
             mResizeMode = source.readInt();
@@ -2289,6 +2320,7 @@
                     && mColorBackground == other.mColorBackground
                     && mStatusBarColor == other.mStatusBarColor
                     && mNavigationBarColor == other.mNavigationBarColor
+                    && mStatusBarAppearance == other.mStatusBarAppearance
                     && mEnsureStatusBarContrastWhenTransparent
                             == other.mEnsureStatusBarContrastWhenTransparent
                     && mEnsureNavigationBarContrastWhenTransparent
@@ -4667,8 +4699,14 @@
     @UnsupportedAppUsage
     public static int checkComponentPermission(String permission, int uid,
             int owningUid, boolean exported) {
+        return checkComponentPermission(permission, uid, Context.DEVICE_ID_DEFAULT,
+                owningUid, exported);
+    }
+
+    /** @hide */
+    public static int checkComponentPermission(String permission, int uid, int deviceId,
+            int owningUid, boolean exported) {
         // Root, system server get to do everything.
-        final int appId = UserHandle.getAppId(uid);
         if (canAccessUnexportedComponents(uid)) {
             return PackageManager.PERMISSION_GRANTED;
         }
@@ -4695,8 +4733,7 @@
             return PackageManager.PERMISSION_GRANTED;
         }
         try {
-            return AppGlobals.getPackageManager()
-                    .checkUidPermission(permission, uid);
+            return AppGlobals.getPermissionManager().checkUidPermission(uid, permission, deviceId);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -4705,8 +4742,8 @@
     /** @hide */
     public static int checkUidPermission(String permission, int uid) {
         try {
-            return AppGlobals.getPackageManager()
-                    .checkUidPermission(permission, uid);
+            return AppGlobals.getPermissionManager().checkUidPermission(
+                    uid, permission, Context.DEVICE_ID_DEFAULT);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 41c58ef..853528f 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -121,6 +121,8 @@
 import android.os.Build;
 import android.os.Bundle;
 import android.os.CancellationSignal;
+import android.os.DdmSyncStageUpdater;
+import android.os.DdmSyncState.Stage;
 import android.os.Debug;
 import android.os.Environment;
 import android.os.FileUtils;
@@ -267,9 +269,11 @@
  */
 public final class ActivityThread extends ClientTransactionHandler
         implements ActivityThreadInternal {
+
+    private final DdmSyncStageUpdater mDdmSyncStageUpdater = new DdmSyncStageUpdater();
+
     /** @hide */
     public static final String TAG = "ActivityThread";
-    private static final android.graphics.Bitmap.Config THUMBNAIL_FORMAT = Bitmap.Config.RGB_565;
     static final boolean localLOGV = false;
     static final boolean DEBUG_MESSAGES = false;
     /** @hide */
@@ -370,7 +374,7 @@
     private final AtomicInteger mNumLaunchingActivities = new AtomicInteger();
     @GuardedBy("mAppThread")
     private int mLastProcessState = PROCESS_STATE_UNKNOWN;
-    ArrayList<WeakReference<AssistStructure>> mLastAssistStructures = new ArrayList<>();
+    final ArrayList<WeakReference<AssistStructure>> mLastAssistStructures = new ArrayList<>();
     private int mLastSessionId;
     // Holds the value of the last reported device ID value from the server for the top activity.
     int mLastReportedDeviceId;
@@ -561,7 +565,7 @@
         Configuration createdConfig;
         Configuration overrideConfig;
         // Used for consolidating configs before sending on to Activity.
-        private Configuration tmpConfig = new Configuration();
+        private final Configuration tmpConfig = new Configuration();
         // Callback used for updating activity override config and camera compat control state.
         ViewRootImpl.ActivityConfigCallback activityConfigCallback;
 
@@ -769,7 +773,7 @@
         }
     }
 
-    final class ProviderClientRecord {
+    static final class ProviderClientRecord {
         final String[] mNames;
         @UnsupportedAppUsage
         final IContentProvider mProvider;
@@ -798,7 +802,7 @@
         }
 
         @UnsupportedAppUsage
-        Intent intent;
+        final Intent intent;
         @UnsupportedAppUsage
         ActivityInfo info;
         @UnsupportedAppUsage
@@ -882,6 +886,7 @@
         ApplicationInfo appInfo;
         String sdkSandboxClientAppVolumeUuid;
         String sdkSandboxClientAppPackage;
+        boolean isSdkInSandbox;
         @UnsupportedAppUsage
         List<ProviderInfo> providers;
         ComponentName instrumentationName;
@@ -1163,19 +1168,34 @@
         }
 
         @Override
-        public final void bindApplication(String processName, ApplicationInfo appInfo,
-                String sdkSandboxClientAppVolumeUuid, String sdkSandboxClientAppPackage,
-                ProviderInfoList providerList, ComponentName instrumentationName,
-                ProfilerInfo profilerInfo, Bundle instrumentationArgs,
+        public final void bindApplication(
+                String processName,
+                ApplicationInfo appInfo,
+                String sdkSandboxClientAppVolumeUuid,
+                String sdkSandboxClientAppPackage,
+                boolean isSdkInSandbox,
+                ProviderInfoList providerList,
+                ComponentName instrumentationName,
+                ProfilerInfo profilerInfo,
+                Bundle instrumentationArgs,
                 IInstrumentationWatcher instrumentationWatcher,
-                IUiAutomationConnection instrumentationUiConnection, int debugMode,
-                boolean enableBinderTracking, boolean trackAllocation,
-                boolean isRestrictedBackupMode, boolean persistent, Configuration config,
-                CompatibilityInfo compatInfo, Map services, Bundle coreSettings,
-                String buildSerial, AutofillOptions autofillOptions,
-                ContentCaptureOptions contentCaptureOptions, long[] disabledCompatChanges,
+                IUiAutomationConnection instrumentationUiConnection,
+                int debugMode,
+                boolean enableBinderTracking,
+                boolean trackAllocation,
+                boolean isRestrictedBackupMode,
+                boolean persistent,
+                Configuration config,
+                CompatibilityInfo compatInfo,
+                Map services,
+                Bundle coreSettings,
+                String buildSerial,
+                AutofillOptions autofillOptions,
+                ContentCaptureOptions contentCaptureOptions,
+                long[] disabledCompatChanges,
                 SharedMemory serializedSystemFontMap,
-                long startRequestedElapsedTime, long startRequestedUptime) {
+                long startRequestedElapsedTime,
+                long startRequestedUptime) {
             if (services != null) {
                 if (false) {
                     // Test code to make sure the app could see the passed-in services.
@@ -1209,6 +1229,7 @@
             data.appInfo = appInfo;
             data.sdkSandboxClientAppVolumeUuid = sdkSandboxClientAppVolumeUuid;
             data.sdkSandboxClientAppPackage = sdkSandboxClientAppPackage;
+            data.isSdkInSandbox = isSdkInSandbox;
             data.providers = providerList.getList();
             data.instrumentationName = instrumentationName;
             data.instrumentationArgs = instrumentationArgs;
@@ -3460,11 +3481,8 @@
     public void registerOnActivityPausedListener(Activity activity,
             OnActivityPausedListener listener) {
         synchronized (mOnPauseListeners) {
-            ArrayList<OnActivityPausedListener> list = mOnPauseListeners.get(activity);
-            if (list == null) {
-                list = new ArrayList<OnActivityPausedListener>();
-                mOnPauseListeners.put(activity, list);
-            }
+            ArrayList<OnActivityPausedListener> list =
+                    mOnPauseListeners.computeIfAbsent(activity, k -> new ArrayList<>());
             list.add(listener);
         }
     }
@@ -5572,7 +5590,7 @@
     /** Core implementation of activity destroy call. */
     void performDestroyActivity(ActivityClientRecord r, boolean finishing,
             int configChanges, boolean getNonConfigInstance, String reason) {
-        Class<? extends Activity> activityClass = null;
+        Class<? extends Activity> activityClass;
         if (localLOGV) Slog.v(TAG, "Performing finish of " + r);
         activityClass = r.activity.getClass();
         r.activity.mConfigChangeFlags |= configChanges;
@@ -6692,6 +6710,8 @@
 
     @UnsupportedAppUsage
     private void handleBindApplication(AppBindData data) {
+        mDdmSyncStageUpdater.next(Stage.Bind);
+
         // Register the UI Thread as a sensitive thread to the runtime.
         VMRuntime.registerSensitiveThread();
         // In the case the stack depth property exists, pass it down to the runtime.
@@ -6741,6 +6761,7 @@
                                                 data.appInfo.packageName,
                                                 UserHandle.myUserId());
         VMRuntime.setProcessPackageName(data.appInfo.packageName);
+        mDdmSyncStageUpdater.next(Stage.Named);
 
         // Pass data directory path to ART. This is used for caching information and
         // should be set before any application code is loaded.
@@ -6945,6 +6966,7 @@
         final StrictMode.ThreadPolicy writesAllowedPolicy = StrictMode.getThreadPolicy();
 
         if (data.debugMode != ApplicationThreadConstants.DEBUG_OFF) {
+            mDdmSyncStageUpdater.next(Stage.Debugger);
             if (data.debugMode == ApplicationThreadConstants.DEBUG_WAIT) {
                 waitForDebugger(data);
             } else if (data.debugMode == ApplicationThreadConstants.DEBUG_SUSPEND) {
@@ -6952,6 +6974,7 @@
             }
             // Nothing special to do in case of DEBUG_ON.
         }
+        mDdmSyncStageUpdater.next(Stage.Running);
 
         try {
             // If the app is being launched for full backup or restore, bring it up in
@@ -7203,8 +7226,14 @@
         // The test context's op package name == the target app's op package name, because
         // the app ops manager checks the op package name against the real calling UID,
         // which is what the target package name is associated with.
-        final ContextImpl instrContext = ContextImpl.createAppContext(this, pi,
-                appContext.getOpPackageName());
+        // In the case of instrumenting an sdk running in the sdk sandbox, appContext refers
+        // to the context of the sdk running in the sandbox. Since the sandbox does not have
+        // access to data outside the sandbox, we require the instrContext to point to the
+        // sdk in the sandbox as well, and not to the test context.
+        final ContextImpl instrContext =
+                (data.isSdkInSandbox)
+                        ? appContext
+                        : ContextImpl.createAppContext(this, pi, appContext.getOpPackageName());
 
         try {
             final ClassLoader cl = instrContext.getClassLoader();
@@ -7303,7 +7332,7 @@
         // Note that we cannot hold the lock while acquiring and installing the
         // provider since it might take a long time to run and it could also potentially
         // be re-entrant in the case where the provider is in the same process.
-        ContentProviderHolder holder = null;
+        ContentProviderHolder holder;
         final ProviderKey key = getGetProviderKey(auth, userId);
         try {
             synchronized (key) {
@@ -7357,11 +7386,7 @@
     private ProviderKey getGetProviderKey(String auth, int userId) {
         final ProviderKey key = new ProviderKey(auth, userId);
         synchronized (mGetProviderKeys) {
-            ProviderKey lock = mGetProviderKeys.get(key);
-            if (lock == null) {
-                lock = key;
-                mGetProviderKeys.put(key, lock);
-            }
+            ProviderKey lock = mGetProviderKeys.computeIfAbsent(key, k -> k);
             return lock;
         }
     }
@@ -7855,6 +7880,7 @@
         mConfigurationController = new ConfigurationController(this);
         mSystemThread = system;
         mStartSeq = startSeq;
+        mDdmSyncStageUpdater.next(Stage.Attach);
 
         if (!system) {
             android.ddm.DdmHandleAppName.setAppName("<pre-initialized>",
@@ -7998,7 +8024,7 @@
             if (!DEPRECATE_DATA_COLUMNS) return;
 
             // Install interception and make sure it sticks!
-            Os def = null;
+            Os def;
             do {
                 def = Os.getDefault();
             } while (!Os.compareAndSetDefault(def, new AndroidOs(def)));
diff --git a/core/java/android/app/AliasActivity.java b/core/java/android/app/AliasActivity.java
index 37be901..f266769 100644
--- a/core/java/android/app/AliasActivity.java
+++ b/core/java/android/app/AliasActivity.java
@@ -16,9 +16,6 @@
 
 package android.app;
 
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
 import android.content.pm.PackageManager;
@@ -29,13 +26,16 @@
 
 import com.android.internal.util.XmlUtils;
 
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
 import java.io.IOException;
 
 /**
  * Stub activity that launches another activity (and then finishes itself)
  * based on information in its component's manifest meta-data.  This is a
  * simple way to implement an alias-like mechanism.
- * 
+ *
  * To use this activity, you should include in the manifest for the associated
  * component an entry named "android.app.alias".  It is a reference to an XML
  * resource describing an intent that launches the real application.
@@ -51,11 +51,11 @@
      * {@hide}
      */
     public final String ALIAS_META_DATA = "android.app.alias";
-    
+
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-        
+
         XmlResourceParser parser = null;
         try {
             ActivityInfo ai = getPackageManager().getActivityInfo(
@@ -66,21 +66,17 @@
                 throw new RuntimeException("Alias requires a meta-data field "
                         + ALIAS_META_DATA);
             }
-            
+
             Intent intent = parseAlias(parser);
             if (intent == null) {
                 throw new RuntimeException(
                         "No <intent> tag found in alias description");
             }
-            
+
             startActivity(intent);
             finish();
-            
-        } catch (PackageManager.NameNotFoundException e) {
-            throw new RuntimeException("Error parsing alias", e);
-        } catch (XmlPullParserException e) {
-            throw new RuntimeException("Error parsing alias", e);
-        } catch (IOException e) {
+
+        } catch (PackageManager.NameNotFoundException | XmlPullParserException | IOException e) {
             throw new RuntimeException("Error parsing alias", e);
         } finally {
             if (parser != null) parser.close();
@@ -90,21 +86,21 @@
     private Intent parseAlias(XmlPullParser parser)
             throws XmlPullParserException, IOException {
         AttributeSet attrs = Xml.asAttributeSet(parser);
-        
+
         Intent intent = null;
-        
+
         int type;
         while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
                 && type != XmlPullParser.START_TAG) {
         }
-        
+
         String nodeName = parser.getName();
         if (!"alias".equals(nodeName)) {
             throw new RuntimeException(
                     "Alias meta-data must start with <alias> tag; found"
                     + nodeName + " at " + parser.getPositionDescription());
         }
-        
+
         int outerDepth = parser.getDepth();
         while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
@@ -120,8 +116,8 @@
                 XmlUtils.skipCurrentTag(parser);
             }
         }
-        
+
         return intent;
     }
-    
+
 }
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index ccd83f7..bcd43ea 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -1477,9 +1477,26 @@
     public static final int OP_RECORD_AUDIO_SANDBOXED =
             AppProtoEnums.APP_OP_RECORD_AUDIO_SANDBOXED;
 
+    /**
+     * Allows the assistant app to receive the PCC-validated hotword and be voice-triggered.
+     *
+     * @hide
+     */
+    public static final int OP_RECEIVE_SANDBOX_TRIGGER_AUDIO =
+            AppProtoEnums.APP_OP_RECEIVE_SANDBOX_TRIGGER_AUDIO;
+
+    /**
+     * Allows the assistant app to get the negative trigger data from the PCC sandbox to improve the
+     * hotword training model.
+     *
+     * @hide
+     */
+    public static final int OP_RECEIVE_SANDBOX_NEGATIVE_DATA_AUDIO =
+            AppProtoEnums.APP_OP_RECEIVE_SANDBOX_NEGATIVE_DATA_AUDIO;
+
     /** @hide */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    public static final int _NUM_OP = 136;
+    public static final int _NUM_OP = 138;
 
     /**
      * All app ops represented as strings.
@@ -1621,7 +1638,9 @@
             OPSTR_CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD,
             OPSTR_USE_FULL_SCREEN_INTENT,
             OPSTR_CAMERA_SANDBOXED,
-            OPSTR_RECORD_AUDIO_SANDBOXED
+            OPSTR_RECORD_AUDIO_SANDBOXED,
+            OPSTR_RECEIVE_SANDBOX_TRIGGER_AUDIO,
+            OPSTR_RECEIVE_SANDBOX_NEGATIVE_DATA_AUDIO
     })
     public @interface AppOpString {}
 
@@ -2232,6 +2251,23 @@
      */
     public static final String OPSTR_USE_FULL_SCREEN_INTENT = "android:use_full_screen_intent";
 
+    /**
+     * Allows the assistant app to receive the PCC-validated hotword and be voice-triggered.
+     *
+     * @hide
+     */
+    public static final String OPSTR_RECEIVE_SANDBOX_TRIGGER_AUDIO =
+            "android:receive_sandbox_trigger_audio";
+
+    /**
+     * Allows the assistant app to get the negative trigger data from the PCC sandbox to improve
+     * the hotword training model.
+     *
+     * @hide
+     */
+    public static final String OPSTR_RECEIVE_SANDBOX_NEGATIVE_DATA_AUDIO =
+            "android:receive_sandbox_negative_data_audio";
+
     /** {@link #sAppOpsToNote} not initialized yet for this op */
     private static final byte SHOULD_COLLECT_NOTE_OP_NOT_INITIALIZED = 0;
     /** Should not collect noting of this app-op in {@link #sAppOpsToNote} */
@@ -2317,6 +2353,7 @@
             OP_ACCESS_NOTIFICATIONS,
             OP_SYSTEM_ALERT_WINDOW,
             OP_WRITE_SETTINGS,
+            OP_GET_USAGE_STATS,
             OP_REQUEST_INSTALL_PACKAGES,
             OP_START_FOREGROUND,
             OP_SMS_FINANCIAL_TRANSACTIONS,
@@ -2768,7 +2805,13 @@
         new AppOpInfo.Builder(OP_CAMERA_SANDBOXED, OPSTR_CAMERA_SANDBOXED,
             "CAMERA_SANDBOXED").setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
         new AppOpInfo.Builder(OP_RECORD_AUDIO_SANDBOXED, OPSTR_RECORD_AUDIO_SANDBOXED,
-                "RECORD_AUDIO_SANDBOXED").setDefaultMode(AppOpsManager.MODE_ALLOWED).build()
+                "RECORD_AUDIO_SANDBOXED").setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_RECEIVE_SANDBOX_TRIGGER_AUDIO,
+                OPSTR_RECEIVE_SANDBOX_TRIGGER_AUDIO,
+                "RECEIVE_SANDBOX_TRIGGER_AUDIO").build(),
+        new AppOpInfo.Builder(OP_RECEIVE_SANDBOX_NEGATIVE_DATA_AUDIO,
+                OPSTR_RECEIVE_SANDBOX_NEGATIVE_DATA_AUDIO,
+                "RECEIVE_SANDBOX_NEGATIVE_DATA_AUDIO").build()
     };
 
     // The number of longs needed to form a full bitmask of app ops
diff --git a/core/java/android/app/ApplicationExitInfo.java b/core/java/android/app/ApplicationExitInfo.java
index d859f3f..7b3d017 100644
--- a/core/java/android/app/ApplicationExitInfo.java
+++ b/core/java/android/app/ApplicationExitInfo.java
@@ -460,6 +460,14 @@
      */
     public static final int SUBREASON_SDK_SANDBOX_NOT_NEEDED = 28;
 
+    /**
+     * The process was killed because the binder proxy limit for system server was exceeded.
+     *
+     * For internal use only.
+     * @hide
+     */
+    public static final int SUBREASON_EXCESSIVE_BINDER_OBJECTS = 29;
+
     // If there is any OEM code which involves additional app kill reasons, it should
     // be categorized in {@link #REASON_OTHER}, with subreason code starting from 1000.
 
@@ -635,6 +643,7 @@
         SUBREASON_KILL_BACKGROUND,
         SUBREASON_PACKAGE_UPDATE,
         SUBREASON_UNDELIVERED_BROADCAST,
+        SUBREASON_EXCESSIVE_BINDER_OBJECTS,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface SubReason {}
@@ -1360,6 +1369,8 @@
                 return "PACKAGE UPDATE";
             case SUBREASON_UNDELIVERED_BROADCAST:
                 return "UNDELIVERED BROADCAST";
+            case SUBREASON_EXCESSIVE_BINDER_OBJECTS:
+                return "EXCESSIVE BINDER OBJECTS";
             default:
                 return "UNKNOWN";
         }
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 0ba56b9..9121cf0 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -91,6 +91,7 @@
 import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
+import android.os.IRemoteCallback;
 import android.os.Looper;
 import android.os.Message;
 import android.os.ParcelFileDescriptor;
@@ -826,7 +827,8 @@
 
     @Override
     public int checkPermission(String permName, String pkgName) {
-        return PermissionManager.checkPackageNamePermission(permName, pkgName, getUserId());
+        return PermissionManager.checkPackageNamePermission(permName, pkgName,
+                mContext.getDeviceId(), getUserId());
     }
 
     @Override
@@ -942,6 +944,13 @@
     }
 
     @Override
+    public Intent buildRequestPermissionsIntent(@NonNull String[] permissions) {
+        Intent intent = super.buildRequestPermissionsIntent(permissions);
+        intent.putExtra(EXTRA_REQUEST_PERMISSIONS_DEVICE_ID, mContext.getDeviceId());
+        return intent;
+    }
+
+    @Override
     public CharSequence getBackgroundPermissionOptionLabel() {
         try {
 
@@ -3911,4 +3920,24 @@
             throw e.rethrowFromSystemServer();
         }
     }
+
+    @Override
+    public void registerPackageMonitorCallback(@NonNull IRemoteCallback callback, int userId) {
+        Objects.requireNonNull(callback);
+        try {
+            mPM.registerPackageMonitorCallback(callback, userId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
+    public void unregisterPackageMonitorCallback(@NonNull IRemoteCallback callback) {
+        Objects.requireNonNull(callback);
+        try {
+            mPM.unregisterPackageMonitorCallback(callback);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
 }
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 82f4315..59b0dac 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -2262,7 +2262,7 @@
             Log.v(TAG, "Treating renounced permission " + permission + " as denied");
             return PERMISSION_DENIED;
         }
-        return PermissionManager.checkPermission(permission, pid, uid);
+        return PermissionManager.checkPermission(permission, pid, uid, getDeviceId());
     }
 
     /** @hide */
@@ -3463,7 +3463,7 @@
         AttributionSource attributionSource = new AttributionSource(Process.myUid(),
                 Process.myPid(), mOpPackageName, attributionTag,
                 (renouncedPermissions != null) ? renouncedPermissions.toArray(new String[0]) : null,
-                nextAttributionSource);
+                getDeviceId(), nextAttributionSource);
         // If we want to access protected data on behalf of another app we need to
         // tell the OS that we opt in to participate in the attribution chain.
         if (nextAttributionSource != null) {
@@ -3478,6 +3478,16 @@
             ((CompatResources) r).setContext(this);
         }
         mResources = r;
+
+        // only do this if the user already has more than one preferred locale
+        if (r.getConfiguration().getLocales().size() > 1) {
+            LocaleConfig lc = LocaleConfig.fromContextIgnoringOverride(this);
+            mResourcesManager.setLocaleList(lc != null
+                    && lc.getSupportedLocales() != null
+                    && !lc.getSupportedLocales().isEmpty()
+                    ? lc.getSupportedLocales()
+                    : null);
+        }
     }
 
     void installSystemApplicationInfo(ApplicationInfo info, ClassLoader classLoader) {
diff --git a/core/java/android/app/Dialog.java b/core/java/android/app/Dialog.java
index 411d157..4851279 100644
--- a/core/java/android/app/Dialog.java
+++ b/core/java/android/app/Dialog.java
@@ -694,12 +694,22 @@
      */
     @Override
     public boolean onKeyUp(int keyCode, @NonNull KeyEvent event) {
-        if ((keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_ESCAPE)
-                && event.isTracking()
-                && !event.isCanceled()
-                && !WindowOnBackInvokedDispatcher.isOnBackInvokedCallbackEnabled(mContext)) {
-            onBackPressed();
-            return true;
+        if (event.isTracking() && !event.isCanceled()) {
+            switch (keyCode) {
+                case KeyEvent.KEYCODE_BACK:
+                    if (!WindowOnBackInvokedDispatcher.isOnBackInvokedCallbackEnabled(mContext)) {
+                        onBackPressed();
+                        return true;
+                    }
+                    break;
+                case KeyEvent.KEYCODE_ESCAPE:
+                    if (mCancelable) {
+                        cancel();
+                    } else {
+                        dismiss();
+                    }
+                    return true;
+            }
         }
         return false;
     }
diff --git a/core/java/android/app/ExitTransitionCoordinator.java b/core/java/android/app/ExitTransitionCoordinator.java
index 930750e..46c677d 100644
--- a/core/java/android/app/ExitTransitionCoordinator.java
+++ b/core/java/android/app/ExitTransitionCoordinator.java
@@ -426,7 +426,7 @@
                 mSharedElementNotified = true;
                 delayCancel();
 
-                if (mExitCallbacks.isReturnTransitionAllowed()) {
+                if (mExitCallbacks != null && mExitCallbacks.isReturnTransitionAllowed()) {
                     mResultReceiver.send(MSG_ALLOW_RETURN_TRANSITION, null);
                 }
 
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 46260ea..a8b1688 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -924,4 +924,6 @@
     void unregisterUidFrozenStateChangedCallback(in IUidFrozenStateChangedCallback callback);
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS)")
     int[] getUidFrozenState(in int[] uids);
+
+    int checkPermissionForDevice(in String permission, int pid, int uid, int deviceId);
 }
diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl
index 4e2b6fa..d189bab 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -24,7 +24,6 @@
 import android.app.IApplicationThread;
 import android.app.IActivityClientController;
 import android.app.IActivityController;
-import android.app.IAppTask;
 import android.app.IAssistDataReceiver;
 import android.app.IInstrumentationWatcher;
 import android.app.IProcessObserver;
@@ -107,14 +106,6 @@
             in ProfilerInfo profilerInfo, in Bundle options, int userId);
     boolean startNextMatchingActivity(in IBinder callingActivity,
             in Intent intent, in Bundle options);
-
-    /**
-    *  The DreamActivity has to be started in a special way that does not involve the PackageParser.
-    *  The DreamActivity is a framework component inserted in the dream application process. Hence,
-    *  it is not declared in the application's manifest and cannot be parsed. startDreamActivity
-    *  creates the activity and starts it without reaching out to the PackageParser.
-    */
-    boolean startDreamActivity(in Intent intent);
     int startActivityIntentSender(in IApplicationThread caller,
             in IIntentSender target, in IBinder whitelistToken, in Intent fillInIntent,
             in String resolvedType, in IBinder resultTo, in String resultWho, int requestCode,
diff --git a/core/java/android/app/IApplicationThread.aidl b/core/java/android/app/IApplicationThread.aidl
index 6b5f6b0..75d8c10 100644
--- a/core/java/android/app/IApplicationThread.aidl
+++ b/core/java/android/app/IApplicationThread.aidl
@@ -78,6 +78,7 @@
     void scheduleStopService(IBinder token);
     void bindApplication(in String packageName, in ApplicationInfo info,
             in String sdkSandboxClientAppVolumeUuid, in String sdkSandboxClientAppPackage,
+            in boolean isSdkInSandbox,
             in ProviderInfoList providerList, in ComponentName testName,
             in ProfilerInfo profilerInfo, in Bundle testArguments,
             IInstrumentationWatcher testWatcher, IUiAutomationConnection uiAutomationConnection,
diff --git a/core/java/android/app/IGameManagerService.aidl b/core/java/android/app/IGameManagerService.aidl
index 3d6ab6f..9a818e4 100644
--- a/core/java/android/app/IGameManagerService.aidl
+++ b/core/java/android/app/IGameManagerService.aidl
@@ -20,6 +20,7 @@
 import android.app.GameModeInfo;
 import android.app.GameState;
 import android.app.IGameModeListener;
+import android.app.IGameStateListener;
 
 /**
  * @hide
@@ -49,4 +50,6 @@
     void addGameModeListener(IGameModeListener gameModeListener);
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_GAME_MODE)")
     void removeGameModeListener(IGameModeListener gameModeListener);
+    void addGameStateListener(IGameStateListener gameStateListener);
+    void removeGameStateListener(IGameStateListener gameStateListener);
 }
diff --git a/core/java/android/app/IGameStateListener.aidl b/core/java/android/app/IGameStateListener.aidl
new file mode 100644
index 0000000..34cff48
--- /dev/null
+++ b/core/java/android/app/IGameStateListener.aidl
@@ -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 android.app;
+
+import android.app.GameState;
+
+/** @hide */
+interface IGameStateListener {
+    /**
+     * Called when the state of the game has changed.
+     */
+    oneway void onGameStateChanged(String packageName, in GameState state, int userId);
+}
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index 0b48621..7f38b27 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -128,13 +128,16 @@
     // INotificationListener method.
     @UnsupportedAppUsage
     StatusBarNotification[] getActiveNotifications(String callingPkg);
+    @EnforcePermission("ACCESS_NOTIFICATIONS")
     StatusBarNotification[] getActiveNotificationsWithAttribution(String callingPkg,
             String callingAttributionTag);
     @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
     StatusBarNotification[] getHistoricalNotifications(String callingPkg, int count, boolean includeSnoozed);
+    @EnforcePermission("ACCESS_NOTIFICATIONS")
     StatusBarNotification[] getHistoricalNotificationsWithAttribution(String callingPkg,
             String callingAttributionTag, int count, boolean includeSnoozed);
 
+    @EnforcePermission("ACCESS_NOTIFICATIONS")
     NotificationHistory getNotificationHistory(String callingPkg, String callingAttributionTag);
 
     void registerListener(in INotificationListener listener, in ComponentName component, int userid);
diff --git a/core/java/android/app/IUiAutomationConnection.aidl b/core/java/android/app/IUiAutomationConnection.aidl
index fbb0748..63cae63 100644
--- a/core/java/android/app/IUiAutomationConnection.aidl
+++ b/core/java/android/app/IUiAutomationConnection.aidl
@@ -24,6 +24,8 @@
 import android.view.WindowContentFrameStats;
 import android.view.WindowAnimationFrameStats;
 import android.os.ParcelFileDescriptor;
+import android.window.ScreenCapture.ScreenCaptureListener;
+import android.window.ScreenCapture.LayerCaptureArgs;
 
 import java.util.List;
 
@@ -43,8 +45,8 @@
     void injectInputEventToInputFilter(in InputEvent event);
     void syncInputTransactions(boolean waitForAnimations);
     boolean setRotation(int rotation);
-    Bitmap takeScreenshot(in Rect crop);
-    Bitmap takeSurfaceControlScreenshot(in SurfaceControl surfaceControl);
+    boolean takeScreenshot(in Rect crop, in ScreenCaptureListener listener);
+    boolean takeSurfaceControlScreenshot(in SurfaceControl surfaceControl, in ScreenCaptureListener listener);
     boolean clearWindowContentFrameStats(int windowId);
     WindowContentFrameStats getWindowContentFrameStats(int windowId);
     void clearWindowAnimationFrameStats();
diff --git a/core/java/android/app/IUiModeManager.aidl b/core/java/android/app/IUiModeManager.aidl
index 2345c27..60b34cd 100644
--- a/core/java/android/app/IUiModeManager.aidl
+++ b/core/java/android/app/IUiModeManager.aidl
@@ -79,6 +79,7 @@
      * @param nightModeCustomType
      * @hide
      */
+    @EnforcePermission("MODIFY_DAY_NIGHT_MODE")
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE)")
     void setNightModeCustomType(int nightModeCustomType);
 
@@ -89,6 +90,7 @@
      * {@link #MODE_NIGHT_CUSTOM_TYPE_UNKNOWN}.
      * @hide
      */
+    @EnforcePermission("MODIFY_DAY_NIGHT_MODE")
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE)")
     int getNightModeCustomType();
 
@@ -163,21 +165,25 @@
     /**
     * Registers a listener for changes to projection state.
     */
+    @EnforcePermission("READ_PROJECTION_STATE")
     void addOnProjectionStateChangedListener(in IOnProjectionStateChangedListener listener, int projectionType);
 
     /**
     * Unregisters a listener for changes to projection state.
     */
+    @EnforcePermission("READ_PROJECTION_STATE")
     void removeOnProjectionStateChangedListener(in IOnProjectionStateChangedListener listener);
 
     /**
     * Returns packages that have currently set the given projection type.
     */
+    @EnforcePermission("READ_PROJECTION_STATE")
     List<String> getProjectingPackages(int projectionType);
 
     /**
     * Returns currently set projection types.
     */
+    @EnforcePermission("READ_PROJECTION_STATE")
     int getActiveProjectionTypes();
 
     /**
diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java
index 09450f5..e447dc5 100644
--- a/core/java/android/app/KeyguardManager.java
+++ b/core/java/android/app/KeyguardManager.java
@@ -61,6 +61,7 @@
 import com.android.internal.widget.LockPatternUtils;
 import com.android.internal.widget.LockPatternView;
 import com.android.internal.widget.LockscreenCredential;
+import com.android.internal.widget.PasswordValidationError;
 import com.android.internal.widget.VerifyCredentialResponse;
 
 import java.nio.charset.Charset;
@@ -117,6 +118,26 @@
             "android.app.action.CONFIRM_REMOTE_DEVICE_CREDENTIAL";
 
     /**
+     * Intent used to prompt user for device credential for entering repair
+     * mode. If the credential is verified successfully, then the information
+     * needed to verify the credential again will be written to a location that
+     * is available to repair mode. This makes it possible for repair mode to
+     * require that the same credential be provided to exit repair mode.
+     * @hide
+     */
+    public static final String ACTION_PREPARE_REPAIR_MODE_DEVICE_CREDENTIAL =
+            "android.app.action.PREPARE_REPAIR_MODE_DEVICE_CREDENTIAL";
+
+    /**
+     * Intent used to prompt user for device credential that is written by
+     * {@link #ACTION_PREPARE_REPAIR_MODE_DEVICE_CREDENTIAL} for exiting
+     * repair mode.
+     * @hide
+     */
+    public static final String ACTION_CONFIRM_REPAIR_MODE_DEVICE_CREDENTIAL =
+            "android.app.action.CONFIRM_REPAIR_MODE_DEVICE_CREDENTIAL";
+
+    /**
      * A CharSequence dialog title to show to the user when used with a
      * {@link #ACTION_CONFIRM_DEVICE_CREDENTIAL}.
      * @hide
@@ -863,17 +884,14 @@
         if (!checkInitialLockMethodUsage()) {
             return false;
         }
+        Objects.requireNonNull(password, "Password cannot be null.");
         complexity = PasswordMetrics.sanitizeComplexityLevel(complexity);
-        // TODO: b/131755827 add devicePolicyManager support for Auto
-        DevicePolicyManager devicePolicyManager =
-                (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
         PasswordMetrics adminMetrics =
-                devicePolicyManager.getPasswordMinimumMetrics(mContext.getUserId());
-        // Check if the password fits the mold of a pin or pattern.
-        boolean isPinOrPattern = lockType != PASSWORD;
-
-        return PasswordMetrics.validatePassword(
-                adminMetrics, complexity, isPinOrPattern, password).size() == 0;
+                mLockPatternUtils.getRequestedPasswordMetrics(mContext.getUserId());
+        try (LockscreenCredential credential = createLockscreenCredential(lockType, password)) {
+            return PasswordMetrics.validateCredential(adminMetrics, complexity,
+                    credential).size() == 0;
+        }
     }
 
     /**
@@ -892,11 +910,8 @@
             return -1;
         }
         complexity = PasswordMetrics.sanitizeComplexityLevel(complexity);
-        // TODO: b/131755827 add devicePolicyManager support for Auto
-        DevicePolicyManager devicePolicyManager =
-                (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
         PasswordMetrics adminMetrics =
-                devicePolicyManager.getPasswordMinimumMetrics(mContext.getUserId());
+                mLockPatternUtils.getRequestedPasswordMetrics(mContext.getUserId());
         PasswordMetrics minMetrics =
                 PasswordMetrics.applyComplexity(adminMetrics, isPin, complexity);
         return minMetrics.length;
@@ -1118,6 +1133,14 @@
                 currentLockType, currentPassword);
         LockscreenCredential newCredential = createLockscreenCredential(
                 newLockType, newPassword);
+        PasswordMetrics adminMetrics =
+                mLockPatternUtils.getRequestedPasswordMetrics(mContext.getUserId());
+        List<PasswordValidationError> errors = PasswordMetrics.validateCredential(adminMetrics,
+                DevicePolicyManager.PASSWORD_COMPLEXITY_NONE, newCredential);
+        if (!errors.isEmpty()) {
+            Log.e(TAG, "New credential is not valid: " + errors.get(0));
+            return false;
+        }
         return mLockPatternUtils.setLockCredential(newCredential, currentCredential, userId);
     }
 
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index b5efb73..8fea03b 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -1401,95 +1401,99 @@
         if (mApplication != null) {
             return mApplication;
         }
-        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "makeApplication");
 
-        synchronized (sApplications) {
-            final Application cached = sApplications.get(mPackageName);
-            if (cached != null) {
-                // Looks like this is always happening for the system server, because
-                // the LoadedApk created in systemMain() -> attach() isn't cached properly?
-                if (!"android".equals(mPackageName)) {
-                    Slog.wtfStack(TAG, "App instance already created for package=" + mPackageName
-                            + " instance=" + cached);
-                }
-                if (!allowDuplicateInstances) {
-                    mApplication = cached;
-                    return cached;
-                }
-                // Some apps intentionally call makeApplication() to create a new Application
-                // instance... Sigh...
-            }
-        }
 
-        Application app = null;
-
-        final String myProcessName = Process.myProcessName();
-        String appClass = mApplicationInfo.getCustomApplicationClassNameForProcess(
-                myProcessName);
-        if (forceDefaultAppClass || (appClass == null)) {
-            appClass = "android.app.Application";
+        if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
+            Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "makeApplication");
         }
 
         try {
-            final java.lang.ClassLoader cl = getClassLoader();
-            if (!mPackageName.equals("android")) {
-                Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
-                        "initializeJavaContextClassLoader");
-                initializeJavaContextClassLoader();
-                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
-            }
-
-            // Rewrite the R 'constants' for all library apks.
-            SparseArray<String> packageIdentifiers = getAssets().getAssignedPackageIdentifiers(
-                    false, false);
-            for (int i = 0, n = packageIdentifiers.size(); i < n; i++) {
-                final int id = packageIdentifiers.keyAt(i);
-                if (id == 0x01 || id == 0x7f) {
-                    continue;
-                }
-
-                rewriteRValues(cl, packageIdentifiers.valueAt(i), id);
-            }
-
-            ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
-            // The network security config needs to be aware of multiple
-            // applications in the same process to handle discrepancies
-            NetworkSecurityConfigProvider.handleNewApplication(appContext);
-            app = mActivityThread.mInstrumentation.newApplication(
-                    cl, appClass, appContext);
-            appContext.setOuterContext(app);
-        } catch (Exception e) {
-            if (!mActivityThread.mInstrumentation.onException(app, e)) {
-                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
-                throw new RuntimeException(
-                    "Unable to instantiate application " + appClass
-                    + " package " + mPackageName + ": " + e.toString(), e);
-            }
-        }
-        mActivityThread.mAllApplications.add(app);
-        mApplication = app;
-        if (!allowDuplicateInstances) {
             synchronized (sApplications) {
-                sApplications.put(mPackageName, app);
-            }
-        }
-
-        if (instrumentation != null) {
-            try {
-                instrumentation.callApplicationOnCreate(app);
-            } catch (Exception e) {
-                if (!instrumentation.onException(app, e)) {
-                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
-                    throw new RuntimeException(
-                        "Unable to create application " + app.getClass().getName()
-                        + ": " + e.toString(), e);
+                final Application cached = sApplications.get(mPackageName);
+                if (cached != null) {
+                    // Looks like this is always happening for the system server, because
+                    // the LoadedApk created in systemMain() -> attach() isn't cached properly?
+                    if (!"android".equals(mPackageName)) {
+                        Slog.wtfStack(TAG, "App instance already created for package="
+                                + mPackageName + " instance=" + cached);
+                    }
+                    if (!allowDuplicateInstances) {
+                        mApplication = cached;
+                        return cached;
+                    }
+                    // Some apps intentionally call makeApplication() to create a new Application
+                    // instance... Sigh...
                 }
             }
+
+            Application app = null;
+
+            final String myProcessName = Process.myProcessName();
+            String appClass = mApplicationInfo.getCustomApplicationClassNameForProcess(
+                    myProcessName);
+            if (forceDefaultAppClass || (appClass == null)) {
+                appClass = "android.app.Application";
+            }
+
+            try {
+                final java.lang.ClassLoader cl = getClassLoader();
+                if (!mPackageName.equals("android")) {
+                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+                            "initializeJavaContextClassLoader");
+                    initializeJavaContextClassLoader();
+                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+                }
+
+                // Rewrite the R 'constants' for all library apks.
+                SparseArray<String> packageIdentifiers = getAssets().getAssignedPackageIdentifiers(
+                        false, false);
+                for (int i = 0, n = packageIdentifiers.size(); i < n; i++) {
+                    final int id = packageIdentifiers.keyAt(i);
+                    if (id == 0x01 || id == 0x7f) {
+                        continue;
+                    }
+
+                    rewriteRValues(cl, packageIdentifiers.valueAt(i), id);
+                }
+
+                ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
+                // The network security config needs to be aware of multiple
+                // applications in the same process to handle discrepancies
+                NetworkSecurityConfigProvider.handleNewApplication(appContext);
+                app = mActivityThread.mInstrumentation.newApplication(
+                        cl, appClass, appContext);
+                appContext.setOuterContext(app);
+            } catch (Exception e) {
+                if (!mActivityThread.mInstrumentation.onException(app, e)) {
+                    throw new RuntimeException(
+                        "Unable to instantiate application " + appClass
+                        + " package " + mPackageName + ": " + e.toString(), e);
+                }
+            }
+            mActivityThread.mAllApplications.add(app);
+            mApplication = app;
+            if (!allowDuplicateInstances) {
+                synchronized (sApplications) {
+                    sApplications.put(mPackageName, app);
+                }
+            }
+
+            if (instrumentation != null) {
+                try {
+                    instrumentation.callApplicationOnCreate(app);
+                } catch (Exception e) {
+                    if (!instrumentation.onException(app, e)) {
+                        throw new RuntimeException(
+                            "Unable to create application " + app.getClass().getName()
+                            + ": " + e.toString(), e);
+                    }
+                }
+            }
+
+            return app;
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
         }
-
-        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
-
-        return app;
     }
 
     @UnsupportedAppUsage
diff --git a/core/java/android/app/LocaleConfig.java b/core/java/android/app/LocaleConfig.java
index 5031a33..729e555 100644
--- a/core/java/android/app/LocaleConfig.java
+++ b/core/java/android/app/LocaleConfig.java
@@ -138,6 +138,10 @@
         try {
             //Get the resource id
             resId = new ApplicationInfo(context.getApplicationInfo()).getLocaleConfigRes();
+            if (resId == 0) {
+                mStatus = STATUS_NOT_SPECIFIED;
+                return;
+            }
             //Get the parser to read XML data
             XmlResourceParser parser = res.getXml(resId);
             parseLocaleConfig(parser, res);
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 11dd30b..892b45e 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -2837,12 +2837,14 @@
     }
 
     /**
-     * Note all {@link Uri} that are referenced internally, with the expectation
-     * that Uri permission grants will need to be issued to ensure the recipient
-     * of this object is able to render its contents.
-     *
-     * @hide
-     */
+    * Note all {@link Uri} that are referenced internally, with the expectation that Uri permission
+    * grants will need to be issued to ensure the recipient of this object is able to render its
+    * contents.
+    * See b/281044385 for more context and examples about what happens when this isn't done
+    * correctly.
+    *
+    * @hide
+    */
     public void visitUris(@NonNull Consumer<Uri> visitor) {
         if (publicVersion != null) {
             publicVersion.visitUris(visitor);
@@ -2886,13 +2888,13 @@
             ArrayList<Person> people = extras.getParcelableArrayList(EXTRA_PEOPLE_LIST, android.app.Person.class);
             if (people != null && !people.isEmpty()) {
                 for (Person p : people) {
-                    visitor.accept(p.getIconUri());
+                    p.visitUris(visitor);
                 }
             }
 
             final Person person = extras.getParcelable(EXTRA_MESSAGING_PERSON, Person.class);
             if (person != null) {
-                visitor.accept(person.getIconUri());
+                person.visitUris(visitor);
             }
 
             final RemoteInputHistoryItem[] history = extras.getParcelableArray(
@@ -2914,12 +2916,7 @@
             if (!ArrayUtils.isEmpty(messages)) {
                 for (MessagingStyle.Message message : MessagingStyle.Message
                         .getMessagesFromBundleArray(messages)) {
-                    visitor.accept(message.getDataUri());
-
-                    Person senderPerson = message.getSenderPerson();
-                    if (senderPerson != null) {
-                        visitor.accept(senderPerson.getIconUri());
-                    }
+                    message.visitUris(visitor);
                 }
             }
 
@@ -2928,12 +2925,7 @@
             if (!ArrayUtils.isEmpty(historic)) {
                 for (MessagingStyle.Message message : MessagingStyle.Message
                         .getMessagesFromBundleArray(historic)) {
-                    visitor.accept(message.getDataUri());
-
-                    Person senderPerson = message.getSenderPerson();
-                    if (senderPerson != null) {
-                        visitor.accept(senderPerson.getIconUri());
-                    }
+                    message.visitUris(visitor);
                 }
             }
 
@@ -2943,7 +2935,7 @@
         if (isStyle(CallStyle.class) & extras != null) {
             Person callPerson = extras.getParcelable(EXTRA_CALL_PERSON, Person.class);
             if (callPerson != null) {
-                visitor.accept(callPerson.getIconUri());
+                callPerson.visitUris(visitor);
             }
             visitIconUri(visitor, extras.getParcelable(EXTRA_VERIFICATION_ICON, Icon.class));
         }
@@ -3055,10 +3047,9 @@
         // cannot look into the extras as there may be parcelables there that
         // the platform does not know how to handle. To go around that we have
         // an explicit list of the pending intents in the extras bundle.
-        final boolean collectPendingIntents = (allPendingIntents == null);
-        if (collectPendingIntents) {
-            PendingIntent.setOnMarshaledListener(
-                    (PendingIntent intent, Parcel out, int outFlags) -> {
+        PendingIntent.OnMarshaledListener addedListener = null;
+        if (allPendingIntents == null) {
+            addedListener = (PendingIntent intent, Parcel out, int outFlags) -> {
                 if (parcel == out) {
                     synchronized (this) {
                         if (allPendingIntents == null) {
@@ -3067,7 +3058,8 @@
                         allPendingIntents.add(intent);
                     }
                 }
-            });
+            };
+            PendingIntent.addOnMarshaledListener(addedListener);
         }
         try {
             // IMPORTANT: Add marshaling code in writeToParcelImpl as we
@@ -3078,8 +3070,8 @@
                 parcel.writeArraySet(allPendingIntents);
             }
         } finally {
-            if (collectPendingIntents) {
-                PendingIntent.setOnMarshaledListener(null);
+            if (addedListener != null) {
+                PendingIntent.removeOnMarshaledListener(addedListener);
             }
         }
     }
@@ -3485,8 +3477,11 @@
      *
      * @hide
      */
-    public void setAllowlistToken(@Nullable IBinder token) {
-        mAllowlistToken = token;
+    public void clearAllowlistToken() {
+        mAllowlistToken = null;
+        if (publicVersion != null) {
+            publicVersion.clearAllowlistToken();
+        }
     }
 
     /**
@@ -8812,9 +8807,11 @@
             }
 
             /**
+             * Converts the message into a {@link Bundle}. To extract the message back,
+             * check {@link #getMessageFromBundle()}
              * @hide
              */
-            @VisibleForTesting
+            @NonNull
             public Bundle toBundle() {
                 Bundle bundle = new Bundle();
                 if (mText != null) {
@@ -8842,6 +8839,18 @@
             }
 
             /**
+             * See {@link Notification#visitUris(Consumer)}.
+             *
+             * @hide
+             */
+            public void visitUris(@NonNull Consumer<Uri> visitor) {
+                visitor.accept(getDataUri());
+                if (mSender != null) {
+                    mSender.visitUris(visitor);
+                }
+            }
+
+            /**
              * Returns a list of messages read from the given bundle list, e.g.
              * {@link #EXTRA_MESSAGES} or {@link #EXTRA_HISTORIC_MESSAGES}.
              */
@@ -12151,10 +12160,7 @@
      * <p>TV extensions can be accessed on an existing notification by using the
      * {@code TvExtender(Notification)} constructor, and then using the {@code get} methods
      * to access values.
-     *
-     * @hide
      */
-    @SystemApi
     public static final class TvExtender implements Extender {
         private static final String TAG = "TvExtender";
 
@@ -12186,7 +12192,7 @@
          *
          * @param notif The notification from which to copy options.
          */
-        public TvExtender(Notification notif) {
+        public TvExtender(@NonNull Notification notif) {
             Bundle bundle = notif.extras == null ?
                 null : notif.extras.getBundle(EXTRA_TV_EXTENDER);
             if (bundle != null) {
@@ -12204,7 +12210,8 @@
          * method of {@link Notification.Builder}.
          */
         @Override
-        public Notification.Builder extend(Notification.Builder builder) {
+        @NonNull
+        public Notification.Builder extend(@NonNull Notification.Builder builder) {
             Bundle bundle = new Bundle();
 
             bundle.putInt(EXTRA_FLAGS, mFlags);
@@ -12223,7 +12230,7 @@
         }
 
         /**
-         * Returns true if this notification should be shown on TV. This method return true
+         * Returns true if this notification should be shown on TV. This method returns true
          * if the notification was extended with a TvExtender.
          */
         public boolean isAvailableOnTv() {
@@ -12233,8 +12240,11 @@
         /**
          * Specifies the channel the notification should be delivered on when shown on TV.
          * It can be different from the channel that the notification is delivered to when
-         * posting on a non-TV device.
+         * posting on a non-TV device. Prefer to use {@link setChannelId(String)}.
+         *
+         * @hide
          */
+        @SystemApi
         public TvExtender setChannel(String channelId) {
             mChannelId = channelId;
             return this;
@@ -12244,14 +12254,21 @@
          * Specifies the channel the notification should be delivered on when shown on TV.
          * It can be different from the channel that the notification is delivered to when
          * posting on a non-TV device.
+         *
+         * @return this object for method chaining
          */
-        public TvExtender setChannelId(String channelId) {
+        @NonNull
+        public TvExtender setChannelId(@Nullable String channelId) {
             mChannelId = channelId;
             return this;
         }
 
-        /** @removed */
+        /**
+         * @removed
+         * @hide
+         */
         @Deprecated
+        @SystemApi
         public String getChannel() {
             return mChannelId;
         }
@@ -12259,6 +12276,7 @@
         /**
          * Returns the id of the channel this notification posts to on TV.
          */
+        @Nullable
         public String getChannelId() {
             return mChannelId;
         }
@@ -12267,8 +12285,12 @@
          * Supplies a {@link PendingIntent} to be sent when the notification is selected on TV.
          * If provided, it is used instead of the content intent specified
          * at the level of Notification.
+         *
+         * @param intent the {@link PendingIntent} for the associated notification content
+         * @return this object for method chaining
          */
-        public TvExtender setContentIntent(PendingIntent intent) {
+        @NonNull
+        public TvExtender setContentIntent(@Nullable PendingIntent intent) {
             mContentIntent = intent;
             return this;
         }
@@ -12277,8 +12299,9 @@
          * Returns the TV-specific content intent.  If this method returns null, the
          * main content intent on the notification should be used.
          *
-         * @see {@link Notification#contentIntent}
+         * @see Notification#contentIntent
          */
+        @Nullable
         public PendingIntent getContentIntent() {
             return mContentIntent;
         }
@@ -12287,8 +12310,12 @@
          * Supplies a {@link PendingIntent} to send when the notification is cleared explicitly
          * by the user on TV.  If provided, it is used instead of the delete intent specified
          * at the level of Notification.
+         *
+         * @param intent the {@link PendingIntent} for the associated notification deletion
+         * @return this object for method chaining
          */
-        public TvExtender setDeleteIntent(PendingIntent intent) {
+        @NonNull
+        public TvExtender setDeleteIntent(@Nullable PendingIntent intent) {
             mDeleteIntent = intent;
             return this;
         }
@@ -12297,8 +12324,9 @@
          * Returns the TV-specific delete intent.  If this method returns null, the
          * main delete intent on the notification should be used.
          *
-         * @see {@link Notification#deleteIntent}
+         * @see Notification#deleteIntent
          */
+        @Nullable
         public PendingIntent getDeleteIntent() {
             return mDeleteIntent;
         }
@@ -12306,7 +12334,11 @@
         /**
          * Specifies whether this notification should suppress showing a message over top of apps
          * outside of the launcher.
+         *
+         * @param suppress whether the notification should suppress showing over apps.
+         * @return this object for method chaining
          */
+        @NonNull
         public TvExtender setSuppressShowOverApps(boolean suppress) {
             mSuppressShowOverApps = suppress;
             return this;
@@ -12315,10 +12347,21 @@
         /**
          * Returns true if this notification should not show messages over top of apps
          * outside of the launcher.
+         *
+         * @hide
          */
+        @SystemApi
         public boolean getSuppressShowOverApps() {
             return mSuppressShowOverApps;
         }
+
+        /**
+         * Returns true if this notification should not show messages over top of apps
+         * outside of the launcher.
+         */
+        public boolean isSuppressShowOverApps() {
+            return mSuppressShowOverApps;
+        }
     }
 
     /**
diff --git a/core/java/android/app/OWNERS b/core/java/android/app/OWNERS
index f7d2afb..e1c45d9 100644
--- a/core/java/android/app/OWNERS
+++ b/core/java/android/app/OWNERS
@@ -63,10 +63,11 @@
 # Multiuser
 per-file *User* = file:/MULTIUSER_OWNERS
 
-# Notification, DND, Status bar
+# Notification, DND, Status bar, UiModeManager
 per-file *Notification* = file:/packages/SystemUI/OWNERS
 per-file *Zen* = file:/packages/SystemUI/OWNERS
 per-file *StatusBar* = file:/packages/SystemUI/OWNERS
+per-file *UiModeManager* = file:/packages/SystemUI/OWNERS
 
 # PackageManager
 per-file ApplicationPackageManager.java = file:/services/core/java/com/android/server/pm/OWNERS
diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java
index 705b5ee..62209b0 100644
--- a/core/java/android/app/PendingIntent.java
+++ b/core/java/android/app/PendingIntent.java
@@ -64,6 +64,7 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
@@ -263,7 +264,7 @@
      * be mutable by default, unless {@link #FLAG_IMMUTABLE} is set. Starting
      * with {@link android.os.Build.VERSION_CODES#S}, it will be required to
      * explicitly specify the mutability of PendingIntents on creation with
-     * either (@link #FLAG_IMMUTABLE} or {@link #FLAG_MUTABLE}. It is strongly
+     * either {@link #FLAG_IMMUTABLE} or {@link #FLAG_MUTABLE}. It is strongly
      * recommended to use {@link #FLAG_IMMUTABLE} when creating a
      * PendingIntent. {@link #FLAG_MUTABLE} should only be used when some
      * functionality relies on modifying the underlying intent, e.g. any
@@ -391,11 +392,12 @@
         void onMarshaled(PendingIntent intent, Parcel parcel, int flags);
     }
 
-    private static final ThreadLocal<OnMarshaledListener> sOnMarshaledListener
-            = new ThreadLocal<>();
+    private static final ThreadLocal<List<OnMarshaledListener>> sOnMarshaledListener =
+            ThreadLocal.withInitial(ArrayList::new);
 
     /**
-     * Registers an listener for pending intents being written to a parcel.
+     * Registers an listener for pending intents being written to a parcel. This replaces any
+     * listeners previously added.
      *
      * @param listener The listener, null to clear.
      *
@@ -403,7 +405,27 @@
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     public static void setOnMarshaledListener(OnMarshaledListener listener) {
-        sOnMarshaledListener.set(listener);
+        final List<OnMarshaledListener> listeners = sOnMarshaledListener.get();
+        listeners.clear();
+        if (listener != null) {
+            listeners.add(listener);
+        }
+    }
+
+    /**
+     * Adds a listener for pending intents being written to a parcel.
+     * @hide
+     */
+    static void addOnMarshaledListener(OnMarshaledListener listener) {
+        sOnMarshaledListener.get().add(listener);
+    }
+
+    /**
+     * Removes a listener for pending intents being written to a parcel.
+     * @hide
+     */
+    static void removeOnMarshaledListener(OnMarshaledListener listener) {
+        sOnMarshaledListener.get().remove(listener);
     }
 
     private static void checkPendingIntent(int flags, @NonNull Intent intent,
@@ -1451,11 +1473,11 @@
 
     public void writeToParcel(Parcel out, int flags) {
         out.writeStrongBinder(mTarget.asBinder());
-        OnMarshaledListener listener = sOnMarshaledListener.get();
-        if (listener != null) {
-            listener.onMarshaled(this, out, flags);
+        final List<OnMarshaledListener> listeners = sOnMarshaledListener.get();
+        final int numListeners = listeners.size();
+        for (int i = 0; i < numListeners; i++) {
+            listeners.get(i).onMarshaled(this, out, flags);
         }
-
     }
 
     public static final @NonNull Creator<PendingIntent> CREATOR = new Creator<PendingIntent>() {
@@ -1483,9 +1505,10 @@
             @NonNull Parcel out) {
         out.writeStrongBinder(sender != null ? sender.mTarget.asBinder() : null);
         if (sender != null) {
-            OnMarshaledListener listener = sOnMarshaledListener.get();
-            if (listener != null) {
-                listener.onMarshaled(sender, out, 0 /* flags */);
+            final List<OnMarshaledListener> listeners = sOnMarshaledListener.get();
+            final int numListeners = listeners.size();
+            for (int i = 0; i < numListeners; i++) {
+                listeners.get(i).onMarshaled(sender, out, 0 /* flags */);
             }
         }
     }
diff --git a/core/java/android/app/Person.java b/core/java/android/app/Person.java
index 97a794d..18fc0ce 100644
--- a/core/java/android/app/Person.java
+++ b/core/java/android/app/Person.java
@@ -24,6 +24,7 @@
 import android.os.Parcelable;
 
 import java.util.Objects;
+import java.util.function.Consumer;
 
 /**
  * Provides an immutable reference to an entity that appears repeatedly on different surfaces of the
@@ -177,6 +178,19 @@
         dest.writeBoolean(mIsBot);
     }
 
+    /**
+     * Note all {@link Uri} that are referenced internally, with the expectation that Uri permission
+     * grants will need to be issued to ensure the recipient of this object is able to render its
+     * contents.
+     * See b/281044385 for more context and examples about what happens when this isn't done
+     * correctly.
+     *
+     * @hide
+     */
+    public void visitUris(@NonNull Consumer<Uri> visitor) {
+        visitor.accept(getIconUri());
+    }
+
     /** Builder for the immutable {@link Person} class. */
     public static class Builder {
         @Nullable private CharSequence mName;
diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java
index 7e6386e..1ecb5d3 100644
--- a/core/java/android/app/ResourcesManager.java
+++ b/core/java/android/app/ResourcesManager.java
@@ -36,6 +36,7 @@
 import android.content.res.loader.ResourcesLoader;
 import android.hardware.display.DisplayManagerGlobal;
 import android.os.IBinder;
+import android.os.LocaleList;
 import android.os.Process;
 import android.os.Trace;
 import android.util.ArrayMap;
@@ -118,6 +119,11 @@
     private final ArrayList<WeakReference<Resources>> mResourceReferences = new ArrayList<>();
     private final ReferenceQueue<Resources> mResourcesReferencesQueue = new ReferenceQueue<>();
 
+    /**
+     * The list of locales the app declares it supports.
+     */
+    private LocaleList mLocaleList = LocaleList.getEmptyLocaleList();
+
     private static class ApkKey {
         public final String path;
         public final boolean sharedLib;
@@ -1605,6 +1611,22 @@
         }
     }
 
+    /**
+     * Returns the LocaleList current set
+     */
+    public LocaleList getLocaleList() {
+        return mLocaleList;
+    }
+
+    /**
+     * Sets the LocaleList of app's supported locales
+     */
+    public void setLocaleList(LocaleList localeList) {
+        if ((localeList != null) && !localeList.isEmpty()) {
+            mLocaleList = localeList;
+        }
+    }
+
     private class UpdateHandler implements Resources.UpdateCallbacks {
 
         /**
diff --git a/core/java/android/app/SearchableInfo.java b/core/java/android/app/SearchableInfo.java
index bd5d105..05742e6 100644
--- a/core/java/android/app/SearchableInfo.java
+++ b/core/java/android/app/SearchableInfo.java
@@ -535,7 +535,7 @@
      */
     public static SearchableInfo getActivityMetaData(Context context, ActivityInfo activityInfo,
             int userId) {
-        Context userContext = null;
+        Context userContext;
         try {
             userContext = context.createPackageContextAsUser("system", 0,
                 new UserHandle(userId));
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 4f5da99..8647dd2 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -171,6 +171,7 @@
 import android.os.IDumpstate;
 import android.os.IHardwarePropertiesManager;
 import android.os.IPowerManager;
+import android.os.IPowerStatsService;
 import android.os.IRecoverySystem;
 import android.os.ISystemUpdateManager;
 import android.os.IThermalService;
@@ -381,8 +382,10 @@
         registerService(Context.SELECTION_TOOLBAR_SERVICE, SelectionToolbarManager.class,
                 new CachedServiceFetcher<SelectionToolbarManager>() {
                     @Override
-                    public SelectionToolbarManager createService(ContextImpl ctx) {
-                        IBinder b = ServiceManager.getService(Context.SELECTION_TOOLBAR_SERVICE);
+                    public SelectionToolbarManager createService(ContextImpl ctx)
+                            throws ServiceNotFoundException {
+                        IBinder b = ServiceManager.getServiceOrThrow(
+                                Context.SELECTION_TOOLBAR_SERVICE);
                         return new SelectionToolbarManager(ctx.getOuterContext(),
                                 ISelectionToolbarManager.Stub.asInterface(b));
                     }});
@@ -1117,8 +1120,10 @@
                 new CachedServiceFetcher<SystemHealthManager>() {
             @Override
             public SystemHealthManager createService(ContextImpl ctx) throws ServiceNotFoundException {
-                IBinder b = ServiceManager.getServiceOrThrow(BatteryStats.SERVICE_NAME);
-                return new SystemHealthManager(IBatteryStats.Stub.asInterface(b));
+                IBinder batteryStats = ServiceManager.getServiceOrThrow(BatteryStats.SERVICE_NAME);
+                IBinder powerStats = ServiceManager.getService(Context.POWER_STATS_SERVICE);
+                return new SystemHealthManager(IBatteryStats.Stub.asInterface(batteryStats),
+                        IPowerStatsService.Stub.asInterface(powerStats));
             }});
 
         registerService(Context.CONTEXTHUB_SERVICE, ContextHubManager.class,
diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java
index c4e4995..2b5175c 100644
--- a/core/java/android/app/TaskInfo.java
+++ b/core/java/android/app/TaskInfo.java
@@ -242,6 +242,18 @@
     public boolean isLetterboxDoubleTapEnabled;
 
     /**
+     * Whether the user aspect ratio settings button is enabled
+     * @hide
+     */
+    public boolean topActivityEligibleForUserAspectRatioButton;
+
+    /**
+     * Hint about the letterbox state of the top activity.
+     * @hide
+     */
+    public boolean topActivityBoundsLetterboxed;
+
+    /**
      * Whether the update comes from a letterbox double-tap action from the user or not.
      * @hide
      */
@@ -460,7 +472,8 @@
     public boolean hasCompatUI() {
         return hasCameraCompatControl() || topActivityInSizeCompat
                 || topActivityEligibleForLetterboxEducation
-                || isLetterboxDoubleTapEnabled;
+                || isLetterboxDoubleTapEnabled
+                || topActivityEligibleForUserAspectRatioButton;
     }
 
     /**
@@ -510,6 +523,8 @@
                 && supportsMultiWindow == that.supportsMultiWindow
                 && displayAreaFeatureId == that.displayAreaFeatureId
                 && isFromLetterboxDoubleTap == that.isFromLetterboxDoubleTap
+                && topActivityEligibleForUserAspectRatioButton
+                    == that.topActivityEligibleForUserAspectRatioButton
                 && topActivityLetterboxVerticalPosition == that.topActivityLetterboxVerticalPosition
                 && topActivityLetterboxWidth == that.topActivityLetterboxWidth
                 && topActivityLetterboxHeight == that.topActivityLetterboxHeight
@@ -543,6 +558,8 @@
                 && taskId == that.taskId
                 && topActivityInSizeCompat == that.topActivityInSizeCompat
                 && isFromLetterboxDoubleTap == that.isFromLetterboxDoubleTap
+                && topActivityEligibleForUserAspectRatioButton
+                    == that.topActivityEligibleForUserAspectRatioButton
                 && topActivityEligibleForLetterboxEducation
                     == that.topActivityEligibleForLetterboxEducation
                 && topActivityLetterboxVerticalPosition == that.topActivityLetterboxVerticalPosition
@@ -606,6 +623,8 @@
         displayAreaFeatureId = source.readInt();
         cameraCompatControlState = source.readInt();
         isLetterboxDoubleTapEnabled = source.readBoolean();
+        topActivityEligibleForUserAspectRatioButton = source.readBoolean();
+        topActivityBoundsLetterboxed = source.readBoolean();
         isFromLetterboxDoubleTap = source.readBoolean();
         topActivityLetterboxVerticalPosition = source.readInt();
         topActivityLetterboxHorizontalPosition = source.readInt();
@@ -660,6 +679,8 @@
         dest.writeInt(displayAreaFeatureId);
         dest.writeInt(cameraCompatControlState);
         dest.writeBoolean(isLetterboxDoubleTapEnabled);
+        dest.writeBoolean(topActivityEligibleForUserAspectRatioButton);
+        dest.writeBoolean(topActivityBoundsLetterboxed);
         dest.writeBoolean(isFromLetterboxDoubleTap);
         dest.writeInt(topActivityLetterboxVerticalPosition);
         dest.writeInt(topActivityLetterboxHorizontalPosition);
@@ -701,8 +722,11 @@
                 + " topActivityInSizeCompat=" + topActivityInSizeCompat
                 + " topActivityEligibleForLetterboxEducation= "
                         + topActivityEligibleForLetterboxEducation
-                + " topActivityLetterboxed= " + isLetterboxDoubleTapEnabled
-                + " isFromDoubleTap= " + isFromLetterboxDoubleTap
+                + " isLetterboxDoubleTapEnabled= " + isLetterboxDoubleTapEnabled
+                + " topActivityEligibleForUserAspectRatioButton= "
+                + topActivityEligibleForUserAspectRatioButton
+                + " topActivityBoundsLetterboxed= " + topActivityBoundsLetterboxed
+                + " isFromLetterboxDoubleTap= " + isFromLetterboxDoubleTap
                 + " topActivityLetterboxVerticalPosition= " + topActivityLetterboxVerticalPosition
                 + " topActivityLetterboxHorizontalPosition= "
                         + topActivityLetterboxHorizontalPosition
diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java
index 247d5bc..b0180c1 100644
--- a/core/java/android/app/UiAutomation.java
+++ b/core/java/android/app/UiAutomation.java
@@ -37,6 +37,7 @@
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.Region;
+import android.hardware.HardwareBuffer;
 import android.hardware.display.DisplayManagerGlobal;
 import android.os.Build;
 import android.os.Handler;
@@ -71,6 +72,8 @@
 import android.view.accessibility.AccessibilityWindowInfo;
 import android.view.accessibility.IAccessibilityInteractionConnection;
 import android.view.inputmethod.EditorInfo;
+import android.window.ScreenCapture;
+import android.window.ScreenCapture.ScreenshotHardwareBuffer;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
@@ -553,6 +556,21 @@
     }
 
     /**
+     * Provides reference to the cache through a locked connection.
+     *
+     * @return the accessibility cache.
+     * @hide
+     */
+    public @Nullable AccessibilityCache getCache() {
+        final int connectionId;
+        synchronized (mLock) {
+            throwIfNotConnectedLocked();
+            connectionId = mConnectionId;
+        }
+        return AccessibilityInteractionClient.getCache(connectionId);
+    }
+
+    /**
      * Adopt the permission identity of the shell UID for all permissions. This allows
      * you to call APIs protected permissions which normal apps cannot hold but are
      * granted to the shell UID. If you already adopted all shell permissions by calling
@@ -827,6 +845,22 @@
      *            established.
      */
     public AccessibilityNodeInfo getRootInActiveWindow() {
+        return getRootInActiveWindow(AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS_HYBRID);
+    }
+
+    /**
+     * Gets the root {@link AccessibilityNodeInfo} in the active window.
+     *
+     * @param prefetchingStrategy the prefetching strategy.
+     * @return The root info.
+     * @throws IllegalStateException If the connection to the accessibility subsystem is not
+     * established.
+     *
+     * @hide
+     */
+    @Nullable
+    public AccessibilityNodeInfo getRootInActiveWindow(
+            @AccessibilityNodeInfo.PrefetchingStrategy int prefetchingStrategy) {
         final int connectionId;
         synchronized (mLock) {
             throwIfNotConnectedLocked();
@@ -834,8 +868,7 @@
         }
         // Calling out without a lock held.
         return AccessibilityInteractionClient.getInstance()
-                .getRootInActiveWindow(connectionId,
-                        AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS_HYBRID);
+                .getRootInActiveWindow(connectionId, prefetchingStrategy);
     }
 
     /**
@@ -1160,17 +1193,12 @@
         Point displaySize = new Point();
         display.getRealSize(displaySize);
 
-        int rotation = display.getRotation();
-
         // Take the screenshot
-        Bitmap screenShot = null;
+        ScreenCapture.SynchronousScreenCaptureListener syncScreenCapture =
+                ScreenCapture.createSyncCaptureListener();
         try {
-            // Calling out without a lock held.
-            screenShot = mUiAutomationConnection.takeScreenshot(
-                    new Rect(0, 0, displaySize.x, displaySize.y));
-            if (screenShot == null) {
-                Log.e(LOG_TAG, "mUiAutomationConnection.takeScreenshot() returned null for display "
-                        + mDisplayId);
+            if (!mUiAutomationConnection.takeScreenshot(
+                    new Rect(0, 0, displaySize.x, displaySize.y), syncScreenCapture)) {
                 return null;
             }
         } catch (RemoteException re) {
@@ -1178,10 +1206,23 @@
             return null;
         }
 
-        // Optimization
-        screenShot.setHasAlpha(false);
+        final ScreenshotHardwareBuffer screenshotBuffer =
+                syncScreenCapture.getBuffer();
+        Bitmap screenShot = screenshotBuffer.asBitmap();
+        if (screenShot == null) {
+            Log.e(LOG_TAG, "mUiAutomationConnection.takeScreenshot() returned null for display "
+                    + mDisplayId);
+            return null;
+        }
+        Bitmap swBitmap;
+        try (HardwareBuffer buffer = screenshotBuffer.getHardwareBuffer()) {
+            swBitmap = screenShot.copy(Bitmap.Config.ARGB_8888, false);
+        }
+        screenShot.recycle();
 
-        return screenShot;
+        // Optimization
+        swBitmap.setHasAlpha(false);
+        return swBitmap;
     }
 
     /**
@@ -1218,12 +1259,27 @@
         // Apply a sync transaction to ensure SurfaceFlinger is flushed before capturing a
         // screenshot.
         new SurfaceControl.Transaction().apply(true);
+        ScreenCapture.SynchronousScreenCaptureListener syncScreenCapture =
+                ScreenCapture.createSyncCaptureListener();
         try {
-            return mUiAutomationConnection.takeSurfaceControlScreenshot(sc);
+            if (!mUiAutomationConnection.takeSurfaceControlScreenshot(sc, syncScreenCapture)) {
+                return null;
+            }
+
         } catch (RemoteException re) {
             Log.e(LOG_TAG, "Error while taking screenshot!", re);
             return null;
         }
+        ScreenCapture.ScreenshotHardwareBuffer captureBuffer =
+                syncScreenCapture.getBuffer();
+        Bitmap screenShot = captureBuffer.asBitmap();
+        Bitmap swBitmap;
+        try (HardwareBuffer buffer = captureBuffer.getHardwareBuffer()) {
+            swBitmap = screenShot.copy(Bitmap.Config.ARGB_8888, false);
+        }
+
+        screenShot.recycle();
+        return swBitmap;
     }
 
     /**
diff --git a/core/java/android/app/UiAutomationConnection.java b/core/java/android/app/UiAutomationConnection.java
index 34f0964..6f4abfd 100644
--- a/core/java/android/app/UiAutomationConnection.java
+++ b/core/java/android/app/UiAutomationConnection.java
@@ -25,7 +25,6 @@
 import android.annotation.UserIdInt;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
-import android.graphics.Bitmap;
 import android.graphics.Rect;
 import android.hardware.input.InputManager;
 import android.hardware.input.InputManagerGlobal;
@@ -51,8 +50,6 @@
 import android.view.accessibility.IAccessibilityManager;
 import android.window.ScreenCapture;
 import android.window.ScreenCapture.CaptureArgs;
-import android.window.ScreenCapture.ScreenshotHardwareBuffer;
-import android.window.ScreenCapture.SynchronousScreenCaptureListener;
 
 import libcore.io.IoUtils;
 
@@ -224,56 +221,54 @@
     }
 
     @Override
-    public Bitmap takeScreenshot(Rect crop) {
+    public boolean takeScreenshot(Rect crop, ScreenCapture.ScreenCaptureListener listener) {
         synchronized (mLock) {
             throwIfCalledByNotTrustedUidLocked();
             throwIfShutdownLocked();
             throwIfNotConnectedLocked();
         }
+
         final long identity = Binder.clearCallingIdentity();
         try {
             final CaptureArgs captureArgs = new CaptureArgs.Builder<>()
                     .setSourceCrop(crop)
                     .build();
-            SynchronousScreenCaptureListener syncScreenCapture =
-                    ScreenCapture.createSyncCaptureListener();
-            mWindowManager.captureDisplay(DEFAULT_DISPLAY, captureArgs,
-                    syncScreenCapture);
-            final ScreenshotHardwareBuffer screenshotBuffer =
-                    syncScreenCapture.getBuffer();
-            return screenshotBuffer == null ? null : screenshotBuffer.asBitmap();
+            mWindowManager.captureDisplay(DEFAULT_DISPLAY, captureArgs, listener);
         } catch (RemoteException re) {
             re.rethrowAsRuntimeException();
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
-        return null;
+
+        return true;
     }
 
     @Nullable
     @Override
-    public Bitmap takeSurfaceControlScreenshot(@NonNull SurfaceControl surfaceControl) {
+    public boolean takeSurfaceControlScreenshot(@NonNull SurfaceControl surfaceControl,
+            ScreenCapture.ScreenCaptureListener listener) {
         synchronized (mLock) {
             throwIfCalledByNotTrustedUidLocked();
             throwIfShutdownLocked();
             throwIfNotConnectedLocked();
         }
 
-        ScreenCapture.ScreenshotHardwareBuffer captureBuffer;
         final long identity = Binder.clearCallingIdentity();
         try {
-            captureBuffer = ScreenCapture.captureLayers(
+            ScreenCapture.LayerCaptureArgs args =
                     new ScreenCapture.LayerCaptureArgs.Builder(surfaceControl)
-                            .setChildrenOnly(false)
-                            .build());
+                    .setChildrenOnly(false)
+                    .build();
+            int status = ScreenCapture.captureLayers(args, listener);
+
+            if (status != 0) {
+                return false;
+            }
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
 
-        if (captureBuffer == null) {
-            return null;
-        }
-        return captureBuffer.asBitmap();
+        return true;
     }
 
     @Override
@@ -348,6 +343,9 @@
         }
     }
 
+    /**
+     * Grants permission for the {@link Context#DEVICE_ID_DEFAULT default device}
+     */
     @Override
     public void grantRuntimePermission(String packageName, String permission, int userId)
             throws RemoteException {
@@ -358,12 +356,16 @@
         }
         final long identity = Binder.clearCallingIdentity();
         try {
-            mPermissionManager.grantRuntimePermission(packageName, permission, userId);
+            mPermissionManager.grantRuntimePermission(packageName, permission,
+                    Context.DEVICE_ID_DEFAULT, userId);
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
     }
 
+    /**
+     * Revokes permission for the {@link Context#DEVICE_ID_DEFAULT default device}
+     */
     @Override
     public void revokeRuntimePermission(String packageName, String permission, int userId)
             throws RemoteException {
@@ -374,7 +376,8 @@
         }
         final long identity = Binder.clearCallingIdentity();
         try {
-            mPermissionManager.revokeRuntimePermission(packageName, permission, userId, null);
+            mPermissionManager.revokeRuntimePermission(packageName, permission,
+                    Context.DEVICE_ID_DEFAULT, userId, null);
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
diff --git a/core/java/android/app/UiModeManager.java b/core/java/android/app/UiModeManager.java
index d90257a..0ccb9cd 100644
--- a/core/java/android/app/UiModeManager.java
+++ b/core/java/android/app/UiModeManager.java
@@ -315,7 +315,7 @@
     @SystemApi
     public static final int MODE_NIGHT_CUSTOM_TYPE_BEDTIME = 1;
 
-    private IUiModeManager mService;
+    private static Globals sGlobals;
 
     /**
      * Context required for getting the opPackageName of API caller; maybe be {@code null} if the
@@ -341,6 +341,60 @@
             mOnProjectionStateChangedListenerResourceManager =
             new OnProjectionStateChangedListenerResourceManager();
 
+    private static class Globals extends IUiModeManagerCallback.Stub {
+
+        private final IUiModeManager mService;
+        private final Object mGlobalsLock = new Object();
+
+        private float mContrast = ContrastUtils.CONTRAST_DEFAULT_VALUE;
+
+        /**
+         * Map that stores user provided {@link ContrastChangeListener} callbacks,
+         * and the executors on which these callbacks should be called.
+         */
+        private final ArrayMap<ContrastChangeListener, Executor>
+                mContrastChangeListeners = new ArrayMap<>();
+
+        Globals(IUiModeManager service) {
+            mService = service;
+            try {
+                mService.addCallback(this);
+                mContrast = mService.getContrast();
+            } catch (RemoteException e) {
+                Log.e(TAG, "Setup failed: UiModeManagerService is dead", e);
+            }
+        }
+
+        private float getContrast() {
+            synchronized (mGlobalsLock) {
+                return mContrast;
+            }
+        }
+
+        private void addContrastChangeListener(ContrastChangeListener listener, Executor executor) {
+            synchronized (mGlobalsLock) {
+                mContrastChangeListeners.put(listener, executor);
+            }
+        }
+
+        private void removeContrastChangeListener(ContrastChangeListener listener) {
+            synchronized (mGlobalsLock) {
+                mContrastChangeListeners.remove(listener);
+            }
+        }
+
+        @Override
+        public void notifyContrastChanged(float contrast) {
+            synchronized (mGlobalsLock) {
+                // if value changed in the settings, update the cached value and notify listeners
+                if (Math.abs(mContrast - contrast) < 1e-10) return;
+                mContrast = contrast;
+                mContrastChangeListeners.forEach((listener, executor) -> executor.execute(
+                        () -> listener.onContrastChanged(contrast)));
+            }
+        }
+    }
+
     /**
      * Define constants and conversions between {@link ContrastLevel}s and contrast values.
      * <p>
@@ -407,43 +461,18 @@
         }
     }
 
-    /**
-     * Map that stores user provided {@link ContrastChangeListener} callbacks,
-     * and the executors on which these callbacks should be called.
-     */
-    private final ArrayMap<ContrastChangeListener, Executor>
-            mContrastChangeListeners = new ArrayMap<>();
-    private float mContrast;
-
-    private final IUiModeManagerCallback.Stub mCallback = new IUiModeManagerCallback.Stub() {
-        @Override
-        public void notifyContrastChanged(float contrast) {
-            final ArrayMap<ContrastChangeListener, Executor> listeners;
-            synchronized (mLock) {
-                // if value changed in the settings, update the cached value and notify listeners
-                if (Math.abs(mContrast - contrast) < 1e-10) return;
-                mContrast = contrast;
-                listeners = new ArrayMap<>(mContrastChangeListeners);
-            }
-            listeners.forEach((listener, executor) -> executor.execute(
-                    () -> listener.onContrastChanged(mContrast)));
-        }
-    };
-
     @UnsupportedAppUsage
     /*package*/ UiModeManager() throws ServiceNotFoundException {
         this(null /* context */);
     }
 
     /*package*/ UiModeManager(Context context) throws ServiceNotFoundException {
-        mService = IUiModeManager.Stub.asInterface(
+        IUiModeManager service = IUiModeManager.Stub.asInterface(
                 ServiceManager.getServiceOrThrow(Context.UI_MODE_SERVICE));
         mContext = context;
-        try {
-            mService.addCallback(mCallback);
-            mContrast = mService.getContrast();
-        } catch (RemoteException e) {
-            Log.e(TAG, "Setup failed: UiModeManagerService is dead", e);
+        if (service == null) return;
+        synchronized (mLock) {
+            if (sGlobals == null) sGlobals = new Globals(service);
         }
     }
 
@@ -533,9 +562,9 @@
     @SystemApi
     @RequiresPermission(android.Manifest.permission.ENTER_CAR_MODE_PRIORITIZED)
     public void enableCarMode(@IntRange(from = 0) int priority, @EnableCarMode int flags) {
-        if (mService != null) {
+        if (sGlobals != null) {
             try {
-                mService.enableCarMode(flags, priority,
+                sGlobals.mService.enableCarMode(flags, priority,
                         mContext == null ? null : mContext.getOpPackageName());
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
@@ -585,9 +614,9 @@
      * @param flags One of the disable car mode flags.
      */
     public void disableCarMode(@DisableCarMode int flags) {
-        if (mService != null) {
+        if (sGlobals != null) {
             try {
-                mService.disableCarModeByCallingPackage(flags,
+                sGlobals.mService.disableCarModeByCallingPackage(flags,
                         mContext == null ? null : mContext.getOpPackageName());
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
@@ -606,9 +635,9 @@
      * {@link Configuration#UI_MODE_TYPE_VR_HEADSET Configuration.UI_MODE_TYPE_VR_HEADSET}.
      */
     public int getCurrentModeType() {
-        if (mService != null) {
+        if (sGlobals != null) {
             try {
-                return mService.getCurrentModeType();
+                return sGlobals.mService.getCurrentModeType();
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
@@ -653,9 +682,9 @@
      * @see #setApplicationNightMode(int)
      */
     public void setNightMode(@NightMode int mode) {
-        if (mService != null) {
+        if (sGlobals != null) {
             try {
-                mService.setNightMode(mode);
+                sGlobals.mService.setNightMode(mode);
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
@@ -674,9 +703,9 @@
     @SystemApi
     @RequiresPermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE)
     public void setNightModeCustomType(@NightModeCustomType int nightModeCustomType) {
-        if (mService != null) {
+        if (sGlobals != null) {
             try {
-                mService.setNightModeCustomType(nightModeCustomType);
+                sGlobals.mService.setNightModeCustomType(nightModeCustomType);
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
@@ -693,9 +722,9 @@
     @SystemApi
     @RequiresPermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE)
     public @NightModeCustomReturnType int getNightModeCustomType() {
-        if (mService != null) {
+        if (sGlobals != null) {
             try {
-                return mService.getNightModeCustomType();
+                return sGlobals.mService.getNightModeCustomType();
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
@@ -732,9 +761,9 @@
      * @see #setNightMode(int)
      */
     public void setApplicationNightMode(@NightMode int mode) {
-        if (mService != null) {
+        if (sGlobals != null) {
             try {
-                mService.setApplicationNightMode(mode);
+                sGlobals.mService.setApplicationNightMode(mode);
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
@@ -757,9 +786,9 @@
      * @see #setNightMode(int)
      */
     public @NightMode int getNightMode() {
-        if (mService != null) {
+        if (sGlobals != null) {
             try {
-                return mService.getNightMode();
+                return sGlobals.mService.getNightMode();
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
@@ -774,9 +803,9 @@
      */
     @TestApi
     public boolean isUiModeLocked() {
-        if (mService != null) {
+        if (sGlobals != null) {
             try {
-                return mService.isUiModeLocked();
+                return sGlobals.mService.isUiModeLocked();
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
@@ -796,9 +825,9 @@
      */
     @TestApi
     public boolean isNightModeLocked() {
-        if (mService != null) {
+        if (sGlobals != null) {
             try {
-                return mService.isNightModeLocked();
+                return sGlobals.mService.isNightModeLocked();
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
@@ -820,9 +849,10 @@
     @RequiresPermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE)
     public boolean setNightModeActivatedForCustomMode(@NightModeCustomType int nightModeCustomType,
             boolean active) {
-        if (mService != null) {
+        if (sGlobals != null) {
             try {
-                return mService.setNightModeActivatedForCustomMode(nightModeCustomType, active);
+                return sGlobals.mService.setNightModeActivatedForCustomMode(
+                        nightModeCustomType, active);
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
@@ -838,9 +868,9 @@
      */
     @RequiresPermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE)
     public boolean setNightModeActivated(boolean active) {
-        if (mService != null) {
+        if (sGlobals != null) {
             try {
-                return mService.setNightModeActivated(active);
+                return sGlobals.mService.setNightModeActivated(active);
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
@@ -856,9 +886,9 @@
      */
     @NonNull
     public LocalTime getCustomNightModeStart() {
-        if (mService != null) {
+        if (sGlobals != null) {
             try {
-                return LocalTime.ofNanoOfDay(mService.getCustomNightModeStart() * 1000);
+                return LocalTime.ofNanoOfDay(sGlobals.mService.getCustomNightModeStart() * 1000);
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
@@ -874,9 +904,9 @@
      * @param time The time of the day Dark theme should activate
      */
     public void setCustomNightModeStart(@NonNull LocalTime time) {
-        if (mService != null) {
+        if (sGlobals != null) {
             try {
-                mService.setCustomNightModeStart(time.toNanoOfDay() / 1000);
+                sGlobals.mService.setCustomNightModeStart(time.toNanoOfDay() / 1000);
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
@@ -891,9 +921,9 @@
      */
     @NonNull
     public LocalTime getCustomNightModeEnd() {
-        if (mService != null) {
+        if (sGlobals != null) {
             try {
-                return LocalTime.ofNanoOfDay(mService.getCustomNightModeEnd() * 1000);
+                return LocalTime.ofNanoOfDay(sGlobals.mService.getCustomNightModeEnd() * 1000);
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
@@ -909,9 +939,9 @@
      * @param time The time of the day Dark theme should deactivate
      */
     public void setCustomNightModeEnd(@NonNull LocalTime time) {
-        if (mService != null) {
+        if (sGlobals != null) {
             try {
-                mService.setCustomNightModeEnd(time.toNanoOfDay() / 1000);
+                sGlobals.mService.setCustomNightModeEnd(time.toNanoOfDay() / 1000);
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
@@ -976,9 +1006,9 @@
     @RequiresPermission(value = android.Manifest.permission.TOGGLE_AUTOMOTIVE_PROJECTION,
             conditional = true)
     public boolean requestProjection(@ProjectionType int projectionType) {
-        if (mService != null) {
+        if (sGlobals != null) {
             try {
-                return mService.requestProjection(new Binder(), projectionType,
+                return sGlobals.mService.requestProjection(new Binder(), projectionType,
                         mContext.getOpPackageName());
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
@@ -1005,9 +1035,10 @@
     @RequiresPermission(value = android.Manifest.permission.TOGGLE_AUTOMOTIVE_PROJECTION,
             conditional = true)
     public boolean releaseProjection(@ProjectionType int projectionType) {
-        if (mService != null) {
+        if (sGlobals != null) {
             try {
-                return mService.releaseProjection(projectionType, mContext.getOpPackageName());
+                return sGlobals.mService.releaseProjection(
+                        projectionType, mContext.getOpPackageName());
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
@@ -1028,9 +1059,9 @@
     @RequiresPermission(android.Manifest.permission.READ_PROJECTION_STATE)
     @NonNull
     public Set<String> getProjectingPackages(@ProjectionType int projectionType) {
-        if (mService != null) {
+        if (sGlobals != null) {
             try {
-                return new ArraySet<>(mService.getProjectingPackages(projectionType));
+                return new ArraySet<>(sGlobals.mService.getProjectingPackages(projectionType));
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
@@ -1046,9 +1077,9 @@
     @SystemApi
     @RequiresPermission(android.Manifest.permission.READ_PROJECTION_STATE)
     public @ProjectionType int getActiveProjectionTypes() {
-        if (mService != null) {
+        if (sGlobals != null) {
             try {
-                return mService.getActiveProjectionTypes();
+                return sGlobals.mService.getActiveProjectionTypes();
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
@@ -1076,11 +1107,12 @@
                 Slog.i(TAG, "Attempted to add listener that was already added.");
                 return;
             }
-            if (mService != null) {
+            if (sGlobals != null) {
                 InnerListener innerListener = new InnerListener(executor, listener,
                         mOnProjectionStateChangedListenerResourceManager);
                 try {
-                    mService.addOnProjectionStateChangedListener(innerListener, projectionType);
+                    sGlobals.mService.addOnProjectionStateChangedListener(
+                            innerListener, projectionType);
                     mProjectionStateListenerMap.put(listener, innerListener);
                 } catch (RemoteException e) {
                     mOnProjectionStateChangedListenerResourceManager.remove(innerListener);
@@ -1107,9 +1139,9 @@
                 Slog.i(TAG, "Attempted to remove listener that was not added.");
                 return;
             }
-            if (mService != null) {
+            if (sGlobals != null) {
                 try {
-                    mService.removeOnProjectionStateChangedListener(innerListener);
+                    sGlobals.mService.removeOnProjectionStateChangedListener(innerListener);
                 } catch (RemoteException e) {
                     throw e.rethrowFromSystemServer();
                 }
@@ -1197,15 +1229,10 @@
      *     <li>       -1 corresponds to the minimum contrast </li>
      *     <li> &nbsp; 1 corresponds to the maximum contrast </li>
      * </ul>
-     *
-     *
-     *
      */
     @FloatRange(from = -1.0f, to = 1.0f)
     public float getContrast() {
-        synchronized (mLock) {
-            return mContrast;
-        }
+        return sGlobals.getContrast();
     }
 
     /**
@@ -1219,9 +1246,7 @@
             @NonNull ContrastChangeListener listener) {
         Objects.requireNonNull(executor);
         Objects.requireNonNull(listener);
-        synchronized (mLock) {
-            mContrastChangeListeners.put(listener, executor);
-        }
+        sGlobals.addContrastChangeListener(listener, executor);
     }
 
     /**
@@ -1232,8 +1257,6 @@
      */
     public void removeContrastChangeListener(@NonNull ContrastChangeListener listener) {
         Objects.requireNonNull(listener);
-        synchronized (mLock) {
-            mContrastChangeListeners.remove(listener);
-        }
+        sGlobals.removeContrastChangeListener(listener);
     }
 }
diff --git a/core/java/android/app/WallpaperColors.java b/core/java/android/app/WallpaperColors.java
index b710644..5f5a7df 100644
--- a/core/java/android/app/WallpaperColors.java
+++ b/core/java/android/app/WallpaperColors.java
@@ -28,6 +28,7 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.SystemProperties;
+import android.os.Trace;
 import android.util.Log;
 import android.util.MathUtils;
 import android.util.Size;
@@ -144,6 +145,7 @@
             throw new IllegalArgumentException("Drawable cannot be null");
         }
 
+        Trace.beginSection("WallpaperColors#fromDrawable");
         Rect initialBounds = drawable.copyBounds();
         int width = drawable.getIntrinsicWidth();
         int height = drawable.getIntrinsicHeight();
@@ -165,6 +167,7 @@
         bitmap.recycle();
 
         drawable.setBounds(initialBounds);
+        Trace.endSection();
         return colors;
     }
 
@@ -195,7 +198,7 @@
     public static WallpaperColors fromBitmap(@NonNull Bitmap bitmap,
             @FloatRange (from = 0f, to = 1f) float dimAmount) {
         Objects.requireNonNull(bitmap, "Bitmap can't be null");
-
+        Trace.beginSection("WallpaperColors#fromBitmap");
         final int bitmapArea = bitmap.getWidth() * bitmap.getHeight();
         boolean shouldRecycle = false;
         if (bitmapArea > MAX_WALLPAPER_EXTRACTION_AREA) {
@@ -247,6 +250,7 @@
             bitmap.recycle();
         }
 
+        Trace.endSection();
         return new WallpaperColors(populationByColor, HINT_FROM_BITMAP | hints);
     }
 
@@ -462,7 +466,7 @@
      * Gets the most visually representative color of the wallpaper.
      * "Visually representative" means easily noticeable in the image,
      * probably happening at high frequency.
-     *
+     *fromBitmap
      * @return A color.
      */
     public @NonNull Color getPrimaryColor() {
@@ -545,6 +549,7 @@
             return 0;
         }
 
+        Trace.beginSection("WallpaperColors#calculateDarkHints");
         dimAmount = MathUtils.saturate(dimAmount);
         int[] pixels = new int[source.getWidth() * source.getHeight()];
         double totalLuminance = 0;
@@ -607,6 +612,7 @@
                     " maxD: " + maxDarkPixels + " numPixels: " + pixels.length);
         }
 
+        Trace.endSection();
         return hints;
     }
 
diff --git a/core/java/android/app/WallpaperInfo.java b/core/java/android/app/WallpaperInfo.java
index b29e73a..e3a05af 100644
--- a/core/java/android/app/WallpaperInfo.java
+++ b/core/java/android/app/WallpaperInfo.java
@@ -96,31 +96,30 @@
             throws XmlPullParserException, IOException {
         mService = service;
         ServiceInfo si = service.serviceInfo;
-        
+
         final PackageManager pm = context.getPackageManager();
-        XmlResourceParser parser = null;
-        try {
-            parser = si.loadXmlMetaData(pm, WallpaperService.SERVICE_META_DATA);
+        try (XmlResourceParser parser = si.loadXmlMetaData(pm,
+                WallpaperService.SERVICE_META_DATA)) {
             if (parser == null) {
                 throw new XmlPullParserException("No "
                         + WallpaperService.SERVICE_META_DATA + " meta-data");
             }
-        
+
             Resources res = pm.getResourcesForApplication(si.applicationInfo);
-            
+
             AttributeSet attrs = Xml.asAttributeSet(parser);
-            
+
             int type;
-            while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
+            while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
                     && type != XmlPullParser.START_TAG) {
             }
-            
+
             String nodeName = parser.getName();
             if (!"wallpaper".equals(nodeName)) {
                 throw new XmlPullParserException(
                         "Meta-data does not start with wallpaper tag");
             }
-            
+
             TypedArray sa = res.obtainAttributes(attrs,
                     com.android.internal.R.styleable.Wallpaper);
             mSettingsActivityName = sa.getString(
@@ -143,9 +142,13 @@
             mShowMetadataInPreview = sa.getBoolean(
                     com.android.internal.R.styleable.Wallpaper_showMetadataInPreview,
                     false);
+
+            // Watch wallpapers support ambient mode by default.
+            final boolean defSupportsAmbientMode =
+                    pm.hasSystemFeature(PackageManager.FEATURE_WATCH);
             mSupportsAmbientMode = sa.getBoolean(
                     com.android.internal.R.styleable.Wallpaper_supportsAmbientMode,
-                    false);
+                    defSupportsAmbientMode);
             mShouldUseDefaultUnfoldTransition = sa.getBoolean(
                     com.android.internal.R.styleable
                             .Wallpaper_shouldUseDefaultUnfoldTransition, true);
@@ -159,8 +162,6 @@
         } catch (NameNotFoundException e) {
             throw new XmlPullParserException(
                     "Unable to create context for: " + si.packageName);
-        } finally {
-            if (parser != null) parser.close();
         }
     }
 
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index ee888ed..723c564 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -853,7 +853,7 @@
     private static boolean isLockscreenLiveWallpaperEnabledHelper() {
         if (sGlobals == null) {
             sIsLockscreenLiveWallpaperEnabled = SystemProperties.getBoolean(
-                    "persist.wm.debug.lockscreen_live_wallpaper", false);
+                    "persist.wm.debug.lockscreen_live_wallpaper", true);
         }
         if (sIsLockscreenLiveWallpaperEnabled == null) {
             try {
@@ -1969,7 +1969,6 @@
                     mContext.getUserId());
             if (fd != null) {
                 FileOutputStream fos = null;
-                boolean ok = false;
                 try {
                     fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd);
                     copyStreamToWallpaperFile(resources.openRawResource(resid), fos);
@@ -2430,19 +2429,38 @@
     }
 
     /**
-     * Reset all wallpaper to the factory default.
+     * Reset all wallpaper to the factory default. As opposed to {@link #clear()}, if the device
+     * is configured to have a live wallpaper by default, apply it.
      *
      * <p>This method requires the caller to hold the permission
      * {@link android.Manifest.permission#SET_WALLPAPER}.
      */
     @RequiresPermission(android.Manifest.permission.SET_WALLPAPER)
     public void clearWallpaper() {
+        if (isLockscreenLiveWallpaperEnabled()) {
+            clearWallpaper(FLAG_LOCK | FLAG_SYSTEM, mContext.getUserId());
+            return;
+        }
         clearWallpaper(FLAG_LOCK, mContext.getUserId());
         clearWallpaper(FLAG_SYSTEM, mContext.getUserId());
     }
 
     /**
-     * Clear the wallpaper for a specific user.  The caller must hold the
+     * Clear the wallpaper for a specific user.
+     * <ul>
+     *     <li> When called with {@code which=}{@link #FLAG_LOCK}, clear the lockscreen wallpaper.
+     *     The home screen wallpaper will become visible on the lock screen. </li>
+     *
+     *     <li> When called with {@code which=}{@link #FLAG_SYSTEM}, revert the home screen
+     *     wallpaper to default. The lockscreen wallpaper will be unchanged: if the previous
+     *     wallpaper was shared between home and lock screen, it will become lock screen only. </li>
+     *
+     *     <li> When called with {@code which=}({@link #FLAG_LOCK} | {@link #FLAG_SYSTEM}), put the
+     *     default wallpaper on both home and lock screen, removing any user defined wallpaper.</li>
+     * </ul>
+     * </p>
+     *
+     * The caller must hold the
      * INTERACT_ACROSS_USERS_FULL permission to clear another user's
      * wallpaper, and must hold the SET_WALLPAPER permission in all
      * circumstances.
@@ -2747,8 +2765,9 @@
 
     /**
      * Remove any currently set system wallpaper, reverting to the system's built-in
-     * wallpaper. On success, the intent {@link Intent#ACTION_WALLPAPER_CHANGED}
-     * is broadcast.
+     * wallpaper. As opposed to {@link #clearWallpaper()}, this method always set a static wallpaper
+     * with the default image, even if the device is configured to have a live wallpaper by default.
+     * On success, the intent {@link Intent#ACTION_WALLPAPER_CHANGED} is broadcast.
      *
      * <p>This method requires the caller to hold the permission
      * {@link android.Manifest.permission#SET_WALLPAPER}.
@@ -2763,9 +2782,14 @@
 
     /**
      * Remove one or more currently set wallpapers, reverting to the system default
-     * display for each one.  If {@link #FLAG_SYSTEM} is set in the {@code which}
-     * parameter, the intent {@link Intent#ACTION_WALLPAPER_CHANGED} will be broadcast
-     * upon success.
+     * display for each one. On success, the intent {@link Intent#ACTION_WALLPAPER_CHANGED}
+     * is broadcast.
+     * <ul>
+     *     <li> If {@link #FLAG_SYSTEM} is set in the {@code which} parameter, put the default
+     *     wallpaper on both home and lock screen, removing any user defined wallpaper. </li>
+     *     <li> When called with {@code which=}{@link #FLAG_LOCK}, clear the lockscreen wallpaper.
+     *     The home screen wallpaper will become visible on the lock screen. </li>
+     * </ul>
      *
      * @param which A bitwise combination of {@link #FLAG_SYSTEM} or
      *   {@link #FLAG_LOCK}
@@ -2775,6 +2799,7 @@
     public void clear(@SetWallpaperFlags int which) throws IOException {
         if ((which & FLAG_SYSTEM) != 0) {
             clear();
+            if (isLockscreenLiveWallpaperEnabled()) return;
         }
         if ((which & FLAG_LOCK) != 0) {
             clearWallpaper(FLAG_LOCK, mContext.getUserId());
@@ -2887,22 +2912,63 @@
             }
         }
 
-        // Check if the package exists
-        if (cn != null) {
-            try {
-                final PackageManager packageManager = context.getPackageManager();
-                packageManager.getPackageInfo(cn.getPackageName(),
-                        PackageManager.MATCH_DIRECT_BOOT_AWARE
-                                | PackageManager.MATCH_DIRECT_BOOT_UNAWARE);
-            } catch (PackageManager.NameNotFoundException e) {
-                cn = null;
-            }
+        if (!isComponentExist(context, cn)) {
+            cn = null;
         }
 
         return cn;
     }
 
     /**
+     * Return {@link ComponentName} of the CMF default wallpaper, or
+     * {@link #getDefaultWallpaperComponent(Context)} if none is defined.
+     *
+     * @hide
+     */
+    public static ComponentName getCmfDefaultWallpaperComponent(Context context) {
+        ComponentName cn = null;
+        String[] cmfWallpaperMap = context.getResources().getStringArray(
+                com.android.internal.R.array.default_wallpaper_component_per_device_color);
+        if (cmfWallpaperMap == null || cmfWallpaperMap.length == 0) {
+            Log.d(TAG, "No CMF wallpaper config");
+            return getDefaultWallpaperComponent(context);
+        }
+
+        for (String entry : cmfWallpaperMap) {
+            String[] cmfWallpaper;
+            if (!TextUtils.isEmpty(entry)) {
+                cmfWallpaper = entry.split(",");
+                if (cmfWallpaper != null && cmfWallpaper.length == 2 && VALUE_CMF_COLOR.equals(
+                        cmfWallpaper[0]) && !TextUtils.isEmpty(cmfWallpaper[1])) {
+                    cn = ComponentName.unflattenFromString(cmfWallpaper[1]);
+                    break;
+                }
+            }
+        }
+
+        if (!isComponentExist(context, cn)) {
+            cn = null;
+        }
+
+        return cn;
+    }
+
+    private static boolean isComponentExist(Context context, ComponentName cn) {
+        if (cn == null) {
+            return false;
+        }
+        try {
+            final PackageManager packageManager = context.getPackageManager();
+            packageManager.getPackageInfo(cn.getPackageName(),
+                    PackageManager.MATCH_DIRECT_BOOT_AWARE
+                            | PackageManager.MATCH_DIRECT_BOOT_UNAWARE);
+        } catch (PackageManager.NameNotFoundException e) {
+            return false;
+        }
+        return true;
+    }
+
+    /**
      * Register a callback for lock wallpaper observation. Only the OS may use this.
      *
      * @return true on success; false on error.
diff --git a/core/java/android/app/admin/DevicePolicyIdentifiers.java b/core/java/android/app/admin/DevicePolicyIdentifiers.java
index aeac59b..ad0af72 100644
--- a/core/java/android/app/admin/DevicePolicyIdentifiers.java
+++ b/core/java/android/app/admin/DevicePolicyIdentifiers.java
@@ -160,6 +160,14 @@
     public static final String CROSS_PROFILE_WIDGET_PROVIDER_POLICY = "crossProfileWidgetProvider";
 
     /**
+     * String identifier for {@link DevicePolicyManager#setUsbDataSignalingEnabled}.
+     *
+     * @hide
+     */
+    @TestApi
+    public static final String USB_DATA_SIGNALING_POLICY = "usbDataSignaling";
+
+    /**
      * @hide
      */
     public static final String USER_RESTRICTION_PREFIX = "userRestriction_";
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index da5e40a..33b8b03 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -12993,7 +12993,7 @@
     }
 
     /**
-     * Called by device owners or profile owners of an organization-owned managed profile to to set
+     * Called by device owners or profile owners of an organization-owned managed profile to set
      * a local system update policy. When a new policy is set,
      * {@link #ACTION_SYSTEM_UPDATE_POLICY_CHANGED} is broadcast.
      * <p>
@@ -14787,12 +14787,12 @@
     }
 
     /**
-     * Called by the system to find out whether the current user's IME was set by the device/profile
-     * owner or the user.
+     * Returns true if the current user's IME was set by an admin.
      *
-     * @return {@code true} if the user's IME was set by the device or profile owner, {@code false}
-     *         otherwise.
-     * @throws SecurityException if the caller is not the device owner/profile owner.
+     * <p>Requires the caller to be the system server, a device owner or profile owner, or a holder
+     * of the QUERY_ADMIN_POLICY permission.
+     *
+     * @throws SecurityException if the caller is not authorized
      *
      * @hide
      */
@@ -16585,10 +16585,28 @@
      * {@link #canUsbDataSignalingBeDisabled()} to check whether enabling or disabling USB data
      * signaling is supported on the device.
      *
+     * Starting from {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}, after the USB data signaling
+     * policy has been set, {@link PolicyUpdateReceiver#onPolicySetResult(Context, String,
+     * Bundle, TargetUser, PolicyUpdateResult)} will notify the admin on whether the policy was
+     * successfully set or not. This callback will contain:
+     * <ul>
+     * li> The policy identifier {@link DevicePolicyIdentifiers#USB_DATA_SIGNALING_POLICY}
+     * <li> The {@link TargetUser} that this policy relates to
+     * <li> The {@link PolicyUpdateResult}, which will be
+     * {@link PolicyUpdateResult#RESULT_POLICY_SET} if the policy was successfully set or the
+     * reason the policy failed to be set
+     * e.g. {@link PolicyUpdateResult#RESULT_FAILURE_CONFLICTING_ADMIN_POLICY})
+     * </ul>
+     * If there has been a change to the policy,
+     * {@link PolicyUpdateReceiver#onPolicyChanged(Context, String, Bundle, TargetUser,
+     * PolicyUpdateResult)} will notify the admin of this change. This callback will contain the
+     * same parameters as PolicyUpdateReceiver#onPolicySetResult and the {@link PolicyUpdateResult}
+     * will contain the reason why the policy changed.
+     *
      * @param enabled whether USB data signaling should be enabled or not.
      * @throws SecurityException if the caller is not permitted to set this policy
      * @throws IllegalStateException if disabling USB data signaling is not supported or
-     *         if USB data signaling fails to be enabled/disabled.
+     * if USB data signaling fails to be enabled/disabled.
      */
     @RequiresPermission(value = MANAGE_DEVICE_POLICY_USB_DATA_SIGNALLING, conditional = true)
     public void setUsbDataSignalingEnabled(boolean enabled) {
@@ -16624,25 +16642,6 @@
     }
 
     /**
-     * Called by the system to check whether USB data signaling is currently enabled for this user.
-     *
-     * @param userId which user to check for.
-     * @return {@code true} if USB data signaling is enabled, {@code false} otherwise.
-     * @hide
-     */
-    public boolean isUsbDataSignalingEnabledForUser(@UserIdInt int userId) {
-        throwIfParentInstance("isUsbDataSignalingEnabledForUser");
-        if (mService != null) {
-            try {
-                return mService.isUsbDataSignalingEnabledForUser(userId);
-            } catch (RemoteException e) {
-                throw e.rethrowFromSystemServer();
-            }
-        }
-        return true;
-    }
-
-    /**
      * Returns whether enabling or disabling USB data signaling is supported on the device.
      *
      * @return {@code true} if the device supports enabling and disabling USB data signaling.
diff --git a/core/java/android/app/admin/DevicePolicyManagerInternal.java b/core/java/android/app/admin/DevicePolicyManagerInternal.java
index 0e78275..8dd50f0 100644
--- a/core/java/android/app/admin/DevicePolicyManagerInternal.java
+++ b/core/java/android/app/admin/DevicePolicyManagerInternal.java
@@ -250,6 +250,16 @@
     public abstract ComponentName getProfileOwnerAsUser(@UserIdInt int userId);
 
     /**
+     * Returns the device owner component for the device, or {@code null} if there is not one.
+     *
+     * @deprecated added temporarily to support Android Role permission granting.
+     * Please contact Android Enterprise Device Policy team before calling this function.
+     */
+    @Deprecated
+    @Nullable
+    public abstract ComponentName getDeviceOwnerComponent(boolean callingUserOnly);
+
+    /**
      * Returns the user id of the device owner, or {@link UserHandle#USER_NULL} if there is not one.
      */
     @UserIdInt
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 003e804..95ec89e 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -565,7 +565,6 @@
 
     void setUsbDataSignalingEnabled(String callerPackage, boolean enabled);
     boolean isUsbDataSignalingEnabled(String callerPackage);
-    boolean isUsbDataSignalingEnabledForUser(int userId);
     boolean canUsbDataSignalingBeDisabled();
 
     void setMinimumRequiredWifiSecurityLevel(String callerPackageName, int level);
diff --git a/core/java/android/app/admin/PasswordMetrics.java b/core/java/android/app/admin/PasswordMetrics.java
index ab48791..dd31175 100644
--- a/core/java/android/app/admin/PasswordMetrics.java
+++ b/core/java/android/app/admin/PasswordMetrics.java
@@ -30,6 +30,7 @@
 import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN;
 import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PIN;
 import static com.android.internal.widget.LockPatternUtils.MIN_LOCK_PASSWORD_SIZE;
+import static com.android.internal.widget.LockPatternUtils.MIN_LOCK_PATTERN_SIZE;
 import static com.android.internal.widget.PasswordValidationError.CONTAINS_INVALID_CHARACTERS;
 import static com.android.internal.widget.PasswordValidationError.CONTAINS_SEQUENCE;
 import static com.android.internal.widget.PasswordValidationError.NOT_ENOUGH_DIGITS;
@@ -134,17 +135,6 @@
         }
     }
 
-    private static boolean hasInvalidCharacters(byte[] password) {
-        // Allow non-control Latin-1 characters only.
-        for (byte b : password) {
-            char c = (char) b;
-            if (c < 32 || c > 127) {
-                return true;
-            }
-        }
-        return false;
-    }
-
     @Override
     public int describeContents() {
         return 0;
@@ -189,19 +179,15 @@
     };
 
     /**
-     * Returns the {@code PasswordMetrics} for a given credential.
-     *
-     * If the credential is a pin or a password, equivalent to
-     * {@link #computeForPasswordOrPin(byte[], boolean)}. {@code credential} cannot be null
-     * when {@code type} is
-     * {@link com.android.internal.widget.LockPatternUtils#CREDENTIAL_TYPE_PASSWORD}.
+     * Returns the {@code PasswordMetrics} for the given credential.
      */
     public static PasswordMetrics computeForCredential(LockscreenCredential credential) {
         if (credential.isPassword() || credential.isPin()) {
-            return PasswordMetrics.computeForPasswordOrPin(credential.getCredential(),
-                    credential.isPin());
+            return computeForPasswordOrPin(credential.getCredential(), credential.isPin());
         } else if (credential.isPattern())  {
-            return new PasswordMetrics(CREDENTIAL_TYPE_PATTERN);
+            PasswordMetrics metrics = new PasswordMetrics(CREDENTIAL_TYPE_PATTERN);
+            metrics.length = credential.size();
+            return metrics;
         } else if (credential.isNone()) {
             return new PasswordMetrics(CREDENTIAL_TYPE_NONE);
         } else {
@@ -210,10 +196,10 @@
     }
 
     /**
-     * Returns the {@code PasswordMetrics} for a given password or pin
+     * Returns the {@code PasswordMetrics} for the given password or pin.
      */
-    public static PasswordMetrics computeForPasswordOrPin(byte[] password, boolean isPin) {
-        // Analyse the characters used
+    private static PasswordMetrics computeForPasswordOrPin(byte[] credential, boolean isPin) {
+        // Analyze the characters used.
         int letters = 0;
         int upperCase = 0;
         int lowerCase = 0;
@@ -221,8 +207,8 @@
         int symbols = 0;
         int nonLetter = 0;
         int nonNumeric = 0;
-        final int length = password.length;
-        for (byte b : password) {
+        final int length = credential.length;
+        for (byte b : credential) {
             switch (categoryChar((char) b)) {
                 case CHAR_LOWER_CASE:
                     letters++;
@@ -247,7 +233,7 @@
         }
 
         final int credType = isPin ? CREDENTIAL_TYPE_PIN : CREDENTIAL_TYPE_PASSWORD;
-        final int seqLength = maxLengthSequence(password);
+        final int seqLength = maxLengthSequence(credential);
         return new PasswordMetrics(credType, length, letters, upperCase, lowerCase,
                 numeric, symbols, nonLetter, nonNumeric, seqLength);
     }
@@ -513,26 +499,24 @@
     }
 
     /**
-     * Validates password against minimum metrics and complexity.
+     * Validates a proposed lockscreen credential against minimum metrics and complexity.
      *
-     * @param adminMetrics - minimum metrics to satisfy admin requirements.
-     * @param minComplexity - minimum complexity imposed by the requester.
-     * @param isPin - whether it is PIN that should be only digits
-     * @param password - password to validate.
-     * @return a list of password validation errors. An empty list means the password is OK.
+     * @param adminMetrics minimum metrics to satisfy admin requirements
+     * @param minComplexity minimum complexity imposed by the requester
+     * @param credential the proposed lockscreen credential
+     *
+     * @return a list of validation errors. An empty list means the credential is OK.
      *
      * TODO: move to PasswordPolicy
      */
-    public static List<PasswordValidationError> validatePassword(
-            PasswordMetrics adminMetrics, int minComplexity, boolean isPin, byte[] password) {
-
-        if (hasInvalidCharacters(password)) {
+    public static List<PasswordValidationError> validateCredential(
+            PasswordMetrics adminMetrics, int minComplexity, LockscreenCredential credential) {
+        if (credential.hasInvalidChars()) {
             return Collections.singletonList(
                     new PasswordValidationError(CONTAINS_INVALID_CHARACTERS, 0));
         }
-
-        final PasswordMetrics enteredMetrics = computeForPasswordOrPin(password, isPin);
-        return validatePasswordMetrics(adminMetrics, minComplexity, enteredMetrics);
+        PasswordMetrics actualMetrics = computeForCredential(credential);
+        return validatePasswordMetrics(adminMetrics, minComplexity, actualMetrics);
     }
 
     /**
@@ -555,9 +539,18 @@
                 || !bucket.allowsCredType(actualMetrics.credType)) {
             return Collections.singletonList(new PasswordValidationError(WEAK_CREDENTIAL_TYPE, 0));
         }
-        if (actualMetrics.credType != CREDENTIAL_TYPE_PASSWORD
-                && actualMetrics.credType != CREDENTIAL_TYPE_PIN) {
-            return Collections.emptyList(); // Nothing to check for pattern or none.
+        if (actualMetrics.credType == CREDENTIAL_TYPE_PATTERN) {
+            // For pattern, only need to check the length against the hardcoded minimum.  If the
+            // pattern length is unavailable (e.g., PasswordMetrics that was stored on-disk before
+            // the pattern length started being included in it), assume it is okay.
+            if (actualMetrics.length != 0 && actualMetrics.length < MIN_LOCK_PATTERN_SIZE) {
+                return Collections.singletonList(new PasswordValidationError(TOO_SHORT,
+                            MIN_LOCK_PATTERN_SIZE));
+            }
+            return Collections.emptyList();
+        }
+        if (actualMetrics.credType == CREDENTIAL_TYPE_NONE) {
+            return Collections.emptyList(); // Nothing to check for none.
         }
 
         if (actualMetrics.credType == CREDENTIAL_TYPE_PIN && actualMetrics.nonNumeric > 0) {
diff --git a/core/java/android/app/ambientcontext/IAmbientContextManager.aidl b/core/java/android/app/ambientcontext/IAmbientContextManager.aidl
index 8f06e76..a06bdd3 100644
--- a/core/java/android/app/ambientcontext/IAmbientContextManager.aidl
+++ b/core/java/android/app/ambientcontext/IAmbientContextManager.aidl
@@ -35,6 +35,7 @@
     void registerObserverWithCallback(in AmbientContextEventRequest request,
         String packageName,
         in IAmbientContextObserver observer);
+    @EnforcePermission("ACCESS_AMBIENT_CONTEXT_EVENT")
     void unregisterObserver(in String callingPackage);
     void queryServiceStatus(in int[] eventTypes, in String callingPackage,
         in RemoteCallback statusCallback);
diff --git a/core/java/android/app/assist/OWNERS b/core/java/android/app/assist/OWNERS
index 80ecaa4..e4ffd7f 100644
--- a/core/java/android/app/assist/OWNERS
+++ b/core/java/android/app/assist/OWNERS
@@ -1,7 +1,2 @@
-augale@google.com
-joannechung@google.com
-markpun@google.com
-lpeter@google.com
-tymtsai@google.com
 hackz@google.com
 volnov@google.com
\ No newline at end of file
diff --git a/core/java/android/app/backup/BackupManager.java b/core/java/android/app/backup/BackupManager.java
index 5848521..4a07bf1 100644
--- a/core/java/android/app/backup/BackupManager.java
+++ b/core/java/android/app/backup/BackupManager.java
@@ -37,6 +37,8 @@
 import android.util.Log;
 import android.util.Pair;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 import java.util.List;
 
 /**
@@ -199,8 +201,15 @@
     public static final int ERROR_TRANSPORT_INVALID = -2;
 
     private Context mContext;
+
+    /**
+     * @hide Making this package private is not sufficient for the test to access it, that's because
+     * the test is in the same package but is loaded with a different class loader. Package
+     * private members are not accessible across class loaders. So we make it public and @hide it.
+     */
     @UnsupportedAppUsage
-    private static IBackupManager sService;
+    @VisibleForTesting
+    public static IBackupManager sService;
 
     @UnsupportedAppUsage
     private static void checkServiceBinder() {
diff --git a/core/java/android/app/cloudsearch/OWNERS b/core/java/android/app/cloudsearch/OWNERS
index aa4da3b..3a5841e 100644
--- a/core/java/android/app/cloudsearch/OWNERS
+++ b/core/java/android/app/cloudsearch/OWNERS
@@ -1,4 +1,3 @@
 # Bug component: 758286
 
-huiwu@google.com
 srazdan@google.com
diff --git a/core/java/android/app/servertransaction/TransactionExecutorHelper.java b/core/java/android/app/servertransaction/TransactionExecutorHelper.java
index 5311b09..baf2a47 100644
--- a/core/java/android/app/servertransaction/TransactionExecutorHelper.java
+++ b/core/java/android/app/servertransaction/TransactionExecutorHelper.java
@@ -193,8 +193,8 @@
         switch (prevState) {
             // TODO(lifecycler): Extend to support all possible states.
             case ON_START:
-                lifecycleItem = StartActivityItem.obtain(null /* activityOptions */);
-                break;
+                // Fall through to return the PAUSE item to ensure the activity is properly
+                // resumed while relaunching.
             case ON_PAUSE:
                 lifecycleItem = PauseActivityItem.obtain();
                 break;
diff --git a/core/java/android/app/wallpapereffectsgeneration/OWNERS b/core/java/android/app/wallpapereffectsgeneration/OWNERS
index 2bc0154..30949f8 100644
--- a/core/java/android/app/wallpapereffectsgeneration/OWNERS
+++ b/core/java/android/app/wallpapereffectsgeneration/OWNERS
@@ -1,5 +1,4 @@
 susharon@google.com
 shanh@google.com
-huiwu@google.com
 srazdan@google.com
 
diff --git a/core/java/android/attention/AttentionManagerInternal.java b/core/java/android/attention/AttentionManagerInternal.java
index 24fe0db..5d3889d 100644
--- a/core/java/android/attention/AttentionManagerInternal.java
+++ b/core/java/android/attention/AttentionManagerInternal.java
@@ -28,6 +28,11 @@
     public abstract boolean isAttentionServiceSupported();
 
     /**
+     * Returns {@code true} if proximity update is supported by the service.
+     */
+    public abstract boolean isProximitySupported();
+
+    /**
      * Checks whether user attention is at the screen and calls in the provided callback.
      *
      * @param timeoutMillis a budget for the attention check; if it takes longer - {@link
diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
index 4dea4a7..e4b2cf3 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -208,14 +208,6 @@
     public static final String EXTRA_ASSOCIATION = "android.companion.extra.ASSOCIATION";
 
     /**
-     * The package name of the companion device discovery component.
-     *
-     * @hide
-     */
-    public static final String COMPANION_DEVICE_DISCOVERY_PACKAGE_NAME =
-            "com.android.companiondevicemanager";
-
-    /**
      * Callback for applications to receive updates about and the outcome of
      * {@link AssociationRequest} issued via {@code associate()} call.
      *
diff --git a/core/java/android/companion/ICompanionDeviceManager.aidl b/core/java/android/companion/ICompanionDeviceManager.aidl
index b5e2670..b89d3fe 100644
--- a/core/java/android/companion/ICompanionDeviceManager.aidl
+++ b/core/java/android/companion/ICompanionDeviceManager.aidl
@@ -36,6 +36,8 @@
         in String callingPackage, int userId);
 
     List<AssociationInfo> getAssociations(String callingPackage, int userId);
+
+    @EnforcePermission("MANAGE_COMPANION_DEVICES")
     List<AssociationInfo> getAllAssociationsForUser(int userId);
 
     /** @deprecated */
@@ -48,25 +50,28 @@
 
     PendingIntent requestNotificationAccess(in ComponentName component, int userId);
 
-    /** @deprecated */
+    @EnforcePermission("MANAGE_COMPANION_DEVICES")
     boolean isDeviceAssociatedForWifiConnection(in String packageName, in String macAddress,
         int userId);
 
+    @EnforcePermission("REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE")
     void registerDevicePresenceListenerService(in String deviceAddress, in String callingPackage,
         int userId);
 
+    @EnforcePermission("REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE")
     void unregisterDevicePresenceListenerService(in String deviceAddress, in String callingPackage,
         int userId);
 
-    /** @deprecated */
     boolean canPairWithoutPrompt(in String packageName, in String deviceMacAddress, int userId);
 
-    /** @deprecated */
+    @EnforcePermission("ASSOCIATE_COMPANION_DEVICES")
     void createAssociation(in String packageName, in String macAddress, int userId,
         in byte[] certificate);
 
+    @EnforcePermission("MANAGE_COMPANION_DEVICES")
     void addOnAssociationsChangedListener(IOnAssociationsChangedListener listener, int userId);
 
+    @EnforcePermission("MANAGE_COMPANION_DEVICES")
     void removeOnAssociationsChangedListener(IOnAssociationsChangedListener listener, int userId);
 
     void addOnTransportsChangedListener(IOnTransportsChangedListener listener);
@@ -89,8 +94,10 @@
     void startSystemDataTransfer(String packageName, int userId, int associationId,
         in ISystemDataTransferCallback callback);
 
+    @EnforcePermission("DELIVER_COMPANION_MESSAGES")
     void attachSystemDataTransport(String packageName, int userId, int associationId, in ParcelFileDescriptor fd);
 
+    @EnforcePermission("DELIVER_COMPANION_MESSAGES")
     void detachSystemDataTransport(String packageName, int userId, int associationId);
 
     boolean isCompanionApplicationBound(String packageName, int userId);
diff --git a/core/java/android/companion/OWNERS b/core/java/android/companion/OWNERS
index 0348fe2..54d9c24 100644
--- a/core/java/android/companion/OWNERS
+++ b/core/java/android/companion/OWNERS
@@ -1,3 +1,5 @@
 evanxinchen@google.com
 guojing@google.com
-raphk@google.com
\ No newline at end of file
+jeremyns@google.com
+raphk@google.com
+yukl@google.com
\ No newline at end of file
diff --git a/core/java/android/companion/virtual/IVirtualDevice.aidl b/core/java/android/companion/virtual/IVirtualDevice.aidl
index 9efdf28..4801d15 100644
--- a/core/java/android/companion/virtual/IVirtualDevice.aidl
+++ b/core/java/android/companion/virtual/IVirtualDevice.aidl
@@ -59,6 +59,11 @@
     int getDeviceId();
 
     /**
+     * Returns the persistent ID of this virtual device.
+     */
+    String getPersistentDeviceId();
+
+    /**
      * Closes the virtual device and frees all associated resources.
      */
     @EnforcePermission("CREATE_VIRTUAL_DEVICE")
diff --git a/core/java/android/companion/virtual/IVirtualDeviceManager.aidl b/core/java/android/companion/virtual/IVirtualDeviceManager.aidl
index 07743cef5..ee7836f 100644
--- a/core/java/android/companion/virtual/IVirtualDeviceManager.aidl
+++ b/core/java/android/companion/virtual/IVirtualDeviceManager.aidl
@@ -44,6 +44,7 @@
      * @param activityListener The listener to listen for activity changes in a virtual device.
      * @param soundEffectListener The listener to listen for sound effect playback requests.
      */
+    @EnforcePermission("CREATE_VIRTUAL_DEVICE")
     IVirtualDevice createVirtualDevice(
             in IBinder token, String packageName, int associationId,
             in VirtualDeviceParams params, in IVirtualDeviceActivityListener activityListener,
diff --git a/core/java/android/companion/virtual/VirtualDevice.java b/core/java/android/companion/virtual/VirtualDevice.java
index 4ee65e0..ceaf7e4 100644
--- a/core/java/android/companion/virtual/VirtualDevice.java
+++ b/core/java/android/companion/virtual/VirtualDevice.java
@@ -35,6 +35,7 @@
 public final class VirtualDevice implements Parcelable {
 
     private final int mId;
+    private final @Nullable String mPersistentId;
     private final @Nullable String mName;
 
     /**
@@ -43,28 +44,55 @@
      *
      * @hide
      */
-    public VirtualDevice(int id, @Nullable String name) {
+    public VirtualDevice(int id, @Nullable String persistentId, @Nullable String name) {
         if (id <= Context.DEVICE_ID_DEFAULT) {
-            throw new IllegalArgumentException("VirtualDevice ID mist be greater than "
+            throw new IllegalArgumentException("VirtualDevice ID must be greater than "
                     + Context.DEVICE_ID_DEFAULT);
         }
         mId = id;
+        mPersistentId = persistentId;
         mName = name;
     }
 
     private VirtualDevice(@NonNull Parcel parcel) {
         mId = parcel.readInt();
+        mPersistentId = parcel.readString8();
         mName = parcel.readString8();
     }
 
     /**
      * Returns the unique ID of the virtual device.
+     *
+     * <p>This identifier corresponds to {@link Context#getDeviceId()} and can be used to access
+     * device-specific system capabilities.
+     *
+     * <p class="note">This identifier is ephemeral and should not be used for persisting any data
+     * per device.
+     *
+     * @see Context#createDeviceContext
+     * @see #getPersistentDeviceId
      */
     public int getDeviceId() {
         return mId;
     }
 
     /**
+     * Returns the persistent identifier of this virtual device, if any.
+     *
+     * <p> If there is no stable identifier for this virtual device, then this returns {@code null}.
+
+     * <p>This identifier may correspond to a physical device. In that case it remains valid for as
+     * long as that physical device is associated with the host device and may be used to persist
+     * data per device.
+     *
+     * <p class="note">This identifier may not be unique across virtual devices, in case there are
+     * more than one virtual devices corresponding to the same physical device.
+     */
+    public @Nullable String getPersistentDeviceId() {
+        return mPersistentId;
+    }
+
+    /**
      * Returns the name of the virtual device (optionally) provided during its creation.
      *
      * @see VirtualDeviceParams.Builder#setName(String)
@@ -81,6 +109,7 @@
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeInt(mId);
+        dest.writeString8(mPersistentId);
         dest.writeString8(mName);
     }
 
@@ -94,12 +123,13 @@
         }
         VirtualDevice that = (VirtualDevice) o;
         return mId == that.mId
+                && Objects.equals(mPersistentId, that.mPersistentId)
                 && Objects.equals(mName, that.mName);
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(mId, mName);
+        return Objects.hash(mId, mPersistentId, mName);
     }
 
     @Override
@@ -107,6 +137,7 @@
     public String toString() {
         return "VirtualDevice("
                 + " mId=" + mId
+                + " mPersistentId=" + mPersistentId
                 + " mName=" + mName
                 + ")";
     }
diff --git a/core/java/android/companion/virtual/VirtualDeviceInternal.java b/core/java/android/companion/virtual/VirtualDeviceInternal.java
index 045e4c6..f68cfff 100644
--- a/core/java/android/companion/virtual/VirtualDeviceInternal.java
+++ b/core/java/android/companion/virtual/VirtualDeviceInternal.java
@@ -160,6 +160,14 @@
         }
     }
 
+    @Nullable String getPersistentDeviceId() {
+        try {
+            return mVirtualDevice.getPersistentDeviceId();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
     @NonNull Context createContext() {
         try {
             return mContext.createDeviceContext(mVirtualDevice.getDeviceId());
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index 2ca2b79bc..c10e898 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -102,6 +102,13 @@
     public static final String EXTRA_VIRTUAL_DEVICE_ID =
             "android.companion.virtual.extra.VIRTUAL_DEVICE_ID";
 
+    /**
+     * A representation of an invalid CDM association ID. Association IDs must be positive.
+     *
+     * @hide
+     */
+    public static final int ASSOCIATION_ID_INVALID = -1;
+
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(
@@ -367,6 +374,13 @@
         }
 
         /**
+         * Returns the persistent ID of this virtual device.
+         */
+        public @Nullable String getPersistentDeviceId() {
+            return mVirtualDeviceInternal.getPersistentDeviceId();
+        }
+
+        /**
          * Returns a new context bound to this device.
          *
          * <p>This is a convenience method equivalent to calling
@@ -680,7 +694,6 @@
          *   visibility is true.
          */
         @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
-        @NonNull
         public void setShowPointerIcon(boolean showPointerIcon) {
             mVirtualDeviceInternal.setShowPointerIcon(showPointerIcon);
         }
diff --git a/core/java/android/content/AttributionSource.java b/core/java/android/content/AttributionSource.java
index cd45f4d..d0bb2b9 100644
--- a/core/java/android/content/AttributionSource.java
+++ b/core/java/android/content/AttributionSource.java
@@ -115,14 +115,14 @@
     public AttributionSource(int uid, @Nullable String packageName,
             @Nullable String attributionTag, @NonNull IBinder token) {
         this(uid, Process.INVALID_PID, packageName, attributionTag, token,
-                /*renouncedPermissions*/ null, /*next*/ null);
+                /*renouncedPermissions*/ null, Context.DEVICE_ID_DEFAULT, /*next*/ null);
     }
 
     /** @hide */
     public AttributionSource(int uid, int pid, @Nullable String packageName,
             @Nullable String attributionTag, @NonNull IBinder token) {
         this(uid, pid, packageName, attributionTag, token, /*renouncedPermissions*/ null,
-                /*next*/ null);
+                Context.DEVICE_ID_DEFAULT, /*next*/ null);
     }
 
     /** @hide */
@@ -132,28 +132,41 @@
             @Nullable AttributionSource next) {
         this(uid, Process.INVALID_PID, packageName, attributionTag, sDefaultToken,
                 (renouncedPermissions != null)
-                ? renouncedPermissions.toArray(new String[0]) : null, /*next*/ next);
+                ? renouncedPermissions.toArray(new String[0]) : null, Context.DEVICE_ID_DEFAULT,
+                /*next*/ next);
     }
 
     /** @hide */
     public AttributionSource(@NonNull AttributionSource current, @Nullable AttributionSource next) {
         this(current.getUid(), current.getPid(), current.getPackageName(),
                 current.getAttributionTag(), current.getToken(),
-                current.mAttributionSourceState.renouncedPermissions, next);
+                current.mAttributionSourceState.renouncedPermissions, current.getDeviceId(), next);
     }
 
     /** @hide */
     public AttributionSource(int uid, int pid, @Nullable String packageName,
-            @Nullable String attributionTag, @Nullable String[] renouncedPermissions,
+            @Nullable String attributionTag, @Nullable String[] renouncedPermissions, int deviceId,
             @Nullable AttributionSource next) {
-        this(uid, pid, packageName, attributionTag, sDefaultToken, renouncedPermissions, next);
+        this(uid, pid, packageName, attributionTag, sDefaultToken, renouncedPermissions, deviceId,
+                next);
     }
 
     /** @hide */
+    @TestApi
     public AttributionSource(int uid, int pid, @Nullable String packageName,
             @Nullable String attributionTag, @NonNull IBinder token,
             @Nullable String[] renouncedPermissions,
             @Nullable AttributionSource next) {
+        this(uid, pid, packageName, attributionTag, token, renouncedPermissions,
+                Context.DEVICE_ID_DEFAULT, next);
+    }
+
+    /** @hide */
+    @TestApi
+    public AttributionSource(int uid, int pid, @Nullable String packageName,
+            @Nullable String attributionTag, @NonNull IBinder token,
+            @Nullable String[] renouncedPermissions,
+            int deviceId, @Nullable AttributionSource next) {
         mAttributionSourceState = new AttributionSourceState();
         mAttributionSourceState.uid = uid;
         mAttributionSourceState.pid = pid;
@@ -161,6 +174,7 @@
         mAttributionSourceState.packageName = packageName;
         mAttributionSourceState.attributionTag = attributionTag;
         mAttributionSourceState.renouncedPermissions = renouncedPermissions;
+        mAttributionSourceState.deviceId = deviceId;
         mAttributionSourceState.next = (next != null) ? new AttributionSourceState[]
                 {next.mAttributionSourceState} : new AttributionSourceState[0];
     }
@@ -196,25 +210,31 @@
     /** @hide */
     public AttributionSource withNextAttributionSource(@Nullable AttributionSource next) {
         return new AttributionSource(getUid(), getPid(), getPackageName(), getAttributionTag(),
-                getToken(), mAttributionSourceState.renouncedPermissions, next);
+                getToken(), mAttributionSourceState.renouncedPermissions, getDeviceId(), next);
     }
 
     /** @hide */
     public AttributionSource withPackageName(@Nullable String packageName) {
         return new AttributionSource(getUid(), getPid(), packageName, getAttributionTag(),
-               getToken(), mAttributionSourceState.renouncedPermissions, getNext());
+               getToken(), mAttributionSourceState.renouncedPermissions, getDeviceId(), getNext());
     }
 
     /** @hide */
     public AttributionSource withToken(@NonNull Binder token) {
         return new AttributionSource(getUid(), getPid(), getPackageName(), getAttributionTag(),
-                token, mAttributionSourceState.renouncedPermissions, getNext());
+                token, mAttributionSourceState.renouncedPermissions, getDeviceId(), getNext());
     }
 
     /** @hide */
     public AttributionSource withPid(int pid) {
         return new AttributionSource(getUid(), pid, getPackageName(), getAttributionTag(),
-                getToken(), mAttributionSourceState.renouncedPermissions, getNext());
+                getToken(), mAttributionSourceState.renouncedPermissions, getDeviceId(), getNext());
+    }
+
+    /** @hide */
+    public AttributionSource withDeviceId(int deviceId) {
+        return new AttributionSource(getUid(), getPid(), getPackageName(), getAttributionTag(),
+                getToken(), mAttributionSourceState.renouncedPermissions, deviceId, getNext());
     }
 
     /** @hide */
@@ -258,6 +278,7 @@
         try {
             return new AttributionSource.Builder(uid)
                 .setPid(Process.myPid())
+                .setDeviceId(Context.DEVICE_ID_DEFAULT)
                 .setPackageName(AppGlobals.getPackageManager().getPackagesForUid(uid)[0])
                 .build();
         } catch (Exception ignored) {
@@ -496,6 +517,13 @@
     }
 
     /**
+     * The device ID for which permissions are checked.
+     */
+    public int getDeviceId() {
+        return mAttributionSourceState.deviceId;
+    }
+
+    /**
      * Unique token for that source.
      *
      * @hide
@@ -661,8 +689,24 @@
         }
 
         /**
-         * The next app to receive the permission protected data.
+         * Set the device ID for this attribution source, permission check would happen
+         * against this device ID.
+         *
+         * @return the builder
          */
+        public @NonNull Builder setDeviceId(int deviceId) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x12;
+            mAttributionSourceState.deviceId = deviceId;
+            return this;
+        }
+
+        /**
+         * The next app to receive the permission protected data.
+         *
+         * @deprecated Use {@link setNextAttributionSource} instead.
+         */
+        @Deprecated
         public @NonNull Builder setNext(@Nullable AttributionSource value) {
             checkNotUsed();
             mBuilderFieldsSet |= 0x20;
@@ -671,6 +715,17 @@
             return this;
         }
 
+        /**
+         * The next app to receive the permission protected data.
+         */
+        public @NonNull Builder setNextAttributionSource(@NonNull AttributionSource value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x20;
+            mAttributionSourceState.next =
+                    new AttributionSourceState[]{value.mAttributionSourceState};
+            return this;
+        }
+
         /** Builds the instance. This builder should not be touched after calling this! */
         public @NonNull AttributionSource build() {
             checkNotUsed();
@@ -688,6 +743,9 @@
             if ((mBuilderFieldsSet & 0x10) == 0) {
                 mAttributionSourceState.renouncedPermissions = null;
             }
+            if ((mBuilderFieldsSet & 0x12) == 0) {
+                mAttributionSourceState.deviceId = Context.DEVICE_ID_DEFAULT;
+            }
             if ((mBuilderFieldsSet & 0x20) == 0) {
                 mAttributionSourceState.next = null;
             }
diff --git a/core/java/android/content/ContentCaptureOptions.java b/core/java/android/content/ContentCaptureOptions.java
index 856bde8..36e0529 100644
--- a/core/java/android/content/ContentCaptureOptions.java
+++ b/core/java/android/content/ContentCaptureOptions.java
@@ -76,6 +76,20 @@
     public final boolean disableFlushForViewTreeAppearing;
 
     /**
+     * Is the content capture receiver enabled.
+     *
+     * @hide
+     */
+    public final boolean enableReceiver;
+
+    /**
+     * Options for the content protection flow.
+     *
+     * @hide
+     */
+    @NonNull public final ContentProtectionOptions contentProtectionOptions;
+
+    /**
      * List of activities explicitly allowlisted for content capture (or {@code null} if allowlisted
      * for all acitivites in the package).
      */
@@ -94,52 +108,99 @@
      * for contexts belonging to the content capture service app.
      */
     public ContentCaptureOptions(int loggingLevel) {
-        this(/* lite= */ true, loggingLevel, /* maxBufferSize= */ 0,
-                /* idleFlushingFrequencyMs= */ 0, /* textChangeFlushingFrequencyMs= */ 0,
-                /* logHistorySize= */ 0, /* disableFlushForViewTreeAppearing= */ false,
+        this(
+                /* lite= */ true,
+                loggingLevel,
+                /* maxBufferSize= */ 0,
+                /* idleFlushingFrequencyMs= */ 0,
+                /* textChangeFlushingFrequencyMs= */ 0,
+                /* logHistorySize= */ 0,
+                /* disableFlushForViewTreeAppearing= */ false,
+                /* enableReceiver= */ false,
+                new ContentProtectionOptions(
+                        /* enableReceiver= */ false,
+                        /* bufferSize= */ 0),
                 /* whitelistedComponents= */ null);
     }
 
-    /**
-     * Default constructor.
-     */
-    public ContentCaptureOptions(int loggingLevel, int maxBufferSize, int idleFlushingFrequencyMs,
-            int textChangeFlushingFrequencyMs, int logHistorySize,
-            @SuppressLint({"ConcreteCollection", "NullableCollection"})
-            @Nullable ArraySet<ComponentName> whitelistedComponents) {
-        this(/* lite= */ false, loggingLevel, maxBufferSize, idleFlushingFrequencyMs,
-                textChangeFlushingFrequencyMs, logHistorySize,
+    /** Default constructor. */
+    public ContentCaptureOptions(
+            int loggingLevel,
+            int maxBufferSize,
+            int idleFlushingFrequencyMs,
+            int textChangeFlushingFrequencyMs,
+            int logHistorySize,
+            @SuppressLint({"ConcreteCollection", "NullableCollection"}) @Nullable
+                    ArraySet<ComponentName> whitelistedComponents) {
+        this(
+                /* lite= */ false,
+                loggingLevel,
+                maxBufferSize,
+                idleFlushingFrequencyMs,
+                textChangeFlushingFrequencyMs,
+                logHistorySize,
                 ContentCaptureManager.DEFAULT_DISABLE_FLUSH_FOR_VIEW_TREE_APPEARING,
+                ContentCaptureManager.DEFAULT_ENABLE_CONTENT_CAPTURE_RECEIVER,
+                new ContentProtectionOptions(
+                        ContentCaptureManager.DEFAULT_ENABLE_CONTENT_PROTECTION_RECEIVER,
+                        ContentCaptureManager.DEFAULT_CONTENT_PROTECTION_BUFFER_SIZE),
                 whitelistedComponents);
     }
 
     /** @hide */
-    public ContentCaptureOptions(int loggingLevel, int maxBufferSize, int idleFlushingFrequencyMs,
-            int textChangeFlushingFrequencyMs, int logHistorySize,
+    public ContentCaptureOptions(
+            int loggingLevel,
+            int maxBufferSize,
+            int idleFlushingFrequencyMs,
+            int textChangeFlushingFrequencyMs,
+            int logHistorySize,
             boolean disableFlushForViewTreeAppearing,
-            @SuppressLint({"ConcreteCollection", "NullableCollection"})
-            @Nullable ArraySet<ComponentName> whitelistedComponents) {
-        this(/* lite= */ false, loggingLevel, maxBufferSize, idleFlushingFrequencyMs,
-                textChangeFlushingFrequencyMs, logHistorySize, disableFlushForViewTreeAppearing,
+            boolean enableReceiver,
+            @NonNull ContentProtectionOptions contentProtectionOptions,
+            @SuppressLint({"ConcreteCollection", "NullableCollection"}) @Nullable
+                    ArraySet<ComponentName> whitelistedComponents) {
+        this(
+                /* lite= */ false,
+                loggingLevel,
+                maxBufferSize,
+                idleFlushingFrequencyMs,
+                textChangeFlushingFrequencyMs,
+                logHistorySize,
+                disableFlushForViewTreeAppearing,
+                enableReceiver,
+                contentProtectionOptions,
                 whitelistedComponents);
     }
 
     /** @hide */
     @VisibleForTesting
     public ContentCaptureOptions(@Nullable ArraySet<ComponentName> whitelistedComponents) {
-        this(ContentCaptureManager.LOGGING_LEVEL_VERBOSE,
+        this(
+                ContentCaptureManager.LOGGING_LEVEL_VERBOSE,
                 ContentCaptureManager.DEFAULT_MAX_BUFFER_SIZE,
                 ContentCaptureManager.DEFAULT_IDLE_FLUSHING_FREQUENCY_MS,
                 ContentCaptureManager.DEFAULT_TEXT_CHANGE_FLUSHING_FREQUENCY_MS,
                 ContentCaptureManager.DEFAULT_LOG_HISTORY_SIZE,
                 ContentCaptureManager.DEFAULT_DISABLE_FLUSH_FOR_VIEW_TREE_APPEARING,
+                ContentCaptureManager.DEFAULT_ENABLE_CONTENT_CAPTURE_RECEIVER,
+                new ContentProtectionOptions(
+                        ContentCaptureManager.DEFAULT_ENABLE_CONTENT_PROTECTION_RECEIVER,
+                        ContentCaptureManager.DEFAULT_CONTENT_PROTECTION_BUFFER_SIZE),
                 whitelistedComponents);
     }
 
-    private ContentCaptureOptions(boolean lite, int loggingLevel, int maxBufferSize,
-            int idleFlushingFrequencyMs, int textChangeFlushingFrequencyMs, int logHistorySize,
+    private ContentCaptureOptions(
+            boolean lite,
+            int loggingLevel,
+            int maxBufferSize,
+            int idleFlushingFrequencyMs,
+            int textChangeFlushingFrequencyMs,
+            int logHistorySize,
             boolean disableFlushForViewTreeAppearing,
-            @Nullable ArraySet<ComponentName> whitelistedComponents) {
+            boolean enableReceiver,
+            @NonNull ContentProtectionOptions contentProtectionOptions,
+            @SuppressLint({"ConcreteCollection", "NullableCollection"}) @Nullable
+                    ArraySet<ComponentName> whitelistedComponents) {
         this.lite = lite;
         this.loggingLevel = loggingLevel;
         this.maxBufferSize = maxBufferSize;
@@ -147,6 +208,8 @@
         this.textChangeFlushingFrequencyMs = textChangeFlushingFrequencyMs;
         this.logHistorySize = logHistorySize;
         this.disableFlushForViewTreeAppearing = disableFlushForViewTreeAppearing;
+        this.enableReceiver = enableReceiver;
+        this.contentProtectionOptions = contentProtectionOptions;
         this.whitelistedComponents = whitelistedComponents;
     }
 
@@ -191,12 +254,22 @@
             return "ContentCaptureOptions [loggingLevel=" + loggingLevel + " (lite)]";
         }
         final StringBuilder string = new StringBuilder("ContentCaptureOptions [");
-        string.append("loggingLevel=").append(loggingLevel)
-            .append(", maxBufferSize=").append(maxBufferSize)
-            .append(", idleFlushingFrequencyMs=").append(idleFlushingFrequencyMs)
-            .append(", textChangeFlushingFrequencyMs=").append(textChangeFlushingFrequencyMs)
-            .append(", logHistorySize=").append(logHistorySize)
-            .append(", disableFlushForViewTreeAppearing=").append(disableFlushForViewTreeAppearing);
+        string.append("loggingLevel=")
+                .append(loggingLevel)
+                .append(", maxBufferSize=")
+                .append(maxBufferSize)
+                .append(", idleFlushingFrequencyMs=")
+                .append(idleFlushingFrequencyMs)
+                .append(", textChangeFlushingFrequencyMs=")
+                .append(textChangeFlushingFrequencyMs)
+                .append(", logHistorySize=")
+                .append(logHistorySize)
+                .append(", disableFlushForViewTreeAppearing=")
+                .append(disableFlushForViewTreeAppearing)
+                .append(", enableReceiver=")
+                .append(enableReceiver)
+                .append(", contentProtectionOptions=")
+                .append(contentProtectionOptions);
         if (whitelistedComponents != null) {
             string.append(", whitelisted=").append(whitelistedComponents);
         }
@@ -210,11 +283,21 @@
             pw.print(", lite");
             return;
         }
-        pw.print(", bufferSize="); pw.print(maxBufferSize);
-        pw.print(", idle="); pw.print(idleFlushingFrequencyMs);
-        pw.print(", textIdle="); pw.print(textChangeFlushingFrequencyMs);
-        pw.print(", logSize="); pw.print(logHistorySize);
-        pw.print(", disableFlushForViewTreeAppearing="); pw.print(disableFlushForViewTreeAppearing);
+        pw.print(", bufferSize=");
+        pw.print(maxBufferSize);
+        pw.print(", idle=");
+        pw.print(idleFlushingFrequencyMs);
+        pw.print(", textIdle=");
+        pw.print(textChangeFlushingFrequencyMs);
+        pw.print(", logSize=");
+        pw.print(logHistorySize);
+        pw.print(", disableFlushForViewTreeAppearing=");
+        pw.print(disableFlushForViewTreeAppearing);
+        pw.print(", enableReceiver=");
+        pw.print(enableReceiver);
+        pw.print(", contentProtectionOptions=[");
+        contentProtectionOptions.dumpShort(pw);
+        pw.print("]");
         if (whitelistedComponents != null) {
             pw.print(", whitelisted="); pw.print(whitelistedComponents);
         }
@@ -236,6 +319,8 @@
         parcel.writeInt(textChangeFlushingFrequencyMs);
         parcel.writeInt(logHistorySize);
         parcel.writeBoolean(disableFlushForViewTreeAppearing);
+        parcel.writeBoolean(enableReceiver);
+        contentProtectionOptions.writeToParcel(parcel);
         parcel.writeArraySet(whitelistedComponents);
     }
 
@@ -254,12 +339,22 @@
                     final int textChangeFlushingFrequencyMs = parcel.readInt();
                     final int logHistorySize = parcel.readInt();
                     final boolean disableFlushForViewTreeAppearing = parcel.readBoolean();
+                    final boolean enableReceiver = parcel.readBoolean();
+                    final ContentProtectionOptions contentProtectionOptions =
+                            ContentProtectionOptions.createFromParcel(parcel);
                     @SuppressWarnings("unchecked")
                     final ArraySet<ComponentName> whitelistedComponents =
                             (ArraySet<ComponentName>) parcel.readArraySet(null);
-                    return new ContentCaptureOptions(loggingLevel, maxBufferSize,
-                            idleFlushingFrequencyMs, textChangeFlushingFrequencyMs, logHistorySize,
-                            disableFlushForViewTreeAppearing, whitelistedComponents);
+                    return new ContentCaptureOptions(
+                            loggingLevel,
+                            maxBufferSize,
+                            idleFlushingFrequencyMs,
+                            textChangeFlushingFrequencyMs,
+                            logHistorySize,
+                            disableFlushForViewTreeAppearing,
+                            enableReceiver,
+                            contentProtectionOptions,
+                            whitelistedComponents);
                 }
 
                 @Override
@@ -267,4 +362,62 @@
                     return new ContentCaptureOptions[size];
                 }
     };
+
+    /**
+     * Content protection options for a given package.
+     *
+     * <p>Does not implement {@code Parcelable} since it is an inner class without a matching AIDL.
+     *
+     * @hide
+     */
+    public static class ContentProtectionOptions {
+
+        /**
+         * Is the content protection receiver enabled.
+         *
+         * @hide
+         */
+        public final boolean enableReceiver;
+
+        /**
+         * Size of the in-memory ring buffer for the content protection flow.
+         *
+         * @hide
+         */
+        public final int bufferSize;
+
+        public ContentProtectionOptions(boolean enableReceiver, int bufferSize) {
+            this.enableReceiver = enableReceiver;
+            this.bufferSize = bufferSize;
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder stringBuilder = new StringBuilder("ContentProtectionOptions [");
+            stringBuilder
+                    .append("enableReceiver=")
+                    .append(enableReceiver)
+                    .append(", bufferSize=")
+                    .append(bufferSize);
+            return stringBuilder.append(']').toString();
+        }
+
+        private void dumpShort(@NonNull PrintWriter pw) {
+            pw.print("enableReceiver=");
+            pw.print(enableReceiver);
+            pw.print(", bufferSize=");
+            pw.print(bufferSize);
+        }
+
+        private void writeToParcel(Parcel parcel) {
+            parcel.writeBoolean(enableReceiver);
+            parcel.writeInt(bufferSize);
+        }
+
+        private static ContentProtectionOptions createFromParcel(Parcel parcel) {
+            boolean enableReceiver = parcel.readBoolean();
+            int bufferSize = parcel.readInt();
+            return new ContentProtectionOptions(enableReceiver, bufferSize);
+        }
+    }
 }
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index 2200af6..a0bbeb5 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -20,7 +20,7 @@
 import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
 import static android.os.Process.SYSTEM_UID;
 import static android.os.Process.myUserHandle;
-import static android.os.Trace.TRACE_TAG_DATABASE;
+import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
 
 import static com.android.internal.util.FrameworkStatsLog.GET_TYPE_ACCESSED_WITHOUT_PERMISSION;
 import static com.android.internal.util.FrameworkStatsLog.GET_TYPE_ACCESSED_WITHOUT_PERMISSION__LOCATION__PROVIDER_CHECK_URI_PERMISSION;
@@ -285,7 +285,7 @@
                 // Return an empty cursor for all columns.
                 return new MatrixCursor(cursor.getColumnNames(), 0);
             }
-            traceBegin(TRACE_TAG_DATABASE, "query: ", uri.getAuthority());
+            traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "query: ", uri.getAuthority());
             final AttributionSource original = setCallingAttributionSource(
                     attributionSource);
             try {
@@ -296,7 +296,7 @@
                 throw e.rethrowAsRuntimeException();
             } finally {
                 setCallingAttributionSource(original);
-                Trace.traceEnd(TRACE_TAG_DATABASE);
+                Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
             }
         }
 
@@ -304,7 +304,7 @@
         public String getType(AttributionSource attributionSource, Uri uri) {
             uri = validateIncomingUri(uri);
             uri = maybeGetUriWithoutUserId(uri);
-            traceBegin(TRACE_TAG_DATABASE, "getType: ", uri.getAuthority());
+            traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "getType: ", uri.getAuthority());
             final AttributionSource original = setCallingAttributionSource(
                     attributionSource);
             try {
@@ -348,7 +348,7 @@
                 throw e.rethrowAsRuntimeException();
             } finally {
                 setCallingAttributionSource(original);
-                Trace.traceEnd(TRACE_TAG_DATABASE);
+                Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
             }
         }
 
@@ -410,7 +410,7 @@
             // getCallingPackage() isn't available in getTypeAnonymous(), as the javadoc states.
             uri = validateIncomingUri(uri);
             uri = maybeGetUriWithoutUserId(uri);
-            traceBegin(TRACE_TAG_DATABASE, "getTypeAnonymous: ", uri.getAuthority());
+            traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "getTypeAnonymous: ", uri.getAuthority());
             final Bundle result = new Bundle();
             try {
                 result.putString(ContentResolver.REMOTE_CALLBACK_RESULT, getTypeAnonymous(uri));
@@ -419,7 +419,7 @@
                         new ParcelableException(e));
             } finally {
                 callback.sendResult(result);
-                Trace.traceEnd(TRACE_TAG_DATABASE);
+                Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
             }
         }
 
@@ -439,7 +439,7 @@
                     setCallingAttributionSource(original);
                 }
             }
-            traceBegin(TRACE_TAG_DATABASE, "insert: ", uri.getAuthority());
+            traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "insert: ", uri.getAuthority());
             final AttributionSource original = setCallingAttributionSource(
                     attributionSource);
             try {
@@ -448,7 +448,7 @@
                 throw e.rethrowAsRuntimeException();
             } finally {
                 setCallingAttributionSource(original);
-                Trace.traceEnd(TRACE_TAG_DATABASE);
+                Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
             }
         }
 
@@ -461,7 +461,7 @@
                     != PermissionChecker.PERMISSION_GRANTED) {
                 return 0;
             }
-            traceBegin(TRACE_TAG_DATABASE, "bulkInsert: ", uri.getAuthority());
+            traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "bulkInsert: ", uri.getAuthority());
             final AttributionSource original = setCallingAttributionSource(
                     attributionSource);
             try {
@@ -470,7 +470,7 @@
                 throw e.rethrowAsRuntimeException();
             } finally {
                 setCallingAttributionSource(original);
-                Trace.traceEnd(TRACE_TAG_DATABASE);
+                Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
             }
         }
 
@@ -507,7 +507,7 @@
                     }
                 }
             }
-            traceBegin(TRACE_TAG_DATABASE, "applyBatch: ", authority);
+            traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "applyBatch: ", authority);
             final AttributionSource original = setCallingAttributionSource(
                     attributionSource);
             try {
@@ -526,7 +526,7 @@
                 throw e.rethrowAsRuntimeException();
             } finally {
                 setCallingAttributionSource(original);
-                Trace.traceEnd(TRACE_TAG_DATABASE);
+                Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
             }
         }
 
@@ -539,7 +539,7 @@
                     != PermissionChecker.PERMISSION_GRANTED) {
                 return 0;
             }
-            traceBegin(TRACE_TAG_DATABASE, "delete: ", uri.getAuthority());
+            traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "delete: ", uri.getAuthority());
             final AttributionSource original = setCallingAttributionSource(
                     attributionSource);
             try {
@@ -548,7 +548,7 @@
                 throw e.rethrowAsRuntimeException();
             } finally {
                 setCallingAttributionSource(original);
-                Trace.traceEnd(TRACE_TAG_DATABASE);
+                Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
             }
         }
 
@@ -561,7 +561,7 @@
                     != PermissionChecker.PERMISSION_GRANTED) {
                 return 0;
             }
-            traceBegin(TRACE_TAG_DATABASE, "update: ", uri.getAuthority());
+            traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "update: ", uri.getAuthority());
             final AttributionSource original = setCallingAttributionSource(
                     attributionSource);
             try {
@@ -570,7 +570,7 @@
                 throw e.rethrowAsRuntimeException();
             } finally {
                 setCallingAttributionSource(original);
-                Trace.traceEnd(TRACE_TAG_DATABASE);
+                Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
             }
         }
 
@@ -581,7 +581,7 @@
             uri = validateIncomingUri(uri);
             uri = maybeGetUriWithoutUserId(uri);
             enforceFilePermission(attributionSource, uri, mode);
-            traceBegin(TRACE_TAG_DATABASE, "openFile: ", uri.getAuthority());
+            traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "openFile: ", uri.getAuthority());
             final AttributionSource original = setCallingAttributionSource(
                     attributionSource);
             try {
@@ -591,7 +591,7 @@
                 throw e.rethrowAsRuntimeException();
             } finally {
                 setCallingAttributionSource(original);
-                Trace.traceEnd(TRACE_TAG_DATABASE);
+                Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
             }
         }
 
@@ -602,7 +602,7 @@
             uri = validateIncomingUri(uri);
             uri = maybeGetUriWithoutUserId(uri);
             enforceFilePermission(attributionSource, uri, mode);
-            traceBegin(TRACE_TAG_DATABASE, "openAssetFile: ", uri.getAuthority());
+            traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "openAssetFile: ", uri.getAuthority());
             final AttributionSource original = setCallingAttributionSource(
                     attributionSource);
             try {
@@ -612,7 +612,7 @@
                 throw e.rethrowAsRuntimeException();
             } finally {
                 setCallingAttributionSource(original);
-                Trace.traceEnd(TRACE_TAG_DATABASE);
+                Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
             }
         }
 
@@ -621,7 +621,7 @@
                 String method, @Nullable String arg, @Nullable Bundle extras) {
             validateIncomingAuthority(authority);
             Bundle.setDefusable(extras, true);
-            traceBegin(TRACE_TAG_DATABASE, "call: ", authority);
+            traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "call: ", authority);
             final AttributionSource original = setCallingAttributionSource(
                     attributionSource);
             try {
@@ -630,7 +630,7 @@
                 throw e.rethrowAsRuntimeException();
             } finally {
                 setCallingAttributionSource(original);
-                Trace.traceEnd(TRACE_TAG_DATABASE);
+                Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
             }
         }
 
@@ -639,7 +639,7 @@
                 Uri uri, String mimeTypeFilter) {
             uri = validateIncomingUri(uri);
             uri = maybeGetUriWithoutUserId(uri);
-            traceBegin(TRACE_TAG_DATABASE, "getStreamTypes: ", uri.getAuthority());
+            traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "getStreamTypes: ", uri.getAuthority());
             final AttributionSource original = setCallingAttributionSource(
                     attributionSource);
             try {
@@ -648,7 +648,7 @@
                 throw e.rethrowAsRuntimeException();
             } finally {
                 setCallingAttributionSource(original);
-                Trace.traceEnd(TRACE_TAG_DATABASE);
+                Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
             }
         }
 
@@ -660,7 +660,7 @@
             uri = validateIncomingUri(uri);
             uri = maybeGetUriWithoutUserId(uri);
             enforceFilePermission(attributionSource, uri, "r");
-            traceBegin(TRACE_TAG_DATABASE, "openTypedAssetFile: ", uri.getAuthority());
+            traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "openTypedAssetFile: ", uri.getAuthority());
             final AttributionSource original = setCallingAttributionSource(
                     attributionSource);
             try {
@@ -670,7 +670,7 @@
                 throw e.rethrowAsRuntimeException();
             } finally {
                 setCallingAttributionSource(original);
-                Trace.traceEnd(TRACE_TAG_DATABASE);
+                Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
             }
         }
 
@@ -688,7 +688,7 @@
                     != PermissionChecker.PERMISSION_GRANTED) {
                 return null;
             }
-            traceBegin(TRACE_TAG_DATABASE, "canonicalize: ", uri.getAuthority());
+            traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "canonicalize: ", uri.getAuthority());
             final AttributionSource original = setCallingAttributionSource(
                     attributionSource);
             try {
@@ -697,7 +697,7 @@
                 throw e.rethrowAsRuntimeException();
             } finally {
                 setCallingAttributionSource(original);
-                Trace.traceEnd(TRACE_TAG_DATABASE);
+                Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
             }
         }
 
@@ -724,7 +724,7 @@
                     != PermissionChecker.PERMISSION_GRANTED) {
                 return null;
             }
-            traceBegin(TRACE_TAG_DATABASE, "uncanonicalize: ", uri.getAuthority());
+            traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "uncanonicalize: ", uri.getAuthority());
             final AttributionSource original = setCallingAttributionSource(
                     attributionSource);
             try {
@@ -733,7 +733,7 @@
                 throw e.rethrowAsRuntimeException();
             } finally {
                 setCallingAttributionSource(original);
-                Trace.traceEnd(TRACE_TAG_DATABASE);
+                Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
             }
         }
 
@@ -760,7 +760,7 @@
                     != PermissionChecker.PERMISSION_GRANTED) {
                 return false;
             }
-            traceBegin(TRACE_TAG_DATABASE, "refresh: ", uri.getAuthority());
+            traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "refresh: ", uri.getAuthority());
             final AttributionSource original = setCallingAttributionSource(
                     attributionSource);
             try {
@@ -768,7 +768,7 @@
                         CancellationSignal.fromTransport(cancellationSignal));
             } finally {
                 setCallingAttributionSource(original);
-                Trace.traceEnd(TRACE_TAG_DATABASE);
+                Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
             }
         }
 
@@ -777,7 +777,7 @@
                 int uid, int modeFlags) {
             uri = validateIncomingUri(uri);
             uri = maybeGetUriWithoutUserId(uri);
-            traceBegin(TRACE_TAG_DATABASE, "checkUriPermission: ", uri.getAuthority());
+            traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "checkUriPermission: ", uri.getAuthority());
             final AttributionSource original = setCallingAttributionSource(
                     attributionSource);
             try {
@@ -786,7 +786,7 @@
                 throw e.rethrowAsRuntimeException();
             } finally {
                 setCallingAttributionSource(original);
-                Trace.traceEnd(TRACE_TAG_DATABASE);
+                Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
             }
         }
 
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 6d82922..2a6d84b 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -5298,6 +5298,13 @@
     public static final String APP_PREDICTION_SERVICE = "app_prediction";
 
     /**
+     * Used for reading system-wide, overridable flags.
+     *
+     * @hide
+     */
+    public static final String FEATURE_FLAGS_SERVICE = "feature_flags";
+
+    /**
      * Official published name of the search ui service.
      *
      * <p><b>NOTE: </b> this service is optional; callers of
diff --git a/core/java/android/content/IClipboard.aidl b/core/java/android/content/IClipboard.aidl
index fe7798f..e0fba1d 100644
--- a/core/java/android/content/IClipboard.aidl
+++ b/core/java/android/content/IClipboard.aidl
@@ -28,6 +28,7 @@
 interface IClipboard {
     void setPrimaryClip(in ClipData clip, String callingPackage, String attributionTag, int userId,
             int deviceId);
+    @EnforcePermission("SET_CLIP_SOURCE")
     void setPrimaryClipAsPackage(in ClipData clip, String callingPackage, String attributionTag,
             int userId, int deviceId, String sourcePackage);
     void clearPrimaryClip(String callingPackage, String attributionTag, int userId, int deviceId);
@@ -46,6 +47,7 @@
     boolean hasClipboardText(String callingPackage, String attributionTag, int userId,
             int deviceId);
 
+    @EnforcePermission("SET_CLIP_SOURCE")
     String getPrimaryClipSource(String callingPackage, String attributionTag, int userId,
             int deviceId);
 
diff --git a/core/java/android/content/IContentService.aidl b/core/java/android/content/IContentService.aidl
index 127466d..0d11c78 100644
--- a/core/java/android/content/IContentService.aidl
+++ b/core/java/android/content/IContentService.aidl
@@ -160,6 +160,7 @@
      * @param cname component to identify sync service, must be null if account/providerName are
      * non-null.
      */
+    @EnforcePermission("READ_SYNC_STATS")
     @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
     boolean isSyncActive(in Account account, String authority, in ComponentName cname);
 
@@ -183,6 +184,7 @@
      * non-null.
      */
     boolean isSyncPending(in Account account, String authority, in ComponentName cname);
+    @EnforcePermission("READ_SYNC_STATS")
     boolean isSyncPendingAsUser(in Account account, String authority, in ComponentName cname,
             int userId);
 
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index e763e95..fe108a5 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -1880,7 +1880,10 @@
      * @see #EXTRA_PACKAGE_NAME
      *
      * @hide
+     * @deprecated Use {@link android.provider.Settings#ACTION_APP_PERMISSIONS_SETTINGS}
+     * instead.
      */
+    @Deprecated
     @SystemApi
     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
     public static final String ACTION_MANAGE_APP_PERMISSIONS =
@@ -6543,6 +6546,14 @@
     public static final String EXTRA_REASON = "android.intent.extra.REASON";
 
     /**
+     * Intent extra: Whether to show the wipe progress UI or to skip it.
+     *
+     * <p>Type: boolean
+     * @hide
+     */
+    public static final String EXTRA_SHOW_WIPE_PROGRESS = "android.intent.extra.SHOW_WIPE_PROGRESS";
+
+    /**
      * {@hide}
      * This extra will be send together with {@link #ACTION_FACTORY_RESET}
      */
diff --git a/core/java/android/content/IntentSender.java b/core/java/android/content/IntentSender.java
index ccb53cf..3686898 100644
--- a/core/java/android/content/IntentSender.java
+++ b/core/java/android/content/IntentSender.java
@@ -417,4 +417,12 @@
 
         return mCachedInfo;
     }
+
+    /**
+     * Check if the PendingIntent is marked with {@link android.app.PendingIntent#FLAG_IMMUTABLE}.
+     * @hide
+     */
+    public boolean isImmutable() {
+        return getCachedInfo().isImmutable();
+    }
 }
diff --git a/core/java/android/content/OWNERS b/core/java/android/content/OWNERS
index d73f48a..90c3d04 100644
--- a/core/java/android/content/OWNERS
+++ b/core/java/android/content/OWNERS
@@ -5,9 +5,7 @@
 per-file *Sync* = file:/services/core/java/com/android/server/am/OWNERS
 per-file IntentFilter.java = file:/PACKAGE_MANAGER_OWNERS
 per-file IntentFilter.java = file:/services/core/java/com/android/server/am/OWNERS
-per-file Intent.java = file:/PACKAGE_MANAGER_OWNERS
-per-file Intent.java = file:/services/core/java/com/android/server/wm/OWNERS
-per-file Intent.java = file:/services/core/java/com/android/server/am/OWNERS
+per-file Intent.java = file:/INTENT_OWNERS
 per-file AutofillOptions* = file:/core/java/android/service/autofill/OWNERS
 per-file ContentCaptureOptions* = file:/core/java/android/service/contentcapture/OWNERS
 per-file LocusId* = file:/core/java/android/service/contentcapture/OWNERS
diff --git a/core/java/android/content/SharedPreferences.java b/core/java/android/content/SharedPreferences.java
index de6dc22..5e5d4181 100644
--- a/core/java/android/content/SharedPreferences.java
+++ b/core/java/android/content/SharedPreferences.java
@@ -30,10 +30,27 @@
  * when they are committed to storage.  Objects that are returned from the
  * various <code>get</code> methods must be treated as immutable by the application.
  *
- * <p>Note: This class provides strong consistency guarantees. It is using expensive operations
- * which might slow down an app. Frequently changing properties or properties where loss can be
- * tolerated should use other mechanisms. For more details read the comments on
- * {@link Editor#commit()} and {@link Editor#apply()}.
+ * <p>SharedPreferences is best suited to storing data about how the user prefers
+ * to experience the app, for example, whether the user prefers a particular UI theme
+ * or whether they prefer viewing particular content in a list vs. a grid. To this end,
+ * SharedPreferences reflects changes {@link Editor#commit() committed} or
+ * {@link Editor#apply() applied} by {@link Editor}s <em>immediately</em>, potentially
+ * before those changes are durably persisted.
+ * Under some circumstances such as app crashes or termination these changes may be lost,
+ * even if an {@link OnSharedPreferenceChangeListener} reported the change was successful.
+ * SharedPreferences is not recommended for storing data that is sensitive to this
+ * kind of rollback to a prior state such as user security or privacy settings.
+ * For other high-level data persistence options, see
+ * <a href="https://d.android.com/room">Room</a> or
+ * <a href="https://d.android.com/datastore">DataStore</a>.
+ *
+ * <p><em>Note:</em> Common implementations guarantee that outstanding edits to preference
+ * files are persisted to disk when host Activities become stopped. In some situations
+ * (e.g. performing many {@link Editor#commit()} or {@link Editor#apply()}
+ * operations just prior to navigating away from the host Activity) this can lead
+ * to blocking the main thread during lifecycle transition events and associated
+ * ANR errors. For more details see the documentation for {@link Editor#commit()} and
+ * {@link Editor#apply()}.
  *
  * <p><em>Note: This class does not support use across multiple processes.</em>
  *
diff --git a/core/java/android/content/om/IOverlayManager.aidl b/core/java/android/content/om/IOverlayManager.aidl
index a0f3d7a..122ab48 100644
--- a/core/java/android/content/om/IOverlayManager.aidl
+++ b/core/java/android/content/om/IOverlayManager.aidl
@@ -190,4 +190,15 @@
      * @throws SecurityException if the transaction failed
      */
     void commit(in OverlayManagerTransaction transaction);
+
+    /**
+     * Returns a String of a list of partitions from low priority to high.
+     */
+    String getPartitionOrder();
+
+    /**
+     * Returns a boolean which represent whether the partition list is sorted by default.
+     * If not then it should be sorted by /product/overlay/partition_order.xml.
+     */
+    boolean isDefaultPartitionOrder();
 }
diff --git a/core/java/android/content/om/TEST_MAPPING b/core/java/android/content/om/TEST_MAPPING
index a9aaf1a..eb9254d 100644
--- a/core/java/android/content/om/TEST_MAPPING
+++ b/core/java/android/content/om/TEST_MAPPING
@@ -18,12 +18,7 @@
       "name": "OverlayHostTests"
     },
     {
-      "name": "CtsAppSecurityHostTestCases",
-      "options": [
-        {
-          "include-filter": "android.appsecurity.cts.OverlayHostTest"
-        }
-      ]
+      "name": "CtsOverlayHostTestCases"
     }
   ],
   "presubmit-large": [
diff --git a/core/java/android/content/pm/AppSearchShortcutInfo.java b/core/java/android/content/pm/AppSearchShortcutInfo.java
index fb41b89..225b3d3 100644
--- a/core/java/android/content/pm/AppSearchShortcutInfo.java
+++ b/core/java/android/content/pm/AppSearchShortcutInfo.java
@@ -445,7 +445,7 @@
     @VisibleForTesting
     public static class Builder extends GenericDocument.Builder<Builder> {
 
-        private List<String> mFlags = new ArrayList<>(1);
+        private final List<String> mFlags = new ArrayList<>(1);
 
         public Builder(String packageName, String id) {
             super(/*namespace=*/ packageName, id, SCHEMA_TYPE);
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 3487e0b..f0b99f1 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -67,7 +67,7 @@
  * &lt;application&gt; tag.
  */
 public class ApplicationInfo extends PackageItemInfo implements Parcelable {
-    private static ForBoolean sForBoolean = Parcelling.Cache.getOrCreate(ForBoolean.class);
+    private static final ForBoolean sForBoolean = Parcelling.Cache.getOrCreate(ForBoolean.class);
     private static final Parcelling.BuiltIn.ForStringSet sForStringSet =
             Parcelling.Cache.getOrCreate(Parcelling.BuiltIn.ForStringSet.class);
 
@@ -1892,7 +1892,7 @@
         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
         private final Collator   sCollator = Collator.getInstance();
         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
-        private PackageManager   mPM;
+        private final PackageManager mPM;
     }
 
     public ApplicationInfo() {
diff --git a/core/java/android/content/pm/Attribution.java b/core/java/android/content/pm/Attribution.java
index 989a5b9..3649249 100644
--- a/core/java/android/content/pm/Attribution.java
+++ b/core/java/android/content/pm/Attribution.java
@@ -33,7 +33,7 @@
     /**
      * The tag of this attribution. From the &lt;manifest&gt; tag's "tag" attribute
      */
-    private @NonNull String mTag;
+    private final @NonNull String mTag;
 
     /**
      * The resource ID of the label of the attribution From the &lt;manifest&gt; tag's "label"
@@ -43,7 +43,7 @@
 
 
 
-    // Code below generated by codegen v1.0.22.
+    // Code below generated by codegen v1.0.23.
     //
     // DO NOT MODIFY!
     // CHECKSTYLE:OFF Generated code
@@ -146,10 +146,10 @@
     };
 
     @DataClass.Generated(
-            time = 1608139558081L,
-            codegenVersion = "1.0.22",
+            time = 1683311736586L,
+            codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/content/pm/Attribution.java",
-            inputSignatures = "private @android.annotation.NonNull java.lang.String mTag\nprivate final @android.annotation.IdRes int mLabel\nclass Attribution extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genHiddenConstructor=true)")
+            inputSignatures = "private final @android.annotation.NonNull java.lang.String mTag\nprivate final @android.annotation.IdRes int mLabel\nclass Attribution extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genHiddenConstructor=true)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/content/pm/BaseParceledListSlice.java b/core/java/android/content/pm/BaseParceledListSlice.java
index 37b1778..cc4e9c8 100644
--- a/core/java/android/content/pm/BaseParceledListSlice.java
+++ b/core/java/android/content/pm/BaseParceledListSlice.java
@@ -42,8 +42,8 @@
  * @hide
  */
 abstract class BaseParceledListSlice<T> implements Parcelable {
-    private static String TAG = "ParceledListSlice";
-    private static boolean DEBUG = false;
+    private static final String TAG = "ParceledListSlice";
+    private static final boolean DEBUG = false;
 
     /*
      * TODO get this number from somewhere else. For now set it to a quarter of
diff --git a/core/java/android/content/pm/CapabilityParams.java b/core/java/android/content/pm/CapabilityParams.java
index 7239bac..60e8123 100644
--- a/core/java/android/content/pm/CapabilityParams.java
+++ b/core/java/android/content/pm/CapabilityParams.java
@@ -172,7 +172,7 @@
         @NonNull
         private final String mKey;
         @NonNull
-        private String mPrimaryValue;
+        private final String mPrimaryValue;
         @NonNull
         private Set<String> mAliases;
 
diff --git a/core/java/android/content/pm/Checksum.java b/core/java/android/content/pm/Checksum.java
index ff17496..2096727 100644
--- a/core/java/android/content/pm/Checksum.java
+++ b/core/java/android/content/pm/Checksum.java
@@ -40,11 +40,11 @@
     /**
      * Root SHA256 hash of a 4K Merkle tree computed over all file bytes.
      * <a href="https://source.android.com/security/apksigning/v4">See APK Signature Scheme V4</a>.
-     * <a href="https://git.kernel.org/pub/scm/fs/fscrypt/fscrypt.git/tree/Documentation/filesystems/fsverity.rst">See fs-verity</a>.
+     * <a href="https://www.kernel.org/doc/html/latest/filesystems/fsverity.html">See fs-verity</a>.
      *
      * Recommended for all new applications.
      * Can be used by kernel to enforce authenticity and integrity of the APK.
-     * <a href="https://git.kernel.org/pub/scm/fs/fscrypt/fscrypt.git/tree/Documentation/filesystems/fsverity.rst#">See fs-verity for details</a>
+     * <a href="https://www.kernel.org/doc/html/latest/filesystems/fsverity.html">See fs-verity for details</a>
      *
      * @see PackageManager#requestChecksums
      */
diff --git a/core/java/android/content/pm/IPackageInstaller.aidl b/core/java/android/content/pm/IPackageInstaller.aidl
index e3016a4..ebe2aa3 100644
--- a/core/java/android/content/pm/IPackageInstaller.aidl
+++ b/core/java/android/content/pm/IPackageInstaller.aidl
@@ -59,6 +59,7 @@
     void installExistingPackage(String packageName, int installFlags, int installReason,
             in IntentSender statusReceiver, int userId, in List<String> whiteListedPermissions);
 
+    @EnforcePermission("INSTALL_PACKAGES")
     void setPermissionsResult(int sessionId, boolean accepted);
 
     void bypassNextStagedInstallerCheck(boolean value);
diff --git a/core/java/android/content/pm/IPackageInstallerSession.aidl b/core/java/android/content/pm/IPackageInstallerSession.aidl
index 081f263..ea69a2b 100644
--- a/core/java/android/content/pm/IPackageInstallerSession.aidl
+++ b/core/java/android/content/pm/IPackageInstallerSession.aidl
@@ -49,8 +49,11 @@
     void seal();
     List<String> fetchPackageNames();
 
+    @EnforcePermission("USE_INSTALLER_V2")
     DataLoaderParamsParcel getDataLoaderParams();
+    @EnforcePermission("USE_INSTALLER_V2")
     void addFile(int location, String name, long lengthBytes, in byte[] metadata, in byte[] signature);
+    @EnforcePermission("USE_INSTALLER_V2")
     void removeFile(int location, String name);
 
     boolean isMultiPackage();
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 47a5db8..7c9ccba 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -55,10 +55,13 @@
 import android.graphics.Bitmap;
 import android.net.Uri;
 import android.os.Bundle;
+import android.os.IRemoteCallback;
 import android.os.ParcelFileDescriptor;
 import android.os.PersistableBundle;
 import android.content.IntentSender;
 
+import java.util.Map;
+
 /**
  *  See {@link PackageManager} for documentation on most of the APIs
  *  here.
@@ -159,6 +162,7 @@
      */
     ParceledListSlice getInstalledPackages(long flags, in int userId);
 
+    @EnforcePermission("GET_APP_METADATA")
     @nullable ParcelFileDescriptor getAppMetadataFd(String packageName,
                 int userId);
 
@@ -282,9 +286,11 @@
     void addCrossProfileIntentFilter(in IntentFilter intentFilter, String ownerPackage,
             int sourceUserId, int targetUserId, int flags);
 
+    @EnforcePermission("INTERACT_ACROSS_USERS_FULL")
     boolean removeCrossProfileIntentFilter(in IntentFilter intentFilter, String ownerPackage,
                 int sourceUserId, int targetUserId, int flags);
 
+    @EnforcePermission("INTERACT_ACROSS_USERS_FULL")
     void clearCrossProfileIntentFilters(int sourceUserId, String ownerPackage);
 
     String[] setDistractingPackageRestrictionsAsUser(in String[] packageNames, int restrictionFlags,
@@ -417,6 +423,7 @@
      * @param observer call back used to notify when
      * the operation is completed
      */
+     @EnforcePermission("CLEAR_APP_CACHE")
      void freeStorageAndNotify(in String volumeUuid, in long freeStorageSize,
              int storageFlags, IPackageDataObserver observer);
 
@@ -441,6 +448,7 @@
      * notify when the operation is completed.May be null
      * to indicate that no call back is desired.
      */
+     @EnforcePermission("CLEAR_APP_CACHE")
      void freeStorage(in String volumeUuid, in long freeStorageSize,
              int storageFlags, in IntentSender pi);
 
@@ -468,6 +476,7 @@
      * files need to be deleted
      * @param observer a callback used to notify when the operation is completed.
      */
+    @EnforcePermission("CLEAR_APP_USER_DATA")
     void clearApplicationUserData(in String packageName, IPackageDataObserver observer, int userId);
 
     /**
@@ -488,15 +497,20 @@
     void getPackageSizeInfo(in String packageName, int userHandle, IPackageStatsObserver observer);
 
     /**
-     * Get a list of shared libraries that are available on the
-     * system.
+     * Get a list of shared libraries that are available on the system.
+     *
+     * @deprecated use getSystemSharedLibraryNamesAndPaths() instead
      */
     @UnsupportedAppUsage
     String[] getSystemSharedLibraryNames();
 
     /**
-     * Get a list of features that are available on the
-     * system.
+     * Get a list of shared library names (key) and paths (values).
+     */
+    Map<String, String> getSystemSharedLibraryNamesAndPaths();
+
+    /**
+     * Get a list of features that are available on the system.
      */
     ParceledListSlice getSystemAvailableFeatures();
 
@@ -579,14 +593,20 @@
     boolean performDexOptSecondary(String packageName,
             String targetCompilerFilter, boolean force);
 
+    @EnforcePermission("MOUNT_UNMOUNT_FILESYSTEMS")
     int getMoveStatus(int moveId);
 
+    @EnforcePermission("MOUNT_UNMOUNT_FILESYSTEMS")
     void registerMoveCallback(in IPackageMoveObserver callback);
+    @EnforcePermission("MOUNT_UNMOUNT_FILESYSTEMS")
     void unregisterMoveCallback(in IPackageMoveObserver callback);
 
+    @EnforcePermission("MOVE_PACKAGE")
     int movePackage(in String packageName, in String volumeUuid);
+    @EnforcePermission("MOVE_PACKAGE")
     int movePrimaryStorage(in String volumeUuid);
 
+    @EnforcePermission("WRITE_SECURE_SETTINGS")
     boolean setInstallLocation(int loc);
     @UnsupportedAppUsage
     int getInstallLocation();
@@ -607,6 +627,7 @@
     ParceledListSlice getIntentFilterVerifications(String packageName);
     ParceledListSlice getAllIntentFilters(String packageName);
 
+    @EnforcePermission("PACKAGE_VERIFICATION_AGENT")
     VerifierDeviceIdentity getVerifierDeviceIdentity();
 
     boolean isFirstBoot();
@@ -616,6 +637,7 @@
     @UnsupportedAppUsage
     boolean isStorageLow();
 
+    @EnforcePermission("MANAGE_USERS")
     @UnsupportedAppUsage
     boolean setApplicationHiddenSettingAsUser(String packageName, boolean hidden, int userId);
     boolean getApplicationHiddenSettingAsUser(String packageName, int userId);
@@ -626,6 +648,7 @@
     @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
     IPackageInstaller getPackageInstaller();
 
+    @EnforcePermission("DELETE_PACKAGES")
     boolean setBlockUninstallForUser(String packageName, boolean blockUninstall, int userId);
     @UnsupportedAppUsage
     boolean getBlockUninstallForUser(String packageName, int userId);
@@ -651,6 +674,7 @@
      * Sets whether or not an update is available. Ostensibly for instant apps
      * to force exteranl resolution.
      */
+    @EnforcePermission("INSTALL_PACKAGES")
     void setUpdateAvailable(String packageName, boolean updateAvaialble);
 
     @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
@@ -678,6 +702,7 @@
 
     ComponentName getInstantAppInstallerComponent();
 
+    @EnforcePermission("ACCESS_INSTANT_APPS")
     String getInstantAppAndroidId(String packageName, int userId);
 
     IArtManager getArtManager();
@@ -770,12 +795,18 @@
 
     void setSplashScreenTheme(String packageName, String themeName, int userId);
 
+    int getUserMinAspectRatio(String packageName, int userId);
+
+    @EnforcePermission("INSTALL_PACKAGES")
+    void setUserMinAspectRatio(String packageName, int userId, int aspectRatio);
+
     List<String> getMimeGroup(String packageName, String group);
 
     boolean isAutoRevokeWhitelisted(String packageName);
 
     void makeProviderVisible(int recipientAppId, String visibleAuthority);
 
+    @EnforcePermission("MAKE_UID_VISIBLE")
     @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
             + ".permission.MAKE_UID_VISIBLE)")
     void makeUidVisible(int recipientAppId, int visibleUid);
@@ -793,4 +824,8 @@
     boolean[] canPackageQuery(String sourcePackageName, in String[] targetPackageNames, int userId);
 
     boolean waitForHandler(long timeoutMillis, boolean forBackgroundHandler);
+
+    void registerPackageMonitorCallback(IRemoteCallback callback, int userId);
+
+    void unregisterPackageMonitorCallback(IRemoteCallback callback);
 }
diff --git a/core/java/android/content/pm/IncrementalStatesInfo.java b/core/java/android/content/pm/IncrementalStatesInfo.java
index f15afdf..7098249 100644
--- a/core/java/android/content/pm/IncrementalStatesInfo.java
+++ b/core/java/android/content/pm/IncrementalStatesInfo.java
@@ -24,8 +24,8 @@
  * @hide
  */
 public class IncrementalStatesInfo implements Parcelable {
-    private boolean mIsLoading;
-    private float mProgress;
+    private final boolean mIsLoading;
+    private final float mProgress;
 
     private long mLoadingCompletedTime;
 
diff --git a/core/java/android/content/pm/KeySet.java b/core/java/android/content/pm/KeySet.java
index fd459e6..3da5bff 100644
--- a/core/java/android/content/pm/KeySet.java
+++ b/core/java/android/content/pm/KeySet.java
@@ -29,7 +29,7 @@
  */
 public class KeySet implements Parcelable {
 
-    private IBinder token;
+    private final IBinder token;
 
     /** @hide */
     public KeySet(IBinder token) {
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index d6592d5..3165e29 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -1737,7 +1737,8 @@
         mCallbacks.add(toAdd);
     }
 
-    private IOnAppsChangedListener.Stub mAppsChangedListener = new IOnAppsChangedListener.Stub() {
+    private final IOnAppsChangedListener.Stub mAppsChangedListener =
+            new IOnAppsChangedListener.Stub() {
 
         @Override
         public void onPackageRemoved(UserHandle user, String packageName)
@@ -1872,7 +1873,7 @@
         private static final int MSG_SHORTCUT_CHANGED = 8;
         private static final int MSG_LOADING_PROGRESS_CHANGED = 9;
 
-        private LauncherApps.Callback mCallback;
+        private final LauncherApps.Callback mCallback;
 
         private static class CallbackInfo {
             String[] packageNames;
diff --git a/core/java/android/content/pm/PackageInfo.java b/core/java/android/content/pm/PackageInfo.java
index 50be5c4..63c11b7 100644
--- a/core/java/android/content/pm/PackageInfo.java
+++ b/core/java/android/content/pm/PackageInfo.java
@@ -16,6 +16,7 @@
 
 package android.content.pm;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Build;
@@ -31,11 +32,13 @@
      * The name of this package.  From the &lt;manifest&gt; tag's "name"
      * attribute.
      */
+    @NonNull
     public String packageName;
 
     /**
      * The names of any installed split APKs for this package.
      */
+    @NonNull
     public String[] splitNames;
 
     /**
@@ -93,6 +96,7 @@
      * tag's {@link android.R.styleable#AndroidManifest_versionName versionName}
      * attribute, or null if there was none.
      */
+    @Nullable
     public String versionName;
 
     /**
@@ -109,6 +113,7 @@
      * {@link android.R.styleable#AndroidManifest_revisionCode revisionCode}
      * attribute. Indexes are a 1:1 mapping against {@link #splitNames}.
      */
+    @NonNull
     public int[] splitRevisionCodes;
 
     /**
@@ -116,21 +121,23 @@
      * tag's {@link android.R.styleable#AndroidManifest_sharedUserId sharedUserId}
      * attribute.
      */
+    @Nullable
     public String sharedUserId;
-    
+
     /**
      * The shared user ID label of this package, as specified by the &lt;manifest&gt;
      * tag's {@link android.R.styleable#AndroidManifest_sharedUserLabel sharedUserLabel}
      * attribute.
      */
     public int sharedUserLabel;
-    
+
     /**
      * Information collected from the &lt;application&gt; tag, or null if
      * there was none.
      */
+    @Nullable
     public ApplicationInfo applicationInfo;
-    
+
     /**
      * The time at which the app was first installed.  Units are as
      * per {@link System#currentTimeMillis()}.
@@ -147,6 +154,7 @@
      * All kernel group-IDs that have been assigned to this package.
      * This is only filled in if the flag {@link PackageManager#GET_GIDS} was set.
      */
+    @Nullable
     public int[] gids;
 
     /**
@@ -155,6 +163,7 @@
      * or null if there were none.  This is only filled in if the flag
      * {@link PackageManager#GET_ACTIVITIES} was set.
      */
+    @Nullable
     public ActivityInfo[] activities;
 
     /**
@@ -163,6 +172,7 @@
      * or null if there were none.  This is only filled in if the flag
      * {@link PackageManager#GET_RECEIVERS} was set.
      */
+    @Nullable
     public ActivityInfo[] receivers;
 
     /**
@@ -171,6 +181,7 @@
      * or null if there were none.  This is only filled in if the flag
      * {@link PackageManager#GET_SERVICES} was set.
      */
+    @Nullable
     public ServiceInfo[] services;
 
     /**
@@ -179,6 +190,7 @@
      * or null if there were none.  This is only filled in if the flag
      * {@link PackageManager#GET_PROVIDERS} was set.
      */
+    @Nullable
     public ProviderInfo[] providers;
 
     /**
@@ -187,6 +199,7 @@
      * or null if there were none.  This is only filled in if the flag
      * {@link PackageManager#GET_INSTRUMENTATION} was set.
      */
+    @Nullable
     public InstrumentationInfo[] instrumentation;
 
     /**
@@ -195,6 +208,7 @@
      * or null if there were none.  This is only filled in if the flag
      * {@link PackageManager#GET_PERMISSIONS} was set.
      */
+    @Nullable
     public PermissionInfo[] permissions;
 
     /**
@@ -205,6 +219,7 @@
      * all permissions requested, even those that were not granted or known
      * by the system at install time.
      */
+    @Nullable
     public String[] requestedPermissions;
 
     /**
@@ -216,6 +231,7 @@
      * the flags {@link #REQUESTED_PERMISSION_GRANTED} and
      * {@link #REQUESTED_PERMISSION_NEVER_FOR_LOCATION} set as appropriate.
      */
+    @Nullable
     public int[] requestedPermissionsFlags;
 
     /**
@@ -224,7 +240,8 @@
      * is only filled if the flag {@link PackageManager#GET_ATTRIBUTIONS_LONG} was set.
      */
     @SuppressWarnings({"ArrayReturn", "NullableCollection"})
-    public @Nullable Attribution[] attributions;
+    @Nullable
+    public Attribution[] attributions;
 
     /**
      * Flag for {@link #requestedPermissionsFlags}: the requested permission
@@ -281,6 +298,7 @@
      * @deprecated use {@code signingInfo} instead
      */
     @Deprecated
+    @Nullable
     public Signature[] signatures;
 
     /**
@@ -292,6 +310,7 @@
      * Use this field instead of the deprecated {@code signatures} field.
      * See {@link SigningInfo} for more information on its contents.
      */
+    @Nullable
     public SigningInfo signingInfo;
 
     /**
@@ -301,6 +320,7 @@
      * or null if there were none. This is only filled in if the flag
      * {@link PackageManager#GET_CONFIGURATIONS} was set.
      */
+    @Nullable
     public ConfigurationInfo[] configPreferences;
 
     /**
@@ -308,6 +328,7 @@
      *
      * @see FeatureInfo#FLAG_REQUIRED
      */
+    @Nullable
     public FeatureInfo[] reqFeatures;
 
     /**
@@ -318,6 +339,7 @@
      *
      * @see FeatureInfo#FLAG_REQUIRED
      */
+    @Nullable
     public FeatureGroupInfo[] featureGroups;
 
     /**
@@ -386,12 +408,14 @@
      * The restricted account authenticator type that is used by this application.
      * @hide
      */
+    @Nullable
     public String restrictedAccountType;
 
     /**
      * The required account type without which this application will not function.
      * @hide
      */
+    @Nullable
     public String requiredAccountType;
 
     /**
@@ -401,6 +425,7 @@
      * @hide
      */
     @UnsupportedAppUsage
+    @Nullable
     public String overlayTarget;
 
     /**
@@ -409,6 +434,7 @@
      * Overlayable name defined within the target package, or null.
      * @hide
      */
+    @Nullable
     public String targetOverlayableName;
 
     /**
@@ -416,6 +442,7 @@
      *
      * @hide
      */
+    @Nullable
     public String overlayCategory;
 
     /** @hide */
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 4b883cd..b43639a 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -46,10 +46,12 @@
 import android.app.ActivityManager;
 import android.app.ActivityThread;
 import android.app.AppGlobals;
+import android.app.PendingIntent;
 import android.compat.annotation.UnsupportedAppUsage;
-import android.content.IIntentReceiver;
-import android.content.IIntentSender;
+import android.content.BroadcastReceiver;
+import android.content.Context;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.IntentSender;
 import android.content.pm.PackageManager.DeleteFlags;
 import android.content.pm.PackageManager.InstallReason;
@@ -62,11 +64,9 @@
 import android.icu.util.ULocale;
 import android.net.Uri;
 import android.os.Build;
-import android.os.Bundle;
 import android.os.FileBridge;
 import android.os.Handler;
 import android.os.HandlerExecutor;
-import android.os.IBinder;
 import android.os.Parcel;
 import android.os.ParcelFileDescriptor;
 import android.os.Parcelable;
@@ -148,6 +148,9 @@
 public class PackageInstaller {
     private static final String TAG = "PackageInstaller";
 
+    private static final String ACTION_WAIT_INSTALL_CONSTRAINTS =
+            "android.content.pm.action.WAIT_INSTALL_CONSTRAINTS";
+
     /** {@hide} */
     public static final boolean ENABLE_REVOCABLE_FD =
             SystemProperties.getBoolean("fw.revocable_fd", false);
@@ -1066,6 +1069,9 @@
      * @param timeoutMillis The maximum time to wait, in milliseconds until the
      *                      constraints are satisfied. The caller will be notified via
      *                      {@code statusReceiver} if timeout happens before commit.
+     * @throws IllegalArgumentException if the {@code statusReceiver} from an immutable
+     *             {@link android.app.PendingIntent} when caller has a target SDK of API
+     *             {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM} or above.
      */
     public void commitSessionAfterInstallConstraintsAreMet(int sessionId,
             @NonNull IntentSender statusReceiver, @NonNull InstallConstraints constraints,
@@ -1074,37 +1080,71 @@
             var session = mInstaller.openSession(sessionId);
             session.seal();
             var packageNames = session.fetchPackageNames();
-            var intentSender = new IntentSender((IIntentSender) new IIntentSender.Stub() {
-                @Override
-                public void send(int code, Intent intent, String resolvedType,
-                        IBinder allowlistToken, IIntentReceiver finishedReceiver,
-                        String requiredPermission, Bundle options)  {
-                    var result = intent.getParcelableExtra(
-                            PackageInstaller.EXTRA_INSTALL_CONSTRAINTS_RESULT,
-                            InstallConstraintsResult.class);
-                    try {
-                        if (result.areAllConstraintsSatisfied()) {
-                            session.commit(statusReceiver, false);
-                        } else {
-                            // timeout
-                            final Intent fillIn = new Intent();
-                            fillIn.putExtra(PackageInstaller.EXTRA_SESSION_ID, sessionId);
-                            fillIn.putExtra(PackageInstaller.EXTRA_STATUS, STATUS_FAILURE_TIMEOUT);
-                            fillIn.putExtra(PackageInstaller.EXTRA_STATUS_MESSAGE,
-                                    "Install constraints not satisfied within timeout");
-                            statusReceiver.sendIntent(
-                                    ActivityThread.currentApplication(), 0, fillIn, null, null);
-                        }
-                    } catch (Exception ignore) {
-                    }
-                }
-            });
-            waitForInstallConstraints(packageNames, constraints, intentSender, timeoutMillis);
+            var context = ActivityThread.currentApplication();
+            var localIntentSender = new LocalIntentSender(context, sessionId, session,
+                    statusReceiver);
+            waitForInstallConstraints(packageNames, constraints,
+                    localIntentSender.getIntentSender(), timeoutMillis);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
     }
 
+    private static final class LocalIntentSender extends BroadcastReceiver {
+
+        private final Context mContext;
+        private final IntentSender mStatusReceiver;
+        private final int mSessionId;
+        private final IPackageInstallerSession mSession;
+
+        LocalIntentSender(Context context, int sessionId, IPackageInstallerSession session,
+                IntentSender statusReceiver) {
+            mContext = context;
+            mSessionId = sessionId;
+            mSession = session;
+            mStatusReceiver = statusReceiver;
+        }
+
+        private IntentSender getIntentSender() {
+            Intent intent = new Intent(ACTION_WAIT_INSTALL_CONSTRAINTS).setPackage(
+                    mContext.getPackageName());
+            mContext.registerReceiver(this, new IntentFilter(ACTION_WAIT_INSTALL_CONSTRAINTS),
+                    Context.RECEIVER_EXPORTED);
+            PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0, intent,
+                    PendingIntent.FLAG_MUTABLE);
+            return pendingIntent.getIntentSender();
+        }
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            InstallConstraintsResult result = intent.getParcelableExtra(
+                    PackageInstaller.EXTRA_INSTALL_CONSTRAINTS_RESULT,
+                    InstallConstraintsResult.class);
+            try {
+                if (result.areAllConstraintsSatisfied()) {
+                    mSession.commit(mStatusReceiver, false);
+                } else {
+                    // timeout
+                    final Intent fillIn = new Intent();
+                    fillIn.putExtra(PackageInstaller.EXTRA_SESSION_ID, mSessionId);
+                    fillIn.putExtra(PackageInstaller.EXTRA_STATUS, STATUS_FAILURE_TIMEOUT);
+                    fillIn.putExtra(PackageInstaller.EXTRA_STATUS_MESSAGE,
+                            "Install constraints not satisfied within timeout");
+                    mStatusReceiver.sendIntent(ActivityThread.currentApplication(), 0, fillIn, null,
+                            null);
+                }
+            } catch (Exception ignore) {
+                // no-op
+            } finally {
+                unregisterReceiver();
+            }
+        }
+
+        private void unregisterReceiver() {
+            mContext.unregisterReceiver(this);
+        }
+    }
+
     /**
      * Events for observing session lifecycle.
      * <p>
@@ -1740,6 +1780,9 @@
          *
          * @throws SecurityException if streams opened through
          *             {@link #openWrite(String, long, long)} are still open.
+         * @throws IllegalArgumentException if the {@code statusReceiver} from an immutable
+         *             {@link android.app.PendingIntent} when caller has a target SDK of API
+         *             version {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM} or above.
          *
          * @see android.app.admin.DevicePolicyManager
          * @see #requestUserPreapproval
@@ -1768,6 +1811,9 @@
          * @param statusReceiver Called when the state of the session changes. Intents
          *                       sent to this receiver contain {@link #EXTRA_STATUS}. Refer to the
          *                       individual status codes on how to handle them.
+         * @throws IllegalArgumentException if the {@code statusReceiver} from an immutable
+         *             {@link android.app.PendingIntent} when caller has a target SDK of API
+         *             {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM} or above.
          *
          * @hide
          */
@@ -2099,6 +2145,7 @@
      * or the parser isn't able to parse the supplied source(s).
      * @hide
      */
+    @SystemApi
     @NonNull
     public InstallInfo readInstallInfo(@NonNull ParcelFileDescriptor pfd,
             @Nullable String debugPathName, int flags) throws PackageParsingException {
@@ -2172,7 +2219,6 @@
          * Includes the size of the raw APKs, possibly unpacked resources, raw dex metadata files,
          * and all relevant native code.
          * @throws IOException when size of native binaries cannot be calculated.
-         * @hide
          */
         public long calculateInstalledSize(@NonNull SessionParams params,
                 @NonNull ParcelFileDescriptor pfd) throws IOException {
@@ -3610,6 +3656,7 @@
          *
          * @hide
          */
+        @SystemApi
         @RequiresPermission(Manifest.permission.READ_INSTALLED_SESSION_PATHS)
         public @Nullable String getResolvedBaseApkPath() {
             return resolvedBaseCodePath;
@@ -4085,7 +4132,8 @@
          * @param label
          *   The label representing the app to be installed.
          * @param locale
-         *   The locale of the app label being used.
+         *   The locale is used to get the app label from the APKs (includes the base APK and
+         *   split APKs) related to the package to be installed.
          * @param packageName
          *   The package name of the app to be installed.
          * @hide
@@ -4192,7 +4240,10 @@
             }
 
             /**
-             * The locale of the app label being used.
+             * The locale is used to get the app label from the APKs (includes the base APK and
+             * split APKs) related to the package to be installed. The caller needs to make sure
+             * the app label is consistent with the app label of {@link PreapprovalDetails} when
+             * validating the installation. Otherwise, the pre-approval install session will fail.
              */
             public @NonNull Builder setLocale(@NonNull ULocale value) {
                 checkNotUsed();
diff --git a/core/java/android/content/pm/PackageItemInfo.java b/core/java/android/content/pm/PackageItemInfo.java
index 2bac066..bb978e0 100644
--- a/core/java/android/content/pm/PackageItemInfo.java
+++ b/core/java/android/content/pm/PackageItemInfo.java
@@ -502,6 +502,6 @@
         }
 
         private final Collator   sCollator = Collator.getInstance();
-        private PackageManager   mPM;
+        private final PackageManager mPM;
     }
 }
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 8fafb18..bef023e 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -65,6 +65,7 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.IRemoteCallback;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.PersistableBundle;
@@ -313,7 +314,7 @@
 
         /**
          * Returns the classname of the component where this property was defined.
-         * <p>If the property was defined within and &lt;application&gt; tag, retutrns
+         * <p>If the property was defined within and &lt;application&gt; tag, returns
          * {@code null}
          */
         @Nullable public String getClassName() {
@@ -2342,6 +2343,64 @@
      */
     public static final int INSTALL_FAILED_SHARED_LIBRARY_BAD_CERTIFICATE_DIGEST = -130;
 
+    /**
+     * App minimum aspect ratio set by the user which will override app-defined aspect ratio.
+     *
+     * @hide
+     */
+    @IntDef(prefix = { "USER_MIN_ASPECT_RATIO_" }, value = {
+            USER_MIN_ASPECT_RATIO_UNSET,
+            USER_MIN_ASPECT_RATIO_SPLIT_SCREEN,
+            USER_MIN_ASPECT_RATIO_DISPLAY_SIZE,
+            USER_MIN_ASPECT_RATIO_4_3,
+            USER_MIN_ASPECT_RATIO_16_9,
+            USER_MIN_ASPECT_RATIO_3_2,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface UserMinAspectRatio {}
+
+    /**
+     * No aspect ratio override has been set by user.
+     *
+     * @hide
+     */
+    public static final int USER_MIN_ASPECT_RATIO_UNSET = 0;
+
+    /**
+     * Aspect ratio override code: user forces app to split screen aspect ratio. This is adjusted to
+     * half of the screen without the split screen divider.
+     *
+     * @hide
+     */
+    public static final int USER_MIN_ASPECT_RATIO_SPLIT_SCREEN = 1;
+
+    /**
+     * Aspect ratio override code: user forces app to the aspect ratio of the device display size.
+     * This will be the portrait aspect ratio of the device if the app is portrait or the landscape
+     * aspect ratio of the device if the app is landscape.
+     *
+     * @hide
+     */
+    public static final int USER_MIN_ASPECT_RATIO_DISPLAY_SIZE = 2;
+
+    /**
+     * Aspect ratio override code: user forces app to 4:3 min aspect ratio
+     * @hide
+     */
+    public static final int USER_MIN_ASPECT_RATIO_4_3 = 3;
+
+    /**
+     * Aspect ratio override code: user forces app to 16:9 min aspect ratio
+     * @hide
+     */
+    public static final int USER_MIN_ASPECT_RATIO_16_9 = 4;
+
+    /**
+     * Aspect ratio override code: user forces app to 3:2 min aspect ratio
+     * @hide
+     */
+    public static final int USER_MIN_ASPECT_RATIO_3_2 = 5;
+
     /** @hide */
     @IntDef(flag = true, prefix = { "DELETE_" }, value = {
             DELETE_KEEP_DATA,
@@ -2576,6 +2635,13 @@
     public static final String EXTRA_MOVE_ID = "android.content.pm.extra.MOVE_ID";
 
     /**
+     * Extra field name for notifying package change event. Currently, it is used by PackageMonitor.
+     * @hide
+     */
+    public static final String EXTRA_PACKAGE_MONITOR_CALLBACK_RESULT =
+            "android.content.pm.extra.EXTRA_PACKAGE_MONITOR_CALLBACK_RESULT";
+
+    /**
      * Usable by the required verifier as the {@code verificationCode} argument
      * for {@link PackageManager#verifyPendingInstall} to indicate that it will
      * allow the installation to proceed without any of the optional verifiers
@@ -4612,6 +4678,19 @@
             "android.content.pm.extra.REQUEST_PERMISSIONS_NAMES";
 
     /**
+     * The deviceId for which the permissions are requested, {@link Context#DEVICE_ID_DEFAULT}
+     * is the default device ID.
+     * <p>
+     * <strong>Type:</strong> int
+     * </p>
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final String EXTRA_REQUEST_PERMISSIONS_DEVICE_ID =
+            "android.content.pm.extra.REQUEST_PERMISSIONS_DEVICE_ID";
+
+    /**
      * The results from the permissions request.
      * <p>
      * <strong>Type:</strong> int[] of #PermissionResult
@@ -6592,7 +6671,7 @@
     @UnsupportedAppUsage
     public Intent buildRequestPermissionsIntent(@NonNull String[] permissions) {
         if (ArrayUtils.isEmpty(permissions)) {
-           throw new IllegalArgumentException("permission cannot be null or empty");
+            throw new IllegalArgumentException("permission cannot be null or empty");
         }
         Intent intent = new Intent(ACTION_REQUEST_PERMISSIONS);
         intent.putExtra(EXTRA_REQUEST_PERMISSIONS_NAMES, permissions);
@@ -9487,7 +9566,8 @@
      * {@link PersistableBundle} objects to be shared with the apps being suspended and the
      * 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.
+     * <p>The caller must hold {@link Manifest.permission#SUSPEND_APPS} to use this API except for
+     * device owner and profile owner.
      *
      * @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
@@ -9513,7 +9593,7 @@
      * @hide
      */
     @SystemApi
-    @RequiresPermission(Manifest.permission.SUSPEND_APPS)
+    @RequiresPermission(value=Manifest.permission.SUSPEND_APPS, conditional=true)
     @Nullable
     public String[] setPackagesSuspended(@Nullable String[] packageNames, boolean suspended,
             @Nullable PersistableBundle appExtras, @Nullable PersistableBundle launcherExtras,
@@ -11025,4 +11105,28 @@
         throw new UnsupportedOperationException(
                 "relinquishUpdateOwnership not implemented in subclass");
     }
+
+    /**
+     * Register for notifications of package changes such as install, removal and other events.
+     *
+     * @param callback the callback to register for receiving the change events
+     * @param userId The id of registered user
+     * @hide
+     */
+    public void registerPackageMonitorCallback(@NonNull IRemoteCallback callback, int userId) {
+        throw new UnsupportedOperationException(
+                "registerPackageMonitorCallback not implemented in subclass");
+    }
+
+    /**
+     * Unregister for notifications of package changes such as install, removal and other events.
+     *
+     * @param callback the callback to unregister for receiving the change events
+     * @see #registerPackageMonitorCallback(IRemoteCallback, int)
+     * @hide
+     */
+    public void unregisterPackageMonitorCallback(@NonNull IRemoteCallback callback) {
+        throw new UnsupportedOperationException(
+                "unregisterPackageMonitorCallback not implemented in subclass");
+    }
 }
diff --git a/core/java/android/content/pm/PermissionInfo.java b/core/java/android/content/pm/PermissionInfo.java
index 4fa80d7..cdda12e 100644
--- a/core/java/android/content/pm/PermissionInfo.java
+++ b/core/java/android/content/pm/PermissionInfo.java
@@ -489,7 +489,8 @@
      */
     public @Nullable CharSequence nonLocalizedDescription;
 
-    private static ForStringSet sForStringSet = Parcelling.Cache.getOrCreate(ForStringSet.class);
+    private static final ForStringSet sForStringSet =
+            Parcelling.Cache.getOrCreate(ForStringSet.class);
 
     /**
      * A {@link Set} of trusted signing certificate digests. If this permission has the {@link
@@ -610,6 +611,40 @@
         return protLevel.toString();
     }
 
+    /** @hide */
+    public static @NonNull String flagsToString(@Flags int flags) {
+        StringBuilder sb = new StringBuilder("[");
+        while (flags != 0) {
+            final int flag = 1 << Integer.numberOfTrailingZeros(flags);
+            flags &= ~flag;
+            switch (flag) {
+                case PermissionInfo.FLAG_COSTS_MONEY:
+                    sb.append("costsMoney");
+                    break;
+                case PermissionInfo.FLAG_REMOVED:
+                    sb.append("removed");
+                    break;
+                case PermissionInfo.FLAG_HARD_RESTRICTED:
+                    sb.append("hardRestricted");
+                    break;
+                case PermissionInfo.FLAG_SOFT_RESTRICTED:
+                    sb.append("softRestricted");
+                    break;
+                case PermissionInfo.FLAG_IMMUTABLY_RESTRICTED:
+                    sb.append("immutablyRestricted");
+                    break;
+                case PermissionInfo.FLAG_INSTALLED:
+                    sb.append("installed");
+                    break;
+                default: sb.append(flag);
+            }
+            if (flags != 0) {
+                sb.append("|");
+            }
+        }
+        return sb.append("]").toString();
+    }
+
     /**
      * @hide
      */
diff --git a/core/java/android/content/pm/ResolveInfo.java b/core/java/android/content/pm/ResolveInfo.java
index f900440..36c03fd 100644
--- a/core/java/android/content/pm/ResolveInfo.java
+++ b/core/java/android/content/pm/ResolveInfo.java
@@ -16,6 +16,7 @@
 
 package android.content.pm;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.compat.annotation.UnsupportedAppUsage;
@@ -33,6 +34,7 @@
 
 import java.text.Collator;
 import java.util.Comparator;
+import java.util.Objects;
 
 /**
  * Information that is returned from resolving an intent
@@ -224,10 +226,17 @@
      * @return Returns a CharSequence containing the resolutions's label.  If the
      * item does not have a label, its name is returned.
      */
-    public CharSequence loadLabel(PackageManager pm) {
+    @NonNull
+    public CharSequence loadLabel(@NonNull PackageManager pm) {
         if (nonLocalizedLabel != null) {
             return nonLocalizedLabel;
         }
+
+        // In order to not change the original behavior. To add null check here to support backward
+        // compatible. If nonLocalizedLabel is not null, we also return nonLocalizedLabel even if pm
+        // is null.
+        Objects.requireNonNull(pm);
+
         CharSequence label;
         if (resolvePackageName != null && labelRes != 0) {
             label = pm.getText(resolvePackageName, labelRes, null);
@@ -577,6 +586,6 @@
         }
 
         private final Collator   mCollator = Collator.getInstance();
-        private PackageManager   mPM;
+        private final PackageManager mPM;
     }
 }
diff --git a/core/java/android/content/pm/SigningDetails.java b/core/java/android/content/pm/SigningDetails.java
index 1e659b7..af2649f 100644
--- a/core/java/android/content/pm/SigningDetails.java
+++ b/core/java/android/content/pm/SigningDetails.java
@@ -867,10 +867,12 @@
             return false;
         }
         // The capabilities for the past signing certs must match as well.
-        for (int i = 0; i < mPastSigningCertificates.length; i++) {
-            if (mPastSigningCertificates[i].getFlags()
-                    != that.mPastSigningCertificates[i].getFlags()) {
-                return false;
+        if (mPastSigningCertificates != null) {
+            for (int i = 0; i < mPastSigningCertificates.length; i++) {
+                if (mPastSigningCertificates[i].getFlags()
+                        != that.mPastSigningCertificates[i].getFlags()) {
+                    return false;
+                }
             }
         }
         return true;
diff --git a/core/java/android/content/pm/TEST_MAPPING b/core/java/android/content/pm/TEST_MAPPING
index 3ffbe1d..3364b7a 100644
--- a/core/java/android/content/pm/TEST_MAPPING
+++ b/core/java/android/content/pm/TEST_MAPPING
@@ -97,6 +97,23 @@
                     "include-filter":"android.incrementalinstall.cts.IncrementalFeatureTest"
                 }
             ]
+        },
+        {
+            "name":"CtsPackageManagerHostTestCases",
+            "options":[
+                {
+                    "include-annotation":"android.platform.test.annotations.Presubmit"
+                },
+                {
+                    "exclude-annotation":"android.platform.test.annotations.Postsubmit"
+                },
+                {
+                    "exclude-annotation":"androidx.test.filters.FlakyTest"
+                },
+                {
+                    "exclude-annotation":"org.junit.Ignore"
+                }
+            ]
         }
     ],
     "presubmit-large":[
@@ -135,23 +152,6 @@
                     "exclude-annotation":"org.junit.Ignore"
                 }
             ]
-        },
-        {
-            "name":"CtsAppSecurityHostTestCases",
-            "options":[
-                {
-                    "include-annotation":"android.platform.test.annotations.Presubmit"
-                },
-                {
-                    "exclude-annotation":"android.platform.test.annotations.Postsubmit"
-                },
-                {
-                    "exclude-annotation":"androidx.test.filters.FlakyTest"
-                },
-                {
-                    "exclude-annotation":"org.junit.Ignore"
-                }
-            ]
         }
     ],
     "postsubmit":[
diff --git a/core/java/android/content/pm/UserInfo.java b/core/java/android/content/pm/UserInfo.java
index 23ba336..9f4459d 100644
--- a/core/java/android/content/pm/UserInfo.java
+++ b/core/java/android/content/pm/UserInfo.java
@@ -386,6 +386,14 @@
         return UserManager.isUserTypeCloneProfile(userType);
     }
 
+    public boolean isCommunalProfile() {
+        return UserManager.isUserTypeCommunalProfile(userType);
+    }
+
+    public boolean isPrivateProfile() {
+        return UserManager.isUserTypePrivateProfile(userType);
+    }
+
     @UnsupportedAppUsage
     public boolean isEnabled() {
         return (flags & FLAG_DISABLED) != FLAG_DISABLED;
diff --git a/core/java/android/content/pm/UserProperties.java b/core/java/android/content/pm/UserProperties.java
index 2669040..96af2b6 100644
--- a/core/java/android/content/pm/UserProperties.java
+++ b/core/java/android/content/pm/UserProperties.java
@@ -62,6 +62,7 @@
     private static final String ATTR_CREDENTIAL_SHAREABLE_WITH_PARENT =
             "credentialShareableWithParent";
     private static final String ATTR_DELETE_APP_WITH_PARENT = "deleteAppWithParent";
+    private static final String ATTR_ALWAYS_VISIBLE = "alwaysVisible";
 
     /** Index values of each property (to indicate whether they are present in this object). */
     @IntDef(prefix = "INDEX_", value = {
@@ -76,6 +77,7 @@
             INDEX_MEDIA_SHARED_WITH_PARENT,
             INDEX_CREDENTIAL_SHAREABLE_WITH_PARENT,
             INDEX_DELETE_APP_WITH_PARENT,
+            INDEX_ALWAYS_VISIBLE,
     })
     @Retention(RetentionPolicy.SOURCE)
     private @interface PropertyIndex {
@@ -91,6 +93,7 @@
     private static final int INDEX_MEDIA_SHARED_WITH_PARENT = 8;
     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;
     /** A bit set, mapping each PropertyIndex to whether it is present (1) or absent (0). */
     private long mPropertiesPresent = 0;
 
@@ -316,6 +319,7 @@
                     orig.getCrossProfileIntentFilterAccessControl());
             setCrossProfileIntentResolutionStrategy(orig.getCrossProfileIntentResolutionStrategy());
             setDeleteAppWithParent(orig.getDeleteAppWithParent());
+            setAlwaysVisible(orig.getAlwaysVisible());
         }
         if (hasManagePermission) {
             // Add items that require MANAGE_USERS or stronger.
@@ -440,6 +444,24 @@
     private boolean mDeleteAppWithParent;
 
     /**
+     * Returns whether the user should always
+     * be {@link android.os.UserManager#isUserVisible() visible}.
+     * The intended usage is for the Communal Profile, which is running and accessible at all times.
+     * @hide
+     */
+    public boolean getAlwaysVisible() {
+        if (isPresent(INDEX_ALWAYS_VISIBLE)) return mAlwaysVisible;
+        if (mDefaultProperties != null) return mDefaultProperties.mAlwaysVisible;
+        throw new SecurityException("You don't have permission to query alwaysVisible");
+    }
+    /** @hide */
+    public void setAlwaysVisible(boolean val) {
+        this.mAlwaysVisible = val;
+        setPresent(INDEX_ALWAYS_VISIBLE);
+    }
+    private boolean mAlwaysVisible;
+
+    /**
      * Return whether, and how, select user restrictions or device policies should be inherited
      * from other user.
      *
@@ -632,6 +654,7 @@
                 + ", mMediaSharedWithParent=" + isMediaSharedWithParent()
                 + ", mCredentialShareableWithParent=" + isCredentialShareableWithParent()
                 + ", mDeleteAppWithParent=" + getDeleteAppWithParent()
+                + ", mAlwaysVisible=" + getAlwaysVisible()
                 + "}";
     }
 
@@ -658,6 +681,7 @@
         pw.println(prefix + "    mCredentialShareableWithParent="
                 + isCredentialShareableWithParent());
         pw.println(prefix + "    mDeleteAppWithParent=" + getDeleteAppWithParent());
+        pw.println(prefix + "    mAlwaysVisible=" + getAlwaysVisible());
     }
 
     /**
@@ -724,6 +748,9 @@
                 case ATTR_DELETE_APP_WITH_PARENT:
                     setDeleteAppWithParent(parser.getAttributeBoolean(i));
                     break;
+                case ATTR_ALWAYS_VISIBLE:
+                    setAlwaysVisible(parser.getAttributeBoolean(i));
+                    break;
                 default:
                     Slog.w(LOG_TAG, "Skipping unknown property " + attributeName);
             }
@@ -783,6 +810,10 @@
             serializer.attributeBoolean(null, ATTR_DELETE_APP_WITH_PARENT,
                     mDeleteAppWithParent);
         }
+        if (isPresent(INDEX_ALWAYS_VISIBLE)) {
+            serializer.attributeBoolean(null, ATTR_ALWAYS_VISIBLE,
+                    mAlwaysVisible);
+        }
     }
 
     // For use only with an object that has already had any permission-lacking fields stripped out.
@@ -800,6 +831,7 @@
         dest.writeBoolean(mMediaSharedWithParent);
         dest.writeBoolean(mCredentialShareableWithParent);
         dest.writeBoolean(mDeleteAppWithParent);
+        dest.writeBoolean(mAlwaysVisible);
     }
 
     /**
@@ -821,6 +853,7 @@
         mMediaSharedWithParent = source.readBoolean();
         mCredentialShareableWithParent = source.readBoolean();
         mDeleteAppWithParent = source.readBoolean();
+        mAlwaysVisible = source.readBoolean();
     }
 
     @Override
@@ -859,6 +892,7 @@
         private boolean mMediaSharedWithParent = false;
         private boolean mCredentialShareableWithParent = false;
         private boolean mDeleteAppWithParent = false;
+        private boolean mAlwaysVisible = false;
 
         public Builder setShowInLauncher(@ShowInLauncher int showInLauncher) {
             mShowInLauncher = showInLauncher;
@@ -926,6 +960,12 @@
             return this;
         }
 
+        /** Sets the value for {@link #mAlwaysVisible}*/
+        public Builder setAlwaysVisible(boolean alwaysVisible) {
+            mAlwaysVisible = alwaysVisible;
+            return this;
+        }
+
         /** Builds a UserProperties object with *all* values populated. */
         public UserProperties build() {
             return new UserProperties(
@@ -939,7 +979,8 @@
                     mCrossProfileIntentResolutionStrategy,
                     mMediaSharedWithParent,
                     mCredentialShareableWithParent,
-                    mDeleteAppWithParent);
+                    mDeleteAppWithParent,
+                    mAlwaysVisible);
         }
     } // end Builder
 
@@ -954,7 +995,8 @@
             @CrossProfileIntentResolutionStrategy int crossProfileIntentResolutionStrategy,
             boolean mediaSharedWithParent,
             boolean credentialShareableWithParent,
-            boolean deleteAppWithParent) {
+            boolean deleteAppWithParent,
+            boolean alwaysVisible) {
         mDefaultProperties = null;
         setShowInLauncher(showInLauncher);
         setStartWithParent(startWithParent);
@@ -967,5 +1009,6 @@
         setMediaSharedWithParent(mediaSharedWithParent);
         setCredentialShareableWithParent(credentialShareableWithParent);
         setDeleteAppWithParent(deleteAppWithParent);
+        setAlwaysVisible(alwaysVisible);
     }
 }
diff --git a/core/java/android/content/pm/parsing/result/ParseTypeImpl.java b/core/java/android/content/pm/parsing/result/ParseTypeImpl.java
index c323704..8343c92 100644
--- a/core/java/android/content/pm/parsing/result/ParseTypeImpl.java
+++ b/core/java/android/content/pm/parsing/result/ParseTypeImpl.java
@@ -41,7 +41,7 @@
     public static final boolean DEBUG_THROW_ALL_ERRORS = false;
 
     @NonNull
-    private Callback mCallback;
+    private final Callback mCallback;
 
     private Object mResult;
 
diff --git a/core/java/android/content/res/ApkAssets.java b/core/java/android/content/res/ApkAssets.java
index 143c00d..653e243 100644
--- a/core/java/android/content/res/ApkAssets.java
+++ b/core/java/android/content/res/ApkAssets.java
@@ -78,6 +78,11 @@
      */
     public static final int PROPERTY_DISABLE_INCREMENTAL_HARDENING = 1 << 4;
 
+    /**
+     * The apk assets only contain the overlayable declarations information.
+     */
+    public static final int PROPERTY_ONLY_OVERLAYABLES = 1 << 5;
+
     /** Flags that change the behavior of loaded apk assets. */
     @IntDef(prefix = { "PROPERTY_" }, value = {
             PROPERTY_SYSTEM,
diff --git a/core/java/android/content/res/AssetFileDescriptor.java b/core/java/android/content/res/AssetFileDescriptor.java
index ac65933..3486d5e 100644
--- a/core/java/android/content/res/AssetFileDescriptor.java
+++ b/core/java/android/content/res/AssetFileDescriptor.java
@@ -16,17 +16,29 @@
 
 package android.content.res;
 
+import static android.system.OsConstants.S_ISFIFO;
+import static android.system.OsConstants.S_ISSOCK;
+
 import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.ParcelFileDescriptor;
 import android.os.Parcelable;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.StructStat;
 
 import java.io.Closeable;
 import java.io.FileDescriptor;
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.MappedByteBuffer;
+import java.nio.channels.FileChannel;
+import java.nio.channels.FileLock;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.channels.WritableByteChannel;
 
 /**
  * File descriptor of an entry in the AssetManager.  This provides your own
@@ -200,13 +212,94 @@
      * An InputStream you can create on a ParcelFileDescriptor, which will
      * take care of calling {@link ParcelFileDescriptor#close
      * ParcelFileDescriptor.close()} for you when the stream is closed.
+     * It has a ParcelFileDescriptor.AutoCloseInputStream member to make delegate calls
+     * and during definition it will create seekable or non seekable child object
+     * AssetFileDescriptor.AutoCloseInputStream depends on the type of file descriptor
+     * to provide different solution.
      */
     public static class AutoCloseInputStream
             extends ParcelFileDescriptor.AutoCloseInputStream {
-        private long mRemaining;
+        private ParcelFileDescriptor.AutoCloseInputStream mDelegateInputStream;
 
         public AutoCloseInputStream(AssetFileDescriptor fd) throws IOException {
             super(fd.getParcelFileDescriptor());
+            StructStat ss;
+            try {
+                ss = Os.fstat(fd.getParcelFileDescriptor().getFileDescriptor());
+            } catch (ErrnoException e) {
+                throw new IOException(e);
+            }
+            if (S_ISSOCK(ss.st_mode) || S_ISFIFO(ss.st_mode)) {
+                mDelegateInputStream = new NonSeekableAutoCloseInputStream(fd);
+            } else {
+                mDelegateInputStream = new SeekableAutoCloseInputStream(fd);
+            }
+        }
+
+        @Override
+        public int available() throws IOException {
+            return mDelegateInputStream.available();
+        }
+
+        @Override
+        public int read() throws IOException {
+            return mDelegateInputStream.read();
+        }
+
+        @Override
+        public int read(byte[] buffer, int offset, int count) throws IOException {
+            return mDelegateInputStream.read(buffer, offset, count);
+        }
+
+        @Override
+        public int read(byte[] buffer) throws IOException {
+            return mDelegateInputStream.read(buffer);
+        }
+
+        @Override
+        public long skip(long count) throws IOException {
+            return mDelegateInputStream.skip(count);
+        }
+
+        @Override
+        public void mark(int readlimit) {
+            mDelegateInputStream.mark(readlimit);
+        }
+
+        @Override
+        public boolean markSupported() {
+            return mDelegateInputStream.markSupported();
+        }
+
+        @Override
+        public synchronized void reset() throws IOException {
+            mDelegateInputStream.reset();
+        }
+
+        @Override
+        public FileChannel getChannel() {
+            return mDelegateInputStream.getChannel();
+        }
+        @Override
+        public void close() throws IOException {
+            // Make the mDelegateInputStream own file descriptor and super.close()
+            // is not needed here to avoid double close the file descriptor.
+            mDelegateInputStream.close();
+        }
+    }
+
+    /**
+     * An InputStream you can create on a non seekable file descriptor,
+     * like PIPE, SOCKET and FIFO, which will take care of calling
+     * {@link ParcelFileDescriptor#close ParcelFileDescriptor.close()}
+     * for you when the stream is closed.
+     */
+    private static class NonSeekableAutoCloseInputStream
+            extends ParcelFileDescriptor.AutoCloseInputStream {
+        private long mRemaining;
+
+        NonSeekableAutoCloseInputStream(AssetFileDescriptor fd) throws IOException {
+            super(fd.getParcelFileDescriptor());
             super.skip(fd.getStartOffset());
             mRemaining = (int) fd.getLength();
         }
@@ -284,6 +377,254 @@
     }
 
     /**
+     * An InputStream you can create on a seekable file descriptor, which means
+     * you can use pread to read from a specific offset, this will take care of
+     * calling {@link ParcelFileDescriptor#close ParcelFileDescriptor.close()}
+     * for you when the stream is closed.
+     */
+    private static class SeekableAutoCloseInputStream
+            extends ParcelFileDescriptor.AutoCloseInputStream {
+        /** Size of current file. */
+        private long mTotalSize;
+        /** The absolute position of current file start point. */
+        private final long mFileOffset;
+        /** The relative position where input stream is against mFileOffset. */
+        private long mOffset;
+        private OffsetCorrectFileChannel mOffsetCorrectFileChannel;
+
+        SeekableAutoCloseInputStream(AssetFileDescriptor fd) throws IOException {
+            super(fd.getParcelFileDescriptor());
+            mTotalSize = fd.getLength();
+            mFileOffset = fd.getStartOffset();
+        }
+
+        @Override
+        public int available() throws IOException {
+            long available = mTotalSize - mOffset;
+            return available >= 0
+                    ? (available < 0x7fffffff ? (int) available : 0x7fffffff)
+                    : 0;
+        }
+
+        @Override
+        public int read() throws IOException {
+            byte[] buffer = new byte[1];
+            int result = read(buffer, 0, 1);
+            return result == -1 ? -1 : buffer[0] & 0xff;
+        }
+
+        @Override
+        public int read(byte[] buffer, int offset, int count) throws IOException {
+            int available = available();
+            if (available <= 0) {
+                return -1;
+            }
+
+            if (count > available) count = available;
+            try {
+                int res = Os.pread(getFD(), buffer, offset, count, mFileOffset + mOffset);
+                // pread returns 0 at end of file, while java's InputStream interface requires -1
+                if (res == 0) res = -1;
+                if (res > 0) {
+                    mOffset += res;
+                    updateChannelPosition(mOffset + mFileOffset);
+                }
+                return res;
+            } catch (ErrnoException e) {
+                throw new IOException(e);
+            }
+        }
+
+        @Override
+        public int read(byte[] buffer) throws IOException {
+            return read(buffer, 0, buffer.length);
+        }
+
+        @Override
+        public long skip(long count) throws IOException {
+            int available = available();
+            if (available <= 0) {
+                return -1;
+            }
+
+            if (count > available) count = available;
+            mOffset += count;
+            updateChannelPosition(mOffset + mFileOffset);
+            return count;
+        }
+
+        @Override
+        public void mark(int readlimit) {
+            // Not supported.
+            return;
+        }
+
+        @Override
+        public boolean markSupported() {
+            return false;
+        }
+
+        @Override
+        public synchronized void reset() throws IOException {
+            // Not supported.
+            return;
+        }
+
+        @Override
+        public FileChannel getChannel() {
+            if (mOffsetCorrectFileChannel == null) {
+                mOffsetCorrectFileChannel = new OffsetCorrectFileChannel(super.getChannel());
+            }
+            try {
+                updateChannelPosition(mOffset + mFileOffset);
+            } catch (IOException e) {
+                throw new RuntimeException(e);
+            }
+            return mOffsetCorrectFileChannel;
+        }
+
+        /**
+         * Update the position of mOffsetCorrectFileChannel only after it is constructed.
+         *
+         * @param newPosition The absolute position mOffsetCorrectFileChannel needs to be moved to.
+         */
+        private void updateChannelPosition(long newPosition) throws IOException {
+            if (mOffsetCorrectFileChannel != null) {
+                mOffsetCorrectFileChannel.position(newPosition);
+            }
+        }
+
+        /**
+         * A FileChannel wrapper that will update mOffset of the AutoCloseInputStream
+         * to correct position when using FileChannel to read. All occurrence of position
+         * should be using absolute solution and each override method just do Delegation
+         * besides additional check. All methods related to write mode have been disabled
+         * and will throw UnsupportedOperationException with customized message.
+         */
+        private class OffsetCorrectFileChannel extends FileChannel {
+            private final FileChannel mDelegate;
+            private static final String METHOD_NOT_SUPPORTED_MESSAGE =
+                    "This Method is not supported in AutoCloseInputStream FileChannel.";
+
+            OffsetCorrectFileChannel(FileChannel fc) {
+                mDelegate = fc;
+            }
+
+            @Override
+            public int read(ByteBuffer dst) throws IOException {
+                if (available() <= 0) return -1;
+                int bytesRead = mDelegate.read(dst);
+                if (bytesRead != -1) mOffset += bytesRead;
+                return bytesRead;
+            }
+
+            @Override
+            public long read(ByteBuffer[] dsts, int offset, int length) throws IOException {
+                if (available() <= 0) return -1;
+                if (mOffset + length > mTotalSize) {
+                    length = (int) (mTotalSize - mOffset);
+                }
+                long bytesRead = mDelegate.read(dsts, offset, length);
+                if (bytesRead != -1) mOffset += bytesRead;
+                return bytesRead;
+            }
+
+            @Override
+            /**The only read method that does not move channel position*/
+            public int read(ByteBuffer dst, long position) throws IOException {
+                if (position - mFileOffset > mTotalSize) return -1;
+                return mDelegate.read(dst, position);
+            }
+
+            @Override
+            public long position() throws IOException {
+                return mDelegate.position();
+            }
+
+            @Override
+            public FileChannel position(long newPosition) throws IOException {
+                mOffset = newPosition - mFileOffset;
+                return mDelegate.position(newPosition);
+            }
+
+            @Override
+            public long size() throws IOException {
+                return mTotalSize;
+            }
+
+            @Override
+            public long transferTo(long position, long count, WritableByteChannel target)
+                    throws IOException {
+                if (position - mFileOffset > mTotalSize) {
+                    return 0;
+                }
+                if (position - mFileOffset + count > mTotalSize) {
+                    count = mTotalSize - (position - mFileOffset);
+                }
+                return mDelegate.transferTo(position, count, target);
+            }
+
+            @Override
+            public MappedByteBuffer map(MapMode mode, long position, long size) throws IOException {
+                if (position - mFileOffset > mTotalSize) {
+                    throw new IOException(
+                            "Cannot map to buffer because position exceed current file size.");
+                }
+                if (position - mFileOffset + size > mTotalSize) {
+                    size = mTotalSize - (position - mFileOffset);
+                }
+                return mDelegate.map(mode, position, size);
+            }
+
+            @Override
+            protected void implCloseChannel() throws IOException {
+                mDelegate.close();
+            }
+
+            @Override
+            public int write(ByteBuffer src) throws IOException {
+                throw new UnsupportedOperationException(METHOD_NOT_SUPPORTED_MESSAGE);
+            }
+
+            @Override
+            public long write(ByteBuffer[] srcs, int offset, int length) throws IOException {
+                throw new UnsupportedOperationException(METHOD_NOT_SUPPORTED_MESSAGE);
+            }
+
+            @Override
+            public int write(ByteBuffer src, long position) throws IOException {
+                throw new UnsupportedOperationException(METHOD_NOT_SUPPORTED_MESSAGE);
+            }
+
+            @Override
+            public long transferFrom(ReadableByteChannel src, long position, long count)
+                    throws IOException {
+                throw new UnsupportedOperationException(METHOD_NOT_SUPPORTED_MESSAGE);
+            }
+
+            @Override
+            public FileChannel truncate(long size) throws IOException {
+                throw new UnsupportedOperationException(METHOD_NOT_SUPPORTED_MESSAGE);
+            }
+
+            @Override
+            public void force(boolean metaData) throws IOException {
+                throw new UnsupportedOperationException(METHOD_NOT_SUPPORTED_MESSAGE);
+            }
+
+            @Override
+            public FileLock lock(long position, long size, boolean shared) throws IOException {
+                throw new UnsupportedOperationException(METHOD_NOT_SUPPORTED_MESSAGE);
+            }
+
+            @Override
+            public FileLock tryLock(long position, long size, boolean shared) throws IOException {
+                throw new UnsupportedOperationException(METHOD_NOT_SUPPORTED_MESSAGE);
+            }
+        }
+    }
+
+    /**
      * An OutputStream you can create on a ParcelFileDescriptor, which will
      * take care of calling {@link ParcelFileDescriptor#close
      * ParcelFileDescriptor.close()} for you when the stream is closed.
diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java
index ef3842a..b225de4 100644
--- a/core/java/android/content/res/AssetManager.java
+++ b/core/java/android/content/res/AssetManager.java
@@ -16,6 +16,8 @@
 
 package android.content.res;
 
+import static android.content.res.Resources.ID_NULL;
+
 import android.annotation.AnyRes;
 import android.annotation.ArrayRes;
 import android.annotation.AttrRes;
@@ -528,6 +530,10 @@
         if (!mOpen) {
             throw new RuntimeException("AssetManager has been closed");
         }
+        // Let's still check if the native object exists, given all the memory corruptions.
+        if (mObject == 0) {
+            throw new RuntimeException("AssetManager is open but the native object is gone");
+        }
     }
 
     /**
@@ -1085,7 +1091,7 @@
     public @NonNull XmlResourceParser openXmlResourceParser(int cookie, @NonNull String fileName)
             throws IOException {
         try (XmlBlock block = openXmlBlockAsset(cookie, fileName)) {
-            XmlResourceParser parser = block.newParser();
+            XmlResourceParser parser = block.newParser(ID_NULL, new Validator());
             // If openXmlBlockAsset doesn't throw, it will always return an XmlBlock object with
             // a valid native pointer, which makes newParser always return non-null. But let's
             // be careful.
@@ -1153,6 +1159,7 @@
     int[] getAttributeResolutionStack(long themePtr, @AttrRes int defStyleAttr,
             @StyleRes int defStyleRes, @StyleRes int xmlStyle) {
         synchronized (this) {
+            ensureValidLocked();
             return nativeAttributeResolutionStack(
                     mObject, themePtr, xmlStyle, defStyleAttr, defStyleRes);
         }
@@ -1414,7 +1421,7 @@
      * <a href="https://tools.ietf.org/html/bcp47">BCP-47</a> language tags and can be
      * parsed using {@link Locale#forLanguageTag(String)}.
      *
-     * <p>On SDK 20 (Android 4.4W: Kitkat for watches) and below, locale strings
+     * <p>On SDK 20 (Android 4.4W: KitKat for watches) and below, locale strings
      * are of the form {@code ll_CC} where {@code ll} is a two letter language code,
      * and {@code CC} is a two letter country code.
      */
diff --git a/core/java/android/content/res/Element.java b/core/java/android/content/res/Element.java
new file mode 100644
index 0000000..62a46b6
--- /dev/null
+++ b/core/java/android/content/res/Element.java
@@ -0,0 +1,482 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.res;
+
+import android.annotation.NonNull;
+import android.util.Pools.SimplePool;
+
+import androidx.annotation.StyleableRes;
+
+import com.android.internal.R;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+/**
+ * Defines the string attribute length and child tag count restrictions for a xml element.
+ *
+ * {@hide}
+ */
+public class Element {
+    private static final int DEFAULT_MAX_STRING_ATTR_LENGTH = 32_768;
+    private static final int MAX_POOL_SIZE = 128;
+
+    private static final String ANDROID_NAMESPACE = "http://schemas.android.com/apk/res/android";
+
+    protected static final String TAG_ACTION = "action";
+    protected static final String TAG_ACTIVITY = "activity";
+    protected static final String TAG_ADOPT_PERMISSIONS = "adopt-permissions";
+    protected static final String TAG_ACTIVITY_ALIAS = "activity-alias";
+    protected static final String TAG_APPLICATION = "application";
+    protected static final String TAG_ATTRIBUTION = "attribution";
+    protected static final String TAG_CATEGORY = "category";
+    protected static final String TAG_COMPATIBLE_SCREENS = "compatible-screens";
+    protected static final String TAG_DATA = "data";
+    protected static final String TAG_EAT_COMMENT = "eat-comment";
+    protected static final String TAG_FEATURE_GROUP = "feature-group";
+    protected static final String TAG_GRANT_URI_PERMISSION = "grant-uri-permission";
+    protected static final String TAG_INSTRUMENTATION = "instrumentation";
+    protected static final String TAG_INTENT = "intent";
+    protected static final String TAG_INTENT_FILTER = "intent-filter";
+    protected static final String TAG_KEY_SETS = "key-sets";
+    protected static final String TAG_LAYOUT = "layout";
+    protected static final String TAG_MANIFEST = "manifest";
+    protected static final String TAG_META_DATA = "meta-data";
+    protected static final String TAG_ORIGINAL_PACKAGE = "original-package";
+    protected static final String TAG_OVERLAY = "overlay";
+    protected static final String TAG_PACKAGE = "package";
+    protected static final String TAG_PACKAGE_VERIFIER = "package-verifier";
+    protected static final String TAG_PATH_PERMISSION = "path-permission";
+    protected static final String TAG_PERMISSION = "permission";
+    protected static final String TAG_PERMISSION_GROUP = "permission-group";
+    protected static final String TAG_PERMISSION_TREE = "permission-tree";
+    protected static final String TAG_PROFILEABLE = "profileable";
+    protected static final String TAG_PROTECTED_BROADCAST = "protected-broadcast";
+    protected static final String TAG_PROPERTY = "property";
+    protected static final String TAG_PROVIDER = "provider";
+    protected static final String TAG_QUERIES = "queries";
+    protected static final String TAG_RECEIVER = "receiver";
+    protected static final String TAG_RESTRICT_UPDATE = "restrict-update";
+    protected static final String TAG_SCREEN = "screen";
+    protected static final String TAG_SERVICE = "service";
+    protected static final String TAG_SUPPORT_SCREENS = "supports-screens";
+    protected static final String TAG_SUPPORTS_GL_TEXTURE = "supports-gl-texture";
+    protected static final String TAG_SUPPORTS_INPUT = "supports-input";
+    protected static final String TAG_SUPPORTS_SCREENS = "supports-screens";
+    protected static final String TAG_USES_CONFIGURATION = "uses-configuration";
+    protected static final String TAG_USES_FEATURE = "uses-feature";
+    protected static final String TAG_USES_GL_TEXTURE = "uses-gl-texture";
+    protected static final String TAG_USES_LIBRARY = "uses-library";
+    protected static final String TAG_USES_NATIVE_LIBRARY = "uses-native-library";
+    protected static final String TAG_USES_PERMISSION = "uses-permission";
+    protected static final String TAG_USES_PERMISSION_SDK_23 = "uses-permission-sdk-23";
+    protected static final String TAG_USES_PERMISSION_SDK_M = "uses-permission-sdk-m";
+    protected static final String TAG_USES_SDK = "uses-sdk";
+    protected static final String TAG_USES_SPLIT = "uses-split";
+
+    protected static final String TAG_ATTR_BACKUP_AGENT = "backupAgent";
+    protected static final String TAG_ATTR_CATEGORY = "category";
+    protected static final String TAG_ATTR_HOST = "host";
+    protected static final String TAG_ATTR_MANAGE_SPACE_ACTIVITY = "manageSpaceActivity";
+    protected static final String TAG_ATTR_MIMETYPE = "mimeType";
+    protected static final String TAG_ATTR_NAME = "name";
+    protected static final String TAG_ATTR_PACKAGE = "package";
+    protected static final String TAG_ATTR_PATH = "path";
+    protected static final String TAG_ATTR_PATH_ADVANCED_PATTERN = "pathAdvancedPattern";
+    protected static final String TAG_ATTR_PATH_PATTERN = "pathPattern";
+    protected static final String TAG_ATTR_PATH_PREFIX = "pathPrefix";
+    protected static final String TAG_ATTR_PATH_SUFFIX = "pathSuffix";
+    protected static final String TAG_ATTR_PARENT_ACTIVITY_NAME = "parentActivityName";
+    protected static final String TAG_ATTR_PERMISSION = "permission";
+    protected static final String TAG_ATTR_PERMISSION_GROUP = "permissionGroup";
+    protected static final String TAG_ATTR_PORT = "port";
+    protected static final String TAG_ATTR_PROCESS = "process";
+    protected static final String TAG_ATTR_READ_PERMISSION = "readPermission";
+    protected static final String TAG_ATTR_REQUIRED_ACCOUNT_TYPE = "requiredAccountType";
+    protected static final String TAG_ATTR_REQUIRED_SYSTEM_PROPERTY_NAME =
+            "requiredSystemPropertyName";
+    protected static final String TAG_ATTR_REQUIRED_SYSTEM_PROPERTY_VALUE =
+            "requiredSystemPropertyValue";
+    protected static final String TAG_ATTR_RESTRICTED_ACCOUNT_TYPE = "restrictedAccountType";
+    protected static final String TAG_ATTR_SCHEME = "scheme";
+    protected static final String TAG_ATTR_SHARED_USER_ID = "sharedUserId";
+    protected static final String TAG_ATTR_TARGET_ACTIVITY = "targetActivity";
+    protected static final String TAG_ATTR_TARGET_NAME = "targetName";
+    protected static final String TAG_ATTR_TARGET_PACKAGE = "targetPackage";
+    protected static final String TAG_ATTR_TARGET_PROCESSES = "targetProcesses";
+    protected static final String TAG_ATTR_TASK_AFFINITY = "taskAffinity";
+    protected static final String TAG_ATTR_VALUE = "value";
+    protected static final String TAG_ATTR_VERSION_NAME = "versionName";
+    protected static final String TAG_ATTR_WRITE_PERMISSION = "writePermission";
+
+    private static final String[] ACTIVITY_STR_ATTR_NAMES = {TAG_ATTR_NAME,
+            TAG_ATTR_PARENT_ACTIVITY_NAME, TAG_ATTR_PERMISSION, TAG_ATTR_PROCESS,
+            TAG_ATTR_TASK_AFFINITY};
+    private static final String[] ACTIVITY_ALIAS_STR_ATTR_NAMES = {TAG_ATTR_NAME,
+            TAG_ATTR_PERMISSION, TAG_ATTR_TARGET_ACTIVITY};
+    private static final String[] APPLICATION_STR_ATTR_NAMES = {TAG_ATTR_BACKUP_AGENT,
+            TAG_ATTR_MANAGE_SPACE_ACTIVITY, TAG_ATTR_NAME, TAG_ATTR_PERMISSION, TAG_ATTR_PROCESS,
+            TAG_ATTR_REQUIRED_ACCOUNT_TYPE, TAG_ATTR_RESTRICTED_ACCOUNT_TYPE,
+            TAG_ATTR_TASK_AFFINITY};
+    private static final String[] DATA_STR_ATTR_NAMES = {TAG_ATTR_SCHEME, TAG_ATTR_HOST,
+            TAG_ATTR_PORT, TAG_ATTR_PATH, TAG_ATTR_PATH_PATTERN, TAG_ATTR_PATH_PREFIX,
+            TAG_ATTR_PATH_SUFFIX, TAG_ATTR_PATH_ADVANCED_PATTERN, TAG_ATTR_MIMETYPE};
+    private static final String[] GRANT_URI_PERMISSION_STR_ATTR_NAMES = {TAG_ATTR_PATH,
+            TAG_ATTR_PATH_PATTERN, TAG_ATTR_PATH_PREFIX};
+    private static final String[] INSTRUMENTATION_STR_ATTR_NAMES = {TAG_ATTR_NAME,
+            TAG_ATTR_TARGET_PACKAGE, TAG_ATTR_TARGET_PROCESSES};
+    private static final String[] MANIFEST_STR_ATTR_NAMES = {TAG_ATTR_PACKAGE,
+            TAG_ATTR_SHARED_USER_ID, TAG_ATTR_VERSION_NAME};
+    private static final String[] OVERLAY_STR_ATTR_NAMES = {TAG_ATTR_CATEGORY,
+            TAG_ATTR_REQUIRED_SYSTEM_PROPERTY_NAME, TAG_ATTR_REQUIRED_SYSTEM_PROPERTY_VALUE,
+            TAG_ATTR_TARGET_PACKAGE, TAG_ATTR_TARGET_NAME};
+    private static final String[] PATH_PERMISSION_STR_ATTR_NAMES = {TAG_ATTR_PATH,
+            TAG_ATTR_PATH_PREFIX, TAG_ATTR_PATH_PATTERN, TAG_ATTR_PERMISSION,
+            TAG_ATTR_READ_PERMISSION, TAG_ATTR_WRITE_PERMISSION};
+    private static final String[] PERMISSION_STR_ATTR_NAMES = {TAG_ATTR_NAME,
+            TAG_ATTR_PERMISSION_GROUP};
+    private static final String[] PROVIDER_STR_ATTR_NAMES = {TAG_ATTR_NAME, TAG_ATTR_PERMISSION,
+            TAG_ATTR_PROCESS, TAG_ATTR_READ_PERMISSION, TAG_ATTR_WRITE_PERMISSION};
+    private static final String[] RECEIVER_SERVICE_STR_ATTR_NAMES = {TAG_ATTR_NAME,
+            TAG_ATTR_PERMISSION, TAG_ATTR_PROCESS};
+    private static final String[] NAME_ATTR = {TAG_ATTR_NAME};
+    private static final String[] NAME_VALUE_ATTRS = {TAG_ATTR_NAME, TAG_ATTR_VALUE};
+
+    private String[] mStringAttrNames = new String[0];
+    // The length of mTagCounters corresponds to the number of tags defined in getCounterIdx. If new
+    // tags are added then the size here should be increased to match.
+    private final TagCounter[] mTagCounters = new TagCounter[35];
+
+    String mTag;
+
+    private static final ThreadLocal<SimplePool<Element>> sPool =
+            ThreadLocal.withInitial(() -> new SimplePool<>(MAX_POOL_SIZE));
+
+    @NonNull
+    static Element obtain(@NonNull String tag) {
+        Element element = sPool.get().acquire();
+        if (element == null) {
+            element = new Element();
+        }
+        element.init(tag);
+        return element;
+    }
+
+    void recycle() {
+        mStringAttrNames = new String[0];
+        mTag = null;
+        sPool.get().release(this);
+    }
+
+    private long mChildTagMask = 0;
+
+    private static int getCounterIdx(String tag) {
+        switch(tag) {
+            case TAG_LAYOUT:
+                return 0;
+            case TAG_META_DATA:
+                return 1;
+            case TAG_INTENT_FILTER:
+                return 2;
+            case TAG_PROFILEABLE:
+                return 3;
+            case TAG_USES_NATIVE_LIBRARY:
+                return 4;
+            case TAG_RECEIVER:
+                return 5;
+            case TAG_SERVICE:
+                return 6;
+            case TAG_ACTIVITY_ALIAS:
+                return 7;
+            case TAG_USES_LIBRARY:
+                return 8;
+            case TAG_PROVIDER:
+                return 9;
+            case TAG_ACTIVITY:
+                return 10;
+            case TAG_ACTION:
+                return 11;
+            case TAG_CATEGORY:
+                return 12;
+            case TAG_DATA:
+                return 13;
+            case TAG_APPLICATION:
+                return 14;
+            case TAG_OVERLAY:
+                return 15;
+            case TAG_INSTRUMENTATION:
+                return 16;
+            case TAG_PERMISSION_GROUP:
+                return 17;
+            case TAG_PERMISSION_TREE:
+                return 18;
+            case TAG_SUPPORTS_GL_TEXTURE:
+                return 19;
+            case TAG_SUPPORTS_SCREENS:
+                return 20;
+            case TAG_USES_CONFIGURATION:
+                return 21;
+            case TAG_USES_PERMISSION_SDK_23:
+                return 22;
+            case TAG_USES_SDK:
+                return 23;
+            case TAG_COMPATIBLE_SCREENS:
+                return 24;
+            case TAG_QUERIES:
+                return 25;
+            case TAG_ATTRIBUTION:
+                return 26;
+            case TAG_USES_FEATURE:
+                return 27;
+            case TAG_PERMISSION:
+                return 28;
+            case TAG_USES_PERMISSION:
+                return 29;
+            case TAG_GRANT_URI_PERMISSION:
+                return 30;
+            case TAG_PATH_PERMISSION:
+                return 31;
+            case TAG_PACKAGE:
+                return 32;
+            case TAG_INTENT:
+                return 33;
+            default:
+                // The size of the mTagCounters array should be equal to this value+1
+                return 34;
+        }
+    }
+
+    private void init(String tag) {
+        this.mTag = tag;
+        mChildTagMask = 0;
+        switch (tag) {
+            case TAG_ACTION:
+            case TAG_CATEGORY:
+            case TAG_PACKAGE:
+            case TAG_PERMISSION_GROUP:
+            case TAG_PERMISSION_TREE:
+            case TAG_SUPPORTS_GL_TEXTURE:
+            case TAG_USES_FEATURE:
+            case TAG_USES_LIBRARY:
+            case TAG_USES_NATIVE_LIBRARY:
+            case TAG_USES_PERMISSION:
+            case TAG_USES_PERMISSION_SDK_23:
+            case TAG_USES_SDK:
+                setStringAttrNames(NAME_ATTR);
+                break;
+            case TAG_ACTIVITY:
+                setStringAttrNames(ACTIVITY_STR_ATTR_NAMES);
+                initializeCounter(TAG_LAYOUT, 1000);
+                initializeCounter(TAG_META_DATA, 8000);
+                initializeCounter(TAG_INTENT_FILTER, 20000);
+                break;
+            case TAG_ACTIVITY_ALIAS:
+                setStringAttrNames(ACTIVITY_ALIAS_STR_ATTR_NAMES);
+                initializeCounter(TAG_META_DATA, 8000);
+                initializeCounter(TAG_INTENT_FILTER, 20000);
+                break;
+            case TAG_APPLICATION:
+                setStringAttrNames(APPLICATION_STR_ATTR_NAMES);
+                initializeCounter(TAG_PROFILEABLE, 100);
+                initializeCounter(TAG_USES_NATIVE_LIBRARY, 100);
+                initializeCounter(TAG_RECEIVER, 1000);
+                initializeCounter(TAG_SERVICE, 1000);
+                initializeCounter(TAG_ACTIVITY_ALIAS, 4000);
+                initializeCounter(TAG_USES_LIBRARY, 4000);
+                initializeCounter(TAG_PROVIDER, 8000);
+                initializeCounter(TAG_META_DATA, 8000);
+                initializeCounter(TAG_ACTIVITY, 40000);
+                break;
+            case TAG_COMPATIBLE_SCREENS:
+                initializeCounter(TAG_SCREEN, 4000);
+                break;
+            case TAG_DATA:
+                setStringAttrNames(DATA_STR_ATTR_NAMES);
+                break;
+            case TAG_GRANT_URI_PERMISSION:
+                setStringAttrNames(GRANT_URI_PERMISSION_STR_ATTR_NAMES);
+                break;
+            case TAG_INSTRUMENTATION:
+                setStringAttrNames(INSTRUMENTATION_STR_ATTR_NAMES);
+                break;
+            case TAG_INTENT:
+            case TAG_INTENT_FILTER:
+                initializeCounter(TAG_ACTION, 20000);
+                initializeCounter(TAG_CATEGORY, 40000);
+                initializeCounter(TAG_DATA, 40000);
+                break;
+            case TAG_MANIFEST:
+                setStringAttrNames(MANIFEST_STR_ATTR_NAMES);
+                initializeCounter(TAG_APPLICATION, 100);
+                initializeCounter(TAG_OVERLAY, 100);
+                initializeCounter(TAG_INSTRUMENTATION, 100);
+                initializeCounter(TAG_PERMISSION_GROUP, 100);
+                initializeCounter(TAG_PERMISSION_TREE, 100);
+                initializeCounter(TAG_SUPPORTS_GL_TEXTURE, 100);
+                initializeCounter(TAG_SUPPORTS_SCREENS, 100);
+                initializeCounter(TAG_USES_CONFIGURATION, 100);
+                initializeCounter(TAG_USES_PERMISSION_SDK_23, 100);
+                initializeCounter(TAG_USES_SDK, 100);
+                initializeCounter(TAG_COMPATIBLE_SCREENS, 200);
+                initializeCounter(TAG_QUERIES, 200);
+                initializeCounter(TAG_ATTRIBUTION, 400);
+                initializeCounter(TAG_USES_FEATURE, 400);
+                initializeCounter(TAG_PERMISSION, 2000);
+                initializeCounter(TAG_USES_PERMISSION, 20000);
+                break;
+            case TAG_META_DATA:
+            case TAG_PROPERTY:
+                setStringAttrNames(NAME_VALUE_ATTRS);
+                break;
+            case TAG_OVERLAY:
+                setStringAttrNames(OVERLAY_STR_ATTR_NAMES);
+                break;
+            case TAG_PATH_PERMISSION:
+                setStringAttrNames(PATH_PERMISSION_STR_ATTR_NAMES);
+                break;
+            case TAG_PERMISSION:
+                setStringAttrNames(PERMISSION_STR_ATTR_NAMES);
+                break;
+            case TAG_PROVIDER:
+                setStringAttrNames(PROVIDER_STR_ATTR_NAMES);
+                initializeCounter(TAG_GRANT_URI_PERMISSION, 100);
+                initializeCounter(TAG_PATH_PERMISSION, 100);
+                initializeCounter(TAG_META_DATA, 8000);
+                initializeCounter(TAG_INTENT_FILTER, 20000);
+                break;
+            case TAG_QUERIES:
+                initializeCounter(TAG_PACKAGE, 1000);
+                initializeCounter(TAG_INTENT, 2000);
+                initializeCounter(TAG_PROVIDER, 8000);
+                break;
+            case TAG_RECEIVER:
+            case TAG_SERVICE:
+                setStringAttrNames(RECEIVER_SERVICE_STR_ATTR_NAMES);
+                initializeCounter(TAG_META_DATA, 8000);
+                initializeCounter(TAG_INTENT_FILTER, 20000);
+                break;
+        }
+    }
+
+    private void setStringAttrNames(String[] attrNames) {
+        mStringAttrNames = attrNames;
+    }
+
+    private static String getAttrNamespace(String attrName) {
+        if (attrName.equals(TAG_ATTR_PACKAGE)) {
+            return null;
+        }
+        return ANDROID_NAMESPACE;
+    }
+
+    private static int getAttrStringMaxLength(String attrName) {
+        switch (attrName) {
+            case TAG_ATTR_HOST:
+            case TAG_ATTR_PACKAGE:
+            case TAG_ATTR_PERMISSION_GROUP:
+            case TAG_ATTR_PORT:
+            case TAG_ATTR_REQUIRED_SYSTEM_PROPERTY_VALUE:
+            case TAG_ATTR_SCHEME:
+            case TAG_ATTR_SHARED_USER_ID:
+            case TAG_ATTR_TARGET_PACKAGE:
+                return 256;
+            case TAG_ATTR_MIMETYPE:
+                return 512;
+            case TAG_ATTR_BACKUP_AGENT:
+            case TAG_ATTR_CATEGORY:
+            case TAG_ATTR_MANAGE_SPACE_ACTIVITY:
+            case TAG_ATTR_NAME:
+            case TAG_ATTR_PARENT_ACTIVITY_NAME:
+            case TAG_ATTR_PERMISSION:
+            case TAG_ATTR_PROCESS:
+            case TAG_ATTR_READ_PERMISSION:
+            case TAG_ATTR_REQUIRED_ACCOUNT_TYPE:
+            case TAG_ATTR_RESTRICTED_ACCOUNT_TYPE:
+            case TAG_ATTR_TARGET_ACTIVITY:
+            case TAG_ATTR_TARGET_NAME:
+            case TAG_ATTR_TARGET_PROCESSES:
+            case TAG_ATTR_TASK_AFFINITY:
+            case TAG_ATTR_WRITE_PERMISSION:
+                return 1024;
+            case TAG_ATTR_PATH:
+            case TAG_ATTR_PATH_ADVANCED_PATTERN:
+            case TAG_ATTR_PATH_PATTERN:
+            case TAG_ATTR_PATH_PREFIX:
+            case TAG_ATTR_PATH_SUFFIX:
+            case TAG_ATTR_VERSION_NAME:
+                return 4000;
+            default:
+                return DEFAULT_MAX_STRING_ATTR_LENGTH;
+        }
+    }
+
+    private static int getResStringMaxLength(@StyleableRes int index) {
+        switch (index) {
+            case R.styleable.AndroidManifestData_host:
+            case R.styleable.AndroidManifestData_port:
+            case R.styleable.AndroidManifestData_scheme:
+                return 255;
+            case R.styleable.AndroidManifestData_mimeType:
+                return 512;
+            default:
+                return DEFAULT_MAX_STRING_ATTR_LENGTH;
+        }
+    }
+
+    private void initializeCounter(String tag, int max) {
+        int idx = getCounterIdx(tag);
+        if (mTagCounters[idx] == null) {
+            mTagCounters[idx] = new TagCounter();
+        }
+        mTagCounters[idx].reset(max);
+        mChildTagMask |= 1 << idx;
+    }
+
+    boolean hasChild(String tag) {
+        return (mChildTagMask & (1 << getCounterIdx(tag))) != 0;
+    }
+
+    void validateStringAttrs(@NonNull XmlPullParser attrs) throws XmlPullParserException {
+        for (int i = 0; i < mStringAttrNames.length; i++) {
+            String attrName = mStringAttrNames[i];
+            String val = attrs.getAttributeValue(getAttrNamespace(attrName), attrName);
+            if (val != null && val.length() > getAttrStringMaxLength(attrName)) {
+                throw new XmlPullParserException("String length limit exceeded for "
+                        + "attribute " + attrName + " in " + mTag);
+            }
+        }
+    }
+
+    void validateResStringAttr(@StyleableRes int index, CharSequence stringValue)
+            throws XmlPullParserException {
+        if (stringValue != null && stringValue.length() > getResStringMaxLength(index)) {
+            throw new XmlPullParserException("String length limit exceeded for "
+                    + "attribute in " + mTag);
+        }
+    }
+
+    void seen(@NonNull Element element) throws XmlPullParserException {
+        TagCounter counter = mTagCounters[getCounterIdx(element.mTag)];
+        if (counter != null) {
+            counter.increment();
+            if (!counter.isValid()) {
+                throw new XmlPullParserException("The number of child " + element.mTag
+                        + " elements exceeded the max allowed in " + this.mTag);
+            }
+        }
+    }
+}
diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java
index 1fdfcd0..61d5aa6 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -27,6 +27,7 @@
 import android.annotation.RawRes;
 import android.annotation.StyleRes;
 import android.annotation.StyleableRes;
+import android.app.ResourcesManager;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ActivityInfo.Config;
@@ -408,17 +409,24 @@
 
                 if ((configChanges & ActivityInfo.CONFIG_LOCALE) != 0) {
                     if (locales.size() > 1) {
-                        // The LocaleList has changed. We must query the AssetManager's available
-                        // Locales and figure out the best matching Locale in the new LocaleList.
-                        String[] availableLocales = mAssets.getNonSystemLocales();
-                        if (LocaleList.isPseudoLocalesOnly(availableLocales)) {
-                            // No app defined locales, so grab the system locales.
-                            availableLocales = mAssets.getLocales();
+                        String[] availableLocales;
+
+                        LocaleList localeList = ResourcesManager.getInstance().getLocaleList();
+                        if (!localeList.isEmpty()) {
+                            availableLocales = localeList.toLanguageTags().split(",");
+                        } else {
+                            // The LocaleList has changed. We must query the AssetManager's
+                            // available Locales and figure out the best matching Locale in the new
+                            // LocaleList.
+                            availableLocales = mAssets.getNonSystemLocales();
                             if (LocaleList.isPseudoLocalesOnly(availableLocales)) {
-                                availableLocales = null;
+                                // No app defined locales, so grab the system locales.
+                                availableLocales = mAssets.getLocales();
+                                if (LocaleList.isPseudoLocalesOnly(availableLocales)) {
+                                    availableLocales = null;
+                                }
                             }
                         }
-
                         if (availableLocales != null) {
                             final Locale bestLocale = locales.getFirstMatchWithEnglishSupported(
                                     availableLocales);
diff --git a/core/java/android/content/res/StringBlock.java b/core/java/android/content/res/StringBlock.java
index 6c07356..c143acb 100644
--- a/core/java/android/content/res/StringBlock.java
+++ b/core/java/android/content/res/StringBlock.java
@@ -62,7 +62,7 @@
     private static final String TAG = "AssetManager";
     private static final boolean localLOGV = false;
 
-    private final long mNative;
+    private long mNative;   // final, but gets modified when closed
     private final boolean mUseSparse;
     private final boolean mOwnsNative;
 
@@ -207,6 +207,7 @@
                 if (mOwnsNative) {
                     nativeDestroy(mNative);
                 }
+                mNative = 0;
             }
         }
     }
diff --git a/core/java/android/content/res/TagCounter.java b/core/java/android/content/res/TagCounter.java
new file mode 100644
index 0000000..94deee7
--- /dev/null
+++ b/core/java/android/content/res/TagCounter.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.content.res;
+
+/**
+ * Counter used to track the number of tags seen during manifest validation.
+ *
+ * {@hide}
+ */
+public class TagCounter {
+    private static final int DEFAULT_MAX_COUNT = 512;
+
+    private int mMaxValue;
+    private int mCount;
+
+    public TagCounter() {
+        mMaxValue = DEFAULT_MAX_COUNT;
+        mCount = 0;
+    }
+
+    void reset(int maxValue) {
+        this.mMaxValue = maxValue;
+        this.mCount = 0;
+    }
+
+    void increment() {
+        mCount += 1;
+    }
+
+    public boolean isValid() {
+        return mCount <= mMaxValue;
+    }
+}
diff --git a/core/java/android/content/res/TypedArray.java b/core/java/android/content/res/TypedArray.java
index 9eb9cd5..2e84636 100644
--- a/core/java/android/content/res/TypedArray.java
+++ b/core/java/android/content/res/TypedArray.java
@@ -35,6 +35,8 @@
 
 import dalvik.system.VMRuntime;
 
+import org.xmlpull.v1.XmlPullParserException;
+
 import java.util.Arrays;
 
 /**
@@ -1397,7 +1399,15 @@
             }
             return null;
         }
-        return mAssets.getPooledStringForCookie(cookie, data[index + STYLE_DATA]);
+        CharSequence value = mAssets.getPooledStringForCookie(cookie, data[index + STYLE_DATA]);
+        if (mXml != null && mXml.mValidator != null) {
+            try {
+                mXml.mValidator.validateAttr(mXml, index, value);
+            } catch (XmlPullParserException e) {
+                throw new RuntimeException("Failed to validate resource string: " + e.getMessage());
+            }
+        }
+        return value;
     }
 
     /** @hide */
diff --git a/core/java/android/content/res/Validator.java b/core/java/android/content/res/Validator.java
new file mode 100644
index 0000000..8b5e6c6
--- /dev/null
+++ b/core/java/android/content/res/Validator.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 android.content.res;
+
+import static android.content.res.Element.TAG_MANIFEST;
+
+import android.annotation.NonNull;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.util.ArrayDeque;
+
+/**
+ * Validates manifest files by ensuring that tag counts and the length of string attributes are
+ * restricted.
+ *
+ * {@hide}
+ */
+public class Validator {
+
+    private final ArrayDeque<Element> mElements = new ArrayDeque<>();
+
+    private void cleanUp() {
+        while (!mElements.isEmpty()) {
+            mElements.pop().recycle();
+        }
+    }
+
+    /**
+     * Validates the elements and it's attributes as the XmlPullParser traverses the xml.
+     */
+    public void validate(@NonNull XmlPullParser parser) throws XmlPullParserException {
+        int eventType = parser.getEventType();
+        int depth = parser.getDepth();
+        // The mElement size should equal to the parser depth-1 when the parser eventType is
+        // START_TAG. If depth - mElement.size() is larger than 1 then that means
+        // validation for the previous element was skipped so we should skip validation for all
+        // descendant elements as well
+        if (depth > mElements.size() + 1) {
+            return;
+        }
+        if (eventType == XmlPullParser.START_TAG) {
+            try {
+                String tag = parser.getName();
+                // only validate manifests
+                if (depth == 0 && mElements.size() == 0 && !TAG_MANIFEST.equals(tag)) {
+                    return;
+                }
+                Element parent = mElements.peek();
+                if (parent == null || parent.hasChild(tag)) {
+                    Element element = Element.obtain(tag);
+                    element.validateStringAttrs(parser);
+                    if (parent != null) {
+                        parent.seen(element);
+                    }
+                    mElements.push(element);
+                }
+            } catch (XmlPullParserException e) {
+                cleanUp();
+                throw e;
+            }
+        } else if (eventType == XmlPullParser.END_TAG && depth == mElements.size()) {
+            mElements.pop().recycle();
+        } else if (eventType == XmlPullParser.END_DOCUMENT) {
+            cleanUp();
+        }
+    }
+
+    /**
+     * Validates the resource string of a manifest tag attribute.
+     */
+    public void validateAttr(@NonNull XmlPullParser parser, int index, CharSequence stringValue)
+            throws XmlPullParserException {
+        if (parser.getDepth() > mElements.size()) {
+            return;
+        }
+        mElements.peek().validateResStringAttr(index, stringValue);
+    }
+}
diff --git a/core/java/android/content/res/XmlBlock.java b/core/java/android/content/res/XmlBlock.java
index 3915a6c..3afc830 100644
--- a/core/java/android/content/res/XmlBlock.java
+++ b/core/java/android/content/res/XmlBlock.java
@@ -73,7 +73,9 @@
     private void decOpenCountLocked() {
         mOpenCount--;
         if (mOpenCount == 0) {
+            mStrings.close();
             nativeDestroy(mNative);
+            mNative = 0;
             if (mAssets != null) {
                 mAssets.xmlBlockGone(hashCode());
             }
@@ -95,6 +97,18 @@
     }
 
     /**
+     * Returns a XmlResourceParser that validates the xml using the given validator.
+     */
+    public XmlResourceParser newParser(@AnyRes int resId, Validator validator) {
+        synchronized (this) {
+            if (mNative != 0) {
+                return new Parser(nativeCreateParseState(mNative, resId), this, validator);
+            }
+            return null;
+        }
+    }
+
+    /**
      * Reference Error.h UNEXPECTED_NULL
      */
     private static final int ERROR_NULL_DOCUMENT = Integer.MIN_VALUE + 8;
@@ -106,12 +120,19 @@
 
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
     public final class Parser implements XmlResourceParser {
+        Validator mValidator;
+
         Parser(long parseState, XmlBlock block) {
             mParseState = parseState;
             mBlock = block;
             block.mOpenCount++;
         }
 
+        Parser(long parseState, XmlBlock block, Validator validator) {
+            this(parseState, block);
+            mValidator = validator;
+        }
+
         @AnyRes
         public int getSourceResId() {
             return nativeGetSourceResId(mParseState);
@@ -327,6 +348,9 @@
                 break;
             }
             mEventType = ev;
+            if (mValidator != null) {
+                mValidator.validate(this);
+            }
             if (ev == END_DOCUMENT) {
                 // Automatically close the parse when we reach the end of
                 // a document, since the standard XmlPullParser interface
@@ -621,7 +645,7 @@
     }
 
     private @Nullable final AssetManager mAssets;
-    private final long mNative;
+    private long mNative;   // final, but gets reset on close
     /*package*/ final StringBlock mStrings;
     private boolean mOpen = true;
     private int mOpenCount = 1;
diff --git a/core/java/android/credentials/CredentialProviderInfo.java b/core/java/android/credentials/CredentialProviderInfo.java
index d66b8f0..a0cb36d 100644
--- a/core/java/android/credentials/CredentialProviderInfo.java
+++ b/core/java/android/credentials/CredentialProviderInfo.java
@@ -29,9 +29,7 @@
 
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.HashSet;
 import java.util.List;
-import java.util.Set;
 
 /**
  * {@link ServiceInfo} and meta-data about a credential provider.
@@ -41,7 +39,7 @@
 @TestApi
 public final class CredentialProviderInfo implements Parcelable {
     @NonNull private final ServiceInfo mServiceInfo;
-    @NonNull private final Set<String> mCapabilities = new HashSet<>();
+    @NonNull private final List<String> mCapabilities = new ArrayList<>();
     @Nullable private final CharSequence mOverrideLabel;
     @Nullable private CharSequence mSettingsSubtitle = null;
     private final boolean mIsSystemProvider;
@@ -98,11 +96,7 @@
     /** Returns a list of capabilities this provider service can support. */
     @NonNull
     public List<String> getCapabilities() {
-        List<String> capabilities = new ArrayList<>();
-        for (String capability : mCapabilities) {
-            capabilities.add(capability);
-        }
-        return Collections.unmodifiableList(capabilities);
+        return Collections.unmodifiableList(mCapabilities);
     }
 
     /** Returns whether the provider is enabled by the user. */
@@ -135,13 +129,11 @@
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeTypedObject(mServiceInfo, flags);
         dest.writeBoolean(mIsSystemProvider);
+        dest.writeStringList(mCapabilities);
         dest.writeBoolean(mIsEnabled);
         dest.writeBoolean(mIsPrimary);
         TextUtils.writeToParcel(mOverrideLabel, dest, flags);
         TextUtils.writeToParcel(mSettingsSubtitle, dest, flags);
-
-        List<String> capabilities = getCapabilities();
-        dest.writeStringList(capabilities);
     }
 
     @Override
@@ -178,14 +170,11 @@
     private CredentialProviderInfo(@NonNull Parcel in) {
         mServiceInfo = in.readTypedObject(ServiceInfo.CREATOR);
         mIsSystemProvider = in.readBoolean();
+        in.readStringList(mCapabilities);
         mIsEnabled = in.readBoolean();
         mIsPrimary = in.readBoolean();
         mOverrideLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
         mSettingsSubtitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
-
-        List<String> capabilities = new ArrayList<>();
-        in.readStringList(capabilities);
-        mCapabilities.addAll(capabilities);
     }
 
     public static final @NonNull Parcelable.Creator<CredentialProviderInfo> CREATOR =
@@ -205,7 +194,7 @@
     public static final class Builder {
 
         @NonNull private ServiceInfo mServiceInfo;
-        @NonNull private Set<String> mCapabilities = new HashSet<>();
+        @NonNull private List<String> mCapabilities = new ArrayList<>();
         private boolean mIsSystemProvider = false;
         @Nullable private CharSequence mSettingsSubtitle = null;
         private boolean mIsEnabled = false;
@@ -249,16 +238,6 @@
             return this;
         }
 
-        /**
-         * Sets a list of capabilities this provider service can support.
-         *
-         * @hide
-         */
-        public @NonNull Builder addCapabilities(@NonNull Set<String> capabilities) {
-            mCapabilities.addAll(capabilities);
-            return this;
-        }
-
         /** Sets whether it is enabled by the user. */
         public @NonNull Builder setEnabled(boolean isEnabled) {
             mIsEnabled = isEnabled;
diff --git a/core/java/android/database/sqlite/OWNERS b/core/java/android/database/sqlite/OWNERS
new file mode 100644
index 0000000..3bebc7c
--- /dev/null
+++ b/core/java/android/database/sqlite/OWNERS
@@ -0,0 +1 @@
+include /SQLITE_OWNERS
diff --git a/core/java/android/database/sqlite/SQLiteConnection.java b/core/java/android/database/sqlite/SQLiteConnection.java
index 4b3eb3a..b7489229 100644
--- a/core/java/android/database/sqlite/SQLiteConnection.java
+++ b/core/java/android/database/sqlite/SQLiteConnection.java
@@ -16,6 +16,9 @@
 
 package android.database.sqlite;
 
+import android.annotation.NonNull;
+import com.android.internal.annotations.GuardedBy;
+
 import android.database.Cursor;
 import android.database.CursorWindow;
 import android.database.DatabaseUtils;
@@ -35,6 +38,7 @@
 import dalvik.system.CloseGuard;
 import java.io.File;
 import java.io.IOException;
+import java.lang.ref.Reference;
 import java.nio.file.FileSystems;
 import java.nio.file.Files;
 import java.nio.file.Path;
@@ -107,9 +111,13 @@
     private final int mConnectionId;
     private final boolean mIsPrimaryConnection;
     private final boolean mIsReadOnlyConnection;
-    private final PreparedStatementCache mPreparedStatementCache;
     private PreparedStatement mPreparedStatementPool;
 
+    // A lock access to the statement cache.
+    private final Object mCacheLock = new Object();
+    @GuardedBy("mCacheLock")
+    private final PreparedStatementCache mPreparedStatementCache;
+
     // The recent operations log.
     private final OperationLog mRecentOperations;
 
@@ -167,6 +175,9 @@
     private static native int nativeGetDbLookaside(long connectionPtr);
     private static native void nativeCancel(long connectionPtr);
     private static native void nativeResetCancel(long connectionPtr, boolean cancelable);
+    private static native int nativeLastInsertRowId(long connectionPtr);
+    private static native long nativeChanges(long connectionPtr);
+    private static native long nativeTotalChanges(long connectionPtr);
 
     private SQLiteConnection(SQLiteConnectionPool pool,
             SQLiteDatabaseConfiguration configuration,
@@ -576,25 +587,27 @@
         final int oldSize = mConfiguration.perConnectionSql.size();
         final int newSize = configuration.perConnectionSql.size();
         boolean perConnectionSqlChanged = newSize > oldSize;
+        boolean journalModeChanged = !configuration.resolveJournalMode().equalsIgnoreCase(
+                mConfiguration.resolveJournalMode());
+        boolean syncModeChanged =
+                !configuration.resolveSyncMode().equalsIgnoreCase(mConfiguration.resolveSyncMode());
 
         // Update configuration parameters.
         mConfiguration.updateParametersFrom(configuration);
 
         // Update prepared statement cache size.
-        mPreparedStatementCache.resize(configuration.maxSqlCacheSize);
+        synchronized (mCacheLock) {
+            mPreparedStatementCache.resize(configuration.maxSqlCacheSize);
+        }
 
         if (foreignKeyModeChanged) {
             setForeignKeyModeFromConfiguration();
         }
 
-        boolean journalModeChanged = !configuration.resolveJournalMode().equalsIgnoreCase(
-                mConfiguration.resolveJournalMode());
         if (journalModeChanged) {
             setJournalFromConfiguration();
         }
 
-        boolean syncModeChanged =
-                !configuration.resolveSyncMode().equalsIgnoreCase(mConfiguration.resolveSyncMode());
         if (syncModeChanged) {
             setSyncModeFromConfiguration();
         }
@@ -620,7 +633,9 @@
     // Called by SQLiteConnectionPool only.
     // Returns true if the prepared statement cache contains the specified SQL.
     boolean isPreparedStatementInCache(String sql) {
-        return mPreparedStatementCache.get(sql) != null;
+        synchronized (mCacheLock) {
+            return mPreparedStatementCache.get(sql) != null;
+        }
     }
 
     /**
@@ -1052,12 +1067,17 @@
         }
     }
 
-    private PreparedStatement acquirePreparedStatement(String sql) {
+    /**
+     * Return a {@link #PreparedStatement}, possibly from the cache.
+     */
+    @GuardedBy("mCacheLock")
+    private PreparedStatement acquirePreparedStatementLI(String sql) {
         ++mPool.mTotalPrepareStatements;
         PreparedStatement statement = mPreparedStatementCache.get(sql);
         boolean skipCache = false;
         if (statement != null) {
             if (!statement.mInUse) {
+                statement.mInUse = true;
                 return statement;
             }
             // The statement is already in the cache but is in use (this statement appears
@@ -1088,7 +1108,20 @@
         return statement;
     }
 
-    private void releasePreparedStatement(PreparedStatement statement) {
+    /**
+     * Return a {@link #PreparedStatement}, possibly from the cache.
+     */
+    PreparedStatement acquirePreparedStatement(String sql) {
+        synchronized (mCacheLock) {
+            return acquirePreparedStatementLI(sql);
+        }
+    }
+
+    /**
+     * Release a {@link #PreparedStatement} that was originally supplied by this connection.
+     */
+    @GuardedBy("mCacheLock")
+    private void releasePreparedStatementLI(PreparedStatement statement) {
         statement.mInUse = false;
         if (statement.mInCache) {
             try {
@@ -1111,11 +1144,38 @@
         }
     }
 
+    /**
+     * Release a {@link #PreparedStatement} that was originally supplied by this connection.
+     */
+    void releasePreparedStatement(PreparedStatement statement) {
+        synchronized (mCacheLock) {
+            releasePreparedStatementLI(statement);
+        }
+    }
+
     private void finalizePreparedStatement(PreparedStatement statement) {
         nativeFinalizeStatement(mConnectionPtr, statement.mStatementPtr);
         recyclePreparedStatement(statement);
     }
 
+    /**
+     * Return a prepared statement for use by {@link SQLiteRawStatement}.  This throws if the
+     * prepared statement is incompatible with this connection.
+     */
+    PreparedStatement acquirePersistentStatement(@NonNull String sql) {
+        final int cookie = mRecentOperations.beginOperation("prepare", sql, null);
+        try {
+            final PreparedStatement statement = acquirePreparedStatement(sql);
+            throwIfStatementForbidden(statement);
+            return statement;
+        } catch (RuntimeException e) {
+            mRecentOperations.failOperation(cookie, e);
+            throw e;
+        } finally {
+            mRecentOperations.endOperation(cookie);
+        }
+    }
+
     private void attachCancellationSignal(CancellationSignal cancellationSignal) {
         if (cancellationSignal != null) {
             cancellationSignal.throwIfCanceled();
@@ -1200,7 +1260,14 @@
         }
     }
 
-    private void throwIfStatementForbidden(PreparedStatement statement) {
+    /**
+     * Verify that the statement is read-only, if the connection only allows read-only
+     * operations.
+     * @param statement The statement to check.
+     * @throws SQLiteException if the statement could update the database inside a read-only
+     * transaction.
+     */
+    void throwIfStatementForbidden(PreparedStatement statement) {
         if (mOnlyAllowReadOnlyOperations && !statement.mReadOnly) {
             throw new SQLiteException("Cannot execute this statement because it "
                     + "might modify the database but the connection is read-only.");
@@ -1260,7 +1327,9 @@
         mRecentOperations.dump(printer);
 
         if (verbose) {
-            mPreparedStatementCache.dump(printer);
+            synchronized (mCacheLock) {
+                mPreparedStatementCache.dump(printer);
+            }
         }
     }
 
@@ -1392,6 +1461,12 @@
         return sql.replaceAll("[\\s]*\\n+[\\s]*", " ");
     }
 
+    void clearPreparedStatementCache() {
+        synchronized (mCacheLock) {
+            mPreparedStatementCache.evictAll();
+        }
+    }
+
     /**
      * Holder type for a prepared statement.
      *
@@ -1401,8 +1476,10 @@
      * In particular, closing the connection requires a guarantee of deterministic
      * resource disposal because all native statement objects must be freed before
      * the native database object can be closed.  So no finalizers here.
+     *
+     * The class is package-visible so that {@link SQLiteRawStatement} can use it.
      */
-    private static final class PreparedStatement {
+    static final class PreparedStatement {
         // Next item in pool.
         public PreparedStatement mPoolNext;
 
@@ -1432,8 +1509,7 @@
         public boolean mInUse;
     }
 
-    private final class PreparedStatementCache
-            extends LruCache<String, PreparedStatement> {
+    private final class PreparedStatementCache extends LruCache<String, PreparedStatement> {
         public PreparedStatementCache(int size) {
             super(size);
         }
@@ -1742,4 +1818,43 @@
         }
 
     }
+
+    /**
+     * Return the ROWID of the last row to be inserted under this connection.  Returns 0 if there
+     * has never been an insert on this connection.
+     * @return The ROWID of the last row to be inserted under this connection.
+     * @hide
+     */
+    long getLastInsertRowId() {
+        try {
+            return nativeLastInsertRowId(mConnectionPtr);
+        } finally {
+            Reference.reachabilityFence(this);
+        }
+    }
+
+    /**
+     * Return the number of database changes on the current connection made by the last SQL
+     * statement
+     * @hide
+     */
+    long getLastChangedRowsCount() {
+        try {
+            return nativeChanges(mConnectionPtr);
+        } finally {
+            Reference.reachabilityFence(this);
+        }
+    }
+
+    /**
+     * Return the total number of database changes made on the current connection.
+     * @hide
+     */
+    long getTotalChangedRowsCount() {
+        try {
+            return nativeTotalChanges(mConnectionPtr);
+        } finally {
+            Reference.reachabilityFence(this);
+        }
+    }
 }
diff --git a/core/java/android/database/sqlite/SQLiteConnectionPool.java b/core/java/android/database/sqlite/SQLiteConnectionPool.java
index dcf1a47..b35a2e4 100644
--- a/core/java/android/database/sqlite/SQLiteConnectionPool.java
+++ b/core/java/android/database/sqlite/SQLiteConnectionPool.java
@@ -1126,6 +1126,16 @@
         mConnectionWaiterPool = waiter;
     }
 
+    void clearAcquiredConnectionsPreparedStatementCache() {
+        synchronized (mLock) {
+            if (!mAcquiredConnections.isEmpty()) {
+                for (SQLiteConnection connection : mAcquiredConnections.keySet()) {
+                    connection.clearPreparedStatementCache();
+                }
+            }
+        }
+    }
+
     /**
      * Dumps debugging information about this connection pool.
      *
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index c08294f..a3f8383 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -675,6 +675,34 @@
     }
 
     /**
+     * Begins a transaction in DEFERRED mode, with the android-specific constraint that the
+     * transaction is read-only. The database may not be modified inside a read-only transaction.
+     * <p>
+     * Read-only transactions may run concurrently with other read-only transactions, and if they
+     * database is in WAL mode, they may also run concurrently with IMMEDIATE or EXCLUSIVE
+     * transactions.
+     * <p>
+     * Transactions can be nested.  However, the behavior of the transaction is not altered by
+     * nested transactions.  A nested transaction may be any of the three transaction types but if
+     * the outermost type is read-only then nested transactions remain read-only, regardless of how
+     * they are started.
+     * <p>
+     * Here is the standard idiom for read-only transactions:
+     *
+     * <pre>
+     *   db.beginTransactionReadOnly();
+     *   try {
+     *     ...
+     *   } finally {
+     *     db.endTransaction();
+     *   }
+     * </pre>
+     */
+    public void beginTransactionReadOnly() {
+        beginTransactionWithListenerReadOnly(null);
+    }
+
+    /**
      * Begins a transaction in EXCLUSIVE mode.
      * <p>
      * Transactions can be nested.
@@ -699,7 +727,8 @@
      * commits, or is rolled back, either explicitly or by a call to
      * {@link #yieldIfContendedSafely}.
      */
-    public void beginTransactionWithListener(SQLiteTransactionListener transactionListener) {
+    public void beginTransactionWithListener(
+            @Nullable SQLiteTransactionListener transactionListener) {
         beginTransaction(transactionListener, true);
     }
 
@@ -728,20 +757,57 @@
      *            explicitly or by a call to {@link #yieldIfContendedSafely}.
      */
     public void beginTransactionWithListenerNonExclusive(
-            SQLiteTransactionListener transactionListener) {
+            @Nullable SQLiteTransactionListener transactionListener) {
         beginTransaction(transactionListener, false);
     }
 
+    /**
+     * Begins a transaction in read-only mode with a {@link SQLiteTransactionListener} listener.
+     * The database may not be updated inside a read-only transaction.
+     * <p>
+     * Transactions can be nested.  However, the behavior of the transaction is not altered by
+     * nested transactions.  A nested transaction may be any of the three transaction types but if
+     * the outermost type is read-only then nested transactions remain read-only, regardless of how
+     * they are started.
+     * <p>
+     * Here is the standard idiom for read-only transactions:
+     *
+     * <pre>
+     *   db.beginTransactionWightListenerReadOnly(listener);
+     *   try {
+     *     ...
+     *   } finally {
+     *     db.endTransaction();
+     *   }
+     * </pre>
+     */
+    public void beginTransactionWithListenerReadOnly(
+            @Nullable SQLiteTransactionListener transactionListener) {
+        beginTransaction(transactionListener, SQLiteSession.TRANSACTION_MODE_DEFERRED);
+    }
+
     @UnsupportedAppUsage
     private void beginTransaction(SQLiteTransactionListener transactionListener,
             boolean exclusive) {
+        beginTransaction(transactionListener,
+                exclusive ? SQLiteSession.TRANSACTION_MODE_EXCLUSIVE :
+                SQLiteSession.TRANSACTION_MODE_IMMEDIATE);
+    }
+
+    /**
+     * Begin a transaction with the specified mode.  Valid modes are
+     * {@link SQLiteSession.TRANSACTION_MODE_DEFERRED},
+     * {@link SQLiteSession.TRANSACTION_MODE_IMMEDIATE}, and
+     * {@link SQLiteSession.TRANSACTION_MODE_EXCLUSIVE}.
+     */
+    private void beginTransaction(@Nullable SQLiteTransactionListener listener, int mode) {
         acquireReference();
         try {
-            getThreadSession().beginTransaction(
-                    exclusive ? SQLiteSession.TRANSACTION_MODE_EXCLUSIVE :
-                            SQLiteSession.TRANSACTION_MODE_IMMEDIATE,
-                    transactionListener,
-                    getThreadDefaultConnectionFlags(false /*readOnly*/), null);
+            // DEFERRED transactions are read-only to allows concurrent read-only transactions.
+            // Others are read/write.
+            boolean readOnly = (mode == SQLiteSession.TRANSACTION_MODE_DEFERRED);
+            getThreadSession().beginTransaction(mode, listener,
+                    getThreadDefaultConnectionFlags(readOnly), null);
         } finally {
             releaseReference();
         }
@@ -2088,10 +2154,12 @@
             try (SQLiteStatement statement = new SQLiteStatement(this, sql, bindArgs)) {
                 return statement.executeUpdateDelete();
             } finally {
-                // If schema was updated, close non-primary connections, otherwise they might
-                // have outdated schema information
+                // If schema was updated, close non-primary connections and clear prepared
+                // statement caches of active connections, otherwise they might have outdated
+                // schema information.
                 if (statementType == DatabaseUtils.STATEMENT_DDL) {
                     mConnectionPoolLocked.closeAvailableNonPrimaryConnectionsAndLogExceptions();
+                    mConnectionPoolLocked.clearAcquiredConnectionsPreparedStatementCache();
                 }
             }
         } finally {
@@ -2100,6 +2168,81 @@
     }
 
     /**
+     * Return a {@link SQLiteRawStatement} connected to the database.  A transaction must be in
+     * progress or an exception will be thrown.  The resulting object will be closed automatically
+     * when the current transaction closes.
+     *
+     * @param sql The SQL string to be compiled into a prepared statement.
+     * @return A {@link SQLiteRawStatement} holding the compiled SQL.
+     * @throws IllegalStateException if a transaction is not in progress.
+     * @throws SQLiteException if the SQL cannot be compiled.
+     */
+    @NonNull
+    public SQLiteRawStatement createRawStatement(@NonNull String sql) {
+        Objects.requireNonNull(sql);
+        return new SQLiteRawStatement(this, sql);
+    }
+
+    /**
+     * Return the "rowid" of the last row to be inserted on the current connection. This method must
+     * only be called when inside a transaction. {@link IllegalStateException} is thrown if the
+     * method is called outside a transaction. If the function is called before any inserts in the
+     * current transaction, the value returned will be from a previous transaction, which may be
+     * from a different thread. If no inserts have occurred on the current connection, the function
+     * returns 0. See the SQLite documentation for the specific details.
+     *
+     * @see <a href="https://sqlite.org/c3ref/last_insert_rowid.html">sqlite3_last_insert_rowid</a>
+     *
+     * @return The ROWID of the last row to be inserted under this connection.
+     * @throws IllegalStateException if there is no current transaction.
+     */
+    public long getLastInsertRowId() {
+        return getThreadSession().getLastInsertRowId();
+    }
+
+    /**
+     * Return the number of database rows that were inserted, updated, or deleted by the most recent
+     * SQL statement within the current transaction.
+     *
+     * @see <a href="https://sqlite.org/c3ref/changes.html">sqlite3_changes64</a>
+     *
+     * @return The number of rows changed by the most recent sql statement
+     * @throws IllegalStateException if there is no current transaction.
+     * @hide
+     */
+    public long getLastChangedRowsCount() {
+        return getThreadSession().getLastChangedRowsCount();
+    }
+
+    /**
+     * Return the total number of database rows that have been inserted, updated, or deleted on
+     * the current connection since it was created.  Due to Android's internal management of
+     * SQLite connections, the value may, or may not, include changes made in earlier
+     * transactions. Best practice is to compare values returned within a single transaction.
+     *
+     * <code><pre>
+     *    database.beginTransaction();
+     *    try {
+     *        long initialValue = database.getTotalChangedRowsCount();
+     *        // Execute SQL statements
+     *        long changedRows = database.getTotalChangedRowsCount() - initialValue;
+     *        // changedRows counts the total number of rows updated in the transaction.
+     *    } finally {
+     *        database.endTransaction();
+     *    }
+     * </pre></code>
+     *
+     * @see <a href="https://sqlite.org/c3ref/changes.html">sqlite3_total_changes64</a>
+     *
+     * @return The number of rows changed on the current connection.
+     * @throws IllegalStateException if there is no current transaction.
+     * @hide
+     */
+    public long getTotalChangedRowsCount() {
+        return getThreadSession().getTotalChangedRowsCount();
+    }
+
+    /**
      * Verifies that a SQL SELECT statement is valid by compiling it.
      * If the SQL statement is not valid, this method will throw a {@link SQLiteException}.
      *
@@ -3113,4 +3256,3 @@
         ContentResolver.onDbCorruption(tag, message, stacktrace);
     }
 }
-
diff --git a/core/java/android/database/sqlite/SQLiteRawStatement.java b/core/java/android/database/sqlite/SQLiteRawStatement.java
new file mode 100644
index 0000000..165f181
--- /dev/null
+++ b/core/java/android/database/sqlite/SQLiteRawStatement.java
@@ -0,0 +1,856 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.database.sqlite;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import dalvik.annotation.optimization.FastNative;
+
+import java.io.Closeable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.Reference;
+import java.util.Objects;
+
+/**
+ * A {@link SQLiteRawStatement} represents a SQLite prepared statement. The methods correspond very
+ * closely to SQLite APIs that operate on a sqlite_stmt object.  In general, each API in this class
+ * corresponds to a single SQLite API.
+
+ * <p>
+ * A {@link SQLiteRawStatement} must be created through a database, and there must be a
+ * transaction open at the time. Statements are implicitly closed when the outermost transaction
+ * ends, or if the current transaction is marked successful. Statements may be explicitly
+ * closed at any time with {@link #close}.  The {@link #close} operation is idempotent and may be
+ * called multiple times without harm.
+ * <p>
+ * Multiple {@link SQLiteRawStatement}s may be open simultaneously.  They are independent of each
+ * other.  Closing one statement does not affect any other statement nor does it have any effect
+ * on the enclosing transaction.
+ * <p>
+ * Once a {@link SQLiteRawStatement} has been closed, no further database operations are
+ * permitted on that statement. An {@link IllegalStateException} will be thrown if a database
+ * operation is attempted on a closed statement.
+ * <p>
+ * All operations on a {@link SQLiteRawStatement} must be invoked from the thread that created
+ * it. A {@link IllegalStateException} will be thrown if cross-thread use is detected.
+ * <p>
+ * A common pattern for statements is try-with-resources.
+ * <code><pre>
+ * // Begin a transaction.
+ * database.beginTransaction();
+ * try (SQLiteRawStatement statement = database.createRawStatement("SELECT * FROM ...")) {
+ *     while (statement.step()) {
+ *         // Fetch columns from the result rows.
+ *     }
+ *     database.setTransactionSuccessful();
+ * } finally {
+ *     database.endTransaction();
+ * }
+ * </pre></code>
+ * Note that {@link SQLiteRawStatement} is unrelated to {@link SQLiteStatement}.
+ *
+ * @see <a href="http://sqlite.org/c3ref/stmt.html">sqlite3_stmt</a>
+ */
+public final class SQLiteRawStatement implements Closeable {
+
+    private static final String TAG = "SQLiteRawStatement";
+
+    /**
+     * The database for this object.
+     */
+    private final SQLiteDatabase mDatabase;
+
+    /**
+     * The session for this object.
+     */
+    private final SQLiteSession mSession;
+
+    /**
+     * The PreparedStatement associated with this object. This is returned to
+     * {@link SQLiteSession} when the object is closed.  This also retains immutable attributes of
+     * the statement, like the parameter count.
+     */
+    private SQLiteConnection.PreparedStatement mPreparedStatement;
+
+    /**
+     * The native statement associated with this object.  This is pulled from the
+     * PreparedStatement for faster access.
+     */
+    private final long mStatement;
+
+    /**
+     * The SQL string, for logging.
+     */
+    private final String mSql;
+
+    /**
+     * The thread that created this object.  The object is tied to a connection, which is tied to
+     * its session, which is tied to the thread.  (The lifetime of this object is bounded by the
+     * lifetime of the enclosing transaction, so there are more rules than just the relationships
+     * in the second sentence.)  This variable is set to null when the statement is closed.
+     */
+    private Thread mThread;
+
+    /**
+     * The field types for SQLite columns.
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(value = {
+                SQLITE_DATA_TYPE_INTEGER,
+                SQLITE_DATA_TYPE_FLOAT,
+                SQLITE_DATA_TYPE_TEXT,
+                SQLITE_DATA_TYPE_BLOB,
+                SQLITE_DATA_TYPE_NULL})
+    public @interface SQLiteDataType {}
+
+    /**
+     * The constant returned by {@link #getColumnType} when the column value is SQLITE_INTEGER.
+     */
+    public static final int SQLITE_DATA_TYPE_INTEGER  = 1;
+
+    /**
+     * The constant returned by {@link #getColumnType} when the column value is SQLITE_FLOAT.
+     */
+    public static final int SQLITE_DATA_TYPE_FLOAT = 2;
+
+    /**
+     * The constant returned by {@link #getColumnType} when the column value is SQLITE_TEXT.
+     */
+    public static final int SQLITE_DATA_TYPE_TEXT = 3;
+
+    /**
+     * The constant returned by {@link #getColumnType} when the column value is SQLITE_BLOB.
+     */
+    public static final int SQLITE_DATA_TYPE_BLOB = 4;
+
+    /**
+     * The constant returned by {@link #getColumnType} when the column value is SQLITE_NULL.
+     */
+    public static final int SQLITE_DATA_TYPE_NULL = 5;
+
+    /**
+     * SQLite error codes that are used by this class.
+     */
+    private static final int SQLITE_BUSY = 5;
+    private static final int SQLITE_LOCKED = 6;
+    private static final int SQLITE_ROW = 100;
+    private static final int SQLITE_DONE = 101;
+
+    /**
+     * Create the statement with empty bindings. The construtor will throw
+     * {@link IllegalStateException} if a transaction is not in progress. Clients should call
+     * {@link SQLiteDatabase.createRawStatement} to create a new instance.
+     */
+    SQLiteRawStatement(@NonNull SQLiteDatabase db, @NonNull String sql) throws SQLiteException {
+        mThread = Thread.currentThread();
+        mDatabase = db;
+        mSession = mDatabase.getThreadSession();
+        mSession.throwIfNoTransaction();
+        mSql = sql;
+        // Acquire a connection and prepare the statement.
+        mPreparedStatement = mSession.acquirePersistentStatement(mSql, this);
+        mStatement = mPreparedStatement.mStatementPtr;
+    }
+
+    /**
+     * Throw if the current session is not the session under which the object was created. Throw
+     * if the object has been closed.  The actual check is that the current thread is not equal to
+     * the creation thread.
+     */
+    private void throwIfInvalid() {
+        if (mThread != Thread.currentThread()) {
+            // Disambiguate the reasons for a mismatch.
+            if (mThread == null) {
+                throw new IllegalStateException("method called on a closed statement");
+            } else {
+                throw new IllegalStateException("method called on a foreign thread: " + mThread);
+            }
+        }
+    }
+
+    /**
+     * Throw {@link IllegalArgumentException} if the length + offset are invalid with respect to
+     * the array length.
+     */
+    private void throwIfInvalidBounds(int arrayLength, int offset, int length) {
+        if (arrayLength < 0) {
+            throw new IllegalArgumentException("invalid array length " + arrayLength);
+        }
+        if (offset < 0 || offset >= arrayLength) {
+            throw new IllegalArgumentException("invalid offset " + offset
+                    + " for array length " + arrayLength);
+        }
+        if (length <= 0 || ((arrayLength - offset) < length)) {
+            throw new IllegalArgumentException("invalid offset " + offset
+                    + " and length " + length
+                    + " for array length " + arrayLength);
+        }
+    }
+
+    /**
+     * Close the object and release any native resources. It is not an error to call this on an
+     * already-closed object.
+     */
+    @Override
+    public void close() {
+        if (mThread != null) {
+            // The object is known not to be closed, so this only throws if the caller is not in
+            // the creation thread.
+            throwIfInvalid();
+            mSession.releasePersistentStatement(mPreparedStatement, this);
+            mThread = null;
+        }
+    }
+
+    /**
+     * Return true if the statement is still open and false otherwise.
+     *
+     * @return True if the statement is open.
+     */
+    public boolean isOpen() {
+        return mThread != null;
+    }
+
+    /**
+     * Step to the next result row. This returns true if the statement stepped to a new row, and
+     * false if the statement is done.  The method throws on any other result, including a busy or
+     * locked database.  If WAL is enabled then the database should never be locked or busy.
+     *
+     * @see <a href="http://sqlite.org/c3ref/step.html">sqlite3_step</a>
+     *
+     * @return True if a row is available and false otherwise.
+     * @throws IllegalStateException if the statement is closed or this is a foreign thread.
+     * @throws SQLiteDatabaseLockedException if the database is locked or busy.
+     * @throws SQLiteException if a native error occurs.
+     */
+    public boolean step() throws SQLiteException {
+        throwIfInvalid();
+        try {
+            int err = nativeStep(mStatement, true);
+            switch (err) {
+                case SQLITE_ROW:
+                    return true;
+                case SQLITE_DONE:
+                    return false;
+                case SQLITE_BUSY:
+                    throw new SQLiteDatabaseLockedException("database " + mDatabase + " busy");
+                case SQLITE_LOCKED:
+                    throw new SQLiteDatabaseLockedException("database " + mDatabase + " locked");
+            }
+            // This line of code should never be reached, because the native method should already
+            // have thrown an exception.
+            throw new SQLiteException("unknown error " + err);
+        } finally {
+            Reference.reachabilityFence(this);
+        }
+    }
+
+    /**
+     * Step to the next result. This returns the raw result code code from the native method.  The
+     * expected values are SQLITE_ROW and SQLITE_DONE.  For other return values, clients must
+     * decode the error and handle it themselves.  http://sqlite.org/rescode.html for the current
+     * list of result codes.
+     *
+     * @return The native result code from the sqlite3_step() operation.
+     * @throws IllegalStateException if the statement is closed or this is a foreign thread.
+     * @hide
+     */
+    public int stepNoThrow() {
+        throwIfInvalid();
+        try {
+            return nativeStep(mStatement, false);
+        } finally {
+            Reference.reachabilityFence(this);
+        }
+    }
+
+    /**
+     * Reset the statement.
+     *
+     * @see <a href="http://sqlite.org/c3ref/reset.html">sqlite3_reset</a>
+     *
+     * @throws IllegalStateException if the statement is closed or this is a foreign thread.
+     * @throws SQLiteException if a native error occurs.
+     */
+    public void reset() {
+        throwIfInvalid();
+        try {
+            nativeReset(mStatement, false);
+        } finally {
+            Reference.reachabilityFence(this);
+        }
+    }
+
+    /**
+     * Clear all parameter bindings.
+     *
+     * @see <a href="http://sqlite.org/c3ref/clear_bindings.html">sqlite3_clear_bindings</a>
+     *
+     * @throws IllegalStateException if the statement is closed or this is a foreign thread.
+     * @throws SQLiteException if a native error occurs.
+     */
+    public void clearBindings() {
+        throwIfInvalid();
+        try {
+            nativeClearBindings(mStatement);
+        } finally {
+            Reference.reachabilityFence(this);
+        }
+    }
+
+    /**
+     * Return the number of parameters in the statement.
+     *
+     * @see
+     * <a href="http://sqlite.org/c3ref/bind_parameter_count.html">sqlite3_bind_parameter_count</a>
+     *
+     * @return The number of parameters in the statement.
+     * @throws IllegalStateException if the statement is closed or this is a foreign thread.
+     */
+    public int getParameterCount() {
+        throwIfInvalid();
+        try {
+            return nativeBindParameterCount(mStatement);
+        } finally {
+            Reference.reachabilityFence(this);
+        }
+    }
+
+    /**
+     * Return the index of the parameter with specified name.  If the name does not match any
+     * parameter, 0 is returned.
+     *
+     * @see
+     * <a href="http://sqlite.org/c3ref/bind_parameter_index.html">sqlite3_bind_parameter_index</a>
+     *
+     * @param name The name of a parameter.
+     * @return The index of the parameter or 0 if the name does not identify a parameter.
+     * @throws IllegalStateException if the statement is closed or this is a foreign thread.
+     */
+    public int getParameterIndex(@NonNull String name) {
+        Objects.requireNonNull(name);
+        throwIfInvalid();
+        try {
+            return nativeBindParameterIndex(mStatement, name);
+        } finally {
+            Reference.reachabilityFence(this);
+        }
+    }
+
+    /**
+     * Return the name of the parameter at the specified index.  Null is returned if there is no
+     * such parameter or if the parameter does not have a name.
+     *
+     * @see
+     * <a href="http://sqlite.org/c3ref/bind_parameter_name.html">sqlite3_bind_parameter_name</a>
+     *
+     * @param parameterIndex The index of the parameter.
+     * @return The name of the parameter.
+     * @throws IllegalStateException if the statement is closed or this is a foreign thread.
+     */
+    @Nullable
+    public String getParameterName(int parameterIndex) {
+        throwIfInvalid();
+        try {
+            return nativeBindParameterName(mStatement, parameterIndex);
+        } finally {
+            Reference.reachabilityFence(this);
+        }
+    }
+
+    /**
+     * Bind a blob to a parameter. Parameter indices start at 1. The function throws if the
+     * parameter index is out of bounds.
+     *
+     * @see <a href="http://sqlite.org/c3ref/bind_blob.html">sqlite3_bind_blob</a>
+     *
+     * @param parameterIndex The index of the parameter in the query. It is one-based.
+     * @param value The value to be bound to the parameter.
+     * @throws IllegalStateException if the statement is closed or this is a foreign thread.
+     * @throws SQLiteBindOrColumnIndexOutOfRangeException if the parameter is out of range.
+     * @throws SQLiteException if a native error occurs.
+     */
+    public void bindBlob(int parameterIndex, @NonNull byte[] value) throws SQLiteException {
+        Objects.requireNonNull(value);
+        throwIfInvalid();
+        try {
+            nativeBindBlob(mStatement, parameterIndex, value, 0, value.length);
+        } finally {
+            Reference.reachabilityFence(this);
+        }
+    }
+
+    /**
+     * Bind a blob to a parameter. Parameter indices start at 1. The function throws if the
+     * parameter index is out of bounds.  The sub-array value[offset] to value[offset+length-1] is
+     * bound.
+     *
+     * @see <a href="http://sqlite.org/c3ref/bind_blob.html">sqlite3_bind_blob</a>
+     *
+     * @param parameterIndex The index of the parameter in the query. It is one-based.
+     * @param value The value to be bound to the parameter.
+     * @param offset An offset into the value array
+     * @param length The number of bytes to bind from the value array.
+     * @throws IllegalStateException if the statement is closed or this is a foreign thread.
+     * @throws IllegalArgumentException if the sub-array exceeds the bounds of the value array.
+     * @throws SQLiteBindOrColumnIndexOutOfRangeException if the parameter is out of range.
+     * @throws SQLiteException if a native error occurs.
+     */
+    public void bindBlob(int parameterIndex, @NonNull byte[] value, int offset, int length)
+            throws SQLiteException {
+        Objects.requireNonNull(value);
+        throwIfInvalid();
+        throwIfInvalidBounds(value.length, offset, length);
+        try {
+            nativeBindBlob(mStatement, parameterIndex, value, offset, length);
+        } finally {
+            Reference.reachabilityFence(this);
+        }
+    }
+
+    /**
+     * Bind a double to a parameter. Parameter indices start at 1. The function throws if the
+     * parameter index is out of bounds.
+     *
+     * @see <a href="http://sqlite.org/c3ref/bind_blob.html">sqlite3_bind_double</a>
+     *
+     * @param parameterIndex The index of the parameter in the query. It is one-based.
+     * @param value The value to be bound to the parameter.
+     * @throws IllegalStateException if the statement is closed or this is a foreign thread.
+     * @throws SQLiteBindOrColumnIndexOutOfRangeException if the parameter is out of range.
+     * @throws SQLiteException if a native error occurs.
+     */
+    public void bindDouble(int parameterIndex, double value) throws SQLiteException {
+        throwIfInvalid();
+        try {
+            nativeBindDouble(mStatement, parameterIndex, value);
+        } finally {
+            Reference.reachabilityFence(this);
+        }
+    }
+
+    /**
+     * Bind an int to a parameter. Parameter indices start at 1. The function throws if the
+     * parameter index is out of bounds.
+     *
+     * @see <a href="http://sqlite.org/c3ref/bind_blob.html">sqlite3_bind_int</a>
+     *
+     * @param parameterIndex The index of the parameter in the query. It is one-based.
+     * @throws IllegalStateException if the statement is closed or this is a foreign thread.
+     * @throws SQLiteBindOrColumnIndexOutOfRangeException if the parameter is out of range.
+     * @throws SQLiteException if a native error occurs.
+     */
+    public void bindInt(int parameterIndex, int value) throws SQLiteException {
+        throwIfInvalid();
+        try {
+            nativeBindInt(mStatement, parameterIndex, value);
+        } finally {
+            Reference.reachabilityFence(this);
+        }
+    }
+
+    /**
+     * Bind a long to the parameter. Parameter indices start at 1. The function throws if the
+     * parameter index is out of bounds.
+     *
+     * @see <a href="http://sqlite.org/c3ref/bind_blob.html">sqlite3_bind_int64</a>
+     *
+     * @param value The value to be bound to the parameter.
+     * @throws IllegalStateException if the statement is closed or this is a foreign thread.
+     * @throws SQLiteBindOrColumnIndexOutOfRangeException if the parameter is out of range.
+     * @throws SQLiteException if a native error occurs.
+     */
+    public void bindLong(int parameterIndex, long value) throws SQLiteException {
+        throwIfInvalid();
+        try {
+            nativeBindLong(mStatement, parameterIndex, value);
+        } finally {
+            Reference.reachabilityFence(this);
+        }
+    }
+
+    /**
+     * Bind a null to the parameter. Parameter indices start at 1. The function throws if the
+     * parameter index is out of bounds.
+     *
+     * @see <a href="http://sqlite.org/c3ref/bind_blob.html">sqlite3_bind_null</a>
+     *
+     * @param parameterIndex The index of the parameter in the query. It is one-based.
+     * @throws IllegalStateException if the statement is closed or this is a foreign thread.
+     * @throws SQLiteBindOrColumnIndexOutOfRangeException if the parameter is out of range.
+     * @throws SQLiteException if a native error occurs.
+     */
+    public void bindNull(int parameterIndex) throws SQLiteException {
+        throwIfInvalid();
+        try {
+            nativeBindNull(mStatement, parameterIndex);
+        } finally {
+            Reference.reachabilityFence(this);
+        }
+    }
+
+    /**
+     * Bind a string to the parameter. Parameter indices start at 1. The function throws if the
+     * parameter index is out of bounds. The string may not be null.
+     *
+     * @see <a href="http://sqlite.org/c3ref/bind_blob.html">sqlite3_bind_text16</a>
+     *
+     * @param parameterIndex The index of the parameter in the query. It is one-based.
+     * @param value The value to be bound to the parameter.
+     * @throws IllegalStateException if the statement is closed or this is a foreign thread.
+     * @throws SQLiteBindOrColumnIndexOutOfRangeException if the parameter is out of range.
+     * @throws SQLiteException if a native error occurs.
+     */
+    public void bindText(int parameterIndex, @NonNull String value) throws SQLiteException {
+        Objects.requireNonNull(value);
+        throwIfInvalid();
+        try {
+            nativeBindText(mStatement, parameterIndex, value);
+        } finally {
+            Reference.reachabilityFence(this);
+        }
+    }
+
+    /**
+     * Return the number of columns in the current result row.
+     *
+     * @see <a href="http://sqlite.org/c3ref/column_count.html">sqlite3_column_count</a>
+     *
+     * @return The number of columns in the result row.
+     * @throws IllegalStateException if the statement is closed or this is a foreign thread.
+     */
+    public int getResultColumnCount() {
+        throwIfInvalid();
+        try {
+            return nativeColumnCount(mStatement);
+        } finally {
+            Reference.reachabilityFence(this);
+        }
+    }
+
+    /**
+     * Return the type of the column in the result row. Column indices start at 0.
+     *
+     * @see <a href="http://sqlite.org/c3ref/column_blob.html">sqlite3_column_type</a>
+     *
+     * @param columnIndex The index of a column in the result row. It is zero-based.
+     * @return The type of the value in the column of the result row.
+     * @throws IllegalStateException if the statement is closed or this is a foreign thread.
+     * @throws SQLiteBindOrColumnIndexOutOfRangeException if the column is out of range.
+     * @throws SQLiteException if a native error occurs.
+     */
+    @SQLiteDataType
+    public int getColumnType(int columnIndex) throws SQLiteException {
+        throwIfInvalid();
+        try {
+            return nativeColumnType(mStatement, columnIndex);
+        } finally {
+            Reference.reachabilityFence(this);
+        }
+    }
+
+    /**
+     * Return the name of the column in the result row. Column indices start at 0. This throws
+     * an exception if column is not in the result.
+     *
+     * @see <a href="http://sqlite.org/c3ref/column_name.html">sqlite3_column_name</a>
+     *
+     * @param columnIndex The index of a column in the result row. It is zero-based.
+     * @return The name of the column in the result row.
+     * @throws IllegalStateException if the statement is closed or this is a foreign thread.
+     * @throws SQLiteBindOrColumnIndexOutOfRangeException if the column is out of range.
+     * @throws SQLiteOutOfMemoryException if the database cannot allocate memory for the name.
+     */
+    @NonNull
+    public String getColumnName(int columnIndex) throws SQLiteException {
+        throwIfInvalid();
+        try {
+            return nativeColumnName(mStatement, columnIndex);
+        } finally {
+            Reference.reachabilityFence(this);
+        }
+    }
+
+    /**
+     * Return the length of the column value in the result row. Column indices start at 0. This
+     * returns 0 for a null and number of bytes for text or blob. Numeric values are converted to a
+     * string and the length of the string is returned.  See the sqlite documentation for
+     * details. Note that this cannot be used to distinguish a null value from an empty text or
+     * blob.  Note that this returns the number of bytes in the text value, not the number of
+     * characters.
+     *
+     * @see <a href="http://sqlite.org/c3ref/column_blob.html">sqlite3_column_bytes</a>
+     *
+     * @param columnIndex The index of a column in the result row. It is zero-based.
+     * @return The length, in bytes, of the value in the column.
+     * @throws IllegalStateException if the statement is closed or this is a foreign thread.
+     * @throws SQLiteBindOrColumnIndexOutOfRangeException if the column is out of range.
+     * @throws SQLiteException if a native error occurs.
+     */
+    public int getColumnLength(int columnIndex) throws SQLiteException {
+        throwIfInvalid();
+        try {
+            return nativeColumnBytes(mStatement, columnIndex);
+        } finally {
+            Reference.reachabilityFence(this);
+        }
+    }
+
+    /**
+     * Return the column value of the result row as a blob. Column indices start at 0. This
+     * throws an exception if column is not in the result.  This returns null if the column value
+     * is null.
+     *
+     * The column value will be converted if it is not of type {@link #SQLITE_DATA_TYPE_BLOB}; see
+     * the sqlite documentation for details.
+     *
+     * @see <a href="http://sqlite.org/c3ref/column_blob.html">sqlite3_column_blob</a>
+     *
+     * @param columnIndex The index of a column in the result row. It is zero-based.
+     * @return The value of the column as a blob, or null if the column is NULL.
+     * @throws IllegalStateException if the statement is closed or this is a foreign thread.
+     * @throws SQLiteBindOrColumnIndexOutOfRangeException if the column is out of range.
+     * @throws SQLiteException if a native error occurs.
+     */
+    @Nullable
+    public byte[] getColumnBlob(int columnIndex) throws SQLiteException {
+        throwIfInvalid();
+        try {
+            return nativeColumnBlob(mStatement, columnIndex);
+        } finally {
+            Reference.reachabilityFence(this);
+        }
+    }
+
+    /**
+     * Copy the column value of the result row, interpreted as a blob, into the buffer. Column
+     * indices start at 0. This throws an exception if column is not in the result row. Bytes are
+     * copied into the buffer starting at the offset. Bytes are copied from the blob starting at
+     * srcOffset.  Length bytes are copied unless the column value has fewer bytes available. The
+     * function returns the number of bytes copied.
+     *
+     * The column value will be converted if it is not of type {@link #SQLITE_DATA_TYPE_BLOB}; see
+     * the sqlite documentation for details.
+     *
+     * @see <a href="http://sqlite.org/c3ref/column_blob.html">sqlite3_column_blob</a>
+     *
+     * @param columnIndex The index of a column in the result row. It is zero-based.
+     * @param buffer A pre-allocated array to be filled with the value of the column.
+     * @param offset An offset into the buffer: copying starts here.
+     * @param length The number of bytes to copy.
+     * @param srcOffset The offset into the blob from which to start copying.
+     * @return the number of bytes that were copied.
+     * @throws IllegalStateException if the statement is closed or this is a foreign thread.
+     * @throws IllegalArgumentException if the buffer is too small for offset+length.
+     * @throws SQLiteBindOrColumnIndexOutOfRangeException if the column is out of range.
+     * @throws SQLiteException if a native error occurs.
+     */
+    public int readColumnBlob(int columnIndex, @NonNull byte[] buffer, int offset,
+            int length, int srcOffset)
+            throws SQLiteException {
+        Objects.requireNonNull(buffer);
+        throwIfInvalid();
+        throwIfInvalidBounds(buffer.length, offset, length);
+        try {
+            return nativeColumnBuffer(mStatement, columnIndex, buffer, offset, length, srcOffset);
+        } finally {
+            Reference.reachabilityFence(this);
+        }
+    }
+
+    /**
+     * Return the column value as a double. Column indices start at 0. This throws an exception
+     * if column is not in the result.
+     *
+     * The column value will be converted if it is not of type {@link #SQLITE_DATA_TYPE_FLOAT}; see
+     * the sqlite documentation for details.
+     *
+     * @see <a href="http://sqlite.org/c3ref/column_blob.html">sqlite3_column_double</a>
+     *
+     * @param columnIndex The index of a column in the result row. It is zero-based.
+     * @return The value of a column as a double.
+     * @throws IllegalStateException if the statement is closed or this is a foreign thread.
+     * @throws SQLiteBindOrColumnIndexOutOfRangeException if the column is out of range.
+     * @throws SQLiteException if a native error occurs.
+     */
+    public double getColumnDouble(int columnIndex) throws SQLiteException {
+        throwIfInvalid();
+        try {
+            return nativeColumnDouble(mStatement, columnIndex);
+        } finally {
+            Reference.reachabilityFence(this);
+        }
+    }
+
+    /**
+     * Return the column value as a int. Column indices start at 0. This throws an exception if
+     * column is not in the result.
+     *
+     * The column value will be converted if it is not of type {@link #SQLITE_DATA_TYPE_INTEGER};
+     * see the sqlite documentation for details.
+     *
+     * @see <a href="http://sqlite.org/c3ref/column_blob.html">sqlite3_column_int</a>
+     *
+     * @param columnIndex The index of a column in the result row. It is zero-based.
+     * @return The value of the column as an int.
+     * @throws IllegalStateException if the statement is closed or this is a foreign thread.
+     * @throws SQLiteBindOrColumnIndexOutOfRangeException if the column is out of range.
+     * @throws SQLiteException if a native error occurs.
+     */
+    public int getColumnInt(int columnIndex) throws SQLiteException {
+        throwIfInvalid();
+        try {
+            return nativeColumnInt(mStatement, columnIndex);
+        } finally {
+            Reference.reachabilityFence(this);
+        }
+    }
+
+    /**
+     * Return the column value as a long. Column indices start at 0. This throws an exception if
+     * column is not in the result.
+     *
+     * The column value will be converted if it is not of type {@link #SQLITE_DATA_TYPE_INTEGER};
+     * see the sqlite documentation for details.
+     *
+     * @see <a href="http://sqlite.org/c3ref/column_blob.html">sqlite3_column_long</a>
+     *
+     * @param columnIndex The index of a column in the result row. It is zero-based.
+     * @return The value of the column as an long.
+     * @throws IllegalStateException if the statement is closed or this is a foreign thread.
+     * @throws SQLiteBindOrColumnIndexOutOfRangeException if the column is out of range.
+     * @throws SQLiteException if a native error occurs.
+     */
+    public long getColumnLong(int columnIndex) throws SQLiteException {
+        throwIfInvalid();
+        try {
+            return nativeColumnLong(mStatement, columnIndex);
+        } finally {
+            Reference.reachabilityFence(this);
+        }
+    }
+
+    /**
+     * Return the column value as a text. Column indices start at 0. This throws an exception if
+     * column is not in the result.
+     *
+     * The column value will be converted if it is not of type {@link #SQLITE_DATA_TYPE_TEXT}; see
+     * the sqlite documentation for details.
+     *
+     * @see <a href="http://sqlite.org/c3ref/column_blob.html">sqlite3_column_text16</a>
+     *
+     * @param columnIndex The index of a column in the result row. It is zero-based.
+     * @return The value of the column as a string.
+     * @throws IllegalStateException if the statement is closed or this is a foreign thread.
+     * @throws SQLiteBindOrColumnIndexOutOfRangeException if the column is out of range.
+     * @throws SQLiteException if a native error occurs.
+     */
+    @NonNull
+    public String getColumnText(int columnIndex) throws SQLiteException {
+        throwIfInvalid();
+        try {
+            return nativeColumnText(mStatement, columnIndex);
+        } finally {
+            Reference.reachabilityFence(this);
+        }
+    }
+
+    @Override
+    public String toString() {
+        if (isOpen()) {
+            return "SQLiteRawStatement: " + mSql;
+        } else {
+            return "SQLiteRawStatement: (closed) " + mSql;
+        }
+    }
+
+    /**
+     * Native methods that only require a statement.
+     */
+
+    /**
+     * Metadata about the prepared statement.  The results are a property of the statement itself
+     * and not of any data in the database.
+     */
+    @FastNative
+    private static native int nativeBindParameterCount(long stmt);
+    @FastNative
+    private static native int nativeBindParameterIndex(long stmt, String name);
+    @FastNative
+    private static native String nativeBindParameterName(long stmt, int param);
+
+    @FastNative
+    private static native int nativeColumnCount(long stmt);
+
+    /**
+     * Operations on the statement
+     */
+    private static native int nativeStep(long stmt, boolean throwOnError);
+    private static native void nativeReset(long stmt, boolean clear);
+    @FastNative
+    private static native void nativeClearBindings(long stmt);
+
+    /**
+     * Methods that bind values to parameters.
+     */
+    @FastNative
+    private static native void nativeBindBlob(long stmt, int param, byte[] val, int off, int len);
+    @FastNative
+    private static native void nativeBindDouble(long stmt, int param, double val);
+    @FastNative
+    private static native void nativeBindInt(long stmt, int param, int val);
+    @FastNative
+    private static native void nativeBindLong(long stmt, int param, long val);
+    @FastNative
+    private static native void nativeBindNull(long stmt, int param);
+    @FastNative
+    private static native void nativeBindText(long stmt, int param, String val);
+
+    /**
+     * Methods that return information about the columns int the current result row.
+     */
+    @FastNative
+    private static native int nativeColumnType(long stmt, int col);
+    @FastNative
+    private static native String nativeColumnName(long stmt, int col);
+
+    /**
+     * Methods that return information about the value columns in the current result row.
+     */
+    @FastNative
+    private static native int nativeColumnBytes(long stmt, int col);
+
+    @FastNative
+    private static native byte[] nativeColumnBlob(long stmt, int col);
+    @FastNative
+    private static native int nativeColumnBuffer(long stmt, int col,
+            byte[] val, int off, int len, int srcOffset);
+    @FastNative
+    private static native double nativeColumnDouble(long stmt, int col);
+    @FastNative
+    private static native int nativeColumnInt(long stmt, int col);
+    @FastNative
+    private static native long nativeColumnLong(long stmt, int col);
+    @FastNative
+    private static native String nativeColumnText(long stmt, int col);
+}
diff --git a/core/java/android/database/sqlite/SQLiteSession.java b/core/java/android/database/sqlite/SQLiteSession.java
index 24b62b8..ef1a9cb 100644
--- a/core/java/android/database/sqlite/SQLiteSession.java
+++ b/core/java/android/database/sqlite/SQLiteSession.java
@@ -16,6 +16,8 @@
 
 package android.database.sqlite;
 
+import android.annotation.NonNull;
+
 import android.compat.annotation.UnsupportedAppUsage;
 import android.database.CursorWindow;
 import android.database.DatabaseUtils;
@@ -23,6 +25,10 @@
 import android.os.OperationCanceledException;
 import android.os.ParcelFileDescriptor;
 
+import java.io.Closeable;
+import java.io.IOException;
+import java.util.ArrayDeque;
+
 /**
  * Provides a single client the ability to use a database.
  *
@@ -171,6 +177,11 @@
     private Transaction mTransactionStack;
 
     /**
+     * A list of dependents that should be closed when the transaction completes.
+     */
+    private final ArrayDeque<Closeable> mOpenDependents = new ArrayDeque<>();
+
+    /**
      * Transaction mode: Deferred.
      * <p>
      * In a deferred transaction, no locks are acquired on the database
@@ -325,7 +336,12 @@
                         mConnection.execute("BEGIN EXCLUSIVE;", null,
                                 cancellationSignal); // might throw
                         break;
+                    case TRANSACTION_MODE_DEFERRED:
+                        mConnection.execute("BEGIN DEFERRED;", null,
+                                cancellationSignal); // might throw
+                        break;
                     default:
+                        // Per SQLite documentation, this executes in DEFERRED mode.
                         mConnection.execute("BEGIN;", null, cancellationSignal); // might throw
                         break;
                 }
@@ -374,6 +390,9 @@
         throwIfTransactionMarkedSuccessful();
 
         mTransactionStack.mMarkedSuccessful = true;
+        // Close open dependents, since the next thing that is supposed to happen is the transaction
+        // ends.
+        closeOpenDependents();
     }
 
     /**
@@ -434,6 +453,11 @@
                 mTransactionStack.mChildFailed = true;
             }
         } else {
+            // Close all dependents before anything that might throw.  The list should have been
+            // cleared when the transaction was marked successful or unsuccessful.  The call here
+            // does nothing if the list is empty but is provided for insurance.
+            closeOpenDependents();
+
             try {
                 if (successful) {
                     mConnection.execute("COMMIT;", null, cancellationSignal); // might throw
@@ -912,7 +936,87 @@
         }
     }
 
-    private void throwIfNoTransaction() {
+    /**
+     * Acquire a prepared statement for external use. A current transaction is required and that
+     * transaction may not have been marked successful. The dependent is registered its close()
+     * method is called when the transaction is closed.
+     */
+    @NonNull
+    SQLiteConnection.PreparedStatement acquirePersistentStatement(@NonNull String query,
+            @NonNull Closeable dependent) {
+        throwIfNoTransaction();
+        throwIfTransactionMarkedSuccessful();
+        mOpenDependents.addFirst(dependent);
+        try {
+            return mConnection.acquirePersistentStatement(query);
+        } catch (Throwable e) {
+            mOpenDependents.remove(dependent);
+            throw e;
+        }
+    }
+
+    /**
+     * Release a prepared statement.  The dependent should be in list of open dependents.
+     */
+    void releasePersistentStatement(@NonNull SQLiteConnection.PreparedStatement statement,
+            @NonNull Closeable dependent) {
+        mConnection.releasePreparedStatement(statement);
+        mOpenDependents.remove(dependent);
+    }
+
+    /**
+     * Close any open dependents.  This may be called multiple times without harm.  It never
+     * throws.
+     */
+    void closeOpenDependents() {
+        while (mOpenDependents.size() > 0) {
+            final Closeable dependent = mOpenDependents.pollFirst();
+            if (dependent != null)
+                try {
+                    dependent.close();
+                } catch (IOException e) {
+                    // Swallow the exception.
+                }
+        }
+    }
+
+    /**
+     * Return the row ID of the last row to be inserted on this connection.  Note that the last row
+     * might not have been inserted on this particular statement, but the return value is the last
+     * row inserted on the same connection as that used by this statement.  The function checks that
+     * it is currently in a transaction before executing.  Because of this check, it is not
+     * necessary to acquire and release the connection: the connection has already been acquired.
+     * @hide
+     */
+    long getLastInsertRowId() {
+        throwIfNoTransaction();
+        return mConnection.getLastInsertRowId();
+    }
+
+    /**
+     * Return the number of database rows that were changed by the most recent SQL statement on
+     * this connection.
+     * @hide
+     */
+    long getLastChangedRowsCount() {
+        throwIfNoTransaction();
+        return mConnection.getLastChangedRowsCount();
+    }
+
+    /**
+     * Return the total number of database rows that were changed on the current connection, since
+     * it was created.
+     * @hide
+     */
+    long getTotalChangedRowsCount() {
+        throwIfNoTransaction();
+        return mConnection.getTotalChangedRowsCount();
+    }
+
+    /**
+     * Throw {@link IllegalStateException} if there is no current transaction.
+     */
+    void throwIfNoTransaction() {
         if (mTransactionStack == null) {
             throw new IllegalStateException("Cannot perform this operation because "
                     + "there is no current transaction.");
diff --git a/core/java/android/database/sqlite/SQLiteStatement.java b/core/java/android/database/sqlite/SQLiteStatement.java
index d33eadc..acdc0fa 100644
--- a/core/java/android/database/sqlite/SQLiteStatement.java
+++ b/core/java/android/database/sqlite/SQLiteStatement.java
@@ -27,6 +27,7 @@
  * <p>
  * This class is not thread-safe.
  * </p>
+ * Note that this class is unrelated to {@link SQLiteRawStatement}.
  */
 public final class SQLiteStatement extends SQLiteProgram {
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
diff --git a/core/java/android/database/sqlite/TEST_MAPPING b/core/java/android/database/sqlite/TEST_MAPPING
new file mode 100644
index 0000000..9dcf4e5
--- /dev/null
+++ b/core/java/android/database/sqlite/TEST_MAPPING
@@ -0,0 +1,24 @@
+{
+    "presubmit": [
+        {
+            "name": "FrameworksCoreTests",
+            "options": [
+                {
+                    "exclude-annotation": "androidx.test.filters.FlakyTest"
+                },
+                {
+                    "exclude-annotation": "org.junit.Ignore"
+                },
+                {
+                    "include-filter": "android.database.sqlite.SQLiteRawStatementTest"
+                }
+            ],
+            "file_patterns": [
+                "(/|^)SQLiteRawStatement.java",
+                "(/|^)SQLiteDatabase.java",
+                "(/|^)SQLiteSession.java",
+                "(/|^)SQLiteConnection.java"
+            ]
+        }
+    ]
+}
diff --git a/core/java/android/database/sqlite/package.html b/core/java/android/database/sqlite/package.html
index 6ececa2..71b8f3f 100644
--- a/core/java/android/database/sqlite/package.html
+++ b/core/java/android/database/sqlite/package.html
@@ -20,6 +20,9 @@
 <p>The version of SQLite depends on the version of Android. See the following table:
 <table style="width:auto;">
   <tr><th>Android API</th><th>SQLite Version</th></tr>
+  <tr><td>API 34</td><td>3.39</td></tr>
+  <tr><td>API 33</td><td>3.32</td></tr>
+  <tr><td>API 32</td><td>3.32</td></tr>
   <tr><td>API 31</td><td>3.32</td></tr>
   <tr><td>API 30</td><td>3.28</td></tr>
   <tr><td>API 28</td><td>3.22</td></tr>
diff --git a/core/java/android/ddm/DdmHandleHello.java b/core/java/android/ddm/DdmHandleHello.java
index 4160029..a51a740 100644
--- a/core/java/android/ddm/DdmHandleHello.java
+++ b/core/java/android/ddm/DdmHandleHello.java
@@ -16,6 +16,7 @@
 
 package android.ddm;
 
+import android.os.DdmSyncState;
 import android.os.Debug;
 import android.os.UserHandle;
 import android.util.Log;
@@ -44,6 +45,7 @@
     private static final String[] FRAMEWORK_FEATURES = new String[] {
         "opengl-tracing",
         "view-hierarchy",
+        "support_boot_stages"
     };
 
     /* singleton, do not instantiate */
@@ -145,7 +147,9 @@
                             + instructionSetDescription.length() * 2
                             + vmFlags.length() * 2
                             + 1
-                            + pkgName.length() * 2);
+                            + pkgName.length() * 2
+                            // STAG id (int)
+                            + Integer.BYTES);
         out.order(ChunkHandler.CHUNK_ORDER);
         out.putInt(CLIENT_PROTOCOL_VERSION);
         out.putInt(android.os.Process.myPid());
@@ -162,6 +166,10 @@
         out.putInt(pkgName.length());
         putString(out, pkgName);
 
+        // Added API 34 (and advertised via FEAT ddm packet)
+        // Send the current boot stage in ActivityThread
+        out.putInt(DdmSyncState.getStage().toInt());
+
         Chunk reply = new Chunk(CHUNK_HELO, out);
 
         /*
diff --git a/core/java/android/flags/BooleanFlag.java b/core/java/android/flags/BooleanFlag.java
new file mode 100644
index 0000000..d4a35b2
--- /dev/null
+++ b/core/java/android/flags/BooleanFlag.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 android.flags;
+
+import android.annotation.NonNull;
+
+/**
+ * A flag representing a true or false value.
+ *
+ * The value will always be the same during the lifetime of the process it is read in.
+ *
+ * @hide
+ */
+public class BooleanFlag extends BooleanFlagBase {
+    private final boolean mDefault;
+
+    /**
+     * @param namespace A namespace for this flag. See {@link android.provider.DeviceConfig}.
+     * @param name A name for this flag.
+     * @param defaultValue The value of this flag if no other override is present.
+     */
+    BooleanFlag(String namespace, String name, boolean defaultValue) {
+        super(namespace, name);
+        mDefault = defaultValue;
+    }
+
+    @Override
+    @NonNull
+    public Boolean getDefault() {
+        return mDefault;
+    }
+
+    @Override
+    public BooleanFlag defineMetaData(String label, String description, String categoryName) {
+        super.defineMetaData(label, description, categoryName);
+        return this;
+    }
+}
diff --git a/core/java/android/flags/BooleanFlagBase.java b/core/java/android/flags/BooleanFlagBase.java
new file mode 100644
index 0000000..985dbe3
--- /dev/null
+++ b/core/java/android/flags/BooleanFlagBase.java
@@ -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 android.flags;
+
+import android.annotation.NonNull;
+
+abstract class BooleanFlagBase implements Flag<Boolean> {
+
+    private final String mNamespace;
+    private final String mName;
+    private String mLabel;
+    private String mDescription;
+    private String mCategoryName;
+
+    /**
+     * @param namespace A namespace for this flag. See {@link android.provider.DeviceConfig}.
+     * @param name A name for this flag.
+     */
+    BooleanFlagBase(String namespace, String name) {
+        mNamespace = namespace;
+        mName = name;
+        mLabel = name;
+    }
+
+    public abstract Boolean getDefault();
+
+    @Override
+    @NonNull
+    public String getNamespace() {
+        return mNamespace;
+    }
+
+    @Override
+    @NonNull
+    public String getName() {
+        return mName;
+    }
+
+    @Override
+    public BooleanFlagBase defineMetaData(String label, String description, String categoryName) {
+        mLabel = label;
+        mDescription = description;
+        mCategoryName = categoryName;
+        return this;
+    }
+
+    @Override
+    @NonNull
+    public String getLabel() {
+        return mLabel;
+    }
+
+    @Override
+    public String getDescription() {
+        return mDescription;
+    }
+
+    @Override
+    public String getCategoryName() {
+        return mCategoryName;
+    }
+
+    @Override
+    @NonNull
+    public String toString() {
+        return getNamespace() + "." + getName() + "[" + getDefault() + "]";
+    }
+}
diff --git a/core/java/android/flags/DynamicBooleanFlag.java b/core/java/android/flags/DynamicBooleanFlag.java
new file mode 100644
index 0000000..271a8c5f4
--- /dev/null
+++ b/core/java/android/flags/DynamicBooleanFlag.java
@@ -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 android.flags;
+
+/**
+ * A flag representing a true or false value.
+ *
+ * The value may be different from one read to the next.
+ *
+ * @hide
+ */
+public class DynamicBooleanFlag extends BooleanFlagBase implements DynamicFlag<Boolean> {
+
+    private final boolean mDefault;
+
+    /**
+     * @param namespace A namespace for this flag. See {@link android.provider.DeviceConfig}.
+     * @param name A name for this flag.
+     * @param defaultValue The value of this flag if no other override is present.
+     */
+    DynamicBooleanFlag(String namespace, String name, boolean defaultValue) {
+        super(namespace, name);
+        mDefault = defaultValue;
+    }
+
+    @Override
+    public Boolean getDefault() {
+        return mDefault;
+    }
+
+    @Override
+    public DynamicBooleanFlag defineMetaData(String label, String description, String categoryName) {
+        super.defineMetaData(label, description, categoryName);
+        return this;
+    }
+}
diff --git a/core/java/android/flags/DynamicFlag.java b/core/java/android/flags/DynamicFlag.java
new file mode 100644
index 0000000..68819c5
--- /dev/null
+++ b/core/java/android/flags/DynamicFlag.java
@@ -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 android.flags;
+
+/**
+ * A flag for which the value may be different from one read to the next.
+ *
+ * @param <T> The type of value that this flag stores. E.g. Boolean or String.
+ *
+ * @hide
+ */
+public interface DynamicFlag<T> extends Flag<T> {
+    @Override
+    default boolean isDynamic() {
+        return true;
+    }
+}
diff --git a/core/java/android/flags/FeatureFlags.java b/core/java/android/flags/FeatureFlags.java
new file mode 100644
index 0000000..8d3112c
--- /dev/null
+++ b/core/java/android/flags/FeatureFlags.java
@@ -0,0 +1,379 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.flags;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.ArraySet;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A class for querying constants from the system - primarily booleans.
+ *
+ * Clients using this class can define their flags and their default values in one place,
+ * can override those values on running devices for debugging and testing purposes, and can control
+ * what flags are available to be used on release builds.
+ *
+ * TODO(b/279054964): A lot. This is skeleton code right now.
+ * @hide
+ */
+public class FeatureFlags {
+    private static final String TAG = "FeatureFlags";
+    private static FeatureFlags sInstance;
+    private static final Object sInstanceLock = new Object();
+
+    private final Set<Flag<?>> mKnownFlags = new ArraySet<>();
+    private final Set<Flag<?>> mDirtyFlags = new ArraySet<>();
+
+    private IFeatureFlags mIFeatureFlags;
+    private final Map<String, Map<String, Boolean>> mBooleanOverrides = new HashMap<>();
+    private final Set<ChangeListener> mListeners = new HashSet<>();
+
+    /**
+     * Obtain a per-process instance of FeatureFlags.
+     * @return A singleton instance of {@link FeatureFlags}.
+     */
+    @NonNull
+    public static FeatureFlags getInstance() {
+        synchronized (sInstanceLock) {
+            if (sInstance == null) {
+                sInstance = new FeatureFlags();
+            }
+        }
+
+        return sInstance;
+    }
+
+    /** See {@link FeatureFlagsFake}. */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    public static void setInstance(FeatureFlags instance) {
+        synchronized (sInstanceLock) {
+            sInstance = instance;
+        }
+    }
+
+    private final IFeatureFlagsCallback mIFeatureFlagsCallback = new IFeatureFlagsCallback.Stub() {
+        @Override
+        public void onFlagChange(SyncableFlag flag) {
+            for (Flag<?> f : mKnownFlags) {
+                if (flagEqualsSyncableFlag(f, flag)) {
+                    if (f instanceof DynamicFlag<?>) {
+                        if (f instanceof DynamicBooleanFlag) {
+                            String value = flag.getValue();
+                            if (value == null) {  // Null means any existing overrides were erased.
+                                value = ((DynamicBooleanFlag) f).getDefault().toString();
+                            }
+                            addBooleanOverride(flag.getNamespace(), flag.getName(), value);
+                        }
+                        FeatureFlags.this.onFlagChange((DynamicFlag<?>) f);
+                    }
+                    break;
+                }
+            }
+        }
+    };
+
+    private FeatureFlags() {
+        this(null);
+    }
+
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    public FeatureFlags(IFeatureFlags iFeatureFlags) {
+        mIFeatureFlags = iFeatureFlags;
+
+        if (mIFeatureFlags != null) {
+            try {
+                mIFeatureFlags.registerCallback(mIFeatureFlagsCallback);
+            } catch (RemoteException e) {
+                // Shouldn't happen with things passed into tests.
+                Log.e(TAG, "Could not register callbacks!", e);
+            }
+        }
+    }
+
+    /**
+     * Construct a new {@link BooleanFlag}.
+     *
+     * Use this instead of constructing a {@link BooleanFlag} directly, as it registers the flag
+     * with the internals of the flagging system.
+     */
+    @NonNull
+    public static BooleanFlag booleanFlag(
+            @NonNull String namespace, @NonNull String name, boolean def) {
+        return getInstance().addFlag(new BooleanFlag(namespace, name, def));
+    }
+
+    /**
+     * Construct a new {@link FusedOffFlag}.
+     *
+     * Use this instead of constructing a {@link FusedOffFlag} directly, as it registers the
+     * flag with the internals of the flagging system.
+     */
+    @NonNull
+    public static FusedOffFlag fusedOffFlag(@NonNull String namespace, @NonNull String name) {
+        return getInstance().addFlag(new FusedOffFlag(namespace, name));
+    }
+
+    /**
+     * Construct a new {@link FusedOnFlag}.
+     *
+     * Use this instead of constructing a {@link FusedOnFlag} directly, as it registers the flag
+     * with the internals of the flagging system.
+     */
+    @NonNull
+    public static FusedOnFlag fusedOnFlag(@NonNull String namespace, @NonNull String name) {
+        return getInstance().addFlag(new FusedOnFlag(namespace, name));
+    }
+
+    /**
+     * Construct a new {@link DynamicBooleanFlag}.
+     *
+     * Use this instead of constructing a {@link DynamicBooleanFlag} directly, as it registers
+     * the flag with the internals of the flagging system.
+     */
+    @NonNull
+    public static DynamicBooleanFlag dynamicBooleanFlag(
+            @NonNull String namespace, @NonNull String name, boolean def) {
+        return getInstance().addFlag(new DynamicBooleanFlag(namespace, name, def));
+    }
+
+    /**
+     * Add a listener to be alerted when a {@link DynamicFlag} changes.
+     *
+     * See also {@link #removeChangeListener(ChangeListener)}.
+     *
+     * @param listener The listener to add.
+     */
+    public void addChangeListener(@NonNull ChangeListener listener) {
+        mListeners.add(listener);
+    }
+
+    /**
+     * Remove a listener that was added earlier.
+     *
+     * See also {@link #addChangeListener(ChangeListener)}.
+     *
+     * @param listener The listener to remove.
+     */
+    public void removeChangeListener(@NonNull ChangeListener listener) {
+        mListeners.remove(listener);
+    }
+
+    protected void onFlagChange(@NonNull DynamicFlag<?> flag) {
+        for (ChangeListener l : mListeners) {
+            l.onFlagChanged(flag);
+        }
+    }
+
+    /**
+     * Returns whether the supplied flag is true or not.
+     *
+     * {@link BooleanFlag} should only be used in debug builds. They do not get optimized out.
+     *
+     * The first time a flag is read, its value is cached for the lifetime of the process.
+     */
+    public boolean isEnabled(@NonNull BooleanFlag flag) {
+        return getBooleanInternal(flag);
+    }
+
+    /**
+     * Returns whether the supplied flag is true or not.
+     *
+     * Always returns false.
+     */
+    public boolean isEnabled(@NonNull FusedOffFlag flag) {
+        return false;
+    }
+
+    /**
+     * Returns whether the supplied flag is true or not.
+     *
+     * Always returns true;
+     */
+    public boolean isEnabled(@NonNull FusedOnFlag flag) {
+        return true;
+    }
+
+    /**
+     * Returns whether the supplied flag is true or not.
+     *
+     * Can return a different value for the flag each time it is called if an override comes in.
+     */
+    public boolean isCurrentlyEnabled(@NonNull DynamicBooleanFlag flag) {
+        return getBooleanInternal(flag);
+    }
+
+    private boolean getBooleanInternal(Flag<Boolean> flag) {
+        sync();
+        Map<String, Boolean> ns = mBooleanOverrides.get(flag.getNamespace());
+        Boolean value = null;
+        if (ns != null) {
+            value = ns.get(flag.getName());
+        }
+        if (value == null) {
+            throw new IllegalStateException("Boolean flag being read but was not synced: " + flag);
+        }
+
+        return value;
+    }
+
+    private <T extends Flag<?>> T addFlag(T flag)  {
+        synchronized (FeatureFlags.class) {
+            mDirtyFlags.add(flag);
+            mKnownFlags.add(flag);
+        }
+        return flag;
+    }
+
+    /**
+     * Sync any known flags that have not yet been synced.
+     *
+     * This is called implicitly when any flag is read, and is not generally needed except in
+     * exceptional circumstances.
+     */
+    public void sync() {
+        synchronized (FeatureFlags.class) {
+            if (mDirtyFlags.isEmpty()) {
+                return;
+            }
+            syncInternal(mDirtyFlags);
+            mDirtyFlags.clear();
+        }
+    }
+
+    /**
+     * Called when new flags have been declared. Gives the implementation a chance to act on them.
+     *
+     * Guaranteed to be called from a synchronized, thread-safe context.
+     */
+    protected void syncInternal(Set<Flag<?>> dirtyFlags) {
+        IFeatureFlags iFeatureFlags = bind();
+        List<SyncableFlag> syncableFlags = new ArrayList<>();
+        for (Flag<?> f : dirtyFlags) {
+            syncableFlags.add(flagToSyncableFlag(f));
+        }
+
+        List<SyncableFlag> serverFlags = List.of();  // Need to initialize the list with something.
+        try {
+            // New values come back from the service.
+            serverFlags = iFeatureFlags.syncFlags(syncableFlags);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+
+        for (Flag<?> f : dirtyFlags) {
+            boolean found = false;
+            for (SyncableFlag sf : serverFlags) {
+                if (flagEqualsSyncableFlag(f, sf)) {
+                    if (f instanceof BooleanFlag || f instanceof DynamicBooleanFlag) {
+                        addBooleanOverride(sf.getNamespace(), sf.getName(), sf.getValue());
+                    }
+                    found = true;
+                    break;
+                }
+            }
+            if (!found) {
+                if (f instanceof BooleanFlag) {
+                    addBooleanOverride(
+                            f.getNamespace(),
+                            f.getName(),
+                            ((BooleanFlag) f).getDefault() ? "true" : "false");
+                }
+            }
+        }
+    }
+
+    private void addBooleanOverride(String namespace, String name, String override) {
+        Map<String, Boolean> nsOverrides = mBooleanOverrides.get(namespace);
+        if (nsOverrides == null) {
+            nsOverrides = new HashMap<>();
+            mBooleanOverrides.put(namespace, nsOverrides);
+        }
+        nsOverrides.put(name, parseBoolean(override));
+    }
+
+    private SyncableFlag flagToSyncableFlag(Flag<?> f) {
+        return new SyncableFlag(
+                f.getNamespace(),
+                f.getName(),
+                f.getDefault().toString(),
+                f instanceof DynamicFlag<?>);
+    }
+
+    private IFeatureFlags bind() {
+        if (mIFeatureFlags == null) {
+            mIFeatureFlags = IFeatureFlags.Stub.asInterface(
+                    ServiceManager.getService(Context.FEATURE_FLAGS_SERVICE));
+            try {
+                mIFeatureFlags.registerCallback(mIFeatureFlagsCallback);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Failed to listen for flag changes!");
+            }
+        }
+
+        return mIFeatureFlags;
+    }
+
+    static boolean parseBoolean(String value) {
+        // Check for a truish string.
+        boolean result = value.equalsIgnoreCase("true")
+                || value.equals("1")
+                || value.equalsIgnoreCase("t")
+                || value.equalsIgnoreCase("on");
+        if (!result) {  // Expect a falsish string, else log an error.
+            if (!(value.equalsIgnoreCase("false")
+                    || value.equals("0")
+                    || value.equalsIgnoreCase("f")
+                    || value.equalsIgnoreCase("off"))) {
+                Log.e(TAG,
+                        "Tried parsing " + value + " as boolean but it doesn't look like one. "
+                                + "Value expected to be one of true|false, 1|0, t|f, on|off.");
+            }
+        }
+        return result;
+    }
+
+    private static boolean flagEqualsSyncableFlag(Flag<?> f, SyncableFlag sf) {
+        return f.getName().equals(sf.getName()) && f.getNamespace().equals(sf.getNamespace());
+    }
+
+
+    /**
+     * A simpler listener that is alerted when a {@link DynamicFlag} changes.
+     *
+     * See {@link #addChangeListener(ChangeListener)}
+     */
+    public interface ChangeListener {
+        /**
+         * Called when a {@link DynamicFlag} changes.
+         *
+         * @param flag The flag that has changed.
+         */
+        void onFlagChanged(DynamicFlag<?> flag);
+    }
+}
diff --git a/core/java/android/flags/FeatureFlagsFake.java b/core/java/android/flags/FeatureFlagsFake.java
new file mode 100644
index 0000000..daedcda
--- /dev/null
+++ b/core/java/android/flags/FeatureFlagsFake.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 android.flags;
+
+import android.annotation.NonNull;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * An implementation of {@link FeatureFlags} for testing.
+ *
+ * Before you read a flag from using this Fake, you must set that flag using
+ * {@link #setFlagValue(BooleanFlagBase, boolean)}. This ensures that your tests are deterministic.
+ *
+ * If you are relying on {@link FeatureFlags#getInstance()} to access FeatureFlags in your code
+ * under test, (instead of dependency injection), you can pass an instance of this fake to
+ * {@link FeatureFlags#setInstance(FeatureFlags)}. Be sure to call that method again, passing null,
+ * to ensure hermetic testing - you don't want static state persisting between your test methods.
+ *
+ * @hide
+ */
+public class FeatureFlagsFake extends FeatureFlags {
+    private final Map<BooleanFlagBase, Boolean> mFlagValues = new HashMap<>();
+    private final Set<BooleanFlagBase> mReadFlags = new HashSet<>();
+
+    public FeatureFlagsFake(IFeatureFlags iFeatureFlags) {
+        super(iFeatureFlags);
+    }
+
+    @Override
+    public boolean isEnabled(@NonNull BooleanFlag flag) {
+        return requireFlag(flag);
+    }
+
+    @Override
+    public boolean isEnabled(@NonNull FusedOffFlag flag) {
+        return requireFlag(flag);
+    }
+
+    @Override
+    public boolean isEnabled(@NonNull FusedOnFlag flag) {
+        return requireFlag(flag);
+    }
+
+    @Override
+    public boolean isCurrentlyEnabled(@NonNull DynamicBooleanFlag flag) {
+        return requireFlag(flag);
+    }
+
+    @Override
+    protected void syncInternal(Set<Flag<?>> dirtyFlags) {
+    }
+
+    /**
+     * Explicitly set a flag's value for reading in tests.
+     *
+     * You _must_ call this for every flag your code-under-test will read. Otherwise, an
+     * {@link IllegalStateException} will be thrown.
+     *
+     * You are able to set values for {@link FusedOffFlag} and {@link FusedOnFlag}, despite those
+     * flags having a fixed value at compile time, since unit tests should still test the state of
+     * those flags as both true and false. I.e. a flag that is off might be turned on in a future
+     * build or vice versa.
+     *
+     * You can not call this method _after_ a non-dynamic flag has been read. Non-dynamic flags
+     * are held stable in the system, so changing a value after reading would not match
+     * real-implementation behavior.
+     *
+     * Calling this method will trigger any {@link android.flags.FeatureFlags.ChangeListener}s that
+     * are registered for the supplied flag if the flag is a {@link DynamicFlag}.
+     *
+     * @param flag  The BooleanFlag that you want to set a value for.
+     * @param value The value that the flag should return when accessed.
+     */
+    public void setFlagValue(@NonNull BooleanFlagBase flag, boolean value) {
+        if (!(flag instanceof DynamicBooleanFlag) && mReadFlags.contains(flag)) {
+            throw new RuntimeException(
+                    "You can not set the value of a flag after it has been read. Tried to set "
+                            + flag + " to " + value + " but it already " + mFlagValues.get(flag));
+        }
+        mFlagValues.put(flag, value);
+        if (flag instanceof DynamicBooleanFlag) {
+            onFlagChange((DynamicFlag<?>) flag);
+        }
+    }
+
+    private boolean requireFlag(BooleanFlagBase flag) {
+        if (!mFlagValues.containsKey(flag)) {
+            throw new IllegalStateException(
+                    "Tried to access " + flag + " in test but no overrided specified. You must "
+                            + "call #setFlagValue for each flag read in a test.");
+        }
+        mReadFlags.add(flag);
+
+        return mFlagValues.get(flag);
+    }
+
+}
diff --git a/core/java/android/flags/Flag.java b/core/java/android/flags/Flag.java
new file mode 100644
index 0000000..b97a4c8
--- /dev/null
+++ b/core/java/android/flags/Flag.java
@@ -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 android.flags;
+
+import android.annotation.NonNull;
+
+/**
+ * Base class for constants read via {@link android.flags.FeatureFlags}.
+ *
+ * @param <T> The type of value that this flag stores. E.g. Boolean or String.
+ *
+ * @hide
+ */
+public interface Flag<T> {
+    /** The namespace for a flag. Should combine uniquely with its name. */
+    @NonNull
+    String getNamespace();
+
+    /** The name of the flag. Should combine uniquely with its namespace. */
+    @NonNull
+    String getName();
+
+    /** The value of this flag if no override has been set. Null values are not supported. */
+    @NonNull
+    T getDefault();
+
+    /** Returns true if the value of this flag can change at runtime. */
+    default boolean isDynamic() {
+        return false;
+    }
+
+    /**
+     * Add human-readable details to the flag. Flag client's are not required to set this.
+     *
+     * See {@link #getLabel()}, {@link #getDescription()}, and {@link #getCategoryName()}.
+     *
+     * @return Returns `this`, to make a fluent api.
+     */
+    Flag<T> defineMetaData(String label, String description, String categoryName);
+
+    /**
+     * A human-readable name for the flag. Defaults to {@link #getName()}
+     *
+     * See {@link #defineMetaData(String, String, String)}
+     */
+    @NonNull
+    default String getLabel() {
+        return getName();
+    }
+
+    /**
+     * A human-readable description for the flag. Defaults to null if unset.
+     *
+     * See {@link #defineMetaData(String, String, String)}
+     */
+    default String getDescription() {
+        return null;
+    }
+
+    /**
+     * A human-readable category name for the flag. Defaults to null if unset.
+     *
+     * See {@link #defineMetaData(String, String, String)}
+     */
+    default String getCategoryName() {
+        return null;
+    }
+}
diff --git a/core/java/android/flags/FusedOffFlag.java b/core/java/android/flags/FusedOffFlag.java
new file mode 100644
index 0000000..6844b8f
--- /dev/null
+++ b/core/java/android/flags/FusedOffFlag.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.flags;
+
+import android.annotation.NonNull;
+import android.provider.DeviceConfig;
+
+/**
+ * A flag representing a false value.
+ *
+ * The flag can never be changed or overridden. It is false at compile time.
+ *
+ * @hide
+ */
+public final class FusedOffFlag extends BooleanFlagBase {
+    /**
+     * @param namespace A namespace for this flag. See {@link DeviceConfig}.
+     * @param name      A name for this flag.
+     */
+    FusedOffFlag(String namespace, String name) {
+        super(namespace, name);
+    }
+
+    @Override
+    @NonNull
+    public Boolean getDefault() {
+        return false;
+    }
+
+    @Override
+    public FusedOffFlag defineMetaData(String label, String description, String categoryName) {
+        super.defineMetaData(label, description, categoryName);
+        return this;
+    }
+}
diff --git a/core/java/android/flags/FusedOnFlag.java b/core/java/android/flags/FusedOnFlag.java
new file mode 100644
index 0000000..e9adba7
--- /dev/null
+++ b/core/java/android/flags/FusedOnFlag.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.flags;
+
+import android.annotation.NonNull;
+import android.provider.DeviceConfig;
+
+/**
+ * A flag representing a true value.
+ *
+ * The flag can never be changed or overridden. It is true at compile time.
+ *
+ * @hide
+ */
+public final class FusedOnFlag extends BooleanFlagBase {
+    /**
+     * @param namespace A namespace for this flag. See {@link DeviceConfig}.
+     * @param name      A name for this flag.
+     */
+    FusedOnFlag(String namespace, String name) {
+        super(namespace, name);
+    }
+
+    @Override
+    @NonNull
+    public Boolean getDefault() {
+        return true;
+    }
+
+    @Override
+    public FusedOnFlag defineMetaData(String label, String description, String categoryName) {
+        super.defineMetaData(label, description, categoryName);
+        return this;
+    }
+}
diff --git a/core/java/android/flags/IFeatureFlags.aidl b/core/java/android/flags/IFeatureFlags.aidl
new file mode 100644
index 0000000..3efcec9
--- /dev/null
+++ b/core/java/android/flags/IFeatureFlags.aidl
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+ package android.flags;
+
+import android.flags.IFeatureFlagsCallback;
+import android.flags.SyncableFlag;
+
+/**
+ * Binder interface for communicating with {@link com.android.server.flags.FeatureFlagsService}.
+ *
+ * This interface is used by {@link android.flags.FeatureFlags} and developers should use that to
+ * interface with the service. FeatureFlags is the "client" in this documentation.
+ *
+ * The methods allow client apps to communicate what flags they care about, and receive back
+ * current values for those flags. For stable flags, this is the finalized value until the device
+ * restarts. For {@link DynamicFlag}s, this is the last known value, though it may change in the
+ * future. Clients can listen for changes to flag values so that it can react accordingly.
+ * @hide
+ */
+interface IFeatureFlags {
+    /**
+     * Synchronize with the {@link com.android.server.flags.FeatureFlagsService} about flags of
+     * interest.
+     *
+     * The client should pass in a list of flags that it is using as {@link SyncableFlag}s, which
+     * includes what it thinks the default values of the flags are.
+     *
+     * The response will contain a list of matching SyncableFlags, whose values are set to what the
+     * value of the flags actually are. The client should update its internal state flag data to
+     * match.
+     *
+     * Generally speaking, if a flag that is passed in is new to the FeatureFlagsService, the
+     * service will cache the passed-in value, and return it back out. If, however, a different
+     * client has synced that flag with the service previously, FeatureFlagsService will return the
+     * existing cached value, which may or may not be what the current client passed in. This allows
+     * FeatureFlagsService to keep clients in agreement with one another.
+     */
+    List<SyncableFlag> syncFlags(in List<SyncableFlag> flagList);
+
+    /**
+     * Pass in an {@link IFeatureFlagsCallback} that will be called whenever a {@link DymamicFlag}
+     * changes.
+     */
+    void registerCallback(IFeatureFlagsCallback callback);
+
+    /**
+     * Remove a {@link IFeatureFlagsCallback} that was previously registered with
+     * {@link #registerCallback}.
+     */
+    void unregisterCallback(IFeatureFlagsCallback callback);
+
+    /**
+     * Query the {@link com.android.server.flags.FeatureFlagsService} for flags, but don't
+     * cache them. See {@link #syncFlags}.
+     *
+     * You almost certainly don't want this method. This is intended for the Flag Flipper
+     * application that needs to query the state of system but doesn't want to affect it by
+     * doing so. All other clients should use {@link syncFlags}.
+     */
+    List<SyncableFlag> queryFlags(in List<SyncableFlag> flagList);
+
+    /**
+     * Change a flags value in the system.
+     *
+     * This is intended for use by the Flag Flipper application.
+     */
+    void overrideFlag(in SyncableFlag flag);
+
+    /**
+     * Restore a flag to its default value.
+     *
+     * This is intended for use by the Flag Flipper application.
+     */
+    void resetFlag(in SyncableFlag flag);
+}
\ No newline at end of file
diff --git a/core/java/android/flags/IFeatureFlagsCallback.aidl b/core/java/android/flags/IFeatureFlagsCallback.aidl
new file mode 100644
index 0000000..f708667
--- /dev/null
+++ b/core/java/android/flags/IFeatureFlagsCallback.aidl
@@ -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 android.flags;
+
+import android.flags.SyncableFlag;
+
+/**
+ * Callback for {@link IFeatureFlags#registerCallback} to get alerts when a {@link DynamicFlag}
+ * changes.
+ *
+ * DynamicFlags can change at run time. Stable flags will never result in a call to this method.
+ *
+ * @hide
+ */
+oneway interface IFeatureFlagsCallback {
+    void onFlagChange(in SyncableFlag flag);
+}
\ No newline at end of file
diff --git a/core/java/android/flags/OWNERS b/core/java/android/flags/OWNERS
new file mode 100644
index 0000000..fa125c4
--- /dev/null
+++ b/core/java/android/flags/OWNERS
@@ -0,0 +1,7 @@
+# Bug component: 1306523
+
+mankoff@google.com
+pixel@google.com
+
+dsandler@android.com
+
diff --git a/core/java/android/flags/SyncableFlag.aidl b/core/java/android/flags/SyncableFlag.aidl
new file mode 100644
index 0000000..1526ec1
--- /dev/null
+++ b/core/java/android/flags/SyncableFlag.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.flags;
+
+/**
+ * A parcelable data class for serializing {@link Flag} across a Binder.
+ */
+parcelable SyncableFlag;
\ No newline at end of file
diff --git a/core/java/android/flags/SyncableFlag.java b/core/java/android/flags/SyncableFlag.java
new file mode 100644
index 0000000..449bcc3c
--- /dev/null
+++ b/core/java/android/flags/SyncableFlag.java
@@ -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 android.flags;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * @hide
+ */
+public final class SyncableFlag implements Parcelable {
+    private final String mNamespace;
+    private final String mName;
+    private final String mValue;
+    private final boolean mDynamic;
+    private final boolean mOverridden;
+
+    public SyncableFlag(
+            @NonNull String namespace,
+            @NonNull String name,
+            @NonNull String value,
+            boolean dynamic) {
+        this(namespace, name, value, dynamic, false);
+    }
+
+    public SyncableFlag(
+            @NonNull String namespace,
+            @NonNull String name,
+            @NonNull String value,
+            boolean dynamic,
+            boolean overridden
+    ) {
+        mNamespace = namespace;
+        mName = name;
+        mValue = value;
+        mDynamic = dynamic;
+        mOverridden = overridden;
+    }
+
+    @NonNull
+    public String getNamespace() {
+        return mNamespace;
+    }
+
+    @NonNull
+    public String getName() {
+        return mName;
+    }
+
+    @NonNull
+    public String getValue() {
+        return mValue;
+    }
+
+    public boolean isDynamic() {
+        return mDynamic;
+    }
+
+    public boolean isOverridden() {
+        return mOverridden;
+    }
+
+    @NonNull
+    public static final Parcelable.Creator<SyncableFlag> CREATOR = new Parcelable.Creator<>() {
+        public SyncableFlag createFromParcel(Parcel in) {
+            return new SyncableFlag(
+                    in.readString(),
+                    in.readString(),
+                    in.readString(),
+                    in.readBoolean(),
+                    in.readBoolean());
+        }
+
+        public SyncableFlag[] newArray(int size) {
+            return new SyncableFlag[size];
+        }
+    };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeString(mNamespace);
+        dest.writeString(mName);
+        dest.writeString(mValue);
+        dest.writeBoolean(mDynamic);
+        dest.writeBoolean(mOverridden);
+    }
+
+    @Override
+    public String toString() {
+        return getNamespace() + "." + getName() + "[" + getValue() + "]";
+    }
+}
diff --git a/core/java/android/hardware/HardwareBuffer.java b/core/java/android/hardware/HardwareBuffer.java
index ddbfb9e..889a43c 100644
--- a/core/java/android/hardware/HardwareBuffer.java
+++ b/core/java/android/hardware/HardwareBuffer.java
@@ -40,7 +40,7 @@
  * HardwareBuffer wraps a native <code>AHardwareBuffer</code> object, which is a low-level object
  * representing a memory buffer accessible by various hardware units. HardwareBuffer allows sharing
  * buffers across different application processes. In particular, HardwareBuffers may be mappable
- * to memory accessibly to various hardware systems, such as the GPU, a sensor or context hub, or
+ * to memory accessible to various hardware systems, such as the GPU, a sensor or context hub, or
  * other auxiliary processing units.
  *
  * For more information, see the NDK documentation for <code>AHardwareBuffer</code>.
diff --git a/core/java/android/hardware/SensorAdditionalInfo.java b/core/java/android/hardware/SensorAdditionalInfo.java
index 12edc5e..59def9f 100644
--- a/core/java/android/hardware/SensorAdditionalInfo.java
+++ b/core/java/android/hardware/SensorAdditionalInfo.java
@@ -63,7 +63,7 @@
     public final int[] intValues;
 
     /**
-     * Typical values of additional infomation type. The set of values is subject to extension in
+     * Typical values of additional information type. The set of values is subject to extension in
      * newer versions and vendors have the freedom of define their own custom values.
      *
      * @hide
diff --git a/core/java/android/hardware/SensorManager.java b/core/java/android/hardware/SensorManager.java
index c2aebd7..f033f97 100644
--- a/core/java/android/hardware/SensorManager.java
+++ b/core/java/android/hardware/SensorManager.java
@@ -893,9 +893,9 @@
 
     /**
      * Flushes the FIFO of all the sensors registered for this listener. If there are events
-     * in the FIFO of the sensor, they are returned as if the maxReportLantecy of the FIFO has
+     * in the FIFO of the sensor, they are returned as if the maxReportLatency of the FIFO has
      * expired. Events are returned in the usual way through the SensorEventListener.
-     * This call doesn't affect the maxReportLantecy for this sensor. This call is asynchronous and
+     * This call doesn't affect the maxReportLatency for this sensor. This call is asynchronous and
      * returns immediately.
      * {@link android.hardware.SensorEventListener2#onFlushCompleted onFlushCompleted} is called
      * after all the events in the batch at the time of calling this method have been delivered
@@ -923,7 +923,7 @@
      * Create a sensor direct channel backed by shared memory wrapped in MemoryFile object.
      *
      * The resulting channel can be used for delivering sensor events to native code, other
-     * processes, GPU/DSP or other co-processors without CPU intervention. This is the recommanded
+     * processes, GPU/DSP or other co-processors without CPU intervention. This is the recommended
      * for high performance sensor applications that use high sensor rates (e.g. greater than 200Hz)
      * and cares about sensor event latency.
      *
@@ -945,7 +945,7 @@
      * Create a sensor direct channel backed by shared memory wrapped in HardwareBuffer object.
      *
      * The resulting channel can be used for delivering sensor events to native code, other
-     * processes, GPU/DSP or other co-processors without CPU intervention. This is the recommanded
+     * processes, GPU/DSP or other co-processors without CPU intervention. This is the recommended
      * for high performance sensor applications that use high sensor rates (e.g. greater than 200Hz)
      * and cares about sensor event latency.
      *
@@ -1355,11 +1355,11 @@
      *        returned by {@link #getRotationMatrix}.
      *
      * @param X
-     *        defines the axis of the new cooridinate system that coincide with the X axis of the
+     *        defines the axis of the new coordinate system that coincide with the X axis of the
      *        original coordinate system.
      *
      * @param Y
-     *        defines the axis of the new cooridinate system that coincide with the Y axis of the
+     *        defines the axis of the new coordinate system that coincide with the Y axis of the
      *        original coordinate system.
      *
      * @param outR
@@ -1847,7 +1847,7 @@
      * This method is used to inject raw sensor data into the HAL.  Call {@link
      * initDataInjection(boolean)} before this method to set the HAL in data injection mode. This
      * method should be called only if a previous call to initDataInjection has been successful and
-     * the HAL and SensorService are already opreating in data injection mode.
+     * the HAL and SensorService are already operating in data injection mode.
      *
      * @param sensor The sensor to inject.
      * @param values Sensor values to inject. The length of this
diff --git a/core/java/android/hardware/SystemSensorManager.java b/core/java/android/hardware/SystemSensorManager.java
index d8ab6f7..0f6f601 100644
--- a/core/java/android/hardware/SystemSensorManager.java
+++ b/core/java/android/hardware/SystemSensorManager.java
@@ -565,7 +565,7 @@
                 public void onReceive(Context context, Intent intent) {
                     if (intent.getAction().equals(Intent.ACTION_DYNAMIC_SENSOR_CHANGED)) {
                         if (DEBUG_DYNAMIC_SENSOR) {
-                            Log.i(TAG, "DYNS received DYNAMIC_SENSOR_CHANED broadcast");
+                            Log.i(TAG, "DYNS received DYNAMIC_SENSOR_CHANGED broadcast");
                         }
                         // Dynamic sensors probably changed
                         mDynamicSensorListDirty = true;
@@ -610,7 +610,7 @@
     protected void unregisterDynamicSensorCallbackImpl(
             DynamicSensorCallback callback) {
         if (DEBUG_DYNAMIC_SENSOR) {
-            Log.i(TAG, "Removing dynamic sensor listerner");
+            Log.i(TAG, "Removing dynamic sensor listener");
         }
         mDynamicSensorCallbacks.remove(callback);
     }
@@ -740,7 +740,7 @@
             }
             if (hardwareBuffer.getWidth() < MIN_DIRECT_CHANNEL_BUFFER_SIZE) {
                 throw new IllegalArgumentException(
-                        "Width if HaradwareBuffer must be greater than "
+                        "Width if HardwareBuffer must be greater than "
                         + MIN_DIRECT_CHANNEL_BUFFER_SIZE);
             }
             if ((hardwareBuffer.getUsage() & HardwareBuffer.USAGE_SENSOR_DIRECT_DATA) == 0) {
@@ -771,7 +771,7 @@
 
     /*
      * BaseEventQueue is the communication channel with the sensor service,
-     * SensorEventQueue, TriggerEventQueue are subclases and there is one-to-one mapping between
+     * SensorEventQueue, TriggerEventQueue are subclasses and there is one-to-one mapping between
      * the queues and the listeners. InjectEventQueue is also a sub-class which is a special case
      * where data is being injected into the sensor HAL through the sensor service. It is not
      * associated with any listener and there is one InjectEventQueue associated with a
diff --git a/core/java/android/hardware/biometrics/BiometricFingerprintConstants.java b/core/java/android/hardware/biometrics/BiometricFingerprintConstants.java
index 5b24fb6..257ad71 100644
--- a/core/java/android/hardware/biometrics/BiometricFingerprintConstants.java
+++ b/core/java/android/hardware/biometrics/BiometricFingerprintConstants.java
@@ -218,8 +218,7 @@
             FINGERPRINT_ACQUIRED_UNKNOWN,
             FINGERPRINT_ACQUIRED_IMMOBILE,
             FINGERPRINT_ACQUIRED_TOO_BRIGHT,
-            FINGERPRINT_ACQUIRED_POWER_PRESSED,
-            FINGERPRINT_ACQUIRED_RE_ENROLL})
+            FINGERPRINT_ACQUIRED_POWER_PRESSED})
     @Retention(RetentionPolicy.SOURCE)
     @interface FingerprintAcquired {}
 
@@ -311,12 +310,6 @@
     int FINGERPRINT_ACQUIRED_POWER_PRESSED = 11;
 
     /**
-     * This message is sent to encourage the user to re-enroll their fingerprints.
-     * @hide
-     */
-    int FINGERPRINT_ACQUIRED_RE_ENROLL = 12;
-
-    /**
      * @hide
      */
     int FINGERPRINT_ACQUIRED_VENDOR_BASE = 1000;
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index 2e40f60..912e8df 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -144,6 +144,7 @@
         private Context mContext;
         private IAuthService mService;
 
+        // LINT.IfChange
         /**
          * Creates a builder for a {@link BiometricPrompt} dialog.
          * @param context The {@link Context} that will be used to build the prompt.
@@ -417,6 +418,7 @@
          * @hide
          */
         @NonNull
+        @RequiresPermission(anyOf = {USE_BIOMETRIC_INTERNAL})
         public Builder setDisallowBiometricsIfPolicyExists(boolean checkDevicePolicyManager) {
             mPromptInfo.setDisallowBiometricsIfPolicyExists(checkDevicePolicyManager);
             return this;
@@ -429,6 +431,7 @@
          * @hide
          */
         @NonNull
+        @RequiresPermission(anyOf = {USE_BIOMETRIC_INTERNAL})
         public Builder setReceiveSystemEvents(boolean set) {
             mPromptInfo.setReceiveSystemEvents(set);
             return this;
@@ -442,6 +445,7 @@
          * @hide
          */
         @NonNull
+        @RequiresPermission(anyOf = {TEST_BIOMETRIC, USE_BIOMETRIC_INTERNAL})
         public Builder setIgnoreEnrollmentState(boolean ignoreEnrollmentState) {
             mPromptInfo.setIgnoreEnrollmentState(ignoreEnrollmentState);
             return this;
@@ -454,10 +458,12 @@
          * @hide
          */
         @NonNull
+        @RequiresPermission(anyOf = {TEST_BIOMETRIC, USE_BIOMETRIC_INTERNAL})
         public Builder setIsForLegacyFingerprintManager(int sensorId) {
             mPromptInfo.setIsForLegacyFingerprintManager(sensorId);
             return this;
         }
+        // LINT.ThenChange(frameworks/base/core/java/android/hardware/biometrics/PromptInfo.java)
 
         /**
          * Creates a {@link BiometricPrompt}.
diff --git a/core/java/android/hardware/biometrics/PromptInfo.java b/core/java/android/hardware/biometrics/PromptInfo.java
index 02aad1d..e275078 100644
--- a/core/java/android/hardware/biometrics/PromptInfo.java
+++ b/core/java/android/hardware/biometrics/PromptInfo.java
@@ -113,6 +113,7 @@
         dest.writeBoolean(mIsForLegacyFingerprintManager);
     }
 
+    // LINT.IfChange
     public boolean containsTestConfigurations() {
         if (mIsForLegacyFingerprintManager
                 && mAllowedSensorIds.size() == 1
@@ -122,6 +123,10 @@
             return true;
         } else if (mAllowBackgroundAuthentication) {
             return true;
+        } else if (mIsForLegacyFingerprintManager) {
+            return true;
+        } else if (mIgnoreEnrollmentState) {
+            return true;
         }
         return false;
     }
@@ -144,6 +149,7 @@
         }
         return false;
     }
+    // LINT.ThenChange(frameworks/base/core/java/android/hardware/biometrics/BiometricPrompt.java)
 
     // Setters
 
diff --git a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
index d48e20e..6baf91d7 100644
--- a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
@@ -34,6 +34,7 @@
 import android.hardware.camera2.impl.CameraMetadataNative;
 import android.hardware.camera2.params.ExtensionSessionConfiguration;
 import android.hardware.camera2.params.StreamConfigurationMap;
+import android.os.Binder;
 import android.os.ConditionVariable;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -48,7 +49,6 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
-import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
@@ -346,28 +346,29 @@
             }
         }
 
-        public long registerClient(Context ctx) {
+        public boolean registerClient(Context ctx, IBinder token) {
             synchronized (mLock) {
                 connectToProxyLocked(ctx);
-                if (mProxy != null) {
-                    try {
-                        return mProxy.registerClient();
-                    } catch (RemoteException e) {
-                        Log.e(TAG, "Failed to initialize extension! Extension service does "
-                                + " not respond!");
-                        return -1;
-                    }
-                } else {
-                    return -1;
+                if (mProxy == null) {
+                    return false;
                 }
+
+                try {
+                    return mProxy.registerClient(token);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "Failed to initialize extension! Extension service does "
+                            + " not respond!");
+                }
+
+                return false;
             }
         }
 
-        public void unregisterClient(long clientId) {
+        public void unregisterClient(IBinder token) {
             synchronized (mLock) {
                 if (mProxy != null) {
                     try {
-                        mProxy.unregisterClient(clientId);
+                        mProxy.unregisterClient(token);
                     } catch (RemoteException e) {
                         Log.e(TAG, "Failed to de-initialize extension! Extension service does"
                                 + " not respond!");
@@ -438,15 +439,15 @@
     /**
      * @hide
      */
-    public static long registerClient(Context ctx) {
-        return CameraExtensionManagerGlobal.get().registerClient(ctx);
+    public static boolean registerClient(Context ctx, IBinder token) {
+        return CameraExtensionManagerGlobal.get().registerClient(ctx, token);
     }
 
     /**
      * @hide
      */
-    public static void unregisterClient(long clientId) {
-        CameraExtensionManagerGlobal.get().unregisterClient(clientId);
+    public static void unregisterClient(IBinder token) {
+        CameraExtensionManagerGlobal.get().unregisterClient(token);
     }
 
     /**
@@ -564,8 +565,9 @@
      */
     public @NonNull List<Integer> getSupportedExtensions() {
         ArrayList<Integer> ret = new ArrayList<>();
-        long clientId = registerClient(mContext);
-        if (clientId < 0) {
+        final IBinder token = new Binder(TAG + "#getSupportedExtensions:" + mCameraId);
+        boolean success = registerClient(mContext, token);
+        if (!success) {
             return Collections.unmodifiableList(ret);
         }
 
@@ -576,7 +578,7 @@
                 }
             }
         } finally {
-            unregisterClient(clientId);
+            unregisterClient(token);
         }
 
         return Collections.unmodifiableList(ret);
@@ -599,8 +601,9 @@
      * supported device-specific extension
      */
     public boolean isPostviewAvailable(@Extension int extension) {
-        long clientId = registerClient(mContext);
-        if (clientId < 0) {
+        final IBinder token = new Binder(TAG + "#isPostviewAvailable:" + mCameraId);
+        boolean success = registerClient(mContext, token);
+        if (!success) {
             throw new IllegalArgumentException("Unsupported extensions");
         }
 
@@ -623,7 +626,7 @@
             Log.e(TAG, "Failed to query the extension for postview availability! Extension "
                     + "service does not respond!");
         } finally {
-            unregisterClient(clientId);
+            unregisterClient(token);
         }
 
         return false;
@@ -656,9 +659,9 @@
     @NonNull
     public List<Size> getPostviewSupportedSizes(@Extension int extension,
             @NonNull Size captureSize, int format) {
-
-        long clientId = registerClient(mContext);
-        if (clientId < 0) {
+        final IBinder token = new Binder(TAG + "#getPostviewSupportedSizes:" + mCameraId);
+        boolean success = registerClient(mContext, token);
+        if (!success) {
             throw new IllegalArgumentException("Unsupported extensions");
         }
 
@@ -719,7 +722,7 @@
                     + "service does not respond!");
             return Collections.emptyList();
         } finally {
-            unregisterClient(clientId);
+            unregisterClient(token);
         }
     }
 
@@ -756,8 +759,9 @@
         // TODO: Revisit this code once the Extension preview processor output format
         //       ambiguity is resolved in b/169799538.
 
-        long clientId = registerClient(mContext);
-        if (clientId < 0) {
+        final IBinder token = new Binder(TAG + "#getExtensionSupportedSizes:" + mCameraId);
+        boolean success = registerClient(mContext, token);
+        if (!success) {
             throw new IllegalArgumentException("Unsupported extensions");
         }
 
@@ -787,7 +791,7 @@
                     + " not respond!");
             return new ArrayList<>();
         } finally {
-            unregisterClient(clientId);
+            unregisterClient(token);
         }
     }
 
@@ -814,8 +818,9 @@
     public @NonNull
     List<Size> getExtensionSupportedSizes(@Extension int extension, int format) {
         try {
-            long clientId = registerClient(mContext);
-            if (clientId < 0) {
+            final IBinder token = new Binder(TAG + "#getExtensionSupportedSizes:" + mCameraId);
+            boolean success = registerClient(mContext, token);
+            if (!success) {
                 throw new IllegalArgumentException("Unsupported extensions");
             }
 
@@ -867,7 +872,7 @@
                     }
                 }
             } finally {
-                unregisterClient(clientId);
+                unregisterClient(token);
             }
         } catch (RemoteException e) {
             Log.e(TAG, "Failed to query the extension supported sizes! Extension service does"
@@ -888,7 +893,6 @@
      * @param format            device-specific extension output format
      * @return the range of estimated minimal and maximal capture latency in milliseconds
      * or null if no capture latency info can be provided
-     *
      * @throws IllegalArgumentException in case of format different from {@link ImageFormat#JPEG} /
      *                                  {@link ImageFormat#YUV_420_888}; or unsupported extension.
      */
@@ -903,8 +907,9 @@
                 throw new IllegalArgumentException("Unsupported format: " + format);
         }
 
-        long clientId = registerClient(mContext);
-        if (clientId < 0) {
+        final IBinder token = new Binder(TAG + "#getEstimatedCaptureLatencyRangeMillis:" + mCameraId);
+        boolean success = registerClient(mContext, token);
+        if (!success) {
             throw new IllegalArgumentException("Unsupported extensions");
         }
 
@@ -952,7 +957,7 @@
             Log.e(TAG, "Failed to query the extension capture latency! Extension service does"
                     + " not respond!");
         } finally {
-            unregisterClient(clientId);
+            unregisterClient(token);
         }
 
         return null;
@@ -968,8 +973,9 @@
      * @throws IllegalArgumentException in case of an unsupported extension.
      */
     public boolean isCaptureProcessProgressAvailable(@Extension int extension) {
-        long clientId = registerClient(mContext);
-        if (clientId < 0) {
+        final IBinder token = new Binder(TAG + "#isCaptureProcessProgressAvailable:" + mCameraId);
+        boolean success = registerClient(mContext, token);
+        if (!success) {
             throw new IllegalArgumentException("Unsupported extensions");
         }
 
@@ -992,7 +998,7 @@
             Log.e(TAG, "Failed to query the extension progress callbacks! Extension service does"
                     + " not respond!");
         } finally {
-            unregisterClient(clientId);
+            unregisterClient(token);
         }
 
         return false;
@@ -1013,8 +1019,9 @@
      */
     @NonNull
     public Set<CaptureRequest.Key> getAvailableCaptureRequestKeys(@Extension int extension) {
-        long clientId = registerClient(mContext);
-        if (clientId < 0) {
+        final IBinder token = new Binder(TAG + "#getAvailableCaptureRequestKeys:" + mCameraId);
+        boolean success = registerClient(mContext, token);
+        if (!success) {
             throw new IllegalArgumentException("Unsupported extensions");
         }
 
@@ -1033,10 +1040,11 @@
             } else {
                 Pair<IPreviewExtenderImpl, IImageCaptureExtenderImpl> extenders =
                         initializeExtension(extension);
-                extenders.second.onInit(mCameraId, mCharacteristicsMapNative.get(mCameraId));
+                extenders.second.onInit(token, mCameraId,
+                        mCharacteristicsMapNative.get(mCameraId));
                 extenders.second.init(mCameraId, mCharacteristicsMapNative.get(mCameraId));
                 captureRequestMeta = extenders.second.getAvailableCaptureRequestKeys();
-                extenders.second.onDeInit();
+                extenders.second.onDeInit(token);
             }
 
             if (captureRequestMeta != null) {
@@ -1067,7 +1075,7 @@
         } catch (RemoteException e) {
             throw new IllegalStateException("Failed to query the available capture request keys!");
         } finally {
-            unregisterClient(clientId);
+            unregisterClient(token);
         }
 
         return Collections.unmodifiableSet(ret);
@@ -1092,8 +1100,9 @@
      */
     @NonNull
     public Set<CaptureResult.Key> getAvailableCaptureResultKeys(@Extension int extension) {
-        long clientId = registerClient(mContext);
-        if (clientId < 0) {
+        final IBinder token = new Binder(TAG + "#getAvailableCaptureResultKeys:" + mCameraId);
+        boolean success = registerClient(mContext, token);
+        if (!success) {
             throw new IllegalArgumentException("Unsupported extensions");
         }
 
@@ -1111,10 +1120,11 @@
             } else {
                 Pair<IPreviewExtenderImpl, IImageCaptureExtenderImpl> extenders =
                         initializeExtension(extension);
-                extenders.second.onInit(mCameraId, mCharacteristicsMapNative.get(mCameraId));
+                extenders.second.onInit(token, mCameraId,
+                        mCharacteristicsMapNative.get(mCameraId));
                 extenders.second.init(mCameraId, mCharacteristicsMapNative.get(mCameraId));
                 captureResultMeta = extenders.second.getAvailableCaptureResultKeys();
-                extenders.second.onDeInit();
+                extenders.second.onDeInit(token);
             }
 
             if (captureResultMeta != null) {
@@ -1126,7 +1136,7 @@
                 }
                 CameraCharacteristics resultChars = new CameraCharacteristics(captureResultMeta);
                 Object crKey = CaptureResult.Key.class;
-                Class<CaptureResult.Key<?>> crKeyTyped = (Class<CaptureResult.Key<?>>)crKey;
+                Class<CaptureResult.Key<?>> crKeyTyped = (Class<CaptureResult.Key<?>>) crKey;
 
                 ret.addAll(resultChars.getAvailableKeyList(CaptureResult.class, crKeyTyped,
                         resultKeys, /*includeSynthetic*/ true));
@@ -1145,7 +1155,7 @@
         } catch (RemoteException e) {
             throw new IllegalStateException("Failed to query the available capture result keys!");
         } finally {
-            unregisterClient(clientId);
+            unregisterClient(token);
         }
 
         return Collections.unmodifiableSet(ret);
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index a64d66f..c2fe080 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -67,6 +67,7 @@
 import java.util.Arrays;
 import java.util.Comparator;
 import java.util.HashMap;
+import java.util.Iterator;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.Executor;
@@ -181,22 +182,20 @@
             boolean folded = ArrayUtils.contains(mFoldedDeviceStates, state);
 
             mFoldedDeviceState = folded;
-            ArrayList<WeakReference<DeviceStateListener>> invalidListeners = new ArrayList<>();
-            for (WeakReference<DeviceStateListener> listener : mDeviceStateListeners) {
-                DeviceStateListener callback = listener.get();
+            Iterator<WeakReference<DeviceStateListener>> it = mDeviceStateListeners.iterator();
+            while(it.hasNext()) {
+                DeviceStateListener callback = it.next().get();
                 if (callback != null) {
                     callback.onDeviceStateChanged(folded);
                 } else {
-                    invalidListeners.add(listener);
+                    it.remove();
                 }
             }
-            if (!invalidListeners.isEmpty()) {
-                mDeviceStateListeners.removeAll(invalidListeners);
-            }
         }
 
         public synchronized void addDeviceStateListener(DeviceStateListener listener) {
             listener.onDeviceStateChanged(mFoldedDeviceState);
+            mDeviceStateListeners.removeIf(l -> l.get() == null);
             mDeviceStateListeners.add(new WeakReference<>(listener));
         }
 
diff --git a/core/java/android/hardware/camera2/extension/ICameraExtensionsProxyService.aidl b/core/java/android/hardware/camera2/extension/ICameraExtensionsProxyService.aidl
index b52c650..3b7d801 100644
--- a/core/java/android/hardware/camera2/extension/ICameraExtensionsProxyService.aidl
+++ b/core/java/android/hardware/camera2/extension/ICameraExtensionsProxyService.aidl
@@ -20,11 +20,13 @@
 import android.hardware.camera2.extension.IImageCaptureExtenderImpl;
 import android.hardware.camera2.extension.IInitializeSessionCallback;
 
+import android.os.IBinder;
+
 /** @hide */
 interface ICameraExtensionsProxyService
 {
-    long registerClient();
-    void unregisterClient(long clientId);
+    boolean registerClient(in IBinder token);
+    void unregisterClient(in IBinder token);
     boolean advancedExtensionsSupported();
     void initializeSession(in IInitializeSessionCallback cb);
     void releaseSession();
diff --git a/core/java/android/hardware/camera2/extension/IImageCaptureExtenderImpl.aidl b/core/java/android/hardware/camera2/extension/IImageCaptureExtenderImpl.aidl
index 754f8f6..5a22418 100644
--- a/core/java/android/hardware/camera2/extension/IImageCaptureExtenderImpl.aidl
+++ b/core/java/android/hardware/camera2/extension/IImageCaptureExtenderImpl.aidl
@@ -24,11 +24,13 @@
 import android.hardware.camera2.extension.Size;
 import android.hardware.camera2.extension.SizeList;
 
+import android.os.IBinder;
+
 /** @hide */
 interface IImageCaptureExtenderImpl
 {
-    void onInit(in String cameraId, in CameraMetadataNative cameraCharacteristics);
-    void onDeInit();
+    void onInit(in IBinder token, in String cameraId, in CameraMetadataNative cameraCharacteristics);
+    void onDeInit(in IBinder token);
     @nullable CaptureStageImpl onPresetSession();
     @nullable CaptureStageImpl onEnableSession();
     @nullable CaptureStageImpl onDisableSession();
diff --git a/core/java/android/hardware/camera2/extension/IPreviewExtenderImpl.aidl b/core/java/android/hardware/camera2/extension/IPreviewExtenderImpl.aidl
index 01046d0..9ea8a74 100644
--- a/core/java/android/hardware/camera2/extension/IPreviewExtenderImpl.aidl
+++ b/core/java/android/hardware/camera2/extension/IPreviewExtenderImpl.aidl
@@ -22,11 +22,13 @@
 import android.hardware.camera2.extension.IRequestUpdateProcessorImpl;
 import android.hardware.camera2.extension.SizeList;
 
+import android.os.IBinder;
+
 /** @hide */
 interface IPreviewExtenderImpl
 {
-    void onInit(in String cameraId, in CameraMetadataNative cameraCharacteristics);
-    void onDeInit();
+    void onInit(in IBinder token, in String cameraId, in CameraMetadataNative cameraCharacteristics);
+    void onDeInit(in IBinder token);
     @nullable CaptureStageImpl onPresetSession();
     @nullable CaptureStageImpl onEnableSession();
     @nullable CaptureStageImpl onDisableSession();
diff --git a/core/java/android/hardware/camera2/extension/ISessionProcessorImpl.aidl b/core/java/android/hardware/camera2/extension/ISessionProcessorImpl.aidl
index 13b93a8..0581ec0 100644
--- a/core/java/android/hardware/camera2/extension/ISessionProcessorImpl.aidl
+++ b/core/java/android/hardware/camera2/extension/ISessionProcessorImpl.aidl
@@ -25,13 +25,15 @@
 import android.hardware.camera2.extension.LatencyRange;
 import android.hardware.camera2.extension.OutputSurface;
 
+import android.os.IBinder;
+
 /** @hide */
 interface ISessionProcessorImpl
 {
-    CameraSessionConfig initSession(in String cameraId,
+    CameraSessionConfig initSession(in IBinder token, in String cameraId,
             in Map<String, CameraMetadataNative> charsMap, in OutputSurface previewSurface,
             in OutputSurface imageCaptureSurface, in OutputSurface postviewSurface);
-    void deInitSession();
+    void deInitSession(in IBinder token);
     void onCaptureSessionStart(IRequestProcessorImpl requestProcessor);
     void onCaptureSessionEnd();
     int startRepeating(in ICaptureCallback callback);
diff --git a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
index 65d4b43..ae700a0 100644
--- a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
@@ -60,6 +60,7 @@
 import android.os.Binder;
 import android.os.Handler;
 import android.os.HandlerThread;
+import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Log;
 import android.util.Size;
@@ -79,7 +80,6 @@
     private final Executor mExecutor;
     private CameraDevice mCameraDevice;
     private final Map<String, CameraMetadataNative> mCharacteristicsMap;
-    private final long mExtensionClientId;
     private final Handler mHandler;
     private final HandlerThread mHandlerThread;
     private final CameraExtensionSession.StateCallback mCallbacks;
@@ -90,6 +90,7 @@
     private final HashMap<Integer, ImageReader> mReaderMap = new HashMap<>();
     private RequestProcessor mRequestProcessor = new RequestProcessor();
     private final int mSessionId;
+    private final IBinder mToken;
 
     private Surface mClientRepeatingRequestSurface;
     private Surface mClientCaptureSurface;
@@ -114,8 +115,9 @@
             @NonNull Map<String, CameraCharacteristics> characteristicsMap,
             @NonNull Context ctx, @NonNull ExtensionSessionConfiguration config, int sessionId)
             throws CameraAccessException, RemoteException {
-        long clientId = CameraExtensionCharacteristics.registerClient(ctx);
-        if (clientId < 0) {
+        final IBinder token = new Binder(TAG + " : " + sessionId);
+        boolean success = CameraExtensionCharacteristics.registerClient(ctx, token);
+        if (!success) {
             throw new UnsupportedOperationException("Unsupported extension!");
         }
 
@@ -202,11 +204,10 @@
         IAdvancedExtenderImpl extender = CameraExtensionCharacteristics.initializeAdvancedExtension(
                 config.getExtension());
         extender.init(cameraId, characteristicsMapNative);
-
-        CameraAdvancedExtensionSessionImpl ret = new CameraAdvancedExtensionSessionImpl(clientId,
-                extender, cameraDevice, characteristicsMapNative, repeatingRequestSurface,
+        CameraAdvancedExtensionSessionImpl ret = new CameraAdvancedExtensionSessionImpl(extender,
+                cameraDevice, characteristicsMapNative, repeatingRequestSurface,
                 burstCaptureSurface, postviewSurface, config.getStateCallback(),
-                config.getExecutor(), sessionId);
+                config.getExecutor(), sessionId, token);
 
         ret.mStatsAggregator.setClientName(ctx.getOpPackageName());
         ret.mStatsAggregator.setExtensionType(config.getExtension());
@@ -216,15 +217,13 @@
         return ret;
     }
 
-    private CameraAdvancedExtensionSessionImpl(long extensionClientId,
-            @NonNull IAdvancedExtenderImpl extender,
+    private CameraAdvancedExtensionSessionImpl(@NonNull IAdvancedExtenderImpl extender,
             @NonNull CameraDeviceImpl cameraDevice,
             Map<String, CameraMetadataNative> characteristicsMap,
             @Nullable Surface repeatingRequestSurface, @Nullable Surface burstCaptureSurface,
             @Nullable Surface postviewSurface,
             @NonNull StateCallback callback, @NonNull Executor executor,
-            int sessionId) {
-        mExtensionClientId = extensionClientId;
+            int sessionId, @NonNull IBinder token) {
         mAdvancedExtender = extender;
         mCameraDevice = cameraDevice;
         mCharacteristicsMap = characteristicsMap;
@@ -240,6 +239,7 @@
         mSessionClosed = false;
         mInitializeHandler = new InitializeSessionHandler();
         mSessionId = sessionId;
+        mToken = token;
         mInterfaceLock = cameraDevice.mInterfaceLock;
 
         mStatsAggregator = new ExtensionSessionStatsAggregator(mCameraDevice.getId(),
@@ -260,7 +260,8 @@
         OutputSurface postviewSurface = initializeParcelable(mClientPostviewSurface);
 
         mSessionProcessor = mAdvancedExtender.getSessionProcessor();
-        CameraSessionConfig sessionConfig = mSessionProcessor.initSession(mCameraDevice.getId(),
+        CameraSessionConfig sessionConfig = mSessionProcessor.initSession(mToken,
+                mCameraDevice.getId(),
                 mCharacteristicsMap, previewSurface, captureSurface, postviewSurface);
         List<CameraOutputConfig> outputConfigs = sessionConfig.outputConfigs;
         ArrayList<OutputConfiguration> outputList = new ArrayList<>();
@@ -526,7 +527,11 @@
         synchronized (mInterfaceLock) {
             if (mInitialized) {
                 try {
-                    mCaptureSession.stopRepeating();
+                    try {
+                        mCaptureSession.stopRepeating();
+                    } catch (IllegalStateException e) {
+                        // OK: already be closed, nothing else to do
+                    }
                     mSessionProcessor.stopRepeating();
                     mSessionProcessor.onCaptureSessionEnd();
                     mSessionClosed = true;
@@ -565,7 +570,7 @@
                     if (!mSessionClosed) {
                         mSessionProcessor.onCaptureSessionEnd();
                     }
-                    mSessionProcessor.deInitSession();
+                    mSessionProcessor.deInitSession(mToken);
                 } catch (RemoteException e) {
                     Log.e(TAG, "Failed to de-initialize session processor, extension service"
                             + " does not respond!") ;
@@ -573,12 +578,10 @@
                 mSessionProcessor = null;
             }
 
-            if (mExtensionClientId >= 0) {
-                CameraExtensionCharacteristics.unregisterClient(mExtensionClientId);
-                if (mInitialized || (mCaptureSession != null)) {
-                    notifyClose = true;
-                    CameraExtensionCharacteristics.releaseSession();
-                }
+            CameraExtensionCharacteristics.unregisterClient(mToken);
+            if (mInitialized || (mCaptureSession != null)) {
+                notifyClose = true;
+                CameraExtensionCharacteristics.releaseSession();
             }
             mInitialized = false;
 
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index e23bbc6..d3bde4b 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -20,6 +20,7 @@
 
 import android.annotation.NonNull;
 import android.content.Context;
+import android.graphics.ImageFormat;
 import android.hardware.ICameraService;
 import android.hardware.camera2.CameraAccessException;
 import android.hardware.camera2.CameraCaptureSession;
@@ -1478,6 +1479,12 @@
             }
         }
 
+        // Allow RAW formats, even when not advertised.
+        if (inputFormat == ImageFormat.RAW_PRIVATE || inputFormat == ImageFormat.RAW10
+                || inputFormat == ImageFormat.RAW12 || inputFormat == ImageFormat.RAW_SENSOR) {
+            return true;
+        }
+
         if (validFormat == false) {
             return false;
         }
diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
index 9ebef0b..1db4808 100644
--- a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
@@ -56,6 +56,7 @@
 import android.os.Binder;
 import android.os.Handler;
 import android.os.HandlerThread;
+import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Log;
 import android.util.LongSparseArray;
@@ -79,7 +80,6 @@
 
     private final Executor mExecutor;
     private final CameraDevice mCameraDevice;
-    private final long mExtensionClientId;
     private final IImageCaptureExtenderImpl mImageExtender;
     private final IPreviewExtenderImpl mPreviewExtender;
     private final Handler mHandler;
@@ -91,6 +91,7 @@
     private final Set<CaptureRequest.Key> mSupportedRequestKeys;
     private final Set<CaptureResult.Key> mSupportedResultKeys;
     private final ExtensionSessionStatsAggregator mStatsAggregator;
+    private final IBinder mToken;
     private boolean mCaptureResultsSupported;
 
     private CameraCaptureSession mCaptureSession = null;
@@ -111,6 +112,7 @@
     private int mPreviewProcessorType = IPreviewExtenderImpl.PROCESSOR_TYPE_NONE;
 
     private boolean mInitialized;
+    private boolean mSessionClosed;
     // Enable/Disable internal preview/(repeating request). Extensions expect
     // that preview/(repeating request) is enabled and active at any point in time.
     // In case the client doesn't explicitly enable repeating requests, the framework
@@ -135,8 +137,9 @@
             @NonNull ExtensionSessionConfiguration config,
             int sessionId)
             throws CameraAccessException, RemoteException {
-        long clientId = CameraExtensionCharacteristics.registerClient(ctx);
-        if (clientId < 0) {
+        final IBinder token = new Binder(TAG + " : " + sessionId);
+        boolean success = CameraExtensionCharacteristics.registerClient(ctx, token);
+        if (!success) {
             throw new UnsupportedOperationException("Unsupported extension!");
         }
 
@@ -224,15 +227,16 @@
         }
 
         extenders.first.init(cameraId, characteristicsMap.get(cameraId).getNativeMetadata());
-        extenders.first.onInit(cameraId, characteristicsMap.get(cameraId).getNativeMetadata());
+        extenders.first.onInit(token, cameraId,
+                characteristicsMap.get(cameraId).getNativeMetadata());
         extenders.second.init(cameraId, characteristicsMap.get(cameraId).getNativeMetadata());
-        extenders.second.onInit(cameraId, characteristicsMap.get(cameraId).getNativeMetadata());
+        extenders.second.onInit(token, cameraId,
+                characteristicsMap.get(cameraId).getNativeMetadata());
 
         CameraExtensionSessionImpl session = new CameraExtensionSessionImpl(
                 extenders.second,
                 extenders.first,
                 supportedPreviewSizes,
-                clientId,
                 cameraDevice,
                 repeatingRequestSurface,
                 burstCaptureSurface,
@@ -240,6 +244,7 @@
                 config.getStateCallback(),
                 config.getExecutor(),
                 sessionId,
+                token,
                 extensionChars.getAvailableCaptureRequestKeys(config.getExtension()),
                 extensionChars.getAvailableCaptureResultKeys(config.getExtension()));
 
@@ -254,7 +259,6 @@
     public CameraExtensionSessionImpl(@NonNull IImageCaptureExtenderImpl imageExtender,
             @NonNull IPreviewExtenderImpl previewExtender,
             @NonNull List<Size> previewSizes,
-            long extensionClientId,
             @NonNull android.hardware.camera2.impl.CameraDeviceImpl cameraDevice,
             @Nullable Surface repeatingRequestSurface,
             @Nullable Surface burstCaptureSurface,
@@ -262,9 +266,9 @@
             @NonNull StateCallback callback,
             @NonNull Executor executor,
             int sessionId,
+            @NonNull IBinder token,
             @NonNull Set<CaptureRequest.Key> requestKeys,
             @Nullable Set<CaptureResult.Key> resultKeys) {
-        mExtensionClientId = extensionClientId;
         mImageExtender = imageExtender;
         mPreviewExtender = previewExtender;
         mCameraDevice = cameraDevice;
@@ -278,8 +282,10 @@
         mHandlerThread.start();
         mHandler = new Handler(mHandlerThread.getLooper());
         mInitialized = false;
+        mSessionClosed = false;
         mInitializeHandler = new InitializeSessionHandler();
         mSessionId = sessionId;
+        mToken = token;
         mSupportedRequestKeys = requestKeys;
         mSupportedResultKeys = resultKeys;
         mCaptureResultsSupported = !resultKeys.isEmpty();
@@ -775,7 +781,12 @@
         synchronized (mInterfaceLock) {
             if (mInitialized) {
                 mInternalRepeatingRequestEnabled = false;
-                mCaptureSession.stopRepeating();
+                try {
+                    mCaptureSession.stopRepeating();
+                } catch (IllegalStateException e) {
+                    // OK: already be closed, nothing else to do
+                    mSessionClosed = true;
+                }
 
                 ArrayList<CaptureStageImpl> captureStageList = new ArrayList<>();
                 try {
@@ -793,13 +804,14 @@
                     Log.e(TAG, "Failed to disable extension! Extension service does not "
                             + "respond!");
                 }
-                if (!captureStageList.isEmpty()) {
+                if (!captureStageList.isEmpty() && !mSessionClosed) {
                     CaptureRequest disableRequest = createRequest(mCameraDevice, captureStageList,
                             mCameraRepeatingSurface, CameraDevice.TEMPLATE_PREVIEW);
                     mCaptureSession.capture(disableRequest,
                             new CloseRequestHandler(mRepeatingRequestImageCallback), mHandler);
                 }
 
+                mSessionClosed = true;
                 mStatsAggregator.commit(/*isFinal*/true); // Commit stats before closing session
                 mCaptureSession.close();
             }
@@ -854,19 +866,22 @@
             mHandlerThread.quit();
 
             try {
-                mPreviewExtender.onDeInit();
-                mImageExtender.onDeInit();
+                if (!mSessionClosed) {
+                    // return value is omitted. nothing can do after session is closed.
+                    mPreviewExtender.onDisableSession();
+                    mImageExtender.onDisableSession();
+                }
+                mPreviewExtender.onDeInit(mToken);
+                mImageExtender.onDeInit(mToken);
             } catch (RemoteException e) {
                 Log.e(TAG, "Failed to release extensions! Extension service does not"
                         + " respond!");
             }
 
-            if (mExtensionClientId >= 0) {
-                CameraExtensionCharacteristics.unregisterClient(mExtensionClientId);
-                if (mInitialized || (mCaptureSession != null)) {
-                    notifyClose = true;
-                    CameraExtensionCharacteristics.releaseSession();
-                }
+            CameraExtensionCharacteristics.unregisterClient(mToken);
+            if (mInitialized || (mCaptureSession != null)) {
+                notifyClose = true;
+                CameraExtensionCharacteristics.releaseSession();
             }
             mInitialized = false;
 
diff --git a/core/java/android/hardware/devicestate/IDeviceStateManager.aidl b/core/java/android/hardware/devicestate/IDeviceStateManager.aidl
index 3e72d81..ea4fe26 100644
--- a/core/java/android/hardware/devicestate/IDeviceStateManager.aidl
+++ b/core/java/android/hardware/devicestate/IDeviceStateManager.aidl
@@ -118,6 +118,7 @@
     *
     * This should only be called from the overlay itself.
     */
+    @EnforcePermission("CONTROL_DEVICE_STATE")
     @JavaPassthrough(annotation=
         "@android.annotation.RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE)")
     void onStateRequestOverlayDismissed(boolean shouldCancelRequest);
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index 76efce5..022f3c4 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -1762,6 +1762,24 @@
          * 123,1,critical,0.8,default;123,1,moderate,0.6,id_2;456,2,moderate,0.9,critical,0.7
          */
         String KEY_BRIGHTNESS_THROTTLING_DATA = "brightness_throttling_data";
+
+        /**
+         * Key for new power controller feature flag. If enabled new DisplayPowerController will
+         * be used.
+         * Read value via {@link android.provider.DeviceConfig#getBoolean(String, String, boolean)}
+         * with {@link android.provider.DeviceConfig#NAMESPACE_DISPLAY_MANAGER} as the namespace.
+         * @hide
+         */
+        String KEY_NEW_POWER_CONTROLLER = "use_newly_structured_display_power_controller";
+
+        /**
+         * Key for normal brightness mode controller feature flag.
+         * It enables NormalBrightnessModeController.
+         * Read value via {@link android.provider.DeviceConfig#getBoolean(String, String, boolean)}
+         * with {@link android.provider.DeviceConfig#NAMESPACE_DISPLAY_MANAGER} as the namespace.
+         * @hide
+         */
+        String KEY_USE_NORMAL_BRIGHTNESS_MODE_CONTROLLER = "use_normal_brightness_mode_controller";
     }
 
     /**
diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index d6df033..94bff89 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -413,6 +413,15 @@
     public abstract HostUsiVersion getHostUsiVersion(int displayId);
 
     /**
+     * Get the ALS data for a particular display.
+     *
+     * @param displayId The id of the display.
+     * @return {@link AmbientLightSensorData}
+     */
+    @Nullable
+    public abstract AmbientLightSensorData getAmbientLightSensorData(int displayId);
+
+    /**
      * Get all available DisplayGroupIds.
      */
     public abstract IntArray getDisplayGroupIds();
@@ -669,4 +678,23 @@
             return "RefreshRateLimitation(" + type + ": " + range + ")";
         }
     }
+
+    /**
+     * Class to provide Ambient sensor data using the API
+     * {@link DisplayManagerInternal#getAmbientLightSensorData(int)}
+     */
+    public static final class AmbientLightSensorData {
+        public String sensorName;
+        public String sensorType;
+
+        public AmbientLightSensorData(String name, String type) {
+            sensorName = name;
+            sensorType = type;
+        }
+
+        @Override
+        public String toString() {
+            return "AmbientLightSensorData(" + sensorName + ", " + sensorType + ")";
+        }
+    }
 }
diff --git a/core/java/android/hardware/display/IColorDisplayManager.aidl b/core/java/android/hardware/display/IColorDisplayManager.aidl
index 200cf736..77dfb47 100644
--- a/core/java/android/hardware/display/IColorDisplayManager.aidl
+++ b/core/java/android/hardware/display/IColorDisplayManager.aidl
@@ -32,26 +32,36 @@
     int getTransformCapabilities();
 
     boolean isNightDisplayActivated();
+    @EnforcePermission("CONTROL_DISPLAY_COLOR_TRANSFORMS")
     boolean setNightDisplayActivated(boolean activated);
     int getNightDisplayColorTemperature();
+    @EnforcePermission("CONTROL_DISPLAY_COLOR_TRANSFORMS")
     boolean setNightDisplayColorTemperature(int temperature);
+    @EnforcePermission("CONTROL_DISPLAY_COLOR_TRANSFORMS")
     int getNightDisplayAutoMode();
     int getNightDisplayAutoModeRaw();
+    @EnforcePermission("CONTROL_DISPLAY_COLOR_TRANSFORMS")
     boolean setNightDisplayAutoMode(int autoMode);
     Time getNightDisplayCustomStartTime();
+    @EnforcePermission("CONTROL_DISPLAY_COLOR_TRANSFORMS")
     boolean setNightDisplayCustomStartTime(in Time time);
     Time getNightDisplayCustomEndTime();
+    @EnforcePermission("CONTROL_DISPLAY_COLOR_TRANSFORMS")
     boolean setNightDisplayCustomEndTime(in Time time);
 
     int getColorMode();
+    @EnforcePermission("CONTROL_DISPLAY_COLOR_TRANSFORMS")
     void setColorMode(int colorMode);
 
     boolean isDisplayWhiteBalanceEnabled();
+    @EnforcePermission("CONTROL_DISPLAY_COLOR_TRANSFORMS")
     boolean setDisplayWhiteBalanceEnabled(boolean enabled);
 
     boolean isReduceBrightColorsActivated();
+    @EnforcePermission("CONTROL_DISPLAY_COLOR_TRANSFORMS")
     boolean setReduceBrightColorsActivated(boolean activated);
     int getReduceBrightColorsStrength();
+    @EnforcePermission("CONTROL_DISPLAY_COLOR_TRANSFORMS")
     boolean setReduceBrightColorsStrength(int strength);
     float getReduceBrightColorsOffsetFactor();
 }
\ No newline at end of file
diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl
index a3b7b51..18edbdb 100644
--- a/core/java/android/hardware/display/IDisplayManager.aidl
+++ b/core/java/android/hardware/display/IDisplayManager.aidl
@@ -47,9 +47,11 @@
 
     // Requires CONFIGURE_WIFI_DISPLAY permission.
     // The process must have previously registered a callback.
+    @EnforcePermission("CONFIGURE_WIFI_DISPLAY")
     void startWifiDisplayScan();
 
     // Requires CONFIGURE_WIFI_DISPLAY permission.
+    @EnforcePermission("CONFIGURE_WIFI_DISPLAY")
     void stopWifiDisplayScan();
 
     // Requires CONFIGURE_WIFI_DISPLAY permission.
@@ -65,18 +67,22 @@
     void forgetWifiDisplay(String address);
 
     // Requires CONFIGURE_WIFI_DISPLAY permission.
+    @EnforcePermission("CONFIGURE_WIFI_DISPLAY")
     void pauseWifiDisplay();
 
     // Requires CONFIGURE_WIFI_DISPLAY permission.
+    @EnforcePermission("CONFIGURE_WIFI_DISPLAY")
     void resumeWifiDisplay();
 
     // No permissions required.
     WifiDisplayStatus getWifiDisplayStatus();
 
     // Requires WRITE_SECURE_SETTINGS permission.
+    @EnforcePermission("WRITE_SECURE_SETTINGS")
     void setUserDisabledHdrTypes(in int[] userDisabledTypes);
 
     // Requires WRITE_SECURE_SETTINGS permission.
+    @EnforcePermission("WRITE_SECURE_SETTINGS")
     void setAreUserDisabledHdrTypesAllowed(boolean areUserDisabledHdrTypesAllowed);
 
     // No permissions required.
@@ -89,6 +95,7 @@
     void overrideHdrTypes(int displayId, in int[] modes);
 
     // Requires CONFIGURE_DISPLAY_COLOR_MODE
+    @EnforcePermission("CONFIGURE_DISPLAY_COLOR_MODE")
     void requestColorMode(int displayId, int colorMode);
 
     // Requires CAPTURE_VIDEO_OUTPUT, CAPTURE_SECURE_VIDEO_OUTPUT, or an appropriate
@@ -114,24 +121,29 @@
     Point getStableDisplaySize();
 
     // Requires BRIGHTNESS_SLIDER_USAGE permission.
+    @EnforcePermission("BRIGHTNESS_SLIDER_USAGE")
     ParceledListSlice getBrightnessEvents(String callingPackage);
 
     // Requires ACCESS_AMBIENT_LIGHT_STATS permission.
+    @EnforcePermission("ACCESS_AMBIENT_LIGHT_STATS")
     ParceledListSlice getAmbientBrightnessStats();
 
     // Sets the global brightness configuration for a given user. Requires
     // CONFIGURE_DISPLAY_BRIGHTNESS, and INTERACT_ACROSS_USER if the user being configured is not
     // the same as the calling user.
+    @EnforcePermission("CONFIGURE_DISPLAY_BRIGHTNESS")
     void setBrightnessConfigurationForUser(in BrightnessConfiguration c, int userId,
             String packageName);
 
     // Sets the global brightness configuration for a given display. Requires
     // CONFIGURE_DISPLAY_BRIGHTNESS.
+    @EnforcePermission("CONFIGURE_DISPLAY_BRIGHTNESS")
     void setBrightnessConfigurationForDisplay(in BrightnessConfiguration c, String uniqueDisplayId,
             int userId, String packageName);
 
     // Gets the brightness configuration for a given display. Requires
     // CONFIGURE_DISPLAY_BRIGHTNESS.
+    @EnforcePermission("CONFIGURE_DISPLAY_BRIGHTNESS")
     BrightnessConfiguration getBrightnessConfigurationForDisplay(String uniqueDisplayId,
             int userId);
 
@@ -141,27 +153,32 @@
     BrightnessConfiguration getBrightnessConfigurationForUser(int userId);
 
     // Gets the default brightness configuration if configured.
+    @EnforcePermission("CONFIGURE_DISPLAY_BRIGHTNESS")
     BrightnessConfiguration getDefaultBrightnessConfiguration();
 
     // Gets the last requested minimal post processing settings for display with displayId.
     boolean isMinimalPostProcessingRequested(int displayId);
 
     // Temporarily sets the display brightness.
+    @EnforcePermission("CONTROL_DISPLAY_BRIGHTNESS")
     void setTemporaryBrightness(int displayId, float brightness);
 
     // Saves the display brightness.
+    @EnforcePermission("CONTROL_DISPLAY_BRIGHTNESS")
     void setBrightness(int displayId, float brightness);
 
     // Retrieves the display brightness.
     float getBrightness(int displayId);
 
     // Temporarily sets the auto brightness adjustment factor.
+    @EnforcePermission("CONTROL_DISPLAY_BRIGHTNESS")
     void setTemporaryAutoBrightnessAdjustment(float adjustment);
 
     // Get the minimum brightness curve.
     Curve getMinimumBrightnessCurve();
 
     // Get Brightness Information for the specified display.
+    @EnforcePermission("CONTROL_DISPLAY_BRIGHTNESS")
     BrightnessInfo getBrightnessInfo(int displayId);
 
     // Gets the id of the preferred wide gamut color space for all displays.
@@ -171,6 +188,7 @@
 
     // Sets the user preferred display mode.
     // Requires MODIFY_USER_PREFERRED_DISPLAY_MODE permission.
+    @EnforcePermission("MODIFY_USER_PREFERRED_DISPLAY_MODE")
     void setUserPreferredDisplayMode(int displayId, in Mode mode);
     Mode getUserPreferredDisplayMode(int displayId);
     Mode getSystemPreferredDisplayMode(int displayId);
@@ -187,10 +205,13 @@
     // When enabled the app requested display resolution and refresh rate is always selected
     // in DisplayModeDirector regardless of user settings and policies for low brightness, low
     // battery etc.
+    @EnforcePermission("OVERRIDE_DISPLAY_MODE_REQUESTS")
     void setShouldAlwaysRespectAppRequestedMode(boolean enabled);
+    @EnforcePermission("OVERRIDE_DISPLAY_MODE_REQUESTS")
     boolean shouldAlwaysRespectAppRequestedMode();
 
     // Sets the refresh rate switching type.
+    @EnforcePermission("MODIFY_REFRESH_RATE_SWITCHING_TYPE")
     void setRefreshRateSwitchingType(int newValue);
 
     // Returns the refresh rate switching type.
diff --git a/core/java/android/hardware/face/IFaceService.aidl b/core/java/android/hardware/face/IFaceService.aidl
index 9d5073e..7080133 100644
--- a/core/java/android/hardware/face/IFaceService.aidl
+++ b/core/java/android/hardware/face/IFaceService.aidl
@@ -177,5 +177,5 @@
     // Internal operation used to clear face biometric scheduler.
     // Ensures that the scheduler is not stuck.
     @EnforcePermission("USE_BIOMETRIC_INTERNAL")
-    void scheduleWatchdog();
+    oneway void scheduleWatchdog();
 }
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index 01977f6..f594377 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -103,6 +103,7 @@
     private static final int MSG_UDFPS_POINTER_DOWN = 108;
     private static final int MSG_UDFPS_POINTER_UP = 109;
     private static final int MSG_POWER_BUTTON_PRESSED = 110;
+    private static final int MSG_UDFPS_OVERLAY_SHOWN = 111;
 
     /**
      * @hide
@@ -121,6 +122,24 @@
     public @interface EnrollReason {}
 
     /**
+     * Udfps ui event of overlay is shown on the screen.
+     * @hide
+     */
+    public static final int UDFPS_UI_OVERLAY_SHOWN = 1;
+    /**
+     * Udfps ui event of the udfps UI being ready (e.g. HBM illumination is enabled).
+     * @hide
+     */
+    public static final int UDFPS_UI_READY = 2;
+
+    /**
+     * @hide
+     */
+    @IntDef({UDFPS_UI_OVERLAY_SHOWN, UDFPS_UI_READY})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface UdfpsUiEvent{}
+
+    /**
      * Request authentication with any single sensor.
      * @hide
      */
@@ -475,12 +494,17 @@
         /**
          * Called when a pointer down event has occurred.
          */
-        public void onPointerDown(int sensorId){ }
+        public void onUdfpsPointerDown(int sensorId){ }
 
         /**
          * Called when a pointer up event has occurred.
          */
-        public void onPointerUp(int sensorId){ }
+        public void onUdfpsPointerUp(int sensorId){ }
+
+        /**
+         * Called when udfps overlay is shown.
+         */
+        public void onUdfpsOverlayShown() { }
     }
 
     /**
@@ -956,23 +980,6 @@
     }
 
     /**
-     * @hide
-     */
-    @RequiresPermission(USE_BIOMETRIC_INTERNAL)
-    public void setUdfpsOverlay(@NonNull IUdfpsOverlay controller) {
-        if (mService == null) {
-            Slog.w(TAG, "setUdfpsOverlay: no fingerprint service");
-            return;
-        }
-
-        try {
-            mService.setUdfpsOverlay(controller);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
      * Forwards BiometricStateListener to FingerprintService
      * @param listener new BiometricStateListener being added
      * @hide
@@ -1112,14 +1119,14 @@
      * @hide
      */
     @RequiresPermission(USE_BIOMETRIC_INTERNAL)
-    public void onUiReady(long requestId, int sensorId) {
+    public void onUdfpsUiEvent(@UdfpsUiEvent int event, long requestId, int sensorId) {
         if (mService == null) {
-            Slog.w(TAG, "onUiReady: no fingerprint service");
+            Slog.w(TAG, "onUdfpsUiEvent: no fingerprint service");
             return;
         }
 
         try {
-            mService.onUiReady(requestId, sensorId);
+            mService.onUdfpsUiEvent(event, requestId, sensorId);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -1365,6 +1372,8 @@
                 case MSG_POWER_BUTTON_PRESSED:
                     sendPowerPressed();
                     break;
+                case MSG_UDFPS_OVERLAY_SHOWN:
+                    sendUdfpsOverlayShown();
                 default:
                     Slog.w(TAG, "Unknown message: " + msg.what);
 
@@ -1489,7 +1498,7 @@
         }
 
         if (mEnrollmentCallback != null) {
-            mEnrollmentCallback.onPointerDown(sensorId);
+            mEnrollmentCallback.onUdfpsPointerDown(sensorId);
         }
     }
 
@@ -1500,7 +1509,7 @@
             mAuthenticationCallback.onUdfpsPointerUp(sensorId);
         }
         if (mEnrollmentCallback != null) {
-            mEnrollmentCallback.onPointerUp(sensorId);
+            mEnrollmentCallback.onUdfpsPointerUp(sensorId);
         }
     }
 
@@ -1512,6 +1521,12 @@
         }
     }
 
+    private void sendUdfpsOverlayShown() {
+        if (mEnrollmentCallback != null) {
+            mEnrollmentCallback.onUdfpsOverlayShown();
+        }
+    }
+
     /**
      * @hide
      */
@@ -1787,6 +1802,11 @@
         public void onUdfpsPointerUp(int sensorId) {
             mHandler.obtainMessage(MSG_UDFPS_POINTER_UP, sensorId, 0).sendToTarget();
         }
+
+        @Override
+        public void onUdfpsOverlayShown() {
+            mHandler.obtainMessage(MSG_UDFPS_OVERLAY_SHOWN).sendToTarget();
+        }
     };
 
 }
diff --git a/core/java/android/hardware/fingerprint/FingerprintServiceReceiver.java b/core/java/android/hardware/fingerprint/FingerprintServiceReceiver.java
index a9779b5..89d710d 100644
--- a/core/java/android/hardware/fingerprint/FingerprintServiceReceiver.java
+++ b/core/java/android/hardware/fingerprint/FingerprintServiceReceiver.java
@@ -75,4 +75,9 @@
     public void onUdfpsPointerUp(int sensorId) throws RemoteException {
 
     }
+
+    @Override
+    public void onUdfpsOverlayShown() throws RemoteException {
+
+    }
 }
diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
index ec5749e..e2840ec 100644
--- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl
+++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
@@ -27,7 +27,6 @@
 import android.hardware.fingerprint.IFingerprintServiceReceiver;
 import android.hardware.fingerprint.IUdfpsOverlayController;
 import android.hardware.fingerprint.ISidefpsController;
-import android.hardware.fingerprint.IUdfpsOverlay;
 import android.hardware.fingerprint.Fingerprint;
 import android.hardware.fingerprint.FingerprintAuthenticateOptions;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
@@ -193,7 +192,7 @@
 
     // Notifies about the fingerprint UI being ready (e.g. HBM illumination is enabled).
     @EnforcePermission("USE_BIOMETRIC_INTERNAL")
-    void onUiReady(long requestId, int sensorId);
+    void onUdfpsUiEvent(int event, long requestId, int sensorId);
 
     // Sets the controller for managing the UDFPS overlay.
     @EnforcePermission("USE_BIOMETRIC_INTERNAL")
@@ -203,10 +202,6 @@
     @EnforcePermission("USE_BIOMETRIC_INTERNAL")
     void setSidefpsController(in ISidefpsController controller);
 
-    // Sets the controller for managing the UDFPS overlay.
-    @EnforcePermission("USE_BIOMETRIC_INTERNAL")
-    void setUdfpsOverlay(in IUdfpsOverlay controller);
-
     // Registers BiometricStateListener.
     @EnforcePermission("USE_BIOMETRIC_INTERNAL")
     void registerBiometricStateListener(IBiometricStateListener listener);
@@ -218,5 +213,5 @@
     // Internal operation used to clear fingerprint biometric scheduler.
     // Ensures that the scheduler is not stuck.
     @EnforcePermission("USE_BIOMETRIC_INTERNAL")
-    void scheduleWatchdog();
+    oneway void scheduleWatchdog();
 }
diff --git a/core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl b/core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl
index 9cea1fe..91a32d7 100644
--- a/core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl
+++ b/core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl
@@ -32,4 +32,5 @@
     void onChallengeGenerated(int sensorId, int userId, long challenge);
     void onUdfpsPointerDown(int sensorId);
     void onUdfpsPointerUp(int sensorId);
+    void onUdfpsOverlayShown();
 }
diff --git a/core/java/android/hardware/fingerprint/IUdfpsOverlay.aidl b/core/java/android/hardware/fingerprint/IUdfpsOverlay.aidl
deleted file mode 100644
index c99fccc..0000000
--- a/core/java/android/hardware/fingerprint/IUdfpsOverlay.aidl
+++ /dev/null
@@ -1,29 +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 android.hardware.fingerprint;
-
-/**
- * Interface for interacting with the under-display fingerprint sensor (UDFPS) overlay.
- * @hide
- */
-oneway interface IUdfpsOverlay {
-    // Shows the overlay.
-    void show(long requestId, int sensorId, int reason);
-
-    // Hides the overlay.
-    void hide(int sensorId);
-}
diff --git a/core/java/android/hardware/input/InputManagerGlobal.java b/core/java/android/hardware/input/InputManagerGlobal.java
index c0877d3..e886f68 100644
--- a/core/java/android/hardware/input/InputManagerGlobal.java
+++ b/core/java/android/hardware/input/InputManagerGlobal.java
@@ -54,6 +54,7 @@
 import android.view.PointerIcon;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.SomeArgs;
 
 import java.util.ArrayList;
@@ -140,23 +141,27 @@
     }
 
     /**
-     * Gets an instance of the input manager.
-     *
-     * @return The input manager instance.
+     * A test session tracker for InputManagerGlobal.
+     * @see #createTestSession(IInputManager)
      */
-    public static InputManagerGlobal resetInstance(IInputManager inputManagerService) {
-        synchronized (InputManager.class) {
-            sInstance = new InputManagerGlobal(inputManagerService);
-            return sInstance;
-        }
+    @VisibleForTesting
+    public interface TestSession extends AutoCloseable {
+        @Override
+        void close();
     }
 
     /**
-     * Clear the instance of the input manager.
+     * Create and set a test instance of InputManagerGlobal.
+     *
+     * @return The test session. The session must be {@link TestSession#close()}-ed at the end
+     * of the test.
      */
-    public static void clearInstance() {
+    @VisibleForTesting
+    public static TestSession createTestSession(IInputManager inputManagerService) {
         synchronized (InputManagerGlobal.class) {
-            sInstance = null;
+            final var oldInstance = sInstance;
+            sInstance = new InputManagerGlobal(inputManagerService);
+            return () -> sInstance = oldInstance;
         }
     }
 
diff --git a/core/java/android/hardware/input/InputSettings.java b/core/java/android/hardware/input/InputSettings.java
index 17bbe14..33960c0 100644
--- a/core/java/android/hardware/input/InputSettings.java
+++ b/core/java/android/hardware/input/InputSettings.java
@@ -266,7 +266,7 @@
      */
     public static boolean useTouchpadTapToClick(@NonNull Context context) {
         return Settings.System.getIntForUser(context.getContentResolver(),
-                Settings.System.TOUCHPAD_TAP_TO_CLICK, 0, UserHandle.USER_CURRENT) == 1;
+                Settings.System.TOUCHPAD_TAP_TO_CLICK, 1, UserHandle.USER_CURRENT) == 1;
     }
 
     /**
diff --git a/core/java/android/hardware/input/KeyboardLayout.java b/core/java/android/hardware/input/KeyboardLayout.java
index 0311da4..4403251 100644
--- a/core/java/android/hardware/input/KeyboardLayout.java
+++ b/core/java/android/hardware/input/KeyboardLayout.java
@@ -73,7 +73,7 @@
     private final int mProductId;
 
     /** Currently supported Layout types in the KCM files */
-    private enum LayoutType {
+    public enum LayoutType {
         UNDEFINED(0, LAYOUT_TYPE_UNDEFINED),
         QWERTY(1, LAYOUT_TYPE_QWERTY),
         QWERTZ(2, LAYOUT_TYPE_QWERTZ),
@@ -88,9 +88,11 @@
         private final int mValue;
         private final String mName;
         private static final Map<Integer, LayoutType> VALUE_TO_ENUM_MAP = new HashMap<>();
+        private static final Map<String, LayoutType> NAME_TO_ENUM_MAP = new HashMap<>();
         static {
             for (LayoutType type : LayoutType.values()) {
                 VALUE_TO_ENUM_MAP.put(type.mValue, type);
+                NAME_TO_ENUM_MAP.put(type.mName, type);
             }
         }
 
@@ -110,6 +112,25 @@
         private String getName() {
             return mName;
         }
+
+        /**
+         * Returns enum value for provided layout type
+         * @param layoutName name of the layout type
+         * @return int value corresponding to the LayoutType enum that matches the layout name.
+         * (LayoutType.UNDEFINED if no match found)
+         */
+        public static int getLayoutTypeEnumValue(String layoutName) {
+            return NAME_TO_ENUM_MAP.getOrDefault(layoutName, UNDEFINED).getValue();
+        }
+
+        /**
+         * Returns name for provided layout type enum value
+         * @param enumValue value representation for LayoutType enum
+         * @return Layout name corresponding to the enum value (LAYOUT_TYPE_UNDEFINED if not found)
+         */
+        public static String getLayoutNameFromValue(int enumValue) {
+            return VALUE_TO_ENUM_MAP.getOrDefault(enumValue, UNDEFINED).getName();
+        }
     }
 
     @NonNull
diff --git a/core/java/android/hardware/input/VirtualTouchEvent.java b/core/java/android/hardware/input/VirtualTouchEvent.java
index 73da5d9..2695a79 100644
--- a/core/java/android/hardware/input/VirtualTouchEvent.java
+++ b/core/java/android/hardware/input/VirtualTouchEvent.java
@@ -272,7 +272,8 @@
         public @NonNull Builder setAction(@Action int action) {
             if (action != ACTION_DOWN && action != ACTION_UP && action != ACTION_MOVE
                     && action != ACTION_CANCEL) {
-                throw new IllegalArgumentException("Unsupported touch event action type");
+                throw new IllegalArgumentException(
+                        "Unsupported touch event action type: " + action);
             }
             mAction = action;
             return this;
diff --git a/core/java/android/hardware/lights/ILightsManager.aidl b/core/java/android/hardware/lights/ILightsManager.aidl
index 077797f..3b257ca 100644
--- a/core/java/android/hardware/lights/ILightsManager.aidl
+++ b/core/java/android/hardware/lights/ILightsManager.aidl
@@ -25,9 +25,14 @@
  * {@hide}
  */
 interface ILightsManager {
+  @EnforcePermission("CONTROL_DEVICE_LIGHTS")
   List<Light> getLights();
+  @EnforcePermission("CONTROL_DEVICE_LIGHTS")
   LightState getLightState(int lightId);
+  @EnforcePermission("CONTROL_DEVICE_LIGHTS")
   void openSession(in IBinder sessionToken, in int priority);
+  @EnforcePermission("CONTROL_DEVICE_LIGHTS")
   void closeSession(in IBinder sessionToken);
+  @EnforcePermission("CONTROL_DEVICE_LIGHTS")
   void setLightStates(in IBinder sessionToken, in int[] lightIds, in LightState[] states);
 }
diff --git a/core/java/android/hardware/lights/Light.java b/core/java/android/hardware/lights/Light.java
index 1df9b75..18d0b09 100644
--- a/core/java/android/hardware/lights/Light.java
+++ b/core/java/android/hardware/lights/Light.java
@@ -110,6 +110,8 @@
     private final int mOrdinal;
     private final int mType;
     private final int mCapabilities;
+    @Nullable
+    private final int[] mPreferredBrightnessLevels;
 
     /**
      * Creates a new light with the given data.
@@ -117,7 +119,7 @@
      * @hide
      */
     public Light(int id, int ordinal, int type) {
-        this(id, "Light", ordinal, type, 0);
+        this(id, "Light", ordinal, type, 0, null);
     }
 
     /**
@@ -126,11 +128,22 @@
      * @hide
      */
     public Light(int id, String name, int ordinal, int type, int capabilities) {
+        this(id, name, ordinal, type, capabilities, null);
+    }
+
+    /**
+     * Creates a new light with the given data.
+     *
+     * @hide
+     */
+    public Light(int id, String name, int ordinal, int type, int capabilities,
+            @Nullable int[] preferredBrightnessLevels) {
         mId = id;
         mName = name;
         mOrdinal = ordinal;
         mType = type;
         mCapabilities = capabilities;
+        mPreferredBrightnessLevels = preferredBrightnessLevels;
     }
 
     private Light(@NonNull Parcel in) {
@@ -139,6 +152,7 @@
         mOrdinal = in.readInt();
         mType = in.readInt();
         mCapabilities = in.readInt();
+        mPreferredBrightnessLevels = in.createIntArray();
     }
 
     /** Implement the Parcelable interface */
@@ -149,6 +163,7 @@
         dest.writeInt(mOrdinal);
         dest.writeInt(mType);
         dest.writeInt(mCapabilities);
+        dest.writeIntArray(mPreferredBrightnessLevels);
     }
 
     /** Implement the Parcelable interface */
@@ -252,4 +267,17 @@
         return (mCapabilities & LIGHT_CAPABILITY_COLOR_RGB) == LIGHT_CAPABILITY_COLOR_RGB;
     }
 
+    /**
+     * Returns preferred brightness levels for the light which will be used when user
+     * increase/decrease brightness levels for the light (currently only used for Keyboard
+     * backlight control using backlight up/down keys).
+     *
+     * The values in the preferred brightness level array are in the range [0, 255].
+     *
+     * @hide
+     */
+    @Nullable
+    public int[] getPreferredBrightnessLevels() {
+        return mPreferredBrightnessLevels;
+    }
 }
diff --git a/core/java/android/hardware/usb/IUsbManager.aidl b/core/java/android/hardware/usb/IUsbManager.aidl
index 21b00e3..09f5f36 100644
--- a/core/java/android/hardware/usb/IUsbManager.aidl
+++ b/core/java/android/hardware/usb/IUsbManager.aidl
@@ -81,6 +81,7 @@
     boolean hasDevicePermission(in UsbDevice device, String packageName);
 
     /* Returns true if the given package/pid/uid has permission to access the device. */
+    @EnforcePermission("MANAGE_USB")
     @JavaPassthrough(annotation=
             "@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_USB)")
     boolean hasDevicePermissionWithIdentity(in UsbDevice device, String packageName,
@@ -90,6 +91,7 @@
     boolean hasAccessoryPermission(in UsbAccessory accessory);
 
     /* Returns true if the given pid/uid has permission to access the accessory. */
+    @EnforcePermission("MANAGE_USB")
     @JavaPassthrough(annotation=
             "@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_USB)")
     boolean hasAccessoryPermissionWithIdentity(in UsbAccessory accessory, int pid, int uid);
@@ -108,9 +110,11 @@
             in PendingIntent pi);
 
     /* Grants permission for the given UID to access the device */
+    @EnforcePermission("MANAGE_USB")
     void grantDevicePermission(in UsbDevice device, int uid);
 
     /* Grants permission for the given UID to access the accessory */
+    @EnforcePermission("MANAGE_USB")
     void grantAccessoryPermission(in UsbAccessory accessory, int uid);
 
     /* Returns true if the USB manager has default preferences or permissions for the package */
@@ -123,29 +127,36 @@
     boolean isFunctionEnabled(String function);
 
     /* Sets the current USB function. */
+    @EnforcePermission("MANAGE_USB")
     void setCurrentFunctions(long functions, int operationId);
 
     /* Compatibility version of setCurrentFunctions(long). */
     void setCurrentFunction(String function, boolean usbDataUnlocked, int operationId);
 
     /* Gets the current USB functions. */
+    @EnforcePermission("MANAGE_USB")
     long getCurrentFunctions();
 
     /* Gets the current USB Speed. */
+    @EnforcePermission("MANAGE_USB")
     int getCurrentUsbSpeed();
 
     /* Gets the Gadget Hal Version. */
+    @EnforcePermission("MANAGE_USB")
     int getGadgetHalVersion();
 
     /* Sets the screen unlocked USB function(s), which will be set automatically
      * when the screen is unlocked.
      */
+    @EnforcePermission("MANAGE_USB")
     void setScreenUnlockedFunctions(long functions);
 
     /* Gets the current screen unlocked functions. */
+    @EnforcePermission("MANAGE_USB")
     long getScreenUnlockedFunctions();
 
     /* Resets the USB gadget. */
+    @EnforcePermission("MANAGE_USB")
     void resetUsbGadget();
 
     /* Resets the USB port. */
@@ -158,15 +169,18 @@
     void enableUsbDataWhileDocked(in String portId, int operationId, in IUsbOperationInternal callback);
 
     /* Gets the USB Hal Version. */
+    @EnforcePermission("MANAGE_USB")
     int getUsbHalVersion();
 
     /* Get the functionfs control handle for the given function. Usb
      * descriptors will already be written, and the handle will be
      * ready to use.
      */
+    @EnforcePermission("ACCESS_MTP")
     ParcelFileDescriptor getControlFd(long function);
 
     /* Gets the list of USB ports. */
+    @EnforcePermission("MANAGE_USB")
     List<ParcelableUsbPort> getPorts();
 
     /* Gets the status of the specified USB port. */
@@ -184,6 +198,7 @@
     void enableContaminantDetection(in String portId, boolean enable);
 
     /* Sets USB device connection handler. */
+    @EnforcePermission("MANAGE_USB")
     void setUsbDeviceConnectionHandler(in ComponentName usbDeviceConnectionHandler);
 
     /* Registers callback for Usb events */
diff --git a/core/java/android/hardware/usb/OWNERS b/core/java/android/hardware/usb/OWNERS
index 8f5c2a0..a753f96 100644
--- a/core/java/android/hardware/usb/OWNERS
+++ b/core/java/android/hardware/usb/OWNERS
@@ -1,3 +1,7 @@
 # Bug component: 175220
 
+aprasath@google.com
+kumarashishg@google.com
+sarup@google.com
+anothermark@google.com
 badhri@google.com
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index a9c4818..e472a40 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -103,11 +103,10 @@
 import android.util.Printer;
 import android.util.Xml;
 import android.util.proto.ProtoOutputStream;
-import android.view.BatchedInputEventReceiver.SimpleBatchedInputEventReceiver;
-import android.view.Choreographer;
 import android.view.Gravity;
 import android.view.InputChannel;
 import android.view.InputDevice;
+import android.view.InputEvent;
 import android.view.InputEventReceiver;
 import android.view.KeyCharacterMap;
 import android.view.KeyEvent;
@@ -1052,17 +1051,22 @@
             stylusEvents.forEach(InputMethodService.this::onStylusHandwritingMotionEvent);
 
             // create receiver for channel
-            mHandwritingEventReceiver = new SimpleBatchedInputEventReceiver(
-                    channel,
-                    Looper.getMainLooper(), Choreographer.getInstance(),
-                    event -> {
+            mHandwritingEventReceiver = new InputEventReceiver(channel, Looper.getMainLooper()) {
+                @Override
+                public void onInputEvent(InputEvent event) {
+                    boolean handled = false;
+                    try {
                         if (!(event instanceof MotionEvent)) {
-                            return false;
+                            return;
                         }
                         onStylusHandwritingMotionEvent((MotionEvent) event);
                         scheduleHandwritingSessionTimeout();
-                        return true;
-                    });
+                        handled = true;
+                    } finally {
+                        finishInputEvent(event, handled);
+                    }
+                }
+            };
             scheduleHandwritingSessionTimeout();
         }
 
diff --git a/core/java/android/inputmethodservice/OWNERS b/core/java/android/inputmethodservice/OWNERS
index d7db7c7..9805ce8 100644
--- a/core/java/android/inputmethodservice/OWNERS
+++ b/core/java/android/inputmethodservice/OWNERS
@@ -3,4 +3,5 @@
 
 include /services/core/java/com/android/server/inputmethod/OWNERS
 
-per-file *InlineSuggestion* = file:/core/java/android/service/autofill/OWNERS
+# Bug component: 1195602 = per-file *InlineSuggestion*
+per-file *InlineSuggestion* = file:/core/java/android/widget/inline/OWNERS
diff --git a/core/java/android/inputmethodservice/navigationbar/KeyButtonView.java b/core/java/android/inputmethodservice/navigationbar/KeyButtonView.java
index 92d358f..f423672 100644
--- a/core/java/android/inputmethodservice/navigationbar/KeyButtonView.java
+++ b/core/java/android/inputmethodservice/navigationbar/KeyButtonView.java
@@ -44,8 +44,6 @@
 import android.view.inputmethod.InputConnection;
 import android.widget.ImageView;
 
-import com.android.internal.annotations.VisibleForTesting;
-
 /**
  * @hide
  */
@@ -60,32 +58,12 @@
     private int mTouchDownY;
     private AudioManager mAudioManager;
     private boolean mGestureAborted;
-    @VisibleForTesting boolean mLongClicked;
     private OnClickListener mOnClickListener;
     private final KeyButtonRipple mRipple;
     private final Paint mOvalBgPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
     private float mDarkIntensity;
     private boolean mHasOvalBg = false;
 
-    private final Runnable mCheckLongPress = new Runnable() {
-        public void run() {
-            if (isPressed()) {
-                // Log.d("KeyButtonView", "longpressed: " + this);
-                if (isLongClickable()) {
-                    // Just an old-fashioned ImageView
-                    performLongClick();
-                    mLongClicked = true;
-                } else {
-                    if (mCode != KEYCODE_UNKNOWN) {
-                        sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.FLAG_LONG_PRESS);
-                        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
-                    }
-                    mLongClicked = true;
-                }
-            }
-        }
-    };
-
     public KeyButtonView(Context context, AttributeSet attrs) {
         super(context, attrs);
 
@@ -181,7 +159,6 @@
         switch (action) {
             case MotionEvent.ACTION_DOWN:
                 mDownTime = SystemClock.uptimeMillis();
-                mLongClicked = false;
                 setPressed(true);
 
                 // Use raw X and Y to detect gestures in case a parent changes the x and y values
@@ -196,8 +173,6 @@
                 if (!showSwipeUI) {
                     playSoundEffect(SoundEffectConstants.CLICK);
                 }
-                removeCallbacks(mCheckLongPress);
-                postDelayed(mCheckLongPress, ViewConfiguration.getLongPressTimeout());
                 break;
             case MotionEvent.ACTION_MOVE:
                 x = (int) ev.getRawX();
@@ -208,7 +183,6 @@
                     // When quick step is enabled, prevent animating the ripple triggered by
                     // setPressed and decide to run it on touch up
                     setPressed(false);
-                    removeCallbacks(mCheckLongPress);
                 }
                 break;
             case MotionEvent.ACTION_CANCEL:
@@ -216,10 +190,9 @@
                 if (mCode != KEYCODE_UNKNOWN) {
                     sendEvent(KeyEvent.ACTION_UP, KeyEvent.FLAG_CANCELED);
                 }
-                removeCallbacks(mCheckLongPress);
                 break;
             case MotionEvent.ACTION_UP:
-                final boolean doIt = isPressed() && !mLongClicked;
+                final boolean doIt = isPressed();
                 setPressed(false);
                 final boolean doHapticFeedback = (SystemClock.uptimeMillis() - mDownTime) > 150;
                 if (showSwipeUI) {
@@ -228,7 +201,7 @@
                         performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
                         playSoundEffect(SoundEffectConstants.CLICK);
                     }
-                } else if (doHapticFeedback && !mLongClicked) {
+                } else if (doHapticFeedback) {
                     // Always send a release ourselves because it doesn't seem to be sent elsewhere
                     // and it feels weird to sometimes get a release haptic and other times not.
                     performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY_RELEASE);
@@ -248,7 +221,6 @@
                         sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
                     }
                 }
-                removeCallbacks(mCheckLongPress);
                 break;
         }
 
@@ -283,12 +255,6 @@
     }
 
     private void sendEvent(int action, int flags, long when) {
-        if (mCode == KeyEvent.KEYCODE_BACK && flags != KeyEvent.FLAG_LONG_PRESS) {
-            if (action == MotionEvent.ACTION_UP) {
-                // TODO(b/215443343): Implement notifyBackAction();
-            }
-        }
-
         // TODO(b/215443343): Consolidate this logic to somewhere else.
         if (mContext instanceof InputMethodService) {
             final int repeatCount = (flags & KeyEvent.FLAG_LONG_PRESS) != 0 ? 1 : 0;
diff --git a/core/java/android/net/INetworkPolicyManager.aidl b/core/java/android/net/INetworkPolicyManager.aidl
index dc24106..8f77571 100644
--- a/core/java/android/net/INetworkPolicyManager.aidl
+++ b/core/java/android/net/INetworkPolicyManager.aidl
@@ -30,29 +30,38 @@
 interface INetworkPolicyManager {
 
     /** Control UID policies. */
+    @EnforcePermission("MANAGE_NETWORK_POLICY")
     @UnsupportedAppUsage
     void setUidPolicy(int uid, int policy);
+    @EnforcePermission("MANAGE_NETWORK_POLICY")
     void addUidPolicy(int uid, int policy);
+    @EnforcePermission("MANAGE_NETWORK_POLICY")
     void removeUidPolicy(int uid, int policy);
+    @EnforcePermission("MANAGE_NETWORK_POLICY")
     @UnsupportedAppUsage
     int getUidPolicy(int uid);
+    @EnforcePermission("MANAGE_NETWORK_POLICY")
     int[] getUidsWithPolicy(int policy);
 
     void registerListener(INetworkPolicyListener listener);
     void unregisterListener(INetworkPolicyListener listener);
 
     /** Control network policies atomically. */
+    @EnforcePermission("MANAGE_NETWORK_POLICY")
     @UnsupportedAppUsage
     void setNetworkPolicies(in NetworkPolicy[] policies);
+    @EnforcePermission("MANAGE_NETWORK_POLICY")
     NetworkPolicy[] getNetworkPolicies(String callingPackage);
 
     /** Snooze limit on policy matching given template. */
+    @EnforcePermission("MANAGE_NETWORK_POLICY")
     @UnsupportedAppUsage
     void snoozeLimit(in NetworkTemplate template);
 
     /** Control if background data is restricted system-wide. */
     @UnsupportedAppUsage
     void setRestrictBackground(boolean restrictBackground);
+    @EnforcePermission("MANAGE_NETWORK_POLICY")
     @UnsupportedAppUsage
     boolean getRestrictBackground();
 
@@ -61,10 +70,13 @@
         2 - whitelisted
         3 - enabled
     */
+    @EnforcePermission("ACCESS_NETWORK_STATE")
     int getRestrictBackgroundByCaller();
     int getRestrictBackgroundStatus(int uid);
 
+    @EnforcePermission("MANAGE_NETWORK_POLICY")
     void setDeviceIdleMode(boolean enabled);
+    @EnforcePermission("MANAGE_NETWORK_POLICY")
     void setWifiMeteredOverride(String networkId, int meteredOverride);
 
     int getMultipathPreference(in Network network);
@@ -76,8 +88,10 @@
     String getSubscriptionPlansOwner(int subId);
     void setSubscriptionOverride(int subId, int overrideMask, int overrideValue, in int[] networkTypes, long expirationDurationMillis, String callingPackage);
 
+    @EnforcePermission("NETWORK_SETTINGS")
     void factoryReset(String subscriber);
 
     boolean isUidNetworkingBlocked(int uid, boolean meteredNetwork);
+    @EnforcePermission("OBSERVE_NETWORK_POLICY")
     boolean isUidRestrictedOnMeteredNetworks(int uid);
 }
diff --git a/core/java/android/net/ITetheringStatsProvider.aidl b/core/java/android/net/ITetheringStatsProvider.aidl
deleted file mode 100644
index da0bf4c..0000000
--- a/core/java/android/net/ITetheringStatsProvider.aidl
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net;
-
-import android.net.NetworkStats;
-
-/**
- * Interface for NetworkManagementService to query tethering statistics and set data limits.
- *
- * TODO: this does not really need to be an interface since Tethering runs in the same process
- * as NetworkManagementService. Consider refactoring Tethering to use direct access to
- * NetworkManagementService instead of using INetworkManagementService, and then deleting this
- * interface.
- *
- * @hide
- */
-interface ITetheringStatsProvider {
-    // Returns cumulative statistics for all tethering sessions since boot, on all upstreams.
-    // @code {how} is one of the NetworkStats.STATS_PER_* constants. If {@code how} is
-    // {@code STATS_PER_IFACE}, the provider should not include any traffic that is already
-    // counted by kernel interface counters.
-    NetworkStats getTetherStats(int how);
-
-    // Sets the interface quota for the specified upstream interface. This is defined as the number
-    // of bytes, starting from zero and counting from now, after which data should stop being
-    // forwarded to/from the specified upstream. A value of QUOTA_UNLIMITED means there is no limit.
-    void setInterfaceQuota(String iface, long quotaBytes);
-
-    // Indicates that no data usage limit is set.
-    const int QUOTA_UNLIMITED = -1;
-}
diff --git a/core/java/android/net/OWNERS b/core/java/android/net/OWNERS
index b989488..feeef55 100644
--- a/core/java/android/net/OWNERS
+++ b/core/java/android/net/OWNERS
@@ -5,3 +5,4 @@
 per-file **IpSec* = file:/services/core/java/com/android/server/vcn/OWNERS
 per-file SSL*,Uri*,Url* = prb@google.com,oth@google.com,narayan@google.com,ngeoffray@google.com
 per-file SntpClient* = file:/services/core/java/com/android/server/timedetector/OWNERS
+per-file Uri.java,Uri.aidl = varunshah@google.com
diff --git a/core/java/android/net/Uri.java b/core/java/android/net/Uri.java
index 7fbaf10..70de477 100644
--- a/core/java/android/net/Uri.java
+++ b/core/java/android/net/Uri.java
@@ -1788,8 +1788,8 @@
      * or external sources like Bluetooth, NFC, or the Internet) should
      * be normalized before they are used to create an Intent.
      *
-     * <p class="note">This method does <em>not</em> validate bad URI's,
-     * or 'fix' poorly formatted URI's - so do not use it for input validation.
+     * <p class="note">This method does <em>not</em> validate bad URIs,
+     * or 'fix' poorly formatted URIs - so do not use it for input validation.
      * A Uri will always be returned, even if the Uri is badly formatted to
      * begin with and a scheme component cannot be found.
      *
@@ -1992,21 +1992,14 @@
      */
     static abstract class AbstractPart {
 
-        // Possible values of mCanonicalRepresentation.
-        static final int REPRESENTATION_ENCODED = 1;
-        static final int REPRESENTATION_DECODED = 2;
-
         volatile String encoded;
         volatile String decoded;
-        private final int mCanonicalRepresentation;
 
         AbstractPart(String encoded, String decoded) {
             if (encoded != NotCachedHolder.NOT_CACHED) {
-                this.mCanonicalRepresentation = REPRESENTATION_ENCODED;
                 this.encoded = encoded;
                 this.decoded = NotCachedHolder.NOT_CACHED;
             } else if (decoded != NotCachedHolder.NOT_CACHED) {
-                this.mCanonicalRepresentation = REPRESENTATION_DECODED;
                 this.encoded = NotCachedHolder.NOT_CACHED;
                 this.decoded = decoded;
             } else {
@@ -2021,24 +2014,6 @@
             boolean hasDecoded = decoded != NotCachedHolder.NOT_CACHED;
             return hasDecoded ? decoded : (decoded = decode(encoded));
         }
-
-        final void writeTo(Parcel parcel) {
-            final String canonicalValue;
-            if (mCanonicalRepresentation == REPRESENTATION_ENCODED) {
-                canonicalValue = encoded;
-            } else if (mCanonicalRepresentation == REPRESENTATION_DECODED) {
-                canonicalValue = decoded;
-            } else {
-                throw new IllegalArgumentException("Unknown representation: "
-                    + mCanonicalRepresentation);
-            }
-            if (canonicalValue == NotCachedHolder.NOT_CACHED) {
-                throw new AssertionError("Canonical value not cached ("
-                    + mCanonicalRepresentation + ")");
-            }
-            parcel.writeInt(mCanonicalRepresentation);
-            parcel.writeString8(canonicalValue);
-        }
     }
 
     /**
@@ -2067,20 +2042,6 @@
             return hasEncoded ? encoded : (encoded = encode(decoded));
         }
 
-        static Part readFrom(Parcel parcel) {
-            int representation = parcel.readInt();
-            String value = parcel.readString8();
-            switch (representation) {
-                case REPRESENTATION_ENCODED:
-                    return fromEncoded(value);
-                case REPRESENTATION_DECODED:
-                    return fromDecoded(value);
-                default:
-                    throw new IllegalArgumentException("Unknown representation: "
-                            + representation);
-            }
-        }
-
         /**
          * Returns given part or {@link #NULL} if the given part is null.
          */
@@ -2256,23 +2217,6 @@
             return appendEncodedSegment(oldPart, encoded);
         }
 
-        static PathPart readFrom(Parcel parcel) {
-            int representation = parcel.readInt();
-            switch (representation) {
-                case REPRESENTATION_ENCODED:
-                    return fromEncoded(parcel.readString8());
-                case REPRESENTATION_DECODED:
-                    return fromDecoded(parcel.readString8());
-                default:
-                    throw new IllegalArgumentException("Unknown representation: " + representation);
-            }
-        }
-
-        static PathPart readFrom(boolean hasSchemeOrAuthority, Parcel parcel) {
-            final PathPart path = readFrom(parcel);
-            return hasSchemeOrAuthority ? makeAbsolute(path) : path;
-        }
-
         /**
          * Creates a path from the encoded string.
          *
diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java
index 9e97216..c111138 100644
--- a/core/java/android/nfc/NfcAdapter.java
+++ b/core/java/android/nfc/NfcAdapter.java
@@ -26,8 +26,6 @@
 import android.annotation.SystemApi;
 import android.annotation.UserIdInt;
 import android.app.Activity;
-import android.app.ActivityThread;
-import android.app.OnActivityPausedListener;
 import android.app.PendingIntent;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
@@ -1473,17 +1471,11 @@
         if (activity == null || intent == null) {
             throw new NullPointerException();
         }
-        if (!activity.isResumed()) {
-            throw new IllegalStateException("Foreground dispatch can only be enabled " +
-                    "when your activity is resumed");
-        }
         try {
             TechListParcel parcel = null;
             if (techLists != null && techLists.length > 0) {
                 parcel = new TechListParcel(techLists);
             }
-            ActivityThread.currentActivityThread().registerOnActivityPausedListener(activity,
-                    mForegroundDispatchListener);
             sService.setForegroundDispatch(intent, filters, parcel);
         } catch (RemoteException e) {
             attemptDeadServiceRecovery(e);
@@ -1511,25 +1503,8 @@
                 throw new UnsupportedOperationException();
             }
         }
-        ActivityThread.currentActivityThread().unregisterOnActivityPausedListener(activity,
-                mForegroundDispatchListener);
-        disableForegroundDispatchInternal(activity, false);
-    }
-
-    OnActivityPausedListener mForegroundDispatchListener = new OnActivityPausedListener() {
-        @Override
-        public void onPaused(Activity activity) {
-            disableForegroundDispatchInternal(activity, true);
-        }
-    };
-
-    void disableForegroundDispatchInternal(Activity activity, boolean force) {
         try {
             sService.setForegroundDispatch(null, null, null);
-            if (!force && !activity.isResumed()) {
-                throw new IllegalStateException("You must disable foreground dispatching " +
-                        "while your activity is still resumed");
-            }
         } catch (RemoteException e) {
             attemptDeadServiceRecovery(e);
         }
diff --git a/core/java/android/nfc/NfcAntennaInfo.java b/core/java/android/nfc/NfcAntennaInfo.java
index b002ca2..d54fcd2 100644
--- a/core/java/android/nfc/NfcAntennaInfo.java
+++ b/core/java/android/nfc/NfcAntennaInfo.java
@@ -85,8 +85,8 @@
         this.mDeviceHeight = in.readInt();
         this.mDeviceFoldable = in.readByte() != 0;
         this.mAvailableNfcAntennas = new ArrayList<>();
-        in.readTypedList(this.mAvailableNfcAntennas,
-                AvailableNfcAntenna.CREATOR);
+        in.readParcelableList(this.mAvailableNfcAntennas,
+                AvailableNfcAntenna.class.getClassLoader());
     }
 
     public static final @NonNull Parcelable.Creator<NfcAntennaInfo> CREATOR =
diff --git a/core/java/android/nfc/cardemulation/CardEmulation.java b/core/java/android/nfc/cardemulation/CardEmulation.java
index ac3344e..4909b08 100644
--- a/core/java/android/nfc/cardemulation/CardEmulation.java
+++ b/core/java/android/nfc/cardemulation/CardEmulation.java
@@ -594,9 +594,6 @@
         if (activity == null || service == null) {
             throw new NullPointerException("activity or service or category is null");
         }
-        if (!activity.isResumed()) {
-            throw new IllegalArgumentException("Activity must be resumed.");
-        }
         try {
             return sService.setPreferredService(service);
         } catch (RemoteException e) {
@@ -629,9 +626,6 @@
         if (activity == null) {
             throw new NullPointerException("activity is null");
         }
-        if (!activity.isResumed()) {
-            throw new IllegalArgumentException("Activity must be resumed.");
-        }
         try {
             return sService.unsetPreferredService();
         } catch (RemoteException e) {
diff --git a/core/java/android/os/BatteryManager.java b/core/java/android/os/BatteryManager.java
index 6bc0f6e..092923e 100644
--- a/core/java/android/os/BatteryManager.java
+++ b/core/java/android/os/BatteryManager.java
@@ -20,6 +20,7 @@
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
+import android.annotation.TestApi;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.Intent;
@@ -232,6 +233,7 @@
                                             OsProtoEnums.CHARGING_POLICY_ADAPTIVE_LONGLIFE; // = 4
 
     /** @hide */
+    @TestApi
     public static final int BATTERY_PLUGGED_ANY =
             BATTERY_PLUGGED_AC | BATTERY_PLUGGED_USB | BATTERY_PLUGGED_WIRELESS
                     | BATTERY_PLUGGED_DOCK;
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 2bad670..8596e54 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -54,6 +54,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.BatteryStatsHistoryIterator;
+import com.android.internal.os.CpuScalingPolicies;
 
 import com.google.android.collect.Lists;
 
@@ -1008,9 +1009,11 @@
          * @param cluster the index of the CPU cluster.
          * @param step the index of the CPU speed. This is not the actual speed of the CPU.
          * @param which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT.
-         * @see com.android.internal.os.PowerProfile#getNumCpuClusters()
-         * @see com.android.internal.os.PowerProfile#getNumSpeedStepsInCpuCluster(int)
+         * @see com.android.internal.os.CpuScalingPolicies#getPolicies
+         * @see com.android.internal.os.CpuScalingPolicies#getFrequencies
+         * @deprecated Unused except in tests
          */
+        @Deprecated
         public abstract long getTimeAtCpuSpeed(int cluster, int step, int which);
 
         /**
@@ -1648,11 +1651,9 @@
     public abstract long getNextMaxDailyDeadline();
 
     /**
-     * Returns the total number of frequencies across all CPU clusters.
+     * Returns the CPU scaling policies.
      */
-    public abstract int getCpuFreqCount();
-
-    public abstract long[] getCpuFreqs();
+    public abstract CpuScalingPolicies getCpuScalingPolicies();
 
     public final static class HistoryTag {
         public String string;
@@ -1966,7 +1967,7 @@
         // Values for NUM_GPS_SIGNAL_QUALITY_LEVELS
         public static final int STATE2_GPS_SIGNAL_QUALITY_SHIFT = 7;
         public static final int STATE2_GPS_SIGNAL_QUALITY_MASK =
-            0x1 << STATE2_GPS_SIGNAL_QUALITY_SHIFT;
+                0x3 << STATE2_GPS_SIGNAL_QUALITY_SHIFT;
 
         public static final int STATE2_POWER_SAVE_FLAG = 1<<31;
         public static final int STATE2_VIDEO_ON_FLAG = 1<<30;
@@ -3028,7 +3029,7 @@
                 "cellular_high_tx_power", "Chtp"),
         new BitDescription(HistoryItem.STATE2_GPS_SIGNAL_QUALITY_MASK,
             HistoryItem.STATE2_GPS_SIGNAL_QUALITY_SHIFT, "gps_signal_quality", "Gss",
-            new String[] { "poor", "good"}, new String[] { "poor", "good"})
+            new String[] { "poor", "good", "none"}, new String[] { "poor", "good", "none"})
     };
 
     public static final String[] HISTORY_EVENT_NAMES = new String[] {
@@ -3390,8 +3391,8 @@
      *     cluster1-speeed1, cluster1-speed2, ..., cluster2-speed1, cluster2-speed2, ...
      * </pre>
      *
-     * @see com.android.internal.os.PowerProfile#getNumCpuClusters()
-     * @see com.android.internal.os.PowerProfile#getNumSpeedStepsInCpuCluster(int)
+     * @see com.android.internal.os.CpuScalingPolicies#getPolicies
+     * @see com.android.internal.os.CpuScalingPolicies#getFrequencies
      */
     @Nullable
     public abstract long[] getSystemServiceTimeAtCpuSpeeds();
@@ -4677,12 +4678,14 @@
                             proportionalAttributionCalculator.getProportionalPowerMah(consumer)));
         }
 
-        final long[] cpuFreqs = getCpuFreqs();
-        if (cpuFreqs != null) {
+        final CpuScalingPolicies scalingPolicies = getCpuScalingPolicies();
+        if (scalingPolicies != null) {
             sb.setLength(0);
-            for (int i = 0; i < cpuFreqs.length; ++i) {
-                if (i != 0) sb.append(',');
-                sb.append(cpuFreqs[i]);
+            for (int policy : scalingPolicies.getPolicies()) {
+                for (int frequency : scalingPolicies.getFrequencies(policy)) {
+                    if (sb.length() != 0) sb.append(',');
+                    sb.append(frequency);
+                }
             }
             dumpLine(pw, 0 /* uid */, category, GLOBAL_CPU_FREQ_DATA, sb.toString());
         }
@@ -4994,11 +4997,12 @@
             }
 
             // If the cpuFreqs is null, then don't bother checking for cpu freq times.
-            if (cpuFreqs != null) {
+            if (scalingPolicies != null) {
                 final long[] cpuFreqTimeMs = u.getCpuFreqTimes(which);
                 // If total cpuFreqTimes is null, then we don't need to check for
                 // screenOffCpuFreqTimes.
-                if (cpuFreqTimeMs != null && cpuFreqTimeMs.length == cpuFreqs.length) {
+                if (cpuFreqTimeMs != null
+                        && cpuFreqTimeMs.length == scalingPolicies.getScalingStepCount()) {
                     sb.setLength(0);
                     for (int i = 0; i < cpuFreqTimeMs.length; ++i) {
                         if (i != 0) sb.append(',');
@@ -5018,7 +5022,8 @@
                             cpuFreqTimeMs.length, sb.toString());
                 }
 
-                final long[] timesInFreqMs = new long[getCpuFreqCount()];
+                final long[] timesInFreqMs =
+                        new long[getCpuScalingPolicies().getScalingStepCount()];
                 for (int procState = 0; procState < Uid.NUM_PROCESS_STATE; ++procState) {
                     if (u.getCpuFreqTimes(timesInFreqMs, procState)) {
                         sb.setLength(0);
@@ -6000,14 +6005,18 @@
             }
         }
 
-        final long[] cpuFreqs = getCpuFreqs();
-        if (cpuFreqs != null) {
+        final CpuScalingPolicies scalingPolicies = getCpuScalingPolicies();
+        if (scalingPolicies != null) {
             sb.setLength(0);
-            sb.append("  CPU freqs:");
-            for (int i = 0; i < cpuFreqs.length; ++i) {
-                sb.append(' ').append(cpuFreqs[i]);
+            sb.append("  CPU scaling: ");
+            for (int policy : scalingPolicies.getPolicies()) {
+                sb.append(" policy").append(policy).append(':');
+                for (int frequency : scalingPolicies.getFrequencies(policy)) {
+                    sb.append(' ').append(frequency);
+                }
             }
-            pw.println(sb.toString());
+
+            pw.println(sb);
             pw.println();
         }
 
@@ -6628,7 +6637,7 @@
                 pw.println(sb.toString());
             }
 
-            final long[] timesInFreqMs = new long[getCpuFreqCount()];
+            final long[] timesInFreqMs = new long[getCpuScalingPolicies().getScalingStepCount()];
             for (int procState = 0; procState < Uid.NUM_PROCESS_STATE; ++procState) {
                 if (u.getCpuFreqTimes(timesInFreqMs, procState)) {
                     sb.setLength(0);
@@ -8078,12 +8087,13 @@
             proto.write(UidProto.Cpu.USER_DURATION_MS, roundUsToMs(u.getUserCpuTimeUs(which)));
             proto.write(UidProto.Cpu.SYSTEM_DURATION_MS, roundUsToMs(u.getSystemCpuTimeUs(which)));
 
-            final long[] cpuFreqs = getCpuFreqs();
-            if (cpuFreqs != null) {
+            final CpuScalingPolicies scalingPolicies = getCpuScalingPolicies();
+            if (scalingPolicies != null) {
                 final long[] cpuFreqTimeMs = u.getCpuFreqTimes(which);
                 // If total cpuFreqTimes is null, then we don't need to check for
                 // screenOffCpuFreqTimes.
-                if (cpuFreqTimeMs != null && cpuFreqTimeMs.length == cpuFreqs.length) {
+                if (cpuFreqTimeMs != null
+                        && cpuFreqTimeMs.length == scalingPolicies.getScalingStepCount()) {
                     long[] screenOffCpuFreqTimeMs = u.getScreenOffCpuFreqTimes(which);
                     if (screenOffCpuFreqTimeMs == null) {
                         screenOffCpuFreqTimeMs = new long[cpuFreqTimeMs.length];
@@ -8100,8 +8110,9 @@
                 }
             }
 
-            final long[] timesInFreqMs = new long[getCpuFreqCount()];
-            final long[] timesInFreqScreenOffMs = new long[getCpuFreqCount()];
+            final int stepCount = getCpuScalingPolicies().getScalingStepCount();
+            final long[] timesInFreqMs = new long[stepCount];
+            final long[] timesInFreqScreenOffMs = new long[stepCount];
             for (int procState = 0; procState < Uid.NUM_PROCESS_STATE; ++procState) {
                 if (u.getCpuFreqTimes(timesInFreqMs, procState)) {
                     if (!u.getScreenOffCpuFreqTimes(timesInFreqScreenOffMs, procState)) {
@@ -8571,10 +8582,12 @@
         dumpDurationSteps(proto, SystemProto.DISCHARGE_STEP, getDischargeLevelStepTracker());
 
         // CPU frequencies (GLOBAL_CPU_FREQ_DATA)
-        final long[] cpuFreqs = getCpuFreqs();
-        if (cpuFreqs != null) {
-            for (long i : cpuFreqs) {
-                proto.write(SystemProto.CPU_FREQUENCY, i);
+        final CpuScalingPolicies scalingPolicies = getCpuScalingPolicies();
+        if (scalingPolicies != null) {
+            for (int policy : scalingPolicies.getPolicies()) {
+                for (int frequency : scalingPolicies.getFrequencies(policy)) {
+                    proto.write(SystemProto.CPU_FREQUENCY, frequency);
+                }
             }
         }
 
diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java
index 00676f3..a861489 100644
--- a/core/java/android/os/Binder.java
+++ b/core/java/android/os/Binder.java
@@ -325,7 +325,24 @@
      * @hide
      */
     @CriticalNative
-    public static final native boolean isDirectlyHandlingTransaction();
+    public static final native boolean isDirectlyHandlingTransactionNative();
+
+    private static boolean sIsHandlingBinderTransaction = false;
+
+    /**
+     * @hide
+     */
+    public static final boolean isDirectlyHandlingTransaction() {
+        return sIsHandlingBinderTransaction || isDirectlyHandlingTransactionNative();
+    }
+
+    /**
+     * This is Test API which will be used to override output of isDirectlyHandlingTransactionNative
+     * @hide
+     */
+    public static void setIsDirectlyHandlingTransactionOverride(boolean isInTransaction) {
+        sIsHandlingBinderTransaction = isInTransaction;
+    }
 
     /**
     * Returns {@code true} if the current thread has had its identity
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 9f9c222..eb471705 100755
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -1223,6 +1223,11 @@
          * Upside Down Cake.
          */
         public static final int UPSIDE_DOWN_CAKE = 34;
+
+        /**
+         * Vanilla Ice Cream.
+         */
+        public static final int VANILLA_ICE_CREAM = CUR_DEVELOPMENT;
     }
 
     /** The type of build, like "user" or "eng". */
diff --git a/core/java/android/os/CombinedVibration.java b/core/java/android/os/CombinedVibration.java
index 5f2c113..db1a741 100644
--- a/core/java/android/os/CombinedVibration.java
+++ b/core/java/android/os/CombinedVibration.java
@@ -127,10 +127,52 @@
     /** @hide */
     public abstract void validate();
 
+    /**
+     * Applies given effect transformation with a fixed parameter to each effect in this vibration.
+     *
+     * @param transformation The vibration effect transformation to be applied to all effects
+     * @param param          The fixed parameter to be applied in all effect transformations
+     * @return the result of running the given transformation on all effects of this vibration
+     * @hide
+     */
+    public abstract <ParamT> CombinedVibration transform(
+            VibrationEffect.Transformation<ParamT> transformation, ParamT param);
+
+    /**
+     * Applies given vibrator adapter to each effect in this combined vibration.
+     *
+     * @param adapter The vibrator adapter to be used on this vibration
+     * @return the result of running the given adapter on all effects of this vibration
+     * @hide
+     */
+    public abstract CombinedVibration adapt(VibratorAdapter adapter);
+
     /** @hide */
     public abstract boolean hasVibrator(int vibratorId);
 
     /**
+     * Adapts a {@link VibrationEffect} to a specific device vibrator using the ID.
+     *
+     * <p>This can be used for adapting effects to the capabilities of the specific device vibrator
+     * it's been mapped to by the combined vibration.
+     *
+     * @hide
+     */
+    public interface VibratorAdapter {
+
+        /**
+         * Return the list of vibrator IDs available on the device, to be used by {@link
+         * CombinedVibration} to fan-out individual effects that aren't assigned to a specific
+         * vibrator.
+         */
+        int[] getAvailableVibratorIds();
+
+        /** Adapts a {@link VibrationEffect} to a given vibrator. */
+        @NonNull
+        VibrationEffect adaptToVibrator(int vibratorId, @NonNull VibrationEffect effect);
+    }
+
+    /**
      * A combination of haptic effects that should be played in multiple vibrators in parallel.
      *
      * @see CombinedVibration#startParallel()
@@ -340,12 +382,44 @@
 
         /** @hide */
         @Override
+        public <ParamT> CombinedVibration transform(
+                VibrationEffect.Transformation<ParamT> transformation, ParamT param) {
+            VibrationEffect newEffect = transformation.transform(mEffect, param);
+            if (mEffect.equals(newEffect)) {
+                return this;
+            }
+            // Make sure the validate methods are triggered
+            return CombinedVibration.createParallel(newEffect);
+        }
+
+        /** @hide */
+        @Override
+        public CombinedVibration adapt(VibratorAdapter adapter) {
+            ParallelCombination combination = CombinedVibration.startParallel();
+            boolean hasSameEffects = true;
+            for (int vibratorId : adapter.getAvailableVibratorIds()) {
+                VibrationEffect newEffect = adapter.adaptToVibrator(vibratorId, mEffect);
+                combination.addVibrator(vibratorId, newEffect);
+                hasSameEffects &= mEffect.equals(newEffect);
+            }
+            if (hasSameEffects) {
+                return this;
+            }
+            // Make sure the validate methods are triggered
+            return combination.combine();
+        }
+
+        /** @hide */
+        @Override
         public boolean hasVibrator(int vibratorId) {
             return true;
         }
 
         @Override
         public boolean equals(Object o) {
+            if (this == o) {
+                return true;
+            }
             if (!(o instanceof Mono)) {
                 return false;
             }
@@ -472,12 +546,54 @@
 
         /** @hide */
         @Override
+        public <ParamT> CombinedVibration transform(
+                VibrationEffect.Transformation<ParamT> transformation, ParamT param) {
+            ParallelCombination combination = CombinedVibration.startParallel();
+            boolean hasSameEffects = true;
+            for (int i = 0; i < mEffects.size(); i++) {
+                int vibratorId = mEffects.keyAt(i);
+                VibrationEffect effect = mEffects.valueAt(i);
+                VibrationEffect newEffect = transformation.transform(effect, param);
+                combination.addVibrator(vibratorId, newEffect);
+                hasSameEffects &= effect.equals(newEffect);
+            }
+            if (hasSameEffects) {
+                return this;
+            }
+            // Make sure the validate methods are triggered
+            return combination.combine();
+        }
+
+        /** @hide */
+        @Override
+        public CombinedVibration adapt(VibratorAdapter adapter) {
+            ParallelCombination combination = CombinedVibration.startParallel();
+            boolean hasSameEffects = true;
+            for (int i = 0; i < mEffects.size(); i++) {
+                int vibratorId = mEffects.keyAt(i);
+                VibrationEffect effect = mEffects.valueAt(i);
+                VibrationEffect newEffect = adapter.adaptToVibrator(vibratorId, effect);
+                combination.addVibrator(vibratorId, newEffect);
+                hasSameEffects &= effect.equals(newEffect);
+            }
+            if (hasSameEffects) {
+                return this;
+            }
+            // Make sure the validate methods are triggered
+            return combination.combine();
+        }
+
+        /** @hide */
+        @Override
         public boolean hasVibrator(int vibratorId) {
             return mEffects.indexOfKey(vibratorId) >= 0;
         }
 
         @Override
         public boolean equals(Object o) {
+            if (this == o) {
+                return true;
+            }
             if (!(o instanceof Stereo)) {
                 return false;
             }
@@ -648,6 +764,43 @@
 
         /** @hide */
         @Override
+        public <ParamT> CombinedVibration transform(
+                VibrationEffect.Transformation<ParamT> transformation, ParamT param) {
+            SequentialCombination combination = CombinedVibration.startSequential();
+            boolean hasSameEffects = true;
+            for (int i = 0; i < mEffects.size(); i++) {
+                CombinedVibration vibration = mEffects.get(i);
+                CombinedVibration newVibration = vibration.transform(transformation, param);
+                combination.addNext(newVibration, mDelays.get(i));
+                hasSameEffects &= vibration.equals(newVibration);
+            }
+            if (hasSameEffects) {
+                return this;
+            }
+            // Make sure the validate methods are triggered
+            return combination.combine();
+        }
+
+        /** @hide */
+        @Override
+        public CombinedVibration adapt(VibratorAdapter adapter) {
+            SequentialCombination combination = CombinedVibration.startSequential();
+            boolean hasSameEffects = true;
+            for (int i = 0; i < mEffects.size(); i++) {
+                CombinedVibration vibration = mEffects.get(i);
+                CombinedVibration newVibration = vibration.adapt(adapter);
+                combination.addNext(newVibration, mDelays.get(i));
+                hasSameEffects &= vibration.equals(newVibration);
+            }
+            if (hasSameEffects) {
+                return this;
+            }
+            // Make sure the validate methods are triggered
+            return combination.combine();
+        }
+
+        /** @hide */
+        @Override
         public boolean hasVibrator(int vibratorId) {
             final int effectCount = mEffects.size();
             for (int i = 0; i < effectCount; i++) {
@@ -660,6 +813,9 @@
 
         @Override
         public boolean equals(Object o) {
+            if (this == o) {
+                return true;
+            }
             if (!(o instanceof Sequential)) {
                 return false;
             }
diff --git a/core/java/android/os/DdmSyncStageUpdater.java b/core/java/android/os/DdmSyncStageUpdater.java
new file mode 100644
index 0000000..90f7076
--- /dev/null
+++ b/core/java/android/os/DdmSyncStageUpdater.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 android.os;
+
+import android.os.DdmSyncState.Stage;
+import android.util.Slog;
+
+import org.apache.harmony.dalvik.ddmc.Chunk;
+import org.apache.harmony.dalvik.ddmc.ChunkHandler;
+import org.apache.harmony.dalvik.ddmc.DdmServer;
+
+import java.nio.ByteBuffer;
+
+/**
+ * @hide
+ */
+// Making it public so ActivityThread can access it.
+public class DdmSyncStageUpdater {
+
+    private static final String TAG = "DdmSyncStageUpdater";
+
+    private static final int CHUNK_STAGE = ChunkHandler.type("STAG");
+
+    /**
+     * @hide
+     */
+    public DdmSyncStageUpdater() {
+    }
+
+    /**
+     * @hide
+     */
+    // Making it public so ActivityThread can access it.
+    public synchronized void next(Stage stage) {
+        try {
+            DdmSyncState.next(stage);
+
+            // Request DDMServer to send a STAG chunk
+            ByteBuffer data = ByteBuffer.allocate(Integer.BYTES);
+            data.putInt(stage.toInt());
+            Chunk stagChunk = new Chunk(CHUNK_STAGE, data);
+            DdmServer.sendChunk(stagChunk);
+        } catch (Exception e) {
+            // Catch everything to make sure we don't impact ActivityThread
+            Slog.w(TAG, "Unable to go to next stage" + stage, e);
+        }
+    }
+
+}
diff --git a/core/java/android/os/DdmSyncState.java b/core/java/android/os/DdmSyncState.java
new file mode 100644
index 0000000..09c9ef2
--- /dev/null
+++ b/core/java/android/os/DdmSyncState.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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 java.util.Arrays;
+
+/**
+ * Keep track of an app boot state. The main purpose is to stream back DDM packet so a DDM client
+ * can synchronize with the app state.
+ *
+ * The state is static so it can be accessed from HELO handler.
+ *
+ * @hide
+ **/
+public final class DdmSyncState {
+
+    /**
+     * @hide
+     */
+    public enum Stage {
+        // From zygote to attach
+        Boot("BOOT"),
+
+        // From attach to handleBindApplication
+        Attach("ATCH"),
+
+        // When handleBindApplication is finally reached
+        Bind("BIND"),
+
+        // When the actual package name is known (not the early "<preinitalized>" value).
+        Named("NAMD"),
+
+        // Can be skipped if the app is not debugged.
+        Debugger("DEBG"),
+
+        // App is in RunLoop
+        Running("A_GO");
+
+        final String mLabel;
+
+        Stage(String label) {
+            if (label.length() != 4) {
+                throw new IllegalStateException(
+                    "Bad stage id '" + label + "'. Must be four letters");
+            }
+            this.mLabel = label;
+        }
+
+        /**
+         * To be included in a DDM packet payload, the stage is encoded in a big-endian int
+         * @hide
+         */
+        public int toInt() {
+            int result = 0;
+            for (int i = 0; i < 4; ++i) {
+                result = ((result << 8) | (mLabel.charAt(i) & 0xff));
+            }
+            return result;
+        }
+    }
+
+    private static int sCurrentStageIndex = 0;
+
+    /**
+     * @hide
+     */
+    public static synchronized Stage getStage() {
+        return Stage.values()[sCurrentStageIndex];
+    }
+
+    /**
+     * @hide
+     */
+    public static void reset() {
+        sCurrentStageIndex = 0;
+    }
+
+    /**
+     * Search for the next level down the list of Stage. Only succeed if the next stage
+     * if a later stage (no cycling allowed).
+     *
+     * @hide
+     */
+    public static synchronized void next(Stage nextStage) {
+        Stage[] stages = Stage.values();
+        // Search for the requested next stage
+        int rover = sCurrentStageIndex;
+        while (rover < stages.length && stages[rover] != nextStage) {
+            rover++;
+        }
+
+        if (rover == stages.length || stages[rover] != nextStage) {
+            throw new IllegalStateException(
+                "Cannot go to " + nextStage + " from:" + getInternalState());
+        }
+
+        sCurrentStageIndex = rover;
+    }
+
+    /**
+     * Use to build error messages
+     * @hide
+     */
+    private static String getInternalState() {
+        StringBuilder sb = new StringBuilder("\n");
+        sb.append("level = ").append(sCurrentStageIndex);
+        sb.append("\n");
+        sb.append("stages = ");
+        sb.append(Arrays.toString(Arrays.stream(Stage.values()).map(Enum::name).toArray()));
+        sb.append("\n");
+        return sb.toString();
+    }
+}
diff --git a/core/java/android/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java
index 94971b8..e6bdfe1 100644
--- a/core/java/android/os/GraphicsEnvironment.java
+++ b/core/java/android/os/GraphicsEnvironment.java
@@ -116,6 +116,8 @@
     private static final String ANGLE_GL_DRIVER_CHOICE_ANGLE = "angle";
     private static final String ANGLE_GL_DRIVER_CHOICE_NATIVE = "native";
 
+    private static final String PROPERTY_RO_ANGLE_SUPPORTED = "ro.gfx.angle.supported";
+
     private ClassLoader mClassLoader;
     private String mLibrarySearchPaths;
     private String mLibraryPermittedPaths;
@@ -123,6 +125,7 @@
 
     private int mAngleOptInIndex = -1;
     private boolean mEnabledByGameMode = false;
+    private boolean mShouldUseAngle = false;
 
     /**
      * Set up GraphicsEnvironment
@@ -141,19 +144,16 @@
 
         // Setup ANGLE and pass down ANGLE details to the C++ code
         Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "setupAngle");
-        boolean useAngle = false;
         if (setupAngle(context, coreSettings, pm, packageName)) {
-            if (shouldUseAngle(context, coreSettings, packageName)) {
-                useAngle = true;
-                setGpuStats(ANGLE_DRIVER_NAME, ANGLE_DRIVER_VERSION_NAME, ANGLE_DRIVER_VERSION_CODE,
-                        0, packageName, getVulkanVersion(pm));
-            }
+            mShouldUseAngle = true;
+            setGpuStats(ANGLE_DRIVER_NAME, ANGLE_DRIVER_VERSION_NAME, ANGLE_DRIVER_VERSION_CODE,
+                    0, packageName, getVulkanVersion(pm));
         }
         Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS);
 
         Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "chooseDriver");
         if (!chooseDriver(context, coreSettings, pm, packageName, appInfoWithMetaData)) {
-            if (!useAngle) {
+            if (!mShouldUseAngle) {
                 setGpuStats(SYSTEM_DRIVER_NAME, SYSTEM_DRIVER_VERSION_NAME,
                         SYSTEM_DRIVER_VERSION_CODE,
                         SystemProperties.getLong(PROPERTY_GFX_DRIVER_BUILD_TIME, 0),
@@ -503,10 +503,12 @@
         final List<ResolveInfo> resolveInfos =
                 pm.queryIntentActivities(intent, PackageManager.MATCH_SYSTEM_ONLY);
         if (resolveInfos.size() != 1) {
-            Log.e(TAG, "Invalid number of ANGLE packages. Required: 1, Found: "
+            Log.v(TAG, "Invalid number of ANGLE packages. Required: 1, Found: "
                     + resolveInfos.size());
-            for (ResolveInfo resolveInfo : resolveInfos) {
-                Log.e(TAG, "Found ANGLE package: " + resolveInfo.activityInfo.packageName);
+            if (DEBUG) {
+                for (ResolveInfo resolveInfo : resolveInfos) {
+                    Log.d(TAG, "Found ANGLE package: " + resolveInfo.activityInfo.packageName);
+                }
             }
             return "";
         }
@@ -541,26 +543,42 @@
     }
 
     /**
-     * Determine whether ANGLE should be used, set it up if so, and pass ANGLE details down to
-     * the C++ GraphicsEnv class.
+     * Determine whether ANGLE should be used, attempt to set up from apk first, if ANGLE can be
+     * set up from apk, pass ANGLE details down to the C++ GraphicsEnv class via
+     * GraphicsEnv::setAngleInfo(). If apk setup fails, attempt to set up to use system ANGLE.
+     * Return false if both fail.
      *
-     * If ANGLE will be used, GraphicsEnv::setAngleInfo() will be called to enable ANGLE to be
-     * properly used.
-     *
-     * @param context
-     * @param bundle
-     * @param pm
+     * @param context - Context of the application.
+     * @param bundle - Bundle of the application.
+     * @param packageManager - PackageManager of the application process.
      * @param packageName - package name of the application.
-     * @return true: ANGLE setup successfully
-     *         false: ANGLE not setup (not on allowlist, ANGLE not present, etc.)
+     * @return true: can set up to use ANGLE successfully.
+     *         false: can not set up to use ANGLE (not on allowlist, ANGLE not present, etc.)
      */
-    private boolean setupAngle(Context context, Bundle bundle, PackageManager pm,
+    private boolean setupAngle(Context context, Bundle bundle, PackageManager packageManager,
             String packageName) {
 
         if (!shouldUseAngle(context, bundle, packageName)) {
             return false;
         }
 
+        return setupAngleFromApk(context, bundle, packageManager, packageName)
+                || setupAngleFromSystem(context, bundle, packageName);
+    }
+
+    /**
+     * Attempt to set up ANGLE from the packaged apk, if the apk can be found, pass ANGLE details to
+     * the C++ GraphicsEnv class.
+     *
+     * @param context - Context of the application.
+     * @param bundle - Bundle of the application.
+     * @param packageManager - PackageManager of the application process.
+     * @param packageName - package name of the application.
+     * @return true: can set up to use ANGLE apk.
+     *         false: can not set up to use ANGLE apk (ANGLE apk not present, etc.)
+     */
+    private boolean setupAngleFromApk(Context context, Bundle bundle, PackageManager packageManager,
+            String packageName) {
         ApplicationInfo angleInfo = null;
 
         // If the developer has specified a debug package over ADB, attempt to find it
@@ -569,7 +587,7 @@
             Log.v(TAG, "ANGLE debug package enabled: " + anglePkgName);
             try {
                 // Note the debug package does not have to be pre-installed
-                angleInfo = pm.getApplicationInfo(anglePkgName, 0);
+                angleInfo = packageManager.getApplicationInfo(anglePkgName, 0);
             } catch (PackageManager.NameNotFoundException e) {
                 // If the debug package is specified but not found, abort.
                 Log.v(TAG, "ANGLE debug package '" + anglePkgName + "' not installed");
@@ -579,7 +597,7 @@
 
         // Otherwise, check to see if ANGLE is properly installed
         if (angleInfo == null) {
-            anglePkgName = getAnglePackageName(pm);
+            anglePkgName = getAnglePackageName(packageManager);
             if (TextUtils.isEmpty(anglePkgName)) {
                 Log.v(TAG, "Failed to find ANGLE package.");
                 return false;
@@ -588,7 +606,7 @@
             Log.v(TAG, "ANGLE package enabled: " + anglePkgName);
             try {
                 // Production ANGLE libraries must be pre-installed as a system app
-                angleInfo = pm.getApplicationInfo(anglePkgName,
+                angleInfo = packageManager.getApplicationInfo(anglePkgName,
                         PackageManager.MATCH_SYSTEM_ONLY);
             } catch (PackageManager.NameNotFoundException e) {
                 Log.v(TAG, "ANGLE package '" + anglePkgName + "' not installed");
@@ -612,12 +630,36 @@
         // If we make it to here, ANGLE will be used.  Call setAngleInfo() with the package name,
         // and features to use.
         final String[] features = getAngleEglFeatures(context, bundle);
-        setAngleInfo(paths, packageName, ANGLE_GL_DRIVER_CHOICE_ANGLE, features);
+        setAngleInfo(paths, false, packageName, features);
 
         return true;
     }
 
     /**
+     * Attempt to set up ANGLE from system, if the apk can be found, pass ANGLE details to
+     * the C++ GraphicsEnv class.
+     *
+     * @param context - Context of the application.
+     * @param bundle - Bundle of the application.
+     * @param packageName - package name of the application.
+     * @return true: can set up to use system ANGLE.
+     *         false: can not set up to use system ANGLE because it doesn't exist.
+     */
+    private boolean setupAngleFromSystem(Context context, Bundle bundle, String packageName) {
+        final boolean systemAngleSupported = SystemProperties
+                                             .getBoolean(PROPERTY_RO_ANGLE_SUPPORTED, false);
+        if (!systemAngleSupported) {
+            return false;
+        }
+
+        // If we make it to here, ANGLE will be used.  Call setAngleInfo() with the package name,
+        // and features to use.
+        final String[] features = getAngleEglFeatures(context, bundle);
+        setAngleInfo("", true, packageName, features);
+        return true;
+    }
+
+    /**
      * Determine if the "ANGLE In Use" dialog box should be shown.
      */
     private boolean shouldShowAngleInUseDialogBox(Context context) {
@@ -636,44 +678,37 @@
     }
 
     /**
-     * Determine if ANGLE will be used and setup the environment
-     */
-    private boolean setupAndUseAngle(Context context, String packageName) {
-        // Need to make sure we are evaluating ANGLE usage for the correct circumstances
-        if (!setupAngle(context, null, context.getPackageManager(), packageName)) {
-            Log.v(TAG, "Package '" + packageName + "' should not use ANGLE");
-            return false;
-        }
-
-        final boolean useAngle = getShouldUseAngle(packageName);
-        Log.v(TAG, "Package '" + packageName + "' should use ANGLE = '" + useAngle + "'");
-
-        return useAngle;
-    }
-
-    /**
-     * Show the ANGLE in Use Dialog Box
+     * Show the ANGLE in use dialog box.
+     * The ANGLE in use dialog box will show up as long as the application
+     * should use ANGLE. It does not mean the application has successfully
+     * loaded ANGLE because this check happens before the loading completes.
      * @param context
      */
     public void showAngleInUseDialogBox(Context context) {
-        final String packageName = context.getPackageName();
-
-        if (shouldShowAngleInUseDialogBox(context) && setupAndUseAngle(context, packageName)) {
-            final Intent intent = new Intent(ACTION_ANGLE_FOR_ANDROID_TOAST_MESSAGE);
-            String anglePkg = getAnglePackageName(context.getPackageManager());
-            intent.setPackage(anglePkg);
-
-            context.sendOrderedBroadcast(intent, null, new BroadcastReceiver() {
-                @Override
-                public void onReceive(Context context, Intent intent) {
-                    Bundle results = getResultExtras(true);
-
-                    String toastMsg = results.getString(INTENT_KEY_A4A_TOAST_MESSAGE);
-                    final Toast toast = Toast.makeText(context, toastMsg, Toast.LENGTH_LONG);
-                    toast.show();
-                }
-            }, null, Activity.RESULT_OK, null, null);
+        if (!shouldShowAngleInUseDialogBox(context)) {
+            return;
         }
+
+        if (!mShouldUseAngle) {
+            return;
+        }
+
+        final Intent intent = new Intent(ACTION_ANGLE_FOR_ANDROID_TOAST_MESSAGE);
+        final String anglePkg = getAnglePackageName(context.getPackageManager());
+        if (anglePkg.isEmpty()) {
+            return;
+        }
+
+        context.sendOrderedBroadcast(intent, null, new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                final Bundle results = getResultExtras(true);
+
+                final String toastMsg = results.getString(INTENT_KEY_A4A_TOAST_MESSAGE);
+                final Toast toast = Toast.makeText(context, toastMsg, Toast.LENGTH_LONG);
+                toast.show();
+            }
+        }, null, Activity.RESULT_OK, null, null);
     }
 
     private String[] getAngleEglFeatures(Context context, Bundle coreSettings) {
@@ -901,9 +936,8 @@
     private static native void setDriverPathAndSphalLibraries(String path, String sphalLibraries);
     private static native void setGpuStats(String driverPackageName, String driverVersionName,
             long driverVersionCode, long driverBuildTime, String appPackageName, int vulkanVersion);
-    private static native void setAngleInfo(String path, String appPackage,
-            String devOptIn, String[] features);
-    private static native boolean getShouldUseAngle(String packageName);
+    private static native void setAngleInfo(String path, boolean useSystemAngle, String packageName,
+            String[] features);
     private static native boolean setInjectLayersPrSetDumpable();
     private static native void nativeToggleAngleAsSystemDriver(boolean enabled);
 
diff --git a/core/java/android/os/INetworkManagementService.aidl b/core/java/android/os/INetworkManagementService.aidl
index 40f7533..ed14652 100644
--- a/core/java/android/os/INetworkManagementService.aidl
+++ b/core/java/android/os/INetworkManagementService.aidl
@@ -19,7 +19,6 @@
 
 import android.net.InterfaceConfiguration;
 import android.net.INetworkManagementEventObserver;
-import android.net.ITetheringStatsProvider;
 import android.net.Network;
 import android.net.NetworkStats;
 import android.net.RouteInfo;
@@ -144,14 +143,6 @@
     void startTethering(in String[] dhcpRanges);
 
     /**
-     * Start tethering services with the specified dhcp server range and
-     * DNS proxy config.
-     * {@code boolean} is used to control legacy DNS proxy server.
-     * {@code String[]} is a set of start end pairs defining the ranges.
-     */
-    void startTetheringWithConfiguration(boolean usingLegacyDnsProxy, in String[] dhcpRanges);
-
-    /**
      * Stop currently running tethering services
      */
     @UnsupportedAppUsage
@@ -182,23 +173,6 @@
     String[] listTetheredInterfaces();
 
     /**
-     * Returns the list of DNS forwarders (in order of priority)
-     */
-    String[] getDnsForwarders();
-
-    /**
-     * Enables unidirectional packet forwarding from {@code fromIface} to
-     * {@code toIface}.
-     */
-    void startInterfaceForwarding(String fromIface, String toIface);
-
-    /**
-     * Disables unidirectional packet forwarding from {@code fromIface} to
-     * {@code toIface}.
-     */
-    void stopInterfaceForwarding(String fromIface, String toIface);
-
-    /**
      *  Enables Network Address Translation between two interfaces.
      *  The address and netmask of the external interface is used for
      *  the NAT'ed network.
@@ -213,42 +187,10 @@
     void disableNat(String internalInterface, String externalInterface);
 
     /**
-     * Registers a {@code ITetheringStatsProvider} to provide tethering statistics.
-     * All registered providers will be called in order, and their results will be added together.
-     * Netd is always registered as a tethering stats provider.
-     */
-    void registerTetheringStatsProvider(ITetheringStatsProvider provider, String name);
-
-    /**
-     * Unregisters a previously-registered {@code ITetheringStatsProvider}.
-     */
-    void unregisterTetheringStatsProvider(ITetheringStatsProvider provider);
-
-    /**
-     * Reports that a tethering provider has reached a data limit.
-     *
-     * Currently triggers a global alert, which causes NetworkStatsService to poll counters and
-     * re-evaluate data usage.
-     *
-     * This does not take an interface name because:
-     * 1. The tethering offload stats provider cannot reliably determine the interface on which the
-     *    limit was reached, because the HAL does not provide it.
-     * 2. Firing an interface-specific alert instead of a global alert isn't really useful since in
-     *    all cases of interest, the system responds to both in the same way - it polls stats, and
-     *    then notifies NetworkPolicyManagerService of the fact.
-     */
-    void tetherLimitReached(ITetheringStatsProvider provider);
-
-    /**
      ** DATA USAGE RELATED
      **/
 
     /**
-     * Return summary of network statistics all tethering interfaces.
-     */
-    NetworkStats getNetworkStatsTethering(int how);
-
-    /**
      * Set quota for an interface.
      */
     void setInterfaceQuota(String iface, long quotaBytes);
@@ -269,11 +211,6 @@
     void removeInterfaceAlert(String iface);
 
     /**
-     * Set alert across all interfaces.
-     */
-    void setGlobalAlert(long alertBytes);
-
-    /**
      * Control network activity of a UID over interfaces with a quota limit.
      */
     void setUidOnMeteredNetworkDenylist(int uid, boolean enable);
@@ -291,7 +228,6 @@
 
     void setFirewallEnabled(boolean enabled);
     boolean isFirewallEnabled();
-    void setFirewallInterfaceRule(String iface, boolean allow);
     void setFirewallUidRule(int chain, int uid, int rule);
     void setFirewallUidRules(int chain, in int[] uids, in int[] rules);
     void setFirewallChainEnabled(int chain, boolean enable);
@@ -306,10 +242,6 @@
      */
     void denyProtect(int uid);
 
-    void addInterfaceToLocalNetwork(String iface, in List<RouteInfo> routes);
-    void removeInterfaceFromLocalNetwork(String iface);
-    int removeRoutesFromLocalNetwork(in List<RouteInfo> routes);
-
     @EnforcePermission("OBSERVE_NETWORK_POLICY")
     boolean isNetworkRestricted(int uid);
 }
diff --git a/core/java/android/os/IPowerStatsService.aidl b/core/java/android/os/IPowerStatsService.aidl
new file mode 100644
index 0000000..a0c2262
--- /dev/null
+++ b/core/java/android/os/IPowerStatsService.aidl
@@ -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 android.os;
+
+import android.os.ResultReceiver;
+
+/** @hide */
+interface IPowerStatsService {
+    /** @hide */
+    const String KEY_MONITORS = "monitors";
+    /** @hide */
+    const String KEY_ENERGY = "energy";
+    /** @hide */
+    const String KEY_TIMESTAMPS = "timestamps";
+
+    /** @hide */
+    const int RESULT_SUCCESS = 0;
+    /** @hide */
+    const int RESULT_UNSUPPORTED_POWER_MONITOR = 1;
+
+    /** {@hide} */
+    oneway void getSupportedPowerMonitors(in ResultReceiver resultReceiver);
+    /** {@hide} */
+    oneway void getPowerMonitorReadings(in int[] powerMonitorIndices,
+            in ResultReceiver resultReceiver);
+}
diff --git a/core/java/android/os/IRecoverySystem.aidl b/core/java/android/os/IRecoverySystem.aidl
index 88bdb7f..b3628ff 100644
--- a/core/java/android/os/IRecoverySystem.aidl
+++ b/core/java/android/os/IRecoverySystem.aidl
@@ -23,6 +23,7 @@
 /** @hide */
 
 interface IRecoverySystem {
+    @EnforcePermission("RECOVERY")
     boolean allocateSpaceForUpdate(in String packageFilePath);
     boolean uncrypt(in String packageFile, IRecoverySystemProgressListener listener);
     boolean setupBcb(in String command);
@@ -31,6 +32,7 @@
     boolean requestLskf(in String packageName, in IntentSender sender);
     boolean clearLskf(in String packageName);
     boolean isLskfCaptured(in String packageName);
+    @EnforcePermission("RECOVERY")
     int rebootWithLskfAssumeSlotSwitch(in String packageName, in String reason);
     int rebootWithLskf(in String packageName, in String reason, in boolean slotSwitch);
 }
diff --git a/core/java/android/os/ISystemUpdateManager.aidl b/core/java/android/os/ISystemUpdateManager.aidl
index f7f5079..cda2fa1 100644
--- a/core/java/android/os/ISystemUpdateManager.aidl
+++ b/core/java/android/os/ISystemUpdateManager.aidl
@@ -23,5 +23,6 @@
 /** @hide */
 interface ISystemUpdateManager {
     Bundle retrieveSystemUpdateInfo();
+    @EnforcePermission("RECOVERY")
     void updateSystemUpdateInfo(in PersistableBundle data);
 }
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index 8e1d2d6..839c56f 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -60,6 +60,7 @@
     ParcelFileDescriptor getUserIcon(int userId);
     UserInfo getPrimaryUser();
     int getMainUserId();
+    int getCommunalProfileId();
     int getPreviousFullUserToEnterForeground();
     List<UserInfo> getUsers(boolean excludePartial, boolean excludeDying, boolean excludePreCreated);
     List<UserInfo> getProfiles(int userId, boolean enabledOnly);
@@ -128,6 +129,7 @@
     int getUserBadgeLabelResId(int userId);
     int getUserBadgeColorResId(int userId);
     int getUserBadgeDarkColorResId(int userId);
+    int getUserStatusBarIconResId(int userId);
     boolean hasBadge(int userId);
     boolean isUserUnlocked(int userId);
     boolean isUserRunning(int userId);
diff --git a/core/java/android/os/IVibratorManagerService.aidl b/core/java/android/os/IVibratorManagerService.aidl
index fb9752f..6275352 100644
--- a/core/java/android/os/IVibratorManagerService.aidl
+++ b/core/java/android/os/IVibratorManagerService.aidl
@@ -25,8 +25,11 @@
 interface IVibratorManagerService {
     int[] getVibratorIds();
     VibratorInfo getVibratorInfo(int vibratorId);
+    @EnforcePermission("ACCESS_VIBRATOR_STATE")
     boolean isVibrating(int vibratorId);
+    @EnforcePermission("ACCESS_VIBRATOR_STATE")
     boolean registerVibratorStateListener(int vibratorId, in IVibratorStateListener listener);
+    @EnforcePermission("ACCESS_VIBRATOR_STATE")
     boolean unregisterVibratorStateListener(int vibratorId, in IVibratorStateListener listener);
     boolean setAlwaysOnEffect(int uid, String opPkg, int alwaysOnId,
             in CombinedVibration vibration, in VibrationAttributes attributes);
diff --git a/core/java/android/os/OWNERS b/core/java/android/os/OWNERS
index e9a3254..69889c5 100644
--- a/core/java/android/os/OWNERS
+++ b/core/java/android/os/OWNERS
@@ -79,3 +79,7 @@
 
 # ART
 per-file ArtModuleServiceManager.java = file:platform/art:/OWNERS
+
+# DDM Protocol
+per-file DdmSyncState.java = sanglardf@google.com, rpaquay@google.com
+per-file DdmSyncStageUpdater.java = sanglardf@google.com, rpaquay@google.com
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index e784c26..352c9d2 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -63,6 +63,7 @@
 import java.lang.reflect.Field;
 import java.lang.reflect.Modifier;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -362,6 +363,22 @@
     // see libbinder's binder/Status.h
     private static final int EX_TRANSACTION_FAILED = -129;
 
+    // Allow limit of 1 MB for allocating arrays
+    private static final int ARRAY_ALLOCATION_LIMIT = 1000000;
+
+    // Following type size are used to determine allocation size while creating arrays
+    private static final int SIZE_BYTE = 1;
+    private static final int SIZE_CHAR = 2;
+    private static final int SIZE_SHORT = 2;
+    private static final int SIZE_BOOLEAN = 4;
+    private static final int SIZE_INT = 4;
+    private static final int SIZE_FLOAT = 4;
+    private static final int SIZE_DOUBLE = 8;
+    private static final int SIZE_LONG = 8;
+
+    // Assume the least possible size for complex objects
+    private static final int SIZE_COMPLEX_TYPE = 1;
+
     @CriticalNative
     private static native void nativeMarkSensitive(long nativePtr);
     @FastNative
@@ -1502,9 +1519,71 @@
         }
     }
 
+    private static <T> int getItemTypeSize(@NonNull Class<T> arrayClass) {
+        final Class<?> componentType = arrayClass.getComponentType();
+        // typeSize has been referred from respective create*Array functions
+        if (componentType == boolean.class) {
+            return SIZE_BOOLEAN;
+        } else if (componentType == byte.class) {
+            return SIZE_BYTE;
+        } else if (componentType == char.class) {
+            return SIZE_CHAR;
+        } else if (componentType == int.class) {
+            return SIZE_INT;
+        } else if (componentType == long.class) {
+            return SIZE_LONG;
+        } else if (componentType == float.class) {
+            return SIZE_FLOAT;
+        } else if (componentType == double.class) {
+            return SIZE_DOUBLE;
+        }
+
+        return SIZE_COMPLEX_TYPE;
+    }
+
+    private void ensureWithinMemoryLimit(int typeSize, @NonNull int... dimensions) {
+        // For Multidimensional arrays, Calculate total object
+        // which will be allocated.
+        int totalObjects = 1;
+        try {
+            for (int dimension : dimensions) {
+                totalObjects = Math.multiplyExact(totalObjects, dimension);
+            }
+        } catch (ArithmeticException e) {
+            Log.e(TAG, "ArithmeticException occurred while multiplying dimensions " + e);
+            BadParcelableException badParcelableException = new BadParcelableException("Estimated "
+                    + "array length is too large. Array Dimensions:" + Arrays.toString(dimensions));
+            SneakyThrow.sneakyThrow(badParcelableException);
+        }
+        ensureWithinMemoryLimit(typeSize, totalObjects);
+    }
+
+    private void ensureWithinMemoryLimit(int typeSize, @NonNull int length) {
+        int estimatedAllocationSize = 0;
+        try {
+            estimatedAllocationSize = Math.multiplyExact(typeSize, length);
+        } catch (ArithmeticException e) {
+            Log.e(TAG, "ArithmeticException occurred while multiplying values " + typeSize
+                    + " and "  + length + " Exception: " + e);
+            BadParcelableException badParcelableException = new BadParcelableException("Estimated "
+                    + "allocation size is too large. typeSize: " + typeSize + " length: " + length);
+            SneakyThrow.sneakyThrow(badParcelableException);
+        }
+
+        boolean isInBinderTransaction = Binder.isDirectlyHandlingTransaction();
+        if (isInBinderTransaction && (estimatedAllocationSize > ARRAY_ALLOCATION_LIMIT)) {
+            Log.e(TAG, "Trying to Allocate " + estimatedAllocationSize
+                    + " memory, In Binder Transaction : " + isInBinderTransaction);
+            BadParcelableException e = new BadParcelableException("Allocation of size "
+                    + estimatedAllocationSize + " is above allowed limit of 1MB");
+            SneakyThrow.sneakyThrow(e);
+        }
+    }
+
     @Nullable
     public final boolean[] createBooleanArray() {
         int N = readInt();
+        ensureWithinMemoryLimit(SIZE_BOOLEAN, N);
         // >>2 as a fast divide-by-4 works in the create*Array() functions
         // because dataAvail() will never return a negative number.  4 is
         // the size of a stored boolean in the stream.
@@ -1547,6 +1626,7 @@
     @Nullable
     public short[] createShortArray() {
         int n = readInt();
+        ensureWithinMemoryLimit(SIZE_SHORT, n);
         if (n >= 0 && n <= (dataAvail() >> 2)) {
             short[] val = new short[n];
             for (int i = 0; i < n; i++) {
@@ -1585,6 +1665,7 @@
     @Nullable
     public final char[] createCharArray() {
         int N = readInt();
+        ensureWithinMemoryLimit(SIZE_CHAR, N);
         if (N >= 0 && N <= (dataAvail() >> 2)) {
             char[] val = new char[N];
             for (int i=0; i<N; i++) {
@@ -1622,6 +1703,7 @@
     @Nullable
     public final int[] createIntArray() {
         int N = readInt();
+        ensureWithinMemoryLimit(SIZE_INT, N);
         if (N >= 0 && N <= (dataAvail() >> 2)) {
             int[] val = new int[N];
             for (int i=0; i<N; i++) {
@@ -1659,6 +1741,7 @@
     @Nullable
     public final long[] createLongArray() {
         int N = readInt();
+        ensureWithinMemoryLimit(SIZE_LONG, N);
         // >>3 because stored longs are 64 bits
         if (N >= 0 && N <= (dataAvail() >> 3)) {
             long[] val = new long[N];
@@ -1697,6 +1780,7 @@
     @Nullable
     public final float[] createFloatArray() {
         int N = readInt();
+        ensureWithinMemoryLimit(SIZE_FLOAT, N);
         // >>2 because stored floats are 4 bytes
         if (N >= 0 && N <= (dataAvail() >> 2)) {
             float[] val = new float[N];
@@ -1735,6 +1819,7 @@
     @Nullable
     public final double[] createDoubleArray() {
         int N = readInt();
+        ensureWithinMemoryLimit(SIZE_DOUBLE, N);
         // >>3 because stored doubles are 8 bytes
         if (N >= 0 && N <= (dataAvail() >> 3)) {
             double[] val = new double[N];
@@ -1788,6 +1873,7 @@
     @Nullable
     public final String[] createString8Array() {
         int N = readInt();
+        ensureWithinMemoryLimit(SIZE_COMPLEX_TYPE, N);
         if (N >= 0) {
             String[] val = new String[N];
             for (int i=0; i<N; i++) {
@@ -1828,6 +1914,7 @@
     @Nullable
     public final String[] createString16Array() {
         int N = readInt();
+        ensureWithinMemoryLimit(SIZE_COMPLEX_TYPE, N);
         if (N >= 0) {
             String[] val = new String[N];
             for (int i=0; i<N; i++) {
@@ -1920,6 +2007,7 @@
     @Nullable
     public final IBinder[] createBinderArray() {
         int N = readInt();
+        ensureWithinMemoryLimit(SIZE_COMPLEX_TYPE, N);
         if (N >= 0) {
             IBinder[] val = new IBinder[N];
             for (int i=0; i<N; i++) {
@@ -1954,6 +2042,7 @@
     public final <T extends IInterface> T[] createInterfaceArray(
             @NonNull IntFunction<T[]> newArray, @NonNull Function<IBinder, T> asInterface) {
         int N = readInt();
+        ensureWithinMemoryLimit(SIZE_COMPLEX_TYPE, N);
         if (N >= 0) {
             T[] val = newArray.apply(N);
             for (int i=0; i<N; i++) {
@@ -3200,6 +3289,7 @@
         if (N < 0) {
             return null;
         }
+        ensureWithinMemoryLimit(SIZE_COMPLEX_TYPE, N);
         FileDescriptor[] f = new FileDescriptor[N];
         for (int i = 0; i < N; i++) {
             f[i] = readRawFileDescriptor();
@@ -3666,6 +3756,7 @@
         if (N < 0) {
             return null;
         }
+        ensureWithinMemoryLimit(SIZE_COMPLEX_TYPE, N);
         ArrayList<T> l = new ArrayList<T>(N);
         while (N > 0) {
             l.add(readTypedObject(c));
@@ -3717,6 +3808,7 @@
         if (count < 0) {
             return null;
         }
+        ensureWithinMemoryLimit(SIZE_COMPLEX_TYPE, count);
         final SparseArray<T> array = new SparseArray<>(count);
         for (int i = 0; i < count; i++) {
             final int index = readInt();
@@ -3745,6 +3837,7 @@
         if (count < 0) {
             return null;
         }
+        ensureWithinMemoryLimit(SIZE_COMPLEX_TYPE, count);
         final ArrayMap<String, T> map = new ArrayMap<>(count);
         for (int i = 0; i < count; i++) {
             final String key = readString();
@@ -3771,6 +3864,7 @@
         if (N < 0) {
             return null;
         }
+        ensureWithinMemoryLimit(SIZE_COMPLEX_TYPE, N);
         ArrayList<String> l = new ArrayList<String>(N);
         while (N > 0) {
             l.add(readString());
@@ -3796,6 +3890,7 @@
         if (N < 0) {
             return null;
         }
+        ensureWithinMemoryLimit(SIZE_COMPLEX_TYPE, N);
         ArrayList<IBinder> l = new ArrayList<IBinder>(N);
         while (N > 0) {
             l.add(readStrongBinder());
@@ -3822,6 +3917,7 @@
         if (N < 0) {
             return null;
         }
+        ensureWithinMemoryLimit(SIZE_COMPLEX_TYPE, N);
         ArrayList<T> l = new ArrayList<T>(N);
         while (N > 0) {
             l.add(asInterface.apply(readStrongBinder()));
@@ -3981,6 +4077,7 @@
         if (N < 0) {
             return null;
         }
+        ensureWithinMemoryLimit(SIZE_COMPLEX_TYPE, N);
         T[] l = c.newArray(N);
         for (int i=0; i<N; i++) {
             l[i] = readTypedObject(c);
@@ -4209,6 +4306,10 @@
             while (innermost.isArray()) {
                 innermost = innermost.getComponentType();
             }
+
+            int typeSize = getItemTypeSize(innermost);
+            ensureWithinMemoryLimit(typeSize, dimensions);
+
             val = (T) Array.newInstance(innermost, dimensions);
             for (int i = 0; i < length; i++) {
                 readFixedArray(Array.get(val, i));
@@ -4265,6 +4366,10 @@
             while (innermost.isArray()) {
                 innermost = innermost.getComponentType();
             }
+
+            int typeSize = getItemTypeSize(innermost);
+            ensureWithinMemoryLimit(typeSize, dimensions);
+
             val = (T) Array.newInstance(innermost, dimensions);
             for (int i = 0; i < length; i++) {
                 readFixedArray(Array.get(val, i), asInterface);
@@ -4320,6 +4425,10 @@
             while (innermost.isArray()) {
                 innermost = innermost.getComponentType();
             }
+
+            int typeSize = getItemTypeSize(innermost);
+            ensureWithinMemoryLimit(typeSize, dimensions);
+
             val = (T) Array.newInstance(innermost, dimensions);
             for (int i = 0; i < length; i++) {
                 readFixedArray(Array.get(val, i), c);
@@ -5076,6 +5185,7 @@
         if (n < 0) {
             return null;
         }
+        ensureWithinMemoryLimit(SIZE_COMPLEX_TYPE, n);
         T[] p = (T[]) ((clazz == null) ? new Parcelable[n] : Array.newInstance(clazz, n));
         for (int i = 0; i < n; i++) {
             p[i] = readParcelableInternal(loader, clazz);
diff --git a/core/java/android/os/PermissionEnforcer.java b/core/java/android/os/PermissionEnforcer.java
index 310ceb3..91d2269 100644
--- a/core/java/android/os/PermissionEnforcer.java
+++ b/core/java/android/os/PermissionEnforcer.java
@@ -34,6 +34,38 @@
  * PermissionEnforcer to validate the permissions. The methods available are
  * purposely similar to the AIDL annotation syntax.
  *
+ * <p>The constructor of the Stub generated by AIDL expects a
+ * PermissionEnforcer. It can be based on the current Context. For example:
+ *
+ * <pre>{@code
+ * class MyFoo extends Foo.Stub {
+ *     MyFoo(Context context) {
+ *         super(PermissionEnforcer.fromContext(context));
+ *     }
+ *
+ *     @Override
+ *     @EnforcePermission(android.Manifest.permission.INTERNET)
+ *     public MyMethod() {
+ *         MyMethod_enforcePermission();
+ *     }
+ * }
+ * }</pre>
+ *
+ * <p>A {@link android.os.test.FakePermissionEnforcer} is available for unit
+ * testing. It can be attached to a mocked Context using:
+ * <pre>{@code
+ * @Mock private Context mContext;
+ *
+ * @Before
+ * public setUp() {
+ *   fakeEnforcer = new FakePermissionEnforcer();
+ *   fakeEnforcer.grant(android.Manifest.permission.INTERNET);
+ *
+ *   doReturn(fakeEnforcer).when(mContext).getSystemService(
+                eq(Context.PERMISSION_ENFORCER_SERVICE));
+ * }
+ * }</pre>
+ *
  * @see android.permission.PermissionManager
  *
  * @hide
@@ -163,6 +195,6 @@
      * @hide
      */
     public static PermissionEnforcer fromContext(@NonNull Context context) {
-        return context.getSystemService(PermissionEnforcer.class);
+        return (PermissionEnforcer) context.getSystemService(Context.PERMISSION_ENFORCER_SERVICE);
     }
 }
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index d1c10fa..c6b9d20 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -3577,25 +3577,6 @@
     }
 
     /**
-     * Constant for PreIdleTimeout normal mode (default mode, not short nor extend timeout) .
-     * @hide
-     */
-    public static final int PRE_IDLE_TIMEOUT_MODE_NORMAL = 0;
-
-    /**
-     * Constant for PreIdleTimeout long mode (extend timeout to keep in inactive mode
-     * longer).
-     * @hide
-     */
-    public static final int PRE_IDLE_TIMEOUT_MODE_LONG = 1;
-
-    /**
-     * Constant for PreIdleTimeout short mode (short timeout to go to doze mode quickly)
-     * @hide
-     */
-    public static final int PRE_IDLE_TIMEOUT_MODE_SHORT = 2;
-
-    /**
      * A listener interface to get notified when the wakelock is enabled/disabled.
      */
     public interface WakeLockStateListener {
diff --git a/core/java/android/os/PowerMonitor.aidl b/core/java/android/os/PowerMonitor.aidl
new file mode 100644
index 0000000..3f8943f
--- /dev/null
+++ b/core/java/android/os/PowerMonitor.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2023, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+/** @hide */
+@JavaOnlyStableParcelable parcelable PowerMonitor;
diff --git a/core/java/android/os/PowerMonitor.java b/core/java/android/os/PowerMonitor.java
new file mode 100644
index 0000000..ebdd463
--- /dev/null
+++ b/core/java/android/os/PowerMonitor.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.IntDef;
+import android.annotation.NonNull;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * @hide
+ */
+public final class PowerMonitor implements Parcelable {
+
+    /**
+     * Power monitor corresponding to a subsystem. The energy value may be a direct pass-through
+     * power rail measurement, or modeled in some fashion.  For example, an energy consumer may
+     * represent a combination of multiple rails or a portion of a rail shared between subsystems,
+     * e.g. WiFi and Bluetooth are often handled by the same chip, powered by a shared rail.
+     * Some consumer names are standardized (see android.hardware.power.stats.EnergyConsumerType),
+     * others are not.
+     */
+    public static final int POWER_MONITOR_TYPE_CONSUMER = 0;
+
+    /**
+     * Power monitor corresponding to a directly measured power rail. Rails are device-specific:
+     * no assumptions can be made about the source of those measurements across different devices,
+     * even if they have the same name.
+     */
+    public static final int POWER_MONITOR_TYPE_MEASUREMENT = 1;
+
+    /** @hide */
+    @IntDef(flag = true, prefix = {"POWER_MONITOR_TYPE_"}, value = {
+            POWER_MONITOR_TYPE_CONSUMER,
+            POWER_MONITOR_TYPE_MEASUREMENT
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface PowerMonitorType {
+    }
+
+    /**
+     * These indices are not guaranteed to be stable across reboots and should not
+     * be persisted.
+     *
+     * @hide
+     */
+    public final int index;
+    @PowerMonitorType
+    public final int type;
+    @NonNull
+    public final String name;
+
+    /**
+     * @hide
+     */
+    public PowerMonitor(int index, int type, @NonNull String name) {
+        this.index = index;
+        this.type = type;
+        this.name = name;
+    }
+
+    private PowerMonitor(Parcel in) {
+        index = in.readInt();
+        type = in.readInt();
+        name = in.readString();
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(index);
+        dest.writeInt(type);
+        dest.writeString(name);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final Creator<PowerMonitor> CREATOR = new Creator<>() {
+        @Override
+        public PowerMonitor createFromParcel(@NonNull Parcel in) {
+            return new PowerMonitor(in);
+        }
+
+        @Override
+        public PowerMonitor[] newArray(int size) {
+            return new PowerMonitor[size];
+        }
+    };
+}
diff --git a/core/java/android/os/PowerMonitorReadings.java b/core/java/android/os/PowerMonitorReadings.java
new file mode 100644
index 0000000..3d7f859
--- /dev/null
+++ b/core/java/android/os/PowerMonitorReadings.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 android.os;
+
+import android.annotation.NonNull;
+
+import java.util.Arrays;
+import java.util.Comparator;
+
+/**
+ * A collection of energy measurements from Power Monitors.
+ *
+ * @hide
+ */
+public final class PowerMonitorReadings {
+    public static final int ENERGY_UNAVAILABLE = -1;
+
+    @NonNull
+    private final PowerMonitor[] mPowerMonitors;
+    @NonNull
+    private final long[] mEnergyUws;
+    @NonNull
+    private final long[] mTimestampsMs;
+
+    private static final Comparator<PowerMonitor> POWER_MONITOR_COMPARATOR =
+            Comparator.comparingInt(pm -> pm.index);
+
+    /**
+     * @param powerMonitors array of power monitor (ODPM) rails, sorted by PowerMonitor.index
+     * @hide
+     */
+    public PowerMonitorReadings(PowerMonitor[] powerMonitors,
+            long[] energyUws, long[] timestampsMs) {
+        mPowerMonitors = powerMonitors;
+        mEnergyUws = energyUws;
+        mTimestampsMs = timestampsMs;
+    }
+
+    /**
+     * Returns energy consumed by the specified power monitor since boot in microwatt-seconds.
+     * Does not persist across reboots.
+     * Represents total energy: both on-battery and plugged-in.
+     */
+    public long getConsumedEnergyUws(PowerMonitor powerMonitor) {
+        int offset = Arrays.binarySearch(mPowerMonitors, powerMonitor, POWER_MONITOR_COMPARATOR);
+        if (offset >= 0) {
+            return mEnergyUws[offset];
+        }
+        return ENERGY_UNAVAILABLE;
+    }
+
+    /**
+     * Elapsed realtime when the snapshot was taken.
+     */
+    public long getTimestampMs(PowerMonitor powerMonitor) {
+        int offset = Arrays.binarySearch(mPowerMonitors, powerMonitor, POWER_MONITOR_COMPARATOR);
+        if (offset >= 0) {
+            return mTimestampsMs[offset];
+        }
+        return 0;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(" monitors: [");
+        for (int i = 0; i < mPowerMonitors.length; i++) {
+            if (i != 0) {
+                sb.append(", ");
+            }
+            sb.append(mPowerMonitors[i].name)
+                    .append(" = ").append(mEnergyUws[i])
+                    .append(" (").append(mTimestampsMs[i]).append(')');
+        }
+        sb.append("]");
+        return sb.toString();
+    }
+}
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index 04525e8..e37b2b5 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -1118,19 +1118,6 @@
     public static final native void setProcessFrozen(int pid, int uid, boolean frozen);
 
     /**
-     * Enable or disable the freezer. When enable == false all frozen processes are unfrozen,
-     * but aren't removed from the freezer. While in this state, processes can be added or removed
-     * by using setProcessFrozen, but they won't actually be frozen until the freezer is enabled
-     * again. If enable == true the freezer is enabled again, and all processes
-     * in the freezer (including the ones added while the freezer was disabled) are frozen.
-     *
-     * @param enable Specify whether to enable (true) or disable (false) the freezer.
-     *
-     * @hide
-     */
-    public static final native void enableFreezer(boolean enable);
-
-    /**
      * Return the scheduling group of requested process.
      *
      * @hide
diff --git a/core/java/android/os/RemoteCallbackList.java b/core/java/android/os/RemoteCallbackList.java
index d89c3d5..2c58021 100644
--- a/core/java/android/os/RemoteCallbackList.java
+++ b/core/java/android/os/RemoteCallbackList.java
@@ -273,7 +273,7 @@
      * handle such an exception by simply ignoring it.
      *
      * @param index Which of the registered callbacks you would like to
-     * retrieve.  Ranges from 0 to 1-{@link #beginBroadcast}.
+     * retrieve.  Ranges from 0 to {@link #beginBroadcast}-1, inclusive.
      *
      * @return Returns the callback interface that you can call.  This will
      * always be non-null.
diff --git a/core/java/android/os/TEST_MAPPING b/core/java/android/os/TEST_MAPPING
index cc54266..5c4aa4a 100644
--- a/core/java/android/os/TEST_MAPPING
+++ b/core/java/android/os/TEST_MAPPING
@@ -52,8 +52,20 @@
       ],
       "name": "FrameworksServicesTests",
       "options": [
-        { "include-filter": "com.android.server.am.BatteryStatsServiceTest" },
-        { "include-filter": "com.android.server.power.stats.BatteryStatsTests" }
+        { "include-filter": "com.android.server.am.BatteryStatsServiceTest" }
+      ]
+    },
+    {
+      "file_patterns": [
+        "BatteryStats[^/]*\\.java",
+        "BatteryUsageStats[^/]*\\.java",
+        "PowerComponents\\.java",
+        "[^/]*BatteryConsumer[^/]*\\.java"
+      ],
+      "name": "FrameworksServicesTests",
+      "options": [
+        { "include-filter": "com.android.server.power.stats" },
+        { "exclude-filter": "com.android.server.power.stats.BatteryStatsTests" }
       ]
     },
     {
diff --git a/core/java/android/os/UserHandle.java b/core/java/android/os/UserHandle.java
index ef39010..0527ed6 100644
--- a/core/java/android/os/UserHandle.java
+++ b/core/java/android/os/UserHandle.java
@@ -606,12 +606,9 @@
 
     @Override
     public boolean equals(@Nullable Object obj) {
-        try {
-            if (obj != null) {
-                UserHandle other = (UserHandle)obj;
-                return mHandle == other.mHandle;
-            }
-        } catch (ClassCastException ignore) {
+        if (obj instanceof UserHandle) {
+            UserHandle other = (UserHandle) obj;
+            return mHandle == other.mHandle;
         }
         return false;
     }
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 84f1880..ba1f979 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -159,6 +159,13 @@
     @SystemApi
     public static final String USER_TYPE_PROFILE_CLONE = "android.os.usertype.profile.CLONE";
 
+
+    /**
+     * User type representing a private profile.
+     * @hide
+     */
+    public static final String USER_TYPE_PROFILE_PRIVATE = "android.os.usertype.profile.PRIVATE";
+
     /**
      * User type representing a generic profile for testing purposes. Only on debuggable builds.
      * @hide
@@ -166,6 +173,12 @@
     public static final String USER_TYPE_PROFILE_TEST = "android.os.usertype.profile.TEST";
 
     /**
+     * User type representing a communal profile, which is shared by all users of the device.
+     * @hide
+     */
+    public static final String USER_TYPE_PROFILE_COMMUNAL = "android.os.usertype.profile.COMMUNAL";
+
+    /**
      * User type representing a {@link UserHandle#USER_SYSTEM system} user that is <b>not</b> a
      * human user.
      * This type of user cannot be created; it can only pre-exist on first boot.
@@ -1764,10 +1777,19 @@
     /**
      * Specifies if a user is not allowed to use 2g networks.
      *
+     * <p> This is a security feature. 2g has no mutual authentication between a device and
+     * cellular base station and downgrading a device's connection to 2g is a common tactic for
+     * several types of privacy and security compromising attacks that could allow an adversary
+     * to intercept, inject, or modify cellular communications.
+     *
      * <p>This restriction can only be set by a device owner or a profile owner of an
      * organization-owned managed profile on the parent profile.
-     * In all cases, the setting applies globally on the device and will prevent the device from
-     * scanning for or connecting to 2g networks, except in the case of an emergency.
+     * In all cases, the setting applies globally on the device.
+     *
+     * <p> Cellular connectivity loss (where a device would have otherwise successfully
+     * connected to a 2g network) occurs if the device is in an area where only 2g networks are
+     * available. Emergency calls are an exception and are never impacted. The device will still
+     * scan for and connect to a 2g network for emergency calls.
      *
      * <p>Holders of the permission
      * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_MOBILE_NETWORK}
@@ -2069,6 +2091,13 @@
     public static final int REMOVE_RESULT_ALREADY_BEING_REMOVED = 2;
 
     /**
+     * A response code indicating that the specified user is removable.
+     *
+     * @hide
+     */
+    public static final int REMOVE_RESULT_USER_IS_REMOVABLE = 3;
+
+    /**
      * A response code from {@link #removeUserWhenPossible(UserHandle, boolean)} indicating that
      * an unknown error occurred that prevented the user from being removed or set as ephemeral.
      *
@@ -2123,6 +2152,7 @@
             REMOVE_RESULT_REMOVED,
             REMOVE_RESULT_DEFERRED,
             REMOVE_RESULT_ALREADY_BEING_REMOVED,
+            REMOVE_RESULT_USER_IS_REMOVABLE,
             REMOVE_RESULT_ERROR_USER_RESTRICTION,
             REMOVE_RESULT_ERROR_USER_NOT_FOUND,
             REMOVE_RESULT_ERROR_SYSTEM_USER,
@@ -2376,6 +2406,16 @@
     }
 
     /**
+     * Returns whether the device is configured to support a Communal Profile.
+     * @hide
+     */
+    public static boolean isCommunalProfileEnabled() {
+        return SystemProperties.getBoolean("persist.fw.omnipresent_communal_user",
+                Resources.getSystem()
+                        .getBoolean(com.android.internal.R.bool.config_omnipresentCommunalUser));
+    }
+
+    /**
      * Returns whether multiple admins are enabled on the device
      * @hide
      */
@@ -2682,6 +2722,38 @@
             throw re.rethrowFromSystemServer();
         }
     }
+    /**
+     * Returns the designated "communal profile" of the device, or {@code null} if there is none.
+     * @hide
+     */
+    @RequiresPermission(anyOf = {
+            Manifest.permission.MANAGE_USERS,
+            Manifest.permission.CREATE_USERS,
+            Manifest.permission.QUERY_USERS})
+    public @Nullable UserHandle getCommunalProfile() {
+        try {
+            final int userId = mService.getCommunalProfileId();
+            if (userId == UserHandle.USER_NULL) {
+                return null;
+            }
+            return UserHandle.of(userId);
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns {@code true} if the given user is the designated "communal profile" of the device.
+     * @hide
+     */
+    @RequiresPermission(anyOf = {
+            Manifest.permission.MANAGE_USERS,
+            Manifest.permission.CREATE_USERS,
+            Manifest.permission.QUERY_USERS})
+    public boolean isCommunalProfile(@UserIdInt int userId) {
+        final UserInfo user = getUserInfo(userId);
+        return user != null && user.isCommunalProfile();
+    }
 
     /**
      * Used to check if the context user is an admin user. An admin user may be allowed to
@@ -2784,6 +2856,25 @@
     }
 
     /**
+     * Returns whether the user type is a
+     * {@link UserManager#USER_TYPE_PROFILE_COMMUNAL communal profile}.
+     * @hide
+     */
+    public static boolean isUserTypeCommunalProfile(@Nullable String userType) {
+        return USER_TYPE_PROFILE_COMMUNAL.equals(userType);
+    }
+
+    /**
+     * Returns whether the user type is a
+     * {@link UserManager#USER_TYPE_PROFILE_PRIVATE private profile}.
+     *
+     * @hide
+     */
+    public static boolean isUserTypePrivateProfile(@Nullable String userType) {
+        return USER_TYPE_PROFILE_PRIVATE.equals(userType);
+    }
+
+    /**
      * @hide
      * @deprecated Use {@link #isRestrictedProfile()}
      */
@@ -2793,7 +2884,8 @@
             enabledSinceTargetSdkVersion = Build.VERSION_CODES.TIRAMISU,
             requiresAnyOfPermissionsIfNotCaller = {
                     android.Manifest.permission.MANAGE_USERS,
-                    android.Manifest.permission.CREATE_USERS}
+                    android.Manifest.permission.CREATE_USERS,
+                    android.Manifest.permission.QUERY_USERS}
     )
     public boolean isLinkedUser() {
         return isRestrictedProfile();
@@ -2811,7 +2903,8 @@
             enabledSinceTargetSdkVersion = Build.VERSION_CODES.TIRAMISU,
             requiresAnyOfPermissionsIfNotCaller = {
                     android.Manifest.permission.MANAGE_USERS,
-                    android.Manifest.permission.CREATE_USERS}
+                    android.Manifest.permission.CREATE_USERS,
+                    android.Manifest.permission.QUERY_USERS}
     )
     public boolean isRestrictedProfile() {
         try {
@@ -2832,7 +2925,8 @@
     @SystemApi
     @RequiresPermission(anyOf = {
             Manifest.permission.MANAGE_USERS,
-            Manifest.permission.CREATE_USERS},
+            Manifest.permission.CREATE_USERS,
+            Manifest.permission.QUERY_USERS},
             conditional = true)
     public boolean isRestrictedProfile(@NonNull UserHandle user) {
         try {
@@ -3069,6 +3163,24 @@
     }
 
     /**
+     * Checks if the context user is a private profile.
+     *
+     * @return whether the context user is a private profile.
+     *
+     * @see android.os.UserManager#USER_TYPE_PROFILE_PRIVATE
+     * @hide
+     */
+    @UserHandleAware(
+            requiresAnyOfPermissionsIfNotCallerProfileGroup = {
+                    android.Manifest.permission.MANAGE_USERS,
+                    android.Manifest.permission.QUERY_USERS,
+                    android.Manifest.permission.INTERACT_ACROSS_USERS})
+    @SuppressAutoDoc
+    public boolean isPrivateProfile() {
+        return isUserTypePrivateProfile(getProfileType());
+    }
+
+    /**
      * Checks if the context user is an ephemeral user.
      *
      * @return whether the context user is an ephemeral user.
@@ -3227,6 +3339,7 @@
      *   <li>(Running) profiles of the current foreground user.
      *   <li>Background users assigned to secondary displays (for example, passenger users on
      *   automotive builds, using the display associated with their seats).
+     *   <li>A communal profile, if present.
      * </ol>
      *
      * @return whether the user is visible at the moment, as defined above.
@@ -5340,6 +5453,21 @@
     }
 
     /**
+     * Returns the Resource ID of the user's status bar icon.
+     *
+     * @return the Resource ID of the user's status bar icon if it has one; otherwise
+     *         {@link Resources#ID_NULL}.
+     * @hide
+     */
+    public @DrawableRes int getUserStatusBarIconResId(@UserIdInt int userId) {
+        try {
+            return mService.getUserStatusBarIconResId(userId);
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * If the target user is a profile of the calling user or the caller
      * is itself a profile, then this returns a badged copy of the given
      * icon to be able to distinguish it from the original icon. For badging an
diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java
index 4366c28..0461b2e 100644
--- a/core/java/android/os/VibrationEffect.java
+++ b/core/java/android/os/VibrationEffect.java
@@ -28,12 +28,12 @@
 import android.hardware.vibrator.V1_0.EffectStrength;
 import android.hardware.vibrator.V1_3.Effect;
 import android.net.Uri;
-import android.os.Vibrator;
 import android.os.vibrator.PrebakedSegment;
 import android.os.vibrator.PrimitiveSegment;
 import android.os.vibrator.RampSegment;
 import android.os.vibrator.StepSegment;
 import android.os.vibrator.VibrationEffectSegment;
+import android.util.Log;
 import android.util.MathUtils;
 
 import com.android.internal.util.Preconditions;
@@ -52,6 +52,7 @@
  * <p>These effects may be any number of things, from single shot vibrations to complex waveforms.
  */
 public abstract class VibrationEffect implements Parcelable {
+    private static final String TAG = "VibrationEffect";
     // Stevens' coefficient to scale the perceived vibration intensity.
     private static final float SCALE_GAMMA = 0.65f;
     // If a vibration is playing for longer than 1s, it's probably not haptic feedback
@@ -334,7 +335,7 @@
      */
     @TestApi
     public static VibrationEffect get(int effectId) {
-        return get(effectId, true);
+        return get(effectId, PrebakedSegment.DEFAULT_SHOULD_FALLBACK);
     }
 
     /**
@@ -353,7 +354,7 @@
      *
      * @param effectId The ID of the effect to perform:
      *                 {@link #EFFECT_CLICK}, {@link #EFFECT_DOUBLE_CLICK}, {@link #EFFECT_TICK}
-     * @param fallback Whether to fallback to a generic pattern if a hardware specific
+     * @param fallback Whether to fall back to a generic pattern if a hardware specific
      *                 implementation doesn't exist.
      *
      * @return The desired effect.
@@ -362,7 +363,7 @@
     @TestApi
     public static VibrationEffect get(int effectId, boolean fallback) {
         VibrationEffect effect = new Composed(
-                new PrebakedSegment(effectId, fallback, EffectStrength.MEDIUM));
+                new PrebakedSegment(effectId, fallback, PrebakedSegment.DEFAULT_STRENGTH));
         effect.validate();
         return effect;
     }
@@ -394,26 +395,32 @@
             return null;
         }
 
-        final ContentResolver cr = context.getContentResolver();
-        Uri uncanonicalUri = cr.uncanonicalize(uri);
-        if (uncanonicalUri == null) {
-            // If we already had an uncanonical URI, it's possible we'll get null back here. In
-            // this case, just use the URI as passed in since it wasn't canonicalized in the first
-            // place.
-            uncanonicalUri = uri;
-        }
+        try {
+            final ContentResolver cr = context.getContentResolver();
+            Uri uncanonicalUri = cr.uncanonicalize(uri);
+            if (uncanonicalUri == null) {
+                // If we already had an uncanonical URI, it's possible we'll get null back here. In
+                // this case, just use the URI as passed in since it wasn't canonicalized in the
+                // first place.
+                uncanonicalUri = uri;
+            }
 
-        for (int i = 0; i < uris.length && i < RINGTONES.length; i++) {
-            if (uris[i] == null) {
-                continue;
+            for (int i = 0; i < uris.length && i < RINGTONES.length; i++) {
+                if (uris[i] == null) {
+                    continue;
+                }
+                Uri mappedUri = cr.uncanonicalize(Uri.parse(uris[i]));
+                if (mappedUri == null) {
+                    continue;
+                }
+                if (mappedUri.equals(uncanonicalUri)) {
+                    return get(RINGTONES[i]);
+                }
             }
-            Uri mappedUri = cr.uncanonicalize(Uri.parse(uris[i]));
-            if (mappedUri == null) {
-                continue;
-            }
-            if (mappedUri.equals(uncanonicalUri)) {
-                return get(RINGTONES[i]);
-            }
+        } catch (Exception e) {
+            // Don't give unexpected exceptions to callers if the Uri's ContentProvider is
+            // misbehaving - it's very unlikely to be mapped in that case anyway.
+            Log.e(TAG, "Exception getting default vibration for Uri " + uri, e);
         }
         return null;
     }
@@ -564,18 +571,28 @@
     public abstract <T extends VibrationEffect> T scale(float scaleFactor);
 
     /**
-     * Applies given effect strength to prebaked effects represented by one of
-     * VibrationEffect.EFFECT_*.
+     * Ensures that the effect is repeating indefinitely or not. This is a lossy operation and
+     * should only be applied once to an original effect - it shouldn't be applied to the
+     * result of this method.
      *
-     * @param effectStrength new effect strength to be applied, one of
-     *                       VibrationEffect.EFFECT_STRENGTH_*.
-     * @return this if there is no change to this effect, or a copy of this effect with applied
-     * effect strength otherwise.
+     * <p>Non-repeating effects will be made repeating by looping the entire effect with the
+     * specified delay between each loop. The delay is added irrespective of whether the effect
+     * already has a delay at the beginning or end.
+     *
+     * <p>Repeating effects will be left with their native repeating portion if it should be
+     * repeating, and otherwise the loop index is removed, so that the entire effect plays once.
+     *
+     * @param wantRepeating Whether the effect is required to be repeating or not.
+     * @param loopDelayMs The milliseconds to pause between loops, if repeating is to be added to
+     *                    the effect. Ignored if {@code repeating==false} or the effect is already
+     *                    repeating itself. No delay is added if <= 0.
+     * @return this if the effect already satisfies the repeating requirement, or a copy of this
+     *         adjusted to repeat or not repeat as appropriate.
      * @hide
      */
-    public <T extends VibrationEffect> T applyEffectStrength(int effectStrength) {
-        return (T) this;
-    }
+    @NonNull
+    public abstract VibrationEffect applyRepeatingIndefinitely(
+            boolean wantRepeating, int loopDelayMs);
 
     /**
      * Scale given vibration intensity by the given factor.
@@ -649,6 +666,22 @@
     }
 
     /**
+     * Transforms a {@link VibrationEffect} using a generic parameter.
+     *
+     * <p>This can be used for scaling effects based on user settings or adapting them to the
+     * capabilities of a specific device vibrator.
+     *
+     * @param <ParamT> The type of parameter to be used on the effect by this transformation
+     * @hide
+     */
+    public interface Transformation<ParamT> {
+
+        /** Transforms given effect by applying the given parameter. */
+        @NonNull
+        VibrationEffect transform(@NonNull VibrationEffect effect, @NonNull ParamT param);
+    }
+
+    /**
      * Implementation of {@link VibrationEffect} described by a composition of one or more
      * {@link VibrationEffectSegment}, with an optional index to represent repeating effects.
      *
@@ -660,7 +693,9 @@
         private final int mRepeatIndex;
 
         Composed(@NonNull Parcel in) {
-            this(in.readArrayList(VibrationEffectSegment.class.getClassLoader(), android.os.vibrator.VibrationEffectSegment.class), in.readInt());
+            this(in.readArrayList(
+                    VibrationEffectSegment.class.getClassLoader(), VibrationEffectSegment.class),
+                    in.readInt());
         }
 
         Composed(@NonNull VibrationEffectSegment segment) {
@@ -845,22 +880,32 @@
         /** @hide */
         @NonNull
         @Override
-        public Composed applyEffectStrength(int effectStrength) {
-            int segmentCount = mSegments.size();
-            ArrayList<VibrationEffectSegment> scaledSegments = new ArrayList<>(segmentCount);
-            for (int i = 0; i < segmentCount; i++) {
-                scaledSegments.add(mSegments.get(i).applyEffectStrength(effectStrength));
-            }
-            if (scaledSegments.equals(mSegments)) {
+        public Composed applyRepeatingIndefinitely(boolean wantRepeating, int loopDelayMs) {
+            boolean isRepeating = mRepeatIndex >= 0;
+            if (isRepeating == wantRepeating) {
                 return this;
+            } else if (!wantRepeating) {
+                return new Composed(mSegments, -1);
+            } else if (loopDelayMs <= 0) {
+                // Loop with no delay: repeat at index zero.
+                return new Composed(mSegments, 0);
+            } else {
+                // Append a delay and loop. It doesn't matter that there's a delay on the
+                // end because the looping is always indefinite until cancelled.
+                ArrayList<VibrationEffectSegment> loopingSegments =
+                        new ArrayList<>(mSegments.size() + 1);
+                loopingSegments.addAll(mSegments);
+                loopingSegments.add(
+                        new StepSegment(/* amplitude= */ 0, /* frequencyHz= */ 0, loopDelayMs));
+                return new Composed(loopingSegments, 0);
             }
-            Composed scaled = new Composed(scaledSegments, mRepeatIndex);
-            scaled.validate();
-            return scaled;
         }
 
         @Override
         public boolean equals(@Nullable Object o) {
+            if (this == o) {
+                return true;
+            }
             if (!(o instanceof Composed)) {
                 return false;
             }
@@ -1120,7 +1165,7 @@
          */
         @NonNull
         public Composition addPrimitive(@PrimitiveType int primitiveId) {
-            return addPrimitive(primitiveId, /*scale*/ 1.0f, /*delay*/ 0);
+            return addPrimitive(primitiveId, PrimitiveSegment.DEFAULT_SCALE);
         }
 
         /**
@@ -1135,7 +1180,7 @@
         @NonNull
         public Composition addPrimitive(@PrimitiveType int primitiveId,
                 @FloatRange(from = 0f, to = 1f) float scale) {
-            return addPrimitive(primitiveId, scale, /*delay*/ 0);
+            return addPrimitive(primitiveId, scale, PrimitiveSegment.DEFAULT_DELAY_MILLIS);
         }
 
         /**
@@ -1150,8 +1195,7 @@
         @NonNull
         public Composition addPrimitive(@PrimitiveType int primitiveId,
                 @FloatRange(from = 0f, to = 1f) float scale, @IntRange(from = 0) int delay) {
-            PrimitiveSegment primitive = new PrimitiveSegment(primitiveId, scale,
-                    delay);
+            PrimitiveSegment primitive = new PrimitiveSegment(primitiveId, scale, delay);
             primitive.validate();
             return addSegment(primitive);
         }
diff --git a/core/java/android/os/health/SystemHealthManager.java b/core/java/android/os/health/SystemHealthManager.java
index 8181911..ab30a8b 100644
--- a/core/java/android/os/health/SystemHealthManager.java
+++ b/core/java/android/os/health/SystemHealthManager.java
@@ -16,17 +16,29 @@
 
 package android.os.health;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemService;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.os.BatteryStats;
 import android.os.Build;
+import android.os.Bundle;
+import android.os.IPowerStatsService;
+import android.os.PowerMonitor;
+import android.os.PowerMonitorReadings;
 import android.os.Process;
 import android.os.RemoteException;
+import android.os.ResultReceiver;
 import android.os.ServiceManager;
 
 import com.android.internal.app.IBatteryStats;
 
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+
 /**
  * Provides access to data about how various system resources are used by applications.
  * @more
@@ -46,20 +58,29 @@
  */
 @SystemService(Context.SYSTEM_HEALTH_SERVICE)
 public class SystemHealthManager {
+    @NonNull
     private final IBatteryStats mBatteryStats;
+    @Nullable
+    private final IPowerStatsService mPowerStats;
+    private PowerMonitor[] mPowerMonitorsInfo;
 
     /**
      * Construct a new SystemHealthManager object.
+     *
      * @hide
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     public SystemHealthManager() {
-        this(IBatteryStats.Stub.asInterface(ServiceManager.getService(BatteryStats.SERVICE_NAME)));
+        this(IBatteryStats.Stub.asInterface(ServiceManager.getService(BatteryStats.SERVICE_NAME)),
+                IPowerStatsService.Stub.asInterface(
+                        ServiceManager.getService(Context.POWER_STATS_SERVICE)));
     }
 
     /** {@hide} */
-    public SystemHealthManager(IBatteryStats batteryStats) {
+    public SystemHealthManager(@NonNull IBatteryStats batteryStats,
+            @Nullable IPowerStatsService powerStats) {
         mBatteryStats = batteryStats;
+        mPowerStats = powerStats;
     }
 
     /**
@@ -69,22 +90,20 @@
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
     public static SystemHealthManager from(Context context) {
-        return (SystemHealthManager)context.getSystemService(Context.SYSTEM_HEALTH_SERVICE);
+        return (SystemHealthManager) context.getSystemService(Context.SYSTEM_HEALTH_SERVICE);
     }
 
     /**
      * Return a {@link HealthStats} object containing a snapshot of system health
      * metrics for the given uid (user-id, which in usually corresponds to application).
-     * @more
-     *
-     * An application must hold the {@link android.Manifest.permission#BATTERY_STATS
-     * android.permission.BATTERY_STATS} permission in order to retrieve any HealthStats
-     * other than its own.
      *
      * @param uid User ID for a given application.
      * @return A {@link HealthStats} object containing the metrics for the requested
      * application. The keys for this HealthStats object will be from the {@link UidHealthStats}
      * class.
+     * @more An application must hold the {@link android.Manifest.permission#BATTERY_STATS
+     * android.permission.BATTERY_STATS} permission in order to retrieve any HealthStats
+     * other than its own.
      * @see Process#myUid() Process.myUid()
      */
     public HealthStats takeUidSnapshot(int uid) {
@@ -111,23 +130,21 @@
     /**
      * Return a {@link HealthStats} object containing a snapshot of system health
      * metrics for the given uids (user-id, which in usually corresponds to application).
-     * @more
-     *
-     * An application must hold the {@link android.Manifest.permission#BATTERY_STATS
-     * android.permission.BATTERY_STATS} permission in order to retrieve any HealthStats
-     * other than its own.
      *
      * @param uids An array of User IDs to retrieve.
      * @return An array of {@link HealthStats} objects containing the metrics for each of
      * the requested uids. The keys for this HealthStats object will be from the
      * {@link UidHealthStats} class.
+     * @more An application must hold the {@link android.Manifest.permission#BATTERY_STATS
+     * android.permission.BATTERY_STATS} permission in order to retrieve any HealthStats
+     * other than its own.
      */
     public HealthStats[] takeUidSnapshots(int[] uids) {
         try {
             final HealthStatsParceler[] parcelers = mBatteryStats.takeUidSnapshots(uids);
             final HealthStats[] results = new HealthStats[uids.length];
             final int N = uids.length;
-            for (int i=0; i<N; i++) {
+            for (int i = 0; i < N; i++) {
                 results[i] = parcelers[i].getHealthStats();
             }
             return results;
@@ -136,5 +153,121 @@
         }
     }
 
-}
+    /**
+     * Returns a list of supported power monitors, which include raw ODPM rails and
+     * modeled energy consumers.  If ODPM is unsupported by PowerStats HAL, this method returns
+     * an empty array.
+     *
+     * @hide
+     */
+    @NonNull
+    public PowerMonitor[] getSupportedPowerMonitors() {
+        synchronized (this) {
+            if (mPowerMonitorsInfo != null) {
+                return mPowerMonitorsInfo;
+            }
 
+            CompletableFuture<PowerMonitor[]> future = new CompletableFuture<>();
+            getSupportedPowerMonitors(future);
+            try {
+                return future.get();
+            } catch (InterruptedException | ExecutionException e) {
+                throw new RuntimeException(e);
+            }
+        }
+    }
+
+    /**
+     * Retrieves a list of supported power monitors, see {@link #getSupportedPowerMonitors()}
+     *
+     * @hide
+     */
+    public void getSupportedPowerMonitors(@NonNull CompletableFuture<PowerMonitor[]> future) {
+        synchronized (this) {
+            if (mPowerMonitorsInfo != null) {
+                future.complete(mPowerMonitorsInfo);
+                return;
+            }
+            try {
+                if (mPowerStats == null) {
+                    mPowerMonitorsInfo = new PowerMonitor[0];
+                    future.complete(mPowerMonitorsInfo);
+                    return;
+                }
+
+                mPowerStats.getSupportedPowerMonitors(new ResultReceiver(null) {
+                    @Override
+                    protected void onReceiveResult(int resultCode, Bundle resultData) {
+                        synchronized (this) {
+                            mPowerMonitorsInfo = resultData.getParcelableArray(
+                                    IPowerStatsService.KEY_MONITORS, PowerMonitor.class);
+                        }
+                        future.complete(mPowerMonitorsInfo);
+                    }
+                });
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
+     * Retrieves the accumulated power consumption reported by the specified power monitors.
+     *
+     * @param powerMonitors power monitors to be returned.
+     * @hide
+     */
+    @NonNull
+    public PowerMonitorReadings getPowerMonitorReadings(@NonNull PowerMonitor[] powerMonitors) {
+        CompletableFuture<PowerMonitorReadings> future = new CompletableFuture<>();
+        getPowerMonitorReadings(powerMonitors, future);
+        try {
+            return future.get();
+        } catch (InterruptedException | ExecutionException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private static final Comparator<PowerMonitor> POWER_MONITOR_COMPARATOR =
+            Comparator.comparingInt(pm -> pm.index);
+
+    /**
+     * @param powerMonitors power monitors to be retrieved.
+     * @hide
+     */
+    public void getPowerMonitorReadings(@NonNull PowerMonitor[] powerMonitors,
+            @NonNull CompletableFuture<PowerMonitorReadings> future) {
+        if (mPowerStats == null) {
+            future.completeExceptionally(
+                    new IllegalArgumentException("Unsupported power monitor"));
+            return;
+        }
+
+        Arrays.sort(powerMonitors, POWER_MONITOR_COMPARATOR);
+        int[] indices = new int[powerMonitors.length];
+        for (int i = 0; i < powerMonitors.length; i++) {
+            indices[i] = powerMonitors[i].index;
+        }
+        try {
+            mPowerStats.getPowerMonitorReadings(indices, new ResultReceiver(null) {
+                @Override
+                protected void onReceiveResult(int resultCode, Bundle resultData) {
+                    if (resultCode == IPowerStatsService.RESULT_SUCCESS) {
+                        future.complete(new PowerMonitorReadings(powerMonitors,
+                                resultData.getLongArray(IPowerStatsService.KEY_ENERGY),
+                                resultData.getLongArray(IPowerStatsService.KEY_TIMESTAMPS)));
+                    } else if (resultCode == IPowerStatsService.RESULT_UNSUPPORTED_POWER_MONITOR) {
+                        future.completeExceptionally(
+                                new IllegalArgumentException("Unsupported power monitor"));
+                    } else {
+                        future.completeExceptionally(
+                                new IllegalStateException(
+                                        "Unrecognized result code " + resultCode));
+                    }
+                }
+            });
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+}
diff --git a/core/java/android/os/image/DynamicSystemClient.java b/core/java/android/os/image/DynamicSystemClient.java
index 218ecc8..88096ab 100644
--- a/core/java/android/os/image/DynamicSystemClient.java
+++ b/core/java/android/os/image/DynamicSystemClient.java
@@ -209,6 +209,13 @@
     public static final String ACTION_HIDE_NOTIFICATION =
             "android.os.image.action.HIDE_NOTIFICATION";
 
+    /**
+     * Intent action: notify the service to post a status update when keyguard is dismissed.
+     * @hide
+     */
+    public static final String ACTION_NOTIFY_KEYGUARD_DISMISSED =
+            "android.os.image.action.NOTIFY_KEYGUARD_DISMISSED";
+
     /*
      * Intent Keys
      */
diff --git a/core/java/android/os/image/DynamicSystemManager.java b/core/java/android/os/image/DynamicSystemManager.java
index 9610b16..536795b 100644
--- a/core/java/android/os/image/DynamicSystemManager.java
+++ b/core/java/android/os/image/DynamicSystemManager.java
@@ -285,4 +285,16 @@
             throw new RuntimeException(e.toString());
         }
     }
+
+    /**
+     * Returns the active DSU slot
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
+    public String getActiveDsuSlot() {
+        try {
+            return mService.getActiveDsuSlot();
+        } catch (RemoteException e) {
+            throw new RuntimeException(e.toString());
+        }
+    }
 }
diff --git a/core/java/android/os/image/IDynamicSystemService.aidl b/core/java/android/os/image/IDynamicSystemService.aidl
index 755368a..0280ebb 100644
--- a/core/java/android/os/image/IDynamicSystemService.aidl
+++ b/core/java/android/os/image/IDynamicSystemService.aidl
@@ -145,4 +145,10 @@
      */
     @EnforcePermission("MANAGE_DYNAMIC_SYSTEM")
     long suggestScratchSize();
+
+    /**
+     * Get the active DSU slot
+     */
+    @EnforcePermission("MANAGE_DYNAMIC_SYSTEM")
+    String getActiveDsuSlot();
 }
diff --git a/core/java/android/os/vibrator/PrebakedSegment.java b/core/java/android/os/vibrator/PrebakedSegment.java
index cc76ffa..da2ee6c 100644
--- a/core/java/android/os/vibrator/PrebakedSegment.java
+++ b/core/java/android/os/vibrator/PrebakedSegment.java
@@ -33,6 +33,13 @@
  */
 @TestApi
 public final class PrebakedSegment extends VibrationEffectSegment {
+
+    /** @hide */
+    public static final int DEFAULT_STRENGTH = VibrationEffect.EFFECT_STRENGTH_MEDIUM;
+
+    /** @hide */
+    public static final boolean DEFAULT_SHOULD_FALLBACK = true;
+
     private final int mEffectId;
     private final boolean mFallback;
     private final int mEffectStrength;
diff --git a/core/java/android/os/vibrator/PrimitiveSegment.java b/core/java/android/os/vibrator/PrimitiveSegment.java
index cde0ff3..e1fa97b 100644
--- a/core/java/android/os/vibrator/PrimitiveSegment.java
+++ b/core/java/android/os/vibrator/PrimitiveSegment.java
@@ -36,6 +36,13 @@
  */
 @TestApi
 public final class PrimitiveSegment extends VibrationEffectSegment {
+
+    /** @hide */
+    public static final float DEFAULT_SCALE = 1f;
+
+    /** @hide */
+    public static final int DEFAULT_DELAY_MILLIS = 0;
+
     private final int mPrimitiveId;
     private final float mScale;
     private final int mDelay;
diff --git a/core/java/android/os/vibrator/StepSegment.java b/core/java/android/os/vibrator/StepSegment.java
index 115a66c..817187e 100644
--- a/core/java/android/os/vibrator/StepSegment.java
+++ b/core/java/android/os/vibrator/StepSegment.java
@@ -113,6 +113,10 @@
         VibrationEffectSegment.checkDurationArgument(mDuration, "duration");
         if (Float.compare(mAmplitude, VibrationEffect.DEFAULT_AMPLITUDE) != 0) {
             Preconditions.checkArgumentInRange(mAmplitude, 0f, 1f, "amplitude");
+            VibrationEffectSegment.checkFrequencyArgument(mFrequencyHz, "frequencyHz");
+        } else if (Float.compare(mFrequencyHz, 0) != 0) {
+            throw new IllegalArgumentException(
+                    "frequency must be default when amplitude is set to default");
         }
     }
 
diff --git a/core/java/android/os/vibrator/VibrationConfig.java b/core/java/android/os/vibrator/VibrationConfig.java
index 4a61472..4790d81 100644
--- a/core/java/android/os/vibrator/VibrationConfig.java
+++ b/core/java/android/os/vibrator/VibrationConfig.java
@@ -49,6 +49,8 @@
     private final int mRampStepDurationMs;
     private final int mRampDownDurationMs;
 
+    private final boolean mIgnoreVibrationsOnWirelessCharger;
+
     @VibrationIntensity
     private final int mDefaultAlarmVibrationIntensity;
     @VibrationIntensity
@@ -69,6 +71,9 @@
         mRampStepDurationMs = loadInteger(resources,
                 com.android.internal.R.integer.config_vibrationWaveformRampStepDuration, 0);
 
+        mIgnoreVibrationsOnWirelessCharger = loadBoolean(resources,
+                com.android.internal.R.bool.config_ignoreVibrationsOnWirelessCharger, false);
+
         mDefaultAlarmVibrationIntensity = loadDefaultIntensity(resources,
                 com.android.internal.R.integer.config_defaultAlarmVibrationIntensity);
         mDefaultHapticFeedbackIntensity = loadDefaultIntensity(resources,
@@ -99,6 +104,10 @@
         return res != null ? res.getInteger(resId) : defaultValue;
     }
 
+    private static boolean loadBoolean(@Nullable Resources res, int resId, boolean defaultValue) {
+        return res != null ? res.getBoolean(resId) : defaultValue;
+    }
+
     /**
      * Return the maximum amplitude the vibrator can play using the audio haptic channels.
      *
@@ -135,6 +144,16 @@
         return mRampStepDurationMs;
     }
 
+    /**
+     * Whether or not vibrations are ignored if the device is on a wireless charger.
+     *
+     * <p>This may be the case if vibration during wireless charging causes unwanted results, like
+     * moving the device out of alignment with the charging pad.
+     */
+    public boolean ignoreVibrationsOnWirelessCharger() {
+        return mIgnoreVibrationsOnWirelessCharger;
+    }
+
     /** Get the default vibration intensity for given usage. */
     @VibrationIntensity
     public int getDefaultVibrationIntensity(@VibrationAttributes.Usage int usage) {
diff --git a/core/java/android/os/vibrator/persistence/VibrationXmlParser.java b/core/java/android/os/vibrator/persistence/VibrationXmlParser.java
new file mode 100644
index 0000000..e91e04e
--- /dev/null
+++ b/core/java/android/os/vibrator/persistence/VibrationXmlParser.java
@@ -0,0 +1,238 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.vibrator.persistence;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.TestApi;
+import android.os.VibrationEffect;
+import android.util.Slog;
+import android.util.Xml;
+
+import com.android.internal.vibrator.persistence.VibrationEffectXmlParser;
+import com.android.internal.vibrator.persistence.XmlConstants;
+import com.android.internal.vibrator.persistence.XmlParserException;
+import com.android.internal.vibrator.persistence.XmlReader;
+import com.android.modules.utils.TypedXmlPullParser;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Parses XML into a {@link VibrationEffect}.
+ *
+ * <p>This parser supports a root element that represent a single vibration effect as follows:
+ *
+ * * Predefined vibration effects
+ *
+ * <pre>
+ *   {@code
+ *     <vibration>
+ *       <predefined-effect name="click" />
+ *     </vibration>
+ *   }
+ * </pre>
+ *
+ * * Waveform vibration effects
+ *
+ * <pre>
+ *   {@code
+ *     <vibration>
+ *       <waveform-effect>
+ *         <waveform-entry amplitude="default" durationMs="10" />
+ *         <waveform-entry amplitude="0" durationMs="10" />
+ *         <waveform-entry amplitude="255" durationMs="100" />
+ *         <repeating>
+ *           <waveform-entry amplitude="128" durationMs="30" />
+ *           <waveform-entry amplitude="192" durationMs="60" />
+ *           <waveform-entry amplitude="255" durationMs="20" />
+ *         </repeating>
+ *       </waveform-effect>
+ *     </vibration>
+ *   }
+ * </pre>
+ *
+ * * Primitive composition effects
+ *
+ * <pre>
+ *   {@code
+ *     <vibration>
+ *       <primitive-effect name="click" />
+ *       <primitive-effect name="slow_rise" scale="0.8" />
+ *       <primitive-effect name="quick_fall" delayMs="50" />
+ *       <primitive-effect name="tick" scale="0.5" delayMs="100" />
+ *     </vibration>
+ *   }
+ * </pre>
+ *
+ * @hide
+ */
+@TestApi
+public final class VibrationXmlParser {
+    private static final String TAG = "VibrationXmlParser";
+
+    /**
+     * The MIME type for a xml holding a vibration.
+     *
+     * <p>This should match the type registered at android.mime.types.
+     *
+     * @hide
+     */
+    public static final String APPLICATION_VIBRATION_XML_MIME_TYPE =
+            "application/vnd.android.haptics.vibration+xml";
+
+    /**
+     * Allows {@link VibrationEffect} instances created via non-public APIs to be parsed/serialized.
+     *
+     * <p>Note that the XML schema for non-public APIs is not backwards compatible. This is intended
+     * for loading custom {@link VibrationEffect} configured per device and platform version, not
+     * to be restored from old platform versions.
+     *
+     * @hide
+     */
+    public static final int FLAG_ALLOW_HIDDEN_APIS = 1 << 0; // Same as VibrationXmlSerializer
+
+    /** @hide */
+    @IntDef(prefix = { "FLAG_" }, flag = true, value = {
+            FLAG_ALLOW_HIDDEN_APIS
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Flags {}
+
+    /**
+     * Returns whether this parser supports parsing files of the given MIME type.
+     *
+     * <p>Returns false for {@code null} value.
+     *
+     * <p><em>Note: MIME type matching in the Android framework is case-sensitive, unlike the formal
+     * RFC definitions. As a result, you should always write these elements with lower case letters,
+     * or use {@link android.content.Intent#normalizeMimeType} to ensure that they are converted to
+     * lower case.</em>
+     *
+     * @hide
+     */
+    public static boolean isSupportedMimeType(@Nullable String mimeType) {
+        // NOTE: prefer using MimeTypeFilter.matches() if MIME_TYPE_VIBRATION_XML becomes a filter
+        // or if more than one MIME type is supported by this parser.
+        return APPLICATION_VIBRATION_XML_MIME_TYPE.equals(mimeType);
+    }
+
+    /**
+     * Parses XML content from given input stream into a {@link VibrationEffect}.
+     *
+     * <p>This parser fails silently and returns {@code null} if the content of the input stream
+     * does not follow the schema or has unsupported values.
+     *
+     * @return the {@link VibrationEffect} if parsed successfully, {@code null} otherwise.
+     * @throws IOException error reading from given {@link Reader}
+     *
+     * @hide
+     */
+    @TestApi
+    @Nullable
+    public static VibrationEffect parse(@NonNull Reader reader) throws IOException {
+        return parse(reader, /* flags= */ 0);
+    }
+
+    /**
+     * Parses XML content from given input stream into a {@link VibrationEffect}.
+     *
+     * <p>Same as {@link #parse(Reader)}, with extra flags to control the parsing behavior.
+     *
+     * @hide
+     */
+    @Nullable
+    public static VibrationEffect parse(@NonNull Reader reader, @Flags int flags)
+            throws IOException {
+        TypedXmlPullParser parser = Xml.newFastPullParser();
+
+        try {
+            parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
+            parser.setInput(reader);
+        } catch (XmlPullParserException e) {
+            throw new RuntimeException("An error occurred while setting up the XML parser", e);
+        }
+
+        try {
+            // Ensure XML starts with expected root tag.
+            XmlReader.readDocumentStartTag(parser, XmlConstants.TAG_VIBRATION);
+
+            // Parse root tag as a vibration effect.
+            VibrationEffect effect = parseTag(parser, flags);
+
+            // Ensure XML ends after root tag is consumed.
+            XmlReader.readDocumentEndTag(parser);
+
+            return effect;
+        } catch (XmlParserException | VibrationXmlParserException e) {
+            Slog.w(TAG, "Error parsing vibration XML", e);
+            return null;
+        }
+    }
+
+    /**
+     * Parses XML content from given open {@link TypedXmlPullParser} into a {@link VibrationEffect}.
+     *
+     * <p>The provided parser should be pointing to a start of a valid vibration XML (i.e. to a
+     * start <vibration> tag). No other parser position, including start of document, is considered
+     * valid.
+     *
+     * <p>This method parses as long as it reads a valid vibration XML, and until an end vibration
+     * tag. After a successful parsing, the parser will point to the end vibration tag (i.e. to a
+     * </vibration> tag).
+     *
+     * @throws IOException error parsing from given {@link TypedXmlPullParser}.
+     * @throws VibrationXmlParserException if the XML tag cannot be parsed into a
+     *      {@link VibrationEffect}. The given {@code parser} might be pointing to a child XML tag
+     *      that caused the parser failure.
+     *
+     * @hide
+     */
+    @NonNull
+    public static VibrationEffect parseTag(@NonNull TypedXmlPullParser parser, @Flags int flags)
+            throws IOException, VibrationXmlParserException {
+        int parserFlags = 0;
+        if ((flags & VibrationXmlParser.FLAG_ALLOW_HIDDEN_APIS) != 0) {
+            parserFlags |= XmlConstants.FLAG_ALLOW_HIDDEN_APIS;
+        }
+        try {
+            return VibrationEffectXmlParser.parseTag(parser, parserFlags).deserialize();
+        } catch (XmlParserException e) {
+            throw new VibrationXmlParserException("Error parsing vibration effect.", e);
+        }
+    }
+
+    /**
+     * Represents an error while parsing a vibration XML input.
+     *
+     * @hide
+     */
+    public static final class VibrationXmlParserException extends Exception {
+        private VibrationXmlParserException(String message, Throwable cause) {
+            super(message, cause);
+        }
+    }
+
+    private VibrationXmlParser() {
+    }
+}
diff --git a/core/java/android/os/vibrator/persistence/VibrationXmlSerializer.java b/core/java/android/os/vibrator/persistence/VibrationXmlSerializer.java
new file mode 100644
index 0000000..1cdfa4f
--- /dev/null
+++ b/core/java/android/os/vibrator/persistence/VibrationXmlSerializer.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.vibrator.persistence;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.TestApi;
+import android.os.CombinedVibration;
+import android.os.VibrationEffect;
+import android.util.Xml;
+
+import com.android.internal.vibrator.persistence.VibrationEffectXmlSerializer;
+import com.android.internal.vibrator.persistence.XmlConstants;
+import com.android.internal.vibrator.persistence.XmlSerializedVibration;
+import com.android.internal.vibrator.persistence.XmlSerializerException;
+import com.android.internal.vibrator.persistence.XmlValidator;
+import com.android.modules.utils.TypedXmlSerializer;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Serializes {@link CombinedVibration} and {@link VibrationEffect} instances to XML.
+ *
+ * <p>This uses the same schema expected by the {@link VibrationXmlParser}.
+ *
+ * @hide
+ */
+@TestApi
+public final class VibrationXmlSerializer {
+
+    /**
+     * Allows {@link VibrationEffect} instances created via non-public APIs to be parsed/serialized.
+     *
+     * <p>Note that the XML schema for non-public APIs is not backwards compatible. This is intended
+     * for loading custom {@link VibrationEffect} configured per device and platform version, not
+     * to be restored from old platform versions or from different devices.
+     *
+     * @hide
+     */
+    public static final int FLAG_ALLOW_HIDDEN_APIS = 1 << 0;
+
+    /**
+     * Writes a more human-readable output XML.
+     *
+     * <p>This will be less compact as it includes extra whitespace for things like indentation.
+     *
+     * @hide
+     */
+    public static final int FLAG_PRETTY_PRINT = 1 << 1;
+
+    /** @hide */
+    @IntDef(prefix = { "FLAG_" }, flag = true, value = {
+            FLAG_PRETTY_PRINT,
+            FLAG_ALLOW_HIDDEN_APIS
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Flags {}
+
+    private static final String XML_ENCODING = Xml.Encoding.UTF_8.name();
+    private static final String XML_FEATURE_INDENT_OUTPUT =
+            "http://xmlpull.org/v1/doc/features.html#indent-output";
+
+    /**
+     * Serializes a {@link VibrationEffect} to XML and writes output to given {@link Writer}.
+     *
+     * <p>This method will only write into the {@link Writer} if the effect can successfully
+     * be represented by the XML serialization. It will throw an exception otherwise.
+     *
+     * @throws SerializationFailedException serialization of input effect failed, no data was
+     *                                      written into given {@link Writer}.
+     * @throws IOException error writing to given {@link Writer}.
+     *
+     * @hide
+     */
+    @TestApi
+    public static void serialize(@NonNull VibrationEffect effect, @NonNull Writer writer)
+            throws SerializationFailedException, IOException {
+        serialize(effect, writer, /* flags= */ 0);
+    }
+
+    /**
+     * Serializes a {@link VibrationEffect} to XML and writes output to given {@link Writer}.
+     *
+     * <p>Same as {@link #serialize(VibrationEffect, Writer)}, with extra flags to control the
+     * serialization behavior.
+     *
+     * @hide
+     */
+    public static void serialize(@NonNull VibrationEffect effect, @NonNull Writer writer,
+            @Flags int flags) throws SerializationFailedException, IOException {
+        // Serialize effect first to fail early.
+        XmlSerializedVibration<VibrationEffect> serializedVibration =
+                toSerializedVibration(effect, flags);
+        TypedXmlSerializer xmlSerializer = Xml.newFastSerializer();
+        xmlSerializer.setFeature(XML_FEATURE_INDENT_OUTPUT, (flags & FLAG_PRETTY_PRINT) != 0);
+        xmlSerializer.setOutput(writer);
+        xmlSerializer.startDocument(XML_ENCODING, /* standalone= */ false);
+        serializedVibration.write(xmlSerializer);
+        xmlSerializer.endDocument();
+    }
+
+    private static XmlSerializedVibration<VibrationEffect> toSerializedVibration(
+            VibrationEffect effect, @Flags int flags) throws SerializationFailedException {
+        XmlSerializedVibration<VibrationEffect> serializedVibration;
+        int serializerFlags = 0;
+        if ((flags & FLAG_ALLOW_HIDDEN_APIS) != 0) {
+            serializerFlags |= XmlConstants.FLAG_ALLOW_HIDDEN_APIS;
+        }
+
+        try {
+            serializedVibration = VibrationEffectXmlSerializer.serialize(effect, serializerFlags);
+            XmlValidator.checkSerializedVibration(serializedVibration, effect);
+        } catch (XmlSerializerException e) {
+            // Serialization failed or created incomplete representation, fail before writing.
+            throw new SerializationFailedException(effect, e);
+        }
+
+        return serializedVibration;
+    }
+
+    /**
+     * Exception thrown when a {@link VibrationEffect} instance serialization fails.
+     *
+     * <p>The serialization can fail if a given vibration cannot be represented using the public
+     * format, or if it uses hidden APIs that are not supported for serialization (e.g.
+     * {@link VibrationEffect.WaveformBuilder}).
+     *
+     * @hide
+     */
+    @TestApi
+    public static final class SerializationFailedException extends RuntimeException {
+        SerializationFailedException(VibrationEffect effect, Throwable cause) {
+            super("Serialization failed for vibration effect " + effect, cause);
+        }
+    }
+
+    private VibrationXmlSerializer() {
+    }
+}
diff --git a/core/java/android/permission/DEFAULT_PERMISSION_GRANT_POLICY_OWNERS b/core/java/android/permission/DEFAULT_PERMISSION_GRANT_POLICY_OWNERS
index 4564c30..6c208a8 100644
--- a/core/java/android/permission/DEFAULT_PERMISSION_GRANT_POLICY_OWNERS
+++ b/core/java/android/permission/DEFAULT_PERMISSION_GRANT_POLICY_OWNERS
@@ -1,7 +1,6 @@
-ashfall@google.com
 hackbod@google.com
+joecastro@google.com
 jsharkey@google.com
-narayan@google.com
 patb@google.com
 yamasani@google.com
-zhanghai@google.com
+zhanghai@google.com
\ No newline at end of file
diff --git a/core/java/android/permission/IPermissionManager.aidl b/core/java/android/permission/IPermissionManager.aidl
index 16ae3bc..18ede44d 100644
--- a/core/java/android/permission/IPermissionManager.aidl
+++ b/core/java/android/permission/IPermissionManager.aidl
@@ -42,10 +42,10 @@
 
     void removePermission(String permissionName);
 
-    int getPermissionFlags(String packageName, String permissionName, int userId);
+    int getPermissionFlags(String packageName, String permissionName, int deviceId, int userId);
 
     void updatePermissionFlags(String packageName, String permissionName, int flagMask,
-            int flagValues, boolean checkAdjustPolicyFlagPermission, int userId);
+            int flagValues, boolean checkAdjustPolicyFlagPermission, int deviceId, int userId);
 
     void updatePermissionFlagsForAllApps(int flagMask, int flagValues, int userId);
 
@@ -62,20 +62,22 @@
     boolean removeAllowlistedRestrictedPermission(String packageName, String permissionName,
             int flags, int userId);
 
-    void grantRuntimePermission(String packageName, String permissionName, int userId);
+    void grantRuntimePermission(String packageName, String permissionName, int deviceId, int userId);
 
-    void revokeRuntimePermission(String packageName, String permissionName, int userId,
-            String reason);
+    void revokeRuntimePermission(String packageName, String permissionName, int deviceId,
+            int userId, String reason);
 
     void revokePostNotificationPermissionWithoutKillForTest(String packageName, int userId);
 
     boolean shouldShowRequestPermissionRationale(String packageName, String permissionName,
-            int userId);
+            int deviceId, int userId);
 
-    boolean isPermissionRevokedByPolicy(String packageName, String permissionName, int userId);
+    boolean isPermissionRevokedByPolicy(String packageName, String permissionName, int deviceId,
+            int userId);
 
     List<SplitPermissionInfoParcelable> getSplitPermissions();
 
+    @EnforcePermission("MANAGE_ONE_TIME_PERMISSION_SESSIONS")
     void startOneTimePermissionSession(String packageName, int userId, long timeout,
             long revokeAfterKilledDelay);
 
@@ -93,4 +95,8 @@
     void registerAttributionSource(in AttributionSourceState source);
 
     boolean isRegisteredAttributionSource(in AttributionSourceState source);
+
+    int checkPermission(String packageName, String permissionName, int deviceId, int userId);
+
+    int checkUidPermission(int uid, String permissionName, int deviceId);
 }
diff --git a/core/java/android/permission/OWNERS b/core/java/android/permission/OWNERS
index 4603e43f..d2f4b50 100644
--- a/core/java/android/permission/OWNERS
+++ b/core/java/android/permission/OWNERS
@@ -1,6 +1,5 @@
 # Bug component: 137825
 
-ashfall@google.com
 augale@google.com
 evanseverson@google.com
 fayey@google.com
@@ -8,12 +7,8 @@
 joecastro@google.com
 kvakil@google.com
 mrulhania@google.com
-narayan@google.com
 ntmyren@google.com
-olekarg@google.com
-pyuli@google.com
 rmacgregor@google.com
-sergeynv@google.com
 theianchen@google.com
 yutingfang@google.com
 zhanghai@google.com
diff --git a/core/java/android/permission/PermissionControllerManager.java b/core/java/android/permission/PermissionControllerManager.java
index b494c7f..84a197a 100644
--- a/core/java/android/permission/PermissionControllerManager.java
+++ b/core/java/android/permission/PermissionControllerManager.java
@@ -40,6 +40,7 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.os.Binder;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Process;
@@ -81,8 +82,8 @@
 public final class PermissionControllerManager {
     private static final String TAG = PermissionControllerManager.class.getSimpleName();
 
-    private static final long REQUEST_TIMEOUT_MILLIS = 60000;
-    private static final long UNBIND_TIMEOUT_MILLIS = 10000;
+    private static final long REQUEST_TIMEOUT_MILLIS = 60000L * Build.HW_TIMEOUT_MULTIPLIER;
+    private static final long UNBIND_TIMEOUT_MILLIS = 10000L * Build.HW_TIMEOUT_MULTIPLIER;
     private static final int CHUNK_SIZE = 4 * 1024;
 
     private static final Object sLock = new Object();
@@ -701,6 +702,8 @@
         }, executor);
     }
 
+    // TODO(b/272129940): Remove this API and device profile role description when we drop T
+    //  support.
     /**
      * Gets the description of the privileges associated with the given device profiles
      *
@@ -708,8 +711,11 @@
      * @param executor Executor on which to invoke the callback
      * @param callback Callback to receive the result
      *
+     * @deprecated Device profile privilege descriptions have been bundled in CDM APK since T.
+     *
      * @hide
      */
+    @Deprecated
     @RequiresPermission(Manifest.permission.MANAGE_COMPANION_DEVICES)
     public void getPrivilegesDescriptionStringForProfile(
             @NonNull String profileName,
diff --git a/core/java/android/permission/PermissionControllerService.java b/core/java/android/permission/PermissionControllerService.java
index 4efffc5a..11005a6 100644
--- a/core/java/android/permission/PermissionControllerService.java
+++ b/core/java/android/permission/PermissionControllerService.java
@@ -348,6 +348,8 @@
         throw new AbstractMethodError("Must be overridden in implementing class");
     }
 
+    // TODO(b/272129940): Remove this API and device profile role description when we drop T
+    //  support.
     /**
      * Get a user-readable sentence, describing the set of privileges that are to be granted to a
      * companion app managing a device of the given profile.
@@ -355,8 +357,11 @@
      * @param deviceProfileName the
      *      {@link android.companion.AssociationRequest.DeviceProfile device profile} name
      *
+     * @deprecated Device profile privilege descriptions have been bundled in CDM APK since T.
+     *
      * @hide
      */
+    @Deprecated
     @SystemApi
     @RequiresPermission(Manifest.permission.MANAGE_COMPANION_DEVICES)
     @NonNull
diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java
index b0dda6f..7d39210 100644
--- a/core/java/android/permission/PermissionManager.java
+++ b/core/java/android/permission/PermissionManager.java
@@ -66,6 +66,7 @@
 import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.os.UserHandle;
+import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.DebugUtils;
@@ -75,6 +76,7 @@
 import com.android.internal.R;
 import com.android.internal.annotations.Immutable;
 import com.android.internal.util.CollectionUtils;
+import com.android.modules.utils.build.SdkLevel;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -177,6 +179,13 @@
     public static final long CANNOT_INSTALL_WITH_BAD_PERMISSION_GROUPS = 146211400;
 
     /**
+     * Whether to use the new {@link com.android.server.permission.access.AccessCheckingService}.
+     *
+     * @hide
+     */
+    public static final boolean USE_ACCESS_CHECKING_SERVICE = SdkLevel.isAtLeastV();
+
+    /**
      * The time to wait in between refreshing the exempted indicator role packages
      */
     private static final long EXEMPTED_INDICATOR_ROLE_UPDATE_FREQUENCY_MS = 15000;
@@ -563,7 +572,7 @@
             @NonNull String permissionName) {
         try {
             return mPermissionManager.isPermissionRevokedByPolicy(packageName, permissionName,
-                    mContext.getUserId());
+                    mContext.getDeviceId(), mContext.getUserId());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -607,7 +616,7 @@
         }
         try {
             mPermissionManager.grantRuntimePermission(packageName, permissionName,
-                    user.getIdentifier());
+                    mContext.getDeviceId(), user.getIdentifier());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -644,8 +653,8 @@
                     + reason, new RuntimeException());
         }
         try {
-            mPermissionManager
-                    .revokeRuntimePermission(packageName, permName, user.getIdentifier(), reason);
+            mPermissionManager.revokeRuntimePermission(packageName, permName,
+                    mContext.getDeviceId(), user.getIdentifier(), reason);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -672,7 +681,7 @@
             @NonNull UserHandle user) {
         try {
             return mPermissionManager.getPermissionFlags(packageName, permissionName,
-                    user.getIdentifier());
+                    mContext.getDeviceId(), user.getIdentifier());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -711,7 +720,8 @@
             final boolean checkAdjustPolicyFlagPermission =
                     mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.Q;
             mPermissionManager.updatePermissionFlags(packageName, permissionName, flagMask,
-                    flagValues, checkAdjustPolicyFlagPermission, user.getIdentifier());
+                    flagValues, checkAdjustPolicyFlagPermission,
+                    mContext.getDeviceId(), user.getIdentifier());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -967,7 +977,7 @@
         try {
             final String packageName = mContext.getPackageName();
             return mPermissionManager.shouldShowRequestPermissionRationale(packageName,
-                    permissionName, mContext.getUserId());
+                    permissionName, mContext.getDeviceId(), mContext.getUserId());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -1501,8 +1511,8 @@
     // to reduce duplicate logcat output.
     private static volatile boolean sShouldWarnMissingActivityManager = true;
 
-    /* @hide */
-    private static int checkPermissionUncached(@Nullable String permission, int pid, int uid) {
+    private static int checkPermissionUncached(@Nullable String permission, int pid, int uid,
+            int deviceId) {
         final IActivityManager am = ActivityManager.getService();
         if (am == null) {
             // Well this is super awkward; we somehow don't have an active ActivityManager
@@ -1523,7 +1533,7 @@
         }
         try {
             sShouldWarnMissingActivityManager = true;
-            return am.checkPermission(permission, pid, uid);
+            return am.checkPermissionForDevice(permission, pid, uid, deviceId);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -1543,26 +1553,26 @@
         final String permission;
         final int pid;
         final int uid;
+        final int deviceId;
 
-        PermissionQuery(@Nullable String permission, int pid, int uid) {
+        PermissionQuery(@Nullable String permission, int pid, int uid, int deviceId) {
             this.permission = permission;
             this.pid = pid;
             this.uid = uid;
+            this.deviceId = deviceId;
         }
 
         @Override
         public String toString() {
-            return String.format("PermissionQuery(permission=\"%s\", pid=%s, uid=%s)",
-                    permission, pid, uid);
+            return TextUtils.formatSimple("PermissionQuery(permission=\"%s\", pid=%d, uid=%d, "
+                            + "deviceId=%d)", permission, pid, uid, deviceId);
         }
 
         @Override
         public int hashCode() {
             // N.B. pid doesn't count toward equality and therefore shouldn't count for
             // hashing either.
-            int hash = Objects.hashCode(permission);
-            hash = hash * 13 + Objects.hashCode(uid);
-            return hash;
+            return Objects.hash(permission, uid, deviceId);
         }
 
         @Override
@@ -1577,7 +1587,7 @@
             } catch (ClassCastException ex) {
                 return false;
             }
-            return uid == other.uid
+            return uid == other.uid && deviceId == other.deviceId
                     && Objects.equals(permission, other.permission);
         }
     }
@@ -1591,13 +1601,14 @@
                     2048, CACHE_KEY_PACKAGE_INFO, "checkPermission") {
                 @Override
                 public Integer recompute(PermissionQuery query) {
-                    return checkPermissionUncached(query.permission, query.pid, query.uid);
+                    return checkPermissionUncached(query.permission, query.pid, query.uid,
+                            query.deviceId);
                 }
             };
 
     /** @hide */
-    public static int checkPermission(@Nullable String permission, int pid, int uid) {
-        return sPermissionCache.query(new PermissionQuery(permission, pid, uid));
+    public static int checkPermission(@Nullable String permission, int pid, int uid, int deviceId) {
+        return sPermissionCache.query(new PermissionQuery(permission, pid, uid, deviceId));
     }
 
     /**
@@ -1617,26 +1628,29 @@
     private static final class PackageNamePermissionQuery {
         final String permName;
         final String pkgName;
+        final int deviceId;
         @UserIdInt
         final int userId;
 
         PackageNamePermissionQuery(@Nullable String permName, @Nullable String pkgName,
-                @UserIdInt int userId) {
+                int deviceId, @UserIdInt int userId) {
             this.permName = permName;
             this.pkgName = pkgName;
+            this.deviceId = deviceId;
             this.userId = userId;
         }
 
         @Override
         public String toString() {
-            return String.format(
-                    "PackageNamePermissionQuery(pkgName=\"%s\", permName=\"%s, userId=%s\")",
-                    pkgName, permName, userId);
+            return TextUtils.formatSimple(
+                    "PackageNamePermissionQuery(pkgName=\"%s\", permName=\"%s\", "
+                            + "deviceId=%s, userId=%s\")",
+                    pkgName, permName, deviceId, userId);
         }
 
         @Override
         public int hashCode() {
-            return Objects.hash(permName, pkgName, userId);
+            return Objects.hash(permName, pkgName, deviceId, userId);
         }
 
         @Override
@@ -1652,16 +1666,17 @@
             }
             return Objects.equals(permName, other.permName)
                     && Objects.equals(pkgName, other.pkgName)
+                    && deviceId == other.deviceId
                     && userId == other.userId;
         }
     }
 
     /* @hide */
     private static int checkPackageNamePermissionUncached(
-            String permName, String pkgName, @UserIdInt int userId) {
+            String permName, String pkgName, int deviceId, @UserIdInt int userId) {
         try {
-            return ActivityThread.getPackageManager().checkPermission(
-                    permName, pkgName, userId);
+            return ActivityThread.getPermissionManager().checkPermission(
+                    pkgName, permName, deviceId, userId);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -1675,7 +1690,7 @@
                 @Override
                 public Integer recompute(PackageNamePermissionQuery query) {
                     return checkPackageNamePermissionUncached(
-                            query.permName, query.pkgName, query.userId);
+                            query.permName, query.pkgName, query.deviceId, query.userId);
                 }
                 @Override
                 public boolean bypass(PackageNamePermissionQuery query) {
@@ -1684,14 +1699,14 @@
             };
 
     /**
-     * Check whether a package has a permission.
+     * Check whether a package has a permission for given device.
      *
      * @hide
      */
-    public static int checkPackageNamePermission(String permName, String pkgName,
+    public static int checkPackageNamePermission(String permName, String pkgName, int deviceId,
             @UserIdInt int userId) {
         return sPackageNamePermissionCache.query(
-                new PackageNamePermissionQuery(permName, pkgName, userId));
+                new PackageNamePermissionQuery(permName, pkgName, deviceId, userId));
     }
 
     /**
diff --git a/core/java/android/print/OWNERS b/core/java/android/print/OWNERS
index 28a24203..0809de2 100644
--- a/core/java/android/print/OWNERS
+++ b/core/java/android/print/OWNERS
@@ -1,4 +1,4 @@
 # Bug component: 47273
 
-svetoslavganov@android.com
-svetoslavganov@google.com
+anothermark@google.com
+kumarashishg@google.com
diff --git a/core/java/android/print/pdf/OWNERS b/core/java/android/print/pdf/OWNERS
deleted file mode 100644
index 28a24203..0000000
--- a/core/java/android/print/pdf/OWNERS
+++ /dev/null
@@ -1,4 +0,0 @@
-# Bug component: 47273
-
-svetoslavganov@android.com
-svetoslavganov@google.com
diff --git a/core/java/android/printservice/OWNERS b/core/java/android/printservice/OWNERS
index 28a24203..0809de2 100644
--- a/core/java/android/printservice/OWNERS
+++ b/core/java/android/printservice/OWNERS
@@ -1,4 +1,4 @@
 # Bug component: 47273
 
-svetoslavganov@android.com
-svetoslavganov@google.com
+anothermark@google.com
+kumarashishg@google.com
diff --git a/core/java/android/printservice/recommendation/OWNERS b/core/java/android/printservice/recommendation/OWNERS
deleted file mode 100644
index 28a24203..0000000
--- a/core/java/android/printservice/recommendation/OWNERS
+++ /dev/null
@@ -1,4 +0,0 @@
-# Bug component: 47273
-
-svetoslavganov@android.com
-svetoslavganov@google.com
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index d695c0c..cb46674 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -295,6 +295,19 @@
             "android.settings.AIRPLANE_MODE_SETTINGS";
 
     /**
+     * Activity Action: Show enabled eSim profile in Settings
+     * <p>
+     * This opens the Settings page for the currently enabled eSim profile
+     * <p>
+     * Input: Nothing.
+     * <p>
+     * Output: Nothing.
+     * @hide
+     */
+    public static final String ACTION_SHOW_ENABLED_ESIM_PROFILE =
+            "android.settings.SHOW_ENABLED_ESIM_PROFILE";
+
+    /**
      * Activity Action: Show mobile data usage list.
      * <p>
      * Input: {@link EXTRA_NETWORK_TEMPLATE} and {@link EXTRA_SUB_ID} should be included to specify
@@ -2186,6 +2199,16 @@
             = "android.settings.APP_NOTIFICATION_BUBBLE_SETTINGS";
 
     /**
+     * Intent Extra: The value of {@link android.app.settings.SettingsEnums#EntryPointType} for
+     * settings metrics that logs the entry point about physical keyboard settings.
+     * <p>
+     * This must be passed as an extra field to the {@link #ACTION_HARD_KEYBOARD_SETTINGS}.
+     * @hide
+     */
+    public static final String EXTRA_ENTRYPOINT =
+            "com.android.settings.inputmethod.EXTRA_ENTRYPOINT";
+
+    /**
      * Activity Extra: The package owner of the notification channel settings to display.
      * <p>
      * This must be passed as an extra field to the {@link #ACTION_CHANNEL_NOTIFICATION_SETTINGS}.
@@ -2443,6 +2466,24 @@
             "android.settings.REQUEST_SET_AUTOFILL_SERVICE";
 
     /**
+     * Activity Action: Show screen that let user enable a Credential Manager provider.
+     * <p>
+     * Input: Intent's data URI set with an application name, using the
+     * "package" schema (like "package:com.my.app").
+     *
+     * <p>
+     * Output: {@link android.app.Activity#RESULT_OK} if user selected a provider belonging
+     * to the caller package.
+     * <p>
+     * <b>NOTE: </b> Applications should call
+     * {@link android.credentials.CredentialManager#isEnabledCredentialProviderService()}
+     * and only use this action to start an activity if they return {@code false}.
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_CREDENTIAL_PROVIDER =
+            "android.settings.CREDENTIAL_PROVIDER";
+
+    /**
      * Activity Action: Show screen for controlling the Quick Access Wallet.
      * <p>
      * In some cases, a matching Activity may not exist, so ensure you
@@ -2610,6 +2651,26 @@
     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
     public static final String ACTION_BEDTIME_SETTINGS = "android.settings.BEDTIME_SETTINGS";
 
+    /**
+     * Activity action: Launch UI to manage the permissions of an app.
+     * <p>
+     * Input: {@link android.content.Intent#EXTRA_PACKAGE_NAME} specifies the package whose
+     * permissions will be managed by the launched UI.
+     * </p>
+     * <p>
+     * Output: Nothing.
+     * </p>
+     *
+     * @see android.content.Intent#EXTRA_PACKAGE_NAME
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.LAUNCH_PERMISSION_SETTINGS)
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_APP_PERMISSIONS_SETTINGS =
+            "android.settings.APP_PERMISSIONS_SETTINGS";
+
     // End of Intent actions for Settings
 
     /**
@@ -2734,6 +2795,9 @@
     /** @hide - Private call() method to reset to defaults the 'configuration' table */
     public static final String CALL_METHOD_DELETE_CONFIG = "DELETE_config";
 
+    /** @hide - Private call() method to reset to defaults the 'system' table */
+    public static final String CALL_METHOD_RESET_SYSTEM = "RESET_system";
+
     /** @hide - Private call() method to reset to defaults the 'secure' table */
     public static final String CALL_METHOD_RESET_SECURE = "RESET_secure";
 
@@ -3949,6 +4013,26 @@
                    overrideableByRestore);
         }
 
+        /**
+         * Store a name/value pair into the database.
+         *
+         * @param resolver to access the database with
+         * @param name to store
+         * @param value to associate with the name
+         * @param makeDefault whether to make the value the default one
+         * @param overrideableByRestore whether restore can override this value
+         * @return true if the value was set, false on database errors
+         *
+         * @hide
+         */
+        @RequiresPermission(Manifest.permission.MODIFY_SETTINGS_OVERRIDEABLE_BY_RESTORE)
+        @SystemApi
+        public static boolean putString(@NonNull ContentResolver resolver, @NonNull String name,
+                @Nullable String value, boolean makeDefault, boolean overrideableByRestore) {
+            return putStringForUser(resolver, name, value, /* tag= */ null,
+                    makeDefault, resolver.getUserId(), overrideableByRestore);
+        }
+
         /** @hide */
         @UnsupportedAppUsage
         public static boolean putStringForUser(ContentResolver resolver, String name, String value,
@@ -3984,6 +4068,60 @@
         }
 
         /**
+         * Reset the settings to their defaults. This would reset <strong>only</strong>
+         * settings set by the caller's package. Think of it of a way to undo your own
+         * changes to the system settings. Passing in the optional tag will reset only
+         * settings changed by your package and associated with this tag.
+         *
+         * @param resolver Handle to the content resolver.
+         * @param tag Optional tag which should be associated with the settings to reset.
+         *
+         * @see #putString(ContentResolver, String, String, boolean, boolean)
+         *
+         * @hide
+         */
+        @SystemApi
+        public static void resetToDefaults(@NonNull ContentResolver resolver,
+                @Nullable String tag) {
+            resetToDefaultsAsUser(resolver, tag, RESET_MODE_PACKAGE_DEFAULTS,
+                    resolver.getUserId());
+        }
+
+        /**
+         * Reset the settings to their defaults for a given user with a specific mode. The
+         * optional tag argument is valid only for {@link #RESET_MODE_PACKAGE_DEFAULTS}
+         * allowing resetting the settings made by a package and associated with the tag.
+         *
+         * @param resolver Handle to the content resolver.
+         * @param tag Optional tag which should be associated with the settings to reset.
+         * @param mode The reset mode.
+         * @param userHandle The user for which to reset to defaults.
+         *
+         * @see #RESET_MODE_PACKAGE_DEFAULTS
+         * @see #RESET_MODE_UNTRUSTED_DEFAULTS
+         * @see #RESET_MODE_UNTRUSTED_CHANGES
+         * @see #RESET_MODE_TRUSTED_DEFAULTS
+         *
+         * @hide
+         */
+        public static void resetToDefaultsAsUser(@NonNull ContentResolver resolver,
+                @Nullable String tag, @ResetMode int mode, @IntRange(from = 0) int userHandle) {
+            try {
+                Bundle arg = new Bundle();
+                arg.putInt(CALL_METHOD_USER_KEY, userHandle);
+                if (tag != null) {
+                    arg.putString(CALL_METHOD_TAG_KEY, tag);
+                }
+                arg.putInt(CALL_METHOD_RESET_MODE_KEY, mode);
+                IContentProvider cp = sProviderHolder.getProvider(resolver);
+                cp.call(resolver.getAttributionSource(),
+                        sProviderHolder.mUri.getAuthority(), CALL_METHOD_RESET_SYSTEM, null, arg);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Can't reset do defaults for " + CONTENT_URI, e);
+            }
+        }
+
+        /**
          * Construct the content URI for a particular name/value pair,
          * useful for monitoring changes with a ContentObserver.
          * @param name to look up in the table
@@ -4418,6 +4556,16 @@
                 = "wear_accessibility_gesture_enabled";
 
         /**
+         * If the triple press gesture for toggling accessibility is enabled during OOBE.
+         * Set to 1 for true and 0 for false.
+         *
+         * This setting is used only internally.
+         * @hide
+         */
+        public static final String WEAR_ACCESSIBILITY_GESTURE_ENABLED_DURING_OOBE =
+                "wear_accessibility_gesture_enabled_during_oobe";
+
+        /**
          * @deprecated Use {@link android.provider.Settings.Global#AIRPLANE_MODE_ON} instead
          */
         @Deprecated
@@ -4699,6 +4847,15 @@
         public static final String PEAK_REFRESH_RATE = "peak_refresh_rate";
 
         /**
+         * Control whether to stay awake on fold
+         *
+         * If this isn't set, the system falls back to a device specific default.
+         * @hide
+         */
+        @Readable
+        public static final String STAY_AWAKE_ON_FOLD = "stay_awake_on_fold";
+
+        /**
          * The amount of time in milliseconds before the device goes to sleep or begins
          * to dream after a period of inactivity.  This value is also known as the
          * user activity timeout period since the screen isn't necessarily turned off
@@ -5837,6 +5994,7 @@
             PRIVATE_SETTINGS.add(END_BUTTON_BEHAVIOR);
             PRIVATE_SETTINGS.add(ADVANCED_SETTINGS);
             PRIVATE_SETTINGS.add(WEAR_ACCESSIBILITY_GESTURE_ENABLED);
+            PRIVATE_SETTINGS.add(WEAR_ACCESSIBILITY_GESTURE_ENABLED_DURING_OOBE);
             PRIVATE_SETTINGS.add(SCREEN_AUTO_BRIGHTNESS_ADJ);
             PRIVATE_SETTINGS.add(VIBRATE_INPUT_DEVICES);
             PRIVATE_SETTINGS.add(VOLUME_MASTER);
@@ -7486,6 +7644,14 @@
         public static final String STYLUS_BUTTONS_ENABLED = "stylus_buttons_enabled";
 
         /**
+         * Preferred default user profile to use with the notes task button shortcut.
+         *
+         * @hide
+         */
+        @SuppressLint("NoSettingsProvider")
+        public static final String DEFAULT_NOTE_TASK_PROFILE = "default_note_task_profile";
+
+        /**
          * Host name and port for global http proxy. Uses ':' seperator for
          * between host and port.
          *
@@ -8481,6 +8647,18 @@
         public static final String MULTI_PRESS_TIMEOUT = "multi_press_timeout";
 
         /**
+         * The duration before a key repeat begins in milliseconds.
+         * @hide
+         */
+        public static final String KEY_REPEAT_TIMEOUT_MS = "key_repeat_timeout";
+
+        /**
+         * The duration between successive key repeats in milliseconds.
+         * @hide
+         */
+        public static final String KEY_REPEAT_DELAY_MS = "key_repeat_delay";
+
+        /**
          * Setting that specifies recommended timeout in milliseconds for controls
          * which don't need user's interactions.
          *
@@ -11627,6 +11805,15 @@
         public static final String LOCK_SCREEN_WEATHER_ENABLED = "lockscreen_weather_enabled";
 
         /**
+         * Whether the feature that the device will fire a haptic when users scroll and hit
+         * the edge of the screen is enabled.
+         *
+         * @hide
+         */
+        public static final String ACCESSIBILITY_DISPLAY_MAGNIFICATION_EDGE_HAPTIC_ENABLED =
+                "accessibility_display_magnification_edge_haptic_enabled";
+
+        /**
          * These entries are considered common between the personal and the managed profile,
          * since the managed profile doesn't get to change them.
          */
@@ -12605,6 +12792,26 @@
         public static final String MOBILE_DATA_ALWAYS_ON = "mobile_data_always_on";
 
         /**
+         * The duration in milliseconds of each action, separated by commas. Ex:
+         *
+         * "18000,18000,18000,18000,0"
+         *
+         * See com.android.internal.telephony.data.DataStallRecoveryManager for more info
+         * @hide
+         */
+        public static final String DSRM_DURATION_MILLIS = "dsrm_duration_millis";
+
+        /**
+         * The list of DSRM enabled actions, separated by commas. Ex:
+         *
+         * "true,true,false,true,true"
+         *
+         * See com.android.internal.telephony.data.DataStallRecoveryManager for more info
+         * @hide
+         */
+        public static final String DSRM_ENABLED_ACTIONS = "dsrm_enabled_actions";
+
+        /**
          * Whether the wifi data connection should remain active even when higher
          * priority networks like Ethernet are active, to keep both networks.
          * In the case where higher priority networks are connected, wifi will be
@@ -16653,6 +16860,30 @@
         public static final String AWARE_ALLOWED = "aware_allowed";
 
         /**
+         * Overrides internal R.integer.config_shortPressOnPowerBehavior.
+         * Allowable values detailed in frameworks/base/core/res/res/values/config.xml.
+         * Used by PhoneWindowManager.
+         * @hide
+         */
+        public static final String POWER_BUTTON_SHORT_PRESS = "power_button_short_press";
+
+        /**
+         * Overrides internal R.integer.config_doublePressOnPowerBehavior.
+         * Allowable values detailed in frameworks/base/core/res/res/values/config.xml.
+         * Used by PhoneWindowManager.
+         * @hide
+         */
+        public static final String POWER_BUTTON_DOUBLE_PRESS = "power_button_double_press";
+
+        /**
+         * Overrides internal R.integer.config_triplePressOnPowerBehavior.
+         * Allowable values detailed in frameworks/base/core/res/res/values/config.xml.
+         * Used by PhoneWindowManager.
+         * @hide
+         */
+        public static final String POWER_BUTTON_TRIPLE_PRESS = "power_button_triple_press";
+
+        /**
          * Overrides internal R.integer.config_longPressOnPowerBehavior.
          * Allowable values detailed in frameworks/base/core/res/res/values/config.xml.
          * Used by PhoneWindowManager.
@@ -16683,6 +16914,42 @@
                 "power_button_very_long_press";
 
         /**
+         * Overrides internal R.integer.config_shortPressOnStemPrimaryBehavior.
+         * Allowable values detailed in frameworks/base/core/res/res/values/config.xml.
+         * Used by PhoneWindowManager.
+         * @hide
+         */
+        public static final String STEM_PRIMARY_BUTTON_SHORT_PRESS =
+                "stem_primary_button_short_press";
+
+        /**
+         * Overrides internal R.integer.config_doublePressOnStemPrimaryBehavior.
+         * Allowable values detailed in frameworks/base/core/res/res/values/config.xml.
+         * Used by PhoneWindowManager.
+         * @hide
+         */
+        public static final String STEM_PRIMARY_BUTTON_DOUBLE_PRESS =
+                "stem_primary_button_double_press";
+
+        /**
+         * Overrides internal R.integer.config_triplePressOnStemPrimaryBehavior.
+         * Allowable values detailed in frameworks/base/core/res/res/values/config.xml.
+         * Used by PhoneWindowManager.
+         * @hide
+         */
+        public static final String STEM_PRIMARY_BUTTON_TRIPLE_PRESS =
+                "stem_primary_button_triple_press";
+
+        /**
+         * Overrides internal R.integer.config_longPressOnStemPrimaryBehavior.
+         * Allowable values detailed in frameworks/base/core/res/res/values/config.xml.
+         * Used by PhoneWindowManager.
+         * @hide
+         */
+        public static final String STEM_PRIMARY_BUTTON_LONG_PRESS =
+                "stem_primary_button_long_press";
+
+        /**
          * Overrides internal R.integer.config_keyChordPowerVolumeUp.
          * Allowable values detailed in frameworks/base/core/res/res/values/config.xml.
          * Used by PhoneWindowManager.
@@ -17979,6 +18246,15 @@
                 "review_permissions_notification_state";
 
         /**
+         * Whether repair mode is active on the device.
+         * <p>
+         * Set to 1 for true and 0 for false.
+         *
+         * @hide
+         */
+        public static final String REPAIR_MODE_ACTIVE = "repair_mode_active";
+
+        /**
          * Settings migrated from Wear OS settings provider.
          * @hide
          */
@@ -18482,7 +18758,7 @@
              * @hide
              */
             @Deprecated
-            public static final String COMBINED_LOCATION_ENABLED = "combined_location_enable";
+            public static final String COMBINED_LOCATION_ENABLE = "combined_location_enable";
 
             /**
              * The wrist orientation mode of the device
diff --git a/core/java/android/provider/TEST_MAPPING b/core/java/android/provider/TEST_MAPPING
index c55572e..8b4a99e 100644
--- a/core/java/android/provider/TEST_MAPPING
+++ b/core/java/android/provider/TEST_MAPPING
@@ -21,7 +21,7 @@
             "name": "SettingsProviderTest"
         },
         {
-            "name": "CtsAppSecurityHostTestCases",
+            "name": "CtsPackageManagerHostTestCases",
             "options": [
                 {
                     "include-filter": "android.appsecurity.cts.ReadableSettingsFieldsTest"
diff --git a/core/java/android/security/OWNERS b/core/java/android/security/OWNERS
index 7140ff1..f6b1235 100644
--- a/core/java/android/security/OWNERS
+++ b/core/java/android/security/OWNERS
@@ -1,7 +1,6 @@
 # Bug component: 36824
 
 cbrubaker@google.com
-vishwath@google.com
 
 per-file NetworkSecurityPolicy.java = cbrubaker@google.com
 per-file NetworkSecurityPolicy.java = klyubin@google.com
diff --git a/core/java/android/security/keymaster/KeymasterDefs.java b/core/java/android/security/keymaster/KeymasterDefs.java
index 4d6422c..9088a77 100644
--- a/core/java/android/security/keymaster/KeymasterDefs.java
+++ b/core/java/android/security/keymaster/KeymasterDefs.java
@@ -337,8 +337,22 @@
             ErrorCode.ROLLBACK_RESISTANCE_UNAVAILABLE; // -67;
     public static final int KM_ERROR_HARDWARE_TYPE_UNAVAILABLE =
             ErrorCode.HARDWARE_TYPE_UNAVAILABLE; // -68;
+    public static final int KM_ERROR_PROOF_OF_PRESENCE_REQUIRED =
+            ErrorCode.PROOF_OF_PRESENCE_REQUIRED; // -69;
+    public static final int KM_ERROR_CONCURRENT_PROOF_OF_PRESENCE_REQUESTED =
+            ErrorCode.CONCURRENT_PROOF_OF_PRESENCE_REQUESTED; // -70;
+    public static final int KM_ERROR_NO_USER_CONFIRMATION =
+            ErrorCode.NO_USER_CONFIRMATION; // -71;
     public static final int KM_ERROR_DEVICE_LOCKED =
             ErrorCode.DEVICE_LOCKED; // -72;
+    public static final int KM_ERROR_EARLY_BOOT_ENDED =
+            ErrorCode.EARLY_BOOT_ENDED; // -73;
+    public static final int KM_ERROR_ATTESTATION_KEYS_NOT_PROVISIONED =
+            ErrorCode.ATTESTATION_KEYS_NOT_PROVISIONED; // -74;
+    public static final int KM_ERROR_ATTESTATION_IDS_NOT_PROVISIONED =
+            ErrorCode.ATTESTATION_IDS_NOT_PROVISIONED; // -75;
+    public static final int KM_ERROR_INVALID_OPERATION =
+            ErrorCode.INVALID_OPERATION; // -76;
     public static final int KM_ERROR_STORAGE_KEY_UNSUPPORTED =
             ErrorCode.STORAGE_KEY_UNSUPPORTED; // -77,
     public static final int KM_ERROR_INCOMPATIBLE_MGF_DIGEST =
@@ -348,7 +362,13 @@
     public static final int KM_ERROR_MISSING_NOT_BEFORE =
             ErrorCode.MISSING_NOT_BEFORE; // -80;
     public static final int KM_ERROR_MISSING_NOT_AFTER =
-            ErrorCode.MISSING_NOT_AFTER; // -80;
+            ErrorCode.MISSING_NOT_AFTER; // -81;
+    public static final int KM_ERROR_MISSING_ISSUER_SUBJECT =
+            ErrorCode.MISSING_ISSUER_SUBJECT; // -82;
+    public static final int KM_ERROR_INVALID_ISSUER_SUBJECT =
+            ErrorCode.INVALID_ISSUER_SUBJECT; // -83;
+    public static final int KM_ERROR_BOOT_LEVEL_EXCEEDED =
+            ErrorCode.BOOT_LEVEL_EXCEEDED; // -84;
     public static final int KM_ERROR_HARDWARE_NOT_YET_AVAILABLE =
             ErrorCode.HARDWARE_NOT_YET_AVAILABLE; // -85
     public static final int KM_ERROR_UNIMPLEMENTED =
diff --git a/core/java/android/service/autofill/FillRequest.java b/core/java/android/service/autofill/FillRequest.java
index 7ec1483..8cf2ce4 100644
--- a/core/java/android/service/autofill/FillRequest.java
+++ b/core/java/android/service/autofill/FillRequest.java
@@ -97,6 +97,8 @@
      */
     public static final @RequestFlags int FLAG_VIEW_NOT_FOCUSED = 0x10;
 
+    // The flag value 0x20 has been defined in AutofillManager.
+
     /**
      * Indicates the request supports fill dialog presentation for the fields, the
      * system will send the request when the activity just started.
diff --git a/core/java/android/service/autofill/augmented/OWNERS b/core/java/android/service/autofill/augmented/OWNERS
index a088632..4187b10 100644
--- a/core/java/android/service/autofill/augmented/OWNERS
+++ b/core/java/android/service/autofill/augmented/OWNERS
@@ -1,9 +1,3 @@
 # Bug component: 351486
 
-joannechung@google.com
-adamhe@google.com
-tymtsai@google.com
-lpeter@google.com
-augale@google.com
-svetoslavganov@android.com
-svetoslavganov@google.com
+wangqi@google.com
diff --git a/core/java/android/service/cloudsearch/OWNERS b/core/java/android/service/cloudsearch/OWNERS
index aa4da3b..3a5841e 100644
--- a/core/java/android/service/cloudsearch/OWNERS
+++ b/core/java/android/service/cloudsearch/OWNERS
@@ -1,4 +1,3 @@
 # Bug component: 758286
 
-huiwu@google.com
 srazdan@google.com
diff --git a/core/java/android/service/contentcapture/ContentCaptureService.java b/core/java/android/service/contentcapture/ContentCaptureService.java
index 737c95f..06e86af 100644
--- a/core/java/android/service/contentcapture/ContentCaptureService.java
+++ b/core/java/android/service/contentcapture/ContentCaptureService.java
@@ -15,6 +15,8 @@
  */
 package android.service.contentcapture;
 
+import static android.view.contentcapture.ContentCaptureEvent.TYPE_SESSION_PAUSED;
+import static android.view.contentcapture.ContentCaptureEvent.TYPE_SESSION_RESUMED;
 import static android.view.contentcapture.ContentCaptureHelper.sDebug;
 import static android.view.contentcapture.ContentCaptureHelper.sVerbose;
 import static android.view.contentcapture.ContentCaptureHelper.toList;
@@ -37,6 +39,7 @@
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.ParcelFileDescriptor;
+import android.os.Process;
 import android.os.RemoteException;
 import android.util.Log;
 import android.util.Slog;
@@ -88,6 +91,18 @@
             "android.service.contentcapture.ContentCaptureService";
 
     /**
+     * The {@link Intent} that must be declared as handled by the protection service.
+     *
+     * <p>To be supported, the service must also require the {@link
+     * android.Manifest.permission#BIND_CONTENT_CAPTURE_SERVICE} permission so that other
+     * applications can not abuse it.
+     *
+     * @hide
+     */
+    public static final String PROTECTION_SERVICE_INTERFACE =
+            "android.service.contentcapture.ContentProtectionService";
+
+    /**
      * Name under which a ContentCaptureService component publishes information about itself.
      *
      * <p>This meta-data should reference an XML resource containing a
@@ -128,10 +143,9 @@
     private long mCallerMismatchTimeout = 1000;
     private long mLastCallerMismatchLog;
 
-    /**
-     * Binder that receives calls from the system server.
-     */
-    private final IContentCaptureService mServerInterface = new IContentCaptureService.Stub() {
+    /** Binder that receives calls from the system server in the content capture flow. */
+    private final IContentCaptureService mContentCaptureServerInterface =
+            new IContentCaptureService.Stub() {
 
         @Override
         public void onConnected(IBinder callback, boolean verbose, boolean debug) {
@@ -187,10 +201,24 @@
         }
     };
 
-    /**
-     * Binder that receives calls from the app.
-     */
-    private final IContentCaptureDirectManager mClientInterface =
+    /** Binder that receives calls from the system server in the content protection flow. */
+    private final IContentProtectionService mContentProtectionServerInterface =
+            new IContentProtectionService.Stub() {
+
+                @Override
+                public void onLoginDetected(
+                        @SuppressWarnings("rawtypes") ParceledListSlice events) {
+                    mHandler.sendMessage(
+                            obtainMessage(
+                                    ContentCaptureService::handleOnLoginDetected,
+                                    ContentCaptureService.this,
+                                    Binder.getCallingUid(),
+                                    events));
+                }
+            };
+
+    /** Binder that receives calls from the app in the content capture flow. */
+    private final IContentCaptureDirectManager mContentCaptureClientInterface =
             new IContentCaptureDirectManager.Stub() {
 
         @Override
@@ -220,9 +248,19 @@
     @Override
     public final IBinder onBind(Intent intent) {
         if (SERVICE_INTERFACE.equals(intent.getAction())) {
-            return mServerInterface.asBinder();
+            return mContentCaptureServerInterface.asBinder();
         }
-        Log.w(TAG, "Tried to bind to wrong intent (should be " + SERVICE_INTERFACE + ": " + intent);
+        if (PROTECTION_SERVICE_INTERFACE.equals(intent.getAction())) {
+            return mContentProtectionServerInterface.asBinder();
+        }
+        Log.w(
+                TAG,
+                "Tried to bind to wrong intent (should be "
+                        + SERVICE_INTERFACE
+                        + " or "
+                        + PROTECTION_SERVICE_INTERFACE
+                        + "): "
+                        + intent);
         return null;
     }
 
@@ -456,7 +494,7 @@
         } else {
             stateFlags |= ContentCaptureSession.STATE_DISABLED;
         }
-        setClientState(clientReceiver, stateFlags, mClientInterface.asBinder());
+        setClientState(clientReceiver, stateFlags, mContentCaptureClientInterface.asBinder());
     }
 
     private void handleSendEvents(int uid,
@@ -524,6 +562,27 @@
         writeFlushMetrics(lastSessionId, activityComponent, metrics, options, reason);
     }
 
+    private void handleOnLoginDetected(
+            int uid, @NonNull ParceledListSlice<ContentCaptureEvent> parceledEvents) {
+        if (uid != Process.SYSTEM_UID) {
+            Log.e(TAG, "handleOnLoginDetected() not allowed for uid: " + uid);
+            return;
+        }
+        List<ContentCaptureEvent> events = parceledEvents.getList();
+        int sessionIdInt = events.isEmpty() ? NO_SESSION_ID : events.get(0).getSessionId();
+        ContentCaptureSessionId sessionId = new ContentCaptureSessionId(sessionIdInt);
+
+        ContentCaptureEvent startEvent =
+                new ContentCaptureEvent(sessionIdInt, TYPE_SESSION_RESUMED);
+        startEvent.setSelectionIndex(0, events.size());
+        onContentCaptureEvent(sessionId, startEvent);
+
+        events.forEach(event -> onContentCaptureEvent(sessionId, event));
+
+        ContentCaptureEvent endEvent = new ContentCaptureEvent(sessionIdInt, TYPE_SESSION_PAUSED);
+        onContentCaptureEvent(sessionId, endEvent);
+    }
+
     private void handleOnActivitySnapshot(int sessionId, @NonNull SnapshotData snapshotData) {
         onActivitySnapshot(new ContentCaptureSessionId(sessionId), snapshotData);
     }
diff --git a/core/java/android/service/contentcapture/ContentCaptureServiceInfo.java b/core/java/android/service/contentcapture/ContentCaptureServiceInfo.java
index fb60619..4aa5c00 100644
--- a/core/java/android/service/contentcapture/ContentCaptureServiceInfo.java
+++ b/core/java/android/service/contentcapture/ContentCaptureServiceInfo.java
@@ -48,7 +48,7 @@
  *
  * @hide
  */
-public final class ContentCaptureServiceInfo {
+public class ContentCaptureServiceInfo {
 
     private static final String TAG = ContentCaptureServiceInfo.class.getSimpleName();
     private static final String XML_TAG_SERVICE = "content-capture-service";
diff --git a/core/java/android/service/contentcapture/IContentProtectionService.aidl b/core/java/android/service/contentcapture/IContentProtectionService.aidl
new file mode 100644
index 0000000..4a13c3f
--- /dev/null
+++ b/core/java/android/service/contentcapture/IContentProtectionService.aidl
@@ -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 android.service.contentcapture;
+
+import android.content.pm.ParceledListSlice;
+import android.view.contentcapture.ContentCaptureEvent;
+
+/**
+ * Interface from the system server to the content protection service.
+ *
+ * @hide
+ */
+oneway interface IContentProtectionService {
+
+    void onLoginDetected(in ParceledListSlice events);
+}
diff --git a/core/java/android/service/credentials/CredentialProviderInfoFactory.java b/core/java/android/service/credentials/CredentialProviderInfoFactory.java
index b196b06..58bc4da 100644
--- a/core/java/android/service/credentials/CredentialProviderInfoFactory.java
+++ b/core/java/android/service/credentials/CredentialProviderInfoFactory.java
@@ -45,8 +45,8 @@
 import android.util.Slog;
 import android.util.Xml;
 
-import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.R;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -135,8 +135,8 @@
     }
 
     /**
-     * Constructs an information instance of the credential provider for testing purposes. Does not
-     * run any verifications and passes parameters as is.
+     * Constructs an information instance of the credential provider for testing purposes. Does
+     * not run any verifications and passes parameters as is.
      */
     @VisibleForTesting
     public static CredentialProviderInfo createForTests(
@@ -151,6 +151,7 @@
                 .setSystemProvider(isSystemProvider)
                 .addCapabilities(capabilities)
                 .build();
+
     }
 
     private static void verifyProviderPermission(ServiceInfo serviceInfo) throws SecurityException {
@@ -229,7 +230,7 @@
 
         // 4. Extract the XML metadata.
         try {
-            builder = extractXmlMetadata(context, builder, serviceInfo, pm, resources);
+            builder = extractXmlMetadata(context, serviceInfo, pm, resources);
         } catch (Exception e) {
             Slog.e(TAG, "Failed to get XML metadata", e);
         }
@@ -239,10 +240,11 @@
 
     private static CredentialProviderInfo.Builder extractXmlMetadata(
             @NonNull Context context,
-            @NonNull CredentialProviderInfo.Builder builder,
             @NonNull ServiceInfo serviceInfo,
             @NonNull PackageManager pm,
             @NonNull Resources resources) {
+        final CredentialProviderInfo.Builder builder =
+                new CredentialProviderInfo.Builder(serviceInfo);
         final XmlResourceParser parser =
                 serviceInfo.loadXmlMetaData(pm, CredentialProviderService.SERVICE_META_DATA);
         if (parser == null) {
@@ -285,9 +287,9 @@
         return builder;
     }
 
-    private static Set<String> parseXmlProviderOuterCapabilities(
+    private static List<String> parseXmlProviderOuterCapabilities(
             XmlPullParser parser, Resources resources) throws IOException, XmlPullParserException {
-        final Set<String> capabilities = new HashSet<>();
+        final List<String> capabilities = new ArrayList<>();
         final int outerDepth = parser.getDepth();
         int type;
         while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
diff --git a/core/java/android/service/credentials/CredentialProviderService.java b/core/java/android/service/credentials/CredentialProviderService.java
index cf2e6a6..be7b722 100644
--- a/core/java/android/service/credentials/CredentialProviderService.java
+++ b/core/java/android/service/credentials/CredentialProviderService.java
@@ -177,8 +177,8 @@
       *          <capability>@string/passwords</capability>
       *          <capability>@string/passkeys</capability>
       *      </capabilities>
-      *      <string name="passwords">android.credentials.TYPE_PASSWORD_CREDENTIAL</string>
-      *      <string name="passkeys">android.credentials.TYPE_PUBLIC_KEY_CREDENTIAL</string>
+      *      <capability name="android.credentials.TYPE_PASSWORD_CREDENTIAL" />
+      *      <capability name="android.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" />
       *  </credential-provider>
       * </code>
       */
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index cd57de5..9b19937 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -16,6 +16,8 @@
 
 package android.service.dreams;
 
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+
 import android.annotation.IdRes;
 import android.annotation.LayoutRes;
 import android.annotation.NonNull;
@@ -24,7 +26,6 @@
 import android.annotation.SdkConstant.SdkConstantType;
 import android.annotation.TestApi;
 import android.app.Activity;
-import android.app.ActivityTaskManager;
 import android.app.AlarmManager;
 import android.app.Service;
 import android.compat.annotation.UnsupportedAppUsage;
@@ -630,7 +631,7 @@
     }
 
     /**
-     * Marks this dream as windowless. Only available to doze dreams.
+     * Marks this dream as windowless. It should be called in {@link #onCreate} method.
      *
      * @hide
      *
@@ -640,7 +641,7 @@
     }
 
     /**
-     * Returns whether this dream is windowless. Only available to doze dreams.
+     * Returns whether this dream is windowless.
      *
      * @hide
      */
@@ -1230,8 +1231,10 @@
 
         mDreamToken = dreamToken;
         mCanDoze = canDoze;
-        if (mWindowless && !mCanDoze) {
-            throw new IllegalStateException("Only doze dreams can be windowless");
+        // This is not a security check to prevent malicious dreams but a guard rail to stop
+        // third-party dreams from being windowless and not working well as a result.
+        if (mWindowless && !mCanDoze && !isCallerSystemUi()) {
+            throw new IllegalStateException("Only doze or SystemUI dreams can be windowless.");
         }
 
         mDispatchAfterOnAttachedToWindow = () -> {
@@ -1264,9 +1267,7 @@
                     fetchDreamLabel(this, serviceInfo, isPreviewMode));
 
             try {
-                if (!ActivityTaskManager.getService().startDreamActivity(i)) {
-                    detach();
-                }
+                mDreamManager.startDreamActivity(i);
             } catch (SecurityException e) {
                 Log.w(mTag,
                         "Received SecurityException trying to start DreamActivity. "
@@ -1366,6 +1367,11 @@
         }
     }
 
+    private boolean isCallerSystemUi() {
+        return checkCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR_SERVICE)
+                == PERMISSION_GRANTED;
+    }
+
     private int applyFlags(int oldFlags, int flags, int mask) {
         return (oldFlags&~mask) | (flags&mask);
     }
diff --git a/core/java/android/service/dreams/IDreamManager.aidl b/core/java/android/service/dreams/IDreamManager.aidl
index 609425c..dd8b3de 100644
--- a/core/java/android/service/dreams/IDreamManager.aidl
+++ b/core/java/android/service/dreams/IDreamManager.aidl
@@ -17,6 +17,7 @@
 package android.service.dreams;
 
 import android.content.ComponentName;
+import android.content.Intent;
 import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
 import android.os.IBinder;
@@ -45,4 +46,5 @@
     void setDreamComponentsForUser(int userId, in ComponentName[] componentNames);
     void setSystemDreamComponent(in ComponentName componentName);
     void registerDreamOverlayService(in ComponentName componentName);
+    void startDreamActivity(in Intent intent);
 }
diff --git a/core/java/android/service/notification/NotificationRankingUpdate.java b/core/java/android/service/notification/NotificationRankingUpdate.java
index a853714..75640bd 100644
--- a/core/java/android/service/notification/NotificationRankingUpdate.java
+++ b/core/java/android/service/notification/NotificationRankingUpdate.java
@@ -16,32 +16,117 @@
 package android.service.notification;
 
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.annotation.TestApi;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.os.SharedMemory;
+import android.system.ErrnoException;
+import android.system.OsConstants;
+
+import androidx.annotation.NonNull;
+
+import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags;
+
+import java.nio.ByteBuffer;
 
 /**
+ * Represents an update to notification rankings.
  * @hide
  */
+@SuppressLint({"ParcelNotFinal", "ParcelCreator"})
+@TestApi
 public class NotificationRankingUpdate implements Parcelable {
     private final NotificationListenerService.RankingMap mRankingMap;
 
+    // The ranking map is stored in shared memory when parceled, for sending across the binder.
+    // This is done because the ranking map can grow large if there are many notifications.
+    private SharedMemory mRankingMapFd = null;
+    private final String mSharedMemoryName = "NotificationRankingUpdatedSharedMemory";
+
+    /**
+     * @hide
+     */
     public NotificationRankingUpdate(NotificationListenerService.Ranking[] rankings) {
         mRankingMap = new NotificationListenerService.RankingMap(rankings);
     }
 
+    /**
+     * @hide
+     */
     public NotificationRankingUpdate(Parcel in) {
-        mRankingMap = in.readParcelable(getClass().getClassLoader(), android.service.notification.NotificationListenerService.RankingMap.class);
+        if (SystemUiSystemPropertiesFlags.getResolver().isEnabled(
+                SystemUiSystemPropertiesFlags.NotificationFlags.RANKING_UPDATE_ASHMEM)) {
+            // Recover the ranking map from the SharedMemory and store it in mapParcel.
+            final Parcel mapParcel = Parcel.obtain();
+            ByteBuffer buffer = null;
+            try {
+                // The ranking map should be stored in shared memory when it is parceled, so we
+                // unwrap the SharedMemory object.
+                mRankingMapFd = in.readParcelable(getClass().getClassLoader(), SharedMemory.class);
+
+                // In the case that the ranking map can't be read, readParcelable may return null.
+                // In this case, we set mRankingMap to null;
+                if (mRankingMapFd == null) {
+                    mRankingMap = null;
+                    return;
+                }
+                // We only need read-only access to the shared memory region.
+                buffer = mRankingMapFd.mapReadOnly();
+                if (buffer == null) {
+                    mRankingMap = null;
+                    return;
+                }
+                byte[] payload = new byte[buffer.remaining()];
+                buffer.get(payload);
+                mapParcel.unmarshall(payload, 0, payload.length);
+                mapParcel.setDataPosition(0);
+
+                mRankingMap = mapParcel.readParcelable(getClass().getClassLoader(),
+                        android.service.notification.NotificationListenerService.RankingMap.class);
+            } catch (ErrnoException e) {
+                // TODO(b/284297289): remove throw when associated flag is moved to droidfood, to
+                // avoid crashes; change to Log.wtf.
+                throw new RuntimeException(e);
+            } finally {
+                mapParcel.recycle();
+                if (buffer != null) {
+                    mRankingMapFd.unmap(buffer);
+                }
+            }
+        } else {
+            mRankingMap = in.readParcelable(getClass().getClassLoader(),
+                    android.service.notification.NotificationListenerService.RankingMap.class);
+        }
     }
 
+    /**
+     * Confirms that the SharedMemory file descriptor is closed. Should only be used for testing.
+     * @hide
+     */
+    @TestApi
+    public final boolean isFdNotNullAndClosed() {
+        return mRankingMapFd != null && mRankingMapFd.getFd() == -1;
+    }
+
+    /**
+     * @hide
+     */
     public NotificationListenerService.RankingMap getRankingMap() {
         return mRankingMap;
     }
 
+    /**
+     * @hide
+     */
     @Override
     public int describeContents() {
         return 0;
     }
 
+    /**
+     * @hide
+     */
     @Override
     public boolean equals(@Nullable Object o) {
         if (this == o) return true;
@@ -51,11 +136,51 @@
         return mRankingMap.equals(other.mRankingMap);
     }
 
+    /**
+     * @hide
+     */
     @Override
-    public void writeToParcel(Parcel out, int flags) {
-        out.writeParcelable(mRankingMap, flags);
+    public void writeToParcel(@NonNull Parcel out, int flags) {
+        if (SystemUiSystemPropertiesFlags.getResolver().isEnabled(
+                SystemUiSystemPropertiesFlags.NotificationFlags.RANKING_UPDATE_ASHMEM)) {
+            final Parcel mapParcel = Parcel.obtain();
+            try {
+                // Parcels the ranking map and measures its size.
+                mapParcel.writeParcelable(mRankingMap, flags);
+                int mapSize = mapParcel.dataSize();
+
+                // Creates a new SharedMemory object with enough space to hold the ranking map.
+                SharedMemory mRankingMapFd = SharedMemory.create(mSharedMemoryName, mapSize);
+                if (mRankingMapFd == null) {
+                    return;
+                }
+
+                // Gets a read/write buffer mapping the entire shared memory region.
+                final ByteBuffer buffer = mRankingMapFd.mapReadWrite();
+
+                // Puts the ranking map into the shared memory region buffer.
+                buffer.put(mapParcel.marshall(), 0, mapSize);
+
+                // Protects the region from being written to, by setting it to be read-only.
+                mRankingMapFd.setProtect(OsConstants.PROT_READ);
+
+                // Puts the SharedMemory object in the parcel.
+                out.writeParcelable(mRankingMapFd, flags);
+            } catch (ErrnoException e) {
+                // TODO(b/284297289): remove throw when associated flag is moved to droidfood, to
+                // avoid crashes; change to Log.wtf.
+                throw new RuntimeException(e);
+            } finally {
+                mapParcel.recycle();
+            }
+        } else {
+            out.writeParcelable(mRankingMap, flags);
+        }
     }
 
+    /**
+    * @hide
+    */
     public static final @android.annotation.NonNull Parcelable.Creator<NotificationRankingUpdate> CREATOR
             = new Parcelable.Creator<NotificationRankingUpdate>() {
         public NotificationRankingUpdate createFromParcel(Parcel parcel) {
diff --git a/core/java/android/service/trust/OWNERS b/core/java/android/service/trust/OWNERS
index 198925b..a895f7f 100644
--- a/core/java/android/service/trust/OWNERS
+++ b/core/java/android/service/trust/OWNERS
@@ -1,5 +1,4 @@
 # Bug component: 36824
 
 cbrubaker@google.com
-vishwath@google.com
-jacobhobbie@google.com
\ No newline at end of file
+jacobhobbie@google.com
diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
index 708ebdf..9d28334 100644
--- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java
+++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
@@ -577,8 +577,7 @@
         }
 
         /**
-         * Timestamp of when the trigger event from SoundTriggerHal was received by the system
-         * server.
+         * Timestamp of when the trigger event from SoundTriggerHal was received by the framework.
          *
          * Clock monotonic including suspend time or its equivalent on the system,
          * in the same units and timebase as {@link SystemClock#elapsedRealtime()}.
diff --git a/core/java/android/service/wallpaper/IWallpaperService.aidl b/core/java/android/service/wallpaper/IWallpaperService.aidl
index da56b4b..3262f3a 100644
--- a/core/java/android/service/wallpaper/IWallpaperService.aidl
+++ b/core/java/android/service/wallpaper/IWallpaperService.aidl
@@ -16,6 +16,7 @@
 
 package android.service.wallpaper;
 
+import android.app.WallpaperInfo;
 import android.graphics.Rect;
 import android.service.wallpaper.IWallpaperConnection;
 
@@ -25,6 +26,7 @@
 oneway interface IWallpaperService {
     void attach(IWallpaperConnection connection,
             IBinder windowToken, int windowType, boolean isPreview,
-            int reqWidth, int reqHeight, in Rect padding, int displayId, int which);
+            int reqWidth, int reqHeight, in Rect padding, int displayId, int which,
+            in WallpaperInfo info);
     void detach(IBinder windowToken);
 }
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index d9ac4850..e47fab4 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -1611,8 +1611,13 @@
             if (!mDestroyed) {
                 mDisplayState =
                         mDisplay == null ? Display.STATE_UNKNOWN : mDisplay.getCommittedState();
-                boolean displayVisible = Display.isOnState(mDisplayState) && !mIsScreenTurningOn;
-                boolean visible = mVisible && displayVisible;
+                boolean displayFullyOn = Display.isOnState(mDisplayState) && !mIsScreenTurningOn;
+                boolean supportsAmbientMode =
+                        mIWallpaperEngine.mInfo == null
+                                ? false
+                                : mIWallpaperEngine.mInfo.supportsAmbientMode();
+                // Report visibility only if display is fully on or wallpaper supports ambient mode.
+                boolean visible = mVisible && (displayFullyOn || supportsAmbientMode);
                 if (DEBUG) {
                     Log.v(
                             TAG,
@@ -2065,7 +2070,7 @@
         }
 
         private void updateFrozenState(boolean frozenRequested) {
-            if (mIWallpaperEngine.mWallpaperManager.getWallpaperInfo() == null
+            if (mIWallpaperEngine.mInfo == null
                     // Procees the unfreeze command in case the wallaper became static while
                     // being paused.
                     && frozenRequested) {
@@ -2364,6 +2369,7 @@
         final DisplayManager mDisplayManager;
         final Display mDisplay;
         final WallpaperManager mWallpaperManager;
+        @Nullable final WallpaperInfo mInfo;
 
         Engine mEngine;
         @SetWallpaperFlags int mWhich;
@@ -2371,7 +2377,7 @@
         IWallpaperEngineWrapper(WallpaperService service,
                 IWallpaperConnection conn, IBinder windowToken,
                 int windowType, boolean isPreview, int reqWidth, int reqHeight, Rect padding,
-                int displayId, @SetWallpaperFlags int which) {
+                int displayId, @SetWallpaperFlags int which, @Nullable WallpaperInfo info) {
             mWallpaperManager = getSystemService(WallpaperManager.class);
             mCaller = new HandlerCaller(service, service.onProvideEngineLooper(), this, true);
             mConnection = conn;
@@ -2383,6 +2389,7 @@
             mDisplayPadding.set(padding);
             mDisplayId = displayId;
             mWhich = which;
+            mInfo = info;
 
             // Create a display context before onCreateEngine.
             mDisplayManager = getSystemService(DisplayManager.class);
@@ -2464,8 +2471,7 @@
                 Trace.beginSection("WPMS.mConnection.engineShown");
                 try {
                     mConnection.engineShown(this);
-                    Log.d(TAG, "Wallpaper has updated the surface:"
-                            + mWallpaperManager.getWallpaperInfo());
+                    Log.d(TAG, "Wallpaper has updated the surface:" + mInfo);
                 } catch (RemoteException e) {
                     Log.w(TAG, "Wallpaper host disappeared", e);
                 }
@@ -2719,11 +2725,11 @@
         @Override
         public void attach(IWallpaperConnection conn, IBinder windowToken,
                 int windowType, boolean isPreview, int reqWidth, int reqHeight, Rect padding,
-                int displayId, @SetWallpaperFlags int which) {
+                int displayId, @SetWallpaperFlags int which, @Nullable WallpaperInfo info) {
             Trace.beginSection("WPMS.ServiceWrapper.attach");
             IWallpaperEngineWrapper engineWrapper =
                     new IWallpaperEngineWrapper(mTarget, conn, windowToken, windowType,
-                            isPreview, reqWidth, reqHeight, padding, displayId, which);
+                            isPreview, reqWidth, reqHeight, padding, displayId, which, info);
             synchronized (mActiveEngines) {
                 mActiveEngines.put(windowToken, engineWrapper);
             }
diff --git a/core/java/android/service/wallpapereffectsgeneration/OWNERS b/core/java/android/service/wallpapereffectsgeneration/OWNERS
index d2d3e2c0..a351032 100644
--- a/core/java/android/service/wallpapereffectsgeneration/OWNERS
+++ b/core/java/android/service/wallpapereffectsgeneration/OWNERS
@@ -1,4 +1,3 @@
 susharon@google.com
 shanh@google.com
-huiwu@google.com
 srazdan@google.com
diff --git a/core/java/android/telephony/TelephonyRegistryManager.java b/core/java/android/telephony/TelephonyRegistryManager.java
index 434b1c7..e2c5539 100644
--- a/core/java/android/telephony/TelephonyRegistryManager.java
+++ b/core/java/android/telephony/TelephonyRegistryManager.java
@@ -490,13 +490,16 @@
     /**
      * 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
      * TelephonyManager#DATA_ACTIVITY_IN}
      */
-    public void notifyDataActivityChanged(int subId, @DataActivityType int dataActivityType) {
+    public void notifyDataActivityChanged(int slotIndex, int subId,
+            @DataActivityType int dataActivityType) {
         try {
-            sRegistry.notifyDataActivityForSubscriber(subId, dataActivityType);
+            sRegistry.notifyDataActivityForSubscriber(slotIndex, subId, dataActivityType);
         } catch (RemoteException ex) {
             // system process is dead
             throw ex.rethrowFromSystemServer();
diff --git a/core/java/android/text/DynamicLayout.java b/core/java/android/text/DynamicLayout.java
index cd76754..ba08f25 100644
--- a/core/java/android/text/DynamicLayout.java
+++ b/core/java/android/text/DynamicLayout.java
@@ -23,6 +23,7 @@
 import android.compat.annotation.UnsupportedAppUsage;
 import android.graphics.Paint;
 import android.graphics.Rect;
+import android.graphics.text.LineBreakConfig;
 import android.os.Build;
 import android.text.method.OffsetMapping;
 import android.text.style.ReplacementSpan;
@@ -88,6 +89,7 @@
             b.mBreakStrategy = Layout.BREAK_STRATEGY_SIMPLE;
             b.mHyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE;
             b.mJustificationMode = Layout.JUSTIFICATION_MODE_NONE;
+            b.mLineBreakConfig = LineBreakConfig.NONE;
             return b;
         }
 
@@ -268,6 +270,22 @@
         }
 
         /**
+         * Set the line break configuration. The line break will be passed to native used for
+         * calculating the text wrapping. The default value of the line break style is
+         * {@link LineBreakConfig#LINE_BREAK_STYLE_NONE}
+         *
+         * @param lineBreakConfig the line break configuration for text wrapping.
+         * @return this builder, useful for chaining.
+         * @see android.widget.TextView#setLineBreakStyle
+         * @see android.widget.TextView#setLineBreakWordStyle
+         */
+        @NonNull
+        public Builder setLineBreakConfig(@NonNull LineBreakConfig lineBreakConfig) {
+            mLineBreakConfig = lineBreakConfig;
+            return this;
+        }
+
+        /**
          * Build the {@link DynamicLayout} after options have been set.
          *
          * <p>Note: the builder object must not be reused in any way after calling this method.
@@ -298,6 +316,7 @@
         private int mJustificationMode;
         private TextUtils.TruncateAt mEllipsize;
         private int mEllipsizedWidth;
+        private LineBreakConfig mLineBreakConfig = LineBreakConfig.NONE;
 
         private final Paint.FontMetricsInt mFontMetricsInt = new Paint.FontMetricsInt();
 
@@ -344,7 +363,7 @@
         this(base, display, paint, width, align, TextDirectionHeuristics.FIRSTSTRONG_LTR,
                 spacingmult, spacingadd, includepad,
                 Layout.BREAK_STRATEGY_SIMPLE, Layout.HYPHENATION_FREQUENCY_NONE,
-                Layout.JUSTIFICATION_MODE_NONE, ellipsize, ellipsizedWidth);
+                Layout.JUSTIFICATION_MODE_NONE, LineBreakConfig.NONE, ellipsize, ellipsizedWidth);
     }
 
     /**
@@ -365,6 +384,7 @@
                          boolean includepad, @BreakStrategy int breakStrategy,
                          @HyphenationFrequency int hyphenationFrequency,
                          @JustificationMode int justificationMode,
+                         @NonNull LineBreakConfig lineBreakConfig,
                          @Nullable TextUtils.TruncateAt ellipsize,
                          @IntRange(from = 0) int ellipsizedWidth) {
         super(createEllipsizer(ellipsize, display),
@@ -381,6 +401,7 @@
         mBreakStrategy = breakStrategy;
         mJustificationMode = justificationMode;
         mHyphenationFrequency = hyphenationFrequency;
+        mLineBreakConfig = lineBreakConfig;
 
         generate(b);
 
@@ -396,6 +417,7 @@
         mBreakStrategy = b.mBreakStrategy;
         mJustificationMode = b.mJustificationMode;
         mHyphenationFrequency = b.mHyphenationFrequency;
+        mLineBreakConfig = b.mLineBreakConfig;
 
         generate(b);
     }
@@ -608,6 +630,7 @@
                 .setBreakStrategy(mBreakStrategy)
                 .setHyphenationFrequency(mHyphenationFrequency)
                 .setJustificationMode(mJustificationMode)
+                .setLineBreakConfig(mLineBreakConfig)
                 .setAddLastLineLineSpacing(!islast);
 
         reflowed.generate(b, false /*includepad*/, true /*trackpad*/);
@@ -1209,6 +1232,18 @@
         return mInts.getValue(line, ELLIPSIS_COUNT);
     }
 
+    /**
+     * Gets the {@link LineBreakconfig} used in this DynamicLayout.
+     * Use this only to consult the LineBreakConfig's properties and not
+     * to change them.
+     *
+     * @return The line break config in this DynamicLayout.
+     */
+    @NonNull
+    public LineBreakConfig getLineBreakConfig() {
+        return mLineBreakConfig;
+    }
+
     private CharSequence mBase;
     private CharSequence mDisplay;
     private ChangeWatcher mWatcher;
@@ -1220,6 +1255,7 @@
     private int mBreakStrategy;
     private int mHyphenationFrequency;
     private int mJustificationMode;
+    private LineBreakConfig mLineBreakConfig;
 
     private PackedIntVector mInts;
     private PackedObjectVector<Directions> mObjects;
diff --git a/core/java/android/text/MeasuredParagraph.java b/core/java/android/text/MeasuredParagraph.java
index bedd409..5b4f195 100644
--- a/core/java/android/text/MeasuredParagraph.java
+++ b/core/java/android/text/MeasuredParagraph.java
@@ -20,6 +20,9 @@
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.Px;
+import android.annotation.SuppressLint;
+import android.annotation.TestApi;
 import android.graphics.Paint;
 import android.graphics.Rect;
 import android.graphics.text.LineBreakConfig;
@@ -28,6 +31,7 @@
 import android.text.AutoGrowArray.FloatArray;
 import android.text.AutoGrowArray.IntArray;
 import android.text.Layout.Directions;
+import android.text.style.LineBreakConfigSpan;
 import android.text.style.MetricAffectingSpan;
 import android.text.style.ReplacementSpan;
 import android.util.Pools.SynchronizedPool;
@@ -57,6 +61,7 @@
  * MeasuredParagraph is NOT a thread safe object.
  * @hide
  */
+@TestApi
 public class MeasuredParagraph {
     private static final char OBJECT_REPLACEMENT_CHARACTER = '\uFFFC';
 
@@ -73,6 +78,7 @@
      * Recycle the MeasuredParagraph.
      *
      * Do not call any methods after you call this method.
+     * @hide
      */
     public void recycle() {
         release();
@@ -126,11 +132,14 @@
     private @Nullable MeasuredText mMeasuredText;
 
     // Following three objects are for avoiding object allocation.
-    private @NonNull TextPaint mCachedPaint = new TextPaint();
+    private final @NonNull TextPaint mCachedPaint = new TextPaint();
     private @Nullable Paint.FontMetricsInt mCachedFm;
+    private final @NonNull LineBreakConfig.Builder mLineBreakConfigBuilder =
+            new LineBreakConfig.Builder();
 
     /**
      * Releases internal buffers.
+     * @hide
      */
     public void release() {
         reset();
@@ -158,6 +167,7 @@
      * Returns the length of the paragraph.
      *
      * This is always available.
+     * @hide
      */
     public int getTextLength() {
         return mTextLength;
@@ -167,6 +177,7 @@
      * Returns the characters to be measured.
      *
      * This is always available.
+     * @hide
      */
     public @NonNull char[] getChars() {
         return mCopiedBuffer;
@@ -176,6 +187,7 @@
      * Returns the paragraph direction.
      *
      * This is always available.
+     * @hide
      */
     public @Layout.Direction int getParagraphDir() {
         return mParaDir;
@@ -185,6 +197,7 @@
      * Returns the directions.
      *
      * This is always available.
+     * @hide
      */
     public Directions getDirections(@IntRange(from = 0) int start,  // inclusive
                                     @IntRange(from = 0) int end) {  // exclusive
@@ -202,6 +215,7 @@
      *
      * This is available only if the MeasuredParagraph is computed with buildForMeasurement.
      * Returns 0 in other cases.
+     * @hide
      */
     public @FloatRange(from = 0.0f) float getWholeWidth() {
         return mWholeWidth;
@@ -212,6 +226,7 @@
      *
      * This is available only if the MeasuredParagraph is computed with buildForMeasurement.
      * Returns empty array in other cases.
+     * @hide
      */
     public @NonNull FloatArray getWidths() {
         return mWidths;
@@ -224,6 +239,7 @@
      *
      * This is available only if the MeasuredParagraph is computed with buildForStaticLayout.
      * Returns empty array in other cases.
+     * @hide
      */
     public @NonNull IntArray getSpanEndCache() {
         return mSpanEndCache;
@@ -236,6 +252,7 @@
      *
      * This is available only if the MeasuredParagraph is computed with buildForStaticLayout.
      * Returns empty array in other cases.
+     * @hide
      */
     public @NonNull IntArray getFontMetrics() {
         return mFontMetrics;
@@ -246,6 +263,7 @@
      *
      * This is available only if the MeasuredParagraph is computed with buildForStaticLayout.
      * Returns null in other cases.
+     * @hide
      */
     public MeasuredText getMeasuredText() {
         return mMeasuredText;
@@ -259,6 +277,7 @@
      *
      * @param start the inclusive start offset of the target region in the text
      * @param end the exclusive end offset of the target region in the text
+     * @hide
      */
     public float getWidth(int start, int end) {
         if (mMeasuredText == null) {
@@ -280,6 +299,7 @@
      * at (0, 0).
      *
      * This is available only if the MeasuredParagraph is computed with buildForStaticLayout.
+     * @hide
      */
     public void getBounds(@IntRange(from = 0) int start, @IntRange(from = 0) int end,
             @NonNull Rect bounds) {
@@ -290,6 +310,7 @@
      * Retrieves the font metrics for the given range.
      *
      * This is available only if the MeasuredParagraph is computed with buildForStaticLayout.
+     * @hide
      */
     public void getFontMetricsInt(@IntRange(from = 0) int start, @IntRange(from = 0) int end,
             @NonNull Paint.FontMetricsInt fmi) {
@@ -300,6 +321,7 @@
      * Returns a width of the character at the offset.
      *
      * This is available only if the MeasuredParagraph is computed with buildForStaticLayout.
+     * @hide
      */
     public float getCharWidthAt(@IntRange(from = 0) int offset) {
         return mMeasuredText.getCharWidthAt(offset);
@@ -318,6 +340,7 @@
      * @param recycle pass existing MeasuredParagraph if you want to recycle it.
      *
      * @return measured text
+     * @hide
      */
     public static @NonNull MeasuredParagraph buildForBidi(@NonNull CharSequence text,
                                                      @IntRange(from = 0) int start,
@@ -343,6 +366,7 @@
      * @param recycle pass existing MeasuredParagraph if you want to recycle it.
      *
      * @return measured text
+     * @hide
      */
     public static @NonNull MeasuredParagraph buildForMeasurement(@NonNull TextPaint paint,
                                                             @NonNull CharSequence text,
@@ -361,25 +385,53 @@
         if (mt.mSpanned == null) {
             // No style change by MetricsAffectingSpan. Just measure all text.
             mt.applyMetricsAffectingSpan(
-                    paint, null /* lineBreakConfig */, null /* spans */, start, end,
-                    null /* native builder ptr */);
+                    paint, null /* lineBreakConfig */, null /* spans */, null /* lbcSpans */,
+                    start, end, null /* native builder ptr */, null);
         } else {
             // There may be a MetricsAffectingSpan. Split into span transitions and apply styles.
             int spanEnd;
             for (int spanStart = start; spanStart < end; spanStart = spanEnd) {
-                spanEnd = mt.mSpanned.nextSpanTransition(spanStart, end, MetricAffectingSpan.class);
+                int maSpanEnd = mt.mSpanned.nextSpanTransition(spanStart, end,
+                        MetricAffectingSpan.class);
+                int lbcSpanEnd = mt.mSpanned.nextSpanTransition(spanStart, end,
+                        LineBreakConfigSpan.class);
+                spanEnd = Math.min(maSpanEnd, lbcSpanEnd);
                 MetricAffectingSpan[] spans = mt.mSpanned.getSpans(spanStart, spanEnd,
                         MetricAffectingSpan.class);
+                LineBreakConfigSpan[] lbcSpans = mt.mSpanned.getSpans(spanStart, spanEnd,
+                        LineBreakConfigSpan.class);
                 spans = TextUtils.removeEmptySpans(spans, mt.mSpanned, MetricAffectingSpan.class);
+                lbcSpans = TextUtils.removeEmptySpans(lbcSpans, mt.mSpanned,
+                        LineBreakConfigSpan.class);
                 mt.applyMetricsAffectingSpan(
-                        paint, null /* line break config */, spans, spanStart, spanEnd,
-                        null /* native builder ptr */);
+                        paint, null /* line break config */, spans, lbcSpans, spanStart, spanEnd,
+                        null /* native builder ptr */, null);
             }
         }
         return mt;
     }
 
     /**
+     * A test interface for observing the style run calculation.
+     * @hide
+     */
+    @TestApi
+    public interface StyleRunCallback {
+        /**
+         * Called when a single style run is identified.
+         */
+        void onAppendStyleRun(@NonNull Paint paint,
+                @Nullable LineBreakConfig lineBreakConfig, @IntRange(from = 0) int length,
+                boolean isRtl);
+
+        /**
+         * Called when a single replacement run is identified.
+         */
+        void onAppendReplacementRun(@NonNull Paint paint,
+                @IntRange(from = 0) int length, @Px @FloatRange(from = 0) float width);
+    }
+
+    /**
      * Generates new MeasuredParagraph for StaticLayout.
      *
      * If recycle is null, this returns new instance. If recycle is not null, this fills computed
@@ -397,6 +449,7 @@
      * @param recycle pass existing MeasuredParagraph if you want to recycle it.
      *
      * @return measured text
+     * @hide
      */
     public static @NonNull MeasuredParagraph buildForStaticLayout(
             @NonNull TextPaint paint,
@@ -409,6 +462,57 @@
             boolean computeLayout,
             @Nullable MeasuredParagraph hint,
             @Nullable MeasuredParagraph recycle) {
+        return buildForStaticLayoutInternal(paint, lineBreakConfig, text, start, end, textDir,
+                hyphenationMode, computeLayout, hint, recycle, null);
+    }
+
+    /**
+     * Generates new MeasuredParagraph for StaticLayout.
+     *
+     * If recycle is null, this returns new instance. If recycle is not null, this fills computed
+     * result to recycle and returns recycle.
+     *
+     * @param paint the paint to be used for rendering the text.
+     * @param lineBreakConfig the line break configuration for text wrapping.
+     * @param text the character sequence to be measured
+     * @param start the inclusive start offset of the target region in the text
+     * @param end the exclusive end offset of the target region in the text
+     * @param textDir the text direction
+     * @param hyphenationMode a hyphenation mode
+     * @param computeLayout true if need to compute full layout, otherwise false.
+     *
+     * @return measured text
+     * @hide
+     */
+    @SuppressLint("ExecutorRegistration")
+    @TestApi
+    @NonNull
+    public static MeasuredParagraph buildForStaticLayoutTest(
+            @NonNull TextPaint paint,
+            @Nullable LineBreakConfig lineBreakConfig,
+            @NonNull CharSequence text,
+            @IntRange(from = 0) int start,
+            @IntRange(from = 0) int end,
+            @NonNull TextDirectionHeuristic textDir,
+            int hyphenationMode,
+            boolean computeLayout,
+            @Nullable StyleRunCallback testCallback) {
+        return buildForStaticLayoutInternal(paint, lineBreakConfig, text, start, end, textDir,
+                hyphenationMode, computeLayout, null, null, testCallback);
+    }
+
+    private static @NonNull MeasuredParagraph buildForStaticLayoutInternal(
+            @NonNull TextPaint paint,
+            @Nullable LineBreakConfig lineBreakConfig,
+            @NonNull CharSequence text,
+            @IntRange(from = 0) int start,
+            @IntRange(from = 0) int end,
+            @NonNull TextDirectionHeuristic textDir,
+            int hyphenationMode,
+            boolean computeLayout,
+            @Nullable MeasuredParagraph hint,
+            @Nullable MeasuredParagraph recycle,
+            @Nullable StyleRunCallback testCallback) {
         final MeasuredParagraph mt = recycle == null ? obtain() : recycle;
         mt.resetAndAnalyzeBidi(text, start, end, textDir);
         final MeasuredText.Builder builder;
@@ -426,23 +530,29 @@
         } else {
             if (mt.mSpanned == null) {
                 // No style change by MetricsAffectingSpan. Just measure all text.
-                mt.applyMetricsAffectingSpan(paint, lineBreakConfig, null /* spans */, start, end,
-                        builder);
+                mt.applyMetricsAffectingSpan(paint, lineBreakConfig, null /* spans */, null,
+                        start, end, builder, testCallback);
                 mt.mSpanEndCache.append(end);
             } else {
                 // There may be a MetricsAffectingSpan. Split into span transitions and apply
                 // styles.
                 int spanEnd;
                 for (int spanStart = start; spanStart < end; spanStart = spanEnd) {
-                    spanEnd = mt.mSpanned.nextSpanTransition(spanStart, end,
+                    int maSpanEnd = mt.mSpanned.nextSpanTransition(spanStart, end,
                                                              MetricAffectingSpan.class);
+                    int lbcSpanEnd = mt.mSpanned.nextSpanTransition(spanStart, end,
+                            LineBreakConfigSpan.class);
+                    spanEnd = Math.min(maSpanEnd, lbcSpanEnd);
                     MetricAffectingSpan[] spans = mt.mSpanned.getSpans(spanStart, spanEnd,
                             MetricAffectingSpan.class);
+                    LineBreakConfigSpan[] lbcSpans = mt.mSpanned.getSpans(spanStart, spanEnd,
+                            LineBreakConfigSpan.class);
                     spans = TextUtils.removeEmptySpans(spans, mt.mSpanned,
                                                        MetricAffectingSpan.class);
-                    // TODO: Update line break config with spans.
-                    mt.applyMetricsAffectingSpan(paint, lineBreakConfig, spans, spanStart, spanEnd,
-                            builder);
+                    lbcSpans = TextUtils.removeEmptySpans(lbcSpans, mt.mSpanned,
+                                                       LineBreakConfigSpan.class);
+                    mt.applyMetricsAffectingSpan(paint, lineBreakConfig, spans, lbcSpans, spanStart,
+                            spanEnd, builder, testCallback);
                     mt.mSpanEndCache.append(spanEnd);
                 }
             }
@@ -519,7 +629,8 @@
                                      @IntRange(from = 0) int start,  // inclusive, in copied buffer
                                      @IntRange(from = 0) int end,  // exclusive, in copied buffer
                                      @NonNull TextPaint paint,
-                                     @Nullable MeasuredText.Builder builder) {
+                                     @Nullable MeasuredText.Builder builder,
+                                     @Nullable StyleRunCallback testCallback) {
         // Use original text. Shouldn't matter.
         // TODO: passing uninitizlied FontMetrics to developers. Do we need to keep this for
         //       backward compatibility? or Should we initialize them for getFontMetricsInt?
@@ -535,13 +646,17 @@
         } else {
             builder.appendReplacementRun(paint, end - start, width);
         }
+        if (testCallback != null) {
+            testCallback.onAppendReplacementRun(paint, end - start, width);
+        }
     }
 
     private void applyStyleRun(@IntRange(from = 0) int start,  // inclusive, in copied buffer
                                @IntRange(from = 0) int end,  // exclusive, in copied buffer
                                @NonNull TextPaint paint,
                                @Nullable LineBreakConfig config,
-                               @Nullable MeasuredText.Builder builder) {
+                               @Nullable MeasuredText.Builder builder,
+                               @Nullable StyleRunCallback testCallback) {
 
         if (mLtrWithoutBidi) {
             // If the whole text is LTR direction, just apply whole region.
@@ -552,6 +667,9 @@
             } else {
                 builder.appendStyleRun(paint, config, end - start, false /* isRtl */);
             }
+            if (testCallback != null) {
+                testCallback.onAppendStyleRun(paint, config, end - start, false);
+            }
         } else {
             // If there is multiple bidi levels, split into individual bidi level and apply style.
             byte level = mLevels.get(start);
@@ -568,6 +686,9 @@
                     } else {
                         builder.appendStyleRun(paint, config, levelEnd - levelStart, isRtl);
                     }
+                    if (testCallback != null) {
+                        testCallback.onAppendStyleRun(paint, config, levelEnd - levelStart, isRtl);
+                    }
                     if (levelEnd == end) {
                         break;
                     }
@@ -582,9 +703,11 @@
             @NonNull TextPaint paint,
             @Nullable LineBreakConfig lineBreakConfig,
             @Nullable MetricAffectingSpan[] spans,
+            @Nullable LineBreakConfigSpan[] lbcSpans,
             @IntRange(from = 0) int start,  // inclusive, in original text buffer
             @IntRange(from = 0) int end,  // exclusive, in original text buffer
-            @Nullable MeasuredText.Builder builder) {
+            @Nullable MeasuredText.Builder builder,
+            @Nullable StyleRunCallback testCallback) {
         mCachedPaint.set(paint);
         // XXX paint should not have a baseline shift, but...
         mCachedPaint.baselineShift = 0;
@@ -609,6 +732,14 @@
             }
         }
 
+        if (lbcSpans != null) {
+            mLineBreakConfigBuilder.reset(lineBreakConfig);
+            for (LineBreakConfigSpan lbcSpan : lbcSpans) {
+                mLineBreakConfigBuilder.merge(lbcSpan.getLineBreakConfig());
+            }
+            lineBreakConfig = mLineBreakConfigBuilder.build();
+        }
+
         final int startInCopiedBuffer = start - mTextStart;
         final int endInCopiedBuffer = end - mTextStart;
 
@@ -618,10 +749,10 @@
 
         if (replacement != null) {
             applyReplacementRun(replacement, startInCopiedBuffer, endInCopiedBuffer, mCachedPaint,
-                    builder);
+                    builder, testCallback);
         } else {
             applyStyleRun(startInCopiedBuffer, endInCopiedBuffer, mCachedPaint,
-                    lineBreakConfig, builder);
+                    lineBreakConfig, builder, testCallback);
         }
 
         if (needFontMetrics) {
@@ -690,6 +821,7 @@
 
     /**
      * This only works if the MeasuredParagraph is computed with buildForStaticLayout.
+     * @hide
      */
     public @IntRange(from = 0) int getMemoryUsage() {
         return mMeasuredText.getMemoryUsage();
diff --git a/core/java/android/text/PrecomputedText.java b/core/java/android/text/PrecomputedText.java
index f31a690..fd97801 100644
--- a/core/java/android/text/PrecomputedText.java
+++ b/core/java/android/text/PrecomputedText.java
@@ -329,22 +329,17 @@
         @Override
         public int hashCode() {
             // TODO: implement MinikinPaint::hashCode and use it to keep consistency with equals.
-            int lineBreakStyle = (mLineBreakConfig != null)
-                    ? mLineBreakConfig.getLineBreakStyle() : LineBreakConfig.LINE_BREAK_STYLE_NONE;
             return Objects.hash(mPaint.getTextSize(), mPaint.getTextScaleX(), mPaint.getTextSkewX(),
                     mPaint.getLetterSpacing(), mPaint.getWordSpacing(), mPaint.getFlags(),
                     mPaint.getTextLocales(), mPaint.getTypeface(),
                     mPaint.getFontVariationSettings(), mPaint.isElegantTextHeight(), mTextDir,
-                    mBreakStrategy, mHyphenationFrequency, lineBreakStyle);
+                    mBreakStrategy, mHyphenationFrequency,
+                    LineBreakConfig.getResolvedLineBreakStyle(mLineBreakConfig),
+                    LineBreakConfig.getResolvedLineBreakWordStyle(mLineBreakConfig));
         }
 
         @Override
         public String toString() {
-            int lineBreakStyle = (mLineBreakConfig != null)
-                    ? mLineBreakConfig.getLineBreakStyle() : LineBreakConfig.LINE_BREAK_STYLE_NONE;
-            int lineBreakWordStyle = (mLineBreakConfig != null)
-                    ? mLineBreakConfig.getLineBreakWordStyle()
-                            : LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE;
             return "{"
                 + "textSize=" + mPaint.getTextSize()
                 + ", textScaleX=" + mPaint.getTextScaleX()
@@ -357,8 +352,9 @@
                 + ", textDir=" + mTextDir
                 + ", breakStrategy=" + mBreakStrategy
                 + ", hyphenationFrequency=" + mHyphenationFrequency
-                + ", lineBreakStyle=" + lineBreakStyle
-                + ", lineBreakWordStyle=" + lineBreakWordStyle
+                + ", lineBreakStyle=" + LineBreakConfig.getResolvedLineBreakStyle(mLineBreakConfig)
+                + ", lineBreakWordStyle="
+                    + LineBreakConfig.getResolvedLineBreakWordStyle(mLineBreakConfig)
                 + "}";
         }
     };
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index 6371da4..ab9cff0 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -25,7 +25,6 @@
 import android.graphics.text.LineBreakConfig;
 import android.graphics.text.LineBreaker;
 import android.os.Build;
-import android.os.SystemProperties;
 import android.text.style.LeadingMarginSpan;
 import android.text.style.LeadingMarginSpan.LeadingMarginSpan2;
 import android.text.style.LineHeightSpan;
@@ -33,7 +32,6 @@
 import android.util.Log;
 import android.util.Pools.SynchronizedPool;
 
-import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.GrowingArrayUtils;
 
@@ -75,13 +73,6 @@
      * default values.
      */
     public final static class Builder {
-        // The content length threshold to enable LINE_BREAK_WORD_STYLE_PHRASE.
-        private static final int DEFAULT_LINECOUNT_THRESHOLD_FOR_PHRASE = 3;
-
-        // The property of content length threshold to enable LINE_BREAK_WORD_STYLE_PHRASE.
-        private static final String PROPERTY_LINECOUNT_THRESHOLD_FOR_PHRASE =
-                "android.phrase.linecount.threshold";
-
         private Builder() {}
 
         /**
@@ -440,55 +431,11 @@
          */
         @NonNull
         public StaticLayout build() {
-            reviseLineBreakConfig();
             StaticLayout result = new StaticLayout(this);
             Builder.recycle(this);
             return result;
         }
 
-        private void reviseLineBreakConfig() {
-            boolean autoPhraseBreaking = mLineBreakConfig.getAutoPhraseBreaking();
-            int wordStyle = mLineBreakConfig.getLineBreakWordStyle();
-            if (autoPhraseBreaking) {
-                if (wordStyle != LineBreakConfig.LINE_BREAK_WORD_STYLE_PHRASE) {
-                    if (shouldEnablePhraseBreaking()) {
-                        mLineBreakConfig = LineBreakConfig.getLineBreakConfig(
-                                mLineBreakConfig.getLineBreakStyle(),
-                                LineBreakConfig.LINE_BREAK_WORD_STYLE_PHRASE,
-                                mLineBreakConfig.getAutoPhraseBreaking());
-                    }
-                }
-            }
-        }
-
-        private boolean shouldEnablePhraseBreaking() {
-            if (TextUtils.isEmpty(mText) || mWidth <= 0) {
-                return false;
-            }
-            int lineLimit = SystemProperties.getInt(
-                    PROPERTY_LINECOUNT_THRESHOLD_FOR_PHRASE,
-                    DEFAULT_LINECOUNT_THRESHOLD_FOR_PHRASE);
-            double desiredWidth = (double) Layout.getDesiredWidth(mText, mStart,
-                    mEnd, mPaint, mTextDir);
-            int lineCount = (int) Math.ceil(desiredWidth / mWidth);
-            if (lineCount > 0 && lineCount <= lineLimit) {
-                return true;
-            }
-            return false;
-        }
-
-        /**
-         * Get the line break word style.
-         *
-         * @return The current line break word style.
-         *
-         * @hide
-         */
-        @VisibleForTesting
-        public int getLineBreakWordStyle() {
-            return mLineBreakConfig.getLineBreakWordStyle();
-        }
-
         private CharSequence mText;
         private int mStart;
         private int mEnd;
diff --git a/core/java/android/text/TextUtils.java b/core/java/android/text/TextUtils.java
index ff66e5f..f256210 100644
--- a/core/java/android/text/TextUtils.java
+++ b/core/java/android/text/TextUtils.java
@@ -872,126 +872,128 @@
                 if (kind == 0)
                     break;
 
+                final Object span;
                 switch (kind) {
                 case ALIGNMENT_SPAN:
-                    readSpan(p, sp, new AlignmentSpan.Standard(p));
+                    span = new AlignmentSpan.Standard(p);
                     break;
 
                 case FOREGROUND_COLOR_SPAN:
-                    readSpan(p, sp, new ForegroundColorSpan(p));
+                    span = new ForegroundColorSpan(p);
                     break;
 
                 case RELATIVE_SIZE_SPAN:
-                    readSpan(p, sp, new RelativeSizeSpan(p));
+                    span = new RelativeSizeSpan(p);
                     break;
 
                 case SCALE_X_SPAN:
-                    readSpan(p, sp, new ScaleXSpan(p));
+                    span = new ScaleXSpan(p);
                     break;
 
                 case STRIKETHROUGH_SPAN:
-                    readSpan(p, sp, new StrikethroughSpan(p));
+                    span = new StrikethroughSpan(p);
                     break;
 
                 case UNDERLINE_SPAN:
-                    readSpan(p, sp, new UnderlineSpan(p));
+                    span = new UnderlineSpan(p);
                     break;
 
                 case STYLE_SPAN:
-                    readSpan(p, sp, new StyleSpan(p));
+                    span = new StyleSpan(p);
                     break;
 
                 case BULLET_SPAN:
-                    readSpan(p, sp, new BulletSpan(p));
+                    span = new BulletSpan(p);
                     break;
 
                 case QUOTE_SPAN:
-                    readSpan(p, sp, new QuoteSpan(p));
+                    span = new QuoteSpan(p);
                     break;
 
                 case LEADING_MARGIN_SPAN:
-                    readSpan(p, sp, new LeadingMarginSpan.Standard(p));
+                    span = new LeadingMarginSpan.Standard(p);
                     break;
 
                 case URL_SPAN:
-                    readSpan(p, sp, new URLSpan(p));
+                    span = new URLSpan(p);
                     break;
 
                 case BACKGROUND_COLOR_SPAN:
-                    readSpan(p, sp, new BackgroundColorSpan(p));
+                    span = new BackgroundColorSpan(p);
                     break;
 
                 case TYPEFACE_SPAN:
-                    readSpan(p, sp, new TypefaceSpan(p));
+                    span = new TypefaceSpan(p);
                     break;
 
                 case SUPERSCRIPT_SPAN:
-                    readSpan(p, sp, new SuperscriptSpan(p));
+                    span = new SuperscriptSpan(p);
                     break;
 
                 case SUBSCRIPT_SPAN:
-                    readSpan(p, sp, new SubscriptSpan(p));
+                    span = new SubscriptSpan(p);
                     break;
 
                 case ABSOLUTE_SIZE_SPAN:
-                    readSpan(p, sp, new AbsoluteSizeSpan(p));
+                    span = new AbsoluteSizeSpan(p);
                     break;
 
                 case TEXT_APPEARANCE_SPAN:
-                    readSpan(p, sp, new TextAppearanceSpan(p));
+                    span = new TextAppearanceSpan(p);
                     break;
 
                 case ANNOTATION:
-                    readSpan(p, sp, new Annotation(p));
+                    span = new Annotation(p);
                     break;
 
                 case SUGGESTION_SPAN:
-                    readSpan(p, sp, new SuggestionSpan(p));
+                    span = new SuggestionSpan(p);
                     break;
 
                 case SPELL_CHECK_SPAN:
-                    readSpan(p, sp, new SpellCheckSpan(p));
+                    span = new SpellCheckSpan(p);
                     break;
 
                 case SUGGESTION_RANGE_SPAN:
-                    readSpan(p, sp, new SuggestionRangeSpan(p));
+                    span = new SuggestionRangeSpan(p);
                     break;
 
                 case EASY_EDIT_SPAN:
-                    readSpan(p, sp, new EasyEditSpan(p));
+                    span = new EasyEditSpan(p);
                     break;
 
                 case LOCALE_SPAN:
-                    readSpan(p, sp, new LocaleSpan(p));
+                    span = new LocaleSpan(p);
                     break;
 
                 case TTS_SPAN:
-                    readSpan(p, sp, new TtsSpan(p));
+                    span = new TtsSpan(p);
                     break;
 
                 case ACCESSIBILITY_CLICKABLE_SPAN:
-                    readSpan(p, sp, new AccessibilityClickableSpan(p));
+                    span = new AccessibilityClickableSpan(p);
                     break;
 
                 case ACCESSIBILITY_URL_SPAN:
-                    readSpan(p, sp, new AccessibilityURLSpan(p));
+                    span = new AccessibilityURLSpan(p);
                     break;
 
                 case LINE_BACKGROUND_SPAN:
-                    readSpan(p, sp, new LineBackgroundSpan.Standard(p));
+                    span = new LineBackgroundSpan.Standard(p);
                     break;
 
                 case LINE_HEIGHT_SPAN:
-                    readSpan(p, sp, new LineHeightSpan.Standard(p));
+                    span = new LineHeightSpan.Standard(p);
                     break;
 
                 case ACCESSIBILITY_REPLACEMENT_SPAN:
-                    readSpan(p, sp, new AccessibilityReplacementSpan(p));
+                    span = new AccessibilityReplacementSpan(p);
                     break;
 
                 default:
                     throw new RuntimeException("bogus span encoding " + kind);
                 }
+                readSpan(p, sp, span);
             }
 
             return sp;
diff --git a/core/java/android/text/format/DateUtils.java b/core/java/android/text/format/DateUtils.java
index 511c974..518a549 100644
--- a/core/java/android/text/format/DateUtils.java
+++ b/core/java/android/text/format/DateUtils.java
@@ -743,7 +743,7 @@
      * @param startMillis the start time in UTC milliseconds
      * @param endMillis the end time in UTC milliseconds
      * @param flags a bit mask of options
-     * @param timeZone the time zone to compute the string in. Use null for local
+     * @param timeZone the id of the time zone to compute the string in. Use null for local
      * or if the FORMAT_UTC flag is being used.
      *
      * @return the formatter with the formatted date/time range appended to the string buffer.
diff --git a/core/java/android/text/method/LinkMovementMethod.java b/core/java/android/text/method/LinkMovementMethod.java
index dae978e..9f4a0ae 100644
--- a/core/java/android/text/method/LinkMovementMethod.java
+++ b/core/java/android/text/method/LinkMovementMethod.java
@@ -221,12 +221,20 @@
             y += widget.getScrollY();
 
             Layout layout = widget.getLayout();
-            int line = layout.getLineForVertical(y);
-            int off = layout.getOffsetForHorizontal(line, x);
+            ClickableSpan[] links;
+            if (y < 0 || y > layout.getHeight()) {
+                links = null;
+            } else {
+                int line = layout.getLineForVertical(y);
+                if (x < layout.getLineLeft(line) || x > layout.getLineRight(line)) {
+                    links = null;
+                } else {
+                    int off = layout.getOffsetForHorizontal(line, x);
+                    links = buffer.getSpans(off, off, ClickableSpan.class);
+                }
+            }
 
-            ClickableSpan[] links = buffer.getSpans(off, off, ClickableSpan.class);
-
-            if (links.length != 0) {
+            if (links != null && links.length != 0) {
                 ClickableSpan link = links[0];
                 if (action == MotionEvent.ACTION_UP) {
                     if (link instanceof TextLinkSpan) {
diff --git a/core/java/android/text/style/LineBreakConfigSpan.java b/core/java/android/text/style/LineBreakConfigSpan.java
new file mode 100644
index 0000000..90a79c6
--- /dev/null
+++ b/core/java/android/text/style/LineBreakConfigSpan.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 android.text.style;
+
+import android.annotation.NonNull;
+import android.graphics.text.LineBreakConfig;
+
+import java.util.Objects;
+
+/**
+ * LineBreakSpan for changing line break style of the specific region of the text.
+ */
+public class LineBreakConfigSpan {
+    private final LineBreakConfig mLineBreakConfig;
+
+    /**
+     * Construct a new {@link LineBreakConfigSpan}
+     * @param lineBreakConfig a line break config
+     */
+    public LineBreakConfigSpan(@NonNull LineBreakConfig lineBreakConfig) {
+        mLineBreakConfig = lineBreakConfig;
+    }
+
+    /**
+     * Gets an associated line break config.
+     * @return associated line break config.
+     */
+    public @NonNull LineBreakConfig getLineBreakConfig() {
+        return mLineBreakConfig;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof LineBreakConfigSpan)) return false;
+        LineBreakConfigSpan that = (LineBreakConfigSpan) o;
+        return Objects.equals(mLineBreakConfig, that.mLineBreakConfig);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mLineBreakConfig);
+    }
+
+    @Override
+    public String toString() {
+        return "LineBreakConfigSpan{mLineBreakConfig=" + mLineBreakConfig + '}';
+    }
+}
diff --git a/core/java/android/text/style/URLSpan.java b/core/java/android/text/style/URLSpan.java
index e811390..9969d29 100644
--- a/core/java/android/text/style/URLSpan.java
+++ b/core/java/android/text/style/URLSpan.java
@@ -106,7 +106,7 @@
         try {
             context.startActivity(intent);
         } catch (ActivityNotFoundException e) {
-            Log.w("URLSpan", "Actvity was not found for intent, " + intent.toString());
+            Log.w("URLSpan", "Activity was not found for intent, " + intent.toString());
         }
     }
 
diff --git a/core/java/android/text/util/Linkify.java b/core/java/android/text/util/Linkify.java
index 946fc30..fb6de56 100644
--- a/core/java/android/text/util/Linkify.java
+++ b/core/java/android/text/util/Linkify.java
@@ -31,6 +31,7 @@
 import android.text.method.LinkMovementMethod;
 import android.text.method.MovementMethod;
 import android.text.style.URLSpan;
+import android.text.TextUtils;
 import android.util.Log;
 import android.util.Patterns;
 import android.webkit.WebView;
@@ -672,8 +673,15 @@
             @Nullable Context context) {
         PhoneNumberUtil phoneUtil = PhoneNumberUtil.getInstance();
         final Context ctx = (context != null) ? context : ActivityThread.currentApplication();
-        final String regionCode = (ctx != null) ? ctx.getSystemService(TelephonyManager.class).
-                getSimCountryIso().toUpperCase(Locale.US) : Locale.getDefault().getCountry();
+        String regionCode = Locale.getDefault().getCountry();
+        if (ctx != null) {
+          String simCountryIso = ctx.getSystemService(TelephonyManager.class).getSimCountryIso();
+          if (!TextUtils.isEmpty(simCountryIso)) {
+            // Assign simCountryIso if it is available
+            regionCode = simCountryIso.toUpperCase(Locale.US);
+          }
+        }
+
         Iterable<PhoneNumberMatch> matches = phoneUtil.findNumbers(s.toString(),
                 regionCode, Leniency.POSSIBLE, Long.MAX_VALUE);
         for (PhoneNumberMatch match : matches) {
diff --git a/core/java/android/transition/ChangeBounds.java b/core/java/android/transition/ChangeBounds.java
index 59a05ac..3c185b1 100644
--- a/core/java/android/transition/ChangeBounds.java
+++ b/core/java/android/transition/ChangeBounds.java
@@ -22,6 +22,8 @@
 import android.animation.ObjectAnimator;
 import android.animation.PropertyValuesHolder;
 import android.animation.RectEvaluator;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.res.TypedArray;
@@ -274,9 +276,11 @@
         return parentMatches;
     }
 
+    @Nullable
     @Override
-    public Animator createAnimator(final ViewGroup sceneRoot, TransitionValues startValues,
-            TransitionValues endValues) {
+    public Animator createAnimator(@NonNull final ViewGroup sceneRoot,
+            @Nullable TransitionValues startValues,
+            @Nullable TransitionValues endValues) {
         if (startValues == null || endValues == null) {
             return null;
         }
diff --git a/core/java/android/transition/ChangeClipBounds.java b/core/java/android/transition/ChangeClipBounds.java
index a6398d3..bc2dfdc 100644
--- a/core/java/android/transition/ChangeClipBounds.java
+++ b/core/java/android/transition/ChangeClipBounds.java
@@ -19,6 +19,8 @@
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ObjectAnimator;
 import android.animation.RectEvaluator;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.Context;
 import android.graphics.Rect;
 import android.util.AttributeSet;
@@ -75,9 +77,11 @@
         captureValues(transitionValues);
     }
 
+    @Nullable
     @Override
-    public Animator createAnimator(final ViewGroup sceneRoot, TransitionValues startValues,
-            TransitionValues endValues) {
+    public Animator createAnimator(@NonNull final ViewGroup sceneRoot,
+            @Nullable TransitionValues startValues,
+            @Nullable TransitionValues endValues) {
         if (startValues == null || endValues == null
                 || !startValues.values.containsKey(PROPNAME_CLIP)
                 || !endValues.values.containsKey(PROPNAME_CLIP)) {
diff --git a/core/java/android/transition/ChangeImageTransform.java b/core/java/android/transition/ChangeImageTransform.java
index 9fa9961..f12515f 100644
--- a/core/java/android/transition/ChangeImageTransform.java
+++ b/core/java/android/transition/ChangeImageTransform.java
@@ -18,6 +18,8 @@
 import android.animation.Animator;
 import android.animation.ObjectAnimator;
 import android.animation.TypeEvaluator;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.Context;
 import android.graphics.Matrix;
 import android.graphics.Rect;
@@ -135,9 +137,11 @@
      * @return An Animator to move an ImageView or null if the View is not an ImageView,
      * the Drawable changed, the View is not VISIBLE, or there was no change.
      */
+    @Nullable
     @Override
-    public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues,
-            TransitionValues endValues) {
+    public Animator createAnimator(@NonNull ViewGroup sceneRoot,
+            @Nullable TransitionValues startValues,
+            @Nullable TransitionValues endValues) {
         if (startValues == null || endValues == null) {
             return null;
         }
diff --git a/core/java/android/transition/ChangeScroll.java b/core/java/android/transition/ChangeScroll.java
index 8a3fd1c..054bcd7 100644
--- a/core/java/android/transition/ChangeScroll.java
+++ b/core/java/android/transition/ChangeScroll.java
@@ -18,6 +18,8 @@
 
 import android.animation.Animator;
 import android.animation.ObjectAnimator;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.Context;
 import android.util.AttributeSet;
 import android.view.View;
@@ -63,9 +65,11 @@
         transitionValues.values.put(PROPNAME_SCROLL_Y, transitionValues.view.getScrollY());
     }
 
+    @Nullable
     @Override
-    public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues,
-            TransitionValues endValues) {
+    public Animator createAnimator(@NonNull ViewGroup sceneRoot,
+            @Nullable TransitionValues startValues,
+            @Nullable TransitionValues endValues) {
         if (startValues == null || endValues == null) {
             return null;
         }
diff --git a/core/java/android/transition/ChangeText.java b/core/java/android/transition/ChangeText.java
index d609763..b5cd46d 100644
--- a/core/java/android/transition/ChangeText.java
+++ b/core/java/android/transition/ChangeText.java
@@ -20,6 +20,8 @@
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
 import android.animation.ValueAnimator;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.graphics.Color;
 import android.util.Log;
 import android.view.ViewGroup;
@@ -151,9 +153,11 @@
         captureValues(transitionValues);
     }
 
+    @Nullable
     @Override
-    public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues,
-            TransitionValues endValues) {
+    public Animator createAnimator(@NonNull ViewGroup sceneRoot,
+            @Nullable TransitionValues startValues,
+            @Nullable TransitionValues endValues) {
         if (startValues == null || endValues == null ||
                 !(startValues.view instanceof TextView) || !(endValues.view instanceof TextView)) {
             return null;
diff --git a/core/java/android/transition/ChangeTransform.java b/core/java/android/transition/ChangeTransform.java
index 02d0a6a..2e0b95d 100644
--- a/core/java/android/transition/ChangeTransform.java
+++ b/core/java/android/transition/ChangeTransform.java
@@ -20,6 +20,7 @@
 import android.animation.FloatArrayEvaluator;
 import android.animation.ObjectAnimator;
 import android.animation.PropertyValuesHolder;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
 import android.content.res.TypedArray;
@@ -223,9 +224,11 @@
         captureValues(transitionValues);
     }
 
+    @Nullable
     @Override
-    public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues,
-            TransitionValues endValues) {
+    public Animator createAnimator(@NonNull ViewGroup sceneRoot,
+            @Nullable TransitionValues startValues,
+            @Nullable TransitionValues endValues) {
         if (startValues == null || endValues == null ||
                 !startValues.values.containsKey(PROPNAME_PARENT) ||
                 !endValues.values.containsKey(PROPNAME_PARENT)) {
diff --git a/core/java/android/transition/Crossfade.java b/core/java/android/transition/Crossfade.java
index 69ce872..f13b8fe 100644
--- a/core/java/android/transition/Crossfade.java
+++ b/core/java/android/transition/Crossfade.java
@@ -22,6 +22,8 @@
 import android.animation.ObjectAnimator;
 import android.animation.RectEvaluator;
 import android.animation.ValueAnimator;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Rect;
@@ -163,9 +165,11 @@
         return mResizeBehavior;
     }
 
+    @Nullable
     @Override
-    public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues,
-            TransitionValues endValues) {
+    public Animator createAnimator(@NonNull ViewGroup sceneRoot,
+            @Nullable TransitionValues startValues,
+            @Nullable TransitionValues endValues) {
         if (startValues == null || endValues == null) {
             return null;
         }
diff --git a/core/java/android/transition/Recolor.java b/core/java/android/transition/Recolor.java
index 1a6864a..bc93d00 100644
--- a/core/java/android/transition/Recolor.java
+++ b/core/java/android/transition/Recolor.java
@@ -18,6 +18,8 @@
 
 import android.animation.Animator;
 import android.animation.ObjectAnimator;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.Context;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
@@ -66,9 +68,11 @@
         captureValues(transitionValues);
     }
 
+    @Nullable
     @Override
-    public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues,
-            TransitionValues endValues) {
+    public Animator createAnimator(@NonNull ViewGroup sceneRoot,
+            @Nullable TransitionValues startValues,
+            @Nullable TransitionValues endValues) {
         if (startValues == null || endValues == null) {
             return null;
         }
diff --git a/core/java/android/transition/Rotate.java b/core/java/android/transition/Rotate.java
index ad1720ca..4b60568 100644
--- a/core/java/android/transition/Rotate.java
+++ b/core/java/android/transition/Rotate.java
@@ -18,6 +18,8 @@
 
 import android.animation.Animator;
 import android.animation.ObjectAnimator;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.view.View;
 import android.view.ViewGroup;
 
@@ -41,9 +43,11 @@
         transitionValues.values.put(PROPNAME_ROTATION, transitionValues.view.getRotation());
     }
 
+    @Nullable
     @Override
-    public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues,
-            TransitionValues endValues) {
+    public Animator createAnimator(@NonNull ViewGroup sceneRoot,
+            @Nullable TransitionValues startValues,
+            @Nullable TransitionValues endValues) {
         if (startValues == null || endValues == null) {
             return null;
         }
diff --git a/core/java/android/transition/Transition.java b/core/java/android/transition/Transition.java
index a204630..95841e0 100644
--- a/core/java/android/transition/Transition.java
+++ b/core/java/android/transition/Transition.java
@@ -19,6 +19,7 @@
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.TimeInterpolator;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
@@ -464,14 +465,17 @@
      *
      *
      * @param sceneRoot The root of the transition hierarchy.
-     * @param startValues The values for a specific target in the start scene.
-     * @param endValues The values for the target in the end scene.
-     * @return A Animator to be started at the appropriate time in the
-     * overall transition for this scene change. A null value means no animation
-     * should be run.
+     * @param startValues The values for a specific target in the start scene, or {@code null} if
+     *                   the target doesn't exist in the start scene.
+     * @param endValues The values for the target in the end scene, or {@code null} if the target
+     *                 doesn't exist in the end scene.
+     * @return an {@link Animator} to be started at the appropriate time in the overall transition
+     * for this scene change. A {@code null} value means no animation should be run.
      */
-    public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues,
-            TransitionValues endValues) {
+    @Nullable
+    public Animator createAnimator(@NonNull ViewGroup sceneRoot,
+            @Nullable TransitionValues startValues,
+            @Nullable TransitionValues endValues) {
         return null;
     }
 
diff --git a/core/java/android/transition/Visibility.java b/core/java/android/transition/Visibility.java
index 3c4b8c3..6b4608f 100644
--- a/core/java/android/transition/Visibility.java
+++ b/core/java/android/transition/Visibility.java
@@ -20,6 +20,8 @@
 import android.animation.Animator.AnimatorListener;
 import android.animation.Animator.AnimatorPauseListener;
 import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.util.AttributeSet;
@@ -242,9 +244,11 @@
         return visInfo;
     }
 
+    @Nullable
     @Override
-    public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues,
-            TransitionValues endValues) {
+    public Animator createAnimator(@NonNull ViewGroup sceneRoot,
+            @Nullable TransitionValues startValues,
+            @Nullable TransitionValues endValues) {
         VisibilityInfo visInfo = getVisibilityChangeInfo(startValues, endValues);
         if (visInfo.visibilityChange
                 && (visInfo.startParent != null || visInfo.endParent != null)) {
diff --git a/core/java/android/transparency/OWNERS b/core/java/android/transparency/OWNERS
index ed0e5e5..b2621b5 100644
--- a/core/java/android/transparency/OWNERS
+++ b/core/java/android/transparency/OWNERS
@@ -1,5 +1,4 @@
 # Bug component: 1164579
 billylau@google.com
 mpgroover@google.com
-vishwath@google.com
 wenhaowang@google.com
diff --git a/core/java/android/util/AtomicFile.java b/core/java/android/util/AtomicFile.java
index be8e2c2..d084a9e 100644
--- a/core/java/android/util/AtomicFile.java
+++ b/core/java/android/util/AtomicFile.java
@@ -135,10 +135,7 @@
     @Deprecated
     public FileOutputStream startWrite(long startTime) throws IOException {
         if (mCommitEventLogger != null) {
-            if (startTime != 0) {
-                mCommitEventLogger.setStartTime(startTime);
-            }
-
+            mCommitEventLogger.setStartTime(startTime);
             mCommitEventLogger.onStartWrite();
         }
 
diff --git a/core/java/android/util/DisplayMetrics.java b/core/java/android/util/DisplayMetrics.java
index 25ee6af..795500a 100755
--- a/core/java/android/util/DisplayMetrics.java
+++ b/core/java/android/util/DisplayMetrics.java
@@ -275,8 +275,12 @@
      */
     public float density;
     /**
-     * The screen density expressed as dots-per-inch.  May be either
-     * {@link #DENSITY_LOW}, {@link #DENSITY_MEDIUM}, or {@link #DENSITY_HIGH}.
+     * The screen density expressed as dots-per-inch. May be any one of the
+     * {@code DENSITY_} constants defined above.
+     *
+     * New constants are frequently added, and constants added on new Android
+     * versions may be backported to previous Android versions, so applications
+     * should not strongly rely on density matching one of the enum constants.
      */
     public int densityDpi;
     /**
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index ff7d8bb..827600c 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -86,9 +86,6 @@
     public static final String SETTINGS_NEED_CONNECTED_BLE_DEVICE_FOR_BROADCAST =
             "settings_need_connected_ble_device_for_broadcast";
 
-    /** @hide */
-    public static final String SETTINGS_AUTO_TEXT_WRAPPING = "settings_auto_text_wrapping";
-
     /**
      * Enable new language and keyboard settings UI
      * @hide
@@ -152,6 +149,13 @@
      */
     public static final String SETTINGS_BIOMETRICS2_ENROLLMENT = "settings_biometrics2_enrollment";
 
+    /**
+     * Flag to enable/disable FingerprintSettings v2
+     * @hide
+     */
+    public static final String SETTINGS_BIOMETRICS2_FINGERPRINT_SETTINGS =
+            "settings_biometrics2_fingerprint";
+
     /** Flag to enable/disable entire page in Accessibility -> Hearing aids
      *  @hide
      */
@@ -225,7 +229,6 @@
         DEFAULT_FLAGS.put(SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS, "true");
         DEFAULT_FLAGS.put(SETTINGS_APP_ALLOW_DARK_THEME_ACTIVATION_AT_BEDTIME, "true");
         DEFAULT_FLAGS.put(SETTINGS_NEED_CONNECTED_BLE_DEVICE_FOR_BROADCAST, "true");
-        DEFAULT_FLAGS.put(SETTINGS_AUTO_TEXT_WRAPPING, "false");
         DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_UI, "true");
         DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_MODIFIER_KEY, "true");
         DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_TRACKPAD, "true");
@@ -243,6 +246,7 @@
         DEFAULT_FLAGS.put(SETTINGS_SHOW_UDFPS_ENROLL_IN_SETTINGS, "true");
         DEFAULT_FLAGS.put(SETTINGS_ENABLE_LOCKSCREEN_TRANSFER_API, "true");
         DEFAULT_FLAGS.put(SETTINGS_REMOTE_DEVICE_CREDENTIAL_VALIDATION, "true");
+        DEFAULT_FLAGS.put(SETTINGS_BIOMETRICS2_FINGERPRINT_SETTINGS, "false");
     }
 
     private static final Set<String> PERSISTENT_FLAGS;
@@ -253,7 +257,6 @@
         PERSISTENT_FLAGS.add(SETTINGS_SUPPORT_LARGE_SCREEN);
         PERSISTENT_FLAGS.add(SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS);
         PERSISTENT_FLAGS.add(SETTINGS_APP_ALLOW_DARK_THEME_ACTIVATION_AT_BEDTIME);
-        PERSISTENT_FLAGS.add(SETTINGS_AUTO_TEXT_WRAPPING);
         PERSISTENT_FLAGS.add(SETTINGS_NEW_KEYBOARD_UI);
         PERSISTENT_FLAGS.add(SETTINGS_NEW_KEYBOARD_MODIFIER_KEY);
         PERSISTENT_FLAGS.add(SETTINGS_NEW_KEYBOARD_TRACKPAD);
diff --git a/core/java/android/util/LongSparseArray.java b/core/java/android/util/LongSparseArray.java
index eefb156..2f14b25 100644
--- a/core/java/android/util/LongSparseArray.java
+++ b/core/java/android/util/LongSparseArray.java
@@ -209,6 +209,39 @@
     }
 
     /**
+     * Returns the index of the first element whose key is greater than or equal to the given key.
+     *
+     * @param key The key for which to search the array.
+     * @return The smallest {@code index} for which {@code (keyAt(index) >= key)} is
+     * {@code true}, or {@link #size() size} if no such {@code index} exists.
+     * @hide
+     */
+    public int firstIndexOnOrAfter(long key) {
+        if (mGarbage) {
+            gc();
+        }
+        final int index = Arrays.binarySearch(mKeys, 0, size(), key);
+        return (index >= 0) ? index : -index - 1;
+    }
+
+    /**
+     * Returns the index of the last element whose key is less than or equal to the given key.
+     *
+     * @param key The key for which to search the array.
+     * @return The largest {@code index} for which {@code (keyAt(index) <= key)} is
+     * {@code true}, or {@code -1} if no such {@code index} exists.
+     * @hide
+     */
+    public int lastIndexOnOrBefore(long key) {
+        final int index = firstIndexOnOrAfter(key);
+
+        if (index < size() && keyAt(index) == key) {
+            return index;
+        }
+        return index - 1;
+    }
+
+    /**
      * Adds a mapping from the specified key to the specified value,
      * replacing the previous mapping from the specified key if there
      * was one.
diff --git a/core/java/android/util/NtpTrustedTime.java b/core/java/android/util/NtpTrustedTime.java
index 5aa0f59..3adbd68 100644
--- a/core/java/android/util/NtpTrustedTime.java
+++ b/core/java/android/util/NtpTrustedTime.java
@@ -216,19 +216,36 @@
 
     private static NtpTrustedTime sSingleton;
 
+    /** A lock to prevent multiple refreshes taking place at the same time. */
+    private final Object mRefreshLock = new Object();
+
+    /** A lock to ensure safe read/writes to configuration. */
+    private final Object mConfigLock = new Object();
+
     /** An in-memory config override for use during tests. */
-    @GuardedBy("this")
+    @GuardedBy("mConfigLock")
     @Nullable
     private NtpConfig mNtpConfigForTests;
 
-    @GuardedBy("this")
+    /**
+     * The latest time result.
+     *
+     * <p>Written when holding {@link #mRefreshLock} but declared volatile and can be read outside
+     * synchronized blocks to avoid blocking dump() during {@link #forceRefresh}.
+     */
     @Nullable
-    private URI mLastSuccessfulNtpServerUri;
-
-    // Declared volatile and accessed outside synchronized blocks to avoid blocking reads during
-    // forceRefresh().
     private volatile TimeResult mTimeResult;
 
+    /**
+     * The last successful NTP server URI, i.e. the one used to obtain {@link #mTimeResult} when it
+     * is non-null.
+     *
+     * <p>Written when holding {@link #mRefreshLock} but declared volatile and can be read outside
+     * synchronized blocks to avoid blocking dump() during {@link #forceRefresh}.
+     */
+    @Nullable
+    private volatile URI mLastSuccessfulNtpServerUri;
+
     protected NtpTrustedTime() {
     }
 
@@ -246,7 +263,7 @@
      * test value, i.e. so the normal value will be used next time.
      */
     public void setServerConfigForTests(@NonNull NtpConfig ntpConfig) {
-        synchronized (this) {
+        synchronized (mConfigLock) {
             mNtpConfigForTests = ntpConfig;
         }
     }
@@ -254,7 +271,7 @@
     /** Forces a refresh using the default network. */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     public boolean forceRefresh() {
-        synchronized (this) {
+        synchronized (mRefreshLock) {
             Network network = getDefaultNetwork();
             if (network == null) {
                 if (LOGD) Log.d(TAG, "forceRefresh: no network available");
@@ -269,12 +286,13 @@
     public boolean forceRefresh(@NonNull Network network) {
         Objects.requireNonNull(network);
 
-        synchronized (this) {
+        synchronized (mRefreshLock) {
+            // Prevent concurrent refreshes.
             return forceRefreshLocked(network);
         }
     }
 
-    @GuardedBy("this")
+    @GuardedBy("mRefreshLock")
     private boolean forceRefreshLocked(@NonNull Network network) {
         Objects.requireNonNull(network);
 
@@ -349,12 +367,13 @@
         return false;
     }
 
-    @GuardedBy("this")
     private NtpConfig getNtpConfig() {
-        if (mNtpConfigForTests != null) {
-            return mNtpConfigForTests;
+        synchronized (mConfigLock) {
+            if (mNtpConfigForTests != null) {
+                return mNtpConfigForTests;
+            }
+            return getNtpConfigInternal();
         }
-        return getNtpConfigInternal();
     }
 
     /**
@@ -363,6 +382,7 @@
      *
      * <p>This method has been made public for easy replacement during tests.
      */
+    @GuardedBy("mConfigLock")
     @VisibleForTesting
     @Nullable
     public abstract NtpConfig getNtpConfigInternal();
@@ -479,14 +499,14 @@
 
     /** Sets the last received NTP time. Intended for use during tests. */
     public void setCachedTimeResult(TimeResult timeResult) {
-        synchronized (this) {
+        synchronized (mRefreshLock) {
             mTimeResult = timeResult;
         }
     }
 
     /** Clears the last received NTP time. Intended for use during tests. */
     public void clearCachedTimeResult() {
-        synchronized (this) {
+        synchronized (mRefreshLock) {
             mTimeResult = null;
         }
     }
@@ -585,15 +605,18 @@
 
     /** Prints debug information. */
     public void dump(PrintWriter pw) {
-        synchronized (this) {
+        synchronized (mConfigLock) {
             pw.println("getNtpConfig()=" + getNtpConfig());
             pw.println("mNtpConfigForTests=" + mNtpConfigForTests);
-            pw.println("mLastSuccessfulNtpServerUri=" + mLastSuccessfulNtpServerUri);
-            pw.println("mTimeResult=" + mTimeResult);
-            if (mTimeResult != null) {
-                pw.println("mTimeResult.getAgeMillis()="
-                        + Duration.ofMillis(mTimeResult.getAgeMillis()));
-            }
+        }
+
+        pw.println("mLastSuccessfulNtpServerUri=" + mLastSuccessfulNtpServerUri);
+
+        TimeResult timeResult = mTimeResult;
+        pw.println("mTimeResult=" + timeResult);
+        if (timeResult != null) {
+            pw.println("mTimeResult.getAgeMillis()="
+                    + Duration.ofMillis(timeResult.getAgeMillis()));
         }
     }
 
diff --git a/core/java/android/util/SystemConfigFileCommitEventLogger.java b/core/java/android/util/SystemConfigFileCommitEventLogger.java
index 04d72fb..e9bc4b9 100644
--- a/core/java/android/util/SystemConfigFileCommitEventLogger.java
+++ b/core/java/android/util/SystemConfigFileCommitEventLogger.java
@@ -21,6 +21,8 @@
 import android.annotation.UptimeMillisLong;
 import android.os.SystemClock;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 /**
  * Writes an EventLog event capturing the performance of system config file writes.
  * The event log entry is formatted like this:
@@ -56,8 +58,9 @@
 
     /**
      * Invoked just before the configuration file writing begins.
+     * @hide
      */
-    void onStartWrite() {
+    public void onStartWrite() {
         if (mStartTime == 0) {
             mStartTime = SystemClock.uptimeMillis();
         }
@@ -65,9 +68,18 @@
 
     /**
      * Invoked just after the configuration file writing ends.
+     * @hide
      */
-    void onFinishWrite() {
-        com.android.internal.logging.EventLogTags.writeCommitSysConfigFile(mName,
-                SystemClock.uptimeMillis() - mStartTime);
+    public void onFinishWrite() {
+        writeLogRecord(SystemClock.uptimeMillis() - mStartTime);
+    }
+
+    /**
+     * The actual write of the log record.
+     * @hide
+     */
+    @VisibleForTesting
+    public void writeLogRecord(long durationMs) {
+        com.android.internal.logging.EventLogTags.writeCommitSysConfigFile(mName, durationMs);
     }
 }
diff --git a/core/java/android/util/TimeSparseArray.java b/core/java/android/util/TimeSparseArray.java
deleted file mode 100644
index 6efc683..0000000
--- a/core/java/android/util/TimeSparseArray.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/**
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy
- * of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES 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;
-
-/**
- * An array that indexes by a long timestamp, representing milliseconds since the epoch.
- * @param <E> The type of values this container maps to a timestamp.
- *
- * {@hide}
- */
-public class TimeSparseArray<E> extends LongSparseArray<E> {
-    private static final String TAG = TimeSparseArray.class.getSimpleName();
-
-    private boolean mWtfReported;
-
-    /**
-     * Finds the index of the first element whose timestamp is greater or equal to
-     * the given time.
-     *
-     * @param time The timestamp for which to search the array.
-     * @return The smallest {@code index} for which {@code (keyAt(index) >= timeStamp)} is
-     * {@code true}, or {@link #size() size} if no such {@code index} exists.
-     */
-    public int closestIndexOnOrAfter(long time) {
-        final int size = size();
-        int result = size;
-        int lo = 0;
-        int hi = size - 1;
-        while (lo <= hi) {
-            final int mid = lo + ((hi - lo) / 2);
-            final long key = keyAt(mid);
-
-            if (time > key) {
-                lo = mid + 1;
-            } else if (time < key) {
-                hi = mid - 1;
-                result = mid;
-            } else {
-                return mid;
-            }
-        }
-        return result;
-    }
-
-    /**
-     * {@inheritDoc}
-     *
-     * <p> This container can store only one value for each timestamp. And so ideally, the caller
-     * should ensure that there are no collisions. Reporting a {@link Slog#wtf(String, String)}
-     * if that happens, as that will lead to the previous value being overwritten.
-     */
-    @Override
-    public void put(long key, E value) {
-        if (indexOfKey(key) >= 0) {
-            if (!mWtfReported) {
-                Slog.wtf(TAG, "Overwriting value " + get(key) + " by " + value);
-                mWtfReported = true;
-            }
-        }
-        super.put(key, value);
-    }
-
-    /**
-     * Finds the index of the first element whose timestamp is less than or equal to
-     * the given time.
-     *
-     * @param time The timestamp for which to search the array.
-     * @return The largest {@code index} for which {@code (keyAt(index) <= timeStamp)} is
-     * {@code true}, or -1 if no such {@code index} exists.
-     */
-    public int closestIndexOnOrBefore(long time) {
-        final int index = closestIndexOnOrAfter(time);
-
-        if (index < size() && keyAt(index) == time) {
-            return index;
-        }
-        return index - 1;
-    }
-}
diff --git a/core/java/android/util/apk/SourceStampVerifier.java b/core/java/android/util/apk/SourceStampVerifier.java
index f9e3121..11d7a00 100644
--- a/core/java/android/util/apk/SourceStampVerifier.java
+++ b/core/java/android/util/apk/SourceStampVerifier.java
@@ -142,16 +142,21 @@
 
     private static SourceStampVerificationResult verify(
             RandomAccessFile apk, byte[] sourceStampCertificateDigest, byte[] manifestBytes) {
+        SignatureInfo signatureInfo;
         try {
-            SignatureInfo signatureInfo =
+            signatureInfo =
                     ApkSigningBlockUtils.findSignature(apk, SOURCE_STAMP_BLOCK_ID);
+        } catch (IOException | SignatureNotFoundException | RuntimeException e) {
+            return SourceStampVerificationResult.notPresent();
+        }
+        try {
             Map<Integer, Map<Integer, byte[]>> signatureSchemeApkContentDigests =
                     getSignatureSchemeApkContentDigests(apk, manifestBytes);
             return verify(
                     signatureInfo,
                     getSignatureSchemeDigests(signatureSchemeApkContentDigests),
                     sourceStampCertificateDigest);
-        } catch (IOException | SignatureNotFoundException | RuntimeException e) {
+        } catch (IOException | RuntimeException e) {
             return SourceStampVerificationResult.notVerified();
         }
     }
diff --git a/core/java/android/view/AccessibilityInteractionController.java b/core/java/android/view/AccessibilityInteractionController.java
index 6901b72..3e61539 100644
--- a/core/java/android/view/AccessibilityInteractionController.java
+++ b/core/java/android/view/AccessibilityInteractionController.java
@@ -1968,21 +1968,42 @@
     }
 
     /** Attaches an accessibility overlay to the specified window. */
-    public void attachAccessibilityOverlayToWindowClientThread(SurfaceControl sc) {
+    public void attachAccessibilityOverlayToWindowClientThread(
+            SurfaceControl sc,
+            int interactionId,
+            IAccessibilityInteractionConnectionCallback callback) {
         mHandler.sendMessage(
                 obtainMessage(
                         AccessibilityInteractionController
                                 ::attachAccessibilityOverlayToWindowUiThread,
                         this,
-                        sc));
+                        sc,
+                        interactionId,
+                        callback));
     }
 
-    private void attachAccessibilityOverlayToWindowUiThread(SurfaceControl sc) {
+    private void attachAccessibilityOverlayToWindowUiThread(
+            SurfaceControl sc,
+            int interactionId,
+            IAccessibilityInteractionConnectionCallback callback) {
         SurfaceControl parent = mViewRootImpl.getSurfaceControl();
-        if (parent.isValid()) {
-            SurfaceControl.Transaction t = new SurfaceControl.Transaction();
-            t.reparent(sc, parent).apply();
-            t.close();
+        if (!parent.isValid()) {
+            try {
+                callback.sendAttachOverlayResult(
+                        AccessibilityService.OVERLAY_RESULT_INTERNAL_ERROR, interactionId);
+                return;
+            } catch (RemoteException re) {
+                /* ignore - the other side will time out */
+            }
+        }
+        SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+        t.reparent(sc, parent).apply();
+        t.close();
+        try {
+            callback.sendAttachOverlayResult(
+                    AccessibilityService.OVERLAY_RESULT_SUCCESS, interactionId);
+        } catch (RemoteException re) {
+            /* ignore - the other side will time out */
         }
     }
 }
diff --git a/core/java/android/view/AttachedSurfaceControl.java b/core/java/android/view/AttachedSurfaceControl.java
index dc06671..1ed5d3f 100644
--- a/core/java/android/view/AttachedSurfaceControl.java
+++ b/core/java/android/view/AttachedSurfaceControl.java
@@ -23,6 +23,9 @@
 import android.hardware.HardwareBuffer;
 import android.window.SurfaceSyncGroup;
 
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
 /**
  * Provides an interface to the root-Surface of a View Hierarchy or Window. This
  * is used in combination with the {@link android.view.SurfaceControl} API to enable
@@ -167,4 +170,40 @@
      */
     default void setChildBoundingInsets(@NonNull Rect insets) {
     }
+
+    /**
+     * Add a trusted presentation listener on the SurfaceControl associated with this window.
+     *
+     * @param t          Transaction that the trusted presentation listener is added on. This should
+     *                   be applied by the caller.
+     * @param thresholds The {@link SurfaceControl.TrustedPresentationThresholds} that will specify
+     *                   when the to invoke the callback.
+     * @param executor   The {@link Executor} where the callback will be invoked on.
+     * @param listener   The {@link Consumer} that will receive the callbacks when entered or
+     *                   exited the threshold.
+     *
+     * @see SurfaceControl.Transaction#setTrustedPresentationCallback(SurfaceControl,
+     * SurfaceControl.TrustedPresentationThresholds, Executor, Consumer)
+     *
+     * @hide b/287076178 un-hide with API bump
+     */
+    default void addTrustedPresentationCallback(@NonNull SurfaceControl.Transaction t,
+            @NonNull SurfaceControl.TrustedPresentationThresholds thresholds,
+            @NonNull Executor executor, @NonNull Consumer<Boolean> listener) {
+    }
+
+    /**
+     * Remove a trusted presentation listener on the SurfaceControl associated with this window.
+     *
+     * @param t          Transaction that the trusted presentation listener removed on. This should
+     *                   be applied by the caller.
+     * @param listener   The {@link Consumer} that was previously registered with
+     *                   addTrustedPresentationCallback that should be removed.
+     *
+     * @see SurfaceControl.Transaction#clearTrustedPresentationCallback(SurfaceControl)
+     * @hide b/287076178 un-hide with API bump
+     */
+    default void removeTrustedPresentationCallback(@NonNull SurfaceControl.Transaction t,
+            @NonNull Consumer<Boolean> listener) {
+    }
 }
diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java
index 0177904..654fa1d 100644
--- a/core/java/android/view/Choreographer.java
+++ b/core/java/android/view/Choreographer.java
@@ -693,6 +693,7 @@
      * @throws IllegalStateException if no frame is in progress.
      * @hide
      */
+    @TestApi
     @UnsupportedAppUsage
     public long getFrameTimeNanos() {
         synchronized (mLock) {
@@ -716,6 +717,57 @@
         }
     }
 
+    /**
+     * Gets the time in {@link System#nanoTime()} timebase which the current frame
+     * is expected to be presented.
+     * <p>
+     * This time should be used to advance any animation clocks.
+     * Prefer using this method over {@link #getFrameTimeNanos()}.
+     * </p><p>
+     * This method should only be called from within a callback.
+     * </p>
+     *
+     * @return The frame start time, in the {@link System#nanoTime()} time base.
+     *
+     * @throws IllegalStateException if no frame is in progress.
+     * @hide
+     */
+    public long getExpectedPresentationTimeNanos() {
+        return mFrameData.getPreferredFrameTimeline().getExpectedPresentationTimeNanos();
+    }
+
+
+    /**
+     * Same as {@link #getExpectedPresentationTimeNanos()} but with millisecond precision.
+     *
+     * @return The frame start time, in the {@link SystemClock#uptimeMillis()} time base.
+     *
+     * @throws IllegalStateException if no frame is in progress.
+     * @hide
+     */
+    public long getExpectedPresentationTimeMillis() {
+        return getExpectedPresentationTimeNanos() / TimeUtils.NANOS_PER_MS;
+    }
+
+    /**
+     * Same as {@link #getExpectedPresentationTimeNanos()},
+     * Should always use {@link #getExpectedPresentationTimeNanos()} if it's possilbe.
+     * This method involves a binder call to SF,
+     * calling this method can potentially influence the performance.
+     *
+     * @return The frame start time, in the {@link System#nanoTime()} time base.
+     *
+     * @hide
+     */
+    public long getLatestExpectedPresentTimeNanos() {
+        if (mDisplayEventReceiver == null) {
+            return System.nanoTime();
+        }
+
+        return mDisplayEventReceiver.getLatestVsyncEventData()
+                .preferredFrameTimeline().expectedPresentationTime;
+    }
+
     private void scheduleFrameLocked(long now) {
         if (!mFrameScheduled) {
             mFrameScheduled = true;
@@ -869,7 +921,8 @@
                 Trace.traceBegin(Trace.TRACE_TAG_VIEW, message);
             }
 
-            AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);
+            AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS,
+                    timeline.mExpectedPresentationTimeNanos);
 
             mFrameInfo.markInputHandlingStart();
             doCallbacks(Choreographer.CALLBACK_INPUT, frameIntervalNanos);
@@ -1167,10 +1220,9 @@
          */
         FrameTimeline update(
                 long frameTimeNanos, DisplayEventReceiver.VsyncEventData vsyncEventData) {
-            if (vsyncEventData.frameTimelinesLength == 0) {
-                throw new IllegalArgumentException(
-                        "Vsync event timelines length must be greater than 0");
-            }
+            // Even if the frame timelines length is 0, continue with allocation for API
+            // FrameData.getFrameTimelines consistency. The 0 length frame timelines code path
+            // should only occur when USE_VSYNC property is false.
             if (mFrameTimelines.length != vsyncEventData.frameTimelinesLength) {
                 allocateFrameTimelines(vsyncEventData.frameTimelinesLength);
             }
@@ -1207,6 +1259,10 @@
             if (newPreferredDeadline < minimumDeadline) {
                 DisplayEventReceiver.VsyncEventData latestVsyncEventData =
                         displayEventReceiver.getLatestVsyncEventData();
+                if (latestVsyncEventData == null) {
+                    throw new IllegalArgumentException(
+                            "Could not get VsyncEventData. Did SurfaceFlinger crash?");
+                }
                 update(frameTimeNanos, latestVsyncEventData);
             } else {
                 update(frameTimeNanos, newPreferredIndex);
diff --git a/core/java/android/view/DisplayEventReceiver.java b/core/java/android/view/DisplayEventReceiver.java
index 50246f6..a46136a 100644
--- a/core/java/android/view/DisplayEventReceiver.java
+++ b/core/java/android/view/DisplayEventReceiver.java
@@ -158,7 +158,11 @@
         static final int FRAME_TIMELINES_CAPACITY = 7;
 
         public static class FrameTimeline {
-            FrameTimeline() {}
+            FrameTimeline() {
+                // Some reasonable values (+10 ms) for default timestamps.
+                deadline = System.nanoTime() + 10_000_000;
+                expectedPresentationTime = deadline + 10_000_000;
+            }
 
             // Called from native code.
             @SuppressWarnings("unused")
@@ -179,11 +183,11 @@
             public long vsyncId = FrameInfo.INVALID_VSYNC_ID;
 
             // The frame timestamp for when the frame is expected to be presented.
-            public long expectedPresentationTime = Long.MAX_VALUE;
+            public long expectedPresentationTime;
 
             // The frame deadline timestamp in {@link System#nanoTime()} timebase that it is
             // allotted for the frame to be completed.
-            public long deadline = Long.MAX_VALUE;
+            public long deadline;
         }
 
         /**
@@ -197,7 +201,9 @@
 
         public int preferredFrameTimelineIndex = 0;
 
-        public int frameTimelinesLength = 0;
+        // The default FrameTimeline is a placeholder populated with invalid vsync ID and some
+        // reasonable timestamps.
+        public int frameTimelinesLength = 1;
 
         VsyncEventData() {
             frameTimelines = new FrameTimeline[FRAME_TIMELINES_CAPACITY];
diff --git a/core/java/android/view/GestureDetector.java b/core/java/android/view/GestureDetector.java
index fc47c19..9ff29a8 100644
--- a/core/java/android/view/GestureDetector.java
+++ b/core/java/android/view/GestureDetector.java
@@ -841,8 +841,10 @@
         mHandler.removeMessages(SHOW_PRESS);
         mHandler.removeMessages(LONG_PRESS);
         mHandler.removeMessages(TAP);
-        mVelocityTracker.recycle();
-        mVelocityTracker = null;
+        if (mVelocityTracker != null) {
+            mVelocityTracker.recycle();
+            mVelocityTracker = null;
+        }
         mIsDoubleTapping = false;
         mStillDown = false;
         mAlwaysInTapRegion = false;
diff --git a/core/java/android/view/HandwritingInitiator.java b/core/java/android/view/HandwritingInitiator.java
index ab58306b..c501f5a 100644
--- a/core/java/android/view/HandwritingInitiator.java
+++ b/core/java/android/view/HandwritingInitiator.java
@@ -132,12 +132,7 @@
             case MotionEvent.ACTION_DOWN:
             case MotionEvent.ACTION_POINTER_DOWN:
                 mState = null;
-                final int actionIndex = motionEvent.getActionIndex();
-                final int toolType = motionEvent.getToolType(actionIndex);
-                // TOOL_TYPE_ERASER is also from stylus. This indicates that the user is holding
-                // the eraser button during handwriting.
-                if (toolType != MotionEvent.TOOL_TYPE_STYLUS
-                        && toolType != MotionEvent.TOOL_TYPE_ERASER) {
+                if (!motionEvent.isStylusPointer()) {
                     // The motion event is not from a stylus event, ignore it.
                     return false;
                 }
diff --git a/core/java/android/view/HapticFeedbackConstants.java b/core/java/android/view/HapticFeedbackConstants.java
index 6551a18..253073a 100644
--- a/core/java/android/view/HapticFeedbackConstants.java
+++ b/core/java/android/view/HapticFeedbackConstants.java
@@ -129,27 +129,25 @@
     public static final int REJECT = 17;
 
     /**
-     * A haptic effect to provide texture while a rotary input device is being scrolled.
+     * A haptic effect to provide texture while scrolling.
      *
      * @hide
      */
-    public static final int ROTARY_SCROLL_TICK = 18;
+    public static final int SCROLL_TICK = 18;
 
     /**
-     * A haptic effect to signal that a list element has been focused while scrolling using a rotary
-     * input device.
+     * A haptic effect to signal that a list element has been focused while scrolling.
      *
      * @hide
      */
-    public static final int ROTARY_SCROLL_ITEM_FOCUS = 19;
+    public static final int SCROLL_ITEM_FOCUS = 19;
 
     /**
-     * A haptic effect to signal reaching the scrolling limits of a list while scrolling using a
-     * rotary input device.
+     * A haptic effect to signal reaching the scrolling limits of a list while scrolling.
      *
      * @hide
      */
-    public static final int ROTARY_SCROLL_LIMIT = 20;
+    public static final int SCROLL_LIMIT = 20;
 
     /**
      * The user has toggled a switch or button into the on position.
diff --git a/core/java/android/view/HapticScrollFeedbackProvider.java b/core/java/android/view/HapticScrollFeedbackProvider.java
new file mode 100644
index 0000000..7e103a5
--- /dev/null
+++ b/core/java/android/view/HapticScrollFeedbackProvider.java
@@ -0,0 +1,141 @@
+/**
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.view;
+
+import static com.android.internal.R.dimen.config_rotaryEncoderAxisScrollTickInterval;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * {@link ScrollFeedbackProvider} that performs haptic feedback when scrolling.
+ *
+ * <p>Each scrolling widget should have its own instance of this class to ensure that scroll state
+ * is isolated.
+ *
+ * @hide
+ */
+public class HapticScrollFeedbackProvider implements ScrollFeedbackProvider {
+    private static final String TAG = "HapticScrollFeedbackProvider";
+
+    private static final int TICK_INTERVAL_NO_TICK = 0;
+    private static final int TICK_INTERVAL_UNSET = Integer.MAX_VALUE;
+
+    private final View mView;
+
+
+    // Info about the cause of the latest scroll event.
+    /** The ID of the {link @InputDevice} that caused the latest scroll event. */
+    private int mDeviceId = -1;
+    /** The axis on which the latest scroll event happened. */
+    private int mAxis = -1;
+    /** The {@link InputDevice} source from which the latest scroll event happened. */
+    private int mSource = -1;
+
+    /**
+     * Cache for tick interval for scroll tick caused by a {@link InputDevice#SOURCE_ROTARY_ENCODER}
+     * on {@link MotionEvent#AXIS_SCROLL}. Set to -1 if the value has not been fetched and cached.
+     */
+    private int mRotaryEncoderAxisScrollTickIntervalPixels = TICK_INTERVAL_UNSET;
+    /** The tick interval corresponding to the current InputDevice/source/axis. */
+    private int mTickIntervalPixels = TICK_INTERVAL_NO_TICK;
+    private int mTotalScrollPixels = 0;
+    private boolean mCanPlayLimitFeedback = true;
+
+    public HapticScrollFeedbackProvider(View view) {
+        this(view, /* rotaryEncoderAxisScrollTickIntervalPixels= */ TICK_INTERVAL_UNSET);
+    }
+
+    /** @hide */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public HapticScrollFeedbackProvider(View view, int rotaryEncoderAxisScrollTickIntervalPixels) {
+        mView = view;
+        mRotaryEncoderAxisScrollTickIntervalPixels = rotaryEncoderAxisScrollTickIntervalPixels;
+    }
+
+    @Override
+    public void onScrollProgress(MotionEvent event, int axis, int deltaInPixels) {
+        maybeUpdateCurrentConfig(event, axis);
+
+        if (mTickIntervalPixels == TICK_INTERVAL_NO_TICK) {
+            // There's no valid tick interval. Exit early before doing any further computation.
+            return;
+        }
+
+        mTotalScrollPixels += deltaInPixels;
+
+        if (Math.abs(mTotalScrollPixels) >= mTickIntervalPixels) {
+            mTotalScrollPixels %= mTickIntervalPixels;
+            // TODO(b/239594271): create a new `performHapticFeedbackForDevice` and use that here.
+            mView.performHapticFeedback(HapticFeedbackConstants.SCROLL_TICK);
+        }
+
+        mCanPlayLimitFeedback = true;
+    }
+
+    @Override
+    public void onScrollLimit(MotionEvent event, int axis, boolean isStart) {
+        maybeUpdateCurrentConfig(event, axis);
+
+        if (!mCanPlayLimitFeedback) {
+            return;
+        }
+
+        // TODO(b/239594271): create a new `performHapticFeedbackForDevice` and use that here.
+        mView.performHapticFeedback(HapticFeedbackConstants.SCROLL_LIMIT);
+
+        mCanPlayLimitFeedback = false;
+    }
+
+    @Override
+    public void onSnapToItem(MotionEvent event, int axis) {
+        // TODO(b/239594271): create a new `performHapticFeedbackForDevice` and use that here.
+        mView.performHapticFeedback(HapticFeedbackConstants.SCROLL_ITEM_FOCUS);
+        mCanPlayLimitFeedback = true;
+    }
+
+    private void maybeUpdateCurrentConfig(MotionEvent event, int axis) {
+        int source = event.getSource();
+        int deviceId = event.getDeviceId();
+
+        if (mAxis != axis || mSource != source || mDeviceId != deviceId) {
+            mSource = source;
+            mAxis = axis;
+            mDeviceId = deviceId;
+
+            mCanPlayLimitFeedback = true;
+            mTotalScrollPixels = 0;
+            calculateTickIntervals(source, axis);
+        }
+    }
+
+    private void calculateTickIntervals(int source, int axis) {
+        mTickIntervalPixels = TICK_INTERVAL_NO_TICK;
+
+        if (axis == MotionEvent.AXIS_SCROLL && source == InputDevice.SOURCE_ROTARY_ENCODER) {
+            if (mRotaryEncoderAxisScrollTickIntervalPixels == TICK_INTERVAL_UNSET) {
+                // Value has not been fetched  yet. Fetch and cache it.
+                mRotaryEncoderAxisScrollTickIntervalPixels =
+                        mView.getContext().getResources().getDimensionPixelSize(
+                                config_rotaryEncoderAxisScrollTickInterval);
+                if (mRotaryEncoderAxisScrollTickIntervalPixels < 0) {
+                    mRotaryEncoderAxisScrollTickIntervalPixels = TICK_INTERVAL_NO_TICK;
+                }
+            }
+            mTickIntervalPixels = mRotaryEncoderAxisScrollTickIntervalPixels;
+        }
+    }
+}
diff --git a/core/java/android/view/ISurfaceControlViewHost.aidl b/core/java/android/view/ISurfaceControlViewHost.aidl
index 15008ae..fd4b329 100644
--- a/core/java/android/view/ISurfaceControlViewHost.aidl
+++ b/core/java/android/view/ISurfaceControlViewHost.aidl
@@ -19,6 +19,7 @@
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.view.InsetsState;
+import android.view.ISurfaceControlViewHostParent;
 import android.window.ISurfaceSyncGroup;
 
 /**
@@ -34,4 +35,9 @@
     oneway void onDispatchDetachedFromWindow();
     oneway void onInsetsChanged(in InsetsState state, in Rect insetFrame);
     ISurfaceSyncGroup getSurfaceSyncGroup();
+    /**
+     * Attaches the parent interface so the embedded content can communicate back to the parent.
+     * If null is passed in, it will remove the parent interface and no more updates will be sent.
+     */
+    oneway void attachParentInterface(in @nullable ISurfaceControlViewHostParent parentInterface);
 }
diff --git a/core/java/android/view/ISurfaceControlViewHostParent.aidl b/core/java/android/view/ISurfaceControlViewHostParent.aidl
new file mode 100644
index 0000000..f42e001
--- /dev/null
+++ b/core/java/android/view/ISurfaceControlViewHostParent.aidl
@@ -0,0 +1,27 @@
+/*
+** Copyright 2023, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package android.view;
+
+import android.view.WindowManager;
+
+/**
+ * API from embedded content in SurfaceControlViewHost to parent containing the embedded.
+ * {@hide}
+ */
+oneway interface ISurfaceControlViewHostParent {
+    void updateParams(in WindowManager.LayoutParams[] childAttrs);
+}
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 209729b..bbd8255 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -93,6 +93,11 @@
      * Only use {@link DisplayRotation#mUserRotation} as the display rotation.
      */
     const int FIXED_TO_USER_ROTATION_ENABLED = 2;
+    /**
+     * If auto-rotation is not supported, {@link DisplayRotation#mUserRotation} will be used.
+     * Otherwise the behavior is same as {link #FIXED_TO_USER_ROTATION_DISABLED}.
+     */
+    const int FIXED_TO_USER_ROTATION_IF_NO_AUTO_ROTATION = 3;
 
     /**
      * ===== NOTICE =====
@@ -112,14 +117,19 @@
     void getInitialDisplaySize(int displayId, out Point size);
     @UnsupportedAppUsage
     void getBaseDisplaySize(int displayId, out Point size);
+    @EnforcePermission("WRITE_SECURE_SETTINGS")
     void setForcedDisplaySize(int displayId, int width, int height);
+    @EnforcePermission("WRITE_SECURE_SETTINGS")
     void clearForcedDisplaySize(int displayId);
     @UnsupportedAppUsage
     int getInitialDisplayDensity(int displayId);
     int getBaseDisplayDensity(int displayId);
     int getDisplayIdByUniqueId(String uniqueId);
+    @EnforcePermission("WRITE_SECURE_SETTINGS")
     void setForcedDisplayDensityForUser(int displayId, int density, int userId);
+    @EnforcePermission("WRITE_SECURE_SETTINGS")
     void clearForcedDisplayDensityForUser(int displayId, int userId);
+    @EnforcePermission("WRITE_SECURE_SETTINGS")
     void setForcedDisplayScalingMode(int displayId, int mode); // 0 = auto, 1 = disable
 
     // These can only be called when holding the MANAGE_APP_TOKENS permission.
@@ -159,6 +169,7 @@
      * @param shellRootLayer The container's layer. See WindowManager#ShellRootLayer.
      * @return a SurfaceControl to add things to.
      */
+    @EnforcePermission("MANAGE_APP_TOKENS")
     SurfaceControl addShellRoot(int displayId, IWindow client, int shellRootLayer);
 
     /**
@@ -167,6 +178,7 @@
      *
      * @param target The IWindow that accessibility service interfaces with.
      */
+    @EnforcePermission("MANAGE_APP_TOKENS")
     void setShellRootAccessibilityWindow(int displayId, int shellRootLayer, IWindow target);
 
     /**
@@ -197,6 +209,7 @@
     void disableKeyguard(IBinder token, String tag, int userId);
     /** @deprecated use Activity.setShowWhenLocked instead. */
     void reenableKeyguard(IBinder token, int userId);
+    @EnforcePermission("DISABLE_KEYGUARD")
     void exitKeyguardSecurely(IOnKeyguardExitResult callback);
     @UnsupportedAppUsage
     boolean isKeyguardLocked();
@@ -417,6 +430,7 @@
     /**
      * Called by System UI to enable or disable haptic feedback on the navigation bar buttons.
      */
+    @EnforcePermission("STATUS_BAR")
     @UnsupportedAppUsage
     void setNavBarVirtualKeyHapticFeedbackEnabled(boolean enabled);
 
@@ -471,10 +485,23 @@
      * Requests Keyboard Shortcuts from the displayed window.
      *
      * @param receiver The receiver to deliver the results to.
+     * @param deviceId The deviceId of KeyEvent by which this request is triggered, or -1 if it's
+     *                 not triggered by a KeyEvent.
+     * @see #requestImeKeyboardShortcuts(IResultReceiver, int)
      */
     void requestAppKeyboardShortcuts(IResultReceiver receiver, int deviceId);
 
     /**
+     * Requests Keyboard Shortcuts from currently selected IME.
+     *
+     * @param receiver The receiver to deliver the results to.
+     * @param deviceId The deviceId of KeyEvent by which this request is triggered, or -1 if it's
+     *                 not triggered by a KeyEvent.
+     * @see #requestAppKeyboardShortcuts(IResultReceiver, int)
+     */
+    void requestImeKeyboardShortcuts(IResultReceiver receiver, int deviceId);
+
+    /**
      * Retrieves the current stable insets from the primary display.
      */
     @UnsupportedAppUsage
@@ -504,6 +531,7 @@
     /**
      * Return the touch region for the current IME window, or an empty region if there is none.
      */
+    @EnforcePermission("RESTRICTED_VR_ACCESS")
     Region getCurrentImeTouchRegion();
 
     /**
@@ -713,6 +741,7 @@
      * When in multi-window mode, the provided displayWindowInsetsController will control insets
      * animations.
      */
+    @EnforcePermission("MANAGE_APP_TOKENS")
     void setDisplayWindowInsetsController(
             int displayId, in IDisplayWindowInsetsController displayWindowInsetsController);
 
@@ -720,6 +749,7 @@
      * Called when a remote process updates the requested visibilities of insets on a display window
      * container.
      */
+    @EnforcePermission("MANAGE_APP_TOKENS")
     void updateDisplayWindowRequestedVisibleTypes(int displayId, int requestedVisibleTypes);
 
     /**
@@ -1014,4 +1044,13 @@
      * @return List of ComponentNames corresponding to the activities that were notified.
     */
     List<ComponentName> notifyScreenshotListeners(int displayId);
+
+    /**
+     * Replace the content of the displayId with the SurfaceControl passed in. This can be used for
+     * tests when creating a VirtualDisplay, but only want to capture specific content and not
+     * mirror the entire display.
+     */
+     @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
+             + ".permission.ACCESS_SURFACE_FLINGER)")
+    boolean replaceContentOnDisplay(int displayId, in SurfaceControl sc);
 }
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 5019b85..1d58268 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -302,11 +302,9 @@
     public static final int ANIMATION_TYPE_NONE = -1;
 
     /** Running animation will show insets */
-    @VisibleForTesting
     public static final int ANIMATION_TYPE_SHOW = 0;
 
     /** Running animation will hide insets */
-    @VisibleForTesting
     public static final int ANIMATION_TYPE_HIDE = 1;
 
     /** Running animation is controlled by user via {@link #controlWindowInsetsAnimation} */
@@ -447,21 +445,21 @@
                     if (mInputMethodJankContext == null) return;
                     ImeTracker.forJank().onRequestAnimation(
                             mInputMethodJankContext,
-                            mShow ? ANIMATION_TYPE_SHOW : ANIMATION_TYPE_HIDE,
+                            getAnimationType(),
                             !mHasAnimationCallbacks);
                 }
 
                 @Override
                 public void onAnimationCancel(Animator animation) {
                     if (mInputMethodJankContext == null) return;
-                    ImeTracker.forJank().onCancelAnimation();
+                    ImeTracker.forJank().onCancelAnimation(getAnimationType());
                 }
 
                 @Override
                 public void onAnimationEnd(Animator animation) {
                     onAnimationFinish();
                     if (mInputMethodJankContext == null) return;
-                    ImeTracker.forJank().onFinishAnimation();
+                    ImeTracker.forJank().onFinishAnimation(getAnimationType());
                 }
             });
             if (!mHasAnimationCallbacks) {
@@ -562,6 +560,14 @@
                 }
             }
         }
+
+        /**
+         * Returns the current animation type.
+         */
+        @AnimationType
+        private int getAnimationType() {
+            return mShow ? ANIMATION_TYPE_SHOW : ANIMATION_TYPE_HIDE;
+        }
     }
 
     /**
@@ -797,7 +803,7 @@
             }
 
             WindowInsets insets = state.calculateInsets(mFrame, mState /* ignoringVisibilityState*/,
-                    mLastInsets.isRound(), mLastInsets.shouldAlwaysConsumeSystemBars(),
+                    mLastInsets.isRound(), false /* alwaysConsumeSystemBars */,
                     mLastLegacySoftInputMode, mLastLegacyWindowFlags, mLastLegacySystemUiFlags,
                     mWindowType, mLastWindowingMode, null /* idSideMap */);
             mHost.dispatchWindowInsetsAnimationProgress(insets,
@@ -841,7 +847,6 @@
         return mLastDispatchedState;
     }
 
-    @VisibleForTesting
     public boolean onStateChanged(InsetsState state) {
         boolean stateChanged = false;
         if (!CAPTION_ON_SHELL) {
diff --git a/core/java/android/view/InsetsFrameProvider.java b/core/java/android/view/InsetsFrameProvider.java
index 37b6c77..83bdb08 100644
--- a/core/java/android/view/InsetsFrameProvider.java
+++ b/core/java/android/view/InsetsFrameProvider.java
@@ -164,6 +164,10 @@
         return mFlags;
     }
 
+    public boolean hasFlags(@Flags int mask) {
+        return (mFlags & mask) == mask;
+    }
+
     public InsetsFrameProvider setInsetsSize(Insets insetsSize) {
         mInsetsSize = insetsSize;
         return this;
diff --git a/core/java/android/view/InsetsSource.java b/core/java/android/view/InsetsSource.java
index 9fc42ff..6441186 100644
--- a/core/java/android/view/InsetsSource.java
+++ b/core/java/android/view/InsetsSource.java
@@ -58,9 +58,26 @@
      */
     public static final int FLAG_SUPPRESS_SCRIM = 1;
 
+    /**
+     * Controls whether the insets frame will be used to move {@link RoundedCorner} inward with the
+     * insets frame size when calculating the rounded corner insets to other windows.
+     *
+     * For example, task bar will draw fake rounded corners above itself, so we need to move the
+     * rounded corner up by the task bar insets size to make other windows see a rounded corner
+     * above the task bar.
+     */
+    public static final int FLAG_INSETS_ROUNDED_CORNER = 1 << 1;
+
+    /**
+     * Controls whether the insets provided by this source should be forcibly consumed.
+     */
+    public static final int FLAG_FORCE_CONSUMING = 1 << 2;
+
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(flag = true, prefix = "FLAG_", value = {
             FLAG_SUPPRESS_SCRIM,
+            FLAG_INSETS_ROUNDED_CORNER,
+            FLAG_FORCE_CONSUMING,
     })
     public @interface Flags {}
 
@@ -78,7 +95,6 @@
     private @Nullable Rect mVisibleFrame;
 
     private boolean mVisible;
-    private boolean mInsetsRoundedCornerFrame;
 
     private final Rect mTmpFrame = new Rect();
 
@@ -98,7 +114,6 @@
                 ? new Rect(other.mVisibleFrame)
                 : null;
         mFlags = other.mFlags;
-        mInsetsRoundedCornerFrame = other.mInsetsRoundedCornerFrame;
     }
 
     public void set(InsetsSource other) {
@@ -108,7 +123,6 @@
                 ? new Rect(other.mVisibleFrame)
                 : null;
         mFlags = other.mFlags;
-        mInsetsRoundedCornerFrame = other.mInsetsRoundedCornerFrame;
     }
 
     public InsetsSource setFrame(int left, int top, int right, int bottom) {
@@ -136,6 +150,11 @@
         return this;
     }
 
+    public InsetsSource setFlags(@Flags int flags, @Flags int mask) {
+        mFlags = (mFlags & ~mask) | (flags & mask);
+        return this;
+    }
+
     public int getId() {
         return mId;
     }
@@ -160,20 +179,15 @@
         return mFlags;
     }
 
+    public boolean hasFlags(int flags) {
+        return (mFlags & flags) == flags;
+    }
+
     boolean isUserControllable() {
         // If mVisibleFrame is null, it will be the same area as mFrame.
         return mVisibleFrame == null || !mVisibleFrame.isEmpty();
     }
 
-    public boolean insetsRoundedCornerFrame() {
-        return mInsetsRoundedCornerFrame;
-    }
-
-    public InsetsSource setInsetsRoundedCornerFrame(boolean insetsRoundedCornerFrame) {
-        mInsetsRoundedCornerFrame = insetsRoundedCornerFrame;
-        return this;
-    }
-
     /**
      * Calculates the insets this source will cause to a client window.
      *
@@ -317,6 +331,12 @@
         if ((flags & FLAG_SUPPRESS_SCRIM) != 0) {
             joiner.add("SUPPRESS_SCRIM");
         }
+        if ((flags & FLAG_INSETS_ROUNDED_CORNER) != 0) {
+            joiner.add("INSETS_ROUNDED_CORNER");
+        }
+        if ((flags & FLAG_FORCE_CONSUMING) != 0) {
+            joiner.add("FORCE_CONSUMING");
+        }
         return joiner.toString();
     }
 
@@ -347,7 +367,6 @@
         }
         pw.print(" visible="); pw.print(mVisible);
         pw.print(" flags="); pw.print(flagsToString(mFlags));
-        pw.print(" insetsRoundedCornerFrame="); pw.print(mInsetsRoundedCornerFrame);
         pw.println();
     }
 
@@ -372,14 +391,12 @@
         if (mFlags != that.mFlags) return false;
         if (excludeInvisibleImeFrames && !mVisible && mType == WindowInsets.Type.ime()) return true;
         if (!Objects.equals(mVisibleFrame, that.mVisibleFrame)) return false;
-        if (mInsetsRoundedCornerFrame != that.mInsetsRoundedCornerFrame) return false;
         return mFrame.equals(that.mFrame);
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(mId, mType, mFrame, mVisibleFrame, mVisible, mFlags,
-                mInsetsRoundedCornerFrame);
+        return Objects.hash(mId, mType, mFrame, mVisibleFrame, mVisible, mFlags);
     }
 
     public InsetsSource(Parcel in) {
@@ -393,7 +410,6 @@
         }
         mVisible = in.readBoolean();
         mFlags = in.readInt();
-        mInsetsRoundedCornerFrame = in.readBoolean();
     }
 
     @Override
@@ -414,7 +430,6 @@
         }
         dest.writeBoolean(mVisible);
         dest.writeInt(mFlags);
-        dest.writeBoolean(mInsetsRoundedCornerFrame);
     }
 
     @Override
@@ -424,7 +439,6 @@
                 + " mFrame=" + mFrame.toShortString()
                 + " mVisible=" + mVisible
                 + " mFlags=[" + flagsToString(mFlags) + "]"
-                + (mInsetsRoundedCornerFrame ? " insetsRoundedCornerFrame" : "")
                 + "}";
     }
 
diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java
index b55d25a..c13b9ab 100644
--- a/core/java/android/view/InsetsState.java
+++ b/core/java/android/view/InsetsState.java
@@ -16,6 +16,8 @@
 
 package android.view;
 
+import static android.view.InsetsSource.FLAG_FORCE_CONSUMING;
+import static android.view.InsetsSource.FLAG_INSETS_ROUNDED_CORNER;
 import static android.view.InsetsStateProto.DISPLAY_CUTOUT;
 import static android.view.InsetsStateProto.DISPLAY_FRAME;
 import static android.view.InsetsStateProto.SOURCES;
@@ -143,12 +145,18 @@
         boolean[] typeVisibilityMap = new boolean[Type.SIZE];
         final Rect relativeFrame = new Rect(frame);
         final Rect relativeFrameMax = new Rect(frame);
+        @InsetsType int forceConsumingTypes = 0;
         @InsetsType int suppressScrimTypes = 0;
         for (int i = mSources.size() - 1; i >= 0; i--) {
             final InsetsSource source = mSources.valueAt(i);
+            final @InsetsType int type = source.getType();
+
+            if ((source.getFlags() & InsetsSource.FLAG_FORCE_CONSUMING) != 0) {
+                forceConsumingTypes |= type;
+            }
 
             if ((source.getFlags() & InsetsSource.FLAG_SUPPRESS_SCRIM) != 0) {
-                suppressScrimTypes |= source.getType();
+                suppressScrimTypes |= type;
             }
 
             processSource(source, relativeFrame, false /* ignoreVisibility */, typeInsetsMap,
@@ -156,7 +164,7 @@
 
             // IME won't be reported in max insets as the size depends on the EditorInfo of the IME
             // target.
-            if (source.getType() != WindowInsets.Type.ime()) {
+            if (type != WindowInsets.Type.ime()) {
                 InsetsSource ignoringVisibilitySource = ignoringVisibilityState != null
                         ? ignoringVisibilityState.peekSource(source.getId())
                         : source;
@@ -177,13 +185,13 @@
         if ((legacyWindowFlags & FLAG_FULLSCREEN) != 0) {
             compatInsetsTypes &= ~statusBars();
         }
-        if (clearsCompatInsets(windowType, legacyWindowFlags, windowingMode)
-                && !alwaysConsumeSystemBars) {
-            compatInsetsTypes = 0;
+        if (clearsCompatInsets(windowType, legacyWindowFlags, windowingMode)) {
+            // Clear all types but forceConsumingTypes.
+            compatInsetsTypes &= forceConsumingTypes;
         }
 
         return new WindowInsets(typeInsetsMap, typeMaxInsetsMap, typeVisibilityMap, isScreenRound,
-                alwaysConsumeSystemBars, suppressScrimTypes, calculateRelativeCutout(frame),
+                forceConsumingTypes, suppressScrimTypes, calculateRelativeCutout(frame),
                 calculateRelativeRoundedCorners(frame),
                 calculateRelativePrivacyIndicatorBounds(frame),
                 calculateRelativeDisplayShape(frame),
@@ -220,7 +228,7 @@
         final Rect roundedCornerFrame = new Rect(mRoundedCornerFrame);
         for (int i = mSources.size() - 1; i >= 0; i--) {
             final InsetsSource source = mSources.valueAt(i);
-            if (source.insetsRoundedCornerFrame()) {
+            if (source.hasFlags(FLAG_INSETS_ROUNDED_CORNER)) {
                 final Insets insets = source.calculateInsets(roundedCornerFrame, false);
                 roundedCornerFrame.inset(insets);
             }
@@ -289,9 +297,8 @@
 
     public Insets calculateVisibleInsets(Rect frame, int windowType, int windowingMode,
             @SoftInputModeFlags int softInputMode, int windowFlags) {
-        if (clearsCompatInsets(windowType, windowFlags, windowingMode)) {
-            return Insets.NONE;
-        }
+        final boolean clearsCompatInsets = clearsCompatInsets(
+                windowType, windowFlags, windowingMode);
         final int softInputAdjustMode = softInputMode & SOFT_INPUT_MASK_ADJUST;
         final int visibleInsetsTypes = softInputAdjustMode != SOFT_INPUT_ADJUST_NOTHING
                 ? systemBars() | ime()
@@ -302,6 +309,9 @@
             if ((source.getType() & visibleInsetsTypes) == 0) {
                 continue;
             }
+            if (clearsCompatInsets && !source.hasFlags(FLAG_FORCE_CONSUMING)) {
+                continue;
+            }
             insets = Insets.max(source.calculateVisibleInsets(frame), insets);
         }
         return insets;
diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java
index 858da55..bb4cc8f 100644
--- a/core/java/android/view/KeyEvent.java
+++ b/core/java/android/view/KeyEvent.java
@@ -2066,6 +2066,7 @@
             case KeyEvent.KEYCODE_STEM_2:
             case KeyEvent.KEYCODE_STEM_3:
             case KeyEvent.KEYCODE_WAKEUP:
+            case KeyEvent.KEYCODE_STEM_PRIMARY:
                 return true;
         }
         return false;
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index 1af8ca2..cdf5eec3 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -1285,6 +1285,8 @@
      * </ul>
      * These values are relative to the state from the last event, not accumulated, so developers
      * should make sure to process this axis value for all batched historical events.
+     * <p>
+     * This axis is only set on the first pointer in a motion event.
      */
     public static final int AXIS_GESTURE_X_OFFSET = 48;
 
@@ -1304,6 +1306,8 @@
      * </ul>
      * These values are relative to the state from the last event, not accumulated, so developers
      * should make sure to process this axis value for all batched historical events.
+     * <p>
+     * This axis is only set on the first pointer in a motion event.
      */
     public static final int AXIS_GESTURE_SCROLL_X_DISTANCE = 50;
 
@@ -1324,14 +1328,29 @@
      * </ul>
      * These values are relative to the state from the last event, not accumulated, so developers
      * should make sure to process this axis value for all batched historical events.
+     * <p>
+     * This axis is only set on the first pointer in a motion event.
      */
     public static final int AXIS_GESTURE_PINCH_SCALE_FACTOR = 52;
 
+    /**
+     * Axis constant: the number of fingers being used in a multi-finger swipe gesture.
+     * <p>
+     * <ul>
+     * <li>For a touch pad, reports the number of fingers being used in a multi-finger swipe gesture
+     * (with CLASSIFICATION_MULTI_FINGER_SWIPE).
+     * </ul>
+     * <p>
+     * Since CLASSIFICATION_MULTI_FINGER_SWIPE is a hidden API, so is this axis. It is only set on
+     * the first pointer in a motion event.
+     * @hide
+     */
+    public static final int AXIS_GESTURE_SWIPE_FINGER_COUNT = 53;
+
     // NOTE: If you add a new axis here you must also add it to:
     //  frameworks/native/include/android/input.h
     //  frameworks/native/libs/input/InputEventLabels.cpp
-    //  platform/cts/tests/tests/view/src/android/view/cts/MotionEventTest.java
-    //    (testAxisFromToString)
+    //  cts/tests/tests/view/src/android/view/cts/MotionEventTest.java (testAxisFromToString)
 
     // Symbolic names of all axes.
     private static final SparseArray<String> AXIS_SYMBOLIC_NAMES = new SparseArray<String>();
@@ -1387,6 +1406,7 @@
         names.append(AXIS_GESTURE_SCROLL_X_DISTANCE, "AXIS_GESTURE_SCROLL_X_DISTANCE");
         names.append(AXIS_GESTURE_SCROLL_Y_DISTANCE, "AXIS_GESTURE_SCROLL_Y_DISTANCE");
         names.append(AXIS_GESTURE_PINCH_SCALE_FACTOR, "AXIS_GESTURE_PINCH_SCALE_FACTOR");
+        names.append(AXIS_GESTURE_SWIPE_FINGER_COUNT, "AXIS_GESTURE_SWIPE_FINGER_COUNT");
     }
 
     /**
@@ -2437,27 +2457,6 @@
      * @return Returns the time this event occurred,
      * in the {@link android.os.SystemClock#uptimeMillis} time base but with
      * nanosecond precision.
-     *
-     * @hide
-     */
-    @UnsupportedAppUsage(publicAlternatives =
-            "Use {@link #getEventTimeNanos()} public API instead.",
-            maxTargetSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
-    public final long getEventTimeNano() {
-        return nativeGetEventTimeNanos(mNativePtr, HISTORY_CURRENT);
-    }
-
-    /**
-     * Retrieve the time this event occurred,
-     * in the {@link android.os.SystemClock#uptimeMillis} time base but with
-     * nanosecond precision.
-     * <p>
-     * The value is in nanosecond precision but it may not have nanosecond accuracy.
-     * </p>
-     *
-     * @return Returns the time this event occurred,
-     * in the {@link android.os.SystemClock#uptimeMillis} time base but with
-     * nanosecond precision.
      */
     @Override
     public long getEventTimeNanos() {
diff --git a/core/java/android/view/NotificationTopLineView.java b/core/java/android/view/NotificationTopLineView.java
index bd20f5b..a2919f5 100644
--- a/core/java/android/view/NotificationTopLineView.java
+++ b/core/java/android/view/NotificationTopLineView.java
@@ -21,6 +21,7 @@
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.graphics.Rect;
+import android.os.Trace;
 import android.util.AttributeSet;
 import android.widget.RemoteViews;
 
@@ -106,6 +107,7 @@
 
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        Trace.beginSection("NotificationTopLineView#onMeasure");
         final int givenWidth = MeasureSpec.getSize(widthMeasureSpec);
         final int givenHeight = MeasureSpec.getSize(heightMeasureSpec);
         final boolean wrapHeight = MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST;
@@ -161,6 +163,7 @@
                     .finish();
         }
         setMeasuredDimension(givenWidth, wrapHeight ? maxChildHeight : givenHeight);
+        Trace.endSection();
     }
 
     @Override
diff --git a/core/java/android/view/PointerIcon.java b/core/java/android/view/PointerIcon.java
index d88994b..fee88d91 100644
--- a/core/java/android/view/PointerIcon.java
+++ b/core/java/android/view/PointerIcon.java
@@ -146,7 +146,14 @@
     // conflicts with any system types that may be defined in the future.
     private static final int TYPE_OEM_FIRST = 10000;
 
-    /** The default pointer icon. */
+    /**
+     * The default pointer icon.
+     * @deprecated This is the same as using {@link #TYPE_ARROW}. Use {@link #TYPE_ARROW} to
+     *     explicitly show an arrow, or use a {@code null} {@link PointerIcon} with
+     *     {@link View#setPointerIcon(PointerIcon)} or
+     *     {@link View#onResolvePointerIcon(MotionEvent, int)} instead to show
+     *     the default pointer icon.
+     */
     public static final int TYPE_DEFAULT = TYPE_ARROW;
 
     private static final PointerIcon gNullIcon = new PointerIcon(TYPE_NULL);
diff --git a/core/java/android/view/RoundScrollbarRenderer.java b/core/java/android/view/RoundScrollbarRenderer.java
index df9e23e..5f6d5e2 100644
--- a/core/java/android/view/RoundScrollbarRenderer.java
+++ b/core/java/android/view/RoundScrollbarRenderer.java
@@ -21,25 +21,37 @@
 import android.graphics.Paint;
 import android.graphics.Rect;
 import android.graphics.RectF;
+import android.util.DisplayMetrics;
 
 /**
  * Helper class for drawing round scroll bars on round Wear devices.
  */
 class RoundScrollbarRenderer {
     // The range of the scrollbar position represented as an angle in degrees.
-    private static final int SCROLLBAR_ANGLE_RANGE = 90;
-    private static final int MAX_SCROLLBAR_ANGLE_SWIPE = 16;
-    private static final int MIN_SCROLLBAR_ANGLE_SWIPE = 6;
-    private static final float WIDTH_PERCENTAGE = 0.02f;
-    private static final int DEFAULT_THUMB_COLOR = 0xFFE8EAED;
+    private static final float SCROLLBAR_ANGLE_RANGE = 28.8f;
+    private static final float MAX_SCROLLBAR_ANGLE_SWIPE = 26.3f; // 90%
+    private static final float MIN_SCROLLBAR_ANGLE_SWIPE = 3.1f; // 10%
+    private static final float THUMB_WIDTH_DP = 4f;
+    private static final float OUTER_PADDING_DP = 2f;
+    private static final int DEFAULT_THUMB_COLOR = 0xFFFFFFFF;
     private static final int DEFAULT_TRACK_COLOR = 0x4CFFFFFF;
 
+    // Rate at which the scrollbar will resize itself when the size of the view changes
+    private static final float RESIZING_RATE = 0.8f;
+    // Threshold at which the scrollbar will stop resizing smoothly and jump to the correct size
+    private static final int RESIZING_THRESHOLD_PX = 20;
+
     private final Paint mThumbPaint = new Paint();
     private final Paint mTrackPaint = new Paint();
     private final RectF mRect = new RectF();
     private final View mParent;
     private final int mMaskThickness;
 
+    private float mPreviousMaxScroll = 0;
+    private float mMaxScrollDiff = 0;
+    private float mPreviousCurrentScroll = 0;
+    private float mCurrentScrollDiff = 0;
+
     public RoundScrollbarRenderer(View parent) {
         // Paints for the round scrollbar.
         // Set up the thumb paint
@@ -61,19 +73,50 @@
                 com.android.internal.R.dimen.circular_display_mask_thickness);
     }
 
-    public void drawRoundScrollbars(Canvas canvas, float alpha, Rect bounds) {
+    public void drawRoundScrollbars(Canvas canvas, float alpha, Rect bounds, boolean drawToLeft) {
         if (alpha == 0) {
             return;
         }
         // Get information about the current scroll state of the parent view.
         float maxScroll = mParent.computeVerticalScrollRange();
         float scrollExtent = mParent.computeVerticalScrollExtent();
-        if (scrollExtent <= 0 || maxScroll <= scrollExtent) {
+        float newScroll = mParent.computeVerticalScrollOffset();
+
+        if (scrollExtent <= 0) {
+            if (!mParent.canScrollVertically(1) && !mParent.canScrollVertically(-1)) {
+                return;
+            } else {
+                scrollExtent = 0;
+            }
+        } else if (maxScroll <= scrollExtent) {
             return;
         }
-        float currentScroll = Math.max(0, mParent.computeVerticalScrollOffset());
-        float linearThumbLength = mParent.computeVerticalScrollExtent();
-        float thumbWidth = mParent.getWidth() * WIDTH_PERCENTAGE;
+
+        // Make changes to the VerticalScrollRange happen gradually
+        if (Math.abs(maxScroll - mPreviousMaxScroll) > RESIZING_THRESHOLD_PX
+                && mPreviousMaxScroll != 0) {
+            mMaxScrollDiff += maxScroll - mPreviousMaxScroll;
+            mCurrentScrollDiff += newScroll - mPreviousCurrentScroll;
+        }
+
+        mPreviousMaxScroll = maxScroll;
+        mPreviousCurrentScroll = newScroll;
+
+        if (Math.abs(mMaxScrollDiff) > RESIZING_THRESHOLD_PX
+                || Math.abs(mCurrentScrollDiff) > RESIZING_THRESHOLD_PX) {
+            mMaxScrollDiff *= RESIZING_RATE;
+            mCurrentScrollDiff *= RESIZING_RATE;
+
+            maxScroll -= mMaxScrollDiff;
+            newScroll -= mCurrentScrollDiff;
+        } else {
+            mMaxScrollDiff = 0;
+            mCurrentScrollDiff = 0;
+        }
+
+        float currentScroll = Math.max(0, newScroll);
+        float linearThumbLength = scrollExtent;
+        float thumbWidth = dpToPx(THUMB_WIDTH_DP);
         mThumbPaint.setStrokeWidth(thumbWidth);
         mTrackPaint.setStrokeWidth(thumbWidth);
 
@@ -85,9 +128,9 @@
         sweepAngle = clamp(sweepAngle, MIN_SCROLLBAR_ANGLE_SWIPE, MAX_SCROLLBAR_ANGLE_SWIPE);
         // Normalize the start angle so that it falls on the track.
         float startAngle = (currentScroll * (SCROLLBAR_ANGLE_RANGE - sweepAngle))
-                / (maxScroll - linearThumbLength) - SCROLLBAR_ANGLE_RANGE / 2;
-        startAngle = clamp(startAngle, -SCROLLBAR_ANGLE_RANGE / 2,
-                SCROLLBAR_ANGLE_RANGE / 2 - sweepAngle);
+                / (maxScroll - linearThumbLength) - SCROLLBAR_ANGLE_RANGE / 2f;
+        startAngle = clamp(startAngle, -SCROLLBAR_ANGLE_RANGE / 2f,
+                SCROLLBAR_ANGLE_RANGE / 2f - sweepAngle);
 
         // Draw the track and the thumb.
         float inset = thumbWidth / 2 + mMaskThickness;
@@ -96,9 +139,26 @@
                 bounds.top + inset,
                 bounds.right - inset,
                 bounds.bottom - inset);
-        canvas.drawArc(mRect, -SCROLLBAR_ANGLE_RANGE / 2, SCROLLBAR_ANGLE_RANGE, false,
-                mTrackPaint);
-        canvas.drawArc(mRect, startAngle, sweepAngle, false, mThumbPaint);
+
+        if (drawToLeft) {
+            canvas.drawArc(mRect, 180 + SCROLLBAR_ANGLE_RANGE / 2f, -SCROLLBAR_ANGLE_RANGE, false,
+                    mTrackPaint);
+            canvas.drawArc(mRect, 180 - startAngle, -sweepAngle, false, mThumbPaint);
+        } else {
+            canvas.drawArc(mRect, -SCROLLBAR_ANGLE_RANGE / 2f, SCROLLBAR_ANGLE_RANGE, false,
+                    mTrackPaint);
+            canvas.drawArc(mRect, startAngle, sweepAngle, false, mThumbPaint);
+        }
+    }
+
+    void getRoundVerticalScrollBarBounds(Rect bounds) {
+        float padding = dpToPx(OUTER_PADDING_DP);
+        final int width = mParent.mRight - mParent.mLeft;
+        final int height = mParent.mBottom - mParent.mTop;
+        bounds.left = mParent.mScrollX + (int) padding;
+        bounds.top = mParent.mScrollY + (int) padding;
+        bounds.right = mParent.mScrollX + width - (int) padding;
+        bounds.bottom = mParent.mScrollY + height - (int) padding;
     }
 
     private static float clamp(float val, float min, float max) {
@@ -127,4 +187,9 @@
             mTrackPaint.setColor(trackColor);
         }
     }
+
+    private float dpToPx(float dp) {
+        return dp * ((float) mParent.getContext().getResources().getDisplayMetrics().densityDpi)
+                / DisplayMetrics.DENSITY_DEFAULT;
+    }
 }
diff --git a/core/java/android/view/ScrollFeedbackProvider.java b/core/java/android/view/ScrollFeedbackProvider.java
new file mode 100644
index 0000000..8d3491d
--- /dev/null
+++ b/core/java/android/view/ScrollFeedbackProvider.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 android.view;
+
+/**
+ * Interface to represent an entity giving consistent feedback for different events surrounding view
+ * scroll.
+ *
+ * @hide
+ */
+public interface ScrollFeedbackProvider {
+    /**
+     * The view has snapped to an item, with a motion from a given {@link MotionEvent} on a given
+     * {@code axis}.
+     *
+     * <p>The interface is not aware of the internal scroll states of the view for which scroll
+     * feedback is played. As such, the client should call
+     * {@link #onScrollLimit(MotionEvent, int, int)} when scrolling has reached limit.
+     *
+     * @param event the {@link MotionEvent} that caused the item to snap.
+     * @param axis the axis of {@code event} that caused the item to snap.
+     */
+    void onSnapToItem(MotionEvent event, int axis);
+
+    /**
+     * The view has reached the scroll limit when scrolled by the motion from a given
+     * {@link MotionEvent} on a given {@code axis}.
+     *
+     * @param event the {@link MotionEvent} that caused scrolling to hit the limit.
+     * @param axis the axis of {@code event} that caused scrolling to hit the limit.
+     * @param isStart {@code true} if scrolling hit limit at the start of the scrolling list, and
+     *                {@code false} if the scrolling hit limit at the end of the scrolling list.
+     */
+    void onScrollLimit(MotionEvent event, int axis, boolean isStart);
+
+    /**
+     * The view has scrolled by {@code deltaInPixels} due to the motion from a given
+     * {@link MotionEvent} on a given {@code axis}.
+     *
+     * <p>The interface is not aware of the internal scroll states of the view for which scroll
+     * feedback is played. As such, the client should call
+     * {@link #onScrollLimit(MotionEvent, int, int)} when scrolling has reached limit.
+     *
+     * @param event the {@link MotionEvent} that caused scroll progress.
+     * @param axis the axis of {@code event} that caused scroll progress.
+     * @param deltaInPixels the amount of scroll progress, in pixels.
+     */
+    void onScrollProgress(MotionEvent event, int axis, int deltaInPixels);
+}
diff --git a/core/java/android/view/SurfaceControlViewHost.java b/core/java/android/view/SurfaceControlViewHost.java
index c8cf7d9..effc127 100644
--- a/core/java/android/view/SurfaceControlViewHost.java
+++ b/core/java/android/view/SurfaceControlViewHost.java
@@ -54,7 +54,7 @@
     private final static String TAG = "SurfaceControlViewHost";
     private final ViewRootImpl mViewRoot;
     private final CloseGuard mCloseGuard = CloseGuard.get();
-    private WindowlessWindowManager mWm;
+    private final WindowlessWindowManager mWm;
 
     private SurfaceControl mSurfaceControl;
     private IAccessibilityEmbeddedConnection mAccessibilityEmbeddedConnection;
@@ -67,9 +67,7 @@
                 return;
             }
             mViewRoot.mHandler.post(() -> {
-                if (mWm != null) {
-                    mWm.setConfiguration(configuration);
-                }
+                mWm.setConfiguration(configuration);
                 if (mViewRoot != null) {
                     mViewRoot.forceWmRelayout();
                 }
@@ -116,6 +114,11 @@
             }
             return null;
         }
+
+        @Override
+        public void attachParentInterface(@Nullable ISurfaceControlViewHostParent parentInterface) {
+            mViewRoot.mHandler.post(() -> mWm.setParentInterface(parentInterface));
+        }
     }
 
     private ISurfaceControlViewHost mRemoteInterface = new ISurfaceControlViewHostImpl();
@@ -148,10 +151,11 @@
         private SurfaceControl mSurfaceControl;
         private final IAccessibilityEmbeddedConnection mAccessibilityEmbeddedConnection;
         private final IBinder mInputToken;
+        @NonNull
         private final ISurfaceControlViewHost mRemoteInterface;
 
         SurfacePackage(SurfaceControl sc, IAccessibilityEmbeddedConnection connection,
-               IBinder inputToken, ISurfaceControlViewHost ri) {
+                IBinder inputToken, @NonNull ISurfaceControlViewHost ri) {
             mSurfaceControl = sc;
             mAccessibilityEmbeddedConnection = connection;
             mInputToken = inputToken;
@@ -213,6 +217,7 @@
         /**
          * @hide
          */
+        @NonNull
         public ISurfaceControlViewHost getRemoteInterface() {
             return mRemoteInterface;
         }
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 0e4cf89..5b69d7f 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -16,6 +16,7 @@
 
 package android.view;
 
+import static android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
 import static android.view.WindowManagerPolicyConstants.APPLICATION_MEDIA_OVERLAY_SUBLAYER;
 import static android.view.WindowManagerPolicyConstants.APPLICATION_MEDIA_SUBLAYER;
 import static android.view.WindowManagerPolicyConstants.APPLICATION_PANEL_SUBLAYER;
@@ -40,6 +41,7 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
+import android.os.RemoteException;
 import android.os.SystemClock;
 import android.util.ArraySet;
 import android.util.AttributeSet;
@@ -54,6 +56,8 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.concurrent.ConcurrentLinkedQueue;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.locks.ReentrantLock;
 import java.util.function.Consumer;
@@ -302,6 +306,26 @@
     private SurfaceControl mBlastSurfaceControl;
     private BLASTBufferQueue mBlastBufferQueue;
 
+    private final ConcurrentLinkedQueue<WindowManager.LayoutParams> mEmbeddedWindowParams =
+            new ConcurrentLinkedQueue<>();
+
+    private final ISurfaceControlViewHostParent mSurfaceControlViewHostParent =
+            new ISurfaceControlViewHostParent.Stub() {
+        @Override
+        public void updateParams(WindowManager.LayoutParams[] childAttrs) {
+            mEmbeddedWindowParams.clear();
+            mEmbeddedWindowParams.addAll(Arrays.asList(childAttrs));
+
+            if (isAttachedToWindow()) {
+                runOnUiThread(() -> {
+                    if (mParent != null) {
+                        mParent.recomputeViewAttributes(SurfaceView.this);
+                    }
+                });
+            }
+        }
+    };
+
     public SurfaceView(Context context) {
         this(context, null);
     }
@@ -801,9 +825,18 @@
                 mBlastSurfaceControl = null;
             }
 
-            if (releaseSurfacePackage && mSurfacePackage != null) {
-                mSurfacePackage.release();
-                mSurfacePackage = null;
+            if (mSurfacePackage != null) {
+                try {
+                    mSurfacePackage.getRemoteInterface().attachParentInterface(null);
+                    mEmbeddedWindowParams.clear();
+                } catch (RemoteException e) {
+                    Log.d(TAG, "Failed to remove parent interface from SCVH. Likely SCVH is "
+                            + "already dead");
+                }
+                if (releaseSurfacePackage) {
+                    mSurfacePackage.release();
+                    mSurfacePackage = null;
+                }
             }
 
             applyTransactionOnVriDraw(transaction);
@@ -1307,7 +1340,7 @@
             mSurfaceControl = new SurfaceControl.Builder(mSurfaceSession)
                     .setName(name)
                     .setLocalOwnerView(this)
-                    .setParent(viewRoot.getBoundsLayer())
+                    .setParent(viewRoot.updateAndGetBoundsLayer(surfaceUpdateTransaction))
                     .setCallsite("SurfaceView.updateSurface")
                     .setContainerLayer()
                     .build();
@@ -1854,6 +1887,12 @@
             applyTransactionOnVriDraw(transaction);
         }
         mSurfacePackage = p;
+        try {
+            mSurfacePackage.getRemoteInterface().attachParentInterface(
+                    mSurfaceControlViewHostParent);
+        } catch (RemoteException e) {
+            Log.d(TAG, "Failed to attach parent interface to SCVH. Likely SCVH is already dead.");
+        }
 
         if (isFocused()) {
             requestEmbeddedFocus(true);
@@ -2014,4 +2053,19 @@
             mBlastBufferQueue.mergeWithNextTransaction(transaction, frameNumber);
         }
     }
+
+    @Override
+    void performCollectViewAttributes(AttachInfo attachInfo, int visibility) {
+        super.performCollectViewAttributes(attachInfo, visibility);
+        if (mEmbeddedWindowParams.isEmpty()) {
+            return;
+        }
+
+        for (WindowManager.LayoutParams embeddedWindowAttr : mEmbeddedWindowParams) {
+            if ((embeddedWindowAttr.flags & FLAG_KEEP_SCREEN_ON) == FLAG_KEEP_SCREEN_ON) {
+                attachInfo.mKeepScreenOn = true;
+                break;
+            }
+        }
+    }
 }
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index f1cde3b..6d9f156 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -8075,6 +8075,7 @@
      *
      * @param rectangle The rectangle in the View's content coordinate space
      * @return Whether any parent scrolled.
+     * @see AccessibilityAction#ACTION_SHOW_ON_SCREEN
      */
     public boolean requestRectangleOnScreen(Rect rectangle) {
         return requestRectangleOnScreen(rectangle, false);
@@ -8414,6 +8415,10 @@
      * for the matching <activity> entry in your application’s manifest or updating the title at
      * runtime with{@link android.app.Activity#setTitle(CharSequence)}.
      *
+     * <p>
+     * <b>Note:</b> Use
+     * {@link androidx.core.view.ViewCompat#setAccessibilityPaneTitle(View, CharSequence)}
+     * for backwards-compatibility. </aside>
      * @param accessibilityPaneTitle The pane's title. Setting to {@code null} indicates that this
      *                               View is not a pane.
      *
@@ -8525,13 +8530,19 @@
      * actions like a button press. Label your controls concisely and precisely instead, and for
      * significant UI changes like window changes, use
      * {@link android.app.Activity#setTitle(CharSequence)} and
-     * {@link View#setAccessibilityPaneTitle(CharSequence)}.
+     * {@link #setAccessibilityPaneTitle(CharSequence)}.
      *
      * <p>
-     * Use {@link View#setAccessibilityLiveRegion(int)} to inform the user of changes to critical
+     * Use {@link #setAccessibilityLiveRegion(int)} to inform the user of changes to critical
      * views within the user interface. These should still be used sparingly as they may generate
      * announcements every time a View is updated.
      *
+     * <p>
+     * Use {@link #setStateDescription(CharSequence)} to convey state changes to views within the
+     * user interface. While a live region may send different types of events generated by the view,
+     * state description will send {@link AccessibilityEvent#TYPE_WINDOW_CONTENT_CHANGED} events of
+     * type {@link AccessibilityEvent#CONTENT_CHANGE_TYPE_STATE_DESCRIPTION}.
+     *
      * @param text The announcement text.
      */
     public void announceForAccessibility(CharSequence text) {
@@ -8652,15 +8663,43 @@
     }
 
     /**
-     * Dispatches an {@link AccessibilityEvent} to the {@link View} first and then
-     * to its children for adding their text content to the event. Note that the
-     * event text is populated in a separate dispatch path since we add to the
+     * Dispatches an {@link AccessibilityEvent} to the {@link View} to add the text content of the
+     * view and its children.
+     * <p>
+     * <b>Note:</b> This method should only be used with event.setText().
+     * Avoid mutating other event state in this method. In general, put UI metadata in the node for
+     * services to easily query.
+     * <ul>
+     *     <li> If you are modifying other event properties, you may be eliminating semantics
+     *     accessibility services may want. Instead, send a separate event using
+     *     {@link #sendAccessibilityEvent(int)} and override
+     *     {@link #onInitializeAccessibilityEvent(AccessibilityEvent)}.
+     *     </li>
+     *     <li>If you are checking for type {@link AccessibilityEvent#TYPE_WINDOW_STATE_CHANGED}
+     *     to generate window/title announcements, you may be causing disruptive announcements
+     *     (or making no announcements at all). Instead, follow the practices described in
+     *     {@link View#announceForAccessibility(CharSequence)}. <b>Note:</b> this does not suggest
+     *     calling announceForAccessibility(), but using the suggestions listed in its
+     *     documentation.
+     *     </li>
+     *     <li>If you are making changes based on the state of accessibility, such as checking for
+     *     an event type to trigger a UI update, while well-intentioned, you are creating brittle,
+     *     less well-maintained code that works for some users but not others. Instead, leverage
+     *     existing code for equitable experiences and less technical debt. See
+     *     {@link AccessibilityManager#isEnabled()} for an example.
+     *     </li>
+     * </ul>
+     * <p>
+     * Note that the event text is populated in a separate dispatch path
+     * ({@link #onPopulateAccessibilityEvent(AccessibilityEvent)}) since we add to the
      * event not only the text of the source but also the text of all its descendants.
+     * <p>
      * A typical implementation will call
-     * {@link #onPopulateAccessibilityEvent(AccessibilityEvent)} on the this view
+     * {@link #onPopulateAccessibilityEvent(AccessibilityEvent)} on this view
      * and then call the {@link #dispatchPopulateAccessibilityEvent(AccessibilityEvent)}
-     * on each child. Override this method if custom population of the event text
-     * content is required.
+     * on each child or the first child that is visible. Override this method if custom population
+     * of the event text content is required.
+     *
      * <p>
      * If an {@link AccessibilityDelegate} has been specified via calling
      * {@link #setAccessibilityDelegate(AccessibilityDelegate)} its
@@ -8704,9 +8743,12 @@
     /**
      * Called from {@link #dispatchPopulateAccessibilityEvent(AccessibilityEvent)}
      * giving a chance to this View to populate the accessibility event with its
-     * text content. While this method is free to modify event
-     * attributes other than text content, doing so should normally be performed in
-     * {@link #onInitializeAccessibilityEvent(AccessibilityEvent)}.
+     * text content.
+     * <p>
+     * <b>Note:</b> This method should only be used with event.setText().
+     * Avoid mutating other event state in this method. Instead, follow the practices described in
+     * {@link #dispatchPopulateAccessibilityEvent(AccessibilityEvent)}. In general, put UI
+     * metadata in the node for services to easily query, than in transient events.
      * <p>
      * Example: Adding formatted date string to an accessibility event in addition
      *          to the text added by the super implementation:
@@ -9323,8 +9365,12 @@
             final AccessibilityNodeInfo info = createAccessibilityNodeInfo();
             structure.setChildCount(1);
             final ViewStructure root = structure.newChild(0);
-            populateVirtualStructure(root, provider, info, forAutofill);
-            info.recycle();
+            if (info != null) {
+                populateVirtualStructure(root, provider, info, forAutofill);
+                info.recycle();
+            } else {
+                Log.w(AUTOFILL_LOG_TAG, "AccessibilityNodeInfo is null.");
+            }
         }
     }
 
@@ -11171,6 +11217,8 @@
      *
      * @param stateDescription The state description.
      * @see #getStateDescription()
+     * @see #setContentDescription(CharSequence) for the difference between content and
+     * state descriptions.
      */
     @RemotableViewMethod
     public void setStateDescription(@Nullable CharSequence stateDescription) {
@@ -11242,26 +11290,36 @@
     }
 
     /**
-     * Sets the id of a view before which this one is visited in accessibility traversal.
-     * A screen-reader must visit the content of this view before the content of the one
-     * it precedes. For example, if view B is set to be before view A, then a screen-reader
-     * will traverse the entire content of B before traversing the entire content of A,
-     * regardles of what traversal strategy it is using.
+     * Sets the id of a view that screen readers are requested to visit after this view.
+     *
      * <p>
-     * Views that do not have specified before/after relationships are traversed in order
-     * determined by the screen-reader.
-     * </p>
+     *
+     * For example, if view B should be visited before view A, with
+     * B.setAccessibilityTraversalBefore(A), this requests that screen readers visit and traverse
+     * view B before visiting view A.
+     *
      * <p>
-     * Setting that this view is before a view that is not important for accessibility
-     * or if this view is not important for accessibility will have no effect as the
-     * screen-reader is not aware of unimportant views.
-     * </p>
+     * <b>Note:</b> Views are visited in the order determined by the screen reader. Avoid
+     * explicitly manipulating focus order, as this may result in inconsistent user
+     * experiences between apps. Instead, use other semantics, such as restructuring the view
+     * hierarchy layout, to communicate order.
+     *
+     * <p>
+     * Setting this view to be after a view that is not important for accessibility,
+     * or if this view is not important for accessibility, means this method will have no effect if
+     * the service is not aware of unimportant views.
+     *
+     * <p>
+     * To avoid a risk of loops, set clear relationships between views. For example, if focus order
+     * should be B -> A, and B.setAccessibilityTraversalBefore(A), then also call
+     * A.setAccessibilityTraversalAfter(B).
      *
      * @param beforeId The id of a view this one precedes in accessibility traversal.
      *
      * @attr ref android.R.styleable#View_accessibilityTraversalBefore
      *
      * @see #setImportantForAccessibility(int)
+     * @see #setAccessibilityTraversalAfter(int)
      */
     @RemotableViewMethod
     public void setAccessibilityTraversalBefore(@IdRes int beforeId) {
@@ -11288,26 +11346,34 @@
     }
 
     /**
-     * Sets the id of a view after which this one is visited in accessibility traversal.
-     * A screen-reader must visit the content of the other view before the content of this
-     * one. For example, if view B is set to be after view A, then a screen-reader
-     * will traverse the entire content of A before traversing the entire content of B,
-     * regardles of what traversal strategy it is using.
-     * <p>
-     * Views that do not have specified before/after relationships are traversed in order
-     * determined by the screen-reader.
-     * </p>
-     * <p>
-     * Setting that this view is after a view that is not important for accessibility
-     * or if this view is not important for accessibility will have no effect as the
-     * screen-reader is not aware of unimportant views.
-     * </p>
+     * Sets the id of a view that screen readers are requested to visit before this view.
      *
-     * @param afterId The id of a view this one succedees in accessibility traversal.
+     * <p>
+     * For example, if view B should be visited after A, with B.setAccessibilityTraversalAfter(A),
+     * then this requests that screen readers visit and traverse view A before visiting view B.
+     *
+     * <p>
+     * <b>Note:</b> Views are visited in the order determined by the screen reader. Avoid
+     * explicitly manipulating focus order, as this may result in inconsistent user
+     * experiences between apps. Instead, use other semantics, such as restructuring the view
+     * hierarchy layout, to communicate order.
+     *
+     * <p>
+     * Setting this view to be after a view that is not important for accessibility,
+     * or if this view is not important for accessibility, means this method will have no effect if
+     * the service is not aware of unimportant views.
+     *
+     * <p>
+     * To avoid a risk of loops, set clear relationships between views. For example, if focus order
+     * should be B -> A, and B.setAccessibilityTraversalBefore(A), then also call
+     * A.setAccessibilityTraversalAfter(B).
+     *
+     * @param afterId The id of a view this one succeeds in accessibility traversal.
      *
      * @attr ref android.R.styleable#View_accessibilityTraversalAfter
      *
      * @see #setImportantForAccessibility(int)
+     * @see #setAccessibilityTraversalBefore(int)
      */
     @RemotableViewMethod
     public void setAccessibilityTraversalAfter(@IdRes int afterId) {
@@ -13706,6 +13772,10 @@
     /**
      * Returns whether the view should be treated as a focusable unit by screen reader
      * accessibility tools.
+     * <p>
+     * <b>Note:</b> Use
+     * {@link androidx.core.view.ViewCompat#setScreenReaderFocusable(View, boolean)}
+     * for backwards-compatibility. </aside>
      * @see #setScreenReaderFocusable(boolean)
      *
      * @return Whether the view should be treated as a focusable unit by screen reader.
@@ -13751,6 +13821,9 @@
      * Users of some accessibility services can choose to navigate between headings
      * instead of between paragraphs, words, etc. Apps that provide headings on
      * sections of text can help the text navigation experience.
+     * <p>
+     * <b>Note:</b> Use {@link androidx.core.view.ViewCompat#setAccessibilityHeading(View, boolean)}
+     * for backwards-compatibility. </aside>
      *
      * @param isHeading {@code true} if the view is a heading, {@code false} otherwise.
      *
@@ -14598,19 +14671,34 @@
      * to the view's content description or text, or to the content descriptions
      * or text of the view's children (where applicable).
      * <p>
-     * For example, in a login screen with a TextView that displays an "incorrect
-     * password" notification, that view should be marked as a live region with
-     * mode {@link #ACCESSIBILITY_LIVE_REGION_POLITE}.
+     * To indicate that the user should be notified of changes, use
+     * {@link #ACCESSIBILITY_LIVE_REGION_POLITE}. Announcements from this region are queued and
+     * do not disrupt ongoing speech.
+     * <p>
+     * For example, selecting an option in a dropdown menu may update a panel below with the updated
+     * content. This panel may be marked as a live region with
+     * {@link #ACCESSIBILITY_LIVE_REGION_POLITE} to notify users of the change.
+     * <p>
+     * For notifying users about errors, such as in a login screen with text that displays an
+     * "incorrect password" notification, that view should send an AccessibilityEvent of type
+     * {@link AccessibilityEvent#CONTENT_CHANGE_TYPE_ERROR} and set
+     * {@link AccessibilityNodeInfo#setError(CharSequence)} instead. Custom widgets should expose
+     * error-setting methods that support accessibility automatically. For example, instead of
+     * explicitly sending this event when using a TextView, use
+     * {@link android.widget.TextView#setError(CharSequence)}.
      * <p>
      * To disable change notifications for this view, use
      * {@link #ACCESSIBILITY_LIVE_REGION_NONE}. This is the default live region
      * mode for most views.
      * <p>
-     * To indicate that the user should be notified of changes, use
-     * {@link #ACCESSIBILITY_LIVE_REGION_POLITE}.
-     * <p>
      * If the view's changes should interrupt ongoing speech and notify the user
-     * immediately, use {@link #ACCESSIBILITY_LIVE_REGION_ASSERTIVE}.
+     * immediately, use {@link #ACCESSIBILITY_LIVE_REGION_ASSERTIVE}. This may result in disruptive
+     * announcements from an accessibility service, so it should generally be used only to convey
+     * information that is time-sensitive or critical for use of the application. Examples may
+     * include an incoming call or an emergency alert.
+     * <p>
+     * <b>Note:</b> Use {@link androidx.core.view.ViewCompat#setAccessibilityLiveRegion(View, int)}
+     * for backwards-compatibility. </aside>
      *
      * @param mode The live region mode for this view, one of:
      *        <ul>
@@ -14742,7 +14830,8 @@
      * view satisfies any of the following:
      * <ul>
      * <li>Is actionable, e.g. {@link #isClickable()},
-     * {@link #isLongClickable()}, or {@link #isFocusable()}
+     * {@link #isLongClickable()}, {@link #isContextClickable()},
+     * {@link #isScreenReaderFocusable()}, or {@link #isFocusable()}
      * <li>Has an {@link AccessibilityDelegate}
      * <li>Has an interaction listener, e.g. {@link OnTouchListener},
      * {@link OnKeyListener}, etc.
@@ -14751,6 +14840,7 @@
      * {@link #ACCESSIBILITY_LIVE_REGION_NONE}.
      * </ul>
      * <li>Has an accessibility pane title, see {@link #setAccessibilityPaneTitle}</li>
+     * <li>Is an accessibility heading, see {@link #setAccessibilityHeading(boolean)}.</li>
      * </ol>
      *
      * @return Whether the view is exposed for accessibility.
@@ -14775,9 +14865,9 @@
         }
 
         return mode == IMPORTANT_FOR_ACCESSIBILITY_YES || isActionableForAccessibility()
-                || hasListenersForAccessibility() || getAccessibilityNodeProvider() != null
+                || hasListenersForAccessibility() || mAccessibilityDelegate != null
                 || getAccessibilityLiveRegion() != ACCESSIBILITY_LIVE_REGION_NONE
-                || isAccessibilityPane();
+                || isAccessibilityPane() || isAccessibilityHeading();
     }
 
     /**
@@ -14937,7 +15027,8 @@
      * @hide
      */
     public boolean isActionableForAccessibility() {
-        return (isClickable() || isLongClickable() || isFocusable());
+        return (isClickable() || isLongClickable() || isFocusable() || isContextClickable()
+                || isScreenReaderFocusable());
     }
 
     /**
@@ -14956,8 +15047,8 @@
     /**
      * Notifies that the accessibility state of this view changed. The change
      * is local to this view and does not represent structural changes such
-     * as children and parent. For example, the view became focusable. The
-     * notification is at at most once every
+     * as children and parent. For example, the view became focusable. Some of
+     * the notification is at at most once every
      * {@link ViewConfiguration#getSendRecurringAccessibilityEventsInterval()}
      * to avoid unnecessary load to the system. Also once a view has a pending
      * notification this method is a NOP until the notification has been sent.
@@ -15019,7 +15110,7 @@
     /**
      * Notifies that the accessibility state of this view changed. The change
      * is *not* local to this view and does represent structural changes such
-     * as children and parent. For example, the view size changed. The
+     * as children and parent. For example, the view size changed. Some of the
      * notification is at at most once every
      * {@link ViewConfiguration#getSendRecurringAccessibilityEventsInterval()}
      * to avoid unnecessary load to the system. Also once a view has a pending
@@ -21166,21 +21257,11 @@
         if (mRoundScrollbarRenderer == null) {
             getStraightVerticalScrollBarBounds(bounds, touchBounds);
         } else {
-            getRoundVerticalScrollBarBounds(bounds != null ? bounds : touchBounds);
+            mRoundScrollbarRenderer.getRoundVerticalScrollBarBounds(
+                    bounds != null ? bounds : touchBounds);
         }
     }
 
-    private void getRoundVerticalScrollBarBounds(Rect bounds) {
-        final int width = mRight - mLeft;
-        final int height = mBottom - mTop;
-        // Do not take padding into account as we always want the scrollbars
-        // to hug the screen for round wearable devices.
-        bounds.left = mScrollX;
-        bounds.top = mScrollY;
-        bounds.right = bounds.left + width;
-        bounds.bottom = mScrollY + height;
-    }
-
     private void getStraightVerticalScrollBarBounds(@Nullable Rect drawBounds,
             @Nullable Rect touchBounds) {
         final Rect bounds = drawBounds != null ? drawBounds : touchBounds;
@@ -21290,8 +21371,14 @@
                 if (drawVerticalScrollBar) {
                     final Rect bounds = cache.mScrollBarBounds;
                     getVerticalScrollBarBounds(bounds, null);
+                    boolean shouldDrawScrollbarAtLeft =
+                            (mVerticalScrollbarPosition == SCROLLBAR_POSITION_LEFT)
+                                    || (mVerticalScrollbarPosition == SCROLLBAR_POSITION_DEFAULT
+                                    && isLayoutRtl());
+
                     mRoundScrollbarRenderer.drawRoundScrollbars(
-                            canvas, (float) cache.scrollBar.getAlpha() / 255f, bounds);
+                            canvas, (float) cache.scrollBar.getAlpha() / 255f, bounds,
+                            shouldDrawScrollbarAtLeft);
                     if (invalidate) {
                         invalidate();
                     }
@@ -21821,6 +21908,8 @@
         mCurrentAnimation = null;
 
         if ((mViewFlags & TOOLTIP) == TOOLTIP) {
+            removeCallbacks(mTooltipInfo.mShowTooltipRunnable);
+            removeCallbacks(mTooltipInfo.mHideTooltipRunnable);
             hideTooltip();
         }
 
@@ -22443,6 +22532,22 @@
     }
 
     /**
+     * Configure the {@link android.graphics.RenderEffect} to apply to the backdrop contents of this
+     * View. This will apply a visual effect to the result of the backdrop contents of this View
+     * before it is drawn. For example if
+     * {@link RenderEffect#createBlurEffect(float, float, RenderEffect, Shader.TileMode)}
+     * is provided, the previous content behind this View will be blurred before this View is drawn.
+     * @param renderEffect to be applied to the View. Passing null clears the previously configured
+     *                     {@link RenderEffect}
+     * @hide
+     */
+    public void setBackdropRenderEffect(@Nullable RenderEffect renderEffect) {
+        if (mRenderNode.setBackdropRenderEffect(renderEffect)) {
+            invalidateViewProperty(true, true);
+        }
+    }
+
+    /**
      * Updates the {@link Paint} object used with the current layer (used only if the current
      * layer type is not set to {@link #LAYER_TYPE_NONE}). Changed properties of the Paint
      * provided to {@link #setLayerType(int, android.graphics.Paint)} will be used the next time
@@ -31408,6 +31513,11 @@
      * Starting in {@link android.os.Build.VERSION_CODES#M API 23}, delegate
      * methods are called <i>after</i> host methods, which all properties to be
      * modified without being overwritten by the host class.
+     * <aside class="note">
+     * <b>Note:</b> Use a {@link androidx.core.view.AccessibilityDelegateCompat}
+     * wrapper instead of this class for backwards-compatibility.
+     * </aside>
+     *
      */
     public static class AccessibilityDelegate {
 
@@ -31999,9 +32109,7 @@
             return false;
         }
 
-        getLocationInWindow(mAttachInfo.mTmpLocation);
-        return mAttachInfo.mTmpLocation[0] == insets.getStableInsetLeft()
-                && mAttachInfo.mTmpLocation[1] == insets.getStableInsetTop();
+        return true;
     }
 
     /**
diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java
index d80819f..3c1ad06 100644
--- a/core/java/android/view/ViewConfiguration.java
+++ b/core/java/android/view/ViewConfiguration.java
@@ -85,9 +85,9 @@
     private static final int DEFAULT_MULTI_PRESS_TIMEOUT = 300;
 
     /**
-     * Defines the time between successive key repeats in milliseconds.
+     * Defines the default duration between successive key repeats in milliseconds.
      */
-    private static final int KEY_REPEAT_DELAY = 50;
+    private static final int DEFAULT_KEY_REPEAT_DELAY_MS = 50;
 
     /**
      * Defines the duration in milliseconds a user needs to hold down the
@@ -669,14 +669,19 @@
      * @return the time before the first key repeat in milliseconds.
      */
     public static int getKeyRepeatTimeout() {
-        return getLongPressTimeout();
+        // 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 use the long press timeout as default value.
+        return AppGlobals.getIntCoreSetting(Settings.Secure.KEY_REPEAT_TIMEOUT_MS,
+                getLongPressTimeout());
     }
 
     /**
      * @return the time between successive key repeats in milliseconds.
      */
     public static int getKeyRepeatDelay() {
-        return KEY_REPEAT_DELAY;
+        return AppGlobals.getIntCoreSetting(Settings.Secure.KEY_REPEAT_DELAY_MS,
+                DEFAULT_KEY_REPEAT_DELAY_MS);
     }
 
     /**
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index d457847..01a99b9 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -2043,7 +2043,10 @@
         final float x = event.getXDispatchLocation(pointerIndex);
         final float y = event.getYDispatchLocation(pointerIndex);
         if (isOnScrollbarThumb(x, y) || isDraggingScrollBar()) {
-            return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_ARROW);
+            // Return null here so that it fallbacks to the default PointerIcon for the source
+            // device. For mouse, the default PointerIcon is PointerIcon.TYPE_ARROW.
+            // For stylus, the default PointerIcon is PointerIcon.TYPE_NULL.
+            return null;
         }
         // Check what the child under the pointer says about the pointer.
         final int childrenCount = mChildrenCount;
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index a28be4b..379d1d87 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -220,7 +220,6 @@
 import com.android.internal.os.SomeArgs;
 import com.android.internal.policy.DecorView;
 import com.android.internal.policy.PhoneFallbackEventHandler;
-import com.android.internal.util.Preconditions;
 import com.android.internal.view.BaseSurfaceHolder;
 import com.android.internal.view.RootViewSurfaceTaker;
 import com.android.internal.view.SurfaceCallbackHelper;
@@ -716,9 +715,9 @@
 
     /**
      * Child container layer of {@code mSurface} with the same bounds as its parent, and cropped to
-     * the surface insets. This surface is created only if a client requests it via {@link
-     * #getBoundsLayer()}. By parenting to this bounds surface, child surfaces can ensure they do
-     * not draw into the surface inset region set by the parent window.
+     * the surface insets. This surface is created only if a client requests it via
+     * {@link #updateAndGetBoundsLayer(Transaction)}. By parenting to this bounds surface, child
+     * surfaces can ensure they do not draw into the surface inset region set by the parent window.
      */
     private SurfaceControl mBoundsLayer;
     private final SurfaceSession mSurfaceSession = new SurfaceSession();
@@ -1900,9 +1899,10 @@
                 && !Objects.equals(mTmpFrames.attachedFrame, attachedFrame);
         final boolean displayChanged = mDisplay.getDisplayId() != displayId;
         final boolean compatScaleChanged = mTmpFrames.compatScale != compatScale;
+        final boolean dragResizingChanged = mPendingDragResizing != dragResizing;
         if (msg == MSG_RESIZED && !frameChanged && !configChanged && !attachedFrameChanged
                 && !displayChanged && !forceNextWindowRelayout
-                && !compatScaleChanged) {
+                && !compatScaleChanged && !dragResizingChanged) {
             return;
         }
 
@@ -2261,7 +2261,7 @@
      * <p>Parenting to this layer will ensure that its children are cropped by the view's surface
      * insets.
      */
-    public SurfaceControl getBoundsLayer() {
+    public SurfaceControl updateAndGetBoundsLayer(Transaction t) {
         if (mBoundsLayer == null) {
             mBoundsLayer = new SurfaceControl.Builder(mSurfaceSession)
                     .setContainerLayer()
@@ -2269,8 +2269,8 @@
                     .setParent(getSurfaceControl())
                     .setCallsite("ViewRootImpl.getBoundsLayer")
                     .build();
-            setBoundsLayerCrop(mTransaction);
-            mTransaction.show(mBoundsLayer).apply();
+            setBoundsLayerCrop(t);
+            t.show(mBoundsLayer);
         }
        return mBoundsLayer;
     }
@@ -3594,6 +3594,11 @@
                         mTmpLocation[1] + host.mBottom - host.mTop);
 
                 host.gatherTransparentRegion(mTransparentRegion);
+                final Rect bounds = mAttachInfo.mTmpInvalRect;
+                if (getAccessibilityFocusedRect(bounds)) {
+                  host.applyDrawableToTransparentRegion(getAccessibilityFocusedDrawable(),
+                      mTransparentRegion);
+                }
                 if (mTranslator != null) {
                     mTranslator.translateRegionInWindowToScreen(mTransparentRegion);
                 }
@@ -3819,7 +3824,7 @@
                 }
                 mPendingTransitions.clear();
             }
-            if (!performDraw() && mActiveSurfaceSyncGroup != null) {
+            if (!performDraw(mActiveSurfaceSyncGroup) && mActiveSurfaceSyncGroup != null) {
                 mActiveSurfaceSyncGroup.markSyncReady();
             }
         }
@@ -3854,7 +3859,15 @@
         mWmsRequestSyncGroupState = WMS_SYNC_PENDING;
         mWmsRequestSyncGroup = new SurfaceSyncGroup("wmsSync-" + mTag, t -> {
             mWmsRequestSyncGroupState = WMS_SYNC_MERGED;
-            reportDrawFinished(t, seqId);
+            // See b/286355097. If the current process is not system, then invoking finishDraw on
+            // any thread is fine since once it calls into system process, finishDrawing will run
+            // on a different thread. However, when the current process is system, the finishDraw in
+            // system server will be run on the current thread, which could result in a deadlock.
+            if (mWindowSession instanceof Binder) {
+                reportDrawFinished(t, seqId);
+            } else {
+                mHandler.postAtFrontOfQueue(() -> reportDrawFinished(t, seqId));
+            }
         });
         if (DEBUG_BLAST) {
             Log.d(mTag, "Setup new sync=" + mWmsRequestSyncGroup.getName());
@@ -4129,6 +4142,9 @@
         // implement AccessibilityNodeProvider#findFocus
         AccessibilityNodeInfo current = provider.createAccessibilityNodeInfo(
                 AccessibilityNodeProvider.HOST_VIEW_ID);
+        if (current == null) {
+            return null;
+        }
         if (current.isFocused()) {
             return current;
         }
@@ -4582,6 +4598,10 @@
         });
     }
 
+    /**
+     * These callbacks check if the draw failed for any reason and apply
+     * those transactions directly so they don't get stuck forever.
+     */
     private void registerCallbackForPendingTransactions() {
         Transaction t = new Transaction();
         t.merge(mPendingTransaction);
@@ -4610,7 +4630,7 @@
         });
     }
 
-    private boolean performDraw() {
+    private boolean performDraw(@Nullable SurfaceSyncGroup surfaceSyncGroup) {
         mLastPerformDrawSkippedReason = null;
         if (mAttachInfo.mDisplayState == Display.STATE_OFF && !mReportNextDraw) {
             mLastPerformDrawSkippedReason = "screen_off";
@@ -4620,7 +4640,7 @@
             return false;
         }
 
-        final boolean fullRedrawNeeded = mFullRedrawNeeded || mActiveSurfaceSyncGroup != null;
+        final boolean fullRedrawNeeded = mFullRedrawNeeded || surfaceSyncGroup != null;
         mFullRedrawNeeded = false;
 
         mIsDrawing = true;
@@ -4628,22 +4648,12 @@
 
         addFrameCommitCallbackIfNeeded();
 
-        boolean usingAsyncReport = isHardwareEnabled() && mActiveSurfaceSyncGroup != null;
-        if (usingAsyncReport) {
-            registerCallbacksForSync(mSyncBuffer, mActiveSurfaceSyncGroup);
-        } else if (mHasPendingTransactions) {
-            // These callbacks are only needed if there's no sync involved and there were calls to
-            // applyTransactionOnDraw. These callbacks check if the draw failed for any reason and
-            // apply those transactions directly so they don't get stuck forever.
-            registerCallbackForPendingTransactions();
-        }
-        mHasPendingTransactions = false;
+        boolean usingAsyncReport;
 
         try {
-            boolean canUseAsync = draw(fullRedrawNeeded, usingAsyncReport && mSyncBuffer);
-            if (usingAsyncReport && !canUseAsync) {
+            usingAsyncReport = draw(fullRedrawNeeded, surfaceSyncGroup, mSyncBuffer);
+            if (mAttachInfo.mThreadedRenderer != null && !usingAsyncReport) {
                 mAttachInfo.mThreadedRenderer.setFrameCallback(null);
-                usingAsyncReport = false;
             }
         } finally {
             mIsDrawing = false;
@@ -4681,10 +4691,12 @@
             }
 
             if (mSurfaceHolder != null && mSurface.isValid()) {
-                final SurfaceSyncGroup surfaceSyncGroup = mActiveSurfaceSyncGroup;
-                SurfaceCallbackHelper sch = new SurfaceCallbackHelper(() ->
-                        mHandler.post(() -> surfaceSyncGroup.markSyncReady()));
-                mActiveSurfaceSyncGroup = null;
+                usingAsyncReport = true;
+                SurfaceCallbackHelper sch = new SurfaceCallbackHelper(() -> {
+                    if (surfaceSyncGroup != null) {
+                        surfaceSyncGroup.markSyncReady();
+                    }
+                });
 
                 SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks();
 
@@ -4695,8 +4707,9 @@
                 }
             }
         }
-        if (mActiveSurfaceSyncGroup != null && !usingAsyncReport) {
-            mActiveSurfaceSyncGroup.markSyncReady();
+
+        if (surfaceSyncGroup != null && !usingAsyncReport) {
+            surfaceSyncGroup.markSyncReady();
         }
         if (mPerformContentCapture) {
             performContentCaptureInitialReport();
@@ -4789,7 +4802,8 @@
         }
     }
 
-    private boolean draw(boolean fullRedrawNeeded, boolean forceDraw) {
+    private boolean draw(boolean fullRedrawNeeded,
+            @Nullable SurfaceSyncGroup activeSyncGroup, boolean syncBuffer) {
         Surface surface = mSurface;
         if (!surface.isValid()) {
             return false;
@@ -4871,17 +4885,11 @@
             dirty.offset(surfaceInsets.left, surfaceInsets.top);
         }
 
-        boolean accessibilityFocusDirty = false;
-        final Drawable drawable = mAttachInfo.mAccessibilityFocusDrawable;
-        if (drawable != null) {
-            final Rect bounds = mAttachInfo.mTmpInvalRect;
-            final boolean hasFocus = getAccessibilityFocusedRect(bounds);
-            if (!hasFocus) {
-                bounds.setEmpty();
-            }
-            if (!bounds.equals(drawable.getBounds())) {
-                accessibilityFocusDirty = true;
-            }
+        boolean accessibilityFocusDirty = isAccessibilityFocusDirty();
+
+        // Force recalculation of transparent regions
+        if (accessibilityFocusDirty) {
+            requestLayout();
         }
 
         mAttachInfo.mDrawingTime =
@@ -4933,9 +4941,19 @@
                     mAttachInfo.mThreadedRenderer.setTargetHdrSdrRatio(mRenderHdrSdrRatio);
                 }
 
-                if (forceDraw) {
-                    mAttachInfo.mThreadedRenderer.forceDrawNextFrame();
+                if (activeSyncGroup != null) {
+                    registerCallbacksForSync(syncBuffer, activeSyncGroup);
+                    if (syncBuffer) {
+                        mAttachInfo.mThreadedRenderer.forceDrawNextFrame();
+                    }
+                } else if (mHasPendingTransactions) {
+                    // Register a calback if there's no sync involved but there were calls to
+                    // applyTransactionOnDraw. If there is a sync involved, the sync callback will
+                    // handle merging the pending transaction.
+                    registerCallbackForPendingTransactions();
                 }
+                mHasPendingTransactions = false;
+
                 mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);
             } else {
                 // If we get here with a disabled & requested hardware renderer, something went
@@ -5428,8 +5446,9 @@
         mAccessibilityFocusedVirtualView = node;
         updateKeepClearForAccessibilityFocusRect();
 
-        if (mAttachInfo.mThreadedRenderer != null) {
-            mAttachInfo.mThreadedRenderer.invalidateRoot();
+        requestInvalidateRootRenderNode();
+        if (isAccessibilityFocusDirty()) {
+            scheduleTraversals();
         }
     }
 
@@ -7186,7 +7205,11 @@
                 final MotionEvent event = (MotionEvent)q.mEvent;
                 final int source = event.getSource();
                 if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
-                    mTrackball.process(event);
+                    // Do not synthesize events for relative mouse movement. If apps opt into
+                    // relative mouse movement they must be prepared to handle the events.
+                    if (!event.isFromSource(InputDevice.SOURCE_MOUSE_RELATIVE)) {
+                        mTrackball.process(event);
+                    }
                     return FINISH_HANDLED;
                 } else if ((source & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
                     mJoystick.process(event);
@@ -9015,8 +9038,7 @@
                 return true;
             }
             return mEvent instanceof MotionEvent
-                    && (mEvent.isFromSource(InputDevice.SOURCE_CLASS_POINTER)
-                        || mEvent.isFromSource(InputDevice.SOURCE_ROTARY_ENCODER));
+                    && (mEvent.isFromSource(InputDevice.SOURCE_CLASS_POINTER));
         }
 
         public boolean shouldSendToSynthesizer() {
@@ -9818,6 +9840,21 @@
         return AccessibilityNodeIdManager.getInstance().findView(accessibilityViewId);
     }
 
+    private boolean isAccessibilityFocusDirty() {
+        final Drawable drawable = mAttachInfo.mAccessibilityFocusDrawable;
+        if (drawable != null) {
+            final Rect bounds = mAttachInfo.mTmpInvalRect;
+            final boolean hasFocus = getAccessibilityFocusedRect(bounds);
+            if (!hasFocus) {
+                bounds.setEmpty();
+            }
+            if (!bounds.equals(drawable.getBounds())) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     /**
      * Updates the focused virtual view, when necessary, in response to a
      * content changed event.
@@ -9904,7 +9941,7 @@
 
     @Override
     public void notifySubtreeAccessibilityStateChanged(View child, View source, int changeType) {
-        postSendWindowContentChangedCallback(Preconditions.checkNotNull(source), changeType);
+        postSendWindowContentChangedCallback(Objects.requireNonNull(source), changeType);
     }
 
     @Override
@@ -10874,12 +10911,16 @@
             }
         }
 
-        public void attachAccessibilityOverlayToWindow(SurfaceControl sc) {
+        public void attachAccessibilityOverlayToWindow(
+                SurfaceControl sc,
+                int interactionId,
+                IAccessibilityInteractionConnectionCallback callback) {
             ViewRootImpl viewRootImpl = mViewRootImpl.get();
             if (viewRootImpl != null) {
                 viewRootImpl
                         .getAccessibilityInteractionController()
-                        .attachAccessibilityOverlayToWindowClientThread(sc);
+                        .attachAccessibilityOverlayToWindowClientThread(
+                                sc, interactionId, callback);
             }
         }
     }
@@ -10960,6 +11001,11 @@
                     run();
                 }
             }
+
+            if (!canContinueThrottle(source, changeType)) {
+                removeCallbacksAndRun();
+            }
+
             if (mSource != null) {
                 // If there is no common predecessor, then mSource points to
                 // a removed view, hence in this case always prefer the source.
@@ -10994,12 +11040,12 @@
                 mOrigin = Thread.currentThread().getStackTrace();
             }
             final long timeSinceLastMillis = SystemClock.uptimeMillis() - mLastEventTimeMillis;
-            final long minEventIntevalMillis =
+            final long minEventIntervalMillis =
                     ViewConfiguration.getSendRecurringAccessibilityEventsInterval();
-            if (timeSinceLastMillis >= minEventIntevalMillis) {
+            if (timeSinceLastMillis >= minEventIntervalMillis) {
                 removeCallbacksAndRun();
             } else {
-                mHandler.postDelayed(this, minEventIntevalMillis - timeSinceLastMillis);
+                mHandler.postDelayed(this, minEventIntervalMillis - timeSinceLastMillis);
             }
         }
 
@@ -11007,6 +11053,20 @@
             mHandler.removeCallbacks(this);
             run();
         }
+
+        private boolean canContinueThrottle(View source, int changeType) {
+            if (mSource == null) {
+                // We don't have a pending event.
+                return true;
+            }
+            if (mSource == source) {
+                // We can merge a new event with a pending event from the same source.
+                return true;
+            }
+            // We can merge subtree change events.
+            return changeType == AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE
+                    && mChangeTypes == AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE;
+        }
     }
 
     private static class UnhandledKeyManager {
@@ -11171,7 +11231,8 @@
     @Nullable public SurfaceControl.Transaction buildReparentTransaction(
         @NonNull SurfaceControl child) {
         if (mSurfaceControl.isValid()) {
-          return new SurfaceControl.Transaction().reparent(child, getBoundsLayer());
+            Transaction t = new Transaction();
+            return t.reparent(child, updateAndGetBoundsLayer(t));
         }
         return null;
     }
@@ -11553,4 +11614,17 @@
         mChildBoundingInsetsChanged = true;
         scheduleTraversals();
     }
+
+    @Override
+    public void addTrustedPresentationCallback(@NonNull SurfaceControl.Transaction t,
+            @NonNull SurfaceControl.TrustedPresentationThresholds thresholds,
+            @NonNull Executor executor, @NonNull Consumer<Boolean> listener) {
+        t.setTrustedPresentationCallback(getSurfaceControl(), thresholds, executor, listener);
+    }
+
+    @Override
+    public void removeTrustedPresentationCallback(@NonNull SurfaceControl.Transaction t,
+            @NonNull Consumer<Boolean> listener) {
+        t.clearTrustedPresentationCallback(getSurfaceControl());
+    }
 }
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index 21fe87f..2f04b0c 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -658,6 +658,12 @@
         void updateStatusBarColor(int color);
 
         /**
+         * Update the status bar appearance.
+         */
+
+        void updateStatusBarAppearance(int appearance);
+
+        /**
          * Update the navigation bar color to a forced one.
          */
         void updateNavigationBarColor(int color);
@@ -1039,6 +1045,9 @@
         if (mDecorCallback != null) {
             mDecorCallback.onSystemBarAppearanceChanged(appearance);
         }
+        if (mWindowControllerCallback != null) {
+            mWindowControllerCallback.updateStatusBarAppearance(appearance);
+        }
     }
 
     /** @hide */
@@ -1482,6 +1491,11 @@
     }
 
     /** @hide */
+    public boolean shouldCloseOnTouchOutside() {
+        return mCloseOnTouchOutside;
+    }
+
+    /** @hide */
     @SuppressWarnings("HiddenAbstractMethod")
     @UnsupportedAppUsage
     public abstract void alwaysReadCloseOnTouchAttr();
diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java
index 4acaea8..57a4161 100644
--- a/core/java/android/view/WindowInsets.java
+++ b/core/java/android/view/WindowInsets.java
@@ -84,13 +84,7 @@
     @Nullable private final PrivacyIndicatorBounds mPrivacyIndicatorBounds;
     @Nullable private final DisplayShape mDisplayShape;
 
-    /**
-     * In multi-window we force show the navigation bar. Because we don't want that the surface size
-     * changes in this mode, we instead have a flag whether the navigation bar size should always
-     * be consumed, so the app is treated like there is no virtual navigation bar at all.
-     */
-    private final boolean mAlwaysConsumeSystemBars;
-
+    private final @InsetsType int mForceConsumingTypes;
     private final @InsetsType int mSuppressScrimTypes;
     private final boolean mSystemWindowInsetsConsumed;
     private final boolean mStableInsetsConsumed;
@@ -117,7 +111,7 @@
 
     static {
         CONSUMED = new WindowInsets(createCompatTypeMap(null), createCompatTypeMap(null),
-                createCompatVisibilityMap(createCompatTypeMap(null)), false, false, 0, null,
+                createCompatVisibilityMap(createCompatTypeMap(null)), false, 0, 0, null,
                 null, null, null, systemBars(), false);
     }
 
@@ -137,7 +131,8 @@
             @Nullable Insets[] typeMaxInsetsMap,
             boolean[] typeVisibilityMap,
             boolean isRound,
-            boolean alwaysConsumeSystemBars, @InsetsType int suppressScrimTypes,
+            @InsetsType int forceConsumingTypes,
+            @InsetsType int suppressScrimTypes,
             DisplayCutout displayCutout,
             RoundedCorners roundedCorners,
             PrivacyIndicatorBounds privacyIndicatorBounds,
@@ -155,7 +150,7 @@
 
         mTypeVisibilityMap = typeVisibilityMap;
         mIsRound = isRound;
-        mAlwaysConsumeSystemBars = alwaysConsumeSystemBars;
+        mForceConsumingTypes = forceConsumingTypes;
         mSuppressScrimTypes = suppressScrimTypes;
         mCompatInsetsTypes = compatInsetsTypes;
         mCompatIgnoreVisibility = compatIgnoreVisibility;
@@ -178,7 +173,7 @@
         this(src.mSystemWindowInsetsConsumed ? null : src.mTypeInsetsMap,
                 src.mStableInsetsConsumed ? null : src.mTypeMaxInsetsMap,
                 src.mTypeVisibilityMap, src.mIsRound,
-                src.mAlwaysConsumeSystemBars, src.mSuppressScrimTypes,
+                src.mForceConsumingTypes, src.mSuppressScrimTypes,
                 displayCutoutCopyConstructorArgument(src),
                 src.mRoundedCorners,
                 src.mPrivacyIndicatorBounds,
@@ -235,7 +230,7 @@
     /** @hide */
     @UnsupportedAppUsage
     public WindowInsets(Rect systemWindowInsets) {
-        this(createCompatTypeMap(systemWindowInsets), null, new boolean[SIZE], false, false, 0,
+        this(createCompatTypeMap(systemWindowInsets), null, new boolean[SIZE], false, 0, 0,
                 null, null, null, null, systemBars(), false /* compatIgnoreVisibility */);
     }
 
@@ -556,7 +551,7 @@
         return new WindowInsets(mSystemWindowInsetsConsumed ? null : mTypeInsetsMap,
                 mStableInsetsConsumed ? null : mTypeMaxInsetsMap,
                 mTypeVisibilityMap,
-                mIsRound, mAlwaysConsumeSystemBars, mSuppressScrimTypes,
+                mIsRound, mForceConsumingTypes, mSuppressScrimTypes,
                 null /* displayCutout */, mRoundedCorners, mPrivacyIndicatorBounds, mDisplayShape,
                 mCompatInsetsTypes, mCompatIgnoreVisibility);
     }
@@ -607,7 +602,7 @@
     public WindowInsets consumeSystemWindowInsets() {
         return new WindowInsets(null, null,
                 mTypeVisibilityMap,
-                mIsRound, mAlwaysConsumeSystemBars, mSuppressScrimTypes,
+                mIsRound, mForceConsumingTypes, mSuppressScrimTypes,
                 // If the system window insets types contain displayCutout, we should also consume
                 // it.
                 (mCompatInsetsTypes & displayCutout()) != 0
@@ -895,8 +890,8 @@
     /**
      * @hide
      */
-    public boolean shouldAlwaysConsumeSystemBars() {
-        return mAlwaysConsumeSystemBars;
+    public @InsetsType int getForceConsumingTypes() {
+        return mForceConsumingTypes;
     }
 
     /**
@@ -930,6 +925,8 @@
         result.append("\n    ");
         result.append(mDisplayShape != null ? "displayShape=" + mDisplayShape : "");
         result.append("\n    ");
+        result.append("forceConsumingTypes=" + Type.toString(mForceConsumingTypes));
+        result.append("\n    ");
         result.append("suppressScrimTypes=" + Type.toString(mSuppressScrimTypes));
         result.append("\n    ");
         result.append("compatInsetsTypes=" + Type.toString(mCompatInsetsTypes));
@@ -1027,7 +1024,7 @@
                         ? null
                         : insetInsets(mTypeMaxInsetsMap, left, top, right, bottom),
                 mTypeVisibilityMap,
-                mIsRound, mAlwaysConsumeSystemBars, mSuppressScrimTypes,
+                mIsRound, mForceConsumingTypes, mSuppressScrimTypes,
                 mDisplayCutoutConsumed
                         ? null
                         : mDisplayCutout == null
@@ -1050,7 +1047,7 @@
         WindowInsets that = (WindowInsets) o;
 
         return mIsRound == that.mIsRound
-                && mAlwaysConsumeSystemBars == that.mAlwaysConsumeSystemBars
+                && mForceConsumingTypes == that.mForceConsumingTypes
                 && mSuppressScrimTypes == that.mSuppressScrimTypes
                 && mSystemWindowInsetsConsumed == that.mSystemWindowInsetsConsumed
                 && mStableInsetsConsumed == that.mStableInsetsConsumed
@@ -1068,7 +1065,7 @@
     public int hashCode() {
         return Objects.hash(Arrays.hashCode(mTypeInsetsMap), Arrays.hashCode(mTypeMaxInsetsMap),
                 Arrays.hashCode(mTypeVisibilityMap), mIsRound, mDisplayCutout, mRoundedCorners,
-                mAlwaysConsumeSystemBars, mSuppressScrimTypes, mSystemWindowInsetsConsumed,
+                mForceConsumingTypes, mSuppressScrimTypes, mSystemWindowInsetsConsumed,
                 mStableInsetsConsumed, mDisplayCutoutConsumed, mPrivacyIndicatorBounds,
                 mDisplayShape);
     }
@@ -1134,7 +1131,7 @@
         private DisplayShape mDisplayShape = DisplayShape.NONE;
 
         private boolean mIsRound;
-        private boolean mAlwaysConsumeSystemBars;
+        private @InsetsType int mForceConsumingTypes;
         private @InsetsType int mSuppressScrimTypes;
 
         private PrivacyIndicatorBounds mPrivacyIndicatorBounds = new PrivacyIndicatorBounds();
@@ -1162,7 +1159,7 @@
             mDisplayCutout = displayCutoutCopyConstructorArgument(insets);
             mRoundedCorners = insets.mRoundedCorners;
             mIsRound = insets.mIsRound;
-            mAlwaysConsumeSystemBars = insets.mAlwaysConsumeSystemBars;
+            mForceConsumingTypes = insets.mForceConsumingTypes;
             mSuppressScrimTypes = insets.mSuppressScrimTypes;
             mPrivacyIndicatorBounds = insets.mPrivacyIndicatorBounds;
             mDisplayShape = insets.mDisplayShape;
@@ -1433,7 +1430,15 @@
         /** @hide */
         @NonNull
         public Builder setAlwaysConsumeSystemBars(boolean alwaysConsumeSystemBars) {
-            mAlwaysConsumeSystemBars = alwaysConsumeSystemBars;
+            // TODO (b/277891341): Remove this and related usages. This has been replaced by
+            //                     #setForceConsumingTypes.
+            return this;
+        }
+
+        /** @hide */
+        @NonNull
+        public Builder setForceConsumingTypes(@InsetsType int forceConsumingTypes) {
+            mForceConsumingTypes = forceConsumingTypes;
             return this;
         }
 
@@ -1453,7 +1458,7 @@
         public WindowInsets build() {
             return new WindowInsets(mSystemInsetsConsumed ? null : mTypeInsetsMap,
                     mStableInsetsConsumed ? null : mTypeMaxInsetsMap, mTypeVisibilityMap,
-                    mIsRound, mAlwaysConsumeSystemBars, mSuppressScrimTypes, mDisplayCutout,
+                    mIsRound, mForceConsumingTypes, mSuppressScrimTypes, mDisplayCutout,
                     mRoundedCorners, mPrivacyIndicatorBounds, mDisplayShape, systemBars(),
                     false /* compatIgnoreVisibility */);
         }
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 157d95d..f1537ca 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -1385,15 +1385,28 @@
             "android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED";
 
     /**
-     * Request for keyboard shortcuts to be retrieved asynchronously.
+     * Request for app's keyboard shortcuts to be retrieved asynchronously.
      *
      * @param receiver The callback to be triggered when the result is ready.
+     * @param deviceId The deviceId of KeyEvent by which this request is triggered, or -1 if it's
+     *                 not triggered by a KeyEvent.
      *
      * @hide
      */
     public void requestAppKeyboardShortcuts(final KeyboardShortcutsReceiver receiver, int deviceId);
 
     /**
+     * Request for ime's keyboard shortcuts to be retrieved asynchronously.
+     *
+     * @param receiver The callback to be triggered when the result is ready.
+     * @param deviceId The deviceId of KeyEvent by which this request is triggered, or -1 if it's
+     *                 not triggered by a KeyEvent.
+     *
+     * @hide
+     */
+    default void requestImeKeyboardShortcuts(KeyboardShortcutsReceiver receiver, int deviceId) {};
+
+    /**
      * Return the touch region for the current IME window, or an empty region if there is none.
      *
      * @return The region of the IME that is accepting touch inputs, or null if there is no IME, no
@@ -4295,17 +4308,6 @@
         public InsetsFrameProvider[] providedInsets;
 
         /**
-         * If specified, the frame that used to calculate relative {@link RoundedCorner} will be
-         * the window frame of this window minus the insets that this window provides.
-         *
-         * Task bar will draw fake rounded corners above itself, so we need this insets to calculate
-         * correct rounded corners for this window.
-         *
-         * @hide
-         */
-        public boolean insetsRoundedCornerFrame = false;
-
-        /**
          * {@link LayoutParams} to be applied to the window when layout with a assigned rotation.
          * This will make layout during rotation change smoothly.
          *
@@ -4761,7 +4763,6 @@
             out.writeBoolean(mFitInsetsIgnoringVisibility);
             out.writeBoolean(preferMinimalPostProcessing);
             out.writeInt(mBlurBehindRadius);
-            out.writeBoolean(insetsRoundedCornerFrame);
             out.writeBoolean(mWallpaperTouchEventsEnabled);
             out.writeTypedArray(providedInsets, 0 /* parcelableFlags */);
             checkNonRecursiveParams();
@@ -4833,7 +4834,6 @@
             mFitInsetsIgnoringVisibility = in.readBoolean();
             preferMinimalPostProcessing = in.readBoolean();
             mBlurBehindRadius = in.readInt();
-            insetsRoundedCornerFrame = in.readBoolean();
             mWallpaperTouchEventsEnabled = in.readBoolean();
             providedInsets = in.createTypedArray(InsetsFrameProvider.CREATOR);
             paramsForRotation = in.createTypedArray(LayoutParams.CREATOR);
@@ -5141,11 +5141,6 @@
                 changes |= LAYOUT_CHANGED;
             }
 
-            if (insetsRoundedCornerFrame != o.insetsRoundedCornerFrame) {
-                insetsRoundedCornerFrame = o.insetsRoundedCornerFrame;
-                changes |= LAYOUT_CHANGED;
-            }
-
             if (paramsForRotation != o.paramsForRotation) {
                 if ((changes & LAYOUT_CHANGED) == 0) {
                     if (paramsForRotation != null && o.paramsForRotation != null
@@ -5383,10 +5378,6 @@
                     sb.append(prefix).append("    ").append(providedInsets[i]);
                 }
             }
-            if (insetsRoundedCornerFrame) {
-                sb.append(" insetsRoundedCornerFrame=");
-                sb.append(insetsRoundedCornerFrame);
-            }
             if (paramsForRotation != null && paramsForRotation.length != 0) {
                 sb.append(System.lineSeparator());
                 sb.append(prefix).append("  paramsForRotation:");
@@ -5711,4 +5702,35 @@
     default @NonNull List<ComponentName> notifyScreenshotListeners(int displayId) {
         throw new UnsupportedOperationException();
     }
+
+    /**
+     * @param displayId The displayId to that should have its content replaced.
+     * @param window The window that should get mirrored and the mirrored content rendered on
+     *               displayId passed in.
+     *
+     * @return Whether it successfully created a mirror SurfaceControl and replaced the display
+     * content with the mirror of the Window.
+     *
+     * @hide
+     */
+    @TestApi
+    @RequiresPermission(permission.ACCESS_SURFACE_FLINGER)
+    default boolean replaceContentOnDisplayWithMirror(int displayId, @NonNull Window window) {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * @param displayId The displayId to that should have its content replaced.
+     * @param sc The SurfaceControl that should get rendered onto the displayId passed in.
+     *
+     * @return Whether it successfully created a mirror SurfaceControl and replaced the display
+     * content with the mirror of the Window.
+     *
+     * @hide
+     */
+    @TestApi
+    @RequiresPermission(permission.ACCESS_SURFACE_FLINGER)
+    default boolean replaceContentOnDisplayWithSc(int displayId, @NonNull SurfaceControl sc) {
+        throw new UnsupportedOperationException();
+    }
 }
diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java
index df3e0bb..d7b74b3 100644
--- a/core/java/android/view/WindowManagerImpl.java
+++ b/core/java/android/view/WindowManagerImpl.java
@@ -34,6 +34,7 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.StrictMode;
+import android.util.Log;
 import android.window.ITaskFpsCallback;
 import android.window.TaskFpsCallback;
 import android.window.WindowContext;
@@ -80,6 +81,8 @@
  * @hide
  */
 public final class WindowManagerImpl implements WindowManager {
+    private static final String TAG = "WindowManager";
+
     @UnsupportedAppUsage
     private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
     @UiContext
@@ -215,14 +218,36 @@
             @Override
             public void send(int resultCode, Bundle resultData) throws RemoteException {
                 List<KeyboardShortcutGroup> result =
-                        resultData.getParcelableArrayList(PARCEL_KEY_SHORTCUTS_ARRAY, android.view.KeyboardShortcutGroup.class);
+                        resultData.getParcelableArrayList(PARCEL_KEY_SHORTCUTS_ARRAY,
+                                android.view.KeyboardShortcutGroup.class);
                 receiver.onKeyboardShortcutsReceived(result);
             }
         };
         try {
             WindowManagerGlobal.getWindowManagerService()
-                .requestAppKeyboardShortcuts(resultReceiver, deviceId);
+                    .requestAppKeyboardShortcuts(resultReceiver, deviceId);
         } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
+    public void requestImeKeyboardShortcuts(
+            final KeyboardShortcutsReceiver receiver, int deviceId) {
+        IResultReceiver resultReceiver = new IResultReceiver.Stub() {
+            @Override
+            public void send(int resultCode, Bundle resultData) throws RemoteException {
+                List<KeyboardShortcutGroup> result =
+                        resultData.getParcelableArrayList(PARCEL_KEY_SHORTCUTS_ARRAY,
+                                android.view.KeyboardShortcutGroup.class);
+                receiver.onKeyboardShortcutsReceived(result);
+            }
+        };
+        try {
+            WindowManagerGlobal.getWindowManagerService()
+                    .requestImeKeyboardShortcuts(resultReceiver, deviceId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
         }
     }
 
@@ -445,4 +470,42 @@
             throw e.rethrowFromSystemServer();
         }
     }
+
+    @Override
+    public boolean replaceContentOnDisplayWithMirror(int displayId, @NonNull Window window) {
+        View decorView = window.peekDecorView();
+        if (decorView == null) {
+            Log.e(TAG, "replaceContentOnDisplayWithMirror: Window's decorView was null.");
+            return false;
+        }
+
+        ViewRootImpl viewRoot = decorView.getViewRootImpl();
+        if (viewRoot == null) {
+            Log.e(TAG, "replaceContentOnDisplayWithMirror: Window's viewRootImpl was null.");
+            return false;
+        }
+
+        SurfaceControl sc = viewRoot.getSurfaceControl();
+        if (!sc.isValid()) {
+            Log.e(TAG, "replaceContentOnDisplayWithMirror: Window's SC is invalid.");
+            return false;
+        }
+        return replaceContentOnDisplayWithSc(displayId, SurfaceControl.mirrorSurface(sc));
+    }
+
+    @Override
+    public boolean replaceContentOnDisplayWithSc(int displayId, @NonNull SurfaceControl sc) {
+        if (!sc.isValid()) {
+            Log.e(TAG, "replaceContentOnDisplayWithSc: Invalid SC.");
+            return false;
+        }
+
+        try {
+            return WindowManagerGlobal.getWindowManagerService()
+                    .replaceContentOnDisplay(displayId, sc);
+        } catch (RemoteException e) {
+            e.rethrowAsRuntimeException();
+        }
+        return false;
+    }
 }
diff --git a/core/java/android/view/WindowManagerPolicyConstants.java b/core/java/android/view/WindowManagerPolicyConstants.java
index afc567e..c88048c 100644
--- a/core/java/android/view/WindowManagerPolicyConstants.java
+++ b/core/java/android/view/WindowManagerPolicyConstants.java
@@ -97,6 +97,12 @@
      */
     String EXTRA_START_REASON = "android.intent.extra.EXTRA_START_REASON";
 
+    /**
+     * Set to {@code true} when intent was invoked from pressing one of the brightness keys.
+     * @hide
+     */
+    String EXTRA_FROM_BRIGHTNESS_KEY = "android.intent.extra.FROM_BRIGHTNESS_KEY";
+
     // TODO: move this to a more appropriate place.
     interface PointerEventListener {
         /**
diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java
index 96bfb2d..7d3d283 100644
--- a/core/java/android/view/WindowlessWindowManager.java
+++ b/core/java/android/view/WindowlessWindowManager.java
@@ -49,7 +49,8 @@
 
     private class State {
         SurfaceControl mSurfaceControl;
-        WindowManager.LayoutParams mParams = new WindowManager.LayoutParams();
+        final WindowManager.LayoutParams mParams = new WindowManager.LayoutParams();
+        final WindowManager.LayoutParams mLastReportedParams = new WindowManager.LayoutParams();
         int mDisplayId;
         IBinder mInputChannelToken;
         Region mInputRegion;
@@ -94,6 +95,8 @@
     private final MergedConfiguration mTmpConfig = new MergedConfiguration();
     private final WindowlessWindowLayout mLayout = new WindowlessWindowLayout();
 
+    private ISurfaceControlViewHostParent mParentInterface;
+
     public WindowlessWindowManager(Configuration c, SurfaceControl rootSurface,
             IBinder hostInputToken) {
         mRootSurface = rootSurface;
@@ -244,6 +247,7 @@
         final int res = WindowManagerGlobal.ADD_OKAY | WindowManagerGlobal.ADD_FLAG_APP_VISIBLE |
                         WindowManagerGlobal.ADD_FLAG_USE_BLAST;
 
+        sendLayoutParamsToParent();
         // Include whether the window is in touch mode.
         return isInTouchModeInternal(displayId) ? res | WindowManagerGlobal.ADD_FLAG_IN_TOUCH_MODE
                 : res;
@@ -425,6 +429,7 @@
             outInsetsState.set(mInsetsState);
         }
 
+        sendLayoutParamsToParent();
         return 0;
     }
 
@@ -645,4 +650,45 @@
             " we shouldn't get here!");
         return false;
     }
+
+    void setParentInterface(@Nullable ISurfaceControlViewHostParent parentInterface) {
+        IBinder oldInterface = mParentInterface == null ? null : mParentInterface.asBinder();
+        IBinder newInterface = parentInterface == null ? null : parentInterface.asBinder();
+        // If the parent interface has changed, it needs to clear the last reported params so it
+        // will update the new interface with the params.
+        if (oldInterface != newInterface) {
+            clearLastReportedParams();
+        }
+        mParentInterface = parentInterface;
+        sendLayoutParamsToParent();
+    }
+
+    private void clearLastReportedParams() {
+        WindowManager.LayoutParams emptyParam = new WindowManager.LayoutParams();
+        for (State windowInfo : mStateForWindow.values()) {
+            windowInfo.mLastReportedParams.copyFrom(emptyParam);
+        }
+    }
+
+    private void sendLayoutParamsToParent() {
+        if (mParentInterface == null) {
+            return;
+        }
+        WindowManager.LayoutParams[] params =
+                new WindowManager.LayoutParams[mStateForWindow.size()];
+        int index = 0;
+        boolean hasChanges = false;
+        for (State windowInfo : mStateForWindow.values()) {
+            int changes = windowInfo.mLastReportedParams.copyFrom(windowInfo.mParams);
+            hasChanges |= (changes != 0);
+            params[index++] = windowInfo.mParams;
+        }
+
+        if (hasChanges) {
+            try {
+                mParentInterface.updateParams(params);
+            } catch (RemoteException e) {
+            }
+        }
+    }
 }
diff --git a/core/java/android/view/accessibility/AccessibilityCache.java b/core/java/android/view/accessibility/AccessibilityCache.java
index e6385a5..f3cde43 100644
--- a/core/java/android/view/accessibility/AccessibilityCache.java
+++ b/core/java/android/view/accessibility/AccessibilityCache.java
@@ -72,6 +72,8 @@
 
     private final AccessibilityNodeRefresher mAccessibilityNodeRefresher;
 
+    private OnNodeAddedListener mOnNodeAddedListener;
+
     private long mAccessibilityFocus = AccessibilityNodeInfo.UNDEFINED_ITEM_ID;
     private long mInputFocus = AccessibilityNodeInfo.UNDEFINED_ITEM_ID;
     /**
@@ -542,6 +544,10 @@
                 mInputFocus = sourceId;
                 mInputFocusWindow = windowId;
             }
+
+            if (mOnNodeAddedListener != null) {
+                mOnNodeAddedListener.onNodeAdded(clone);
+            }
         }
     }
 
@@ -881,6 +887,26 @@
         }
     }
 
+    /**
+     * Registers a listener to receive callbacks whenever nodes are added to cache.
+     *
+     * @param listener the listener to be registered.
+     */
+    public void registerOnNodeAddedListener(OnNodeAddedListener listener) {
+        synchronized (mLock) {
+            mOnNodeAddedListener = listener;
+        }
+    }
+
+    /**
+     * Clears the current reference to an OnNodeAddedListener, if one exists.
+     */
+    public void clearOnNodeAddedListener() {
+        synchronized (mLock) {
+            mOnNodeAddedListener = null;
+        }
+    }
+
     // Layer of indirection included to break dependency chain for testing
     public static class AccessibilityNodeRefresher {
         /** Refresh the given AccessibilityNodeInfo object. */
@@ -893,4 +919,12 @@
             return info.refresh();
         }
     }
+
+    /**
+     * Listener interface that receives callbacks when nodes are added to cache.
+     */
+    public interface OnNodeAddedListener {
+        /** Called when a node is added to cache. */
+        void onNodeAdded(AccessibilityNodeInfo node);
+    }
 }
diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java
index 0acc022..a43906f 100644
--- a/core/java/android/view/accessibility/AccessibilityEvent.java
+++ b/core/java/android/view/accessibility/AccessibilityEvent.java
@@ -192,6 +192,8 @@
  *   <li>{@link #getEventTime()}  - The event time.</li>
  *   <li>{@link #getScrollDeltaX()} - The difference in the horizontal position.</li>
  *   <li>{@link #getScrollDeltaY()} - The difference in the vertical position.</li>
+ *   <li>{@link #getMaxScrollX()} ()} -  The max scroll offset of the source left edge</li>
+ *   <li>{@link #getMaxScrollY()} ()} - The max scroll offset of the source top edge.</li>
  * </ul>
  * </p>
  * <p>
@@ -454,83 +456,109 @@
     @Deprecated
     public static final int MAX_TEXT_LENGTH = 500;
 
+    // Event types.
+
     /**
      * Represents the event of clicking on a {@link android.view.View} like
      * {@link android.widget.Button}, {@link android.widget.CompoundButton}, etc.
+     * <p>See {@link AccessibilityNodeInfo.AccessibilityAction#ACTION_CLICK} for more
+     * details.
      */
-    public static final int TYPE_VIEW_CLICKED = 0x00000001;
+    public static final int TYPE_VIEW_CLICKED = 1 /* << 0 */;;
 
     /**
      * Represents the event of long clicking on a {@link android.view.View} like
      * {@link android.widget.Button}, {@link android.widget.CompoundButton}, etc.
+     * <p>See {@link AccessibilityNodeInfo.AccessibilityAction#ACTION_LONG_CLICK} for more
+     * details.
      */
-    public static final int TYPE_VIEW_LONG_CLICKED = 0x00000002;
+    public static final int TYPE_VIEW_LONG_CLICKED = 1 << 1;
 
     /**
      * Represents the event of selecting an item usually in the context of an
      * {@link android.widget.AdapterView}.
+     * @see AccessibilityNodeInfo.AccessibilityAction#ACTION_SELECT
      */
-    public static final int TYPE_VIEW_SELECTED = 0x00000004;
+    public static final int TYPE_VIEW_SELECTED = 1 << 2;
 
     /**
      * Represents the event of setting input focus of a {@link android.view.View}.
+     * @see AccessibilityNodeInfo.AccessibilityAction#ACTION_ACCESSIBILITY_FOCUS for the difference
+     * between input and accessibility focus.
      */
-    public static final int TYPE_VIEW_FOCUSED = 0x00000008;
+    public static final int TYPE_VIEW_FOCUSED = 1 << 3;
 
     /**
      * Represents the event of changing the text of an {@link android.widget.EditText}.
+     * @see AccessibilityNodeInfo#setText(CharSequence)
      */
-    public static final int TYPE_VIEW_TEXT_CHANGED = 0x00000010;
+    public static final int TYPE_VIEW_TEXT_CHANGED = 1 << 4;
 
     /**
      * Represents the event of a change to a visually distinct section of the user interface.
+     * <p>
      * These events should only be dispatched from {@link android.view.View}s that have
      * accessibility pane titles, and replaces {@link #TYPE_WINDOW_CONTENT_CHANGED} for those
      * sources. Details about the change are available from {@link #getContentChangeTypes()}.
+     * <p>
+     * Do not use this to get an accessibility service to make non-pane announcements. Instead,
+     * follow the practices described in {@link View#announceForAccessibility(CharSequence)}.
+     * <b>Note:</b> this does not suggest calling announceForAccessibility(), but using the
+     * suggestions listed in its documentation.
      */
-    public static final int TYPE_WINDOW_STATE_CHANGED = 0x00000020;
+    public static final int TYPE_WINDOW_STATE_CHANGED = 1 << 5;
 
     /**
      * Represents the event showing a {@link android.app.Notification}.
      */
-    public static final int TYPE_NOTIFICATION_STATE_CHANGED = 0x00000040;
+    public static final int TYPE_NOTIFICATION_STATE_CHANGED = 1 << 6;
 
     /**
      * Represents the event of a hover enter over a {@link android.view.View}.
      */
-    public static final int TYPE_VIEW_HOVER_ENTER = 0x00000080;
+    public static final int TYPE_VIEW_HOVER_ENTER = 1 << 7;
 
     /**
      * Represents the event of a hover exit over a {@link android.view.View}.
      */
-    public static final int TYPE_VIEW_HOVER_EXIT = 0x00000100;
+    public static final int TYPE_VIEW_HOVER_EXIT = 1 << 8;
 
     /**
      * Represents the event of starting a touch exploration gesture.
      */
-    public static final int TYPE_TOUCH_EXPLORATION_GESTURE_START = 0x00000200;
+    public static final int TYPE_TOUCH_EXPLORATION_GESTURE_START = 1 << 9;
 
     /**
      * Represents the event of ending a touch exploration gesture.
      */
-    public static final int TYPE_TOUCH_EXPLORATION_GESTURE_END = 0x00000400;
+    public static final int TYPE_TOUCH_EXPLORATION_GESTURE_END = 1 << 10;
 
     /**
      * Represents the event of changing the content of a window and more
      * specifically the sub-tree rooted at the event's source.
      */
-    public static final int TYPE_WINDOW_CONTENT_CHANGED = 0x00000800;
+    public static final int TYPE_WINDOW_CONTENT_CHANGED = 1 << 11;
 
     /**
-     * Represents the event of scrolling a view. This event type is generally not sent directly.
-     * @see android.view.View#onScrollChanged(int, int, int, int)
+     * Represents the event of scrolling a view. This event type is generally not sent directly. In
+     * the View system, this is sent in
+     * {@link android.view.View#onScrollChanged(int, int, int, int)}
+     * <p>In addition to the source and package name, the event should populate scroll-specific
+     * properties like {@link #setScrollDeltaX(int)}, {@link #setScrollDeltaY(int)},
+     * {@link #setMaxScrollX(int)}, and {@link #setMaxScrollY(int)}.
+     * <p>Services are encouraged to rely on the source to query UI state over AccessibilityEvents
+     * properties. For example, to check after a scroll if the bottom of the scrolling UI element
+     * has been reached, check if the source node is scrollable and has the
+     * {@link AccessibilityNodeInfo.AccessibilityAction#ACTION_SCROLL_BACKWARD} action but not the
+     * {@link AccessibilityNodeInfo.AccessibilityAction#ACTION_SCROLL_FORWARD} action.
+     * For scrolling to a target, use {@link #TYPE_VIEW_TARGETED_BY_SCROLL}.
      */
-    public static final int TYPE_VIEW_SCROLLED = 0x00001000;
+    public static final int TYPE_VIEW_SCROLLED = 1 << 12;
 
     /**
      * Represents the event of changing the selection in an {@link android.widget.EditText}.
      */
-    public static final int TYPE_VIEW_TEXT_SELECTION_CHANGED = 0x00002000;
+    public static final int TYPE_VIEW_TEXT_SELECTION_CHANGED = 1 << 13;
 
     /**
      * Represents the event of an application making an announcement.
@@ -538,58 +566,64 @@
      * In general, follow the practices described in
      * {@link View#announceForAccessibility(CharSequence)}.
      */
-    public static final int TYPE_ANNOUNCEMENT = 0x00004000;
+    public static final int TYPE_ANNOUNCEMENT = 1 << 14;
 
     /**
      * Represents the event of gaining accessibility focus.
+     * @see AccessibilityNodeInfo.AccessibilityAction#ACTION_ACCESSIBILITY_FOCUS for the difference
+     * between input and accessibility focus.
      */
-    public static final int TYPE_VIEW_ACCESSIBILITY_FOCUSED = 0x00008000;
+    public static final int TYPE_VIEW_ACCESSIBILITY_FOCUSED = 1 << 15;
 
     /**
      * Represents the event of clearing accessibility focus.
+     * @see AccessibilityNodeInfo.AccessibilityAction#ACTION_ACCESSIBILITY_FOCUS for the difference
+     * between input and accessibility focus.
      */
-    public static final int TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED = 0x00010000;
+    public static final int TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED = 1 << 16;
 
     /**
      * Represents the event of traversing the text of a view at a given movement granularity.
      */
-    public static final int TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY = 0x00020000;
+    public static final int TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY = 1 << 17;
 
     /**
      * Represents the event of beginning gesture detection.
      */
-    public static final int TYPE_GESTURE_DETECTION_START = 0x00040000;
+    public static final int TYPE_GESTURE_DETECTION_START = 1 << 18;
 
     /**
      * Represents the event of ending gesture detection.
      */
-    public static final int TYPE_GESTURE_DETECTION_END = 0x00080000;
+    public static final int TYPE_GESTURE_DETECTION_END = 1 << 19;
 
     /**
      * Represents the event of the user starting to touch the screen.
      */
-    public static final int TYPE_TOUCH_INTERACTION_START = 0x00100000;
+    public static final int TYPE_TOUCH_INTERACTION_START = 1 << 20;
 
     /**
      * Represents the event of the user ending to touch the screen.
      */
-    public static final int TYPE_TOUCH_INTERACTION_END = 0x00200000;
+    public static final int TYPE_TOUCH_INTERACTION_END = 1 << 21;
 
     /**
      * Represents the event change in the system windows shown on the screen. This event type should
      * only be dispatched by the system.
      */
-    public static final int TYPE_WINDOWS_CHANGED = 0x00400000;
+    public static final int TYPE_WINDOWS_CHANGED = 1 << 22;
 
     /**
      * Represents the event of a context click on a {@link android.view.View}.
+     * <p>See {@link AccessibilityNodeInfo.AccessibilityAction#ACTION_CONTEXT_CLICK} for more
+     * details.
      */
-    public static final int TYPE_VIEW_CONTEXT_CLICKED = 0x00800000;
+    public static final int TYPE_VIEW_CONTEXT_CLICKED = 1 << 23;
 
     /**
      * Represents the event of the assistant currently reading the users screen context.
      */
-    public static final int TYPE_ASSIST_READING_CONTEXT = 0x01000000;
+    public static final int TYPE_ASSIST_READING_CONTEXT = 1 << 24;
 
     /**
      * Represents a change in the speech state defined by the speech state change types.
@@ -607,37 +641,40 @@
      * @see #getSpeechStateChangeTypes
      * @see #setSpeechStateChangeTypes
      */
-    public static final int TYPE_SPEECH_STATE_CHANGE = 0x02000000;
+    public static final int TYPE_SPEECH_STATE_CHANGE = 1 << 25;
 
     /**
      * Represents the event of a scroll having completed and brought the target node on screen.
      */
-    public static final int TYPE_VIEW_TARGETED_BY_SCROLL = 0x04000000;
+    public static final int TYPE_VIEW_TARGETED_BY_SCROLL = 1 << 26;
+
+    // Content change types.
 
     /**
      * Change type for {@link #TYPE_WINDOW_CONTENT_CHANGED} event: The type of change is not
      * defined.
      */
-    public static final int CONTENT_CHANGE_TYPE_UNDEFINED = 0x00000000;
+    public static final int CONTENT_CHANGE_TYPE_UNDEFINED = 0;
 
     /**
      * Change type for {@link #TYPE_WINDOW_CONTENT_CHANGED} event:
      * One or more content changes occurred in the the subtree rooted at the source node,
      * or the subtree's structure changed when a node was added or removed.
      */
-    public static final int CONTENT_CHANGE_TYPE_SUBTREE = 0x00000001;
+    public static final int CONTENT_CHANGE_TYPE_SUBTREE = 1 /* << 0 */;
 
     /**
      * Change type for {@link #TYPE_WINDOW_CONTENT_CHANGED} event:
      * The node's text changed.
+     * @see AccessibilityNodeInfo#setText(CharSequence)
      */
-    public static final int CONTENT_CHANGE_TYPE_TEXT = 0x00000002;
+    public static final int CONTENT_CHANGE_TYPE_TEXT = 1 << 1;
 
     /**
      * Change type for {@link #TYPE_WINDOW_CONTENT_CHANGED} event:
      * The node's content description changed.
      */
-    public static final int CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION = 0x00000004;
+    public static final int CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION = 1 << 2;
 
     /**
      * Change type for {@link #TYPE_WINDOW_STATE_CHANGED} event:
@@ -646,16 +683,17 @@
      * If this makes the pane appear, {@link #CONTENT_CHANGE_TYPE_PANE_APPEARED} is sent
      * instead. If this makes the pane disappear, {@link #CONTENT_CHANGE_TYPE_PANE_DISAPPEARED}
      * is sent.
-     *
+     * @see View#setAccessibilityPaneTitle(CharSequence)
      */
-    public static final int CONTENT_CHANGE_TYPE_PANE_TITLE = 0x00000008;
+    public static final int CONTENT_CHANGE_TYPE_PANE_TITLE = 1 << 3;
 
     /**
      * Change type for {@link #TYPE_WINDOW_STATE_CHANGED} event:
      * The node has a pane title, and either just appeared or just was assigned a title when it
      * had none before.
+     * @see View#setAccessibilityPaneTitle(CharSequence)
      */
-    public static final int CONTENT_CHANGE_TYPE_PANE_APPEARED = 0x00000010;
+    public static final int CONTENT_CHANGE_TYPE_PANE_APPEARED = 1 << 4;
 
     /**
      * Change type for {@link #TYPE_WINDOW_STATE_CHANGED} event:
@@ -665,8 +703,9 @@
      * No source will be returned if the node is no longer on the screen. To make the change more
      * clear for the user, the first entry in {@link #getText()} will return the value that would
      * have been returned by {@code getSource().getPaneTitle()}.
+     * @see View#setAccessibilityPaneTitle(CharSequence)
      */
-    public static final int CONTENT_CHANGE_TYPE_PANE_DISAPPEARED = 0x00000020;
+    public static final int CONTENT_CHANGE_TYPE_PANE_DISAPPEARED = 1 << 5;
 
     /**
      * Change type for {@link #TYPE_WINDOW_CONTENT_CHANGED} event:
@@ -675,8 +714,9 @@
      * changes, the changed part can be put into event text. For example, if state description
      * changed from "on, wifi signal full" to "on, wifi three bars", "wifi three bars" can be put
      * into the event text.
+     * @see View#setStateDescription(CharSequence)
      */
-    public static final int CONTENT_CHANGE_TYPE_STATE_DESCRIPTION = 0x00000040;
+    public static final int CONTENT_CHANGE_TYPE_STATE_DESCRIPTION = 1 << 6;
 
     /**
      * Change type for {@link #TYPE_WINDOW_CONTENT_CHANGED} event:
@@ -686,7 +726,7 @@
      *
      * @see AccessibilityNodeInfo.AccessibilityAction#ACTION_DRAG_START
      */
-    public static final int CONTENT_CHANGE_TYPE_DRAG_STARTED = 0x00000080;
+    public static final int CONTENT_CHANGE_TYPE_DRAG_STARTED = 1 << 7;
 
     /**
      * Change type for {@link #TYPE_WINDOW_CONTENT_CHANGED} event:
@@ -695,7 +735,7 @@
      *
      * @see AccessibilityNodeInfo.AccessibilityAction#ACTION_DRAG_DROP
      */
-    public static final int CONTENT_CHANGE_TYPE_DRAG_DROPPED = 0x00000100;
+    public static final int CONTENT_CHANGE_TYPE_DRAG_DROPPED = 1 << 8;
 
     /**
      * Change type for {@link #TYPE_WINDOW_CONTENT_CHANGED} event:
@@ -706,7 +746,7 @@
      *
      * @see AccessibilityNodeInfo.AccessibilityAction#ACTION_DRAG_CANCEL
      */
-    public static final int CONTENT_CHANGE_TYPE_DRAG_CANCELLED = 0x0000200;
+    public static final int CONTENT_CHANGE_TYPE_DRAG_CANCELLED = 1 << 9;
 
     /**
      * Change type for {@link #TYPE_WINDOW_CONTENT_CHANGED} event:
@@ -718,7 +758,7 @@
      * @see AccessibilityNodeInfo#isContentInvalid
      * @see AccessibilityNodeInfo#setContentInvalid
      */
-    public static final int CONTENT_CHANGE_TYPE_CONTENT_INVALID = 0x0000400;
+    public static final int CONTENT_CHANGE_TYPE_CONTENT_INVALID = 1 << 10;
 
     /**
      * Change type for {@link #TYPE_WINDOW_CONTENT_CHANGED} event:
@@ -730,7 +770,7 @@
      * @see AccessibilityNodeInfo#getError
      * @see AccessibilityNodeInfo#setError
      */
-    public static final int CONTENT_CHANGE_TYPE_ERROR = 0x0000800;
+    public static final int CONTENT_CHANGE_TYPE_ERROR = 1 << 11;
 
     /**
      * Change type for {@link #TYPE_WINDOW_CONTENT_CHANGED} event:
@@ -744,44 +784,48 @@
      */
     public static final int CONTENT_CHANGE_TYPE_ENABLED = 1 << 12;
 
+    // Speech state change types.
+
     /** Change type for {@link #TYPE_SPEECH_STATE_CHANGE} event: another service is speaking. */
-    public static final int SPEECH_STATE_SPEAKING_START = 0x00000001;
+    public static final int SPEECH_STATE_SPEAKING_START = 1 /* << 0 */;;
 
     /**
      * Change type for {@link #TYPE_SPEECH_STATE_CHANGE} event: another service is no longer
      * speaking.
      */
-    public static final int SPEECH_STATE_SPEAKING_END = 0x00000002;
+    public static final int SPEECH_STATE_SPEAKING_END = 1 << 1;
 
     /**
      * Change type for {@link #TYPE_SPEECH_STATE_CHANGE} event: another service is listening to the
      * microphone.
      */
-    public static final int SPEECH_STATE_LISTENING_START = 0x00000004;
+    public static final int SPEECH_STATE_LISTENING_START = 1 << 2;
 
     /**
      * Change type for {@link #TYPE_SPEECH_STATE_CHANGE} event: another service is no longer
      * listening to the microphone.
      */
-    public static final int SPEECH_STATE_LISTENING_END = 0x00000008;
+    public static final int SPEECH_STATE_LISTENING_END = 1 << 3;
+
+    // Windows change types.
 
     /**
      * Change type for {@link #TYPE_WINDOWS_CHANGED} event:
      * The window was added.
      */
-    public static final int WINDOWS_CHANGE_ADDED = 0x00000001;
+    public static final int WINDOWS_CHANGE_ADDED = 1 /* << 0 */;;
 
     /**
      * Change type for {@link #TYPE_WINDOWS_CHANGED} event:
      * A window was removed.
      */
-    public static final int WINDOWS_CHANGE_REMOVED = 0x00000002;
+    public static final int WINDOWS_CHANGE_REMOVED = 1 << 1;
 
     /**
      * Change type for {@link #TYPE_WINDOWS_CHANGED} event:
      * The window's title changed.
      */
-    public static final int WINDOWS_CHANGE_TITLE = 0x00000004;
+    public static final int WINDOWS_CHANGE_TITLE = 1 << 2;
 
     /**
      * Change type for {@link #TYPE_WINDOWS_CHANGED} event:
@@ -791,49 +835,49 @@
      * region changed. It's also possible that region changed but bounds doesn't.
      * </p>
      */
-    public static final int WINDOWS_CHANGE_BOUNDS = 0x00000008;
+    public static final int WINDOWS_CHANGE_BOUNDS = 1 << 3;
 
     /**
      * Change type for {@link #TYPE_WINDOWS_CHANGED} event:
      * The window's layer changed.
      */
-    public static final int WINDOWS_CHANGE_LAYER = 0x00000010;
+    public static final int WINDOWS_CHANGE_LAYER = 1 << 4;
 
     /**
      * Change type for {@link #TYPE_WINDOWS_CHANGED} event:
      * The window's {@link AccessibilityWindowInfo#isActive()} changed.
      */
-    public static final int WINDOWS_CHANGE_ACTIVE = 0x00000020;
+    public static final int WINDOWS_CHANGE_ACTIVE = 1 << 5;
 
     /**
      * Change type for {@link #TYPE_WINDOWS_CHANGED} event:
      * The window's {@link AccessibilityWindowInfo#isFocused()} changed.
      */
-    public static final int WINDOWS_CHANGE_FOCUSED = 0x00000040;
+    public static final int WINDOWS_CHANGE_FOCUSED = 1 << 6;
 
     /**
      * Change type for {@link #TYPE_WINDOWS_CHANGED} event:
      * The window's {@link AccessibilityWindowInfo#isAccessibilityFocused()} changed.
      */
-    public static final int WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED = 0x00000080;
+    public static final int WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED = 1 << 7;
 
     /**
      * Change type for {@link #TYPE_WINDOWS_CHANGED} event:
      * The window's parent changed.
      */
-    public static final int WINDOWS_CHANGE_PARENT = 0x00000100;
+    public static final int WINDOWS_CHANGE_PARENT = 1 << 8;
 
     /**
      * Change type for {@link #TYPE_WINDOWS_CHANGED} event:
      * The window's children changed.
      */
-    public static final int WINDOWS_CHANGE_CHILDREN = 0x00000200;
+    public static final int WINDOWS_CHANGE_CHILDREN = 1 << 9;
 
     /**
      * Change type for {@link #TYPE_WINDOWS_CHANGED} event:
      * The window either entered or exited picture-in-picture mode.
      */
-    public static final int WINDOWS_CHANGE_PIP = 0x00000400;
+    public static final int WINDOWS_CHANGE_PIP = 1 << 10;
 
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
@@ -889,6 +933,7 @@
     public @interface SpeechStateChangeTypes {}
 
     /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
     @IntDef(
             flag = true,
             prefix = {"TYPE_"},
@@ -921,7 +966,6 @@
                 TYPE_SPEECH_STATE_CHANGE,
                 TYPE_VIEW_TARGETED_BY_SCROLL
             })
-    @Retention(RetentionPolicy.SOURCE)
     public @interface EventType {}
 
     /**
@@ -942,6 +986,8 @@
      * @see #TYPE_VIEW_SCROLLED
      * @see #TYPE_VIEW_TEXT_SELECTION_CHANGED
      * @see #TYPE_ANNOUNCEMENT
+     * @see #TYPE_VIEW_ACCESSIBILITY_FOCUSED
+     * @see #TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED
      * @see #TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
      * @see #TYPE_GESTURE_DETECTION_START
      * @see #TYPE_GESTURE_DETECTION_END
@@ -949,12 +995,15 @@
      * @see #TYPE_TOUCH_INTERACTION_END
      * @see #TYPE_WINDOWS_CHANGED
      * @see #TYPE_VIEW_CONTEXT_CLICKED
+     * @see #TYPE_ASSIST_READING_CONTEXT
+     * @see #TYPE_SPEECH_STATE_CHANGE
      * @see #TYPE_VIEW_TARGETED_BY_SCROLL
      */
     public static final int TYPES_ALL_MASK = 0xFFFFFFFF;
 
     @UnsupportedAppUsage
-    private @EventType int mEventType;
+    @EventType
+    private int mEventType;
     private CharSequence mPackageName;
     private long mEventTime;
     int mMovementGranularity;
@@ -1021,10 +1070,8 @@
         mEventTime = event.mEventTime;
         mPackageName = event.mPackageName;
         if (event.mRecords != null) {
-            final int recordCount = event.mRecords.size();
-            mRecords = new ArrayList<>(recordCount);
-            for (int i = 0; i < recordCount; i++) {
-                final AccessibilityRecord record = event.mRecords.get(i);
+            mRecords = new ArrayList<>(event.mRecords.size());
+            for (AccessibilityRecord record : event.mRecords) {
                 final AccessibilityRecord recordClone = new AccessibilityRecord(record);
                 mRecords.add(recordClone);
             }
@@ -1044,9 +1091,7 @@
         super.setSealed(sealed);
         final List<AccessibilityRecord> records = mRecords;
         if (records != null) {
-            final int recordCount = records.size();
-            for (int i = 0; i < recordCount; i++) {
-                AccessibilityRecord record = records.get(i);
+            for (AccessibilityRecord record : records) {
                 record.setSealed(sealed);
             }
         }
@@ -1094,7 +1139,8 @@
      *
      * @return The event type.
      */
-    public @EventType int getEventType() {
+    @EventType
+    public int getEventType() {
         return mEventType;
     }
 
@@ -1113,6 +1159,12 @@
      *         <li>{@link #CONTENT_CHANGE_TYPE_UNDEFINED}
      *         <li>{@link #CONTENT_CHANGE_TYPE_PANE_APPEARED}
      *         <li>{@link #CONTENT_CHANGE_TYPE_PANE_DISAPPEARED}
+     *         <li>{@link #CONTENT_CHANGE_TYPE_DRAG_STARTED}
+     *         <li>{@link #CONTENT_CHANGE_TYPE_DRAG_DROPPED}
+     *         <li>{@link #CONTENT_CHANGE_TYPE_DRAG_CANCELLED}
+     *         <li>{@link #CONTENT_CHANGE_TYPE_CONTENT_INVALID}
+     *         <li>{@link #CONTENT_CHANGE_TYPE_ERROR}
+     *         <li>{@link #CONTENT_CHANGE_TYPE_ENABLED}
      *         </ul>
      */
     @ContentChangeTypes
@@ -1203,7 +1255,9 @@
     }
 
     /**
-     * Gets the bit mask of the speech state signaled by a {@link #TYPE_SPEECH_STATE_CHANGE} event
+     * Gets the bit mask of the speech state signaled by a {@link #TYPE_SPEECH_STATE_CHANGE} event.
+     *
+     * @return The bit mask of speech change types.
      *
      * @see #SPEECH_STATE_SPEAKING_START
      * @see #SPEECH_STATE_SPEAKING_END
@@ -1256,6 +1310,18 @@
      * single event may represent multiple change types.
      *
      * @return The bit mask of change types.
+     *
+     * @see #WINDOWS_CHANGE_ADDED
+     * @see #WINDOWS_CHANGE_REMOVED
+     * @see #WINDOWS_CHANGE_TITLE
+     * @see #WINDOWS_CHANGE_BOUNDS
+     * @see #WINDOWS_CHANGE_LAYER
+     * @see #WINDOWS_CHANGE_ACTIVE
+     * @see #WINDOWS_CHANGE_FOCUSED
+     * @see #WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED
+     * @see #WINDOWS_CHANGE_PARENT
+     * @see #WINDOWS_CHANGE_CHILDREN
+     * @see #WINDOWS_CHANGE_PIP
      */
     @WindowsChangeTypes
     public int getWindowChanges() {
@@ -1375,6 +1441,21 @@
      * <li>{@link AccessibilityNodeInfo#ACTION_CLEAR_FOCUS}
      * <li>{@link AccessibilityNodeInfo#ACTION_CLEAR_SELECTION}
      * <li>{@link AccessibilityNodeInfo#ACTION_CLICK}
+     * <li>{@link AccessibilityNodeInfo#ACTION_LONG_CLICK}
+     * <li>{@link AccessibilityNodeInfo#ACTION_NEXT_AT_MOVEMENT_GRANULARITY}
+     * <li>{@link AccessibilityNodeInfo#ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY}
+     * <li>{@link AccessibilityNodeInfo#ACTION_NEXT_HTML_ELEMENT}
+     * <li>{@link AccessibilityNodeInfo#ACTION_PREVIOUS_HTML_ELEMENT}
+     * <li>{@link AccessibilityNodeInfo#ACTION_SCROLL_FORWARD}
+     * <li>{@link AccessibilityNodeInfo#ACTION_SCROLL_BACKWARD}
+     * <li>{@link AccessibilityNodeInfo#ACTION_COPY}
+     * <li>{@link AccessibilityNodeInfo#ACTION_PASTE}
+     * <li>{@link AccessibilityNodeInfo#ACTION_CUT}
+     * <li>{@link AccessibilityNodeInfo#ACTION_SET_SELECTION}
+     * <li>{@link AccessibilityNodeInfo#ACTION_EXPAND}
+     * <li>{@link AccessibilityNodeInfo#ACTION_COLLAPSE}
+     * <li>{@link AccessibilityNodeInfo#ACTION_DISMISS}
+     * <li>{@link AccessibilityNodeInfo#ACTION_SET_TEXT}
      * <li>etc.
      * </ul>
      *
@@ -1758,7 +1839,8 @@
     /**
      * @see Parcelable.Creator
      */
-    public static final @android.annotation.NonNull Parcelable.Creator<AccessibilityEvent> CREATOR =
+    @NonNull
+    public static final Parcelable.Creator<AccessibilityEvent> CREATOR =
             new Parcelable.Creator<AccessibilityEvent>() {
         public AccessibilityEvent createFromParcel(Parcel parcel) {
             AccessibilityEvent event = new AccessibilityEvent();
diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
index d9fcfb5..f14ec37 100644
--- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java
+++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
@@ -62,6 +62,7 @@
 import java.util.Queue;
 import java.util.concurrent.Executor;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.IntConsumer;
 
 /**
  * This class is a singleton that performs accessibility interaction
@@ -158,11 +159,15 @@
     private final SparseArray<Pair<Executor, AccessibilityService.TakeScreenshotCallback>>
             mTakeScreenshotOfWindowCallbacks = new SparseArray<>();
 
+    // SparseArray of interaction ID -> overlay executor+callback.
+    private final SparseArray<Pair<Executor, IntConsumer>> mAttachAccessibilityOverlayCallbacks =
+            new SparseArray<>();
     private Message mSameThreadMessage;
 
     private int mInteractionIdWaitingForPrefetchResult = -1;
     private int mConnectionIdWaitingForPrefetchResult;
     private String[] mPackageNamesForNextPrefetchResult;
+    private Handler mMainHandler = new Handler(Looper.getMainLooper());
 
     /**
      * @return The client for the current thread.
@@ -830,7 +835,7 @@
                                             interactionId));
                     connection.takeScreenshotOfWindow(accessibilityWindowId, interactionId,
                             listener, this);
-                    new Handler(Looper.getMainLooper()).postDelayed(() -> {
+                    mMainHandler.postDelayed(() -> {
                         synchronized (mInstanceLock) {
                             // Notify failure if we still haven't sent a response after timeout.
                             if (mTakeScreenshotOfWindowCallbacks.contains(interactionId)) {
@@ -1621,7 +1626,11 @@
 
     private void logTraceClient(
             IAccessibilityServiceConnection connection, String method, String params) {
-        logTrace(connection, method, params, Binder.getCallingUid(),
+        logTrace(
+                connection,
+                method,
+                params,
+                Binder.getCallingUid(),
                 Arrays.asList(Thread.currentThread().getStackTrace()),
                 new HashSet<String>(Arrays.asList("getStackTrace", "logTraceClient")),
                 FLAGS_ACCESSIBILITY_INTERACTION_CLIENT);
@@ -1629,18 +1638,105 @@
 
     /** Attaches an accessibility overlay to the specified window. */
     public void attachAccessibilityOverlayToWindow(
-            int connectionId, int accessibilityWindowId, SurfaceControl sc) {
+            int connectionId,
+            int accessibilityWindowId,
+            SurfaceControl sc,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull IntConsumer callback) {
         synchronized (mInstanceLock) {
             try {
                 IAccessibilityServiceConnection connection = getConnection(connectionId);
                 if (connection == null) {
-                    Log.e(LOG_TAG, "Error while getting service connection.");
+                    executor.execute(
+                            () ->
+                                    callback.accept(
+                                            AccessibilityService.OVERLAY_RESULT_INTERNAL_ERROR));
                     return;
                 }
-                connection.attachAccessibilityOverlayToWindow(accessibilityWindowId, sc);
+                final int interactionId = mInteractionIdCounter.getAndIncrement();
+                mAttachAccessibilityOverlayCallbacks.put(
+                        interactionId, Pair.create(executor, callback));
+                connection.attachAccessibilityOverlayToWindow(
+                        interactionId, accessibilityWindowId, sc, this);
+                mMainHandler.postDelayed(
+                        () -> {
+                            synchronized (mInstanceLock) {
+                                // Notify failure if we still haven't sent a response after timeout.
+                                if (mAttachAccessibilityOverlayCallbacks.contains(interactionId)) {
+                                    sendAttachOverlayResult(
+                                            AccessibilityService.OVERLAY_RESULT_INTERNAL_ERROR,
+                                            interactionId);
+                                }
+                            }
+                        },
+                        TIMEOUT_INTERACTION_MILLIS);
             } catch (RemoteException re) {
                 re.rethrowFromSystemServer();
             }
         }
     }
+
+    /** Attaches an accessibility overlay to the specified display. */
+    public void attachAccessibilityOverlayToDisplay(
+            int connectionId,
+            int displayId,
+            SurfaceControl sc,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull IntConsumer callback) {
+        synchronized (mInstanceLock) {
+            try {
+                IAccessibilityServiceConnection connection = getConnection(connectionId);
+                if (connection == null) {
+                    executor.execute(
+                            () ->
+                                    callback.accept(
+                                            AccessibilityService.OVERLAY_RESULT_INTERNAL_ERROR));
+                    return;
+                }
+                final int interactionId = mInteractionIdCounter.getAndIncrement();
+                mAttachAccessibilityOverlayCallbacks.put(
+                        interactionId, Pair.create(executor, callback));
+                connection.attachAccessibilityOverlayToDisplay(interactionId, displayId, sc, this);
+                mMainHandler.postDelayed(
+                        () -> {
+                            // Notify failure if we still haven't sent a response after timeout.
+                            if (mAttachAccessibilityOverlayCallbacks.contains(interactionId)) {
+                                sendAttachOverlayResult(
+                                        AccessibilityService.OVERLAY_RESULT_INTERNAL_ERROR,
+                                        interactionId);
+                            }
+                        },
+                        TIMEOUT_INTERACTION_MILLIS);
+            } catch (RemoteException re) {
+                re.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
+     * Sends a result code for an attach window overlay request to the requesting client.
+     *
+     * @param result The result code from {@link AccessibilityService.OverlayResult}.
+     * @param interactionId The interaction id of the request.
+     */
+    @Override
+    public void sendAttachOverlayResult(
+            @AccessibilityService.AttachOverlayResult int result, int interactionId) {
+        synchronized (mInstanceLock) {
+            if (mAttachAccessibilityOverlayCallbacks.contains(interactionId)) {
+                final Pair<Executor, IntConsumer> pair =
+                        mAttachAccessibilityOverlayCallbacks.get(interactionId);
+                if (pair == null) {
+                    return;
+                }
+                final Executor executor = pair.first;
+                final IntConsumer callback = pair.second;
+                if (executor == null || callback == null) {
+                    return;
+                }
+                executor.execute(() -> callback.accept(result));
+                mAttachAccessibilityOverlayCallbacks.remove(interactionId);
+            }
+        }
+    }
 }
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index efa2a01..b220c4d 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -99,30 +99,30 @@
     private static final String LOG_TAG = "AccessibilityManager";
 
     /** @hide */
-    public static final int STATE_FLAG_ACCESSIBILITY_ENABLED = 0x00000001;
+    public static final int STATE_FLAG_ACCESSIBILITY_ENABLED = 1 /* << 0 */;
 
     /** @hide */
-    public static final int STATE_FLAG_TOUCH_EXPLORATION_ENABLED = 0x00000002;
+    public static final int STATE_FLAG_TOUCH_EXPLORATION_ENABLED = 1 << 1;
 
     /** @hide */
-    public static final int STATE_FLAG_HIGH_TEXT_CONTRAST_ENABLED = 0x00000004;
+    public static final int STATE_FLAG_HIGH_TEXT_CONTRAST_ENABLED = 1 << 2;
 
     /** @hide */
-    public static final int STATE_FLAG_DISPATCH_DOUBLE_TAP = 0x00000008;
+    public static final int STATE_FLAG_DISPATCH_DOUBLE_TAP = 1 << 3;
 
     /** @hide */
-    public static final int STATE_FLAG_REQUEST_MULTI_FINGER_GESTURES = 0x00000010;
+    public static final int STATE_FLAG_REQUEST_MULTI_FINGER_GESTURES = 1 << 4;
 
     /** @hide */
-    public static final int STATE_FLAG_TRACE_A11Y_INTERACTION_CONNECTION_ENABLED = 0x00000100;
+    public static final int STATE_FLAG_TRACE_A11Y_INTERACTION_CONNECTION_ENABLED = 1 << 8;
     /** @hide */
-    public static final int STATE_FLAG_TRACE_A11Y_INTERACTION_CONNECTION_CB_ENABLED = 0x00000200;
+    public static final int STATE_FLAG_TRACE_A11Y_INTERACTION_CONNECTION_CB_ENABLED = 1 << 9;
     /** @hide */
-    public static final int STATE_FLAG_TRACE_A11Y_INTERACTION_CLIENT_ENABLED = 0x00000400;
+    public static final int STATE_FLAG_TRACE_A11Y_INTERACTION_CLIENT_ENABLED = 1 << 10;
     /** @hide */
-    public static final int STATE_FLAG_TRACE_A11Y_SERVICE_ENABLED = 0x00000800;
+    public static final int STATE_FLAG_TRACE_A11Y_SERVICE_ENABLED = 1 << 11;
     /** @hide */
-    public static final int STATE_FLAG_AUDIO_DESCRIPTION_BY_DEFAULT_ENABLED = 0x00001000;
+    public static final int STATE_FLAG_AUDIO_DESCRIPTION_BY_DEFAULT_ENABLED = 1 << 12;
 
     /** @hide */
     public static final int DALTONIZER_DISABLED = -1;
@@ -848,7 +848,7 @@
 
         List<AccessibilityServiceInfo> services = null;
         try {
-            services = service.getInstalledAccessibilityServiceList(userId);
+            services = service.getInstalledAccessibilityServiceList(userId).getList();
             if (DEBUG) {
                 Log.i(LOG_TAG, "Installed AccessibilityServices " + services);
             }
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index ddd7734..e6a8b78 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -23,6 +23,7 @@
 
 import android.accessibilityservice.AccessibilityService;
 import android.accessibilityservice.AccessibilityServiceInfo;
+import android.annotation.Hide;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -99,6 +100,9 @@
  * <a href="{@docRoot}guide/topics/ui/accessibility/index.html">Accessibility</a>
  * developer guide.</p>
  * </div>
+ * <aside class="note">
+ * <b>Note:</b> Use a {@link androidx.core.view.accessibility.AccessibilityNodeInfoCompat}
+ * wrapper instead of this class for backwards-compatibility. </aside>
  *
  * @see android.accessibilityservice.AccessibilityService
  * @see AccessibilityEvent
@@ -136,6 +140,8 @@
     public static final long LEASHED_NODE_ID = makeNodeId(LEASHED_ITEM_ID,
             AccessibilityNodeProvider.HOST_VIEW_ID);
 
+    // Prefetch flags.
+
     /**
      * Prefetching strategy that prefetches the ancestors of the requested node.
      * <p> Ancestors will be prefetched before siblings and descendants.
@@ -146,7 +152,7 @@
      * @see AccessibilityService#getRootInActiveWindow(int)
      * @see AccessibilityEvent#getSource(int)
      */
-    public static final int FLAG_PREFETCH_ANCESTORS = 0x00000001;
+    public static final int FLAG_PREFETCH_ANCESTORS = 1 /* << 0 */;
 
     /**
      * Prefetching strategy that prefetches the siblings of the requested node.
@@ -155,7 +161,7 @@
      *
      * @see #FLAG_PREFETCH_ANCESTORS for where to use these flags.
      */
-    public static final int FLAG_PREFETCH_SIBLINGS = 0x00000002;
+    public static final int FLAG_PREFETCH_SIBLINGS = 1 << 1;
 
     /**
      * Prefetching strategy that prefetches the descendants in a hybrid depth first and breadth
@@ -167,7 +173,7 @@
      *
      * @see #FLAG_PREFETCH_ANCESTORS for where to use these flags.
      */
-    public static final int FLAG_PREFETCH_DESCENDANTS_HYBRID = 0x00000004;
+    public static final int FLAG_PREFETCH_DESCENDANTS_HYBRID = 1 << 2;
 
     /**
      * Prefetching strategy that prefetches the descendants of the requested node depth-first.
@@ -177,7 +183,7 @@
      *
      * @see #FLAG_PREFETCH_ANCESTORS for where to use these flags.
      */
-    public static final int FLAG_PREFETCH_DESCENDANTS_DEPTH_FIRST = 0x00000008;
+    public static final int FLAG_PREFETCH_DESCENDANTS_DEPTH_FIRST = 1 << 3;
 
     /**
      * Prefetching strategy that prefetches the descendants of the requested node breadth-first.
@@ -187,7 +193,7 @@
      *
      * @see #FLAG_PREFETCH_ANCESTORS for where to use these flags.
      */
-    public static final int FLAG_PREFETCH_DESCENDANTS_BREADTH_FIRST = 0x00000010;
+    public static final int FLAG_PREFETCH_DESCENDANTS_BREADTH_FIRST = 1 << 4;
 
     /**
      * Prefetching flag that specifies prefetching should not be interrupted by a request to
@@ -195,12 +201,31 @@
      *
      * @see #FLAG_PREFETCH_ANCESTORS for where to use these flags.
      */
-    public static final int FLAG_PREFETCH_UNINTERRUPTIBLE = 0x00000020;
+    public static final int FLAG_PREFETCH_UNINTERRUPTIBLE = 1 << 5;
 
-    /** @hide */
-    public static final int FLAG_PREFETCH_MASK = 0x0000003f;
+    /**
+     * Mask for {@link PrefetchingStrategy} all types.
+     *
+     * @see #FLAG_PREFETCH_ANCESTORS
+     * @see #FLAG_PREFETCH_SIBLINGS
+     * @see #FLAG_PREFETCH_DESCENDANTS_HYBRID
+     * @see #FLAG_PREFETCH_DESCENDANTS_DEPTH_FIRST
+     * @see #FLAG_PREFETCH_DESCENDANTS_BREADTH_FIRST
+     * @see #FLAG_PREFETCH_UNINTERRUPTIBLE
+     *
+     * @hide
+     */
+    public static final int FLAG_PREFETCH_MASK = 0x0000003F;
 
-    /** @hide */
+    /**
+     * Mask for {@link PrefetchingStrategy} that includes only descendants-related strategies.
+     *
+     * @see #FLAG_PREFETCH_DESCENDANTS_HYBRID
+     * @see #FLAG_PREFETCH_DESCENDANTS_DEPTH_FIRST
+     * @see #FLAG_PREFETCH_DESCENDANTS_BREADTH_FIRST
+     *
+     * @hide
+     */
     public static final int FLAG_PREFETCH_DESCENDANTS_MASK = 0x0000001C;
 
     /**
@@ -210,6 +235,7 @@
     public static final int MAX_NUMBER_OF_PREFETCHED_NODES = 50;
 
     /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
     @IntDef(flag = true, prefix = { "FLAG_PREFETCH" }, value = {
             FLAG_PREFETCH_ANCESTORS,
             FLAG_PREFETCH_SIBLINGS,
@@ -218,28 +244,33 @@
             FLAG_PREFETCH_DESCENDANTS_BREADTH_FIRST,
             FLAG_PREFETCH_UNINTERRUPTIBLE
     })
-    @Retention(RetentionPolicy.SOURCE)
     public @interface PrefetchingStrategy {}
 
+    // Service flags.
+
     /**
      * @see AccessibilityServiceInfo#FLAG_INCLUDE_NOT_IMPORTANT_VIEWS
      * @hide
      */
-    public static final int FLAG_SERVICE_REQUESTS_INCLUDE_NOT_IMPORTANT_VIEWS = 0x00000080;
+    public static final int FLAG_SERVICE_REQUESTS_INCLUDE_NOT_IMPORTANT_VIEWS = 1 << 7;
 
     /**
      * @see AccessibilityServiceInfo#FLAG_REPORT_VIEW_IDS
      * @hide
      */
-    public static final int FLAG_SERVICE_REQUESTS_REPORT_VIEW_IDS = 0x00000100;
+    public static final int FLAG_SERVICE_REQUESTS_REPORT_VIEW_IDS = 1 << 8;
 
     /**
      * @see AccessibilityServiceInfo#isAccessibilityTool()
      * @hide
      */
-    public static final int FLAG_SERVICE_IS_ACCESSIBILITY_TOOL = 0x00000200;
+    public static final int FLAG_SERVICE_IS_ACCESSIBILITY_TOOL = 1 << 9;
 
-    /** @hide */
+    /**
+     * Mask for all types of additional view data exposed to services.
+     *
+     * @hide
+     */
     public static final int FLAG_REPORT_MASK =
             FLAG_SERVICE_REQUESTS_INCLUDE_NOT_IMPORTANT_VIEWS
                     | FLAG_SERVICE_REQUESTS_REPORT_VIEW_IDS
@@ -249,47 +280,53 @@
 
     /**
      * Action that gives input focus to the node.
+     * See {@link AccessibilityAction#ACTION_FOCUS}
      */
-    public static final int ACTION_FOCUS =  0x00000001;
+    public static final int ACTION_FOCUS =  1 /* << 0 */;
 
     /**
      * Action that clears input focus of the node.
+     * See {@link AccessibilityAction#ACTION_CLEAR_FOCUS}
      */
-    public static final int ACTION_CLEAR_FOCUS = 0x00000002;
+    public static final int ACTION_CLEAR_FOCUS = 1 << 1;
 
     /**
      * Action that selects the node.
+     * @see AccessibilityAction#ACTION_SELECT
      */
-    public static final int ACTION_SELECT = 0x00000004;
+    public static final int ACTION_SELECT = 1 << 2;
 
     /**
      * Action that deselects the node.
      */
-    public static final int ACTION_CLEAR_SELECTION = 0x00000008;
+    public static final int ACTION_CLEAR_SELECTION = 1 << 3;
 
     /**
      * Action that clicks on the node info.
      *
-     * See {@link AccessibilityAction#ACTION_CLICK}
+     * @see AccessibilityAction#ACTION_CLICK
      */
-    public static final int ACTION_CLICK = 0x00000010;
+    public static final int ACTION_CLICK = 1 << 4;
 
     /**
      * Action that long clicks on the node.
      *
      * <p>It does not support coordinate information for anchoring.</p>
+     * @see AccessibilityAction#ACTION_LONG_CLICK
      */
-    public static final int ACTION_LONG_CLICK = 0x00000020;
+    public static final int ACTION_LONG_CLICK = 1 << 5;
 
     /**
      * Action that gives accessibility focus to the node.
+     * See {@link AccessibilityAction#ACTION_ACCESSIBILITY_FOCUS}
      */
-    public static final int ACTION_ACCESSIBILITY_FOCUS = 0x00000040;
+    public static final int ACTION_ACCESSIBILITY_FOCUS = 1 << 6;
 
     /**
      * Action that clears accessibility focus of the node.
+     * See {@link AccessibilityAction#ACTION_CLEAR_ACCESSIBILITY_FOCUS}
      */
-    public static final int ACTION_CLEAR_ACCESSIBILITY_FOCUS = 0x00000080;
+    public static final int ACTION_CLEAR_ACCESSIBILITY_FOCUS = 1 << 7;
 
     /**
      * Action that requests to go to the next entity in this node's text
@@ -321,7 +358,7 @@
      * @see #MOVEMENT_GRANULARITY_PARAGRAPH
      * @see #MOVEMENT_GRANULARITY_PAGE
      */
-    public static final int ACTION_NEXT_AT_MOVEMENT_GRANULARITY = 0x00000100;
+    public static final int ACTION_NEXT_AT_MOVEMENT_GRANULARITY = 1 << 8;
 
     /**
      * Action that requests to go to the previous entity in this node's text
@@ -354,7 +391,7 @@
      * @see #MOVEMENT_GRANULARITY_PARAGRAPH
      * @see #MOVEMENT_GRANULARITY_PAGE
      */
-    public static final int ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY = 0x00000200;
+    public static final int ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY = 1 << 9;
 
     /**
      * Action to move to the next HTML element of a given type. For example, move
@@ -369,7 +406,7 @@
      * </code></pre></p>
      * </p>
      */
-    public static final int ACTION_NEXT_HTML_ELEMENT = 0x00000400;
+    public static final int ACTION_NEXT_HTML_ELEMENT = 1 << 10;
 
     /**
      * Action to move to the previous HTML element of a given type. For example, move
@@ -384,95 +421,90 @@
      * </code></pre></p>
      * </p>
      */
-    public static final int ACTION_PREVIOUS_HTML_ELEMENT = 0x00000800;
+    public static final int ACTION_PREVIOUS_HTML_ELEMENT = 1 << 11;
 
     /**
      * Action to scroll the node content forward.
+     * @see AccessibilityAction#ACTION_SCROLL_FORWARD
      */
-    public static final int ACTION_SCROLL_FORWARD = 0x00001000;
+    public static final int ACTION_SCROLL_FORWARD = 1 << 12;
 
     /**
      * Action to scroll the node content backward.
+     * @see AccessibilityAction#ACTION_SCROLL_BACKWARD
      */
-    public static final int ACTION_SCROLL_BACKWARD = 0x00002000;
+    public static final int ACTION_SCROLL_BACKWARD = 1 << 13;
 
     /**
      * Action to copy the current selection to the clipboard.
      */
-    public static final int ACTION_COPY = 0x00004000;
+    public static final int ACTION_COPY = 1 << 14;
 
     /**
      * Action to paste the current clipboard content.
      */
-    public static final int ACTION_PASTE = 0x00008000;
+    public static final int ACTION_PASTE = 1 << 15;
 
     /**
      * Action to cut the current selection and place it to the clipboard.
      */
-    public static final int ACTION_CUT = 0x00010000;
+    public static final int ACTION_CUT = 1 << 16;
 
     /**
      * Action to set the selection. Performing this action with no arguments
      * clears the selection.
-     * <p>
-     * <strong>Arguments:</strong>
-     * {@link #ACTION_ARGUMENT_SELECTION_START_INT},
-     * {@link #ACTION_ARGUMENT_SELECTION_END_INT}<br>
-     * <strong>Example:</strong>
-     * <code><pre><p>
-     *   Bundle arguments = new Bundle();
-     *   arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, 1);
-     *   arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, 2);
-     *   info.performAction(AccessibilityNodeInfo.ACTION_SET_SELECTION, arguments);
-     * </code></pre></p>
-     * </p>
      *
+     * @see AccessibilityAction#ACTION_SET_SELECTION
      * @see #ACTION_ARGUMENT_SELECTION_START_INT
      * @see #ACTION_ARGUMENT_SELECTION_END_INT
      */
-    public static final int ACTION_SET_SELECTION = 0x00020000;
+    public static final int ACTION_SET_SELECTION = 1 << 17;
 
     /**
      * Action to expand an expandable node.
      */
-    public static final int ACTION_EXPAND = 0x00040000;
+    public static final int ACTION_EXPAND = 1 << 18;
 
     /**
      * Action to collapse an expandable node.
      */
-    public static final int ACTION_COLLAPSE = 0x00080000;
+    public static final int ACTION_COLLAPSE = 1 << 19;
 
     /**
      * Action to dismiss a dismissable node.
      */
-    public static final int ACTION_DISMISS = 0x00100000;
+    public static final int ACTION_DISMISS = 1 << 20;
 
     /**
      * Action that sets the text of the node. Performing the action without argument, using <code>
      * null</code> or empty {@link CharSequence} will clear the text. This action will also put the
      * cursor at the end of text.
-     * <p>
-     * <strong>Arguments:</strong>
-     * {@link #ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE}<br>
-     * <strong>Example:</strong>
-     * <code><pre><p>
-     *   Bundle arguments = new Bundle();
-     *   arguments.putCharSequence(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE,
-     *       "android");
-     *   info.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, arguments);
-     * </code></pre></p>
+     * @see AccessibilityAction#ACTION_SET_TEXT
      */
-    public static final int ACTION_SET_TEXT = 0x00200000;
+    public static final int ACTION_SET_TEXT = 1 << 21;
 
     /** @hide */
     public static final int LAST_LEGACY_STANDARD_ACTION = ACTION_SET_TEXT;
 
     /**
-     * Mask to see if the value is larger than the largest ACTION_ constant
+     * Mask to verify if a given value is a combination of the existing ACTION_ constants.
+     *
+     * The smallest possible action is 1, and the largest is 1 << 21, or {@link ACTION_SET_TEXT}. A
+     * node can have any combination of actions present, so a node's max action int is:
+     *
+     *   0000 0000 0011 1111 1111 1111 1111 1111
+     *
+     * Therefore, if an action has any of the following bits flipped, it will be invalid:
+     *
+     *   1111 1111 11-- ---- ---- ---- ---- ----
+     *
+     * This can be represented in hexadecimal as 0xFFC00000.
+     *
+     * @see AccessibilityNodeInfo#addAction(int)
      */
-    private static final int ACTION_TYPE_MASK = 0xFF000000;
+    private static final int INVALID_ACTIONS_MASK = 0xFFC00000;
 
-    // Action arguments
+    // Action arguments.
 
     /**
      * Argument for which movement granularity to be used when traversing the node text.
@@ -678,7 +710,52 @@
     public static final String ACTION_ARGUMENT_DIRECTION_INT =
             "android.view.accessibility.action.ARGUMENT_DIRECTION_INT";
 
-    // Focus types
+    /**
+     * <p>Argument to represent the scroll amount as a percent of the visible area of a node, with
+     * 1.0F as the default. Values smaller than 1.0F represent a partial scroll of the node, and
+     * values larger than 1.0F represent a scroll that extends beyond the currently visible node
+     * Rect. Setting this to {@link Float#POSITIVE_INFINITY} or to another "too large" value should
+     * scroll to the end of the node. Negative values should not be used with this argument.
+     * </p>
+     *
+     * <p>
+     *     This argument should be used with the following scroll actions:
+     *     <ul>
+     *         <li>{@link AccessibilityAction#ACTION_SCROLL_FORWARD}</li>
+     *         <li>{@link AccessibilityAction#ACTION_SCROLL_BACKWARD}</li>
+     *         <li>{@link AccessibilityAction#ACTION_SCROLL_UP}</li>
+     *         <li>{@link AccessibilityAction#ACTION_SCROLL_DOWN}</li>
+     *         <li>{@link AccessibilityAction#ACTION_SCROLL_LEFT}</li>
+     *         <li>{@link AccessibilityAction#ACTION_SCROLL_RIGHT}</li>
+     *     </ul>
+     * </p>
+     * <p>
+     *     Example: if a view representing a list of items implements
+     *     {@link AccessibilityAction#ACTION_SCROLL_FORWARD} to scroll forward by an entire screen
+     *     (one "page"), then passing a value of .25F via this argument should scroll that view
+     *     only by 1/4th of a screen. Passing a value of 1.50F via this argument should scroll the
+     *     view by 1 1/2 screens or to end of the node if the node doesn't extend to 1 1/2 screens.
+     * </p>
+     *
+     * <p>
+     *     This argument should not be used with the following scroll actions, which don't cleanly
+     *     conform to granular scroll semantics:
+     *     <ul>
+     *         <li>{@link AccessibilityAction#ACTION_SCROLL_IN_DIRECTION}</li>
+     *         <li>{@link AccessibilityAction#ACTION_SCROLL_TO_POSITION}</li>
+     *     </ul>
+     * </p>
+     *
+     * <p>
+     *     Views that support this argument should set
+     *     {@link #setGranularScrollingSupported(boolean)} to true. Clients should use
+     *     {@link #isGranularScrollingSupported()} to check if granular scrolling is supported.
+     * </p>
+     */
+    public static final String ACTION_ARGUMENT_SCROLL_AMOUNT_FLOAT =
+            "android.view.accessibility.action.ARGUMENT_SCROLL_AMOUNT_FLOAT";
+
+    // Focus types.
 
     /**
      * The input focus.
@@ -690,32 +767,34 @@
      */
     public static final int FOCUS_ACCESSIBILITY = 2;
 
-    // Movement granularities
+    // Movement granularities.
 
     /**
      * Movement granularity bit for traversing the text of a node by character.
      */
-    public static final int MOVEMENT_GRANULARITY_CHARACTER = 0x00000001;
+    public static final int MOVEMENT_GRANULARITY_CHARACTER = 1 /* << 0 */;
 
     /**
      * Movement granularity bit for traversing the text of a node by word.
      */
-    public static final int MOVEMENT_GRANULARITY_WORD = 0x00000002;
+    public static final int MOVEMENT_GRANULARITY_WORD = 1 << 1;
 
     /**
      * Movement granularity bit for traversing the text of a node by line.
      */
-    public static final int MOVEMENT_GRANULARITY_LINE = 0x00000004;
+    public static final int MOVEMENT_GRANULARITY_LINE = 1 << 2;
 
     /**
      * Movement granularity bit for traversing the text of a node by paragraph.
      */
-    public static final int MOVEMENT_GRANULARITY_PARAGRAPH = 0x00000008;
+    public static final int MOVEMENT_GRANULARITY_PARAGRAPH = 1 << 3;
 
     /**
      * Movement granularity bit for traversing the text of a node by page.
      */
-    public static final int MOVEMENT_GRANULARITY_PAGE = 0x00000010;
+    public static final int MOVEMENT_GRANULARITY_PAGE = 1 << 4;
+
+    // Extra data arguments.
 
     /**
      * Key used to request and locate extra data for text character location. This key requests that
@@ -746,7 +825,7 @@
 
     /**
      * Integer argument specifying the end index of the requested text location data. Must be
-     * positive and no larger than {@link #EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH}.
+     * positive and no larger than {@link #EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_MAX_LENGTH}.
      *
      * @see #EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY
      */
@@ -782,58 +861,60 @@
 
     // Boolean attributes.
 
-    private static final int BOOLEAN_PROPERTY_CHECKABLE = 0x00000001;
+    private static final int BOOLEAN_PROPERTY_CHECKABLE = 1 /* << 0 */;
 
-    private static final int BOOLEAN_PROPERTY_CHECKED = 0x00000002;
+    private static final int BOOLEAN_PROPERTY_CHECKED = 1 << 1;
 
-    private static final int BOOLEAN_PROPERTY_FOCUSABLE = 0x00000004;
+    private static final int BOOLEAN_PROPERTY_FOCUSABLE = 1 << 2;
 
-    private static final int BOOLEAN_PROPERTY_FOCUSED = 0x00000008;
+    private static final int BOOLEAN_PROPERTY_FOCUSED = 1 << 3;
 
-    private static final int BOOLEAN_PROPERTY_SELECTED = 0x00000010;
+    private static final int BOOLEAN_PROPERTY_SELECTED = 1 << 4;
 
-    private static final int BOOLEAN_PROPERTY_CLICKABLE = 0x00000020;
+    private static final int BOOLEAN_PROPERTY_CLICKABLE = 1 << 5;
 
-    private static final int BOOLEAN_PROPERTY_LONG_CLICKABLE = 0x00000040;
+    private static final int BOOLEAN_PROPERTY_LONG_CLICKABLE = 1 << 6;
 
-    private static final int BOOLEAN_PROPERTY_ENABLED = 0x00000080;
+    private static final int BOOLEAN_PROPERTY_ENABLED = 1 << 7;
 
-    private static final int BOOLEAN_PROPERTY_PASSWORD = 0x00000100;
+    private static final int BOOLEAN_PROPERTY_PASSWORD = 1 << 8;
 
-    private static final int BOOLEAN_PROPERTY_SCROLLABLE = 0x00000200;
+    private static final int BOOLEAN_PROPERTY_SCROLLABLE = 1 << 9;
 
-    private static final int BOOLEAN_PROPERTY_ACCESSIBILITY_FOCUSED = 0x00000400;
+    private static final int BOOLEAN_PROPERTY_ACCESSIBILITY_FOCUSED = 1 << 10;
 
-    private static final int BOOLEAN_PROPERTY_VISIBLE_TO_USER = 0x00000800;
+    private static final int BOOLEAN_PROPERTY_VISIBLE_TO_USER = 1 << 11;
 
-    private static final int BOOLEAN_PROPERTY_EDITABLE = 0x00001000;
+    private static final int BOOLEAN_PROPERTY_EDITABLE = 1 << 12;
 
-    private static final int BOOLEAN_PROPERTY_OPENS_POPUP = 0x00002000;
+    private static final int BOOLEAN_PROPERTY_OPENS_POPUP = 1 << 13;
 
-    private static final int BOOLEAN_PROPERTY_DISMISSABLE = 0x00004000;
+    private static final int BOOLEAN_PROPERTY_DISMISSABLE = 1 << 14;
 
-    private static final int BOOLEAN_PROPERTY_MULTI_LINE = 0x00008000;
+    private static final int BOOLEAN_PROPERTY_MULTI_LINE = 1 << 15;
 
-    private static final int BOOLEAN_PROPERTY_CONTENT_INVALID = 0x00010000;
+    private static final int BOOLEAN_PROPERTY_CONTENT_INVALID = 1 << 16;
 
-    private static final int BOOLEAN_PROPERTY_CONTEXT_CLICKABLE = 0x00020000;
+    private static final int BOOLEAN_PROPERTY_CONTEXT_CLICKABLE = 1 << 17;
 
-    private static final int BOOLEAN_PROPERTY_IMPORTANCE = 0x0040000;
+    private static final int BOOLEAN_PROPERTY_IMPORTANCE = 1 << 18;
 
-    private static final int BOOLEAN_PROPERTY_SCREEN_READER_FOCUSABLE = 0x0080000;
+    private static final int BOOLEAN_PROPERTY_SCREEN_READER_FOCUSABLE = 1 << 19;
 
-    private static final int BOOLEAN_PROPERTY_IS_SHOWING_HINT = 0x0100000;
+    private static final int BOOLEAN_PROPERTY_IS_SHOWING_HINT = 1 << 20;
 
-    private static final int BOOLEAN_PROPERTY_IS_HEADING = 0x0200000;
+    private static final int BOOLEAN_PROPERTY_IS_HEADING = 1 << 21;
 
-    private static final int BOOLEAN_PROPERTY_IS_TEXT_ENTRY_KEY = 0x0400000;
+    private static final int BOOLEAN_PROPERTY_IS_TEXT_ENTRY_KEY = 1 << 22;
 
-    private static final int BOOLEAN_PROPERTY_IS_TEXT_SELECTABLE = 0x0800000;
+    private static final int BOOLEAN_PROPERTY_IS_TEXT_SELECTABLE = 1 << 23;
 
     private static final int BOOLEAN_PROPERTY_REQUEST_INITIAL_ACCESSIBILITY_FOCUS = 1 << 24;
 
     private static final int BOOLEAN_PROPERTY_ACCESSIBILITY_DATA_SENSITIVE = 1 << 25;
 
+    private static final int BOOLEAN_PROPERTY_SUPPORTS_GRANULAR_SCROLLING = 1 << 26;
+
     /**
      * Bits that provide the id of a virtual descendant of a view.
      */
@@ -1481,6 +1562,10 @@
      * describes the action.
      * </p>
      * <p>
+     * Use {@link androidx.core.view.ViewCompat#addAccessibilityAction(View,
+     * AccessibilityNodeInfoCompat.AccessibilityActionCompat)} to register an action directly on the
+     * view.
+     * <p>
      *   <strong>Note:</strong> Cannot be called from an
      *   {@link android.accessibilityservice.AccessibilityService}.
      *   This class is made immutable before being delivered to an AccessibilityService.
@@ -1528,7 +1613,7 @@
     public void addAction(int action) {
         enforceNotSealed();
 
-        if ((action & ACTION_TYPE_MASK) != 0) {
+        if ((action & INVALID_ACTIONS_MASK) != 0) {
             throw new IllegalArgumentException("Action is not a combination of the standard " +
                     "actions: " + action);
         }
@@ -2254,6 +2339,7 @@
     /**
      * Gets whether this node is focusable.
      *
+     * <p>In the View system, this typically maps to {@link View#isFocusable()}.
      * @return True if the node is focusable.
      */
     public boolean isFocusable() {
@@ -2267,6 +2353,8 @@
      *   {@link android.accessibilityservice.AccessibilityService}.
      *   This class is made immutable before being delivered to an AccessibilityService.
      * </p>
+     * <p>To mark a node as explicitly focusable for a screen reader, consider using
+     * {@link #setScreenReaderFocusable(boolean)} instead.
      *
      * @param focusable True if the node is focusable.
      *
@@ -2279,6 +2367,9 @@
     /**
      * Gets whether this node is focused.
      *
+     * <p>This is distinct from {@link #isAccessibilityFocused()}, which is used by screen readers.
+     * See {@link AccessibilityAction#ACTION_ACCESSIBILITY_FOCUS} for details.
+     *
      * @return True if the node is focused.
      */
     public boolean isFocused() {
@@ -2335,6 +2426,8 @@
     /**
      * Gets whether this node is accessibility focused.
      *
+     * <p>This is distinct from {@link #isFocused()}, which is used to track system focus.
+     * See {@link #ACTION_ACCESSIBILITY_FOCUS} for details.
      * @return True if the node is accessibility focused.
      */
     public boolean isAccessibilityFocused() {
@@ -2348,7 +2441,10 @@
      *   {@link android.accessibilityservice.AccessibilityService}.
      *   This class is made immutable before being delivered to an AccessibilityService.
      * </p>
-     *
+     * <p>The UI element updating this property should send an event of
+     * {@link AccessibilityEvent#TYPE_VIEW_ACCESSIBILITY_FOCUSED}
+     * or {@link AccessibilityEvent#TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED} if its
+     * accessibility-focused state changes.
      * @param focused True if the node is accessibility focused.
      *
      * @throws IllegalStateException If called from an AccessibilityService.
@@ -2508,6 +2604,35 @@
     }
 
     /**
+     * Gets if the node supports granular scrolling.
+     *
+     * @return True if all scroll actions that could support
+     * {@link #ACTION_ARGUMENT_SCROLL_AMOUNT_FLOAT} have done so, false otherwise.
+     */
+    public boolean isGranularScrollingSupported() {
+        return getBooleanProperty(BOOLEAN_PROPERTY_SUPPORTS_GRANULAR_SCROLLING);
+    }
+
+    /**
+     * Sets if the node supports granular scrolling. This should be set to true if all scroll
+     * actions which could support {@link #ACTION_ARGUMENT_SCROLL_AMOUNT_FLOAT} have done so.
+     * <p>
+     *   <strong>Note:</strong> Cannot be called from an
+     *   {@link android.accessibilityservice.AccessibilityService}.
+     *   This class is made immutable before being delivered to an AccessibilityService.
+     * </p>
+     *
+     * @param granularScrollingSupported True if the node supports granular scrolling, false
+     *                                  otherwise.
+     *
+     * @throws IllegalStateException If called from an AccessibilityService.
+     */
+    public void setGranularScrollingSupported(boolean granularScrollingSupported) {
+        setBooleanProperty(BOOLEAN_PROPERTY_SUPPORTS_GRANULAR_SCROLLING,
+                granularScrollingSupported);
+    }
+
+    /**
      * Gets if the node has selectable text.
      *
      * <p>
@@ -2995,6 +3120,10 @@
      *   {@link android.accessibilityservice.AccessibilityService}.
      *   This class is made immutable before being delivered to an AccessibilityService.
      * </p>
+     * <p>This can be used to
+     * <a href="{@docRoot}guide/topics/ui/accessibility/principles#content-groups">group related
+     * content.</a>
+     * </p>
      *
      * @param screenReaderFocusable {@code true} if the node is a focusable unit for screen readers,
      *                              {@code false} otherwise.
@@ -4358,6 +4487,8 @@
             parcel.writeInt(mCollectionInfo.getColumnCount());
             parcel.writeInt(mCollectionInfo.isHierarchical() ? 1 : 0);
             parcel.writeInt(mCollectionInfo.getSelectionMode());
+            parcel.writeInt(mCollectionInfo.getItemCount());
+            parcel.writeInt(mCollectionInfo.getImportantForAccessibilityItemCount());
         }
 
         if (isBitSet(nonDefaultFields, fieldIndex++)) {
@@ -4486,7 +4617,8 @@
         CollectionInfo ci = other.mCollectionInfo;
         mCollectionInfo = (ci == null) ? null
                 : new CollectionInfo(ci.mRowCount, ci.mColumnCount,
-                                     ci.mHierarchical, ci.mSelectionMode);
+                        ci.mHierarchical, ci.mSelectionMode, ci.mItemCount,
+                        ci.mImportantForAccessibilityItemCount);
         CollectionItemInfo cii = other.mCollectionItemInfo;
         CollectionItemInfo.Builder builder = new CollectionItemInfo.Builder();
         mCollectionItemInfo = (cii == null)  ? null
@@ -4616,6 +4748,8 @@
                         parcel.readInt(),
                         parcel.readInt(),
                         parcel.readInt() == 1,
+                        parcel.readInt(),
+                        parcel.readInt(),
                         parcel.readInt())
                 : null;
 
@@ -4934,6 +5068,7 @@
         builder.append("; enabled: ").append(isEnabled());
         builder.append("; password: ").append(isPassword());
         builder.append("; scrollable: ").append(isScrollable());
+        builder.append("; granularScrollingSupported: ").append(isGranularScrollingSupported());
         builder.append("; importantForAccessibility: ").append(isImportantForAccessibility());
         builder.append("; visible: ").append(isVisibleToUser());
         builder.append("; actions: ").append(mActions);
@@ -5012,7 +5147,7 @@
      * and handled by custom widgets. i.e. ones that are not part of the UI toolkit. For
      * example, an application may define a custom action for clearing the user history.
      * </li>
-     * <li><strong>Overriden standard actions</strong> - These are actions that override
+     * <li><strong>Overridden standard actions</strong> - These are actions that override
      * standard actions to customize them. For example, an app may add a label to the
      * standard {@link #ACTION_CLICK} action to indicate to the user that this action clears
      * browsing history.
@@ -5024,12 +5159,17 @@
      * {@link View#onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo)} and are performed
      * within {@link View#performAccessibilityAction(int, Bundle)}.
      * </p>
-     * <p class="note">
-     * <strong>Note:</strong> Views which support these actions should invoke
+     * <aside class="note">
+     * <b>Note:</b> Views which support these actions should invoke
      * {@link View#setImportantForAccessibility(int)} with
      * {@link View#IMPORTANT_FOR_ACCESSIBILITY_YES} to ensure an {@link AccessibilityService}
      * can discover the set of supported actions.
      * </p>
+     * <aside class="note">
+     * <b>Note:</b> Use {@link androidx.core.view.ViewCompat#addAccessibilityAction(View,
+     * AccessibilityNodeInfoCompat.AccessibilityActionCompat)} to register an action directly on the
+     * view.
+     * </p>
      */
     public static final class AccessibilityAction implements Parcelable {
 
@@ -5038,51 +5178,94 @@
 
         /**
          * Action that gives input focus to the node.
+         * <p>The focus request send an event of {@link AccessibilityEvent#TYPE_VIEW_FOCUSED}
+         * if successful. In the View system, this is handled by {@link View#requestFocus}.
+         *
+         * <p>The node that is focused should return {@code true} for
+         * {@link AccessibilityNodeInfo#isFocused()}.
+         *
+         * @see #ACTION_ACCESSIBILITY_FOCUS for the difference between system and accessibility
+         * focus.
          */
         public static final AccessibilityAction ACTION_FOCUS =
                 new AccessibilityAction(AccessibilityNodeInfo.ACTION_FOCUS);
 
         /**
          * Action that clears input focus of the node.
+         * <p>The node that is cleared should return {@code false} for
+         * {@link AccessibilityNodeInfo#isFocused)}.
          */
         public static final AccessibilityAction ACTION_CLEAR_FOCUS =
                 new AccessibilityAction(AccessibilityNodeInfo.ACTION_CLEAR_FOCUS);
 
         /**
          *  Action that selects the node.
+         *  The view the implements this should send a
+         *  {@link AccessibilityEvent#TYPE_VIEW_SELECTED} event.
+         * @see AccessibilityAction#ACTION_CLEAR_SELECTION
          */
         public static final AccessibilityAction ACTION_SELECT =
                 new AccessibilityAction(AccessibilityNodeInfo.ACTION_SELECT);
 
         /**
          * Action that deselects the node.
+         * @see AccessibilityAction#ACTION_SELECT
          */
         public static final AccessibilityAction ACTION_CLEAR_SELECTION =
                 new AccessibilityAction(AccessibilityNodeInfo.ACTION_CLEAR_SELECTION);
 
         /**
          * Action that clicks on the node info.
+         *
+         * <p>The UI element that implements this should send a
+         * {@link AccessibilityEvent#TYPE_VIEW_CLICKED} event. In the View system,
+         * the default handling of this action when performed by a service is to call
+         * {@link View#performClick()}, and setting a
+         * {@link View#setOnClickListener(View.OnClickListener)} automatically adds this action.
+         *
+         * <p>{@link #isClickable()} should return true if this action is available.
          */
         public static final AccessibilityAction ACTION_CLICK =
                 new AccessibilityAction(AccessibilityNodeInfo.ACTION_CLICK);
 
         /**
          * Action that long clicks on the node.
+         *
+         * <p>The UI element that implements this should send a
+         * {@link AccessibilityEvent#TYPE_VIEW_LONG_CLICKED} event. In the View system,
+         * the default handling of this action when performed by a service is to call
+         * {@link View#performLongClick()}, and setting a
+         * {@link View#setOnLongClickListener(View.OnLongClickListener)} automatically adds this
+         * action.
+         *
+         * <p>{@link #isLongClickable()} should return true if this action is available.
          */
         public static final AccessibilityAction ACTION_LONG_CLICK =
                 new AccessibilityAction(AccessibilityNodeInfo.ACTION_LONG_CLICK);
 
         /**
          * Action that gives accessibility focus to the node.
-         * <p>
-         * This is intended to be used by screen readers. Apps changing focus can confuse screen
-         * readers, so the resulting behavior can vary by device and screen reader version.
+         *
+         * <p>The UI element that implements this should send a
+         * {@link AccessibilityEvent#TYPE_VIEW_ACCESSIBILITY_FOCUSED} event
+         * if successful. The node that is focused should return {@code true} for
+         * {@link AccessibilityNodeInfo#isAccessibilityFocused()}.
+         *
+         * <p>This is intended to be used by screen readers to assist with user navigation. Apps
+         * changing focus can confuse screen readers, so the resulting behavior can vary by device
+         * and screen reader version.
+         * <p>This is distinct from {@link #ACTION_FOCUS}, which refers to system focus. System
+         * focus is typically used to convey targets for keyboard navigation.
          */
         public static final AccessibilityAction ACTION_ACCESSIBILITY_FOCUS =
                 new AccessibilityAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
 
         /**
          * Action that clears accessibility focus of the node.
+         * <p>The UI element that implements this should send a
+         * {@link AccessibilityEvent#TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED} event if successful. The
+         * node that is cleared should return {@code false} for
+         * {@link AccessibilityNodeInfo#isAccessibilityFocused()}.
          */
         public static final AccessibilityAction ACTION_CLEAR_ACCESSIBILITY_FOCUS =
                 new AccessibilityAction(AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS);
@@ -5218,12 +5401,105 @@
 
         /**
          * Action to scroll the node content forward.
+         *
+         * <p>
+         *     <strong>Arguments:</strong>
+         *     {@link #ACTION_ARGUMENT_SCROLL_AMOUNT_FLOAT}. This is an optional argument.
+         * </p>
+         * <p>The UI element that implements this should send a
+         * {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} event. Depending on the orientation,
+         * this element should also add the relevant directional scroll actions of
+         * {@link #ACTION_SCROLL_LEFT}, {@link #ACTION_SCROLL_RIGHT},
+         * {@link #ACTION_SCROLL_UP}, and {@link #ACTION_SCROLL_DOWN}. If the scrolling brings
+         * the next or previous element into view as the center element, such as in a ViewPager2,
+         * use {@link #ACTION_PAGE_DOWN} and the other page actions instead of the directional
+         * actions.
+         * <p>Example: a scrolling UI of vertical orientation with a forward
+         * scroll action should also add the scroll down action:
+         * <pre class="prettyprint"><code>
+         *     onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+         *          super.onInitializeAccessibilityNodeInfo(info);
+         *          if (canScrollForward) {
+         *              info.addAction(ACTION_SCROLL_FORWARD);
+         *              info.addAction(ACTION_SCROLL_DOWN);
+         *          }
+         *     }
+         *     performAccessibilityAction(int action, Bundle bundle) {
+         *          if (action == ACTION_SCROLL_FORWARD || action == ACTION_SCROLL_DOWN) {
+         *              scrollForward();
+         *          }
+         *     }
+         *     scrollForward() {
+         *         ...
+         *         if (mAccessibilityManager.isEnabled()) {
+         *             event = new AccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SCROLLED);
+         *             event.setScrollDeltaX(dx);
+         *             event.setScrollDeltaY(dy);
+         *             event.setMaxScrollX(maxDx);
+         *             event.setMaxScrollY(maxDY);
+         *             sendAccessibilityEventUnchecked(event);
+         *        }
+         *     }
+         *      </code>
+         * </pre></p>
          */
         public static final AccessibilityAction ACTION_SCROLL_FORWARD =
                 new AccessibilityAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
 
         /**
          * Action to scroll the node content backward.
+         * <p>
+         *     <strong>Arguments:</strong>
+         *     {@link #ACTION_ARGUMENT_SCROLL_AMOUNT_FLOAT}. This is an optional argument.
+         * </p>
+         * <p>The UI element that implements this should send a
+         * {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} event. Depending on the orientation,
+         * this element should also add the relevant directional scroll actions of
+         * {@link #ACTION_SCROLL_LEFT}, {@link #ACTION_SCROLL_RIGHT},
+         * {@link #ACTION_SCROLL_UP}, and {@link #ACTION_SCROLL_DOWN}. If the scrolling brings
+         * the next or previous element into view as the center element, such as in a ViewPager2,
+         * use {@link #ACTION_PAGE_DOWN} and the other page actions instead of the directional
+         * actions.
+         * <p> Example: a scrolling UI of horizontal orientation with a backward
+         * scroll action should also add the scroll left/right action (LTR/RTL):
+         * <pre class="prettyprint"><code>
+         *     onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+         *          super.onInitializeAccessibilityNodeInfo(info);
+         *          if (canScrollBackward) {
+         *              info.addAction(ACTION_SCROLL_FORWARD);
+         *              if (leftToRight) {
+         *                  info.addAction(ACTION_SCROLL_LEFT);
+         *              } else {
+         *                  info.addAction(ACTION_SCROLL_RIGHT);
+         *              }
+         *          }
+         *     }
+         *     performAccessibilityAction(int action, Bundle bundle) {
+         *          if (action == ACTION_SCROLL_BACKWARD) {
+         *              scrollBackward();
+         *          } else if (action == ACTION_SCROLL_LEFT) {
+         *              if (!isRTL()){
+         *                  scrollBackward();
+         *              }
+         *          } else if (action == ACTION_SCROLL_RIGHT) {
+         *              if (isRTL()){
+         *                  scrollBackward();
+         *              }
+         *          }
+         *     }
+         *     scrollBackward() {
+         *         ...
+         *         if (mAccessibilityManager.isEnabled()) {
+         *             event = new AccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SCROLLED);
+         *             event.setScrollDeltaX(dx);
+         *             event.setScrollDeltaY(dy);
+         *             event.setMaxScrollX(maxDx);
+         *             event.setMaxScrollY(maxDY);
+         *             sendAccessibilityEventUnchecked(event);
+         *        }
+         *     }
+         *      </code>
+         * </pre></p>
          */
         public static final AccessibilityAction ACTION_SCROLL_BACKWARD =
                 new AccessibilityAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
@@ -5263,7 +5539,10 @@
          *   info.performAction(AccessibilityAction.ACTION_SET_SELECTION.getId(), arguments);
          * </code></pre></p>
          * </p>
-         *
+         * <p> If this is a text selection, the UI element that implements this should send a
+         * {@link AccessibilityEvent#TYPE_VIEW_TEXT_SELECTION_CHANGED} event if its selection is
+         * updated. This element should also return {@code true} for
+         * {@link AccessibilityNodeInfo#isTextSelectable()}.
          * @see AccessibilityNodeInfo#ACTION_ARGUMENT_SELECTION_START_INT
          *  AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT
          * @see AccessibilityNodeInfo#ACTION_ARGUMENT_SELECTION_END_INT
@@ -5305,6 +5584,10 @@
          *       "android");
          *   info.performAction(AccessibilityAction.ACTION_SET_TEXT.getId(), arguments);
          * </code></pre></p>
+         * <p> The UI element that implements this should send a
+         * {@link AccessibilityEvent#TYPE_VIEW_TEXT_CHANGED} event if its text is updated.
+         * This element should also return {@code true} for
+         * {@link AccessibilityNodeInfo#isEditable()}.
          */
         public static final AccessibilityAction ACTION_SET_TEXT =
                 new AccessibilityAction(AccessibilityNodeInfo.ACTION_SET_TEXT);
@@ -5312,6 +5595,8 @@
         /**
          * Action that requests the node make its bounding rectangle visible
          * on the screen, scrolling if necessary just enough.
+         * <p>The UI element that implements this should send a
+         * {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} event.
          *
          * @see View#requestRectangleOnScreen(Rect)
          */
@@ -5327,6 +5612,8 @@
          *     <li>{@link AccessibilityNodeInfo#ACTION_ARGUMENT_ROW_INT}</li>
          *     <li>{@link AccessibilityNodeInfo#ACTION_ARGUMENT_COLUMN_INT}</li>
          * <ul>
+         * <p>The UI element that implements this should send a
+         * {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} event.
          *
          * @see AccessibilityNodeInfo#getCollectionInfo()
          */
@@ -5361,54 +5648,98 @@
 
         /**
          * Action to scroll the node content up.
+         * <p>
+         *     <strong>Arguments:</strong>
+         *     {@link #ACTION_ARGUMENT_SCROLL_AMOUNT_FLOAT}. This is an optional argument.
+         * </p>
+         * <p>The UI element that implements this should send a
+         * {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} event.
          */
         public static final AccessibilityAction ACTION_SCROLL_UP =
                 new AccessibilityAction(R.id.accessibilityActionScrollUp);
 
         /**
          * Action to scroll the node content left.
+         * <p>
+         *     <strong>Arguments:</strong>
+         *     {@link #ACTION_ARGUMENT_SCROLL_AMOUNT_FLOAT}. This is an optional argument.
+         * </p>
+         * <p>The UI element that implements this should send a
+         * {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} event.
          */
         public static final AccessibilityAction ACTION_SCROLL_LEFT =
                 new AccessibilityAction(R.id.accessibilityActionScrollLeft);
 
         /**
          * Action to scroll the node content down.
+         * <p>
+         *     <strong>Arguments:</strong>
+         *     {@link #ACTION_ARGUMENT_SCROLL_AMOUNT_FLOAT}. This is an optional argument.
+         * </p>
+         * <p>The UI element that implements this should send a
+         * {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} event.
          */
         public static final AccessibilityAction ACTION_SCROLL_DOWN =
                 new AccessibilityAction(R.id.accessibilityActionScrollDown);
 
         /**
          * Action to scroll the node content right.
+         * <p>
+         *     <strong>Arguments:</strong>
+         *     {@link #ACTION_ARGUMENT_SCROLL_AMOUNT_FLOAT}. This is an optional argument.
+         * </p>
+         * <p>The UI element that implements this should send a
+         * {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} event.
          */
         public static final AccessibilityAction ACTION_SCROLL_RIGHT =
                 new AccessibilityAction(R.id.accessibilityActionScrollRight);
 
         /**
          * Action to move to the page above.
+         * <p>The UI element that implements this should send a
+         * {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} event.
          */
         public static final AccessibilityAction ACTION_PAGE_UP =
                 new AccessibilityAction(R.id.accessibilityActionPageUp);
 
         /**
          * Action to move to the page below.
+         * <p>The UI element that implements this should send a
+         * {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} event.
          */
         public static final AccessibilityAction ACTION_PAGE_DOWN =
                 new AccessibilityAction(R.id.accessibilityActionPageDown);
 
         /**
          * Action to move to the page left.
+         * <p>The UI element that implements this should send a
+         * {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} event.
          */
         public static final AccessibilityAction ACTION_PAGE_LEFT =
                 new AccessibilityAction(R.id.accessibilityActionPageLeft);
 
         /**
          * Action to move to the page right.
+         * <p>The UI element that implements this should send a
+         * {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} event.
          */
         public static final AccessibilityAction ACTION_PAGE_RIGHT =
                 new AccessibilityAction(R.id.accessibilityActionPageRight);
 
         /**
          * Action that context clicks the node.
+         *
+         * <p>The UI element that implements this should send a
+         * {@link AccessibilityEvent#TYPE_VIEW_CONTEXT_CLICKED} event. In the View system,
+         * the default handling of this action when performed by a service is to call
+         * {@link View#performContextClick()}, and setting a
+         * {@link View#setOnContextClickListener(View.OnContextClickListener)} automatically adds
+         * this action.
+         *
+         * <p>A context click usually occurs from a mouse pointer right-click or a stylus button
+         * press.
+         *
+         * <p>{@link #isContextClickable()} should return true if this action is available.
          */
         public static final AccessibilityAction ACTION_CONTEXT_CLICK =
                 new AccessibilityAction(R.id.accessibilityActionContextClick);
@@ -5495,8 +5826,9 @@
          * This action initiates a drag & drop within the system. The source's dragged content is
          * prepared before the drag begins. In View, this action should prepare the arguments to
          * {@link View#startDragAndDrop(ClipData, View.DragShadowBuilder, Object, int)} and then
-         * call {@link View#startDragAndDrop(ClipData, View.DragShadowBuilder, Object, int)}. The
-         * equivalent should be performed for other UI toolkits.
+         * call {@link View#startDragAndDrop(ClipData, View.DragShadowBuilder, Object, int)} with
+         * {@link View#DRAG_FLAG_ACCESSIBILITY_ACTION}. The equivalent should be performed for other
+         * UI toolkits.
          * </p>
          *
          * @see AccessibilityEvent#CONTENT_CHANGE_TYPE_DRAG_STARTED
@@ -5510,7 +5842,8 @@
          * This action is added to potential drop targets if the source started a drag with
          * {@link #ACTION_DRAG_START}. In View, these targets are Views that accepted
          * {@link android.view.DragEvent#ACTION_DRAG_STARTED} and have an
-         * {@link View.OnDragListener}.
+         * {@link View.OnDragListener}, and the drop occurs at the center location of the View's
+         * window bounds.
          * </p>
          *
          * @see AccessibilityEvent#CONTENT_CHANGE_TYPE_DRAG_DROPPED
@@ -5782,10 +6115,20 @@
         /** Selection mode where multiple items may be selected. */
         public static final int SELECTION_MODE_MULTIPLE = 2;
 
+        /**
+         * Constant to denote a missing collection count.
+         *
+         * This should be used for {@code mItemCount} and
+         * {@code mImportantForAccessibilityItemCount} when values for those fields are not known.
+         */
+        public static final int UNDEFINED = -1;
+
         private int mRowCount;
         private int mColumnCount;
         private boolean mHierarchical;
         private int mSelectionMode;
+        private int mItemCount;
+        private int mImportantForAccessibilityItemCount;
 
         /**
          * Instantiates a CollectionInfo that is a clone of another one.
@@ -5799,7 +6142,8 @@
          */
         public static CollectionInfo obtain(CollectionInfo other) {
             return new CollectionInfo(other.mRowCount, other.mColumnCount, other.mHierarchical,
-                    other.mSelectionMode);
+                    other.mSelectionMode, other.mItemCount,
+                    other.mImportantForAccessibilityItemCount);
         }
 
         /**
@@ -5867,6 +6211,36 @@
             mColumnCount = columnCount;
             mHierarchical = hierarchical;
             mSelectionMode = selectionMode;
+            mItemCount = UNDEFINED;
+            mImportantForAccessibilityItemCount = UNDEFINED;
+        }
+
+        /**
+         * Creates a new instance.
+         *
+         * @param rowCount The number of rows.
+         * @param columnCount The number of columns.
+         * @param hierarchical Whether the collection is hierarchical.
+         * @param selectionMode The collection's selection mode.
+         * @param itemCount The collection's item count, which includes items that are unimportant
+         *                  for accessibility. When ViewGroups map cleanly to both row and column
+         *                  semantics, clients should populate the row and column counts and
+         *                  optionally populate this field. In all other cases, clients should
+         *                  populate this field so that accessibility services can use it to relay
+         *                  the collection size to users. This should be set to {@code UNDEFINED} if
+         *                  the item count is not known.
+         * @param importantForAccessibilityItemCount The count of the collection's views considered
+         *                                           important for accessibility.
+         */
+        @Hide
+        public CollectionInfo(int rowCount, int columnCount, boolean hierarchical,
+                int selectionMode, int itemCount, int importantForAccessibilityItemCount) {
+            mRowCount = rowCount;
+            mColumnCount = columnCount;
+            mHierarchical = hierarchical;
+            mSelectionMode = selectionMode;
+            mItemCount = itemCount;
+            mImportantForAccessibilityItemCount = importantForAccessibilityItemCount;
         }
 
         /**
@@ -5911,6 +6285,25 @@
         }
 
         /**
+         * Gets the number of items in the collection.
+         *
+         * @return The count of items, which may be {@code UNDEFINED} if the count is not known.
+         */
+        public int getItemCount() {
+            return mItemCount;
+        }
+
+        /**
+         * Gets the number of items in the collection considered important for accessibility.
+         *
+         * @return The count of items important for accessibility, which may be {@code UNDEFINED}
+         * if the count is not known.
+         */
+        public int getImportantForAccessibilityItemCount() {
+            return mImportantForAccessibilityItemCount;
+        }
+
+        /**
          * Previously would recycle this instance.
          *
          * @deprecated Object pooling has been discontinued. Calling this function now will have
@@ -5924,6 +6317,111 @@
             mColumnCount = 0;
             mHierarchical = false;
             mSelectionMode = SELECTION_MODE_NONE;
+            mItemCount = UNDEFINED;
+            mImportantForAccessibilityItemCount = UNDEFINED;
+        }
+
+        /**
+         * The builder for CollectionInfo.
+         */
+
+        public static final class Builder {
+            private int mRowCount = 0;
+            private int mColumnCount = 0;
+            private boolean mHierarchical = false;
+            private int mSelectionMode;
+            private int mItemCount = UNDEFINED;
+            private int mImportantForAccessibilityItemCount = UNDEFINED;
+
+            /**
+             * Creates a new Builder.
+             */
+            public Builder() {
+            }
+
+            /**
+             * Sets the row count.
+             * @param rowCount The number of rows in the collection.
+             * @return This builder.
+             */
+            @NonNull
+            public CollectionInfo.Builder setRowCount(int rowCount) {
+                mRowCount = rowCount;
+                return this;
+            }
+
+            /**
+             * Sets the column count.
+             * @param columnCount The number of columns in the collection.
+             * @return This builder.
+             */
+            @NonNull
+            public CollectionInfo.Builder setColumnCount(int columnCount) {
+                mColumnCount = columnCount;
+                return this;
+            }
+            /**
+             * Sets whether the collection is hierarchical.
+             * @param hierarchical Whether the collection is hierarchical.
+             * @return This builder.
+             */
+            @NonNull
+            public CollectionInfo.Builder setHierarchical(boolean hierarchical) {
+                mHierarchical = hierarchical;
+                return this;
+            }
+
+            /**
+             * Sets the selection mode.
+             * @param selectionMode The selection mode.
+             * @return This builder.
+             */
+            @NonNull
+            public CollectionInfo.Builder setSelectionMode(int selectionMode) {
+                mSelectionMode = selectionMode;
+                return this;
+            }
+
+            /**
+             * Sets the number of items in the collection. Can be optionally set for ViewGroups with
+             * clear row and column semantics; should be set for all other clients.
+             *
+             * @param itemCount The number of items in the collection. This should be set to
+             *                  {@code UNDEFINED} if the item count is not known.
+             * @return This builder.
+             */
+            @NonNull
+            public CollectionInfo.Builder setItemCount(int itemCount) {
+                mItemCount = itemCount;
+                return this;
+            }
+
+            /**
+             * Sets the number of views considered important for accessibility.
+             * @param importantForAccessibilityItemCount The number of items important for
+             *                                            accessibility.
+             * @return This builder.
+             */
+            @NonNull
+            public CollectionInfo.Builder setImportantForAccessibilityItemCount(
+                    int importantForAccessibilityItemCount) {
+                mImportantForAccessibilityItemCount = importantForAccessibilityItemCount;
+                return this;
+            }
+
+            /**
+             * Creates a new {@link CollectionInfo} instance.
+             */
+            @NonNull
+            public CollectionInfo build() {
+                CollectionInfo collectionInfo = new CollectionInfo(mRowCount, mColumnCount,
+                        mHierarchical);
+                collectionInfo.mSelectionMode = mSelectionMode;
+                collectionInfo.mItemCount = mItemCount;
+                collectionInfo.mImportantForAccessibilityItemCount =
+                        mImportantForAccessibilityItemCount;
+                return collectionInfo;
+            }
         }
     }
 
@@ -6466,7 +6964,7 @@
         /**
          * @see android.os.Parcelable.Creator
          */
-        public static final @android.annotation.NonNull Parcelable.Creator<TouchDelegateInfo> CREATOR =
+        public static final @NonNull Parcelable.Creator<TouchDelegateInfo> CREATOR =
                 new Parcelable.Creator<TouchDelegateInfo>() {
             @Override
             public TouchDelegateInfo createFromParcel(Parcel parcel) {
@@ -6638,7 +7136,7 @@
     /**
      * @see android.os.Parcelable.Creator
      */
-    public static final @android.annotation.NonNull Parcelable.Creator<AccessibilityNodeInfo> CREATOR =
+    public static final @NonNull Parcelable.Creator<AccessibilityNodeInfo> CREATOR =
             new Parcelable.Creator<AccessibilityNodeInfo>() {
         @Override
         public AccessibilityNodeInfo createFromParcel(Parcel parcel) {
diff --git a/core/java/android/view/accessibility/AccessibilityNodeProvider.java b/core/java/android/view/accessibility/AccessibilityNodeProvider.java
index 91d4a35..1868a43 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeProvider.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeProvider.java
@@ -45,6 +45,10 @@
  * View itself. Similarly the returned instance is responsible for performing accessibility
  * actions on any virtual view or the root view itself. For example:
  * </p>
+ * <aside class="note">
+ * <b>Note:</b> Consider using a {@link androidx.customview.widget.ExploreByTouchHelper}, a utility
+ * extension of AccessibilityNodeProvider, to simplify many aspects of providing information to
+ * accessibility services and managing accessibility focus. </aside>
  * <div>
  * <div class="ds-selector-tabs"><section><h3 id="kotlin">Kotlin</h3>
  * <pre class="prettyprint lang-kotlin">
diff --git a/core/java/android/view/accessibility/AccessibilityRecord.java b/core/java/android/view/accessibility/AccessibilityRecord.java
index d69c781..7f8926d 100644
--- a/core/java/android/view/accessibility/AccessibilityRecord.java
+++ b/core/java/android/view/accessibility/AccessibilityRecord.java
@@ -66,13 +66,13 @@
 
     private static final int UNDEFINED = -1;
 
-    private static final int PROPERTY_CHECKED = 0x00000001;
-    private static final int PROPERTY_ENABLED = 0x00000002;
-    private static final int PROPERTY_PASSWORD = 0x00000004;
-    private static final int PROPERTY_FULL_SCREEN = 0x00000080;
-    private static final int PROPERTY_SCROLLABLE = 0x00000100;
-    private static final int PROPERTY_IMPORTANT_FOR_ACCESSIBILITY = 0x00000200;
-    private static final int PROPERTY_ACCESSIBILITY_DATA_SENSITIVE = 0x00000400;
+    private static final int PROPERTY_CHECKED = 1 /* << 0 */;
+    private static final int PROPERTY_ENABLED = 1 << 1;
+    private static final int PROPERTY_PASSWORD = 1 << 2;
+    private static final int PROPERTY_FULL_SCREEN = 1 << 7;
+    private static final int PROPERTY_SCROLLABLE = 1 << 8;
+    private static final int PROPERTY_IMPORTANT_FOR_ACCESSIBILITY = 1 << 9;
+    private static final int PROPERTY_ACCESSIBILITY_DATA_SENSITIVE = 1 << 10;
 
     private static final int GET_SOURCE_PREFETCH_FLAGS =
             AccessibilityNodeInfo.FLAG_PREFETCH_ANCESTORS
@@ -198,8 +198,7 @@
      *
      * @see AccessibilityNodeInfo#getParent(int) for a description of prefetching.
      */
-    @Nullable
-    public AccessibilityNodeInfo getSource(
+    public @Nullable AccessibilityNodeInfo getSource(
             @AccessibilityNodeInfo.PrefetchingStrategy int prefetchingStrategy) {
         enforceSealed();
         if ((mConnectionId == UNDEFINED)
diff --git a/core/java/android/view/accessibility/AccessibilityWindowInfo.java b/core/java/android/view/accessibility/AccessibilityWindowInfo.java
index fa0052c..09b2f9c 100644
--- a/core/java/android/view/accessibility/AccessibilityWindowInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityWindowInfo.java
@@ -99,6 +99,7 @@
     /** @hide */
     public static final int UNDEFINED_CONNECTION_ID = -1;
     /** @hide */
+    @TestApi
     public static final int UNDEFINED_WINDOW_ID = -1;
     /** @hide */
     public static final int ANY_WINDOW_ID = -2;
diff --git a/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl b/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl
index 0eeba7c..a43acf9 100644
--- a/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl
@@ -66,5 +66,6 @@
     void takeScreenshotOfWindow(int interactionId,
         in ScreenCapture.ScreenCaptureListener listener,
         IAccessibilityInteractionConnectionCallback callback);
-    void attachAccessibilityOverlayToWindow(in SurfaceControl sc);
+
+    void attachAccessibilityOverlayToWindow(in SurfaceControl sc, int interactionId, in IAccessibilityInteractionConnectionCallback callback);
 }
diff --git a/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl b/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl
index 456bf58..fe59519 100644
--- a/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl
@@ -68,4 +68,9 @@
     * Sends an error code for a window screenshot request to the requesting client.
     */
     void sendTakeScreenshotOfWindowError(int errorCode, int interactionId);
+
+    /**
+    * Sends an result code for an attach overlay request to the requesting client.
+    */
+    void sendAttachOverlayResult(int result, int interactionId);
 }
diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl
index 390503b..7a1112f 100644
--- a/core/java/android/view/accessibility/IAccessibilityManager.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl
@@ -21,6 +21,7 @@
 import android.accessibilityservice.IAccessibilityServiceConnection;
 import android.accessibilityservice.IAccessibilityServiceClient;
 import android.content.ComponentName;
+import android.content.pm.ParceledListSlice;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.accessibility.IAccessibilityInteractionConnection;
@@ -47,7 +48,7 @@
 
     boolean removeClient(IAccessibilityManagerClient client, int userId);
 
-    List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList(int userId);
+    ParceledListSlice<AccessibilityServiceInfo> getInstalledAccessibilityServiceList(int userId);
 
     @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
     List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList(int feedbackType, int userId);
diff --git a/core/java/android/view/accessibility/IWindowMagnificationConnection.aidl b/core/java/android/view/accessibility/IWindowMagnificationConnection.aidl
index 8a30f8c..a11c6d0 100644
--- a/core/java/android/view/accessibility/IWindowMagnificationConnection.aidl
+++ b/core/java/android/view/accessibility/IWindowMagnificationConnection.aidl
@@ -115,4 +115,13 @@
      * @param callback the interface to be called.
      */
     void setConnectionCallback(in IWindowMagnificationConnectionCallback callback);
+
+    /**
+     * Notify System UI the magnification scale on the specified display for userId is changed.
+     *
+     * @param userId the user id.
+     * @param displayId the logical display id.
+     * @param scale magnification scale.
+     */
+    void onUserMagnificationScaleChanged(int userId, int displayId, float scale);
 }
diff --git a/core/java/android/view/accessibility/IWindowMagnificationConnectionCallback.aidl b/core/java/android/view/accessibility/IWindowMagnificationConnectionCallback.aidl
index adfeb6d..21b4334 100644
--- a/core/java/android/view/accessibility/IWindowMagnificationConnectionCallback.aidl
+++ b/core/java/android/view/accessibility/IWindowMagnificationConnectionCallback.aidl
@@ -57,8 +57,9 @@
      *
      * @param displayId The logical display id.
      * @param scale the target scale, or {@link Float#NaN} to leave unchanged
+     * @param updatePersistence whether the new scale should be persisted in Settings
      */
-    void onPerformScaleAction(int displayId, float scale);
+    void onPerformScaleAction(int displayId, float scale, boolean updatePersistence);
 
     /**
      * Called when the accessibility action is performed.
diff --git a/core/java/android/view/accessibility/TEST_MAPPING b/core/java/android/view/accessibility/TEST_MAPPING
index 9b1b677..1c67399 100644
--- a/core/java/android/view/accessibility/TEST_MAPPING
+++ b/core/java/android/view/accessibility/TEST_MAPPING
@@ -1,56 +1,7 @@
 {
-  "presubmit": [
+  "imports": [
     {
-      "name": "CtsAccessibilityServiceTestCases",
-      "options": [
-        {
-          "include-annotation": "android.platform.test.annotations.Presubmit"
-        },
-        {
-          "exclude-annotation": "android.support.test.filters.FlakyTest"
-        }
-      ]
-    },
-    {
-      "name": "CtsUiAutomationTestCases",
-      "options": [
-        {
-          "include-annotation": "android.platform.test.annotations.Presubmit"
-        },
-        {
-          "exclude-annotation": "android.support.test.filters.FlakyTest"
-        }
-      ]
-    },
-    {
-      "name": "FrameworksCoreTests",
-      "options": [
-        {
-          "include-filter": "android.view.accessibility"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        }
-      ]
-    }
-  ],
-  "postsubmit": [
-    {
-      "name": "CtsAccessibilityServiceSdk29TestCases"
-    },
-    {
-      "name": "CtsAccessibilityServiceTestCases"
-    },
-    {
-      "name": "CtsUiAutomationTestCases"
-    },
-    {
-      "name": "FrameworksCoreTests",
-      "options": [
-        {
-          "include-filter": "android.view.accessibility"
-        }
-      ]
+      "path": "frameworks/base/services/accessibility/TEST_MAPPING"
     }
   ]
 }
diff --git a/core/java/android/view/animation/AnimationUtils.java b/core/java/android/view/animation/AnimationUtils.java
index 84ef226..8ba8b8c 100644
--- a/core/java/android/view/animation/AnimationUtils.java
+++ b/core/java/android/view/animation/AnimationUtils.java
@@ -19,6 +19,9 @@
 import android.annotation.AnimRes;
 import android.annotation.InterpolatorRes;
 import android.annotation.TestApi;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledSince;
+import android.compat.annotation.Overridable;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.res.Resources;
@@ -27,6 +30,7 @@
 import android.content.res.XmlResourceParser;
 import android.os.SystemClock;
 import android.util.AttributeSet;
+import android.util.TimeUtils;
 import android.util.Xml;
 import android.view.InflateException;
 
@@ -47,10 +51,23 @@
     private static final int TOGETHER = 0;
     private static final int SEQUENTIALLY = 1;
 
+     /**
+     * For apps targeting {@link Build.VERSION_CODES#VANILLA_ICE_CREAM} and above,
+     * this change ID enables to use expectedPresentationTime instead of the frameTime
+     * for the frame start time .
+     *
+     * @hide
+     */
+    @ChangeId
+    @EnabledSince(targetSdkVersion = android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM)
+    @Overridable
+    public static final long OVERRIDE_ENABLE_EXPECTED_PRSENTATION_TIME = 278730197L;
+
     private static class AnimationState {
         boolean animationClockLocked;
         long currentVsyncTimeMillis;
         long lastReportedTimeMillis;
+        long mExpectedPresentationTimeNanos;
     };
 
     private static ThreadLocal<AnimationState> sAnimationState
@@ -62,7 +79,8 @@
     };
 
     /**
-     * Locks AnimationUtils{@link #currentAnimationTimeMillis()} to a fixed value for the current
+     * Locks AnimationUtils{@link #currentAnimationTimeMillis()} and
+     * AnimationUtils{@link #expectedPresentationTimeNanos()} to a fixed value for the current
      * thread. This is used by {@link android.view.Choreographer} to ensure that all accesses
      * during a vsync update are synchronized to the timestamp of the vsync.
      *
@@ -74,8 +92,9 @@
      * Note that time is not allowed to "rewind" and must perpetually flow forward. So the
      * lock may fail if the time is in the past from a previously returned value, however
      * time will be frozen for the duration of the lock. The clock is a thread-local, so
-     * ensure that {@link #lockAnimationClock(long)}, {@link #unlockAnimationClock()}, and
-     * {@link #currentAnimationTimeMillis()} are all called on the same thread.
+     * ensure that {@link #lockAnimationClock(long)}, {@link #unlockAnimationClock()},
+     * {@link #currentAnimationTimeMillis()}, and {@link #expectedPresentationTimeNanos()}
+     * are all called on the same thread.
      *
      * This is also not reference counted in any way. Any call to {@link #unlockAnimationClock()}
      * will unlock the clock for everyone on the same thread. It is therefore recommended
@@ -83,12 +102,13 @@
      * {@link android.view.Choreographer} instance.
      *
      * @hide
-     * */
+     */
     @TestApi
-    public static void lockAnimationClock(long vsyncMillis) {
+    public static void lockAnimationClock(long vsyncMillis, long expectedPresentationTimeNanos) {
         AnimationState state = sAnimationState.get();
         state.animationClockLocked = true;
         state.currentVsyncTimeMillis = vsyncMillis;
+        state.mExpectedPresentationTimeNanos = expectedPresentationTimeNanos;
     }
 
     /**
@@ -124,6 +144,31 @@
     }
 
     /**
+     * The expected presentation time of a frame in the {@link System#nanoTime()}.
+     * Developers should prefer using this method over {@link #currentAnimationTimeMillis()}
+     * because it offers a more accurate time for the calculating animation progress.
+     *
+     * @return the expected presentation time of a frame in the
+     *         {@link System#nanoTime()} time base.
+     */
+    public static long getExpectedPresentationTimeNanos() {
+        AnimationState state = sAnimationState.get();
+        return state.mExpectedPresentationTimeNanos;
+    }
+
+    /**
+     * The expected presentation time of a frame in the {@link SystemClock#uptimeMillis()}.
+     * Developers should prefer using this method over {@link #currentAnimationTimeMillis()}
+     * because it offers a more accurate time for the calculating animation progress.
+     *
+     * @return the expected presentation time of a frame in the
+     *         {@link SystemClock#uptimeMillis()} time base.
+     */
+    public static long getExpectedPresentationTimeMillis() {
+        return getExpectedPresentationTimeNanos() / TimeUtils.NANOS_PER_MS;
+    }
+
+    /**
      * Loads an {@link Animation} object from a resource
      *
      * @param context Application context used to access resources
diff --git a/core/java/android/view/autofill/AutofillFeatureFlags.java b/core/java/android/view/autofill/AutofillFeatureFlags.java
index 461b78c..956230d 100644
--- a/core/java/android/view/autofill/AutofillFeatureFlags.java
+++ b/core/java/android/view/autofill/AutofillFeatureFlags.java
@@ -100,21 +100,16 @@
 
     // START CREDENTIAL MANAGER FLAGS //
     /**
-     * (deprecated) Indicates whether credential manager tagged views should be ignored from
-     * autofill structures.This flag is further gated by
-     * {@link #DEVICE_CONFIG_AUTOFILL_CREDENTIAL_MANAGER_ENABLED}
-     *
-     * TODO(b/280661772): Remove this flag once API change is allowed
+     * Indicates whether credential manager tagged views should be ignored from autofill structures.
+     * This flag is further gated by {@link #DEVICE_CONFIG_AUTOFILL_CREDENTIAL_MANAGER_ENABLED}
      */
     public static final String DEVICE_CONFIG_AUTOFILL_CREDENTIAL_MANAGER_IGNORE_VIEWS =
             "autofill_credential_manager_ignore_views";
 
     /**
-     * (deprecated) Indicates CredentialManager feature enabled or not.
+     * Indicates CredentialManager feature enabled or not.
      * This is the overall feature flag. Individual behavior of credential manager may be controlled
      * via a different flag, but gated by this flag.
-     *
-     * TODO(b/280661772): Remove this flag once API change is allowed
      */
     public static final String DEVICE_CONFIG_AUTOFILL_CREDENTIAL_MANAGER_ENABLED =
             "autofill_credential_manager_enabled";
@@ -265,6 +260,8 @@
 
 
     // CREDENTIAL MANAGER DEFAULTS
+    // Credential manager is enabled by default so as to allow testing by app developers
+    private static final boolean DEFAULT_CREDENTIAL_MANAGER_ENABLED = true;
     private static final boolean DEFAULT_CREDENTIAL_MANAGER_SUPPRESS_FILL_AND_SAVE_DIALOG = true;
     // END CREDENTIAL MANAGER DEFAULTS
 
@@ -321,12 +318,24 @@
 
     /* starts credman flag getter function */
     /**
+     * Whether the Credential Manager feature is enabled or not
+     *
+     * @hide
+     */
+    public static boolean isCredentialManagerEnabled() {
+        return DeviceConfig.getBoolean(
+                DeviceConfig.NAMESPACE_AUTOFILL,
+                DEVICE_CONFIG_AUTOFILL_CREDENTIAL_MANAGER_ENABLED,
+                DEFAULT_CREDENTIAL_MANAGER_ENABLED);
+    }
+
+    /**
      * Whether credential manager tagged views should not trigger fill dialog requests.
      *
      * @hide
      */
     public static boolean isFillAndSaveDialogDisabledForCredentialManager() {
-        return DeviceConfig.getBoolean(
+        return isCredentialManagerEnabled() && DeviceConfig.getBoolean(
                     DeviceConfig.NAMESPACE_AUTOFILL,
                     DEVICE_CONFIG_AUTOFILL_CREDENTIAL_MANAGER_SUPPRESS_FILL_AND_SAVE_DIALOG,
                     DEFAULT_CREDENTIAL_MANAGER_SUPPRESS_FILL_AND_SAVE_DIALOG);
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 739c1bf..792b38b 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -16,6 +16,7 @@
 
 package android.view.autofill;
 
+import static android.Manifest.permission.PROVIDE_OWN_AUTOFILL_SUGGESTIONS;
 import static android.service.autofill.FillRequest.FLAG_IME_SHOWING;
 import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST;
 import static android.service.autofill.FillRequest.FLAG_PASSWORD_INPUT_TYPE;
@@ -30,10 +31,12 @@
 import static android.view.autofill.Helper.toList;
 
 import android.accessibilityservice.AccessibilityServiceInfo;
+import android.annotation.CallbackExecutor;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresFeature;
+import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.annotation.TestApi;
@@ -50,16 +53,21 @@
 import android.content.pm.ResolveInfo;
 import android.graphics.Rect;
 import android.metrics.LogMaker;
+import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
+import android.os.CancellationSignal;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.ICancellationSignal;
 import android.os.Looper;
 import android.os.Parcelable;
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.service.autofill.AutofillService;
+import android.service.autofill.FillCallback;
 import android.service.autofill.FillEventHistory;
+import android.service.autofill.IFillCallback;
 import android.service.autofill.UserData;
 import android.text.TextUtils;
 import android.util.ArrayMap;
@@ -80,6 +88,7 @@
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.accessibility.AccessibilityNodeProvider;
 import android.view.accessibility.AccessibilityWindowInfo;
+import android.view.inputmethod.InlineSuggestionsRequest;
 import android.view.inputmethod.InputMethodManager;
 import android.widget.CheckBox;
 import android.widget.DatePicker;
@@ -109,6 +118,7 @@
 import java.util.List;
 import java.util.Objects;
 import java.util.Set;
+import java.util.concurrent.Executor;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 import sun.misc.Cleaner;
@@ -178,6 +188,12 @@
  * shows an autofill save UI if the value of savable views have changed. If the user selects the
  * option to Save, the current value of the views is then sent to the autofill service.
  *
+ * <p>There is another choice for the application to provide it's datasets to the Autofill framework
+ * by setting an {@link AutofillRequestCallback} through
+ * {@link #setAutofillRequestCallback(Executor, AutofillRequestCallback)}. The application can use
+ * its callback instead of the default {@link AutofillService}. See
+ * {@link AutofillRequestCallback} for more details.
+ *
  * <h3 id="additional-notes">Additional notes</h3>
  *
  * <p>It is safe to call <code>AutofillManager</code> methods from any thread.
@@ -311,6 +327,7 @@
     /** @hide */ public static final int FLAG_ADD_CLIENT_DEBUG = 0x2;
     /** @hide */ public static final int FLAG_ADD_CLIENT_VERBOSE = 0x4;
     /** @hide */ public static final int FLAG_ADD_CLIENT_ENABLED_FOR_AUGMENTED_AUTOFILL_ONLY = 0x8;
+    /** @hide */ public static final int FLAG_ENABLED_CLIENT_SUGGESTIONS = 0x20;
 
     // NOTE: flag below is used by the session start receiver only, hence it can have values above
     /** @hide */ public static final int RECEIVER_FLAG_SESSION_FOR_AUGMENTED_AUTOFILL_ONLY = 0x1;
@@ -644,7 +661,12 @@
     @GuardedBy("mLock")
     private boolean mEnabledForAugmentedAutofillOnly;
 
-    private boolean mHasCredentialField;
+    @GuardedBy("mLock")
+    @Nullable private AutofillRequestCallback mAutofillRequestCallback;
+    @GuardedBy("mLock")
+    @Nullable private Executor mRequestCallbackExecutor;
+
+    private boolean mScreenHasCredmanField;
 
     /**
      * Indicates whether there is already a field to do a fill request after
@@ -709,6 +731,9 @@
     // Indicates whether called the showAutofillDialog() method.
     private boolean mShowAutofillDialogCalled = false;
 
+    // Cached autofill feature flag
+    private boolean mShouldIgnoreCredentialViews = false;
+
     private final String[] mFillDialogEnabledHints;
 
     // Tracked all views that have appeared, including views that there are no
@@ -1431,7 +1456,7 @@
                 Log.d(TAG, "Ignoring Fill Dialog request since important for credMan:"
                         + view.getAutofillId().toString());
             }
-            mHasCredentialField = true;
+            mScreenHasCredmanField = true;
             return;
         }
         for (int i = 0; i < infos.size(); i++) {
@@ -1459,7 +1484,7 @@
                 Log.d(TAG, "Ignoring Fill Dialog request since important for credMan:"
                         + v.getAutofillId());
             }
-            mHasCredentialField = true;
+            mScreenHasCredmanField = true;
             return;
         }
         notifyViewReadyInner(v.getAutofillId(), v.getAutofillHints());
@@ -1758,7 +1783,7 @@
 
             // Update session when screen has credman field
             if (AutofillFeatureFlags.isFillAndSaveDialogDisabledForCredentialManager()
-                    && mHasCredentialField) {
+                    && mScreenHasCredmanField) {
                 flags |= FLAG_SCREEN_HAS_CREDMAN_FIELD;
                 if (sVerbose) {
                     Log.v(TAG, "updating session with flag screen has credman view");
@@ -2269,6 +2294,11 @@
     }
 
     /** @hide */
+    public boolean shouldIgnoreCredentialViews() {
+        return mShouldIgnoreCredentialViews;
+    }
+
+    /** @hide */
     public void onAuthenticationResult(int authenticationId, Intent data, View focusView) {
         if (!hasAutofillFeature()) {
             return;
@@ -2350,6 +2380,38 @@
         return new AutofillId(parent.getAutofillViewId(), virtualId);
     }
 
+    /**
+     * Sets the client's suggestions callback for autofill.
+     *
+     * @see AutofillRequestCallback
+     *
+     * @param executor specifies the thread upon which the callbacks will be invoked.
+     * @param callback which handles autofill request to provide client's suggestions.
+     */
+    @RequiresPermission(PROVIDE_OWN_AUTOFILL_SUGGESTIONS)
+    public void setAutofillRequestCallback(@NonNull @CallbackExecutor Executor executor,
+            @NonNull AutofillRequestCallback callback) {
+        if (mContext.checkSelfPermission(PROVIDE_OWN_AUTOFILL_SUGGESTIONS)
+                != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("Requires PROVIDE_OWN_AUTOFILL_SUGGESTIONS permission!");
+        }
+
+        synchronized (mLock) {
+            mRequestCallbackExecutor = executor;
+            mAutofillRequestCallback = callback;
+        }
+    }
+
+    /**
+     * clears the client's suggestions callback for autofill.
+     */
+    public void clearAutofillRequestCallback() {
+        synchronized (mLock) {
+            mRequestCallbackExecutor = null;
+            mAutofillRequestCallback = null;
+        }
+    }
+
     @GuardedBy("mLock")
     private void startSessionLocked(@NonNull AutofillId id, @NonNull Rect bounds,
             @NonNull AutofillValue value, int flags) {
@@ -2410,6 +2472,13 @@
                 }
             }
 
+            if (mAutofillRequestCallback != null) {
+                if (sDebug) {
+                    Log.d(TAG, "startSession with the client suggestions provider");
+                }
+                flags |= FLAG_ENABLED_CLIENT_SUGGESTIONS;
+            }
+
             mService.startSession(client.autofillClientGetActivityToken(),
                     mServiceClient.asBinder(), id, bounds, value, mContext.getUserId(),
                     mCallback != null, flags, clientActivity,
@@ -2473,7 +2542,7 @@
         mIsFillRequested.set(false);
         mShowAutofillDialogCalled = false;
         mFillDialogTriggerIds = null;
-        mHasCredentialField = false;
+        mScreenHasCredmanField = false;
         mAllTrackedViews.clear();
         if (resetEnteredIds) {
             mEnteredIds = null;
@@ -2764,6 +2833,28 @@
         }
     }
 
+    private void onFillRequest(InlineSuggestionsRequest request,
+            CancellationSignal cancellationSignal, FillCallback callback) {
+        final AutofillRequestCallback autofillRequestCallback;
+        final Executor executor;
+        synchronized (mLock) {
+            autofillRequestCallback = mAutofillRequestCallback;
+            executor = mRequestCallbackExecutor;
+        }
+        if (autofillRequestCallback != null && executor != null) {
+            final long ident = Binder.clearCallingIdentity();
+            try {
+                executor.execute(() ->
+                        autofillRequestCallback.onFillRequest(
+                                request, cancellationSignal, callback));
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        } else {
+            callback.onSuccess(null);
+        }
+    }
+
     /** @hide */
     public static final int SET_STATE_FLAG_ENABLED = 0x01;
     /** @hide */
@@ -3566,7 +3657,7 @@
         if (!hasFillDialogUiFeature()
                 || mShowAutofillDialogCalled
                 || mFillDialogTriggerIds == null
-                || mHasCredentialField) {
+                || mScreenHasCredmanField) {
             return false;
         }
 
@@ -4316,6 +4407,23 @@
         }
 
         @Override
+        public void requestFillFromClient(int id, InlineSuggestionsRequest request,
+                IFillCallback callback) {
+            final AutofillManager afm = mAfm.get();
+            if (afm != null) {
+                ICancellationSignal transport = CancellationSignal.createTransport();
+                try {
+                    callback.onCancellable(transport);
+                } catch (RemoteException e) {
+                    Slog.w(TAG, "Error requesting a cancellation", e);
+                }
+
+                afm.onFillRequest(request, CancellationSignal.fromTransport(transport),
+                        new FillCallback(callback, id));
+            }
+        }
+
+        @Override
         public void notifyFillDialogTriggerIds(List<AutofillId> ids) {
             final AutofillManager afm = mAfm.get();
             if (afm != null) {
diff --git a/core/java/android/view/autofill/AutofillRequestCallback.java b/core/java/android/view/autofill/AutofillRequestCallback.java
new file mode 100644
index 0000000..e632a58
--- /dev/null
+++ b/core/java/android/view/autofill/AutofillRequestCallback.java
@@ -0,0 +1,72 @@
+/*
+ * 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.
+ */
+
+package android.view.autofill;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.CancellationSignal;
+import android.service.autofill.FillCallback;
+import android.view.inputmethod.InlineSuggestionsRequest;
+
+/**
+ * <p>This class is used to provide some input suggestions to the Autofill framework.
+ *
+ * <P>When the user is requested to input something, Autofill will try to query input suggestions
+ * for the user choosing. If the application want to provide some internal input suggestions,
+ * implements this callback and register via
+ * {@link AutofillManager#setAutofillRequestCallback(java.util.concurrent.Executor,
+ * AutofillRequestCallback)}. Autofill will callback the
+ * {@link #onFillRequest(InlineSuggestionsRequest, CancellationSignal, FillCallback)} to request
+ * input suggestions.
+ *
+ * <P>To make sure the callback to take effect, must register before the autofill session starts.
+ * If the autofill session is started, calls {@link AutofillManager#cancel()} to finish current
+ * session, and then the callback will be used at the next restarted session.
+ *
+ * <P>To create a {@link android.service.autofill.FillResponse}, application should fetch
+ * {@link AutofillId}s from its view structure. Below is an example:
+ * <pre class="prettyprint">
+ * AutofillId usernameId = findViewById(R.id.username).getAutofillId();
+ * AutofillId passwordId = findViewById(R.id.password).getAutofillId();
+ * </pre>
+ * To learn more about creating a {@link android.service.autofill.FillResponse}, read
+ * <a href="/guide/topics/text/autofill-services#fill">Fill out client views</a>.
+ *
+ * <P>To fallback to the default {@link android.service.autofill.AutofillService}, just respond
+ * a null of the {@link android.service.autofill.FillResponse}. And then Autofill will do a fill
+ * request with the default {@link android.service.autofill.AutofillService}. Or clear the callback
+ * from {@link AutofillManager} via {@link AutofillManager#clearAutofillRequestCallback()}. If the
+ * client would like to keep no suggestions for the field, respond with an empty
+ * {@link android.service.autofill.FillResponse} which has no dataset.
+ *
+ * <P>IMPORTANT: This should not be used for displaying anything other than input suggestions, or
+ * the keyboard may choose to block your app from the inline strip.
+ */
+public interface AutofillRequestCallback {
+    /**
+     * Called by the Android system to decide if a screen can be autofilled by the callback.
+     *
+     * @param inlineSuggestionsRequest the {@link InlineSuggestionsRequest request} to handle if
+     *     currently inline suggestions are supported and can be displayed.
+     * @param cancellationSignal signal for observing cancellation requests. The system will use
+     *     this to notify you that the fill result is no longer needed and you should stop
+     *     handling this fill request in order to save resources.
+     * @param callback object used to notify the result of the request.
+     */
+    void onFillRequest(@Nullable InlineSuggestionsRequest inlineSuggestionsRequest,
+            @NonNull CancellationSignal cancellationSignal, @NonNull FillCallback callback);
+}
diff --git a/core/java/android/view/autofill/IAutoFillManagerClient.aidl b/core/java/android/view/autofill/IAutoFillManagerClient.aidl
index 51afe4c..2e5967c 100644
--- a/core/java/android/view/autofill/IAutoFillManagerClient.aidl
+++ b/core/java/android/view/autofill/IAutoFillManagerClient.aidl
@@ -24,9 +24,11 @@
 import android.content.IntentSender;
 import android.graphics.Rect;
 import android.os.IBinder;
+import android.service.autofill.IFillCallback;
 import android.view.autofill.AutofillId;
 import android.view.autofill.AutofillValue;
 import android.view.autofill.IAutofillWindowPresenter;
+import android.view.inputmethod.InlineSuggestionsRequest;
 import android.view.KeyEvent;
 
 import com.android.internal.os.IResultReceiver;
@@ -142,6 +144,12 @@
    void requestShowSoftInput(in AutofillId id);
 
     /**
+     * Requests to determine if a screen can be autofilled by the client app.
+     */
+    void requestFillFromClient(int id, in InlineSuggestionsRequest request,
+            in IFillCallback callback);
+
+    /**
      * Notifies autofill ids that require to show the fill dialog.
      */
     void notifyFillDialogTriggerIds(in List<AutofillId> ids);
diff --git a/core/java/android/view/autofill/OWNERS b/core/java/android/view/autofill/OWNERS
index 622b0e2..37c6f5b 100644
--- a/core/java/android/view/autofill/OWNERS
+++ b/core/java/android/view/autofill/OWNERS
@@ -4,3 +4,6 @@
 haoranzhang@google.com
 skxu@google.com
 yunicorn@google.com
+
+# Bug component: 543785 = per-file *Augmented*
+per-file *Augmented* = wangqi@google.com
diff --git a/core/java/android/view/contentcapture/ContentCaptureManager.java b/core/java/android/view/contentcapture/ContentCaptureManager.java
index 668351b..2c7d326 100644
--- a/core/java/android/view/contentcapture/ContentCaptureManager.java
+++ b/core/java/android/view/contentcapture/ContentCaptureManager.java
@@ -52,6 +52,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.RingBuffer;
 import com.android.internal.util.SyncResultReceiver;
 
 import java.io.PrintWriter;
@@ -352,6 +353,30 @@
     public static final String DEVICE_CONFIG_PROPERTY_DISABLE_FLUSH_FOR_VIEW_TREE_APPEARING =
             "disable_flush_for_view_tree_appearing";
 
+    /**
+     * Enables the content protection receiver.
+     *
+     * @hide
+     */
+    public static final String DEVICE_CONFIG_PROPERTY_ENABLE_CONTENT_PROTECTION_RECEIVER =
+            "enable_content_protection_receiver";
+
+    /**
+     * Sets the size of the app blocklist for the content protection flow.
+     *
+     * @hide
+     */
+    public static final String DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_APPS_BLOCKLIST_SIZE =
+            "content_protection_apps_blocklist_size";
+
+    /**
+     * Sets the size of the in-memory ring buffer for the content protection flow.
+     *
+     * @hide
+     */
+    public static final String DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_BUFFER_SIZE =
+            "content_protection_buffer_size";
+
     /** @hide */
     @TestApi
     public static final int LOGGING_LEVEL_OFF = 0;
@@ -384,6 +409,14 @@
     public static final int DEFAULT_LOG_HISTORY_SIZE = 10;
     /** @hide */
     public static final boolean DEFAULT_DISABLE_FLUSH_FOR_VIEW_TREE_APPEARING = false;
+    /** @hide */
+    public static final boolean DEFAULT_ENABLE_CONTENT_CAPTURE_RECEIVER = true;
+    /** @hide */
+    public static final boolean DEFAULT_ENABLE_CONTENT_PROTECTION_RECEIVER = false;
+    /** @hide */
+    public static final int DEFAULT_CONTENT_PROTECTION_APPS_BLOCKLIST_SIZE = 5000;
+    /** @hide */
+    public static final int DEFAULT_CONTENT_PROTECTION_BUFFER_SIZE = 150;
 
     private final Object mLock = new Object();
 
@@ -414,6 +447,9 @@
     @Nullable // set on-demand by addDumpable()
     private Dumper mDumpable;
 
+    // Created here in order to live across activity and session changes
+    @Nullable private final RingBuffer<ContentCaptureEvent> mContentProtectionEventBuffer;
+
     /** @hide */
     public interface ContentCaptureClient {
         /**
@@ -424,12 +460,15 @@
     }
 
     /** @hide */
-    static class StrippedContext {
-        final String mPackageName;
-        final String mContext;
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public static class StrippedContext {
+        @NonNull final String mPackageName;
+        @NonNull final String mContext;
         final @UserIdInt int mUserId;
 
-        private StrippedContext(Context context) {
+        /** @hide */
+        @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+        public StrippedContext(@NonNull Context context) {
             mPackageName = context.getPackageName();
             mContext = context.toString();
             mUserId = context.getUserId();
@@ -440,6 +479,7 @@
             return mContext;
         }
 
+        @NonNull
         public String getPackageName() {
             return mPackageName;
         }
@@ -469,6 +509,16 @@
         mHandler = Handler.createAsync(Looper.getMainLooper());
 
         mDataShareAdapterResourceManager = new LocalDataShareAdapterResourceManager();
+
+        if (mOptions.contentProtectionOptions.enableReceiver
+                && mOptions.contentProtectionOptions.bufferSize > 0) {
+            mContentProtectionEventBuffer =
+                    new RingBuffer(
+                            ContentCaptureEvent.class,
+                            mOptions.contentProtectionOptions.bufferSize);
+        } else {
+            mContentProtectionEventBuffer = null;
+        }
     }
 
     /**
@@ -837,6 +887,13 @@
         activity.addDumpable(mDumpable);
     }
 
+    /** @hide */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    @Nullable
+    public RingBuffer<ContentCaptureEvent> getContentProtectionEventBuffer() {
+        return mContentProtectionEventBuffer;
+    }
+
     // NOTE: ContentCaptureManager cannot implement it directly as it would be exposed as public API
     private final class Dumper implements Dumpable {
         @Override
diff --git a/core/java/android/view/contentcapture/ContentCaptureSession.java b/core/java/android/view/contentcapture/ContentCaptureSession.java
index 62044aa..dc3d323 100644
--- a/core/java/android/view/contentcapture/ContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/ContentCaptureSession.java
@@ -191,20 +191,22 @@
     static final long NOTIFY_NODES_DISAPPEAR_NOW_SENDS_TREE_EVENTS = 258825825L;
 
     /** @hide */
-    @IntDef(prefix = { "FLUSH_REASON_" }, value = {
-            FLUSH_REASON_FULL,
-            FLUSH_REASON_VIEW_ROOT_ENTERED,
-            FLUSH_REASON_SESSION_STARTED,
-            FLUSH_REASON_SESSION_FINISHED,
-            FLUSH_REASON_IDLE_TIMEOUT,
-            FLUSH_REASON_TEXT_CHANGE_TIMEOUT,
-            FLUSH_REASON_SESSION_CONNECTED,
-            FLUSH_REASON_FORCE_FLUSH,
-            FLUSH_REASON_VIEW_TREE_APPEARING,
-            FLUSH_REASON_VIEW_TREE_APPEARED
-    })
+    @IntDef(
+            prefix = {"FLUSH_REASON_"},
+            value = {
+                FLUSH_REASON_FULL,
+                FLUSH_REASON_VIEW_ROOT_ENTERED,
+                FLUSH_REASON_SESSION_STARTED,
+                FLUSH_REASON_SESSION_FINISHED,
+                FLUSH_REASON_IDLE_TIMEOUT,
+                FLUSH_REASON_TEXT_CHANGE_TIMEOUT,
+                FLUSH_REASON_SESSION_CONNECTED,
+                FLUSH_REASON_FORCE_FLUSH,
+                FLUSH_REASON_VIEW_TREE_APPEARING,
+                FLUSH_REASON_VIEW_TREE_APPEARED
+            })
     @Retention(RetentionPolicy.SOURCE)
-    public @interface FlushReason{}
+    public @interface FlushReason {}
 
     private final Object mLock = new Object();
 
@@ -686,7 +688,7 @@
             case FLUSH_REASON_VIEW_TREE_APPEARED:
                 return "VIEW_TREE_APPEARED";
             default:
-                return "UNKOWN-" + reason;
+                return "UNKNOWN-" + reason;
         }
     }
 
diff --git a/core/java/android/view/contentcapture/IContentCaptureManager.aidl b/core/java/android/view/contentcapture/IContentCaptureManager.aidl
index a641110..1487997 100644
--- a/core/java/android/view/contentcapture/IContentCaptureManager.aidl
+++ b/core/java/android/view/contentcapture/IContentCaptureManager.aidl
@@ -17,6 +17,7 @@
 package android.view.contentcapture;
 
 import android.content.ComponentName;
+import android.content.pm.ParceledListSlice;
 import android.view.contentcapture.ContentCaptureContext;
 import android.view.contentcapture.ContentCaptureEvent;
 import android.view.contentcapture.DataRemovalRequest;
@@ -108,4 +109,9 @@
      */
     void registerContentCaptureOptionsCallback(String packageName,
                                                in IContentCaptureOptionsCallback callback);
+
+    /**
+     * Notifies the system server that a login was detected.
+     */
+    void onLoginDetected(in ParceledListSlice<ContentCaptureEvent> events);
 }
diff --git a/core/java/android/view/contentcapture/MainContentCaptureSession.java b/core/java/android/view/contentcapture/MainContentCaptureSession.java
index efd50e7..2241fd5 100644
--- a/core/java/android/view/contentcapture/MainContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/MainContentCaptureSession.java
@@ -52,8 +52,10 @@
 import android.util.TimeUtils;
 import android.view.autofill.AutofillId;
 import android.view.contentcapture.ViewNode.ViewStructureImpl;
+import android.view.contentprotection.ContentProtectionEventProcessor;
 import android.view.inputmethod.BaseInputConnection;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.IResultReceiver;
 
 import java.io.PrintWriter;
@@ -118,9 +120,13 @@
     /**
      * Direct interface to the service binder object - it's used to send the events, including the
      * last ones (when the session is finished)
+     *
+     * @hide
      */
-    @NonNull
-    private IContentCaptureDirectManager mDirectServiceInterface;
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    @Nullable
+    public IContentCaptureDirectManager mDirectServiceInterface;
+
     @Nullable
     private DeathRecipient mDirectServiceVulture;
 
@@ -131,14 +137,19 @@
     @Nullable
     private IBinder mShareableActivityToken;
 
+    /** @hide */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
     @Nullable
-    private ComponentName mComponentName;
+    public ComponentName mComponentName;
 
     /**
      * List of events held to be sent as a batch.
+     *
+     * @hide
      */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
     @Nullable
-    private ArrayList<ContentCaptureEvent> mEvents;
+    public ArrayList<ContentCaptureEvent> mEvents;
 
     // Used just for debugging purposes (on dump)
     private long mNextFlush;
@@ -157,6 +168,11 @@
     @NonNull
     private final SessionStateReceiver mSessionStateReceiver;
 
+    /** @hide */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    @Nullable
+    public ContentProtectionEventProcessor mContentProtectionEventProcessor;
+
     private static class SessionStateReceiver extends IResultReceiver.Stub {
         private final WeakReference<MainContentCaptureSession> mMainSession;
 
@@ -194,8 +210,12 @@
         }
     }
 
-    protected MainContentCaptureSession(@NonNull ContentCaptureManager.StrippedContext context,
-            @NonNull ContentCaptureManager manager, @NonNull Handler handler,
+    /** @hide */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED)
+    public MainContentCaptureSession(
+            @NonNull ContentCaptureManager.StrippedContext context,
+            @NonNull ContentCaptureManager manager,
+            @NonNull Handler handler,
             @NonNull IContentCaptureManager systemServerInterface) {
         mContext = context;
         mManager = manager;
@@ -273,15 +293,16 @@
     }
 
     /**
-     * Callback from {@code system_server} after call to
-     * {@link IContentCaptureManager#startSession(IBinder, ComponentName, String, int,
-     * IResultReceiver)}.
+     * Callback from {@code system_server} after call to {@link
+     * IContentCaptureManager#startSession(IBinder, ComponentName, String, int, IResultReceiver)}.
      *
      * @param resultCode session state
      * @param binder handle to {@code IContentCaptureDirectManager}
+     * @hide
      */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
     @UiThread
-    private void onSessionStarted(int resultCode, @Nullable IBinder binder) {
+    public void onSessionStarted(int resultCode, @Nullable IBinder binder) {
         if (binder != null) {
             mDirectServiceInterface = IContentCaptureDirectManager.Stub.asInterface(binder);
             mDirectServiceVulture = () -> {
@@ -296,6 +317,20 @@
             }
         }
 
+        // Should not be possible for mComponentName to be null here but check anyway
+        if (mManager.mOptions.contentProtectionOptions.enableReceiver
+                && mManager.getContentProtectionEventBuffer() != null
+                && mComponentName != null) {
+            mContentProtectionEventProcessor =
+                    new ContentProtectionEventProcessor(
+                            mManager.getContentProtectionEventBuffer(),
+                            mHandler,
+                            mSystemServerInterface,
+                            mComponentName.getPackageName());
+        } else {
+            mContentProtectionEventProcessor = null;
+        }
+
         if ((resultCode & STATE_DISABLED) != 0) {
             resetSession(resultCode);
         } else {
@@ -311,8 +346,10 @@
         }
     }
 
+    /** @hide */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
     @UiThread
-    private void sendEvent(@NonNull ContentCaptureEvent event) {
+    public void sendEvent(@NonNull ContentCaptureEvent event) {
         sendEvent(event, /* forceFlush= */ false);
     }
 
@@ -337,6 +374,25 @@
             if (sVerbose) Log.v(TAG, "handleSendEvent(): ignoring when disabled");
             return;
         }
+
+        if (isContentProtectionReceiverEnabled()) {
+            sendContentProtectionEvent(event);
+        }
+        if (isContentCaptureReceiverEnabled()) {
+            sendContentCaptureEvent(event, forceFlush);
+        }
+    }
+
+    @UiThread
+    private void sendContentProtectionEvent(@NonNull ContentCaptureEvent event) {
+        if (mContentProtectionEventProcessor != null) {
+            mContentProtectionEventProcessor.processEvent(event);
+        }
+    }
+
+    @UiThread
+    private void sendContentCaptureEvent(@NonNull ContentCaptureEvent event, boolean forceFlush) {
+        final int eventType = event.getType();
         final int maxBufferSize = mManager.mOptions.maxBufferSize;
         if (mEvents == null) {
             if (sVerbose) {
@@ -528,9 +584,11 @@
         flush(reason);
     }
 
+    /** @hide */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
     @Override
     @UiThread
-    void flush(@FlushReason int reason) {
+    public void flush(@FlushReason int reason) {
         if (mEvents == null || mEvents.size() == 0) {
             if (sVerbose) {
                 Log.v(TAG, "Don't flush for empty event buffer.");
@@ -544,6 +602,10 @@
             return;
         }
 
+        if (!isContentCaptureReceiverEnabled()) {
+            return;
+        }
+
         if (mDirectServiceInterface == null) {
             if (sVerbose) {
                 Log.v(TAG, "handleForceFlush(" + getDebugState(reason) + "): hold your horses, "
@@ -607,8 +669,10 @@
         return new ParceledListSlice<>(events);
     }
 
+    /** hide */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
     @UiThread
-    private void destroySession() {
+    public void destroySession() {
         if (sDebug) {
             Log.d(TAG, "Destroying session (ctx=" + mContext + ", id=" + mId + ") with "
                     + (mEvents == null ? 0 : mEvents.size()) + " event(s) for "
@@ -626,12 +690,15 @@
             mDirectServiceInterface.asBinder().unlinkToDeath(mDirectServiceVulture, 0);
         }
         mDirectServiceInterface = null;
+        mContentProtectionEventProcessor = null;
     }
 
     // TODO(b/122454205): once we support multiple sessions, we might need to move some of these
     // clearings out.
+    /** @hide */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
     @UiThread
-    private void resetSession(int newState) {
+    public void resetSession(int newState) {
         if (sVerbose) {
             Log.v(TAG, "handleResetSession(" + getActivityName() + "): from "
                     + getStateAsString(mState) + " to " + getStateAsString(newState));
@@ -651,6 +718,7 @@
             }
         }
         mDirectServiceInterface = null;
+        mContentProtectionEventProcessor = null;
         mHandler.removeMessages(MSG_FLUSH);
     }
 
@@ -878,4 +946,14 @@
     private String getDebugState(@FlushReason int reason) {
         return getDebugState() + ", reason=" + getFlushReasonAsString(reason);
     }
+
+    @UiThread
+    private boolean isContentProtectionReceiverEnabled() {
+        return mManager.mOptions.contentProtectionOptions.enableReceiver;
+    }
+
+    @UiThread
+    private boolean isContentCaptureReceiverEnabled() {
+        return mManager.mOptions.enableReceiver;
+    }
 }
diff --git a/core/java/android/view/contentcapture/OWNERS b/core/java/android/view/contentcapture/OWNERS
index a958bbe..e4b0952 100644
--- a/core/java/android/view/contentcapture/OWNERS
+++ b/core/java/android/view/contentcapture/OWNERS
@@ -1,10 +1,5 @@
 # Bug component: 544200
 
-augale@google.com
-joannechung@google.com
-markpun@google.com
-lpeter@google.com
-tymtsai@google.com
 hackz@google.com
 shivanker@google.com
 volnov@google.com
diff --git a/core/java/android/view/contentcapture/ViewNode.java b/core/java/android/view/contentcapture/ViewNode.java
index 044a31f..f218995 100644
--- a/core/java/android/view/contentcapture/ViewNode.java
+++ b/core/java/android/view/contentcapture/ViewNode.java
@@ -480,6 +480,11 @@
         return mLocaleList;
     }
 
+    /** @hide */
+    public void setTextIdEntry(@NonNull String textIdEntry) {
+        mTextIdEntry = textIdEntry;
+    }
+
     private void writeSelfToParcel(@NonNull Parcel parcel, int parcelFlags) {
         long nodeFlags = mFlags;
 
diff --git a/core/java/android/view/contentprotection/ContentProtectionEventProcessor.java b/core/java/android/view/contentprotection/ContentProtectionEventProcessor.java
new file mode 100644
index 0000000..b44abf3
--- /dev/null
+++ b/core/java/android/view/contentprotection/ContentProtectionEventProcessor.java
@@ -0,0 +1,254 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.view.contentprotection;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UiThread;
+import android.content.pm.ParceledListSlice;
+import android.os.Handler;
+import android.text.InputType;
+import android.util.Log;
+import android.view.contentcapture.ContentCaptureEvent;
+import android.view.contentcapture.IContentCaptureManager;
+import android.view.contentcapture.ViewNode;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.RingBuffer;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Main entry point for processing {@link ContentCaptureEvent} for the content protection flow.
+ *
+ * @hide
+ */
+public class ContentProtectionEventProcessor {
+
+    private static final String TAG = "ContentProtectionEventProcessor";
+
+    private static final List<Integer> PASSWORD_FIELD_INPUT_TYPES =
+            Collections.unmodifiableList(
+                    Arrays.asList(
+                            InputType.TYPE_NUMBER_VARIATION_PASSWORD,
+                            InputType.TYPE_TEXT_VARIATION_PASSWORD,
+                            InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD,
+                            InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD));
+
+    private static final List<String> PASSWORD_TEXTS =
+            Collections.unmodifiableList(
+                    Arrays.asList("password", "pass word", "code", "pin", "credential"));
+
+    private static final List<String> ADDITIONAL_SUSPICIOUS_TEXTS =
+            Collections.unmodifiableList(
+                    Arrays.asList("user", "mail", "phone", "number", "login", "log in", "sign in"));
+
+    private static final Duration MIN_DURATION_BETWEEN_FLUSHING = Duration.ofSeconds(3);
+
+    private static final String ANDROID_CLASS_NAME_PREFIX = "android.";
+
+    private static final Set<Integer> EVENT_TYPES_TO_STORE =
+            Collections.unmodifiableSet(
+                    new HashSet<>(
+                            Arrays.asList(
+                                    ContentCaptureEvent.TYPE_VIEW_APPEARED,
+                                    ContentCaptureEvent.TYPE_VIEW_DISAPPEARED,
+                                    ContentCaptureEvent.TYPE_VIEW_TEXT_CHANGED)));
+
+    private static final int RESET_LOGIN_TOTAL_EVENTS_TO_PROCESS = 150;
+
+    @NonNull private final RingBuffer<ContentCaptureEvent> mEventBuffer;
+
+    @NonNull private final Handler mHandler;
+
+    @NonNull private final IContentCaptureManager mContentCaptureManager;
+
+    @NonNull private final String mPackageName;
+
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    public boolean mPasswordFieldDetected = false;
+
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    public boolean mSuspiciousTextDetected = false;
+
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    @Nullable
+    public Instant mLastFlushTime;
+
+    private int mResetLoginRemainingEventsToProcess;
+
+    public ContentProtectionEventProcessor(
+            @NonNull RingBuffer<ContentCaptureEvent> eventBuffer,
+            @NonNull Handler handler,
+            @NonNull IContentCaptureManager contentCaptureManager,
+            @NonNull String packageName) {
+        mEventBuffer = eventBuffer;
+        mHandler = handler;
+        mContentCaptureManager = contentCaptureManager;
+        mPackageName = packageName;
+    }
+
+    /** Main entry point for {@link ContentCaptureEvent} processing. */
+    @UiThread
+    public void processEvent(@NonNull ContentCaptureEvent event) {
+        if (EVENT_TYPES_TO_STORE.contains(event.getType())) {
+            storeEvent(event);
+        }
+        if (event.getType() == ContentCaptureEvent.TYPE_VIEW_APPEARED) {
+            processViewAppearedEvent(event);
+        }
+    }
+
+    @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();
+        viewNode.setTextIdEntry(mPackageName);
+        event.setViewNode(viewNode);
+        mEventBuffer.append(event);
+    }
+
+    @UiThread
+    private void processViewAppearedEvent(@NonNull ContentCaptureEvent event) {
+        mPasswordFieldDetected |= isPasswordField(event);
+        mSuspiciousTextDetected |= isSuspiciousText(event);
+        if (mPasswordFieldDetected && mSuspiciousTextDetected) {
+            loginDetected();
+        } else {
+            maybeResetLoginFlags();
+        }
+    }
+
+    @UiThread
+    private void loginDetected() {
+        if (mLastFlushTime == null
+                || Instant.now().isAfter(mLastFlushTime.plus(MIN_DURATION_BETWEEN_FLUSHING))) {
+            flush();
+        }
+        resetLoginFlags();
+    }
+
+    @UiThread
+    private void resetLoginFlags() {
+        mPasswordFieldDetected = false;
+        mSuspiciousTextDetected = false;
+        mResetLoginRemainingEventsToProcess = 0;
+    }
+
+    @UiThread
+    private void maybeResetLoginFlags() {
+        if (mPasswordFieldDetected || mSuspiciousTextDetected) {
+            if (mResetLoginRemainingEventsToProcess <= 0) {
+                mResetLoginRemainingEventsToProcess = RESET_LOGIN_TOTAL_EVENTS_TO_PROCESS;
+            } else {
+                mResetLoginRemainingEventsToProcess--;
+                if (mResetLoginRemainingEventsToProcess <= 0) {
+                    resetLoginFlags();
+                }
+            }
+        }
+    }
+
+    @UiThread
+    private void flush() {
+        mLastFlushTime = Instant.now();
+
+        // Note the thread annotations, do not move clearEvents to mHandler
+        ParceledListSlice<ContentCaptureEvent> events = clearEvents();
+        mHandler.post(() -> handlerOnLoginDetected(events));
+    }
+
+    @UiThread
+    @NonNull
+    private ParceledListSlice<ContentCaptureEvent> clearEvents() {
+        List<ContentCaptureEvent> events = Arrays.asList(mEventBuffer.toArray());
+        mEventBuffer.clear();
+        return new ParceledListSlice<>(events);
+    }
+
+    private void handlerOnLoginDetected(@NonNull ParceledListSlice<ContentCaptureEvent> events) {
+        try {
+            mContentCaptureManager.onLoginDetected(events);
+        } catch (Exception ex) {
+            Log.e(TAG, "Failed to flush events for: " + mPackageName, ex);
+        }
+    }
+
+    private boolean isPasswordField(@NonNull ContentCaptureEvent event) {
+        return isPasswordField(event.getViewNode());
+    }
+
+    private boolean isPasswordField(@Nullable ViewNode viewNode) {
+        if (viewNode == null) {
+            return false;
+        }
+        return isAndroidPasswordField(viewNode) || isWebViewPasswordField(viewNode);
+    }
+
+    private boolean isAndroidPasswordField(@NonNull ViewNode viewNode) {
+        if (!isAndroidViewNode(viewNode)) {
+            return false;
+        }
+        int inputType = viewNode.getInputType();
+        return PASSWORD_FIELD_INPUT_TYPES.stream()
+                .anyMatch(passwordInputType -> (inputType & passwordInputType) != 0);
+    }
+
+    private boolean isWebViewPasswordField(@NonNull ViewNode viewNode) {
+        if (viewNode.getClassName() != null) {
+            return false;
+        }
+        return isPasswordText(ContentProtectionUtils.getViewNodeText(viewNode));
+    }
+
+    private boolean isAndroidViewNode(@NonNull ViewNode viewNode) {
+        String className = viewNode.getClassName();
+        return className != null && className.startsWith(ANDROID_CLASS_NAME_PREFIX);
+    }
+
+    private boolean isSuspiciousText(@NonNull ContentCaptureEvent event) {
+        return isSuspiciousText(ContentProtectionUtils.getEventText(event))
+                || isSuspiciousText(ContentProtectionUtils.getViewNodeText(event));
+    }
+
+    private boolean isSuspiciousText(@Nullable String text) {
+        if (text == null) {
+            return false;
+        }
+        if (isPasswordText(text)) {
+            return true;
+        }
+        String lowerCaseText = text.toLowerCase();
+        return ADDITIONAL_SUSPICIOUS_TEXTS.stream()
+                .anyMatch(suspiciousText -> lowerCaseText.contains(suspiciousText));
+    }
+
+    private boolean isPasswordText(@Nullable String text) {
+        if (text == null) {
+            return false;
+        }
+        String lowerCaseText = text.toLowerCase();
+        return PASSWORD_TEXTS.stream()
+                .anyMatch(passwordText -> lowerCaseText.contains(passwordText));
+    }
+}
diff --git a/core/java/android/view/contentprotection/ContentProtectionUtils.java b/core/java/android/view/contentprotection/ContentProtectionUtils.java
new file mode 100644
index 0000000..9abf6f1
--- /dev/null
+++ b/core/java/android/view/contentprotection/ContentProtectionUtils.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.contentprotection;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.view.contentcapture.ContentCaptureEvent;
+import android.view.contentcapture.ViewNode;
+
+/**
+ * Utilities for reading data from {@link ContentCaptureEvent} and {@link ViewNode}.
+ *
+ * @hide
+ */
+public final class ContentProtectionUtils {
+
+    /** Returns the text extracted directly from the {@link ContentCaptureEvent}, if set. */
+    @Nullable
+    public static String getEventText(@NonNull ContentCaptureEvent event) {
+        CharSequence text = event.getText();
+        if (text == null) {
+            return null;
+        }
+        return text.toString();
+    }
+
+    /** Returns the text extracted from the event's {@link ViewNode}, if set. */
+    @Nullable
+    public static String getViewNodeText(@NonNull ContentCaptureEvent event) {
+        ViewNode viewNode = event.getViewNode();
+        if (viewNode == null) {
+            return null;
+        }
+        return getViewNodeText(viewNode);
+    }
+
+    /** Returns the text extracted directly from the {@link ViewNode}, if set. */
+    @Nullable
+    public static String getViewNodeText(@NonNull ViewNode viewNode) {
+        CharSequence text = viewNode.getText();
+        if (text == null) {
+            return null;
+        }
+        return text.toString();
+    }
+}
diff --git a/core/java/android/view/inputmethod/ImeTracker.java b/core/java/android/view/inputmethod/ImeTracker.java
index f0d1019..03d1cd8 100644
--- a/core/java/android/view/inputmethod/ImeTracker.java
+++ b/core/java/android/view/inputmethod/ImeTracker.java
@@ -16,8 +16,12 @@
 
 package android.view.inputmethod;
 
+import static android.view.InsetsController.ANIMATION_TYPE_HIDE;
+import static android.view.InsetsController.ANIMATION_TYPE_SHOW;
+
 import static com.android.internal.inputmethod.InputMethodDebug.softInputDisplayReasonToString;
-import static com.android.internal.jank.InteractionJankMonitor.CUJ_IME_INSETS_ANIMATION;
+import static com.android.internal.jank.InteractionJankMonitor.CUJ_IME_INSETS_HIDE_ANIMATION;
+import static com.android.internal.jank.InteractionJankMonitor.CUJ_IME_INSETS_SHOW_ANIMATION;
 import static com.android.internal.util.LatencyTracker.ACTION_REQUEST_IME_HIDDEN;
 import static com.android.internal.util.LatencyTracker.ACTION_REQUEST_IME_SHOWN;
 
@@ -696,20 +700,22 @@
         /**
          * Called when the animation, which is going to be monitored, starts.
          *
-         * @param jankContext context which is needed by {@link InteractionJankMonitor}
-         * @param animType {@link AnimationType}
+         * @param jankContext context which is needed by {@link InteractionJankMonitor}.
+         * @param animType the animation type.
          * @param useSeparatedThread {@code true} if the animation is handled by the app,
          *                           {@code false} if the animation will be scheduled on the
-         *                           {@link android.view.InsetsAnimationThread}
+         *                           {@link android.view.InsetsAnimationThread}.
          */
         public void onRequestAnimation(@NonNull InputMethodJankContext jankContext,
                 @AnimationType int animType, boolean useSeparatedThread) {
+            final int cujType = getImeInsetsCujFromAnimation(animType);
             if (jankContext.getDisplayContext() == null
-                    || jankContext.getTargetSurfaceControl() == null) {
+                    || jankContext.getTargetSurfaceControl() == null
+                    || cujType == -1) {
                 return;
             }
             final Configuration.Builder builder = Configuration.Builder.withSurface(
-                            CUJ_IME_INSETS_ANIMATION,
+                            cujType,
                             jankContext.getDisplayContext(),
                             jankContext.getTargetSurfaceControl())
                     .setTag(String.format(Locale.US, "%d@%d@%s", animType,
@@ -719,16 +725,44 @@
 
         /**
          * Called when the animation, which is going to be monitored, cancels.
+         *
+         * @param animType the animation type.
          */
-        public void onCancelAnimation() {
-            InteractionJankMonitor.getInstance().cancel(CUJ_IME_INSETS_ANIMATION);
+        public void onCancelAnimation(@AnimationType int animType) {
+            final int cujType = getImeInsetsCujFromAnimation(animType);
+            if (cujType == -1) {
+                InteractionJankMonitor.getInstance().cancel(cujType);
+            }
         }
 
         /**
          * Called when the animation, which is going to be monitored, ends.
+         *
+         * @param animType the animation type.
          */
-        public void onFinishAnimation() {
-            InteractionJankMonitor.getInstance().end(CUJ_IME_INSETS_ANIMATION);
+        public void onFinishAnimation(@AnimationType int animType) {
+            final int cujType = getImeInsetsCujFromAnimation(animType);
+            if (cujType != -1) {
+                InteractionJankMonitor.getInstance().end(cujType);
+            }
+        }
+
+        /**
+         * A helper method to translate animation type to CUJ type for IME animations.
+         *
+         * @param animType the animation type.
+         * @return the integer in {@link com.android.internal.jank.InteractionJankMonitor.CujType},
+         * or {@code -1} if the animation type is not supported for tracking yet.
+         */
+        private static int getImeInsetsCujFromAnimation(@AnimationType int animType) {
+            switch (animType) {
+                case ANIMATION_TYPE_SHOW:
+                    return CUJ_IME_INSETS_SHOW_ANIMATION;
+                case ANIMATION_TYPE_HIDE:
+                    return CUJ_IME_INSETS_HIDE_ANIMATION;
+                default:
+                    return -1;
+            }
         }
     }
 
diff --git a/core/java/android/view/inputmethod/InlineSuggestionsRequest.java b/core/java/android/view/inputmethod/InlineSuggestionsRequest.java
index 581feca..70279cc 100644
--- a/core/java/android/view/inputmethod/InlineSuggestionsRequest.java
+++ b/core/java/android/view/inputmethod/InlineSuggestionsRequest.java
@@ -18,7 +18,6 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.TestApi;
 import android.app.ActivityThread;
 import android.app.compat.CompatChanges;
 import android.compat.annotation.ChangeId;
@@ -112,6 +111,22 @@
     private @Nullable InlinePresentationSpec mInlineTooltipPresentationSpec;
 
     /**
+     * Whether the IME supports inline suggestions from the default Autofill service that
+     * provides the input view.
+     *
+     * Note: The default value is {@code true}.
+     */
+    private boolean mServiceSupported;
+
+    /**
+     * Whether the IME supports inline suggestions from the application that provides the
+     * input view.
+     *
+     * Note: The default value is {@code true}.
+     */
+    private boolean mClientSupported;
+
+    /**
      * @hide
      * @see {@link #mHostInputToken}.
      */
@@ -205,9 +220,15 @@
         return Bundle.EMPTY;
     }
 
-    /**
-     * @hide
-     */
+    private static boolean defaultServiceSupported() {
+        return true;
+    }
+
+    private static boolean defaultClientSupported() {
+        return true;
+    }
+
+    /** @hide */
     abstract static class BaseBuilder {
         abstract Builder setInlinePresentationSpecs(
                 @NonNull List<android.widget.inline.InlinePresentationSpec> specs);
@@ -219,14 +240,25 @@
         abstract Builder setHostDisplayId(int value);
     }
 
+    /** @hide */
+    public boolean isServiceSupported() {
+        return mServiceSupported;
+    }
 
-    // Code below generated by codegen v1.0.23.
+    /** @hide */
+    public boolean isClientSupported() {
+        return mClientSupported;
+    }
+
+
+
+    // Code below generated by codegen v1.0.22.
     //
     // DO NOT MODIFY!
     // CHECKSTYLE:OFF Generated code
     //
     // To regenerate run:
-    // $ codegen $ANDROID_BUILD_TOP/./frameworks/base/core/java/android/view/inputmethod/InlineSuggestionsRequest.java
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/view/inputmethod/InlineSuggestionsRequest.java
     //
     // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
     //   Settings > Editor > Code Style > Formatter Control
@@ -242,7 +274,9 @@
             @NonNull Bundle extras,
             @Nullable IBinder hostInputToken,
             int hostDisplayId,
-            @Nullable InlinePresentationSpec inlineTooltipPresentationSpec) {
+            @Nullable InlinePresentationSpec inlineTooltipPresentationSpec,
+            boolean serviceSupported,
+            boolean clientSupported) {
         this.mMaxSuggestionCount = maxSuggestionCount;
         this.mInlinePresentationSpecs = inlinePresentationSpecs;
         com.android.internal.util.AnnotationValidations.validate(
@@ -259,6 +293,8 @@
         this.mHostInputToken = hostInputToken;
         this.mHostDisplayId = hostDisplayId;
         this.mInlineTooltipPresentationSpec = inlineTooltipPresentationSpec;
+        this.mServiceSupported = serviceSupported;
+        this.mClientSupported = clientSupported;
 
         onConstructed();
     }
@@ -342,7 +378,9 @@
     }
 
     /**
-     * Specifies the UI specification for the inline suggestion tooltip in the response.
+     * The {@link InlinePresentationSpec} for the inline suggestion tooltip in the response.
+     *
+     * @see android.service.autofill.InlinePresentation#createTooltipPresentation(Slice, InlinePresentationSpec)
      */
     @DataClass.Generated.Member
     public @Nullable InlinePresentationSpec getInlineTooltipPresentationSpec() {
@@ -363,7 +401,9 @@
                 "extras = " + mExtras + ", " +
                 "hostInputToken = " + mHostInputToken + ", " +
                 "hostDisplayId = " + mHostDisplayId + ", " +
-                "inlineTooltipPresentationSpec = " + mInlineTooltipPresentationSpec +
+                "inlineTooltipPresentationSpec = " + mInlineTooltipPresentationSpec + ", " +
+                "serviceSupported = " + mServiceSupported + ", " +
+                "clientSupported = " + mClientSupported +
         " }";
     }
 
@@ -387,7 +427,9 @@
                 && extrasEquals(that.mExtras)
                 && java.util.Objects.equals(mHostInputToken, that.mHostInputToken)
                 && mHostDisplayId == that.mHostDisplayId
-                && java.util.Objects.equals(mInlineTooltipPresentationSpec, that.mInlineTooltipPresentationSpec);
+                && java.util.Objects.equals(mInlineTooltipPresentationSpec, that.mInlineTooltipPresentationSpec)
+                && mServiceSupported == that.mServiceSupported
+                && mClientSupported == that.mClientSupported;
     }
 
     @Override
@@ -405,6 +447,8 @@
         _hash = 31 * _hash + java.util.Objects.hashCode(mHostInputToken);
         _hash = 31 * _hash + mHostDisplayId;
         _hash = 31 * _hash + java.util.Objects.hashCode(mInlineTooltipPresentationSpec);
+        _hash = 31 * _hash + Boolean.hashCode(mServiceSupported);
+        _hash = 31 * _hash + Boolean.hashCode(mClientSupported);
         return _hash;
     }
 
@@ -415,6 +459,8 @@
         // void parcelFieldName(Parcel dest, int flags) { ... }
 
         int flg = 0;
+        if (mServiceSupported) flg |= 0x100;
+        if (mClientSupported) flg |= 0x200;
         if (mHostInputToken != null) flg |= 0x20;
         if (mInlineTooltipPresentationSpec != null) flg |= 0x80;
         dest.writeInt(flg);
@@ -440,9 +486,11 @@
         // static FieldType unparcelFieldName(Parcel in) { ... }
 
         int flg = in.readInt();
+        boolean serviceSupported = (flg & 0x100) != 0;
+        boolean clientSupported = (flg & 0x200) != 0;
         int maxSuggestionCount = in.readInt();
         List<InlinePresentationSpec> inlinePresentationSpecs = new ArrayList<>();
-        in.readParcelableList(inlinePresentationSpecs, InlinePresentationSpec.class.getClassLoader());
+        in.readParcelableList(inlinePresentationSpecs, InlinePresentationSpec.class.getClassLoader(), android.widget.inline.InlinePresentationSpec.class);
         String hostPackageName = in.readString();
         LocaleList supportedLocales = (LocaleList) in.readTypedObject(LocaleList.CREATOR);
         Bundle extras = in.readBundle();
@@ -466,6 +514,8 @@
         this.mHostInputToken = hostInputToken;
         this.mHostDisplayId = hostDisplayId;
         this.mInlineTooltipPresentationSpec = inlineTooltipPresentationSpec;
+        this.mServiceSupported = serviceSupported;
+        this.mClientSupported = clientSupported;
 
         onConstructed();
     }
@@ -499,6 +549,8 @@
         private @Nullable IBinder mHostInputToken;
         private int mHostDisplayId;
         private @Nullable InlinePresentationSpec mInlineTooltipPresentationSpec;
+        private boolean mServiceSupported;
+        private boolean mClientSupported;
 
         private long mBuilderFieldsSet = 0L;
 
@@ -631,7 +683,9 @@
         }
 
         /**
-         * Specifies the UI specification for the inline suggestion tooltip in the response.
+         * The {@link InlinePresentationSpec} for the inline suggestion tooltip in the response.
+         *
+         * @see android.service.autofill.InlinePresentation#createTooltipPresentation(Slice, InlinePresentationSpec)s
          */
         @DataClass.Generated.Member
         public @NonNull Builder setInlineTooltipPresentationSpec(@NonNull InlinePresentationSpec value) {
@@ -641,10 +695,38 @@
             return this;
         }
 
+        /**
+         * Whether the IME supports inline suggestions from the default Autofill service that
+         * provides the input view.
+         *
+         * Note: The default value is {@code true}.
+         */
+        @DataClass.Generated.Member
+        public @NonNull Builder setServiceSupported(boolean value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x100;
+            mServiceSupported = value;
+            return this;
+        }
+
+        /**
+         * Whether the IME supports inline suggestions from the application that provides the
+         * input view.
+         *
+         * Note: The default value is {@code true}.
+         */
+        @DataClass.Generated.Member
+        public @NonNull Builder setClientSupported(boolean value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x200;
+            mClientSupported = value;
+            return this;
+        }
+
         /** Builds the instance. This builder should not be touched after calling this! */
         public @NonNull InlineSuggestionsRequest build() {
             checkNotUsed();
-            mBuilderFieldsSet |= 0x100; // Mark builder used
+            mBuilderFieldsSet |= 0x400; // Mark builder used
 
             if ((mBuilderFieldsSet & 0x1) == 0) {
                 mMaxSuggestionCount = defaultMaxSuggestionCount();
@@ -667,6 +749,12 @@
             if ((mBuilderFieldsSet & 0x80) == 0) {
                 mInlineTooltipPresentationSpec = defaultInlineTooltipPresentationSpec();
             }
+            if ((mBuilderFieldsSet & 0x100) == 0) {
+                mServiceSupported = defaultServiceSupported();
+            }
+            if ((mBuilderFieldsSet & 0x200) == 0) {
+                mClientSupported = defaultClientSupported();
+            }
             InlineSuggestionsRequest o = new InlineSuggestionsRequest(
                     mMaxSuggestionCount,
                     mInlinePresentationSpecs,
@@ -675,12 +763,14 @@
                     mExtras,
                     mHostInputToken,
                     mHostDisplayId,
-                    mInlineTooltipPresentationSpec);
+                    mInlineTooltipPresentationSpec,
+                    mServiceSupported,
+                    mClientSupported);
             return o;
         }
 
         private void checkNotUsed() {
-            if ((mBuilderFieldsSet & 0x100) != 0) {
+            if ((mBuilderFieldsSet & 0x400) != 0) {
                 throw new IllegalStateException(
                         "This Builder should not be reused. Use a new Builder instance instead");
             }
@@ -688,10 +778,10 @@
     }
 
     @DataClass.Generated(
-            time = 1682382296877L,
-            codegenVersion = "1.0.23",
+            time = 1615798784918L,
+            codegenVersion = "1.0.22",
             sourceFile = "frameworks/base/core/java/android/view/inputmethod/InlineSuggestionsRequest.java",
-            inputSignatures = "public static final  int SUGGESTION_COUNT_UNLIMITED\nprivate final  int mMaxSuggestionCount\nprivate final @android.annotation.NonNull java.util.List<android.widget.inline.InlinePresentationSpec> mInlinePresentationSpecs\nprivate @android.annotation.NonNull java.lang.String mHostPackageName\nprivate @android.annotation.NonNull android.os.LocaleList mSupportedLocales\nprivate @android.annotation.NonNull android.os.Bundle mExtras\nprivate @android.annotation.Nullable android.os.IBinder mHostInputToken\nprivate  int mHostDisplayId\nprivate @android.annotation.Nullable android.widget.inline.InlinePresentationSpec mInlineTooltipPresentationSpec\nprivate static final @android.compat.annotation.ChangeId @android.compat.annotation.EnabledSince long IME_AUTOFILL_DEFAULT_SUPPORTED_LOCALES_IS_EMPTY\npublic  void setHostInputToken(android.os.IBinder)\nprivate  boolean extrasEquals(android.os.Bundle)\nprivate  void parcelHostInputToken(android.os.Parcel,int)\nprivate @android.annotation.Nullable android.os.IBinder unparcelHostInputToken(android.os.Parcel)\npublic  void setHostDisplayId(int)\nprivate  void onConstructed()\npublic  void filterContentTypes()\nprivate static  int defaultMaxSuggestionCount()\nprivate static  java.lang.String defaultHostPackageName()\nprivate static  android.widget.inline.InlinePresentationSpec defaultInlineTooltipPresentationSpec()\nprivate static  android.os.LocaleList defaultSupportedLocales()\nprivate static @android.annotation.Nullable android.os.IBinder defaultHostInputToken()\nprivate static @android.annotation.Nullable int defaultHostDisplayId()\nprivate static @android.annotation.NonNull android.os.Bundle defaultExtras()\nclass InlineSuggestionsRequest extends java.lang.Object implements [android.os.Parcelable]\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setInlinePresentationSpecs(java.util.List<android.widget.inline.InlinePresentationSpec>)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setHostPackageName(java.lang.String)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setHostInputToken(android.os.IBinder)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setHostDisplayId(int)\nclass BaseBuilder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genBuilder=true)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setInlinePresentationSpecs(java.util.List<android.widget.inline.InlinePresentationSpec>)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setHostPackageName(java.lang.String)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setHostInputToken(android.os.IBinder)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setHostDisplayId(int)\nclass BaseBuilder extends java.lang.Object implements []")
+            inputSignatures = "public static final  int SUGGESTION_COUNT_UNLIMITED\nprivate final  int mMaxSuggestionCount\nprivate final @android.annotation.NonNull java.util.List<android.widget.inline.InlinePresentationSpec> mInlinePresentationSpecs\nprivate @android.annotation.NonNull java.lang.String mHostPackageName\nprivate @android.annotation.NonNull android.os.LocaleList mSupportedLocales\nprivate @android.annotation.NonNull android.os.Bundle mExtras\nprivate @android.annotation.Nullable android.os.IBinder mHostInputToken\nprivate  int mHostDisplayId\nprivate @android.annotation.Nullable android.widget.inline.InlinePresentationSpec mInlineTooltipPresentationSpec\nprivate  boolean mServiceSupported\nprivate  boolean mClientSupported\nprivate static final @android.compat.annotation.ChangeId @android.compat.annotation.EnabledSince long IME_AUTOFILL_DEFAULT_SUPPORTED_LOCALES_IS_EMPTY\npublic  void setHostInputToken(android.os.IBinder)\nprivate  boolean extrasEquals(android.os.Bundle)\nprivate  void parcelHostInputToken(android.os.Parcel,int)\nprivate @android.annotation.Nullable android.os.IBinder unparcelHostInputToken(android.os.Parcel)\npublic  void setHostDisplayId(int)\nprivate  void onConstructed()\npublic  void filterContentTypes()\nprivate static  int defaultMaxSuggestionCount()\nprivate static  java.lang.String defaultHostPackageName()\nprivate static  android.widget.inline.InlinePresentationSpec defaultInlineTooltipPresentationSpec()\nprivate static  android.os.LocaleList defaultSupportedLocales()\nprivate static @android.annotation.Nullable android.os.IBinder defaultHostInputToken()\nprivate static @android.annotation.Nullable int defaultHostDisplayId()\nprivate static @android.annotation.NonNull android.os.Bundle defaultExtras()\nprivate static  boolean defaultServiceSupported()\nprivate static  boolean defaultClientSupported()\npublic  boolean isServiceSupported()\npublic  boolean isClientSupported()\nclass InlineSuggestionsRequest extends java.lang.Object implements [android.os.Parcelable]\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setInlinePresentationSpecs(java.util.List<android.widget.inline.InlinePresentationSpec>)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setHostPackageName(java.lang.String)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setHostInputToken(android.os.IBinder)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setHostDisplayId(int)\nclass BaseBuilder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genBuilder=true)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setInlinePresentationSpecs(java.util.List<android.widget.inline.InlinePresentationSpec>)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setHostPackageName(java.lang.String)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setHostInputToken(android.os.IBinder)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setHostDisplayId(int)\nclass BaseBuilder extends java.lang.Object implements []")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/view/inputmethod/InputMethod.java b/core/java/android/view/inputmethod/InputMethod.java
index 92380ed..6340388 100644
--- a/core/java/android/view/inputmethod/InputMethod.java
+++ b/core/java/android/view/inputmethod/InputMethod.java
@@ -282,7 +282,11 @@
      * Flag for {@link #showSoftInput}: this show has been forced to
      * happen by the user.  If set, the input method should remain visible
      * until deliberated dismissed by the user in its UI.
+     *
+     * @deprecated {@link InputMethodManager#SHOW_FORCED} is deprecated and
+     * should no longer be used by apps. IMEs likewise should no longer react to this flag.
      */
+    @Deprecated
     public static final int SHOW_FORCED = 0x00002;
 
     /**
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 3cac1e5..48bf973 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -50,6 +50,7 @@
 import android.annotation.UiThread;
 import android.annotation.UserIdInt;
 import android.app.ActivityThread;
+import android.app.PropertyInvalidatedCache;
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.EnabledSince;
 import android.compat.annotation.UnsupportedAppUsage;
@@ -536,6 +537,13 @@
     @UnsupportedAppUsage
     Rect mCursorRect = new Rect();
 
+    /** Cached value for {@link #isStylusHandwritingAvailable} for userId. */
+    @GuardedBy("mH")
+    private PropertyInvalidatedCache<Integer, Boolean> mStylusHandwritingAvailableCache;
+
+    private static final String CACHE_KEY_STYLUS_HANDWRITING_PROPERTY =
+            "cache_key.system_server.stylus_handwriting";
+
     @GuardedBy("mH")
     private int mCursorSelStart;
     @GuardedBy("mH")
@@ -662,6 +670,15 @@
     private static final int MSG_UPDATE_VIRTUAL_DISPLAY_TO_SCREEN_MATRIX = 30;
     private static final int MSG_ON_SHOW_REQUESTED = 31;
 
+    /**
+     * Calling this will invalidate Local stylus handwriting availability Cache which
+     * forces the next query in any process to recompute the cache.
+     * @hide
+     */
+    public static void invalidateLocalStylusHandwritingAvailabilityCaches() {
+        PropertyInvalidatedCache.invalidateCache(CACHE_KEY_STYLUS_HANDWRITING_PROPERTY);
+    }
+
     private static boolean isAutofillUIShowing(View servedView) {
         AutofillManager afm = servedView.getContext().getSystemService(AutofillManager.class);
         return afm != null && afm.isAutofillUiShowing();
@@ -1577,8 +1594,21 @@
         if (fallbackContext == null) {
             return false;
         }
-
-        return IInputMethodManagerGlobalInvoker.isStylusHandwritingAvailableAsUser(userId);
+        boolean isAvailable;
+        synchronized (mH) {
+            if (mStylusHandwritingAvailableCache == null) {
+                mStylusHandwritingAvailableCache = new PropertyInvalidatedCache<>(
+                        4 /* maxEntries */, CACHE_KEY_STYLUS_HANDWRITING_PROPERTY) {
+                    @Override
+                    public Boolean recompute(Integer userId) {
+                        return IInputMethodManagerGlobalInvoker.isStylusHandwritingAvailableAsUser(
+                                userId);
+                    }
+                };
+            }
+            isAvailable = mStylusHandwritingAvailableCache.query(userId);
+        }
+        return isAvailable;
     }
 
     /**
@@ -2018,7 +2048,7 @@
      *
      * @deprecated Use {@link #showSoftInput} without this flag instead. Using this flag can lead
      * to the soft input remaining visible even when the calling application is closed. The
-     * use of this flag can make the soft input remains visible globally. Starting in
+     * use of this flag can make the soft input remain visible globally. Starting in
      * {@link Build.VERSION_CODES#TIRAMISU Android T}, this flag only has an effect while the
      * caller is currently focused.
      */
@@ -4361,15 +4391,14 @@
      * @param icProto {@link InputConnection} call data in proto format.
      * @hide
      */
-    @GuardedBy("mH")
     public void dumpDebug(ProtoOutputStream proto, @Nullable byte[] icProto) {
-        if (!isImeSessionAvailableLocked()) {
-            return;
-        }
-
-        proto.write(DISPLAY_ID, mDisplayId);
-        final long token = proto.start(INPUT_METHOD_MANAGER);
         synchronized (mH) {
+            if (!isImeSessionAvailableLocked()) {
+                return;
+            }
+
+            proto.write(DISPLAY_ID, mDisplayId);
+            final long token = proto.start(INPUT_METHOD_MANAGER);
             proto.write(CUR_ID, mCurBindState.mImeId);
             proto.write(FULLSCREEN_MODE, mFullscreenMode);
             proto.write(ACTIVE, mActive);
diff --git a/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java b/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java
index aa9225b..e9d7b9b 100644
--- a/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java
+++ b/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java
@@ -1118,7 +1118,7 @@
             @InputConnection.CursorUpdateFilter int cursorUpdateFilter, int imeDisplayId) {
         final InputConnection ic = getInputConnection();
         if (ic == null || !isActive()) {
-            Log.w(TAG, "requestCursorAnchorInfo on inactive InputConnection");
+            Log.w(TAG, "requestCursorUpdates on inactive InputConnection");
             return false;
         }
         if (mParentInputMethodManager.mRequestCursorUpdateDisplayIdCheck.get()
diff --git a/core/java/android/view/inputmethod/TextAppearanceInfo.java b/core/java/android/view/inputmethod/TextAppearanceInfo.java
index 05717dd..7eee33f 100644
--- a/core/java/android/view/inputmethod/TextAppearanceInfo.java
+++ b/core/java/android/view/inputmethod/TextAppearanceInfo.java
@@ -238,7 +238,10 @@
                 .setFontFeatureSettings(textPaint.getFontFeatureSettings())
                 .setFontVariationSettings(textPaint.getFontVariationSettings())
                 .setTextScaleX(textPaint.getTextScaleX())
-                .setTextColor(textPaint.getColor())
+                // When there is a hint text (text length is 0), the text color should be the normal
+                // text color rather than hint text color.
+                .setTextColor(text.length() == 0
+                        ? textView.getCurrentTextColor() : textPaint.getColor())
                 .setLinkTextColor(textPaint.linkColor)
                 .setAllCaps(textView.isAllCaps())
                 .setFallbackLineSpacing(textView.isFallbackLineSpacing())
diff --git a/core/java/android/view/textclassifier/OWNERS b/core/java/android/view/textclassifier/OWNERS
index a205be2..224ed71 100644
--- a/core/java/android/view/textclassifier/OWNERS
+++ b/core/java/android/view/textclassifier/OWNERS
@@ -1,8 +1,3 @@
 # Bug component: 709498
 
-mns@google.com
-toki@google.com
-augale@google.com
-joannechung@google.com
-tonymak@google.com
-licha@google.com
+wangqi@google.com
diff --git a/core/java/android/view/translation/OWNERS b/core/java/android/view/translation/OWNERS
index b772ad3..977bda1 100644
--- a/core/java/android/view/translation/OWNERS
+++ b/core/java/android/view/translation/OWNERS
@@ -1,7 +1,3 @@
 # Bug component: 994311
 
-augale@google.com
-joannechung@google.com
-markpun@google.com
-lpeter@google.com
-tymtsai@google.com
+wangqi@google.com
diff --git a/core/java/android/webkit/CookieManager.java b/core/java/android/webkit/CookieManager.java
index 20230e7..0427d10 100644
--- a/core/java/android/webkit/CookieManager.java
+++ b/core/java/android/webkit/CookieManager.java
@@ -103,15 +103,22 @@
      * will be ignored if it is expired. To set multiple cookies, your application should invoke
      * this method multiple times.
      *
-     * <p>The {@code value} parameter must follow the format of the {@code Set-Cookie} HTTP
-     * response header defined by
-     * <a href="https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-03">RFC6265bis</a>.
-     * This is a key-value pair of the form {@code "key=value"}, optionally followed by a list of
-     * cookie attributes delimited with semicolons (ex. {@code "key=value; Max-Age=123"}). Please
-     * consult the RFC specification for a list of valid attributes.
+     * <p>The {@code value} parameter must follow the format of the {@code Set-Cookie} HTTP response
+     * header. This is a key-value pair of the form {@code "key=value"}, optionally followed by a
+     * list of cookie attributes delimited with semicolons (ex. {@code "key=value; Max-Age=123"}).
+     * For the header format and attributes supported by WebView, see the <a href=
+     * "https://developer.mozilla.org/docs/Web/HTTP/Headers/Set-Cookie">{@code Set-Cookie}
+     * documentation on MDN</a>.
      *
-     * <p class="note"><b>Note:</b> if specifying a {@code value} containing the {@code "Secure"}
-     * attribute, {@code url} must use the {@code "https://"} scheme.
+     * <p class="note">
+     * <b>Notes:</b>
+     * <ul>
+     * <li>If specifying a {@code value} containing the {@code "Secure"} attribute,
+     * {@code url} must use the {@code "https://"} scheme.</li>
+     * <li>if specifying a {@code value} containing the {@code "Partitioned"}
+     * attribute, the cookie will be set for the top-level partition of the
+     * {@code url}.</li>
+     * </ul>
      *
      * @param url the URL for which the cookie is to be set
      * @param value the cookie as a string, using the format of the 'Set-Cookie'
@@ -125,12 +132,12 @@
      * will be ignored if it is expired. To set multiple cookies, your application should invoke
      * this method multiple times.
      *
-     * <p>The {@code value} parameter must follow the format of the {@code Set-Cookie} HTTP
-     * response header defined by
-     * <a href="https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-03">RFC6265bis</a>.
-     * This is a key-value pair of the form {@code "key=value"}, optionally followed by a list of
-     * cookie attributes delimited with semicolons (ex. {@code "key=value; Max-Age=123"}). Please
-     * consult the RFC specification for a list of valid attributes.
+     * <p>The {@code value} parameter must follow the format of the {@code Set-Cookie} HTTP response
+     * header. This is a key-value pair of the form {@code "key=value"}, optionally followed by a
+     * list of cookie attributes delimited with semicolons (ex. {@code "key=value; Max-Age=123"}).
+     * For the header format and attributes supported by WebView, see the <a href=
+     * "https://developer.mozilla.org/docs/Web/HTTP/Headers/Set-Cookie">{@code Set-Cookie}
+     * documentation on MDN</a>.
      *
      * <p>This method is asynchronous. If a {@link ValueCallback} is provided,
      * {@link ValueCallback#onReceiveValue} will be called on the current
@@ -140,8 +147,15 @@
      * completes or whether it succeeded, and in this case it is safe to call the method from a
      * thread without a Looper.
      *
-     * <p class="note"><b>Note:</b> if specifying a {@code value} containing the {@code "Secure"}
-     * attribute, {@code url} must use the {@code "https://"} scheme.
+     * <p class="note">
+     * <b>Notes:</b>
+     * <ul>
+     * <li>If specifying a {@code value} containing the {@code "Secure"} attribute,
+     * {@code url} must use the {@code "https://"} scheme.</li>
+     * <li>if specifying a {@code value} containing the {@code "Partitioned"}
+     * attribute, the cookie will be set for the top-level partition of the
+     * {@code url}.</li>
+     * </ul>
      *
      * @param url the URL for which the cookie is to be set
      * @param value the cookie as a string, using the format of the 'Set-Cookie'
@@ -157,6 +171,10 @@
      * "; "} characters (semicolon followed by a space). Each key-value pair will be of the form
      * {@code "key=value"}.
      *
+     * <p class="note">
+     * <b>Note:</b> Any cookies set with the {@code "Partitioned"} attribute will only be returned
+     * for the top-level partition of {@code url}.
+     *
      * @param url the URL for which the cookies are requested
      * @return value the cookies as a string, using the format of the 'Cookie'
      *               HTTP request header
diff --git a/core/java/android/webkit/OWNERS b/core/java/android/webkit/OWNERS
index b33da57..e7fd7a5 100644
--- a/core/java/android/webkit/OWNERS
+++ b/core/java/android/webkit/OWNERS
@@ -1,4 +1,3 @@
-changwan@google.com
+# Bug component: 76427
 ntfschr@google.com
-tobiasjs@google.com
 torne@google.com
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index adeb889..6ad1960 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -4703,7 +4703,7 @@
 
     @Override
     public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) {
-        if (mFastScroll != null) {
+        if (mFastScroll != null && event.isFromSource(InputDevice.SOURCE_MOUSE)) {
             PointerIcon pointerIcon = mFastScroll.onResolvePointerIcon(event, pointerIndex);
             if (pointerIcon != null) {
                 return pointerIcon;
diff --git a/core/java/android/widget/Button.java b/core/java/android/widget/Button.java
index 634cbe3..405099d 100644
--- a/core/java/android/widget/Button.java
+++ b/core/java/android/widget/Button.java
@@ -18,6 +18,7 @@
 
 import android.content.Context;
 import android.util.AttributeSet;
+import android.view.InputDevice;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.PointerIcon;
@@ -173,7 +174,8 @@
 
     @Override
     public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) {
-        if (getPointerIcon() == null && isClickable() && isEnabled()) {
+        if (getPointerIcon() == null && isClickable() && isEnabled()
+                && event.isFromSource(InputDevice.SOURCE_MOUSE)) {
             return PointerIcon.getSystemIcon(getContext(), PointerIcon.TYPE_HAND);
         }
         return super.onResolvePointerIcon(event, pointerIndex);
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 2dbff58..58f6613 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -6454,7 +6454,7 @@
     @VisibleForTesting
     public int getCurrentLineAdjustedForSlop(Layout layout, int prevLine, float y) {
         final int trueLine = mTextView.getLineAtCoordinate(y);
-        if (layout == null || prevLine > layout.getLineCount()
+        if (layout == null || prevLine >= layout.getLineCount()
                 || layout.getLineCount() <= 0 || prevLine < 0) {
             // Invalid parameters, just return whatever line is at y.
             return trueLine;
@@ -8105,6 +8105,16 @@
         private final Paint mHighlightPaint;
         private final Path mHighlightPath;
 
+        /**
+         * Whether it is in the progress of updating transformation method. It's needed because
+         * {@link TextView#setTransformationMethod(TransformationMethod)} will eventually call
+         * {@link TextView#setText(CharSequence)}.
+         * Because it normally should exit insert mode when {@link TextView#setText(CharSequence)}
+         * is called externally, we need this boolean to distinguish whether setText is triggered
+         * by setTransformation or not.
+         */
+        private boolean mUpdatingTransformationMethod;
+
         InsertModeController(@NonNull TextView textView) {
             mTextView = Objects.requireNonNull(textView);
             mIsInsertModeActive = false;
@@ -8112,16 +8122,10 @@
             mHighlightPaint = new Paint();
             mHighlightPath = new Path();
 
-            // The highlight color is supposed to be 12% of the color primary40. We can't
-            // directly access Material 3 theme. But because Material 3 sets the colorPrimary to
-            // be primary40, here we hardcoded it to be 12% of colorPrimary.
-            final TypedValue typedValue = new TypedValue();
-            mTextView.getContext().getTheme()
-                    .resolveAttribute(R.attr.colorPrimary, typedValue, true);
-            final int colorPrimary = typedValue.data;
-            final int highlightColor = ColorUtils.setAlphaComponent(colorPrimary,
-                    (int) (0.12f * Color.alpha(colorPrimary)));
-            mHighlightPaint.setColor(highlightColor);
+            // Insert mode highlight color is 20% opacity of the default text color.
+            int color = mTextView.getTextColors().getDefaultColor();
+            color = ColorUtils.setAlphaComponent(color, (int) (0.2f * Color.alpha(color)));
+            mHighlightPaint.setColor(color);
         }
 
         /**
@@ -8143,7 +8147,7 @@
             final boolean isSingleLine = mTextView.isSingleLine();
             mInsertModeTransformationMethod = new InsertModeTransformationMethod(offset,
                     isSingleLine, oldTransformationMethod);
-            mTextView.setTransformationMethodInternal(mInsertModeTransformationMethod);
+            setTransformationMethod(mInsertModeTransformationMethod, true);
             Selection.setSelection((Spannable) mTextView.getText(), offset);
 
             mIsInsertModeActive = true;
@@ -8151,6 +8155,10 @@
         }
 
         void exitInsertMode() {
+            exitInsertMode(true);
+        }
+
+        void exitInsertMode(boolean updateText) {
             if (!mIsInsertModeActive) return;
             if (mInsertModeTransformationMethod == null
                     || mInsertModeTransformationMethod != mTextView.getTransformationMethod()) {
@@ -8163,7 +8171,7 @@
             final int selectionEnd = mTextView.getSelectionEnd();
             final TransformationMethod oldTransformationMethod =
                     mInsertModeTransformationMethod.getOldTransformationMethod();
-            mTextView.setTransformationMethodInternal(oldTransformationMethod);
+            setTransformationMethod(oldTransformationMethod, updateText);
             Selection.setSelection((Spannable) mTextView.getText(), selectionStart, selectionEnd);
             mIsInsertModeActive = false;
         }
@@ -8184,6 +8192,32 @@
         }
 
         /**
+         * Update the TransformationMethod on the {@link TextView}.
+         * @param method the new method to be set on the {@link TextView}/
+         * @param updateText whether to update the text during setTransformationMethod call.
+         */
+        private void setTransformationMethod(TransformationMethod method, boolean updateText) {
+            mUpdatingTransformationMethod = true;
+            mTextView.setTransformationMethodInternal(method, updateText);
+            mUpdatingTransformationMethod = false;
+        }
+
+        /**
+         * Notify the InsertMode controller that the {@link TextView} is about to set its text.
+         */
+        void beforeSetText() {
+            // TextView#setText is called because our call to
+            // TextView#setTransformationMethodInternal in enterInsertMode() or exitInsertMode().
+            // Do nothing in this case.
+            if (mUpdatingTransformationMethod) {
+                return;
+            }
+            // TextView#setText is called externally. Exit InsertMode but don't update text again
+            // when calling setTransformationMethod.
+            exitInsertMode(/* updateText */ false);
+        }
+
+        /**
          * Notify the {@link InsertModeController} before the TextView's
          * {@link TransformationMethod} is updated. If it's not in the insert mode,
          * the given method is directly returned. Otherwise, it will wrap the given transformation
@@ -8211,6 +8245,9 @@
         return mInsertModeController.enterInsertMode(offset);
     }
 
+    /**
+     * Exit insert mode if this editor is in insert mode.
+     */
     void exitInsertMode() {
         if (mInsertModeController == null) return;
         mInsertModeController.exitInsertMode();
@@ -8223,7 +8260,7 @@
      */
     void setTransformationMethod(TransformationMethod method) {
         if (mInsertModeController == null || !mInsertModeController.mIsInsertModeActive) {
-            mTextView.setTransformationMethodInternal(method);
+            mTextView.setTransformationMethodInternal(method, /* updateText */ true);
             return;
         }
 
@@ -8232,11 +8269,19 @@
         final int selectionStart = mTextView.getSelectionStart();
         final int selectionEnd = mTextView.getSelectionEnd();
         method = mInsertModeController.updateTransformationMethod(method);
-        mTextView.setTransformationMethodInternal(method);
+        mTextView.setTransformationMethodInternal(method, /* updateText */ true);
         Selection.setSelection((Spannable) mTextView.getText(), selectionStart, selectionEnd);
     }
 
     /**
+     * Notify that the Editor that the associated {@link TextView} is about to set its text.
+     */
+    void beforeSetText() {
+        if (mInsertModeController == null) return;
+        mInsertModeController.beforeSetText();
+    }
+
+    /**
      * Initializes the nodeInfo with smart actions.
      */
     void onInitializeSmartActionsAccessibilityNodeInfo(AccessibilityNodeInfo nodeInfo) {
diff --git a/core/java/android/widget/ExpandableListView.java b/core/java/android/widget/ExpandableListView.java
index efe3fd4..a67d839 100644
--- a/core/java/android/widget/ExpandableListView.java
+++ b/core/java/android/widget/ExpandableListView.java
@@ -31,6 +31,7 @@
 import android.view.ContextMenu.ContextMenuInfo;
 import android.view.SoundEffectConstants;
 import android.view.View;
+import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.ExpandableListConnector.PositionMetadata;
 
@@ -660,7 +661,11 @@
 
         // Internally handle the item click
         final int adjustedPosition = getFlatPositionForConnector(position);
-        return handleItemClick(v, adjustedPosition, id);
+        final boolean clicked = handleItemClick(v, adjustedPosition, id);
+        if (v != null) {
+            v.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
+        }
+        return clicked;
     }
 
     /**
@@ -1150,13 +1155,16 @@
     public void onInitializeAccessibilityNodeInfoForItem(
             View view, int position, AccessibilityNodeInfo info) {
         super.onInitializeAccessibilityNodeInfoForItem(view, position, info);
-
         final PositionMetadata metadata = mConnector.getUnflattenedPos(position);
         if (metadata.position.type == ExpandableListPosition.GROUP) {
-            if (isGroupExpanded(metadata.position.groupPos)) {
-                info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_COLLAPSE);
-            } else {
-                info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_EXPAND);
+            if (view != null && view.isEnabled()) {
+                info.setClickable(true);
+                info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK);
+                if (isGroupExpanded(metadata.position.groupPos)) {
+                    info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_COLLAPSE);
+                } else {
+                    info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_EXPAND);
+                }
             }
         }
 
diff --git a/core/java/android/widget/ImageButton.java b/core/java/android/widget/ImageButton.java
index e1b0c91..b6c5396 100644
--- a/core/java/android/widget/ImageButton.java
+++ b/core/java/android/widget/ImageButton.java
@@ -18,6 +18,7 @@
 
 import android.content.Context;
 import android.util.AttributeSet;
+import android.view.InputDevice;
 import android.view.MotionEvent;
 import android.view.PointerIcon;
 import android.widget.RemoteViews.RemoteView;
@@ -99,7 +100,8 @@
 
     @Override
     public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) {
-        if (getPointerIcon() == null && isClickable() && isEnabled()) {
+        if (getPointerIcon() == null && isClickable() && isEnabled()
+                && event.isFromSource(InputDevice.SOURCE_MOUSE)) {
             return PointerIcon.getSystemIcon(getContext(), PointerIcon.TYPE_HAND);
         }
         return super.onResolvePointerIcon(event, pointerIndex);
diff --git a/core/java/android/widget/RadialTimePickerView.java b/core/java/android/widget/RadialTimePickerView.java
index f3600b0..edf0f48 100644
--- a/core/java/android/widget/RadialTimePickerView.java
+++ b/core/java/android/widget/RadialTimePickerView.java
@@ -38,6 +38,7 @@
 import android.util.StateSet;
 import android.util.TypedValue;
 import android.view.HapticFeedbackConstants;
+import android.view.InputDevice;
 import android.view.MotionEvent;
 import android.view.PointerIcon;
 import android.view.View;
@@ -1060,9 +1061,11 @@
         if (!isEnabled()) {
             return null;
         }
-        final int degrees = getDegreesFromXY(event.getX(), event.getY(), false);
-        if (degrees != -1) {
-            return PointerIcon.getSystemIcon(getContext(), PointerIcon.TYPE_HAND);
+        if (event.isFromSource(InputDevice.SOURCE_MOUSE)) {
+            final int degrees = getDegreesFromXY(event.getX(), event.getY(), false);
+            if (degrees != -1) {
+                return PointerIcon.getSystemIcon(getContext(), PointerIcon.TYPE_HAND);
+            }
         }
         return super.onResolvePointerIcon(event, pointerIndex);
     }
diff --git a/core/java/android/widget/RemoteViews.aidl b/core/java/android/widget/RemoteViews.aidl
index ec86410..6a5fc03 100644
--- a/core/java/android/widget/RemoteViews.aidl
+++ b/core/java/android/widget/RemoteViews.aidl
@@ -17,3 +17,4 @@
 package android.widget;
 
 parcelable RemoteViews;
+parcelable RemoteViews.RemoteCollectionItems;
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 7f96266..66aa66c 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -33,16 +33,19 @@
 import android.app.Activity;
 import android.app.ActivityOptions;
 import android.app.ActivityThread;
+import android.app.AppGlobals;
 import android.app.Application;
 import android.app.LoadedApk;
 import android.app.PendingIntent;
 import android.app.RemoteInput;
 import android.appwidget.AppWidgetHostView;
 import android.compat.annotation.UnsupportedAppUsage;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.ContextWrapper;
 import android.content.Intent;
 import android.content.IntentSender;
+import android.content.ServiceConnection;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.res.ColorStateList;
@@ -65,10 +68,12 @@
 import android.os.Build;
 import android.os.Bundle;
 import android.os.CancellationSignal;
+import android.os.IBinder;
 import android.os.Parcel;
 import android.os.ParcelFileDescriptor;
 import android.os.Parcelable;
 import android.os.Process;
+import android.os.RemoteException;
 import android.os.StrictMode;
 import android.os.UserHandle;
 import android.system.Os;
@@ -98,8 +103,10 @@
 import android.widget.CompoundButton.OnCheckedChangeListener;
 
 import com.android.internal.R;
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
 import com.android.internal.util.ContrastColorUtil;
 import com.android.internal.util.Preconditions;
+import com.android.internal.widget.IRemoteViewsFactory;
 
 import java.io.ByteArrayOutputStream;
 import java.io.FileDescriptor;
@@ -124,7 +131,9 @@
 import java.util.Map;
 import java.util.Objects;
 import java.util.Stack;
+import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
 
@@ -232,6 +241,7 @@
     private static final int NIGHT_MODE_REFLECTION_ACTION_TAG = 30;
     private static final int SET_REMOTE_COLLECTION_ITEMS_ADAPTER_TAG = 31;
     private static final int ATTRIBUTE_REFLECTION_ACTION_TAG = 32;
+    private static final int SET_REMOTE_ADAPTER_TAG = 33;
 
     /** @hide **/
     @IntDef(prefix = "MARGIN_", value = {
@@ -323,6 +333,13 @@
             (clazz) -> clazz.isAnnotationPresent(RemoteViews.RemoteView.class);
 
     /**
+     * The maximum waiting time for remote adapter conversion in milliseconds
+     *
+     * @hide
+     */
+    private static final int MAX_ADAPTER_CONVERSION_WAITING_TIME_MS = 2000;
+
+    /**
      * Application that hosts the remote views.
      *
      * @hide
@@ -412,6 +429,13 @@
     /** Class cookies of the Parcel this instance was read from. */
     private Map<Class, Object> mClassCookies;
 
+    /**
+     * {@link LayoutInflater.Factory2} which will be passed into a {@link LayoutInflater} instance
+     * used by this class.
+     */
+    @Nullable
+    private LayoutInflater.Factory2 mLayoutInflaterFactory2;
+
     private static final InteractionHandler DEFAULT_INTERACTION_HANDLER =
             (view, pendingIntent, response) ->
                     startPendingIntent(view, pendingIntent, response.getLaunchOptions(view));
@@ -431,6 +455,29 @@
     }
 
     /**
+     * Sets {@link LayoutInflater.Factory2} to be passed into {@link LayoutInflater} used
+     * by this class instance. It has to be set before the views are inflated to have any effect.
+     *
+     * The factory callbacks will be called on the background thread so the implementation needs
+     * to be thread safe.
+     *
+     * @hide
+     */
+    public void setLayoutInflaterFactory(@Nullable LayoutInflater.Factory2 factory) {
+        mLayoutInflaterFactory2 = factory;
+    }
+
+    /**
+     * Returns currently set {@link LayoutInflater.Factory2}.
+     *
+     * @hide
+     */
+    @Nullable
+    public LayoutInflater.Factory2 getLayoutInflaterFactory() {
+        return mLayoutInflaterFactory2;
+    }
+
+    /**
      * Reduces all images and ensures that they are all below the given sizes.
      *
      * @param maxWidth the maximum width allowed
@@ -960,6 +1007,11 @@
             return SET_REMOTE_VIEW_ADAPTER_LIST_TAG;
         }
 
+        @Override
+        public String getUniqueKey() {
+            return (SET_REMOTE_ADAPTER_TAG + "_" + viewId);
+        }
+
         int viewTypeCount;
         ArrayList<RemoteViews> list;
     }
@@ -1006,28 +1058,96 @@
     }
 
     private class SetRemoteCollectionItemListAdapterAction extends Action {
-        private final RemoteCollectionItems mItems;
+        private @NonNull CompletableFuture<RemoteCollectionItems> mItemsFuture;
 
-        SetRemoteCollectionItemListAdapterAction(@IdRes int id, RemoteCollectionItems items) {
+        SetRemoteCollectionItemListAdapterAction(@IdRes int id,
+                @NonNull RemoteCollectionItems items) {
             viewId = id;
-            mItems = items;
-            mItems.setHierarchyRootData(getHierarchyRootData());
+            items.setHierarchyRootData(getHierarchyRootData());
+            mItemsFuture = CompletableFuture.completedFuture(items);
+        }
+
+        SetRemoteCollectionItemListAdapterAction(@IdRes int id, Intent intent) {
+            viewId = id;
+            mItemsFuture = getItemsFutureFromIntentWithTimeout(intent);
+            setHierarchyRootData(getHierarchyRootData());
+        }
+
+        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) {
             viewId = parcel.readInt();
-            mItems = new RemoteCollectionItems(parcel, getHierarchyRootData());
+            mItemsFuture = CompletableFuture.completedFuture(
+                    new RemoteCollectionItems(parcel, getHierarchyRootData()));
         }
 
         @Override
         public void setHierarchyRootData(HierarchyRootData rootData) {
-            mItems.setHierarchyRootData(rootData);
+            mItemsFuture = mItemsFuture
+                    .thenApply(rc -> {
+                        rc.setHierarchyRootData(rootData);
+                        return rc;
+                    });
+        }
+
+        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();
+            }
+
+            return items;
         }
 
         @Override
         public void writeToParcel(Parcel dest, int flags) {
             dest.writeInt(viewId);
-            mItems.writeToParcel(dest, flags, /* attached= */ true);
+            RemoteCollectionItems items = getCollectionItemsFromFuture(mItemsFuture);
+            items.writeToParcel(dest, flags, /* attached= */ true);
         }
 
         @Override
@@ -1036,6 +1156,8 @@
             View target = root.findViewById(viewId);
             if (target == null) return;
 
+            RemoteCollectionItems items = getCollectionItemsFromFuture(mItemsFuture);
+
             // Ensure that we are applying to an AppWidget root
             if (!(rootParent instanceof AppWidgetHostView)) {
                 Log.e(LOG_TAG, "setRemoteAdapter can only be used for "
@@ -1056,10 +1178,10 @@
             // recycling in setAdapter, so we must call setAdapter again if the number of view types
             // increases.
             if (adapter instanceof RemoteCollectionItemsAdapter
-                    && adapter.getViewTypeCount() >= mItems.getViewTypeCount()) {
+                    && adapter.getViewTypeCount() >= items.getViewTypeCount()) {
                 try {
                     ((RemoteCollectionItemsAdapter) adapter).setData(
-                            mItems, params.handler, params.colorResources);
+                            items, params.handler, params.colorResources);
                 } catch (Throwable throwable) {
                     // setData should never failed with the validation in the items builder, but if
                     // it does, catch and rethrow.
@@ -1069,7 +1191,7 @@
             }
 
             try {
-                adapterView.setAdapter(new RemoteCollectionItemsAdapter(mItems,
+                adapterView.setAdapter(new RemoteCollectionItemsAdapter(items,
                         params.handler, params.colorResources));
             } catch (Throwable throwable) {
                 // This could throw if the AdapterView somehow doesn't accept BaseAdapter due to
@@ -1082,6 +1204,11 @@
         public int getActionTag() {
             return SET_REMOTE_COLLECTION_ITEMS_ADAPTER_TAG;
         }
+
+        @Override
+        public String getUniqueKey() {
+            return (SET_REMOTE_ADAPTER_TAG + "_" + viewId);
+        }
     }
 
     private class SetRemoteViewsAdapterIntent extends Action {
@@ -4636,8 +4763,17 @@
      * @param viewId The id of the {@link AdapterView}
      * @param intent The intent of the service which will be
      *            providing data to the RemoteViewsAdapter
+     * @deprecated use
+     * {@link #setRemoteAdapter(int, android.widget.RemoteViews.RemoteCollectionItems)} instead
      */
+    @Deprecated
     public void setRemoteAdapter(@IdRes int viewId, Intent intent) {
+        if (AppGlobals.getIntCoreSetting(
+                SystemUiDeviceConfigFlags.REMOTEVIEWS_ADAPTER_CONVERSION,
+                SystemUiDeviceConfigFlags.REMOTEVIEWS_ADAPTER_CONVERSION_DEFAULT ? 1 : 0) == 1) {
+            addAction(new SetRemoteCollectionItemListAdapterAction(viewId, intent));
+            return;
+        }
         addAction(new SetRemoteViewsAdapterIntent(viewId, intent));
     }
 
@@ -5648,6 +5784,9 @@
         // we don't add a filter to the static version returned by getSystemService.
         inflater = inflater.cloneInContext(inflationContext);
         inflater.setFilter(shouldUseStaticFilter() ? INFLATER_FILTER : this);
+        if (mLayoutInflaterFactory2 != null) {
+            inflater.setFactory2(mLayoutInflaterFactory2);
+        }
         View v = inflater.inflate(rv.getLayoutId(), parent, false);
         if (mViewId != View.NO_ID) {
             v.setId(mViewId);
diff --git a/core/java/android/widget/RemoteViewsService.java b/core/java/android/widget/RemoteViewsService.java
index 214e5cc..d4f4d19 100644
--- a/core/java/android/widget/RemoteViewsService.java
+++ b/core/java/android/widget/RemoteViewsService.java
@@ -43,6 +43,13 @@
     private static final Object sLock = new Object();
 
     /**
+     * Used for determining the maximum number of entries to retrieve from RemoteViewsFactory
+     *
+     * @hide
+     */
+    private static final int MAX_NUM_ENTRY = 25;
+
+    /**
      * An interface for an adapter between a remote collection view (ListView, GridView, etc) and
      * the underlying data for that view.  The implementor is responsible for making a RemoteView
      * for each item in the data set. This interface is a thin wrapper around {@link Adapter}.
@@ -227,6 +234,30 @@
             }
         }
 
+        @Override
+        public RemoteViews.RemoteCollectionItems getRemoteCollectionItems() {
+            RemoteViews.RemoteCollectionItems items = new RemoteViews.RemoteCollectionItems
+                    .Builder().build();
+
+            try {
+                RemoteViews.RemoteCollectionItems.Builder itemsBuilder =
+                        new RemoteViews.RemoteCollectionItems.Builder();
+                mFactory.onDataSetChanged();
+
+                itemsBuilder.setHasStableIds(mFactory.hasStableIds());
+                final int numOfEntries = Math.min(mFactory.getCount(), MAX_NUM_ENTRY);
+                for (int i = 0; i < numOfEntries; i++) {
+                    itemsBuilder.addItem(mFactory.getItemId(i), mFactory.getViewAt(i));
+                }
+
+                items = itemsBuilder.build();
+            } catch (Exception ex) {
+                Thread t = Thread.currentThread();
+                Thread.getDefaultUncaughtExceptionHandler().uncaughtException(t, ex);
+            }
+            return items;
+        }
+
         private RemoteViewsFactory mFactory;
         private boolean mIsCreated;
     }
diff --git a/core/java/android/widget/SimpleMonthView.java b/core/java/android/widget/SimpleMonthView.java
index 6c53a44..1317b51 100644
--- a/core/java/android/widget/SimpleMonthView.java
+++ b/core/java/android/widget/SimpleMonthView.java
@@ -39,6 +39,7 @@
 import android.util.IntArray;
 import android.util.MathUtils;
 import android.util.StateSet;
+import android.view.InputDevice;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.PointerIcon;
@@ -1041,12 +1042,15 @@
         if (!isEnabled()) {
             return null;
         }
-        // Add 0.5f to event coordinates to match the logic in onTouchEvent.
-        final int x = (int) (event.getX() + 0.5f);
-        final int y = (int) (event.getY() + 0.5f);
-        final int dayUnderPointer = getDayAtLocation(x, y);
-        if (dayUnderPointer >= 0) {
-            return PointerIcon.getSystemIcon(getContext(), PointerIcon.TYPE_HAND);
+
+        if (event.isFromSource(InputDevice.SOURCE_MOUSE)) {
+            // Add 0.5f to event coordinates to match the logic in onTouchEvent.
+            final int x = (int) (event.getX() + 0.5f);
+            final int y = (int) (event.getY() + 0.5f);
+            final int dayUnderPointer = getDayAtLocation(x, y);
+            if (dayUnderPointer >= 0) {
+                return PointerIcon.getSystemIcon(getContext(), PointerIcon.TYPE_HAND);
+            }
         }
         return super.onResolvePointerIcon(event, pointerIndex);
     }
diff --git a/core/java/android/widget/Spinner.java b/core/java/android/widget/Spinner.java
index ad431ef..ecc41a5 100644
--- a/core/java/android/widget/Spinner.java
+++ b/core/java/android/widget/Spinner.java
@@ -38,6 +38,7 @@
 import android.util.Log;
 import android.view.ContextThemeWrapper;
 import android.view.Gravity;
+import android.view.InputDevice;
 import android.view.MotionEvent;
 import android.view.PointerIcon;
 import android.view.View;
@@ -935,7 +936,8 @@
 
     @Override
     public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) {
-        if (getPointerIcon() == null && isClickable() && isEnabled()) {
+        if (getPointerIcon() == null && isClickable() && isEnabled()
+                && event.isFromSource(InputDevice.SOURCE_MOUSE)) {
             return PointerIcon.getSystemIcon(getContext(), PointerIcon.TYPE_HAND);
         }
         return super.onResolvePointerIcon(event, pointerIndex);
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index db7d484..56349d1 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -156,7 +156,6 @@
 import android.util.ArraySet;
 import android.util.AttributeSet;
 import android.util.DisplayMetrics;
-import android.util.FeatureFlagUtils;
 import android.util.IntArray;
 import android.util.Log;
 import android.util.SparseIntArray;
@@ -831,11 +830,6 @@
     private int mLineBreakStyle = DEFAULT_LINE_BREAK_STYLE;
     private int mLineBreakWordStyle = DEFAULT_LINE_BREAK_WORD_STYLE;
 
-    // The auto option for LINE_BREAK_WORD_STYLE_PHRASE may not be applied in recycled view due to
-    // one-way flag flipping. This is a tentative limitation during experiment and will not have the
-    // issue once this is finalized to LINE_BREAK_WORD_STYLE_PHRASE_AUTO option.
-    private boolean mUserSpeficiedLineBreakwordStyle = false;
-
     // This is used to reflect the current user preference for changing font weight and making text
     // more bold.
     private int mFontWeightAdjustment;
@@ -1546,9 +1540,6 @@
                     break;
 
                 case com.android.internal.R.styleable.TextView_lineBreakWordStyle:
-                    if (a.hasValue(attr)) {
-                        mUserSpeficiedLineBreakwordStyle = true;
-                    }
                     mLineBreakWordStyle = a.getInt(attr,
                             LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE);
                     break;
@@ -2795,11 +2786,22 @@
         if (mEditor != null) {
             mEditor.setTransformationMethod(method);
         } else {
-            setTransformationMethodInternal(method);
+            setTransformationMethodInternal(method, /* updateText */ true);
         }
     }
 
-    void setTransformationMethodInternal(@Nullable TransformationMethod method) {
+    /**
+     * Set the transformation that is applied to the text that this TextView is displaying,
+     * optionally call the setText.
+     * @param method the new transformation method to be set.
+     * @param updateText whether the call {@link #setText} which will update the TextView to display
+     *                   the new content. This method is helpful when updating
+     *                   {@link TransformationMethod} inside {@link #setText}. It should only be
+     *                   false if text will be updated immediately after this call, otherwise the
+     *                   TextView will enter an inconsistent state.
+     */
+    void setTransformationMethodInternal(@Nullable TransformationMethod method,
+            boolean updateText) {
         if (method == mTransformation) {
             // Avoid the setText() below if the transformation is
             // the same.
@@ -2821,7 +2823,9 @@
             mAllowTransformationLengthChange = false;
         }
 
-        setText(mText);
+        if (updateText) {
+            setText(mText);
+        }
 
         if (hasPasswordTransformationMethod()) {
             notifyViewAccessibilityStateChangedIfNeeded(
@@ -4337,7 +4341,6 @@
                     break;
                 case com.android.internal.R.styleable.TextAppearance_lineBreakWordStyle:
                     attributes.mHasLineBreakWordStyle = true;
-                    mUserSpeficiedLineBreakwordStyle = true;
                     attributes.mLineBreakWordStyle =
                             appearance.getInt(attr, attributes.mLineBreakWordStyle);
                     break;
@@ -5073,7 +5076,6 @@
      * @param lineBreakWordStyle The line-break word style for the text.
      */
     public void setLineBreakWordStyle(@LineBreakConfig.LineBreakWordStyle int lineBreakWordStyle) {
-        mUserSpeficiedLineBreakwordStyle = true;
         if (mLineBreakWordStyle != lineBreakWordStyle) {
             mLineBreakWordStyle = lineBreakWordStyle;
             if (mLayout != null) {
@@ -5109,12 +5111,8 @@
      * @see PrecomputedText
      */
     public @NonNull PrecomputedText.Params getTextMetricsParams() {
-        final boolean autoPhraseBreaking =
-                !mUserSpeficiedLineBreakwordStyle && FeatureFlagUtils.isEnabled(mContext,
-                        FeatureFlagUtils.SETTINGS_AUTO_TEXT_WRAPPING);
         return new PrecomputedText.Params(new TextPaint(mTextPaint),
-                LineBreakConfig.getLineBreakConfig(mLineBreakStyle, mLineBreakWordStyle,
-                        autoPhraseBreaking),
+                LineBreakConfig.getLineBreakConfig(mLineBreakStyle, mLineBreakWordStyle),
                 getTextDirectionHeuristic(),
                 mBreakStrategy, mHyphenationFrequency);
     }
@@ -5132,9 +5130,8 @@
         mBreakStrategy = params.getBreakStrategy();
         mHyphenationFrequency = params.getHyphenationFrequency();
         LineBreakConfig lineBreakConfig = params.getLineBreakConfig();
-        mLineBreakStyle = lineBreakConfig.getLineBreakStyle();
-        mLineBreakWordStyle = lineBreakConfig.getLineBreakWordStyle();
-        mUserSpeficiedLineBreakwordStyle = true;
+        mLineBreakStyle = LineBreakConfig.getResolvedLineBreakStyle(lineBreakConfig);
+        mLineBreakWordStyle = LineBreakConfig.getResolvedLineBreakWordStyle(lineBreakConfig);
         if (mLayout != null) {
             nullLayouts();
             requestLayout();
@@ -6941,12 +6938,26 @@
      *                                  parameters used to create the PrecomputedText mismatches
      *                                  with this TextView.
      */
-    @android.view.RemotableViewMethod
+    @android.view.RemotableViewMethod(asyncImpl = "setTextAsync")
     public final void setText(CharSequence text) {
         setText(text, mBufferType);
     }
 
     /**
+     * RemotableViewMethod's asyncImpl of {@link #setText(CharSequence)}.
+     * This should be called on a background thread, and returns a Runnable which is then must be
+     * called on the main thread to complete the operation and set text.
+     * @param text text to be displayed
+     * @return Runnable that sets text; must be called on the main thread by the caller of this
+     * method to complete the operation
+     * @hide
+     */
+    @NonNull
+    public Runnable setTextAsync(@Nullable CharSequence text) {
+        return () -> setText(text);
+    }
+
+    /**
      * Sets the text to be displayed but retains the cursor position. Same as
      * {@link #setText(CharSequence)} except that the cursor position (if any) is retained in the
      * new text.
@@ -7000,6 +7011,9 @@
     @UnsupportedAppUsage
     private void setText(CharSequence text, BufferType type,
                          boolean notifyBefore, int oldlen) {
+        if (mEditor != null) {
+            mEditor.beforeSetText();
+        }
         mTextSetFromXmlOrResourceId = false;
         if (text == null) {
             text = "";
@@ -7061,13 +7075,10 @@
             if (mTextDir == null) {
                 mTextDir = getTextDirectionHeuristic();
             }
-            final boolean autoPhraseBreaking =
-                    !mUserSpeficiedLineBreakwordStyle && FeatureFlagUtils.isEnabled(mContext,
-                            FeatureFlagUtils.SETTINGS_AUTO_TEXT_WRAPPING);
             final @PrecomputedText.Params.CheckResultUsableResult int checkResult =
                     precomputed.getParams().checkResultUsable(getPaint(), mTextDir, mBreakStrategy,
                             mHyphenationFrequency, LineBreakConfig.getLineBreakConfig(
-                                    mLineBreakStyle, mLineBreakWordStyle, autoPhraseBreaking));
+                                    mLineBreakStyle, mLineBreakWordStyle));
             switch (checkResult) {
                 case PrecomputedText.Params.UNUSABLE:
                     throw new IllegalArgumentException(
@@ -9223,18 +9234,20 @@
 
     @Override
     public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) {
-        if (mSpannable != null && mLinksClickable) {
-            final float x = event.getX(pointerIndex);
-            final float y = event.getY(pointerIndex);
-            final int offset = getOffsetForPosition(x, y);
-            final ClickableSpan[] clickables = mSpannable.getSpans(offset, offset,
-                    ClickableSpan.class);
-            if (clickables.length > 0) {
-                return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_HAND);
+        if (event.isFromSource(InputDevice.SOURCE_MOUSE)) {
+            if (mSpannable != null && mLinksClickable) {
+                final float x = event.getX(pointerIndex);
+                final float y = event.getY(pointerIndex);
+                final int offset = getOffsetForPosition(x, y);
+                final ClickableSpan[] clickables = mSpannable.getSpans(offset, offset,
+                        ClickableSpan.class);
+                if (clickables.length > 0) {
+                    return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_HAND);
+                }
             }
-        }
-        if (isTextSelectable() || isTextEditable()) {
-            return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_TEXT);
+            if (isTextSelectable() || isTextEditable()) {
+                return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_TEXT);
+            }
         }
         return super.onResolvePointerIcon(event, pointerIndex);
     }
@@ -10622,9 +10635,6 @@
             }
             // TODO: code duplication with makeSingleLayout()
             if (mHintLayout == null) {
-                final boolean autoPhraseBreaking =
-                        !mUserSpeficiedLineBreakwordStyle && FeatureFlagUtils.isEnabled(mContext,
-                                FeatureFlagUtils.SETTINGS_AUTO_TEXT_WRAPPING);
                 StaticLayout.Builder builder = StaticLayout.Builder.obtain(mHint, 0,
                         mHint.length(), mTextPaint, hintWidth)
                         .setAlignment(alignment)
@@ -10637,7 +10647,7 @@
                         .setJustificationMode(mJustificationMode)
                         .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE)
                         .setLineBreakConfig(LineBreakConfig.getLineBreakConfig(
-                                mLineBreakStyle, mLineBreakWordStyle, autoPhraseBreaking));
+                                mLineBreakStyle, mLineBreakWordStyle));
                 if (shouldEllipsize) {
                     builder.setEllipsize(mEllipsize)
                             .setEllipsizedWidth(ellipsisWidth);
@@ -10697,6 +10707,8 @@
                     .setBreakStrategy(mBreakStrategy)
                     .setHyphenationFrequency(mHyphenationFrequency)
                     .setJustificationMode(mJustificationMode)
+                    .setLineBreakConfig(LineBreakConfig.getLineBreakConfig(
+                            mLineBreakStyle, mLineBreakWordStyle))
                     .setEllipsize(getKeyListener() == null ? effectiveEllipsize : null)
                     .setEllipsizedWidth(ellipsisWidth);
             result = builder.build();
@@ -10741,9 +10753,6 @@
             }
         }
         if (result == null) {
-            final boolean autoPhraseBreaking =
-                    !mUserSpeficiedLineBreakwordStyle && FeatureFlagUtils.isEnabled(mContext,
-                            FeatureFlagUtils.SETTINGS_AUTO_TEXT_WRAPPING);
             StaticLayout.Builder builder = StaticLayout.Builder.obtain(mTransformed,
                     0, mTransformed.length(), mTextPaint, wantWidth)
                     .setAlignment(alignment)
@@ -10756,7 +10765,7 @@
                     .setJustificationMode(mJustificationMode)
                     .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE)
                     .setLineBreakConfig(LineBreakConfig.getLineBreakConfig(
-                            mLineBreakStyle, mLineBreakWordStyle, autoPhraseBreaking));
+                            mLineBreakStyle, mLineBreakWordStyle));
             if (shouldEllipsize) {
                 builder.setEllipsize(effectiveEllipsize)
                         .setEllipsizedWidth(ellipsisWidth);
@@ -11114,9 +11123,6 @@
 
         final StaticLayout.Builder layoutBuilder = StaticLayout.Builder.obtain(
                 text, 0, text.length(),  mTempTextPaint, Math.round(availableSpace.right));
-        final boolean autoPhraseBreaking =
-                !mUserSpeficiedLineBreakwordStyle && FeatureFlagUtils.isEnabled(mContext,
-                        FeatureFlagUtils.SETTINGS_AUTO_TEXT_WRAPPING);
         layoutBuilder.setAlignment(getLayoutAlignment())
                 .setLineSpacing(getLineSpacingExtra(), getLineSpacingMultiplier())
                 .setIncludePad(getIncludeFontPadding())
@@ -11127,7 +11133,7 @@
                 .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE)
                 .setTextDirection(getTextDirectionHeuristic())
                 .setLineBreakConfig(LineBreakConfig.getLineBreakConfig(
-                        mLineBreakStyle, mLineBreakWordStyle, autoPhraseBreaking));
+                        mLineBreakStyle, mLineBreakWordStyle));
 
         final StaticLayout layout = layoutBuilder.build();
 
@@ -13809,13 +13815,14 @@
     }
 
     /**
-     * Helper method to set {@code rect} to the text content's non-clipped area in the view's
-     * coordinates.
+     * Helper method to set {@code rect} to this TextView's non-clipped area in its own coordinates.
+     * This method obtains the view's visible rectangle whereas the method
+     * {@link #getContentVisibleRect} returns the text layout's visible rectangle.
      *
      * @return true if at least part of the text content is visible; false if the text content is
      * completely clipped or translated out of the visible area.
      */
-    private boolean getContentVisibleRect(Rect rect) {
+    private boolean getViewVisibleRect(Rect rect) {
         if (!getLocalVisibleRect(rect)) {
             return false;
         }
@@ -13824,6 +13831,20 @@
         // view's coordinates. So we need to offset it with the negative scrolled amount to convert
         // it to view's coordinate.
         rect.offset(-getScrollX(), -getScrollY());
+        return true;
+    }
+
+    /**
+     * Helper method to set {@code rect} to the text content's non-clipped area in the view's
+     * coordinates.
+     *
+     * @return true if at least part of the text content is visible; false if the text content is
+     * completely clipped or translated out of the visible area.
+     */
+    private boolean getContentVisibleRect(Rect rect) {
+        if (!getViewVisibleRect(rect)) {
+            return false;
+        }
         // Clip the view's visible rect with the text layout's visible rect.
         return rect.intersect(getCompoundPaddingLeft(), getCompoundPaddingTop(),
                 getWidth() - getCompoundPaddingRight(), getHeight() - getCompoundPaddingBottom());
@@ -13953,14 +13974,25 @@
         builder.setMatrix(viewToScreenMatrix);
 
         if (includeEditorBounds) {
-            final RectF editorBounds = new RectF();
-            editorBounds.set(0 /* left */, 0 /* top */,
-                    getWidth(), getHeight());
-            final RectF handwritingBounds = new RectF(
-                    -getHandwritingBoundsOffsetLeft(),
-                    -getHandwritingBoundsOffsetTop(),
-                    getWidth() + getHandwritingBoundsOffsetRight(),
-                    getHeight() + getHandwritingBoundsOffsetBottom());
+            if (mTempRect == null) {
+                mTempRect = new Rect();
+            }
+            final Rect bounds = mTempRect;
+            final RectF editorBounds;
+            final RectF handwritingBounds;
+            if (getViewVisibleRect(bounds)) {
+                editorBounds = new RectF(bounds);
+                handwritingBounds = new RectF(editorBounds);
+                handwritingBounds.top -= getHandwritingBoundsOffsetTop();
+                handwritingBounds.left -= getHandwritingBoundsOffsetLeft();
+                handwritingBounds.bottom += getHandwritingBoundsOffsetBottom();
+                handwritingBounds.right += getHandwritingBoundsOffsetRight();
+            } else {
+                // The editor is not visible at all, return empty rectangles. We still need to
+                // return an EditorBoundsInfo because IME has subscribed the EditorBoundsInfo.
+                editorBounds = new RectF();
+                handwritingBounds = new RectF();
+            }
             EditorBoundsInfo.Builder boundsBuilder = new EditorBoundsInfo.Builder();
             EditorBoundsInfo editorBoundsInfo = boundsBuilder.setEditorBounds(editorBounds)
                     .setHandwritingBounds(handwritingBounds).build();
diff --git a/core/java/android/widget/Toast.java b/core/java/android/widget/Toast.java
index ee6ac12..65984f5 100644
--- a/core/java/android/widget/Toast.java
+++ b/core/java/android/widget/Toast.java
@@ -155,8 +155,7 @@
      * Construct an empty Toast object.  You must call {@link #setView} before you
      * can call {@link #show}.
      *
-     * @param context  The context to use.  Usually your {@link android.app.Application}
-     *                 or {@link android.app.Activity} object.
+     * @param context  The context to use.  Usually your {@link android.app.Activity} object.
      */
     public Toast(Context context) {
         this(context, null);
@@ -482,8 +481,7 @@
     /**
      * Make a standard toast that just contains text.
      *
-     * @param context  The context to use.  Usually your {@link android.app.Application}
-     *                 or {@link android.app.Activity} object.
+     * @param context  The context to use.  Usually your {@link android.app.Activity} object.
      * @param text     The text to show.  Can be formatted text.
      * @param duration How long to display the message.  Either {@link #LENGTH_SHORT} or
      *                 {@link #LENGTH_LONG}
@@ -539,8 +537,7 @@
     /**
      * Make a standard toast that just contains text from a resource.
      *
-     * @param context  The context to use.  Usually your {@link android.app.Application}
-     *                 or {@link android.app.Activity} object.
+     * @param context  The context to use.  Usually your {@link android.app.Activity} object.
      * @param resId    The resource id of the string resource to use.  Can be formatted text.
      * @param duration How long to display the message.  Either {@link #LENGTH_SHORT} or
      *                 {@link #LENGTH_LONG}
diff --git a/core/java/android/widget/inline/OWNERS b/core/java/android/widget/inline/OWNERS
index 9a30e82..73651da 100644
--- a/core/java/android/widget/inline/OWNERS
+++ b/core/java/android/widget/inline/OWNERS
@@ -1,3 +1,3 @@
-# Bug component: 351486
+# Bug component: 1195602
 
-include /core/java/android/view/autofill/OWNERS
+wangqi@google.com
diff --git a/core/java/android/window/TaskFragmentOperation.java b/core/java/android/window/TaskFragmentOperation.java
index 413f0cc..e153bb7 100644
--- a/core/java/android/window/TaskFragmentOperation.java
+++ b/core/java/android/window/TaskFragmentOperation.java
@@ -73,6 +73,13 @@
     /** Sets the relative bounds with {@link WindowContainerTransaction#setRelativeBounds}. */
     public static final int OP_TYPE_SET_RELATIVE_BOUNDS = 9;
 
+    /**
+     * Reorders the TaskFragment to be the front-most TaskFragment in the Task.
+     * Note that there could still have other WindowContainer on top of the front-most
+     * TaskFragment, such as a non-embedded Activity.
+     */
+    public static final int OP_TYPE_REORDER_TO_FRONT = 10;
+
     @IntDef(prefix = { "OP_TYPE_" }, value = {
             OP_TYPE_UNKNOWN,
             OP_TYPE_CREATE_TASK_FRAGMENT,
@@ -84,7 +91,8 @@
             OP_TYPE_REQUEST_FOCUS_ON_TASK_FRAGMENT,
             OP_TYPE_SET_COMPANION_TASK_FRAGMENT,
             OP_TYPE_SET_ANIMATION_PARAMS,
-            OP_TYPE_SET_RELATIVE_BOUNDS
+            OP_TYPE_SET_RELATIVE_BOUNDS,
+            OP_TYPE_REORDER_TO_FRONT
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface OperationType {}
diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java
index 9f804b1..3323ae5 100644
--- a/core/java/android/window/WindowContainerTransaction.java
+++ b/core/java/android/window/WindowContainerTransaction.java
@@ -688,6 +688,7 @@
                         .setInsetsFrameProvider(new InsetsFrameProvider(owner, index, type)
                                 .setSource(InsetsFrameProvider.SOURCE_ARBITRARY_RECTANGLE)
                                 .setArbitraryRectangle(frame))
+                        .setInsetsFrameOwner(owner)
                         .build();
         mHierarchyOps.add(hierarchyOp);
         return this;
@@ -712,6 +713,7 @@
                 new HierarchyOp.Builder(HierarchyOp.HIERARCHY_OP_TYPE_REMOVE_INSETS_FRAME_PROVIDER)
                         .setContainer(receiver.asBinder())
                         .setInsetsFrameProvider(new InsetsFrameProvider(owner, index, type))
+                        .setInsetsFrameOwner(owner)
                         .build();
         mHierarchyOps.add(hierarchyOp);
         return this;
@@ -1344,8 +1346,12 @@
         @Nullable
         private IBinder mReparent;
 
+        @Nullable
         private InsetsFrameProvider mInsetsFrameProvider;
 
+        @Nullable
+        private IBinder mInsetsFrameOwner;
+
         // Moves/reparents to top of parent when {@code true}, otherwise moves/reparents to bottom.
         private boolean mToTop;
 
@@ -1478,6 +1484,7 @@
             mContainer = copy.mContainer;
             mReparent = copy.mReparent;
             mInsetsFrameProvider = copy.mInsetsFrameProvider;
+            mInsetsFrameOwner = copy.mInsetsFrameOwner;
             mToTop = copy.mToTop;
             mReparentTopOnly = copy.mReparentTopOnly;
             mWindowingModes = copy.mWindowingModes;
@@ -1496,6 +1503,7 @@
             mContainer = in.readStrongBinder();
             mReparent = in.readStrongBinder();
             mInsetsFrameProvider = in.readTypedObject(InsetsFrameProvider.CREATOR);
+            mInsetsFrameOwner = in.readStrongBinder();
             mToTop = in.readBoolean();
             mReparentTopOnly = in.readBoolean();
             mWindowingModes = in.createIntArray();
@@ -1527,6 +1535,11 @@
             return mInsetsFrameProvider;
         }
 
+        @Nullable
+        public IBinder getInsetsFrameOwner() {
+            return mInsetsFrameOwner;
+        }
+
         @NonNull
         public IBinder getContainer() {
             return mContainer;
@@ -1657,7 +1670,8 @@
                 case HIERARCHY_OP_TYPE_ADD_INSETS_FRAME_PROVIDER:
                 case HIERARCHY_OP_TYPE_REMOVE_INSETS_FRAME_PROVIDER:
                     sb.append("container=").append(mContainer)
-                            .append(" provider=").append(mInsetsFrameProvider);
+                            .append(" provider=").append(mInsetsFrameProvider)
+                            .append(" owner=").append(mInsetsFrameOwner);
                     break;
                 case HIERARCHY_OP_TYPE_SET_ALWAYS_ON_TOP:
                     sb.append("container=").append(mContainer)
@@ -1697,6 +1711,7 @@
             dest.writeStrongBinder(mContainer);
             dest.writeStrongBinder(mReparent);
             dest.writeTypedObject(mInsetsFrameProvider, flags);
+            dest.writeStrongBinder(mInsetsFrameOwner);
             dest.writeBoolean(mToTop);
             dest.writeBoolean(mReparentTopOnly);
             dest.writeIntArray(mWindowingModes);
@@ -1737,8 +1752,12 @@
             @Nullable
             private IBinder mReparent;
 
+            @Nullable
             private InsetsFrameProvider mInsetsFrameProvider;
 
+            @Nullable
+            private IBinder mInsetsFrameOwner;
+
             private boolean mToTop;
 
             private boolean mReparentTopOnly;
@@ -1782,8 +1801,13 @@
                 return this;
             }
 
-            Builder setInsetsFrameProvider(InsetsFrameProvider providers) {
-                mInsetsFrameProvider = providers;
+            Builder setInsetsFrameProvider(InsetsFrameProvider provider) {
+                mInsetsFrameProvider = provider;
+                return this;
+            }
+
+            Builder setInsetsFrameOwner(IBinder owner) {
+                mInsetsFrameOwner = owner;
                 return this;
             }
 
@@ -1854,6 +1878,7 @@
                         ? Arrays.copyOf(mActivityTypes, mActivityTypes.length)
                         : null;
                 hierarchyOp.mInsetsFrameProvider = mInsetsFrameProvider;
+                hierarchyOp.mInsetsFrameOwner = mInsetsFrameOwner;
                 hierarchyOp.mToTop = mToTop;
                 hierarchyOp.mReparentTopOnly = mReparentTopOnly;
                 hierarchyOp.mLaunchOptions = mLaunchOptions;
diff --git a/core/java/android/window/WindowInfosListenerForTest.java b/core/java/android/window/WindowInfosListenerForTest.java
index 25bf85c..be88e53 100644
--- a/core/java/android/window/WindowInfosListenerForTest.java
+++ b/core/java/android/window/WindowInfosListenerForTest.java
@@ -20,6 +20,7 @@
 import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
 import android.annotation.TestApi;
+import android.graphics.Matrix;
 import android.graphics.Rect;
 import android.os.IBinder;
 import android.os.InputConfig;
@@ -78,14 +79,31 @@
          */
         public final boolean isVisible;
 
+        /**
+         * Return the transform to get the bounds from display space into window space.
+         */
+        @NonNull
+        public final Matrix transform;
+
         WindowInfo(@NonNull IBinder windowToken, @NonNull String name, int displayId,
-                @NonNull Rect bounds, int inputConfig) {
+                @NonNull Rect bounds, int inputConfig, @NonNull Matrix transform) {
             this.windowToken = windowToken;
             this.name = name;
             this.displayId = displayId;
             this.bounds = bounds;
             this.isTrustedOverlay = (inputConfig & InputConfig.TRUSTED_OVERLAY) != 0;
             this.isVisible = (inputConfig & InputConfig.NOT_VISIBLE) == 0;
+            this.transform = transform;
+        }
+
+        @Override
+        public String toString() {
+            return name + ", displayId=" + displayId
+                    + ", frame=" + bounds
+                    + ", isVisible=" + isVisible
+                    + ", isTrustedOverlay=" + isTrustedOverlay
+                    + ", token=" + windowToken
+                    + ", transform=" + transform;
         }
     }
 
@@ -146,7 +164,7 @@
             var bounds = new Rect(handle.frameLeft, handle.frameTop, handle.frameRight,
                     handle.frameBottom);
             windowInfos.add(new WindowInfo(handle.getWindowToken(), handle.name, handle.displayId,
-                    bounds, handle.inputConfig));
+                    bounds, handle.inputConfig, handle.transform));
         }
         return windowInfos;
     }
diff --git a/core/java/android/window/WindowMetricsController.java b/core/java/android/window/WindowMetricsController.java
index 954f686..2858f0a 100644
--- a/core/java/android/window/WindowMetricsController.java
+++ b/core/java/android/window/WindowMetricsController.java
@@ -145,13 +145,13 @@
         for (int i = 0; i < possibleDisplayInfos.size(); i++) {
             currentDisplayInfo = possibleDisplayInfos.get(i);
 
-            // Calculate max bounds for this rotation and state.
-            Rect maxBounds = new Rect(0, 0, currentDisplayInfo.logicalWidth,
-                    currentDisplayInfo.logicalHeight);
+            // Calculate max bounds for natural rotation and state.
+            Rect maxBounds = new Rect(0, 0, currentDisplayInfo.getNaturalWidth(),
+                    currentDisplayInfo.getNaturalHeight());
 
-            // Calculate insets for the rotated max bounds.
+            // Calculate insets for the natural max bounds.
             final boolean isScreenRound = (currentDisplayInfo.flags & Display.FLAG_ROUND) != 0;
-            // Initialize insets based upon display rotation. Note any window-provided insets
+            // Initialize insets based on Surface.ROTATION_0. Note any window-provided insets
             // will not be set.
             windowInsets = getWindowInsetsFromServerForDisplay(
                     currentDisplayInfo.displayId, null /* token */,
diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java
index 632208c..849e0b3 100644
--- a/core/java/android/window/WindowOnBackInvokedDispatcher.java
+++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java
@@ -23,6 +23,7 @@
 import android.content.ContextWrapper;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
+import android.content.res.TypedArray;
 import android.os.Handler;
 import android.os.RemoteException;
 import android.os.SystemProperties;
@@ -33,6 +34,7 @@
 
 import androidx.annotation.VisibleForTesting;
 
+
 import java.io.PrintWriter;
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
@@ -62,6 +64,9 @@
             .getInt("persist.wm.debug.predictive_back", 1) != 0;
     private static final boolean ALWAYS_ENFORCE_PREDICTIVE_BACK = SystemProperties
             .getInt("persist.wm.debug.predictive_back_always_enforce", 0) != 0;
+    private static final boolean PREDICTIVE_BACK_FALLBACK_WINDOW_ATTRIBUTE =
+            SystemProperties.getInt("persist.wm.debug.predictive_back_fallback_window_attribute", 0)
+                    != 0;
     @Nullable
     private ImeOnBackInvokedDispatcher mImeDispatcher;
 
@@ -500,6 +505,31 @@
                             applicationInfo.packageName,
                             requestsPredictiveBack));
                 }
+
+                if (PREDICTIVE_BACK_FALLBACK_WINDOW_ATTRIBUTE && !requestsPredictiveBack) {
+                    // Compatibility check for legacy window style flag used by Wear OS.
+                    // Note on compatibility behavior:
+                    // 1. windowSwipeToDismiss should be respected for all apps not opted in.
+                    // 2. windowSwipeToDismiss should be true for all apps not opted in, which
+                    //    enables the PB animation for them.
+                    // 3. windowSwipeToDismiss=false should be respected for apps not opted in,
+                    //    which disables PB & onBackPressed caused by BackAnimController's
+                    //    setTrigger(true)
+                    TypedArray windowAttr =
+                            context.obtainStyledAttributes(
+                                    new int[] {android.R.attr.windowSwipeToDismiss});
+                    boolean windowSwipeToDismiss = true;
+                    if (windowAttr.getIndexCount() > 0) {
+                        windowSwipeToDismiss = windowAttr.getBoolean(0, true);
+                    }
+                    windowAttr.recycle();
+
+                    if (DEBUG) {
+                        Log.i(TAG, "falling back to windowSwipeToDismiss: " + windowSwipeToDismiss);
+                    }
+
+                    requestsPredictiveBack = windowSwipeToDismiss;
+                }
             }
 
             return requestsPredictiveBack;
diff --git a/core/java/android/window/WindowProviderService.java b/core/java/android/window/WindowProviderService.java
index f2ae973..611da3c 100644
--- a/core/java/android/window/WindowProviderService.java
+++ b/core/java/android/window/WindowProviderService.java
@@ -34,6 +34,7 @@
 import android.hardware.display.DisplayManager;
 import android.os.Bundle;
 import android.os.IBinder;
+import android.util.Log;
 import android.view.Display;
 import android.view.WindowManager;
 import android.view.WindowManager.LayoutParams.WindowType;
@@ -52,6 +53,8 @@
 @UiContext
 public abstract class WindowProviderService extends Service implements WindowProvider {
 
+    private static final String TAG = WindowProviderService.class.getSimpleName();
+
     private final Bundle mOptions;
     private final WindowTokenClient mWindowToken = new WindowTokenClient();
     private final WindowContextController mController = new WindowContextController(mWindowToken);
@@ -194,8 +197,16 @@
     public final Context createServiceBaseContext(ActivityThread mainThread,
             LoadedApk packageInfo) {
         final Context context = super.createServiceBaseContext(mainThread, packageInfo);
-        final Display display = context.getSystemService(DisplayManager.class)
-                .getDisplay(getInitialDisplayId());
+        final DisplayManager displayManager = context.getSystemService(DisplayManager.class);
+        final int initialDisplayId = getInitialDisplayId();
+        Display display = displayManager.getDisplay(initialDisplayId);
+        // Fallback to use the default display if the initial display to start WindowProviderService
+        // is detached.
+        if (display == null) {
+            Log.e(TAG, "Display with id " + initialDisplayId + " not found, falling back to "
+                    + "DEFAULT_DISPLAY");
+            display = displayManager.getDisplay(DEFAULT_DISPLAY);
+        }
         return context.createTokenContext(mWindowToken, display);
     }
 
diff --git a/core/java/com/android/internal/accessibility/TEST_MAPPING b/core/java/com/android/internal/accessibility/TEST_MAPPING
new file mode 100644
index 0000000..1c67399
--- /dev/null
+++ b/core/java/com/android/internal/accessibility/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "imports": [
+    {
+      "path": "frameworks/base/services/accessibility/TEST_MAPPING"
+    }
+  ]
+}
diff --git a/core/java/com/android/internal/accessibility/common/MagnificationConstants.java b/core/java/com/android/internal/accessibility/common/MagnificationConstants.java
index 94c230b..2c49303 100644
--- a/core/java/com/android/internal/accessibility/common/MagnificationConstants.java
+++ b/core/java/com/android/internal/accessibility/common/MagnificationConstants.java
@@ -17,7 +17,7 @@
 package com.android.internal.accessibility.common;
 
 /**
- * Collection of common constants for accessibility shortcut.
+ * Collection of common constants for accessibility magnification.
  */
 public final class MagnificationConstants {
     private MagnificationConstants() {}
@@ -27,4 +27,10 @@
      * the min value, there will be no obvious magnification effect.
      */
     public static final float PERSISTED_SCALE_MIN_VALUE = 1.3f;
+
+    /** Minimum supported value for magnification scale. */
+    public static final float SCALE_MIN_VALUE = 1.0f;
+
+    /** Maximum supported value for magnification scale. */
+    public static final float SCALE_MAX_VALUE = 8.0f;
 }
diff --git a/core/java/com/android/internal/app/AssistUtils.java b/core/java/com/android/internal/app/AssistUtils.java
index 57cc38c..0ea8014 100644
--- a/core/java/com/android/internal/app/AssistUtils.java
+++ b/core/java/com/android/internal/app/AssistUtils.java
@@ -59,6 +59,8 @@
     public static final int INVOCATION_TYPE_POWER_BUTTON_LONG_PRESS = 6;
     /** value for INVOCATION_TYPE_KEY: press on physcial assistant button */
     public static final int INVOCATION_TYPE_ASSIST_BUTTON = 7;
+    /** value for INVOCATION_TYPE_KEY: long press on nav handle */
+    public static final int INVOCATION_TYPE_NAV_HANDLE_LONG_PRESS = 8;
 
     private final Context mContext;
     private final IVoiceInteractionManagerService mVoiceInteractionManagerService;
diff --git a/core/java/com/android/internal/app/BlockedAppStreamingActivity.java b/core/java/com/android/internal/app/BlockedAppStreamingActivity.java
index ec2d2ef..980b92c 100644
--- a/core/java/com/android/internal/app/BlockedAppStreamingActivity.java
+++ b/core/java/com/android/internal/app/BlockedAppStreamingActivity.java
@@ -63,7 +63,7 @@
                 mAlertParams.mTitle =
                         getString(R.string.app_streaming_blocked_title_for_permission_dialog);
                 mAlertParams.mMessage =
-                        getString(R.string.app_streaming_blocked_message, streamedDeviceName);
+                        getString(R.string.app_streaming_blocked_message_for_permission_request);
             } else if (TextUtils.equals(activityInfo.packageName, BLOCKED_COMPONENT_PLAYSTORE)) {
                 mAlertParams.mTitle =
                         getString(R.string.app_streaming_blocked_title_for_playstore_dialog);
diff --git a/core/java/com/android/internal/app/IAppOpsService.aidl b/core/java/com/android/internal/app/IAppOpsService.aidl
index ff3c015..2dcac82 100644
--- a/core/java/com/android/internal/app/IAppOpsService.aidl
+++ b/core/java/com/android/internal/app/IAppOpsService.aidl
@@ -83,12 +83,19 @@
     void getHistoricalOpsFromDiskRaw(int uid, String packageName, String attributionTag,
             in List<String> ops, int historyFlags, int filter, long beginTimeMillis,
             long endTimeMillis, int flags, in RemoteCallback callback);
+    @EnforcePermission("MANAGE_APPOPS")
     void offsetHistory(long duration);
+    @EnforcePermission("MANAGE_APPOPS")
     void setHistoryParameters(int mode, long baseSnapshotInterval, int compressionStep);
+    @EnforcePermission("MANAGE_APPOPS")
     void addHistoricalOps(in AppOpsManager.HistoricalOps ops);
+    @EnforcePermission("MANAGE_APPOPS")
     void resetHistoryParameters();
+    @EnforcePermission("MANAGE_APPOPS")
     void resetPackageOpsNoHistory(String packageName);
+    @EnforcePermission("MANAGE_APPOPS")
     void clearHistory();
+    @EnforcePermission("MANAGE_APPOPS")
     void rebootHistory(long offlineDurationMillis);
     List<AppOpsManager.PackageOps> getUidOps(int uid, in int[] ops);
     void setUidMode(int code, int uid, int mode);
diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
index dd52de4..a2c4b23 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
@@ -532,6 +532,17 @@
     public static final String TASK_MANAGER_SHOW_FOOTER_DOT = "task_manager_show_footer_dot";
 
     /**
+     * (boolean) Whether to enable the adapter conversion in RemoteViews
+     */
+    public static final String REMOTEVIEWS_ADAPTER_CONVERSION = "remoteviews_adapter_conversion";
+
+    /**
+     * Default value for whether the adapter conversion is enabled or not. This is set for
+     * RemoteViews and should not be a common practice.
+     */
+    public static final boolean REMOTEVIEWS_ADAPTER_CONVERSION_DEFAULT = false;
+
+    /**
      * (boolean) Whether the task manager should show a stop button if the app is allowlisted
      * by the user.
      */
@@ -552,12 +563,6 @@
             "task_manager_inform_job_scheduler_of_pending_app_stop";
 
     /**
-     * (boolean) Whether widget provider info would be saved to / loaded from system persistence
-     * layer as opposed to individual manifests in respective apps.
-     */
-    public static final String PERSISTS_WIDGET_PROVIDER_INFO = "persists_widget_provider_info";
-
-    /**
      * (boolean) Whether to show smart chips (based on TextClassifier) in the clipboard overlay.
      */
     public static final String CLIPBOARD_OVERLAY_SHOW_ACTIONS = "clipboard_overlay_show_actions";
@@ -574,6 +579,11 @@
      */
     public static final String COMBINED_BROADCAST_ENABLED = "combined_broadcast_enabled";
 
+    /**
+     * (boolean) Whether to allow cursor hover states for certain elements.
+     */
+    public static final String CURSOR_HOVER_STATES_ENABLED = "cursor_hover_states_enabled";
+
     private SystemUiDeviceConfigFlags() {
     }
 }
diff --git a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
index c9e7600..81cd280 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
@@ -84,7 +84,14 @@
 
         /** Gating the holding of WakeLocks until NLSes are told about a new notification. */
         public static final Flag WAKE_LOCK_FOR_POSTING_NOTIFICATION =
-                devFlag("persist.sysui.notification.wake_lock_for_posting_notification");
+                releasedFlag("persist.sysui.notification.wake_lock_for_posting_notification");
+
+        /** Gating storing NotificationRankingUpdate ranking map in shared memory. */
+        public static final Flag RANKING_UPDATE_ASHMEM = devFlag(
+                "persist.sysui.notification.ranking_update_ashmem");
+
+        public static final Flag PROPAGATE_CHANNEL_UPDATES_TO_CONVERSATIONS = devFlag(
+                "persist.sysui.notification.propagate_channel_updates_to_conversations");
     }
 
     //// == End of flags.  Everything below this line is the implementation. == ////
diff --git a/core/java/com/android/internal/content/OWNERS b/core/java/com/android/internal/content/OWNERS
index c42bee6..c8e4005 100644
--- a/core/java/com/android/internal/content/OWNERS
+++ b/core/java/com/android/internal/content/OWNERS
@@ -1,5 +1,7 @@
 # Bug component: 36137
 include /core/java/android/content/pm/OWNERS
 
+per-file FileSystemProvider.java = file:/core/java/android/os/storage/OWNERS
+
 per-file ReferrerIntent.aidl = file:/services/core/java/com/android/server/am/OWNERS
 per-file ReferrerIntent.java = file:/services/core/java/com/android/server/am/OWNERS
diff --git a/core/java/com/android/internal/content/PackageMonitor.java b/core/java/com/android/internal/content/PackageMonitor.java
index 4cbf475..0d1871d 100644
--- a/core/java/com/android/internal/content/PackageMonitor.java
+++ b/core/java/com/android/internal/content/PackageMonitor.java
@@ -22,16 +22,23 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.PackageManager;
 import android.net.Uri;
+import android.os.Bundle;
 import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.os.IRemoteCallback;
 import android.os.Looper;
+import android.os.RemoteException;
 import android.os.UserHandle;
+import android.util.Log;
 import android.util.Slog;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.BackgroundThread;
 
-import java.util.HashSet;
 import java.util.Objects;
+import java.util.concurrent.Executor;
 
 /**
  * Helper class for monitoring the state of packages: adding, removing,
@@ -42,10 +49,7 @@
 
     final IntentFilter mPackageFilt;
     final IntentFilter mNonDataFilt;
-    final IntentFilter mExternalFilt;
 
-    final HashSet<String> mUpdatingPackages = new HashSet<String>();
-    
     Context mRegisteredContext;
     Handler mRegisteredHandler;
     String[] mDisappearingPackages;
@@ -58,15 +62,16 @@
 
     String[] mTempArray = new String[1];
 
+    PackageMonitorCallback mPackageMonitorCallback;
+
     @UnsupportedAppUsage
     public PackageMonitor() {
         final boolean isCore = UserHandle.isCore(android.os.Process.myUid());
 
         mPackageFilt = new IntentFilter();
-        mPackageFilt.addAction(Intent.ACTION_PACKAGE_ADDED);
-        mPackageFilt.addAction(Intent.ACTION_PACKAGE_REMOVED);
-        mPackageFilt.addAction(Intent.ACTION_PACKAGE_CHANGED);
+        // Settings app sends the broadcast
         mPackageFilt.addAction(Intent.ACTION_QUERY_PACKAGE_RESTART);
+        // AMS sends the broadcast
         mPackageFilt.addAction(Intent.ACTION_PACKAGE_RESTARTED);
         mPackageFilt.addAction(Intent.ACTION_PACKAGE_DATA_CLEARED);
         mPackageFilt.addDataScheme("package");
@@ -75,20 +80,11 @@
         }
 
         mNonDataFilt = new IntentFilter();
-        mNonDataFilt.addAction(Intent.ACTION_UID_REMOVED);
+        // UserController sends the broadcast
         mNonDataFilt.addAction(Intent.ACTION_USER_STOPPED);
-        mNonDataFilt.addAction(Intent.ACTION_PACKAGES_SUSPENDED);
-        mNonDataFilt.addAction(Intent.ACTION_PACKAGES_UNSUSPENDED);
         if (isCore) {
             mNonDataFilt.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
         }
-
-        mExternalFilt = new IntentFilter();
-        mExternalFilt.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
-        mExternalFilt.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
-        if (isCore) {
-            mExternalFilt.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
-        }
     }
 
     @UnsupportedAppUsage
@@ -99,12 +95,15 @@
     @UnsupportedAppUsage
     public void register(Context context, Looper thread, UserHandle user,
             boolean externalStorage) {
-        register(context, user, externalStorage,
+        register(context, user,
                 (thread == null) ? BackgroundThread.getHandler() : new Handler(thread));
     }
 
-    public void register(Context context, UserHandle user,
-        boolean externalStorage, Handler handler) {
+
+    /**
+     * Register for notifications of package changes such as install, removal and other events.
+     */
+    public void register(Context context, UserHandle user, Handler handler) {
         if (mRegisteredContext != null) {
             throw new IllegalStateException("Already registered");
         }
@@ -113,15 +112,17 @@
         if (user != null) {
             context.registerReceiverAsUser(this, user, mPackageFilt, null, mRegisteredHandler);
             context.registerReceiverAsUser(this, user, mNonDataFilt, null, mRegisteredHandler);
-            if (externalStorage) {
-                context.registerReceiverAsUser(this, user, mExternalFilt, null,
-                        mRegisteredHandler);
-            }
         } else {
             context.registerReceiver(this, mPackageFilt, null, mRegisteredHandler);
             context.registerReceiver(this, mNonDataFilt, null, mRegisteredHandler);
-            if (externalStorage) {
-                context.registerReceiver(this, mExternalFilt, null, mRegisteredHandler);
+        }
+        if (mPackageMonitorCallback == null) {
+            PackageManager pm = mRegisteredContext.getPackageManager();
+            if (pm != null) {
+                mPackageMonitorCallback = new PackageMonitorCallback(this,
+                        new HandlerExecutor(mRegisteredHandler));
+                int userId = user != null ? user.getIdentifier() : mRegisteredContext.getUserId();
+                pm.registerPackageMonitorCallback(mPackageMonitorCallback, userId);
             }
         }
     }
@@ -136,16 +137,15 @@
             throw new IllegalStateException("Not registered");
         }
         mRegisteredContext.unregisterReceiver(this);
+
+        PackageManager pm = mRegisteredContext.getPackageManager();
+        if (pm != null && mPackageMonitorCallback != null) {
+            pm.unregisterPackageMonitorCallback(mPackageMonitorCallback);
+        }
+        mPackageMonitorCallback = null;
         mRegisteredContext = null;
     }
-    
-    //not yet implemented
-    boolean isPackageUpdating(String packageName) {
-        synchronized (mUpdatingPackages) {
-            return mUpdatingPackages.contains(packageName);
-        }
-    }
-    
+
     public void onBeginPackageChanges() {
     }
 
@@ -337,21 +337,30 @@
         String pkg = uri != null ? uri.getSchemeSpecificPart() : null;
         return pkg;
     }
-    
+
     @Override
     public void onReceive(Context context, Intent intent) {
+        doHandlePackageEvent(intent);
+    }
+
+    /**
+     * Handle the package related event
+     * @param intent the intent that contains package related event information
+     */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    public void doHandlePackageEvent(Intent intent) {
         mChangeUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE,
                 UserHandle.USER_NULL);
         if (mChangeUserId == UserHandle.USER_NULL) {
-            Slog.w("PackageMonitor", "Intent broadcast does not contain user handle: " + intent);
+            Slog.w(TAG, "Intent broadcast does not contain user handle: " + intent);
             return;
         }
         onBeginPackageChanges();
-        
+
         mDisappearingPackages = mAppearingPackages = null;
         mSomePackagesChanged = false;
         mModifiedComponents = null;
-        
+
         String action = intent.getAction();
         if (Intent.ACTION_PACKAGE_ADDED.equals(action)) {
             String pkg = getPackageName(intent);
@@ -373,11 +382,6 @@
                     onPackageAdded(pkg, uid);
                 }
                 onPackageAppeared(pkg, mChangeType);
-                if (mChangeType == PACKAGE_UPDATING) {
-                    synchronized (mUpdatingPackages) {
-                        mUpdatingPackages.remove(pkg);
-                    }
-                }
             }
         } else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
             String pkg = getPackageName(intent);
@@ -387,10 +391,6 @@
                 mTempArray[0] = pkg;
                 if (intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
                     mChangeType = PACKAGE_UPDATING;
-                    synchronized (mUpdatingPackages) {
-                        //not used for now
-                        //mUpdatingPackages.add(pkg);
-                    }
                     onPackageUpdateStarted(pkg, uid);
                 } else {
                     mChangeType = PACKAGE_PERMANENT_CHANGE;
@@ -484,4 +484,30 @@
         onFinishPackageChanges();
         mChangeUserId = UserHandle.USER_NULL;
     }
+
+    private static final class PackageMonitorCallback extends IRemoteCallback.Stub {
+
+        private final PackageMonitor mPackageMonitor;
+        private final Executor mExecutor;
+
+        PackageMonitorCallback(PackageMonitor monitor, Executor executor) {
+            mPackageMonitor = monitor;
+            mExecutor = executor;
+        }
+
+        @Override
+        public void sendResult(Bundle data) throws RemoteException {
+            onHandlePackageMonitorCallback(data);
+        }
+
+        private void onHandlePackageMonitorCallback(Bundle bundle) {
+            Intent intent = bundle.getParcelable(
+                    PackageManager.EXTRA_PACKAGE_MONITOR_CALLBACK_RESULT, Intent.class);
+            if (intent == null) {
+                Log.w(TAG, "No intent is set for PackageMonitorCallback");
+                return;
+            }
+            mExecutor.execute(() -> mPackageMonitor.doHandlePackageEvent(intent));
+        }
+    }
 }
diff --git a/core/java/com/android/internal/content/om/OverlayConfig.java b/core/java/com/android/internal/content/om/OverlayConfig.java
index 2d04bdb..07e178c 100644
--- a/core/java/com/android/internal/content/om/OverlayConfig.java
+++ b/core/java/com/android/internal/content/om/OverlayConfig.java
@@ -34,8 +34,15 @@
 import com.android.internal.util.Preconditions;
 import com.android.internal.util.function.TriConsumer;
 
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.SAXException;
+
 import java.io.File;
 import java.io.FileInputStream;
+import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -46,6 +53,10 @@
 import java.util.Map;
 import java.util.function.Supplier;
 
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+
 /**
  * Responsible for reading overlay configuration files and handling queries of overlay mutability,
  * default-enabled state, and priority.
@@ -61,6 +72,8 @@
     @VisibleForTesting
     public static final int DEFAULT_PRIORITY = Integer.MAX_VALUE;
 
+    public static final String PARTITION_ORDER_FILE_PATH = "/product/overlay/partition_order.xml";
+
     @VisibleForTesting
     public static final class Configuration {
         @Nullable
@@ -119,6 +132,10 @@
     // Singleton instance only assigned in system server
     private static OverlayConfig sInstance;
 
+    private final String mPartitionOrder;
+
+    private final boolean mIsDefaultPartitionOrder;
+
     @VisibleForTesting
     public OverlayConfig(@Nullable File rootDirectory,
             @Nullable Supplier<OverlayScanner> scannerFactory,
@@ -137,6 +154,8 @@
                             new File(rootDirectory, p.getNonConicalFolder().getPath()),
                             p)));
         }
+        mIsDefaultPartitionOrder = !sortPartitions(PARTITION_ORDER_FILE_PATH, partitions);
+        mPartitionOrder = generatePartitionOrderString(partitions);
 
         ArrayMap<Integer, List<String>> activeApexesPerPartition = getActiveApexes(partitions);
 
@@ -198,6 +217,96 @@
         }
     }
 
+    private static String generatePartitionOrderString(List<OverlayPartition> partitions) {
+        if (partitions == null || partitions.size() == 0) {
+            return "";
+        }
+        StringBuilder partitionOrder = new StringBuilder();
+        partitionOrder.append(partitions.get(0).getName());
+        for (int i = 1; i < partitions.size(); i++) {
+            partitionOrder.append(", ").append(partitions.get(i).getName());
+        }
+        return partitionOrder.toString();
+    }
+
+    private static boolean parseAndValidatePartitionsOrderXml(String partitionOrderFilePath,
+            Map<String, Integer> orderMap, List<OverlayPartition> partitions) {
+        try {
+            File file = new File(partitionOrderFilePath);
+            if (!file.exists()) {
+                Log.w(TAG, "partition_order.xml does not exist.");
+                return false;
+            }
+            var dbFactory = DocumentBuilderFactory.newInstance();
+            DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
+            Document doc = dBuilder.parse(file);
+            doc.getDocumentElement().normalize();
+
+            Element root = doc.getDocumentElement();
+            if (!root.getNodeName().equals("partition-order")) {
+                Log.w(TAG, "Invalid partition_order.xml, "
+                        + "xml root element is not partition-order");
+                return false;
+            }
+
+            NodeList partitionList = doc.getElementsByTagName("partition");
+            for (int order = 0; order < partitionList.getLength(); order++) {
+                Node partitionNode = partitionList.item(order);
+                if (partitionNode.getNodeType() == Node.ELEMENT_NODE) {
+                    Element partitionElement = (Element) partitionNode;
+                    String partitionName = partitionElement.getAttribute("name");
+                    if (orderMap.containsKey(partitionName)) {
+                        Log.w(TAG, "Invalid partition_order.xml, "
+                                + "it has duplicate partition: " + partitionName);
+                        return false;
+                    }
+                    orderMap.put(partitionName, order);
+                }
+            }
+
+            if (orderMap.keySet().size() != partitions.size()) {
+                Log.w(TAG, "Invalid partition_order.xml, partition_order.xml has "
+                        + orderMap.keySet().size() + " partitions, "
+                        + "which is different from SYSTEM_PARTITIONS");
+                return false;
+            }
+            for (int i = 0; i < partitions.size(); i++) {
+                if (!orderMap.keySet().contains(partitions.get(i).getName())) {
+                    Log.w(TAG, "Invalid Parsing partition_order.xml, "
+                            + "partition_order.xml does not have partition: "
+                            + partitions.get(i).getName());
+                    return false;
+                }
+            }
+        } catch (ParserConfigurationException | SAXException | IOException e) {
+            Log.w(TAG, "Parsing or validating partition_order.xml failed, "
+                    + "exception thrown: " + e.getMessage());
+            return false;
+        }
+        Log.i(TAG, "Sorting partitions in the specified order from partitions_order.xml");
+        return true;
+    }
+
+    /**
+     * Sort partitions by order in partition_order.xml if the file exists.
+     *
+     * @hide
+     */
+    @VisibleForTesting
+    public static boolean sortPartitions(String partitionOrderFilePath,
+            List<OverlayPartition> partitions) {
+        Map<String, Integer> orderMap = new HashMap<>();
+        if (!parseAndValidatePartitionsOrderXml(partitionOrderFilePath, orderMap, partitions)) {
+            return false;
+        }
+
+        Comparator<OverlayPartition> partitionComparator = Comparator.comparingInt(
+                o -> orderMap.get(o.getName()));
+        Collections.sort(partitions, partitionComparator);
+
+        return true;
+    }
+
     /**
      * Creates an instance of OverlayConfig for use in the zygote process.
      * This instance will not include information of static overlays existing outside of a partition
@@ -476,4 +585,19 @@
      */
     private static native String[] createIdmap(@NonNull String targetPath,
             @NonNull String[] overlayPath, @NonNull String[] policies, boolean enforceOverlayable);
+
+    /**
+     * @hide
+     */
+    public boolean isDefaultPartitionOrder() {
+        return mIsDefaultPartitionOrder;
+    }
+
+    /**
+     * @hide
+     */
+    public String getPartitionOrder() {
+        return mPartitionOrder;
+    }
+
 }
diff --git a/core/java/com/android/internal/content/om/OverlayConfigParser.java b/core/java/com/android/internal/content/om/OverlayConfigParser.java
index 5ab77d8..faaf7d5 100644
--- a/core/java/com/android/internal/content/om/OverlayConfigParser.java
+++ b/core/java/com/android/internal/content/om/OverlayConfigParser.java
@@ -28,6 +28,7 @@
 import android.util.Log;
 import android.util.Xml;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.content.om.OverlayScanner.ParsedOverlayInfo;
 import com.android.internal.util.Preconditions;
 import com.android.internal.util.XmlUtils;
@@ -54,8 +55,11 @@
  *
  * @see #parseOverlay(File, XmlPullParser, OverlayScanner, ParsingContext)
  * @see #parseMerge(File, XmlPullParser, OverlayScanner, ParsingContext)
+ *
+ * @hide
  **/
-final class OverlayConfigParser {
+@VisibleForTesting
+public final class OverlayConfigParser {
 
     /** Represents a part of a parsed overlay configuration XML file. */
     public static class ParsedConfigFile {
@@ -160,7 +164,11 @@
         }
     }
 
-    static class OverlayPartition extends SystemPartition {
+    /**
+     * @hide
+     **/
+    @VisibleForTesting
+    public static class OverlayPartition extends SystemPartition {
         // Policies passed to idmap2 during idmap creation.
         // Keep partition policy constants in sync with f/b/cmds/idmap2/include/idmap2/Policies.h.
         static final String POLICY_ODM = "odm";
@@ -173,7 +181,11 @@
         @NonNull
         public final String policy;
 
-        OverlayPartition(@NonNull SystemPartition partition) {
+        /**
+         * @hide
+         **/
+        @VisibleForTesting
+        public OverlayPartition(@NonNull SystemPartition partition) {
             super(partition);
             this.policy = policyForPartition(partition);
         }
diff --git a/core/java/com/android/internal/flags/CoreFlags.java b/core/java/com/android/internal/flags/CoreFlags.java
new file mode 100644
index 0000000..f177ef8
--- /dev/null
+++ b/core/java/com/android/internal/flags/CoreFlags.java
@@ -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.internal.flags;
+
+import android.flags.BooleanFlag;
+import android.flags.DynamicBooleanFlag;
+import android.flags.FeatureFlags;
+import android.flags.FusedOffFlag;
+import android.flags.FusedOnFlag;
+import android.flags.SyncableFlag;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Flags defined here are can be read by code in core.
+ *
+ * Flags not defined here will throw a security exception if third-party processes attempts to read
+ * them.
+ *
+ * DO NOT define a flag here unless you explicitly intend for that flag to be readable by code that
+ * runs inside a third party process.
+ */
+public abstract class CoreFlags {
+    private static final List<SyncableFlag> sKnownFlags = new ArrayList<>();
+
+    public static BooleanFlag BOOL_FLAG = booleanFlag("core", "bool_flag", false);
+    public static FusedOffFlag OFF_FLAG = fusedOffFlag("core", "off_flag");
+    public static FusedOnFlag ON_FLAG = fusedOnFlag("core", "on_flag");
+    public static DynamicBooleanFlag DYN_FLAG = dynamicBooleanFlag("core", "dyn_flag", true);
+
+    /** Returns true if the passed in flag matches a flag in this class. */
+    public static boolean isCoreFlag(SyncableFlag flag) {
+        for (SyncableFlag knownFlag : sKnownFlags) {
+            if (knownFlag.getName().equals(flag.getName())
+                    && knownFlag.getNamespace().equals(flag.getNamespace())) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public static List<SyncableFlag> getCoreFlags() {
+        return sKnownFlags;
+    }
+
+    private static BooleanFlag booleanFlag(String namespace, String name, boolean defaultValue) {
+        BooleanFlag f = FeatureFlags.booleanFlag(namespace, name, defaultValue);
+
+        sKnownFlags.add(new SyncableFlag(namespace, name, Boolean.toString(defaultValue), false));
+
+        return f;
+    }
+
+    private static FusedOffFlag fusedOffFlag(String namespace, String name) {
+        FusedOffFlag f = FeatureFlags.fusedOffFlag(namespace, name);
+
+        sKnownFlags.add(new SyncableFlag(namespace, name, "false", false));
+
+        return f;
+    }
+
+    private static FusedOnFlag fusedOnFlag(String namespace, String name) {
+        FusedOnFlag f = FeatureFlags.fusedOnFlag(namespace, name);
+
+        sKnownFlags.add(new SyncableFlag(namespace, name, "true", false));
+
+        return f;
+    }
+
+    private static DynamicBooleanFlag dynamicBooleanFlag(
+            String namespace, String name, boolean defaultValue) {
+        DynamicBooleanFlag f = FeatureFlags.dynamicBooleanFlag(namespace, name, defaultValue);
+
+        sKnownFlags.add(new SyncableFlag(namespace, name, Boolean.toString(defaultValue), true));
+
+        return f;
+    }
+}
diff --git a/core/java/com/android/internal/infra/ServiceConnector.java b/core/java/com/android/internal/infra/ServiceConnector.java
index cb16267..6489c8e 100644
--- a/core/java/com/android/internal/infra/ServiceConnector.java
+++ b/core/java/com/android/internal/infra/ServiceConnector.java
@@ -745,6 +745,10 @@
             boolean mAsync = false;
             private String mDebugName;
             {
+                // The timeout handler must be set before any calls to set timeouts on the
+                // AndroidFuture, to ensure they are posted on the proper thread.
+                setTimeoutHandler(getJobHandler());
+
                 long requestTimeout = getRequestTimeoutMs();
                 if (requestTimeout > 0) {
                     orTimeout(requestTimeout, TimeUnit.MILLISECONDS);
diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java
index e530aec..03d7450 100644
--- a/core/java/com/android/internal/jank/InteractionJankMonitor.java
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -25,9 +25,11 @@
 import static com.android.internal.jank.FrameTracker.REASON_END_NORMAL;
 import static com.android.internal.jank.FrameTracker.REASON_END_UNKNOWN;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__BIOMETRIC_PROMPT_TRANSITION;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__IME_INSETS_ANIMATION;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__IME_INSETS_HIDE_ANIMATION;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__IME_INSETS_SHOW_ANIMATION;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_ALL_APPS_SCROLL;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_HOME;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_PIP;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_LAUNCH_FROM_ICON;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_LAUNCH_FROM_RECENTS;
@@ -68,6 +70,7 @@
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_CLEAR_ALL;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_DIALOG_OPEN;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_EXPAND_FROM_STATUS_BAR;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_HEADS_UP_APPEAR;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_HEADS_UP_DISAPPEAR;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_NOTIFICATION_ADD;
@@ -255,11 +258,21 @@
     public static final int CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS = 66;
     public static final int CUJ_LAUNCHER_CLOSE_ALL_APPS_SWIPE = 67;
     public static final int CUJ_LAUNCHER_CLOSE_ALL_APPS_TO_HOME = 68;
-    public static final int CUJ_IME_INSETS_ANIMATION = 69;
     public static final int CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION = 70;
     public static final int CUJ_LAUNCHER_OPEN_SEARCH_RESULT = 71;
+    // 72 - 77 are reserved for b/281564325.
 
-    private static final int LAST_CUJ = CUJ_LAUNCHER_OPEN_SEARCH_RESULT;
+    /**
+     * In some cases when we do not have any end-target, we play a simple slide-down animation.
+     * eg: Open an app from Overview/Task switcher such that there is no home-screen icon.
+     * eg: Exit the app using back gesture.
+     */
+    public static final int CUJ_LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK = 78;
+    public static final int CUJ_SHADE_EXPAND_FROM_STATUS_BAR = 79;
+    public static final int CUJ_IME_INSETS_SHOW_ANIMATION = 80;
+    public static final int CUJ_IME_INSETS_HIDE_ANIMATION = 81;
+
+    private static final int LAST_CUJ = CUJ_IME_INSETS_HIDE_ANIMATION;
     private static final int NO_STATSD_LOGGING = -1;
 
     // Used to convert CujType to InteractionType enum value for statsd logging.
@@ -337,9 +350,20 @@
         CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_SWIPE_TO_RECENTS;
         CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_CLOSE_ALL_APPS_SWIPE] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_CLOSE_ALL_APPS_SWIPE;
         CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_CLOSE_ALL_APPS_TO_HOME] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_CLOSE_ALL_APPS_TO_HOME;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_IME_INSETS_ANIMATION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__IME_INSETS_ANIMATION;
+        CUJ_TO_STATSD_INTERACTION_TYPE[69] = NO_STATSD_LOGGING; // This is deprecated.
         CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_CLOCK_MOVE_ANIMATION;
         CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_OPEN_SEARCH_RESULT] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_OPEN_SEARCH_RESULT;
+        // 72 - 77 are reserved for b/281564325.
+        CUJ_TO_STATSD_INTERACTION_TYPE[72] = NO_STATSD_LOGGING;
+        CUJ_TO_STATSD_INTERACTION_TYPE[73] = NO_STATSD_LOGGING;
+        CUJ_TO_STATSD_INTERACTION_TYPE[74] = NO_STATSD_LOGGING;
+        CUJ_TO_STATSD_INTERACTION_TYPE[75] = NO_STATSD_LOGGING;
+        CUJ_TO_STATSD_INTERACTION_TYPE[76] = NO_STATSD_LOGGING;
+        CUJ_TO_STATSD_INTERACTION_TYPE[77] = NO_STATSD_LOGGING;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SHADE_EXPAND_FROM_STATUS_BAR] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_EXPAND_FROM_STATUS_BAR;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_IME_INSETS_SHOW_ANIMATION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__IME_INSETS_SHOW_ANIMATION;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_IME_INSETS_HIDE_ANIMATION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__IME_INSETS_HIDE_ANIMATION;
     }
 
     private static class InstanceHolder {
@@ -436,9 +460,12 @@
             CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS,
             CUJ_LAUNCHER_CLOSE_ALL_APPS_SWIPE,
             CUJ_LAUNCHER_CLOSE_ALL_APPS_TO_HOME,
-            CUJ_IME_INSETS_ANIMATION,
             CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION,
             CUJ_LAUNCHER_OPEN_SEARCH_RESULT,
+            CUJ_LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK,
+            CUJ_SHADE_EXPAND_FROM_STATUS_BAR,
+            CUJ_IME_INSETS_SHOW_ANIMATION,
+            CUJ_IME_INSETS_HIDE_ANIMATION,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface CujType {
@@ -1044,12 +1071,18 @@
                 return "LAUNCHER_CLOSE_ALL_APPS_SWIPE";
             case CUJ_LAUNCHER_CLOSE_ALL_APPS_TO_HOME:
                 return "LAUNCHER_CLOSE_ALL_APPS_TO_HOME";
-            case CUJ_IME_INSETS_ANIMATION:
-                return "IME_INSETS_ANIMATION";
             case CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION:
                 return "LOCKSCREEN_CLOCK_MOVE_ANIMATION";
             case CUJ_LAUNCHER_OPEN_SEARCH_RESULT:
                 return "LAUNCHER_OPEN_SEARCH_RESULT";
+            case CUJ_LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK:
+                return "LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK";
+            case CUJ_SHADE_EXPAND_FROM_STATUS_BAR:
+                return "SHADE_EXPAND_FROM_STATUS_BAR";
+            case CUJ_IME_INSETS_SHOW_ANIMATION:
+                return "IME_INSETS_SHOW_ANIMATION";
+            case CUJ_IME_INSETS_HIDE_ANIMATION:
+                return "IME_INSETS_HIDE_ANIMATION";
         }
         return "UNKNOWN";
     }
diff --git a/core/java/com/android/internal/jank/OWNERS b/core/java/com/android/internal/jank/OWNERS
index 352c132..2f3bbee 100644
--- a/core/java/com/android/internal/jank/OWNERS
+++ b/core/java/com/android/internal/jank/OWNERS
@@ -3,4 +3,5 @@
 # Jank people
 ahanwu@google.com
 vadimt@google.com
-marcinoc@google.com
\ No newline at end of file
+marcinoc@google.com
+pmuetschard@google.com
\ No newline at end of file
diff --git a/core/java/com/android/internal/os/BatteryStatsHistory.java b/core/java/com/android/internal/os/BatteryStatsHistory.java
index bbaaa47..0a9bab5 100644
--- a/core/java/com/android/internal/os/BatteryStatsHistory.java
+++ b/core/java/com/android/internal/os/BatteryStatsHistory.java
@@ -129,6 +129,13 @@
     static final int EXTENSION_CPU_USAGE_HEADER_FLAG = 0x00000004;
     static final int EXTENSION_CPU_USAGE_FLAG = 0x00000008;
 
+    // For state1, trace everything except the wakelock bit (which can race with
+    // suspend) and the running bit (which isn't meaningful in traces).
+    static final int STATE1_TRACE_MASK = ~(HistoryItem.STATE_WAKE_LOCK_FLAG
+                                          | HistoryItem.STATE_CPU_RUNNING_FLAG);
+    // For state2, trace all bit changes.
+    static final int STATE2_TRACE_MASK = ~0;
+
     private final Parcel mHistoryBuffer;
     private final File mSystemDir;
     private final HistoryStepDetailsCalculator mStepDetailsCalculator;
@@ -1270,8 +1277,9 @@
     /**
      * Writes changes to a HistoryItem state bitmap to Atrace.
      */
-    private void recordTraceCounters(int oldval, int newval, BitDescription[] descriptions) {
-        int diff = oldval ^ newval;
+    private void recordTraceCounters(int oldval, int newval, int mask,
+            BitDescription[] descriptions) {
+        int diff = (oldval ^ newval) & mask;
         if (diff == 0) return;
 
         for (int i = 0; i < descriptions.length; i++) {
@@ -1284,7 +1292,6 @@
             } else {
                 value = (newval & bd.mask) >> bd.shift;
             }
-
             mTracer.traceCounter("battery_stats." + bd.name, value);
         }
     }
@@ -1325,9 +1332,9 @@
     private void writeHistoryItem(long elapsedRealtimeMs, long uptimeMs, HistoryItem cur) {
         if (mTracer != null && mTracer.tracingEnabled()) {
             recordTraceEvents(cur.eventCode, cur.eventTag);
-            recordTraceCounters(mTraceLastState, cur.states,
+            recordTraceCounters(mTraceLastState, cur.states, STATE1_TRACE_MASK,
                     BatteryStats.HISTORY_STATE_DESCRIPTIONS);
-            recordTraceCounters(mTraceLastState2, cur.states2,
+            recordTraceCounters(mTraceLastState2, cur.states2, STATE2_TRACE_MASK,
                     BatteryStats.HISTORY_STATE2_DESCRIPTIONS);
             mTraceLastState = cur.states;
             mTraceLastState2 = cur.states2;
@@ -1545,7 +1552,7 @@
 
         State2 change int: if C in the first token is set,
         31              23              15               7             0
-        █M|L|K|J|I|H|H|G█F|E|D|C| | | | █ | | | | | | | █ |B|B|B|A|A|A|A█
+        █M|L|K|J|I|H|H|G█F|E|D|C| | | | █ | | | | | | |N█N|B|B|B|A|A|A|A█
 
         A: 4 bits indicating the wifi supplicant state: {@link BatteryStats#WIFI_SUPPL_STATE_NAMES}.
         B: 3 bits indicating the wifi signal strength: 0, 1, 2, 3, 4.
@@ -1560,6 +1567,7 @@
         K: wifi was running.
         L: video was playing.
         M: power save mode was on.
+        N: 2 bits indicating the gps signal strength: poor, good, none.
 
         Wakelock/wakereason struct: if D in the first token is set,
         Event struct: if E in the first token is set,
diff --git a/core/java/com/android/internal/os/CpuScalingPolicies.java b/core/java/com/android/internal/os/CpuScalingPolicies.java
new file mode 100644
index 0000000..6dbe8ab
--- /dev/null
+++ b/core/java/com/android/internal/os/CpuScalingPolicies.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.os;
+
+import android.annotation.NonNull;
+import android.util.SparseArray;
+
+import libcore.util.EmptyArray;
+
+import java.util.Arrays;
+
+/**
+ * CPU scaling policies: the policy IDs and corresponding supported scaling for those
+ * policies.
+ */
+public class CpuScalingPolicies {
+    private final SparseArray<int[]> mCpusByPolicy;
+    private final SparseArray<int[]> mFreqsByPolicy;
+    private final int[] mPolicies;
+    private final int mScalingStepCount;
+
+    public CpuScalingPolicies(@NonNull SparseArray<int[]> cpusByPolicy,
+            @NonNull SparseArray<int[]> freqsByPolicy) {
+        mCpusByPolicy = cpusByPolicy;
+        mFreqsByPolicy = freqsByPolicy;
+
+        mPolicies = new int[cpusByPolicy.size()];
+        for (int i = 0; i < mPolicies.length; i++) {
+            mPolicies[i] = cpusByPolicy.keyAt(i);
+        }
+
+        Arrays.sort(mPolicies);
+
+        int count = 0;
+        for (int i = freqsByPolicy.size() - 1; i >= 0; i--) {
+            count += freqsByPolicy.valueAt(i).length;
+        }
+        mScalingStepCount = count;
+    }
+
+    /**
+     * Returns available policies (aka clusters).
+     */
+    @NonNull
+    public int[] getPolicies() {
+        return mPolicies;
+    }
+
+    /**
+     * CPUs covered by the specified policy.
+     */
+    @NonNull
+    public int[] getRelatedCpus(int policy) {
+        return mCpusByPolicy.get(policy, EmptyArray.INT);
+    }
+
+    /**
+     * Scaling frequencies supported for the specified policy.
+     */
+    @NonNull
+    public int[] getFrequencies(int policy) {
+        return mFreqsByPolicy.get(policy, EmptyArray.INT);
+    }
+
+    /**
+     * Returns the overall number of supported scaling steps: grand total of available frequencies
+     * across all scaling policies.
+     */
+    public int getScalingStepCount() {
+        return mScalingStepCount;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        for (int policy : mPolicies) {
+            sb.append("policy").append(policy)
+                    .append("\n CPUs: ").append(Arrays.toString(mCpusByPolicy.get(policy)))
+                    .append("\n freqs: ").append(Arrays.toString(mFreqsByPolicy.get(policy)))
+                    .append("\n");
+        }
+        return sb.toString();
+    }
+}
diff --git a/core/java/com/android/internal/os/CpuScalingPolicyReader.java b/core/java/com/android/internal/os/CpuScalingPolicyReader.java
new file mode 100644
index 0000000..c96089a
--- /dev/null
+++ b/core/java/com/android/internal/os/CpuScalingPolicyReader.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.os;
+
+import android.annotation.NonNull;
+import android.os.FileUtils;
+import android.util.IntArray;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import libcore.util.EmptyArray;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Captures a CPU scaling policies such as available scaling frequencies as well as
+ * CPUs (cores) for each policy.
+ *
+ * See <a
+ * href="https://www.kernel.org/doc/html/latest/admin-guide/pm/cpufreq.html
+ * #policy-interface-in-sysfs">Policy Interface in sysfs</a>
+ */
+public class CpuScalingPolicyReader {
+    private static final String TAG = "CpuScalingPolicyReader";
+    private static final String CPUFREQ_DIR = "/sys/devices/system/cpu/cpufreq";
+    private static final Pattern POLICY_PATTERN = Pattern.compile("policy(\\d+)");
+    private static final String FILE_NAME_RELATED_CPUS = "related_cpus";
+    private static final String FILE_NAME_SCALING_AVAILABLE_FREQUENCIES =
+            "scaling_available_frequencies";
+    private static final String FILE_NAME_SCALING_BOOST_FREQUENCIES = "scaling_boost_frequencies";
+    private static final String FILE_NAME_CPUINFO_CUR_FREQ = "cpuinfo_cur_freq";
+
+    private final String mCpuFreqDir;
+
+    public CpuScalingPolicyReader() {
+        this(CPUFREQ_DIR);
+    }
+
+    @VisibleForTesting
+    public CpuScalingPolicyReader(String cpuFreqDir) {
+        mCpuFreqDir = cpuFreqDir;
+    }
+
+    /**
+     * Reads scaling policy info from sysfs files in /sys/devices/system/cpu/cpufreq
+     */
+    @NonNull
+    public CpuScalingPolicies read() {
+        SparseArray<int[]> cpusByPolicy = new SparseArray<>();
+        SparseArray<int[]> freqsByPolicy = new SparseArray<>();
+
+        File cpuFreqDir = new File(mCpuFreqDir);
+        File[] policyDirs = cpuFreqDir.listFiles();
+        if (policyDirs != null) {
+            for (File policyDir : policyDirs) {
+                Matcher matcher = POLICY_PATTERN.matcher(policyDir.getName());
+                if (matcher.matches()) {
+                    int[] relatedCpus = readIntsFromFile(
+                            new File(policyDir, FILE_NAME_RELATED_CPUS));
+                    if (relatedCpus.length == 0) {
+                        continue;
+                    }
+
+                    int[] availableFreqs = readIntsFromFile(
+                            new File(policyDir, FILE_NAME_SCALING_AVAILABLE_FREQUENCIES));
+                    int[] boostFreqs = readIntsFromFile(
+                            new File(policyDir, FILE_NAME_SCALING_BOOST_FREQUENCIES));
+                    int[] freqs;
+                    if (boostFreqs.length == 0) {
+                        freqs = availableFreqs;
+                    } else {
+                        freqs = Arrays.copyOf(availableFreqs,
+                                availableFreqs.length + boostFreqs.length);
+                        System.arraycopy(boostFreqs, 0, freqs, availableFreqs.length,
+                                boostFreqs.length);
+                    }
+                    if (freqs.length == 0) {
+                        freqs = readIntsFromFile(new File(policyDir, FILE_NAME_CPUINFO_CUR_FREQ));
+                        if (freqs.length == 0) {
+                            freqs = new int[]{0};  // Unknown frequency
+                        }
+                    }
+                    int policy = Integer.parseInt(matcher.group(1));
+                    cpusByPolicy.put(policy, relatedCpus);
+                    freqsByPolicy.put(policy, freqs);
+                }
+            }
+        }
+
+        if (cpusByPolicy.size() == 0) {
+            // There just has to be at least one CPU - otherwise, what's executing this code?
+            cpusByPolicy.put(0, new int[]{0});
+            freqsByPolicy.put(0, new int[]{0});
+        }
+
+        return new CpuScalingPolicies(cpusByPolicy, freqsByPolicy);
+    }
+
+    @NonNull
+    private static int[] readIntsFromFile(File file) {
+        if (!file.exists()) {
+            return EmptyArray.INT;
+        }
+
+        IntArray intArray = new IntArray(16);
+        try {
+            String contents = FileUtils.readTextFile(file, 0, null).trim();
+            String[] strings = contents.split(" ");
+            intArray.clear();
+            for (String s : strings) {
+                try {
+                    intArray.add(Integer.parseInt(s));
+                } catch (NumberFormatException e) {
+                    Slog.e(TAG, "Unexpected file format " + file
+                            + ": " + contents, e);
+                }
+            }
+            return intArray.toArray();
+        } catch (IOException e) {
+            Slog.e(TAG, "Cannot read " + file, e);
+            return EmptyArray.INT;
+        }
+    }
+}
diff --git a/core/java/com/android/internal/os/KernelCpuUidTimeReader.java b/core/java/com/android/internal/os/KernelCpuUidTimeReader.java
index c801be0..21e0dc5 100644
--- a/core/java/com/android/internal/os/KernelCpuUidTimeReader.java
+++ b/core/java/com/android/internal/os/KernelCpuUidTimeReader.java
@@ -16,9 +16,7 @@
 package com.android.internal.os;
 
 import static com.android.internal.os.KernelCpuProcStringReader.asLongs;
-import static com.android.internal.util.Preconditions.checkNotNull;
 
-import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.os.StrictMode;
 import android.util.IntArray;
@@ -29,11 +27,9 @@
 import com.android.internal.os.KernelCpuProcStringReader.ProcFileIterator;
 import com.android.internal.os.KernelCpuUidBpfMapReader.BpfMapIterator;
 
-import java.io.BufferedReader;
 import java.io.FileWriter;
 import java.io.IOException;
 import java.nio.CharBuffer;
-import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 
@@ -352,7 +348,7 @@
         private int mFreqCount = 0;
         private int mErrors = 0;
         private boolean mPerClusterTimesAvailable;
-        private boolean mAllUidTimesAvailable = true;
+        private boolean mAllUidTimesAvailable;
 
         public KernelCpuUidFreqTimeReader(boolean throttle) {
             this(throttle, Clock.SYSTEM_CLOCK);
@@ -376,10 +372,22 @@
         }
 
         /**
+         * Initializes the reader.  Should be called during the system-ready boot phase.
+         */
+        public void onSystemReady() {
+            if (mBpfTimesAvailable && mCpuFreqs == null) {
+                readFreqsThroughBpf();
+                // By extension: if we can read CPU frequencies through eBPF, we can also
+                // read per-UID CPU time-in-state
+                mAllUidTimesAvailable = mCpuFreqs != null;
+            }
+        }
+
+        /**
          * @return Whether per-cluster times are available.
          */
         public boolean perClusterTimesAvailable() {
-            return mPerClusterTimesAvailable;
+            return mBpfTimesAvailable;
         }
 
         /**
@@ -396,59 +404,6 @@
             return mLastTimes;
         }
 
-        /**
-         * Reads a list of CPU frequencies from /proc/uid_time_in_state. Uses a given PowerProfile
-         * to determine if per-cluster times are available.
-         *
-         * @param powerProfile The PowerProfile to compare against.
-         * @return A long[] of CPU frequencies in Hz.
-         */
-        public long[] readFreqs(@NonNull PowerProfile powerProfile) {
-            checkNotNull(powerProfile);
-            if (mCpuFreqs != null) {
-                // No need to read cpu freqs more than once.
-                return mCpuFreqs;
-            }
-            if (!mAllUidTimesAvailable) {
-                return null;
-            }
-            if (mBpfTimesAvailable) {
-                readFreqsThroughBpf();
-            }
-            if (mCpuFreqs == null) {
-                final int oldMask = StrictMode.allowThreadDiskReadsMask();
-                try (BufferedReader reader = Files.newBufferedReader(mProcFilePath)) {
-                    if (readFreqs(reader.readLine()) == null) {
-                        return null;
-                    }
-                } catch (IOException e) {
-                    if (++mErrors >= MAX_ERROR_COUNT) {
-                        mAllUidTimesAvailable = false;
-                    }
-                    Slog.e(mTag, "Failed to read " + UID_TIMES_PROC_FILE + ": " + e);
-                    return null;
-                } finally {
-                    StrictMode.setThreadPolicyMask(oldMask);
-                }
-            }
-            // Check if the freqs in the proc file correspond to per-cluster freqs.
-            final IntArray numClusterFreqs = extractClusterInfoFromProcFileFreqs();
-            final int numClusters = powerProfile.getNumCpuClusters();
-            if (numClusterFreqs.size() == numClusters) {
-                mPerClusterTimesAvailable = true;
-                for (int i = 0; i < numClusters; ++i) {
-                    if (numClusterFreqs.get(i) != powerProfile.getNumSpeedStepsInCpuCluster(i)) {
-                        mPerClusterTimesAvailable = false;
-                        break;
-                    }
-                }
-            } else {
-                mPerClusterTimesAvailable = false;
-            }
-            Slog.i(mTag, "mPerClusterTimesAvailable=" + mPerClusterTimesAvailable);
-            return mCpuFreqs;
-        }
-
         private long[] readFreqsThroughBpf() {
             if (!mBpfTimesAvailable || mBpfReader == null) {
                 return null;
diff --git a/core/java/com/android/internal/os/PowerProfile.java b/core/java/com/android/internal/os/PowerProfile.java
index 2bcc645..503e689 100644
--- a/core/java/com/android/internal/os/PowerProfile.java
+++ b/core/java/com/android/internal/os/PowerProfile.java
@@ -26,6 +26,7 @@
 import android.content.res.XmlResourceParser;
 import android.util.IndentingPrintWriter;
 import android.util.Slog;
+import android.util.SparseArray;
 import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.annotations.GuardedBy;
@@ -294,6 +295,8 @@
 
     private static final long SUBSYSTEM_FIELDS_MASK = 0xFFFF_FFFF;
 
+    private static final int DEFAULT_CPU_POWER_BRACKET_NUMBER = 3;
+
     /**
      * A map from Power Use Item to its power consumption.
      */
@@ -357,6 +360,8 @@
             readPowerValuesFromXml(context, xmlId);
         }
         initCpuClusters();
+        initCpuScalingPolicies();
+        initCpuPowerBrackets(DEFAULT_CPU_POWER_BRACKET_NUMBER);
         initDisplays();
         initModem();
     }
@@ -452,9 +457,7 @@
     private static final String CPU_CLUSTER_POWER_COUNT = "cpu.cluster_power.cluster";
     private static final String CPU_CORE_SPEED_PREFIX = "cpu.core_speeds.cluster";
     private static final String CPU_CORE_POWER_PREFIX = "cpu.core_power.cluster";
-    private static final String CPU_POWER_BRACKETS_PREFIX = "cpu.power_brackets.cluster";
-
-    private static final int DEFAULT_CPU_POWER_BRACKET_NUMBER = 3;
+    private static final String CPU_POWER_BRACKETS_PREFIX = "cpu.power_brackets.policy";
 
     private void initCpuClusters() {
         if (sPowerArrayMap.containsKey(CPU_PER_CLUSTER_CORE_COUNT)) {
@@ -476,8 +479,82 @@
             mCpuClusters[0] = new CpuClusterKey(CPU_CORE_SPEED_PREFIX + 0,
                     CPU_CLUSTER_POWER_COUNT + 0, CPU_CORE_POWER_PREFIX + 0, numCpus);
         }
+    }
 
-        initCpuPowerBrackets(DEFAULT_CPU_POWER_BRACKET_NUMBER);
+    private SparseArray<CpuScalingPolicyPower> mCpuScalingPolicies;
+    private static final String CPU_SCALING_POLICY_POWER_POLICY = "cpu.scaling_policy_power.policy";
+    private static final String CPU_SCALING_STEP_POWER_POLICY = "cpu.scaling_step_power.policy";
+
+    private void initCpuScalingPolicies() {
+        int policyCount = 0;
+        for (String key : sPowerItemMap.keySet()) {
+            if (key.startsWith(CPU_SCALING_POLICY_POWER_POLICY)) {
+                int policy =
+                        Integer.parseInt(key.substring(CPU_SCALING_POLICY_POWER_POLICY.length()));
+                policyCount = Math.max(policyCount, policy + 1);
+            }
+        }
+        for (String key : sPowerArrayMap.keySet()) {
+            if (key.startsWith(CPU_SCALING_STEP_POWER_POLICY)) {
+                int policy =
+                        Integer.parseInt(key.substring(CPU_SCALING_STEP_POWER_POLICY.length()));
+                policyCount = Math.max(policyCount, policy + 1);
+            }
+        }
+
+        if (policyCount > 0) {
+            mCpuScalingPolicies = new SparseArray<>(policyCount);
+            for (int policy = 0; policy < policyCount; policy++) {
+                Double policyPower = sPowerItemMap.get(CPU_SCALING_POLICY_POWER_POLICY + policy);
+                Double[] stepPower = sPowerArrayMap.get(CPU_SCALING_STEP_POWER_POLICY + policy);
+                if (policyPower != null || stepPower != null) {
+                    double[] primitiveStepPower;
+                    if (stepPower != null) {
+                        primitiveStepPower = new double[stepPower.length];
+                        for (int i = 0; i < stepPower.length; i++) {
+                            primitiveStepPower[i] = stepPower[i];
+                        }
+                    } else {
+                        primitiveStepPower = new double[0];
+                    }
+                    mCpuScalingPolicies.put(policy, new CpuScalingPolicyPower(
+                            policyPower != null ? policyPower : 0, primitiveStepPower));
+                }
+            }
+        } else {
+            // Legacy power_profile.xml
+            int cpuId = 0;
+            for (CpuClusterKey cpuCluster : mCpuClusters) {
+                policyCount = cpuId + 1;
+                cpuId += cpuCluster.numCpus;
+            }
+
+            if (policyCount > 0) {
+                mCpuScalingPolicies = new SparseArray<>(policyCount);
+                cpuId = 0;
+                for (CpuClusterKey cpuCluster : mCpuClusters) {
+                    double clusterPower = getAveragePower(cpuCluster.clusterPowerKey);
+                    double[] stepPower;
+                    int numSteps = getNumElements(cpuCluster.corePowerKey);
+                    if (numSteps != 0) {
+                        stepPower = new double[numSteps];
+                        for (int step = 0; step < numSteps; step++) {
+                            stepPower[step] = getAveragePower(cpuCluster.corePowerKey, step);
+                        }
+                    } else {
+                        stepPower = new double[1];
+                    }
+                    mCpuScalingPolicies.put(cpuId,
+                            new CpuScalingPolicyPower(clusterPower, stepPower));
+                    cpuId += cpuCluster.numCpus;
+                }
+            } else {
+                mCpuScalingPolicies = new SparseArray<>(1);
+                mCpuScalingPolicies.put(0,
+                        new CpuScalingPolicyPower(getAveragePower(POWER_CPU_ACTIVE),
+                                new double[]{0}));
+            }
+        }
     }
 
     /**
@@ -487,33 +564,38 @@
     public void initCpuPowerBrackets(int defaultCpuPowerBracketNumber) {
         boolean anyBracketsSpecified = false;
         boolean allBracketsSpecified = true;
-        for (int cluster = 0; cluster < mCpuClusters.length; cluster++) {
-            final int steps = getNumSpeedStepsInCpuCluster(cluster);
-            mCpuClusters[cluster].powerBrackets = new int[steps];
-            if (sPowerArrayMap.get(CPU_POWER_BRACKETS_PREFIX + cluster) != null) {
+        for (int i = mCpuScalingPolicies.size() - 1; i >= 0; i--) {
+            int policy = mCpuScalingPolicies.keyAt(i);
+            CpuScalingPolicyPower cpuScalingPolicyPower = mCpuScalingPolicies.valueAt(i);
+            final int steps = cpuScalingPolicyPower.stepPower.length;
+            cpuScalingPolicyPower.powerBrackets = new int[steps];
+            if (sPowerArrayMap.get(CPU_POWER_BRACKETS_PREFIX + policy) != null) {
                 anyBracketsSpecified = true;
             } else {
                 allBracketsSpecified = false;
             }
         }
-
         if (anyBracketsSpecified && !allBracketsSpecified) {
             throw new RuntimeException(
-                    "Power brackets should be specified for all clusters or no clusters");
+                    "Power brackets should be specified for all scaling policies or none");
         }
 
         mCpuPowerBracketCount = 0;
         if (allBracketsSpecified) {
-            for (int cluster = 0; cluster < mCpuClusters.length; cluster++) {
-                final Double[] data = sPowerArrayMap.get(CPU_POWER_BRACKETS_PREFIX + cluster);
-                if (data.length != mCpuClusters[cluster].powerBrackets.length) {
+            for (int i = mCpuScalingPolicies.size() - 1; i >= 0; i--) {
+                int policy = mCpuScalingPolicies.keyAt(i);
+                CpuScalingPolicyPower cpuScalingPolicyPower = mCpuScalingPolicies.valueAt(i);
+                final Double[] data = sPowerArrayMap.get(CPU_POWER_BRACKETS_PREFIX + policy);
+                if (data.length != cpuScalingPolicyPower.powerBrackets.length) {
                     throw new RuntimeException(
-                            "Wrong number of items in " + CPU_POWER_BRACKETS_PREFIX + cluster);
+                            "Wrong number of items in " + CPU_POWER_BRACKETS_PREFIX + policy
+                                    + ", expected: "
+                                    + cpuScalingPolicyPower.powerBrackets.length);
                 }
 
-                for (int i = 0; i < data.length; i++) {
-                    final int bracket = (int) Math.round(data[i]);
-                    mCpuClusters[cluster].powerBrackets[i] = bracket;
+                for (int j = 0; j < data.length; j++) {
+                    final int bracket = (int) Math.round(data[j]);
+                    cpuScalingPolicyPower.powerBrackets[j] = bracket;
                     if (bracket > mCpuPowerBracketCount) {
                         mCpuPowerBracketCount = bracket;
                     }
@@ -524,10 +606,12 @@
             double minPower = Double.MAX_VALUE;
             double maxPower = Double.MIN_VALUE;
             int stateCount = 0;
-            for (int cluster = 0; cluster < mCpuClusters.length; cluster++) {
-                final int steps = getNumSpeedStepsInCpuCluster(cluster);
+            for (int i = mCpuScalingPolicies.size() - 1; i >= 0; i--) {
+                int policy = mCpuScalingPolicies.keyAt(i);
+                CpuScalingPolicyPower cpuScalingPolicyPower = mCpuScalingPolicies.valueAt(i);
+                final int steps = cpuScalingPolicyPower.stepPower.length;
                 for (int step = 0; step < steps; step++) {
-                    final double power = getAveragePowerForCpuCore(cluster, step);
+                    final double power = getAveragePowerForCpuScalingStep(policy, step);
                     if (power < minPower) {
                         minPower = power;
                     }
@@ -541,10 +625,11 @@
             if (stateCount <= defaultCpuPowerBracketNumber) {
                 mCpuPowerBracketCount = stateCount;
                 int bracket = 0;
-                for (int cluster = 0; cluster < mCpuClusters.length; cluster++) {
-                    final int steps = getNumSpeedStepsInCpuCluster(cluster);
+                for (int i = 0; i < mCpuScalingPolicies.size(); i++) {
+                    CpuScalingPolicyPower cpuScalingPolicyPower = mCpuScalingPolicies.valueAt(i);
+                    final int steps = cpuScalingPolicyPower.stepPower.length;
                     for (int step = 0; step < steps; step++) {
-                        mCpuClusters[cluster].powerBrackets[step] = bracket++;
+                        cpuScalingPolicyPower.powerBrackets[step] = bracket++;
                     }
                 }
             } else {
@@ -553,27 +638,70 @@
                 final double logBracket = (Math.log(maxPower) - minLogPower)
                         / defaultCpuPowerBracketNumber;
 
-                for (int cluster = 0; cluster < mCpuClusters.length; cluster++) {
-                    final int steps = getNumSpeedStepsInCpuCluster(cluster);
+                for (int i = mCpuScalingPolicies.size() - 1; i >= 0; i--) {
+                    int policy = mCpuScalingPolicies.keyAt(i);
+                    CpuScalingPolicyPower cpuScalingPolicyPower = mCpuScalingPolicies.valueAt(i);
+                    final int steps = cpuScalingPolicyPower.stepPower.length;
                     for (int step = 0; step < steps; step++) {
-                        final double power = getAveragePowerForCpuCore(cluster, step);
+                        final double power = getAveragePowerForCpuScalingStep(policy, step);
                         int bracket = (int) ((Math.log(power) - minLogPower) / logBracket);
                         if (bracket >= defaultCpuPowerBracketNumber) {
                             bracket = defaultCpuPowerBracketNumber - 1;
                         }
-                        mCpuClusters[cluster].powerBrackets[step] = bracket;
+                        cpuScalingPolicyPower.powerBrackets[step] = bracket;
                     }
                 }
             }
         }
     }
 
+    private static class CpuScalingPolicyPower {
+        public final double policyPower;
+        public final double[] stepPower;
+        public int[] powerBrackets;
+
+        private CpuScalingPolicyPower(double policyPower, double[] stepPower) {
+            this.policyPower = policyPower;
+            this.stepPower = stepPower;
+        }
+    }
+
+    /**
+     * Returns the average additional power in (mA) when the CPU scaling policy <code>policy</code>
+     * is used.
+     *
+     * @param policy Policy ID as per <code>ls /sys/devices/system/cpu/cpufreq</code>. Typically,
+     *               policy ID corresponds to the index of the first related CPU, e.g. for "policy6"
+     *               <code>/sys/devices/system/cpu/cpufreq/policy6/related_cpus</code> will
+     *               contain CPU IDs like <code>6, 7</code>
+     */
+    public double getAveragePowerForCpuScalingPolicy(int policy) {
+        CpuScalingPolicyPower cpuScalingPolicyPower = mCpuScalingPolicies.get(policy);
+        if (cpuScalingPolicyPower != null) {
+            return cpuScalingPolicyPower.policyPower;
+        }
+        return 0;
+    }
+
+    /**
+     * Returns the average additional power in (mA) when the CPU scaling policy <code>policy</code>
+     * is used at the <code>step</code> frequency step (this is not the frequency itself, but the
+     * integer index of the frequency step).
+     */
+    public double getAveragePowerForCpuScalingStep(int policy, int step) {
+        CpuScalingPolicyPower cpuScalingPolicyPower = mCpuScalingPolicies.get(policy);
+        if (cpuScalingPolicyPower != null
+                && step >= 0 && step < cpuScalingPolicyPower.stepPower.length) {
+            return cpuScalingPolicyPower.stepPower[step];
+        }
+        return 0;
+    }
+
     private static class CpuClusterKey {
         public final String freqKey;
         public final String clusterPowerKey;
         public final String corePowerKey;
         public final int numCpus;
-        public int[] powerBrackets;
 
         private CpuClusterKey(String freqKey, String clusterPowerKey,
                 String corePowerKey, int numCpus) {
@@ -584,11 +712,19 @@
         }
     }
 
+    /**
+     * @deprecated Use CpuScalingPolicy instead
+     */
     @UnsupportedAppUsage
+    @Deprecated
     public int getNumCpuClusters() {
         return mCpuClusters.length;
     }
 
+    /**
+     * @deprecated Use CpuScalingPolicy instead
+     */
+    @Deprecated
     public int getNumCoresInCpuCluster(int cluster) {
         if (cluster < 0 || cluster >= mCpuClusters.length) {
             return 0; // index out of bound
@@ -596,7 +732,11 @@
         return mCpuClusters[cluster].numCpus;
     }
 
+    /**
+     * @deprecated Use CpuScalingPolicy instead
+     */
     @UnsupportedAppUsage
+    @Deprecated
     public int getNumSpeedStepsInCpuCluster(int cluster) {
         if (cluster < 0 || cluster >= mCpuClusters.length) {
             return 0; // index out of bound
@@ -607,6 +747,10 @@
         return 1; // Only one speed
     }
 
+    /**
+     * @deprecated Use getAveragePowerForCpuScalingPolicy
+     */
+    @Deprecated
     public double getAveragePowerForCpuCluster(int cluster) {
         if (cluster >= 0 && cluster < mCpuClusters.length) {
             return getAveragePower(mCpuClusters[cluster].clusterPowerKey);
@@ -614,6 +758,10 @@
         return 0;
     }
 
+    /**
+     * @deprecated Use getAveragePowerForCpuScalingStep
+     */
+    @Deprecated
     public double getAveragePowerForCpuCore(int cluster, int step) {
         if (cluster >= 0 && cluster < mCpuClusters.length) {
             return getAveragePower(mCpuClusters[cluster].corePowerKey, step);
@@ -631,26 +779,28 @@
     /**
      * Description of a CPU power bracket: which cluster/frequency combinations are included.
      */
-    public String getCpuPowerBracketDescription(int powerBracket) {
+    public String getCpuPowerBracketDescription(CpuScalingPolicies cpuScalingPolicies,
+            int powerBracket) {
         StringBuilder sb = new StringBuilder();
-        for (int cluster = 0; cluster < mCpuClusters.length; cluster++) {
-            int[] brackets = mCpuClusters[cluster].powerBrackets;
+        for (int i = 0; i < mCpuScalingPolicies.size(); i++) {
+            int policy = mCpuScalingPolicies.keyAt(i);
+            CpuScalingPolicyPower cpuScalingPolicyPower = mCpuScalingPolicies.valueAt(i);
+            int[] brackets = cpuScalingPolicyPower.powerBrackets;
+            int[] freqs = cpuScalingPolicies.getFrequencies(policy);
             for (int step = 0; step < brackets.length; step++) {
                 if (brackets[step] == powerBracket) {
                     if (sb.length() != 0) {
                         sb.append(", ");
                     }
-                    if (mCpuClusters.length > 1) {
-                        sb.append(cluster).append('/');
+                    if (mCpuScalingPolicies.size() > 1) {
+                        sb.append(policy).append('/');
                     }
-                    Double[] freqs = sPowerArrayMap.get(mCpuClusters[cluster].freqKey);
-                    if (freqs != null && step < freqs.length) {
-                        // Frequency in MHz
-                        sb.append(freqs[step].intValue() / 1000);
+                    if (step < freqs.length) {
+                        sb.append(freqs[step] / 1000);
                     }
                     sb.append('(');
                     sb.append(String.format(Locale.US, "%.1f",
-                            getAveragePowerForCpuCore(cluster, step)));
+                            getAveragePowerForCpuScalingStep(policy, step)));
                     sb.append(')');
                 }
             }
@@ -659,19 +809,18 @@
     }
 
     /**
-     * Returns the CPU power bracket corresponding to the specified cluster and frequency step
+     * Returns the CPU power bracket corresponding to the specified scaling policy and frequency
+     * step
      */
-    public int getPowerBracketForCpuCore(int cluster, int step) {
-        if (cluster >= 0
-                && cluster < mCpuClusters.length
-                && step >= 0
-                && step < mCpuClusters[cluster].powerBrackets.length) {
-            return mCpuClusters[cluster].powerBrackets[step];
+    public int getCpuPowerBracketForScalingStep(int policy, int step) {
+        CpuScalingPolicyPower cpuScalingPolicyPower = mCpuScalingPolicies.get(policy);
+        if (cpuScalingPolicyPower != null
+                && step >= 0 && step < cpuScalingPolicyPower.powerBrackets.length) {
+            return cpuScalingPolicyPower.powerBrackets[step];
         }
         return 0;
     }
 
-
     private int mNumDisplays;
 
     private void initDisplays() {
diff --git a/core/java/com/android/internal/os/TimeoutRecord.java b/core/java/com/android/internal/os/TimeoutRecord.java
index a0e2934..e9a8d4b 100644
--- a/core/java/com/android/internal/os/TimeoutRecord.java
+++ b/core/java/com/android/internal/os/TimeoutRecord.java
@@ -58,6 +58,7 @@
         int APP_REGISTERED = 7;
         int SHORT_FGS_TIMEOUT = 8;
         int JOB_SERVICE = 9;
+        int APP_START = 10;
     }
 
     /** Kind of timeout, e.g. BROADCAST_RECEIVER, etc. */
@@ -151,7 +152,11 @@
 
     /** Record for a service exec timeout. */
     @NonNull
-    public static TimeoutRecord forServiceExec(@NonNull String reason) {
+    public static TimeoutRecord forServiceExec(@NonNull String shortInstanceName,
+            long timeoutDurationMs) {
+        String reason =
+                "executing service " + shortInstanceName + ", waited "
+                        + timeoutDurationMs + "ms";
         return TimeoutRecord.endingNow(TimeoutKind.SERVICE_EXEC, reason);
     }
 
@@ -186,4 +191,10 @@
     public static TimeoutRecord forJobService(String reason) {
         return TimeoutRecord.endingNow(TimeoutKind.JOB_SERVICE, reason);
     }
+
+    /** Record for app startup timeout. */
+    @NonNull
+    public static TimeoutRecord forAppStart(String reason) {
+        return TimeoutRecord.endingNow(TimeoutKind.APP_START, reason);
+    }
 }
diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java
index 2063542..0c6d6f9 100644
--- a/core/java/com/android/internal/os/Zygote.java
+++ b/core/java/com/android/internal/os/Zygote.java
@@ -195,6 +195,11 @@
      */
     public static final int PROFILEABLE = 1 << 24;
 
+    /**
+     * Enable ptrace.  This is enabled on eng or userdebug builds, or if the app is debuggable.
+     */
+    public static final int DEBUG_ENABLE_PTRACE = 1 << 25;
+
     /** No external storage should be mounted. */
     public static final int MOUNT_EXTERNAL_NONE = IVold.REMOUNT_MODE_NONE;
     /** Default external storage should be mounted. */
@@ -1028,6 +1033,9 @@
         if (Build.IS_ENG || ENABLE_JDWP) {
             args.mRuntimeFlags |= Zygote.DEBUG_ENABLE_JDWP;
         }
+        if (RoSystemProperties.DEBUGGABLE) {
+            args.mRuntimeFlags |= Zygote.DEBUG_ENABLE_PTRACE;
+        }
     }
 
     /**
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index a95ce64..7f53cb4 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -771,7 +771,7 @@
             Zygote.applyInvokeWithSystemProperty(parsedArgs);
 
             if (Zygote.nativeSupportsMemoryTagging()) {
-                String mode = SystemProperties.get("arm64.memtag.process.system_server", "");
+                String mode = SystemProperties.get("persist.arm64.memtag.system_server", "");
                 if (mode.isEmpty()) {
                   /* The system server has ASYNC MTE by default, in order to allow
                    * system services to specify their own MTE level later, as you
diff --git a/core/java/com/android/internal/os/anr/AnrLatencyTracker.java b/core/java/com/android/internal/os/anr/AnrLatencyTracker.java
index 80d753c..f62ff38 100644
--- a/core/java/com/android/internal/os/anr/AnrLatencyTracker.java
+++ b/core/java/com/android/internal/os/anr/AnrLatencyTracker.java
@@ -24,6 +24,7 @@
 import static com.android.internal.util.FrameworkStatsLog.ANRLATENCY_REPORTED__ANR_TYPE__EXECUTING_SERVICE;
 import static com.android.internal.util.FrameworkStatsLog.ANRLATENCY_REPORTED__ANR_TYPE__INPUT_DISPATCHING_TIMEOUT;
 import static com.android.internal.util.FrameworkStatsLog.ANRLATENCY_REPORTED__ANR_TYPE__INPUT_DISPATCHING_TIMEOUT_NO_FOCUSED_WINDOW;
+import static com.android.internal.util.FrameworkStatsLog.ANRLATENCY_REPORTED__ANR_TYPE__JOB_SERVICE;
 import static com.android.internal.util.FrameworkStatsLog.ANRLATENCY_REPORTED__ANR_TYPE__SHORT_FGS_TIMEOUT;
 import static com.android.internal.util.FrameworkStatsLog.ANRLATENCY_REPORTED__ANR_TYPE__START_FOREGROUND_SERVICE;
 import static com.android.internal.util.FrameworkStatsLog.ANRLATENCY_REPORTED__ANR_TYPE__UNKNOWN_ANR_TYPE;
@@ -545,6 +546,8 @@
                 return ANRLATENCY_REPORTED__ANR_TYPE__CONTENT_PROVIDER_NOT_RESPONDING;
             case TimeoutKind.SHORT_FGS_TIMEOUT:
                 return ANRLATENCY_REPORTED__ANR_TYPE__SHORT_FGS_TIMEOUT;
+            case TimeoutKind.JOB_SERVICE:
+                return ANRLATENCY_REPORTED__ANR_TYPE__JOB_SERVICE;
             default:
                 return ANRLATENCY_REPORTED__ANR_TYPE__UNKNOWN_ANR_TYPE;
         }
diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
index af1fdd7..bb86801 100644
--- a/core/java/com/android/internal/policy/DecorView.java
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -232,7 +232,7 @@
     private boolean mLastHasRightStableInset = false;
     private boolean mLastHasLeftStableInset = false;
     private int mLastWindowFlags = 0;
-    private boolean mLastShouldAlwaysConsumeSystemBars = false;
+    private @InsetsType int mLastForceConsumingTypes = 0;
     private @InsetsType int mLastSuppressScrimTypes = 0;
 
     private int mRootScrollY = 0;
@@ -1111,19 +1111,19 @@
                     : controller.getSystemBarsAppearance();
 
             if (insets != null) {
-                mLastShouldAlwaysConsumeSystemBars = insets.shouldAlwaysConsumeSystemBars();
+                mLastForceConsumingTypes = insets.getForceConsumingTypes();
 
-                final boolean clearsCompatInsets =
-                        clearsCompatInsets(attrs.type, attrs.flags,
-                                getResources().getConfiguration().windowConfiguration
-                                        .getWindowingMode())
-                        && !mLastShouldAlwaysConsumeSystemBars;
+                @InsetsType int compatInsetsTypes =
+                        WindowInsets.Type.systemBars() | WindowInsets.Type.displayCutout();
+                if (clearsCompatInsets(attrs.type, attrs.flags,
+                        getResources().getConfiguration().windowConfiguration.getWindowingMode())) {
+                    compatInsetsTypes &= mLastForceConsumingTypes;
+                }
                 final Insets stableBarInsets = insets.getInsetsIgnoringVisibility(
                         WindowInsets.Type.systemBars());
-                final Insets systemInsets = clearsCompatInsets
+                final Insets systemInsets = compatInsetsTypes == 0
                         ? Insets.NONE
-                        : Insets.min(insets.getInsets(WindowInsets.Type.systemBars()
-                                | WindowInsets.Type.displayCutout()), stableBarInsets);
+                        : Insets.min(insets.getInsets(compatInsetsTypes), stableBarInsets);
                 mLastTopInset = systemInsets.top;
                 mLastBottomInset = systemInsets.bottom;
                 mLastRightInset = systemInsets.right;
@@ -1208,7 +1208,8 @@
                         && (sysUiVisibility & SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) == 0
                         && decorFitsSystemWindows
                         && !hideNavigation)
-                || (mLastShouldAlwaysConsumeSystemBars && hideNavigation);
+                || ((mLastForceConsumingTypes & WindowInsets.Type.navigationBars()) != 0
+                        && hideNavigation);
 
         boolean consumingNavBar =
                 ((attrs.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0
@@ -1224,13 +1225,15 @@
         boolean fullscreen = (sysUiVisibility & SYSTEM_UI_FLAG_FULLSCREEN) != 0
                 || (attrs.flags & FLAG_FULLSCREEN) != 0
                 || (requestedVisibleTypes & WindowInsets.Type.statusBars()) == 0;
-        boolean consumingStatusBar = (sysUiVisibility & SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) == 0
-                && decorFitsSystemWindows
-                && (attrs.flags & FLAG_LAYOUT_IN_SCREEN) == 0
-                && (attrs.flags & FLAG_LAYOUT_INSET_DECOR) == 0
-                && mForceWindowDrawsBarBackgrounds
-                && mLastTopInset != 0
-                || (mLastShouldAlwaysConsumeSystemBars && fullscreen);
+        boolean consumingStatusBar =
+                ((sysUiVisibility & SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) == 0
+                        && decorFitsSystemWindows
+                        && (attrs.flags & FLAG_LAYOUT_IN_SCREEN) == 0
+                        && (attrs.flags & FLAG_LAYOUT_INSET_DECOR) == 0
+                        && mForceWindowDrawsBarBackgrounds
+                        && mLastTopInset != 0)
+                || ((mLastForceConsumingTypes & WindowInsets.Type.statusBars()) != 0
+                        && fullscreen);
 
         int consumedTop = consumingStatusBar ? mLastTopInset : 0;
         int consumedRight = consumingNavBar ? mLastRightInset : 0;
@@ -1434,9 +1437,9 @@
     private void updateColorViewInt(final ColorViewState state, int color, int dividerColor,
             int size, boolean verticalBar, boolean seascape, int sideMargin, boolean animate,
             boolean force, @InsetsType int requestedVisibleTypes) {
+        final @InsetsType int type = state.attributes.insetsType;
         state.present = state.attributes.isPresent(
-                (requestedVisibleTypes & state.attributes.insetsType) != 0
-                        || mLastShouldAlwaysConsumeSystemBars,
+                (requestedVisibleTypes & type) != 0 || (mLastForceConsumingTypes & type) != 0,
                 mWindow.getAttributes().flags, force);
         boolean show = state.attributes.isVisible(state.present, color,
                 mWindow.getAttributes().flags, force);
diff --git a/core/java/com/android/internal/protolog/common/ProtoLog.java b/core/java/com/android/internal/protolog/common/ProtoLog.java
index 93765cd..8870096 100644
--- a/core/java/com/android/internal/protolog/common/ProtoLog.java
+++ b/core/java/com/android/internal/protolog/common/ProtoLog.java
@@ -16,6 +16,8 @@
 
 package com.android.internal.protolog.common;
 
+import android.util.Log;
+
 /**
  * ProtoLog API - exposes static logging methods. Usage of this API is similar
  * to {@code android.utils.Log} class. Instead of plain text log messages each call consists of
@@ -53,6 +55,9 @@
             throw new UnsupportedOperationException(
                     "ProtoLog calls MUST be processed with ProtoLogTool");
         }
+        if (group.isLogToLogcat()) {
+            Log.d(group.getTag(), String.format(messageString, args));
+        }
     }
 
     /**
@@ -68,6 +73,9 @@
             throw new UnsupportedOperationException(
                     "ProtoLog calls MUST be processed with ProtoLogTool");
         }
+        if (group.isLogToLogcat()) {
+            Log.v(group.getTag(), String.format(messageString, args));
+        }
     }
 
     /**
@@ -83,6 +91,9 @@
             throw new UnsupportedOperationException(
                     "ProtoLog calls MUST be processed with ProtoLogTool");
         }
+        if (group.isLogToLogcat()) {
+            Log.i(group.getTag(), String.format(messageString, args));
+        }
     }
 
     /**
@@ -98,6 +109,9 @@
             throw new UnsupportedOperationException(
                     "ProtoLog calls MUST be processed with ProtoLogTool");
         }
+        if (group.isLogToLogcat()) {
+            Log.w(group.getTag(), String.format(messageString, args));
+        }
     }
 
     /**
@@ -113,6 +127,9 @@
             throw new UnsupportedOperationException(
                     "ProtoLog calls MUST be processed with ProtoLogTool");
         }
+        if (group.isLogToLogcat()) {
+            Log.e(group.getTag(), String.format(messageString, args));
+        }
     }
 
     /**
@@ -128,5 +145,8 @@
             throw new UnsupportedOperationException(
                     "ProtoLog calls MUST be processed with ProtoLogTool");
         }
+        if (group.isLogToLogcat()) {
+            Log.wtf(group.getTag(), String.format(messageString, args));
+        }
     }
 }
diff --git a/core/java/com/android/internal/security/TEST_MAPPING b/core/java/com/android/internal/security/TEST_MAPPING
index b47ecec..2d598dc 100644
--- a/core/java/com/android/internal/security/TEST_MAPPING
+++ b/core/java/com/android/internal/security/TEST_MAPPING
@@ -20,12 +20,7 @@
       "file_patterns": ["VerityUtils\\.java"]
     },
     {
-      "name": "CtsAppSecurityHostTestCases",
-      "options": [
-        {
-          "include-filter": "android.appsecurity.cts.ApkVerityInstallTest"
-        }
-      ],
+      "name": "CtsApkVerityInstallHostTestCases",
       "file_patterns": ["VerityUtils\\.java"]
     }
   ]
diff --git a/core/java/com/android/internal/statusbar/IAppClipsService.aidl b/core/java/com/android/internal/statusbar/IAppClipsService.aidl
index 013d0d3..d6ab8bc 100644
--- a/core/java/com/android/internal/statusbar/IAppClipsService.aidl
+++ b/core/java/com/android/internal/statusbar/IAppClipsService.aidl
@@ -23,4 +23,6 @@
  */
 interface IAppClipsService {
     boolean canLaunchCaptureContentActivityForNote(in int taskId);
-}
\ No newline at end of file
+
+    int canLaunchCaptureContentActivityForNoteInternal(in int taskId);
+}
diff --git a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
index 8a02dd6..dadeb2b 100644
--- a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
+++ b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
@@ -60,9 +60,7 @@
     @UnsupportedAppUsage(maxTargetSdk = 28)
     void notifyCallForwardingChanged(boolean cfi);
     void notifyCallForwardingChangedForSubscriber(in int subId, boolean cfi);
-    @UnsupportedAppUsage(maxTargetSdk = 28)
-    void notifyDataActivity(int state);
-    void notifyDataActivityForSubscriber(in int subId, int state);
+    void notifyDataActivityForSubscriber(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/internal/util/TraceBuffer.java b/core/java/com/android/internal/util/TraceBuffer.java
index fcc77bd4..c23e902 100644
--- a/core/java/com/android/internal/util/TraceBuffer.java
+++ b/core/java/com/android/internal/util/TraceBuffer.java
@@ -18,6 +18,7 @@
 
 import android.util.proto.ProtoOutputStream;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 
 import java.io.File;
@@ -39,12 +40,13 @@
  * {@hide}
  */
 public class TraceBuffer<P, S extends P, T extends P> {
-    private final Object mBufferLock = new Object();
-
     private final ProtoProvider<P, S, T> mProtoProvider;
+    @GuardedBy("this")
     private final Queue<T> mBuffer = new ArrayDeque<>();
     private final Consumer mProtoDequeuedCallback;
+    @GuardedBy("this")
     private int mBufferUsedSize;
+    @GuardedBy("this")
     private int mBufferCapacity;
 
     /**
@@ -115,18 +117,18 @@
         resetBuffer();
     }
 
-    public int getAvailableSpace() {
+    public synchronized int getAvailableSpace() {
         return mBufferCapacity - mBufferUsedSize;
     }
 
     /**
      * Returns buffer size.
      */
-    public int size() {
+    public synchronized int size() {
         return mBuffer.size();
     }
 
-    public void setCapacity(int capacity) {
+    public synchronized void setCapacity(int capacity) {
         mBufferCapacity = capacity;
     }
 
@@ -137,22 +139,19 @@
      * @throws IllegalStateException if the element cannot be added because it is larger
      *                               than the buffer size.
      */
-    public void add(T proto) {
+    public synchronized void add(T proto) {
         int protoLength = mProtoProvider.getItemSize(proto);
         if (protoLength > mBufferCapacity) {
             throw new IllegalStateException("Trace object too large for the buffer. Buffer size:"
                     + mBufferCapacity + " Object size: " + protoLength);
         }
-        synchronized (mBufferLock) {
-            discardOldest(protoLength);
-            mBuffer.add(proto);
-            mBufferUsedSize += protoLength;
-            mBufferLock.notify();
-        }
+        discardOldest(protoLength);
+        mBuffer.add(proto);
+        mBufferUsedSize += protoLength;
     }
 
     @VisibleForTesting
-    public boolean contains(byte[] other) {
+    public synchronized boolean contains(byte[] other) {
         return mBuffer.stream()
                 .anyMatch(p -> Arrays.equals(mProtoProvider.getBytes(p), other));
     }
@@ -160,15 +159,13 @@
     /**
      * Writes the trace buffer to disk inside the encapsulatingProto.
      */
-    public void writeTraceToFile(File traceFile, S encapsulatingProto)
+    public synchronized void writeTraceToFile(File traceFile, S encapsulatingProto)
             throws IOException {
-        synchronized (mBufferLock) {
-            traceFile.delete();
-            try (OutputStream os = new FileOutputStream(traceFile)) {
-                traceFile.setReadable(true /* readable */, false /* ownerOnly */);
-                mProtoProvider.write(encapsulatingProto, mBuffer, os);
-                os.flush();
-            }
+        traceFile.delete();
+        try (OutputStream os = new FileOutputStream(traceFile)) {
+            traceFile.setReadable(true /* readable */, false /* ownerOnly */);
+            mProtoProvider.write(encapsulatingProto, mBuffer, os);
+            os.flush();
         }
     }
 
@@ -199,31 +196,27 @@
     /**
      * Removes all elements from the buffer
      */
-    public void resetBuffer() {
-        synchronized (mBufferLock) {
-            if (mProtoDequeuedCallback != null) {
-                for (T item : mBuffer) {
-                    mProtoDequeuedCallback.accept(item);
-                }
+    public synchronized void resetBuffer() {
+        if (mProtoDequeuedCallback != null) {
+            for (T item : mBuffer) {
+                mProtoDequeuedCallback.accept(item);
             }
-            mBuffer.clear();
-            mBufferUsedSize = 0;
         }
+        mBuffer.clear();
+        mBufferUsedSize = 0;
     }
 
     @VisibleForTesting
-    public int getBufferSize() {
+    public synchronized int getBufferSize() {
         return mBufferUsedSize;
     }
 
     /**
      * Returns the buffer status in human-readable form.
      */
-    public String getStatus() {
-        synchronized (mBufferLock) {
-            return "Buffer size: " + mBufferCapacity + " bytes" + "\n"
-                    + "Buffer usage: " + mBufferUsedSize + " bytes" + "\n"
-                    + "Elements in the buffer: " + mBuffer.size();
-        }
+    public synchronized String getStatus() {
+        return "Buffer size: " + mBufferCapacity + " bytes" + "\n"
+                + "Buffer usage: " + mBufferUsedSize + " bytes" + "\n"
+                + "Elements in the buffer: " + mBuffer.size();
     }
 }
diff --git a/core/java/com/android/internal/vibrator/OWNERS b/core/java/com/android/internal/vibrator/OWNERS
new file mode 100644
index 0000000..d073e2b
--- /dev/null
+++ b/core/java/com/android/internal/vibrator/OWNERS
@@ -0,0 +1 @@
+include platform/frameworks/base:/services/core/java/com/android/server/vibrator/OWNERS
diff --git a/core/java/com/android/internal/vibrator/persistence/SerializedAmplitudeStepWaveform.java b/core/java/com/android/internal/vibrator/persistence/SerializedAmplitudeStepWaveform.java
new file mode 100644
index 0000000..15ecedd
--- /dev/null
+++ b/core/java/com/android/internal/vibrator/persistence/SerializedAmplitudeStepWaveform.java
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.vibrator.persistence;
+
+import static com.android.internal.vibrator.persistence.XmlConstants.ATTRIBUTE_AMPLITUDE;
+import static com.android.internal.vibrator.persistence.XmlConstants.ATTRIBUTE_DURATION_MS;
+import static com.android.internal.vibrator.persistence.XmlConstants.NAMESPACE;
+import static com.android.internal.vibrator.persistence.XmlConstants.TAG_REPEATING;
+import static com.android.internal.vibrator.persistence.XmlConstants.TAG_WAVEFORM_EFFECT;
+import static com.android.internal.vibrator.persistence.XmlConstants.TAG_WAVEFORM_ENTRY;
+import static com.android.internal.vibrator.persistence.XmlConstants.VALUE_AMPLITUDE_DEFAULT;
+
+import android.annotation.NonNull;
+import android.os.VibrationEffect;
+import android.util.IntArray;
+import android.util.LongArray;
+
+import com.android.internal.vibrator.persistence.SerializedVibrationEffect.SerializedSegment;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
+import java.io.IOException;
+import java.util.Arrays;
+
+/**
+ * Serialized representation of a waveform effect created via
+ * {@link VibrationEffect#createWaveform(long[], int[], int)}.
+ *
+ * @hide
+ */
+final class SerializedAmplitudeStepWaveform implements SerializedSegment {
+
+    @NonNull private final long[] mTimings;
+    @NonNull private final int[] mAmplitudes;
+    private final int mRepeatIndex;
+
+    private SerializedAmplitudeStepWaveform(long[] timings, int[] amplitudes, int repeatIndex) {
+        mTimings = timings;
+        mAmplitudes = amplitudes;
+        mRepeatIndex = repeatIndex;
+    }
+
+    @Override
+    public void deserializeIntoComposition(@NonNull VibrationEffect.Composition composition) {
+        composition.addEffect(VibrationEffect.createWaveform(mTimings, mAmplitudes, mRepeatIndex));
+    }
+
+    @Override
+    public void write(@NonNull TypedXmlSerializer serializer) throws IOException {
+        serializer.startTag(NAMESPACE, TAG_WAVEFORM_EFFECT);
+
+        for (int i = 0; i < mTimings.length; i++) {
+            if (i == mRepeatIndex) {
+                serializer.startTag(NAMESPACE, TAG_REPEATING);
+            }
+            writeWaveformEntry(serializer, i);
+        }
+
+        if (mRepeatIndex >= 0) {
+            serializer.endTag(NAMESPACE, TAG_REPEATING);
+        }
+        serializer.endTag(NAMESPACE, TAG_WAVEFORM_EFFECT);
+    }
+
+    private void writeWaveformEntry(@NonNull TypedXmlSerializer serializer, int index)
+            throws IOException {
+        serializer.startTag(NAMESPACE, TAG_WAVEFORM_ENTRY);
+
+        if (mAmplitudes[index] == VibrationEffect.DEFAULT_AMPLITUDE) {
+            serializer.attribute(NAMESPACE, ATTRIBUTE_AMPLITUDE, VALUE_AMPLITUDE_DEFAULT);
+        } else {
+            serializer.attributeInt(NAMESPACE, ATTRIBUTE_AMPLITUDE, mAmplitudes[index]);
+        }
+
+        serializer.attributeLong(NAMESPACE, ATTRIBUTE_DURATION_MS, mTimings[index]);
+        serializer.endTag(NAMESPACE, TAG_WAVEFORM_ENTRY);
+    }
+
+    @Override
+    public String toString() {
+        return "SerializedAmplitudeStepWaveform{"
+                + "timings=" + Arrays.toString(mTimings)
+                + ", amplitudes=" + Arrays.toString(mAmplitudes)
+                + ", repeatIndex=" + mRepeatIndex
+                + '}';
+    }
+
+    /** Builder for {@link SerializedAmplitudeStepWaveform}. */
+    static final class Builder {
+        private final LongArray mTimings = new LongArray();
+        private final IntArray mAmplitudes = new IntArray();
+        private int mRepeatIndex = -1;
+
+        void addDurationAndAmplitude(long durationMs, int amplitude) {
+            mTimings.add(durationMs);
+            mAmplitudes.add(amplitude);
+        }
+
+        void setRepeatIndexToCurrentEntry() {
+            mRepeatIndex = mTimings.size();
+        }
+
+        boolean hasNonZeroDuration() {
+            for (int i = 0; i < mTimings.size(); i++) {
+                if (mTimings.get(i) > 0) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        SerializedAmplitudeStepWaveform build() {
+            return new SerializedAmplitudeStepWaveform(
+                    mTimings.toArray(), mAmplitudes.toArray(), mRepeatIndex);
+        }
+    }
+
+    /** Parser implementation for the {@link XmlConstants#TAG_WAVEFORM_EFFECT}. */
+    static final class Parser {
+
+        @NonNull
+        static SerializedAmplitudeStepWaveform parseNext(@NonNull TypedXmlPullParser parser)
+                throws XmlParserException, IOException {
+            XmlValidator.checkStartTag(parser, TAG_WAVEFORM_EFFECT);
+            XmlValidator.checkTagHasNoUnexpectedAttributes(parser);
+
+            Builder waveformBuilder = new Builder();
+            int outerDepth = parser.getDepth();
+
+            // Read all nested tag that is not a repeating tag as a waveform entry.
+            while (XmlReader.readNextTagWithin(parser, outerDepth)
+                    && !TAG_REPEATING.equals(parser.getName())) {
+                parseWaveformEntry(parser, waveformBuilder);
+            }
+
+            // If found a repeating tag, read its content.
+            if (TAG_REPEATING.equals(parser.getName())) {
+                parseRepeating(parser, waveformBuilder);
+            }
+
+            // Check schema assertions about <waveform-effect>
+            XmlValidator.checkParserCondition(waveformBuilder.hasNonZeroDuration(),
+                    "Unexpected %s tag with total duration zero", TAG_WAVEFORM_EFFECT);
+
+            // Consume tag
+            XmlReader.readEndTag(parser, TAG_WAVEFORM_EFFECT, outerDepth);
+
+            return waveformBuilder.build();
+        }
+
+        private static void parseRepeating(TypedXmlPullParser parser, Builder waveformBuilder)
+                throws XmlParserException, IOException {
+            XmlValidator.checkStartTag(parser, TAG_REPEATING);
+            XmlValidator.checkTagHasNoUnexpectedAttributes(parser);
+
+            waveformBuilder.setRepeatIndexToCurrentEntry();
+
+            boolean hasEntry = false;
+            int outerDepth = parser.getDepth();
+            while (XmlReader.readNextTagWithin(parser, outerDepth)) {
+                parseWaveformEntry(parser, waveformBuilder);
+                hasEntry = true;
+            }
+
+            // Check schema assertions about <repeating>
+            XmlValidator.checkParserCondition(hasEntry, "Unexpected empty %s tag", TAG_REPEATING);
+
+            // Consume tag
+            XmlReader.readEndTag(parser, TAG_REPEATING, outerDepth);
+        }
+
+        private static void parseWaveformEntry(TypedXmlPullParser parser, Builder waveformBuilder)
+                throws XmlParserException, IOException {
+            XmlValidator.checkStartTag(parser, TAG_WAVEFORM_ENTRY);
+            XmlValidator.checkTagHasNoUnexpectedAttributes(
+                    parser, ATTRIBUTE_DURATION_MS, ATTRIBUTE_AMPLITUDE);
+
+            String rawAmplitude = parser.getAttributeValue(NAMESPACE, ATTRIBUTE_AMPLITUDE);
+            int amplitude = VALUE_AMPLITUDE_DEFAULT.equals(rawAmplitude)
+                    ? VibrationEffect.DEFAULT_AMPLITUDE
+                    : XmlReader.readAttributeIntInRange(
+                            parser, ATTRIBUTE_AMPLITUDE, 0, VibrationEffect.MAX_AMPLITUDE);
+            int durationMs = XmlReader.readAttributeIntNonNegative(parser, ATTRIBUTE_DURATION_MS);
+
+            waveformBuilder.addDurationAndAmplitude(durationMs, amplitude);
+
+            // Consume tag
+            XmlReader.readEndTag(parser);
+        }
+    }
+}
diff --git a/core/java/com/android/internal/vibrator/persistence/SerializedCompositionPrimitive.java b/core/java/com/android/internal/vibrator/persistence/SerializedCompositionPrimitive.java
new file mode 100644
index 0000000..db5c7ff
--- /dev/null
+++ b/core/java/com/android/internal/vibrator/persistence/SerializedCompositionPrimitive.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.vibrator.persistence;
+
+import static com.android.internal.vibrator.persistence.XmlConstants.ATTRIBUTE_DELAY_MS;
+import static com.android.internal.vibrator.persistence.XmlConstants.ATTRIBUTE_NAME;
+import static com.android.internal.vibrator.persistence.XmlConstants.ATTRIBUTE_SCALE;
+import static com.android.internal.vibrator.persistence.XmlConstants.NAMESPACE;
+import static com.android.internal.vibrator.persistence.XmlConstants.TAG_PRIMITIVE_EFFECT;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.VibrationEffect;
+import android.os.vibrator.PrimitiveSegment;
+
+import com.android.internal.vibrator.persistence.SerializedVibrationEffect.SerializedSegment;
+import com.android.internal.vibrator.persistence.XmlConstants.PrimitiveEffectName;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
+import java.io.IOException;
+
+/**
+ * Serialized representation of a single {@link PrimitiveSegment} created via
+ * {@link VibrationEffect.Composition#addPrimitive(int, float, int)}.
+ *
+ * @hide
+ */
+final class SerializedCompositionPrimitive implements SerializedSegment {
+
+    @NonNull
+    private final PrimitiveEffectName mPrimitiveName;
+    private final float mPrimitiveScale;
+    private final int mPrimitiveDelayMs;
+
+    SerializedCompositionPrimitive(PrimitiveEffectName primitiveName, float scale, int delayMs) {
+        mPrimitiveName = primitiveName;
+        mPrimitiveScale = scale;
+        mPrimitiveDelayMs = delayMs;
+    }
+
+    @Override
+    public void deserializeIntoComposition(@NonNull VibrationEffect.Composition composition) {
+        composition.addPrimitive(mPrimitiveName.getPrimitiveId(), mPrimitiveScale,
+                mPrimitiveDelayMs);
+    }
+
+    @Override
+    public void write(@NonNull TypedXmlSerializer serializer) throws IOException {
+        serializer.startTag(NAMESPACE, TAG_PRIMITIVE_EFFECT);
+        serializer.attribute(NAMESPACE, ATTRIBUTE_NAME, mPrimitiveName.toString());
+
+        if (Float.compare(mPrimitiveScale, PrimitiveSegment.DEFAULT_SCALE) != 0) {
+            serializer.attributeFloat(NAMESPACE, ATTRIBUTE_SCALE, mPrimitiveScale);
+        }
+
+        if (mPrimitiveDelayMs != PrimitiveSegment.DEFAULT_DELAY_MILLIS) {
+            serializer.attributeInt(NAMESPACE, ATTRIBUTE_DELAY_MS, mPrimitiveDelayMs);
+        }
+
+        serializer.endTag(NAMESPACE, TAG_PRIMITIVE_EFFECT);
+    }
+
+    @Override
+    public String toString() {
+        return "SerializedCompositionPrimitive{"
+                + "name=" + mPrimitiveName
+                + ", scale=" + mPrimitiveScale
+                + ", delayMs=" + mPrimitiveDelayMs
+                + '}';
+    }
+
+    /** Parser implementation for {@link SerializedCompositionPrimitive}. */
+    static final class Parser {
+
+        @NonNull
+        static SerializedCompositionPrimitive parseNext(@NonNull TypedXmlPullParser parser)
+                throws XmlParserException, IOException {
+            XmlValidator.checkStartTag(parser, TAG_PRIMITIVE_EFFECT);
+            XmlValidator.checkTagHasNoUnexpectedAttributes(parser,
+                    ATTRIBUTE_NAME, ATTRIBUTE_DELAY_MS, ATTRIBUTE_SCALE);
+
+            PrimitiveEffectName primitiveName = parsePrimitiveName(
+                    parser.getAttributeValue(NAMESPACE, ATTRIBUTE_NAME));
+            float scale = XmlReader.readAttributeFloatInRange(
+                    parser, ATTRIBUTE_SCALE, 0, 1, PrimitiveSegment.DEFAULT_SCALE);
+            int delayMs = XmlReader.readAttributeIntNonNegative(
+                    parser, ATTRIBUTE_DELAY_MS, PrimitiveSegment.DEFAULT_DELAY_MILLIS);
+
+            // Consume tag
+            XmlReader.readEndTag(parser);
+
+            return new SerializedCompositionPrimitive(primitiveName, scale, delayMs);
+        }
+
+        @NonNull
+        private static PrimitiveEffectName parsePrimitiveName(@Nullable String name)
+                throws XmlParserException {
+            if (name == null) {
+                throw new XmlParserException("Missing primitive effect name");
+            }
+            PrimitiveEffectName effectName = PrimitiveEffectName.findByName(name);
+            if (effectName == null) {
+                throw new XmlParserException("Unexpected primitive effect name " + name);
+            }
+            return effectName;
+        }
+    }
+}
diff --git a/core/java/com/android/internal/vibrator/persistence/SerializedPredefinedEffect.java b/core/java/com/android/internal/vibrator/persistence/SerializedPredefinedEffect.java
new file mode 100644
index 0000000..8924311
--- /dev/null
+++ b/core/java/com/android/internal/vibrator/persistence/SerializedPredefinedEffect.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.vibrator.persistence;
+
+import static com.android.internal.vibrator.persistence.XmlConstants.ATTRIBUTE_FALLBACK;
+import static com.android.internal.vibrator.persistence.XmlConstants.ATTRIBUTE_NAME;
+import static com.android.internal.vibrator.persistence.XmlConstants.NAMESPACE;
+import static com.android.internal.vibrator.persistence.XmlConstants.TAG_PREDEFINED_EFFECT;
+
+import android.annotation.NonNull;
+import android.os.VibrationEffect;
+import android.os.vibrator.PrebakedSegment;
+
+import com.android.internal.vibrator.persistence.SerializedVibrationEffect.SerializedSegment;
+import com.android.internal.vibrator.persistence.XmlConstants.PredefinedEffectName;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
+import java.io.IOException;
+
+/**
+ * Serialized representation of a predefined effect created via
+ * {@link VibrationEffect#get(int, boolean)}.
+ *
+ * @hide
+ */
+final class SerializedPredefinedEffect implements SerializedSegment {
+
+    @NonNull
+    private final PredefinedEffectName mEffectName;
+    private final boolean mShouldFallback;
+
+    SerializedPredefinedEffect(PredefinedEffectName effectName, boolean shouldFallback) {
+        mEffectName = effectName;
+        mShouldFallback = shouldFallback;
+    }
+
+    @Override
+    public void deserializeIntoComposition(@NonNull VibrationEffect.Composition composition) {
+        composition.addEffect(VibrationEffect.get(mEffectName.getEffectId(), mShouldFallback));
+    }
+
+    @Override
+    public void write(@NonNull TypedXmlSerializer serializer) throws IOException {
+        serializer.startTag(NAMESPACE, TAG_PREDEFINED_EFFECT);
+        serializer.attribute(NAMESPACE, ATTRIBUTE_NAME, mEffectName.toString());
+        if (mShouldFallback != PrebakedSegment.DEFAULT_SHOULD_FALLBACK) {
+            serializer.attributeBoolean(NAMESPACE, ATTRIBUTE_FALLBACK, mShouldFallback);
+        }
+        serializer.endTag(NAMESPACE, TAG_PREDEFINED_EFFECT);
+    }
+
+    @Override
+    public String toString() {
+        return "SerializedPredefinedEffect{"
+                + "name=" + mEffectName
+                + ", fallback=" + mShouldFallback
+                + '}';
+    }
+
+    /** Parser implementation for {@link SerializedPredefinedEffect}. */
+    static final class Parser {
+
+        @NonNull
+        static SerializedPredefinedEffect parseNext(@NonNull TypedXmlPullParser parser,
+                @XmlConstants.Flags int flags) throws XmlParserException, IOException {
+            XmlValidator.checkStartTag(parser, TAG_PREDEFINED_EFFECT);
+
+            boolean allowHidden = (flags & XmlConstants.FLAG_ALLOW_HIDDEN_APIS) != 0;
+            if (allowHidden) {
+                XmlValidator.checkTagHasNoUnexpectedAttributes(parser, ATTRIBUTE_NAME,
+                        ATTRIBUTE_FALLBACK);
+            } else {
+                XmlValidator.checkTagHasNoUnexpectedAttributes(parser, ATTRIBUTE_NAME);
+            }
+
+            String nameAttr = parser.getAttributeValue(NAMESPACE, ATTRIBUTE_NAME);
+            if (nameAttr == null) {
+                throw new XmlParserException("Missing predefined effect name");
+            }
+            PredefinedEffectName effectName = PredefinedEffectName.findByName(nameAttr, flags);
+            if (effectName == null) {
+                throw new XmlParserException("Unexpected predefined effect name " + nameAttr);
+            }
+
+            boolean defaultFallback = PrebakedSegment.DEFAULT_SHOULD_FALLBACK;
+            boolean fallback = allowHidden
+                    ? parser.getAttributeBoolean(NAMESPACE, ATTRIBUTE_FALLBACK, defaultFallback)
+                    : defaultFallback;
+
+            // Consume tag
+            XmlReader.readEndTag(parser);
+
+            return new SerializedPredefinedEffect(effectName, fallback);
+        }
+    }
+}
diff --git a/core/java/com/android/internal/vibrator/persistence/SerializedVibrationEffect.java b/core/java/com/android/internal/vibrator/persistence/SerializedVibrationEffect.java
new file mode 100644
index 0000000..84e8647
--- /dev/null
+++ b/core/java/com/android/internal/vibrator/persistence/SerializedVibrationEffect.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.vibrator.persistence;
+
+import static com.android.internal.util.Preconditions.checkArgument;
+
+import static java.util.Objects.requireNonNull;
+
+import android.annotation.NonNull;
+import android.os.VibrationEffect;
+
+import com.android.modules.utils.TypedXmlSerializer;
+
+import java.io.IOException;
+import java.util.Arrays;
+
+/**
+ * Serialized representation of a {@link VibrationEffect}.
+ *
+ * <p>The vibration is represented by a list of serialized segments that can be added to a
+ * {@link VibrationEffect.Composition} during the {@link #deserialize()} procedure.
+ *
+ * @hide
+ */
+final class SerializedVibrationEffect implements XmlSerializedVibration<VibrationEffect> {
+
+    @NonNull
+    private final SerializedSegment[] mSegments;
+
+    SerializedVibrationEffect(@NonNull SerializedSegment segment) {
+        requireNonNull(segment);
+        mSegments = new SerializedSegment[]{ segment };
+    }
+
+    SerializedVibrationEffect(@NonNull SerializedSegment[] segments) {
+        requireNonNull(segments);
+        checkArgument(segments.length > 0, "Unsupported empty vibration");
+        mSegments = segments;
+    }
+
+    @NonNull
+    @Override
+    public VibrationEffect deserialize() {
+        VibrationEffect.Composition composition = VibrationEffect.startComposition();
+        for (SerializedSegment segment : mSegments) {
+            segment.deserializeIntoComposition(composition);
+        }
+        return composition.compose();
+    }
+
+    @Override
+    public void write(@NonNull TypedXmlSerializer serializer)
+            throws IOException {
+        serializer.startTag(XmlConstants.NAMESPACE, XmlConstants.TAG_VIBRATION);
+        writeContent(serializer);
+        serializer.endTag(XmlConstants.NAMESPACE, XmlConstants.TAG_VIBRATION);
+    }
+
+    @Override
+    public void writeContent(@NonNull TypedXmlSerializer serializer) throws IOException {
+        for (SerializedSegment segment : mSegments) {
+            segment.write(serializer);
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "SerializedVibrationEffect{"
+                + "segments=" + Arrays.toString(mSegments)
+                + '}';
+    }
+
+    /**
+     * Serialized representation of a generic part of a {@link VibrationEffect}.
+     *
+     * <p>This can represent a single {@link android.os.vibrator.VibrationEffectSegment} (e.g. a
+     * single primitive or predefined effect) or a more complex effect, like a repeating
+     * amplitude-step waveform.
+     *
+     * @see XmlSerializedVibration
+     */
+    interface SerializedSegment {
+
+        /** Writes this segment into a {@link TypedXmlSerializer}. */
+        void write(@NonNull TypedXmlSerializer serializer) throws IOException;
+
+        /** Adds this segment into a {@link VibrationEffect.Composition}. */
+        void deserializeIntoComposition(@NonNull VibrationEffect.Composition composition);
+    }
+}
diff --git a/core/java/com/android/internal/vibrator/persistence/VibrationEffectXmlParser.java b/core/java/com/android/internal/vibrator/persistence/VibrationEffectXmlParser.java
new file mode 100644
index 0000000..3561fe4
--- /dev/null
+++ b/core/java/com/android/internal/vibrator/persistence/VibrationEffectXmlParser.java
@@ -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.internal.vibrator.persistence;
+
+import static com.android.internal.vibrator.persistence.XmlConstants.TAG_PREDEFINED_EFFECT;
+import static com.android.internal.vibrator.persistence.XmlConstants.TAG_PRIMITIVE_EFFECT;
+import static com.android.internal.vibrator.persistence.XmlConstants.TAG_VIBRATION;
+import static com.android.internal.vibrator.persistence.XmlConstants.TAG_WAVEFORM_EFFECT;
+
+import android.annotation.NonNull;
+import android.os.VibrationEffect;
+
+import com.android.internal.vibrator.persistence.SerializedVibrationEffect.SerializedSegment;
+import com.android.modules.utils.TypedXmlPullParser;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Parser implementation for {@link VibrationEffect}.
+ *
+ * <p>This parser supports the schema defined by services/core/xsd/vibrator/vibration/vibration.xsd.
+ *
+ * <p>This parser does not support effects created with {@link VibrationEffect.WaveformBuilder} nor
+ * {@link VibrationEffect.Composition#addEffect(VibrationEffect)}. It only supports vibration
+ * effects defined as:
+ *
+ * * Predefined vibration effects
+ *
+ * <pre>
+ *   {@code
+ *     <vibration>
+ *       <predefined-effect name="click" />
+ *     </vibration>
+ *   }
+ * </pre>
+ *
+ * * Waveform vibration effects
+ *
+ * <pre>
+ *   {@code
+ *     <vibration>
+ *       <waveform-effect>
+ *         <waveform-entry amplitude="default" durationMs="10" />
+ *         <waveform-entry amplitude="0" durationMs="10" />
+ *         <waveform-entry amplitude="255" durationMs="100" />
+ *         <repeating>
+ *           <waveform-entry amplitude="128" durationMs="30" />
+ *           <waveform-entry amplitude="192" durationMs="60" />
+ *           <waveform-entry amplitude="255" durationMs="20" />
+ *         </repeating>
+ *       </waveform-effect>
+ *     </vibration>
+ *   }
+ * </pre>
+ *
+ * * Primitive composition effects
+ *
+ * <pre>
+ *   {@code
+ *     <vibration>
+ *       <primitive-effect name="click" />
+ *       <primitive-effect name="tick" scale="0.5" delayMs="100" />
+ *     </vibration>
+ *   }
+ * </pre>
+ *
+ * @hide
+ */
+public class VibrationEffectXmlParser {
+
+    /**
+     * Parses the current XML tag with all nested tags into a single {@link XmlSerializedVibration}
+     * wrapping a {@link VibrationEffect}.
+     *
+     * @see XmlParser#parseTag(TypedXmlPullParser)
+     */
+    @NonNull
+    public static XmlSerializedVibration<VibrationEffect> parseTag(
+            @NonNull TypedXmlPullParser parser, @XmlConstants.Flags int flags)
+            throws XmlParserException, IOException {
+        XmlValidator.checkStartTag(parser, TAG_VIBRATION);
+        XmlValidator.checkTagHasNoUnexpectedAttributes(parser);
+
+        return parseVibrationContent(parser, flags);
+    }
+
+    /**
+     * Reads all tags within the currently open tag into a serialized representation of a
+     * {@link VibrationEffect}, skipping any validation for the top level tag itself.
+     *
+     * <p>This can be reused for reading a vibration from an XML root tag or from within a combined
+     * vibration, but it should always be called from places that validates the top level tag.
+     */
+    static SerializedVibrationEffect parseVibrationContent(TypedXmlPullParser parser,
+            @XmlConstants.Flags int flags) throws XmlParserException, IOException {
+        String vibrationTagName = parser.getName();
+        int vibrationTagDepth = parser.getDepth();
+
+        XmlValidator.checkParserCondition(
+                XmlReader.readNextTagWithin(parser, vibrationTagDepth),
+                "Unsupported empty vibration tag");
+
+        SerializedVibrationEffect serializedVibration;
+
+        switch (parser.getName()) {
+            case TAG_PREDEFINED_EFFECT:
+                serializedVibration = new SerializedVibrationEffect(
+                        SerializedPredefinedEffect.Parser.parseNext(parser, flags));
+                break;
+            case TAG_PRIMITIVE_EFFECT:
+                List<SerializedSegment> primitives = new ArrayList<>();
+                do { // First primitive tag already open
+                    primitives.add(SerializedCompositionPrimitive.Parser.parseNext(parser));
+                } while (XmlReader.readNextTagWithin(parser, vibrationTagDepth));
+                serializedVibration = new SerializedVibrationEffect(
+                        primitives.toArray(new SerializedSegment[primitives.size()]));
+                break;
+            case TAG_WAVEFORM_EFFECT:
+                serializedVibration = new SerializedVibrationEffect(
+                        SerializedAmplitudeStepWaveform.Parser.parseNext(parser));
+                break;
+            default:
+                throw new XmlParserException("Unexpected tag " + parser.getName()
+                        + " in vibration tag " + vibrationTagName);
+        }
+
+        // Consume tag.
+        XmlReader.readEndTag(parser, vibrationTagName, vibrationTagDepth);
+
+        return serializedVibration;
+    }
+}
diff --git a/core/java/com/android/internal/vibrator/persistence/VibrationEffectXmlSerializer.java b/core/java/com/android/internal/vibrator/persistence/VibrationEffectXmlSerializer.java
new file mode 100644
index 0000000..f561c14
--- /dev/null
+++ b/core/java/com/android/internal/vibrator/persistence/VibrationEffectXmlSerializer.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.vibrator.persistence;
+
+import android.annotation.NonNull;
+import android.os.VibrationEffect;
+import android.os.vibrator.PrebakedSegment;
+import android.os.vibrator.PrimitiveSegment;
+import android.os.vibrator.StepSegment;
+import android.os.vibrator.VibrationEffectSegment;
+
+import com.android.internal.vibrator.persistence.SerializedVibrationEffect.SerializedSegment;
+import com.android.internal.vibrator.persistence.XmlConstants.PredefinedEffectName;
+import com.android.internal.vibrator.persistence.XmlConstants.PrimitiveEffectName;
+
+import java.util.List;
+
+/**
+ * Serializer implementation for {@link VibrationEffect}.
+ *
+ * <p>This serializer does not support effects created with {@link VibrationEffect.WaveformBuilder}
+ * nor {@link VibrationEffect.Composition#addEffect(VibrationEffect)}. It only supports vibration
+ * effects defined as:
+ *
+ * <ul>
+ *     <li>{@link VibrationEffect#createPredefined(int)}
+ *     <li>{@link VibrationEffect#createWaveform(long[], int[], int)}
+ *     <li>A composition created exclusively via
+ *         {@link VibrationEffect.Composition#addPrimitive(int, float, int)}
+ * </ul>
+ *
+ * @hide
+ */
+public final class VibrationEffectXmlSerializer {
+
+    /**
+     * Creates a serialized representation of the input {@code vibration}.
+     *
+     * @see XmlSerializer#serialize
+     */
+    @NonNull
+    public static XmlSerializedVibration<VibrationEffect> serialize(
+            @NonNull VibrationEffect vibration, @XmlConstants.Flags int flags)
+            throws XmlSerializerException {
+        XmlValidator.checkSerializerCondition(vibration instanceof VibrationEffect.Composed,
+                "Unsupported VibrationEffect type %s", vibration);
+
+        VibrationEffect.Composed composed = (VibrationEffect.Composed) vibration;
+        XmlValidator.checkSerializerCondition(!composed.getSegments().isEmpty(),
+                "Unsupported empty VibrationEffect %s", vibration);
+
+        VibrationEffectSegment firstSegment = composed.getSegments().get(0);
+        if (firstSegment instanceof PrebakedSegment) {
+            return serializePredefinedEffect(composed, flags);
+        }
+        if (firstSegment instanceof PrimitiveSegment) {
+            return serializePrimitiveEffect(composed);
+        }
+        return serializeWaveformEffect(composed);
+    }
+
+    private static SerializedVibrationEffect serializePredefinedEffect(
+            VibrationEffect.Composed effect, @XmlConstants.Flags int flags)
+            throws XmlSerializerException {
+        List<VibrationEffectSegment> segments = effect.getSegments();
+        XmlValidator.checkSerializerCondition(effect.getRepeatIndex() == -1,
+                "Unsupported repeating predefined effect %s", effect);
+        XmlValidator.checkSerializerCondition(segments.size() == 1,
+                "Unsupported multiple segments in predefined effect %s", effect);
+        return new SerializedVibrationEffect(serializePrebakedSegment(segments.get(0), flags));
+    }
+
+    private static SerializedVibrationEffect serializePrimitiveEffect(
+            VibrationEffect.Composed effect) throws XmlSerializerException {
+        List<VibrationEffectSegment> segments = effect.getSegments();
+        XmlValidator.checkSerializerCondition(effect.getRepeatIndex() == -1,
+                "Unsupported repeating primitive composition %s", effect);
+
+        SerializedSegment[] primitives = new SerializedSegment[segments.size()];
+        for (int i = 0; i < segments.size(); i++) {
+            primitives[i] = serializePrimitiveSegment(segments.get(i));
+        }
+
+        return new SerializedVibrationEffect(primitives);
+    }
+
+    private static SerializedVibrationEffect serializeWaveformEffect(
+            VibrationEffect.Composed effect) throws XmlSerializerException {
+        SerializedAmplitudeStepWaveform.Builder serializedWaveformBuilder =
+                new SerializedAmplitudeStepWaveform.Builder();
+
+        List<VibrationEffectSegment> segments = effect.getSegments();
+        for (int i = 0; i < segments.size(); i++) {
+            XmlValidator.checkSerializerCondition(segments.get(i) instanceof StepSegment,
+                    "Unsupported segment for waveform effect %s", segments.get(i));
+
+            StepSegment segment = (StepSegment) segments.get(i);
+            if (effect.getRepeatIndex() == i) {
+                serializedWaveformBuilder.setRepeatIndexToCurrentEntry();
+            }
+
+            XmlValidator.checkSerializerCondition(Float.compare(segment.getFrequencyHz(), 0) == 0,
+                    "Unsupported segment with non-default frequency %f", segment.getFrequencyHz());
+
+            serializedWaveformBuilder.addDurationAndAmplitude(
+                    segment.getDuration(), toAmplitudeInt(segment.getAmplitude()));
+        }
+
+        return new SerializedVibrationEffect(serializedWaveformBuilder.build());
+    }
+
+    private static SerializedPredefinedEffect serializePrebakedSegment(
+            VibrationEffectSegment segment, @XmlConstants.Flags int flags)
+            throws XmlSerializerException {
+        XmlValidator.checkSerializerCondition(segment instanceof PrebakedSegment,
+                "Unsupported segment for predefined effect %s", segment);
+
+        PrebakedSegment prebaked = (PrebakedSegment) segment;
+        PredefinedEffectName effectName = PredefinedEffectName.findById(
+                prebaked.getEffectId(), flags);
+
+        XmlValidator.checkSerializerCondition(effectName != null,
+                "Unsupported predefined effect id %s", prebaked.getEffectId());
+
+        if ((flags & XmlConstants.FLAG_ALLOW_HIDDEN_APIS) == 0) {
+            // Only allow effects with default fallback flag if using the public APIs schema.
+            XmlValidator.checkSerializerCondition(
+                    prebaked.shouldFallback() == PrebakedSegment.DEFAULT_SHOULD_FALLBACK,
+                    "Unsupported predefined effect with should fallback %s",
+                    prebaked.shouldFallback());
+        }
+
+        return new SerializedPredefinedEffect(effectName, prebaked.shouldFallback());
+    }
+
+    private static SerializedCompositionPrimitive serializePrimitiveSegment(
+            VibrationEffectSegment segment) throws XmlSerializerException {
+        XmlValidator.checkSerializerCondition(segment instanceof PrimitiveSegment,
+                "Unsupported segment for primitive composition %s", segment);
+
+        PrimitiveSegment primitive = (PrimitiveSegment) segment;
+        PrimitiveEffectName primitiveName =
+                PrimitiveEffectName.findById(primitive.getPrimitiveId());
+
+        XmlValidator.checkSerializerCondition(primitiveName != null,
+                "Unsupported primitive effect id %s", primitive.getPrimitiveId());
+
+        return new SerializedCompositionPrimitive(
+                primitiveName, primitive.getScale(), primitive.getDelay());
+    }
+
+    private static int toAmplitudeInt(float amplitude) {
+        return Float.compare(amplitude, VibrationEffect.DEFAULT_AMPLITUDE) == 0
+                ? VibrationEffect.DEFAULT_AMPLITUDE
+                : Math.round(amplitude * VibrationEffect.MAX_AMPLITUDE);
+    }
+}
diff --git a/core/java/com/android/internal/vibrator/persistence/XmlConstants.java b/core/java/com/android/internal/vibrator/persistence/XmlConstants.java
new file mode 100644
index 0000000..d1c78f0
--- /dev/null
+++ b/core/java/com/android/internal/vibrator/persistence/XmlConstants.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.vibrator.persistence;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.VibrationEffect;
+import android.os.VibrationEffect.Composition.PrimitiveType;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Locale;
+
+/**
+ * Constants used for vibration XML serialization and parsing.
+ *
+ * @hide
+ */
+public final class XmlConstants {
+
+    public static final String NAMESPACE = null;
+
+    public static final String TAG_VIBRATION = "vibration";
+
+    public static final String TAG_PREDEFINED_EFFECT = "predefined-effect";
+    public static final String TAG_PRIMITIVE_EFFECT = "primitive-effect";
+    public static final String TAG_WAVEFORM_EFFECT = "waveform-effect";
+    public static final String TAG_WAVEFORM_ENTRY = "waveform-entry";
+    public static final String TAG_REPEATING = "repeating";
+
+    public static final String ATTRIBUTE_NAME = "name";
+    public static final String ATTRIBUTE_FALLBACK = "fallback";
+    public static final String ATTRIBUTE_DURATION_MS = "durationMs";
+    public static final String ATTRIBUTE_AMPLITUDE = "amplitude";
+    public static final String ATTRIBUTE_SCALE = "scale";
+    public static final String ATTRIBUTE_DELAY_MS = "delayMs";
+
+    public static final String VALUE_AMPLITUDE_DEFAULT = "default";
+
+    /**
+     * Allow {@link VibrationEffect} hidden APIs to be used during parsing/serializing.
+     *
+     * <p>Use the schema at services/core/xsd/vibrator/vibration/vibration-plus-hidden-apis.xsd.
+     */
+    public static final int FLAG_ALLOW_HIDDEN_APIS = 1 << 0;
+
+    /** @hide */
+    @IntDef(prefix = { "FLAG_" }, flag = true, value = {
+            FLAG_ALLOW_HIDDEN_APIS
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Flags {}
+
+    /** Represent supported values for attribute name in {@link #TAG_PRIMITIVE_EFFECT}  */
+    public enum PrimitiveEffectName {
+        LOW_TICK(VibrationEffect.Composition.PRIMITIVE_LOW_TICK),
+        TICK(VibrationEffect.Composition.PRIMITIVE_TICK),
+        CLICK(VibrationEffect.Composition.PRIMITIVE_CLICK),
+        SLOW_RISE(VibrationEffect.Composition.PRIMITIVE_SLOW_RISE),
+        QUICK_RISE(VibrationEffect.Composition.PRIMITIVE_QUICK_RISE),
+        QUICK_FALL(VibrationEffect.Composition.PRIMITIVE_QUICK_FALL),
+        SPIN(VibrationEffect.Composition.PRIMITIVE_SPIN),
+        THUD(VibrationEffect.Composition.PRIMITIVE_THUD);
+
+        @PrimitiveType private final int mPrimitiveId;
+
+        PrimitiveEffectName(@PrimitiveType int id) {
+            mPrimitiveId = id;
+        }
+
+        /**
+         * Return the {@link PrimitiveEffectName} that represents given primitive id, or null if
+         * none of the available names maps to the given id.
+         */
+        @Nullable
+        public static PrimitiveEffectName findById(int primitiveId) {
+            for (PrimitiveEffectName name : PrimitiveEffectName.values()) {
+                if (name.mPrimitiveId == primitiveId) {
+                    return name;
+                }
+            }
+            return null;
+        }
+
+        /**
+         * Return the {@link PrimitiveEffectName} that represents given primitive name, or null if
+         * none of the available names maps to the given name.
+         */
+        @Nullable
+        public static PrimitiveEffectName findByName(@NonNull String primitiveName) {
+            try {
+                return PrimitiveEffectName.valueOf(primitiveName.toUpperCase(Locale.ROOT));
+            } catch (IllegalArgumentException e) {
+                return null;
+            }
+        }
+
+        @PrimitiveType
+        public int getPrimitiveId() {
+            return mPrimitiveId;
+        }
+
+        @Override
+        public String toString() {
+            return name().toLowerCase(Locale.ROOT);
+        }
+    }
+
+    /** Represent supported values for attribute name in {@link #TAG_PREDEFINED_EFFECT}  */
+    public enum PredefinedEffectName {
+        // Public effects
+        TICK(VibrationEffect.EFFECT_TICK, true),
+        CLICK(VibrationEffect.EFFECT_CLICK, true),
+        HEAVY_CLICK(VibrationEffect.EFFECT_HEAVY_CLICK, true),
+        DOUBLE_CLICK(VibrationEffect.EFFECT_DOUBLE_CLICK, true),
+
+        // Hidden effects
+        TEXTURE_TICK(VibrationEffect.EFFECT_TEXTURE_TICK, false),
+        THUD(VibrationEffect.EFFECT_THUD, false),
+        POP(VibrationEffect.EFFECT_POP, false),
+        RINGTONE_1(VibrationEffect.RINGTONES[0], false),
+        RINGTONE_2(VibrationEffect.RINGTONES[1], false),
+        RINGTONE_3(VibrationEffect.RINGTONES[2], false),
+        RINGTONE_4(VibrationEffect.RINGTONES[3], false),
+        RINGTONE_5(VibrationEffect.RINGTONES[4], false),
+        RINGTONE_6(VibrationEffect.RINGTONES[5], false),
+        RINGTONE_7(VibrationEffect.RINGTONES[6], false),
+        RINGTONE_8(VibrationEffect.RINGTONES[7], false),
+        RINGTONE_9(VibrationEffect.RINGTONES[8], false),
+        RINGTONE_10(VibrationEffect.RINGTONES[9], false),
+        RINGTONE_11(VibrationEffect.RINGTONES[10], false),
+        RINGTONE_12(VibrationEffect.RINGTONES[11], false),
+        RINGTONE_13(VibrationEffect.RINGTONES[12], false),
+        RINGTONE_14(VibrationEffect.RINGTONES[13], false),
+        RINGTONE_15(VibrationEffect.RINGTONES[14], false);
+
+        private final int mEffectId;
+        private final boolean mIsPublic;
+
+        PredefinedEffectName(int id, boolean isPublic) {
+            mEffectId = id;
+            mIsPublic = isPublic;
+        }
+
+        /**
+         * Return the {@link PredefinedEffectName} that represents given effect id, or null if
+         * none of the available names maps to the given id.
+         */
+        @Nullable
+        public static PredefinedEffectName findById(int effectId, @XmlConstants.Flags int flags) {
+            boolean allowHidden = (flags & XmlConstants.FLAG_ALLOW_HIDDEN_APIS) != 0;
+            for (PredefinedEffectName name : PredefinedEffectName.values()) {
+                if (name.mEffectId == effectId) {
+                    return (name.mIsPublic || allowHidden) ? name : null;
+                }
+            }
+            return null;
+        }
+
+        /**
+         * Return the {@link PredefinedEffectName} that represents given effect name, or null if
+         * none of the available names maps to the given name.
+         */
+        @Nullable
+        public static PredefinedEffectName findByName(@NonNull String effectName,
+                @XmlConstants.Flags int flags) {
+            boolean allowHidden = (flags & XmlConstants.FLAG_ALLOW_HIDDEN_APIS) != 0;
+            try {
+                PredefinedEffectName name = PredefinedEffectName.valueOf(
+                        effectName.toUpperCase(Locale.ROOT));
+                return (name.mIsPublic || allowHidden) ? name : null;
+            } catch (IllegalArgumentException e) {
+                return null;
+            }
+        }
+
+        public int getEffectId() {
+            return mEffectId;
+        }
+
+        @Override
+        public String toString() {
+            return name().toLowerCase(Locale.ROOT);
+        }
+    }
+}
diff --git a/core/java/com/android/internal/vibrator/persistence/XmlParser.java b/core/java/com/android/internal/vibrator/persistence/XmlParser.java
new file mode 100644
index 0000000..6712f1c
--- /dev/null
+++ b/core/java/com/android/internal/vibrator/persistence/XmlParser.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.internal.vibrator.persistence;
+
+import android.annotation.NonNull;
+
+import com.android.modules.utils.TypedXmlPullParser;
+
+import java.io.IOException;
+
+/**
+ * Parse XML tags into valid {@link XmlSerializedVibration} instances.
+ *
+ * @param <T> The vibration type that will be parsed.
+ * @see XmlSerializedVibration
+ * @hide
+ */
+@FunctionalInterface
+public interface XmlParser<T> {
+
+    /**
+     * Parses the current XML tag with all nested tags into a single {@link XmlSerializedVibration}.
+     *
+     * <p>This method will consume nested XML tags until it finds the
+     * {@link TypedXmlPullParser#END_TAG} for the current tag.
+     *
+     * <p>The vibration reconstructed by the returned {@link XmlSerializedVibration#deserialize()}
+     * is guaranteed to be valid. This method will throw an exception otherwise.
+     *
+     * @param pullParser The {@link TypedXmlPullParser} with the input XML.
+     * @return The parsed vibration wrapped in a {@link XmlSerializedVibration} representation.
+     * @throws IOException        On any I/O error while reading the input XML
+     * @throws XmlParserException If the XML content does not represent a valid vibration.
+     */
+    XmlSerializedVibration<T> parseTag(@NonNull TypedXmlPullParser pullParser)
+            throws XmlParserException, IOException;
+}
diff --git a/core/java/com/android/internal/vibrator/persistence/XmlParserException.java b/core/java/com/android/internal/vibrator/persistence/XmlParserException.java
new file mode 100644
index 0000000..7507864
--- /dev/null
+++ b/core/java/com/android/internal/vibrator/persistence/XmlParserException.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.internal.vibrator.persistence;
+
+import android.text.TextUtils;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+/**
+ * Represents an error while parsing a vibration XML input.
+ *
+ * @see XmlParser
+ * @hide
+ */
+public final class XmlParserException extends Exception {
+
+    /**
+     * Creates a {@link XmlParserException} from a {@link XmlPullParserException}, with root cause
+     * and default error message that includes the tag name.
+     */
+    public static XmlParserException createFromPullParserException(
+            String tagName, XmlPullParserException cause) {
+        return new XmlParserException("Error parsing " + tagName, cause);
+    }
+
+    /**
+     * Creates a {@link XmlParserException} from a {@link XmlPullParserException}, with root cause
+     * and default error message that includes the tag name, the attribute name and value.
+     */
+    public static XmlParserException createFromPullParserException(
+            String tagName, String attributeName, String attributeValue,
+            XmlPullParserException cause) {
+        return new XmlParserException(TextUtils.formatSimple("Error parsing %s = %s in tag %s",
+                attributeName, attributeValue, tagName), cause);
+    }
+
+    public XmlParserException(String message) {
+        super(message);
+    }
+
+    public XmlParserException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/core/java/com/android/internal/vibrator/persistence/XmlReader.java b/core/java/com/android/internal/vibrator/persistence/XmlReader.java
new file mode 100644
index 0000000..7507338
--- /dev/null
+++ b/core/java/com/android/internal/vibrator/persistence/XmlReader.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.vibrator.persistence;
+
+import static com.android.internal.util.Preconditions.checkArgument;
+import static com.android.internal.vibrator.persistence.XmlConstants.NAMESPACE;
+
+import com.android.modules.utils.TypedXmlPullParser;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+
+/**
+ * Helper methods for reading elements from a {@link XmlPullParser}.
+ *
+ * @hide
+ */
+public final class XmlReader {
+
+    /**
+     * Check parser is currently at {@link XmlPullParser#START_DOCUMENT} and that it has a start tag
+     * with expected root tag name.
+     *
+     * <p>The parser will be pointing to the root start tag found after this method.
+     */
+    public static void readDocumentStartTag(TypedXmlPullParser parser, String expectedRootTag)
+            throws XmlParserException, IOException {
+        try {
+            int type = parser.getEventType();
+            checkArgument(type == XmlPullParser.START_DOCUMENT, "Document already started");
+
+            type = parser.nextTag(); // skips comments, instruction tokens and whitespace only
+            XmlValidator.checkParserCondition(type == XmlPullParser.START_TAG,
+                    "Unexpected element at document start, expected root tag %s", expectedRootTag);
+
+            String tagName = parser.getName();
+            XmlValidator.checkParserCondition(expectedRootTag.equals(tagName),
+                    "Unexpected root tag found %s, expected %s", tagName, expectedRootTag);
+        } catch (XmlPullParserException e) {
+            throw XmlParserException.createFromPullParserException("document start tag", e);
+        }
+    }
+
+    /**
+     * Check parser is currently at {@link XmlPullParser#END_TAG} and that has the expected root tag
+     * name, and that the next tag is the {@link XmlPullParser#END_DOCUMENT} tag.
+     *
+     * <p>The parser will be pointing to the end document tag after this method.
+     */
+    public static void readDocumentEndTag(TypedXmlPullParser parser)
+            throws XmlParserException, IOException {
+        try {
+            int type = parser.getEventType();
+            XmlValidator.checkParserCondition(type == XmlPullParser.END_TAG,
+                    "Unexpected element at document end, expected end of root tag");
+
+            type = parser.next(); // skips comments and instruction tokens
+            if (type == XmlPullParser.TEXT && parser.isWhitespace()) { // skip whitespace only
+                type = parser.next();
+            }
+
+            XmlValidator.checkParserCondition(type == XmlPullParser.END_DOCUMENT,
+                    "Unexpected tag found %s, expected document end", parser.getName());
+        } catch (XmlPullParserException e) {
+            throw XmlParserException.createFromPullParserException("document end tag", e);
+        }
+    }
+
+    /**
+     * Read the next tag and returns true if it's a {@link XmlPullParser#START_TAG} at depth
+     * {@code outerDepth + 1} or false if it's a {@link XmlPullParser#END_TAG} at
+     * {@code outerDepth}. Any other tag will fail this check.
+     *
+     * <p>The parser will be pointing to the next nested start tag when this method returns true,
+     * or to the end tag for given depth if it returns false.
+     *
+     * @return true if start tag found within given depth, false otherwise
+     */
+    public static boolean readNextTagWithin(TypedXmlPullParser parser, int outerDepth)
+            throws XmlParserException, IOException {
+        int type;
+        try {
+            type = parser.getEventType();
+            if (type == XmlPullParser.END_TAG && parser.getDepth() == outerDepth) {
+                // Already pointing to the end tag at outerDepth, just return before calling next.
+                return false;
+            }
+
+            type = parser.nextTag(); // skips comments, instruction tokens and whitespace only
+        } catch (XmlPullParserException e) {
+            throw XmlParserException.createFromPullParserException(parser.getName(), e);
+        }
+
+        if (type == XmlPullParser.START_TAG && parser.getDepth() == outerDepth + 1) {
+            return true;
+        }
+
+        // Next tag is not a start tag at outerDepth+1, expect it to be the end tag for outerDepth.
+        XmlValidator.checkParserCondition(
+                type == XmlPullParser.END_TAG && parser.getDepth() == outerDepth,
+                "Unexpected tag found %s, expected end tag at depth %d",
+                parser.getName(), outerDepth);
+
+        return false;
+    }
+
+    /**
+     * Check parser has a {@link XmlPullParser#END_TAG} as the next tag, with no nested tags.
+     *
+     * <p>The parser will be pointing to the end tag after this method.
+     */
+    public static void readEndTag(TypedXmlPullParser parser)
+            throws XmlParserException, IOException {
+        readEndTag(parser, parser.getName(), parser.getDepth());
+    }
+
+    /**
+     * Check parser has a {@link XmlPullParser#END_TAG} with same {@code tagDepth} as the next tag,
+     * with no more nested start tags.
+     *
+     * <p>The parser will be pointing to the end tag after this method.
+     */
+    public static void readEndTag(TypedXmlPullParser parser, String tagName, int tagDepth)
+            throws XmlParserException, IOException {
+        // Read nested tag first, so we can use the parser.getName() in the error message.
+        boolean hasNestedTag = readNextTagWithin(parser, tagDepth);
+        XmlValidator.checkParserCondition(!hasNestedTag,
+                "Unexpected nested tag %s found in tag %s", parser.getName(), tagName);
+    }
+
+    /**
+     * Read attribute from current tag as a non-negative integer, returning default value if
+     * attribute is missing.
+     */
+    public static int readAttributeIntNonNegative(
+            TypedXmlPullParser parser, String attrName, int defaultValue)
+            throws XmlParserException {
+        if (parser.getAttributeIndex(NAMESPACE, attrName) < 0) {
+            return defaultValue;
+        }
+        return readAttributeIntNonNegative(parser, attrName);
+    }
+
+    /** Read attribute from current tag as a non-negative integer. */
+    public static int readAttributeIntNonNegative(TypedXmlPullParser parser, String attrName)
+            throws XmlParserException {
+        String tagName = parser.getName();
+        int value = readAttributeInt(parser, attrName);
+
+        XmlValidator.checkParserCondition(value >= 0,
+                "Unexpected %s = %d in tag %s, expected %s >= 0",
+                attrName, value, tagName, attrName);
+        return value;
+    }
+
+    /** Read attribute from current tag as an integer within given inclusive range. */
+    public static int readAttributeIntInRange(
+            TypedXmlPullParser parser, String attrName, int lowerInclusive, int upperInclusive)
+            throws XmlParserException {
+        String tagName = parser.getName();
+        int value = readAttributeInt(parser, attrName);
+
+        XmlValidator.checkParserCondition(
+                value >= lowerInclusive && value <= upperInclusive,
+                "Unexpected %s = %d in tag %s, expected %s in [%d, %d]",
+                attrName, value, tagName, attrName, lowerInclusive, upperInclusive);
+        return value;
+    }
+
+    /**
+     * Read attribute from current tag as a float within given inclusive range, returning default
+     * value if attribute is missing.
+     */
+    public static float readAttributeFloatInRange(
+            TypedXmlPullParser parser, String attrName, float lowerInclusive,
+            float upperInclusive, float defaultValue) throws XmlParserException {
+        if (parser.getAttributeIndex(NAMESPACE, attrName) < 0) {
+            return defaultValue;
+        }
+        String tagName = parser.getName();
+        float value = readAttributeFloat(parser, attrName);
+
+        XmlValidator.checkParserCondition(value >= lowerInclusive && value <= upperInclusive,
+                "Unexpected %s = %f in tag %s, expected %s in [%f, %f]",
+                attrName, value, tagName, attrName, lowerInclusive, upperInclusive);
+        return value;
+    }
+
+    private static int readAttributeInt(TypedXmlPullParser parser, String attrName)
+            throws XmlParserException {
+        String tagName = parser.getName();
+        try {
+            return parser.getAttributeInt(NAMESPACE, attrName);
+        } catch (XmlPullParserException e) {
+            String rawValue = parser.getAttributeValue(NAMESPACE, attrName);
+            throw XmlParserException.createFromPullParserException(tagName, attrName, rawValue, e);
+        }
+    }
+
+    private static float readAttributeFloat(TypedXmlPullParser parser, String attrName)
+            throws XmlParserException {
+        String tagName = parser.getName();
+        try {
+            return parser.getAttributeFloat(NAMESPACE, attrName);
+        } catch (XmlPullParserException e) {
+            String rawValue = parser.getAttributeValue(NAMESPACE, attrName);
+            throw XmlParserException.createFromPullParserException(tagName, attrName, rawValue, e);
+        }
+    }
+}
diff --git a/core/java/com/android/internal/vibrator/persistence/XmlSerializedVibration.java b/core/java/com/android/internal/vibrator/persistence/XmlSerializedVibration.java
new file mode 100644
index 0000000..f807ab9
--- /dev/null
+++ b/core/java/com/android/internal/vibrator/persistence/XmlSerializedVibration.java
@@ -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.internal.vibrator.persistence;
+
+import android.annotation.NonNull;
+
+import com.android.modules.utils.TypedXmlSerializer;
+
+import java.io.IOException;
+
+/**
+ * Serialized representation of a generic vibration.
+ *
+ * <p>This can be used to represent a {@link android.os.CombinedVibration} or a
+ * {@link android.os.VibrationEffect}. Instances can be created from vibration objects via
+ * {@link XmlSerializer}, or from XML content via {@link XmlParser}.
+ *
+ * <p>The separation of serialization and writing procedures enables configurable rules to define
+ * which vibrations can be successfully serialized before any data is written to the output stream.
+ * Serialization can fail early and prevent writing partial data into the output.
+ *
+ * @param <T> The type of vibration represented by this serialization
+ * @hide
+ */
+public interface XmlSerializedVibration<T> {
+
+    /** Reconstructs the vibration using the serialized fields. */
+    @NonNull
+    T deserialize();
+
+    /**
+     * Writes the top level XML tag and the serialized fields into given XML.
+     *
+     * @param serializer The output XML serializer where the vibration will be written
+     */
+    void write(@NonNull TypedXmlSerializer serializer) throws IOException;
+
+    /**
+     * Writes the serialized fields into given XML, without the top level XML tag.
+     *
+     * <p>This allows the same serialized representation of a vibration to be used in different
+     * contexts (e.g. a {@link android.os.VibrationEffect} can be written into any of the tags
+     * {@code <vibration>}, {@code <parallel-vibration>} or {@code <vibration vibratorId="0">}).
+     *
+     * @param serializer The output XML serializer where the vibration will be written
+     */
+    void writeContent(@NonNull TypedXmlSerializer serializer) throws IOException;
+}
diff --git a/core/java/com/android/internal/vibrator/persistence/XmlSerializer.java b/core/java/com/android/internal/vibrator/persistence/XmlSerializer.java
new file mode 100644
index 0000000..102e6c1
--- /dev/null
+++ b/core/java/com/android/internal/vibrator/persistence/XmlSerializer.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.internal.vibrator.persistence;
+
+import android.annotation.NonNull;
+
+/**
+ * Creates a {@link XmlSerializedVibration} instance representing a vibration.
+ *
+ * @param <T> The vibration type that will be serialized.
+ * @see XmlSerializedVibration
+ * @hide
+ */
+@FunctionalInterface
+public interface XmlSerializer<T> {
+
+    /**
+     * Creates a serialized representation of the input {@code vibration}.
+     *
+     * @param vibration The vibration to be serialized
+     * @return The serialized representation of the input vibration
+     * @throws XmlSerializerException If the input vibration cannot be serialized
+     */
+    @NonNull
+    XmlSerializedVibration<T> serialize(@NonNull T vibration) throws XmlSerializerException;
+}
diff --git a/core/java/com/android/internal/vibrator/persistence/XmlSerializerException.java b/core/java/com/android/internal/vibrator/persistence/XmlSerializerException.java
new file mode 100644
index 0000000..c57ff5d
--- /dev/null
+++ b/core/java/com/android/internal/vibrator/persistence/XmlSerializerException.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.internal.vibrator.persistence;
+
+/**
+ * Represents an error while serializing a vibration input.
+ *
+ * @see XmlSerializer
+ * @hide
+ */
+public final class XmlSerializerException extends Exception {
+
+    XmlSerializerException(String message) {
+        super(message);
+    }
+}
diff --git a/core/java/com/android/internal/vibrator/persistence/XmlValidator.java b/core/java/com/android/internal/vibrator/persistence/XmlValidator.java
new file mode 100644
index 0000000..ba95e35
--- /dev/null
+++ b/core/java/com/android/internal/vibrator/persistence/XmlValidator.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.vibrator.persistence;
+
+import static java.util.Objects.requireNonNull;
+
+import android.annotation.NonNull;
+import android.text.TextUtils;
+
+import com.android.internal.util.ArrayUtils;
+import com.android.modules.utils.TypedXmlPullParser;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.util.Objects;
+
+/**
+ * Helper methods for validating elements from a {@link XmlPullParser}.
+ *
+ * @hide
+ */
+public final class XmlValidator {
+
+    /**
+     * Check parser is currently at {@link XmlPullParser#START_TAG} and that it has the expected
+     * name.
+     */
+    public static void checkStartTag(TypedXmlPullParser parser, String expectedTag)
+            throws XmlParserException {
+        String tagName = parser.getName();
+        try {
+            checkParserCondition(
+                    parser.getEventType() == parser.START_TAG && expectedTag.equals(tagName),
+                    "Unexpected tag found %s, expected %s", tagName, expectedTag);
+        } catch (XmlPullParserException e) {
+            throw XmlParserException.createFromPullParserException(tagName, e);
+        }
+    }
+
+    /** Check current tag only has attributes from the expected list */
+    public static void checkTagHasNoUnexpectedAttributes(
+            TypedXmlPullParser parser, String... expectedAttributes) throws XmlParserException {
+        if (expectedAttributes == null || expectedAttributes.length == 0) {
+            checkParserCondition(parser.getAttributeCount() == 0,
+                    "Unexpected attributes in tag %s, expected no attributes", parser.getName());
+            return;
+        }
+
+        String tagName = parser.getName();
+        int attributeCount = parser.getAttributeCount();
+
+        for (int i = 0; i < attributeCount; i++) {
+            String attributeName = parser.getAttributeName(i);
+            checkParserCondition(ArrayUtils.contains(expectedAttributes, attributeName),
+                    "Unexpected attribute %s found in tag %s", attributeName, tagName);
+        }
+    }
+
+    /**
+     * Check given {@link XmlSerializedVibration} represents the expected {@code vibration} object
+     * when it's deserialized.
+     */
+    @NonNull
+    public static <T> void checkSerializedVibration(
+            XmlSerializedVibration<T> serializedVibration, T expectedVibration)
+            throws XmlSerializerException {
+        T deserializedVibration = requireNonNull(serializedVibration.deserialize());
+        checkSerializerCondition(Objects.equals(expectedVibration, deserializedVibration),
+                "Unexpected serialized vibration %s: found deserialization %s, expected %s",
+                serializedVibration, deserializedVibration, expectedVibration);
+    }
+
+    /**
+     * Check generic serializer condition
+     *
+     * @throws XmlSerializerException if {@code expression} is false
+     */
+    public static void checkSerializerCondition(boolean expression,
+            String messageTemplate, Object... messageArgs) throws XmlSerializerException {
+        if (!expression) {
+            throw new XmlSerializerException(TextUtils.formatSimple(messageTemplate, messageArgs));
+        }
+    }
+
+    /**
+     * Check generic parser condition
+     *
+     * @throws XmlParserException if {@code expression} is false
+     */
+    public static void checkParserCondition(boolean expression,
+            String messageTemplate, Object... messageArgs) throws XmlParserException {
+        if (!expression) {
+            throw new XmlParserException(TextUtils.formatSimple(messageTemplate, messageArgs));
+        }
+    }
+}
diff --git a/core/java/com/android/internal/widget/FadingWearableScrollView.java b/core/java/com/android/internal/widget/FadingWearableScrollView.java
new file mode 100644
index 0000000..050952c
--- /dev/null
+++ b/core/java/com/android/internal/widget/FadingWearableScrollView.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.widget;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ScrollView;
+
+/** ScrollView that includes scaling and fading animations */
+public class FadingWearableScrollView extends ScrollView {
+    private ViewGroupFader mFader;
+
+    public FadingWearableScrollView(Context context) {
+        this(context, null);
+    }
+
+    public FadingWearableScrollView(Context context, AttributeSet attrs) {
+        this(context, attrs, com.android.internal.R.attr.scrollViewStyle);
+    }
+
+    public FadingWearableScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
+    }
+
+    public FadingWearableScrollView(Context context, AttributeSet attrs, int defStyleAttr,
+            int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+        init();
+    }
+
+    private void init() {
+        mFader = createFader(this);
+    }
+
+    /**
+     * Creates a simple ViewGroupFader that animates views from both top and bottom.
+     */
+    private ViewGroupFader createFader(ViewGroup container) {
+        return new ViewGroupFader(
+                container,
+                new ViewGroupFader.AnimationCallback() {
+                    @Override
+                    public boolean shouldFadeFromTop(View view) {
+                        return true;
+                    }
+
+                    @Override
+                    public boolean shouldFadeFromBottom(View view) {
+                        return true;
+                    }
+
+                    @Override
+                    public void viewHasBecomeFullSize(View view) {
+                    }
+                },
+                new ViewGroupFader.GlobalVisibleViewBoundsProvider());
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        super.onLayout(changed, l, t, r, b);
+        mFader.updateFade();
+    }
+
+    @Override
+    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
+        super.onScrollChanged(l, t, oldl, oldt);
+        mFader.updateFade();
+    }
+}
diff --git a/core/java/com/android/internal/widget/IRemoteViewsFactory.aidl b/core/java/com/android/internal/widget/IRemoteViewsFactory.aidl
index c06dab9..918d9c0 100644
--- a/core/java/com/android/internal/widget/IRemoteViewsFactory.aidl
+++ b/core/java/com/android/internal/widget/IRemoteViewsFactory.aidl
@@ -39,5 +39,6 @@
     boolean hasStableIds();
     @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
     boolean isCreated();
+    RemoteViews.RemoteCollectionItems getRemoteCollectionItems();
 }
 
diff --git a/core/java/com/android/internal/widget/ImageFloatingTextView.java b/core/java/com/android/internal/widget/ImageFloatingTextView.java
index 1ac5e1f..de10bd2 100644
--- a/core/java/com/android/internal/widget/ImageFloatingTextView.java
+++ b/core/java/com/android/internal/widget/ImageFloatingTextView.java
@@ -18,6 +18,7 @@
 
 import android.annotation.Nullable;
 import android.content.Context;
+import android.os.Trace;
 import android.text.BoringLayout;
 import android.text.Layout;
 import android.text.StaticLayout;
@@ -68,6 +69,7 @@
     protected Layout makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth,
             Layout.Alignment alignment, boolean shouldEllipsize,
             TextUtils.TruncateAt effectiveEllipsize, boolean useSaved) {
+        Trace.beginSection("ImageFloatingTextView#makeSingleLayout");
         TransformationMethod transformationMethod = getTransformationMethod();
         CharSequence text = getText();
         if (transformationMethod != null) {
@@ -110,7 +112,9 @@
             builder.setIndents(null, margins);
         }
 
-        return builder.build();
+        final StaticLayout result = builder.build();
+        Trace.endSection();
+        return result;
     }
 
     /**
@@ -135,6 +139,7 @@
 
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        Trace.beginSection("ImageFloatingTextView#onMeasure");
         int availableHeight = MeasureSpec.getSize(heightMeasureSpec) - mPaddingTop - mPaddingBottom;
         if (getLayout() != null && getLayout().getHeight() != availableHeight) {
             // We've been measured before and the new size is different than before, lets make sure
@@ -161,6 +166,7 @@
                 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
             }
         }
+        Trace.endSection();
     }
 
     @Override
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index e60a5c3..d5b8f62 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -47,6 +47,7 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemClock;
+import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.os.storage.StorageManager;
@@ -96,7 +97,7 @@
     public static final int MIN_LOCK_PATTERN_SIZE = 4;
 
     /**
-     * The minimum size of a valid password.
+     * The minimum size of a valid password or PIN.
      */
     public static final int MIN_LOCK_PASSWORD_SIZE = 4;
 
@@ -160,9 +161,17 @@
      */
     public static final int VERIFY_FLAG_REQUEST_GK_PW_HANDLE = 1 << 0;
 
+    /**
+     * Flag provided to {@link #verifyCredential(LockscreenCredential, int, int)} . If set, the
+     * method writes the password data to the repair mode file after the credential is verified
+     * successfully.
+     */
+    public static final int VERIFY_FLAG_WRITE_REPAIR_MODE_PW = 1 << 1;
+
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(flag = true, value = {
-            VERIFY_FLAG_REQUEST_GK_PW_HANDLE
+            VERIFY_FLAG_REQUEST_GK_PW_HANDLE,
+            VERIFY_FLAG_WRITE_REPAIR_MODE_PW
     })
     public @interface VerifyFlag {}
 
@@ -171,7 +180,11 @@
      */
     public static final int USER_FRP = UserHandle.USER_NULL + 1;
 
-    public final static String PATTERN_EVER_CHOSEN_KEY = "lockscreen.patterneverchosen";
+    /**
+     * Special user id for triggering the exiting repair mode verification flow.
+     */
+    public static final int USER_REPAIR_MODE = UserHandle.USER_NULL + 2;
+
     public final static String PASSWORD_TYPE_KEY = "lockscreen.password_type";
     @Deprecated
     public final static String PASSWORD_TYPE_ALTERNATE_KEY = "lockscreen.password_type_alternate";
@@ -201,6 +214,8 @@
     public static final String CURRENT_LSKF_BASED_PROTECTOR_ID_KEY = "sp-handle";
     public static final String PASSWORD_HISTORY_DELIMITER = ",";
 
+    private static final String GSI_RUNNING_PROP = "ro.gsid.image_running";
+
     /**
      * drives the pin auto confirmation feature availability in code logic.
      */
@@ -389,7 +404,7 @@
 
     @UnsupportedAppUsage
     public void reportFailedPasswordAttempt(int userId) {
-        if (userId == USER_FRP && frpCredentialEnabled(mContext)) {
+        if (isSpecialUserId(mContext, userId, /* checkDeviceSupported= */ true)) {
             return;
         }
         getDevicePolicyManager().reportFailedPasswordAttempt(userId);
@@ -398,7 +413,7 @@
 
     @UnsupportedAppUsage
     public void reportSuccessfulPasswordAttempt(int userId) {
-        if (userId == USER_FRP && frpCredentialEnabled(mContext)) {
+        if (isSpecialUserId(mContext, userId, /* checkDeviceSupported= */ true)) {
             return;
         }
         getDevicePolicyManager().reportSuccessfulPasswordAttempt(userId);
@@ -406,21 +421,21 @@
     }
 
     public void reportPasswordLockout(int timeoutMs, int userId) {
-        if (userId == USER_FRP && frpCredentialEnabled(mContext)) {
+        if (isSpecialUserId(mContext, userId, /* checkDeviceSupported= */ true)) {
             return;
         }
         getTrustManager().reportUnlockLockout(timeoutMs, userId);
     }
 
     public int getCurrentFailedPasswordAttempts(int userId) {
-        if (userId == USER_FRP && frpCredentialEnabled(mContext)) {
+        if (isSpecialUserId(mContext, userId, /* checkDeviceSupported= */ true)) {
             return 0;
         }
         return getDevicePolicyManager().getCurrentFailedPasswordAttempts(userId);
     }
 
     public int getMaximumFailedPasswordsForWipe(int userId) {
-        if (userId == USER_FRP && frpCredentialEnabled(mContext)) {
+        if (isSpecialUserId(mContext, userId, /* checkDeviceSupported= */ true)) {
             return 0;
         }
         return getDevicePolicyManager().getMaximumFailedPasswordsForWipe(
@@ -597,16 +612,6 @@
     }
 
     /**
-     * Return true if the user has ever chosen a pattern.  This is true even if the pattern is
-     * currently cleared.
-     *
-     * @return True if the user has ever chosen a pattern.
-     */
-    public boolean isPatternEverChosen(int userId) {
-        return getBoolean(PATTERN_EVER_CHOSEN_KEY, false, userId);
-    }
-
-    /**
      * Returns the length of the PIN set by a particular user.
      * @param userId user id of the user whose pin length we have to return
      * @return
@@ -639,13 +644,6 @@
             return false;
         }
     }
-    /**
-     * Records that the user has chosen a pattern at some time, even if the pattern is
-     * currently cleared.
-     */
-    public void reportPatternWasChosen(int userId) {
-        setBoolean(PATTERN_EVER_CHOSEN_KEY, true, userId);
-    }
 
     /**
      * Used by device policy manager to validate the current password
@@ -757,6 +755,17 @@
         }
     }
 
+    /** Returns the credential type corresponding to the given PIN or password quality. */
+    public static int pinOrPasswordQualityToCredentialType(int quality) {
+        if (isQualityAlphabeticPassword(quality)) {
+            return CREDENTIAL_TYPE_PASSWORD;
+        }
+        if (isQualityNumericPin(quality)) {
+            return CREDENTIAL_TYPE_PIN;
+        }
+        throw new IllegalArgumentException("Quality is neither Pin nor password: " + quality);
+    }
+
     /**
      * Save a new lockscreen credential.
      *
@@ -771,7 +780,6 @@
      * and return false if the given credential is wrong.
      * @throws RuntimeException if password change encountered an unrecoverable error.
      * @throws UnsupportedOperationException secure lockscreen is not supported on this device.
-     * @throws IllegalArgumentException if new credential is too short.
      */
     public boolean setLockCredential(@NonNull LockscreenCredential newCredential,
             @NonNull LockscreenCredential savedCredential, int userHandle) {
@@ -779,7 +787,6 @@
             throw new UnsupportedOperationException(
                     "This operation requires the lock screen feature.");
         }
-        newCredential.checkLength();
 
         try {
             if (!getLockSettings().setLockCredential(newCredential, savedCredential, userHandle)) {
@@ -991,7 +998,7 @@
                 }
                 @Override
                 public boolean shouldBypassCache(Integer userHandle) {
-                    return userHandle == USER_FRP;
+                    return isSpecialUserId(userHandle);
                 }
             };
 
@@ -1055,7 +1062,7 @@
      */
     @UnsupportedAppUsage
     public boolean isVisiblePatternEnabled(int userId) {
-        return getBoolean(Settings.Secure.LOCK_PATTERN_VISIBLE, false, userId);
+        return getBoolean(Settings.Secure.LOCK_PATTERN_VISIBLE, true, userId);
     }
 
     /**
@@ -1091,13 +1098,6 @@
     }
 
     /**
-     * Set whether the visible password is enabled for cryptkeeper screen.
-     */
-    public void setVisiblePasswordEnabled(boolean enabled, int userId) {
-        // No longer does anything.
-    }
-
-    /**
      * Set and store the lockout deadline, meaning the user can't attempt their unlock
      * pattern until the deadline has passed.
      * @return the chosen deadline.
@@ -1105,9 +1105,10 @@
     @UnsupportedAppUsage
     public long setLockoutAttemptDeadline(int userId, int timeoutMs) {
         final long deadline = SystemClock.elapsedRealtime() + timeoutMs;
-        if (userId == USER_FRP) {
-            // For secure password storage (that is required for FRP), the underlying storage also
-            // enforces the deadline. Since we cannot store settings for the FRP user, don't.
+        if (isSpecialUserId(userId)) {
+            // For secure password storage (that is required for special users such as FRP), the
+            // underlying storage also enforces the deadline. Since we cannot store settings
+            // for special users, don't.
             return deadline;
         }
         mLockoutDeadlines.put(userId, deadline);
@@ -1550,7 +1551,6 @@
             throw new UnsupportedOperationException(
                     "This operation requires the lock screen feature.");
         }
-        credential.checkLength();
         LockSettingsInternal localService = getLockSettingsInternal();
 
         return localService.setLockCredentialWithToken(credential, tokenHandle, token, userHandle);
@@ -1847,6 +1847,64 @@
     }
 
     /**
+     * Return {@code true} if repair mode is supported by the device.
+     */
+    public static boolean isRepairModeSupported(Context context) {
+        return context.getResources().getBoolean(
+                com.android.internal.R.bool.config_repairModeSupported);
+    }
+
+    /**
+     * Return {@code true} if repair mode is active on the device.
+     */
+    public static boolean isRepairModeActive(Context context) {
+        return Settings.Global.getInt(context.getContentResolver(),
+                Settings.Global.REPAIR_MODE_ACTIVE, /* def= */ 0) > 0;
+    }
+
+    /**
+     * Return {@code true} if repair mode is supported by the device and the user has been granted
+     * admin privileges.
+     */
+    public static boolean canUserEnterRepairMode(Context context, UserInfo info) {
+        return info != null && info.isAdmin() && isRepairModeSupported(context);
+    }
+
+    /**
+     * Return {@code true} if GSI is running on the device.
+     */
+    public static boolean isGsiRunning() {
+        return SystemProperties.getInt(GSI_RUNNING_PROP, 0) > 0;
+    }
+
+    /**
+     * Return {@code true} if the given user id is a special user such as {@link #USER_FRP}.
+     */
+    public static boolean isSpecialUserId(int userId) {
+        return isSpecialUserId(/* context= */ null, userId, /* checkDeviceSupported= */ false);
+    }
+
+    /**
+     * Return {@code true} if the given user id is a special user for the verification flow.
+     *
+     * @param checkDeviceSupported {@code true} to check the specified user is supported
+     *                             by the device.
+     */
+    private static boolean isSpecialUserId(@Nullable Context context, int userId,
+            boolean checkDeviceSupported) {
+        switch (userId) {
+            case USER_FRP:
+                if (checkDeviceSupported) return frpCredentialEnabled(context);
+                return true;
+
+            case USER_REPAIR_MODE:
+                if (checkDeviceSupported) return isRepairModeSupported(context);
+                return true;
+        }
+        return false;
+    }
+
+    /**
      * Attempt to rederive the unified work challenge for the specified profile user and unlock the
      * user. If successful, this would allow the user to leave quiet mode automatically without
      * additional user authentication.
diff --git a/core/java/com/android/internal/widget/LockPatternView.java b/core/java/com/android/internal/widget/LockPatternView.java
index fc5da13..c2cfcd6 100644
--- a/core/java/com/android/internal/widget/LockPatternView.java
+++ b/core/java/com/android/internal/widget/LockPatternView.java
@@ -80,15 +80,21 @@
     private static final int DOT_ACTIVATION_DURATION_MILLIS = 50;
     private static final int DOT_RADIUS_INCREASE_DURATION_MILLIS = 96;
     private static final int DOT_RADIUS_DECREASE_DURATION_MILLIS = 192;
+    private static final int ALPHA_MAX_VALUE = 255;
     private static final float MIN_DOT_HIT_FACTOR = 0.2f;
     private final CellState[][] mCellStates;
 
+    private static final int CELL_ACTIVATE = 0;
+    private static final int CELL_DEACTIVATE = 1;
+
     private final int mDotSize;
     private final int mDotSizeActivated;
     private final float mDotHitFactor;
     private final int mPathWidth;
     private final int mLineFadeOutAnimationDurationMs;
     private final int mLineFadeOutAnimationDelayMs;
+    private final int mFadePatternAnimationDurationMs;
+    private final int mFadePatternAnimationDelayMs;
 
     private boolean mDrawingProfilingStarted = false;
 
@@ -145,11 +151,16 @@
     private boolean mPatternInProgress = false;
     private boolean mFadePattern = true;
 
+    private boolean mFadeClear = false;
+    private int mFadeAnimationAlpha = ALPHA_MAX_VALUE;
+    private final Path mPatternPath = new Path();
+
     @UnsupportedAppUsage
     private float mSquareWidth;
     @UnsupportedAppUsage
     private float mSquareHeight;
     private float mDotHitRadius;
+    private float mDotHitMaxRadius;
     private final LinearGradient mFadeOutGradientShader;
 
     private final Path mCurrentPath = new Path();
@@ -162,9 +173,12 @@
     private int mSuccessColor;
     private int mDotColor;
     private int mDotActivatedColor;
+    private boolean mKeepDotActivated;
+    private boolean mEnlargeVertex;
 
     private final Interpolator mFastOutSlowInInterpolator;
     private final Interpolator mLinearOutSlowInInterpolator;
+    private final Interpolator mStandardAccelerateInterpolator;
     private final PatternExploreByTouchHelper mExploreByTouchHelper;
 
     private Drawable mSelectedDrawable;
@@ -335,6 +349,8 @@
         mSuccessColor = a.getColor(R.styleable.LockPatternView_successColor, 0);
         mDotColor = a.getColor(R.styleable.LockPatternView_dotColor, mRegularColor);
         mDotActivatedColor = a.getColor(R.styleable.LockPatternView_dotActivatedColor, mDotColor);
+        mKeepDotActivated = a.getBoolean(R.styleable.LockPatternView_keepDotActivated, false);
+        mEnlargeVertex = a.getBoolean(R.styleable.LockPatternView_enlargeVertexEntryArea, false);
 
         int pathColor = a.getColor(R.styleable.LockPatternView_pathColor, mRegularColor);
         mPathPaint.setColor(pathColor);
@@ -351,6 +367,11 @@
         mLineFadeOutAnimationDelayMs =
             getResources().getInteger(R.integer.lock_pattern_line_fade_out_delay);
 
+        mFadePatternAnimationDurationMs =
+                getResources().getInteger(R.integer.lock_pattern_fade_pattern_duration);
+        mFadePatternAnimationDelayMs =
+                getResources().getInteger(R.integer.lock_pattern_fade_pattern_delay);
+
         mDotSize = getResources().getDimensionPixelSize(R.dimen.lock_pattern_dot_size);
         mDotSizeActivated = getResources().getDimensionPixelSize(
                 R.dimen.lock_pattern_dot_size_activated);
@@ -381,6 +402,8 @@
                 AnimationUtils.loadInterpolator(context, android.R.interpolator.fast_out_slow_in);
         mLinearOutSlowInInterpolator =
                 AnimationUtils.loadInterpolator(context, android.R.interpolator.linear_out_slow_in);
+        mStandardAccelerateInterpolator =
+                AnimationUtils.loadInterpolator(context, android.R.interpolator.fast_out_linear_in);
         mExploreByTouchHelper = new PatternExploreByTouchHelper(this);
         setAccessibilityDelegate(mExploreByTouchHelper);
 
@@ -621,6 +644,15 @@
         resetPattern();
     }
 
+    /**
+     * Clear the pattern by fading it out.
+     */
+    @UnsupportedAppUsage
+    public void fadeClearPattern() {
+        mFadeClear = true;
+        startFadePatternAnimation();
+    }
+
     @Override
     protected boolean dispatchHoverEvent(MotionEvent event) {
         // Dispatch to onHoverEvent first so mPatternInProgress is up to date when the
@@ -634,12 +666,26 @@
      * Reset all pattern state.
      */
     private void resetPattern() {
+        if (mKeepDotActivated && !mPattern.isEmpty()) {
+            resetLastActivatedCellProgress();
+        }
         mPattern.clear();
+        mPatternPath.reset();
         clearPatternDrawLookup();
         mPatternDisplayMode = DisplayMode.Correct;
         invalidate();
     }
 
+    private void resetLastActivatedCellProgress() {
+        final ArrayList<Cell> pattern = mPattern;
+        final Cell lastCell = pattern.get(pattern.size() - 1);
+        final CellState cellState = mCellStates[lastCell.row][lastCell.column];
+        if (cellState.activationAnimator != null) {
+            cellState.activationAnimator.cancel();
+        }
+        cellState.activationAnimationProgress = 0f;
+    }
+
     /**
      * If there are any cells being drawn.
      */
@@ -686,7 +732,8 @@
         final int height = h - mPaddingTop - mPaddingBottom;
         mSquareHeight = height / 3.0f;
         mExploreByTouchHelper.invalidateRoot();
-        mDotHitRadius = Math.min(mSquareHeight / 2, mSquareWidth / 2) * mDotHitFactor;
+        mDotHitMaxRadius = Math.min(mSquareHeight / 2, mSquareWidth / 2);
+        mDotHitRadius = mDotHitMaxRadius * mDotHitFactor;
 
         if (mUseLockPatternDrawable) {
             mNotSelectedDrawable.setBounds(mPaddingLeft, mPaddingTop, width, height);
@@ -748,8 +795,9 @@
             // check for gaps in existing pattern
             Cell fillInGapCell = null;
             final ArrayList<Cell> pattern = mPattern;
+            Cell lastCell = null;
             if (!pattern.isEmpty()) {
-                final Cell lastCell = pattern.get(pattern.size() - 1);
+                lastCell = pattern.get(pattern.size() - 1);
                 int dRow = cell.row - lastCell.row;
                 int dColumn = cell.column - lastCell.column;
 
@@ -770,7 +818,15 @@
             if (fillInGapCell != null &&
                     !mPatternDrawLookup[fillInGapCell.row][fillInGapCell.column]) {
                 addCellToPattern(fillInGapCell);
+                if (mKeepDotActivated) {
+                    startCellDeactivatedAnimation(fillInGapCell);
+                }
             }
+
+            if (mKeepDotActivated && lastCell != null) {
+                startCellDeactivatedAnimation(lastCell);
+            }
+
             addCellToPattern(cell);
             performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY,
                     HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
@@ -788,7 +844,42 @@
         notifyCellAdded();
     }
 
+    private void startFadePatternAnimation() {
+        AnimatorSet animatorSet = new AnimatorSet();
+        animatorSet.play(createFadePatternAnimation());
+        animatorSet.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                mFadeAnimationAlpha = ALPHA_MAX_VALUE;
+                mFadeClear = false;
+                resetPattern();
+            }
+        });
+        animatorSet.start();
+
+    }
+
+    private Animator createFadePatternAnimation() {
+        ValueAnimator valueAnimator = ValueAnimator.ofInt(ALPHA_MAX_VALUE, 0);
+        valueAnimator.addUpdateListener(animation -> {
+            mFadeAnimationAlpha = (int) animation.getAnimatedValue();
+            invalidate();
+        });
+        valueAnimator.setInterpolator(mStandardAccelerateInterpolator);
+        valueAnimator.setStartDelay(mFadePatternAnimationDelayMs);
+        valueAnimator.setDuration(mFadePatternAnimationDurationMs);
+        return valueAnimator;
+    }
+
     private void startCellActivatedAnimation(Cell cell) {
+        startCellActivationAnimation(cell, CELL_ACTIVATE);
+    }
+
+    private void startCellDeactivatedAnimation(Cell cell) {
+        startCellActivationAnimation(cell, CELL_DEACTIVATE);
+    }
+
+    private void startCellActivationAnimation(Cell cell, int activate) {
         final CellState cellState = mCellStates[cell.row][cell.column];
 
         if (cellState.activationAnimator != null) {
@@ -803,7 +894,7 @@
             animatorSetBuilder.with(createDotRadiusAnimation(cellState));
         }
         if (mDotColor != mDotActivatedColor) {
-            animatorSetBuilder.with(createDotActivationColorAnimation(cellState));
+            animatorSetBuilder.with(createDotActivationColorAnimation(cellState, activate));
         }
 
         animatorSet.addListener(new AnimatorListenerAdapter() {
@@ -817,7 +908,7 @@
         animatorSet.start();
     }
 
-    private Animator createDotActivationColorAnimation(CellState cellState) {
+    private Animator createDotActivationColorAnimation(CellState cellState, int activate) {
         ValueAnimator.AnimatorUpdateListener updateListener =
                 valueAnimator -> {
                     cellState.activationAnimationProgress =
@@ -835,10 +926,17 @@
         activateAnimator.setDuration(DOT_ACTIVATION_DURATION_MILLIS);
         deactivateAnimator.setDuration(DOT_ACTIVATION_DURATION_MILLIS);
         AnimatorSet set = new AnimatorSet();
-        set.play(deactivateAnimator)
-                .after(mLineFadeOutAnimationDelayMs + mLineFadeOutAnimationDurationMs
-                        - DOT_ACTIVATION_DURATION_MILLIS * 2)
-                .after(activateAnimator);
+
+        if (mKeepDotActivated) {
+            set.play(activate == CELL_ACTIVATE ? activateAnimator : deactivateAnimator);
+        } else {
+            // 'activate' ignored in this case, do full deactivate -> activate cycle
+            set.play(deactivateAnimator)
+                    .after(mLineFadeOutAnimationDelayMs + mLineFadeOutAnimationDurationMs
+                            - DOT_ACTIVATION_DURATION_MILLIS * 2)
+                    .after(activateAnimator);
+        }
+
         return set;
     }
 
@@ -909,11 +1007,24 @@
     /** Helper method to find which cell a point maps to. */
     @Nullable
     private Cell detectCellHit(float x, float y) {
-        final float hitRadiusSquared = mDotHitRadius * mDotHitRadius;
         for (int row = 0; row < 3; row++) {
             for (int column = 0; column < 3; column++) {
                 float centerY = getCenterYForRow(row);
                 float centerX = getCenterXForColumn(column);
+                float hitRadiusSquared;
+
+                if (mEnlargeVertex) {
+                    // Maximize vertex dots' hit radius for the small screen.
+                    // This eases users to draw more patterns with diagnal lines, while keeps
+                    // drawing patterns with vertex dots easy.
+                    hitRadiusSquared =
+                            isVertex(row, column)
+                                    ? (mDotHitMaxRadius * mDotHitMaxRadius)
+                                    : (mDotHitRadius * mDotHitRadius);
+                } else {
+                    hitRadiusSquared = mDotHitRadius * mDotHitRadius;
+                }
+
                 if ((x - centerX) * (x - centerX) + (y - centerY) * (y - centerY)
                         < hitRadiusSquared) {
                     return Cell.of(row, column);
@@ -923,6 +1034,10 @@
         return null;
     }
 
+    private boolean isVertex(int row, int column) {
+        return !(row == 1 || column == 1);
+    }
+
     @Override
     public boolean onHoverEvent(MotionEvent event) {
         if (AccessibilityManager.getInstance(mContext).isTouchExplorationEnabled()) {
@@ -1055,6 +1170,9 @@
         if (!mPattern.isEmpty()) {
             setPatternInProgress(false);
             cancelLineAnimations();
+            if (mKeepDotActivated) {
+                deactivateLastCell();
+            }
             notifyPatternDetected();
             // Also clear pattern if fading is enabled
             if (mFadePattern) {
@@ -1071,6 +1189,11 @@
         }
     }
 
+    private void deactivateLastCell() {
+        Cell lastCell = mPattern.get(mPattern.size() - 1);
+        startCellDeactivatedAnimation(lastCell);
+    }
+
     private void cancelLineAnimations() {
         for (int i = 0; i < 3; i++) {
             for (int j = 0; j < 3; j++) {
@@ -1079,9 +1202,9 @@
                     state.activationAnimator.cancel();
                     state.activationAnimator = null;
                     state.radius = mDotSize / 2f;
-                    state.activationAnimationProgress = 0f;
                     state.lineEndX = Float.MIN_VALUE;
                     state.lineEndY = Float.MIN_VALUE;
+                    state.activationAnimationProgress = 0f;
                 }
             }
         }
@@ -1197,14 +1320,14 @@
         // draw the path of the pattern (unless we are in stealth mode)
         final boolean drawPath = !mInStealthMode;
 
-        if (drawPath) {
+        if (drawPath && !mFadeClear) {
             mPathPaint.setColor(getCurrentColor(true /* partOfPattern */));
 
             boolean anyCircles = false;
             float lastX = 0f;
             float lastY = 0f;
             long elapsedRealtime = SystemClock.elapsedRealtime();
-           for (int i = 0; i < count; i++) {
+            for (int i = 0; i < count; i++) {
                 Cell cell = pattern.get(i);
 
                 // only draw the part of the pattern stored in
@@ -1235,6 +1358,11 @@
                     }
                     drawLineSegment(canvas, /* startX = */ lastX, /* startY = */ lastY, endX, endY,
                             mLineFadeStart[i], elapsedRealtime);
+
+                    Path tempPath = new Path();
+                    tempPath.moveTo(lastX, lastY);
+                    tempPath.lineTo(centerX, centerY);
+                    mPatternPath.addPath(tempPath);
                 }
                 lastX = centerX;
                 lastY = centerY;
@@ -1253,6 +1381,11 @@
             }
         }
 
+        if (mFadeClear) {
+            mPathPaint.setAlpha(mFadeAnimationAlpha);
+            canvas.drawPath(mPatternPath, mPathPaint);
+        }
+
         // draw the circles
         for (int i = 0; i < 3; i++) {
             float centerY = getCenterYForRow(i);
@@ -1376,6 +1509,8 @@
             int resultColor = ColorUtils.blendARGB(mDotColor, mDotActivatedColor,
                     /* ratio= */ activationAnimationProgress);
             mPaint.setColor(resultColor);
+        } else if (!mFadePattern && partOfPattern){
+            mPaint.setColor(mDotActivatedColor);
         } else {
             mPaint.setColor(getDotColor());
         }
diff --git a/core/java/com/android/internal/widget/LockscreenCredential.java b/core/java/com/android/internal/widget/LockscreenCredential.java
index 03e7fd1..c88763c 100644
--- a/core/java/com/android/internal/widget/LockscreenCredential.java
+++ b/core/java/com/android/internal/widget/LockscreenCredential.java
@@ -60,10 +60,24 @@
 public class LockscreenCredential implements Parcelable, AutoCloseable {
 
     private final int mType;
-    // Stores raw credential bytes, or null if credential has been zeroized. An empty password
+    // Stores raw credential bytes, or null if credential has been zeroized. A none credential
     // is represented as a byte array of length 0.
     private byte[] mCredential;
 
+    // This indicates that the credential used characters outside ASCII 32–127.
+    //
+    // Such credentials were never intended to be allowed.  However, Android 10–14 had a bug where
+    // conversion from the chars the user entered to the credential bytes used a simple truncation.
+    // Thus, any 'char' whose remainder mod 256 was in the range 32–127 was accepted and was
+    // equivalent to some ASCII character.  For example, ™, which is U+2122, was truncated to ASCII
+    // 0x22 which is the double-quote character ".
+    //
+    // We have to continue to allow a LockscreenCredential to be constructed with this bug, so that
+    // existing devices can be unlocked if their password used this bug.  However, we prevent new
+    // passwords that use this bug from being set.  The boolean below keeps track of the information
+    // needed to do that check, since the conversion to mCredential may have been lossy.
+    private final boolean mHasInvalidChars;
+
     /**
      * Private constructor, use static builder methods instead.
      *
@@ -71,7 +85,7 @@
      * LockscreenCredential will only store the reference internally without copying. This is to
      * minimize the number of extra copies introduced.
      */
-    private LockscreenCredential(int type, byte[] credential) {
+    private LockscreenCredential(int type, byte[] credential, boolean hasInvalidChars) {
         Objects.requireNonNull(credential);
         if (type == CREDENTIAL_TYPE_NONE) {
             Preconditions.checkArgument(credential.length == 0);
@@ -80,17 +94,28 @@
             Preconditions.checkArgument(type == CREDENTIAL_TYPE_PIN
                     || type == CREDENTIAL_TYPE_PASSWORD
                     || type == CREDENTIAL_TYPE_PATTERN);
-            Preconditions.checkArgument(credential.length > 0);
+            // Do not validate credential.length yet.  All non-none credentials have a minimum
+            // length requirement; however, one of the uses of LockscreenCredential is to represent
+            // a proposed credential that might be too short.  For example, a LockscreenCredential
+            // with type CREDENTIAL_TYPE_PIN and length 0 represents an attempt to set an empty PIN.
+            // This differs from an actual attempt to set a none credential.  We have to allow the
+            // LockscreenCredential object to be constructed so that the validation logic can run,
+            // even though the validation logic will ultimately reject the credential as too short.
         }
         mType = type;
         mCredential = credential;
+        mHasInvalidChars = hasInvalidChars;
+    }
+
+    private LockscreenCredential(int type, CharSequence credential) {
+        this(type, charsToBytesTruncating(credential), hasInvalidChars(credential));
     }
 
     /**
-     * Creates a LockscreenCredential object representing empty password.
+     * Creates a LockscreenCredential object representing a none credential.
      */
     public static LockscreenCredential createNone() {
-        return new LockscreenCredential(CREDENTIAL_TYPE_NONE, new byte[0]);
+        return new LockscreenCredential(CREDENTIAL_TYPE_NONE, new byte[0], false);
     }
 
     /**
@@ -98,15 +123,14 @@
      */
     public static LockscreenCredential createPattern(@NonNull List<LockPatternView.Cell> pattern) {
         return new LockscreenCredential(CREDENTIAL_TYPE_PATTERN,
-                LockPatternUtils.patternToByteArray(pattern));
+                LockPatternUtils.patternToByteArray(pattern), /* hasInvalidChars= */ false);
     }
 
     /**
      * Creates a LockscreenCredential object representing the given alphabetic password.
      */
     public static LockscreenCredential createPassword(@NonNull CharSequence password) {
-        return new LockscreenCredential(CREDENTIAL_TYPE_PASSWORD,
-                charSequenceToByteArray(password));
+        return new LockscreenCredential(CREDENTIAL_TYPE_PASSWORD, password);
     }
 
     /**
@@ -117,20 +141,19 @@
      */
     public static LockscreenCredential createManagedPassword(@NonNull byte[] password) {
         return new LockscreenCredential(CREDENTIAL_TYPE_PASSWORD,
-                Arrays.copyOf(password, password.length));
+                Arrays.copyOf(password, password.length), /* hasInvalidChars= */ false);
     }
 
     /**
      * Creates a LockscreenCredential object representing the given numeric PIN.
      */
     public static LockscreenCredential createPin(@NonNull CharSequence pin) {
-        return new LockscreenCredential(CREDENTIAL_TYPE_PIN,
-                charSequenceToByteArray(pin));
+        return new LockscreenCredential(CREDENTIAL_TYPE_PIN, pin);
     }
 
     /**
      * Creates a LockscreenCredential object representing the given alphabetic password.
-     * If the supplied password is empty, create an empty credential object.
+     * If the supplied password is empty, create a none credential object.
      */
     public static LockscreenCredential createPasswordOrNone(@Nullable CharSequence password) {
         if (TextUtils.isEmpty(password)) {
@@ -142,7 +165,7 @@
 
     /**
      * Creates a LockscreenCredential object representing the given numeric PIN.
-     * If the supplied password is empty, create an empty credential object.
+     * If the supplied password is empty, create a none credential object.
      */
     public static LockscreenCredential createPinOrNone(@Nullable CharSequence pin) {
         if (TextUtils.isEmpty(pin)) {
@@ -175,7 +198,7 @@
         return mCredential;
     }
 
-    /** Returns whether this is an empty credential */
+    /** Returns whether this is a none credential */
     public boolean isNone() {
         ensureNotZeroized();
         return mType == CREDENTIAL_TYPE_NONE;
@@ -205,10 +228,17 @@
         return mCredential.length;
     }
 
+    /** Returns true if this credential was constructed with any chars outside the allowed range */
+    public boolean hasInvalidChars() {
+        ensureNotZeroized();
+        return mHasInvalidChars;
+    }
+
     /** Create a copy of the credential */
     public LockscreenCredential duplicate() {
         return new LockscreenCredential(mType,
-                mCredential != null ? Arrays.copyOf(mCredential, mCredential.length) : null);
+                mCredential != null ? Arrays.copyOf(mCredential, mCredential.length) : null,
+                mHasInvalidChars);
     }
 
     /**
@@ -222,27 +252,37 @@
     }
 
     /**
-     * Check if the credential meets minimal length requirement.
+     * Checks whether the credential meets basic requirements for setting it as a new credential.
      *
-     * @throws IllegalArgumentException if the credential is too short.
+     * This is redundant if {@link android.app.admin.PasswordMetrics#validateCredential()}, which
+     * does more comprehensive checks, is correctly called first (which it should be).
+     *
+     * @throws IllegalArgumentException if the credential contains invalid characters or is too
+     * short
      */
-    public void checkLength() {
-        if (isNone()) {
-            return;
+    public void validateBasicRequirements() {
+        if (mHasInvalidChars) {
+            throw new IllegalArgumentException("credential contains invalid characters");
         }
-        if (isPattern()) {
-            if (size() < LockPatternUtils.MIN_LOCK_PATTERN_SIZE) {
-                throw new IllegalArgumentException("pattern must not be null and at least "
-                        + LockPatternUtils.MIN_LOCK_PATTERN_SIZE + " dots long.");
-            }
-            return;
-        }
-        if (isPassword() || isPin()) {
-            if (size() < LockPatternUtils.MIN_LOCK_PASSWORD_SIZE) {
-                throw new IllegalArgumentException("password must not be null and at least "
-                        + "of length " + LockPatternUtils.MIN_LOCK_PASSWORD_SIZE);
-            }
-            return;
+        switch (getType()) {
+            case CREDENTIAL_TYPE_PATTERN:
+                if (size() < LockPatternUtils.MIN_LOCK_PATTERN_SIZE) {
+                    throw new IllegalArgumentException("pattern must be at least "
+                            + LockPatternUtils.MIN_LOCK_PATTERN_SIZE + " dots long.");
+                }
+                break;
+            case CREDENTIAL_TYPE_PIN:
+                if (size() < LockPatternUtils.MIN_LOCK_PASSWORD_SIZE) {
+                    throw new IllegalArgumentException("PIN must be at least "
+                            + LockPatternUtils.MIN_LOCK_PASSWORD_SIZE + " digits long.");
+                }
+                break;
+            case CREDENTIAL_TYPE_PASSWORD:
+                if (size() < LockPatternUtils.MIN_LOCK_PASSWORD_SIZE) {
+                    throw new IllegalArgumentException("password must be at least "
+                            + LockPatternUtils.MIN_LOCK_PASSWORD_SIZE + " characters long.");
+                }
+                break;
         }
     }
 
@@ -317,6 +357,7 @@
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeInt(mType);
         dest.writeByteArray(mCredential);
+        dest.writeBoolean(mHasInvalidChars);
     }
 
     public static final Parcelable.Creator<LockscreenCredential> CREATOR =
@@ -324,7 +365,8 @@
 
         @Override
         public LockscreenCredential createFromParcel(Parcel source) {
-            return new LockscreenCredential(source.readInt(), source.createByteArray());
+            return new LockscreenCredential(source.readInt(), source.createByteArray(),
+                    source.readBoolean());
         }
 
         @Override
@@ -346,7 +388,7 @@
     @Override
     public int hashCode() {
         // Effective Java — Always override hashCode when you override equals
-        return Objects.hash(mType, Arrays.hashCode(mCredential));
+        return Objects.hash(mType, Arrays.hashCode(mCredential), mHasInvalidChars);
     }
 
     @Override
@@ -354,20 +396,45 @@
         if (o == this) return true;
         if (!(o instanceof LockscreenCredential)) return false;
         final LockscreenCredential other = (LockscreenCredential) o;
-        return mType == other.mType && Arrays.equals(mCredential, other.mCredential);
+        return mType == other.mType && Arrays.equals(mCredential, other.mCredential)
+            && mHasInvalidChars == other.mHasInvalidChars;
+    }
+
+    private static boolean hasInvalidChars(CharSequence chars) {
+        //
+        // Consider the password to have invalid characters if it contains any non-ASCII characters
+        // or control characters.  There are multiple reasons for this restriction:
+        //
+        // - Non-ASCII characters might only be possible to enter on a third-party keyboard app
+        //   (IME) that is available when setting the password but not when verifying it after a
+        //   reboot.  This can happen if the keyboard is not direct boot aware or gets uninstalled.
+        //
+        // - Unicode strings that look identical to the user can map to different byte[].  Yet, only
+        //   one byte[] can be accepted.  Unicode normalization can solve this problem to some
+        //   extent, but still many Unicode characters look similar and could cause confusion.
+        //
+        // - For backwards compatibility reasons, the upper 8 bits of the 16-bit 'chars' are
+        //   discarded by charsToBytesTruncating().  Thus, as-is passwords with characters above
+        //   U+00FF (255) are not as secure as they should be.  IMPORTANT: Do not change the below
+        //   code to allow characters above U+00FF (255) without fixing this issue!
+        //
+        for (int i = 0; i < chars.length(); i++) {
+            char c = chars.charAt(i);
+            if (c < 32 || c > 127) {
+                return true;
+            }
+        }
+        return false;
     }
 
     /**
-     * Converts a CharSequence to a byte array without requiring a toString(), which creates an
-     * additional copy.
+     * Converts a CharSequence to a byte array, intentionally truncating chars greater than 255 for
+     * backwards compatibility reasons.  See {@link #mHasInvalidChars}.
      *
      * @param chars The CharSequence to convert
      * @return A byte array representing the input
      */
-    private static byte[] charSequenceToByteArray(CharSequence chars) {
-        if (chars == null) {
-            return new byte[0];
-        }
+    private static byte[] charsToBytesTruncating(CharSequence chars) {
         byte[] bytes = new byte[chars.length()];
         for (int i = 0; i < chars.length(); i++) {
             bytes[i] = (byte) chars.charAt(i);
diff --git a/core/java/com/android/internal/widget/NotificationExpandButton.java b/core/java/com/android/internal/widget/NotificationExpandButton.java
index 07ee9b5..d4dd1e7 100644
--- a/core/java/com/android/internal/widget/NotificationExpandButton.java
+++ b/core/java/com/android/internal/widget/NotificationExpandButton.java
@@ -21,6 +21,8 @@
 import android.content.Context;
 import android.content.res.ColorStateList;
 import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.LayerDrawable;
 import android.util.AttributeSet;
 import android.view.RemotableViewMethod;
 import android.view.View;
@@ -42,7 +44,7 @@
 @RemoteViews.RemoteView
 public class NotificationExpandButton extends FrameLayout {
 
-    private View mPillView;
+    private Drawable mPillDrawable;
     private TextView mNumberView;
     private ImageView mIconView;
     private boolean mExpanded;
@@ -73,7 +75,10 @@
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
-        mPillView = findViewById(R.id.expand_button_pill);
+
+        final View pillView = findViewById(R.id.expand_button_pill);
+        final LayerDrawable layeredPill = (LayerDrawable) pillView.getBackground();
+        mPillDrawable = layeredPill.findDrawableByLayerId(R.id.expand_button_pill_colorized_layer);
         mNumberView = findViewById(R.id.expand_button_number);
         mIconView = findViewById(R.id.expand_button_icon);
     }
@@ -156,7 +161,7 @@
     private void updateColors() {
         if (shouldShowNumber()) {
             if (mHighlightPillColor != 0) {
-                mPillView.setBackgroundTintList(ColorStateList.valueOf(mHighlightPillColor));
+                mPillDrawable.setTintList(ColorStateList.valueOf(mHighlightPillColor));
             }
             mIconView.setColorFilter(mHighlightTextColor);
             if (mHighlightTextColor != 0) {
@@ -164,7 +169,7 @@
             }
         } else {
             if (mDefaultPillColor != 0) {
-                mPillView.setBackgroundTintList(ColorStateList.valueOf(mDefaultPillColor));
+                mPillDrawable.setTintList(ColorStateList.valueOf(mDefaultPillColor));
             }
             mIconView.setColorFilter(mDefaultTextColor);
             if (mDefaultTextColor != 0) {
diff --git a/core/java/com/android/internal/widget/OWNERS b/core/java/com/android/internal/widget/OWNERS
index d068a3a..e2672f5 100644
--- a/core/java/com/android/internal/widget/OWNERS
+++ b/core/java/com/android/internal/widget/OWNERS
@@ -21,3 +21,6 @@
 per-file ObservableTextView.java = file:/services/core/java/com/android/server/notification/OWNERS
 per-file RemeasuringLinearLayout.java = file:/services/core/java/com/android/server/notification/OWNERS
 per-file ViewClippingUtil.java = file:/services/core/java/com/android/server/notification/OWNERS
+
+# Appwidget related
+per-file *RemoteViews* = file:/services/appwidget/java/com/android/server/appwidget/OWNERS
diff --git a/core/java/com/android/internal/widget/PointerLocationView.java b/core/java/com/android/internal/widget/PointerLocationView.java
index f55d15d..e65b4b6 100644
--- a/core/java/com/android/internal/widget/PointerLocationView.java
+++ b/core/java/com/android/internal/widget/PointerLocationView.java
@@ -163,6 +163,8 @@
     @UnsupportedAppUsage
     private boolean mPrintCoords = true;
 
+    private float mDensity;
+
     public PointerLocationView(Context c) {
         super(c);
         setFocusableInTouchMode(true);
@@ -365,11 +367,8 @@
                 drawOval(canvas, ps.mCoords.x, ps.mCoords.y, ps.mCoords.toolMajor,
                         ps.mCoords.toolMinor, ps.mCoords.orientation, mPaint);
 
-                // Draw the orientation arrow.
-                float arrowSize = ps.mCoords.toolMajor * 0.7f;
-                if (arrowSize < 20) {
-                    arrowSize = 20;
-                }
+                // Draw the orientation arrow, and ensure it has a minimum size of 24dp.
+                final float arrowSize = Math.max(ps.mCoords.toolMajor * 0.7f, 24 * mDensity);
                 mPaint.setARGB(255, pressureLevel, 255, 0);
                 float orientationVectorX = (float) (Math.sin(ps.mCoords.orientation)
                         * arrowSize);
@@ -398,7 +397,7 @@
                 canvas.drawCircle(
                         ps.mCoords.x + orientationVectorX * tiltScale,
                         ps.mCoords.y + orientationVectorY * tiltScale,
-                        3.0f, mPaint);
+                        3.0f * mDensity, mPaint);
 
                 // Draw the current bounding box
                 if (ps.mHasBoundingBox) {
@@ -1003,10 +1002,10 @@
 
     // Compute size by display density.
     private void configureDensityDependentFactors() {
-        final float density = getResources().getDisplayMetrics().density;
-        mTextPaint.setTextSize(10 * density);
-        mPaint.setStrokeWidth(1 * density);
-        mCurrentPointPaint.setStrokeWidth(1 * density);
-        mPathPaint.setStrokeWidth(1 * density);
+        mDensity = getResources().getDisplayMetrics().density;
+        mTextPaint.setTextSize(10 * mDensity);
+        mPaint.setStrokeWidth(1 * mDensity);
+        mCurrentPointPaint.setStrokeWidth(1 * mDensity);
+        mPathPaint.setStrokeWidth(1 * mDensity);
     }
 }
diff --git a/core/java/com/android/internal/widget/ViewGroupFader.java b/core/java/com/android/internal/widget/ViewGroupFader.java
new file mode 100644
index 0000000..b54023a
--- /dev/null
+++ b/core/java/com/android/internal/widget/ViewGroupFader.java
@@ -0,0 +1,315 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.widget;
+
+import android.graphics.Rect;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewGroup.MarginLayoutParams;
+import android.view.animation.BaseInterpolator;
+import android.view.animation.PathInterpolator;
+
+/**
+ * This class is ported from
+ * com.google.android.clockwork.common.wearable.wearmaterial.list.ViewGroupFader with minor
+ * modifications set the opacity of the views during animation (uses setTransitionAlpha on the view
+ * instead of setLayerType as the latter doesn't play nicely with a dialog. See - b/193583546)
+ *
+ * Fades of the children of a {@link ViewGroup} in and out, based on the position of the child.
+ *
+ * <p>Children are "faded" when they lie entirely in a region on the top and bottom of a {@link
+ * ViewGroup}. This region is sized as a fraction of the {@link ViewGroup}'s height, based on the
+ * height of the child. When not in the top or bottom regions, children have their default alpha and
+ * scale.
+ */
+class ViewGroupFader {
+
+    private static final float SCALE_LOWER_BOUND = 0.7f;
+    private float mScaleLowerBound = SCALE_LOWER_BOUND;
+
+    private static final float ALPHA_LOWER_BOUND = 0.5f;
+    private float mAlphaLowerBound = ALPHA_LOWER_BOUND;
+
+    private static final float CHAINED_BOUNDS_TOP_FRACTION = 0.6f;
+    private static final float CHAINED_BOUNDS_BOTTOM_FRACTION = 0.2f;
+    private static final float CHAINED_LOWER_REGION_FRACTION = 0.35f;
+    private static final float CHAINED_UPPER_REGION_FRACTION = 0.55f;
+
+    private float mChainedBoundsTop = CHAINED_BOUNDS_TOP_FRACTION;
+    private float mChainedBoundsBottom = CHAINED_BOUNDS_BOTTOM_FRACTION;
+    private float mChainedLowerRegion = CHAINED_LOWER_REGION_FRACTION;
+    private float mChainedUpperRegion = CHAINED_UPPER_REGION_FRACTION;
+
+    protected final ViewGroup mParent;
+
+    private final Rect mContainerBounds = new Rect();
+    private final Rect mOffsetViewBounds = new Rect();
+    private final AnimationCallback mCallback;
+    private final ChildViewBoundsProvider mChildViewBoundsProvider;
+
+    private ContainerBoundsProvider mContainerBoundsProvider;
+    private float mTopBoundPixels;
+    private float mBottomBoundPixels;
+    private BaseInterpolator mTopInterpolator = new PathInterpolator(0.3f, 0f, 0.7f, 1f);
+    private BaseInterpolator mBottomInterpolator = new PathInterpolator(0.3f, 0f, 0.7f, 1f);
+
+    /** Callback which is called when attempting to fade a view. */
+    interface AnimationCallback {
+        boolean shouldFadeFromTop(View view);
+
+        boolean shouldFadeFromBottom(View view);
+
+        void viewHasBecomeFullSize(View view);
+    }
+
+    /**
+     * Interface for providing the bounds of the child views. This is needed because for
+     * RecyclerViews, we might need to use bounds that represents the post-layout position, instead
+     * of the current position.
+     */
+    // TODO(b/182846214): Clean up the interface design to avoid exposing too much details to users.
+    interface ChildViewBoundsProvider {
+        /**
+         * Provide the bounds of the child view.
+         *
+         * @param parent the parent container.
+         * @param child the child view.
+         * @param bounds the bounds of the child view. The bounds are relative to
+         * the value of the bounds for setContainerBoundsProvider. By default,
+         * this is relative to the screen.
+         */
+        void provideBounds(ViewGroup parent, View child, Rect bounds);
+    }
+
+    /** Interface for providing the bounds of the container for use in calculating item fades. */
+    interface ContainerBoundsProvider {
+        /**
+         * Provide the bounds of the container for use in calculating item fades.
+         *
+         * @param parent the parent of the container.
+         * @param bounds the baseline bounds to which the child bounds are relative.
+         */
+        void provideBounds(ViewGroup parent, Rect bounds);
+    }
+
+    /**
+     * Implementation of {@link ContainerBoundsProvider} that returns the screen bounds as the
+     * container that is used for calculating the animation of the child elements in the ViewGroup.
+     */
+    static final class ScreenContainerBoundsProvider implements ContainerBoundsProvider {
+        @Override
+        public void provideBounds(ViewGroup parent, Rect bounds) {
+            bounds.set(
+                    0,
+                    0,
+                    parent.getResources().getDisplayMetrics().widthPixels,
+                    parent.getResources().getDisplayMetrics().heightPixels);
+        }
+    }
+
+    /**
+     * Implementation of {@link ContainerBoundsProvider} that returns the parent ViewGroup bounds as
+     * the container that is used for calculating the animation of the child elements in the
+     * ViewGroup.
+     */
+    static final class ParentContainerBoundsProvider implements ContainerBoundsProvider {
+        @Override
+        public void provideBounds(ViewGroup parent, Rect bounds) {
+            parent.getGlobalVisibleRect(bounds);
+        }
+    }
+
+    /**
+     * Default implementation of {@link ChildViewBoundsProvider} that returns the post-layout
+     * bounds of the child view. This should be used when the {@link ViewGroupFader} is used
+     * together with a RecyclerView.
+     */
+    static final class DefaultViewBoundsProvider implements ChildViewBoundsProvider {
+        @Override
+        public void provideBounds(ViewGroup parent, View child, Rect bounds) {
+            child.getDrawingRect(bounds);
+            bounds.offset(0, (int) child.getTranslationY());
+            parent.offsetDescendantRectToMyCoords(child, bounds);
+
+            // Additionally offset the bounds based on parent container's absolute position.
+            Rect parentGlobalVisibleBounds = new Rect();
+            parent.getGlobalVisibleRect(parentGlobalVisibleBounds);
+            bounds.offset(parentGlobalVisibleBounds.left, parentGlobalVisibleBounds.top);
+        }
+    }
+
+    /**
+     * Implementation of {@link ChildViewBoundsProvider} that returns the global visible bounds of
+     * the child view. This should be used when the {@link ViewGroupFader} is not used together with
+     * a RecyclerView.
+     */
+    static final class GlobalVisibleViewBoundsProvider implements ChildViewBoundsProvider {
+        @Override
+        public void provideBounds(ViewGroup parent, View child, Rect bounds) {
+            // Get the absolute position of the child. Normally we'd need to also reset the
+            // transformation matrix before computing this, but the transformations we apply set
+            // a pivot that preserves the coordinate of the top/bottom boundary used to compute the
+            // scaling factor in the first place.
+            child.getGlobalVisibleRect(bounds);
+        }
+    }
+
+    ViewGroupFader(
+            ViewGroup parent,
+            AnimationCallback callback,
+            ChildViewBoundsProvider childViewBoundsProvider) {
+        this.mParent = parent;
+        this.mCallback = callback;
+        this.mChildViewBoundsProvider = childViewBoundsProvider;
+        this.mContainerBoundsProvider = new ScreenContainerBoundsProvider();
+    }
+
+    AnimationCallback getAnimationCallback() {
+        return mCallback;
+    }
+
+    /**
+     * Sets the lower bound of the scale the view can reach, on a scale of 0 to 1.
+     *
+     * @param scale the value for the lower bound of the scale.
+     */
+    void setScaleLowerBound(float scale) {
+        mScaleLowerBound = scale;
+    }
+
+    /**
+     * Sets the lower bound of the alpha the view can reach, on a scale of 0 to 1.
+     *
+     * @param alpha the value for the lower bound of the alpha.
+     */
+    void setAlphaLowerBound(float alpha) {
+        mAlphaLowerBound = alpha;
+    }
+
+    void setTopInterpolator(BaseInterpolator interpolator) {
+        this.mTopInterpolator = interpolator;
+    }
+
+    void setBottomInterpolator(BaseInterpolator interpolator) {
+        this.mBottomInterpolator = interpolator;
+    }
+
+    void setContainerBoundsProvider(ContainerBoundsProvider boundsProvider) {
+        this.mContainerBoundsProvider = boundsProvider;
+    }
+
+    void updateFade() {
+        mContainerBoundsProvider.provideBounds(mParent, mContainerBounds);
+        mTopBoundPixels = mContainerBounds.height() * mChainedBoundsTop;
+        mBottomBoundPixels = mContainerBounds.height() * mChainedBoundsBottom;
+
+        updateListElementFades(mParent, true);
+    }
+
+    /** For each list element, calculate and adjust the scale and alpha based on its position */
+    private void updateListElementFades(ViewGroup parent, boolean shouldFade) {
+        for (int i = 0; i < parent.getChildCount(); i++) {
+            View child = parent.getChildAt(i);
+            if (child.getVisibility() != View.VISIBLE) {
+                continue;
+            }
+
+            if (shouldFade) {
+                fadeElement(parent, child);
+            }
+        }
+    }
+
+    private void fadeElement(ViewGroup parent, View child) {
+        mChildViewBoundsProvider.provideBounds(parent, child, mOffsetViewBounds);
+        setViewPropertiesByPosition(child, mOffsetViewBounds, mTopBoundPixels, mBottomBoundPixels);
+    }
+
+    /** Set the bounds and change the view's scale and alpha accordingly */
+    private void setViewPropertiesByPosition(
+            View view, Rect bounds, float topBoundPixels, float bottomBoundPixels) {
+        float fadeOutRegionFraction;
+        if (view.getHeight() < topBoundPixels && view.getHeight() > bottomBoundPixels) {
+            // Scale from LOWER_REGION_FRACTION to UPPER_REGION_FRACTION based on the ratio of view
+            // height to chain region height.
+            fadeOutRegionFraction = lerp(
+                    mChainedLowerRegion,
+                    mChainedUpperRegion,
+                    (view.getHeight() - bottomBoundPixels) / (topBoundPixels - bottomBoundPixels));
+        } else if (view.getHeight() < bottomBoundPixels) {
+            fadeOutRegionFraction = mChainedLowerRegion;
+        } else {
+            fadeOutRegionFraction = mChainedUpperRegion;
+        }
+        int fadeOutRegionHeight = (int) (mContainerBounds.height() * fadeOutRegionFraction);
+        int topFadeBoundary = fadeOutRegionHeight + mContainerBounds.top;
+        int bottomFadeBoundary = mContainerBounds.bottom - fadeOutRegionHeight;
+        boolean wasFullSize = (view.getScaleX() == 1);
+
+        MarginLayoutParams lp = (MarginLayoutParams) view.getLayoutParams();
+        view.setPivotX(view.getWidth() * 0.5f);
+        if (bounds.top > bottomFadeBoundary && mCallback.shouldFadeFromBottom(view)) {
+            view.setPivotY((float) -lp.topMargin);
+            scaleAndFadeByRelativeOffsetFraction(
+                    view,
+                    mBottomInterpolator.getInterpolation(
+                            (float) (mContainerBounds.bottom - bounds.top) / fadeOutRegionHeight));
+        } else if (bounds.bottom < topFadeBoundary && mCallback.shouldFadeFromTop(view)) {
+            view.setPivotY(view.getMeasuredHeight() + (float) lp.bottomMargin);
+            scaleAndFadeByRelativeOffsetFraction(
+                    view,
+                    mTopInterpolator.getInterpolation(
+                            (float) (bounds.bottom - mContainerBounds.top) / fadeOutRegionHeight));
+        } else {
+            if (!wasFullSize) {
+                mCallback.viewHasBecomeFullSize(view);
+            }
+            setDefaultSizeAndAlphaForView(view);
+        }
+    }
+
+    /**
+     * Change the scale and opacity of the view based on its offset fraction to the
+     * determining bound.
+     */
+    private void scaleAndFadeByRelativeOffsetFraction(View view, float offsetFraction) {
+        float alpha = lerp(mAlphaLowerBound, 1, offsetFraction);
+        view.setTransitionAlpha(alpha);
+        float scale = lerp(mScaleLowerBound, 1, offsetFraction);
+        view.setScaleX(scale);
+        view.setScaleY(scale);
+    }
+
+    /** Set the scale and alpha of the view to the full default */
+    private void setDefaultSizeAndAlphaForView(View view) {
+        view.setTransitionAlpha(1f);
+        view.setScaleX(1f);
+        view.setScaleY(1f);
+    }
+
+    /**
+     * Linear interpolation between [min, max] using [fraction].
+     *
+     * @param min   the starting point of the interpolation range.
+     * @param max   the ending point of the interpolation range.
+     * @param fraction the proportion of the range to linearly interpolate for.
+     * @return the interpolated value.
+     */
+    private static float lerp(float min, float max, float fraction) {
+        return min + (max - min) * fraction;
+    }
+}
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 42d6896..c19265a 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -123,6 +123,7 @@
                 "android_database_SQLiteConnection.cpp",
                 "android_database_SQLiteGlobal.cpp",
                 "android_database_SQLiteDebug.cpp",
+                "android_database_SQLiteRawStatement.cpp",
                 "android_graphics_GraphicBuffer.cpp",
                 "android_graphics_SurfaceTexture.cpp",
                 "android_view_CompositionSamplingListener.cpp",
@@ -378,7 +379,6 @@
                 "libbinary_parse",
                 "libdng_sdk",
                 "libft2",
-                "libhostgraphics",
                 "libhwui",
                 "libimage_type_recognition",
                 "libjpeg",
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index e5d5676..9aa992b 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -137,6 +137,7 @@
 extern int register_android_database_SQLiteConnection(JNIEnv* env);
 extern int register_android_database_SQLiteGlobal(JNIEnv* env);
 extern int register_android_database_SQLiteDebug(JNIEnv* env);
+extern int register_android_database_SQLiteRawStatement(JNIEnv* env);
 extern int register_android_media_MediaMetrics(JNIEnv *env);
 extern int register_android_os_Debug(JNIEnv* env);
 extern int register_android_os_GraphicsEnvironment(JNIEnv* env);
@@ -1566,6 +1567,7 @@
         REG_JNI(register_android_database_SQLiteConnection),
         REG_JNI(register_android_database_SQLiteGlobal),
         REG_JNI(register_android_database_SQLiteDebug),
+        REG_JNI(register_android_database_SQLiteRawStatement),
         REG_JNI(register_android_os_Debug),
         REG_JNI(register_android_os_FileObserver),
         REG_JNI(register_android_os_GraphicsEnvironment),
diff --git a/core/jni/OWNERS b/core/jni/OWNERS
index 4e4abec..dd43527 100644
--- a/core/jni/OWNERS
+++ b/core/jni/OWNERS
@@ -104,3 +104,6 @@
 
 # PM
 per-file com_android_internal_content_* = file:/PACKAGE_MANAGER_OWNERS
+
+# SQLite
+per-file android_database_SQLite* = file:/SQLITE_OWNERS
diff --git a/core/jni/android_content_res_ApkAssets.cpp b/core/jni/android_content_res_ApkAssets.cpp
index e9ada23..52a9578 100644
--- a/core/jni/android_content_res_ApkAssets.cpp
+++ b/core/jni/android_content_res_ApkAssets.cpp
@@ -74,17 +74,37 @@
   FORMAT_DIRECTORY = 3,
 };
 
-Guarded<std::unique_ptr<const ApkAssets>>& ApkAssetsFromLong(jlong ptr) {
-    return *reinterpret_cast<Guarded<std::unique_ptr<const ApkAssets>>*>(ptr);
+Guarded<AssetManager2::ApkAssetsPtr>& ApkAssetsFromLong(jlong ptr) {
+  return *reinterpret_cast<Guarded<AssetManager2::ApkAssetsPtr>*>(ptr);
 }
 
-static jlong CreateGuardedApkAssets(std::unique_ptr<const ApkAssets> assets) {
-    auto guarded_assets = new Guarded<std::unique_ptr<const ApkAssets>>(std::move(assets));
-    return reinterpret_cast<jlong>(guarded_assets);
+static jlong CreateGuardedApkAssets(AssetManager2::ApkAssetsPtr assets) {
+  auto guarded_assets = new Guarded<AssetManager2::ApkAssetsPtr>(std::move(assets));
+  return reinterpret_cast<jlong>(guarded_assets);
 }
 
-static void DeleteGuardedApkAssets(Guarded<std::unique_ptr<const ApkAssets>>& apk_assets) {
-    delete &apk_assets;
+static void DeleteGuardedApkAssets(Guarded<AssetManager2::ApkAssetsPtr>& apk_assets) {
+  apk_assets.safeDelete([&apk_assets](AssetManager2::ApkAssetsPtr* assets) {
+    if (!assets) {
+      ALOGW("ApkAssets: Double delete of native assets object %p, ignored", &apk_assets);
+    } else if (!*assets) {
+      ALOGW("ApkAssets: Empty native assets pointer in native assets object %p", &apk_assets);
+    } else {
+      // |RefBase| increments |StrongCount| for each |sp<>| instance, and |WeakCount| for
+      // both |sp<>| and |wp<>| instances. This means the actual |wp<>| instance count
+      // is |WeakCount - StrongCount|.
+      const auto useCount = (*assets)->getStrongCount();
+      const auto weakCount = (*assets)->getWeakRefs()->getWeakCount() - useCount;
+      if (useCount > 1) {
+        ALOGW("ApkAssets: Deleting an object '%s' with %d > 1 strong and %d weak references",
+              (*assets)->GetDebugName().c_str(), int(useCount), int(weakCount));
+      } else if (weakCount > 0) {
+        ALOGW("ApkAssets: Deleting an ApkAssets object '%s' with %d weak references",
+              (*assets)->GetDebugName().c_str(), int(weakCount));
+      }
+    }
+  });
+  delete &apk_assets;
 }
 
 class LoaderAssetsProvider : public AssetsProvider {
@@ -209,7 +229,7 @@
   ATRACE_NAME(base::StringPrintf("LoadApkAssets(%s)", path.c_str()).c_str());
 
   auto loader_assets = LoaderAssetsProvider::Create(env, assets_provider);
-  std::unique_ptr<ApkAssets> apk_assets;
+  AssetManager2::ApkAssetsPtr apk_assets;
   switch (format) {
     case FORMAT_APK: {
         auto assets = MultiAssetsProvider::Create(std::move(loader_assets),
@@ -269,7 +289,7 @@
   }
 
   auto loader_assets = LoaderAssetsProvider::Create(env, assets_provider);
-  std::unique_ptr<const ApkAssets> apk_assets;
+  AssetManager2::ApkAssetsPtr apk_assets;
   switch (format) {
     case FORMAT_APK: {
         auto assets =
@@ -336,7 +356,7 @@
   }
 
   auto loader_assets = LoaderAssetsProvider::Create(env, assets_provider);
-  std::unique_ptr<const ApkAssets> apk_assets;
+  AssetManager2::ApkAssetsPtr apk_assets;
   switch (format) {
     case FORMAT_APK: {
         auto assets =
@@ -374,11 +394,17 @@
 
 static jlong NativeLoadEmpty(JNIEnv* env, jclass /*clazz*/, jint flags, jobject assets_provider) {
   auto apk_assets = ApkAssets::Load(LoaderAssetsProvider::Create(env, assets_provider), flags);
+  if (apk_assets == nullptr) {
+    const std::string error_msg =
+        base::StringPrintf("Failed to load empty assets with provider %p", (void*)assets_provider);
+    jniThrowException(env, "java/io/IOException", error_msg.c_str());
+    return 0;
+  }
   return CreateGuardedApkAssets(std::move(apk_assets));
 }
 
 static void NativeDestroy(JNIEnv* /*env*/, jclass /*clazz*/, jlong ptr) {
-    DeleteGuardedApkAssets(ApkAssetsFromLong(ptr));
+  DeleteGuardedApkAssets(ApkAssetsFromLong(ptr));
 }
 
 static jstring NativeGetAssetPath(JNIEnv* env, jclass /*clazz*/, jlong ptr) {
diff --git a/core/jni/android_content_res_ApkAssets.h b/core/jni/android_content_res_ApkAssets.h
index 7e525dc..8159a53 100644
--- a/core/jni/android_content_res_ApkAssets.h
+++ b/core/jni/android_content_res_ApkAssets.h
@@ -18,13 +18,13 @@
 #define ANDROID_CONTENT_RES_APKASSETS_H
 
 #include "androidfw/ApkAssets.h"
+#include "androidfw/AssetManager2.h"
 #include "androidfw/MutexGuard.h"
-
 #include "jni.h"
 
 namespace android {
 
-Guarded<std::unique_ptr<const ApkAssets>>& ApkAssetsFromLong(jlong ptr);
+Guarded<AssetManager2::ApkAssetsPtr>& ApkAssetsFromLong(jlong ptr);
 
 } // namespace android
 
diff --git a/core/jni/android_database_SQLiteConnection.cpp b/core/jni/android_database_SQLiteConnection.cpp
index 32697ae..29520c2 100644
--- a/core/jni/android_database_SQLiteConnection.cpp
+++ b/core/jni/android_database_SQLiteConnection.cpp
@@ -880,6 +880,20 @@
     }
 }
 
+static jint nativeLastInsertRowId(JNIEnv* env, jclass, jlong connectionPtr) {
+    SQLiteConnection* connection = reinterpret_cast<SQLiteConnection*>(connectionPtr);
+    return sqlite3_last_insert_rowid(connection->db);
+}
+
+static jlong nativeChanges(JNIEnv* env, jclass, jlong connectionPtr) {
+    SQLiteConnection* connection = reinterpret_cast<SQLiteConnection*>(connectionPtr);
+    return sqlite3_changes64(connection->db);
+}
+
+static jlong nativeTotalChanges(JNIEnv* env, jclass, jlong connectionPtr) {
+    SQLiteConnection* connection = reinterpret_cast<SQLiteConnection*>(connectionPtr);
+    return sqlite3_total_changes64(connection->db);
+}
 
 static const JNINativeMethod sMethods[] =
 {
@@ -938,6 +952,10 @@
             (void*)nativeCancel },
     { "nativeResetCancel", "(JZ)V",
             (void*)nativeResetCancel },
+
+    { "nativeLastInsertRowId", "(J)I", (void*) nativeLastInsertRowId },
+    { "nativeChanges", "(J)J", (void*) nativeChanges },
+    { "nativeTotalChanges", "(J)J", (void*) nativeTotalChanges },
 };
 
 int register_android_database_SQLiteConnection(JNIEnv *env)
diff --git a/core/jni/android_database_SQLiteRawStatement.cpp b/core/jni/android_database_SQLiteRawStatement.cpp
new file mode 100644
index 0000000..b6b78811
--- /dev/null
+++ b/core/jni/android_database_SQLiteRawStatement.cpp
@@ -0,0 +1,323 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "SQLiteRawStatement"
+
+#include <string.h>
+#include <algorithm>
+
+#include <jni.h>
+#include <nativehelper/JNIHelp.h>
+#include <nativehelper/scoped_primitive_array.h>
+#include <nativehelper/scoped_string_chars.h>
+#include <android_runtime/AndroidRuntime.h>
+#include <android_runtime/Log.h>
+#include <android-base/stringprintf.h>
+#include <core_jni_helpers.h>
+
+#include <utils/Log.h>
+#include <utils/Unicode.h>
+
+#include <sqlite3.h>
+#include <sqlite3_android.h>
+
+#include "android_database_SQLiteCommon.h"
+
+/**
+ * JNI functions supporting the android.database.sqlite.SQLiteRawStatement class.
+ */
+namespace android {
+
+// Helper functions.
+static sqlite3 *db(long statementPtr) {
+    return sqlite3_db_handle(reinterpret_cast<sqlite3_stmt*>(statementPtr));
+}
+
+static sqlite3_stmt* stmt(long statementPtr) {
+    return reinterpret_cast<sqlite3_stmt*>(statementPtr);
+}
+
+// This throws a SQLiteBindOrColumnIndexOutOfRangeException if the parameter index is out
+// of bounds.  The function exists to construct an error message that includes
+// the bounds.
+static void throwInvalidParameter(JNIEnv *env, jlong stmtPtr, jint index) {
+    if (sqlite3_extended_errcode(db(stmtPtr)) == SQLITE_RANGE) {
+        int count = sqlite3_bind_parameter_count(stmt(stmtPtr));
+        std::string message = android::base::StringPrintf(
+            "parameter index %d out of bounds [1,%d]", index, count);
+        char const * errmsg = sqlite3_errstr(SQLITE_RANGE);
+        throw_sqlite3_exception(env, SQLITE_RANGE, errmsg, message.c_str());
+    } else {
+        throw_sqlite3_exception(env, db(stmtPtr), nullptr);
+    }
+}
+
+
+// This throws a SQLiteBindOrColumnIndexOutOfRangeException if the column index is out
+// of bounds.
+static void throwIfInvalidColumn(JNIEnv *env, jlong stmtPtr, jint col) {
+    if (col < 0 || col >= sqlite3_data_count(stmt(stmtPtr))) {
+        int count = sqlite3_data_count(stmt(stmtPtr));
+        std::string message = android::base::StringPrintf(
+            "column index %d out of bounds [0,%d]", col, count - 1);
+        char const * errmsg = sqlite3_errstr(SQLITE_RANGE);
+        throw_sqlite3_exception(env, SQLITE_RANGE, errmsg, message.c_str());
+    }
+}
+
+
+static jint bindParameterCount(JNIEnv* env, jclass, jlong stmtPtr) {
+    return sqlite3_bind_parameter_count(stmt(stmtPtr));
+}
+
+// jname must be in standard UTF-8.  This throws an NPE if jname is null.
+static jint bindParameterIndex(JNIEnv *env, jclass, jlong stmtPtr, jstring jname) {
+    ScopedStringChars name(env, jname);
+    if (name.get() == nullptr) {
+        return 0;
+    }
+    size_t len16 = env->GetStringLength(jname);
+    size_t len8 = utf16_to_utf8_length(reinterpret_cast<const char16_t*>(name.get()), len16);
+    // The extra byte is for the terminating null.
+    char *utf8Name = new char[len8 + 1];
+    utf16_to_utf8(reinterpret_cast<const char16_t*>(name.get()), len16, utf8Name, len8 + 1);
+    int r = sqlite3_bind_parameter_index(stmt(stmtPtr), utf8Name);
+    delete [] utf8Name;
+    return r;
+}
+
+// The name returned from the database is UTF-8.  If there is no matching name,
+// null is returned.
+static jstring bindParameterName(JNIEnv *env, jclass, jlong stmtPtr, jint param) {
+    char const *src = sqlite3_bind_parameter_name(stmt(stmtPtr), param);
+    if (src == nullptr) {
+        return NULL;
+    }
+    return env->NewStringUTF(src);
+}
+
+static jint columnCount(JNIEnv* env, jclass, jlong stmtPtr) {
+    return sqlite3_column_count(stmt(stmtPtr));
+}
+
+// Step the prepared statement.  If the result is other than ROW, DONE, BUSY, or LOCKED, throw an
+// exception if throwOnError is true.  The advantage of throwing from the native latyer is that
+// all the error codes and error strings are easily visible.
+static jint step(JNIEnv* env, jclass, jlong stmtPtr, jboolean throwOnError) {
+    sqlite3_stmt* statement = stmt(stmtPtr);
+    int err = sqlite3_step(statement);
+    switch (err) {
+        case SQLITE_ROW:
+        case SQLITE_DONE:
+        case SQLITE_BUSY:
+        case SQLITE_LOCKED:
+            return err;
+    }
+    if (throwOnError) {
+        throw_sqlite3_exception(env, db(stmtPtr), "failure in step()");
+    }
+    return err;
+}
+
+static void reset(JNIEnv*, jclass, jlong stmtPtr, jboolean clear) {
+    if (clear) sqlite3_clear_bindings(stmt(stmtPtr));
+    // The return value is ignored.
+    sqlite3_reset(stmt(stmtPtr));
+}
+
+static void clearBindings(JNIEnv*, jclass, jlong stmtPtr) {
+    sqlite3_clear_bindings(stmt(stmtPtr));
+}
+
+
+// This binds null to the parameter if the incoming array is null.
+static void bindBlob(JNIEnv* env, jclass obj, jlong stmtPtr, jint index, jbyteArray val,
+        jint offset, jint length) {
+    ScopedByteArrayRO value(env, val);
+    int err;
+    if (value.get() == nullptr) {
+        err = sqlite3_bind_null(stmt(stmtPtr), index);
+    } else {
+        err = sqlite3_bind_blob(stmt(stmtPtr), index, value.get() + offset,
+                                length, SQLITE_TRANSIENT);
+    }
+    if (err != SQLITE_OK) {
+        throwInvalidParameter(env, stmtPtr, index);
+    }
+}
+
+static void bindDouble(JNIEnv* env, jclass, jlong stmtPtr, jint index, jdouble val) {
+    if (sqlite3_bind_double(stmt(stmtPtr), index, val) != SQLITE_OK) {
+        throwInvalidParameter(env, stmtPtr, index);
+    }
+}
+
+static void bindInt(JNIEnv* env, jclass, jlong stmtPtr, jint index, jint val) {
+    if (sqlite3_bind_int(stmt(stmtPtr), index, val) != SQLITE_OK) {
+        throwInvalidParameter(env, stmtPtr, index);
+    }
+}
+
+static void bindLong(JNIEnv* env, jclass, jlong stmtPtr, jint index, jlong val) {
+    if (sqlite3_bind_int64(stmt(stmtPtr), index, val) != SQLITE_OK) {
+        throwInvalidParameter(env, stmtPtr, index);
+    }
+}
+
+static void bindNull(JNIEnv* env, jclass, jlong stmtPtr, jint index) {
+    if (sqlite3_bind_null(stmt(stmtPtr), index) != SQLITE_OK) {
+        throwInvalidParameter(env, stmtPtr, index);
+    }
+}
+
+// This binds null to the parameter if the string is null.
+static void bindText(JNIEnv* env, jclass, jlong stmtPtr, jint index, jstring val) {
+    ScopedStringChars value(env, val);
+    int err;
+    if (value.get() == nullptr) {
+        err = sqlite3_bind_null(stmt(stmtPtr), index);
+    } else {
+        jsize valueLength = env->GetStringLength(val);
+        err = sqlite3_bind_text16(stmt(stmtPtr), index, value.get(),
+            valueLength * sizeof(jchar), SQLITE_TRANSIENT);
+    }
+    if (err != SQLITE_OK) {
+        throwInvalidParameter(env, stmtPtr, index);
+    }
+}
+
+
+static jint columnType(JNIEnv* env, jclass, jlong stmtPtr, jint col) {
+    throwIfInvalidColumn(env, stmtPtr, col);
+    return sqlite3_column_type(stmt(stmtPtr), col);
+}
+
+static jstring columnName(JNIEnv* env, jclass, jlong stmtPtr, jint col) {
+    throwIfInvalidColumn(env, stmtPtr, col);
+    const jchar* name = static_cast<const jchar*>(sqlite3_column_name16(stmt(stmtPtr), col));
+    if (name == nullptr) {
+        throw_sqlite3_exception(env, db(stmtPtr), "error fetching columnName()");
+        return NULL;
+    }
+    size_t length = strlen16(reinterpret_cast<const char16_t*>(name));
+    return env->NewString(name, length);
+}
+
+static jint columnBytes(JNIEnv* env, jclass, jlong stmtPtr, jint col) {
+    throwIfInvalidColumn(env, stmtPtr, col);
+    return sqlite3_column_bytes16(stmt(stmtPtr), col);
+}
+
+
+static jbyteArray columnBlob(JNIEnv* env, jclass, jlong stmtPtr, jint col) {
+    throwIfInvalidColumn(env, stmtPtr, col);
+    const void* blob = sqlite3_column_blob(stmt(stmtPtr), col);
+    if (blob == nullptr) {
+        return NULL;
+    }
+    size_t size = sqlite3_column_bytes(stmt(stmtPtr), col);
+    jbyteArray result = env->NewByteArray(size);
+    if (result == nullptr) {
+        // An OutOfMemory exception will have been thrown.
+        return NULL;
+    }
+    env->SetByteArrayRegion(result, 0, size, reinterpret_cast<const jbyte*>(blob));
+    return result;
+}
+
+static int columnBuffer(JNIEnv* env, jclass, jlong stmtPtr, jint col,
+        jbyteArray buffer, jint offset, jint length, jint srcOffset) {
+    throwIfInvalidColumn(env, stmtPtr, col);
+    const void* blob = sqlite3_column_blob(stmt(stmtPtr), col);
+    if (blob == nullptr) {
+        return 0;
+    }
+    jsize bsize = sqlite3_column_bytes(stmt(stmtPtr), col);
+    if (bsize == 0 || bsize <= srcOffset) {
+        return 0;
+    }
+    jsize want = std::min(bsize - srcOffset, length);
+    env->SetByteArrayRegion(buffer, offset, want, reinterpret_cast<const jbyte*>(blob) + srcOffset);
+    return want;
+}
+
+static jdouble columnDouble(JNIEnv* env, jclass, jlong stmtPtr, jint col) {
+    throwIfInvalidColumn(env, stmtPtr, col);
+    return sqlite3_column_double(stmt(stmtPtr), col);
+}
+
+static jint columnInt(JNIEnv* env, jclass, jlong stmtPtr, jint col) {
+    throwIfInvalidColumn(env, stmtPtr, col);
+    return sqlite3_column_int(stmt(stmtPtr), col);
+}
+
+static jlong columnLong(JNIEnv* env, jclass, jlong stmtPtr, jint col) {
+    throwIfInvalidColumn(env, stmtPtr, col);
+    return sqlite3_column_int64(stmt(stmtPtr), col);
+}
+
+static jstring columnText(JNIEnv* env, jclass, jlong stmtPtr, jint col) {
+    throwIfInvalidColumn(env, stmtPtr, col);
+    const jchar* text = static_cast<const jchar*>(sqlite3_column_text16(stmt(stmtPtr), col));
+    if (text == nullptr) {
+        return NULL;
+    }
+    size_t length = sqlite3_column_bytes16(stmt(stmtPtr), col) / sizeof(jchar);
+    return env->NewString(text, length);
+}
+
+static const JNINativeMethod sStatementMethods[] =
+{
+    // Metadata
+    { "nativeBindParameterCount", "(J)I", (void*) bindParameterCount },
+    { "nativeBindParameterIndex", "(JLjava/lang/String;)I", (void*) bindParameterIndex },
+    { "nativeBindParameterName", "(JI)Ljava/lang/String;", (void*) bindParameterName },
+
+    // Operations on a statement
+    { "nativeStep", "(JZ)I", (void*) step },
+    { "nativeReset", "(JZ)V", (void*) reset },
+    { "nativeClearBindings", "(J)V", (void*) clearBindings },
+
+    // Methods that bind values to parameters
+    { "nativeBindBlob", "(JI[BII)V", (void*) bindBlob },
+    { "nativeBindDouble", "(JID)V", (void*) bindDouble },
+    { "nativeBindInt", "(JII)V", (void*) bindInt },
+    { "nativeBindLong", "(JIJ)V", (void*) bindLong },
+    { "nativeBindNull", "(JI)V", (void*) bindNull },
+    { "nativeBindText", "(JILjava/lang/String;)V", (void*) bindText },
+
+    // Methods that return information about columns in a result row.
+    { "nativeColumnCount", "(J)I", (void*) columnCount },
+    { "nativeColumnType", "(JI)I", (void*) columnType },
+    { "nativeColumnName", "(JI)Ljava/lang/String;", (void*) columnName },
+
+    { "nativeColumnBytes", "(JI)I", (void*) columnBytes },
+
+    { "nativeColumnBlob", "(JI)[B", (void*) columnBlob },
+    { "nativeColumnBuffer", "(JI[BIII)I", (void*) columnBuffer },
+    { "nativeColumnDouble", "(JI)D", (void*) columnDouble },
+    { "nativeColumnInt", "(JI)I", (void*) columnInt },
+    { "nativeColumnLong", "(JI)J", (void*) columnLong },
+    { "nativeColumnText", "(JI)Ljava/lang/String;", (void*) columnText },
+};
+
+int register_android_database_SQLiteRawStatement(JNIEnv *env)
+{
+    return RegisterMethodsOrDie(env, "android/database/sqlite/SQLiteRawStatement",
+                                sStatementMethods, NELEM(sStatementMethods));
+}
+
+} // namespace android
diff --git a/core/jni/android_hardware_Camera.cpp b/core/jni/android_hardware_Camera.cpp
index 2a670e8..1c59742 100644
--- a/core/jni/android_hardware_Camera.cpp
+++ b/core/jni/android_hardware_Camera.cpp
@@ -17,22 +17,21 @@
 
 //#define LOG_NDEBUG 0
 #define LOG_TAG "Camera-JNI"
-#include <utils/Log.h>
-
-#include "jni.h"
-#include <nativehelper/JNIHelp.h>
-#include "core_jni_helpers.h"
 #include <android_runtime/android_graphics_SurfaceTexture.h>
 #include <android_runtime/android_view_Surface.h>
-
+#include <binder/IMemory.h>
+#include <camera/Camera.h>
+#include <camera/StringUtils.h>
 #include <cutils/properties.h>
-#include <utils/Vector.h>
-#include <utils/Errors.h>
-
 #include <gui/GLConsumer.h>
 #include <gui/Surface.h>
-#include <camera/Camera.h>
-#include <binder/IMemory.h>
+#include <nativehelper/JNIHelp.h>
+#include <utils/Errors.h>
+#include <utils/Log.h>
+#include <utils/Vector.h>
+
+#include "core_jni_helpers.h"
+#include "jni.h"
 
 using namespace android;
 
@@ -562,7 +561,7 @@
     const char16_t *rawClientName = reinterpret_cast<const char16_t*>(
         env->GetStringChars(clientPackageName, NULL));
     jsize rawClientNameLen = env->GetStringLength(clientPackageName);
-    String16 clientName(rawClientName, rawClientNameLen);
+    std::string clientName = toStdString(rawClientName, rawClientNameLen);
     env->ReleaseStringChars(clientPackageName,
                             reinterpret_cast<const jchar*>(rawClientName));
 
diff --git a/core/jni/android_hardware_input_InputWindowHandle.cpp b/core/jni/android_hardware_input_InputWindowHandle.cpp
index 416d991..eb5f297 100644
--- a/core/jni/android_hardware_input_InputWindowHandle.cpp
+++ b/core/jni/android_hardware_input_InputWindowHandle.cpp
@@ -163,10 +163,9 @@
 
     mInfo.touchOcclusionMode = static_cast<TouchOcclusionMode>(
             env->GetIntField(obj, gInputWindowHandleClassInfo.touchOcclusionMode));
-    mInfo.ownerPid = env->GetIntField(obj,
-            gInputWindowHandleClassInfo.ownerPid);
-    mInfo.ownerUid = env->GetIntField(obj,
-            gInputWindowHandleClassInfo.ownerUid);
+    mInfo.ownerPid = gui::Pid{env->GetIntField(obj, gInputWindowHandleClassInfo.ownerPid)};
+    mInfo.ownerUid = gui::Uid{
+            static_cast<uid_t>(env->GetIntField(obj, gInputWindowHandleClassInfo.ownerUid))};
     mInfo.packageName = getStringField(env, obj, gInputWindowHandleClassInfo.packageName, "<null>");
     mInfo.displayId = env->GetIntField(obj,
             gInputWindowHandleClassInfo.displayId);
@@ -308,8 +307,10 @@
 
     env->SetIntField(inputWindowHandle, gInputWindowHandleClassInfo.touchOcclusionMode,
                      static_cast<int32_t>(windowInfo.touchOcclusionMode));
-    env->SetIntField(inputWindowHandle, gInputWindowHandleClassInfo.ownerPid, windowInfo.ownerPid);
-    env->SetIntField(inputWindowHandle, gInputWindowHandleClassInfo.ownerUid, windowInfo.ownerUid);
+    env->SetIntField(inputWindowHandle, gInputWindowHandleClassInfo.ownerPid,
+                     windowInfo.ownerPid.val());
+    env->SetIntField(inputWindowHandle, gInputWindowHandleClassInfo.ownerUid,
+                     windowInfo.ownerUid.val());
     ScopedLocalRef<jstring> packageName(env, env->NewStringUTF(windowInfo.packageName.data()));
     env->SetObjectField(inputWindowHandle, gInputWindowHandleClassInfo.packageName,
                         packageName.get());
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index e1be0cd..4cf17b7 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -30,6 +30,8 @@
 #include <media/AudioSystem.h>
 #include <nativehelper/JNIHelp.h>
 #include <nativehelper/ScopedLocalRef.h>
+#include <nativehelper/ScopedPrimitiveArray.h>
+#include <nativehelper/jni_macros.h>
 #include <system/audio.h>
 #include <system/audio_policy.h>
 #include <utils/Log.h>
@@ -285,7 +287,7 @@
         ALOGE("Can't find class %s", kEventHandlerClassPathName);
         return;
     }
-    mClass = (jclass)env->NewGlobalRef(clazz);
+    mClass = static_cast<jclass>(env->NewGlobalRef(clazz));
 
     // We use a weak reference so the AudioPortEventHandler object can be garbage collected.
     // The reference is only used as a proxy for callbacks.
@@ -337,15 +339,16 @@
                                        const sp<JNIAudioPortCallback>& callback)
 {
     Mutex::Autolock l(gLock);
-    sp<JNIAudioPortCallback> old =
-            (JNIAudioPortCallback*)env->GetLongField(thiz, gEventHandlerFields.mJniCallback);
+    sp<JNIAudioPortCallback> old = reinterpret_cast<JNIAudioPortCallback *>(
+            env->GetLongField(thiz, gEventHandlerFields.mJniCallback));
     if (callback.get()) {
-        callback->incStrong((void*)setJniCallback);
+        callback->incStrong(reinterpret_cast<void *>(setJniCallback));
     }
     if (old != 0) {
-        old->decStrong((void*)setJniCallback);
+        old->decStrong(reinterpret_cast<void *>(setJniCallback));
     }
-    env->SetLongField(thiz, gEventHandlerFields.mJniCallback, (jlong)callback.get());
+    env->SetLongField(thiz, gEventHandlerFields.mJniCallback,
+                      reinterpret_cast<jlong>(callback.get()));
     return old;
 }
 
@@ -374,43 +377,44 @@
                                            jobjectArray deviceAddresses,
                                            AudioDeviceTypeAddrVector &audioDeviceTypeAddrVector) {
     if (deviceTypes == nullptr || deviceAddresses == nullptr) {
-        return (jint)AUDIO_JAVA_BAD_VALUE;
+    return AUDIO_JAVA_BAD_VALUE;
     }
     jsize deviceCount = env->GetArrayLength(deviceTypes);
     if (deviceCount == 0 || deviceCount != env->GetArrayLength(deviceAddresses)) {
-        return (jint)AUDIO_JAVA_BAD_VALUE;
+    return AUDIO_JAVA_BAD_VALUE;
     }
     // retrieve all device types
     std::vector<audio_devices_t> deviceTypesVector;
     jint *typesPtr = nullptr;
     typesPtr = env->GetIntArrayElements(deviceTypes, 0);
     if (typesPtr == nullptr) {
-        return (jint)AUDIO_JAVA_BAD_VALUE;
+    return AUDIO_JAVA_BAD_VALUE;
     }
     for (jint i = 0; i < deviceCount; i++) {
-        deviceTypesVector.push_back((audio_devices_t)typesPtr[i]);
+    deviceTypesVector.push_back(static_cast<audio_devices_t>(typesPtr[i]));
     }
     // check each address is a string and add device type/address to list
     jclass stringClass = FindClassOrDie(env, "java/lang/String");
     for (jint i = 0; i < deviceCount; i++) {
         jobject addrJobj = env->GetObjectArrayElement(deviceAddresses, i);
         if (!env->IsInstanceOf(addrJobj, stringClass)) {
-            return (jint)AUDIO_JAVA_BAD_VALUE;
+        return AUDIO_JAVA_BAD_VALUE;
         }
-        const char *address = env->GetStringUTFChars((jstring)addrJobj, NULL);
-        AudioDeviceTypeAddr dev = AudioDeviceTypeAddr((audio_devices_t)typesPtr[i], address);
+        const char *address = env->GetStringUTFChars(static_cast<jstring>(addrJobj), NULL);
+        AudioDeviceTypeAddr dev =
+                AudioDeviceTypeAddr(static_cast<audio_devices_t>(typesPtr[i]), address);
         audioDeviceTypeAddrVector.push_back(dev);
-        env->ReleaseStringUTFChars((jstring)addrJobj, address);
+        env->ReleaseStringUTFChars(static_cast<jstring>(addrJobj), address);
     }
     env->ReleaseIntArrayElements(deviceTypes, typesPtr, 0);
 
-    return (jint)NO_ERROR;
+    return NO_ERROR;
 }
 
 static jint
 android_media_AudioSystem_muteMicrophone(JNIEnv *env, jobject thiz, jboolean on)
 {
-    return (jint) check_AudioSystem_Command(AudioSystem::muteMicrophone(on));
+    return check_AudioSystem_Command(AudioSystem::muteMicrophone(on));
 }
 
 static jboolean
@@ -425,7 +429,7 @@
 android_media_AudioSystem_isStreamActive(JNIEnv *env, jobject thiz, jint stream, jint inPastMs)
 {
     bool state = false;
-    AudioSystem::isStreamActive((audio_stream_type_t) stream, &state, inPastMs);
+    AudioSystem::isStreamActive(static_cast<audio_stream_type_t>(stream), &state, inPastMs);
     return state;
 }
 
@@ -434,7 +438,7 @@
         jint inPastMs)
 {
     bool state = false;
-    AudioSystem::isStreamActiveRemotely((audio_stream_type_t) stream, &state, inPastMs);
+    AudioSystem::isStreamActiveRemotely(static_cast<audio_stream_type_t>(stream), &state, inPastMs);
     return state;
 }
 
@@ -442,7 +446,7 @@
 android_media_AudioSystem_isSourceActive(JNIEnv *env, jobject thiz, jint source)
 {
     bool state = false;
-    AudioSystem::isSourceActive((audio_source_t) source, &state);
+    AudioSystem::isSourceActive(static_cast<audio_source_t>(source), &state);
     return state;
 }
 
@@ -477,8 +481,7 @@
             env->GetStringLength(keyValuePairs));
         env->ReleaseStringCritical(keyValuePairs, c_keyValuePairs);
     }
-    int status = check_AudioSystem_Command(AudioSystem::setParameters(c_keyValuePairs8));
-    return (jint) status;
+    return check_AudioSystem_Command(AudioSystem::setParameters(c_keyValuePairs8));
 }
 
 static jstring
@@ -558,15 +561,15 @@
         return;
     }
     jint recParamData[REC_PARAM_SIZE];
-    recParamData[0] = (jint) audioFormatFromNative(clientConfig->format);
+    recParamData[0] = audioFormatFromNative(clientConfig->format);
     // FIXME this doesn't support index-based masks
-    recParamData[1] = (jint) inChannelMaskFromNative(clientConfig->channel_mask);
-    recParamData[2] = (jint) clientConfig->sample_rate;
-    recParamData[3] = (jint) audioFormatFromNative(deviceConfig->format);
+    recParamData[1] = inChannelMaskFromNative(clientConfig->channel_mask);
+    recParamData[2] = clientConfig->sample_rate;
+    recParamData[3] = audioFormatFromNative(deviceConfig->format);
     // FIXME this doesn't support index-based masks
-    recParamData[4] = (jint) inChannelMaskFromNative(deviceConfig->channel_mask);
-    recParamData[5] = (jint) deviceConfig->sample_rate;
-    recParamData[6] = (jint) patchHandle;
+    recParamData[4] = inChannelMaskFromNative(deviceConfig->channel_mask);
+    recParamData[5] = deviceConfig->sample_rate;
+    recParamData[6] = patchHandle;
     env->SetIntArrayRegion(recParamArray, 0, REC_PARAM_SIZE, recParamData);
 
     jobjectArray jClientEffects;
@@ -580,10 +583,9 @@
 
     env->CallStaticVoidMethod(clazz,
                               gAudioPolicyEventHandlerMethods.postRecordConfigEventFromNative,
-                              event, (jint) clientInfo->riid, (jint) clientInfo->uid,
-                              clientInfo->session, clientInfo->source, clientInfo->port_id,
-                              clientInfo->silenced, recParamArray, jClientEffects, jEffects,
-                              source);
+                              event, clientInfo->riid, clientInfo->uid, clientInfo->session,
+                              clientInfo->source, clientInfo->port_id, clientInfo->silenced,
+                              recParamArray, jClientEffects, jEffects, source);
     env->DeleteLocalRef(clazz);
     env->DeleteLocalRef(recParamArray);
     env->DeleteLocalRef(jClientEffects);
@@ -626,11 +628,9 @@
     if (Parcel *parcel = parcelForJavaObject(env, jParcel); parcel != nullptr) {
         android::media::audio::common::AudioPort port{};
         if (status_t statusOfParcel = port.readFromParcel(parcel); statusOfParcel == OK) {
-            status = check_AudioSystem_Command(
-                    AudioSystem::setDeviceConnectionState(static_cast<audio_policy_dev_state_t>(
-                                                                  state),
-                                                          port,
-                                                          static_cast<audio_format_t>(codec)));
+        status = check_AudioSystem_Command(
+                AudioSystem::setDeviceConnectionState(static_cast<audio_policy_dev_state_t>(state),
+                                                      port, static_cast<audio_format_t>(codec)));
         } else {
             ALOGE("Failed to read from parcel: %s", statusToString(statusOfParcel).c_str());
             status = kAudioStatusError;
@@ -639,17 +639,17 @@
         ALOGE("Failed to retrieve the native parcel from Java parcel");
         status = kAudioStatusError;
     }
-    return (jint) status;
+    return status;
 }
 
 static jint
 android_media_AudioSystem_getDeviceConnectionState(JNIEnv *env, jobject thiz, jint device, jstring device_address)
 {
     const char *c_address = env->GetStringUTFChars(device_address, NULL);
-    int state = static_cast <int>(AudioSystem::getDeviceConnectionState(static_cast <audio_devices_t>(device),
-                                          c_address));
+    int state = static_cast<int>(
+            AudioSystem::getDeviceConnectionState(static_cast<audio_devices_t>(device), c_address));
     env->ReleaseStringUTFChars(device_address, c_address);
-    return (jint) state;
+    return state;
 }
 
 static jint
@@ -658,38 +658,41 @@
 {
     const char *c_address = env->GetStringUTFChars(device_address, NULL);
     const char *c_name = env->GetStringUTFChars(device_name, NULL);
-    int status = check_AudioSystem_Command(AudioSystem::handleDeviceConfigChange(static_cast <audio_devices_t>(device),
-                                          c_address, c_name, static_cast <audio_format_t>(codec)));
+    int status = check_AudioSystem_Command(
+            AudioSystem::handleDeviceConfigChange(static_cast<audio_devices_t>(device), c_address,
+                                                  c_name, static_cast<audio_format_t>(codec)));
     env->ReleaseStringUTFChars(device_address, c_address);
     env->ReleaseStringUTFChars(device_name, c_name);
-    return (jint) status;
+    return status;
 }
 
 static jint android_media_AudioSystem_setPhoneState(JNIEnv *env, jobject thiz, jint state,
                                                     jint uid) {
-    return (jint)check_AudioSystem_Command(
-            AudioSystem::setPhoneState((audio_mode_t)state, (uid_t)uid));
+    return check_AudioSystem_Command(
+            AudioSystem::setPhoneState(static_cast<audio_mode_t>(state), static_cast<uid_t>(uid)));
 }
 
 static jint
 android_media_AudioSystem_setForceUse(JNIEnv *env, jobject thiz, jint usage, jint config)
 {
-    return (jint) check_AudioSystem_Command(AudioSystem::setForceUse(static_cast <audio_policy_force_use_t>(usage),
-                                                           static_cast <audio_policy_forced_cfg_t>(config)));
+    return check_AudioSystem_Command(
+            AudioSystem::setForceUse(static_cast<audio_policy_force_use_t>(usage),
+                                     static_cast<audio_policy_forced_cfg_t>(config)));
 }
 
 static jint
 android_media_AudioSystem_getForceUse(JNIEnv *env, jobject thiz, jint usage)
 {
-    return static_cast <jint>(AudioSystem::getForceUse(static_cast <audio_policy_force_use_t>(usage)));
+    return static_cast<jint>(
+            AudioSystem::getForceUse(static_cast<audio_policy_force_use_t>(usage)));
 }
 
 static jint
 android_media_AudioSystem_initStreamVolume(JNIEnv *env, jobject thiz, jint stream, jint indexMin, jint indexMax)
 {
-    return (jint) check_AudioSystem_Command(AudioSystem::initStreamVolume(static_cast <audio_stream_type_t>(stream),
-                                                                   indexMin,
-                                                                   indexMax));
+    return check_AudioSystem_Command(
+            AudioSystem::initStreamVolume(static_cast<audio_stream_type_t>(stream), indexMin,
+                                          indexMax));
 }
 
 static jint
@@ -699,10 +702,9 @@
                                                jint index,
                                                jint device)
 {
-    return (jint) check_AudioSystem_Command(
-            AudioSystem::setStreamVolumeIndex(static_cast <audio_stream_type_t>(stream),
-                                              index,
-                                              (audio_devices_t)device));
+    return check_AudioSystem_Command(
+            AudioSystem::setStreamVolumeIndex(static_cast<audio_stream_type_t>(stream), index,
+                                              static_cast<audio_devices_t>(device)));
 }
 
 static jint
@@ -712,13 +714,11 @@
                                                jint device)
 {
     int index;
-    if (AudioSystem::getStreamVolumeIndex(static_cast <audio_stream_type_t>(stream),
-                                          &index,
-                                          (audio_devices_t)device)
-            != NO_ERROR) {
+    if (AudioSystem::getStreamVolumeIndex(static_cast<audio_stream_type_t>(stream), &index,
+                                          static_cast<audio_devices_t>(device)) != NO_ERROR) {
         index = -1;
     }
-    return (jint) index;
+    return index;
 }
 
 static jint
@@ -731,11 +731,12 @@
     // read the AudioAttributes values
     JNIAudioAttributeHelper::UniqueAaPtr paa = JNIAudioAttributeHelper::makeUnique();
     jint jStatus = JNIAudioAttributeHelper::nativeFromJava(env, jaa, paa.get());
-    if (jStatus != (jint)AUDIO_JAVA_SUCCESS) {
+    if (jStatus != AUDIO_JAVA_SUCCESS) {
         return jStatus;
     }
-    return (jint) check_AudioSystem_Command(
-            AudioSystem::setVolumeIndexForAttributes(*(paa.get()), index, (audio_devices_t)device));
+    return check_AudioSystem_Command(
+            AudioSystem::setVolumeIndexForAttributes(*(paa.get()), index,
+                                                     static_cast<audio_devices_t>(device)));
 }
 
 static jint
@@ -747,15 +748,16 @@
     // read the AudioAttributes values
     JNIAudioAttributeHelper::UniqueAaPtr paa = JNIAudioAttributeHelper::makeUnique();
     jint jStatus = JNIAudioAttributeHelper::nativeFromJava(env, jaa, paa.get());
-    if (jStatus != (jint)AUDIO_JAVA_SUCCESS) {
+    if (jStatus != AUDIO_JAVA_SUCCESS) {
         return jStatus;
     }
     int index;
-    if (AudioSystem::getVolumeIndexForAttributes(*(paa.get()), index, (audio_devices_t)device)
-            != NO_ERROR) {
+    if (AudioSystem::getVolumeIndexForAttributes(*(paa.get()), index,
+                                                 static_cast<audio_devices_t>(device)) !=
+        NO_ERROR) {
         index = -1;
     }
-    return (jint) index;
+    return index;
 }
 
 static jint
@@ -766,7 +768,7 @@
     // read the AudioAttributes values
     JNIAudioAttributeHelper::UniqueAaPtr paa = JNIAudioAttributeHelper::makeUnique();
     jint jStatus = JNIAudioAttributeHelper::nativeFromJava(env, jaa, paa.get());
-    if (jStatus != (jint)AUDIO_JAVA_SUCCESS) {
+    if (jStatus != AUDIO_JAVA_SUCCESS) {
         return jStatus;
     }
     int index;
@@ -774,7 +776,7 @@
             != NO_ERROR) {
         index = -1;
     }
-    return (jint) index;
+    return index;
 }
 
 static jint
@@ -785,7 +787,7 @@
     // read the AudioAttributes values
     JNIAudioAttributeHelper::UniqueAaPtr paa = JNIAudioAttributeHelper::makeUnique();
     jint jStatus = JNIAudioAttributeHelper::nativeFromJava(env, jaa, paa.get());
-    if (jStatus != (jint)AUDIO_JAVA_SUCCESS) {
+    if (jStatus != AUDIO_JAVA_SUCCESS) {
         return jStatus;
     }
     int index;
@@ -793,13 +795,13 @@
             != NO_ERROR) {
         index = -1;
     }
-    return (jint) index;
+    return index;
 }
 
 static jint
 android_media_AudioSystem_setMasterVolume(JNIEnv *env, jobject thiz, jfloat value)
 {
-    return (jint) check_AudioSystem_Command(AudioSystem::setMasterVolume(value));
+    return check_AudioSystem_Command(AudioSystem::setMasterVolume(value));
 }
 
 static jfloat
@@ -815,7 +817,7 @@
 static jint
 android_media_AudioSystem_setMasterMute(JNIEnv *env, jobject thiz, jboolean mute)
 {
-    return (jint) check_AudioSystem_Command(AudioSystem::setMasterMute(mute));
+    return check_AudioSystem_Command(AudioSystem::setMasterMute(mute));
 }
 
 static jboolean
@@ -831,7 +833,7 @@
 static jint
 android_media_AudioSystem_setMasterMono(JNIEnv *env, jobject thiz, jboolean mono)
 {
-    return (jint) check_AudioSystem_Command(AudioSystem::setMasterMono(mono));
+    return check_AudioSystem_Command(AudioSystem::setMasterMono(mono));
 }
 
 static jboolean
@@ -847,7 +849,7 @@
 static jint
 android_media_AudioSystem_setMasterBalance(JNIEnv *env, jobject thiz, jfloat balance)
 {
-    return (jint) check_AudioSystem_Command(AudioSystem::setMasterBalance(balance));
+    return check_AudioSystem_Command(AudioSystem::setMasterBalance(balance));
 }
 
 static jfloat
@@ -865,37 +867,37 @@
 static jint
 android_media_AudioSystem_getPrimaryOutputSamplingRate(JNIEnv *env, jobject clazz)
 {
-    return (jint) AudioSystem::getPrimaryOutputSamplingRate();
+    return AudioSystem::getPrimaryOutputSamplingRate();
 }
 
 static jint
 android_media_AudioSystem_getPrimaryOutputFrameCount(JNIEnv *env, jobject clazz)
 {
-    return (jint) AudioSystem::getPrimaryOutputFrameCount();
+    return AudioSystem::getPrimaryOutputFrameCount();
 }
 
 static jint
 android_media_AudioSystem_getOutputLatency(JNIEnv *env, jobject clazz, jint stream)
 {
     uint32_t afLatency;
-    if (AudioSystem::getOutputLatency(&afLatency, static_cast <audio_stream_type_t>(stream))
-            != NO_ERROR) {
+    if (AudioSystem::getOutputLatency(&afLatency, static_cast<audio_stream_type_t>(stream)) !=
+        NO_ERROR) {
         afLatency = -1;
     }
-    return (jint) afLatency;
+    return afLatency;
 }
 
 static jint
 android_media_AudioSystem_setLowRamDevice(
         JNIEnv *env, jobject clazz, jboolean isLowRamDevice, jlong totalMemory)
 {
-    return (jint) AudioSystem::setLowRamDevice((bool) isLowRamDevice, (int64_t) totalMemory);
+    return AudioSystem::setLowRamDevice(isLowRamDevice, totalMemory);
 }
 
 static jint
 android_media_AudioSystem_checkAudioFlinger(JNIEnv *env, jobject clazz)
 {
-    return (jint) check_AudioSystem_Command(AudioSystem::checkAudioFlinger());
+    return check_AudioSystem_Command(AudioSystem::checkAudioFlinger());
 }
 
 static void android_media_AudioSystem_setAudioFlingerBinder(JNIEnv *env, jobject clazz,
@@ -909,8 +911,8 @@
                                                bool useInMask)
 {
     nAudioGainConfig->index = env->GetIntField(jAudioGainConfig, gAudioGainConfigFields.mIndex);
-    nAudioGainConfig->mode =
-            (audio_gain_mode_t)env->GetIntField(jAudioGainConfig, gAudioGainConfigFields.mMode);
+    nAudioGainConfig->mode = static_cast<audio_gain_mode_t>(
+            env->GetIntField(jAudioGainConfig, gAudioGainConfigFields.mMode));
     ALOGV("convertAudioGainConfigToNative got gain index %d", nAudioGainConfig->index);
     jint jMask = env->GetIntField(jAudioGainConfig, gAudioGainConfigFields.mChannelMask);
     audio_channel_mask_t nMask;
@@ -924,8 +926,8 @@
     nAudioGainConfig->channel_mask = nMask;
     nAudioGainConfig->ramp_duration_ms = env->GetIntField(jAudioGainConfig,
                                                        gAudioGainConfigFields.mRampDurationMs);
-    jintArray jValues = (jintArray)env->GetObjectField(jAudioGainConfig,
-                                                       gAudioGainConfigFields.mValues);
+    jintArray jValues = static_cast<jintArray>(
+            env->GetObjectField(jAudioGainConfig, gAudioGainConfigFields.mValues));
     int *nValues = env->GetIntArrayElements(jValues, NULL);
     size_t size = env->GetArrayLength(jValues);
     memcpy(nAudioGainConfig->values, nValues, size * sizeof(int));
@@ -940,8 +942,8 @@
     jobject jAudioPort = env->GetObjectField(jAudioPortConfig, gAudioPortConfigFields.mPort);
     jobject jHandle = env->GetObjectField(jAudioPort, gAudioPortFields.mHandle);
     nAudioPortConfig->id = env->GetIntField(jHandle, gAudioHandleFields.mId);
-    nAudioPortConfig->role = (audio_port_role_t)env->GetIntField(jAudioPort,
-                                                                 gAudioPortFields.mRole);
+    nAudioPortConfig->role =
+            static_cast<audio_port_role_t>(env->GetIntField(jAudioPort, gAudioPortFields.mRole));
     if (env->IsInstanceOf(jAudioPort, gAudioDevicePortClass)) {
         nAudioPortConfig->type = AUDIO_PORT_TYPE_DEVICE;
     } else if (env->IsInstanceOf(jAudioPort, gAudioMixPortClass)) {
@@ -949,7 +951,7 @@
     } else {
         env->DeleteLocalRef(jAudioPort);
         env->DeleteLocalRef(jHandle);
-        return (jint)AUDIO_JAVA_ERROR;
+        return AUDIO_JAVA_ERROR;
     }
     ALOGV("convertAudioPortConfigToNative handle %d role %d type %d",
           nAudioPortConfig->id, nAudioPortConfig->role, nAudioPortConfig->type);
@@ -1004,7 +1006,7 @@
     }
     env->DeleteLocalRef(jAudioPort);
     env->DeleteLocalRef(jHandle);
-    return (jint)AUDIO_JAVA_SUCCESS;
+    return AUDIO_JAVA_SUCCESS;
 }
 
 /**
@@ -1025,15 +1027,15 @@
     }
     // Supports AUDIO_PORT_TYPE_DEVICE only
     if (nAudioPortConfig->type != AUDIO_PORT_TYPE_DEVICE) {
-        return (jint)AUDIO_JAVA_BAD_VALUE;
+        return AUDIO_JAVA_BAD_VALUE;
     }
 
     jobject jAudioDevicePort = env->GetObjectField(jAudioPortConfig,
             gAudioPortConfigFields.mPort);
-    nAudioPortConfig->ext.device.type =
-            (audio_devices_t)env->GetIntField(jAudioDevicePort, gAudioPortFields.mType);
-    jstring jDeviceAddress = (jstring)env->GetObjectField(jAudioDevicePort,
-            gAudioPortFields.mAddress);
+    nAudioPortConfig->ext.device.type = static_cast<audio_devices_t>(
+            env->GetIntField(jAudioDevicePort, gAudioPortFields.mType));
+    jstring jDeviceAddress =
+            static_cast<jstring>(env->GetObjectField(jAudioDevicePort, gAudioPortFields.mAddress));
     const char *nDeviceAddress = env->GetStringUTFChars(jDeviceAddress, NULL);
     strncpy(nAudioPortConfig->ext.device.address,
             nDeviceAddress, AUDIO_DEVICE_MAX_ADDRESS_LEN - 1);
@@ -1043,45 +1045,41 @@
     return jStatus;
 }
 
-static jint convertAudioPortConfigFromNative(JNIEnv *env,
-                                                 jobject jAudioPort,
-                                                 jobject *jAudioPortConfig,
-                                                 const struct audio_port_config *nAudioPortConfig)
-{
-    jint jStatus = AUDIO_JAVA_SUCCESS;
-    jobject jAudioGainConfig = NULL;
-    jobject jAudioGain = NULL;
+static jint convertAudioPortConfigFromNative(JNIEnv *env, ScopedLocalRef<jobject> *jAudioPort,
+                                             ScopedLocalRef<jobject> *jAudioPortConfig,
+                                             const struct audio_port_config *nAudioPortConfig) {
     jintArray jGainValues;
     bool audioportCreated = false;
 
     ALOGV("convertAudioPortConfigFromNative jAudioPort %p", jAudioPort);
 
-    if (jAudioPort == NULL) {
-        jobject jHandle = env->NewObject(gAudioHandleClass, gAudioHandleCstor,
-                                                 nAudioPortConfig->id);
+    if (*jAudioPort == nullptr) {
+        ScopedLocalRef<jobject> jHandle(env,
+                                        env->NewObject(gAudioHandleClass, gAudioHandleCstor,
+                                                       nAudioPortConfig->id));
 
         ALOGV("convertAudioPortConfigFromNative handle %d is a %s", nAudioPortConfig->id,
               nAudioPortConfig->type == AUDIO_PORT_TYPE_DEVICE ? "device" : "mix");
 
         if (jHandle == NULL) {
-            return (jint)AUDIO_JAVA_ERROR;
+            return AUDIO_JAVA_ERROR;
         }
         // create placeholder port and port config objects with just the correct handle
         // and configuration data. The actual AudioPortConfig objects will be
         // constructed by java code with correct class type (device, mix etc...)
         // and reference to AudioPort instance in this client
-        jAudioPort = env->NewObject(gAudioPortClass, gAudioPortCstor,
-                                           jHandle, // handle
-                                           0,       // role
-                                           NULL,    // name
-                                           NULL,    // samplingRates
-                                           NULL,    // channelMasks
-                                           NULL,    // channelIndexMasks
-                                           NULL,    // formats
-                                           NULL);   // gains
-        env->DeleteLocalRef(jHandle);
-        if (jAudioPort == NULL) {
-            return (jint)AUDIO_JAVA_ERROR;
+        jAudioPort->reset(env->NewObject(gAudioPortClass, gAudioPortCstor,
+                                         jHandle.get(), // handle
+                                         0,             // role
+                                         nullptr,       // name
+                                         nullptr,       // samplingRates
+                                         nullptr,       // channelMasks
+                                         nullptr,       // channelIndexMasks
+                                         nullptr,       // formats
+                                         nullptr));     // gains
+
+        if (*jAudioPort == nullptr) {
+            return AUDIO_JAVA_ERROR;
         }
         ALOGV("convertAudioPortConfigFromNative jAudioPort created for handle %d",
               nAudioPortConfig->id);
@@ -1089,6 +1087,9 @@
         audioportCreated = true;
     }
 
+    ScopedLocalRef<jobject> jAudioGainConfig(env, nullptr);
+    ScopedLocalRef<jobject> jAudioGain(env, nullptr);
+
     bool useInMask = audio_port_config_has_input_direction(nAudioPortConfig);
 
     audio_channel_mask_t nMask;
@@ -1102,36 +1103,28 @@
               gainIndex, nAudioPortConfig->gain.mode);
         if (audioportCreated) {
             ALOGV("convertAudioPortConfigFromNative creating gain");
-            jAudioGain = env->NewObject(gAudioGainClass, gAudioGainCstor,
-                                               gainIndex,
-                                               0,
-                                               0,
-                                               0,
-                                               0,
-                                               0,
-                                               0,
-                                               0,
-                                               0);
+            jAudioGain.reset(env->NewObject(gAudioGainClass, gAudioGainCstor, gainIndex, 0 /*mode*/,
+                                            0 /*channelMask*/, 0 /*minValue*/, 0 /*maxValue*/,
+                                            0 /*defaultValue*/, 0 /*stepValue*/,
+                                            0 /*rampDurationMinMs*/, 0 /*rampDurationMaxMs*/));
             if (jAudioGain == NULL) {
                 ALOGV("convertAudioPortConfigFromNative creating gain FAILED");
-                jStatus = (jint)AUDIO_JAVA_ERROR;
-                goto exit;
+                return AUDIO_JAVA_ERROR;
             }
         } else {
             ALOGV("convertAudioPortConfigFromNative reading gain from port");
-            jobjectArray jGains = (jobjectArray)env->GetObjectField(jAudioPort,
-                                                                      gAudioPortFields.mGains);
+            ScopedLocalRef<jobjectArray>
+                    jGains(env,
+                           static_cast<jobjectArray>(env->GetObjectField(jAudioPort->get(),
+                                                                         gAudioPortFields.mGains)));
             if (jGains == NULL) {
                 ALOGV("convertAudioPortConfigFromNative could not get gains from port");
-                jStatus = (jint)AUDIO_JAVA_ERROR;
-                goto exit;
+                return AUDIO_JAVA_ERROR;
             }
-            jAudioGain = env->GetObjectArrayElement(jGains, gainIndex);
-            env->DeleteLocalRef(jGains);
+            jAudioGain.reset(env->GetObjectArrayElement(jGains.get(), gainIndex));
             if (jAudioGain == NULL) {
                 ALOGV("convertAudioPortConfigFromNative could not get gain at index %d", gainIndex);
-                jStatus = (jint)AUDIO_JAVA_ERROR;
-                goto exit;
+                return AUDIO_JAVA_ERROR;
             }
         }
         int numValues;
@@ -1143,8 +1136,7 @@
         jGainValues = env->NewIntArray(numValues);
         if (jGainValues == NULL) {
             ALOGV("convertAudioPortConfigFromNative could not create gain values %d", numValues);
-            jStatus = (jint)AUDIO_JAVA_ERROR;
-            goto exit;
+            return AUDIO_JAVA_ERROR;
         }
         env->SetIntArrayRegion(jGainValues, 0, numValues,
                                nAudioPortConfig->gain.values);
@@ -1158,19 +1150,14 @@
             ALOGV("convertAudioPortConfigFromNative OUT mask java %x native %x", jMask, nMask);
         }
 
-        jAudioGainConfig = env->NewObject(gAudioGainConfigClass,
-                                        gAudioGainConfigCstor,
-                                        gainIndex,
-                                        jAudioGain,
-                                        nAudioPortConfig->gain.mode,
-                                        jMask,
-                                        jGainValues,
-                                        nAudioPortConfig->gain.ramp_duration_ms);
+        jAudioGainConfig.reset(env->NewObject(gAudioGainConfigClass, gAudioGainConfigCstor,
+                                              gainIndex, jAudioGain.get(),
+                                              nAudioPortConfig->gain.mode, jMask, jGainValues,
+                                              nAudioPortConfig->gain.ramp_duration_ms));
         env->DeleteLocalRef(jGainValues);
         if (jAudioGainConfig == NULL) {
             ALOGV("convertAudioPortConfigFromNative could not create gain config");
-            jStatus = (jint)AUDIO_JAVA_ERROR;
-            goto exit;
+            return AUDIO_JAVA_ERROR;
         }
     }
     jclass clazz;
@@ -1180,17 +1167,16 @@
         methodID = gAudioPortConfigCstor;
         ALOGV("convertAudioPortConfigFromNative building a generic port config");
     } else {
-        if (env->IsInstanceOf(jAudioPort, gAudioDevicePortClass)) {
+        if (env->IsInstanceOf(jAudioPort->get(), gAudioDevicePortClass)) {
             clazz = gAudioDevicePortConfigClass;
             methodID = gAudioDevicePortConfigCstor;
             ALOGV("convertAudioPortConfigFromNative building a device config");
-        } else if (env->IsInstanceOf(jAudioPort, gAudioMixPortClass)) {
+        } else if (env->IsInstanceOf(jAudioPort->get(), gAudioMixPortClass)) {
             clazz = gAudioMixPortConfigClass;
             methodID = gAudioMixPortConfigCstor;
             ALOGV("convertAudioPortConfigFromNative building a mix config");
         } else {
-            jStatus = (jint)AUDIO_JAVA_ERROR;
-            goto exit;
+            return AUDIO_JAVA_ERROR;
         }
     }
     nMask = (nAudioPortConfig->config_mask & AUDIO_PORT_CONFIG_CHANNEL_MASK)
@@ -1204,8 +1190,8 @@
         ALOGV("convertAudioPortConfigFromNative OUT mask java %x native %x", jMask, nMask);
     }
 
-    *jAudioPortConfig =
-            env->NewObject(clazz, methodID, jAudioPort,
+    jAudioPortConfig->reset(
+            env->NewObject(clazz, methodID, jAudioPort->get(),
                            (nAudioPortConfig->config_mask & AUDIO_PORT_CONFIG_SAMPLE_RATE)
                                    ? nAudioPortConfig->sample_rate
                                    : AUDIO_CONFIG_BASE_INITIALIZER.sample_rate,
@@ -1214,31 +1200,14 @@
                                    (nAudioPortConfig->config_mask & AUDIO_PORT_CONFIG_FORMAT)
                                            ? nAudioPortConfig->format
                                            : AUDIO_CONFIG_BASE_INITIALIZER.format),
-                           jAudioGainConfig);
+                           jAudioGainConfig.get()));
     if (*jAudioPortConfig == NULL) {
         ALOGV("convertAudioPortConfigFromNative could not create new port config");
-        jStatus = (jint)AUDIO_JAVA_ERROR;
+        return AUDIO_JAVA_ERROR;
     } else {
         ALOGV("convertAudioPortConfigFromNative OK");
     }
-
-exit:
-    if (audioportCreated) {
-        env->DeleteLocalRef(jAudioPort);
-        if (jAudioGain != NULL) {
-            env->DeleteLocalRef(jAudioGain);
-        }
-    }
-    if (jAudioGainConfig != NULL) {
-        env->DeleteLocalRef(jAudioGainConfig);
-    }
-    return jStatus;
-}
-
-// TODO: pull out to separate file
-template <typename T, size_t N>
-static constexpr size_t array_size(const T (&)[N]) {
-    return N;
+    return AUDIO_JAVA_SUCCESS;
 }
 
 static jintArray convertEncapsulationInfoFromNative(JNIEnv *env, uint32_t encapsulationInfo) {
@@ -1252,7 +1221,8 @@
         }
     }
     jintArray result = env->NewIntArray(encapsulation.size());
-    env->SetIntArrayRegion(result, 0, encapsulation.size(), (jint *)encapsulation.data());
+    env->SetIntArrayRegion(result, 0, encapsulation.size(),
+                           reinterpret_cast<jint *>(encapsulation.data()));
     return result;
 }
 
@@ -1260,8 +1230,8 @@
                                              std::stringstream &ss) {
     ss << " num_audio_profiles " << nAudioPort->num_audio_profiles << " num_gains "
        << nAudioPort->num_gains;
-    if (nAudioPort->num_audio_profiles > array_size(nAudioPort->audio_profiles) ||
-        nAudioPort->num_gains > array_size(nAudioPort->gains)) {
+    if (nAudioPort->num_audio_profiles > std::size(nAudioPort->audio_profiles) ||
+        nAudioPort->num_gains > std::size(nAudioPort->gains)) {
         return true;
     }
     for (size_t i = 0; i < nAudioPort->num_audio_profiles; ++i) {
@@ -1269,16 +1239,16 @@
            << " num_sample_rates " << nAudioPort->audio_profiles[i].num_sample_rates
            << " num_channel_masks " << nAudioPort->audio_profiles[i].num_channel_masks;
         if (nAudioPort->audio_profiles[i].num_sample_rates >
-                    array_size(nAudioPort->audio_profiles[i].sample_rates) ||
+                    std::size(nAudioPort->audio_profiles[i].sample_rates) ||
             nAudioPort->audio_profiles[i].num_channel_masks >
-                    array_size(nAudioPort->audio_profiles[i].channel_masks)) {
+                    std::size(nAudioPort->audio_profiles[i].channel_masks)) {
             return true;
         }
     }
     return false;
 }
 
-static jint convertAudioProfileFromNative(JNIEnv *env, jobject *jAudioProfile,
+static jint convertAudioProfileFromNative(JNIEnv *env, ScopedLocalRef<jobject> *jAudioProfile,
                                           const audio_profile *nAudioProfile, bool useInMask) {
     size_t numPositionMasks = 0;
     size_t numIndexMasks = 0;
@@ -1309,7 +1279,8 @@
 
     if (nAudioProfile->num_sample_rates) {
         env->SetIntArrayRegion(jSamplingRates.get(), 0 /*start*/, nAudioProfile->num_sample_rates,
-                               (jint *)nAudioProfile->sample_rates);
+                               const_cast<jint *>(reinterpret_cast<const jint *>(
+                                       nAudioProfile->sample_rates)));
     }
 
     // put the masks in the output arrays
@@ -1331,10 +1302,9 @@
         ALOGW("Unknown encapsulation type for JAVA API: %u", nAudioProfile->encapsulation_type);
     }
 
-    *jAudioProfile = env->NewObject(gAudioProfileClass, gAudioProfileCstor, audioFormat,
-                                    jSamplingRates.get(), jChannelMasks.get(),
-                                    jChannelIndexMasks.get(), encapsulationType);
-
+    jAudioProfile->reset(env->NewObject(gAudioProfileClass, gAudioProfileCstor, audioFormat,
+                                        jSamplingRates.get(), jChannelMasks.get(),
+                                        jChannelIndexMasks.get(), encapsulationType));
     if (*jAudioProfile == nullptr) {
         return AUDIO_JAVA_ERROR;
     }
@@ -1342,18 +1312,8 @@
     return AUDIO_JAVA_SUCCESS;
 }
 
-static jint convertAudioPortFromNative(JNIEnv *env, jobject *jAudioPort,
+static jint convertAudioPortFromNative(JNIEnv *env, ScopedLocalRef<jobject> *jAudioPort,
                                        const struct audio_port_v7 *nAudioPort) {
-    jint jStatus = (jint)AUDIO_JAVA_SUCCESS;
-    jintArray jEncapsulationModes = NULL;
-    jintArray jEncapsulationMetadataTypes = NULL;
-    jobjectArray jGains = NULL;
-    jobject jHandle = NULL;
-    jobject jAudioPortConfig = NULL;
-    jstring jDeviceName = NULL;
-    jobject jAudioProfiles = NULL;
-    jobject jAudioDescriptors = nullptr;
-    ScopedLocalRef<jobject> jPcmFloatProfileFromExtendedInteger(env, nullptr);
     bool hasFloat = false;
     bool useInMask;
 
@@ -1377,30 +1337,30 @@
         } else {
             ALOGE("%s", s.c_str());
         }
-        jStatus = (jint)AUDIO_JAVA_ERROR;
-        goto exit;
+        return AUDIO_JAVA_ERROR;
     }
 
     useInMask = audio_has_input_direction(nAudioPort->type, nAudioPort->role);
-    jAudioProfiles = env->NewObject(gArrayListClass, gArrayListMethods.cstor);
+    ScopedLocalRef<jobject> jAudioProfiles(env,
+                                           env->NewObject(gArrayListClass,
+                                                          gArrayListMethods.cstor));
     if (jAudioProfiles == nullptr) {
-        jStatus = (jint)AUDIO_JAVA_ERROR;
-        goto exit;
+        return AUDIO_JAVA_ERROR;
     }
 
+    ScopedLocalRef<jobject> jPcmFloatProfileFromExtendedInteger(env, nullptr);
     for (size_t i = 0; i < nAudioPort->num_audio_profiles; ++i) {
-        jobject jAudioProfile = nullptr;
-        jStatus = convertAudioProfileFromNative(env, &jAudioProfile, &nAudioPort->audio_profiles[i],
-                                                useInMask);
+        ScopedLocalRef<jobject> jAudioProfile(env);
+        jint jStatus = convertAudioProfileFromNative(env, &jAudioProfile,
+                                                     &nAudioPort->audio_profiles[i], useInMask);
         if (jStatus == AUDIO_JAVA_BAD_VALUE) {
             // skipping Java layer unsupported audio formats
             continue;
         }
         if (jStatus != NO_ERROR) {
-            jStatus = (jint)AUDIO_JAVA_ERROR;
-            goto exit;
+            return AUDIO_JAVA_ERROR;
         }
-        env->CallBooleanMethod(jAudioProfiles, gArrayListMethods.add, jAudioProfile);
+        env->CallBooleanMethod(jAudioProfiles.get(), gArrayListMethods.add, jAudioProfile.get());
 
         if (nAudioPort->audio_profiles[i].format == AUDIO_FORMAT_PCM_FLOAT) {
             hasFloat = true;
@@ -1409,21 +1369,23 @@
                    audio_bytes_per_sample(nAudioPort->audio_profiles[i].format) > 2) {
             ScopedLocalRef<jintArray>
                     jSamplingRates(env,
-                                   (jintArray)
-                                           env->GetObjectField(jAudioProfile,
-                                                               gAudioProfileFields.mSamplingRates));
+                                   static_cast<jintArray>(
+                                           env->GetObjectField(jAudioProfile.get(),
+                                                               gAudioProfileFields
+                                                                       .mSamplingRates)));
             ScopedLocalRef<jintArray>
                     jChannelMasks(env,
-                                  (jintArray)
-                                          env->GetObjectField(jAudioProfile,
-                                                              gAudioProfileFields.mChannelMasks));
+                                  static_cast<jintArray>(
+                                          env->GetObjectField(jAudioProfile.get(),
+                                                              gAudioProfileFields.mChannelMasks)));
             ScopedLocalRef<jintArray>
                     jChannelIndexMasks(env,
-                                       (jintArray)env->GetObjectField(jAudioProfile,
-                                                                      gAudioProfileFields
-                                                                              .mChannelIndexMasks));
+                                       static_cast<jintArray>(
+                                               env->GetObjectField(jAudioProfile.get(),
+                                                                   gAudioProfileFields
+                                                                           .mChannelIndexMasks)));
             int encapsulationType =
-                    env->GetIntField(jAudioProfile, gAudioProfileFields.mEncapsulationType);
+                    env->GetIntField(jAudioProfile.get(), gAudioProfileFields.mEncapsulationType);
 
             jPcmFloatProfileFromExtendedInteger.reset(
                     env->NewObject(gAudioProfileClass, gAudioProfileCstor,
@@ -1431,24 +1393,21 @@
                                    jSamplingRates.get(), jChannelMasks.get(),
                                    jChannelIndexMasks.get(), encapsulationType));
         }
-
-        if (jAudioProfile != nullptr) {
-            env->DeleteLocalRef(jAudioProfile);
-        }
     }
     if (!hasFloat && jPcmFloatProfileFromExtendedInteger.get() != nullptr) {
         // R and earlier compatibility - add ENCODING_PCM_FLOAT to the end
         // (replacing the zero pad). This ensures pre-S apps that look
         // for ENCODING_PCM_FLOAT continue to see that encoding if the device supports
         // extended precision integers.
-        env->CallBooleanMethod(jAudioProfiles, gArrayListMethods.add,
+        env->CallBooleanMethod(jAudioProfiles.get(), gArrayListMethods.add,
                                jPcmFloatProfileFromExtendedInteger.get());
     }
 
-    jAudioDescriptors = env->NewObject(gArrayListClass, gArrayListMethods.cstor);
+    ScopedLocalRef<jobject> jAudioDescriptors(env,
+                                              env->NewObject(gArrayListClass,
+                                                             gArrayListMethods.cstor));
     if (jAudioDescriptors == nullptr) {
-        jStatus = (jint)AUDIO_JAVA_ERROR;
-        goto exit;
+        return AUDIO_JAVA_ERROR;
     }
     for (size_t i = 0; i < nAudioPort->num_extra_audio_descriptors; ++i) {
         const auto &extraAudioDescriptor = nAudioPort->extra_audio_descriptors[i];
@@ -1478,15 +1437,16 @@
                                         env->NewObject(gAudioDescriptorClass, gAudioDescriptorCstor,
                                                        standard, encapsulationType,
                                                        jDescriptor.get()));
-        env->CallBooleanMethod(jAudioDescriptors, gArrayListMethods.add, jAudioDescriptor.get());
+        env->CallBooleanMethod(jAudioDescriptors.get(), gArrayListMethods.add,
+                               jAudioDescriptor.get());
     }
 
     // gains
-    jGains = env->NewObjectArray(nAudioPort->num_gains,
-                                          gAudioGainClass, NULL);
-    if (jGains == NULL) {
-        jStatus = (jint)AUDIO_JAVA_ERROR;
-        goto exit;
+    ScopedLocalRef<jobjectArray> jGains(env,
+                                        env->NewObjectArray(nAudioPort->num_gains, gAudioGainClass,
+                                                            nullptr));
+    if (jGains == nullptr) {
+        return AUDIO_JAVA_ERROR;
     }
 
     for (size_t j = 0; j < nAudioPort->num_gains; j++) {
@@ -1511,88 +1471,71 @@
                                                  nAudioPort->gains[j].min_ramp_ms,
                                                  nAudioPort->gains[j].max_ramp_ms);
         if (jGain == NULL) {
-            jStatus = (jint)AUDIO_JAVA_ERROR;
-            goto exit;
+            return AUDIO_JAVA_ERROR;
         }
-        env->SetObjectArrayElement(jGains, j, jGain);
+        env->SetObjectArrayElement(jGains.get(), j, jGain);
         env->DeleteLocalRef(jGain);
     }
 
-    jHandle = env->NewObject(gAudioHandleClass, gAudioHandleCstor,
-                                             nAudioPort->id);
-    if (jHandle == NULL) {
-        jStatus = (jint)AUDIO_JAVA_ERROR;
-        goto exit;
+    ScopedLocalRef<jobject> jHandle(env,
+                                    env->NewObject(gAudioHandleClass, gAudioHandleCstor,
+                                                   nAudioPort->id));
+    if (jHandle == nullptr) {
+        return AUDIO_JAVA_ERROR;
     }
 
-    jDeviceName = env->NewStringUTF(nAudioPort->name);
-
+    ScopedLocalRef<jstring> jDeviceName(env, env->NewStringUTF(nAudioPort->name));
     if (nAudioPort->type == AUDIO_PORT_TYPE_DEVICE) {
-        ALOGV("convertAudioPortFromNative is a device %08x", nAudioPort->ext.device.type);
-        jstring jAddress = env->NewStringUTF(nAudioPort->ext.device.address);
-        jEncapsulationModes =
-                convertEncapsulationInfoFromNative(env, nAudioPort->ext.device.encapsulation_modes);
-        jEncapsulationMetadataTypes =
+        ScopedLocalRef<jintArray> jEncapsulationModes(
+                env,
+                convertEncapsulationInfoFromNative(env,
+                                                   nAudioPort->ext.device.encapsulation_modes));
+        ScopedLocalRef<jintArray> jEncapsulationMetadataTypes(
+                env,
                 convertEncapsulationInfoFromNative(env,
                                                    nAudioPort->ext.device
-                                                           .encapsulation_metadata_types);
-        *jAudioPort =
-                env->NewObject(gAudioDevicePortClass, gAudioDevicePortCstor, jHandle, jDeviceName,
-                               jAudioProfiles, jGains, nAudioPort->ext.device.type, jAddress,
-                               jEncapsulationModes, jEncapsulationMetadataTypes, jAudioDescriptors);
-        env->DeleteLocalRef(jAddress);
+                                                           .encapsulation_metadata_types));
+        ALOGV("convertAudioPortFromNative is a device %08x", nAudioPort->ext.device.type);
+        ScopedLocalRef<jstring> jAddress(env, env->NewStringUTF(nAudioPort->ext.device.address));
+        jAudioPort->reset(env->NewObject(gAudioDevicePortClass, gAudioDevicePortCstor,
+                                         jHandle.get(), jDeviceName.get(), jAudioProfiles.get(),
+                                         jGains.get(), nAudioPort->ext.device.type, jAddress.get(),
+                                         jEncapsulationModes.get(),
+                                         jEncapsulationMetadataTypes.get(),
+                                         jAudioDescriptors.get()));
     } else if (nAudioPort->type == AUDIO_PORT_TYPE_MIX) {
         ALOGV("convertAudioPortFromNative is a mix");
-        *jAudioPort = env->NewObject(gAudioMixPortClass, gAudioMixPortCstor, jHandle,
-                                     nAudioPort->ext.mix.handle, nAudioPort->role, jDeviceName,
-                                     jAudioProfiles, jGains);
+        jAudioPort->reset(env->NewObject(gAudioMixPortClass, gAudioMixPortCstor, jHandle.get(),
+                                         nAudioPort->ext.mix.handle, nAudioPort->role,
+                                         jDeviceName.get(), jAudioProfiles.get(), jGains.get()));
     } else {
         ALOGE("convertAudioPortFromNative unknown nAudioPort type %d", nAudioPort->type);
-        jStatus = (jint)AUDIO_JAVA_ERROR;
-        goto exit;
+        return AUDIO_JAVA_ERROR;
     }
     if (*jAudioPort == NULL) {
-        jStatus = (jint)AUDIO_JAVA_ERROR;
-        goto exit;
+        return AUDIO_JAVA_ERROR;
     }
 
-    jStatus = convertAudioPortConfigFromNative(env,
-                                                       *jAudioPort,
-                                                       &jAudioPortConfig,
+    ScopedLocalRef<jobject> jAudioPortConfig(env, nullptr);
+
+    if (int jStatus = convertAudioPortConfigFromNative(env, jAudioPort, &jAudioPortConfig,
                                                        &nAudioPort->active_config);
-    if (jStatus != AUDIO_JAVA_SUCCESS) {
-        goto exit;
+        jStatus != AUDIO_JAVA_SUCCESS) {
+        return jStatus;
     }
 
-    env->SetObjectField(*jAudioPort, gAudioPortFields.mActiveConfig, jAudioPortConfig);
+    env->SetObjectField(jAudioPort->get(), gAudioPortFields.mActiveConfig, jAudioPortConfig.get());
+    return AUDIO_JAVA_SUCCESS;
+}
 
-exit:
-    if (jDeviceName != NULL) {
-        env->DeleteLocalRef(jDeviceName);
+static bool setGeneration(JNIEnv *env, jintArray jGeneration, unsigned int generation1) {
+    ScopedIntArrayRW nGeneration(env, jGeneration);
+    if (nGeneration.get() == nullptr) {
+        return false;
+    } else {
+        nGeneration[0] = generation1;
+        return true;
     }
-    if (jEncapsulationModes != NULL) {
-        env->DeleteLocalRef(jEncapsulationModes);
-    }
-    if (jEncapsulationMetadataTypes != NULL) {
-        env->DeleteLocalRef(jEncapsulationMetadataTypes);
-    }
-    if (jAudioProfiles != NULL) {
-        env->DeleteLocalRef(jAudioProfiles);
-    }
-    if (jGains != NULL) {
-        env->DeleteLocalRef(jGains);
-    }
-    if (jHandle != NULL) {
-        env->DeleteLocalRef(jHandle);
-    }
-    if (jAudioPortConfig != NULL) {
-        env->DeleteLocalRef(jAudioPortConfig);
-    }
-    if (jAudioDescriptors != nullptr) {
-        env->DeleteLocalRef(jAudioDescriptors);
-    }
-
-    return jStatus;
 }
 
 static jint
@@ -1603,23 +1546,22 @@
 
     if (jPorts == NULL) {
         ALOGE("listAudioPorts NULL AudioPort ArrayList");
-        return (jint)AUDIO_JAVA_BAD_VALUE;
+        return AUDIO_JAVA_BAD_VALUE;
     }
     if (!env->IsInstanceOf(jPorts, gArrayListClass)) {
         ALOGE("listAudioPorts not an arraylist");
-        return (jint)AUDIO_JAVA_BAD_VALUE;
+        return AUDIO_JAVA_BAD_VALUE;
     }
 
     if (jGeneration == NULL || env->GetArrayLength(jGeneration) != 1) {
-        return (jint)AUDIO_JAVA_BAD_VALUE;
+        return AUDIO_JAVA_BAD_VALUE;
     }
 
     status_t status;
-    unsigned int generation1;
+    unsigned int generation1 = 0;
     unsigned int generation;
     unsigned int numPorts;
-    jint *nGeneration;
-    struct audio_port_v7 *nPorts = nullptr;
+    std::vector<audio_port_v7> nPorts;
     int attempts = MAX_PORT_GENERATION_SYNC_ATTEMPTS;
     jint jStatus;
 
@@ -1638,43 +1580,29 @@
             break;
         }
         if (numPorts == 0) {
-            jStatus = (jint)AUDIO_JAVA_SUCCESS;
-            goto exit;
+            return setGeneration(env, jGeneration, generation1) ? AUDIO_JAVA_SUCCESS
+                                                                : AUDIO_JAVA_ERROR;
         }
-        nPorts = (struct audio_port_v7 *)realloc(nPorts, numPorts * sizeof(struct audio_port_v7));
+        nPorts.resize(numPorts);
 
         status = AudioSystem::listAudioPorts(AUDIO_PORT_ROLE_NONE, AUDIO_PORT_TYPE_NONE, &numPorts,
-                                             nPorts, &generation);
+                                             &nPorts[0], &generation);
         ALOGV("listAudioPorts AudioSystem::listAudioPorts numPorts %d generation %d generation1 %d",
               numPorts, generation, generation1);
     } while (generation1 != generation && status == NO_ERROR);
 
     jStatus = nativeToJavaStatus(status);
-    if (jStatus != AUDIO_JAVA_SUCCESS) {
-        goto exit;
-    }
-
-    for (size_t i = 0; i < numPorts; i++) {
-        jobject jAudioPort = NULL;
-        jStatus = convertAudioPortFromNative(env, &jAudioPort, &nPorts[i]);
-        if (jStatus != AUDIO_JAVA_SUCCESS) {
-            goto exit;
-        }
-        env->CallBooleanMethod(jPorts, gArrayListMethods.add, jAudioPort);
-        if (jAudioPort != NULL) {
-            env->DeleteLocalRef(jAudioPort);
+    if (jStatus == AUDIO_JAVA_SUCCESS) {
+        for (size_t i = 0; i < numPorts; i++) {
+            ScopedLocalRef<jobject> jAudioPort(env, nullptr);
+            jStatus = convertAudioPortFromNative(env, &jAudioPort, &nPorts[i]);
+            if (jStatus != AUDIO_JAVA_SUCCESS) break;
+            env->CallBooleanMethod(jPorts, gArrayListMethods.add, jAudioPort.get());
         }
     }
-
-exit:
-    nGeneration = env->GetIntArrayElements(jGeneration, NULL);
-    if (nGeneration == NULL) {
-        jStatus = (jint)AUDIO_JAVA_ERROR;
-    } else {
-        nGeneration[0] = generation1;
-        env->ReleaseIntArrayElements(jGeneration, nGeneration, 0);
+    if (!setGeneration(env, jGeneration, generation1)) {
+        jStatus = AUDIO_JAVA_ERROR;
     }
-    free(nPorts);
     return jStatus;
 }
 
@@ -1687,64 +1615,56 @@
 
     ALOGV("createAudioPatch");
     if (jPatches == NULL || jSources == NULL || jSinks == NULL) {
-        return (jint)AUDIO_JAVA_BAD_VALUE;
+        return AUDIO_JAVA_BAD_VALUE;
     }
 
     if (env->GetArrayLength(jPatches) != 1) {
-        return (jint)AUDIO_JAVA_BAD_VALUE;
+        return AUDIO_JAVA_BAD_VALUE;
     }
     jint numSources = env->GetArrayLength(jSources);
     if (numSources == 0 || numSources > AUDIO_PATCH_PORTS_MAX) {
-        return (jint)AUDIO_JAVA_BAD_VALUE;
+        return AUDIO_JAVA_BAD_VALUE;
     }
 
     jint numSinks = env->GetArrayLength(jSinks);
     if (numSinks == 0 || numSinks > AUDIO_PATCH_PORTS_MAX) {
-        return (jint)AUDIO_JAVA_BAD_VALUE;
+        return AUDIO_JAVA_BAD_VALUE;
     }
 
-    audio_patch_handle_t handle = (audio_patch_handle_t)0;
-    jobject jPatch = env->GetObjectArrayElement(jPatches, 0);
-    jobject jPatchHandle = NULL;
-    if (jPatch != NULL) {
-        if (!env->IsInstanceOf(jPatch, gAudioPatchClass)) {
-            return (jint)AUDIO_JAVA_BAD_VALUE;
+    audio_patch_handle_t handle = static_cast<audio_patch_handle_t>(AUDIO_PATCH_HANDLE_NONE);
+    ScopedLocalRef<jobject> jPatch(env, env->GetObjectArrayElement(jPatches, 0));
+    ScopedLocalRef<jobject> jPatchHandle(env, nullptr);
+    if (jPatch != nullptr) {
+        if (!env->IsInstanceOf(jPatch.get(), gAudioPatchClass)) {
+            return AUDIO_JAVA_BAD_VALUE;
         }
-        jPatchHandle = env->GetObjectField(jPatch, gAudioPatchFields.mHandle);
-        handle = (audio_patch_handle_t)env->GetIntField(jPatchHandle, gAudioHandleFields.mId);
+        jPatchHandle.reset(env->GetObjectField(jPatch.get(), gAudioPatchFields.mHandle));
+        handle = static_cast<audio_patch_handle_t>(
+                env->GetIntField(jPatchHandle.get(), gAudioHandleFields.mId));
     }
 
     struct audio_patch nPatch = { .id = handle };
 
-    jobject jSource = NULL;
-    jobject jSink = NULL;
-
     for (jint i = 0; i < numSources; i++) {
-        jSource = env->GetObjectArrayElement(jSources, i);
-        if (!env->IsInstanceOf(jSource, gAudioPortConfigClass)) {
-            jStatus = (jint)AUDIO_JAVA_BAD_VALUE;
-            goto exit;
+        ScopedLocalRef<jobject> jSource(env, env->GetObjectArrayElement(jSources, i));
+        if (!env->IsInstanceOf(jSource.get(), gAudioPortConfigClass)) {
+            return AUDIO_JAVA_BAD_VALUE;
         }
-        jStatus = convertAudioPortConfigToNative(env, &nPatch.sources[i], jSource, false);
-        env->DeleteLocalRef(jSource);
-        jSource = NULL;
+        jStatus = convertAudioPortConfigToNative(env, &nPatch.sources[i], jSource.get(), false);
         if (jStatus != AUDIO_JAVA_SUCCESS) {
-            goto exit;
+            return jStatus;
         }
         nPatch.num_sources++;
     }
 
     for (jint i = 0; i < numSinks; i++) {
-        jSink = env->GetObjectArrayElement(jSinks, i);
-        if (!env->IsInstanceOf(jSink, gAudioPortConfigClass)) {
-            jStatus = (jint)AUDIO_JAVA_BAD_VALUE;
-            goto exit;
+        ScopedLocalRef<jobject> jSink(env, env->GetObjectArrayElement(jSinks, i));
+        if (!env->IsInstanceOf(jSink.get(), gAudioPortConfigClass)) {
+            return AUDIO_JAVA_BAD_VALUE;
         }
-        jStatus = convertAudioPortConfigToNative(env, &nPatch.sinks[i], jSink, false);
-        env->DeleteLocalRef(jSink);
-        jSink = NULL;
+        jStatus = convertAudioPortConfigToNative(env, &nPatch.sinks[i], jSink.get(), false);
         if (jStatus != AUDIO_JAVA_SUCCESS) {
-            goto exit;
+            return jStatus;
         }
         nPatch.num_sinks++;
     }
@@ -1755,38 +1675,22 @@
 
     jStatus = nativeToJavaStatus(status);
     if (jStatus != AUDIO_JAVA_SUCCESS) {
-        goto exit;
+        return jStatus;
     }
 
-    if (jPatchHandle == NULL) {
-        jPatchHandle = env->NewObject(gAudioHandleClass, gAudioHandleCstor,
-                                           handle);
-        if (jPatchHandle == NULL) {
-            jStatus = (jint)AUDIO_JAVA_ERROR;
-            goto exit;
+    if (jPatchHandle == nullptr) {
+        jPatchHandle.reset(env->NewObject(gAudioHandleClass, gAudioHandleCstor, handle));
+        if (jPatchHandle == nullptr) {
+            return AUDIO_JAVA_ERROR;
         }
-        jPatch = env->NewObject(gAudioPatchClass, gAudioPatchCstor, jPatchHandle, jSources, jSinks);
-        if (jPatch == NULL) {
-            jStatus = (jint)AUDIO_JAVA_ERROR;
-            goto exit;
+        jPatch.reset(env->NewObject(gAudioPatchClass, gAudioPatchCstor, jPatchHandle.get(),
+                                    jSources, jSinks));
+        if (jPatch == nullptr) {
+            return AUDIO_JAVA_ERROR;
         }
-        env->SetObjectArrayElement(jPatches, 0, jPatch);
+        env->SetObjectArrayElement(jPatches, 0, jPatch.get());
     } else {
-        env->SetIntField(jPatchHandle, gAudioHandleFields.mId, handle);
-    }
-
-exit:
-    if (jPatchHandle != NULL) {
-        env->DeleteLocalRef(jPatchHandle);
-    }
-    if (jPatch != NULL) {
-        env->DeleteLocalRef(jPatch);
-    }
-    if (jSource != NULL) {
-        env->DeleteLocalRef(jSource);
-    }
-    if (jSink != NULL) {
-        env->DeleteLocalRef(jSink);
+        env->SetIntField(jPatchHandle.get(), gAudioHandleFields.mId, handle);
     }
     return jStatus;
 }
@@ -1797,16 +1701,17 @@
 {
     ALOGV("releaseAudioPatch");
     if (jPatch == NULL) {
-        return (jint)AUDIO_JAVA_BAD_VALUE;
+        return AUDIO_JAVA_BAD_VALUE;
     }
 
-    audio_patch_handle_t handle = (audio_patch_handle_t)0;
+    audio_patch_handle_t handle = static_cast<audio_patch_handle_t>(AUDIO_PATCH_HANDLE_NONE);
     jobject jPatchHandle = NULL;
     if (!env->IsInstanceOf(jPatch, gAudioPatchClass)) {
-        return (jint)AUDIO_JAVA_BAD_VALUE;
+        return AUDIO_JAVA_BAD_VALUE;
     }
     jPatchHandle = env->GetObjectField(jPatch, gAudioPatchFields.mHandle);
-    handle = (audio_patch_handle_t)env->GetIntField(jPatchHandle, gAudioHandleFields.mId);
+    handle = static_cast<audio_patch_handle_t>(
+            env->GetIntField(jPatchHandle, gAudioHandleFields.mId));
     env->DeleteLocalRef(jPatchHandle);
 
     ALOGV("AudioSystem::releaseAudioPatch");
@@ -1823,28 +1728,22 @@
     ALOGV("listAudioPatches");
     if (jPatches == NULL) {
         ALOGE("listAudioPatches NULL AudioPatch ArrayList");
-        return (jint)AUDIO_JAVA_BAD_VALUE;
+        return AUDIO_JAVA_BAD_VALUE;
     }
     if (!env->IsInstanceOf(jPatches, gArrayListClass)) {
         ALOGE("listAudioPatches not an arraylist");
-        return (jint)AUDIO_JAVA_BAD_VALUE;
+        return AUDIO_JAVA_BAD_VALUE;
     }
 
     if (jGeneration == NULL || env->GetArrayLength(jGeneration) != 1) {
-        return (jint)AUDIO_JAVA_BAD_VALUE;
+        return AUDIO_JAVA_BAD_VALUE;
     }
 
     status_t status;
     unsigned int generation1;
     unsigned int generation;
     unsigned int numPatches;
-    jint *nGeneration;
-    struct audio_patch *nPatches = NULL;
-    jobjectArray jSources = NULL;
-    jobject jSource = NULL;
-    jobjectArray jSinks = NULL;
-    jobject jSink = NULL;
-    jobject jPatch = NULL;
+    std::vector<audio_patch> nPatches;
     int attempts = MAX_PORT_GENERATION_SYNC_ATTEMPTS;
     jint jStatus;
 
@@ -1865,15 +1764,13 @@
             break;
         }
         if (numPatches == 0) {
-            jStatus = (jint)AUDIO_JAVA_SUCCESS;
-            goto exit;
+            return setGeneration(env, jGeneration, generation1) ? AUDIO_JAVA_SUCCESS
+                                                                : AUDIO_JAVA_ERROR;
         }
 
-        nPatches = (struct audio_patch *)realloc(nPatches, numPatches * sizeof(struct audio_patch));
+        nPatches.resize(numPatches);
 
-        status = AudioSystem::listAudioPatches(&numPatches,
-                                               nPatches,
-                                               &generation);
+        status = AudioSystem::listAudioPatches(&numPatches, nPatches.data(), &generation);
         ALOGV("listAudioPatches AudioSystem::listAudioPatches numPatches %d generation %d generation1 %d",
               numPatches, generation, generation1);
 
@@ -1881,15 +1778,21 @@
 
     jStatus = nativeToJavaStatus(status);
     if (jStatus != AUDIO_JAVA_SUCCESS) {
-        goto exit;
+        if (!setGeneration(env, jGeneration, generation1)) {
+            jStatus = AUDIO_JAVA_ERROR;
+        }
+        return jStatus;
     }
 
     for (size_t i = 0; i < numPatches; i++) {
+        ScopedLocalRef<jobject> jPatch(env, nullptr);
+        ScopedLocalRef<jobjectArray> jSources(env, nullptr);
+        ScopedLocalRef<jobjectArray> jSinks(env, nullptr);
         jobject patchHandle = env->NewObject(gAudioHandleClass, gAudioHandleCstor,
                                                  nPatches[i].id);
         if (patchHandle == NULL) {
-            jStatus = AUDIO_JAVA_ERROR;
-            goto exit;
+            setGeneration(env, jGeneration, generation1);
+            return AUDIO_JAVA_ERROR;
         }
         ALOGV("listAudioPatches patch %zu num_sources %d num_sinks %d",
               i, nPatches[i].num_sources, nPatches[i].num_sinks);
@@ -1897,96 +1800,66 @@
         env->SetIntField(patchHandle, gAudioHandleFields.mId, nPatches[i].id);
 
         // load sources
-        jSources = env->NewObjectArray(nPatches[i].num_sources,
-                                       gAudioPortConfigClass, NULL);
-        if (jSources == NULL) {
-            jStatus = AUDIO_JAVA_ERROR;
-            goto exit;
+        jSources.reset(env->NewObjectArray(nPatches[i].num_sources, gAudioPortConfigClass, NULL));
+        if (jSources == nullptr) {
+            setGeneration(env, jGeneration, generation1);
+            return AUDIO_JAVA_ERROR;
         }
 
         for (size_t j = 0; j < nPatches[i].num_sources; j++) {
-            jStatus = convertAudioPortConfigFromNative(env,
-                                                      NULL,
-                                                      &jSource,
-                                                      &nPatches[i].sources[j]);
+            ScopedLocalRef<jobject> jSource(env, nullptr);
+            ScopedLocalRef<jobject> jAudioPort(env, nullptr);
+            jStatus = convertAudioPortConfigFromNative(env, &jAudioPort, &jSource,
+                                                       &nPatches[i].sources[j]);
             if (jStatus != AUDIO_JAVA_SUCCESS) {
-                goto exit;
+                if (!setGeneration(env, jGeneration, generation1)) {
+                    jStatus = AUDIO_JAVA_ERROR;
+                }
+                return jStatus;
             }
-            env->SetObjectArrayElement(jSources, j, jSource);
-            env->DeleteLocalRef(jSource);
-            jSource = NULL;
+            env->SetObjectArrayElement(jSources.get(), j, jSource.get());
             ALOGV("listAudioPatches patch %zu source %zu is a %s handle %d",
                   i, j,
                   nPatches[i].sources[j].type == AUDIO_PORT_TYPE_DEVICE ? "device" : "mix",
                   nPatches[i].sources[j].id);
         }
         // load sinks
-        jSinks = env->NewObjectArray(nPatches[i].num_sinks,
-                                     gAudioPortConfigClass, NULL);
-        if (jSinks == NULL) {
-            jStatus = AUDIO_JAVA_ERROR;
-            goto exit;
+        jSinks.reset(env->NewObjectArray(nPatches[i].num_sinks, gAudioPortConfigClass, NULL));
+        if (jSinks == nullptr) {
+            setGeneration(env, jGeneration, generation1);
+            return AUDIO_JAVA_ERROR;
         }
 
         for (size_t j = 0; j < nPatches[i].num_sinks; j++) {
-            jStatus = convertAudioPortConfigFromNative(env,
-                                                      NULL,
-                                                      &jSink,
-                                                      &nPatches[i].sinks[j]);
+            ScopedLocalRef<jobject> jSink(env, nullptr);
+            ScopedLocalRef<jobject> jAudioPort(env, nullptr);
+            jStatus = convertAudioPortConfigFromNative(env, &jAudioPort, &jSink,
+                                                       &nPatches[i].sinks[j]);
 
             if (jStatus != AUDIO_JAVA_SUCCESS) {
-                goto exit;
+                if (!setGeneration(env, jGeneration, generation1)) {
+                    jStatus = AUDIO_JAVA_ERROR;
+                }
+                return jStatus;
             }
-            env->SetObjectArrayElement(jSinks, j, jSink);
-            env->DeleteLocalRef(jSink);
-            jSink = NULL;
+            env->SetObjectArrayElement(jSinks.get(), j, jSink.get());
             ALOGV("listAudioPatches patch %zu sink %zu is a %s handle %d",
                   i, j,
                   nPatches[i].sinks[j].type == AUDIO_PORT_TYPE_DEVICE ? "device" : "mix",
                   nPatches[i].sinks[j].id);
         }
 
-        jPatch = env->NewObject(gAudioPatchClass, gAudioPatchCstor,
-                                       patchHandle, jSources, jSinks);
-        env->DeleteLocalRef(jSources);
-        jSources = NULL;
-        env->DeleteLocalRef(jSinks);
-        jSinks = NULL;
-        if (jPatch == NULL) {
-            jStatus = AUDIO_JAVA_ERROR;
-            goto exit;
+        jPatch.reset(env->NewObject(gAudioPatchClass, gAudioPatchCstor, patchHandle, jSources.get(),
+                                    jSinks.get()));
+        if (jPatch == nullptr) {
+            setGeneration(env, jGeneration, generation1);
+            return AUDIO_JAVA_ERROR;
         }
-        env->CallBooleanMethod(jPatches, gArrayListMethods.add, jPatch);
-        env->DeleteLocalRef(jPatch);
-        jPatch = NULL;
+        env->CallBooleanMethod(jPatches, gArrayListMethods.add, jPatch.get());
     }
-
-exit:
-
-    nGeneration = env->GetIntArrayElements(jGeneration, NULL);
-    if (nGeneration == NULL) {
+    if (!setGeneration(env, jGeneration, generation1)) {
         jStatus = AUDIO_JAVA_ERROR;
-    } else {
-        nGeneration[0] = generation1;
-        env->ReleaseIntArrayElements(jGeneration, nGeneration, 0);
     }
-
-    if (jSources != NULL) {
-        env->DeleteLocalRef(jSources);
-    }
-    if (jSource != NULL) {
-        env->DeleteLocalRef(jSource);
-    }
-    if (jSinks != NULL) {
-        env->DeleteLocalRef(jSinks);
-    }
-    if (jSink != NULL) {
-        env->DeleteLocalRef(jSink);
-    }
-    if (jPatch != NULL) {
-        env->DeleteLocalRef(jPatch);
-    }
-    free(nPatches);
     return jStatus;
 }
 
@@ -2035,7 +1908,7 @@
     }
     auto paa = JNIAudioAttributeHelper::makeUnique();
     jStatus = JNIAudioAttributeHelper::nativeFromJava(env, jAudioAttributes, paa.get());
-    if (jStatus != (jint)AUDIO_JAVA_SUCCESS) {
+    if (jStatus != AUDIO_JAVA_SUCCESS) {
         return jStatus;
     }
     audio_port_handle_t handle;
@@ -2052,8 +1925,7 @@
 android_media_AudioSystem_stopAudioSource(JNIEnv *env, jobject clazz, jint handle)
 {
     ALOGV("stopAudioSource");
-    status_t status = AudioSystem::stopAudioSource(
-            static_cast <audio_port_handle_t>(handle));
+    status_t status = AudioSystem::stopAudioSource(static_cast<audio_port_handle_t>(handle));
     ALOGV("AudioSystem::stopAudioSource() returned %d", status);
     return nativeToJavaStatus(status);
 }
@@ -2085,7 +1957,7 @@
 static jint
 android_media_AudioSystem_getAudioHwSyncForSession(JNIEnv *env, jobject thiz, jint sessionId)
 {
-    return (jint) AudioSystem::getAudioHwSyncForSession((audio_session_t) sessionId);
+    return AudioSystem::getAudioHwSyncForSession(static_cast<audio_session_t>(sessionId));
 }
 
 static void
@@ -2204,11 +2076,11 @@
 {
     nAudioMix->mMixType = env->GetIntField(jAudioMix, gAudioMixFields.mMixType);
     nAudioMix->mRouteFlags = env->GetIntField(jAudioMix, gAudioMixFields.mRouteFlags);
-    nAudioMix->mDeviceType = (audio_devices_t)
-            env->GetIntField(jAudioMix, gAudioMixFields.mDeviceType);
+    nAudioMix->mDeviceType =
+            static_cast<audio_devices_t>(env->GetIntField(jAudioMix, gAudioMixFields.mDeviceType));
 
-    jstring jDeviceAddress = (jstring)env->GetObjectField(jAudioMix,
-                                                           gAudioMixFields.mDeviceAddress);
+    jstring jDeviceAddress =
+            static_cast<jstring>(env->GetObjectField(jAudioMix, gAudioMixFields.mDeviceAddress));
     const char *nDeviceAddress = env->GetStringUTFChars(jDeviceAddress, NULL);
     nAudioMix->mDeviceAddress = String8(nDeviceAddress);
     env->ReleaseStringUTFChars(jDeviceAddress, nDeviceAddress);
@@ -2227,8 +2099,8 @@
     nAudioMix->mVoiceCommunicationCaptureAllowed =
             env->GetBooleanField(jRule, gAudioMixingRuleFields.mVoiceCommunicationCaptureAllowed);
     env->DeleteLocalRef(jRule);
-    jobjectArray jCriteria = (jobjectArray)env->CallObjectMethod(jRuleCriteria,
-                                                                 gArrayListMethods.toArray);
+    jobjectArray jCriteria = static_cast<jobjectArray>(
+            env->CallObjectMethod(jRuleCriteria, gArrayListMethods.toArray));
     env->DeleteLocalRef(jRuleCriteria);
 
     jint numCriteria = env->GetArrayLength(jCriteria);
@@ -2264,8 +2136,8 @@
 
             auto paa = JNIAudioAttributeHelper::makeUnique();
             jint jStatus = JNIAudioAttributeHelper::nativeFromJava(env, jAttributes, paa.get());
-            if (jStatus != (jint)AUDIO_JAVA_SUCCESS) {
-                return jStatus;
+            if (jStatus != AUDIO_JAVA_SUCCESS) {
+                    return jStatus;
             }
             if (match_rule == RULE_MATCH_ATTRIBUTE_USAGE) {
                 nCriterion.mValue.mUsage = paa->usage;
@@ -2283,7 +2155,7 @@
 
     env->DeleteLocalRef(jCriteria);
 
-    return (jint)AUDIO_JAVA_SUCCESS;
+    return AUDIO_JAVA_SUCCESS;
 }
 
 static jint
@@ -2293,34 +2165,29 @@
     ALOGV("registerPolicyMixes");
 
     if (jMixesList == NULL) {
-        return (jint)AUDIO_JAVA_BAD_VALUE;
+        return AUDIO_JAVA_BAD_VALUE;
     }
     if (!env->IsInstanceOf(jMixesList, gArrayListClass)) {
-        return (jint)AUDIO_JAVA_BAD_VALUE;
+        return AUDIO_JAVA_BAD_VALUE;
     }
-    jobjectArray jMixes = (jobjectArray)env->CallObjectMethod(jMixesList,
-                                                              gArrayListMethods.toArray);
+    jobjectArray jMixes =
+            static_cast<jobjectArray>(env->CallObjectMethod(jMixesList, gArrayListMethods.toArray));
     jint numMixes = env->GetArrayLength(jMixes);
     if (numMixes > MAX_MIXES_PER_POLICY) {
         numMixes = MAX_MIXES_PER_POLICY;
     }
 
     status_t status;
-    jint jStatus;
-    jobject jAudioMix = NULL;
     Vector <AudioMix> mixes;
     for (jint i = 0; i < numMixes; i++) {
-        jAudioMix = env->GetObjectArrayElement(jMixes, i);
-        if (!env->IsInstanceOf(jAudioMix, gAudioMixClass)) {
-            jStatus = (jint)AUDIO_JAVA_BAD_VALUE;
-            goto exit;
+        ScopedLocalRef<jobject> jAudioMix(env, env->GetObjectArrayElement(jMixes, i));
+        if (!env->IsInstanceOf(jAudioMix.get(), gAudioMixClass)) {
+            return AUDIO_JAVA_BAD_VALUE;
         }
         AudioMix mix;
-        jStatus = convertAudioMixToNative(env, &mix, jAudioMix);
-        env->DeleteLocalRef(jAudioMix);
-        jAudioMix = NULL;
-        if (jStatus != AUDIO_JAVA_SUCCESS) {
-            goto exit;
+        if (jint jStatus = convertAudioMixToNative(env, &mix, jAudioMix.get());
+            jStatus != AUDIO_JAVA_SUCCESS) {
+            return jStatus;
         }
         mixes.add(mix);
     }
@@ -2329,16 +2196,7 @@
     status = AudioSystem::registerPolicyMixes(mixes, registration);
     ALOGV("AudioSystem::registerPolicyMixes() returned %d", status);
 
-    jStatus = nativeToJavaStatus(status);
-    if (jStatus != AUDIO_JAVA_SUCCESS) {
-        goto exit;
-    }
-
-exit:
-    if (jAudioMix != NULL) {
-        env->DeleteLocalRef(jAudioMix);
-    }
-    return jStatus;
+    return nativeToJavaStatus(status);
 }
 
 static jint android_media_AudioSystem_setUidDeviceAffinities(JNIEnv *env, jobject clazz,
@@ -2348,14 +2206,14 @@
     if (results != NO_ERROR) {
         return results;
     }
-    status_t status = AudioSystem::setUidDeviceAffinities((uid_t) uid, deviceVector);
-    return (jint) nativeToJavaStatus(status);
+    status_t status = AudioSystem::setUidDeviceAffinities(uid, deviceVector);
+    return nativeToJavaStatus(status);
 }
 
 static jint android_media_AudioSystem_removeUidDeviceAffinities(JNIEnv *env, jobject clazz,
         jint uid) {
-    status_t status = AudioSystem::removeUidDeviceAffinities((uid_t) uid);
-    return (jint) nativeToJavaStatus(status);
+    status_t status = AudioSystem::removeUidDeviceAffinities(static_cast<uid_t>(uid));
+    return nativeToJavaStatus(status);
 }
 
 static jint android_media_AudioSystem_setUserIdDeviceAffinities(JNIEnv *env, jobject clazz,
@@ -2366,14 +2224,14 @@
     if (results != NO_ERROR) {
         return results;
     }
-    status_t status = AudioSystem::setUserIdDeviceAffinities((int)userId, deviceVector);
-    return (jint)nativeToJavaStatus(status);
+    status_t status = AudioSystem::setUserIdDeviceAffinities(userId, deviceVector);
+    return nativeToJavaStatus(status);
 }
 
 static jint android_media_AudioSystem_removeUserIdDeviceAffinities(JNIEnv *env, jobject clazz,
                                                                    jint userId) {
-    status_t status = AudioSystem::removeUserIdDeviceAffinities((int)userId);
-    return (jint)nativeToJavaStatus(status);
+    status_t status = AudioSystem::removeUserIdDeviceAffinities(userId);
+    return nativeToJavaStatus(status);
 }
 
 static jint
@@ -2386,19 +2244,18 @@
 android_media_AudioSystem_getStreamVolumeDB(JNIEnv *env, jobject thiz,
                                             jint stream, jint index, jint device)
 {
-    return (jfloat)AudioSystem::getStreamVolumeDB((audio_stream_type_t)stream,
-                                                  (int)index,
-                                                  (audio_devices_t)device);
+    return AudioSystem::getStreamVolumeDB(static_cast<audio_stream_type_t>(stream), index,
+                                          static_cast<audio_devices_t>(device));
 }
 
 static jint android_media_AudioSystem_getOffloadSupport(JNIEnv *env, jobject thiz, jint encoding,
                                                         jint sampleRate, jint channelMask,
                                                         jint channelIndexMask, jint streamType) {
     audio_offload_info_t format = AUDIO_INFO_INITIALIZER;
-    format.format = (audio_format_t) audioFormatToNative(encoding);
-    format.sample_rate = (uint32_t) sampleRate;
+    format.format = static_cast<audio_format_t>(audioFormatToNative(encoding));
+    format.sample_rate = sampleRate;
     format.channel_mask = nativeChannelMaskFromJavaChannelMasks(channelMask, channelIndexMask);
-    format.stream_type = (audio_stream_type_t) streamType;
+    format.stream_type = static_cast<audio_stream_type_t>(streamType);
     format.has_video = false;
     format.is_streaming = false;
     // offload duration unknown at this point:
@@ -2415,11 +2272,11 @@
 
     if (jMicrophonesInfo == NULL) {
         ALOGE("jMicrophonesInfo NULL MicrophoneInfo ArrayList");
-        return (jint)AUDIO_JAVA_BAD_VALUE;
+        return AUDIO_JAVA_BAD_VALUE;
     }
     if (!env->IsInstanceOf(jMicrophonesInfo, gArrayListClass)) {
         ALOGE("getMicrophones not an arraylist");
-        return (jint)AUDIO_JAVA_BAD_VALUE;
+        return AUDIO_JAVA_BAD_VALUE;
     }
 
     jint jStatus;
@@ -2431,7 +2288,7 @@
         return jStatus;
     }
     if (microphones.size() == 0) {
-        jStatus = (jint)AUDIO_JAVA_SUCCESS;
+        jStatus = AUDIO_JAVA_SUCCESS;
         return jStatus;
     }
     for (size_t i = 0; i < microphones.size(); i++) {
@@ -2453,7 +2310,7 @@
     jint jStatus = AUDIO_JAVA_SUCCESS;
     if (!env->IsInstanceOf(jEncodingFormatList, gArrayListClass)) {
         ALOGE("%s: jEncodingFormatList not an ArrayList", __FUNCTION__);
-        return (jint)AUDIO_JAVA_BAD_VALUE;
+        return AUDIO_JAVA_BAD_VALUE;
     }
     std::vector<audio_format_t> encodingFormats;
     status_t status =
@@ -2572,12 +2429,10 @@
 android_media_AudioSystem_setSurroundFormatEnabled(JNIEnv *env, jobject thiz,
                                                    jint audioFormat, jboolean enabled)
 {
-    status_t status = AudioSystem::setSurroundFormatEnabled(audioFormatToNative(audioFormat),
-                                                            (bool)enabled);
-    if (status != NO_ERROR) {
-        ALOGE_IF(status != NO_ERROR, "AudioSystem::setSurroundFormatEnabled error %d", status);
-    }
-    return (jint)nativeToJavaStatus(status);
+    status_t status =
+            AudioSystem::setSurroundFormatEnabled(audioFormatToNative(audioFormat), enabled);
+    ALOGE_IF(status != NO_ERROR, "AudioSystem::setSurroundFormatEnabled error %d", status);
+    return nativeToJavaStatus(status);
 }
 
 static jint android_media_AudioSystem_getMaxChannelCount(JNIEnv *env, jobject thiz) {
@@ -2618,7 +2473,7 @@
 
     status_t status = AudioSystem::setAssistantServicesUids(nativeUidsVector);
 
-    return (jint)nativeToJavaStatus(status);
+    return nativeToJavaStatus(status);
 }
 
 static jint android_media_AudioSystem_setActiveAssistantServicesUids(JNIEnv *env, jobject thiz,
@@ -2627,7 +2482,7 @@
 
     status_t status = AudioSystem::setActiveAssistantServicesUids(nativeActiveUidsVector);
 
-    return (jint)nativeToJavaStatus(status);
+    return nativeToJavaStatus(status);
 }
 
 static jint
@@ -2635,12 +2490,12 @@
     std::vector<uid_t> nativeUidsVector = convertJIntArrayToUidVector(env, uids);
 
     status_t status = AudioSystem::setA11yServicesUids(nativeUidsVector);
-    return (jint)nativeToJavaStatus(status);
+    return nativeToJavaStatus(status);
 }
 
 static jint android_media_AudioSystem_setCurrentImeUid(JNIEnv *env, jobject thiz, jint uid) {
     status_t status = AudioSystem::setCurrentImeUid(uid);
-    return (jint)nativeToJavaStatus(status);
+    return nativeToJavaStatus(status);
 }
 
 static jboolean
@@ -2658,7 +2513,7 @@
     std::vector<audio_usage_t> nativeSystemUsagesVector;
 
     if (systemUsages == nullptr) {
-        return (jint) AUDIO_JAVA_BAD_VALUE;
+        return AUDIO_JAVA_BAD_VALUE;
     }
 
     int *nativeSystemUsages = nullptr;
@@ -2675,7 +2530,7 @@
     }
 
     status_t status = AudioSystem::setSupportedSystemUsages(nativeSystemUsagesVector);
-    return (jint)nativeToJavaStatus(status);
+    return nativeToJavaStatus(status);
 }
 
 static jint
@@ -2686,16 +2541,16 @@
 static jint
 android_media_AudioSystem_setRttEnabled(JNIEnv *env, jobject thiz, jboolean enabled)
 {
-    return (jint) check_AudioSystem_Command(AudioSystem::setRttEnabled(enabled));
+    return check_AudioSystem_Command(AudioSystem::setRttEnabled(enabled));
 }
 
 static jint
 android_media_AudioSystem_setAudioHalPids(JNIEnv *env, jobject clazz, jintArray jPids)
 {
     if (jPids == NULL) {
-        return (jint) AUDIO_JAVA_BAD_VALUE;
+        return AUDIO_JAVA_BAD_VALUE;
     }
-    pid_t *nPidsArray = (pid_t *) env->GetIntArrayElements(jPids, NULL);
+    pid_t *nPidsArray = reinterpret_cast<pid_t *>(env->GetIntArrayElements(jPids, nullptr));
     std::vector<pid_t> nPids(nPidsArray, nPidsArray + env->GetArrayLength(jPids));
     status_t status = AudioSystem::setAudioHalPids(nPids);
     env->ReleaseIntArrayElements(jPids, nPidsArray, 0);
@@ -2719,9 +2574,9 @@
         return results;
     }
     int status = check_AudioSystem_Command(
-            AudioSystem::setDevicesRoleForStrategy((product_strategy_t)strategy,
-                                                   (device_role_t)role, nDevices));
-    return (jint) status;
+            AudioSystem::setDevicesRoleForStrategy(static_cast<product_strategy_t>(strategy),
+                                                   static_cast<device_role_t>(role), nDevices));
+    return status;
 }
 
 static jint android_media_AudioSystem_removeDevicesRoleForStrategy(JNIEnv *env, jobject thiz,
@@ -2734,8 +2589,8 @@
         return results;
     }
     int status = check_AudioSystem_Command(
-            AudioSystem::removeDevicesRoleForStrategy((product_strategy_t)strategy,
-                                                      (device_role_t)role, nDevices));
+            AudioSystem::removeDevicesRoleForStrategy(static_cast<product_strategy_t>(strategy),
+                                                      static_cast<device_role_t>(role), nDevices));
     return (jint)status;
 }
 
@@ -2753,10 +2608,10 @@
                                                                    jobject jDevices) {
     AudioDeviceTypeAddrVector nDevices;
     status_t status = check_AudioSystem_Command(
-            AudioSystem::getDevicesForRoleAndStrategy((product_strategy_t)strategy,
-                                                      (device_role_t)role, nDevices));
+            AudioSystem::getDevicesForRoleAndStrategy(static_cast<product_strategy_t>(strategy),
+                                                      static_cast<device_role_t>(role), nDevices));
     if (status != NO_ERROR) {
-        return (jint) status;
+        return status;
     }
     for (const auto &device : nDevices) {
         jobject jAudioDeviceAttributes = NULL;
@@ -2779,9 +2634,10 @@
         return results;
     }
     int status = check_AudioSystem_Command(
-            AudioSystem::setDevicesRoleForCapturePreset((audio_source_t)capturePreset,
-                                                        (device_role_t)role, nDevices));
-    return (jint)status;
+            AudioSystem::setDevicesRoleForCapturePreset(static_cast<audio_source_t>(capturePreset),
+                                                        static_cast<device_role_t>(role),
+                                                        nDevices));
+    return status;
 }
 
 static jint android_media_AudioSystem_addDevicesRoleForCapturePreset(
@@ -2793,9 +2649,10 @@
         return results;
     }
     int status = check_AudioSystem_Command(
-            AudioSystem::addDevicesRoleForCapturePreset((audio_source_t)capturePreset,
-                                                        (device_role_t)role, nDevices));
-    return (jint)status;
+            AudioSystem::addDevicesRoleForCapturePreset(static_cast<audio_source_t>(capturePreset),
+                                                        static_cast<device_role_t>(role),
+                                                        nDevices));
+    return status;
 }
 
 static jint android_media_AudioSystem_removeDevicesRoleForCapturePreset(
@@ -2807,17 +2664,20 @@
         return results;
     }
     int status = check_AudioSystem_Command(
-            AudioSystem::removeDevicesRoleForCapturePreset((audio_source_t)capturePreset,
-                                                           (device_role_t)role, nDevices));
-    return (jint)status;
+            AudioSystem::removeDevicesRoleForCapturePreset(static_cast<audio_source_t>(
+                                                                   capturePreset),
+                                                           static_cast<device_role_t>(role),
+                                                           nDevices));
+    return status;
 }
 
 static jint android_media_AudioSystem_clearDevicesRoleForCapturePreset(JNIEnv *env, jobject thiz,
                                                                        jint capturePreset,
                                                                        jint role) {
-    return (jint)check_AudioSystem_Command(
-            AudioSystem::clearDevicesRoleForCapturePreset((audio_source_t)capturePreset,
-                                                          (device_role_t)role));
+    return static_cast<jint>(check_AudioSystem_Command(
+            AudioSystem::clearDevicesRoleForCapturePreset(static_cast<audio_source_t>(
+                                                                  capturePreset),
+                                                          static_cast<device_role_t>(role))));
 }
 
 static jint android_media_AudioSystem_getDevicesForRoleAndCapturePreset(JNIEnv *env, jobject thiz,
@@ -2826,10 +2686,12 @@
                                                                         jobject jDevices) {
     AudioDeviceTypeAddrVector nDevices;
     status_t status = check_AudioSystem_Command(
-            AudioSystem::getDevicesForRoleAndCapturePreset((audio_source_t)capturePreset,
-                                                           (device_role_t)role, nDevices));
+            AudioSystem::getDevicesForRoleAndCapturePreset(static_cast<audio_source_t>(
+                                                                   capturePreset),
+                                                           static_cast<device_role_t>(role),
+                                                           nDevices));
     if (status != NO_ERROR) {
-        return (jint)status;
+        return status;
     }
     for (const auto &device : nDevices) {
         jobject jAudioDeviceAttributes = NULL;
@@ -2854,12 +2716,12 @@
     // components call this method often
     if (jDeviceArray == nullptr || maxResultSize == 0) {
         ALOGE("%s invalid array to store AudioDeviceAttributes", __FUNCTION__);
-        return (jint)AUDIO_JAVA_BAD_VALUE;
+        return AUDIO_JAVA_BAD_VALUE;
     }
 
     JNIAudioAttributeHelper::UniqueAaPtr paa = JNIAudioAttributeHelper::makeUnique();
     jint jStatus = JNIAudioAttributeHelper::nativeFromJava(env, jaa, paa.get());
-    if (jStatus != (jint) AUDIO_JAVA_SUCCESS) {
+    if (jStatus != AUDIO_JAVA_SUCCESS) {
         return jStatus;
     }
 
@@ -2888,7 +2750,7 @@
 static jint android_media_AudioSystem_setVibratorInfos(JNIEnv *env, jobject thiz,
                                                        jobject jVibrators) {
     if (!env->IsInstanceOf(jVibrators, gListClass)) {
-        return (jint)AUDIO_JAVA_BAD_VALUE;
+        return AUDIO_JAVA_BAD_VALUE;
     }
     const jint size = env->CallIntMethod(jVibrators, gListMethods.size);
     std::vector<media::AudioVibratorInfo> vibratorInfos;
@@ -2896,7 +2758,7 @@
         ScopedLocalRef<jobject> jVibrator(env,
                                           env->CallObjectMethod(jVibrators, gListMethods.get, i));
         if (!env->IsInstanceOf(jVibrator.get(), gVibratorClass)) {
-            return (jint)AUDIO_JAVA_BAD_VALUE;
+            return AUDIO_JAVA_BAD_VALUE;
         }
         media::AudioVibratorInfo vibratorInfo;
         vibratorInfo.id = env->CallIntMethod(jVibrator.get(), gVibratorMethods.getId);
@@ -2907,7 +2769,7 @@
                 env->CallFloatMethod(jVibrator.get(), gVibratorMethods.getMaxAmplitude);
         vibratorInfos.push_back(vibratorInfo);
     }
-    return (jint)check_AudioSystem_Command(AudioSystem::setVibratorInfos(vibratorInfos));
+    return check_AudioSystem_Command(AudioSystem::setVibratorInfos(vibratorInfos));
 }
 
 static jobject android_media_AudioSystem_getSpatializer(JNIEnv *env, jobject thiz,
@@ -2929,8 +2791,8 @@
                                                        jobjectArray jDeviceArray) {
     JNIAudioAttributeHelper::UniqueAaPtr paa = JNIAudioAttributeHelper::makeUnique();
     jint jStatus = JNIAudioAttributeHelper::nativeFromJava(env, jaa, paa.get());
-    if (jStatus != (jint)AUDIO_JAVA_SUCCESS) {
-       return false;
+    if (jStatus != AUDIO_JAVA_SUCCESS) {
+        return false;
     }
 
     AudioDeviceTypeAddrVector nDevices;
@@ -2943,7 +2805,7 @@
             return false;
         }
         jStatus = createAudioDeviceTypeAddrFromJava(env, &device, jDevice);
-        if (jStatus != (jint)AUDIO_JAVA_SUCCESS) {
+        if (jStatus != AUDIO_JAVA_SUCCESS) {
             return false;
         }
         nDevices.push_back(device);
@@ -3000,7 +2862,7 @@
                                                                jobject jFormat, jobject jaa) {
     JNIAudioAttributeHelper::UniqueAaPtr paa = JNIAudioAttributeHelper::makeUnique();
     jint jStatus = JNIAudioAttributeHelper::nativeFromJava(env, jaa, paa.get());
-    if (jStatus != (jint)AUDIO_JAVA_SUCCESS) {
+    if (jStatus != AUDIO_JAVA_SUCCESS) {
         return DIRECT_NOT_SUPPORTED;
     }
 
@@ -3023,20 +2885,20 @@
 
     if (jAudioAttributes == nullptr) {
         ALOGE("jAudioAttributes is NULL");
-        return (jint)AUDIO_JAVA_BAD_VALUE;
+        return AUDIO_JAVA_BAD_VALUE;
     }
     if (jAudioProfilesList == nullptr) {
         ALOGE("jAudioProfilesList is NULL");
-        return (jint)AUDIO_JAVA_BAD_VALUE;
+        return AUDIO_JAVA_BAD_VALUE;
     }
     if (!env->IsInstanceOf(jAudioProfilesList, gArrayListClass)) {
         ALOGE("jAudioProfilesList not an ArrayList");
-        return (jint)AUDIO_JAVA_BAD_VALUE;
+        return AUDIO_JAVA_BAD_VALUE;
     }
 
     JNIAudioAttributeHelper::UniqueAaPtr paa = JNIAudioAttributeHelper::makeUnique();
     jint jStatus = JNIAudioAttributeHelper::nativeFromJava(env, jAudioAttributes, paa.get());
-    if (jStatus != (jint)AUDIO_JAVA_SUCCESS) {
+    if (jStatus != AUDIO_JAVA_SUCCESS) {
         return jStatus;
     }
 
@@ -3049,7 +2911,7 @@
     }
 
     for (const auto &audioProfile : audioProfiles) {
-        jobject jAudioProfile;
+        ScopedLocalRef<jobject> jAudioProfile(env);
         jint jConvertProfileStatus = convertAudioProfileFromNative(
                                         env, &jAudioProfile, &audioProfile, false);
         if (jConvertProfileStatus == AUDIO_JAVA_BAD_VALUE) {
@@ -3059,8 +2921,7 @@
         if (jConvertProfileStatus != AUDIO_JAVA_SUCCESS) {
             return jConvertProfileStatus;
         }
-        env->CallBooleanMethod(jAudioProfilesList, gArrayListMethods.add, jAudioProfile);
-        env->DeleteLocalRef(jAudioProfile);
+        env->CallBooleanMethod(jAudioProfilesList, gArrayListMethods.add, jAudioProfile.get());
     }
     return jStatus;
 }
@@ -3212,8 +3073,7 @@
 
 static int android_media_AudioSystem_setBluetoothVariableLatencyEnabled(JNIEnv *env, jobject thiz,
                                                                         jboolean enabled) {
-    return (jint)check_AudioSystem_Command(
-            AudioSystem::setBluetoothVariableLatencyEnabled(enabled));
+    return check_AudioSystem_Command(AudioSystem::setBluetoothVariableLatencyEnabled(enabled));
 }
 
 static jboolean android_media_AudioSystem_isBluetoothVariableLatencyEnabled(JNIEnv *env,
@@ -3227,191 +3087,182 @@
 
 // ----------------------------------------------------------------------------
 
+#define MAKE_AUDIO_SYSTEM_METHOD(x) \
+    MAKE_JNI_NATIVE_METHOD_AUTOSIG(#x, android_media_AudioSystem_##x)
+
 static const JNINativeMethod gMethods[] =
-        {{"setParameters", "(Ljava/lang/String;)I",
-          (void *)android_media_AudioSystem_setParameters},
-         {"getParameters", "(Ljava/lang/String;)Ljava/lang/String;",
-          (void *)android_media_AudioSystem_getParameters},
-         {"muteMicrophone", "(Z)I", (void *)android_media_AudioSystem_muteMicrophone},
-         {"isMicrophoneMuted", "()Z", (void *)android_media_AudioSystem_isMicrophoneMuted},
-         {"isStreamActive", "(II)Z", (void *)android_media_AudioSystem_isStreamActive},
-         {"isStreamActiveRemotely", "(II)Z",
-          (void *)android_media_AudioSystem_isStreamActiveRemotely},
-         {"isSourceActive", "(I)Z", (void *)android_media_AudioSystem_isSourceActive},
-         {"newAudioSessionId", "()I", (void *)android_media_AudioSystem_newAudioSessionId},
-         {"newAudioPlayerId", "()I", (void *)android_media_AudioSystem_newAudioPlayerId},
-         {"newAudioRecorderId", "()I", (void *)android_media_AudioSystem_newAudioRecorderId},
-         {"setDeviceConnectionState", "(ILandroid/os/Parcel;I)I",
-          (void *)android_media_AudioSystem_setDeviceConnectionState},
-         {"getDeviceConnectionState", "(ILjava/lang/String;)I",
-          (void *)android_media_AudioSystem_getDeviceConnectionState},
-         {"handleDeviceConfigChange", "(ILjava/lang/String;Ljava/lang/String;I)I",
-          (void *)android_media_AudioSystem_handleDeviceConfigChange},
-         {"setPhoneState", "(II)I", (void *)android_media_AudioSystem_setPhoneState},
-         {"setForceUse", "(II)I", (void *)android_media_AudioSystem_setForceUse},
-         {"getForceUse", "(I)I", (void *)android_media_AudioSystem_getForceUse},
-         {"initStreamVolume", "(III)I", (void *)android_media_AudioSystem_initStreamVolume},
-         {"setStreamVolumeIndex", "(III)I", (void *)android_media_AudioSystem_setStreamVolumeIndex},
-         {"getStreamVolumeIndex", "(II)I", (void *)android_media_AudioSystem_getStreamVolumeIndex},
-         {"setVolumeIndexForAttributes", "(Landroid/media/AudioAttributes;II)I",
-          (void *)android_media_AudioSystem_setVolumeIndexForAttributes},
-         {"getVolumeIndexForAttributes", "(Landroid/media/AudioAttributes;I)I",
-          (void *)android_media_AudioSystem_getVolumeIndexForAttributes},
-         {"getMinVolumeIndexForAttributes", "(Landroid/media/AudioAttributes;)I",
-          (void *)android_media_AudioSystem_getMinVolumeIndexForAttributes},
-         {"getMaxVolumeIndexForAttributes", "(Landroid/media/AudioAttributes;)I",
-          (void *)android_media_AudioSystem_getMaxVolumeIndexForAttributes},
-         {"setMasterVolume", "(F)I", (void *)android_media_AudioSystem_setMasterVolume},
-         {"getMasterVolume", "()F", (void *)android_media_AudioSystem_getMasterVolume},
-         {"setMasterMute", "(Z)I", (void *)android_media_AudioSystem_setMasterMute},
-         {"getMasterMute", "()Z", (void *)android_media_AudioSystem_getMasterMute},
-         {"setMasterMono", "(Z)I", (void *)android_media_AudioSystem_setMasterMono},
-         {"getMasterMono", "()Z", (void *)android_media_AudioSystem_getMasterMono},
-         {"setMasterBalance", "(F)I", (void *)android_media_AudioSystem_setMasterBalance},
-         {"getMasterBalance", "()F", (void *)android_media_AudioSystem_getMasterBalance},
-         {"getPrimaryOutputSamplingRate", "()I",
-          (void *)android_media_AudioSystem_getPrimaryOutputSamplingRate},
-         {"getPrimaryOutputFrameCount", "()I",
-          (void *)android_media_AudioSystem_getPrimaryOutputFrameCount},
-         {"getOutputLatency", "(I)I", (void *)android_media_AudioSystem_getOutputLatency},
-         {"setLowRamDevice", "(ZJ)I", (void *)android_media_AudioSystem_setLowRamDevice},
-         {"checkAudioFlinger", "()I", (void *)android_media_AudioSystem_checkAudioFlinger},
-         {"setAudioFlingerBinder", "(Landroid/os/IBinder;)V",
-          (void *)android_media_AudioSystem_setAudioFlingerBinder},
-         {"listAudioPorts", "(Ljava/util/ArrayList;[I)I",
-          (void *)android_media_AudioSystem_listAudioPorts},
-         {"createAudioPatch",
-          "([Landroid/media/AudioPatch;[Landroid/media/AudioPortConfig;[Landroid/media/"
-          "AudioPortConfig;)I",
-          (void *)android_media_AudioSystem_createAudioPatch},
-         {"releaseAudioPatch", "(Landroid/media/AudioPatch;)I",
-          (void *)android_media_AudioSystem_releaseAudioPatch},
-         {"listAudioPatches", "(Ljava/util/ArrayList;[I)I",
-          (void *)android_media_AudioSystem_listAudioPatches},
-         {"setAudioPortConfig", "(Landroid/media/AudioPortConfig;)I",
-          (void *)android_media_AudioSystem_setAudioPortConfig},
-         {"startAudioSource", "(Landroid/media/AudioPortConfig;Landroid/media/AudioAttributes;)I",
-          (void *)android_media_AudioSystem_startAudioSource},
-         {"stopAudioSource", "(I)I", (void *)android_media_AudioSystem_stopAudioSource},
-         {"getAudioHwSyncForSession", "(I)I",
-          (void *)android_media_AudioSystem_getAudioHwSyncForSession},
-         {"registerPolicyMixes", "(Ljava/util/ArrayList;Z)I",
-          (void *)android_media_AudioSystem_registerPolicyMixes},
-         {"setUidDeviceAffinities", "(I[I[Ljava/lang/String;)I",
-          (void *)android_media_AudioSystem_setUidDeviceAffinities},
-         {"removeUidDeviceAffinities", "(I)I",
-          (void *)android_media_AudioSystem_removeUidDeviceAffinities},
-         {"native_register_dynamic_policy_callback", "()V",
-          (void *)android_media_AudioSystem_registerDynPolicyCallback},
-         {"native_register_recording_callback", "()V",
-          (void *)android_media_AudioSystem_registerRecordingCallback},
-         {"native_register_routing_callback", "()V",
-          (void *)android_media_AudioSystem_registerRoutingCallback},
-         {"native_register_vol_range_init_req_callback", "()V",
-          (void *)android_media_AudioSystem_registerVolRangeInitReqCallback},
-         {"systemReady", "()I", (void *)android_media_AudioSystem_systemReady},
-         {"getStreamVolumeDB", "(III)F", (void *)android_media_AudioSystem_getStreamVolumeDB},
-         {"native_get_offload_support", "(IIIII)I",
-          (void *)android_media_AudioSystem_getOffloadSupport},
-         {"getMicrophones", "(Ljava/util/ArrayList;)I",
-          (void *)android_media_AudioSystem_getMicrophones},
-         {"getSurroundFormats", "(Ljava/util/Map;)I",
-          (void *)android_media_AudioSystem_getSurroundFormats},
-         {"getReportedSurroundFormats", "(Ljava/util/ArrayList;)I",
-          (void *)android_media_AudioSystem_getReportedSurroundFormats},
-         {"setSurroundFormatEnabled", "(IZ)I",
-          (void *)android_media_AudioSystem_setSurroundFormatEnabled},
-         {"setAssistantServicesUids", "([I)I",
-          (void *)android_media_AudioSystem_setAssistantServicesUids},
-         {"setActiveAssistantServicesUids", "([I)I",
-          (void *)android_media_AudioSystem_setActiveAssistantServicesUids},
-         {"setA11yServicesUids", "([I)I", (void *)android_media_AudioSystem_setA11yServicesUids},
-         {"isHapticPlaybackSupported", "()Z",
-          (void *)android_media_AudioSystem_isHapticPlaybackSupported},
-         {"isUltrasoundSupported", "()Z", (void *)android_media_AudioSystem_isUltrasoundSupported},
-         {"getHwOffloadFormatsSupportedForBluetoothMedia", "(ILjava/util/ArrayList;)I",
-          (void *)android_media_AudioSystem_getHwOffloadFormatsSupportedForBluetoothMedia},
-         {"setSupportedSystemUsages", "([I)I",
-          (void *)android_media_AudioSystem_setSupportedSystemUsages},
-         {"setAllowedCapturePolicy", "(II)I",
-          (void *)android_media_AudioSystem_setAllowedCapturePolicy},
-         {"setRttEnabled", "(Z)I", (void *)android_media_AudioSystem_setRttEnabled},
-         {"setAudioHalPids", "([I)I", (void *)android_media_AudioSystem_setAudioHalPids},
-         {"isCallScreeningModeSupported", "()Z",
-          (void *)android_media_AudioSystem_isCallScreeningModeSupported},
-         {"setDevicesRoleForStrategy", "(II[I[Ljava/lang/String;)I",
-          (void *)android_media_AudioSystem_setDevicesRoleForStrategy},
-         {"removeDevicesRoleForStrategy", "(II[I[Ljava/lang/String;)I",
-          (void *)android_media_AudioSystem_removeDevicesRoleForStrategy},
-         {"clearDevicesRoleForStrategy", "(II)I",
-          (void *)android_media_AudioSystem_clearDevicesRoleForStrategy},
-         {"getDevicesForRoleAndStrategy", "(IILjava/util/List;)I",
-          (void *)android_media_AudioSystem_getDevicesForRoleAndStrategy},
-         {"setDevicesRoleForCapturePreset", "(II[I[Ljava/lang/String;)I",
-          (void *)android_media_AudioSystem_setDevicesRoleForCapturePreset},
-         {"addDevicesRoleForCapturePreset", "(II[I[Ljava/lang/String;)I",
-          (void *)android_media_AudioSystem_addDevicesRoleForCapturePreset},
-         {"removeDevicesRoleForCapturePreset", "(II[I[Ljava/lang/String;)I",
-          (void *)android_media_AudioSystem_removeDevicesRoleForCapturePreset},
-         {"clearDevicesRoleForCapturePreset", "(II)I",
-          (void *)android_media_AudioSystem_clearDevicesRoleForCapturePreset},
-         {"getDevicesForRoleAndCapturePreset", "(IILjava/util/List;)I",
-          (void *)android_media_AudioSystem_getDevicesForRoleAndCapturePreset},
-         {"getDevicesForAttributes",
-          "(Landroid/media/AudioAttributes;[Landroid/media/AudioDeviceAttributes;Z)I",
-          (void *)android_media_AudioSystem_getDevicesForAttributes},
-         {"setUserIdDeviceAffinities", "(I[I[Ljava/lang/String;)I",
-          (void *)android_media_AudioSystem_setUserIdDeviceAffinities},
-         {"removeUserIdDeviceAffinities", "(I)I",
-          (void *)android_media_AudioSystem_removeUserIdDeviceAffinities},
-         {"setCurrentImeUid", "(I)I", (void *)android_media_AudioSystem_setCurrentImeUid},
-         {"setVibratorInfos", "(Ljava/util/List;)I",
-          (void *)android_media_AudioSystem_setVibratorInfos},
-         {"nativeGetSpatializer",
-          "(Landroid/media/INativeSpatializerCallback;)Landroid/os/IBinder;",
-          (void *)android_media_AudioSystem_getSpatializer},
-         {"canBeSpatialized",
-          "(Landroid/media/AudioAttributes;Landroid/media/AudioFormat;"
-          "[Landroid/media/AudioDeviceAttributes;)Z",
-          (void *)android_media_AudioSystem_canBeSpatialized},
-         {"nativeGetSoundDose", "(Landroid/media/ISoundDoseCallback;)Landroid/os/IBinder;",
-          (void *)android_media_AudioSystem_nativeGetSoundDose},
-         {"getDirectPlaybackSupport",
-          "(Landroid/media/AudioFormat;Landroid/media/AudioAttributes;)I",
-          (void *)android_media_AudioSystem_getDirectPlaybackSupport},
-         {"getDirectProfilesForAttributes",
-          "(Landroid/media/AudioAttributes;Ljava/util/ArrayList;)I",
-          (void *)android_media_AudioSystem_getDirectProfilesForAttributes},
-         {"getSupportedMixerAttributes", "(ILjava/util/List;)I",
-          (void *)android_media_AudioSystem_getSupportedMixerAttributes},
-         {"setPreferredMixerAttributes",
-          "(Landroid/media/AudioAttributes;IILandroid/media/AudioMixerAttributes;)I",
-          (void *)android_media_AudioSystem_setPreferredMixerAttributes},
-         {"getPreferredMixerAttributes", "(Landroid/media/AudioAttributes;ILjava/util/List;)I",
-          (void *)android_media_AudioSystem_getPreferredMixerAttributes},
-         {"clearPreferredMixerAttributes", "(Landroid/media/AudioAttributes;II)I",
-          (void *)android_media_AudioSystem_clearPreferredMixerAttributes},
-         {"supportsBluetoothVariableLatency", "()Z",
-          (void *)android_media_AudioSystem_supportsBluetoothVariableLatency},
-         {"setBluetoothVariableLatencyEnabled", "(Z)I",
-          (void *)android_media_AudioSystem_setBluetoothVariableLatencyEnabled},
-         {"isBluetoothVariableLatencyEnabled", "()Z",
-          (void *)android_media_AudioSystem_isBluetoothVariableLatencyEnabled}};
+        {MAKE_AUDIO_SYSTEM_METHOD(setParameters),
+         MAKE_AUDIO_SYSTEM_METHOD(getParameters),
+         MAKE_AUDIO_SYSTEM_METHOD(muteMicrophone),
+         MAKE_AUDIO_SYSTEM_METHOD(isMicrophoneMuted),
+         MAKE_AUDIO_SYSTEM_METHOD(isStreamActive),
+         MAKE_AUDIO_SYSTEM_METHOD(isStreamActiveRemotely),
+         MAKE_AUDIO_SYSTEM_METHOD(isSourceActive),
+         MAKE_AUDIO_SYSTEM_METHOD(newAudioSessionId),
+         MAKE_AUDIO_SYSTEM_METHOD(newAudioPlayerId),
+         MAKE_AUDIO_SYSTEM_METHOD(newAudioRecorderId),
+         MAKE_JNI_NATIVE_METHOD("setDeviceConnectionState", "(ILandroid/os/Parcel;I)I",
+                                android_media_AudioSystem_setDeviceConnectionState),
+         MAKE_AUDIO_SYSTEM_METHOD(getDeviceConnectionState),
+         MAKE_AUDIO_SYSTEM_METHOD(handleDeviceConfigChange),
+         MAKE_AUDIO_SYSTEM_METHOD(setPhoneState),
+         MAKE_AUDIO_SYSTEM_METHOD(setForceUse),
+         MAKE_AUDIO_SYSTEM_METHOD(getForceUse),
+         MAKE_AUDIO_SYSTEM_METHOD(initStreamVolume),
+         MAKE_AUDIO_SYSTEM_METHOD(setStreamVolumeIndex),
+         MAKE_AUDIO_SYSTEM_METHOD(getStreamVolumeIndex),
+         MAKE_JNI_NATIVE_METHOD("setVolumeIndexForAttributes",
+                                "(Landroid/media/AudioAttributes;II)I",
+                                android_media_AudioSystem_setVolumeIndexForAttributes),
+         MAKE_JNI_NATIVE_METHOD("getVolumeIndexForAttributes",
+                                "(Landroid/media/AudioAttributes;I)I",
+                                android_media_AudioSystem_getVolumeIndexForAttributes),
+         MAKE_JNI_NATIVE_METHOD("getMinVolumeIndexForAttributes",
+                                "(Landroid/media/AudioAttributes;)I",
+                                android_media_AudioSystem_getMinVolumeIndexForAttributes),
+         MAKE_JNI_NATIVE_METHOD("getMaxVolumeIndexForAttributes",
+                                "(Landroid/media/AudioAttributes;)I",
+                                android_media_AudioSystem_getMaxVolumeIndexForAttributes),
+         MAKE_AUDIO_SYSTEM_METHOD(setMasterVolume),
+         MAKE_AUDIO_SYSTEM_METHOD(getMasterVolume),
+         MAKE_AUDIO_SYSTEM_METHOD(setMasterMute),
+         MAKE_AUDIO_SYSTEM_METHOD(getMasterMute),
+         MAKE_AUDIO_SYSTEM_METHOD(setMasterMono),
+         MAKE_AUDIO_SYSTEM_METHOD(getMasterMono),
+         MAKE_AUDIO_SYSTEM_METHOD(setMasterBalance),
+         MAKE_AUDIO_SYSTEM_METHOD(getMasterBalance),
+         MAKE_AUDIO_SYSTEM_METHOD(getPrimaryOutputSamplingRate),
+         MAKE_AUDIO_SYSTEM_METHOD(getPrimaryOutputFrameCount),
+         MAKE_AUDIO_SYSTEM_METHOD(getOutputLatency),
+         MAKE_AUDIO_SYSTEM_METHOD(setLowRamDevice),
+         MAKE_AUDIO_SYSTEM_METHOD(checkAudioFlinger),
+         MAKE_JNI_NATIVE_METHOD("setAudioFlingerBinder", "(Landroid/os/IBinder;)V",
+                                android_media_AudioSystem_setAudioFlingerBinder),
+         MAKE_JNI_NATIVE_METHOD("listAudioPorts", "(Ljava/util/ArrayList;[I)I",
+                                android_media_AudioSystem_listAudioPorts),
+         MAKE_JNI_NATIVE_METHOD("createAudioPatch",
+                                "([Landroid/media/AudioPatch;[Landroid/media/"
+                                "AudioPortConfig;[Landroid/media/AudioPortConfig;)I",
+                                android_media_AudioSystem_createAudioPatch),
+         MAKE_JNI_NATIVE_METHOD("releaseAudioPatch", "(Landroid/media/AudioPatch;)I",
+                                android_media_AudioSystem_releaseAudioPatch),
+         MAKE_JNI_NATIVE_METHOD("listAudioPatches", "(Ljava/util/ArrayList;[I)I",
+                                android_media_AudioSystem_listAudioPatches),
+         MAKE_JNI_NATIVE_METHOD("setAudioPortConfig", "(Landroid/media/AudioPortConfig;)I",
+                                android_media_AudioSystem_setAudioPortConfig),
+         MAKE_JNI_NATIVE_METHOD("startAudioSource",
+                                "(Landroid/media/AudioPortConfig;Landroid/media/AudioAttributes;)I",
+                                android_media_AudioSystem_startAudioSource),
+         MAKE_AUDIO_SYSTEM_METHOD(stopAudioSource),
+         MAKE_AUDIO_SYSTEM_METHOD(getAudioHwSyncForSession),
+         MAKE_JNI_NATIVE_METHOD("registerPolicyMixes", "(Ljava/util/ArrayList;Z)I",
+                                android_media_AudioSystem_registerPolicyMixes),
+         MAKE_JNI_NATIVE_METHOD("setUidDeviceAffinities", "(I[I[Ljava/lang/String;)I",
+                                android_media_AudioSystem_setUidDeviceAffinities),
+         MAKE_AUDIO_SYSTEM_METHOD(removeUidDeviceAffinities),
+         MAKE_JNI_NATIVE_METHOD_AUTOSIG("native_register_dynamic_policy_callback",
+                                        android_media_AudioSystem_registerDynPolicyCallback),
+         MAKE_JNI_NATIVE_METHOD_AUTOSIG("native_register_recording_callback",
+                                        android_media_AudioSystem_registerRecordingCallback),
+         MAKE_JNI_NATIVE_METHOD_AUTOSIG("native_register_routing_callback",
+                                        android_media_AudioSystem_registerRoutingCallback),
+         MAKE_JNI_NATIVE_METHOD_AUTOSIG("native_register_vol_range_init_req_callback",
+                                        android_media_AudioSystem_registerVolRangeInitReqCallback),
+         MAKE_AUDIO_SYSTEM_METHOD(systemReady),
+         MAKE_AUDIO_SYSTEM_METHOD(getStreamVolumeDB),
+         MAKE_JNI_NATIVE_METHOD_AUTOSIG("native_get_offload_support",
+                                        android_media_AudioSystem_getOffloadSupport),
+         MAKE_JNI_NATIVE_METHOD("getMicrophones", "(Ljava/util/ArrayList;)I",
+                                android_media_AudioSystem_getMicrophones),
+         MAKE_JNI_NATIVE_METHOD("getSurroundFormats", "(Ljava/util/Map;)I",
+                                android_media_AudioSystem_getSurroundFormats),
+         MAKE_JNI_NATIVE_METHOD("getReportedSurroundFormats", "(Ljava/util/ArrayList;)I",
+                                android_media_AudioSystem_getReportedSurroundFormats),
+         MAKE_AUDIO_SYSTEM_METHOD(setSurroundFormatEnabled),
+         MAKE_AUDIO_SYSTEM_METHOD(setAssistantServicesUids),
+         MAKE_AUDIO_SYSTEM_METHOD(setActiveAssistantServicesUids),
+         MAKE_AUDIO_SYSTEM_METHOD(setA11yServicesUids),
+         MAKE_AUDIO_SYSTEM_METHOD(isHapticPlaybackSupported),
+         MAKE_AUDIO_SYSTEM_METHOD(isUltrasoundSupported),
+         MAKE_JNI_NATIVE_METHOD(
+                 "getHwOffloadFormatsSupportedForBluetoothMedia", "(ILjava/util/ArrayList;)I",
+                 android_media_AudioSystem_getHwOffloadFormatsSupportedForBluetoothMedia),
+         MAKE_AUDIO_SYSTEM_METHOD(setSupportedSystemUsages),
+         MAKE_AUDIO_SYSTEM_METHOD(setAllowedCapturePolicy),
+         MAKE_AUDIO_SYSTEM_METHOD(setRttEnabled),
+         MAKE_AUDIO_SYSTEM_METHOD(setAudioHalPids),
+         MAKE_AUDIO_SYSTEM_METHOD(isCallScreeningModeSupported),
+         MAKE_JNI_NATIVE_METHOD("setDevicesRoleForStrategy", "(II[I[Ljava/lang/String;)I",
+                                android_media_AudioSystem_setDevicesRoleForStrategy),
+         MAKE_JNI_NATIVE_METHOD("removeDevicesRoleForStrategy", "(II[I[Ljava/lang/String;)I",
+                                android_media_AudioSystem_removeDevicesRoleForStrategy),
+         MAKE_AUDIO_SYSTEM_METHOD(clearDevicesRoleForStrategy),
+         MAKE_JNI_NATIVE_METHOD("getDevicesForRoleAndStrategy", "(IILjava/util/List;)I",
+                                android_media_AudioSystem_getDevicesForRoleAndStrategy),
+         MAKE_JNI_NATIVE_METHOD("setDevicesRoleForCapturePreset", "(II[I[Ljava/lang/String;)I",
+                                android_media_AudioSystem_setDevicesRoleForCapturePreset),
+         MAKE_JNI_NATIVE_METHOD("addDevicesRoleForCapturePreset", "(II[I[Ljava/lang/String;)I",
+                                android_media_AudioSystem_addDevicesRoleForCapturePreset),
+         MAKE_JNI_NATIVE_METHOD("removeDevicesRoleForCapturePreset", "(II[I[Ljava/lang/String;)I",
+                                android_media_AudioSystem_removeDevicesRoleForCapturePreset),
+         MAKE_AUDIO_SYSTEM_METHOD(clearDevicesRoleForCapturePreset),
+         MAKE_JNI_NATIVE_METHOD("getDevicesForRoleAndCapturePreset", "(IILjava/util/List;)I",
+                                android_media_AudioSystem_getDevicesForRoleAndCapturePreset),
+         MAKE_JNI_NATIVE_METHOD("getDevicesForAttributes",
+                                "(Landroid/media/AudioAttributes;[Landroid/media/"
+                                "AudioDeviceAttributes;Z)I",
+                                android_media_AudioSystem_getDevicesForAttributes),
+         MAKE_JNI_NATIVE_METHOD("setUserIdDeviceAffinities", "(I[I[Ljava/lang/String;)I",
+                                android_media_AudioSystem_setUserIdDeviceAffinities),
+         MAKE_AUDIO_SYSTEM_METHOD(removeUserIdDeviceAffinities),
+         MAKE_AUDIO_SYSTEM_METHOD(setCurrentImeUid),
+         MAKE_JNI_NATIVE_METHOD("setVibratorInfos", "(Ljava/util/List;)I",
+                                android_media_AudioSystem_setVibratorInfos),
+         MAKE_JNI_NATIVE_METHOD("nativeGetSpatializer",
+                                "(Landroid/media/INativeSpatializerCallback;)Landroid/os/IBinder;",
+                                android_media_AudioSystem_getSpatializer),
+         MAKE_JNI_NATIVE_METHOD("canBeSpatialized",
+                                "(Landroid/media/AudioAttributes;Landroid/media/AudioFormat;"
+                                "[Landroid/media/AudioDeviceAttributes;)Z",
+                                android_media_AudioSystem_canBeSpatialized),
+         MAKE_JNI_NATIVE_METHOD("nativeGetSoundDose",
+                                "(Landroid/media/ISoundDoseCallback;)Landroid/os/IBinder;",
+                                android_media_AudioSystem_nativeGetSoundDose),
+         MAKE_JNI_NATIVE_METHOD("getDirectPlaybackSupport",
+                                "(Landroid/media/AudioFormat;Landroid/media/AudioAttributes;)I",
+                                android_media_AudioSystem_getDirectPlaybackSupport),
+         MAKE_JNI_NATIVE_METHOD("getDirectProfilesForAttributes",
+                                "(Landroid/media/AudioAttributes;Ljava/util/ArrayList;)I",
+                                android_media_AudioSystem_getDirectProfilesForAttributes),
+         MAKE_JNI_NATIVE_METHOD("getSupportedMixerAttributes", "(ILjava/util/List;)I",
+                                android_media_AudioSystem_getSupportedMixerAttributes),
+         MAKE_JNI_NATIVE_METHOD("setPreferredMixerAttributes",
+                                "(Landroid/media/AudioAttributes;IILandroid/media/"
+                                "AudioMixerAttributes;)I",
+                                android_media_AudioSystem_setPreferredMixerAttributes),
+         MAKE_JNI_NATIVE_METHOD("getPreferredMixerAttributes",
+                                "(Landroid/media/AudioAttributes;ILjava/util/List;)I",
+                                android_media_AudioSystem_getPreferredMixerAttributes),
+         MAKE_JNI_NATIVE_METHOD("clearPreferredMixerAttributes",
+                                "(Landroid/media/AudioAttributes;II)I",
+                                android_media_AudioSystem_clearPreferredMixerAttributes),
+         MAKE_AUDIO_SYSTEM_METHOD(supportsBluetoothVariableLatency),
+         MAKE_AUDIO_SYSTEM_METHOD(setBluetoothVariableLatencyEnabled),
+         MAKE_AUDIO_SYSTEM_METHOD(isBluetoothVariableLatencyEnabled)};
 
-static const JNINativeMethod gEventHandlerMethods[] = {
-    {"native_setup",
-        "(Ljava/lang/Object;)V",
-        (void *)android_media_AudioSystem_eventHandlerSetup},
-    {"native_finalize",
-        "()V",
-        (void *)android_media_AudioSystem_eventHandlerFinalize},
-};
+static const JNINativeMethod gEventHandlerMethods[] =
+        {MAKE_JNI_NATIVE_METHOD("native_setup", "(Ljava/lang/Object;)V",
+                                android_media_AudioSystem_eventHandlerSetup),
+         MAKE_JNI_NATIVE_METHOD_AUTOSIG("native_finalize",
+                                        android_media_AudioSystem_eventHandlerFinalize)};
 
-static const JNINativeMethod gFrameworkCapabilities[] = {
-        {"native_getMaxChannelCount", "()I", (void *)android_media_AudioSystem_getMaxChannelCount},
-        {"native_getMaxSampleRate", "()I", (void *)android_media_AudioSystem_getMaxSampleRate},
-        {"native_getMinSampleRate", "()I", (void *)android_media_AudioSystem_getMinSampleRate},
-};
+static const JNINativeMethod gFrameworkCapabilities[] =
+        {MAKE_JNI_NATIVE_METHOD_AUTOSIG("native_getMaxChannelCount",
+                                        android_media_AudioSystem_getMaxChannelCount),
+         MAKE_JNI_NATIVE_METHOD_AUTOSIG("native_getMaxSampleRate",
+                                        android_media_AudioSystem_getMaxSampleRate),
+         MAKE_JNI_NATIVE_METHOD_AUTOSIG("native_getMinSampleRate",
+                                        android_media_AudioSystem_getMinSampleRate)};
 
 int register_android_media_AudioSystem(JNIEnv *env)
 {
@@ -3589,7 +3440,7 @@
     gClsAudioTrackRoutingProxy =
             android::FindClassOrDie(env, "android/media/AudioTrackRoutingProxy");
     // make sure this reference doesn't get deleted
-    gClsAudioTrackRoutingProxy = (jclass)env->NewGlobalRef(gClsAudioTrackRoutingProxy);
+    gClsAudioTrackRoutingProxy = static_cast<jclass>(env->NewGlobalRef(gClsAudioTrackRoutingProxy));
 
     gMidAudioTrackRoutingProxy_ctor =
             android::GetMethodIDOrDie(env, gClsAudioTrackRoutingProxy, "<init>", "(J)V");
@@ -3600,7 +3451,8 @@
     gClsAudioRecordRoutingProxy =
             android::FindClassOrDie(env, "android/media/AudioRecordRoutingProxy");
     // make sure this reference doesn't get deleted
-    gClsAudioRecordRoutingProxy = (jclass)env->NewGlobalRef(gClsAudioRecordRoutingProxy);
+    gClsAudioRecordRoutingProxy =
+            static_cast<jclass>(env->NewGlobalRef(gClsAudioRecordRoutingProxy));
 
     gMidAudioRecordRoutingProxy_ctor =
             android::GetMethodIDOrDie(env, gClsAudioRecordRoutingProxy, "<init>", "(J)V");
diff --git a/core/jni/android_os_GraphicsEnvironment.cpp b/core/jni/android_os_GraphicsEnvironment.cpp
index 01dbceb..afc3cbd 100644
--- a/core/jni/android_os_GraphicsEnvironment.cpp
+++ b/core/jni/android_os_GraphicsEnvironment.cpp
@@ -16,11 +16,12 @@
 
 #define LOG_TAG "GraphicsEnvironment"
 
-#include <vector>
-
 #include <graphicsenv/GraphicsEnv.h>
 #include <nativehelper/ScopedUtfChars.h>
 #include <nativeloader/native_loader.h>
+
+#include <vector>
+
 #include "core_jni_helpers.h"
 
 namespace {
@@ -49,11 +50,10 @@
                                                     appPackageNameChars.c_str(), vulkanVersion);
 }
 
-void setAngleInfo_native(JNIEnv* env, jobject clazz, jstring path, jstring appName,
-                         jstring devOptIn, jobjectArray featuresObj) {
+void setAngleInfo_native(JNIEnv* env, jobject clazz, jstring path, jboolean useSystemAngle,
+                         jstring packageName, jobjectArray featuresObj) {
     ScopedUtfChars pathChars(env, path);
-    ScopedUtfChars appNameChars(env, appName);
-    ScopedUtfChars devOptInChars(env, devOptIn);
+    ScopedUtfChars packageNameChars(env, packageName);
 
     std::vector<std::string> features;
     if (featuresObj != nullptr) {
@@ -73,13 +73,8 @@
         }
     }
 
-    android::GraphicsEnv::getInstance().setAngleInfo(pathChars.c_str(), appNameChars.c_str(),
-                                                     devOptInChars.c_str(), features);
-}
-
-bool shouldUseAngle_native(JNIEnv* env, jobject clazz, jstring appName) {
-    ScopedUtfChars appNameChars(env, appName);
-    return android::GraphicsEnv::getInstance().shouldUseAngle(appNameChars.c_str());
+    android::GraphicsEnv::getInstance().setAngleInfo(pathChars.c_str(), useSystemAngle,
+                                                     packageNameChars.c_str(), features);
 }
 
 void setLayerPaths_native(JNIEnv* env, jobject clazz, jobject classLoader, jstring layerPaths) {
@@ -123,11 +118,8 @@
          reinterpret_cast<void*>(setGpuStats_native)},
         {"setInjectLayersPrSetDumpable", "()Z",
          reinterpret_cast<void*>(setInjectLayersPrSetDumpable_native)},
-        {"setAngleInfo",
-         "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;)V",
+        {"setAngleInfo", "(Ljava/lang/String;ZLjava/lang/String;[Ljava/lang/String;)V",
          reinterpret_cast<void*>(setAngleInfo_native)},
-        {"getShouldUseAngle", "(Ljava/lang/String;)Z",
-         reinterpret_cast<void*>(shouldUseAngle_native)},
         {"setLayerPaths", "(Ljava/lang/ClassLoader;Ljava/lang/String;)V",
          reinterpret_cast<void*>(setLayerPaths_native)},
         {"setDebugLayers", "(Ljava/lang/String;)V", reinterpret_cast<void*>(setDebugLayers_native)},
diff --git a/core/jni/android_os_UEventObserver.cpp b/core/jni/android_os_UEventObserver.cpp
index 2df74b0..eda5075 100644
--- a/core/jni/android_os_UEventObserver.cpp
+++ b/core/jni/android_os_UEventObserver.cpp
@@ -71,7 +71,11 @@
         }
         buffer[length] = '\0';
 
-        ALOGV("Received uevent message: %s", buffer);
+        IF_ALOGV() {
+            std::string message(buffer, length);
+            std::replace(message.begin(), message.end(), '\0', ' ');
+            ALOGV("Received uevent message: %s", message.c_str());
+        }
 
         if (isMatch(buffer, length)) {
             // Assume the message is ASCII.
diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp
index a2205eb..979c9e3 100644
--- a/core/jni/android_util_AssetManager.cpp
+++ b/core/jni/android_util_AssetManager.cpp
@@ -17,6 +17,9 @@
 #define ATRACE_TAG ATRACE_TAG_RESOURCES
 #define LOG_TAG "asset"
 
+#include "android_runtime/android_util_AssetManager.h"
+
+#include <errno.h>
 #include <inttypes.h>
 #include <linux/capability.h>
 #include <stdio.h>
@@ -31,7 +34,7 @@
 #include "android-base/logging.h"
 #include "android-base/properties.h"
 #include "android-base/stringprintf.h"
-#include "android_runtime/android_util_AssetManager.h"
+#include "android_content_res_ApkAssets.h"
 #include "android_runtime/AndroidRuntime.h"
 #include "android_util_Binder.h"
 #include "androidfw/Asset.h"
@@ -39,11 +42,9 @@
 #include "androidfw/AssetManager2.h"
 #include "androidfw/AttributeResolution.h"
 #include "androidfw/MutexGuard.h"
-#include <androidfw/ResourceTimer.h>
+#include "androidfw/ResourceTimer.h"
 #include "androidfw/ResourceTypes.h"
 #include "androidfw/ResourceUtils.h"
-
-#include "android_content_res_ApkAssets.h"
 #include "core_jni_helpers.h"
 #include "jni.h"
 #include "nativehelper/JNIPlatformHelp.h"
@@ -161,9 +162,30 @@
   return *AssetManagerForNdkAssetManager(reinterpret_cast<AAssetManager*>(ptr));
 }
 
+struct ScopedLockedAssetsOperation {
+  ScopedLockedAssetsOperation(Guarded<AssetManager2>& guarded_am)
+        : am_(guarded_am), op_(am_->StartOperation()) {}
+
+  AssetManager2& operator*() { return *am_; }
+
+  AssetManager2* operator->() { return am_.get(); }
+
+  AssetManager2* get() { return am_.get(); }
+
+  private:
+  DISALLOW_COPY_AND_ASSIGN(ScopedLockedAssetsOperation);
+
+  ScopedLock<AssetManager2> am_;
+  AssetManager2::ScopedOperation op_;
+};
+
+ScopedLockedAssetsOperation LockAndStartAssetManager(jlong ptr) {
+  return ScopedLockedAssetsOperation(AssetManagerFromLong(ptr));
+}
+
 static jobject NativeGetOverlayableMap(JNIEnv* env, jclass /*clazz*/, jlong ptr,
                                        jstring package_name) {
-  ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+  auto assetmanager = LockAndStartAssetManager(ptr);
   const ScopedUtfChars package_name_utf8(env, package_name);
   CHECK(package_name_utf8.c_str() != nullptr);
   const std::string std_package_name(package_name_utf8.c_str());
@@ -209,7 +231,7 @@
 
 static jstring NativeGetOverlayablesToString(JNIEnv* env, jclass /*clazz*/, jlong ptr,
                                              jstring package_name) {
-  ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+  auto assetmanager = LockAndStartAssetManager(ptr);
   const ScopedUtfChars package_name_utf8(env, package_name);
   CHECK(package_name_utf8.c_str() != nullptr);
   const std::string std_package_name(package_name_utf8.c_str());
@@ -296,7 +318,7 @@
   ATRACE_NAME("AssetManager::SetApkAssets");
 
   const jsize apk_assets_len = env->GetArrayLength(apk_assets_array);
-  std::vector<const ApkAssets*> apk_assets;
+  std::vector<AssetManager2::ApkAssetsPtr> apk_assets;
   apk_assets.reserve(apk_assets_len);
   for (jsize i = 0; i < apk_assets_len; i++) {
     jobject obj = env->GetObjectArrayElement(apk_assets_array, i);
@@ -310,12 +332,17 @@
     if (env->ExceptionCheck()) {
       return;
     }
-
+    if (!apk_assets_native_ptr) {
+      ALOGW("Got a closed ApkAssets instance at index %d for AssetManager %p", i, (void*)ptr);
+      std::string msg = StringPrintf("ApkAssets at index %d is closed, native pointer is null", i);
+      jniThrowException(env, "java/lang/IllegalArgumentException", msg.c_str());
+      return;
+    }
     auto scoped_assets = ScopedLock(ApkAssetsFromLong(apk_assets_native_ptr));
-    apk_assets.push_back(scoped_assets->get());
+    apk_assets.emplace_back(*scoped_assets);
   }
 
-  ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+  auto assetmanager = LockAndStartAssetManager(ptr);
   assetmanager->SetApkAssets(apk_assets, invalidate_caches);
 }
 
@@ -365,14 +392,14 @@
   configuration.screenLayout2 =
       static_cast<uint8_t>((screen_layout & kScreenLayoutRoundMask) >> kScreenLayoutRoundShift);
 
-  ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+  auto assetmanager = LockAndStartAssetManager(ptr);
   assetmanager->SetConfiguration(configuration);
 }
 
 static jobject NativeGetAssignedPackageIdentifiers(JNIEnv* env, jclass /*clazz*/, jlong ptr,
                                                    jboolean includeOverlays,
                                                    jboolean includeLoaders) {
-  ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+  auto assetmanager = LockAndStartAssetManager(ptr);
 
   jobject sparse_array =
         env->NewObject(gSparseArrayOffsets.classObject, gSparseArrayOffsets.constructor);
@@ -402,7 +429,7 @@
 }
 
 static jboolean ContainsAllocatedTable(JNIEnv* env, jclass /*clazz*/, jlong ptr) {
-  ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+  auto assetmanager = LockAndStartAssetManager(ptr);
   return assetmanager->ContainsAllocatedTable();
 }
 
@@ -413,7 +440,7 @@
     return nullptr;
   }
 
-  ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+  auto assetmanager = LockAndStartAssetManager(ptr);
   std::unique_ptr<AssetDir> asset_dir =
       assetmanager->OpenDir(path_utf8.c_str());
   if (asset_dir == nullptr) {
@@ -461,7 +488,7 @@
     return 0;
   }
 
-  ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+  auto assetmanager = LockAndStartAssetManager(ptr);
   std::unique_ptr<Asset> asset =
       assetmanager->Open(asset_path_utf8.c_str(), static_cast<Asset::AccessMode>(access_mode));
   if (!asset) {
@@ -481,7 +508,7 @@
 
   ATRACE_NAME(base::StringPrintf("AssetManager::OpenAssetFd(%s)", asset_path_utf8.c_str()).c_str());
 
-  ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+  auto assetmanager = LockAndStartAssetManager(ptr);
   std::unique_ptr<Asset> asset = assetmanager->Open(asset_path_utf8.c_str(), Asset::ACCESS_RANDOM);
   if (!asset) {
     jniThrowException(env, "java/io/FileNotFoundException", asset_path_utf8.c_str());
@@ -507,7 +534,7 @@
     return 0;
   }
 
-  ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+  auto assetmanager = LockAndStartAssetManager(ptr);
   std::unique_ptr<Asset> asset;
   if (cookie != kInvalidCookie) {
     asset = assetmanager->OpenNonAsset(asset_path_utf8.c_str(), cookie,
@@ -535,7 +562,7 @@
 
   ATRACE_NAME(base::StringPrintf("AssetManager::OpenNonAssetFd(%s)", asset_path_utf8.c_str()).c_str());
 
-  ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+  auto assetmanager = LockAndStartAssetManager(ptr);
   std::unique_ptr<Asset> asset;
   if (cookie != kInvalidCookie) {
     asset = assetmanager->OpenNonAsset(asset_path_utf8.c_str(), cookie, Asset::ACCESS_RANDOM);
@@ -561,7 +588,7 @@
 
   ATRACE_NAME(base::StringPrintf("AssetManager::OpenXmlAsset(%s)", asset_path_utf8.c_str()).c_str());
 
-  ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+  auto assetmanager = LockAndStartAssetManager(ptr);
   std::unique_ptr<Asset> asset;
   if (cookie != kInvalidCookie) {
     asset = assetmanager->OpenNonAsset(asset_path_utf8.c_str(), cookie, Asset::ACCESS_RANDOM);
@@ -609,7 +636,8 @@
   std::unique_ptr<Asset>
       asset(Asset::createFromFd(dup_fd.release(), nullptr, Asset::AccessMode::ACCESS_BUFFER));
 
-  ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+  auto assetmanager = LockAndStartAssetManager(ptr);
+
   ApkAssetsCookie cookie = JavaCookieToApkAssetsCookie(jcookie);
 
   const incfs::map_ptr<void> buffer = asset->getIncFsBuffer(true /* aligned */);
@@ -632,8 +660,9 @@
 static jint NativeGetResourceValue(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint resid,
                                    jshort density, jobject typed_value,
                                    jboolean resolve_references) {
-  ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+  auto assetmanager = LockAndStartAssetManager(ptr);
   ResourceTimer _timer(ResourceTimer::Counter::GetResourceValue);
+
   auto value = assetmanager->GetResource(static_cast<uint32_t>(resid), false /*may_be_bag*/,
                                          static_cast<uint16_t>(density));
   if (!value.has_value()) {
@@ -651,7 +680,8 @@
 
 static jint NativeGetResourceBagValue(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint resid,
                                       jint bag_entry_id, jobject typed_value) {
-  ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+  auto assetmanager = LockAndStartAssetManager(ptr);
+
   auto bag = assetmanager->GetBag(static_cast<uint32_t>(resid));
   if (!bag.has_value()) {
     return ApkAssetsCookieToJavaCookie(kInvalidCookie);
@@ -678,7 +708,8 @@
 }
 
 static jintArray NativeGetStyleAttributes(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint resid) {
-  ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+  auto assetmanager = LockAndStartAssetManager(ptr);
+
   auto bag_result = assetmanager->GetBag(static_cast<uint32_t>(resid));
   if (!bag_result.has_value()) {
     return nullptr;
@@ -699,7 +730,8 @@
 
 static jobjectArray NativeGetResourceStringArray(JNIEnv* env, jclass /*clazz*/, jlong ptr,
                                                  jint resid) {
-  ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+  auto assetmanager = LockAndStartAssetManager(ptr);
+
   auto bag_result = assetmanager->GetBag(static_cast<uint32_t>(resid));
   if (!bag_result.has_value()) {
     return nullptr;
@@ -720,31 +752,36 @@
     }
 
     if (attr_value.type == Res_value::TYPE_STRING) {
-      const ApkAssets* apk_assets = assetmanager->GetApkAssets()[attr_value.cookie];
-      const ResStringPool* pool = apk_assets->GetLoadedArsc()->GetStringPool();
+      const auto& apk_assets = assetmanager->GetApkAssets(attr_value.cookie);
+      if (apk_assets) {
+          const ResStringPool* pool = apk_assets->GetLoadedArsc()->GetStringPool();
 
-      jstring java_string;
-      if (auto str_utf8 = pool->string8At(attr_value.data); str_utf8.has_value()) {
-          java_string = env->NewStringUTF(str_utf8->data());
-      } else {
-          auto str_utf16 = pool->stringAt(attr_value.data);
-          if (!str_utf16.has_value()) {
+          jstring java_string;
+          if (auto str_utf8 = pool->string8At(attr_value.data); str_utf8.has_value()) {
+              java_string = env->NewStringUTF(str_utf8->data());
+          } else {
+              auto str_utf16 = pool->stringAt(attr_value.data);
+              if (!str_utf16.has_value()) {
+                  return nullptr;
+              }
+              java_string = env->NewString(reinterpret_cast<const jchar*>(str_utf16->data()),
+                                           str_utf16->size());
+          }
+
+          // Check for errors creating the strings (if malformed or no memory).
+          if (env->ExceptionCheck()) {
               return nullptr;
           }
-          java_string = env->NewString(reinterpret_cast<const jchar*>(str_utf16->data()),
-                                       str_utf16->size());
+
+          env->SetObjectArrayElement(array, i, java_string);
+
+          // If we have a large amount of string in our array, we might overflow the
+          // local reference table of the VM.
+          env->DeleteLocalRef(java_string);
+      } else {
+          ALOGW("NativeGetResourceStringArray: an expired assets object #%d / %d", i,
+                attr_value.cookie);
       }
-
-      // Check for errors creating the strings (if malformed or no memory).
-      if (env->ExceptionCheck()) {
-        return nullptr;
-      }
-
-      env->SetObjectArrayElement(array, i, java_string);
-
-      // If we have a large amount of string in our array, we might overflow the
-      // local reference table of the VM.
-      env->DeleteLocalRef(java_string);
     }
   }
   return array;
@@ -752,7 +789,8 @@
 
 static jintArray NativeGetResourceStringArrayInfo(JNIEnv* env, jclass /*clazz*/, jlong ptr,
                                                   jint resid) {
-  ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+  auto assetmanager = LockAndStartAssetManager(ptr);
+
   auto bag_result = assetmanager->GetBag(static_cast<uint32_t>(resid));
   if (!bag_result.has_value()) {
     return nullptr;
@@ -790,7 +828,8 @@
 }
 
 static jintArray NativeGetResourceIntArray(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint resid) {
-  ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+  auto assetmanager = LockAndStartAssetManager(ptr);
+
   auto bag_result = assetmanager->GetBag(static_cast<uint32_t>(resid));
   if (!bag_result.has_value()) {
     return nullptr;
@@ -825,21 +864,22 @@
 }
 
 static jint NativeGetResourceArraySize(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint resid) {
-    ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
-    auto bag = assetmanager->GetBag(static_cast<uint32_t>(resid));
-    if (!bag.has_value()) {
-      return -1;
-    }
+  auto assetmanager = LockAndStartAssetManager(ptr);
+  auto bag = assetmanager->GetBag(static_cast<uint32_t>(resid));
+  if (!bag.has_value()) {
+    return -1;
+  }
     return static_cast<jint>((*bag)->entry_count);
 }
 
 static jint NativeGetResourceArray(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint resid,
                                    jintArray out_data) {
-  ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
-  auto bag_result = assetmanager->GetBag(static_cast<uint32_t>(resid));
-  if (!bag_result.has_value()) {
+    auto assetmanager = LockAndStartAssetManager(ptr);
+
+    auto bag_result = assetmanager->GetBag(static_cast<uint32_t>(resid));
+    if (!bag_result.has_value()) {
     return -1;
-  }
+    }
 
   const jsize out_data_length = env->GetArrayLength(out_data);
   if (env->ExceptionCheck()) {
@@ -886,7 +926,7 @@
 }
 
 static jint NativeGetParentThemeIdentifier(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint resid) {
-  ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+  auto assetmanager = LockAndStartAssetManager(ptr);
   const auto parentThemeResId = assetmanager->GetParentThemeResourceId(resid);
   return parentThemeResId.value_or(0);
 }
@@ -913,7 +953,7 @@
     package = package_utf8.c_str();
   }
 
-  ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+  auto assetmanager = LockAndStartAssetManager(ptr);
   auto resid = assetmanager->GetResourceId(name_utf8.c_str(), type, package);
   if (!resid.has_value()) {
     return 0;
@@ -923,7 +963,7 @@
 }
 
 static jstring NativeGetResourceName(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint resid) {
-  ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+  auto assetmanager = LockAndStartAssetManager(ptr);
   auto name = assetmanager->GetResourceName(static_cast<uint32_t>(resid));
   if (!name.has_value()) {
     return nullptr;
@@ -934,7 +974,7 @@
 }
 
 static jstring NativeGetResourcePackageName(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint resid) {
-  ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+  auto assetmanager = LockAndStartAssetManager(ptr);
   auto name = assetmanager->GetResourceName(static_cast<uint32_t>(resid));
   if (!name.has_value()) {
     return nullptr;
@@ -947,7 +987,7 @@
 }
 
 static jstring NativeGetResourceTypeName(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint resid) {
-  ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+  auto assetmanager = LockAndStartAssetManager(ptr);
   auto name = assetmanager->GetResourceName(static_cast<uint32_t>(resid));
   if (!name.has_value()) {
     return nullptr;
@@ -962,7 +1002,7 @@
 }
 
 static jstring NativeGetResourceEntryName(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint resid) {
-  ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+  auto assetmanager = LockAndStartAssetManager(ptr);
   auto name = assetmanager->GetResourceName(static_cast<uint32_t>(resid));
   if (!name.has_value()) {
     return nullptr;
@@ -980,14 +1020,14 @@
                                                       jclass /*clazz*/,
                                                       jlong ptr,
                                                       jboolean enabled) {
-  ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+  auto assetmanager = LockAndStartAssetManager(ptr);
   assetmanager->SetResourceResolutionLoggingEnabled(enabled);
 }
 
 static jstring NativeGetLastResourceResolution(JNIEnv* env,
                                                jclass /*clazz*/,
                                                jlong ptr) {
-  ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+  auto assetmanager = LockAndStartAssetManager(ptr);
   std::string resolution = assetmanager->GetLastResourceResolution();
   if (resolution.empty()) {
     return nullptr;
@@ -998,7 +1038,7 @@
 
 static jobjectArray NativeGetLocales(JNIEnv* env, jclass /*class*/, jlong ptr,
                                      jboolean exclude_system) {
-  ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+  auto assetmanager = LockAndStartAssetManager(ptr);
   std::set<std::string> locales =
       assetmanager->GetResourceLocales(exclude_system, true /*merge_equivalent_languages*/);
 
@@ -1036,7 +1076,7 @@
 }
 
 static jobjectArray GetSizeAndUiModeConfigurations(JNIEnv* env, jlong ptr) {
-  ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+  auto assetmanager = LockAndStartAssetManager(ptr);
   auto configurations = assetmanager->GetResourceConfigurations(true /*exclude_system*/,
                                                                 false /*exclude_mipmap*/);
   if (!configurations.has_value()) {
@@ -1070,12 +1110,10 @@
   return GetSizeAndUiModeConfigurations(env, ptr);
 }
 
-static jintArray NativeAttributeResolutionStack(
-    JNIEnv* env, jclass /*clazz*/, jlong ptr,
-    jlong theme_ptr, jint xml_style_res,
-    jint def_style_attr, jint def_style_resid) {
-
-  ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+static jintArray NativeAttributeResolutionStack(JNIEnv* env, jclass /*clazz*/, jlong ptr,
+                                                jlong theme_ptr, jint xml_style_res,
+                                                jint def_style_attr, jint def_style_resid) {
+  auto assetmanager = LockAndStartAssetManager(ptr);
   Theme* theme = reinterpret_cast<Theme*>(theme_ptr);
   CHECK(theme->GetAssetManager() == &(*assetmanager));
   (void) assetmanager;
@@ -1088,29 +1126,36 @@
     }
   }
 
-  auto style_stack = assetmanager->GetBagResIdStack(xml_style_res);
-  auto def_style_stack = assetmanager->GetBagResIdStack(def_style_resid);
+  const auto maybe_style_stack = assetmanager->GetBagResIdStack(xml_style_res);
+  if (!maybe_style_stack.ok()) {
+    jniThrowIOException(env, EBADMSG);
+    return nullptr;
+  }
+  const auto& style_stack = *maybe_style_stack.value();
+  const auto maybe_def_style_stack = assetmanager->GetBagResIdStack(def_style_resid);
+  if (!maybe_def_style_stack.ok()) {
+    jniThrowIOException(env, EBADMSG);
+    return nullptr;
+  }
+  const auto& def_style_stack = *maybe_def_style_stack.value();
 
   jintArray array = env->NewIntArray(style_stack.size() + def_style_stack.size());
   if (env->ExceptionCheck()) {
     return nullptr;
   }
 
-  for (uint32_t i = 0; i < style_stack.size(); i++) {
-    jint attr_resid = style_stack[i];
-    env->SetIntArrayRegion(array, i, 1, &attr_resid);
-  }
-  for (uint32_t i = 0; i < def_style_stack.size(); i++) {
-    jint attr_resid = def_style_stack[i];
-    env->SetIntArrayRegion(array, style_stack.size() + i, 1, &attr_resid);
-  }
+  static_assert(sizeof(jint) == sizeof(decltype(style_stack.front())));
+  env->SetIntArrayRegion(array, 0, style_stack.size(),
+                         reinterpret_cast<const jint*>(style_stack.data()));
+  env->SetIntArrayRegion(array, style_stack.size(), def_style_stack.size(),
+                         reinterpret_cast<const jint*>(def_style_stack.data()));
   return array;
 }
 
 static void NativeApplyStyle(JNIEnv* env, jclass /*clazz*/, jlong ptr, jlong theme_ptr,
                              jint def_style_attr, jint def_style_resid, jlong xml_parser_ptr,
                              jintArray java_attrs, jlong out_values_ptr, jlong out_indices_ptr) {
-  ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+  auto assetmanager = LockAndStartAssetManager(ptr);
   Theme* theme = reinterpret_cast<Theme*>(theme_ptr);
   CHECK(theme->GetAssetManager() == &(*assetmanager));
   (void) assetmanager;
@@ -1185,7 +1230,7 @@
     }
   }
 
-  ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+  auto assetmanager = LockAndStartAssetManager(ptr);
   Theme* theme = reinterpret_cast<Theme*>(theme_ptr);
   CHECK(theme->GetAssetManager() == &(*assetmanager));
   (void) assetmanager;
@@ -1244,7 +1289,7 @@
     }
   }
 
-  ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+  auto assetmanager = LockAndStartAssetManager(ptr);
   ResourceTimer _timer(ResourceTimer::Counter::RetrieveAttributes);
   ResXMLParser* xml_parser = reinterpret_cast<ResXMLParser*>(xml_parser_ptr);
   auto result =
@@ -1262,7 +1307,7 @@
 }
 
 static jlong NativeThemeCreate(JNIEnv* /*env*/, jclass /*clazz*/, jlong ptr) {
-  ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+  auto assetmanager = LockAndStartAssetManager(ptr);
   return reinterpret_cast<jlong>(assetmanager->NewTheme().release());
 }
 
@@ -1277,7 +1322,7 @@
 static void NativeThemeApplyStyle(JNIEnv* env, jclass /*clazz*/, jlong ptr, jlong theme_ptr,
                                   jint resid, jboolean force) {
   // AssetManager is accessed via the theme, so grab an explicit lock here.
-  ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+  auto assetmanager = LockAndStartAssetManager(ptr);
   Theme* theme = reinterpret_cast<Theme*>(theme_ptr);
   CHECK(theme->GetAssetManager() == &(*assetmanager));
   (void) assetmanager;
@@ -1295,7 +1340,7 @@
                               jint style_count) {
   // Lock both the original asset manager of the theme and the new asset manager to be used for the
   // theme.
-  ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+  auto assetmanager = LockAndStartAssetManager(ptr);
 
   uint32_t* style_id_args = nullptr;
   if (style_ids != nullptr) {
@@ -1338,25 +1383,23 @@
   Theme* dst_theme = reinterpret_cast<Theme*>(dst_theme_ptr);
   Theme* src_theme = reinterpret_cast<Theme*>(src_theme_ptr);
 
-  ScopedLock<AssetManager2> src_assetmanager(AssetManagerFromLong(src_asset_manager_ptr));
+  auto src_assetmanager = LockAndStartAssetManager(src_asset_manager_ptr);
   CHECK(src_theme->GetAssetManager() == &(*src_assetmanager));
-  (void) src_assetmanager;
 
   if (dst_asset_manager_ptr != src_asset_manager_ptr) {
-    ScopedLock<AssetManager2> dst_assetmanager(AssetManagerFromLong(dst_asset_manager_ptr));
+    auto dst_assetmanager = LockAndStartAssetManager(dst_asset_manager_ptr);
     CHECK(dst_theme->GetAssetManager() == &(*dst_assetmanager));
-    (void) dst_assetmanager;
-
     dst_theme->SetTo(*src_theme);
   } else {
-      dst_theme->SetTo(*src_theme);
+    dst_theme->SetTo(*src_theme);
   }
 }
 
 static jint NativeThemeGetAttributeValue(JNIEnv* env, jclass /*clazz*/, jlong ptr, jlong theme_ptr,
                                          jint resid, jobject typed_value,
                                          jboolean resolve_references) {
-  ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+  auto assetmanager = LockAndStartAssetManager(ptr);
+
   Theme* theme = reinterpret_cast<Theme*>(theme_ptr);
   CHECK(theme->GetAssetManager() == &(*assetmanager));
   (void) assetmanager;
@@ -1379,7 +1422,7 @@
 
 static void NativeThemeDump(JNIEnv* /*env*/, jclass /*clazz*/, jlong ptr, jlong theme_ptr,
                             jint priority, jstring tag, jstring prefix) {
-  ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+  auto assetmanager = LockAndStartAssetManager(ptr);
   Theme* theme = reinterpret_cast<Theme*>(theme_ptr);
   CHECK(theme->GetAssetManager() == &(*assetmanager));
   (void) assetmanager;
diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp
index b24dc8a..c198797 100644
--- a/core/jni/android_util_Binder.cpp
+++ b/core/jni/android_util_Binder.cpp
@@ -974,7 +974,7 @@
     return IPCThreadState::self()->getCallingUid();
 }
 
-static jboolean android_os_Binder_isDirectlyHandlingTransaction() {
+static jboolean android_os_Binder_isDirectlyHandlingTransactionNative() {
     return getCurrentServingCall() == BinderCallType::BINDER;
 }
 
@@ -1082,7 +1082,8 @@
     // @CriticalNative
     { "getCallingUid", "()I", (void*)android_os_Binder_getCallingUid },
     // @CriticalNative
-    { "isDirectlyHandlingTransaction", "()Z", (void*)android_os_Binder_isDirectlyHandlingTransaction },
+    { "isDirectlyHandlingTransactionNative", "()Z",
+        (void*)android_os_Binder_isDirectlyHandlingTransactionNative },
     // @CriticalNative
     { "clearCallingIdentity", "()J", (void*)android_os_Binder_clearCallingIdentity },
     // @CriticalNative
@@ -1304,103 +1305,6 @@
     return alive ? JNI_TRUE : JNI_FALSE;
 }
 
-static int getprocname(pid_t pid, char *buf, size_t len) {
-    char filename[32];
-    FILE *f;
-
-    snprintf(filename, sizeof(filename), "/proc/%d/cmdline", pid);
-    f = fopen(filename, "re");
-    if (!f) {
-        *buf = '\0';
-        return 1;
-    }
-    if (!fgets(buf, len, f)) {
-        *buf = '\0';
-        fclose(f);
-        return 2;
-    }
-    fclose(f);
-    return 0;
-}
-
-static bool push_eventlog_string(char** pos, const char* end, const char* str) {
-    jint len = strlen(str);
-    int space_needed = 1 + sizeof(len) + len;
-    if (end - *pos < space_needed) {
-        ALOGW("not enough space for string. remain=%" PRIdPTR "; needed=%d",
-             end - *pos, space_needed);
-        return false;
-    }
-    **pos = EVENT_TYPE_STRING;
-    (*pos)++;
-    memcpy(*pos, &len, sizeof(len));
-    *pos += sizeof(len);
-    memcpy(*pos, str, len);
-    *pos += len;
-    return true;
-}
-
-static bool push_eventlog_int(char** pos, const char* end, jint val) {
-    int space_needed = 1 + sizeof(val);
-    if (end - *pos < space_needed) {
-        ALOGW("not enough space for int.  remain=%" PRIdPTR "; needed=%d",
-             end - *pos, space_needed);
-        return false;
-    }
-    **pos = EVENT_TYPE_INT;
-    (*pos)++;
-    memcpy(*pos, &val, sizeof(val));
-    *pos += sizeof(val);
-    return true;
-}
-
-// From frameworks/base/core/java/android/content/EventLogTags.logtags:
-
-static const bool kEnableBinderSample = false;
-
-#define LOGTAG_BINDER_OPERATION 52004
-
-static void conditionally_log_binder_call(int64_t start_millis,
-                                          IBinder* target, jint code) {
-    int duration_ms = static_cast<int>(uptimeMillis() - start_millis);
-
-    int sample_percent;
-    if (duration_ms >= 500) {
-        sample_percent = 100;
-    } else {
-        sample_percent = 100 * duration_ms / 500;
-        if (sample_percent == 0) {
-            return;
-        }
-        if (sample_percent < (random() % 100 + 1)) {
-            return;
-        }
-    }
-
-    char process_name[40];
-    getprocname(getpid(), process_name, sizeof(process_name));
-    String8 desc(target->getInterfaceDescriptor());
-
-    char buf[LOGGER_ENTRY_MAX_PAYLOAD];
-    buf[0] = EVENT_TYPE_LIST;
-    buf[1] = 5;
-    char* pos = &buf[2];
-    char* end = &buf[LOGGER_ENTRY_MAX_PAYLOAD - 1];  // leave room for final \n
-    if (!push_eventlog_string(&pos, end, desc.string())) return;
-    if (!push_eventlog_int(&pos, end, code)) return;
-    if (!push_eventlog_int(&pos, end, duration_ms)) return;
-    if (!push_eventlog_string(&pos, end, process_name)) return;
-    if (!push_eventlog_int(&pos, end, sample_percent)) return;
-    *(pos++) = '\n';   // conventional with EVENT_TYPE_LIST apparently.
-    android_bWriteLog(LOGTAG_BINDER_OPERATION, buf, pos - buf);
-}
-
-// We only measure binder call durations to potentially log them if
-// we're on the main thread.
-static bool should_time_binder_calls() {
-  return (getpid() == gettid());
-}
-
 static jboolean android_os_BinderProxy_transact(JNIEnv* env, jobject obj,
         jint code, jobject dataObj, jobject replyObj, jint flags) // throws RemoteException
 {
@@ -1427,29 +1331,10 @@
     ALOGV("Java code calling transact on %p in Java object %p with code %" PRId32 "\n",
             target, obj, code);
 
-
-    bool time_binder_calls;
-    int64_t start_millis;
-    if (kEnableBinderSample) {
-        // Only log the binder call duration for things on the Java-level main thread.
-        // But if we don't
-        time_binder_calls = should_time_binder_calls();
-
-        if (time_binder_calls) {
-            start_millis = uptimeMillis();
-        }
-    }
-
     //printf("Transact from Java code to %p sending: ", target); data->print();
     status_t err = target->transact(code, *data, reply, flags);
     //if (reply) printf("Transact from Java code to %p received: ", target); reply->print();
 
-    if (kEnableBinderSample) {
-        if (time_binder_calls) {
-            conditionally_log_binder_call(start_millis, target, code);
-        }
-    }
-
     if (err == NO_ERROR) {
         return JNI_TRUE;
     } else if (err == UNKNOWN_TRANSACTION) {
diff --git a/core/jni/android_view_InputEventReceiver.cpp b/core/jni/android_view_InputEventReceiver.cpp
index 0f41229..5c1d91f 100644
--- a/core/jni/android_view_InputEventReceiver.cpp
+++ b/core/jni/android_view_InputEventReceiver.cpp
@@ -278,7 +278,7 @@
 
     if (events & ALOOPER_EVENT_INPUT) {
         JNIEnv* env = AndroidRuntime::getJNIEnv();
-        status_t status = consumeEvents(env, false /*consumeBatches*/, -1, nullptr);
+        status_t status = consumeEvents(env, /*consumeBatches=*/false, -1, nullptr);
         mMessageQueue->raiseAndClearException(env, "handleReceiveCallback");
         return status == OK || status == NO_MEMORY ? KEEP_CALLBACK : REMOVE_CALLBACK;
     }
@@ -398,7 +398,7 @@
                     env->CallVoidMethod(receiverObj.get(),
                                         gInputEventReceiverClassInfo.onFocusEvent,
                                         jboolean(focusEvent->getHasFocus()));
-                    finishInputEvent(seq, true /* handled */);
+                    finishInputEvent(seq, /*handled=*/true);
                     continue;
                 }
                 case InputEventType::CAPTURE: {
@@ -411,7 +411,7 @@
                     env->CallVoidMethod(receiverObj.get(),
                                         gInputEventReceiverClassInfo.onPointerCaptureEvent,
                                         jboolean(captureEvent->getPointerCaptureEnabled()));
-                    finishInputEvent(seq, true /* handled */);
+                    finishInputEvent(seq, /*handled=*/true);
                     continue;
                 }
                 case InputEventType::DRAG: {
@@ -423,7 +423,7 @@
                     env->CallVoidMethod(receiverObj.get(), gInputEventReceiverClassInfo.onDragEvent,
                                         jboolean(dragEvent->isExiting()), dragEvent->getX(),
                                         dragEvent->getY());
-                    finishInputEvent(seq, true /* handled */);
+                    finishInputEvent(seq, /*handled=*/true);
                     continue;
                 }
                 case InputEventType::TOUCH_MODE: {
@@ -436,7 +436,7 @@
                     env->CallVoidMethod(receiverObj.get(),
                                         gInputEventReceiverClassInfo.onTouchModeChanged,
                                         jboolean(touchModeEvent->isInTouchMode()));
-                    finishInputEvent(seq, true /* handled */);
+                    finishInputEvent(seq, /*handled=*/true);
                     continue;
                 }
 
@@ -571,8 +571,8 @@
     sp<NativeInputEventReceiver> receiver =
             reinterpret_cast<NativeInputEventReceiver*>(receiverPtr);
     bool consumedBatch;
-    status_t status = receiver->consumeEvents(env, true /*consumeBatches*/, frameTimeNanos,
-            &consumedBatch);
+    status_t status =
+            receiver->consumeEvents(env, /*consumeBatches=*/true, frameTimeNanos, &consumedBatch);
     if (status && status != DEAD_OBJECT && !env->ExceptionCheck()) {
         std::string message =
                 android::base::StringPrintf("Failed to consume batched input event.  status=%s(%d)",
diff --git a/core/jni/android_view_InputQueue.cpp b/core/jni/android_view_InputQueue.cpp
index 21db37e..a0d081d 100644
--- a/core/jni/android_view_InputQueue.cpp
+++ b/core/jni/android_view_InputQueue.cpp
@@ -239,7 +239,7 @@
         return -1;
     }
     MotionEvent* event = queue->createMotionEvent();
-    event->copyFrom(originalEvent, true /* keepHistory */);
+    event->copyFrom(originalEvent, /*keepHistory=*/true);
     queue->enqueueEvent(event);
     return reinterpret_cast<jlong>(event);
 }
diff --git a/core/jni/android_view_KeyCharacterMap.cpp b/core/jni/android_view_KeyCharacterMap.cpp
index 8fa03cf..ddaeb5a 100644
--- a/core/jni/android_view_KeyCharacterMap.cpp
+++ b/core/jni/android_view_KeyCharacterMap.cpp
@@ -76,7 +76,7 @@
                           reinterpret_cast<jlong>(nativeMap));
 }
 
-static jobject nativeObtainEmptyKeyCharacterMap(JNIEnv* env, jobject /* clazz */, jint deviceId) {
+static jobject nativeObtainEmptyKeyCharacterMap(JNIEnv* env, /*clazz=*/jobject, jint deviceId) {
     return android_view_KeyCharacterMap_create(env, deviceId, nullptr);
 }
 
@@ -202,7 +202,7 @@
         jcharArray charsArray) {
     NativeKeyCharacterMap* map = reinterpret_cast<NativeKeyCharacterMap*>(ptr);
     if (!map || !map->getMap()) {
-        return env->NewObjectArray(0 /* size */, gKeyEventClassInfo.clazz, NULL);
+        return env->NewObjectArray(/*size=*/0, gKeyEventClassInfo.clazz, NULL);
     }
     jchar* chars = env->GetCharArrayElements(charsArray, NULL);
     if (!chars) {
diff --git a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
index 2c81987..da308b2 100644
--- a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
+++ b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
@@ -133,6 +133,7 @@
     void** args = reinterpret_cast<void**>(arg);
     jstring* javaNativeLibPath = (jstring*) args[0];
     jboolean extractNativeLibs = *(jboolean*) args[1];
+    jboolean debuggable = *(jboolean*) args[2];
 
     ScopedUtfChars nativeLibPath(env, *javaNativeLibPath);
 
@@ -148,7 +149,12 @@
         return INSTALL_FAILED_INVALID_APK;
     }
 
-    if (!extractNativeLibs) {
+    // Always extract wrap.sh for debuggable, even if extractNativeLibs=false. This makes it
+    // easier to use wrap.sh because it only works when it is extracted, see
+    // frameworks/base/services/core/java/com/android/server/am/ProcessList.java.
+    bool forceExtractCurrentFile = debuggable && strcmp(fileName, "wrap.sh") == 0;
+
+    if (!extractNativeLibs && !forceExtractCurrentFile) {
         // check if library is uncompressed and page-aligned
         if (method != ZipFileRO::kCompressStored) {
             ALOGE("Library '%s' is compressed - will not be able to open it directly from apk.\n",
@@ -274,7 +280,7 @@
  *
  * - The entry is under the lib/ directory.
  * - The entry name ends with ".so" and the entry name starts with "lib",
- *   an exception is made for entries whose name is "gdbserver".
+ *   an exception is made for debuggable apps.
  * - The entry filename is "safe" (as determined by isFilenameSafe).
  *
  */
@@ -286,21 +292,31 @@
     }
 
 public:
-    static NativeLibrariesIterator* create(ZipFileRO* zipFile, bool debuggable) {
-        void* cookie = nullptr;
+    static base::expected<std::unique_ptr<NativeLibrariesIterator>, int32_t> create(
+            ZipFileRO* zipFile, bool debuggable) {
         // Do not specify a suffix to find both .so files and gdbserver.
-        if (!zipFile->startIteration(&cookie, APK_LIB.data(), nullptr /* suffix */)) {
-            return nullptr;
+        auto result = zipFile->startIterationOrError(APK_LIB.data(), nullptr /* suffix */);
+        if (!result.ok()) {
+            return base::unexpected(result.error());
         }
 
-        return new NativeLibrariesIterator(zipFile, debuggable, cookie);
+        return std::unique_ptr<NativeLibrariesIterator>(
+                new NativeLibrariesIterator(zipFile, debuggable, result.value()));
     }
 
-    ZipEntryRO next() {
-        ZipEntryRO next = nullptr;
-        while ((next = mZipFile->nextEntry(mCookie)) != nullptr) {
+    base::expected<ZipEntryRO, int32_t> next() {
+        ZipEntryRO nextEntry;
+        while (true) {
+            auto next = mZipFile->nextEntryOrError(mCookie);
+            if (!next.ok()) {
+                return base::unexpected(next.error());
+            }
+            nextEntry = next.value();
+            if (nextEntry == nullptr) {
+                break;
+            }
             // Make sure this entry has a filename.
-            if (mZipFile->getEntryFileName(next, fileName, sizeof(fileName))) {
+            if (mZipFile->getEntryFileName(nextEntry, fileName, sizeof(fileName))) {
                 continue;
             }
 
@@ -311,7 +327,7 @@
             }
         }
 
-        return next;
+        return nextEntry;
     }
 
     inline const char* currentEntry() const {
@@ -342,19 +358,28 @@
         return INSTALL_FAILED_INVALID_APK;
     }
 
-    std::unique_ptr<NativeLibrariesIterator> it(
-            NativeLibrariesIterator::create(zipFile, debuggable));
-    if (it.get() == nullptr) {
+    auto result = NativeLibrariesIterator::create(zipFile, debuggable);
+    if (!result.ok()) {
         return INSTALL_FAILED_INVALID_APK;
     }
+    std::unique_ptr<NativeLibrariesIterator> it(std::move(result.value()));
 
     const ScopedUtfChars cpuAbi(env, javaCpuAbi);
     if (cpuAbi.c_str() == nullptr) {
         // This would've thrown, so this return code isn't observable by Java.
         return INSTALL_FAILED_INVALID_APK;
     }
-    ZipEntryRO entry = nullptr;
-    while ((entry = it->next()) != nullptr) {
+
+    while (true) {
+        auto next = it->next();
+        if (!next.ok()) {
+            return INSTALL_FAILED_INVALID_APK;
+        }
+        auto entry = next.value();
+        if (entry == nullptr) {
+            break;
+        }
+
         const char* fileName = it->currentEntry();
         const char* lastSlash = it->lastSlash();
 
@@ -382,11 +407,11 @@
         return INSTALL_FAILED_INVALID_APK;
     }
 
-    std::unique_ptr<NativeLibrariesIterator> it(
-            NativeLibrariesIterator::create(zipFile, debuggable));
-    if (it.get() == nullptr) {
+    auto result = NativeLibrariesIterator::create(zipFile, debuggable);
+    if (!result.ok()) {
         return INSTALL_FAILED_INVALID_APK;
     }
+    std::unique_ptr<NativeLibrariesIterator> it(std::move(result.value()));
 
     const int numAbis = env->GetArrayLength(supportedAbisArray);
 
@@ -396,9 +421,17 @@
         supportedAbis.emplace_back(env, (jstring)env->GetObjectArrayElement(supportedAbisArray, i));
     }
 
-    ZipEntryRO entry = nullptr;
     int status = NO_NATIVE_LIBRARIES;
-    while ((entry = it->next()) != nullptr) {
+    while (true) {
+        auto next = it->next();
+        if (!next.ok()) {
+            return INSTALL_FAILED_INVALID_APK;
+        }
+        auto entry = next.value();
+        if (entry == nullptr) {
+            break;
+        }
+
         // We're currently in the lib/ directory of the APK, so it does have some native
         // code. We should return INSTALL_FAILED_NO_MATCHING_ABIS if none of the
         // libraries match.
@@ -431,7 +464,7 @@
         jlong apkHandle, jstring javaNativeLibPath, jstring javaCpuAbi,
         jboolean extractNativeLibs, jboolean debuggable)
 {
-    void* args[] = { &javaNativeLibPath, &extractNativeLibs };
+    void* args[] = { &javaNativeLibPath, &extractNativeLibs, &debuggable };
     return (jint) iterateOverNativeFiles(env, apkHandle, javaCpuAbi, debuggable,
             copyFileIfChanged, reinterpret_cast<void*>(args));
 }
diff --git a/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp b/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp
index a95b6e3..76f5c10 100644
--- a/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp
+++ b/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp
@@ -127,16 +127,17 @@
     }
 }
 
-static void throwReadRE(JNIEnv *env, binder_status_t status) {
+static void throwReadException(JNIEnv *env, binder_status_t status) {
     ALOGE("Could not read LongArrayMultiStateCounter from Parcel, status = %d", status);
-    jniThrowRuntimeException(env, "Could not read LongArrayMultiStateCounter from Parcel");
+    jniThrowException(env, "android.os.BadParcelableException",
+                      "Could not read LongArrayMultiStateCounter from Parcel");
 }
 
 #define THROW_AND_RETURN_ON_READ_ERROR(expr) \
     {                                        \
         binder_status_t status = expr;       \
         if (status != STATUS_OK) {           \
-            throwReadRE(env, status);        \
+            throwReadException(env, status); \
             return 0L;                       \
         }                                    \
     }
@@ -147,6 +148,11 @@
     int32_t stateCount;
     THROW_AND_RETURN_ON_READ_ERROR(AParcel_readInt32(parcel.get(), &stateCount));
 
+    if (stateCount < 0 || stateCount > 0xEFFF) {
+        throwReadException(env, STATUS_INVALID_OPERATION);
+        return 0L;
+    }
+
     int32_t arrayLength;
     THROW_AND_RETURN_ON_READ_ERROR(AParcel_readInt32(parcel.get(), &arrayLength));
 
diff --git a/core/jni/com_android_internal_os_LongMultiStateCounter.cpp b/core/jni/com_android_internal_os_LongMultiStateCounter.cpp
index 1712b3a8..ddf7a67 100644
--- a/core/jni/com_android_internal_os_LongMultiStateCounter.cpp
+++ b/core/jni/com_android_internal_os_LongMultiStateCounter.cpp
@@ -131,16 +131,17 @@
     }
 }
 
-static void throwReadRE(JNIEnv *env, binder_status_t status) {
+static void throwReadException(JNIEnv *env, binder_status_t status) {
     ALOGE("Could not read LongMultiStateCounter from Parcel, status = %d", status);
-    jniThrowRuntimeException(env, "Could not read LongMultiStateCounter from Parcel");
+    jniThrowException(env, "android.os.BadParcelableException",
+                      "Could not read LongMultiStateCounter from Parcel");
 }
 
 #define THROW_AND_RETURN_ON_READ_ERROR(expr) \
     {                                        \
         binder_status_t status = expr;       \
         if (status != STATUS_OK) {           \
-            throwReadRE(env, status);        \
+            throwReadException(env, status); \
             return 0L;                       \
         }                                    \
     }
@@ -151,6 +152,11 @@
     int32_t stateCount;
     THROW_AND_RETURN_ON_READ_ERROR(AParcel_readInt32(parcel.get(), &stateCount));
 
+    if (stateCount < 0 || stateCount > 0xEFFF) {
+        throwReadException(env, STATUS_INVALID_OPERATION);
+        return 0L;
+    }
+
     auto counter = std::make_unique<battery::LongMultiStateCounter>(stateCount, 0);
 
     for (battery::state_t state = 0; state < stateCount; state++) {
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index ce806a0..c368fa8 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -356,6 +356,7 @@
     GWP_ASAN_LEVEL_DEFAULT = 3 << 21,
     NATIVE_HEAP_ZERO_INIT_ENABLED = 1 << 23,
     PROFILEABLE = 1 << 24,
+    DEBUG_ENABLE_PTRACE = 1 << 25,
 };
 
 enum UnsolicitedZygoteMessageTypes : uint32_t {
@@ -1887,8 +1888,10 @@
     }
 
     // Set process properties to enable debugging if required.
-    if ((runtime_flags & RuntimeFlags::DEBUG_ENABLE_JDWP) != 0) {
+    if ((runtime_flags & RuntimeFlags::DEBUG_ENABLE_PTRACE) != 0) {
         EnableDebugger();
+        // Don't pass unknown flag to the ART runtime.
+        runtime_flags &= ~RuntimeFlags::DEBUG_ENABLE_PTRACE;
     }
     if ((runtime_flags & RuntimeFlags::PROFILE_FROM_SHELL) != 0) {
         // simpleperf needs the process to be dumpable to profile it.
diff --git a/core/jni/com_android_internal_os_ZygoteInit.cpp b/core/jni/com_android_internal_os_ZygoteInit.cpp
index a5152b6..56c01ba2 100644
--- a/core/jni/com_android_internal_os_ZygoteInit.cpp
+++ b/core/jni/com_android_internal_os_ZygoteInit.cpp
@@ -23,43 +23,13 @@
 
 namespace {
 
-// Shadow call stack (SCS) is a security mitigation that uses a separate stack
-// (the SCS) for return addresses. In versions of Android newer than P, the
-// compiler cooperates with the system to ensure that the SCS address is always
-// stored in register x18, as long as the app was compiled with a new enough
-// compiler and does not use features that rely on SP-HALs (this restriction is
-// because the SP-HALs might not preserve x18 due to potentially having been
-// compiled with an old compiler as a consequence of Treble; it generally means
-// that the app must be a system app without a UI). This struct is used to
-// temporarily store the address on the stack while preloading the SP-HALs, so
-// that such apps can use the same zygote as everything else.
-struct ScopedSCSExit {
-#ifdef __aarch64__
-    void* scs;
-
-    ScopedSCSExit() {
-        __asm__ __volatile__("str x18, [%0]" ::"r"(&scs));
-    }
-
-    ~ScopedSCSExit() {
-        __asm__ __volatile__("ldr x18, [%0]; str xzr, [%0]" ::"r"(&scs));
-    }
-#else
-    // Silence unused variable warnings in non-SCS builds.
-    ScopedSCSExit() {}
-    ~ScopedSCSExit() {}
-#endif
-};
-
 void android_internal_os_ZygoteInit_nativePreloadAppProcessHALs(JNIEnv* env, jclass) {
-    ScopedSCSExit x;
     android::GraphicBufferMapper::preloadHal();
     // Add preloading here for other HALs that are (a) always passthrough, and
     // (b) loaded by most app processes.
 }
 
 void android_internal_os_ZygoteInit_nativePreloadGraphicsDriver(JNIEnv* env, jclass) {
-    ScopedSCSExit x;
     zygote_preload_graphics();
 }
 
diff --git a/core/jni/com_android_internal_security_VerityUtils.cpp b/core/jni/com_android_internal_security_VerityUtils.cpp
index 4a9e2d4..d7449e0 100644
--- a/core/jni/com_android_internal_security_VerityUtils.cpp
+++ b/core/jni/com_android_internal_security_VerityUtils.cpp
@@ -70,7 +70,11 @@
 int statxForFsverity(JNIEnv *env, jobject /* clazz */, jstring filePath) {
     ScopedUtfChars path(env, filePath);
 
-    // Call statx and check STATX_ATTR_VERITY.
+    // There are two ways to check whether a file has fs-verity enabled: statx() and FS_IOC_GETFLAGS
+    // (See https://www.kernel.org/doc/html/latest/filesystems/fsverity.html#statx and
+    // https://www.kernel.org/doc/html/latest/filesystems/fsverity.html#fs-ioc-getflags.)
+    // We try statx() first, since it doesn't require opening the file.
+
     struct statx out = {};
     if (statx(AT_FDCWD, path.c_str(), 0 /* flags */, STATX_ALL, &out) != 0) {
         return -errno;
@@ -80,8 +84,12 @@
         return (out.stx_attributes & STATX_ATTR_VERITY) != 0;
     }
 
-    // STATX_ATTR_VERITY is not supported for the file path.
-    // In this case, call ioctl(FS_IOC_GETFLAGS) and check FS_VERITY_FL.
+    // The filesystem doesn't support STATX_ATTR_VERITY.  This normally means that it doesn't
+    // support fs-verity, in which case we should simply return 0.  Unfortunately, virtio-fs is an
+    // exception, since it doesn't support STATX_ATTR_VERITY but does support querying FS_VERITY_FL
+    // via FS_IOC_GETFLAGS.  So we have to fall back to FS_IOC_GETFLAGS.  Note: despite being an
+    // ioctl, FS_IOC_GETFLAGS doesn't require the "ioctl" SELinux permission but rather "getattr".
+
     ::android::base::unique_fd rfd(open(path.c_str(), O_RDONLY | O_CLOEXEC));
     if (rfd.get() < 0) {
         ALOGE("open failed at %s", path.c_str());
@@ -90,6 +98,11 @@
 
     unsigned int flags;
     if (ioctl(rfd.get(), FS_IOC_GETFLAGS, &flags) < 0) {
+        if (errno == ENOTTY) {
+            // If the filesystem supports neither STATX_ATTR_VERITY nor FS_IOC_GETFLAGS, then assume
+            // that it doesn't support fs-verity.
+            return 0;
+        }
         ALOGE("ioctl(FS_IOC_GETFLAGS) failed at %s", path.c_str());
         return -errno;
     }
diff --git a/core/jni/fd_utils.cpp b/core/jni/fd_utils.cpp
index 5c71f69..8e4addd 100644
--- a/core/jni/fd_utils.cpp
+++ b/core/jni/fd_utils.cpp
@@ -17,6 +17,7 @@
 #include "fd_utils.h"
 
 #include <algorithm>
+#include <utility>
 
 #include <fcntl.h>
 #include <grp.h>
@@ -174,7 +175,7 @@
 class FileDescriptorInfo {
  public:
   // Create a FileDescriptorInfo for a given file descriptor.
-  static FileDescriptorInfo* CreateFromFd(int fd, fail_fn_t fail_fn);
+  static std::unique_ptr<FileDescriptorInfo> CreateFromFd(int fd, fail_fn_t fail_fn);
 
   // Checks whether the file descriptor associated with this object refers to
   // the same description.
@@ -213,7 +214,7 @@
   DISALLOW_COPY_AND_ASSIGN(FileDescriptorInfo);
 };
 
-FileDescriptorInfo* FileDescriptorInfo::CreateFromFd(int fd, fail_fn_t fail_fn) {
+std::unique_ptr<FileDescriptorInfo> FileDescriptorInfo::CreateFromFd(int fd, fail_fn_t fail_fn) {
   struct stat f_stat;
   // This should never happen; the zygote should always have the right set
   // of permissions required to stat all its open files.
@@ -234,7 +235,7 @@
                                             socket_name.c_str(), fd));
     }
 
-    return new FileDescriptorInfo(fd);
+    return std::unique_ptr<FileDescriptorInfo>(new FileDescriptorInfo(fd));
   }
 
   // We only handle allowlisted regular files and character devices. Allowlisted
@@ -315,7 +316,8 @@
   int open_flags = fs_flags & (kOpenFlags);
   fs_flags = fs_flags & (~(kOpenFlags));
 
-  return new FileDescriptorInfo(f_stat, file_path, fd, open_flags, fd_flags, fs_flags, offset);
+  return std::unique_ptr<FileDescriptorInfo>(
+    new FileDescriptorInfo(f_stat, file_path, fd, open_flags, fd_flags, fs_flags, offset));
 }
 
 bool FileDescriptorInfo::RefersToSameFile() const {
@@ -482,11 +484,11 @@
 FileDescriptorTable* FileDescriptorTable::Create(const std::vector<int>& fds_to_ignore,
                                                  fail_fn_t fail_fn) {
   std::unique_ptr<std::set<int>> open_fds = GetOpenFdsIgnoring(fds_to_ignore, fail_fn);
-  std::unordered_map<int, FileDescriptorInfo*> open_fd_map;
+  std::unordered_map<int, std::unique_ptr<FileDescriptorInfo>> open_fd_map;
   for (auto fd : *open_fds) {
     open_fd_map[fd] = FileDescriptorInfo::CreateFromFd(fd, fail_fn);
   }
-  return new FileDescriptorTable(open_fd_map);
+  return new FileDescriptorTable(std::move(open_fd_map));
 }
 
 static std::unique_ptr<std::set<int>> GetOpenFdsIgnoring(const std::vector<int>& fds_to_ignore,
@@ -535,9 +537,9 @@
 
 // Reopens all file descriptors that are contained in the table.
 void FileDescriptorTable::ReopenOrDetach(fail_fn_t fail_fn) {
-  std::unordered_map<int, FileDescriptorInfo*>::const_iterator it;
+  std::unordered_map<int, std::unique_ptr<FileDescriptorInfo>>::const_iterator it;
   for (it = open_fd_map_.begin(); it != open_fd_map_.end(); ++it) {
-    const FileDescriptorInfo* info = it->second;
+    const FileDescriptorInfo* info = it->second.get();
     if (info == nullptr) {
       return;
     } else {
@@ -547,15 +549,11 @@
 }
 
 FileDescriptorTable::FileDescriptorTable(
-    const std::unordered_map<int, FileDescriptorInfo*>& map)
-    : open_fd_map_(map) {
+  std::unordered_map<int, std::unique_ptr<FileDescriptorInfo>> map)
+  : open_fd_map_(std::move(map)) {
 }
 
-FileDescriptorTable::~FileDescriptorTable() {
-    for (auto& it : open_fd_map_) {
-        delete it.second;
-    }
-}
+FileDescriptorTable::~FileDescriptorTable() {}
 
 void FileDescriptorTable::RestatInternal(std::set<int>& open_fds, fail_fn_t fail_fn) {
   // ART creates a file through memfd for optimization purposes. We make sure
@@ -569,7 +567,7 @@
   // (b) they refer to the same file.
   //
   // We'll only store the last error message.
-  std::unordered_map<int, FileDescriptorInfo*>::iterator it = open_fd_map_.begin();
+  std::unordered_map<int, std::unique_ptr<FileDescriptorInfo>>::iterator it = open_fd_map_.begin();
   while (it != open_fd_map_.end()) {
     std::set<int>::const_iterator element = open_fds.find(it->first);
     if (element == open_fds.end()) {
@@ -580,7 +578,6 @@
       // TODO(narayan): This will be an error in a future android release.
       // error = true;
       // ALOGW("Zygote closed file descriptor %d.", it->first);
-      delete it->second;
       it = open_fd_map_.erase(it);
     } else {
       // The entry from the file descriptor table is still open. Restat
@@ -588,7 +585,6 @@
       if (!it->second->RefersToSameFile()) {
         // The file descriptor refers to a different description. We must
         // update our entry in the table.
-        delete it->second;
         it->second = FileDescriptorInfo::CreateFromFd(*element, fail_fn);
       } else {
         // It's the same file. Nothing to do here. Move on to the next open
diff --git a/core/jni/fd_utils.h b/core/jni/fd_utils.h
index a28ebf1..ac2d2a4 100644
--- a/core/jni/fd_utils.h
+++ b/core/jni/fd_utils.h
@@ -17,6 +17,7 @@
 #ifndef FRAMEWORKS_BASE_CORE_JNI_FD_UTILS_H_
 #define FRAMEWORKS_BASE_CORE_JNI_FD_UTILS_H_
 
+#include <memory>
 #include <set>
 #include <string>
 #include <unordered_map>
@@ -98,12 +99,12 @@
   void ReopenOrDetach(fail_fn_t fail_fn);
 
  private:
-  explicit FileDescriptorTable(const std::unordered_map<int, FileDescriptorInfo*>& map);
+  explicit FileDescriptorTable(std::unordered_map<int, std::unique_ptr<FileDescriptorInfo>> map);
 
   void RestatInternal(std::set<int>& open_fds, fail_fn_t fail_fn);
 
   // Invariant: All values in this unordered_map are non-NULL.
-  std::unordered_map<int, FileDescriptorInfo*> open_fd_map_;
+  std::unordered_map<int, std::unique_ptr<FileDescriptorInfo>> open_fd_map_;
 
   DISALLOW_COPY_AND_ASSIGN(FileDescriptorTable);
 };
diff --git a/core/proto/android/input/OWNERS b/core/proto/android/input/OWNERS
new file mode 100644
index 0000000..4c20c4d
--- /dev/null
+++ b/core/proto/android/input/OWNERS
@@ -0,0 +1 @@
+include /INPUT_OWNERS
diff --git a/core/proto/android/input/keyboard_configured.proto b/core/proto/android/input/keyboard_configured.proto
new file mode 100644
index 0000000..0b4bf8e
--- /dev/null
+++ b/core/proto/android/input/keyboard_configured.proto
@@ -0,0 +1,54 @@
+/*
+ * 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.
+ */
+
+syntax = "proto2";
+
+package com.android.internal.os;
+
+option java_outer_classname = "KeyboardConfiguredProto";
+
+/**
+ * RepeatedKeyboardLayout proto from input_extension_atoms.proto,
+ * duplicated here so that it's accessible in the build.
+ * Must be kept in sync with the version in input_extension_atoms.proto.
+ */
+
+// Message containing the repeated field for KeyboardLayoutConfig
+message RepeatedKeyboardLayoutConfig {
+  repeated KeyboardLayoutConfig keyboard_layout_config = 1;
+}
+
+// Keyboard layout configured when the device is connected
+// used in KeyboardConfigured atom
+message KeyboardLayoutConfig {
+  // Keyboard configuration details
+  // Layout type mappings found at:
+  // frameworks/base/core/res/res/values/attrs.xml
+  optional int32 keyboard_layout_type = 1;
+  // PK language language tag (e.g. en-US, ru-Cyrl, etc). This will follow
+  // BCP-47 language tag standards.
+  optional string keyboard_language_tag = 2;
+  // Selected keyboard layout name (e.g. English(US), English(Dvorak), etc.)
+  optional string keyboard_layout_name = 3;
+  // Criteria for layout selection (such as user, device, virtual keyboard based)
+  // IntDef annotation at:
+  // services/core/java/com/android/server/input/KeyboardMetricsCollector.java
+  optional int32 layout_selection_criteria = 4;
+  // Keyboard layout type provided by IME
+  optional int32 ime_layout_type = 5;
+  // Language tag provided by IME (e.g. en-US, ru-Cyrl etc.)
+  optional string ime_language_tag = 6;
+}
diff --git a/core/proto/android/os/system_properties.proto b/core/proto/android/os/system_properties.proto
index 10f07ac..5a3539a 100644
--- a/core/proto/android/os/system_properties.proto
+++ b/core/proto/android/os/system_properties.proto
@@ -172,7 +172,7 @@
         optional Status tombstoned = 29;
         optional Status ueventd = 30;
         optional Status update_engine = 31;
-        optional Status update_verifier_nonencrypted = 32;
+        optional Status update_verifier = 32;
         optional Status virtual_touchpad = 33;
         optional Status vndservicemanager = 34;
         optional Status vold = 35;
@@ -558,3 +558,4 @@
 
     // Next Tag: 32
 }
+
diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto
index a5d287c..d7969cf 100644
--- a/core/proto/android/providers/settings/secure.proto
+++ b/core/proto/android/providers/settings/secure.proto
@@ -374,6 +374,8 @@
     optional SettingProto lock_to_app_exit_locked = 33 [ (android.privacy).dest = DEST_AUTOMATIC ];
     optional SettingProto lockdown_in_power_menu = 34 [ (android.privacy).dest = DEST_AUTOMATIC ];
     optional SettingProto long_press_timeout = 35 [ (android.privacy).dest = DEST_AUTOMATIC ];
+    optional SettingProto key_press_timeout_ms = 96 [ (android.privacy).dest = DEST_AUTOMATIC ];
+    optional SettingProto key_press_delay_ms = 97 [ (android.privacy).dest = DEST_AUTOMATIC ];
 
     message ManagedProfile {
         option (android.msg_privacy).dest = DEST_EXPLICIT;
@@ -705,5 +707,5 @@
 
     // Please insert fields in alphabetical order and group them into messages
     // if possible (to avoid reaching the method limit).
-    // Next tag = 96;
+    // Next tag = 98;
 }
diff --git a/core/proto/android/server/vibrator/vibratormanagerservice.proto b/core/proto/android/server/vibrator/vibratormanagerservice.proto
index c211a5e..db99e5b 100644
--- a/core/proto/android/server/vibrator/vibratormanagerservice.proto
+++ b/core/proto/android/server/vibrator/vibratormanagerservice.proto
@@ -91,7 +91,7 @@
     option (.android.msg_privacy).dest = DEST_AUTOMATIC;
     optional int64 start_time = 1;
     optional int64 end_time = 2;
-    optional CombinedVibrationEffectProto effect = 3;
+    optional CombinedVibrationEffectProto played_effect = 3;
     optional CombinedVibrationEffectProto original_effect = 4;
     optional VibrationAttributesProto attributes = 5;
     optional int64 duration_ms = 7;
@@ -128,6 +128,7 @@
         IGNORED_FOR_SETTINGS = 24;
         IGNORED_SUPERSEDED = 25;
         IGNORED_FROM_VIRTUAL_DEVICE = 26;
+        IGNORED_ON_WIRELESS_CHARGER = 27;
     }
 }
 
diff --git a/core/proto/android/service/package.proto b/core/proto/android/service/package.proto
index 0fe2a6b..eb14db0 100644
--- a/core/proto/android/service/package.proto
+++ b/core/proto/android/service/package.proto
@@ -101,6 +101,30 @@
             COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED = 4;
         }
 
+        // Information about the state of an archived app.
+        // All fields are gathered at the time of archival.
+        message ArchiveState {
+            option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+            message ArchiveActivityInfo {
+                // Corresponds to android:label in the app's locale at the time of archival.
+                optional string title = 1;
+
+                // Icon of the archived app.
+                optional string icon_bitmap_path = 2;
+
+                // Only set if the app defined a monochrome icon.
+                optional string monochrome_icon_bitmap_path = 3;
+            }
+
+            /** Information about main activities. */
+            repeated ArchiveActivityInfo activity_infos = 1;
+
+            // Corresponds to android:label of the installer responsible for the unarchival of the
+            // app. Stored in the installer's locale at the time of archival.
+            optional string installer_title = 2;
+        }
+
         optional int32 id = 1;
         optional InstallType install_type = 2;
         // Is the app restricted by owner / admin
@@ -114,6 +138,7 @@
         optional int32 distraction_flags = 10;
         // UTC timestamp of first install for the user
         optional int32 first_install_time_ms = 11;
+        optional ArchiveState archive_state = 12;
     }
 
     message InstallSourceProto {
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 2f9f6ae..baa47da 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2373,6 +2373,8 @@
          <p>Protection level: normal
     -->
     <permission android:name="android.permission.NFC_TRANSACTION_EVENT"
+        android:description="@string/permdesc_nfcTransactionEvent"
+        android:label="@string/permlab_nfcTransactionEvent"
       android:protectionLevel="normal" />
 
     <!-- Allows applications to receive NFC preferred payment service information.
@@ -5419,7 +5421,7 @@
     <permission android:name="android.permission.INSTALL_DPC_PACKAGES"
                 android:protectionLevel="signature|role" />
 
-    <!-- Allows an application to read resolved paths to the APKs (Base and any splits)
+    <!-- @SystemApi Allows an application to read resolved paths to the APKs (Base and any splits)
          of a session based install.
          <p>Not for use by third-party applications.
          @hide
@@ -5518,6 +5520,12 @@
     <permission android:name="android.permission.GRANT_RUNTIME_PERMISSIONS"
         android:protectionLevel="signature|installer|verifier" />
 
+    <!-- @SystemApi Allows an application to launch the settings page which manages various
+         permissions.
+         @hide -->
+    <permission android:name="android.permission.LAUNCH_PERMISSION_SETTINGS"
+                android:protectionLevel="signature|privileged" />
+
     <!-- @SystemApi Allows an app that has this permission and the permissions to install packages
          to request certain runtime permissions to be granted at installation.
          @hide -->
@@ -6094,7 +6102,7 @@
     <!-- @hide @SystemApi Allows an application to observe usage time of apps. The app can register
          for callbacks when apps reach a certain usage time limit, etc. -->
     <permission android:name="android.permission.OBSERVE_APP_USAGE"
-        android:protectionLevel="signature|privileged" />
+        android:protectionLevel="signature|privileged|role" />
 
     <!-- @hide @TestApi @SystemApi Allows an application to change the app idle state of an app.
          <p>Not for use by third-party applications. -->
@@ -6724,7 +6732,7 @@
          it will be ignored.
         @hide -->
     <permission android:name="android.permission.MODIFY_DAY_NIGHT_MODE"
-      android:protectionLevel="signature|privileged" />
+      android:protectionLevel="signature|privileged|role" />
 
     <!-- @SystemApi Allows entering or exiting car mode using a specified priority.
         This permission is required to use UiModeManager while specifying a priority for the calling
@@ -7655,6 +7663,24 @@
     <permission android:name="android.permission.GET_ANY_PROVIDER_TYPE"
                 android:protectionLevel="signature" />
 
+
+    <!-- @hide Allows internal applications to read and synchronize non-core flags.
+         Apps without this permission can only read a subset of flags specifically intended
+         for use in "core", (i.e. third party apps). Apps with this permission can define their
+         own flags, and federate those values with other system-level apps.
+         <p>Not for use by third-party applications.
+         <p>Protection level: signature
+    -->
+    <permission android:name="android.permission.SYNC_FLAGS"
+        android:protectionLevel="signature" />
+
+    <!-- @hide Allows internal applications to override flags in the FeatureFlags service.
+         <p>Not for use by third-party applications.
+         <p>Protection level: signature
+    -->
+    <permission android:name="android.permission.WRITE_FLAGS"
+        android:protectionLevel="signature" />
+
     <!-- 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 bce500c..b46902e 100644
--- a/core/res/OWNERS
+++ b/core/res/OWNERS
@@ -33,6 +33,9 @@
 # Multiuser
 per-file res/xml/config_user_types.xml = file:/MULTIUSER_OWNERS
 
+# Battery Saver
+per-file res/values/config_battery_saver.xml = file:/services/core/java/com/android/server/power/batterysaver/OWNERS
+
 # Car
 per-file res/values/dimens_car.xml = file:/platform/packages/services/Car:/OWNERS
 
diff --git a/core/res/assets/geoid_height_map/map-params.pb b/core/res/assets/geoid_height_map/map-params.pb
index 8ca032c..6414557 100644
--- a/core/res/assets/geoid_height_map/map-params.pb
+++ b/core/res/assets/geoid_height_map/map-params.pb
Binary files differ
diff --git a/core/res/assets/geoid_height_map/tile-1.pb b/core/res/assets/geoid_height_map/tile-1.pb
index 93a2fa0..c0f1242 100644
--- a/core/res/assets/geoid_height_map/tile-1.pb
+++ b/core/res/assets/geoid_height_map/tile-1.pb
Binary files differ
diff --git a/core/res/assets/geoid_height_map/tile-3.pb b/core/res/assets/geoid_height_map/tile-3.pb
index 4e22ca1..cc30471 100644
--- a/core/res/assets/geoid_height_map/tile-3.pb
+++ b/core/res/assets/geoid_height_map/tile-3.pb
Binary files differ
diff --git a/core/res/assets/geoid_height_map/tile-5.pb b/core/res/assets/geoid_height_map/tile-5.pb
index c5f5127..7e1f008 100644
--- a/core/res/assets/geoid_height_map/tile-5.pb
+++ b/core/res/assets/geoid_height_map/tile-5.pb
Binary files differ
diff --git a/core/res/assets/geoid_height_map/tile-7.pb b/core/res/assets/geoid_height_map/tile-7.pb
index 0928a6a..3bcdaac 100644
--- a/core/res/assets/geoid_height_map/tile-7.pb
+++ b/core/res/assets/geoid_height_map/tile-7.pb
Binary files differ
diff --git a/core/res/assets/geoid_height_map/tile-9.pb b/core/res/assets/geoid_height_map/tile-9.pb
index 6a2210a..558970d 100644
--- a/core/res/assets/geoid_height_map/tile-9.pb
+++ b/core/res/assets/geoid_height_map/tile-9.pb
Binary files differ
diff --git a/core/res/assets/geoid_height_map/tile-b.pb b/core/res/assets/geoid_height_map/tile-b.pb
index 5fce996..fbe02da 100644
--- a/core/res/assets/geoid_height_map/tile-b.pb
+++ b/core/res/assets/geoid_height_map/tile-b.pb
Binary files differ
diff --git a/core/res/geoid_height_map_assets/README.md b/core/res/geoid_height_map_assets/README.md
index 800b3e5..37a57b8 100644
--- a/core/res/geoid_height_map_assets/README.md
+++ b/core/res/geoid_height_map_assets/README.md
@@ -1,8 +1,11 @@
-These text protos contain composite JPEG/PNG images representing the EGM2008 Earth Gravitational
-Model[^1] published by the National Geospatial-Intelligence Agency.[^2]
+These text protos contain composite JPEG/PNG images^[1] representing the EGM2008 Earth Gravitational
+Model[^2] published by the National Geospatial-Intelligence Agency.[^3]
 
-[^1]: Pavlis, Nikolaos K., et al. "The development and evaluation of the Earth Gravitational Model
+[^1] Julian, Brian, and Angermann, Michael. "Resource efficient and accurate altitude conversion to
+Mean Sea Level." To appear in 2023 IEEE/ION Position, Location and Navigation Symposium (PLANS).
+
+[^2]: Pavlis, Nikolaos K., et al. "The development and evaluation of the Earth Gravitational Model
 2008 (EGM2008)." Journal of geophysical research: solid earth 117.B4 (2012).
 
-[^2]: National Geospatial-Intelligence Agency. “Office of Geomatics.” 2022.
+[^3]: National Geospatial-Intelligence Agency. “Office of Geomatics.” 2022.
 URL: https://earth-info.nga.mil.
\ No newline at end of file
diff --git a/core/res/geoid_height_map_assets/map-params.textpb b/core/res/geoid_height_map_assets/map-params.textpb
index 5ca6e4e..170e73b 100644
--- a/core/res/geoid_height_map_assets/map-params.textpb
+++ b/core/res/geoid_height_map_assets/map-params.textpb
@@ -3,4 +3,4 @@
 disk_tile_s2_level: 0
 model_a_meters: 193.0
 model_b_meters: -107.0
-model_rmse_meters: 0.29
+model_rmse_meters: 0.27
diff --git a/core/res/geoid_height_map_assets/tile-1.textpb b/core/res/geoid_height_map_assets/tile-1.textpb
index 7edba5b..b0c8044 100644
--- a/core/res/geoid_height_map_assets/tile-1.textpb
+++ b/core/res/geoid_height_map_assets/tile-1.textpb
@@ -1,3 +1,3 @@
 tile_key: "1"
-byte_jpeg: "\377\330\377\340\000\020JFIF\000\001\002\000\000\001\000\001\000\000\377\333\000C\000\004\003\003\003\003\002\004\003\003\003\004\004\004\004\005\t\006\005\005\005\005\013\010\010\007\t\r\014\016\016\r\014\r\r\017\020\025\022\017\020\024\020\r\r\022\031\022\024\026\026\027\030\027\016\022\032\034\032\027\033\025\027\027\027\377\300\000\013\010\002\000\002\000\001\001\021\000\377\304\000\037\000\000\001\005\001\001\001\001\001\001\000\000\000\000\000\000\000\000\001\002\003\004\005\006\007\010\t\n\013\377\304\000\265\020\000\002\001\003\003\002\004\003\005\005\004\004\000\000\001}\001\002\003\000\004\021\005\022!1A\006\023Qa\007\"q\0242\201\221\241\010#B\261\301\025R\321\360$3br\202\t\n\026\027\030\031\032%&\'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz\203\204\205\206\207\210\211\212\222\223\224\225\226\227\230\231\232\242\243\244\245\246\247\250\251\252\262\263\264\265\266\267\270\271\272\302\303\304\305\306\307\310\311\312\322\323\324\325\326\327\330\331\332\341\342\343\344\345\346\347\350\351\352\361\362\363\364\365\366\367\370\371\372\377\332\000\010\001\001\000\000?\000\262zRf\212\007Zu(\251\005H\2652\324\311V\022\247Z\235*e\251\222\247Z\231jU\251V\244Zu\024f\220\232M\324\231\244-I\232B\307\322\232Z\223i#\"\233\234\034\0328\244\243&\237\237Zr\346\244\000\323\205H\0058\n\\R\3654\361\322\224\032p4\340i\300\324\212\325\"\340\323\2155\272Tg\255 <\322\226\246\226\2463S\013\363F\372]\374SK\322\007\346\245F\342\244\025\342\346\233E\024\341J:\323\326\245Z\231jt\251\323\255XZ\231je\0252\212\231ML\246\246Z\220\032\221i\331\2434\224\204\322S{\322\036\224\224\205\211\\SW\2574bEl\2575\031fc\222)A\347\232w\031\340\361G\000\365\247\003O\rO\014i\331\315H\254E<\034\323\207\265.9\247t\242\2274\240\322\346\236\255S\241\342\236MF\315\357L&\233\232\t\240\221\212\211\232\242-\315\033\275\351\273\351\013P\036\247\215\376Z\234\034/5\343\004\322\023H\r-\024\361\326\236\265*\232\231jt\251\322\247J\235je\251\224\324\313R\251\251T\324\202\244\007\212vh\315\031\244\2434\204\323I\246\223M\310\246\232h\225\220piRp\017\314)\306H\335\260\026\214\247\360\3233\3158\032p5\"\232\221MH)\343\332\234\033\035i\343\004qF}h\006\226\212Pi\301\260je~)\305\3522\336\364\322\334sM\335\2323\315-D\346\240f\346\232Z\232^\215\364n\253\020\277\025+\310\000\257\035\311\240ry\240\364\240\032Zx\251\007Z\221EL\2252\232\235*u\251\320\324\313S)\251\224\324\253R\n\220\032\220\032vh\315\'z3A<Rg\212i4\204\214t\250\211\244-M&\231\212Q\3058R\216\224\341\232\\\323\301\251T\324\240\323\301\247u\342\200YN)\302\215\330\247\007\006\235I\232@\334\324\252\334R\226\246\227\246\026\244\rO\rKQ\277J\254\347\025\021zaz7R\356\342\245G\302\322\264\231\257*\"\222\202h\034\323\251\343\255H\242\245Z\231jU\025a\027\212\231F*e52\021S-J\265*\234T\252i\352j@iwQ\272\214\373\320Z\2234n\246\356 \346\232\316MFi\244\342\233\232L\322\203N\024\341\322\234)\302\234*AO\006\236\r<\037\232\237\273q\311\244\247u\024\230\247\200\343\2474\243\334b\220\216r:P\033\024\027\365\246\356\246\226\240\032\221M<\232c\364\252s6\rUg\346\232^\223\314\243\314\367\247\254\334rh2\361^i\272\214\322\023B\232z\234\232\224\n\231*P*E\025*\234T\252\030\377\000\026>\225(\007\373\325*\023\353V\020\361S+T\252jU5 5\"\232~h\335\357F\352\003\016\364\233\271\353F\341I\272\202i\244\323\016i\234\236\224\234\203I\232u8S\201\247\216\224\360)\340S\300\245\002\2368\247\016\264\352Q\326\2342jE\\\232\230\014\014S_\221\305@Cf\233\363\003N\003\"\232\300\212a\244\3158>;\323\204\224<\200\255Q\235\252\243?4\302\364\322\364\236g\2754\313\317Z_7\216\265\300?\006\232\t\024\271\006\224T\212jU<\325\230\300\253\n\243\034\212pL\373R\355\307J\221jU5 <\324\252\336\3652\265L\257R\253\324\201\252@\324\355\307\024\273\250\335HZ\223u\033\2517P[\320\322n\242\233\310<\032NM\000R\323\200\247\001O\024\360jE4\341\214\323\307Zp\245\243\245<T\252\005L\253\212~)\245N)\244c\232\205\310\31593\216iv\347\255E\"c\245W9\006\231\232]\330\244.MT\235\273\346\2523\324e\351\273\351\245\352\'|R\244\231\025\305\023\223IHr\r\001\252UpE<75f)\010\034\325\224\22350\222\2246i\342\245^\224\360jE5*\232\225MJ\016*E5\"\265?4\340h&\220\232i4\231\2434f\234\r)\007<\032OcF9\245 \367\245\013\3058\np\024\264\341\322\234)\342\236)\342\234\005\030\247(\346\254\306\230\0315*\017\232\244\333\351M \346\243d&\205\203\234\232\220D\000\351Mh\317\\Tl\231\025NT \364\252\304\235\324\322\334S\013\361U\'z\246\317Q\227\246\357\244/Q3f\221\t\316+\223\221J\2754\032S\3154\216x\247(5\"\236jun*dc\232\235[5(8\251A\251\003qN\006\236\rJ\255R\253T\252\325\"\265H\032\234\032\236\r;9\246\223II\2322is\232QN\000\203\232R7\037\224\032r\200\243\346\024\273O\336\0074\243\2459S4\270\346\227\024\240S\200\247\201O\024\3409\247\201\305<-=\023&\247\035p*E\300\251\343\\.OSK\205\'\2457\n\r!#4\271\002\224\200\313\300\250$\\\n\251&;\325\t\370\351UKTl\304Ui\216A\252n\370\250K\322o\246\227\246\027\346\245\217\221\232\346\344L\216\225U\220\251\342\223\232@y\247\003OSS)\251\221\252elT\252\365*\3101\357N\017N\017R\006\251\025\252ej\221Z\244V\251\025\252@\324\360\324\360i3\357IE\024S\201\247\212x\343\356\232P\t\357OS\267\202)q\316@\251c\311B=)\240\035\334\323\261\305\000S\200\247\205\3434\340)\313R\001O\003\0252\214.iTsR\2049\315<\265&\363M-\357H\033\234\320\322g\232\226\031=j\033\231{-P\221\2169\252\223\034\245Rf\305@\357P;eMQrI\250\230\323wR\026\250\367\022\370\253h\300.3XL\274\361Q2\003P\264>\225\t\\\032QO\002\236\265*\232\225Z\244\rO\017R+\323\303T\212\325\"\265L\255R+T\201\252@\325 j\2205<\032vi3Fh\024\264\341O\024\341OZ\220s\326\234\020\366\247(\303zT\214\271]\324\301\310\245\002\234\005<\nv)G\025\"\324\350\271\247\037Jz\216i\345\305D\317\317\024\240\320\302\243\3174n\247\243\205\006\243v\004U\031\334\n\253+\374\237\205g\273\363U\344j\201\244\302\325fl\232\211\2150\266)\214\374S\021\263%X\335Y\244f\243*)\207\203\203P\315\037q\322\240\034\032\222\234\r<5<5H\032\234\032\236\257R+T\252\325*\265J\255R\253T\212\325\"\232\221MH\246\244\006\235\2323K@\353N\247v\247\216\264\361\322\234:\324\202\236\017j\221@\245l\343\255 \245\247\016\224\240\323\201\247\016\265\"\364\253\021\364\247\001JA\315#\003\212\21384\273\360i\032aL,)\245\361M2\212C(\332y\346\263.f\303\021\232\255$\277\'Z\246\317\223QH\337-Uw\310\353P\027\250\331\3522\365\0337\024\261\260\034\323\332Z\246\030\021Q\273sL\311\2458)U\230\r\324\242\224S\3058qN\006\224\036i\341\251\352\325*\275L\255R\253T\252\325\"\265J\255R\251\251\024\324\252i\331\245\245\024\264\352p\247\016\264\361OZx4\3655 5 \346\220\251\006\224\n\\R\343\024R\216\2652U\205\030\025*\200z\322K\305Dd\355Q\226\025\02374\306jn\376:\324O6\017Z\215\245\'\241\250ZfPsT&\230\273\342\241\226L\014f\253\0319\250\344\227\212\246\362u\346\253\264\2304\322\371\031\250\332Za\222\2177\024\246L\214\325E\223\035\351\036NsR+\202\264\021\201\327\255V|\206\240\003\214\322\203O\006\236)\302\226\224\032p4\365j\225^\246W\025*\265J\255S+T\252\325*\232\225O\275?4\240\323\201\245\007\326\234\r8\032x4\361N\024\340j@i\340\324\210qR\251\007\255?\313R3Q\036\030\342\214\322f\224\036jx\3175eO5:\021Q\314sUX\363L-\305D\355Q\027\250\236Oz\254\354}i\242LTo #\255Sb7f\252\315\'5\\\275A,\274UG\227\336\242i\006:\324M)\035\352?2\233\346d\320\315\315;\177\000T\000\363Crz\320\254T\342\245\r\221\212\0317\034\212v6\'_\302\221Dl\331\'\025 H\313\3434\245\027\034\032\0008\245\246\321N\006\236\rJ\255S+T\252\3252\265L\255S+T\312j@iA\247f\2274\240\323\203S\303S\301\247\202)\340\323\305<\032x5*T\352p\265\024\204d\324Y\2434\345\353V#\367\251\325\252d<\323d\252\256y\250Y\261P\274\225\013\275@\317Q3f\240w\252\354\347\326\242y\224\n\255#\206\351Udb*\243\266MT\225\231MW\363\2114\326zo\231\332\200\3376sJ_-O\r\315@\254jL\323\360\010\3159x5*\021I8\314y\035\215B\265 \251\026\244RGJJ01M\242\234\rH\246\245SS)\251\224\324\312je52\237z\224\032x\351KK\232Pi\300\323\201\247\212x\247\003\212x5\"\232\221ML\255R\206\342\243\2238\315F\304`b\2234\3655b3\362\324\240\346\245V\305\016\331\025Y\201\316j\254\274UV|\032\215\237\212\201\232\243-\305D\3075^CT\344\004\363U\367`\234\232\255$\234\021U\332@\rE#\007Z\252\300)\250\030\363L/\212E\2239\247\253\367\247\t~j\007\002\234\016jD\315J\007\024\240\363@\2339\\f\232\230\363>a\200i\347\001\270<S\224\324\200\321E6\223<\322\203R)\251T\324\312jd\251\226\246Z\231ML\206\244\000b\226\212QJ:\323\3058\032x4\340i\300\323\303T\212\325 \'\265H\036\235\2735\033d\232Jz\365\251\324\340S\325\352@\374u\2442b\242\222Q\212\247+\346\251\273sQ<\234T\014\365\013Hj&\227\025\021\2235\033\234\255T\224\000\0175A\330s\315U\221\252/3\006\242\226QQ\026\004Ui_\024\304\223\t\365\246\375\243\007\255)\271\\guh\032\027\255L\214=jBhS\316i\222\355Q\271\0174\304\220\223\317509\247\216)\340\323\201\2434\322i)EH\2652\212\231EL\202\247QR\250\251V\246J\2234\341E(4\264\242\236)\302\235K\232pjpjz\277\275H\036\224=8H1\31580\'<S\324\323\311\3158\032C&;\323\014\325\013\276{\325y_\212\250\317\311\346\241g\250\231\205W\221\310\350*\263\313\307Z\214I\223CK\205\252\027\023\222N\rS\337\317&\240\220\344\361Udb*\006sM\363q\305C;esQ+|\225\033\2675\0236Mt\'\030\246n\301\245\014sS!\312\340\324\212p\246\253\311\222i\023\212\260\255\305H\r8\032p4\036\224\224\231\247\n\225\005N\275*U\251\220T\352*U\251\007\025*\232x4\360ih\242\234)\300\323\301\247\003KFh\315858=;}\033\351C\034\360j\314o\362\324\313\3174\346#\025]\3175\021j\215\236\253\312\374UFj\215\232\241c\212ia\264\346\250L0\331\035*\0030Q\357U$\273b\330QU\336B\300\222j\r\304\232B\340.*\263\221P9\025\003\036j9[\344\300\357Q\226\332\270\250\031\351\253\226l\n\337/\221L,jx\376\340\251P\214\323\367u\024\306\031\031\246\016)\352jU5(\245\242\212)\313R-L\246\246SS\241\251\226\245Z\225M<\032x\353N\006\234\r-(\305-(\353N\006\2274\271\2434dQ\232pj]\324\006\251\024\363VT\345*TlR\261\342\253\273\032\211\232\241v\252\3225Wf\250Y\351\214\374Uws\353U\335\361\326\250\314\303\250\025P\221\232\211\334\364\240\020\027&\253\263\356$\324\014\325\021aQ6*\274\215\206\252\357!4\221\256\366\301\251B\210\316\354\216+S\'4\365\344\340\324\350s\305?\030\247t\245\352\271\246\232QR\255H:S\263FiiE8T\213R\250\251\224T\312*e\251\226\245Z}8S\251\303\2458t\247\0026\343\037\215\024R\366\245\243>\364g\336\214\373\322\344\321\232\001\315(\251\026\254\306\374`\324\312A S\330dqU\335MB\300\212\201\311\252\322UGnj\0265\0137\025\0136*\274\217U\334d\032\245(*I\355P\253\203L\232LGU\321\2629\252\362Hw\034TE\351\273\252\tOz\204\340\214\323\343tT9\034\324/&{\327B\0004\323\303T\221\223\272\247\006\221\211\333O\207&>iJ\234SFsR\251\251\001\002\215\324S\201\247\003O\025\"\324\253S\245N\242\246QR\250\251@4\352p\351N\006\234\016i\302\235E\024f\223\"\214\321\2323FiA\247\003N\247/\0252\232\260\277w4\365n)\030dT\022\014\n\251%V\220\360j\213\236M@\315P\263\32426EUs\203Q\263\325i~l\212\244AI)\263\214\307\232\256\271T\252\362\017\232\2424s\212\206f\001\rW\017\362Tm\'\275F^\272d\223\006\244\300nE9A\006\245SO#\345\247D0*P8\366\250\317\r\322\212viA\247\216\224\341\322\224t\251\026\245Z\231\005N\202\247U\251\224\032\225EJ:R\201KJ:\323\205<S\203)\0351FG\255&A<\323I\244\315\031\367\243>\364g\336\2274\240\323\201\247\212\220S\324\325\210\337\214T\203\031\247\026\025Vw\002\250H\375j\254\217U\034\325gnj6\344T\016\330\025^S\221\305Vf\346\230\306\252\312\t4\222\200\260c\271\252\255\220\2435\004\225\rG#\200\247\332\250M1n*2\377\000-FZ\232Z\272Ez\2367\307z\262\2445J\026\236\027#\025\"\256*`\277-1\343\371O\265C\216h\247\003O\024\341O\002\244Z\225\005XAV\021ju\025*\212\225EH\242\237\262\215\270\243\024\240S\200\245\034\036\224\224\206\222\220\201\214\346\222\214\212L\322\346\2245<585H\032\236\24752\034t\247\211>j\032L)\252\023\312I\252\216\365ZG\252\316\325RY>n\265\027\235\216\365\014\222g\245FN\027&\252\310\3377\024\3259\240\200MD\350\031\306O\002\253\334\340/\025M\216V\240c\212\2551%H\252.1Q\026\246\026\246\226\256\221O525Z\211\252\354|\212\220qR\246\rL\005+/\025NA\363`Spi\303\232\220\np\353OZ\225EL\225<b\254\245N\202\247U\251\025jUZ\223\034Rm\366\244\333K\266\214R\342\223\024\205i1\355HTc\"\230\300c#\2553\"\214\321\223@4\340\324\340\324\360\365\"\275L\217\305!\177\233\255$\222\215\225\237,\234\360j\273\275V\221\352\273\311Ud95^C\201U\374\314\034\032Vo\223\031\252\314\017Zb12b\211$>f\325\374\3526c\236\264\307 \214\032\247\"\340\222*\263\236j\t8\346\250\315\326\2531\346\230M0\232\351\325\252U5j#W\243<\n\224\036je\004U\204\351J\307\212\250\313\363\023M\332i@\247\201O\003\232xZ\225V\245E\253\010\265a\001\253\010*u\251\026\247QO\306h\333F\332LR\342\223\024\270\243m!Zc-FS9\305FGjm74\271\243w\322\215\324\340\364\360MJ\257\201\315D\322e\372\320\357\362u\252R?=j\007~*\244\262{\325W\226\240iy\353Lw\371j\224\214wR\t\t\342\245\004\0049\250\027\345\311\365\244<\032c\021\214\325Y\030\226\246\220JUiGz\247)\340\325)\016j\263\036j2i\205\253\246CS\251\2531\032\273\033t\253+\311\253\013\322\244SJ\307\010j\034R`R\205\247\205\247\005\251\024T\252\265*\255N\202\254 \253\010*e\025*\250\305J\253R\000i\330\"\212L\nLRb\226\227\024b\202\265\013!\007\336\241e \221Q\221\3150\212CM\315\0314\252y\253)\214S%|US)\337Oi>^\265U\337\232\2554\230\025FI3U\245~8\252\215!\317Zo\230}i\216\331\246.wT\305\3601L<\324r1\034T,\334b\242#\346\247\201\362\325i\2078\254\351\007\316T\3259\0075U\3705\003\032c\032\351\34395i\007\031\2531U\2658\305[\214\346\246\007\212ps\234\016hi\0161\212@\t\245\002\244QO\013N\013R*\324\212*U\0252\212\235\005XAV\024T\252*E\025(\024\374qI\217jiQM+A\007\322\223\221\332\224\032wZ\010\250\310\367\250]y\315B\302\243 \342\230i\206\212\013\005\024\345\233\013P\311)c\305E\273\236i\036O\226\252\311.*\234\222\363U^Z\254\362Uvl\232izM\331\247\240\346\236\352w\001I\322\241\227\223P7Z\\\0023K\3749\252\3569\315P\270\0309\252.2j\254\334f\252\265D\306\272\270\2075eH\305O\031\346\256\2475f>\225(`x\315\n\3309\025&\355\303\232r\021\267\004S\306\010\351NQR\001\3058\n\225EH\005J\242\246QS \253\010*d\025:\212\225EH\243\212v)(\"\223\036\364\230\244\305\030\024\270\244\"\232W\332\242e\250Y0j2\274Tej2)\216v\364\250\276f4\216p0)\231\310\300\246\266@\305Wv+\326\251\315\'5NG\367\252\316\347\326\241g\305@\317\223\232n\354\232z\363V\241\\\265X\362\376f\315Wu\305V\222\240 \232pS\214R\200X`Tr.\334\203Y\367\0309\252\014\270\315Q\233\255@zTO]dq\232\260\221\346\246E\000\360j\322\034\016\265\'\233\306\005<1\305=Z\247S\232\221jQR\n\221EH\253R\252\324\201MH\240\372T\252\017\245L\202\246Pj\302\n\231EJ\242\245Q\3058\322R\021IHE%\024\240\322\322c\212c(=\252&OJ\215\222\242d\250\231*\023\031-Mq\261p*\"7P#\3052A\305Q\236\263\346\357T\244lUs\'5^I2j=\324\003\315XJ\267o\367\305]+\3015Ra\326\251\275G\336\235\203\267#\255\021r3\336\241\270\357Y\263UV\350Ef\3160\306\253\032\215\353\264\034\016)\305\2601J\214sS\253f\246B3\315XF\\T\2523S*\324\312\206\245X\315L\261\032\225acR\254,*A\031\025*\217j\220/\265H\240T\312\005L\2121S*T\252\010\352*E\346\245\002\226\223\024\224\204SM%\024\240\322\322f\220\323H\246\225\250\33103Q\024\250\332,\034\325yP\356\246\254}\315\016\000Z\253)\300\254\371\233\346\254\373\206\306k>F\316j\253\232\204\365\244\3159z\325\210\375*\354#\004\032\272H+\326\250\314y5U\2075\0369\251TR\005\333\237z\255q\315gL9\252\222U9\323p\315Qq\203\212\211\253\262\'\271\246\007\311\353R\206\311\342\247CS\241\251\322\254!\253\010jt5a\005N\200\325\204\025:\212\220-H\020c\245;o\265(@}\252EC\354EH\247i\301\006\254&\017z\234\001\212\000\346\2348\245\315.E%%!\024\322)(\244\315\031\2434\224\207\255&3Q\262\342\243c\306*\264\202\231\310\024\307\351T\2478\252\022u\315g\\r\325JA\216\005Uq\315FV\243\"\244J\262\235j\344<\232\232L\256\010\351U\344\344\324.\225\001\004\032\2263\232\220\250e5Fq\265\2105BaTe\025]\271\2527\t\203\232\252\334WZd\3341\332\204\305N\203\232\260\265:/5a\026\247E9\253(\207\322\254F\206\254\242\032\262\210jtCS(\305J)\343\351N\030\247\355\006\224\002:\032pb\01752:\036\243\006\246\016\244p\324\340\376\242\227z\323\201\007\275;\212m\024\231\244\310\246\320Fi\264SsK\232J\003`\322HK\235\330\250\010\346\241)\227\244 \n\202\\sY\363\214\325)\001\301\254\371\207&\251\272\325w\034\324l8\250\312\323\220U\230\305Z\210\342\2452\014r8\250\030\203\322\233\326\241e\347\002\236\027j\032\003|\231\252s\034\223\232\317\233\031\252\222\n\254\313\203U\346M\303\245P\225\0105\322)\251\320U\204\253\010\005XLU\230\352\312U\230\352\312\n\262\202\254\240\251@\346\244\002\236\005<R\201O\031\002\227\232p\346\224\002)\343=\252E&\246P\017Zw\2261\307\024\273\010\357I\226\244\311\240\020iqL\"\232i3KM<Rf\220\232)\301\366\241\030\353P\232i\344\324N\016*\254\202\251\3123\322\252H2\265FX\372\325\031\220\201UXz\324X\313b\224\246hX\352\302.\026\244Q\315I\214\212\257\"\230\337\330\321L\311\r\232qrA\367\246n\371H\252s\232\244\352I\315WqP2\324l\234U)\342\311\255\204\253\tS\241\253\010j\302\032\265\035Z\214U\230\305Z\214U\250\3075ajU\247\212x\251\007Jp\024\360)\341x\245\013\315<(\247\204\247\205\251\024{S\300\245\3078\2451\323J{Sv{SH\"\233\311\355HE4\212LzSI\246\037jU\004\366\251\226/j\036<S<\222\312O\247j\215\323h\250\230qUdL\325g\217\212\253,Dv\252r\307\201\322\250\313\036j\234\221TB\022\016qO\362\370\245\t\355K\217jQR\003\362\324S\034\255F(`1\232\215\251\204\360j\254\234\212\256\303\003\025]\327\255@\303\006\243a\305U\225kEML\206\254\'5a*\314uj:\265\035[\216\255G\364\2531\375*u\351R\255H*@*@)\341y\251\025i\341i\300S\200\247\250\247\201R(\247\205\346\246\216\035\307\245O\366p\0055\340\030\342\2431c\265D\321\344\342\232 \357H\321c\250\250\232<r*=\225\033\2550/5b4\025)!F)\204\346\205\004\036\234To\031,r8\250Z<qP\264jj\007\214\n\251*\212\245:|\207\212\314\221O5Y\227&\220\307\306i\270\307\024\322)\247\212h\353O\355Ln\224\3209\245#\345\250\030qQ\036\224\302\231\025\003\247\025RAUd\246u\030\250d\025eNj\302qV\020\325\204\2531\325\250\352\334un:\267\020\253+\322\246QR\250\251\024T\213R\255J\242\245QRm\310\243\034\323\200\251\024S\302\324\212\265<p\222j\332 Q\300\247\342\230\302\230\313\305FT\023\322\215\234R\025\2464c\322\2430\003\322\253I\003g\212\207\311pzT\252\254\243\221Mm\304\323\221\016*Lq@\\\216\177\n\257\"\374\325Y\301\006\253\270\252\322-S\231r\270\254\331\223\232\254W\006\223m0\240\315G\"\340T\007\255*\255/C\212G^*>\235iKdb\243q\301\250\302\360(\333\301\342\240\220s\212\2472V|\243\346\250\372TrT\310y\315XCV\022\254\307VR\255\307V\343\253q\325\310\352\302\324\313R\251\251W\221R(\346\245\003\212\231\005J\007\031\310\251\027\2458\npZxZ\225#,p\005\\\212\330\377\000\025X\010\007\013R,t\245\016zS\030sQ\260\250\217Z)\r0\344\322\n\010\006\230c\036\224\323\036i\206!I\267\024\323\326\230[\024\326 \365\353P8\007\245W\221\0063T\345\305S\224dU)P\223\322\2534\\\323Lx\024\306J\202E\315E\345\363N\330\0051\2074\323\234Tei\241y\2472dSD\177/\343HS\202*\274\251\212\2457B*\204\211\363T,\270\250\034f\244J\260\225e\rY\216\255F*\334B\256G\305Z\215\200\253(\342\247G\0252\276jecR\253\032\231X\324\252ML\225:\032\220\014\366\247\216\264\365\034\324\310\271\355W\355\242\001rEN\370U\342\210\372T\352)\314\006*\263\236j\"j&4\235\0050\2657w\2757w4\340x\2434\231\3074\306zo\033y\250\3109\342\243j\214\363Q7\326\253\271\355T\345\3435U\310\315W|T\004\014\323\030\003Q\262\361Q\030\375i\205EF\302\242aM<\323H\244\013\315=W=\2516b\232G\315\232\212Xw\216+>X\212\223\221Te\\\036\225U\224\324L\224\211S\241\2531\325\250\301=\005[\211I\253\221\200\rYCV\025\252tj\231\rN\206\247F\251\320\324\311S\255J\265:\021\221\236\225 ~\010\035\351\350y\353V\341@y\253\221\306\243\034U\225\340qNe\310\024\252\273E;v)\013\361P9\250\311\3151\263L=)\215L5\031\340\323\325\270\245&\232M  6M8\220\335(\030\025\004\243=*\273\n\211\201\250\034Ui\024UGJ\255\"T\014\244S(=:Tdf\242u\301\250\212\324L2x\240&\0055\2050/52.\006\010\241\2075\033.W\216\324\3022\236\365Ja\234\326|\221\363\322\253\264Y5\003\307\216\325Y*\302.j\334J*\344C\247\025i8\351V\022\247J\235\rN\206\247CS\247\326\247J\260\225:T\312*u\024\376\264\365\0252\212\277m\234U\261\326\245CS!\005\262M=\260G\025\031\024\303Q7Z@\0055\2526\024\302)\204S\010\246\034\203C7\025\031s\232p<S\267Q\232c\032\201\270\250\230\324\017\315Wq\232\205\326\253\262T\017\035Dc\024\302\206\230V\243e\315B\302\230#\311\351JS\025\023.\005\010\243\034\324\204c\232n3I\264}*\031\020\250\310\252\262&\340H\252\222E\355U\332/j\206H\375\253*1W#\025n!V\322\246SS+\212\231\034\324\350\306\254!5a\rXL\325\204\025e\026\254\242\324\352\265(\251\025jUZ\225E^\267_\222\254\005=\252P\010\245\004\253S\267\232vr)\215Q7Zi4\224\303L\2448\2465F\302\230zTD\032@\330\247\206\247g\212\215\215F\325\003\324-\315F\335*\027\031\250XqP\225\346\232R\230c\366\250\336:\201\3439\250\314|R\354\000t\2462\324,\234\320\027\212i\031niv\236\335)\254\205Wu0\202P\202*\243.\t\250\330\003\324T\016\202\252\310\204\326$jqV\243\006\255G\221V\024\237Z\231jd\253\010j\302\032\260\225f1V\243\025j1VcZ\262\213S\252\324\241jEZ\231V\246H\363WaR\251\315N\010\002\2369\245e\3434\314\322\356\246\226\246\023\3154\322\216\224\311\024\216\225\001&\232X\212\214\2614d\221\212kp*3\3150\212\005<\032\030TL1P=Bz\323\032\2425\033.j\"\274\322\204\243eE\"\014\361P2Te)\2451Q\021Q2\344\323\010\246\021ON\264\254\013\n\215\207\311\214UG^M@\313PH\rT|\203Y\021 \357VPqS-L\2652T\351S\245YAV\243Z\265\032\325\270\326\255F\206\255\306\225j4\253p\300_\245O\366G\331\273\034S|\262;T\212\265:\014U\304\301\\R\201\363T\3109\244\231\200\\Up\334\365\245\335HZ\232M\0034\2439\245<\324l\200\324O\035DP\372P\020\367\2468\301\246m\346\220\216)\002\323\202\320V\241pj\006Z\211\205FEF\302\231\214\323Y{\322\250\342\220\2574\306\\\325wJa\\\nc/\025\013-DV\243+\3150\245*\214\032s)\333\221\322\243a\230\370\252\254\265\013/\025ZU9\252\256\225\216\213\305N\265*\n\235\005N\202\247AVcZ\264\213\355V\243SV\343J\271\032U\270\322\255\306\225i\024U\270\243n\302\257\333\034\202\217\323\025\004\252\276g\3128\241S\212\220\n\236.\2656\337\232\244Q\315A1\313b\240 \321I\322\226\214\320\032\224\034\322\3434\335\271\244+\3050\257\265B\353L\333AZM\274\324\213\036E#\'\025\003\245D\310*\007J\205\224\324L*<\363N\306V\205\0242\324DS\n\212\211\226\242e\2462\361P\272\324Ei\245i\245q\315*\222s\216\206\220\246\024\325G\034\324.0*\007\\\325w\216\260\324qR(\251\226\247AV\020f\254F\265n4\315Z\215*\334IWcJ\267\034un4\253(\265j%\344f\264\003\242\304\002\016{\232Eb\t#\275.3F1K\232\222&;\252\3568\006\244Q\205\252\356\240\2651\220b\230V\230F)\247\245!\243\265\000\363R)\247\205\006\215\234\320c\006\241x\352\"\224\302\264\230\346\246\214dR8\030\252\354\274T,*&\025\023%D\361\361P\030\360h\306\006)\003\001K\301\2462\323\n\324l9\250\231y\250\310\342\242e\250\331*\"\264\233r1B\307\203\200h\223\n\244w\252l;\232\201\305G\267\232G\213p\256\\\037J\225A5:\n\260\202\254F*\324kV\343Z\267\032\325\330\222\256\304\225r5\253(\265:\001V\020T\300\324\253O\310\305!4\016jxP\347&\255\241\340S\363\306)\2453\3154\246*6Z\211\2050\212i\024c\212@9\251\224\014S\2063Ru\240\014\323Y\001\250\2319\250\332:\210\2574\364\024\254*\007\034\324\014)\205h\362\363H`$t\250%\203`\311\025M\306\r3\031\243\024\021I\267\212\211\227\232\211\2075\031\024\302\271\250\331N1Q\225\246\021\212:\014\212\257!$\346\240`j&ZENiYv\255r(\2652\212\235\005XAV\020U\250\305[\210U\310\226\257D\265v%\253\221\255N\253R\255L\246\245\rOV\247n\244\335R#sWc\345EZU\371i\333A\247\005\342\232\351P2\324l*\026\342\242&\233\272\200\3075*\267\025\"\232\220t\247\016\264\354dSv\323Z>3\212\204\307\232\004x\355LaQ2\324\014\264\300\234\324\321G\226\346\254\210\323n1U\257!\0333X\322\'5\036\334\032LsK\267\"\233\214Tl9\250]y\246\025\246\221Q\260\250\312\323Z<\212\213n\001\315B\313\315F\313\305BW&\223n\r6Rv\364\256M\026\246E\251\321j\302-YE\2531\255[\215j\344KW\342Z\275\022\325\264\025(\351J\r=MH\246\245\024\372P\246\244U9\253\360\003\266\256\240\310\247\010\371\351O*\000\346\242a\351P\262\361P0\250\231y\250\331*2\224\230\3058\034\324\211S)\247g\232\225H\247\000\t\251B)\\\032\211\341\002\243d\342\240d\347\245E \305Wa\3154(\315N\253\307\024\356\202\243\224\227\\\032\315\232,\032\247 \250\361\315H\213\236\264\216\270\250\030TdTdPS#5\023%3m#\014-V~\265\031Z\211\206i\241\t8\002\217,\214\223U\2468\004W.\213S\242\324\350\225a\026\254\"\325\250\322\255D\265v$\253\261-]\217\212\260\246\237\232QR(52\212\225EH\242\245T\251\322*\261\031\nqV\025\360sV\222@\313C\014\323\n\324l\225\003.\r@\303\232aZB\224\302\224\3020h\r\212p\223\024\242Z\2327\315YCR\206\024\204\202)\244`T\0220\351U\230d\324.\265\027CSFi\355Q61U&\031\315Q\225j\014sR\245\0168\250Yj\026\\S\010\024\336\224\322)\214\275\351\256>Z\250\313\3151\2050\246)\321\'$\232d\247\002\263\247<\232\347\321*t_j\235\026\254\"\325\224J\263\032\325\270\327\245[\211j\344ue*u\251\000\251\025jE\025*\212\225EL\213VcJ\262\023\002\230\334\032z\260\"\247\214\221\315Y\007\"\2341H\3121U\332<\324\017\036*=\234\322\354\342\230R\240\221y\250[\212o4d\324\210\344T\3511\251\226Q\267\255\006nz\322\031r)\204\346\243\250\334TEy\247\247\024\366\037-B\365ZJ\251\"\346\253\262\340\323\223\216\2643s\212\214\372\324n*-\264\205i\002\363Lp1Q>6\325v\024\2018\245\362{\232G\340qT\345=j\204\375\353\025\026\254\"\324\350\265a\022\254\242\325\204Z\265\030\253Q\212\262\200\325\204\025e\005L\253S*T\202:xLT\212*t\025f>*\302\3621H\321\223\332\221m\244#p\034\n\2364u\352*\302\216)G\006\203M#\212\211\227=\251\276]\006\023Q\274~\325]\343>\225\003E\203\322\231\266\232W\232r\245;\030\243w\024\201\262is\357J)\330\244+\232iJM\274\323\361\362T\016\005Wu\025\001J\255\"\363Q7\024\302i\273\275i\t\3153\034\323Xb\223\024\307^3U\330S\030qH\024\346\236zT2\016*\234\253Tf\025\224\211\305N\211\355V\021=\252\312%XD\253\010\225f8\352\312\'\265YD\253(\225f4\253\n\2252\245H\026\227\024\241Njd\006\247AV\341\031aW\320B\215\373\305\004\373S\036\340.V!\362\237j\222\031\004\213\265\200\311\350iY6\022\010\246\000\244\324\251\010jV\265n\302\233\366V\356)~\314})\0148\035*\027\207\332\253\274|t\252\317\017\265Wx\210\355Q\354 \321\320SX\323I\342\220\034R\206\346\244\0074\361\322\234\005\004z\323X\001M\'\214T\017P0\250\230TN\274UI\006*#\322\230F)\0014\354\342\232\3447A@^*)=*\022\2714\323\031\243f)\030T.*\264\213\236\225Nh\353%\024\021VQ*\302\'\265YD\2531\307VR:\262\221\325\204\216\254\307\035Y\216:\262\221T\341)\341i\341i\301i\312\2252\245J\253S&GJ\230\022\335jA\030\"\247H\260r*|e0y\367\250\0323\273\212\265o\021=MhG\0261\226\006\2440\257aQ\2748\031\252\357\025A$B\253\264#\322\240xEVx\375\252\026\213\'\245@\361\021\332\253\272\324d\032L\032P\2475\"\255L\026\234\006\r5\21534\323Q\270\250XT$sMe\342\252\312\231\252\345qL<\2321M\"\200\224\347\033V\253\260\346\231\263\276)\344q\214S\030s\322\243aP8\250\035y\252\322\245d\306\225j4\253)\035YH\375\252\314iV\243\216\254\307\035Z\216?j\262\221U\230\343\253)\035J\261\023\320S\214%z\322\204\245\333J\242\247U\251UjUJ\220-H\240\212\231\037\024\362\336\207\255.\340\274\232\226\031\001\351\326\255+g\275J\222:\236:U\225uu\301\034\324o\032z\325w\210T\r\020\3061U\344\204\325W\214\347\245Dc\366\250^.*\244\261\363\322\253\262sM\333OU\0252\240\305-\033sQ\262\342\242n)\273\251\244\346\232\313\305@\312EF}\3526\000\324\022G\305W*\001\240\257\313M\003\232\220\000\006i\216\245\201#\265E\260\346\235\345\340f\232\312)\214\265\023\n\256\342\253\271\252\362\034\326tiV\243J\267\032U\224\216\254\307\035Z\216:\265\034uj8\275\252\312GVR:\231S\332\245@C\014\016i\322ng\313\nLzR\0244\345\\T\312*x\327&\255$9\2531\333\307\217\230\323^\020\033\212\210\251ZP\334S\035\216*%\231\243\223\'\245]\212\344\021\326\254\255\310\035\352d\274@z\324\313q\034\202\220\220x\246\025\250\2313P\264c\322\241x\207\245@\361{U)\243\307j\245\"\340\324X\346\234\240\372T\352\016)H\246\236;S\0335\013f\243\"\220u\251\225C\n\212X\275\252\234\213\203P\263\001\326\230\314\245z\324\014E3p\244\030\315?\256\006)\357\030\021\361L\211\006\303\221\310\246>G\025\013S\017J\215\252\t\005S\224sU\\\340\3241\'j\264\211V\343J\265\034uj8\375\252\334q{U\250\342\366\253I\035XH\352\302%L\261\324\210\230l\342\246h\325\227$\363Q\254>\324\246*n\312z\245X\210b\255+`T\250\031\217\024\374\034sMd\310\344\322}\230\323\036\r\275\301\250Z\000{SB\025\355F\342)\3502y5n\027U8\002\254\357\315/\231\317ZB\331\357L$S\030\002:Uw\036\325ZT\317j\254m7\236x\245\026*)\r\250\034\201L1c\265\'\227\355M1\342\242)Q\274|f\253\270\301\250\330\342\232\322\343\220i\032\360m\303V}\315\310\347\006\263d\273ny\250\276\322\347\253R\371\304\236\265<gwz\233\3138\310\240\002\rI\234\361NQ\311\305E\"\363U\335qQ1\250\331\252\007j\253.*\224\207\346\251\"\\\212\271\032f\255\307\035[\216:\267\034un8\375\252\314q\373U\244\217\332\254$~\325:GS\254u(\2134\357+\265\00508\024\315\244\366\245\362\275\251Dx\247\250\305J\270\253\021\266*p7\014To\033\003J#r98\024\257h\3737)\315D\2609<\324\242\333+\202\265\033Y\340\344\nh\267\000\363R$J:\032\234Dv\323\0362*<\021\336\215\304u\031\244\363GB\264\206D\357M&>\271\024\321\345\267JB\027\2650\250\250\2360i\233*6J\217\312\315V\237\344\342\251\261\3115\024\234\n\2434\273sY\323\\\221\221\232\250\316\362\034\014\324m\023\355\316*\234\216\310\324Gq\223W\241\233\241\315_\216\\\212ql\232\001\346\245\003\346\006\211\000\252\316*\007S\212\254\365\003\265U\220\325I\0075j\025\351W\242Z\273\024y\253\221\307V\343\216\255\307\035Z\216:\262\221\325\224\217\332\254,u*E\236\265*\307\315I\345qI\344\346\224A\355G\225\201M1\023I\345\036\302\201\023T\311\031\025:pj\306T\247AUef\317\024\304\231\324\343<\032\263\037\314A\315\\P\010\351JcS\326\242{u5\027\221\203\3059c9\347\212\031{c5\033 \364\250Y=)\214\206\243+Q\224\246\025\244\301\035\351W9\346\235\212M\276\324\306\217#\201LD\344\356\025\235x?zj\213u\250\345\031J\311\271V\347\025\234\320<\215\3005~\322\303\241e\316j\343i\350W\356\363X\267\372n\030\225\037Z\307{w\215\372qR\306\344U\330%\347\031\253j\331\247\003\223W#\031AJ\361df\240h\361U\345^*\214\242\252?\025]\372\325i*\364KW\341Z\277\n\364\253\321\'\025r8\352\334q\325\250\343\2531\307\355VR<T\353\035L\261\324\253\030\025 \216\234\"\311\247\264$.@\315Fb8\311\240F1\315#(\307\024\212\000\034\322s\236)EH\231\316\r\022G\232\205bR\334\234U\310\243\215G\007&\247Q\216\224\343M\372\232C\214u\246g\236)\016sQ\2605\021\0079\246\344\037jF\214\036\225\013&\005FE7o5 \214m\246\272\025\246f\214\322\003\317Z\202\342\331dR@\344\326D\261\354b*\224\315\216\225_`q\234T\266\366\310\374m\255(mUG\002\2476\340\257J\243ub\256\247\345\346\271\315GO(\013(\357Y\277e;2T\323\0226Y+b\332\311\246\217 \363D\226\257\021\346\254Z0\337\264\325\231\020\257\035\252\263\255V\225x\2522\255Q\225j\233\365\252\262\032\325\211j\364K\322\257\302\265~\021\322\257D\275*\354I\310\253q\240\3435j8\352\312G\355S\254u*\245J\261\023\332\245X\375EH\25023J\301G\335\315D\313\236\325\031\\\nL\014sM\362\362h)\212f\010\244\336\303\221L294\334\234\344\232\261n~nj\372\016)\304\nB\252:\324,\240\236)\002\221A\300\024\302\303\025\033u\342\242`sL\311\024\306$\365\246R\021\315N\200b\231*\344\324[i6\212p\2174\214\273T\222+\036\351<\307;W\025B[lu\025\\@T\236:\325\253hJ\234\347\025\247\032qR\021\305B\311\236\242\251\\Y\254\240\202*\221\323\220)R\274Vt\272hY~QV\355\255\244\2152;S\245\371\224\207\034\3251\036\311\262=j\3431*3U\244\372UY\005S\225z\325\031\226\250J1Te5\273\n\325\330\226\257D\275*\364C\245^\210U\330\205\\\210f\256\306\265j5\315YD\366\251\2261\351S\244b\244\021\212\n\016\302\232W\212aZ\214\247\2651\223\006\231\203\232\010&\230\313L+\232n\312p\21354Q\341\205\\QN\013\232k\212\210\216h8\307&\240v\031\300\246\355&\227c\032<\263Q:\360j\002\274\323\010\244\305K\030$\343\322\225\3078\246\355\3158F)v\343\255#\200\313\212\245,Q\2575\235:\202\324\304\2005N\266\3309\251\000#\265;\024\204qQ2\203\332\230c\315Dm\324\266H\240\304\241p\005T\236\327r\022\274\232\317\362\376l\021\322\244+\305B\343\212\251\"\363U\244Z\243:qY\263\255g\312+\240\210U\330\227\245^\211j\364KWb\025v!W\"\355W#\305[\214\032\267\030\253(\265(_jx\034PW\232iZaZn\337Zk \"\2421\234\322yt\326\216\242#\006\234\211\272\245*\253K\031\\\324\312E;x\035M1\244SP\274\250:T\r2\372\323C\202sO\022\201\320R\371\347\260\246\231\030\323H&\232TSLY\035j&B\246\225\034\243S\316\013f\224b\227r\200j\273\317\223\212\004\240\214\032\2539\315Qu$\364\2536\361|\265d\3066\342\243)\201\322\243 \212N:S\017Z1M\"\243a\305FG<Ui\255\201;\324Uf\217\216EV\221*\263\245V\221*\234\310\010\254\313\210\372\326t\251\315oD\265z%\253\321\016\225v%\253\261\n\271\030\253Q\373U\270\205]\214t\253\221\212\262\202\247\002\235\212\010\246\225\244\331HR\230W\024\233\001\2441\342\242a\316*?(\023\223La\267\245W\221\334\367\246\254\314\016\rJ\327J\203\214\223U\336\365\317A\212`\273\220u\246\274\345\273P\204c&\246\0141I\272\224\034\324\253R\205\310\246\262\340\322\252g\024\262F\241rEQ\223ho\226\232d\013\324\342\230n\027\035j&\270\335\300\246\344\232p\006\202\245\273R\375\234\036\325\"\307\267\245?\024\322\007z\205\366\324\rQ19\343\245\033\250\316i\204S\010\246\032\211\343\004g\025NX\210\344UI#\252\322/\250\252\222\307Y\363\305\327\212\316\222\034\236\225\377\331"
-byte_png: "\211PNG\r\n\032\n\000\000\000\rIHDR\000\000\002\000\000\000\002\000\010\000\000\000\000\321\023\213&\000\000\0021IDATx^\355\335\341\212\2030\014\000`\321\367\177\344\311\301q\333A\031\247\363\2646\311\367\375\014\010\352:m\322t\233&\000\000\000\000\000\000\000\310cn\003\265\024\277\374iZ\333\000\000\000\000\300Q\345+-\000\000\000\000}(\303p\217\245\rl\321\232\004\377\341a\017\000\000EH\237\001\000\330\246d\014\000\000\000\014F\271\002\000\000\000\340l?m\305\272\213\241\242\346\233\257\002\013\000\000\334e\270\302\204\004\t\000 \rS;\000\000\000\000\212\233\325\310\000\000\000\000\000\000\200\212\254\225\002\000\000\000\224\240\014\004\000\000\345\331J\013\000\000\300/I\"\000\000\2440\334_[\002\000\000\177z\226\347\217\225\351\277\217:v(\000\000@hR!\000\000\000\000.\245%\027\000\000\000`\014\032\205\000\030]\3565\005ob\000\000\000\000\000\000\000\000\000\000\200\354t\216\003\000\000\000\000\000\000\000\000\000C\323\354\010\000\000\000\000\000\000\360\226\345T\306\023`T.m\000\000\200~\3266\000\000\000\000\000\000\354\241\304\016\000\000\000\347\013\260\005\002\000\200\253\230\014\002\000c\270qV\242\033\005\000\000\340\036\3621\000\000\000\000\000\000\000\000\000\000\000\372\270q33;\330a\000\000\000\244\'1\005\000\000\000\000\000\000\000\200\275\362t\333\344\271\022\000v\262W\2568\003\000\200-\2176\000\000\000\000\000\000\000\000\220\234\336:\000\000\000\000\000\000\310,\302\212`\204s\004.\344!\000\000\000\000\000\000\000\000\000\000\000@\030\232\340\001\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\200\314\3466\000\000\244\342]_\336\352\2675\253\363\024\000\250\315{\240\270\347\0000\020\330C\352\000\000\000\000\274((\001\000\000\\K\336\005\000\300G\2266\000\000\261i^\007\000\000\000\016RV8\217\366\025\000\000\000z\222\207\2264\373\334\001\000\000\000\000*R\035\006\200\300z\265\354\207\2330\364\2721\000\000\220N\270\331?\347*?\000\242\337\200\350\347\317Q\n!\000\000\000!H\337\030\211\361\010\000\000\000\000\000\000\300;_s\363!1$IHR\000\000\000\000IEND\256B`\202"
+byte_jpeg: "\377\330\377\340\000\020JFIF\000\001\002\000\000\001\000\001\000\000\377\333\000C\000\003\002\002\003\002\002\003\003\003\003\004\004\003\004\005\010\005\005\005\005\005\n\007\010\006\010\014\013\r\014\014\013\014\013\r\017\023\020\r\016\022\016\013\014\021\027\021\022\024\024\025\026\025\r\020\030\031\027\025\031\023\025\025\025\377\300\000\013\010\002\000\002\000\001\001\021\000\377\304\000\037\000\000\001\005\001\001\001\001\001\001\000\000\000\000\000\000\000\000\001\002\003\004\005\006\007\010\t\n\013\377\304\000\265\020\000\002\001\003\003\002\004\003\005\005\004\004\000\000\001}\001\002\003\000\004\021\005\022!1A\006\023Qa\007\"q\0242\201\221\241\010#B\261\301\025R\321\360$3br\202\t\n\026\027\030\031\032%&\'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz\203\204\205\206\207\210\211\212\222\223\224\225\226\227\230\231\232\242\243\244\245\246\247\250\251\252\262\263\264\265\266\267\270\271\272\302\303\304\305\306\307\310\311\312\322\323\324\325\326\327\330\331\332\341\342\343\344\345\346\347\350\351\352\361\362\363\364\365\366\367\370\371\372\377\332\000\010\001\001\000\000?\000\272N)\t\315%\003\212u9jU\251T\325\204\253\021\212\265\025Z\217\245Y\216\254!\2531\325\210\352\312T\311S\255J\225&x\244\245\3154\265&\357zB\324\205\261M\337\232i|SK\nM\245\201\"\243\335\203\315\007\024P\t\024\360\331\034\323\223>\225(\346\244S\315J\006i\340S\261@9\251\007\002\234\r8\032x4\365lT\250\3654x4\362)\2148\250Z\22074\342\342\230\317Lg\342\2432Ry\224\276g\2755\245\244\022sSF\365(9\025\363\253\000i\224QN\006\224u\251P\324\310*\304uf:\263\025Z\212\254\240\253\021\212\260\234U\230\315XCS\245L\246\245Z\2234\233\251\t\315!4\332nsHzSi\031\362\270\246)\311\346\217\2366;y\025\023\312\316\334\212\025\275zT\234g\203A\340\365\245\006\245V\251\026LS\303g\265H\216EJ\033>\324\360)q\3158qKK\272\234\r(j\221\032\254\306jBqQ\263\324D\342\231\273\232\013Q\221\212\205\332\241/\315\'\231He\246\0313@\222\254\303 \307\275YV\343\232\371\330\234SI\240\034\322\321O^\265*T\321\232\261\035Y\216\254\305V\243\253)V#5e\rN\225:\032\235\rL\265*\236)\344\321\272\202\324\332BqA9\246\223Q\226\3057p\24655n\032.\224\261]\000\307p\034\323\336H\335\200\002\202\020}\323\317\245F[\232p4\3655*\232\225ML\264\361OW\305J\274\216)3\353J\r-\031\305(4\345lU\230\344\342\236d\250\231\251\205\351\273\362i\013R\346\241\224\342\2533sL\337M2Ry\224o\253V\256\rXy\200\025\363\331bh\034\232R8\244\006\235R-H\2652\n\261\035X\214\325\230\315Y\216\254\306j\312\032\260\206\254!\251\320\324\253S+T\252\324\374\321\272\220\236h\315\004\214{\323s\3054\232i \203\353P\263SK\323\030\346\230E\000b\236\t4\340i\300\323\201\305H\254jtj\231Z\244V\247u\245Vd4\340I\352iC\340\323\304\200\322\346\215\324\233\271\251Q\270\247\227\250\332Jc>i\003\323\303R\347\212\212L\342\252J\330&\2402S\032JO34\241\370\251\341\227h\315:I\363^\030F)(-B\363O\247\257Z\231\006jd\353S\245XAV\343L\212\260\203\025<l*\314f\254%N\22526*e5*\265J\246\234\036\215\364\273\251\013{\322\026\244\335\355M\337\265\263\201M\222R\303\260\250I\246\026\3057p\244&\200i\352i\353\322\234\264\3403R(\251\027\212\225Z\245V\247\206\033\205H_\314>\224\235\r;\250\244\333\212z\356\0359\024\365 \236F)\214\274\361\315*\276(ix\246\357\246\027\2405K\031\315JN*7\034f\263\356[\006\251<\230\250\314\264\202lR\371\325$w<`\232V\236\274d\265&\352Fj\025\271\251T\3565*\212\261\02758Z\225\005XC\212\2317\036\340\n\235C\001\367\252x\330\364\253Q\267\025a\032\247F\251\324\324\252jUj\2207\275\033\275\351K\320\254\017SM\337\315.\372B\331\357HZ\230MF\304\323\016OJa$\036h\007\024\341N\006\236\246\244Z\224\nz\212\220\014S\224S\307\025 \353N\316)GZz\344\324\212\2315e\023h\244q\301\307Z\252\333\272Td\225<\323\302\356\024\326R*6\244\r\212p\227\035\351\3536id\227\345\254\333\247\254\371$\346\2432S\014\264\236o\2755\2560i~\321\307Z\362\25184\300\344S\213\003@\251\222\247CV\241\000\342\255\242\014sR\010\363\320S\202b\244N*\3021\251T\340\324\350\376\365:>*\304r\325\204\220T\252\365*\275?y\305(z7{\322\027\246\227\243}\033\351\013\373\322o\242\230r\016s\3154\345\217\275\000S\200\247\201J\005J\265*\232\225M<S\307Zx\024\264\271\"\236\246\247E\025:&9\251v\323\031O4\302\276\325\014\230\315\021\222GJ~\315\335j\t\242\307J\252\331\006\243.}iC\342\221\245&\250\335\310q\326\250<\225\013KL2\323L\225\014\222\342\210\347\335^p\355\223IM$\203J\257S\244\200\212\221d\301\253pK\216\265v)sV\004\240S\267\346\236\265:t\247\253b\246F\315L\206\247CS)\305L\257R\243\324\201\251\352\324\023M&\220\2657u\005\250\335J\016i\304\036\324\231\365\240\216iv\236\364\241i\340S\202\323\200\3058t\251\001\251\026\244^\325\"\323\200\315.\332r\255Z\206.\346\254\242\374\330\251v\323XsQ:\023LKR\307&\247[p\007Jk\304Gj\211\342\310\254\373\210\366\223T\334\374\324\322\370\025\031\222\250\335I\212\317y*\026\226\233\346SZJ\202I3M\211\3105\302\314\233\032\230\r)\346\230W\035)\350\rH\207\232\267\033qV#sVQ\263S\251\305L\255S#\361N\006\244V\305N\217S\243\324\310\3652\275J\257O\rR\006\247n\3154\232i4\205\250\335J\016i\300\323\206A\3159\206\343\362\344\232r&\334\356\024\245\t\344\034\212r\340\216i\310\233\251\333py\247m\366\245\013O\013O\002\244\003\232z\216jEZxL\324\221\307\223V\207\030\002\246@\005X\205x\311\247\025Ri\273T\032\t\037J\003\001N 2\236*\264\261\225\035*\204\300w\254\313\240\027$U\006\223\232\211\344\"\252\\6\341Y\322\311\212\256\322S|\312C%Dd\346\245\204\356\346\271\t\242\r\332\251<eO\024\200\232n\356i\301\261R\253f\247F\253\021\265XG\305N\222U\204\224b\234$\247\254\225*\311S$\225:=J\217S#\324\312\365\"\265H\257R\003A<u\246\223\232(\240\034S\201\357R\251\315=r\234\251\247\000[\222y\247\246\027\202)v\362H\351SC\226R1\315 \007w4\375\274P\005<\npN3O\013R(\251\024T\2521S\30603OQ\315J\250s\236\325.\374\014Pe\246\027\367\246\357\347\232W\2275-\274\27185\025\365\300\003\002\262e\220\221\232\243rr\206\263]\261\232\257$\265VI2\rf\312\304\223P9\250\367\322\027\342\2412e\261W\341`\024\n\346\0359\250$\2105W\222\334\366\252\354\205M(\346\244U\251P\342\247C\305L\217R\253\342\245Y*E\223\232\225^\245V\251\221\352tz\231\036\245W\251\225\252Uj\225^\244V\247\346\223p\244\335@\247S\207Jz\361R)\251\024\324\200\006\353OX\317n\225\"\002\255\351R\274{\206\357J`9\024\001\212z\212\221E<\n\000\305L\225b4\334jV\030\342\234\213\322\2462\000*\027\226\205485\036\352M\325$rm\3175\024\256\030\032\316\271p\240\325\t\345\005+.Y95RY*\253\313\200j\233\276MB\355Q3\001Q\264\225\032>d\025l>\000\254f\033\252&AQ\236\016\rV\271\213\270\351U\224\020jPx\247\251\251\025\252Uz\220=<IR\254\225*\275N\217S\243\324\310\3652\275J\257S+T\312\325*\265J\246\237\2323K@\353O\247\016\225\"\323\327\245H\275\252U\251T\361\212\225\006i\314H\\g\212b\212v)\300`S\201\247\203N\035jh\305Y\204p}j@\271\2459\244e\3435\t8j_4\n\036\340TE\3019\246\231\000\246\264\343\2654\3146\236y\254\213\353\2541\031\252\022\334~\357\255Pyrz\324\022\277\313Te\223 \363U\214\265\013\311Q\0313Q\274\234RB\300\034\367\251Z~+=\\\021Q\312\334\324;\251s\225\301\252\222(\rB\363\3059i\343\245H8\247\003NV\346\244V\251\021\352d\222\254F\365:=L\217S#\324\350\32525L\255S!\346\244\317\024S\205-8t\247\212x\355R-H\247\025 5\"\232\225N*U\346\220\241\007\245(\\S\266\322\201\212)\312j\304ui\027\002\247E\315$\377\000-Bf\300\301\250\231\205@\357\203Q\273\323|\337z\206K\235\275\352\027\271\'\245Wk\242\271\315f]]y\217U\246\233\013T\232nz\324R\315\201Y\362\315\311\346\252\274\330\246\03123Q<\375\252&\2334y\373\005)\233\"\250\3071\024\222\315\315>9\025\227\'\2558\215\243=\215S\224\341\261H\240\221\232pjz\265H\246\236\264\264\340i\312\325\"\275O\034\225a$\036\265:=N\217S\243\324\350\32525N\215\232\2245(4\340sN\007\326\234\r=MH\246\236\246\244SOSR)\251\024\324\321\266*u!\272\323\374\220\335\370\250Xmb\001\243u&M(nj\314G\221W\020\363Vc#\212e\313dqT$<\323\013\361PH\365\t\220\324\022M\216\365NYI\357LY\361Q\3130#\255gI\367\263T\256\245\301\252\206_z\255=\306\001\2522\\{\324\017.j\007\270+\336\2423S\014\3314<\231\247\t~P*\252\265+\363MF(q\332\254+\344b\222H\367\340\212\221S\312On\364\325X\335\263\234T\253\002\027\300jS\010\031\301\240/\245.8\246\322\203\212pj\221Z\247F\251\321\352\3029\253\010\365b7\251\321\352\304m\232\230\032Pi\340\322\206\247\003OV\247\253\324\212\302\244S\357R)\251\024\324\212jU54uf3\201P\314y&\240\rF\352ru\253P\365\025i[\232\261\023sI75FS\203U\335\361U\344\224Uw\226\253<\235j\t$\315V\222\\UW\224\372\324\022N\243\251\252s\310\037\241\2522\311\266\251K!cT.\034\241\366\252\206\350\223\214\323^l\367\250\374\332\003\374\302\225\245\311\305=^\253$\206\246\rO\n\010\3159x5<d\032.Fb\317\245VCR\2519\251\220\324\310H<R\036\264\201A\007&\233IR)\251P\343\275L\215V\021\252\3025XF\253\010\325b6#\275N\255\353R/JZ\\\323\201\247\003OSR/\"\236\271\251\024\343\275H\255R\243T\312\325<n*u~*)rG\265D\330\n1\326\230\rH\207\006\255\302j\302\266qS#\342\235#\344U\031\001$\232\251?\002\250I&\rD\362qU\244z\211\237\216\265]\316j\254\306\263\346\311\315T\336w\020MT\232n\242\251\274\270\250eu\221qT$P\246\240w\346\2422b\221&\3114\364\223\'&\244\023r9\244QNV\315M\03152\216)A\301\247\013\200r\270\315E\036\014\234\360*V\001[\203\221NSS+R\236i)\247\212ni\312y\251\024\324\350jt5f3V\022\254Fjt5a\rL\000\247\n)FE8S\301\247\203R)4\360i\352\325\"\275J\257R\253\036\3252\313N\337\232\211\371=)*D\353VP\340S\322J\235d\310\244iqP\3138\307\025\237q-g\313\'&\240y\260=\352\253\313U\336b*\007\270\"\242y\362j\007 \203TfLd\326d\3142y\2523>*\017;\006\241\270\231qU\313\202*\254\362m\250b\233\nNi\r\336\323\326\235\366\325\306s\212\323\'\212E\353Vc\"\245-\201B\234\232l\301c\005\325\271\364\250\222r\307\236ju9\025\"\361R\003\212p4\244\323I\3056\201S%XAV#\025b1Vc\025:\n\235*x\352pqJ\016iiA\247S\205<\032x\247\npjplS\325\352U\227\025(\227=i|\332\221f\035\372\322\202\t\366\251\020\202jRr:\323\203Pf\333Q\265\3005ZI}\352\254\322qY\357)\334y\250\036Nj\006qUg\227\035*\233\317\305D&\311\244y\260\246\263o.\372\340\326i\223$\222j\264\355\270\361T\246r*\253\312sL\373F*\033\2517.j\262?\313QJ\3715\013\276k\253lb\231\273\024\0079\342\254\304\333\206\rK\031\302\232\2531$\323c\342\255F\374T\241\251\340\323\301\245\'\212m&iW\255O\030\253(8\251\320U\230\305XAS\255L\2652\034T\241\251\352i\324R\346\224\032z\232\224\032p>\364\264f\22458==d\247\211(\363)VB:\032\267\014\274\034\325\210\316\341\232\221\210\305T\225\215@\315\212\215\344\305T\236^\rQy95\013\275Wv#\2750\270#\232\314\272\0041+\322\252\375\240 9<\325+\215H\356*\005T\226b\340\223U<\302N)\032@\240\216\365RV\006\252I\212\254\347\232\212y?vEB\033b\n\256\362f\243\004\261\342\272\243&ED\316j\315\277)\315O\033\014\324\241\360H\250\231r3Q\201\212\221\rN\206\246\036\264\3523E%=*d\253\010jx\315Y\214\325\224\251\226\246CR\251\251\026\234\016)\340\323\251G4\270\245\035i\340\342\234\r.\352]\324n\243u8=.\372P\365\"6M\\\214\345*x\237\024\347s\216\265VI\016j\026z\257+\361T\346\222\251\310\370\250\036Z\211\245\025VYOj\253$\236\265\233t\303$\212\317b2Oz\206I{Sc\3062j\264\262nbj\253\311P3f\241sU&l\021\236\225VY\263L\211L\257\212\235b\362\230\267\030\025\267\270\346\236\207\'\232\265\023v\251q\212p\342\2279Za\024\345\343\0252v\251\20585.E-(\247\255J\242\247AS\240\253\021\212\260\225a*d\025.)\353\300\247\323\2274\361\323\232z\221\267\030\346\212)\331\2434g\336\227w\275\033\275\350\335K\272\200\324\354\324\210j\344\022`sV\024\214\214T\256\271\025RU\252\3561Ue5RS\326\250\312\3705Y\332\240y8\252\356\370\353U%\223\255T\220n\316k6\340\024$\325u\220\022sM\236m\261\234U(\344\335\232\255<\330b\005WiM7\314\252\367\r\236j\263`\212|\014\250\030\236\275\252\031&\'\275uACt\246\260\332jH\233\346\253@\346\206c\212[bYM8\241\305 \310\251P\342\246V\035M\033\275\351sN\006\236\r=jd\025:U\230\305X\214U\204\025<b\247Q\212}8t\247\251\247\203\232x\247\321IFh\310\243u&\3527R\356\245\006\236\r:\236\231\251\320\325\270\371PjTs\212G\031\025VU\300\2523w\252s\036\rfJy5Y\337\025]\344\252\362\276zU)_\232\205\245\305T\237\347\310\254\306\006)=\251\2679h\270\252\221\222\212j\254\331,j\006\244\311\002\241\235\200CU\026O\222\241iqQ\264\265\330\305.\rJW\177Jr)SS\241\315HFV\235n\273G\326\254\001\217\245B\300\006\240\032x>\364\340sO\035)\303\245=zT\252*d\253\021\212\263\032\325\230\326\254\"\232\235\005N\264\240S\251GZz\323\307J\2202\221\3374g\035\3512\t\346\230H\024\233\250\335\357I\237z\\\373\322\356\245\006\236\246\244SR-J\207\025j\t\007CS.3O$UK\247\000VT\322\365\252SK\326\250J\331\315S\225\360j\0079\025^F\300\252s\266G\025Q\236\243sT\256F\356i\222\250X9\353\212\244\334(\315W\224\014\032\254j9\037\n}\253.\346\353p\"\241\363p\265\013I\232az\354\022J\263\014\265q\030=L\251R\205\315K\034x\253\n\237-G$\031\007\332\253\343\232)\340\324\213OZ\221F*D\353V#\025f1V\342L\325\224Z\235\026\247AS*\346\244\362\3516b\214c\2658\np\024\341\301\344P}\251\244\361M\240\250 \234\323i2(\310\240585=Z\244W\251U\352El\324\361\234T\242o\232\234\322\340\032\313\273\270$\232\241,\265Ji*\244\222U\t\345\303T\006\343\025\004\323dqP\026\371rj\224\357\363qLV\315#\000x\250f\210\273\001\236\007Z\255z\241S\212\315s\225\252\316qUn\030\3545\227(\307Z\205\237\002\242/Lg\256\301N\rX\215\361W \222\264\241;\226\246P\001\251\343\346\254*\322\262qT\'\\\034\016\265\030\007\2759y\372T\312)\343\212\221jd\025b0*\324C5r!VcZ\260\211S*b\247D\251vqI\266\223fM/\227J\022\227m&)\n\232n\337jk\'qLe\033s\236j<\320Z\223u(jpzxz\221d\305L\222\212\23698\315#K\363u\242I\276J\313\271\233\236\265NI*\234\322\325Y%\2523\266MT\227\201\234\325S>\323\203\322\225\344\3711T\244\311$\212\215$&M\264\263\315\261\366\216\265\013\271\343\232\216F\005pk>d\303\022:U9\0178\252\263q\326\263\256OZ\246\347\025\021j\215\232\2735j\235\r\\\267<\326\234\r\300\253\000\324\350\010\253qr)\354x\305g\310\271ri\233M*\255J\242\236\253\315H\253S*\324\361\245Z\211*\334@\325\310\252\302T\351V#\034T\233ivRl\305\033h\305\033h\333F\332\nTl\225\013G\234\342\242\"\231I\234Q\272\223u.\372p\222\244V5<R\340sQ\311>_\255#Jvu\254\351\244\301<\325i%\342\250O5R\226|w\252\257q\223\326\242\222_\227\255g\314\344\032h\234\236*d )\315V_\221\230\323H\347\'\2551\333\212\245;\222x\250\310,\247\326\252N\274V|\355\220k:w\316j\224\207\346\250Y\2522\330\256\3166\2531\265\\\200\340\326\214-\322\256\247&\254\241\342\247C\212s\034)5T\214\2326\212P\224\360\264\365J\231V\246D\251\321*\314kV\243\253q\016\225eV\247D\025*%L\252i\341H\243\024\205A\246\355\305\033iz\322\355\243m!L\324\016\204\036:\325wB\016\rB\303\006\230E5\251\273\2517S\225\252\334X\305$\322m\034U\027\237\347\251\032o\226\251K&I\252w\023`Vl\363f\251O\'\025A\347 \323|\362sQJ\373\205F\277z\247/\205\3050\362*)X\250\252\357\'\030\252\356\016\352\225W\013T\356G&\262g_\230\251\357Y\323\214\022*\224\243\006\253;TL\325\331\304rj\354c\212\267o\326\264c8\305^\205\262\005YS\3058I\212V\237\345\3069\246.M8\n\221\0275(Jz\245J\211S\"\342\246AV#\025f5\253q\n\265\030\251\321jeZ\231V\245\013\3054\257\265!AL(i\010\366\244\344\036\224\340i\303\232\n\324N*\274\313\3115]\327\035j\0228\2460\250\310\305%.\340\242\236\227\033GZ\202k\222\347\212\257\277\232I&\371z\325)n1T\'\271\344\363T\245\234U9g\315S\222L\232a\223\024\335\371\247\240\311\251dC\362\212L\342\240\27095Q\372\322\355\310\247\014\342\252J>bk2\365pr+.a\270\346\251\\\014\032\246\365\003\232\355\241\\\234\325\344 \014U\250[\232\320\210\356\305]\207\212\2348<\003B\276\016EK\2748\347\255>\"\000\301\025 \003\0359\247 \251\200\247*\324\312\265*\255O\032\325\204Z\263\030\253Q\212\263\030\253\010*d\025:/\024\355\264b\220\212M\264\322\264\233h\332)v\320A\2462\373T\016\231\250\036<T\014\225\023-FV\242\221\266\n\204\226sH\347j\340T@\344qLr@\367\252\322\310T\363T.\246\311\254\351\245\343\255S\222CU\336L\016j\264\222\344\346\243\337\223R\'5n\33170\253\206\034\271\317\245V\2256\223T\3465U\2014\344V\003\024\345\004\256\005C,{3\232\313\274\301\315e\272rk6\344\362j\243s\232\202A]\324\021\032\265\034E\215ZH\366\343\232\273\t\332957\33200*E\220\340\037Z\221\036\254F\331\251\222\247SR\250\251PT\250\2252%L\251R\242\373T\350\rX\215sVc\025f1VPT\350*tSN\"\223\024\204SH\244\"\222\212Pii\n\360j6@{T/\036:T\017\025B\361\324\017\035Wh\3135#\247\224\2759\252\3547\nE\213\034\323&^+>\353\245d\\\023\223Y\323\271\031\252\215/5Zyrj\276\372\003d\325\250\252\375\257\336\025\244S\253\037\245Q\270\035k>^sP\221\315;\034dQ\007\315\223\336\241\274<\032\307\270\311\006\250\260\3005\221x0\306\251\023QHx\257DQ\264S\367\355\036\364\251!\315ZI2*\304m\223\315Z\215\227\025:\000OJ\2368\352\302Fjd\210\325\204\204\232\235-\330\366\251\222\335\207j\235b#\265M\032\324\252\2652(\251\321\005Y\215*\302GS\252\221\332\245Nj\300^)i\010\315!\030\246\221LaIE\000\342\235I\232C\3151\2074\306J\205\342\357P\264y=*\'\203\0075V\3423\221Q$9\346\234\352\000\252S\234\n\313\271nk*\355\266\346\262g\223vj\214\255\212\254\304\223M\3159:\325\270\273V\215\262`\203Z|\025\353Y\327\'\004\325\t\0075\0163S\"\322*lb}j\255\3379\254\213\232\2417\025B\3557\214\326\\\243i5\003\362+\320\311\246y\233\232\245V\007\030\2531\032\263\031\2531\366\253q\034U\270\315Z\214\325\250\305Y\214U\270\205YE\366\251\202\003\332\245XA\035)\3020;S\204@\2368\251\02221\320\324\350B\036x\253q`\343\006\254\205\342\200\274\360)\340\221K\272\2274\023M\244\"\230E%\024\233\250\335I\232Jku\244#<TN\273j)\010\306*\244\343\212\213\225\025\034\234\212\317\272l\003YS\234\234\326M\351\313qY\263\r\243\025JE\346\241)Q0\305I\020\253q\016Eh\333rEZ\230\224\000\216\225Rnj\254\221\325v\004\032\232\026\r\305LP2\232\315\273]\254A\254\253\232\316\234pj\233\363\305g^E\264\346\2507\031\256\361\346\3340(\214f\254F\265m>\225j5\346\255F\265f45n(\317\025n(\315]\2123\351V\342\210\325\250\343>\225a\027\025:\216\225*\375)\312\005H\020\032P\n\3644\365s\236EX\216H\317\261\253*\343\03404\345\223\035i\336b\372\323\201\007\275;\002\232h\244\335M$Si\010\3156\212nh\335FsH\033\006\222w\363z\016\225U\227\232\257$e\237\332\221\220\n\255?\265f]\214\346\263&\004f\262\256G\'\326\263\345OZ\251*\324\0148\250\331)cZ\271\n\347\025z\003\203S\264\243\034\364\252\362\020zT}j\t\020f\236\211\261I\3074+aj\205\313n\'5\227rk>a\232\245\"b\252\334G\275k.x\366\223]z\034\325\230\205[\210\n\267\030\025j *\344X\253\221v\253\220\363W\"Z\271\022\325\330\227\212\231W\232\231R\244Q\212\221x\247\001O\031\024\340M8r)@\305H=\252dcS\252\206\352)\342\021\333\212_,\216\206\215\314)7\373P\010&\202\264\306Zg\"\2234\264\323\326\233\272\202\324\224\344p\200\344g5\003u5\031\0315\024\243\212\2437\031\252\027\0039\252\022\256A\342\263.!\316k6\342<\003T\035}j\022\271lP\321\023\332\204\204\203V\342L\n\231\001\006\245\306EU\231\014-\317\3354\003Q1;\263R\031r\017\035j \374\021Tn\233\004\326d\252\\\325YV\253:f\240x\353>\356\014\346\267\342\030\253q\325\230\216*\324mW\"9\253\220\366\253\261\n\271\022\325\350E]\204U\310\370\0252\324\213R\257j\221zS\300\247\205\315H\251\305<\'5 \214b\234\261T\2411R\242\212\224-)\035\2501f\232c>\224\337*\232\312V\231\235\335\251\254\264\322)1\216\224\3265\033\037J\024\022zT\351\016{P\360\342\231\366r\352H\355QI\026\301\232\201\307\031\252s\307\2735JXx\252S\333\221\232\317\236\034\003Y\227\020\3475\2374\030\252\342\334\356\251D<R\210\361\332\234\027\035\251W\255L\244m\250\256Hd\252\3528\024\254\243\025\023\0361Qn\306j\234\377\00005Q\327\002\252J\225U\306\rD\313\305S\2353Z\250j\304mV\2429\253q\n\271\r^\206\256\302j\3645v*\273\025YC\221S\245J\242\245QR\252\361R*sS\"\324\241qJ\026\244\002\244E\251\002\324\212\265 J\236+\1770\212\265\366 \0055\355F8\250M\276;T2C\2361L\026\274\322=\276:\212\206H1\322\242\362\352)\023\025\020^j\3241\003VxE\250\230\356\244Q\203\355QM\036\366<`Uw\207o\025\003\300\016j\254\220\001Tn#\034\326u\324Ca\254Y\223\223T\344L\323L<f\231\267\024\322\270\246\236)\200\344\324\231\342\230\374\212\215W\232R\274Uw\034T\'\241\250LY\036\365^X\261Tf\\f\250\315\305D\016x\250&Z\270\2075f>*\334F\255\304j\344&\256\303W\341\253\320\325\350G5y:\n\261\030\342\246J\231F*T\342\247J\231\005L\213S\004\310\243m<-H\253R\204\315J\211Vb\200\261\025z\030\202\017z\230/\255F\353Q\262qP\264`\232<\241\216\224\206<\212\211\340\007\265D\326\240\347\025NkV\007\245W08=*h\325\224r)\035\213\032thH\251B\361\357@L\347\"\253O\037&\251\312\244UY\006j\214\311\221T.\027*k\036\346.MQh\360h\331\217\245F\321\363QJ\233EVc\315\010\234\323\372\034b\230\353\305G\323\255)pF*\'\035j\000\235\375\351vc<Ui\200\316*\205\314c\232\312\270\\5A\322\243\232\247\210\363V\220\325\270\215[\210\325\330\215^\203\265_\207\265^\206\264!\342\255\307\332\254!\251\320\324\351\315J\243\232\235G\025<B\254*\361\236\325*t\247\005\315=R\244T\251\243\210\261\000U\350,\330\365\025u \013\300\251\026#\232q\212\230\351Q:\361P\221\203I\232F\250\316I\244\0242\206\250\214#\322\232\320f\243kp)\2416\323OZa|S\034\206\353\326\253J\242\251\313\037\025B\340c5\2378\3105\233q\036sT\236\016i\246\034\n\215\243\305W\235;U\177\'\236\224\361\026*7\\S\033$TL\224\300\2314\346\217#\336\230\261eM\006>\243\275T\236-\2475\237q\320\212\312\236>j\273\246*\264\274\324\261\032\265\021\351W\"=*\344Uv\021W\340\035+B\036*\354,\005]\212AVRQVR\\\324\350\365<njtsV\021\252x\316j\314f\247\000\036\203\212r\212\225\0075a\0235\245cl\016I\037J\270\340F\271\024\220\363\315ZA\232s(\306j\244\307\232\205\2335\013\232`\351Q\263b\233\277\236\264\315\374\232z\267\024n\244\335\336\242i)\243\004T.\274\361Q=D\334\324\017\232\253+\366\252\027\025BB9\252\262\340\325V\000\232\215\3005\013\245@b\311\311\250\331\000\250\235qQ0\315F\334\212c/\024\320\234\324\210\233\273Pb\332=\2526\\\034\324S\333\371\200\342\262n *NEf\\&\t\252R-B\361\346\231\025Y\214\325\330NqWa\311\351W\340RqW\340\\c&\256G\326\255\306\3358\2531\265Y\214\325\230\332\255F\371\253\021\232\263\035YJ\235*\314dq\236\225:\310\0008\024\370\311\317Z\271o\036\343\232\321\206\005\034\221WS\n8\247:\356QND\332)\376f(ir*\264\255\326\240\'5\033f\230O\025\023w\246\023\212\211\211\006\236\217N-L-M\004n\346\244b\255\320ST\001PN\240\347\025M\306*\007\006\253J*\234\353\234\325\031c\305S\232#\332\252:\221Q\363J@\"\242a\232\202E\301\250\031sP\272\363\201H#\3074\326J\210.MX\211v\216G4\256\242\242d\3108\355Q\343)\356+:\351wg5\221<<\232\251$\0315^HqT\2435n%\315^\202>\225\243\002\216:\325\330\206:\n\267\035Z\216\254\306\325f3Vb5j3Vc\253QU\250\352\302\n\262\202\245\251\020\032\235\026\264\254\262@\317j\321\007\006\246\214\346\254\306Aa\355R\270\030\342\240aL<T\017\311\246\205\024\327\025\013\212a\031\250\330Tl3Q\234\255\014\374TFS\232z\266i\333\261F\352\216C\221U\237\212\202CU\245\3475VE\315U\221*\254\221\346\252\311\016j\006\203\006\230c\250\3311P\272f\240e\342\242\362\262i\306<\n\206D\300\246\307\037\255LW\002\231\267u\033\006q\336\253\315\031Q\221Tf\217x&\250\315\005Tx;\325ya\366\2548G5z\025\253\360\n\277\027n\365e\032\254\244\200T\361\311VcsV\242j\267\021\315Z\213\234U\310\205\\\211j\334IV\243Z\235EJ\213\232\235\022\246E\305iY\'\313\236\325p)\251\200#\024\365%[4\377\0004\322\356\310\246=@\375i\244\323j6\250\351\030TL\005F\342\243n\225\003\002)\003b\236\257\232~\354\212\215\332\240z\257!\"\253\271\315@\342\240\220f\253\262\365\252\356\274\324f,\323\0145\014\220\361U\344\210\366\250L4\242\034\014\324o\035W\222<\232\025)\2547\034t\245\n{\nk\304B\356\250\316YH\305Pt\303\032\205\300n\242\253I\020\002\251M\0315\315@\247\255]\204\036*\3649\030\253q\261\372T\350y\2531\373\325\250\315Z\210\325\270\252\344C5v\025\253\260\255]\205j\344IVQ*eJ\231\022\254\"U\210\342\315hZ\241E\031\253j@\251\001\3159\223\345\250\263N\335\212k>j2y\246\232\026\243\225qU\331\2151\244\305D\317\232M\304\212k\034\n\210\363Q\260\244\034T\200\366\241\206j\027\030\252\262\234\325s\326\243qP\260\250\235r*\273\'4\242,\322yx\250\246\210v\252\257\025Dc\2464x\250Yq\232\201\327&\230F*23N\217\255:E,\244\016\225\023(\331\323\232\243*rj\263\240\252\263\014\n\243) \327=\004y\306j\344K\201VR\254FsV#\253Q\325\230\352\334@\325\330V\257B\265z\025\351W\242\214\325\350c\253\320\307Wm\355\214\207\000sV\227O}\273\261\3054DV\246\215*\324+\202+B,m\306)TsS\306\274\322\316\301V\252o\346\227}5\236\232M &\224\023\232q\301\025\023F\rA$5\001\210\320#\246J\270\250\266\363H\313\305 JpJ\n\324\022\212\254\353\232\201\226\242aP\270\250\310\3151\323\2759\027\212k.\rF\351\232\253$}j#\036\005F\313\305@\361\324\014\225\023%F\321\320\243\006\236\352v\344Tl7Fj\214\211\315@\351T\347CTe\2175\316\304\274U\244\251\320U\230\305Z\214U\230\226\256D\225r$\351W\241N\225z\030\363\212\320\202.\225~\030\352\374\021U\350\243\253\266\361\266x\025\253bwe\037\246*\013\250\224>\027\2455#\251UqV`\0370\253[0\325\"/5\005\331\311\305T`E\007\2323\212\001\315.\354R\007\247\006\315)\031\246\224\3154\3061Q2c\265A*f\243\t\212B\224\2339\251R-\302\207\217\002\253I\035Wx\352\274\221\342\253:\221P\270\250\2623N\333\225\241W\024:\324%qQ\224\034\324\016\225\003\255D\351\221PI\035B\311Q\262SJc\232Uc\223\216\206\223\313 \032\243*\362j\274\213\201Ud\\\325Ya\256a\027\212\231\005XA\212\261\030\253q.j\334IW\240\2175v\030\372U\370\"\351Z0EW\341\212\257\303\025\\\2111W`NEk\306\321\305\020\332>jH\344*\305\207ZR7}h\306)A\305K\013\374\302\264@\310\006\246E\3435Zd\005\252&\210b\2421\342\230W\024\323\322\230x\243<R\203R\241\251\002\003G\227\315\r\0105ZX}\252\023\026)\214\224\322\2705b\025\364\244\225\000\252\256\225Y\327\223P\272\346\253\274|\364\250d\207\"\252\264<\321\215\242\200\300\032S\206\250\231i\205*\027^j\t\023\232\211\227\212\205\326\242x\352\006CM\332H\305\"C\264\340S\244\033\024\344\326{\216I5VNj\035\2314I\006\361\\J\221\305L\200\232\263\032\325\250\226\256D\265r\024\255\010\022\257B\231\255\010#\351Z0G\322\264a\216\256F\225j1V\242\025iML\225(\300\024\204\322u\253\026\361\234\346\264#<sR\356\342\243h\367sLh\361Q:T.\270\250\210\246\221F8\246\205\346\254F\243\025 \342\244\373\300R\201\223L\222 j\007\213\025\013\305P\262b\244\210b\225\327\"\253\312\265U\326\242)\232O\'4\215jXt\252\323\332l\\\221Td\\\032\213nh\013\212\010\315&\336*\t\027\232\205\3075\013-F\311\236\325\023\257\025\013&i\205q@\343\232\255p\305\2115U\324\232\256\351LX\371\247\272mZ\340#J\263\032\325\230\226\255\304\265n%\253\260\255^\205kB\005\255+t\351ZP%hB\225j4\251\320b\254#b\247V\251\025\352O2\223}>6\347\232\321\203\224\030\253\261\307\362\203O\362\301\247\210\370\246I\037\025]\327\025\013\255@\343\025\013\032g\231\212@\374\324\350\374T\212\331\251\307JQ\326\237\267\"\230S\232k\303\306j\273\303\232\0048\355Lu\305@\351U\235*!\0375<1naWV\004\nF*\245\375\250\021\203\212\347\247\213\014j\r\230\243\034\320S4\335\270\250\244^j\274\211Q\024\246\025\305D\353\315D\311Lh\267\n\207i\\\346\253\310\274\324.\234UvL\322l\301\024\331\311\333\322\2708\326\254F\225j5\253Q%\\\211*\344IW\240^\225\241n\275+R\335:V\225\272t\253\361/\025`\014\npj\221Z\246V\251\224\346\244\024\241I\251\021\ri\331\203\214\032\323\211r)\342,\032\224\307\201PH*\273)\346\2538\250\035MD\361T&>i6\342\234\032\246\214\325\204jx=*d\"\234\024\023S\254*\313\212\202K`\246\242h\270\342\253I\0275\004\253\201U\\d\323\002\202j\314q\343\245I\222\005E;\371\211\264\3265\324\0305BU\305C\212\226%\315$\221\343\232\254\342\241qQ\025\2441dT2GQl\241\227j\3259:\324%*\031\026\242\362\311\355\223J  \344\216j\265\301\3005\303F\225j4\253Q\307V\243J\267\022U\310c\253\360G\322\264 \217\245i[\2461Z0\214U\270\316*PsJ\rJ\200\232\235\026\254 \251Tf\247H\372U\230\340\253pb3V\322M\2475v9C\257\275+\214\324L\225\023\307U\244\217\025Y\327\232\210\256i\014\\TM\0350\256\332P\370\247\t\200\247\t\375\352xe\335V\3435`8\244f\004S\n\361\232\2551P=\352\224\203q5^H\352\014m5b\027\251_\025\013\216*\205\322\203Y\223\2463Uq\315O\025,\203\"\252\272T\016\230\250\310\031\246\362)\214\271\250\335)\222\017\224\325\027NMD\302\2431\367\247A\021/\2228\024\223\234f\262/\017&\271\030\343\253q\307Vc\216\255D\225n(\352\354Q\325\350c\255\010\023\245_\204c\025r#V\223\232\231V\244T\251\221jd\253\010*x\3235n\030\371\025u\"\300\246I\362\232\221$\014\005Y\204\221W\003ei\303\024\216\231\025VHI\252\262C\212\210\305\315)\217\212\215\242\252\322\245Wc\212g&\200H\251\242\224\255[\212\344\325\204\234c\255#\\\320n2:\324,\333\252#\336\241\220T,\224\370\306\rJ\303\"\253\311T\346\2523.j\233\246\r:.\264\347n\325\013s\315E\"\361U\312\322\025\246\204\311\246\310\240T\017\367j\243\2554G\232>\316[\223\322\225\300Q\305P\270<\232\312\272\3475\315\307\035Z\215*\314q\325\270\243\253qGW\"\216\256\304\265v\025\253\221)\253\221\n\271\022\325\204J\235#\251\226*\221b\305L\213V\"\025r\034\n\270\235)\257\021jjZ9\345A\342\254\301\033\257QW\021x\245\034\032V4\3020:T\022&\356\324\317\'\332\203nj)\"\305T\226\022{UW\203\232g\227\212aA\232zGO\306\332]\370\024\320\345\215;q\245\004\232p\031\244d\315Fc\366\244\331R\252\374\265ZU\301\252\262\2409\252\215\0375Rd\301\250\033\216\225\03350>x\244c\232\214\2574\306\030\244\003&\243\225N3U\034f\243a\3055T\361R\221\300\250&\\\212\317\235z\326e\312pk\0068\352\314q\325\270\243\366\253qEW\"\213\245[\212*\267\024Ur(\352\344Q\325\270\242\253\260\307V\222:\263\034u2\240\247m\024\0059\251\343Z\265\020\253\326\343,\005jG\024HG\2329>\224Ks\0349X\200\301\247\333J\222\215\254\243\'\245+\305\260\220E0*\347\255J\226\373\251^\305\207A\3050Y7qN\026\'\322\221\255qU\345\266\036\225VX8\351T\345\267\366\252\222@Gj\207\313\"\235\214\nc57w\024\320qJ\037\232\23004\360x\247\250\240\257\2555\224\na<UiMV\220Uy\005A\"dU\031\327m@y\025\033.)\024\342\234\016\323M\220\206\034R*qQM\351U\212d\323\014D\366\243\313\305\016\265^AT\346\\\326u\304<\032\301\211A\025n(\252\334Q{U\310\242\253\260\303W\"\206\255\305\r[\212\032\267\0145v(3W\"\202\254\254\\T\252\230\251B\323\202S\326>jt\216\247H\361V\"\005Nj\300b\375ML\260\344\003\232\236(0A\025snW\r\311\365\252\262BA\342\256Y\302\315\301\342\265a\2678\033\260jf\264A\332\242\222\327\035\005T\222\337\332\253Kl\007j\250\366\303\322\253Km\355U$\200zUg\267\317j\257,\033j\244\211Q\020i\2704\241NjdZ\235R\236\006\r5\316*=\324\323P\3123U\335j\273/4\307N*\234\361dU6\217\024\306\346\233\266\232A\241c4\366]\213U\\d\324{9\315?\003\030\3051\327\333\212\211\326\253H\271\252\262\246*\235\302\022+\002\030\261Wa\2175v(j\344Q{U\310b\253\221EWb\212\256E\r\\\212\n\273\014\025n8ju\204\267AO6\345z\212r\305N\331\212UZ\261\032U\204J\235#\251V<T\250\010\2531I\212\224\311\350z\320\030\016MX\267\230\036\007Z\270\222g\275O\034\356\247\332\256+\254\253\310\346\241\226\005\365\252\222\333\346\253<\003\035*\254\326\346\250\313\t\007\245B\320\373Uy`\3105Bx0zUW\213\006\231\262\236\221\346\247H\252B1I\263uD\351\212\205\206)\233\351\254sQ\262dUgB\246\242\366\250dPj\264\320\361\232\252\321\340\320S\"\230\027\232\225Pc5\034\252_8\355U\374\274\232x\203\214\322:\n\211\223\216*\007^MU\225qUe<UINA\254x\242\366\253\220\305W\341\212\256E\025\\\212\032\273\0245v\030}\252\3640{U\330\240\351V\342\207\025e\"\366\251\243]\2548\251g-#\014\214b\231\266\220\246i\311\035XE\253P\307\270\325\330\355\263V\340\262V\373\304\nl\266\241[\003\245D\321\354\245V\000S$\220\221\212\201.L2\002zV\225\275\350|sW#\274_Z\261\036\240\212y5an\343\233\2750\220r*6\\\3242G\232\255$\000\366\252\362[\016\325VX=\253>\346\014v\254\351\223\006\253\221\315=3Vc\007\024\342\264\322q\332\243\220\223PI\232\200\212A\326\247H\303\n\206{|\003\305P\2316\346\253\263\001Q\263\202*\254\230\025\036\341M\030\316jBA\000\n\225\341\002>:\324V\361\002\016{S%;x\035*\273\036*3\322\242qU\245\\V}\300\301\252R6\rT\202>\325z(\252\3641U\350a\253\260\301W\241\203\245^\206\017j\273\0245n8\252\324qU\224\213\212\225\"\303\002EXh\004\213\223\301\250V\337\232q\267\3057\312\305H\261\325\250S\025z6\300\251\323s\036*R\017z\215\342,=\251\277c$t\246KhS\255W{\\\216\224\301\026\316\224\276a\035\315><\261\344\325\373gT u5tJ\r/\233\315!|\212\215\230TN\001\035*\254\213T\356#\r\332\250\265\201\225\250\032H\006\206\323\366\364\024\323o\266\230a4\326\207\025\013GQI\017\025VE\301\250X\355\246\264\344\000A\245mDm\303b\262\257\257W\007\007\232\307\233QnEW\373{\223\326\217\2653w\253\020\266\376\365`BH\240\002\247\232\233vF)\3100\325\014\311\315U\221qP1\250]\261U\345z\245q\212\315\230\363N\267\217 qZ0\307\232\275\0145z\030zU\370a\366\253\320\301\322\257E\017\265\\\212\032\267\024\036\325j8=\252\324p\324\353o\237\245;\310\305\006 \242\231\260\236\324y\024\341\016;T\210\270\251\323\025n\027\013VW\347\035*)\"`iV9\010\366\242K\031vn\0075\002\333H\307\232\235l\262>aQI\246\340\344t\246\013<\037J\236;`\247\203V\026\006\3052H\212\212\213\004t&\2170\216\2434y\353\350i\246D=qQ\262\307\327\"\232\002\036\206\220\240\250\331\001\252\362@\t\250\374\237j\215\343\366\250\014\0315Z\350yB\263\234\344\364\250%\340V}\314\373\001\346\262no\260O5\235-\303\312p*\007\215\310\316*\214\322\264MDW\2315\243mq\310\346\265 \270\310\305<\276M\000\212\224)\312\236\324\351Tb\251\312:\325Y\024\212\251!\305U\221\252\234\315Y\363\003\232\267n\230\305i\300\235+J\010\263\212\277\014=8\253\360\303\355W\341\206\256\303\007J\273\0245n(j\324p\325\210\340\315N\260\363\212\224A\221M6\371\355J-=\251~\317\212g\221\232i\200\366\245X\rX\212\"*\314@\251\253\237)^@\252s\273\016\235*8\256\335\0163\305Z\213\347 \346\257\242\002\007\024\343\002\267j\202[%=*\017\262\025<T\211\023}(\221@\3523P\264@\366\250\036,t\250\232#Q4u\023GL)\212o\314;\232Uc\236i\305i\nf\243xr\016\005E\034`\267\314+3T\\H@\355Yn9\250f\031J\304\276V\346\261e\201\345n\001\255\r?J$\202\302\264\233FB\277t\346\271\375[D*\305\224\037\245s\322\332</\323\212\261\014\205@\255\033[\216@\315_G\335OS\223W\341]\310)\322BXf\252\274X\252\263\'\025\233p\270\315P\227\"\252Hj\244\325\241n\235+N\335:V\245\272\003Zv\361V\2040\372U\370a\366\253\321C\355W!\206\256E\016*\314p\347\265XHN*t\200\n\225a\315<[\346\245kr\253\234T&\003\214\221\212\022\001\212\036 \007\002\230\212\007\\Ps\236:S\205I\0319\301\242xs\315W[p[\223\212\277ol\251\374Y5m\006)\347\2450\344\367\244 z\324e\260x\246\222I\351Q\270>\225]\201\3154\220i\257\016j\273\304@\250\212\323\n\324\253\000\3056H\312Ty\243u <\325k\273\025\2343c\346\254\013\210<\246 \326u\313\355\006\251\224\022\347\201O\265\260I\033\356\367\255\213]9P\014\n\270lT\257J\316\277\322VE<s\\\216\265\243\030\325\230\n\304[\023\267$\021L\216\026I=\253z\307N{\224\312\236i\362\330\311n~aVt\366\371\302\236\365zX\212\347\320\3259R\251\316\231\025\231q\037Z\316\2351\232\317\227\202j\224\307\255l\333\247J\323\267J\323\267N\225\251l\275+N\005\034b\264`\217\245hC\035\\\212/J\273\024\036\325e!\251\322*\231!\'\265N\220z\212\231a\031\036\224\347UQ\301\250\035\001\250\212`Sv\2029\246\030\262h1c\2650\214R\031\n\363Q\265\303\023\357L\334\304\344\232\265h\3377\'\351Z\221\003\212yPi\n(\353PH\200\237\224\324a\010\247\034\001Lf\030\250\037\035\252\027S\236)\231\"\243\221\313pzTG\203M\"\254\304\240\201M\2352x\252\345)\273\001\247\010h1\355RH\254\035J1+\235\242\262&\262<\344UAj\310\304\3665r\312\334\253f\266\355\342\340f\246+\201\357PI\030n\010\254\313\3355g\004b\263\016\206\212\245v\214VM\316\204#\223**\355\205\234\220\214\201\322\247\234\t\024\207^k;\3101L\010\351\232\320g,\2035R`*\224\3035\237:u\254\313\224\306x\254\273\201\214\326l\355\326\272;t\351ZV\353ZV\353\322\264\355\326\264\355\327\245i@+B\004\3163Z\020\247J\277\014y\253q\305\355V\022\020{U\230\341\036\2258\200\n\0320\017\024\306\217\216\225\031\216\242h\271\351Q\274x5\031S\232\010&\230\311\305B\321\346\233\345S\2049\253\020A\206\025\242\213\322\236\0234\331\027\002\253\225\346\224\364\353U\345p\017\035j=\244\321\261\215\006\023\216\225\024\221\365\252\254\2074\306\\Sq\232\232\020I\305:Q\203\212f\314\323\226\021K\345\343\250\244\221C\256+:\342\325\024\223Y7\210\t\342\241KA!\2531\330\355#\0258]\264\354R\025\342\242x\301\250Z\034\324\rb\256rE\037fT\\\001T\256\254w\253\021\326\262L\'v\010\344T\245p\270\250%\034r*\214\311\216\235*\234\311\305f\335G\301\254k\265\353Y3\257Z\352`^\225\245n\235+J\335s\212\323\267N\225\243n\275+F\005\351Z0\n\320\207\265_\204U\350V\256F\225:%J\253\305\014\271\246\024\250\312sM\331\223Lx\201\250\032,\032o\225Mhx\250X`\323\243\217y\251\214A\005:-\271\253\nFi\373\300\246I*\036\365\004\223\"\3645U\356\027\326\243\022\253\032\221gU\355N\373W\240\246\233\2064\306\311\352*2\202\230\320\026\357P\264e\r\021Hc|\324\314C\234\320\000\247\002\0075^[\241\234\nh\270\004`\325;\267\315eJ\204\232\265g\007\025x\3006\324M\020QQ\025\301\246\361\322\230z\322`S\031j6\025\003\014\036*\235\325\220\'z\212\250\320\340c\025Rh\372\3259c\252SG\326\250\\\306\0105\215y\017Z\307\270\213\232\351\255\327\030\255;t\316+N\335zV\214\tZ0\001\305h\300\265v\036\265~\001\310\255(EhB\275*\344C\245YU\247\205\305\005i\205i<\274\3224U\033.)\276P4\326\207\025\023\214qP\233}\3074\306_/\245V\232g=\3524\272e8\"\247k\345\211s\234\232\255.\252O@sQ.\244\375\3052[\3170t\246\304\301\271&\254+\014R\0319\247+\346\246N\225:\256E1\343\301\245X\363\212t\260.\334\232\315\234(o\226\231\347m\352qMk\260;\212\201\3577p\rG\2774\253\232VB\303\221I\3660\303\246*h\340\362\307\025&\334\323Y\0075^E^\325]\370\250\035\216x\351H\036\2279\250\330Tdu\250\332\240\226\020\3035B\342\0029\025BX\272\325)S\326\250\317\027Z\312\272\2039\342\262\'\267\311<W\377\331"
+byte_png: "\211PNG\r\n\032\n\000\000\000\rIHDR\000\000\002\000\000\000\002\000\010\000\000\000\000\321\023\213&\000\000\001JIDATx^\355\335!\016\300 \020EA\262\367\277rI\253*\276C4\024\230\221\317b\226d\t\255\001\000\000\000\000\000\000\000\000\000\000\000\254\2552\000\000\000\000\000\000\000\000\000\000\000\234\315\2325\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\\\031\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\030S\031\000\000\000\000\000\000\000\000\200_\260\333\003\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\323\371F\004\000\000\000\000\000\000\000\000\000\000\000\000\340\345\311\025\000\0000\203\273\010\3004=\003\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\313\250\014\000\000\354\313\360w0\207\017\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\360\251\312\260\271\236\001\000\000\000\0367\231\251\003\020\030\305\203\234\000\000\000\000IEND\256B`\202"
diff --git a/core/res/geoid_height_map_assets/tile-3.textpb b/core/res/geoid_height_map_assets/tile-3.textpb
index c147825..9abaaaa 100644
--- a/core/res/geoid_height_map_assets/tile-3.textpb
+++ b/core/res/geoid_height_map_assets/tile-3.textpb
@@ -1,3 +1,3 @@
 tile_key: "3"
-byte_jpeg: "\377\330\377\340\000\020JFIF\000\001\002\000\000\001\000\001\000\000\377\333\000C\000\004\003\003\003\003\002\004\003\003\003\004\004\004\004\005\t\006\005\005\005\005\013\010\010\007\t\r\014\016\016\r\014\r\r\017\020\025\022\017\020\024\020\r\r\022\031\022\024\026\026\027\030\027\016\022\032\034\032\027\033\025\027\027\027\377\300\000\013\010\002\000\002\000\001\001\021\000\377\304\000\037\000\000\001\005\001\001\001\001\001\001\000\000\000\000\000\000\000\000\001\002\003\004\005\006\007\010\t\n\013\377\304\000\265\020\000\002\001\003\003\002\004\003\005\005\004\004\000\000\001}\001\002\003\000\004\021\005\022!1A\006\023Qa\007\"q\0242\201\221\241\010#B\261\301\025R\321\360$3br\202\t\n\026\027\030\031\032%&\'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz\203\204\205\206\207\210\211\212\222\223\224\225\226\227\230\231\232\242\243\244\245\246\247\250\251\252\262\263\264\265\266\267\270\271\272\302\303\304\305\306\307\310\311\312\322\323\324\325\326\327\330\331\332\341\342\343\344\345\346\347\350\351\352\361\362\363\364\365\366\367\370\371\372\377\332\000\010\001\001\000\000?\000\367>\235(\335\317Zz\311\212\231d\2517\347\2751\316qP\261\250\330\324L\324\302\324\302\324\233\251\271\2434f\214\322\023M&\232Z\230^\220\2650\2650\2654\265F\315Q\261\250\213Tl\325\0235D\315Q3TL\325\0235D\315P\263T\014\325\013\275Wv\250\035\352\007z\201\236\240w\250]\352\006z\205\232\241f\250Y\2522\324\302\324\322\324\231\244-I\2323J\r.i\300\322\203O\006\236\r<\032\2324-W`\207=\005j[\300\027\006\275\334\2750\275(z\221d\251VOzs8\332>\264\307<\324Lj&5\tjijB\324\233\251s\357I\2327Q\232a4\302\324\302\324\315\364\205\251\205\251\205\251\205\251\214\325\013\032\211\236\243f\250\231\252&j\211\232\242f\250Y\352\026j\201\236\240w\250\035\352\273\275@\357U\331\352\026z\205\336\240w\250Y\370\250Y\2526j\214\2650\265!jB\324\233\275\350\335\357F}\351sK\272\234\r8\032x4\360jh\320\261\366\253\360\307\320b\264`\214\001\315^\213\257\025\354\276g\024\323%\001\352E\222\244\022{\323\304\231\025!`?*\215\273\324,y\250\030\363M&\2234\231\245\335Fi3HZ\232Z\243f\250\313S\013Ro\246\226\2463Tl\325\033=F\317Q3\324E\3526j\205\236\242g\250Y\352\026z\201\336\241w\252\356\365\003\275Ww\250\035\352\007z\205\236\241g\250\035\352\026j\215\233\232\214\2650\2650\265&\352Bi7P\032\215\324\273\251CS\201\247\203R\256jx\243,\325z(\272\014V\2041m\031\"\255\'Z\275\002\2168\257U\022{\321\346R\357\247\t)\342Ozz\311\357S,\231\247n\004~\025\023\324\014j<\321\2322)i3HZ\230Z\230Z\243f\250\313S\013R\026\244\335\221Lf\250\231\352&z\211\236\242g\250\231\352&\222\242i*&z\205\236\241w\250\035\352\007z\201\236\240w\250\035\352\273=@\357P\263\324,\365\013=D\315L-L&\230Z\230M&\354SK\323w\322n4\340I\251\002\223R\254f\244X\215L\220\237J\260\220\037J\271\014\030\034\212\273\014`\021V\366p1SE\0375\241\nb\275\024I\2327\347\275\'\231\216\246\234%\247\211=\351\313%L\222\373\324\353 8\311\244f\343\336\241cQ1\346\215\324\271\2434\204\323\013S\013S\013Tl\325\031jajn\352\003sLsP3\324,\365\023=D\317Q3\324L\365\013IQ4\225\013=B\317P<\225\013\275@\357P;\325wz\201\336\240w\250Y\352\026z\211\236\243-M/Q4\224\303\'\2753~{\320\r8)jx\205\261\322\224D{\212\225!>\225a!\035\352\302D\276\225a!^\302\247HG\245J\261\201\332\245A\315Y\211rE_\216<\212\265\0245r8\253\256YjA\'\275!z\003\323\204\224\361%J\262qS\244\276\365/\230\n\363Q\226\342\243c\3153u(zv\372ijajaj\214\265FZ\230Z\230Z\232Z\223~\r5\236\253\310\3309\252\354\365\023=D\317Q3\324,\365\013=B\317Q3\324,\365\003=B\357P;\324\016\365\003=@\317P\263\324,\365\013=D\316\005F\322\014u\250\213\222z\323K\344\360)\2474\000I\305Y\216\036\346\255\244\\p*_+\326\223f;S\302\n\225\02352&:\325\224Q\212\231G\024w\251\220U\310S\232\321\205*\354iV\343J\332V\367\247\253\340\323\367\323K\340\321\346S\326L\324\312\365*\311R\tx\247\007\033i\254x\250\313a\261@z7\322\027\246\227\246\026\250\313S\013Tl\324\302\324\322\324\302\364\326~*\027z\254\355\203P\263\324L\365\013=D\317P\263\324,\365\013\275B\317P\263\324\016\365\003\275@\357P\263\324,\365\0135B\357\201P4\237\205@\\\036\246\232Z\214T\213\036FM)@\005*(\335V\324\000\225n!\362\212\227\034R\004\317jp\217\332\236\213\265\272qS\205\335\322\246U\307\025*\255\001~j\261\032U\330W\025\241\0168\253\321(5m\026\256\206 sN\363)\302\\w\246\264\264\321\'5\"\311\357S,\225 \226\234%\251R^\324\375\371\034\364\250\334\374\324\315\364o\244/HZ\230Z\230^\230Z\243-L-L-L/Q\263\324,\365\003\276j\026z\205\236\242g\250Y\352\007z\205\236\242g\250Y\352\006z\205\336\240w\250\031\352\026z\211\232\241w\300\252\356\304\362j\027l\364\250\371\3158\nr\34358a\267\2550\276M*\036j\310<\001W`9\214T\340d\324\201i\341i\3733RD\270n\225g\313\357N\013\355OT9\351S\306\225n8\311\350*\334Q\277\241\253\360#\367\025~4=\352q\351Q\267\025\031\223\236\264\236e\002Jp\223\336\245YjA\'\2758KOY\275\352u\224\025\306h-\317ZilSw\321\272\220\2754\2750\275F^\230Z\243-M/Q\263\324,\365\013IP\263\324.\374T-%D\317Q\263\325wz\201\236\241g\250Y\352\026z\205\336\240f\250Y\352\"\325\0335Wrwgw\341Q9\'\251\250\215.\0062i)E<\036(\035jT\034\325\265L\246j\354\t\373\261VUjP\234S\202\232\235\"\251V,v\251BT\253\02052\302OAV\340\264f=+F+eA\310\251\324 \350*\304]zU\330\2235\022\237Jc\037\326\2531\3014\335\364n\245\335NW\251VJ~\372p\223\336\244Yy\251\004\231#\007\2458\276x\246\261\305&\372B\364\322\364\302\365\033=0\275F^\230^\243g\250\036J\205\244\250ZJ\205\344\250^J\205\244\301\353M2f\241\221\252\273\275@\317P\263\324,\365\01351\201#\223\212\205\266z\232\211\266\212\215\231GAQ\2221P7&\232E\030#\212LR\216\224\341\326\244QS\305\031f\000U\365\213\345\n*\344q\355\000U\204J\224-J\221\367\305N\211S,y\251\004^\325b\030\t=*\364V\3309\"\257\307\032*qC\002\306\237\034^\265n(\371\351W\341\210\2228\254\264l\216\264;\201\305V\224\363\326\241\337@pi\341\251CS\303S\303\322\357\367\245\022c\275H\262\343\232\224I\232w\231\3074\335\334\365\246\226\301\246\227\246\027\250\331\3522\365\031z\215\244\250\232J\201\344\250\032Oz\205\244\367\250ZOz\211\344\250\232Oz\217\314\346\230\362\002*\273\267\275B\355\357P3\014\375\352\214\225\317,M1\235{T.\344\367\250Y\251\205\252&4\322s\3054\323H\315\030\346\202))\312*tRN+B\332\036kE!\300\034U\205\216\244\013\212\225\0235a#\251\202T\311\037\025:\307\223V\342P\200U\225\313T\300ai\312\244\232\267\014E\210\300\255(,\316\001j\322\206\331Gj\343b\224\355\247\261\312\3475ZV\371A\252\345\350\017\316\rH\036\236\032\234\032\234\032\227}.\352]\370\247\254\230\251\004\264\246@E4\271\034\365\024\306zaz\214\275F\317Q\264\225\013IP\274\236\365\003I\357P<\236\365\013K\357P\264\225\023I\357Q4\236\364\303\'\275F\363\n\256\363sP\274\265\013I\351L\363)\205\3526z\210\2674\247\356\324$\363I\232\\PzSh\240-H\243<U\310\"\316+N\024\300\034U\344\031\002\247Q\3058)&\254\305\030\025ac\'\265L!\307&\245H\371\253Q\3066\364\251\243\207-\232\264\221{U\204\267-\326\255\307j\275\352\3541*t\025z\030\311\253\321\307^k\024\2318\315Z\007\345\252\356~V\025M\233\006\220==^\246\017N\337N\017\305\033\371\245\017N\rJ\033\024\273\350\337\357K\346\3664\322\370\350sL/Q\264\225\023IQ4\225\003\311\357P<\236\365\003\311\357P\264\276\365\013\311P\264\225\023IQ\264\236\365\031|\232\205\337\035\352\003\'\275B\362d\324e\351\206J\214\311M2Sw\322\231x\3053>\264\264\340h\353K\2126\320\027&\254E\0375\243o\027\025u\027\025j%\343\232\260\027\003\245K\034lM^\206\016*\322\307\212\220GR\244\'=*\322G\201V\"\213\236\225u!\366\2531\303\355Vc\200\347\245]\212\000\0075r4Q\300\025n$\357^I\t\357W\021\262*9N\t\036\265\235+a\351\201\351\341\352E\222\246R[\245)b\247\006\223}(zpzv\3727\321\276\220\311M2S\014\225\023I\357Q<\265\013KP<\236\365\003\313\357P4\274\324-%D\322{\324M%B\322{\324fJM\376\365\014\257\232\256\317P\263\363Q\227\246\027\246\027\246\027\244\337K\272\236\270=\351s\3159i\342\235\364\037\215\001K\032\231c\300\251\242\034\326\225\270\351WUrj\324Q\364\253\211\026{U\210\342\031\253*\010\342\254\306\271\025j8A\031\251\204c\260\251\222.j\3240\367\305]\216?j\267\024G\322\255$x\025*\251&\255\305\025\\D\257\027\212@\006*\322II#du\254\373\203\363f\240\335N\017N\017\203Wm.\021\037/\310\245\273\2366|\247\025]d=\352E\223\212v\356x\243}/\231\357H^\232d\246\231)\215\'\025\013KP\264\265\003\313\357P\274\276\365]\345\367\250^_z\205\245\250\232_z\211\244\367\250\232Z\210\311J$\250\344\222\253;\324E\371\353L/L/M-M\335I\272\200\324\360\325 4\3655*\344\364\247\201\306\007J\236%\343\221R\225\342\204\030<U\353v\350+R\001\272\264\"Z\266\213\305H\243\006\254\304\205\2175v(\272qWR<-J\261\363\322\254\307\017\265[\216!\351W\"\210zU\330\341\343\245L\"\251R.zU\270\343\253I\037\265|\376&\035\215X\206\343<f\247\3633U\2479S\355T\313P\036\236\036\227\314\3474\206BOZ\270\2775\226q\206\035\352\024\227\234T\276e\'\231\357K\277\336\220\311M2S\014\225\023\311P<\276\365\003\313\357P\264\336\365\003\315P<\276\365\013KP\264\276\365\021\227\336\243i}\352&\222\230d\243\314\250\344~\371\250Y\352\"\374\323\013\323w\321\272\2234\322\324\006\247\253T\252y\251\323\006\254\242\022=\005.\3346*\314`b\237\216)\350\225j$\305h\333\261\004V\244\'\200j\330\347\221S\"n5\241\004G\003\212\321\206.3VV:\2328\262zU\264\207\034U\270\240\351\305ZH\260zU\310\22358\216\246\216*\260\251VbN+\346T\2347CV\"\227\r\326\256\307.\345\353M\221\271\252rp\334S\003sN\rN\337I\273\0075e.I\217f@\024\301\265O\336\353K\346`\3434\031W\336\227\315\030\340\232O3\336\232\322TM/\275B\362\325w\233\336\241i\275\352\007\227\336\240y}\352\006\227\336\241i}\352&\227\336\242i}\352#/\2754\312\r4\311@\222\233$\231\\Ur\376\364\322\324\322\324\322\324n\247f\230M 5*\232\231\rY\214\325\245\177\227\024\3602sS \253\010\231\251\322:\265\032v\025n8\361W\355\317!Mi$y\351W!\214zV\214\0108\253\321\250\307\025f8\363W#\213\320U\250\341\307j\275\014=2*\301\204m\006\237\032\342\254\252U\210\322\247H\362j\324q`W\311q\276\323\305\\\216PqV\341\220\347\255N\347\345\025^n*\002\334\321\276\234\036\202\334Sw\363\326\246NS\223M\337\212<\317zo\233\216\364\276w\035i\215/\275D\322\373\324\0177\275Vy\271\353P\274\336\365\003\315\357P4\334u\250Z_z\211\245\250Z_z\211\346\367\250\214\264\3237\275\'\234\t\353OY3\336\232\357\357P\357\347\255&\372\\\346\232M\033\251CQ\326\2009\251V\246A\212\261\035ZJ\235EXE\2531\255[D\310\253\021Fs\322\257F\237-X\216\"\032\257\305\221Z6\304\022\005i\302\231\034U\350b<U\370\241\351\305]\216,\016\225n(\263W\022<T\2732)R.j\312G\355Vc\212\255G\017\265ZH\253\343\262\n\234\036*D|\036\265\243nz15e\232\253L\374\365\252\314\364\233\375\351\301\370\245/L/\316jhf\\\021\232I\030\347\"\2422\373\323\014\247\326\223\315\246\231x\250^oz\256\363{\325w\233\336\253\274\336\365\003M\357Q\264\336\365\013K\357Q4\274\324M/\275B\362\373\324F_zC.{\324fB\017Z\226)\275\352I\030\355\316j\277\231\317Z7\361J\262\032\220\035\302\233\273\006\234\rH\274\323\302\363R\252\363R\250\346\254F*\324kVQj\314iV\243J\271\022U\310\343\253\321\304\002\n\262\221\r\271\315O\032\342\256\333)\r\2221[V\303\201Z\260G\221W\243\216\255$ur\024\305ZT\251|\276*D\213\236\225i\"\253qC\355V\322\036:T\242:\370\302YD\217\236\224\337\272\303\232\320\205\212\250\346\247\363~^j\244\262\345\272\324%\3517\322\211)|\312k?\275C\346\220\335j\337\231\373\260}\252\263I\315Fe\367\2442\373\323\032_z\201\346\367\252\362K\357U\244\233\336\253<\376\365\013M\357Q\031\275\351\246oz\211\245\357\232\211\245\367\250^Z\210\313\357L2\322\371\271\025$rsS\2313\035V\337\363S\203\366\247)\315H\255\203R0\005r(\217\232\235G52\255L\213\305J\250I\253\021\245[\215j\314iW\"\216\256G\025Z\216:\273\024um\024\221\200*\324p\372\325\270\341\366\253\261E\355Zv\311\323\212\327\267\030\000\032\320\215}\252\334k\355V\021qVPT\350\205\217\265Y\021\362*\324Q{U\310\343\342\254\242|\265\"\246M|6_\232U|\236\265e.\230\000=*Ss\271z\324/&M7}4\2774\241\375\350/M2TN\342\246I\363\017&\240i}\352#-4\313\357Li\270\353U\336nz\325y&\367\252\262M\357U^nz\324M7\275F\323{\323\014\334\365\250\314\336\364\303/\275F\322du\250\213\323w\373\322\253f\246L\216jS!\331\326\240W\375\345?q\311\251\021\352Ubjh\316x5*\256\326\253!;\324\252\274U\204\\\214U\210\343\253)\021\364\253\021\307\355W\"\210\232\277\024\'\216*\354p\035\271\305XH}\252\3541{U\370\341\000p*\314p\363\234U\310\341\366\253qE\323\212\323\266\207\332\264#\213\030\255\010\223 U\310\243\253\n\236\225<qs\315[H\352tL\265^\216?\224U\210\323\332\254*\361\322\234\253\315|\036Z\200\364\340\370\251\004\230\245-\306h\335\3057w4\273\251\245\375\351\215%D\322dQ\034\235Fz\323\036B\016*&\226\2432\373\324o/\035j\273\315\357U\336_z\253$\336\365Y\346\367\250\032oz\211\247\367\250\314\376\364\236w\2754\313\357M2\322\027\342\2422sR$\300T\302\340b\236\262nZ\217?=K\273\214\346\244\214\324\361\232\2323\363\325\324R\300U\270\324c\232\221W\245XE\253\320E\221WV\036:T\311\007\265^\206\034v\253\321C\322\264b\207+\216\365:A\317J\271\014\034r*\342DGQV\242\206\256$#\025j(\306j\374*\005\\\214\363\300\253\320c\275]B\265f<\023\212\271\032dU\224\216\247\2159\253\210\265e\005N\027\212r\250\315|\003\346\000y\247\027,7(\340Q\346njv\342\034\n\231\\\037\224\322\026\301\246\226\244-M-Q\263\324L\364\344\340d\323&q\2675M\345\367\250\214\336\365\023\315U\344\227\336\253\274\276\365^Ix\353U\036nz\324\r7\275B\323{\324Fnz\321\347{\321\347Ry\276\364\242N)\254\374\320\262S\267\221Vazs7\315R\253ejTlT\361\236j\344 \026\311\255\010\260\000\305ZT\357\353S\242t\253\t\036H\255+x\361\212\275\034y=*\344Pg\034U\264\207\007\245]\212.\231\253\321 \253\261F\276\225q\020`S\311P\334\232\231$\003\245Z\215\330\216\225f2\336\225r\000\356\370\355Z1G\216\242\245\336\021\261R\306d\310#8\253\261M\203\363V\2043\257\255]\216e\365\253)\"\346\255F\343\212\262\214*\312\034\212\221G5\371\354\314;\232\236\031\000\210\340f\240\016D\234\324\341\263\3159d!\271\342\234\316;\032g\230I\351\305\005\251\245\270\250]\251\201\271\346\225\245\003\203U\346\230\021\200j\234\222\325v\227\336\243i\262:\325g\233\336\253\264\334\365\250d\227\"\251\313/\275Vi\275\352&\233\336\2432\373\322y\276\364\242oz\014\236\364\242^z\324\233\367\n\0019\251\003dU\210_\236jF99\0254-\352*\302\340\325\210\306*\344B\257\303\327\232\274\235\000\253Q\257J\277\024C\203WbLb\257\302\225\247m\036@\253\206\014(oZr\341j\304n\007j\261\024\254OCWc,j\302\333\022\241\2175<v\355\321T\325\330\255% qW\240\261va\272\272\033\r64\3035X\271\201@\371\005f<\017\346g\006\264\254\221v\341\307\343W\r\226\377\000\231j&\267\222>Fx\246,\322\2111\223W\241\235\370\316kB)\233\002\257C6\352\321\210\202\265n5\257\316\326\"\230%d\340P$gq\232\262\255\205\245\335\232\034\341(-\200\007\240\246\357\342\232\317\301\250Y\351\273\352)e\317z\253$\234UY$\252\315/5\023KU\336Z\256\322\363\326\240y=\352\244\322\325V\227\336\242i}\3523/\2757\3164\341/\275<I\305(z\225$\367\251\226L\324\200\324\321\365\251\263S\303V\223\255[\214dqW!\034\212\320\214`\003W\242L\212\275\024}+B\005\253\361\305\236j\3641\361Z0\341ENf\310\3329\247G\031c\223W\240\203w\001kN\r:GL\204\255\013})\3627V\264\032_\000m\253\360ij\017\"\264a\260\205T1\003\025al\341\316G\003\326\255D\261\'\005\270\251\235\240\306\000\311\246\210\"s\222\202\247\216\010\227\370*p\2038Q\201K$\000\257\336\006\253\375\220\003\300\346\234\221\354?2\323\213\205<\036*xf\031\004\032\330\264\2240\034\326\254g+_\235\004\323\r\000c\232\220>E<=\014\371\000{\323K\374\344\032ilS7\361\223\353Q\226$\346\243\222L\n\252\362Ug\223\336\253I%Vy*\026\227\336\240\222N:\3257\227\236\265\033I\225\316j\234\322\036y\252\215-D\322\373\324fJo\233OY\252Q-<IR$\265:IS\243\325\230\233\232\261\232\236\023\315[\217\357U\330\205_\204\002*\364|\340V\215\272\020\005iE\036@\342\257E\021\030\342\264!N*\342\020\242\245\334H\300\253V\220\274\216\025A&\272\013\035\032G \272\221\355[\366\332dQ\340m\346\265#\201\021F@\2531\204\014>^*\324L\003\344-XP\307\247\031\251\222\023\216j\302\333\344c\232p\265m\336\325r;4\013\223\315L\"\033~Q\212z\307\315L\261\361\234S\274\241\216\224\236^:\nF\214\025\344V-\3434Rc\265Od\306\\V\314!\341 \366\255\313V\337\0305\371\323\232L\322\026\245\rN\rJ[\212s\2601\206=j\"\331<\323@%\010\250\331\2609\252\262I\315U\221\352\263\275U\222J\254\362{\324\017%B\362qT\345\223\236\265\020\227*Fj\274\357\212\240\362{\324&Zi\223\336\220\311J\262\017Z\235d\004u\251D\200T\210\3435a\032\254\306\325n.H\253\212\274T\321\214\032\271\020\346\264!^\005]\210b\264!L\340\326\244\013\225\025\251n\274\n\322\2161\264U\204\343\212\263\032g\223W`\265i\233\n8\256\233L\323\342\210\253\021\315tq0U\300\0252d\232\264\212[\255Y\216?j\267\034~\325r(\352\312GV\222<T\311\021c\322\246\333\311\030\251V\023\216\225$p\214d\216jM\203\265(\216\224\304*&\217\212\307\324`.\300\001Ri\360\030\361\221\326\267\202n\200\014U\333\025!p{W\347F\352i4\023FiCS\267Q\270\021\203\322\244\001@\246H\373W5FY2j\244\217U\244z\255#\325Y\036\252\273\325wz\205\344\343\212\2473\325q6\033\255E4\245\207Z\317y9#\336\2412R\031)\206Nh\022sS$\204\324\301\310\251\243sWa~*\3325\\\205\271\253\321\267\034\325\210\3715z\025\351\232\321\210p1Wa^\000\255;x\376QZ\226\351\225\255;t\340U\365l\n\263o\036\362\t\255\004\200\261\001\005tZu\226\310\201\"\266\340\214(\000U\350\226\256F\276\325n5\253q\2575n$\253\261G\232\262\221\324\252ppj\374\0106\347\332\236\"S(\305X\021\324\\\357 T\212\275\252M\243\361\244)\223\315!E\007\025ZKp\356X\216;RE\n\211\000\002\266\241\266\036X\'\275Y\212\r\2475\371\262\307\232n}\3513\317Z]\336\364\240\320Z\223u8LUqPK6\352\252\357U\235\252\254\217Udz\253#\325gz\256\355U\335\370\252r\275S\222Nj&\227\212\251+\376\360\324%\351\206JB\342\215\343\035jx\244\025h0+R!\364\253p\265^\214\346\256\300sZ1\256@\2531\214\032\277\010\316+N\334z\212\320\2059\255;t\030\255[d\034V\224 \001S\240\337 \003\245k\333C\220\024\016MtV6\210\2403\n\327\211@<U\330\305\\\210U\310\305[\214U\330\226\256\304\265r$\311\253\210\234R\264D\034\201W\255ab\225j\030s#g\265<\256\rW#2\222*U\034\342\254\024DL\360O\255D\205H\352\r!\214\023\222i\262\014\200\024S\355\255\031\23468\255U]\240\017J\224\036\342\2774Z\243\'\232i4\241\271\245\rHZ\233\272\232\315P;T\016\325^F\252\222=T\221\352\263\277Z\254\355U\335\352\274\217\301\2523=T\221\352\002\365Zf\301\006\240g\246\0319\246\231\005*\2775b3\315]V\005)\350\304\032\271\003V\204G\245_\207\265h\304zU\310\207\"\264 \034\212\323\200t\2558\000 V\235\272`\212\323\200`\212\267\273\260\253\366\250\024n5\320i\220\227`\304W@\230\030\002\256\302\005]\213\265\\\214dU\330\205]\211j\354KWbZ\267\020\303\n\274\203\212\221\2600=kN\323kE\214v\2516l\311=\352\t\0133`t\246*8=*\302FN8\346\226D##5L\306\342^3\212\267\025\263\267Z\264\226\200\036j\334qm\034\nVC\330S\221Oz\374\321qU\317Zi<\321\236E(jBi3LcP9\250\034\325i\032\252Hj\244\215U]\252\273\265Wv\252\262\265Q\225\252\234\217\212\204\265A;|\265\001n*\"i\205\251U\215Y\212J\266\222U\210\333\'\232\273\t\346\264!l\021Z0\266qZ1\016\005]\213#\025\243\007QZ\260\016+R\335zV\245\270\344U\341\362\342\257\332\307\270nj\321\266O2P;\n\351\354\325b\210\016\365\241\021\3175~\023\305^\213\255]\212\257CZ\020\216\234U\350\226\256\304\265n%\371\261V\300\305\005N\354\326\205\237\030\346\256\345\031\366\036\265\024\221\205|\366\252\263^$-\364\252\347Y\347\345\300\242;\366\232`3\234\232\333\212%\221\001\013V\222\034t\251\322<\366\251\322,\324\313n\270\346\241\2224\022azW\3463\032\256\375j\"y\245\034\2123A4\302i\214j\027\342\253\271\252\322\032\251!\252\222\032\253!\252\356j\263\267\025RV\252R\265S\221\252\020\325\004\315\362\232\256[\212ajajP\330\251\343nj\332t\315Z\210\325\370{V\204<\342\264\255\373V\234\006\257E\202x\255\010GJ\325\267\347\025\255n8\025\247\026\000\006\255G\363\270\002\266m\220\371`\n\324\262\213\016+n&\351W\241=+F\023Wb5~\036\325~\n\321\207\265_\210\216*\364C5m\024\344\032\260\2751Nlm\025f\335\260*\312\034\234\236\264\2636\324\311o\302\261n\225\246\223#\245eK\013\244\235\361\232\333\320\255\274\311\201a\232\353\243\210\"`\n\22249\311\351V\002|\276\224\221\312\021\360\334\323\245\237p\302\361Q\002Ks_\230\354*\273u\250X\320\247\265\024\204\323I\246\023Q9\310\252\322UY\rU\222\252\311Ud\353Ud5ZC\305R\231\252\214\255U$j\215H\r\315C)\3105X\236*2i\264\240\324\261\234\032\273\023\r\265b7\346\257\300\365\247\001\316+N\n\323\267RkB\025\"\264 \035+R\337\265k@x\025\240\216\002\n\277d\233\233q\255\313R\000\305kA\205Q\216\265~\023\232\320\205\272V\204-\322\257\302\334\212\277\021\255\010OJ\320\210\325\370z\212\321\203<U\370\306x\251\3251O\362K\032txK\200\275\253I#V\\\212Im7b\205\323\242#\030\346\263\357\364\320\274\201S\350\351\345K\203]:((\r/\002\227p\'\031\246\225\031\340R\021\201P\271`r\r~i<df\252H1U\\\363H\247\232q\246\236\224\302x\250\330\324l\325\004\235*\254\225Y\352\254\225RJ\253%U\222\250Lj\214\246\2529\346\243\316MF\346\2531\346\242&\227\265 5\"\232\261\023\032\275\0078\255\030\007J\322\203\203Z\266\374\342\265m\306\0005\245\030\'\025z\021\201Z\0206*\3742\363\201ZP+0\031\351Z\2201@\006kn\310\026\301\255X\310\004U\350Z\264 5\241\013U\350\232\264!n\225~\027\351Z0\275_\201\253J\007\255(NqV\324\202\005M\270\001\305Wf\036\177\035\252\3543\221\026\001\247y\356H\346\254\301#n\004\324\367\210\036\020qYq8\212\351~\265\320\3056\020\021\320\212O0\227\"\245_Z\177zC\322\242nk\363nD\004\032\2434UFD\371\252=\270j\030\374\324\323\3235\031\353Lj\205\215B\347\212\256\365ZJ\253 \252\262\n\253%R\224\325\t\215R\226\252=1;\324/\324\212\254\335j>\364\022)2*D5j\"*\354=\210\255(\017J\322\203\222+V\334t\255[s\310\255XG\312*\354}*\314m\310\002\264\255\366\246\t\353Z\266\363\344akR\322&\221\263\330V\335\263\204@\243\255_\211\263\315hBzV\204MWbz\275\013\362+B&\351W\342~\225~\027\351Z\020\311\322\264\355\3378\346\264\340\223\003\025r6\315HX\343\212b\246O5b45f8\363W\"\214\n\262St$W?r\031n\370\365\255k;\221\264#\325\304!\234\232\260\264\341A<TLk\363\201\3075\004\210\030Vl\310C\036*\271S\326\241bFi\212\375\215!84\326\250\230T\014*\007\025ZAU\244\025R^*\224\246\250\312j\214\246\251Hpj\253\236i\253\320\324\017\367\215Wa\311\250\361Q1\371\261J*T\253Qu\255\010G\"\264a\352+J\337\255k[\366\255K~\325\253\007#\232\270\244\001V-\3179=\252\322JL\200\016\365\275a\001*\t\255\350\231c\217j\365\2530\261\3175\245\013t\255(\033\214\325\350\332\255\306\365v\031\017\255h\301\'J\320\211\352\374/\322\264!j\320\267\223\025\251o&MhD\3318\025eA5*\241\007\245N\213V\243\025e8\025f\034\034\347\322\262.\240\r30\355N\2120\252\0335z\334\361\232\264\246\234\255\221\212\t\342\242c\315~r8\252\355U\246Q\203\305R~\265V^\265_\275?!\206i\247\245F\325\023\016*\007\025Y\326\252\313\300\2523\036\265BSTe5FSU$\346\252\2654t5\003\3655\023S\010\342\240\332K\325\210\355\231\206jS\001A\234T\221\255^\206\264`\007\212\323\267\035+R\337\265k[V\244#\24751r\242\244\216|GW\354~i\003\032\350\355\346!\000\035\253B\t\t<\232\323\205\272V\204-\322\264a\223\013W#\227\212\265\034\231=j\374\017Z0?5\241\023\325\370_\000V\204\0161W\340\227\346\305jB\370\301\006\264\355\311 \032\275\024\243 b\256\241V\030\251B\025>\3252T\273\260\264\350n\006Z\263\256\256\202\263\002z\324p\334\371\244\"\236+^\016\024U\215\330\245V\3474\245\273TD\363_\235N\265]\326\253\3102*\204\310A\'\025JNy\250M38z\220\323XqQ0\250\035j\274\202\251MY\363w\254\371\217Z\2435R\222\252I\324\325w\024\323\302\325v\353L\"\221\242%sQ\252s\212\321\267Q\201\232\225\325H8\034TH\234\325\250\227\221Z\020\016+J\016\202\264\355\307J\325\267\343\025\245\023`S\244l\212l,|\320\265\275h\000\003\035+j\335\276Z\321\201\271\255(_\245hB\376\365r9=\352\334Rt\253\260\275h\301\'\003\232\320\206N\225\241\014\235\352\3642qZ0=_Bx\"\264\255f\316\024\326\315\264\230\030\253\221\237\2375m&\332x\255(]eJyR\215\355OH\232s\261i\322Z\371\0216\016N+\234\325\213!\\w\251\364\230\311@\306\267\323\205\247n\241[\232y~*2\334\327\347\244\213U\331*\273\247\265U\232<\251\254\313\204\307J\252EF\302\204b~SR\021\305F\313Q:\361Ue\351Y\363w\254\371\273\326|\335j\224\2435JAUd\025]\2050\216*\006\034\320\250X\324\214\277/\322\243\362\376l\342\254F6\361Nw\300\300\241\016j\334C5z.\325\245o\320V\225\277Z\323\207\265\\V\300\251\003df\235\003\005\230\023\336\267m[*+V\332N\331\2558\032\264!nj\364r`u\253Q\311\357W\"~\225~\031:U\370d\255\010_8\346\264a~+B\006\034V\224\r\300\255(\033\245]C\206\004\032\323\265\230\226\025\250\257\214\037Z\261\033sV\341\220\306\331\006\265\243a4 \324\326\200\3071\357\232\226\340\026\213\031\357\\\366\263lJ\207\307N*M4m\205kU[\212B\324\006\346\236[\345\376U\003HK`\365\257\200\331*\026\212\241x\270\252\322E\362\326U\314?1\315g\272`\324ei\2730sSm\312\212a^*\t\006\005R\233\275g\315\336\250J3\232\243*\363T\245Z\247*\325I\026\253\262\363Le\342\241e\346\237\032\340sO)\224\316)\270\246\222A\246\002X\346\254F*\344C\025r.\265\243\007j\322\267\355Zp\236*\306\356)\352\337\'\024D\314g\003\322\267\255\033\n+J\007\303\n\326\267z\277\024\234\325\270\344\367\253q\311Waz\277\024\225z\027\351Z0I\322\264\241\223\"\257\301\'J\323\267\220qZPIZ0\276j\374\r\265\201\255h\217\231\020\301\346\254\302\177\204\365\025h\034\n\263iz\310\302>\2435\257\034\352\0109\353V%u)\307z\311\325\231D\000T\026g\021\212\274\033\2127\363K\272\224?4\223(\362\374\320y\035k\340\307\216\241d\305D\313\236\325\004\221\326m\324\031\311\254\231b \342\240)L+\315=}\r\014\274t\252\262\214U\t\273\325\tFj\224\213T\345Z\245*\3259\026\252H\274\324%*7^*-\231j\223\313\317\312\005+\302Tz\323V>*9\227\013\305E\032\036*\334kV\343\025f!\315hC\332\264\240\343\025\241\021\253=\251\321\236\325,\n\005\306Ml[\2368\253\3617J\325\201\370\025z\027\253\221\275Z\211\352\364/W\243~\225~\007\351Z0\022qZP1\030\253\321=h\301\'NkN\ty\025\251\014\230 \326\204M\220+J\326L\034V\212\000He<\325\245;\223\007\255X\262E$\226\352*\356\335\304\020zU\230$,\370n\202\262uysr\020S\355\316\020U\235\374R\027\247\007\367\245\335N\335\272&\\\360k\341\246\216\241h\271\346\243h\300\252\322\2475Fx\301\025\223s\017\315UZ<Tf<\364\243c\001\203Q1*\330=;UY\252\214\253T\345^*\224\253T\345Z\247\"\3259\026\252\310\225\026\312\205\322\221c\307&\245\211\024\344\201R4D\216\225\013\304TT\016\201\2074\304\216\254\306\225aF\005X\204d\325\370\227\332\264!\253\361v\253K\310\247\"\374\365!\371\034\021Z\266\257\225\025~#\234V\234\r\362\212\271\023sWQ\352\324OW\242|\n\271\024\2075\241\013\364\255Ki\000\305i$\200\200EY\216Nz\326\204\022V\234\022t\255He\340V\255\264\240\250\031\253\261J7pkV\tr\242\256\243\344U\250\016\016}j\352\277\315V\025\302\2515\317\\\312f\324\233\234\200j\364G\n*m\324\205\3517\323\203\361R#\361\212\370\231\271\250H\371\216i\214\024\016\265]\225I$\325Y\243\004\022+2k|\265U\222\324\366\025\010\200\257Zc%V\232?\227=\305Q\224`U9\006j\244\213\305S\221j\234\213U$J\251\"UY\023\232\205\223\212\214\246\343\322\206\214\364\024\364L\n{\034\n\202F\317\025\t\217\214\323Bc\265O\032\324\273\t\253p&\005]\214U\350\272U\330\272U\264\025&;\212p\371\370\255\033U\302\216kJ,\014U\330Z\257D\330\025j6\346\256Dx\253q\267\275\\\205\372V\204.8\2558\037$V\204r`\014\232\263\034\247ui@\376\365\245\004\2359\2558$\351Z0\277\275h\303\'J\320\206\351P\340\236+J\t\203\214\203\305\\\216R;\325\250\245\3169\251..B[6\0178\254h\016\351\213\236\346\264\221\360)\345\351\013\322o\245\017R+\327\306\005MF\313P\262\232\201\326\240u\342\252\274u\003!^\331\252\322FI\311\252\355\035V\23185\235*rF*\214\213\203\212\255\"\325IR\251\310\236\325VD\252r\247\265Vh\375\252&N)\253\037\265\002\"OJv\312\211\320\236\325\021\210\223\234T\211\010n\242\234m\0068\024\337+oj\221\027\326\254\304\265n5\253\221\n\271\020\"\256\'J\230\016)\240bL\326\235\267\335\253\321\232\271\t\253\3215Y\214\363V\221\210\251\222B*\3442\232\321\202Ny5\251m\'\313\311\342\256\244\331=j\3442g\025\245\003\340V\214\022t\346\264\340\223\245i\301%hE\'\035jUm\362\000I\255\233V)\030QW\321\252}\354\027\212\317\271\270\225\233a<\023S\333\360\242\256+\361K\276\215\364o\245\rR#\363_\036\272\342\242e\366\250Yj\027Z\201\324UvNM@\361\324\016\225]\322\250\3149\254\331\303\006\371y\252\022r\346\241u\310\252\322%T\222:\253,x\006\251\272d\364\250^.*\017+\'\2459\242\332\275)\0263\212\014T\317\'\'\245)\203\216\224\251\006\017J\260\220n\244{,\236\225\013[2v\247E\031\335\322\256\"U\224\\U\270\273U\270\307\025-\030$\325\333m\330\305_\213$\325\330\262\005\\\217\246jx\333\232\266\215\305M\031\031\253H\340U\370\033\245hE6\000\031\253\220\310Oz\275\004\2308\255H\034\034V\215\274\2000\315i\306\340`\203\326\264`\223\2475\247\013\202*\355\242\357\271\031\365\255\204\302\310}\251V\3665lf\255-\332\354\316j\241\1772m\325r6\300\251\203\322\357\367\245\017F\372xjz\265|\240\320q\234Ug\207\232\204\305\317J\257$g8\305Wh\317\245A\"\340\346\253\270\252\356*\254\242\250\314:\325C\030 \346\262d\\JG\2754\257\025\004\221\325g\216\251L\2318\002\2400\340t\250\036,\360(\026\341G#\232\216H\263\320R\010\260:Q\344\236\364yt\276W\035(X\362zT\361\300A\315Z\362\203\247Nj6\265\3349\025\t\264(r\005H\261q\322\246H\352eB\rY@qR\200Oj\2268\217SW!\001j\344#\025z1\362\212\260\275*d\253*~Pjt\311\344U\230\201\316M]\216Lt\2531I\317Z\320\201\317j\277\033g\236\365~\tH\305iC\'C\232\322\202N\225\247o\'J\323\206^\005i\331L\026\\\232\334\204\251\217~z\326}\354[e\016\247\203S[\344\250\005\211\253h\240T\312\370\247\207\247\007\247\007\247\006\247\006\251\025\253\345\271\037\003\002\252\263s\315B\354\000\342\253\273T\017\322\253:\223U\244S\330\212\257\"\2663U\\\022:U9c89\252\254>Z\312\235\017\236O\2557g\025\023\247\265Wt\317AU\332\016rEV\2311\332\243K~7\021Mx\316zS\032/\223\245\'\223\305\'\223G\223\355G\224iV\034\036\2250Zr\214\032\234(`8\240\304)\236X\317J\2368T\212\220B\271\251\2225\0252\"\372T\3012q\212\010*\341E\\\204t\006\257(\343\031\251T\366\251\227\202*\3127\002\255F\325e\033\26152\260\315O\033\340\326\215\264\240V\224R\002j\364,3W\341>\225~\027#\025\245o/J\324\206N:\325\373y\260\343\232\350 \224\375\233\000\3247S\017-S\024\353y1\201\232\272\034\036\224\340\364\360\364\360\331\247\006\374i\341\251\341\252En+\345\311\007<\325g Ui$\025U\345\0035\003\315PI6\005Ty\2115\023MQn\335L\2212\2475E\343\301\252W1\2563\216EV\300\305F\351\305V~\rD\343#\245Vh\213\267J\224A\204\306*&\200Tf!\214Ry9\035(\362@\024\010A\245\3629\244\3629\351Hc \342\200\234\364\251\321)\3738\346\232c\031\340S\320b\244\024\360\265*\014\032\262\230\353Qn\r6M[\211\276aWU\375jU5:\032\231\rX\215\215YC\201\272\245V$\325\270\317\025j#\310\255(\037\245hB\375+F\027\253\360\234\343\232\321\204\364\255\010d#\002\256\306\344r+R\322\365\266\354-\364\253\022\313\271@=E>)\010\357V\322CR\207\251\025\375\352@\324\360\364\340\364\365j\221\036\276c\225\272\325\031Z\250\312\375j\224\216sU\336CP<\204\236\265\00350\234\323\220sR2\356^*\264\261|\244\325\tb\316A\252\262@P\203\330\323\032\034\255S\222\002\rFb$t\240E\264S\266\361\322\242t=j\026\214\322\010\370\2441\n\014D\nn\n\323\270#\232\215\216N1B\21475:\205\305;\002\200\240\322\005\301\247\201R(\247\214T\312F:\325F\312\334u\3435\241\017@j\352\020W\336\246^\225:\324\311V#\342\246R{\324\351V#j\271\023U\350X\325\370_\232\320\205\353F\007\344V\234\017\322\257\304\365md\371j\305\274\204J+FI1\201R\304\371\025i\037\336\247W\251U\352@\364\340\325 jz\265H\255\315|\3131\315P\224\360j\204\247\255S\220\363U\234\324,j2)6\323\227\255XQ\362\342\225\242\005\t\254\311T\0068\252\223\222Wo\2450H6\035\313\323\275@J\276qM1\000\271\246\030\275i\276VzSZ\037ja\203=\251\255\016\321\322\242+\212c\016*\0278\355Q\226\246\026\002\232e\002\220\\c\245H\267$\324\211?52\035\334\232\225W\322\246\0203t\251\005\234\230\247\010vpXTR@I\310\251\340;W\rVQ\271\030\253\211\322\247AS\250\251\324T\243\212\221X\324\350j\324M\357W\241n\225~\026\255\010^\264!n\225\245\003\364\255\030[ U\325\031\025f!\202\017\275Y\022n?J\261\023\374\242\255#T\352\3252\265H\036\244\rO\rOV\251U\253\346\251\253>~\365B^\365NJ\254\365\021\024\200S\266qB\247\315W#\207\271\351Q]\312\021<\265\357Yn3\315U\2226=\252\026\215\300\306*5\211\267\344\255K\262\243u\246\250\031\346\234\311\305FW\024\326P\313\357T\345R\r@\304\201\322\253H\325]\237\025\013\313P\231\t4\201\232\246\215]\217\000\325\350\255\'l|\246\257Eg8\034\241\253q\331\312W;\rJ!\224tSO+2\257\3355RW+\235\331\250#\271\303\340\364\253@\357\371\224\325\210\367\005\346\257\302r\2435eEL\265:\221R\016\224\253\311\251\323\336\254\307\332\256\304\334\201W\341j\275\013t\255\030_\245h\302\331\305h\333\2675\245\023dU\370\202\233r{\212\256n65O\r\320 \014\325\370\245\014:\325\244qV\024\323\301\251\025\251\341\252Ej\221Z\276s\225+>\341q\232\316\225z\325)\005WaQ\221@\024\265<\021\006\033\217AN\222C\214-Sx]\333-J-\0169\024\033U\364\246\233U\307J\202[eQ\322\251\274x\'\212\256\313\223Q\221\203N\004\032k`\364\250\360A\250\245@j\224\213\214\212\245(\252\216\244\232#\264y[\000\023W\342\320\345l\022\r\\\207\303\262\226\037!\307\322\267l\2746\212\0032\201Z\277\3316\311\036\000\031\247\255\235\272\250\033zw\251\014\021\036\000\002\243k(\363\221\326\201e\0360{\326]\356\2302Lc \326,\332k#gn)\320Z\313\274\000\rk\245\231h\307j\222;vC\203V\2218\351S\252qHx\346\225e\003\214\324\213 \365\251\321\363V\3425r3\305Z\211\261W\340|\365\253\360\276+J\007\351Z0\267CZ\020\275h[\310\000 \367\025\\\300\355!\347\214\324\253l\3523\326\255B$S\305^\212^\307\255\\\215\352`sO\006\244SR)\251\001\257\237\344A\212\317\270\217\255e\314\230\315P\225y5]\227\232\210\216i\246\232z\325\373r\r\271\003\255H\220n\346\236`\003\265#E\307J\256\351\212\210\236i\031\003\2575Fxx<V~\300\030\324\022\214U\1773\234S\263\232)\217UdL\325f\266gl(\251\341\322\035\334n\025\320i\372\004`\006a\212\336M2\010\324qR\010\221\006\025E5\233\035*2\304\367\250\330\267\255G\226\035\351<\302;\322\211\275\351\336b\367\252\367\021\305\"\223\201PC\022\356\311\035*\340EQ\236\364\326\306i\353R\214m\252\262H\000\"\2514\247\177\006\247\212~@cW\342pG\025v\027\351W\2439\253(qW!nEhB\365\241\003\363Zp?J\320\211\261\212\273\023U\264\347\232\265\036EM\264u^\r5\217\361\016\243\255O\004\271\357W\2439\025 \247\212\225M<\032\360\211R\250\\/\025\227:pk>U\305Tq\315DE4\2550\306Oj\232\022\3126\326\245\250\314d\232s)\'\000S|\246\352j\274\361\340{\325_(\223\322\232P\216\364\313\210\301\204\340sY+\0212\221\212e\335\261\021\022+\034\214=N\243\212q\034Tdd\324\260\331<\307\201\305l\331h\245\261\362\217\251\255h\364\350!\003(2*|*\016\000\342\230\322\212\211\244\364\250\211$\323zSY\2522i\215Q\223\212n\352B\300\365\241d\301\351O\363s\322\200\331\251\224\324\252x5N\352\027\306\345\351T9\007\232\221^\256\301!\030\007\245iC\222\001\255\010\211\003\232\260\217\315[\205\253B\026\255\010[\221\315iB\331\305h\304\334\n\273\013U\330\232\256\304\325iGqM\222\"N\345\353Q\200\310wU\333y\201\002\256\307\363T\230\002\234\010\024\365a\351^\037 \2523\200A\254\273\201\326\263f\025M\227\232\217nMM\025\266\343\222*\317\331F\334b\237\025\206\366\351W\322\321\243\033q\201Lt\np((\002f\251\310\027<\363P\266\320\274\n\254\351\223K\345\3450zT\006\3324$\201T\357\024\010\017\322\271\326Be<w\251\2218\346\222B\000\246D\215$\240(&\272\235+Mm\240\270\300\255\215\236Z\340T\016\325\003\261\250\311\024\302i\205\275\351\205\207\255F\304z\324e\251\013S\t\250\3154\236)\245\251\310je5*\232\225[\002\244$2`\326m\324!\\\225\025\002\251\2530\203\232\324\266,\007N+F68\035jU\'5r\023\323\232\320\205\253B\026\255\030\rhD\375*\364mW\"j\275\023U\350\233\"\254/ZIP\024\315TV\362\344\343\245h\303)`1S\341\232\236\241\200\371\205<p:\327\212I\310\252\023\326d\375Mg\314*\243/5,0\026n\225~;r;U\270\255ry\025\243\005\272\"\347\003\353Q\335\025H\370\025\234Wsd\324s\023\267\002\251\270\250\034S1\305(\034Tr&GZ\313\276F\362\310\355X\214U\t\342\242iy\3004\350\240\222f\340\032\335\3234\300\0301^k\244M\261G\201\216*\031\245\311\353U]\215FNz\323\t\025\0335D\317Q3\324l\364\302\324\322\370\246\231)\205\250\315&y\247/Z\225[\025\"\265<7\024\364zd\2447j\201Pn\2531\204\317\025r)1\306*\344R\234sS\253\344\325\270\233\245hBsZ\020\234c\232\321\205\272U\370[\245^\211\252\354&\256\304j\344OW\021\252Br\270\252S&\033\353R[JT\355&\264c\223#\255L\037\326\201\327\212\361Y[\212\317\230\365\346\250J*\224\253\232\201b\334\365\255kj\002\347\025q`\311\351S\307\036\0074\362x\364\025F\340\357l\016\202\241\333\305W\224UfZg\227\232\215\242\333@Q\217z\206A\212\314\276\000\245`\\\252\223\362sL\202\325\235\306V\267-mv\000\241q\236\265\271\n\210\241\030\034\322\263\026\250^\2429\3151\311\003\006\253\263T,\365\0239\250\331\2522\324\302\324\322\324\322\324\322\324n\024\241\2058?\275=\\S\274\320)\353&{\323\303\320Xb\221NML\256\243\250\2531J\244\343\245]\216\255F*\324|U\330[\245h\300\325\243\001\342\264\"5z\023W\2435r3V\343j\271\031\310\251A\246H\271R\rUN%\255(NT`\324\340\232\221Mx\224\246\251K\324\325\031zUf\\\232\263kk\275\301\305l\307\000T\002\237\263\260\024\216\002\214TR\034.\007Z\250\343\232a\351T\346?6*\034d\322\220\005F\370\250\310\031\252\362\343\221\232\313\236\007\235\210\317\025\017\366r\2022*\3546Q \014\024dS\335\304m\3063\351RF\354\347\255N~T\252\362\314\250\271j\316\233QD$\356\025I\265`[\236\224\035R\003G\332\343~\215G\230\010\3105\021\224Tm(\250\214\343=i\276x\250\244\271\013\336\253\276\240\007J\204\337\2714\365\276\222\244\027R7\025j9\216\336i\305\331\272qR#\260\034\232\223y\343\232z\276x\2531c\251\251\000\347\212\235#\343=\352\334E\224\200M_\2178\006\255&1V\241\353ZPv\255(M^\210\325\350M^\210\364\253\210j\324f\256Dj\3004\346\033\206*\234\250RM\303\245Oo.\016*\362\266EJ\243\326\274JST&j\250\347&\210\342\334\340b\266-m\302\017z\266W\214R\037\225}\315B\304b\252\310z\232\201\272\3242>\0063Te89\250\214\200rM0\334\002x\300\250\332u\365\252\362^(<\032\315\274\275\000\034\036j(/\224\367\346\256,\276g\275J\031\261\214\3242)\335\232E\234\307\317aT\256\365\226BT\032\306\271\325&\220\237\230\325\026\232G9$\323\014\204u5\033M\203\326\201vW\2759u\'O\342\253\t\252D\303\347\353S\245\3242/\rI#(\344\020j\244\267\014\007\312*\224\263\273\036\265\007\314MO\014d\236EhG\020\002\245X\2239\305N\252\005H\024z\324\200\250\352i\014\311\234u\245Y\001>\325f99\034\325\224`M[\216\255\307\206\"\257\300\017J\266\261\215\271\025<#\014+F\036\325\241\rhE\322\256E\305^\204\325\310\352\324}\252\324f\255!\310\251W\221L\2252\204b\253\307\303}+B#\305N\rx|\306\250JI5\020RM_\262\266gpq[\"\r\253HP/-PH\302\253H\343\025Q\333\232\201\311\316j\026RNMS\270e\000\232\315\226nO5U\347\307z\251%\313d\340\3259n$\355Tf/ $\223N\265\014$\002\267\240\341\005X\310\025\024\207\035\3532\356\361#B\240\363XSHdri\202<\3221U\025Ri@5U\245\367\250Zoz\211\246\347\2553\315\367\245[\211\020\345X\212\235u)G\004\346\2365\014\365\024\357\265#v\025$sC\374U<sD[\203V\321\324\216\032\246WQ\374B\203s\032\177\0275\033]\223\367iD\307\034\234\320\254sV#cV\323;r*\324D\361W\242cWa\373\302\264\242\310\301\307\025y\000)\221RG\301\253\320\032\321\207\245_\212\256\304zU\310\215]\210\346\255\307V\223\025b3\315XQ\315+\256V\251\203\211H\253Q1\035j\322\034\327\207\312\t5\007\224X\340\n\261\r\2133\002El[[\254k\322\2540\000\022GJ\247#\344\223T\345\3115]\220\346\240q\203P\261\366\252\362\260\nI5\215u>X\200x\254\351\036\252H\304\324\'\2558\"\3106\232i\262\344\363\305*\333l\344u\025a$)\301\025\'\332x\351Q\311#:\341k*{7\221\211\344\324\037be<\203QK\031\215N\005e\\\273\202EPvcP\263\032\205\230\323@$\323\3262M?\310cA\2674\303\036\017\"\234\022\234\020\216\365\"\203\330\324\350\362\001\324\324\333\344\307Z\001c\324\323\325\210\342\246F\311\353Vc\000\325\204\025j6*q\332\255\306A\305_\204f\257D:V\244#**\334y\013\322\245N\265r\032\277\t5\241\t\365\253\321t\253q\325\310\215\\\217\232\265\031\253)S\241\251\205Wx\201\223\"\245X\316\337zz\022\016\rx\337\226Y\261\212\267\r\240\000\02295v+p9\305X\330\025x\252\363\002O\265W\362I\344\360*\t\004`\341NMT\223\216r*\234\204\223U\344p\240\222\177\032\307\274\272\334J\251\342\263$bj\263d\324,*&\024\200\355<u\246\275\321Q\353P\265\334\244|\240\325W\274\233wz\222\033\211\\\216\265}\037j\363\326\235\346\344\364\247\022\244r*\255\304\033\324\225\254\251\364\307p[\025\231=\203\241\306\332\246m\034\236\224\323c\'\367i\r\233F2E\"\253\217\340\375(g`:T~n:\232]\352\302\232X\nM\336\225\"\023\351Vc\"\245\355E(\251W\255Y\214\343\265YF\253Q\341\206*\304{\225\260kJ\335\270\255\030\217\250\255\033r6\325\350\275\rN\251V\"\030\340\325\330\273U\370N*\374G\212\271\021\253q\325\310\316*\324f\255\247J\224\034T\252\324\270\371\263V\024er)\216\207\250\353^Oo\016\\\022+I!\317j\234D\024S\037\000Uv\003\005\337\200+2\366\364}\3048\002\262\244\271#\241\252\217rKri\206|\216MP\272\270-\362\251\342\263$\344\324\014*\006\034\324L*?\255C#\2000*\233\362jH\023\'\221V\036\321\034gh\024\261\301\260\000\270\024\363\264\036X\na\220g\345\301\250X\314y\rI\347\262\2141\3152K\206\306\007J\245,\300\217\230Uc,y8\024(\311\315,\2011\363b\250\3154J\n\214U\007\205\246l\2514\337\260c\253\363CZ\021\321\2054[154v\236\3659\216(\323\007\255F\030\026\342\246\013\357O\n1OT\317J\231#=\352t\\v\253\021\2405b1\266\256\304Cu\025~\000\243\232\321\205\2061W\241\306x5z3V\343>\325a\007=*\344B\255\306*\364\006\257GV\3435m\017\025f3V\3435>~ZT|\232\260\204\036\rL\231Z\225\260\303\322\274\272\336\002\010\342\264R<-5\2075Vi\025~\361\342\261\265\013\360F\3058\002\261d\233q\353T\345\227\025_v\343H\344\343\002\252I\336\253=Wz\205\205B\330\250_\247\025RC\315@\331\317\02549\034\346\244i\317L\361Q=\303\021\200q\364\250\303\022rM#\310@\3008\250\274\307\354\306\232\306S\3375\013\254\347\326\241te\\\271\252\336|jNEW\227Pp~A\201Tf\275\231\273\232\250\322\310\307\222jx\245\224\014\002j\302\227=sR\250&\237\222)3\'\360\203H\"y\033\234\324\353m\264sO\021\324\213\037\240\251\0262\265*\223\236\225:c\275N\230\355S(\315XN*\344MW\242n\225~\027\255\010_5z\026\253\211\216*\344X\342\256 \300\2531\034\021W\3425n:\267\031\253IVc\251\213\014b\232\247\232\2367\344U\304~9\251F\010\3105\377\331"
-byte_png: "\211PNG\r\n\032\n\000\000\000\rIHDR\000\000\002\000\000\000\002\000\010\000\000\000\000\321\023\213&\000\000\003JIDATx^\355\334Yn\3430\014\000\320 \275\377\221k\014\246\235\026\250\006\256W1\224\370\336_\2354\213$S\244\344\370\361\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000`RK{\200Z\014\000\000\000\000\000\000\000\000\000\000\000\000\000\000\006\365l\017\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\0000-\367\232\000\000\000\000\000\000\000\000\000\000\000\000\340\005\374\254\245\256\245=\000\000\000\000\244g-\007\000\230\226\215\213-RA\000\3160\177\000\000\000\000\000\000\000\000\314\3075!\000\000\005\371\351\r\000\014\345i\001\007\000n\245,\006\000\250\340\255=\000\024ey\025\000\000\200\241(d\263\212\352\231\263\333\331Q\237o\030g\033\222\201\030\365\014\353\275=\000\000\034%\027\334C+Q[\2273\300j\003L\241K|\000\000 \231^Y_\257\327\005x\0101\247\365m\270\357%Ak\203Y\365\035\000$\246\353+X\273\361\342\312\341UB8\014l\337\t|4,0\032=\\\315\235=\276/\212\000\000@\200;\023\335\271\254\255\002\302\313\250%\001\240\013i\037\000\024\245\316&\001\303\020 \216\372\277\254$]\357\026\257P\310j\232\237$ \321\270\332/W\377\037\000\000\200K\276\3122\345\031\220\202`\304\303\246Py\253\233\004\000L\316\014\020.(\371\326\263\300\016A\021\tHD\212P\234\001\000@CM\000\000\231\231\251\001\200M\233\253\276\233O` \207\362\303CO\006\000\000\310GY\003\000\000\300>*\310\342j\r\200Z\337\026\000\000\000\356\347\266\313\000\000\000\000\014\312e#\000\000\300\005nF\367\032M\273\377\275h\341\253\272s\001Ca\316G\000\000\200\001]\330\257U\007\002T!\342\003\017\241\000\240\224\013\213\005\244wlJ7\026\000\n96E\000@g\252\221`~\001\000\000\0000\033\245uC\203\000\000\300Ord\000H\312\325\234\000\000\000\300\026\353\007\000\000@N\252\225\237\\\242\005\000\000\000\3005o\355\001\000\000\210f\033\270\032\033\335\000\000\261d\334H\302\001\352\222\007\000\000\324c\031\000\200\341X\300\270\211,\000\000\000\200\\\346(\371\325\333\000\000\000\003S\324\365\322\266l\373\367\213,I>\007\020\252\327\231?\307\362f1\275\006\003P\224\231`,\037\223\300\221N\333z\356\326\343\3743\354\364\373\313\007\327\371\005\3752\036\000\200\214L\336\000\325\324\216\374K\301\357_\357\033\003\237\"\327f\227\3107c\277\250~\211z\037&%W\201\016\336\333\003\014N\250d\027\247>\214@H\007\200\211\005.\224\313\376+\213\033g\244\245\254\000\000\000\200y=\255\377\000\000\300\334l\366\325\246\342\343\233`\320\3370m,2\324\243\317\0010\033@=\303\024(\264N\004\354\247\356\006(\313\024\000\220\321\275\321\371\336W\243?=\006\3779\261\326\001\000\0000\263\271\312\244\271\276\315\005\032\002\000\000\000\000\000\010b[\002\362q\r9\221\314\003\221>[;\366\034\277\275\207c?>p\320\3069\277\361\3606!\000\000\340\210\313\351\0270\034U\023\000\224#\355\007\230\333\037\020\217U\035\335\242\351\375\000\000\000\000IEND\256B`\202"
+byte_jpeg: "\377\330\377\340\000\020JFIF\000\001\002\000\000\001\000\001\000\000\377\333\000C\000\003\002\002\003\002\002\003\003\003\003\004\004\003\004\005\010\005\005\005\005\005\n\007\010\006\010\014\013\r\014\014\013\014\013\r\017\023\020\r\016\022\016\013\014\021\027\021\022\024\024\025\026\025\r\020\030\031\027\025\031\023\025\025\025\377\300\000\013\010\002\000\002\000\001\001\021\000\377\304\000\037\000\000\001\005\001\001\001\001\001\001\000\000\000\000\000\000\000\000\001\002\003\004\005\006\007\010\t\n\013\377\304\000\265\020\000\002\001\003\003\002\004\003\005\005\004\004\000\000\001}\001\002\003\000\004\021\005\022!1A\006\023Qa\007\"q\0242\201\221\241\010#B\261\301\025R\321\360$3br\202\t\n\026\027\030\031\032%&\'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz\203\204\205\206\207\210\211\212\222\223\224\225\226\227\230\231\232\242\243\244\245\246\247\250\251\252\262\263\264\265\266\267\270\271\272\302\303\304\305\306\307\310\311\312\322\323\324\325\326\327\330\331\332\341\342\343\344\345\346\347\350\351\352\361\362\363\364\365\366\367\370\371\372\377\332\000\010\001\001\000\000?\000\372d\361@|T\251.;\325\204\2375/\231\234Ts6qP9\300\250]\252\006|Tl\364\302\364\302\364\205\2517Q\272\215\324\205\251\205\251\205\351\206Jizc=F\317Q\263\324l\325\013\265B\317P\273\324\016\365\013\275@\357P\273\324\016\365\003\275Ww\252\362IU\244\222\252\311%V\222J\251,\225Y\345\252\362IUd\222\253\274\225ZG\252\357%@\357P\263\324e\351\245\351\273\251\245\3517Q\272\224\032v\352P\324\360jE5\"\232z\265M\022\0275\243mm\223\322\266\254\3556\340\232\372\210\3101L2P\262\363S$\336\3652\317R4\200\240\372\323$nj\273\232\201\333\212\201\237\024\302\364\322\364\233\351w{\323wR\357\244-Mg\250\231\3526za\222\232^\243/Q\263\324l\365\023\275@\355P4\225\023\275@\357P\273\324\016\365\003\275W\222J\256\362Ui$\252\322IUd\222\252\311%U\226J\252\362Uy$\252\362IUd\222\240y*\273\311P\273\324,\364\302\364\322\324\322\324\233\275\350\335\357F\352P\324\340\364\345jx5\"\265H\rO\n\027\"\264\255\242\351[\026p\005\031\"\264\341\030\300\257\242<\376)\246j\004\265*MR\254\325(\233#\025&\365\300\346\242\220\360j\273\265Vv\246\026\246\226\243w\275\001\351wf\223u!z\215\236\242w\250\231\3526\177zo\231M/Q\263\324M&*&\222\241y*\t$\315B\322T.\365\003\275@\362Uw\222\253\311%V\222J\255$\265ZYj\254\222UYe\252\262KU\244\222\253<\265]\344\252\362IU\336J\201\337\232\211\236\242f\246\026\246\356\244-I\2734\006\315\033\251w\323\203S\225\252E9\251\223\332\247\2122\354+R\336\016\000\305jZ\333\355\031\"\257\3049\305iZF\0168\257t\023Q\346\322\211i\3136*E\236\245\216~z\324\3516q\232~\374\217\302\240\220\325g5\021jM\324\271\024\264\335\324\205\251\205\3526z\211\236\242g\250\213\323K\321\2775\023\276*\007\222\241yqP\274\265\013\311P<\225\003M\232\205\345\252\362KU\336Z\257$\265^Ij\254\222\325i%\252\262KUe\226\252\311-V\222Z\256\362\325y$\252\357%@\362sQ3\346\230\315Q3\323\013SK\342\232\322S<\312O0\322\253\023R\250&\246H\211\251\222\002jd\2675n;S\351Z\026\266\230\0315\245o\000\004V\200\217\345\030\251\340\207&\265mc\333\212\365\345\2334\276n{\322y\370\247\t\352E\233\336\236\263\363V#\237\034\346\254$\273\200\006\206|\214\036\265Y\332\241f\301\244\017N\014\r.i\254\325\031zc=D\322T,\365\023=F^\233\276\220?4\311[\212\252\362T\017%@\362T/-@\362\324\017-Wyj\007\232\253\274\265\004\222\325i%\252\322KUd\226\253I-U\222Z\255$\265ZI*\263\311P<\225]\244\250\231\351\206@*&\233\025\023K\357Q\231sI\2734\340\245\251\342\006\306qN\020\023\324T\261\333\373U\270\255\275j\334V\313\351V\343\264_J\261\035\250\354*\302@\027\265O\032\325\310\027$V\2341n\002\257Am\355W\340\200\212\364\010\347\307z\224M\357A\220\322\tjE\232\236&\251\322n\0075b9\361S\211\201S\232\210\277\006\241v\246o\247\t)|\312C%F\317Q3\324L\365\023=D\317Q\263\323\013\322y\2304\217&ES\231\360sU\236J\201\245\250\036J\201\345\250$\226\253<\265\003\313U\344\226\253\274\265^Ij\264\222\325Y%\252\322KU\244\227\336\253I-W\222J\254\362T\017%@\362\342\242y}\352\006\224\347\257\024\307\2234\306&\221FM[\206\330\266\t\253\320\333\2008\0258\267\342\217$\016\325\"F=*X\342\315Y\216<U\330\220b\254\"\214R\343\232\236%\253\326\321\344\212\330\266\213\201Z\020\305\323\212\320\212,b\272D\223\245H\262`\324\202L\323ZL\032\004\336\365\"M\232\235%\251\322Z\231g\342\236\262\002\270&\230\355\362\324,\370jA%;\315\246\231i\215%F\322Tm%D\317Q3\324L\364\303%4\311Q\264\265\004\222f\251\313&\rWy*\007\222\240yj\273\313U\344\226\253\274\265\004\222Uw\226\253I-W\222Z\253$\265VIj\273\313U\336J\257$\225\003\311\216\365U\346\316qU\232Pz\232\214\277\245\030\315K\0349\0314\343\020\024G\030\335W\343\000%^\267\\\2408\251\366\361H#\3158ERF\233[\221V\202n\350*\302&\006*d\\R\371\1775Z\206:\320\266Lb\265\355\2008\2558#\315]\2111Z\213!\003\232x\226\236&\3051\347\006\231\347{\324\2517\275XI\275\352e\236\236.*h\347\354jO7 g\245E+`\212\214\311G\233Hd\246\031)\215%1\244\250\232J\211\236\242g\2464\225\033IQ<\265^Ij\264\262f\253<\265\003\313P<\225ZI*\274\222\325w\226\253\274\265\004\222\325g\226\253I%V\226Z\255$\225]\344\250\036J\202Ip*\244\216[\332\253H\371\357P\362i\3529\247.3VT\341z\323\032L\232X\317\315W\003|\240V\215\243e\005YQ\223R\252T\213\036jA\026jXSkU\301\027zz\255=c$\364\253P\307W\242\210\236\202\257\301\023\216\325\251h\217\351Z\220\306N3V\327\232\215\362\246\242i\217\2554\315\232\004\324\345\233\025<w\036\3652\317N\363\375\352D\270\367\2531\334eq\232V\223$s\232c>3M\363i\014\224\206Ja\222\243i*&\222\243i*&\2235\033IQ4\225\003\313U\344\226\253\311-V\222Z\256\322\324-%B\362UY$\252\322IU\344\226\253\274\265]\345\252\322KU\244\2235^I*\006\222\242w\305U\231\2306r1U\344rEBy4\001\221\232\t\367\245SR\003\305\000f\245\215y\253\321E\271sZ6q\376\354U\324\216\247X\370\247\252U\210\341&\254$\030\307\025:\306jd\200\032\235-\275\252\355\255\2139\351Z\366\366\001\007\"\256$h\235\252\324\030\354+F\010\367b\242V\347\212d\215\311\367\252r6\322i\236e\'\231N\022S\326\\T\3517\275I\346\322\211\261S\307q\317Z\224O\2200z\032y\227?Zc6)\276e\006JcIQ\264\225\023IQ4\225\033KQ4\265\023\313U\244\232\253\3115Vy\252\t&\252\362K\305Wi\260j30=\352\t^\252I%V\222Z\256\362\325w\222\253\274\225\023e\206zT\022*\201\313sP8Q\334\324N\352\005D\304b\252\311\311\250\310\342\224\251\034Rb\225E9z\324\250\265b(\3130\000V\254pm\214/z\277o\016\325\002\256G\036je\216\246\216\034\234\342\255\307\025N\221f\246X*\314\026\305\217J\322\267\260\347\221Z\326\366\351\032\361J\340\267\003\245:(\t\353W\340\203\221\305i\333BN8\254x\244\310\024\262H\007\025N\341\271\315V2b\224IO\017N\017R,\230\251\004\264\276m*\315\203\326\245I\371\253\002m\324\341/\03754\310\t\353Lg\307za\222\243i*&\222\242i*&\226\242yj\t&\367\252\322MU\236oz\257$\336\365]\346\367\250$\233\336\240y\263\336\2413`\363L\222`j\244\217\357U\244\177z\254\357\223\367\205D\304g\2265\033:\216\225^YO\255Ww\315D\315P\273S\013dc\2651\2054\214\212\010\346\220\217JJz\014\325\210\220\234V\235\225\267 \326\304V\300(5m \251\226<T\361\307\232\265\034^\325ac\253\021EVR\034\325\350\"\021\201\353W\243;\261\201V\024az\323\321I5r\336\002\304qZ\366\232ql\022+b\326\304.8\256\002\332~9\251]\267)9\252s\276\0235T\311\236\364\325\227\236je\226\244\017\232z\275<=\036e.\372p\223\006\245I\361R\211\370\2453\003MiH\347\250\250\332J\215\244\250\232Z\211\244\250^\\w\252\362MU\344\232\253<\365ZI\275\352\273\317U\336oz\205\347\367\250\036Z\204\315\357Q\311p*\254\22795^K\217z\254\363Tfl\3655\033KP\274\225\013I\315\004|\265\003\036i3\357K\214\322\021\232n(\244\013R\306\265~\326\014\221[6\261\005\305i\3068\025m\020b\234\023\'\212\267\004\036\265q!\317j\235m\372T\321\305\315^\206\016*\304v\345\215^\216\337\245Z\212\320\275]\203O^\365\245ml\221\364\0315\251m\021b+N\030zW\216\333\315\3163WU\216\332\255!\310aY\356\373M4IR\244\2652\311R\t)\342N(2R\211)\336f{\322\207\305;\315\245\363i|\3760i\206Lt9\025\033KQ4\265\013\315\357PI7\275U\222j\255$\365^I\252\263\317\357U\344\232\240y\375\352\007\232\240i\275\352&\224\261\353PI!\035\352\263MU\345\233&\241i*6\227\035\352&\234\372\324m1\246y\234\323\374\377\000\227\336\242\335\223J)\301\251q\2326\322\354\024\005\311\342\254\301\016H\255k8+N(\360\005]\2012*\332\256\005K\024l\304qZV\326\244\362j\364pc\0258\206\245\216\334\236\325z(0*\325\274\034\364\255\030\255\275\252\3446\276\325r\033ROJ\321\267\264\000\014\326\2041*\325\370#\3175\340\366\304\3479\255\030\237\"\243\230\355&\262n_k\232\204IR,\2652MS\243\027\351N.P\340\322\031sN\022S\204\264\341/\275/\231G\233\212i\227\336\232f\2464\325\003\317P<\376\365]\347\252\322O\357Ud\237\336\253I=A$\325]\347\367\250\036oz\201\346\367\250Zzh\226\240\236\\\325W\226\253I/5\023IQ\264\265\023IL2Ry\264\t*E\371\207Z\\\363\212r\232\225i\331\305(\005\217\0252C\201\232\263\010\303\n\327\264\003\212\321D\316*\354\021t\255\010\255\363V\341\200f\256\306\273j\334)\236\242\256\303m\273\265YX\000\355S\307\006j\355\265\2779\255(a\366\253\320\302}*\364P\340t\253\010\204\236*\355\274\035+F(\260+\347[i@\035j\364SzQ3\356\025\225zy\315U\017J$\251\026^kB\302\351\021\306\376\225.\245q\0236c\340U\024\237\326\246IsN\337\315(\226\227\316\2442\323\014\264\3235F\363Uw\237\336\253\275\307=j\264\223\373\325y\'\367\252\262\\Ui\'\367\252\355=B\363\373\324\017?\275A$\365\t\232\205\237\336\242\232l\367\252\222KP4\274\324O%F\322S\013\323\013Ro\245\017OW\251\025\263\315J\246\246L\236\005H\007\030\025b\004\030\351V6qDc\006\264\354\337\030\255\233Q\277\025\253o\035hE\037\0252\215\265j\004.FkJ\010:qZP\303\205\251\222\034\366\253p\333U\370-\353F\013p{V\2046\334\016*\312\333\373T\361A\203\322\257A\017\265^\212,\327\312\342\343\035\rZ\266\274\311\306j\327\237\232\251vASY\305\360i\004\224\365\222\237\347\020sH\367\004\367\253\320\342[2q\363\016\206\240\216~qS\211i<\332_7\216\264\206Zi\227\336\243i\252\t\'\367\252\322\\{\325i\'\367\252\357q\357Ud\270\367\252\322\\{\325w\270\317z\256\363\324\rq\357Q5\305@\363\373\324fjO:\243\232^3U\336Z\256\322sLi)\236g4\205\351\013S\013\320\036\245G\251\221\252\304x=j\334Q\026\034\014\n~\315\247\025n\025\030\251q\305>8\371\253\260G\202+^\315\366\221[v\307 \036\325}y\306*\304Q\226\"\264\355 8\351Z\366\326\371\253\253\027lU\210\240\311\351W\342\266\300\036\265z\013^\234U\350m\366\221\305_\202,\342\255\2545<PsV\322,U\313x\375\253\343H\356\303\3645j\033\202\033\255iC8q\357L\231\352\204\307kqQ\007\346\234$\247\211)\245\360j\354\027\330\217`\3075\037\335$\226\034\322\231\260q\232\014\352;\232w\2341\301\2443S\032lw\250\236\343\336\253Iq\357Ue\270\252\317qU\244\270\367\252\262\\{\325w\270\367\252\317q\203\326\240{\212\201\356*\026\270\246\031\363M3{\322\t\271\246\3136W\025U\245\250\313\363\326\230^\230^\224=.\357zc\032@\325*5X\215\252\344\'\245_\216N)\300d\346\254\304*\314q\346\255G\r]\206.\202\257\303\0161\305jY\260\310SZ\361E\2201Z6\320\217J\326\265\204q\305i\302\200\n\271\0149=*\374\026\370\355W\241\267\347$V\225\265\266q\221W~\315\362d\n|)\266\256$y\253P\305V\322\034\232\271\004\025\360\2142\354<U\370n\003\001\3175~\336c\236\265jG\371\001\315U\270\350\rU/\203G\231\357OY3J_\212\214\312A\340\325\210\216\340ri\246LQ\346\342\233\366\214t\245\027\031\357Q\275\307\275C%\307\275U\226\347\336\252Ks\327\232\255%\317\275V\222\347\2575Y\3563\336\253\275\307\275W{\214\367\250\036\343\336\240\222\343\035\352\023qQ\265\316;\321\366\240i\3136i%\223\212\255\346\363I\346\373\321\2734\326j\003\322\207\245<\322\001\315J\225f!\214U\270\252\354Uj1\232\263\022\325\330V\257E\036EZ\202\"\017J\323\202.:U\250a!\205j\333\222\240V\265\231\014@5\265m\026@\255+x\t\305jAm\323\212\322\206\r\243\245^\202\r\335\253F(6\201S\371Y\024G\001\315\\\212*\271\014\025z\013\177j\273\024\025\371\366AC\315K\034\230\357Z\326m\234\032\274\355\214UK\251;U\'\223\232o\233R,\274Pe\2464\265b\332u\311\031\244\231\3109\025\001\237\336\230g\367\246\233\232c\\`u\250$\271\367\252\222\334\373\325In}\352\264\227>\365U\356\271\353P\265\306{\324\017q\236\365]\347\347\255B\363\373\325y\'\367\250\032zg\3323\305F\323\025\251\240\271\311\353SK!\333\221U<\356h\363iVsR\206\336)\233\360i\341\252U\247\252\363S\306\234\324\350\270\253Q-]\211j\344IW!\216\257C\025hA\037J\277\0145\247on6\325\310\255\3623\232\263\014x\353Z6I\363\347\240\256\216\311x\025\271i\006@\255(a\307j\277\024Y\025z\336,\032\277\034u0\207\212\22689\034U\330\255\372U\350-\275\253B\033n:U\205\207\025\371\333qp%#\025\037(FkR\325\212 \3645p\\|\244\032\245sq\226\252\255.i\276m(\227\002\235\346\323\032J\201\247*\334\032\274&\0060s\332\251\3116\t\250\232\177zi\270\367\250\332\343\336\253\311q\216\365Rk\217z\247-\317\275T\222\353\336\253=\317\275B\327X\357Q\265\317\275C%\306{\324\017qPI>*\006\270\250\314\376\364}\243\"\244\212nj\341\233)T\214\237=9e\353NV\315L\217\203\232\225\200e\310\353DGuYA\315XE\346\247\2118\251\321\t5n(\352\3541\325\310c\255\010!\351Z\020\301\322\257C\rh\333\303\234V\204K\221\200*\3346\344\216j\3746\2758\255\033{\177j\332\261\210\341x\255\3735\300\025\255\n\016*\3641\373U\310\343\305[\214U\250\323q\025lE\302\325\350 \3168\253\360\303\200*\354Q\374\265*\307\223_\232&Ozr\312X\3435r+\362\212\024\366\253\037n\334\265ZI\362j?6\232e\367\247\th2\343\2751\246\250d\227\336\247\202\3531`\236\225]\347\250\032~z\323\r\307\275F\367\034u\252\262\334\347\275T\232\347\336\251Ms\357T\245\271\347\255Wk\232\211\256s\336\242k\236:\324ms\317Z\211\256=\352\'\237\"\240i}\351\206\\\367\247#\344\324\361\2229\251\374\354-V\022\376\363\255I\274\2065$rT\350\371\253\020\266~SS\"mqW\0253\203S\306\265j$\343\025j\030j\354p\037J\265\024X\355Z\026\360\023\216+N\336\330\361\305iAjH\351V\342\266\366\255\033k\177j\324\202\320\005\351\315\\\206\337\'8\253\360\333t\342\264-\355\361\216+b\312\3378\342\265\240\267\306+V\336,\201Z6\360\325\264\212\254\303\001$U\350\242\307\025j8\262EiA\017\312*\344QU\264\217\212z\'5\371\200d\241d\3058KS,\370\036\324\255&Fh\337\3054\311F\374R\031j&\232\242y\263I\014\330$g\2552Y\260H5]\347\250\315\307\2775\014\227\030\357Ud\271\353\315T\226\343\035\352\224\327\036\365NK\216z\325g\271\367\250\036\353\035\352#u\236\364\323s\357L7\024\303?\2754\313\232\211\246\301\251#\271\002\254-\330\"\245I\267\003P\226\303\324\301\370\316jHX\232\265\023sV#8z\321\2157\001W`N0jt\217\332\255E\035iZA\272\264R\333\216\225b+\\\366\255+k]\270\342\265-\355\217\025\255mm\362\343\275Y\216\323\236\225\241mi\216\325\243\025\271\030\342\256\301o\323\212\321\212\333\000qW \203\221Z\266\261\355\305hBGAZv\230\255(\312\216\225n\034\023Z\020F\r\\\212\032\263\024<\326\204I\200*\344KV\221x\247\252\014\327\345q\224\016\264\343&\361\225\350:\321\347\206#\024\377\0000\202*\302H\016\024\323Km4\205\363M/L/\232\211\344\250\032ZX\211\034\232m\304\237-g\3116;\324\rs\357QIq\357T\345\270\367\252\262\\g\275T\232~*\214\267\034\365\252\257s\357U\336\347\336\241k\237zAu\307ZSq\236\364\323?=iD\371\357Ly9\244I\252O7\025n\332\\\361RH\377\0005J\217\271jh\237mZ\211\262j\375\272\006`Mk[\250\000b\256$}\361\326\255G\027J\267\0249#\212\327\264\203\030\255H\241\311\351W\340\264\335\216*\364V\245q\307\025\243o\007N+N\332 +F\010\007\025\243\024@\257\002\246 )\025<R\250\305]\206R@\300\253\220\271\343\212\277l]\334\000+^\336\022\275z\325\215\376[`\324\361\310\371\310\311\025\243os\2023\326\265\255n\227\216kJ\033\205\342\255\307\"\372\325\330d\007\275\\\215\205\\\210\344T\250\274\327\344\373\277\251\253\026\222\205F\357\221U\374\334J{U\245\22384\345\237\r\351Ni1\216sL\363\262z\034z\320\322S\032N\rA$\234T;\362jF\234(\305U\270\271\005H\006\263\246\236\252<\376\365\023\334\344u\252\222\334{\3259.9\353U\245\270\310\353T\'\237\035\352\233\334\373\324\017q\357P\265\307=i>\323\357J.3\336\224\317\357J\267\030\251<\335\343\255\000\234\324\241\362*\325\264\2305<\215\223S[>z\212\270\2407J\263\n\342\264m\326\264\355\272\214\326\244_t\n\271\ng\025\247on\016\riA\036\334V\245\274]+f\312\034\2001Z&\317\010\033\265>5\tV\341\220\014U\270n\t8\305hB\354\325mm\031\306\342j\304V\244`\005\311\255\030,\345`01ZV\272l\214\303=+\251\3224\025\030f\255\033\3134\215p\213X\323[\261~\365\253\245\304\270\303\212\321}7\3149Z\210\332I\017<\323\026\362D\223\031\255;k\307 V\245\265\313`V\235\265\316\354V\265\273\006QW\241L\327\344\233\221\336\230\267&#\307J<\363+\014\325\270\337\013\315;~hv\333\0314o\302\001L2\323ZN\rA$\234TbN\365\024\363\325\031g\252SMT\344\236\253\274\365V[\214\325I\'\311\353U\345\237\031\346\250\\O\305Q{\217z\201\356=\352&\270\246y\376\364\341qR\t\262:\323\204\271\251\242\226\254,\271\342\245V\251\341\'5k&\247\26785z/\275W\342\033\207\025~\331pkV\005\300\006\264\355\323p\315iA\027\002\265m\023\245jC\006{V\235\2548\003\212\327\264P\202\2565\300\333\264Q\024M!\347\245h\333Z\202@\003&\265\355t\227\225r\022\265l\364)N\003t\255\353M\010\340\014f\265-<>\001\031\025\261m\242\306\0241\351V\323J\2107\313\320V\205\274\t\016\006\376*\314\237g\013\352j!g\014\274\224\0305f\013\030S\220\247\025mbU  8\247MhJ\365\006\251\0351C\023\216i\361[\204o\231qR\227\021\236\rY\267\270\031\030\255\375>p\340V\344\007+_\221\254\325\033t\244Q\216je\227\"\236\262R\311&S\024\326\223\346\3051\237\006\2432pj&\223sq\322\242\226]\242\251K=T\226~\265JY\272\3259&\367\252\3177\275V\232l\216\265B[\214\036\265\013O\270u\252\027\023\037Z\240\367\030\250\036\342\242i\351\206|w\247\245\316ju\270\305=f\315M\034\365f9j\314rU\310_\221V\301\315X\26785~\037\275Z\026\353Z\226\312\010\025\247\010\310\002\265\254\343 \014\326\315\2749\002\264\255\340\306+Z\326>+F,(\346\254\t\0168\2536Q4\256\000\031&\272}/\303\362\334\020YH\025\325Xx~8q\270d\326\365\275\204q(;@\037J\273\024Q\2020*\374\014\201\206\027\245[M\304\345F3S\305n\304rO5j;RF\0014\377\000\260\276{\342\257A\246.\320O5im\200_\224T\211\017\265XH=\251\376Fi<\215\2478\244x\003)\310\254\rM\232\335\310\355Ri\222\231\260+\242\265\337nA\355]5\204\276d`\346\277#\267SKR\027\305(zxzql\212Wp\361\002O#\212\204\276H\317Jb\202C\212\215\237h\346\251M.I\252R\313\326\251K/\025Ni\252\234\263{\325Y&\367\252\362M\221Y\367\023sP,\371\004\n\253u.\005e\313>3\315Wi\371\353L3\346\230e\367\247$\243\326\254\3070=\352e\224T\261\3123V\342|\325\310Z\257[\234\221Z\010\271\002\254D0\325\241\000\371\205j\333&Eh\333\2561Z\266\321\356\000\326\325\232e\000\305m\331\247\312+f\010\006\320j\334XJ\271\014[\2715\241\005\223N@Q\232\353\264\035\r\"dv\373\335\353\264\265)\022\200\024U\250\311\' U\310\324\2763\322\255\303\017\265^\202\037j\320\202,v\253\321\303\234U\310\241\305YHw\221\305X\021\355$\017J\231-\317\245M\r\250<\232\227\311\003\2659`\245k~*\027\213\212\301\327-w\200\000\346\233\243\332\030\216H\256\2628\303[\201\214\326\206\224\205x5\371\034[\024\322\324\205\250\335O\rK\276\224\270#\007\247Z\221Q@\246H\341FMg\\M\222j\204\262\342\251\313%R\226J\2454\276\365JY*\254\222\325i&\342\250\\K\357T\376\323\265\272\324WW\005\227\035\253&i\260\314*\263MM3S\014\364\013\216z\325\210\246\317z\260$#\232\236)I5\243o&qW\242j\320\266nEjB\374U\250\2715\243l\271\305k[\257\003\025\245n\207\002\266l\342\371A\255\2738\362+f\316.\005kF\333TU\253X\374\326\006\265\222\324\234\005\344\327S\241i\233c\014\302\272\213H\004`b\265 N\225~\024\034U\370S\245^\206>j\374\021f\264`\213\245]\212\032\260\230^\rh\331\306\010\007\025)\267\006A\352j\332\303\212\210\2349Q\332\244D\305M\260c\2574\2052qH\321*\236zU\033\233!3\223\216\0056\033UG\000\n\350m,\263\020\'\245^\267\264\330\331\257\307\2268\246\356\246\356 \322\356\317zpjB\324y\224\345\271\3320j\275\305\316\341\201Td\223\255T\225\352\224\322pj\214\322U\031\245\252\222IU%\222\252I\'\025B\342^\265\2354\265\013\\ey5B\342O\336Uf\222\230f\246\264\240\321\274c\255X\202aW\225\201^\265,m\216\225~\325\372V\224&\264my\"\265\240N\005]\205qZV\303\245l\331\257J\327\266\217$V\315\224u\267e\0275\265l\241G5iF\367\000V\335\215\267\312\240u5\325\351:Z\340\026\256\216\3325\\\005\351ZP/J\320\201qW\340^\206\257\302\265\243n\235+F\336:\321\202<\342\264\"\213 R\275\271\0075\245a\0032\364\253\266\366\331\230\203\330T\356\233MUu\006S\201R\242\343\212\265\344* -\311\250\223kg&\232\320\356$\347\002\231*\215\230\024\266\226\r#\203\212\337\216?-@\364\253\nk\361\261\372\324E\260i\245\250\r\315855\236\233\276\232\317U\244z\253#\325Yd\305P\236J\2434\225JY:\3259\037\255U\222J\2514\230\315f\334K\326\263\346\222\2534\274\325K\227\306\rUy*&\226\232e\342\225e\367\2530\267\"\264\242`S\255K\033\220j\365\263\363Z\326\307 V\235\261 \212\327\267n\005h\3003\212\324\265^EmZ\250\030\255\233D\316+f\316>EmZ\256\322+C~\000\305h\351\361\377\000\023WS\242[\231\244\007\025\326\302\241\024(\255\033n\325\247\005hCZ\026\353ZP\'J\321\267Z\322\201zU\350\027\014\017j\324\211jW\302\257\326\265\364\300\257\030\035\352\300\214\306\305\217z\257p\314N\026\241TpzU\250\343$\0169\247\313\033(\"\263\335\0369x\316\r]\202\325\345\0035r=;\246kB\013m\200`S\344\214\342\235\022\223\327\275~6\310*\253}\352c5\033\272R\207\244f\246\356\246;Ui^\252\310\325Rg\252\0237Z\2433U\031_\255U\225\352\234\217\326\251\316\365\233q\'Z\316\232LUs\'&\253]?\311U\013\344T,\336\365\0339\241$9\253\260MW\243\232\255C&Mi[\021\305j[>\010\255\213v\334\006+V\334p1ZV\331\342\265\255z\212\334\264\031\255\2535\351[\226\213\310\2554\371\000\255;\010\274\323\270\364\255kX\367\310\024t\025\332i1\010\"_Z\327\201\267\021Zv\307\245i\301Z0v\255Kq\234V\245\272\347\025\247o\037J\321\202>\225~\004\344\n\276\243h\024\256\234\203Z\232a \214V\231(\357\264\236MA4\001\037#\245U\270\275Kc\311\252\315\342DS\362\200\r1u\243q(\036\265\277mn\223F\247nI\253\361Z\355\351Vc\207wj\263\035\276{U\224\262\r\214\3243\333\2428\013_\213\256j\254\234\032\201\2174\240\344Q\232\t\246\023\212\215\2335ZSUdj\2473U\031\233\025Ff&\250\312j\234\255\214\325I\033\031\2527\017\214\326l\357\326\263\347n\265X?5\005\303\374\246\251\227\342\242g\246\026\245W\305X\205\371\253\361t\006\256\300rEi[\036\225\255m\332\266,\373V\325\243\014b\264\340\000\342\265mF1[v}\253v\314p+n\330`\003WP\371\214\000\256\202\3022#\000u\255\2552\337\016+\247\201\370\025\245l\331\305j\333V\244\006\264\255\273V\255\250\351Z\326\300qZ\226\344f\264\355\3008\253\361\2460j\332\034\214S\337\356U\273\'\333Wcm\315\223\326\237;\225\\\226\342\271\335G3\277\313\322\260na\222)\017\\V\357\206,\315\305\302\226\034W\241\333[,(0*h\243%\263\332\255\254\177/\024\221N\"|61R\317v\n\341*\251r\315\223_\214\016*\254\235j\273\234\032D4\247\2554\2654\232\211\232\241\224\345~\225NST\2465JcT\2455Fn\246\251Lz\3259\217\006\263\356_\255fN\375j\204\257P)\033\271\351P\\\034\203T\230\342\242cL\311\245\006\247\211\260kF\007\371j\3342`\326\245\254\235+b\324\347\025\265i\236+b\321\t\255kd#\031\255ka\322\266\254\3061[\266\207\201Z\360\310\002\n\321\323c2>\356\325\323X\340q[\366\200\"\203\336\265-\337&\265m\237\030\255kW\351Zv\355\322\265m\210\342\265mZ\265m\317\002\265-\273V\275\2475\247\022\356\305YX\261R\233r\370\247\306<\231\225kb(\025\227 \323n,\013\201\311\246\246\211\033\016z\326f\255\241l\004\201\305M\341\310E\274\340\032\355\342@\350)\340\005\372S\267\202q\232\215\243\004\361HWh\250%f^\225\370\333,%sTgLf\251Hy\246#sR=F\307\024\302\334TL\330\250]\261U\245\252SU)j\234\325B~\246\251M\315R\230\326e\313u\254\313\203T%<\232\20395\024\247\255S\220\365\250Y\250\355H\016jD5r\007\"\264m\2715\257j\275+b\323\202+r\317\234V\345\242\343\006\265\340\005\200\255Ke\300\025\251j\330\255ki\360@\255k@\362\001\307\025\267f\346 +\243\323A|\032\336\204\343\025\247j\330\305j\332\267J\325\266n\225\251n\375+R\331\361\212\325\266\223\245j\333I\234V\255\254\230\305lZ\311\322\265\355\233v+A0TU\220\301@\252\2238\363\207<\326\225\255\336\330\210\357O7\216{\325\253k\206,\t\253w\361\211\240\007\025\207\033\213{\205\372\327Ykr\004j{\021H\327\031r;T\311\353R\323[\245B\374\214W\343\324\261\202\rf\\\301\214\326d\321|\325\tM\246\225\333\007\006\232\334\212\204\365\246=WsU\345<U9j\244\242\250\314*\224\302\250\315\306k>\341\253.\344\3475\2339\316j\204\246\243\217\357\032\257/\014j\244\207\223P\236\2643`R\006\251#<\325\3109\"\264\355\3060k^\321\272V\275\247\314EnY\016\225\273hzV\345\260\371Eh[\364\253\260\2768\025\257d\002`\267Z\335\264\272\000aG5\263a\013N\343\322\272[\007\020\2463\315j\333\276\356Mj\332\267J\325\267|b\264\355\344\002\264\255\244\311\034\326\265\273\364\255;y\000\305j\333I\322\265-\245\306+f\316L\342\266\255%\305hD\373\273\324\354\347\034T+\031f\311\353V\242\214\212\271\0149\255\013x\000\253\336V\370\210\366\256[RV\216\353\217Z\332\322\357@EG\255\024\303\310qW\023\201\212r\365\241\217\025\013\032\374\201\224sU&\21405\221s\026\326<UFC\326\253\27199\250\322^\306\221\216\r5\271\252\362\n\254\342\253H*\234\302\251\312\265F~+6\341\2536\341\272\326e\301\316k6v\306j\224\247$\323\023\275V\233\2065U\307&\241#\232\206V\371\261H\rO\031\346\257[\363Z\226\243\245k[\016A\255\2331\214V\355\231\351[v\234\342\266\355\016@\255(\360\242\254\331\235\317\223\332\257\245\307\317\201]&\221l\316\001=\353\253\264\333o\036\001\346\256\333\311\226\315lZ\277\002\265\355\037\212\324\205\370\025z\0311\212\321\266\233\2475\257i.qZ\266\362r+N\332^\225\253m\'J\327\264\233i\025\265i.Mj\301&p;\325\324\005\252e\214\251\344U\230\322\256\302\265v!\212\271m\202\307\351Xz\235\240y\230\372R[A\205\r\351Zvm\336\257\243S\325\363\221\357A=j\007nk\362\032QU_\256*\225\312\003\236+6Q\315S\270\0035P\360i\373\203\217qM=*\'\305@\353\221U\245^\265NU\252S\214Vm\317z\313\270=k2\344\365\254\331\332\263\347\346\251IL^\246\253K\313\032\201\206j&^*\261R^\254Cf\322s\332\247\373)\214t\251\340\\\032\322\266$b\265\255\201$V\325\240\350Mm\331\366\255\333#\322\266\255\201\300\305Z2\025\034\324\326\367[T\363Z:o\357e\014zWccrQ\000Z\325\265\235\231\206MlZ\277J\327\266\223\245kZ\312\000\255\010\'\340U\350g\311\025\247k\'CZ\326\222r9\255h%\351Z\226\322`\016kZ\322Pq\315jZ\3167\001[V\262m\301\025\263h\344\220\325\251\004\343 \036\265\243\021W\030\251\204e\017\265X\212\254\207\332)\326\327`;sY\232\205\360W|\367\252\260_\371\273QOZ\335\264\341E\\\r\212T|\266i\345\261\232\201\217&\277#\245J\251\"`\325IW \326e\304eI8\342\263f\3475\\\212\217v\326\251M1\227\212\201\227\025^E\252\262\256+6\344\365\254\273\232\312\271=k.\343<\326l\307\232\243(\346\251\310)\230\300\252\3149\250\212\3224$\256j\005\217\346\344V\265\222\014.j\314\221+d\016\225^8\2005v\005\301\025\253j1\212\330\264\031\002\266\254\307J\333\263\343\025\265n\370\025$\262dTPH|\300\265\323i\253\264\014WId\377\000-k\332?\"\266m\244\340V\255\274\225\241\014\376\365~\t\362\0075\243o.qZ\366\262\342\265me\344V\255\274\331\255Ki\270\353Z\266\222\364\255H\217B+b\302\347v\024\327Ce.\320\005h\306\33785~\033\235\204V\275\264\213:T\245<\266\366\251\0226\234\354^\264\347\323\332\325\t\316I\025\312\370\201\332%R:\232_\017Fd\303\032\353a\371TT\205\350G\371\261R\0318\250\232J\374\232\225*\254\221\3259c\353T\356!\334\246\261\256\342\3300*\213\n\211\305\021\311\374&\245\307\025\023\256j\t\023\203Tn\006\005e\334\212\311\271\357Yw\035Mf\334/Z\316\235z\325\031\205T\220TdqU\234`\232D\214\261\251\214x\\zT\036N[\245[\205v\201R\311 Q\212H\216M]\201A5\247\000\255{1\300\255\233.\010\255\253n\225\243\034\205EJ$\335Kn\300L\t\365\256\237OpTb\267,e\347\006\266\255d\344V\255\274\234\326\244\022\340\016j\3543g\275h[\313\322\265-\245\351Zv\323r9\255ki3\216k^\326N:\326\245\253\216+b\326A\201[\026\257\3235\245\027\312C-l\330\334\226 f\267#\227\0305j\'\311\253\326\323\264,\010\255\350\034\\\302\rO`\0147\004\236\365j\357/\013z\327\'\342K\"\361+\001\300\244\320\223d*+\240F\342\202\364\201\271\315K\277\"\253<\3376\017Z\374\256\222,\325g\202\240\222\337\332\251\315o\301\254;\373|1\342\262\245\217\006\241d\310\250\374\254\034\325\200\231QLd\340\372\325y\227\0035\235p:\326M\317z\313\270\035k2\341z\326t\353Y\363\255P\231*\243\246j6N*\274\211\315>\004\306jO/r\223M\tM$\251\250\367\357j\263\n\326\205\272\363Z\020\036EkZ\234\001[\026\235Em[\034\001W\003qR\243\374\264\330\344&e\002\272}9\360\242\266\255e\303\n\334\263\227\245j\333\315\203Z\020\315\234U\330e\255+yzV\234\022\364\255Kis\216kZ\322~\225\261k6Ei\332\315\310\255\253I\272s[\026\262\342\265\255\244\334\005jZ\276\302\ro[\260\226 \001\346\256[6~S\324U\3458\025r\303S0\037/\031\004\326\374\027J\n\266z\325\271\245V\217#\275b\353\314\242\323\025\235\245\034 \255u\223\212<\312v\372Q%%\304A\243\363\007Q\326\277.\245\202\253\264X\250\035=\252\254\320\326E\375\256\354\326\025\304\005I\315Uh\3526J\221:b\207L\212\247p0\re\334t5\227p\275k:t\353Y\327\tY\263\245g\314\225Fd\346\253\030\262j)\023\002\253\230\367\032\230C\201\2009\247In\321\257\265F\220\344sL\270\217\013\357U\241\214\212\275\nb\257\302\265v\005\371\253Z\333\265kZu\025\255nzU\321\322\237\t\311\"\246\267@.A5\320Y\234\001\212\324\267|b\266\354\344\340V\245\274\225\241\014\225z\t+J\332NEiC\'J\324\265\223\245k\332\266qZ\366\256EjA/J\326\264\233\2475\265iq\310\255\253iq\203Z\320>@\255{\t\366\220+^1\270\206S\315^\214\357L\036\rX\323b\016\344\267j\325\332X\214v\253V\323\231\030!\350+#\304\327\037:\240\246X\035\261\212\276$\342\223\314\245Y}\351\333\375\352A&Q\327<\021_\231\217\017\265Wx2j\026\200\n\251<x\315f\335C\220k\n\372\337\236\225A\341\305B\320\360q\326\201\023*\340\201Q;\024|\036\235\252\235\310\315e\334.sY\363\245gN\225\235:u\254\371\322\250M\035P\232:\203\313\250%\210\365\246\307\0179\251\241\205K\023\336\246h21P=\261AUe\2148\301\250\243\207\025r\030\252\344i\264U\253u\311\255;u#\025\255j1Z\220qW\223\221NE\303\324\355\230\3105\263\247\313\225\034\326\254\r\322\266m\033\345\255+y9\025\243\034\230\305]\267\222\264\355\345\300\253\360M\222+Z\326^\225\265e.1\232\331\212PT\021\326\255\3036\010\346\265\255&\367\255\233I\272V\335\254\374\n\333\261\234\025\000\365\2558\'\001\206\rm\332O\225\034\326\224RdU\333V\332\331\255\024\227\006\255\304\341F\356\365\313\352\367\006\343R\306x\006\257Z\266\024U\235\364\206JA%<K\232\2229:\212\374\336|\265Wu\371\275\2527@\001\346\252\274j\315\315Q\271\200\020p8\254[\273B\306\250\315`q\300\252\302\324\251\344Sd\212\252\\\301\225\'\270\254\311\324\342\263\346\\\325\031\323\203Y\323\245P\232:\317\236*\243<UFh\252\273EP\264{\210\030\241\342 `T\221E\264T\255\205\025Zg\315Vhs\223\212`\217\025f\024\253\033\t\305\\\264\217\025\247\002\364\255+q\214V\234\0035z!Sm\357N\037\274\030\255]>2\2522kf\014\n\322\266~kN\335\273\325\330\237\232\320\267j\320\211\372sW\355\344\306+Z\326P1Z\366\222d\212\327\206l(\346\256\305?\"\265\255%\3169\255\233YzV\315\244\335+^\326_z\327\266\227\245j\332\336\210\310\004\361[\026\267\"N\207\"\264!\234\2560x\253\320\334n\031\315Y\232\360G\003s\316+\231\205\374\333\246s\3175\261\013\340T\276e4\311G\231J$\305K\034\265\371\326\310j\027CU\344CU\244J\255*\022*\214\260\363UZ=\244\344f\252O\021c\234UG\206\252\334E\301\342\261\356#\344\214Vd\311\203U&\217\212\317\236:\2414}j\224\321V|\361\342\251I\026j\007\213\216\225\032\301\311\342\217 \263t\247\3718\250\245\214\232\256\320\0269\305K\035\266\374qN:\177\034SE\267\226zT\321\245[\2011Z\020\247J\275\002\364\255\030\001\342\264a\253\000qL\013\265\301\025\261c\312\212\324\204\326\205\263d\326\235\273U\270\233\232\275\023\220*\314S\020j\375\274\347\212\326\265\237$s[v3q\311\342\264\343\270\316\000\351W\355\246\311\025\257i.1[\026\223t\346\266-&\351[6\222\364\255{yx\034\324\302B\356\006q]\006\234\306(\302\326\264Rg\025ie*8\254\333\355BS\362g\203RY\r\252+I$\300\247y\224y\224y\224\242J\2229pk\363\366D\305@\351\355U\335*\274\211U\244O\312\252<|\221\212\255,UVH\252\244\261\342\263\256W\322\261\356\301S\300\315e\334r\346\253\310\231\025Jx\252\214\321U\031\342\306k>X\267\032\255,\030\355UL98\247\233m\213\234SV\037jSoQ\233\\\236\2245\237\035)c\264\301\351W\"\264\335\332\222]3\'\245Vk#\037j|\021\035\325\241\024X\253\221&*\374\002\257\304\274T\340`PFMhX\223\200+Z\002I\255\013~;V\204\007\200j\334M\310\253\321\277\025b#\3175z\'\002\264\255\037\221Z\366\367\033@\253\366\363\022kN\326r\254+j\322\\\343\232\327\264\224\006\031\351[PH\027\005O\025\257i7Nkb\332P@\253\366k\346\\(=3]\034cd\270\355\212\2215\010\343l\026\253\261\337\251\\\346\250K/\235>{f\257@\330\025dKK\346\322\211)|\312z\275H\217\315|*\366\204\214\342\252KnA5Y\241\347\245U\236-\247\030\252\217\021\367\252\323G\216j\254\242\252H*\224\303\212\315\271\\\346\263\314;\211&\260\356Sl\314=\3523\037\025ZX\252\234\220Vu\314]\252\253[`t\252\263C\236)\022\310(\311\034\323&\267\364\024\301o\2008\243\354\304\362h\362=\251~\317\305\t\006\343\214U\230\255H \212\274-\303\307\323\221P\275\206\361\322\253\235<\306r\005L\220z\212\261\0345a# \325\310\201\305N\001=\252x\241\'\234U\373U\n~\225\243l9\315i@\277(\253q\364\2531\034U\310\317\312\rX\213$\361W-\301&\264\341\220 \000U\310&\311\353Z\226\262\364\255X[v\017z\323\264\270\306+b\332|\343\232\331\265\237\2475\263g?Nkj\326~\0075\261\246\334\004\233&\272kf\022/\231\236\265\231\252@Q\303\251\371I\251l\331\231\002\2268\253\361\240Z\260\217\212\224KO\022S\204\224\360\371\247\253\324\210\365\3614\322\000\010\252.\334\363PH\340t\252\222?Z\253!\315S\225Kt\252\223F@\355T\345F\3015J@Xt\254\371\34289\030\252,0\rb^\307\376\220N:\324~VEC$URX\275\252\233\332\344\344\212\253q\026\321\322\240\216\317vX\212l\220\234\340\n\215\355\360\207\216M4[\361\323\232\r\266i\277e\305/\331\317\245*\333`\364\253\t\036*D\0305aP0\024\255\000\364\250\214\003=*\3046\252EN\266\2438\251\343\267QVc\205}*\300\213<\001C\017(\200*\375\2708\031\255H\324\355\002\247F\355V\023\214U\310\233\214U\330_5r\'\355VQ\200\357Va\223\006\265\254g\000\363[\020J\0161ZV\354\t\255Kc\216\225\253k)\030\255\213I\372V\325\254\335+R\322\343\0149\256\256\306\340\213r=\252+\373\201\344*\236\244\321e.\334\003ZK.E=e\251\004\264\365|\324\201\300\247\253\324\213%J\217_\023\3149&\251\312\300f\251\313(\025JI\200&\253Ir*\264\267 \n\243-\316MW{\232\200\270c\305E4[\201\342\263%\207\004\212\316\275\201H\317qT\302\202*)#\252r\200\rA \310\252R\303\274\364\251\222\323\021\364\250\236\316\241k|\016\224\337\263dt\243\354\270\245[`h6\234\322}\227\232C\001SJ\261{U\210\343\305K\345qLhFx\251\"\\T\313R*\232\232!\203W\"\003\255D\356\036o\245\\\201\271\025\246\222t\315L\215\316j\324m\322\254F\334\325\250_\232\273\021\343uL\222d\325\350[\201W\255\333\007\255lZI\322\265\255\245\344V\255\264\275+V\335\263\216kV\325\272V\275\254\244`V\2242\343\006\266\364\335Q\261\260\236*\335\305\307\231\036\017^\324\353yH\357Z\021Np9\251\326Z\225d\367\251D\225 \222\236$\247\254\231\251\322J\370\276\341\272\326e\304\230\315f\334I\311\254\351\24695RI\210\252\322LMV\222J\204\266ic\034\324\356\233\227\212\247=\271*Me\334A\273\"\250\313hb \343\203Lk}\313Y\363\332\260n\225\t\200\221\322\221m\366\366\247\210\370\351P\313\027\265W\222\023\212E\204\342\232\326\364\236A\002\232\024\255<a\2527#8\3055W\236j\322\"\342\236TP\020\032@\270lS\302\324\250*U\025a8Z\2451+q\327\212\322\265\354kN\"\031y\353S\240\342\255G\322\254G\332\255\305\305YS\232\261\037j\267\013\342\257\300\365\247o&\010\255[i9\025\253m/J\326\265\227\221[6\222\364\255X$\351W\343\233\013V\255f\"E\364\315l\313.\000\036\325,\022\344U\330\245\253Q\313S\244\202\245\022S\326J\220IR,\2252?5\361\235\323u\254\273\203\326\262\356\033\255g\314\335j\234\215U\235\252&\031\246\205\247\257Z\263\030\342\234\326\371Rk\032\3460\256ER\272o\335\355\305E\034\213\264\206\\c\275V\221\222B@\250\315\270\013\234Tm\006E3\310\3155\255\363\332\242kL\366\246\275\266\301P\264x\2460\342\253\310\330\250K\212ap*30\024\013\275\275\352T\275\315K\035\330&\247F\337\315L\253V\026\335\230p*U\260\227\322\236\266\314\235H\250\'\265-\310\2536\237\"\341\252\354O\310\305_\210qV\243\006\254\240\253H8\251\307\0252IV\"j\275\003\364\255;w\340V\235\273\326\255\264\235+Z\326N\225\257i\'J\330\265}\300V\214c U\270\001R\017\275_\363\2677\322\254\333\311\305^\212J\264\222T\351%L\262T\212\365\"\275J\257S#\327\3077#\255e\334\360\re\\V|\335MS\222\240aM\013N\021\346\205\217\346\253\360\333\022\0056\372e\202-\253\367\215aK\226$\325+\210\213\236\005Ux\335F1P\254$\276J\325\203\025E\"b\230\253\3159\242\3435\021Zk\240u\367\252\023\251SUY\260*\244\317\326\252<\270\250$\270\305Vy\311\246\371\204\232\236-\355\320\032\320\202\322f\306\024\326\224\026S(\031C\217\245hAc#\014\3545a`\221;\032\2272\250\345MR\270\224\202wg5V;\354>\030\344U\320\302A\225\306*\324\033\200\351Zv\255\225\031\253\250*\312U\224#\025(\351NNO\025f/z\271\t\255\033w\306+R\331\262+J\335\361\216kZ\326N\225\257j\371\305lY\311\203Z\366\357\221Z\266\252\036\022s\323\232\256\327b\'#5=\265\372\221\214\326\235\275\300lsW\242\222\255\243dT\201\252Uj\221^\246V\251U\253\344Y\343\316k*\356<f\262.\027\255f\316\265Q\326\241e\240-;\245X\265\203\3149=\005M4\273F\026\263g\206I\333-H\272y=\251[O_Ja\323\224\216\202\253M`\024\036*\204\220\355\'\322\252\310\273\217\025\013.\323R\006\004S$\003<T8 \373T7\021\006\031\254\331\223\031\025\237p\275j\204\252M2;7\231\260\007Z\277\017\206\346\223\037)\253\366\376\021\225\230|\204\217\245t\272g\202\001\332\305@\365\315t)\341x\"\213\260j\226=\036\335T\002\006j\177\260B\006\024\n\212M&<\356\030\315 \322\223\030=\353\033U\320\202\222Pd\032\347.tgF-\264\212[[IC\200\001\255\350t\346x\200\350{\324\360\3324x\006\257\307\027\035*\312E\305\007\217\245*\314\007\031\251\222Q\330\325\210\344\315^\200\212\320\204\214\n\275n\370\255KY2qZ\226\317\203Z\366\222d\212\330\265~\230\255[y3\212\325\262\230) \236\242\252\313n\3573c\246jX\354\244Nz\217j\275k\346!\343\221Zv\363\347\031\353Z0\311\232\260\016i\352\3252\265J\246\245S_*\315\027\006\262\257!\340\361X\2671\3435\227:rj\233\246\rB\313M#\024\306\342\264\254X\030\033\007\232\226;_0\232\220\331\205\355H\326\370\035*\264\221m\250\030\340\323^!\"\326u\325\260\301\343\212\3111a\315V\234\000MT3a\251\333\367P*9y\025Jh\263\232\245%\243Hp\005I\006\201$\25420+\250\322< \2743\014WW\007\207a\211\027\"\254\255\234p\214\005\006\206m\2358\250\213\223\336\241vaQ\026`z\322y\344w\245\027\'\326\237\346\253}\352\253wo\024\312x\002\251[\332\256\376\235+EaU\031\357H\341s\322\237\030\305N\203 \325Y\246Q\221Y\222\\\235\334U\233{\254\220\ri\301( V\215\264\203\"\264\340l\325\310\233\025\241l\374\212\324\267\223\245kZI\310\255\233Y1\212\325\267\223\245i@\371\253\321\r\3075z\036*\307\226>\362\360i\254\177\210p\302\254\332\334g\275iD\331\002\246\025\"\366\251\224\324\252k\346\033\210\375\2532\3610\246\260\356\243\353YS\307\212\241*\363P2\324ei\215\021=\252{V1\214z\326\326\2367)\315K\"\222p\005G\3441<\364\252\3271m\004\342\250\030I4\215\031Ze\314@\302Ms\376Qi\210\3056\366\304\210\211\002\271\367]\256jt\\\323\366\340Tn3O\202\301\356O\312+sM\360\3039\037-t6\372\0046\340o_\232\256\254i\020\300\034\nG\234t\315@\363\016\325]\230\261\246\347\024\307l\324L\325\033T,\330\246\371\207=i\255&z\232\026P\247\201R}\243=\371\245\017\232\235\033\245N\207\000\325\013\370\\\002\313\322\262\262A\346\245\216J\320\264\234\214\003\322\266-\311 \032\326\266c\216j\344rsW\255\236\265m\232\265m_\245lZ\311\220=k^\331\376QZ6\357Z0=i[\277J\272\203#\"\2334$\374\313\326\241\001\243;\207\036\265\245gp\010\034\326\214GuM\267\035\351\301\200\251\021\301\353_5\3163Y\227`\021X\267j0k\032\351z\326|\213\223Q\024\311\251a\2632\036G\025pi\343\035)\321i;\333\245j\333\351\315\n\205\333I$!\r\002 \027&\250N\252O5Y\325\024\034\016j\234\261\226\'\024\010\267&\rVm>8\311lsT\357\343\002\006\372W\0374d\314q\353S\305\021\305\022\220\253P\306\246I\000\002\273\017\017\350\316\352\245\224\201]:[\375\235p\000\250%~\271\353U\245sP\026\037\215F\306\230^\243g\367\250]\275\352\"\324\205\35269\250\230\342\230M0\265>3\223S\241\251\321\252t|\n\227*\351\202:\326E\375\250F\312\212\246\251W-\301\310\255\253\027e\300\307\025\261\003\374\242\254)9\253\326\315\322\265\255\237\030\255[W\351Z\366\255\322\265m\344\351Zp>\000\255\033w\351ZV\357\322\264\255\3335m:\322M\010+\221TQ\374\231=\253Z\326\340\260\030\253D\263z\323\323=\307\025*\360+\347\031\216Ee\335\036\265\217w\336\262.W9\252\016\274\323\355\355L\215\322\265a\263+\2161W`\261.y\025\257i\247\242\014\220)oBE\031 V3\2171\252;\203\204\300\254\331\006j\264\253Q\343?Zr\216*)\243\334\rcjh\3026\035\253\231\223lls\326\241k\200:\032#\205\356[\200Mt:.\207\231\025\230\034\327on\213m\030\003\265Csq\357Td\220\363P\261\317Z\215\210\025\023\265B\362T\017%D\322TM\'\2754\311\212cKQ\2313H[4\231\247\241\301\251\321\261\326\245W\251\003\361RG-GpC\325U\210n\253q\"\016\225\241m(N1ZPO\307\265YY7\021W\255\337\245jZ\266\354V\265\261\306+Z\331\372V\255\273\326\235\273\361Z6\355Z05h\333\311\214V\204o\220*br\275k:\352=\255\237Z}\225\301F\332kb\031\262\005XYs\326\200y\342\276s\235\260\246\262\256[\255e\\\016\246\263g\\\346\252y\005\233\245mi\366\003\000\342\264\322\327\'\245Z\206\337\025)8\004\016\225\237|\373\360\243\265T\021\361U\356\027\"\250\272\346\2420\356\250\332\r\204\322\004\004{\3243.+#T\000\306k\224\276E\004\200r}\252\265\275\223H\343#\212\3514\353\017,.\027\223]=\224Ko\020 sR<\205\252\ty\252\355\234\324r\022\006*\264\217\212\256\362\324/)\250\036J\211\244\246\027\250\313\323K\323\014\224\273\351C\373\323\204\225\"\311R\031\302\323\222l\367\251VNhg\007\2751N[\336\254\307\"\216\265n\t\320\340\003Z\020\363W\241Z\273\017\030\255;g\306+Z\325\267b\265\355N1Z\226\347\245j[\266p+J\003\322\264!n\225~\026\255\010\037\"\254)\315G2eH5Er\262\326\275\261\312\214\032\266\244\216\265\"\032\371\302s\301\254\333\203\220k6z\244\351\223S\331Yy\2568\256\212\336\314G\030\030\251\374\2208\002\207]\213PH\333S\336\250H\274\346\243#\212\241t\3308\252\330\335K\260(\250\244\301\025\tQ\237z\255p\007#5\211{\013\3341Px\252gD\003\255hZ\350\361\306\025\261\315Y\220\210O\000\017AR\307;Hq\333\025d\374\251\315V\232u\214d\326e\306\254\221\223\363\014\n\316\223\304\n[\007\245!\326\240=M!\277\216O\272\302\232f\r\322\241i\306z\324/8\250Z\344z\323\r\310\354j)o\002UI5uN\207\232\256\332\301=)\351\2531\355R\215I\332\256AvH\344\323\332v~\202\245\212VQSy\315\201\315=f\317\006\255\301\206<\324\330\031\342\254E\016pz\032\277n\356\270\007\245jC\222\001\253\261t\253\366\335\253^\320\364\255{bx\255H\017J\322\266n\225\247nzV\204F\257@\325~\006\306*\342\232s.\345\254\373\210\214O\270t\253Vs\340\212\323G\334*d\036\265\363}\301\305f\\7Z\317\224\346\231\034%\334\014V\376\237c\345\200kGf\005\004\004\\\367\252\362\020G\277z\2473rMU~\336\265^i6\016\274\326m\303s\223U\314\240rM1\256\206p*7\270\035\315U\232\375P\360y\254\255CS\010\244\203\363UK]QI\311<\325\365\270\363\263\216je\225\207\025^pI\311\244\216\343\312\374*\246\241\342O$\220\017J\347\257\274G,\331\001\270\254\211oe\224\234\223P\231\330u5\033\\\340\3654.\242S\241\251\023[x\377\000\213\212\265\036\271\014\203\34685f;\330\245\034=2b\243\220sT\246\274*>QY\3277\256\375\352\231vcS\301\0331\034V\254\026\300\n\260\226\313\234\325\250\343\002\245\010=jQ\267\035iZt^3B\3123\307J\267\014\335\006j\354n\030\325\350z\n\275\026\033\002\265-\027\034v\253\351\000+\221V\255\306\rkZ\216\005kZ\366\255K~\242\264`\030\255+sZ\021\032\275\r^\205\252\354G T\353\315G<a\224\212\251\010\301\372V\255\273dU\265j\371\256\345\261\232\312\2709\252\333\t5\241\247Y\231$\034WK\035\247\226\203\216h1\001\311\351U\346#\360\252S8\002\251H\374\373UYX\3475VE,rj\225\331P\017=+\036\342\343\223\315R\226\353oz\241>\240\300\234\032\241q\177&8\254\273\251\244\233$\223Ie\2748\004\344WQf0\202\256p*)\230\216\365\223}}\034\010\334\363\\\275\345\301\236BEV\021\026\241\202\245S\271\230\n\243$\365]\356}\352\007\271\367\250\374\362{\323\243\277\222#\225cV\223]\224\0141\310\251\006\260\033\255/\333c~\325$R\302O<U\250\246\213w\rW\242\221H\030j\262\216\001\306i\306\351\023\253\n\211\265\020~\355 \272$rh\022\022j\324.M^\210\222\271\025v\006<V\225\273\234V\215\277Q[6\377\000(\007\034V\234+\2712*X\206\032\264\355\273V\275\267j\324\203\214V\205\273V\214\rZ0\034\325\370M]\213\030\253p\234U\244\034\323\335r\265\234N\311H\253\2609\035j\364m\232\371\256\343\232\250\320\026=*{}1\244#\216+\241\323\354\026\025\034U\346P\001\252\023\311\270\373\016\325\237pI5M\321\211\252\322\214\036j\007#\025Rf\302\222O5\203\250]\374\304\003X\323\315\326\250M!&\2537Zr\302\262\256\332\211\264\302\t\307JrXy|\343\221Wa\224\307\301\025?\333A\035*\031\256\013\247\035k\n\366\302Y\234\223\232\244t\326S\3104\311\2410\257\002\261oge\'\255e\313+1\315Vy\rWw5\037-OX\211\247\213fn\324}\225\251\206\022\0174\365\217\336\236\261\267\255J\233\207CV\342\232A\336\254\013\211\010\353H\031\330\362jTr8\251\343rOZ\267\020\315[\211qWa\223a\307cW\341 \342\264\255FqZ\266\353\214V\315\250\312\n\320\204\341x\251\243\353\232\320\203\265j[61Z\326\315\232\323\200\016*\3645\241n\330\255\030Nj\354&\256GV\243j\235y\252\263[\203&EJ\220\235\276\3654LT\340\327\317\r\021cWmt\354\000H\346\264\255\354\200\355W\026 \213\305W\270\311\310\007\212\246m\313\223\330Ui\242D8\316MQ\230\021\223\332\263\347l\236\225VG\306I\254-N\377\000\222\250k\ny\t\315R\220\222j\273\255B\313MV\330}\350\222\377\000`\347\232\254\372\244\204\035\243\212\245.\255(n\365$\032\204\222\2209\2558e\332\240\267Z\223\355\nN1Jv\260\344U+\273_1\t\025\203w\242\3110\'mb\335\351o\027\360\232\240\326\016\307\030\250\333L|\375\332\016\234\361\214\221\212ER\247\356\322\264\205GJ\214\316GZ]\352\302\230H\024\3372\245\215\271\253\220\220z\324\374b\216\224\340y\251c\253\2216*\354OWa\371\306*\324\005\24385\261f\331\305k[\267J\330\263#\025\245\007\245ZX\270\253P\014\032\322\267\353Zv\315\322\265m\333\245h@j\374=\252\374\007\002\257B\335*\374G\"\247S\212\231\036\234G9\253(\240\2504\331\"=@\346\274\036\326\333{\214\212\330\212\3338\030\253in\024Se\001G5Q\223~Y\270QY:\226\244\261\215\221\234V\024\327\344\022sY\363_\261<\232\214\335\356\035k7P\2758*\207\353Xs\222I\252\222.j\263\216j\027\025\t\031\353PM P@\034\326|\304\261\247\333G\271\271\351Vd\323\222A\220\274\323\241\262\362\207\000T\305\002\236H\2464\241N\027\006\240\222Y\273SE\323\001\206\246KxB\220\005g\\\\+\003\271j\213I\026N\000\315\"\250f\366\247K\022\025\371\261Y\323\311\014@\2163YS\302\327\014v\036=\252?\354\227\352^\221\264\367N\206\233\3667c\212\232-=\217z\266-\243\205>c\315F\030\026\300\251\325}\352P\234u\247\244Y\351V#\204\372U\230\323\025n(\201\253p\256\323\232\321\200\207\306kN\325\024\034\326\275\263/\025\247l\000#\006\265!5v\003V\320d\364\253\366\353\322\264 \004V\225\253t\2558{U\370M_\210\364\253\220\265_\205\270\2539\371i\321I\223V\243!\2705f<\247N\2253\200\353\307Z\361\013Kb\244q[1A\265A\305#\214U;\211\025\017=+\013V\325\225Wb\034W1qu\274\236j\205\304\370\357T\214\273\3155\330\343\212\2455R\220UY*\263\212\201\361U\344\3438\252\0236I\252\256Njkm\331\315Y{\262\0063\305C%\353c\013\305A\346\263\236X\323^b\243\216*\023s&xjc\274\255\357U\244i\333\216j\t\022Le\316\005T\373DH\3075Z}c\3138E\342\263nu\211\\\3655\237%\334\222\036I\251\355\256\345A\200j\332J\355\311&\246RMH\033\002\233\346\260<\nO*I\2179\251\343\261+\311\251\226\n\221a\366\251\222\022\2475:\236zU\230\3005j<\016\225a\006j\324C\030\255\033w\351ZP?J\325\265\227\245j\333\311\232\322\267l\342\264\"\347\025\241\006\016+B%\300\253\220\034\032\324\267n\225~\036\325z\023\322\256\3048\253\260\232\262\\c\002\230\247\232\265\024\244b\257\305\'\034\324\353\310\3105\377\331"
+byte_png: "\211PNG\r\n\032\n\000\000\000\rIHDR\000\000\002\000\000\000\002\000\010\000\000\000\000\321\023\213&\000\000\001\212IDATx^\355\3351\016\3020\014\005\320*\367\2772\025\003\023FA@U\322\374\2747z\214\322V\262]{\333\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\200t\255\006\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000`\270\275\006\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\230\314^\003\000\000\000\000\000\000\000\000\000\000\000\234\257\325\000\000\000\000\000\000\027rh$\207\0240\000\000\000\000\000\314AN\037\000\000R\035j\377\001\000\000\000\000\000\000\000\000\000\000x\245M\031\000\000\000\000\000\000 \221j0\000\344\361}\007\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\200\261\354\026\004\000\000\000\000\000\000\000\000\256B?\023\360\244\325\000\000\000\000\000\000\000\000\314K\031\234\0077\001\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000`$\323\240`U6f\002\000\000\000\000\000\000\000\000\000\000\000\000\000\300\347\374\237\013\014\324{\005\031\034\322\321;\260P\267\032\2005,\366\244\003\000\300\202$>\000\000\000\022\250\351\000\000\360\206b\000\000\000\000\304S-\002\000\000\000\000H%\003\374%\007\006\000\000\000\000\000\000\000\000\000\000\000\000\000\000\220\315&\026~b \001\000\000\000\000\000\000\000\000\000\000\000\374\335\035/,\023\360\002\312$\322\000\000\000\000IEND\256B`\202"
diff --git a/core/res/geoid_height_map_assets/tile-5.textpb b/core/res/geoid_height_map_assets/tile-5.textpb
index ac2a9ba..0e43c84 100644
--- a/core/res/geoid_height_map_assets/tile-5.textpb
+++ b/core/res/geoid_height_map_assets/tile-5.textpb
@@ -1,3 +1,3 @@
 tile_key: "5"
-byte_jpeg: "\377\330\377\340\000\020JFIF\000\001\002\000\000\001\000\001\000\000\377\333\000C\000\004\003\003\003\003\002\004\003\003\003\004\004\004\004\005\t\006\005\005\005\005\013\010\010\007\t\r\014\016\016\r\014\r\r\017\020\025\022\017\020\024\020\r\r\022\031\022\024\026\026\027\030\027\016\022\032\034\032\027\033\025\027\027\027\377\300\000\013\010\002\000\002\000\001\001\021\000\377\304\000\037\000\000\001\005\001\001\001\001\001\001\000\000\000\000\000\000\000\000\001\002\003\004\005\006\007\010\t\n\013\377\304\000\265\020\000\002\001\003\003\002\004\003\005\005\004\004\000\000\001}\001\002\003\000\004\021\005\022!1A\006\023Qa\007\"q\0242\201\221\241\010#B\261\301\025R\321\360$3br\202\t\n\026\027\030\031\032%&\'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz\203\204\205\206\207\210\211\212\222\223\224\225\226\227\230\231\232\242\243\244\245\246\247\250\251\252\262\263\264\265\266\267\270\271\272\302\303\304\305\306\307\310\311\312\322\323\324\325\326\327\330\331\332\341\342\343\344\345\346\347\350\351\352\361\362\363\364\365\366\367\370\371\372\377\332\000\010\001\001\000\000?\000\355\240^@\305^\013\307J\216D\364\254\353\333c$$\250\344W){\033\243\220Ee\274\233N\r@\347q\247\305\037\034\212{E\362\325)\243\366\252n\265\003.j\'LT,\202\253\3128\252\216\224\325\213-Ne\3300*\027\250\271\3154\266\005B\314sQ\371\204S\322\343\007\232\237\355q\221\214b\241\231cu\'uQ\026\310\344\232\206k4\301\305f\313m\206\351Qy\030=*T\213\007\245[\2163\351V\0211\332\234\"\311\351R,>\324\343\032\251\353\212P\201\272\232pD\035\251\331_JQ\317AOU\377\000f\236\0015*)\253(*\314k\236\325j5\301\253\321-^\205j\374+\322\264!\216\257\305\035\\\215qV\343\025f5\346\257B\231\253\221\245[\212>j\322\200)\341\373\nx\315J\242\247\215FsS\203\307\0252\034W\017\034D6E[\t\224\2462\014sPI\030\333\310\256wW\262\004\026Q\\\205\344L\214qU\"$\310\001\255\025N\001\245#\212\247:\216j\204\213\315@W\006\241p*\273\325i\0275\003-4\014Tl3P\277&\230W\212\214\255F\313P:\324{M;i\244*\304w\244\021\277jw\222q\363S\r\242\021\234\212\257%\262\251\342\230\260\200zU\210\340\'\265YX\000\\\232v\305Q\3154\221\351Q\225\014zT\212\240p)\342<\365\247yB\236\261\014p)\333\0058%J\213S\242\325\230\326\255\306\271\355W\242J\277\014}+B\030\353F\004\253\261\216j\324kV\243Z\271\022V\204)\305\\D\346\256G\036\0274\214\010\247 \251S\255L\271&\247\034\n\2259\002\255\302\231<\327\036\251\206\351V\226!\266\242\222,s\212\257\"\022\207\025\221y\021\332w\n\3455\033#\274\2208\254\'\210\3077N\365z\026\312\ns\217J\2470\311\252\217\037z\255\"sU\234T\016\271\250]8\250\031j&\025\036\332\214\307\363S]qQ\025\2462\324f<\320!\366\245\362}\251\2736\236E5\363\216\006*\003\274\232M\222\021\306iD\016\335jT\265\\\344\324\3425Q\300\244a\336\240`\305\263\212M\2714\241\005H\261\343\232\220\001N\243\236\324\000jE\0254jI\253(\225f4\253q%^\2059\351Z0\250\035\252\374J8\253\360\255[E\253Q\255\\\211j\354I\322\257\302\265v5\351V\321N9\250\237\031\241jd\0315a\027\002\244\0315j\024\030\315[\207\357\375:W*c\300\251P|\234\320W#\221UZ<?\326\251_@\032\"}+\005\355\204\214A\025\316_\332\005\231\270\252\211\031S\212{\003\212\253\"T\016\274UY\024sU$Z\256\302\243+\232\201\322\242)\3154\307\3050\307Q\274f\242)M)\232O/\232p@\0055\360\005@\334\3230\017\024\2061\236\005H\213\330\001R\010\217\\T\236_\034\212aJ\210\241\'\035\0055\220\0163L\010=i\330^\324\240\032x\\v\245\333N\013\223O\tN\013S\306\225m\022\254\306\236\325n%\253\261/J\275\020\351W\341Z\277\nU\324J\265\032\325\310\226\256\304\265v!\322\256\305\305L\317\204\305A\324\346\244QVa\025aG\313OE\346\254\246z\n\265\n\363\\\361\217\'\2458G\201HP\212\202H\362sT\356\223\367f\261\204_\276<u\254mV\334,\205\200\254GQ\232\215\252\031\007\025ZJ\251\"\325gC\232\205\243\250\3323\351P\272T,\2304\004\310\244)Q\264y\355Q\230\275\251\246/j\214\307\212c`T.\t\250J\232@\2252(\034b\244\300\035)G\245;\006\220\257\025\023c\322\241h\363\336\223\312\367\024\345E\247\200;\nx\217\"\227\313\301\245\331N\333\3058\016j\304kV\343\\\325\250\322\255F\265r%\253\261/J\321\201+F\024\342\256F\265e\026\255D\265v!Wb^3VPT\233{SJm?Zz\212\23585aNjd\034\325\250\327\240\253\210\002\212\303\331\355F\314S\031j\007\025Ru\005H\"\250\033|\022\330\254\035Wo\314;\3277\"\220\306\240aQ\225\315A\"\014\325y\020UwQ\232f\300i\215\t\003\245@\361Ug\212\231\267\024\205)\n\037J\214\2450\256;TN\265\003\'5\031Z\214\257=)6\201Hr)\273\232\234\254jEcR\003\3051\2004\302\202\233\345\344\373S\304j=\351\352\200\366\251Dy\024\246>\370\244\300\317J@\311\273m</5b%\342\255\306\225i\005[\215j\344IWaNkJ\004\351Z1\257\002\255F\265f5\346\255\306\265n1W\020|\242\254/\025*\323\312\345y\246\250\346\246QS%Z\211sWbL\014\221K,\253\032\034\232\317+L\306\005W\235\302)\365\254\311.\333uBf2\260\024\267\001\226\330\221\326\271{\341\271\215c\313\027=*\263EQ:`UY*\254\202\253\272\324}\rI\031\007\345=\r6Hy\252\317\026:\212\201\243\246\004\244)L)\355Ld\250\032>j\027Z\201\226\243+M\"\215\200\323J{P\023\024\365\000\324\2332\265\023)\007\212L\032x_Zz\306\rH\020/z^\005!q\332\232y\246\264Y\0359\251a\004\214\036\325n5\307j\267\022\325\264^j\324IW\241J\275\nsZ0\'J\277\032\364\253q\255Z\215*\314kV\321p*\312t\251\226\246_j{p\264\325\353S(\2531\246E\\\213\n*Y.c\212<\2223XW\332\226\342@<V\231\036\325\023\234\n\312\274s\222+2CE\270\335\'\025r\347\002\034\037J\347/\"\033\216\005eK\027=*\253\246*\244\313\305g\312\016j\006Z\217f{SZ\016*\r\245Z\254(\336\230\357Q\311\027j\254\361\032\204\246\r\0052*&Z\215\205B\353P:\324\014\234\324e)<\261\232_.\223\313\244)J\261\340\346\236\334t\250\233\336\220\001N\033E(qA\220b\242.I\342\234\240\232\231\022\244\333\315.\3148\"\255\304\271\253\221\247J\267\032U\270\222\257\302\235*\364)\322\264a\\\n\273\032\325\270\326\255F\265f1\317J\235z\324\350je\353V\242^3H\344g\024(\253\021.\346\0259\221cS\236*\244\372\232\247\nk6\343Rg\007-Tw\264\357\355]\211\342\241\224qY\027c\255g\312>Z\257o)I\352\335\314\341\227\255eNCf\263\245A\232\2472b\250L\274\032\317tl\364\250\2323Q\355\3074\326\344qQ\030\211\355RD\2305+\304\010\315V\222,\366\252\357\027\245B\321\221Q\262\032\210\247\034\324L\225\003\245BR\230V\233\266\223\024\204RS]\261\322\242,M\031\244-\351M>\244\322\026 qM\3114\365\031\251\220T\3523R\201\3058\251$\n\267\014d?>\225z4\342\255F\265n%\253\320\255_\205zU\370\226\256D1V\323\000T\351\326\254\307\232\234T\3103V\021y\253 \355J\213\253T\310\274\325\204;W\245W\271Y$S\316\005a]\220\231\033\263T\025\232Y1\236+N\004\n\242\273\006\002\240\227\001I\254[\2627qY\316x\"\251\225\"\\\321+\035\330\252\3563U\2359\252\263G\221Y\363FA\351Ud@*\006@{T\r\037\265 \200\372T\202\334c\2455\241\332sJ\027\326\232\360\2022\005Vx}\252\273\307U\3351P\262\324N\265]\327\232\214\255D\311Q\225\246\221L\"\223\024\205sQ\225\3054\216iBf\230\350s\3050\202(\003\232\231\026\245\013R\n\225~f\002\256,9\301\035\252\314c\236G5n%\342\255F\231\253\221\245\\\211j\354C\245^\212\255\307V\223\232\263\032\325\270\326\245\003\232\225\0075j0:\323\235\2060)\261\365\253\np\000\251\227\2474\311\220\272\234\032\346\265\004\362\344`j\255\240\033\363Z\212p+\252\226P\243\232\316\270\271b0+6f,MRpsM\332B\356\305VpY\363M)Q\264~\325ZD\346\252K\016{UI-s\332\252\275\271\007\2450[\367\305?\310\366\245\362\206:TRG\236\325Y\243*iW8\344S^ FES\226#\232\252\351\353P:b\240t\250Y*\026^j2\265\033/5\031ZiJo\227AJ\215\224\322,y\352*Q\027\035)\031\000\250\314c\322\233\345\217Jr\255N\023\345\243a\251#R\032\264b\344U\205NEZ\2123\332\257\302\203\035*\312\256*\314KWbL\325\310\324\212\267\030\253q\212\267\030\300\253H8\024\363\326\244\214U\220p*6ni\350x\251U\261O\022c\275L\016V\271\215^P\'j\255fr3Z\000\361]=\316zU\007L\325Yb\246G\030$\356\\\323g\215v\340\014U6\204g\212i\207\035\252\'L\n\244\313\226\250\335\001\025]\222\241\222!\351P\371X\2441\373SvS\035\006*\254\211\315F\023\332\224\256*\274\321\036\265JH\371\252\357\035Wh\352\027LUvJ\215\222\243+\232aZn\312]\236\324\236]0\305\226\351N\021\201\332\224\214\016\005B\352z\324x4\355\264\340\225 S\212pZ\221W\025<M\206\305hF\003\n\265\030\013\203\232\273\031\033\263V\223\346 U\310\243\253\221-[@j\324C=j\334iV\321F*`\330\247\003\232\2318\024\346~)\2523\311\251C\000:\322y\271<t\247+\222j\331p\260\023\350+\212\325n7\336\020\017z\265d\010\210U\302x\256\306x\2623Y\356\254\033\030\2440\356^j\006\210&j\273\214\324;9\246\262\361U&Q\212\242\303\rMa\232\201\227\232\215\227\"\243\331\355M)Q\264~\325\033&j\274\221T^Y\007\245\005)\217\036S\025FX\360q\212\250\350EWu\250\035sP\262TE2*3\036\r1\243\246\354\240!\247l\317\024\236X\035)\245)\n\323\031\001\025\021\214f\215\235\251\301qN\3059G5 \034P2\255W\255\337+V\324\234\365\253\260\223\305h\302+B\030\317\004\325\350\243\253q\303V\2221SF0\330\253\033\200\342\225y\251\223\000\212yp(_\230\344\364\247\026\354:SX\361\232\025\262jh\371p(\324\'\362l\334\203\332\270f\224\315\250rs\315o\333\340D*Fj\357\331r1U\336\021\311\252\316\230\252\362.EVh\371\250\312T.\274U)\224\363T\335y\250\312\324n\265\021Za\\\nf)\n\212i\217\212\205\242\250\232:\215\223\212\210\361\326\253\317\020a\270U\'\217\034\032\256\361Uv\214\347\245B\321\373T&:c\'\2657g\2654\307F\316)\205pi6\032_.\230\340TEM0\251\244\300\240\003N\0035\"\245H\026\224G\223V\241M\275j\334K\223Z\020\'J\320\210c\034V\214$`U\370\270\301\253Q\216r*\300\351O\035jA\315J\265 <R\257\'\232y`)7SX\223J\274T\361\023\274b\250\353\262\355\263a\232\344\254\206\353\242}\353\241\214\341\005\016\374W\245\225\302\346\253\311\320\325)\033\006\253\221\223Ld\2462dUI\020\203U\244L\366\252\217\036\r@\313\212\205\326\242\333C\'\025\026\312@\200\232\0311Q\225\250\231*\027J\255\"\363Q\373\032\255<g\265Te\250\231\001\250Z<\324/\030\025\003!\246\204\244d\346\215\234S\0319\246m\346\224\216*3\036M4\246(1\002)\202\036zS\374\216(\020\034\324\202\023\216\224\276^)Pa\352g\004c\025v\331~Q\232\320\210U\350\372U\310\215_\201\270\301\253\252jd\344T\275\351\312j@\302\236\032\220\276)\003f\236\r>\224u\251\342\341\263\336\2615\3713\0063\326\261\364\350q\363\036\365\256\016\005F\357\305z\254\203\002\252\310\274U_\'{\023Q\274\033y\002\242+\332\230\311\305V\222:\252\351\236*\264\221\340\363U\244QP4t\337,b\230\311\355Q2S6R\025\250\310\2460\315A\"\342\253\025\313Tn\230\2462\007Z\253$>\325U\342\305B\350j\007Z\200\2574l\244d\346\220%&\314\324f/jo\227\315\036_\265\'\225\232C\035(\217\035\251\342<\323\304C\024\377\000+\332\243d\035)\004X9\251\025A<\325\270\260\005Z\211\271\253\321\034\325\350\205]\210\340\325\310\316j\324t\362y\240f\245\\c\232\013\372R\014\232x\025\"\324\270\342\201\326\247P\0262Msz\273y\267\001\007\255%\274B8\305H\315\212\206F\257_q\220EV\221\017LP\261m\213=\315B\312\017\006\252J\233[\332\242 \032\202U\342\251Jv\266j\274\256\254*\233\365\250\310\244#\212\215\210\250\331M7e5\200\025\031\003\322\243+\212\205\343$Uf\217\006\241qQ\021\264\344S\035\327o\"\240\"78\250\244\203\216*\244\221c\265V)\316)6\373PV\233\266\215\264\214\244SB\022zS\374\260GJo\226=)\014C4\024\244\nE=G5&\336)\206<\236\224\322\204Sy\006\247\2175a\016\0105\241n\300\342\264b\305YL\223\305_\210`\n\262\247\002\22795\"\212~\323\212\002\323\324S\300\342\234\274T\203\030\247\306\273\2152\366\341m\355\311>\234\n\347\243\314\363\231[\271\253\'\000T\016\330\252\356\365\355,*2}i\254s\326\241\221Fr*\234\343*MT\315F\374\325;\204\342\2502\235\325\033%FW\024\322*\026Ni\n\234Tnq\305G\3114\204S\031y\246\260\312\325vNMV\225y\250\031x\250]\005@\321\200sM\335\203\212\202^j\253&M&\312B\224\302)\n\363\232P\240\360i\341\024\016i\010\002\243`{SB\234\323\274\262\324\276Q\245\010}*EZ]\225\033(\246\010\213\267\002\257Am\201\310\251\036\330\237\273O\2127\214\362+B\007$\201Z\220 \332\r[Q\212\220S\227\255N\20350\240\n\\sN\002\236\253R\005\251\031\322\010K\023\\\305\375\343]\335\354S\300\251\241@\250(\221\270\252\22275]\332\275\300\217\226\242\"\243j\211\263P:\361T$R\030\342\240b{\324\017\310\252\357\0379\250]*\007Z\205\201\024\303\327\232R\006\332\257*\234\346\230\006E!\024\025\342\240~\016\005B\334\036j)\023#5]\227\031\250\030T.8\250\031p3P5FE&(*1Q2\203L)I\202(\301=\315\000{\324\212\027\034\322aI\342\245D\006\236\312\000\342\243\"\200)[\201U\231\362\330\025z\326/\227$U\261\2000*h\3075dF\254:T\321\333\250l\212\275\032\2201V\024T\201x\346\234\027\0252\034\n\225i\364S\224sS*\324\221\250\316\343\320V>\263x\0262\212k\032\321\tb\355\336\257\347\002\241\221\252\253\265Wv\257u\030+La\336\230\302\243+Q\272|\271\2522\'\314j\244\321\367\025X\255F\313PH*\273\212\205\307\025X\3474\274\342\232F\356)\230\000\321\267\232F\034TE\006rj)P\036\202\241\333\301\252\322\016MWu\250\030d\325yW\236*\002\246\220\255&\321A\034T{i\254\242\231\266\202\231\246\354*iH\371r)\213\220\325aM.I4`S\325\0052D\310\250\243\200\2313Z(\241\020\001OQVcQ\212\263\030\305YC\201S\2415a\rJ\005H\242\234\006)\301\261O\rR/5:-L\253\315%\304\253\014\004\347\265r\027\222\233\213\242\001\34354K\265\000\2473b\253\310\365Y\332\253\273W\274#eqN<\212f)\207\2555\271\030\252\262\256\006j\214\300sU\031y\250\234b\253?5\003\017Z\211\300\"\253\270\301\246g&\235\266\230\303&\200\264\256\237-FS\345\250]qU\334aj\254\213\315Wa\315Fc\357P<|\364\250\032>i\205)\n\323H\246c\232\nf\231\345\322m\"\202\271\246c\007\245!Nr)Fi\3034\361\322\244JW\031\024D0sS\257&\245U\315Y\2163\334\325\244\003\0252\214\364\025*}*u\315XOz\224\nv>Z@*H\324U\230\343-\320U\205\214(\250\347\235`\214\223\332\271\275CSiIU<U;t$\356=MZ\316\005D\357U\235\352\263\275B\315^\363\033\032\260\005.\312\205\320\347\212f\323\336\253\312:\212\316\234`\232\252\315\316*\t*\273\016j\tEC\332\241q\223Q\205;\252]\274SJ\322\016\r9\206V\232W\344\342\253J\274UgSU\335*\273\2450`qM1\006\344T/\t\035\252\022\236\325\033-DE0\320r\005&}\250\340\322\025\244\331N\020\223A\207\035\2516R\355\305(\006\214S\324T\350\242\247A\316jl\340f\254E\261\272\234U\224P:\034\324\243\216\265\"\232\231\016ju\351K\221@\345\252\314j8\253J\301\023\336\206\223j\344\326>\245\177\030VBy\305s\250\014\323\356\355\232\276\200(\024;qU\235\252\273\265WsQ\023^\363\023\032\271\021\334qR\260\001j\034\344\032c\364\252\362\216+>\341j\223\2475\013\241\252\3161U\244\316\352\204\234\032C\203M \003E4\321\301\244\301\247\001\362\325yW\232\255\"\325vZ\205\326\240d\346\233\202)\303\004r)\215\022\236\365\013@\247\241\252\317\0378\246y=\3151\227\034b\231\267\332\224\'\265;e<EN\010E\014\265\031JB\236\324\233)BS\302T\212*U8\342\245^jU_J\225\031\222\246Wby\251\325\215O\035O\277\013M\335R\241\2531\236)\346U\034\223\370U[\273\264X\030\206\344\n\345.%\222{\234\003\236j\344\021lA\305NN\005B\355\305Wv\252\356\325\0030\250\213W\276\333\200\307\025\245\014AFOZl\374t\250\020\363C/\025^A\305R\230f\252\262\361Q2qUd\217\025RH\370\316*\253\247sL\000\322\021M&\222\224-.\323\232w\226@\250\244Z\201\2235]\343\250Z:\211\243\250\232>i\245@\355MaQ\260\342\2532\363@L\323Z,\366\246\030\271\245\021\323\204\\\363N\021\322\025\3057\214\320R\223e!JP\224\241)\301qNQS(\251\226\244\034\324\213S%YN\224\254iTqV\020qR\027\001}\252\255\304\247\313f\007\240\256vi\345\222B\245\2163V-\242P3\216j\346\000\034TN\330\252\2621\252\257!\252\317-@\322\023Q\0279\257\241`\005n1\357Z\352\006\320qP\315\311\250@\305.2*\t\005T\221rj\263\246*\"\271\250dL\325Y#\305Vx\262j#\026;To\037\030\002\232\260\023\332\217 \346\236\">\224\361\027\265)\213\"\240\222.\rUd\250\331\006j&\217\332\240x\352\"\236\325\023%4\245F\321\361P4DR*\034\323\214d\016\225\036\337QO\021\361K\345\361\322\232@\025\033\014\232f\312B9\247\201\305 \\\265<\247\024\230\346\214{R\201\212\220T\202\244Z\225z\324\311\326\254)\342\224r\334\323\267\000i\341\317\2552Iq\225\007\232\251.\346\3435VK\022\351\272#\363\016\265\n\273A&\3118\"\254\254\301\207ZF \214\325i\031q\315@\301J\014\n\204\307\023\003\203\315C\373\224\310\3175\032\307\026\342Y\270\257\241b\205\214\275*\371R\261\363P`\263R\355\246\221QH8\252\2169\342\240e\250\212\340\323\0361\212\253\"sP\230\306y\243\311\004t\246\033q\236\224\206 \005 \210zS\274\241G\225\307J\211\320\366\025\003\241\3475Y\343\346\2421\323Z>*\027\212\240h\3523\036{S\014G\322\232c>\225\031\204\236\324\317#\236\224\276W\035)\206\036i6\001H\330\013\212\201\201&\232W\024\3023MaJ\006V\236\006\005\007\232B(\242\234\rH\265*\324\313S(\251A\342\214\220x\240\034\236i\373\360*\256\377\000\230\234\322\226\315\"\311\264\361T\357\224I\363\257Z\247\034\254\255\264\325\245\223\"\231 \014\rU\221[\261\252\305Xg\232\201\220\346\242`\330\353_R,h\234\201QJs\300\250\200\300\351M9>\324\230\250e\342\251\277Z\217\031\246\262Tex\250\214y=)\215\007\265\013\027\035)\216\235\361Qyd\232_/\024\334\200i\330\310\340Tm\031\034\324\017\021\'\245@\360\221\332\2431\037J\215\242\343\245@\361\361\322\2411\323<\256zQ\345f\232\320\373Tf<v\244TRi\222\"\216\225\003\014T,=*2\204\323J\000)\214\275\315D\374TF\236\237v\236\242\224\250\317\002\232E7\275\024\345\025\"\324\253S.ju\351N\035h,(\0075\034\357\265x\252\341\370\245\014i\254\370\346\243-\237z\253s\030\021\357^\010\246A.\345\353\315NX2\324M\327\245Wp*\007\025\023\001_M;\222i\244\214g\275G\272\233\273\232F8\346\240\220\344\325Y94\305\353N#\212\210\255=c\342\203\036x\305\036A\364\250\336\034T^N;S\032#\236\225\030\265f9\251\204!@\030\241\240\310\351P\264\001y\305@\360\226=*&\207\035\252\027\217\332\253\311\027\035*\003\0274\276H\2464U\033\246:\325yF:\n\200\014\032k\363Q\024\3150\240\025\031\317aQ\260\356j&\250XsL*\000\245^\230\247\201\212\\f\221\207\025\021\0304S\205H\246\246NEL\242\244\007\024\273\270\240\036i\335\005A;(_\230\324!\327\265#8\365\250\231\211\250\363\207\251\22614l\246\263$F\266\271\307j\262\207+\221H\336\265\003\214\032\205\252\006\007<W\323n\240T-Q\226\346\22074\214r*\0065\003\212`\034\323\361\305 \034\325\204A\266\237\345\202i\345WnqU\2353\315@E \217\'4\355\273V\232\027\'4\273)J\006\030\"\241x\200\355Q\275\266\344\310\252\022DC\021\212\204\307\221\214T&.i\273;Tl0j\ty\025U\3275\tJiJc\'\245D\312\007Z\211\215@\374\324L*2\270\250\330SA\301\251G#4PNj2)\000\247\001J:\324\250qS\253\nxn(\335\223J\r<\310\002\326e\334\205\244\340\361Q+7\255H\r!l\nb\235\322\342\254D\373%\036\206\253jQ\226>`\250\255\333)R\265B\3759\252\356*&\257\246\237\247Z\256\325\013\032ny\244&\230\325\023S@\357O\307\024c\232\220t\251\024\343\255+\032\211\360\006\rE\263\'4\354`{\323v\026\353N\t\201AZP\234S\0352*\"\n\212\257$a\273sU\274\262\037\030\250\245\213\346\340UwM\240\3256\004\232\211\226\242)\3150\306*6P\007^j\007\340qU\336\240j\214\203M)\216MB\352I\250\231j3\301\247#\343\203N\'=)\246\222\201\315;\"\200i\353R\255;w\024\240\340S\267TRI\216*\204\257\271\350SRg\212c\036)\320\241\332d4\363\326\222\361\317\331\016\006N+2\316_\230\251\253\347\221\232\211\352\007\025\013\n\372T\267\025\013\032\205\271\246\342\222\214dS\031i\240T\212\274P\027\232v\332P\246\244\013\232\206D$\322\204\342\217/&\237\345\340Rl\243fi\3730*7\003\246*\026_J\205\327\332\253\272\214\362*\027\030\311\252r\215\306\252\310\230\342\242)Q\225\000\032\205\224c\232\201\300\354*\263\365\252\362\n\210\306@\311\244\340\016\325\023\234\232\204\203Q\260\250\330TD`\323\220\323\262)\t\244\024\3409\245<S\220\324\303\245\035\005&\352\025\263\234\324\022d\344\325R\tjx\030\024\361\322\230\365a\177\343\330\001L&\235\"o\266#\332\261\020\230\356\361\357Z\200\345*7\250Z\242j\372;vi\247\232i\024\3021I\212P\264\205i\002sN\013\212pZP9\247\005$\323\366\020)6f\234#\000Rl\301\244\"\223m=R\206\\\n\254\343\234\324-\355P\271 T\016x\250\033\200sU\335qU\231rMB\343\035*\006\342\253Hj\273\232\205\252\027\0375A&I\301\246m\342\232W\212\215\226\242aQ0\246\025\244\333\212CI\232\005H\2645*\234T\301\250\'4\323\322\221O4\3761\212\253,a[\212`\031\247\201H\313\221R@\300\257\226\177\nq\214\206\344S\230\205\214f\271\351\330\177hq\3235\251\031\314b\221\252\026\025\003\n\3720S\261F)\254\274S\002\363R\252f\224\307G\227\355F\312]\270\240\'9\251\02503A4\252\274\323\310\342\232V\232W\035\250\013\315H\023\345\246H*\254\202\252\270 \361P\276qP05\014\200\342\253\276j\006#\323\025\004\207\212\255!\252\316sP0\250\230\036\325\023\200\277Z\204\256MF\324\200ToU\330\324li\271\243\212a S:\267\024\247\212p4\244\361\212\001\346\236\032\227&\216\364\243\255:\230\3005B\321\262\362:P)\304TeH9\025 \231\372\032\t2g5\205p\240_\340\036\365\253\010>P\245aP8\250Z\276\214\002\235\203J\007\024\025\240GR$f\244\331\353N\330\r4\307\3155\223\240\247\254|R8\343\002\221c\346\227\0304\355\274R\025\246\3554\001\203R\250\371>\225\024\202\252\270\252\354\271\252\263\0208\025BI\260z\324M)aQ\227\300\346\242r\030d\032\256\334\234\032\2570\317N\325Y\207\025\033.MF\303\265@\353\315FF*\026\034\323\017J\211\352\263\032\214\232i4\274\232c\n#\03148\301\246\203N\355IN\006\236\r8S\200\247\021\306)6\342\224\256T\325}\270\245\034\322\355\246\021\212z\361\023\034v\254\031>k\354\373\326\274C\367c\351C\212\201\205B\342\276\213\214\202\264\360)\312)\341sOT\025\"\256\005;\031\355J\027\024m\243`\353K\267\232i\217-NT\366\244h\271\351K\345\340Ry~\264\206>(\362\300<\212q\\\'\002\253\3108\252\3169\252\322\215\243\216\265\231u.\320Tu\254\311\030\223\232El\234S\23521P\210\310lSd\030\037J\251!\252\315\313`S\\aj\003Lj\211\205D\370\252\354j\0075]\215D\315M\007&\245\310\305FNO4\241\202\232\034\202\271\250\327\255?\265%\003\232\225G\024\3608\247\216\224\360)v\203F\334\016\225]\303g\247\024\325\353O#\212i\024\262\376\356\305\233\326\260#\033\256\363\357[\0100\224\214*\026\025\003\327\320\250p\265<d0\251\300\342\236\253R\001N\003<S\302\322\355\245\013K\266\230\344\001\307Z\024f\246T\035\351\3053\332\223\313\244)\212@\224\273)\245x\252\262\255@S\034\232\317\272\3178\254{\216\033\221\315Rp\\\234t\244\2162\032\255,C\0315\033\247\031\035\252\224\255\203\217Z\256\3000\307z\252\343cS\034\344Uv<\323\t\342\243c\212\255#Uwj\256\355P3\032\211\2157q\007\212vx\347\232B\337\205&\356i\331\342\201\305;4P>\365L:S\327\255H\005<\np\024\374Pc\014\270\305T)\266B)J\322\005\313b\242\324\330Ge\264V5\222n\233uk\205\302\323\030T/P=}\010\237v\236\231V\253\2502\271\251\025jM\264\345\024\340)\364\034\nM\340Td\206oJX\3075:\212\231E<-\005\001\352*#\036\r&\332\211\370\252\356\275\315V\226\251\311\030`I\254\351-D\222{TRZ*\360\005@b\nzPH\013\212\256\354\002\232\312\272r\030\340U?8\347\255\014\341\2075\023t\342\240bA\346\241f\250\236N*\263\275@\355P1\250X\342\243-Q\347\234\203K\270\343\232C&)\276`\025$d\260\346\244\343\024\231\346\236\001\357J\007\315S\252\361O\333\212\220t\247\001\315J\242\235\216i\300UYS\022\223I\266\234\211\316k;X?\273\013T\354\023\0038\255 8\250\334T\016*\006\025\364\"\014\n\221E[\204\360\001\253\010\265(ZpZ\\P~Q\232\210\266\016i\243\0079\243\0254c\212\224S\301\311\305[\21523J\313\307J\205\226\242~*\007\300\025^CT\344\334OC\305V\22398\250w*\365\252\362\260&\251J\3305Y\233\212\257+qY\327<\346\262\246r\215R\307\"\262\212s\343\025ZF\035\352\263\363\336\253H\304\032\254\356j\026\222\240ij\026|\324l\324\320\364\355\374R\026\2461\251\"|\032\230\266E*\014\265[\013\204\243n:T\250\274S\310\247(\251TT\201i\330\245\002\242\222<\266{Sv\361OT\302\023X\032\213\231.\n\203\221\232\232\322-\261\216*\321\034T/U\336\240j\372\031W\212zu\305M\037\016\r_\214n\031\251\200\002\227\0034\340\006*)\272\201P\3435$Q\344\344\364\247\030\300l\366\247\001\201N\035jE\034\325\330\370ZV\306*\006\252\322\036j\']\340\3253\220\331&\240\221\267\0021\212\252\374\003\315R\221\360\335j\264\217\236\365RF\311\252\356j\274\206\250\334t5\213t\334\232[Y\003\246\t\346\245\222L\034\032\254\362\202j\007\222\240f\334j\'L\212\255\"\221U\0379\250\211\300\250\231\251\273\351\301\351wS\031\252H[\236j\312\363SF\274\325\240\016\005<.jUZv\316j@\270\247\001\212\220\nv)v\321\267\"\220G\226\305C}\"\301\t\003\251\025\317\252\031\256\013\037Z\321\2156\255)\250^\253\270\252\357_GF\200\241\372S6\235\3254c\234\342\256\304H\024\362\3314\364\346\244\002\242\224e\251\250\2718\251\210\300\300\246\362E.>Zp\025*\216*\312\034(\024\254j\t\017z\252\334\234\321\301CT\2350\344\365\252\322\232\253.6\363Y\227\r\363qU]\252\263\236j\002q\326\243r\rS\235r\ra^FA&\250\307#\306\374T\322\\\026NG5XHKu\245\222E\333\214\325G\220\347\203Q\231\330w\246\231\367\016j\007`j2\271\025\003\241\315B\331SH\032\235\272\221\216jH\201\334*\344}j\354)\222*\340Q\216\224\005\305H\243\332\2368\355N\034\323\302\373S\302\323\361F\005(\\\364\247\235\261FX\327?\177pg\230\2504[\301\265rEY\306\005F\325\003\346\253\275Wz\372L)\t\305\t\026j@\230\251\001\"\236\t\251\321\224\nq\223\216)\230\'\232z(\316i\314F)\024qGSNQ\315L\243\212\224t\246\226\250\035\263\305@\346\242g\307\031\340\324\016\371\031\025Jg\2523I\3335JS\221U\\\325Y\016\016j\254\262\017Z\252\323d\365\250d\220\342\263\256$R\010\"\263\300_4\361I0]\243\034U\031$\021\236\rWy\263\320\324-6\017Z\215\246\3175\031\230SL\271\246\2311\336\221d\014y4J\237.EV\344\032p\346\236\006M\\\211@\213=\352\324\020\226\347\025\243\024AG\"\245\306\005(Zp\004\236\224\340\005=G5*\343\024\341\326\227\212\007&\245A\203\223T5;\200\020\242\237\255dA\016\3717\032\320T\300\244j\201\352\273\232\201\352\006\257\246\000\036]\n\2705 \024\355\264m\245\301\245\004\324\313\215\264\361\200(<\232\017\024\224\365\0252\322\223\201P\273\361P\027\031\344\324NsQ\034\223Ue}\271\025FY9\353Te|\325g5^R\000\254\371\245\352\001\254\371d9\353U\032S\236)\246V+\315S\230\026=\352\253(S\234\325yd\031\301j\245q\317C\232\250r\rA!9\342\242.GZ\211\236\2247\035j\'\223\234f\221\031\263\232\264\262\345v\2654\246NE*\241\024\360\234\325\270Wq\000V\254*\252\200T\333\261J\244\261\251Gj\221T\236\225\"\307\223\315.\314\036\224\240b\226\223\222x\251Qq\311\246\334>\310\211\2549KM9\006\254\305\026\325\034T\207\201Q9\250\034\325w5\003\232\201\253\351\264\033\233\025\"\257\315N\013O\013F\303K\263\332\215\224\340)NH\247F\0079\241\272\320:\324\252*A\300\250\335\252\254\217U\213\374\324\222I\264Tm8\331\214\325\031_$\325\t\237\006\251\311 \252\3176;\325if\005q\336\263f$\223\212\247\266I\037\030\246\264a\007<\232\214\270\3060*\tYpk.\341\362\373A\252r\3061\235\374\32578\352j\t\037\266j\263\277\275V\222_z\201\246\347\255\'\235\357B\266\346\346\247\\\nvOj\222)\212\234\032\264\256\206\224\272\366\251\355\233\347\255(\332\245\000\232\236%\305N\213\226\253 m\034\014\221Ud\235\222_\230qO[\210\334u\346\236\034\023N,\000\246\371\252\017\024\241\311\250.\245\312\355\252\261E\203\223\326\254`\005\250\334\324\014j\0275\003\232\201\315@\306\276\241\215v\362jLqN\013O\002\227m.=\250\332qQ\3644\274\232r\360\r%9jU\245f\300\252\322=T\221\352\020\3375U\270\234\324BM\313\357L~\225\235p\330\315e\3176\334\325\006\237{\340f\227\2675\013\2202j\234\263\355\310^*\234\223\022z\324&N:\325;\211\361\3005\225q<\212I\004U\031.\244\317&\253\274\356z\324FBj\007\223\236j\254\262b\253\231i\276a\315M\034\225ad\036\265 \224b\205l\234\325\204j\235Wq\253P\215\254+F2x5i9\025b1\212\261\020\313\375*\302\221\232\257w\002\261\311\357U\205\272\250\316iw`\342\245Q\221\232O/\234\322I E\300\250\000.\3315&0)\t\342\241sP1\250X\324,j\0075\003\032\372\224\360qOZ\227\256)\300S\200\243\024\275\25229\244\307\024\023IOZx8\025\034\217T\345z\252\355Qo\301\252\223e\345\300\245\t\265j\031d\254\333\231\0075\217tI\316*\224jD\231\251\035\360:\325I\246\033N\rfO8\007\255Q\222\351W\275V\226\364\343\212\247-\300\332N\356k6I\333\177\'5^Yr:\325f\230\201Q\375\243\265G$\274\365\252\322\313U|\317\232\227\314\031\251\342pj]\370\247\243d\340\325\310\243\316\017AS\252\340\325\210\370\253*\325z\007\312b\257B8\251\301\033\270\251\342?=[\0108#\255Ar\334\212\200\234\216h\n\247\236)\306DE\252\315p\316\330^\224l$e\251\352\000\024\214j\026j\215\332\240f\250Y\252\026j\205\332\240s_T\205\311\251U{S\300 \323\2058R\221IL4\334\361IH\r=x\245f\300\252\262\311T\344z\201\236\242v\001ri\261\000\331j%\220\0055\227s6\001\254\271%\017\236j\224\254NEV\334\007SU\247\234m\3005\235,\303\004\223X\363\334\356\224\200x\252\262L\000\344\325)\256\200\035k>k\337CT\336\357=\352\273\\\373\324/u\357Q\371\371n\264I0\307Z\254\363g\275@d\301\2442\361\326\246\202nz\325\325\220b\247\210\214\346\257$\213\267\203R\207\025:IR\306K8\025\257n\200\'5l8\003\002\236\215\315Z\211\276aW\200\371s\350+>\342_\336\022{UG\231\211\343\245 \226Lb\234r\303\223O\211@5+\035\274S7Tl\325\0135D\315P;TL\325\0135D\315P\261\257\254UMJ\253\212x\\\323\202\214Q\267\006\214SH\246\221\221Q7\006\220\320\r(5\024\217\306*\244\257U$z\204\265FX3\373\n\014\312\203\212\247s8\307\006\261\256g$\360j\231c\234\223U\346\230c\000\325)$\371z\326u\305\301\307\025\2354\216\312rqYsH\251\336\263n.\361\336\262\347\273\'<\325\027\270$\365\250L\331=j)%\003\275C\346g\275\'\233\212\215\356\017\255Df\367\246\231s\336\232$5,r\2259\253\221\316\307\025v)\t^j\3129\003\255L\222\234\3435f7\367\255\0136\005\362kZ7\371i\341\271\251\321\252\314rU\227\270\"\002\027\322\263\335\362\204\223Q\251\024\361\212q\"\225\037\rN\221\352\"\374Tl\365\013=F\317P3TL\325\0235D\315Q3W\327\n\243\025\"\347\275J\005;\024\021M8\2461\246\026\366\250\234\212fx\2434\326|Uid\252rI\326\253<\225\013\310\000\252\223N\300`UG\273!pMT\232\345\210\344\340U\tn\027>\265RK\203\3175RI\262z\325\033\273\235\211\200k1\356\200RI\254\313\273\376\300\361X\367\027\204\347\232\313\232\344\223\326\250\313>{\325V\224\347\255 \177SQ\311&j0\344\320\315\305Ww\346\242g>\264\335\347\326\236\255\232\235\016M]\213\240\253\2616*\300n*X\316M[\210\372\326\205\273a\270\2558\337\345\353R\006\346\247G\340\n\261\034\2035.\360\313\267=j\233\276#\306{\323U\352@\374Q\346P\036\203&i\254\374T,\374\324e\352&z\211\236\242g\250\231\2522\325\0335}x\265*\324\200\323\201\246\263\001Q\263\372TM%F\322Te\363Q\371\2304\246Q\212\201\345\367\252\322I\357U$\222\252\311\'\275V\226^:\3259\3468\353Td\227$\363U%\227\336\251K \346\251K7\275V3ry\351Yw\327\031;Ee\312\314\300\362k>\340\343\275eN\374\236k:W\344\325I\036\252\264\230\246y\324\323&h\363\000\2464\271\250\331\370\250K\363@|\323\325\252\3126*\324RU\264\222\247Y*x\344\253\220\277\025v\031p\302\264\243\227\345\034\324\302J\225\0375:I\212y\223\234\203PL\307#\236)\201\352A\'\035i<\312Q\'\024\027\367\246\031*6z\214\275F\317P\263\324e\3526jajal\327\330\013R\203N\0243\342\242g\250\231\352\026\222\2422S\014\225\033=Df\307z\211\345\252\357/\275U\222J\251,\275j\234\223q\326\251\3137\275R\222lg\232\245,\376\365JY\375\352\234\222\344\365\250%\224\"\365\346\262n\246\031\316k>K\2360+>i\207$\234\326U\304\343\'\025\235$\371&\252\313/\025U\234\223I\300\034\232a|SK\322\027\250\335\352\022\374\320$\251\222J\262\214\010\253\021\266*\302\275L\262U\230\244\253\320\313\212\267\033\022r*\3443`\341\215\\Y2:\325\210\333\212\260\255\353J\314;S\035\267!\035\352\272\311\316\r?\314\2442P$\245\363=\351\206OzcIQ\231*6\222\243g\250\313\323\013S\013\023HZ\276\304\024\360h/\212\215\236\241i*\026\223\336\241g\250\313\323\031\352&\223\336\240y*\006\227\261\250\036_z\255$\265Ni}\352\224\263U\031f\347\255R\226c\353Te\233\2575JI\275\352\263\312z\346\251O?^k\036\356\357\031\254\231n\230\236\rT\222vn\365RP\304d\3259\001\355Udoj\256\316j#!\007\232a\226\2433R\031\270\353L3qL/\236\224\002jtlU\230\333\236\265iZ\246W\251U\252\304lsW\241$\342\256\306\370\253*\371\031\253v\355\317Z\274\215\317Z\230I\357A\223\214\323|\317z\255#m\227>\264\276g\024\323\'\275\036e\036e#I\3150\311Q\231)\205\351\205\3522\364\322\364\233\2517W\331#\245\005\200\250\231\352&\222\241g\250Y\352&\222\241y*#/\275B\362\325w\227\336\253\274\330=j\007\233\336\253I?\275S\226oz\2434\336\365FI\275\352\234\263{\325\031e\346\252\274\252\243,j\214\327@\236\rg\334\\`\036k&y\201\'&\250;\214\232\201\210\353P\311&*\224\262\202x5Q\334\032\254\357\315Ww\250\213\324L\306\243.i\273\351\312\334\324\310je\253\021\346\255#qS\255L\265b.\265\241\020\302\365\251\325\275\352\302\023\212\267\013`\216j\342\2775\'\231G\230qM2b\243\225\262\231\035EF$\312\322y\224\236e\036g\2754\311\357M2S\014\224\322\364\322\364\322\324\335\364\233\250\335_d\227\305F\322TM%B\317P\264\234\365\250^J\205\245\250\036J\210\311\357P\274\236\365]\345\367\252\262KU\236~1\232\253$\336\365NYz\325)$,z\3259\337\000\363T\036ny5N\342\351\020u\311\254\251\356\214\204\363U\332L\014\346\250\\K\327\232\314\236^j\241\227&\242iq\336\253\311.{\325\031\344\367\252fc\232cI\232\201\332\242\335\315\007\245D\324\334\363OZ\260\202\247QS\2409\253(*e\310\251\220\232\263\026sW\020\260\253\t\353V\343n*tl\032\260\262qO\337\317Zpn84\326~y\246\357\312\221\353U\303\320^\233\346R\031)\246Ozi\222\223\314\244\337H^\232^\233\272\215\324\273\253\354fz\205\244\367\250ZOz\215\244\367\250^J\256\362{\324-\'\275@\362\373\324&Rj\'s\212\251,\270\357U%\233\320\325ff<\324\022>:\232\251,\243\034U\031f\300\'5\233qpq\301\254\271\256X\023\315P\226m\304\344\325v`;\324\022\313\307Z\317\270\224\016\365\233+\3565\003\270\002\253<\236\365ZIj\244\262\023\232\252MD_\232k6j<\321\272\232ri6\234\324\210\rYE5e\026\254F\230\253\n\271\251\225*eQV#\035\305ZC\221S\247\0252\032\231Z\247\007\013K\272\244V\244f\346\242g\301\315D_\232izizazizizO2\227}\033\3517Rn\244\335J\032\276\302y}\352\006\223\232\211\244\250\232J\205\345\367\252\357&j\006\226\241g\317z@x\315E+\200+:y0MT,Y\251\256\333S\255g\3157\315T\345\227\216\265Fi\307J\241<\240\326M\324\230\'\025\236\362\234\346\242i}\352\t$\033k>bI\252r6*\254\217Udz\252\356j\2731&\242s\212\2074\322sHzR\001N\002\234\007\265N\211S\242\325\230\326\254\242\324\312\2652\216)\340sSG\232\262\206\254!\251\327\025*.MM\216\202\223\245=Z\234\330\252\357\334\325p\371\'\353HZ\230Z\243/M/M/I\276\227}.\356(\335I\272\215\324\273\361_]<\265\013I\357Q\231=\352\'\227\336\253\274\276\365\003K\212\205\345\311\3115\013INY\206\314T\022\313\226\306j\244\305H\3115X\262\2579\252w7\000\214\003Y\362K\234\363Tn\'\300\"\262\344\270 \236j\244\263\346\251L\340\203Td\034qU\237 Uvz\255+U)MU\220\325I\rT\221\2105\016\354\323\030\023Q\2254\334Q\212P\016jEZ\225PT\312\240T\350\265a\024T\352\270\251@\251T\032\225V\245AR\250\251\220\325\2049\253Q)<\323\317\007\232i4\344>\264;\324r\034G\232\243\273\014i\013f\232Z\243f\250\313SwQ\272\215\324\273\250\337I\272\227u\005\373W\326\317%B\322{\324M-Wy\252\006\227\336\241y}\352\026\226\2432S\014\330R*\264\263\221\3005NY\333\326\252\311p}j\243\315\234\344\325g\227\216\265B\342N\t\315c\3157&\252I>;\325v\227=\352&~*\274\207 \3257l\032\255#UI\rU\221\252\244\215U\334\346\242\305!\3054\323\010\346\224-=R\245T\251U*EZ\225\027\025a\006j\302\255H\253S\306\276\3252\255J\251\305H\022\234\027\232\232>\r_\207\033)\254z\232\210\223\234S\201\305F\362sI#\342\036j\223\037\232\220\2654\232\215\215FZ\230Z\223u\033\251wQ\272\215\364\027\244V\347&\276\260ij\026\227\336\241y}\352\273\315\357U\336oz\211\246\343\255Be\367\2464\330\357Q\231\262z\325ye\025JY\272\3257\227$\344\325g\232\252\311>;\325\033\251\276^\265\2174\274\232\246\356sP\264\204SL\271\024\326|\325I\271\252N\3308\250\035\252\254\246\2529\252\354Ni\2314\224\020i6\232z\255J\251R*T\252\265*\247\265J\022\244U\305XCV\021r2*tJ\231V\236\000\006\244\030\247`S\224U\250O\312E)\351L4\235F*\274\334sLg-\025V-M\335\232B\324\3265\013\036i\204\323wQ\272\215\324\273\2517SY\351U\253\352\271$\307z\256\322\325w\232\253\274\276\365\003\313\357P\264\276\365\031\227\'\2555\234\343\212\214\270U\353U\345\233\212\241$\274\232\251$\270\315T\226n\274\325\031g\353\315Q\270\270$b\250\271\'\232\201\263\232a\217#\255WrP\342\230_\212\202G\343\255S\224\325v5]\352\263\n\204\2574l\366\244\333N\t\232\014t\241*EJ\231PT\252\2652\255I\266\224-J\252j\324+\212\262\253\315J\027\212]\274\324\252\231\024\340\224\340\2475*\002)\335\3151\215D\317\3155\260\352A\250v\262\361\332\240\225\010\344w\2503\203Aji5\033\034\324d\323I\244\315\031\243u1\236\232\016i\341\253\352Y%\343\255T\222_z\256\362\373\324\017/\275Wi}\352\027\230\016\365\021\270\305/\332GL\3242\3161\326\250\315t\000\353T\245\273\003\275U{\215\3035Ri\275\352\233\271\'\255W\220\214f\2531\250\231\271\246\226\"\253Lr*\266\343\320\324\022\276:\032\252\314MD\306\240rMBFi\002\nv\312iOj\000\247\000\r9TT\312\203\035)\341EH\252*UZ\220-=R\247D\036\225aV\246E\251\200\247\005\315<\014S\351\312*U\034\323\010 \232c\n\201\352,\220i\333\276^j2FpG\025Vh\312\234\216\206\240\335\316\r!4\302j3M&\232M4\2654\27579\245\006\234\032\276\235\222^*\234\222\373\325W\233\336\240ij\027\227\336\253\274\265\003\315\212\203\355\030bI\250&\274\367\252\022\334\226\357U\332L\216\265\027\236\001\306j\031d\356*\263\313\317Z\211\237#\255B\306\242cLf\030\252\3625A\301c\232\212U\034\325fQQ2\324\014\265\031Z6\322\320E&\332P)\300b\244Zx\251\024T\252*e\025\"\216jt\305N\270\"\245A\232\233\024\341N\002\236\242\244\013\223\315?a\0074\2143Q\262\361Q\024\317j\205\242\347\212aLSH\317\007\255F\303+\264\325\031\220\253f\243\007\"\232i\206\230\306\243f\250\313\323rO4\354\321\234\322\203_I\274\276\365RI}\352\254\222\363\326\253\274\325]\345\367\250^n*\264\223q\326\252<\307\035j\263\312OSP4\225\004\223\200:\325V\237\346\353O\363\003\'\275Vs\315Dd#\2750\311\232c\277\025\tcLn\225\003g\250\250\035\352\0265\0315\033sQ\355\346\227m\033ph\"\223\024\001\315<\014\214T\212\230=jt\210\021\326\235\345\221R(\251\005IR\'\275L\246\246C\212\260\274\212\220\016)GZ\220b\245SR\021\221HV\243e\250\310\305D\335j6\250\233\255D\302\240\221C-Qa\265\2150\232i5\013\270\002\240f&\2234g\002\214\346\2274\003\315}\022\362\373\325Y%\252\317/\275Wy}\352\273\313U\336_z\253$\276\365U\345\367\252\3575Vy\375\352\264\223g\275ViNiV\340\203\214\323\313\356\250\236\231\237zBj&<\3237Tly\252\3562\325\023\000*6\250\3174\334\021\326\202i2(\316iv\322\2055\"\241\316sS\205\004sN\000\203\201S\205%y\240%<\nu=jd5*\324\350j\300?-\000S\300\251\200\332\2715e0c\030\240\342\242`\rF\343\212\252\343\232\215\205FED\365\021\372U+\205\301\316*\241$\034TO%BI\'&\232i\271\2434\003K\232Q^\374\362\325i%\367\252\257-Wyj\273\313\357U\336_z\253$\265VI}\352\254\222\325w\222\240y*\026zn\376i\353!\365\247\207\334)\271\346\232Z\243\'4\302y\246\223\336\230~\355V\220\363Q\022)\244\323I\246\021F\017zr\216i\304\220)\273\216jE\222\246Y\001\251A\025\"\310A\366\2513\236{S\201\247\365\247\001OS\212\224\032\225\033\232\262\207<T\370\300\241~\360\253\022\220Tc\322\244\201\206\334S\237\025\003pj6<T\r\311\2465B\325\023sP\276{Ui\033q \365\252r\2569\252\254*2i\205\251\271\244\315\024\240\323\301\257\377\331"
-byte_png: "\211PNG\r\n\032\n\000\000\000\rIHDR\000\000\002\000\000\000\002\000\010\000\000\000\000\321\023\213&\000\000\002\217IDATx^\355\335\341n\2020\024\006P\303\373\277\262dq\233\313\2546\020\004i\373\235\363\263\022\025\250\264\367\366\202\227\013\000\000\000\360\312T6\020a.\033\000\000\000\350\235\020\177\201X\030\000\000\200\036\r\032\360\013\323\001\000X4\350\\\230\265t\000^[\352\031\342M\272\243\323\002\000\000\000\000\000\000\260\215\025g\000\000\000\000\000\000\000\340c\024*\000\000\000@\036\371\000\000\000\000\000\000\000\000\030\307\322\377@\326\251 \000\000\000\000\000\000\000\000\330\344\273\354b{\325\006\000\000\000\000\000\000\000\231\016\277\313\337R6\000\000\000\000\000\000\000\211N]/\277\226\r\000\000\000\000\214e\372M?\035~[\010\000\000\000m8\265\014!\330\211\201\367|\342g\003\000\000\000\000\000\000\260\217\347\245\337\242\000\340y\003\000\000\000\000\200*\267\030\001\000@S\254\372\307\021\225\001\377\030\005\000\000Bx\"\\\272\311\344\037\330L>\021\000\240c\327\262\241\352\0366\n\037{e\342\016\000\000\000\334\311\023\204;\252\003\034\365\276\000\000@\n\005\t\000\000\000\000\260\017+\370\000\261,\267\000\000\000\000@\010\311\3000\345\t\237\237Z\2061\354\216\001\000\000p$\341$]\320Q\001\000\000^\332\347\226\330}\336\345\024\342\305l\177]WG\000\000\000\332\320q\204\r\000\000\000\000\000\300G\251{\003\036\271*\204H?\321\n+\000\000\000`Qz\372\000\000\000\000\200\321\3342^\262^\214G\257\376\254\346\216ws_hp\2167-Q\013\013Y\2121\310\220\364I\343\\p\307\331\023\000\310c\034Of\366\017\220\314(\000\000\000\000\217\304\312\000\300\030T\001\000\3002Y\200\r\3328h\357~\013S\245\316\275\333\001\000\000\200\332\254\272\326\316\316\304\245\000\000\000\034\241\022\330\337\233+/W\333\241Iyy\225\325?\321\325\033\366\"\357\\\003\000\000\300!\346\361\262\006\000\000@\377\004*\000\000\000\260\262VV\020\235neG\001h\215\001\014\000\2003\010\243\341,\242@\250H\371q\244\354\'\025\327\262\201(\346\340\220\3155 \335l\036\010\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\374\360\357\347\000\000\000\000\354a*\033\000\000\000\240[\375VS\210\317\001\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\340\246\337g\t\001\000\000\000\000@[b\236\202\377\005\355\265M\274\251\013\246j\000\000\000\000IEND\256B`\202"
+byte_jpeg: "\377\330\377\340\000\020JFIF\000\001\002\000\000\001\000\001\000\000\377\333\000C\000\003\002\002\003\002\002\003\003\003\003\004\004\003\004\005\010\005\005\005\005\005\n\007\010\006\010\014\013\r\014\014\013\014\013\r\017\023\020\r\016\022\016\013\014\021\027\021\022\024\024\025\026\025\r\020\030\031\027\025\031\023\025\025\025\377\300\000\013\010\002\000\002\000\001\001\021\000\377\304\000\037\000\000\001\005\001\001\001\001\001\001\000\000\000\000\000\000\000\000\001\002\003\004\005\006\007\010\t\n\013\377\304\000\265\020\000\002\001\003\003\002\004\003\005\005\004\004\000\000\001}\001\002\003\000\004\021\005\022!1A\006\023Qa\007\"q\0242\201\221\241\010#B\261\301\025R\321\360$3br\202\t\n\026\027\030\031\032%&\'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz\203\204\205\206\207\210\211\212\222\223\224\225\226\227\230\231\232\242\243\244\245\246\247\250\251\252\262\263\264\265\266\267\270\271\272\302\303\304\305\306\307\310\311\312\322\323\324\325\326\327\330\331\332\341\342\343\344\345\346\347\350\351\352\361\362\363\364\365\366\367\370\371\372\377\332\000\010\001\001\000\000?\000\364kU\301\025\250\251\362\364\250\246\217\003\"\262\265+3,$\257\336\025\303\352\221<n\331\025\207$\305I\252\322\266\376\224\266\361g9\251^\337 \326u\314X\317\025BT\305Ut\315A$dUw\214\032\2552\201\232\317\226<\324K\006\343R\262yC\025^J\200\236i\205\360*\t\034\346\2423\025\247\305v\001\346\255\213\350\210\3069\252\3271$\312H~+/\354\013#\036x\252\327\032b\201\305d\\X\225\'\212\257\366S\236\225<Pc\265^\206,\201\305[\216\034v\247\2102zT\251k\354*C\000\\\034\200i\336V\356\246\234\260\242\373\323\360\202\201\203\332\245E\343\356\324\201sS\304\206\256D\265r$\315\\\2051\212\322\267L\342\264\355\223\245j[\'N+V\326#\305j\333\303\232\277\004aj\374+\300\253\260\2475\245m\036{V\204\021\364\253\360E\223W\221\002\212\224H\007\003\255=rju^\225f(\306j\322\034\n\261\021\300\2572\212\337kt\255\004\213)Lx\2069\252\262\3046\234\327)\342-00.\242\270\rJ\003\033\034V|,L\2305\253\024_(8\247\225\342\250]F9\254\251\323\223UY0j\031FsT\344\340\325I\224\232\252\361\323\025pj)>bj\274\235j\"\225\023.j\'J\255\"b\241\332i\333O\275\014\030\214v\246,n:S\374\206#\346\351Q\266\232\034f\252M\247\210\315D\226\240\036\225j+\\\343\212\273\035\236\006H\251\004*\203\232B@\372T,\241\333\030\251\0220\275:\324\202\"z\323\276\3168\315H\226\340t\025\'\227\212r\307\355S\307\035Y\215*\344)W\241L\326\225\274}+R\332,\342\265\255\242\351Z\326\261\364\255(W\025v$\311\253\360\245^\202<\326\255\254U\243\024x\255\010!\3434\255\220)c\025<g\236j\312\034\325\225\312\201SE\310\025z\336=\304W\237\244xaW\243\203\345\250f\267\307j\251,D\251\000v\254-F\022\312w\016+\205\326\264\322Y\210\034\032\345\346\2670\313\322\264\255\237*3RH=*\205\310\316k>HsT\346\217\025RE\252\262&j\274\221\361U\244J\201\227\025\021Nj\027\213\346\246\274x\025\003.j6\216\241h\263\332\220[R\233l\036\224\236HS\310\246\277\3128\025Y\335\311\246\355\220\2163@\267y:\212\232;\000O5i X\307\002\207\034{Ui2\307\247\024\302\245\2158ER\244]\352eQO\300\240\223\332\200\rH\2435b%&\255\305\035]\206:\277o\027J\322\266\217\245kZ\307\322\265 J\324\266\\b\264\"LU\330R\257\300\235+J\336>\225\251n\230\305iC\037J\277\022\034TR\360i\022\247\215rj\334I\216je\311\253\226\321w\253\326\340y\230\354+\2100\205\025<+\225\346\206L\366\252rC\265\353;R\264\022D\330\256V{\0213\225\"\271-gM\t3`Vtq\030\316*V^*\234\361\325W\217\212\245:U\t\223\025U\326\241d\315V\222:\256\321\344\323L<Tm\rC$F\2411`\323Z,\323<\201OX\200\244u\013U_\222j2\001\342\232\320\016\242\244\216<q\266\246[s\234\342\246\362}\2526\213\322\2411\226b)\217\0268\250\304C=E?b\216\364\252\r=V\236\022\224&MH\"\247\252b\254\301\036j\374Qt\253qG\355W\340\216\264m\343\351Z\226\353\322\264\355\2278\255Kh\372V\224Q\364\253\260\307\322\257\300\225\245o\035i@\235+F\001\212\264e\302\032\252N\346\251PU\273q\315\\\215x4\364Nj\354|`\n\271l\2375rf\035\335\252D\207\002\221\243#5Vhrj\205\354x\214\375+\2350\376\377\000\247Z\300\361\005\220V,\005s2F\001\250\236\253J\271Z\247(\340\325\031\2275JX\311\252\357\017\265D\320\324\022EU\236>i\004y\024\206*\212Hj\026\202\230`\036\225\033C\212\215\206*\274\2715]\224\323Dd\232\2368\361S\004U\344\001\232x>\224\356\324\233=\252\027\002\253<E\211\346\232 >\242\234\261\001\336\245\010\007AR\010\267\n_\'\006\234\"\247\004\342\236\253\315Z\2119\253\320\246j\3641\325\350c\253\360\'J\321\267NEkZ\307\322\265\355\242\300\255\030S\245\\\211*\365\272t\255\030\026\264m\323\212\273\030\251\266\232a\213i\366\251\021j\314\\U\270\333\"\247\215y\253\260\247AZ\021 E\256h\307\203\322\227\313\305F\351\232\255\"\216EQ\271@\312A\254\223g\227-\216+\234\327\302\374\303\275q\227\010C\032\252\353P\262f\253\313\025T\226!T\344\214f\2421\003\3051\355\210\0358\252\322\333\342\252I\007\265C\345\343\265\006<\323Z>:TF:c&\007J\202D\252\322G\315B\311\315D\311\232h\214-!;i\276i\247\254\2075\"\271\'\232\2306EF\352\246\242h\2050\303\223\355R\010\000\003&\244H\207\245N\260\344b\234a\357M\300\3161H\0317c<\324\2019\253P\'\025~\030\352\354KW\241N\225\241\004}+J\336*\327\264\217\245k\300\230\002\257B\225n$\311\025~\024\351W\340^+B\020B\325\310\306\005N\2435!M\313\3151W\232\2361V#\253\260.H\255\033x\261\311\025,\262\210\224\223X\354\202\243+\212\257r\3425\'\275c\\j$1\025Y\356\314\304\016\324\267\nV\335\210\353\\f\256\014\214\331\256r\342\016O\025M\355\352\007\207mS\234c5FQ\232\251\"\363Qt5$L\t\301\351M\226\333\232\245-\276\017\"\253I\005F#\2441S\032.j\'\213\332\253\274X5ZD\305Vu\250Y)\245i<\260i\246!\351H#\307j\221@5*\305\221\322\242\221\n\236)\270&\236\253\236\265\"\302\t\251\226\020\275\351\330\024\206@)\204\356\2464\033\307Nj[pq\203\332\257\302\230\253\360.qW\242J\275\004u\245o\035i\333G\310\255{X\372V\254)\300\253\261\'J\273\014uv$\253\321G\200*\354}*\302\032\262\234\342\245\'\ni\211\311\251\320U\270c\310\255\013e\n9\253Ot\220&I\256wU\326\301$\003\305i\021\355QHv\212\305\324\246<\363X\2236sL\265\313\313\307\255j\\\200!\301\364\256CS\200nb+\002\342\016zU9!\305Q\270L\003YW\000\344\325GZ\204\307\272\230\366\274Ur\205\032\255\306<\330\360z\324\023AT\345\203\025\\\305\203Jc\310\250]1P\272\324\022&j\254\251U^<\232\211\2424\321\016M/\223\212C\027\265#E\351J\220`\346\245~\007\025\003\014\3474\201A\247\000\253\326\236\256\007J\032QP4\244\236)S&\254\305\037\255K\262\227\312\303\002\005]\267]\325\243\004x\305_\206>\225~\010\353J\332>\225\251m\027J\325\265\217\002\264\241L\342\264!\217\245]\211*\344+\203\322\255/\025f3V\023\255]\267N9\242V\031\305$b\254\302\233\230\n\275\275bST\356u\244\207 \036k\"\357]iA\371\270\254\207\270k\247\300\351^\200\334T\023\014\212\302\324\0279\254\211\206T\325;Y\374\253\201\365\255+\313\240\3109\355X7\2046k\"\342,\232\317\271\213\031\254\273\204%MeL\204\223\305Wx\211\355Q\024\307Zk\034\216*\273BX\364\251 \213k\n\263,\001\205S\232\014\366\252\222[\325f\210\212\211\343&\2411z\324\022GU\244\216\253\264\\\324f:aJLb\232E&3Mw\333\322\241g&\2234\322\376\224\207=I\246\357\307Jir\324\344\004\325\210\227\025i\006j`\270\024\346R@\253\326\361\025q\221\332\265!\217\212\273\022U\350\022\264\355\223\247\025\251l\235+R\004\340V\214\013\322\257\305\200*\304G&\256\304sV\200\351S\306*\324k\315]O\221*\022w5O\032\346\255\303\204\035*\275\357\2312\220\274\n\346u\017\335d\026\311\254\2375\246}\271\342\265\254\341\n\242\275\t\327\216j\274\300`\232\347\365\"7\034V4\207\250\254\347R%\310\245\236B03U%\344U9#$\325+\210\262\rd\334\302A<U\tc\000\325w\214\036\325Y\342\250\305\256{T\313g\221\322\232\366\333\017JU^9\246\313n\010\310\252r\333w\305T\222\037j\253$X\250\035*\274\211UdNj\022\225\013\305Q\024\246\025\2460\3053\006\221\3235\021\216\230Tf\224G\236\325\034\221\234\324d\021@\344\325\210\322\247T\251\227\212\231~b\005_Km\312\244v\253p\214\234\021\203W\355\322\257C\035_\202>\225\243n\230\255\033q\322\264\255\372V\204&\256\3043W!J\277\nT\340b\246\214b\257@\235\315I#\205\030\024\310\271<\325\264`\000\251\343\351L\271R\352@<W!\254Db\221\263Y\266*\014\204\326\344g\n+\273\232`\213\223YWw\304\251\002\261.\244.y\254\331s\232\217g\004\342\252M\227|\324f:\212Hj\244\320\325\t\355\263\332\263\347\260\335\332\251=\231^\325\030\263\317Zx\264\013\332\234\266\376\325\014\320\347\265Sx\212\032r\234\216E6H\003\014\212\316\236\014\023T\244\217\256EU\226<Ui#\252\357\025@\351\203P\262\324.\2315\033G\315F\321\323LT\236]D\353\216\324\213\016\343\322\246H0:P\361\001\324T-\002\223\322\243\362\006zT\211\035YH\370\243g5,HCsZ\326\334\212\266\221\347\232\273o\021\355Zv\320\372\212\273\032b\256\300\235+F\010\363\212\320\206<c\212\275\002\325\370W\245_\201p*\354+\220*S\306*XFM\\C\201\327\212\215\337\232|g\212\235_\037Z\224M\214sV\025\267/5\307\370\226`\2635fi\247q\315k\251\300\256\326\364\361\214V\\\261\3475J\342\016\017\025\0040+1\014).-\320&\024Vs\333\014\361L6\274TRE\212\316\225w1\250d\213\"\252\274^\325Z[p{U\177\263b\221\241\3050\307Q\311\020\305S\232*\205c\245\331\212\253s\001\344\343\212\315\232\032\251,5U\341\252\362E\216\325U\342\346\241h\252#\036j&\217\024\337/&\217(\032O\'5\033A\223OX\000\035)J\340p*\t\025\217j\213\006\235\2634\345\216\246U S\225=\252d\217\025f\006\332\325\253\010\016\005]\201\002\340\346\264a \2605z3\270\201Z\020E\323\212\320\2011W\342\034U\330\027=E_\206:\277\022\014U\200\333qN\r\232\261\027\002\244i0)\213\2275:\220\243\255!\234g\002\234\262d\325\364p\261g\332\274\373\304\327\233\356\230\003\337\025&\222\270\214\032\324\335\201^\203u\006\345\254\251\220\253c\025\033[oZ\256\366\376Y&\252\3123U\314\\\323\036<\n\245p\234\034Vd\213\2065\033.EV\2219\250^<\212\207\313\3155\242\250\236*\205\343\342\252\315\006j\017$\203A\212\231$[\220\212\314\270\213\007\245Q\226\"\rT\221*\254\211\232\257$DT-\036GJ\201\241\301\246<Y\246y^\324\242*Q\036i<\220)\246?JiB)\217\020\"\240h9\243\313\247\254x\247\343\332\234\243\0250^)\006Q\205i\331\313\221\212\275\0319\353ZV\314N+V\331s\365\255[h\216\006kN\010\272U\370m\211\253\260\301\212\263\022\340\325\305`\242\225N\343S\3061\212\233xQH\244\271\366\247\2311\300\246\263\034SQ\262j\304G,\005K\250\334\375\236\321\317\240\2571\275\2727Z\207\'\275t\226\003lKV\235\370\257St\310\305S\226\330\022MUx\266\346\251\314\271\006\251<Y\250\332,Uy\023\002\263\356T\214\326t\211\223Q\024\250dJ\201\222\242+\201L+\232B\231\2464<Uw\202\240h*\'\217\002\241#\035j\245\325\270q\220+:Xq\301\252\222\333\372U7\207\223\305W\222\032\201\242\305F\321\323\014\\SL<Ry|S\n`\323v\022i|\254\323$@*\002\231\2462\232hZ\024\032p\031\251\022:\234\'\024y;\215\\\266\213eh@\2315\253k\027J\326\201q\212\327\265#\002\265 \350\t\034U\350\207<U\265\344T\200\343\025(9\251\222\247\007\002\201\2265)`\274f\233\270S]\213\nT\342\254B\330aU<K?\227d\343=\253\316\354\301\226\370\237z\353-\316\324\002\236\362q^\304c\340\232\2557CY\323\276\rT\177\230\324O\035D\321dU)\220\203\355T\346\2135\237,8&\253\274x\252\322%BS\232G\217\212\204\307\311\244\362\30144x\025\013%B\361\325y#\252S/5\020\364=*\235\324=qT]1PI\0305^HsU\244\207\025Y\343\3054GMx\350\021dTo\0253g4\244\014T&\"\306\232b\307jF\267\014*1m\223\322\244\373\'\035)\005\267=*U\267#\265?\311\"\225\027\0168\253\022\002\000\305hX\256G5\253n\274\326\244\030\300\255\013sZ\226\217\221\203ZQ0\307\025b2j|c\024\365j\231\\\001O\017A\227\024\320\344\367\251\024\323\305*\365\2530p\300\232\303\361d\377\000\350\2543\326\271M\036\334\227.}k\242S\265i\222I\305{\214\313\201\355Tf\\\203TZ\330\312\307\322\242\222\323g#\245B\311\333\025\023G\305T\236,\325\031\"\317\326\251\315\016\t\310\252r\306*\263\303\232\217\310\0305\033\305P\274u\037\226i\031}j&\030\250\331sU\246\\\n\242\351\271\2529\"\305F\361\007\\U\031\255\261\236*\224\260\025\250\0362*\254\251U\231\t=)\004t\326\212\220GM1d\364\250\332\014\036\224\317\'\232_#\035\251\206\014\232C\016)\313\016;S\304Y\251V\337\245<[\324o\027j`\203\006\246D\316\001\253\326\340(\342\257@\374\326\225\271\311\025\247n+F\016\rh\302sW \353S1\346\221I\251\320\014sJ\322\001\300\244\311jz\212\231*P2(\003\232\265\n\355R\306\271O\023\311\346\270A\3275Z\306\330C\030\342\2553b\253\313%{\364\203p#\025Jh\360:REm\2662\306\241t\007 \212\241<[\033\332\240#5^d\340\326l\347kUY\335XVt\274\232\205\2054\257\024\307\300\025\003\251=\251\236_\2555\320\n\205\325}*&LT\022\302XU)\"\301\250$Z\256\303a\317jl\256\244r*\253$r\034Uy\25508\2523A\216\325M\243\346\233\263\024\030\351\273(\n\0055\320\212h\217\'\245?\312\004t\246\371#\322\232\320sA\213\002\220&*E^jP\234Tm\016M0\306E7\033MY\204\346\256FpsZ\226l\016+^\337\240\253\261\036kF\334p*\374gh\247\026\311\247\242\223SllR\004&\236\253R\252\323\324`\324\312x\247D\233\332\226\372\345m-\3131\300\002\270\362\306\366\351\244=3\305\\\332\025j\t_\025NY:\327\321l1Q6\017Zk\020EV\231\007QTn\327r\361Y\341\271\250\245\3475\237w\036A5\225\"\035\325\013\307Q2b\232W\265W\222>i\245\016*7 qP\236M!Z\211\322\232\303+U$\217$\3259\343\346\253:dUi#\315Vx0sM\335\216*\275\307\315T\036,\265\'\225M1\342\230W\024\302\274\323\302\356\030\247\254 u\305!P\275\252&\036\224\300\255\232\177\226Z\217 \323\204X\355R\242f\235\345\324O\030\250\274\202\355\305hZX\372\212\261%\221#\345\247\333\306\360\343\"\265\255e\316\005mZ\307\225\004\326\204k\212\230\032r\234\232\263\022\346\254(\245\013\315(^i\312*EJ\224-N\n\333\304Y\215r:\356\252o\'\362\220\374\243\212KH\274\270\305>V\300\2523=T\221\353\351R2\265\003\n\211\352\031:UY\027\"\262\347B\216qU\235\252\264\247p\252rE\223\232\257$X\252\362-VpEFy4\245F*\254\350j%\031\244\"\202\274Uy8\351U\244\340\364\250f\217p\315Sd\306j\273\255W\220pj\253\2569\252\362sP\025\246\340Pc\342\242x\363Q\030\351\270\"\227\223\336\200\t\357R\"/z\n.p*h\342\315H\321\252\255D\313B\2559\206\321U%\223-\212\275a\006\341\222+IT(\300\251\241\031#5u`W\035*hl@l\212\324\201\n\200*\334b\246U\315<&*x\315X^i\342\227\024\364\\\324\310\225<Q\345\262z\n\307\361\026\242!\205\221O5\312YFe\224\310\336\265\254\033\013PM%R\225\352\244\257_M\257+Q\270\357Q:\344TE3QI\027\312Mf\317\026X\325\013\230{\212\244\311Q:\325iV\252H\265^A\305T|\203J\t\3051\206\356*=\241M!Nhe\340\324\r\020&\240\236 G\025_g\006\252L\274\325I\023\232\253\"\363U\247\\\014\n\250T\346\232R\233\345\214\322\225\030\250\212sLt\250\366\342\203\026zS<\262\206\234G\031\024\304\310j\264\206\234X\236)6\212\221c\3152h\370\252\321\332\227\223\245k\303\030\2121\212\225Fj\334)\305\\\205qW\"lU\250\234\325\270\316EN\242\245Q\232x\030\247\253b\244W\251S\346\253\021\245XT\247O\"\333\302I\364\256\017Y\2727W%A\3434\353h\374\264\025+>\005U\232J\245+\325Y\036\276\237\211\3621N<\212f\332\215\261\232c\214\214U)\342\352k6\344\016k=\323\232\202E\300\252\222\325g\036\265\004\200\036\225RE\301\250\363\223N\t\336\243u\311\241R\226H\276Z\213\312\312\325y\027oZ\254\352\0005Fe\346\252:\363Q\030\273\325Ib\313Uw\213\232\210\305Hc\342\232\313\216*2\274\3224y\250\314$Rl\"\206\\\212\217n\0161\3055\243\347\"\234\271\024\361\232\221jX\305,\213\221D\013\203\232\262\274\232\235\0235n\010O\341W\343@\005N\252\017J\2361\355V\243\315Y\216\254(\247\343\345\246\201R\304\225r(\267t\025n86\214\232e\305\302\333!c\332\271]k_2\202\250p+\032\325\014\217\275\273\325\360v\212\206Ij\234\262\325I$\252\316\371\257\250a|U\220\271\002\224\305P\313\031\007\212\213a\357U\256\027\031\025\223t\2705A\333\234Ui\252\243\216j\264\342\253\212\257*\346\240\010wT\3418\246\262f\232\006\016)\3542\264\302\237\'\025Rx\370\252r!\305T\222<\325Yc\250\200\002\232\360\007\344Uim\361\332\253\264X\250\2311P\262\346\243#\006\203\300\3157w\265\030\006\232R\220E\236\324\341nOjSm\216\324\236V(\331\212r\203JsOE\2531-Z\215q\212\262\016\0279\2536\3409\373\325r4\332x9\025:\361\332\246F\253\0216j\324})wv\240\r\315V\241Z\275\033,k\232sM\205\311\254]gS\215\020\241nH\256,\223spq\367sZp\250E\024\347n*\234\257\326\252J\365VF\315Wc_P\333\275_\201\2676*\303.\026\240\316sQ\311\323\212\2478\310\254\273\264\316k2X\260j\274\261\232\251\"\342\251\315\235\325]\210\006\232\300\023L*\001\351E4\322\020\r!\006\234\243\"\253\\%S\2310*\243\255W\221}\252\263\305\3153\004R\200\030|\324\307\266SU\336\320\036\206\251\313\006\322EE\366ni\216\233x\305G\267\332\234#\245\021{S\326\017J\220E\266\206\\\324f:i\216\223\3134\010\363R,u2.*t8\342\247A\232\231S\322\254G#\307\336\247I\331\217\265Y\215\315Z\213\255Z\017\265i\233\31154F\256D\325)\230wlb\252\336_*D\3047A\\6\245y%\325\301\000\236MY\262\267\330\240\232\270x\025\014\217\305S\225\372\325I\036\253\310\365]\236\276\245\265\001\216;\326\275\255\270^M-\326\024qU#<\322\310\274UIW\"\263\256W \325\027\217#\245@\361qT\247\207\031\342\250O\016F{\325)\"\357Q\200i\010\246\023IJ\022\227a\315<DqPL\225U\343\315U\226\032\257$\'\322\240h\252\027\206\230c\307jc\014TL0*\244\253\223M\021\346\232\360g\265Dm\361\316)\313\017\265<A\203O\020\342\232\311\212i\000\236h1\361I\345\323LT\242:p\216\234\251\212\221\005N\213V\023\212\230sR\240\251\342\352*\344})\356\324\253\3235f\025\342\254y\201ET\272\270\371I\007\240\256J\362\376ied\334qRX\333/V\344\326\220@\243\212\212V\305R\232LU)f\252r\317U\236bM@\322\232\372\276\000c\270\307\275o\304\001@j\033\254\032\254\027\024\270\310\252\363-g\314\233\211\252rG\203P2g\265W\226,\212\247,\030\252R\301\223\322\253\233|v\250\236,\017z\215-Kv\245\3731\317J\221m\317\245<[\373S\314\031\025Zh0\rRx\261P\274C5\013\303\236\325ZHqP4U\013\305Q\230\252\'\203\212\253$84\325\217\232{Dq\322\243\331\317Jr\303\336\234b\310\351M*\026\242\220g\245Fc\346\221\205=W\"\223fMI\345\001I\266\214{S\225qR\251\301\251T\324\313\322\246N\265b>\242\255\241\300\245\0371\2517\000jE\220\372\361L\226\343nGz\243;3\0023\301\254\351\264\263\"o\217\357\372Ut\221\255\237d\203\004U\350\356\003\257ZI\030\021\223T\346e\306\rTdS\036GZ\256\326\361\270<\363P\010\241\214\234\234\232\205m\320\271,\303\025\365|p3M\234w\255`\214\221\363U\233.\324\241;SJ\342\240\224pj\214\243\004\232\253\"\344\324\014\2305\034\261\014f\251\315\0275T\303\223\322\217\263\006\355Q5\220\317J>\314\024t\246\213aN\026\303\322\227\354\374\036*\031##\240\252\322FNsT\344\213\255@`\366\2464>\325^H*\263\301\216\325\013A\236\324\306\267>\225\033C\355P\265\266\343\322\243\373)\035\251\337g\2465\2679\3057\312\301\245l*\364\252\2162i\23309\2462\223\364\250\334`S\220ejD\\\014\320M!S\326\212)\300\324\250jx\352\302\n\260\212ju8\024n \322\206$\323\374\315\265Q\245-!$\367\244g\315\"M\260\361TuT\023\215\353\326\263\240\271h\316\323WRm\302\243\231C\203TfV\307\007\212\246\312\312O=j\264\210s\232\205\213\000y\257\266R\331#9\3075\035\303\002\010\025X\000\275z\323X\223\320Rb\253\316+>n\rC\214\323\036:\211\227\"\240hKTOk\337\024,\030\355L\226,t\025\001\204\223J!\307Zi\3004\354\0028\025\023\302s\232\255,%\217J\253-\261\035\252\023\006{Too\307J\255$<Uv\2075\021\267\346\217#=\251\215m\355Q\0301\324SV\025f\246\315\n\216\225U\224\001P:\372T,\204\323\014X\024\306N2j\t8\250[\232tc\345\251\025i\305\007jc)\246\367\242\234\2435*\n\261\030\251\322\255G\322\236\016)Y\205\"\261&\243\272\227bqU\026J\003\232G\223\025\013>\352\243y\016#\336\274\021Q\332\317\274u\344U\242\341\207\275W~\2705Z@9\252\262.*\007Q_gK1\316*6#\0315\031~i\233\362i\035\2603Uf|\232\2457$\324K\326\234\313Q2S\243\207#\221J\320\202:S~\313\355Q\313m\201\315A\366lv\250\336\003\236\225\017\330\232F\310\025:Z\004P1\315+\332\344t\250\032\320)\316*\254\266\345\217J\205\355\266\366\252\362A\355Uf\267\366\252\255o\315\002\330c\2455\240\307J\205\342\307Z\2538\333\320U`0I\246\311\315Wd\315F\321`sQ0\301\340TN\274\344\324\017U\334f\230P\001B\016\325\"\214S\261H\303\"\240a\203E={T\212qVb\346\254 \251\224\342\235\273\336\232\016MH8\025^\344\215\207q\305VVQH\322\001P<\205\270\250w\025j\235\"\373J\262\237J\307\270\211\254\2561\330\325\270\337r\202\r\017\353\351Ud\034\325w\252\322\003\236+\354\327LT\017Q3b\233\277\232I\033\"\2539\353U\244\025\032\203\232\223\034Sv\325\250\242\033jO$\034T\236R\205\004\n\2514`\234\342\252\260\246\210\267\034\323\304a\0050&\343N\021\322\230\203\014\021U\344\266\003\265C%\236\364\310\353Y\223\300T\221\212\256\321d`\212\200\333\363L1`\3242&\016*\274\340\021\305Q\2253U\332:a\212\243x\373\001P<x\353P\276\005V\220\346\253\270\250\331q\326\241q\232`m\246\247\034\214\321\232\031\263P\270\315 \024\360)\303\255M\023b\254\243\202*P\334R\027\311\3059Z\2442\000\265\223\251NY\360\017\025U%oZ\2247\255\033\261Q\006\335 \025j\t|\251A\252\332\334f_\234\016\005U\262\223)\217J\260\365ZA\232\255 \252\357_gJx\342\251\271\305@\315L\335\315!l\324m\315@\342\230\027\275H\027\2126\324\2508\251\220\340\363Ns\305C&\000\346\253\030\367\032xM\243\336\230P\277\322\236\261`R\230\370\241b\246I\026EA\202\231\252\263\302$\347\034\325\023\tW\301\025\024\366\340\036\005Tx\212\202j\214\271$\325gL\324-\026MF\320\324N\200\003\353U\234`qU%\252\317\315B\302\2431\343\223P\312\271<T\016\206\241a\203O\216\\qO,\017Ji\246\322)\311\247\344R\203\212\2219\251\2235&\374\n\003qO\rP\3136\321\214\326e\324\273\232\233\031\315J\017\024\307j-\243,K\366\0253R\337J\005\2418\311\305a\351\363\2370\203Z\207\221P\310*\254\202\253\272\327\331\005\362*\274\206\253\275F\005%\030\315F\351LU\251Q2)D|\323\266\323\202\232\221W5\014\321\222i\022#\216\224\246,\323\3048\024ytyy4\361\026\005G\"\201\306*\263\307\315W\222?j\253\"\002zUy\023\0315B\344\026\'\212\2414[~\246\240h\2526@3\232\256\3500sU\245QT\344\034\325I\252\003\021\306\177\235&\000\3543PJ\334\342\240`j\027\031\250]sP\260\301\247\306\334\323\363McM^\264\3409\247\036)\361\036j\300\340R\203\201\357M\337J\222d\232\253>I$U\026\313=H\243\002\244\035)\222t\2530ql\007\2550\2659\343\363-\310\256o\230/1\357[h\333\220TRUw\025\003\327\327\241\363Mnj2\264\306\\SqN\013\232k%4ER*b\236\0234\005\247\204\311\366\251\0262\0057\312\311\247\254\030\024\236V\r#-&\314\323\322*VL\n\2470\3475]\375\252\t\016\005U\224\344f\253?|\325YS\275R\2252j\273\256*\273\214U9\217Z\247#Uw\346\253J\274\346\240\224\261\342\242\331\305FR\242u\250]j\027Z\210\307\232M\230\2444\231\315 8\251R\225\371\241\016*\312\267\002\202\331\246\223MC\203R`~uJx66EF\006i\300P\353\221R\331\270*c?\2059\342!\272T\207\344\217\232\345u\'\002\377\000\217Z\327\2669\210P\342\253\270\252\3569\257\256V\237\2126\323$N*0\234\324\311\036iLT\276M\'\227N\362\361@\213\234\324\251\025+\032UNjB\274S\0318\246\024\3074*f\246H\360*9\227\212\247*\325)\001C\305V\220\361U\\\021\315A(;j\244\204\325w \347\216j\264\247\031\2522\232\251)\252\2563P:\236\325\014\213\264d\365\252\354\2715\023\361L\0035\024\234\n\255!\250\\\3233\315!\3051\210\025\037V\342\224\361O\007\201N\310\301\244\007\025\"\276)\333\251\001\311\247\001\315:\231\"\357\252\357\013\'=\251\026\236W\212\210\251\007\"\246[\246\003\004f\202\346^+\231\324\223m\3663\336\266m\007\356\227\351O\220Ui\005W\220W\327@S\260i\312\271\024\024\342\220C\315K\034U0\213\035\251\336P\364\246\2309\246<X\251\022\036\006hu\300\300\246$$\232~\334\032p^)\nz\323\n\322\001\203V\021r\265\004\253T\245\035j\254\213\232\247s\204\030\025\2315\306\322y\252\357rXu\250\014\200u\351P\310U\201\332j\234\234\234Uk\201\221\201\320U6\031\250\035sQ\272\343\212\255*sP2\342\240u\246\036\005A/J\247!\346\241c\232i8\240d\324l)\"\031jYW\024\300qO\317\024\224\345j\221Z\236\005<\nq_\226\220.)veMU\331\203N\0034\025\246\025\305>!\303\037A\\\315\351\363/s\357[v\243\021\001Nq\305V\220Uy\005}q\013\006Q\353R\201OE\247\205\315H\221\324\321\240\002\237\264\032Q\036()G\224\r\002>i\255\026\346\247\244T\217\0074\242,\no\223\232\0144\202\000\0175&\300\023\201Uf^\rR\225sU\'\033\006{\326=\365\306\300Gz\305\232B\306\230\217\270\342\235\"n\\Uq\023+SfL\017qT\2465I\371lS\034mZ\254\365\023\214\324.;\324\022\014Ui\rU\225\252\253\232\256\357L\017\223S\203\201Q9\311\241X!\247HC.j\005\344\324\203\245%\003\232\231\005J\2435\"\216)\3523N\331\2326b\252\312\010\'\216)\213\326\236W\212k-<\201\025\244\217\355\\\241\375\345\347\343[\360\014 \241\305W\220UiE}a\021\3323V\242`\342\254\"\324\210\27052-<.x\251\002b\235\262\200\224\341\0352B\000\367\244A\237\255N\221\323\314`\216\224\236I4\215\036\007Jh\216\227\313\024\322\200U)\323\031\252\315\027sY\227\331\000\342\271\353\303\363sY\222\202\347\002\233\024D5]H3\311\250\345\217\214\201\322\263\356\037\004\3257\001\201\365\252R/\226\331\246Hr\265Q\316\rF\307\025\013\260\034\325I\237\255T\221\372\325I$\252\322=@\346\231\270\203\305?q#&\220\277\2457~O4\374\361H\274sO\316x\242\220}\352\260\235*U\353R\252\361OQ\212\220-?m\006\020\352F*\213\305\345\312E8\257\024\315\2318\246j\315\344\330\021\353\\\316\236\236e\306k\240D\302\323d\025^J\253%}_\037\335\002\244\217(\302\264b\033\224\032\231\026\245\013\201OU\301\247\205\247\201\201JF)\013\201\336\242r\035\200\024D>n\265i\005N\253\232\224 \305\r\010#\221P4;M!J\206L\n\251\"n\346\252Lk:h|\314\237J\310\271\261\363\244\300\351U\245\323\204}\005W6\301\017JRB\251\252\3228\000\326&\241.\t\305g}\250\347\255#\310\034sP\277\003\203Udb\r@\357U\345\227\212\251$\231\252\262>j\263\232\256\355\212\205\236\242-\316E8I\3074\3230\024\3230\025$2\0313S`b\233\273\232\221A4\241~j\263\032\346\245\013\212\221\005H\243\232\225\026\244\333NU\252w1\021.i\241ic\213-\232\317\361\021\305\260Z\307\322c\344\234V\332\216*9\005U\224UW\025\365|C\345\025*\214\325\353f\371@5r5\315N\251N\tN\333\216\224\247\345\031\250\032NsL\00414\233jx\0275az\324\241\252\344\021\344S\335\000\025\003\256j\t8\025^@\000\346\251\314\334qT\'$\232\2451<\212\257\225RsU\256\034\023Y\267\017\202j\243\275S\232N\rd\336\374\300\326\035\314\206&\251a\225YG\275>@;UYXt\252Rw\301\252s1S\315S\222J\257$\325ZI\352\273\313\232\205\244\246\t9\247\2113M,*7j\226\336L\032\262_\"\204\031j\274\2106f\201\036*x\227\212\224\216)\310\271\251\221j`\234S\266\323\224b\241\270\210\263g\2650F1\322\244\216\034)5\315k\322\231&(\017\002\2156\337lc\212\321\333\201PJ\005U\222\252\311_YF\274T\221\360\325b\036\034V\234\013\270f\254*\342\235\264\032xN*\033\236\000\003\275V+\270\324\220C\226\347\245H\360\200\331\355OE\000qN^\2652/5\243\007\013\355O|b\252\277z\2473sPH\246@Eg\260`\307\'\245W\232M\300\340`\325\t\006\320Mg\317&\033\223U%\227=\353>w\3115RG\342\252L\325\235vN\rs\267\357\311\311\246\330L\035qVe\233o\004\361T\345\234\023U\244\227\323\245U\222@\374\032\202H\211\025Jd#5FL\202j\0268\025\003\277\2750I\357O\022R\227\250\335\351\366\317\226\346\256\251\315X\205y\253\240\035\242\244U\315N\251O\362\363R,x\025\"\256*U\024\360\264\241(\331\221\212`\207,\0057P\220Z@y\344\327 \352n\256I>\265\255\004;\020T\215\300\252\322\216*\244\243\025VN+\353\270\"\014\277\2050\256\032\247\205y\255+s\201\322\246/\223ONjU\\\3247\003$\n\2164\344\017Z\263\267b\361L<\212p_\226\234\242\247E\342\255\302H\\S\234\346\253\312j\224\207s\023H\000 \372\326|\321\376\360\346\251\334\021\316*\204\337v\262/\030n\342\250\310\365JV$\325f>\265\014\2046j\205\322eMs:\234$\022EeE;C\'\025jK\35719\034\32531f\306ie\220\005\306j\214\263\020x5\021\274e\352i\215u\274r*\244\254\rBW\"\253I\031\315W`T\320\262S\203\323X\346\245\200\022EhE\326\264-\343\334Eh\254C\035)Bm\355R \366\251\027\351R\016i\352\225\"\245H\027\024\273E(\\\324\201DHY\253\231\326\257\276\321)E<\n\202\312\323\003q\034\232\275\263\002\242z\255%U\222\252\313_`\306\230@E\t\016\342ML\261m\251\221\212\324\201\363V\242*\027\2558\312\005Fr\3074\350\323\346\315L\304`\323\021h<\234S\224sVPqS\'\024\326j\257+\366\252\262\032\205\244\333\306x5ZW\316qY\327.9\025\233q/j\314\2709\346\251Jj\224\307\004\232\2454\300\016\265E\3569<\324\023Lv\232\310\275\231\030\020G5\217\265<\323\351E\302\250N8\254\331d\020\234\346\252\313u\236\206\253=\306\017Z\212K\235\325\017\332\0055\247\rL3c\275 \2349\344\322O\027\313\221T\317\006\236\274\323\200\311\253\366\321\201\036j\355\265\271|\034V\275\265\266\321\315X\013\201N\013\232r\256O\002\244U\247\252\347\212\235\000\305<S\216):\232\232$\301\311\351Tu\253\261\034%A\344\3275\004\006iw\036\225\253\034AT\n\034Uy*\254\225VNj\254\235k\354\270\320yt\210\2705(\\\323\302Q\263\024\354\020)T\325\204\306\332\221@\002\203\311\240\214RT\221\255N\202\236N\321U\344\227\002\252\2312\335j\031\0335\003e\217\265T\236M\231\002\262\347\227\336\263n%\311\252R\266j\254\344*\326]\315\300\031\025\221q9\311\346\250IpA\340\323M\313\021\203Y\327YsT\035\002\234\346\253\31778\'\212\315\273\031\034\034\326y$\032\2573\034\361P\031\010\353P\274\234\320\257\307Z\212Y\210\3434\310\335\263\232\273\035\306W\rLh\267\034\212U\214\203R\010\316E^\267\\\341Em\332F\021\006j\320|S\225\267t\251\200\340\n\225T\366\251V\022\307\232_+kS\302\342\226\220\223\232\226$\344\023Ks\'\227\0315\314\336\312\3273\021V-m\302(\342\254\036\005C!\252\322\032\251!\252\262\032\255%}\235\032\356l\n\225S\r\322\236\022\236\022\227g\265\036]\002:\221F0)\304\222)a\034\363J\347\232@9\251\320T\243\201L\221\361T\247\226\251\264\23756Yv\255Dn\206\323\3175\233s6\342k.\342\\\032\317\232QT\245\270\002\252Or\031q\236k\032\351\311c\212\316}\362\266\320)\255\007\226\016z\324%\327\030\305W\235\227\006\261oe\303ak:x\2163\277\232\317\220\221\234\232\2534\270\357Ud\227\025N[\214f\252\275\327\275 \272\317zE\223{rj\302q\336\236I\355SAq\261\260E\\I\021\271\247\227^\325=\233\342J\331\205\363S\200MX\2019\253(\233\232\256\"\010\307L\221U&\2721\313\317\002\245K\270\344\035Fi\342E\'\255<\221\212\217\316U>\364\341)&\240\275\2371\355\254\350`\313\226=j\342\250\013Q\310j\264\215U\2445VCUd5]\315}\251\004{NML\007\036\346\236\250jEZv\312]\276\324m\366\250\317\006\216I\247&Fh\'&\234\242\246Zs6\005U\232^\265BisU\267\374\325R\356\353\031\252\3130a\326\243\220\361Yw\215\2675\211ws\2635\225%\337\230\345A4\203\246I\250$\300\311\2523\334\210\363\267\257\255g\315t[<\325f\233\336\250\335\335\354\007\236k\n\366\356E$\201Y\263j/\336\251\313z\355U\332r\325^Y\216j\224\363U6\234f\233\347\363SC5\\I}\352Q8\035\350Y75Z\215\317\025e\027u]\266]\214\t\255hI\030\364\253\321|\302\255B1V\241]\316*\352\340\036j\256\243j\034\002x\025E,\202\363\232xm\207\0252e\251\255\t-\232W\220D\235j\241&g\311\351R\201\264R\026\305A#Ui\032\253\310\325]\315U\224\325W5\366\341\343\212\221\005L9\305<-<\n\\Q\212\211\327\234\322c\002\202i)\350jU8\025\024\322`U\t\345\252RIPy\230j\241x\333\344\300\244\216\"\213\315E<\270\254\213\331\201\007\232\347\257\337$\201Y\221)\363jy\037h<\325\033\213\200\024\340\3265\335\320\004\363Y\263_*\344\346\251\315\251\341~Z\317\270\274\334\t-X\327\027\214\317\313dU;\213\215\300\3257\271 T_l\355P\315q\234sU\'\232\250\264\331cG\233\315Y\202@\306\254y\200S\322M\307\025z\332-\330\364\253h\2305n\036*\344n8\255+YA\\V\235\260\310\253@\200p*\304\007\016*\360\217\200{\324W\255\300\025P\266G\2654F\t\315I\271\"\\\346\252K|Y\260\202\223cH2\306\237\032\205\241\332\240g\250dz\254\357U\344z\256\357U\244j\255!\257\270\002\2265:&\005<)\006\244\024\341N\"\232)\255Q\223\305% 9\247)\305=\233\002\251\317/Z\317\232Z\250\362\324N\340\002I\250\241\002F,i\323J\021Mc^\334\200\016\rbOp$$f\263.\034\3621\315S\335\264\363\326\252\335]\005R\001\254\213\213\236\2715\201}}\272R\024\361Te\270\000\034\236k:\346\370(<\326U\316\245\214\363Y\322\3529\357U$\276\367\250$\277\317z\203\355Y=i%\270\001G5R[\234\367\252\255>\033\255!\270\343\255Mks\363\016kI%\030\253\020\020[5\251\024\212\027\002\247Y\005Y\216^*x\230\273\001[\2261\000\203=kE$\0100*H\337\'5v\006\344V\222\r\312\rg^\316\014\207\320Vt\267Lx^\224\213r\370\305)fu\344\323\240\214\006\253.v\n\217}G$\225Y\336\241y*\264\217P;\325wz\201\332\240v\257\272cCS\252c\255H\0234\365@h\331\203K\266\230E1\206EB\334\032Bx\244\034S\201\250\245\227\000\325\t\345\252\023IU\331\363P\273\357`;\n_=b^*\215\355\330\333\220k\236\277\273c\320\326a\221\213\022MV\270\270\000\034\036k6i\270<\326M\345\346\001\3075\221sr\356\017j\304\272\220!\'<\326M\336\243\267<\326%\336\242Ny\254\271\257K\036\265Y\256w\036\265\014\323\355\037z\253\031\311=i>\320W\234\324R^\037Z\204\334\363\326\230\323\347\275F&\251\241\270*\300\326\204W\204\201Z\026\323\222*\354R\234u\253\021\334\036\231\253\220\312Oz\324\323\3344\234\326\374\022\341EJ$\346\254D\376\365r\031j\361\275)\t\307\245d\313.\365bM@\204T\253\212y`)c\223\rO\226L\324%\370\250^Z\201\344\250\036J\256\357P\263\325wz\205\336\240w\257\275#A\212\225s\320\324\3528\247\355\244+M \n\215\216)\214\365\004\244T`\346\214\323^@\242\251\3175P\232nMR\222Z\202I\260\247\232\243svP\020\265FMD\205 \232\241s|\314\017<VT\367j\t\3475Fk\323\353T&\271\311\353Y\367\367\236Tg\236k\r\357\201\311&\262u\rX\000B\232\347\257u\"s\315a\335_\026\'\232\314\236\3539\346\250IpI\353M\022\234\344\232\212y\262*\025\2274\254\374UI$\301\250ZSQ\371\247=jE\2235b6\316+F\333\265i[\266\332\270$\310\251bm\306\257\333\236kR\315\266\267\025\267\004\271Z\231d\346\254\305.\000\253QJ\001\253\036`pW5BY6\243\003\3275\032IR\254\274u\2453d\320%\245i\263\336\243ip\265\003\313\315B\322T/%Wy*\027\222\241y*\026z\211\232\276\372J\231jPi\340\323Y\300\250\232Lt\250^Z\205\345\250\232L\324B]\246\225\247\030\353U\244\237\255S\232~\274\325\t\246\252sM\216\365N\342\343\345\353Y\3677\'\034\032\313\236|\223\223Tg\234\343\031\254\351\345\353Y\327\027\030\357T\232\347\223\315c\352\267\274m\315aO3\260<\220+&\355\260\016Mb]\311\327\232\310\270\223\223Y\363IT\336\\w\250\376\323\305F\323\346\220K\264SZ\340\036\365\014\222\203U\336Ni\004\231\251\021\252\334O\214U\373yj\364S\325\224\233\336\255C(\255\013ikF\332\343\004V\304\027\037-X\022\324\361K\223V\243\227\035jF\237\236\rV\271\220\344s\301\250\226Z\224MG\235\357J&\240\313Lij\027\226\241ij\'\226\240y*\026\222\242i*&z\214\276k\357\364\251\224\324\213CI\212\201\244\250^Z\257$\325\013MQ\264\330\250^Z\204\334\220qP\311q\357U%\237\336\251M5Q\236~\265\2375\316A\346\263\347\271\353\223Y\263\334c<\326u\305\327\275g\\]{\326|\367\031\357Uf\230F\275y\254-B\340\023\234\326T\327\300\002\005e]\\g$\232\303\275\272\000\232\310\236\353$\325\031\347\310\342\250\3111&\233\3655\033\311\216\364\303.{\323L\225\033\313\212\256\322\322,\334\325\210\246\253\2218\"\255B\3705q$\253\t5[\202l\326\225\264\340U\370d\311\310\255\033k\255\244\0065\241\034\333\207\006\255C%[Y:f\234\3161\221Q\310\373\220\216\365Qf\303`\324\236u!\232\2015/\237\357Li\252&\232\242ij\'\226\241ij&\222\2432Tl\371\244-_\240\213\305H\246\225\244\305D\362T\017-Vyj\007\227\336\241ij7\227\336\240y\261\336\253I-Vk\216\2435ZY\361\232\247,\376\365\237s?^k:{\234\003Y\227\027\030\'\232\316\270\2715\231qs\327\232\317\236\353\336\250\313pk>\352\354\363\315`j7\340df\260\2565\023\223\203Y\363\336\263g\232\316\234\263d\232\317\230\021T\346\223\002\251\274\265\t\234\212\215\247\250\215\306)\r\316*6\271\315D\322\346\221X\223Vb|U\330^\256F\365e$\251\322J\263\004\2075\247l\304\342\264\241\223h\346\256G&y\253\326\222r\006kR\'\301\253\002lw\2452\344f\231\346\3259\337l\271\365\245\363\270\246\231\250\363\250\363\251\2555F\323TFZ\215\245\250\232Z\215\244\246\031)\013\323w\327\350X\351J_\002\241yj\027\226\253I/\275@\362\325w\226\240y\361\336\241k\212\255%\307\275U\222\343\336\252Ks\203\326\253Ms\357T\346\272\254\371\356s\336\263nnz\363Y\223\334\373\326|\3679\357Y\227\023\362y\252R\314\000%\216+6\346\374d\200k*\356\363\203\315`\336]\006\'&\262\245\220\022j\253\221\234\346\253\3156\005g\3178\347\025\237,\241\263T\345\223\006\252K-@\322\324/!\250Zb;\323|\332r\311\223V#l\325\204\346\254\302H\253\3217\025e*\302U\273~\265\251n\274\003V\321\361V\242cW\355\230\202+E%\351\315M\347{\320\'\374\251\246lTS>\370\311\356*\005\237+\326\220\315I\347Q\347\323L\376\365\031\236\230\323S\014\264\326\222\243i)\236e\'\231F\372\375\r2b\242y\252\007\232\253\274\331\252\362M\317Z\202I\252\264\223\325i&\250\032j\257$\336\365VY\361T\247\270\367\252r]pFj\224\367]y\254\371\356z\326l\367\005\311\031\252\0272m\007\232\313\226\344\223\311\252\027w\251\030?75\207w\250\031I\000\361TZn\2475\233yq\327\232\306\272\270\353\232\316k\214\223QIq\216\365Rk\214\326e\324\276\206\263\332\344\2065\023\315\272\253\310\365\\\2774\036\225\004\225\036y\251R\254\304*\324c\025j sW#\025b<\212\261\031&\256A\234\326\204,\300U\270\315^\201\270\253Q\311\203V\343\233\"\244\363i\352\371\035y\246\274\271\353\326\231\346\365\025Qd\3014\031i\206jC54\315L3S|\337zO6\220\311L2Sw\322o\247o\257\320\207\227\035\352\274\223T\0177\275B\363Uy&\252\262MU\244\232\253K>;\325v\2375\014\262\344U\031\3566\236\265B{\2368<\325)%cUe\224\216\246\250\334N\000\353Y\263\334`\023Y\027\267\247\007\006\261no\230g\232\312\271\273.y5M\334u\315V\232|\014\n\312\273\270\003<\326=\304\333\215Ty\000\025RY\261\232\251,\365\237q>sTY\2715\013I\317Zc\276j\035\324\273\3150\234\3236\234\324\321\203V\342Z\271\022f\255\305\036*\334k\232\235#5e#\253Q\014U\350\216EY\213\217\245Y\214\342\254#\325\224o\226\237\346f\244I1C\311\315@\362`\324\r/\314M5\244\250\314\224\303-0\313M2\322y\264y\224y\224\205\351\246JO3\336\234\036\277@\345\237\035\352\253\317\223\326\240y\275\352\027\233\336\253\311?\275U\222|\325W\236\253\274\271\357MV\315E3\205\006\262n\246\3015\236\362njk\271D\315e\335\\\344\326t\323\365\346\263.n\307J\313\272\270\006\260\357\345\3018\254yg9\315B\327\034Ui\246\033z\326M\323\226&\250J\330\025Fi*\224\322U)e5NG$\324\022\266\005W-Mc\232a\351@\024\340\264\365_j\261\034uf8\352\344)W#QV\021*\314k\305H\006\rO\027\025r#V\3435f0*x\327&\254\343\000\nL\342\244F\247\270\004g\275U\227\255S\3632\315H\317Q\264\225\033IQ\231i\246ZO2\2172\235\346P^\232d\243}8>+\357\231\'\252\357=B\323{\3242O\212\251,\376\365Y\3561\326\253I>M@\363\373\323\243\270\001H\3175Z\342\343<f\250\\\220\3039\252d\252\234\346\251^^\014`\032\307\232|\347\232\316\273\272\332\0175\2115\347\314rj\205\305\336\356\365\233u(`k.Py\252r\022*\263\311T\247z\317\235\252\204\315Tfn\265Bi\0105\\\271&\242\220\026\250\212\221L\332h\333J\024\346\245D\315L\221U\210\343\002\254\305\035[\2121V\243LT\3521S\240\251U*x\324\n\260\202\247\215\261V\3429\253\220!cS\023\206\346\230\3074\350\315:G\305C!\371\t\254\322\373\\\323K\346\230^\242w\250\213\323\014\224\233\351w\323\204\224\031)7\322\207\240\313\201\212\373\312I\252\007\237\336\240{\212\255%\317\275U\222\343=\352\274\227\036\365]\356*\026\2334\303q\264\037Z\251qtW\247Z\317\236\365\217\031\252\023^\020\0175BK\235\331\346\251\313?\006\263o%\340\234\327?us\206<\326|\327X=j\244\227\033\252\273\311\221Ufl\203Y\362\311\202j\234\322g5Ff\2523=P\231\352\244\2475\001\034\322\021\3051\205FW\232P\224\365\216\247H\352d\212\246H\352x\323\025n!\322\255\"\034T\312\225f%\253\013\035M\034|T\253\035=W\232\263\027\025\247j\006\332I\017&\241-\316)U\361L\226\\S^O\334\234\326t\215\316i\245\251\214\325\023\232\205\233\025\031zM\364o\305;\314\243}\036e\036e5d\311\311\257\273\036\343\255@\363\325i.=\352\244\267\036\365VK\237z\201\356=\352\006\270\311\353Q\275\316;\324\rs\223U\247\234Vm\305\307Z\316\226|\223\317\025VY\361\236j\214\327X\315g_\\\374\247\232\347n\247\344\363Y\362\314MWy\210\357L3\202:\324m.j\215\3179\300\254\351_\034UY^\251N\325BSUd\'5\021&\233ASH\024\232z\307S$u:GS$u:G\355S,U*.*\334F\255F\233\272U\230\343\253(\230\251T\000jQ\216)\333i\3503W\255\233\024\255\336\243\"\220s\221\232\255p0:\324m!hqT\231\371\3053}!jc\034\324\016\334\324E\251\273\250\337F\3727\321\276\230\362c\275\010\371\257\271\246\233\025VK\212\253-\317\275T\222\343\336\252\311q\357U\336\343\336\241i\362i\217\'\025\037\233\264d\365\252\227\027<\032\314\232\343,j\204\323\340\236j\204\367=y\254\331\356\371<\326m\345\356V\262\345r\334\325I2MD\321\026\035j\264\244\306qQ\0318\252\363I\305gNy5RCUe\346\251\310*\273\256M7\313\366\243\313\245\021f\203\r9c\251\222:\260\221\324\311\035X\215*P\224\340\2252&j\355\262b\256\242\324\352\224\355\234\324\251\036i\3423\232z\241\315O\030 \324\204\363Q1\342\241y1Mr$R*\260FBG85Zx\312\234\212\255\273\006\202\324\302\325\024\215\232\205\2154\2657u\033\250\337LyqL\r\223R\006\305}\273,\371\006\250Kq\357U$\270\367\252\262\\{\325i\'\252\322\\\343\275Bn\300\357@\275P1\232\206{\260\007Z\315\270\276\000u\254\331\365\000;\325)/7\202sT..sY\322\310I\353U&9\004\3257j\201\333\232a\220\212\251r\333\305R\336rEW\270\223\025A\344$\324\016\325ZV\315Wa\232h\213&\237\345b\230c\366\241V\236\024\032r\3063S\244B\245X\300\251Q\005N\221\324\313\035H\221\325\210\342\036\225i\023\025f4\351V\025i\342<\324\212\273j@)\350\276\2652\2574\307\004\023Q\270\310\252\322qP\357\301\247n\312\363Q1\004\340\216*\215\314%\016GJ\253\277\007\006\202\325\023\032\214\365\250\311\246\226\246\227\246\031*=\305\21586)\341\363_iM?\025By\370\252R\334{\325W\270\367\252\362\\c\275T\222|\325w\271\307z\254o>l\223U\256u\036\0175\225=\361l\363U\036rGZ\200\\\2058\315A<\336\225JI\371\353P<\233\263\315Vv\250]\261Q3\201\232\2533\346\253uz\202\3421T\235\006N*\007\216\253\310\230\250\nP\026\235C-4-(Zz\214T\311R\255H\202\247AS\240\251\324U\210\261V\220\002*h\306j\300^)\353\305=FMH\213\315J\023&\244\362\310\346\211\006j\027^*\006\213&\253\311ns\305Fc+\326\232\313\236\265\013\r\303\006\263nc*\331\305B\033p\246\261\250\330\323\030\324,\365\023J)\233\363N\rF\354\322\206\257\261\345\270\353\315P\232\177z\2434\365RK\212\253%\305W\222\347\031\346\252Ms\220y\252\022\335\020:\325)n\t\3175U\346\367\252\362\334\205\035j\214\227\1777Zx\230:u\346\251\314\334\324\r6\332\214\315\232\216I2*\27395\033\234\212\254\371\352*\274\222\347\255Vv\250Y\252\027\346\242)\232]\231\243e)\024\314\nP*E\\\212\221\"\307\322\255Gn\010\353O\362\266\324\261\255L\243\006\245\003\245K\027\275Y\214\325\210\333\006\255!\334*U^)W\255L\265:\032\224\215\302\232R\243d\250\212b\241s\315@\340\032\205\3075\013\257\347Uf@\343\232\315\221|\267>\225\0336j65\014\216\024\036j\253\310Z\231\234\323\203`Rn\315.\352P\325\365\304\267\036\365Jk\214\367\252R\317\357U$\237\336\252\311=T\226\177z\2455\307^j\224\263\373\325In*\234\267X\357T\346\271\316y\252o?4\253xW\214\323\314\273\205C!5\026}\351\t\250\035\260i\205\263P\271\347\212\251*\345\216*\026\030\250_\025\013sL\301\035iI\244\334)3\232]\231\245\010jT\210\372\325\245@G\"\236\240\257J\262\024\262\362(\021\324\212\264\361R)\346\247\214\325\2045f&\305ZS\362\322\205\247\250\253(\270\\\232\267\010V\217\"\225\200\250]A\315C\"\361T\245^j\026\\TL*\031*\273\016\274U\013\264\301\315g\263`\342\242\222\\UVb\3074\322qL\'\024\233\251CR\3474\345\257\252e\270\252r\317\357T\245\237\336\252I=U\226z\251,\365Ji\352\224\327\036\365F[\217z\251$\376\365ZI\252\273\311Q\371\234\324\2119\025 \227p\246n\346\232\317Q3f\242c\203L\'\275G\216\t\252\223\032\200\232ajc\034\324l:\322`\232z/5!%E3\314\346\245Ij\302J\017z\235Xu\251\222b\275zT\273\263\310\351O\0074\341\203\332\236\242\245S\212\231[\0254o\315\\\211\263VB\340R\257\336\025nb\014k\212\226\321\200\\S\344\305Ws\203P;dUy9\250\236\253\311\305B\374\325y2:U9\237\177\007\255g\334&2j\223\214\346\241c\3151\232\230Z\233\234\321\234R\206\251\025\253\377\331"
+byte_png: "\211PNG\r\n\032\n\000\000\000\rIHDR\000\000\002\000\000\000\002\000\010\000\000\000\000\321\023\213&\000\000\001NIDATx^\355\3351\016\200 \024DA\362\275\377\225\211\375VFE!\314\224\357\006l\002\264\006\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\277\251\014\223\350\031\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\200\313\216\014\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\300\020=\003\260\222\312\000\000\000\000\000\300\352L\277\000\000\000\000\000\000\000\260-\227>\001\000\000\000\000\000\000\000\000\000\000\000\000\000\000`^\276\023\000\000\000`+\016\302\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\357\351\031\000\000\000\000\266g1\001\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\340\013\336\307\007\000\000\000\270\2512\000\000\000\0000\206!\006\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000`-\225\001\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000x\352\004b\"\006\010O\232\021\276\000\000\000\000IEND\256B`\202"
diff --git a/core/res/geoid_height_map_assets/tile-7.textpb b/core/res/geoid_height_map_assets/tile-7.textpb
index d00efe8..9667e64 100644
--- a/core/res/geoid_height_map_assets/tile-7.textpb
+++ b/core/res/geoid_height_map_assets/tile-7.textpb
@@ -1,3 +1,3 @@
 tile_key: "7"
-byte_jpeg: "\377\330\377\340\000\020JFIF\000\001\002\000\000\001\000\001\000\000\377\333\000C\000\004\003\003\003\003\002\004\003\003\003\004\004\004\004\005\t\006\005\005\005\005\013\010\010\007\t\r\014\016\016\r\014\r\r\017\020\025\022\017\020\024\020\r\r\022\031\022\024\026\026\027\030\027\016\022\032\034\032\027\033\025\027\027\027\377\300\000\013\010\002\000\002\000\001\001\021\000\377\304\000\037\000\000\001\005\001\001\001\001\001\001\000\000\000\000\000\000\000\000\001\002\003\004\005\006\007\010\t\n\013\377\304\000\265\020\000\002\001\003\003\002\004\003\005\005\004\004\000\000\001}\001\002\003\000\004\021\005\022!1A\006\023Qa\007\"q\0242\201\221\241\010#B\261\301\025R\321\360$3br\202\t\n\026\027\030\031\032%&\'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz\203\204\205\206\207\210\211\212\222\223\224\225\226\227\230\231\232\242\243\244\245\246\247\250\251\252\262\263\264\265\266\267\270\271\272\302\303\304\305\306\307\310\311\312\322\323\324\325\326\327\330\331\332\341\342\343\344\345\346\347\350\351\352\361\362\363\364\365\366\367\370\371\372\377\332\000\010\001\001\000\000?\000\367\325\3014\371\"R\231\350j\233.\036\244A\305?\024\273i\301iq\322\237\263&\223i\364\247\000A\247\201\232xRMH\252qR\252\324\253R\255J\265*\324\253S\245J\277xT\303>\264\342\271\003\214\323\343#\247z\260\242\245\024\360i\300\323\301\240\324M\301\315Z\267\225Yv\265H\313\316\005T\224\020qK\0023?z\261s\022,@\216\265\234s\232LPi\247\024\302\005D\3521P\2203PI\212\251 \252\262-V\221j\254\203\002\252J*\253\212\254\331\252\362-T\231x\254\371\220U\031V\251\310\2475\023-B\302\253:\344\324E*\027J\204\307PH\203\025\357\215\021\316E1\303l\301\250\n\372\323\324S\300\247\001N\013N\013J\026\234\027\212v\316)\352\224\360\265 Zx\024\365\025 \251V\244Z\231jT5*\324\313\232\260\200w\250\201\006rW\245ZS\305H\r8\032x4\360ii\214)\261\261W\253\350r\271\250\235C\311\315]\265\214q\201F\241\016\330\201\025\223\267\232M\274\322\025\250\331qQ\034\324MQ0\252\356*\273\212\257 \025NA\317\025Y\306j\254\252MUu\252\316*\274\202\251\312*\214\300sY\362\216j\263\212\256\302\241e\315B\313Q\025\250\230\n\201\326\240u\342\276\200\003\326\241\230\002x\252\373rsJ\020\323\366\032pCJ\0058\nx\024\360\271\247\005\247\205\247\005\247\005\247\205\247\201O\002\244QR\n\225M=H\365\251<\330\323\031aR\255\304{~\\\223@\363enX\201\350*\314h\024T\300\323\301\346\236)\340\323\3058t\245#\"\242a\206\315[\200\3459\241\201\r\221Wme b\245\277\005\255\301\035+\030\214\032J\010\315F\313Q2\324L\265\013\200\005V\222\252\310j\263\325wZ\256\353\212\252\340\232\254\350j\263\245U\224`U)GZ\317\230rj\234\213U\034sQ\025\250\314d\366\250^3\351Q4F\240t\305WqU\334W\2767\003\"\252\3111\r\203J\256\255\300\353S*qK\201\232\\sF)\300R\342\236\007\024\360=\251\340S\200\247b\234\001\247\014\366\247\000i\342\2363N\024\244\344c5\022.\331\366\036A\365\2558\342P\271\310\253\010GJ\220\032p4\3654\360i\340\323\301\251\005<R\024\315 \334\207\212\231\030\267j\263\016U\207\025v\\Ih@\355\332\262^.zS\nSv\323Yj&Z\205\306\007\025VJ\255 \252\262\n\256\303\232\215\326\253\310\265\003 \364\250^>*\244\250\000\2523-P\230\001\232\316\227\255S\220Uw\034\324Ey\2461\307\002\242<\324NqUd9\252\262\014\325y\026\275\335\345UZ\314\272\270_^k5\365/&\\\356\255\013}b9\024|\302\247k\325<\206\024\364\274\004\365\247\033\300\rK\035\310aS\254\253\364\247\254\253\237\274*u\301\024\375\246\227\276)A\247\003O\024\341\326\234)\302\226\242\225\312\014\324\006\360\t\327\'\232\330\266\270\363\024dU\241R\203\221N\024\361R\nx\251\005H)\353O\003\212F\034T\226\303$\214T\340a\263\332\247\21418\035\352\t\227ksU\317Zi\024\302*\'\007\265V|\367\252\322\016j\273\212\255\"\232\256\302\242~\225]\352\026j\201\315U\220\325\031\210\031\254\331\316j\204\243\232\252\353P\024\346\230\311P\264u\013\214\n\251)\364\252\315\222j&Z\201\326\275vk\261\264\363X\267wyc\315e\314^F\342\243\215.c9V5i\'\272\003\251\251\222\356\345G9\253Q\352\017\374`\326\276\237\177\004\214\003\234\032\350\022+Y\"\0048\006\253\315\004jr\257M\212]\235^\254\255\3169\316j\302H\255\315?\000\364\247`\322\342\234)\343\2558S\202\223Q\317\036R\271\3717\256\242\003\036\365\321Y\311\205\025\253\033\202\265(8\251\027\223\221O\002\244\006\236\r<\021R\003R-H)B\356<T\221\241G\006\247n\306\246\204\372\322\\\306]w\001T\212\220i6\212aZ\211\326\253\274u]\323\332\253\272\325Y\024\324\014\225]\322\253:\032\252\343\006\253\271\342\252\312\334V|\3079\2522\373UGSU\335y\250\312\324L*\273\234\n\247+f\2538\315DV\230V\241\221p+\266{\302\313\200j\253\006\221\263S\307\0161\232\262\221\003\306*U\205s\202)\376B\372Q\366d=E\002\324)\312\361Vc\226\3421\2649\253\021\3159a\271\262*\311\212F\\\206\305Vyg\204\363\234\n\267g|\314\303\232\331\212`\313\234\325\205n:\323\301\247\214zT\200\017Jp_jv*\031\234*\034\326\034\314\262\337\215\275\253^\334\020\202\257C!S\315^G\004u\251T\342\234\032\236\032\236\032\236\032\244V\251\225\251\341\252\304c\212\234/\0241\3711L\206C\346\214\326\201\001\243\342\250\314\2705\\\344\032Jkb\240\223\025]\260{\212\201\322\253\274uY\327\007\245@\311\315C$\177-P\2310MS\221j\214\335\352\214\240\232\247\"\324\014\265\003\245B\313\212\253)\364\252rd\324\014\265\023-D\313Q\225\250$\357]zB\005H\261\200zT\303\000\234S\325\260jP\3319\251\003S\267T\212sR\000*\302\000G\246*\310f\362\301\035\272\3222\254\211Y\263F\320\276\350\370\253VZ\211\334\025\215m\305p\254\274\032\262\262\217Z\225$\025:\2605(9\245$\001\363\034VV\247v\261\304Bw\254\315=K\314]\273\232\350\241U\333\214\324\352>n*\322\016\001\251\203qRg\2758\032p4\360jUj\2205K\037/W\342l\214T\300qQI\322\252\031\032)w\036\225\245\005\302I\027\0075\014\304\347\232\256O4\307\220(\250\231\362*\264\231=\352\273d\032p9L\032\215\205@\351\223P\262\n\257 \025Bt\2522%R\225=\252\224\251T\3359\250\0351U\335j\274\213\305S\221\016MWt\250\031j\026Z\205\205F\343\013\305T\220z\327l1\212\t\2405\033\271\247\254\230\251\004\224\355\365\"I\212\235d\253\013.@\036\2252JGzr\271\r\212I\000u\254\371-\312>\345\253V\267m\037\312\306\257\255\360\340f\254%\341\007\255[\212\364g\223V\016\242\2128\305S\271\324x\'uc\3114\267sc<V\265\234F5\034V\254l@\253\021\270\'\232\267\033\034`\366\2513\315<\032p4\360j@i\340\324\201\252h[&\257E\367\205[\003\212k\256k>\355\016\334\212m\263\262\307\221\324u\025\241\023y\311\367j)\241\220\002v\342\263\344\334\257\315\000\344sQ\271\031\250\033\257\025\023\023Q\2279\346\232\317\307\025\013\023P=U\225sT\344J\247,}x\2522\307\355U$J\253 \002\252\272\324\016\231\252\322&*\263\256j\273\255@\353Q\371f\253\315\264qT\2449\351]\216\352B\364\335\376\364\273\3517\363\326\236$\347\031\251\003\232z\311R\254\225b9x\251\204\235\352\306\362@#\222G4\365\177Z~\320\303\2450\333+v\244{b\2540i\271\225s\355H%\237=\rYF\231\327\245<Z\263\237\235\252\335\275\252\241\031\253\340\005\350i\352\376\365j0N\t8\253\260\347\025)<\212p5\"\236i\340\217Zxjz\270\365\247\347\212\226\006\303c5\241\021\031\025u\0334\375\271\025\004\261\344U\031$[@\305\206A\252\366\232\260K\235\270\340\232\350\"\270\216\342.\203\232\247wl\270$V[|\247\025\013\036\265\t84\036j\027Z\256\340\212i9\025\023sPH1Ud\252\262\000j\224\340\n\241/5Q\327\232\256\352*\007\025\003\256j\263\245Wt\315B\321s\322\241\230\205\\V\\\247,j\273\n\353K{\323\031\3523\'=h\363)w\322\356\367\251\026S\334\323\374\301\236*Uz\231d#\275XIr\270\251\343\224\250\353S$\244\266qVc\220\221Sg\"\244\213\030$\214\201I\265\013\020\007\024\341\n\372U\210\320/\030\305J\027\326\234\247\232\223w\024\233\271\342\245\216Gf\003&\265!\225\322?\231\177\032\224J\244\344\236\264\365n\375\251|\320;\323\204\231\357\212\221Y[\370\252@\213\331\310\245\303\257G\006\245\206FY\006\354`\326\234D\214\021\322\256\306\325aNE8\214\326V\261\thN\005`G\003\211\001\036\265\322\332e-\320\223V\334\253\306y\346\261\347]\254j\231<\324Ny\241O\313H\340\325y\001\305CQ\265B\347#\232\253 \252\262\n\243=Q\220UY\005Wu5\021N9\250\231*\t\020Uv\0305ZW\030\306k:\341\3075\225$\247\314<\324-7\035+\253-Lg\342\242f\346\220=/\231O\022f\234\036\244\r\306sR,\225:\276jd|\032\262\217\232\236966j\300\221wqS\253\361SC>\323\214qR\226L\345{\325\210\266\354\334O\"\207\224\0312\016i\333\370\245\337\315(zpl\323\343r\254\rh\255\323<!1\323\2759I\315L\034\201\212P\3709\247\357\247\253\212\225$>\265(|\323\362}kB\326l\240\004\326\224R\002\243\025a^\246F5\235\252\334\241\002 A5\237\n\000FGz\331P\242\025\003\245&y\340\361T/\007$\326y\353P\275*\0361J\325^SP\036\225\023\032\257!\250\031\252\t\000\252\222\2405FD\353UY9\351Q\262\014t\252\322)\002\252\273`UYe\000\034\232\316\270\272\003\214\326t\267LI\252SL\315T\2379\250^\272\306j\214\275FZ\233\276\215\364\241\352E\222\244\016E=^\246I*x\345\303sVc\220g\255XV\315N\2161\201\326\247I*P\374\346\254,\233\227\030\344T\210\375\273\324\245\243\\ps\336\236Y\n\r\275i\241\215<5<5J\204f\264\240h\304G=jx\345]\273v\365\357HH\316(\310\247\003R.jE5\"\2675*\266{\324\360Hc|\036\365\251\024\300\r\302\247[\245\0377j\255s\254$jU3\322\263\340\221\256%.\3479\255\r\277\'\025v\335\213\303\203\332\202p\325Z\353;+9\2175\033S\001\301\245,*\t\016MF\304c\025\013\342\253HqU\335\252\007j\201\310\305U\223\0075U\360\rA#\200j\234\322u\254\313\213\205N3\223YS\312\357\236p*\234\200\325W\025]\305@\342\253\270\256\221\236\242f\246\027\246\227\244\337J\036\236\255\315L\257O\017R\254\203o\275J\222\014`\365\251\243\222\255\244\274U\230\245(Cf\247\363\2030*1S\tr\243\216\225<r.\317z\221\\\203\221R|\3167\366\245W\355O\017OW\315H\246\245V\253\021\310sV\321\216*e9l\324\244/\033M\000T\252=*@3N\000\346\244\000\212\2327\033H#\'\265J\262\270\340\362)Y\344*|\266!\217j\242\320\334H\307w\353E\264\317o7\227 \305m\303.\365\253Q\002\2719\305)j\206\341\263\t\315g\340\236i\215Q\021\315!\340u\250\034\324\016\370\250\214\235\215C#dU\031e\332\330\250\232e#\223\212\201\347L\3435ZI\224w\252sN\240psT%\270l\236*\214\323H\335\361Td\004\234\232\255 \252\316*\263\212\256\342\253\276*\263\326\343=FZ\243-L/@zxjP\3305*=J\032\236\032\236\257\315XG\251\322J\264\222d\016j\3126\027\255O\034\312\001\004g4\364~j\302\276EH%`0\017\02524~I$\374\336\224\320\354G\034\212z=N\257\232\231\030\032\261\031\031\025~-\214\204\226\346\234\016\rH\246\246F\030\247\203\212\225MH\0174\372\221jT4\375\2719\034\032\221\034\243a\300#\351R\313i\004\311\275@\3156\331\014M\260\363\216\225t7\2754\2775RG2\311\264t\315\0050\265\003\255B\303\024\3228\250e\373\265U\352\273\203\236j\t\t\344\003T\244\353\223Ud \232\254@-\311\305W\231\030w\252RUY\rUpOj\253%WqU\334UY*\264\225Y\305WqZ\214\364\302\324\302\325\031j\003S\303S\203T\212j`\324\273\251\352\3252=L\217V\242z\264\030\201\311\251\243q\221\3179\253A\324\215\270\344R\254\234\324\301\262)rj\304\022\005V\311\355\322\235\021R\307q\251\343x\311\347\"\246R\240\360sV\021\252\3129\035*\302\022\3250\343\212\225\010\035\351\373\226\236\033\322\244Y*@\365*\265H\257V#`i\316@\024\353y\227qBz\324\341Fs\336\235\273\034S\033t\234/\342i\025\002P\354\000\252\256\331\'\025\023\017Z\211\216\005B\355\220j\273\363P?CU^\252\3123T\344\030\252\262f\2539>\265]\262\016qU&\31318\250\031\366\202\000\035*\233\325g\252\3626F1U$\252\317\326\253=Wz\276\315Q\226\2463Te\251\312\324\362F)U\371\251\225\252M\324\233\371\247\254\225*\2775:=XG=\252\322\3121\315M\033\215\303\'\0258\223\022gvj_0\023\221\305L\217R\206\315=X\253f\245V\311\311\251A\307\025<mV\021\352\324m\310\253\276daF\336\264\340\376\364\340\304\367\247\206>\265\"\276)\342Jz\275H\262{\324\253%XI)\354\344\212\2127\304\325t\316W\006\234f\004dT\213.!\372\323\004\231\357MbMF\300\001P\261\250\037$qQ\230\230\236\265\023\243\003\216\325V\\\347\025Q\363U\344\351U$\252\317\357U\330\016\265ZNx\002\252\310\010\353U$\034\032\251!\252\262\032\253!\252\357U\234\325g\252\357VY\2522\324\302\324\302\324\241\251\333\351U\271\251D\224\361%858\032\225[\025:\275O\034\225hH\030\016*\304R/\000\212\225~\376\007\"\247\306\334f\247F\342\245C\270\3435(\334\006H\340w\247\253T\241\301\307\255N\273\210\315J\256A\346\254\307\'\025ad\007\275J\257\3163S+{\323\374\314S\204\231\247\007\247\254\224\365\226\247I*\304rU\200\340\256*\031\t\007p\2530\310\255\016\032\246E\017\031\001\272\014\212lr\0026\267j\224\306G(i\271b9\342\230\330\305B\355P\266{\032\217\220:\324lX\036MA$\203?tUY1\234\342\240`\233y\353T\337\000\236*\244\234\236\225\003\223\214\n\256\303\234\325i\201j\245,|U)S\025R@y\252\256*\264\225]\305Wz\254\365#5FZ\230Z\233\2323F\352P\364\3573\336\236\262T\201\352T|\324\271\251Q\215XCVU\370\002\254F\334d\036j\314R\355<\216jP\304\375\354\342\246F\305H\255\315XIH\300=;\324\303i\034R\240\014\330\315YI\n\214\003\370T\236fXdT\312\346\246I1R\254\203\255L\263f\245\016\017zP\324\341%8IOV\357S\243U\204z\260\262qR\002\030S\014\215\t\340dU\210\356\001\371\223\203\334R\312\215\031\022vj\2369\tA\3159\217sP;\222j&$\324D\220qL\'\236hb\n\344\n\252\340T\016\265Y\326\252\310\265VE\252\356\265Y\352\264\225RQT\345\025NQ\326\251\310*\263\212\256\342\253\310*\263\212\215\232\230Z\232Z\233\272\227u!jn\3527\323\325\352ej\225\032\247\017R#\325\224z\260\222b\247G\346\254\240\'\007=j\310\220\340!\355R\241\347\232\235B\263`\034}jN\000\3109\251\020\343\004\364\251c\223k\344T\302M\317\222?*\223~_\203OW\303u\251\225\315X\215\201^MH\254)\352\376\365 zpjP\3075\"\261\365\251\221\361\336\247I*\302?\275L\222`\324\334H9\244\020\341\276S\214S\247\231\374\225\213\25754G\010\005L[\345\250\310\3435\021`F\000\315\001w)\'\202*\t\025\227\250\250w\340\324NFj\007\342\240z\255 \252\262UY*\264\225]\305V\220\n\247*\361T\344\025NE9\252\256\247\255WqU\334Uw\034\32564\302i\245\251\205\2517\321\272\220\265\000\323\225\271\251\225\252ej\225Z\245V\251\221\361V\021\352\314mVc\223\025b=\316r*\3021\035jej\2207\241\253\t 1\355<S\343\004\364\346\244\r\203N\014*Ej\231^\245W\251U\352P\365\"\265<5.\352z\275J\255\357\212\235\034b\247G\346\246W\2530\311\201\223S\253qP<\231\270\031\355Vc\220\032\235\270N)\214\333\223oJ\214.8\245\357\301\243\206\371Z\252\\\302c9\035\rU<\324.j\0075ZF\252\262\032\252\344\325g\250\0335]\371\252\362.j\253\2475RT\346\252H\270\025VAU\234Ug\025\236MF\306\230M34\231\245\317\024\204\363@4\340qR+T\252\3252\265J\255S+T\350\325b7\342\255F\331\346\254\306\3079\007\025`1*9\251U\2601\326\246F\031\03156T\036\016jh\330\203\221R\222Y\263\214f\234\230\r\317\"\227?5H\032\244W\0252\275J\257R\253S\367R\206\247\253sR\253w5*\265L\217Vcl\324\352\330\220\n\266\207#\025Z\351\037\031\217\255V\216\352x\333\016\207\212\275\036\240\2546\234\203SB\354\316I\351R\203\315.h\341\271\035hp%\214\251\353Y\322FU\210\"\253\270\036\225Y\326\252\312\225Y\322\253H\265Y\324\347\245@\374\034\032\254\343\234\324M\310\252\262\216\265RJ\247.MUu\252\322-Vq\305d\226\246\023Q\223Q\356\367\245\315(4QJ)\300\324\252jUj\231Z\245V\251\320\346\254Fj\324lG\025r\006\031\346\247B\017z\2262;\323\267\020j\324j\206\035\333\271\364\247D\344?=*\324\215\362\014\021N\204\253\0341\353J\300\256y\244W\251Fz\324\252\330\025*\275J\257R\007\315<8\306)\301\252Enjej\221\032\254\243\325\210\316X\032\275\033t\251\202\203\324R\375\231I\316(\373\002g~\006j\312*\371x+\217|Uy\303Dp\006j\271\270 \341\201\247\3078\335\234\324\305\260\301\327\241\250\346\001\206ER\221*\263\240\252\322%Wx\2168\025VH\315Wa\216\242\253\312\252z\201T\345\\\032\256\353P\272dUY#\342\252\311\030\364\252\222 \305S\221j\214\301\206qX[\3154\311Q\2264\335\324\007\247\006\247\356\245\315(4\361O\025\"\232\2240\251\021\252\3025X\215\271\353V\243<U\204j\260\217S+T\252rqR\347i\3009\251\343 \216N)\341\311\372T\270*\241\273\032\221\037\236\275}i_n\357\222\236\013\005\301\351R\203\307\006\234\032\245Y8\247\211=\352@\365\"\275J\032\244V\251U\252dz\265\033\236\335j\3542g\212\273\031\253+\332\246^j@(dW\352\240\325Yl\321\262v\326M\305\254\321JY:zS\241\271 l\220b\247,1\301\315B\3705Y\305V\220T,H\340UI3\232\253&j\263\212\255\"\345j\263\257\025\t\025\014\203\255R\224U9{\3259\007Z\204D\013d\212\346\235#Y8l\212a\205\030\375\354Q\035\270c\214\212\212\341\022!\214\202j\274\1773\201S\315\037\227\214w\024\305bx\245$\212r\266j`i\342\237O\006\245SS\241\253\010j\314m\305XV\253\t\22229\251\221\271\251A\305H\255\232\263\033\003\324\324\350\343n\322;\324\310\352\t\004qI\237\234\340`S\267\374\331\251<\302\344f\224\032\221Z\237O\034\nz\265H\257\315J\262T\253\'\025\"\275L\257Vb\223\232\273\033\177\020\352+B\031\001\025q\rN\246\245SN\240\362*\t#\r\326\263o-\201BTr*\22420;[\250\251X\203\355P\275WqP8\252\322\n\253 \252\356\277/\270\252\255\301\346\253\270\344\325g\340\325yMT\227\232\251 \315W1\344\323]6\360+\210!\275i\244\260\356i\242G\\\340\365\250\233s\234\232tm\345\364\024\366\230\276\003\016\225$e<\300{w\253r\305\013G\274\020*\025\2156\360i\n\225\353OSO\310\245\006\245SS\306j\302\036*\304f\254.H\3105v&P\230\024\241\276j\225Z\244V\346\247F\346\247S\305H\037\r\234\324\306r@\030\024\343\206\210z\346\206R\207\203\232P\346\244W\247\207\247\371\234S\225\363R\251\342\236\032\244V\342\245G\251\225\252\304oW`|\361\232\275\003\020\370?\205h\306\325aZ\245V\247\203K\234\212kUy\260T\346\261\034\001x\333jC\322\243aP=B\302\253\310*\264\213P\225\311\305T\232<\023T\344\030\252\322\364\252\217\315VqP\262z\324eB\202j\234\254s\\q\0034\322\242\242*3HV\230V\215\264\240\021\322\2367\221\214\232zeMYb\257\017\'\221L_j~1K\355OZ\235\rYJ\235x\30752\203\334\325\230\316\016\005M\273&\236\rJ\244\324\350jt5 4\360j@\374b\235\274\205\306i\341\325\227\035\351\331B\270\031\315H\2411\202pip\017\3359\246\206 \324\251%L\032\236\255\357S#qS!\253\010j\324M\203Z\020\2718\307Z\320\211\362\005YV\251\225\252@\324\355\324\326<Uy\233\nMd/\31736)\347\245F\325\003\201P\260\036\265\023\343\025]\361P\036\032\242\22523Y\363&\t\252r-T\230\205\025M\245\001\251\254\340\362j\244\217\222Fj\254\307\025\3071 \323KSI\244\315%\024\240T\2129\247\225\364\244\035i\350y\251;f\216\364\361S%XS\201Vb\031\344\324\253\326\254FqR\003\322\245SR\251\251P\324\312\3250jpj~\352pl\323\201\247\251\247\344g\203N\016Tq\336\236\233Yy?7j\010(\3305\"\310jEo\232\247V\346\254!\253\010jtnj\375\273\343\006\264!l\n\264\215S+T\241\251\333\251\013U[\226\375\321\252\01003\353C\032\211\215D\306\240z\201\252\026\250\232\232yZ\2512\003\232\243*V]\337\312\246\262\032L\277\025\034\222\262\346\230\231#&\243\230W\"\3035\013)\034\323OJm(4\341\322\235\3058u\251\001\371i\271\346\245\000\005\036\264\341\315;\024\242\245N\265b<g&\255Fw(\305L\240\324\252jE52\364\251W\000sO\004v\251U\252P\334S\303\016\224\340\303<\364\247\340\377\000\t\006\224\026\035j@\331\247)\347\223\212~\354\232U?5M\221\320\362E4\0346*`@#\006\247Rv\346\246\215\371\253(\325:\265h[0+\357W\342n*\3225L\255R\007\245\337Az\255p\333\276Z\256H\025\033\032\211\215D\306\242j\205\272\324,*&\025\0218\250$\"\251JG5\225z\003Fq\324\326RC\206$\325k\200\014\230\024\364\030J\202q\\\203TmQ0\301\246\321O\006\235\324S\200\247\017\273H:\324\213\315H\235jJ\\qNZ\262\225f\037_z\237<\323\201\346\245SS)\251\001\342\236\rJ\246\244\007\'\024\376\206\216\246\2342;\323\203\266z\324\201\263O\r\357O\006\234\032\237\274\223\223O\334\t\342\244V\305M\033\022x54d\022y\253\010\336\365aZ\256[\276\ri\304\334U\224j\231^\245\034\216\274\322\027\342\230\322`{\324\014\347\251\352j2\325\0315\033\032a5\033T,*6\250\237\245V\223\326\251M&;\325)%\310\254\311\231\235\375\252\274\204\001\201T\235r\371\2511\204\252S\232\344\332\2425\031\246b\2234\240\324\212i\342\237\216)\005=2O\025 \340\324\213\315:\234\242\246CV\242\373\270\251A\346\244S\315H\247\007\025*\265<5H\246\245\006\236\247\030\247\347&\234\r;u8\032x4\354\324\212i\340\214R\203R)\342\244\r\305=\037\232\261\037&\254)\003\241\251\221\253B\324\256\316z\325\350\337\232\266\215R\243v\251\225\360\334\323\032Q\311\364\250w\345\267\032k74\302i\244\324mM=)\206\243aQ\221\232\211\306*\234\355\201Xw\263\034\234UA&\345\246>1\315Tq\223P\224\3074\215\367j\204\347\223\\\253\n\211\2523L4\322)GJr\232\221jL\214R\016\224\345851;\200\300\247\255;\255<\n\221z\342\254\241\251\224\324\203\255<\032z\232x\340\324\273\362j@i\341\251\340\323\301\342\227>\364\3655 4\354\323\324\323\263N\006\244\rO\335R)\035\215M\033\342\254F\377\000.*\324#.\0015u\006\016\001\351V#\227o\007\275^\215\270\251\343o\230S\236O\236\243$\236\275)3\305&I\245#4\322)\214\0050\364\246\355&\232\313\353Q5V\231\302\212\311\272\271\007 \032\307\235\3015\000\366\244+\353Q\262T\022t\250\034\374\265B\342\271\206\250XTG\2555\272RQN\247\255;\275-;<\n\261\000\334qO \251\247-H\242\236\275ju\351S%J\270\301\342\224T\203\255H)\340\001OSO\006\236\246\244\006\234)\303\255H\264\372p\300\247dR\346\236\032\234\032\236\255\315J\255V#j\271\013\373\326\204-\214\036\265e@.\030U\264j\231\032\234y\346\216qJ\026\227o\265\033i\n\323\n\212i\000Td\324N\302\252\3132\"\222Mb\337j\013\310\006\261&\273,x5\n\356\220\344\324\341p)\255Q9\342\252\312j\t\017\025F\342\271\206\250\232\241=i\207\255\'C\365\245\3174\3454\365\247\034R\255<T\3206\331*\334\201J\356\034S\000\251TR\201\315J\265:\032\224R\216\rH*U\353O\305/Jx\247\203\305H\275i\375\005(<\324\212i\343\245(\353N\310\2434\240\323\201\346\236\032\245F\253(j\324m\212\273o(\003\232\271\033\340\017z\270\215\300\251\203T\212\325 l\323\301\036\224\271\244\246\265FMD\316\005U\232\345\020ry\254\253\275P 85\207s\251\273\222\003V{4\262\266NhX\017z\235\020(\247\036\225\013\265@\357U\24495\013\364\252W\035\rsMP\260\250\217Ja\353M>\224\237Zx\353R\255\004\020sNZx\351R\3062\302\256\034\234\016\240R\262\355jr\364\247\201O\002\245PEL\2434\374q\212r\361R/Z\224\032)A\247\253f\246SN\316i\300S\207\024\3658\024\377\000\306\223\232PisJ\r8\032\221\032\254\243U\230\332\255#t\301\253\201\211\333\316M_\211\376Q\315N\255S)\251\226\244\002\235\201Lc\212\201\246\003\275T\226\355\001#p\2527\027\310\001\371\353\036\353P\31085\225!\232v\357\212\022\323\007$T\302\020;PP\na\030\250\235\270\252\356\325]\336\240s\305FNEU\270\037)\256i\205B\365\021\250\3154\322\216\224\240\323\267v\024\365c\322\227\004\032\221jd\353W\004\213\344\343\034\347\255\'\336n:T\252*@8\247\001R\257^j\302.O\2659\200\315\'\361T\202\236\246\235\216)\271\305875*\275H\255R\251\247f\2274\240\320\\\212\177`M\034\2122i\300\324\210jtj\265\033U\224nEZI\006r\0175j\336B\033\035\215_F\315XCS\241\251\205G,\351\022\222\314\005a\337k\221\306v\241\254\306\325%\224\360MW\226I\333\236y\252\315\034\316~f4\253j;\363R\210T\016\224\245@\250\310\002\242sU\335\261U\335\370\252\316\336\365]\233\232\211\333\212\210\266\005V\235\376CX\rP8\250M0\323i;Q\320\322\203O\006\244\\g\232\231zc\0252\240\3019\351O\031\253\366\361\006\204\237J@9\251\025sO\013R*\324\3506\214\320y\315 \247\212x\247\203\305!\353IOZ\221s\332\244V\251\001\245\3158\032\0174\375\304\256(^\264\273\273\032\\q\220i\351\232\2363\315[N\225*\2675idP\177\n\232\031\300q\223\326\264\342\220\021\326\255#\014T\236z \371\230\014T2\352\320\304\244n\025\201\177\252=\303\355\214\361T\022\006w\334\347$\325\264\205@\351S`\001\202\0055\243R\231^\275\352\0221\332\232[\025\0335B\317PH\365VF\252\3625U\221\352\002\324\306j\205\237\212\2513\326CTL*\026\025\031\246\232oJJQOZ\225jd\025a\027\212\220-[\266/\367\027\275?\313*\377\0005J\242\244\002\244E\035i\371\244\301\243\270\342\236:\324\213\203J}\251\264`\323\200\247\251\"\244\024\360i\324\240\323\201\315;4\240\322\343<\3203ORj\302c\031\025b6\342\245\315I\021\314\200z\324\267(\321/\007\236\264\221j\245Wk\360EX\376\332\n\274u\252\263\337\3176Ys\315W\002Y\016]\215N\221\201S.\005J\036\202\325\031r:To/&\242g\r\323\255@\356A\305B\322T.\325\004\215Udj\254\346\253\273S7z\324\022=T\231\253=\2526\250Z\243\"\233M=)\264\243\255=jU\251\343\253H3Sm\253\226k\222@\\\232s\347\314;\252E^)\340T\212)q\203J\000\240\014\344\n\007\002\236\264\277Jv\3363IJ:\323\301\247\212ZP{S\201\247\016\231\245\006\244\340\2504\006\300<\320\rH\246\244S\203S\207\002\245\017\3059d!\262*\353\312&\201r9Z\246\360+6qJ\220\252\365\025(P\006;R\360(\336\005\036e/\233\212<\332i~*6z\205\244\301\3105\021\227\223\236sQ\263)\034u\250\013T.rqU\334\325y\rVs\315D\315\315@\346\252J\325U\205B\325\023S\017Za\246\232oz\007Z\221jU\253\021\366\253q\212\235Fj\345\2332K\362\212t\247t\333\261R \342\244\002\235\333\203\326\202=z\212\024\022\t\024\244`\n\\aA\245Z\221\027\223\2321\212LQ\212p\353N\006\236\016h\3058u\247g\265(\353\305/\"\214\234\346\235\332\234\032\245V\251\001\247\253b\244W\253q8\362O5\037\233\363u\247y\231\024\365l\3655\033H\001\3057}4\311I\346Q\346Q\346S\014\225\0235D\315Q\261\246y\230\353\315D\374\234\3665\004\200\212\253!\252\354j\0268\250\035\270\252\222\032\211\372T\rQ50\323OZa\246\np\353OQR\245Y\216\255\305V\224U\273D\014\373{\232\236ks\020\311\024\304\346\245\024\243\000\344\366\244\357\222)\300\r\271S\370R\362W\036\224\270\342\234\006)\303\257\024\275\261E&)qN\002\224qN\036\224\017J\\\343\232z\236)i{R\322S\303S\303S\267\323\225\352E\230\201\2674\236g4\361/\275?\316\343\031\246\027\367\246\371\224\206OzC%&\372<\312B\364\306zaz\215\232\230MF\304\342\242/\306\337Z\202d+\315Un*\t\rWv\342\252\311Q\275B\325\023Tf\232zS\017\335\246\212z\212x\251TsV#\025r.\325i9\253\020\313\345?R*\371\304\366\244\253\022GPj\262\014\034\032\230R\0323\353J8\351N\0370\372S\205<u\247t<\320\0074b\227\024P:\323\250\247R\201\232^\364\240\323\205.(<Q\232P\324n\243q\243y\365\245\337\357@sK\346R\231\016:\322\t=\350/I\346Q\276\215\364\233\351\245\351\205\251\205\251\245\251\205\252&=\3523/ 7 Uiz\344t5Y\215W\222\253=F\346\241cQ\261\250\217ZC\322\230\3351INZ\221jT\253\021\325\270\373U\264\351R\021W\254\334*\037\\S1\363\232\220t\246\023I\236i\340\322\203\203R\002\017\265<u\25101\326\225\224\216{Sz\322\201\212v3I\214\032\\\023@\024\264\016)A\247\016\264\374R\022h\006\220\361M\335K\270\021\357M.G\024o\243}\001\350/K\2734\205\250\337F\352M\324\233\351w\232k7\024\302\364\322\324\233\251\204\323\031\252\0265\013?\0305\014\253\201\270t5Y\352\264\225\003\232\211\215DM0\322\023M4\224\341R\245J\265:u\253Q\325\270\315L:U\233Y\002?#4\367#\314$\n\\\323M4u\245\006\234\r<\032x5*\362)\343 u\243\003\034u\245\030\305;\036\224\233y\243\245/\247\024\243\030>\246\220\212AN\006\237\236)\t\355M\316)\t\'\245\030\342\220\361M\'\212nqHM&\352v~Z\003PM&\3527\323wsK\272\223u\033\351\245\251\245\251\205\251\013Tli\214j\027\351Qn\307\r\322\240\224\000r\275*\263\325F5\023\032\214\232a4\204\361L\315\002\236\2652\032\231}\252d\315Y\216\255GS\212\236\334fQS\3101!\3157<R\023L\315(4\271\247\203R\003R+qR\344\343\232Q\307Jp\03194\340\010\247\001\232B1IG\265!\244\245\006\226\220\235\304SI\346\200\324\241\251I\3150\212c\036j2\334\320\016i\305\276\\Rn\245\315&sHz\323\t\305&\3527\321\276\220\2654\2654\2654\2654\232\215\215F\306\241j\214\221\336\253\310*\213\032\211\25264\302i\244\323riA\247\251\251P\324\312jh\315[\214\325\244\251\207J\232)60\"\2479\221\2629\246\367\305\006\230i3J\032\234\246\245S\315H\016jU\3509\251\027\337\221R\002:b\234y\342\201\220y\245<\323OZC\301\240\363M\"\212L\321\232i84\233\217Bi\273\260i\333\361\322\220\275F\315\236\365\036y\245\317\275.\352L\363N\006\220\232n\352i4\302i7Q\272\223u\033\251\244\323I\244\317\024\323Q\267J\205\252\026\250\211\347\232\317cQ1\250\311\346\230i\244\322f\226\236\246\245S\305H\246\247CV\2435n3\322\247^\224\345\353\212\322\217\313K3\317\314j\256~j\\\346\232\324\302y\2434\360j@{\324\252F=\352U<T\240\364\305J9\247\001\3158\014\321\212LRc\332\220\375)\017Ja\244\367\244\'\212a<SKS7\322n\240\2650\232L\373\321\2327S\263\225\310\024\252\300\036i\031\275*2\324\205\251\245\251\245\251\245\250\337F\354\320Z\232M74f\230\325\023T-Q5e\223Lf\250\311\250\313sI\236h\240u\247\203R)\251T\324\350j\314f\255\306j\312\364\247\023\216j\365\216\331Q\225\215G*\354\224\257\245 #\024\3265\021\353H\r=[\232\225Z\244SS)\033G\255J\246\245S\305H\032\236\r.\356\306\234W\003\353L>\324\323M\246\232a\342\230\324\302i\204\320\341B\202\246\242\315\033\251\t\244\311\2434\231\347\25586)\\\343\2453p\307^i\205\251\245\251\245\251\013SwQ\272\223u.\352ijn\3527PMF\306\242j\205\215d\261\250\330\323\t\246\023I\232)GJx\247\212\225ML\206\254\306j\324f\255\241\342\234\307\212\237O\230\013\260\t\342\264nmZc\230\2278\364\2522#\304p\343\006\242\335\232a4\314\363OSR\203R\251\251T\324\312\325*\232\221M<\032\\\346\235\274\343\004\320H4\323M=)\264\323Q\234\232a\250\332\230M34\204\322\023Fi(\316)\245\251w\361L\'\232ijB}\351\205\251\244\321\272\214\322n\2434\023M-I\273\336\215\336\364\206\243cP\265b\261\250\311\246\023L&\200iA\247\216\264\365\247\216\225 5*\032\260\206\254\306\325j6\251\031\262*%\220\244\241\207j\3504\375b8am\340\037L\326m\355\351\271\270/\3335\\=!zM\324\3655*\232\225MJ\246\245SS)\251T\322\346\234\032\224\032\\\321\232i\246\232i#\031\250\311\250\311\346\230\325\033S\r74\233\251sK\333\212c\034\034SwRn\244\'<S\t\244\3154\232i4\231\367\245\315\031\2434\231\246\223M\315\033\250\317\024\3265\013\032\302f\250\313S\013SKsI\272\234\rH\246\245Zx4\360rjU\251T\324\350\325a$\300\251<\312i9\2401\035\351\300\323\267R\027\240752\265J\246\245SR\251\251U\252U5(jpjvE(4\264g\336\214\373\323[\332\230i\215Q\223L=)\215Q\223Q\223I\232PiwqH\307\217z\214\223\212ijaj3\336\232Z\220\232i4\231\024f\235\232\t\246\223\212i4\322i3\317Z7PNj6\256t\232\214\2650\265&h\006\234\rJ\246\245SR\003OZ\220\032\221MH\255R\253\323\304\224\360\364\241\251\301\250\335F\352p5*\032\225Z\246SR\251\251\024\324\252\325 jxjx94\372\\\361I\232L\2123Mj\210\222)\214}\251\204\367\246\023Q\261\250\233\2453u.x\245\007\232\225\223(\010\252\344\3434\306\343\361\246\023M\311\307\024\335\324n\242\212L\342\214\321\232\t\246\023HM74\233\250\335\357M&\271\247n*\"\324\233\250\335Fi\300\324\252\325*\232\220\032x5 4\360\324\365jxjxnj@\324\273\251\341\251sJ\r8\036jU5\"\232\231Z\246V\251\024\324\200\324\212j@i\312i\341\251\331\244\315&M\031\367\244&\243c\315FMDN\017\024\205\363\326\243c\305F\304\032\211\216(\031#\255(4\361!\306)\034|\271\250\030\361\212\214\2657y\035)\013qH\r;>\364\271\240\363GJi\244\315!\246\223L\'\232i4\233\251\013W0\355\223M\315&E&is\357J\rH\255S+qR\251\3434\3654\360i\340\323\303S\303S\203S\303S\203S\303S\203S\303S\301\342\236\rJ\246\244SS+T\252j@j@\324\375\324\360i\300\323\263\357FM\031\367\244\315!<Tlj&5\0214\322i\244\373\324lj6<sH\030\003\315/l\203@l\034\324\276p1m#\232\254\307\234TLj2\324\007\3004\003\306E8\036\306\215\324\273\251wR\023M\244=)\244\323I\246\032a4\233\253\227-\223M\335I\273\2323N\315(5\"\232\225NML\rH\r858585<585<5<585<5<5H\rH\rH\246\245SR)\251\025\252Ej\2205=[&\244\006\236\032\235\232\\\322\023M-F\352\215\215D\306\242cL&\232M0\232\215\2150\261\315J\214\n\340\367\2460a\317jh~)\214\325\0314\302i\273\251\341\206\336:\322\344\223K\223\334Q\232L\322\356\244\335A<S\r0\232Bx\246\036\264\322k\223-\3054\265(4\271\367\245\006\224\032\221Z\246\214\361\232\231M<\032p4\340iwS\203S\303S\303S\303S\303S\303S\324\344\324\240\323\301\251T\324\201\252@\325\"\232\2205H\032\236\246\244\rN\rO\r\305.\352\013SKSwSKTlj&5\0314\302i\205\251\205\251\231\245\007\217z\223v\341P\236\t\3050\232\214\2654\232i4\240\324\200\340\360i\373\263Hx\246\223M&\215\324n\244&\232M4\236i\244\323Mq\345\2517Q\276\234\032\224\032p5\"\234\232\235MH\032\236\032\234\032\234\032\215\324\241\251\341\251\341\251\341\251\341\252@\325*\236*Ujx5\"\265H\032\244V\247\206\251U\251\341\252@\325 jpjpj]\324n\244-I\272\232M0\232\215\252&\250\311\246\023L-L\3174\240\322\207\303sC\234\214\216\265\t<\3233\216\264\302sHzQ\236)CT\212\324\377\000\274\277\312\232})\204\323I\243u&h\3154\232i\244\315qE\2517S\201\247\006\247\003N\006\245C\212\224\032xj\220585.\3527R\206\247\206\247\206\247\206\247\206\251\025\262qS\003R+T\200\324\201\251\341\252Ej\221Z\244\rR+T\201\251\301\251\341\251\333\251wQ\272\220\265&\357zBi\271\246\232\215\215D\325\033Tf\232\017ZM\324\204\364\247\356\310\305D\307\006\230M7\214SsI\232\001\247\206\251U\251X\367\025\031\351L=i\2714\271\2434\206\233M\256\037u\031\247\203N\006\236\r8\036jPi\341\251\341\251\341\251\301\251wQ\272\234\032\234\032\234\032\236\032\244V\251P\367\251\203T\212\325 jxjxjxj\221Z\245\rR+T\201\251\301\251\341\251\333\250\335N\rAjij3M\315!5\031\311\250\330\324lj3M\355M\3474\016h&\232O\0353Q\036\231\037\2254\234\322f\214\361IK\232z\265<5!\246\232a\244\2434\244\346\220\364\246\032\3417R\203O\006\236\r8\032\220\032p4\360\324\340\325 jP\324\273\250\335N\335J\032\234\032\244V\251U\252el\n\2245<5<5H\032\234\032\236\032\245V\251U\252Ujxjv\352pjpjv\352\003R\356\244\335F\357zL\323I\247\2466\234\323\n\007REVpT\342\230\324\314\361L&\215\324\023\3050\232i=\361M>\242\233\324\322\023\317\024\231\2434\240\323\303S\201\310\305!4\323\322\222\2234\003KM5\300\003\3158\032x4\360x\247\003O\006\234\r;u85<5.\352]\324\006\247\006\247n\245\rR+T\310\334\346\246\rO\rO\017O\017O\rO\rR\006\251\025\252Tj\2245<5<5;u8585.\357z7Q\237z7Q\237z\017Jn\342(\022`\021\353C&\344\316EVpGZa\351Q\232a4n\240\234\323\017\024\334\343\232C\216\242\223\004\216)2i(\315(4\340\324\354\320Ni\246\222\212RsI^z\r<\032p<\323\301\247\203N\315(4\271\247\206\245\335K\272\227u(jpjpjpni\341\252tl\n\225Z\234\032\236\032\236\032\244\rR\006\247\253T\212\325*\265J\255O\rO\rN\rN\rN\rK\272\215\324\273\250\335F\357z]\324\204\323M |Sd;\215B\325\0314\3064\314\321\232\t\342\232h\315&qM4\334\321FisN\006\235\232J);\322\321^v\r?<S\201\247f\234\r8\032pj\001\247\006\245\rK\272\227u(jpjpjxjz79\251\225\252@\324\340\324\360\325\"\265H\255R\006\247\206\251\003T\212\325*\265<5H\032\224585<5.\3527R\356\2434\273\250\335F\352B\324\322i\t\342\243cQ\023L&\230M!4\271\240\372\323I\240r0i\017\024\323\3274\231\244\245\311\245\006\234\r\031\245\315%\024W\235\203\3158\032p4\340i\333\251\300\321\232]\324\273\251wqK\272\215\334\322\206\247\206\245\rO\rR\253T\212\325 jxjz\265<5J\255R+S\303T\212\325 j\225Z\236\032\236\032\234\032\236\032\234\032\234\032\215\324\273\250\315.\3527{\321\272\2234\204\322f\230MF\324\303M4\323I\232\\\322\032L\363J\334\322\000H\3152\203\322\233\223K\232Pi\331\245\006\214\321\232Z\377\331"
-byte_png: "\211PNG\r\n\032\n\000\000\000\rIHDR\000\000\002\000\000\000\002\000\010\000\000\000\000\321\023\213&\000\000\004RIDATx^\355\335as\2420\020\000P\207\376\377\237\\\346\346\254\236\225\023\205\020`\223}\357\333\341\264\232d\223\335\004\352].\000\000\000\320\216az\001\000\000\200\236\331\006\3466N/\000\000\335\222\367\001\000\222\371\232^ \007\225?\000\247\220\200\000\002\361\034\010\000\000\000l\347\320\363\265\321\301\003\220\210%\017\000\200\030T\246\341,}N\337\001S\237\214+\000\360Dq\000\374\257|\'oMiR\225a\253\362K\000\000\n\250C\222\023\000\313\275\332\352\351\277\354^E\005\275\032.\337\323K7Y\343 k\273\001\240y\2228@8s\373M\330\231\252\340\255\361\247\207\364\022\244\324\351\324wS\013 \214N3M\017>d\313\017//u^\000\234\367\316\274R)\240\272\247\237h\301\341\353\253\211\021\307\341\203\017t\301\332\321\240\263\222\357\375}\253\004\315\322\357J\343\243-\343\261)\2266\375p\\q\233u\033i\317s\000\360\317\352* n\232\343\267\325\003\013ley\004\232\021\277Np\346W\333\320\300\250\223\234\020=\234\342\025(`\351\200\246|\230\262\037^\256g\356\215\032-\000\303~l\367\3779F\330)@O\204\331\223|\335\221\257\305\000\311\314m\021y\341\264\316\252|\243\366\247\035\343i\315a\245\020#\245&\204\276\205Xh8^\345\n\003VQ\\\204`\375\007\256\246\213\301\364\337,\244\343\n\335;\356Z\035\350E\310e\260/ %\331.9\001\000m[_\275\254\377\211\\\364\317\341t\371\361\3649\027a\000\3752\273\201\217\234\202\003\320\021Y\r\322\263\014@c<\235\000G\030\343\375m\336 iW\325\352\367\007J\002\225T\350H\023\262-\317\213z\205\000\000:eu\007 \222\277yi\274\214\276J\260D\272N\233\253b\322uDgL\377\243\305\354\360\271\371\r\300\216\336.\276\236 o_\314\234\017k\335\"y\020\321\200\342\004\000\000\000\240\224\023V\000\010\307\215\017:\242\332\004\330\313\212\025Vm\321\243\025\001\360\244\325\257\230a\336\252\031^\0328\264\312\210\003\301]\277\337pU*;Z\350\017\267F\314\206\310S\000\244\0263=\003\320\001{-\332u-\2206TI\242\277I\033F\234\0164\372\345UV\033\200\014\332\314QT#\000\016\320H\'\337?\346\323\307\r_\017\036\334\271\007\277]\004\t\233\014\354/|va_\033\003@j\0028\234\245\227Q\024\000qY\240\222\373\035\000\202\001b27\001\310@\276\003\000\0008\311\306G\321\032c\373\t\000\000\3009\276\246\027\000\000\000\000X\"\327c\r\000\000\000\241y\026\033\000\000\232\340\356\n\000\313L\017{\366\316 {\377~\200r\323\025\021\000\000h\202R\036(e\375\000\000HK)H\337\276\247\027\266\231\2350\263/\000\000\000\307\363\210vr{\006@\370\335\337\236\215o\314\020~\260\000\330\217$\000@\"\366\201\233\364R5\364\322\016\000\250Fr\004\323\000Rq8@6\2451_\372s@\016*hh\211\031\013\220\217\265\377\215\336;\347\247}\275\267\022J9\360\002\310Ju\004\271\335\326\200uK\301\373\322q|\377r\24166t\2734}w\321{\025\330\213\331\017\000\273\220b\tmv\337*ry\" \000 \225\331\"\021\200\336\331\375U\022=\227\032h\000\000\200\276\335\367\245\321\367\247\034C\034\344\346\034\010\000\000(e?\271T\274\236\252\271\027,h]\315\267\007\3261\377\222\023\000T&\244\000\000\000\000\000:Up\037\030\270\270y\002\300Y\276\246\027`1\321\003\000\000\215Jy\220\357\004\366!e\000\000\000\000\000\355q\240\003@\021\247\3407\2113i\342\246\003\300\203\204\010\220\214\335pb\006?9\001\300\203h\310\352:\362\206?/c\017\300E:\000z7\270\357\005\000\000\000\000y8\r\004\200\\<\362\000\000\000\000\035s\350\017\000\000\000\000\000\000\000\000\000\000\000\375\030\374\241\000\000\000\000\260\220c\004\000\000\000\000\000\000\000\010n\364\337\010\002\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\017\177\000UW\201D\216\024\213\243\000\000\000\000IEND\256B`\202"
+byte_jpeg: "\377\330\377\340\000\020JFIF\000\001\002\000\000\001\000\001\000\000\377\333\000C\000\003\002\002\003\002\002\003\003\003\003\004\004\003\004\005\010\005\005\005\005\005\n\007\010\006\010\014\013\r\014\014\013\014\013\r\017\023\020\r\016\022\016\013\014\021\027\021\022\024\024\025\026\025\r\020\030\031\027\025\031\023\025\025\025\377\300\000\013\010\002\000\002\000\001\001\021\000\377\304\000\037\000\000\001\005\001\001\001\001\001\001\000\000\000\000\000\000\000\000\001\002\003\004\005\006\007\010\t\n\013\377\304\000\265\020\000\002\001\003\003\002\004\003\005\005\004\004\000\000\001}\001\002\003\000\004\021\005\022!1A\006\023Qa\007\"q\0242\201\221\241\010#B\261\301\025R\321\360$3br\202\t\n\026\027\030\031\032%&\'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz\203\204\205\206\207\210\211\212\222\223\224\225\226\227\230\231\232\242\243\244\245\246\247\250\251\252\262\263\264\265\266\267\270\271\272\302\303\304\305\306\307\310\311\312\322\323\324\325\326\327\330\331\332\341\342\343\344\345\346\347\350\351\352\361\362\363\364\365\366\367\370\371\372\377\332\000\010\001\001\000\000?\000\372\231pH\251$\267VL\367\254\371\020\207\251\243^*@\224\241)\301i\341i\3060y\244\333\216)\343 \324\2523\332\236\02752!52&*t\025:\n\231jt\251\322\254GV\024\362*u\'\326\244d\312\214\363O\207\003\203\326\255\306\265:\361\322\244\rOV\247\203J\335*\007$\034\325\3339\303\200\032\245t\303c\265S\270\0058\246[!w\305]\272\265T\207wz\311nM7o\265\030\2466\rFP\032\206H\306*\263 \315W\230\n\2432\325\031\226\251J\225NU\305P\234u\2522\216\265RJ\2512\325\013\204\310\254\233\230\205f\316\265Be9\252\356\225]\327\025Rd\311\252\355\025W\226*\254\320\325yb\3005\365#\302A\310\246\311\273\313 \325R\236\265\"-H\006)\301y\247\204\247\004\247\004\247\204\342\235\345\360=jD\216\244X\352P\265\"\255J\242\245QS \251\320T\350*h\315XNjt\346\255F\240\365\250\230\217\264|\275\252\344m\305N\032\236\r=MH\r:\243qL\211\366=i\304\373\2275\024\310$z\273cn\001\030Z\227U\266\333\000#\245`\024\346\232S\232B\225\023\246*\026\250\036\240u\252\262\214\346\252J\005T\225EP\234u\252R\214\325+\204\2522\245T\221qU&\025\2378\353Y\267 \020k*u\252R\255Uu\252\316\231\252\356\225\003%B\352*\264\251UeN+\352\245\\\216j\013\224\035\252\231L\267\035\251\313\035<!\247\210\310\247\005\247\201O\013R*f\236\251R*b\236\026\236\026\236\026\244U\251UjU\025*\324\351\305J\206\246\363\02602jd\273\214\016\033\'\332\201$\3236\001\332\265j\030\266\017z\262\207\212\225[&\236\246\244\006\244SO^\224\244dT\016\2705z\315\267.)\322\002\255W\364\371\3600EZ\325\001{P@\342\271\306\0304\334PFj\'J\201\322\241d\252\362.\001\252r\361T\2465RS\232\247*UI\027\0315Ja\315S\2262{U9b\252s.3Y\267\002\262\256FI\254\351\327\223Te\\\232\201\243\250Z\"OJ\257$\'=*\007\204\325i\"\307Z\255(\364\025NQ_S7\000\021T\346\271\303`\212\022E\177\255O\034y\024\355\200\032\\P\006i\301i\333qOQR(\251TS\200\247\201N\000\372S\327\351O\\\324\212jU\'\322\236\r\014\304\214\003P\000V`\247$\037Z\330\202\330*\203\305[\213\013\305N\246\236\rH\206\244V\251\025\252E5*\323\307\"\221\243\317jD-\021\342\254G)~\325n\334\225a\305i\311\373\333B\007\345XR\301\363\032\204\305\212M\230\246:\324\016\225\004\240\000j\224\325Ja\326\251L:\325G\034\324N\225Vh\352\243\304\rW\226\032\245<X\315f\\\'Z\314\271Lf\262nF\t\254\351\206j\234\213P\224\346\243r\027\245@\334\324\022\020\265Nc\232\2450&\251\312\247\232\372~I\225W\232\310\277\273^y\346\262$\326E\274\231\335Z\266^\"\216e\0370\253/\251)\3440\305:=H\023\311\247\235AA\353SCz\257VVe=\352E\225}EYL0\247\205\364\247\016\r8\034S\201\305H\r8\036i\353O\006\226\242\232S\030\315V\223P\013\"\022y\255\353\013\3012\000j\362\234sS+f\236\246\244^\325*\324\253R\255H\265*T\200f\232\353\305If\001b\r\\\003\r\355V!-\236:\032\202\352=\215\315Sn\264\302=\2526\025\004\240\325II\357T\345\034\325I\005S\231s\332\252\272\325y*\264\225ZG\002\253J\331\252S\221\315f\\\340f\262.\3339\002\262\247^\265FT\252\317\025D\361b\253\274U\014\211\264U\031\333\035*\223\344\324\016\234UiR\275\376\343P\\\036k\235\324u\034\261\031\254+\271\236c\305W\207\3550\266T\234U\370\365\013\240\270$\325\230\265[\205\352\r]\207[c\215\340\212\335\32258f`\030\340\327S\r\234\023\304\n\3103U\256\254\204G+%6\t\314g\226\253\251{\216I\310\2531\314\262sRc=)v\220)\300S\307J\220u\247\257ZxRj+\230\267%r\267fD\277U\'\214\327U\245\313\265\026\267a\2202\373U\204;jE\344\202*U\025*\234T\252\325\"\260\251\024\346\245J\231zS\266\356<S\342\214\306\340\325\266\003\031\025=\271\351\232[\330\214\211\220:VaB\r&\312\215\222\241\2213U%\212\252K\026*\244\211\355T\346Bj\244\221\232\253,g\232\251,dU)T\203U%<U)\332\262\256\2379\254\311\372\232\317\225\t5VD\346\241d\025\014\213\232\253)\3329\252\023\276j\224\213\232\256\311Q\262T\023G\2055\351Rj{\327\255g\312Zg\357S\303k\300\315]\216\331N\006*e\264\\\362*O\262\'\245\037`F\355J\272\177\226r\274\032\277oys\000\332\034\325\250\257\356\035\276c\220j\331\216W\\\251\252r]\317lp\331\300\253\372v\254]\205t6\367\"@9\253H\374T\212\303\275H\270\364\251UE=PzS\366\3243\260U9\256b\375\322k\325\333\311\025\271`\244 \255[y\212\032\323\212@\302\247F\301\247\207\247\207\251\003\324\212\365*5XF\251\003\325\250\007\002\255*qC\234&*8f>h\035\253X\200\361\361Y\2671\340\232\250N)3\232c\001U\245\002\252H\001\342\252\313\025T\222,\325Ic\301\252\262G\223U\345\207\212\313\271\217\031\254\371\226\263nA\346\263\'\031\252\023&*\244\211\232\255$u]\343\305S\234\3435\2371&\252:f\241t\250\035*\"\225Zq\332\273\310\355\252t\204)\351S\250\000\361\322\245F\301\251\325\303\034\324\252\324\360\300T\250sS\000\rY\205A\030\002\256\243\237(\021\333\255$\210\263\247AY\0270\265\253\356\217\212\275\246k$0V<\327Gox\035A\006\255\244\342\254G(5b7\006\247SN \001\311\305dk7\311o\t\333\3115\317i\200\317r]\273\232\353\255\021v\201\232\264\253\363qW\242\007h=*\302\277\025(n\375\251\312j@jEj\231\032\246V\251c%\230\n\324\201\301P1VB\361QM\322\2504\246\031\003\036\225\257gv\262\305\327\365\250.\230\347\232\246NM5\344T\025]\345\3348\252\263\263\020y\252O\220z\323\325\267\'5\013\212\255,y5]\342\025VU\0305\231w\037Z\313\232>\265\235q\027\265f\317\0363Y\362\305\315V\222,UIR\252\312\265\2374d\223T\344\213\031\252\317\035Wt\252\356\265\024\203\002\250\3142y\257G\\\001JH\024\007\243~*D\233\025*\315\232x\226\245\216lU\230\346\315Z\216|\256=*\314w\005;\361NY\260\304Q2\211V\262g\263h\337r\344b\256\330j-\016\003\032\327MPp3\326\254\307\251\020G5~\337S^2j\347\366\264h=\352\215\366\263\301;\253\234\274\276\226\376]\203\356\346\265\364\253s\022\251\"\267\341\223h\025r\031\0015z\031\0161Sg\245H\033\214v\247\253T\212jE5*\232\225^\254[>X\326\214\007\221\353Z*8\246H\271\254\275E\010S\212\216\302VD\310\352:\212\325\202Ar\274\255Cqn\343$)\254\251\313+r(V\310\250e\"\253I\327\212\205\230\217cQ4\307<\324l\371\351P;\021U\245\3475Fu\315g\317\025g\334E\326\263\'\207\255P\232*\2452\340\032\245\"\325Y#\353T\345\217\031\252r\246j\244\251U\244J\207\312\'\265V\271\001F+6f\257B\022`SZJo\231G\233G\233OY\360qS,\246\236\263T\351?\275[\206~*\300\237\276j\330\220\262)\030$\324\211&z\323\312\007\355Q=\222\267A\3156K&\214\256\017\024\201\345C\216\270\240^J\017\000\325\250\347\226U\350sO\373\034\223\0343`U\333-5c#8\255dA\030\030\251\322J\267\000/\203\222+J\33759n\224\365o\316\245S\310\364\251\003\017Z\221_\025*\270\247\347\"\246\265|1\025\253n\334\216kJ7\316*R\271\025V\342\r\302\263\244t\260,\314\001\006\252\331x\201\"\271\307\360\223]L\027q]\305\320g\265g\352\026\013\202Eb\3106\022*\273\266sU\231\260i\010\310\250eL\325I\001\024\302\331\025\013\363U&\030\252r\343\025Je\315g\\\240\025\225p2MP\2252j\254\211Ud\025VD\315T\2260*\244\261f\253<\007=*\t\361\032\221\336\261\256X\263\032\245 \256\360\2751\244\305Df\301\240MK\346R\371\207\255J\227\007\216j_4v\251\222\\\325\204\230\212\267\034\371\\U\230\')V\022\340\263f\255\303>F*\306\354\212\232\000\017,2\0055\243B\307\034\212r\332\257\245Z\206%LqS\204\365\251\025\260jP\364o\301\251\342\271v f\266m.\014i\226O\306\254\t\325\316I\305=[\234\366\365\247y\341OZz\317\273\241\251Q\203\177\025J\261\372Hi\303\314^\214\010\251\255\346e\220n\342\266 \'\000\366\255(_8\253\210\331\024\254\271\254\177\021[\227\2678\025\310Gl\342@Gj\354t\260\311l\205\215i\271\022\306\334\344\342\271\373\305\332\346\263\231\2715\004\206\204l\212I\001\252\262\202\001\252\325\024\225^S\221\315R\230zU)Ef\335\034\326d\303\223Te^j\244\250M@\321T/\035U\232!\326\252H\200\032\2553\200\010\351Y\027\222\216k\016\342|H}*\273\334\212\356\032LTo\'\025\003\2774\202Zp\232\236%\3159e\251U\3623R\307.*\322I\232\261\024\2705r93Va\233c\003W\026e\316A\253).EOms\261\260G\025a\212\023\225\357V\255\300e\311=)e\230o\0304\341/\024\242Jp\226\22495,Rm`k^-C|!6\364\357J\214I\253)1\003\024\242NsR\211F*D\220\n\2369\317\324T\353.{\323\267\037Z\325\323\356r\200\023[6\362\202\243\035j\334rU\210\344\317j\315\327\357\021b\331\237\230\326\034\021\0162:\232\350aEKu\307Jql\036\017\025\231\251/$\372\326;u\250e\244\210\323\232\252\334\036*\251\316*\031\017\025VSU]\275j\264\240U\013\210\301\315fM\027Z\245$y5\013\305\301\252\222\246\321\234U)\037\002\251O>3\223YWw\312\200\363Y\027\032\211$\326m\315\333>k2RI\317z\257!\342\273\247\222\242ij&zg\231\315/\231\232\004\270\357R\244\325*\310EL\222T\361\315Vb\237\346\031\351W\"\230g\255[\215\367U\210\344\037\215[\212n*e\223\234\325\270\347\336\240\001\310\251\242\230\203\214\324\244\242\200I;\273\212\224\272\030\301\007\232`\220\324\212\365\"\275M\033\002k^\310\307\345\234\3435n\031c\000\202\271&\202\300\032L\212r\265J\231\251\220\324\312\3652>{\325\233Y\314O\317J\333\267\270\000\002*\332_(9\355P]\370\212+e!I\316;v\254H\256\336\376r\354I\031\255A\036#\342\264l\234\311\026\323\332\225\233kUK\342L|\372\326C\236j\0319\250\324\3554\342\340\325Y\333&\242b\000\252\362UIN*\234\215U\235\252\264\244\032\245.\016j\214\200\003U\246p\265\237s6A\305d]\334\254c\2575\207wt\362\023\216\005fL\t\316MR\225j\254\213U$Z\253(\256\305\344\250\032J\215\244\250\314\224y\264\242L\324\213&\017Z\235$\315J$\305O\034\243o=jhf\0079\353V!\233\025~9\370\025n\t\212\020\325h\334\253\220Tc\326\247Y\362\243\326\255[\314\244\034\236{T\253.\016jR\315/\315\216\005=%\355R\t)\353&jTj\235\036\255C9\365\255\010\2448\025:6\342\017z\260\312\270\004\032EZ\231\027\320\324\240f\236\252sS\000@\253\020H6\220\303\351S\307r\351\307jy\270}\244\306\330>\225\225*\\O!\3349\242\312\351\354\346\331 \305tv\267\036b\014U\373|\253\026\316\0059\233\234\324\027/\230[5\220FMF\365\013\014\232C\220*\264\246\252\311&;\324\r78\252\362\266A\254\351\347\362\332\240k\205#\256*\254\267)\234n\252\223N\243\275g\334\\\252\202sYw\027\244\347\002\263.\257\035\272qY\223\345\216I\252R\255S\224U9ET\224UI@\252\222w\256\225\344\250\231\352&z\214\275 \2234\360\324\341&*x\345\251\325\351\352\365*I\203V\242\222\255G6*\344S\356\000f\256B\370\\\325\250gU\310a\220jH\345\347\212\266\222\344u\251\226\345\220`\036*xJ4e\231\271\246\211I\316:T\221\313VR\\\325\210\3335j\0222+R\334,\213\367\272S\301\332z\324\250j\304l*UlT\310\331\251T\363R\016jX\305O\035I\345\3569\034\032\226960\016\240\217Z\232\343L\206\3517\250\347\326\231e\021\267m\215\310\355Za\370\246\264\225J\346s#\354^\224\337\'\013U\244Nj\273\214S\0108\315A6\002\325\tj\254\240\346\253J\3142\001\254\351\316I,j\214\304\036\225M\300-\311\305U\271G\035zVt\265Jc\326\250\313T\245\252\222\n\247(\353T\346\252r\3259Fj\234\243\255n<\225\033=D\317Q3\320\036\244W\247\206\315J\215S\243\323\367\323\325\352\304r\325\210\345\311\253\260K\322\257,\204\016jx\244\004\216j\362\310\230\301\341\251\311.\rXY2)w\221\322\255Y\314\027vOn\224\370Hy\016N\005Y\211\343\'\222EXB\024\360\331\253Q=]\212R\243\212\265\023\027\253+\307\0254|\036MK\270S\325\375*d\226\245Y3S#\324\351%Y\211\263R\271\000S\254\356@}\205\2705dF7\023\236i\333\361L\221\232N\027\2551!\021\236z\322\273\000*\234\257\270\234T\0143\326\241g\306j\264\215\234\325I\016\357j\255.0sT\245\352j\215\302\206\315g\3141\232\245.j\234\254\335\315S~\271\"\251\\\235\314H\030\252\254\341\007\335\004\326|\334\346\251\313U&~1\212\2431\315R\227\212\251-T\226\265\035\352&z\215\244\250\231\351Q\352R\303\024$\234\325\230\336\245\337@\222\244Y\252T\222\255G%[\212Lt\253\2518*2MX\212O\230sV\274\342\262g9\251\314\312H+S\307&EN\257\232z\271V\253\t&\346\311\251T\355\374j\324Rt\253q\313\212\271\013\344\212\323G\215P`\363O\022{\323\204\204\3645\"\271\251RLT\213/\275J\262\324\3136*d\232\255E6*G\224\225\250#\220\211\205i\233\262\201M8\334\007\031\025,3\205\214\236\346\230&\334j9\030\265D\313\201P;b\252\310I\351P\230\\\232\206H\231N1T\256\t\034b\250JMT\224\365\315Q\233\275R\224\n\251\"\202MT\237\330U\031\201\035\252\214\335\rP\224\325)MR\224\3259j\234\246\252IU%\025q\336\242g\250\331\3526z\026J\177\233NY9\251\226lT\2135H$\31585M\034\230\2531\311V\242\232\256\254\301\224q\315Z\202U\340\021S\203\207\3009\025k\005@\315Y\205\370\251\343m\307\0250\310\352:w\251Q\352e\220\021\327\232\263\033\0223\212\235$\301\346\256C7\025m%\310\353S\244\335\263V\021\252A.:\323\204\331\247\254\265\"\315R\254\376\365b9\275\352\3343U\241 +PLJ\235\302\255\332L\262DCU\210Sz\225\007\334Sa\230\020U\217\"\246h\231yS\370SC1\034\361L~\235MW\221\273Ug\'\261\250\262\300\023\232\212Gu\'5Zi\0279\333\317z\2430\004\203\217\255UtB\t=j\204\240\006\'\025Bq\223\322\252\310p\016*\254\213\222I\252w#x\254\331\341 \032\316\23623T&\025NQT\345\025NA\232\253 \305S\226\245w\250\231\2522\364\302\324n\243}9d\247\211\251\353-L\262\324\321\311\232\237uM\033\325\250\233\245\\\216L\0001V\341l\200GQW \234\003\226\0258\224\2663\322\247\215\361\364\251\326Nj\334w\004\014\036\207\255L\241H\340\323\243\033\233\031\253\221K\345\014\003\305M\346\207#\212\260\222\360;\n\2369qV\026PH5e.=\352Q(\"\234\036\234\263b\236&\251\021\363\315Y\215\352\334Rb\255\244\334T\241\203\212\217\3156\307\246EZ\206\361xd\340\367\024\263\243&%\037t\325\270&,\200f\236\315\236j\264\262\363\305W\221\262*\002\304\032\214\236i_\014\265FU\034\325Y\022\252J\235j\224\311TfJ\251\"\325IES\232\250\316+:q\326\263\347\031\252\023-R\224UI\026\252J*\244\213Q3\324L\364\302\324\335\364\273\3054\265&\372<\337zzI\315N\262U\210\244\305XY3SE%[\212Z\267\024\270\2531K\203Wc\313\000s\326\256,\245Wa\352*h\217J\264\252\031\200\006\245\306\321\234\346\245\211\217\004\236*x\345\332\340\212\263\347\007|\221\217\245Jd\371\370\301\251VB\016\t\251\326SV\240p\303\223S+\014\324\213%J$\247\211)C\234\324\311!\365\253\021I\357Vc\224U\250\245\317z\263\034\2705c\211\206)\237f*\337#\021R\334]2Z\371g\222N*[V*\200U\255\377\000-BFr{T,\352\006\000\311\244X\374\300O\245V\225\n\223\221P\0316\347\322\241\220\214\325ix\252\262\032\2470\2523\n\247-R\232\252H*\234\312*\205\302pk:e\254\371\224\325)P\372UIET\221j\244\253T\031\2526jc>*2\364\236e\005\363H\317H\032\236\257\212\261\033\325\204z\231$\315N\257V#\223\025n)j\334Rf\256\3036\334U\250\231\244n*\334nGZ\260\217\315L\037\035\rZ\206Pc+\306jH\201\'\003\232\225_i\367\251\026L\232\221\037\232\260\222T\351/\275O\034\276\365:\311R\253\324\201\351\336g\275H\222T\251\'<U\270\344\000\n\261\034\2705e$\253\226\263\001\234\366\253)&r}j\264\362\356\231W\323\232\271\014\243\212\260\347\021\344SL\233\243\300\340\324\0016\361N\307<qFC\374\255To-\214\'#\356\325\026\346\240\224\343\212\253!\353U&|U\031\233\255R\225\272\3259MU|\325Yy\252\223&j\214\261\363Tn#\252\023F@5FU\252r\255T\230\016\325\222\306\243c\212\211\232\243&\232Z\234\017\024\322\324\003N\007\025*=N\222U\204z\260\215\322\247G\2531\2661W\"\223\212\271\013\346\256\303!\r\220pj\322\310Y9<\212\261\033\340T\361\2608\253\031\nx9\251\341r\255\221\332\247w\336\331\003\002\237\030\001\271<S\263\206\300\251U\361SG%N\222T\351-N\217R\t)C\363R+\363S$\231<\324\361\311Vc\222\255\305&j\312>\034\014\365\255\010\216\341U/\321\300\334\203-T\241\324\245\211\260\350x\255Hu\210\335v\223\203SA9\222C\351S\203\223K\322\214\007\344u\245p\'\215\220\365\305dM\001\215\210\"\252J\242\251\312\235j\224\361\325)\023\255T\226<f\251J\207=*\264\234pj\234\303\234\324\017\202*\234\342\250MT\'\3475BE\353U&J\245\"\326\031|\324L\325\0135F[\2327R\203\2321KJ\264\3655*5XF\251\321\252\304mVcl\325\270\232\256\301!^\225~\325\306\356j\334g$\216*hH\'\223O\363\n\232\273\004a\342\335\273\237Jt2\025lv\253\3628\362\324\212[b\035\260M9\324\2419\241e\251T\232\2367\305L\222\325\204\227\212\225d\315H$\030\247\253\342\245F\251\321\352h\336\256E.*\324o\270\212\323\205\372U\201\030n\264\206\301X\347\024\035\035Ko\300\253\260E\030\217\005p}qU\356\225\240\372\032\250oppF*H\256\201 \203V\031\371\016\275\372\3247 H2+:h\272\3259\"\025Jhx\353U$\204\3259\241>\225Q\324\001\312\325Y\343V\352+>x\366\237j\251\"\342\253\311\036ER\232\016\rQ\232\021Tf\210`\326|\313\324Vm\310+\234W0e\2464\325\023I\232az\004\224\365\222\244\r\305\033\251\300\323\307Z\225OJ\225\033\0252\267J\2327\346\255F\370\253q6M]\205\270\253qI\212\267\024\225a\036\246V\334j\300c\027\000\325\210\016\356\247\0257\232zv\251T2\250n\3254r\344\363\337\326\235.\325#i\317\255=\035\202\343\034T\312\330\003\006\236\257S\244\274T\253/\275H\262T\311%L\255S#\324\310\370\2531\311W!\223\246+J\332]\303\336\264aj\271\035XQ\221R*\372\200id\205%\306\345\2527\032bHI\013Xw\226S[\313\230\363\266\237k|G\311 \305Z.1U\245\301\252r\255S\225j\273\261Q\212\245>I5Fl\325)GZ\2512n\006\251H\274T\005j\264\313\326\263\347\025\2378\316k>e\353U~\317\275\362G\025\307\315\002#\360\300\255DmC\236\033\255$Ve\333\031\250\356\240X\007\'\232\247\031\334\330\355Vg\204\302\252s\234\323\025\363\305)$S\321\363V\025\270\247\255J\016)\352\330\251\3435f#V\243j\273\013\325\270\332\255Fr29\2531\275L\255\212\231\0375n\026\007\251\2531H\241J\221\370\325\230\244P\304\021\362\323I\001\316\321\305?\177 \342\246\363\314\230\036\224\241\261R\243\346\244\251\024\021R#\342\245Y9\253\t/\0252J\rL\262T\351-[\202n\225\245\013\364#\250\255[Y\203\014\326\204MVQ\252u4\372\017\"\253O\010|\344\n\310\324t\361\260\224\034\212\315\267\235\224\354n\242\246v\007\332\240\222\252J\265ZE\252\223-Q\225G5NE\033H\357T\344\371MS\224rj\244\237)\252\263\265g\317\315Q\230\023\232\246\361d\323^-\203\025\346\315\273\326\232]\307zb\\I\03185\014\316\322\234\223K\003\010\216H\315M%\321\224\000\335\251\321\204\336\247\267z\320\232\3229\"\334\244\n\254\260.8<\320P\245H\2075&sJ\246\247F\2531\032\267\031\253P\267J\266\231a\305h\3332\252\373\323\367\374\325*>jdl\032\263\033\325\244n*Q&\0109\253?j\005@\300\315;\206\213\266sC!\213\036\364\253-L\222S\304\325 \233\212\221$\315J\255\232\221^\247G\300\251\243\2235a\036\255C&1Z6\262\347\0035\247k!W\002\265\242|b\255\306\365:\275J\255K\273\"\230\306\252\334\200T\3277r\241/\016\rJFV\241\220b\253\311U\334UI\226\251\312\265Y\220\036*\215\324;I\252\023.*\224\343\212\243/5NQ\315Vh\362j\023\030\\\232\245p\374\361^vTS\031\001\250Y\0054\307L)I\262\234\001\025\"\311&0\t\305I\031d5wp\226\023\223\363\n\2058\351R\201\214f\227\245H\225f#W\"\253)\306*\312g\327\212\267\t\333\200\017^j\316\374\232\221[?Z\231\t\2531\034\325\250\332\246\0074\36552\311\201\212x\224\205\366\247\253\253\'\275H6l\352wT\221\252\340d\342\234T\037\272sM\022m5<sT\352\365\"=X\215\361V#l\325\250\233\245]\267\223\004V\255\274\273\200\307QZ\320M\2203\326\256\306\365:=L\257N\335H\315\305U\270o\224\327>\307\315\271f\307J\220\360*\'5ZA\236\365]\327\035\352\t\000\305U\224\n\250\303kT7\021\356\004\326]\314x&\263\246J\241q\204\006\250<\301MF\322+sT\247\227$\212\243p@\006\274\371\333\006\230^\230\306\233\272\220\363@\024\240T\2109\251Jw\024\203 \342\244\214\363Su\031\244\3075\"\325\210\215[\214\325\270\006\343\223V\023\203\212\265\t\251\203qS#T\350qS\306\325b7\251\303\361OW\251\003\323\303\346\236\246\236\246\245\317#\006\236\222\030\363RG\266@r~n\324\204\030\3175*KS#\363V\221\352\324MV\242j\265\023\343\025\247i.\334\032\326\266z\275\034\225a\036\246W\247\357\244/U.\237\367m\217J\310\215pI\365\245sP\271\250\034\325i\rV\220\325i9\250\037\236\264\303\2021Tnc\0075\231<X\315cj\000\252\265`I7\316Fj)n\031A\250b,\371&\243\270\351\\\004\213\221P2\225\246\023L\245\006\236\275)\300\017\306\236\274T\252r)\271\346\246U\302\203\353R/\"\227\030\247-M\021\305[\213\223\317J\273\013\356\\\n\260\212jx\316*d5:\216*x\300\035jP\300t\251\221\252\302?\024\365p)\341\3015&\010\344r)U\310\352*U\2234\365njM\371\245V\346\254n\310\303\036E m\255\212\235H\030\346\254#\022\271\2530\313\315[\211\352\324rb\265,\\0\301\255h\037\245\\\215\352\312=L\262`R\371\236\364\031*\245\334\231\033}j\231\300\250\235\252\0275\013\232\256\375j\274\234Uw\025\003\016\265\013\034\034Ui\210\254\351\310\346\261u0\0326\305s\313m\373\302MV\276Q\270\001N\2010\225\005\310\3005\347\356*&\250\034`\373S(\247\251\247\365\036\364\3408\251W\201M\251P\346\246\216\244\305.0)\361\325\270\252\355\261\344\232\267\234\032r\234\324\350\325:5L\255\221R)\251\220\324\312\335\251\375\017\275/Zz\226^\364\361+q\232\224>i\341\217\255H\246\236\036\245\363I\"\237\2701\342\245\215\361\326\254\302\344\236*x\216X\325\270\237\003\255Z\215\372V\205\234\233XV\314\017\300\253\261\275XI*u$\214\346\220\311\212c\315\264UW\220\261$\324,\365\0335B\355Q1\250\234\346\253\270\250XT/\322\252M\305g\\\315\266\263\246\270\0075\215w)\221\360:U9p\242\263f]\317R*\341*\215\321\353\\\023\324-Q79\250\310\30578\247)\251T\323\324\324\240dS@\353R\'^*U\033ML\274\323\251\312*\304F\257[\234-N\247\346\251T\363S)\332qS#T\213%L\215S#T\212\330\305K\273=\351\312\325 zPrjU4\360\325\"5H\030b\234\247\232\225\rJ\257\201R\307&\rY\204\356j\270\207\035\352\304oZ\226\030#$\363Zp\312*\374o\322\247G\253)&\0174\307\230e\275\005V2\356l\366\246;\344\324e\251\214j\'5\031\2465B\302\242e\315A\"\342\250\334\266\320k\234\325.\260N+8O\275}\352)1\216j\204\303$\342\253\264}\351\033\204\254\273\263\326\270g\025\013\324-\336\230i\204P:T\212jU50a\212h\357NC\202*\311!\224c\255=*N\264\365\025*u\253\221\034U\205l\324\253\351R\203R#T\203\202\rO\346g\034T\252\331\251\003\324\212\325 4\340\324\365j\231M;uI\031\2512i\312\325*\276*P\365\"\037z\261\014\273M[\212Q\202;\325\310\001fPkJ!\264\3435r\031\366\016kJ\0312\242\254\304\331\"\244\222o\233\332\241.I#\265&\354\nL\346\2023M+Q\272\212\210\361\232i\\\323\031=j\026\342\252\\8QX\227\367\203\220\rs\327\222\006\'\232\246\247\322\221\224\236\265\013\307U\346\342\2539\371k.\364\365\256)\352\273\212\201\272\323\033\245 9\242\234\006\005J\206\235\234\232Z\220\037\224U\233Q\271\3005+&\323N^jd\024\365\341\252\324g\212\261\035N\2309\247/<T\213\326\246Z\220\014w\247\253T\200\324\250x\251T\346\236\0059z\324\310jJr\361O\335N\r\212x~)\342J\225\037\232\231\037\232\265\013\326\205\264\274\203Z\266\357\310=j\342\252\273\202*\374mV#z\220\374\324\234\342\224-;g\265\033)\254\224\306\216\230\312\005F\307\025\004\217U\'\235P\022Ms\372\246\252\252\010\006\271\233\275Kq85M]\2465a#\332(~*\t\017\025FsU\245<Vm\347C\\[\325w\357U\333\2550\365\246\3644\271\346\234\246\245^i\304b\225j@*{f\331 5\243*\253\240aQ(\305L\213O\013\315L\234U\210\215N\264\341\326\245^jd\346\245\333\305(\030\251\026\244S\201R\247Z\224p)CsR\243T\252sJ\0174\354\322\346\224587=jUz\2327\253q5^\205\361\212\322\263\230\003\311\342\264 \227\201\357W\342\223 U\205z\235^\244\014\rH\270\247n\244\3155\252\0265\013\270\025R\342\355b\034\232\305\277\326\326%85\315_x\201\237 \036\265\2135\304\267\014z\323V\330\236MY\212-\225)\351U\344z\253$\225Rf\315V\224\361Y\367]\rq\222\n\201\307\006\253\267z\215\272\323OJN\334\323\227\265O\035+\002\016i\310sR\255K\022\345\205h6v\252\372S\236=\244S\323\245H\253R(\251\243\004b\254\240\310\025!\030\0304\345\342\246N\2652\232RsJ\016*El\325\210\310\247\356\3158\nx\310\251\021\261R\016E7&\234\r;4\240\363O\017R\306\365r\'\253\221=\\\215\272`\326\202\310J\250\3175\251o\'\3129\253h\365:\034\324\351\315N\242\237\216*7;j\273\316\001\353T\247\277U\'\232\315\273\325\025A;\305`j\032\300 \341\262k\237\271\270\232\351\310\031\3052=<\347-\311\253\th\027\265)\210\001La\212\212F\300\252r\275U\222J\253+T%\262*\235\330\371Mq\256*\274\274T\rQ0\2465\003\221N\007\024\360\373zu\247\253\340`\323\366\225>\306\245Njx\2705\242\222\'\222G\361R\026\336x9\025*-L\243\212z\214\324\3109\031\253Q.H\307J{\214\032N\342\245Z\221\rI\212nqNV\301\251\222J\231_525?4\240\340S\203R\371\204S\271 \0322E(jpn\225,g\232\265\033\325\330_5n7\306*\374Sr\010<\212\273i9\r\203\320\326\244o\234U\250\232\255Fj\302\323&\235b\004\261\002\271\375S\304\261[\202\003sXrx\215\346\'i\340\325+\213\331\337\221\236j\224\236|\247\223M[\022\334\2675:Z\252\366\247\030\200\250\330\001PHj\254\217\212\253$\234U)_\255T\221\362j\031_\212\200\276\005U\272\223\3445\311\270\252\322\212\201\206\005D\324\312N\324\231\301\247\003\315H\016jd=3SG\2001\216j\312E\3019\351R.kJ\306\334K\033\036\343\2326\341\216:\n\231W5\"\245L\253Vb\\\014\320\334\223H9\251\000\251\027\237\255H\017\024\326\244\247\241\315L\244\324\310\365(4\355\324\340iI\315<9\333\212\027\255;p\034\032P\0163\232\2222j\304M\315_\207\245N\215\315^\216E\\\023\351V-\356\202\262\346\266 \230\0209\253\361?\275L.\025z\260\030\250\345\327!\201NXf\271\215k\304F\340\225\210\326\020\201\356d\335!5z\033EP8\253!@\\\020)\255\002\230\362:\324\005v\365\2463\342\242w\250\035\352\254\262U)d\252\223I\212\2454\265X\311QH\374Uw\223\002\250\334\313\305`?z\256\353\232\201\305B\324\306\024\316\224\224\3454\364\251\322\254F3V\243\\\212\230%]\261vS\264\016\274T\306&W;\252d_J\224-M\032\016\265)4\230\244\356=)\353\327\212\225pM8\361\322\233F3NPjT85*\324\212\324\374\322\206\305<6i\300\361N\006\227\357P3\232\221X\325\250G\031\025r\'\342\246\rR\302\344\270\025b\3567\2013\236z\322\332\353\333\027k\360\302\256/\211Q\027\2575J\363]\226\343%3\212\243\346\3179\371\230\342\246\212\000*\314`-N\216(g\250\374\322\265\024\223\363\234T\022H\034q\326\252\313!RA\250\032j\257$\231\252\262\275R\231\252\234\255\326\252\310\370\250|\314\325y\244\2527\017Y2t\250^\253\270\250XS\010\3155\270\3152\224u\251\022\247\216\255E\332\256D*\300^\005_\323P\0311\214\236\325,\333\274\302\017Z\2225\342\245\013\212\225\026\234W\006\224\014\320\253\234\201H\277.jD84\357\247Zv\314\214\367\242\201\326\244SR\255-9Z\234\r<q\315(j\230a\226\200\304g\232\025\252E9\251\220\342\255+\201\203S\254\231\024\364\227k\002+E\356\005\315\262\203\367\226\263\246\265W9\357I\025\242\257QV\025\002\256\007Jp\001i\336f(\363}\351|\374Q\347\373\323Z^3P\274\225]\345\332r\rB\367\033\211&\240vS\337\232\256\317\326\253\310\331\342\252H}j\244\307\025JF\346\241f\252\362\265Q\270z\241 \252\357P=D\335i\207\212a\346\231\336\224T\211\326\247J\265\017QW\241\031\253h\271\025{Oc\024\300\201\223S\335\260y\262\0063N\210qR\205\366\251:\n\010\343\236\264*\226\316)H\302\217Z]\270L\323\226\244\2152y\247c\024\335\264m\247\016\265\"\232x9\245\247\n~{R\200)\303\"\202\304\232p\351\357OW\305L\217S+f\244G\307z\225$\315_\265q\345\2675\027\237\363S\374\340\302\236\217\236\rF\362\200H\3153\315\246\031\275\351<\352<\377\000z\014\371\357Q4\271\250\232L\324\016\325\013\265G\346\343\257J\206S\223\307J\2530+T\246l\325I\rWf\3075ZG\340\325\t\233\223PI\322\253I\326\240z\215\251\215Q\236j0y\247\016\265*\n\232:\267\017QW\340\025v5\253\326\t\272Lw\253W\026M\010,\325\034U8\247\0003\317J\t\311$\216)\312\274eO\341FI\\zS\261\306;S\225qO\035x\245\355@\030\240\214\322\205\247\001\371\323\207\006\236\r\003\216)s\216i\350x\346\237K\212ZNjElT\213%?\314\3059%\251\343\271+\306z\323L\334\365\251\026\343\025 \271\343\255F\322\373\323|\352i\232\232f\2442\321\347R\031i\215%D\322T,\365\0335B\354qQ\031x*{\325[\210\312\363\332\251=V\225\252\244\255\305R\230\344\3242\032\256\374\324\017Q1\3050\324g\200j1\326\244QR\250\251\243\025n\021\315hA\305^\214g\025j\326o!\301\255\\\375\256\331\212\271$v5F0C`\325\221\322\220\321\236)G\035)\353\363S\327\221O\024\374c\024c&\227m\033ih\035i\324\264\341\315(\031\245\3058\032r\323\300\315!\342\214\322\206\305\006JP\346\227\315\243\316\367\245\022\237Zw\236}iL\347\006\232&\240\313M\363h\363\2502Q\346\373\323\032J\211\244\246\031)\215&j6z\201\317z\215\247\316\003r\005R\270\353\221\322\251\310s\232\2515S\224\3242\032\201\215B\346\240c\3154\323\037\2454\014S\320T\2503SG\326\255\303W\341\355W\242\251\212\344V\226\226\342<\347\322\230\334\310}\352Q\322\230\306\223u9M<\034\032\225H<\216\r<u\251@\004u\2472\025\301\355M\'4\240b\235\2674\205piq\232P)h\034R\203O^\264\374\001A$P\016i\033\212n\352\003\202=\351\246B8\240I\232O2\224I\232\014\224\276fE!z\004\231\240\275&\374R\031h\363\r#I\201Q\031sL/HZ\243f\250\335\352\273\232\201\344\300 \212\255:aC\016\225NCT\346\025ZF\250\030\324,j\"ri\244\323\030\346\222\236\017J\2321S\240\2531U\350j\364&\254\003\305\\\260\221c\220\0223SNA\220\2200)\001\355H\335)\235\351A\247\206\315H\246\244\007\0252r:\324\200\2201\332\215\243\035y\247\000\r;o\245!J1\266\234@8\305*\343\222\177\ni\024\202\236\247\025 n)\031\251\205\266\322\026\315\000z\322\021\212c\036)\233\261H[\212M\370\247\006\310\241^\224\232n\374R\031)\276g4\273\363Az\014\224\306j\215\232\230_\336\232\315\357Q\273{\324L\325^Nj\r\370\340\364\252\367\010\001\310\351T\244\346\250\310j\0075\013\032\214\265!j\2174\003\315H\265b3\203S\250\364\253\021qW!\253\321\032\260\246\254\332\214\310>\265nu\333!\3150\036)\031\252=\324\252i\301\251\352\325*\266jdl\016\2658c\217\2558q\365\247(\334\300\366\247\200E8\r\324\2141IA\244n\224\332Pi\331\244f\335\212a<\236i\003\323\203\346\224\234\212c\n\215\233\265D\315\3174\003\232v\377\000\227\024\320\330\247n\244\316i\244\363Q\226\301\244\337\212<\312<\312B\364\322\365\031jizc5D\315Q9\252\362T\004\214\340\364\252\322\257>\325\232\346\253\2675\023\232\214\232ajniA\315=MO\031\305XF\253\02175z\023Wc\253\013\322\254\301/\226\300\325\251\030\314\300\203\222i\235\016(j\214\214Rn\301\245\rOV\251P\344\342\246S\232\235\016GZ\225O\345S.:S\310\310\307zA\22584\247\232a\0304\036\r\007\232a\024t\244\335Fi\214\3304\335\347\241\246\356\301\247\371\270\306\r!\226\241w\317z\210\266M86\005.\374\322\026\245V\241\2157}1\232\243-\212M\364\233\3517\320^\232MFZ\220\2651\252\'\252\356j\0075\0037cY.\331\250]\252\0269\250\330\323\t\244\315-=Njt52\032\263\021\346\257@\325v&\351V\220\323\327\255lZ*Ej\314H\334j\231o\234\322\223\232c\324D\363J\032\236\246\246S\336\246\214\344\023\336\247C\305N\2540\000\251\227\232~1\212x\033\250+\315&)\270\315#q\332\232zS\033\245%4\266\001\250\313qLg\356i\206N\264\233\351\013\323\013Sw{\322\356\244\337O\334\n\214u\241\034\003\315+\270\317\025\013=4\276i\205\351\205\351\013\322y\224n\315\005\361L-L\335K\272\243nj\t*\273\365\250\034qX\254\325\033\265D\315Q3\344\323\t\311\242\224u\247\251\305J\255S\241\25315\\\205\252\374-W#\351O\'\025\243\245\342}\310\307\2652\346?&]\264\325#\024\3275\013\036i\241\251\352\334\324\350\36525XB6\373\324\312jx\315J\255R\003\351N\r\353JW\0034\303\355L\'4\332a\357L\'\025\033\032\215\215D\3074\256\212\250\010<\324\005\250\335Mf\246\356\245\335I\273\232r\276)\322\034c\025\037\231\220y\250\331\351\205\351\245\351\245\351\233\350\337F\3727\323K\323w\221I\276\202\325\033\232\257%@\346\260]\252&j\211\232\243-\212n\354\232\\\346\225M=MJ\2652\034U\230\315[\211\252\364-\322\257D\334T\216x\251\264\253\200\227j\t\342\266u\013371\256OS\212\314\2267\203\357\014\032\204\311\232\215\232\231\273\232z5N\206\246F\251\321\252\302=N\215R\251\251\001\305;9\247\211N\334\032BA\036\364\306\024\303\322\233\322\230\3075\023s\232\215\252\'\342\242-M\3154\236i\t\244\335HM\031\246\226\245\363x\305F\315\3150\265!<u\250\313S\013Ro\245-I\276\215\331\244-M-I\276\215\364\3265\033\232\256\365\316;T,\325\0335F\306\200iA\247\255J\242\244Z\225jx\315Z\211\252\344/\322\257E%L\317\305WYLr\206\025\326h~ \212%>h\007\353Y\232\316\254\267\327\004\250\001{Vx\222\221\244\246\357\251\021\252tj\231\032\247F\251\321\252tj\235\032\235\272\234\036\234\032\227u\005\262)\255L&\230\304\020y\346\242\'\025\023\036j65\023\032\211\251\233\250\337F\354\322\365\034S\\\221Q\226\244\337H[<Td\323wSX\324e\250\335\357K\272\215\324\026\244\3151\2157q\244\337J[5\033\032\201\315s\014\365\023=F^\230_\232M\364\360\325\"\032\235\rH\r=O\"\247CV#5f7\305Z\212\\w\251\304\331\024\306l\322\007+\320\323\325\263O\3631Hd\244W\346\247F\253\010\32525N\215S\243T\350\3252\275<5;4\3458\245\245\335\212L\347\2755\215F\306\242z\215\215F\335*\'\250\230\340\324D\323sJ\032\237\277\212c\220G\275F\314H=*2\324\306z\013\023\317jil\323KqL&\223\"\200\324\355\324\023M\'\024\302\324\322\324\335\324\273\251\013f\242z\344\231\252\026~i\205\263L&\200i\352\3252\032\235\032\245V\251\020\346\246V\305J\255S#\324\313-J\262\324\202L\322\207\247\207\243}\033\363J\255S\306\325:5XF\251\321\252ej\235\036\245W\251\025\351\352\3315 9\245\315!8\243\"\214\212\215\252\026$S\030\212\210\237z\215\216j\'=j\026\357\353Q\357\2406)CT\315\0311\202*\2618&\242s\216{\032c7\024\335\307\007\024\315\374\321\272\202sII\232\001\243u\005\263Q\223M&\233\272\220\265\033\351\254\325\306\311&\005@^\232^\215\324n\247\253T\310\325:5J\032\245V\305J\255R+T\212\325(zz\275J\262S\204\224\360\364\355\324\340i\340\363R\241\251\221\252\304mS\243T\350\325*\265J\215R\253S\325\252P\364\273\275\350&\223&\214\323I\250\234\346\241v\250Y\260i\246L\365\250\334\344T.sP\261\305\"\344\322\206\301\251R\340\201\216\324\331\007\313\232\256\355\306*\026zg\231\264\361M.\010\367\244\rO\316)CPy\244\305#qI\232kS\t\250\330\323KSw\322\027\256.W\317\024\315\324\322E\033\250\335\357NV\251\221\252tz\231\033\214\324\252j@\325\"\265H\255R\007\247\253\324\212\364\360\324\360\364\365j\221Z\236\247\212\225Z\246SS#T\361\265N\215S+T\212\325(zz\265H\032\236\0334n\244\317\275&\352\013T.\325\0035B\315L-Lf\250\235\263P\263qH\257\264\363\322\224\237\342\006\200\365an\025\242*G5NF\346\253\273Tl\324\202Lf\200\334dS\203z\322\356\301\245\337N\335\232i4\312\t\250\313S\t\315F\306\230Z\223v+\211g\334i\245\251\246Nh\rN\rN\rR#T\350\334\342\254\253b\245V\30585<5H\036\236\257R\007\247\253\324\212\324\360\324\365z\2205L\255\305J\255R\251\251\221\252ej\231\036\246W\315H\255R+\344\324\301\251\352\325 jP\324\205\251\245\2517Tnj\007j\201\232\243-\212c5D\315Q\261\250\231\316jx\244\030\301\250\244\014\271=\251\253/\024\307\2235\0135F\315L\335R\006\0058<\322\357\315.\356y\024\023F\354R\356\244\337HO\025\033S\t\246\223\221L=i\204\327\010_\003\336\232\322{\320\r;w\275(jpj\221\033\025f\023\334\325\205j\2205<585;}9^\244W\251U\352Ezxz\221Z\244V\311\251\325\252Ej\231\032\246V\251U\252Tj\225Z\245W\251Q\252P\364\360\365 ~)C\320Z\232Z\232^\230\317\232\211\316j\0075\0135FZ\230Z\242g\250\313sNF\343\2575(\223z\342\240a\264\234~U\0235D\317L-M\'\002\205lT\252pG5.\375\324\323\3054\232n\352M\364\273\351\245\263L&\230M!j\215\272W\000^\233\276\224I\212P\371\247\006\247\206\251\020\344\342\254\243b\246W\251U\251\301\251\341\350\363)\312\364\365z\221d\251\026J\221^\245W\251\221\252uj\221Z\246F\251U\352Tz\225^\246G\251U\352Uz\221^\244\017N\017N\rF\372B\324\335\324\302\324\3065\013\324/Q1\250\231\2526l\032f\356h\r\214\323\204\233N\r\022\034\214\367\252\354s\365\250\313`\363\322\243\'4\207\245 <S\225\352T\222\245\373\312@\250\311\355LcL&\223}\033\251\013SI\2461\244&\274\340\275!zr\265<585H\033\002\246\214\342\247V\251\025\352Uzz\265.\3727\323\203\323\303\324\212\365\"\275J\257R\253\347\212\260\255\212\225Z\246V\251\025\352Uz\225^\245W\251\225\352Dj\231^\244W\247\207\247\207\245\337K\276\220\2657}4\2654\2651\215D\346\240sQ1\305D\306\243\007\223I\272\206$\342\236\037#\025\013\234\032\211\216i\234\032i4\2314\003\212z\265O\033\340\323\2449\347\275D\335*3\326\231\2327Q\272\220\232i4\322s^i\276\200\324\360\324\360jE4\365952\266*Ez\221^\245W\247\206\245\335F\352pzpzx\222\244W\251\221\252x\233<\325\205j\221^\246W\251\025\352Ez\225^\245G\251\225\352d~*P\364\365zxzxz]\364\340\364\026\246\226\244\335\3054\2654\265D\3475\023\032\211\216j\0264\317Zfy\240sJ[\024\306n9\344T,})\204\346\233\272\227<SsJ\033\025\"?\275J\036\232O\345M=j3\300\246\346\214\322\223\232i\351L=+\3147\373\323\225\263O\rR)\247\206\251T\342\236\032\244V\247\207\251U\351\333\351w\321\2774\340\364\340\364\365z\221^\247G\253(\330\0252\275H\257R+\324\252\364\365z\225^\245W\251\321\352ez\224=<5<=<=.\372P\364\355\364\335\364o\244-L-O\213\004\034\323\032! $v\252\222)S\212\205\316\0053\2650\234Rn\346\202x\250\230\323X\217Ja\365\246\3654\214px\244\315&iCT\212\365 l\212i4\323\322\233HN(\0074\264\303^T\0175\"\232z\232\221Z\244SO\rO\rO\337OV\247\207\247\007\245\363)C\323\203\323\203S\203\324\250\325b&\311\315XW\251\025\352Uz\220IR+\324\212\365*\275J\217S\306\3652\275J\257O\017O\017OW\247\007\247o\243}\033\2517R\356\367\244=)\241\312\322\244\333r=i\257\031\225sU$R\265\021\351Q\267Z\214\234Q\276\220\234\323\017\024\314\343\232C\323\"\233\214\212nM!4f\234\032\236\036\234\0334\023\232i4\332)I\310\244\257\'\rR)\247\206\251\024\323\303S\367S\203R\206\251\025\261N\017N\337F\372P\365 zpzz\275H\036\254\306\330\0252\275H\036\236\257R\253\324\252\365\"\275J\257R\253\324\310\370\251\325\352Uz\220=8=9^\244\017N\017F\3727\321\276\227u\033\251\t\246\023\326\205\220\251\347\2452f\335U\337\212\211\215D\346\243\335K\272\202\331\250\3114g\024\231\306i\2074\322h\240\034R\206\247\206\247n\240\234\322\023\212N\374R\321^J\r<7\024\3455 jxjpjpjP\324\360\364\241\351\333\363F\372P\364\360\364\360\365\"\275I\033d\325\204z\231d\247\211*Ez\221^\246W\251U\252Uz\221^\246W\251\321\352Ez\220=<=8=<=<=\033\351wQ\272\227\177\275\001\350\337HZ\232Z\230\307\212\211\315B\306\243cQ\223\212B\324\006\240\372\323I\241Nx\357M<SI\3157u&iriCS\303R\223Fi)sFk\310\303sO\006\236\032\236\255O\rN\rJ\032\224=.\372pz]\364\233\362i\301\351\341\351\352\371\251\003\342\246G\305L\257R\253\323\303\324\212\374\324\252\365*=L\217R\253\324\250\365*\275L\217R\253\324\201\351\352\364\360\324\360\324\340\364\241\251wQ\272\224=\033\351w\323KR\026\246\226\250\331\252&4\302x\24651\272R\003\212]\324\215\326\232OCN~G\024\3003\232a\353A\351M\315(4\340\330\247\356\240\0323F\352Z\377\331"
+byte_png: "\211PNG\r\n\032\n\000\000\000\rIHDR\000\000\002\000\000\000\002\000\010\000\000\000\000\321\023\213&\000\000\001\322IDATx^\355\335K\n\203@\014\000P\321\373\037Y\351\246]4\264\2102\352L\362\3362\224\ne\254I\234\3174\001\000\000\000\000\000\000\000tf\216\001\000\000\222\222\371\001\000\000\000\000\000\000\000\000\000\000\000\000\\m\211\001\000\000\000\000\000\000\000`Dk\014d\263\305\000\000\000\000\000\220\207\006 oN.\003\000\370\351\2664\351\266\013\001\000\0000\210\335\2278\273\037\000\000\000\000\252\3226\350T\363\251\001\315\277\020\000\000\200\356\251\372\001\032\323d\003\000\200\212T\002P\231&+\000MH)\241*w?\000%x\340\221\300\311a\254{\010\000P\305\311\204\021\000\000\240\023\372\331\234\362\0318\006\020\000\000\300\243\326\030\330\241\214\003\000\000\000\000\000\030\303\321\367@\000\000\000\000\000\000\000\000\3000,\373\006\000\000\000\000\200k9`\022\000\000\000\000\000\000\000\000\000 \021\0335\334\243\375\357lZ/\000\000@5*A\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\340b\266?\004\000\000\000\000\000\000\000\000\000\000\000H\314\342\021\000\000HN\322\017|\363\257\220\332\026\003\000\000\251,1\000\220\335\032\003\177\250\366\001\000\000\000\000\000\000\000\006c\302\007\000\000\300\2448\302\362x\000\000\000(D+\010\000\000\000\000\240\nS\303\000\000\000\000\000\000\000\000\000\000\000\000\000`8\263\025\202\000\000\000\224`\313t\000\000\030\223\\\036\000\000\000\000\000\016Xb\000\250\30421\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\206\341`\032\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\200\'\274\000>R\030\020\037\177\342\335\000\000\000\000IEND\256B`\202"
diff --git a/core/res/geoid_height_map_assets/tile-9.textpb b/core/res/geoid_height_map_assets/tile-9.textpb
index 5f23f1c..f556a35 100644
--- a/core/res/geoid_height_map_assets/tile-9.textpb
+++ b/core/res/geoid_height_map_assets/tile-9.textpb
@@ -1,3 +1,3 @@
 tile_key: "9"
-byte_jpeg: "\377\330\377\340\000\020JFIF\000\001\002\000\000\001\000\001\000\000\377\333\000C\000\004\003\003\003\003\002\004\003\003\003\004\004\004\004\005\t\006\005\005\005\005\013\010\010\007\t\r\014\016\016\r\014\r\r\017\020\025\022\017\020\024\020\r\r\022\031\022\024\026\026\027\030\027\016\022\032\034\032\027\033\025\027\027\027\377\300\000\013\010\002\000\002\000\001\001\021\000\377\304\000\037\000\000\001\005\001\001\001\001\001\001\000\000\000\000\000\000\000\000\001\002\003\004\005\006\007\010\t\n\013\377\304\000\265\020\000\002\001\003\003\002\004\003\005\005\004\004\000\000\001}\001\002\003\000\004\021\005\022!1A\006\023Qa\007\"q\0242\201\221\241\010#B\261\301\025R\321\360$3br\202\t\n\026\027\030\031\032%&\'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz\203\204\205\206\207\210\211\212\222\223\224\225\226\227\230\231\232\242\243\244\245\246\247\250\251\252\262\263\264\265\266\267\270\271\272\302\303\304\305\306\307\310\311\312\322\323\324\325\326\327\330\331\332\341\342\343\344\345\346\347\350\351\352\361\362\363\364\365\366\367\370\371\372\377\332\000\010\001\001\000\000?\000\363]\336\364\340\324\355\324\360\324\273\275\351wR\356\245\335F\352]\324\006\245\335K\272\224=<=<?5 z\220=<=H\036\236\032\236\032\244W\251U\252@\325\"\265H\032\244V\251\003S\303S\303S\303S\203R\346\2274\273\251s\357Fh\335F\352L\321\232i4\322i\244\323)\017Zi\244\242\220\364\244\247SC`\021M\240\364\246\321J:\322\346\235E\024\240\327\233Q\234w\247\003N\rN\335J\032\227u\033\250\335K\272\200\324\354\321\272\215\324\340\324\340\365\"\275H\036\244W\251\025\352@\364\365jxj\225^\245W\251U\252Ej\221Z\244V\251\003S\303S\303S\201\247\006\245\315\031\245\315.i3F\352L\321\232\t\246\232i\351IM=i\017ZJBi3\232(\244=i)\r%\024S\273QN\355J\005\037Z\363SI\232\001\247\356\357F\352]\324\241\250\315\033\250\rN\rK\272\215\324n\245\335N\rR\007\342\236\036\236\257R+\324\212\365\"\275H\032\244W\251\225\252dj\225Z\236\032\244V\251\003T\201\251\341\251\301\251\300\323\201\243>\364\271\245\335I\2323I\232Ph\315!4\204\322SqHz\322R\032J(\244=i)\r%\024S\251GZZ^\203\212\017\245y\241\246\236\264S\201\2434f\215\324n\243u\033\251CR\356\245\rK\272\215\324\241\251\341\251\301\252@\325\"\265H\257R\253T\212\325 j\231Z\246V\251\225\251\341\252Ej\2245<5<5<5;u85.isK\2323I\237z3K\232ZC\322\222\212\017Ja\244\2444\224\277\303F8\244\357M\2444\224R\212Zp\035\251qA4\225\346\307\2554\323M.h\311\240\322f\223u\033\2517R\206\245\335K\272\215\324n\247\006\247\006\247\006\251\025\252Ej\221Z\245W\251\025\252Uj\231Z\246V\251U\252P\325 j\221Z\236\032\236\032\236\032\236\r8\032vh\006\234\r.i3\232)\324Q\232(\242\232zRc\212Jozu\024\230\246\221IHG4b\224S\251GJ;RRw\2578\"\230zSOJJQ\326\202i\247\25534g\024\231\245\335J\032\227w\275&\356iwR\206\247\006\247\006\247\253T\212\325 j\221^\245V\251\225\252Uj\235\032\246V\251\025\252@\325 j\2205<5<5<585<5\000\363N\315.h\315(9\247\nZ(\242\212C\326\222\220\322w\243\024\276\364\224\336\364\204RR\342\226\214sN\2444\224\206\274\351\226\230E0\212m\024SI\357M&\233\232i4n\245\315\031\243u\033\251\301\251\301\251CS\303S\303T\201\252Ej\225Z\246V\251\225\252tj\231Z\244V\251\003T\201\252Ej\2205<585<\032x4\354\322\203N\315\024\345\247\212Z(\002\235\212LSH\315\004RRc\232Z\017Jm!\244\242\212Q\353KG\322\220\364\2444\332\340\030TL*2)\204RR`\322S\017Ze!\353M\311\315\031\2434f\214\323\201\367\247f\2245<\032pj\221Z\244V\251U\252ej\231Z\246F\251\325\252Uj\2205H\255OV\251\003S\301\247\203O\006\244\006\236\r(4\240\323\207Zz\323\207Zu(\024\264b\227\024\334sA\036\224\230\244\305\030\244\305!\351IHE\000R\342\212?\032;R\0220\005!\244\315qR[\202*\253\302W5\021^qLe\250\210\244\246\232a\024\303\326\232i\244\322d\321\232L\321\232\\\323\201\342\234\r8\032P\325 j\221Z\246V\251U\252ej\235ML\255R\253T\212\325 j\221MH\r<\032\220\032x5\"\232x4\352p\351NZ\220t\247\216\264\243\255:\224\nZ\\\032LR\021HE&(\305!\244\"\232E%\024PO4\203\024\204\320x\2444\225\312\260`9\025\004\200zUy\"\005r*\263\306\303\255BG\250\246\221L#\232i\250\3150\323i\247\212nM\0314QN\006\234\r8\032p4\340jU5*\232\231MJ\246\247SS)\251T\324\212jU4\360jPi\340\324\200\324\212jE5 \245\035i\342\236\265 \247\216\224\340)\300S\261K\212\\Q\203I\217jB)1HE!\024\323M\244=i(\2444\322qKHOJJ+\005\221[\255V\222\337\236*\253\306\313P\260\004c\364\250\0361U\331\010\250\310\307Za\025\031\024\302)\204S\017ZB)(\242\212p\247S\201\247\212\221MH\246\246SS!\251\324\324\312jE<T\200\324\252j@jE5 5 5\"\232\220\032\220\032x\247\016\224\361\326\245^\264\361O\035i\340S\200\245\002\227\024b\214R\021M\"\220\212B)\206\230z\322\032J)\246\203\322\233E&h\347\025\214\302\242\'\232c\242\272\362*\234\266\304t\025X\243)\344qP\314\233I\307J\201\224\032\205\227\035*2)\214*2)\204SH\244\305%\024\270\245\372S\251\324\361OZ\221jU\251\326\246J\231jU5 \251\024\324\200\324\212jAR\003R)\251A\247\212\220S\326\236*U\251\026\236\005H\0058\np\024`R\340Rb\220\212B)\244SOZ\215\251\246\222\233A\351Hz\323H\346\222\212N\364\265\222\313P\224\346\230\300\255D\314@\252\362\020{\n\254\352\030\020z\325g\214\201P\262\372\212\211\226\243a\3061Q\225\250\310\246\221HE&)1\315.)@\247\005\247b\234\005=EH\242\245Z\231je\251EH\265*\324\213R-H\265\"\324\213R-J:T\202\244Zz\324\200T\213R\250\251\000\251\000\342\234\0058\n\\{Q\2121M\"\232E!\025\031\024\306\246\236\224\332CIM\357E4\365\242\2121T\035*\026Z\205\327\212\256\353U\335\016*\0220zTl@\355Q\224F\351\201PI\001\307\025Y\343#\255B\331\007\221M\306E&\336i\205i6\321\266\227m(Z\\S\200\245\013O\002\244QR\250\251TT\253R\250\251\027\255J\242\244Z\221jE\251\026\244Z\225jU\247\255H\265 \025\"\212\225EJ\242\245QR\001N\002\235\212\\\032JB)\r6\232j6\246\036\264\312i\353Hi)\017ZL`\322b\214\032\000\245\252l8\250XT.\271\252\356\225]\3628\025\003\217j\201\226\241`GJO1\207\024\306\n\343\004sQ\024\003\2028\252\362DP\344t\246\025\244+M\331F\332]\264\273)vS\202S\202\323\202\323\302\324\201jU\025\"\212\225EH\242\245QR\001R\001R\001O\002\244QR\250\251\026\244QR(\251@\251TT\252*U\025\"\212\220\np\024\352(4\332CM=i\206\230\335*3M4\323I\332\233F)\010\244\242\214R\342\252\270\342\240aQ0\250\231I\250Z>y\025\013\307\336\253H\2305]\226\242d\246\025\357I\327\202)\254\274q\310\250Z y\025\031B)6Q\263\332\227e;e.\312P\264\340\264\340\264\340\265 Z\221V\244\013OQR\201R(\251\024T\200T\200T\200S\324T\252*E\025\"\212\221jU\025*\212\231EH\242\244QO\024\341N\002\227\024\332CM4\323\326\230z\323MFi\247\2456\233HE%\035\250\242\212P*\274\213P\024\346\230P\323\n\201\324\325i\016\032\2438\3175\004\221\345x\252\214\204\036\224\302\264\302\225\023%0\202\246\223h<\216\264\2058\344Tf<\036:Q\262\224%(Ojv\3126S\266R\204\247\004\251\002S\302\323\302\323\302\324\212*@*E\025 \024\360*@*@)\340T\200T\252*E\025*\212\225EJ\242\244Z\220S\307Ju:\212CM4\323M4\323Lja\353L=i\010\246\236\224\224QI\2121K\212*)x\340\212\211q\273\232\206C\211\010\252\316Npj6Nri\031A\025\001\0308\250d\217\034\342\242*)\214\225\021Nj6\2175\031B\017\024\014\3644\343\030#\"\243\tN\tJ\024\322\204\245\331K\266\224%<%<%8-<-<-8-<\n\221EH\005<\nx\024\360*@*E\025*\212\221EJ\242\245Z\221jE\247\216\224\372u8QM4\207\2554\365\246\236\264\224\303Q\232i\246\236\224\224\230\243\024c\024\224QED\322\007R\030sP\222\007j\211\260[4\306@Nj&C\217\245G\234q\212c&Nj7\\\212\200\246\r0\246i\0144\323\017\035*&\213\007\030\250\332:n\322)BR\354\245\013\212]\224\2418\245\tN\331N\tO\013\203\365\247l\245\tO\013N\013N\013R\001O\002\236\0058\nx\025 \025\"\212\220T\253R-H\265\"\324\203\265<S\351\300\346\212(\246\236\264\207\2554\212Ja\246\032a\351IHG\024\235\350\2444\224QN\003\025[\200\307\035\351\273AlPb\301\246\024\347\216\224\306Q\214UW\214\206\250\330m\342\243cQ0\365\246b\234\005;\003\024\206 \335*\'\203\035\252#\027\2653\313>\224\230\000\340\323\202\203N\362\351\302*p\217\332\235\262\224\'\265;e8-.\332P\264\340\264\340\264\340\264\360)\300S\300\247\001O\002\244Z\221jE\251\026\244SO\025 \247\212x\247\nZ)\t\244\244=i)\010\246\032a\353M=i\244RRRQF\005\030\242\212\244\033\006\245\005O=(g^\346\243\336\244pj69\357Q0$T.\024s\232\214\340\365\250\331\027\034\032\214\216}\250\307\245\0035\"\323\360\017QH\320\206\034\n\200\304A\351Q\275\276\376q\3150\333\274|\365\251\221r\242\244\021\322\371t\241)vR\354\245\tK\262\215\224\241i\301i\300R\201N\3058\np\024\341\322\244\025 \353O\006\236)\340\324\200\323\301\251\001\247\203K\236i\331\346\214\322QE!\351Hi\207\255Fi\017Jm\030\246\342\220\212J\\\0321F)k=\206\rF\362`\361Q\027$u\246\207\3055\2459\316h\017\221\326\243\221\270\353P\022I\240\222\0055[-\203R\025\343\212P8\247\001\212x\031\025*\255)\213=\251\026<\036\224\366\2002\036*\233Fb~\234\032\224(#4\340\224\276].\3126R\354\244\333K\262\215\264m\245\305.)qN\002\224S\3058t\247\212x\353O\006\234\r<\032\221M<\032\220\032r\236?\032vh\242\212P))\rFi\206\220\322Rb\223\034\323OZ)pi(\245\301\254c!=\351\254sQ\223\212\215\236\243-G\231\212\215\244\367\244\017\315H\030b\231\362\347\212z\236*E\024\360\005=W\232\231W\212\224.i\301)\373\016\336\005E<A\340\351\310\252\261)\350jp\224\355\224\273)6Rm\366\245\333I\266\215\264\233h\305\030\245\305.)i\302\234:R\203N\006\234\r8\032\221M=MH\246\244\006\236)iA\245\245\003\232Z1\3054\364\250\332\232E4\322b\214R\021\3154\212(\242\220R\3275\346\341\252@\374S\031\252\0264\302i\214\306\231\234\236\264\205\2104\375\370^\264\3459\025 \353O\014zS\325\216j\314g$\014U\200\270\251T\n\220.j@\274t\246\252+\266\323\336\252\317\007\2257N)\352\271\024\340\224\273)\nRl\243e&\332M\264\233i6\322b\227\024b\226\212QKN\245\035)\342\244Zz\324\202\244Zvi\302\234:\322\323\251@\243\034SH\250\310\246\232i\244\242\2029\246\221F(\305&9\240u\245\305r\016pjD|\212Rx\250\230\363L&\230\307\212\217<\322\266p\r4\261\305M\033qVW\2454\234R\244\2305e%\350A\346\256G( f\247\030\352\rK\037&\255\"qM1\205\223\212m\324[\323\2475Z!\306\rK\262\215\224\205i6\321\266\223m4\255!ZM\264\322(\307\265&\005.\005\024QN\245\024\341R\n\220T\202\244\024\242\234:\323\307Zp\024\340)\303\255!\246\232a\246\021M\244\305.)\017ZB)1I\212N\364\240S\200\2565\251\020\221O&\243\'\232ajc6h\002\236\006F*6\\R\306qV\026A\266\230_4\201\271\251\221\216j\344r|\2705e\034\025\034\325\210\237\006\264\340!\3059\223\347\316)\356\231\217\245Pd\3319\036\265 Z\\S\n\321\266\215\264\205i\245i\n\323v\323J\322b\223\024\230\244\242\212u(\247\016\225 \251\026\244Z\220t\245\247S\307Zp\351O\035)\324\204SM0\212a\246\036\264QK\216h\"\233\2121HW\346\024\340\271\342\214s\\Su\244\006\224\236*3\307z\211\237\234\nM\324\240\324\252E\004n\030\357M\010i\335\0050\232z\016j\312c\024\362\352\005=%\367\253QI\223\326\264\355f\332\331=*\340\271FoJ\270\245Y\005R\274\217l\200\323TqA\031\244\333F\3326\323J\323H\244+M\333M\"\233\212B)\244SOJJ)\303\245(\247\212x\251\027\255H)\364\341\322\234)\342\236\005<S\205!\246\221L\"\230E6\223\024\270\245\024\021F(\305!\034\322c\270\245\357\\.\352il\032n\342Z\243y2\325\031nh\007&\244\004\342\232\\\203S\306\304\256M?4\302s@\031\241\233h\246\375\240\216\364\206|\367\251RS\353V\341\224\216\365~)\310\3075z<\262\357^\325r\t\333\247\245\027\023\031\016})c!\226\244\333F\3326\320V\230V\232V\232E&\332k\n\214\212B)\244S\r!\024\224\341J:T\202\236)\340T\202\237N\024\360)\340T\200S\200\247v\240\216i\244S\r0\212a\024b\226\224\016(\307\024b\227\002\230E\000R\327\000N\r3\253R\261\010\270\356j\273\032e9z\324\340qQ\262\345\252t\030\\S\261\3057\2759i\262\0163U\331\0163M\000\346\254D\246\256\304*\364K\232\320\210\225L\016\365f\335s\232\260\025J\025#\232\2113\034\245\017N\325l\014\212]\264m\244+M+L+M+I\266\232\313Q\021M\"\232E0\212m\024S\200\247\212\220\nx\247\216\224\361\322\236)\342\244QO\002\236\005.8\242\220\212a\024\302)\244Rb\224t\245\003\2121F)p)\244Rc\212P+\316\331\3014\322\373z\016j&r\334\232ni\247\245I\037Z\234q@\\\265H\007\024\352a\0304\364\034P\343\345\250H\312\342\230\251\315XL\n\267\n\023\316*\374\013\315_U\033j\345\254`\214\216\265e\243\310\334\005A*\344\007\003\221S\304C(\251v\321\266\232V\232V\230E4\212B\264\326Z\211\226\230E0\212a\024\322)1K\212P)\353O\025 \247\212p\353O\025 \251\005H)\302\226\216\324\204SH\246\221L\"\232F){R\322\321\212\\SH\244\305\030\2575\335A\"\243c\3153\275:\244QR\255<u\251(\357L=jD\351D\237v\241\035i\300`f\234\234\266+j\3260\"\000\325\245A\232\260\252M\\\267;\005h\333\200\340\2361\216\225^h\202\310W\261\250\341\033\\\241\355V\200\342\202\264\322)\244TdSv\320E4\212\215\224\223\212cD\303\232\210\212a\024\302)1F)\300S\205<\n\221E<\np\247\212\221EH\264\361O\024\356\377\000J1\305&)1L#\232a\024\322(\242\224t\247\001F8\244\"\223\024b\274\261\237\232P\371\240\232Jp\251\224qN^*@i\324\016\264\207\255K\030\242Nj,sJG\0255\264e\244\007\025\263\020\302\212\264\2035:\n\262\202\256\333d=Ip\271\001\205T<L\033\326\256(\312\321\212B\264\302)\205i6\322\021L\"\230\313\232\\|\270\315Vd\332\306\230\313L\"\233\212\000\245\3058\nx\025\"\212x\024\340)\352*@)\340T\200S\200\357N\003\2574c4\224\207\2454\365\250\315%&8\244\035i\324\341\322\216\324Rb\214W\222\026\346\234\265%.)\300\324\311O\245\006\234Z\205<\323X\374\365<g\214\320NZ\233\216i\t\347\025\245g\026#\004\325\364\025f1V\220U\210\306H\253Q\360\334T\354\277-Tt\371O\265X\204\356\214T\233i\n\323J\323\n\323v\323H\246\021M\"\223\261\250\034e\3526\024\302\264\334Q\212P)\300S\200\247\201O\247\001O\002\244\002\244\002\236\0058\np\353@\242\232z\323\017Ji\024\323\326\220\364\244\3058\np\024\270\342\220\n0(\305y\005<\032\220\036)\371\245\035jE5 4\341Ct\244SA\345\252B\333c\246F\371z\237\265$k\231y\365\255x\016\024\n\266\230\2531\216\346\247\334\000\353R\307 \355Wm\376nj\331\031J\251\"\220\344v4\266\347k\2245so\024\205i\245i\205i\245i\205qQ\221L\"\223\025\023\016j2\264\302\264\233i6\321\212p\024\340)\340S\251\300S\300\251\005<\n\220\np\024\270\343\024\270\242\230z\323H\246\032i\244\305\0304\240S\251q\305(\024\270\243\025\343\200\022*@\264\356\224f\235\232z\265J\246\245\316\0055\233&\225G\024\270\245a\225\250\207\313%[S\225\247\201\206\006\257@zf\264#*\006I\247\031\273-9\035\210\2531\347\326\265\355\006\024f\256\214\036*9b\004t\252\3426\335\323\221V\242\'\030a\315HV\232V\220\2551\226\242e\250\312\323\n\323H\250\312\323J\323\n\323J\322m\244\333J\0058\np\024\240S\300\247\201O\002\244\002\236\005<\n\\s\232\\`\232B)\207\2554\212a\024\204Rb\2008\245\002\227\024\275\251\312)\330\244\"\274\201TR\221\203K\326\233\216h\3178\245S\315XJvE-=\007\024\023\3159y\034\3224y\344S\221\212\234\032\2308\305X\201\302\256z\232\260%c\337\212\261\020\317&\254\251\355V\340\371\234zV\315\277\"\255\247\255K\215\302\205\214n\351Ox\227``9\244Q\221AJa\\TL*2\264\302\264\302\264\322\264\322\224\302\224\302\264\322\264\322\264\233iv\322\355\245\305\000S\300\247\201R\001O\002\236\005<\016)qK\216M!\034S\010\246\221M\"\223\024\230\366\240\016:Q\217j1N\003\212z\216)qF+\306\303S\363\221L-\203NS\232\0104\364\034\363Sn\300\300\247\001\205\315\n2ja\200)\207\2559y5`\001\2120\t\351CE\214\021RB\244\260Z\266\303n\005M\033\361W#\037.\343Wm\227\200kR\026\302\325\244n*\3025L\2705c\311\337\016\341Q,ex4\245j\026^j2\264\302\265\031ZaL\366\243e!J\214\2450\2454\2554\2557e\033h\333F\332P\264\340\265 Zx\024\360)\340S\2004\354z\320G\315HE0\212B)1I\212M\264m\243o\265\033i@\247\201N\333I\212\361`i\331\244\352i\303\201N\r\232z\365\247\365js6N\005=N\00585\004\363J:\324\352\337.)\353\326\236O\313O\265\346Rj\324\270\305,g\232\275\t\310\305_\265\341@>\265}\rZ\214\361S\253\nz\310wU\353i\317\335\'\203\326\247t\347\212\203\222q\212aZaZiCQ\224\244\362\351\336_\2651\222\241p\005B\330\024\302E\'Z]\242\223m\033)6\322\204\247\005\247\205\247\205\247\205\251\002\322\205\364\243i#\212R>jk-&\336)\010\246\342\215\264m\244\333F\3326\320\027\232x\024\354Q\212\361\021KNQN<\320\001\315J\243\024\026\347\002\225GzR\334\320\036\244\006\234\0175*5L\255\315=\210\331\305:\324\341\271\251\345|\200=\352H\217\000z\325\330\010\365\255\010\0161W\243aV\021\275\352da\334\322\356\332\335j\304\022\374\343\025\257\031\335\032\372\324r\000\262t\353M`1\300\244\021\3654\322\234\323\nS0\001\245!\233\356\2551\341\227\031\305S\225\033v\t\241l^E\316N)\255\247\310\017\006\253\2742\304y\024\320\344u\030\251\025\201\251\002\203K\262\227e8%8%8%<-<\201\322\223\234`p)@#\245\033i\010\246\342\220\255\033i\002\321\266\202\264\233h\333J\027\232pZv(\305xfx\245\034\232\224\014\nUZ\221W\034\232By\241W-\223Nf\002\243,)7\324\213\'\2758J;\324\211 \251\204\243\024\365}\334\036\225b2\001\251$\004\374\302\244\211\276\\\372\n\265\003|\243\326\264\241\223\003\232\264\257\357Vc\220c\232\235\037=\351Y\260*\345\202\231\033>\225\261\031\306)%;\345\030\355K\212\017J\215\210\024\314\0265*Z\344g\255L\220m\344\212\222DF\204\340r+<\331\263\276\342\274U\330\255\306\314t\252\363A\"\261\302\325W\210\267\004T/`\031zU\031m\236\022H\351Drs\203V\224\006\024\361\035;\313\245\021\322\354\245\333F\337j)pi\301A\316i\245i\002\322\021I\266\205\030\315\000PV\233\266\215\264\241y\247\205\243\036\324m\257\010=qSF\265!\000\nM\300R\206$R\214c\232kI\201\201Q4\224\3170Ry\235\351\004\2715\"\275X\212E\3163S#\215\306\254\306T\325\205\03052\260\306\323I\235\210G\255Y\265$\256\356\302\264\021\306\320j\304o\315YW\367\025b98\247\227\315i\351\262\000\370=\353m\024\021\305B\331\216R\r;p\306i\217\'aBD_\223\322\255Ch\314\303\002\264\343\264\n\200\021\315Ha\211W\346\003\036\365\023-\257#\201\364\252sK\020;W\265T{\215\247\212\221eyq\351S\010\021\307A\232\032\317\013\362\363\355U&\260.\016V\263&\323\231X\220*$G\214\340\203V\243\000\212\224G\236\324\276]!JiZB\264\005\245\333J\026\202\274Rm\244+I\266\225\000\004\344v\246\343&\215\264\205h\013K\267\232v\3326\373Q\212\360|sR\247\002\225\263MP\t\311\247\026\002\243i=\352&z\215\216EFO\035i\255/\030\024\325\230w\355R%\302\223\367\252T\235\025\303n\251V\343\315\227j\034z\325\350\337h\312\267N\325q%\3713N\206m\357\217J\222\342@\241W\2715r\311\307\220\300\325\244|\212\231d#\245X\216R\306\255\243\361\326\236$\253v\356\341\301\025\320Z\334\345Fz\325\321\373\316\253\232\014#\030T4\211a+\267\3345\243\006\237\267\357U\301\022\3047\020\024\n\257=\337d\374\352\234\227\016\343\004\325Ww\003\203T\345\363\013g5\036H<\325\250\'+\323\025h^\355`1\232\260\223\211\010\303c\332\264Da\243\031\003\006\242k\005q\300\252\027:Y\031!k5\355^&\310\034S\343\301\340\324\245\006)\205)\205)\245)\002S\266\322\205\315)^)\233x\244\333F\336)\241pM\001)J\323J\321\212]\264\270\024`R\021\315xW\227\212xJd\207\260\246tZ\215\230\232\211\211\355Q\222{\232\206K\200\274\016j\277\234X\362i\373\201Z\211\262M\002\244Z\225\030\243nS\315_\202|\340\356\347\275[78m\275\252[\031G\332y=j\325\331\315\322`\366\253p>\325\030\357V\221\30052\311R\244\240\032\231n;f\255\302wsZ18\000f\264\255\'\033\205t\266\222\304c\007\214\325\325x\313\177\016Gz\237\355\020D>f\014}\005!\274\014\270\216<\037Z\2554\354\331\004\325B3\336\220\250\2462\034Uy\"\366\252\355\037\265 B*@\274g\034\324\261\261\004`V\265\254\347\0001\343\336\264\342!\276\3575iaW]\256*\255\316\224\245\013 \315`^X<,]G\025^6\r\305I\2674\322\224\302\224\004\243m.\314PS\212n\312B\264m\342\230S\232]\270\024\323\212a#4\322\302\215\324\271\317j9\364\240+\032\361\027\307ji\'\030\002\243e\307Z\212C\306*\006p\242\240i\217j\202II\030\315Tv\346\242/\203OI\261Ro\3174\241\251\341\300\247\t*X\344\333\322\256+\003\026I\346\232\223\264ro\007\241\255X$7\r\346\236\303\212\272\317\2624a\334T\261M\236\365a$4\343.;\322\305)i\200\256\246\322\301\236\307\316V\343\275\013\033\007\301\253\261\025\214r\016kJ\332\342V!P\034V\325\2742\025\014\355VG\331\3429s\223\351I\347+\003\265\200\366\246\023\270\365\243h\245\002\202\0054\240#\245F\320\217Jg\222=*E\200\021\322\224Z\363\220*e\215\200\003\025\251g\300\003\241\2558\210\316\017\036\365eA\0318\315T\272\263Y\321\206\3203\\\205\375\263Z\\\222\007\031\346\226&\016\271\024\362\231\246\224\244\013N\010)\n\374\324\024\342\231\266\232@\315.\0050\201\326\242w\002\240,\314\330QR-\264\215\353N\373!\034\232h\207\234b\246[s\212w\221\3074\206\"+\303\n\214t\2466\007j\202F5VV\300\346\251\273\0265\023\032\256\344\324\016j\023H\rH\257\212~\372P\364\340\365*\275M\034\254F\301\326\233,\256\256\020\2163]&\237\264\332\241\007\034T\227/\202\251\355\232|,p*\3628\002\230\362|\325r\3220@c\326\272+K\341\024\001\030\344\016\324\246\377\0002|\240\001Z6\227\001\300\336\240\257\251\255\3536\265\333\271H\000u\346\247\270\324\342U\331\021\374j\232\334\226|\223\232\267\034\200\201V\220\347\275?<b\224\032)A4\264\341\0304\364J\220\014\036*U\036\3252\020\033\322\264\354\323y\0319\255o\'\216\230\250\2361\212\347\365{\0375\013m\344W7\261\355\245\350qV\222Du\340\322\340Q\200)\254E7\"\232dP:\324-(\035*##\223\302\2327K\217\272j63\036\324\251m+\234\2605v\013P\274\260\253\001\025z\nk\014\366\250\304\\\364\251\004m\212C\021\244\362\275k\300\310\342\241aU\344\3435JL\261\346\253\277\025\013T/\305@\3435\021\024\303\326\212\\\232]\306\224=H\257RG6\311\001\315K4\233\302\267\275tZt\203\354\303\236@\247\313t\245\366\340\022;\324\221K\221V\004\244w\246\254\273\345\305i\303/\226\240\n\275\0233\212\273i\003K7\314p\243\251\255\026\225\021<\250\317\002\237\034\356\243\001\215XI\030\236M[\211\371\025z)1\336\256G-X\022f\227x\245\337NV\251\003\n\225y\251i\352\001\251\025y\247\205=j\325\275\301\205\3075\277it\223\305\264\375\352\225\2429\252\227\026\341\324\361\234\3277\177\247\215\347\013X\362XJ\247)\221H \273\003\246i\246\013\302x\006\225l\256\330\363\232\177\366m\316psO]\"Rr\304\325\210\264\264\030\3343W\027IR\233\202p)[L\n\333J\001Q6\234\213\316\321\371S>\316\027\200\277\2454\333>y\030\245\020\0009\024\2065\035\251\245\000\346\223\217Ji\351M&\276~j\205\315T\230\374\265M\252\273\365\250\215D\3435\013\n\211\226\230V\233\212LR\032\001\247\203JMJ\035<\276G\"\2654\373\274\305\214\364\247\264\331\227\361\255\013w\312\212\260\317\362\324Q\313\211\263\232\330\266m\300du\255\2100\000\253\242s\267j\360;\323\225\271\253\010\325a$\025j)*\344r\032\265\034\206\247\022\236\306\244Y}MH$\247\t\005J\257\236\365b6\367\251\363\357NV\346\247V\310\342\246S\236\264\021\315i\351\363,N2kx<rE\225 \232\205\2075Z[X\345\352?\032\256\366\010?\204\032\251%\240S\304t\202\025\003\230\261\370S\0322\256\n\256?\n\235c2/*)\342\324zR\033uNqI\346\004R\275\215V\226\343-\221\236=j?\264\215\270lTMq\010~\0056I\303\016\005B_\214\324\016\374\236\325\021\223\336\224\034\212By\305!\353^\000\302\240u\2523\037\233\332\252=@\303&\230G\025\021ZaZaZ\214\2450\2454\2554\2554\212\026\237\216*\265\314\306\030\213\003\212v\217t\314N[9\351[\001\211\223\232\323\201\360\242\2472dT;\366\276s[\226R\201n\246\265\255\345\3348\253\2215XV\251<\317J\2322MZ\215\3105r)F:\325\205\234\016*u\234\032x\226\237\347{\322\211Nz\324\361\313\357Vc\227\336\247Y\275\352T\2235b9*\3026je\031\245%\221\363\236\225j\rL\243\001\223Z\366\367\2512\214\216j\316\321\214\216\225\023f\232p{\ncD\030T\r\003\027\366\251\025\000\343\024\374\001U\256\'U\312\343\025\235#\026\031\016:\361T\245Iwd\034\346\252\315\270q\223Q\251\"\236%\"\225\246\312\324\014\365\021jx\223\024\355\331\244-^\n\3353Ufq\202\005P\227$\325vZ\214\2554\255DR\232R\243e\246\025\250\312\323\010\24651\2056\235\236+\037U\230\210\312\216\365w\303\221\022\273\332\267\177\345\271\253\2217\002\254\003\305C#`\346\266,\\\033e\255\210fU]\242\256E-YW\251\343*OZ\262\222\252\036\231\245\363\tl\216*T\225\263VQ\230\363S\253\221\326\247Yi\341\363\336\236\036\246G\253\013%L\262\373\324\3130\365\2531K\357W\242l\325\264e\365\244\224\344qU\034\025l\212\267e{\345\310\0015\324\303p\222[\202\016iN\322*&\034\322\003K\221M%G5\033L\247#\246=k6\363$d\036\247\212\241+\272G\203P\t\030\237\230\323\\\003M\362\370\315D\303\025\0215\033Te\250\r\305\002L\032V\220W\204J\304\3259\r@\3035\031Z\214\2554\2550\245=`R\205\230\343\025VE\0318\250\030TL*6\353Q50\236i\271\245=+\032\3762\367\001kwL\210Ad6\365\"\257\017\277V\2435a9\2474{\205]\266W\214\016N*\344r\225l\346\257\301>H\346\264\"\2235j&\001\276aVF\037\356\212\2352\007j^Cf\254\305p\024`\212w\234\033\245H\262T\202\\w\247\t\271\353R\245\300\035\352u\270\007\241\251\222l\367\251\222Bj\314R\343\275h\303?\313VR_z\235\033#\255G%W\223(7V\236\223\250\260;\031\270\255\244\272\004\343=jF\270@\247\346\250~\324\203\223Q\276\245\0108\315T{\347\2238<T_io_\306\240\226vv\311n\225\033H\010\301\250\010\311\342\244\0106\362pi\010\000u\252\357\311\250\036\2435\023S\0014\204\323KW\210IU\\d\323v\374\246\243+Q2\323\n\323qMl\342\240qP8\250\036\240cQ1\246\023@\034\323\210\371k*\344\377\000\244\202kr\300\027\267V=+@(\364\251\223\035*\302\005\251<\304\035MZ\212P\311\223\300\035*9n\202\374\240\363V\355&$\016kb\031p\0075n9\t\346\255\3039\3175ie\367\247\371\240\214Q\273\232\221Z\244\017\212w\231G\233\357I\347\034\365\251\343\230\372\325\270\2445z\'\310\253HE\\\205\305ZI9\253\t.;\3224\204\236\264\342\003\246)\210<\246\312\366\253\177n;q\336\217\2661\376*a\272l\365\2463\2269\247\t\0161O\335\305F\306\231\232]\370\034S|\336\324\323\'\025\03385\013\034\232a\353Lnj&\340\323I\246\023\315x\233TEsM\333\216\242\243+Q\224\250\312\323Xf\242aP?z\257%U\223\245@\325\031\353M\3059E8\214\214Ve\344\\\356\255\r&\340cc\270\000V\320*\307\000\203N\344v\247\006n\324\252\214NO\003\326\246\363\202\256\320zUB\314\363\347\336\266l\270Q\232\325\216N\225\245l\341\216*\341\n\007\024\202L\034f\246\215\363\336\245\007\216M<5;}!z\003\323\327\236\36526:\325\270\345\002\255\3057J\271\034\334U\230\346\253qK\236\365:\310H\247\357\251\243z{\236*#\301\251\027\030\240\225\024\007\024\360i\333\270\246\263S\013SKTL\3304\322\324\205\251\271\244&\230j&<\323\r0\212\361vZ@\000\344\323\031A\030\025\023.\rFEF\302\242j\205\315Wz\255%WqP\221L\"\233\266\234\005<&j\265\334Y\\b\262\310\226&\312\344U\273MRH\234\371\271=\253^\333UG<\237\316\257\013\264\362\367\000*\007\276f8\006\234\263\345}\352\305\272\356`kb\001\265j\332>\r^\266\233\007\255^\023\345z\323D\234\365\251\222_z\230K\357O\022\217Zw\231G\230)C\363S\304\331\357Rn\301\353R\306\365n\'\253i\'\275L\263c\275Z\206~y5u\'\036\265*\312*t\224z\324\236h#\004\322n\310\353B\312\001\301\2512\010\3153\200x\247\t1N\337F\352ajajc53u&M.i;PFEA \"\230\016x\242\274h\2551\226\230T\323\031p*\0229\250\231j\026\025\013\n\201\305V\220T\014\265\021ZaZM\264\345L\324\241qP\312\241\252\253\333\206\311\305W6\1777J\r\263#q\305_\267Y\004@\023RyM\2735b\030\361\326\257Dv0\2558\\\025\004U\2255<m\212\262\262\361R\254\2315\"\27752\311\3058K\317Zp\226\234$\247\0075f\027\365\247\031>j\236)*\334oS\254\246\244\023s\326\247\212l\2663WQ\2163\232\262\222q\315J&\301\353R\t\271\251\004\271\350iKg\275H\262`Q\346\346\220\2759^\235\276\220\2654\2654\232i4\264\036\224\200\372\320x\2460\310\250\010\303R\366\346\274\200\2550\2451\226\242qP\262\234\324n8\252\3169\250\230T\016\265]\326\240e\250\331i\233h\331R*\342\221\316\007\025\0263\326\230\303\002\224\247\312\033\035i\2142\300T\312q\306)\306JzIV\243l\212\275l\3308\255\004\"\246SS+\212xq\353R\253T\201\370\244\336sO\017\305=$$\325\224\347\223R\371\201W\002\221d\313U\204\223\025a%\251\304\207\024\345rzT\251!W\255(\256r\243\245X\027\000\216\324\033\201\332\244\216|\367\253)(\251D\264\343%(n\371\240\276(\022\343\275H%\006\227\177\2754\2754\267\275(oZx4\036\224\323\301\243vF)\204\342\230\307\2757<W\2242S\nTl\265\013%@\352j\'Z\201\222\241e\250Yj\273\255DR\243d\346\231\262\200\224\244Te\t\244\331\212\215\3078\241\316\024\n\215G9\251@\244\"\234\243\025b&\253\360\267J\276\207*\r<\312V\2016jd\2235i\033\212\223u\031\245\335R\306\340\036j\320|\257\024\306s\336\225$\301\251\304\2652KV\222\\\255=%\332\324\351\'\3475=\265\316F\t\253Bb\017Zx\2275f91\336\254,\336\365*\315\357O\363\351E\307\275\036w\275!\233\232z\313\307Z\220K\357G\233@\222\244V\251CqK\232F5\021l\032izajn\354W\2332TL\225\033%B\311P:T,\225\013%@\311P\262T,\225\021J\214\307\3154\2454\255&\332M\274\323Yj\273\257\315Q\260$\322\252\324\252\274R\021\315(\025\")\'\212\275\n\236\346\257\307\300\305+\014\323\007\006\254FEYG\342\244\017N\315.M9s\232\265\033`b\226B)\231\305=\036\247G\251\226B*u|\216\264\355\331\251\2418j\266\037\"\234\257\317Z\262\262\340T\253.i\342lw\245\363\362:\321\347c\275(\270\367\245\023d\365\251VOz\221e4\360\365*\265H\255\203S\006\342\234\032\202\334T.\3305\031l\324n\373j#/\275qm\035B\361\324,\225\003\245B\351P2T\014\225\013%B\351Q2Tl\225\021Jk\'\035)\205)\205qM\305#\n\201\327\232\217g4\273qN\246\236\264T\210v\266j\364L8\253\261\234\212V8\342\230y5*f\247CR\203R\003R\003O\004T\212p3Mi3\336\22075\"\232\231MJ\244\324\350I\251T\232\2326\305N\037\212r\261\006\247W\342\244W\342\234[\336\232d\"\220JM<==_\232\260\222T\212\30752\236ju<S\367s\232\225_\212R\374\365\240\311\362\342\242f\346\230[\025\004\222qU\313\232\347\331*\026Ny\025\014\221\200p\005@\361\324\016\225\003%B\321\373T,\236\325\013GQ4u\023G\355Q\230\351\214\224\302\225\023%0\2551\2075\033-3m!\024\322)\244P\0058T\261\271\025z\t\rYnW4\321\326\247S\306*T\0250Z\220\003N\013N\301\315?\234S6\363OQR(\251\227\245J*T8\251\201\245\0143S#\342\237\277\236*\304o\221R\003N\335\305F\314)7S\225\252Uj\235\030\346\254\3063\212\262\240b\236\033\265\014\306\225$\343\2558\311I\2774\271\356j)$\343\212\256\355P\226\254\306J\205\223\212\211\243\343\'\251\250\035*\273GQ4u\013GP\264u\013GQ4u\023\'5\021J\215\322\243d\250\331*2\224\302\225\031J\215\227\rQ\221M\"\220\255&\337jp\024\344\0305f6\305XI{\032\220\016x\251TT\353S)5(aN\337\3158\034\323\201\244\3174\365\305J\005<\034\032pqN\022S\274\33684y\20754r\232\260\215\223V\025\261S\007\310\240\260\365\246\236i\264\340H\251\003U\230[\326\257F8\310\251C\021N\0143\232q<TE\260\334\032pl\216\264\365`:\232I\034m\340\325f\222\241g\315FZ\243d\250Z<\346\242d\343\245@\351P\264u\013GP\264u\013GP\264~\325\023GP\264|\324m\035B\361\324l\225\023%FR\230R\230S\214\324N\2375DS\232\215\226\223m\033h\305*\212\225EL\213\232\271\nqS\354\305\003\212\220\032x&\234\rH)wPI\241\\\203R\211iL\224\236a\247\253\023R\256MH\026\247\215q\326\254.\005?~)\311!\317Zs= \226\245V\006\237\232p54m\203W\242\227\345\251\213\214u\246y\200\032sJJ\340SFOZqm\2439\250\214\347=h3\0221Q3\324E\3513VZ<\324o\0368\025\013%@\321\324M\035B\321\324/\035D\321\324-\037\265B\321\373T-\0375\023GQ2TL\225\023GQ\264t\302\225\031N*\'J\205\222\242t\244\331F\312\nR\005\251\025jx\305[\217\201Sg\212P\0058\n~8\247\np\246\220sJ\017jx\240\232Pi\352EH\246\246SS)\030\247\356\305*\313\332\235\274\232z\032\223vh\003\232\225EL\2314\3609\247\255N\256@\251C\223O\033{\232p#\326\234d\002\243i\001\035j\276\376iwR\026\342\243-\315\000\326\263%D\321\324M\035B\321\324M\035B\321\324M\037\265B\321\324-\035D\321\212\201\223\223P\262TM\037=*&\216\242d\250\312q\322\230R\242)Q\262T,\225\023\247\024\315\224\004\240\245&\314S\302T\252\270\251\224\323\267\032\025\310j\265\033\006\025&)B\322\362)\0175\031$\0327S\203\032r\232x5*\232\225ju\247\020M\0023N\n\300T\250)\371\247+U\204\306*Q\3075*\200\302\244\tN<\nT|f\202\3704\236n\017ZS0\"\231\277\336\233\273\232vx\244\317\024\323\326\214\327B\311\355Q\264u\013GQ4u\023GQ4u\013GP\264u\023\'\265Wu\307nj\026J\205\220b\241e\250]}j\026\025\031\025\033\nf\312i\216\242d\250^<\324e)6R\354\342\202\224\004\251\025i\341i\301i\nsR&T\325\305\303(4\360\264\2168\252\354\304\032a$\320\265 \025\"\212~\332\221\005XE\030\251\300\033h\31752T\233E\030\024\215\301\241:\324\352\330\251T\223V#\004\n\235M#u4\301Lv\300\250\203\344\323\267`Ro\243q\247\253\346\2274\032@k\377\331"
-byte_png: "\211PNG\r\n\032\n\000\000\000\rIHDR\000\000\002\000\000\000\002\000\010\000\000\000\000\321\023\213&\000\000\002\322IDATx^\355\335QR\2030\020\000\320\016\336\377\3102\216N\255\032\241\224\220\322d\367\275\317(\010\311fI \310\345\002\000\000\000\000\000\000@&SY\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\300\002_\203MN\000\000\000p\237\021\343\350\264 \000\000Pc.\013\000\000H\304h\020\000\000\000\000\000r\261\352<)\r\017,\223\035\000 ,+\303X$0\000\000\000\000\000\000\000r\260>\020\000\000\000\000\000\000\000\000^\304\342\035\326\211\016\000\000\000\000\000\000\200\010<\375\005\000\000\000\340\331\334\203\002\000\000\000\000\000\200T<\"\004\200\261\271\226\003\000\367\314e\001\000\000\020\2121?\000\000\000\000\000\244\343\361\000\000@^^%\003\000\000\000\000\000\000\200x\254\r\005\000\000\000\310\306\033\"\000\000\000\221t2\313\353\3440X\241}\000\000\000\000\000\000\000\000\000\000\000\342\261V\034\000\000\000\000\000\000\000F\343i?\000\000\000\260`.\013F\342~G\003\357e\001\000\220\315\320\003B\216\023\000\220\222\256\317\250\334\306\000\000\000\340!\026\023<\215\252\005\000\000\010fj2\323k\262\023\000\000\000j\231\226A\004\303\364\344a\016\024\270x\r\207:2=\300~r\'_zy\177\377z\034\225q9\367r\032\235\253\254\336\016\034i\340#\333\322J\321\n\323\251\301x\346\337\002V\235\224\215\257=^\307\037\310I\241\001@^\337\343\002\343\203\r\277\257\311\273+k\210\013\372\356\263\312e\2106<D\000\374\023\261J\"\236\023\300\013<\222N\343\217\035 \243Gz\377\2154\020\307O\303\357\010\201\035\277J.B\003\272\247\233\322\324\357\200\362\006\020/\"\257eV\266\276\211\352\231\312\332g0\237\r\250\313\000\000$4\374 p6\033\341\211a\374\274=\323N}+M\233\3371x+\013\342\250\257\2664\326\253h\375\'\304\246\345\001\000\030\325\306\344w\323\321\355\007\261u\223\000\000\000\200\230<\002JJ\303\247\264p\373g\241\010\200\030\\\354!\022=\232\372\201{\375\226\001%\251\214\317\214q\313\032S\340\227\001\000\222\010x\371\nxJp\202(\323\302(\347\001p\036\231\263\324\272FZ\357\257\261\303\203\347\303;\000\030\216\314\007\000\361t>s\343D\376\021BJR@j\223\000HM\322g\233\034\001\231\271N \n\022x\340\303\277\242 0C=\000\000\310\304\014\200\277L\370\001\000\000\000\000\000\000\202\260$\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000.\276\324\010\000\000\000\000\000\000\000\000\000\000iYL\014\000\000\000\320\265\017\265\301<:\355\356j\346\000\000\000\000IEND\256B`\202"
+byte_jpeg: "\377\330\377\340\000\020JFIF\000\001\002\000\000\001\000\001\000\000\377\333\000C\000\003\002\002\003\002\002\003\003\003\003\004\004\003\004\005\010\005\005\005\005\005\n\007\010\006\010\014\013\r\014\014\013\014\013\r\017\023\020\r\016\022\016\013\014\021\027\021\022\024\024\025\026\025\r\020\030\031\027\025\031\023\025\025\025\377\300\000\013\010\002\000\002\000\001\001\021\000\377\304\000\037\000\000\001\005\001\001\001\001\001\001\000\000\000\000\000\000\000\000\001\002\003\004\005\006\007\010\t\n\013\377\304\000\265\020\000\002\001\003\003\002\004\003\005\005\004\004\000\000\001}\001\002\003\000\004\021\005\022!1A\006\023Qa\007\"q\0242\201\221\241\010#B\261\301\025R\321\360$3br\202\t\n\026\027\030\031\032%&\'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz\203\204\205\206\207\210\211\212\222\223\224\225\226\227\230\231\232\242\243\244\245\246\247\250\251\252\262\263\264\265\266\267\270\271\272\302\303\304\305\306\307\310\311\312\322\323\324\325\326\327\330\331\332\341\342\343\344\345\346\347\350\351\352\361\362\363\364\365\366\367\370\371\372\377\332\000\010\001\001\000\000?\000\361\315\336\364\365z~\372z\277\024\340\364\340\364\241\351w\322\357\245\337@zv\3727\323\204\224\365\222\244\022r*e\222\245Y*U\222\245Y*E\222\244Y*U\222\246I*Uz\231\036\246W\251Q\352ezxz\221^\244\017N\017O\rJ\032\2245.}\350\335\357F\377\000z\013\322n\243u4\2651\2151\215FNi\255\326\232M6\212\t\342\233N\355M\017\264\232a\240\364\246QJ:\323\301\305:\212)A\257\036\240\034S\203S\325\251\341\351CS\267\322o\245\337F\372P\324\355\324\273\350\337N\022S\304\2252KR\254\265*\313R\254\265*\311R\254\231\251\026J\231$\251\322L\324\310\3652\275J\255S#\324\212\325\"\265H\032\236\032\236\032\235\221F\354S\203Q\272\223u\033\251\013P\032\202i\204\361M=)\264\323\326\230\335i)\t\305!9\242\214\323OZJF\351M\242\227\2458t\245\315(\030\024\345\031\243\251\257\035\'\002\232M\001\252M\371\245\337J\036\224=\033\350\337@zxz]\364o\245\335F\372p\222\246Y8\251\026J\221d\251\222J\231e\251\022J\231d\251VJ\235\036\254F\365:=J\257R\243\324\312\365\"\275H\255O\rN\rO\rK\272\2245.\357zB\324n\244&\2245\031\244\'4\322i)\244b\230\324\224\215I@\024u\246\265%#Sh\242\236:R\216\264\352wN\235i\017\002\274p\236i\207\255%=[=iA\305\033\2517\321\276\215\364\007\3058IK\346{\323\203\322\357\244\337J\036\244I*A%J\262T\253%L\222T\312\3652=J\257S\306\365f7\251\325\352Uz\225\036\246W\251U\352Ez\220=8=<5.\352]\336\364\355\324n\244\317\275\0314\240\322\320zSh\244#\"\230zSi\t\244\245\035(\003\212CL<\032F\246\321J\0074\352p\024\340(&\222\274u\272\323Z\232x\024\003\232\\\232Bi7Ro\243}&\352P\364\355\364o\243}(zpjxzxz\221^\246Y*U\222\247I*d\222\246G\251\321\352\314oS\243\324\312\365*\265L\217R\007\251U\352@\364\360\324\365jpjP\324\340\324\273\2513\2323\212u.M\031\242\212)\207\275&8\246\3434\3229\247QF9\246\021M\246\221\315\001i\300S\2058t\245\355M\2445\343\354)\215\322\232zSiA\346\202i\215L&\223v;\322n\245\rN\rK\276\215\334\321\276\234\036\234\032\236\036\236\217S+\324\252\3652IS\244\225:=N\217\305Y\215\352\302=L\255R\253\324\312\365*\265H\255R+\324\201\351\312\325 l\322\206\346\234\032\2274\003N\0074\340i\324QE\024\3229\244\246\221\212LsK\2121IM=i\244RR\201J\006)qN\351H}))\255^H\353\212\211\205FE6\212)\204\323\t\246\223M-I\273\336\2245.\3527Q\276\234\032\236\032\234\036\236\257R\253\324\213%L\217S\243\324\351%X\215\352\314oV\021\352dz\231^\245W\251Q\352Uz\221^\236\032\244\rR+S\303S\203R\203\232Zr\232\220\034S\250\240\014\322\355\243\024\3223HE%&9\366\245\240\364\246R54\363@\030\245\245\002\227\024w\244=))\204\346\274\256E\025\003\255D\313L+M\246\340\232CQ\265Fz\32250\223@jR\331\243w\002\223u9Z\237\272\234\255O\rOW\251U\352dz\235\036\247G\342\254F\365f7\253\010\365:=L\257R\253\324\250\365(z\221Z\244V\251\025\252Ej\22058\032pjp<\324\213O\035i\364\001\232u.(\3054\2574\021M\300\244\013F\3321M#4\332B)\000\315;\034Q\212\010=r(\316)\t\030\036\264\323I\221^q-\230#\212\245%\273)5\003G\212\215\323\025\023\014Si\247\255F\302\243=j6=i\244\323w\0323Fh\335@jxn)\301\251\341\251\312\365*\275L\215S\243T\361\265X\215\252\314mVQ\352dj\231Z\245V\251U\352ej\221Z\245V\251\024\324\252j@i\364\341\322\236\247\245J:S\326\236)\324\240f\235F\r\030\024\204R\025\246\355\243m!\024\3221M\"\233E\024\023\203H1\232BsHN(cM\256\035\324\201\310\252\262\250\252\263[\356\031\035j\234\2210\252\354\274\362)\205i\204sQ\265F\302\243a\326\243\"\232x\246\346\214\232JPqN\rN\rO\006\236\032\236\246\246F\251\321\252tj\261\033U\230\332\254#qS\243T\250\3252\265J\255S+T\252\325*\232\225Z\246SR)\247\016\r<\032\221ja\322\244Zx\024\360)\300f\227\024\355\264\230\244\333HW\212B1HE4\212i\024\303\3054\322QH\324\322qKMc\306):\321\\\274\221\007\342\251\317g\351Te\211\223<T\014\003\014UY`\353U^\">\225\013.\rF\313\232\215\205D\313Q\260\250\330sM\"\222\203E\024\365=)\364\340jE\251P\324\310jt5f3Vc5:\032\235\rJ\255S!\251T\324\310jU52\232\225\rL\246\245SR\003\232p\351R-L\225*\324\240S\300\3158\nx\024\273h\333I\266\220\212i\024\204SH\342\243n\224\303\326\232Fi))\255\315!\351\212L\322R\023@<W:\342\241c\212\216H\226A\315gOdW<qT\3326S\317\"\240\270\217gN\206\252\262f\240x\360x\250XTn\265\023\n\215\226\232E3m%\024\240f\234\005>\234*E\353R/j\231*x\352\304uf3S\255N\206\245SS!\251T\324\310jU5*\232\231\rL\246\245SR\255=jE\0252T\313R\250\251\000\342\236\005<-.\005\030\024m\246\221M\"\232E1\2526\2467Jm4\322\036\224\323HFM4\214QI\336\214b\260\336:\256\321\363Q:\225\250\235\310\315T\237\007\234\n\247*o\004\036\265JHJ\216\225]\227\326\241t\250X`\021\212\205\222\243e\246\025\246\225\244+\212LsK\266\234\026\234\253N\305=EH\202\245QS\240\253\021\325\204\251\326\246SS-J\2252v\251R\245J\231{T\311S-J\265*t\251\026\245QS \251\324T\252*U^*EZpZ\\q\322\227\024\204SH\3054\212a\025\033\n\211\273\323)\264\322))\247\255\006\232z\322Q@\031\254\271#\252\357\035A*\340UI\026\252I\031\301\252\3540j\'*3\307\025\003B\222t\252\363Z\221\234U9\" `\325g\371O\"\232@\"\232S\232c.\r4\245\033(\tN\013N\333\3058-8-H\253R\242\324\310\265:\n\260\202\246AS(\251\220T\312:T\251\332\245Z\225je\355S%L\242\246Z\225z\324\252*T\025:\n\235\026\246U\251TS\300\247\001K\203E!\024\322)\264\302*\'\250\230sL<\032i\353Mn\224\332C\326\233\202\0174c&\223\006\200)\325\236\353\305Wu\252\362&j\244\251Ud\312\325YG\265U\221j\273\251\035)\004\244S\034,\200\372\325s\020^\010\342\252\315o\345\234\216\225\031Zi\216\232c\243e(JQ\035.\316)\353\037\024\340\224\360\225\"\255L\251S\"\324\310*d\0252\212\231\005L\242\245QR\250\251TT\312*U\0252T\310*U\0252\212\2325\251\321jt\0252\212\225E<\np\030\245\244#4\332i\024\323\326\243=j7\025\023S\030SZ\232i\224\021\232B)1E\000f\224\n\247\"\340UGZ\205\305A\"\023U\336\017\230\344Uy \374\252\224\260\340\232\254\351P<U\021J:\360EF\351\201\323\"\240{p\334\212\205\243 \363I\345\321\345\322\210\351\336](\216\236#\245\021\323\304t\365J\225R\245D\251UjTZ\231V\246E\251\224T\252*U\025*\255J\242\246QR\250\251\224T\252*t\025:\n\235\005L\202\245QR\255<\np\024\270\246\32251\2150\365\246\036\265\033\n\211\251\207\221M\246\021\212B)(\355\232LP\006(\245\0035Zd\252\255\0375\023FOja\214\n\2511\332j\006\301<\325y\241\3108\351T\036<\032\210\307\232\215\242\250Z:\210\202\246\220\306\033\221\326\220\305\221\310\250Z\035\247\216\224\010\363J#\247\010\351\302:Q\035/\227N\021\323\326:\225c\251\026:\221R\245T\251\025jeZ\225EL\253R(\251TT\252*U\0252\212\225EL\213S\240\251\320T\312*e\025*\323\307J\222\234:QH\302\230\324\306\246\2651\2051\273\324ML=i\244SOJm\030\243\024\230\244\332i@\305-E>\027\202*\262\343\1775\014\355\266B*\234\254s\203P<d\234\236\224\326\2140\342\253\272\3558\252\323C\337\025\t\214To\035@\321\324O\0205\t\214\255\000\236\204S\214\001\227#\025\030\213\006\234#\245\t\201\322\224GN\362\351|\272p\216\236#\251\026:\221c\342\236\251R,u\"\245H\253R\252\324\252\265\"\255H\253R(\251\224T\250*d\0252\212\231\005L\202\246Z\231;T\213\322\244\035)\364\341\322\212BsLja\353Mn\264\225\033TL)\215M=)\235i6\320E\030\300\244\242\212*\'\234J\010a\317\255Ub\001\250e\303\034\324O\020cP\274d\214TY\301 \212\211\343\334sQ\310\234b\252\264x5\031\2174\326\267\357Lkn:T/\001\035\252\026\203\2769\246\204+J#\245\021\322\204\305;\313\247\010\3704\242:p\212\236\261\324\213\036\010\247\210\351\302:z\245<%<-H\253R*\324\252)\340T\212*U\025*\n\231EL\265*\324\311S/j\225{T\213R\016\224\340sJ\016(\'4SOZa\353MaIQ\265F\335j3\322\233HG\024\206\212C\322\233E\024\3401U\031B\277\006\243d\005\261A\203\007\332\230\361\014\234t\250\3320\005R\226\022\030\324L6\214\032\211\315@\343&\243\333J\242\236\024SL\001\252\t-q\332\241h=\251\236V;Rm\000\340\323\225\001\342\235\345sO\020\323\226\032\177\225N\021S\204T\361\035/\227N\tN\tO\tO\tR\005\247\205\247\201R(\251\024T\252*U\035*e\025*\366\251\026\246Z\225MH\rH\246\234\r:\212Bi\264\326\353IM#\024\306\035j6\246\021\203M\"\222\232GZJ)6\2126\322\321Y\242L\032\234\025l\034\363J\316\275\315D]H\340\324.s\221\336\241pH\252\322 \034\223\223P\260\007\322\243x\2062\r@W\007\332\215\270\351J3R \251\n\206\352)\257l\030\014\n\256\320\020zT3Zy\234\343\232\214\332\2748=j\304i\271EJ\"\247y>\324\242*p\212\224E\305(\216\227\313\245\362\351BS\202S\302\323\202\323\202\323\300\247\201OZ\225jU\353R)\251T\324\200\324\252jU5\"\232z\232~y\305;4\026\240\234\212J1\232B8\2445\023u\250\330S\010\342\233HFi\270\344\320Fi\264`\322\355\243m.+)\327i\250\344\233oJ\205\246\'\275F%\307zk\316s\326\205\227wz\212g\343\255Uf$\320X\201Q\254\233\233\006\245)\307\024\252\274T\212\270\251\024dT\250\224\343\006\352j\300\001\351R\275\240e\316+=\3410?N*e@Fi\342*p\212\217*\227\313\243\313\243\313\245\362\350\331K\262\227m(\036\224\340\264\240S\300\247\201R/Jx\251\024\324\212i\341\252Ej\225Z\245V\251\024\323\324\343>\246\237\221E\000\321J\006h\3074\323Q5F\303\232a\030\244\243m7\034\323H\305%.\r\004b\214Q\203X\006r\303\255F\3075\0218\357Q4\230\250\231\363I\347`To?\2750I\315J\256\010\250\310\031\310\251\021\216*U^jP\264\365Nj\304kS\252f\236\"\006\244\021\235\270\3075\r\315\272\311\007NEP\205\010;MY\021\323\304T\276]\036]\'\227G\227G\227I\262\223e\033h\333K\266\227\024\3401N\003\024\361\322\234\017\024\360i\301\251\340\363R!\251\224\324\252jU5 4\264\340sK@\0314\3721L=*&\246\021\232a\031\246\340\321\212\010\346\230E\000QE \034\322\327\037\347\340\324\242\\\216\265\033\275Wv\250\313{\324n\370\250\213\344\322\027\332jA&\006i\310\333\252Q\326\245Y01R$\204\232\267\021\311\002\255\252c\265L\213S*f\244T\3004\325\214;m\354j\235\325\247\331\345\004\016)\310\231\024\361\035;\313\2441\323|\272<\272\nSJR\024\244+I\267\332\215\264m\245\003\024\264\341KN\024\341O\006\245J\225MJ\2652\032~i\300\323\207Zu8t\247\001F0\r1\205F\302\243aLjJZi\034\212B)1F9\244\306I\244\035i\340W\003#`\324\221K\221Of\342\240f\346\243&\243sP\223\3159\362@4\302\347\0254\r\221W\023\221HN)c\227\006\256E>0A\346\264!\270\016\006z\325\221\201\3105<G\'\025r8\362)\215\010G\342\213\333\1776>\234\342\250\300\274`\365\0258\216\227\313\246\262SvQ\262\220\2454\2454\2454\255!ZM\264m\024`Q\212)i\303\2459i\313S/J\225jU\251\227\2458\034\032p<\323\307Zx\031\247\001\232x\034\212\rF\303\255F\324\306\024\302)6\321\214PG\"\232\313I\266\220\214Rw\245\002\234\005y\353\232lm\203R\226\250\235\271\250\313Tn\371\246\250\251@\310\"\241t#4\260\266*\342L6\323\032L\346\232\037\232\23679\255\010&\371pj\344r\345z\325\270%!\270\255\213V\022\n\222H\376`EH\361\356\217\221YrE\345O\354jP\271\024\245i\205i6Q\262\220\2454\2450\2457m5\226\233\266\220\217jB\264\224QN\034\212Q\326\236\265*\324\253S\255H:\032p\247\016\225\"\365\251\007Jx\351N\307\"\220\212ku\250\310\250\330S\017ZJ)J\362(+M+I\266\221\223\004S\202\372u\245\003\265y\313\034\323A\245f\300\353Q6ED\362\001\300\246o\315(5:\020he\334\017\2550Fi\300m\024\322i\321\216j\354`b\245\363\025GZ|S\373\325\350\'$\216k^\302\340\253\202kL]\2430\002\257!W\214V~\245\006\302\255Q\240\342\202\271\244\331I\262\227e4\2550\2554\2550\255#/\025\031ZB1L\"\220\364\246\321N\035)\303\265<t\251V\245Z\225jAN\035*E\025\"\324\200S\300\346\236)\010\246\221Q\260\250\330S\010\246\225\245\3058\n1I\2674\005\240\2574\230\307\"\227\255y\226\372i|TfB\307\024\311f\371\261P3\363B\266jP\307\024\326\224\251\251\341\223p\311\251wTls@\0314\346m\213L\027eF3C]\347\025$W&\257\333NA\353Z\226\367eqZ1\026\220o^\325\243ix\337w\322\237}rfP=)\260\260e\030\251\266Q\262\215\224\205i\205i\245i\205i\273)\256\265\021\024\3021M#\024\3021M\"\222\234\006)\313R(\251W\265H\242\245Z\222\236;T\212*E\025*\212z\212v(#\232i\025\033\n\215\205FFi1N\351J\006iq\221F\3320)\254)\240f\235\214W\225\261\346\230NM+\021\032\344\365\252\216j<\323\323\223V\024qQ\310\2775X\210m\\T\230\342\243\3178\247\240\346\222a\225\252n\204\214\323\0009\253P)\310\255\033u\306+N\3353Z\326\254cR\007z\267h\273\230\325\304\215H \365\250\"\006\t\212\236\235\252\362.E;e\033)\nS\nS\n\323\n\322\025\2462\324%qQ\221L\"\230E4\214RQO\002\244Z\221EJ\275\252E\251\007J\221jE\025*\212\225E=E;\034PG&\232Fi\214\265\033-FV\223\024\3401J\243\255.\336(\333\315\030\024\326\024\335\264\240W\223\274\200\232\214\310#\347\251\250d\224\277&\231\2734\303\322\244\210d\325\225\030\243n\346\251\202\340S\3526\030\247\3062)\322/\313U\230\002\244Tk\036MZ\210\005\253\326\352[\234V\245\252\364\255H\323\013\315hX\303\236GZ\272\360\344n\003\245V\270\217r\206\035EX\266;\320U\215\224\233i\n\323\n\324l\264\302\264\205)\214\265\013\255D\313L+Q\221L\"\233\266\224\014S\200\251\024T\213R\250\251\007Jx\251\026\245QS-J\242\236\264\3521\326\232E4\214\324l\264\302)\204b\234\007\024\016\224\240R\201K\212aZM\264m\257\036\334)\030\202*\'=\252<\363N\251#\025:\034\324\243\255K\232*6\344\324\260\364\247K\302\325Q\311\247\005\300\315>>XWA\247B\004C5\240\221\000x\025i\024\221Z\026G\313\255{E\022\344\366\252\3276\376\\\205{\032\202\331|\271\n\032\274\027\"\202\264\322\264\306Z\215\226\230V\220\2551\226\242t\250\236\007\035\252\022)\214\265\031\024\335\264m\247\001\232z\212\225EJ\242\244\002\234\005J\242\245QR(\251V\244\024\360:{Q\212M\264\322)\214\274\324l)\204QE(\024\3403JG\006\232Fi6\321\264\327\211<\2704\213&iKf\233O^qS\240\310\251\024`\324\252i\364\016\264\326\353SD:S\245\344Ur\2704\3420\265-\234E\344\034q]\035\262\355QWc\031\253Q\n\271\022\364\255\033\"VJ\236\3617\000\336\225\237!\304\252\325~1\225\024\342\264\322\224\306Z\215\226\231\266\220\2550\255F\311\221J\007\313\214\3259#\330\346\243e\250\212\323J\322\005\247\005\247\201R(\251PT\200f\234\005H\242\246QR(\251TS\300\247\201F):\322\021\221Lj\215\273\323)\010\244\247S\207J\\Q\212M\264\233Mx3>M>3R\322\205\247\255X\217\265IN\006\234[\024)\346\232\347\rVa\351C6Z\232W\232k\036\325\253\245\333\374\233\210\353Z\321\256*\334*j\364iV\342\031\305]\210maV\331r\274\325\tb\341\275\252\315\243n\214z\324\373i\n\324l\264\302\224\302\224\322\265\033-0\212h\035j\274\313\227\250\231j&ZaZ6\322\205\247\205\247\252\324\212*AO\002\244QR\250\251TT\200S\300\247\201\223I\353KM=j6\025\031\024\302)\010\342\220\np\031\247\201\232P8\244\002\227m\033k\300:\324\212qR\251\310\2513J:\324\250\3250jz\320\374\nj\036z\320\307-So\331\036j8\245\334\365g\250\246F\233\345\301\255\353B\025@\255\010\271\253\221\n\266\010QSA(&\264-\016\363\232\320\333\224\2523\251W>\206\2133\345\310T\326\200\\\322\024\246\024\2462S\nTl\230\250\231j6Zn\332\201\327-Q\262Te)\205)6P\026\236\026\236\026\236\005<\014S\324T\212*U\025*\212\221E<\nv0\r(\024b\243jc\na\024\302)\270\243\006\234\0058\014S\200\342\225Fiv\320V\276{PH\251UjA\305\033\251\340\323\321\363S\251\316*el\014\322;\356\244AO\333Nq\224\252\340l\220U\370\316V\236\253\206\006\264\355[\030\255Xq\214\223R\033\220\274->9\313{\325\270\t\365\342\267t\325\302\214\326\232\200x\250\247\267\310\351T\304GwNE^\201\211\030=jb\224\322\224\302\224\306\217\025\003\255DV\243e\246\025\250\231)\205*2\224\302\224\205)6S\202\323\202\323\302\322\201OQ\232\225EH\242\245QR(\251\000\305.?Jv\334\032B*6\024\306\024\302)\245i\273h\0034\240S\202\323\200\342\234\213N\300\240\255|\376\211\3058\215\264\270\3150\216h\335\316)\312pj\324g5&h\315I\020\357J\347\232z\234\365\246<;\206GZ|Rm\340\325\220\340\212\265k&2\304\363V\305\311=:U\2302\335j\354m\214\n\275l72\217z\350l\206kB>jm\241\305\"\300\013r*W\266P\233\207Zj.E+GQ\262\342\241qP\262Tl\225\033%0\2450\307Lh\3523\0354\307L)M\331J\022\224-;m(Zz\212\225EH\242\245QR(\251\000\245\013\232R\274\322\025\246\021\212\214\212iZiZM\264\005\342\200\276\324\270\247(\247\240\342\237\267\332\202\265\363\310z\220\035\302\230_i\245V\r\326\224\203N\210d\363V\203\005\034S\225x\311\241FMXP\025i\215\326\234\247\232\264\2106\212\n\002zP\320\020\001\025-\272\222\333}j\373.\300\242\254\301&\000\253\320\215\313\270\326\225\222\347\346\255\273V\332\242\257E\'\025j7\030\253\t\203V\305\271\222\022EWHJ\032sG\305A\"sQ2TM\035D\311Q\264t\202*C\027\265F\321\324M\0350\307L)I\345\322yt\241(\333\355@J\221R\244U\251\025jEZ\225V\236\006)q\232V^@\246\221M+L+M\333HV\223e\033(\333F\312P\265*\255;m&\332\371\315M?8\246\236M=F\336i\341\263ON\265/qOg\340\001R#m\247\207\244f\245S\315ZG\371i\352\t51?--\227\315=h\317\215\271\244\200\363Zv\315\270b\264\254\006\027\236\346\265\242j\273\013qV\243j\225f9\255\033+\254\034\036\206\254\313\026\017\037QU\317\'\030\250\331*2\225\033F}*&\216\233\345sN\020{S\036,T\016\200\n\201\261Q1\024\334R\354\243e\'\227I\345\322\210\351\341*EJ\220%H\251R\254t\355\277\235\033I\024\245~o\302\232\353M\331\3054\2557m\005h\331I\262\220\255\001)v\363R*\323\366\322m\257\233\026\235OAO&\220\003\232\235\006\005\014\370\351\326\225\0079\247\027\346\224IR\251\310\247\003\315M\023\325\204l\032\225\210\333Kb\333_5ry~Z\222\335\270\255\033R\003\036}\253V\325\261\212\323\205\352\334rU\210\237\236M;v\306\2536\363\374\303\025\277\001\017\022\236\365\034\310\025\307\024\307\003m0C\222OjkG\315F\321\3236\200y\245<\375\321\232\212H$<\355\2527\010\331\301\342\230\232t\223\014\203\3051\364\231\201\340\325i \226\023\3104\325\233\261\251Q\201\251\225sK\345P\"\247\210\251\353\035=c\251\026:\223h\003\031\3054t\342\234\240\251\342\215\234\232F^)\233i\nRl\240%\033h+M\331@J6sR\005\247m\243m|\317\273\002\224rje\004\nURjd\217\326\206ni\252\233\233=\252F`\006*\"\340S|\332\225f\305H\'\025$s\n\262\'\030\353R$\273\270\2530\341MO($dt\251m\333#\351W-$\3435\261m.\000\315^\216N\2075r\t\207\025f93\323\245=\344\300\253\232Z\231\236\2728N\334\016\302\222\351\274\307P(+\322\224\216*& TG.x\251\022\304\260\311\346\254\307i\267\031\025bHQ\2418\034\212\307\222\301\245|\355\340V\215\265\210\362\361\322\253\334\332\2721\302\325\t`\3638+U\344\322C\256@\254\331\354d\266bG\"\210f\347\007\255]A\270T\242\032w\225\212Q\0258E\212pLPS4\001\212P)\312\201\263M)M\333AZn\332\021pM\033sAJiZ\002P\023\232\220-.\337j6W\314-\327\0254I\232\234\250\002\223xZ\004\204\323\206\010\346\221\246\n0*\007\233\236\265\021\230g\255 \224f\223\355\031<T\251.j\324\022.pj\304n7\232\271\tSV\221px\253(\300\214\032@\336PoJ\263b\304\214\366\025\257\023\215\240\325\310e\311\353V\321\370\353V\341\227\003\255Jd\335Z\272$\201e\301=k\246\2120\303\216j\031\007\225)\004S\203\2023L\222`:Sc\204\314rzU\273{\022\354\000\031\255\253}8*\r\303\232\230\331F\027\346\034TMom\310\310\037\215R\271x\243\371W\004U\t.\374\263\221OK\246\237\000\016=ju\263I\006p2i\037M\332\271^}\252\215\316\227\346\203\362\326-\316\214\310\304\205\250c\215\3428\"\256\304\003U\201\016iD\030\2441\323JSJR\004\247\204\240GJS\212O.\232R\223e\021(\336wt\3057h\'\212]\224\322\224\004\245\331O\331F\3126\342\276_+\315M\027\002\234\3474\305\033\217\265<\220\243\002\230\322\343\275@\362T\014\304\346\243-\201\326\230\323\205\030\024\304\270\031\251\243\272R~\360\251\243\270UpwT\342\364K.\330\317=\353J\t\n\016\0338\255\010n2\2314\350.|\327\306y\025=\324\241#Q\236I\253\272L\203\313pj\374Rg\201Vc\233oJ\263\034\345\210\253\361I\201\326\245\023{\325\313I\335\034\021]f\237}\271F\356\265\242\n\317\367\2274\215l\2528\006\230\232l\222\267\nkV\317Ge\003w\025\247\035\240\204\002\000\000w5\025\336\240\020mN\276\265\2355\363\270\"\250\3113\214\234\326u\303\310\315\234\324E\333\275\\\264\271\331\216\005_\032\222\241\000\200j\324wbl\005o\300\326\242B\0360J\216j\t4\205\220\034\n\312\276\320J\344\205\254\231,\036\334\344\016*XpG\275Lc\030\246\030\252&\216\232c\244\021\323\266b\224&iJqM\331L)@\216\232\023\004\320\261\322\224\246\024\243m(Jv\321F\005!\025\363\031\207\024\365\2174\331x\030\025\032\374\253P\273\223P\273\036\325\021c\236j\t\256\226>:\232\250n\313\237jx|\255@\344\344\342\205\030\251S>\2654Nbp\312y\025\253mx_\007<\367\025|\336\355!jM:|\335\362x5\241\2527\357\"\301\253\266Rl\003\007\255^\212@\016j\302M\353S\305>\326\253Ix\007\031\253\226\357\346s\232\327\266p\2523Z\366\027#x\346\273\0355\342h\301\343>\365\246\202\'a\362\256E[Y\341\204e\312\375\0054\352Q\020|\244\371\252\255\305\343\260\3015\234\312X\365\246\224\305D\321g<Ui`\252\217\t&\232\250T\324\241r2GJ\236\027\332F\0075\273\247\335\235\241X\234V\314\005X\214sWR\331e\0048\353To|>\245K \310\256_Q\322^\331\213\250\342\252\304\341\270=jM\231\246\264u\031\216\201\035.\312_/\024\206>)\276_4\215\036)\002qQ\264y4\340\230\024\326\030\250\330\200i\245\205\033\351wf\223\'\322\227k\036\325\363l\240b\243-\306\000\250\235\017z\206V\300\305Vy\002\216j\263\334zUi\256\t\035j\204\317U\314\2705$w&\245\3633\315(l\324\253 \024\341-M\014\373\016{\326\204R\007\217,y\246\245\323C e=+n\336\344\3370s\321Ei\371\236\\(\300\365\353SAu\2209\253q\317O3\342\226+\222\322\201\232\354\264\255-\347\263\363T\344\016\265\"\304\352\3705\241lV!\223\232\331\262\324\037\205L\327Eg\024\322\000\314H\025tG\014|\312\347\351K\366\204l\354 \n\211\233q\353I\260\nP\271\245(*6\2105B\366\243\322\243\373(\364\251c\264\004t\247\013\002\016@\253\021\304\312\000\305l\351\247n3\326\267 a\273\236*\342\251\034\365\025CP\323\222\355Xm\003\"\270-^\301\264\373\202@\3434\226\354%\\\324\206<\323LT\202:p\210\nk\'\"\225\243\342\231\262\230\313\2126\214S\n\014\324R8QU\036R\307\013\315=-d\222\237\366\006\035i>\317\315O\035\241\307Jx\265\365\246\233r\005|\316\321\212\215\200^\325^g\252s>\321\223Y\322\310X\325w5ZF5VSU\230\322\006\305M\034\270\247\371\274\323\204\264\345\222\247Ij\3147\007\033GS\322\231qr\321\235\204u\256\257D\001\355P\251\346\256_I\265\021?\032-_ V\234.\000\246\31575sO\204I\2065\330\351:\240\266\267\362\331\210^\340T\217\253!\223(\270\255->\361g\341\324\021\353]F\227\035\267Ta\307^j\375\316\261\014\021\354\214\344\373Vg\366\213J\371,M]\202`\303\255]\210\347\275K\234\n\024\323\217\326\200i\335z\322\210\203T\221\305\212\230.\323S \007\265Y\210\205#\265li\350e g\212\336\026\270^\230\342\241\226\000\007\245r\376#\323>\320\204\205\344W\031\3455\224\270\376\032\277\024\211*\360y\240\2504\004\002\232\330\246\344\021\357Mi\024\016\265\003\316\253\236j\273\334\022x\006\232f\177\356\232\211\345\224\364\024\211m,\315\222\016+B\323O\003\005\205^\020\254t\331\006z\n\200[\344\364\251\226\026\003\245#@i<\203_-\025\342\240\220UYx\254\331\330\261>\225RN*\273\363U\344\342\252\3103P0\246\021\203FiC\032]\364\345\222\245I*Xn<\271\001\253\027S\tP7|\327Q\240\312>\314\006{U\253\213\324-\264\200H\247\301>G\025q.\n\367\246\033\215\362\343\275lZM\344 \2558%i\007\025z\312\331\356&\013\320w5\270dKX\374\2449>\264\370/$\214aX\214\325\250\356\031\210\311\253\326\362t\346\264\355\346\3069\255\030n*\322\315\221N\363E/\231\357NI9\251\203\003SGS\201\212z(52&\rH\020\365\253\266wf\331\201\315uZu\374w1`\237\233\247\326\246\222\334\222x\2527vbE9\034\327\037\254h\3409\300\256z]2h\3331\346\232\"\272Q\367sM1\3351\341\r\013ev\347\241\247\377\000e]\361\326\236\272\r\303\237\230\232\267o\240\017\342\353W\323\303\252\313\220\234\nsh*\247i\217\255B\372\032/\360\212g\330\202p\026\232m\034\366\3058Z\0009\315\r\002\212a\210\016qM\300\024\215\322\243\'5\362\243\325y\016*\224\347\203Y\322UIj\006\250$\031\252\356\265\013%D\313L\305\030\246\265\000\342\244V\247\023S#\251\210\344\234\326\326\213\250|\230\317J\236{\255\322u\3475\245e.TU\326\223\345\252\361\317\211\253~\306_0\014\212\337\264\001@\025\250\227^Z\355N\017sOG$\344\232\263\023\325\270\246\003\025z\t\272sZ\020\317Wa\230\325\245\234\201S$\371\251Vjz\314*d\223=\352\324-\357Vs\357OG\253H\331\025:6iYy\255m\036q\014\212I\342\272\264\221&\213*Fj\264\213T\256\264\370\356:\216j\224\232*\001\367A\252si\213\0318\216\232\226q\201\314u\033\300\021\301U\375*\322\303\347(\312\014\323\326\300g\240\240\330\252s\212Q\"\304\245GCUn/\001#\004\234T\002\361H;\207\025\004\2270+q\326\2315\312\0206\212\256d\310\342\253I\'5\003K\357B\266h-\316)\207\255|\256\353UeZ\316\272nqT%<UG\031\250\331x\250Yj&J\215\243\250\232:\214\307M)Q\262\323J\322\241\247\343\212\257uq\366x\313\003\310\246\370wQy%b[\255t>c<\2035\261g.\024U\2636EWi6\276k\243\322g\037gS[\326\227\001\272V\214\017\315[F\351R\211{\n\261\013\223Wa\223\004V\214\023\014u\253\221\335*\325\210\356\301\251V~z\323\305\307\275(\2719\353Va\271\367\253\261\\t\346\255%\317\275J\223d\365\253p\315V\342}\325a\006\352RZ&\004\036*\375\256\271\344\220\273\215n\331\352Q\335(\317\006\256m\004dt5\013\361Q\260\007\265F\366\341\207J\253%\253\026\3501SG\010A\323\232\227`\025V\356\345\023+\322\262\'\221\233$0\366\346\263nD\301\263\234\325+\206p1\272\240F#\251\251D\345i\317p\n\361Ud\227\320\324&Jr\315\212x\223u\005\270\257\227\034U+\211\000\004\n\313\237\223U$Z\205\222\243h\352\'\216\230c\250\232<TL\265\023/4\306\025\023\212\215\2052\236\033\212\305\327\256vB\300T\236\014\204\312\373\230q]s\361>*\375\273`\n\266\017\025\014\355\212\335\321\344\006\325k\242\264\235c\\w5~\t\363WR_z\261\021\004\362j\354S,G\221\232y\270\334\331Q\201R\307rj\344S3U\250\345#\255YI\375\352E\227&\244Y*x\245\305\\\216j\235\'\307z\263\035\300\253pO\236\365\245n\371\305hD\303\326\222v\317J\241(*\331\006\257i\232\237\225 \004\327ogv\223[+g4\255\206\350j\026\034\322\006\305\034R\034\016j&\270S\307\245d\352E\230\022\275+\"Y\0368\316EV[\206c\317\024\331@|\234\346\242\020\361\232\212D\305@\306\242sQ3R\006\241f\332i\3170\257\230n$\'\201\322\263\3465Q\306{TL\225\021Jc%Dc\315>;A $\234\001U\'\214\0068\351U\231j\007\025\023\365\250\030\363Q\261\31574\036\225\201\255!\222@+\240\360\335\260\264\264\334:\342\266\027;\301=j\344-V\343$\212{\303\274U\373\005x\225\177\273ZQ\\\025|\347\212\324\265\273\334G5\255\004\333\252\345\273\000\3375^\371_\356\3475<@\201\310\024\354\220\331\305\\\267\272T\034\324\246\3441\342\244I\252U\270\307zz\334\325\210\356\300\357VR\354\036\206\247K\234\367\253\021\314I\253\220O\267\034\326\265\255\320\332*\354w\031=j\304o\272\243\232\252I\230\306\352\331\360\366\264\312\376[7\313],w\300\236\265+] \004\356\252\346\365\007\\Trj\360\251\3015Fm\\\310HN\225X\3371=y\365\252\3677\217!\0377\002\253\3110e\347\223UYw\036*T\213+\311\305!M\243\257\025V^MU\223\212\211\272T/Q\206\301\244-L/_5\315\315R\225rj1\036A\250Yj\027J\214\2554\2551\211\003\025ZE\252\322\014UY*\263\265B\315Q\023H\006MI\217\226\2615\023\231\306k\240\321\263-\270?\302+dD\276\225<J\007\025n0=jp\350\275M]\267\2342\377\000\262)\223\337,@\214\363V\364\353\242\370\346\272\013Y\360\0075\241\024\345\271\253\326\367\\\325\344\270\367\251|\340GjO3\232\225\037\035\352e\227\024\357?\336\227\316\367\244\373Q\007\255X\206\350\325\373y\311\255+yCU\330\230V\205\264\200b\257G(\315[\216|\016\264\2179&\234@\2251QD\237gl\212\322][\010=iN\250\344}\352\215\265\006\317S\212\211\347.sJ\223\0200*@\374T.\325\036iD\201G\275!\270\'\212kM\305B\362\003U\335\262j3\326\242q\232\201\2704\306jc5|\335\'5\003&i\273q\326\241d\250\232:\211\243\2462\347\034TN\270\025VJ\251-S\224\325G\250XsM\333NU\247\221\362\326F\247\001\373\325\243\341\333\276\221\263\000\243\326\272U*\330\000\203N\311SO\0227\255*\253\271\357V\205\300\2116\203\322\250M3K?^3[\332Q \n\336\202\\b\265\354d\016@\255\035\252\243\212\004\333OZ\232\031s\336\254+z\232\221^\235\346\320f\367\240M\236\365\"\362z\324\3616\r_\206p\275\352\374\027=9\253\360\334U\330nj\365\275\306j\322JMI\346f\247\206J\225\317\031\250\017_j\2250E)\305\002A\322\236\016i\341\270\246\273\003Q\227\024\302\365\013\276\r0\2754\2654\2654\232\211\252\'5\023\n\215\201\305|\350\353MT\000\344\323\035F8\352j\006L\032\215\226\241\221j\006\025\004\246\252Hj\244\325NQU\331sQ\262\3236{S\325i\3413U5\0107!\030\254C\346[\266W5\241\247\353\357\024\237\275\311\000`V\375\236\275\034\307\004\376u\246\227\261\354,\024Uy\265bx\035=\250\216\3540\353\315X\265_1\301\256\202\315v\250\255\010\244\301\255;+\235\254+Un\262\274\232`\233\3465<W\030\3075en3\336\236\'\367\247\211\250\363iD\2305f\007\334z\324\245\360z\324\360\311\232\277\004\265z)\252\314w8\357Wm\256\371\034\326\214W@\367\251\326q\353Vc\234z\324\302p\303\031\246\226\316y\241&\npMK\27053\200x\247\211p)\336nh2qQ\227\250\331\352\'jf\372M\306\200i;PFEV\224\021Q\006\317\006\226\276xd\250\331*2\244Tn\230\025\003\016j\t\020\232\257 \252\322\014\325i\026\252J\265RE\250Y*2\224\233)\313\036jdLT71\206\025B[!&x\252m\247|\335(6o\013dqZ\266^`\213\004\232\225\241b\331\253v\321s\315i[\376\351\201\255\253iC(\"\256F\325n\t0j\352\\p9\346\245Y\263R\244\274\325\230\346\342\237\366\214S\305\317\2758NOzp\232\255\333K\315J\323|\325b\tsW\342\226\254\244\370\251\205\306;\325\230.\262@\315hE+c \361W\241\233\216je\270\332z\361S-\317J\224N\017zR\373\273\363R\3076\325\346\227\355\000\232C-9e\247\371\234R\027\315F\317L-M&\226\202qH\017\255\014qQ\270\334*\253.\032\224t\257\001h\3526\216\243h\361PH\265^D\346\243q\305S\225y\252\356\265^E\252\222\245Ut\250\236:\217e\'\227R\244t\262\020\007\025\\\215\335i\2166\212O+r\206\3052A\270\201\212\2363\264\001\212\227\315\036\224\370\346\346\256\302\373\205iY>\016+V20*\324m\212\235\034T\253 \035\352d\222\246Yp)\014\246\236%\342\244\216l\232\267\037\315\311\253\"E\215i\2136\346\253qK\266\255\307qV\226s\214\323\226b\306\246\216r\216+b\336\364\025\034U\265\273\004S\232\360v\251b\273\317z\267\024\343\326\247\023\347\024\3637\024\007\3179\2452b\205\237\025\"\316\r;\314\246\264\224\303%(\177Zx4\247\2450\360h\335\270b\230[\025\023\220i\233\253\302\332:\215\243\250\235*\274\221\325i\020\364\355PH\225ZH\352\273\245W\221*\254\251U\232:\211\343\246\030\375\251\004t\355\270\250\235\t\246\371x\250\245^)_\210\300\250\025rsV\025sH\313\315*\014U\270\037\006\265-_\245jD\331QR\265\306\316)E\331n\365<sg\034\325\330\233\216\2656\372B\324\340\36542\005<\325\345\223+\305F\362\232X\346\301\253Iq\232\261\024\365v)\362\270\315K\034\373Z\2375\326:U\213+\334\361W\226\350\251\353R\375\243v9\2530M\216j\352\\c\275L\227>\365/\332\3061\232Qw\357K\366\234\3654\323s\351OK\217z\231n=\3503\347\275\002l\324\210\371\251\225\360)\373\270\246\271\250\031\360i\036L\212\204\2774\322\370\353^:\361T/\025B\361Uy#\252\262\307U\336:\256\361\325y#\252\362GU\244\2135\003EP\264\\\323LX\246\230\361M)Hc\246<|UYS\232\211\301<P\211S\242\361C/4\252\265$jKqZV\252x\315j\303\302\212s\214\324c\203V\242\"\256\305&\005L\262S\367R\207\247)\253\260>\0074\371Xb\241\r\212\2269j\314r\325\230\347\305XIw\016\265&\375\3255\261\332\325\240\262dS\322S\232\271\035\306\005L\227\031\357R-\316;\323\376\324\017z\005\326;\322\213\302;\323\205\326OZ\231\'\367\251\026\340\324\213!525L\217\203V\025\370\247\207\240\277\025\004\217\203P\263\346\242\222]\203\232\204\334f\274\335\241\250$\213\025Y\343\252\362G\305V\222*\254\361\325i#\250\036*\257$u]\342\250^,\n\205\242\246\264u\021\216\230\311\212f\332FPEV\2259\250LY4\242<S\300\3050\214\232*X\216\326\310\255;w\034V\214-\221Ov\305D\307&\246\2175f2EN\254je5*\232\221H\251\221\273\322<\331\3434\300\3475*\032\261\031\251\321\215X\211\211\253\010\325</\203V\326^)\313!\006\254\244\274T\311%<\2754\315\266\223\355\006\234&\317z\225$\346\254\307-L\262sV\021\216EZF\342\237\276\254G/\024\246Oz_8`\325w\223&\243g\305W\232^*\233Jk\224x\261U\236<\236\225^h@\307\035j\274\220\325Yb\252\317\025V\222*\202H\252\273\303P<=x\250d\212\2410\324o\027\025\031\213\025\013\307Q\224\250\335y\250^:\217e!ZiZa\024\2305\"\324\320\312T\326\225\254\306\256\277\314\240\323\027\255Y\214\214T\361\214\324\352\231\251\225H\247\205\247\001\315J3\212\214\241\315=V\246E\253\021\212\231jx\233\025aNi\312\374\325\210\244\305Hd\346\255E&EL\r;w\035j6|\323w\342\234\262U\204\222\254F\370>\325r!\273\025r5\310\251\025\361\305+>\005,s{\323\314\276\364\206\\\320\016y\250\345\224c\002\251\311&sU\331\361X\262EU\336.*\007\207+\223\370UY\"\252\262CU\336\032\201\341\252\357\rWxj\007\206\240\222*\205\242\250d\212\243h\370\250\036*\210\307Q\264U\023GQ:`\324l\265\033-4\255&\312xZtk\315\\\205\261W#\237\261\251@\311\251\243SVS\212\261\033U\205aN\363\005804\365lP[\232zsS\250\251\001\002\236$\003\275=e\305?\355\024\236y\315X\212\3435n7\334j\334n\026\247Y3N.\000\250\333\236\224\314\232z\222*Uz\267o&x5\245\010\300\315XW\"\236\030\036i\344\344T,\333\032\234\037wz\221\010\035h\226A\267\212\245$\325]\345\315D\322T\017\035Wxwg\025\013\305\307N\225VH\263U\336\032\256\360\325w\206\240xj\274\220\324\017\rV\222\036zTM\rA$U\023\305\305@\361T-\027\024\303\025D\320\324\022\307\315@\321\340\324n\224\335\224l\243m9EL\202\254F\231\253\366\321dU\237+\002\234\274T\252\334T\201\215<\034\324\252iK\343\275.\343BJA\251\326~)L\371\246\231\211\251#\220\223S\256MH\020\232\263\004x\253i\205\251|\334\016\264\370\3469\353OyH\357H.*dp\324\360i\352jx\233\004V\235\274\370Z\263\274c\2553\316\000\323\332\343\345\342\243V-\311\247\357\330:\324-tsA\271\310\353P\311&j\006\223\232n\352\266\361f\241xq\305Wx\252\273\305U\336\032\201\341\252\362CU\336\032\201\341\252\357\rWxy\250^\036\265]\342\250^*\201\342\250\232*\214\305Q4\\T\022\305\223P<U\004\221\361M\362\350\362\351\014t\212\2252%X\210sZ\020\036\005X\335\221J\027<\323\325j@1NQR\016)\257\222iU\217Jr\212q4\252i\352A\251\220\212\235\010\253(\303\025 }\264\344\270\031\247\371\273\251\361\276\rJ\0334\001\223S\240\251\343\313T\240\034\323\320\342\255G)QS\254\245\252P\007sNR\001\353O2\205\025\033\312\010<\325C\'\314i|\312k?\025\01374\241\253q\342\250^*\201\342\252\317\rB\360\373Uw\206\240\222\032\256\360\373T\017\017Z\201\340\252\257\027&\240x\252\007\212\240x\263P\274U\013G\305F\321qP<~\325\023E\232\202H\252\274\261qQ\371t\010\350\362\251<\252z\307SF\230\253(\330\2517\361B\312U\252\344L\034T\333x\245U\245\344Py\250\230\2254y\230\247\007\315=MH\247\025*\032\260\206\254%HA\"\232\"5*\253\001SF\010\251A\3059^\255E\202*t\030\346\247\214\006\025 \213\006\237\214\ntR`\322\264\270=i>\321\212q\270\014*#7\2750\276M?w\024\233\275\3526\353@8\256\261\342\250^*\201\341\250\036\032\201\341\344\324\017\rWxj\007\202\253\311\025V\225\000\252\317\025@\361Uw\217\025^D\252\356\270\250Yj&\025\037\225\232cCP<\\\364\250%\207\"\2411b\223\313\245\021\322yt\010\352UJ\221R\244\021\322\030\371\251a\312\032\276\230u\025*\245#\256\005Uw*j6rh^\265*\214\324\250\265&\312\2265\253Q \253H\200\n\\\363S\3063R\354\030\245\010\0055\270\242>\265i\033\02529<U\250\201\002\255)\241\372\232\210\034\032d\217\201P\tw\034S\367`S|\317z]\344S\326L\323\267R\023\232h5\377\331"
+byte_png: "\211PNG\r\n\032\n\000\000\000\rIHDR\000\000\002\000\000\000\002\000\010\000\000\000\000\321\023\213&\000\000\001\213IDATx^\355\335\313\n\2040\014\005P\321\377\377\344\021q1\013C\231\301\0073Mz\316\362.\004\241\332&\021\234&\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\200!\3151\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\0008m\215\001\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000@\333\034\003\000\000\000\000\000\262Yb\000\347\230\026\000\000@\022\016\357\000\000\320\2455\006\351\274\357@\321\001\000\360o>\003\202\316)\233\000\250\"\177S\023:\366\212\001\000Y\335?3\035;\t\367\257\007\017\260\020\001\340;\003!\000h\260A\322h*4\"`\010\236~\000\000\000\000\200\252\214\005\001\000\000\000\240\006_{b\r@\035\3467\000\037yM&\342\017\014p\201\342nX68\000\000\200\003e\022\000\000\000\300(\366N\220n\020\000\000\000\000\000\000@u&\303\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\300\317m\204\275\016\017\263vs\375\000\000\000\000IEND\256B`\202"
diff --git a/core/res/geoid_height_map_assets/tile-b.textpb b/core/res/geoid_height_map_assets/tile-b.textpb
index 83d160b..b9b5bfc 100644
--- a/core/res/geoid_height_map_assets/tile-b.textpb
+++ b/core/res/geoid_height_map_assets/tile-b.textpb
@@ -1,3 +1,3 @@
 tile_key: "b"
-byte_jpeg: "\377\330\377\340\000\020JFIF\000\001\002\000\000\001\000\001\000\000\377\333\000C\000\004\003\003\003\003\002\004\003\003\003\004\004\004\004\005\t\006\005\005\005\005\013\010\010\007\t\r\014\016\016\r\014\r\r\017\020\025\022\017\020\024\020\r\r\022\031\022\024\026\026\027\030\027\016\022\032\034\032\027\033\025\027\027\027\377\300\000\013\010\002\000\002\000\001\001\021\000\377\304\000\037\000\000\001\005\001\001\001\001\001\001\000\000\000\000\000\000\000\000\001\002\003\004\005\006\007\010\t\n\013\377\304\000\265\020\000\002\001\003\003\002\004\003\005\005\004\004\000\000\001}\001\002\003\000\004\021\005\022!1A\006\023Qa\007\"q\0242\201\221\241\010#B\261\301\025R\321\360$3br\202\t\n\026\027\030\031\032%&\'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz\203\204\205\206\207\210\211\212\222\223\224\225\226\227\230\231\232\242\243\244\245\246\247\250\251\252\262\263\264\265\266\267\270\271\272\302\303\304\305\306\307\310\311\312\322\323\324\325\326\327\330\331\332\341\342\343\344\345\346\347\350\351\352\361\362\363\364\365\366\367\370\371\372\377\332\000\010\001\001\000\000?\000fy\245\006\202sE\024R\346\224\0323\357J\r=Z\236\032\237\272\215\324\340\324n\243v(\335\223O\337\205\300\250\313\323K\323w\321\276\224?\2758=8I\357O\022\373\324\202S\353O\023{\323\274\363\353@\234\372\323\204\376\364\3617\024\34158M\357N\363\250\363\275\351<\336x5<R\347\251\253\005\262)\204\215\270\254\273\3051I\221\320\324K!\"\224\2755\236\243/M\337\315)z7\322\027\244\337H^\205bM[\213\205\247\023M\335C>\007Z\201\256\000\357Q5\310\365\250\232\346\242k\234\036\264\326\273\000T/z\000\252\315xI\353W(\245\315&h\242\2274\240\321Fy\247\003O\rK\272\2245;u.\352Bis\201\3154\2654\2650\265&\3527Q\276\224=/\231N\022{\322\371\336\364y\336\364\3417\2758K\317Z\221X\232\224\023N\016iw\232w\231F\3727\324\261\315\203\326\254\254\331\034\032\014\240u5Z\361\326H\376\225EO\034S\213\361L/L-L\335\315)z7{\322o\244\335Aj\226.Nj\342\364\306i\030\250\352j\027\231W\275S\232\353<\003U\332R{\324fJas\353Q4\234\324M\'\275@\357P\274\230\255\343\322\212L\321\223Fi3\315.isFM\024\271\247\003J\032\2245(j]\324\273\271\241\245\312\201Q\227\246\026\244\335I\272\233\276\215\364\027\244\363(\363=\350\363=\351|\317zp\222\236\262sV\243\220c\232\233\314\024\236o4y\207\326\227}\033\350\337J$\346\245Y\215=\245\312\373\325Ig \021Q\243\344f\234_\212\211\237\007\2554\2757}\033\351wqM\335NV\024\027\311\342\254Dp*_4\001\301\252\323\\\340\340\034\325\'\230\261\316j2\3714\322i\t\305F\315P\273TE\252\'j\205\216k\244\244&\222\212L\322d\322\346\2234\271\243\"\234\017\024f\2274\271\245\335J\032\215\3704\302\324\322\364\302\324\233\251\013\322\026\246\357\244/I\276\220\275\'\231I\346sR\251$g4\355\3705*\334c\275J.\001\357N\363s\336\224K\317Z\220J=i\336e\033\370\244\337O\022S\314\270Z\257+\023\021\"\233\031\371E+5D\315L\337F\356h\335K\272\233\273\232P\324o\346\246\023`To9\307\006\253\226\'\2754\322\023M&\230Z\243f\250]\252\"\325\0235D\306\272|\322Rf\222\212L\321\232J2isFisK\2323K\272\215\334R\026\246\226\250\331\251\245\2517SK\322\027\246\227\246\227\244\337H^\230d\244\363=\352E\237\003\255)\237=\351<\337zp\233\035\351\353p}jD\234\223\326\247Y}\352A/\275<IK\276\234\032\226G\302\342\221[r0\246n\300\3054\275F\317L-I\272\2245.\343@98\245\346\216i\030\361L\315&}\3513M-\3050\265F\315Q\263T.j\"\325\031j\214\232\352h\244\343\232JL\361IE!\353IE&i\300\322\346\214\373\322\216\005&ri\013Tl\325\031jilSwf\220\2650\2754\2750\275\033\351\206Ja\222\232d\243\315\367\243\315\367\247\t}\351D\264\242_z\221&\301\353VRl\212\231d\367\251\004\224\361%8KD\222\360\006ic\224\003J\347\370\205DZ\230Z\233\272\227u\000\323\306M=p)K\014TL\376\224\204\323I\244\315%4\232\214\232\215\232\242cQ9\250\230\361Q\223L&\272\312)\264Sh4\231\244&\233\237z3I\232Ph\317\275.}\350,i\001\3434\205\252&j\214\232c78\024g\002\230\315Q\226\246\027\246\027\246\231=\351\206Ja\223\336\232d\244\363)<\312Q-8KJ%\367\247\t}\352\304S\201\336\254\254\336\365*\312=i\376p\305\002Nz\3224\271jz\311\357R\244\204\241\007\261\2463\214\323I\315\031\245\006\224\037Z~\3768\244\336h\337\232L\322f\220\232N\264\204\323\t\250\230\324lj2j&5\033\032\214\232\214\232\353\350\244\"\222\233E!\353M4\3123\306)3\315\031\2434f\202h\'\024\302j2j6l\n`9\346\220\265F\315Q\227\250\331\3522\364\302\364\302\364\302\364\303%4\311I\346P$\367\247\t)\302J_7\336\224M\216\365*\\\220z\325\244\234\036\365:\313\232x\223\276i<\301\277\255J\222\017Z\225e\000\020)7\321\2734\240\363N\315\000\363N\335I\232p\342\202i3F3HO\024\302j65\033\032\215\215F\306\243cQ\223Q\223L5\330\212C\326\212i\353Hz\322R\036\264\323M=i\246\222\212L\320\247\234\321\236i\t\250\330\324d\323\030\347\212i5\0335F\315Q3TL\325\031j\215\236\243/L/Q\231)\276e!z<\312<\312_6\227\315\245\022\323\226Oz\235&\307z\265\035\307\024\363?\241\247,\271\347\275L\262\032\263\013|\214\177\n7\032z\26585.ri\300\342\22794\341\307Z\\\320M \365\245&\230MFMF\306\230MF\306\242&\230j3L&\243c]\225:\220\212i\244\244\"\232i\017Ja\246\236\264\334\320\0174\215\305\000\341sM\0353\353HM0\232\215\215G\357McQ3TL\325\0235D\315P\263\324L\365\031\177z\215\237\336\243/M/Hd\246\371\224y\224\276g\275/\231K\346{\323\204\225*\311S$\206\246W\311\2531\232\235MN\222`l\251\003sO\006\235\234\323\301\245\316jE\030\353N\315!4\224\271\244&\230M0\232\214\232\214\232\215\2150\323\r0\324mQ\232\354\351GJZB)1M\"\220\212a\246\265Fz\323i\t\246\261\342\227\370@\240\323\rF\306\243\'4\322j&j\211\215D\315P\263T,\325\013\265D\315Q3\324e\251\205\251\245\251\205\2517Ro\367\244\337\357J\036\234\036\236\255R\243T\350\325a\033\232\265\033qVQ\266\215\304\360) b\316[\326\255)\251\001\247\203N\0075*\3603N\315\031\244\317\255.\3523M&\232M0\232\214\232a\250\3154\324f\230\325\033Tf\273JQ\322\226\212LRb\232E4\212cTf\230zSi\214y\305H=i\033\245D\306\242nM4\232\211\215D\306\242f\250Y\252\026j\201\332\241f\367\250\231\252\"\324\302i\205\251\245\251\205\251\273\251\013\032M\306\227u8\032\225ML\206\247J\235\rXF\307SR\202\362q\310Z\264\230U\300\251\225\252E8\247\203\232\221H\024\360\324\273\2517Q\272\215\324\271\244&\232M4\232a\246\032a\246\032a\246\032\214\323\rv\230\245\245\305)\031\244#\212JLsM\"\243aQ\260\250\330S\r1F^\245\2461\250\230\324,\334\324l\325\0235D\315P\263T,\325\0135@\315Q3TLsQ\223\212\215\2150\232\214\2654\2654\265&\3523J\030S\301\364\251\221X\373U\210\323\025eFjd\025b<\016\242\247V\025 l\364\251\224\342\236\032\234\036\236\036\234\036\203%(jP\324\240\323\263Fi\244\346\232M4\323\017Ji\246\236\264\302)\204Tf\230\325\332R\342\226\212(\307\024\334R0\250\310\250\330Tl*6\024\2120sJMF\306\241v\252\354\3315\0335B\315Q3T,\365\013=B\315P\263TL\325\031j\215\232\243f\250\313S\013SKSKQ\237zQ\311\344\323\327\006\246R\242\245W\0252\275L\217S\243\324\352\325 \177z\225X\001O\363)\302Ozx\222\236\036\227}8585<\032p4\354\321\221A4\334\346\220\323M4\323\010\246\232a\025\031\246\032\355h\240u\247b\220\2121\305%!\024\302*2*6\025\023\016\324\323\301\2461\250\230\324\0225@\306\241f\250Y\252\026z\205\236\241g\250Y\352\026\222\241i*&\222\230^\2432S\013\323K\322n\244\334)\014\200P$\356i\342^i\352\374\365\251\225\352ez\235\036\247Y*U\227\260\251\026@;\323\374\352z\311\357O\022T\202Jxzz\265H\032\236\rH\r8\032p4\271\244\'4\264\230\244\"\232E4\212i\025\033TdS\010\256\327\024b\226\212(\"\212i\034\323XTdTdTdsQ5D\306\241sU\230\344\223P\273Uvj\201\336\241g\250Y\352\006z\205\344\250\031\352\026z\211\244\250\332Ja\222\230d\246\031=\351\246`\007&\231\347\026\351J$\247\007\367\247\253T\252\306\246F\251\325\252E\222\245Y\017\255J\262b\236%\367\247\211=\352E\222\244W\367\251C\373\324\212\325*\265J\255R\251\247\203R\003N\006\224S\200\315(\024b\202)\244S\010\246\221Q\221Q\221L\"\273J)qKF)\010\243\034RS\010\2460\250\310\250\237\241\252\347\245D\306\253\310x\252\354p*\273\265Wv\250\035\252\006j\205\332\253\273\361U\331\371\250\031\363Q3\324,\374\323\013\324M\'\275Fd\250\236b;\324[\3119\251\021\373\342\234\030\023O\rR\251\346\245V\367\251Q\327<\324\202^\302\244W\251U\233\031\3058HI\300\247\356\"\236\262\034\324\253)\251\203\234S\326C\232\260\217\221S+T\352\325*\232\225jAO\002\236\005<\np\024\270\366\244\"\232V\232EFV\230V\230E0\212\354@\245\242\212(\2434\332C\326\2434\306\025ZS\203\212\205\215@\346\253\310{Uw<Ui\rUv\252\356\325\013\265B\315\305Vv\342\253;T\014\365\013=B_\232c=D\315P\264\225\03357w\275=X\221R)5*\324\200\323\203sOROJ\224d\016jh\330c$\324\206\\w\247$\242\247\022\206\340\212p\035\326\244_z\235\010\25103R\243U\2245:T\353S-J\242\244\002\244\002\236\005;\024\270\243\024\322\264\322\264\302\264\302\264\302\265\031Z\353\250\242\212)\017JJL\322\023L4\3065RC\227\250^\240sU\244<\325y\033\212\253!\252\256j\273\236j\027j\205\333\214Ui\032\252\273Uwj\201\232\242/\315F\315Q\263\032\211\216i\233\275h\034\236*d\030\034\323\307Z\221zT\310\204\365\247\355\013\316)\276q\007\002\244\014XS\267\221F\362MO\020&\255\"\234\325\264\217\212\223h\306\010\247,|\361RmaNN\265i*\314ua\005N\242\246QR(\251\000\247\201N\013N\305\033i\n\323J\323J\323\n\324l\265\031Z\352h\242\212L\322\023HM&i\244\323I\250\330\325G?1\250\234\325g5ZS\315V\220\325g\252\362Ug\250\034\325w5^F\252\316j\273\232\256\346\241cL&\243f\250\231\2513\232p\3009\251\224\202)\341j\302\021\266\247\217\024\367\\\257\025\\\307\206\311\247\006\n)7\344\324\2503V\341\034\326\2141\344t\253!@\247\005\315=W\006\246T\004`\323\014{Z\247\214qV\022\254\307V\020T\352\265*\255H\026\236\026\234\026\236\026\227m!ZiJiZaZ\214\245FV\272ZB))\r%4\322\023\212JBi\204\324.\330\025Y\217\314j&5^CUe\252\356*\006\250\034UwZ\254\342\253H*\254\202\253=V\220\325v5\023\032\214\232a\006\243\"\215\246\234\001\251PqS\'5 \03052\234T\341\306\332\202W\035\252\014\222i\352*\324\\\325\310\260\rhE \013R\006\313U\270\307\313\315)\340\324\261\221\234T\255\036W4\210\2705:\216j\314b\255\"\324\352\265*\255H\026\244\013O\013O\tK\266\220\245!JiJaJ\215\222\243)[\324SOZCHzSi\r!\246\032a5\004\207\212\257!\371\252\0265\003\232\201\372Uw\250\030T,*\027\025^E\342\252\310\265RAUd\025U\326\240u\250Yy\246\025\246\225\244\t\223R\210p:R\371B\227`\350*E\\S\366\342\235\217\226\243.@\246\362\306\234\027\024\341\326\256@\274U\214\034\361V\241V5r4\307Z\262\033\003\002\236\212X\325\250\3419\253\033>LS6f\245D\253\010\274\325\250\305XE\251\225j@\265\"\245H\022\234\026\227e\033i\245)\245)\205)\214\265\033%l\342\220\365\246\232i\351IM\244=i\206\232\324\306\252\362Ui{TMP\265B\365\003t\250XT\rQ5@\342\252\310\265ZE\252\262\'5]\2435\023\302qP<-\351L\021\036\342\221\2414\337,\251\247\200qJ\026\224/5*\2558\246i|\274\361I\344R\030\202\324m\201NE\311\253\260\251\025q#\006\256\304\200\n\234\017J\231\"-W!\213\030\310\253\212\203\024\241y\247\375\237\214\212z\305\212xL\032\261\032\325\244J\235R\244T\251U)\341)\333)vRl\244\331HR\230R\243d\250\312V\241\024\334SH\246\322\021M=i\247\2554\323\032\243j\201\352\274\203\"\240j\211\252\027\250Z\241j\205\205D\302\240\220a\261U\335r*\273!\317JF\200\025\346\242hT\014\001P\274\031\355PI\t\003\245Wd\301\246\355\366\2441\322l\245\tJ\023\232\221V\244\013N\000R7\025^F\250\261\226\2531 \253H@\253\021\266M\\\214\234U\250\206H\255\030Pb\254*\363\305N\252H\245\010sV\241\036\264\366L\014\342\232\027&\254F\225i\022\246T\251U*@\265 JpJ]\224\233)6R\024\246\024\250\331*2\225|\214\322\021\212a\024\3029\244\"\233M\"\232zTl*6\250\034d\032\205\207\312j\006\034T,*&\025\013\n\205\226\242e\250XUy*0\205\207JC\0368\305F\311M\362\263L1\214\324\022\240\305Rh\362i\2060)\204Rl\247\004\245\tO\tN\333@^i\031x\252\322G\3151S\232\263\032\032\262\2203U\310\255\310\251\266\355\253V\300\223Z\221.\000\253H\271\253\010\265(\2175*\307\212~\323\212EL\265YD\305YE\251\325*EZ\220-<-<%\033(\331HR\220\2450\245F\311Q\262U\254sMaM\"\232E4\212f9\246\221L\"\232EF\302\241aU\334u\025\013\016*&\025\013\n\215\226\242e\342\240qP\260\250|\262\355\212\231cU^\225^A\223Q\371t\2458\250$\\\032\255(\310\252\254\274\324.\r0!\245\331K\266\234\022\237\262\224&i\306>:T.\0105\023.i\026\"Z\256E\017\265]\215@\034\212\260\244c\212\031rj\325\252\340\326\244``U\244P*d\0315i\023\212\220\n\230E\2713B\305\216\265*\246MN\211\212\230-J\253R*T\241)\301(\333F\312M\224\322\264\322\224\306J\211\222\244\"\220\364\246\032B)\270\246\221\3154\212a\024\302*6\250Z\240q\315@\302\242aQ0\250\330T-P\260\250\231sBFG4\327\004\234\n\217\313\311\240\246\005F\302\253\310*\264\211U\3319\250\212Rm\244\331\355N\021\323\266S\3262{T\253\007\265<\3041U\244\207\236\225\030\207\236\2252@\007j\235c\003\265J\"8\251\021\016jm\207\035*X\201\006\257\304\330\025ad\346\256C\223\212\275\022\2265a`=qV#\213\003\004PS\234b\225W\0252\212\225\0275:\245H\026\244\013N\333\355K\262\223e\033)\245)\nTl\225\023%\004qL\"\230E\024\332a\024\204SO\025\021\250\332\241aP\270\250\\TL*&\025\023\n\205\205D\313H\221nz\225\243\003 Ur\234\322m\2468\250Yj\007J\201\326\240d\250\314t\337+\332\227\312\247\010\371\251\226\337=\252d\203\007\245I\345\200*6^i\276V{S\01484\345\213\332\245HI=*\342[dt\251\222\320\223\322\246\373\031\364\245[2;T\202\022;T\261Bw\016+N\010\016\005i[B3\315h\010\227oJ_+\322\221\255\311\\\212\211\243\301\351OU\251\321juZ\221R\244\t\3058!=i\333}\2516Rl\244)M+Le\250\231*\034qM\"\230E6\232i\264\323M&\230j6\025\023\n\205\207\025\013\n\211\205D\302\242aP\262\323\031i\321.)\354\244\324-\035FV\242q\212\204\324N*\022\231\250\332:\217e!JQ\021=\252X\340\346\256\307\010\003\245\014\200\032c\n\204\2474\365N)\032<\232\226(3\332\255$\000v\253\221D1\322\256G\010\035\252u\205Oj\224Z\202:R}\217\332\245\216\317\236\225r86\216\225:\r\247\212\262\233\215N\243\0254k\220j\t#\347\212ENjeJ\235R\245T\247\205\247\005\245\333F\312M\224\205i\205j2\264\306Z\251\212i\024\322)\204SH\246\032i\250\233\351M\243\025\033\200\rB\302\241e\250\231j&Z\211\226\242e\250\233\212\2265\371A\365\251vqQ\272UvNj\027J\213\313\250\236:\217\313\244h\275\252?+\236\224\341\007=*U\200b\245X\200\355R\005\300\246:\324L*=\2715:E\232\231m\362zT\361\301\216\325e-\363\332\254G\001\035\252q\031\307J\232(\3115z8\370\351S\010A=*\304v\343\322\234\360\340p)b\267,\335*\362Z\355^EF\351\206\305M\022\374\265\033\246\r5W\232\231\022\254*\324\241)\301)v\322\355\243m\005i\245i\205i\205j6Z\240E7\024\204S\010\246\021L\"\232V\230\313L\305!\025\033\016*&\025\033\n\214\255F\311\362\346\241e\250\235}\005B`bjq\031\030\247\201Mu\342\2532\363Q:\361P\225\346\220\307\232i\217\024\206:o\225\317Jx\213\002\224&)\341i\016*7\250H\311\245D\346\256E\037\265\\\216\034\366\251\322\016zU\310\240\343\245N \366\247\213\177j\2328\000\355V\226>*x\342\315[H\260:S\274\235\307\245Z\202\330\001\234T\262(\013\212\244\351\226\251\025p\224\205i\2339\251Q*\302\245J\027\212pZ]\264\273h\333HV\232V\232V\243+Q\225\254\322(\3054\212a\024\302)\204Sq\355H\313Q\020sI\216*2*&\025\031ZaJa\217vqP\262SDY8\250\335v\266*`\277&M3o4:\374\265]\223\232a\2174\323\0057\313\305F\353\212\210\212r\250\247\034b\231\212\017\025\021&\214f\234#\315J\220\363\322\255\305\017N*\354q{U\310\240\317j\273\035\277\035*u\203\332\244\020q\322\225a\301\251\322/j\2368\361VU8\251#\217\236\225qW\013QH\271\252\355\035.\314\n\002Q\345\324\212\230\251UjP\264\355\264\273iv\321\266\220\2554\2550\2550\255FV\262qF)\010\246\021L\"\243\"\223\006\220\216*\"9\244#\212a\034\324L\275j2)\244S\031j2\264\335\274\323\014E\244\251\031p\270\246\005\346\224\257\025\003\245F\027\236\224\255\214T\014y\250^\242\3074\034\201H\001cR\210\270\246:\324$`\323\220d\325\250\342\317j\265\034\036\325i!\307j\263\034^\325z\010}\253B8\206:T\342/jp\217\332\236\"\030\351OX\371\251\204u\"%XH\275\252P\274SYj=\224\323\035\036].\316)\3018\251\025x\251\002\323\266\322\355\245\333K\266\232V\232V\232V\243e\250\331k\037\024\204SH\246\221M\"\230V\230E!\025\033\016i1L+Le\250\212\323H\244+L\333M+\315*\'\314N)\254\23757g4\2458\250Y9\250\33103Ud$\032\211\272T\014y\244\035iH\251#\0035+\260\013U]\362j2\244\232\236\030\211=+B(\270\351W#AV\022<\232\271\014#\214\212\275\024C\216*\342GV\222!\216\225\034\253\266\232\204\032\231W\232\235W\212\2268\376j\266\261\374\264\214\224\322\224\233)\241)\002sN\021\363\212_.\225W\025*\250\305.\332v\332]\264m\244+M+L+Q\262\324l\265\211\216i1I\212n)\010\246\025\250\310\244\3054\2554\2554\212\214\255FV\220\245!Jn\312n\316jA\036\027\2450\307I\345\320c\343\245W\2210j\007\373\265RE\346\253\270\250\212\346\223n)v\223NDjq\211\332\220[7qR-\267\265Y\216\034v\253q\307S\204\"\247\204sZ\021\216*\324Ur1V\025\260*)\262\325\002\202\032\256\3042\005YD\253\021\2475eG\0242\212\214\255\005i\212\275iU2jU\213&\245\020g\2651\241\332i\241i\301{\032xZ]\224m\244+M+L+Q\262\324L\265\204G4\204Rb\220\212i\024\322\264\302\264\322\264\205i\245i\205i\205i\2739\243g4\206:iJn\316je\217*8\246\264~\324\303\035&\312\215\341\310\315Q\2322\265M\305@\353\315F\313\201Qc-V\0220EN\221\214\325\225\211q\322\203\032\216\324\004\036\224\355\270\251b<\325\325\2140\247\254EM[\210g\025z$\253\013\201R\001O\330\010\250\2360\017\025$\\\032\270\207\212\235\030f\254\003\305\031\311\245\013\232d\230\007\003\255\"\247\024\364R\rYD\350juN8\244x\301\031\252\357\036\326\351H\027\'\024\340\0108\306i\341r3K\266\220\2550\255F\313Q\262\324L\265\201\267\2326\322\025\246\342\223m!ZiZiOJiJiZiZ\214\255\033)Dt\246>)\236_4\357\'\'\245J\"\343\030\246\264U\033G\201Ql\313S\231\000J\315\271\003&\263\344Z\254\303\232M\231\024\337(\206\351R\250\300\251QI5aT\342\227a4\340\270\245\333\232T\\5i[\256@\253~W\035*H\342 \364\253j0)\3435*\323\306iJ\023@\\\032\231I\305L\204\346\255\253|\264`\223R*\220\264\320\231l\232\225S\212~\312\231\007\0252\212~\316*9#\310\250<\262\r9W\007\353Rl\347\353K\262\232V\243+Q\225\250\331j&Z\347\361I\212B)1F\332n\332\n\323J\323J\323J\323\n\322l\245\tN\021\322\371t\323\025L\221a9\035i\302:kDOj\211\341\366\246yX\250g\000-d\3162\306\251\310\265\001\217&\234\261\322\262qQ\343\006\246L\n\260\244S\270\246\023\223OPML\221\222G\025\245m\021\002\257\254|T\252\200T\201i\301y\251R<\325\210\341\317j{F\024Tb<\232\225!>\225am\217\245L\260\221\332\245Xy\351S,9RMDS\006\236\242\244\331OE\251\321j]\237-4\245G\345rx\246\224\305I\260l\006\202\224\306Z\214\255F\313Q2\324,\265\317\201\232B\264\205i1F(\333HE&\332iZaZM\224l\247\010\371\247\004\247yt\253\026Z\246\020\322\3714\276P\250\244@*\234\244\016\225Br[5FH\3175Y\342>\224\317+\332\232W\006\220\256EG\263\232\221b5\"\304jC\031\3059!$\363V\222\337\212\261\034 \032\273\032\200*\302\324\203\232\225W5*\307\315Y\216>*\302\256\005#\246i\360\301\232\264\260\200zU\270\241\005zS\314 \036\224\205\000\246\223\306\321L\330M8&)\340S\224T\361\212\230\201\214SvR\204\310\243\312\366\2451b/\306\230V\230V\243e\250\231j\026Z\211\205s\252\275iv\323J\323\033\212i4d\322a\275)\t\"\2200&\234\0274\276]8%8G\3058GN\021\324\251\027\031\305L\261q\322\231 \333U\332J\255#\223U]KT\r\0175\023C\355U\236\036zTf/Z\257,X5\032\246E\036Q\317J\221W\035EL\212\t\351S\210\301\024\005\njtaS\'&\247U52\n\235\0275j4\342\246U\251\320T\200T\201s\332\247\215j\312\246j\334I\307JVZ\215\227\212\257\267.H\251\024qK\266\225V\237\264\324\211\305H94\375\274S\221j@\234S\214\177.*\273\246\030\212\214\255F\313P\262\324L*\026Z\347\302\361AZ\215\2054D\315\332\245Kbz\212\220[\250\2450\247LTF\321I\3105Zks\033dR\'\241\253\010\234T\236^{R\210\215<GN\021sS\254T\362\201ES\230d\325VJ\257(\010\t\252\236o4\241\201\353C(\"\253\274c4\303\026{T\023A\362\236*\252\246\033\025a`\005zP`\364\024,$\036\225(R\005!Bi\311\031\315Z\2123\351V\2250*U^j\314kV\221x\251\225*u\216\245\021\324\211\035XH\252\302G\315[\2158\241\327\232\211\327\212\256\024\006\247m\346\227\024\345\024\360\264\273jH\327-S\025\002\225EM\032\2268\251\0310*\t\020\036{\325vZ\211\226\241e\250\231j\026\025\317Q\214\322\254\034\345\252p\021F\000\245\332\314x\034R\025\n\271&\243$Sr\000\353H@\220`\324F\327\234\212t@\347\004r*\312\245H\251K\345\343\245\"\237\336\343\035*\322/\025\034\302\251\272\022j\tp\242\263\346\313f\251\224 \321\234R\027\"\241y\r\021\315\316*\313F$L\372\325Co\206\351R\004 R\252\344\324\342\000GJcC\216\324,\\\364\251\343\204zU\225\210\001N\331\315J\221f\254\307\025[H\270\251\0250j\312&EL\022\235\214T\321\325\204\034\325\310\300\333L\221y\250\231~^j\271O\232\235\266\227m9E<\nq\034S\342\031\351O\307<\323\200\251cm\255R\261\315F\302\253\272\374\330\250XT,\265\013\n\205\205s\313\037\343S\307\037\255H\261g\236\324\335\2007L\322\273\235\270\252\356\334sP\263\034\322\000Z\246\217\n*P\271\346\232B\357\351\310\251\220df\244\000S\261\305G\n\346s\305]U\371j\031W5ZE\n\244\326l\331,j\273&j\t\022\253\262Tl\274T.\231\250\260T\325\225\224\225\305:3\275\360j\317\223\225\246\010\366\265\\\211AZy\200\036\324\013oj\220A\216\325 \212\225a\251\322<U\210\323\236\225m#\371i\010\301\251\342 \212\233\214Sr7U\250\224\021R}\323S\305\'\025)\301\246>1P\0203@Z\\R\355\366\245\031\247{T\361&\324\244?z\226\214\343\232\23662\014c\245\014y\250\235rs\212\205\327\232\205\226\241qP0\254D\217\332\246\330\000\245\347mFN:T26j\273\234\232\217\034\324\212\006i\314q\315<I\362\214S\224f\247E\251\202\322\225\371j;~%\"\256\250\342\241\230\020\t\254\351\\\223U\235sP\262\324.\225\003%D\351P\262Te3@LS\327\206\315hBw-\022\2469\3056\027;\260kR\024\014\271\251\274\260\005\036Vz\np\200\372S\304\007\322\245X*UUS\315L$P\265ZY\006x\245\212S\232\263\346\344S7\374\325n)HZ\223~O5b>\231\2517\340Rn;j>\364\365\034S\200\346\234\026\224(\247\205\251\024\361Mj^\324\224\344fS\305XD\3343\353H\361\372T/\037\025ZE\252\356*\026\025\226\020\n\033\000Tl\334\323\010\030\252\362\036qP56\236\240\322\271\302\323P\374\265j!\221VPT\230\312\323\271\306;RE\030\363\267U\203\362\256j\031\016\344>\265FH\216s\212\210\3061Q<u\013\245B\310*\007J\256\313\3157e\033(\331V-\316\033\025y\243\336\225_\311*\371\305[\206]\243\0258\233&\254\302\340\365\253>d`Q\346\307CN\270\342\253<\304\236)\201\234\236\264\361\0337Z\231\"\305N\0234\361\016MN\252\000\305<\001\236\265*6:T\301\262)\254\307\245\000T\213\322\234)\340S\361K\212\\Q\212A\311\305(\251\000\253\020\237\227\024\366\002\240q\201T\344\034\232\256\342\240qYg\326\243cP\261\250\331\316:\324\005\263Q\223\223OU\024\360\274S$\031\024@\001\340\325\304]\274\032\235*A\332\226\234\230\r\223Ng\312\221Q\036\225\023\255B\351P\262\324\016\206\241e\346\243e\250\032>j2\224\233h\333NQ\264\361W\240\223\200\rX\362\325\2057\311\247,52\251\003\212\033\177\255 \017\357O\010\306\236\261z\323\200Pi\301\261N\023\021R\307?8\"\247-\221\225\245\334M=sS\2408\251E?niB\323\200\251\024S\302\323\200\247\001K\217j6\322\025\346\225E=\006\346\305I\215\217\305H\033\"\242\223\221T\344\250\036\240z\310v\250]\207\255@\357P\226\3150\323@\346\245^\006i\301\201\246\311\323\002\240G)6\ri!\014\231\315H\254i\341\263N\3158\036)\001\000\340\367\251\004G\251\246I\036:Uv\025\023-B\313P\262\373T,*6Z\214\2557m&\332]\264\36485a%5/\232@\353J\'\247\211\351\353.i\342Oj\220?\035(\363N*=\304\232]\324\240\346\245QR\2432\237j\262\244\036\2252\n\260\202\245U\251\000\245\002\234\026\236\242\236\005<\nxZ6\321M\"\227\034P\207k\323\231\262\331\240H\0055\334Ui\030Uv\250\230V\023\2775\0035@\3074\322qM\243\275H:R\343\034\323O&\253\316\010\031\025%\265\321\306\322j\354r\203S\206\356i\300\203O\024\216\017\336\035E>+\257\340\224s\353R;\'\250\252\304d\323\035y\250Yj\026Z\205\226\242+L+L#\024\230\315\033h\003\232x\315<\014\365\247\005\346\234\026\244Q\212\225A\305H\277v\224\016(\333\315;fi\301*EZ\225W\"\244U \361Vc\351VR\247Q\305H\0058-<-<%8%=V\236\026\235\260R\024\024\322\224\230\342\233\201\236ha\305D\331\025\021j\215\216j6\250\315s,\365\0335GHM%&i\301\251\341\205!<\324\023\034\203U\341\037\275\255$\371qVQ\263R\017j\220\034S\263Q\355\033\263O\330\016\017B)\301N3Mt\004\006\025\023%B\311\355P:\373TL\276\325\031Z\215\226\233\266\214Q\212x\024\365\024\360\rH\250i\341)\341jE\025(A\212B\230\024\241i\341i\341jU\025\"\255J\203\025a*\302T\252*P)\340T\201i\301)\330\305L\";s\212B\234\321\267\212a\024\322\264\302\264\326\025\023T.8\250\215F\324\303\\\2514\302i\224\204\323K\201L2R\253\322\357\245\017\223L\220\361P!\333%]Y\tQVb~*un)\340\361N\rN\034\323\207Jz\212v\334\323\031=\252\026OJ\201\320\372T,\225\013-FV\233\266\223m\030\247\001O\002\236\243\232\260\200T\233(\333\216\364\345\025\"\212x\\\366\243e8-H\026\236\242\245U\251Ujd\025:\212\225\005N\242\244QR\252\324\252\224\2730\300\342\237\271\215!\004v\243\024\3229\340S\n\323J\323\031j\026Z\201\305BEF\325\033W$M4\232Bp*\007\224\016\225\t\2234\322\364\202LS\274\321\353J\262\363\326\234_\"\243|\343\212[I7K\261\217\322\265Tm\025\"\277cR\203OZ\220S\326\244\024\361H\325Ji\325\033\024\315\340\214\220y\244*\032\242t\364\025\tC\232aCM\332iBR\342\200)\353R\253\021R\253f\236)\352*eZ\220-8-(A\332\234\006:\323\302\324\252*@*D\353S(\251\220sV\020T\312\265*\245L\253\212p_\233\245+(+\221MbJ\340\323qHE&)\244Tl\265\023-Wu\250\030T,*3\\\205!\300\025^G\347\002\252\2719\353Q4\235\251\206CI\276\220\261\354i\310\374\363S\253\347\212\224`\255@\312VM\313V\241\273l\205sZ1\374\303\"\246^\005H\255\332\246\025\"\363R->\232\325\223{\362\236\007z\226\333\230\206j\177,\036\224\306\214\342\241h\315Fc\246\224\036\224\233)\n\321\266\227\024\341\305H\265*\232\225qS%J1J:\323\200\247S\200\364\251\026\245Z\221EL\242\246J\235\005Z\215j\302\'\025&\332\224\304\004Y\250\266\361Q\221M\243\006\212i\024\322*&Z\256\353U\335j\006Z\205\2075\310\036\265\034\215\205\252N\374\223U\235\262j#\326\232M3u\033\251\340\324\250\376\265b3\236\364\375\271\355L(T\346\246\216\351\343\030\006\264l\356\322S\206\340\326\232$L3\300\240\371j\247\221\305\n\300\3645(aK\277\212\202i\302\016\274\326l\322\033\211B\257AW!]\250\005N\016)H\343\232c-DS\332\230S\332\230V\220\255&\3326\320\0058\nx\251\024\324\252\3252\265<\032x4\341O\035*A\315<T\313S%N\202\247J\263\031\2531\232\235F\356\22418\301\355Q\236\225\031\353IE&3O\362\211Bj\022*6\250\034T\016*\273\255Wq\\i5Vw\343\025E\337&\242&\230MFZ\230M jpj\225MX\211\271\253\310AZk\340\232a\217540\225;\201\253\3134\201p)\256e+\303\032dW3\3020y\025:jK\234?\025$\232\244\013\031!\3015\234\3273]I\204\004\002z\326\205\265\276\305\347\223V\300\300\245\351\332\224\026\244,\331\351I\237QF\001\351M)\355I\345\322yt\236U/\227I\263\024\270\366\245\000\323\306E8\032\225Z\244^\2652\212\225EH\027\322\236\026\244U\346\245Z\235ML\246\254!\253\010\325:\271\035\r\005\263Q\263S\013sKHi\321\215\315\212\270A\362\260\007j\242\343\004\324L*\027\025]\305@\365]\305p\362\260U&\263\246|\265VcL&\230Ni\206\230i)GZ\225*\304mVR^1\232p\223&\245C\223V\025\206*x\306\357\245M\201\322\232\321\n\255-\250s\322\243\217OR\3315\247\005\262 \030\025gn\321K\364\244\003\232u.)B\217Jp\214g\245;\313\036\224\206!\212o\226\007jM\224\233)\nq\322\232R\223n(\002\234\026\244U\251\320T\310\2652\257\245L\253R\004\315;\313 S\325j@\010\251\026\246F\251\321\352P\324\355\331\2444\303J\0175 \031\024\203(\371\025?\3322\274\365\252\316rMFzTn*\273\212\256\342\253\270\257=\236N*\203\266MDMFM%4\365\246\032LS\200\247\202\005H\215\315J\032\234\244\203VcoZ\260\204n\007\265[\216A\214\n\235XS\211\024\001\232\221\023\275XP\000\342\226\227\031\024\241iqK\266\234\024\323\200\247\201F>\270\244#\330\322m\246\224\245\331Hc\3154\306})6\036\342\224\'\265=R\246E\251\224T\352=\252dZ\235S\326\236\253\371PS\272\322+s\203\326\245\013\334S\205H\246\244V\247\006\346\234\016h4\200\366\251Q\205<\340\366\250\310\346\233M#\212\215\252\027\031\035*\273\n\256\342\274\276Y2j\273\032\214\232a4\224\032i\351H1HM\033\252E5*\265N\246\246Z\235\rXCS\251\251\223\232\235@\333R)\340\np\315<S\300\245\305<\nP\264\360\264\340)\300R\342\224\n6\212k(\354(\010\t\245(\000\244+\3521M+\3528\240 \247\210\352EJ\225\022\247U\365\251\224zT\2038\247n\240\036x\244 \037\255*1\007\006\247\030#\212p\007\245<\nv)Fi\343\221K\030\033\276j1\206\342\237\223\212i4\332CQ\265D\334T\016*\007Z\362Vj\205\232\230M74f\220\232ijn\3523\232\005<\032\221O5b3VR\246\003\270\251Q\210\343\025e2@5a8\02504\365\251E<\nx\024\360)\340S\200\247\001K\212p\024\354RQ\2322)\264\2718\244\335FE\003\320T\2528\251W\334T\243\247Jx\307z~\3527\036\324\241\263N\335J\030w\247\202\017\006\236\244\257CS\243+q\320\324\252)\373i\312\027?0\245\003i8\245\003\212B)3M4\332Bi\246\2435\023\n\205\205x\3435DM4\232n\357zB\324\322\324\205\251\273\275\351A\247\212x\251\026\254F*\302\n\262\225:\255N\240\324\313S\240\251Ui\340T\212*E\031\247\201N\013N\000\212p\247R\340b\222\220\232JL\322n\243\266i3FiA\251\025\210\251RQ\322\245\017\31587\024\241\215;u;4\273\251wS\203\324\210\376\2652\225\"\247W\350\017\347S\251\342\2361\216)@\245\351Hy\025\031\004u\246\344\322u\246\236\264\204Tf\232EF\350\010\257\023cL&\230M4\232i4\334\322QO\002\236\265*\212\225V\254 \251\343Z\262\202\254 \251\320T\312*e\025*\203R(\315H\005=EH\0058\016iizR\320M4\232L\212B\324\205\2513\305\'\030\243\240\240\222\007J\003S\324\346\227$\232\225I\007\223R\006\367\247\006\251\024\323\263Fis\315(4\241\210\251RLT\353/\275L\262\343\277\025:\313\3375*\2704\244\212B\337\225#\020E0\364\244\355Hy\353\371\323H\244\333\336\232G\2751\200\257\014&\232M0\323M4\232i\245\035)\300S\300\251\025jU\025:\n\235\026\254\306\265a\005N\242\246QS \251\224T\252*@*@)\340T\200S\200\243\002\220\323KRn\246\226\244-M\335I\272\215\324\271\245\245\014G\322\221\207p\r9\001\306i\343\030\247\251\035)\343\2558T\212i\331\245\245\031\245\245\006\22784\340\325*\311\305N\222\361\305J$\343 \324\202\\\216iw\322o\243}\031\3474\245\251\245\251\273\361\327\245#\021\216)\205\205xQ4\204\323I\246\023IE8\nx\025\"\212\221V\246E\251\321*\302-XE\251\321jeZ\231V\246QS(\251TT\252*@)\340\014sK\322\234\r\006\230O4\303M\'\232ni\271\2434\231\244\315\0314\241\215<\023\330\323\376ls\315*\000s\221N\343\024\003O\r\315H\rH)\340S\205-\024QJ3N\335NW\"\245Y8\251\004\224\341\'\024\276`\243}/\231K\276\223u4\2654\266)\205\353\303\363IM\246\2321N\002\234\005H\005H\005J\242\246E\253(*t\0252\212\235\005N\202\246QS(\251\225jE\025\"\212\220S\262h\311\244&\232X\322\026\004sM\'\212ni\244\322Q\232i4\231\2434\240\324\212i\373\200\357G9\342\224\003O\035)\364\340j@j@i\300\322\346\2274\352(\244\"\200{S\201\"\224?4\341%;}\'\231\357N\022{\323\267\322y\224\273\363HMF\306\274K4\224\204\322\016\264\352\007Z\221EJ\242\244QR\252\325\204Z\260\213S\242\324\312\2652\255N\202\246QS(\251TT\212*AK\2323Fi3IHqM\"\232A\246\346\220\221A`\005!#\256i(\305(\343\2558\023\332\234:\324\253N\245\024\361\322\235\236)\300\361O\006\236\032\224\032p\351O\035)@\245\333A\025\037z_j1A4\231\305\033\250\017\357N\337H^\220I\315H$\310\244-_\377\331"
-byte_png: "\211PNG\r\n\032\n\000\000\000\rIHDR\000\000\002\000\000\000\002\000\010\000\000\000\000\321\023\213&\000\000\001\355IDATx^\355\334\355\n\2020\024\000P\261\367\177\344$\022\214\270\344Wl\272\217s~\004m\2452k\314\273\355\016C\211\306X\000\000\000\000\000\000\000\000\000\000\000\000-\231b\001\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000p\3343\026\000\000\000\000\260\255\326L\210\343\373%\303\305g8$\000\000\000\000\000\000\000\000\000\000\000\000\260\2307\006\001\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\371I\376\005\000\000\000\000\000\000\000|XH\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000@\007\244\331\000\000h_\301c\276\261\340k\243FS,\000\000\000\000\232\'\036\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\300\255$\316\007\000\000\000\000h\337\030\013\000\000\000\000\000\000\000\000\262\260c\r\000\000\000\000\000\000\000\000\000Hi5\203\322j\005\000\000\000\000\000\000\000\220\234\344B\000\000\000\000\000\000\000\000\211H\230\000\000\000\000\000\000\000\360mm\026u\331\346\276V_\n\333\361S\323\242\000\000\000l\330}l\334\375\000\000\000\000@\023J_Oq=q!\000\000\000\000\000\000\200\266\230\007\006\000\000\000\000\000\000\200\036X!\000\000\000\000\235\210A\000\t\365f\232\241w\361\217\001\000\000\000\000\000\000p3\023\331i\324\336\216\217X\000\000\000\000t\307rw\200\376\350\373\001\0008\240\366%\021\000\000\300\037<\010\000\000\000\000$#\324\002\000\000\000\000P\026q[\000\000\310\340\352\\FW\237\217cN\337\227\323_\000j!\000C&;?\255\235j\000\000~2\212\002\000\240\036&\227\000\212\223\271k\316|x\000\240\026qP\020\337\003\220\234\256\026\000\000\000\240[\266\030\364\313\275\007\000\000\000\000\000\000\000\000\000\000\212\363\002\273\027\037\377]\026V#\000\000\000\000IEND\256B`\202"
+byte_jpeg: "\377\330\377\340\000\020JFIF\000\001\002\000\000\001\000\001\000\000\377\333\000C\000\003\002\002\003\002\002\003\003\003\003\004\004\003\004\005\010\005\005\005\005\005\n\007\010\006\010\014\013\r\014\014\013\014\013\r\017\023\020\r\016\022\016\013\014\021\027\021\022\024\024\025\026\025\r\020\030\031\027\025\031\023\025\025\025\377\300\000\013\010\002\000\002\000\001\001\021\000\377\304\000\037\000\000\001\005\001\001\001\001\001\001\000\000\000\000\000\000\000\000\001\002\003\004\005\006\007\010\t\n\013\377\304\000\265\020\000\002\001\003\003\002\004\003\005\005\004\004\000\000\001}\001\002\003\000\004\021\005\022!1A\006\023Qa\007\"q\0242\201\221\241\010#B\261\301\025R\321\360$3br\202\t\n\026\027\030\031\032%&\'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz\203\204\205\206\207\210\211\212\222\223\224\225\226\227\230\231\232\242\243\244\245\246\247\250\251\252\262\263\264\265\266\267\270\271\272\302\303\304\305\306\307\310\311\312\322\323\324\325\326\327\330\331\332\341\342\343\344\345\346\347\350\351\352\361\362\363\364\365\366\367\370\371\372\377\332\000\010\001\001\000\000?\000\214\234\032PiI\244\242\212]\324\340irh\rR+T\252\370\247\357\245\337N\017F\372]\370\244\3632jA(U>\265\021\227\232kKL\363}\350\363iD\264\341-H&\305H\263\373\324\253p}jArGz\177\3338\353@\274>\264\361u\357O\0275\"\334\373\323\205\317\275<\\{\320n)>\322j\304\027\033\272\232\264_+Lf\302\220k\027RCn\341\207CU\322rGZq\2234\306\222\2432\232g\231\223A\223\336\21734\206JC%4\312iU\362j\355\270\3322z\324\205\316i\245\351\014\230\025\004\227A{\324\rz1\326\240{\341\353P=\356\017Zcj\000\n\206MI@\353T\344\324\213\036:U\332)wR\023Fh\315(4\340i(\316\r=Z\244W\247o\245\017N\rN\017HZ\200qHd\2464\224\303%7\314\244\337J$\245\363iD\324\3616)~\320i\302\346\224\\g\2758\\g\275L\222\223S+\232x\220\212Q)\247\211\215/\233\357@\226\246\212\343i\353W\022\347#\255)\237\003\223T\365)\026hq\351Yhx\342\235\346qLi3Q\231*=\374\322\231(\3631Hd\244\363)\013\324\266\374\232\321\214\3601J\344\016\246\253\311:\257z\243s\177\331MR{\222{\324M1\365\250\332S\353P<\334\324/7\035j\264\222\346\253<\333{\327LzP:Rn\243u\033\250\3174\273\250\rK\223FiCS\203S\203\322\206\247\007\247\007\305\033\371\245y\376]\265\021\222\243i)\245\363I\276\232^\223\314\243\314\243\315\305\036u\036u\002ozx\237\336\236\263sW\240\230`f\254\t\206)\014\324y\330\245\022\322\371\264y\264\t\271\251\322\344\203\326\245i\367/\275R\236\354\200A\252\361\313\236i\346N*\007\223\006\230d\246\371\234\321\346R\357\342\230^\236\256(i2x\2536\347\0035k\317\013\365\252\2277\273{\363Y\322\335\227<\232\256\322\222i\205\251\245\2527z\257#\324-%A#\325i\0335\330R\023\332\233E!8\244\334h\335F\352]\324dS\203R\206\245\006\227q\245\337NW\2452`\346\243g\250\332Ja\222\223}4\311M/M2SL\224\206ZC-\'\235\212O?\236\265,nZ\244\022\355=jh\356\361\336\247[\300{\323\276\320\017zp\237\236\265*\3161\326\237\346\212<\317zO3\006\236\262\324\276~\0279\252\223\2711\261\250\340o\224S\336N\325\004\222S<\312B\3704\007\245\337M/@z\004\234\325\224\271\300\250\345\273<\340\325G\220\261\353L4\302i\245\2526z\211\336\253\310\365\0035B\357P\273Wg\272\233I\232BsII\272\215\324\204\321\223K\272\2245.\354\320\032\227u.\3727\322\027\246\263\324M%F^\223\314\3054\311M2SL\264\303%\'\231\232i\222\230\322\323|\341\353R\307w\264u\247\233\254\322}\247\336\225n\260z\324\213xA\353S%\336OZ\260\227\007\326\247\023\323\304\331\357K\346S\326Jt\262\341)\221\276\365a\355Q\206\3321Mi*\'\222\2432Ry\224\241\351w\363@bN)\3314\2314\214\307\035j2\331\344\322\026\246\226\246\226\250\331\252\'z\205\336\240\221\252\006z\205\336\242f\256\332\212i\034\322Q\232m\024\323I\2323F\357zUjR\331\245\317\275(8\372Swd\320\315Q3\324L\364\303&)\236fM#IQ\264\224\303%0\313I\346\361Li\2526\232\2433Q\347\373\322\375\242\234.)\302\177zQ?\275J\227\030=j\344W9\025a&\317z\224MR,\324\361=\023\\|\203\232X\'\001\251\322\266~aP4\225\033=3v{\322\356\2406)\353\223R \003\255=\230\001P\274\236\224\302\336\264\322\324\233\251\271\246\263TL\325\013\275B\315P\310\325\0035B\306\243f\256\352\212i\244\246\322\023\2123M&\233\272\202sM\335J\032\227&\215\306\224\271\30547zFz\205\336\242-Q<\230\240\034\nc=D\322Tm%Fe\2464\270\357Q\264\325\031\232\230f\246\371\336\364y\336\364\t\351\342zp\237\024\365\237\336\254\301s\216\365u.}\352d\270\007\275K\366\221\214\n\004\374\322I>H\024\370\345\251\342\234\262\220{S\036Nx\250\313f\220\032p4\340\325 \220\001\201M\363\010\243\314\315&i7SKR\003\232F8\250\231\252\'j\205\332\241f\250]\252\027j\211\215F\306\273\332)\010\246\232i\242\232\324\326\250\315\033\261M\335\315.\341F\356h-C\032\t\300\250\331\252\026j\215\233\002\242\316Ni\031\352\'z\205\244\250\232Z\211\245\367\250\332J\215\245\250\232Za\226\230f\367\2443P&\247\t\351\342ozQ=8\\\220j\304W\304w\253\221]\206\350j\312O\236\364\3617\275\006_\233\255M\034\276\365<s\205\310\365\246\371\2314\273\363J\030\346\235\272\22474\273\250\316i\303\326\224\234\323I\244\344\320N\005F\315Q3T.\325\023\032\211\232\241sP\261\250\330\324Lk\320@\315\007\255%4\214\032i\353IMji\024\306\246\2656\212i<\322\251\311\240\234\232k5F\315Q1\315E#v\246\023\201\212\215\336\241g\250]\352\006z\205\244\250\332\\T--F\322\324M-0\313M2\321\347c\275(\237\336\234\'\243\317\367\245\023\373\323\326z\263\025\311\007\255]\206\363\324\324\306\357\003 \323\222\343y\316jt\233\336\256[I\2703\036\302\215\374\324\210\371\247\207\247n\311\245V\305.sO^\234\323\263HZ\220s\364\245\'\322\230\315Q3TL\325\023\032\211\332\241cQ1\250\230\324lj&5\350T\354R\021\212i\031\246\322\021\212cR\036\225\031\024\3064\334\342\223<\3227\024!\3004\320s\223McQ\261\250\231\261Q\023\316i\214\325\013\265B\355\212\256\357P\273\324\017%@\362\324-%D\362\324FZa\226\232e\246\231\250\363\251D\324\276q\365\245\023T\2135L\223f\254G1\025a$\315Z\205\252\3325Z\206]\203o\2575(|\324\212qO\r\232z\234S\267f\244A\353O\315!jN\264\354\342\232Z\243f\250\331\252&5\023\032\211\315D\324\306\353Q5B\306\242j\364JU4\270\244#\024\3223HF)\204SH\301\2460\305Dz\324f\220\232k\036)G\335\307\255\007\201\212\215\215D\355P\263f\230\306\241w\250]\252\007z\256\357U\335\352\t$\252\357%B\362T,\365\031zazcIM\363)<\312O6\224KN\022\324\211%O\033\325\230\336\255F\365v\007\253\26169=\005$\022\031%f\317\025uMJ\0335*\232p9\342\246\214c\232\2234\026\246\347\326\227u\033\251\245\251\204\324lj&5\033\032\211\216i\215Q5F\325\013w\250\232\275\026\225i\324R\021M\"\232E0\212\215\205B\302\243<f\233Q\271\355R(\357C\360*\0278\250\034\347\212\214\234TN\330\250\035\252\t\036\253\310\365^G\252\362IU\244z\201\236\241g\250\331\252&\177Jazc=3}4\275\001\351CS\225\252dj\261\033U\230\216j\324f\255\306\370\306O\0258\221\345\033W\205\356j\344\n#P\005YV\251\220\323\303f\246V\002\244\022R\357\2442Q\276\215\324\273\251\t\246\223Q\261\250\330\324d\324li\215\326\243c\305D\375\3526\025\023W\243m\245\245\003\024\244f\220\216))\244sLe\250\230TN\265\013\na\342\243\333\271\305M\214S\034\324\016j\273\276*&z\201\336\241w\252\356\365]\332\253\273\325gz\256\355P\273f\242c\212\211\332\242f\250\331\3522\364\322\324\233\3517R\206\031\251\025\252x\3037AV\342\217\034\232\267\030\342\254\306\265j ;\212\266\214\000\251U\352t8\251C\323\326J\220IO\022Q\346\373\322\207\245\017N\rN\335\357F}\351\244\346\230M0\232\215\272SZ\243n\265\033\n\215\205F\302\243a\326\275\026\224\014R\321E\030\246\342\232\302\243aP\270\250XTL1MA\203\232{5B\355U\344j\250\355\223Q;\324\016\365\003\311U\336J\202I*\264\217U\335\352\007z\211\236\241g\250\231\352&\222\242/M-M-I\223J9\357R*\203V#\n*tp*\304r\n\261\034\225f9*\312IS,\225<n\005I\347\001\336\234&\315H&\251\026Zw\233NW\247\207\315=Z\244\rN\335K\232B\324\302i\r0\363L\"\230E1\205F\302\242aQ\265z5\024\243\255.(#4m\244\3051\2051\205B\313Q2\324.\264\3220~\225\0335@\355U\246~*\253\266*\007z\256\362Uy$\252\357%Wy*\274\222Uy%\250\036Z\201\346\250ZZ\214\313Q\264\264\303%4\311I\274R\031\200\240MO[\216jU\233\236\265:KV#\222\254\307%Y\216Z\260\263\342\245I\261\324\324\242\347\322\236\263{\324\2135J\262\324\202Z\221d\251U\352Uj\221Z\244\rN\rN\335HNh\305&)\010\246\025\246\221Q\260\250\330TL*&\025\350\373h\333KE\024\021E4\216j6\025\033-B\302\242e\346\240s\326\241sU\344j\247#e\217\265W\221\352\253\275V\222LUg\222\253\274\225]\345\252\322KU^_z\201\345\315@\362\324M5F\322\324fj\214\315M7 w\250\315\326\343\305 \232\236\262g\275H\257\212\235$\346\254F\365a\036\247Ijt\23352\315\212\220O\357R,\325*MS$\2652\313R\243\324\350\365:=L\255R\253T\212i\340\323\201\3158\014\323\200\243m!Zk\naZ\215\205D\302\242e\250\331k\321h\245\333K\214Q\212\010\315\030\342\233\326\243aQ\260\250\230T2\034f\2521\342\240s\305V\225\260\rSv\353Uez\251#\325Y\036\253\273\325i\036\252\311&\005T\222Nj\264\222\346\240y*\273I\315F\322T/-B\323TR\\m\025X\314X\346\245\216N\234qRy\200\232z\311\216\36527=jt\177z\2369\006z\324\302\340/J\221&\367\253\t!\306E;\3179\300\251\004\204T\2119&\247[\212\235%\3435\"\316sV\343\223\"\254#\325\204z\260\215S\241\251W\265H\265\"\255<-<-.\332\n\323\n\323\031j6Z\211\226\243e\250\231k\320\200\315(\245\242\212)\t\3056\232\302\243aQ\260\252\227\007\031\025U\317\025^C\305S\230\361\212\253)\300\252r\265R\225\352\254\257U\244j\256\357\305U\225\270\252R\275Vy1\232\254\362\324\r%D\362\324/%W\222j\211\2335\036\357zz9\351S!52sS\251\3058IR+\223\322\246B@\311\2530\260<\223R\233\214\016\264\370\347\035j\302\316\030`\324\201zm\251\023\336\255D\302\245\3075<O\322\255\306j\324f\254\245XJ\235\005J\242\246U\247\205\247\201N\333HV\232R\230R\230\311Q2Tl\225\023%w\324QE\024\207\2456\220\232i4\306\250\334\325\033\206\313\325i:Ui\032\251\316\334\212\253+qTfj\247+U9Z\253\310\325]\337\212\251+\3435JW\252\222\265V\221\352\273IP\263\324L\365\003\234\324{\371\346\214\344\325\210\227\003\232\220u\342\245N\225<hZ\245\362\202s\212O\264m8\305H%/O\022\025\243\315-\305X\204\026\305\\\211\017\025~(\211\0258A\214\032r\305\351R\205aRFpj\344\\\325\310j\344b\254\242\324\350\2652\255J\253R\005\247\205\247\355\243e!JaJc%F\311Q2TL\225\333\342\212(\244\316)\254i\t\342\233\232i9\2461\250\234\325\031O\314j\274\206\252\312j\225\301\252r\267\025RZ\251/J\247-U\220\325YZ\252J\325JS\232\253+UY\032\253\271\346\242f\250\235\252\027jfsNP\001\006\254\306\300\212\221P\325\230\200\003\236\265j\020*i\023r\361T\332\022\0335\"\260QM2n5,k\232\275l\274\212\326\266\20788\253\312\233E<&i\352\2305e#\0140j6\204\251\2530\257\025j1\212\273\r[\214U\204Z\235V\245T\251\025j@\224\360\224\357.\220\2450\2451\243\250\331*&\216\242d\256\306\220\212JF\351M\246\223\232BqM\246\261\2461\250%l\n\245!\371\215@\346\252\312z\325)\352\244\265VAUeZ\251*\325YW\025JaT\346\025JZ\247)\252\256\325\003\265B\306\242`j&SI\260\323\300\305O\030\3435b>je]\265b6\305YY\001Z\202y\006*\241r\306\244A\212\267\0078\255\030\006\010\255ky\000Z\230I\270\361W`_\227\232{\014T\261\020MN\360\356\\\322D\205N;U\224Z\271\n\325\330\226\255F\225:%J\251R\252T\253\035<GN\362\351\014t\323\0351\243\250\314u\023GQ4u\324\342\222\232z\32256\231H\324\332a<Tlj\264\315\301\252\223\034\021U\335\263U\3445R^sUd\351U\244\025ZAU\344Z\253*U\031\222\250\314\265FU\252R\255U\221*\273\245FS4\302\224\202-\306\245[n9\247\013qN\021`b\245D\305K\263\034\323\200\302\323\014\244qQ\026/OT\305<\n\275j\225s\004c\025r\3301\305h\303\031\3175u[h\247\240.j\3446\3479\305]\021e1Q\371u4Q\325\230\223\232\275\nU\270\322\254\"T\313\035L\261\324\253\035<GK\345\322\030\351\014t\306\216\243h\352&\216\242h\353\242\3054\214SZ\232zSi\224\215L4\306\250\230\325Y\272\032\2519\340Uv\250\036\253\311Ud\025^AU\234T\016*\264\242\251N\230\317\255R\2313T\246\213\006\252<$\324\022[\222:Ui-\330v\250\204\014{SZ\335\275)\004%y\247\214\221\3158-(Njh\343\315H\321\347\245\036Q\"\232ms\332\217\263\205\250\330c\212X\3275\243l\207\212\277\034[\253F\332 \242\255\252\342\247\216\022\325~\332\333\030\342\264\243\204\001\322\235\263\232x\264\317\"\237\035\276;T\313\026\rZ\205*\354Q\325\224\216\246X\352d\216\245X\351\342:_.\223\313\2441\323Lu\033GQ4u\023G[dS\010\246\221L\246\221L#\024\326\2460\250\336\242z\255/J\2530\310\252\317\322\253\275A\'J\254\342\253\310*\273\212\202A\332\253\3146\232\251*\344U7L\236\224\215i\275sU\336\325@\367\252\362[f\253\313j@\252\217\026\r4\246{SL9\246\371x\247\010\351DU2GR\204\247\005\002\225\206\005U\231\352\261\033\232\255\333\307\322\257G\205\253p\276j\374-\300\253\320\r\304V\255\264#\025q#\305Z\215\t\024\276Y\315\\\266^pzT\315\020\031\342\230\023&\255A\025^\212:\260\221\324\351\035J\261\324\253\035<GK\345\322\030\351\014t\323\0351\243\250^:\211\243\255B3M#\024\306\025\031\034\322\021L\2460\246\032\215\305B\365^A\234\325g\037)\252\316\274Uw\025\003\212\256\353U\335j\007Z\201\305T\236\240\010[\2654\301\267\250\250\336:g\221\236\325\033@=*\275\304 \n\315\222\034\267J\215\241\305FV\233\345\346\234#\247\010\352E\216\237\345\322\0049\245h\362*\224\361sQ\244|\325\270P\325\270\255\331\361W\355\354\310\253a6U\2730K\n\334\267R\024U\330\2235n$\251\204;\252x\341\305I\260\343\245\"E\226\351W\"\213\025n$\253I\035L\261\324\253\035J\261\323\304t\276^h\362\351\246:i\216\232\321\324-\035B\361\325\354sMa\232\214\212iZc-0\2574\302*2)\214\265\023\255Wu\346\252\3100MVa\305@\353P:\324.\265\003\245W\220b\253H*\263E\3466*x\340T^EW\235r\325\017\225\232_+\002\253J\270\252\223\216*\213\2475^U\250Dd\323\374\254R\371t\345\216\244\021\323\204y\247\030p*\t\001Z\201\327u5 %\272U\353{~\234V\234\021\252\343\212\270\230\307\024\216\233\252\335\204xa[\260  U\330\220\001Vc\\\232\271\024|T\301EN\226\373\322\204\266\301\351V\026*\263\024x\253*\225*%N\221\324\253\035<GK\345\322yt\206:C\0351\243\250\236:\205\343\251\231{\323OJa\031\246\221L\"\230\302\230Fi\214\265\023\n\215\305W\220u\252\262\2575]\305Wu\250]j\027\025\003\214Ui\005@\353\232H\3419\315$\240\364\025\017\222X\320a\300\351Q2\342\252\314\271\252r\307U\036:\201\242\246\371x\246\354\311\251\026\032p\213\025\"\303\232\225-\252Sm\305T\236\327&\241\026\307=*h\255=\252\334p\201S\210MI\024g5g\312\342\247\267\005Z\265m\344\300\025m&\344U\373c\270V\214*[\002\256%\251=\252\3240m\035)Z,\034b\225#\333V\021jx\3235e#\251\225*UJ~\312Q\035!\216\223\313\2441\323\014u\033GP<t\025\342\243aL#\232JmFFi\245i\244`\324\rQ8\250$\025ZE\252\362\n\201\305B\342\240\220Uw\025\003\2451!\336\370\253\r\010Q\212\250\321sI\263\025\034\213U\335*\264\221\325ic\252\317\025@\320\346\233\344P \247\254\\\324\351h[\265X\216\323\007\245M\344`Tn\224\303o\277\265F\326\330\355NHjh\355\311=*\3746y\035*\304zy\'\245X\376\315>\224\253\247\021\332\246[b\275\252xm\211n\225\263ijv\212\327\262\265\371\205l%\272\205\024\242\001\351H\326\204\256@\250Z\r\264\364J\265\032U\224J\231#\311\251V:x\214\232w\227G\227M1\322\030\351\215\035F\321\324/\037\265W#\212c\n\214\212i\030\246\036\264\312i\2467z\211\252\'\025\013\212\256\353\326\253\272\324\016\265\013\255A\"\325vJ\211\322\237o\036\0335#\241<\325w\206\241)QJ\270\252\355\326\240\221j\273\307\232\205\342\250\274\252i\213\332\224BOj\232\033R[\245h\301l\000\344S\232 \265\023/\025\003G\315H\221\361\322\221\341\311\351RCk\236\325v+@\017J\320\267\266\030\255\010mW\322\255%\262\2361S\215<2\364\246\377\000f\363\322\245\213M\301\351ZPZl\035*\334K\262\256DKU\225\030\305X\205s\236*\264\320\362p)\253\0175b8\361Vc\212\247H\261R\210\351\302:_.\217.\223\313\244)Q\262TL\225\023%Q \212k\naZ\214\2551\2050\212\215\252\'\250\350\306j)\027\030\252\362-@\351P:T\016\225\004\221\324.\225\003\361SB\231\\\324\336^EE$\\UG\217\232\206X\363U\314U\014\260\324>O=)\257\006{T_g\245\026\2715<v\203\322\247K`;T\353\036\005G\"T\014\265\026\314\232\261\024\031\355S\255\236{U\230m1\332\256GfOj\267\r\251\035\252\332\304EO\014D\265i\303\006W\245N\266\300\366\253P\331\203\216*Y-v\216\005$6\205\333\245i\305a\265A\3052X\366\232\261m\036EE,|\236)\212\234\325\210\343\253Q\307S,t\361\0358%.\312<\272\nS\nS\032:\211\243\250\231+,\214R\021M+Q\262\324l*2)\205j7J\214\245!Z\212E\310\250\035j\027Z\211\222\242x\270&\253\272\324\022\'\265VkfsVV\022\200\n\224-6D\342\251\272sPJ\234Ur\274\322\030\267S\r\276;SL<t\246y\0314\365\267\366\247\254x\251\002Pp*\031*\273)4G\0275~\336\037j\320\212\337=\252\314v\274\364\253\366\366\271\355V\326\323\332\245\026\236\325<6\240\036\225v80*\314Pd\364\253\360\333\340t\251\r\266\352\267ib\006\016*\324\261\204\\\n\316\232<\265I\022ah)\232\210\307\315O\024uj8\352uN)\341)Dt\276].\312B\224\302\224\306J\211\222\242d\254b\264m\246\225\250\331j6Z\215\226\231\267\332\232\313\305B\313\355I\214\212\211\227\232\205\326\241d\250\3313Q\230Kd\n\256\361\021Q\371%\216*9\023\313lT\351\036P\032f\317\232\207O\226\252<|\324M\026\352\214\332g\265\'\221\216\325\034\221\342\240+J\2503R\034\001L\306O\024\215\305B\315I\215\324\253\006jh\355\275\252\375\275\267N+J\013~\234U\370-A\307\025\243\r\237\035*\322Z{T\302\327\332\234\266\330=*\314v\376\325f(0zU\304\217\212\232(\262j\374Q\205Z\216d\316j\233\303K\345`P#\243\311\366\251#\213\025a\022\246T\251\004t\273)Dt\273)\245)\205)\214\225\023%D\311Xei6\322\021Q\260\250\331j&\024\334\032B\274T.\274\323\n\361Le\346\240t\353Q\025\246\225\250\231H\351Q2f\232\023\006\241\222\002\362\n\230\246\325\305F\023\232s&EU\226,TA0i[\030\252\362\021U\344\252\345y\240\360)\000,je\200\343\2452H\361U\334b\2265\311\253\260\301\273\265]\206\323=\252\364V\330\355W!\203\245iZ\333\3628\255h-\370\351V\226\337=\251\302\014v\251\005\276{T\221\303\203\322\254,\025*EV\342\203\275XU\300\246I\036j/\'\232i\207\255\'\223J\"\300\247\254u*\'\0252\245<-.\312v\3126SJSJS\031*&J\205\222\271\362)\010\246\221L#4\306Z\211\226\230V\220\212\211\327\232n\332\215\226\242e\250\2311L+Md\250\366S\014|\322\307\026^\222H\376cQ\230\351L|T\022G\315D\361`f\251\314\304\023U\334\361U\244<\323\027\223O\"\237\n\214\325\207eU\252r\311\223P0,j\305\264\004\232\326\267\203\201\305hC\020\305Z\216\034\325\373{n\231\025\247ol8\255\030\241\351Wb\267\310\250\347\214%6&\007\212\235R\254\242dT\321E\223W\343\207\345\241\242\307ja\216\223\312\246\010\363\232A\037\315N\020\323\274\234\nT\217\025:F\010\247\004\247\004\245\331G\227HR\232R\243d\250\231*\027J\346\312\363HV\232V\232V\232V\243e\250\212\323J\323Y*2\230\355M+Q2\324M\0357\313\2441\324~U4\305\315J\221aI\246\030}\251\276Oz\014<UY\242\301\252\357\323\232\241:rj\254\203\025\003&i\233piH&\237\0325=\241v\246-\233\023\310\251R\310\372U\310-v\342\264!\206\254\254Ej\325\270\371\205j@\006\005_\200t\255\010E[F\300\250n~q\305T@U\253B\001\270U\330\243\253QG\315]\215@\024\256\200\324E)\nb\243U\3114\251\026Z\247X2zT\302\327=\2527\266\330O\2455S\024\365^j@\224\276]/\227M1\323\031*6J\211\322\240u\256d\257\"\220\2557m!ZaZk%F\311L)\212iZiJ\215\223\025\033%3\313\346\223\313\2441dS\014T\323\0275:E\225\244x\275\2523\r7\313\250\245\266\334\017\025\233s\tL\326|\213UdL\232\211\223\002\241+\226\2531C\270U\230\340\000\325\270\355\224\216\224\343n\243\265\"\302)\3731S\301\324V\212B\035i\351\006\323W\355\326\264\255\342\253\261\200*U\025 \217p\250d\204\003R@v\232\321\205\206*\314L3V\324\340Q\273&\224.i\222\341F;\323c\217\214\324\210\2305n8\307\025e\"\343\212$\2040\252\222\301\264\322\004\311\002\234\252Cc\025(L\212<\272C\035F\311Q2TL\225\003\245r\305rh)M+M+HR\232V\231\345\323Lg\265Fc\244)Q\262Te)<\272Q\026i\306\016*?\'\236\224\277f\317j\235-\3601Mx=\252&\207\002\2411d\364\2474@/5\223|\203&\262fLf\251\270\346\233\345\356\024\317\263\363\232\2325\300\251\321I5m\024\342\235\260\232pLu\245\331\221K\032a\253^\311w\001W\305\266{T\320\300A\351W\243R\005J\271\251\322\244V4\343\031jE\217i\253\021\222*\304lA\253\3216V\227\222jTR\0053\313,\3715:G\305I\345b\247\205x\253H\274qO\362\370\250\245\207p\252\246\")\350\270l\232\230G\203\365\2451\323\032:\211\222\242d\250]*\027Z\3456\365\244\333HV\233\266\220\245&\312B\224\322\224\306\216\230R\243d\244\362\251DT\361\r/\223M0s\322\246\212\014.q\311\251\004>\324\326\2035\004\226\307\322\242\026\3705\r\312\355SXw|\261\254\351\222\252<9=)\321\303Nx@\025\026\334\032\236,\n\266\204T\234S\030\346\234\200\232\2368\211\"\265\354\240#\034V\254qqS\244 T\312\202\236#\251\243\2135j+l\324\257\000QQy[\217J\226;cV\222\314\236\325b;v^\325:[\373U\224\267\334\rBb\332\325$kR\371|T\221\245Z\212<\324\336_\313M1\346\2410ri\206,T\302 c\006\203\035F\351Q2TL\225\003\245@\351\\\230\\\232B\224\322\264\233h\333F\332B\264\322\224\322\224\302\224\337.\201\035(\213\006\244\021\323\204T\253\006\342\005X\026\375\251\377\000f\366\245\3738\003\245A4@U\031\210Z\313\272r\331\025\2274D\223T\345\200\372T?g>\224\323\036\323C.EBb\346\244H\rL\220\232\224\302\330\247GnX\363Wb\263\343\245Z\206\327\006\264\340\214(\253iS\016\3254hML\220\363W!\202\255\307\036)%\217u>\336\327q\346\256\245\250\004qW\340\265\004t\251M\250\035\251\014Ai\013\000\n\216\265\t\214\223NX\261R\205\247\242\325\230\205X m\3053\313\3159b\310\243\310\006\224[\3423Q\262Tl\225\023\245@\353P:T\016\265\311*rh)M+Ln)\204\322d\322\022OjBH\355H\034\032v\314\322\210\263\332\234\261sO\020\346\234!\247\210jx\255\370\315N\220SeP\202\252\274\330\252\223I\232\2412\027\252\262[\222j\007\265\366\252s[s\322\2416\376\325Z\342\337i\250V<\320` \364\251cLT\361\240&\255,\000\216\224\253\020SVcaV#\000\232\264\212jx\326\254\306\231\253\260\305\305YH\361VcZ\231V\244X\363V`\217\025v8\363\212\275o\026\005=\343\305D\361\344UB\271s\216\325\"-;m9S4\375\230\251c\371j`sO\013\232|kS\210\262)L_.*\264\221\341\210\250Y*\'J\256\351PH\265]\322\271EJ\nsQ8\305G\344\263\366\342\245\216\310\236\242\247\026*:\212q\265Lb\240}?q\310<UK\2133\021\310\351M\210\347\203V\243\217\"\245\020S\326\002)\353\016i\342\n\262\226\374b\2451l\025B\345rj\213\305UgP\200\223TL\377\0001\245\014\033\255#\306\010\252\262\3023Q\033l\325k\233S\203\305QH\266\266*\322\332\206\035)\032\323\035\251R\330\203\322\254*\2201Mh\311\247G\023U\330!<U\344\217\002\246D\346\256A\035^\2158\253\t\036j\324p\324\313\rK\034<\325\270\240\253q\305W\240\217\002\226D\250$N*\246\315\255O+\212]\271\024\365Z\220%;f*HS-V\n\000)\310\005O\022\2268\251\232,\n\2574[\371\357U\035*\027J\254\351P:T\016\265\311t\245\306\352AjX\202zU\264\215\021q\216iv\263}\321CG\265rMDH\346\231\235\243\2555\200\230`\325v\260 \345i\360\002\016\3229\025q#\315J\261S\274\2209\246\203\373\300\270\253\321G\232\216\341p8\254\371P\223U\246\0021\317Z\312\272%\363Y\315\031\006\214\343\275!\224\216\365^Y\315\021]d\200j\333\302&L\326{\331\355j\231#\332)\313\036\343V\026\327#\2451\355\210\355H\266\374\364\2530\333\017J\270\226\340\016\224\361\035O\034\031\253\220\301\322\257E\007\025:E\203V\342\217\"\254,T\360\273j\3045n 3W\341Q\266\2332T\014\234sU^?\232\224-;\313\247*b\245QO\333\305:\005\334x\251\261\3174\3409\251\241p\244\032\263#f\241u\252\222\246\032\253\310\265]\326\240u\252\322-rI\026z\325\210`\317QS\255\276\343\355M1\005ny\245yv\246\000\002\252\310\374\032\256\362s\3053\226\253\020\200\275z\324\341s\3151\321wt\344T\361.EL\252)\341x\250b@\323\326\224i\200j\031\3239\252rF\024\022k\"\350\226cT\236<\325ib\305Ux\352\026^1U\344\2175\000R\206\256\3059\333\212tG\314|\032\266-\262\264\301\016\326\253\360F\010\251\032\3207jE\261\366\251\226\323oj\225`\247\013nj\314P\343\265[\212.zU\350\241\342\206]\246\254\333\220j\3168\246\222\t\253v\350\010\251\217\312j\324\023T\347\014*)\000\002\253\0203@\\\323\266\322\355\366\245\003\006\235\232\261m\036\0018\245c\363\322\321\234sVa\1774\001\216E+\236qPJ\2719\250$Z\254\353\326\253\310*\254\213\232\346\342\213\247\025`D\002\322\214\225\300\351Q\263\n\202f\315T\221\262j\0223RF\000\247\261\3075*M\362\361NA\272\254\306\206\247\t\305<\247\313P[\014\\\034\326\232\016\265\004\343\000\232\311\236bMR\221wUgLUyc\315Vx\252\t\"\252\357\035B\321f\205\217mH\237+f\265m[z\212Y\342\333\3152\336\\6+f\3361\"\346\254\210\000\355G\221\236\224\345\265>\225\"\333\037J\236;SS\244A\0175edEZ\255q0\317\024\333{\214\032\274\'\312\324f_\232\256\333\334\020*o7q\253Pt\315O\346`S\013\222=\252,\363R(\310\247\201\3158.iB\n\221R\246C\201\212k\2009\240\0369\244\247\306\3463\305ZD\363~oZI!\300\342\253\311\017\0075Nd\252\262-Vu\254E\210(\346\225\200\305D\355\203\305F\303<\325Y\2175U\373\323)\350)\362\034-G\021\371j\354\003 U\310\226\247\301+N\031\306;Sb\200\031\301\253dl\004\324\022\266\365<V\\\320\222O\025\013CPI\rW\2221U\336!U\244\212\252\311\035Fc\243\312\315\036V*\325\237\312\325\250\320\371\211T\215\261G\316+F\326o,\000j\327\332\201\253V\356\017Z\274\035\000\240K\030\245k\224\003\212\2475\326O\025\020\231\333\275=Q\237\255O\024\004U\224\210\232\221m\311\253Q\307\216\rH\024g\255X\215\266\364\253\010\340\212k\276)\2523S%<\n\221E<\np\024\240PA\240\014\361J\242\244\013V\255\230\001\212\232@\rV\225x\254\371\207&\252H\265ZAX\254;\324\022\032\201\315D\362\235\265U\237&\242c\223ND\315J\022\231(\342\233j\240\234V\204I\263\212\265\037^\225(<S\251\321\360\300\366\251\032L\206\025\t\351PH\265ZH\352\273\245V\222:\256\353\315B\351\305V\222\032\204\307\212M\224\004\247\240\332sZV\263d\000j\331\205\\S>\313\216\224\364\2665a\020\245+3\323F\374\324\201\035\252E\267\365\247\205T\247\207\305<\\m\251\241\272\031\301\034U\243&W+K\346\026\247\243\022j\324`\342\246\\\324\2337S\202b\236\005H\253\232\220-H\005<-(_j6SJ\340\346\225V\244A\271\261R\355\362\237\212\230>\341QJx\254\371\207&\252H*\264\225\203#b\253\310\336\365VI*\006|\324ML\306MO\030\300\247\253\206\342\2310\300\300\252\251!\212lv\255\210O\230\200\347\232\231\\\212\220>i\340\323\203qH\030\002A=jE\204\236\275)\222\303\212\252\353\326\240\2211PH\225VD\250\035j\027Z\211\222\230R\223e.\334T\221\235\246\255\307pEX\027\030\024\345\272\247\213\241OY\367T\242Q\351R\254\240\016\224\033\216:TFM\306\227}*\266jd\251\343vC\355W\021\203\036>\265<C5n1\212\260\251R\005\247\005\247\004\251\021j@*EZ\220%.\337j1\212i\\\320\007\024\261\266\331*I\037sf\221e\002\233$\202\251\316\300\346\2529\250\034f\271\211d\346\253H\371\252\316\331\250\311\3057\255\003\255H\274\212p\\\034\323[\223UnW\0035-\215\371_\225\215h\3038cVU\363\326\236\030\032z\232IA\352;T\266\367\340\235\222\014\037Z\232V_QT\\d\237Jd\211\315Vt\252\356\225]\343\250Y*&J\214\256(\306{Q\262\220.\rH\271\025*\344\365\247\0049\247\204\251P\021S\24052t4\252\264l\347\245.\314\323\326<T\312\225:.EI\032\224<U\330:\n\271\030\253(*`\264\360\231\251\004t\341\035H#\247\254u(LS\204`\320b\030\250\314x\244\333\201M\333\223C\017\226\241|\202j\026\222\242s\232\201\352\026\357\\{\311\232\201\336\242\246\222))\t\3059^\244W\024\326<\324\027\' \325\030?\327V\304#f3\336\256\306\371\0252\217J\225N\r?\"\241d\005\263R\030\303\000{\212pBA\244\2220\3000\250\036:\257$~\325ZD\250\031*&J\205\222\231\267\024\273h\333O\013R*\324\212\rJ\221\237J\224GR*\324\250\2652\306\010\2441\001NT\247\252\324\252\225*-N\251\232\2321\203V\343\253Q\324\3503S*\324\252\265*\245<GN\013\212\260\260\222\271\305!\217\006\215\224\302\264\306J\214\2455\205@\365^A\305BzTOQ\265p\354j&5\0319\244\'\024\323 \025\021\233\232U\2274\377\0007\024\tri\2237\025Q\016\311+E&%j\344\022f\255#\324\252\324\360\364\243\223O\007\212\2221\332\235\267\265F\361{Uw\217\322\253I\037\265W\222?j\201\343\305B\311M)M+@Zr\255H\027\025\"/5n%\006\246\362\275)6c\322\236\252EJ\242\244\013\232_+\035)\312\225*\245H\213S\242\324\352\225<k\212\262\202\254F*\302.jeZ\231\022\247H\351\306 \010\251D\204\361\214\nk\016\364\233r)\254\240\236\0050\2450\255D\351U\344Z\257 \252\356*\027\025\023\n\341\031\25264\322qPK8\\\325f\230\2650\313\357H\263b\236\'\315*\334sOi7\n\206N\001\307Z[\031\313K\261\217^\225\267\032\354\0252I\330\365\251\325\252E\251W\212\221\006jU\025\"\212\034\014sT.n\022&\344\324>ha\232c ~j\031#\307AU\232>i\2062i\205\r\002:v\334R\201R-M\033\225\253\013&jA\315=\0275:\'\265J\251O\t\355N\021\323\202\342\244T\251\343Z\231V\245\214sVPT\361\216j\324kV\0213V\022:\235\023\024\375\231#\212s\306\245r8\2461%pzS1\305!\036\324\205i\214\265\033%W\221*\254\251UdZ\201\305B\325\300\323H\342\253O.8\025FW9\353P\264\330\025\021\230\323|\343Hd=\215:9ry\353VR\\\361S\2140\252\262!I\003/Z\277m\2517\n\365\253\t\3362*\312p*dqS)\251\226\245J\224S_\245a\352\243a8\247\330\035\361\014\325\257$\036\234S\036\003\212\256\360\221Q\030i\206 )\276_4\2051F\312P\264\361\305J\246\247CS\245O\035X\\R\216M<\nu=W\035*T\251\226\246E\253\0101S\306*\324b\256B\225r(\352_.\247\362\007\225\273\275C\263\212\211\206)\224\2704\230\246\225\250\331j\027J\253*UI\022\253:\325w\025\347\354y\250\345}\252k:Yy&\251\313&\343P1\346\230\315Q\227\240IO\rS\307&O=j\324-\272\245)\236\325\033E\264\346\254\301\250<\000\000kWO\324\022\340\341\2705\263\0241\270\317\002\235\345\242\203\363t\244F\007\241\251\325\2058\276\005W\236\344F2Mc^\334\375\245\225\027\361\253\266\221\354@1V\324\342\234FED\351P\264^\325\023G\355L)\212i\216\223e&\314R\205\247\001R\257\025*5N\217S\243\324\252\325\"\265=MH\265\"\363R/\025a*\304|\325\230\326\254\307W!5v#VUwt\024\256\314\006\337J\210\364\250[\255%\024\204f\234 ,\244\324\014\270\250\234UiEU\221j\254\213Ud\025\347\214pj\245\324\200\014V\\\262d\232\256Z\243f\250\231\2522\324\320\374\323\325\352dj\267\003\340\212\322\210\202\240\322K\206\250\2149\251\255\255\331\033p\342\265c\274\221\027\003\255#\3133)!\272\324V\367\363[d0\310\253qkh[\014pjw\326`H\311\363\006~\265\217>\251%\353\225\214\034z\325\353\0132\230g\373\306\264\325p1K\322\2241\241\2449\373\274Sw\016\342\202\001\351Lh\251\246\032O&\220\301G\223G\225\212P\264\345\024\361\221R+b\246F\311\251\224\344\212\235\005N\213\212\225S\320T\212\265*.\rN\202\254\306j\314f\255Dj\334OVc\220\251\3104\255&\343Q;\324e\371\240\034\320ib\033\233\025\240\001\021`\016\325\2350\303\032\201\306j\264\253Ud\025ZJ\251 \2576\231\202\2515\223u)$\325\'j\211\232\243-\232\214\324m\322\233J\0175<f\255B\325z)\360)\336v\343SF\333\210\253q\270\002\254\3047\366\253\001\0061M{p{U\033\213\020\347\212\202=\037{rN+b\317NH@\300\255\001\036\301\322\227\267\024c\232p\024\273iB\003NXA\355O\362\007\245\rn1\326\231\344\201Hb\002\232c\2441f\230b\244\333\2121N\013R\242\325\230\326\254\306\265a\023\322\247D\251\226<\232\177\222T{T\210\271\251UH54f\254\306\325f9*u\222\237\276\232Nj3J\247\006\245Q\221H3\033\003V\205\350+\357U%m\314MBzT2.j\244\253U$Z\253\"\327\224\335\315\201\212\312\226L\232\256\315Q1\246\323OZ\215\251\000\315(\030\251\025\200\251\243~j\302\276i\310\304\032\267\013\346\255\306\303p\347\212\277\014\300\014\n\262\214\0174\362i6\206\251\"\217\034\325\324\000\001\212w^\246\215\271\245\tN\n\005;fi\301\r=V\236\026\227o=\351\031i\2733M1\320#\2440\347\2450\302A\351H\"\366\247\010\351\351\0375b4\253\010\265f5\253\021\246j\314qz\324\252\230\351\322\206\213\003+H\255\316\017Z\230\'q\322\236\274T\250\330\251\221\263O\017\203O\r\232\033\2455[\034T\3610\251\016\rD\313\203\3050\323\030qQ\277J\255(\315T\220UiEx\265\314\333\211\025E\332\242cQ\223I\232BqM<\322\001\212B\324\201\352X\332\254#\325\230\333\"\247N9\355Vbj\271\021\253(\307\275N\231j\262\2126\373\324\311\200\270\251\0014\365\355\232\220\014\323\202\323\300\247\005\247\252\323\300\3158-;m(Z<\261Mh\307jE\213u8\304\005&\317Q\212a_^\224\2421OXjU\217\025<q\325\230\343\315XE\305L\271\247\357\300\240\036sH\3007=\351br\247\006\254\014\021\305=G\025*\214S\361\232Q\221O\034\212t(\013\363JWk\234t\251\003\034S\031\251\224\326\250\236\241a\201U\245Z\253\"W\203\310\371\250\035\252&jnE\033\251\245\251\245\251\205\350\316h\025\"\232\225\rZ\211\252\334g5eW\270\251\342\220\216\r\\\213,\001\253q\034\001S\253T\253S-H\242\244QR\250\315=V\236\026\236\026\224-<-8-\024\231\243\"\232z\361K\270\201H_\212L\212U\343\216\242\246A\221S\'\270\251\320\016\302\245_z\2208\003\212]\364\241\363J\037\024\360\340\365\247\251\007\203R!+\364\2531\262\277\035\rL\243\232\224%9\025s\363\016)\300\004\'\035(U\357\212\010\244&\232Ni\204\322\023\212\215\271\250\233\245B\353U\234f\276|\221\352\026j\214\2657u!zazizij\003T\213\315J\265*\n\263\020\253\221\n\267\030\315ZD\351VPb\254Fj\314c5:-J\253R(\305L\2435\"\256)\341M8\002*A\332\235\212v)\r4\232JL\373Ro\244\355\221M\335J\032\234\rH\222\021S\3078\3163S\2111N\017\232psO\r\232pjpj7\342\236\262v\251\242\227\362\253\010CU\224\227\240?\235YC\221\357R\014c\212p\031\245\351H\303\"\242e#\2550\232o^\235i\247\2554\212\215\2522*)#\3348\257\233\335\25265\0314\302i\245\251\205\263I\232)\352*D\251\220g\025:%Z\215j\334KV\342Z\267\030\253Q\255XE\251\321ju\315J\253\232\225V\245U\251TS\300\247\016iq\212\\\320M4\232M\302\220\260\365\246\227\246\223\201G\033}\351\275\006iK`f\200\365\"6i\331$\361S#\021\214\232\225^\244\017\357R+S\367Q\272\235\273\245\001\251\352\344T\321\315\212\264\223\202*\302O\264{U\230\346\3175:J\033\332\202\302\232_\267j\031\262*3\300\244\355HNG?\2350\212iZk-F\312+\346Ri\204\323\t\2461\246\023M\'\212\007Jr\255J\253R\242\324\350\265b5\253Q%[\211*\334kVcZ\262\202\254F*\302-N\213S*\324\252\265\"\255J\253N\013N\330)\247\212ijizizizn\372B\324\007\243u8`\322\207+\3068\244p\007#4\261\203\214\324\252F*D qR)\346\236\265*5I\232ZQ\234\322\322\203J\033\006\244Y*x\347\343\025f9\360\006\rN\263dpjQq\221\203Jd\244\363qG\231AnsJ_\002\230_\024\323\'\277\024\214EF\314+\3462\324\322i\214i\204\342\233E*\255J\253R\252\324\250\225:%Z\212:\267\024uj5\253Q\245Y\215*\302%N\213V\021j\302-L\213R\250\251\002\323\200\3059M+\032\214\236j6\246\022sL-I\232Bi\271\244\335@jr\271\247\2065*\226\306\010\342\210\300bGj~\006\r\nqR+t\251T\324\253\322\244QO\002\235E\024R\214\323\303S\322R8\251\322oz\225f\247\211\251|\341\353K\346\323\274\332\014\224\335\364\322\364\323!\025\033I_4n\244&\232Ni\204\344\321\214\323\200\305=EJ\253\232\225W\025<b\254D\225n%\253Q\255X\215j\314b\255F*\302-X\215jtZ\231V\246QR\250\305?8\243u!4\323!\006\232\322\006\034\365\246\023L\335M&\233Fi\244\323I\243u8\032\221\rL\030\016\364\200\220x\245\031=jE\030\247\216\203\212\225MH\255S+S\301\305(j\\\323\205\004f\226\220\212@{S\303\021N\022\363O\022\323\274\332O7\006\236&\367\247y\224\236m/\233\232B\331\025\023\032\371\2674\204\346\232M%:\224u\251\020T\310*eZ\2365\253Q%Z\215j\324kV\021*\302-Y\215j\312-N\202\254 \251TT\2521N\007\024n\315\005\2517R\023\232i\3054\214\323\0104\334\342\232X\032\013\000)\244\203\3104\224c\024\243\216\264\365$t\247\214\324\313O\247-=M<\021\212z\236*ElT\212\324\241\251\343\245=i\300R\354\240\212\214\365\245\316\r\000PN)7b\223}\002\\w\247\211\251\014\264\202njA6E\014\365\377\331"
+byte_png: "\211PNG\r\n\032\n\000\000\000\rIHDR\000\000\002\000\000\000\002\000\010\000\000\000\000\321\023\213&\000\000\001GIDATx^\355\3359\016\2000\014E\301(\367?\263E\377+\220\034D\310L\371n`9\313\030\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\300\013f\006\000\000\000\330\234Y\027\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\240\213\227\034\000\000\000\200\273*\003\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000l\2402\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\234\2552\000\000\000\000\000\000\000\000\000\000\000\000<43\000\000\000p\204\206\373\352FJ\000\000\000\000\000\000\200\337h\330\"\003\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000@/\377$\001\000\000\000\000\000\000\000\000\000,\344\260&\000\000\000\000\000\000\000\000\000\000\000_p\001c\233\005\005v\341\233P\000\000\000\000IEND\256B`\202"
diff --git a/core/res/res/anim-watch/rounded_window_enter.xml b/core/res/res/anim-watch/rounded_window_enter.xml
new file mode 100644
index 0000000..b9ec8d6
--- /dev/null
+++ b/core/res/res/anim-watch/rounded_window_enter.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2021, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT 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 xmlns:android="http://schemas.android.com/apk/res/android"
+     android:zAdjustment="normal" android:hasRoundedCorners="true" android:shareInterpolator="false"
+     android:detachWallpaper="true">
+    <scale android:fromXScale="0.75" android:toXScale="1.0"
+           android:fromYScale="0.75" android:toYScale="1.0"
+           android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true"
+           android:pivotX="50%p" android:pivotY="50%p"
+           android:interpolator="@interpolator/rounded_window_interpolator"
+           android:duration="250"/>
+    <alpha android:fromAlpha="0.25" android:toAlpha="1.0"
+           android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true"
+           android:duration="150"
+           android:interpolator="@android:interpolator/rounded_window_interpolator"/>
+</set>
diff --git a/core/res/res/anim-watch/rounded_window_exit.xml b/core/res/res/anim-watch/rounded_window_exit.xml
new file mode 100644
index 0000000..757b851
--- /dev/null
+++ b/core/res/res/anim-watch/rounded_window_exit.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2021, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT 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 xmlns:android="http://schemas.android.com/apk/res/android"
+     android:zAdjustment="normal" android:hasRoundedCorners="true" android:shareInterpolator="false"
+     android:detachWallpaper="true">
+    <scale android:fromXScale="1.0" android:toXScale="0.75"
+           android:fromYScale="1.0" android:toYScale="0.75"
+           android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true"
+           android:pivotX="50%p" android:pivotY="50%p"
+           android:interpolator="@interpolator/rounded_window_interpolator"
+           android:duration="250"/>
+    <alpha android:fromAlpha="1.0" android:toAlpha="0.0"
+           android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true"
+           android:duration="250"
+           android:interpolator="@android:interpolator/rounded_window_interpolator"/>
+</set>
diff --git a/core/res/res/color-night/notification_expand_button_state_tint.xml b/core/res/res/color-night/notification_expand_button_state_tint.xml
new file mode 100644
index 0000000..a794d53
--- /dev/null
+++ b/core/res/res/color-night/notification_expand_button_state_tint.xml
@@ -0,0 +1,21 @@
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_pressed="true" android:color="@android:color/system_on_surface_dark" android:alpha="0.06"/>
+    <item android:state_hovered="true" android:color="@android:color/system_on_surface_dark" android:alpha="0.03"/>
+    <item android:color="@android:color/system_on_surface_dark" android:alpha="0.00"/>
+</selector>
\ No newline at end of file
diff --git a/core/res/res/color-watch/btn_watch_default_dark.xml b/core/res/res/color-watch/btn_watch_default_dark.xml
index 68b0eb6..333b44b 100644
--- a/core/res/res/color-watch/btn_watch_default_dark.xml
+++ b/core/res/res/color-watch/btn_watch_default_dark.xml
@@ -17,6 +17,6 @@
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
     <item android:state_enabled="false"
           android:alpha="?attr/disabledAlpha"
-          android:color="?attr/colorPrimaryDark"/>
-    <item android:color="?attr/colorPrimaryDark"/>
+          android:color="?attr/colorSurface"/>
+    <item android:color="?attr/colorSurface"/>
 </selector>
diff --git a/core/res/res/color-watch/global_actions_container_background.xml b/core/res/res/color-watch/global_actions_container_background.xml
new file mode 100644
index 0000000..efa4d88
--- /dev/null
+++ b/core/res/res/color-watch/global_actions_container_background.xml
@@ -0,0 +1,19 @@
+<?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.
+  -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:color="@android:color/black"/>
+</selector>
\ No newline at end of file
diff --git a/core/res/res/color-watch/overview_background.xml b/core/res/res/color-watch/overview_background.xml
new file mode 100644
index 0000000..48ad0e7
--- /dev/null
+++ b/core/res/res/color-watch/overview_background.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <!-- customizing to black for watches as this is used as the background for task transitions
+         (and WindowContainer fallback color) and all themes on watches are typically dark for
+         power savings -->
+    <item android:color="@android:color/black"/>
+</selector>
\ No newline at end of file
diff --git a/core/res/res/color-watch/switch_track_watch_default_dark.xml b/core/res/res/color-watch/switch_track_watch_default_dark.xml
index 15bbeda..5af2566 100644
--- a/core/res/res/color-watch/switch_track_watch_default_dark.xml
+++ b/core/res/res/color-watch/switch_track_watch_default_dark.xml
@@ -17,6 +17,6 @@
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
     <item android:state_enabled="false"
           android:alpha="?attr/disabledAlpha"
-          android:color="?android:colorPrimaryDark" />
-    <item android:color="?android:colorPrimaryDark" />
+          android:color="?android:colorSurface" />
+    <item android:color="?android:colorSurface" />
 </selector>
diff --git a/core/res/res/color/letterbox_background.xml b/core/res/res/color/letterbox_background.xml
deleted file mode 100644
index 955948a..0000000
--- a/core/res/res/color/letterbox_background.xml
+++ /dev/null
@@ -1,19 +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.
-  -->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:color="@color/system_neutral1_500" android:lStar="5" />
-</selector>
diff --git a/core/res/res/color/notification_expand_button_state_tint.xml b/core/res/res/color/notification_expand_button_state_tint.xml
new file mode 100644
index 0000000..67b2c25
--- /dev/null
+++ b/core/res/res/color/notification_expand_button_state_tint.xml
@@ -0,0 +1,21 @@
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_pressed="true" android:color="@android:color/system_on_surface_light" android:alpha="0.12"/>
+    <item android:state_hovered="true" android:color="@android:color/system_on_surface_light" android:alpha="0.08"/>
+    <item android:color="@android:color/system_on_surface_light" android:alpha="0.00"/>
+</selector>
\ No newline at end of file
diff --git a/core/res/res/drawable-watch/global_actions_item_grey_background_shape.xml b/core/res/res/drawable-watch/global_actions_item_grey_background_shape.xml
index f2df319..3ac9ffba 100644
--- a/core/res/res/drawable-watch/global_actions_item_grey_background_shape.xml
+++ b/core/res/res/drawable-watch/global_actions_item_grey_background_shape.xml
@@ -18,5 +18,5 @@
 <shape xmlns:android="http://schemas.android.com/apk/res/android"
        android:shape="rectangle">
     <corners android:radius="26dp"/>
-    <solid android:color="@color/wear_material_grey_900"/>
+    <solid android:color="?attr/colorSurface"/>
 </shape>
\ No newline at end of file
diff --git a/core/res/res/drawable-watch/global_actions_item_red_background_shape.xml b/core/res/res/drawable-watch/global_actions_item_red_background_shape.xml
index 4f23700..b85e01d 100644
--- a/core/res/res/drawable-watch/global_actions_item_red_background_shape.xml
+++ b/core/res/res/drawable-watch/global_actions_item_red_background_shape.xml
@@ -18,5 +18,5 @@
 <shape xmlns:android="http://schemas.android.com/apk/res/android"
        android:shape="rectangle">
     <corners android:radius="26dp"/>
-    <solid android:color="@color/wear_material_red_mid"/>
+    <solid android:color="?attr/colorError"/>
 </shape>
\ No newline at end of file
diff --git a/core/res/res/drawable-watch/input_method_item_background.xml b/core/res/res/drawable-watch/input_method_item_background.xml
new file mode 100644
index 0000000..1097773
--- /dev/null
+++ b/core/res/res/drawable-watch/input_method_item_background.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        xmlns:aapt="http://schemas.android.com/aapt"
+        android:width="172dp"
+        android:height="52dp"
+        android:viewportWidth="172"
+        android:viewportHeight="52" >
+
+    <group>
+        <clip-path
+                android:pathData="M26 0H146C160.359 0 172 11.6406 172 26C172 40.3594 160.359 52 146 52H26C11.6406 52 0 40.3594 0 26C0 11.6406 11.6406 0 26 0Z"
+        />
+
+        <path
+                android:pathData="M0 0V52H172V0"
+                android:fillColor="#262523"
+        />
+    </group>
+</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable-watch/input_method_item_background_selected.xml b/core/res/res/drawable-watch/input_method_item_background_selected.xml
new file mode 100644
index 0000000..17a4c2b5
--- /dev/null
+++ b/core/res/res/drawable-watch/input_method_item_background_selected.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        xmlns:aapt="http://schemas.android.com/aapt"
+        android:width="172dp"
+        android:height="52dp"
+        android:viewportWidth="172"
+        android:viewportHeight="52" >
+
+    <group>
+        <clip-path
+                android:pathData="M26 0H146C160.359 0 172 11.6406 172 26C172 40.3594 160.359 52 146 52H26C11.6406 52 0 40.3594 0 26C0 11.6406 11.6406 0 26 0Z"
+        />
+
+        <group
+                android:translateX="0.49"
+                android:translateY="-5.604"
+                android:pivotX="86"
+                android:pivotY="26"
+                android:scaleX="2.924"
+                android:scaleY="22.357"
+                android:rotation="45.256" >
+
+            <path
+                    android:pathData="M0 0V52H172V0" >
+
+                <aapt:attr name="android:fillColor" >
+                    <gradient
+                            android:type="linear"
+                            android:startX="43"
+                            android:startY="26"
+                            android:endX="129"
+                            android:endY="26" >
+                        <item
+                                android:color="#00202124"
+                                android:offset="0" />
+                        <item
+                                android:color="#80FCF7EB"
+                                android:offset="1" />
+                    </gradient>
+                </aapt:attr>
+            </path>
+        </group>
+    </group>
+</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable-watch/input_method_item_background_selector.xml b/core/res/res/drawable-watch/input_method_item_background_selector.xml
new file mode 100644
index 0000000..1d8786f
--- /dev/null
+++ b/core/res/res/drawable-watch/input_method_item_background_selector.xml
@@ -0,0 +1,21 @@
+<?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.
+  -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+  <item android:state_selected="false" android:drawable="@drawable/input_method_item_background"/>
+  <item android:state_selected="true" android:drawable="@drawable/input_method_item_background_selected"/>
+</selector>
diff --git a/core/res/res/drawable/expand_button_pill_bg.xml b/core/res/res/drawable/expand_button_pill_bg.xml
index f95044a..a14d33c 100644
--- a/core/res/res/drawable/expand_button_pill_bg.xml
+++ b/core/res/res/drawable/expand_button_pill_bg.xml
@@ -13,7 +13,17 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<shape xmlns:android="http://schemas.android.com/apk/res/android">
-    <corners android:radius="@dimen/notification_expand_button_pill_height" />
-    <solid android:color="@android:color/white" />
-</shape>
\ No newline at end of file
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:id="@+id/expand_button_pill_colorized_layer">
+        <shape xmlns:android="http://schemas.android.com/apk/res/android">
+            <corners android:radius="@dimen/notification_expand_button_pill_height" />
+            <solid android:color="@android:color/white" />
+        </shape>
+    </item>
+    <item>
+        <shape xmlns:android="http://schemas.android.com/apk/res/android">
+            <corners android:radius="@dimen/notification_expand_button_pill_height" />
+            <solid android:color="@color/notification_expand_button_state_tint" />
+        </shape>
+    </item>
+</layer-list>
\ No newline at end of file
diff --git a/core/res/res/drawable/ic_bluetooth_share_icon.xml b/core/res/res/drawable/ic_bluetooth_share_icon.xml
deleted file mode 100644
index 6acfd57..0000000
--- a/core/res/res/drawable/ic_bluetooth_share_icon.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<!--
-     Copyright (C) 2019 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<!-- This drawable should only be used by the Bluetooth application for its share target icon. -->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="24dp"
-    android:height="24dp"
-    android:viewportWidth="24"
-    android:viewportHeight="24"
-    android:tint="@*android:color/accent_device_default_light">
-
-    <path
-        android:fillColor="@android:color/white"
-        android:pathData="M17.71,7.71L12,2h-1v7.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L11,14.41V22h1l5.71-5.71L13.41,12L17.71,7.71z M13,5.83 l1.88,1.88L13,9.59V5.83z M14.88,16.29L13,18.17v-3.76L14.88,16.29z" />
-</vector>
diff --git a/packages/SystemUI/res/drawable/stat_sys_managed_profile_status.xml b/core/res/res/drawable/stat_sys_managed_profile_status.xml
similarity index 100%
rename from packages/SystemUI/res/drawable/stat_sys_managed_profile_status.xml
rename to core/res/res/drawable/stat_sys_managed_profile_status.xml
diff --git a/core/res/res/interpolator-watch/rounded_window_interpolator.xml b/core/res/res/interpolator-watch/rounded_window_interpolator.xml
new file mode 100644
index 0000000..c322169
--- /dev/null
+++ b/core/res/res/interpolator-watch/rounded_window_interpolator.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2021, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
+                  android:controlX1="0.4"
+                  android:controlY1="0"
+                  android:controlX2="0.2"
+                  android:controlY2="1" />
diff --git a/core/res/res/layout-watch/app_anr_dialog.xml b/core/res/res/layout-watch/app_anr_dialog.xml
new file mode 100644
index 0000000..f9605af
--- /dev/null
+++ b/core/res/res/layout-watch/app_anr_dialog.xml
@@ -0,0 +1,44 @@
+<?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.
+  -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical"
+    android:showDividers="middle"
+    android:divider="@drawable/global_action_item_divider">
+    <Button
+        android:id="@+id/aerr_close"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="@string/aerr_close_app"
+        android:drawableStart="@drawable/ic_close"
+        style="@style/aerr_list_item"/>
+    <Button
+        android:id="@+id/aerr_wait"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="@string/aerr_wait"
+        android:drawableStart="@drawable/ic_schedule"
+        style="@style/aerr_list_item"/>
+    <Button
+        android:id="@+id/aerr_report"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="@string/aerr_report"
+        android:drawableStart="@drawable/ic_feedback"
+        style="@style/aerr_list_item"/>
+</LinearLayout>
diff --git a/core/res/res/layout-watch/app_error_dialog.xml b/core/res/res/layout-watch/app_error_dialog.xml
new file mode 100644
index 0000000..8857b5f
--- /dev/null
+++ b/core/res/res/layout-watch/app_error_dialog.xml
@@ -0,0 +1,58 @@
+<?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.
+  -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical"
+    android:showDividers="middle"
+    android:divider="@drawable/global_action_item_divider">
+    <Button
+        android:id="@+id/aerr_restart"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="@string/aerr_restart"
+        android:drawableStart="@drawable/ic_refresh"
+        style="@style/aerr_list_item" />
+    <Button
+        android:id="@+id/aerr_app_info"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="@string/app_info"
+        android:drawableStart="@drawable/ic_info_outline_24"
+        style="@style/aerr_list_item" />
+    <Button
+        android:id="@+id/aerr_close"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="@string/aerr_close_app"
+        android:drawableStart="@drawable/ic_close"
+        style="@style/aerr_list_item" />
+    <Button
+        android:id="@+id/aerr_report"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="@string/aerr_report"
+        android:drawableStart="@drawable/ic_feedback"
+        style="@style/aerr_list_item" />
+    <Button
+        android:id="@+id/aerr_mute"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="@string/aerr_mute"
+        android:drawableStart="@drawable/ic_eject_24dp"
+        style="@style/aerr_list_item" />
+</LinearLayout>
diff --git a/core/res/res/layout-watch/global_actions.xml b/core/res/res/layout-watch/global_actions.xml
index d8e569b..e97e7f2 100644
--- a/core/res/res/layout-watch/global_actions.xml
+++ b/core/res/res/layout-watch/global_actions.xml
@@ -17,7 +17,8 @@
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:clipToPadding="false"
-    android:fillViewport="true">
+    android:fillViewport="true"
+    android:background="@color/global_actions_container_background">
     <LinearLayout
         android:id="@+id/actions_container"
         android:layout_width="match_parent"
diff --git a/core/res/res/layout-watch/global_actions_item.xml b/core/res/res/layout-watch/global_actions_item.xml
index 3d3f341..021c9ab 100644
--- a/core/res/res/layout-watch/global_actions_item.xml
+++ b/core/res/res/layout-watch/global_actions_item.xml
@@ -15,21 +15,18 @@
 -->
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
         android:layout_width="match_parent"
-	android:layout_height="wrap_content"
+        android:layout_height="52dp"
         android:gravity="center"
-        android:minHeight="52dp"
         android:minWidth="172dp"
-        android:paddingStart="12dp"
-        android:paddingEnd="12dp"
-        android:paddingTop="6dp"
-        android:paddingBottom="6dp"
+        android:paddingStart="14dp"
+        android:paddingEnd="14dp"
         android:background="@drawable/global_actions_item_grey_background">
 
     <ImageView android:id="@+id/icon"
             android:duplicateParentState="true"
             android:scaleType="centerInside"
             android:gravity="center"
-            android:layout_marginEnd="8dp"
+            android:layout_marginEnd="6dp"
             android:layout_width="24dp"
             android:layout_height="24dp"/>
 
@@ -39,7 +36,7 @@
             android:textSize="15sp"
             android:letterSpacing="0.013"
             android:fadingEdgeLength="12dp"
-            android:textColor="@android:color/white"
+            android:textColor="?attr/textColorPrimary"
             android:layout_weight="1"
             android:fontFamily="google-sans-text-medium"
             android:layout_width="wrap_content"
diff --git a/core/res/res/layout-watch/grant_credentials_permission.xml b/core/res/res/layout-watch/grant_credentials_permission.xml
new file mode 100644
index 0000000..5012b54
--- /dev/null
+++ b/core/res/res/layout-watch/grant_credentials_permission.xml
@@ -0,0 +1,152 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * Copyright (c) 2008, Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:divider="?android:attr/dividerHorizontal"
+    android:showDividers="middle"
+    android:dividerPadding="0dip"
+    android:theme="@style/Theme.DeviceDefault"
+    android:background="?attr/colorBackground">
+
+    <!-- The list of packages that correspond to the requesting UID
+    and the account/authtokenType that is being requested -->
+    <ScrollView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:fillViewport="true"
+        android:layout_weight="1"
+        android:gravity="top|center_horizontal">
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:paddingTop="36dip"
+            android:orientation="vertical">
+
+            <TextView
+                android:id="@+id/grant_credentials_permission_message_header"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/grant_credentials_permission_message_header"
+                android:textAppearance="?android:attr/textAppearanceMedium"
+                android:paddingStart="28dip"
+                android:paddingEnd="20dp"
+                android:paddingBottom="12dip" />
+
+            <LinearLayout
+                android:id="@+id/packages_list"
+                android:orientation="vertical"
+                android:paddingStart="16dip"
+                android:paddingEnd="12dip"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content" />
+
+            <RelativeLayout
+                android:paddingStart="16dip"
+                android:paddingEnd="12dip"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content">
+
+                <ImageView
+                    android:id="@+id/permission_icon"
+                    android:layout_width="30dip"
+                    android:layout_height="30dip"
+                    android:src="@drawable/ic_bullet_key_permission"
+                    android:layout_alignParentStart="true"
+                    android:scaleType="fitCenter" />
+
+                <TextView
+                    android:id="@+id/account_type"
+                    android:textAppearance="?android:attr/textAppearanceMedium"
+                    android:textColor="@color/perms_dangerous_perm_color"
+                    android:textStyle="bold"
+                    android:paddingStart="16dip"
+                    android:layout_toEndOf="@id/permission_icon"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content" />
+
+                <TextView
+                    android:id="@+id/account_name"
+                    android:textAppearance="?android:attr/textAppearanceSmall"
+                    android:textColor="@color/perms_dangerous_perm_color"
+                    android:layout_marginTop="-4dip"
+                    android:paddingBottom="8dip"
+                    android:paddingStart="16dip"
+                    android:layout_below="@id/account_type"
+                    android:layout_toEndOf="@id/permission_icon"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content" />
+
+                <TextView
+                    android:id="@+id/authtoken_type"
+                    android:textAppearance="?android:attr/textAppearanceMedium"
+                    android:textColor="@color/perms_dangerous_perm_color"
+                    android:textStyle="bold"
+                    android:layout_marginTop="-4dip"
+                    android:paddingBottom="8dip"
+                    android:paddingStart="16dip"
+                    android:layout_below="@id/account_name"
+                    android:layout_toEndOf="@id/permission_icon"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content" />
+            </RelativeLayout>
+
+            <TextView
+                android:id="@+id/grant_credentials_permission_message_footer"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/grant_credentials_permission_message_footer"
+                android:textAppearance="?android:attr/textAppearanceMedium"
+                android:paddingStart="19dip"
+                android:paddingBottom="12dip" />
+        </LinearLayout>
+    </ScrollView>
+
+    <!-- The buttons to allow or deny -->
+    <LinearLayout
+        android:id="@+id/buttons"
+        android:layout_marginStart="25dp"
+        android:layout_marginEnd="25dp"
+        android:layout_marginBottom="10dp"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        style="?android:attr/buttonBarStyle">
+
+        <Button
+            android:id="@+id/deny_button"
+            android:text="@string/deny"
+            android:layout_width="0dip"
+            android:layout_height="wrap_content"
+            android:layout_weight="2"
+            style="?android:attr/buttonBarButtonStyle" />
+
+        <Button
+            android:id="@+id/allow_button"
+            android:text="@string/allow"
+            android:layout_width="0dip"
+            android:layout_height="wrap_content"
+            android:layout_weight="2"
+            style="?android:attr/buttonBarButtonStyle" />
+
+    </LinearLayout>
+</LinearLayout>
diff --git a/core/res/res/layout-watch/input_method_switch_dialog_title.xml b/core/res/res/layout-watch/input_method_switch_dialog_title.xml
new file mode 100644
index 0000000..3d53ade
--- /dev/null
+++ b/core/res/res/layout-watch/input_method_switch_dialog_title.xml
@@ -0,0 +1,107 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT 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="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical" >
+
+    <LinearLayout
+            android:layout_width="172dp"
+            android:layout_height="48dp"
+            android:layout_marginStart="10dp"
+            android:layout_marginEnd="10dp"
+            android:layout_marginTop="20dp"
+            android:orientation="vertical">
+
+        <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:orientation="horizontal"
+                android:gravity="center" >
+
+            <TextView
+                    android:id="@+id/alertTitle"
+                    android:layout_width="match_parent"
+                    android:layout_height="48dp"
+                    android:layout_marginStart="10dp"
+                    android:layout_marginEnd="10dp"
+                    android:gravity="center_vertical"
+                    android:lineSpacingExtra="0sp"
+                    android:text="@string/select_input_method"
+                    android:textColor="#BDC1C6"
+                    android:fontFamily="google-sans-text-medium"
+                    android:letterSpacing="0.01"
+                    android:textSize="15sp" />
+        </LinearLayout>
+    </LinearLayout>
+
+    <!-- Hard keyboard switch -->
+
+    <LinearLayout
+            android:id="@+id/hard_keyboard_section"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="vertical" >
+
+        <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:orientation="horizontal" >
+
+            <LinearLayout
+                    android:layout_width="0dp"
+                    android:layout_height="wrap_content"
+                    android:layout_weight="1"
+                    android:background="?attr/selectableItemBackground"
+                    android:ellipsize="marquee"
+                    android:gravity="center_vertical"
+                    android:orientation="vertical"
+                    style="@style/InputMethodSwitchHardKeyboardText">
+
+                <TextView
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:singleLine="true"
+                        android:text="@string/hardware"
+                        android:textAppearance="?attr/textAppearanceListItem"
+                        android:ellipsize="marquee" />
+
+                <TextView
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:maxLines="10"
+                        android:text="@string/show_ime"
+                        android:textAppearance="?attr/textAppearanceListItemSecondary"
+                        android:textColor="?attr/textColorSecondary" />
+            </LinearLayout>
+
+            <Switch
+                    android:id="@+id/hard_keyboard_switch"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_gravity="center_vertical"
+                    style="@style/InputMethodSwitchHardKeyboardSwitch"/>
+        </LinearLayout>
+
+        <View
+                android:layout_width="match_parent"
+                android:layout_height="1dp"
+                android:background="?attr/listDividerAlertDialog" />
+    </LinearLayout>
+</LinearLayout>
diff --git a/core/res/res/layout-watch/input_method_switch_item.xml b/core/res/res/layout-watch/input_method_switch_item.xml
new file mode 100644
index 0000000..a77bd8f
--- /dev/null
+++ b/core/res/res/layout-watch/input_method_switch_item.xml
@@ -0,0 +1,106 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT 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:id="@+id/dialog_item"
+              android:layout_width="172dp"
+              android:layout_height="52dp"
+              android:layout_marginStart="10dp"
+              android:layout_marginTop="68dp"
+              android:weightSum="100"
+              android:gravity="center"
+              android:orientation="horizontal" >
+
+        <TextView
+                android:layout_width="0dp"
+                android:layout_height="match_parent"
+                android:layout_weight="5.21"
+                android:background="@android:color/transparent"/>
+
+        <LinearLayout
+                android:id="@+id/title"
+                android:layout_width="0dp"
+                android:layout_height="52dp"
+                android:layout_weight="89.58"
+                android:duplicateParentState="true"
+                android:gravity="center_vertical"
+                android:orientation="horizontal"
+                android:paddingEnd="6dp"
+                android:paddingStart="6dp"
+                android:background="@drawable/input_method_item_background_selector" >
+
+            <LinearLayout
+                    android:id="@+id/text_layout"
+                    android:layout_width="124dp"
+                    android:layout_height="match_parent"
+                    android:duplicateParentState="true"
+                    android:gravity="center_vertical"
+                    android:orientation="vertical" >
+
+                <TextView
+                        android:id="@+id/text1"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:layout_weight="1"
+                        android:layout_marginStart="8dp"
+                        android:layout_marginEnd="8dp"
+                        android:gravity="center_vertical"
+                        android:letterSpacing="0.01"
+                        android:textColor="#FFFFFF"
+                        android:maxLines="1"
+                        android:fontFamily="google-sans-text-medium"
+                        android:textSize="15sp" />
+
+                <TextView
+                        android:id="@+id/text2"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:layout_weight="1"
+                        android:layout_marginStart="8dp"
+                        android:layout_marginEnd="8dp"
+                        android:gravity="center_vertical"
+                        android:letterSpacing="0.01"
+                        android:textColor="#CAC5BC"
+                        android:maxLines="1"
+                        android:fontFamily="google-sans-text-medium"
+                        android:textSize="13sp" />
+
+            </LinearLayout>
+
+            <RadioButton
+                    android:id="@+id/radio"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:buttonTint="#E6FF7B"
+                    android:clickable="false"
+                    android:duplicateParentState="true"
+                    android:focusable="false"
+                    android:gravity="center"
+                    android:scaleX="1"
+                    android:scaleY="1" />
+
+
+        </LinearLayout>
+
+        <TextView
+                android:layout_width="0dp"
+                android:layout_height="match_parent"
+                android:layout_weight="5.21"
+                android:background="@android:color/transparent"/>
+
+
+</LinearLayout>
+
diff --git a/core/res/res/layout-watch/input_method_switcher_list_layout.xml b/core/res/res/layout-watch/input_method_switcher_list_layout.xml
new file mode 100644
index 0000000..6311f8a
--- /dev/null
+++ b/core/res/res/layout-watch/input_method_switcher_list_layout.xml
@@ -0,0 +1,29 @@
+<?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.
+-->
+<view class="com.android.internal.app.AlertController$RecycleListView"
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@id/select_dialog_listview"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:cacheColorHint="@null"
+    android:divider="?attr/listDividerAlertDialog"
+    android:scrollbars="vertical"
+    android:overScrollMode="ifContentScrolls"
+    android:textAlignment="viewStart"
+    android:clipToPadding="false"
+    android:paddingBottomNoButtons="@dimen/dialog_list_padding_bottom_no_buttons"
+    android:paddingTopNoTitle="@dimen/dialog_list_padding_top_no_title"
+  android:dividerHeight="@dimen/input_method_divider_height"/>
diff --git a/core/res/res/layout-watch/permissions_package_list_item.xml b/core/res/res/layout-watch/permissions_package_list_item.xml
new file mode 100644
index 0000000..e2171c1
--- /dev/null
+++ b/core/res/res/layout-watch/permissions_package_list_item.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<!--
+  Defines the layout of a single package item.
+  Contains a bullet point icon and the name of the package.
+-->
+
+<RelativeLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:theme="@style/Theme.DeviceDefault">
+
+    <ImageView
+        android:id="@+id/package_icon"
+        android:layout_width="30dip"
+        android:layout_height="30dip"
+        android:layout_alignParentStart="true"
+        android:src="@drawable/ic_text_dot"
+        android:scaleType="fitCenter" />
+
+
+    <TextView
+        android:id="@+id/package_label"
+        android:textAppearance="?android:attr/textAppearanceMedium"
+        android:textStyle="bold"
+        android:paddingStart="6dip"
+        android:layout_toEndOf="@id/package_icon"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content" />
+
+</RelativeLayout>
diff --git a/core/res/res/layout-watch/watch_base_error_dialog.xml b/core/res/res/layout-watch/watch_base_error_dialog.xml
new file mode 100644
index 0000000..c8ea017
--- /dev/null
+++ b/core/res/res/layout-watch/watch_base_error_dialog.xml
@@ -0,0 +1,114 @@
+<?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.
+  -->
+<com.android.internal.widget.WatchListDecorLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/parentPanel"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+    <com.android.internal.widget.FadingWearableScrollView
+        android:id="@+id/scrollView"
+        android:fillViewport="true"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:paddingLeft="?dialogPreferredPadding"
+            android:paddingRight="?dialogPreferredPadding"
+            android:paddingTop="@dimen/base_error_dialog_top_padding"
+            android:paddingBottom="@dimen/base_error_dialog_bottom_padding"
+            android:orientation="vertical" >
+            <!-- Top Panel -->
+            <FrameLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:id="@+id/topPanel">
+                <include android:id="@+id/title_template"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    layout="@layout/watch_base_error_dialog_title"/>
+            </FrameLayout>
+            <FrameLayout
+                android:layout_width="match_parent"
+                android:layout_height="16dp">
+            </FrameLayout>
+            <!-- Content Panel -->
+            <FrameLayout
+                android:id="@+id/contentPanel"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:clipToPadding="false">
+                <TextView
+                    android:id="@+id/message"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:gravity="center_horizontal|top"
+                    android:textAppearance="@style/TextAppearance.DeviceDefault.Body1"
+                    android:paddingTop="8dip"
+                    android:paddingBottom="8dip"/>
+            </FrameLayout>
+            <!-- Custom Panel, to replace content panel if needed -->
+            <FrameLayout
+                android:id="@+id/customPanel"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:minHeight="64dp">
+                <FrameLayout
+                    android:id="@+android:id/custom"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"/>
+            </FrameLayout>
+
+            <!-- Button Panel -->
+            <FrameLayout
+                android:id="@+id/buttonPanel"
+                android:layout_weight="1"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content">
+                <LinearLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_gravity="bottom"
+                    android:orientation="vertical"
+                    style="?android:attr/buttonBarStyle"
+                    android:measureWithLargestChild="true">
+                    <Button
+                        android:id="@+id/button1"
+                        android:layout_gravity="start"
+                        android:layout_weight="1"
+                        style="?android:attr/buttonBarButtonStyle"
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"/>
+                    <Button
+                        android:id="@+id/button3"
+                        android:layout_gravity="start"
+                        android:layout_weight="1"
+                        style="?android:attr/buttonBarButtonStyle"
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"/>
+                    <Button
+                        android:id="@+id/button2"
+                        android:layout_gravity="start"
+                        android:layout_weight="1"
+                        style="?android:attr/buttonBarButtonStyle"
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"/>
+                </LinearLayout>
+            </FrameLayout>
+        </LinearLayout>
+    </com.android.internal.widget.FadingWearableScrollView>
+</com.android.internal.widget.WatchListDecorLayout>
diff --git a/core/res/res/layout-watch/watch_base_error_dialog_title.xml b/core/res/res/layout-watch/watch_base_error_dialog_title.xml
new file mode 100644
index 0000000..aa14c08
--- /dev/null
+++ b/core/res/res/layout-watch/watch_base_error_dialog_title.xml
@@ -0,0 +1,47 @@
+<?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.
+  -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:paddingLeft="@dimen/base_error_dialog_contents_padding"
+    android:paddingRight="@dimen/base_error_dialog_contents_padding"
+    android:orientation="vertical"
+    android:gravity="top|center_horizontal">
+    <FrameLayout
+        android:adjustViewBounds="true"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+        <ImageView
+            android:id="@+id/icon"
+            android:adjustViewBounds="true"
+            android:maxHeight="24dp"
+            android:maxWidth="24dp"
+            android:layout_marginTop="@dimen/screen_percentage_10"
+            android:layout_gravity="center_horizontal"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:src="@null"/>
+    </FrameLayout>
+    <TextView
+        android:id="@+id/alertTitle"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:minHeight="38dp"
+        android:textAppearance="@style/TextAppearance.Watch.BaseErrorDialog.Title"
+        android:maxLines="3"
+        android:gravity="center_horizontal|top"/>
+</LinearLayout>
diff --git a/core/res/res/layout/notification_expand_button.xml b/core/res/res/layout/notification_expand_button.xml
index 8eae064..63fe471 100644
--- a/core/res/res/layout/notification_expand_button.xml
+++ b/core/res/res/layout/notification_expand_button.xml
@@ -34,6 +34,7 @@
         android:background="@drawable/expand_button_pill_bg"
         android:gravity="center_vertical"
         android:layout_gravity="center_vertical"
+        android:duplicateParentState="true"
         >
 
         <TextView
diff --git a/core/res/res/layout/shutdown_dialog.xml b/core/res/res/layout/shutdown_dialog.xml
index ec67aa8..726c255 100644
--- a/core/res/res/layout/shutdown_dialog.xml
+++ b/core/res/res/layout/shutdown_dialog.xml
@@ -40,7 +40,7 @@
         android:fontFamily="@string/config_headlineFontFamily"/>
 
     <TextView
-        android:id="@+id/text2"
+        android:id="@id/text2"
         android:layout_width="wrap_content"
         android:layout_height="32sp"
         android:text="@string/shutdown_progress"
diff --git a/core/res/res/values-af/strings.xml b/core/res/res/values-af/strings.xml
index 6a7c065..1c64f5f 100644
--- a/core/res/res/values-af/strings.xml
+++ b/core/res/res/values-af/strings.xml
@@ -590,6 +590,8 @@
     <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"Laat die program toe om voorkeur-NFC-betalingdiensinligting soos geregistreerde hulpmiddels en roetebestemming te kry."</string>
     <string name="permlab_nfc" msgid="1904455246837674977">"beheer kortveldkommunikasie"</string>
     <string name="permdesc_nfc" msgid="8352737680695296741">"Laat die program toe om met kortveldkommunikasie- (NFC) merkers, kaarte en lesers te kommunikeer."</string>
+    <string name="permlab_nfcTransactionEvent" msgid="5868209446710407679">"Veilige Element-transaksiegeval"</string>
+    <string name="permdesc_nfcTransactionEvent" msgid="1904286701876487397">"Laat die app toe om inligting te ontvang oor transaksies wat op ’n Veilige Element plaasvind."</string>
     <string name="permlab_disableKeyguard" msgid="3605253559020928505">"deaktiveer jou skermslot"</string>
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"Laat die program toe om die sleutelslot en enige verwante wagwoordsekuriteit te deaktiveer. Byvoorbeeld, die foon deaktiveer die sleutelslot wanneer ’n oproep inkom, en atkiveer dit dan weer wanneer die oproep eindig."</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"versoek skermslot-kompleksiteit"</string>
@@ -620,6 +622,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Biometriese hardeware is nie beskikbaar nie"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Stawing is gekanselleer"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Nie herken nie"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Gesig word nie herken nie"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Stawing is gekanselleer"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"Geen PIN, patroon of wagwoord is gestel nie"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Kon nie staaf nie"</string>
@@ -1393,7 +1396,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"WEIER"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Kies invoermetode"</string>
     <string name="show_ime" msgid="6406112007347443383">"Hou dit op die skerm terwyl fisieke sleutelbord aktief is"</string>
-    <string name="hardware" msgid="1800597768237606953">"Wys virtuele sleutelbord"</string>
+    <string name="hardware" msgid="3611039921284836033">"Gebruik skermsleutelbord"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"Stel <xliff:g id="DEVICE_NAME">%s</xliff:g> op"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Stel fisieke sleutelborde op"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Tik om taal en uitleg te kies"</string>
@@ -1956,7 +1959,7 @@
     <string name="app_blocked_title" msgid="7353262160455028160">"Program is nie beskikbaar nie"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> is nie op die oomblik beskikbaar nie."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> is nie beskikbaar nie"</string>
-    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="4483161748582966785">"Toestemming word benodig"</string>
+    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="3805704317624448487">"Toestemmingsversoek is onderdruk"</string>
     <string name="app_streaming_blocked_title_for_camera_dialog" msgid="3935701653713853065">"Kamera is nie beskikbaar nie"</string>
     <string name="app_streaming_blocked_title_for_fingerprint_dialog" msgid="3516853717714141951">"Gaan voort op foon"</string>
     <string name="app_streaming_blocked_title_for_microphone_dialog" msgid="544822455127171206">"Mikrofoon is nie beskikbaar nie"</string>
@@ -1967,6 +1970,9 @@
     <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"Jy kan nie nou toegang hiertoe op jou <xliff:g id="DEVICE">%1$s</xliff:g> kry nie. Probeer eerder op jou Android TV-toestel."</string>
     <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"Jy kan nie nou toegang hiertoe op jou <xliff:g id="DEVICE">%1$s</xliff:g> kry nie. Probeer eerder op jou tablet."</string>
     <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"Jy kan nie nou toegang hiertoe op jou <xliff:g id="DEVICE">%1$s</xliff:g> kry nie. Probeer eerder op jou foon."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tv" msgid="4706276040125072077">"Hierdie app versoek tans bykomende toestemmings, maar toestemmings kan nie in ’n stromingsessie verleen word nie. Verleen eers die toestemming op jou Android TV-toestel."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tablet" msgid="1824604581465771629">"Hierdie app versoek tans bykomende toestemmings, maar toestemmings kan nie in ’n stromingsessie verleen word nie. Verleen eers die toestemming op jou tablet."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="default" msgid="7755223160363292105">"Hierdie app versoek tans bykomende toestemmings, maar toestemmings kan nie in ’n stromingsessie verleen word nie. Verleen eers die toestemming op jou foon."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"Hierdie program versoek tans bykomende sekuriteit. Probeer eerder op jou Android TV-toestel."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"Hierdie program versoek tans bykomende sekuriteit. Probeer eerder op jou tablet."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"Hierdie program versoek tans bykomende sekuriteit. Probeer eerder op jou foon."</string>
diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml
index ecab978b..1a57480 100644
--- a/core/res/res/values-am/strings.xml
+++ b/core/res/res/values-am/strings.xml
@@ -590,6 +590,8 @@
     <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"እንደ የተመዘገቡ እርዳታዎች እና የጉዞ መሥመር መዳረሻ የመሳሰለ ተመራጭ nfc የክፍያ አገልግሎት መረጃን ለማግኘት ለመተግበሪያው ያፈቅድለታል።"</string>
     <string name="permlab_nfc" msgid="1904455246837674977">"ቅርብ የግኑኙነትመስክ (NFC) ተቆጣጠር"</string>
     <string name="permdesc_nfc" msgid="8352737680695296741">"ከቅርብ ግኑኙነት መስክ (NFC) መለያዎች፣ ካርዶች እና አንባቢ ጋር ለማገናኘት ለመተግበሪያው ይፈቅዳሉ።"</string>
+    <string name="permlab_nfcTransactionEvent" msgid="5868209446710407679">"የክፍለ አካል ግብይት ክስተትን ደህንነቱ የተጠበቀ ያድርጉ"</string>
+    <string name="permdesc_nfcTransactionEvent" msgid="1904286701876487397">"ደህንነቱ በተጠበቀ ክፍለ አካል ላይ እየተከሰቱ ስላሉ ግብይቶች መተግበሪያው መረጃ እንዲቀበል ይፈቅዳል።"</string>
     <string name="permlab_disableKeyguard" msgid="3605253559020928505">"የማያ ገጽዎን መቆለፊያ ያሰናክሉ"</string>
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"መተግበሪያው መቆለፊያውና ማንኛውም የተጎዳኘ የይለፍ ቃል ደህንነት እንዲያሰናክል ይፈቅድለታል። ለምሳሌ ስልኩ ገቢ የስልክ ጥሪ በሚቀበልበት ጊዜ መቆለፊያውን ያሰናክልና ከዚያም ጥሪው ሲጠናቀቅ መቆለፊያውን በድጋሚ ያነቃዋል።"</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"የማያ ገፅ መቆለፊያ ውስብስብነትን ጠይቅ"</string>
@@ -620,6 +622,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"ባዮሜትራዊ ሃርድዌር አይገኝም"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"ማረጋገጥ ተሰርዟል"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"አልታወቀም"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"መልክ አልታወቀም"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"ማረጋገጥ ተሰርዟል"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"ምንም ፒን፣ ሥርዓተ ጥለት ወይም የይለፍ ቃል አልተቀናበረም"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"ማረጋገጥ ላይ ስህተት"</string>
@@ -1393,7 +1396,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"አትቀበል"</string>
     <string name="select_input_method" msgid="3971267998568587025">"የግቤት ስልት ምረጥ"</string>
     <string name="show_ime" msgid="6406112007347443383">"አካላዊ የቁልፍ ሰሌዳ ገቢር ሆኖ ሳለ በማያ ገፅ ላይ አቆየው"</string>
-    <string name="hardware" msgid="1800597768237606953">"ምናባዊ የቁልፍ ሰሌዳን አሳይ"</string>
+    <string name="hardware" msgid="3611039921284836033">"የማያ ገጽ ላይ የቁልፍ ሰሌዳ ይጠቀሙ"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"<xliff:g id="DEVICE_NAME">%s</xliff:g>ን ያዋቅሩ"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"አካላዊ የቁልፍ ሰሌዳዎችን ያዋቅሩ"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"ቋንቋ እና አቀማመጥን ለመምረጥ መታ ያድርጉ"</string>
@@ -1956,7 +1959,7 @@
     <string name="app_blocked_title" msgid="7353262160455028160">"መተግበሪያ አይገኝም"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> አሁን አይገኝም።"</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> አይገኝም"</string>
-    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="4483161748582966785">"ፈቃድ ያስፈልጋል"</string>
+    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="3805704317624448487">"የፈቃድ ጥያቄ ታፍኗል"</string>
     <string name="app_streaming_blocked_title_for_camera_dialog" msgid="3935701653713853065">"ካሜራ አይገኝም"</string>
     <string name="app_streaming_blocked_title_for_fingerprint_dialog" msgid="3516853717714141951">"በስልክ ላይ ይቀጥሉ"</string>
     <string name="app_streaming_blocked_title_for_microphone_dialog" msgid="544822455127171206">"ማይክሮፎን አይገኝም"</string>
@@ -1967,6 +1970,9 @@
     <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"ይህ በዚህ ጊዜ በእርስዎ <xliff:g id="DEVICE">%1$s</xliff:g> ላይ ሊደረስበት አይችልም። በምትኩ በAndroid TV መሣሪያዎ ላይ ይሞክሩ።"</string>
     <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"ይህ በዚህ ጊዜ በእርስዎ <xliff:g id="DEVICE">%1$s</xliff:g> ላይ ሊደረስበት አይችልም። በምትኩ በጡባዊዎ ላይ ይሞክሩ።"</string>
     <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"ይህ በዚህ ጊዜ በእርስዎ <xliff:g id="DEVICE">%1$s</xliff:g> ላይ ሊደረስበት አይችልም። በምትኩ በስልክዎ ላይ ይሞክሩ።"</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tv" msgid="4706276040125072077">"ይህ መተግበሪያ ተጨማሪ ፈቃዶችን እየጠየቀ ነው፣ ነገር ግን ፈቃዶች በዥረት ክፍለ ጊዜ ውስጥ ሊሰጡ አይችሉም። መጀመሪያ በAndroid TV መሣሪያዎ ላይ ፍቃድ ይስጡ።"</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tablet" msgid="1824604581465771629">"ይህ መተግበሪያ ተጨማሪ ፈቃዶችን እየጠየቀ ነው፣ ነገር ግን ፈቃዶች በዥረት ክፍለ ጊዜ ውስጥ ሊሰጡ አይችሉም። መጀመሪያ በጡባዊዎ ላይ ፍቃድ ይስጡ።"</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="default" msgid="7755223160363292105">"ይህ መተግበሪያ ተጨማሪ ፈቃዶችን እየጠየቀ ነው፣ ነገር ግን ፈቃዶች በዥረት ክፍለ ጊዜ ውስጥ ሊሰጡ አይችሉም። መጀመሪያ በስልክዎ ላይ ፍቃድ ይስጡ።"</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"ይህ መተግበሪያ ተጨማሪ ደህንነትን እየጠየቀ ነው። በምትኩ በAndroid TV መሣሪያዎ ላይ ይሞክሩ።"</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"ይህ መተግበሪያ ተጨማሪ ደህንነትን እየጠየቀ ነው። በምትኩ በጡባዊዎ ላይ ይሞክሩ።"</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"ይህ መተግበሪያ ተጨማሪ ደህንነትን እየጠየቀ ነው። በምትኩ በስልክዎ ላይ ይሞክሩ።"</string>
diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml
index 87df6bb..4138b0a 100644
--- a/core/res/res/values-ar/strings.xml
+++ b/core/res/res/values-ar/strings.xml
@@ -594,6 +594,8 @@
     <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"‏يسمح هذا الإذن للتطبيق بالحصول على معلومات الخدمات المدفوعة باستخدام الاتصال قصير المدى NFC المفضّل، مثلاً المساعدات المسجّلة ووجهة المسار."</string>
     <string name="permlab_nfc" msgid="1904455246837674977">"التحكم في اتصال الحقل القريب"</string>
     <string name="permdesc_nfc" msgid="8352737680695296741">"‏للسماح للتطبيق بالاتصال بعلامات الاتصال قريب المدى (NFC)، والبطاقات وبرامج القراءة."</string>
+    <string name="permlab_nfcTransactionEvent" msgid="5868209446710407679">"حدث معاملة \"عنصر آمن\""</string>
+    <string name="permdesc_nfcTransactionEvent" msgid="1904286701876487397">"يسمح هذا الإذن للتطبيق بتلقّي معلومات حول المعاملات التي تتم على \"عنصر آمن\"."</string>
     <string name="permlab_disableKeyguard" msgid="3605253559020928505">"إيقاف قفل الشاشة"</string>
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"للسماح للتطبيق بإيقاف تأمين المفاتيح وأي أمان لكلمة مرور مرتبطة. على سبيل المثال، يعطل الهاتف تأمين المفاتيح عند استقبال مكالمة هاتفية واردة، ثم يعيد تفعيل تأمين المفاتيح عند انتهاء المكالمة."</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"طلب معرفة مستوى صعوبة قفل الشاشة"</string>
@@ -624,6 +626,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"معدّات المقاييس الحيوية غير متاحة."</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"تم إلغاء المصادقة."</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"لم يتم التعرف عليها."</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"لم يتم التعرّف على الوجه."</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"تم إلغاء المصادقة."</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"لم يتم ضبط رقم تعريف شخصي أو نقش أو كلمة مرور."</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"خطأ في المصادقة"</string>
@@ -1397,7 +1400,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"رفض"</string>
     <string name="select_input_method" msgid="3971267998568587025">"اختيار أسلوب الإدخال"</string>
     <string name="show_ime" msgid="6406112007347443383">"استمرار عرضها على الشاشة عندما تكون لوحة المفاتيح الخارجية متصلة"</string>
-    <string name="hardware" msgid="1800597768237606953">"إظهار لوحة المفاتيح الافتراضية"</string>
+    <string name="hardware" msgid="3611039921284836033">"استخدام لوحة المفاتيح على الشاشة"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"إعداد <xliff:g id="DEVICE_NAME">%s</xliff:g>"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"إعداد لوحات المفاتيح الخارجية"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"انقر لاختيار لغة وتنسيق"</string>
@@ -1960,7 +1963,7 @@
     <string name="app_blocked_title" msgid="7353262160455028160">"التطبيق غير متاح"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"تطبيق <xliff:g id="APP_NAME">%1$s</xliff:g> غير متاح الآن."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"تطبيق <xliff:g id="ACTIVITY">%1$s</xliff:g> غير متاح"</string>
-    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="4483161748582966785">"مطلوب منح الإذن"</string>
+    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="3805704317624448487">"تم إلغاء طلب الإذن"</string>
     <string name="app_streaming_blocked_title_for_camera_dialog" msgid="3935701653713853065">"الكاميرا غير متاحة"</string>
     <string name="app_streaming_blocked_title_for_fingerprint_dialog" msgid="3516853717714141951">"الاستمرار على الهاتف"</string>
     <string name="app_streaming_blocked_title_for_microphone_dialog" msgid="544822455127171206">"الميكروفون غير متاح"</string>
@@ -1971,6 +1974,9 @@
     <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"‏لا يمكن الوصول إلى هذا التطبيق على <xliff:g id="DEVICE">%1$s</xliff:g> في الوقت الحالي. حاوِل الوصول إليه على جهاز Android TV بدلاً من ذلك."</string>
     <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"لا يمكن الوصول إلى هذا التطبيق على <xliff:g id="DEVICE">%1$s</xliff:g> في الوقت الحالي. حاوِل الوصول إليه على جهازك اللوحي بدلاً من ذلك."</string>
     <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"لا يمكن الوصول إلى هذا التطبيق على <xliff:g id="DEVICE">%1$s</xliff:g> في الوقت الحالي. حاوِل الوصول إليه على هاتفك بدلاً من ذلك."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tv" msgid="4706276040125072077">"‏يطلب هذا التطبيق الحصول على أذونات إضافية، ولكن لا يمكن منح أذونات في جلسة بث. امنح الإذن على جهاز Android TV أولاً."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tablet" msgid="1824604581465771629">"يطلب هذا التطبيق الحصول على أذونات إضافية، ولكن لا يمكن منح أذونات في جلسة بث. امنح الإذن على جهازك اللوحي أولاً."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="default" msgid="7755223160363292105">"يطلب هذا التطبيق الحصول على أذونات إضافية، ولكن لا يمكن منح أذونات في جلسة بث. امنح الإذن على هاتفك أولاً."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"‏يطلب هذا التطبيق الحصول على ميزات أمان إضافية. بدلاً من ذلك، جرِّب استخدام Android TV."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"يطلب هذا التطبيق الحصول على ميزات أمان إضافية. بدلاً من ذلك، جرِّب استخدام جهازك اللوحي."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"يطلب هذا التطبيق الحصول على ميزات أمان إضافية. بدلاً من ذلك، جرِّب استخدام هاتفك."</string>
diff --git a/core/res/res/values-as/strings.xml b/core/res/res/values-as/strings.xml
index 2542163..4d08974 100644
--- a/core/res/res/values-as/strings.xml
+++ b/core/res/res/values-as/strings.xml
@@ -590,6 +590,8 @@
     <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"এপ্‌টোক অগ্ৰাধিকাৰ দিয়া nfc পৰিশোধ সেৱাৰ পঞ্জীকৃত সহায়কসমূহ আৰু পৰিশোধ কৰিব লগা লক্ষ্যস্থান দৰে তথ্য পাবলৈ অনুমতি দিয়ে।"</string>
     <string name="permlab_nfc" msgid="1904455246837674977">"নিয়েৰ ফিল্ড কমিউনিকেশ্বন নিয়ন্ত্ৰণ কৰক"</string>
     <string name="permdesc_nfc" msgid="8352737680695296741">"এপ্‌টোক নিয়েৰ ফিল্ড কমিউনিকেশ্বন (NFC) টেগ, কাৰ্ড আৰু ৰিডাৰসমূহৰ সৈতে যোগাযোগ কৰিবলৈ অনুমতি দিয়ে।"</string>
+    <string name="permlab_nfcTransactionEvent" msgid="5868209446710407679">"সুৰক্ষিত উপাদানৰ লেনদেন সম্পৰ্কীয় অনুষ্ঠান"</string>
+    <string name="permdesc_nfcTransactionEvent" msgid="1904286701876487397">"এপ্‌টোক এটা সুৰক্ষিত উপাদানত হোৱা লেনদেনৰ বিষয়ে তথ্য লাভ কৰাৰ অনুমতি দিয়ে।"</string>
     <string name="permlab_disableKeyguard" msgid="3605253559020928505">"আপোনাৰ স্ক্ৰীন লক অক্ষম কৰক"</string>
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"এপ্‌টোক কী ল\'ক আৰু জড়িত হোৱা যিকোনো পাছৱৰ্ডৰ সুৰক্ষা অক্ষম কৰিব দিয়ে৷ উদাহৰণস্বৰূপে, কোনো অন্তৰ্গামী ফ\'ন কল উঠোৱাৰ সময়ত ফ\'নটোৱে কী-লকটো অক্ষম কৰে, তাৰ পাছত কল শেষ হ\'লেই কী লকটো পুনৰ সক্ষম কৰে৷"</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"স্ক্ৰীন লকৰ জটিলতাৰ অনুৰোধ"</string>
@@ -620,6 +622,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"বায়োমেট্ৰিক হাৰ্ডৱেৰ উপলব্ধ নহয়"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"বিশ্বাসযোগ্যতাৰ প্ৰমাণীকৰণ বাতিল কৰা হৈছে"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"চিনাক্ত কৰিব পৰা নাই"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"মুখাৱয়ব চিনি পোৱা নাই"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"বিশ্বাসযোগ্যতাৰ প্ৰমাণীকৰণ বাতিল কৰা হৈছে"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"কোনো পিন, আৰ্হি বা পাছৱৰ্ড ছেট কৰা নাই"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"আসোঁৱাহৰ বিশ্বাসযোগ্যতা প্ৰমাণীকৰণ কৰি থকা হৈছে"</string>
@@ -1393,7 +1396,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"প্ৰত্যাখ্যান কৰক"</string>
     <string name="select_input_method" msgid="3971267998568587025">"ইনপুট পদ্ধতি বাছনি কৰক"</string>
     <string name="show_ime" msgid="6406112007347443383">"কায়িক কীব’ৰ্ড সক্ৰিয় হৈ থাকোঁতে ইয়াক স্ক্ৰীনত ৰাখক"</string>
-    <string name="hardware" msgid="1800597768237606953">"ভাৰ্শ্বুৱল কীব\'ৰ্ড দেখুৱাওক"</string>
+    <string name="hardware" msgid="3611039921284836033">"অন-স্ক্ৰীন কীব’ৰ্ড ব্যৱহাৰ কৰক"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"<xliff:g id="DEVICE_NAME">%s</xliff:g> কনফিগাৰ কৰক"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"ভৌতিক কীব’ৰ্ড কনফিগাৰ কৰক"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"ভাষা আৰু চানেকি বাছনি কৰিবলৈ ইয়াত টিপক"</string>
@@ -1956,7 +1959,7 @@
     <string name="app_blocked_title" msgid="7353262160455028160">"এপ্‌টো উপলব্ধ নহয়"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"এই মুহূৰ্তত <xliff:g id="APP_NAME">%1$s</xliff:g> উপলব্ধ নহয়।"</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> উপলব্ধ নহয়"</string>
-    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="4483161748582966785">"অনুমতিৰ প্ৰয়োজন"</string>
+    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="3805704317624448487">"অনুমতিৰ অনুৰোধ অৱদমন কৰা হৈছে"</string>
     <string name="app_streaming_blocked_title_for_camera_dialog" msgid="3935701653713853065">"কেমেৰা উপলব্ধ নহয়"</string>
     <string name="app_streaming_blocked_title_for_fingerprint_dialog" msgid="3516853717714141951">"ফ’নতে অব্যাহত ৰাখক"</string>
     <string name="app_streaming_blocked_title_for_microphone_dialog" msgid="544822455127171206">"মাইক্ৰ’ফ’ন উপলব্ধ নহয়"</string>
@@ -1967,6 +1970,9 @@
     <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"এইটো এতিয়া আপোনাৰ <xliff:g id="DEVICE">%1$s</xliff:g>ত এক্সেছ কৰিব পৰা নাযায়। তাৰ পৰিৱৰ্তে আপোনাৰ Android TVত চেষ্টা কৰি চাওক।"</string>
     <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"এইটো এতিয়া আপোনাৰ <xliff:g id="DEVICE">%1$s</xliff:g>ত এক্সেছ কৰিব পৰা নাযায়। তাৰ পৰিৱৰ্তে আপোনাৰ টেবলেটটোত চেষ্টা কৰি চাওক।"</string>
     <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"এইটো এতিয়া আপোনাৰ <xliff:g id="DEVICE">%1$s</xliff:g>ত এক্সেছ কৰিব পৰা নাযায়। তাৰ পৰিৱৰ্তে আপোনাৰ ফ’নত চেষ্টা কৰি চাওক।"</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tv" msgid="4706276040125072077">"এই এপ্‌টোৱে অতিৰিক্ত অনুমতিৰ বাবে অনুৰোধ কৰিছে, কিন্তু ষ্ট্ৰীমিং ছেশ্বনত অনুমতি দিব নোৱাৰি। আপোনাৰ Android TV ডিভাইচটোত প্ৰথমে অনুমতি দিয়ক।"</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tablet" msgid="1824604581465771629">"এই এপ্‌টোৱে অতিৰিক্ত অনুমতিৰ বাবে অনুৰোধ কৰিছে, কিন্তু ষ্ট্ৰীমিং ছেশ্বনত অনুমতি দিব নোৱাৰি। আপোনাৰ টেবলেটটোত প্ৰথমে অনুমতি দিয়ক।"</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="default" msgid="7755223160363292105">"এই এপ্‌টোৱে অতিৰিক্ত অনুমতিৰ বাবে অনুৰোধ কৰিছে, কিন্তু ষ্ট্ৰীমিং ছেশ্বনত অনুমতি দিব নোৱাৰি। আপোনাৰ ফ’নটোত প্ৰথমে অনুমতি দিয়ক।"</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"এই এপ্‌টোৱে অতিৰিক্ত সুৰক্ষাৰ বাবে অনুৰোধ কৰিছে। তাৰ পৰিৱৰ্তে আপোনাৰ Android TV ডিভাইচত চেষ্টা কৰি চাওক।"</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"এই এপ্‌টোৱে অতিৰিক্ত সুৰক্ষাৰ বাবে অনুৰোধ কৰিছে। তাৰ পৰিৱৰ্তে আপোনাৰ টেবলেটত চেষ্টা কৰি চাওক।"</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"এই এপ্‌টোৱে অতিৰিক্ত সুৰক্ষাৰ বাবে অনুৰোধ কৰিছে। তাৰ পৰিৱৰ্তে আপোনাৰ ফ’নত চেষ্টা কৰি চাওক।"</string>
diff --git a/core/res/res/values-az/strings.xml b/core/res/res/values-az/strings.xml
index 3d7e487..e2a2808 100644
--- a/core/res/res/values-az/strings.xml
+++ b/core/res/res/values-az/strings.xml
@@ -590,6 +590,8 @@
     <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"Tətbiqə qeydiyyatdan keçmiş yardım və marşrut təyinatı kimi tərcih edilən nfc ödəniş xidməti məlumatını əldə etmək icazəsi verir."</string>
     <string name="permlab_nfc" msgid="1904455246837674977">"Near Field Communication\'ı kontrol et"</string>
     <string name="permdesc_nfc" msgid="8352737680695296741">"Tətbiqə Yaxın Məsafə Kommunikasiyası (NFC) teqləri, kartları və oxuyucuları ilə əlaqə qurmağa icazə verir."</string>
+    <string name="permlab_nfcTransactionEvent" msgid="5868209446710407679">"Təhlükəsiz Element əməliyyatı"</string>
+    <string name="permdesc_nfcTransactionEvent" msgid="1904286701876487397">"Tətbiqə Təhlükəsiz Elementdəki əməliyyatlar haqqında məlumat almaq imkanı verir."</string>
     <string name="permlab_disableKeyguard" msgid="3605253559020928505">"Ekran kilidini deaktiv edir"</string>
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"Tətbiqə kilid açarını və təhlükəsizlik parolunu deaktiv etməyə imkan verir. Qanuni misal budur ki, telefon zəng qəbul edən zaman kilidi açır və zəng qurtarandan sonra kilidi bağlayır."</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"ekran kilidi mürəkkəbliyi tələb edin"</string>
@@ -620,6 +622,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Biometrik proqram əlçatan deyil"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Doğrulama ləğv edildi"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Tanınmır"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Üz tanınmadı"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Doğrulama ləğv edildi"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"Pin, nümunə və ya parol ayarlanmayıb"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Doğrulama zamanı xəta baş verdi"</string>
@@ -1393,7 +1396,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"RƏDD EDİN"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Daxiletmə metodunu seçin"</string>
     <string name="show_ime" msgid="6406112007347443383">"Fiziki klaviatura aktiv olanda görünsün"</string>
-    <string name="hardware" msgid="1800597768237606953">"Virtual klaviaturanı göstərin"</string>
+    <string name="hardware" msgid="3611039921284836033">"Ekran klaviaturası işlədin"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"<xliff:g id="DEVICE_NAME">%s</xliff:g> cihazını konfiqurasiya edin"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Fiziki klaviaturaları konfiqurasiya edin"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Dil və tərtibatı seçmək üçün tıklayın"</string>
@@ -1956,7 +1959,7 @@
     <string name="app_blocked_title" msgid="7353262160455028160">"Tətbiq əlçatan deyil"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> hazırda əlçatan deyil."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> əlçatan deyil"</string>
-    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="4483161748582966785">"İcazə tələb olunur"</string>
+    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="3805704317624448487">"İcazə sorğusu dayandırıldı"</string>
     <string name="app_streaming_blocked_title_for_camera_dialog" msgid="3935701653713853065">"Kamera əlçatan deyil"</string>
     <string name="app_streaming_blocked_title_for_fingerprint_dialog" msgid="3516853717714141951">"Telefonda davam edin"</string>
     <string name="app_streaming_blocked_title_for_microphone_dialog" msgid="544822455127171206">"Mikrofon əlçatan deyil"</string>
@@ -1967,6 +1970,9 @@
     <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"Hazırda <xliff:g id="DEVICE">%1$s</xliff:g> cihazınızda buna giriş mümkün deyil. Android TV cihazınızda sınayın."</string>
     <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"Hazırda <xliff:g id="DEVICE">%1$s</xliff:g> cihazınızda buna giriş mümkün deyil. Planşetinizdə sınayın."</string>
     <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"Hazırda <xliff:g id="DEVICE">%1$s</xliff:g> cihazınızda buna giriş mümkün deyil. Telefonunuzda sınayın."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tv" msgid="4706276040125072077">"Bu tətbiqə əlavə icazələr lazımdır, lakin yayım sessiyasında icazə vermək olmur. Əvvəlcə Android TV cihazında icazə verin."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tablet" msgid="1824604581465771629">"Bu tətbiqə əlavə icazələr lazımdır, lakin yayım sessiyasında icazə vermək olmur. Əvvəlcə planşetdə icazə verin."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="default" msgid="7755223160363292105">"Bu tətbiqə əlavə icazələr lazımdır, lakin yayım sessiyasında icazə vermək olmur. Əvvəlcə telefonda icazə verin."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"Bu tətbiq əlavə təhlükəsizlik tələb edir. Android TV cihazınızda sınayın."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"Bu tətbiq əlavə təhlükəsizlik tələb edir. Planşetinizdə sınayın."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"Bu tətbiq əlavə təhlükəsizlik tələb edir. Telefonunuzda sınayın."</string>
diff --git a/core/res/res/values-b+sr+Latn/strings.xml b/core/res/res/values-b+sr+Latn/strings.xml
index 1e731e8..4a1101b 100644
--- a/core/res/res/values-b+sr+Latn/strings.xml
+++ b/core/res/res/values-b+sr+Latn/strings.xml
@@ -591,6 +591,8 @@
     <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"Dozvoljava aplikaciji da preuzima informacije o željenoj NFC usluzi za plaćanje, poput registrovanih identifikatora aplikacija i odredišta preusmeravanja."</string>
     <string name="permlab_nfc" msgid="1904455246837674977">"kontrola komunikacije u užem polju (Near Field Communication)"</string>
     <string name="permdesc_nfc" msgid="8352737680695296741">"Dozvoljava aplikaciji da komunicira sa oznakama, karticama i čitačima komunikacije kratkog dometa (NFC)."</string>
+    <string name="permlab_nfcTransactionEvent" msgid="5868209446710407679">"Transakcija u okviru bezbednosnog elementa"</string>
+    <string name="permdesc_nfcTransactionEvent" msgid="1904286701876487397">"Omogućava aplikaciji da dobija informacije o transkacijama koje se izvršavaju u okviru bezbednosnog elementa."</string>
     <string name="permlab_disableKeyguard" msgid="3605253559020928505">"onemogućavanje zaključavanja ekrana"</string>
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"Dozvoljava aplikaciji da onemogući zaključavanje tastature i sve povezane bezbednosne mere sa lozinkama. Na primer, telefon onemogućava zaključavanje tastature pri prijemu dolaznog telefonskog poziva, a zatim ga ponovo omogućava po završetku poziva."</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"traženje složenosti zaključavanja ekrana"</string>
@@ -621,6 +623,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Biometrijski hardver nije dostupan"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Potvrda identiteta je otkazana"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Nije prepoznato"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Lice nije prepoznato"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Potvrda identiteta je otkazana"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"Niste podesili ni PIN, ni šablon, ni lozinku"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Greška pri potvrdi identiteta"</string>
@@ -1394,7 +1397,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"ODBIJ"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Izbor metoda unosa"</string>
     <string name="show_ime" msgid="6406112007347443383">"Zadržava se na ekranu dok je fizička tastatura aktivna"</string>
-    <string name="hardware" msgid="1800597768237606953">"Prikaži virtuelnu tastaturu"</string>
+    <string name="hardware" msgid="3611039921284836033">"Koristi tastaturu na ekranu"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"Konfigurišite uređaj <xliff:g id="DEVICE_NAME">%s</xliff:g>"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Konfigurišite fizičke tastature"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Dodirnite da biste izabrali jezik i raspored"</string>
@@ -1957,7 +1960,7 @@
     <string name="app_blocked_title" msgid="7353262160455028160">"Aplikacija nije dostupna"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> trenutno nije dostupna."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> – nije dostupno"</string>
-    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="4483161748582966785">"Potrebna je dozvola"</string>
+    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="3805704317624448487">"Zahtev za dozvolu je blokiran"</string>
     <string name="app_streaming_blocked_title_for_camera_dialog" msgid="3935701653713853065">"Kamera nije dostupna"</string>
     <string name="app_streaming_blocked_title_for_fingerprint_dialog" msgid="3516853717714141951">"Nastavite na telefonu"</string>
     <string name="app_streaming_blocked_title_for_microphone_dialog" msgid="544822455127171206">"Mikrofon je nedostupan"</string>
@@ -1968,6 +1971,9 @@
     <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"Ovoj aplikaciji trenutno ne može da se pristupi sa uređaja <xliff:g id="DEVICE">%1$s</xliff:g>. Probajte na Android TV uređaju."</string>
     <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"Ovoj aplikaciji trenutno ne može da se pristupi sa uređaja <xliff:g id="DEVICE">%1$s</xliff:g>. Probajte na tabletu."</string>
     <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"Ovoj aplikaciji trenutno ne može da se pristupi sa uređaja <xliff:g id="DEVICE">%1$s</xliff:g>. Probajte na telefonu."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tv" msgid="4706276040125072077">"Ova aplikacija zahteva dodatne dozvole, ali dozvole ne mogu da se daju u sesiji strimovanja. Prvo dajte dozvolu na Android TV uređaju."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tablet" msgid="1824604581465771629">"Ova aplikacija zahteva dodatne dozvole, ali dozvole ne mogu da se daju u sesiji strimovanja. Prvo dajte dozvolu na tabletu."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="default" msgid="7755223160363292105">"Ova aplikacija zahteva dodatne dozvole, ali dozvole ne mogu da se daju u sesiji strimovanja. Prvo dajte dozvolu na telefonu."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"Ova aplikacija zahteva dodatnu bezbednost. Probajte na Android TV uređaju."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"Ova aplikacija zahteva dodatnu bezbednost. Probajte na tabletu."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"Ova aplikacija zahteva dodatnu bezbednost. Probajte na telefonu."</string>
diff --git a/core/res/res/values-be/strings.xml b/core/res/res/values-be/strings.xml
index b23022f..217f2c8 100644
--- a/core/res/res/values-be/strings.xml
+++ b/core/res/res/values-be/strings.xml
@@ -592,6 +592,8 @@
     <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"Дазваляе праграме атрымаць доступ да інфармацыі пра прыярытэтны сэрвіс аплаты NFC, напрыклад зарэгістраваныя ідэнтыфікатары праграм і маршруты адпраўкі даных."</string>
     <string name="permlab_nfc" msgid="1904455246837674977">"кантроль Near Field Communication"</string>
     <string name="permdesc_nfc" msgid="8352737680695296741">"Дазваляе прыкладаннzv спалучацца з тэгамі, картамі і счытваючымі прыладамі Near Field Communication (NFC)."</string>
+    <string name="permlab_nfcTransactionEvent" msgid="5868209446710407679">"Трасанкцыі з выкарыстаннем ахоўнага элемента"</string>
+    <string name="permdesc_nfcTransactionEvent" msgid="1904286701876487397">"Праграма зможа атрымліваць інфармацыю пра трансакцыі, якія адбываюцца з выкарыстаннем ахоўнага элемента."</string>
     <string name="permlab_disableKeyguard" msgid="3605253559020928505">"адключэнне блакiроўкi экрана"</string>
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"Дазваляе прыкладанням адключаць блакiроўку клавіятуры і любыя сродкі абароны, звязаныя з паролем. Прыкладам гэтага з\'яўляецца адключэнне тэлефонам блакiроўкi клавіятуры пры атрыманні ўваходнага выкліку і паўторнае ўключэнне блакiроўкi клавіятуры, калі выклік завершаны."</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"запытваць узровень складанасці блакіроўкі экрана"</string>
@@ -622,6 +624,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Біяметрычнае абсталяванне недаступнае"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Аўтэнтыфікацыя скасавана"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Не распазнана"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Твар не распазнаны"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Аўтэнтыфікацыя скасавана"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"Не заданы PIN-код, узор разблакіроўкі або пароль"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Памылка аўтэнтыфікацыі"</string>
@@ -1395,7 +1398,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"АДХІЛІЦЬ"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Выберыце метад уводу"</string>
     <string name="show_ime" msgid="6406112007347443383">"Захоўваць яе на экране ў той час, калі фізічная клавіятура актыўная"</string>
-    <string name="hardware" msgid="1800597768237606953">"Паказаць віртуальную клавіятуру"</string>
+    <string name="hardware" msgid="3611039921284836033">"Экранная клавіятура"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"Наладзьце прыладу \"<xliff:g id="DEVICE_NAME">%s</xliff:g>\""</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Наладзьце фізічныя клавіятуры"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Дакраніцеся, каб выбраць мову і раскладку"</string>
@@ -1958,7 +1961,7 @@
     <string name="app_blocked_title" msgid="7353262160455028160">"Праграма недаступная"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"Праграма \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" цяпер недаступная."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"Недаступна: <xliff:g id="ACTIVITY">%1$s</xliff:g>"</string>
-    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="4483161748582966785">"Патрабуецца дазвол"</string>
+    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="3805704317624448487">"Запыт дазволу заблакіраваны"</string>
     <string name="app_streaming_blocked_title_for_camera_dialog" msgid="3935701653713853065">"Камера недаступная"</string>
     <string name="app_streaming_blocked_title_for_fingerprint_dialog" msgid="3516853717714141951">"Працягніце на тэлефоне"</string>
     <string name="app_streaming_blocked_title_for_microphone_dialog" msgid="544822455127171206">"Мікрафон недаступны"</string>
@@ -1969,6 +1972,9 @@
     <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"Не ўдаецца атрымаць доступ з вашай прылады \"<xliff:g id="DEVICE">%1$s</xliff:g>\". Паспрабуйце скарыстаць прыладу Android TV."</string>
     <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"Не ўдаецца атрымаць доступ з вашай прылады \"<xliff:g id="DEVICE">%1$s</xliff:g>\". Паспрабуйце скарыстаць планшэт."</string>
     <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"Не ўдаецца атрымаць доступ з вашай прылады \"<xliff:g id="DEVICE">%1$s</xliff:g>\". Паспрабуйце скарыстаць тэлефон."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tv" msgid="4706276040125072077">"Гэта праграма запытвае дадатковыя дазволы, якія немагчыма даць падчас трансляцыі. Спачатку дайце дазвол на прыладзе Android TV."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tablet" msgid="1824604581465771629">"Гэта праграма запытвае дадатковыя дазволы, якія немагчыма даць падчас трансляцыі. Спачатку дайце дазвол на планшэце."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="default" msgid="7755223160363292105">"Гэта праграма запытвае дадатковыя дазволы, якія немагчыма даць падчас трансляцыі. Спачатку дайце дазвол на тэлефоне."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"Гэтай праграме патрабуецца дадатковая бяспека. Паспрабуйце скарыстаць прыладу Android TV."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"Гэтай праграме патрабуецца дадатковая бяспека. Паспрабуйце скарыстаць планшэт."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"Гэтай праграме патрабуецца дадатковая бяспека. Паспрабуйце скарыстаць тэлефон."</string>
diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml
index 65c883a..ad166e4 100644
--- a/core/res/res/values-bg/strings.xml
+++ b/core/res/res/values-bg/strings.xml
@@ -590,6 +590,8 @@
     <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"Дава възможност на приложението да получава информация за предпочитаната услуга за плащане чрез NFC, като например регистрирани помощни средства и местоназначение."</string>
     <string name="permlab_nfc" msgid="1904455246837674977">"контролиране на комуникацията в близкото поле"</string>
     <string name="permdesc_nfc" msgid="8352737680695296741">"Разрешава на приложението да комуникира с маркери, карти и четци, ползващи комуникация в близкото поле (NFC)."</string>
+    <string name="permlab_nfcTransactionEvent" msgid="5868209446710407679">"Събитие за транзакции в защитен елемент"</string>
+    <string name="permdesc_nfcTransactionEvent" msgid="1904286701876487397">"Разрешава на приложението да получава информация за транзакциите, които се осъществяват в защитен елемент."</string>
     <string name="permlab_disableKeyguard" msgid="3605253559020928505">"деактивиране на заключването на екрана ви"</string>
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"Разрешава на приложението да деактивира заключването на клавиатурата и свързаната защита с парола. Например телефонът деактивира заключването при получаване на входящо обаждане и после го активира отново, когато обаждането завърши."</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"заявяване на сложност на опцията за заключване на екрана"</string>
@@ -620,6 +622,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Биометричният хардуер не е налице"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Удостоверяването бе анулирано"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Не е разпознато"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Лицето не е разпознато"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Удостоверяването бе анулирано"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"Няма зададен ПИН код, фигура или парола"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Грешка при удостоверяването"</string>
@@ -1393,7 +1396,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"ОТХВЪРЛЯНЕ"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Избор на метод на въвеждане"</string>
     <string name="show_ime" msgid="6406112007347443383">"Показване на екрана, докато физическата клавиатура е активна"</string>
-    <string name="hardware" msgid="1800597768237606953">"Показване на вирт. клавиатура"</string>
+    <string name="hardware" msgid="3611039921284836033">"Ползв. на екранната клавиатура"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"Конфигуриране на <xliff:g id="DEVICE_NAME">%s</xliff:g>"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Конфигуриране на физически клавиатури"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Докоснете, за да изберете език и подредба"</string>
@@ -1956,7 +1959,7 @@
     <string name="app_blocked_title" msgid="7353262160455028160">"Приложението не е достъпно"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"В момента няма достъп до <xliff:g id="APP_NAME">%1$s</xliff:g>."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> не е налице"</string>
-    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="4483161748582966785">"Необходимо е разрешение"</string>
+    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="3805704317624448487">"Заявката за разрешения е блокирана"</string>
     <string name="app_streaming_blocked_title_for_camera_dialog" msgid="3935701653713853065">"Няма достъп до камерата"</string>
     <string name="app_streaming_blocked_title_for_fingerprint_dialog" msgid="3516853717714141951">"Продължете на телефона"</string>
     <string name="app_streaming_blocked_title_for_microphone_dialog" msgid="544822455127171206">"Микрофонът не е достъпен"</string>
@@ -1967,6 +1970,9 @@
     <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"Понастоящем не може да се осъществи достъп от устройството ви <xliff:g id="DEVICE">%1$s</xliff:g>. Вместо това опитайте от устройството си с Android TV."</string>
     <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"Понастоящем не може да се осъществи достъп от устройството ви <xliff:g id="DEVICE">%1$s</xliff:g>. Вместо това опитайте от таблета си."</string>
     <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"Понастоящем не може да се осъществи достъп от устройството ви <xliff:g id="DEVICE">%1$s</xliff:g>. Вместо това опитайте от телефона си."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tv" msgid="4706276040125072077">"Това приложение изисква допълнителни разрешения, но те не могат да бъдат предоставени в сесия за поточно предаване. Първо ги предоставете на устройството си с Android TV."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tablet" msgid="1824604581465771629">"Това приложение изисква допълнителни разрешения, но те не могат да бъдат предоставени в сесия за поточно предаване. Първо ги предоставете на таблета си."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="default" msgid="7755223160363292105">"Това приложение изисква допълнителни разрешения, но те не могат да бъдат предоставени в сесия за поточно предаване. Първо ги предоставете на телефона си."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"Това приложение изисква допълнителна стъпка за сигурност. Вместо това опитайте от устройството си с Android TV."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"Това приложение изисква допълнителна стъпка за сигурност. Вместо това опитайте от таблета си."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"Това приложение изисква допълнителна стъпка за сигурност. Вместо това опитайте от телефона си."</string>
diff --git a/core/res/res/values-bn/strings.xml b/core/res/res/values-bn/strings.xml
index fe686db..4e038b5 100644
--- a/core/res/res/values-bn/strings.xml
+++ b/core/res/res/values-bn/strings.xml
@@ -590,6 +590,8 @@
     <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"অ্যাপের মাধ্যমে পছন্দসই এনএফসি পেমেন্ট পরিষেবার তথ্য, যেমন রেজিস্ট্রার করার সহায়তা এবং রুট ডেস্টিনেশন সম্পর্কিত তথ্য অ্যাক্সেস করার অনুমতি দেয়।"</string>
     <string name="permlab_nfc" msgid="1904455246837674977">"নিয়ার ফিল্ড কমিউনিকেশন নিয়ন্ত্রণ করে"</string>
     <string name="permdesc_nfc" msgid="8352737680695296741">"অ্যাপ্লিকেশানকে নিয়ার ফিল্ড কমিউনিকেশন (NFC) ট্যাগ, কার্ড এবং রিডারগুলির সাথে যোগাযোগ করতে দেয়৷"</string>
+    <string name="permlab_nfcTransactionEvent" msgid="5868209446710407679">"নিরাপদ এলিমেন্ট ট্রানজ্যাকশন ইভেন্ট"</string>
+    <string name="permdesc_nfcTransactionEvent" msgid="1904286701876487397">"নিরাপদ এলিমেন্ট ব্যবহার করা হচ্ছে এমন ট্রানজ্যাকশন সম্পর্কে তথ্য পেতে অ্যাপকে অনুমতি দেয়।"</string>
     <string name="permlab_disableKeyguard" msgid="3605253559020928505">"আপনার স্ক্রিন লক অক্ষম করুন"</string>
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"কী-লক এবং যেকোনো সংশ্লিষ্ট পাসওয়ার্ড সুরক্ষা অক্ষম করতে অ্যাপ্লিকেশানটিকে মঞ্জুর করে৷ উদাহরণস্বরূপ, একটি ইনকামিং ফোন কল গ্রহণ করার সময়ে ফোনটি কী-লক অক্ষম করে, তারপরে কল শেষ হয়ে গেলে কী-লকটিকে আবার সক্ষম করে৷"</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"স্ক্রিন লকের জটিলতা জানার অনুরোধ করুন"</string>
@@ -620,6 +622,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"বায়োমেট্রিক হার্ডওয়্যার পাওয়া যাবে না"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"যাচাইকরণ বাতিল হয়েছে"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"স্বীকৃত নয়"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"ফেস চেনা যায়নি"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"যাচাইকরণ বাতিল হয়েছে"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"পিন, প্যাটার্ন অথবা পাসওয়ার্ড সেট করা নেই"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"যাচাইকরণে সমস্যা হয়েছে"</string>
@@ -1393,7 +1396,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"অস্বীকার করুন"</string>
     <string name="select_input_method" msgid="3971267998568587025">"ইনপুট পদ্ধতি বেছে নিন"</string>
     <string name="show_ime" msgid="6406112007347443383">"ফিজিক্যাল কীবোর্ড সক্রিয় থাকার সময় এটিকে স্ক্রীনে রাখুন"</string>
-    <string name="hardware" msgid="1800597768237606953">"ভার্চুয়াল কীবোর্ড দেখুন"</string>
+    <string name="hardware" msgid="3611039921284836033">"স্ক্রিনের কীবোর্ড ব্যবহার করুন"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"<xliff:g id="DEVICE_NAME">%s</xliff:g> কনফিগার করুন"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"ফিজিক্যাল কীবোর্ড কনফিগার করুন"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"ভাষা ও লেআউট বেছে নিতে ট্যাপ করুন"</string>
@@ -1956,7 +1959,7 @@
     <string name="app_blocked_title" msgid="7353262160455028160">"অ্যাপ পাওয়া যাচ্ছে না"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"এই মুহূর্তে <xliff:g id="APP_NAME">%1$s</xliff:g> অ্যাপ পাওয়া যাচ্ছে না।"</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> উপলভ্য নেই"</string>
-    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="4483161748582966785">"অনুমতি প্রয়োজন"</string>
+    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="3805704317624448487">"অনুমতির অনুরোধ ব্লক করা হয়েছে"</string>
     <string name="app_streaming_blocked_title_for_camera_dialog" msgid="3935701653713853065">"ক্যামেরা উপলভ্য নেই"</string>
     <string name="app_streaming_blocked_title_for_fingerprint_dialog" msgid="3516853717714141951">"ফোনে চালিয়ে যান"</string>
     <string name="app_streaming_blocked_title_for_microphone_dialog" msgid="544822455127171206">"মাইক্রোফোন উপলভ্য নেই"</string>
@@ -1967,6 +1970,9 @@
     <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"এই সময়ে আপনার <xliff:g id="DEVICE">%1$s</xliff:g>-এ এটি অ্যাক্সেস করা যাবে না। পরিবর্তে আপনার Android TV ডিভাইস ব্যবহার করে দেখুন।"</string>
     <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"এই সময়ে আপনার <xliff:g id="DEVICE">%1$s</xliff:g>-এ এটি অ্যাক্সেস করা যাবে না। পরিবর্তে আপনার ট্যাবলেটে ব্যবহার করে দেখুন।"</string>
     <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"এই সময়ে আপনার <xliff:g id="DEVICE">%1$s</xliff:g>-এ এটি অ্যাক্সেস করা যাবে না। পরিবর্তে আপনার ফোনে ব্যবহার করে দেখুন।"</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tv" msgid="4706276040125072077">"এই অ্যাপ অতিরিক্ত অনুমতির জন্য অনুরোধ করছে, কিন্তু স্ট্রিমিং সেশন চলাকালীন অনুমতি দেওয়া যাবে না। প্রথমে আপনার Android TV ডিভাইসে অনুমতি দিন।"</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tablet" msgid="1824604581465771629">"এই অ্যাপ অতিরিক্ত অনুমতির জন্য অনুরোধ করছে, কিন্তু স্ট্রিমিং সেশন চলাকালীন অনুমতি দেওয়া যাবে না। প্রথমে আপনার ট্যাবলেটে অনুমতি দিন।"</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="default" msgid="7755223160363292105">"এই অ্যাপ অতিরিক্ত অনুমতির জন্য অনুরোধ করছে, কিন্তু স্ট্রিমিং সেশন চলাকালীন অনুমতি দেওয়া যাবে না। প্রথমে আপনার ফোনে অনুমতি দিন।"</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"এই অ্যাপ অতিরিক্ত নিরাপত্তার জন্য অনুরোধ করছে। পরিবর্তে আপনার Android TV ডিভাইস ব্যবহার করে দেখুন।"</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"এই অ্যাপ অতিরিক্ত নিরাপত্তার জন্য অনুরোধ করছে। পরিবর্তে আপনার ট্যাবলেটে ব্যবহার করে দেখুন।"</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"এই অ্যাপ অতিরিক্ত নিরাপত্তার জন্য অনুরোধ করছে। পরিবর্তে আপনার ফোনে ব্যবহার করে দেখুন।"</string>
diff --git a/core/res/res/values-bs/strings.xml b/core/res/res/values-bs/strings.xml
index 63fff4f..029d6f4 100644
--- a/core/res/res/values-bs/strings.xml
+++ b/core/res/res/values-bs/strings.xml
@@ -591,6 +591,8 @@
     <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"Dozvoljava aplikaciji da dobije informacije o preferiranoj usluzi plaćanja putem NFC-a kao što su registrirana pomagala i odredište rute."</string>
     <string name="permlab_nfc" msgid="1904455246837674977">"upravljanje NFC-om"</string>
     <string name="permdesc_nfc" msgid="8352737680695296741">"Dozvoljava aplikaciji komuniciranje sa NFC (komunikacija bliskog polja) oznakama, karticama i čitačima."</string>
+    <string name="permlab_nfcTransactionEvent" msgid="5868209446710407679">"Događaj transakcije na sigurnom elementu"</string>
+    <string name="permdesc_nfcTransactionEvent" msgid="1904286701876487397">"Dozvoljava aplikaciji da prima informacije o transakcijama koje se događaju na sigurnom elementu"</string>
     <string name="permlab_disableKeyguard" msgid="3605253559020928505">"deaktivacija zaključavanja ekrana"</string>
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"Omogućava aplikaciji deaktivaciju zaključane tastature i svih povezanih zaštita. Naprimjer, telefon deaktivira zaključavanje tastature kod dolaznog telefonskog poziva, a zatim ponovo aktivira zaključavanje tastature kada je poziv završen."</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"zahtjev za kompleksnost zaključavanja ekrana"</string>
@@ -621,6 +623,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Biometrijski hardver nije dostupan"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Autentifikacija je otkazana"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Nije prepoznato"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Lice nije prepoznato"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Autentifikacija je otkazana"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"Nije postavljen PIN, uzorak niti lozinka"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Greška pri autentifikaciji"</string>
@@ -1394,7 +1397,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"ODBACI"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Odaberite način unosa"</string>
     <string name="show_ime" msgid="6406112007347443383">"Prikaži na ekranu dok je fizička tastatura aktivna"</string>
-    <string name="hardware" msgid="1800597768237606953">"Prikaz virtuelne tastature"</string>
+    <string name="hardware" msgid="3611039921284836033">"Koristi tastaturu na ekranu"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"Konfigurirajte uređaj <xliff:g id="DEVICE_NAME">%s</xliff:g>"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Konfigurirajte fizičke tastature"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Dodirnite da odaberete jezik i raspored"</string>
@@ -1957,7 +1960,7 @@
     <string name="app_blocked_title" msgid="7353262160455028160">"Aplikacija nije dostupna"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> trenutno nije dostupna."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"Nedostupno: <xliff:g id="ACTIVITY">%1$s</xliff:g>"</string>
-    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="4483161748582966785">"Potrebno je odobrenje"</string>
+    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="3805704317624448487">"Zahtjev za odobrenje je potisnut"</string>
     <string name="app_streaming_blocked_title_for_camera_dialog" msgid="3935701653713853065">"Kamera nije dostupna"</string>
     <string name="app_streaming_blocked_title_for_fingerprint_dialog" msgid="3516853717714141951">"Nastavite na telefonu"</string>
     <string name="app_streaming_blocked_title_for_microphone_dialog" msgid="544822455127171206">"Mikrofon nije dostupan"</string>
@@ -1968,6 +1971,9 @@
     <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"Trenutno ne možete pristupiti ovoj aplikaciji na uređaju <xliff:g id="DEVICE">%1$s</xliff:g>. Umjesto toga pokušajte na uređaju Android TV."</string>
     <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"Trenutno ne možete pristupiti ovoj aplikaciji na uređaju <xliff:g id="DEVICE">%1$s</xliff:g>. Umjesto toga pokušajte na tabletu."</string>
     <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"Trenutno ne možete pristupiti ovoj aplikaciji na uređaju <xliff:g id="DEVICE">%1$s</xliff:g>. Umjesto toga pokušajte na telefonu."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tv" msgid="4706276040125072077">"Aplikacija traži dodatna odobrenja, ali se ona ne mogu dati u sesiji prijenosa. Prvo dajte odobrenje na Android TV uređaju."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tablet" msgid="1824604581465771629">"Aplikacija traži dodatna odobrenja, ali se ona ne mogu dati u sesiji prijenosa. Prvo dajte odobrenje na tabletu."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="default" msgid="7755223160363292105">"Aplikacija traži dodatna odobrenja, ali se ona ne mogu dati u sesiji prijenosa. Prvo dodajte odobrenje na telefonu."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"Ova aplikacija zahtijeva dodatnu sigurnost. Umjesto toga pokušajte na uređaju Android TV."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"Ova aplikacija zahtijeva dodatnu sigurnost. Umjesto toga pokušajte na tabletu."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"Ova aplikacija zahtijeva dodatnu sigurnost. Umjesto toga pokušajte na telefonu."</string>
diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml
index b581d49..4417083 100644
--- a/core/res/res/values-ca/strings.xml
+++ b/core/res/res/values-ca/strings.xml
@@ -591,6 +591,8 @@
     <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"Permet que l\'aplicació obtingui informació preferent sobre el servei de pagament per NFC, com ara complements registrats i destinacions de rutes."</string>
     <string name="permlab_nfc" msgid="1904455246837674977">"controlar Comunicació de camp proper (NFC)"</string>
     <string name="permdesc_nfc" msgid="8352737680695296741">"Permet que l\'aplicació es comuniqui amb les etiquetes, les targetes i els lectors de Comunicació de camp proper (NFC)."</string>
+    <string name="permlab_nfcTransactionEvent" msgid="5868209446710407679">"Esdeveniment de transacció d\'un element segur"</string>
+    <string name="permdesc_nfcTransactionEvent" msgid="1904286701876487397">"Permet que l\'aplicació rebi informació sobre les transaccions que tenen lloc en un element segur."</string>
     <string name="permlab_disableKeyguard" msgid="3605253559020928505">"desactivació del bloqueig de pantalla"</string>
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"Permet que l\'aplicació desactivi el bloqueig del teclat i qualsevol element de seguretat de contrasenyes associat. Per exemple, el telèfon desactiva el bloqueig del teclat en rebre una trucada entrant i, a continuació, reactiva el bloqueig del teclat quan finalitza la trucada."</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"sol·licita una determinada complexitat del bloqueig de pantalla"</string>
@@ -621,6 +623,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Maquinari biomètric no disponible"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"S\'ha cancel·lat l\'autenticació"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"No s\'ha reconegut"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"No s\'ha reconegut la cara"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"S\'ha cancel·lat l\'autenticació"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"No s\'ha definit cap PIN, patró o contrasenya"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Error en l\'autenticació"</string>
@@ -1394,7 +1397,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"REBUTJA"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Selecciona un mètode d\'introducció"</string>
     <string name="show_ime" msgid="6406112007347443383">"Mantén-lo en pantalla mentre el teclat físic està actiu"</string>
-    <string name="hardware" msgid="1800597768237606953">"Mostra el teclat virtual"</string>
+    <string name="hardware" msgid="3611039921284836033">"Utilitza el teclat en pantalla"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"Configura <xliff:g id="DEVICE_NAME">%s</xliff:g>"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Configura els teclats físics"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Toca per seleccionar l\'idioma i la disposició"</string>
@@ -1957,7 +1960,7 @@
     <string name="app_blocked_title" msgid="7353262160455028160">"L\'aplicació no està disponible"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"Ara mateix, <xliff:g id="APP_NAME">%1$s</xliff:g> no està disponible."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> no està disponible"</string>
-    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="4483161748582966785">"Permís necessari"</string>
+    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="3805704317624448487">"S\'ha suprimit la sol·licitud de permís"</string>
     <string name="app_streaming_blocked_title_for_camera_dialog" msgid="3935701653713853065">"La càmera no està disponible"</string>
     <string name="app_streaming_blocked_title_for_fingerprint_dialog" msgid="3516853717714141951">"Continua al telèfon"</string>
     <string name="app_streaming_blocked_title_for_microphone_dialog" msgid="544822455127171206">"El micròfon no està disponible"</string>
@@ -1968,6 +1971,9 @@
     <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"En aquests moments, No s\'hi pot accedir des del teu <xliff:g id="DEVICE">%1$s</xliff:g>. Prova-ho al dispositiu Android TV."</string>
     <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"En aquests moments, No s\'hi pot accedir des del teu <xliff:g id="DEVICE">%1$s</xliff:g>. Prova-ho a la tauleta."</string>
     <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"No s\'hi pot accedir des del teu <xliff:g id="DEVICE">%1$s</xliff:g>. Prova-ho al telèfon."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tv" msgid="4706276040125072077">"Aquesta aplicació requereix permisos addicionals, però els permisos no es poden concedir en una sessió de reproducció en continu. Primer concedeix el permís al teu dispositiu Android TV."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tablet" msgid="1824604581465771629">"Aquesta aplicació requereix permisos addicionals, però els permisos no es poden concedir en una sessió de reproducció en continu. Primer concedeix el permís a la teva tauleta."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="default" msgid="7755223160363292105">"Aquesta aplicació requereix permisos addicionals, però els permisos no es poden concedir en una sessió de reproducció en continu. Primer concedeix el permís al teu telèfon."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"Aquesta aplicació sol·licita seguretat addicional. Prova-ho al dispositiu Android TV."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"Aquesta aplicació sol·licita seguretat addicional. Prova-ho a la tauleta."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"Aquesta aplicació sol·licita seguretat addicional. Prova-ho al telèfon."</string>
diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml
index bb07a79..df6cc64 100644
--- a/core/res/res/values-cs/strings.xml
+++ b/core/res/res/values-cs/strings.xml
@@ -592,6 +592,8 @@
     <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"Umožňuje aplikaci získat informace o preferované platební službě NFC, například o registrovaných pomůckách a cíli směrování."</string>
     <string name="permlab_nfc" msgid="1904455246837674977">"ovládání technologie NFC"</string>
     <string name="permdesc_nfc" msgid="8352737680695296741">"Umožňuje aplikaci komunikovat se štítky, kartami a čtečkami s podporou technologie NFC."</string>
+    <string name="permlab_nfcTransactionEvent" msgid="5868209446710407679">"Událost transakce Secure Element"</string>
+    <string name="permdesc_nfcTransactionEvent" msgid="1904286701876487397">"Umožňuje aplikaci přijímat informace o transakcích probíhajících v prvku Secure Element."</string>
     <string name="permlab_disableKeyguard" msgid="3605253559020928505">"vypnutí zámku obrazovky"</string>
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"Umožňuje aplikaci vypnout zámek kláves a související zabezpečení heslem. Telefon například vypne zámek klávesnice při příchozím hovoru a po skončení hovoru jej zase zapne."</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"zjištění složitosti zámku obrazovky"</string>
@@ -622,6 +624,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Biometrický hardware není k dispozici"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Ověření bylo zrušeno"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Nerozpoznáno"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Obličej nebyl rozpoznán"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Ověření bylo zrušeno"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"Není nastaven žádný PIN, gesto ani heslo"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Při ověřování došlo k chybě"</string>
@@ -1395,7 +1398,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"ODMÍTNOUT"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Vybrat metodu zadávání"</string>
     <string name="show_ime" msgid="6406112007347443383">"Ponechat na obrazovce, když je aktivní fyzická klávesnice"</string>
-    <string name="hardware" msgid="1800597768237606953">"Zobrazit virtuální klávesnici"</string>
+    <string name="hardware" msgid="3611039921284836033">"Použít softwarovou klávesnici"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"Nakonfigurujte zařízení <xliff:g id="DEVICE_NAME">%s</xliff:g>"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Nakonfigurujte fyzické klávesnice"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Klepnutím vyberte jazyk a rozvržení"</string>
@@ -1958,7 +1961,7 @@
     <string name="app_blocked_title" msgid="7353262160455028160">"Aplikace není k dispozici"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"Aplikace <xliff:g id="APP_NAME">%1$s</xliff:g> v tuto chvíli není k dispozici."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> není k dispozici"</string>
-    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="4483161748582966785">"Je vyžadováno oprávnění"</string>
+    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="3805704317624448487">"Žádost o oprávnění byla potlačena"</string>
     <string name="app_streaming_blocked_title_for_camera_dialog" msgid="3935701653713853065">"Kamera není k dispozici"</string>
     <string name="app_streaming_blocked_title_for_fingerprint_dialog" msgid="3516853717714141951">"Pokračujte na telefonu"</string>
     <string name="app_streaming_blocked_title_for_microphone_dialog" msgid="544822455127171206">"Mikrofon není k dispozici"</string>
@@ -1969,6 +1972,9 @@
     <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"Tato položka na vašem zařízení <xliff:g id="DEVICE">%1$s</xliff:g> v tuto chvíli není k dispozici. Zkuste to na zařízení Android TV."</string>
     <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"Tato položka na vašem zařízení <xliff:g id="DEVICE">%1$s</xliff:g> v tuto chvíli není k dispozici. Zkuste to na tabletu."</string>
     <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"Tato položka na vašem zařízení <xliff:g id="DEVICE">%1$s</xliff:g> v tuto chvíli není k dispozici. Zkuste to na telefonu."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tv" msgid="4706276040125072077">"Tato aplikace požaduje další oprávnění, která ale nelze udělit během relace streamování. Oprávnění udělte nejprve na zařízení Android TV."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tablet" msgid="1824604581465771629">"Tato aplikace požaduje další oprávnění, která ale nelze udělit během relace streamování. Oprávnění udělte nejprve na tabletu."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="default" msgid="7755223160363292105">"Tato aplikace požaduje další oprávnění, která ale nelze udělit během relace streamování. Oprávnění udělte nejprve na telefonu."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"Tato aplikace požaduje další zabezpečení. Zkuste to na zařízení Android TV."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"Tato aplikace požaduje další zabezpečení. Zkuste to na tabletu."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"Tato aplikace požaduje další zabezpečení. Zkuste to na telefonu."</string>
diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml
index 43f6a8c..1a74897 100644
--- a/core/res/res/values-da/strings.xml
+++ b/core/res/res/values-da/strings.xml
@@ -590,6 +590,8 @@
     <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"Tillader, at appen får foretrukne oplysninger vedrørende NFC-betalingstjeneste, f.eks. registrerede hjælpemidler og rutedestinationer."</string>
     <string name="permlab_nfc" msgid="1904455246837674977">"administrere Near Field Communication"</string>
     <string name="permdesc_nfc" msgid="8352737680695296741">"Tillader, at appen kan kommunikere med NFC-tags (Near Field Communication), -kort og -læsere."</string>
+    <string name="permlab_nfcTransactionEvent" msgid="5868209446710407679">"Secure Element-transaktion"</string>
+    <string name="permdesc_nfcTransactionEvent" msgid="1904286701876487397">"Tillader, at appen kan modtage oplysninger om transaktioner, der foretages via Secure Element."</string>
     <string name="permlab_disableKeyguard" msgid="3605253559020928505">"deaktivere din skærmlås"</string>
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"Tillader, at appen kan deaktivere tastaturlåsen og anden form for tilknyttet adgangskodesikkerhed. Telefonen deaktiverer f.eks. tastaturlåsen ved indgående telefonopkald og aktiverer tastaturlåsen igen, når opkaldet er afsluttet."</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"anmode om skærmlåsens kompleksitet"</string>
@@ -620,6 +622,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Biometrisk hardware er ikke tilgængelig"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Godkendelsen blev annulleret"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Ikke genkendt"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Ansigt blev ikke genkendt"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Godkendelsen blev annulleret"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"Der er ikke angivet pinkode, mønster eller adgangskode"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Der opstod fejl i forbindelse med godkendelse"</string>
@@ -1393,7 +1396,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"AFVIS"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Vælg inputmetode"</string>
     <string name="show_ime" msgid="6406112007347443383">"Behold den på skærmen, mens det fysiske tastatur er aktivt"</string>
-    <string name="hardware" msgid="1800597768237606953">"Vis virtuelt tastatur"</string>
+    <string name="hardware" msgid="3611039921284836033">"Brug skærmtastaturet"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"Konfigurer <xliff:g id="DEVICE_NAME">%s</xliff:g>"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Konfigurer fysiske tastaturer"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Tryk for at vælge sprog og layout"</string>
@@ -1956,7 +1959,7 @@
     <string name="app_blocked_title" msgid="7353262160455028160">"Appen er ikke tilgængelig"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> er ikke tilgængelig lige nu."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> er ikke understøttet"</string>
-    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="4483161748582966785">"Der kræves tilladelse"</string>
+    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="3805704317624448487">"Anmodning om tilladelse blokeret"</string>
     <string name="app_streaming_blocked_title_for_camera_dialog" msgid="3935701653713853065">"Kameraet er ikke tilgængeligt"</string>
     <string name="app_streaming_blocked_title_for_fingerprint_dialog" msgid="3516853717714141951">"Fortsæt på telefonen"</string>
     <string name="app_streaming_blocked_title_for_microphone_dialog" msgid="544822455127171206">"Mikrofonen er ikke tilgængelig"</string>
@@ -1967,6 +1970,9 @@
     <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"Dette er ikke tilgængeligt på din <xliff:g id="DEVICE">%1$s</xliff:g> på nuværende tidspunkt. Prøv på din Android TV-enhed i stedet."</string>
     <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"Dette er ikke tilgængeligt på din <xliff:g id="DEVICE">%1$s</xliff:g> på nuværende tidspunkt. Prøv på din tablet i stedet."</string>
     <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"Dette er ikke tilgængeligt på din <xliff:g id="DEVICE">%1$s</xliff:g> på nuværende tidspunkt. Prøv på din telefon i stedet."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tv" msgid="4706276040125072077">"Denne app anmoder om yderligere tilladelser, men der kan ikke gives tilladelser under en streamingsession. Giv tilladelse på din Android TV-enhed først."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tablet" msgid="1824604581465771629">"Denne app anmoder om yderligere tilladelser, men der kan ikke gives tilladelser under en streamingsession. Giv tilladelse på din tablet først."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="default" msgid="7755223160363292105">"Denne app anmoder om yderligere tilladelser, men der kan ikke gives tilladelser under en streamingsession. Giv tilladelse på din telefon først."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"Denne app anmoder om yderligere sikkerhed. Prøv på din Android TV-enhed i stedet."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"Denne app anmoder om yderligere sikkerhed. Prøv på din tablet i stedet."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"Denne app anmoder om yderligere sikkerhed. Prøv på din telefon i stedet."</string>
diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml
index 55e8455..b8275c5 100644
--- a/core/res/res/values-de/strings.xml
+++ b/core/res/res/values-de/strings.xml
@@ -590,6 +590,8 @@
     <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"Ermöglicht der App, Informationen zum bevorzugten NFC-Zahlungsdienst abzurufen, etwa registrierte Hilfsmittel oder das Routenziel."</string>
     <string name="permlab_nfc" msgid="1904455246837674977">"Nahfeldkommunikation steuern"</string>
     <string name="permdesc_nfc" msgid="8352737680695296741">"Ermöglicht der App die Kommunikation mit Tags für die Nahfeldkommunikation, Karten und Readern"</string>
+    <string name="permlab_nfcTransactionEvent" msgid="5868209446710407679">"Transaktion in einem Secure Element"</string>
+    <string name="permdesc_nfcTransactionEvent" msgid="1904286701876487397">"Ermöglicht der App, Informationen zu Transaktionen abzurufen, die in einem Secure Element stattfinden."</string>
     <string name="permlab_disableKeyguard" msgid="3605253559020928505">"Displaysperre deaktivieren"</string>
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"Ermöglicht der App, die Tastensperre sowie den damit verbundenen Passwortschutz zu deaktivieren. Das Telefon deaktiviert die Tastensperre beispielsweise, wenn ein Anruf eingeht, und aktiviert sie wieder, nachdem das Gespräch beendet wurde."</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"Komplexitätsstufe der Displaysperre anfragen"</string>
@@ -620,6 +622,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Biometrische Hardware nicht verfügbar"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Authentifizierung abgebrochen"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Nicht erkannt"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Gesicht nicht erkannt"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Authentifizierung abgebrochen"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"Keine PIN, kein Muster und kein Passwort festgelegt"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Fehler bei der Authentifizierung"</string>
@@ -1393,7 +1396,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"ABLEHNEN"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Eingabemethode wählen"</string>
     <string name="show_ime" msgid="6406112007347443383">"Bildschirmtastatur auch dann anzeigen, wenn physische Tastatur aktiv ist"</string>
-    <string name="hardware" msgid="1800597768237606953">"Virtuelle Tastatur einblenden"</string>
+    <string name="hardware" msgid="3611039921284836033">"Bildschirmtastatur verwenden"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"<xliff:g id="DEVICE_NAME">%s</xliff:g> konfigurieren"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Physische Tastaturen konfigurieren"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Zum Auswählen von Sprache und Layout tippen"</string>
@@ -1956,7 +1959,7 @@
     <string name="app_blocked_title" msgid="7353262160455028160">"App ist nicht verfügbar"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> ist derzeit nicht verfügbar."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> nicht verfügbar"</string>
-    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="4483161748582966785">"Berechtigung erforderlich"</string>
+    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="3805704317624448487">"Berechtigungsanfrage unterdrückt"</string>
     <string name="app_streaming_blocked_title_for_camera_dialog" msgid="3935701653713853065">"Kamera nicht verfügbar"</string>
     <string name="app_streaming_blocked_title_for_fingerprint_dialog" msgid="3516853717714141951">"Weiter auf Smartphone"</string>
     <string name="app_streaming_blocked_title_for_microphone_dialog" msgid="544822455127171206">"Mikrofon nicht verfügbar"</string>
@@ -1967,6 +1970,9 @@
     <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"Auf deinem <xliff:g id="DEVICE">%1$s</xliff:g> ist derzeit kein Zugriff möglich. Versuche es stattdessen auf deinem Android TV-Gerät."</string>
     <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"Auf deinem <xliff:g id="DEVICE">%1$s</xliff:g> ist derzeit kein Zugriff möglich. Versuche es stattdessen auf deinem Tablet."</string>
     <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"Auf deinem <xliff:g id="DEVICE">%1$s</xliff:g> ist derzeit kein Zugriff möglich. Versuche es stattdessen auf deinem Smartphone."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tv" msgid="4706276040125072077">"Diese App fordert zusätzliche Berechtigungen an – in einer Streamingsitzung können jedoch keine Berechtigungen gewährt werden. Gewähre die Berechtigung zuerst auf deinem Android TV-Gerät."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tablet" msgid="1824604581465771629">"Diese App fordert zusätzliche Berechtigungen an – in einer Streamingsitzung können jedoch keine Berechtigungen gewährt werden. Gewähre die Berechtigung zuerst auf deinem Tablet."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="default" msgid="7755223160363292105">"Diese App fordert zusätzliche Berechtigungen an – in einer Streamingsitzung können jedoch keine Berechtigungen gewährt werden. Gewähre die Berechtigung zuerst auf deinem Smartphone."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"Diese App fordert zusätzliche Sicherheit an. Versuch es stattdessen auf deinem Android TV-Gerät."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"Diese App fordert zusätzliche Sicherheit an. Versuch es stattdessen auf deinem Tablet."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"Diese App fordert zusätzliche Sicherheit an. Versuch es stattdessen auf deinem Smartphone."</string>
diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml
index 5f7306a..f2e5c4d 100644
--- a/core/res/res/values-el/strings.xml
+++ b/core/res/res/values-el/strings.xml
@@ -590,6 +590,8 @@
     <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"Επιτρέπει στην εφαρμογή να λαμβάνει πληροφορίες προτιμώμενης υπηρεσίας πληρωμής NFC, όπως καταχωρημένα βοηθήματα και προορισμό διαδρομής."</string>
     <string name="permlab_nfc" msgid="1904455246837674977">"ελέγχει την Επικοινωνία κοντινού πεδίου (FNC)"</string>
     <string name="permdesc_nfc" msgid="8352737680695296741">"Επιτρέπει στην εφαρμογή την επικοινωνία με ετικέτες, κάρτες και αναγνώστες της Επικοινωνίας κοντινού πεδίου (NFC)."</string>
+    <string name="permlab_nfcTransactionEvent" msgid="5868209446710407679">"Συμβάν συναλλαγής ασφαλούς στοιχείου"</string>
+    <string name="permdesc_nfcTransactionEvent" msgid="1904286701876487397">"Επιτρέπει στην εφαρμογή να λαμβάνει πληροφορίες σχετικά με συναλλαγές που πραγματοποιούνται σε ένα ασφαλές στοιχείο."</string>
     <string name="permlab_disableKeyguard" msgid="3605253559020928505">"απενεργοποιεί το κλείδωμα οθόνης"</string>
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"Επιτρέπει στην εφαρμογή την απενεργοποίηση του κλειδώματος πληκτρολογίου και άλλης σχετικής ασφάλειας με κωδικό πρόσβασης. Για παράδειγμα, το κλείδωμα πληκτρολογίου στο τηλέφωνο απενεργοποιείται όταν λαμβάνεται εισερχόμενη τηλεφωνική κλήση και ενεργοποιείται ξανά όταν η κλήση τερματιστεί."</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"υποβολή αιτήματος για πολυπλοκότητα οθόνης κλειδώματος"</string>
@@ -620,6 +622,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Δεν υπάρχει διαθέσιμος βιομετρικός εξοπλισμός"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Ο έλεγχος ταυτότητας ακυρώθηκε"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Δεν αναγνωρίστηκε"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Το πρόσωπο δεν αναγνωρίστηκε"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Ο έλεγχος ταυτότητας ακυρώθηκε"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"Δεν έχει οριστεί PIN, μοτίβο ή κωδικός πρόσβασης"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Σφάλμα κατά τον έλεγχο ταυτότητας"</string>
@@ -1393,7 +1396,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"ΑΠΟΡΡΙΨΗ"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Επιλογή μεθόδου εισόδου"</string>
     <string name="show_ime" msgid="6406112007347443383">"Να παραμένει στην οθόνη όταν είναι ενεργό το κανονικό πληκτρολόγιο"</string>
-    <string name="hardware" msgid="1800597768237606953">"Εμφάνιση εικονικού πληκτρολ."</string>
+    <string name="hardware" msgid="3611039921284836033">"Χρήση πληκτρολογίου οθόνης"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"Διαμόρφωση <xliff:g id="DEVICE_NAME">%s</xliff:g>"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Διαμόρφωση φυσικών πληκτρολογίων"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Πατήστε για να επιλέξετε γλώσσα και διάταξη"</string>
@@ -1956,7 +1959,7 @@
     <string name="app_blocked_title" msgid="7353262160455028160">"Η εφαρμογή δεν είναι διαθέσιμη"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"Η εφαρμογή <xliff:g id="APP_NAME">%1$s</xliff:g> δεν είναι διαθέσιμη αυτήν τη στιγμή."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> δεν διατίθεται"</string>
-    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="4483161748582966785">"Απαιτείται άδεια"</string>
+    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="3805704317624448487">"Το αίτημα άδειας ανεστάλη"</string>
     <string name="app_streaming_blocked_title_for_camera_dialog" msgid="3935701653713853065">"Η κάμερα δεν είναι διαθέσιμη"</string>
     <string name="app_streaming_blocked_title_for_fingerprint_dialog" msgid="3516853717714141951">"Συνέχεια στο τηλέφωνο"</string>
     <string name="app_streaming_blocked_title_for_microphone_dialog" msgid="544822455127171206">"Το μικρόφωνο δεν είναι διαθέσιμο"</string>
@@ -1967,6 +1970,9 @@
     <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"Δεν είναι δυνατή η πρόσβαση στη συγκεκριμένη εφαρμογή από τη συσκευή <xliff:g id="DEVICE">%1$s</xliff:g> αυτήν τη στιγμή. Δοκιμάστε στη συσκευή Android TV."</string>
     <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"Δεν είναι δυνατή η πρόσβαση στη συγκεκριμένη εφαρμογή από τη συσκευή <xliff:g id="DEVICE">%1$s</xliff:g> αυτήν τη στιγμή. Δοκιμάστε στο tablet σας."</string>
     <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"Δεν είναι δυνατή η πρόσβαση στη συγκεκριμένη εφαρμογή από τη συσκευή <xliff:g id="DEVICE">%1$s</xliff:g> αυτήν τη στιγμή. Δοκιμάστε στο τηλέφωνό σας."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tv" msgid="4706276040125072077">"Αυτή η εφαρμογή ζητά πρόσθετες άδειες, αλλά οι άδειες δεν μπορούν να παραχωρηθούν σε μια περίοδο σύνδεσης ροής. Εκχωρήστε πρώτα την άδεια στη συσκευή Android TV."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tablet" msgid="1824604581465771629">"Αυτή η εφαρμογή ζητά πρόσθετες άδειες, αλλά οι άδειες δεν μπορούν να παραχωρηθούν σε μια περίοδο σύνδεσης ροής. Εκχωρήστε πρώτα την άδεια στο tablet σας."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="default" msgid="7755223160363292105">"Αυτή η εφαρμογή ζητά πρόσθετες άδειες, αλλά οι άδειες δεν μπορούν να παραχωρηθούν σε μια περίοδο σύνδεσης ροής. Εκχωρήστε πρώτα την άδεια στο τηλέφωνό σας."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"Αυτή η εφαρμογή ζητά πρόσθετη ασφάλεια. Δοκιμάστε στη συσκευή Android TV."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"Αυτή η εφαρμογή ζητά πρόσθετη ασφάλεια. Δοκιμάστε στο tablet σας."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"Αυτή η εφαρμογή ζητά πρόσθετη ασφάλεια. Δοκιμάστε στο τηλέφωνό σας."</string>
diff --git a/core/res/res/values-en-rAU/strings.xml b/core/res/res/values-en-rAU/strings.xml
index 73b2968..a60b6cc 100644
--- a/core/res/res/values-en-rAU/strings.xml
+++ b/core/res/res/values-en-rAU/strings.xml
@@ -590,6 +590,8 @@
     <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"Allows the app to get preferred NFC payment service information, such as registered aids and route destination."</string>
     <string name="permlab_nfc" msgid="1904455246837674977">"control Near-Field Communication"</string>
     <string name="permdesc_nfc" msgid="8352737680695296741">"Allows the app to communicate with Near Field Communication (NFC) tags, cards and readers."</string>
+    <string name="permlab_nfcTransactionEvent" msgid="5868209446710407679">"Secure Element transaction event"</string>
+    <string name="permdesc_nfcTransactionEvent" msgid="1904286701876487397">"Allows the app to receive information about transactions happening on a Secure Element."</string>
     <string name="permlab_disableKeyguard" msgid="3605253559020928505">"disable your screen lock"</string>
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"Allows the app to disable the keylock and any associated password security. For example, the phone disables the keylock when receiving an incoming phone call, then re-enables the keylock when the call is finished."</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"request screen lock complexity"</string>
@@ -620,6 +622,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Biometric hardware unavailable"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Authentication cancelled"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Not recognised"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Face not recognised"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Authentication cancelled"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"No pin, pattern or password set"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Error while authenticating"</string>
@@ -1393,7 +1396,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"DECLINE"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Choose input method"</string>
     <string name="show_ime" msgid="6406112007347443383">"Keep it on screen while physical keyboard is active"</string>
-    <string name="hardware" msgid="1800597768237606953">"Show virtual keyboard"</string>
+    <string name="hardware" msgid="3611039921284836033">"Use on-screen keyboard"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"Configure <xliff:g id="DEVICE_NAME">%s</xliff:g>"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Configure physical keyboards"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Tap to select language and layout"</string>
@@ -1956,7 +1959,7 @@
     <string name="app_blocked_title" msgid="7353262160455028160">"App is not available"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> is not available right now."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> unavailable"</string>
-    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="4483161748582966785">"Permission needed"</string>
+    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="3805704317624448487">"Permission request suppressed"</string>
     <string name="app_streaming_blocked_title_for_camera_dialog" msgid="3935701653713853065">"Camera unavailable"</string>
     <string name="app_streaming_blocked_title_for_fingerprint_dialog" msgid="3516853717714141951">"Continue on phone"</string>
     <string name="app_streaming_blocked_title_for_microphone_dialog" msgid="544822455127171206">"Microphone unavailable"</string>
@@ -1967,6 +1970,9 @@
     <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"This can’t be accessed on your <xliff:g id="DEVICE">%1$s</xliff:g> at this time. Try on your Android TV device instead."</string>
     <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"This can’t be accessed on your <xliff:g id="DEVICE">%1$s</xliff:g> at this time. Try on your tablet instead."</string>
     <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"This can’t be accessed on your <xliff:g id="DEVICE">%1$s</xliff:g> at this time. Try on your phone instead."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tv" msgid="4706276040125072077">"This app is requesting additional permissions, but permissions can\'t be granted in a streaming session. Grant the permission on your Android TV device first."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tablet" msgid="1824604581465771629">"This app is requesting additional permissions, but permissions can\'t be granted in a streaming session. Grant the permission on your tablet first."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="default" msgid="7755223160363292105">"This app is requesting additional permissions, but permissions can\'t be granted in a streaming session. Grant the permission on your phone first."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"This app is requesting additional security. Try on your Android TV device instead."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"This app is requesting additional security. Try on your tablet instead."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"This app is requesting additional security. Try on your phone instead."</string>
diff --git a/core/res/res/values-en-rCA/strings.xml b/core/res/res/values-en-rCA/strings.xml
index 73017c1..2c6f6a2 100644
--- a/core/res/res/values-en-rCA/strings.xml
+++ b/core/res/res/values-en-rCA/strings.xml
@@ -590,6 +590,8 @@
     <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"Allows the app to get preferred nfc payment service information like registered aids and route destination."</string>
     <string name="permlab_nfc" msgid="1904455246837674977">"control Near Field Communication"</string>
     <string name="permdesc_nfc" msgid="8352737680695296741">"Allows the app to communicate with Near Field Communication (NFC) tags, cards, and readers."</string>
+    <string name="permlab_nfcTransactionEvent" msgid="5868209446710407679">"Secure Element transaction event"</string>
+    <string name="permdesc_nfcTransactionEvent" msgid="1904286701876487397">"Allows the app to receive information about transactions happening on a Secure Element."</string>
     <string name="permlab_disableKeyguard" msgid="3605253559020928505">"disable your screen lock"</string>
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"Allows the app to disable the keylock and any associated password security. For example, the phone disables the keylock when receiving an incoming phone call, then re-enables the keylock when the call is finished."</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"request screen lock complexity"</string>
@@ -620,6 +622,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Biometric hardware unavailable"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Authentication canceled"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Not recognized"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Face not recognized"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Authentication canceled"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"No pin, pattern, or password set"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Error authenticating"</string>
@@ -1393,7 +1396,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"DECLINE"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Choose input method"</string>
     <string name="show_ime" msgid="6406112007347443383">"Keep it on screen while physical keyboard is active"</string>
-    <string name="hardware" msgid="1800597768237606953">"Show virtual keyboard"</string>
+    <string name="hardware" msgid="3611039921284836033">"Use on-screen keyboard"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"Configure <xliff:g id="DEVICE_NAME">%s</xliff:g>"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Configure physical keyboards"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Tap to select language and layout"</string>
@@ -1956,7 +1959,7 @@
     <string name="app_blocked_title" msgid="7353262160455028160">"App is not available"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> is not available right now."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> unavailable"</string>
-    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="4483161748582966785">"Permission needed"</string>
+    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="3805704317624448487">"Permission request suppressed"</string>
     <string name="app_streaming_blocked_title_for_camera_dialog" msgid="3935701653713853065">"Camera unavailable"</string>
     <string name="app_streaming_blocked_title_for_fingerprint_dialog" msgid="3516853717714141951">"Continue on phone"</string>
     <string name="app_streaming_blocked_title_for_microphone_dialog" msgid="544822455127171206">"Microphone unavailable"</string>
@@ -1967,6 +1970,9 @@
     <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"This can’t be accessed on your <xliff:g id="DEVICE">%1$s</xliff:g> at this time. Try on your Android TV device instead."</string>
     <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"This can’t be accessed on your <xliff:g id="DEVICE">%1$s</xliff:g> at this time. Try on your tablet instead."</string>
     <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"This can’t be accessed on your <xliff:g id="DEVICE">%1$s</xliff:g> at this time. Try on your phone instead."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tv" msgid="4706276040125072077">"This app is requesting additional permissions, but permissions can’t be granted in a streaming session. Grant the permission on your Android TV device first."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tablet" msgid="1824604581465771629">"This app is requesting additional permissions, but permissions can’t be granted in a streaming session. Grant the permission on your tablet first."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="default" msgid="7755223160363292105">"This app is requesting additional permissions, but permissions can’t be granted in a streaming session. Grant the permission on your phone first."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"This app is requesting additional security. Try on your Android TV device instead."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"This app is requesting additional security. Try on your tablet instead."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"This app is requesting additional security. Try on your phone instead."</string>
diff --git a/core/res/res/values-en-rGB/strings.xml b/core/res/res/values-en-rGB/strings.xml
index aeabd00..1918fc7 100644
--- a/core/res/res/values-en-rGB/strings.xml
+++ b/core/res/res/values-en-rGB/strings.xml
@@ -590,6 +590,8 @@
     <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"Allows the app to get preferred NFC payment service information, such as registered aids and route destination."</string>
     <string name="permlab_nfc" msgid="1904455246837674977">"control Near-Field Communication"</string>
     <string name="permdesc_nfc" msgid="8352737680695296741">"Allows the app to communicate with Near Field Communication (NFC) tags, cards and readers."</string>
+    <string name="permlab_nfcTransactionEvent" msgid="5868209446710407679">"Secure Element transaction event"</string>
+    <string name="permdesc_nfcTransactionEvent" msgid="1904286701876487397">"Allows the app to receive information about transactions happening on a Secure Element."</string>
     <string name="permlab_disableKeyguard" msgid="3605253559020928505">"disable your screen lock"</string>
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"Allows the app to disable the keylock and any associated password security. For example, the phone disables the keylock when receiving an incoming phone call, then re-enables the keylock when the call is finished."</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"request screen lock complexity"</string>
@@ -620,6 +622,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Biometric hardware unavailable"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Authentication cancelled"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Not recognised"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Face not recognised"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Authentication cancelled"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"No pin, pattern or password set"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Error while authenticating"</string>
@@ -1393,7 +1396,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"DECLINE"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Choose input method"</string>
     <string name="show_ime" msgid="6406112007347443383">"Keep it on screen while physical keyboard is active"</string>
-    <string name="hardware" msgid="1800597768237606953">"Show virtual keyboard"</string>
+    <string name="hardware" msgid="3611039921284836033">"Use on-screen keyboard"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"Configure <xliff:g id="DEVICE_NAME">%s</xliff:g>"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Configure physical keyboards"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Tap to select language and layout"</string>
@@ -1956,7 +1959,7 @@
     <string name="app_blocked_title" msgid="7353262160455028160">"App is not available"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> is not available right now."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> unavailable"</string>
-    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="4483161748582966785">"Permission needed"</string>
+    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="3805704317624448487">"Permission request suppressed"</string>
     <string name="app_streaming_blocked_title_for_camera_dialog" msgid="3935701653713853065">"Camera unavailable"</string>
     <string name="app_streaming_blocked_title_for_fingerprint_dialog" msgid="3516853717714141951">"Continue on phone"</string>
     <string name="app_streaming_blocked_title_for_microphone_dialog" msgid="544822455127171206">"Microphone unavailable"</string>
@@ -1967,6 +1970,9 @@
     <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"This can’t be accessed on your <xliff:g id="DEVICE">%1$s</xliff:g> at this time. Try on your Android TV device instead."</string>
     <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"This can’t be accessed on your <xliff:g id="DEVICE">%1$s</xliff:g> at this time. Try on your tablet instead."</string>
     <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"This can’t be accessed on your <xliff:g id="DEVICE">%1$s</xliff:g> at this time. Try on your phone instead."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tv" msgid="4706276040125072077">"This app is requesting additional permissions, but permissions can\'t be granted in a streaming session. Grant the permission on your Android TV device first."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tablet" msgid="1824604581465771629">"This app is requesting additional permissions, but permissions can\'t be granted in a streaming session. Grant the permission on your tablet first."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="default" msgid="7755223160363292105">"This app is requesting additional permissions, but permissions can\'t be granted in a streaming session. Grant the permission on your phone first."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"This app is requesting additional security. Try on your Android TV device instead."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"This app is requesting additional security. Try on your tablet instead."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"This app is requesting additional security. Try on your phone instead."</string>
diff --git a/core/res/res/values-en-rIN/strings.xml b/core/res/res/values-en-rIN/strings.xml
index 92f9897..95b37b0 100644
--- a/core/res/res/values-en-rIN/strings.xml
+++ b/core/res/res/values-en-rIN/strings.xml
@@ -590,6 +590,8 @@
     <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"Allows the app to get preferred NFC payment service information, such as registered aids and route destination."</string>
     <string name="permlab_nfc" msgid="1904455246837674977">"control Near-Field Communication"</string>
     <string name="permdesc_nfc" msgid="8352737680695296741">"Allows the app to communicate with Near Field Communication (NFC) tags, cards and readers."</string>
+    <string name="permlab_nfcTransactionEvent" msgid="5868209446710407679">"Secure Element transaction event"</string>
+    <string name="permdesc_nfcTransactionEvent" msgid="1904286701876487397">"Allows the app to receive information about transactions happening on a Secure Element."</string>
     <string name="permlab_disableKeyguard" msgid="3605253559020928505">"disable your screen lock"</string>
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"Allows the app to disable the keylock and any associated password security. For example, the phone disables the keylock when receiving an incoming phone call, then re-enables the keylock when the call is finished."</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"request screen lock complexity"</string>
@@ -620,6 +622,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Biometric hardware unavailable"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Authentication cancelled"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Not recognised"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Face not recognised"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Authentication cancelled"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"No pin, pattern or password set"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Error while authenticating"</string>
@@ -1393,7 +1396,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"DECLINE"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Choose input method"</string>
     <string name="show_ime" msgid="6406112007347443383">"Keep it on screen while physical keyboard is active"</string>
-    <string name="hardware" msgid="1800597768237606953">"Show virtual keyboard"</string>
+    <string name="hardware" msgid="3611039921284836033">"Use on-screen keyboard"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"Configure <xliff:g id="DEVICE_NAME">%s</xliff:g>"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Configure physical keyboards"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Tap to select language and layout"</string>
@@ -1956,7 +1959,7 @@
     <string name="app_blocked_title" msgid="7353262160455028160">"App is not available"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> is not available right now."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> unavailable"</string>
-    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="4483161748582966785">"Permission needed"</string>
+    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="3805704317624448487">"Permission request suppressed"</string>
     <string name="app_streaming_blocked_title_for_camera_dialog" msgid="3935701653713853065">"Camera unavailable"</string>
     <string name="app_streaming_blocked_title_for_fingerprint_dialog" msgid="3516853717714141951">"Continue on phone"</string>
     <string name="app_streaming_blocked_title_for_microphone_dialog" msgid="544822455127171206">"Microphone unavailable"</string>
@@ -1967,6 +1970,9 @@
     <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"This can’t be accessed on your <xliff:g id="DEVICE">%1$s</xliff:g> at this time. Try on your Android TV device instead."</string>
     <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"This can’t be accessed on your <xliff:g id="DEVICE">%1$s</xliff:g> at this time. Try on your tablet instead."</string>
     <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"This can’t be accessed on your <xliff:g id="DEVICE">%1$s</xliff:g> at this time. Try on your phone instead."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tv" msgid="4706276040125072077">"This app is requesting additional permissions, but permissions can\'t be granted in a streaming session. Grant the permission on your Android TV device first."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tablet" msgid="1824604581465771629">"This app is requesting additional permissions, but permissions can\'t be granted in a streaming session. Grant the permission on your tablet first."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="default" msgid="7755223160363292105">"This app is requesting additional permissions, but permissions can\'t be granted in a streaming session. Grant the permission on your phone first."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"This app is requesting additional security. Try on your Android TV device instead."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"This app is requesting additional security. Try on your tablet instead."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"This app is requesting additional security. Try on your phone instead."</string>
diff --git a/core/res/res/values-en-rXC/strings.xml b/core/res/res/values-en-rXC/strings.xml
index b560c01..fc534cd 100644
--- a/core/res/res/values-en-rXC/strings.xml
+++ b/core/res/res/values-en-rXC/strings.xml
@@ -590,6 +590,8 @@
     <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‏‏‎‎‎‏‏‏‏‎‏‏‏‎‎‏‏‏‎‏‏‏‏‏‏‎‎‏‏‎‎‎‎‏‏‏‎‎‏‎‏‎‎‏‏‎‏‎‎‏‏‏‏‏‎‏‏‏‎Allows the app to get preferred nfc payment service information like registered aids and route destination.‎‏‎‎‏‎"</string>
     <string name="permlab_nfc" msgid="1904455246837674977">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‏‎‎‏‏‎‏‏‎‏‏‏‏‏‏‎‏‏‏‏‏‏‎‏‎‎‏‏‏‎‏‏‏‎‎‏‏‎‎‎‏‏‎‏‏‏‎‎‏‏‏‏‏‎‎‎‎‏‎control Near Field Communication‎‏‎‎‏‎"</string>
     <string name="permdesc_nfc" msgid="8352737680695296741">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‏‏‏‏‏‎‏‎‏‎‏‏‏‎‎‎‏‎‏‎‏‎‏‎‏‎‎‏‏‏‎‏‏‏‏‏‎‏‏‏‎‏‎‎‎‎‎‎‏‎‏‏‏‎‎‏‎‏‎Allows the app to communicate with Near Field Communication (NFC) tags, cards, and readers.‎‏‎‎‏‎"</string>
+    <string name="permlab_nfcTransactionEvent" msgid="5868209446710407679">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‎‏‎‏‏‏‎‎‎‎‎‎‎‏‎‎‎‏‎‏‏‎‎‏‏‎‏‎‎‏‎‎‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‎‎‏‏‏‏‏‏‏‏‏‎Secure Element transaction event‎‏‎‎‏‎"</string>
+    <string name="permdesc_nfcTransactionEvent" msgid="1904286701876487397">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‏‎‎‏‏‎‏‏‎‏‎‏‏‎‎‎‏‎‏‎‏‎‏‎‏‎‎‏‏‏‏‏‏‏‏‏‎‏‏‎‎‏‎‎‏‎‎‏‎‎‏‏‏‎‎‏‎‏‎Allows the app to receive information about transactions happening on a Secure Element.‎‏‎‎‏‎"</string>
     <string name="permlab_disableKeyguard" msgid="3605253559020928505">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‏‎‎‎‎‎‏‎‎‎‎‏‏‎‏‏‏‏‎‎‎‎‎‎‏‎‏‎‏‎‏‏‏‎‏‎‏‎‎‏‏‏‏‎‏‏‏‏‎‏‏‏‏‏‏‎‎‏‎disable your screen lock‎‏‎‎‏‎"</string>
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‏‎‎‏‎‏‏‏‏‎‎‏‏‏‎‏‎‏‏‎‎‏‎‏‎‏‎‏‏‏‏‏‎‎‏‏‎‎‏‎‎‎‎‎‏‎‏‏‎‎‎‏‏‏‎‏‏‏‎‎Allows the app to disable the keylock and any associated password security. For example, the phone disables the keylock when receiving an incoming phone call, then re-enables the keylock when the call is finished.‎‏‎‎‏‎"</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‎‏‎‎‎‏‏‎‏‎‏‏‎‎‎‏‏‏‎‎‏‎‏‎‏‏‏‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‏‏‏‏‏‏‎‎‏‏‏‏‎‏‎request screen lock complexity‎‏‎‎‏‎"</string>
@@ -620,6 +622,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‎‏‎‏‎‎‏‏‏‎‎‏‎‏‏‏‏‏‎‎‎‏‎‏‎‎‏‏‏‎‏‎‏‎‏‎‏‏‏‎‏‎‏‏‏‎‎‏‎‏‎‎‎‏‏‎‎‎‎‎Biometric hardware unavailable‎‏‎‎‏‎"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‎‏‎‏‏‎‏‏‎‏‏‏‏‏‎‎‏‎‏‏‎‎‎‏‎‏‏‏‏‎‎‎‎‏‏‎‎‎‏‏‎‎‎‎‎‏‏‎‎‏‎‎‏‎‎‎‏‎‎Authentication canceled‎‏‎‎‏‎"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‏‏‎‏‏‎‏‏‏‏‎‏‎‎‏‏‎‎‏‎‏‎‏‏‎‏‎‏‏‎‎‏‎‎‏‏‎‏‏‎‎‎‎‏‏‎‏‎‎‎‏‎‏‏‏‎‎‎‎‎Not recognized‎‏‎‎‏‎"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‏‎‎‏‏‎‏‎‎‏‎‎‏‏‎‎‏‏‎‎‏‏‎‎‎‎‎‎‏‎‎‏‎‏‎‎‏‏‏‏‏‏‏‏‎‎‎‎‎‏‏‏‎‎‏‎‎‎‎‎Face not recognized‎‏‎‎‏‎"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‏‎‏‎‏‏‏‎‎‎‏‏‎‎‏‏‎‎‏‏‏‎‎‏‏‎‎‎‏‎‎‎‏‏‎‎‏‏‏‎‏‏‎‏‎‏‎‎‎‏‎‏‎‎‎‎‏‎‎Authentication canceled‎‏‎‎‏‎"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‏‏‎‏‏‎‏‏‏‏‎‏‏‏‎‎‎‏‏‎‎‎‎‎‎‏‎‎‎‎‎‏‏‏‏‏‏‏‎‏‎‎‏‏‏‎‏‏‎‏‎‎‎‏‎‏‎‎‎No pin, pattern, or password set‎‏‎‎‏‎"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‏‎‎‎‏‎‎‏‏‎‏‏‏‎‏‏‏‎‎‏‎‏‎‎‏‏‎‎‏‏‏‏‏‎‏‏‏‎‏‎‎‏‏‏‎‎‎‎‏‏‎‏‎‎‎‏‏‏‎Error authenticating‎‏‎‎‏‎"</string>
@@ -1393,7 +1396,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‎‎‎‎‎‏‎‏‎‎‎‎‏‎‎‏‎‎‎‏‏‎‎‎‏‏‏‏‏‏‏‏‎‎‏‏‎‎‏‏‎‎‎‏‎‎‎‎‎‏‏‎‎‏‏‎‏‎‎DECLINE‎‏‎‎‏‎"</string>
     <string name="select_input_method" msgid="3971267998568587025">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‏‏‎‎‎‏‏‏‎‎‏‏‎‎‎‏‏‏‎‎‏‏‎‏‎‎‎‎‏‏‏‎‏‎‏‎‎‎‏‏‏‎‏‎‎‏‏‏‏‏‎‎‎‏‎‎‎‏‎Choose input method‎‏‎‎‏‎"</string>
     <string name="show_ime" msgid="6406112007347443383">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‎‎‏‏‏‎‎‏‏‏‎‎‎‏‎‏‎‎‏‏‏‎‏‏‎‏‏‏‏‏‏‏‎‏‎‏‎‎‏‏‏‏‎‎‎‏‎‏‏‎‏‎‏‏‎‏‏‏‎Keep it on screen while physical keyboard is active‎‏‎‎‏‎"</string>
-    <string name="hardware" msgid="1800597768237606953">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‎‎‏‏‏‏‏‏‎‏‎‎‎‎‎‎‏‎‎‎‏‎‎‎‎‏‎‏‎‏‎‏‏‏‎‎‏‎‏‎‎‏‏‏‏‎‏‎‎‎‎‎‏‎‏‎‎‏‎Show virtual keyboard‎‏‎‎‏‎"</string>
+    <string name="hardware" msgid="3611039921284836033">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‏‎‎‎‎‏‏‏‎‎‏‏‏‏‏‏‎‏‏‎‏‎‏‏‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‏‏‏‏‏‏‎‏‎‏‎‏‏‎‎‎‎‎‏‎Use on-screen keyboard‎‏‎‎‏‎"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‎‎‏‏‎‏‎‎‎‎‎‎‏‎‏‎‎‏‎‏‏‏‎‎‏‏‏‏‎‎‎‎‎‎‏‏‎‎‎‏‎‏‏‎‎‏‏‏‎‏‏‎‎‏‎‏‎‏‎Configure ‎‏‎‎‏‏‎<xliff:g id="DEVICE_NAME">%s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‎‎‏‎‎‏‎‎‎‏‏‎‎‏‏‎‎‎‎‎‎‎‎‎‎‎‎‏‎‏‎‎‎‎‏‎‎‏‏‎‏‎‏‎‏‎‏‎‎‎‏‎‎‎‏‎‎‏‎‎Configure physical keyboards‎‏‎‎‏‎"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‎‏‎‏‎‎‏‏‏‎‎‏‏‎‎‏‎‎‏‏‎‎‏‏‏‎‏‏‎‎‎‎‏‎‎‏‏‎‎‏‏‎‏‎‎‏‎‏‏‏‎‎‎‎‎‏‎‏‎‎Tap to select language and layout‎‏‎‎‏‎"</string>
@@ -1956,7 +1959,7 @@
     <string name="app_blocked_title" msgid="7353262160455028160">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‏‎‎‎‎‎‏‏‎‎‎‎‎‎‏‎‎‎‏‏‏‏‏‎‎‏‏‏‎‎‎‏‏‎‎‎‏‏‏‏‏‎‎‏‎‎‏‏‎‏‏‏‎‎‎‎‎‎‎App is not available‎‏‎‎‏‎"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‏‏‏‏‏‎‎‎‏‎‎‏‎‎‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‎‏‏‎‏‎‎‎‎‎‏‏‎‎‏‎‏‎‎‏‏‏‏‏‏‏‏‏‏‏‏‎‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎ is not available right now.‎‏‎‎‏‎"</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‎‎‏‎‎‎‎‏‏‏‎‏‏‎‎‎‏‎‏‏‏‏‎‏‏‏‎‏‏‎‏‏‏‎‏‏‏‎‎‏‎‎‎‎‎‎‎‎‏‏‎‎‏‏‏‎‏‏‎‎‏‎‎‏‏‎<xliff:g id="ACTIVITY">%1$s</xliff:g>‎‏‎‎‏‏‏‎ unavailable‎‏‎‎‏‎"</string>
-    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="4483161748582966785">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‏‎‎‎‏‏‎‏‏‏‎‏‏‎‎‎‏‏‏‏‎‏‏‎‏‏‏‎‎‎‎‏‏‎‏‏‏‎‏‎‎‎‎‎‏‎‎‏‏‎‎‎‎‎‎‎‎‏‎Permission needed‎‏‎‎‏‎"</string>
+    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="3805704317624448487">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‎‎‏‏‎‏‎‎‎‎‏‎‎‏‎‎‏‏‏‏‏‎‏‎‎‏‏‏‎‏‎‏‎‎‎‎‏‎‏‏‎‎‏‏‏‏‎‏‎‏‏‏‏‎‎‏‏‏‎Permission request suppressed‎‏‎‎‏‎"</string>
     <string name="app_streaming_blocked_title_for_camera_dialog" msgid="3935701653713853065">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‏‎‏‎‎‏‏‏‏‎‎‏‏‎‏‎‏‏‏‏‎‎‏‏‎‏‎‏‎‎‎‎‏‏‏‎‎‎‏‎‎‎‎‎‎‏‏‎‏‎‏‎‎‎‏‎‎‏‎Camera unavailable‎‏‎‎‏‎"</string>
     <string name="app_streaming_blocked_title_for_fingerprint_dialog" msgid="3516853717714141951">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‎‎‏‏‎‎‏‏‏‎‎‏‎‏‏‏‏‏‏‏‎‏‎‎‏‎‏‎‏‏‏‎‏‏‎‏‎‏‏‏‎‏‎‎‎‎‎‎‏‎‏‏‏‏‏‏‏‏‎Continue on phone‎‏‎‎‏‎"</string>
     <string name="app_streaming_blocked_title_for_microphone_dialog" msgid="544822455127171206">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‏‏‏‏‏‎‎‎‏‏‏‏‏‎‎‏‏‎‎‏‎‎‏‎‎‎‏‎‏‏‏‎‎‏‏‏‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‏‏‎‎Microphone unavailable‎‏‎‎‏‎"</string>
@@ -1967,6 +1970,9 @@
     <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‏‏‏‎‎‎‏‏‎‏‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‎‎‎‏‎‎‏‏‏‏‎‏‎‏‎‎‎‏‎‏‎‏‏‎‏‎‎‏‎‏‎‎‏‎This can’t be accessed on your ‎‏‎‎‏‏‎<xliff:g id="DEVICE">%1$s</xliff:g>‎‏‎‎‏‏‏‎ at this time. Try on your Android TV device instead.‎‏‎‎‏‎"</string>
     <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‏‎‏‏‎‏‏‏‏‎‏‏‎‎‏‏‎‎‏‏‎‎‎‏‎‏‎‎‎‏‏‎‏‏‎‎‎‏‏‎‏‎‏‎‏‏‏‎‏‎‏‎‎‎‏‏‏‎‎This can’t be accessed on your ‎‏‎‎‏‏‎<xliff:g id="DEVICE">%1$s</xliff:g>‎‏‎‎‏‏‏‎ at this time. Try on your tablet instead.‎‏‎‎‏‎"</string>
     <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‎‏‎‏‏‏‏‎‎‏‏‏‎‎‎‎‏‏‎‏‎‏‎‏‎‎‏‏‏‏‎‎‏‏‎‎‏‏‎‎‏‏‎‎‎‎‎‎‎‏‏‏‎‎‎‏‏‎‎This can’t be accessed on your ‎‏‎‎‏‏‎<xliff:g id="DEVICE">%1$s</xliff:g>‎‏‎‎‏‏‏‎ at this time. Try on your phone instead.‎‏‎‎‏‎"</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tv" msgid="4706276040125072077">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‎‏‎‏‎‏‎‎‎‎‎‎‎‎‏‏‎‏‎‎‎‏‏‏‏‏‏‎‏‎‎‎‏‎‏‎‎‏‎‏‏‎‎‏‎‎‎‎‏‎‏‏‎‎‏‏‎‏‎This app is requesting additional permissions, but permissions can’t be granted in a streaming session. Grant the permission on your Android TV device first.‎‏‎‎‏‎"</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tablet" msgid="1824604581465771629">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‎‏‎‏‎‏‎‎‏‎‎‏‎‎‏‏‎‎‎‎‏‏‎‎‏‏‎‎‎‏‏‏‎‏‏‏‎‏‎‎‎‏‎‏‎‎‏‏‏‎‎‏‏‎‏‏‎‏‎This app is requesting additional permissions, but permissions can’t be granted in a streaming session. Grant the permission on your tablet first.‎‏‎‎‏‎"</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="default" msgid="7755223160363292105">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‏‏‏‎‏‎‎‎‎‎‎‎‎‏‎‏‏‎‎‏‏‎‎‎‎‎‎‎‎‏‏‎‏‏‎‏‎‏‎‏‏‎‎‎‎‏‎‏‎‏‏‏‎‎‏‎‎‏‎This app is requesting additional permissions, but permissions can’t be granted in a streaming session. Grant the permission on your phone first.‎‏‎‎‏‎"</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‎‎‎‎‏‎‏‎‏‏‎‏‏‎‎‎‏‏‎‏‏‏‏‎‏‎‎‏‏‏‎‎‏‏‏‎‎‏‏‎‏‎‏‏‎‎‏‏‏‏‎‏‏‏‎‏‏‏‎This app is requesting additional security. Try on your Android TV device instead.‎‏‎‎‏‎"</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‎‏‏‎‏‏‎‎‎‏‎‏‏‎‏‏‎‏‏‏‎‎‎‎‎‎‏‏‎‎‎‎‏‏‏‎‎‏‎‎‏‎‏‏‎‏‎‎‏‎‎‎‎‎‎‏‎‎‎This app is requesting additional security. Try on your tablet instead.‎‏‎‎‏‎"</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‏‎‏‎‏‏‎‎‎‏‎‏‎‎‎‏‎‎‎‎‎‎‏‎‎‎‏‏‎‎‏‎‏‏‎‎‎‏‎‎‎‏‎‎‎‏‎‏‎‎‏‎‎‎‎‎‏‏‎This app is requesting additional security. Try on your phone instead.‎‏‎‎‏‎"</string>
diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml
index 655e123..4caae15 100644
--- a/core/res/res/values-es-rUS/strings.xml
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -591,6 +591,8 @@
     <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"Permite que la app reciba información del servicio de pago NFC preferido, como el servicio de asistencia registrado y el destino de la ruta."</string>
     <string name="permlab_nfc" msgid="1904455246837674977">"controlar la Transmisión de datos en proximidad"</string>
     <string name="permdesc_nfc" msgid="8352737680695296741">"Permite que la aplicación se comunique con lectores, tarjetas y etiquetas de Comunicación de campo cercano (NFC)."</string>
+    <string name="permlab_nfcTransactionEvent" msgid="5868209446710407679">"Evento de transacción de Elemento seguro"</string>
+    <string name="permdesc_nfcTransactionEvent" msgid="1904286701876487397">"Permite que la app reciba información sobre transacciones que ocurren en un Elemento seguro."</string>
     <string name="permlab_disableKeyguard" msgid="3605253559020928505">"desactivar el bloqueo de pantalla"</string>
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"Permite que la aplicación desactive el bloqueo del teclado y cualquier protección con contraseña asociada. Por ejemplo, el dispositivo puede desactivar el bloqueo del teclado cuando recibe una llamada telefónica y volver a activarlo cuando finaliza la llamada."</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"solicitar complejidad del bloqueo de pantalla"</string>
@@ -621,6 +623,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"No hay hardware biométrico disponible"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Se canceló la autenticación"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"No se reconoció"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"No se reconoció el rostro"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Se canceló la autenticación"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"No se estableció ningún PIN, patrón ni contraseña"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Error de autenticación"</string>
@@ -1394,7 +1397,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"RECHAZAR"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Selecciona el método de entrada"</string>
     <string name="show_ime" msgid="6406112007347443383">"Mientras el teclado físico está activo"</string>
-    <string name="hardware" msgid="1800597768237606953">"Mostrar teclado virtual"</string>
+    <string name="hardware" msgid="3611039921284836033">"Usar teclado en pantalla"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"Configura <xliff:g id="DEVICE_NAME">%s</xliff:g>"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Configura teclados físicos"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Presiona para seleccionar el idioma y el diseño"</string>
@@ -1957,7 +1960,7 @@
     <string name="app_blocked_title" msgid="7353262160455028160">"La app no está disponible"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> no está disponible en este momento."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> no disponible"</string>
-    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="4483161748582966785">"Se necesitan permisos"</string>
+    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="3805704317624448487">"Se rechazó la solicitud de permiso"</string>
     <string name="app_streaming_blocked_title_for_camera_dialog" msgid="3935701653713853065">"La cámara no está disponible"</string>
     <string name="app_streaming_blocked_title_for_fingerprint_dialog" msgid="3516853717714141951">"Continúa en el teléfono"</string>
     <string name="app_streaming_blocked_title_for_microphone_dialog" msgid="544822455127171206">"El micrófono no está disponible"</string>
@@ -1968,6 +1971,9 @@
     <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"Por el momento, no se puede acceder a esto en tu <xliff:g id="DEVICE">%1$s</xliff:g>. Inténtalo en tu dispositivo Android TV."</string>
     <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"Por el momento, no se puede acceder a esto en tu <xliff:g id="DEVICE">%1$s</xliff:g>. Inténtalo en tu tablet."</string>
     <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"Por el momento, no se puede acceder a esto en tu <xliff:g id="DEVICE">%1$s</xliff:g>. Inténtalo en tu teléfono."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tv" msgid="4706276040125072077">"Esta app solicita permisos adicionales, pero estos no se pueden otorgar durante una sesión de transmisión. Primero, otorga el permiso en el dispositivo Android TV."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tablet" msgid="1824604581465771629">"Esta app solicita permisos adicionales, pero estos no se pueden otorgar durante una sesión de transmisión. Primero, otorga el permiso en la tablet."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="default" msgid="7755223160363292105">"Esta app solicita permisos adicionales, pero estos no se pueden otorgar durante una sesión de transmisión. Primero, otorga el permiso en el teléfono."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"Esta app solicita seguridad adicional. Inténtalo en tu dispositivo Android TV."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"Esta app solicita seguridad adicional. Inténtalo en tu tablet."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"Esta app solicita seguridad adicional. Inténtalo en tu teléfono."</string>
diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml
index d5dc1bc..b7547f8 100644
--- a/core/res/res/values-es/strings.xml
+++ b/core/res/res/values-es/strings.xml
@@ -591,6 +591,8 @@
     <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"Permite que la aplicación obtenga información sobre el servicio de pago por NFC preferido, como identificadores de aplicación registrados y destinos de rutas."</string>
     <string name="permlab_nfc" msgid="1904455246837674977">"controlar Comunicación de campo cercano (NFC)"</string>
     <string name="permdesc_nfc" msgid="8352737680695296741">"Permite que la aplicación se comunique con lectores, tarjetas y etiquetas de Comunicación de campo cercano (NFC)."</string>
+    <string name="permlab_nfcTransactionEvent" msgid="5868209446710407679">"Evento de transacción de elemento seguro"</string>
+    <string name="permdesc_nfcTransactionEvent" msgid="1904286701876487397">"Permite que la aplicación reciba información sobre las transacciones que ocurren en un elemento seguro."</string>
     <string name="permlab_disableKeyguard" msgid="3605253559020928505">"inhabilitar el bloqueo de pantalla"</string>
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"Permite que la aplicación inhabilite el bloqueo del teclado y cualquier protección con contraseña asociada. Por ejemplo, el teléfono puede inhabilitar el bloqueo del teclado cuando se recibe una llamada telefónica y volver a habilitarlo cuando finaliza la llamada."</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"solicitar complejidad del bloqueo de pantalla"</string>
@@ -621,6 +623,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Hardware biométrico no disponible"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Autenticación cancelada"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"No se reconoce"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Cara no reconocida"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Autenticación cancelada"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"No se ha definido el PIN, el patrón o la contraseña"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"No se ha podido autenticar"</string>
@@ -1394,7 +1397,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"RECHAZAR"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Selecciona un método de entrada"</string>
     <string name="show_ime" msgid="6406112007347443383">"Mantenlo en pantalla mientras el teclado físico está activo"</string>
-    <string name="hardware" msgid="1800597768237606953">"Mostrar teclado virtual"</string>
+    <string name="hardware" msgid="3611039921284836033">"Usar teclado en pantalla"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"Configura <xliff:g id="DEVICE_NAME">%s</xliff:g>"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Configura teclados físicos"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Toca para seleccionar el idioma y el diseño"</string>
@@ -1957,7 +1960,7 @@
     <string name="app_blocked_title" msgid="7353262160455028160">"La aplicación no está disponible"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"En estos momentos, <xliff:g id="APP_NAME">%1$s</xliff:g> no está disponible."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> no disponible"</string>
-    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="4483161748582966785">"Se necesita permiso"</string>
+    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="3805704317624448487">"Solicitud de permiso rechazada"</string>
     <string name="app_streaming_blocked_title_for_camera_dialog" msgid="3935701653713853065">"Cámara no disponible"</string>
     <string name="app_streaming_blocked_title_for_fingerprint_dialog" msgid="3516853717714141951">"Continúa en el teléfono"</string>
     <string name="app_streaming_blocked_title_for_microphone_dialog" msgid="544822455127171206">"Micrófono no disponible"</string>
@@ -1968,6 +1971,9 @@
     <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"En estos momentos, no se puede acceder a este contenido en tu <xliff:g id="DEVICE">%1$s</xliff:g>. Prueba en tu dispositivo Android TV."</string>
     <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"En estos momentos, no se puede acceder a este contenido en tu <xliff:g id="DEVICE">%1$s</xliff:g>. Prueba en tu tablet."</string>
     <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"En estos momentos, no se puede acceder a este contenido en tu <xliff:g id="DEVICE">%1$s</xliff:g>. Prueba en tu teléfono."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tv" msgid="4706276040125072077">"Esta aplicación está solicitando permisos adicionales, pero no se pueden dar durante una sesión de streaming. Da primero el permiso en tu dispositivo Android TV."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tablet" msgid="1824604581465771629">"Esta aplicación está solicitando permisos adicionales, pero no se pueden dar durante una sesión de streaming. Da primero el permiso en tu tablet."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="default" msgid="7755223160363292105">"Esta aplicación está solicitando permisos adicionales, pero no se pueden dar durante una sesión de streaming. Da primero el permiso en tu teléfono."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"Esta aplicación solicita seguridad adicional. Prueba en tu dispositivo Android TV."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"Esta aplicación solicita seguridad adicional. Prueba en tu tablet."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"Esta aplicación solicita seguridad adicional. Prueba en tu teléfono."</string>
diff --git a/core/res/res/values-et/strings.xml b/core/res/res/values-et/strings.xml
index e4c9948..eb8c8106 100644
--- a/core/res/res/values-et/strings.xml
+++ b/core/res/res/values-et/strings.xml
@@ -590,6 +590,8 @@
     <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"Võimaldab rakendusel hankida eelistatud NFC-makseteenuse teavet (nt registreeritud abi ja marsruudi sihtkoht)."</string>
     <string name="permlab_nfc" msgid="1904455246837674977">"lähiväljaside juhtimine"</string>
     <string name="permdesc_nfc" msgid="8352737680695296741">"Võimaldab rakendusel suhelda lähiväljaside (NFC) märgendite, kaartide ja lugeritega."</string>
+    <string name="permlab_nfcTransactionEvent" msgid="5868209446710407679">"Turvaelemendiga tehingu sündmus"</string>
+    <string name="permdesc_nfcTransactionEvent" msgid="1904286701876487397">"Võimaldab rakendusel võtta vastu teavet tehingute kohta, mis toimuvad turvaelemendi abil."</string>
     <string name="permlab_disableKeyguard" msgid="3605253559020928505">"keelake ekraanilukk"</string>
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"Võimaldab rakendusel keelata klahviluku ja muu seotud parooli turvalisuse. Näiteks keelab telefon klahviluku sissetuleva kõne vastuvõtmisel ja lubab klahviluku uuesti, kui kõne on lõpetatud."</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"ekraaniluku keerukuse taotlemine"</string>
@@ -620,6 +622,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Biomeetriline riistvara ei ole saadaval"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Autentimine tühistati"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Ei tuvastatud"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Nägu ei tuvastatud"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Autentimine tühistati"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"PIN-koodi, mustrit ega parooli pole määratud"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Viga autentimisel"</string>
@@ -1393,7 +1396,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"KEELDU"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Valige sisestusmeetod"</string>
     <string name="show_ime" msgid="6406112007347443383">"Hoia seda ekraanil, kui füüsiline klaviatuur on aktiivne"</string>
-    <string name="hardware" msgid="1800597768237606953">"Virtuaalse klaviatuuri kuvam."</string>
+    <string name="hardware" msgid="3611039921284836033">"Kasuta ekraaniklaviatuuri"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"Seadistage <xliff:g id="DEVICE_NAME">%s</xliff:g>"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Seadistage füüsilised klaviatuurid"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Puudutage keele ja paigutuse valimiseks"</string>
@@ -1956,7 +1959,7 @@
     <string name="app_blocked_title" msgid="7353262160455028160">"Rakendus ei ole saadaval"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> ei ole praegu saadaval."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> ei ole saadaval"</string>
-    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="4483161748582966785">"Vaja on luba"</string>
+    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="3805704317624448487">"Loataotlus peideti"</string>
     <string name="app_streaming_blocked_title_for_camera_dialog" msgid="3935701653713853065">"Kaamera pole saadaval"</string>
     <string name="app_streaming_blocked_title_for_fingerprint_dialog" msgid="3516853717714141951">"Jätkake telefonis"</string>
     <string name="app_streaming_blocked_title_for_microphone_dialog" msgid="544822455127171206">"Mikrofon pole saadaval"</string>
@@ -1967,6 +1970,9 @@
     <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"Sellele ei pääse praegu teie seadmega (<xliff:g id="DEVICE">%1$s</xliff:g>) juurde. Proovige juurde pääseda oma Android TV seadmega."</string>
     <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"Sellele ei pääse praegu teie seadmega (<xliff:g id="DEVICE">%1$s</xliff:g>) juurde. Proovige juurde pääseda oma tahvelarvutiga."</string>
     <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"Sellele ei pääse praegu teie seadmega (<xliff:g id="DEVICE">%1$s</xliff:g>) juurde. Proovige juurde pääseda oma telefoniga."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tv" msgid="4706276040125072077">"See rakendus taotleb lisalube, kuid lube ei saa voogesituse seansis anda. Kõigepealt andke luba oma Android TV seadmele."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tablet" msgid="1824604581465771629">"See rakendus taotleb lisalube, kuid lube ei saa voogesituse seansis anda. Kõigepealt andke luba oma tahvelarvutile."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="default" msgid="7755223160363292105">"See rakendus taotleb lisalube, kuid lube ei saa voogesituse seansis anda. Kõigepealt andke luba oma telefonile."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"See rakendus nõuab lisaturvalisust. Proovige juurde pääseda oma Android TV seadmes."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"See rakendus nõuab lisaturvalisust. Proovige juurde pääseda oma tahvelarvutis."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"See rakendus nõuab lisaturvalisust. Proovige juurde pääseda oma telefonis."</string>
diff --git a/core/res/res/values-eu/strings.xml b/core/res/res/values-eu/strings.xml
index be67a13..31ad27b2 100644
--- a/core/res/res/values-eu/strings.xml
+++ b/core/res/res/values-eu/strings.xml
@@ -590,6 +590,8 @@
     <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"NFC bidezko ordainketa-zerbitzu lehenetsiari buruzko informazioa jasotzeko baimena ematen die aplikazioei, hala nola erregistratutako laguntzaileak eta ibilbidearen helmuga."</string>
     <string name="permlab_nfc" msgid="1904455246837674977">"kontrolatu Near Field Communication komunikazioa"</string>
     <string name="permdesc_nfc" msgid="8352737680695296741">"Near Field Communication (NFC) etiketekin, txartelekin eta irakurgailuekin komunikatzeko baimena ematen die aplikazioei."</string>
+    <string name="permlab_nfcTransactionEvent" msgid="5868209446710407679">"Elementu seguruetako transakzioen gertaerak"</string>
+    <string name="permdesc_nfcTransactionEvent" msgid="1904286701876487397">"Elementu seguru batean egiten ari diren transakzioei buruzko informazioa jasotzeko baimena ematen dio aplikazioari."</string>
     <string name="permlab_disableKeyguard" msgid="3605253559020928505">"desgaitu pantailaren blokeoa"</string>
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"Teklen blokeoa eta erlazionatutako pasahitz-segurtasuna desgaitzeko baimena ematen die aplikazioei. Adibidez, telefonoak teklen blokeoa desgaitzen du telefono-deiak jasotzen dituenean, eta berriro gaitzen du deiak amaitzean."</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"eskatu pantailaren blokeoa konplexua izatea"</string>
@@ -620,6 +622,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Hardware biometrikoa ez dago erabilgarri"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Utzi da autentifikazioa"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Ez da ezagutu"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Ez da ezagutu aurpegia"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Utzi egin da autentifikazioa"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"Ez da ezarri PIN koderik, eredurik edo pasahitzik"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Errorea autentifikatzean"</string>
@@ -1393,7 +1396,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"BAZTERTU"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Aukeratu idazketa-metodoa"</string>
     <string name="show_ime" msgid="6406112007347443383">"Erakutsi pantailan teklatu fisikoa aktibo dagoen bitartean"</string>
-    <string name="hardware" msgid="1800597768237606953">"Erakutsi teklatu birtuala"</string>
+    <string name="hardware" msgid="3611039921284836033">"Erabili pantailako teklatua"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"Konfiguratu <xliff:g id="DEVICE_NAME">%s</xliff:g>"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Konfiguratu teklatu fisikoak"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Hizkuntza eta diseinua hautatzeko, sakatu hau"</string>
@@ -1956,7 +1959,7 @@
     <string name="app_blocked_title" msgid="7353262160455028160">"Aplikazioa ez dago erabilgarri"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> ez dago erabilgarri une honetan."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> ez dago erabilgarri"</string>
-    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="4483161748582966785">"Baimena behar da"</string>
+    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="3805704317624448487">"Baztertu da baimen-eskaera"</string>
     <string name="app_streaming_blocked_title_for_camera_dialog" msgid="3935701653713853065">"Kamera ez dago erabilgarri"</string>
     <string name="app_streaming_blocked_title_for_fingerprint_dialog" msgid="3516853717714141951">"Jarraitu telefonoan"</string>
     <string name="app_streaming_blocked_title_for_microphone_dialog" msgid="544822455127171206">"Mikrofonoa ez dago erabilgarri"</string>
@@ -1967,6 +1970,9 @@
     <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"Une honetan, aplikazioa ezin da <xliff:g id="DEVICE">%1$s</xliff:g> erabilita atzitu. Gailu horren ordez, erabili Android TV gailua."</string>
     <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"Une honetan, aplikazioa ezin da <xliff:g id="DEVICE">%1$s</xliff:g> erabilita atzitu. Gailu horren ordez, erabili tableta."</string>
     <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"Une honetan, aplikazioa ezin da <xliff:g id="DEVICE">%1$s</xliff:g> erabilita atzitu. Gailu horren ordez, erabili telefonoa."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tv" msgid="4706276040125072077">"Baimen gehigarriak eskatzen ari da aplikazioa, baina ezin da eman baimenik igorpen-saioetan. Lehenik eta behin, eman baimena Android TV darabilen gailuan."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tablet" msgid="1824604581465771629">"Baimen gehigarriak eskatzen ari da aplikazioa, baina ezin da eman baimenik igorpen-saioetan. Lehenik eta behin, eman baimena tabletan."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="default" msgid="7755223160363292105">"Baimen gehigarriak eskatzen ari da aplikazioa, baina ezin da eman baimenik igorpen-saioetan. Lehenik eta behin, eman baimena telefonoan."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"Segurtasun gehigarria eskatzen ari da aplikazioa. Gailu horren ordez, erabili Android TV darabilen bat."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"Segurtasun gehigarria eskatzen ari da aplikazioa. Gailu horren ordez, erabili tableta."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"Segurtasun gehigarria eskatzen ari da aplikazioa. Gailu horren ordez, erabili telefonoa."</string>
diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml
index 1620ad58..0878b76 100644
--- a/core/res/res/values-fa/strings.xml
+++ b/core/res/res/values-fa/strings.xml
@@ -590,6 +590,8 @@
     <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"‏به برنامه اجازه می‌دهد اطلاعات ترجیحی سرویس پولی NFC، مانند کمک‌های ثبت‌شده و مقصد مسیر را دریافت کند."</string>
     <string name="permlab_nfc" msgid="1904455246837674977">"کنترل ارتباط راه نزدیک"</string>
     <string name="permdesc_nfc" msgid="8352737680695296741">"‏به برنامه اجازه می‎دهد تا با تگ‌های NFC، کارت‌ها و فایل‌خوان ارتباط برقرار کند."</string>
+    <string name="permlab_nfcTransactionEvent" msgid="5868209446710407679">"‏رویداد تراکنش Secure Element"</string>
+    <string name="permdesc_nfcTransactionEvent" msgid="1904286701876487397">"‏به برنامه اجازه می‌دهد اطلاعات مربوط به تراکنش‌هایی را که در Secure Element انجام می‌شود دریافت کند."</string>
     <string name="permlab_disableKeyguard" msgid="3605253559020928505">"غیرفعال کردن قفل صفحه شما"</string>
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"به برنامه امکان می‌دهد قفل کلید و هر گونه امنیت گذرواژه مرتبط را غیرفعال کند. به‌عنوان مثال تلفن هنگام دریافت یک تماس تلفنی ورودی قفل کلید را غیرفعال می‌کند و بعد از پایان تماس، قفل کلید را دوباره فعال می‌کند."</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"درخواست پیچیدگی قفل صفحه"</string>
@@ -620,6 +622,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"سخت‌افزار زیست‌سنجی دردسترس نیست"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"اصالت‌سنجی لغو شد"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"شناسایی نشد"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"چهره شناسایی نشد"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"اصالت‌سنجی لغو شد"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"پین، الگو یا گذرواژه‌ای تنظیم نشده است"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"خطا هنگام اصالت‌سنجی"</string>
@@ -1393,7 +1396,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"نپذیرفتن"</string>
     <string name="select_input_method" msgid="3971267998568587025">"انتخاب روش ورودی"</string>
     <string name="show_ime" msgid="6406112007347443383">"وقتی صفحه‌کلید فیزیکی فعال است این ویرایشگر را روی صفحه نگه‌می‌دارد"</string>
-    <string name="hardware" msgid="1800597768237606953">"نمایش صفحه‌کلید مجازی"</string>
+    <string name="hardware" msgid="3611039921284836033">"استفاده از صفحه‌کلید مجازی"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"پیکربندی <xliff:g id="DEVICE_NAME">%s</xliff:g>"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"پیکربندی صفحه‌کلیدهای فیزیکی"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"برای انتخاب زبان و چیدمان ضربه بزنید"</string>
@@ -1956,7 +1959,7 @@
     <string name="app_blocked_title" msgid="7353262160455028160">"برنامه در دسترس نیست"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> درحال‌حاضر در دسترس نیست."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> دردسترس نیست"</string>
-    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="4483161748582966785">"اجازه لازم است"</string>
+    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="3805704317624448487">"درخواست اجازه رد شد"</string>
     <string name="app_streaming_blocked_title_for_camera_dialog" msgid="3935701653713853065">"دوربین دردسترس نیست"</string>
     <string name="app_streaming_blocked_title_for_fingerprint_dialog" msgid="3516853717714141951">"ادامه دادن در تلفن"</string>
     <string name="app_streaming_blocked_title_for_microphone_dialog" msgid="544822455127171206">"میکروفون دردسترس نیست"</string>
@@ -1967,6 +1970,9 @@
     <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"‏درحال‌حاضر نمی‌توان در <xliff:g id="DEVICE">%1$s</xliff:g> شما به این برنامه دسترسی داشت. دسترسی به آن را در دستگاه Android TV امتحان کنید."</string>
     <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"درحال‌حاضر نمی‌توان در <xliff:g id="DEVICE">%1$s</xliff:g> شما به این برنامه دسترسی داشت. دسترسی به آن را در رایانه لوحی‌تان امتحان کنید."</string>
     <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"درحال‌حاضر نمی‌توان در <xliff:g id="DEVICE">%1$s</xliff:g> شما به این برنامه دسترسی داشت. دسترسی به آن را در تلفنتان امتحان کنید."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tv" msgid="4706276040125072077">"‏این برنامه درخواست اجازه‌های اضافی دارد، اما امکان دادن این اجازه‌ها در جلسه جاری‌سازی وجود ندارد. ابتدا اجازه را در دستگاه Android TV مجاز کنید."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tablet" msgid="1824604581465771629">"این برنامه درخواست اجازه‌های اضافی دارد، اما امکان دادن این اجازه‌ها در جلسه جاری‌سازی وجود ندارد. ابتدا اجازه را در رایانه لوحی‌تان مجاز کنید."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="default" msgid="7755223160363292105">"این برنامه درخواست اجازه‌های اضافی دارد، اما امکان دادن این اجازه‌ها در جلسه جاری‌سازی وجود ندارد. ابتدا اجازه را در تلفنتان مجاز کنید."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"‏این برنامه درخواست امنیت اضافی دارد. دسترسی به آن را در دستگاه Android TV امتحان کنید."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"این برنامه درخواست امنیت اضافی دارد. دسترسی به آن را در رایانه لوحی‌تان امتحان کنید."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"این برنامه درخواست امنیت اضافی دارد. دسترسی به آن را در تلفنتان امتحان کنید."</string>
diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml
index 5f120c5..5f1b048 100644
--- a/core/res/res/values-fi/strings.xml
+++ b/core/res/res/values-fi/strings.xml
@@ -590,6 +590,8 @@
     <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"Sallii sovelluksen noutaa tietoja rekisteröidyistä sovellustunnuksista, maksureitin kohteesta ja muita ensisijaisia NFC-maksupalvelutietoja."</string>
     <string name="permlab_nfc" msgid="1904455246837674977">"hallitse Near Field Communication -tunnistusta"</string>
     <string name="permdesc_nfc" msgid="8352737680695296741">"Antaa sovelluksen kommunikoida NFC (Near Field Communication) -tagien, -korttien ja -lukijoiden kanssa."</string>
+    <string name="permlab_nfcTransactionEvent" msgid="5868209446710407679">"Secure Element ‑maksutapahtuma"</string>
+    <string name="permdesc_nfcTransactionEvent" msgid="1904286701876487397">"Sallii sovelluksen vastaanottaa tietoa Secure Elementissä tapahtuvista maksutapahtumista."</string>
     <string name="permlab_disableKeyguard" msgid="3605253559020928505">"poista näytön lukitus käytöstä"</string>
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"Antaa sovelluksen ottaa näppäinlukon ja siihen liittyvän salasanasuojauksen pois käytöstä. Esimerkki: puhelin poistaa näppäinlukon käytöstä puhelun saapuessa ja asettaa lukon takaisin käyttöön puhelun päättyessä."</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"pyytää näytön lukituksen monimutkaisuutta"</string>
@@ -620,6 +622,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Biometrinen laitteisto ei käytettävissä"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Todennus peruutettu"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Ei tunnistettu"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Kasvoja ei tunnistettu"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Todennus peruutettu"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"PIN-koodia, kuviota tai salasanaa ei ole asetettu"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Virhe todennuksessa"</string>
@@ -1393,7 +1396,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"HYLKÄÄ"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Valitse syöttötapa"</string>
     <string name="show_ime" msgid="6406112007347443383">"Pidä näytöllä, kun fyysinen näppäimistö on aktiivinen"</string>
-    <string name="hardware" msgid="1800597768237606953">"Näytä virtuaalinen näppäimistö"</string>
+    <string name="hardware" msgid="3611039921284836033">"Käytä näyttönäppäimistöä"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"Määritä <xliff:g id="DEVICE_NAME">%s</xliff:g>"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Määritä fyysiset näppäimistöt"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Valitse kieli ja asettelu koskettamalla."</string>
@@ -1956,7 +1959,7 @@
     <string name="app_blocked_title" msgid="7353262160455028160">"Sovellus ei ole käytettävissä"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> ei ole nyt käytettävissä."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> ei käytettävissä"</string>
-    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="4483161748582966785">"Edellyttää käyttöoikeutta"</string>
+    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="3805704317624448487">"Lupapyyntö estetty"</string>
     <string name="app_streaming_blocked_title_for_camera_dialog" msgid="3935701653713853065">"Kamera ei käytettävissä"</string>
     <string name="app_streaming_blocked_title_for_fingerprint_dialog" msgid="3516853717714141951">"Jatka puhelimella"</string>
     <string name="app_streaming_blocked_title_for_microphone_dialog" msgid="544822455127171206">"Mikrofoni ei ole käytettävissä"</string>
@@ -1967,6 +1970,9 @@
     <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"<xliff:g id="DEVICE">%1$s</xliff:g> ei tällä hetkellä saa pääsyä sovellukseen. Kokeile striimausta Android TV ‑laitteella."</string>
     <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"<xliff:g id="DEVICE">%1$s</xliff:g> ei tällä hetkellä saa pääsyä sovellukseen. Kokeile striimausta tabletilla."</string>
     <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"<xliff:g id="DEVICE">%1$s</xliff:g> ei tällä hetkellä saa pääsyä sovellukseen. Kokeile striimausta puhelimella."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tv" msgid="4706276040125072077">"Sovellus pyytää lisälupia, mutta lupia ei voi myöntää striimatessa. Myönnä lupa ensin Android TV ‑laitteella."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tablet" msgid="1824604581465771629">"Sovellus pyytää lisälupia, mutta lupia ei voi myöntää striimatessa. Myönnä lupa ensin tabletilla."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="default" msgid="7755223160363292105">"Sovellus pyytää lisälupia, mutta lupia ei voi myöntää striimatessa. Myönnä lupa ensin puhelimella."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"Sovellus pyytää lisäsuojausta. Kokeile striimausta Android TV ‑laitteella."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"Sovellus pyytää lisäsuojausta. Kokeile striimausta tabletilla."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"Sovellus pyytää lisäsuojausta. Kokeile striimausta puhelimella."</string>
diff --git a/core/res/res/values-fr-rCA/strings.xml b/core/res/res/values-fr-rCA/strings.xml
index e72b41e..04b4e36 100644
--- a/core/res/res/values-fr-rCA/strings.xml
+++ b/core/res/res/values-fr-rCA/strings.xml
@@ -591,6 +591,8 @@
     <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"Permet à l\'application d\'obtenir de l\'information sur le service préféré de paiement CCP comme les aides enregistrées et la route de destination."</string>
     <string name="permlab_nfc" msgid="1904455246837674977">"gérer la communication en champ proche"</string>
     <string name="permdesc_nfc" msgid="8352737680695296741">"Permet à l\'application de communiquer avec des bornes, des cartes et des lecteurs compatibles avec la technologie CCP (communication en champ proche)."</string>
+    <string name="permlab_nfcTransactionEvent" msgid="5868209446710407679">"Événement de transaction relatif à un élément sécurisé"</string>
+    <string name="permdesc_nfcTransactionEvent" msgid="1904286701876487397">"Autorise l\'application à recevoir des informations relatives aux transactions effectuées sur un élément sécurisé."</string>
     <string name="permlab_disableKeyguard" msgid="3605253559020928505">"désactiver le verrouillage de l\'écran"</string>
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"Permet à l\'application de désactiver le verrouillage des touches et toute mesure de sécurité par mot de passe associée. Par exemple, votre téléphone désactive le verrouillage des touches lorsque vous recevez un appel, puis le réactive lorsque vous raccrochez."</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"demander la complexité du verrouillage d\'écran"</string>
@@ -621,6 +623,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Matériel biométrique indisponible"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Authentification annulée"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Données biométriques non reconnues"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Visage non reconnu"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Authentification annulée"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"Aucun NIP, schéma ou mot de passe défini"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Erreur d\'authentification"</string>
@@ -1394,7 +1397,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"REFUSER"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Sélectionnez le mode de saisie"</string>
     <string name="show_ime" msgid="6406112007347443383">"Afficher lorsque le clavier physique est activé"</string>
-    <string name="hardware" msgid="1800597768237606953">"Afficher le clavier virtuel"</string>
+    <string name="hardware" msgid="3611039921284836033">"Utiliser le clavier à l\'écran"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"Configurer <xliff:g id="DEVICE_NAME">%s</xliff:g>"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Configurer les claviers physiques"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Touchez pour sélectionner la langue et la configuration du clavier"</string>
@@ -1957,7 +1960,7 @@
     <string name="app_blocked_title" msgid="7353262160455028160">"L\'application n\'est pas accessible"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> n\'est pas accessible pour le moment."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> non accessible"</string>
-    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="4483161748582966785">"Autorisation nécessaire"</string>
+    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="3805704317624448487">"La demande d\'autorisation a été supprimée."</string>
     <string name="app_streaming_blocked_title_for_camera_dialog" msgid="3935701653713853065">"Appareil photo non accessible"</string>
     <string name="app_streaming_blocked_title_for_fingerprint_dialog" msgid="3516853717714141951">"Continuer sur le téléphone"</string>
     <string name="app_streaming_blocked_title_for_microphone_dialog" msgid="544822455127171206">"Microphone non accessible"</string>
@@ -1968,6 +1971,9 @@
     <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"Impossible d\'accéder à ce contenu sur votre <xliff:g id="DEVICE">%1$s</xliff:g> pour le moment. Essayez sur votre appareil Android TV à la place."</string>
     <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"Impossible d\'accéder à ce contenu sur votre <xliff:g id="DEVICE">%1$s</xliff:g> pour le moment. Essayez sur votre tablette à la place."</string>
     <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"Impossible d\'accéder à ce contenu sur votre <xliff:g id="DEVICE">%1$s</xliff:g> pour le moment. Essayez sur votre téléphone à la place."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tv" msgid="4706276040125072077">"Cette application demande des autorisations supplémentaires, mais les autorisations ne peuvent pas être accordées lors d\'une session de diffusion en continu. Accordez l\'autorisation sur votre appareil Android TV d\'abord."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tablet" msgid="1824604581465771629">"Cette application demande des autorisations supplémentaires, mais les autorisations ne peuvent pas être accordées lors d\'une session de diffusion en continu. Accordez l\'autorisation sur votre tablette d\'abord."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="default" msgid="7755223160363292105">"Cette application demande des autorisations supplémentaires, mais les autorisations ne peuvent pas être accordées lors d\'une session de diffusion en continu. Accordez l\'autorisation sur votre téléphone d\'abord."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"Cette application demande une sécurité supplémentaire. Essayez sur votre appareil Android TV à la place."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"Cette application demande une sécurité supplémentaire. Essayez sur votre tablette à la place."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"Cette application demande une sécurité supplémentaire. Essayez sur votre téléphone à la place."</string>
diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml
index c162672..36da634 100644
--- a/core/res/res/values-fr/strings.xml
+++ b/core/res/res/values-fr/strings.xml
@@ -591,6 +591,8 @@
     <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"Permet à l\'application d\'obtenir des informations sur le service de paiement NFC préféré, y compris les ID d\'applications et les destinations de routage enregistrés."</string>
     <string name="permlab_nfc" msgid="1904455246837674977">"contrôler la communication en champ proche"</string>
     <string name="permdesc_nfc" msgid="8352737680695296741">"Permet à l\'application de communiquer avec des tags, des cartes et des lecteurs compatibles avec la technologie NFC (communication en champ proche)."</string>
+    <string name="permlab_nfcTransactionEvent" msgid="5868209446710407679">"Événement de transaction sur un composant sécurisé"</string>
+    <string name="permdesc_nfcTransactionEvent" msgid="1904286701876487397">"Permet à l\'appli de recevoir des informations sur les transactions qui ont lieu sur un composant sécurisé."</string>
     <string name="permlab_disableKeyguard" msgid="3605253559020928505">"Désactiver le verrouillage de l\'écran"</string>
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"Permet à l\'application de désactiver le verrouillage des touches et toute mesure de sécurité via mot de passe associée. Par exemple, votre téléphone désactive le verrouillage des touches lorsque vous recevez un appel, puis le réactive lorsque vous raccrochez."</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"demander la complexité du verrouillage de l\'écran"</string>
@@ -621,6 +623,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Matériel biométrique indisponible"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Authentification annulée"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Non reconnue"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Visage non reconnu"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Authentification annulée"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"Aucun code, schéma ni mot de passe n\'est défini"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Erreur d\'authentification"</string>
@@ -1394,7 +1397,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"REFUSER"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Sélectionnez le mode de saisie"</string>
     <string name="show_ime" msgid="6406112007347443383">"Afficher le clavier virtuel même lorsque le clavier physique est actif"</string>
-    <string name="hardware" msgid="1800597768237606953">"Afficher le clavier virtuel"</string>
+    <string name="hardware" msgid="3611039921284836033">"Utiliser le clavier à l\'écran"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"Configurer <xliff:g id="DEVICE_NAME">%s</xliff:g>"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Configurez les claviers physiques"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Appuyez pour sélectionner la langue et la disposition"</string>
@@ -1957,7 +1960,7 @@
     <string name="app_blocked_title" msgid="7353262160455028160">"Application non disponible"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> n\'est pas disponible pour le moment."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> indisponible"</string>
-    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="4483161748582966785">"Autorisation nécessaire"</string>
+    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="3805704317624448487">"Demande d\'autorisation supprimée"</string>
     <string name="app_streaming_blocked_title_for_camera_dialog" msgid="3935701653713853065">"Caméra indisponible"</string>
     <string name="app_streaming_blocked_title_for_fingerprint_dialog" msgid="3516853717714141951">"Continuez sur le téléphone"</string>
     <string name="app_streaming_blocked_title_for_microphone_dialog" msgid="544822455127171206">"Micro indisponible"</string>
@@ -1968,6 +1971,9 @@
     <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"Actuellement, vous ne pouvez pas accéder à cette application sur votre <xliff:g id="DEVICE">%1$s</xliff:g>. Essayez plutôt d\'y accéder sur votre appareil Android TV."</string>
     <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"Actuellement, vous ne pouvez pas accéder à cette application sur votre <xliff:g id="DEVICE">%1$s</xliff:g>. Essayez plutôt d\'y accéder sur votre tablette."</string>
     <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"Actuellement, vous ne pouvez pas accéder à cette application sur votre <xliff:g id="DEVICE">%1$s</xliff:g>. Essayez plutôt d\'y accéder sur votre téléphone."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tv" msgid="4706276040125072077">"Cette appli demande des autorisations supplémentaires, mais il est impossible d\'accorder des autorisations lors d\'une session de streaming. Accordez tout d\'abord l\'autorisation sur votre appareil Android TV."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tablet" msgid="1824604581465771629">"Cette appli demande des autorisations supplémentaires, mais il est impossible d\'accorder des autorisations lors d\'une session de streaming. Accordez tout d\'abord l\'autorisation sur votre tablette."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="default" msgid="7755223160363292105">"Cette appli demande des autorisations supplémentaires, mais il est impossible d\'accorder des autorisations lors d\'une session de streaming. Accordez tout d\'abord l\'autorisation sur votre téléphone."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"Cette appli demande une sécurité supplémentaire. Essayez plutôt d\'y accéder sur votre appareil Android TV."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"Cette appli demande une sécurité supplémentaire. Essayez plutôt d\'y accéder sur votre tablette."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"Cette appli demande une sécurité supplémentaire. Essayez plutôt d\'y accéder sur votre téléphone."</string>
diff --git a/core/res/res/values-gl/strings.xml b/core/res/res/values-gl/strings.xml
index 53967e6..20e3812 100644
--- a/core/res/res/values-gl/strings.xml
+++ b/core/res/res/values-gl/strings.xml
@@ -590,6 +590,8 @@
     <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"Permite que a aplicación obteña información do servizo de pago de NFC preferido, como as axudas rexistradas e o destino da ruta."</string>
     <string name="permlab_nfc" msgid="1904455246837674977">"controlar Near Field Communication"</string>
     <string name="permdesc_nfc" msgid="8352737680695296741">"Permite á aplicación comunicarse con etiquetas, tarxetas e lectores Near Field Communication (NFC)."</string>
+    <string name="permlab_nfcTransactionEvent" msgid="5868209446710407679">"Evento de transacción no elemento seguro"</string>
+    <string name="permdesc_nfcTransactionEvent" msgid="1904286701876487397">"Permite que a aplicación reciba información sobre transaccións que ocorran nun elemento seguro."</string>
     <string name="permlab_disableKeyguard" msgid="3605253559020928505">"desactivar o bloqueo da pantalla"</string>
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"Permite á aplicación desactivar o bloqueo do teclado e calquera seguranza dos contrasinais asociada. Por exemplo, o teléfono desactiva o bloqueo do teclado ao recibir unha chamada telefónica entrante e, a continuación, volve activar o bloqueo do teclado unha vez finalizada a chamada."</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"solicitar o nivel de complexidade do bloqueo de pantalla"</string>
@@ -620,6 +622,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"O hardware biométrico non está dispoñible"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Cancelouse a autenticación"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Non se recoñeceu"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Non se recoñeceu a cara"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Cancelouse a autenticación"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"Non se estableceu ningún PIN, padrón ou contrasinal"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Produciuse un erro ao realizar a autenticación"</string>
@@ -1393,7 +1396,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"ANULAR"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Escoller método de introdución de texto"</string>
     <string name="show_ime" msgid="6406112007347443383">"Móstrase na pantalla mentres o teclado físico estea activo"</string>
-    <string name="hardware" msgid="1800597768237606953">"Mostrar o teclado virtual"</string>
+    <string name="hardware" msgid="3611039921284836033">"Utilizar o teclado en pantalla"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"Configura o teclado (<xliff:g id="DEVICE_NAME">%s</xliff:g>)"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Configura os teclados físicos"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Toca para seleccionar o idioma e o deseño"</string>
@@ -1956,7 +1959,7 @@
     <string name="app_blocked_title" msgid="7353262160455028160">"A aplicación non está dispoñible"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"A aplicación <xliff:g id="APP_NAME">%1$s</xliff:g> non está dispoñible neste momento."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> non está dispoñible"</string>
-    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="4483161748582966785">"Necesítase permiso"</string>
+    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="3805704317624448487">"Eliminouse a solicitude de permiso"</string>
     <string name="app_streaming_blocked_title_for_camera_dialog" msgid="3935701653713853065">"A cámara non está dispoñible"</string>
     <string name="app_streaming_blocked_title_for_fingerprint_dialog" msgid="3516853717714141951">"Continúa no teléfono"</string>
     <string name="app_streaming_blocked_title_for_microphone_dialog" msgid="544822455127171206">"O micrófono non está dispoñible"</string>
@@ -1967,6 +1970,9 @@
     <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"Nestes momentos, non podes acceder a este contido desde o teu dispositivo (<xliff:g id="DEVICE">%1$s</xliff:g>). Proba a facelo desde o dispositivo Android TV."</string>
     <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"Nestes momentos, non podes acceder a este contido desde o teu dispositivo (<xliff:g id="DEVICE">%1$s</xliff:g>). Proba a facelo desde a tableta."</string>
     <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"Nestes momentos, non podes acceder a este contido desde o teu dispositivo (<xliff:g id="DEVICE">%1$s</xliff:g>). Proba a facelo desde o teléfono."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tv" msgid="4706276040125072077">"Esta aplicación solicita permisos adicionais, pero non se poden conceder nunha sesión de reprodución en tempo real. Primeiro tes que conceder o permiso no teu dispositivo Android TV."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tablet" msgid="1824604581465771629">"Esta aplicación solicita permisos adicionais, pero non se poden conceder nunha sesión de reprodución en tempo real. Primeiro tes que conceder o permiso na túa tableta."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="default" msgid="7755223160363292105">"Esta aplicación solicita permisos adicionais, pero non se poden conceder nunha sesión de reprodución en tempo real. Primeiro tes que conceder o permiso no teu teléfono."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"Esta aplicación solicita seguranza adicional. Proba a facelo desde o dispositivo Android TV."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"Esta aplicación solicita seguranza adicional. Proba a facelo desde a tableta."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"Esta aplicación solicita seguranza adicional. Proba a facelo desde o teléfono."</string>
diff --git a/core/res/res/values-gu/strings.xml b/core/res/res/values-gu/strings.xml
index ab3859e..a48e343 100644
--- a/core/res/res/values-gu/strings.xml
+++ b/core/res/res/values-gu/strings.xml
@@ -590,6 +590,8 @@
     <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"આ મંજૂરીને આપવાથી, ઍપ તમારી પસંદગીની NFC ચુકવણીની સેવા વિશે માહિતી મેળવી શકે છે, જેમ કે રજિસ્ટર થયેલી સહાય અને નિર્ધારિત સ્થાન."</string>
     <string name="permlab_nfc" msgid="1904455246837674977">"નિઅર ફીલ્ડ કમ્યુનિકેશન નિયંત્રિત કરો"</string>
     <string name="permdesc_nfc" msgid="8352737680695296741">"ઍપ્લિકેશનને નિઅર ફીલ્ડ કમ્યુનિકેશન (NFC) ટૅગ, કાર્ડ અને રીડર સાથે સંચાર કરવાની મંજૂરી આપે છે."</string>
+    <string name="permlab_nfcTransactionEvent" msgid="5868209446710407679">"સુરક્ષિત તત્વ પરના વ્યવહારની ઇવેન્ટ"</string>
+    <string name="permdesc_nfcTransactionEvent" msgid="1904286701876487397">"ઍપને સુરક્ષિત તત્વ પર થતા વ્યવહારો વિશેની માહિતી મેળવવાની મંજૂરી આપે છે."</string>
     <string name="permlab_disableKeyguard" msgid="3605253559020928505">"તમારું સ્ક્રીન લૉક અક્ષમ કરો"</string>
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"એપ્લિકેશનને કીલૉક અને કોઈપણ સંકળાયેલ પાસવર્ડ સુરક્ષા અક્ષમ કરવાની મંજૂરી આપે છે. ઉદાહરણ તરીકે, ઇનકમિંગ ફોન કૉલ પ્રાપ્ત કરતી વખતે ફોન, કીલૉકને અક્ષમ કરે છે, પછી કૉલ સમાપ્ત થઈ જવા પર કીલૉક ફરીથી સક્ષમ કરે છે."</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"સ્ક્રીન લૉકની જટિલતા જાણવા માટે વિનંતી કરો"</string>
@@ -620,6 +622,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"બાયોમેટ્રિક હાર્ડવેર ઉપલબ્ધ નથી"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"પ્રમાણીકરણ રદ કર્યું"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"ઓળખાયેલ નથી"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"ચહેરો ઓળખાયો નથી"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"પ્રમાણીકરણ રદ કર્યું"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"કોઈ પિન, પૅટર્ન અથવા પાસવર્ડ સેટ કરેલો નથી"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"પ્રમાણિત કરવામાં ભૂલ આવી"</string>
@@ -1393,7 +1396,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"નકારો"</string>
     <string name="select_input_method" msgid="3971267998568587025">"ઇનપુટ પદ્ધતિ પસંદ કરો"</string>
     <string name="show_ime" msgid="6406112007347443383">"જ્યારે ભૌતિક કીબોર્ડ સક્રિય હોય ત્યારે તેને સ્ક્રીન પર રાખો"</string>
-    <string name="hardware" msgid="1800597768237606953">"વર્ચ્યુઅલ કીબોર્ડ બતાવો"</string>
+    <string name="hardware" msgid="3611039921284836033">"ઑન-સ્ક્રીન કીબોર્ડનો ઉપયોગ કરો"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"<xliff:g id="DEVICE_NAME">%s</xliff:g>ની ગોઠવણી કરો"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"ભૌતિક કીબોર્ડની ગોઠવણી કરો"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"ભાષા અને લેઆઉટ પસંદ કરવા માટે ટૅપ કરો"</string>
@@ -1956,7 +1959,7 @@
     <string name="app_blocked_title" msgid="7353262160455028160">"ઍપ ઉપલબ્ધ નથી"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> હાલમાં ઉપલબ્ધ નથી."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> ઉપલબ્ધ નથી"</string>
-    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="4483161748582966785">"પરવાનગી જરૂરી છે"</string>
+    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="3805704317624448487">"પરવાનગીની વિનંતી નકારવામાં આવી"</string>
     <string name="app_streaming_blocked_title_for_camera_dialog" msgid="3935701653713853065">"કૅમેરા ઉપલબ્ધ નથી"</string>
     <string name="app_streaming_blocked_title_for_fingerprint_dialog" msgid="3516853717714141951">"ફોન પર ચાલુ રાખો"</string>
     <string name="app_streaming_blocked_title_for_microphone_dialog" msgid="544822455127171206">"માઇક્રોફોન ઉપલબ્ધ નથી"</string>
@@ -1967,6 +1970,9 @@
     <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"અત્યારે આને તમારા <xliff:g id="DEVICE">%1$s</xliff:g> પર ઍક્સેસ કરી શકાતી નથી. તેના બદલે તમારા Android TV ડિવાઇસ પર પ્રયાસ કરો."</string>
     <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"અત્યારે આને તમારા <xliff:g id="DEVICE">%1$s</xliff:g> પર ઍક્સેસ કરી શકાતી નથી. તેના બદલે તમારા ટૅબ્લેટ પર પ્રયાસ કરો."</string>
     <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"અત્યારે આને તમારા <xliff:g id="DEVICE">%1$s</xliff:g> પર ઍક્સેસ કરી શકાતી નથી. તેના બદલે તમારા ફોન પર પ્રયાસ કરો."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tv" msgid="4706276040125072077">"આ ઍપ વધારાની પરવાનગીઓની વિનંતી કરી રહી છે પણ સ્ટ્રીમિંગ સત્રમાં પરવાનગીઓને મંજૂરી આપી શકાતી નથી. પહેલા તમારા Android TV ડિવાઇસ પર પરવાનગીની મંજૂરી આપો."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tablet" msgid="1824604581465771629">"આ ઍપ વધારાની પરવાનગીઓની વિનંતી કરી રહી છે પણ સ્ટ્રીમિંગ સત્રમાં પરવાનગીઓને મંજૂરી આપી શકાતી નથી. પહેલા તમારા ટૅબ્લેટ પર પરવાનગીની મંજૂરી આપો."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="default" msgid="7755223160363292105">"આ ઍપ વધારાની પરવાનગીઓની વિનંતી કરી રહી છે પણ સ્ટ્રીમિંગ સત્રમાં પરવાનગીઓને મંજૂરી આપી શકાતી નથી. પહેલા તમારા ફોન પર પરવાનગીની મંજૂરી આપો."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"આ ઍપ દ્વારા વધારાની સુરક્ષાની વિનંતી કરવામાં આવી રહી છે. તેના બદલે તમારા Android TV ડિવાઇસ પર પ્રયાસ કરો."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"આ ઍપ દ્વારા વધારાની સુરક્ષાની વિનંતી કરવામાં આવી રહી છે. તેના બદલે તમારા ટૅબ્લેટ પર પ્રયાસ કરો."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"આ ઍપ દ્વારા વધારાની સુરક્ષાની વિનંતી કરવામાં આવી રહી છે. તેના બદલે તમારા ફોન પર પ્રયાસ કરો."</string>
diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml
index 4c66168..5f2ba3a 100644
--- a/core/res/res/values-hi/strings.xml
+++ b/core/res/res/values-hi/strings.xml
@@ -590,6 +590,8 @@
     <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"अगर ऐप्लिकेशन को अनुमति दी जाती है, तो वह पैसे चुकाने की आपकी उस पसंदीदा सेवा के बारे में जानकारी पा सकता है जो NFC का इस्तेमाल करती है. इसमें रजिस्टर किए गए डिवाइस और उनके आउटपुट के रूट जैसी जानकारी शामिल होती है."</string>
     <string name="permlab_nfc" msgid="1904455246837674977">"नियर फ़ील्‍ड कम्‍यूनिकेशन नियंत्रित करें"</string>
     <string name="permdesc_nfc" msgid="8352737680695296741">"ऐप्स  को नियर फ़ील्ड कम्यूनिकेशन (NFC) टैग, कार्ड, और रीडर के साथ संचार करने देता है."</string>
+    <string name="permlab_nfcTransactionEvent" msgid="5868209446710407679">"सुरक्षा चिप में होने वाला लेन-देन की जानकारी"</string>
+    <string name="permdesc_nfcTransactionEvent" msgid="1904286701876487397">"इससे ऐप्लिकेशन को सुरक्षा चिप पर होने वाले लेन-देन की जानकारी मिलेगी."</string>
     <string name="permlab_disableKeyguard" msgid="3605253559020928505">"अपना स्‍क्रीन लॉक अक्षम करें"</string>
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"ऐप्स को कीलॉक और कोई भी संबद्ध पासवर्ड सुरक्षा बंद करने देता है. उदाहरण के लिए, इनकमिंग फ़ोन कॉल पाते समय फ़ोन, कीलॉक को बंद कर देता है, फिर कॉल खत्म होने पर कीलॉक को फिर से चालू कर देता है."</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"जानें कि स्क्रीन लॉक कितना मुश्किल बनाया गया है"</string>
@@ -620,6 +622,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"बायोमेट्रिक हार्डवेयर उपलब्ध नहीं है"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"प्रमाणीकरण रद्द किया गया"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"पहचान नहीं हो पाई"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"चेहरा नहीं पहचाना गया"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"प्रमाणीकरण रद्द किया गया"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"पिन, पैटर्न या पासवर्ड सेट नहीं है"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"गड़बड़ी की पुष्टि की जा रही है"</string>
@@ -1393,7 +1396,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"अस्वीकार करें"</string>
     <string name="select_input_method" msgid="3971267998568587025">"इनपुट का तरीका चुनें"</string>
     <string name="show_ime" msgid="6406112007347443383">"सामान्य कीबोर्ड के सक्रिय होने के दौरान इसे स्‍क्रीन पर बनाए रखें"</string>
-    <string name="hardware" msgid="1800597768237606953">"वर्चुअल कीबोर्ड दिखाएं"</string>
+    <string name="hardware" msgid="3611039921284836033">"ऑन-स्क्रीन कीबोर्ड इस्तेमाल करें"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"<xliff:g id="DEVICE_NAME">%s</xliff:g> को कॉन्फ़िगर करें"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"फ़िज़िकल कीबोर्ड को कॉन्फ़िगर करें"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"भाषा और लेआउट चुनने के लिए टैप करें"</string>
@@ -1956,7 +1959,7 @@
     <string name="app_blocked_title" msgid="7353262160455028160">"ऐप्लिकेशन उपलब्ध नहीं है"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> इस समय उपलब्ध नहीं है."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> उपलब्ध नहीं है"</string>
-    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="4483161748582966785">"अनुमति ज़रूरी है"</string>
+    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="3805704317624448487">"अनुमति के अनुरोध को अस्वीकार किया गया"</string>
     <string name="app_streaming_blocked_title_for_camera_dialog" msgid="3935701653713853065">"कैमरा उपलब्ध नहीं है"</string>
     <string name="app_streaming_blocked_title_for_fingerprint_dialog" msgid="3516853717714141951">"फ़ोन पर जारी रखें"</string>
     <string name="app_streaming_blocked_title_for_microphone_dialog" msgid="544822455127171206">"माइक्रोफ़ोन उपलब्ध नहीं है"</string>
@@ -1967,6 +1970,9 @@
     <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"इस समय, आपके <xliff:g id="DEVICE">%1$s</xliff:g> पर इसे ऐक्सेस नहीं किया जा सकता. इसके बजाय, अपने Android TV डिवाइस पर कोशिश करें."</string>
     <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"इस समय, आपके <xliff:g id="DEVICE">%1$s</xliff:g> पर इसे ऐक्सेस नहीं किया जा सकता. इसके बजाय, अपने टैबलेट पर कोशिश करें."</string>
     <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"इस समय, आपके <xliff:g id="DEVICE">%1$s</xliff:g> पर इसे ऐक्सेस नहीं किया जा सकता. इसके बजाय, अपने फ़ोन पर कोशिश करें."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tv" msgid="4706276040125072077">"यह ऐप्लिकेशन ज़्यादा अनुमतियों का अनुरोध कर रहा है. हालांकि, स्ट्रीमिंग सेशन के दौरान अनुमतियां नहीं दी जा सकतीं. सबसे पहले अपने Android TV डिवाइस पर अनुमति दें."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tablet" msgid="1824604581465771629">"यह ऐप्लिकेशन ज़्यादा अनुमतियों का अनुरोध कर रहा है. हालांकि, स्ट्रीमिंग सेशन के दौरान अनुमतियां नहीं दी जा सकतीं. सबसे पहले अपने टैबलेट पर अनुमति दें."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="default" msgid="7755223160363292105">"यह ऐप्लिकेशन ज़्यादा अनुमतियों का अनुरोध कर रहा है. हालांकि, स्ट्रीमिंग सेशन के दौरान अनुमतियां नहीं दी जा सकतीं. सबसे पहले अपने फ़ोन पर अनुमति दें."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"यह ऐप्लिकेशन ज़्यादा सुरक्षा का अनुरोध कर रहा है. इसके बजाय, अपने Android TV डिवाइस पर ऐक्सेस करने की कोशिश करें."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"यह ऐप्लिकेशन ज़्यादा सुरक्षा का अनुरोध कर रहा है. इसके बजाय, अपने टैबलेट पर ऐक्सेस करने की कोशिश करें."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"यह ऐप्लिकेशन ज़्यादा सुरक्षा का अनुरोध कर रहा है. इसके बजाय, अपने फ़ोन पर ऐक्सेस करने की कोशिश करें."</string>
diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml
index eb8d111..711ae1e 100644
--- a/core/res/res/values-hr/strings.xml
+++ b/core/res/res/values-hr/strings.xml
@@ -591,6 +591,8 @@
     <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"Omogućuje aplikaciji primanje informacija o preferiranoj usluzi plaćanja NFC kao što su registrirana pomagala i odredište."</string>
     <string name="permlab_nfc" msgid="1904455246837674977">"upravljanje beskontaktnom komunikacijom (NFC)"</string>
     <string name="permdesc_nfc" msgid="8352737680695296741">"Aplikaciji omogućuje komunikaciju s oznakama, karticama i čitačima komunikacije kratkog dometa (NFC)."</string>
+    <string name="permlab_nfcTransactionEvent" msgid="5868209446710407679">"Događaj transakcije na sigurnosnom elementu"</string>
+    <string name="permdesc_nfcTransactionEvent" msgid="1904286701876487397">"Aplikaciji omogućuje da prima podatke o transakcijama koje se odvijaju na sigurnosnom elementu."</string>
     <string name="permlab_disableKeyguard" msgid="3605253559020928505">"onemogućavanje zaključavanja zaslona"</string>
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"Aplikaciji omogućuje onemogućavanje zaključavanja tipkovnice i svih pripadajućih sigurnosnih zaporki. Na primjer, telefon onemogućuje zaključavanje tipkovnice kod primanja dolaznog telefonskog poziva, nakon kojeg se zaključavanje tipkovnice ponovo omogućuje."</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"zahtijevati složenost zaključavanja zaslona"</string>
@@ -621,6 +623,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Biometrijski hardver nije dostupan"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Autentifikacija otkazana"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Nije prepoznato"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Lice nije prepoznato"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Autentifikacija otkazana"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"Nisu postavljeni PIN, uzorak ni zaporka"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Pogreška prilikom autentifikacije"</string>
@@ -1394,7 +1397,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"ODBIJ"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Odabir načina unosa"</string>
     <string name="show_ime" msgid="6406112007347443383">"Zadrži na zaslonu dok je fizička tipkovnica aktivna"</string>
-    <string name="hardware" msgid="1800597768237606953">"Prikaži virtualnu tipkovnicu"</string>
+    <string name="hardware" msgid="3611039921284836033">"Upotreba zaslonske tipkovnice"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"Konfigurirajte uređaj <xliff:g id="DEVICE_NAME">%s</xliff:g>"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Konfigurirajte fizičke tipkovnice"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Dodirnite da biste odabrali jezik i raspored"</string>
@@ -1957,7 +1960,7 @@
     <string name="app_blocked_title" msgid="7353262160455028160">"Aplikacija nije dostupna"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> trenutačno nije dostupna."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> – nije dostupno"</string>
-    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="4483161748582966785">"Potrebno je dopuštenje"</string>
+    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="3805704317624448487">"Upit za dopuštenje je spriječen"</string>
     <string name="app_streaming_blocked_title_for_camera_dialog" msgid="3935701653713853065">"Kamera nije dostupna"</string>
     <string name="app_streaming_blocked_title_for_fingerprint_dialog" msgid="3516853717714141951">"Nastavite na telefonu"</string>
     <string name="app_streaming_blocked_title_for_microphone_dialog" msgid="544822455127171206">"Mikrofon nije dostupan"</string>
@@ -1968,6 +1971,9 @@
     <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"Trenutačno toj aplikaciji nije moguće pristupiti na vašem uređaju <xliff:g id="DEVICE">%1$s</xliff:g>. Pokušajte joj pristupiti na Android TV uređaju."</string>
     <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"Trenutačno toj aplikaciji nije moguće pristupiti na vašem uređaju <xliff:g id="DEVICE">%1$s</xliff:g>. Pokušajte joj pristupiti na tabletu."</string>
     <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"Trenutačno toj aplikaciji nije moguće pristupiti na vašem uređaju <xliff:g id="DEVICE">%1$s</xliff:g>. Pokušajte joj pristupiti na telefonu."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tv" msgid="4706276040125072077">"Aplikacija zahtijeva dodatna dopuštenja, no dopuštenja se ne mogu odobriti u sesiji streaminga. Dopuštenje najprije odobrite na Android TV uređaju."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tablet" msgid="1824604581465771629">"Aplikacija zahtijeva dodatna dopuštenja, no dopuštenja se ne mogu odobriti u sesiji streaminga. Dopuštenje najprije odobrite na tabletu."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="default" msgid="7755223160363292105">"Aplikacija zahtijeva dodatna dopuštenja, no dopuštenja se ne mogu odobriti u sesiji streaminga. Dopuštenje najprije odobrite na telefonu."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"Ta aplikacija zahtijeva dodatnu sigurnost. Pokušajte joj pristupiti na Android TV uređaju."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"Ta aplikacija zahtijeva dodatnu sigurnost. Pokušajte joj pristupiti na tabletu."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"Ta aplikacija zahtijeva dodatnu sigurnost. Pokušajte joj pristupiti na telefonu."</string>
diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml
index 793fc86..a1fa587 100644
--- a/core/res/res/values-hu/strings.xml
+++ b/core/res/res/values-hu/strings.xml
@@ -590,6 +590,8 @@
     <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"Lehetővé teszi az alkalmazás számára preferált NFC fizetési szolgáltatási információk (pl. regisztrált alkalmazásazonosítók és útvonali cél) lekérését."</string>
     <string name="permlab_nfc" msgid="1904455246837674977">"NFC technológia vezérlése"</string>
     <string name="permdesc_nfc" msgid="8352737680695296741">"Lehetővé teszi az alkalmazás számára, hogy NFC (Near Field Communication - kis hatósugarú vezeték nélküli kommunikáció) technológiát használó címkékkel, kártyákkal és leolvasókkal kommunikáljon."</string>
+    <string name="permlab_nfcTransactionEvent" msgid="5868209446710407679">"Biztonságos elemen zajló tranzakciós esemény"</string>
+    <string name="permdesc_nfcTransactionEvent" msgid="1904286701876487397">"Lehetővé teszi az alkalmazás számára, hogy információkat kapjon a biztonságos elemeken zajló tranzakciókról."</string>
     <string name="permlab_disableKeyguard" msgid="3605253559020928505">"képernyőzár kikapcsolása"</string>
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"Lehetővé teszi az alkalmazás számára a billentyűzár és bármely kapcsolódó jelszavas védelem kikapcsolását. Például a telefon feloldja a billentyűzárat bejövő hívás esetén, majd újra bekapcsolja azt a hívás végeztével."</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"képernyőzár összetettségi szintjének lekérése"</string>
@@ -620,6 +622,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Biometrikus hardver nem áll rendelkezésre"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Hitelesítés megszakítva"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Nem ismerhető fel"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Sikertelen arcfelismerés"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Hitelesítés megszakítva"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"Nem állított be PIN-kódot, mintát vagy jelszót."</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Hiba történt a hitelesítés közben"</string>
@@ -1393,7 +1396,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"ELUTASÍTÁS"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Beviteli mód kiválasztása"</string>
     <string name="show_ime" msgid="6406112007347443383">"Maradjon a képernyőn, amíg a billentyűzet aktív"</string>
-    <string name="hardware" msgid="1800597768237606953">"Virtuális billentyűzet"</string>
+    <string name="hardware" msgid="3611039921284836033">"Képernyő-billentyűzet"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"A(z) <xliff:g id="DEVICE_NAME">%s</xliff:g> beállítása"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Fizikai billentyűzetek beállítása"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Koppintson a nyelv és a billentyűzetkiosztás kiválasztásához"</string>
@@ -1956,7 +1959,7 @@
     <string name="app_blocked_title" msgid="7353262160455028160">"Az alkalmazás nem hozzáférhető"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"A(z) <xliff:g id="APP_NAME">%1$s</xliff:g> jelenleg nem hozzáférhető."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"A(z) <xliff:g id="ACTIVITY">%1$s</xliff:g> nem áll rendelkezése"</string>
-    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="4483161748582966785">"Engedély szükséges"</string>
+    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="3805704317624448487">"Engedélykérés letiltva"</string>
     <string name="app_streaming_blocked_title_for_camera_dialog" msgid="3935701653713853065">"A kamera nem áll rendelkezésre"</string>
     <string name="app_streaming_blocked_title_for_fingerprint_dialog" msgid="3516853717714141951">"Folytatás a telefonon"</string>
     <string name="app_streaming_blocked_title_for_microphone_dialog" msgid="544822455127171206">"A mikrofon nem áll rendelkezésre"</string>
@@ -1967,6 +1970,9 @@
     <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"Ehhez jelenleg nem lehet hozzáférni a következő eszközön: <xliff:g id="DEVICE">%1$s</xliff:g>. Próbálja újra Android TV-eszközén."</string>
     <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"Ehhez jelenleg nem lehet hozzáférni a következő eszközön: <xliff:g id="DEVICE">%1$s</xliff:g>. Próbálja újra a táblagépén."</string>
     <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"Ehhez jelenleg nem lehet hozzáférni a következő eszközön: <xliff:g id="DEVICE">%1$s</xliff:g>. Próbálja újra a telefonján."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tv" msgid="4706276040125072077">"Ez az alkalmazás további engedélyeket kér, de az engedélyeket nem lehet megadni streamelési munkamenetben. Előbb adja meg az engedélyeket Android TV-eszközén."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tablet" msgid="1824604581465771629">"Ez az alkalmazás további engedélyeket kér, de az engedélyeket nem lehet megadni streamelési munkamenetben. Előbb adja meg az engedélyeket a táblagépén."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="default" msgid="7755223160363292105">"Ez az alkalmazás további engedélyeket kér, de az engedélyeket nem lehet megadni streamelési munkamenetben. Előbb adja meg az engedélyeket a telefonján."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"Ez az alkalmazás nagyobb biztonságot igényel. Próbálja újra Android TV-eszközén."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"Ez az alkalmazás nagyobb biztonságot igényel. Próbálja újra a táblagépén."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"Ez az alkalmazás nagyobb biztonságot igényel. Próbálja újra a telefonján."</string>
diff --git a/core/res/res/values-hy/strings.xml b/core/res/res/values-hy/strings.xml
index da1b5dc..8c30f1d 100644
--- a/core/res/res/values-hy/strings.xml
+++ b/core/res/res/values-hy/strings.xml
@@ -590,6 +590,8 @@
     <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"Թույլ է տալիս հավելվածին ստանալ նախընտրելի NFC վճարային ծառայության մասին տեղեկություններ (օր․՝ գրանցված լրացուցիչ սարքերի և երթուղու նպատակակետի մասին տվյալներ)։"</string>
     <string name="permlab_nfc" msgid="1904455246837674977">"վերահսկել Մոտ Տարածությամբ Հաղորդակցումը"</string>
     <string name="permdesc_nfc" msgid="8352737680695296741">"Թույլ է տալիս հավելվածին հաղորդակցվել Մոտ տարածությամբ հաղորդակցման (NFC) պիտակների, քարտերի և ընթերցիչների հետ:"</string>
+    <string name="permlab_nfcTransactionEvent" msgid="5868209446710407679">"Secure Element գործարքի իրադարձություն"</string>
+    <string name="permdesc_nfcTransactionEvent" msgid="1904286701876487397">"Թույլ է տալիս հավելվածին ստանալ տեղեկություններ Secure Element-ում կատարվող գործարքների մասին։"</string>
     <string name="permlab_disableKeyguard" msgid="3605253559020928505">"անջատել ձեր էկրանի կողպեքը"</string>
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"Թույլ է տալիս հավելվածին անջատել ստեղնաշարի կողպումը և ցանկացած կապված գաղտնաբառի պաշտպանվածությունը: Սրա ճիշտ օրինակն է, երբ հեռախոսը անջատում է ստեղնաշարի կողպումը մուտքային զանգ ստանալիս, հետո այն կրկին միացնում է, երբ զանգը ավարտվում է:"</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"հարցում էկրանի կողպման բարդության մակարդակի մասին"</string>
@@ -620,6 +622,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Կենսաչափական սարքը հասանելի չէ"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Նույնականացումը չեղարկվեց"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Չհաջողվեց ճանաչել"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Դեմքը չի ճանաչվել"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Նույնականացումը չեղարկվեց"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"Ավելացրեք PIN կոդ, նախշ կամ գաղտնաբառ։"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Չհաջողվեց նույնականացնել"</string>
@@ -1393,7 +1396,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"ՄԵՐԺԵԼ"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Ընտրեք ներածման եղանակը"</string>
     <string name="show_ime" msgid="6406112007347443383">"Պահել էկրանին, երբ ֆիզիկական ստեղնաշարն ակտիվ է"</string>
-    <string name="hardware" msgid="1800597768237606953">"Ցույց տալ վիրտուալ ստեղնաշարը"</string>
+    <string name="hardware" msgid="3611039921284836033">"Օգտագործել էկրանային ստեղնաշար"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"Կարգավորեք <xliff:g id="DEVICE_NAME">%s</xliff:g> սարքը"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Կարգավորեք ֆիզիկական ստեղնաշարերը"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Հպեք՝ լեզուն և դասավորությունն ընտրելու համար"</string>
@@ -1956,7 +1959,7 @@
     <string name="app_blocked_title" msgid="7353262160455028160">"Հավելվածը հասանելի չէ"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> հավելվածն այս պահին հասանելի չէ։"</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g>՝ անհասանելի է"</string>
-    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="4483161748582966785">"Անհրաժեշտ է թույլտվություն"</string>
+    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="3805704317624448487">"Թույլտվության հայտն արգելափակվել է"</string>
     <string name="app_streaming_blocked_title_for_camera_dialog" msgid="3935701653713853065">"Տեսախցիկն անհասանելի է"</string>
     <string name="app_streaming_blocked_title_for_fingerprint_dialog" msgid="3516853717714141951">"Շարու­նակեք հեռախոսով"</string>
     <string name="app_streaming_blocked_title_for_microphone_dialog" msgid="544822455127171206">"Խոսափողն անհասանելի է"</string>
@@ -1967,6 +1970,9 @@
     <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"Այս պահին հնարավոր չէ բացել հավելվածը <xliff:g id="DEVICE">%1$s</xliff:g> սարքում։ Փորձեք Android TV սարքում։"</string>
     <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"Այս պահին հնարավոր չէ բացել հավելվածը <xliff:g id="DEVICE">%1$s</xliff:g> սարքում։ Փորձեք ձեր պլանշետում։"</string>
     <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"Այս պահին հնարավոր չէ բացել հավելվածը <xliff:g id="DEVICE">%1$s</xliff:g> սարքում։ Փորձեք ձեր հեռախոսում։"</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tv" msgid="4706276040125072077">"Այս հավելվածը պահանջում է լրացուցիչ թույլտվություններ, սակայն դրանք հնարավոր չէ տրամադրել հեռարձակման ժամանակ։ Նախ տրամադրեք թույլտվությունը ձեր Android TV սարքում։"</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tablet" msgid="1824604581465771629">"Այս հավելվածը պահանջում է լրացուցիչ թույլտվություններ, սակայն դրանք հնարավոր չէ տրամադրել հեռարձակման ժամանակ։ Նախ տրամադրեք թույլտվությունը ձեր պլանշետում։"</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="default" msgid="7755223160363292105">"Այս հավելվածը պահանջում է լրացուցիչ թույլտվություններ, սակայն դրանք հնարավոր չէ տրամադրել հեռարձակման ժամանակ։ Նախ տրամադրեք թույլտվությունը ձեր հեռախոսում։"</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"Այս հավելվածը պահանջում է անվտանգության լրացուցիչ միջոցներ։ Օգտագործեք ձեր Android TV սարքը։"</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"Այս հավելվածը պահանջում է անվտանգության լրացուցիչ միջոցներ։ Օգտագործեք ձեր պլանշետը։"</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"Այս հավելվածը պահանջում է անվտանգության լրացուցիչ միջոցներ։ Օգտագործեք ձեր հեռախոսը։"</string>
diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml
index af5e5fe..120d261 100644
--- a/core/res/res/values-in/strings.xml
+++ b/core/res/res/values-in/strings.xml
@@ -590,6 +590,8 @@
     <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"Mengizinkan aplikasi untuk mendapatkan informasi layanan pembayaran NFC pilihan seperti bantuan terdaftar dan tujuan rute."</string>
     <string name="permlab_nfc" msgid="1904455246837674977">"kontrol NFC"</string>
     <string name="permdesc_nfc" msgid="8352737680695296741">"Mengizinkan apl berkomunikasi dengan tag, kartu, dan alat pembaca Komunikasi Nirkabel Jarak Dekat (NFC)."</string>
+    <string name="permlab_nfcTransactionEvent" msgid="5868209446710407679">"Peristiwa transaksi Elemen Pengaman"</string>
+    <string name="permdesc_nfcTransactionEvent" msgid="1904286701876487397">"Mengizinkan aplikasi menerima informasi tentang transaksi yang dilakukan pada Elemen Pengaman."</string>
     <string name="permlab_disableKeyguard" msgid="3605253559020928505">"nonaktifkan kunci layar Anda"</string>
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"Memungkinkan aplikasi menonaktifkan kunci tombol dan keamanan sandi apa pun yang terkait. Misalnya, ponsel menonaktifkan kunci tombol saat menerima panggilan telepon masuk, kemudian mengaktifkan kembali kunci tombol ketika panggilan selesai."</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"meminta kompleksitas kunci layar"</string>
@@ -620,6 +622,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Hardware biometrik tidak tersedia"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Autentikasi dibatalkan"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Tidak dikenali"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Wajah tidak dikenali"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Autentikasi dibatalkan"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"Tidak ada PIN, pola, atau sandi yang disetel"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Error saat mengautentikasi"</string>
@@ -1393,7 +1396,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"TOLAK"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Pilih metode masukan"</string>
     <string name="show_ime" msgid="6406112007347443383">"Biarkan di layar meski keyboard fisik aktif"</string>
-    <string name="hardware" msgid="1800597768237606953">"Tampilkan keyboard virtual"</string>
+    <string name="hardware" msgid="3611039921284836033">"Gunakan keyboard virtual"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"Konfigurasi <xliff:g id="DEVICE_NAME">%s</xliff:g>"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Konfigurasi keyboard fisik"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Ketuk untuk memilih bahasa dan tata letak"</string>
@@ -1956,7 +1959,7 @@
     <string name="app_blocked_title" msgid="7353262160455028160">"Aplikasi tidak tersedia"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> tidak tersedia saat ini."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> tidak tersedia"</string>
-    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="4483161748582966785">"Perlu izin"</string>
+    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="3805704317624448487">"Permintaan izin diblokir"</string>
     <string name="app_streaming_blocked_title_for_camera_dialog" msgid="3935701653713853065">"Kamera tidak tersedia"</string>
     <string name="app_streaming_blocked_title_for_fingerprint_dialog" msgid="3516853717714141951">"Lanjutkan di ponsel"</string>
     <string name="app_streaming_blocked_title_for_microphone_dialog" msgid="544822455127171206">"Mikrofon tidak tersedia"</string>
@@ -1967,6 +1970,9 @@
     <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"Aplikasi ini tidak dapat diakses di <xliff:g id="DEVICE">%1$s</xliff:g> untuk saat ini. Coba di perangkat Android TV."</string>
     <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"Aplikasi ini tidak dapat diakses di <xliff:g id="DEVICE">%1$s</xliff:g> untuk saat ini. Coba di tablet."</string>
     <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"Aplikasi ini tidak dapat diakses di <xliff:g id="DEVICE">%1$s</xliff:g> untuk saat ini. Coba di ponsel."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tv" msgid="4706276040125072077">"Aplikasi ini meminta izin tambahan, tetapi izin tidak dapat diberikan dalam sesi streaming. Berikan izin di perangkat Android TV terlebih dahulu."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tablet" msgid="1824604581465771629">"Aplikasi ini meminta izin tambahan, tetapi izin tidak dapat diberikan dalam sesi streaming. Berikan izin di tablet terlebih dahulu."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="default" msgid="7755223160363292105">"Aplikasi ini meminta izin tambahan, tetapi izin tidak dapat diberikan dalam sesi streaming. Berikan izin di ponsel terlebih dahulu."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"Aplikasi ini meminta keamanan tambahan. Coba di perangkat Android TV."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"Aplikasi ini meminta keamanan tambahan. Coba di tablet."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"Aplikasi ini meminta keamanan tambahan. Coba di ponsel."</string>
diff --git a/core/res/res/values-is/strings.xml b/core/res/res/values-is/strings.xml
index b43935f..552fa6c 100644
--- a/core/res/res/values-is/strings.xml
+++ b/core/res/res/values-is/strings.xml
@@ -590,6 +590,8 @@
     <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"Gerir forritinu kleift að fá valda NFC-greiðsluþjónustu, svo sem skráða aðstoð og áfangastað leiðar."</string>
     <string name="permlab_nfc" msgid="1904455246837674977">"stjórna nándarsamskiptum (NFC)"</string>
     <string name="permdesc_nfc" msgid="8352737680695296741">"Leyfir forriti að eiga samskipti við NFC-merki, -spjöld og -lesara (nándarsamskipti)."</string>
+    <string name="permlab_nfcTransactionEvent" msgid="5868209446710407679">"Færslutilvik í öryggiseiningu"</string>
+    <string name="permdesc_nfcTransactionEvent" msgid="1904286701876487397">"Gerir forritinu kleift að móttaka upplýsingar um færslur sem fara fram í öryggiseiningu."</string>
     <string name="permlab_disableKeyguard" msgid="3605253559020928505">"slökkva á skjálásnum"</string>
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"Leyfir forriti að slökkva á símalásnum og öðrum öryggisaðgerðum tengdum aðgangsorði. Til dæmis gerir síminn lásinn óvirkan þegar símtal berst og læsist svo aftur að símtali loknu."</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"biðja um flókinn skjálás"</string>
@@ -620,6 +622,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Lífkennavélbúnaður ekki tiltækur"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Hætt við auðkenningu"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Þekktist ekki"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Andlit þekkist ekki"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Hætt við auðkenningu"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"Ekkert PIN-númer, mynstur eða aðgangsorð stillt"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Villa við auðkenningu"</string>
@@ -1393,7 +1396,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"HAFNA"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Veldu innsláttaraðferð"</string>
     <string name="show_ime" msgid="6406112007347443383">"Halda því á skjánum meðan vélbúnaðarlyklaborðið er virkt"</string>
-    <string name="hardware" msgid="1800597768237606953">"Sýna sýndarlyklaborð"</string>
+    <string name="hardware" msgid="3611039921284836033">"Nota skjályklaborð"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"Stilla <xliff:g id="DEVICE_NAME">%s</xliff:g>"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Stilla vélbúnaðarlyklaborð"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Ýttu til að velja tungumál og útlit"</string>
@@ -1956,7 +1959,7 @@
     <string name="app_blocked_title" msgid="7353262160455028160">"Forrit er ekki tiltækt"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> er ekki tiltækt núna."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> ekki í boði"</string>
-    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="4483161748582966785">"Heimildar krafist"</string>
+    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="3805704317624448487">"Heimildarbeiðni hafnað"</string>
     <string name="app_streaming_blocked_title_for_camera_dialog" msgid="3935701653713853065">"Myndavél ekki tiltæk"</string>
     <string name="app_streaming_blocked_title_for_fingerprint_dialog" msgid="3516853717714141951">"Halda áfram í símanum"</string>
     <string name="app_streaming_blocked_title_for_microphone_dialog" msgid="544822455127171206">"Hljóðnemi ekki tiltækur"</string>
@@ -1967,6 +1970,9 @@
     <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"Aðgangur að þessu í <xliff:g id="DEVICE">%1$s</xliff:g> er ekki í boði eins og er. Prófaðu það í Android TV tækinu í staðinn."</string>
     <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"Aðgangur að þessu í <xliff:g id="DEVICE">%1$s</xliff:g> er ekki í boði eins og er. Prófaðu það í spjaldtölvunni í staðinn."</string>
     <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"Aðgangur að þessu í <xliff:g id="DEVICE">%1$s</xliff:g> er ekki í boði eins og er. Prófaðu það í símanum í staðinn."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tv" msgid="4706276040125072077">"Þetta forrit biður um viðbótarheimildir en ekki er hægt að veita heimildir í streymislotu. Veittu heimildina í Android TV-tækinu fyrst."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tablet" msgid="1824604581465771629">"Þetta forrit biður um viðbótarheimildir en ekki er hægt að veita heimildir í streymislotu. Veittu heimildina í spjaldtölvunni fyrst."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="default" msgid="7755223160363292105">"Þetta forrit biður um viðbótarheimildir en ekki er hægt að veita heimildir í streymislotu. Veittu heimildina í símanum fyrst."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"Þetta forrit biður um viðbótaröryggi. Prófaðu það í Android TV tækinu í staðinn."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"Þetta forrit biður um viðbótaröryggi. Prófaðu það í spjaldtölvunni í staðinn."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"Þetta forrit biður um viðbótaröryggi. Prófaðu það í símanum í staðinn."</string>
diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml
index f34b103..74c1ac3 100644
--- a/core/res/res/values-it/strings.xml
+++ b/core/res/res/values-it/strings.xml
@@ -591,6 +591,8 @@
     <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"Consente all\'app di recuperare informazioni del servizio di pagamento NFC preferito, quali destinazione della route e identificatori applicazione registrati."</string>
     <string name="permlab_nfc" msgid="1904455246837674977">"controllo Near Field Communication"</string>
     <string name="permdesc_nfc" msgid="8352737680695296741">"Consente all\'applicazione di comunicare con tag, schede e lettori NFC (Near Field Communication)."</string>
+    <string name="permlab_nfcTransactionEvent" msgid="5868209446710407679">"Evento di transazione Secure Element"</string>
+    <string name="permdesc_nfcTransactionEvent" msgid="1904286701876487397">"Consente all\'app di ricevere informazioni sulle transazioni in corso su Secure Element."</string>
     <string name="permlab_disableKeyguard" msgid="3605253559020928505">"disattivazione blocco schermo"</string>
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"Consente all\'applicazione di disattivare il blocco tastiera ed eventuali protezioni tramite password associate. Ad esempio, il telefono disattiva il blocco tastiera quando riceve una telefonata in arrivo e lo riattiva al termine della chiamata."</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"richiesta di complessità del blocco schermo"</string>
@@ -621,6 +623,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Hardware biometrico non disponibile"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Autenticazione annullata"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Non riconosciuto"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Volto non riconosciuto"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Autenticazione annullata"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"Non hai impostato PIN, sequenza o password"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Errore durante l\'autenticazione"</string>
@@ -1394,7 +1397,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"RIFIUTO"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Scegli il metodo di immissione"</string>
     <string name="show_ime" msgid="6406112007347443383">"Tieni sullo schermo quando è attiva la tastiera fisica"</string>
-    <string name="hardware" msgid="1800597768237606953">"Mostra tastiera virtuale"</string>
+    <string name="hardware" msgid="3611039921284836033">"Usa tastiera sullo schermo"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"Configura <xliff:g id="DEVICE_NAME">%s</xliff:g>"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Configura le tastiere fisiche"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Tocca per selezionare la lingua e il layout"</string>
@@ -1957,7 +1960,7 @@
     <string name="app_blocked_title" msgid="7353262160455028160">"L\'app non è disponibile"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"L\'app <xliff:g id="APP_NAME">%1$s</xliff:g> non è al momento disponibile."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> non disponibile"</string>
-    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="4483161748582966785">"Autorizzazione necessaria"</string>
+    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="3805704317624448487">"Richiesta di autorizzazione rifiutata"</string>
     <string name="app_streaming_blocked_title_for_camera_dialog" msgid="3935701653713853065">"Fotocamera non disponibile"</string>
     <string name="app_streaming_blocked_title_for_fingerprint_dialog" msgid="3516853717714141951">"Continua sul telefono"</string>
     <string name="app_streaming_blocked_title_for_microphone_dialog" msgid="544822455127171206">"Microfono non disponibile"</string>
@@ -1968,6 +1971,9 @@
     <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"Al momento non è possibile accedere a questa app su <xliff:g id="DEVICE">%1$s</xliff:g>. Prova a usare il dispositivo Android TV."</string>
     <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"Al momento non è possibile accedere a questa app su <xliff:g id="DEVICE">%1$s</xliff:g>. Prova a usare il tablet."</string>
     <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"Al momento non è possibile accedere a questa app su <xliff:g id="DEVICE">%1$s</xliff:g>. Prova a usare il telefono."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tv" msgid="4706276040125072077">"Questa app richiede autorizzazioni aggiuntive, ma non è possibile concedere autorizzazioni in una sessione di streaming. Devi prima concedere l\'autorizzazione sul tuo dispositivo Android TV."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tablet" msgid="1824604581465771629">"Questa app richiede autorizzazioni aggiuntive, ma non è possibile concedere autorizzazioni in una sessione di streaming. Devi prima concedere l\'autorizzazione sul tuo tablet."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="default" msgid="7755223160363292105">"Questa app richiede autorizzazioni aggiuntive, ma non è possibile concedere autorizzazioni in una sessione di streaming. Devi prima concedere l\'autorizzazione sul tuo smartphone."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"Questa app richiede maggiore sicurezza. Prova a usare il dispositivo Android TV."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"Questa app richiede maggiore sicurezza. Prova a usare il tablet."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"Questa app richiede maggiore sicurezza. Prova a usare il telefono."</string>
diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml
index cc03284..f9693ba 100644
--- a/core/res/res/values-iw/strings.xml
+++ b/core/res/res/values-iw/strings.xml
@@ -591,6 +591,8 @@
     <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"‏מאפשרת לאפליקציה לקבל פרטים על שירות תשלום מועדף ב-NFC, כמו עזרים רשומים ויעד של נתיב."</string>
     <string name="permlab_nfc" msgid="1904455246837674977">"שליטה בתקשורת מטווח קצר"</string>
     <string name="permdesc_nfc" msgid="8352737680695296741">"‏מאפשרת לאפליקציה נהל תקשורת עם תגים, כרטיסים וקוראים מסוג \'תקשורת מטווח קצר\' (NFC)."</string>
+    <string name="permlab_nfcTransactionEvent" msgid="5868209446710407679">"אירוע עסקה של רכיב מאובטח"</string>
+    <string name="permdesc_nfcTransactionEvent" msgid="1904286701876487397">"ההרשאה הזו מאפשרת לאפליקציה לקבל מידע על עסקאות שמתרחשות ברכיב מאובטח."</string>
     <string name="permlab_disableKeyguard" msgid="3605253559020928505">"ביטול נעילת המסך שלך"</string>
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"מאפשרת לאפליקציה להשבית את נעילת המקשים וכל אמצעי אבטחה משויך המבוסס על סיסמה. לדוגמה, הטלפון ישבית את נעילת המקשים במהלך שיחת טלפון נכנסת, ויפעיל מחדש את נעילת המקשים עם סיום השיחה."</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"בקשת מידע לגבי מידת המורכבות של נעילת המסך"</string>
@@ -621,6 +623,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"חומרה ביומטרית לא זמינה"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"האימות בוטל"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"לא זוהתה"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"הפנים לא זוהו"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"האימות בוטל"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"עוד לא הוגדרו קוד אימות, קו ביטול נעילה או סיסמה"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"שגיאה באימות"</string>
@@ -1394,7 +1397,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"עדיף שלא"</string>
     <string name="select_input_method" msgid="3971267998568587025">"בחירה של שיטת הזנה"</string>
     <string name="show_ime" msgid="6406112007347443383">"להשאיר במסך בזמן שהמקלדת הפיזית פעילה"</string>
-    <string name="hardware" msgid="1800597768237606953">"הצגת מקלדת וירטואלית"</string>
+    <string name="hardware" msgid="3611039921284836033">"שימוש במקלדת שמופיעה במסך"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"הגדרה של <xliff:g id="DEVICE_NAME">%s</xliff:g>"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"הגדרת מקלדות פיזיות"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"יש להקיש כדי לבחור שפה ופריסה"</string>
@@ -1957,7 +1960,7 @@
     <string name="app_blocked_title" msgid="7353262160455028160">"האפליקציה לא זמינה"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"האפליקציה <xliff:g id="APP_NAME">%1$s</xliff:g> לא זמינה בשלב זה."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> לא זמינה"</string>
-    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="4483161748582966785">"דרושה הרשאה"</string>
+    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="3805704317624448487">"בקשת ההרשאה בוטלה"</string>
     <string name="app_streaming_blocked_title_for_camera_dialog" msgid="3935701653713853065">"המצלמה לא זמינה"</string>
     <string name="app_streaming_blocked_title_for_fingerprint_dialog" msgid="3516853717714141951">"יש להמשיך בטלפון"</string>
     <string name="app_streaming_blocked_title_for_microphone_dialog" msgid="544822455127171206">"המיקרופון לא זמין"</string>
@@ -1968,6 +1971,9 @@
     <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"‏אי אפשר לגשת לאפליקציה הזו במכשיר <xliff:g id="DEVICE">%1$s</xliff:g> כרגע. במקום זאת, יש לנסות במכשיר Android TV."</string>
     <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"אי אפשר לגשת לאפליקציה הזו במכשיר <xliff:g id="DEVICE">%1$s</xliff:g> כרגע. במקום זאת, יש לנסות בטאבלט."</string>
     <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"אי אפשר לגשת לאפליקציה הזו במכשיר <xliff:g id="DEVICE">%1$s</xliff:g> כרגע. במקום זאת, אפשר לנסות בטלפון."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tv" msgid="4706276040125072077">"‏האפליקציה הזו מבקשת הרשאות נוספות, אך לא ניתן להעניק הרשאות בסשן סטרימינג. צריך לתת את ההרשאה קודם במכשיר Android TV."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tablet" msgid="1824604581465771629">"האפליקציה הזו מבקשת הרשאות נוספות, אך לא ניתן להעניק הרשאות בסשן סטרימינג. צריך לתת את ההרשאה קודם בטאבלט."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="default" msgid="7755223160363292105">"האפליקציה הזו מבקשת הרשאות נוספות, אך לא ניתן להעניק הרשאות בסשן סטרימינג. צריך לתת את ההרשאה קודם בטלפון."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"‏האפליקציה הזו מבקשת אמצעי אבטחה נוסף. במקום זאת, יש לנסות במכשיר Android TV."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"האפליקציה הזו מבקשת אמצעי אבטחה נוסף. במקום זאת, יש לנסות בטאבלט."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"האפליקציה הזו מבקשת אמצעי אבטחה נוסף. במקום זאת, יש לנסות בטלפון."</string>
diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml
index 66d6852..a422e09 100644
--- a/core/res/res/values-ja/strings.xml
+++ b/core/res/res/values-ja/strings.xml
@@ -590,6 +590,8 @@
     <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"登録されている支援やルートの目的地など、優先される NFC お支払いサービスの情報を取得することをアプリに許可します。"</string>
     <string name="permlab_nfc" msgid="1904455246837674977">"NFCの管理"</string>
     <string name="permdesc_nfc" msgid="8352737680695296741">"NFCタグ、カード、リーダーとの通信をアプリに許可します。"</string>
+    <string name="permlab_nfcTransactionEvent" msgid="5868209446710407679">"セキュア エレメントのトランザクション イベント"</string>
+    <string name="permdesc_nfcTransactionEvent" msgid="1904286701876487397">"セキュア エレメントで起きたトランザクションに関する情報の受信をアプリに許可します。"</string>
     <string name="permlab_disableKeyguard" msgid="3605253559020928505">"画面ロックの無効化"</string>
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"キーロックとキーロックに関連付けられたパスワードのセキュリティを無効にすることをアプリに許可します。たとえば、かかってきた電話を受ける際にキーロックを無効にし、通話が終了したらキーロックを再度有効にする場合などに使用します。"</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"画面ロックの複雑さのリクエスト"</string>
@@ -620,6 +622,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"生体認証ハードウェアが利用できません"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"認証をキャンセルしました"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"認識されませんでした"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"顔を認識できません"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"認証をキャンセルしました"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"PIN、パターン、パスワードが設定されていません"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"エラー認証"</string>
@@ -1393,7 +1396,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"共有しない"</string>
     <string name="select_input_method" msgid="3971267998568587025">"入力方法の選択"</string>
     <string name="show_ime" msgid="6406112007347443383">"物理キーボードが有効になっていても画面に表示させます"</string>
-    <string name="hardware" msgid="1800597768237606953">"仮想キーボードの表示"</string>
+    <string name="hardware" msgid="3611039921284836033">"画面キーボードの使用"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"<xliff:g id="DEVICE_NAME">%s</xliff:g>の設定"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"物理キーボードの設定"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"タップして言語とレイアウトを選択してください"</string>
@@ -1956,7 +1959,7 @@
     <string name="app_blocked_title" msgid="7353262160455028160">"アプリの利用不可"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"現在 <xliff:g id="APP_NAME">%1$s</xliff:g> はご利用になれません。"</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g>は利用できません"</string>
-    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="4483161748582966785">"権限が必要"</string>
+    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="3805704317624448487">"権限のリクエストが抑制されています"</string>
     <string name="app_streaming_blocked_title_for_camera_dialog" msgid="3935701653713853065">"カメラ: 使用不可"</string>
     <string name="app_streaming_blocked_title_for_fingerprint_dialog" msgid="3516853717714141951">"スマートフォンで続行"</string>
     <string name="app_streaming_blocked_title_for_microphone_dialog" msgid="544822455127171206">"マイク: 使用不可"</string>
@@ -1967,6 +1970,9 @@
     <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"現在、<xliff:g id="DEVICE">%1$s</xliff:g> からアクセスできません。Android TV デバイスでのアクセスをお試しください。"</string>
     <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"現在、<xliff:g id="DEVICE">%1$s</xliff:g> からアクセスできません。タブレットでのアクセスをお試しください。"</string>
     <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"現在、<xliff:g id="DEVICE">%1$s</xliff:g> からアクセスできません。スマートフォンでのアクセスをお試しください。"</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tv" msgid="4706276040125072077">"このアプリは追加の権限を求めていますが、ストリーミング セッションでは権限を付与できません。Android TV デバイスで先に権限を付与してください。"</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tablet" msgid="1824604581465771629">"このアプリは追加の権限を求めていますが、ストリーミング セッションでは権限を付与できません。タブレットで先に権限を付与してください。"</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="default" msgid="7755223160363292105">"このアプリは追加の権限を求めていますが、ストリーミング セッションでは権限を付与できません。スマートフォンで先に権限を付与してください。"</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"このアプリはセキュリティの強化を求めています。Android TV デバイスでのアクセスをお試しください。"</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"このアプリはセキュリティの強化を求めています。タブレットでのアクセスをお試しください。"</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"このアプリはセキュリティの強化を求めています。スマートフォンでのアクセスをお試しください。"</string>
diff --git a/core/res/res/values-ka/strings.xml b/core/res/res/values-ka/strings.xml
index c7fa7d0..ab98d64 100644
--- a/core/res/res/values-ka/strings.xml
+++ b/core/res/res/values-ka/strings.xml
@@ -590,6 +590,8 @@
     <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"საშუალებას აძლევს აპს, მიიღოს უპირატესი NFC გადახდის სერვისის ინფორმაცია, მაგალითად, რეგისტრირებული დახმარება და დანიშნულება."</string>
     <string name="permlab_nfc" msgid="1904455246837674977">"ახლო მოქმედების რადიოკავშირი (NFC) მართვა"</string>
     <string name="permdesc_nfc" msgid="8352737680695296741">"აპს შეეძლება ახლო მოქმედების რადიოკავშირის (NFC) მეშვეობით ტეგების, ბარათებისა და წამკითხველების შემცველი მონაცემების მიმოცვლა."</string>
+    <string name="permlab_nfcTransactionEvent" msgid="5868209446710407679">"უსაფრთხო ელემენტის ტრანზაქციის ღონისძიება"</string>
+    <string name="permdesc_nfcTransactionEvent" msgid="1904286701876487397">"საშუალებს აძლევს აპს, მიიღოს ინფორმაცია უსაფრთხო ელემენტზე განხორციელებული ტრანზაქციების შესახებ."</string>
     <string name="permlab_disableKeyguard" msgid="3605253559020928505">"თქვენი ეკრანის ბლოკის გათიშვა"</string>
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"შეეძლება კლავიატურის დაბლოკვისა და პაროლით უზრუნველყოფილი ნებისმიერი უსაფრთხოების ფუნქციის დეაქტივაცია. მაგალითად, ტელეფონი შემომავალი ზარის დროს აუქმებს კლავიატურის დაბლოკვას და კვლავ ააქტიურებს მას, როგორც კი ზარი დასრულდება."</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"ეკრანის დაბლოკვის მეთოდის სირთულის შესახებ ინფორმაციის მოთხოვნა"</string>
@@ -620,6 +622,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"ბიომეტრიული აპარატურა მიუწვდომელია"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"ავტორიზაცია გაუქმდა"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"არ არის ამოცნობილი"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"სახის ამოცნობა ვერ მოხერხდა"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"ავტორიზაცია გაუქმდა"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"PIN-კოდი, ნიმუში ან პაროლი დაყენებული არ არის"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"შეცდომა ავთენტიკაციისას"</string>
@@ -1393,7 +1396,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"უარყოფა"</string>
     <string name="select_input_method" msgid="3971267998568587025">"აირჩიეთ შეყვანის მეთოდი"</string>
     <string name="show_ime" msgid="6406112007347443383">"აქტიური ფიზიკური კლავიატურისას ეკრანზე შენარჩუნება"</string>
-    <string name="hardware" msgid="1800597768237606953">"ვირტუალური კლავიატურის ჩვენება"</string>
+    <string name="hardware" msgid="3611039921284836033">"ეკრანული კლავიატურის გამოყენება"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"მოახდინეთ <xliff:g id="DEVICE_NAME">%s</xliff:g>-ის კონფიგურირება"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"მოახდინეთ ფიზიკური კლავიატურების კონფიგურირება"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"შეეხეთ ენისა და განლაგების ასარჩევად"</string>
@@ -1956,7 +1959,7 @@
     <string name="app_blocked_title" msgid="7353262160455028160">"აპი მიუწვდომელია"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> ამჟამად მიუწვდომელია."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> მიუწვდომელია"</string>
-    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="4483161748582966785">"საჭიროა ნებართვა"</string>
+    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="3805704317624448487">"ნებართვის მოთხოვნა შეჩერებულია"</string>
     <string name="app_streaming_blocked_title_for_camera_dialog" msgid="3935701653713853065">"კამერა მიუწვდომელია"</string>
     <string name="app_streaming_blocked_title_for_fingerprint_dialog" msgid="3516853717714141951">"ტელეფონზე გაგრძელება"</string>
     <string name="app_streaming_blocked_title_for_microphone_dialog" msgid="544822455127171206">"მიკროფონი მიუწვდომელია"</string>
@@ -1967,6 +1970,9 @@
     <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"ამჟამად ამ აპზე თქვენი <xliff:g id="DEVICE">%1$s</xliff:g>-დან წვდომა შეუძლებელია. ცადეთ Android TV მოწყობილობიდან."</string>
     <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"ამჟამად ამ აპზე თქვენი <xliff:g id="DEVICE">%1$s</xliff:g>-დან წვდომა შეუძლებელია. ცადეთ ტაბლეტიდან."</string>
     <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"ამჟამად ამ აპზე თქვენი <xliff:g id="DEVICE">%1$s</xliff:g>-დან წვდომა შეუძლებელია. ცადეთ ტელეფონიდან."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tv" msgid="4706276040125072077">"ეს აპი დამატებით ნებართვებს მოითხოვს, მაგრამ ნებართვების მიცემა შეუძლებელია სტრიმინგის სესიაში თავდაპირველად მიანიჭეთ ნებართვა Android TV მოწყობილობაზე."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tablet" msgid="1824604581465771629">"ეს აპი დამატებით ნებართვებს მოითხოვს, მაგრამ ნებართვების მიცემა შეუძლებელია სტრიმინგის სესიაში თავდაპირველად მიანიჭეთ ნებართვა ტაბლეტზე."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="default" msgid="7755223160363292105">"ეს აპი დამატებით ნებართვებს მოითხოვს, მაგრამ ნებართვების მიცემა შეუძლებელია სტრიმინგის სესიაში თავდაპირველად მიანიჭეთ ნებართვა ტელეფონზე."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"ეს აპი დამატებით უსაფრთხოებას ითხოვს. ცადეთ Android TV მოწყობილობიდან."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"ეს აპი დამატებით უსაფრთხოებას ითხოვს. ცადეთ ტაბლეტიდან."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"ეს აპი დამატებით უსაფრთხოებას ითხოვს. ცადეთ ტელეფონიდან."</string>
diff --git a/core/res/res/values-kk-television/strings.xml b/core/res/res/values-kk-television/strings.xml
index 5730867..57d6322 100644
--- a/core/res/res/values-kk-television/strings.xml
+++ b/core/res/res/values-kk-television/strings.xml
@@ -17,6 +17,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="sensor_privacy_start_use_mic_notification_content_title" msgid="7002619958660406548">"Микрофон бөгелген"</string>
-    <string name="sensor_privacy_start_use_camera_notification_content_title" msgid="2131954635322568179">"Камера бөгелген"</string>
+    <string name="sensor_privacy_start_use_mic_notification_content_title" msgid="7002619958660406548">"Микрофон блокталған"</string>
+    <string name="sensor_privacy_start_use_camera_notification_content_title" msgid="2131954635322568179">"Камера блокталған"</string>
 </resources>
diff --git a/core/res/res/values-kk/strings.xml b/core/res/res/values-kk/strings.xml
index 09c9bfb..681aa41 100644
--- a/core/res/res/values-kk/strings.xml
+++ b/core/res/res/values-kk/strings.xml
@@ -590,6 +590,8 @@
     <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"Қолданба тіркелген көмектер және баратын жер маршруты сияқты таңдаулы NFC төлеу қызметі туралы ақпаратты ала алатын болады."</string>
     <string name="permlab_nfc" msgid="1904455246837674977">"NFC функциясын басқару"</string>
     <string name="permdesc_nfc" msgid="8352737680695296741">"Қолданбаға NFC белгілерімен, карталармен және оқу құралдарымен байланысуға рұқсат береді."</string>
+    <string name="permlab_nfcTransactionEvent" msgid="5868209446710407679">"Қауіпсіз элемент арқылы жасалатын транзакция оқиғасы"</string>
+    <string name="permdesc_nfcTransactionEvent" msgid="1904286701876487397">"Қолданбаға қауіпсіз элемент арқылы жасалатын транзакциялар жөнінде ақпарат алуға мүмкіндік береді."</string>
     <string name="permlab_disableKeyguard" msgid="3605253559020928505">"экран бекітпесін істен шығару"</string>
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"Қолданбаларға кілтперне және басқа кілтсөзге қатысты қауіпсіздік шараларын өшіру мүмкіндігін береді. Мысалы, телефон кіріс қоңырауларын алғанда кілтпернені өшіреді және қоңырау аяқталғанда қайта қосады."</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"экранды құлыптау күрделілігін сұрау"</string>
@@ -620,6 +622,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Биометрикалық жабдық жоқ"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Аутентификациядан бас тартылды."</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Танылмады"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Бет танылмады."</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Аутентификациядан бас тартылды."</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"Ешқандай PIN коды, өрнек немесе құпия сөз орнатылмаған."</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Аутентификациялауда қате шықты."</string>
@@ -1393,7 +1396,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"ҚАБЫЛДАМАУ"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Енгізу әдісін таңдау"</string>
     <string name="show_ime" msgid="6406112007347443383">"Физикалық пернетақта қосулы кезде оны экранға шығару"</string>
-    <string name="hardware" msgid="1800597768237606953">"Виртуалдық пернетақтаны көрсету"</string>
+    <string name="hardware" msgid="3611039921284836033">"Экрандағы пернетақтаны пайдалану"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"<xliff:g id="DEVICE_NAME">%s</xliff:g> конфигурациялау"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Физикалық пернетақталарды конфигурациялау"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Тіл мен пернетақта схемасын таңдау үшін түртіңіз."</string>
@@ -1956,7 +1959,7 @@
     <string name="app_blocked_title" msgid="7353262160455028160">"Қолданба қолжетімді емес"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> қазір қолжетімді емес."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> қолжетімсіз"</string>
-    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="4483161748582966785">"Рұқсат қажет"</string>
+    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="3805704317624448487">"Рұқсат сұрауы блокталды"</string>
     <string name="app_streaming_blocked_title_for_camera_dialog" msgid="3935701653713853065">"Камера қолжетімді емес"</string>
     <string name="app_streaming_blocked_title_for_fingerprint_dialog" msgid="3516853717714141951">"Телефоннан жалғастыру"</string>
     <string name="app_streaming_blocked_title_for_microphone_dialog" msgid="544822455127171206">"Микрофон қолжетімді емес"</string>
@@ -1967,6 +1970,9 @@
     <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"Қазір бұған <xliff:g id="DEVICE">%1$s</xliff:g> құрылғысынан кіру мүмкін емес. Оның орнына Android TV құрылғысын пайдаланып көріңіз."</string>
     <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"Қазір бұған <xliff:g id="DEVICE">%1$s</xliff:g> құрылғысынан кіру мүмкін емес. Оның орнына планшетті пайдаланып көріңіз."</string>
     <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"Қазір бұған <xliff:g id="DEVICE">%1$s</xliff:g> құрылғысынан кіру мүмкін емес. Оның орнына телефонды пайдаланып көріңіз."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tv" msgid="4706276040125072077">"Бұл қолданба қосымша рұқсаттар сұрап жатыр, бірақ трансляция жүріп жатқанда рұқсат беру мүмкін емес. Алдымен Android TV құрылғысында рұқсат беріңіз."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tablet" msgid="1824604581465771629">"Бұл қолданба қосымша рұқсаттар сұрап жатыр, бірақ трансляция жүріп жатқанда рұқсат беру мүмкін емес. Алдымен планшетте рұқсат беріңіз."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="default" msgid="7755223160363292105">"Бұл қолданба қосымша рұқсаттар сұрап жатыр, бірақ трансляция жүріп жатқанда рұқсат беру мүмкін емес. Алдымен телефонда рұқсат беріңіз."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"Бұл қолданба үшін қосымша қауіпсіздік шарасы қажет. Оның орнына Android TV құрылғысын пайдаланып көріңіз."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"Бұл қолданба үшін қосымша қауіпсіздік шарасы қажет. Оның орнына планшетті пайдаланып көріңіз."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"Бұл қолданба үшін қосымша қауіпсіздік шарасы қажет. Оның орнына телефонды пайдаланып көріңіз."</string>
diff --git a/core/res/res/values-km/strings.xml b/core/res/res/values-km/strings.xml
index 65c1983..974b314 100644
--- a/core/res/res/values-km/strings.xml
+++ b/core/res/res/values-km/strings.xml
@@ -590,6 +590,8 @@
     <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"អនុញ្ញាតឱ្យ​កម្មវិធី​ទទួលបាន​ព័ត៌មានអំពី​សេវាបង់ប្រាក់តាម nfc ជាអាទិភាព​ដូចជា គោលដៅផ្លូវ និង​ព័ត៌មាន​កំណត់អត្តសញ្ញាណ​កម្មវិធី ដែលបានចុះឈ្មោះ​ជាដើម។"</string>
     <string name="permlab_nfc" msgid="1904455246837674977">"ពិនិត្យ​ការ​ទាក់ទង​នៅ​ក្បែរ (NFC)"</string>
     <string name="permdesc_nfc" msgid="8352737680695296741">"ឲ្យ​កម្មវិធី​ទាក់ទង​ជា​មួយ​ស្លាក (NFC) កាត និង​កម្មវិធី​អាន។"</string>
+    <string name="permlab_nfcTransactionEvent" msgid="5868209446710407679">"ព្រឹត្តិការណ៍ប្រតិបត្តិការធាតុសុវត្ថិភាព"</string>
+    <string name="permdesc_nfcTransactionEvent" msgid="1904286701876487397">"អនុញ្ញាតឱ្យកម្មវិធីទទួលព័ត៌មានអំពីប្រតិបត្តិការដែលកើតឡើងនៅលើធាតុសុវត្ថិភាព។"</string>
     <string name="permlab_disableKeyguard" msgid="3605253559020928505">"បិទ​ការ​ចាក់​សោ​អេក្រង់​របស់​អ្នក"</string>
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"ឲ្យ​កម្មវិធី​បិទ​ការ​ចាក់សោ​សុវត្ថិភាព​ពាក្យ​សម្ងាត់​ដែល​បាន​ភ្ជាប់​ណា​មួយ។ ​ឧទាហរណ៍​ត្រឹមត្រូវ​​​នៃ​ការ​បិទ​ទូរស័ព្ទ​ពេល​ទទួលការ​ហៅ​ចូល បន្ទាប់​ម​បើក​សោ​ពេល​ការ​ហៅ​បាន​បញ្ចប់។"</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"ស្នើ​សុំកម្រិត​​ស្មុគស្មាញ​នៃការចាក់សោអេក្រង់"</string>
@@ -620,6 +622,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"មិនអាច​ប្រើឧបករណ៍​ស្កេន​ស្នាមម្រាមដៃ​បានទេ"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"បាន​បោះបង់​ការ​ផ្ទៀងផ្ទាត់"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"មិនអាចសម្គាល់បានទេ"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"មិន​ស្គាល់​មុខ"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"បាន​បោះបង់​ការ​ផ្ទៀងផ្ទាត់"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"គ្មាន​ការកំណត់​កូដ pin លំនាំ ឬពាក្យសម្ងាត់​ទេ"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"មានបញ្ហាក្នុង​ការផ្ទៀងផ្ទាត់"</string>
@@ -1393,7 +1396,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"បដិសេធ"</string>
     <string name="select_input_method" msgid="3971267998568587025">"ជ្រើស​វិធីសាស្ត្រ​បញ្ចូល"</string>
     <string name="show_ime" msgid="6406112007347443383">"ទុកវានៅលើអេក្រង់ខណៈពេលក្តារចុចពិតប្រាកដកំពុងសកម្ម"</string>
-    <string name="hardware" msgid="1800597768237606953">"បង្ហាញក្ដារចុចនិម្មិត"</string>
+    <string name="hardware" msgid="3611039921284836033">"ប្រើក្ដារចុច​លើអេក្រង់"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"កំណត់រចនាសម្ព័ន្ធ <xliff:g id="DEVICE_NAME">%s</xliff:g>"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"កំណត់រចនាសម្ព័ន្ធ​ក្ដារចុចរូបវន្ត"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"ប៉ះដើម្បីជ្រើសភាសា និងប្លង់"</string>
@@ -1956,7 +1959,7 @@
     <string name="app_blocked_title" msgid="7353262160455028160">"មិនអាច​ប្រើ​កម្មវិធី​នេះបានទេ"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"មិនអាច​ប្រើ <xliff:g id="APP_NAME">%1$s</xliff:g> នៅពេល​នេះ​បានទេ​។"</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"មិនអាចប្រើ <xliff:g id="ACTIVITY">%1$s</xliff:g> បានទេ"</string>
-    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="4483161748582966785">"តម្រូវឱ្យមាន​ការអនុញ្ញាត"</string>
+    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="3805704317624448487">"បានទប់ស្កាត់សំណើសុំការអនុញ្ញាត"</string>
     <string name="app_streaming_blocked_title_for_camera_dialog" msgid="3935701653713853065">"មិនអាចប្រើ​កាមេរ៉ា​បានទេ"</string>
     <string name="app_streaming_blocked_title_for_fingerprint_dialog" msgid="3516853717714141951">"បន្ត​នៅលើ​ទូរសព្ទ"</string>
     <string name="app_streaming_blocked_title_for_microphone_dialog" msgid="544822455127171206">"មិនអាចប្រើ​មីក្រូហ្វូនបានទេ"</string>
@@ -1967,6 +1970,9 @@
     <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"មិនអាចប្រើ​កម្មវិធីនេះ​នៅលើ <xliff:g id="DEVICE">%1$s</xliff:g> របស់អ្នក​នៅពេលនេះ​បានទេ។ សូមសាកល្បងប្រើ​នៅលើ​ឧបករណ៍ Android TV របស់អ្នក​ជំនួសវិញ។"</string>
     <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"មិនអាចប្រើ​កម្មវិធីនេះ​នៅលើ <xliff:g id="DEVICE">%1$s</xliff:g> របស់អ្នក​នៅពេលនេះ​បានទេ។ សូមសាកល្បងប្រើ​នៅលើ​ថេប្លេត​របស់អ្នក​ជំនួសវិញ។"</string>
     <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"មិនអាចប្រើ​កម្មវិធីនេះ​នៅលើ <xliff:g id="DEVICE">%1$s</xliff:g> របស់អ្នក​នៅពេលនេះ​បានទេ។ សូមសាកល្បងប្រើ​នៅលើ​ទូរសព្ទរបស់អ្នក​ជំនួសវិញ។"</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tv" msgid="4706276040125072077">"កម្មវិធីនេះកំពុងស្នើសុំការអនុញ្ញាតបន្ថែម ប៉ុន្តែមិនអាចផ្តល់ការអនុញ្ញាតក្នុងវគ្គផ្សាយបានទេ។ ផ្តល់ការអនុញ្ញាតនៅលើឧបករណ៍ Android TV របស់អ្នកជាមុនសិន។"</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tablet" msgid="1824604581465771629">"កម្មវិធីនេះកំពុងស្នើសុំការអនុញ្ញាតបន្ថែម ប៉ុន្តែមិនអាចផ្តល់ការអនុញ្ញាតក្នុងវគ្គផ្សាយបានទេ។ ផ្តល់ការអនុញ្ញាតនៅលើថេប្លេតរបស់អ្នកជាមុនសិន។"</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="default" msgid="7755223160363292105">"កម្មវិធីនេះកំពុងស្នើសុំការអនុញ្ញាតបន្ថែម ប៉ុន្តែមិនអាចផ្តល់ការអនុញ្ញាតក្នុងវគ្គផ្សាយបានទេ។ ផ្តល់ការអនុញ្ញាតនៅលើទូរសព្ទរបស់អ្នកជាមុនសិន។"</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"កម្មវិធីនេះ​កំពុងស្នើសុំ​សុវត្ថិភាពបន្ថែម។ សូមសាកល្បងប្រើ​នៅលើ​ឧបករណ៍ Android TV របស់អ្នក​ជំនួសវិញ។"</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"កម្មវិធីនេះ​កំពុងស្នើសុំ​សុវត្ថិភាពបន្ថែម។ សូមសាកល្បងប្រើ​នៅលើ​ថេប្លេត​របស់អ្នក​ជំនួសវិញ។"</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"កម្មវិធីនេះ​កំពុងស្នើសុំ​សុវត្ថិភាពបន្ថែម។ សូមសាកល្បងប្រើ​នៅលើ​ទូរសព្ទរបស់អ្នក​ជំនួសវិញ។"</string>
diff --git a/core/res/res/values-kn/strings.xml b/core/res/res/values-kn/strings.xml
index 1f0cbfc..9eb8516 100644
--- a/core/res/res/values-kn/strings.xml
+++ b/core/res/res/values-kn/strings.xml
@@ -590,6 +590,8 @@
     <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"ನೋಂದಾಯಿತ ಅಪ್ಲಿಕೇಶನ್ ಗುರುತಿಸುವಿಕೆಗಳು ಮತ್ತು ಮಾರ್ಗ ಗಮ್ಯಸ್ಥಾನಗಳಂತಹ ಆದ್ಯತೆಯ NFC ಪಾವತಿ ಸೇವೆಗಳ ಬಗ್ಗೆ ಮಾಹಿತಿಯನ್ನು ಪಡೆಯಲು ಅಪ್ಲಿಕೇಶನ್‌ಗೆ ಅನುಮತಿಸುತ್ತದೆ."</string>
     <string name="permlab_nfc" msgid="1904455246837674977">"ಸಮೀಪ ಕ್ಷೇತ್ರ ಸಂವಹನವನ್ನು ನಿಯಂತ್ರಿಸಿ"</string>
     <string name="permdesc_nfc" msgid="8352737680695296741">"ಸಮೀಪದ ಕ್ಷೇತ್ರ ಸಂವಹನ (NFC) ಟ್ಯಾಗ್‌ಗಳು, ಕಾರ್ಡ್‌ಗಳು, ಮತ್ತು ಓದುಗರನ್ನು ಅಪ್ಲಿಕೇಶನ್‌ ಅನುಮತಿಸುತ್ತದೆ."</string>
+    <string name="permlab_nfcTransactionEvent" msgid="5868209446710407679">"ಸುರಕ್ಷಿತ ಅಂಶದ ವಹಿವಾಟು ಈವೆಂಟ್"</string>
+    <string name="permdesc_nfcTransactionEvent" msgid="1904286701876487397">"ಸುರಕ್ಷಿತ ಅಂಶದಲ್ಲಿ ನಡೆಯುತ್ತಿರುವ ವಹಿವಾಟುಗಳ ಕುರಿತು ಮಾಹಿತಿಯನ್ನು ಸ್ವೀಕರಿಸಲು ಆ್ಯಪ್‌ಗೆ ಅನುಮತಿಸುತ್ತದೆ."</string>
     <string name="permlab_disableKeyguard" msgid="3605253559020928505">"ನಿಮ್ಮ ಸ್ಕ್ರೀನ್ ಲಾಕ್ ಅನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಿ"</string>
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"ಕೀಲಾಕ್ ಮತ್ತು ಯಾವುದೇ ಸಂಬಂಧಿತ ಭದ್ರತಾ ಪಾಸ್‍‍ವರ್ಡ್ ಭದ್ರತೆಯನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲು ಅಪ್ಲಿಕೇಶನ್‍‍ಗೆ ಅನುಮತಿ ನೀಡುತ್ತದೆ. ಉದಾಹರಣೆಗೆ, ಒಳಬರುವ ಕರೆಯನ್ನು ಸ್ವೀಕರಿಸುವಾಗ ಕೀಲಾಕ್ ಅನ್ನು ಫೋನ್ ನಿಷ್ಕ್ರಿಯಗೊಳಿಸುತ್ತದೆ, ನಂತರ ಕರೆಯು ಅಂತ್ಯಗೊಂಡಾಗ ಕೀಲಾಕ್ ಅನ್ನು ಮರು ಸಕ್ರಿಯಗೊಳಿಸುತ್ತದೆ."</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"ಸ್ಕ್ರೀನ್ ಲಾಕ್ ಸಂಕೀರ್ಣತೆಯನ್ನು ವಿನಂತಿಸಿ"</string>
@@ -620,6 +622,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"ಬಯೋಮೆಟ್ರಿಕ್ ಹಾರ್ಡ್‌ವೇರ್‌ ಲಭ್ಯವಿಲ್ಲ"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"ಪ್ರಮಾಣೀಕರಣವನ್ನು ರದ್ದುಗೊಳಿಸಲಾಗಿದೆ"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"ಗುರುತಿಸಲಾಗಿಲ್ಲ"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"ಮುಖವನ್ನು ಗುರುತಿಸಲಾಗಿಲ್ಲ"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"ಪ್ರಮಾಣೀಕರಣವನ್ನು ರದ್ದುಗೊಳಿಸಲಾಗಿದೆ"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"ಪಿನ್, ಪ್ಯಾಟರ್ನ್ ಅಥವಾ ಪಾಸ್‌ವರ್ಡ್ ಸೆಟ್ ಮಾಡಿಲ್ಲ"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"ದೃಢೀಕರಿಸುವಾಗ ದೋಷ ಎದುರಾಗಿದೆ"</string>
@@ -1393,7 +1396,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"ನಿರಾಕರಿಸಿ"</string>
     <string name="select_input_method" msgid="3971267998568587025">"ಇನ್‌ಪುಟ್‌‌ ವಿಧಾನವನ್ನು ಆರಿಸಿ"</string>
     <string name="show_ime" msgid="6406112007347443383">"ಭೌತಿಕ ಕೀಬೋರ್ಡ್ ಸಕ್ರಿಯವಾಗಿರುವಾಗ ಅದನ್ನು ಪರದೆಯ ಮೇಲಿರಿಸಿ"</string>
-    <string name="hardware" msgid="1800597768237606953">"ವರ್ಚುವಲ್ ಕೀಬೋರ್ಡ್ ತೋರಿಸಿ"</string>
+    <string name="hardware" msgid="3611039921284836033">"ಆನ್-ಸ್ಕ್ರೀನ್ ಕೀಬೋರ್ಡ್ ಬಳಸಿ"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"<xliff:g id="DEVICE_NAME">%s</xliff:g> ಕಾನ್ಫಿಗರ್ ಮಾಡಿ"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"ಭೌತಿಕ ಕೀಬೋರ್ಡ್‌ಗಳನ್ನು ಕಾನ್ಫಿಗರ್ ಮಾಡಿ"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"ಭಾಷೆ ಮತ್ತು ವಿನ್ಯಾಸವನ್ನು ಆಯ್ಕೆ ಮಾಡಲು ಟ್ಯಾಪ್ ಮಾಡಿ"</string>
@@ -1956,7 +1959,7 @@
     <string name="app_blocked_title" msgid="7353262160455028160">"ಆ್ಯಪ್ ಲಭ್ಯವಿಲ್ಲ"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> ಇದೀಗ ಲಭ್ಯವಿಲ್ಲ."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> ಲಭ್ಯವಿಲ್ಲ"</string>
-    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="4483161748582966785">"ಅನುಮತಿಯ ಅಗತ್ಯವಿದೆ"</string>
+    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="3805704317624448487">"ಅನುಮತಿ ವಿನಂತಿಯನ್ನು ನಿಗ್ರಹಿಸಲಾಗಿದೆ"</string>
     <string name="app_streaming_blocked_title_for_camera_dialog" msgid="3935701653713853065">"ಕ್ಯಾಮರಾ ಲಭ್ಯವಿಲ್ಲ"</string>
     <string name="app_streaming_blocked_title_for_fingerprint_dialog" msgid="3516853717714141951">"ಫೋನ್‌ನಲ್ಲಿ ಮುಂದುವರಿಸಿ"</string>
     <string name="app_streaming_blocked_title_for_microphone_dialog" msgid="544822455127171206">"ಮೈಕ್ರೊಫೋನ್ ಲಭ್ಯವಿಲ್ಲ"</string>
@@ -1967,6 +1970,9 @@
     <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"ಈ ಸಮಯದಲ್ಲಿ ನಿಮ್ಮ <xliff:g id="DEVICE">%1$s</xliff:g> ನಲ್ಲಿ ಇದನ್ನು ಪ್ರವೇಶಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ. ಅದರ ಬದಲು ನಿಮ್ಮ Android TV ಸಾಧನದಲ್ಲಿ ಪ್ರಯತ್ನಿಸಿ."</string>
     <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"ಈ ಸಮಯದಲ್ಲಿ ನಿಮ್ಮ <xliff:g id="DEVICE">%1$s</xliff:g> ನಲ್ಲಿ ಇದನ್ನು ಪ್ರವೇಶಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ. ಅದರ ಬದಲು ನಿಮ್ಮ ಟ್ಯಾಬ್ಲೆಟ್‌ನಲ್ಲಿ ಪ್ರಯತ್ನಿಸಿ."</string>
     <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"ಈ ಸಮಯದಲ್ಲಿ ನಿಮ್ಮ <xliff:g id="DEVICE">%1$s</xliff:g> ನಲ್ಲಿ ಇದನ್ನು ಪ್ರವೇಶಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ. ಅದರ ಬದಲು ನಿಮ್ಮ ಫೋನ್‌ನಲ್ಲಿ ಪ್ರಯತ್ನಿಸಿ."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tv" msgid="4706276040125072077">"ಈ ಆ್ಯಪ್ ಹೆಚ್ಚುವರಿ ಅನುಮತಿಗಳಿಗಾಗಿ ವಿನಂತಿಸುತ್ತಿದೆ, ಆದರೆ ಸ್ಟ್ರೀಮಿಂಗ್ ಸೆಶನ್‌ನಲ್ಲಿ ಅನುಮತಿಗಳನ್ನು ನೀಡಲಾಗುವುದಿಲ್ಲ. ಮೊದಲು ನಿಮ್ಮ Android TV ಸಾಧನದಲ್ಲಿ ಅನುಮತಿಯನ್ನು ನೀಡಿ."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tablet" msgid="1824604581465771629">"ಈ ಆ್ಯಪ್ ಹೆಚ್ಚುವರಿ ಅನುಮತಿಗಳಿಗಾಗಿ ವಿನಂತಿಸುತ್ತಿದೆ, ಆದರೆ ಸ್ಟ್ರೀಮಿಂಗ್ ಸೆಶನ್‌ನಲ್ಲಿ ಅನುಮತಿಗಳನ್ನು ನೀಡಲಾಗುವುದಿಲ್ಲ. ಮೊದಲು ನಿಮ್ಮ ಟ್ಯಾಬ್ಲೆಟ್‌ನಲ್ಲಿ ಅನುಮತಿ ನೀಡಿ."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="default" msgid="7755223160363292105">"ಈ ಆ್ಯಪ್ ಹೆಚ್ಚುವರಿ ಅನುಮತಿಗಳಿಗಾಗಿ ವಿನಂತಿಸುತ್ತಿದೆ, ಆದರೆ ಸ್ಟ್ರೀಮಿಂಗ್ ಸೆಶನ್‌ನಲ್ಲಿ ಅನುಮತಿಗಳನ್ನು ನೀಡಲಾಗುವುದಿಲ್ಲ. ಮೊದಲು ನಿಮ್ಮ ಫೋನ್‌ನಲ್ಲಿ ಅನುಮತಿ ನೀಡಿ."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"ಈ ಆ್ಯಪ್ ಹೆಚ್ಚುವರಿ ಭದ್ರತೆಯನ್ನು ವಿನಂತಿಸುತ್ತಿದೆ. ಅದರ ಬದಲು ನಿಮ್ಮ Android TV ಸಾಧನದಲ್ಲಿ ಪ್ರಯತ್ನಿಸಿ."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"ಈ ಆ್ಯಪ್ ಹೆಚ್ಚುವರಿ ಭದ್ರತೆಯನ್ನು ವಿನಂತಿಸುತ್ತಿದೆ. ಅದರ ಬದಲು ನಿಮ್ಮ ಟ್ಯಾಬ್ಲೆಟ್‌ನಲ್ಲಿ ಪ್ರಯತ್ನಿಸಿ."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"ಈ ಆ್ಯಪ್ ಹೆಚ್ಚುವರಿ ಭದ್ರತೆಯನ್ನು ವಿನಂತಿಸುತ್ತಿದೆ. ಅದರ ಬದಲು ನಿಮ್ಮ ಫೋನ್‌ನಲ್ಲಿ ಪ್ರಯತ್ನಿಸಿ."</string>
diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml
index 6da3465..83510e7 100644
--- a/core/res/res/values-ko/strings.xml
+++ b/core/res/res/values-ko/strings.xml
@@ -590,6 +590,8 @@
     <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"앱이 등록된 AID와 경로 목적지 같은 기본 NFC 결제 서비스 정보를 확인하도록 허용합니다."</string>
     <string name="permlab_nfc" msgid="1904455246837674977">"NFC(Near Field Communication) 제어"</string>
     <string name="permdesc_nfc" msgid="8352737680695296741">"앱이 NFC(근거리 무선 통신) 태그, 카드 및 리더와 통신할 수 있도록 허용합니다."</string>
+    <string name="permlab_nfcTransactionEvent" msgid="5868209446710407679">"보안 요소 트랜잭션 이벤트"</string>
+    <string name="permdesc_nfcTransactionEvent" msgid="1904286701876487397">"앱이 보안 요소에서 발생하는 트랜잭션에 관한 정보를 받도록 허용합니다."</string>
     <string name="permlab_disableKeyguard" msgid="3605253559020928505">"화면 잠금 사용 중지"</string>
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"앱이 키 잠금 및 관련 비밀번호 보안을 사용중지할 수 있도록 허용합니다. 예를 들어, 휴대전화가 수신전화를 받을 때 키 잠금을 사용중지했다가 통화가 끝나면 키 잠금을 다시 사용할 수 있습니다."</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"화면 잠금 복잡도 요청"</string>
@@ -620,6 +622,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"생체 인식 하드웨어를 사용할 수 없음"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"인증이 취소되었습니다."</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"인식할 수 없음"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"얼굴을 인식할 수 없습니다."</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"인증이 취소되었습니다."</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"PIN, 패턴, 비밀번호가 설정되지 않음"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"인증 오류"</string>
@@ -1393,7 +1396,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"거부"</string>
     <string name="select_input_method" msgid="3971267998568587025">"입력 방법 선택"</string>
     <string name="show_ime" msgid="6406112007347443383">"물리적 키보드가 활성 상태인 경우 화면에 켜 둠"</string>
-    <string name="hardware" msgid="1800597768237606953">"가상 키보드 표시"</string>
+    <string name="hardware" msgid="3611039921284836033">"터치 키보드 사용"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"<xliff:g id="DEVICE_NAME">%s</xliff:g> 설정"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"실제 키보드 설정"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"탭하여 언어와 레이아웃을 선택하세요."</string>
@@ -1956,7 +1959,7 @@
     <string name="app_blocked_title" msgid="7353262160455028160">"앱을 사용할 수 없습니다"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"현재 <xliff:g id="APP_NAME">%1$s</xliff:g> 앱을 사용할 수 없습니다."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> 사용할 수 없음"</string>
-    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="4483161748582966785">"권한이 필요함"</string>
+    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="3805704317624448487">"권한 요청 거부됨"</string>
     <string name="app_streaming_blocked_title_for_camera_dialog" msgid="3935701653713853065">"카메라를 사용할 수 없음"</string>
     <string name="app_streaming_blocked_title_for_fingerprint_dialog" msgid="3516853717714141951">"휴대전화에서 진행하기"</string>
     <string name="app_streaming_blocked_title_for_microphone_dialog" msgid="544822455127171206">"마이크를 사용할 수 없음"</string>
@@ -1967,6 +1970,9 @@
     <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"현재 <xliff:g id="DEVICE">%1$s</xliff:g>에서 액세스할 수 없습니다. 대신 Android TV 기기에서 시도해 보세요."</string>
     <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"현재 <xliff:g id="DEVICE">%1$s</xliff:g>에서 액세스할 수 없습니다. 대신 태블릿에서 시도해 보세요."</string>
     <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"현재 <xliff:g id="DEVICE">%1$s</xliff:g>에서 액세스할 수 없습니다. 대신 스마트폰에서 시도해 보세요."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tv" msgid="4706276040125072077">"앱에서 추가 보안을 요청합니다. 그러나 스트리밍 세션에서는 권한을 부여할 수 없습니다. Android TV 기기에서 먼저 권한을 부여하세요."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tablet" msgid="1824604581465771629">"앱에서 추가 보안을 요청합니다. 그러나 스트리밍 세션에서는 권한을 부여할 수 없습니다. 태블릿에서 먼저 권한을 부여하세요."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="default" msgid="7755223160363292105">"앱에서 추가 보안을 요청합니다. 그러나 스트리밍 세션에서는 권한을 부여할 수 없습니다. 휴대전화에서 먼저 권한을 부여하세요."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"앱에서 추가 보안을 요청합니다. 대신 Android TV 기기에서 시도해 보세요."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"앱에서 추가 보안을 요청합니다. 대신 태블릿에서 시도해 보세요."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"앱에서 추가 보안을 요청합니다. 대신 휴대전화에서 시도해 보세요."</string>
diff --git a/core/res/res/values-ky/strings.xml b/core/res/res/values-ky/strings.xml
index 9fe0dfe..7f63669 100644
--- a/core/res/res/values-ky/strings.xml
+++ b/core/res/res/values-ky/strings.xml
@@ -590,6 +590,8 @@
     <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"Колдонмого катталган жардам же көздөлгөн жерге маршрут сыяктуу тандалган nfc төлөм кызматы жөнүндө маалыматты алууга уруксат берүү."</string>
     <string name="permlab_nfc" msgid="1904455246837674977">"Near Field Communication көзөмөлү"</string>
     <string name="permdesc_nfc" msgid="8352737680695296741">"Колдонмого Жакынкы аралыкта байланышуу (NFC) белгилери, карталары жана окугучтары менен байланышуу мүмкүнчүлүгүн берет."</string>
+    <string name="permlab_nfcTransactionEvent" msgid="5868209446710407679">"Коргоочу элементтеги транзакция иш-чарасы"</string>
+    <string name="permdesc_nfcTransactionEvent" msgid="1904286701876487397">"Колдонмо Коргоочу элементте аткарылган транзакциялар жөнүндө маалымат ала алат."</string>
     <string name="permlab_disableKeyguard" msgid="3605253559020928505">"экранды бөгөттөөнү өчүрүү"</string>
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"Колдонмого экрандын бөгөттөөчү жана ага байланыштуу сырсөз коргоосун өчүрүү уруксатын берет. Мисалы, чалуу келгенде экрандын бөгөтүн алып салат, чалуу бүткөндө кайрадан орнотот."</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"экранды бөгөттөөнүн татаалдык деңгээлин суроо"</string>
@@ -620,6 +622,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Биометрикалык аппарат жеткиликсиз"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Аныктыгын текшерүү жокко чыгарылды"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Таанылган жок"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Жүз таанылган жок"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Аныктыгын текшерүү жокко чыгарылды"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"PIN код, графикалык ачкыч же сырсөз коюлган жок"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Аутентификация катасы"</string>
@@ -1393,7 +1396,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"ЧЕТКЕ КАГУУ"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Дайын киргизүү ыкмасын тандаңыз"</string>
     <string name="show_ime" msgid="6406112007347443383">"Баскычтоп иштетилгенде экранда көрүнүп турат"</string>
-    <string name="hardware" msgid="1800597768237606953">"Виртуалдык баскычтоп"</string>
+    <string name="hardware" msgid="3611039921284836033">"Экрандагы баскычтопту колдонуу"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"<xliff:g id="DEVICE_NAME">%s</xliff:g> түзмөгүн конфигурациялоо"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Физикалык баскычтопторду конфигурациялоо"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Тил жана калып тандоо үчүн таптап коюңуз"</string>
@@ -1956,7 +1959,7 @@
     <string name="app_blocked_title" msgid="7353262160455028160">"Колдонмо учурда жеткиликсиз"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> учурда жеткиликсиз"</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> жеткиликсиз"</string>
-    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="4483161748582966785">"Уруксат керек"</string>
+    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="3805704317624448487">"Уруксат берүү сурамы четке кагылды"</string>
     <string name="app_streaming_blocked_title_for_camera_dialog" msgid="3935701653713853065">"Камера жеткиликсиз"</string>
     <string name="app_streaming_blocked_title_for_fingerprint_dialog" msgid="3516853717714141951">"Телефондон улантуу"</string>
     <string name="app_streaming_blocked_title_for_microphone_dialog" msgid="544822455127171206">"Микрофон жеткиликсиз"</string>
@@ -1967,6 +1970,9 @@
     <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"Учурда буга <xliff:g id="DEVICE">%1$s</xliff:g> түзмөгүңүздөн кире албайсыз. Android TV түзмөгүңүздөн аракет кылып көрүңүз."</string>
     <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"Учурда буга <xliff:g id="DEVICE">%1$s</xliff:g> түзмөгүңүздөн кире албайсыз. Планшетиңизден кирип көрүңүз."</string>
     <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"Учурда буга <xliff:g id="DEVICE">%1$s</xliff:g> түзмөгүңүздөн кире албайсыз. Анын ордуна телефондон кирип көрүңүз."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tv" msgid="4706276040125072077">"Бул колдонмо кошумча уруксаттарды берүүнү суранып жатат, бирок алып ойнотуу сеансында уруксаттарды берүүгө болбойт. Адегенде Android TV түзмөгүндө уруксат бериңиз."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tablet" msgid="1824604581465771629">"Бул колдонмо кошумча уруксаттарды берүүнү суранып жатат, бирок алып ойнотуу сеансында уруксаттарды берүүгө болбойт. Адегенде планшетте уруксат бериңиз."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="default" msgid="7755223160363292105">"Бул колдонмо кошумча уруксаттарды берүүнү суранып жатат, бирок алып ойнотуу сеансында уруксаттарды берүүгө болбойт. Адегенде телефондо уруксат бериңиз."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"Бул колдонмо кошумча коопсуздукту иштетүүнү суранып жатат. Android TV түзмөгүңүздөн аракет кылып көрүңүз."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"Бул колдонмо кошумча коопсуздукту иштетүүнү суранып жатат. Планшетиңизден кирип көрүңүз."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"Бул колдонмо кошумча коопсуздукту иштетүүнү суранып жатат. Анын ордуна телефондон кирип көрүңүз."</string>
diff --git a/core/res/res/values-lo/strings.xml b/core/res/res/values-lo/strings.xml
index 15517b5..d078881 100644
--- a/core/res/res/values-lo/strings.xml
+++ b/core/res/res/values-lo/strings.xml
@@ -590,6 +590,8 @@
     <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"ອະນຸຍາດໃຫ້ແອັບຮັບຂໍ້ມູນບໍລິການການຈ່າຍເງິນ NFC ທີ່ຕ້ອງການໄດ້ ເຊັ່ນ: ການຊ່ວຍເຫຼືອແບບລົງທະບຽນ ແລະ ປາຍທາງເສັ້ນທາງ."</string>
     <string name="permlab_nfc" msgid="1904455246837674977">"ຄວບຄຸມ Near Field Communication"</string>
     <string name="permdesc_nfc" msgid="8352737680695296741">"ອະນຸຍາດໃຫ້ແອັບຯຕິດຕໍ່ສື່ສານກັບປ້າຍກຳກັບ, ບັດ ແລະໂຕອ່ານຂອງການສື່ສານໄລຍະສັ້ນ (NFC)."</string>
+    <string name="permlab_nfcTransactionEvent" msgid="5868209446710407679">"ເຫດການທຸລະກໍາອົງປະກອບຄວາມປອດໄພ"</string>
+    <string name="permdesc_nfcTransactionEvent" msgid="1904286701876487397">"ອະນຸຍາດໃຫ້ແອັບຮັບຂໍ້ມູນກ່ຽວກັບທຸລະກໍາທີ່ເກີດຂຶ້ນຢູ່ໃນອົງປະກອບທີ່ປອດໄພ."</string>
     <string name="permlab_disableKeyguard" msgid="3605253559020928505">"ປິດການລັອກໜ້າຈໍ"</string>
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"ອະນຸຍາດໃຫ້ແອັບຯປິດການເຮັດວຽກຂອງປຸ່ມລັອກ ແລະລະບົບຄວາມປອດໄພຂອງລະຫັດຜ່ານທີ່ເຊື່ອມໂຍງກັນ. ໂຕຢ່າງ: ໂທລະສັບຈະປິດການເຮັດວຽກຂອງປຸ່ມລັອກເມື່ອມີສາຍໂທເຂົ້າ ຈາກນັ້ນຈຶ່ງເປີດໃຊ້ໄດ້ອີກເມື່ອວາງສາຍແລ້ວ."</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"ຮ້ອງຂໍຄວາມຊັບຊ້ອນການລັອກໜ້າຈໍ"</string>
@@ -620,6 +622,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"ຮາດແວຊີວະມິຕິບໍ່ສາມາດໃຊ້ໄດ້"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"ຍົກເລີກການຮັບຮອງຄວາມຖືກຕ້ອງແລ້ວ"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"ບໍ່ຮັບຮູ້"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"ບໍ່ສາມາດຈຳແນກໜ້າໄດ້"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"ຍົກເລີກການຮັບຮອງຄວາມຖືກຕ້ອງແລ້ວ"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"ບໍ່ໄດ້ຕັ້ງ PIN, ຮູບແບບປົດລັອກ ຫຼື ລະຫັດຜ່ານ"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"ເກີດຄວາມຜິດພາດໃນການພິສູດຢືນຢັນ"</string>
@@ -1393,7 +1396,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"ປະຕິເສດ"</string>
     <string name="select_input_method" msgid="3971267998568587025">"ເລືອກຮູບແບບການປ້ອນ"</string>
     <string name="show_ime" msgid="6406112007347443383">"ເປີດໃຊ້ໃຫ້ມັນຢູ່ໃນໜ້າຈໍໃນຂະນະທີ່ໃຊ້ແປ້ນພິມພາຍນອກຢູ່"</string>
-    <string name="hardware" msgid="1800597768237606953">"ສະແດງແປ້ນພິມສະເໝືອນ"</string>
+    <string name="hardware" msgid="3611039921284836033">"ໃຊ້ແປ້ນພິມໃນໜ້າຈໍ"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"ຕັ້ງຄ່າ <xliff:g id="DEVICE_NAME">%s</xliff:g>"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"ຕັ້ງຄ່າແປ້ນພິມແທ້"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"ແຕະເພື່ອເລືອກພາສາ ແລະ ໂຄງແປ້ນພິມ"</string>
@@ -1956,7 +1959,7 @@
     <string name="app_blocked_title" msgid="7353262160455028160">"ແອັບບໍ່ສາມາດໃຊ້ໄດ້"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> ບໍ່ສາມາດໃຊ້ໄດ້ໃນຕອນນີ້."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"ບໍ່ສາມາດໃຊ້ <xliff:g id="ACTIVITY">%1$s</xliff:g> ໄດ້"</string>
-    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="4483161748582966785">"ຕ້ອງມີການອະນຸຍາດ"</string>
+    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="3805704317624448487">"ຄຳຮ້ອງຂໍການອະນຸຍາດຖືກລະງັບ"</string>
     <string name="app_streaming_blocked_title_for_camera_dialog" msgid="3935701653713853065">"ກ້ອງຖ່າຍຮູບບໍ່ສາມາດໃຊ້ໄດ້"</string>
     <string name="app_streaming_blocked_title_for_fingerprint_dialog" msgid="3516853717714141951">"ສືບຕໍ່ຢູ່ໂທລະສັບ"</string>
     <string name="app_streaming_blocked_title_for_microphone_dialog" msgid="544822455127171206">"ໄມໂຄຣໂຟນບໍ່ສາມາດໃຊ້ໄດ້"</string>
@@ -1967,6 +1970,9 @@
     <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"ບໍ່ສາມາດເຂົ້າເຖິງແອັບນີ້ໄດ້ຢູ່ <xliff:g id="DEVICE">%1$s</xliff:g> ຂອງທ່ານໃນຕອນນີ້. ກະລຸນາລອງໃຊ້ຢູ່ອຸປະກອນ Android TV ຂອງທ່ານແທນ."</string>
     <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"ບໍ່ສາມາດເຂົ້າເຖິງແອັບນີ້ໄດ້ຢູ່ <xliff:g id="DEVICE">%1$s</xliff:g> ຂອງທ່ານໃນຕອນນີ້. ກະລຸນາລອງຢູ່ແທັບເລັດຂອງທ່ານແທນ."</string>
     <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"ບໍ່ສາມາດເຂົ້າເຖິງແອັບນີ້ໄດ້ຢູ່ <xliff:g id="DEVICE">%1$s</xliff:g> ຂອງທ່ານໃນຕອນນີ້. ກະລຸນາລອງຢູ່ໂທລະສັບຂອງທ່ານແທນ."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tv" msgid="4706276040125072077">"ແອັບນີ້ກຳລັງຮ້ອງຂໍການອະນຸຍາດເພີ່ມເຕີມ, ແຕ່ບໍ່ສາມາດໃຫ້ການອະນຸຍາດໃນເຊດຊັນການສະຕຣີມໄດ້. ກະລຸນາໃຫ້ການອະນຸຍາດໃນອຸປະກອນ Android TV ຂອງທ່ານກ່ອນ."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tablet" msgid="1824604581465771629">"ແອັບນີ້ກຳລັງຮ້ອງຂໍການອະນຸຍາດເພີ່ມເຕີມ, ແຕ່ບໍ່ສາມາດໃຫ້ການອະນຸຍາດໃນເຊດຊັນການສະຕຣີມໄດ້. ກະລຸນາໃຫ້ການອະນຸຍາດໃນແທັບເລັດຂອງທ່ານກ່ອນ."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="default" msgid="7755223160363292105">"ແອັບນີ້ກຳລັງຮ້ອງຂໍການອະນຸຍາດເພີ່ມເຕີມ, ແຕ່ບໍ່ສາມາດໃຫ້ການອະນຸຍາດໃນເຊດຊັນການສະຕຣີມໄດ້. ກະລຸນາໃຫ້ການອະນຸຍາດໃນໂທລະສັບຂອງທ່ານກ່ອນ."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"ແອັບນີ້ກຳລັງຮ້ອງຂໍຄວາມປອດໄພເພີ່ມເຕີມ. ກະລຸນາລອງໃຊ້ຢູ່ອຸປະກອນ Android TV ຂອງທ່ານແທນ."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"ແອັບນີ້ກຳລັງຮ້ອງຂໍຄວາມປອດໄພເພີ່ມເຕີມ. ກະລຸນາລອງຢູ່ແທັບເລັດຂອງທ່ານແທນ."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"ແອັບນີ້ກຳລັງຮ້ອງຂໍຄວາມປອດໄພເພີ່ມເຕີມ. ກະລຸນາລອງຢູ່ໂທລະສັບຂອງທ່ານແທນ."</string>
diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml
index 6a12170..33e23da 100644
--- a/core/res/res/values-lt/strings.xml
+++ b/core/res/res/values-lt/strings.xml
@@ -592,6 +592,8 @@
     <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"Programai leidžiama gauti pageidaujamą ARL mokamos paslaugos informaciją, pvz., užregistruotą pagalbą ir maršrutų tikslus."</string>
     <string name="permlab_nfc" msgid="1904455246837674977">"valdyti artimo lauko perdavimą (angl. „Near Field Communication“)"</string>
     <string name="permdesc_nfc" msgid="8352737680695296741">"Leidžiama programai perduoti artimojo lauko ryšių technologijos (ALR) žymas, korteles ir skaitymo programas."</string>
+    <string name="permlab_nfcTransactionEvent" msgid="5868209446710407679">"Saugos elemento operacijos įvykis"</string>
+    <string name="permdesc_nfcTransactionEvent" msgid="1904286701876487397">"Leidžiama programai gauti informaciją apie operacijas, vykstančias saugos elemente."</string>
     <string name="permlab_disableKeyguard" msgid="3605253559020928505">"išjungti ekrano užraktą"</string>
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"Leidžiama programai neleisti klavišo užrakto ir visos susijusios slaptažodžio apsaugos. Pvz., telefonas neleidžia klavišo užrakto priimant gaunamąjį skambutį ir pakartotinai jį įgalina, kai skambutis baigiamas."</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"pateikti ekrano užrakto sudėtingumo užklausą"</string>
@@ -622,6 +624,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Biometrinė aparatinė įranga nepasiekiama"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Autentifikavimas atšauktas"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Neatpažinta"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Veidas neatpažintas"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Autentifikavimas atšauktas"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"Nenustatytas PIN kodas, atrakinimo piešinys arba slaptažodis"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Autentifikuojant įvyko klaida"</string>
@@ -1395,7 +1398,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"ATMESTI"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Pasirinkite įvesties metodą"</string>
     <string name="show_ime" msgid="6406112007347443383">"Palikti ekrane, kol fizinė klaviatūra aktyvi"</string>
-    <string name="hardware" msgid="1800597768237606953">"Rodyti virtualiąją klaviatūrą"</string>
+    <string name="hardware" msgid="3611039921284836033">"Ekrano klaviatūros naudojimas"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"„<xliff:g id="DEVICE_NAME">%s</xliff:g>“ konfigūravimas"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Fizinių klaviatūrų konfigūravimas"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Palieskite, kad pasirinktumėte kalbą ir išdėstymą"</string>
@@ -1958,7 +1961,7 @@
     <string name="app_blocked_title" msgid="7353262160455028160">"Programa nepasiekiama."</string>
     <string name="app_blocked_message" msgid="542972921087873023">"Programa „<xliff:g id="APP_NAME">%1$s</xliff:g>“ šiuo metu nepasiekiama."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"„<xliff:g id="ACTIVITY">%1$s</xliff:g>“ nepasiekiama"</string>
-    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="4483161748582966785">"Reikalingas leidimas"</string>
+    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="3805704317624448487">"Leidimo užklausa atmesta"</string>
     <string name="app_streaming_blocked_title_for_camera_dialog" msgid="3935701653713853065">"Kamera nepasiekiama"</string>
     <string name="app_streaming_blocked_title_for_fingerprint_dialog" msgid="3516853717714141951">"Tęsti telefone"</string>
     <string name="app_streaming_blocked_title_for_microphone_dialog" msgid="544822455127171206">"Mikrofonas nepasiekiamas"</string>
@@ -1969,6 +1972,9 @@
     <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"Šįkart nepavyksta pasiekti programos iš jūsų „<xliff:g id="DEVICE">%1$s</xliff:g>“. Pabandykite naudoti „Android TV“ įrenginį."</string>
     <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"Šįkart nepavyksta pasiekti programos iš jūsų „<xliff:g id="DEVICE">%1$s</xliff:g>“. Pabandykite naudoti planšetinį kompiuterį."</string>
     <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"Šįkart nepavyksta pasiekti programos iš jūsų „<xliff:g id="DEVICE">%1$s</xliff:g>“. Pabandykite naudoti telefoną."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tv" msgid="4706276040125072077">"Ši programa prašo papildomų leidimų, bet jų negalima suteikti per srautinio perdavimo seansą. Leidimą pirmiausia suteikite „Android TV“ įrenginyje."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tablet" msgid="1824604581465771629">"Ši programa prašo papildomų leidimų, bet jų negalima suteikti per srautinio perdavimo seansą. Leidimą pirmiausia suteikite planšetiniame kompiuteryje."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="default" msgid="7755223160363292105">"Ši programa prašo papildomų leidimų, bet jų negalima suteikti per srautinio perdavimo seansą. Leidimą pirmiausia suteikite telefone."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"Ši programa prašo papildomų saugos funkcijų. Pabandykite naudoti „Android TV“ įrenginį."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"Ši programa prašo papildomų saugos funkcijų. Pabandykite naudoti planšetinį kompiuterį."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"Ši programa prašo papildomų saugos funkcijų. Pabandykite naudoti telefoną."</string>
diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml
index fade02e..346aa30 100644
--- a/core/res/res/values-lv/strings.xml
+++ b/core/res/res/values-lv/strings.xml
@@ -591,6 +591,8 @@
     <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"Ļauj lietotnei iegūt informāciju par vēlamo NFC maksājumu pakalpojumu, piemēram, par reģistrētajiem lietojumprogrammu ID un maršruta galamērķi."</string>
     <string name="permlab_nfc" msgid="1904455246837674977">"kontrolē tuvlauka saziņu"</string>
     <string name="permdesc_nfc" msgid="8352737680695296741">"Ļauj lietotnei sazināties ar tuva darbības lauka sakaru (Near Field Communication — NFC) atzīmēm, kartēm un lasītājiem."</string>
+    <string name="permlab_nfcTransactionEvent" msgid="5868209446710407679">"Darījums, kas apstrādājams, izmantojot drošības elementu"</string>
+    <string name="permdesc_nfcTransactionEvent" msgid="1904286701876487397">"Ļauj lietotnei saņemt informāciju par darījumiem, kas apstrādājami, izmantojot drošības elementu."</string>
     <string name="permlab_disableKeyguard" msgid="3605253559020928505">"atspējot ekrāna bloķēšanu"</string>
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"Ļauj lietotnei atspējot taustiņslēgu un visu saistīto paroļu drošību. Piemēram, tālrunis atspējo taustiņslēgu, saņemot ienākošu zvanu, un pēc zvana pabeigšanas atkārtoti iespējo taustiņslēgu."</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"ekrāna bloķēšanas sarežģītības pakāpes informācijas pieprasījums"</string>
@@ -621,6 +623,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Biometrisko datu aparatūra nav pieejama"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Autentifikācija ir atcelta"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Dati nav atpazīti"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Seja netika atpazīta"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Autentifikācija ir atcelta"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"PIN, kombinācija vai parole nav iestatīta"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Autentifikācijas kļūda"</string>
@@ -1394,7 +1397,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"NORAIDĪT"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Ievades metodes izvēle"</string>
     <string name="show_ime" msgid="6406112007347443383">"Paturēt ekrānā, kamēr ir aktīva fiziskā tastatūra"</string>
-    <string name="hardware" msgid="1800597768237606953">"Virtuālās tastatūras rādīšana"</string>
+    <string name="hardware" msgid="3611039921284836033">"Izmantojiet ekrāna tastatūru"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"Jākonfigurē <xliff:g id="DEVICE_NAME">%s</xliff:g>"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Konfigurējiet fiziskās tastatūras"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Pieskarieties, lai atlasītu valodu un izkārtojumu"</string>
@@ -1957,7 +1960,7 @@
     <string name="app_blocked_title" msgid="7353262160455028160">"Lietotne nav pieejama"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"Lietotne <xliff:g id="APP_NAME">%1$s</xliff:g> pašlaik nav pieejama."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> nav pieejams"</string>
-    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="4483161748582966785">"Nepieciešama atļauja"</string>
+    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="3805704317624448487">"Atļaujas pieprasījums bloķēts"</string>
     <string name="app_streaming_blocked_title_for_camera_dialog" msgid="3935701653713853065">"Kamera nav pieejama"</string>
     <string name="app_streaming_blocked_title_for_fingerprint_dialog" msgid="3516853717714141951">"Turpiniet tālrunī"</string>
     <string name="app_streaming_blocked_title_for_microphone_dialog" msgid="544822455127171206">"Mikrofons nav pieejams"</string>
@@ -1968,6 +1971,9 @@
     <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"Šajā ierīcē (<xliff:g id="DEVICE">%1$s</xliff:g>) pašlaik nevar piekļūt šai lietotnei. Mēģiniet tai piekļūt savā Android TV ierīcē."</string>
     <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"Šajā ierīcē (<xliff:g id="DEVICE">%1$s</xliff:g>) pašlaik nevar piekļūt šai lietotnei. Mēģiniet tai piekļūt savā planšetdatorā."</string>
     <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"Šajā ierīcē (<xliff:g id="DEVICE">%1$s</xliff:g>) pašlaik nevar piekļūt šai lietotnei. Mēģiniet tai piekļūt savā tālrunī."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tv" msgid="4706276040125072077">"Šī lietotne pieprasa papildu atļaujas, taču atļaujas nevar piešķirt straumēšanas sesijā. Vispirms piešķiriet attiecīgo atļauju savā Android TV ierīcē."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tablet" msgid="1824604581465771629">"Šī lietotne pieprasa papildu atļaujas, taču atļaujas nevar piešķirt straumēšanas sesijā. Vispirms piešķiriet attiecīgo atļauju savā planšetdatorā."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="default" msgid="7755223160363292105">"Šī lietotne pieprasa papildu atļaujas, taču atļaujas nevar piešķirt straumēšanas sesijā. Vispirms piešķiriet attiecīgo atļauju savā tālrunī."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"Šī lietotne pieprasa papildu drošību. Mēģiniet tai piekļūt savā Android TV ierīcē."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"Šī lietotne pieprasa papildu drošību. Mēģiniet tai piekļūt savā planšetdatorā."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"Šī lietotne pieprasa papildu drošību. Mēģiniet tai piekļūt savā tālrunī."</string>
diff --git a/core/res/res/values-mk/strings.xml b/core/res/res/values-mk/strings.xml
index 4fb53a7..05246ce 100644
--- a/core/res/res/values-mk/strings.xml
+++ b/core/res/res/values-mk/strings.xml
@@ -590,6 +590,8 @@
     <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"Дозволува апликацијата да добие информации за претпочитаната услуга за плаќање преку NFC, како регистрирани помагала и дестинација на маршрутата."</string>
     <string name="permlab_nfc" msgid="1904455246837674977">"контролирај комуникација на блиско поле"</string>
     <string name="permdesc_nfc" msgid="8352737680695296741">"Дозволува апликацијата да комуницира со ознаки, картички и читачи за Комуникација при непосредна близина (NFC)."</string>
+    <string name="permlab_nfcTransactionEvent" msgid="5868209446710407679">"Трансакциски настан на безбедносен елемент"</string>
+    <string name="permdesc_nfcTransactionEvent" msgid="1904286701876487397">"Овозможува апликацијата да прима податоци за трансакциите што се случуваат на безбедносен елемент."</string>
     <string name="permlab_disableKeyguard" msgid="3605253559020928505">"оневозможи заклучување на екран"</string>
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"Овозможува апликацијата да го оневозможи заклучувањето и каква било безбедност поврзана со лозинка. На пример, телефонот го оневозможува заклучувањето при прием на телефонски повик, а потоа повторно го овозможува заклучувањето кога повикот ќе заврши."</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"побарува сложеност за заклучувањето на екранот"</string>
@@ -620,6 +622,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Биометрискиот хардвер е недостапен"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Проверката е откажана"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Непознат"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Ликот не е препознаен"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Проверката е откажана"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"Не е поставен PIN, шема или лозинка"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Грешка при проверката"</string>
@@ -1393,7 +1396,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"ОДБИЈ"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Одбери метод на внес"</string>
     <string name="show_ime" msgid="6406112007347443383">"Прикажувај ја на екранот додека е активна физичката тастатура"</string>
-    <string name="hardware" msgid="1800597768237606953">"Прикажи виртуелна тастатура"</string>
+    <string name="hardware" msgid="3611039921284836033">"Користете тастатура на екран"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"Конфигурирање на <xliff:g id="DEVICE_NAME">%s</xliff:g>"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Конфигурирање физички тастатури"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Допрете за избирање јазик и распоред"</string>
@@ -1956,7 +1959,7 @@
     <string name="app_blocked_title" msgid="7353262160455028160">"Апликацијата не е достапна"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> не е достапна во моментов."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> е недостапна"</string>
-    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="4483161748582966785">"Потребна е дозвола"</string>
+    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="3805704317624448487">"Барањето за дозвола е потиснато"</string>
     <string name="app_streaming_blocked_title_for_camera_dialog" msgid="3935701653713853065">"Камерата е недостапна"</string>
     <string name="app_streaming_blocked_title_for_fingerprint_dialog" msgid="3516853717714141951">"Продолжете на телефонот"</string>
     <string name="app_streaming_blocked_title_for_microphone_dialog" msgid="544822455127171206">"Микрофонот не е достапен"</string>
@@ -1967,6 +1970,9 @@
     <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"Ова не може да се отвори на <xliff:g id="DEVICE">%1$s</xliff:g> во моментов. Пробајте на вашиот Android TV како алтернатива."</string>
     <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"Ова не може да се отвори на <xliff:g id="DEVICE">%1$s</xliff:g> во моментов. Пробајте на вашиот таблет како алтернатива."</string>
     <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"Ова не може да се отвори на <xliff:g id="DEVICE">%1$s</xliff:g> во моментов. Пробајте на вашиот телефон како алтернатива."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tv" msgid="4706276040125072077">"Апликацијава бара дополнителни дозволи, но нив не може да ги доделите во сесија за стриминг. Прво доделете ја дозволата на вашиот уред со Android TV."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tablet" msgid="1824604581465771629">"Апликацијава бара дополнителни дозволи, но нив не може да ги доделите во сесија за стриминг. Прво доделете ја дозволата на вашиот таблет."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="default" msgid="7755223160363292105">"Апликацијава бара дополнителни дозволи, но нив не може да ги доделите во сесија за стриминг. Прво доделете ја дозволата на вашиот телефон."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"Апликацијава бара дополнителна безбедност. Пробајте на вашиот Android TV како алтернатива."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"Апликацијава бара дополнителна безбедност. Пробајте на вашиот таблет како алтернатива."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"Апликацијава бара дополнителна безбедност. Пробајте на вашиот телефон како алтернатива."</string>
diff --git a/core/res/res/values-ml/strings.xml b/core/res/res/values-ml/strings.xml
index c3b86e3..392a580 100644
--- a/core/res/res/values-ml/strings.xml
+++ b/core/res/res/values-ml/strings.xml
@@ -590,6 +590,8 @@
     <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"റൂട്ട് ലക്ഷ്യസ്ഥാനം, രജിസ്‌റ്റർ ചെയ്തിരിക്കുന്ന സഹായങ്ങൾ എന്നിവ പോലുള്ള, തിരഞ്ഞെടുത്ത NFC പേയ്‌മെന്റ് സേവനത്തെ സംബന്ധിച്ച വിവരങ്ങൾ ലഭിക്കാൻ ആപ്പിനെ അനുവദിക്കുന്നു."</string>
     <string name="permlab_nfc" msgid="1904455246837674977">"സമീപ ഫീൽഡുമായുള്ള ആശയവിനിമയം നിയന്ത്രിക്കുക"</string>
     <string name="permdesc_nfc" msgid="8352737680695296741">"നിയർ ഫീൽഡ് കമ്മ്യൂണിക്കേഷൻ (NFC) ടാഗുകളുമായും കാർഡുകളുമായും റീഡറുകളുമായുള്ള ആശയവിനിമയത്തിന് അപ്ലിക്കേഷനുകളെ അനുവദിക്കുന്നു."</string>
+    <string name="permlab_nfcTransactionEvent" msgid="5868209446710407679">"സുരക്ഷിത ഘടക ഇടപാട് ഇവന്റ്"</string>
+    <string name="permdesc_nfcTransactionEvent" msgid="1904286701876487397">"ഒരു സുരക്ഷിത ഘടകത്തിൽ നടക്കുന്ന ഇടപാടുകളെ കുറിച്ചുള്ള വിവരങ്ങൾ നേടാൻ ആപ്പിനെ അനുവദിക്കുന്നു."</string>
     <string name="permlab_disableKeyguard" msgid="3605253559020928505">"നിങ്ങളുടെ സ്‌ക്രീൻ ലോക്ക് പ്രവർത്തനരഹിതമാക്കുക"</string>
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"കീലോക്കും ഏതെങ്കിലും അനുബന്ധ പാസ്‌വേഡ് സുരക്ഷയും പ്രവർത്തനരഹിതമാക്കാൻ അപ്ലിക്കേഷനെ അനുവദിക്കുന്നു. ഉദാഹരണത്തിന്, ഒരു ഇൻകമിംഗ് കോൾ സ്വീകരിക്കുമ്പോൾ ഫോൺ കീലോക്ക് പ്രവർത്തനരഹിതമാക്കുന്നു, കോൾ അവസാനിക്കുമ്പോൾ കീലോക്ക് വീണ്ടും പ്രവർത്തനക്ഷമമാകുന്നു."</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"സ്‌ക്രീൻ ലോക്ക് സങ്കീർണ്ണത അഭ്യർത്ഥിക്കുക"</string>
@@ -620,6 +622,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"ബയോമെട്രിക് ഹാർ‌ഡ്‌വെയർ ലഭ്യമല്ല"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"പരിശോധിച്ചുറപ്പിക്കൽ റദ്ദാക്കി"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"തിരിച്ചറിഞ്ഞില്ല"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"മുഖം തിരിച്ചറിഞ്ഞിട്ടില്ല"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"പരിശോധിച്ചുറപ്പിക്കൽ റദ്ദാക്കി"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"പിന്നോ പാറ്റേണോ പാസ്‌വേഡോ സജ്ജീകരിച്ചിട്ടില്ല"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"പിശക് പരിശോധിച്ചുറപ്പിക്കുന്നു"</string>
@@ -1393,7 +1396,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"നിരസിക്കുക"</string>
     <string name="select_input_method" msgid="3971267998568587025">"ഇൻപുട്ട് രീതി തിരഞ്ഞെടുക്കുക"</string>
     <string name="show_ime" msgid="6406112007347443383">"ഫിസിക്കൽ കീബോർഡ് സജീവമായിരിക്കുമ്പോൾ സ്ക്രീനിൽ നിലനിർത്തുക"</string>
-    <string name="hardware" msgid="1800597768237606953">"വെർച്വൽ കീബോർഡ് കാണിക്കുക"</string>
+    <string name="hardware" msgid="3611039921284836033">"ഓൺ-സ്ക്രീൻ കീബോർഡ് ഉപയോഗിക്കൂ"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"<xliff:g id="DEVICE_NAME">%s</xliff:g> കോൺഫിഗർ ചെയ്യുക"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"യഥാർത്ഥ കീബോർഡുകൾ കോൺഫിഗർ ചെയ്യുക"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"ഭാഷയും ലേഔട്ടും തിരഞ്ഞെടുക്കുന്നതിന് ടാപ്പ് ചെയ്യുക"</string>
@@ -1956,7 +1959,7 @@
     <string name="app_blocked_title" msgid="7353262160455028160">"ആപ്പ് ലഭ്യമല്ല"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> ഇപ്പോൾ ലഭ്യമല്ല."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> ലഭ്യമല്ല"</string>
-    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="4483161748582966785">"അനുമതി ആവശ്യമാണ്"</string>
+    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="3805704317624448487">"അനുമതി അഭ്യർത്ഥന നിയന്ത്രിച്ചു"</string>
     <string name="app_streaming_blocked_title_for_camera_dialog" msgid="3935701653713853065">"ക്യാമറ ലഭ്യമല്ല"</string>
     <string name="app_streaming_blocked_title_for_fingerprint_dialog" msgid="3516853717714141951">"ഫോണിൽ തുടരുക"</string>
     <string name="app_streaming_blocked_title_for_microphone_dialog" msgid="544822455127171206">"മൈക്രോഫോൺ ലഭ്യമല്ല"</string>
@@ -1967,6 +1970,9 @@
     <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"ഇപ്പോൾ നിങ്ങളുടെ <xliff:g id="DEVICE">%1$s</xliff:g> ഉപകരണത്തിൽ ഇത് ആക്‌സസ് ചെയ്യാനാകില്ല. പകരം Android TV ഉപകരണത്തിൽ ശ്രമിച്ച് നോക്കൂ."</string>
     <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"ഇപ്പോൾ നിങ്ങളുടെ <xliff:g id="DEVICE">%1$s</xliff:g> ഉപകരണത്തിൽ ഇത് ആക്‌സസ് ചെയ്യാനാകില്ല. പകരം നിങ്ങളുടെ ടാബ്‌ലെറ്റിൽ ശ്രമിച്ച് നോക്കൂ."</string>
     <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"ഇപ്പോൾ നിങ്ങളുടെ <xliff:g id="DEVICE">%1$s</xliff:g> ഉപകരണത്തിൽ ഇത് ആക്‌സസ് ചെയ്യാനാകില്ല. പകരം നിങ്ങളുടെ ഫോണിൽ ശ്രമിച്ച് നോക്കൂ."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tv" msgid="4706276040125072077">"ഈ ആപ്പ് അധിക അനുമതികൾ അഭ്യർത്ഥിക്കുന്നു, എന്നാൽ സ്‌ട്രീമിംഗ് സെഷനിടയിൽ അനുമതികൾ നൽകാനാകില്ല. ആദ്യം നിങ്ങളുടെ Android TV ഉപകരണത്തിൽ അനുമതി നൽകുക."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tablet" msgid="1824604581465771629">"ഈ ആപ്പ് അധിക അനുമതികൾ അഭ്യർത്ഥിക്കുന്നു, എന്നാൽ സ്‌ട്രീമിംഗ് സെഷനിടയിൽ അനുമതികൾ നൽകാനാകില്ല. ആദ്യം നിങ്ങളുടെ ടാബ്‌ലെറ്റിൽ അനുമതി നൽകുക."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="default" msgid="7755223160363292105">"ഈ ആപ്പ് അധിക അനുമതികൾ അഭ്യർത്ഥിക്കുന്നു, എന്നാൽ സ്‌ട്രീമിംഗ് സെഷനിടയിൽ അനുമതികൾ നൽകാനാകില്ല. ആദ്യം നിങ്ങളുടെ ഫോണിൽ അനുമതി നൽകുക."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"ഈ ആപ്പ് അധിക സുരക്ഷ അഭ്യർത്ഥിക്കുന്നു. പകരം നിങ്ങളുടെ Android TV ഉപകരണത്തിൽ ശ്രമിച്ച് നോക്കൂ."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"ഈ ആപ്പ് അധിക സുരക്ഷ അഭ്യർത്ഥിക്കുന്നു. പകരം നിങ്ങളുടെ ടാബ്‌ലെറ്റിൽ ശ്രമിച്ച് നോക്കൂ."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"ഈ ആപ്പ് അധിക സുരക്ഷ അഭ്യർത്ഥിക്കുന്നു. പകരം നിങ്ങളുടെ ഫോണിൽ ശ്രമിച്ച് നോക്കൂ."</string>
diff --git a/core/res/res/values-mn/strings.xml b/core/res/res/values-mn/strings.xml
index b69fb83..6cd4424 100644
--- a/core/res/res/values-mn/strings.xml
+++ b/core/res/res/values-mn/strings.xml
@@ -590,6 +590,8 @@
     <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"Бүртгүүлсэн төхөөрөмж болон маршрутын хүрэх цэг зэрэг сонгосон nfc төлбөрийн үйлчилгээний мэдээллийг авахыг аппад зөвшөөрдөг."</string>
     <string name="permlab_nfc" msgid="1904455246837674977">"ойролцоо талбарын холбоог удирдах"</string>
     <string name="permdesc_nfc" msgid="8352737680695296741">"Апп нь Ойролцоо Талбарын Холболт(NFC) таг, карт, болон уншигчтай холбогдох боломжтой."</string>
+    <string name="permlab_nfcTransactionEvent" msgid="5868209446710407679">"Аюулгүй элементийн гүйлгээний үйл явдал"</string>
+    <string name="permdesc_nfcTransactionEvent" msgid="1904286701876487397">"Аппад Аюулгүй элементэд хийгдэж буй гүйлгээний талаарх мэдээллийг хүлээн авахыг зөвшөөрнө."</string>
     <string name="permlab_disableKeyguard" msgid="3605253559020928505">"дэлгэцний түгжээг идэвхгүй болгох"</string>
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"Апп нь түгжээ болон бусад холбоотой нууц үгийн аюулгүй байдлыг идэвхгүй болгох боломжтой. Жишээ нь бол утас нь дуудлага ирэх үед түгжээг идэвхгүй болгох ба дуудлага дуусахад буцаан идэвхтэй болгодог."</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"дэлгэцийн түгжээний төвөгтэй байдлын хүсэлт тавих"</string>
@@ -620,6 +622,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Биометрийн техник хангамж боломжгүй байна"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Нотолгоог цуцаллаа"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Таниагүй"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Царайг таньсангүй"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Нотолгоог цуцаллаа"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"Тохируулсан пин, хээ эсвэл нууц үг алга"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Баталгаажуулахад алдаа гарлаа"</string>
@@ -1393,7 +1396,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"ТАТГАЛЗАХ"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Оруулах аргыг сонгоно уу"</string>
     <string name="show_ime" msgid="6406112007347443383">"Биет гар идэвхтэй үед үүнийг дэлгэцэд харуулна уу"</string>
-    <string name="hardware" msgid="1800597768237606953">"Хийсвэр гарыг харуулах"</string>
+    <string name="hardware" msgid="3611039921284836033">"Дэлгэц дээрх гарыг ашиглах"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"<xliff:g id="DEVICE_NAME">%s</xliff:g>-г тохируулна уу"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Биет гарыг тохируулна уу"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Хэл болон бүдүүвчийг сонгохын тулд дарна уу"</string>
@@ -1956,7 +1959,7 @@
     <string name="app_blocked_title" msgid="7353262160455028160">"Апп боломжгүй байна"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> яг одоо боломжгүй байна."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> боломжгүй байна"</string>
-    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="4483161748582966785">"Зөвшөөрөл шаардлагатай"</string>
+    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="3805704317624448487">"Зөвшөөрлийн хүсэлтийг зогсоосон"</string>
     <string name="app_streaming_blocked_title_for_camera_dialog" msgid="3935701653713853065">"Камер боломжгүй байна"</string>
     <string name="app_streaming_blocked_title_for_fingerprint_dialog" msgid="3516853717714141951">"Утсан дээр үргэлжлүүлэх"</string>
     <string name="app_streaming_blocked_title_for_microphone_dialog" msgid="544822455127171206">"Микрофон боломжгүй байна"</string>
@@ -1967,6 +1970,9 @@
     <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"Одоогоор үүнд таны <xliff:g id="DEVICE">%1$s</xliff:g> дээрээс хандах боломжгүй. Оронд нь Android TV төхөөрөмж дээрээ туршиж үзнэ үү."</string>
     <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"Одоогоор үүнд таны <xliff:g id="DEVICE">%1$s</xliff:g> дээрээс хандах боломжгүй. Оронд нь таблет дээрээ туршиж үзнэ үү."</string>
     <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"Одоогоор үүнд таны <xliff:g id="DEVICE">%1$s</xliff:g> дээрээс хандах боломжгүй. Оронд нь утсан дээрээ туршиж үзнэ үү."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tv" msgid="4706276040125072077">"Энэ апп нэмэлт зөвшөөрөл хүсэж байгаа ч дамжуулалтын харилцан үйлдлийн үеэр зөвшөөрөл олгох боломжгүй. Эхлээд Android TV төхөөрөмждөө зөвшөөрөл олгоно уу."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tablet" msgid="1824604581465771629">"Энэ апп нэмэлт зөвшөөрөл хүсэж байгаа ч дамжуулалтын харилцан үйлдлийн үеэр зөвшөөрөл олгох боломжгүй. Эхлээд таблетдаа зөвшөөрөл олгоно уу."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="default" msgid="7755223160363292105">"Энэ апп нэмэлт зөвшөөрөл хүсэж байгаа ч дамжуулалтын харилцан үйлдлийн үеэр зөвшөөрөл олгох боломжгүй. Эхлээд утсандаа зөвшөөрөл олгоно уу."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"Энэ апп нэмэлт аюулгүй байдал хүсэж байна. Оронд нь Android TV төхөөрөмж дээрээ туршиж үзнэ үү."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"Энэ апп нэмэлт аюулгүй байдал хүсэж байна. Оронд нь таблет дээрээ туршиж үзнэ үү."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"Энэ апп нэмэлт аюулгүй байдал хүсэж байна. Оронд нь утсан дээрээ туршиж үзнэ үү."</string>
diff --git a/core/res/res/values-mr/strings.xml b/core/res/res/values-mr/strings.xml
index 5f6b35d..fc99355 100644
--- a/core/res/res/values-mr/strings.xml
+++ b/core/res/res/values-mr/strings.xml
@@ -590,6 +590,8 @@
     <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"नोंदणीकृत एड्स आणि मार्ग गंतव्यस्थान सारखी प्राधान्यकृत एनएफसी पेमेंट सेवेची माहिती मिळवण्यासाठी अ‍ॅपला अनुमती देते."</string>
     <string name="permlab_nfc" msgid="1904455246837674977">"फील्ड जवळील कम्युनिकेशन नियंत्रित करा"</string>
     <string name="permdesc_nfc" msgid="8352737680695296741">"फील्ड जवळील कम्युनिकेशन (NFC) टॅग, कार्डे आणि वाचक यांच्यासह संवाद करण्यासाठी ॲपला अनुमती देते."</string>
+    <string name="permlab_nfcTransactionEvent" msgid="5868209446710407679">"सुरक्षित घटक यावरील व्यवहार इव्हेंट"</string>
+    <string name="permdesc_nfcTransactionEvent" msgid="1904286701876487397">"अ‍ॅपला सुरक्षित घटक यावर होत असलेल्या व्यवहारांविषयी माहिती मिळवू देते."</string>
     <string name="permlab_disableKeyguard" msgid="3605253559020928505">"तुमचे स्क्रीन लॉक अक्षम करा"</string>
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"कीलॉक आणि कोणतीही संबद्ध पासवर्ड सुरक्षितता अक्षम करण्यासाठी अ‍ॅप ला अनुमती देते. उदाहरणार्थ, येणारा फोन कॉल प्राप्त करताना फोन कीलॉक अक्षम करतो, नंतर जेव्हा कॉल समाप्त होतो तेव्हा तो कीलॉक पुन्हा-सक्षम करतो."</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"स्क्रीन लॉक क्लिष्टतेची विनंती करा"</string>
@@ -620,6 +622,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"बायोमेट्रिक हार्डवेअर उपलब्ध नाही"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"ऑथेंटिकेशन रद्द केले"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"ओळखले नाही"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"चेहरा ओळखता आला नाही"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"ऑथेंटिकेशन रद्द केले"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"कोणताही पिन, पॅटर्न किंवा पासवर्ड सेट केलेला नाही"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"एरर ऑथेंटिकेट करत आहे"</string>
@@ -1393,7 +1396,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"नकार द्या"</string>
     <string name="select_input_method" msgid="3971267998568587025">"इनपुट पद्धत निवडा"</string>
     <string name="show_ime" msgid="6406112007347443383">"भौतिक कीबोर्ड सक्रिय असताना त्यास स्क्रीनवर ठेवा"</string>
-    <string name="hardware" msgid="1800597768237606953">"व्हर्च्युअल कीबोर्ड दर्शवा"</string>
+    <string name="hardware" msgid="3611039921284836033">"ऑन-स्क्रीन कीबोर्ड वापरा"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"<xliff:g id="DEVICE_NAME">%s</xliff:g> कॉंफिगर करा"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"वास्तविक कीबोर्ड कॉंफिगर करा"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"भाषा आणि लेआउट निवडण्यासाठी टॅप करा"</string>
@@ -1956,7 +1959,7 @@
     <string name="app_blocked_title" msgid="7353262160455028160">"ॲप उपलब्ध नाही"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> आता उपलब्ध नाही."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> उपलब्ध नाही"</string>
-    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="4483161748582966785">"परवानगी आवश्यक आहे"</string>
+    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="3805704317624448487">"परवानगीची विनंती ब्लॉक केली आहे"</string>
     <string name="app_streaming_blocked_title_for_camera_dialog" msgid="3935701653713853065">"कॅमेरा उपलब्ध नाही"</string>
     <string name="app_streaming_blocked_title_for_fingerprint_dialog" msgid="3516853717714141951">"फोनवर पुढे सुरू ठेवा"</string>
     <string name="app_streaming_blocked_title_for_microphone_dialog" msgid="544822455127171206">"मायक्रोफोन उपलब्ध नाही"</string>
@@ -1967,6 +1970,9 @@
     <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"हे यावेळी तुमच्या <xliff:g id="DEVICE">%1$s</xliff:g> वर अ‍ॅक्सेस करू शकत नाही. त्याऐवजी तुमच्या Android TV डिव्हाइसवर अ‍ॅक्सेस करून पहा."</string>
     <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"हे यावेळी तुमच्या <xliff:g id="DEVICE">%1$s</xliff:g> वर अ‍ॅक्सेस करू शकत नाही. त्याऐवजी तुमच्या टॅबलेटवर अ‍ॅक्सेस करून पहा."</string>
     <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"हे यावेळी तुमच्या <xliff:g id="DEVICE">%1$s</xliff:g> वर अ‍ॅक्सेस करू शकत नाही. त्याऐवजी तुमच्या फोनवर अ‍ॅक्सेस करून पहा."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tv" msgid="4706276040125072077">"हे ॲप अतिरिक्त परवानग्यांची विनंती करत आहे, पण स्ट्रीमिंग सेशनमध्ये परवानग्या दिल्या जाऊ शकत नाहीत. आधी तुमच्या Android TV डिव्हाइसवर परवानगी द्या."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tablet" msgid="1824604581465771629">"हे ॲप अतिरिक्त परवानग्यांची विनंती करत आहे, पण स्ट्रीमिंग सेशनमध्ये परवानग्या दिल्या जाऊ शकत नाहीत. आधी तुमच्या टॅबलेटवर परवानगी द्या."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="default" msgid="7755223160363292105">"हे ॲप अतिरिक्त परवानग्यांची विनंती करत आहे, पण स्ट्रीमिंग सेशनमध्ये परवानग्या दिल्या जाऊ शकत नाहीत. आधी तुमच्या फोनवर परवानगी द्या."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"हे अ‍ॅप अतिरिक्त सुरक्षेची विनंती करत आहे. त्याऐवजी तुमच्या Android TV डिव्हाइसवर अ‍ॅक्सेस करून पहा."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"हे अ‍ॅप अतिरिक्त सुरक्षेची विनंती करत आहे. त्याऐवजी तुमच्या टॅबलेटवर अ‍ॅक्सेस करून पहा."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"हे अ‍ॅप अतिरिक्त सुरक्षेची विनंती करत आहे. त्याऐवजी तुमच्या फोनवर अ‍ॅक्सेस करून पहा."</string>
diff --git a/core/res/res/values-ms/strings.xml b/core/res/res/values-ms/strings.xml
index f62c706..b0a82cb 100644
--- a/core/res/res/values-ms/strings.xml
+++ b/core/res/res/values-ms/strings.xml
@@ -590,6 +590,8 @@
     <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"Membenarkan apl mendapatkan maklumat perkhidmatan pembayaran nfc pilihan seperti bantuan berdaftar dan destinasi laluan."</string>
     <string name="permlab_nfc" msgid="1904455246837674977">"mengawal Komunikasi Medan Dekat"</string>
     <string name="permdesc_nfc" msgid="8352737680695296741">"Membenarkan apl berkomunikasi dengan teg, kad dan pembaca Komunikasi Medan Dekat (NFC)."</string>
+    <string name="permlab_nfcTransactionEvent" msgid="5868209446710407679">"Peristiwa transaksi Unsur Selamat"</string>
+    <string name="permdesc_nfcTransactionEvent" msgid="1904286701876487397">"Membenarkan apl menerima maklumat tentang transaksi yang berlaku pada Unsur Selamat."</string>
     <string name="permlab_disableKeyguard" msgid="3605253559020928505">"lumpuhkan kunci skrin anda"</string>
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"Membenarkan apl melumpuhkan kunci kekunci dan sebarang keselamatan kata laluan yang berkaitan. Sebagai contoh, telefon melumpuhkan kunci kekunci apabila menerima panggilan telefon masuk kemudian mendayakan semula kunci kekunci apabila panggilan selesai."</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"minta kerumitan kunci skrin"</string>
@@ -620,6 +622,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Perkakasan biometrik tidak tersedia"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Pengesahan dibatalkan"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Tidak dikenali"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Wajah tidak dikenali"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Pengesahan dibatalkan"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"Pin, corak atau kata laluan tidak ditetapkan"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Ralat semasa membuat pengesahan"</string>
@@ -1393,7 +1396,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"TOLAK"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Pilih kaedah input"</string>
     <string name="show_ime" msgid="6406112007347443383">"Pastikannya pada skrin, semasa papan kekunci fizikal aktif"</string>
-    <string name="hardware" msgid="1800597768237606953">"Tunjukkan papan kekunci maya"</string>
+    <string name="hardware" msgid="3611039921284836033">"Guna papan kekunci pada skrin"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"Konfigurasikan <xliff:g id="DEVICE_NAME">%s</xliff:g>"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Konfigurasikan papan kekunci fizikal"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Ketik untuk memilih bahasa dan reka letak"</string>
@@ -1956,7 +1959,7 @@
     <string name="app_blocked_title" msgid="7353262160455028160">"Apl tidak tersedia"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> tidak tersedia sekarang."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> tidak tersedia"</string>
-    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="4483161748582966785">"Kebenaran diperlukan"</string>
+    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="3805704317624448487">"Permintaan kebenaran disekat"</string>
     <string name="app_streaming_blocked_title_for_camera_dialog" msgid="3935701653713853065">"Kamera tidak tersedia"</string>
     <string name="app_streaming_blocked_title_for_fingerprint_dialog" msgid="3516853717714141951">"Teruskan pada telefon"</string>
     <string name="app_streaming_blocked_title_for_microphone_dialog" msgid="544822455127171206">"Mikrofon tidak tersedia"</string>
@@ -1967,6 +1970,9 @@
     <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"Aplikasi ini tidak boleh diakses pada <xliff:g id="DEVICE">%1$s</xliff:g> anda pada masa ini. Cuba pada peranti Android TV anda."</string>
     <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"Aplikasi ini tidak boleh diakses pada <xliff:g id="DEVICE">%1$s</xliff:g> anda pada masa ini. Cuba pada tablet anda."</string>
     <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"Aplikasi ini tidak boleh diakses pada <xliff:g id="DEVICE">%1$s</xliff:g> anda pada masa ini. Cuba pada telefon anda."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tv" msgid="4706276040125072077">"Apl ini meminta kebenaran tambahan tetapi kebenaran tidak boleh diberikan dalam sesi penstriman. Berikan kebenaran pada peranti Android TV anda dahulu."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tablet" msgid="1824604581465771629">"Apl ini meminta kebenaran tambahan tetapi kebenaran tidak boleh diberikan dalam sesi penstriman. Berikan kebenaran pada tablet anda dahulu."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="default" msgid="7755223160363292105">"Apl ini meminta kebenaran tambahan tetapi kebenaran tidak boleh diberikan dalam sesi penstriman. Berikan kebenaran pada telefon anda dahulu."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"Apl ini meminta keselamatan tambahan. Cuba pada peranti Android TV anda."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"Apl ini meminta keselamatan tambahan. Cuba pada tablet anda."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"Apl ini meminta keselamatan tambahan. Cuba pada telefon anda."</string>
diff --git a/core/res/res/values-my/strings.xml b/core/res/res/values-my/strings.xml
index d2fc38f..cf958db 100644
--- a/core/res/res/values-my/strings.xml
+++ b/core/res/res/values-my/strings.xml
@@ -590,6 +590,8 @@
     <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"အက်ပ်အား ဦစားပေး NFC ငွေပေးချေမှုဆိုင်ရာ ဝန်ဆောင်မှု အချက်အလက်များဖြစ်သည့် မှတ်ပုံတင်ထားသော အကူအညီများနှင့် သွားလာရာ လမ်းကြောင်းတို့ကို ရယူရန် ခွင့်ပြုသည်။"</string>
     <string name="permlab_nfc" msgid="1904455246837674977">"Near Field Communicationအား ထိန်းချုပ်ရန်"</string>
     <string name="permdesc_nfc" msgid="8352737680695296741">"အက်ပ်အား တာတို စက်ကွင်း ဆက်သွယ်ရေး (NFC) တဲဂ်များ၊ ကဒ်များ နှင့် ဖတ်ကြသူတို့နှင့် ဆက်သွယ်ပြောဆိုခွင့် ပြုသည်။"</string>
+    <string name="permlab_nfcTransactionEvent" msgid="5868209446710407679">"‘လုံခြုံရေး အစိတ်အပိုင်း’ ငွေလွှဲပြောင်းမှု အစီအစဉ်"</string>
+    <string name="permdesc_nfcTransactionEvent" msgid="1904286701876487397">"‘လုံခြုံရေး အစိတ်အပိုင်း’ တွင် ဖြစ်ပေါ်နေသည့် ငွေလွှဲပြောင်းမှုများအကြောင်း အချက်အလက်ကိုရယူရန် အက်ပ်အား ခွင့်ပြုနိုင်သည်။"</string>
     <string name="permlab_disableKeyguard" msgid="3605253559020928505">"ဖန်သားပြင် သော့ချခြင်းအား မလုပ်နိုင်အောင် ပိတ်ရန်"</string>
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"အပလီကေးရှင်းအား သော့ချခြင်းနှင့် သက်ဆိုင်ရာ စကားဝှက်သတ်မှတ်ခြင်းများအား မသုံးနိုင်အောင် ပိတ်ခြင်းကို ခွင့်ပြုရန်။ ဥပမာ ဖုန်းလာလျှင် သော့ပိတ်ခြင်း ပယ်ဖျက်ခြင်း၊ ဖုန်းပြောပြီးလျှင် သော့ကို အလိုအလျောက် ပြန်ပိတ်ခြင်း"</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"ဖုန်းမျက်နှာပြင် လော့ခ်ချရန် ရှုပ်ထွေးမှုအဆင့် တောင်းခံခြင်း"</string>
@@ -620,6 +622,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"ဇီဝအချက်အလက်သုံး ကွန်ပျူတာစက်ပစ္စည်း မရရှိနိုင်ပါ"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"အထောက်အထားစိစစ်ခြင်းကို ပယ်ဖျက်လိုက်သည်"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"မသိ"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"မျက်နှာကို မသိရှိပါ"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"အထောက်အထားစိစစ်ခြင်းကို ပယ်ဖျက်လိုက်သည်"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"ပင်နံပါတ်၊ လော့ခ်ပုံစံ သို့မဟုတ် စကားဝှက် သတ်မှတ်မထားပါ"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"အထောက်အထားစိစစ်ရာတွင် အမှားအယွင်းရှိနေသည်"</string>
@@ -1393,7 +1396,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"ငြင်းပယ်ပါ"</string>
     <string name="select_input_method" msgid="3971267998568587025">"ထည့်သွင်းရေး နည်းကို ရွေးရန်"</string>
     <string name="show_ime" msgid="6406112007347443383">"စက်၏ကီးဘုတ် ဖွင့်ထားစဉ်တွင် ၎င်းကို ဖန်သားပြင်ပေါ်တွင် ဆက်ထားပါ"</string>
-    <string name="hardware" msgid="1800597768237606953">"ပကတိအသွင်ကီးဘုတ်ပြရန်"</string>
+    <string name="hardware" msgid="3611039921284836033">"မျက်နှာပြင် လက်ကွက် သုံးခြင်း"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"<xliff:g id="DEVICE_NAME">%s</xliff:g> ကို စီစဉ်သတ်မှတ်ရန်"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"ပကတိကီးဘုတ်များကို စီစဉ်သတ်မှတ်ရန်"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"ဘာသာစကားနှင့် အသွင်အပြင်ရွေးချယ်ရန် တို့ပါ"</string>
@@ -1956,7 +1959,7 @@
     <string name="app_blocked_title" msgid="7353262160455028160">"အက်ပ်ကို မရနိုင်ပါ"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> ကို ယခု မရနိုင်ပါ။"</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> မရနိုင်ပါ"</string>
-    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="4483161748582966785">"ခွင့်ပြုချက်လိုအပ်သည်"</string>
+    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="3805704317624448487">"ခွင့်ပြုချက်တောင်းဆိုမှု ပိတ်ထားသည်"</string>
     <string name="app_streaming_blocked_title_for_camera_dialog" msgid="3935701653713853065">"ကင်မရာ မရနိုင်ပါ"</string>
     <string name="app_streaming_blocked_title_for_fingerprint_dialog" msgid="3516853717714141951">"ဖုန်းပေါ်တွင် ရှေ့ဆက်ပါ"</string>
     <string name="app_streaming_blocked_title_for_microphone_dialog" msgid="544822455127171206">"မိုက်ခရိုဖုန်း မရနိုင်ပါ"</string>
@@ -1967,6 +1970,9 @@
     <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"၎င်းအား ယခု သင့် <xliff:g id="DEVICE">%1$s</xliff:g> တွင် ဝင်၍မရပါ။ ယင်းအစား Android TV စက်တွင် စမ်းကြည့်ပါ။"</string>
     <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"၎င်းအား ယခု သင့် <xliff:g id="DEVICE">%1$s</xliff:g> တွင် ဝင်၍မရပါ။ ယင်းအစား တက်ဘလက်တွင် စမ်းကြည့်ပါ။"</string>
     <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"၎င်းအား ယခု သင့် <xliff:g id="DEVICE">%1$s</xliff:g> တွင် ဝင်၍မရပါ။ ယင်းအစား ဖုန်းတွင် စမ်းကြည့်ပါ။"</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tv" msgid="4706276040125072077">"ဤအက်ပ်သည် နောက်ထပ်ခွင့်ပြုချက်များကို တောင်းဆိုနေသော်လည်း တိုက်ရိုက်လွှင့်စက်ရှင်တွင် ခွင့်ပြုချက်များ ပေး၍မရပါ။ သင့် Android TV စက်တွင် ဦးစွာ ခွင့်ပြုချက်ပေးပါ။"</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tablet" msgid="1824604581465771629">"ဤအက်ပ်သည် နောက်ထပ်ခွင့်ပြုချက်များကို တောင်းဆိုနေသော်လည်း တိုက်ရိုက်လွှင့်စက်ရှင်တွင် ခွင့်ပြုချက်များ ပေး၍မရပါ။ သင့်တက်ဘလက်တွင် ဦးစွာ ခွင့်ပြုချက်ပေးပါ။"</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="default" msgid="7755223160363292105">"ဤအက်ပ်သည် နောက်ထပ်ခွင့်ပြုချက်များကို တောင်းဆိုနေသော်လည်း တိုက်ရိုက်လွှင့်စက်ရှင်တွင် ခွင့်ပြုချက်များ ပေး၍မရပါ။ သင့်ဖုန်းတွင် ဦးစွာ ခွင့်ပြုချက်ပေးပါ။"</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"ဤအက်ပ်က ထပ်ဆောင်းလုံခြုံရေးကို တောင်းဆိုနေသည်။ Android TV စက်တွင် စမ်းကြည့်ပါ။"</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"ဤအက်ပ်က ထပ်ဆောင်းလုံခြုံရေးကို တောင်းဆိုနေသည်။ တက်ဘလက်တွင် စမ်းကြည့်ပါ။"</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"ဤအက်ပ်က ထပ်ဆောင်းလုံခြုံရေးကို တောင်းဆိုနေသည်။ ဖုန်းတွင် စမ်းကြည့်ပါ။"</string>
diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml
index 15ef328..e307d21 100644
--- a/core/res/res/values-nb/strings.xml
+++ b/core/res/res/values-nb/strings.xml
@@ -590,6 +590,8 @@
     <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"Gir appen tilgang til informasjon om prioritert NFC-betalingstjeneste, for eksempel registrerte hjelpemidler og destinasjon."</string>
     <string name="permlab_nfc" msgid="1904455246837674977">"kontroller overføring av data med NFC-teknologi"</string>
     <string name="permdesc_nfc" msgid="8352737680695296741">"Lar appen kommunisere med etiketter, kort og lesere som benytter NFC-teknologi."</string>
+    <string name="permlab_nfcTransactionEvent" msgid="5868209446710407679">"Transaksjonshendelse på sikkert element"</string>
+    <string name="permdesc_nfcTransactionEvent" msgid="1904286701876487397">"Lar appen motta informasjon om transaksjoner som finner sted på et sikkert element."</string>
     <string name="permlab_disableKeyguard" msgid="3605253559020928505">"deaktivere skjermlåsen"</string>
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"Lar appen deaktivere tastelåsen og eventuell tilknyttet passordsikkerhet. Et eksempel er at telefonen deaktiverer tastelåsen når du mottar et innkommende anrop, og deretter aktiverer tastelåsen igjen når samtalen er ferdig."</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"be om skjermlåsens kompleksitet"</string>
@@ -620,6 +622,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Biometrisk maskinvare er utilgjengelig"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Autentiseringen er avbrutt"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Ikke gjenkjent"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Gjenkjenner ikke ansiktet"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Autentiseringen er avbrutt"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"PIN-kode, mønster eller passord er ikke angitt"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Feil under autentiseringen"</string>
@@ -1393,7 +1396,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"AVSLÅ"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Velg inndatametode"</string>
     <string name="show_ime" msgid="6406112007347443383">"Ha den på skjermen mens det fysiske tastaturet er aktivt"</string>
-    <string name="hardware" msgid="1800597768237606953">"Vis det virtuelle tastaturet"</string>
+    <string name="hardware" msgid="3611039921284836033">"Bruk skjermtastaturet"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"Konfigurer <xliff:g id="DEVICE_NAME">%s</xliff:g>"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Konfigurer de fysiske tastaturene"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Trykk for å velge språk og layout"</string>
@@ -1956,7 +1959,7 @@
     <string name="app_blocked_title" msgid="7353262160455028160">"Appen er ikke tilgjengelig"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> er ikke tilgjengelig for øyeblikket."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> er utilgjengelig"</string>
-    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="4483161748582966785">"Du må gi tillatelse"</string>
+    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="3805704317624448487">"Forespørselen om tillatelse er skjult"</string>
     <string name="app_streaming_blocked_title_for_camera_dialog" msgid="3935701653713853065">"Kameraet er utilgjengelig"</string>
     <string name="app_streaming_blocked_title_for_fingerprint_dialog" msgid="3516853717714141951">"Fortsett på telefonen"</string>
     <string name="app_streaming_blocked_title_for_microphone_dialog" msgid="544822455127171206">"Mikrofonen er utilgjengelig"</string>
@@ -1967,6 +1970,9 @@
     <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"Dette er ikke tilgjengelig på <xliff:g id="DEVICE">%1$s</xliff:g> for øyeblikket. Prøv på Android TV-enheten din i stedet."</string>
     <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"Dette er ikke tilgjengelig på <xliff:g id="DEVICE">%1$s</xliff:g> for øyeblikket. Prøv på nettbrettet ditt i stedet."</string>
     <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"Dette er ikke tilgjengelig på <xliff:g id="DEVICE">%1$s</xliff:g> for øyeblikket. Prøv på telefonen din i stedet."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tv" msgid="4706276040125072077">"Denne appen ber om flere tillatelser, men du kan ikke gi tillatelser i en strømmeøkt. Gi tillatelsen på Android TV-enheten først."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tablet" msgid="1824604581465771629">"Denne appen ber om flere tillatelser, men du kan ikke gi tillatelser i en strømmeøkt. Gi tillatelsen på nettbrettet først."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="default" msgid="7755223160363292105">"Denne appen ber om flere tillatelser, men du kan ikke gi tillatelser i en strømmeøkt. Gi tillatelsen på telefonen først."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"Denne appen ber om ekstra sikkerhet. Prøv på Android TV-enheten din i stedet."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"Denne appen ber om ekstra sikkerhet. Prøv på nettbrettet ditt i stedet."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"Denne appen ber om ekstra sikkerhet. Prøv på telefonen din i stedet."</string>
diff --git a/core/res/res/values-ne/strings.xml b/core/res/res/values-ne/strings.xml
index 92ee0a1..ef82242 100644
--- a/core/res/res/values-ne/strings.xml
+++ b/core/res/res/values-ne/strings.xml
@@ -590,6 +590,8 @@
     <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"यसले एपलाई दर्ता गरिएका सहायता तथा मार्गको गन्तव्य जस्ता रुचाइएका NFC भुक्तानी सेवासम्बन्धी जानकारी प्राप्त गर्न दिन्छ।"</string>
     <string name="permlab_nfc" msgid="1904455246837674977">"नजिक क्षेत्र संचार नियन्त्रणहरू"</string>
     <string name="permdesc_nfc" msgid="8352737680695296741">"एपलाई नयाँ क्षेत्र संचार (NFC) ट्यागहरू, कार्डहरू र पाठकहरूसँग अन्तर्क्रिया गर्न अनुमति दिन्छ।"</string>
+    <string name="permlab_nfcTransactionEvent" msgid="5868209446710407679">"\"सुरक्षित तत्त्व\" मा भएको कारोबार"</string>
+    <string name="permdesc_nfcTransactionEvent" msgid="1904286701876487397">"यो विकल्प छनौट गर्नाले एपलाई \"सुरक्षित तत्त्व\" मा भएका कारोबारहरूका बारेमा जानकारी प्राप्त गर्ने अनुमति दिन्छ।"</string>
     <string name="permlab_disableKeyguard" msgid="3605253559020928505">"स्क्रिन लक असक्षम पार्नुहोस्"</string>
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"कुनै सम्बन्धित पासवर्ड सुरक्षा र किलकलाई असक्षम पार्न एपलाई अनुमति दिन्छ। उदाहरणको लागि, अन्तर्गमन फोन कल प्राप्त गर्दा फोनले किलकलाई असक्षम पार्छ, त्यसपछि कल सकिएको बेला किलक पुनःसक्षम पार्छ।"</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"स्क्रिन लकको जटिलतासम्बन्धी जानकारी प्राप्त गर्ने अनुरोध गर्नुहोस्"</string>
@@ -620,6 +622,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"बायोमेट्रिक हार्डवेयर उपलब्ध छैन"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"प्रमाणीकरण रद्द गरियो"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"पहिचान भएन"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"अनुहार पहिचान गर्न सकिएन"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"प्रमाणीकरण रद्द गरियो"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"कुनै पनि PIN, ढाँचा वा पासवर्ड सेट गरिएको छैन"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"प्रमाणित गर्ने क्रममा त्रुटि भयो"</string>
@@ -1393,7 +1396,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"अस्वीकार गर्नुहोस्"</string>
     <string name="select_input_method" msgid="3971267998568587025">"निवेश विधि छान्नुहोस्"</string>
     <string name="show_ime" msgid="6406112007347443383">"फिजिकल किबोर्ड सक्रिय हुँदा यसलाई स्क्रिनमा राखियोस्"</string>
-    <string name="hardware" msgid="1800597768237606953">"भर्चुअल किबोर्ड देखाउनुहोस्"</string>
+    <string name="hardware" msgid="3611039921284836033">"अनस्क्रिन किबोर्ड प्रयोग गर्नुहोस्"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"<xliff:g id="DEVICE_NAME">%s</xliff:g> कन्फिगर गर्नुहोस्"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"भौतिक किबोर्डहरू कन्फिगर गर्नुहोस्"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"भाषा र लेआउट चयन गर्न ट्याप गर्नुहोस्"</string>
@@ -1956,7 +1959,7 @@
     <string name="app_blocked_title" msgid="7353262160455028160">"एप उपलब्ध छैन"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> अहिले उपलब्ध छैन।"</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> उपलब्ध छैन"</string>
-    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="4483161748582966785">"अनुमति चाहिन्छ"</string>
+    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="3805704317624448487">"अनुमतिसम्बन्धी अनुरोध रद्द गरिएको छ"</string>
     <string name="app_streaming_blocked_title_for_camera_dialog" msgid="3935701653713853065">"क्यामेरा उपलब्ध छैन"</string>
     <string name="app_streaming_blocked_title_for_fingerprint_dialog" msgid="3516853717714141951">"फोनमा जारी राख्नुहोस्"</string>
     <string name="app_streaming_blocked_title_for_microphone_dialog" msgid="544822455127171206">"माइक्रोफोन उपलब्ध छैन"</string>
@@ -1967,6 +1970,9 @@
     <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"यस बखत तपाईंको <xliff:g id="DEVICE">%1$s</xliff:g> मा यो एप स्ट्रिम गर्न मिल्दैन। बरु तपाईंको Android TV डिभाइसमा स्ट्रिम गरी हेर्नुहोस्।"</string>
     <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"यस बखत तपाईंको <xliff:g id="DEVICE">%1$s</xliff:g> मा यो एप स्ट्रिम गर्न मिल्दैन। बरु तपाईंको ट्याब्लेटमा स्ट्रिम गरी हेर्नुहोस्।"</string>
     <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"यस बखत तपाईंको <xliff:g id="DEVICE">%1$s</xliff:g> मा यो एप स्ट्रिम गर्न मिल्दैन। बरु तपाईंको फोनमा स्ट्रिम गरी हेर्नुहोस्।"</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tv" msgid="4706276040125072077">"यो एपले अतिरिक्त अनुमति मागिरहेको छ तर स्ट्रिमिङ सत्रमा अनुमति दिन मिल्दैन। सर्वप्रथम आफ्नो Android TV डिभाइसमा अनुमति दिनुहोस्।"</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tablet" msgid="1824604581465771629">"यो एपले अतिरिक्त अनुमति मागिरहेको छ तर स्ट्रिमिङ सत्रमा अनुमति दिन मिल्दैन। सर्वप्रथम आफ्नो ट्याब्लेटमा अनुमति दिनुहोस्।"</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="default" msgid="7755223160363292105">"यो एपले अतिरिक्त अनुमति मागिरहेको छ तर स्ट्रिमिङ सत्रमा अनुमति दिन मिल्दैन। सर्वप्रथम आफ्नो फोनमा अनुमति दिनुहोस्।"</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"यो एपले सुरक्षासम्बन्धी अतिरिक्त सुविधा अन गर्न अनुरोध गरिरहेको छ। बरु तपाईंको Android TV डिभाइसमा स्ट्रिम गरी हेर्नुहोस्।"</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"यो एपले सुरक्षासम्बन्धी अतिरिक्त सुविधा अन गर्न अनुरोध गरिरहेको छ। बरु तपाईंको ट्याब्लेटमा स्ट्रिम गरी हेर्नुहोस्।"</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"यो एपले सुरक्षासम्बन्धी अतिरिक्त सुविधा अन गर्न अनुरोध गरिरहेको छ। बरु तपाईंको फोनमा स्ट्रिम गरी हेर्नुहोस्।"</string>
diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml
index 80082c8..966d42e 100644
--- a/core/res/res/values-nl/strings.xml
+++ b/core/res/res/values-nl/strings.xml
@@ -590,6 +590,8 @@
     <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"Hiermee kun je zorgen dat de app informatie krijgt over de voorkeursservice voor NFC-betaling, zoals geregistreerde hulpmiddelen en routebestemmingen."</string>
     <string name="permlab_nfc" msgid="1904455246837674977">"Near Field Communication regelen"</string>
     <string name="permdesc_nfc" msgid="8352737680695296741">"Hiermee kan de app communiceren met NFC-tags (Near Field Communication), kaarten en lezers."</string>
+    <string name="permlab_nfcTransactionEvent" msgid="5868209446710407679">"Secure Element-transactiegebeurtenis"</string>
+    <string name="permdesc_nfcTransactionEvent" msgid="1904286701876487397">"Staat toe dat de app informatie krijgt over transacties die plaatsvinden in een Secure Element."</string>
     <string name="permlab_disableKeyguard" msgid="3605253559020928505">"je schermvergrendeling uitzetten"</string>
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"Hiermee kan de app de toetsenblokkering en bijbehorende wachtwoordbeveiliging uitzetten. Zo kan de telefoon de toetsenblokkering uitzetten als je wordt gebeld en de toetsenblokkering weer aanzetten als het gesprek is beëindigd."</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"complexiteit van schermvergrendeling opvragen"</string>
@@ -620,6 +622,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Biometrische hardware niet beschikbaar"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Verificatie geannuleerd"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Niet herkend"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Gezicht niet herkend"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Verificatie geannuleerd"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"Geen pincode, patroon of wachtwoord ingesteld"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Fout bij verificatie"</string>
@@ -1393,7 +1396,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"WEIGEREN"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Invoermethode selecteren"</string>
     <string name="show_ime" msgid="6406112007347443383">"Toon op het scherm terwijl het fysieke toetsenbord actief is"</string>
-    <string name="hardware" msgid="1800597768237606953">"Virtueel toetsenbord tonen"</string>
+    <string name="hardware" msgid="3611039921284836033">"Schermtoetsenbord gebruiken"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"<xliff:g id="DEVICE_NAME">%s</xliff:g> instellen"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Fysieke toetsenborden instellen"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Tik om een taal en indeling te selecteren"</string>
@@ -1956,7 +1959,7 @@
     <string name="app_blocked_title" msgid="7353262160455028160">"App is niet beschikbaar"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> is momenteel niet beschikbaar."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> niet beschikbaar"</string>
-    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="4483161748582966785">"Rechten vereist"</string>
+    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="3805704317624448487">"Verzoek om rechten onderdrukt"</string>
     <string name="app_streaming_blocked_title_for_camera_dialog" msgid="3935701653713853065">"Camera niet beschikbaar"</string>
     <string name="app_streaming_blocked_title_for_fingerprint_dialog" msgid="3516853717714141951">"Doorgaan op telefoon"</string>
     <string name="app_streaming_blocked_title_for_microphone_dialog" msgid="544822455127171206">"Microfoon niet beschikbaar"</string>
@@ -1967,6 +1970,9 @@
     <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"Je hebt hier nu geen toegang toe op je <xliff:g id="DEVICE">%1$s</xliff:g>. Probeer het in plaats daarvan op je Android TV-apparaat."</string>
     <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"Je hebt hier nu geen toegang toe op je <xliff:g id="DEVICE">%1$s</xliff:g>. Probeer het in plaats daarvan op je tablet."</string>
     <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"Je hebt hier nu geen toegang toe op je <xliff:g id="DEVICE">%1$s</xliff:g>. Probeer het in plaats daarvan op je telefoon."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tv" msgid="4706276040125072077">"Deze app vraagt om extra rechten, maar je kunt geen rechten verlenen tijdens een streamingsessie. Verleen het recht eerst op je Android TV-apparaat."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tablet" msgid="1824604581465771629">"Deze app vraagt om extra rechten, maar je kunt geen rechten verlenen tijdens een streamingsessie. Verleen het recht eerst op je tablet."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="default" msgid="7755223160363292105">"Deze app vraagt om extra rechten, maar je kunt geen rechten verlenen tijdens een streamingsessie. Verleen het recht eerst op je telefoon."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"Deze app vraagt om aanvullende beveiliging. Probeer het in plaats daarvan op je Android TV-apparaat."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"Deze app vraagt om aanvullende beveiliging. Probeer het in plaats daarvan op je tablet."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"Deze app vraagt om aanvullende beveiliging. Probeer het in plaats daarvan op je telefoon."</string>
diff --git a/core/res/res/values-or/strings.xml b/core/res/res/values-or/strings.xml
index 4dba4ab..5743fd7 100644
--- a/core/res/res/values-or/strings.xml
+++ b/core/res/res/values-or/strings.xml
@@ -590,6 +590,8 @@
     <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"ପଞ୍ଜିକୃତ ଯନ୍ତ୍ର ଏବଂ ମାର୍ଗ ଲକ୍ଷସ୍ଥଳ ପରି ପସନ୍ଦର nfc ପେମେଣ୍ଟ ସେବା ସୂଚନା ପାଇବାକୁ ଆପ୍ ଅନୁମତି କରିଥାଏ।"</string>
     <string name="permlab_nfc" msgid="1904455246837674977">"ନିଅର୍ ଫିଲ୍ଡ କମ୍ୟୁନିକେଶନ୍ ଉପରେ ନିୟନ୍ତ୍ରଣ ରଖନ୍ତୁ"</string>
     <string name="permdesc_nfc" msgid="8352737680695296741">"ନିଅର୍‍ ଫିଲ୍ଡ କମ୍ୟୁନିକେସନ୍‍ନ (NFC) ଟାଗ୍‍, କାର୍ଡ ଓ ରିଡରଗୁଡ଼ିକ ସହ ଯୋଗାଯୋଗ କରିବା ପାଇଁ ଆପ୍‍କୁ ଅନୁମତି ଦେଇଥାଏ।"</string>
+    <string name="permlab_nfcTransactionEvent" msgid="5868209446710407679">"ସୁରକ୍ଷିତ ଏଲିମେଣ୍ଟ ଟ୍ରାଞ୍ଜେକସନ ଇଭେଣ୍ଟ"</string>
+    <string name="permdesc_nfcTransactionEvent" msgid="1904286701876487397">"ଏହା ଏକ ସୁରକ୍ଷିତ ଏଲିମେଣ୍ଟରେ ହେଉଥିବା ଟ୍ରାଞ୍ଜେକସନ ବିଷୟରେ ସୂଚନା ପାଇବା ପାଇଁ ଆପକୁ ଅନୁମତି ଦିଏ।"</string>
     <string name="permlab_disableKeyguard" msgid="3605253559020928505">"ଆପଣଙ୍କ ସ୍କ୍ରୀନ୍‍ ଲକ୍‍ ଅକ୍ଷମ କରନ୍ତୁ"</string>
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"ଆପ୍‌କୁ କୀ\'ଲକ୍ କିମ୍ବା ସେଥିରେ ଥିବା କୌଣସି ପାସ୍‌ୱର୍ଡ ସୁରକ୍ଷାକୁ ଅକ୍ଷମ କରିବା ପାଇଁ ଅନୁମତି ଦିଏ, ଉଦାହରଣସ୍ୱରୂପ, ଇନ୍‌କମିଙ୍ଗ ଫୋନ୍‌ କଲ୍ ପ୍ରାପ୍ତ କରିବା ସମୟରେ ଫୋନ୍‌ଟି କୀ\'ଲକ୍‌କୁ ଅକ୍ଷମ କରିଦିଏ, ତା’ପରେ କଲ୍ ସମାପ୍ତ ହେବାପରେ ପୁଣି କୀ\'ଲକ୍‌କୁ ସକ୍ଷମ କରିଥାଏ।"</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"ସ୍କ୍ରିନ୍ ଲକ୍ ଜଟିଳତା ସଂକ୍ରାନ୍ତ ଅନୁରୋଧ"</string>
@@ -620,6 +622,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"ବାୟୋମେଟ୍ରିକ୍‌ ହାର୍ଡୱେର୍‌ ଉପଲବ୍ଧ ନାହିଁ"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"ପ୍ରାମାଣିକତାକୁ ବାତିଲ୍ କରାଯାଇଛି"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"ଚିହ୍ନଟ ହେଲାନାହିଁ"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"ଫେସ ଚିହ୍ନଟ କରାଯାଇନାହିଁ"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"ପ୍ରାମାଣିକତାକୁ ବାତିଲ୍ କରାଯାଇଛି"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"କୌଣସି ପିନ୍, ପେଟେର୍ନ ବା ପାସ୍‍ୱର୍ଡ ସେଟ୍ ନାହିଁ"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"ପ୍ରାମାଣିକରଣ କରିବା ସମୟରେ ତ୍ରୁଟି"</string>
@@ -1393,7 +1396,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"ଅଗ୍ରାହ୍ୟ କରନ୍ତୁ"</string>
     <string name="select_input_method" msgid="3971267998568587025">"ଇନପୁଟ୍ ପଦ୍ଧତି ବାଛନ୍ତୁ"</string>
     <string name="show_ime" msgid="6406112007347443383">"ଫିଜିକାଲ୍‌ କୀବୋର୍ଡ ସକ୍ରିୟ ଥିବାବେଳେ ଏହାକୁ ସ୍କ୍ରିନ୍‌ ଉପରେ ରଖନ୍ତୁ"</string>
-    <string name="hardware" msgid="1800597768237606953">"ଭର୍ଚୁଆଲ୍ କୀ’ବୋର୍ଡ ଦେଖାନ୍ତୁ"</string>
+    <string name="hardware" msgid="3611039921284836033">"ଅନ-ସ୍କ୍ରିନ କୀବୋର୍ଡ ବ୍ୟବହାର କର"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"<xliff:g id="DEVICE_NAME">%s</xliff:g>କୁ କନଫିଗର କରନ୍ତୁ"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"ଫିଜିକାଲ କୀବୋର୍ଡଗୁଡ଼ିକୁ କନଫିଗର କରନ୍ତୁ"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"ଭାଷା ଓ ଲେଆଉଟ ଚୟନ କରିବା ପାଇଁ ଟାପ କରନ୍ତୁ"</string>
@@ -1956,7 +1959,7 @@
     <string name="app_blocked_title" msgid="7353262160455028160">"ଆପ୍ ଉପଲବ୍ଧ ନାହିଁ"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> ବର୍ତ୍ତମାନ ଉପଲବ୍ଧ ନାହିଁ।"</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> ଉପଲବ୍ଧ ନାହିଁ"</string>
-    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="4483161748582966785">"ଅନୁମତି ଆବଶ୍ୟକ"</string>
+    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="3805704317624448487">"ଅନୁମତି ଅନୁରୋଧକୁ ବନ୍ଦ କରାଯାଇଛି"</string>
     <string name="app_streaming_blocked_title_for_camera_dialog" msgid="3935701653713853065">"କ୍ୟାମେରା ଉପଲବ୍ଧ ନାହିଁ"</string>
     <string name="app_streaming_blocked_title_for_fingerprint_dialog" msgid="3516853717714141951">"ଫୋନରେ ଜାରି ରଖନ୍ତୁ"</string>
     <string name="app_streaming_blocked_title_for_microphone_dialog" msgid="544822455127171206">"ମାଇକ୍ରୋଫୋନ ଉପଲବ୍ଧ ନାହିଁ"</string>
@@ -1967,6 +1970,9 @@
     <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"ବର୍ତ୍ତମାନ ଏହାକୁ ଆପଣଙ୍କ <xliff:g id="DEVICE">%1$s</xliff:g>ରେ ଆକ୍ସେସ କରାଯାଇପାରିବ ନାହିଁ। ଏହା ପରିବର୍ତ୍ତେ ଆପଣଙ୍କ Android TV ଡିଭାଇସରେ ଚେଷ୍ଟା କରନ୍ତୁ।"</string>
     <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"ବର୍ତ୍ତମାନ ଏହାକୁ ଆପଣଙ୍କ <xliff:g id="DEVICE">%1$s</xliff:g>ରେ ଆକ୍ସେସ କରାଯାଇପାରିବ ନାହିଁ। ଏହା ପରିବର୍ତ୍ତେ ଆପଣଙ୍କ ଟାବଲେଟରେ ଚେଷ୍ଟା କରନ୍ତୁ।"</string>
     <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"ବର୍ତ୍ତମାନ ଏହାକୁ ଆପଣଙ୍କ <xliff:g id="DEVICE">%1$s</xliff:g>ରେ ଆକ୍ସେସ କରାଯାଇପାରିବ ନାହିଁ। ଏହା ପରିବର୍ତ୍ତେ ଆପଣଙ୍କ ଫୋନରେ ଚେଷ୍ଟା କରନ୍ତୁ।"</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tv" msgid="4706276040125072077">"ଏହି ଆପ ଅତିରିକ୍ତ ଅନୁମତି ପାଇଁ ଅନୁରୋଧ କରୁଛି, ଏକ ଷ୍ଟ୍ରିମିଂ ସେସନରେ ଅନୁମତି ଦିଆଯାଇପାରିବ ନାହିଁ। ପ୍ରଥମେ ଆପଣଙ୍କ Android TV ଡିଭାଇସରେ ଅନୁମତି ଦିଅନ୍ତୁ।"</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tablet" msgid="1824604581465771629">"ଏହି ଆପ ଅତିରିକ୍ତ ଅନୁମତି ପାଇଁ ଅନୁରୋଧ କରୁଛି, ଏକ ଷ୍ଟ୍ରିମିଂ ସେସନରେ ଅନୁମତି ଦିଆଯାଇପାରିବ ନାହିଁ। ପ୍ରଥମେ ଆପଣଙ୍କ ଟାବଲେଟରେ ଅନୁମତି ଦିଅନ୍ତୁ।"</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="default" msgid="7755223160363292105">"ଏହି ଆପ ଅତିରିକ୍ତ ଅନୁମତି ପାଇଁ ଅନୁରୋଧ କରୁଛି, ଏକ ଷ୍ଟ୍ରିମିଂ ସେସନରେ ଅନୁମତି ଦିଆଯାଇପାରିବ ନାହିଁ। ପ୍ରଥମେ ଆପଣଙ୍କ ଫୋନରେ ଅନୁମତି ଦିଅନ୍ତୁ।"</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"ଏହି ଆପ ଅତିରିକ୍ତ ସୁରକ୍ଷା ପାଇଁ ଅନୁରୋଧ କରୁଛି। ଏହା ପରିବର୍ତ୍ତେ ଆପଣଙ୍କ Android TV ଡିଭାଇସରେ ଚେଷ୍ଟା କରନ୍ତୁ।"</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"ଏହି ଆପ ଅତିରିକ୍ତ ସୁରକ୍ଷା ପାଇଁ ଅନୁରୋଧ କରୁଛି। ଏହା ପରିବର୍ତ୍ତେ ଆପଣଙ୍କ ଟାବଲେଟରେ ଚେଷ୍ଟା କରନ୍ତୁ।"</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"ଏହି ଆପ ଅତିରିକ୍ତ ସୁରକ୍ଷା ପାଇଁ ଅନୁରୋଧ କରୁଛି। ଏହା ପରିବର୍ତ୍ତେ ଆପଣଙ୍କ ଫୋନରେ ଚେଷ୍ଟା କରନ୍ତୁ।"</string>
diff --git a/core/res/res/values-pa/strings.xml b/core/res/res/values-pa/strings.xml
index 9c61e5b..8bc7a326 100644
--- a/core/res/res/values-pa/strings.xml
+++ b/core/res/res/values-pa/strings.xml
@@ -590,6 +590,8 @@
     <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"ਐਪ ਨੂੰ ਤਰਜੀਹੀ NFC ਭੁਗਤਾਨਸ਼ੁਦਾ ਸੇਵਾ ਜਾਣਕਾਰੀ ਪ੍ਰਾਪਤ ਕਰਨ ਦਿੰਦਾ ਹੈ ਜਿਵੇਂ ਕਿ ਰਜਿਸਟਰ ਕੀਤੇ ਸਾਧਨ ਅਤੇ ਮੰਜ਼ਿਲ ਰਸਤਾ।"</string>
     <string name="permlab_nfc" msgid="1904455246837674977">"ਨਜ਼ਦੀਕੀ ਖੇਤਰ ਸੰਚਾਰ ਤੇ ਨਿਯੰਤਰਣ ਪਾਓ"</string>
     <string name="permdesc_nfc" msgid="8352737680695296741">"ਐਪ ਨੂੰ ਨਜ਼ਦੀਕੀ ਖੇਤਰ ਸੰਚਾਰ (NFC) ਟੈਗਾਂ, ਕਾਰਡਾਂ ਅਤੇ ਰੀਡਰਾਂ ਨਾਲ ਸੰਚਾਰ ਕਰਨ ਦੀ ਆਗਿਆ ਦਿੰਦਾ ਹੈ।"</string>
+    <string name="permlab_nfcTransactionEvent" msgid="5868209446710407679">"ਸੁਰੱਖਿਅਤ ਅੰਸ਼ ਦੇ ਲੈਣ-ਦੇਣ ਸੰਬੰਧੀ ਇਵੈਂਟ"</string>
+    <string name="permdesc_nfcTransactionEvent" msgid="1904286701876487397">"ਐਪ ਨੂੰ ਸੁਰੱਖਿਅਤ ਅੰਸ਼ \'ਤੇ ਹੋਣ ਵਾਲੇ ਲੈਣ-ਦੇਣ ਬਾਰੇ ਜਾਣਕਾਰੀ ਪ੍ਰਾਪਤ ਕਰਨ ਦੀ ਆਗਿਆ ਦਿੰਦਾ ਹੈ।"</string>
     <string name="permlab_disableKeyguard" msgid="3605253559020928505">"ਆਪਣਾ ਸਕ੍ਰੀਨ  ਲਾਕ  ਅਸਮਰੱਥ ਬਣਾਓ"</string>
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"ਐਪ ਨੂੰ ਕੀਲਾਕ ਅਤੇ ਕਿਸੇ ਵੀ ਸੰਬੰਧਿਤ ਪਾਸਵਰਡ ਸੁਰੱਖਿਆ ਨੂੰ ਬੰਦ ਕਰਨ ਦੀ ਆਗਿਆ ਦਿੰਦਾ ਹੈ। ਉਦਾਹਰਨ ਲਈ, ਫ਼ੋਨ ਇੱਕ ਇਨਕਮਿੰਗ ਫ਼ੋਨ ਕਾਲ ਪ੍ਰਾਪਤ ਕਰਨ ਵੇਲੇ ਬੰਦ ਕਰਦਾ ਹੈ, ਫਿਰ ਜਦੋਂ ਕਾਲ ਖਤਮ ਹੁੰਦੀ ਹੈ ਤਾਂ ਕੀਲਾਕ ਨੂੰ ਮੁੜ-ਚਾਲੂ ਕਰਦਾ ਹੈ।"</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"ਸਕ੍ਰੀਨ ਲਾਕ ਦੀ ਜਟਿਲਤਾ ਲਈ ਬੇਨਤੀ ਕਰੋ"</string>
@@ -620,6 +622,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"ਬਾਇਓਮੈਟ੍ਰਿਕ ਹਾਰਡਵੇਅਰ ਉਪਲਬਧ ਨਹੀਂ ਹੈ"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"ਪ੍ਰਮਾਣੀਕਰਨ ਰੱਦ ਕੀਤਾ ਗਿਆ"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"ਪਛਾਣ ਨਹੀਂ ਹੋਈ"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"ਚਿਹਰੇ ਦੀ ਪਛਾਣ ਨਹੀਂ ਹੋਈ"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"ਪ੍ਰਮਾਣੀਕਰਨ ਰੱਦ ਕੀਤਾ ਗਿਆ"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"ਕੋਈ ਪਿੰਨ, ਪੈਟਰਨ ਜਾਂ ਪਾਸਵਰਡ ਸੈੱਟ ਨਹੀਂ ਕੀਤਾ ਗਿਆ"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"ਗੜਬੜ ਨੂੰ ਪ੍ਰਮਾਣਿਤ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ"</string>
@@ -1393,7 +1396,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"ਅਸਵੀਕਾਰ ਕਰੋ"</string>
     <string name="select_input_method" msgid="3971267998568587025">"ਇਨਪੁਟ ਵਿਧੀ ਚੁਣੋ"</string>
     <string name="show_ime" msgid="6406112007347443383">"ਭੌਤਿਕ ਕੀ-ਬੋਰਡ ਸਰਗਰਮ ਹੋਣ ਦੌਰਾਨ ਇਸ ਨੂੰ ਸਕ੍ਰੀਨ \'ਤੇ ਬਣਾਈ ਰੱਖੋ"</string>
-    <string name="hardware" msgid="1800597768237606953">"ਆਭਾਸੀ ਕੀ-ਬੋਰਡ ਦਿਖਾਓ"</string>
+    <string name="hardware" msgid="3611039921284836033">"ਆਨ-ਸਕ੍ਰੀਨ ਕੀ-ਬੋਰਡ ਨੂੰ ਵਰਤੋ"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"<xliff:g id="DEVICE_NAME">%s</xliff:g> ਦਾ ਸੰਰੂਪਣ ਕਰੋ"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"ਭੌਤਿਕ ਕੀ-ਬੋਰਡਾਂ ਦਾ ਸੰਰੂਪਣ ਕਰੋ"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"ਭਾਸ਼ਾ ਅਤੇ ਖਾਕਾ ਚੁਣਨ ਲਈ ਟੈਪ ਕਰੋ"</string>
@@ -1956,7 +1959,7 @@
     <string name="app_blocked_title" msgid="7353262160455028160">"ਐਪ ਉਪਲਬਧ ਨਹੀਂ ਹੈ"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> ਐਪ ਇਸ ਵੇਲੇ ਉਪਲਬਧ ਨਹੀਂ ਹੈ।"</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> ਉਪਲਬਧ ਨਹੀਂ ਹੈ"</string>
-    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="4483161748582966785">"ਇਜਾਜ਼ਤ ਦੀ ਲੋੜ ਹੈ"</string>
+    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="3805704317624448487">"ਇਜਾਜ਼ਤ ਸੰਬੰਧੀ ਬੇਨਤੀ ਨੂੰ ਰੋਕਿਆ ਗਿਆ"</string>
     <string name="app_streaming_blocked_title_for_camera_dialog" msgid="3935701653713853065">"ਕੈਮਰਾ ਉਪਲਬਧ ਨਹੀਂ ਹੈ"</string>
     <string name="app_streaming_blocked_title_for_fingerprint_dialog" msgid="3516853717714141951">"ਫ਼ੋਨ \'ਤੇ ਜਾਰੀ ਰੱਖੋ"</string>
     <string name="app_streaming_blocked_title_for_microphone_dialog" msgid="544822455127171206">"ਮਾਈਕ੍ਰੋਫ਼ੋਨ ਅਣਉਪਲਬਧ ਹੈ"</string>
@@ -1967,6 +1970,9 @@
     <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"ਇਸ ਸਮੇਂ ਤੁਹਾਡੇ <xliff:g id="DEVICE">%1$s</xliff:g> \'ਤੇ ਇਸ ਤੱਕ ਪਹੁੰਚ ਨਹੀਂ ਕੀਤੀ ਜਾ ਸਕਦੀ। ਇਸਦੀ ਬਜਾਏ ਆਪਣੇ Android TV ਡੀਵਾਈਸ \'ਤੇ ਵਰਤ ਕੇ ਦੇਖੋ।"</string>
     <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"ਇਸ ਸਮੇਂ ਤੁਹਾਡੇ <xliff:g id="DEVICE">%1$s</xliff:g> \'ਤੇ ਇਸ ਤੱਕ ਪਹੁੰਚ ਨਹੀਂ ਕੀਤੀ ਜਾ ਸਕਦੀ। ਇਸਦੀ ਬਜਾਏ ਆਪਣੇ ਟੈਬਲੈੱਟ \'ਤੇ ਵਰਤ ਕੇ ਦੇਖੋ।"</string>
     <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"ਇਸ ਸਮੇਂ ਤੁਹਾਡੇ <xliff:g id="DEVICE">%1$s</xliff:g> \'ਤੇ ਇਸ ਤੱਕ ਪਹੁੰਚ ਨਹੀਂ ਕੀਤੀ ਜਾ ਸਕਦੀ। ਇਸਦੀ ਬਜਾਏ ਆਪਣੇ ਫ਼ੋਨ \'ਤੇ ਵਰਤ ਕੇ ਦੇਖੋ।"</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tv" msgid="4706276040125072077">"ਇਹ ਐਪ ਵਧੀਕ ਇਜਾਜ਼ਤਾਂ ਦੀ ਬੇਨਤੀ ਕਰ ਰਹੀ ਹੈ, ਪਰ ਸਟ੍ਰੀਮਿੰਗ ਸੈਸ਼ਨ ਵਿੱਚ ਇਜਾਜ਼ਤਾਂ ਨਹੀਂ ਦਿੱਤੀਆਂ ਜਾ ਸਕਦੀਆਂ। ਪਹਿਲਾਂ ਆਪਣੇ Android TV ਡੀਵਾਈਸ \'ਤੇ ਇਜਾਜ਼ਤ ਦਿਓ।"</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tablet" msgid="1824604581465771629">"ਇਹ ਐਪ ਵਧੀਕ ਇਜਾਜ਼ਤਾਂ ਦੀ ਬੇਨਤੀ ਕਰ ਰਹੀ ਹੈ, ਪਰ ਸਟ੍ਰੀਮਿੰਗ ਸੈਸ਼ਨ ਵਿੱਚ ਇਜਾਜ਼ਤਾਂ ਨਹੀਂ ਦਿੱਤੀਆਂ ਜਾ ਸਕਦੀਆਂ। ਪਹਿਲਾਂ ਆਪਣੇ ਟੈਬਲੈੱਟ \'ਤੇ ਇਜਾਜ਼ਤ ਦਿਓ।"</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="default" msgid="7755223160363292105">"ਇਹ ਐਪ ਵਧੀਕ ਇਜਾਜ਼ਤਾਂ ਦੀ ਬੇਨਤੀ ਕਰ ਰਹੀ ਹੈ, ਪਰ ਸਟ੍ਰੀਮਿੰਗ ਸੈਸ਼ਨ ਵਿੱਚ ਇਜਾਜ਼ਤਾਂ ਨਹੀਂ ਦਿੱਤੀਆਂ ਜਾ ਸਕਦੀਆਂ। ਪਹਿਲਾਂ ਆਪਣੇ ਫ਼ੋਨ \'ਤੇ ਇਜਾਜ਼ਤ ਦਿਓ।"</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"ਇਹ ਐਪ ਵਧੀਕ ਸੁਰੱਖਿਆ ਦੀ ਬੇਨਤੀ ਕਰ ਰਹੀ ਹੈ। ਇਸਦੀ ਬਜਾਏ ਆਪਣੇ Android TV ਡੀਵਾਈਸ \'ਤੇ ਵਰਤ ਕੇ ਦੇਖੋ।"</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"ਇਹ ਐਪ ਵਧੀਕ ਸੁਰੱਖਿਆ ਦੀ ਬੇਨਤੀ ਕਰ ਰਹੀ ਹੈ। ਇਸਦੀ ਬਜਾਏ ਆਪਣੇ ਟੈਬਲੈੱਟ \'ਤੇ ਵਰਤ ਕੇ ਦੇਖੋ।"</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"ਇਹ ਐਪ ਵਧੀਕ ਸੁਰੱਖਿਆ ਦੀ ਬੇਨਤੀ ਕਰ ਰਹੀ ਹੈ। ਇਸਦੀ ਬਜਾਏ ਆਪਣੇ ਫ਼ੋਨ \'ਤੇ ਵਰਤ ਕੇ ਦੇਖੋ।"</string>
diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml
index 7a8684f4..2671e39 100644
--- a/core/res/res/values-pl/strings.xml
+++ b/core/res/res/values-pl/strings.xml
@@ -592,6 +592,8 @@
     <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"Pozwala aplikacji uzyskiwać informacje o preferowanych usługach płatniczych NFC, np. zarejestrowanych pomocach i miejscach docelowych tras."</string>
     <string name="permlab_nfc" msgid="1904455246837674977">"kontrolowanie łączności Near Field Communication"</string>
     <string name="permdesc_nfc" msgid="8352737680695296741">"Pozwala aplikacji na komunikowanie się z tagami, kartami i czytnikami NFC (Near Field Communication)."</string>
+    <string name="permlab_nfcTransactionEvent" msgid="5868209446710407679">"Wydarzenie transakcji w Bezpiecznym elemencie"</string>
+    <string name="permdesc_nfcTransactionEvent" msgid="1904286701876487397">"Zezwala na otrzymywanie przez aplikację informacji o transakcjach realizowanych w Bezpiecznym elemencie."</string>
     <string name="permlab_disableKeyguard" msgid="3605253559020928505">"wyłączanie blokady ekranu"</string>
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"Pozwala aplikacji na wyłączenie blokady klawiatury i wszystkich związanych z tym haseł zabezpieczających. Na przykład telefon wyłącza blokadę klawiatury, gdy odbiera połączenie przychodzące, a następnie włącza ją ponownie po zakończeniu połączenia."</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"żądaj informacji o stopniu złożoności blokady ekranu"</string>
@@ -622,6 +624,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Sprzęt biometryczny niedostępny"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Anulowano uwierzytelnianie"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Nie rozpoznano"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Nie rozpoznano twarzy"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Anulowano uwierzytelnianie"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"Nie ustawiono kodu PIN, wzoru ani hasła"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Podczas uwierzytelniania wystąpił błąd"</string>
@@ -1395,7 +1398,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"ODRZUĆ"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Wybierz metodę wprowadzania"</string>
     <string name="show_ime" msgid="6406112007347443383">"Pozostaw na ekranie, gdy aktywna jest klawiatura fizyczna"</string>
-    <string name="hardware" msgid="1800597768237606953">"Pokaż klawiaturę wirtualną"</string>
+    <string name="hardware" msgid="3611039921284836033">"Używaj klawiatury ekranowej"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"Skonfiguruj urządzenie <xliff:g id="DEVICE_NAME">%s</xliff:g>"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Skonfiguruj klawiatury fizyczne"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Kliknij, aby wybrać język i układ"</string>
@@ -1958,7 +1961,7 @@
     <string name="app_blocked_title" msgid="7353262160455028160">"Aplikacja jest niedostępna"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"Aplikacja <xliff:g id="APP_NAME">%1$s</xliff:g> jest obecnie niedostępna."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> – brak dostępu"</string>
-    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="4483161748582966785">"Wymagane są uprawnienia"</string>
+    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="3805704317624448487">"Zablokowana prośba o uprawnienia"</string>
     <string name="app_streaming_blocked_title_for_camera_dialog" msgid="3935701653713853065">"Aparat niedostępny"</string>
     <string name="app_streaming_blocked_title_for_fingerprint_dialog" msgid="3516853717714141951">"Kontynuuj na telefonie"</string>
     <string name="app_streaming_blocked_title_for_microphone_dialog" msgid="544822455127171206">"Mikrofon niedostępny"</string>
@@ -1969,6 +1972,9 @@
     <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"W tej chwili nie można z tego skorzystać na urządzeniu <xliff:g id="DEVICE">%1$s</xliff:g>. Użyj urządzenia z Androidem TV."</string>
     <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"W tej chwili nie można z tego skorzystać na urządzeniu <xliff:g id="DEVICE">%1$s</xliff:g>. Użyj tabletu."</string>
     <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"W tej chwili nie można z tego skorzystać na urządzeniu <xliff:g id="DEVICE">%1$s</xliff:g>. Użyj telefonu."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tv" msgid="4706276040125072077">"Ta aplikacja prosi o dodatkowe uprawnienia, ale nie można ich przyznać w trakcie sesji odtwarzania strumieniowego. Najpierw przyznaj te uprawnienia na urządzeniu z Androidem TV."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tablet" msgid="1824604581465771629">"Ta aplikacja prosi o dodatkowe uprawnienia, ale nie można ich przyznać w trakcie sesji odtwarzania strumieniowego. Najpierw przyznaj te uprawnienia na tablecie."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="default" msgid="7755223160363292105">"Ta aplikacja prosi o dodatkowe uprawnienia, ale nie można ich przyznać w trakcie sesji odtwarzania strumieniowego. Najpierw przyznaj te uprawnienia na telefonie."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"Ta aplikacja wymaga dodatkowych zabezpieczeń. Użyj urządzenia z Androidem TV."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"Ta aplikacja wymaga dodatkowych zabezpieczeń. Użyj tabletu."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"Ta aplikacja wymaga dodatkowych zabezpieczeń. Użyj telefonu."</string>
diff --git a/core/res/res/values-pt-rBR/strings.xml b/core/res/res/values-pt-rBR/strings.xml
index 0a6f769..dfd046a 100644
--- a/core/res/res/values-pt-rBR/strings.xml
+++ b/core/res/res/values-pt-rBR/strings.xml
@@ -591,6 +591,8 @@
     <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"Permite que o app acesse as informações preferidas de serviço de pagamento por NFC, como auxílios registrados ou destinos de trajetos."</string>
     <string name="permlab_nfc" msgid="1904455246837674977">"controlar a comunicação a curta distância"</string>
     <string name="permdesc_nfc" msgid="8352737680695296741">"Permite que o app se comunique com leitores, cartões e etiqueta NFC (comunicação a curta distância)."</string>
+    <string name="permlab_nfcTransactionEvent" msgid="5868209446710407679">"Evento de transação do Elemento de segurança"</string>
+    <string name="permdesc_nfcTransactionEvent" msgid="1904286701876487397">"Permite que o app receba informações sobre transações que ocorrem em um Elemento de segurança."</string>
     <string name="permlab_disableKeyguard" msgid="3605253559020928505">"desativar o bloqueio de tela"</string>
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"Permite que o app desative o bloqueio de teclas e qualquer segurança por senha associada. Por exemplo, o telefone desativa o bloqueio de telas ao receber uma ligação e o reativa quando a chamada é finalizada."</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"Solicitar complexidade do bloqueio de tela"</string>
@@ -621,6 +623,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Hardware biométrico indisponível"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Autenticação cancelada"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Não reconhecido"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Rosto não reconhecido"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Autenticação cancelada"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"Nenhum PIN, padrão ou senha configurado"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Erro na autenticação"</string>
@@ -1394,7 +1397,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"RECUSAR"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Selecione o método de entrada"</string>
     <string name="show_ime" msgid="6406112007347443383">"Mantém o teclado virtual na tela enquanto o teclado físico está ativo"</string>
-    <string name="hardware" msgid="1800597768237606953">"Mostrar teclado virtual"</string>
+    <string name="hardware" msgid="3611039921284836033">"Usar teclado na tela"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"Configure o dispositivo <xliff:g id="DEVICE_NAME">%s</xliff:g>"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Configure teclados físicos"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Toque para selecionar o idioma e o layout"</string>
@@ -1957,7 +1960,7 @@
     <string name="app_blocked_title" msgid="7353262160455028160">"O app não está disponível"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"O app <xliff:g id="APP_NAME">%1$s</xliff:g> não está disponível no momento."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> indisponível"</string>
-    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="4483161748582966785">"Permissão necessária"</string>
+    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="3805704317624448487">"Solicitação de permissão suprimida"</string>
     <string name="app_streaming_blocked_title_for_camera_dialog" msgid="3935701653713853065">"Câmera indisponível"</string>
     <string name="app_streaming_blocked_title_for_fingerprint_dialog" msgid="3516853717714141951">"Continuar no smartphone"</string>
     <string name="app_streaming_blocked_title_for_microphone_dialog" msgid="544822455127171206">"Microfone indisponível"</string>
@@ -1968,6 +1971,9 @@
     <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"No momento, não é possível acessar esse app pelo <xliff:g id="DEVICE">%1$s</xliff:g>. Tente pelo dispositivo Android TV."</string>
     <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"No momento, não é possível acessar esse app pelo <xliff:g id="DEVICE">%1$s</xliff:g>. Tente pelo seu tablet."</string>
     <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"No momento, não é possível acessar esse app pelo <xliff:g id="DEVICE">%1$s</xliff:g>. Tente pelo seu smartphone."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tv" msgid="4706276040125072077">"Esse app está solicitando permissões extras, mas elas não podem ser concedidas em uma sessão de streaming. Dê permissão pelo dispositivo Android TV primeiro."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tablet" msgid="1824604581465771629">"Esse app está solicitando permissões extras, mas elas não podem ser concedidas em uma sessão de streaming. Dê permissão pelo tablet primeiro."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="default" msgid="7755223160363292105">"Esse app está solicitando permissões extras, mas elas não podem ser concedidas em uma sessão de streaming. Dê permissão pelo smartphone primeiro."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"Esse app está solicitando segurança extra. Tente pelo dispositivo Android TV."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"Esse app está solicitando segurança extra. Tente pelo tablet."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"Esse app está solicitando segurança extra. Tente pelo smartphone."</string>
diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml
index 96242e1..d094e3e 100644
--- a/core/res/res/values-pt-rPT/strings.xml
+++ b/core/res/res/values-pt-rPT/strings.xml
@@ -591,6 +591,8 @@
     <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"Permite que a app obtenha informações de serviços de pagamento com NFC preferenciais, como apoios registados e destino da rota."</string>
     <string name="permlab_nfc" msgid="1904455246837674977">"controlo Near Field Communication"</string>
     <string name="permdesc_nfc" msgid="8352737680695296741">"Permite que a app comunique com etiquetas, cartões e leitores Near Field Communication (NFC)."</string>
+    <string name="permlab_nfcTransactionEvent" msgid="5868209446710407679">"Evento de transação do elemento seguro"</string>
+    <string name="permdesc_nfcTransactionEvent" msgid="1904286701876487397">"Permite que a app receba informações sobre transações que ocorram num elemento seguro."</string>
     <string name="permlab_disableKeyguard" msgid="3605253559020928505">"desativar o bloqueio do ecrã"</string>
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"Permite que a app desative o bloqueio de teclas e qualquer segurança por palavra-passe associada. Por exemplo, o telemóvel desativa o bloqueio de teclas quando recebe uma chamada e reativa o bloqueio de teclas ao terminar a chamada."</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"solicitar a complexidade do bloqueio de ecrã"</string>
@@ -621,6 +623,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Hardware biométrico indisponível."</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Autenticação cancelada"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Não reconhecido."</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Rosto não reconhecido"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Autenticação cancelada"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"Nenhum PIN, padrão ou palavra-passe definidos."</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Erro ao autenticar."</string>
@@ -1394,7 +1397,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"RECUSAR"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Escolher o método de entrada"</string>
     <string name="show_ime" msgid="6406112007347443383">"Manter no ecrã enquanto o teclado físico estiver ativo"</string>
-    <string name="hardware" msgid="1800597768237606953">"Mostrar o teclado virtual"</string>
+    <string name="hardware" msgid="3611039921284836033">"Usar o teclado no ecrã"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"Configure o dispositivo <xliff:g id="DEVICE_NAME">%s</xliff:g>"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Configure teclados físicos"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Toque para selecionar o idioma e o esquema"</string>
@@ -1957,7 +1960,7 @@
     <string name="app_blocked_title" msgid="7353262160455028160">"A app não está disponível"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"De momento, a app <xliff:g id="APP_NAME">%1$s</xliff:g> não está disponível."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> indisponível"</string>
-    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="4483161748582966785">"Autorização necessária"</string>
+    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="3805704317624448487">"Pedido de autorização suprimido"</string>
     <string name="app_streaming_blocked_title_for_camera_dialog" msgid="3935701653713853065">"Câmara indisponível"</string>
     <string name="app_streaming_blocked_title_for_fingerprint_dialog" msgid="3516853717714141951">"Continue no telemóvel"</string>
     <string name="app_streaming_blocked_title_for_microphone_dialog" msgid="544822455127171206">"Microfone indisponível"</string>
@@ -1968,6 +1971,9 @@
     <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"De momento, não é possível aceder a esta app no seu <xliff:g id="DEVICE">%1$s</xliff:g>. Em alternativa, experimente no dispositivo Android TV."</string>
     <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"De momento, não é possível aceder a esta app no seu <xliff:g id="DEVICE">%1$s</xliff:g>. Em alternativa, experimente no tablet."</string>
     <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"De momento, não é possível aceder a esta app no seu <xliff:g id="DEVICE">%1$s</xliff:g>. Em alternativa, experimente no telemóvel."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tv" msgid="4706276040125072077">"Esta app está a pedir autorizações adicionais, mas não é possível conceder autorizações numa sessão de streaming. Comece por conceder a autorização no seu dispositivo Android TV."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tablet" msgid="1824604581465771629">"Esta app está a pedir autorizações adicionais, mas não é possível conceder autorizações numa sessão de streaming. Comece por conceder a autorização no seu tablet."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="default" msgid="7755223160363292105">"Esta app está a pedir autorizações adicionais, mas não é possível conceder autorizações numa sessão de streaming. Comece por conceder a autorização no seu telemóvel."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"Esta app está a solicitar segurança adicional. Em alternativa, experimente no dispositivo Android TV."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"Esta app está a solicitar segurança adicional. Em alternativa, experimente no tablet."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"Esta app está a solicitar segurança adicional. Em alternativa, experimente no telemóvel."</string>
diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml
index 0a6f769..dfd046a 100644
--- a/core/res/res/values-pt/strings.xml
+++ b/core/res/res/values-pt/strings.xml
@@ -591,6 +591,8 @@
     <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"Permite que o app acesse as informações preferidas de serviço de pagamento por NFC, como auxílios registrados ou destinos de trajetos."</string>
     <string name="permlab_nfc" msgid="1904455246837674977">"controlar a comunicação a curta distância"</string>
     <string name="permdesc_nfc" msgid="8352737680695296741">"Permite que o app se comunique com leitores, cartões e etiqueta NFC (comunicação a curta distância)."</string>
+    <string name="permlab_nfcTransactionEvent" msgid="5868209446710407679">"Evento de transação do Elemento de segurança"</string>
+    <string name="permdesc_nfcTransactionEvent" msgid="1904286701876487397">"Permite que o app receba informações sobre transações que ocorrem em um Elemento de segurança."</string>
     <string name="permlab_disableKeyguard" msgid="3605253559020928505">"desativar o bloqueio de tela"</string>
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"Permite que o app desative o bloqueio de teclas e qualquer segurança por senha associada. Por exemplo, o telefone desativa o bloqueio de telas ao receber uma ligação e o reativa quando a chamada é finalizada."</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"Solicitar complexidade do bloqueio de tela"</string>
@@ -621,6 +623,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Hardware biométrico indisponível"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Autenticação cancelada"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Não reconhecido"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Rosto não reconhecido"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Autenticação cancelada"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"Nenhum PIN, padrão ou senha configurado"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Erro na autenticação"</string>
@@ -1394,7 +1397,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"RECUSAR"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Selecione o método de entrada"</string>
     <string name="show_ime" msgid="6406112007347443383">"Mantém o teclado virtual na tela enquanto o teclado físico está ativo"</string>
-    <string name="hardware" msgid="1800597768237606953">"Mostrar teclado virtual"</string>
+    <string name="hardware" msgid="3611039921284836033">"Usar teclado na tela"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"Configure o dispositivo <xliff:g id="DEVICE_NAME">%s</xliff:g>"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Configure teclados físicos"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Toque para selecionar o idioma e o layout"</string>
@@ -1957,7 +1960,7 @@
     <string name="app_blocked_title" msgid="7353262160455028160">"O app não está disponível"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"O app <xliff:g id="APP_NAME">%1$s</xliff:g> não está disponível no momento."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> indisponível"</string>
-    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="4483161748582966785">"Permissão necessária"</string>
+    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="3805704317624448487">"Solicitação de permissão suprimida"</string>
     <string name="app_streaming_blocked_title_for_camera_dialog" msgid="3935701653713853065">"Câmera indisponível"</string>
     <string name="app_streaming_blocked_title_for_fingerprint_dialog" msgid="3516853717714141951">"Continuar no smartphone"</string>
     <string name="app_streaming_blocked_title_for_microphone_dialog" msgid="544822455127171206">"Microfone indisponível"</string>
@@ -1968,6 +1971,9 @@
     <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"No momento, não é possível acessar esse app pelo <xliff:g id="DEVICE">%1$s</xliff:g>. Tente pelo dispositivo Android TV."</string>
     <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"No momento, não é possível acessar esse app pelo <xliff:g id="DEVICE">%1$s</xliff:g>. Tente pelo seu tablet."</string>
     <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"No momento, não é possível acessar esse app pelo <xliff:g id="DEVICE">%1$s</xliff:g>. Tente pelo seu smartphone."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tv" msgid="4706276040125072077">"Esse app está solicitando permissões extras, mas elas não podem ser concedidas em uma sessão de streaming. Dê permissão pelo dispositivo Android TV primeiro."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tablet" msgid="1824604581465771629">"Esse app está solicitando permissões extras, mas elas não podem ser concedidas em uma sessão de streaming. Dê permissão pelo tablet primeiro."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="default" msgid="7755223160363292105">"Esse app está solicitando permissões extras, mas elas não podem ser concedidas em uma sessão de streaming. Dê permissão pelo smartphone primeiro."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"Esse app está solicitando segurança extra. Tente pelo dispositivo Android TV."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"Esse app está solicitando segurança extra. Tente pelo tablet."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"Esse app está solicitando segurança extra. Tente pelo smartphone."</string>
diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml
index 2c58ed5..b386223 100644
--- a/core/res/res/values-ro/strings.xml
+++ b/core/res/res/values-ro/strings.xml
@@ -591,6 +591,8 @@
     <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"Permite aplicației să obțină informații despre serviciul de plăți NFC preferat, de exemplu, identificatorii de aplicație înregistrați și destinația traseului."</string>
     <string name="permlab_nfc" msgid="1904455246837674977">"controlare schimb de date prin Near Field Communication"</string>
     <string name="permdesc_nfc" msgid="8352737680695296741">"Permite aplicației să comunice cu etichetele, cardurile și cititoarele NFC (Near Field Communication)."</string>
+    <string name="permlab_nfcTransactionEvent" msgid="5868209446710407679">"Eveniment de tranzacție în cadrul unui element securizat"</string>
+    <string name="permdesc_nfcTransactionEvent" msgid="1904286701876487397">"Permite aplicației să primească informații despre tranzacțiile care au loc în cadrul unui element securizat."</string>
     <string name="permlab_disableKeyguard" msgid="3605253559020928505">"dezactivează blocarea ecranului"</string>
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"Permite aplicației să dezactiveze blocarea tastelor și orice modalitate asociată de securizare prin parolă. De exemplu, telefonul dezactivează blocarea tastelor când se primește un apel telefonic și reactivează blocarea tastelor la terminarea apelului."</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"să solicite complexitatea blocării ecranului"</string>
@@ -621,6 +623,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Hardware biometric indisponibil"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Autentificarea a fost anulată"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Nu este recunoscut"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Fața nu a fost recunoscută"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Autentificarea a fost anulată"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"Nu este setat un cod PIN, un model sau o parolă"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Eroare la autentificare"</string>
@@ -1394,7 +1397,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"REFUZ"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Alege metoda de introducere de text"</string>
     <string name="show_ime" msgid="6406112007347443383">"Se păstrează pe ecran cât timp este activată tastatura fizică"</string>
-    <string name="hardware" msgid="1800597768237606953">"Afișează tastatura virtuală"</string>
+    <string name="hardware" msgid="3611039921284836033">"Folosește tastatura pe ecran"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"Configurează <xliff:g id="DEVICE_NAME">%s</xliff:g>"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Configurează tastaturi fizice"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Atinge pentru a selecta limba și aspectul"</string>
@@ -1957,7 +1960,7 @@
     <string name="app_blocked_title" msgid="7353262160455028160">"Aplicația nu este disponibilă"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> nu este disponibilă momentan."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> nu este disponibilă"</string>
-    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="4483161748582966785">"Necesită permisiune"</string>
+    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="3805704317624448487">"Solicitarea de permisiune s-a suprimat"</string>
     <string name="app_streaming_blocked_title_for_camera_dialog" msgid="3935701653713853065">"Camera video nu este disponibilă"</string>
     <string name="app_streaming_blocked_title_for_fingerprint_dialog" msgid="3516853717714141951">"Continuă pe telefon"</string>
     <string name="app_streaming_blocked_title_for_microphone_dialog" msgid="544822455127171206">"Microfon indisponibil"</string>
@@ -1968,6 +1971,9 @@
     <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"Aplicația nu poate fi accesată pe <xliff:g id="DEVICE">%1$s</xliff:g> momentan. Încearcă pe dispozitivul Android TV."</string>
     <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"Aplicația nu poate fi accesată pe <xliff:g id="DEVICE">%1$s</xliff:g> momentan. Încearcă pe tabletă."</string>
     <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"Aplicația nu poate fi accesată pe <xliff:g id="DEVICE">%1$s</xliff:g> momentan. Încearcă pe telefon."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tv" msgid="4706276040125072077">"Aplicația solicită permisiuni suplimentare, dar acestea nu pot fi acordate într-o sesiune de streaming. Acordă permisiunea întâi pe dispozitivul Android TV."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tablet" msgid="1824604581465771629">"Aplicația solicită permisiuni suplimentare, dar acestea nu pot fi acordate într-o sesiune de streaming. Acordă permisiunea întâi pe tabletă."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="default" msgid="7755223160363292105">"Aplicația solicită permisiuni suplimentare, dar acestea nu pot fi acordate într-o sesiune de streaming. Acordă permisiunea întâi pe telefon."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"Aplicația necesită securitate suplimentară. Încearcă pe dispozitivul Android TV."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"Aplicația necesită securitate suplimentară. Încearcă pe tabletă."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"Aplicația necesită securitate suplimentară. Încearcă pe telefon."</string>
diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml
index e48c487..39aa55a 100644
--- a/core/res/res/values-ru/strings.xml
+++ b/core/res/res/values-ru/strings.xml
@@ -592,6 +592,8 @@
     <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"Приложение сможет получать сведения о предпочтительном платежном сервисе NFC (например, зарегистрированные идентификаторы AID и конечный пункт маршрута)."</string>
     <string name="permlab_nfc" msgid="1904455246837674977">"Управление NFC-модулем"</string>
     <string name="permdesc_nfc" msgid="8352737680695296741">"Приложение сможет обмениваться данными с NFC-метками, картами и устройствами считывания, используя NFC."</string>
+    <string name="permlab_nfcTransactionEvent" msgid="5868209446710407679">"Транзакции, обрабатываемые в защитном элементе"</string>
+    <string name="permdesc_nfcTransactionEvent" msgid="1904286701876487397">"Позволяет приложению получать информацию о транзакциях, обрабатываемых в защитном элементе."</string>
     <string name="permlab_disableKeyguard" msgid="3605253559020928505">"Отключение функции блокировки экрана"</string>
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"Приложение сможет отключать блокировку экрана и другие функции защиты. Например, блокировка экрана будет отключаться при получении входящего вызова и включаться после завершения разговора."</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"Запрос данных об уровне сложности блокировки экрана"</string>
@@ -622,6 +624,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Биометрическое оборудование недоступно"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Аутентификация отменена"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Не распознано"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Лицо не распознано."</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Аутентификация отменена"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"Укажите PIN-код, пароль или графический ключ"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Ошибка аутентификации."</string>
@@ -1395,7 +1398,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"ОТКЛОНИТЬ"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Выберите способ ввода"</string>
     <string name="show_ime" msgid="6406112007347443383">"Не скрывать экранную клавиатуру, когда включена физическая"</string>
-    <string name="hardware" msgid="1800597768237606953">"Виртуальная клавиатура"</string>
+    <string name="hardware" msgid="3611039921284836033">"Использовать экранную клавиатуру"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"Настройте устройство \"<xliff:g id="DEVICE_NAME">%s</xliff:g>\""</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Настройте физические клавиатуры"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Нажмите, чтобы выбрать язык и раскладку"</string>
@@ -1958,7 +1961,7 @@
     <string name="app_blocked_title" msgid="7353262160455028160">"Приложение недоступно"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"Приложение \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" сейчас недоступно."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"Недоступно: <xliff:g id="ACTIVITY">%1$s</xliff:g>"</string>
-    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="4483161748582966785">"Требуется разрешение"</string>
+    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="3805704317624448487">"Запрос разрешений заблокирован"</string>
     <string name="app_streaming_blocked_title_for_camera_dialog" msgid="3935701653713853065">"Камера недоступна"</string>
     <string name="app_streaming_blocked_title_for_fingerprint_dialog" msgid="3516853717714141951">"Продолжите на телефоне"</string>
     <string name="app_streaming_blocked_title_for_microphone_dialog" msgid="544822455127171206">"Микрофон недоступен"</string>
@@ -1969,6 +1972,9 @@
     <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"Эта функция пока недоступна на устройстве <xliff:g id="DEVICE">%1$s</xliff:g>. Используйте Android TV."</string>
     <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"Эта функция пока недоступна на устройстве <xliff:g id="DEVICE">%1$s</xliff:g>. Используйте планшет."</string>
     <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"На устройстве <xliff:g id="DEVICE">%1$s</xliff:g> эта функция пока недоступна. Используйте телефон."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tv" msgid="4706276040125072077">"Это приложение запрашивает дополнительные разрешения, которые невозможно предоставить во время трансляции на устройство. Сначала откройте доступ на устройстве Android TV."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tablet" msgid="1824604581465771629">"Это приложение запрашивает дополнительные разрешения, которые невозможно предоставить во время трансляции на устройство. Сначала откройте доступ на планшете."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="default" msgid="7755223160363292105">"Это приложение запрашивает дополнительные разрешения, которые невозможно предоставить во время трансляции на устройство. Сначала откройте доступ на телефоне."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"Это приложение запрашивает дополнительные меры защиты. Используйте Android TV."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"Это приложение запрашивает дополнительные меры защиты. Используйте планшет."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"Это приложение запрашивает дополнительные меры защиты. Используйте телефон."</string>
diff --git a/core/res/res/values-si/strings.xml b/core/res/res/values-si/strings.xml
index 0df5c65..bde7d93 100644
--- a/core/res/res/values-si/strings.xml
+++ b/core/res/res/values-si/strings.xml
@@ -590,6 +590,8 @@
     <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"ලියාපදිංචි කළ ආධාර සහ ගමන් මාර්ග ගමනාන්ත වැනි කැමති nfc ගෙවීම් සේවා තොරතුරු ලබා ගැනීමට යෙදුමට ඉඩ දෙයි."</string>
     <string name="permlab_nfc" msgid="1904455246837674977">"ආසන්න ක්ෂේත්‍ර සන්නිවේදනය පාලනය කරන්න"</string>
     <string name="permdesc_nfc" msgid="8352737680695296741">"ආසන්න ක්ෂේත්‍ර සන්නිවේදන (NFC) ටැග්, පත්, සහ කියවන්නන් සමඟ සන්නිවේදනය කිරීමට යෙදුමට අවසර දෙන්න."</string>
+    <string name="permlab_nfcTransactionEvent" msgid="5868209446710407679">"සුරක්ෂිත මූලද්‍රව්‍ය ගනුදෙනු සිදුවීම"</string>
+    <string name="permdesc_nfcTransactionEvent" msgid="1904286701876487397">"සුරක්ෂිත මූලද්‍රව්‍යයක සිදු වන ගනුදෙනු පිළිබඳ තොරතුරු ලබා ගැනීමට යෙදුමට ඉඩ දෙයි."</string>
     <string name="permlab_disableKeyguard" msgid="3605253559020928505">"ඔබගේ තිරයේ අගුල අබල කරන්න"</string>
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"යතුරු අගුල සහ ඕනෑම සම්බන්ධිත මුරපද ආරක්ෂාවක් අබල කිරීමට යෙදුමට අවසර දෙන්න. මෙහි උදාහරණයක් වන්නේ පැමිණෙන ඇමතුමක් ලැබෙද්දී, දුරකථනය අක්‍රිය වන අතර ඇමතුම අවසාන වන විට යතුරු අගුල නැවත සක්‍රිය වෙයි."</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"තිර අඟුලු සංකීර්ණතාව ඉල්ලන්න"</string>
@@ -620,6 +622,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"ජීවමිතික දෘඪාංග ලබා ගත නොහැකිය"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"සත්‍යාපනය අවලංගු කළා"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"හඳුනා නොගන්නා ලදී"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"මුහුණ හඳුනා නොගන්නා ලදි"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"සත්‍යාපනය අවලංගු කළා"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"රහස් අංක, රටා, හෝ මුරපද කිසිවක් සකසා නැත"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"සත්‍යාපනය කිරීමේ දෝෂයකි"</string>
@@ -1393,7 +1396,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"ප්‍රතික්ෂේප කරන්න"</string>
     <string name="select_input_method" msgid="3971267998568587025">"ආදාන ක්‍රමයක් තෝරන්න"</string>
     <string name="show_ime" msgid="6406112007347443383">"භෞතික යතුරු පුවරුව සක්‍රිය අතරතුර එය තිරය මත තබා ගන්න"</string>
-    <string name="hardware" msgid="1800597768237606953">"අතථ්‍ය යතුරු පුවරුව පෙන්වන්න"</string>
+    <string name="hardware" msgid="3611039921284836033">"තිරය මත යතුරු පුවරුව භාවිතය"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"<xliff:g id="DEVICE_NAME">%s</xliff:g> වින්‍යාස කරන්න"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"භෞතික යතුරුපුවරුව වින්‍යාස කරන්න"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"භාෂාව හා පිරිසැලසුම තේරීමට තට්ටු කරන්න"</string>
@@ -1956,7 +1959,7 @@
     <string name="app_blocked_title" msgid="7353262160455028160">"යෙදුම ලබා ගත නොහැකිය"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> මේ දැන් ලබා ගත නොහැකිය."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> නොතිබේ"</string>
-    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="4483161748582966785">"අවසරය අවශ්‍යයි"</string>
+    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="3805704317624448487">"අවසර ඉල්ලීම වළක්වා ඇත"</string>
     <string name="app_streaming_blocked_title_for_camera_dialog" msgid="3935701653713853065">"කැමරාව ලබා ගත නොහැකිය"</string>
     <string name="app_streaming_blocked_title_for_fingerprint_dialog" msgid="3516853717714141951">"දුරකථනයෙන් දිගටම කර ගෙන යන්න"</string>
     <string name="app_streaming_blocked_title_for_microphone_dialog" msgid="544822455127171206">"මයික්‍රෆෝනය ලබා ගත නොහැකිය"</string>
@@ -1967,6 +1970,9 @@
     <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"මේ අවස්ථාවේදී මෙයට ඔබගේ <xliff:g id="DEVICE">%1$s</xliff:g> හි ප්‍රවේශ විය නොහැකිය. ඒ වෙනුවට ඔබගේ Android TV උපාංගයෙහි උත්සාහ කරන්න."</string>
     <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"මේ අවස්ථාවේදී මෙයට ඔබගේ <xliff:g id="DEVICE">%1$s</xliff:g> හි ප්‍රවේශ විය නොහැකිය. ඒ වෙනුවට ඔබගේ ටැබ්ලටයෙහි උත්සාහ කරන්න."</string>
     <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"මේ අවස්ථාවේදී මෙයට ඔබගේ <xliff:g id="DEVICE">%1$s</xliff:g> හි ප්‍රවේශ විය නොහැකිය. ඒ වෙනුවට ඔබගේ දුරකථනයෙහි උත්සාහ කරන්න."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tv" msgid="4706276040125072077">"මෙම යෙදුම අතිරේක අවසර ඉල්ලා සිටින නමුත්, ප්‍රවාහ සැසියක අවසර ලබා දිය නොහැක. පළමුව ඔබේ Android TV උපාංගයෙන් අවසරය ලබා දෙන්න."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tablet" msgid="1824604581465771629">"මෙම යෙදුම අතිරේක අවසර ඉල්ලා සිටින නමුත්, ප්‍රවාහ සැසියක අවසර ලබා දිය නොහැක. පළමුව ඔබේ ටැබ්ලටයෙන් අවසරය ලබා දෙන්න."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="default" msgid="7755223160363292105">"මෙම යෙදුම අතිරේක අවසර ඉල්ලා සිටින නමුත්, ප්‍රවාහ සැසියක අවසර ලබා දිය නොහැක. පළමුව ඔබේ දුරකථනයෙන් අවසරය ලබා දෙන්න."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"මෙම යෙදුම අමතර ආරක්ෂාවක් ඉල්ලා සිටී. ඒ වෙනුවට ඔබගේ Android TV උපාංගයෙහි උත්සාහ කරන්න."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"මෙම යෙදුම අමතර ආරක්ෂාවක් ඉල්ලා සිටී. ඒ වෙනුවට ඔබගේ ටැබ්ලටයෙහි උත්සාහ කරන්න."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"මෙම යෙදුම අමතර ආරක්ෂාවක් ඉල්ලා සිටී. ඒ වෙනුවට ඔබගේ දුරකථනයෙහි උත්සාහ කරන්න."</string>
diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml
index d4cfcf9..02e6959 100644
--- a/core/res/res/values-sk/strings.xml
+++ b/core/res/res/values-sk/strings.xml
@@ -592,6 +592,8 @@
     <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"Umožňuje aplikácii získavať preferované informácie platenej služby NFC, napríklad o registrovanej pomoci a trasách k cieľu."</string>
     <string name="permlab_nfc" msgid="1904455246837674977">"ovládať technológiu NFC"</string>
     <string name="permdesc_nfc" msgid="8352737680695296741">"Umožňuje aplikácii komunikovať so značkami, kartami a čítačkami s podporou technológie NFC."</string>
+    <string name="permlab_nfcTransactionEvent" msgid="5868209446710407679">"Udalosť transakcie bezpečnostného prvku"</string>
+    <string name="permdesc_nfcTransactionEvent" msgid="1904286701876487397">"Umožňuje aplikácii dostávať informácie o transakciách prebiehajúcich v bezpečnostnom prvku."</string>
     <string name="permlab_disableKeyguard" msgid="3605253559020928505">"deaktivácia zámky obrazovky"</string>
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"Umožňuje aplikácii zakázať uzamknutie klávesnice a akékoľvek súvisiace zabezpečenie heslom. Príkladom je zakázanie uzamknutia klávesnice pri prichádzajúcom telefonickom hovore a jeho opätovné povolenie po skončení hovoru."</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"požadovať zložitosť zámky obrazovky"</string>
@@ -622,6 +624,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Biometrický hardvér nie je k dispozícii"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Overenie bolo zrušené"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Nerozpoznané"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Tvár nebola rozpoznaná"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Overenie bolo zrušené"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"Nie je nastavený PIN, vzor ani heslo"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Chyba overenia"</string>
@@ -1395,7 +1398,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"ODMIETNUŤ"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Zvoliť metódu vstupu"</string>
     <string name="show_ime" msgid="6406112007347443383">"Ponechať na obrazovke, keď je aktívna fyzická klávesnica"</string>
-    <string name="hardware" msgid="1800597768237606953">"Zobraziť virtuálnu klávesnicu"</string>
+    <string name="hardware" msgid="3611039921284836033">"Použiť klávesnicu na obrazovke"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"Nakonfigurujte <xliff:g id="DEVICE_NAME">%s</xliff:g>"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Nakonfigurujte fyzické klávesnice"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Klepnutím vyberte jazyk a rozloženie"</string>
@@ -1958,7 +1961,7 @@
     <string name="app_blocked_title" msgid="7353262160455028160">"Aplikácia nie je dostupná"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"Aplikácia <xliff:g id="APP_NAME">%1$s</xliff:g> nie je teraz dostupná."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> nie je k dispozícii"</string>
-    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="4483161748582966785">"Vyžaduje sa povolenie"</string>
+    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="3805704317624448487">"Žiadosť o povolenie bola zablokovaná"</string>
     <string name="app_streaming_blocked_title_for_camera_dialog" msgid="3935701653713853065">"Kamera nie je k dispozícii"</string>
     <string name="app_streaming_blocked_title_for_fingerprint_dialog" msgid="3516853717714141951">"Pokračujte v telefóne"</string>
     <string name="app_streaming_blocked_title_for_microphone_dialog" msgid="544822455127171206">"Mikrofón nie je k dispozícii"</string>
@@ -1969,6 +1972,9 @@
     <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"V zariadení <xliff:g id="DEVICE">%1$s</xliff:g> momentálne nemáte k tomuto obsahu prístup. Skúste namiesto toho použiť zariadenie Android TV."</string>
     <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"V zariadení <xliff:g id="DEVICE">%1$s</xliff:g> momentálne nemáte k tomuto obsahu prístup. Skúste namiesto toho použiť tablet."</string>
     <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"V zariadení <xliff:g id="DEVICE">%1$s</xliff:g> momentálne nemáte k tomuto obsahu prístup. Skúste použiť telefón."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tv" msgid="4706276040125072077">"Táto aplikácia požaduje ďalšie povolenia, ale povolenia nie je možné udeliť v relácii streamovania. Najprv udeľte povolenie v zariadení Android TV."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tablet" msgid="1824604581465771629">"Táto aplikácia požaduje ďalšie povolenia, ale povolenia nie je možné udeliť v relácii streamovania. Najprv udeľte povolenie v tablete."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="default" msgid="7755223160363292105">"Táto aplikácia požaduje ďalšie povolenia, ale povolenia nie je možné udeliť v relácii streamovania. Najprv udeľte povolenie v telefóne."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"Táto aplikácia požaduje dodatočné zabezpečenie. Skúste namiesto toho použiť zariadenie Android TV."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"Táto aplikácia požaduje dodatočné zabezpečenie. Skúste namiesto toho použiť tablet."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"Táto aplikácia požaduje dodatočné zabezpečenie. Skúste použiť telefón."</string>
diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml
index 1b06b0f..d3acfba 100644
--- a/core/res/res/values-sl/strings.xml
+++ b/core/res/res/values-sl/strings.xml
@@ -592,6 +592,8 @@
     <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"Aplikaciji omogoča pridobivanje podatkov o prednostni storitvi za plačevanje prek povezave NFC, kot so registrirani pripomočki in cilj preusmeritve."</string>
     <string name="permlab_nfc" msgid="1904455246837674977">"nadzor nad komunikacijo s tehnologijo bližnjega polja"</string>
     <string name="permdesc_nfc" msgid="8352737680695296741">"Podpira komunikacijo med računalnikom in oznakami, karticami in bralniki komunikacije s tehnologijo bližnjega polja."</string>
+    <string name="permlab_nfcTransactionEvent" msgid="5868209446710407679">"Dogodek transakcije prek varnostnega elementa"</string>
+    <string name="permdesc_nfcTransactionEvent" msgid="1904286701876487397">"Aplikaciji omogoča prejemanje podatkov o transakcijah, ki potekajo prek varnostnega elementa."</string>
     <string name="permlab_disableKeyguard" msgid="3605253559020928505">"onemogočanje zaklepanja zaslona"</string>
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"Aplikaciji dovoljuje, da onemogoči zaklep tipkovnice in morebitno povezano varnostno geslo. Telefon na primer onemogoči zaklep tipkovnice pri dohodnem klicu ter vnovič omogoči zaklep, ko je klic končan."</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"zahteva zapletenost zaklepanja zaslona"</string>
@@ -622,6 +624,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Strojna oprema za biometrične podatke ni na voljo"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Preverjanje pristnosti je preklicano"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Ni prepoznano"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Obraz ni prepoznan"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Preverjanje pristnosti je preklicano"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"Nastavljena ni nobena koda PIN, vzorec ali geslo"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Napaka pri preverjanju pristnosti"</string>
@@ -1395,7 +1398,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"NE SPREJMEM"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Izberite način vnosa"</string>
     <string name="show_ime" msgid="6406112007347443383">"Ohrani na zaslonu, dokler je aktivna fizična tipkovnica"</string>
-    <string name="hardware" msgid="1800597768237606953">"Pokaži navidezno tipkovnico"</string>
+    <string name="hardware" msgid="3611039921284836033">"Uporaba zaslonske tipkovnice"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"Konfiguriranje naprave <xliff:g id="DEVICE_NAME">%s</xliff:g>"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Konfiguriranje fizičnih tipkovnic"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Dotaknite se, če želite izbrati jezik in postavitev."</string>
@@ -1958,7 +1961,7 @@
     <string name="app_blocked_title" msgid="7353262160455028160">"Aplikacija ni na voljo"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> trenutno ni na voljo."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"»<xliff:g id="ACTIVITY">%1$s</xliff:g>« ni na voljo"</string>
-    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="4483161748582966785">"Potrebno je dovoljenje"</string>
+    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="3805704317624448487">"Zahteva za dovoljenje je bila prezrta"</string>
     <string name="app_streaming_blocked_title_for_camera_dialog" msgid="3935701653713853065">"Fotoaparat ni na voljo"</string>
     <string name="app_streaming_blocked_title_for_fingerprint_dialog" msgid="3516853717714141951">"Nadaljevanje v telefonu"</string>
     <string name="app_streaming_blocked_title_for_microphone_dialog" msgid="544822455127171206">"Mikrofon ni na voljo"</string>
@@ -1969,6 +1972,9 @@
     <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"V napravi <xliff:g id="DEVICE">%1$s</xliff:g> trenutno ni mogoče dostopati do te vsebine. Poskusite z napravo Android TV."</string>
     <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"V napravi <xliff:g id="DEVICE">%1$s</xliff:g> trenutno ni mogoče dostopati do te vsebine. Poskusite s tabličnim računalnikom."</string>
     <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"V napravi <xliff:g id="DEVICE">%1$s</xliff:g> trenutno ni mogoče dostopati do te vsebine. Poskusite s telefonom."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tv" msgid="4706276040125072077">"Ta aplikacija zahteva dodatna dovoljenja, vendar teh ni mogoče odobriti med sejo pretočnega predvajanja. Dovoljenje najprej odobrite v napravi Android TV."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tablet" msgid="1824604581465771629">"Ta aplikacija zahteva dodatna dovoljenja, vendar teh ni mogoče odobriti med sejo pretočnega predvajanja. Dovoljenje najprej odobrite v tabličnem računalniku."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="default" msgid="7755223160363292105">"Ta aplikacija zahteva dodatna dovoljenja, vendar teh ni mogoče odobriti med sejo pretočnega predvajanja. Dovoljenje najprej odobrite v telefonu."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"Ta aplikacija zahteva dodatno varnost. Poskusite z napravo Android TV."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"Ta aplikacija zahteva dodatno varnost. Poskusite s tabličnim računalnikom."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"Ta aplikacija zahteva dodatno varnost. Poskusite s telefonom."</string>
diff --git a/core/res/res/values-sq/strings.xml b/core/res/res/values-sq/strings.xml
index 82d92c1..228ce16 100644
--- a/core/res/res/values-sq/strings.xml
+++ b/core/res/res/values-sq/strings.xml
@@ -590,6 +590,8 @@
     <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"Lejon aplikacionin të marrë informacione për shërbimin e preferuar të pagesës me NFC si p.sh. ndihmat e regjistruara dhe destinacionin e itinerarit."</string>
     <string name="permlab_nfc" msgid="1904455246837674977">"kontrollo \"Komunikimin e fushës në afërsi\" NFC"</string>
     <string name="permdesc_nfc" msgid="8352737680695296741">"Lejon aplikacionin të komunikojë me etiketimet e \"Komunikimit të fushës së afërt (NFC)\", kartat dhe lexuesit."</string>
+    <string name="permlab_nfcTransactionEvent" msgid="5868209446710407679">"Ngjarje transaksioni me elementin e sigurt"</string>
+    <string name="permdesc_nfcTransactionEvent" msgid="1904286701876487397">"Lejon që aplikacioni të marrë informacione për transaksionet që kryhen në një element të sigurt."</string>
     <string name="permlab_disableKeyguard" msgid="3605253559020928505">"çaktivizo kyçjen e ekranit"</string>
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"Lejon aplikacionin të çaktivizojë kyçjen e tasteve dhe çdo mbrojtje të lidhur me fjalëkalimin. Për shembull, telefoni çaktivizon kyçjen e tasteve kur merr një telefonatë hyrëse, pastaj riaktivizon kyçjen e tasteve kur mbaron telefonata."</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"kërko kompleksitetin e kyçjes së ekranit"</string>
@@ -620,6 +622,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Nuk ofrohet harduer biometrik"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Vërtetimi u anulua"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Nuk njihet"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Fytyra nuk njihet"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Vërtetimi u anulua"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"Nuk është vendosur kod PIN, motiv ose fjalëkalim"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Gabim gjatë vërtetimit"</string>
@@ -1393,7 +1396,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"REFUZO"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Zgjidh metodën e hyrjes"</string>
     <string name="show_ime" msgid="6406112007347443383">"Mbaje në ekran ndërsa tastiera fizike është aktive"</string>
-    <string name="hardware" msgid="1800597768237606953">"Shfaq tastierën virtuale"</string>
+    <string name="hardware" msgid="3611039921284836033">"Përdor tastierën në ekran"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"Konfiguro <xliff:g id="DEVICE_NAME">%s</xliff:g>"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Konfiguro tastierat fizike"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Trokit për të zgjedhur gjuhën dhe strukturën"</string>
@@ -1956,7 +1959,7 @@
     <string name="app_blocked_title" msgid="7353262160455028160">"Aplikacioni nuk ofrohet"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> nuk ofrohet për momentin."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> nuk ofrohet"</string>
-    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="4483161748582966785">"Kërkohet leje"</string>
+    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="3805704317624448487">"Kërkesa për leje është ndaluar"</string>
     <string name="app_streaming_blocked_title_for_camera_dialog" msgid="3935701653713853065">"Kamera nuk ofrohet"</string>
     <string name="app_streaming_blocked_title_for_fingerprint_dialog" msgid="3516853717714141951">"Vazhdo në telefon"</string>
     <string name="app_streaming_blocked_title_for_microphone_dialog" msgid="544822455127171206">"Mikrofoni nuk ofrohet"</string>
@@ -1967,6 +1970,9 @@
     <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"Qasja është e pamundur në <xliff:g id="DEVICE">%1$s</xliff:g> për momentin. Provoje në pajisjen Android TV më mirë."</string>
     <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"Qasja është e pamundur në <xliff:g id="DEVICE">%1$s</xliff:g> për momentin. Provoje në tablet më mirë."</string>
     <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"Qasja është e pamundur në <xliff:g id="DEVICE">%1$s</xliff:g> për momentin. Provoje në telefon më mirë."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tv" msgid="4706276040125072077">"Ky aplikacion po kërkon leje shtesë, por lejet nuk mund të jepen në një seancë transmetimi. Fillimisht jep lejen në pajisjen tënde Android TV."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tablet" msgid="1824604581465771629">"Ky aplikacion po kërkon leje shtesë, por lejet nuk mund të jepen në një seancë transmetimi. Fillimisht jep lejen në tabletin tënd."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="default" msgid="7755223160363292105">"Ky aplikacion po kërkon leje shtesë, por lejet nuk mund të jepen në një seancë transmetimi. Fillimisht jep lejen në telefonin tënd."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"Ky aplikacion po kërkon siguri shtesë. Provoje në pajisjen Android TV më mirë."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"Ky aplikacion po kërkon siguri shtesë. Provoje në tablet më mirë."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"Ky aplikacion po kërkon siguri shtesë. Provoje në telefon më mirë."</string>
diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml
index ef2456e..84ead15 100644
--- a/core/res/res/values-sr/strings.xml
+++ b/core/res/res/values-sr/strings.xml
@@ -591,6 +591,8 @@
     <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"Дозвољава апликацији да преузима информације о жељеној NFC услузи за плаћање, попут регистрованих идентификатора апликација и одредишта преусмеравања."</string>
     <string name="permlab_nfc" msgid="1904455246837674977">"контрола комуникације у ужем пољу (Near Field Communication)"</string>
     <string name="permdesc_nfc" msgid="8352737680695296741">"Дозвољава апликацији да комуницира са ознакама, картицама и читачима комуникације кратког домета (NFC)."</string>
+    <string name="permlab_nfcTransactionEvent" msgid="5868209446710407679">"Трансакција у оквиру безбедносног елемента"</string>
+    <string name="permdesc_nfcTransactionEvent" msgid="1904286701876487397">"Омогућава апликацији да добија информације о транскацијама које се извршавају у оквиру безбедносног елемента."</string>
     <string name="permlab_disableKeyguard" msgid="3605253559020928505">"онемогућавање закључавања екрана"</string>
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"Дозвољава апликацији да онемогући закључавање тастатуре и све повезане безбедносне мере са лозинкама. На пример, телефон онемогућава закључавање тастатуре при пријему долазног телефонског позива, а затим га поново омогућава по завршетку позива."</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"тражење сложености закључавања екрана"</string>
@@ -621,6 +623,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Биометријски хардвер није доступан"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Потврда идентитета је отказана"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Није препознато"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Лице није препознато"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Потврда идентитета је отказана"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"Нисте подесили ни PIN, ни шаблон, ни лозинку"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Грешка при потврди идентитета"</string>
@@ -1394,7 +1397,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"ОДБИЈ"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Избор метода уноса"</string>
     <string name="show_ime" msgid="6406112007347443383">"Задржава се на екрану док је физичка тастатура активна"</string>
-    <string name="hardware" msgid="1800597768237606953">"Прикажи виртуелну тастатуру"</string>
+    <string name="hardware" msgid="3611039921284836033">"Користи тастатуру на екрану"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"Конфигуришите уређај <xliff:g id="DEVICE_NAME">%s</xliff:g>"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Конфигуришите физичке тастатуре"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Додирните да бисте изабрали језик и распоред"</string>
@@ -1957,7 +1960,7 @@
     <string name="app_blocked_title" msgid="7353262160455028160">"Апликација није доступна"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"Апликација <xliff:g id="APP_NAME">%1$s</xliff:g> тренутно није доступна."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> – није доступно"</string>
-    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="4483161748582966785">"Потребна је дозвола"</string>
+    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="3805704317624448487">"Захтев за дозволу је блокиран"</string>
     <string name="app_streaming_blocked_title_for_camera_dialog" msgid="3935701653713853065">"Камера није доступна"</string>
     <string name="app_streaming_blocked_title_for_fingerprint_dialog" msgid="3516853717714141951">"Наставите на телефону"</string>
     <string name="app_streaming_blocked_title_for_microphone_dialog" msgid="544822455127171206">"Микрофон је недоступан"</string>
@@ -1968,6 +1971,9 @@
     <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"Овој апликацији тренутно не може да се приступи са уређаја <xliff:g id="DEVICE">%1$s</xliff:g>. Пробајте на Android TV уређају."</string>
     <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"Овој апликацији тренутно не може да се приступи са уређаја <xliff:g id="DEVICE">%1$s</xliff:g>. Пробајте на таблету."</string>
     <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"Овој апликацији тренутно не може да се приступи са уређаја <xliff:g id="DEVICE">%1$s</xliff:g>. Пробајте на телефону."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tv" msgid="4706276040125072077">"Ова апликација захтева додатне дозволе, али дозволе не могу да се дају у сесији стримовања. Прво дајте дозволу на Android TV уређају."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tablet" msgid="1824604581465771629">"Ова апликација захтева додатне дозволе, али дозволе не могу да се дају у сесији стримовања. Прво дајте дозволу на таблету."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="default" msgid="7755223160363292105">"Ова апликација захтева додатне дозволе, али дозволе не могу да се дају у сесији стримовања. Прво дајте дозволу на телефону."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"Ова апликација захтева додатну безбедност. Пробајте на Android TV уређају."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"Ова апликација захтева додатну безбедност. Пробајте на таблету."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"Ова апликација захтева додатну безбедност. Пробајте на телефону."</string>
diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml
index c40b205..3739f1f 100644
--- a/core/res/res/values-sv/strings.xml
+++ b/core/res/res/values-sv/strings.xml
@@ -590,6 +590,8 @@
     <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"Tillåter att appen hämtar information kopplad till standardtjänsten för NFC-betalning, till exempel registrerade hjälpmedel och ruttdestinationer."</string>
     <string name="permlab_nfc" msgid="1904455246837674977">"kontrollera närfältskommunikationen"</string>
     <string name="permdesc_nfc" msgid="8352737680695296741">"Tillåter att appen kommunicerar med etiketter, kort och läsare för närfältskommunikation (NFC)."</string>
+    <string name="permlab_nfcTransactionEvent" msgid="5868209446710407679">"Transaktionshändelse i ett säkert element"</string>
+    <string name="permdesc_nfcTransactionEvent" msgid="1904286701876487397">"Tillåter att appen tar emot information om transaktioner som sker i ett säkert element."</string>
     <string name="permlab_disableKeyguard" msgid="3605253559020928505">"inaktivera skärmlåset"</string>
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"Tillåter att appen inaktiverar tangentlåset och tillhörande lösenordsskydd. Ett exempel kan vara att tangentlåset inaktiveras vid inkommande samtal och aktiveras igen när samtalet är avslutat."</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"begär komplexitetsnivå för skärmlåset"</string>
@@ -620,6 +622,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Biometrisk maskinvara är inte tillgänglig"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Autentiseringen avbröts"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Identifierades inte"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Ansiktet känns inte igen"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Autentiseringen avbröts"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"Pinkod, mönster eller lösenord har inte angetts"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Ett fel uppstod vid autentiseringen"</string>
@@ -1393,7 +1396,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"AVVISA"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Välj inmatningsmetod"</string>
     <string name="show_ime" msgid="6406112007347443383">"Ha kvar det på skärmen när det fysiska tangentbordet används"</string>
-    <string name="hardware" msgid="1800597768237606953">"Visa virtuellt tangentbord"</string>
+    <string name="hardware" msgid="3611039921284836033">"Använd skärmtangentbord"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"Konfigurera <xliff:g id="DEVICE_NAME">%s</xliff:g>"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Konfigurera fysiska tangentbord"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Tryck om du vill välja språk och layout"</string>
@@ -1956,7 +1959,7 @@
     <string name="app_blocked_title" msgid="7353262160455028160">"Appen är inte tillgänglig"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> är inte tillgängligt just nu."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> är inte tillgänglig"</string>
-    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="4483161748582966785">"Behörighet krävs"</string>
+    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="3805704317624448487">"Begäran om behörighet har dolts"</string>
     <string name="app_streaming_blocked_title_for_camera_dialog" msgid="3935701653713853065">"Kameran är inte tillgänglig"</string>
     <string name="app_streaming_blocked_title_for_fingerprint_dialog" msgid="3516853717714141951">"Fortsätt på telefonen"</string>
     <string name="app_streaming_blocked_title_for_microphone_dialog" msgid="544822455127171206">"Mikrofonen är inte tillgänglig"</string>
@@ -1967,6 +1970,9 @@
     <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"Det går inte att streama detta till <xliff:g id="DEVICE">%1$s</xliff:g> för närvarande. Testa med Android TV-enheten i stället."</string>
     <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"Det går inte att streama detta till <xliff:g id="DEVICE">%1$s</xliff:g> för närvarande. Testa med surfplattan i stället."</string>
     <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"Det går inte att streama detta till <xliff:g id="DEVICE">%1$s</xliff:g> för närvarande. Testa med telefonen i stället."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tv" msgid="4706276040125072077">"Appen begär ytterligare behörigheter, men det går inte att bevilja behörigheter under streamingsessionen. Bevilja behörigheten på Android TV-enheten först."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tablet" msgid="1824604581465771629">"Appen begär ytterligare behörigheter, men det går inte att bevilja behörigheter under streamingsessionen. Bevilja behörigheten på surfplattan först."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="default" msgid="7755223160363292105">"Appen begär ytterligare behörigheter, men det går inte att bevilja behörigheter under streamingsessionen. Bevilja behörigheten på telefonen först."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"Appen begär ytterligare säkerhet. Testa med Android TV-enheten i stället."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"Appen begär ytterligare säkerhet. Testa med surfplattan i stället."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"Appen begär ytterligare säkerhet. Testa med telefonen i stället."</string>
diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml
index caf09cb..86beafe 100644
--- a/core/res/res/values-sw/strings.xml
+++ b/core/res/res/values-sw/strings.xml
@@ -590,6 +590,8 @@
     <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"Huruhusu programu kupata maelezo ya huduma inayopendelewa ya malipo ya nfc kama vile huduma zilizosajiliwa na njia."</string>
     <string name="permlab_nfc" msgid="1904455246837674977">"kudhibiti Mawasiliano ya Vifaa Vilivyokaribu (NFC)"</string>
     <string name="permdesc_nfc" msgid="8352737680695296741">"Inaruhusu programu kuwasiliana na lebo, kadi na wasomaji wa Near Field Communication (NFC)."</string>
+    <string name="permlab_nfcTransactionEvent" msgid="5868209446710407679">"Muamala kupitia Kipengele Salama"</string>
+    <string name="permdesc_nfcTransactionEvent" msgid="1904286701876487397">"Huruhusu programu kupokea maelezo kuhusu miamala inayofanyika kupitia Kipengele Salama."</string>
     <string name="permlab_disableKeyguard" msgid="3605253559020928505">"zima kufuli la skrini yako"</string>
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"Inaruhusu programu kulemaza ufunguo wa vitufe na usalama mwingine ambata wa nenosiri. Kwa mfano, simu inalemaza ufunguo wa viitufe inapopokea simu inayoingia, kisha inawezesha upya ufunguo wa vitufe wakati simu inapokamilika."</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"iombe kiwango cha uchangamano wa kufunga skrini"</string>
@@ -620,6 +622,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Maunzi ya bayometriki hayapatikani"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Imeghairi uthibitishaji"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Hayatambuliki"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Imeshindwa kutambua uso"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Imeghairi uthibitishaji"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"Hujaweka pin, mchoro au nenosiri"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Hitilafu imetokea wakati wa uthibitishaji"</string>
@@ -1393,7 +1396,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"KATAA"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Chagua njia ya ingizo"</string>
     <string name="show_ime" msgid="6406112007347443383">"Ionyeshe kwenye skrini wakati kibodi halisi inatumika"</string>
-    <string name="hardware" msgid="1800597768237606953">"Onyesha kibodi pepe"</string>
+    <string name="hardware" msgid="3611039921284836033">"Tumia kibodi ya skrini"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"Wekea mipangilio <xliff:g id="DEVICE_NAME">%s</xliff:g>"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Wekea kibodi halisi mipangilio"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Gusa ili uchague lugha na muundo"</string>
@@ -1956,7 +1959,7 @@
     <string name="app_blocked_title" msgid="7353262160455028160">"Programu haipatikani"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> haipatikani hivi sasa."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> haipatikani"</string>
-    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="4483161748582966785">"Ruhusa inahitajika"</string>
+    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="3805704317624448487">"Ombi la ruhusa limekataliwa"</string>
     <string name="app_streaming_blocked_title_for_camera_dialog" msgid="3935701653713853065">"Kamera haipatikani"</string>
     <string name="app_streaming_blocked_title_for_fingerprint_dialog" msgid="3516853717714141951">"Endelea kwenye simu"</string>
     <string name="app_streaming_blocked_title_for_microphone_dialog" msgid="544822455127171206">"Maikrofoni haipatikani"</string>
@@ -1967,6 +1970,9 @@
     <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"Programu hii haiwezi kufikiwa kwenye <xliff:g id="DEVICE">%1$s</xliff:g> kwa muda huu. Badala yake jaribu kwenye kifaa chako cha Android TV."</string>
     <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"Programu hii haiwezi kufikiwa kwenye <xliff:g id="DEVICE">%1$s</xliff:g> kwa muda huu. Badala yake jaribu kwenye kompyuta kibao yako."</string>
     <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"Programu hii haiwezi kufikiwa kwenye <xliff:g id="DEVICE">%1$s</xliff:g> kwa muda huu. Badala yake jaribu kwenye simu yako."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tv" msgid="4706276040125072077">"Programu hii inaomba ruhusa za ziada. Hata hivyo, huwezi kutoa ruhusa ukitiririsha. Ruhusu kwanza kwenye kifaa chako cha Android TV."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tablet" msgid="1824604581465771629">"Programu hii inaomba ruhusa za ziada. Hata hivyo, huwezi kutoa ruhusa ukitiririsha. Ruhusu kwenye kishikwambi chako kwanza."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="default" msgid="7755223160363292105">"Programu hii inaomba ruhusa za ziada. Hata hivyo, huwezi kutoa ruhusa ukitiririsha. Ruhusu kwenye simu yako kwanza."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"Programu hii inaomba usalama wa ziada. Badala yake jaribu kwenye kifaa chako cha Android TV."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"Programu hii inaomba usalama wa ziada. Badala yake jaribu kwenye kompyuta yako kibao."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"Programu hii inaomba usalama wa ziada. Badala yake jaribu kwenye simu yako."</string>
diff --git a/core/res/res/values-ta/strings.xml b/core/res/res/values-ta/strings.xml
index 4c765ed..783b709 100644
--- a/core/res/res/values-ta/strings.xml
+++ b/core/res/res/values-ta/strings.xml
@@ -590,6 +590,8 @@
     <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"பதிவுசெய்யப்பட்ட கருவிகள், சேருமிடத்திற்கான வழி போன்ற விருப்பமான NFC பேமெண்ட் சேவை தொடர்பான தகவல்களைப் பெற ஆப்ஸை அனுமதிக்கிறது."</string>
     <string name="permlab_nfc" msgid="1904455246837674977">"குறுகிய இடைவெளி தகவல்பரிமாற்றத்தைக் கட்டுப்படுத்துதல்"</string>
     <string name="permdesc_nfc" msgid="8352737680695296741">"குறுகிய இடைவெளி தகவல்பரிமாற்றம் (NFC), குறிகள், கார்டுகள் மற்றும் ரீடர்கள் ஆகியவற்றுடன் தொடர்புகொள்ள, ஆப்ஸை அனுமதிக்கிறது."</string>
+    <string name="permlab_nfcTransactionEvent" msgid="5868209446710407679">"பாதுகாப்பு உறுப்பில் நிகழ்கின்ற பணப் பரிமாற்ற நிகழ்வு"</string>
+    <string name="permdesc_nfcTransactionEvent" msgid="1904286701876487397">"பாதுகாப்பு உறுப்பில் நிகழ்கின்ற பணப் பரிமாற்றங்கள் குறித்த தகவல்களைப் பெற ஆப்ஸை அனுமதிக்கும்."</string>
     <string name="permlab_disableKeyguard" msgid="3605253559020928505">"உங்கள் திரைப் பூட்டை முடக்குதல்"</string>
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"விசைப்பூட்டையும், தொடர்புடைய கடவுச்சொல் பாதுகாப்பையும் முடக்கப் ஆப்ஸை அனுமதிக்கிறது. எடுத்துக்காட்டாக, உள்வரும் மொபைல் அழைப்பைப் பெறும்போது மொபைல் விசைப்பூட்டை முடக்குகிறது, பிறகு அழைப்பு முடிந்தவுடன் விசைப்பூட்டை மீண்டும் இயக்குகிறது."</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"திரைப் பூட்டு தொடர்பான சிக்கலைத் தீர்க்க அனுமதி கோருதல்"</string>
@@ -620,6 +622,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"பயோமெட்ரிக் வன்பொருள் இல்லை"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"அங்கீகரிப்பு ரத்தானது"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"அடையாளங்காணபடவில்லை"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"முகத்தைக் கண்டறிய முடியவில்லை"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"அங்கீகரிப்பு ரத்தானது"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"பின்னோ, பேட்டர்னோ, கடவுச்சொல்லோ அமைக்கப்படவில்லை"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"அங்கீகரிப்பதில் பிழை"</string>
@@ -1393,7 +1396,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"வேண்டாம்"</string>
     <string name="select_input_method" msgid="3971267998568587025">"உள்ளீட்டு முறையைத் தேர்வுசெய்க"</string>
     <string name="show_ime" msgid="6406112007347443383">"கைமுறை கீபோர்டு இயக்கத்தில் இருக்கும் போது IMEஐ திரையில் வைத்திரு"</string>
-    <string name="hardware" msgid="1800597768237606953">"விர்ச்சுவல் கீபோர்டை காட்டு"</string>
+    <string name="hardware" msgid="3611039921284836033">"ஸ்கிரீன் கீபோர்டை உபயோகி"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"<xliff:g id="DEVICE_NAME">%s</xliff:g> சாதனத்தை உள்ளமைத்தல்"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"கீபோர்டுகளை உள்ளமைத்தல்"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"மொழியையும் தளவமைப்பையும் தேர்ந்தெடுக்க, தட்டவும்"</string>
@@ -1956,7 +1959,7 @@
     <string name="app_blocked_title" msgid="7353262160455028160">"இந்த ஆப்ஸ் இப்போது கிடைப்பதில்லை"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> ஆப்ஸ் இப்போது கிடைப்பதில்லை."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> இல்லை"</string>
-    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="4483161748582966785">"அனுமதி தேவை"</string>
+    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="3805704317624448487">"அணுகல் கோரிக்கை முடக்கப்பட்டது"</string>
     <string name="app_streaming_blocked_title_for_camera_dialog" msgid="3935701653713853065">"கேமராவைப் பயன்படுத்த முடியாது"</string>
     <string name="app_streaming_blocked_title_for_fingerprint_dialog" msgid="3516853717714141951">"மொபைலில் தொடருங்கள்"</string>
     <string name="app_streaming_blocked_title_for_microphone_dialog" msgid="544822455127171206">"மைக்ரோஃபோனைப் பயன்படுத்த முடியாது"</string>
@@ -1967,6 +1970,9 @@
     <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"தற்போது உங்கள் <xliff:g id="DEVICE">%1$s</xliff:g> சாதனத்தில் இதை அணுக முடியாது. அதற்குப் பதிலாக Android TV சாதனத்தில் பயன்படுத்திப் பாருங்கள்."</string>
     <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"தற்போது உங்கள் <xliff:g id="DEVICE">%1$s</xliff:g> சாதனத்தில் இதை அணுக முடியாது. அதற்குப் பதிலாக உங்கள் டேப்லெட்டில் பயன்படுத்திப் பாருங்கள்."</string>
     <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"தற்போது உங்கள் <xliff:g id="DEVICE">%1$s</xliff:g> சாதனத்தில் இதை அணுக முடியாது. அதற்குப் பதிலாக உங்கள் மொபைலில் பயன்படுத்திப் பாருங்கள்."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tv" msgid="4706276040125072077">"இந்த ஆப்ஸ் கூடுதல் அனுமதிகளைக் கோருகிறது. ஆனால் ஸ்ட்ரீமிங் அமர்வில் அனுமதிகள் வழங்கப்படாது. முதலில் உங்கள் Android TV சாதனத்தில் அனுமதி வழங்கவும்."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tablet" msgid="1824604581465771629">"இந்த ஆப்ஸ் கூடுதல் அனுமதிகளைக் கோருகிறது. ஆனால் ஸ்ட்ரீமிங் அமர்வில் அனுமதிகள் வழங்கப்படாது. முதலில் உங்கள் டேப்லெட்டில் அனுமதி வழங்கவும்."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="default" msgid="7755223160363292105">"இந்த ஆப்ஸ் கூடுதல் அனுமதிகளைக் கோருகிறது. ஆனால் ஸ்ட்ரீமிங் அமர்வில் அனுமதிகள் வழங்கப்படாது. முதலில் உங்கள் மொபைலில் அனுமதி வழங்கவும்."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"இந்த ஆப்ஸ் கூடுதல் பாதுகாப்பைக் கோருகிறது. அதற்குப் பதிலாக உங்கள் Android TV சாதனத்தில் பயன்படுத்திப் பார்க்கவும்."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"இந்த ஆப்ஸ் கூடுதல் பாதுகாப்பைக் கோருகிறது. அதற்குப் பதிலாக உங்கள் டேப்லெட்டில் பயன்படுத்திப் பார்க்கவும்."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"இந்த ஆப்ஸ் கூடுதல் பாதுகாப்பைக் கோருகிறது. அதற்குப் பதிலாக உங்கள் மொபைலில் பயன்படுத்திப் பார்க்கவும்."</string>
diff --git a/core/res/res/values-te/strings.xml b/core/res/res/values-te/strings.xml
index 351acb2..634681e 100644
--- a/core/res/res/values-te/strings.xml
+++ b/core/res/res/values-te/strings.xml
@@ -590,6 +590,8 @@
     <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"ప్రాధాన్యత ఇవ్వబడిన NFC చెల్లింపు సేవల సమాచారాన్ని, అంటే రిజిస్టర్ చేయబడిన సహాయక సాధనాలు, మార్గం, గమ్యస్థానం వంటి వాటిని పొందేందుకు యాప్‌ను అనుమతిస్తుంది."</string>
     <string name="permlab_nfc" msgid="1904455246837674977">"సమీప క్షేత్ర కమ్యూనికేషన్‌ను నియంత్రించడం"</string>
     <string name="permdesc_nfc" msgid="8352737680695296741">"సమీప ఫీల్డ్ కమ్యూనికేషన్ (NFC) ట్యాగ్‌లు, కార్డులు మరియు రీడర్‌లతో కమ్యూనికేట్ చేయడానికి యాప్‌ను అనుమతిస్తుంది."</string>
+    <string name="permlab_nfcTransactionEvent" msgid="5868209446710407679">"సెక్యూర్ ఎలిమెంట్ లావాదేవీ ఈవెంట్"</string>
+    <string name="permdesc_nfcTransactionEvent" msgid="1904286701876487397">"సెక్యూర్ ఎలిమెంట్‌లో జరుగుతున్న లావాదేవీల గురించిన సమాచారాన్ని స్వీకరించడానికి యాప్‌ను అనుమతిస్తుంది."</string>
     <string name="permlab_disableKeyguard" msgid="3605253559020928505">"మీ స్క్రీన్ లాక్‌ను నిలిపివేయడం"</string>
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"కీలాక్‌ను, అలాగే ఏదైనా అనుబంధించబడిన పాస్‌వర్డ్ సెక్యూరిటీని డిజేబుల్ చేయడానికి యాప్‌ను అనుమతిస్తుంది. ఉదాహరణకు, ఇన్‌కమింగ్ ఫోన్ కాల్ వస్తున్నప్పుడు ఫోన్ కీలాక్‌ను డిజేబుల్ చేస్తుంది, ఆపై కాల్ ముగిసిన తర్వాత కీలాక్‌ను మళ్లీ ఎనేబుల్ చేస్తుంది."</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"స్క్రీన్ లాక్ సంక్లిష్టత రిక్వెస్ట్‌"</string>
@@ -620,6 +622,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"బయోమెట్రిక్ హార్డ్‌వేర్‌ అందుబాటులో లేదు"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"ప్రమాణీకరణ రద్దు చేయబడింది"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"గుర్తించలేదు"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"ముఖం గుర్తించబడలేదు"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"ప్రమాణీకరణ రద్దు చేయబడింది"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"పిన్, ఆకృతి లేదా పాస్‌వర్డ్‌ సెట్ చేయబడలేదు"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"ప్రామాణీకరిస్తున్నప్పుడు ఎర్రర్ ఏర్పడింది"</string>
@@ -1393,7 +1396,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"తిరస్కరిస్తున్నాను"</string>
     <string name="select_input_method" msgid="3971267998568587025">"ఇన్‌పుట్ పద్ధతిని ఎంచుకోండి"</string>
     <string name="show_ime" msgid="6406112007347443383">"దీన్ని భౌతిక కీబోర్డ్ యాక్టివ్‌గా ఉన్నప్పుడు స్క్రీన్‌పై ఉంచుతుంది"</string>
-    <string name="hardware" msgid="1800597768237606953">"వర్చువల్ కీబోర్డ్‌ను చూపు"</string>
+    <string name="hardware" msgid="3611039921284836033">"స్క్రీన్‌పై కీబోర్డ్ ఉపయోగించు"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"<xliff:g id="DEVICE_NAME">%s</xliff:g>‌ను కాన్ఫిగర్ చేయండి"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"ఫిజికల్ కీబోర్డ్‌లను కాన్ఫిగ‌ర్ చేయండి"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"భాషను, లేఅవుట్‌ను ఎంచుకోవడానికి ట్యాప్ చేయండి"</string>
@@ -1956,7 +1959,7 @@
     <string name="app_blocked_title" msgid="7353262160455028160">"యాప్ అందుబాటులో లేదు"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> ప్రస్తుతం అందుబాటులో లేదు."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> అందుబాటులో లేదు"</string>
-    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="4483161748582966785">"అనుమతి అవసరం"</string>
+    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="3805704317624448487">"అనుమతి రిక్వెస్ట్ బ్లాక్ చేయబడింది"</string>
     <string name="app_streaming_blocked_title_for_camera_dialog" msgid="3935701653713853065">"కెమెరా అందుబాటులో లేదు"</string>
     <string name="app_streaming_blocked_title_for_fingerprint_dialog" msgid="3516853717714141951">"ఫోన్‌లో కొనసాగించండి"</string>
     <string name="app_streaming_blocked_title_for_microphone_dialog" msgid="544822455127171206">"మైక్రోఫోన్ అందుబాటులో లేదు"</string>
@@ -1967,6 +1970,9 @@
     <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"ఈ సమయంలో మీ <xliff:g id="DEVICE">%1$s</xliff:g>లో దీన్ని యాక్సెస్ చేయడం సాధ్యపడదు. బదులుగా మీ Android TV పరికరంలో ట్రై చేయండి."</string>
     <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"ఈ సమయంలో మీ <xliff:g id="DEVICE">%1$s</xliff:g>లో దీన్ని యాక్సెస్ చేయడం సాధ్యపడదు. బదులుగా మీ టాబ్లెట్‌లో ట్రై చేయండి."</string>
     <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"ఈ సమయంలో మీ <xliff:g id="DEVICE">%1$s</xliff:g>లో దీన్ని యాక్సెస్ చేయడం సాధ్యపడదు. బదులుగా మీ ఫోన్‌లో ట్రై చేయండి."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tv" msgid="4706276040125072077">"ఈ యాప్ అదనపు అనుమతి కోసం రిక్వెస్ట్ చేస్తోంది, కానీ స్ట్రీమింగ్ సెషన్‌లో అనుమతులను మంజూరు చేయడం సాధ్యం కాదు. ముందుగా మీ Android TV పరికరంలో అనుమతిని మంజూరు చేయండి."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tablet" msgid="1824604581465771629">"ఈ యాప్ అదనపు అనుమతి కోసం రిక్వెస్ట్ చేస్తోంది, కానీ స్ట్రీమింగ్ సెషన్‌లో అనుమతులను మంజూరు చేయడం సాధ్యం కాదు. ముందుగా మీ టాబ్లెట్‌లో అనుమతిని మంజూరు చేయండి."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="default" msgid="7755223160363292105">"ఈ యాప్ అదనపు అనుమతి కోసం రిక్వెస్ట్ చేస్తోంది, కానీ స్ట్రీమింగ్ సెషన్‌లో అనుమతులను మంజూరు చేయడం సాధ్యం కాదు. ముందుగా మీ ఫోన్‌లో అనుమతిని మంజూరు చేయండి."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"ఈ యాప్ అదనపు సెక్యూరిటీ కోసం రిక్వెస్ట్ చేస్తోంది. బదులుగా మీ Android TV పరికరంలో ట్రై చేయండి."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"ఈ యాప్ అదనపు సెక్యూరిటీ కోసం రిక్వెస్ట్ చేస్తోంది. బదులుగా మీ టాబ్లెట్‌లో ట్రై చేయండి."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"ఈ యాప్ అదనపు సెక్యూరిటీ కోసం రిక్వెస్ట్ చేస్తోంది. బదులుగా మీ ఫోన్‌లో ట్రై చేయండి."</string>
diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml
index 107a66a..a987d4d 100644
--- a/core/res/res/values-th/strings.xml
+++ b/core/res/res/values-th/strings.xml
@@ -590,6 +590,8 @@
     <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"อนุญาตให้แอปรับข้อมูลบริการชำระเงิน NFC ที่ต้องการ เช่น รหัสแอป (AID) ที่ลงทะเบียนและปลายทางของเส้นทาง"</string>
     <string name="permlab_nfc" msgid="1904455246837674977">"ควบคุม Near Field Communication"</string>
     <string name="permdesc_nfc" msgid="8352737680695296741">"อนุญาตให้แอปพลิเคชันสื่อสารกับแท็ก Near Field Communication (NFC) การ์ด และโปรแกรมอ่าน"</string>
+    <string name="permlab_nfcTransactionEvent" msgid="5868209446710407679">"เหตุการณ์เกี่ยวกับธุรกรรมในองค์ประกอบความปลอดภัย"</string>
+    <string name="permdesc_nfcTransactionEvent" msgid="1904286701876487397">"อนุญาตให้แอปรับข้อมูลเกี่ยวกับธุรกรรมที่เกิดขึ้นในองค์ประกอบความปลอดภัย"</string>
     <string name="permlab_disableKeyguard" msgid="3605253559020928505">"ปิดใช้งานการล็อกหน้าจอของคุณ"</string>
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"อนุญาตให้แอปพลิเคชันปิดใช้งานการล็อกปุ่มกดและการรักษาความปลอดภัยด้วยรหัสผ่านใดๆ ที่เกี่ยวข้อง ตัวอย่างเช่น โทรศัพท์ปิดใช้งานการล็อกปุ่มกดเมื่อรับสายเรียกเข้า จากนั้นจึงเปิดใช้งานการล็อกปุ่มกดใหม่หลังจากวางสาย"</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"ขอความซับซ้อนของการล็อกหน้าจอ"</string>
@@ -620,6 +622,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"ฮาร์ดแวร์ไบโอเมตริกไม่พร้อมใช้งาน"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"ยกเลิกการตรวจสอบสิทธิ์แล้ว"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"ไม่รู้จัก"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"ไม่รู้จักใบหน้า"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"ยกเลิกการตรวจสอบสิทธิ์แล้ว"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"ไม่ได้ตั้ง PIN, รูปแบบ หรือรหัสผ่าน"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"การตรวจสอบข้อผิดพลาด"</string>
@@ -1393,7 +1396,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"ปฏิเสธ"</string>
     <string name="select_input_method" msgid="3971267998568587025">"เลือกวิธีการป้อนข้อมูล"</string>
     <string name="show_ime" msgid="6406112007347443383">"เปิดทิ้งไว้บนหน้าจอในระหว่างใช้งานแป้นพิมพ์จริง"</string>
-    <string name="hardware" msgid="1800597768237606953">"แสดงแป้นพิมพ์เสมือน"</string>
+    <string name="hardware" msgid="3611039921284836033">"ใช้แป้นพิมพ์บนหน้าจอ"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"กำหนดค่า <xliff:g id="DEVICE_NAME">%s</xliff:g>"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"กำหนดค่าแป้นพิมพ์จริง"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"แตะเพื่อเลือกภาษาและรูปแบบ"</string>
@@ -1956,7 +1959,7 @@
     <string name="app_blocked_title" msgid="7353262160455028160">"แอปไม่พร้อมใช้งาน"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> ไม่พร้อมใช้งานในขณะนี้"</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> ไม่พร้อมใช้งาน"</string>
-    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="4483161748582966785">"ต้องการสิทธิ์"</string>
+    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="3805704317624448487">"ระงับคำขอสิทธิ์อยู่"</string>
     <string name="app_streaming_blocked_title_for_camera_dialog" msgid="3935701653713853065">"กล้องไม่พร้อมใช้งาน"</string>
     <string name="app_streaming_blocked_title_for_fingerprint_dialog" msgid="3516853717714141951">"ดำเนินการต่อบนโทรศัพท์"</string>
     <string name="app_streaming_blocked_title_for_microphone_dialog" msgid="544822455127171206">"ไมโครโฟนไม่พร้อมใช้งาน"</string>
@@ -1967,6 +1970,9 @@
     <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"เข้าถึงแอปนี้ใน <xliff:g id="DEVICE">%1$s</xliff:g> ของคุณไม่ได้ในขณะนี้ โปรดลองเข้าถึงในอุปกรณ์ Android TV แทน"</string>
     <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"เข้าถึงแอปนี้ใน <xliff:g id="DEVICE">%1$s</xliff:g> ของคุณไม่ได้ในขณะนี้ โปรดลองเข้าถึงในแท็บเล็ตแทน"</string>
     <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"เข้าถึงแอปนี้ใน <xliff:g id="DEVICE">%1$s</xliff:g> ของคุณไม่ได้ในขณะนี้ โปรดลองเข้าถึงในโทรศัพท์แทน"</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tv" msgid="4706276040125072077">"แอปนี้มีการขอสิทธิ์เพิ่มเติม แต่ไม่สามารถให้สิทธิ์ในเซสชันที่สตรีมอยู่ โปรดให้สิทธิ์ในอุปกรณ์ Android TV ก่อน"</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tablet" msgid="1824604581465771629">"แอปนี้มีการขอสิทธิ์เพิ่มเติม แต่ไม่สามารถให้สิทธิ์ในเซสชันที่สตรีมอยู่ โปรดให้สิทธิ์ในแท็บเล็ตก่อน"</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="default" msgid="7755223160363292105">"แอปนี้มีการขอสิทธิ์เพิ่มเติม แต่ไม่สามารถให้สิทธิ์ในเซสชันที่สตรีมอยู่ โปรดให้สิทธิ์ในโทรศัพท์ก่อน"</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"แอปนี้มีการขอการรักษาความปลอดภัยเพิ่มเติม โปรดลองเข้าถึงในอุปกรณ์ Android TV แทน"</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"แอปนี้มีการขอการรักษาความปลอดภัยเพิ่มเติม โปรดลองเข้าถึงในแท็บเล็ตแทน"</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"แอปนี้มีการขอการรักษาความปลอดภัยเพิ่มเติม โปรดลองเข้าถึงในโทรศัพท์แทน"</string>
diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml
index 549882b..6d7a6fd 100644
--- a/core/res/res/values-tl/strings.xml
+++ b/core/res/res/values-tl/strings.xml
@@ -590,6 +590,8 @@
     <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"Pinapayagan ang app na makakuha ng impormasyon sa gustong nfc na serbisyo sa pagbabayad tulad ng mga nakarehistrong application ID at destinasyon ng ruta."</string>
     <string name="permlab_nfc" msgid="1904455246837674977">"kontrolin ang Near Field Communication"</string>
     <string name="permdesc_nfc" msgid="8352737680695296741">"Pinapayagan ang app na makipag-ugnay sa Near Field Communication (NFC) na mga tag, card, at reader."</string>
+    <string name="permlab_nfcTransactionEvent" msgid="5868209446710407679">"Event ng transaksyon sa Secure na Elemento"</string>
+    <string name="permdesc_nfcTransactionEvent" msgid="1904286701876487397">"Nagbibigay-daan sa app na makatanggap ng impormasyon tungkol sa mga transaksyong nangyayari sa isang Secure na Elemento."</string>
     <string name="permlab_disableKeyguard" msgid="3605253559020928505">"i-disable ang iyong screen lock"</string>
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"Pinapayagan ang app na i-disable ang keylock at anumang nauugnay na seguridad sa password. Halimbawa, hindi pinapagana ng telepono ang keylock kapag nakakatanggap ng papasok na tawag sa telepono, pagkatapos ay muling pinapagana ang keylock kapag tapos na ang tawag."</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"humiling ng pagiging kumplikado ng lock ng screen"</string>
@@ -620,6 +622,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Walang biometric hardware"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Nakansela ang pag-authenticate"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Hindi nakilala"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Hindi nakilala ang mukha"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Nakansela ang pag-authenticate"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"Walang itinakdang pin, pattern, o password"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Nagkaroon ng error sa pag-authenticate"</string>
@@ -1393,7 +1396,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"TANGGIHAN"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Pumili ng pamamaraan ng pag-input"</string>
     <string name="show_ime" msgid="6406112007347443383">"Panatilihin ito sa screen habang aktibo ang pisikal na keyboard"</string>
-    <string name="hardware" msgid="1800597768237606953">"Ipakita ang virtual keyboard"</string>
+    <string name="hardware" msgid="3611039921284836033">"Gumamit ng on-screen keyboard"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"I-configure ang <xliff:g id="DEVICE_NAME">%s</xliff:g>"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"I-configure ang mga pisikal na keyboard"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"I-tap upang pumili ng wika at layout"</string>
@@ -1956,7 +1959,7 @@
     <string name="app_blocked_title" msgid="7353262160455028160">"Hindi available ang app"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"Hindi available sa ngayon ang <xliff:g id="APP_NAME">%1$s</xliff:g>."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"Hindi available ang <xliff:g id="ACTIVITY">%1$s</xliff:g>"</string>
-    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="4483161748582966785">"Kailangan ng pahintulot"</string>
+    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="3805704317624448487">"Na-suppress ang kahilingan sa pahintulot"</string>
     <string name="app_streaming_blocked_title_for_camera_dialog" msgid="3935701653713853065">"Hindi available ang camera"</string>
     <string name="app_streaming_blocked_title_for_fingerprint_dialog" msgid="3516853717714141951">"Magpatuloy sa telepono"</string>
     <string name="app_streaming_blocked_title_for_microphone_dialog" msgid="544822455127171206">"Hindi available ang mikropono"</string>
@@ -1967,6 +1970,9 @@
     <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"Hindi ito maa-access sa iyong <xliff:g id="DEVICE">%1$s</xliff:g> sa ngayon. Subukan na lang sa iyong Android TV device."</string>
     <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"Hindi ito maa-access sa iyong <xliff:g id="DEVICE">%1$s</xliff:g> sa ngayon. Subukan na lang sa iyong tablet."</string>
     <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"Hindi ito maa-access sa iyong <xliff:g id="DEVICE">%1$s</xliff:g> sa ngayon. Subukan na lang sa iyong telepono."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tv" msgid="4706276040125072077">"Humihingi ang app na ito ng dagdag na pahintulot, pero hindi puwedeng ibigay ang mga pahintulot sa isang session ng streaming. Ibigay muna ang pahintulot sa iyong Android TV device."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tablet" msgid="1824604581465771629">"Humihingi ang app na ito ng dagdag na pahintulot, pero hindi puwedeng ibigay ang mga pahintulot sa isang session ng streaming. Ibigay muna ang pahintulot sa iyong tablet."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="default" msgid="7755223160363292105">"Humihingi ang app na ito ng dagdag na pahintulot, pero hindi puwedeng ibigay ang mga pahintulot sa isang session ng streaming. Ibigay muna ang pahintulot sa iyong telepono."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"Humihiling ng karagdagang seguridad ang app na ito. Subukan na lang sa iyong Android TV device."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"Humihiling ng karagdagang seguridad ang app na ito. Subukan na lang sa iyong tablet."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"Humihiling ng karagdagang seguridad ang app na ito. Subukan na lang sa iyong telepono."</string>
diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml
index f9f9c3a..329c718 100644
--- a/core/res/res/values-tr/strings.xml
+++ b/core/res/res/values-tr/strings.xml
@@ -590,6 +590,8 @@
     <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"Uygulamaya, kayıtlı yardımlar ve rota hedefi gibi tercih edilen NFC ödeme hizmeti bilgilerini alma izni verir."</string>
     <string name="permlab_nfc" msgid="1904455246837674977">"Yakın Alan İletişimini denetle"</string>
     <string name="permdesc_nfc" msgid="8352737680695296741">"Uygulamaya, Near Field Communication (NFC) etiketleri, kartlar ve okuyucular ile iletişim kurma izni verir."</string>
+    <string name="permlab_nfcTransactionEvent" msgid="5868209446710407679">"Güvenlik Unsuru işlemiyle ilgili etkinlik"</string>
+    <string name="permdesc_nfcTransactionEvent" msgid="1904286701876487397">"Uygulamanın bir Güvenlik Unsuru\'nda gerçekleşen işlemlerle ilgili bilgi almasına izin verir."</string>
     <string name="permlab_disableKeyguard" msgid="3605253559020928505">"ekran kilidimi devre dışı bırak"</string>
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"Uygulamaya, tuş kilidini ve ilişkili tüm şifreli güvenlik önlemlerini devre dışı bırakma izni verir. Örneğin, telefon, çağrı alındığında tuş kilidinin devre dışı bırakır ve sonra, görüşme bittiğinde kilidi yeniden etkinleştirir."</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"ekran kilidi karmaşıklığı iste"</string>
@@ -620,6 +622,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Biyometrik donanım kullanılamıyor"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Kimlik doğrulama iptal edildi"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Tanınmadı"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Yüz tanınmadı"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Kimlik doğrulama iptal edildi"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"PIN, desen veya şifre seti yok"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Kimlik doğrulama sırasında hata oluştu"</string>
@@ -1393,7 +1396,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"REDDET"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Giriş yöntemini seçin"</string>
     <string name="show_ime" msgid="6406112007347443383">"Fiziksel klavye etkin durumdayken ekranda tut"</string>
-    <string name="hardware" msgid="1800597768237606953">"Sanal klavyeyi göster"</string>
+    <string name="hardware" msgid="3611039921284836033">"Ekran klavyesi kullanın"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"<xliff:g id="DEVICE_NAME">%s</xliff:g> ayarlarını yapılandır"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Fiziksel klavyeleri yapılandırın"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Dili ve düzeni seçmek için dokunun"</string>
@@ -1956,7 +1959,7 @@
     <string name="app_blocked_title" msgid="7353262160455028160">"Uygulama kullanılamıyor"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> uygulaması şu anda kullanılamıyor."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> kullanılamıyor"</string>
-    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="4483161748582966785">"İzin gerekli"</string>
+    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="3805704317624448487">"İzin isteği reddedildi"</string>
     <string name="app_streaming_blocked_title_for_camera_dialog" msgid="3935701653713853065">"Kamera kullanılamıyor"</string>
     <string name="app_streaming_blocked_title_for_fingerprint_dialog" msgid="3516853717714141951">"İşleme telefonda devam edin"</string>
     <string name="app_streaming_blocked_title_for_microphone_dialog" msgid="544822455127171206">"Mikrofon kullanılamıyor"</string>
@@ -1967,6 +1970,9 @@
     <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"Bu uygulamaya şu anda <xliff:g id="DEVICE">%1$s</xliff:g> cihazınızdan erişilemiyor. Bunun yerine Android TV cihazınızı kullanmayı deneyin."</string>
     <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"Bu uygulamaya şu anda <xliff:g id="DEVICE">%1$s</xliff:g> cihazınızdan erişilemiyor. Bunun yerine tabletinizi kullanmayı deneyin."</string>
     <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"Bu uygulamaya şu anda <xliff:g id="DEVICE">%1$s</xliff:g> cihazınızdan erişilemiyor. Bunun yerine telefonunuzu kullanmayı deneyin."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tv" msgid="4706276040125072077">"Bu uygulama ek izinler istiyor ancak akış oturumundayken izin verilemez. Önce Android TV cihazınızda ilgili izni verin."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tablet" msgid="1824604581465771629">"Bu uygulama ek izinler istiyor ancak akış oturumundayken izin verilemez. Önce tabletinizde ilgili izni verin."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="default" msgid="7755223160363292105">"Bu uygulama ek izinler istiyor ancak akış oturumundayken izin verilemez. Önce telefonunuzda ilgili izni verin."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"Bu uygulama daha fazla güvenlik istiyor. Bunun yerine Android TV cihazınızı kullanmayı deneyin."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"Bu uygulama daha fazla güvenlik istiyor. Bunun yerine tabletinizi kullanmayı deneyin."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"Bu uygulama daha fazla güvenlik istiyor. Bunun yerine telefonunuzu kullanmayı deneyin."</string>
diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml
index c6ba301a..4bc7abf 100644
--- a/core/res/res/values-uk/strings.xml
+++ b/core/res/res/values-uk/strings.xml
@@ -592,6 +592,8 @@
     <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"Дозволяє додатку отримувати доступ до інформації потрібного платіжного NFC-сервісу (наприклад, пов\'язаних ідентифікаторів чи даних про маршрутизацію трансакцій)."</string>
     <string name="permlab_nfc" msgid="1904455246837674977">"контрол. Near Field Communication"</string>
     <string name="permdesc_nfc" msgid="8352737680695296741">"Дозволяє програмі обмінюватися даними з тегами, картками та читачами екрана Near Field Communication (NFC)."</string>
+    <string name="permlab_nfcTransactionEvent" msgid="5868209446710407679">"Подія, пов’язана з транcакцією в Компоненті захисту"</string>
+    <string name="permdesc_nfcTransactionEvent" msgid="1904286701876487397">"Надає додатку доступ до інформації про транcакції, що відбуваються в Компоненті захисту."</string>
     <string name="permlab_disableKeyguard" msgid="3605253559020928505">"вимикати блокування екрана"</string>
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"Дозволяє програмі вимикати блокування клавіатури та будь-який пов’язаний паролем захист. Наприклад: телефон вимикає блокування клавіатури під час отримання вхідного дзвінка, після закінчення якого блокування клавіатури відновлюється."</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"запитувати рівень складності блокування екрана"</string>
@@ -622,6 +624,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Біометричне апаратне забезпечення недоступне"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Автентифікацію скасовано"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Не розпізнано"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Обличчя не розпізнано"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Автентифікацію скасовано"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"Не вказано PIN-код, ключ або пароль"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Помилка автентифікації"</string>
@@ -1395,7 +1398,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"ВІДХИЛИТИ"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Вибрати метод введення"</string>
     <string name="show_ime" msgid="6406112007347443383">"Утримуйте на екрані, коли активна фізична клавіатура"</string>
-    <string name="hardware" msgid="1800597768237606953">"Показати віртуальну клавіатуру"</string>
+    <string name="hardware" msgid="3611039921284836033">"Екранна клавіатура"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"Налаштуйте клавіатуру \"<xliff:g id="DEVICE_NAME">%s</xliff:g>\""</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Налаштуйте фізичні клавіатури"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Торкніться, щоб вибрати мову та розкладку"</string>
@@ -1958,7 +1961,7 @@
     <string name="app_blocked_title" msgid="7353262160455028160">"Додаток недоступний"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"Додаток <xliff:g id="APP_NAME">%1$s</xliff:g> зараз недоступний."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"Недоступно: <xliff:g id="ACTIVITY">%1$s</xliff:g>"</string>
-    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="4483161748582966785">"Потрібен дозвіл"</string>
+    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="3805704317624448487">"Запит на доступ відхилено"</string>
     <string name="app_streaming_blocked_title_for_camera_dialog" msgid="3935701653713853065">"Камера недоступна"</string>
     <string name="app_streaming_blocked_title_for_fingerprint_dialog" msgid="3516853717714141951">"Продовжте на телефоні"</string>
     <string name="app_streaming_blocked_title_for_microphone_dialog" msgid="544822455127171206">"Мікрофон недоступний"</string>
@@ -1969,6 +1972,9 @@
     <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"Цей додаток зараз недоступний на вашому <xliff:g id="DEVICE">%1$s</xliff:g>. Спробуйте натомість скористатися пристроєм Android TV."</string>
     <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"Цей додаток зараз недоступний на вашому <xliff:g id="DEVICE">%1$s</xliff:g>. Спробуйте натомість скористатися планшетом."</string>
     <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"Цей додаток зараз недоступний на вашому <xliff:g id="DEVICE">%1$s</xliff:g>. Спробуйте натомість скористатися телефоном."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tv" msgid="4706276040125072077">"Цьому додатку потрібні додаткові дозволи, але їх не можна надати під час потокового передавання. Спершу надайте дозвіл на пристрої Android TV."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tablet" msgid="1824604581465771629">"Цьому додатку потрібні додаткові дозволи, але їх не можна надати під час потокового передавання. Спершу надайте дозвіл на планшеті."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="default" msgid="7755223160363292105">"Цьому додатку потрібні додаткові дозволи, але їх не можна надати під час потокового передавання. Спершу надайте дозвіл на телефоні."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"Цьому додатку потрібен додатковий рівень безпеки. Спробуйте натомість скористатися пристроєм Android TV."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"Цьому додатку потрібен додатковий рівень безпеки. Спробуйте натомість скористатися планшетом."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"Цьому додатку потрібен додатковий рівень безпеки. Спробуйте натомість скористатися телефоном."</string>
diff --git a/core/res/res/values-ur/strings.xml b/core/res/res/values-ur/strings.xml
index e692be7..4755d65 100644
--- a/core/res/res/values-ur/strings.xml
+++ b/core/res/res/values-ur/strings.xml
@@ -590,6 +590,8 @@
     <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"‏ایپ کو رجسٹرشدہ ایڈز اور روٹ ڈسٹنیشن جیسی ترجیح شدہ nfc ادائیگی سروس کی معلومات حاصل کرنے کی اجازت دیتا ہے۔"</string>
     <string name="permlab_nfc" msgid="1904455246837674977">"‏Near Field کمیونیکیشن کنٹرول کریں"</string>
     <string name="permdesc_nfc" msgid="8352737680695296741">"‏ایپ کو Near Field Communication (NFC)‎ ٹیگز، کارڈز اور ریڈرز کے ساتھ مواصلت کرنے کی اجازت دیٹا ہے۔"</string>
+    <string name="permlab_nfcTransactionEvent" msgid="5868209446710407679">"محفوظ عنصر کے ٹرانزیکشن کا ایونٹ"</string>
+    <string name="permdesc_nfcTransactionEvent" msgid="1904286701876487397">"ایپ کو ایک محفوظ عنصر پر ہونے والے ٹرانزیکشنز کے بارے میں معلومات حاصل کرنے کی اجازت دیتا ہے۔"</string>
     <string name="permlab_disableKeyguard" msgid="3605253559020928505">"اپنے اسکرین لاک کو غیر فعال کریں"</string>
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"ایپ کو کلیدی لاک اور کسی بھی متعلقہ پاس ورڈ سیکیورٹی کو غیر فعال کرنے کی اجازت دیتا ہے۔ مثلاً، کوئی آنے والی فون کال موصول ہونے کے وقت فون کلیدی لاک کو غیر فعال کرتا ہے، پھر کال پوری ہوجانے پر کلیدی لاک کو دوبارہ فعال کردیتا ہے۔"</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"اسکرین لاک کی پیچیدگی کی درخواست کریں"</string>
@@ -620,6 +622,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"بایومیٹرک ہارڈ ویئر دستیاب نہیں ہے"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"تصدیق کا عمل منسوخ ہو گیا"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"تسلیم شدہ نہیں ہے"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"چہرے کی شناخت نہیں ہو سکی"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"تصدیق کا عمل منسوخ ہو گیا"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"کوئی پن، پیٹرن، یا پاس ورڈ سیٹ نہیں ہے"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"خرابی کی توثیق ہو رہی ہے"</string>
@@ -1393,7 +1396,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"مسترد کریں"</string>
     <string name="select_input_method" msgid="3971267998568587025">"ان پٹ کا طریقہ منتخب کریں"</string>
     <string name="show_ime" msgid="6406112007347443383">"‏جب فزیکل کی بورڈ فعال ہو تو IME کو اسکرین پر رکھیں"</string>
-    <string name="hardware" msgid="1800597768237606953">"ورچوئل کی بورڈ دکھائیں"</string>
+    <string name="hardware" msgid="3611039921284836033">"آن اسکرین کی بورڈ استعمال کریں"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"<xliff:g id="DEVICE_NAME">%s</xliff:g> کنفیگر کریں"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"فزیکل کی بورڈز کنفیگر کریں"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"زبان اور لے آؤٹ منتخب کرنے کیلئے تھپتھپائیں"</string>
@@ -1956,7 +1959,7 @@
     <string name="app_blocked_title" msgid="7353262160455028160">"ایپ دستیاب نہیں ہے"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> ابھی دستیاب نہیں ہے۔"</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> دستیاب نہیں ہے"</string>
-    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="4483161748582966785">"اجازت درکار ہے"</string>
+    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="3805704317624448487">"اجازت کی درخواست دبا دی گئی"</string>
     <string name="app_streaming_blocked_title_for_camera_dialog" msgid="3935701653713853065">"کیمرا دستیاب نہیں ہے"</string>
     <string name="app_streaming_blocked_title_for_fingerprint_dialog" msgid="3516853717714141951">"فون پر جاری رکھیں"</string>
     <string name="app_streaming_blocked_title_for_microphone_dialog" msgid="544822455127171206">"مائیکروفون دستیاب نہیں ہے"</string>
@@ -1967,6 +1970,9 @@
     <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"‏اس وقت آپ کے <xliff:g id="DEVICE">%1$s</xliff:g> پر اس تک رسائی نہیں مل سکتی۔ اس کے بجائے اپنے Android TV آلے پر کوشش کریں۔"</string>
     <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"اس وقت آپ کے <xliff:g id="DEVICE">%1$s</xliff:g> پر اس تک رسائی نہیں مل سکتی۔ اس کے بجائے اپنے ٹیبلیٹ پر کوشش کریں۔"</string>
     <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"اس وقت آپ کے <xliff:g id="DEVICE">%1$s</xliff:g> پر اس تک رسائی نہیں مل سکتی۔ اس کے بجائے اپنے فون پر کوشش کریں۔"</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tv" msgid="4706276040125072077">"‏یہ ایپ اضافی اجازتوں کی درخواست کر رہی ہے، لیکن سلسلہ بندی کے سیشن میں اجازتیں نہیں دی جا سکتیں۔ پہلے اپنے Android TV آلہ پر اجازت دیں۔"</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tablet" msgid="1824604581465771629">"یہ ایپ اضافی اجازتوں کی درخواست کر رہی ہے، لیکن سلسلہ بندی کے سیشن میں اجازتیں نہیں دی جا سکتیں۔ پہلے اپنے ٹیبلیٹ پر اجازت دیں۔"</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="default" msgid="7755223160363292105">"یہ ایپ اضافی اجازتوں کی درخواست کر رہی ہے، لیکن سلسلہ بندی کے سیشن میں اجازتیں نہیں دی جا سکتیں۔ پہلے اپنے فون پر اجازت دیں۔"</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"‏یہ ایپ اضافی سیکیورٹی کی درخواست کر رہی ہے۔ اس کے بجائے اپنے Android TV آلے پر کوشش کریں۔"</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"یہ ایپ اضافی سیکیورٹی کی درخواست کر رہی ہے۔ اس کے بجائے اپنے ٹیبلیٹ پر کوشش کریں۔"</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"یہ ایپ اضافی سیکیورٹی کی درخواست کر رہی ہے۔ اس کے بجائے اپنے فون پر کوشش کریں۔"</string>
diff --git a/core/res/res/values-uz/strings.xml b/core/res/res/values-uz/strings.xml
index bb9cbd2..cdf1c93 100644
--- a/core/res/res/values-uz/strings.xml
+++ b/core/res/res/values-uz/strings.xml
@@ -590,6 +590,8 @@
     <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"Bu ilovaga asosiy NFC toʻlov xizmati haqidagi axborotni olish imkonini beradi (masalan, qayd qilingan AID identifikatorlari va marshrutning yakuniy manzili)."</string>
     <string name="permlab_nfc" msgid="1904455246837674977">"NFC modulini boshqarish"</string>
     <string name="permdesc_nfc" msgid="8352737680695296741">"Ilova qisqa masofali aloqa (NFC) texnologiyasi yordamida NFC yorliqlari, kartalar va o‘qish moslamalari bilan ma’lumot almashishi mumkin."</string>
+    <string name="permlab_nfcTransactionEvent" msgid="5868209446710407679">"Secure Element tranzaksiya hodisasi"</string>
+    <string name="permdesc_nfcTransactionEvent" msgid="1904286701876487397">"Ilovaga Secure Element orqali amalga oshuvchi tranzaksiyalar axborotini olishga ruxsat beradi."</string>
     <string name="permlab_disableKeyguard" msgid="3605253559020928505">"ekran qulfini o‘chirib qo‘yish"</string>
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"Ilovaga ekran qulfini va har qanday parol  yordamidagi xavfsizlik himoyalarini o‘chirishga ruxsat beradi. Masalan, kirish qo‘ng‘irog‘ida telefon ekran qulfini o‘chiradi va qo‘ng‘iroq tugashi bilan qulfni yoqadi."</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"ekran qulfi qiyinligi darajasini talab qilish"</string>
@@ -620,6 +622,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Biometrik sensor ishlamayapti"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Autentifikatsiya bekor qilindi"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Aniqlanmadi"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Yuz aniqlanmadi"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Autentifikatsiya bekor qilindi"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"PIN kod, grafik kalit yoki parol sozlanmagan"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Autentifikatsiya amalga oshmadi"</string>
@@ -1393,7 +1396,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"RAD ETISH"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Matn kiritish usulini tanlang"</string>
     <string name="show_ime" msgid="6406112007347443383">"Tashqi klaviatura ulanganida ekranda chiqib turadi"</string>
-    <string name="hardware" msgid="1800597768237606953">"Virtual klaviatura"</string>
+    <string name="hardware" msgid="3611039921284836033">"Ekrandagi klaviatura"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"Sozlang: <xliff:g id="DEVICE_NAME">%s</xliff:g>"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Tashqi klaviaturalarni sozlang"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Til va sxemani belgilash uchun bosing"</string>
@@ -1956,7 +1959,7 @@
     <string name="app_blocked_title" msgid="7353262160455028160">"Ilova ishlamayapti"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"Ayni vaqtda <xliff:g id="APP_NAME">%1$s</xliff:g> ilovasi ishlamayapti."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> kanali ish faoliyatida emas"</string>
-    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="4483161748582966785">"Ruxsat zarur"</string>
+    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="3805704317624448487">"Ruxsat talabi bostirildi"</string>
     <string name="app_streaming_blocked_title_for_camera_dialog" msgid="3935701653713853065">"Kamera ishlamayapti"</string>
     <string name="app_streaming_blocked_title_for_fingerprint_dialog" msgid="3516853717714141951">"Telefonda davom ettirish"</string>
     <string name="app_streaming_blocked_title_for_microphone_dialog" msgid="544822455127171206">"Mikrofon ishlamayapti"</string>
@@ -1967,6 +1970,9 @@
     <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"Ayni vaqtda bu translatsiya <xliff:g id="DEVICE">%1$s</xliff:g> qurilmangizda ishlamaydi. Android TV qurilmasi orqali urinib koʻring."</string>
     <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"Ayni vaqtda bu translatsiya <xliff:g id="DEVICE">%1$s</xliff:g> qurilmangizda ishlamaydi. Planshet orqali urinib koʻring."</string>
     <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"Ayni vaqtda bu translatsiya <xliff:g id="DEVICE">%1$s</xliff:g> qurilmangizda ishlamaydi. Telefon orqali urininb koʻring."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tv" msgid="4706276040125072077">"Bu ilova qoʻshimcha ruxsatlar talab qilmoqda, lekin ruxsatlar striming seansida berilmaydi. Avval Android TV qurilmangizga ruxsat bering."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tablet" msgid="1824604581465771629">"Bu ilova qoʻshimcha ruxsatlar talab qilmoqda, lekin ruxsatlar striming seansida berilmaydi. Avval planshetingizda ruxsat bering."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="default" msgid="7755223160363292105">"Bu ilova qoʻshimcha ruxsatlar talab qilmoqda, lekin ruxsatlar striming seansida berilmaydi. Avval telefoningizda ruxsat bering."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"Bu ilova qoʻshimcha himoyani talab qilmoqda. Android TV qurilmasi orqali urinib koʻring."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"Bu ilova qoʻshimcha himoyani talab qilmoqda. Planshet orqali urinib koʻring."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"Bu ilova qoʻshimcha himoyani talab qilmoqda. Telefon orqali urininb koʻring."</string>
diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml
index 83730d5..68bafc8 100644
--- a/core/res/res/values-vi/strings.xml
+++ b/core/res/res/values-vi/strings.xml
@@ -590,6 +590,8 @@
     <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"Cho phép ứng dụng nhận thông tin về dịch vụ thanh toán qua công nghệ giao tiếp tầm gần mà bạn ưu tiên, chẳng hạn như các hình thức hỗ trợ đã đăng ký và điểm đến trong hành trình."</string>
     <string name="permlab_nfc" msgid="1904455246837674977">"kiểm soát Liên lạc trường gần"</string>
     <string name="permdesc_nfc" msgid="8352737680695296741">"Cho phép ứng dụng giao tiếp với thẻ Giao tiếp trường gần (NFC), thẻ và trình đọc."</string>
+    <string name="permlab_nfcTransactionEvent" msgid="5868209446710407679">"Sự kiện giao dịch trên Phần tử bảo mật"</string>
+    <string name="permdesc_nfcTransactionEvent" msgid="1904286701876487397">"Cho phép ứng dụng nhận thông tin về giao dịch diễn ra trên Phần tử bảo mật."</string>
     <string name="permlab_disableKeyguard" msgid="3605253559020928505">"vô hiệu hóa khóa màn hình của bạn"</string>
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"Cho phép ứng dụng tắt khóa phím và bất kỳ bảo mật mật khẩu được liên kết nào. Ví dụ: điện thoại tắt khóa phím khi nhận được cuộc gọi đến, sau đó bật lại khóa phím khi cuộc gọi kết thúc."</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"cần biết độ phức tạp của khóa màn hình"</string>
@@ -620,6 +622,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Không có phần cứng sinh trắc học"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Đã hủy xác thực"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Không nhận dạng được"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Không nhận dạng được khuôn mặt"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Đã hủy xác thực"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"Chưa đặt mã PIN, hình mở khóa hoặc mật khẩu"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Lỗi khi xác thực"</string>
@@ -1393,7 +1396,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"TỪ CHỐI"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Chọn phương thức nhập"</string>
     <string name="show_ime" msgid="6406112007347443383">"Hiện bàn phím ảo trên màn hình trong khi bàn phím vật lý đang hoạt động"</string>
-    <string name="hardware" msgid="1800597768237606953">"Hiện bàn phím ảo"</string>
+    <string name="hardware" msgid="3611039921284836033">"Sử dụng bàn phím ảo"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"Định cấu hình <xliff:g id="DEVICE_NAME">%s</xliff:g>"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Định cấu hình bàn phím vật lý"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Nhấn để chọn ngôn ngữ và bố cục"</string>
@@ -1956,7 +1959,7 @@
     <string name="app_blocked_title" msgid="7353262160455028160">"Ứng dụng này không dùng được"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> hiện không dùng được."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"Không hỗ trợ <xliff:g id="ACTIVITY">%1$s</xliff:g>"</string>
-    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="4483161748582966785">"Cần có quyền"</string>
+    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="3805704317624448487">"Yêu cầu quyền đã bị chặn"</string>
     <string name="app_streaming_blocked_title_for_camera_dialog" msgid="3935701653713853065">"Không dùng được máy ảnh"</string>
     <string name="app_streaming_blocked_title_for_fingerprint_dialog" msgid="3516853717714141951">"Tiếp tục trên điện thoại"</string>
     <string name="app_streaming_blocked_title_for_microphone_dialog" msgid="544822455127171206">"Không dùng được micrô"</string>
@@ -1967,6 +1970,9 @@
     <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"Hiện tại, bạn không thể truy cập vào ứng dụng này trên <xliff:g id="DEVICE">%1$s</xliff:g>. Hãy thử trên thiết bị Android TV."</string>
     <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"Hiện tại, bạn không thể truy cập vào ứng dụng này trên <xliff:g id="DEVICE">%1$s</xliff:g>. Hãy thử trên máy tính bảng."</string>
     <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"Hiện tại, bạn không thể truy cập vào ứng dụng này trên <xliff:g id="DEVICE">%1$s</xliff:g>. Hãy thử trên điện thoại."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tv" msgid="4706276040125072077">"Ứng dụng này đang yêu cầu thêm quyền, nhưng bạn không thể cấp quyền trong một phiên truyền trực tuyến. Trước tiên, hãy cấp quyền trên thiết bị Android TV của bạn."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tablet" msgid="1824604581465771629">"Ứng dụng này đang yêu cầu thêm quyền, nhưng bạn không thể cấp quyền trong một phiên truyền trực tuyến. Trước tiên, hãy cấp quyền trên máy tính bảng của bạn."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="default" msgid="7755223160363292105">"Ứng dụng này đang yêu cầu thêm quyền, nhưng bạn không thể cấp quyền trong một phiên truyền trực tuyến. Trước tiên, hãy cấp quyền trên điện thoại của bạn."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"Ứng dụng này đang yêu cầu tính năng bảo mật bổ sung. Hãy thử trên thiết bị Android TV."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"Ứng dụng này đang yêu cầu tính năng bảo mật bổ sung. Hãy thử trên máy tính bảng."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"Ứng dụng này đang yêu cầu tính năng bảo mật bổ sung. Hãy thử trên điện thoại."</string>
diff --git a/core/res/res/values-w180dp-notround-watch/dimens.xml b/core/res/res/values-w180dp-notround-watch/dimens.xml
new file mode 100644
index 0000000..5887661
--- /dev/null
+++ b/core/res/res/values-w180dp-notround-watch/dimens.xml
@@ -0,0 +1,24 @@
+<?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.
+  -->
+<resources>
+    <!-- 14.4% of display size -->
+    <dimen name="base_error_dialog_top_padding">26dp</dimen>
+    <!-- 2.8% of display size -->
+    <dimen name="base_error_dialog_padding">5dp</dimen>
+    <!-- 35.56% of display size -->
+    <dimen name="base_error_dialog_bottom_padding">64dp</dimen>
+</resources>
diff --git a/core/res/res/values-w192dp-round-watch/dimens.xml b/core/res/res/values-w192dp-round-watch/dimens.xml
new file mode 100644
index 0000000..5aed20e
--- /dev/null
+++ b/core/res/res/values-w192dp-round-watch/dimens.xml
@@ -0,0 +1,24 @@
+<?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.
+  -->
+<resources>
+    <!-- 16.7% of display size -->
+    <dimen name="base_error_dialog_top_padding">32dp</dimen>
+    <!-- 5.2% of display size -->
+    <dimen name="base_error_dialog_padding">10dp</dimen>
+    <!-- 20.83% of display size -->
+    <dimen name="base_error_dialog_bottom_padding">40dp</dimen>
+</resources>
diff --git a/core/res/res/values-w213dp-round-watch/dimens.xml b/core/res/res/values-w213dp-round-watch/dimens.xml
new file mode 100644
index 0000000..27fff75
--- /dev/null
+++ b/core/res/res/values-w213dp-round-watch/dimens.xml
@@ -0,0 +1,24 @@
+<?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.
+  -->
+<resources>
+    <!-- 16.7% of display size -->
+    <dimen name="base_error_dialog_top_padding">36dp</dimen>
+    <!-- 5.2% of display size -->
+    <dimen name="base_error_dialog_padding">11dp</dimen>
+    <!-- 36.46% of display size -->
+    <dimen name="base_error_dialog_bottom_padding">78dp</dimen>
+</resources>
diff --git a/core/res/res/values-watch/colors.xml b/core/res/res/values-watch/colors.xml
deleted file mode 100644
index 6d908be..0000000
--- a/core/res/res/values-watch/colors.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2021 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT 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>
-    <!-- Wear Material standard colors -->
-    <color name="wear_material_red_mid">#CC5D58</color>
-    <color name="wear_material_grey_900">#202124</color>
-</resources>
\ No newline at end of file
diff --git a/core/res/res/values-watch/colors_device_defaults.xml b/core/res/res/values-watch/colors_device_defaults.xml
index 6ffd6e6..ee9481c 100644
--- a/core/res/res/values-watch/colors_device_defaults.xml
+++ b/core/res/res/values-watch/colors_device_defaults.xml
@@ -16,102 +16,51 @@
 
 <!-- Colors specific to Theme.DeviceDefault on watches, as specified via themes_device_default.xml
      Note: These colors specifically proide a darker, high-contrast UI that is suitable for
-     wearables with respect to 'glanceability'. OEM customization is supported within this set. -->
+     wearables with respect to 'glanceability'. -->
 <resources>
     <!--
        accent_device_default_dark
-         > from values/colors_material/accent_material_dark
-         > from values/colors_material/material_deep_teal_200
-         = #ff80cbc4
-         ! replaced with custom color #5E97F6
-         ! OEMS can customize as per specification
+         > from values/system_accent1_100
+         ! replaced with color/system_accent1_400
     -->
-    <color name="accent_device_default_dark">#5E97F6</color>
+    <color name="accent_device_default_dark">@color/system_accent1_400</color>
 
     <!--
        foreground_device_default_dark
          - introduced to avoid coupling to foreground_material_dark
          - colorForeground typically falls through Theme.DeviceDefault to Theme.Material
          ! fixed as white for optimal glanceability/contrast
-         ! OEMs should not customize
     -->
     <color name="foreground_device_default_dark">@color/white</color>
 
     <!--
        background_device_default_dark
-         > from values/colors_material/background_material_dark
-         > from values/colors_material/material_grey_850
-         = #ff303030
+         > from values/system_neutral1_900
          ! replaced with custom color #000000
-         ! OEMs can customized as per specification
-           (derived from accent color, constrained by brightness)
     -->
     <color name="background_device_default_dark">#000000</color>
 
-    <!--
-       background_floating_device_default_dark
-         > from values/colors_material/background_floating_material_dark
-         > from values/colors_material/material_grey_800
-         = #ff424242
-         ! replaced with custom color #1D2E4D
-           (derived from accent color, constrained by brightness)
-    -->
-    <color name="background_floating_device_default_dark">#1D2E4D</color>
+    <!-- Derived from accent color at 20% luminance -->
+    <color name="background_floating_device_default_dark">@color/system_accent1_800</color>
 
     <!--
-       primary_device_default_dark
-         > from values/colors_material/primary_material_dark
-         > from values/colors_material/material_grey_900
-         = #ff212121
-         ! replaced with custom color #808080
-         ! OEMs can customize as per specification
-           (derived from background color + foreground @ 50% opacity)
-    -->
-    <color name="primary_device_default_dark">#808080</color>
+        primary_device_default_dark
+          > from values/colors/system_neutral1_900
+          ! replaced with system_neutral1_500
+     -->
+    <color name="primary_device_default_dark">@color/system_neutral1_500</color>
 
-    <!--
-       primary_dark_device_default_dark
-         > from values/colors_material/primary_dark_material_dark
-         = @color/black
-         ! replaced with custom color #333333
-         ! OEMS can customize as per specification
-           (derived from background color + foreground @ 20% opacity)
-    -->
-    <color name="primary_dark_device_default_dark">#333333</color>
+    <!-- Currently matches the "surface dark" definition for phones. -->
+    <color name="surface_dark">@color/system_neutral1_800</color>
 
     <!--
        button_normal_device_default_dark
-         - uses ?attr/disabledAlpha and ?attr/colorPrimaryDark to draw state list
+         - uses ?attr/disabledAlpha and ?attr/colorSurface to draw state list
            (used as colorButtonNormal attribute in theme)
          - see color-watch/btn_watch_default_dark.xml
     -->
     <color name="button_normal_device_default_dark">@color/btn_watch_default_dark</color>
 
-    <!--
-       error_color_device_default_dark
-         - introduced to avoid coupling to error_color_mtterial (also #F4511E)
-         - colorError typically falls through Theme.DeviceDefault to Theme.Material
-         ! OEMs can customize as per specification
-    -->
-    <color name="error_color_device_default_dark">#F4511E</color>
-
-    <!-- no customization required/suggested below this point -->
-
-    <!--
-       background_cache_hint_selector_device_default
-         - note that this is based off of colors/background_cache_hint_selector_device_default
-           xml drawable
-         - uses ?attr/colorBackground and transparency to draw
-         - no color customization required here
-    -->
-
-    <!-- deprecated for Wear
-         these overrides exist only for compatibility with existing
-         WTS theme test heuristics, based on the previous modifications
-         to the material theme, they should not be used for customization
-         as they are not exposed via publicly accessible attributes -->
-    <color name="accent_device_default_dark_60_percent_opacity">#995E97f6</color>
-    <color name="accent_device_default_700">#5385DB</color>
-    <color name="accent_device_default_light">#75A4F5</color>
-    <color name="accent_device_default_50">#93B7F5</color>
+    <!-- Matches the Wear Compose error color. -->
+    <color name="error_color_device_default_dark">#FF746E</color>
 </resources>
diff --git a/core/res/res/values-watch/config_material.xml b/core/res/res/values-watch/config_material.xml
index 03d3637..529f18b 100644
--- a/core/res/res/values-watch/config_material.xml
+++ b/core/res/res/values-watch/config_material.xml
@@ -30,9 +30,6 @@
     <!-- Always overscan by default to ensure onApplyWindowInsets will always be called. -->
     <bool name="config_windowOverscanByDefault">true</bool>
 
-    <!-- Enable windowSwipeToDismiss. -->
-    <bool name="config_windowSwipeToDismiss">true</bool>
-
     <!-- Style the scrollbars accoridngly. -->
     <drawable name="config_scrollbarThumbVertical">@drawable/scrollbar_vertical_thumb</drawable>
     <drawable name="config_scrollbarTrackVertical">@drawable/scrollbar_vertical_track</drawable>
diff --git a/core/res/res/values-watch/dimens.xml b/core/res/res/values-watch/dimens.xml
index 5472316..bbca987 100644
--- a/core/res/res/values-watch/dimens.xml
+++ b/core/res/res/values-watch/dimens.xml
@@ -20,4 +20,17 @@
     <dimen name="alert_dialog_button_bar_height">0dp</dimen>
 
     <dimen name="toast_y_offset">0dip</dimen>
+
+    <!-- AppErrorDialog's list item height -->
+    <dimen name="aerr_list_item_height">52dp</dimen>
+    <!-- Padding for contents in a view of BaseErrorDialog such as a title and buttons -->
+    <dimen name="base_error_dialog_contents_padding">14dp</dimen>
+
+    <!-- Specifies height of the divider in the input method chooser -->
+    <dimen name="input_method_divider_height">4dp</dimen>
+
+    <!-- The width/height of the icon view on staring surface. -->
+    <dimen name="starting_surface_icon_size">48dp</dimen>
+    <!-- The default width/height of the icon on the spec of adaptive icon drawable. -->
+    <dimen name="starting_surface_default_icon_size">48dp</dimen>
 </resources>
diff --git a/core/res/res/values-watch/strings.xml b/core/res/res/values-watch/strings.xml
index e44eda3..6d6cfc7 100644
--- a/core/res/res/values-watch/strings.xml
+++ b/core/res/res/values-watch/strings.xml
@@ -27,4 +27,7 @@
 
    <!-- Reboot to Recovery Progress Dialog. This is shown before it reboots to recovery. -->
     <string name="reboot_to_update_title">Wear OS system update</string>
-</resources>
\ No newline at end of file
+
+   <!-- Title of the pop-up dialog in which the user switches keyboard, also known as input method. -->
+    <string name="select_input_method">Choose input</string>
+</resources>
diff --git a/core/res/res/values-watch/styles.xml b/core/res/res/values-watch/styles.xml
index 3172f73..4a7d57d 100644
--- a/core/res/res/values-watch/styles.xml
+++ b/core/res/res/values-watch/styles.xml
@@ -19,4 +19,61 @@
         <item name="fontFamily">sans-serif-regular</item>
         <item name="textSize">13sp</item>
     </style>
+
+    <!-- @hide -->
+    <style name="TextAppearance.Watch"/>
+
+    <!-- @hide -->
+    <style name="TextAppearance.Watch.BaseErrorDialog">
+        <item name="fontFamily">google-sans-text-medium</item>
+        <item name="textColor">@android:color/white</item>
+        <item name="textAllCaps">false</item>
+    </style>
+
+    <!-- @hide -->
+    <style name="TextAppearance.Watch.BaseErrorDialog.Title">
+        <item name="textSize">16sp</item>
+        <item name="letterSpacing">0.024</item>
+    </style>
+
+    <!-- @hide -->
+    <style name="TextAppearance.Watch.AppErrorDialog"
+           parent="TextAppearance.Watch.BaseErrorDialog"/>
+
+    <!-- @hide -->
+    <style name="TextAppearance.Watch.AppErrorDialog.Item">
+        <item name="textSize">15sp</item>
+        <item name="letterSpacing">0.01</item>
+    </style>
+
+    <!-- @hide -->
+    <style name="aerr_list_item">
+        <item name="minHeight">@dimen/aerr_list_item_height</item>
+        <item name="textAppearance">@style/TextAppearance.Watch.AppErrorDialog.Item</item>
+        <item name="gravity">center_vertical</item>
+        <item name="paddingStart">@dimen/base_error_dialog_contents_padding</item>
+        <item name="paddingEnd">@dimen/base_error_dialog_contents_padding</item>
+        <item name="background">@drawable/global_actions_item_grey_background</item>
+        <item name="drawablePadding">6dp</item>
+        <item name="drawableTint">@android:color/white</item>
+        <item name="drawableTintMode">src_atop</item>
+    </style>
+
+    <!-- @hide -->
+    <style name="InputMethodSwitchHardKeyboardText">
+        <item name="paddingStart">?attr/listPreferredItemPaddingStart</item>
+        <item name="paddingEnd">16dp</item>
+        <item name="paddingTop">16dp</item>
+        <item name="paddingBottom">5dp</item>
+        <item name="minHeight">?attr/listPreferredItemHeightSmall</item>
+    </style>
+
+    <!-- @hide -->
+    <style name="InputMethodSwitchHardKeyboardSwitch">
+        <item name="layout_marginEnd">?attr/listPreferredItemPaddingEnd</item>
+    </style>
+
+    <style name="InputMethodSwitchDialogStyle" parent="AlertDialog.DeviceDefault">
+        <item name="android:listLayout">@layout/input_method_switcher_list_layout</item>
+    </style>
 </resources>
diff --git a/core/res/res/values-watch/styles_device_default.xml b/core/res/res/values-watch/styles_device_default.xml
index e2261af..8a2ce5d 100644
--- a/core/res/res/values-watch/styles_device_default.xml
+++ b/core/res/res/values-watch/styles_device_default.xml
@@ -34,4 +34,7 @@
         <item name="android:textSize">16sp</item>
         <item name="android:fontFamily">google-sans-medium</item>
     </style>
+    <style name="BaseErrorDialog.DeviceDefault" parent="AlertDialog.DeviceDefault">
+        <item name="layout">@layout/watch_base_error_dialog</item>
+    </style>
 </resources>
diff --git a/core/res/res/values-watch/styles_material.xml b/core/res/res/values-watch/styles_material.xml
index f34d9f9..12bc406 100644
--- a/core/res/res/values-watch/styles_material.xml
+++ b/core/res/res/values-watch/styles_material.xml
@@ -30,27 +30,27 @@
  -->
 <resources>
     <style name="Animation.Material.Activity" parent="Animation.Activity">
-        <item name="activityOpenEnterAnimation">@anim/slide_in_enter_micro</item>
-        <item name="activityOpenRemoteViewsEnterAnimation">@anim/slide_in_enter_micro</item>
-        <item name="activityOpenExitAnimation">@anim/slide_in_exit_micro</item>
+        <item name="activityOpenEnterAnimation">@anim/rounded_window_enter</item>
+        <item name="activityOpenRemoteViewsEnterAnimation">@anim/rounded_window_enter</item>
+        <item name="activityOpenExitAnimation">@null</item>
         <item name="activityCloseEnterAnimation">@null</item>
-        <item name="activityCloseExitAnimation">@anim/slide_out_micro</item>
-        <item name="taskOpenEnterAnimation">@anim/slide_in_enter_micro</item>
-        <item name="taskOpenExitAnimation">@anim/slide_in_exit_micro</item>
+        <item name="activityCloseExitAnimation">@anim/rounded_window_exit</item>
+        <item name="taskOpenEnterAnimation">@anim/rounded_window_enter</item>
+        <item name="taskOpenExitAnimation">@null</item>
         <item name="taskCloseEnterAnimation">@null</item>
-        <item name="taskCloseExitAnimation">@anim/slide_out_micro</item>
-        <item name="taskToFrontEnterAnimation">@null</item>
-        <item name="taskToFrontExitAnimation">@anim/slide_out_micro</item>
+        <item name="taskCloseExitAnimation">@anim/rounded_window_exit</item>
+        <item name="taskToFrontEnterAnimation">@anim/rounded_window_enter</item>
+        <item name="taskToFrontExitAnimation">@null</item>
         <item name="taskToBackEnterAnimation">@null</item>
-        <item name="taskToBackExitAnimation">@anim/slide_out_micro</item>
+        <item name="taskToBackExitAnimation">@anim/rounded_window_exit</item>
         <item name="wallpaperOpenEnterAnimation">@null</item>
-        <item name="wallpaperOpenExitAnimation">@anim/slide_out_micro</item>
-        <item name="wallpaperCloseEnterAnimation">@anim/slide_in_enter_micro</item>
-        <item name="wallpaperCloseExitAnimation">@anim/slide_in_exit_micro</item>
+        <item name="wallpaperOpenExitAnimation">@anim/rounded_window_exit</item>
+        <item name="wallpaperCloseEnterAnimation">@anim/rounded_window_enter</item>
+        <item name="wallpaperCloseExitAnimation">@null</item>
         <item name="wallpaperIntraOpenEnterAnimation">@null</item>
-        <item name="wallpaperIntraOpenExitAnimation">@anim/slide_out_micro</item>
-        <item name="wallpaperIntraCloseEnterAnimation">@anim/slide_in_enter_micro</item>
-        <item name="wallpaperIntraCloseExitAnimation">@anim/slide_in_exit_micro</item>
+        <item name="wallpaperIntraOpenExitAnimation">@anim/rounded_window_exit</item>
+        <item name="wallpaperIntraCloseEnterAnimation">@anim/rounded_window_enter</item>
+        <item name="wallpaperIntraCloseExitAnimation">@null</item>
     </style>
 
     <style name="PreferenceFragment.Material" parent="BasePreferenceFragment">
diff --git a/core/res/res/values-watch/themes_device_defaults.xml b/core/res/res/values-watch/themes_device_defaults.xml
index 1db006f..c4c1ed9 100644
--- a/core/res/res/values-watch/themes_device_defaults.xml
+++ b/core/res/res/values-watch/themes_device_defaults.xml
@@ -427,6 +427,8 @@
 
     <!-- Theme for the dialog shown when an app crashes or ANRs. Override to make it dark. -->
     <style name="Theme.DeviceDefault.Dialog.AppError" parent="Theme.DeviceDefault.Dialog.Alert">
+        <item name="alertDialogStyle">@style/BaseErrorDialog.DeviceDefault</item>
+        <item name="dialogPreferredPadding">@dimen/base_error_dialog_padding</item>
         <item name="windowContentTransitions">false</item>
         <item name="windowActivityTransitions">false</item>
         <item name="windowCloseOnTouchOutside">false</item>
diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml
index 8055813..b0ec73b 100644
--- a/core/res/res/values-zh-rCN/strings.xml
+++ b/core/res/res/values-zh-rCN/strings.xml
@@ -590,6 +590,8 @@
     <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"允许应用获取首选 NFC 付款服务信息,例如注册的应用标识符和路线目的地。"</string>
     <string name="permlab_nfc" msgid="1904455246837674977">"控制近距离通信"</string>
     <string name="permdesc_nfc" msgid="8352737680695296741">"允许应用与近距离无线通信(NFC)标签、卡和读取器通信。"</string>
+    <string name="permlab_nfcTransactionEvent" msgid="5868209446710407679">"安全元件事务事件"</string>
+    <string name="permdesc_nfcTransactionEvent" msgid="1904286701876487397">"允许应用接收与安全元件上发生的事务相关的信息。"</string>
     <string name="permlab_disableKeyguard" msgid="3605253559020928505">"停用屏幕锁定"</string>
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"允许该应用停用键锁以及任何关联的密码安全措施。例如,让手机在接听来电时停用键锁,在通话结束后重新启用键锁。"</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"请求屏幕锁定复杂度"</string>
@@ -620,6 +622,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"生物识别硬件无法使用"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"身份验证已取消"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"无法识别"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"无法识别面孔"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"身份验证已取消"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"未设置任何 PIN 码、图案和密码"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"进行身份验证时出错"</string>
@@ -1393,7 +1396,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"拒绝"</string>
     <string name="select_input_method" msgid="3971267998568587025">"选择输入法"</string>
     <string name="show_ime" msgid="6406112007347443383">"开启后,连接到实体键盘时,它会一直显示在屏幕上"</string>
-    <string name="hardware" msgid="1800597768237606953">"显示虚拟键盘"</string>
+    <string name="hardware" msgid="3611039921284836033">"使用屏幕键盘"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"配置“<xliff:g id="DEVICE_NAME">%s</xliff:g>”"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"配置物理键盘"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"点按即可选择语言和布局"</string>
@@ -1956,7 +1959,7 @@
     <string name="app_blocked_title" msgid="7353262160455028160">"应用无法使用"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g>目前无法使用。"</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g>不可用"</string>
-    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="4483161748582966785">"需要权限"</string>
+    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="3805704317624448487">"权限请求被阻止"</string>
     <string name="app_streaming_blocked_title_for_camera_dialog" msgid="3935701653713853065">"无法使用摄像头"</string>
     <string name="app_streaming_blocked_title_for_fingerprint_dialog" msgid="3516853717714141951">"继续在手机上操作"</string>
     <string name="app_streaming_blocked_title_for_microphone_dialog" msgid="544822455127171206">"无法使用麦克风"</string>
@@ -1967,6 +1970,9 @@
     <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"目前无法在您的<xliff:g id="DEVICE">%1$s</xliff:g>上访问此内容。您可以尝试在 Android TV 设备上访问。"</string>
     <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"目前无法在您的<xliff:g id="DEVICE">%1$s</xliff:g>上访问此内容。您可以尝试在平板电脑上访问。"</string>
     <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"目前无法在您的<xliff:g id="DEVICE">%1$s</xliff:g>上访问此内容。您可以尝试在手机上访问。"</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tv" msgid="4706276040125072077">"此应用请求获得额外的权限,但在流式传输会话期间无法授予权限。请先在 Android TV 设备上授予相应权限。"</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tablet" msgid="1824604581465771629">"此应用请求获得额外的权限,但在流式传输会话期间无法授予权限。请先在平板电脑上授予相应权限。"</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="default" msgid="7755223160363292105">"此应用请求获得额外的权限,但在流式传输会话期间无法授予权限。请先在手机上授予相应权限。"</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"此应用要求进行额外的安全性验证,您可以尝试在 Android TV 设备上访问。"</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"此应用要求进行额外的安全性验证,您可以尝试在平板电脑上访问。"</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"此应用要求进行额外的安全性验证,您可以尝试在手机上访问。"</string>
diff --git a/core/res/res/values-zh-rHK/strings.xml b/core/res/res/values-zh-rHK/strings.xml
index 3055600..c498d14 100644
--- a/core/res/res/values-zh-rHK/strings.xml
+++ b/core/res/res/values-zh-rHK/strings.xml
@@ -590,6 +590,8 @@
     <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"允許應用程式取得由用戶允許授權的 NFC 付款服務資訊 (如已註冊的付款輔助功能和最終付款對象)。"</string>
     <string name="permlab_nfc" msgid="1904455246837674977">"控制近距離無線通訊"</string>
     <string name="permdesc_nfc" msgid="8352737680695296741">"允許應用程式使用近距離無線通訊 (NFC) 標記、卡片及讀取程式進行通訊。"</string>
+    <string name="permlab_nfcTransactionEvent" msgid="5868209446710407679">"安全元件交易活動"</string>
+    <string name="permdesc_nfcTransactionEvent" msgid="1904286701876487397">"允許應用程式接收在安全元件上發生的交易相關資訊。"</string>
     <string name="permlab_disableKeyguard" msgid="3605253559020928505">"停用螢幕上鎖"</string>
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"允許應用程式停用按鍵鎖定以及其他相關的密碼安全措施。例如:手機收到來電時停用按鍵鎖定,通話結束後重新啟用按鍵鎖定。"</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"要求螢幕鎖定複雜程度"</string>
@@ -620,6 +622,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"無法使用生物識別硬件"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"已取消驗證"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"未能識別"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"無法辨識面孔"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"已取消驗證"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"未設定 PIN、圖案或密碼"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"驗證時發生錯誤"</string>
@@ -1393,7 +1396,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"拒絕"</string>
     <string name="select_input_method" msgid="3971267998568587025">"選擇輸入法"</string>
     <string name="show_ime" msgid="6406112007347443383">"在實體鍵盤處於連接狀態時保持顯示"</string>
-    <string name="hardware" msgid="1800597768237606953">"顯示虛擬鍵盤"</string>
+    <string name="hardware" msgid="3611039921284836033">"使用屏幕鍵盤"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"設定「<xliff:g id="DEVICE_NAME">%s</xliff:g>」"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"設定實體鍵盤"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"輕按即可選取語言和鍵盤配置"</string>
@@ -1956,7 +1959,7 @@
     <string name="app_blocked_title" msgid="7353262160455028160">"無法使用應用程式"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"目前無法使用「<xliff:g id="APP_NAME">%1$s</xliff:g>」。"</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"無法使用「<xliff:g id="ACTIVITY">%1$s</xliff:g>」"</string>
-    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="4483161748582966785">"需要權限"</string>
+    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="3805704317624448487">"已抑制權限要求"</string>
     <string name="app_streaming_blocked_title_for_camera_dialog" msgid="3935701653713853065">"無法使用相機"</string>
     <string name="app_streaming_blocked_title_for_fingerprint_dialog" msgid="3516853717714141951">"請繼續透過手機操作"</string>
     <string name="app_streaming_blocked_title_for_microphone_dialog" msgid="544822455127171206">"無法使用麥克風"</string>
@@ -1967,6 +1970,9 @@
     <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"目前無法在 <xliff:g id="DEVICE">%1$s</xliff:g> 上存取此應用程式,請改用 Android TV 裝置存取。"</string>
     <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"目前無法在 <xliff:g id="DEVICE">%1$s</xliff:g> 上存取此應用程式,請改用平板電腦存取。"</string>
     <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"目前無法在 <xliff:g id="DEVICE">%1$s</xliff:g> 上存取此應用程式,請改用手機存取。"</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tv" msgid="4706276040125072077">"此應用程式要求額外的權限,但串流工作階段期間無法授予權限。請先在 Android TV 裝置上授予權限。"</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tablet" msgid="1824604581465771629">"此應用程式要求額外的權限,但串流工作階段期間無法授予權限。請先在平板電腦上授予權限。"</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="default" msgid="7755223160363292105">"此應用程式要求額外的權限,但串流工作階段期間無法授予權限。請先在手機上授予權限。"</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"此應用程式要求額外的安全措施,請改用 Android TV 裝置。"</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"此應用程式要求額外的安全措施,請改用平板電腦。"</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"此應用程式要求額外的安全措施,請改用手機。"</string>
diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml
index 958073a..0089747 100644
--- a/core/res/res/values-zh-rTW/strings.xml
+++ b/core/res/res/values-zh-rTW/strings.xml
@@ -590,6 +590,8 @@
     <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"允許應用程式取得首選 NFC 付費服務資訊,例如已註冊的輔助工具和路線目的地。"</string>
     <string name="permlab_nfc" msgid="1904455246837674977">"控制近距離無線通訊"</string>
     <string name="permdesc_nfc" msgid="8352737680695296741">"允許應用程式與近距離無線通訊 (NFC) 電子感應標籤、卡片及感應器進行通訊。"</string>
+    <string name="permlab_nfcTransactionEvent" msgid="5868209446710407679">"安全元件交易事件"</string>
+    <string name="permdesc_nfcTransactionEvent" msgid="1904286701876487397">"允許應用程式接收在安全元件上發生的交易相關資訊。"</string>
     <string name="permlab_disableKeyguard" msgid="3605253559020928505">"停用螢幕鎖定"</string>
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"允許應用程式停用按鍵鎖定以及其他相關的密碼安全性功能。例如:手機收到來電時停用按鍵鎖定,通話結束後重新啟用按鍵鎖定。"</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"要求螢幕鎖定的複雜度"</string>
@@ -620,6 +622,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"無法使用生物特徵辨識硬體"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"已取消驗證"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"無法辨識"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"無法辨識臉孔"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"已取消驗證"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"未設定 PIN 碼、解鎖圖案或密碼"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"驗證時發生錯誤"</string>
@@ -1393,7 +1396,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"拒絕"</string>
     <string name="select_input_method" msgid="3971267998568587025">"選擇輸入法"</string>
     <string name="show_ime" msgid="6406112007347443383">"使用實體鍵盤時仍繼續顯示螢幕小鍵盤"</string>
-    <string name="hardware" msgid="1800597768237606953">"顯示虛擬鍵盤"</string>
+    <string name="hardware" msgid="3611039921284836033">"使用螢幕小鍵盤"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"設定「<xliff:g id="DEVICE_NAME">%s</xliff:g>」"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"設定實體鍵盤"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"輕觸即可選取語言和版面配置"</string>
@@ -1956,7 +1959,7 @@
     <string name="app_blocked_title" msgid="7353262160455028160">"應用程式無法使用"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」目前無法使用。"</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"無法存取「<xliff:g id="ACTIVITY">%1$s</xliff:g>」"</string>
-    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="4483161748582966785">"需要相關權限"</string>
+    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="3805704317624448487">"權限要求遭拒"</string>
     <string name="app_streaming_blocked_title_for_camera_dialog" msgid="3935701653713853065">"無法使用相機"</string>
     <string name="app_streaming_blocked_title_for_fingerprint_dialog" msgid="3516853717714141951">"請繼續在手機上操作"</string>
     <string name="app_streaming_blocked_title_for_microphone_dialog" msgid="544822455127171206">"無法使用麥克風"</string>
@@ -1967,6 +1970,9 @@
     <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"目前無法在 <xliff:g id="DEVICE">%1$s</xliff:g> 上存取這個應用程式,請改用 Android TV 裝置。"</string>
     <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"目前無法在 <xliff:g id="DEVICE">%1$s</xliff:g> 上存取這個項目,請改用平板電腦。"</string>
     <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"目前無法在 <xliff:g id="DEVICE">%1$s</xliff:g> 上存取這個應用程式,請改用手機。"</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tv" msgid="4706276040125072077">"這個應用程式要求額外權限,但串流期間無法授權。請先在 Android TV 裝置上授予權限。"</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tablet" msgid="1824604581465771629">"這個應用程式要求額外權限,但串流期間無法授權。請先在平板電腦上授予權限。"</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="default" msgid="7755223160363292105">"這個應用程式要求額外權限,但串流期間無法授權。請先在手機上授予權限。"</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"這個應用程式要求進行額外的安全性驗證,請改用 Android TV 裝置。"</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"這個應用程式要求進行額外的安全性驗證,請改用平板電腦。"</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"這個應用程式要求進行額外的安全性驗證,請改用手機。"</string>
diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml
index 270cc4b..a75f8eb 100644
--- a/core/res/res/values-zu/strings.xml
+++ b/core/res/res/values-zu/strings.xml
@@ -590,6 +590,8 @@
     <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"Ivuemela uhlelo lokusebenza ukuthola ulwazi lesevisi yenkokhelo ye-nfc njengezinsiza zokubhalisa nezindawo zomzila."</string>
     <string name="permlab_nfc" msgid="1904455246837674977">"lawula Uxhumano Lwenkambu Eseduze"</string>
     <string name="permdesc_nfc" msgid="8352737680695296741">"Ivuela uhlelo lokusebenza ukuthi ixhumane ne-Near Field Communication (NFC) amathegi, amakhadi kanye nezinhlelo zokufunda."</string>
+    <string name="permlab_nfcTransactionEvent" msgid="5868209446710407679">"Umcimbi wokwenziwe Ku-elementi Evikelekile"</string>
+    <string name="permdesc_nfcTransactionEvent" msgid="1904286701876487397">"Ivumela i-app ukuthi ithole ulwazi mayelana nemisebenzi eyenzekayo Ku-elementi Evikelekile."</string>
     <string name="permlab_disableKeyguard" msgid="3605253559020928505">"khubaza ukukhiya kwakho iskrini"</string>
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"Ivumela uhlelo lokusebenza ukukhubaza ukuvala ukhiye nanoma yikuphi ukuphepha kwephasiwedi okuhlobene. Isibonelo, ifoni ikhubaza ukuvala ukhiye lapho ithola ikholi yefoni engenayo, bese inike amandla kabusha ukuvala ukhiye lapho ikholi isiqedile."</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"cela ubunkimbinkimbi kokukhiya isikrini"</string>
@@ -620,6 +622,7 @@
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"I-Biometric hardware ayitholakali"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Ukufakazela ubuqiniso kukhanseliwe"</string>
     <string name="biometric_not_recognized" msgid="5106687642694635888">"Akwaziwa"</string>
+    <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Ubuso abaziwa"</string>
     <string name="biometric_error_canceled" msgid="8266582404844179778">"Ukufakazela ubuqiniso kukhanseliwe"</string>
     <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"Ayikho iphinikhodi, iphethini, noma iphasiwedi esethiwe"</string>
     <string name="biometric_error_generic" msgid="6784371929985434439">"Iphutha lokufakazela ubuqiniso"</string>
@@ -1393,7 +1396,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"YENQABA"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Khetha indlela yokufaka"</string>
     <string name="show_ime" msgid="6406112007347443383">"Yigcine kusikrini ngenkathi kusebenza ikhibhodi ephathekayo"</string>
-    <string name="hardware" msgid="1800597768237606953">"Bonisa ikhibhodi ebonakalayo"</string>
+    <string name="hardware" msgid="3611039921284836033">"Sebenzisa ikhibhodi ekuskrini"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"Lungisa i-<xliff:g id="DEVICE_NAME">%s</xliff:g>"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Lungiselela amakhibhodi aphathekayo"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Thepha ukuze ukhethe ulimi nesakhiwo"</string>
@@ -1956,7 +1959,7 @@
     <string name="app_blocked_title" msgid="7353262160455028160">"Uhlelo lokusebenza alutholakali"</string>
     <string name="app_blocked_message" msgid="542972921087873023">"I-<xliff:g id="APP_NAME">%1$s</xliff:g> ayitholakali khona manje."</string>
     <string name="app_streaming_blocked_title" msgid="6090945835898766139">"okungatholakali <xliff:g id="ACTIVITY">%1$s</xliff:g>"</string>
-    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="4483161748582966785">"Kudingeka imvume"</string>
+    <string name="app_streaming_blocked_title_for_permission_dialog" msgid="3805704317624448487">"Isicelo semvume sicindezelwe"</string>
     <string name="app_streaming_blocked_title_for_camera_dialog" msgid="3935701653713853065">"Ikhamera ayitholakali"</string>
     <string name="app_streaming_blocked_title_for_fingerprint_dialog" msgid="3516853717714141951">"Qhubeka kufoni"</string>
     <string name="app_streaming_blocked_title_for_microphone_dialog" msgid="544822455127171206">"Imakrofoni ayitholakali"</string>
@@ -1967,6 +1970,9 @@
     <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"Lokhu akukwazi ukufinyelelwa ku-<xliff:g id="DEVICE">%1$s</xliff:g> yakho ngalesi sikhathi. Zama kudivayisi yakho ye-Android TV kunalokho."</string>
     <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"Lokhu akukwazi ukufinyelelwa ku-<xliff:g id="DEVICE">%1$s</xliff:g> yakho ngalesi sikhathi. Zama kuthebhulethi yakho kunalokho."</string>
     <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"Lokhu akukwazi ukufinyelelwa ku-<xliff:g id="DEVICE">%1$s</xliff:g> yakho ngalesi sikhathi. Zama efonini yakho kunalokho."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tv" msgid="4706276040125072077">"Le-app icela izimvume ezengeziwe, kodwa izimvume azikwazi ukunikezwa ngesikhathi sokusakaza-bukhoma. Nikeza imvume kudivayisi yakho ye-Android TV kuqala."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="tablet" msgid="1824604581465771629">"Le-app icela izimvume ezengeziwe, kodwa izimvume azikwazi ukunikezwa ngesikhathi sokusakaza-bukhoma. Nikeza imvume kuthebulethi yakho kuqala."</string>
+    <string name="app_streaming_blocked_message_for_permission_request" product="default" msgid="7755223160363292105">"Le-app icela izimvume ezengeziwe, kodwa izimvume azikwazi ukunikezwa ngesikhathi sokusakaza-bukhoma. Nikeza imvume kufoni yakho kuqala."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"Le app icela ukuvikeleka okwengeziwe. Zama kudivayisi yakho ye-Android TV kunalokho."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"Le app icela ukuvikeleka okwengeziwe. Zama kuthebhulethi yakho kunalokho."</string>
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"Le app icela ukuvikeleka okwengeziwe. Zama efonini yakho kunalokho."</string>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 6f7bc53..d80cfa3 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -3030,15 +3030,15 @@
              representation this attribute can be used for providing such. -->
         <attr name="contentDescription" format="string" localization="suggested" />
 
-        <!-- Sets the id of a view before which this one is visited in accessibility traversal.
-             A screen-reader must visit the content of this view before the content of the one
-             it precedes.
+        <!-- Sets the id of a view that screen readers are requested to visit after this view.
+             Requests that a screen-reader visits the content of this view before the content of the
+             one it precedes. This does nothing if either view is not important for accessibility.
              {@see android.view.View#setAccessibilityTraversalBefore(int)} -->
         <attr name="accessibilityTraversalBefore" format="integer" />
 
-        <!-- Sets the id of a view after which this one is visited in accessibility traversal.
-             A screen-reader must visit the content of the other view before the content of
-             this one.
+        <!-- Sets the id of a view that screen readers are requested to visit before this view.
+             Requests that a screen-reader visits the content of the other view before the content
+             of this one. This does nothing if either view is not important for accessibility.
              {@see android.view.View#setAccessibilityTraversalAfter(int)} -->
         <attr name="accessibilityTraversalAfter" format="integer" />
 
@@ -9094,7 +9094,10 @@
         <attr name="dotColor" format="color|reference"/>
         <!-- Color of the dot when it's activated -->
         <attr name="dotActivatedColor" format="color|reference"/>
-
+        <!-- Keep dot in activated state until segment completion -->
+        <attr name="keepDotActivated" format="boolean"/>
+        <!-- Enlarge vertex entry area for some form factors -->
+        <attr name="enlargeVertexEntryArea" format="boolean"/>
     </declare-styleable>
 
     <!-- =============================== -->
@@ -9160,10 +9163,10 @@
              {@link android.os.Build.VERSION_CODES#N} and not used in previous versions. -->
         <attr name="supportsLocalInteraction" format="boolean" />
         <!-- The service that provides {@link android.service.voice.HotwordDetectionService}.
-             @hide @SystemApi -->
+             Expect a component name to be provided. @hide @SystemApi -->
         <attr name="hotwordDetectionService" format="string" />
         <!-- The service that provides {@link android.service.voice.VisualQueryDetectionService}.
-             @hide @SystemApi -->
+             Expect a component name to be provided. @hide @SystemApi -->
         <attr name="visualQueryDetectionService" format="string" />
 
     </declare-styleable>
@@ -10113,13 +10116,12 @@
     <declare-styleable name="CredentialProvider">
         <!-- A string that is displayed to the user in the Credential Manager settings
              screen that can be used to provide more information about a provider. For
-             longer strings (40 char) it will be truncated. If multiple services
-             show the subtitle then the string will be joined together. -->
+             longer strings it will be truncated. -->
         <attr name="settingsSubtitle" format="string" />
     </declare-styleable>
 
     <!-- A list of capabilities that indicates to the OS what kinds of credentials
-             this provider supports. This list is defined in CredentialProviderService. -->
+             this provider supports. -->
     <declare-styleable name="CredentialProvider_Capabilities" parent="CredentialProvider">
         <!-- An individual capability declared by the provider. -->
         <attr name="capability" format="string" />
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 06be5fa..42a249c 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -46,6 +46,7 @@
         <item><xliff:g id="id">@string/status_bar_secure</xliff:g></item>
         <item><xliff:g id="id">@string/status_bar_managed_profile</xliff:g></item>
         <item><xliff:g id="id">@string/status_bar_cast</xliff:g></item>
+        <item><xliff:g id="id">@string/status_bar_connected_display</xliff:g></item>
         <item><xliff:g id="id">@string/status_bar_screen_record</xliff:g></item>
         <item><xliff:g id="id">@string/status_bar_vpn</xliff:g></item>
         <item><xliff:g id="id">@string/status_bar_bluetooth</xliff:g></item>
@@ -72,6 +73,7 @@
     <string translatable="false" name="status_bar_sync_failing">sync_failing</string>
     <string translatable="false" name="status_bar_sync_active">sync_active</string>
     <string translatable="false" name="status_bar_cast">cast</string>
+    <string translatable="false" name="status_bar_connected_display">connected_display</string>
     <string translatable="false" name="status_bar_hotspot">hotspot</string>
     <string translatable="false" name="status_bar_location">location</string>
     <string translatable="false" name="status_bar_bluetooth">bluetooth</string>
@@ -192,6 +194,10 @@
          available on some devices. -->
     <bool name="config_enableHapticTextHandle">false</bool>
 
+    <!-- Enables or disables proximity service that approximates proximity with aiai attention
+         service. Off by default, since the service may not be available on some devices. -->
+    <bool name="config_enableProximityService">false</bool>
+
     <!-- Whether dialogs should close automatically when the user touches outside
          of them.  This should not normally be modified. -->
     <bool name="config_closeDialogWhenTouchOutside">true</bool>
@@ -1022,7 +1028,7 @@
          requires swapping ROTATION_90 and ROTATION_270.
          TODO(b/265991392): This should eventually be configured and parsed in
           display_settings.xml -->
-    <bool name="config_matchSecondaryInternalDisplaysOrientationToReverseDefaultDisplay">true</bool>
+    <bool name="config_matchSecondaryInternalDisplaysOrientationToReverseDefaultDisplay">false</bool>
 
     <!-- Indicate available ColorDisplayManager.COLOR_MODE_xxx. -->
     <integer-array name="config_availableColorModes">
@@ -1675,16 +1681,56 @@
          in the config_autoBrightnessLevels array.  This array should have size one greater
          than the size of the config_autoBrightnessLevels array.
          The brightness values must be between 0 and 255 and be non-decreasing.
+
          This must be overridden in platform specific overlays -->
     <integer-array name="config_autoBrightnessButtonBacklightValues">
     </integer-array>
 
-    <!-- Array of output values for keyboard backlight corresponding to the lux values
-         in the config_autoBrightnessLevels array.  This array should have size one greater
-         than the size of the config_autoBrightnessLevels array.
-         The brightness values must be between 0 and 255 and be non-decreasing.
+    <!-- Smoothing constant for Ambient keyboard backlight change. It should contain value
+         in the range (0.0, 1.0] that will be used to calculate smoothed lux values using
+         simple exponential smoothing. This value indicated how quickly we transition to
+         the lux values provided by the Ambient light sensor.
+         Simple formula for newLuxValue = (1-constant)*currLuxValue + constant*rawLuxValue, where
+         rawLuxValue is the value provided by the Ambient light sensor. (e.g. value of 1.0 means we
+         immediately start using the value provided by the Ambient light sensor)
          This must be overridden in platform specific overlays -->
-    <integer-array name="config_autoBrightnessKeyboardBacklightValues">
+    <item name="config_autoKeyboardBrightnessSmoothingConstant" format="float" type="dimen">
+        1.0
+    </item>
+
+    <!-- Array of output values for keyboard backlight corresponding to the lux values
+         in the config_autoKeyboardBacklight(Increase/Decrease)LuxThreshold arrays.
+         The brightness values must be between 0 and 255 and be non-decreasing.
+
+         This can be overridden in platform specific overlays -->
+    <integer-array name="config_autoKeyboardBacklightBrightnessValues">
+        <item>102</item>
+        <item>153</item>
+        <item>0</item>
+    </integer-array>
+
+    <!-- Array of threshold values for keyboard backlight corresponding to the values
+         in the config_autoKeyboardBacklightBrightnessValues array.
+         These lux values indicate when to move to a lower keyboard backlight value,
+         as defined in config_autoKeyboardBacklightBrightnessValues array.
+
+         This can be overridden in platform specific overlays -->
+    <integer-array name="config_autoKeyboardBacklightDecreaseLuxThreshold">
+        <item>-1</item>
+        <item>40</item>
+        <item>150</item>
+    </integer-array>
+
+    <!-- Array of threshold values for keyboard backlight corresponding to the values
+         in the config_autoKeyboardBacklightBrightnessValues array.
+         These lux values indicate when to move to a higher keyboard backlight value,
+         as defined in config_autoKeyboardBacklightBrightnessValues array.
+
+         This can be overridden in platform specific overlays -->
+    <integer-array name="config_autoKeyboardBacklightIncreaseLuxThreshold">
+        <item>55</item>
+        <item>200</item>
+        <item>-1</item>
     </integer-array>
 
     <!-- An array describing the screen's backlight values corresponding to the brightness
@@ -1817,6 +1863,17 @@
          specified -->
     <string name="default_wallpaper_component" translatable="false">@null</string>
 
+    <!-- Default wallpaper component per device color map, each item is a comma separated key-value
+         pair with key being a device color and value being the corresponding wallpaper component.
+         The component with its key matching the device color will be the default wallpaper, the
+         default wallpaper component will be the default if this config is not specified.
+
+         E.g. for SLV color, and com.android.example/com.android.example.SlVDefaultWallpaper
+         <item>SLV,com.android.example/com.android.example.SlVDefaultWallpaper</item> -->
+    <string-array name="default_wallpaper_component_per_device_color" translatable="false">
+        <!-- Add packages here -->
+    </string-array>
+
     <!-- By default a product has no distinct default lock wallpaper -->
     <item name="default_lock_wallpaper" type="drawable">@null</item>
 
@@ -1840,6 +1897,11 @@
         <item>telephony</item>
     </string-array>
 
+    <!-- The difference in millis that has to exist between a time suggestion under
+         consideration by the time_detector and the system clock before the system clock will be
+         changed. -->
+    <integer name="config_timeDetectorAutoUpdateDiffMillis">2000</integer>
+
     <!-- Enables the GnssTimeUpdate service. This is the global switch for enabling Gnss time based
          suggestions to TimeDetector service. See also config_autoTimeSourcesPriority. -->
     <bool name="config_enableGnssTimeUpdateService">false</bool>
@@ -2247,6 +2309,8 @@
     <string name="config_systemFinancedDeviceController" translatable="false">com.android.devicelockcontroller</string>
     <!-- The name of the package that will hold the call streaming role. -->
     <string name="config_systemCallStreaming" translatable="false"></string>
+    <!-- The name of the package that will hold the default retail demo role. -->
+    <string name="config_defaultRetailDemo" translatable="false"></string>
 
     <!-- The component name of the wear service class that will be started by the system server. -->
     <string name="config_wearServiceComponent" translatable="false"></string>
@@ -2747,6 +2811,11 @@
          measured in dips per second. Setting this to -1dp disables rotary encoder fling.  -->
     <dimen name="config_viewMaxRotaryEncoderFlingVelocity">-1dp</dimen>
 
+    <!-- Tick intervals in dp for rotary encoder scrolls on {@link MotionEvent#AXIS_SCROLL}
+         generated by {@link InputDevice#SOURCE_ROTARY_ENCODER} devices. A valid tick interval value
+         is a positive value. Setting this to 0dp disables scroll tick. -->
+    <dimen name="config_rotaryEncoderAxisScrollTickInterval">21dp</dimen>
+
     <!-- Amount of time in ms the user needs to press the relevant key to bring up the
          global actions dialog -->
     <integer name="config_globalActionsKeyTimeout">500</integer>
@@ -2853,6 +2922,10 @@
      with admin privileges and admin privileges can be granted/revoked from existing users. -->
     <bool name="config_enableMultipleAdmins">false</bool>
 
+    <!-- Whether there is a communal profile which should always be running.
+         Only relevant for Headless System User Mode (HSUM) devices. -->
+    <bool name="config_omnipresentCommunalUser">false</bool>
+
     <!-- Whether the new Auto Selection Network UI should be shown -->
     <bool name="config_enableNewAutoSelectNetworkUI">false</bool>
 
@@ -3392,7 +3465,7 @@
     <!-- default window ShowCircularMask property -->
     <bool name="config_windowShowCircularMask">false</bool>
 
-    <!-- default value for whether circular emulators (ro.emulator.circular)
+    <!-- default value for whether circular emulators (ro.boot.emulator.circular)
          should show a display overlay on the screen -->
     <bool name="config_windowEnableCircularEmulatorDisplayOverlay">false</bool>
 
@@ -3868,6 +3941,10 @@
          non-zero amplitudes, to bring the vibrator amplitude down to zero using this timing. -->
     <integer name="config_vibrationWaveformRampDownDuration">0</integer>
 
+    <!-- Ignores vibrations when the device is on a wireless charger.
+         A vibrating device may move out of alignment with the charging pad. -->
+    <bool name="config_ignoreVibrationsOnWirelessCharger">false</bool>
+
     <!-- Number of retries Cell Data should attempt for a given error code before
          restarting the modem.
          Error codes not listed will not lead to modem restarts.
@@ -4048,6 +4125,10 @@
     -->
     <string name="config_wallpaperCropperPackage" translatable="false">com.android.wallpapercropper</string>
 
+    <!-- Wallpaper will get top app scheduling priority if this is set to true.
+    -->
+    <bool name="config_wallpaperTopApp">false</bool>
+
     <!-- True if the device supports at least one form of multi-window.
          E.g. freeform, split-screen, picture-in-picture. -->
     <bool name="config_supportsMultiWindow">true</bool>
@@ -4307,12 +4388,12 @@
          Example: "com.android.companiondevicemanager"
          See android.companion.CompanionDeviceManager.
     -->
-    <string name="config_companionDeviceManagerPackage" translatable="false"></string>
+    <string name="config_companionDeviceManagerPackage" translatable="false">com.android.companiondevicemanager</string>
 
     <!-- A list of packages managing companion device(s) by the same manufacturers as the main
          device. It will fall back to showing a prompt if the association has been called multiple
          times in a short period.
-         Note that config_companionDeviceManagerPackage and config_companionDeviceCerts are
+         Note that config_companionDevicePackages and config_companionDeviceCerts are
          parallel arrays.
      -->
     <string-array name="config_companionDevicePackages" translatable="false"></string-array>
@@ -4320,7 +4401,7 @@
     <!-- A list of SHA256 Certificates managing companion device(s) by the same manufacturers as
          the main device. It will fall back to showing a prompt if the association has been called
          multiple times in a short period.
-         Note that config_companionDeviceCerts and config_companionDeviceManagerPackage are parallel
+         Note that config_companionDeviceCerts and config_companionDevicePackages are parallel
          arrays.
          Example: "1A:2B:3C:4D"
      -->
@@ -4419,6 +4500,14 @@
     -->
     <string name="config_defaultContentCaptureService" translatable="false"></string>
 
+    <!-- The package name for the system's content protection service.
+         This service must be trusted, as it can be activated without explicit consent of the user.
+         If no service with the specified name exists on the device, content protection will be
+         disabled.
+         Example: "com.android.contentprotection/.ContentProtectionService"
+    -->
+    <string name="config_defaultContentProtectionService" translatable="false"></string>
+
     <!-- The package name for the system's augmented autofill service.
          This service must be trusted, as it can be activated without explicit consent of the user.
          If no service with the specified name exists on the device, augmented autofill wil be
@@ -4723,8 +4812,6 @@
     <!-- Size of icon shown beside a preference locked by admin -->
     <dimen name="config_restrictedIconSize">@dimen/restricted_icon_size_material</dimen>
 
-    <string translatable="false" name="config_batterySaverDeviceSpecificConfig"></string>
-
     <!-- Component name that should be granted Notification Assistant access -->
     <string name="config_defaultAssistantAccessComponent" translatable="false">android.ext.services/android.ext.services.notification.Assistant</string>
 
@@ -4751,11 +4838,8 @@
     <!-- Whether or not we should show the option to show battery percentage -->
     <bool name="config_battery_percentage_setting_available">true</bool>
 
-    <!-- Whether or not battery saver should be "sticky" when manually enabled. -->
-    <bool name="config_batterySaverStickyBehaviourDisabled">false</bool>
-
-    <!-- Config flag to track default disable threshold for Dynamic power savings enabled battery saver. -->
-    <integer name="config_dynamicPowerSavingsDefaultDisableThreshold">80</integer>
+    <!-- Default value set for battery percentage in status bar false = disabled, true = enabled -->
+    <bool name="config_defaultBatteryPercentageSetting">false</bool>
 
     <!-- Model of potentially misprovisioned devices. If none is specified in an overlay, an
          empty string is passed in. -->
@@ -5513,13 +5597,13 @@
 
     <!-- Blur radius for the Option 3 in R.integer.config_letterboxBackgroundType. Values < 0 are
         ignored and 0 is used. -->
-    <dimen name="config_letterboxBackgroundWallpaperBlurRadius">31dp</dimen>
+    <dimen name="config_letterboxBackgroundWallpaperBlurRadius">24dp</dimen>
 
     <!-- Alpha of a black translucent scrim showed over wallpaper letterbox background when
         the Option 3 is selected for R.integer.config_letterboxBackgroundType.
         Values < 0 or >= 1 are ignored and 0.0 (transparent) is used instead. -->
     <item name="config_letterboxBackgroundWallaperDarkScrimAlpha" format="float" type="dimen">
-        0.5
+        0.68
     </item>
 
     <!-- Corners appearance of the letterbox background.
@@ -5544,7 +5628,7 @@
             but isn't supported on the device or both dark scrim alpha and blur radius aren't
             provided.
      -->
-    <color name="config_letterboxBackgroundColor">@color/letterbox_background</color>
+    <color name="config_letterboxBackgroundColor">@color/system_on_secondary_fixed</color>
 
     <!-- Horizontal position of a center of the letterboxed app window.
         0 corresponds to the left side of the screen and 1 to the right side. If given value < 0
@@ -5643,6 +5727,9 @@
          TODO(b/255532890) Enable when ignoreOrientationRequest is set -->
     <bool name="config_letterboxIsEnabledForTranslucentActivities">false</bool>
 
+    <!-- Whether per-app user aspect ratio override settings is enabled -->
+    <bool name="config_appCompatUserAppAspectRatioSettingsIsEnabled">false</bool>
+
     <!-- Whether sending compat fake focus for split screen resumed activities is enabled.
          Needed because some game engines wait to get focus before drawing the content of
          the app which isn't guaranteed by default in multi-window modes. -->
@@ -5978,9 +6065,6 @@
     <!-- Whether changing sensor privacy SW setting requires device to be unlocked -->
     <bool name="config_sensorPrivacyRequiresAuthentication">true</bool>
 
-    <!-- List containing the allowed install sources for accessibility service. -->
-    <string-array name="config_accessibility_allowed_install_source" translatable="false"/>
-
     <!-- Default value for Settings.ASSIST_LONG_PRESS_HOME_ENABLED -->
     <bool name="config_assistLongPressHomeEnabledDefault">true</bool>
     <!-- Default value for Settings.ASSIST_TOUCH_GESTURE_ENABLED -->
@@ -6185,7 +6269,7 @@
 
     <!-- Flag indicating whether the show Stylus pointer icon.
      If set, a pointer icon will be shown over the location of a stylus pointer.-->
-    <bool name="config_enableStylusPointerIcon">false</bool>
+    <bool name="config_enableStylusPointerIcon">true</bool>
 
     <!-- Determines whether SafetyCenter feature is enabled. -->
     <bool name="config_enableSafetyCenter">true</bool>
@@ -6446,4 +6530,16 @@
     <!-- Whether the AOSP support for app cloning building blocks is to be enabled for the
          device. -->
     <bool name="config_enableAppCloningBuildingBlocks">true</bool>
+
+    <!-- Enables or disables support for repair mode. The feature creates a secure
+         environment to protect the user's privacy when the device is being repaired.
+         Off by default, since OEMs may have had a similar feature on their devices. -->
+    <bool name="config_repairModeSupported">false</bool>
+
+    <!-- The file path in which the default shutdown vibration effect should be serialized. If the
+         device does not specify any such file path here, if the file path specified here does not
+         exist, or if the contents of the file does not make up a valid VibrationEffect
+         serialization, a default vibration will be used.
+         Note that, indefinitely repeating vibrations are not allowed as shutdown vibrations. -->
+    <string name="config_defaultShutdownVibrationFile" />
 </resources>
diff --git a/core/res/res/values/config_battery_saver.xml b/core/res/res/values/config_battery_saver.xml
new file mode 100644
index 0000000..eb396df
--- /dev/null
+++ b/core/res/res/values/config_battery_saver.xml
@@ -0,0 +1,90 @@
+<?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.
+*/
+-->
+
+<!-- These resources are around just to allow their values to be customized
+     for different hardware and product builds.  Do not translate.
+
+     NOTE: The naming convention is "config_camelCaseValue". Some legacy
+     entries do not follow the convention, but all new entries should. -->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string translatable="false" name="config_batterySaverDeviceSpecificConfig"></string>
+
+    <!-- Whether or not battery saver should be "sticky" when manually enabled. -->
+    <bool name="config_batterySaverStickyBehaviourDisabled">false</bool>
+
+    <!-- Config flag to track default disable threshold for Dynamic power savings enabled battery saver. -->
+    <integer name="config_dynamicPowerSavingsDefaultDisableThreshold">80</integer>
+
+    <!-- Default for BatterySaverPolicy.DEFAULT_FULL_POLICY.adjustBrightnessFactor -->
+    <item name="config_batterySaver_full_adjustBrightnessFactor" format="float" type="dimen">
+        .5
+    </item>
+
+    <!-- Default for BatterySaverPolicy.DEFAULT_FULL_POLICY.deferFullBackup -->
+    <bool name="config_batterySaver_full_deferFullBackup">true</bool>
+
+    <!-- Default for BatterySaverPolicy.DEFAULT_FULL_POLICY.deferKeyValueBackup -->
+    <bool name="config_batterySaver_full_deferKeyValueBackup">true</bool>
+
+    <!-- Default for BatterySaverPolicy.DEFAULT_FULL_POLICY.disableAnimation -->
+    <bool name="config_batterySaver_full_disableAnimation">false</bool>
+
+    <!-- Default for BatterySaverPolicy.DEFAULT_FULL_POLICY.disableAod -->
+    <bool name="config_batterySaver_full_disableAod">true</bool>
+
+    <!-- Default for BatterySaverPolicy.DEFAULT_FULL_POLICY.disableLaunchBoost -->
+    <bool name="config_batterySaver_full_disableLaunchBoost">true</bool>
+
+    <!-- Default for BatterySaverPolicy.DEFAULT_FULL_POLICY.disableOptionalSensors -->
+    <bool name="config_batterySaver_full_disableOptionalSensors">true</bool>
+
+    <!-- Default for BatterySaverPolicy.DEFAULT_FULL_POLICY.disableVibration -->
+    <bool name="config_batterySaver_full_disableVibration">false</bool>
+
+    <!-- Default for BatterySaverPolicy.DEFAULT_FULL_POLICY.enableAdjustBrightness -->
+    <bool name="config_batterySaver_full_enableAdjustBrightness">false</bool>
+
+    <!-- Default for BatterySaverPolicy.DEFAULT_FULL_POLICY.enableDataSaver -->
+    <bool name="config_batterySaver_full_enableDataSaver">false</bool>
+
+    <!-- Default for BatterySaverPolicy.DEFAULT_FULL_POLICY.enableFirewall -->
+    <bool name="config_batterySaver_full_enableFirewall">true</bool>
+
+    <!-- Default for BatterySaverPolicy.DEFAULT_FULL_POLICY.enableNightMode -->
+    <bool name="config_batterySaver_full_enableNightMode">true</bool>
+
+    <!-- Default for BatterySaverPolicy.DEFAULT_FULL_POLICY.enableQuickDoze -->
+    <bool name="config_batterySaver_full_enableQuickDoze">true</bool>
+
+    <!-- Default for BatterySaverPolicy.DEFAULT_FULL_POLICY.forceAllAppsStandby -->
+    <bool name="config_batterySaver_full_forceAllAppsStandby">true</bool>
+
+    <!-- Default for BatterySaverPolicy.DEFAULT_FULL_POLICY.forceBackgroundCheck -->
+    <bool name="config_batterySaver_full_forceBackgroundCheck">true</bool>
+
+    <!-- Default for BatterySaverPolicy.DEFAULT_FULL_POLICY.locationMode
+       (LOCATION_MODE_FOREGROUND_ONLY) -->
+    <integer name="config_batterySaver_full_locationMode">3</integer>
+
+    <!-- Default for BatterySaverPolicy.DEFAULT_FULL_POLICY.soundTriggerMode
+       (SOUND_TRIGGER_MODE_CRITICAL_ONLY) -->
+    <integer name="config_batterySaver_full_soundTriggerMode">1</integer>
+</resources>
+
diff --git a/core/res/res/values/config_device_idle.xml b/core/res/res/values/config_device_idle.xml
index 5576b9f..764dbbe 100644
--- a/core/res/res/values/config_device_idle.xml
+++ b/core/res/res/values/config_device_idle.xml
@@ -117,13 +117,10 @@
     <!-- Default for DeviceIdleController.Constants.WAIT_FOR_UNLOCK -->
     <bool name="device_idle_wait_for_unlock">true</bool>
 
-    <!-- Default for DeviceIdleController.Constants.PRE_IDLE_FACTOR_LONG -->
-    <item name="device_idle_pre_idle_factor_long" format="float" type="integer">1.67</item>
-
-    <!-- Default for DeviceIdleController.Constants.PRE_IDLE_FACTOR_SHORT -->
-    <item name="device_idle_pre_idle_factor_short" format="float" type="integer">0.33</item>
-
     <!-- Default for DeviceIdleController.Constants.USE_WINDOW_ALARMS -->
     <bool name="device_idle_use_window_alarms">true</bool>
+
+    <!-- Default for DeviceIdleController.Constants.USE_BODY_SENSOR -->
+    <bool name="device_idle_use_body_sensor">false</bool>
 </resources>
 
diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml
index fd74185..b7a5bc8 100644
--- a/core/res/res/values/config_telephony.xml
+++ b/core/res/res/values/config_telephony.xml
@@ -67,6 +67,15 @@
     <bool name="auto_data_switch_ping_test_before_switch">true</bool>
     <java-symbol type="bool" name="auto_data_switch_ping_test_before_switch" />
 
+    <!-- Define the tolerated gap of score for auto data switch decision, larger than which the
+         device will switch to the SIM with higher score. The score is used in conjunction with the
+         score table defined in
+         CarrierConfigManager#KEY_AUTO_DATA_SWITCH_RAT_SIGNAL_SCORE_STRING_ARRAY.
+         If 0, the device always switch to the higher score SIM.
+         If < 0, the network type and signal strength based auto switch is disabled. -->
+    <integer name="auto_data_switch_score_tolerance">3000</integer>
+    <java-symbol type="integer" name="auto_data_switch_score_tolerance" />
+
     <!-- Boolean indicating whether the Iwlan data service supports persistence of iwlan ipsec
          tunnels across service restart. If iwlan tunnels are not persisted across restart,
          Framework will clean up dangling data connections when service restarts -->
@@ -142,6 +151,23 @@
     <integer name="config_timeout_to_receive_delivered_ack_millis">300000</integer>
     <java-symbol type="integer" name="config_timeout_to_receive_delivered_ack_millis" />
 
+    <!-- Telephony config for services supported by satellite providers. The format of each config
+         string in the array is as follows: "PLMN_1:service_1,service_2,..."
+         where PLMN is the satellite PLMN of a provider and service is an integer with the
+         following value:
+            1 = {@link android.telephony.NetworkRegistrationInfo#SERVICE_TYPE_VOICE}
+            2 = {@link android.telephony.NetworkRegistrationInfo#SERVICE_TYPE_DATA}
+            3 = {@link android.telephony.NetworkRegistrationInfo#SERVICE_TYPE_SMS}
+            4 = {@link android.telephony.NetworkRegistrationInfo#SERVICE_TYPE_VIDEO}
+            5 = {@link android.telephony.NetworkRegistrationInfo#SERVICE_TYPE_EMERGENCY}
+         Example of a config string: "10011:2,3"
+
+         The PLMNs not configured in this array will be ignored and will not be used for satellite
+         scanning. -->
+    <string-array name="config_satellite_services_supported_by_providers" translatable="false">
+    </string-array>
+    <java-symbol type="array" name="config_satellite_services_supported_by_providers" />
+
     <!-- Whether enhanced IWLAN handover check is enabled. If enabled, telephony frameworks
          will not perform handover if the target transport is out of service, or VoPS not
          supported. The network will be torn down on the source transport, and will be
@@ -168,8 +194,9 @@
     <bool name="ignore_emergency_number_routing_from_db">false</bool>
     <java-symbol type="bool" name="ignore_emergency_number_routing_from_db" />
 
-    <!-- Whether "Virtual DSDA", i.e. in-call IMS connectivity can be provided on both subs with
-         only single logical modem, by using its data connection in addition to cellular IMS. -->
-    <bool name="config_enable_virtual_dsda">false</bool>
-    <java-symbol type="bool" name="config_enable_virtual_dsda" />
+    <!-- Boolean indicating whether allow sending null to modem to clear the previous initial attach
+         data profile -->
+    <bool name="allow_clear_initial_attach_data_profile">false</bool>
+    <java-symbol type="bool" name="allow_clear_initial_attach_data_profile" />
+
 </resources>
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 24da59a..0f2c264a 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -95,8 +95,10 @@
     <dimen name="navigation_bar_height_landscape_car_mode">96dp</dimen>
     <!-- Width of the navigation bar when it is placed vertically on the screen in car mode -->
     <dimen name="navigation_bar_width_car_mode">96dp</dimen>
-    <!-- Height of notification icons in the status bar -->
+    <!-- Original dp height of notification icons in the status bar  -->
     <dimen name="status_bar_icon_size">22dip</dimen>
+    <!-- New sp height of notification icons in the status bar  -->
+    <dimen name="status_bar_icon_size_sp">22sp</dimen>
     <!-- Desired size of system icons in status bar. -->
     <dimen name="status_bar_system_icon_size">15dp</dimen>
     <!-- Intrinsic size of most system icons in status bar. This is the default value that
@@ -685,6 +687,9 @@
     <!-- Parameters applied to line disappearing animation in LockPatternView in milliseconds. -->
     <integer name="lock_pattern_line_fade_out_duration">500</integer>
     <integer name="lock_pattern_line_fade_out_delay">150</integer>
+    <!-- Parameters applied to fade pattern animation in LockPatternView in milliseconds. -->
+    <integer name="lock_pattern_fade_pattern_duration">200</integer>
+    <integer name="lock_pattern_fade_pattern_delay">2300</integer>
 
     <dimen name="text_handle_min_size">40dp</dimen>
 
diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml
index 49a1940..adc7fe0 100644
--- a/core/res/res/values/public-staging.xml
+++ b/core/res/res/values/public-staging.xml
@@ -119,6 +119,8 @@
   </staging-public-group>
 
   <staging-public-group type="string" first-id="0x01ba0000">
+    <!-- @hide @SystemApi -->
+    <public name="config_defaultRetailDemo" />
   </staging-public-group>
 
   <staging-public-group type="dimen" first-id="0x01b90000">
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index a5b2b85..a11eaa9 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1711,6 +1711,12 @@
       with Near Field Communication (NFC) tags, cards, and readers.</string>
 
     <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_nfcTransactionEvent">Secure Element transaction event</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_nfcTransactionEvent">Allows the app to receive information about
+      transactions happening on a Secure Element.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permlab_disableKeyguard">disable your screen lock</string>
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permdesc_disableKeyguard">Allows the app to disable the
@@ -1780,6 +1786,10 @@
     <string name="biometric_dialog_default_title">Verify it\u2019s you</string>
     <!-- Subtitle shown on the system-provided biometric dialog, asking the user to authenticate with a biometric (e.g. fingerprint or face). [CHAR LIMIT=70] -->
     <string name="biometric_dialog_default_subtitle">Use your biometric to continue</string>
+    <!-- Subtitle shown on the system-provided biometric dialog, asking the user to authenticate with fingerprint. [CHAR LIMIT=70] -->
+    <string name="biometric_dialog_fingerprint_subtitle">Use your fingerprint to continue</string>
+    <!-- Subtitle shown on the system-provided biometric dialog, asking the user to authenticate with face. [CHAR LIMIT=70] -->
+    <string name="biometric_dialog_face_subtitle">Use your face to continue</string>
     <!-- Subtitle shown on the system-provided biometric dialog, asking the user to authenticate with a biometric (e.g. fingerprint or face) or their screen lock credential (i.e. PIN, pattern, or password). [CHAR LIMIT=90] -->
     <string name="biometric_or_screen_lock_dialog_default_subtitle">Use your biometric or screen lock to continue</string>
 
@@ -1789,6 +1799,8 @@
     <string name="biometric_error_user_canceled">Authentication canceled</string>
     <!-- Message shown by the biometric dialog when biometric is not recognized -->
     <string name="biometric_not_recognized">Not recognized</string>
+    <!-- Message shown by the biometric dialog when face is not recognized [CHAR LIMIT=50] -->
+    <string name="biometric_face_not_recognized">Face not recognized</string>
     <!-- Message shown when biometric authentication has been canceled [CHAR LIMIT=50] -->
     <string name="biometric_error_canceled">Authentication canceled</string>
     <!-- Message returned to applications if BiometricPrompt setAllowDeviceCredentials is enabled but no pin, pattern, or password is set. [CHAR LIMIT=NONE] -->
@@ -3790,7 +3802,7 @@
          keyboard is connected -->
     <string name="show_ime">Keep it on screen while physical keyboard is active</string>
     <!-- Title of the physical keyboard category in the input method selector [CHAR LIMIT=30] -->
-    <string name="hardware">Show virtual keyboard</string>
+    <string name="hardware">Use on-screen keyboard</string>
 
     <!-- Title of the notification to prompt the user to configure physical keyboard settings. [CHAR LIMIT=NOTIF_TITLE] -->
     <string name="select_keyboard_layout_notification_title">Configure <xliff:g id="device_name" example="Foobar USB Keyboard">%s</xliff:g></string>
@@ -5371,7 +5383,7 @@
     <!-- Title of the dialog shown when an app is blocked from being streamed to a remote device. [CHAR LIMIT=NONE] -->
     <string name="app_streaming_blocked_title"><xliff:g id="activity" example="Permission dialog">%1$s</xliff:g> unavailable</string>
     <!-- Title of the dialog shown when the permissioncontroller is blocked from being streamed to a remote device. [CHAR LIMIT=NONE] -->
-    <string name="app_streaming_blocked_title_for_permission_dialog">Permission needed</string>
+    <string name="app_streaming_blocked_title_for_permission_dialog">Permission request suppressed</string>
     <!-- Title of the dialog shown when the camera permission is blocked from being streamed to a remote device. [CHAR LIMIT=NONE] -->
     <string name="app_streaming_blocked_title_for_camera_dialog">Camera unavailable</string>
     <!-- Title of the dialog shown when the fingerprint permission is blocked from being streamed to a remote device. [CHAR LIMIT=NONE] -->
@@ -5392,6 +5404,12 @@
     <string name="app_streaming_blocked_message" product="tablet">This can’t be accessed on your <xliff:g id="device" example="Chromebook">%1$s</xliff:g> at this time. Try on your tablet instead.</string>
     <!-- Message shown when an app is blocked from being streamed to a remote device. [CHAR LIMIT=NONE] -->
     <string name="app_streaming_blocked_message" product="default">This can’t be accessed on your <xliff:g id="device" example="Chromebook">%1$s</xliff:g> at this time. Try on your phone instead.</string>
+    <!-- Message shown when a runtime permission request is blocked from being streamed to a remote device. [CHAR LIMIT=NONE] -->
+    <string name="app_streaming_blocked_message_for_permission_request" product="tv">This app is requesting additional permissions, but permissions can’t be granted in a streaming session. Grant the permission on your Android TV device first.</string>
+    <!-- Message shown when a runtime permission request is blocked from being streamed to a remote device. [CHAR LIMIT=NONE] -->
+    <string name="app_streaming_blocked_message_for_permission_request" product="tablet">This app is requesting additional permissions, but permissions can’t be granted in a streaming session. Grant the permission on your tablet first.</string>
+    <!-- Message shown when a runtime permission request is blocked from being streamed to a remote device. [CHAR LIMIT=NONE] -->
+    <string name="app_streaming_blocked_message_for_permission_request" product="default">This app is requesting additional permissions, but permissions can’t be granted in a streaming session. Grant the permission on your phone first.</string>
     <!-- Message shown when an app being streamed to another device requests authentication such as via the biometrics API, and the user needs to complete the on their device. [CHAR LIMIT=NONE] -->
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv">This app is requesting additional security. Try on your Android TV device instead.</string>
     <!-- Message shown when an app being streamed to another device requests authentication such as via the biometrics API, and the user needs to complete the on their device. [CHAR LIMIT=NONE] -->
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index 79964b3..164f713 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -1560,4 +1560,8 @@
     <style name="NotificationTombstoneAction" parent="NotificationAction">
         <item name="textColor">#555555</item>
     </style>
+
+    <!-- The default style for input method switch dialog -->
+    <style name="InputMethodSwitchDialogStyle" parent="AlertDialog.DeviceDefault">
+    </style>
 </resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index dc4eafd..0951aec 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -358,6 +358,7 @@
   <java-symbol type="bool" name="config_canSwitchToHeadlessSystemUser"/>
   <java-symbol type="bool" name="config_enableMultiUserUI"/>
   <java-symbol type="bool" name="config_enableMultipleAdmins"/>
+  <java-symbol type="bool" name="config_omnipresentCommunalUser"/>
   <java-symbol type="bool" name="config_enableNewAutoSelectNetworkUI"/>
   <java-symbol type="bool" name="config_disableUsbPermissionDialogs"/>
   <java-symbol type="dimen" name="config_highResTaskSnapshotScale" />
@@ -416,6 +417,7 @@
   <java-symbol type="bool" name="config_guestUserAllowEphemeralStateChange" />
   <java-symbol type="bool" name="config_localDisplaysMirrorContent" />
   <java-symbol type="bool" name="config_ignoreUdfpsVote" />
+  <java-symbol type="bool" name="config_enableProximityService" />
   <java-symbol type="array" name="config_localPrivateDisplayPorts" />
   <java-symbol type="integer" name="config_defaultDisplayDefaultColorMode" />
   <java-symbol type="bool" name="config_enableAppWidgetService" />
@@ -520,6 +522,7 @@
   <java-symbol type="dimen" name="config_viewConfigurationHandwritingSlop" />
   <java-symbol type="dimen" name="config_viewConfigurationHoverSlop" />
   <java-symbol type="dimen" name="config_ambiguousGestureMultiplier" />
+  <java-symbol type="dimen" name="config_autoKeyboardBrightnessSmoothingConstant" />
   <java-symbol type="dimen" name="config_viewMinFlingVelocity" />
   <java-symbol type="dimen" name="config_viewMaxFlingVelocity" />
   <java-symbol type="dimen" name="config_viewMinRotaryEncoderFlingVelocity" />
@@ -658,6 +661,7 @@
   <java-symbol type="string" name="cfTemplateRegisteredTime" />
   <java-symbol type="string" name="chooseActivity" />
   <java-symbol type="string" name="checked" />
+  <java-symbol type="string" name="config_companionDeviceManagerPackage" />
   <java-symbol type="array" name="config_companionDevicePackages" />
   <java-symbol type="array" name="config_companionDeviceCerts" />
   <java-symbol type="string" name="config_default_dns_server" />
@@ -1283,6 +1287,8 @@
   <java-symbol type="dimen" name="lock_pattern_fade_away_gradient_width" />
   <java-symbol type="integer" name="lock_pattern_line_fade_out_duration" />
   <java-symbol type="integer" name="lock_pattern_line_fade_out_delay" />
+  <java-symbol type="integer" name="lock_pattern_fade_pattern_delay" />
+  <java-symbol type="integer" name="lock_pattern_fade_pattern_duration" />
   <java-symbol type="drawable" name="clock_dial" />
   <java-symbol type="drawable" name="clock_hand_hour" />
   <java-symbol type="drawable" name="clock_hand_minute" />
@@ -1388,6 +1394,7 @@
   <java-symbol type="drawable" name="ic_corp_user_badge" />
   <java-symbol type="drawable" name="ic_corp_badge_no_background" />
   <java-symbol type="drawable" name="ic_corp_statusbar_icon" />
+  <java-symbol type="drawable" name="stat_sys_managed_profile_status" />
   <java-symbol type="drawable" name="ic_test_badge_experiment" />
   <java-symbol type="drawable" name="ic_test_badge_no_background" />
   <java-symbol type="drawable" name="ic_test_icon_badge_experiment" />
@@ -1888,11 +1895,13 @@
   <java-symbol type="anim" name="dream_activity_open_enter" />
   <java-symbol type="anim" name="dream_activity_close_exit" />
   <java-symbol type="array" name="config_autoBrightnessButtonBacklightValues" />
-  <java-symbol type="array" name="config_autoBrightnessKeyboardBacklightValues" />
   <java-symbol type="array" name="config_autoBrightnessLcdBacklightValues" />
   <java-symbol type="array" name="config_autoBrightnessLcdBacklightValues_doze" />
   <java-symbol type="array" name="config_autoBrightnessLevels" />
   <java-symbol type="array" name="config_autoBrightnessLevelsIdle" />
+  <java-symbol type="array" name="config_autoKeyboardBacklightBrightnessValues" />
+  <java-symbol type="array" name="config_autoKeyboardBacklightDecreaseLuxThreshold" />
+  <java-symbol type="array" name="config_autoKeyboardBacklightIncreaseLuxThreshold" />
   <java-symbol type="array" name="config_ambientThresholdLevels" />
   <java-symbol type="array" name="config_ambientBrighteningThresholds" />
   <java-symbol type="array" name="config_ambientDarkeningThresholds" />
@@ -2027,10 +2036,12 @@
   <java-symbol type="integer" name="config_notificationsBatteryMediumARGB" />
   <java-symbol type="integer" name="config_notificationsBatteryNearlyFullLevel" />
   <java-symbol type="integer" name="config_notificationServiceArchiveSize" />
+  <java-symbol type="dimen" name="config_rotaryEncoderAxisScrollTickInterval" />
   <java-symbol type="integer" name="config_previousVibrationsDumpLimit" />
   <java-symbol type="integer" name="config_defaultVibrationAmplitude" />
   <java-symbol type="dimen" name="config_hapticChannelMaxVibrationAmplitude" />
   <java-symbol type="integer" name="config_vibrationWaveformRampStepDuration" />
+  <java-symbol type="bool" name="config_ignoreVibrationsOnWirelessCharger" />
   <java-symbol type="integer" name="config_vibrationWaveformRampDownDuration" />
   <java-symbol type="integer" name="config_radioScanningTimeout" />
   <java-symbol type="integer" name="config_screenBrightnessSettingMinimum" />
@@ -2116,6 +2127,7 @@
   <java-symbol type="string" name="data_usage_rapid_body" />
   <java-symbol type="string" name="data_usage_rapid_app_body" />
   <java-symbol type="string" name="default_wallpaper_component" />
+  <java-symbol type="array" name="default_wallpaper_component_per_device_color" />
   <java-symbol type="string" name="device_storage_monitor_notification_channel" />
   <java-symbol type="string" name="dlg_ok" />
   <java-symbol type="string" name="dump_heap_notification" />
@@ -2235,6 +2247,7 @@
   <java-symbol type="string" name="config_persistentDataPackageName" />
   <java-symbol type="string" name="config_deviceConfiguratorPackageName" />
   <java-symbol type="array" name="config_autoTimeSourcesPriority" />
+  <java-symbol type="integer" name="config_timeDetectorAutoUpdateDiffMillis" />
   <java-symbol type="bool" name="config_enableGnssTimeUpdateService" />
   <java-symbol type="bool" name="config_enableGeolocationTimeZoneDetection" />
   <java-symbol type="bool" name="config_enablePrimaryLocationTimeZoneProvider" />
@@ -2294,6 +2307,7 @@
 
   <java-symbol type="bool" name="config_alwaysUseCdmaRssi" />
   <java-symbol type="dimen" name="status_bar_icon_size" />
+  <java-symbol type="dimen" name="status_bar_icon_size_sp" />
   <java-symbol type="dimen" name="status_bar_system_icon_size" />
   <java-symbol type="dimen" name="status_bar_system_icon_intrinsic_size" />
   <java-symbol type="drawable" name="list_selector_pressed_holo_dark" />
@@ -2334,6 +2348,7 @@
   <java-symbol type="style" name="Animation.RecentApplications" />
   <java-symbol type="integer" name="dock_enter_exit_duration" />
   <java-symbol type="bool" name="config_battery_percentage_setting_available" />
+  <java-symbol type="bool" name="config_defaultBatteryPercentageSetting" />
   <java-symbol type="string" name="nas_upgrade_notification_title" />
   <java-symbol type="string" name="nas_upgrade_notification_content" />
   <java-symbol type="string" name="nas_upgrade_notification_enable_action" />
@@ -2566,10 +2581,13 @@
   <java-symbol type="string" name="biometric_or_screen_lock_app_setting_name" />
   <java-symbol type="string" name="biometric_dialog_default_title" />
   <java-symbol type="string" name="biometric_dialog_default_subtitle" />
+  <java-symbol type="string" name="biometric_dialog_face_subtitle" />
+  <java-symbol type="string" name="biometric_dialog_fingerprint_subtitle" />
   <java-symbol type="string" name="biometric_or_screen_lock_dialog_default_subtitle" />
   <java-symbol type="string" name="biometric_error_hw_unavailable" />
   <java-symbol type="string" name="biometric_error_user_canceled" />
   <java-symbol type="string" name="biometric_not_recognized" />
+  <java-symbol type="string" name="biometric_face_not_recognized" />
   <java-symbol type="string" name="biometric_error_canceled" />
   <java-symbol type="string" name="biometric_error_device_not_secured" />
   <java-symbol type="string" name="biometric_error_generic" />
@@ -3045,6 +3063,7 @@
   <java-symbol type="id" name="header_text_secondary" />
   <java-symbol type="id" name="expand_button" />
   <java-symbol type="id" name="expand_button_pill" />
+  <java-symbol type="id" name="expand_button_pill_colorized_layer" />
   <java-symbol type="id" name="expand_button_number" />
   <java-symbol type="id" name="expand_button_icon" />
   <java-symbol type="id" name="alternate_expand_target" />
@@ -3094,6 +3113,7 @@
   <java-symbol type="string" name="status_bar_sync_failing" />
   <java-symbol type="string" name="status_bar_sync_active" />
   <java-symbol type="string" name="status_bar_cast" />
+  <java-symbol type="string" name="status_bar_connected_display" />
   <java-symbol type="string" name="status_bar_hotspot" />
   <java-symbol type="string" name="status_bar_location" />
   <java-symbol type="string" name="status_bar_bluetooth" />
@@ -3229,6 +3249,7 @@
   <!-- WallpaperManager config -->
   <java-symbol type="string" name="config_wallpaperCropperPackage" />
   <java-symbol type="string" name="expand_action_accessibility" />
+  <java-symbol type="bool" name="config_wallpaperTopApp" />
 
   <java-symbol type="id" name="textSpacerNoTitle" />
   <java-symbol type="id" name="titleDividerNoCustom" />
@@ -3335,6 +3356,7 @@
   <java-symbol type="string" name="app_streaming_blocked_title_for_playstore_dialog" />
   <java-symbol type="string" name="app_streaming_blocked_title_for_settings_dialog" />
   <java-symbol type="string" name="app_streaming_blocked_message" />
+  <java-symbol type="string" name="app_streaming_blocked_message_for_permission_request" />
   <java-symbol type="string" name="app_streaming_blocked_message_for_fingerprint_dialog" />
   <java-symbol type="string" name="app_streaming_blocked_message_for_settings_dialog" />
 
@@ -3762,6 +3784,7 @@
   <java-symbol type="string" name="config_defaultTextClassifierPackage" />
   <java-symbol type="string" name="config_defaultWellbeingPackage" />
   <java-symbol type="string" name="config_defaultContentCaptureService" />
+  <java-symbol type="string" name="config_defaultContentProtectionService" />
   <java-symbol type="string" name="config_defaultAugmentedAutofillService" />
   <java-symbol type="string" name="config_defaultTranslationService" />
   <java-symbol type="string" name="config_defaultAppPredictionService" />
@@ -3921,7 +3944,6 @@
 
   <!-- From media projection -->
   <java-symbol type="string" name="config_mediaProjectionPermissionDialogComponent" />
-  <java-symbol type="string" name="config_batterySaverDeviceSpecificConfig" />
 
   <!-- Compile SDK check -->
   <java-symbol type="layout" name="unsupported_compile_sdk_dialog_content" />
@@ -3985,11 +4007,30 @@
   <java-symbol type="string" name="notification_app_name_system" />
   <java-symbol type="string" name="notification_app_name_settings" />
 
+  <!-- Battery saver config -->
   <java-symbol type="integer" name="config_lowBatteryAutoTriggerDefaultLevel" />
+  <java-symbol type="string" name="config_batterySaverDeviceSpecificConfig" />
   <java-symbol type="bool" name="config_batterySaverStickyBehaviourDisabled" />
   <java-symbol type="integer" name="config_dynamicPowerSavingsDefaultDisableThreshold" />
   <java-symbol type="string" name="config_batterySaverScheduleProvider" />
   <java-symbol type="string" name="config_powerSaveModeChangedListenerPackage" />
+  <java-symbol type="dimen" name="config_batterySaver_full_adjustBrightnessFactor" />
+  <java-symbol type="bool" name="config_batterySaver_full_deferFullBackup" />
+  <java-symbol type="bool" name="config_batterySaver_full_deferKeyValueBackup" />
+  <java-symbol type="bool" name="config_batterySaver_full_disableAnimation" />
+  <java-symbol type="bool" name="config_batterySaver_full_disableAod" />
+  <java-symbol type="bool" name="config_batterySaver_full_disableLaunchBoost" />
+  <java-symbol type="bool" name="config_batterySaver_full_disableOptionalSensors" />
+  <java-symbol type="bool" name="config_batterySaver_full_disableVibration" />
+  <java-symbol type="bool" name="config_batterySaver_full_enableAdjustBrightness" />
+  <java-symbol type="bool" name="config_batterySaver_full_enableDataSaver" />
+  <java-symbol type="bool" name="config_batterySaver_full_enableFirewall" />
+  <java-symbol type="bool" name="config_batterySaver_full_enableNightMode" />
+  <java-symbol type="bool" name="config_batterySaver_full_enableQuickDoze" />
+  <java-symbol type="bool" name="config_batterySaver_full_forceAllAppsStandby" />
+  <java-symbol type="bool" name="config_batterySaver_full_forceBackgroundCheck" />
+  <java-symbol type="integer" name="config_batterySaver_full_locationMode" />
+  <java-symbol type="integer" name="config_batterySaver_full_soundTriggerMode" />
 
   <!-- For car devices -->
   <java-symbol type="string" name="car_loading_profile" />
@@ -4424,9 +4465,6 @@
   <!-- Set to true to make assistant show in front of the dream/screensaver. -->
   <java-symbol type="bool" name="config_assistantOnTopOfDream"/>
 
-  <!-- Set to true to enable letterboxing on translucent activities. -->
-  <java-symbol type="bool" name="config_letterboxIsEnabledForTranslucentActivities" />
-
   <java-symbol type="string" name="config_overrideComponentUiPackage" />
 
   <java-symbol type="string" name="notification_channel_network_status" />
@@ -4477,9 +4515,8 @@
   <java-symbol type="integer" name="device_idle_sms_temp_app_allowlist_duration_ms" />
   <java-symbol type="integer" name="device_idle_notification_allowlist_duration_ms" />
   <java-symbol type="bool" name="device_idle_wait_for_unlock" />
-  <java-symbol type="integer" name="device_idle_pre_idle_factor_long" />
-  <java-symbol type="integer" name="device_idle_pre_idle_factor_short" />
   <java-symbol type="bool" name="device_idle_use_window_alarms" />
+  <java-symbol type="bool" name="device_idle_use_body_sensor" />
 
   <!-- Binder heavy hitter watcher configs -->
   <java-symbol type="bool" name="config_defaultBinderHeavyHitterWatcherEnabled" />
@@ -4522,6 +4559,12 @@
   <java-symbol type="dimen" name="config_letterboxDefaultMinAspectRatioForUnresizableApps" />
   <java-symbol type="bool" name="config_letterboxIsSplitScreenAspectRatioForUnresizableAppsEnabled" />
   <java-symbol type="bool" name="config_letterboxIsDisplayAspectRatioForFixedOrientationLetterboxEnabled" />
+  <!-- Set to true to enable letterboxing on translucent activities. -->
+  <java-symbol type="bool" name="config_letterboxIsEnabledForTranslucentActivities" />
+
+  <!-- Whether per-app user aspect ratio override settings is enabled -->
+  <java-symbol type="bool" name="config_appCompatUserAppAspectRatioSettingsIsEnabled" />
+
   <java-symbol type="bool" name="config_isCompatFakeFocusEnabled" />
   <java-symbol type="bool" name="config_isWindowManagerCameraCompatTreatmentEnabled" />
   <java-symbol type="bool" name="config_isWindowManagerCameraCompatSplitScreenAspectRatioEnabled" />
@@ -4912,6 +4955,8 @@
 
   <java-symbol type="bool" name="config_safetyProtectionEnabled" />
 
+  <java-symbol type="bool" name="config_repairModeSupported" />
+
   <java-symbol type="string" name="config_devicePolicyManagementUpdater" />
 
   <java-symbol type="string" name="config_deviceSpecificDeviceStatePolicyProvider" />
@@ -4932,7 +4977,6 @@
   <!-- For VirtualDeviceManager -->
   <java-symbol type="string" name="vdm_camera_access_denied" />
   <java-symbol type="string" name="vdm_secure_window" />
-  <java-symbol type="string" name="vdm_pip_blocked" />
 
   <java-symbol type="color" name="camera_privacy_light_day"/>
   <java-symbol type="color" name="camera_privacy_light_night"/>
@@ -5130,5 +5174,6 @@
   <java-symbol type="style" name="ThemeOverlay.DeviceDefault.Dark.ActionBar.Accent" />
 
   <java-symbol type="drawable" name="focus_event_pressed_key_background" />
+  <java-symbol type="string" name="config_defaultShutdownVibrationFile" />
   <java-symbol type="string" name="lockscreen_too_many_failed_attempts_countdown" />
 </resources>
diff --git a/core/res/res/xml/sms_short_codes.xml b/core/res/res/xml/sms_short_codes.xml
index df3ae0e..3b099e8 100644
--- a/core/res/res/xml/sms_short_codes.xml
+++ b/core/res/res/xml/sms_short_codes.xml
@@ -40,7 +40,7 @@
     <shortcode country="al" pattern="\\d{5}" premium="15191|55[56]00" />
 
     <!-- Argentina: 5 digits, known short codes listed -->
-    <shortcode country="ar" pattern="\\d{5}" free="11711|28291|44077" />
+    <shortcode country="ar" pattern="\\d{5}" free="11711|28291|44077|78887" />
 
     <!-- Armenia: 3-4 digits, emergency numbers 10[123] -->
     <shortcode country="am" pattern="\\d{3,4}" premium="11[2456]1|3024" free="10[123]" />
@@ -162,7 +162,7 @@
     <shortcode country="jp" pattern="\\d{1,5}" free="8083" />
 
     <!-- Kenya: 5 digits, known premium codes listed -->
-    <shortcode country="ke" pattern="\\d{5}" free="21725|21562|40520" />
+    <shortcode country="ke" pattern="\\d{5}" free="21725|21562|40520|23342|40023" />
 
     <!-- Kyrgyzstan: 4 digits, known premium codes listed -->
     <shortcode country="kg" pattern="\\d{4}" premium="415[2367]|444[69]" />
@@ -208,7 +208,7 @@
     <shortcode country="nz" pattern="\\d{3,4}" premium="3903|8995|4679" free="1737|176|2141|3067|3068|3110|4006|4053|4061|4062|4202|4300|4334|4412|4575|5626|8006|8681" />
 
     <!-- Peru: 4-5 digits (not confirmed), known premium codes listed -->
-    <shortcode country="pe" pattern="\\d{4,5}" free="9963|40777" />
+    <shortcode country="pe" pattern="\\d{4,5}" free="9963|40778" />
 
     <!-- Philippines -->
     <shortcode country="ph" pattern="\\d{1,5}" free="2147|5495|5496" />
@@ -235,6 +235,9 @@
     <!-- Russia: 4 digits, known premium codes listed: http://smscoin.net/info/pricing-russia/ -->
     <shortcode country="ru" pattern="\\d{4}" premium="1(?:1[56]1|899)|2(?:09[57]|322|47[46]|880|990)|3[589]33|4161|44(?:4[3-9]|81)|77(?:33|81)|8424" free="6954|8501" standard="2037|2044"/>
 
+    <!-- Rwanda: 4 digits -->
+    <shortcode country="rw" pattern="\\d{4}" free="5060" />
+
     <!-- Saudi Arabia -->
     <shortcode country="sa" pattern="\\d{1,5}" free="8145" />
 
diff --git a/core/tests/BroadcastRadioTests/Android.bp b/core/tests/BroadcastRadioTests/Android.bp
index 436f058..85d54e0 100644
--- a/core/tests/BroadcastRadioTests/Android.bp
+++ b/core/tests/BroadcastRadioTests/Android.bp
@@ -45,6 +45,7 @@
     libs: ["android.test.base"],
     test_suites: [
         "general-tests",
+        "automotive-general-tests",
     ],
     // mockito-target-inline dependency
     jni_libs: [
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index 286697c..c7aaeb0 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -53,7 +53,7 @@
         "junit-params",
         "kotlin-test",
         "mockito-target-minus-junit4",
-        "ub-uiautomator",
+        "androidx.test.uiautomator_uiautomator",
         "platform-test-annotations",
         "platform-compat-test-rules",
         "truth-prebuilt",
diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml
index c1deba3..129de64 100644
--- a/core/tests/coretests/AndroidManifest.xml
+++ b/core/tests/coretests/AndroidManifest.xml
@@ -1716,6 +1716,17 @@
             <meta-data android:name="android.view.im"
                        android:resource="@xml/ime_meta_handwriting"/>
         </service>
+
+        <activity android:name="android.widget.PointerIconTestActivity"
+                  android:label="PointerIconTestActivity"
+                  android:screenOrientation="portrait"
+                  android:exported="true"
+                  android:theme="@android:style/Theme.Material.Light">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+            </intent-filter>
+        </activity>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/core/tests/coretests/OWNERS b/core/tests/coretests/OWNERS
index e8c9fe7..b7e008b 100644
--- a/core/tests/coretests/OWNERS
+++ b/core/tests/coretests/OWNERS
@@ -2,3 +2,4 @@
 
 per-file BinderTest.java = file:platform/frameworks/native:/libs/binder/OWNERS
 per-file ParcelTest.java = file:platform/frameworks/native:/libs/binder/OWNERS
+per-file SurfaceControlRegistryTests.java = file:/services/core/java/com/android/server/wm/OWNERS
diff --git a/core/tests/coretests/res/layout/pointer_icon_test.xml b/core/tests/coretests/res/layout/pointer_icon_test.xml
new file mode 100644
index 0000000..a2a6447
--- /dev/null
+++ b/core/tests/coretests/res/layout/pointer_icon_test.xml
@@ -0,0 +1,59 @@
+<?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.
+  -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:orientation="vertical"
+              android:layout_width="match_parent"
+              android:layout_height="match_parent">
+    <TextView
+        android:id="@+id/textview"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="Test"/>
+
+    <EditText
+        android:id="@+id/edittext"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="Test"/>
+
+    <Button
+        android:id="@+id/button"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="Test"/>
+
+    <ImageButton
+        android:id="@+id/imagebutton"
+        android:layout_width="50dp"
+        android:layout_height="50dp"/>
+
+    <Spinner
+        android:id="@+id/spinner"
+        android:layout_width="50dp"
+        android:layout_height="50dp"/>
+
+    <RadialTimePickerView
+        android:id="@+id/timepicker"
+        android:layout_width="200dp"
+        android:layout_height="200dp"/>
+
+    <CalendarView
+        android:id="@+id/calendar"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"/>
+</LinearLayout>
\ No newline at end of file
diff --git a/core/tests/coretests/res/values/overlayable_icons_test.xml b/core/tests/coretests/res/values/overlayable_icons_test.xml
index 7ea1848..6a50e9a 100644
--- a/core/tests/coretests/res/values/overlayable_icons_test.xml
+++ b/core/tests/coretests/res/values/overlayable_icons_test.xml
@@ -21,7 +21,6 @@
     <item>@*android:drawable/ic_audio_alarm</item>
     <item>@*android:drawable/ic_audio_alarm_mute</item>
     <item>@*android:drawable/ic_battery_80_24dp</item>
-    <item>@*android:drawable/ic_bluetooth_share_icon</item>
     <item>@*android:drawable/ic_bt_headphones_a2dp</item>
     <item>@*android:drawable/ic_bt_headset_hfp</item>
     <item>@*android:drawable/ic_bt_hearing_aid</item>
diff --git a/core/tests/coretests/res/xml/power_profile_test.xml b/core/tests/coretests/res/xml/power_profile_test.xml
index 2257114..322ae05 100644
--- a/core/tests/coretests/res/xml/power_profile_test.xml
+++ b/core/tests/coretests/res/xml/power_profile_test.xml
@@ -19,12 +19,6 @@
     <!-- This is the battery capacity in mAh -->
     <item name="battery.capacity">3000</item>
 
-    <!-- Number of cores each CPU cluster contains -->
-    <array name="cpu.clusters.cores">
-        <value>4</value> <!-- Cluster 0 has 4 cores (cpu0, cpu1, cpu2, cpu3) -->
-        <value>4</value> <!-- Cluster 1 has 4 cores (cpu4, cpu5, cpu5, cpu7) -->
-    </array>
-
     <!-- Power consumption when CPU is suspended -->
     <item name="cpu.suspend">5</item>
     <!-- Additional power consumption when CPU is in a kernel idle loop -->
@@ -32,37 +26,21 @@
     <!-- Additional power consumption by CPU excluding cluster and core when  running -->
     <item name="cpu.active">2.55</item>
 
-    <!-- Additional power consumption by CPU cluster0 itself when running excluding cores in it -->
-    <item name="cpu.cluster_power.cluster0">2.11</item>
-    <!-- Additional power consumption by CPU cluster1 itself when running excluding cores in it -->
-    <item name="cpu.cluster_power.cluster1">2.22</item>
+    <!-- Additional power consumption of CPU policy0 itself when running on related cores -->
+    <item name="cpu.scaling_policy_power.policy0">2.11</item>
+    <!-- Additional power consumption of CPU policy4 itself when running on related cores -->
+    <item name="cpu.scaling_policy_power.policy3">2.22</item>
 
-    <!-- Different CPU speeds as reported in
-         /sys/devices/system/cpu/cpu0/cpufreq/stats/scaling_available_frequencies -->
-    <array name="cpu.core_speeds.cluster0">
-        <value>300000</value> <!-- 300 MHz CPU speed -->
-        <value>1000000</value> <!-- 1000 MHz CPU speed -->
-        <value>2000000</value> <!-- 2000 MHz CPU speed -->
-    </array>
-    <!-- Different CPU speeds as reported in
-         /sys/devices/system/cpu/cpu4/cpufreq/stats/scaling_available_frequencies -->
-    <array name="cpu.core_speeds.cluster1">
-        <value>300000</value> <!-- 300 MHz CPU speed -->
-        <value>1000000</value> <!-- 1000 MHz CPU speed -->
-        <value>2500000</value> <!-- 2500 MHz CPU speed -->
-        <value>3000000</value> <!-- 3000 MHz CPU speed -->
-    </array>
-
-    <!-- Additional power used by a CPU from cluster 0 when running at different
-         speeds. Currently this measurement also includes cluster cost. -->
-    <array name="cpu.core_power.cluster0">
+    <!-- Additional power used by a CPU related to policy3 when running at different
+     speeds. -->
+    <array name="cpu.scaling_step_power.policy0">
         <value>10</value> <!-- 300 MHz CPU speed -->
         <value>20</value> <!-- 1000 MHz CPU speed -->
         <value>30</value> <!-- 1900 MHz CPU speed -->
     </array>
-    <!-- Additional power used by a CPU from cluster 1 when running at different
-         speeds. Currently this measurement also includes cluster cost. -->
-    <array name="cpu.core_power.cluster1">
+    <!-- Additional power used by a CPU related to policy3 when running at different
+         speeds. -->
+    <array name="cpu.scaling_step_power.policy3">
         <value>25</value> <!-- 300 MHz CPU speed -->
         <value>35</value> <!-- 1000 MHz CPU speed -->
         <value>50</value> <!-- 2500 MHz CPU speed -->
diff --git a/core/tests/coretests/res/xml/power_profile_test_cpu_legacy.xml b/core/tests/coretests/res/xml/power_profile_test_cpu_legacy.xml
new file mode 100644
index 0000000..bd7d712
--- /dev/null
+++ b/core/tests/coretests/res/xml/power_profile_test_cpu_legacy.xml
@@ -0,0 +1,116 @@
+<?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.
+  -->
+<device name="Android">
+    <!-- All values are in mAh except as noted.
+         This file is for PowerProfileTest.java. Changes must be synced between these two. Since
+         power_profile.xml may be overridden by actual device's power_profile.xml at compile time,
+         this test config ensures we have something constant to test against. Values below are
+         sample values, not meant to reflect any real device.
+    -->
+
+    <!-- This is the battery capacity in mAh -->
+    <item name="battery.capacity">3000</item>
+
+    <!-- Number of cores each CPU cluster contains -->
+    <array name="cpu.clusters.cores">
+        <value>4</value> <!-- Cluster 0 has 4 cores (cpu0, cpu1, cpu2, cpu3) -->
+        <value>4</value> <!-- Cluster 1 has 4 cores (cpu4, cpu5, cpu5, cpu7) -->
+    </array>
+
+    <!-- Power consumption when CPU is suspended -->
+    <item name="cpu.suspend">5</item>
+    <!-- Additional power consumption when CPU is in a kernel idle loop -->
+    <item name="cpu.idle">1.11</item>
+    <!-- Additional power consumption by CPU excluding cluster and core when  running -->
+    <item name="cpu.active">2.55</item>
+
+    <!-- Additional power consumption by CPU cluster0 itself when running excluding cores in it -->
+    <item name="cpu.cluster_power.cluster0">2.11</item>
+    <!-- Additional power consumption by CPU cluster1 itself when running excluding cores in it -->
+    <item name="cpu.cluster_power.cluster1">2.22</item>
+
+    <!-- Different CPU speeds as reported in
+         /sys/devices/system/cpu/cpu0/cpufreq/stats/scaling_available_frequencies -->
+    <array name="cpu.core_speeds.cluster0">
+        <value>300000</value> <!-- 300 MHz CPU speed -->
+        <value>1000000</value> <!-- 1000 MHz CPU speed -->
+        <value>2000000</value> <!-- 2000 MHz CPU speed -->
+    </array>
+    <!-- Different CPU speeds as reported in
+         /sys/devices/system/cpu/cpu4/cpufreq/stats/scaling_available_frequencies -->
+    <array name="cpu.core_speeds.cluster1">
+        <value>300000</value> <!-- 300 MHz CPU speed -->
+        <value>1000000</value> <!-- 1000 MHz CPU speed -->
+        <value>2500000</value> <!-- 2500 MHz CPU speed -->
+        <value>3000000</value> <!-- 3000 MHz CPU speed -->
+    </array>
+
+    <!-- Additional power used by a CPU from cluster 0 when running at different
+         speeds. Currently this measurement also includes cluster cost. -->
+    <array name="cpu.core_power.cluster0">
+        <value>10</value> <!-- 300 MHz CPU speed -->
+        <value>20</value> <!-- 1000 MHz CPU speed -->
+        <value>30</value> <!-- 1900 MHz CPU speed -->
+    </array>
+    <!-- Additional power used by a CPU from cluster 1 when running at different
+         speeds. Currently this measurement also includes cluster cost. -->
+    <array name="cpu.core_power.cluster1">
+        <value>25</value> <!-- 300 MHz CPU speed -->
+        <value>35</value> <!-- 1000 MHz CPU speed -->
+        <value>50</value> <!-- 2500 MHz CPU speed -->
+        <value>60</value> <!-- 3000 MHz CPU speed -->
+    </array>
+
+    <!-- Power used by display unit in ambient display mode, including back lighting-->
+    <item name="ambient.on">0.5</item>
+    <!-- Additional power used when screen is turned on at minimum brightness -->
+    <item name="screen.on">100</item>
+    <!-- Additional power used when screen is at maximum brightness, compared to
+         screen at minimum brightness -->
+    <item name="screen.full">800</item>
+
+    <!-- Average power used by the camera flash module when on -->
+    <item name="camera.flashlight">500</item>
+    <!-- Average power use by the camera subsystem for a typical camera
+         application. Intended as a rough estimate for an application running a
+         preview and capturing approximately 10 full-resolution pictures per
+         minute. -->
+    <item name="camera.avg">600</item>
+
+    <!-- Additional power used by the audio hardware, probably due to DSP -->
+    <item name="audio">100.0</item>
+
+    <!-- Additional power used by the video hardware, probably due to DSP -->
+    <item name="video">150.0</item> <!-- ~50mA -->
+
+    <!-- Additional power used when GPS is acquiring a signal -->
+    <item name="gps.on">10</item>
+
+    <!-- Additional power used when cellular radio is transmitting/receiving -->
+    <item name="radio.active">60</item>
+    <!-- Additional power used when cellular radio is paging the tower -->
+    <item name="radio.scanning">3</item>
+    <!-- Additional power used when the cellular radio is on. Multi-value entry,
+         one per signal strength (no signal, weak, moderate, strong) -->
+    <array name="radio.on"> <!-- Strength 0 to BINS-1 -->
+        <value>6</value>       <!-- none -->
+        <value>5</value>       <!-- poor -->
+        <value>4</value>       <!-- moderate -->
+        <value>3</value>       <!-- good -->
+        <value>3</value>       <!-- great -->
+    </array>
+</device>
diff --git a/core/tests/coretests/res/xml/power_profile_test_power_brackets.xml b/core/tests/coretests/res/xml/power_profile_test_power_brackets.xml
index c129388..a46c608 100644
--- a/core/tests/coretests/res/xml/power_profile_test_power_brackets.xml
+++ b/core/tests/coretests/res/xml/power_profile_test_power_brackets.xml
@@ -17,34 +17,20 @@
 
 <device name="Android">
 
-    <array name="cpu.clusters.cores">
-        <value>1</value>
-        <value>2</value>
-    </array>
-
-    <array name="cpu.core_speeds.cluster0">
-        <value>300000</value>
-    </array>
-
-    <array name="cpu.core_speeds.cluster1">
-        <value>300000</value>
-        <value>1000000</value>
-    </array>
-
-    <array name="cpu.core_power.cluster0">
+    <array name="cpu.scaling_step_power.policy0">
         <value>10</value>
     </array>
 
-    <array name="cpu.core_power.cluster1">
+    <array name="cpu.scaling_step_power.policy4">
         <value>25</value>
         <value>35</value>
     </array>
 
-    <array name="cpu.power_brackets.cluster0">
+    <array name="cpu.power_brackets.policy0">
         <value>1</value>
     </array>
 
-    <array name="cpu.power_brackets.cluster1">
+    <array name="cpu.power_brackets.policy4">
         <value>1</value>
         <value>0</value>
     </array>
diff --git a/core/tests/coretests/src/android/accessibilityservice/AccessibilityServiceTest.java b/core/tests/coretests/src/android/accessibilityservice/AccessibilityServiceTest.java
deleted file mode 100644
index 53ba140..0000000
--- a/core/tests/coretests/src/android/accessibilityservice/AccessibilityServiceTest.java
+++ /dev/null
@@ -1,226 +0,0 @@
-/*
- * 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.
- */
-
-package android.accessibilityservice;
-
-import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
-import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;
-import static android.view.Display.DEFAULT_DISPLAY;
-import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY;
-import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
-
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.verify;
-
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.PixelFormat;
-import android.hardware.display.DisplayManager;
-import android.hardware.display.VirtualDisplay;
-import android.media.ImageReader;
-import android.os.Binder;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.RemoteException;
-import android.util.SparseArray;
-import android.view.Display;
-import android.view.View;
-import android.view.WindowManager;
-import android.view.WindowManagerGlobal;
-import android.view.accessibility.AccessibilityEvent;
-import android.window.WindowTokenClient;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.core.app.ApplicationProvider;
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-/**
- * Unit tests for AccessibilityService.
- */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class AccessibilityServiceTest {
-    private static final String TAG = "AccessibilityServiceTest";
-    private static final int CONNECTION_ID = 1;
-    private final WindowManager.LayoutParams mParams = new WindowManager.LayoutParams(
-            TYPE_ACCESSIBILITY_OVERLAY);
-
-    private static class AccessibilityServiceTestClass extends AccessibilityService {
-        private IAccessibilityServiceClient mCallback;
-        private Looper mLooper;
-
-        AccessibilityServiceTestClass() {
-            super();
-            Context context = ApplicationProvider.getApplicationContext();
-            final Display display = context.getSystemService(DisplayManager.class)
-                    .getDisplay(DEFAULT_DISPLAY);
-
-            attachBaseContext(context.createTokenContext(new WindowTokenClient(), display));
-            mLooper = InstrumentationRegistry.getContext().getMainLooper();
-        }
-
-        public void setupCallback(IAccessibilityServiceClient callback) {
-            mCallback = callback;
-        }
-
-        public Looper getMainLooper() {
-            return mLooper;
-        }
-
-        public void onAccessibilityEvent(AccessibilityEvent event) { }
-        public void onInterrupt() { }
-
-        @Override
-        public void onSystemActionsChanged() {
-            try {
-                if (mCallback != null) mCallback.onSystemActionsChanged();
-            } catch (RemoteException e) {
-            }
-        }
-    }
-
-    private @Mock IAccessibilityServiceClient  mMockClientForCallback;
-    private @Mock IAccessibilityServiceConnection mMockConnection;
-    private @Mock IBinder mMockIBinder;
-    private IAccessibilityServiceClient mServiceInterface;
-    private AccessibilityServiceTestClass mService;
-    private final SparseArray<IBinder> mWindowTokens = new SparseArray<>();
-
-    @Before
-    public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
-        mService = new AccessibilityServiceTestClass();
-        mService.onCreate();
-        mService.setupCallback(mMockClientForCallback);
-        mServiceInterface = (IAccessibilityServiceClient) mService.onBind(new Intent());
-        mServiceInterface.init(mMockConnection, CONNECTION_ID, mMockIBinder);
-        doAnswer(invocation -> {
-            Object[] args = invocation.getArguments();
-            final int displayId = (int) args[0];
-            final IBinder token = new Binder();
-            WindowManagerGlobal.getWindowManagerService().addWindowToken(token,
-                    TYPE_ACCESSIBILITY_OVERLAY, displayId, null /* options */);
-            mWindowTokens.put(displayId, token);
-            return token;
-        }).when(mMockConnection).getOverlayWindowToken(anyInt());
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        for (int i = mWindowTokens.size() - 1; i >= 0; --i) {
-            WindowManagerGlobal.getWindowManagerService().removeWindowToken(
-                    mWindowTokens.valueAt(i), mWindowTokens.keyAt(i));
-        }
-    }
-
-    @Test
-    public void testOnSystemActionsChanged() throws RemoteException {
-        mServiceInterface.onSystemActionsChanged();
-
-        verify(mMockClientForCallback).onSystemActionsChanged();
-    }
-
-    @Test
-    public void testGetSystemActions() throws RemoteException {
-        mService.getSystemActions();
-
-        verify(mMockConnection).getSystemActions();
-    }
-
-    @Test
-    public void testAddViewWithA11yServiceDerivedDisplayContext() throws Exception {
-        try (VirtualDisplaySession session = new VirtualDisplaySession()) {
-            final Context context = mService.createDisplayContext(session.getDisplay());
-            InstrumentationRegistry.getInstrumentation().runOnMainSync(
-                    () -> context.getSystemService(WindowManager.class)
-                            .addView(new View(context), mParams)
-            );
-        }
-    }
-
-    @Test
-    public void testAddViewWithA11yServiceDerivedWindowContext() throws Exception {
-        try (VirtualDisplaySession session = new VirtualDisplaySession()) {
-            final Context context = mService.createDisplayContext(session.getDisplay())
-                    .createWindowContext(TYPE_ACCESSIBILITY_OVERLAY, null /* options */);
-            InstrumentationRegistry.getInstrumentation().runOnMainSync(
-                    () -> context.getSystemService(WindowManager.class)
-                            .addView(new View(context), mParams)
-            );
-        }
-    }
-
-    @Test
-    public void testAddViewWithA11yServiceDerivedWindowContextWithDisplay() throws Exception {
-        try (VirtualDisplaySession session = new VirtualDisplaySession()) {
-            final Context context = mService.createWindowContext(session.getDisplay(),
-                    TYPE_ACCESSIBILITY_OVERLAY, null /* options */);
-            InstrumentationRegistry.getInstrumentation().runOnMainSync(
-                    () -> context.getSystemService(WindowManager.class)
-                            .addView(new View(context), mParams)
-            );
-        }
-    }
-
-    @Test(expected = WindowManager.BadTokenException.class)
-    public void testAddViewWithA11yServiceDerivedWindowContextWithDifferentType()
-            throws Exception {
-        try (VirtualDisplaySession session = new VirtualDisplaySession()) {
-            final Context context = mService.createWindowContext(session.getDisplay(),
-                    TYPE_APPLICATION_OVERLAY, null /* options */);
-            InstrumentationRegistry.getInstrumentation().runOnMainSync(
-                    () -> context.getSystemService(WindowManager.class)
-                            .addView(new View(context), mParams)
-            );
-        }
-    }
-
-
-    private static class VirtualDisplaySession implements AutoCloseable {
-        private final VirtualDisplay mVirtualDisplay;
-
-        VirtualDisplaySession() {
-            final DisplayManager displayManager = ApplicationProvider.getApplicationContext()
-                    .getSystemService(DisplayManager.class);
-            final int width = 800;
-            final int height = 480;
-            final int density = 160;
-            ImageReader reader = ImageReader.newInstance(width, height, PixelFormat.RGBA_8888,
-                    2 /* maxImages */);
-            mVirtualDisplay = displayManager.createVirtualDisplay(
-                    TAG, width, height, density, reader.getSurface(),
-                    VIRTUAL_DISPLAY_FLAG_PUBLIC | VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY);
-        }
-
-        private Display getDisplay() {
-            return mVirtualDisplay.getDisplay();
-        }
-
-        @Override
-        public void close() throws Exception {
-            mVirtualDisplay.release();
-        }
-    }
-}
diff --git a/core/tests/coretests/src/android/accessibilityservice/TEST_MAPPING b/core/tests/coretests/src/android/accessibilityservice/TEST_MAPPING
new file mode 100644
index 0000000..1c67399
--- /dev/null
+++ b/core/tests/coretests/src/android/accessibilityservice/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "imports": [
+    {
+      "path": "frameworks/base/services/accessibility/TEST_MAPPING"
+    }
+  ]
+}
diff --git a/core/tests/coretests/src/android/animation/AnimatorSetActivityTest.java b/core/tests/coretests/src/android/animation/AnimatorSetActivityTest.java
index 0525443..0c07470 100644
--- a/core/tests/coretests/src/android/animation/AnimatorSetActivityTest.java
+++ b/core/tests/coretests/src/android/animation/AnimatorSetActivityTest.java
@@ -24,6 +24,7 @@
 import android.util.Property;
 import android.view.View;
 
+import androidx.annotation.NonNull;
 import androidx.test.annotation.UiThreadTest;
 import androidx.test.filters.SmallTest;
 import androidx.test.rule.ActivityTestRule;
@@ -36,6 +37,8 @@
 import org.junit.Test;
 
 import java.util.ArrayList;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 
 @SmallTest
 public class AnimatorSetActivityTest {
@@ -613,6 +616,68 @@
         });
     }
 
+    @Test
+    public void initAfterStartNotification() throws Throwable {
+        Property<int[], Integer> property = new Property<>(Integer.class, "firstValue") {
+            @Override
+            public Integer get(int[] target) {
+                throw new IllegalStateException("Shouldn't be called");
+            }
+
+            @Override
+            public void set(int[] target, Integer value) {
+                target[0] = value;
+            }
+        };
+        int[] target = new int[1];
+        ObjectAnimator animator1 = ObjectAnimator.ofInt(target, property, 0, 100);
+        ObjectAnimator animator2 = ObjectAnimator.ofInt(target, property, 0, 100);
+        ObjectAnimator animator3 = ObjectAnimator.ofInt(target, property, 0, 100);
+        animator1.setDuration(10);
+        animator2.setDuration(10);
+        animator3.setDuration(10);
+        AnimatorSet set = new AnimatorSet();
+        set.playSequentially(animator1, animator2, animator3);
+        final int[] values = new int[4];
+        animator2.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationStart(@NonNull Animator animation, boolean isReverse) {
+                values[0] = target[0];
+                animator2.setIntValues(target[0], target[0] + 100);
+            }
+
+            @Override
+            public void onAnimationEnd(@NonNull Animator animation, boolean isReverse) {
+                values[1] = target[0];
+            }
+        });
+        animator3.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationStart(@NonNull Animator animation, boolean isReverse) {
+                values[2] = target[0];
+                animator3.setIntValues(target[0], target[0] + 100);
+            }
+
+            @Override
+            public void onAnimationEnd(@NonNull Animator animation, boolean isReverse) {
+                values[3] = target[0];
+            }
+        });
+        final CountDownLatch latch = new CountDownLatch(1);
+        set.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(@NonNull Animator animation, boolean isReverse) {
+                latch.countDown();
+            }
+        });
+        mActivityRule.runOnUiThread(() -> set.start());
+        assertTrue(latch.await(1, TimeUnit.SECONDS));
+        assertEquals(100, values[0]);
+        assertEquals(200, values[1]);
+        assertEquals(200, values[2]);
+        assertEquals(300, values[3]);
+    }
+
     /**
      * Check that the animator list contains exactly the given animators and nothing else.
      */
diff --git a/core/tests/coretests/src/android/app/DownloadManagerBaseTest.java b/core/tests/coretests/src/android/app/DownloadManagerBaseTest.java
index cb66fc8..c62c471 100644
--- a/core/tests/coretests/src/android/app/DownloadManagerBaseTest.java
+++ b/core/tests/coretests/src/android/app/DownloadManagerBaseTest.java
@@ -33,10 +33,11 @@
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.provider.Settings;
-import android.support.test.uiautomator.UiDevice;
 import android.test.InstrumentationTestCase;
 import android.util.Log;
 
+import androidx.test.uiautomator.UiDevice;
+
 import com.google.mockwebserver.MockResponse;
 import com.google.mockwebserver.MockWebServer;
 
diff --git a/core/tests/coretests/src/android/app/KeyguardManagerTest.java b/core/tests/coretests/src/android/app/KeyguardManagerTest.java
index 958906c..ed8b288 100644
--- a/core/tests/coretests/src/android/app/KeyguardManagerTest.java
+++ b/core/tests/coretests/src/android/app/KeyguardManagerTest.java
@@ -174,6 +174,22 @@
     }
 
     @Test
+    public void setLock_validatesCredential() {
+        // setLock() should validate the credential before setting it.  Test one example, which is
+        // that PINs must contain only ASCII digits 0-9, i.e. bytes 48-57.  Using bytes 0-9 is
+        // incorrect and should *not* be accepted.
+        byte[] invalidPin = new byte[] { 1, 2, 3, 4 };
+        byte[] validPin = "1234".getBytes();
+
+        assertFalse(mKeyguardManager.setLock(KeyguardManager.PIN, invalidPin, -1, null));
+        assertFalse(mKeyguardManager.isDeviceSecure());
+
+        assertTrue(mKeyguardManager.setLock(KeyguardManager.PIN, validPin, -1, null));
+        assertTrue(mKeyguardManager.isDeviceSecure());
+        assertTrue(mKeyguardManager.setLock(-1, null, KeyguardManager.PIN, validPin));
+    }
+
+    @Test
     public void checkLock_correctCredentials() {
         // Set to `true` to behave as if SET_INITIAL_LOCK permission had been granted.
         doReturn(true).when(mKeyguardManager).checkInitialLockMethodUsage();
diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java
index eba7f58..a936cea 100644
--- a/core/tests/coretests/src/android/app/NotificationTest.java
+++ b/core/tests/coretests/src/android/app/NotificationTest.java
@@ -271,6 +271,54 @@
     }
 
     @Test
+    public void allPendingIntents_resilientToAnotherNotificationInExtras() {
+        PendingIntent contentIntent = createPendingIntent("content");
+        PendingIntent actionIntent = createPendingIntent("action");
+        Notification another = new Notification.Builder(mContext, "channel").build();
+        Bundle bundleContainingAnotherNotification = new Bundle();
+        bundleContainingAnotherNotification.putParcelable(null, another);
+        Notification source = new Notification.Builder(mContext, "channel")
+                .setContentIntent(contentIntent)
+                .addAction(new Notification.Action.Builder(null, "action", actionIntent).build())
+                .setExtras(bundleContainingAnotherNotification)
+                .build();
+
+        Parcel p = Parcel.obtain();
+        source.writeToParcel(p, 0);
+        p.setDataPosition(0);
+        Notification unparceled = new Notification(p);
+
+        assertThat(unparceled.allPendingIntents).containsExactly(contentIntent, actionIntent);
+    }
+
+    @Test
+    public void allPendingIntents_alsoInPublicVersion() {
+        PendingIntent contentIntent = createPendingIntent("content");
+        PendingIntent actionIntent = createPendingIntent("action");
+        PendingIntent publicContentIntent = createPendingIntent("publicContent");
+        PendingIntent publicActionIntent = createPendingIntent("publicAction");
+        Notification source = new Notification.Builder(mContext, "channel")
+                .setContentIntent(contentIntent)
+                .addAction(new Notification.Action.Builder(null, "action", actionIntent).build())
+                .setPublicVersion(new Notification.Builder(mContext, "channel")
+                        .setContentIntent(publicContentIntent)
+                        .addAction(new Notification.Action.Builder(
+                                null, "publicAction", publicActionIntent).build())
+                        .build())
+                .build();
+
+        Parcel p = Parcel.obtain();
+        source.writeToParcel(p, 0);
+        p.setDataPosition(0);
+        Notification unparceled = new Notification(p);
+
+        assertThat(unparceled.allPendingIntents).containsExactly(contentIntent, actionIntent,
+                publicContentIntent, publicActionIntent);
+        assertThat(unparceled.publicVersion.allPendingIntents).containsExactly(publicContentIntent,
+                publicActionIntent);
+    }
+
+    @Test
     public void messagingStyle_isGroupConversation() {
         mContext.getApplicationInfo().targetSdkVersion = Build.VERSION_CODES.P;
         Notification.MessagingStyle messagingStyle = new Notification.MessagingStyle("self name")
@@ -937,6 +985,27 @@
         // no crash, good
     }
 
+    @Test
+    public void testToBundle_getMessageFromBundle_returnsSameData() {
+        Notification.MessagingStyle.Message message =
+                new Notification.MessagingStyle.Message(
+                        "a", 100, new Person.Builder().setName("hi").build());
+        message.setData("text", Uri.parse("http://test/uri"));
+
+        Notification.MessagingStyle.Message convertedMessage =
+                Notification.MessagingStyle.Message.getMessageFromBundle(message.toBundle());
+
+        assertThat(convertedMessage).isNotNull();
+        assertThat(message.getText()).isEqualTo(convertedMessage.getText());
+        assertThat(message.getTimestamp()).isEqualTo(convertedMessage.getTimestamp());
+        assertThat(message.getExtras().size()).isEqualTo(convertedMessage.getExtras().size());
+        assertThat(message.getSender()).isEqualTo(convertedMessage.getSender());
+        assertThat(message.getSenderPerson()).isEqualTo(convertedMessage.getSenderPerson());
+        assertThat(message.getDataMimeType()).isEqualTo(convertedMessage.getDataMimeType());
+        assertThat(message.getDataUri()).isEqualTo(convertedMessage.getDataUri());
+        assertThat(message.isRemoteInputHistory())
+                .isEqualTo(convertedMessage.isRemoteInputHistory());
+    }
 
     @Test
     public void testDoesNotStripsExtenders() {
diff --git a/core/tests/coretests/src/android/app/OWNERS b/core/tests/coretests/src/android/app/OWNERS
index 8d9461d..64f5e6c 100644
--- a/core/tests/coretests/src/android/app/OWNERS
+++ b/core/tests/coretests/src/android/app/OWNERS
@@ -7,3 +7,6 @@
 
 # A11Y and related
 per-file *UiAutomation* = file:/services/accessibility/OWNERS
+
+# KeyguardManagerTest
+per-file KeyguardManagerTest.java = file:/services/core/java/com/android/server/locksettings/OWNERS
diff --git a/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java b/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java
index 81eb213..5ac99db 100644
--- a/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java
+++ b/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java
@@ -128,6 +128,7 @@
                 0x222222,                // colorBackground
                 0x333333,                // statusBarColor
                 0x444444,                // navigationBarColor
+                0,                       // statusBarAppearance
                 true,                    // ensureStatusBarContrastWhenTransparent
                 true,                    // ensureNavigationBarContrastWhenTransparent
                 RESIZE_MODE_RESIZEABLE,  // resizeMode
@@ -152,6 +153,7 @@
                 0x222222,                  // colorBackground
                 0x333333,                  // statusBarColor
                 0x444444,                  // navigationBarColor
+                0,                         // statusBarAppearance
                 false,                     // ensureStatusBarContrastWhenTransparent
                 false,                     // ensureNavigationBarContrastWhenTransparent
                 RESIZE_MODE_UNRESIZEABLE,  // resizeMode
@@ -167,6 +169,7 @@
                 0x2222222,               // colorBackground
                 0x3333332,               // statusBarColor
                 0x4444442,               // navigationBarColor
+                0,                       // statusBarAppearance
                 true,                    // ensureStatusBarContrastWhenTransparent
                 true,                    // ensureNavigationBarContrastWhenTransparent
                 RESIZE_MODE_RESIZEABLE,  // resizeMode
@@ -197,6 +200,7 @@
                 0x222222,                  // colorBackground
                 0x333333,                  // statusBarColor
                 0x444444,                  // navigationBarColor
+                0,                         // statusBarAppearance
                 false,                     // ensureStatusBarContrastWhenTransparent
                 false,                     // ensureNavigationBarContrastWhenTransparent
                 RESIZE_MODE_UNRESIZEABLE,  // resizeMode
@@ -219,6 +223,7 @@
                 0x222222,                  // colorBackground
                 0x333333,                  // statusBarColor
                 0x444444,                  // navigationBarColor
+                0,                         // statusBarAppearance
                 false,                     // ensureStatusBarContrastWhenTransparent
                 false,                     // ensureNavigationBarContrastWhenTransparent
                 RESIZE_MODE_UNRESIZEABLE,  // resizeMode
@@ -250,6 +255,7 @@
             assertEquals(td1.getBackgroundColor(), td2.getBackgroundColor());
             assertEquals(td1.getStatusBarColor(), td2.getStatusBarColor());
             assertEquals(td1.getNavigationBarColor(), td2.getNavigationBarColor());
+            assertEquals(td1.getStatusBarAppearance(), td2.getStatusBarAppearance());
             assertEquals(td1.getResizeMode(), td2.getResizeMode());
             assertEquals(td1.getMinWidth(), td2.getMinWidth());
             assertEquals(td1.getMinHeight(), td2.getMinHeight());
diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
index b73a87c..4857741 100644
--- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
+++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
@@ -884,12 +884,13 @@
             mConfig.setTo(config);
             ++mNumOfConfigChanges;
 
-            if (mConfigLatch != null) {
+            final CountDownLatch configLatch = mConfigLatch;
+            if (configLatch != null) {
                 if (mTestLatch != null) {
                     mTestLatch.countDown();
                 }
                 try {
-                    mConfigLatch.await(TIMEOUT_SEC, TimeUnit.SECONDS);
+                    configLatch.await(TIMEOUT_SEC, TimeUnit.SECONDS);
                 } catch (InterruptedException e) {
                     throw new IllegalStateException(e);
                 }
diff --git a/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java b/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java
index c9e02f8..33e81c1 100644
--- a/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java
+++ b/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java
@@ -25,6 +25,7 @@
 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
 import static android.app.admin.PasswordMetrics.complexityLevelToMinQuality;
 import static android.app.admin.PasswordMetrics.sanitizeComplexityLevel;
+import static android.app.admin.PasswordMetrics.validateCredential;
 import static android.app.admin.PasswordMetrics.validatePasswordMetrics;
 
 import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
@@ -41,6 +42,8 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
+import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.LockscreenCredential;
 import com.android.internal.widget.PasswordValidationError;
 
 import org.junit.Test;
@@ -94,8 +97,7 @@
 
     @Test
     public void testComputeForPassword_metrics() {
-        final PasswordMetrics metrics = PasswordMetrics.computeForPasswordOrPin(
-                "6B~0z1Z3*8A".getBytes(), /* isPin */ false);
+        final PasswordMetrics metrics = metricsForPassword("6B~0z1Z3*8A");
         assertEquals(11, metrics.length);
         assertEquals(4, metrics.letters);
         assertEquals(3, metrics.upperCase);
@@ -133,72 +135,54 @@
 
     @Test
     public void testDetermineComplexity_lowNumeric() {
-        assertEquals(PASSWORD_COMPLEXITY_LOW,
-                PasswordMetrics.computeForPasswordOrPin("1234".getBytes(),
-                        /* isPin */true).determineComplexity());
+        assertEquals(PASSWORD_COMPLEXITY_LOW, metricsForPin("1234").determineComplexity());
     }
 
     @Test
     public void testDetermineComplexity_lowNumericComplex() {
-        assertEquals(PASSWORD_COMPLEXITY_LOW,
-                PasswordMetrics.computeForPasswordOrPin("124".getBytes(),
-                        /* isPin */ true).determineComplexity());
+        assertEquals(PASSWORD_COMPLEXITY_LOW, metricsForPin("124").determineComplexity());
     }
 
     @Test
     public void testDetermineComplexity_lowAlphabetic() {
-        assertEquals(PASSWORD_COMPLEXITY_LOW,
-                PasswordMetrics.computeForPasswordOrPin("a!".getBytes(),
-                        /* isPin */ false).determineComplexity());
+        assertEquals(PASSWORD_COMPLEXITY_LOW, metricsForPassword("a!").determineComplexity());
     }
 
     @Test
     public void testDetermineComplexity_lowAlphanumeric() {
-        assertEquals(PASSWORD_COMPLEXITY_LOW,
-                PasswordMetrics.computeForPasswordOrPin("a!1".getBytes(),
-                        /* isPin */ false).determineComplexity());
+        assertEquals(PASSWORD_COMPLEXITY_LOW, metricsForPassword("a!1").determineComplexity());
     }
 
     @Test
     public void testDetermineComplexity_mediumNumericComplex() {
-        assertEquals(PASSWORD_COMPLEXITY_MEDIUM,
-                PasswordMetrics.computeForPasswordOrPin("1238".getBytes(),
-                        /* isPin */ true).determineComplexity());
+        assertEquals(PASSWORD_COMPLEXITY_MEDIUM, metricsForPin("1238").determineComplexity());
     }
 
     @Test
     public void testDetermineComplexity_mediumAlphabetic() {
-        assertEquals(PASSWORD_COMPLEXITY_MEDIUM,
-                PasswordMetrics.computeForPasswordOrPin("ab!c".getBytes(),
-                        /* isPin */ false).determineComplexity());
+        assertEquals(PASSWORD_COMPLEXITY_MEDIUM, metricsForPassword("ab!c").determineComplexity());
     }
 
     @Test
     public void testDetermineComplexity_mediumAlphanumeric() {
-        assertEquals(PASSWORD_COMPLEXITY_MEDIUM,
-                PasswordMetrics.computeForPasswordOrPin("ab!1".getBytes(),
-                        /* isPin */ false).determineComplexity());
+        assertEquals(PASSWORD_COMPLEXITY_MEDIUM, metricsForPassword("ab!1").determineComplexity());
     }
 
     @Test
     public void testDetermineComplexity_highNumericComplex() {
-        assertEquals(PASSWORD_COMPLEXITY_HIGH,
-                PasswordMetrics.computeForPasswordOrPin("12389647!".getBytes(),
-                        /* isPin */ true).determineComplexity());
+        assertEquals(PASSWORD_COMPLEXITY_HIGH, metricsForPin("12389647!").determineComplexity());
     }
 
     @Test
     public void testDetermineComplexity_highAlphabetic() {
         assertEquals(PASSWORD_COMPLEXITY_HIGH,
-                PasswordMetrics.computeForPasswordOrPin("alphabetic!".getBytes(),
-                        /* isPin */ false).determineComplexity());
+                metricsForPassword("alphabetic!").determineComplexity());
     }
 
     @Test
     public void testDetermineComplexity_highAlphanumeric() {
         assertEquals(PASSWORD_COMPLEXITY_HIGH,
-                PasswordMetrics.computeForPasswordOrPin("alphanumeric123!".getBytes(),
-                        /* isPin */ false).determineComplexity());
+                metricsForPassword("alphanumeric123!").determineComplexity());
     }
 
     @Test
@@ -374,8 +358,74 @@
                 PasswordValidationError.NOT_ENOUGH_NON_DIGITS, 1);
     }
 
+    @Test
+    public void testValidateCredential_none() {
+        PasswordMetrics adminMetrics;
+        LockscreenCredential none = LockscreenCredential.createNone();
+
+        adminMetrics = new PasswordMetrics(CREDENTIAL_TYPE_NONE);
+        assertValidationErrors(
+                validateCredential(adminMetrics, PASSWORD_COMPLEXITY_NONE, none));
+
+        adminMetrics = new PasswordMetrics(CREDENTIAL_TYPE_PIN);
+        assertValidationErrors(
+                validateCredential(adminMetrics, PASSWORD_COMPLEXITY_NONE, none),
+                PasswordValidationError.WEAK_CREDENTIAL_TYPE, 0);
+    }
+
+    @Test
+    public void testValidateCredential_password() {
+        PasswordMetrics adminMetrics;
+        LockscreenCredential password;
+
+        adminMetrics = new PasswordMetrics(CREDENTIAL_TYPE_NONE);
+        password = LockscreenCredential.createPassword("password");
+        assertValidationErrors(
+                validateCredential(adminMetrics, PASSWORD_COMPLEXITY_LOW, password));
+
+        // Test that validateCredential() checks LockscreenCredential#hasInvalidChars().
+        adminMetrics = new PasswordMetrics(CREDENTIAL_TYPE_NONE);
+        password = LockscreenCredential.createPassword("™™™™");
+        assertTrue(password.hasInvalidChars());
+        assertValidationErrors(
+                validateCredential(adminMetrics, PASSWORD_COMPLEXITY_LOW, password),
+                PasswordValidationError.CONTAINS_INVALID_CHARACTERS, 0);
+
+        // Test one more case where validateCredential() should reject the password.  Beyond this,
+        // the unit tests for the lower-level method validatePasswordMetrics() should be sufficient.
+        adminMetrics = new PasswordMetrics(CREDENTIAL_TYPE_NONE);
+        adminMetrics.length = 6;
+        password = LockscreenCredential.createPassword("pass");
+        assertValidationErrors(
+                validateCredential(adminMetrics, PASSWORD_COMPLEXITY_LOW, password),
+                PasswordValidationError.TOO_SHORT, 6);
+    }
+
+    private LockscreenCredential createPattern(String patternString) {
+        return LockscreenCredential.createPattern(LockPatternUtils.byteArrayToPattern(
+                patternString.getBytes()));
+    }
+
+    private static PasswordMetrics metricsForPassword(String password) {
+        return PasswordMetrics.computeForCredential(LockscreenCredential.createPassword(password));
+    }
+
+    private static PasswordMetrics metricsForPin(String pin) {
+        return PasswordMetrics.computeForCredential(LockscreenCredential.createPin(pin));
+    }
+
+    @Test
+    public void testValidateCredential_pattern() {
+        PasswordMetrics adminMetrics = new PasswordMetrics(CREDENTIAL_TYPE_NONE);
+        assertValidationErrors(
+                validateCredential(adminMetrics, PASSWORD_COMPLEXITY_NONE, createPattern("123")),
+                PasswordValidationError.TOO_SHORT, 4);
+        assertValidationErrors(
+                validateCredential(adminMetrics, PASSWORD_COMPLEXITY_NONE, createPattern("1234")));
+    }
+
     /**
-     * @param expected sequense of validation error codes followed by requirement values, must have
+     * @param expected sequence of validation error codes followed by requirement values, must have
      *                 even number of elements. Empty means no errors.
      */
     private void assertValidationErrors(
diff --git a/core/tests/coretests/src/android/app/backup/BackupManagerTest.java b/core/tests/coretests/src/android/app/backup/BackupManagerTest.java
index 27ee82e..567ca01 100644
--- a/core/tests/coretests/src/android/app/backup/BackupManagerTest.java
+++ b/core/tests/coretests/src/android/app/backup/BackupManagerTest.java
@@ -19,11 +19,14 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertThrows;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.app.backup.BackupAnnotations.BackupDestination;
 import android.app.backup.BackupAnnotations.OperationType;
 import android.content.Context;
 import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
 import android.os.UserHandle;
 import android.platform.test.annotations.Presubmit;
 
@@ -31,7 +34,6 @@
 
 import org.junit.Before;
 import org.junit.Test;
-import org.junit.function.ThrowingRunnable;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
@@ -43,14 +45,28 @@
 public class BackupManagerTest {
     private BackupManager mBackupManager;
 
+    private static final int USER_ID = 12;
+
     @Mock
     Context mContext;
+    @Mock
+    IBackupManager mIBackupManager;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
 
         mBackupManager = new BackupManager(mContext);
+        BackupManager.sService = mIBackupManager;
+    }
+
+    @Test
+    public void testSetFrameworkSchedulingEnabled_delegatesToService() throws RemoteException {
+        when(mContext.getUserId()).thenReturn(USER_ID);
+        mBackupManager.setFrameworkSchedulingEnabled(true);
+
+        verify(mIBackupManager).setFrameworkSchedulingEnabledForUser(
+                USER_ID, /* isEnabled= */true);
     }
 
     @Test
diff --git a/core/tests/coretests/src/android/app/usage/UsageStatsTest.java b/core/tests/coretests/src/android/app/usage/UsageStatsTest.java
index 858bbd2..f728080 100644
--- a/core/tests/coretests/src/android/app/usage/UsageStatsTest.java
+++ b/core/tests/coretests/src/android/app/usage/UsageStatsTest.java
@@ -36,11 +36,11 @@
 import android.app.usage.UsageEvents.Event;
 import android.os.Parcel;
 import android.os.UserHandle;
-import android.support.test.uiautomator.UiDevice;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
+import androidx.test.uiautomator.UiDevice;
 
 import org.junit.Before;
 import org.junit.Test;
diff --git a/core/tests/coretests/src/android/content/ContentCaptureOptionsTest.java b/core/tests/coretests/src/android/content/ContentCaptureOptionsTest.java
index c6f4fa2..f8348d2 100644
--- a/core/tests/coretests/src/android/content/ContentCaptureOptionsTest.java
+++ b/core/tests/coretests/src/android/content/ContentCaptureOptionsTest.java
@@ -22,6 +22,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.os.Parcel;
 import android.util.ArraySet;
 import android.view.contentcapture.ContentCaptureManager.ContentCaptureClient;
 
@@ -40,16 +41,29 @@
 @RunWith(MockitoJUnitRunner.class)
 public class ContentCaptureOptionsTest {
 
-    private final ComponentName mContextComponent = new ComponentName("marco", "polo");
-    private final ComponentName mComp1 = new ComponentName("comp", "one");
-    private final ComponentName mComp2 = new ComponentName("two", "comp");
+    private static final ComponentName CONTEXT_COMPONENT = new ComponentName("marco", "polo");
+    private static final ComponentName COMPONENT1 = new ComponentName("comp", "one");
+    private static final ComponentName COMPONENT2 = new ComponentName("two", "comp");
+    private static final ContentCaptureOptions CONTENT_CAPTURE_OPTIONS =
+            new ContentCaptureOptions(
+                    /* loggingLevel= */ 1000,
+                    /* maxBufferSize= */ 1001,
+                    /* idleFlushingFrequencyMs= */ 1002,
+                    /* textChangeFlushingFrequencyMs= */ 1003,
+                    /* logHistorySize= */ 1004,
+                    /* disableFlushForViewTreeAppearing= */ true,
+                    /* enableReceiver= */ false,
+                    new ContentCaptureOptions.ContentProtectionOptions(
+                            /* enableReceiver= */ true,
+                            /* bufferSize= */ 2001),
+                    /* whitelistedComponents= */ toSet(COMPONENT1, COMPONENT2));
 
     @Mock private Context mContext;
     @Mock private ContentCaptureClient mClient;
 
     @Before
     public void setExpectation() {
-        when(mClient.contentCaptureClientGetComponentName()).thenReturn(mContextComponent);
+        when(mClient.contentCaptureClientGetComponentName()).thenReturn(CONTEXT_COMPONENT);
         when(mContext.getContentCaptureClient()).thenReturn(mClient);
     }
 
@@ -67,26 +81,27 @@
 
     @Test
     public void testIsWhitelisted_notWhitelisted() {
-        ContentCaptureOptions options = new ContentCaptureOptions(toSet(mComp1, mComp2));
+        ContentCaptureOptions options = new ContentCaptureOptions(toSet(COMPONENT1, COMPONENT2));
         assertThat(options.isWhitelisted(mContext)).isFalse();
     }
 
     @Test
     public void testIsWhitelisted_whitelisted() {
-        ContentCaptureOptions options = new ContentCaptureOptions(toSet(mComp1, mContextComponent));
+        ContentCaptureOptions options =
+                new ContentCaptureOptions(toSet(COMPONENT1, CONTEXT_COMPONENT));
         assertThat(options.isWhitelisted(mContext)).isTrue();
     }
 
     @Test
     public void testIsWhitelisted_invalidContext() {
-        ContentCaptureOptions options = new ContentCaptureOptions(toSet(mContextComponent));
+        ContentCaptureOptions options = new ContentCaptureOptions(toSet(CONTEXT_COMPONENT));
         Context invalidContext = mock(Context.class); // has no client
         assertThat(options.isWhitelisted(invalidContext)).isFalse();
     }
 
     @Test
     public void testIsWhitelisted_clientWithNullComponentName() {
-        ContentCaptureOptions options = new ContentCaptureOptions(toSet(mContextComponent));
+        ContentCaptureOptions options = new ContentCaptureOptions(toSet(CONTEXT_COMPONENT));
         ContentCaptureClient client = mock(ContentCaptureClient.class);
         Context context = mock(Context.class);
         when(context.getContentCaptureClient()).thenReturn(client);
@@ -94,8 +109,69 @@
         assertThat(options.isWhitelisted(context)).isFalse();
     }
 
+    @Test
+    public void testToString() {
+        String actual = CONTENT_CAPTURE_OPTIONS.toString();
+
+        String expected =
+                new StringBuilder("ContentCaptureOptions [")
+                        .append("loggingLevel=")
+                        .append(CONTENT_CAPTURE_OPTIONS.loggingLevel)
+                        .append(", maxBufferSize=")
+                        .append(CONTENT_CAPTURE_OPTIONS.maxBufferSize)
+                        .append(", idleFlushingFrequencyMs=")
+                        .append(CONTENT_CAPTURE_OPTIONS.idleFlushingFrequencyMs)
+                        .append(", textChangeFlushingFrequencyMs=")
+                        .append(CONTENT_CAPTURE_OPTIONS.textChangeFlushingFrequencyMs)
+                        .append(", logHistorySize=")
+                        .append(CONTENT_CAPTURE_OPTIONS.logHistorySize)
+                        .append(", disableFlushForViewTreeAppearing=")
+                        .append(CONTENT_CAPTURE_OPTIONS.disableFlushForViewTreeAppearing)
+                        .append(", enableReceiver=")
+                        .append(CONTENT_CAPTURE_OPTIONS.enableReceiver)
+                        .append(", contentProtectionOptions=ContentProtectionOptions [")
+                        .append("enableReceiver=")
+                        .append(CONTENT_CAPTURE_OPTIONS.contentProtectionOptions.enableReceiver)
+                        .append(", bufferSize=")
+                        .append(CONTENT_CAPTURE_OPTIONS.contentProtectionOptions.bufferSize)
+                        .append("], whitelisted=")
+                        .append(CONTENT_CAPTURE_OPTIONS.whitelistedComponents)
+                        .append(']')
+                        .toString();
+        assertThat(actual).isEqualTo(expected);
+    }
+
+    @Test
+    public void testParcelSerializationDeserialization() {
+        Parcel parcel = Parcel.obtain();
+        CONTENT_CAPTURE_OPTIONS.writeToParcel(parcel, /* flags= */ 0);
+        parcel.setDataPosition(0);
+
+        ContentCaptureOptions actual = ContentCaptureOptions.CREATOR.createFromParcel(parcel);
+        parcel.recycle();
+
+        assertThat(actual).isNotNull();
+        assertThat(actual.loggingLevel).isEqualTo(CONTENT_CAPTURE_OPTIONS.loggingLevel);
+        assertThat(actual.maxBufferSize).isEqualTo(CONTENT_CAPTURE_OPTIONS.maxBufferSize);
+        assertThat(actual.idleFlushingFrequencyMs)
+                .isEqualTo(CONTENT_CAPTURE_OPTIONS.idleFlushingFrequencyMs);
+        assertThat(actual.textChangeFlushingFrequencyMs)
+                .isEqualTo(CONTENT_CAPTURE_OPTIONS.textChangeFlushingFrequencyMs);
+        assertThat(actual.logHistorySize).isEqualTo(CONTENT_CAPTURE_OPTIONS.logHistorySize);
+        assertThat(actual.disableFlushForViewTreeAppearing)
+                .isEqualTo(CONTENT_CAPTURE_OPTIONS.disableFlushForViewTreeAppearing);
+        assertThat(actual.enableReceiver).isEqualTo(CONTENT_CAPTURE_OPTIONS.enableReceiver);
+        assertThat(actual.contentProtectionOptions).isNotNull();
+        assertThat(actual.contentProtectionOptions.enableReceiver)
+                .isEqualTo(CONTENT_CAPTURE_OPTIONS.contentProtectionOptions.enableReceiver);
+        assertThat(actual.contentProtectionOptions.bufferSize)
+                .isEqualTo(CONTENT_CAPTURE_OPTIONS.contentProtectionOptions.bufferSize);
+        assertThat(actual.whitelistedComponents)
+                .containsExactlyElementsIn(CONTENT_CAPTURE_OPTIONS.whitelistedComponents);
+    }
+
     @NonNull
-    private ArraySet<ComponentName> toSet(@Nullable ComponentName... comps) {
+    private static ArraySet<ComponentName> toSet(@Nullable ComponentName... comps) {
         ArraySet<ComponentName> set = new ArraySet<>();
         if (comps != null) {
             for (int i = 0; i < comps.length; i++) {
diff --git a/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java b/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java
index df4fb44..0941a2b 100644
--- a/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java
+++ b/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java
@@ -137,7 +137,7 @@
             );
         });
 
-        PollingCheck.waitFor(/* timeout= */ 5000, () -> {
+        PollingCheck.waitFor(/* timeout= */ 7000, () -> {
             AtomicBoolean isActivityAtCorrectScale = new AtomicBoolean(false);
             rule.getScenario().onActivity(activity ->
                     isActivityAtCorrectScale.set(
@@ -146,12 +146,7 @@
                                 .fontScale == fontScale
                     )
             );
-            return isActivityAtCorrectScale.get() && InstrumentationRegistry
-                    .getInstrumentation()
-                    .getContext()
-                    .getResources()
-                    .getConfiguration()
-                    .fontScale == fontScale;
+            return isActivityAtCorrectScale.get();
         });
     }
 
diff --git a/core/tests/coretests/src/android/database/DatabaseGeneralTest.java b/core/tests/coretests/src/android/database/DatabaseGeneralTest.java
index b8dbfd3..95b0e32 100644
--- a/core/tests/coretests/src/android/database/DatabaseGeneralTest.java
+++ b/core/tests/coretests/src/android/database/DatabaseGeneralTest.java
@@ -25,7 +25,6 @@
 import android.database.sqlite.SQLiteDebug;
 import android.database.sqlite.SQLiteException;
 import android.os.Parcel;
-import android.support.test.uiautomator.UiDevice;
 import android.test.AndroidTestCase;
 import android.test.PerformanceTestCase;
 import android.util.Log;
@@ -35,6 +34,7 @@
 import androidx.test.filters.LargeTest;
 import androidx.test.filters.MediumTest;
 import androidx.test.filters.SmallTest;
+import androidx.test.uiautomator.UiDevice;
 
 import junit.framework.Assert;
 
diff --git a/core/tests/coretests/src/android/database/sqlite/OWNERS b/core/tests/coretests/src/android/database/sqlite/OWNERS
new file mode 100644
index 0000000..3bebc7c
--- /dev/null
+++ b/core/tests/coretests/src/android/database/sqlite/OWNERS
@@ -0,0 +1 @@
+include /SQLITE_OWNERS
diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteCursorTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteCursorTest.java
index 78d3c41..bc7acd9 100644
--- a/core/tests/coretests/src/android/database/sqlite/SQLiteCursorTest.java
+++ b/core/tests/coretests/src/android/database/sqlite/SQLiteCursorTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2010 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.
@@ -187,4 +187,39 @@
         }
         assertEquals("All rows should be visited", 10, n);
     }
-}
\ No newline at end of file
+
+    // Return the number of columns associated with a new cursor.
+    private int columnCount() {
+        final String query = "SELECT * FROM t1";
+        try (Cursor c = mDatabase.rawQuery(query, null)) {
+            return c.getColumnCount();
+        }
+    }
+
+    /**
+     * Verify that a cursor that is created after the database schema is updated, sees the updated
+     * schema.
+     */
+    public void testSchemaChangeCursor() {
+        // Create the t1 table and put some data in it.
+        mDatabase.beginTransaction();
+        try {
+            mDatabase.execSQL("CREATE TABLE t1 (i int);");
+            mDatabase.execSQL("INSERT INTO t1 (i) VALUES (2)");
+            mDatabase.execSQL("INSERT INTO t1 (i) VALUES (3)");
+            mDatabase.execSQL("INSERT INTO t1 (i) VALUES (5)");
+            mDatabase.setTransactionSuccessful();
+        } finally {
+            mDatabase.endTransaction();
+        }
+
+        mDatabase.beginTransaction();
+        try {
+            assertEquals(1, columnCount());
+            mDatabase.execSQL("ALTER TABLE t1 ADD COLUMN j int");
+            assertEquals(2, columnCount());
+        } finally {
+            mDatabase.endTransaction();
+        }
+    }
+}
diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java
new file mode 100644
index 0000000..fc72f61
--- /dev/null
+++ b/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java
@@ -0,0 +1,217 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.database.sqlite;
+
+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.Assert.fail;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.database.DatabaseUtils;
+import android.os.SystemClock;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class SQLiteDatabaseTest {
+
+    private static final String TAG = "SQLiteDatabaseTest";
+
+    private final Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
+
+    private SQLiteDatabase mDatabase;
+    private File mDatabaseFile;
+    private static final String DATABASE_FILE_NAME = "database_test.db";
+
+    @Before
+    public void setUp() throws Exception {
+        assertNotNull(mContext);
+        mContext.deleteDatabase(DATABASE_FILE_NAME);
+        mDatabaseFile = mContext.getDatabasePath(DATABASE_FILE_NAME);
+        mDatabaseFile.getParentFile().mkdirs(); // directory may not exist
+        mDatabase = SQLiteDatabase.openOrCreateDatabase(mDatabaseFile, null);
+        assertNotNull(mDatabase);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        closeAndDeleteDatabase();
+    }
+
+    private void closeAndDeleteDatabase() {
+        mDatabase.close();
+        SQLiteDatabase.deleteDatabase(mDatabaseFile);
+    }
+
+    @Test
+    public void testStatementDDLEvictsCache() {
+        // The following will be cached (key is SQL string)
+        String selectQuery = "SELECT * FROM t1";
+
+        mDatabase.beginTransaction();
+        mDatabase.execSQL("CREATE TABLE `t1` (`c1` INTEGER NOT NULL PRIMARY KEY, data TEXT)");
+        try (Cursor c = mDatabase.rawQuery(selectQuery, null)) {
+            assertEquals(2, c.getColumnCount());
+        }
+        // Alter the schema in such a way that if the cached query is used it would produce wrong
+        // results due to the change in column amounts.
+        mDatabase.execSQL("ALTER TABLE `t1` RENAME TO `t1_old`");
+        mDatabase.execSQL("CREATE TABLE `t1` (`c1` INTEGER NOT NULL PRIMARY KEY)");
+        // Execute cached query (that should have been evicted), validating it sees the new schema.
+        try (Cursor c = mDatabase.rawQuery(selectQuery, null)) {
+            assertEquals(1, c.getColumnCount());
+        }
+        mDatabase.setTransactionSuccessful();
+        mDatabase.endTransaction();
+    }
+
+    @Test
+    public void testStressDDLEvicts() {
+        mDatabase.enableWriteAheadLogging();
+        mDatabase.execSQL("CREATE TABLE `t1` (`c1` INTEGER NOT NULL PRIMARY KEY, data TEXT)");
+        final int iterations = 1000;
+        ExecutorService exec = Executors.newFixedThreadPool(2);
+        exec.execute(() -> {
+                    boolean pingPong = true;
+                    for (int i = 0; i < iterations; i++) {
+                        mDatabase.beginTransaction();
+                        if (pingPong) {
+                            mDatabase.execSQL("ALTER TABLE `t1` RENAME TO `t1_old`");
+                            mDatabase.execSQL("CREATE TABLE `t1` (`c1` INTEGER NOT NULL "
+                                + "PRIMARY KEY)");
+                            pingPong = false;
+                        } else {
+                            mDatabase.execSQL("DROP TABLE `t1`");
+                            mDatabase.execSQL("ALTER TABLE `t1_old` RENAME TO `t1`");
+                            pingPong = true;
+                        }
+                        mDatabase.setTransactionSuccessful();
+                        mDatabase.endTransaction();
+                    }
+                });
+        exec.execute(() -> {
+                    for (int i = 0; i < iterations; i++) {
+                        try (Cursor c = mDatabase.rawQuery("SELECT * FROM t1", null)) {
+                            c.getCount();
+                        }
+                    }
+                });
+        try {
+            exec.shutdown();
+            assertTrue(exec.awaitTermination(1, TimeUnit.MINUTES));
+        } catch (InterruptedException e) {
+            fail("Timed out");
+        }
+    }
+
+    /**
+     * Create a database with one table with three columns.
+     */
+    private void createComplexDatabase() {
+        mDatabase.beginTransaction();
+        try {
+            mDatabase.execSQL("CREATE TABLE t1 (i int, d double, t text);");
+            mDatabase.setTransactionSuccessful();
+        } finally {
+            mDatabase.endTransaction();
+        }
+    }
+
+    /**
+     * A three-value insert for the complex database.
+     */
+    private String createComplexInsert() {
+        return "INSERT INTO t1 (i, d, t) VALUES (?1, ?2, ?3)";
+    }
+
+    @Test
+    public void testAutomaticCounters() {
+        final int size = 10;
+
+        createComplexDatabase();
+
+        // Put 10 lines in the database.
+        mDatabase.beginTransaction();
+        try {
+            try (SQLiteRawStatement s = mDatabase.createRawStatement(createComplexInsert())) {
+                for (int i = 0; i < size; i++) {
+                    int vi = i * 3;
+                    double vd = i * 2.5;
+                    String vt = String.format("text%02dvalue", i);
+                    s.bindInt(1, vi);
+                    s.bindDouble(2, vd);
+                    s.bindText(3, vt);
+                    boolean r = s.step();
+                    // No row is returned by this query.
+                    assertFalse(r);
+                    s.reset();
+                    assertEquals(i + 1, mDatabase.getLastInsertRowId());
+                    assertEquals(1, mDatabase.getLastChangedRowsCount());
+                    assertEquals(i + 2, mDatabase.getTotalChangedRowsCount());
+                }
+            }
+            mDatabase.setTransactionSuccessful();
+        } finally {
+            mDatabase.endTransaction();
+        }
+
+        // Put a second 10 lines in the database.
+        mDatabase.beginTransaction();
+        try {
+            try (SQLiteRawStatement s = mDatabase.createRawStatement(createComplexInsert())) {
+                for (int i = 0; i < size; i++) {
+                    int vi = i * 3;
+                    double vd = i * 2.5;
+                    String vt = String.format("text%02dvalue", i);
+                    s.bindInt(1, vi);
+                    s.bindDouble(2, vd);
+                    s.bindText(3, vt);
+                    boolean r = s.step();
+                    // No row is returned by this query.
+                    assertFalse(r);
+                    s.reset();
+                    assertEquals(size + i + 1, mDatabase.getLastInsertRowId());
+                    assertEquals(1, mDatabase.getLastChangedRowsCount());
+                    assertEquals(size + i + 2, mDatabase.getTotalChangedRowsCount());
+                }
+            }
+            mDatabase.setTransactionSuccessful();
+        } finally {
+            mDatabase.endTransaction();
+        }
+    }
+}
diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteRawStatementTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteRawStatementTest.java
new file mode 100644
index 0000000..36bb8e5
--- /dev/null
+++ b/core/tests/coretests/src/android/database/sqlite/SQLiteRawStatementTest.java
@@ -0,0 +1,880 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.database.sqlite;
+
+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.Assert.fail;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.database.DatabaseUtils;
+import android.os.SystemClock;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class SQLiteRawStatementTest {
+
+    private static final String TAG = "SQLiteRawStatementTest";
+
+    private final Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
+
+    private SQLiteDatabase mDatabase;
+    private File mDatabaseFile;
+    private static final String DATABASE_FILE_NAME = "database_test.db";
+
+    @Before
+    public void setUp() throws Exception {
+        assertNotNull(mContext);
+        mContext.deleteDatabase(DATABASE_FILE_NAME);
+        mDatabaseFile = mContext.getDatabasePath(DATABASE_FILE_NAME);
+        mDatabaseFile.getParentFile().mkdirs(); // directory may not exist
+        mDatabase = SQLiteDatabase.openOrCreateDatabase(mDatabaseFile, null);
+        assertNotNull(mDatabase);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        closeAndDeleteDatabase();
+    }
+
+    private void closeAndDeleteDatabase() {
+        mDatabase.close();
+        SQLiteDatabase.deleteDatabase(mDatabaseFile);
+    }
+
+    /**
+     * Create a database with a single table with one column and two rows.  Exceptions are allowed
+     * to percolate out.
+     */
+    private void createSimpleDatabase() {
+        // Create the t1 table and put some data in it.
+        mDatabase.beginTransaction();
+        try {
+            mDatabase.execSQL("CREATE TABLE t1 (i int);");
+            mDatabase.execSQL("INSERT INTO t1 (i) VALUES (2)");
+            mDatabase.execSQL("INSERT INTO t1 (i) VALUES (3)");
+            mDatabase.setTransactionSuccessful();
+        } finally {
+            mDatabase.endTransaction();
+        }
+    }
+
+    /**
+     * A simple insert for the simple database.
+     */
+    private String createSimpleInsert() {
+        return "INSERT INTO t1 (i) VALUES (1)";
+    }
+
+    /**
+     * Create a database with one table with three columns.
+     */
+    private void createComplexDatabase() {
+        mDatabase.beginTransaction();
+        try {
+            mDatabase.execSQL("CREATE TABLE t1 (i int, d double, t text);");
+            mDatabase.setTransactionSuccessful();
+        } finally {
+            mDatabase.endTransaction();
+        }
+    }
+
+    /**
+     * A three-value insert for the complex database.
+     */
+    private String createComplexInsert() {
+        return "INSERT INTO t1 (i, d, t) VALUES (?1, ?2, ?3)";
+    }
+
+    /**
+     * Create a database with one table with 12 columns.
+     */
+    private void createWideDatabase() {
+        StringBuilder sp = new StringBuilder();
+        sp.append(String.format("i%d int", 0));
+        for (int i = 1; i < 12; i++) {
+            sp.append(String.format(", i%d int", i));
+        }
+        mDatabase.beginTransaction();
+        try {
+            mDatabase.execSQL("CREATE TABLE t1 (" + sp.toString() + ")");
+            mDatabase.setTransactionSuccessful();
+        } finally {
+            mDatabase.endTransaction();
+        }
+    }
+
+    /**
+     * A 12-value insert for the wide database.
+     */
+    private String createWideInsert() {
+        StringBuilder sp = new StringBuilder();
+        sp.append("INSERT INTO t1 (i0");
+        for (int i = 1; i < 12; i++) {
+            sp.append(String.format(", i%d", i));
+        }
+        sp.append(") VALUES (?");
+        for (int i = 1; i < 12; i++) {
+            sp.append(", ?");
+        }
+        sp.append(")");
+        return sp.toString();
+    }
+
+    @Test
+    public void testSingleTransaction() {
+        createSimpleDatabase();
+
+        mDatabase.beginTransaction();
+        try {
+            int found = 0;
+            try (SQLiteRawStatement s = mDatabase.createRawStatement("SELECT i from t1")) {
+                for (int i = 0; s.step() && i < 5; i++) {
+                    found++;
+                }
+            }
+            assertEquals(2, found);
+            long r = DatabaseUtils.longForQuery(mDatabase, "SELECT count(*) from t1", null);
+            assertEquals(2, r);
+            mDatabase.setTransactionSuccessful();
+        } finally {
+            mDatabase.endTransaction();
+        }
+    }
+
+    @Test
+    public void testMultipleTransactions() {
+        createSimpleDatabase();
+
+        mDatabase.beginTransaction();
+        try {
+            final String query = "SELECT i from t1";
+            try (SQLiteRawStatement s = mDatabase.createRawStatement(query);
+                 SQLiteRawStatement t = mDatabase.createRawStatement(query)) {
+                int found = 0;
+                for (int i = 0; s.step() && i < 5; i++) {
+                    boolean r = t.step();
+                    assertTrue(r);
+                    assertEquals(t.getColumnInt(0), s.getColumnInt(0));
+                    found++;
+                }
+                assertFalse(t.step());
+                assertEquals(2, found);
+                long r = DatabaseUtils.longForQuery(mDatabase, "SELECT count(*) from t1", null);
+                assertEquals(2, r);
+            }
+            mDatabase.setTransactionSuccessful();
+        } finally {
+            mDatabase.endTransaction();
+        }
+    }
+
+    @Test
+    public void testInsert() {
+        createComplexDatabase();
+
+        // Populate the database
+        mDatabase.beginTransaction();
+        try {
+            try (SQLiteRawStatement s = mDatabase.createRawStatement(createComplexInsert())) {
+                for (int i = 0; i < 9; i++) {
+                    int vi = i * 3;
+                    double vd = i * 2.5;
+                    String vt = String.format("text%02dvalue", i);
+                    s.bindInt(1, vi);
+                    s.bindDouble(2, vd);
+                    s.bindText(3, vt);
+                    boolean r = s.step();
+                    // No row is returned by this query.
+                    assertFalse(r);
+                    s.reset();
+                }
+                // The last row has a null double and a null text.
+                s.bindInt(1, 20);
+                s.bindNull(2);
+                s.bindNull(3);
+                assertFalse(s.step());
+                s.reset();
+            }
+            mDatabase.setTransactionSuccessful();
+        } finally {
+            mDatabase.endTransaction();
+        }
+
+        // Verify that 10 rows have been inserted.
+        mDatabase.beginTransaction();
+        try {
+            final String query = "SELECT COUNT(*) FROM t1";
+            try (SQLiteRawStatement s = mDatabase.createRawStatement(query)) {
+                boolean r = s.step();
+                assertTrue(r);
+                int rows = s.getColumnInt(0);
+                assertEquals(10, rows);
+            }
+            mDatabase.setTransactionSuccessful();
+        } finally {
+            mDatabase.endTransaction();
+        }
+
+        // Verify that the element created with i == 3 is correct.
+        mDatabase.beginTransactionReadOnly();
+        try {
+            final String query = "SELECT i, d, t FROM t1 WHERE t = 'text03value'";
+            try (SQLiteRawStatement s = mDatabase.createRawStatement(query)) {
+                assertTrue(s.step());
+                assertEquals(3, s.getResultColumnCount());
+                int vi = s.getColumnInt(0);
+                double vd = s.getColumnDouble(1);
+                String vt = s.getColumnText(2);
+                assertEquals(3 * 3, vi);
+                assertEquals(2.5 * 3, vd, 0.1);
+                assertEquals("text03value", vt);
+                // No more rows.
+                assertFalse(s.step());
+            }
+        } finally {
+            mDatabase.endTransaction();
+        }
+
+        mDatabase.beginTransactionReadOnly();
+        try {
+            final String query = "SELECT i, d, t FROM t1 WHERE i == 20";
+            try (SQLiteRawStatement s = mDatabase.createRawStatement(query)) {
+                assertTrue(s.step());
+                assertEquals(3, s.getResultColumnCount());
+                assertEquals(20, s.getColumnInt(0));
+                assertEquals(0.0, s.getColumnDouble(1), 0.01);
+                assertEquals(null, s.getColumnText(2));
+                // No more rows.
+                assertFalse(s.step());
+            }
+        } finally {
+            mDatabase.endTransaction();
+        }
+    }
+
+    @Test
+    public void testRequiresTransaction() {
+        createSimpleDatabase();
+
+        // Verify that a statement cannot be created outside a transaction.
+        try {
+            try (SQLiteRawStatement s = mDatabase.createRawStatement(createSimpleInsert())) {
+                fail("created a statement outside a transaction");
+                // Suppress warnings about unused variables.
+                s.close();
+            }
+        } catch (IllegalStateException e) {
+            // There is more than one source of this exception.  Scrape the message and look for
+            // "no current transaction", which comes from
+            // {@link SQLiteSession.throwIfNoTransaction}.
+            if (!e.getMessage().contains("no current transaction")) {
+                fail("unexpected IllegalStateException, got " + e);
+            }
+        } catch (AssertionError e) {
+            // Pass on the fail from the try-block before the generic catch below can see it.
+            throw e;
+        } catch (Throwable e) {
+            fail("expected IllegalStateException, got " + e);
+        }
+    }
+
+    // Test a variety of conditions under which a SQLiteRawStatement should close.  These methods
+    // deliberately do not use try/finally blocks on the statement to make sure a specific
+    // behavior is being tested.
+    @Test
+    public void testAutoClose() {
+        createSimpleDatabase();
+
+        SQLiteRawStatement s;
+
+        // Verify that calling close(), closes the statement.
+        mDatabase.beginTransaction();
+        try {
+            s = mDatabase.createRawStatement(createSimpleInsert());
+            assertTrue(s.isOpen());
+            s.close();
+            assertFalse(s.isOpen());
+        } finally {
+            mDatabase.endTransaction();
+        }
+
+        // Verify that a statement is closed automatically at the end of a try-with-resource
+        // block.
+        mDatabase.beginTransaction();
+        try {
+            try (var t = mDatabase.createRawStatement(createSimpleInsert())) {
+                // Save a reference to t for examination ouside the try-with-resource block.
+                s = t;
+                assertTrue(s.isOpen());
+            }
+            assertFalse(s.isOpen());
+        } finally {
+            mDatabase.endTransaction();
+        }
+        assertFalse(s.isOpen());
+
+
+        // Verify that a statement is closed implicitly when the transaction is marked
+        // successful.
+        mDatabase.beginTransaction();
+        try {
+            s = mDatabase.createRawStatement(createSimpleInsert());
+            mDatabase.setTransactionSuccessful();
+            assertFalse(s.isOpen());
+        } finally {
+            mDatabase.endTransaction();
+        }
+        assertFalse(s.isOpen());
+
+        // Verify that a statement is closed implicitly when the transaction is closed without
+        // being marked successful.  The try-with-resources pattern is not used here.
+        mDatabase.beginTransaction();
+        try {
+            s = mDatabase.createRawStatement(createSimpleInsert());
+        } finally {
+            mDatabase.endTransaction();
+        }
+        assertFalse(s.isOpen());
+    }
+
+    @Test
+    public void testMustBeOpen() {
+        createSimpleDatabase();
+
+        mDatabase.beginTransaction();
+        try {
+            SQLiteRawStatement s = mDatabase.createRawStatement(createSimpleInsert());
+            assertTrue(s.isOpen());
+            s.close();
+            assertFalse(s.isOpen());
+
+            // Verify that a statement cannot be accessed once closed.
+            try {
+                s.getResultColumnCount();
+                fail("accessed closed statement");
+            } catch (AssertionError e) {
+                // Pass on the fail from the try-block before the generic catch below can see it.
+                throw e;
+            } catch (IllegalStateException e) {
+                // There is more than one source of this exception.  Scrape the message and look for
+                // the message from {@link SQLiteRawStatement.throwIfInvalid}.
+                if (!e.getMessage().contains("method called on a closed statement")) {
+                    fail("unexpected IllegalStateException, got " + e);
+                }
+            } catch (Throwable e) {
+                fail("expected IllegalStateException, got " + e);
+            }
+        } finally {
+            mDatabase.endTransaction();
+        }
+    }
+
+    @Test
+    public void testThreadRestrictions() throws Exception {
+        createComplexDatabase();
+
+        final ArrayList<String> errors = new ArrayList<>();
+        errors.add("test failed to run");
+
+        mDatabase.beginTransaction();
+        try {
+            SQLiteRawStatement s = mDatabase.createRawStatement("SELECT i FROM t1");
+
+            Thread peerThread = new Thread(
+                () -> {
+                    try {
+                        s.step();
+                        errors.add("expected IllegalStateException");
+                    } catch (IllegalStateException e) {
+                        // There is more than one source of this exception.  Scrape the message
+                        // and look for the message from {@link SQLiteRawStatement.throwIfInvalid}.
+                        if (e.getMessage().contains("method called on a foreign thread")) {
+                            // The test ran properly.  Remove the default "did-not-run" error.
+                            errors.remove(0);
+                        } else {
+                            errors.add("unexpected IllegalStateException, got " + e);
+                        }
+                    } catch (Throwable e) {
+                        errors.add("expected IllegalStateException, got " + e);
+                    }
+                });
+            peerThread.start();
+            peerThread.join(500L);
+            mDatabase.setTransactionSuccessful();
+        } finally {
+            mDatabase.endTransaction();
+        }
+        if (errors.size() > 0) {
+            fail(errors.get(0));
+        }
+    }
+
+    @Test
+    public void testBlob() {
+        mDatabase.beginTransaction();
+        try {
+            final String query = "CREATE TABLE t1 (i int, b blob)";
+            try (SQLiteRawStatement s = mDatabase.createRawStatement(query)) {
+                assertFalse(s.step());
+            }
+            mDatabase.setTransactionSuccessful();
+        } finally {
+            mDatabase.endTransaction();
+        }
+
+        // Create a the reference copy of a byte array.
+        byte[] src = new byte[32];
+        for (int i = 0; i < src.length; i++) {
+            src[i] = (byte) (i * 3);
+        }
+
+        // Insert data into the table.
+        mDatabase.beginTransaction();
+        try {
+            final String query = "INSERT INTO t1 (i, b) VALUES (?1, ?2)";
+            try (SQLiteRawStatement s = mDatabase.createRawStatement(query)) {
+                // Bind the entire src array
+                s.bindInt(1, 1);
+                s.bindBlob(2, src);
+                s.step();
+                s.reset();
+                // Bind the fragment starting at 4, length 8.
+                s.bindInt(1, 2);
+                s.bindBlob(2, src, 4, 8);
+                s.step();
+                s.reset();
+                // Bind null
+                s.clearBindings();
+                s.bindInt(1, 3);
+                s.step();
+                s.reset();
+            }
+            mDatabase.setTransactionSuccessful();
+        } finally {
+            mDatabase.endTransaction();
+        }
+
+        // Read back data and verify it against the reference copy.
+        mDatabase.beginTransactionReadOnly();
+        try {
+            final String query = "SELECT (b) FROM t1 WHERE i = ?1";
+            try (SQLiteRawStatement s = mDatabase.createRawStatement(query)) {
+                // Fetch the entire reference array.
+                s.bindInt(1, 1);
+                assertTrue(s.step());
+                byte[] a = s.getColumnBlob(0);
+                assertTrue(Arrays.equals(src, a));
+                s.reset();
+
+                // Fetch the fragment starting at 4, length 8.
+                s.bindInt(1, 2);
+                assertTrue(s.step());
+                byte[] c = new byte[src.length];
+                assertEquals(8, s.readColumnBlob(0, c, 0, c.length, 0));
+                assertTrue(Arrays.equals(src, 4, 4+8, c, 0, 0+8));
+                s.reset();
+
+                // Fetch the null.
+                s.bindInt(1, 3);
+                assertTrue(s.step());
+                assertEquals(null, s.getColumnBlob(0));
+                s.reset();
+
+                // Fetch the null and ensure the buffer is not modified.
+                for (int i = 0; i < c.length; i++) c[i] = 0;
+                s.bindInt(1, 3);
+                assertTrue(s.step());
+                assertEquals(0, s.readColumnBlob(0, c, 0, c.length, 0));
+                for (int i = 0; i < c.length; i++) assertEquals(0, c[i]);
+                s.reset();
+            }
+        } finally {
+            mDatabase.endTransaction();
+        }
+
+        // Test NPE detection
+        mDatabase.beginTransaction();
+        try {
+            final String query = "INSERT INTO t1 (i, b) VALUES (?1, ?2)";
+            try (SQLiteRawStatement s = mDatabase.createRawStatement(query)) {
+                s.bindBlob(2, null);
+                fail("expected a NullPointerException");
+            }
+        } catch (NullPointerException e) {
+            // Expected
+        } catch (AssertionError e) {
+            // Pass on the fail from the try-block before the generic catch below can see it.
+            throw e;
+        } finally {
+            mDatabase.endTransaction();
+        }
+    }
+
+    @Test
+    public void testParameterMetadata() {
+        createComplexDatabase();
+
+        final String sql = "INSERT INTO t1 (i, d, t) VALUES (:1, ?2, @FOO)";
+
+        // Start a transaction that allows updates.
+        mDatabase.beginTransaction();
+        try {
+            try (SQLiteRawStatement s = mDatabase.createRawStatement(sql)) {
+                assertEquals(3, s.getParameterCount());
+
+                assertEquals(1, s.getParameterIndex(":1"));
+                assertEquals(2, s.getParameterIndex("?2"));
+                assertEquals(3, s.getParameterIndex("@FOO"));
+                assertEquals(0, s.getParameterIndex("@BAR"));
+
+                assertEquals(":1", s.getParameterName(1));
+                assertEquals("?2", s.getParameterName(2));
+                assertEquals("@FOO", s.getParameterName(3));
+                assertEquals(null, s.getParameterName(4));
+            }
+        } finally {
+            mDatabase.endTransaction();
+        }
+
+        // Start a transaction that allows updates.
+        mDatabase.beginTransaction();
+        try {
+            try (SQLiteRawStatement s = mDatabase.createRawStatement(sql)) {
+                // Error case.  The name is not supposed to be null.
+                assertEquals(0, s.getParameterIndex(null));
+                fail("expected a NullPointerException");
+            }
+        } catch (NullPointerException e) {
+            // Expected
+        } catch (AssertionError e) {
+            // Pass on the fail from the try-block before the generic catch below can see it.
+            throw e;
+        } catch (Throwable e) {
+            fail("expected NullPointerException, got " + e);
+        } finally {
+            mDatabase.endTransaction();
+        }
+    }
+
+    // This test cannot fail, but the log messages report timing for the new SQLiteRawStatement APIs
+    // vs the Cursor APIs.
+    @Test
+    public void testSpeedSimple() {
+        final int size = 100000;
+
+        createComplexDatabase();
+
+        // Populate the database.
+        mDatabase.beginTransaction();
+        try {
+            long start = SystemClock.uptimeMillis();
+            try (var s = mDatabase.createRawStatement(createComplexInsert())) {
+                for (int i = 0; i < size; i++) {
+                    int vi = i * 3;
+                    double vd = i * 2.5;
+                    String vt = String.format("text%02dvalue", i);
+                    s.bindInt(1, vi);
+                    s.bindDouble(2, vd);
+                    s.bindText(3, vt);
+                    boolean r = s.step();
+                    // No row is returned by this query.
+                    assertFalse(r);
+                    s.reset();
+                }
+            }
+            long elapsed = SystemClock.uptimeMillis() - start;
+            Log.i(TAG, "timing simple insert: " + elapsed + "ms");
+            mDatabase.setTransactionSuccessful();
+        } finally {
+            mDatabase.endTransaction();
+        }
+
+        final String query = "SELECT i, d, t FROM t1";
+
+        // Iterate over the database.
+        mDatabase.beginTransactionReadOnly();
+        try {
+            long start = SystemClock.uptimeMillis();
+            int found = 0;
+            try (var s = mDatabase.createRawStatement(query)) {
+                for (int i = 0; s.step(); i++) {
+                    int vi = s.getColumnInt(0);
+                    int expected = i * 3;
+                    assertEquals(expected, vi);
+                    found = i;
+                }
+            }
+            long elapsed = SystemClock.uptimeMillis() - start;
+            Log.i(TAG, "timing statement simple: " + elapsed + "ms");
+            assertEquals(size - 1, found);
+        } finally {
+            mDatabase.endTransaction();
+        }
+
+        // Iterate over the database using cursors.
+        mDatabase.beginTransactionReadOnly();
+        try {
+            long start = SystemClock.uptimeMillis();
+            try (Cursor c = mDatabase.rawQuery(query, null)) {
+                c.moveToFirst();
+                int found = 0;
+                for (int i = 0; i < size; i++) {
+                    int vi = c.getInt(0);
+                    int expected = i * 3;
+                    assertEquals(expected, vi);
+                    c.moveToNext();
+                    found = i;
+                }
+                assertEquals(size - 1, found);
+            }
+            long elapsed = SystemClock.uptimeMillis() - start;
+            Log.i(TAG, "timing cursor simple: " + elapsed + "ms");
+        } finally {
+            mDatabase.endTransaction();
+        }
+    }
+
+    @Test
+    public void testSpeedSingleQuery() {
+        final int size = 1000;
+        final int loops = size;
+
+        createComplexDatabase();
+
+        // Populate the database.
+        mDatabase.beginTransaction();
+        try {
+            try (var s = mDatabase.createRawStatement(createComplexInsert())) {
+                for (int i = 0; i < size; i++) {
+                    int vi = i * 3;
+                    double vd = i * 2.5;
+                    String vt = String.format("text%02dvalue", i);
+                    s.bindInt(1, vi);
+                    s.bindDouble(2, vd);
+                    s.bindText(3, vt);
+                    boolean r = s.step();
+                    // No row is returned by this query.
+                    assertFalse(r);
+                    s.reset();
+                }
+            }
+            mDatabase.setTransactionSuccessful();
+        } finally {
+            mDatabase.endTransaction();
+        }
+
+        final String query = "SELECT i, d, t FROM t1";
+
+        // Iterate over the database.
+        mDatabase.beginTransactionReadOnly();
+        try {
+            long start = SystemClock.uptimeMillis();
+            for (int i = 0; i < loops; i++) {
+                try (var s = mDatabase.createRawStatement(query)) {
+                    assertTrue(s.step());
+                    int vi = s.getColumnInt(0);
+                    int expected = 0;
+                    assertEquals(expected, vi);
+                }
+            }
+            long elapsed = SystemClock.uptimeMillis() - start;
+            Log.i(TAG, "timing statement query: " + elapsed + "ms");
+        } finally {
+            mDatabase.endTransaction();
+        }
+
+        // Iterate over the database using cursors.
+        mDatabase.beginTransactionReadOnly();
+        try {
+            long start = SystemClock.uptimeMillis();
+            for (int i = 0; i < loops; i++) {
+                try (Cursor c = mDatabase.rawQuery(query, null)) {
+                    c.moveToFirst();
+                    int vi = c.getInt(0);
+                    int expected = 0;
+                    assertEquals(expected, vi);
+                }
+            }
+            long elapsed = SystemClock.uptimeMillis() - start;
+            mDatabase.setTransactionSuccessful();
+            Log.i(TAG, "timing cursor query: " + elapsed + "ms");
+        } finally {
+            mDatabase.endTransaction();
+        }
+    }
+
+    private int wideVal(int i, int j) {
+        return i + j;
+    }
+
+    @Test
+    public void testSpeedWideQuery() {
+        final int size = 100000;
+
+        createWideDatabase();
+
+        // Populate the database.
+        mDatabase.beginTransaction();
+        try {
+            try (var s = mDatabase.createRawStatement(createWideInsert())) {
+                for (int i = 0; i < size; i++) {
+                    for (int j = 0; j < 12; j++) {
+                        s.bindInt(j+1, wideVal(i, j));
+                    }
+                    boolean r = s.step();
+                    // No row is returned by this query.
+                    assertFalse(r);
+                    s.reset();
+                }
+            }
+            mDatabase.setTransactionSuccessful();
+        } finally {
+            mDatabase.endTransaction();
+        }
+
+        final String query = "SELECT * FROM t1";
+
+        // Iterate over the database.
+        mDatabase.beginTransactionReadOnly();
+        try {
+            long start = SystemClock.uptimeMillis();
+            try (var s = mDatabase.createRawStatement(query)) {
+                for (int i = 0; i < size; i++) {
+                    assertTrue(s.step());
+                    for (int j = 0; j < 12; j++) {
+                        assertEquals(s.getColumnInt(j), wideVal(i, j));
+                    }
+                }
+            }
+            long elapsed = SystemClock.uptimeMillis() - start;
+            Log.i(TAG, "timing statement wide: " + elapsed + "ms");
+        } finally {
+            mDatabase.endTransaction();
+        }
+
+        // Iterate over the database using cursors.
+        mDatabase.beginTransactionReadOnly();
+        try {
+            long start = SystemClock.uptimeMillis();
+            try (Cursor c = mDatabase.rawQuery(query, null)) {
+                c.moveToFirst();
+                for (int i = 0; i < size; i++) {
+                    for (int j = 0; j < 12; j++) {
+                        assertEquals(c.getInt(j), wideVal(i, j));
+                    }
+                    c.moveToNext();
+                }
+            }
+            long elapsed = SystemClock.uptimeMillis() - start;
+            mDatabase.setTransactionSuccessful();
+            Log.i(TAG, "timing cursor wide: " + elapsed + "ms");
+        } finally {
+            mDatabase.endTransaction();
+        }
+    }
+
+
+    @Test
+    public void testSpeedRecursive() {
+        createComplexDatabase();
+
+        final String query = "WITH RECURSIVE t1(i) AS "
+                             + "(SELECT 123 UNION ALL SELECT i+1 FROM t1) "
+                             + "SELECT * from t1 LIMIT 1000000";
+
+        mDatabase.beginTransaction();
+        try {
+            long start = SystemClock.uptimeMillis();
+            try (SQLiteRawStatement s = mDatabase.createRawStatement(query)) {
+                while (s.step()) {
+                    s.getColumnInt(0);
+                }
+            }
+            long elapsed = SystemClock.uptimeMillis() - start;
+            Log.i(TAG, "timing statement recursive: " + elapsed + "ms");
+        } finally {
+            mDatabase.endTransaction();
+        }
+
+        mDatabase.beginTransaction();
+        try {
+            long start = SystemClock.uptimeMillis();
+            try (Cursor c = mDatabase.rawQuery(query, null)) {
+                c.moveToFirst();
+                while (c.moveToNext()) {
+                    c.getInt(0);
+                }
+            }
+            long elapsed = SystemClock.uptimeMillis() - start;
+            Log.i(TAG, "timing cursor recursive: " + elapsed + "ms");
+        } finally {
+            mDatabase.endTransaction();
+        }
+    }
+
+    @Test
+    public void testUnicode() {
+        // Create the t1 table and put some data in it.
+        mDatabase.beginTransaction();
+        try {
+            mDatabase.execSQL("CREATE TABLE t1 (i int, j int);");
+            mDatabase.execSQL("INSERT INTO t1 (i, j) VALUES (2, 20)");
+            mDatabase.execSQL("INSERT INTO t1 (i, j) VALUES (3, 30)");
+            mDatabase.setTransactionSuccessful();
+        } finally {
+            mDatabase.endTransaction();
+        }
+
+        // Exploding Head Emoji
+        final String head = ":\u1F92F";
+        // Heart Eyes Cat Emoji
+        final String cat = "\u1F63B";
+
+        final String sql = "SELECT i AS " + cat + " FROM t1 WHERE j = " + head;
+
+        mDatabase.beginTransactionReadOnly();
+        try (SQLiteRawStatement s = mDatabase.createRawStatement(sql)) {
+            assertEquals(1, s.getParameterIndex(head));
+            assertEquals(head, s.getParameterName(1));
+            s.bindInt(1, 20);
+            assertTrue(s.step());
+            assertEquals(2, s.getColumnInt(0));
+            assertEquals(cat, s.getColumnName(0));
+        } finally {
+            mDatabase.endTransaction();
+        }
+    }
+}
diff --git a/core/tests/coretests/src/android/flags/FeatureFlagsTest.java b/core/tests/coretests/src/android/flags/FeatureFlagsTest.java
new file mode 100644
index 0000000..3fc9439
--- /dev/null
+++ b/core/tests/coretests/src/android/flags/FeatureFlagsTest.java
@@ -0,0 +1,155 @@
+/*
+ * 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.
+ */
+package android.flags;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+
+@SmallTest
+@Presubmit
+public class FeatureFlagsTest {
+
+    IFeatureFlagsFake mIFeatureFlagsFake = new IFeatureFlagsFake();
+    FeatureFlags mFeatureFlags = new FeatureFlags(mIFeatureFlagsFake);
+
+    @Before
+    public void setup() {
+        FeatureFlags.setInstance(mFeatureFlags);
+    }
+
+    @Test
+    public void testFusedOff_Disabled() {
+        FusedOffFlag flag = FeatureFlags.fusedOffFlag("test", "a");
+        assertThat(mFeatureFlags.isEnabled(flag)).isFalse();
+    }
+
+    @Test
+    public void testFusedOn_Enabled() {
+        FusedOnFlag flag = FeatureFlags.fusedOnFlag("test", "a");
+        assertThat(mFeatureFlags.isEnabled(flag)).isTrue();
+    }
+
+    @Test
+    public void testBooleanFlag_DefaultDisabled() {
+        BooleanFlag flag = FeatureFlags.booleanFlag("test", "a", false);
+        assertThat(mFeatureFlags.isEnabled(flag)).isFalse();
+    }
+
+    @Test
+    public void testBooleanFlag_DefaultEnabled() {
+        BooleanFlag flag = FeatureFlags.booleanFlag("test", "a", true);
+        assertThat(mFeatureFlags.isEnabled(flag)).isTrue();
+    }
+
+    @Test
+    public void testDynamicBooleanFlag_DefaultDisabled() {
+        DynamicBooleanFlag flag = FeatureFlags.dynamicBooleanFlag("test", "a", false);
+        assertThat(mFeatureFlags.isCurrentlyEnabled(flag)).isFalse();
+    }
+
+    @Test
+    public void testDynamicBooleanFlag_DefaultEnabled() {
+        DynamicBooleanFlag flag = FeatureFlags.dynamicBooleanFlag("test", "a", true);
+        assertThat(mFeatureFlags.isCurrentlyEnabled(flag)).isTrue();
+    }
+
+    @Test
+    public void testBooleanFlag_OverrideBeforeRead() {
+        BooleanFlag flag = FeatureFlags.booleanFlag("test", "a", false);
+        SyncableFlag syncableFlag = new SyncableFlag(
+                flag.getNamespace(), flag.getName(), "true", false);
+
+        mIFeatureFlagsFake.setFlagOverrides(List.of(syncableFlag));
+
+        assertThat(mFeatureFlags.isEnabled(flag)).isTrue();
+    }
+
+    @Test
+    public void testFusedOffFlag_OverrideHasNoEffect() {
+        FusedOffFlag flag = FeatureFlags.fusedOffFlag("test", "a");
+        SyncableFlag syncableFlag = new SyncableFlag(
+                flag.getNamespace(), flag.getName(), "true", false);
+
+        mIFeatureFlagsFake.setFlagOverrides(List.of(syncableFlag));
+
+        assertThat(mFeatureFlags.isEnabled(flag)).isFalse();
+    }
+
+    @Test
+    public void testFusedOnFlag_OverrideHasNoEffect() {
+        FusedOnFlag flag = FeatureFlags.fusedOnFlag("test", "a");
+        SyncableFlag syncableFlag = new SyncableFlag(
+                flag.getNamespace(), flag.getName(), "false", false);
+
+        mIFeatureFlagsFake.setFlagOverrides(List.of(syncableFlag));
+
+        assertThat(mFeatureFlags.isEnabled(flag)).isTrue();
+    }
+
+    @Test
+    public void testDynamicFlag_OverrideBeforeRead() {
+        DynamicBooleanFlag flag = FeatureFlags.dynamicBooleanFlag("test", "a", false);
+        SyncableFlag syncableFlag = new SyncableFlag(
+                flag.getNamespace(), flag.getName(), "true", true);
+
+        mIFeatureFlagsFake.setFlagOverrides(List.of(syncableFlag));
+
+        // Changes to true
+        assertThat(mFeatureFlags.isCurrentlyEnabled(flag)).isTrue();
+    }
+
+    @Test
+    public void testDynamicFlag_OverrideAfterRead() {
+        DynamicBooleanFlag flag = FeatureFlags.dynamicBooleanFlag("test", "a", false);
+        SyncableFlag syncableFlag = new SyncableFlag(
+                flag.getNamespace(), flag.getName(), "true", true);
+
+        // Starts false
+        assertThat(mFeatureFlags.isCurrentlyEnabled(flag)).isFalse();
+
+        mIFeatureFlagsFake.setFlagOverrides(List.of(syncableFlag));
+
+        // Changes to true
+        assertThat(mFeatureFlags.isCurrentlyEnabled(flag)).isTrue();
+    }
+
+    @Test
+    public void testDynamicFlag_FiresListener() {
+        DynamicBooleanFlag flag = FeatureFlags.dynamicBooleanFlag("test", "a", false);
+        AtomicBoolean called = new AtomicBoolean(false);
+        FeatureFlags.ChangeListener listener = flag1 -> called.set(true);
+
+        mFeatureFlags.addChangeListener(listener);
+
+        SyncableFlag syncableFlag = new SyncableFlag(
+                flag.getNamespace(), flag.getName(), flag.getDefault().toString(), true);
+
+        mIFeatureFlagsFake.setFlagOverrides(List.of(syncableFlag));
+
+        // Fires listener.
+        assertThat(called.get()).isTrue();
+    }
+}
diff --git a/core/tests/coretests/src/android/flags/IFeatureFlagsFake.java b/core/tests/coretests/src/android/flags/IFeatureFlagsFake.java
new file mode 100644
index 0000000..bc5d8aa
--- /dev/null
+++ b/core/tests/coretests/src/android/flags/IFeatureFlagsFake.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.flags;
+
+import android.os.IBinder;
+import android.os.RemoteException;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+class IFeatureFlagsFake implements IFeatureFlags {
+
+    private final Set<IFeatureFlagsCallback> mCallbacks = new HashSet<>();
+
+    List<SyncableFlag> mOverrides;
+
+    @Override
+    public IBinder asBinder() {
+        return null;
+    }
+
+    @Override
+    public List<SyncableFlag> syncFlags(List<SyncableFlag> flagList) {
+        return mOverrides == null ? flagList : mOverrides;
+    }
+
+    @Override
+    public List<SyncableFlag> queryFlags(List<SyncableFlag> flagList) {
+        return mOverrides == null ? flagList : mOverrides;    }
+
+    @Override
+    public void overrideFlag(SyncableFlag syncableFlag) {
+        SyncableFlag match = findFlag(syncableFlag);
+        if (match != null) {
+            mOverrides.remove(match);
+        }
+
+        mOverrides.add(syncableFlag);
+
+        for (IFeatureFlagsCallback cb : mCallbacks) {
+            try {
+                cb.onFlagChange(syncableFlag);
+            } catch (RemoteException e) {
+                // does not happen in fakes.
+            }
+        }
+    }
+
+    @Override
+    public void resetFlag(SyncableFlag syncableFlag) {
+        SyncableFlag match = findFlag(syncableFlag);
+        if (match != null) {
+            mOverrides.remove(match);
+        }
+
+        for (IFeatureFlagsCallback cb : mCallbacks) {
+            try {
+                cb.onFlagChange(syncableFlag);
+            } catch (RemoteException e) {
+                // does not happen in fakes.
+            }
+        }
+    }
+
+    private SyncableFlag findFlag(SyncableFlag syncableFlag) {
+        SyncableFlag match = null;
+        for (SyncableFlag sf : mOverrides) {
+            if (sf.getName().equals(syncableFlag.getName())
+                    && sf.getNamespace().equals(syncableFlag.getNamespace())) {
+                match = sf;
+                break;
+            }
+        }
+
+        return match;
+    }
+    @Override
+    public void registerCallback(IFeatureFlagsCallback callback) {
+        mCallbacks.add(callback);
+    }
+
+    @Override
+    public void unregisterCallback(IFeatureFlagsCallback callback) {
+        mCallbacks.remove(callback);
+    }
+
+    public void setFlagOverrides(List<SyncableFlag> flagList) {
+        mOverrides = flagList;
+        for (SyncableFlag sf : flagList) {
+            for (IFeatureFlagsCallback cb : mCallbacks) {
+                try {
+                    cb.onFlagChange(sf);
+                } catch (RemoteException e) {
+                    // does not happen in fakes.
+                }
+            }
+        }
+    }
+}
diff --git a/core/tests/coretests/src/android/hardware/input/InputDeviceBatteryListenerTest.kt b/core/tests/coretests/src/android/hardware/input/InputDeviceBatteryListenerTest.kt
deleted file mode 100644
index fdcc7c9..0000000
--- a/core/tests/coretests/src/android/hardware/input/InputDeviceBatteryListenerTest.kt
+++ /dev/null
@@ -1,243 +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 android.hardware.input
-
-import android.content.Context
-import android.content.ContextWrapper
-import android.hardware.BatteryState
-import android.os.Handler
-import android.os.HandlerExecutor
-import android.os.test.TestLooper
-import android.platform.test.annotations.Presubmit
-import androidx.test.core.app.ApplicationProvider
-import com.android.server.testutils.any
-import java.util.concurrent.Executor
-import kotlin.test.assertEquals
-import kotlin.test.assertNotNull
-import kotlin.test.assertTrue
-import kotlin.test.fail
-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
-import org.mockito.Mockito
-import org.mockito.Mockito.anyInt
-import org.mockito.Mockito.doAnswer
-import org.mockito.Mockito.`when`
-import org.mockito.junit.MockitoJUnit
-import org.mockito.junit.MockitoJUnitRunner
-
-/**
- * Tests for [InputManager.InputDeviceBatteryListener].
- *
- * Build/Install/Run:
- * atest FrameworksCoreTests:InputDeviceBatteryListenerTest
- */
-@Presubmit
-@RunWith(MockitoJUnitRunner::class)
-class InputDeviceBatteryListenerTest {
-    @get:Rule
-    val rule = MockitoJUnit.rule()!!
-
-    private lateinit var testLooper: TestLooper
-    private var registeredListener: IInputDeviceBatteryListener? = null
-    private val monitoredDevices = mutableListOf<Int>()
-    private lateinit var executor: Executor
-    private lateinit var context: Context
-    private lateinit var inputManager: InputManager
-
-    @Mock
-    private lateinit var iInputManagerMock: IInputManager
-
-    @Before
-    fun setUp() {
-        context = Mockito.spy(ContextWrapper(ApplicationProvider.getApplicationContext()))
-        testLooper = TestLooper()
-        executor = HandlerExecutor(Handler(testLooper.looper))
-        registeredListener = null
-        monitoredDevices.clear()
-        InputManagerGlobal.resetInstance(iInputManagerMock)
-        inputManager = InputManager(context)
-        `when`(context.getSystemService(Mockito.eq(Context.INPUT_SERVICE)))
-                .thenReturn(inputManager)
-
-        // Handle battery listener registration.
-        doAnswer {
-            val deviceId = it.getArgument(0) as Int
-            val listener = it.getArgument(1) as IInputDeviceBatteryListener
-            if (registeredListener != null &&
-                    registeredListener!!.asBinder() != listener.asBinder()) {
-                // There can only be one registered battery listener per process.
-                fail("Trying to register a new listener when one already exists")
-            }
-            if (monitoredDevices.contains(deviceId)) {
-                fail("Trying to start monitoring a device that was already being monitored")
-            }
-            monitoredDevices.add(deviceId)
-            registeredListener = listener
-            null
-        }.`when`(iInputManagerMock).registerBatteryListener(anyInt(), any())
-
-        // Handle battery listener being unregistered.
-        doAnswer {
-            val deviceId = it.getArgument(0) as Int
-            val listener = it.getArgument(1) as IInputDeviceBatteryListener
-            if (registeredListener == null ||
-                    registeredListener!!.asBinder() != listener.asBinder()) {
-                fail("Trying to unregister a listener that is not registered")
-            }
-            if (!monitoredDevices.remove(deviceId)) {
-                fail("Trying to stop monitoring a device that is not being monitored")
-            }
-            if (monitoredDevices.isEmpty()) {
-                registeredListener = null
-            }
-        }.`when`(iInputManagerMock).unregisterBatteryListener(anyInt(), any())
-    }
-
-    @After
-    fun tearDown() {
-        InputManagerGlobal.clearInstance()
-    }
-
-    private fun notifyBatteryStateChanged(
-        deviceId: Int,
-        isPresent: Boolean = true,
-        status: Int = BatteryState.STATUS_FULL,
-        capacity: Float = 1.0f,
-        eventTime: Long = 12345L
-    ) {
-        registeredListener!!.onBatteryStateChanged(IInputDeviceBatteryState().apply {
-            this.deviceId = deviceId
-            this.updateTime = eventTime
-            this.isPresent = isPresent
-            this.status = status
-            this.capacity = capacity
-        })
-    }
-
-    @Test
-    fun testListenerIsNotifiedCorrectly() {
-        var callbackCount = 0
-
-        // Add a battery listener to monitor battery changes.
-        inputManager.addInputDeviceBatteryListener(1 /*deviceId*/, executor) {
-                deviceId: Int, eventTime: Long, batteryState: BatteryState ->
-            callbackCount++
-            assertEquals(1, deviceId)
-            assertEquals(true, batteryState.isPresent)
-            assertEquals(BatteryState.STATUS_DISCHARGING, batteryState.status)
-            assertEquals(0.5f, batteryState.capacity)
-            assertEquals(8675309L, eventTime)
-        }
-
-        // Adding the listener should register the callback with InputManagerService.
-        assertNotNull(registeredListener)
-        assertTrue(monitoredDevices.contains(1))
-
-        // Notifying battery change for a different device should not trigger the listener.
-        notifyBatteryStateChanged(deviceId = 2)
-        testLooper.dispatchAll()
-        assertEquals(0, callbackCount)
-
-        // Notifying battery change for the registered device will notify the listener.
-        notifyBatteryStateChanged(1 /*deviceId*/, true /*isPresent*/,
-            BatteryState.STATUS_DISCHARGING, 0.5f /*capacity*/, 8675309L /*eventTime*/)
-        testLooper.dispatchNext()
-        assertEquals(1, callbackCount)
-    }
-
-    @Test
-    fun testMultipleListeners() {
-        // Set up two callbacks.
-        var callbackCount1 = 0
-        var callbackCount2 = 0
-        val callback1 = InputManager.InputDeviceBatteryListener { _, _, _ -> callbackCount1++ }
-        val callback2 = InputManager.InputDeviceBatteryListener { _, _, _ -> callbackCount2++ }
-
-        // Monitor battery changes for three devices. The first callback monitors devices 1 and 3,
-        // while the second callback monitors devices 2 and 3.
-        inputManager.addInputDeviceBatteryListener(1 /*deviceId*/, executor, callback1)
-        assertEquals(1, monitoredDevices.size)
-        inputManager.addInputDeviceBatteryListener(2 /*deviceId*/, executor, callback2)
-        assertEquals(2, monitoredDevices.size)
-        inputManager.addInputDeviceBatteryListener(3 /*deviceId*/, executor, callback1)
-        assertEquals(3, monitoredDevices.size)
-        inputManager.addInputDeviceBatteryListener(3 /*deviceId*/, executor, callback2)
-        assertEquals(3, monitoredDevices.size)
-
-        // Notifying battery change for each of the devices should trigger the registered callbacks.
-        notifyBatteryStateChanged(deviceId = 1)
-        testLooper.dispatchNext()
-        assertEquals(1, callbackCount1)
-        assertEquals(0, callbackCount2)
-
-        notifyBatteryStateChanged(deviceId = 2)
-        testLooper.dispatchNext()
-        assertEquals(1, callbackCount1)
-        assertEquals(1, callbackCount2)
-
-        notifyBatteryStateChanged(deviceId = 3)
-        testLooper.dispatchNext()
-        testLooper.dispatchNext()
-        assertEquals(2, callbackCount1)
-        assertEquals(2, callbackCount2)
-
-        // Stop monitoring devices 1 and 2.
-        inputManager.removeInputDeviceBatteryListener(1 /*deviceId*/, callback1)
-        assertEquals(2, monitoredDevices.size)
-        inputManager.removeInputDeviceBatteryListener(2 /*deviceId*/, callback2)
-        assertEquals(1, monitoredDevices.size)
-
-        // Ensure device 3 continues to be monitored.
-        notifyBatteryStateChanged(deviceId = 3)
-        testLooper.dispatchNext()
-        testLooper.dispatchNext()
-        assertEquals(3, callbackCount1)
-        assertEquals(3, callbackCount2)
-
-        // Stop monitoring all devices.
-        inputManager.removeInputDeviceBatteryListener(3 /*deviceId*/, callback1)
-        assertEquals(1, monitoredDevices.size)
-        inputManager.removeInputDeviceBatteryListener(3 /*deviceId*/, callback2)
-        assertEquals(0, monitoredDevices.size)
-    }
-
-    @Test
-    fun testAdditionalListenersNotifiedImmediately() {
-        var callbackCount1 = 0
-        var callbackCount2 = 0
-        val callback1 = InputManager.InputDeviceBatteryListener { _, _, _ -> callbackCount1++ }
-        val callback2 = InputManager.InputDeviceBatteryListener { _, _, _ -> callbackCount2++ }
-
-        // Add a battery listener and send the latest battery state.
-        inputManager.addInputDeviceBatteryListener(1 /*deviceId*/, executor, callback1)
-        assertEquals(1, monitoredDevices.size)
-        notifyBatteryStateChanged(deviceId = 1)
-        testLooper.dispatchNext()
-        assertEquals(1, callbackCount1)
-
-        // Add a second listener for the same device that already has the latest battery state.
-        inputManager.addInputDeviceBatteryListener(1 /*deviceId*/, executor, callback2)
-        assertEquals(1, monitoredDevices.size)
-
-        // Ensure that this listener is notified immediately.
-        testLooper.dispatchNext()
-        assertEquals(1, callbackCount2)
-    }
-}
diff --git a/core/tests/coretests/src/android/hardware/input/InputDeviceLightsManagerTest.java b/core/tests/coretests/src/android/hardware/input/InputDeviceLightsManagerTest.java
deleted file mode 100644
index 1e505ab..0000000
--- a/core/tests/coretests/src/android/hardware/input/InputDeviceLightsManagerTest.java
+++ /dev/null
@@ -1,286 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.hardware.input;
-
-import static android.hardware.lights.LightsRequest.Builder;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static junit.framework.TestCase.assertEquals;
-import static junit.framework.TestCase.assertNotNull;
-
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-import android.content.ContextWrapper;
-import android.hardware.lights.Light;
-import android.hardware.lights.LightState;
-import android.hardware.lights.LightsManager;
-import android.hardware.lights.LightsRequest;
-import android.os.IBinder;
-import android.platform.test.annotations.Presubmit;
-import android.util.ArrayMap;
-import android.view.InputDevice;
-
-import androidx.test.InstrumentationRegistry;
-
-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;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoJUnitRunner;
-import org.mockito.junit.MockitoRule;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-/**
- * Tests for {@link InputDeviceLightsManager}.
- *
- * Build/Install/Run:
- * atest FrameworksCoreTests:InputDeviceLightsManagerTest
- */
-@Presubmit
-@RunWith(MockitoJUnitRunner.class)
-public class InputDeviceLightsManagerTest {
-    private static final String TAG = "InputDeviceLightsManagerTest";
-
-    private static final int DEVICE_ID = 1000;
-    private static final int PLAYER_ID = 3;
-
-    @Rule public final MockitoRule mockito = MockitoJUnit.rule();
-
-    private InputManager mInputManager;
-
-    @Mock private IInputManager mIInputManagerMock;
-
-    @Before
-    public void setUp() throws Exception {
-        final Context context = spy(new ContextWrapper(InstrumentationRegistry.getContext()));
-        when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{DEVICE_ID});
-
-        when(mIInputManagerMock.getInputDevice(eq(DEVICE_ID))).thenReturn(
-                createInputDevice(DEVICE_ID));
-
-        InputManagerGlobal.resetInstance(mIInputManagerMock);
-        mInputManager = new InputManager(context);
-        when(context.getSystemService(eq(Context.INPUT_SERVICE))).thenReturn(mInputManager);
-
-        ArrayMap<Integer, LightState> lightStatesById = new ArrayMap<>();
-        doAnswer(invocation -> {
-            final int[] lightIds = (int[]) invocation.getArguments()[1];
-            final LightState[] lightStates =
-                    (LightState[]) invocation.getArguments()[2];
-            for (int i = 0; i < lightIds.length; i++) {
-                lightStatesById.put(lightIds[i], lightStates[i]);
-            }
-            return null;
-        }).when(mIInputManagerMock).setLightStates(eq(DEVICE_ID),
-                any(int[].class), any(LightState[].class), any(IBinder.class));
-
-        doAnswer(invocation -> {
-            int lightId = (int) invocation.getArguments()[1];
-            if (lightStatesById.containsKey(lightId)) {
-                return lightStatesById.get(lightId);
-            }
-            return new LightState(0);
-        }).when(mIInputManagerMock).getLightState(eq(DEVICE_ID), anyInt());
-    }
-
-    @After
-    public void tearDown() {
-        InputManagerGlobal.clearInstance();
-    }
-
-    private InputDevice createInputDevice(int id) {
-        return new InputDevice.Builder()
-                .setId(id)
-                .setName("Test Device " + id)
-                .build();
-    }
-
-    private void mockLights(Light[] lights) throws Exception {
-        // Mock the Lights returned form InputManagerService
-        when(mIInputManagerMock.getLights(eq(DEVICE_ID))).thenReturn(
-                new ArrayList(Arrays.asList(lights)));
-    }
-
-    @Test
-    public void testGetInputDeviceLights() throws Exception {
-        InputDevice device = mInputManager.getInputDevice(DEVICE_ID);
-        assertNotNull(device);
-
-        Light[] mockedLights = {
-            new Light(1 /* id */, "Light1", 0 /* ordinal */, Light.LIGHT_TYPE_INPUT,
-                        Light.LIGHT_CAPABILITY_BRIGHTNESS),
-            new Light(2 /* id */, "Light2", 0 /* ordinal */, Light.LIGHT_TYPE_INPUT,
-                        Light.LIGHT_CAPABILITY_COLOR_RGB),
-            new Light(3 /* id */, "Light3", 0 /* ordinal */, Light.LIGHT_TYPE_INPUT,
-                        0 /* capabilities */)
-        };
-        mockLights(mockedLights);
-
-        LightsManager lightsManager = device.getLightsManager();
-        List<Light> lights = lightsManager.getLights();
-        verify(mIInputManagerMock).getLights(eq(DEVICE_ID));
-        assertEquals(lights, Arrays.asList(mockedLights));
-    }
-
-    @Test
-    public void testControlMultipleLights() throws Exception {
-        InputDevice device = mInputManager.getInputDevice(DEVICE_ID);
-        assertNotNull(device);
-
-        Light[] mockedLights = {
-            new Light(1 /* id */, "Light1", 0 /* ordinal */, Light.LIGHT_TYPE_INPUT,
-                        Light.LIGHT_CAPABILITY_COLOR_RGB),
-            new Light(2 /* id */, "Light2", 0 /* ordinal */, Light.LIGHT_TYPE_INPUT,
-                        Light.LIGHT_CAPABILITY_COLOR_RGB),
-            new Light(3 /* id */, "Light3", 0 /* ordinal */, Light.LIGHT_TYPE_INPUT,
-                        Light.LIGHT_CAPABILITY_COLOR_RGB),
-            new Light(4 /* id */, "Light4", 0 /* ordinal */, Light.LIGHT_TYPE_INPUT,
-                        Light.LIGHT_CAPABILITY_COLOR_RGB)
-        };
-        mockLights(mockedLights);
-
-        LightsManager lightsManager = device.getLightsManager();
-        List<Light> lightList = lightsManager.getLights();
-        LightState[] states = new LightState[]{new LightState(0xf1), new LightState(0xf2),
-                new LightState(0xf3)};
-        // Open a session to request turn 3/4 lights on:
-        LightsManager.LightsSession session = lightsManager.openSession();
-        session.requestLights(new Builder()
-                .addLight(lightsManager.getLights().get(0), states[0])
-                .addLight(lightsManager.getLights().get(1), states[1])
-                .addLight(lightsManager.getLights().get(2), states[2])
-                .build());
-        IBinder token = session.getToken();
-
-        verify(mIInputManagerMock).openLightSession(eq(DEVICE_ID),
-                any(String.class), eq(token));
-        verify(mIInputManagerMock).setLightStates(eq(DEVICE_ID), eq(new int[]{1, 2, 3}),
-                eq(states), eq(token));
-
-        // Then all 3 should turn on.
-        assertThat(lightsManager.getLightState(lightsManager.getLights().get(0)).getColor())
-                .isEqualTo(0xf1);
-        assertThat(lightsManager.getLightState(lightsManager.getLights().get(1)).getColor())
-                .isEqualTo(0xf2);
-        assertThat(lightsManager.getLightState(lightsManager.getLights().get(2)).getColor())
-                .isEqualTo(0xf3);
-
-        // And the 4th should remain off.
-        assertThat(lightsManager.getLightState(lightsManager.getLights().get(3)).getColor())
-                .isEqualTo(0x00);
-
-        // close session
-        session.close();
-        verify(mIInputManagerMock).closeLightSession(eq(DEVICE_ID), eq(token));
-    }
-
-    @Test
-    public void testControlPlayerIdLight() throws Exception {
-        InputDevice device = mInputManager.getInputDevice(DEVICE_ID);
-        assertNotNull(device);
-
-        Light[] mockedLights = {
-                new Light(1 /* id */, "Light1", 0 /* ordinal */, Light.LIGHT_TYPE_PLAYER_ID,
-                            0 /* capabilities */),
-                new Light(2 /* id */, "Light2", 0 /* ordinal */, Light.LIGHT_TYPE_INPUT,
-                            Light.LIGHT_CAPABILITY_COLOR_RGB | Light.LIGHT_CAPABILITY_BRIGHTNESS),
-                new Light(3 /* id */, "Light3", 0 /* ordinal */, Light.LIGHT_TYPE_INPUT,
-                            Light.LIGHT_CAPABILITY_BRIGHTNESS)
-        };
-        mockLights(mockedLights);
-
-        LightsManager lightsManager = device.getLightsManager();
-        List<Light> lightList = lightsManager.getLights();
-        LightState[] states = new LightState[]{new LightState(0xf1, PLAYER_ID)};
-        // Open a session to request set Player ID light:
-        LightsManager.LightsSession session = lightsManager.openSession();
-        session.requestLights(new Builder()
-                .addLight(lightsManager.getLights().get(0), states[0])
-                .build());
-        IBinder token = session.getToken();
-
-        verify(mIInputManagerMock).openLightSession(eq(DEVICE_ID),
-                any(String.class), eq(token));
-        verify(mIInputManagerMock).setLightStates(eq(DEVICE_ID), eq(new int[]{1}),
-                eq(states), eq(token));
-
-        // Verify the light state
-        assertThat(lightsManager.getLightState(lightsManager.getLights().get(0)).getColor())
-                .isEqualTo(0xf1);
-        assertThat(lightsManager.getLightState(lightsManager.getLights().get(0)).getPlayerId())
-                .isEqualTo(PLAYER_ID);
-
-        // close session
-        session.close();
-        verify(mIInputManagerMock).closeLightSession(eq(DEVICE_ID), eq(token));
-    }
-
-    @Test
-    public void testLightCapabilities() throws Exception {
-        Light light = new Light(1 /* id */, "Light1", 0 /* ordinal */, Light.LIGHT_TYPE_INPUT,
-                Light.LIGHT_CAPABILITY_COLOR_RGB | Light.LIGHT_CAPABILITY_BRIGHTNESS);
-        assertThat(light.getType()).isEqualTo(Light.LIGHT_TYPE_INPUT);
-        assertThat(light.getCapabilities()).isEqualTo(Light.LIGHT_CAPABILITY_COLOR_RGB
-                | Light.LIGHT_CAPABILITY_BRIGHTNESS);
-        assertTrue(light.hasBrightnessControl());
-        assertTrue(light.hasRgbControl());
-    }
-
-    @Test
-    public void testLightsRequest() throws Exception {
-        Light light1 = new Light(1 /* id */, "Light1", 0 /* ordinal */, Light.LIGHT_TYPE_INPUT,
-                0 /* capabilities */);
-        Light light2 = new Light(2 /* id */, "Light2", 0 /* ordinal */, Light.LIGHT_TYPE_PLAYER_ID,
-                0 /* capabilities */);
-        LightState state1 = new LightState(0xf1);
-        LightState state2 = new LightState(0xf2, PLAYER_ID);
-        LightsRequest request = new Builder().addLight(light1, state1)
-                .addLight(light2, state2).build();
-
-        // Covers the LightsRequest.getLights
-        assertThat(request.getLights().size()).isEqualTo(2);
-        assertThat(request.getLights().get(0)).isEqualTo(1);
-        assertThat(request.getLights().get(1)).isEqualTo(2);
-
-        // Covers the LightsRequest.getLightStates
-        assertThat(request.getLightStates().size()).isEqualTo(2);
-        assertThat(request.getLightStates().get(0)).isEqualTo(state1);
-        assertThat(request.getLightStates().get(1)).isEqualTo(state2);
-
-        // Covers the LightsRequest.getLightsAndStates
-        assertThat(request.getLightsAndStates().size()).isEqualTo(2);
-        assertThat(request.getLightsAndStates().containsKey(light1)).isTrue();
-        assertThat(request.getLightsAndStates().get(light1)).isEqualTo(state1);
-        assertThat(request.getLightsAndStates().get(light2)).isEqualTo(state2);
-    }
-
-}
diff --git a/core/tests/coretests/src/android/hardware/input/InputDeviceSensorManagerTest.java b/core/tests/coretests/src/android/hardware/input/InputDeviceSensorManagerTest.java
deleted file mode 100644
index b33cfdd..0000000
--- a/core/tests/coretests/src/android/hardware/input/InputDeviceSensorManagerTest.java
+++ /dev/null
@@ -1,242 +0,0 @@
-/*
- * 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.
- */
-
-package android.hardware.input;
-
-import static junit.framework.TestCase.assertEquals;
-import static junit.framework.TestCase.assertNotNull;
-import static junit.framework.TestCase.assertTrue;
-import static junit.framework.TestCase.fail;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-import android.content.ContextWrapper;
-import android.hardware.Sensor;
-import android.hardware.SensorEvent;
-import android.hardware.SensorEventListener;
-import android.hardware.SensorManager;
-import android.platform.test.annotations.Presubmit;
-import android.view.InputDevice;
-
-import androidx.test.InstrumentationRegistry;
-
-import com.android.internal.annotations.GuardedBy;
-
-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;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoJUnitRunner;
-import org.mockito.junit.MockitoRule;
-
-import java.util.List;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Tests for {@link InputDeviceSensorManager}.
- *
- * Build/Install/Run:
- * atest FrameworksCoreTests:InputDeviceSensorManagerTest
- */
-@Presubmit
-@RunWith(MockitoJUnitRunner.class)
-public class InputDeviceSensorManagerTest {
-    private static final String TAG = "InputDeviceSensorManagerTest";
-
-    private static final int DEVICE_ID = 1000;
-
-    @Rule public final MockitoRule mockito = MockitoJUnit.rule();
-
-    private InputManager mInputManager;
-    private IInputSensorEventListener mIInputSensorEventListener;
-    private final Object mLock = new Object();
-
-    @Mock private IInputManager mIInputManagerMock;
-
-    @Before
-    public void setUp() throws Exception {
-        final Context context = spy(new ContextWrapper(InstrumentationRegistry.getContext()));
-        InputManagerGlobal.resetInstance(mIInputManagerMock);
-        mInputManager = new InputManager(context);
-        when(context.getSystemService(eq(Context.INPUT_SERVICE))).thenReturn(mInputManager);
-
-        when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{DEVICE_ID});
-
-        when(mIInputManagerMock.getInputDevice(eq(DEVICE_ID))).thenReturn(
-                createInputDeviceWithSensor(DEVICE_ID));
-
-        when(mIInputManagerMock.getSensorList(eq(DEVICE_ID))).thenReturn(new InputSensorInfo[] {
-                createInputSensorInfo(DEVICE_ID, Sensor.TYPE_ACCELEROMETER),
-                createInputSensorInfo(DEVICE_ID, Sensor.TYPE_GYROSCOPE)});
-
-        when(mIInputManagerMock.enableSensor(eq(DEVICE_ID), anyInt(), anyInt(), anyInt()))
-                .thenReturn(true);
-
-        when(mIInputManagerMock.registerSensorListener(any())).thenReturn(true);
-    }
-
-    @After
-    public void tearDown() {
-        InputManagerGlobal.clearInstance();
-    }
-
-    private class InputTestSensorEventListener implements SensorEventListener {
-        @GuardedBy("mLock")
-        private final BlockingQueue<SensorEvent> mEvents = new LinkedBlockingQueue<>();
-        InputTestSensorEventListener() {
-            super();
-        }
-
-        public SensorEvent waitForSensorEvent() {
-            try {
-                return mEvents.poll(5, TimeUnit.SECONDS);
-            } catch (InterruptedException e) {
-                fail("unexpectedly interrupted while waiting for SensorEvent");
-                return null;
-            }
-        }
-
-        @Override
-        public void onSensorChanged(SensorEvent event) {
-            synchronized (mLock) {
-                try {
-                    mEvents.put(event);
-                } catch (InterruptedException ex) {
-                    fail("interrupted while adding a SensorEvent to the queue");
-                }
-            }
-        }
-
-        @Override
-        public void onAccuracyChanged(Sensor sensor, int accuracy) {
-        }
-    }
-
-    private InputDevice createInputDeviceWithSensor(int id) {
-        return new InputDevice.Builder()
-                .setId(id)
-                .setName("Test Device " + id)
-                .setHasSensor(true)
-                .build();
-    }
-
-    private InputSensorInfo createInputSensorInfo(int id, int type) {
-        InputSensorInfo info = new InputSensorInfo("name", "vendor", 0 /* version */,
-                0 /* handle */, type, 100.0f /*maxRange */, 0.02f /* resolution */,
-                0.8f /* power */, 1000 /* minDelay */, 0 /* fifoReservedEventCount */,
-                0 /* fifoMaxEventCount */, "" /* stringType */, "" /* requiredPermission */,
-                0 /* maxDelay */, 0 /* flags */, id);
-        return info;
-    }
-
-    private InputDevice getSensorDevice(int[] deviceIds) {
-        for (int deviceId : deviceIds) {
-            InputDevice device = mInputManager.getInputDevice(deviceId);
-            if (device.hasSensor()) {
-                return device;
-            }
-        }
-        return null;
-    }
-
-    @Test
-    public void getInputDeviceSensors_withExpectedType() throws Exception {
-        InputDevice device = getSensorDevice(mInputManager.getInputDeviceIds());
-        assertNotNull(device);
-
-        SensorManager sensorManager = device.getSensorManager();
-        List<Sensor> accelList = sensorManager.getSensorList(Sensor.TYPE_ACCELEROMETER);
-        verify(mIInputManagerMock).getSensorList(eq(DEVICE_ID));
-        assertEquals(1, accelList.size());
-        assertEquals(DEVICE_ID, accelList.get(0).getId());
-        assertEquals(Sensor.TYPE_ACCELEROMETER, accelList.get(0).getType());
-
-        List<Sensor> gyroList = sensorManager.getSensorList(Sensor.TYPE_GYROSCOPE);
-        verify(mIInputManagerMock).getSensorList(eq(DEVICE_ID));
-        assertEquals(1, gyroList.size());
-        assertEquals(DEVICE_ID, gyroList.get(0).getId());
-        assertEquals(Sensor.TYPE_GYROSCOPE, gyroList.get(0).getType());
-
-    }
-
-    @Test
-    public void getInputDeviceSensors_withUnexpectedType() throws Exception {
-        InputDevice device = getSensorDevice(mInputManager.getInputDeviceIds());
-
-        assertNotNull(device);
-        SensorManager sensorManager = device.getSensorManager();
-
-        List<Sensor> gameRotationList = sensorManager.getSensorList(
-                Sensor.TYPE_GAME_ROTATION_VECTOR);
-        verify(mIInputManagerMock).getSensorList(eq(DEVICE_ID));
-        assertEquals(0, gameRotationList.size());
-
-        List<Sensor> gravityList = sensorManager.getSensorList(Sensor.TYPE_GRAVITY);
-        verify(mIInputManagerMock).getSensorList(eq(DEVICE_ID));
-        assertEquals(0, gravityList.size());
-    }
-
-    @Test
-    public void testInputDeviceSensorListener() throws Exception {
-        InputDevice device = getSensorDevice(mInputManager.getInputDeviceIds());
-        assertNotNull(device);
-
-        SensorManager sensorManager = device.getSensorManager();
-        Sensor sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
-        assertEquals(Sensor.TYPE_ACCELEROMETER, sensor.getType());
-
-        doAnswer(invocation -> {
-            mIInputSensorEventListener = invocation.getArgument(0);
-            assertNotNull(mIInputSensorEventListener);
-            return true;
-        }).when(mIInputManagerMock).registerSensorListener(any());
-
-        InputTestSensorEventListener listener = new InputTestSensorEventListener();
-        assertTrue(sensorManager.registerListener(listener, sensor,
-                SensorManager.SENSOR_DELAY_NORMAL));
-        verify(mIInputManagerMock).registerSensorListener(any());
-        verify(mIInputManagerMock).enableSensor(eq(DEVICE_ID), eq(sensor.getType()),
-                anyInt(), anyInt());
-
-        float[] values = new float[] {0.12f, 9.8f, 0.2f};
-        mIInputSensorEventListener.onInputSensorChanged(DEVICE_ID, Sensor.TYPE_ACCELEROMETER,
-                SensorManager.SENSOR_STATUS_ACCURACY_HIGH,  /* timestamp */ 0x1234abcd, values);
-
-        SensorEvent event = listener.waitForSensorEvent();
-        assertNotNull(event);
-        assertEquals(0x1234abcd, event.timestamp);
-        assertEquals(values.length, event.values.length);
-        for (int i = 0; i < values.length; i++) {
-            assertEquals(values[i], event.values[i], 0.001f);
-        }
-
-        sensorManager.unregisterListener(listener);
-        verify(mIInputManagerMock).disableSensor(eq(DEVICE_ID), eq(sensor.getType()));
-    }
-
-}
diff --git a/core/tests/coretests/src/android/hardware/input/InputManagerTest.kt b/core/tests/coretests/src/android/hardware/input/InputManagerTest.kt
deleted file mode 100644
index 2ebe362..0000000
--- a/core/tests/coretests/src/android/hardware/input/InputManagerTest.kt
+++ /dev/null
@@ -1,150 +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.hardware.input
-
-import android.content.Context
-import android.content.ContextWrapper
-import android.content.res.Resources
-import android.platform.test.annotations.Presubmit
-import android.view.Display
-import android.view.DisplayInfo
-import android.view.InputDevice
-import androidx.test.core.app.ApplicationProvider
-import org.junit.After
-import org.junit.Assert.assertNotNull
-import org.junit.Assert.assertEquals
-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.Mockito.eq
-import org.mockito.Mockito.`when`
-import org.mockito.junit.MockitoJUnit
-import org.mockito.junit.MockitoJUnitRunner
-
-/**
- * Tests for [InputManager].
- *
- * Build/Install/Run:
- * atest FrameworksCoreTests:InputManagerTest
- */
-@Presubmit
-@RunWith(MockitoJUnitRunner::class)
-class InputManagerTest {
-
-    companion object {
-        const val DEVICE_ID = 42
-        const val SECOND_DEVICE_ID = 96
-        const val THIRD_DEVICE_ID = 99
-    }
-
-    @get:Rule
-    val rule = MockitoJUnit.rule()!!
-
-    private lateinit var devicesChangedListener: IInputDevicesChangedListener
-    private val deviceGenerationMap = mutableMapOf<Int /*deviceId*/, Int /*generation*/>()
-    private lateinit var context: Context
-    private lateinit var inputManager: InputManager
-
-    @Mock
-    private lateinit var iInputManager: IInputManager
-
-    @Before
-    fun setUp() {
-        context = Mockito.spy(ContextWrapper(ApplicationProvider.getApplicationContext()))
-        InputManagerGlobal.resetInstance(iInputManager)
-        inputManager = InputManager(context)
-        `when`(context.getSystemService(eq(Context.INPUT_SERVICE))).thenReturn(inputManager)
-        `when`(iInputManager.inputDeviceIds).then {
-            deviceGenerationMap.keys.toIntArray()
-        }
-    }
-
-    @After
-    fun tearDown() {
-        InputManagerGlobal.clearInstance()
-    }
-
-    private fun notifyDeviceChanged(
-        deviceId: Int,
-        associatedDisplayId: Int,
-        usiVersion: HostUsiVersion?,
-    ) {
-        val generation = deviceGenerationMap[deviceId]?.plus(1)
-            ?: throw IllegalArgumentException("Device $deviceId was never added!")
-        deviceGenerationMap[deviceId] = generation
-
-        `when`(iInputManager.getInputDevice(deviceId))
-            .thenReturn(createInputDevice(deviceId, associatedDisplayId, usiVersion, generation))
-        val list = deviceGenerationMap.flatMap { listOf(it.key, it.value) }
-        if (::devicesChangedListener.isInitialized) {
-            devicesChangedListener.onInputDevicesChanged(list.toIntArray())
-        }
-    }
-
-    private fun addInputDevice(
-        deviceId: Int,
-        associatedDisplayId: Int,
-        usiVersion: HostUsiVersion?,
-    ) {
-        deviceGenerationMap[deviceId] = 0
-        notifyDeviceChanged(deviceId, associatedDisplayId, usiVersion)
-    }
-
-    @Test
-    fun testUsiVersionDisplayAssociation() {
-        addInputDevice(DEVICE_ID, Display.DEFAULT_DISPLAY, null)
-        addInputDevice(SECOND_DEVICE_ID, Display.INVALID_DISPLAY, HostUsiVersion(9, 8))
-        addInputDevice(THIRD_DEVICE_ID, 42, HostUsiVersion(3, 1))
-
-        val usiVersion = inputManager.getHostUsiVersion(createDisplay(42))
-        assertNotNull(usiVersion)
-        assertEquals(3, usiVersion!!.majorVersion)
-        assertEquals(1, usiVersion.minorVersion)
-    }
-
-    @Test
-    fun testUsiVersionFallBackToDisplayConfig() {
-        addInputDevice(DEVICE_ID, Display.DEFAULT_DISPLAY, null)
-
-        `when`(iInputManager.getHostUsiVersionFromDisplayConfig(eq(42)))
-            .thenReturn(HostUsiVersion(9, 8))
-        val usiVersion = inputManager.getHostUsiVersion(createDisplay(42))
-        assertEquals(HostUsiVersion(9, 8), usiVersion)
-    }
-}
-
-private fun createInputDevice(
-    deviceId: Int,
-    associatedDisplayId: Int,
-    usiVersion: HostUsiVersion? = null,
-    generation: Int = -1,
-): InputDevice =
-    InputDevice.Builder()
-        .setId(deviceId)
-        .setName("Device $deviceId")
-        .setDescriptor("descriptor $deviceId")
-        .setAssociatedDisplayId(associatedDisplayId)
-        .setUsiVersion(usiVersion)
-        .setGeneration(generation)
-        .build()
-
-private fun createDisplay(displayId: Int): Display {
-    val res: Resources? = null
-    return Display(null /* global */, displayId, DisplayInfo(), res)
-}
diff --git a/core/tests/coretests/src/android/hardware/input/KeyboardBacklightListenerTest.kt b/core/tests/coretests/src/android/hardware/input/KeyboardBacklightListenerTest.kt
deleted file mode 100644
index ce816ab..0000000
--- a/core/tests/coretests/src/android/hardware/input/KeyboardBacklightListenerTest.kt
+++ /dev/null
@@ -1,173 +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 android.hardware.input
-
-import android.content.Context
-import android.content.ContextWrapper
-import android.os.Handler
-import android.os.HandlerExecutor
-import android.os.test.TestLooper
-import android.platform.test.annotations.Presubmit
-import androidx.test.core.app.ApplicationProvider
-import com.android.server.testutils.any
-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
-import org.mockito.Mockito
-import org.mockito.Mockito.doAnswer
-import org.mockito.Mockito.`when`
-import org.mockito.junit.MockitoJUnit
-import org.mockito.junit.MockitoJUnitRunner
-import java.util.concurrent.Executor
-import kotlin.test.assertEquals
-import kotlin.test.assertNotNull
-import kotlin.test.assertNull
-import kotlin.test.fail
-
-/**
- * Tests for [InputManager.KeyboardBacklightListener].
- *
- * Build/Install/Run:
- * atest FrameworksCoreTests:KeyboardBacklightListenerTest
- */
-@Presubmit
-@RunWith(MockitoJUnitRunner::class)
-class KeyboardBacklightListenerTest {
-    @get:Rule
-    val rule = MockitoJUnit.rule()!!
-
-    private lateinit var testLooper: TestLooper
-    private var registeredListener: IKeyboardBacklightListener? = null
-    private lateinit var executor: Executor
-    private lateinit var context: Context
-    private lateinit var inputManager: InputManager
-
-    @Mock
-    private lateinit var iInputManagerMock: IInputManager
-
-    @Before
-    fun setUp() {
-        context = Mockito.spy(ContextWrapper(ApplicationProvider.getApplicationContext()))
-        InputManagerGlobal.resetInstance(iInputManagerMock)
-        testLooper = TestLooper()
-        executor = HandlerExecutor(Handler(testLooper.looper))
-        registeredListener = null
-        inputManager = InputManager(context)
-        `when`(context.getSystemService(Mockito.eq(Context.INPUT_SERVICE)))
-                .thenReturn(inputManager)
-
-        // Handle keyboard backlight listener registration.
-        doAnswer {
-            val listener = it.getArgument(0) as IKeyboardBacklightListener
-            if (registeredListener != null &&
-                    registeredListener!!.asBinder() != listener.asBinder()) {
-                // There can only be one registered keyboard backlight listener per process.
-                fail("Trying to register a new listener when one already exists")
-            }
-            registeredListener = listener
-            null
-        }.`when`(iInputManagerMock).registerKeyboardBacklightListener(any())
-
-        // Handle keyboard backlight listener being unregistered.
-        doAnswer {
-            val listener = it.getArgument(0) as IKeyboardBacklightListener
-            if (registeredListener == null ||
-                    registeredListener!!.asBinder() != listener.asBinder()) {
-                fail("Trying to unregister a listener that is not registered")
-            }
-            registeredListener = null
-            null
-        }.`when`(iInputManagerMock).unregisterKeyboardBacklightListener(any())
-    }
-
-    @After
-    fun tearDown() {
-        InputManagerGlobal.clearInstance()
-    }
-
-    private fun notifyKeyboardBacklightChanged(
-        deviceId: Int,
-        brightnessLevel: Int,
-        maxBrightnessLevel: Int = 10,
-        isTriggeredByKeyPress: Boolean = true
-    ) {
-        registeredListener!!.onBrightnessChanged(deviceId, IKeyboardBacklightState().apply {
-            this.brightnessLevel = brightnessLevel
-            this.maxBrightnessLevel = maxBrightnessLevel
-        }, isTriggeredByKeyPress)
-    }
-
-    @Test
-    fun testListenerIsNotifiedCorrectly() {
-        var callbackCount = 0
-
-        // Add a keyboard backlight listener
-        inputManager.registerKeyboardBacklightListener(executor) {
-                deviceId: Int,
-                keyboardBacklightState: KeyboardBacklightState,
-                isTriggeredByKeyPress: Boolean ->
-            callbackCount++
-            assertEquals(1, deviceId)
-            assertEquals(2, keyboardBacklightState.brightnessLevel)
-            assertEquals(10, keyboardBacklightState.maxBrightnessLevel)
-            assertEquals(true, isTriggeredByKeyPress)
-        }
-
-        // Adding the listener should register the callback with InputManagerService.
-        assertNotNull(registeredListener)
-
-        // Notifying keyboard backlight change will notify the listener.
-        notifyKeyboardBacklightChanged(1 /*deviceId*/, 2 /* brightnessLevel */)
-        testLooper.dispatchNext()
-        assertEquals(1, callbackCount)
-    }
-
-    @Test
-    fun testMultipleListeners() {
-        // Set up two callbacks.
-        var callbackCount1 = 0
-        var callbackCount2 = 0
-        val callback1 = InputManager.KeyboardBacklightListener { _, _, _ -> callbackCount1++ }
-        val callback2 = InputManager.KeyboardBacklightListener { _, _, _ -> callbackCount2++ }
-
-        // Add both keyboard backlight listeners
-        inputManager.registerKeyboardBacklightListener(executor, callback1)
-        inputManager.registerKeyboardBacklightListener(executor, callback2)
-
-        // Adding the listeners should register the callback with InputManagerService.
-        assertNotNull(registeredListener)
-
-        // Notifying keyboard backlight change trigger the both callbacks.
-        notifyKeyboardBacklightChanged(1 /*deviceId*/, 1 /* brightnessLevel */)
-        testLooper.dispatchAll()
-        assertEquals(1, callbackCount1)
-        assertEquals(1, callbackCount2)
-
-        inputManager.unregisterKeyboardBacklightListener(callback2)
-        // Notifying keyboard backlight change should still trigger callback1.
-        notifyKeyboardBacklightChanged(1 /*deviceId*/, 2 /* brightnessLevel */)
-        testLooper.dispatchAll()
-        assertEquals(2, callbackCount1)
-
-        // Unregister all listeners, should remove registered listener from InputManagerService
-        inputManager.unregisterKeyboardBacklightListener(callback1)
-        assertNull(registeredListener)
-    }
-}
diff --git a/core/tests/coretests/src/android/os/BinderProxyCountingTest.java b/core/tests/coretests/src/android/os/BinderProxyCountingTest.java
index ce6ad87..2089c6c 100644
--- a/core/tests/coretests/src/android/os/BinderProxyCountingTest.java
+++ b/core/tests/coretests/src/android/os/BinderProxyCountingTest.java
@@ -24,12 +24,12 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.ServiceConnection;
-import android.support.test.uiautomator.UiDevice;
 import android.util.Log;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
+import androidx.test.uiautomator.UiDevice;
 
 import com.android.frameworks.coretests.aidl.IBpcCallbackObserver;
 import com.android.frameworks.coretests.aidl.IBpcTestAppCmdService;
diff --git a/core/tests/coretests/src/android/os/ParcelTest.java b/core/tests/coretests/src/android/os/ParcelTest.java
index e2fe87b4..4b993fa 100644
--- a/core/tests/coretests/src/android/os/ParcelTest.java
+++ b/core/tests/coretests/src/android/os/ParcelTest.java
@@ -246,4 +246,93 @@
         assertThrows(IllegalArgumentException.class, () -> Parcel.compareData(pA, -1, pB, iB, 0));
         assertThrows(IllegalArgumentException.class, () -> Parcel.compareData(pA, 0, pB, -1, 0));
     }
+
+    /***
+     * Tests for b/205282403
+     * This test checks if allocations made over limit of 1MB for primitive types
+     * and 1M length for complex objects are not allowed.
+     */
+    @Test
+    public void testAllocationsOverLimit_whenOverLimit_throws() {
+        Binder.setIsDirectlyHandlingTransactionOverride(true);
+        Parcel p = Parcel.obtain();
+        p.setDataPosition(0);
+        p.writeInt(Integer.MAX_VALUE);
+
+        p.setDataPosition(0);
+        assertThrows(BadParcelableException.class, () ->p.createBooleanArray());
+
+        p.setDataPosition(0);
+        assertThrows(BadParcelableException.class, () ->p.createCharArray());
+
+        p.setDataPosition(0);
+        assertThrows(BadParcelableException.class, () ->p.createIntArray());
+
+        p.setDataPosition(0);
+        assertThrows(BadParcelableException.class, () ->p.createLongArray());
+
+        p.setDataPosition(0);
+        assertThrows(BadParcelableException.class, () ->p.createBinderArray());
+
+        int[] dimensions = new int[]{Integer.MAX_VALUE, 100, 100};
+        p.setDataPosition(0);
+        assertThrows(BadParcelableException.class,
+                () -> p.createFixedArray(int[][][].class, dimensions));
+
+        p.setDataPosition(0);
+        assertThrows(BadParcelableException.class,
+                () -> p.createFixedArray(String[][][].class, dimensions));
+
+        p.setDataPosition(0);
+        assertThrows(BadParcelableException.class,
+                () -> p.createFixedArray(IBinder[][][].class, dimensions));
+
+        p.recycle();
+        Binder.setIsDirectlyHandlingTransactionOverride(false);
+    }
+
+    /***
+     * Tests for b/205282403
+     * This test checks if allocations made under limit of 1MB for primitive types
+     * and 1M length for complex objects are allowed.
+     */
+    @Test
+    public void testAllocations_whenWithinLimit() {
+        Binder.setIsDirectlyHandlingTransactionOverride(true);
+        Parcel p = Parcel.obtain();
+        p.setDataPosition(0);
+        p.writeInt(100000);
+
+        p.setDataPosition(0);
+        p.createByteArray();
+
+        p.setDataPosition(0);
+        p.createCharArray();
+
+        p.setDataPosition(0);
+        p.createIntArray();
+
+        p.setDataPosition(0);
+        p.createLongArray();
+
+        p.setDataPosition(0);
+        p.createBinderArray();
+
+        int[] dimensions = new int[]{ 100, 100, 100 };
+
+        p.setDataPosition(0);
+        int[][][] data  =  new int[100][100][100];
+        p.writeFixedArray(data, 0, dimensions);
+        p.setDataPosition(0);
+        p.createFixedArray(int[][][].class, dimensions);
+
+        p.setDataPosition(0);
+        IBinder[][][] parcelables  =  new IBinder[100][100][100];
+        p.writeFixedArray(parcelables, 0, dimensions);
+        p.setDataPosition(0);
+        p.createFixedArray(IBinder[][][].class, dimensions);
+
+        p.recycle();
+        Binder.setIsDirectlyHandlingTransactionOverride(false);
+    }
 }
diff --git a/core/tests/coretests/src/android/os/PowerManagerTest.java b/core/tests/coretests/src/android/os/PowerManagerTest.java
index 9f85d6f..21d1dbb 100644
--- a/core/tests/coretests/src/android/os/PowerManagerTest.java
+++ b/core/tests/coretests/src/android/os/PowerManagerTest.java
@@ -24,11 +24,11 @@
 import static org.mockito.Mockito.verify;
 
 import android.content.Context;
-import android.support.test.uiautomator.UiDevice;
 import android.test.AndroidTestCase;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
+import androidx.test.uiautomator.UiDevice;
 
 import org.junit.After;
 import org.junit.Test;
diff --git a/core/tests/coretests/src/android/os/VibrationEffectTest.java b/core/tests/coretests/src/android/os/VibrationEffectTest.java
index 627feab..73954da 100644
--- a/core/tests/coretests/src/android/os/VibrationEffectTest.java
+++ b/core/tests/coretests/src/android/os/VibrationEffectTest.java
@@ -37,11 +37,7 @@
 import android.content.res.Resources;
 import android.hardware.vibrator.IVibrator;
 import android.net.Uri;
-import android.os.SystemVibrator;
 import android.os.VibrationEffect.Composition.UnreachableAfterRepeatingIndefinitelyException;
-import android.os.Vibrator;
-import android.os.VibratorInfo;
-import android.os.vibrator.PrebakedSegment;
 import android.os.vibrator.PrimitiveSegment;
 import android.os.vibrator.StepSegment;
 import android.platform.test.annotations.Presubmit;
@@ -50,6 +46,7 @@
 
 import com.android.internal.R;
 
+import org.jetbrains.annotations.NotNull;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.junit.MockitoJUnitRunner;
@@ -676,43 +673,6 @@
     }
 
     @Test
-    public void testApplyEffectStrengthOneShot() {
-        VibrationEffect.Composed applied = DEFAULT_ONE_SHOT.applyEffectStrength(
-                VibrationEffect.EFFECT_STRENGTH_LIGHT);
-        assertEquals(DEFAULT_ONE_SHOT, applied);
-    }
-
-    @Test
-    public void testApplyEffectStrengthWaveform() {
-        VibrationEffect.Composed applied = TEST_WAVEFORM.applyEffectStrength(
-                VibrationEffect.EFFECT_STRENGTH_LIGHT);
-        assertEquals(TEST_WAVEFORM, applied);
-    }
-
-    @Test
-    public void testApplyEffectStrengthPrebaked() {
-        VibrationEffect.Composed applied = VibrationEffect.get(VibrationEffect.EFFECT_CLICK)
-                .applyEffectStrength(VibrationEffect.EFFECT_STRENGTH_LIGHT);
-        assertEquals(VibrationEffect.EFFECT_STRENGTH_LIGHT,
-                ((PrebakedSegment) applied.getSegments().get(0)).getEffectStrength());
-    }
-
-    @Test
-    public void testApplyEffectStrengthComposed() {
-        VibrationEffect effect = VibrationEffect.startComposition()
-                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f, 1)
-                .compose();
-        assertEquals(effect, effect.applyEffectStrength(VibrationEffect.EFFECT_STRENGTH_LIGHT));
-
-        VibrationEffect.Composed applied = VibrationEffect.startComposition()
-                .addEffect(VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
-                .compose()
-                .applyEffectStrength(VibrationEffect.EFFECT_STRENGTH_LIGHT);
-        assertEquals(VibrationEffect.EFFECT_STRENGTH_LIGHT,
-                ((PrebakedSegment) applied.getSegments().get(0)).getEffectStrength());
-    }
-
-    @Test
     public void testScaleOneShot() {
         VibrationEffect.Composed scaledUp = TEST_ONE_SHOT.scale(1.5f);
         assertTrue(100 / 255f < ((StepSegment) scaledUp.getSegments().get(0)).getAmplitude());
@@ -745,9 +705,9 @@
     public void testScaleComposed() {
         VibrationEffect.Composed effect =
                 (VibrationEffect.Composed) VibrationEffect.startComposition()
-                        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f, 1)
-                        .addEffect(TEST_ONE_SHOT)
-                        .compose();
+                    .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f, 1)
+                    .addEffect(TEST_ONE_SHOT)
+                    .compose();
 
         VibrationEffect.Composed scaledUp = effect.scale(1.5f);
         assertTrue(0.5f < ((PrimitiveSegment) scaledUp.getSegments().get(0)).getScale());
@@ -758,6 +718,109 @@
         assertTrue(100 / 255f > ((StepSegment) scaledDown.getSegments().get(1)).getAmplitude());
     }
 
+    private void doTestApplyRepeatingWithNonRepeatingOriginal(@NotNull VibrationEffect original) {
+        assertTrue(original.getDuration() != Long.MAX_VALUE);
+        int loopDelayMs = 123;
+        assertEquals(original, original.applyRepeatingIndefinitely(false, loopDelayMs));
+
+        // Looping with no delay gets the raw repeated effect.
+        VibrationEffect loopingOriginal = VibrationEffect.startComposition()
+                .repeatEffectIndefinitely(original)
+                .compose();
+        assertEquals(Long.MAX_VALUE, loopingOriginal.getDuration());
+        assertEquals(loopingOriginal, original.applyRepeatingIndefinitely(true, 0));
+
+        VibrationEffect loopingPart = VibrationEffect.startComposition()
+                .addEffect(original)
+                .addOffDuration(Duration.ofMillis(loopDelayMs))
+                .compose();
+
+        VibrationEffect loopingWithDelay = VibrationEffect.startComposition()
+                .repeatEffectIndefinitely(loopingPart)
+                .compose();
+        assertEquals(Long.MAX_VALUE, loopingWithDelay.getDuration());
+        assertEquals(loopingWithDelay, original.applyRepeatingIndefinitely(true, loopDelayMs));
+    }
+
+    @Test
+    public void testApplyRepeatingIndefinitely_nonRepeatingOriginal() {
+        VibrationEffect oneshot = VibrationEffect.createOneShot(100, DEFAULT_AMPLITUDE);
+        doTestApplyRepeatingWithNonRepeatingOriginal(oneshot);
+
+        VibrationEffect predefined = VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK);
+        doTestApplyRepeatingWithNonRepeatingOriginal(predefined);
+
+        VibrationEffect primitives = VibrationEffect.startComposition()
+                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
+                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 1, 100)
+                .compose();
+        doTestApplyRepeatingWithNonRepeatingOriginal(primitives);
+
+        VibrationEffect legacyWaveform = VibrationEffect.createWaveform(
+                new long[]{1, 2, 3}, new int[]{1, 2, 3}, -1);
+        doTestApplyRepeatingWithNonRepeatingOriginal(legacyWaveform);
+
+        // Test a mix of segments ending in a delay, for completeness.
+        doTestApplyRepeatingWithNonRepeatingOriginal(VibrationEffect.startComposition()
+                .addEffect(oneshot)
+                .addEffect(predefined)
+                .addEffect(primitives)
+                .addEffect(legacyWaveform)
+                .addOffDuration(Duration.ofMillis(1000))
+                .compose());
+    }
+
+    @Test
+    public void testApplyRepeatingIndefinitely_repeatingOriginalWaveform() {
+        // The delay parameter has no effect when the effect is already repeating.
+        int delayMs = 999;
+        VibrationEffect waveformNoRepeat = VibrationEffect.createWaveform(
+                new long[]{1, 2, 3}, new int[]{1, 2, 3}, -1);
+        VibrationEffect waveformFullRepeat = VibrationEffect.createWaveform(
+                new long[]{1, 2, 3}, new int[]{1, 2, 3}, 0);
+        assertEquals(waveformFullRepeat,
+                waveformFullRepeat.applyRepeatingIndefinitely(true, delayMs));
+        assertEquals(waveformNoRepeat,
+                waveformFullRepeat.applyRepeatingIndefinitely(false, delayMs));
+
+        VibrationEffect waveformOffsetRepeat = VibrationEffect.createWaveform(
+                new long[]{1, 2, 3}, new int[]{1, 2, 3}, 1);
+        assertEquals(waveformOffsetRepeat,
+                waveformOffsetRepeat.applyRepeatingIndefinitely(true, delayMs));
+        assertEquals(waveformNoRepeat,
+                waveformOffsetRepeat.applyRepeatingIndefinitely(false, delayMs));
+    }
+
+    @Test
+    public void testApplyRepeatingIndefinitely_repeatingOriginalComposition() {
+        // The delay parameter has no effect when the effect is already repeating.
+        int delayMs = 999;
+        VibrationEffect innerEffect = VibrationEffect.startComposition()
+                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
+                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK)
+                .compose();
+
+        VibrationEffect repeatingOriginal = VibrationEffect.startComposition()
+                .repeatEffectIndefinitely(innerEffect)
+                .compose();
+        assertEquals(repeatingOriginal,
+                repeatingOriginal.applyRepeatingIndefinitely(true, delayMs));
+        assertEquals(innerEffect,
+                repeatingOriginal.applyRepeatingIndefinitely(false, delayMs));
+
+        VibrationEffect offsetOriginal = VibrationEffect.startComposition()
+                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_THUD)
+                .repeatEffectIndefinitely(innerEffect)
+                .compose();
+        assertEquals(offsetOriginal,
+                offsetOriginal.applyRepeatingIndefinitely(true, delayMs));
+        assertEquals(VibrationEffect.startComposition()
+                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_THUD)
+                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
+                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK)
+                .compose(),
+                offsetOriginal.applyRepeatingIndefinitely(false, delayMs));
+    }
 
     @Test
     public void testDuration() {
diff --git a/core/tests/coretests/src/android/os/health/SystemHealthManagerTest.java b/core/tests/coretests/src/android/os/health/SystemHealthManagerTest.java
new file mode 100644
index 0000000..2232e3a
--- /dev/null
+++ b/core/tests/coretests/src/android/os/health/SystemHealthManagerTest.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 android.os.health;
+
+import static androidx.test.InstrumentationRegistry.getContext;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.PowerMonitor;
+import android.os.PowerMonitorReadings;
+
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class SystemHealthManagerTest {
+
+    @Test
+    public void getPowerMonitors() {
+        SystemHealthManager shm = getContext().getSystemService(SystemHealthManager.class);
+        PowerMonitor[] powerMonitorInfo = shm.getSupportedPowerMonitors();
+        assertThat(powerMonitorInfo).isNotNull();
+        if (powerMonitorInfo.length == 0) {
+            // This device does not support PowerStats HAL
+            return;
+        }
+
+        PowerMonitor consumerMonitor = null;
+        PowerMonitor measurementMonitor = null;
+        for (PowerMonitor pmi : powerMonitorInfo) {
+            if (pmi.type == PowerMonitor.POWER_MONITOR_TYPE_MEASUREMENT) {
+                measurementMonitor = pmi;
+            } else {
+                consumerMonitor = pmi;
+            }
+        }
+
+        List<PowerMonitor> pmis = new ArrayList<>();
+        if (consumerMonitor != null) {
+            pmis.add(consumerMonitor);
+        }
+        if (measurementMonitor != null) {
+            pmis.add(measurementMonitor);
+        }
+
+        PowerMonitor[] selectedMonitors = pmis.toArray(new PowerMonitor[0]);
+        PowerMonitorReadings readings = shm.getPowerMonitorReadings(selectedMonitors);
+
+        for (PowerMonitor monitor : selectedMonitors) {
+            assertThat(readings.getConsumedEnergyUws(monitor)).isAtLeast(0);
+            assertThat(readings.getTimestampMs(monitor)).isGreaterThan(0);
+        }
+    }
+}
diff --git a/core/tests/coretests/src/android/os/vibrator/persistence/VibrationEffectXmlSerializationTest.java b/core/tests/coretests/src/android/os/vibrator/persistence/VibrationEffectXmlSerializationTest.java
new file mode 100644
index 0000000..b31af89
--- /dev/null
+++ b/core/tests/coretests/src/android/os/vibrator/persistence/VibrationEffectXmlSerializationTest.java
@@ -0,0 +1,354 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.vibrator.persistence;
+
+import static android.os.VibrationEffect.Composition.PRIMITIVE_CLICK;
+import static android.os.VibrationEffect.Composition.PRIMITIVE_LOW_TICK;
+import static android.os.VibrationEffect.Composition.PRIMITIVE_SPIN;
+import static android.os.VibrationEffect.Composition.PRIMITIVE_TICK;
+import static android.os.vibrator.persistence.VibrationXmlParser.isSupportedMimeType;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+
+import android.os.VibrationEffect;
+import android.os.vibrator.PrebakedSegment;
+import android.platform.test.annotations.Presubmit;
+import android.util.Xml;
+
+import com.android.modules.utils.TypedXmlPullParser;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.xmlpull.v1.XmlPullParser;
+
+import java.io.IOException;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Unit tests for {@link VibrationXmlParser} and {@link VibrationXmlSerializer}.
+ *
+ * <p>The {@link VibrationEffect} public APIs are covered by CTS to enforce the schema defined at
+ * services/core/xsd/vibrator/vibration/vibration.xsd.
+ */
+@Presubmit
+@RunWith(JUnit4.class)
+public class VibrationEffectXmlSerializationTest {
+
+    @Test
+    public void isSupportedMimeType_onlySupportsVibrationXmlMimeType() {
+        // Single MIME type supported
+        assertThat(isSupportedMimeType(
+                VibrationXmlParser.APPLICATION_VIBRATION_XML_MIME_TYPE)).isTrue();
+        assertThat(isSupportedMimeType("application/vnd.android.haptics.vibration+xml")).isTrue();
+        // without xml suffix not supported
+        assertThat(isSupportedMimeType("application/vnd.android.haptics.vibration")).isFalse();
+        // different top-level not supported
+        assertThat(isSupportedMimeType("haptics/vnd.android.haptics.vibration+xml")).isFalse();
+        // different type not supported
+        assertThat(isSupportedMimeType("application/vnd.android.vibration+xml")).isFalse();
+    }
+
+    @Test
+    public void testParseTag_succeedAndParserPointsToEndVibrationTag() throws Exception {
+        VibrationEffect effect = VibrationEffect.startComposition()
+                .addPrimitive(PRIMITIVE_CLICK)
+                .addPrimitive(PRIMITIVE_TICK, 0.2497f)
+                .compose();
+        String xml = "<vibration>"
+                + "<primitive-effect name=\"click\"/>"
+                + "<primitive-effect name=\"tick\" scale=\"0.2497\"/>"
+                + "</vibration>";
+        VibrationEffect effect2 = VibrationEffect.startComposition()
+                .addPrimitive(PRIMITIVE_LOW_TICK, 1f, 356)
+                .addPrimitive(PRIMITIVE_SPIN, 0.6364f, 7)
+                .compose();
+        String xml2 = "<vibration>"
+                + "<primitive-effect name=\"low_tick\" delayMs=\"356\"/>"
+                + "<primitive-effect name=\"spin\" scale=\"0.6364\" delayMs=\"7\"/>"
+                + "</vibration>";
+
+        TypedXmlPullParser parser = createXmlPullParser(xml);
+        assertParseTagSucceeds(parser, effect);
+        parser.next();
+        assertEndOfDocument(parser);
+
+        // Test no-issues when an end-tag follows the vibration XML.
+        // To test this, starting with the corresponding "start-tag" is necessary.
+        parser = createXmlPullParser("<next-tag>" + xml + "</next-tag>");
+        // Move the parser once to point to the "<vibration> tag.
+        parser.next();
+        assertParseTagSucceeds(parser, effect);
+        parser.next();
+        assertEndTag(parser, "next-tag");
+
+        parser = createXmlPullParser(xml + "<next-tag>");
+        assertParseTagSucceeds(parser, effect);
+        parser.next();
+        assertStartTag(parser, "next-tag");
+
+        parser = createXmlPullParser(xml + xml2);
+        assertParseTagSucceeds(parser, effect);
+        parser.next();
+        assertParseTagSucceeds(parser, effect2);
+        parser.next();
+        assertEndOfDocument(parser);
+    }
+
+    @Test
+    public void testParseTag_badXml_throwsException() throws Exception {
+        assertParseTagFails(
+                "<vibration>random text<primitive-effect name=\"click\"/></vibration>");
+        assertParseTagFails("<bad-tag><primitive-effect name=\"click\"/></vibration>");
+        assertParseTagFails("<primitive-effect name=\"click\"/></vibration>");
+        assertParseTagFails("<vibration><primitive-effect name=\"click\"/>");
+    }
+
+    @Test
+    public void testPrimitives_allSucceed() throws IOException {
+        VibrationEffect effect = VibrationEffect.startComposition()
+                .addPrimitive(PRIMITIVE_CLICK)
+                .addPrimitive(PRIMITIVE_TICK, 0.2497f)
+                .addPrimitive(PRIMITIVE_LOW_TICK, 1f, 356)
+                .addPrimitive(PRIMITIVE_SPIN, 0.6364f, 7)
+                .compose();
+        String xml = "<vibration>"
+                + "<primitive-effect name=\"click\"/>"
+                + "<primitive-effect name=\"tick\" scale=\"0.2497\"/>"
+                + "<primitive-effect name=\"low_tick\" delayMs=\"356\"/>"
+                + "<primitive-effect name=\"spin\" scale=\"0.6364\" delayMs=\"7\"/>"
+                + "</vibration>";
+
+        assertPublicApisParserSucceeds(xml, effect);
+        assertPublicApisSerializerSucceeds(effect, "click", "tick", "low_tick", "spin");
+        assertPublicApisRoundTrip(effect);
+
+        assertHiddenApisParserSucceeds(xml, effect);
+        assertHiddenApisSerializerSucceeds(effect, "click", "tick", "low_tick", "spin");
+        assertHiddenApisRoundTrip(effect);
+    }
+
+    @Test
+    public void testWaveforms_allSucceed() throws IOException {
+        VibrationEffect effect = VibrationEffect.createWaveform(new long[]{123, 456, 789, 0},
+                new int[]{254, 1, 255, 0}, /* repeat= */ 0);
+        String xml = "<vibration>"
+                + "<waveform-effect><repeating>"
+                + "<waveform-entry durationMs=\"123\" amplitude=\"254\"/>"
+                + "<waveform-entry durationMs=\"456\" amplitude=\"1\"/>"
+                + "<waveform-entry durationMs=\"789\" amplitude=\"255\"/>"
+                + "<waveform-entry durationMs=\"0\" amplitude=\"0\"/>"
+                + "</repeating></waveform-effect>"
+                + "</vibration>";
+
+        assertPublicApisParserSucceeds(xml, effect);
+        assertPublicApisSerializerSucceeds(effect, "123", "456", "789", "254", "1", "255", "0");
+        assertPublicApisRoundTrip(effect);
+
+        assertHiddenApisParserSucceeds(xml, effect);
+        assertHiddenApisSerializerSucceeds(effect, "123", "456", "789", "254", "1", "255", "0");
+        assertHiddenApisRoundTrip(effect);
+    }
+
+    @Test
+    public void testPredefinedEffects_publicEffectsWithDefaultFallback_allSucceed()
+            throws IOException {
+        for (Map.Entry<String, Integer> entry : createPublicPredefinedEffectsMap().entrySet()) {
+            VibrationEffect effect = VibrationEffect.get(entry.getValue());
+            String xml = String.format(
+                    "<vibration><predefined-effect name=\"%s\"/></vibration>", entry.getKey());
+
+            assertPublicApisParserSucceeds(xml, effect);
+            assertPublicApisSerializerSucceeds(effect, entry.getKey());
+            assertPublicApisRoundTrip(effect);
+
+            assertHiddenApisParserSucceeds(xml, effect);
+            assertHiddenApisSerializerSucceeds(effect, entry.getKey());
+            assertHiddenApisRoundTrip(effect);
+        }
+    }
+
+    @Test
+    public void testPredefinedEffects_hiddenEffects_onlySucceedsWithFlag() throws IOException {
+        for (Map.Entry<String, Integer> entry : createHiddenPredefinedEffectsMap().entrySet()) {
+            VibrationEffect effect = VibrationEffect.get(entry.getValue());
+            String xml = String.format(
+                    "<vibration><predefined-effect name=\"%s\"/></vibration>", entry.getKey());
+
+            assertPublicApisParserFails(xml);
+            assertPublicApisSerializerFails(effect);
+
+            assertHiddenApisParserSucceeds(xml, effect);
+            assertHiddenApisSerializerSucceeds(effect, entry.getKey());
+            assertHiddenApisRoundTrip(effect);
+        }
+    }
+
+    @Test
+    public void testPredefinedEffects_allEffectsWithNonDefaultFallback_onlySucceedsWithFlag()
+            throws IOException {
+        for (Map.Entry<String, Integer> entry : createAllPredefinedEffectsMap().entrySet()) {
+            boolean nonDefaultFallback = !PrebakedSegment.DEFAULT_SHOULD_FALLBACK;
+            VibrationEffect effect = VibrationEffect.get(entry.getValue(), nonDefaultFallback);
+            String xml = String.format(
+                    "<vibration><predefined-effect name=\"%s\" fallback=\"%s\"/></vibration>",
+                    entry.getKey(), nonDefaultFallback);
+
+            assertPublicApisParserFails(xml);
+            assertPublicApisSerializerFails(effect);
+
+            assertHiddenApisParserSucceeds(xml, effect);
+            assertHiddenApisSerializerSucceeds(effect, entry.getKey());
+            assertHiddenApisRoundTrip(effect);
+        }
+    }
+
+    private void assertPublicApisParserFails(String xml) throws IOException {
+        assertThat(parse(xml, /* flags= */ 0)).isNull();
+    }
+
+    private void assertPublicApisParserSucceeds(String xml, VibrationEffect effect)
+            throws IOException {
+        assertThat(parse(xml, /* flags= */ 0)).isEqualTo(effect);
+    }
+
+    private TypedXmlPullParser createXmlPullParser(String xml) throws Exception {
+        TypedXmlPullParser parser = Xml.newFastPullParser();
+        parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
+        parser.setInput(new StringReader(xml));
+        parser.next(); // read START_DOCUMENT
+        return parser;
+    }
+
+    /**
+     * Asserts parsing vibration from an open TypedXmlPullParser succeeds, and that the parser
+     * points to the end "vibration" tag.
+     */
+    private void assertParseTagSucceeds(
+            TypedXmlPullParser parser, VibrationEffect effect) throws Exception {
+        assertThat(parseTag(parser)).isEqualTo(effect);
+
+        assertThat(parser.getEventType()).isEqualTo(XmlPullParser.END_TAG);
+        assertThat(parser.getName()).isEqualTo("vibration");
+    }
+
+    private void assertEndTag(TypedXmlPullParser parser, String expectedTagName) throws Exception {
+        assertThat(parser.getName()).isEqualTo(expectedTagName);
+        assertThat(parser.getEventType()).isEqualTo(parser.END_TAG);
+    }
+
+    private void assertStartTag(TypedXmlPullParser parser, String expectedTagName)
+            throws Exception {
+        assertThat(parser.getName()).isEqualTo(expectedTagName);
+        assertThat(parser.getEventType()).isEqualTo(parser.START_TAG);
+    }
+
+    private void assertEndOfDocument(TypedXmlPullParser parser) throws Exception {
+        assertThat(parser.getEventType()).isEqualTo(parser.END_DOCUMENT);
+    }
+
+    private void assertHiddenApisParserSucceeds(String xml, VibrationEffect effect)
+            throws IOException {
+        assertThat(parse(xml, VibrationXmlParser.FLAG_ALLOW_HIDDEN_APIS)).isEqualTo(effect);
+    }
+
+    private void assertPublicApisSerializerFails(VibrationEffect effect) {
+        assertThrows("Expected serialization to fail for " + effect,
+                VibrationXmlSerializer.SerializationFailedException.class,
+                () -> serialize(effect, /* flags= */ 0));
+    }
+
+    private void assertParseTagFails(String xml) {
+        assertThrows("Expected parsing to fail for " + xml,
+                VibrationXmlParser.VibrationXmlParserException.class,
+                () -> parseTag(createXmlPullParser(xml)));
+    }
+
+    private void assertPublicApisSerializerSucceeds(VibrationEffect effect,
+            String... expectedSegments) throws IOException {
+        assertSerializationContainsSegments(serialize(effect, /* flags= */ 0), expectedSegments);
+    }
+
+    private void assertHiddenApisSerializerSucceeds(VibrationEffect effect,
+            String... expectedSegments) throws IOException {
+        assertSerializationContainsSegments(
+                serialize(effect, VibrationXmlSerializer.FLAG_ALLOW_HIDDEN_APIS), expectedSegments);
+    }
+
+    private void assertSerializationContainsSegments(String xml, String[] expectedSegments) {
+        for (String expectedSegment : expectedSegments) {
+            assertThat(xml).contains(expectedSegment);
+        }
+    }
+
+    private void assertPublicApisRoundTrip(VibrationEffect effect) throws IOException {
+        assertThat(parse(serialize(effect, /* flags= */ 0), /* flags= */ 0)).isEqualTo(effect);
+    }
+
+    private void assertHiddenApisRoundTrip(VibrationEffect effect) throws IOException {
+        String xml = serialize(effect, VibrationXmlSerializer.FLAG_ALLOW_HIDDEN_APIS);
+        assertThat(parse(xml, VibrationXmlParser.FLAG_ALLOW_HIDDEN_APIS)).isEqualTo(effect);
+    }
+
+    private static VibrationEffect parse(String xml, @VibrationXmlParser.Flags int flags)
+            throws IOException {
+        return VibrationXmlParser.parse(new StringReader(xml), flags);
+    }
+
+    private static VibrationEffect parseTag(TypedXmlPullParser parser)
+            throws IOException, VibrationXmlParser.VibrationXmlParserException {
+        return VibrationXmlParser.parseTag(parser, VibrationXmlParser.FLAG_ALLOW_HIDDEN_APIS);
+    }
+
+    private static String serialize(VibrationEffect effect, @VibrationXmlSerializer.Flags int flags)
+            throws IOException {
+        StringWriter writer = new StringWriter();
+        VibrationXmlSerializer.serialize(effect, writer, flags);
+        return writer.toString();
+    }
+
+    private static Map<String, Integer> createAllPredefinedEffectsMap() {
+        Map<String, Integer> map = createHiddenPredefinedEffectsMap();
+        map.putAll(createPublicPredefinedEffectsMap());
+        return map;
+    }
+
+    private static Map<String, Integer> createPublicPredefinedEffectsMap() {
+        Map<String, Integer> map = new HashMap<>();
+        map.put("tick", VibrationEffect.EFFECT_TICK);
+        map.put("click", VibrationEffect.EFFECT_CLICK);
+        map.put("heavy_click", VibrationEffect.EFFECT_HEAVY_CLICK);
+        map.put("double_click", VibrationEffect.EFFECT_DOUBLE_CLICK);
+        return map;
+    }
+
+    private static Map<String, Integer> createHiddenPredefinedEffectsMap() {
+        Map<String, Integer> map = new HashMap<>();
+        map.put("texture_tick", VibrationEffect.EFFECT_TEXTURE_TICK);
+        map.put("pop", VibrationEffect.EFFECT_POP);
+        map.put("thud", VibrationEffect.EFFECT_THUD);
+        for (int i = 0; i < VibrationEffect.RINGTONES.length; i++) {
+            map.put(String.format("ringtone_%d", i + 1), VibrationEffect.RINGTONES[i]);
+        }
+        return map;
+    }
+}
diff --git a/core/tests/coretests/src/android/print/IPrintManagerParametersTest.java b/core/tests/coretests/src/android/print/IPrintManagerParametersTest.java
index 3766cd4..c25aa51 100644
--- a/core/tests/coretests/src/android/print/IPrintManagerParametersTest.java
+++ b/core/tests/coretests/src/android/print/IPrintManagerParametersTest.java
@@ -41,11 +41,11 @@
 import android.print.test.services.PrinterDiscoverySessionCallbacks;
 import android.print.test.services.StubbablePrinterDiscoverySession;
 import android.printservice.recommendation.IRecommendationsChangeListener;
-import android.support.test.uiautomator.UiDevice;
 
 import androidx.test.filters.LargeTest;
 import androidx.test.filters.MediumTest;
 import androidx.test.runner.AndroidJUnit4;
+import androidx.test.uiautomator.UiDevice;
 
 import org.junit.Before;
 import org.junit.Test;
diff --git a/core/tests/coretests/src/android/service/notification/NotificationRankingUpdateTest.java b/core/tests/coretests/src/android/service/notification/NotificationRankingUpdateTest.java
new file mode 100644
index 0000000..a84ac55
--- /dev/null
+++ b/core/tests/coretests/src/android/service/notification/NotificationRankingUpdateTest.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.service.notification;
+
+import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.RANKING_UPDATE_ASHMEM;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.assertTrue;
+
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.os.Parcel;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@SmallTest
+@RunWith(Parameterized.class)
+public class NotificationRankingUpdateTest {
+
+    private static final String NOTIFICATION_CHANNEL_ID = "test_channel_id";
+    private static final String TEST_KEY = "key";
+
+    private NotificationChannel mNotificationChannel;
+
+    // TODO(b/284297289): remove this flag set once resolved.
+    @Parameterized.Parameters(name = "rankingUpdateAshmem={0}")
+    public static Boolean[] getRankingUpdateAshmem() {
+        return new Boolean[] { true, false };
+    }
+
+    @Parameterized.Parameter
+    public boolean mRankingUpdateAshmem;
+
+    @Before
+    public void setUp() {
+        mNotificationChannel = new NotificationChannel(NOTIFICATION_CHANNEL_ID, "test channel",
+                NotificationManager.IMPORTANCE_DEFAULT);
+
+        SystemUiSystemPropertiesFlags.TEST_RESOLVER = flag -> {
+            if (flag.mSysPropKey.equals(RANKING_UPDATE_ASHMEM.mSysPropKey)) {
+                return mRankingUpdateAshmem;
+            }
+            return new SystemUiSystemPropertiesFlags.DebugResolver().isEnabled(flag);
+        };
+    }
+
+    @After
+    public void tearDown() {
+        SystemUiSystemPropertiesFlags.TEST_RESOLVER = null;
+    }
+
+    public NotificationListenerService.Ranking createTestRanking(String key, int rank) {
+        NotificationListenerService.Ranking ranking = new NotificationListenerService.Ranking();
+        ranking.populate(
+                /* key= */ key,
+                /* rank= */ rank,
+                /* matchesInterruptionFilter= */ false,
+                /* visibilityOverride= */ 0,
+                /* suppressedVisualEffects= */ 0,
+                mNotificationChannel.getImportance(),
+                /* explanation= */ null,
+                /* overrideGroupKey= */ null,
+                mNotificationChannel,
+                /* overridePeople= */ null,
+                /* snoozeCriteria= */ null,
+                /* showBadge= */ true,
+                /* userSentiment= */ 0,
+                /* hidden= */ false,
+                /* lastAudiblyAlertedMs= */ -1,
+                /* noisy= */ false,
+                /* smartActions= */ null,
+                /* smartReplies= */ null,
+                /* canBubble= */ false,
+                /* isTextChanged= */ false,
+                /* isConversation= */ false,
+                /* shortcutInfo= */ null,
+                /* rankingAdjustment= */ 0,
+                /* isBubble= */ false,
+                /* proposedImportance= */ 0,
+                /* sensitiveContent= */ false
+        );
+        return ranking;
+    }
+
+    @Test
+    public void testRankingUpdate_rankingConstructor() {
+        NotificationListenerService.Ranking ranking = createTestRanking(TEST_KEY, 123);
+        NotificationRankingUpdate rankingUpdate = new NotificationRankingUpdate(
+                new NotificationListenerService.Ranking[]{ranking});
+
+        NotificationListenerService.RankingMap retrievedRankings = rankingUpdate.getRankingMap();
+        NotificationListenerService.Ranking retrievedRanking =
+                new NotificationListenerService.Ranking();
+        assertTrue(retrievedRankings.getRanking(TEST_KEY, retrievedRanking));
+        assertEquals(123, retrievedRanking.getRank());
+    }
+
+    @Test
+    public void testRankingUpdate_parcelConstructor() {
+        NotificationListenerService.Ranking ranking = createTestRanking(TEST_KEY, 123);
+        NotificationRankingUpdate rankingUpdate = new NotificationRankingUpdate(
+                new NotificationListenerService.Ranking[]{ranking});
+
+        Parcel parceledRankingUpdate = Parcel.obtain();
+        rankingUpdate.writeToParcel(parceledRankingUpdate, 0);
+        parceledRankingUpdate.setDataPosition(0);
+
+        NotificationRankingUpdate retrievedRankingUpdate = new NotificationRankingUpdate(
+                parceledRankingUpdate);
+
+        NotificationListenerService.RankingMap retrievedRankings =
+                retrievedRankingUpdate.getRankingMap();
+        assertNotNull(retrievedRankings);
+        assertTrue(retrievedRankingUpdate.isFdNotNullAndClosed());
+        NotificationListenerService.Ranking retrievedRanking =
+                new NotificationListenerService.Ranking();
+        assertTrue(retrievedRankings.getRanking(TEST_KEY, retrievedRanking));
+        assertEquals(123, retrievedRanking.getRank());
+        assertTrue(retrievedRankingUpdate.equals(rankingUpdate));
+        parceledRankingUpdate.recycle();
+    }
+
+    @Test
+    public void testRankingUpdate_emptyParcelInCheck() {
+        NotificationListenerService.Ranking ranking = createTestRanking(TEST_KEY, 123);
+        NotificationRankingUpdate rankingUpdate = new NotificationRankingUpdate(
+                new NotificationListenerService.Ranking[]{ranking});
+
+        Parcel parceledRankingUpdate = Parcel.obtain();
+        rankingUpdate.writeToParcel(parceledRankingUpdate, 0);
+
+        // This will fail to read the parceledRankingUpdate, because the data position hasn't
+        // been reset, so it'll find no data to read.
+        NotificationRankingUpdate retrievedRankingUpdate = new NotificationRankingUpdate(
+                parceledRankingUpdate);
+        assertNull(retrievedRankingUpdate.getRankingMap());
+    }
+
+    @Test
+    public void testRankingUpdate_describeContents() {
+        NotificationListenerService.Ranking ranking = createTestRanking(TEST_KEY, 123);
+        NotificationRankingUpdate rankingUpdate = new NotificationRankingUpdate(
+                new NotificationListenerService.Ranking[]{ranking});
+        assertEquals(0, rankingUpdate.describeContents());
+    }
+
+    @Test
+    public void testRankingUpdate_equals() {
+        NotificationListenerService.Ranking ranking = createTestRanking(TEST_KEY, 123);
+        NotificationRankingUpdate rankingUpdate = new NotificationRankingUpdate(
+                new NotificationListenerService.Ranking[]{ranking});
+        // Reflexive equality.
+        assertTrue(rankingUpdate.equals(rankingUpdate));
+        // Null or wrong class inequality.
+        assertFalse(rankingUpdate.equals(null));
+        assertFalse(rankingUpdate.equals(ranking));
+
+        // Different ranking contents inequality.
+        NotificationListenerService.Ranking ranking2 = createTestRanking(TEST_KEY, 456);
+        NotificationRankingUpdate rankingUpdate2 = new NotificationRankingUpdate(
+                new NotificationListenerService.Ranking[]{ranking2});
+        assertFalse(rankingUpdate.equals(rankingUpdate2));
+
+        // Same ranking contents equality.
+        ranking2 = createTestRanking(TEST_KEY, 123);
+        rankingUpdate2 = new NotificationRankingUpdate(
+                new NotificationListenerService.Ranking[]{ranking2});
+        assertTrue(rankingUpdate.equals(rankingUpdate2));
+    }
+}
diff --git a/core/tests/coretests/src/android/text/StaticLayoutTest.java b/core/tests/coretests/src/android/text/StaticLayoutTest.java
index 925da49..0ebf03f 100644
--- a/core/tests/coretests/src/android/text/StaticLayoutTest.java
+++ b/core/tests/coretests/src/android/text/StaticLayoutTest.java
@@ -24,7 +24,6 @@
 import android.graphics.Canvas;
 import android.graphics.Paint;
 import android.graphics.Paint.FontMetricsInt;
-import android.graphics.text.LineBreakConfig;
 import android.os.LocaleList;
 import android.platform.test.annotations.Presubmit;
 import android.text.Layout.Alignment;
@@ -926,24 +925,4 @@
         assertEquals(0, layout.getHeight(true));
         assertEquals(2, layout.getLineCount());
     }
-
-    @Test
-    public void testBuilder_autoPhraseBreaking() {
-        {
-            // setAutoPhraseBreaking true
-            LineBreakConfig lineBreakConfig = new LineBreakConfig.Builder()
-                    .setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_STYLE_NONE)
-                    .setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE)
-                    .setAutoPhraseBreaking(true)
-                    .build();
-            final String text = "これが正解。";
-            // Obtain.
-            StaticLayout.Builder builder = StaticLayout.Builder.obtain(text, 0,
-                    text.length(), mDefaultPaint, DEFAULT_OUTER_WIDTH);
-            builder.setLineBreakConfig(lineBreakConfig);
-            builder.build();
-            assertEquals(LineBreakConfig.LINE_BREAK_WORD_STYLE_PHRASE,
-                    builder.getLineBreakWordStyle());
-        }
-    }
 }
diff --git a/core/tests/coretests/src/android/util/LongSparseArrayTest.java b/core/tests/coretests/src/android/util/LongSparseArrayTest.java
index bf3f0f5..247fe37 100644
--- a/core/tests/coretests/src/android/util/LongSparseArrayTest.java
+++ b/core/tests/coretests/src/android/util/LongSparseArrayTest.java
@@ -51,4 +51,73 @@
                     .isFalse();
         }
     }
+
+    @Test
+    public void firstIndexOnOrAfter() {
+        final LongSparseArray<Object> longSparseArray = new LongSparseArray<>();
+
+        // Values don't matter for this test.
+        longSparseArray.put(51, new Object());
+        longSparseArray.put(10, new Object());
+        longSparseArray.put(59, new Object());
+
+        assertThat(longSparseArray.size()).isEqualTo(3);
+
+        // Testing any number arbitrarily smaller than 10.
+        assertThat(longSparseArray.firstIndexOnOrAfter(-141213)).isEqualTo(0);
+        for (long time = -43; time <= 10; time++) {
+            assertThat(longSparseArray.firstIndexOnOrAfter(time)).isEqualTo(0);
+        }
+
+        for (long time = 11; time <= 51; time++) {
+            assertThat(longSparseArray.firstIndexOnOrAfter(time)).isEqualTo(1);
+        }
+
+        for (long time = 52; time <= 59; time++) {
+            assertThat(longSparseArray.firstIndexOnOrAfter(time)).isEqualTo(2);
+        }
+
+        for (long time = 60; time <= 102; time++) {
+            assertThat(longSparseArray.firstIndexOnOrAfter(time)).isEqualTo(3);
+        }
+        // Testing any number arbitrarily larger than 59.
+        assertThat(longSparseArray.firstIndexOnOrAfter(15332)).isEqualTo(3);
+    }
+
+    @Test
+    public void lastIndexOnOrBefore() {
+        final LongSparseArray<Object> longSparseArray = new LongSparseArray<>();
+
+        // Values don't matter for this test.
+        longSparseArray.put(21, new Object());
+        longSparseArray.put(4, new Object());
+        longSparseArray.put(91, new Object());
+        longSparseArray.put(39, new Object());
+
+        assertThat(longSparseArray.size()).isEqualTo(4);
+
+        // Testing any number arbitrarily smaller than 4.
+        assertThat(longSparseArray.lastIndexOnOrBefore(-1478133)).isEqualTo(-1);
+        for (long time = -42; time < 4; time++) {
+            assertThat(longSparseArray.lastIndexOnOrBefore(time)).isEqualTo(-1);
+        }
+
+        for (long time = 4; time < 21; time++) {
+            assertThat(longSparseArray.lastIndexOnOrBefore(time)).isEqualTo(0);
+        }
+
+        for (long time = 21; time < 39; time++) {
+            assertThat(longSparseArray.lastIndexOnOrBefore(time)).isEqualTo(1);
+        }
+
+        for (long time = 39; time < 91; time++) {
+            assertThat(longSparseArray.lastIndexOnOrBefore(time)).isEqualTo(2);
+        }
+
+        for (long time = 91; time < 109; time++) {
+            assertThat(longSparseArray.lastIndexOnOrBefore(time)).isEqualTo(3);
+        }
+        // Testing any number arbitrarily larger than 91.
+        assertThat(longSparseArray.lastIndexOnOrBefore(1980732)).isEqualTo(3);
+    }
 }
diff --git a/core/tests/coretests/src/android/util/TimeSparseArrayTest.java b/core/tests/coretests/src/android/util/TimeSparseArrayTest.java
deleted file mode 100644
index c8e2364..0000000
--- a/core/tests/coretests/src/android/util/TimeSparseArrayTest.java
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * 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.
- */
-package android.util;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Tests for {@link TimeSparseArray}.
- * This class only tests subclass specific functionality. Tests for the super class
- * {@link LongSparseArray} should be covered under {@link LongSparseArrayTest}.
- */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class TimeSparseArrayTest {
-
-    @Test
-    public void closestIndexOnOrAfter() {
-        final TimeSparseArray<Object> timeSparseArray = new TimeSparseArray<>();
-
-        // Values don't matter for this test.
-        timeSparseArray.put(51, new Object());
-        timeSparseArray.put(10, new Object());
-        timeSparseArray.put(59, new Object());
-
-        assertThat(timeSparseArray.size()).isEqualTo(3);
-
-        // Testing any number arbitrarily smaller than 10.
-        assertThat(timeSparseArray.closestIndexOnOrAfter(-141213)).isEqualTo(0);
-        for (long time = -43; time <= 10; time++) {
-            assertThat(timeSparseArray.closestIndexOnOrAfter(time)).isEqualTo(0);
-        }
-
-        for (long time = 11; time <= 51; time++) {
-            assertThat(timeSparseArray.closestIndexOnOrAfter(time)).isEqualTo(1);
-        }
-
-        for (long time = 52; time <= 59; time++) {
-            assertThat(timeSparseArray.closestIndexOnOrAfter(time)).isEqualTo(2);
-        }
-
-        for (long time = 60; time <= 102; time++) {
-            assertThat(timeSparseArray.closestIndexOnOrAfter(time)).isEqualTo(3);
-        }
-        // Testing any number arbitrarily larger than 59.
-        assertThat(timeSparseArray.closestIndexOnOrAfter(15332)).isEqualTo(3);
-    }
-
-    @Test
-    public void closestIndexOnOrBefore() {
-        final TimeSparseArray<Object> timeSparseArray = new TimeSparseArray<>();
-
-        // Values don't matter for this test.
-        timeSparseArray.put(21, new Object());
-        timeSparseArray.put(4, new Object());
-        timeSparseArray.put(91, new Object());
-        timeSparseArray.put(39, new Object());
-
-        assertThat(timeSparseArray.size()).isEqualTo(4);
-
-        // Testing any number arbitrarily smaller than 4.
-        assertThat(timeSparseArray.closestIndexOnOrBefore(-1478133)).isEqualTo(-1);
-        for (long time = -42; time < 4; time++) {
-            assertThat(timeSparseArray.closestIndexOnOrBefore(time)).isEqualTo(-1);
-        }
-
-        for (long time = 4; time < 21; time++) {
-            assertThat(timeSparseArray.closestIndexOnOrBefore(time)).isEqualTo(0);
-        }
-
-        for (long time = 21; time < 39; time++) {
-            assertThat(timeSparseArray.closestIndexOnOrBefore(time)).isEqualTo(1);
-        }
-
-        for (long time = 39; time < 91; time++) {
-            assertThat(timeSparseArray.closestIndexOnOrBefore(time)).isEqualTo(2);
-        }
-
-        for (long time = 91; time < 109; time++) {
-            assertThat(timeSparseArray.closestIndexOnOrBefore(time)).isEqualTo(3);
-        }
-        // Testing any number arbitrarily larger than 91.
-        assertThat(timeSparseArray.closestIndexOnOrBefore(1980732)).isEqualTo(3);
-    }
-}
diff --git a/core/tests/coretests/src/android/util/apk/SourceStampVerifierTest.java b/core/tests/coretests/src/android/util/apk/SourceStampVerifierTest.java
index bc0bddb..e0c583d 100644
--- a/core/tests/coretests/src/android/util/apk/SourceStampVerifierTest.java
+++ b/core/tests/coretests/src/android/util/apk/SourceStampVerifierTest.java
@@ -100,7 +100,7 @@
         SourceStampVerificationResult result =
                 SourceStampVerifier.verify(mPrimaryApk.getAbsolutePath());
 
-        assertTrue(result.isPresent());
+        assertFalse(result.isPresent());
         assertFalse(result.isVerified());
         assertNull(result.getCertificate());
     }
diff --git a/core/tests/coretests/src/android/view/HapticScrollFeedbackProviderTest.java b/core/tests/coretests/src/android/view/HapticScrollFeedbackProviderTest.java
new file mode 100644
index 0000000..6bdb07d
--- /dev/null
+++ b/core/tests/coretests/src/android/view/HapticScrollFeedbackProviderTest.java
@@ -0,0 +1,389 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.view;
+
+import static android.view.HapticFeedbackConstants.SCROLL_ITEM_FOCUS;
+import static android.view.HapticFeedbackConstants.SCROLL_LIMIT;
+import static android.view.HapticFeedbackConstants.SCROLL_TICK;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+@Presubmit
+public final class HapticScrollFeedbackProviderTest {
+    private static final int INPUT_DEVICE_1 = 1;
+    private static final int INPUT_DEVICE_2 = 2;
+
+    private static final int TICK_INTERVAL_PIXELS = 100;
+
+    private TestView mView;
+    private long mCurrentTimeMillis = 1000; // arbitrary starting time value
+
+    private HapticScrollFeedbackProvider mProvider;
+
+    @Before
+    public void setUp() {
+        mView = new TestView(InstrumentationRegistry.getContext());
+        mProvider = new HapticScrollFeedbackProvider(mView, TICK_INTERVAL_PIXELS);
+    }
+
+    @Test
+    public void testSnapToItem() {
+        mProvider.onSnapToItem(createRotaryEncoderScrollEvent(), MotionEvent.AXIS_SCROLL);
+
+        assertOnlyFeedback(mView, HapticFeedbackConstants.SCROLL_ITEM_FOCUS);
+    }
+
+    @Test
+    public void testScrollLimit_start() {
+        mProvider.onScrollLimit(
+                createRotaryEncoderScrollEvent(), MotionEvent.AXIS_SCROLL, /* isStart= */ true);
+
+        assertOnlyFeedback(mView, HapticFeedbackConstants.SCROLL_LIMIT);
+    }
+
+    @Test
+    public void testScrollLimit_stop() {
+        mProvider.onScrollLimit(
+                createRotaryEncoderScrollEvent(), MotionEvent.AXIS_SCROLL, /* isStart= */ false);
+
+        assertOnlyFeedback(mView, HapticFeedbackConstants.SCROLL_LIMIT);
+    }
+
+    @Test
+    public void testScrollProgress_zeroTickInterval() {
+        mProvider =
+                new HapticScrollFeedbackProvider(
+                        mView, /* rotaryEncoderAxisScrollTickIntervalPixels= */ 0);
+
+        mProvider.onScrollProgress(
+                createRotaryEncoderScrollEvent(), MotionEvent.AXIS_SCROLL, /* deltaInPixels= */ 10);
+        mProvider.onScrollProgress(
+                createRotaryEncoderScrollEvent(), MotionEvent.AXIS_SCROLL, /* deltaInPixels= */ 30);
+        mProvider.onScrollProgress(
+                createRotaryEncoderScrollEvent(), MotionEvent.AXIS_SCROLL, /* deltaInPixels= */ 30);
+
+        assertNoFeedback(mView);
+    }
+
+    @Test
+    public void testScrollProgress_progressEqualsOrExceedsPositiveThreshold() {
+        mProvider =
+                new HapticScrollFeedbackProvider(
+                        mView, /* rotaryEncoderAxisScrollTickIntervalPixels= */ 100);
+
+        mProvider.onScrollProgress(
+                createRotaryEncoderScrollEvent(), MotionEvent.AXIS_SCROLL, /* deltaInPixels= */ 20);
+
+        assertNoFeedback(mView);
+
+        mProvider.onScrollProgress(
+                createRotaryEncoderScrollEvent(), MotionEvent.AXIS_SCROLL, /* deltaInPixels= */ 80);
+
+        assertOnlyFeedback(mView, HapticFeedbackConstants.SCROLL_TICK, 1);
+
+
+        mProvider.onScrollProgress(
+                createRotaryEncoderScrollEvent(), MotionEvent.AXIS_SCROLL, /* deltaInPixels= */ 80);
+        mProvider.onScrollProgress(
+                createRotaryEncoderScrollEvent(), MotionEvent.AXIS_SCROLL, /* deltaInPixels= */ 40);
+
+        assertOnlyFeedback(mView, HapticFeedbackConstants.SCROLL_TICK, 2);
+    }
+
+    @Test
+    public void testScrollProgress_progressEqualsOrExceedsNegativeThreshold() {
+        mProvider =
+                new HapticScrollFeedbackProvider(
+                        mView, /* rotaryEncoderAxisScrollTickIntervalPixels= */ 100);
+
+        mProvider.onScrollProgress(
+                createRotaryEncoderScrollEvent(),
+                MotionEvent.AXIS_SCROLL,
+                /* deltaInPixels= */ -20);
+
+        assertNoFeedback(mView);
+
+        mProvider.onScrollProgress(
+                createRotaryEncoderScrollEvent(),
+                MotionEvent.AXIS_SCROLL,
+                /* deltaInPixels= */ -80);
+
+        assertOnlyFeedback(mView, HapticFeedbackConstants.SCROLL_TICK, 1);
+
+        mProvider.onScrollProgress(
+                createRotaryEncoderScrollEvent(),
+                MotionEvent.AXIS_SCROLL,
+                /* deltaInPixels= */ -70);
+        mProvider.onScrollProgress(
+                createRotaryEncoderScrollEvent(),
+                MotionEvent.AXIS_SCROLL,
+                /* deltaInPixels= */ -40);
+
+        assertOnlyFeedback(mView, HapticFeedbackConstants.SCROLL_TICK, 2);
+    }
+
+    @Test
+    public void testScrollProgress_positiveAndNegativeProgresses() {
+        mProvider =
+                new HapticScrollFeedbackProvider(
+                        mView, /* rotaryEncoderAxisScrollTickIntervalPixels= */ 100);
+
+        mProvider.onScrollProgress(
+                createRotaryEncoderScrollEvent(), MotionEvent.AXIS_SCROLL, /* deltaInPixels= */ 20);
+        mProvider.onScrollProgress(
+                createRotaryEncoderScrollEvent(),
+                MotionEvent.AXIS_SCROLL,
+                /* deltaInPixels= */ -90);
+
+        assertNoFeedback(mView);
+
+        mProvider.onScrollProgress(
+                createRotaryEncoderScrollEvent(), MotionEvent.AXIS_SCROLL, /* deltaInPixels= */ 10);
+
+        assertNoFeedback(mView);
+
+        mProvider.onScrollProgress(
+                createRotaryEncoderScrollEvent(),
+                MotionEvent.AXIS_SCROLL,
+                /* deltaInPixels= */ -50);
+
+        assertOnlyFeedback(mView, HapticFeedbackConstants.SCROLL_TICK, 1);
+
+        mProvider.onScrollProgress(
+                createRotaryEncoderScrollEvent(), MotionEvent.AXIS_SCROLL, /* deltaInPixels= */ 40);
+        mProvider.onScrollProgress(
+                createRotaryEncoderScrollEvent(), MotionEvent.AXIS_SCROLL, /* deltaInPixels= */ 50);
+        mProvider.onScrollProgress(
+                createRotaryEncoderScrollEvent(), MotionEvent.AXIS_SCROLL, /* deltaInPixels= */ 60);
+
+        assertOnlyFeedback(mView, HapticFeedbackConstants.SCROLL_TICK, 2);
+    }
+
+    @Test
+    public void testScrollProgress_singleProgressExceedsThreshold() {
+        mProvider =
+                new HapticScrollFeedbackProvider(
+                        mView, /* rotaryEncoderAxisScrollTickIntervalPixels= */ 100);
+
+        mProvider.onScrollProgress(
+                createRotaryEncoderScrollEvent(),
+                MotionEvent.AXIS_SCROLL,
+                /* deltaInPixels= */ 1000);
+
+        assertOnlyFeedback(mView, HapticFeedbackConstants.SCROLL_TICK, 1);
+    }
+
+    @Test
+    public void testScrollLimit_startAndEndLimit_playsOnlyOneFeedback() {
+        mProvider.onScrollLimit(
+                createRotaryEncoderScrollEvent(), MotionEvent.AXIS_SCROLL, /* isStart= */ false);
+        mProvider.onScrollLimit(
+                createRotaryEncoderScrollEvent(), MotionEvent.AXIS_SCROLL, /* isStart= */ true);
+
+        assertOnlyFeedback(mView, HapticFeedbackConstants.SCROLL_LIMIT);
+    }
+
+    @Test
+    public void testScrollLimit_doubleStartLimit_playsOnlyOneFeedback() {
+        mProvider.onScrollLimit(
+                createRotaryEncoderScrollEvent(), MotionEvent.AXIS_SCROLL, /* isStart= */ true);
+        mProvider.onScrollLimit(
+                createRotaryEncoderScrollEvent(), MotionEvent.AXIS_SCROLL, /* isStart= */ true);
+
+        assertOnlyFeedback(mView, HapticFeedbackConstants.SCROLL_LIMIT);
+    }
+
+    @Test
+    public void testScrollLimit_doubleEndLimit_playsOnlyOneFeedback() {
+        mProvider.onScrollLimit(
+                createRotaryEncoderScrollEvent(), MotionEvent.AXIS_SCROLL, /* isStart= */ false);
+        mProvider.onScrollLimit(
+                createRotaryEncoderScrollEvent(), MotionEvent.AXIS_SCROLL, /* isStart= */ false);
+
+        assertOnlyFeedback(mView, HapticFeedbackConstants.SCROLL_LIMIT);
+    }
+
+    @Test
+    public void testScrollLimit_enabledWithProgress() {
+        mProvider.onScrollLimit(
+                createRotaryEncoderScrollEvent(), MotionEvent.AXIS_SCROLL, /* isStart= */ false);
+
+        mProvider.onScrollProgress(
+                createRotaryEncoderScrollEvent(), MotionEvent.AXIS_SCROLL, /* deltaInPixels= */ 80);
+        mProvider.onScrollLimit(
+                createRotaryEncoderScrollEvent(), MotionEvent.AXIS_SCROLL, /* isStart= */ false);
+
+        assertOnlyFeedback(mView, HapticFeedbackConstants.SCROLL_LIMIT, 2);
+    }
+
+    @Test
+    public void testScrollLimit_enabledWithSnap() {
+        mProvider.onScrollLimit(
+                createRotaryEncoderScrollEvent(), MotionEvent.AXIS_SCROLL, /* isStart= */ false);
+
+        mProvider.onSnapToItem(createRotaryEncoderScrollEvent(), MotionEvent.AXIS_SCROLL);
+        mProvider.onScrollLimit(
+                createRotaryEncoderScrollEvent(), MotionEvent.AXIS_SCROLL, /* isStart= */ false);
+
+        assertFeedbackCount(mView, HapticFeedbackConstants.SCROLL_LIMIT, 2);
+    }
+
+    @Test
+    public void testScrollLimit_enabledWithDissimilarSnap() {
+        mProvider.onScrollLimit(
+                createRotaryEncoderScrollEvent(), MotionEvent.AXIS_SCROLL, /* isStart= */ false);
+
+        mProvider.onSnapToItem(createTouchMoveEvent(), MotionEvent.AXIS_X);
+        mProvider.onScrollLimit(
+                createRotaryEncoderScrollEvent(), MotionEvent.AXIS_SCROLL, /* isStart= */ false);
+
+        assertFeedbackCount(mView, HapticFeedbackConstants.SCROLL_LIMIT, 2);
+    }
+
+    @Test
+    public void testScrollLimit_enabledWithDissimilarProgress() {
+        mProvider.onScrollLimit(
+                createRotaryEncoderScrollEvent(), MotionEvent.AXIS_SCROLL, /* isStart= */ false);
+
+        mProvider.onScrollProgress(
+                createTouchMoveEvent(), MotionEvent.AXIS_SCROLL, /* deltaInPixels= */ 80);
+        mProvider.onScrollLimit(
+                createRotaryEncoderScrollEvent(), MotionEvent.AXIS_SCROLL, /* isStart= */ false);
+
+        assertOnlyFeedback(mView, HapticFeedbackConstants.SCROLL_LIMIT, 2);
+    }
+
+    @Test
+    public void testScrollLimit_enabledWithDissimilarLimit() {
+        mProvider.onScrollLimit(
+                createRotaryEncoderScrollEvent(), MotionEvent.AXIS_SCROLL, /* isStart= */ false);
+
+        mProvider.onScrollLimit(createTouchMoveEvent(), MotionEvent.AXIS_SCROLL, false);
+        mProvider.onScrollLimit(
+                createRotaryEncoderScrollEvent(), MotionEvent.AXIS_SCROLL, /* isStart= */ false);
+
+        assertOnlyFeedback(mView, HapticFeedbackConstants.SCROLL_LIMIT, 3);
+    }
+
+    @Test
+    public void testScrollLimit_enabledWithMotionFromDifferentDeviceId() {
+        mProvider.onScrollLimit(
+                createRotaryEncoderScrollEvent(INPUT_DEVICE_1),
+                MotionEvent.AXIS_SCROLL,
+                /* isStart= */ false);
+
+        mProvider.onScrollLimit(
+                createRotaryEncoderScrollEvent(INPUT_DEVICE_2),
+                MotionEvent.AXIS_SCROLL,
+                /* isStart= */ false);
+        mProvider.onScrollLimit(
+                createRotaryEncoderScrollEvent(INPUT_DEVICE_1),
+                MotionEvent.AXIS_SCROLL,
+                /* isStart= */ false);
+
+        assertOnlyFeedback(mView, HapticFeedbackConstants.SCROLL_LIMIT, 3);
+    }
+
+    private void assertNoFeedback(TestView view) {
+        for (int feedback : new int[] {SCROLL_ITEM_FOCUS, SCROLL_LIMIT, SCROLL_TICK}) {
+            assertFeedbackCount(view, feedback, 0);
+        }
+    }
+
+    private void assertOnlyFeedback(TestView view, int expectedFeedback) {
+        assertOnlyFeedback(view, expectedFeedback, /* expectedCount= */ 1);
+    }
+
+    private void assertOnlyFeedback(TestView view, int expectedFeedback, int expectedCount) {
+        for (int feedback : new int[] {SCROLL_ITEM_FOCUS, SCROLL_LIMIT, SCROLL_TICK}) {
+            assertFeedbackCount(view, feedback, (feedback == expectedFeedback) ? expectedCount : 0);
+        }
+    }
+
+    private void assertFeedbackCount(TestView view, int feedback, int expectedCount) {
+        int count = view.mFeedbackCount.getOrDefault(feedback, 0);
+        assertThat(count).isEqualTo(expectedCount);
+    }
+
+    private MotionEvent createTouchMoveEvent() {
+        long downTime = mCurrentTimeMillis;
+        long eventTime = mCurrentTimeMillis + 2; // arbitrary increment from the down time.
+        ++mCurrentTimeMillis;
+        return MotionEvent.obtain(
+                downTime , eventTime, MotionEvent.ACTION_MOVE, /* x= */ 3, /* y= */ 5, 0);
+    }
+
+    private MotionEvent createRotaryEncoderScrollEvent() {
+        return createRotaryEncoderScrollEvent(INPUT_DEVICE_1);
+    }
+
+    private MotionEvent createRotaryEncoderScrollEvent(int deviceId) {
+        MotionEvent.PointerProperties props = new MotionEvent.PointerProperties();
+        props.id = 0;
+
+        MotionEvent.PointerCoords coords = new MotionEvent.PointerCoords();
+        coords.setAxisValue(MotionEvent.AXIS_SCROLL, 20);
+
+        return MotionEvent.obtain(0 /* downTime */,
+                ++mCurrentTimeMillis,
+                MotionEvent.ACTION_SCROLL,
+                /* pointerCount= */ 1,
+                new MotionEvent.PointerProperties[] {props},
+                new MotionEvent.PointerCoords[] {coords},
+                /* metaState= */ 0,
+                /* buttonState= */ 0,
+                /* xPrecision= */ 0,
+                /* yPrecision= */ 0,
+                deviceId,
+                /* edgeFlags= */ 0,
+                InputDevice.SOURCE_ROTARY_ENCODER,
+                /* flags= */ 0);
+    }
+
+    private static class TestView extends View  {
+        final Map<Integer, Integer> mFeedbackCount = new HashMap<>();
+
+        TestView(Context context) {
+            super(context);
+        }
+
+        @Override
+        public boolean performHapticFeedback(int feedback) {
+            if (!mFeedbackCount.containsKey(feedback)) {
+                mFeedbackCount.put(feedback, 0);
+            }
+            mFeedbackCount.put(feedback, mFeedbackCount.get(feedback) + 1);
+            return true;
+        }
+    }
+}
diff --git a/core/tests/coretests/src/android/view/ViewAttachTest.java b/core/tests/coretests/src/android/view/ViewAttachTest.java
index 1a8dd99..1da724a 100644
--- a/core/tests/coretests/src/android/view/ViewAttachTest.java
+++ b/core/tests/coretests/src/android/view/ViewAttachTest.java
@@ -92,4 +92,43 @@
             assertFalse(shouldDrawRoundScrollbars);
         }
     }
+
+    /**
+     * Make sure that on any attached view, if the view is full-screen and hosted
+     * on a round device, the round scrollbars will be displayed even if the activity
+     * window is offset.
+     *
+     * @throws Throwable
+     */
+    @UiThreadTest
+    public void testRoundScrollbarsWithMargins() throws Throwable {
+        final ViewAttachTestActivity activity = getActivity();
+        final View rootView = activity.getWindow().getDecorView();
+        final WindowManager.LayoutParams params =
+                new WindowManager.LayoutParams(
+                    rootView.getWidth(),
+                    rootView.getHeight(),
+                    50, /* xPosition */
+                    0, /* yPosition */
+                    WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY,
+                    WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN,
+                    PixelFormat.TRANSLUCENT);
+
+        rootView.setLayoutParams(params);
+
+        // Configure margins to make sure they don't cause issues configuring rounded scrollbars.
+        final ViewGroup.MarginLayoutParams lp = new ViewGroup.MarginLayoutParams(1, 1);
+        lp.setMargins(1, 2, 3, 4);
+        rootView.setLayoutParams(lp);
+
+        View contentView = activity.findViewById(R.id.view_attach_view);
+        boolean shouldDrawRoundScrollbars = contentView.shouldDrawRoundScrollbar();
+
+        if (activity.getResources().getConfiguration().isScreenRound()) {
+            assertTrue(shouldDrawRoundScrollbars);
+        } else {
+            // Never draw round scrollbars on non-round devices.
+            assertFalse(shouldDrawRoundScrollbars);
+        }
+    }
 }
diff --git a/core/tests/coretests/src/android/view/WindowInsetsTest.java b/core/tests/coretests/src/android/view/WindowInsetsTest.java
index b4ba23c..69abf5f 100644
--- a/core/tests/coretests/src/android/view/WindowInsetsTest.java
+++ b/core/tests/coretests/src/android/view/WindowInsetsTest.java
@@ -40,14 +40,14 @@
     @Test
     public void systemWindowInsets_afterConsuming_isConsumed() {
         assertTrue(new WindowInsets(WindowInsets.createCompatTypeMap(new Rect(1, 2, 3, 4)), null,
-                null, false, false, 0, null, null, null, null,
+                null, false, 0, 0, null, null, null, null,
                 WindowInsets.Type.systemBars(), false)
                 .consumeSystemWindowInsets().isConsumed());
     }
 
     @Test
     public void multiNullConstructor_isConsumed() {
-        assertTrue(new WindowInsets(null, null, null, false, false, 0, null, null, null, null,
+        assertTrue(new WindowInsets(null, null, null, false, 0, 0, null, null, null, null,
                 WindowInsets.Type.systemBars(), false).isConsumed());
     }
 
@@ -63,7 +63,7 @@
         boolean[] visible = new boolean[SIZE];
         WindowInsets.assignCompatInsets(maxInsets, new Rect(0, 10, 0, 0));
         WindowInsets.assignCompatInsets(insets, new Rect(0, 0, 0, 0));
-        WindowInsets windowInsets = new WindowInsets(insets, maxInsets, visible, false, false,
+        WindowInsets windowInsets = new WindowInsets(insets, maxInsets, visible, false, 0,
                 0, null, null, null, DisplayShape.NONE, systemBars(),
                 true /* compatIgnoreVisibility */);
         assertEquals(Insets.of(0, 10, 0, 0), windowInsets.getSystemWindowInsets());
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityManagerTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityManagerTest.java
index 7cbf3ffa..84252f9 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityManagerTest.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityManagerTest.java
@@ -32,6 +32,7 @@
 import android.app.PendingIntent;
 import android.app.RemoteAction;
 import android.content.Intent;
+import android.content.pm.ParceledListSlice;
 import android.graphics.drawable.Icon;
 import android.os.UserHandle;
 
@@ -135,7 +136,7 @@
 
         // configure the mock service behavior
         when(mMockService.getInstalledAccessibilityServiceList(anyInt()))
-                .thenReturn(expectedServices);
+                .thenReturn(new ParceledListSlice<>(expectedServices));
 
         // invoke the method under test
         AccessibilityManager manager = createManager(true);
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
index 6d635af..3d4918b 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
@@ -58,7 +58,7 @@
 
     // The number of flags held in boolean properties. Their values should also be double-checked
     // in the methods above.
-    private static final int NUM_BOOLEAN_PROPERTIES = 26;
+    private static final int NUM_BOOLEAN_PROPERTIES = 27;
 
     @Test
     public void testStandardActions_serializationFlagIsValid() {
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java
index 756888f..610b8ae 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java
@@ -225,8 +225,16 @@
     }
 
     @Override
-    public void attachAccessibilityOverlayToDisplay(int displayId, SurfaceControl sc) {}
+    public void attachAccessibilityOverlayToDisplay(
+            int interactionId,
+            int displayId,
+            SurfaceControl sc,
+            IAccessibilityInteractionConnectionCallback callback) {}
 
     @Override
-    public void attachAccessibilityOverlayToWindow(int accessibilityWindowId, SurfaceControl sc) {}
+    public void attachAccessibilityOverlayToWindow(
+            int interactionId,
+            int accessibilityWindowId,
+            SurfaceControl sc,
+            IAccessibilityInteractionConnectionCallback callback) {}
 }
diff --git a/core/tests/coretests/src/android/view/accessibility/TEST_MAPPING b/core/tests/coretests/src/android/view/accessibility/TEST_MAPPING
new file mode 100644
index 0000000..1c67399
--- /dev/null
+++ b/core/tests/coretests/src/android/view/accessibility/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "imports": [
+    {
+      "path": "frameworks/base/services/accessibility/TEST_MAPPING"
+    }
+  ]
+}
diff --git a/core/tests/coretests/src/android/view/autofill/AutofillFeatureFlagsTest.java b/core/tests/coretests/src/android/view/autofill/AutofillFeatureFlagsTest.java
index f9b3239..481993e5 100644
--- a/core/tests/coretests/src/android/view/autofill/AutofillFeatureFlagsTest.java
+++ b/core/tests/coretests/src/android/view/autofill/AutofillFeatureFlagsTest.java
@@ -51,12 +51,26 @@
         assertThat(fillDialogHints[1]).isEqualTo("creditCardNumber");
     }
 
+    @Test
+    public void testIsCredentialManagerEnabled() {
+        setCredentialManagerEnabled(false);
+        assertThat(AutofillFeatureFlags.isCredentialManagerEnabled()).isFalse();
+        setCredentialManagerEnabled(true);
+        assertThat(AutofillFeatureFlags.isCredentialManagerEnabled()).isTrue();
+    }
+
     private static void setFillDialogHints(String value) {
         setDeviceConfig(
                 AutofillFeatureFlags.DEVICE_CONFIG_AUTOFILL_DIALOG_HINTS,
                 value);
     }
 
+    private static void setCredentialManagerEnabled(boolean value) {
+        setDeviceConfig(
+                AutofillFeatureFlags.DEVICE_CONFIG_AUTOFILL_CREDENTIAL_MANAGER_ENABLED,
+                String.valueOf(value));
+    }
+
     private static void setDeviceConfig(String key, String value) {
         DeviceConfig.setProperty(
                 DeviceConfig.NAMESPACE_AUTOFILL, key, value, /* makeDefault */ false);
diff --git a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureManagerTest.java b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureManagerTest.java
index 17ed4c4..101f7c2 100644
--- a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureManagerTest.java
+++ b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureManagerTest.java
@@ -15,14 +15,17 @@
  */
 package android.view.contentcapture;
 
+import static android.view.contentcapture.ContentCaptureEvent.TYPE_SESSION_STARTED;
+
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.mockito.Mockito.mock;
 import static org.testng.Assert.assertThrows;
 
 import android.content.ContentCaptureOptions;
 import android.content.Context;
 
+import com.android.internal.util.RingBuffer;
+
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -37,9 +40,15 @@
 @RunWith(MockitoJUnitRunner.class)
 public class ContentCaptureManagerTest {
 
+    private static final int BUFFER_SIZE = 100;
+
+    private static final ContentCaptureOptions EMPTY_OPTIONS = new ContentCaptureOptions(null);
+
     @Mock
     private Context mMockContext;
 
+    @Mock private IContentCaptureManager mMockContentCaptureManager;
+
     @Test
     public void testConstructor_invalidParametersThrowsException() {
         assertThrows(NullPointerException.class,
@@ -48,11 +57,65 @@
     }
 
     @Test
+    public void testConstructor_contentProtection_default_bufferNotCreated() {
+        ContentCaptureManager manager =
+                new ContentCaptureManager(mMockContext, mMockContentCaptureManager, EMPTY_OPTIONS);
+
+        assertThat(manager.getContentProtectionEventBuffer()).isNull();
+    }
+
+    @Test
+    public void testConstructor_contentProtection_disabled_bufferNotCreated() {
+        ContentCaptureOptions options =
+                createOptions(
+                        new ContentCaptureOptions.ContentProtectionOptions(
+                                /* enableReceiver= */ false, BUFFER_SIZE));
+
+        ContentCaptureManager manager =
+                new ContentCaptureManager(mMockContext, mMockContentCaptureManager, options);
+
+        assertThat(manager.getContentProtectionEventBuffer()).isNull();
+    }
+
+    @Test
+    public void testConstructor_contentProtection_invalidBufferSize_bufferNotCreated() {
+        ContentCaptureOptions options =
+                createOptions(
+                        new ContentCaptureOptions.ContentProtectionOptions(
+                                /* enableReceiver= */ true, /* bufferSize= */ 0));
+
+        ContentCaptureManager manager =
+                new ContentCaptureManager(mMockContext, mMockContentCaptureManager, options);
+
+        assertThat(manager.getContentProtectionEventBuffer()).isNull();
+    }
+
+    @Test
+    public void testConstructor_contentProtection_enabled_bufferCreated() {
+        ContentCaptureOptions options =
+                createOptions(
+                        new ContentCaptureOptions.ContentProtectionOptions(
+                                /* enableReceiver= */ true, BUFFER_SIZE));
+
+        ContentCaptureManager manager =
+                new ContentCaptureManager(mMockContext, mMockContentCaptureManager, options);
+        RingBuffer<ContentCaptureEvent> buffer = manager.getContentProtectionEventBuffer();
+
+        assertThat(buffer).isNotNull();
+        ContentCaptureEvent[] expected = new ContentCaptureEvent[BUFFER_SIZE];
+        int offset = 3;
+        for (int i = 0; i < BUFFER_SIZE + offset; i++) {
+            ContentCaptureEvent event = new ContentCaptureEvent(i, TYPE_SESSION_STARTED);
+            buffer.append(event);
+            expected[(i + BUFFER_SIZE - offset) % BUFFER_SIZE] = event;
+        }
+        assertThat(buffer.toArray()).isEqualTo(expected);
+    }
+
+    @Test
     public void testRemoveData_invalidParametersThrowsException() {
-        final IContentCaptureManager mockService = mock(IContentCaptureManager.class);
-        final ContentCaptureOptions options = new ContentCaptureOptions(null);
         final ContentCaptureManager manager =
-                new ContentCaptureManager(mMockContext, mockService, options);
+                new ContentCaptureManager(mMockContext, mMockContentCaptureManager, EMPTY_OPTIONS);
 
         assertThrows(NullPointerException.class, () -> manager.removeData(null));
     }
@@ -60,10 +123,8 @@
     @Test
     @SuppressWarnings("GuardedBy")
     public void testFlushViewTreeAppearingEventDisabled_setAndGet() {
-        final IContentCaptureManager mockService = mock(IContentCaptureManager.class);
-        final ContentCaptureOptions options = new ContentCaptureOptions(null);
         final ContentCaptureManager manager =
-                new ContentCaptureManager(mMockContext, mockService, options);
+                new ContentCaptureManager(mMockContext, mMockContentCaptureManager, EMPTY_OPTIONS);
 
         assertThat(manager.getFlushViewTreeAppearingEventDisabled()).isFalse();
         manager.setFlushViewTreeAppearingEventDisabled(true);
@@ -71,4 +132,18 @@
         manager.setFlushViewTreeAppearingEventDisabled(false);
         assertThat(manager.getFlushViewTreeAppearingEventDisabled()).isFalse();
     }
+
+    private ContentCaptureOptions createOptions(
+            ContentCaptureOptions.ContentProtectionOptions contentProtectionOptions) {
+        return new ContentCaptureOptions(
+                /* loggingLevel= */ 0,
+                /* maxBufferSize= */ 0,
+                /* idleFlushingFrequencyMs= */ 0,
+                /* textChangeFlushingFrequencyMs= */ 0,
+                /* logHistorySize= */ 0,
+                /* disableFlushForViewTreeAppearing= */ false,
+                /* enableReceiver= */ true,
+                contentProtectionOptions,
+                /* whitelistedComponents= */ null);
+    }
 }
diff --git a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java
index 27d58b8..23b9b9b 100644
--- a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java
+++ b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java
@@ -27,9 +27,12 @@
 import android.view.autofill.AutofillId;
 import android.view.contentcapture.ViewNode.ViewStructureImpl;
 
+import com.google.common.collect.ImmutableMap;
+
 import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
 import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
 
+import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TestRule;
@@ -37,6 +40,8 @@
 import org.mockito.Mock;
 import org.mockito.junit.MockitoJUnitRunner;
 
+import java.util.Map;
+
 /**
  * Unit tests for {@link ContentCaptureSession}.
  *
@@ -126,6 +131,7 @@
                 () -> mSession1.notifyViewsDisappeared(new AutofillId(42, 108), new long[] {666}));
     }
 
+    @Ignore("b/286134492")
     @Test
     public void testNotifyViewsDisappeared_noSendTreeEventBeforeU() {
         MyContentCaptureSession session = new MyContentCaptureSession(121);
@@ -135,6 +141,7 @@
         assertThat(session.mInternalNotifyViewTreeEventFinishedCount).isEqualTo(0);
     }
 
+    @Ignore("b/286134492")
     @EnableCompatChanges({ContentCaptureSession.NOTIFY_NODES_DISAPPEAR_NOW_SENDS_TREE_EVENTS})
     @Test
     public void testNotifyViewsDisappeared_sendTreeEventSinceU() {
@@ -145,6 +152,34 @@
         assertThat(session.mInternalNotifyViewTreeEventFinishedCount).isEqualTo(1);
     }
 
+    @Test
+    public void testGetFlushReasonAsString() {
+        int invalidFlushReason = ContentCaptureSession.FLUSH_REASON_VIEW_TREE_APPEARED + 1;
+        Map<Integer, String> expectedMap =
+                new ImmutableMap.Builder<Integer, String>()
+                        .put(ContentCaptureSession.FLUSH_REASON_FULL, "FULL")
+                        .put(ContentCaptureSession.FLUSH_REASON_VIEW_ROOT_ENTERED, "VIEW_ROOT")
+                        .put(ContentCaptureSession.FLUSH_REASON_SESSION_STARTED, "STARTED")
+                        .put(ContentCaptureSession.FLUSH_REASON_SESSION_FINISHED, "FINISHED")
+                        .put(ContentCaptureSession.FLUSH_REASON_IDLE_TIMEOUT, "IDLE")
+                        .put(ContentCaptureSession.FLUSH_REASON_TEXT_CHANGE_TIMEOUT, "TEXT_CHANGE")
+                        .put(ContentCaptureSession.FLUSH_REASON_SESSION_CONNECTED, "CONNECTED")
+                        .put(ContentCaptureSession.FLUSH_REASON_FORCE_FLUSH, "FORCE_FLUSH")
+                        .put(
+                                ContentCaptureSession.FLUSH_REASON_VIEW_TREE_APPEARING,
+                                "VIEW_TREE_APPEARING")
+                        .put(
+                                ContentCaptureSession.FLUSH_REASON_VIEW_TREE_APPEARED,
+                                "VIEW_TREE_APPEARED")
+                        .put(invalidFlushReason, "UNKNOWN-" + invalidFlushReason)
+                        .build();
+
+        expectedMap.forEach(
+                (reason, expected) ->
+                        assertThat(ContentCaptureSession.getFlushReasonAsString(reason))
+                                .isEqualTo(expected));
+    }
+
     // Cannot use @Spy because we need to pass the session id on constructor
     private class MyContentCaptureSession extends ContentCaptureSession {
         int mInternalNotifyViewTreeEventStartedCount = 0;
diff --git a/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java b/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java
new file mode 100644
index 0000000..3373b8b
--- /dev/null
+++ b/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java
@@ -0,0 +1,360 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.view.contentcapture;
+
+import static android.view.contentcapture.ContentCaptureEvent.TYPE_SESSION_STARTED;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+
+import android.content.ComponentName;
+import android.content.ContentCaptureOptions;
+import android.content.Context;
+import android.content.pm.ParceledListSlice;
+import android.os.Handler;
+import android.os.Looper;
+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.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.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Test for {@link MainContentCaptureSession}.
+ *
+ * <p>Run with: {@code atest
+ * FrameworksCoreTests:android.view.contentcapture.MainContentCaptureSessionTest}
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class MainContentCaptureSessionTest {
+
+    private static final int BUFFER_SIZE = 100;
+
+    private static final int REASON = 123;
+
+    private static final ContentCaptureEvent EVENT =
+            new ContentCaptureEvent(/* sessionId= */ 0, TYPE_SESSION_STARTED);
+
+    private static final ComponentName COMPONENT_NAME =
+            new ComponentName("com.test.package", "TestClass");
+
+    private static final Context sContext = ApplicationProvider.getApplicationContext();
+
+    private static final ContentCaptureManager.StrippedContext sStrippedContext =
+            new ContentCaptureManager.StrippedContext(sContext);
+
+    @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+
+    @Mock private IContentCaptureManager mMockSystemServerInterface;
+
+    @Mock private ContentProtectionEventProcessor mMockContentProtectionEventProcessor;
+
+    @Mock private IContentCaptureDirectManager mMockContentCaptureDirectManager;
+
+    @Test
+    public void onSessionStarted_contentProtectionEnabled_processorCreated() {
+        MainContentCaptureSession session = createSession();
+        assertThat(session.mContentProtectionEventProcessor).isNull();
+
+        session.onSessionStarted(/* resultCode= */ 0, /* binder= */ null);
+
+        assertThat(session.mContentProtectionEventProcessor).isNotNull();
+    }
+
+    @Test
+    public void onSessionStarted_contentProtectionDisabled_processorNotCreated() {
+        MainContentCaptureSession session =
+                createSession(
+                        /* enableContentCaptureReceiver= */ true,
+                        /* enableContentProtectionReceiver= */ false);
+        session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor;
+
+        session.onSessionStarted(/* resultCode= */ 0, /* binder= */ null);
+
+        assertThat(session.mContentProtectionEventProcessor).isNull();
+        verifyZeroInteractions(mMockContentProtectionEventProcessor);
+    }
+
+    @Test
+    public void onSessionStarted_contentProtectionNoBuffer_processorNotCreated() {
+        ContentCaptureOptions options =
+                createOptions(
+                        /* enableContentCaptureReceiver= */ true,
+                        new ContentCaptureOptions.ContentProtectionOptions(
+                                /* enableReceiver= */ true, -BUFFER_SIZE));
+        MainContentCaptureSession session = createSession(options);
+        session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor;
+
+        session.onSessionStarted(/* resultCode= */ 0, /* binder= */ null);
+
+        assertThat(session.mContentProtectionEventProcessor).isNull();
+        verifyZeroInteractions(mMockContentProtectionEventProcessor);
+    }
+
+    @Test
+    public void onSessionStarted_noComponentName_processorNotCreated() {
+        MainContentCaptureSession session = createSession();
+        session.mComponentName = null;
+
+        session.onSessionStarted(/* resultCode= */ 0, /* binder= */ null);
+
+        assertThat(session.mContentProtectionEventProcessor).isNull();
+    }
+
+    @Test
+    public void sendEvent_contentCaptureDisabled_contentProtectionDisabled() {
+        MainContentCaptureSession session =
+                createSession(
+                        /* enableContentCaptureReceiver= */ false,
+                        /* enableContentProtectionReceiver= */ false);
+        session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor;
+
+        session.sendEvent(EVENT);
+
+        verifyZeroInteractions(mMockContentProtectionEventProcessor);
+        assertThat(session.mEvents).isNull();
+    }
+
+    @Test
+    public void sendEvent_contentCaptureDisabled_contentProtectionEnabled() {
+        MainContentCaptureSession session =
+                createSession(
+                        /* enableContentCaptureReceiver= */ false,
+                        /* enableContentProtectionReceiver= */ true);
+        session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor;
+
+        session.sendEvent(EVENT);
+
+        verify(mMockContentProtectionEventProcessor).processEvent(EVENT);
+        assertThat(session.mEvents).isNull();
+    }
+
+    @Test
+    public void sendEvent_contentCaptureEnabled_contentProtectionDisabled() {
+        MainContentCaptureSession session =
+                createSession(
+                        /* enableContentCaptureReceiver= */ true,
+                        /* enableContentProtectionReceiver= */ false);
+        session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor;
+
+        session.sendEvent(EVENT);
+
+        verifyZeroInteractions(mMockContentProtectionEventProcessor);
+        assertThat(session.mEvents).isNotNull();
+        assertThat(session.mEvents).containsExactly(EVENT);
+    }
+
+    @Test
+    public void sendEvent_contentCaptureEnabled_contentProtectionEnabled() {
+        MainContentCaptureSession session = createSession();
+        session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor;
+
+        session.sendEvent(EVENT);
+
+        verify(mMockContentProtectionEventProcessor).processEvent(EVENT);
+        assertThat(session.mEvents).isNotNull();
+        assertThat(session.mEvents).containsExactly(EVENT);
+    }
+
+    @Test
+    public void sendEvent_contentProtectionEnabled_processorNotCreated() {
+        MainContentCaptureSession session =
+                createSession(
+                        /* enableContentCaptureReceiver= */ false,
+                        /* enableContentProtectionReceiver= */ true);
+
+        session.sendEvent(EVENT);
+
+        verifyZeroInteractions(mMockContentProtectionEventProcessor);
+        assertThat(session.mEvents).isNull();
+    }
+
+    @Test
+    public void flush_contentCaptureDisabled_contentProtectionDisabled() throws Exception {
+        ContentCaptureOptions options =
+                createOptions(
+                        /* enableContentCaptureReceiver= */ false,
+                        /* enableContentProtectionReceiver= */ false);
+        MainContentCaptureSession session = createSession(options);
+        session.mEvents = new ArrayList<>(Arrays.asList(EVENT));
+        session.mDirectServiceInterface = mMockContentCaptureDirectManager;
+
+        session.flush(REASON);
+
+        verifyZeroInteractions(mMockContentProtectionEventProcessor);
+        verifyZeroInteractions(mMockContentCaptureDirectManager);
+        assertThat(session.mEvents).containsExactly(EVENT);
+    }
+
+    @Test
+    public void flush_contentCaptureDisabled_contentProtectionEnabled() {
+        MainContentCaptureSession session =
+                createSession(
+                        /* enableContentCaptureReceiver= */ false,
+                        /* enableContentProtectionReceiver= */ true);
+        session.mEvents = new ArrayList<>(Arrays.asList(EVENT));
+        session.mDirectServiceInterface = mMockContentCaptureDirectManager;
+
+        session.flush(REASON);
+
+        verifyZeroInteractions(mMockContentProtectionEventProcessor);
+        verifyZeroInteractions(mMockContentCaptureDirectManager);
+        assertThat(session.mEvents).containsExactly(EVENT);
+    }
+
+    @Test
+    public void flush_contentCaptureEnabled_contentProtectionDisabled() throws Exception {
+        ContentCaptureOptions options =
+                createOptions(
+                        /* enableContentCaptureReceiver= */ true,
+                        /* enableContentProtectionReceiver= */ false);
+        MainContentCaptureSession session = createSession(options);
+        session.mEvents = new ArrayList<>(Arrays.asList(EVENT));
+        session.mDirectServiceInterface = mMockContentCaptureDirectManager;
+
+        session.flush(REASON);
+
+        verifyZeroInteractions(mMockContentProtectionEventProcessor);
+        assertThat(session.mEvents).isEmpty();
+        assertEventFlushedContentCapture(options);
+    }
+
+    @Test
+    public void flush_contentCaptureEnabled_contentProtectionEnabled() throws Exception {
+        ContentCaptureOptions options =
+                createOptions(
+                        /* enableContentCaptureReceiver= */ true,
+                        /* enableContentProtectionReceiver= */ true);
+        MainContentCaptureSession session = createSession(options);
+        session.mEvents = new ArrayList<>(Arrays.asList(EVENT));
+        session.mDirectServiceInterface = mMockContentCaptureDirectManager;
+
+        session.flush(REASON);
+
+        verifyZeroInteractions(mMockContentProtectionEventProcessor);
+        assertThat(session.mEvents).isEmpty();
+        assertEventFlushedContentCapture(options);
+    }
+
+    @Test
+    public void destroySession() throws Exception {
+        MainContentCaptureSession session = createSession();
+        session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor;
+
+        session.destroySession();
+
+        verify(mMockSystemServerInterface).finishSession(anyInt());
+        verifyZeroInteractions(mMockContentProtectionEventProcessor);
+        assertThat(session.mDirectServiceInterface).isNull();
+        assertThat(session.mContentProtectionEventProcessor).isNull();
+    }
+
+    @Test
+    public void resetSession() {
+        MainContentCaptureSession session = createSession();
+        session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor;
+
+        session.resetSession(/* newState= */ 0);
+
+        verifyZeroInteractions(mMockSystemServerInterface);
+        verifyZeroInteractions(mMockContentProtectionEventProcessor);
+        assertThat(session.mDirectServiceInterface).isNull();
+        assertThat(session.mContentProtectionEventProcessor).isNull();
+    }
+
+    private static ContentCaptureOptions createOptions(
+            boolean enableContentCaptureReceiver,
+            ContentCaptureOptions.ContentProtectionOptions contentProtectionOptions) {
+        return new ContentCaptureOptions(
+                /* loggingLevel= */ 0,
+                BUFFER_SIZE,
+                /* idleFlushingFrequencyMs= */ 0,
+                /* textChangeFlushingFrequencyMs= */ 0,
+                /* logHistorySize= */ 0,
+                /* disableFlushForViewTreeAppearing= */ false,
+                enableContentCaptureReceiver,
+                contentProtectionOptions,
+                /* whitelistedComponents= */ null);
+    }
+
+    private static ContentCaptureOptions createOptions(
+            boolean enableContentCaptureReceiver, boolean enableContentProtectionReceiver) {
+        return createOptions(
+                enableContentCaptureReceiver,
+                new ContentCaptureOptions.ContentProtectionOptions(
+                        enableContentProtectionReceiver, BUFFER_SIZE));
+    }
+
+    private ContentCaptureManager createManager(ContentCaptureOptions options) {
+        return new ContentCaptureManager(sContext, mMockSystemServerInterface, options);
+    }
+
+    private MainContentCaptureSession createSession(ContentCaptureManager manager) {
+        MainContentCaptureSession session =
+                new MainContentCaptureSession(
+                        sStrippedContext,
+                        manager,
+                        new Handler(Looper.getMainLooper()),
+                        mMockSystemServerInterface);
+        session.mComponentName = COMPONENT_NAME;
+        return session;
+    }
+
+    private MainContentCaptureSession createSession(ContentCaptureOptions options) {
+        return createSession(createManager(options));
+    }
+
+    private MainContentCaptureSession createSession(
+            boolean enableContentCaptureReceiver, boolean enableContentProtectionReceiver) {
+        return createSession(
+                createOptions(enableContentCaptureReceiver, enableContentProtectionReceiver));
+    }
+
+    private MainContentCaptureSession createSession() {
+        return createSession(
+                /* enableContentCaptureReceiver= */ true,
+                /* enableContentProtectionReceiver= */ true);
+    }
+
+    private void assertEventFlushedContentCapture(ContentCaptureOptions options) throws Exception {
+        ArgumentCaptor<ParceledListSlice> captor = ArgumentCaptor.forClass(ParceledListSlice.class);
+        verify(mMockContentCaptureDirectManager)
+                .sendEvents(captor.capture(), eq(REASON), eq(options));
+
+        assertThat(captor.getValue()).isNotNull();
+        List<ContentCaptureEvent> actual = captor.getValue().getList();
+        assertThat(actual).isNotNull();
+        assertThat(actual).containsExactly(EVENT);
+    }
+}
diff --git a/core/tests/coretests/src/android/view/contentcapture/ViewNodeTest.java b/core/tests/coretests/src/android/view/contentcapture/ViewNodeTest.java
index 93315f1..a4e77f5 100644
--- a/core/tests/coretests/src/android/view/contentcapture/ViewNodeTest.java
+++ b/core/tests/coretests/src/android/view/contentcapture/ViewNodeTest.java
@@ -41,49 +41,60 @@
 
     private final Context mContext = InstrumentationRegistry.getTargetContext();
 
+    private final View mView = new View(mContext);
+
+    private final ViewStructureImpl mViewStructure = new ViewStructureImpl(mView);
+
+    private final ViewNode mViewNode = mViewStructure.getNode();
+
     @Mock
     private HtmlInfo mHtmlInfoMock;
 
     @Test
     public void testUnsupportedProperties() {
-        View view = new View(mContext);
+        mViewStructure.setChildCount(1);
+        assertThat(mViewNode.getChildCount()).isEqualTo(0);
 
-        ViewStructureImpl structure = new ViewStructureImpl(view);
-        ViewNode node = structure.getNode();
+        mViewStructure.addChildCount(1);
+        assertThat(mViewNode.getChildCount()).isEqualTo(0);
 
-        structure.setChildCount(1);
-        assertThat(node.getChildCount()).isEqualTo(0);
+        assertThat(mViewStructure.newChild(0)).isNull();
+        assertThat(mViewNode.getChildCount()).isEqualTo(0);
 
-        structure.addChildCount(1);
-        assertThat(node.getChildCount()).isEqualTo(0);
+        assertThat(mViewStructure.asyncNewChild(0)).isNull();
+        assertThat(mViewNode.getChildCount()).isEqualTo(0);
 
-        assertThat(structure.newChild(0)).isNull();
-        assertThat(node.getChildCount()).isEqualTo(0);
+        mViewStructure.asyncCommit();
+        assertThat(mViewNode.getChildCount()).isEqualTo(0);
 
-        assertThat(structure.asyncNewChild(0)).isNull();
-        assertThat(node.getChildCount()).isEqualTo(0);
+        mViewStructure.setWebDomain("Y U NO SET?");
+        assertThat(mViewNode.getWebDomain()).isNull();
 
-        structure.asyncCommit();
-        assertThat(node.getChildCount()).isEqualTo(0);
+        assertThat(mViewStructure.newHtmlInfoBuilder("WHATEVER")).isNull();
 
-        structure.setWebDomain("Y U NO SET?");
-        assertThat(node.getWebDomain()).isNull();
+        mViewStructure.setHtmlInfo(mHtmlInfoMock);
+        assertThat(mViewNode.getHtmlInfo()).isNull();
 
-        assertThat(structure.newHtmlInfoBuilder("WHATEVER")).isNull();
+        mViewStructure.setDataIsSensitive(true);
 
-        structure.setHtmlInfo(mHtmlInfoMock);
-        assertThat(node.getHtmlInfo()).isNull();
-
-        structure.setDataIsSensitive(true);
-
-        assertThat(structure.getTempRect()).isNull();
+        assertThat(mViewStructure.getTempRect()).isNull();
 
         // Graphic properties
-        structure.setElevation(6.66f);
-        assertThat(node.getElevation()).isWithin(1.0e-10f).of(0f);
-        structure.setAlpha(66.6f);
-        assertThat(node.getAlpha()).isWithin(1.0e-10f).of(1.0f);
-        structure.setTransformation(Matrix.IDENTITY_MATRIX);
-        assertThat(node.getTransformation()).isNull();
+        mViewStructure.setElevation(6.66f);
+        assertThat(mViewNode.getElevation()).isEqualTo(0f);
+        mViewStructure.setAlpha(66.6f);
+        assertThat(mViewNode.getAlpha()).isEqualTo(1.0f);
+        mViewStructure.setTransformation(Matrix.IDENTITY_MATRIX);
+        assertThat(mViewNode.getTransformation()).isNull();
+    }
+
+    @Test
+    public void testGetSet_textIdEntry() {
+        assertThat(mViewNode.getTextIdEntry()).isNull();
+
+        String expected = "TEXT_ID_ENTRY";
+        mViewNode.setTextIdEntry(expected);
+
+        assertThat(mViewNode.getTextIdEntry()).isEqualTo(expected);
     }
 }
diff --git a/core/tests/coretests/src/android/view/contentprotection/ContentProtectionEventProcessorTest.java b/core/tests/coretests/src/android/view/contentprotection/ContentProtectionEventProcessorTest.java
new file mode 100644
index 0000000..39a2e0e
--- /dev/null
+++ b/core/tests/coretests/src/android/view/contentprotection/ContentProtectionEventProcessorTest.java
@@ -0,0 +1,568 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.view.contentprotection;
+
+import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_APPEARED;
+import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_DISAPPEARED;
+import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_TEXT_CHANGED;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.pm.ParceledListSlice;
+import android.os.Handler;
+import android.os.Looper;
+import android.text.InputType;
+import android.view.View;
+import android.view.contentcapture.ContentCaptureEvent;
+import android.view.contentcapture.IContentCaptureManager;
+import android.view.contentcapture.ViewNode;
+import android.view.contentcapture.ViewNode.ViewStructureImpl;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.util.RingBuffer;
+
+import com.google.common.collect.ImmutableSet;
+
+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.time.Instant;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Test for {@link ContentProtectionEventProcessor}.
+ *
+ * <p>Run with: {@code atest
+ * FrameworksCoreTests:android.view.contentprotection.ContentProtectionEventProcessorTest}
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class ContentProtectionEventProcessorTest {
+
+    private static final String PACKAGE_NAME = "com.test.package.name";
+
+    private static final String ANDROID_CLASS_NAME = "android.test.some.class.name";
+
+    private static final String PASSWORD_TEXT = "ENTER PASSWORD HERE";
+
+    private static final String SUSPICIOUS_TEXT = "PLEASE SIGN IN";
+
+    private static final String SAFE_TEXT = "SAFE TEXT";
+
+    private static final ContentCaptureEvent PROCESS_EVENT = createProcessEvent();
+
+    private static final ContentCaptureEvent[] BUFFERED_EVENTS =
+            new ContentCaptureEvent[] {PROCESS_EVENT};
+
+    private static final Set<Integer> EVENT_TYPES_TO_STORE =
+            ImmutableSet.of(TYPE_VIEW_APPEARED, TYPE_VIEW_DISAPPEARED, TYPE_VIEW_TEXT_CHANGED);
+
+    private static final int RESET_LOGIN_TOTAL_EVENTS_TO_PROCESS = 150;
+
+    @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+
+    @Mock private RingBuffer<ContentCaptureEvent> mMockEventBuffer;
+
+    @Mock private IContentCaptureManager mMockContentCaptureManager;
+
+    private final Context mContext = ApplicationProvider.getApplicationContext();
+
+    private ContentProtectionEventProcessor mContentProtectionEventProcessor;
+
+    @Before
+    public void setup() {
+        mContentProtectionEventProcessor =
+                new ContentProtectionEventProcessor(
+                        mMockEventBuffer,
+                        new Handler(Looper.getMainLooper()),
+                        mMockContentCaptureManager,
+                        PACKAGE_NAME);
+    }
+
+    @Test
+    public void processEvent_buffer_storesOnlySubsetOfEventTypes() {
+        List<ContentCaptureEvent> expectedEvents = new ArrayList<>();
+        for (int type = -100; type <= 100; type++) {
+            ContentCaptureEvent event = createEvent(type);
+            if (EVENT_TYPES_TO_STORE.contains(type)) {
+                expectedEvents.add(event);
+            }
+
+            mContentProtectionEventProcessor.processEvent(event);
+        }
+
+        assertThat(expectedEvents).hasSize(EVENT_TYPES_TO_STORE.size());
+        expectedEvents.forEach((expectedEvent) -> verify(mMockEventBuffer).append(expectedEvent));
+        verifyNoMoreInteractions(mMockEventBuffer);
+    }
+
+    @Test
+    public void processEvent_buffer_setsTextIdEntry_withoutExistingViewNode() {
+        ContentCaptureEvent event = createStoreEvent();
+
+        mContentProtectionEventProcessor.processEvent(event);
+
+        assertThat(event.getViewNode()).isNotNull();
+        assertThat(event.getViewNode().getTextIdEntry()).isEqualTo(PACKAGE_NAME);
+        verify(mMockEventBuffer).append(event);
+    }
+
+    @Test
+    public void processEvent_buffer_setsTextIdEntry_withExistingViewNode() {
+        ViewNode viewNode = new ViewNode();
+        viewNode.setTextIdEntry(PACKAGE_NAME + "TO BE OVERWRITTEN");
+        ContentCaptureEvent event = createStoreEvent();
+        event.setViewNode(viewNode);
+
+        mContentProtectionEventProcessor.processEvent(event);
+
+        assertThat(event.getViewNode()).isSameInstanceAs(viewNode);
+        assertThat(viewNode.getTextIdEntry()).isEqualTo(PACKAGE_NAME);
+        verify(mMockEventBuffer).append(event);
+    }
+
+    @Test
+    public void processEvent_loginDetected_inspectsOnlyTypeViewAppeared() {
+        mContentProtectionEventProcessor.mPasswordFieldDetected = true;
+        mContentProtectionEventProcessor.mSuspiciousTextDetected = true;
+
+        for (int type = -100; type <= 100; type++) {
+            if (type == TYPE_VIEW_APPEARED) {
+                continue;
+            }
+
+            mContentProtectionEventProcessor.processEvent(createEvent(type));
+
+            assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isTrue();
+            assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue();
+        }
+
+        verify(mMockEventBuffer, never()).clear();
+        verify(mMockEventBuffer, never()).toArray();
+        verifyZeroInteractions(mMockContentCaptureManager);
+    }
+
+    @Test
+    public void processEvent_loginDetected() throws Exception {
+        when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS);
+        mContentProtectionEventProcessor.mPasswordFieldDetected = true;
+        mContentProtectionEventProcessor.mSuspiciousTextDetected = true;
+
+        mContentProtectionEventProcessor.processEvent(PROCESS_EVENT);
+
+        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
+        assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
+        verify(mMockEventBuffer).clear();
+        verify(mMockEventBuffer).toArray();
+        assertOnLoginDetected();
+    }
+
+    @Test
+    public void processEvent_loginDetected_passwordFieldNotDetected() {
+        mContentProtectionEventProcessor.mSuspiciousTextDetected = true;
+        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
+
+        mContentProtectionEventProcessor.processEvent(PROCESS_EVENT);
+
+        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
+        assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue();
+        verify(mMockEventBuffer, never()).clear();
+        verify(mMockEventBuffer, never()).toArray();
+        verifyZeroInteractions(mMockContentCaptureManager);
+    }
+
+    @Test
+    public void processEvent_loginDetected_suspiciousTextNotDetected() {
+        mContentProtectionEventProcessor.mPasswordFieldDetected = true;
+        assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
+
+        mContentProtectionEventProcessor.processEvent(PROCESS_EVENT);
+
+        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isTrue();
+        assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
+        verify(mMockEventBuffer, never()).clear();
+        verify(mMockEventBuffer, never()).toArray();
+        verifyZeroInteractions(mMockContentCaptureManager);
+    }
+
+    @Test
+    public void processEvent_loginDetected_withoutViewNode() {
+        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
+        assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
+
+        mContentProtectionEventProcessor.processEvent(PROCESS_EVENT);
+
+        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
+        assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
+        verify(mMockEventBuffer, never()).clear();
+        verify(mMockEventBuffer, never()).toArray();
+        verifyZeroInteractions(mMockContentCaptureManager);
+    }
+
+    @Test
+    public void processEvent_loginDetected_belowResetLimit() throws Exception {
+        when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS);
+        mContentProtectionEventProcessor.mSuspiciousTextDetected = true;
+        ContentCaptureEvent event =
+                createAndroidPasswordFieldEvent(
+                        ANDROID_CLASS_NAME, InputType.TYPE_TEXT_VARIATION_PASSWORD);
+
+        for (int i = 0; i < RESET_LOGIN_TOTAL_EVENTS_TO_PROCESS; i++) {
+            mContentProtectionEventProcessor.processEvent(PROCESS_EVENT);
+        }
+
+        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
+        assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue();
+        verify(mMockEventBuffer, never()).clear();
+        verify(mMockEventBuffer, never()).toArray();
+
+        mContentProtectionEventProcessor.processEvent(event);
+
+        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
+        assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
+        verify(mMockEventBuffer).clear();
+        verify(mMockEventBuffer).toArray();
+        assertOnLoginDetected();
+    }
+
+    @Test
+    public void processEvent_loginDetected_aboveResetLimit() throws Exception {
+        mContentProtectionEventProcessor.mSuspiciousTextDetected = true;
+        ContentCaptureEvent event =
+                createAndroidPasswordFieldEvent(
+                        ANDROID_CLASS_NAME, InputType.TYPE_TEXT_VARIATION_PASSWORD);
+
+        for (int i = 0; i < RESET_LOGIN_TOTAL_EVENTS_TO_PROCESS + 1; i++) {
+            mContentProtectionEventProcessor.processEvent(PROCESS_EVENT);
+        }
+
+        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
+        assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
+        verify(mMockEventBuffer, never()).clear();
+        verify(mMockEventBuffer, never()).toArray();
+
+        mContentProtectionEventProcessor.processEvent(event);
+
+        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isTrue();
+        assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
+        verify(mMockEventBuffer, never()).clear();
+        verify(mMockEventBuffer, never()).toArray();
+    }
+
+    @Test
+    public void processEvent_multipleLoginsDetected_belowFlushThreshold() throws Exception {
+        when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS);
+
+        mContentProtectionEventProcessor.mPasswordFieldDetected = true;
+        mContentProtectionEventProcessor.mSuspiciousTextDetected = true;
+        mContentProtectionEventProcessor.processEvent(PROCESS_EVENT);
+
+        mContentProtectionEventProcessor.mPasswordFieldDetected = true;
+        mContentProtectionEventProcessor.mSuspiciousTextDetected = true;
+        mContentProtectionEventProcessor.processEvent(PROCESS_EVENT);
+
+        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
+        assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
+        verify(mMockEventBuffer).clear();
+        verify(mMockEventBuffer).toArray();
+        assertOnLoginDetected();
+    }
+
+    @Test
+    public void processEvent_multipleLoginsDetected_aboveFlushThreshold() throws Exception {
+        when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS);
+
+        mContentProtectionEventProcessor.mPasswordFieldDetected = true;
+        mContentProtectionEventProcessor.mSuspiciousTextDetected = true;
+        mContentProtectionEventProcessor.processEvent(PROCESS_EVENT);
+
+        mContentProtectionEventProcessor.mLastFlushTime = Instant.now().minusSeconds(5);
+
+        mContentProtectionEventProcessor.mPasswordFieldDetected = true;
+        mContentProtectionEventProcessor.mSuspiciousTextDetected = true;
+        mContentProtectionEventProcessor.processEvent(PROCESS_EVENT);
+
+        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
+        assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
+        verify(mMockEventBuffer, times(2)).clear();
+        verify(mMockEventBuffer, times(2)).toArray();
+        assertOnLoginDetected(PROCESS_EVENT, /* times= */ 2);
+    }
+
+    @Test
+    public void isPasswordField_android() {
+        ContentCaptureEvent event =
+                createAndroidPasswordFieldEvent(
+                        ANDROID_CLASS_NAME, InputType.TYPE_TEXT_VARIATION_PASSWORD);
+
+        mContentProtectionEventProcessor.processEvent(event);
+
+        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isTrue();
+        verify(mMockEventBuffer, never()).clear();
+        verify(mMockEventBuffer, never()).toArray();
+        verifyZeroInteractions(mMockContentCaptureManager);
+    }
+
+    @Test
+    public void isPasswordField_android_withoutClassName() {
+        ContentCaptureEvent event =
+                createAndroidPasswordFieldEvent(
+                        /* className= */ null, InputType.TYPE_TEXT_VARIATION_PASSWORD);
+
+        mContentProtectionEventProcessor.processEvent(event);
+
+        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
+        verify(mMockEventBuffer, never()).clear();
+        verify(mMockEventBuffer, never()).toArray();
+        verifyZeroInteractions(mMockContentCaptureManager);
+    }
+
+    @Test
+    public void isPasswordField_android_wrongClassName() {
+        ContentCaptureEvent event =
+                createAndroidPasswordFieldEvent(
+                        "wrong.prefix" + ANDROID_CLASS_NAME,
+                        InputType.TYPE_TEXT_VARIATION_PASSWORD);
+
+        mContentProtectionEventProcessor.processEvent(event);
+
+        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
+        verify(mMockEventBuffer, never()).clear();
+        verify(mMockEventBuffer, never()).toArray();
+        verifyZeroInteractions(mMockContentCaptureManager);
+    }
+
+    @Test
+    public void isPasswordField_android_wrongInputType() {
+        ContentCaptureEvent event =
+                createAndroidPasswordFieldEvent(
+                        ANDROID_CLASS_NAME, InputType.TYPE_TEXT_VARIATION_NORMAL);
+
+        mContentProtectionEventProcessor.processEvent(event);
+
+        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
+        verify(mMockEventBuffer, never()).clear();
+        verify(mMockEventBuffer, never()).toArray();
+        verifyZeroInteractions(mMockContentCaptureManager);
+    }
+
+    @Test
+    public void isPasswordField_webView() throws Exception {
+        ContentCaptureEvent event =
+                createWebViewPasswordFieldEvent(
+                        /* className= */ null, /* eventText= */ null, PASSWORD_TEXT);
+        when(mMockEventBuffer.toArray()).thenReturn(new ContentCaptureEvent[] {event});
+
+        mContentProtectionEventProcessor.processEvent(event);
+
+        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
+        verify(mMockEventBuffer).clear();
+        verify(mMockEventBuffer).toArray();
+        assertOnLoginDetected(event, /* times= */ 1);
+    }
+
+    @Test
+    public void isPasswordField_webView_withClassName() {
+        ContentCaptureEvent event =
+                createWebViewPasswordFieldEvent(
+                        /* className= */ "any.class.name", /* eventText= */ null, PASSWORD_TEXT);
+
+        mContentProtectionEventProcessor.processEvent(event);
+
+        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
+        verify(mMockEventBuffer, never()).clear();
+        verify(mMockEventBuffer, never()).toArray();
+        verifyZeroInteractions(mMockContentCaptureManager);
+    }
+
+    @Test
+    public void isPasswordField_webView_withSafeViewNodeText() {
+        ContentCaptureEvent event =
+                createWebViewPasswordFieldEvent(
+                        /* className= */ null, /* eventText= */ null, SAFE_TEXT);
+
+        mContentProtectionEventProcessor.processEvent(event);
+
+        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
+        verify(mMockEventBuffer, never()).clear();
+        verify(mMockEventBuffer, never()).toArray();
+        verifyZeroInteractions(mMockContentCaptureManager);
+    }
+
+    @Test
+    public void isPasswordField_webView_withEventText() {
+        ContentCaptureEvent event =
+                createWebViewPasswordFieldEvent(/* className= */ null, PASSWORD_TEXT, SAFE_TEXT);
+
+        mContentProtectionEventProcessor.processEvent(event);
+
+        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
+        verify(mMockEventBuffer, never()).clear();
+        verify(mMockEventBuffer, never()).toArray();
+        verifyZeroInteractions(mMockContentCaptureManager);
+    }
+
+    @Test
+    public void isSuspiciousText_withSafeText() {
+        ContentCaptureEvent event = createSuspiciousTextEvent(SAFE_TEXT, SAFE_TEXT);
+
+        mContentProtectionEventProcessor.processEvent(event);
+
+        assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
+        verify(mMockEventBuffer, never()).clear();
+        verify(mMockEventBuffer, never()).toArray();
+        verifyZeroInteractions(mMockContentCaptureManager);
+    }
+
+    @Test
+    public void isSuspiciousText_eventText_suspiciousText() {
+        ContentCaptureEvent event = createSuspiciousTextEvent(SUSPICIOUS_TEXT, SAFE_TEXT);
+
+        mContentProtectionEventProcessor.processEvent(event);
+
+        assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue();
+        verify(mMockEventBuffer, never()).clear();
+        verify(mMockEventBuffer, never()).toArray();
+        verifyZeroInteractions(mMockContentCaptureManager);
+    }
+
+    @Test
+    public void isSuspiciousText_viewNodeText_suspiciousText() {
+        ContentCaptureEvent event = createSuspiciousTextEvent(SAFE_TEXT, SUSPICIOUS_TEXT);
+
+        mContentProtectionEventProcessor.processEvent(event);
+
+        assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue();
+        verify(mMockEventBuffer, never()).clear();
+        verify(mMockEventBuffer, never()).toArray();
+        verifyZeroInteractions(mMockContentCaptureManager);
+    }
+
+    @Test
+    public void isSuspiciousText_eventText_passwordText() {
+        ContentCaptureEvent event = createSuspiciousTextEvent(PASSWORD_TEXT, SAFE_TEXT);
+
+        mContentProtectionEventProcessor.processEvent(event);
+
+        assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue();
+        verify(mMockEventBuffer, never()).clear();
+        verify(mMockEventBuffer, never()).toArray();
+        verifyZeroInteractions(mMockContentCaptureManager);
+    }
+
+    @Test
+    public void isSuspiciousText_viewNodeText_passwordText() {
+        // Specify the class to differ from {@link isPasswordField_webView} test in this version
+        ContentCaptureEvent event =
+                createProcessEvent(
+                        "test.class.not.a.web.view", /* inputType= */ 0, SAFE_TEXT, PASSWORD_TEXT);
+
+        mContentProtectionEventProcessor.processEvent(event);
+
+        assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue();
+        verify(mMockEventBuffer, never()).clear();
+        verify(mMockEventBuffer, never()).toArray();
+        verifyZeroInteractions(mMockContentCaptureManager);
+    }
+
+    private static ContentCaptureEvent createEvent(int type) {
+        return new ContentCaptureEvent(/* sessionId= */ 123, type);
+    }
+
+    private static ContentCaptureEvent createStoreEvent() {
+        return createEvent(TYPE_VIEW_TEXT_CHANGED);
+    }
+
+    private static ContentCaptureEvent createProcessEvent() {
+        return createEvent(TYPE_VIEW_APPEARED);
+    }
+
+    private ContentCaptureEvent createProcessEvent(
+            @Nullable String className,
+            int inputType,
+            @Nullable String eventText,
+            @Nullable String viewNodeText) {
+        View view = new View(mContext);
+        ViewStructureImpl viewStructure = new ViewStructureImpl(view);
+        if (className != null) {
+            viewStructure.setClassName(className);
+        }
+        if (viewNodeText != null) {
+            viewStructure.setText(viewNodeText);
+        }
+        viewStructure.setInputType(inputType);
+
+        ContentCaptureEvent event = createProcessEvent();
+        event.setViewNode(viewStructure.getNode());
+        if (eventText != null) {
+            event.setText(eventText);
+        }
+
+        return event;
+    }
+
+    private ContentCaptureEvent createAndroidPasswordFieldEvent(
+            @Nullable String className, int inputType) {
+        return createProcessEvent(
+                className, inputType, /* eventText= */ null, /* viewNodeText= */ null);
+    }
+
+    private ContentCaptureEvent createWebViewPasswordFieldEvent(
+            @Nullable String className, @Nullable String eventText, @Nullable String viewNodeText) {
+        return createProcessEvent(className, /* inputType= */ 0, eventText, viewNodeText);
+    }
+
+    private ContentCaptureEvent createSuspiciousTextEvent(
+            @Nullable String eventText, @Nullable String viewNodeText) {
+        return createProcessEvent(
+                /* className= */ null, /* inputType= */ 0, eventText, viewNodeText);
+    }
+
+    private void assertOnLoginDetected() throws Exception {
+        assertOnLoginDetected(PROCESS_EVENT, /* times= */ 1);
+    }
+
+    private void assertOnLoginDetected(ContentCaptureEvent event, int times) throws Exception {
+        ArgumentCaptor<ParceledListSlice> captor = ArgumentCaptor.forClass(ParceledListSlice.class);
+        verify(mMockContentCaptureManager, times(times)).onLoginDetected(captor.capture());
+
+        assertThat(captor.getValue()).isNotNull();
+        List<ContentCaptureEvent> actual = captor.getValue().getList();
+        assertThat(actual).isNotNull();
+        assertThat(actual).containsExactly(event);
+    }
+}
diff --git a/core/tests/coretests/src/android/view/contentprotection/ContentProtectionUtilsTest.java b/core/tests/coretests/src/android/view/contentprotection/ContentProtectionUtilsTest.java
new file mode 100644
index 0000000..1459799
--- /dev/null
+++ b/core/tests/coretests/src/android/view/contentprotection/ContentProtectionUtilsTest.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.view.contentprotection;
+
+import static android.view.contentcapture.ContentCaptureEvent.TYPE_SESSION_STARTED;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.view.View;
+import android.view.contentcapture.ContentCaptureEvent;
+import android.view.contentcapture.ViewNode;
+import android.view.contentcapture.ViewNode.ViewStructureImpl;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test for {@link ContentProtectionUtils}.
+ *
+ * <p>Run with: {@code atest
+ * FrameworksCoreTests:android.view.contentprotection.ContentProtectionUtilsTest}
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class ContentProtectionUtilsTest {
+
+    private static final String TEXT = "TEST_TEXT";
+
+    private static final ContentCaptureEvent EVENT = createEvent();
+
+    private static final ViewNode VIEW_NODE = new ViewNode();
+
+    private static final ViewNode VIEW_NODE_WITH_TEXT = createViewNodeWithText();
+
+    @Test
+    public void event_getEventText_null() {
+        String actual = ContentProtectionUtils.getEventText(EVENT);
+
+        assertThat(actual).isNull();
+    }
+
+    @Test
+    public void event_getEventText_notNull() {
+        ContentCaptureEvent event = createEvent();
+        event.setText(TEXT);
+
+        String actual = ContentProtectionUtils.getEventText(event);
+
+        assertThat(actual).isEqualTo(TEXT);
+    }
+
+    @Test
+    public void event_getViewNodeText_null() {
+        String actual = ContentProtectionUtils.getViewNodeText(EVENT);
+
+        assertThat(actual).isNull();
+    }
+
+    @Test
+    public void event_getViewNodeText_notNull() {
+        ContentCaptureEvent event = createEvent();
+        event.setViewNode(VIEW_NODE_WITH_TEXT);
+
+        String actual = ContentProtectionUtils.getViewNodeText(event);
+
+        assertThat(actual).isEqualTo(TEXT);
+    }
+
+    @Test
+    public void viewNode_getViewNodeText_null() {
+        String actual = ContentProtectionUtils.getViewNodeText(VIEW_NODE);
+
+        assertThat(actual).isNull();
+    }
+
+    @Test
+    public void viewNode_getViewNodeText_notNull() {
+        String actual = ContentProtectionUtils.getViewNodeText(VIEW_NODE_WITH_TEXT);
+
+        assertThat(actual).isEqualTo(TEXT);
+    }
+
+    private static ContentCaptureEvent createEvent() {
+        return new ContentCaptureEvent(/* sessionId= */ 123, TYPE_SESSION_STARTED);
+    }
+
+    private static ViewNode createViewNodeWithText() {
+        View view = new View(ApplicationProvider.getApplicationContext());
+        ViewStructureImpl viewStructure = new ViewStructureImpl(view);
+        viewStructure.setText(TEXT);
+        return viewStructure.getNode();
+    }
+}
diff --git a/core/tests/coretests/src/android/view/inputmethod/TextAppearanceInfoTest.java b/core/tests/coretests/src/android/view/inputmethod/TextAppearanceInfoTest.java
index f93cd18..0750cf1 100644
--- a/core/tests/coretests/src/android/view/inputmethod/TextAppearanceInfoTest.java
+++ b/core/tests/coretests/src/android/view/inputmethod/TextAppearanceInfoTest.java
@@ -37,6 +37,7 @@
 import android.text.style.ScaleXSpan;
 import android.text.style.StyleSpan;
 import android.text.style.TypefaceSpan;
+import android.text.util.Linkify;
 import android.view.ViewGroup;
 import android.widget.EditText;
 
@@ -53,7 +54,7 @@
 @RunWith(AndroidJUnit4.class)
 public class TextAppearanceInfoTest {
     private static final float EPSILON = 0.0000001f;
-    private static final String TEST_TEXT = "Happy birthday!";
+    private static final String TEST_TEXT = "Hello: google.com";
     private static final float TEXT_SIZE = 16.5f;
     private static final LocaleList TEXT_LOCALES = LocaleList.forLanguageTags("en,ja");
     private static final String FONT_FAMILY_NAME = "sans-serif";
@@ -84,39 +85,7 @@
 
     @Before
     public void setUp() {
-        mEditText.setText(mSpannableText);
-        mEditText.getPaint().setTextSize(TEXT_SIZE);
-        mEditText.setTextLocales(TEXT_LOCALES);
-        Typeface family = Typeface.create(FONT_FAMILY_NAME, Typeface.NORMAL);
-        mEditText.setTypeface(
-                Typeface.create(family, TEXT_WEIGHT, (TEXT_STYLE & Typeface.ITALIC) != 0));
-        mEditText.setAllCaps(ALL_CAPS);
-        mEditText.setShadowLayer(SHADOW_RADIUS, SHADOW_DX, SHADOW_DY, SHADOW_COLOR);
-        mEditText.setElegantTextHeight(ELEGANT_TEXT_HEIGHT);
-        mEditText.setFallbackLineSpacing(FALLBACK_LINE_SPACING);
-        mEditText.setLetterSpacing(LETTER_SPACING);
-        mEditText.setFontFeatureSettings(FONT_FEATURE_SETTINGS);
-        mEditText.setFontVariationSettings(FONT_VARIATION_SETTINGS);
-        mEditText.setLineBreakStyle(LINE_BREAK_STYLE);
-        mEditText.setLineBreakWordStyle(LINE_BREAK_WORD_STYLE);
-        mEditText.setTextScaleX(TEXT_SCALEX);
-        mEditText.setHighlightColor(HIGHLIGHT_TEXT_COLOR);
-        mEditText.setTextColor(TEXT_COLOR);
-        mEditText.setHintTextColor(HINT_TEXT_COLOR);
-        mEditText.setLinkTextColor(LINK_TEXT_COLOR);
-        ViewGroup.LayoutParams params =
-                new ViewGroup.LayoutParams(
-                        ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
-        mEditText.setLayoutParams(params);
-        mEditText.measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
-        Bitmap bitmap =
-                Bitmap.createBitmap(
-                        Math.max(1, mEditText.getMeasuredWidth()),
-                        Math.max(1, mEditText.getMeasuredHeight()),
-                        Bitmap.Config.ARGB_8888);
-        mEditText.layout(0, 0, mEditText.getMeasuredWidth(), mEditText.getMeasuredHeight());
-        mCanvas = new Canvas(bitmap);
-        mEditText.draw(mCanvas);
+        initEditText(mSpannableText);
     }
 
     @Test
@@ -233,6 +202,43 @@
         assertEquals(info1.getSystemFontFamilyName(), FONT_FAMILY_NAME);
     }
 
+    private void initEditText(CharSequence text) {
+        mEditText.setText(text);
+        mEditText.getPaint().setTextSize(TEXT_SIZE);
+        mEditText.setTextLocales(TEXT_LOCALES);
+        Typeface family = Typeface.create(FONT_FAMILY_NAME, Typeface.NORMAL);
+        mEditText.setTypeface(
+                Typeface.create(family, TEXT_WEIGHT, (TEXT_STYLE & Typeface.ITALIC) != 0));
+        mEditText.setAllCaps(ALL_CAPS);
+        mEditText.setShadowLayer(SHADOW_RADIUS, SHADOW_DX, SHADOW_DY, SHADOW_COLOR);
+        mEditText.setElegantTextHeight(ELEGANT_TEXT_HEIGHT);
+        mEditText.setFallbackLineSpacing(FALLBACK_LINE_SPACING);
+        mEditText.setLetterSpacing(LETTER_SPACING);
+        mEditText.setFontFeatureSettings(FONT_FEATURE_SETTINGS);
+        mEditText.setFontVariationSettings(FONT_VARIATION_SETTINGS);
+        mEditText.setLineBreakStyle(LINE_BREAK_STYLE);
+        mEditText.setLineBreakWordStyle(LINE_BREAK_WORD_STYLE);
+        mEditText.setTextScaleX(TEXT_SCALEX);
+        mEditText.setHighlightColor(HIGHLIGHT_TEXT_COLOR);
+        mEditText.setTextColor(TEXT_COLOR);
+        mEditText.setHintTextColor(HINT_TEXT_COLOR);
+        mEditText.setHint("Hint text");
+        mEditText.setLinkTextColor(LINK_TEXT_COLOR);
+        mEditText.setAutoLinkMask(Linkify.WEB_URLS);
+        ViewGroup.LayoutParams params =
+                new ViewGroup.LayoutParams(
+                        ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+        mEditText.setLayoutParams(params);
+        mEditText.measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+        Bitmap bitmap =
+                Bitmap.createBitmap(
+                        Math.max(1, mEditText.getMeasuredWidth()),
+                        Math.max(1, mEditText.getMeasuredHeight()),
+                        Bitmap.Config.ARGB_8888);
+        mEditText.layout(0, 0, mEditText.getMeasuredWidth(), mEditText.getMeasuredHeight());
+        mCanvas = new Canvas(bitmap);
+        mEditText.draw(mCanvas);
+    }
     private void assertTextAppearanceInfoContentsEqual(TextAppearanceInfo textAppearanceInfo) {
         assertEquals(textAppearanceInfo.getTextSize(), TEXT_SIZE, EPSILON);
         assertEquals(textAppearanceInfo.getTextLocales(), TEXT_LOCALES);
@@ -258,6 +264,15 @@
         assertEquals(textAppearanceInfo.getLinkTextColor(), LINK_TEXT_COLOR);
     }
 
+    @Test
+    public void testCreateFromTextView_withHintText() {
+        // Make hint text display
+        initEditText("");
+
+        // The text color should not be hint color
+        assertTextAppearanceInfoContentsEqual(TextAppearanceInfo.createFromTextView(mEditText));
+    }
+
     static class CustomForegroundColorSpan extends ForegroundColorSpan {
         @Nullable public TextPaint lastTextPaint = null;
 
diff --git a/core/tests/coretests/src/android/widget/EditTextCursorAnchorInfoTest.java b/core/tests/coretests/src/android/widget/EditTextCursorAnchorInfoTest.java
index 1a01987..62adc20 100644
--- a/core/tests/coretests/src/android/widget/EditTextCursorAnchorInfoTest.java
+++ b/core/tests/coretests/src/android/widget/EditTextCursorAnchorInfoTest.java
@@ -30,6 +30,7 @@
 import android.view.Gravity;
 import android.view.View;
 import android.view.inputmethod.CursorAnchorInfo;
+import android.view.inputmethod.EditorBoundsInfo;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.platform.app.InstrumentationRegistry;
@@ -54,8 +55,15 @@
     private static final int[] sLocationOnScreen = new int[2];
     private static Typeface sTypeface;
     private static final float TEXT_SIZE = 1f;
-    // The line height of the test font font is 1.2 * textSize.
+    // The line height of the test font is 1.2 * textSize.
     private static final int LINE_HEIGHT = 12;
+    private static final int HW_BOUNDS_OFFSET_LEFT = 10;
+    private static final int HW_BOUNDS_OFFSET_TOP = 20;
+    private static final int HW_BOUNDS_OFFSET_RIGHT = 30;
+    private static final int HW_BOUNDS_OFFSET_BOTTOM = 40;
+
+
+    // Default text has 5 lines of text. The needed width is 50px and the needed height is 60px.
     private static final CharSequence DEFAULT_TEXT = "X\nXX\nXXX\nXXXX\nXXXXX";
     private static final ImmutableList<RectF> DEFAULT_LINE_BOUNDS = ImmutableList.of(
             new RectF(0f, 0f, 10f, LINE_HEIGHT),
@@ -131,6 +139,55 @@
     }
 
     @Test
+    public void testEditorBoundsInfo_allVisible() {
+        // The needed width and height of the DEFAULT_TEXT are 50 px and 60 px respectfully.
+        int width = 100;
+        int height = 200;
+        setupEditText(DEFAULT_TEXT, width, height);
+        CursorAnchorInfo cursorAnchorInfo =
+                mEditText.getCursorAnchorInfo(0, sCursorAnchorInfoBuilder, sMatrix);
+        EditorBoundsInfo editorBoundsInfo = cursorAnchorInfo.getEditorBoundsInfo();
+        assertThat(editorBoundsInfo).isNotNull();
+        assertThat(editorBoundsInfo.getEditorBounds()).isEqualTo(new RectF(0, 0, width, height));
+        assertThat(editorBoundsInfo.getHandwritingBounds())
+                .isEqualTo(new RectF(-HW_BOUNDS_OFFSET_LEFT, -HW_BOUNDS_OFFSET_TOP,
+                        width + HW_BOUNDS_OFFSET_RIGHT, height + HW_BOUNDS_OFFSET_BOTTOM));
+    }
+
+    @Test
+    public void testEditorBoundsInfo_scrolled() {
+        // The height of the editor will be 60 px.
+        int width = 100;
+        int visibleTop = 10;
+        int visibleBottom = 30;
+        setupVerticalClippedEditText(width, visibleTop, visibleBottom);
+        CursorAnchorInfo cursorAnchorInfo =
+                mEditText.getCursorAnchorInfo(0, sCursorAnchorInfoBuilder, sMatrix);
+        EditorBoundsInfo editorBoundsInfo = cursorAnchorInfo.getEditorBoundsInfo();
+        assertThat(editorBoundsInfo).isNotNull();
+        assertThat(editorBoundsInfo.getEditorBounds())
+                .isEqualTo(new RectF(0, visibleTop, width, visibleBottom));
+        assertThat(editorBoundsInfo.getHandwritingBounds())
+                .isEqualTo(new RectF(-HW_BOUNDS_OFFSET_LEFT, visibleTop - HW_BOUNDS_OFFSET_TOP,
+                        width + HW_BOUNDS_OFFSET_RIGHT, visibleBottom + HW_BOUNDS_OFFSET_BOTTOM));
+    }
+
+    @Test
+    public void testEditorBoundsInfo_invisible() {
+        // The height of the editor will be 60px. Scroll it to 70px will make it invisible.
+        int width = 100;
+        int visibleTop = 70;
+        int visibleBottom = 70;
+        setupVerticalClippedEditText(width, visibleTop, visibleBottom);
+        CursorAnchorInfo cursorAnchorInfo =
+                mEditText.getCursorAnchorInfo(0, sCursorAnchorInfoBuilder, sMatrix);
+        EditorBoundsInfo editorBoundsInfo = cursorAnchorInfo.getEditorBoundsInfo();
+        assertThat(editorBoundsInfo).isNotNull();
+        assertThat(editorBoundsInfo.getEditorBounds()).isEqualTo(new RectF(0, 0, 0, 0));
+        assertThat(editorBoundsInfo.getHandwritingBounds()).isEqualTo(new RectF(0, 0, 0, 0));
+    }
+
+    @Test
     public void testVisibleLineBounds_allVisible() {
         setupEditText(DEFAULT_TEXT, /* height= */ 100);
         CursorAnchorInfo cursorAnchorInfo =
@@ -465,32 +522,26 @@
     }
 
     private void setupVerticalClippedEditText(int visibleTop, int visibleBottom) {
+        setupVerticalClippedEditText(1000, visibleTop, visibleBottom);
+    }
+
+    /**
+     * Helper method to create an EditText in a vertical ScrollView so that its visible bounds
+     * is Rect(0, visibleTop, width, visibleBottom) in the EditText's coordinates. Both ScrollView
+     * and EditText's width is set to the given width.
+     */
+    private void setupVerticalClippedEditText(int width, int visibleTop, int visibleBottom) {
         ScrollView scrollView = new ScrollView(mActivity);
-        mEditText = new EditText(mActivity);
-        mEditText.setTypeface(sTypeface);
-        mEditText.setText(DEFAULT_TEXT);
-        mEditText.setTextSize(TypedValue.COMPLEX_UNIT_PX, TEXT_SIZE);
-
-        mEditText.setPadding(0, 0, 0, 0);
-        mEditText.setCompoundDrawables(null, null, null, null);
-        mEditText.setCompoundDrawablePadding(0);
-
-        mEditText.scrollTo(0, 0);
-        mEditText.setLineSpacing(0f, 1f);
-
-        // Place the text layout top to the view's top.
-        mEditText.setGravity(Gravity.TOP);
-        int width = 1000;
-        int height = visibleBottom - visibleTop;
+        createEditText();
+        int scrollViewHeight = visibleBottom - visibleTop;
 
         scrollView.addView(mEditText, new FrameLayout.LayoutParams(
                 View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY),
                 View.MeasureSpec.makeMeasureSpec(5 * LINE_HEIGHT, View.MeasureSpec.EXACTLY)));
         scrollView.measure(
                 View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY),
-                View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY));
-        scrollView.layout(0, 0, width, height);
-
+                View.MeasureSpec.makeMeasureSpec(scrollViewHeight, View.MeasureSpec.EXACTLY));
+        scrollView.layout(0, 0, width, scrollViewHeight);
         scrollView.scrollTo(0, visibleTop);
     }
 
@@ -499,6 +550,11 @@
         measureEditText(height);
     }
 
+    private void setupEditText(CharSequence text, int width, int height) {
+        createEditText(text);
+        measureEditText(width, height);
+    }
+
     private void setupEditText(CharSequence text, int height, float lineSpacing,
             float lineMultiplier) {
         createEditText(text);
@@ -537,6 +593,8 @@
         mEditText.setTypeface(sTypeface);
         mEditText.setText(text);
         mEditText.setTextSize(TypedValue.COMPLEX_UNIT_PX, TEXT_SIZE);
+        mEditText.setHandwritingBoundsOffsets(HW_BOUNDS_OFFSET_LEFT, HW_BOUNDS_OFFSET_TOP,
+                HW_BOUNDS_OFFSET_RIGHT, HW_BOUNDS_OFFSET_BOTTOM);
 
         mEditText.setPadding(0, 0, 0, 0);
         mEditText.setCompoundDrawables(null, null, null, null);
diff --git a/core/tests/coretests/src/android/widget/FloatingToolbarUtils.java b/core/tests/coretests/src/android/widget/FloatingToolbarUtils.java
index 2d3ed95..4ff1065 100644
--- a/core/tests/coretests/src/android/widget/FloatingToolbarUtils.java
+++ b/core/tests/coretests/src/android/widget/FloatingToolbarUtils.java
@@ -20,12 +20,12 @@
 import static com.google.common.truth.Truth.assertWithMessage;
 
 import android.content.res.Resources;
-import android.support.test.uiautomator.By;
-import android.support.test.uiautomator.BySelector;
-import android.support.test.uiautomator.UiDevice;
-import android.support.test.uiautomator.Until;
 
 import androidx.test.InstrumentationRegistry;
+import androidx.test.uiautomator.By;
+import androidx.test.uiautomator.BySelector;
+import androidx.test.uiautomator.UiDevice;
+import androidx.test.uiautomator.Until;
 
 import com.android.internal.R;
 
diff --git a/core/tests/coretests/src/android/widget/PointerIconTest.java b/core/tests/coretests/src/android/widget/PointerIconTest.java
new file mode 100644
index 0000000..8e9e1a5
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/PointerIconTest.java
@@ -0,0 +1,306 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.widget;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.annotation.UiThread;
+import android.app.Instrumentation;
+import android.graphics.Rect;
+import android.os.SystemClock;
+import android.view.InputDevice;
+import android.view.MotionEvent;
+import android.view.PointerIcon;
+import android.view.View;
+
+import androidx.test.ext.junit.rules.ActivityScenarioRule;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.frameworks.coretests.R;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class PointerIconTest {
+    private Instrumentation mInstrumentation;
+
+    @Before
+    public void setup() {
+        mInstrumentation = InstrumentationRegistry.getInstrumentation();
+    }
+
+    @Rule
+    public ActivityScenarioRule<PointerIconTestActivity> mActivityScenarioRule =
+            new ActivityScenarioRule<>(PointerIconTestActivity.class);
+
+    @Test
+    @UiThread
+    public void button_mouse_onResolvePointerIcon_returnsTypeHand() {
+        assertOnResolvePointerIconForMouseEvent(R.id.button, PointerIcon.TYPE_HAND);
+    }
+
+    @Test
+    @UiThread
+    public void button_mouse_disabled_onResolvePointerIcon_returnsNull() {
+        assertOnResolvePointerIconReturnNull(R.id.button, /* enabled */ false, /* clickable */ true,
+                /* isMouse */ true);
+    }
+
+    @Test
+    @UiThread
+    public void button_mouse_unclickable_onResolvePointerIcon_returnsNull() {
+        assertOnResolvePointerIconReturnNull(R.id.button, /* enabled */ true, /* clickable */ false,
+                /* isMouse */ true);
+    }
+
+    @Test
+    @UiThread
+    public void button_stylus_onResolvePointerIcon_returnsNull() {
+        assertOnResolvePointerIconReturnNull(R.id.button, /* enabled */ true, /* clickable */ true,
+                /* isMouse */ false);
+    }
+
+    @Test
+    @UiThread
+    public void imageButton_mouse_onResolvePointerIconreturnsTypeHand() {
+        assertOnResolvePointerIconForMouseEvent(R.id.imagebutton, PointerIcon.TYPE_HAND);
+    }
+
+    @Test
+    @UiThread
+    public void imageButton_mouse_diabled_onResolvePointerIcon_returnsNull() {
+        assertOnResolvePointerIconReturnNull(R.id.imagebutton, /* enabled */ false,
+                /* clickable */ true, /* isMouse */ true);
+    }
+
+    @Test
+    @UiThread
+    public void imageButton_mouse_unclickable_onResolvePointerIcon_returnsNull() {
+        assertOnResolvePointerIconReturnNull(R.id.imagebutton, /* enabled */ true,
+                /* clickable */ false, /* isMouse */ true);
+    }
+
+    @Test
+    @UiThread
+    public void imageButton_stylus_onResolvePointerIcon_returnsNull() {
+        assertOnResolvePointerIconReturnNull(R.id.imagebutton, /* enabled */ true,
+                /* clickable */ true, /* isMouse */ false);
+    }
+
+    @Test
+    @UiThread
+    public void textView_mouse_onResolvePointerIcon_returnsNull() {
+        assertOnResolvePointerIconReturnNull(R.id.textview, /* enabled */ true,
+                /* clickable */ true, /* isMouse */ true);
+    }
+
+    @Test
+    @UiThread
+    public void textView_stylus_onResolvePointerIcon_returnsNull() {
+        assertOnResolvePointerIconReturnNull(R.id.textview, /* enabled */ true,
+                /* clickable */ true, /* isMouse */ false);
+    }
+
+    @Test
+    @UiThread
+    public void editText_mouse_onResolvePointerIcon_returnsTypeText() {
+        assertOnResolvePointerIconForMouseEvent(R.id.edittext, PointerIcon.TYPE_TEXT);
+    }
+
+    @Test
+    @UiThread
+    public void editText_stylus_onResolvePointerIcon_returnsNull() {
+        assertOnResolvePointerIconReturnNull(R.id.edittext, /* enabled */ true,
+                /* clickable */ true, /* isMouse */ false);
+    }
+
+    @Test
+    @UiThread
+    public void spinner_mouse_onResolvePointerIcon_returnsTypeHand() {
+        assertOnResolvePointerIconForMouseEvent(R.id.spinner, PointerIcon.TYPE_HAND);
+    }
+
+    @Test
+    @UiThread
+    public void spinner_mouse_disabled_onResolvePointerIcon_returnsNull() {
+        assertOnResolvePointerIconReturnNull(R.id.spinner, /* enabled */ false,
+                /* clickable */ true, /* isMouse */ true);
+    }
+
+    @Test
+    @UiThread
+    public void spinner_mouse_unclickable_onResolvePointerIcon_returnsNull() {
+        assertOnResolvePointerIconReturnNull(R.id.spinner, /* enabled */ true,
+                /* clickable */ false, /* isMouse */ true);
+    }
+
+    @Test
+    @UiThread
+    public void spinner_stylus_onResolvePointerIcon_returnsNull() {
+        assertOnResolvePointerIconReturnNull(R.id.spinner, /* enabled */ true, /* clickable */ true,
+                /* isMouse */ false);
+    }
+
+    @Test
+    @UiThread
+    public void radialTimePickerView_mouse_onResolvePointerIcon_returnsTypeHand() {
+        assertOnResolvePointerIconForMouseEvent(R.id.timepicker, PointerIcon.TYPE_HAND);
+
+    }
+
+    @Test
+    @UiThread
+    public void radialTimePickerView_mouse_disabled_onResolvePointerIcon_returnsNull() {
+        assertOnResolvePointerIconReturnNull(R.id.timepicker, /* enabled */ false,
+                /* clickable */ true, /* isMouse */ true);
+    }
+
+    @Test
+    @UiThread
+    public void radialTimePickerView_stylus_onResolvePointerIcon_returnsNull() {
+        assertOnResolvePointerIconReturnNull(R.id.timepicker, /* enabled */ true,
+                /* clickable */ true, /* isMouse */ false);
+    }
+
+    @Test
+    @UiThread
+    public void calendarView_mouse_onResolvePointerIcon_returnsTypeHand() {
+        assertPointerIconForCalendarView(/* pointerType */ PointerIcon.TYPE_HAND,
+                /* isMouse */ true);
+    }
+
+    @Test
+    @UiThread
+    public void calendarView_stylus_onResolvePointerIcon_returnsNull() {
+        assertPointerIconForCalendarView(/* pointerType */ Integer.MIN_VALUE, /* isMouse */ false);
+    }
+
+    /**
+     * Assert {@link View#onResolvePointerIcon} method for {@link CalendarView}.
+     *
+     * @param pointerType the expected type of the {@link PointerIcon}.
+     *                    When {@link Integer#MIN_VALUE} is passed, it will verify that the
+     *                    returned {@link PointerIcon} is null.
+     * @param isMouse if true, mouse events are used to test the given view. Otherwise, it uses
+     *               stylus events to test the view.
+     */
+    void assertPointerIconForCalendarView(int pointerType, boolean isMouse) {
+        Calendar calendar = new GregorianCalendar();
+        calendar.set(2023, 0, 1);
+        long time = calendar.getTimeInMillis();
+        mActivityScenarioRule.getScenario().onActivity(activity -> {
+            CalendarView calendarView = activity.findViewById(R.id.calendar);
+            calendarView.setDate(time, /* animate */ false, /* center */true);
+        });
+
+        // Wait for setDate to finish and then verify.
+        mInstrumentation.waitForIdleSync();
+        mActivityScenarioRule.getScenario().onActivity(activity -> {
+            CalendarView calendarView = activity.findViewById(R.id.calendar);
+            Rect bounds = new Rect();
+            calendarView.getBoundsForDate(time, bounds);
+            MotionEvent event = createHoverEvent(isMouse, bounds.centerX(), bounds.centerY());
+            PointerIcon icon = calendarView.onResolvePointerIcon(event, /* pointerIndex */ 0);
+            if (pointerType != Integer.MIN_VALUE) {
+                assertThat(icon.getType()).isEqualTo(pointerType);
+            } else {
+                assertThat(icon).isNull();
+            }
+        });
+    }
+
+    /**
+     * Assert that the given view's {@link View#onResolvePointerIcon(MotionEvent, int)} method
+     * returns a {@link PointerIcon} with the specified pointer type. The passed {@link MotionEvent}
+     * locates at the center of the view.
+     *
+     * @param resId the resource id of the view to be tested.
+     * @param pointerType the expected pointer type. When {@link Integer#MIN_VALUE} is passed, it
+     *                   will verify that the returned {@link PointerIcon} is null.
+     */
+    public void assertOnResolvePointerIconForMouseEvent(int resId, int pointerType) {
+        mActivityScenarioRule.getScenario().onActivity(activity -> {
+            View view = activity.findViewById(resId);
+            MotionEvent event = createHoverEvent(/* isMouse */ true, /* x */ 0, /* y */ 0);
+            PointerIcon icon = view.onResolvePointerIcon(event, /* pointerIndex */ 0);
+            if (pointerType != Integer.MIN_VALUE) {
+                assertThat(icon.getType()).isEqualTo(pointerType);
+            } else {
+                assertThat(icon).isNull();
+            }
+        });
+    }
+
+    /**
+     * Assert that the given view's {@link View#onResolvePointerIcon(MotionEvent, int)} method
+     * returns a {@link PointerIcon} with the specified pointer type. The passed {@link MotionEvent}
+     * locates at the center of the view.
+     *
+     * @param resId the resource id of the view to be tested.
+     * @param enabled whether the tested  view is enabled.
+     * @param clickable whether the tested view is clickable.
+     * @param isMouse if true, mouse events are used to test the given view. Otherwise, it uses
+     *               stylus events to test the view.
+     */
+    public void assertOnResolvePointerIconReturnNull(int resId, boolean enabled, boolean clickable,
+            boolean isMouse) {
+        mActivityScenarioRule.getScenario().onActivity(activity -> {
+            View view = activity.findViewById(resId);
+            view.setEnabled(enabled);
+            view.setClickable(clickable);
+            MotionEvent event = createHoverEvent(isMouse, /* x */ 0, /* y */ 0);
+            PointerIcon icon = view.onResolvePointerIcon(event, /* pointerIndex */ 0);
+            assertThat(icon).isNull();
+        });
+    }
+
+
+    /**
+     * Create a hover {@link MotionEvent} for testing.
+     *
+     * @param isMouse if true, a {@link MotionEvent} from mouse is returned. Otherwise,
+     *               a {@link MotionEvent} from stylus is returned.
+     * @param x the x coordinate of the returned {@link MotionEvent}
+     * @param y the y coordinate of the returned {@link MotionEvent}
+     */
+    private MotionEvent createHoverEvent(boolean isMouse, int x, int y) {
+        MotionEvent.PointerProperties[] properties = MotionEvent.PointerProperties.createArray(1);
+        properties[0].toolType =
+                isMouse ? MotionEvent.TOOL_TYPE_MOUSE : MotionEvent.TOOL_TYPE_STYLUS;
+
+        MotionEvent.PointerCoords[] coords = MotionEvent.PointerCoords.createArray(1);
+        coords[0].x = x;
+        coords[0].y = y;
+
+        int source = isMouse ? InputDevice.SOURCE_MOUSE : InputDevice.SOURCE_STYLUS;
+        long eventTime = SystemClock.uptimeMillis();
+        return MotionEvent.obtain(/* downTime */ 0, eventTime, MotionEvent.ACTION_HOVER_MOVE,
+                /* pointerCount */ 1, properties, coords, /* metaState */ 0, /* buttonState */ 0,
+                /* xPrecision */ 1, /* yPrecision */ 1, /* deviceId */ 0, /* edgeFlags */ 0,
+                source, /* flags */ 0);
+    }
+
+}
diff --git a/core/tests/coretests/src/android/widget/PointerIconTestActivity.java b/core/tests/coretests/src/android/widget/PointerIconTestActivity.java
new file mode 100644
index 0000000..d491fb0
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/PointerIconTestActivity.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 android.widget;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+import com.android.frameworks.coretests.R;
+
+public class PointerIconTestActivity extends Activity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.pointer_icon_test);
+
+        RadialTimePickerView timePicker = findViewById(R.id.timepicker);
+        // Set the time of TimePicker to 0:00 so that the test is stable.
+        timePicker.setCurrentHour(0);
+        timePicker.setCurrentMinute(0);
+    }
+}
diff --git a/core/tests/coretests/src/android/widget/RemoteViewsAdapterTest.java b/core/tests/coretests/src/android/widget/RemoteViewsAdapterTest.java
index 184b9ea..4f722ce 100644
--- a/core/tests/coretests/src/android/widget/RemoteViewsAdapterTest.java
+++ b/core/tests/coretests/src/android/widget/RemoteViewsAdapterTest.java
@@ -353,6 +353,23 @@
         public boolean isCreated() {
             return false;
         }
+
+        @Override
+        public RemoteViews.RemoteCollectionItems getRemoteCollectionItems() {
+            RemoteViews.RemoteCollectionItems.Builder itemsBuilder =
+                    new RemoteViews.RemoteCollectionItems.Builder();
+            itemsBuilder.setHasStableIds(hasStableIds())
+                    .setViewTypeCount(getViewTypeCount());
+            try {
+                for (int i = 0; i < mCount; i++) {
+                    itemsBuilder.addItem(getItemId(i), getViewAt(i));
+                }
+            } catch (RemoteException e) {
+                // No-op
+            }
+
+            return itemsBuilder.build();
+        }
     }
 
     private static class DistinctIntent extends Intent {
diff --git a/core/tests/coretests/src/android/widget/RemoteViewsTest.java b/core/tests/coretests/src/android/widget/RemoteViewsTest.java
index 73aa936..c8ea374 100644
--- a/core/tests/coretests/src/android/widget/RemoteViewsTest.java
+++ b/core/tests/coretests/src/android/widget/RemoteViewsTest.java
@@ -22,6 +22,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.spy;
@@ -33,17 +34,24 @@
 import android.appwidget.AppWidgetHostView;
 import android.content.Context;
 import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
 import android.graphics.drawable.Icon;
 import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Binder;
 import android.os.Looper;
 import android.os.Parcel;
+import android.util.AttributeSet;
 import android.util.SizeF;
 import android.view.ContextThemeWrapper;
+import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -827,4 +835,71 @@
         verify(visitor, times(1)).accept(eq(icon3S.getUri()));
         verify(visitor, times(1)).accept(eq(icon4S.getUri()));
     }
+
+    @Test
+    public void layoutInflaterFactory_nothingSet_returnsNull() {
+        final RemoteViews rv = new RemoteViews(mPackage, R.layout.remote_views_test);
+        assertNull(rv.getLayoutInflaterFactory());
+    }
+
+    @Test
+    public void layoutInflaterFactory_replacesImageView_viewReplaced() {
+        final RemoteViews rv = new RemoteViews(mPackage, R.layout.remote_views_test);
+        final View replacement = new FrameLayout(mContext);
+        replacement.setId(1337);
+
+        LayoutInflater.Factory2 factory = createLayoutInflaterFactory("ImageView", replacement);
+        rv.setLayoutInflaterFactory(factory);
+
+        // Now inflate the views.
+        View inflated = rv.apply(mContext, mContainer);
+
+        assertEquals(factory, rv.getLayoutInflaterFactory());
+        View replacedFrameLayout = inflated.findViewById(1337);
+        assertNotNull(replacedFrameLayout);
+        assertEquals(replacement, replacedFrameLayout);
+        // ImageView should be fully replaced.
+        assertNull(inflated.findViewById(R.id.image));
+    }
+
+    @Test
+    public void layoutInflaterFactory_replacesImageView_settersStillFunctional() {
+        final RemoteViews rv = new RemoteViews(mPackage, R.layout.remote_views_test);
+        final TextView replacement = new TextView(mContext);
+        replacement.setId(R.id.text);
+        final String testText = "testText";
+        rv.setLayoutInflaterFactory(createLayoutInflaterFactory("TextView", replacement));
+        rv.setTextViewText(R.id.text, testText);
+
+
+        // Now inflate the views.
+        View inflated = rv.apply(mContext, mContainer);
+
+        TextView replacedTextView = inflated.findViewById(R.id.text);
+        assertSame(replacement, replacedTextView);
+        assertEquals(testText, replacedTextView.getText());
+    }
+
+    private static LayoutInflater.Factory2 createLayoutInflaterFactory(String viewTypeToReplace,
+            View replacementView) {
+        return new LayoutInflater.Factory2() {
+            @Nullable
+            @Override
+            public View onCreateView(@Nullable View parent, @NonNull String name,
+                                     @NonNull Context context, @NonNull AttributeSet attrs) {
+                if (viewTypeToReplace.equals(name)) {
+                    return replacementView;
+                }
+
+                return null;
+            }
+
+            @Nullable
+            @Override
+            public View onCreateView(@NonNull String name, @NonNull Context context,
+                                     @NonNull AttributeSet attrs) {
+                return null;
+            }
+        };
+    }
 }
diff --git a/core/tests/coretests/src/android/widget/TextViewActivityTest.java b/core/tests/coretests/src/android/widget/TextViewActivityTest.java
index 659cd98..9cf2e42 100644
--- a/core/tests/coretests/src/android/widget/TextViewActivityTest.java
+++ b/core/tests/coretests/src/android/widget/TextViewActivityTest.java
@@ -67,9 +67,6 @@
 import android.content.Intent;
 import android.graphics.drawable.Icon;
 import android.os.Bundle;
-import android.support.test.uiautomator.By;
-import android.support.test.uiautomator.UiDevice;
-import android.support.test.uiautomator.Until;
 import android.text.InputType;
 import android.text.Selection;
 import android.text.Spannable;
@@ -95,6 +92,9 @@
 import androidx.test.filters.Suppress;
 import androidx.test.rule.ActivityTestRule;
 import androidx.test.runner.AndroidJUnit4;
+import androidx.test.uiautomator.By;
+import androidx.test.uiautomator.UiDevice;
+import androidx.test.uiautomator.Until;
 
 import com.android.frameworks.coretests.R;
 
diff --git a/core/tests/coretests/src/android/window/BackNavigationTest.java b/core/tests/coretests/src/android/window/BackNavigationTest.java
index d6145eb..a66fe26 100644
--- a/core/tests/coretests/src/android/window/BackNavigationTest.java
+++ b/core/tests/coretests/src/android/window/BackNavigationTest.java
@@ -26,12 +26,12 @@
 import android.app.EmptyActivity;
 import android.app.Instrumentation;
 import android.os.RemoteException;
-import android.support.test.uiautomator.UiDevice;
 
 import androidx.lifecycle.Lifecycle;
 import androidx.test.core.app.ActivityScenario;
 import androidx.test.ext.junit.rules.ActivityScenarioRule;
 import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.uiautomator.UiDevice;
 
 import org.junit.Before;
 import org.junit.Rule;
diff --git a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java
index 57a1376..681ba9c 100644
--- a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java
@@ -43,6 +43,7 @@
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.ParceledListSlice;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
 import android.os.Handler;
@@ -111,7 +112,8 @@
         when(mResolveInfo.loadLabel(any(PackageManager.class))).thenReturn(TEST_LABEL);
         when(mAccessibilityServiceInfo.getComponentName()).thenReturn(TEST_COMPONENT_NAME);
         when(mAccessibilityManagerService.getInstalledAccessibilityServiceList(
-                anyInt())).thenReturn(Collections.singletonList(mAccessibilityServiceInfo));
+                anyInt())).thenReturn(new ParceledListSlice<>(
+                        Collections.singletonList(mAccessibilityServiceInfo)));
         when(mAccessibilityManagerService.isAccessibilityTargetAllowed(
                 anyString(), anyInt(), anyInt())).thenReturn(true);
         TestAccessibilityShortcutChooserActivity.setupForTesting(mAccessibilityManagerService);
diff --git a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java
index 9763679..2de1230 100644
--- a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java
+++ b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java
@@ -55,6 +55,7 @@
 import android.content.DialogInterface;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.ParceledListSlice;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
 import android.content.res.Resources;
@@ -150,7 +151,7 @@
         when(mContext.getContentResolver()).thenReturn(mContentResolver);
 
         when(mAccessibilityManagerService.getInstalledAccessibilityServiceList(anyInt()))
-                .thenReturn(Collections.singletonList(mServiceInfo));
+                .thenReturn(new ParceledListSlice<>(Collections.singletonList(mServiceInfo)));
 
         // Use the extra level of indirection in the object to mock framework objects
         AccessibilityManager accessibilityManager =
diff --git a/core/tests/coretests/src/com/android/internal/accessibility/TEST_MAPPING b/core/tests/coretests/src/com/android/internal/accessibility/TEST_MAPPING
new file mode 100644
index 0000000..1c67399
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/accessibility/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "imports": [
+    {
+      "path": "frameworks/base/services/accessibility/TEST_MAPPING"
+    }
+  ]
+}
diff --git a/core/tests/coretests/src/com/android/internal/content/OWNERS b/core/tests/coretests/src/com/android/internal/content/OWNERS
new file mode 100644
index 0000000..dd9ede5
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/content/OWNERS
@@ -0,0 +1,4 @@
+
+per-file PackageMonitorTest.java = file:/core/java/android/content/pm/OWNERS
+
+per-file Overlay* = file:/core/java/android/content/res/OWNERS
diff --git a/core/tests/coretests/src/com/android/internal/content/PackageMonitorTest.java b/core/tests/coretests/src/com/android/internal/content/PackageMonitorTest.java
new file mode 100644
index 0000000..7ccbf1d
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/content/PackageMonitorTest.java
@@ -0,0 +1,347 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.content;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.UserHandle;
+
+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;
+
+/**
+ * A unit test for PackageMonitor implementation.
+ */
+@RunWith(AndroidJUnit4.class)
+public class PackageMonitorTest {
+
+    private static final String FAKE_PACKAGE_NAME = "com.android.internal.content.fakeapp";
+    private static final int FAKE_PACKAGE_UID = 123;
+    private static final int FAKE_USER_ID = 0;
+
+    @Mock
+    Context mMockContext;
+    @Mock
+    Handler mMockHandler;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    @Test
+    public void testPackageMonitorMultipleRegisterThrowsException() throws Exception {
+        PackageMonitor spyPackageMonitor = spy(new TestPackageMonitor());
+
+        spyPackageMonitor.register(mMockContext, UserHandle.ALL, mMockHandler);
+        assertThat(spyPackageMonitor.getRegisteredHandler()).isEqualTo(mMockHandler);
+        verify(mMockContext, times(2)).registerReceiverAsUser(any(), eq(UserHandle.ALL), any(),
+                eq(null), eq(mMockHandler));
+
+        assertThrows(IllegalStateException.class,
+                () -> spyPackageMonitor.register(mMockContext, UserHandle.ALL, mMockHandler));
+    }
+
+    @Test
+    public void testPackageMonitorRegisterMultipleUnRegisterThrowsException() throws Exception {
+        PackageMonitor spyPackageMonitor = spy(new TestPackageMonitor());
+
+        spyPackageMonitor.register(mMockContext, UserHandle.ALL, mMockHandler);
+        spyPackageMonitor.unregister();
+
+        assertThrows(IllegalStateException.class, spyPackageMonitor::unregister);
+    }
+
+    @Test
+    public void testPackageMonitorNotRegisterUnRegisterThrowsException() throws Exception {
+        PackageMonitor spyPackageMonitor = spy(new TestPackageMonitor());
+
+        assertThrows(IllegalStateException.class, spyPackageMonitor::unregister);
+    }
+
+    @Test
+    public void testPackageMonitorDoHandlePackageEventUidRemoved() throws Exception {
+        PackageMonitor spyPackageMonitor = spy(new TestPackageMonitor());
+
+        Intent intent = new Intent(Intent.ACTION_UID_REMOVED);
+        intent.putExtra(Intent.EXTRA_USER_HANDLE, FAKE_USER_ID);
+        intent.putExtra(Intent.EXTRA_UID, FAKE_PACKAGE_UID);
+        spyPackageMonitor.doHandlePackageEvent(intent);
+
+        verify(spyPackageMonitor, times(1)).onBeginPackageChanges();
+        verify(spyPackageMonitor, times(1)).onUidRemoved(eq(FAKE_PACKAGE_UID));
+        verify(spyPackageMonitor, times(1)).onFinishPackageChanges();
+    }
+
+    @Test
+    public void testPackageMonitorDoHandlePackageEventPackageSuspended() throws Exception {
+        PackageMonitor spyPackageMonitor = spy(new TestPackageMonitor());
+
+        Intent intent = new Intent(Intent.ACTION_PACKAGES_SUSPENDED);
+        intent.putExtra(Intent.EXTRA_USER_HANDLE, FAKE_USER_ID);
+        String [] packageList = new String[]{FAKE_PACKAGE_NAME};
+        intent.putExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST, packageList);
+        spyPackageMonitor.doHandlePackageEvent(intent);
+
+        verify(spyPackageMonitor, times(1)).onBeginPackageChanges();
+        verify(spyPackageMonitor, times(1)).onPackagesSuspended(eq(packageList));
+        verify(spyPackageMonitor, times(1)).onSomePackagesChanged();
+        verify(spyPackageMonitor, times(1)).onFinishPackageChanges();
+    }
+
+    @Test
+    public void testPackageMonitorDoHandlePackageEventPackageUnSuspended() throws Exception {
+        PackageMonitor spyPackageMonitor = spy(new TestPackageMonitor());
+
+        Intent intent = new Intent(Intent.ACTION_PACKAGES_UNSUSPENDED);
+        intent.putExtra(Intent.EXTRA_USER_HANDLE, FAKE_USER_ID);
+        String [] packageList = new String[]{FAKE_PACKAGE_NAME};
+        intent.putExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST, packageList);
+        spyPackageMonitor.doHandlePackageEvent(intent);
+
+        verify(spyPackageMonitor, times(1)).onBeginPackageChanges();
+        verify(spyPackageMonitor, times(1)).onPackagesUnsuspended(eq(packageList));
+        verify(spyPackageMonitor, times(1)).onSomePackagesChanged();
+        verify(spyPackageMonitor, times(1)).onFinishPackageChanges();
+    }
+
+    @Test
+    public void testPackageMonitorDoHandlePackageEventUserStop() throws Exception {
+        PackageMonitor spyPackageMonitor = spy(new TestPackageMonitor());
+
+        Intent intent = new Intent(Intent.ACTION_USER_STOPPED);
+        intent.putExtra(Intent.EXTRA_USER_HANDLE, FAKE_USER_ID);
+        spyPackageMonitor.doHandlePackageEvent(intent);
+
+        verify(spyPackageMonitor, times(1)).onBeginPackageChanges();
+        verify(spyPackageMonitor, times(1)).onHandleUserStop(eq(intent), eq(FAKE_USER_ID));
+        verify(spyPackageMonitor, times(1)).onFinishPackageChanges();
+    }
+
+    @Test
+    public void testPackageMonitorDoHandlePackageEventExternalApplicationAvailable()
+            throws Exception {
+        PackageMonitor spyPackageMonitor = spy(new TestPackageMonitor());
+
+        Intent intent = new Intent(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
+        intent.putExtra(Intent.EXTRA_USER_HANDLE, FAKE_USER_ID);
+        String [] packageList = new String[]{FAKE_PACKAGE_NAME};
+        intent.putExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST, packageList);
+        intent.putExtra(Intent.EXTRA_REPLACING, true);
+        spyPackageMonitor.doHandlePackageEvent(intent);
+
+        verify(spyPackageMonitor, times(1)).onBeginPackageChanges();
+        verify(spyPackageMonitor, times(1)).onPackagesAvailable(eq(packageList));
+        verify(spyPackageMonitor, times(1)).onPackageAppeared(eq(FAKE_PACKAGE_NAME),
+                eq(PackageMonitor.PACKAGE_UPDATING));
+        verify(spyPackageMonitor, times(1)).onSomePackagesChanged();
+        verify(spyPackageMonitor, times(1)).onFinishPackageChanges();
+    }
+
+    @Test
+    public void testPackageMonitorDoHandlePackageEventExternalApplicationUnavailable()
+            throws Exception {
+        PackageMonitor spyPackageMonitor = spy(new TestPackageMonitor());
+
+        Intent intent = new Intent(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
+        intent.putExtra(Intent.EXTRA_USER_HANDLE, FAKE_USER_ID);
+        String [] packageList = new String[]{FAKE_PACKAGE_NAME};
+        intent.putExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST, packageList);
+        intent.putExtra(Intent.EXTRA_REPLACING, true);
+        spyPackageMonitor.doHandlePackageEvent(intent);
+
+        verify(spyPackageMonitor, times(1)).onBeginPackageChanges();
+        verify(spyPackageMonitor, times(1)).onPackagesUnavailable(eq(packageList));
+        verify(spyPackageMonitor, times(1)).onPackageDisappeared(eq(FAKE_PACKAGE_NAME),
+                eq(PackageMonitor.PACKAGE_UPDATING));
+        verify(spyPackageMonitor, times(1)).onSomePackagesChanged();
+        verify(spyPackageMonitor, times(1)).onFinishPackageChanges();
+    }
+
+    @Test
+    public void testPackageMonitorDoHandlePackageEventPackageRestarted() throws Exception {
+        PackageMonitor spyPackageMonitor = spy(new TestPackageMonitor());
+
+        Intent intent = new Intent(Intent.ACTION_PACKAGE_RESTARTED);
+        intent.putExtra(Intent.EXTRA_USER_HANDLE, FAKE_USER_ID);
+        intent.putExtra(Intent.EXTRA_UID, FAKE_PACKAGE_UID);
+        intent.setData(Uri.fromParts("package", FAKE_PACKAGE_NAME, null));
+        spyPackageMonitor.doHandlePackageEvent(intent);
+
+        verify(spyPackageMonitor, times(1)).onBeginPackageChanges();
+        verify(spyPackageMonitor, times(1)).onHandleForceStop(eq(intent),
+                eq(new String[]{FAKE_PACKAGE_NAME}), eq(FAKE_PACKAGE_UID), eq(true));
+        verify(spyPackageMonitor, times(1)).onFinishPackageChanges();
+    }
+
+    @Test
+    public void testPackageMonitorDoHandlePackageEventPackageQueryRestarted() throws Exception {
+        PackageMonitor spyPackageMonitor = spy(new TestPackageMonitor());
+
+        Intent intent = new Intent(Intent.ACTION_QUERY_PACKAGE_RESTART);
+        intent.putExtra(Intent.EXTRA_USER_HANDLE, FAKE_USER_ID);
+        String [] packageList = new String[]{FAKE_PACKAGE_NAME};
+        intent.putExtra(Intent.EXTRA_PACKAGES, packageList);
+        intent.putExtra(Intent.EXTRA_UID, FAKE_PACKAGE_UID);
+        spyPackageMonitor.doHandlePackageEvent(intent);
+
+        verify(spyPackageMonitor, times(1)).onBeginPackageChanges();
+        verify(spyPackageMonitor, times(1)).onHandleForceStop(eq(intent),
+                eq(packageList), eq(FAKE_PACKAGE_UID), eq(false));
+        verify(spyPackageMonitor, times(1)).onFinishPackageChanges();
+    }
+
+    @Test
+    public void testPackageMonitorDoHandlePackageEventPackageDataClear() throws Exception {
+        PackageMonitor spyPackageMonitor = spy(new TestPackageMonitor());
+
+        Intent intent = new Intent(Intent.ACTION_PACKAGE_DATA_CLEARED);
+        intent.setData(Uri.fromParts("package", FAKE_PACKAGE_NAME, null));
+        intent.putExtra(Intent.EXTRA_USER_HANDLE, FAKE_USER_ID);
+        intent.putExtra(Intent.EXTRA_UID, FAKE_PACKAGE_UID);
+        spyPackageMonitor.doHandlePackageEvent(intent);
+
+        verify(spyPackageMonitor, times(1)).onBeginPackageChanges();
+        verify(spyPackageMonitor, times(1))
+                .onPackageDataCleared(eq(FAKE_PACKAGE_NAME), eq(FAKE_PACKAGE_UID));
+        verify(spyPackageMonitor, times(1)).onFinishPackageChanges();
+    }
+
+    @Test
+    public void testPackageMonitorDoHandlePackageEventPackageChanged() throws Exception {
+        PackageMonitor spyPackageMonitor = spy(new TestPackageMonitor());
+
+        Intent intent = new Intent(Intent.ACTION_PACKAGE_CHANGED);
+        intent.setData(Uri.fromParts("package", FAKE_PACKAGE_NAME, null));
+        intent.putExtra(Intent.EXTRA_USER_HANDLE, FAKE_USER_ID);
+        intent.putExtra(Intent.EXTRA_UID, FAKE_PACKAGE_UID);
+        String [] packageList = new String[]{FAKE_PACKAGE_NAME};
+        intent.putExtra(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST, packageList);
+        spyPackageMonitor.doHandlePackageEvent(intent);
+
+        verify(spyPackageMonitor, times(1)).onBeginPackageChanges();
+        verify(spyPackageMonitor, times(1))
+                .onPackageChanged(eq(FAKE_PACKAGE_NAME), eq(FAKE_PACKAGE_UID), eq(packageList));
+        verify(spyPackageMonitor, times(1)).onPackageModified(eq(FAKE_PACKAGE_NAME));
+        verify(spyPackageMonitor, times(1)).onSomePackagesChanged();
+        verify(spyPackageMonitor, times(1)).onFinishPackageChanges();
+    }
+
+    @Test
+    public void testPackageMonitorDoHandlePackageEventPackageRemovedReplacing() throws Exception {
+        PackageMonitor spyPackageMonitor = spy(new TestPackageMonitor());
+
+        Intent intent = new Intent(Intent.ACTION_PACKAGE_REMOVED);
+        intent.setData(Uri.fromParts("package", FAKE_PACKAGE_NAME, null));
+        intent.putExtra(Intent.EXTRA_USER_HANDLE, FAKE_USER_ID);
+        intent.putExtra(Intent.EXTRA_UID, FAKE_PACKAGE_UID);
+        intent.putExtra(Intent.EXTRA_REPLACING, true);
+        intent.putExtra(Intent.EXTRA_REMOVED_FOR_ALL_USERS, true);
+        spyPackageMonitor.doHandlePackageEvent(intent);
+
+        verify(spyPackageMonitor, times(1)).onBeginPackageChanges();
+        verify(spyPackageMonitor, times(1))
+                .onPackageUpdateStarted(eq(FAKE_PACKAGE_NAME), eq(FAKE_PACKAGE_UID));
+        verify(spyPackageMonitor, times(1))
+                .onPackageDisappeared(eq(FAKE_PACKAGE_NAME), eq(PackageMonitor.PACKAGE_UPDATING));
+        verify(spyPackageMonitor, times(1)).onFinishPackageChanges();
+    }
+
+    @Test
+    public void testPackageMonitorDoHandlePackageEventPackageRemovedNotReplacing()
+            throws Exception {
+        PackageMonitor spyPackageMonitor = spy(new TestPackageMonitor());
+
+        Intent intent = new Intent(Intent.ACTION_PACKAGE_REMOVED);
+        intent.setData(Uri.fromParts("package", FAKE_PACKAGE_NAME, null));
+        intent.putExtra(Intent.EXTRA_USER_HANDLE, FAKE_USER_ID);
+        intent.putExtra(Intent.EXTRA_UID, FAKE_PACKAGE_UID);
+        intent.putExtra(Intent.EXTRA_REPLACING, false);
+        intent.putExtra(Intent.EXTRA_REMOVED_FOR_ALL_USERS, true);
+        spyPackageMonitor.doHandlePackageEvent(intent);
+
+        verify(spyPackageMonitor, times(1)).onBeginPackageChanges();
+        verify(spyPackageMonitor, times(1))
+                .onPackageRemoved(eq(FAKE_PACKAGE_NAME), eq(FAKE_PACKAGE_UID));
+        verify(spyPackageMonitor, times(1))
+                .onPackageRemovedAllUsers(eq(FAKE_PACKAGE_NAME), eq(FAKE_PACKAGE_UID));
+        verify(spyPackageMonitor, times(1)).onPackageDisappeared(eq(FAKE_PACKAGE_NAME),
+                eq(PackageMonitor.PACKAGE_PERMANENT_CHANGE));
+        verify(spyPackageMonitor, times(1)).onSomePackagesChanged();
+        verify(spyPackageMonitor, times(1)).onFinishPackageChanges();
+    }
+
+    @Test
+    public void testPackageMonitorDoHandlePackageEventPackageAddReplacing() throws Exception {
+        PackageMonitor spyPackageMonitor = spy(new TestPackageMonitor());
+
+        Intent intent = new Intent(Intent.ACTION_PACKAGE_ADDED);
+        intent.setData(Uri.fromParts("package", FAKE_PACKAGE_NAME, null));
+        intent.putExtra(Intent.EXTRA_USER_HANDLE, FAKE_USER_ID);
+        intent.putExtra(Intent.EXTRA_UID, FAKE_PACKAGE_UID);
+        intent.putExtra(Intent.EXTRA_REPLACING, true);
+        spyPackageMonitor.doHandlePackageEvent(intent);
+
+        verify(spyPackageMonitor, times(1)).onBeginPackageChanges();
+        verify(spyPackageMonitor, times(1))
+                .onPackageUpdateFinished(eq(FAKE_PACKAGE_NAME), eq(FAKE_PACKAGE_UID));
+        verify(spyPackageMonitor, times(1)).onPackageModified(eq(FAKE_PACKAGE_NAME));
+        verify(spyPackageMonitor, times(1))
+                .onPackageAppeared(eq(FAKE_PACKAGE_NAME), eq(PackageMonitor.PACKAGE_UPDATING));
+        verify(spyPackageMonitor, times(1)).onSomePackagesChanged();
+        verify(spyPackageMonitor, times(1)).onFinishPackageChanges();
+    }
+
+    @Test
+    public void testPackageMonitorDoHandlePackageEventPackageAddNotReplacing() throws Exception {
+        PackageMonitor spyPackageMonitor = spy(new TestPackageMonitor());
+
+        Intent intent = new Intent(Intent.ACTION_PACKAGE_ADDED);
+        intent.setData(Uri.fromParts("package", FAKE_PACKAGE_NAME, null));
+        intent.putExtra(Intent.EXTRA_USER_HANDLE, FAKE_USER_ID);
+        intent.putExtra(Intent.EXTRA_UID, FAKE_PACKAGE_UID);
+        intent.putExtra(Intent.EXTRA_REPLACING, false);
+        spyPackageMonitor.doHandlePackageEvent(intent);
+
+        verify(spyPackageMonitor, times(1)).onBeginPackageChanges();
+        verify(spyPackageMonitor, times(1))
+                .onPackageAdded(eq(FAKE_PACKAGE_NAME), eq(FAKE_PACKAGE_UID));
+        verify(spyPackageMonitor, times(1)).onPackageAppeared(eq(FAKE_PACKAGE_NAME),
+                eq(PackageMonitor.PACKAGE_PERMANENT_CHANGE));
+        verify(spyPackageMonitor, times(1)).onSomePackagesChanged();
+        verify(spyPackageMonitor, times(1)).onFinishPackageChanges();
+    }
+
+    public static class TestPackageMonitor extends PackageMonitor {
+    }
+}
diff --git a/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigTest.java b/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigTest.java
index 0f30cfe..246a1e7 100644
--- a/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigTest.java
+++ b/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigTest.java
@@ -22,6 +22,7 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
+import android.content.pm.PackagePartitions;
 import android.os.FileUtils;
 import android.os.SystemProperties;
 import android.platform.test.annotations.Presubmit;
@@ -32,6 +33,7 @@
 import com.android.frameworks.coretests.R;
 import com.android.internal.content.om.OverlayConfig;
 import com.android.internal.content.om.OverlayConfig.IdmapInvocation;
+import com.android.internal.content.om.OverlayConfigParser.OverlayPartition;
 import com.android.internal.content.om.OverlayScanner;
 
 import org.junit.Rule;
@@ -46,6 +48,7 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.ArrayList;
+import java.util.List;
 
 @Presubmit
 @RunWith(AndroidJUnit4.class)
@@ -88,6 +91,17 @@
         assertEquals(configIndex, config.configIndex);
     }
 
+    private String generatePartitionOrderString(List<OverlayPartition> partitions) {
+        StringBuilder partitionOrder = new StringBuilder();
+        for (int i = 0; i < partitions.size(); i++) {
+            partitionOrder.append(partitions.get(i).getName());
+            if (i < partitions.size() - 1) {
+                partitionOrder.append(", ");
+            }
+        }
+        return partitionOrder.toString();
+    }
+
     @Test
     public void testImmutableAfterNonImmutableFails() throws IOException {
         mExpectedException.expect(IllegalStateException.class);
@@ -685,4 +699,122 @@
         OverlayConfig.Configuration o3 = overlayConfig.getConfiguration("three");
         assertNotNull(o3);
     }
+
+    @Test
+    public void testSortPartitionsWithoutXml() throws IOException {
+        ArrayList<OverlayPartition> partitions = new ArrayList<>(
+                PackagePartitions.getOrderedPartitions(OverlayPartition::new));
+
+        final OverlayConfig overlayConfig = createConfigImpl();
+        String partitionOrderFilePath = String.format("%s/%s", mTestFolder.getRoot(),
+                "/product/overlay/partition_order.xml");
+        assertEquals(false, overlayConfig.sortPartitions(partitionOrderFilePath, partitions));
+        assertEquals("system, vendor, odm, oem, product, system_ext",
+                generatePartitionOrderString(partitions));
+    }
+
+    @Test
+    public void testSortPartitionsWithInvalidXmlRootElement() throws IOException {
+        ArrayList<OverlayPartition> partitions = new ArrayList<>(
+                PackagePartitions.getOrderedPartitions(OverlayPartition::new));
+        createFile("/product/overlay/partition_order.xml",
+                "<partition-list>\n"
+                        + "  <partition name=\"system_ext\"/>\n"
+                        + "  <partition name=\"vendor\"/>\n"
+                        + "  <partition name=\"oem\"/>\n"
+                        + "  <partition name=\"odm\"/>\n"
+                        + "  <partition name=\"product\"/>\n"
+                        + "  <partition name=\"system\"/>\n"
+                        + "</partition-list>\n");
+        final OverlayConfig overlayConfig = createConfigImpl();
+        String partitionOrderFilePath = String.format("%s/%s", mTestFolder.getRoot(),
+                "/product/overlay/partition_order.xml");
+        assertEquals(false, overlayConfig.sortPartitions(partitionOrderFilePath, partitions));
+        assertEquals("system, vendor, odm, oem, product, system_ext",
+                generatePartitionOrderString(partitions));
+    }
+
+    @Test
+    public void testSortPartitionsWithInvalidPartition() throws IOException {
+        ArrayList<OverlayPartition> partitions = new ArrayList<>(
+                PackagePartitions.getOrderedPartitions(OverlayPartition::new));
+        createFile("/product/overlay/partition_order.xml",
+                "<partition-order>\n"
+                        + "  <partition name=\"INVALID\"/>\n"
+                        + "  <partition name=\"vendor\"/>\n"
+                        + "  <partition name=\"oem\"/>\n"
+                        + "  <partition name=\"odm\"/>\n"
+                        + "  <partition name=\"product\"/>\n"
+                        + "  <partition name=\"system\"/>\n"
+                        + "</partition-order>\n");
+        final OverlayConfig overlayConfig = createConfigImpl();
+        String partitionOrderFilePath = String.format("%s/%s", mTestFolder.getRoot(),
+                "/product/overlay/partition_order.xml");
+        assertEquals(false, overlayConfig.sortPartitions(partitionOrderFilePath, partitions));
+        assertEquals("system, vendor, odm, oem, product, system_ext",
+                generatePartitionOrderString(partitions));
+    }
+
+    @Test
+    public void testSortPartitionsWithDuplicatePartition() throws IOException {
+        ArrayList<OverlayPartition> partitions = new ArrayList<>(
+                PackagePartitions.getOrderedPartitions(OverlayPartition::new));
+        createFile("/product/overlay/partition_order.xml",
+                "<partition-order>\n"
+                        + "  <partition name=\"system_ext\"/>\n"
+                        + "  <partition name=\"system\"/>\n"
+                        + "  <partition name=\"vendor\"/>\n"
+                        + "  <partition name=\"oem\"/>\n"
+                        + "  <partition name=\"odm\"/>\n"
+                        + "  <partition name=\"product\"/>\n"
+                        + "  <partition name=\"system\"/>\n"
+                        + "</partition-order>\n");
+        final OverlayConfig overlayConfig = createConfigImpl();
+        String partitionOrderFilePath = String.format("%s/%s", mTestFolder.getRoot(),
+                "/product/overlay/partition_order.xml");
+        assertEquals(false, overlayConfig.sortPartitions(partitionOrderFilePath, partitions));
+        assertEquals("system, vendor, odm, oem, product, system_ext",
+                generatePartitionOrderString(partitions));
+    }
+
+    @Test
+    public void testSortPartitionsWithMissingPartition() throws IOException {
+        ArrayList<OverlayPartition> partitions = new ArrayList<>(
+                PackagePartitions.getOrderedPartitions(OverlayPartition::new));
+        createFile("/product/overlay/partition_order.xml",
+                "<partition-order>\n"
+                        + "  <partition name=\"vendor\"/>\n"
+                        + "  <partition name=\"oem\"/>\n"
+                        + "  <partition name=\"odm\"/>\n"
+                        + "  <partition name=\"product\"/>\n"
+                        + "  <partition name=\"system\"/>\n"
+                        + "</partition-order>\n");
+        final OverlayConfig overlayConfig = createConfigImpl();
+        String partitionOrderFilePath = String.format("%s/%s", mTestFolder.getRoot(),
+                "/product/overlay/partition_order.xml");
+        assertEquals(false, overlayConfig.sortPartitions(partitionOrderFilePath, partitions));
+        assertEquals("system, vendor, odm, oem, product, system_ext",
+                generatePartitionOrderString(partitions));
+    }
+
+    @Test
+    public void testSortPartitionsWithCorrectPartitionOrderXml() throws IOException {
+        ArrayList<OverlayPartition> partitions = new ArrayList<>(
+                PackagePartitions.getOrderedPartitions(OverlayPartition::new));
+        createFile("/product/overlay/partition_order.xml",
+                "<partition-order>\n"
+                        + "  <partition name=\"system_ext\"/>\n"
+                        + "  <partition name=\"vendor\"/>\n"
+                        + "  <partition name=\"oem\"/>\n"
+                        + "  <partition name=\"odm\"/>\n"
+                        + "  <partition name=\"product\"/>\n"
+                        + "  <partition name=\"system\"/>\n"
+                        + "</partition-order>\n");
+        final OverlayConfig overlayConfig = createConfigImpl();
+        String partitionOrderFilePath = String.format("%s/%s", mTestFolder.getRoot(),
+                "/product/overlay/partition_order.xml");
+        assertEquals(true, overlayConfig.sortPartitions(partitionOrderFilePath, partitions));
+        assertEquals("system_ext, vendor, oem, odm, product, system",
+                generatePartitionOrderString(partitions));
+    }
 }
diff --git a/core/tests/coretests/src/com/android/internal/inputmethod/OWNERS b/core/tests/coretests/src/com/android/internal/inputmethod/OWNERS
index 5deb2ce..cbd94ba 100644
--- a/core/tests/coretests/src/com/android/internal/inputmethod/OWNERS
+++ b/core/tests/coretests/src/com/android/internal/inputmethod/OWNERS
@@ -1 +1,2 @@
+# Bug component: 34867
 include /core/java/android/view/inputmethod/OWNERS
diff --git a/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java b/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java
index 8f83461..b61f995 100644
--- a/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java
+++ b/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java
@@ -79,6 +79,7 @@
 
 import java.lang.reflect.Field;
 import java.lang.reflect.Modifier;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -93,6 +94,8 @@
     private static final String ENUM_NAME_PREFIX =
             "UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__";
 
+    private static final ArrayList<String> DEPRECATED_VALUES = new ArrayList<>();
+
     private ViewAttachTestActivity mActivity;
     private View mView;
     private HandlerThread mWorker;
@@ -126,6 +129,7 @@
                 CUJ_NOTIFICATION_SHADE_ROW_SWIPE, getEnumName("SHADE_ROW_SWIPE"));
         ENUM_NAME_EXCEPTION_MAP.put(
                 CUJ_NOTIFICATION_SHADE_SCROLL_FLING, getEnumName("SHADE_SCROLL_FLING"));
+        DEPRECATED_VALUES.add(ENUM_NAME_PREFIX + "IME_INSETS_ANIMATION");
     }
 
     private static String getEnumName(String name) {
@@ -239,6 +243,7 @@
 
         Map<Integer, String> enumsMap = Arrays.stream(FrameworkStatsLog.class.getDeclaredFields())
                 .filter(f -> f.getName().startsWith(ENUM_NAME_PREFIX)
+                        && !DEPRECATED_VALUES.contains(f.getName())
                         && Modifier.isStatic(f.getModifiers())
                         && f.getType() == int.class)
                 .collect(Collectors.toMap(this::getIntFieldChecked, Field::getName));
diff --git a/core/tests/coretests/src/com/android/internal/os/CpuScalingPolicyReaderTest.java b/core/tests/coretests/src/com/android/internal/os/CpuScalingPolicyReaderTest.java
new file mode 100644
index 0000000..7f054d1
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/os/CpuScalingPolicyReaderTest.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.os;
+
+import static androidx.test.InstrumentationRegistry.getContext;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.os.FileUtils;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.io.IOException;
+
+@RunWith(AndroidJUnit4.class)
+public class CpuScalingPolicyReaderTest {
+    private CpuScalingPolicyReader mCpuScalingPolicyReader;
+
+    @Before
+    public void setup() throws IOException {
+        File testDir = getContext().getDir("test", Context.MODE_PRIVATE);
+        FileUtils.deleteContents(testDir);
+
+        File policy0 = new File(testDir, "policy0");
+        FileUtils.createDir(policy0);
+        FileUtils.stringToFile(new File(policy0, "related_cpus"), "0 2 7");
+        FileUtils.stringToFile(new File(policy0, "scaling_available_frequencies"), "1234 9876");
+
+        File policy5 = new File(testDir, "policy5");
+        FileUtils.createDir(policy5);
+        FileUtils.stringToFile(new File(policy5, "related_cpus"), "3 6\n");
+        FileUtils.stringToFile(new File(policy5, "scaling_available_frequencies"), "1234 5678\n");
+        FileUtils.stringToFile(new File(policy5, "scaling_boost_frequencies"), "9998 9999\n");
+
+        File policy7 = new File(testDir, "policy7");
+        FileUtils.createDir(policy7);
+        FileUtils.stringToFile(new File(policy7, "related_cpus"), "8\n");
+        FileUtils.stringToFile(new File(policy7, "cpuinfo_cur_freq"), "1000000");
+
+        File policy9 = new File(testDir, "policy9");
+        FileUtils.createDir(policy9);
+        FileUtils.stringToFile(new File(policy9, "related_cpus"), "42");
+
+        File policy999 = new File(testDir, "policy999");
+        FileUtils.createDir(policy999);
+
+        mCpuScalingPolicyReader = new CpuScalingPolicyReader(testDir.getPath());
+    }
+
+    @Test
+    public void readFromSysFs() {
+        CpuScalingPolicies info = mCpuScalingPolicyReader.read();
+        assertThat(info.getPolicies()).isEqualTo(new int[]{0, 5, 7, 9});
+        assertThat(info.getRelatedCpus(0)).isEqualTo(new int[]{0, 2, 7});
+        assertThat(info.getFrequencies(0)).isEqualTo(new int[]{1234, 9876});
+        assertThat(info.getRelatedCpus(5)).isEqualTo(new int[]{3, 6});
+        assertThat(info.getFrequencies(5)).isEqualTo(new int[]{1234, 5678, 9998, 9999});
+        assertThat(info.getRelatedCpus(7)).isEqualTo(new int[]{8});
+        assertThat(info.getFrequencies(7)).isEqualTo(new int[]{1000000});
+        assertThat(info.getRelatedCpus(9)).isEqualTo(new int[]{42});
+        assertThat(info.getFrequencies(9)).isEqualTo(new int[]{0});     // Unknown
+        assertThat(info.getScalingStepCount()).isEqualTo(8);
+    }
+}
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelCpuUidFreqTimeReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelCpuUidFreqTimeReaderTest.java
index c60a6d6..783f264 100644
--- a/core/tests/coretests/src/com/android/internal/os/KernelCpuUidFreqTimeReaderTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/KernelCpuUidFreqTimeReaderTest.java
@@ -18,7 +18,6 @@
 
 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.mockito.Mockito.when;
@@ -100,63 +99,6 @@
     }
 
     @Test
-    public void testReadFreqs_perClusterTimesNotAvailable() throws Exception {
-        final long[][] freqs = {
-                {1, 12, 123, 1234},
-                {1, 12, 123, 23, 123, 1234, 12345, 123456},
-                {1, 12, 123, 23, 123, 1234, 12345, 123456, 12, 123, 12345},
-                {1, 12, 123, 23, 2345, 234567}
-        };
-        final int[] numClusters = {2, 2, 3, 1};
-        final int[][] numFreqs = {{3, 6}, {4, 5}, {3, 5, 4}, {3}};
-        for (int i = 0; i < freqs.length; ++i) {
-            mReader = new KernelCpuUidFreqTimeReader(mTestFile.getAbsolutePath(),
-                    new KernelCpuProcStringReader(mTestFile.getAbsolutePath()), mBpfMapReader, false);
-            setCpuClusterFreqs(numClusters[i], numFreqs[i]);
-            setFreqs(freqs[i]);
-            long[] actualFreqs = mReader.readFreqs(mPowerProfile);
-            assertArrayEquals(freqs[i], actualFreqs);
-            final String errMsg = String.format("Freqs=%s, nClusters=%d, nFreqs=%s",
-                    Arrays.toString(freqs[i]), numClusters[i], Arrays.toString(numFreqs[i]));
-            assertFalse(errMsg, mReader.perClusterTimesAvailable());
-
-            // Verify that a second call won't re-read the freqs
-            clearFreqsAndData();
-            actualFreqs = mReader.readFreqs(mPowerProfile);
-            assertArrayEquals(freqs[i], actualFreqs);
-            assertFalse(errMsg, mReader.perClusterTimesAvailable());
-        }
-    }
-
-    @Test
-    public void testReadFreqs_perClusterTimesAvailable() throws Exception {
-        final long[][] freqs = {
-                {1, 12, 123, 1234},
-                {1, 12, 123, 23, 123, 1234, 12345, 123456},
-                {1, 12, 123, 23, 123, 1234, 12345, 123456, 12, 123, 12345, 1234567}
-        };
-        final int[] numClusters = {1, 2, 3};
-        final int[][] numFreqs = {{4}, {3, 5}, {3, 5, 4}};
-        for (int i = 0; i < freqs.length; ++i) {
-            mReader = new KernelCpuUidFreqTimeReader(mTestFile.getAbsolutePath(),
-                    new KernelCpuProcStringReader(mTestFile.getAbsolutePath()), mBpfMapReader, false);
-            setCpuClusterFreqs(numClusters[i], numFreqs[i]);
-            setFreqs(freqs[i]);
-            long[] actualFreqs = mReader.readFreqs(mPowerProfile);
-            assertArrayEquals(freqs[i], actualFreqs);
-            final String errMsg = String.format("Freqs=%s, nClusters=%d, nFreqs=%s",
-                    Arrays.toString(freqs[i]), numClusters[i], Arrays.toString(numFreqs[i]));
-            assertTrue(errMsg, mReader.perClusterTimesAvailable());
-
-            // Verify that a second call won't re-read the freqs
-            clearFreqsAndData();
-            actualFreqs = mReader.readFreqs(mPowerProfile);
-            assertArrayEquals(freqs[i], actualFreqs);
-            assertTrue(errMsg, mReader.perClusterTimesAvailable());
-        }
-    }
-
-    @Test
     public void testReadDelta() throws Exception {
         final long[] freqs = {110, 123, 145, 167, 289, 997};
         final long[][] times = increaseTime(new long[mUids.length][freqs.length]);
@@ -170,8 +112,6 @@
 
         // Verify that readDelta also reads the frequencies if not already available.
         clearFreqsAndData();
-        long[] actualFreqs = mReader.readFreqs(mPowerProfile);
-        assertArrayEquals(freqs, actualFreqs);
 
         // Verify that a second call will only return deltas.
         mCallback.clear();
@@ -222,8 +162,6 @@
 
         // Verify that readDelta also reads the frequencies if not already available.
         clearFreqsAndData();
-        long[] actualFreqs = mReader.readFreqs(mPowerProfile);
-        assertArrayEquals(freqs, actualFreqs);
 
         // Verify that a second call should still return absolute values
         mCallback.clear();
diff --git a/core/tests/coretests/src/com/android/internal/os/LongArrayMultiStateCounterTest.java b/core/tests/coretests/src/com/android/internal/os/LongArrayMultiStateCounterTest.java
index 516dee7..faccf1a 100644
--- a/core/tests/coretests/src/com/android/internal/os/LongArrayMultiStateCounterTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/LongArrayMultiStateCounterTest.java
@@ -20,6 +20,7 @@
 
 import static org.junit.Assert.assertThrows;
 
+import android.os.BadParcelableException;
 import android.os.Parcel;
 
 import androidx.test.filters.SmallTest;
@@ -163,6 +164,45 @@
     }
 
     @Test
+    public void createFromBadBundle() {
+        Parcel data = Parcel.obtain();
+        int bundleLenPos = data.dataPosition();
+        data.writeInt(0);
+        data.writeInt(0x4C444E42);      // BaseBundle.BUNDLE_MAGIC
+
+        int bundleStart = data.dataPosition();
+
+        data.writeInt(1);
+        data.writeString("key");
+        data.writeInt(4);
+        int lazyValueLenPos = data.dataPosition();
+        data.writeInt(0);
+        int lazyValueStart = data.dataPosition();
+        data.writeString("com.android.internal.os.LongArrayMultiStateCounter");
+
+        // Invalid int16 value
+        data.writeInt(0x10000);     // stateCount
+        data.writeInt(10);          // arrayLength
+        for (int i = 0; i < 0x10000; ++i) {
+            data.writeLong(0);
+        }
+
+        backPatchLength(data, lazyValueLenPos, lazyValueStart);
+        backPatchLength(data, bundleLenPos, bundleStart);
+        data.setDataPosition(0);
+
+        assertThrows(BadParcelableException.class,
+                () -> data.readBundle().getParcelable("key", LongArrayMultiStateCounter.class));
+    }
+
+    private static void backPatchLength(Parcel parcel, int lengthPos, int startPos) {
+        int endPos = parcel.dataPosition();
+        parcel.setDataPosition(lengthPos);
+        parcel.writeInt(endPos - startPos);
+        parcel.setDataPosition(endPos);
+    }
+
+    @Test
     public void combineValues() {
         long[] values = new long[] {0, 1, 2, 3, 42};
         LongArrayMultiStateCounter.LongArrayContainer container =
diff --git a/core/tests/coretests/src/com/android/internal/os/LongMultiStateCounterTest.java b/core/tests/coretests/src/com/android/internal/os/LongMultiStateCounterTest.java
index fc86ebe..3413753 100644
--- a/core/tests/coretests/src/com/android/internal/os/LongMultiStateCounterTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/LongMultiStateCounterTest.java
@@ -20,6 +20,7 @@
 
 import static org.junit.Assert.assertThrows;
 
+import android.os.BadParcelableException;
 import android.os.Parcel;
 
 import androidx.test.filters.SmallTest;
@@ -210,4 +211,42 @@
         assertThrows(RuntimeException.class,
                 () -> LongMultiStateCounter.CREATOR.createFromParcel(parcel));
     }
+
+    @Test
+    public void createFromBadBundle() {
+        Parcel data = Parcel.obtain();
+        int bundleLenPos = data.dataPosition();
+        data.writeInt(0);
+        data.writeInt(0x4C444E42);      // BaseBundle.BUNDLE_MAGIC
+
+        int bundleStart = data.dataPosition();
+
+        data.writeInt(1);
+        data.writeString("key");
+        data.writeInt(4);
+        int lazyValueLenPos = data.dataPosition();
+        data.writeInt(0);
+        int lazyValueStart = data.dataPosition();
+        data.writeString("com.android.internal.os.LongMultiStateCounter");
+
+        // Invalid int16 value
+        data.writeInt(0x10000);     // stateCount
+        for (int i = 0; i < 0x10000; ++i) {
+            data.writeLong(0);
+        }
+
+        backPatchLength(data, lazyValueLenPos, lazyValueStart);
+        backPatchLength(data, bundleLenPos, bundleStart);
+        data.setDataPosition(0);
+
+        assertThrows(BadParcelableException.class,
+                () -> data.readBundle().getParcelable("key", LongMultiStateCounter.class));
+    }
+
+    private static void backPatchLength(Parcel parcel, int lengthPos, int startPos) {
+        int endPos = parcel.dataPosition();
+        parcel.setDataPosition(lengthPos);
+        parcel.writeInt(endPos - startPos);
+        parcel.setDataPosition(endPos);
+    }
 }
diff --git a/core/tests/coretests/src/com/android/internal/os/PowerProfileTest.java b/core/tests/coretests/src/com/android/internal/os/PowerProfileTest.java
index 6a3d379..8fa6376 100644
--- a/core/tests/coretests/src/com/android/internal/os/PowerProfileTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/PowerProfileTest.java
@@ -30,6 +30,7 @@
 import android.content.Context;
 import android.content.res.Resources;
 import android.content.res.XmlResourceParser;
+import android.util.SparseArray;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
@@ -71,18 +72,13 @@
     public void testPowerProfile() {
         mProfile.forceInitForTesting(mContext, R.xml.power_profile_test);
 
-        assertEquals(2, mProfile.getNumCpuClusters());
-        assertEquals(4, mProfile.getNumCoresInCpuCluster(0));
-        assertEquals(4, mProfile.getNumCoresInCpuCluster(1));
         assertEquals(5.0, mProfile.getAveragePower(PowerProfile.POWER_CPU_SUSPEND));
         assertEquals(1.11, mProfile.getAveragePower(PowerProfile.POWER_CPU_IDLE));
         assertEquals(2.55, mProfile.getAveragePower(PowerProfile.POWER_CPU_ACTIVE));
-        assertEquals(2.11, mProfile.getAveragePowerForCpuCluster(0));
-        assertEquals(2.22, mProfile.getAveragePowerForCpuCluster(1));
-        assertEquals(3, mProfile.getNumSpeedStepsInCpuCluster(0));
-        assertEquals(30.0, mProfile.getAveragePowerForCpuCore(0, 2));
-        assertEquals(4, mProfile.getNumSpeedStepsInCpuCluster(1));
-        assertEquals(60.0, mProfile.getAveragePowerForCpuCore(1, 3));
+        assertEquals(2.11, mProfile.getAveragePowerForCpuScalingPolicy(0));
+        assertEquals(2.22, mProfile.getAveragePowerForCpuScalingPolicy(3));
+        assertEquals(30.0, mProfile.getAveragePowerForCpuScalingStep(0, 2));
+        assertEquals(60.0, mProfile.getAveragePowerForCpuScalingStep(3, 3));
         assertEquals(3000.0, mProfile.getBatteryCapacity());
         assertEquals(0.5,
                 mProfile.getAveragePowerForOrdinal(POWER_GROUP_DISPLAY_AMBIENT, 0));
@@ -130,6 +126,23 @@
                         | ModemPowerProfile.MODEM_DRAIN_TYPE_TX
                         | ModemPowerProfile.MODEM_TX_LEVEL_4));
     }
+    @Test
+    public void testPowerProfile_legacyCpuConfig() {
+        // This power profile has per-cluster data, rather than per-policy
+        mProfile.forceInitForTesting(mContext, R.xml.power_profile_test_cpu_legacy);
+
+        assertEquals(2.11, mProfile.getAveragePowerForCpuScalingPolicy(0));
+        assertEquals(2.22, mProfile.getAveragePowerForCpuScalingPolicy(4));
+        assertEquals(30.0, mProfile.getAveragePowerForCpuScalingStep(0, 2));
+        assertEquals(60.0, mProfile.getAveragePowerForCpuScalingStep(4, 3));
+        assertEquals(3000.0, mProfile.getBatteryCapacity());
+        assertEquals(0.5,
+                mProfile.getAveragePowerForOrdinal(POWER_GROUP_DISPLAY_AMBIENT, 0));
+        assertEquals(100.0,
+                mProfile.getAveragePowerForOrdinal(POWER_GROUP_DISPLAY_SCREEN_ON, 0));
+        assertEquals(800.0,
+                mProfile.getAveragePowerForOrdinal(POWER_GROUP_DISPLAY_SCREEN_FULL, 0));
+    }
 
     @Test
     public void testModemPowerProfile_defaultRat() throws Exception {
@@ -524,15 +537,10 @@
         return null;
     }
 
-    private void assertEquals(int expected, int actual) {
-        Assert.assertEquals(expected, actual);
-    }
-
     private void assertEquals(double expected, double actual) {
         Assert.assertEquals(expected, actual, 0.1);
     }
 
-
     @Test
     public void powerBrackets_specifiedInPowerProfile() {
         mProfile.forceInitForTesting(mContext, R.xml.power_profile_test_power_brackets);
@@ -541,31 +549,40 @@
         int cpuPowerBracketCount = mProfile.getCpuPowerBracketCount();
         assertThat(cpuPowerBracketCount).isEqualTo(2);
         assertThat(new int[]{
-                mProfile.getPowerBracketForCpuCore(0, 0),
-                mProfile.getPowerBracketForCpuCore(1, 0),
-                mProfile.getPowerBracketForCpuCore(1, 1),
+                mProfile.getCpuPowerBracketForScalingStep(0, 0),
+                mProfile.getCpuPowerBracketForScalingStep(4, 0),
+                mProfile.getCpuPowerBracketForScalingStep(4, 1),
         }).isEqualTo(new int[]{1, 1, 0});
     }
 
     @Test
     public void powerBrackets_automatic() {
         mProfile.forceInitForTesting(mContext, R.xml.power_profile_test);
+        CpuScalingPolicies scalingPolicies = new CpuScalingPolicies(
+                new SparseArray<>() {{
+                    put(0, new int[]{0, 1, 2});
+                    put(3, new int[]{3, 4});
+                }},
+                new SparseArray<>() {{
+                    put(0, new int[]{300000, 1000000, 2000000});
+                    put(3, new int[]{300000, 1000000, 2500000, 3000000});
+                }});
 
         assertThat(mProfile.getCpuPowerBracketCount()).isEqualTo(3);
-        assertThat(mProfile.getCpuPowerBracketDescription(0))
+        assertThat(mProfile.getCpuPowerBracketDescription(scalingPolicies, 0))
                 .isEqualTo("0/300(10.0)");
-        assertThat(mProfile.getCpuPowerBracketDescription(1))
-                .isEqualTo("0/1000(20.0), 0/2000(30.0), 1/300(25.0)");
-        assertThat(mProfile.getCpuPowerBracketDescription(2))
-                .isEqualTo("1/1000(35.0), 1/2500(50.0), 1/3000(60.0)");
+        assertThat(mProfile.getCpuPowerBracketDescription(scalingPolicies, 1))
+                .isEqualTo("0/1000(20.0), 0/2000(30.0), 3/300(25.0)");
+        assertThat(mProfile.getCpuPowerBracketDescription(scalingPolicies, 2))
+                .isEqualTo("3/1000(35.0), 3/2500(50.0), 3/3000(60.0)");
         assertThat(new int[]{
-                mProfile.getPowerBracketForCpuCore(0, 0),
-                mProfile.getPowerBracketForCpuCore(0, 1),
-                mProfile.getPowerBracketForCpuCore(0, 2),
-                mProfile.getPowerBracketForCpuCore(1, 0),
-                mProfile.getPowerBracketForCpuCore(1, 1),
-                mProfile.getPowerBracketForCpuCore(1, 2),
-                mProfile.getPowerBracketForCpuCore(1, 3),
+                mProfile.getCpuPowerBracketForScalingStep(0, 0),
+                mProfile.getCpuPowerBracketForScalingStep(0, 1),
+                mProfile.getCpuPowerBracketForScalingStep(0, 2),
+                mProfile.getCpuPowerBracketForScalingStep(3, 0),
+                mProfile.getCpuPowerBracketForScalingStep(3, 1),
+                mProfile.getCpuPowerBracketForScalingStep(3, 2),
+                mProfile.getCpuPowerBracketForScalingStep(3, 3),
         }).isEqualTo(new int[]{0, 1, 1, 1, 2, 2, 2});
     }
 
@@ -576,13 +593,13 @@
 
         assertThat(mProfile.getCpuPowerBracketCount()).isEqualTo(7);
         assertThat(new int[]{
-                mProfile.getPowerBracketForCpuCore(0, 0),
-                mProfile.getPowerBracketForCpuCore(0, 1),
-                mProfile.getPowerBracketForCpuCore(0, 2),
-                mProfile.getPowerBracketForCpuCore(1, 0),
-                mProfile.getPowerBracketForCpuCore(1, 1),
-                mProfile.getPowerBracketForCpuCore(1, 2),
-                mProfile.getPowerBracketForCpuCore(1, 3),
+                mProfile.getCpuPowerBracketForScalingStep(0, 0),
+                mProfile.getCpuPowerBracketForScalingStep(0, 1),
+                mProfile.getCpuPowerBracketForScalingStep(0, 2),
+                mProfile.getCpuPowerBracketForScalingStep(3, 0),
+                mProfile.getCpuPowerBracketForScalingStep(3, 1),
+                mProfile.getCpuPowerBracketForScalingStep(3, 2),
+                mProfile.getCpuPowerBracketForScalingStep(3, 3),
         }).isEqualTo(new int[]{0, 1, 2, 3, 4, 5, 6});
     }
 }
diff --git a/core/tests/coretests/src/com/android/internal/util/FakeLatencyTrackerTest.java b/core/tests/coretests/src/com/android/internal/util/FakeLatencyTrackerTest.java
index e6f10ad..3946cdf 100644
--- a/core/tests/coretests/src/com/android/internal/util/FakeLatencyTrackerTest.java
+++ b/core/tests/coretests/src/com/android/internal/util/FakeLatencyTrackerTest.java
@@ -16,14 +16,19 @@
 
 package com.android.internal.util;
 
+import static android.provider.DeviceConfig.NAMESPACE_LATENCY_TRACKER;
+
 import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_SHOW_VOICE_INTERACTION;
 import static com.android.internal.util.FrameworkStatsLog.UI_ACTION_LATENCY_REPORTED;
 import static com.android.internal.util.LatencyTracker.ACTION_SHOW_VOICE_INTERACTION;
 
 import static com.google.common.truth.Truth.assertThat;
 
+import android.provider.DeviceConfig;
+
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -40,12 +45,22 @@
 public class FakeLatencyTrackerTest {
 
     private FakeLatencyTracker mFakeLatencyTracker;
+    private int mInitialSyncDisabledMode;
 
     @Before
     public void setUp() throws Exception {
+        mInitialSyncDisabledMode = DeviceConfig.getSyncDisabledMode();
+        DeviceConfig.setSyncDisabledMode(DeviceConfig.SYNC_DISABLED_MODE_NONE);
         mFakeLatencyTracker = FakeLatencyTracker.create();
     }
 
+    @After
+    public void tearDown() throws Exception {
+        DeviceConfig.setProperties(
+                new DeviceConfig.Properties.Builder(NAMESPACE_LATENCY_TRACKER).build());
+        DeviceConfig.setSyncDisabledMode(mInitialSyncDisabledMode);
+    }
+
     @Test
     public void testForceEnabled() throws Exception {
         mFakeLatencyTracker.logAction(ACTION_SHOW_VOICE_INTERACTION, 1234);
diff --git a/core/tests/coretests/src/com/android/internal/util/LatencyTrackerTest.java b/core/tests/coretests/src/com/android/internal/util/LatencyTrackerTest.java
index 584ad20..f24894e 100644
--- a/core/tests/coretests/src/com/android/internal/util/LatencyTrackerTest.java
+++ b/core/tests/coretests/src/com/android/internal/util/LatencyTrackerTest.java
@@ -58,15 +58,21 @@
     // Fake is used because it tests the real logic of LatencyTracker, and it only fakes the
     // outcomes (PerfettoTrigger and FrameworkStatsLog).
     private FakeLatencyTracker mLatencyTracker;
+    private int mInitialSyncDisabledMode;
 
     @Before
     public void setUp() throws Exception {
+        mInitialSyncDisabledMode = DeviceConfig.getSyncDisabledMode();
+        DeviceConfig.setSyncDisabledMode(DeviceConfig.SYNC_DISABLED_MODE_NONE);
         mLatencyTracker = FakeLatencyTracker.create();
     }
 
     @After
-    public void tearDown() {
+    public void tearDown() throws Exception {
         mLatencyTracker.stopListeningForLatencyTrackerConfigChanges();
+        DeviceConfig.setProperties(
+                new DeviceConfig.Properties.Builder(NAMESPACE_LATENCY_TRACKER).build());
+        DeviceConfig.setSyncDisabledMode(mInitialSyncDisabledMode);
     }
 
     @Test
diff --git a/core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java b/core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java
index a1a4265..84dd274 100644
--- a/core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java
+++ b/core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java
@@ -169,7 +169,7 @@
 
     private WindowInsets insetsWith(Insets content, DisplayCutout cutout) {
         return new WindowInsets(WindowInsets.createCompatTypeMap(content.toRect()), null, null,
-                false, false, 0, cutout, null, null, null, WindowInsets.Type.systemBars(), false);
+                false, 0, 0, cutout, null, null, null, WindowInsets.Type.systemBars(), false);
     }
 
     private ViewGroup createViewGroupWithId(int id) {
diff --git a/core/tests/coretests/src/com/android/internal/widget/LockPatternUtilsTest.java b/core/tests/coretests/src/com/android/internal/widget/LockPatternUtilsTest.java
index 6167c4b..1a668f7 100644
--- a/core/tests/coretests/src/com/android/internal/widget/LockPatternUtilsTest.java
+++ b/core/tests/coretests/src/com/android/internal/widget/LockPatternUtilsTest.java
@@ -37,10 +37,23 @@
     }
 
     @Test
+    public void testUserRepairMode_isNotRegularUser() {
+        assertTrue(LockPatternUtils.USER_REPAIR_MODE < 0);
+    }
+
+    @Test
     public void testUserFrp_isNotAReservedSpecialUser() throws Exception {
         assertNotEquals(UserHandle.USER_NULL, LockPatternUtils.USER_FRP);
         assertNotEquals(UserHandle.USER_ALL, LockPatternUtils.USER_FRP);
         assertNotEquals(UserHandle.USER_CURRENT, LockPatternUtils.USER_FRP);
         assertNotEquals(UserHandle.USER_CURRENT_OR_SELF, LockPatternUtils.USER_FRP);
     }
+
+    @Test
+    public void testUserRepairMode_isNotAReservedSpecialUser() throws Exception {
+        assertNotEquals(UserHandle.USER_NULL, LockPatternUtils.USER_REPAIR_MODE);
+        assertNotEquals(UserHandle.USER_ALL, LockPatternUtils.USER_REPAIR_MODE);
+        assertNotEquals(UserHandle.USER_CURRENT, LockPatternUtils.USER_REPAIR_MODE);
+        assertNotEquals(UserHandle.USER_CURRENT_OR_SELF, LockPatternUtils.USER_REPAIR_MODE);
+    }
 }
diff --git a/core/tests/coretests/src/com/android/internal/widget/LockscreenCredentialTest.java b/core/tests/coretests/src/com/android/internal/widget/LockscreenCredentialTest.java
index a47868d..5692742 100644
--- a/core/tests/coretests/src/com/android/internal/widget/LockscreenCredentialTest.java
+++ b/core/tests/coretests/src/com/android/internal/widget/LockscreenCredentialTest.java
@@ -16,52 +16,71 @@
 
 package com.android.internal.widget;
 
-
 import static com.google.common.truth.Truth.assertThat;
 
-import android.test.AndroidTestCase;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
 
 import java.util.Arrays;
 
+@RunWith(AndroidJUnit4.class)
+public class LockscreenCredentialTest {
 
-public class LockscreenCredentialTest extends AndroidTestCase {
+    @Test
+    public void testNoneCredential() {
+        LockscreenCredential none = LockscreenCredential.createNone();
 
-    public void testEmptyCredential() {
-        LockscreenCredential empty = LockscreenCredential.createNone();
+        assertTrue(none.isNone());
+        assertEquals(0, none.size());
+        assertArrayEquals(new byte[0], none.getCredential());
 
-        assertTrue(empty.isNone());
-        assertEquals(0, empty.size());
-        assertNotNull(empty.getCredential());
-
-        assertFalse(empty.isPin());
-        assertFalse(empty.isPassword());
-        assertFalse(empty.isPattern());
+        assertFalse(none.isPin());
+        assertFalse(none.isPassword());
+        assertFalse(none.isPattern());
+        assertFalse(none.hasInvalidChars());
+        none.validateBasicRequirements();
     }
 
+    @Test
     public void testPinCredential() {
         LockscreenCredential pin = LockscreenCredential.createPin("3456");
 
         assertTrue(pin.isPin());
         assertEquals(4, pin.size());
-        assertTrue(Arrays.equals("3456".getBytes(), pin.getCredential()));
+        assertArrayEquals("3456".getBytes(), pin.getCredential());
 
         assertFalse(pin.isNone());
         assertFalse(pin.isPassword());
         assertFalse(pin.isPattern());
+        assertFalse(pin.hasInvalidChars());
+        pin.validateBasicRequirements();
     }
 
+    @Test
     public void testPasswordCredential() {
         LockscreenCredential password = LockscreenCredential.createPassword("password");
 
         assertTrue(password.isPassword());
         assertEquals(8, password.size());
-        assertTrue(Arrays.equals("password".getBytes(), password.getCredential()));
+        assertArrayEquals("password".getBytes(), password.getCredential());
 
         assertFalse(password.isNone());
         assertFalse(password.isPin());
         assertFalse(password.isPattern());
+        assertFalse(password.hasInvalidChars());
+        password.validateBasicRequirements();
     }
 
+    @Test
     public void testPatternCredential() {
         LockscreenCredential pattern = LockscreenCredential.createPattern(Arrays.asList(
                 LockPatternView.Cell.of(0, 0),
@@ -73,13 +92,34 @@
 
         assertTrue(pattern.isPattern());
         assertEquals(5, pattern.size());
-        assertTrue(Arrays.equals("12369".getBytes(), pattern.getCredential()));
+        assertArrayEquals("12369".getBytes(), pattern.getCredential());
 
         assertFalse(pattern.isNone());
         assertFalse(pattern.isPin());
         assertFalse(pattern.isPassword());
+        assertFalse(pattern.hasInvalidChars());
+        pattern.validateBasicRequirements();
     }
 
+    // Constructing a LockscreenCredential with a too-short length, even 0, should not throw an
+    // exception.  This is because LockscreenCredential needs to be able to represent a request to
+    // set a credential that is too short.
+    @Test
+    public void testZeroLengthCredential() {
+        LockscreenCredential credential = LockscreenCredential.createPin("");
+        assertTrue(credential.isPin());
+        assertEquals(0, credential.size());
+
+        credential = createPattern("");
+        assertTrue(credential.isPattern());
+        assertEquals(0, credential.size());
+
+        credential = LockscreenCredential.createPassword("");
+        assertTrue(credential.isPassword());
+        assertEquals(0, credential.size());
+    }
+
+    @Test
     public void testPasswordOrNoneCredential() {
         assertEquals(LockscreenCredential.createNone(),
                 LockscreenCredential.createPasswordOrNone(null));
@@ -89,6 +129,7 @@
                 LockscreenCredential.createPasswordOrNone("abcd"));
     }
 
+    @Test
     public void testPinOrNoneCredential() {
         assertEquals(LockscreenCredential.createNone(),
                 LockscreenCredential.createPinOrNone(null));
@@ -98,6 +139,35 @@
                 LockscreenCredential.createPinOrNone("1357"));
     }
 
+    // Test that passwords containing invalid characters that were incorrectly allowed in
+    // Android 10–14 are still interpreted in the same way, but are not allowed for new passwords.
+    @Test
+    public void testPasswordWithInvalidChars() {
+        // ™ is U+2122, which was truncated to ASCII 0x22 which is double quote.
+        String[] passwords = new String[] { "foo™", "™™™™", "™foo" };
+        String[] equivalentAsciiPasswords = new String[] { "foo\"", "\"\"\"\"", "\"foo" };
+        for (int i = 0; i < passwords.length; i++) {
+            LockscreenCredential credential = LockscreenCredential.createPassword(passwords[i]);
+            assertTrue(credential.hasInvalidChars());
+            assertArrayEquals(equivalentAsciiPasswords[i].getBytes(), credential.getCredential());
+            try {
+                credential.validateBasicRequirements();
+                fail("should not be able to set password with invalid chars");
+            } catch (IllegalArgumentException expected) { }
+        }
+    }
+
+    @Test
+    public void testPinWithInvalidChars() {
+        LockscreenCredential pin = LockscreenCredential.createPin("\n\n\n\n");
+        assertTrue(pin.hasInvalidChars());
+        try {
+            pin.validateBasicRequirements();
+            fail("should not be able to set PIN with invalid chars");
+        } catch (IllegalArgumentException expected) { }
+    }
+
+    @Test
     public void testSanitize() {
         LockscreenCredential password = LockscreenCredential.createPassword("password");
         password.zeroize();
@@ -123,11 +193,16 @@
             fail("Sanitized credential still accessible");
         } catch (IllegalStateException expected) { }
         try {
+            password.hasInvalidChars();
+            fail("Sanitized credential still accessible");
+        } catch (IllegalStateException expected) { }
+        try {
             password.getCredential();
             fail("Sanitized credential still accessible");
         } catch (IllegalStateException expected) { }
     }
 
+    @Test
     public void testEquals() {
         assertEquals(LockscreenCredential.createNone(), LockscreenCredential.createNone());
         assertEquals(LockscreenCredential.createPassword("1234"),
@@ -136,34 +211,40 @@
                 LockscreenCredential.createPin("4321"));
         assertEquals(createPattern("1234"), createPattern("1234"));
 
-        assertNotSame(LockscreenCredential.createPassword("1234"),
+        assertNotEquals(LockscreenCredential.createPassword("1234"),
                 LockscreenCredential.createNone());
-        assertNotSame(LockscreenCredential.createPassword("1234"),
+        assertNotEquals(LockscreenCredential.createPassword("1234"),
                 LockscreenCredential.createPassword("4321"));
-        assertNotSame(LockscreenCredential.createPassword("1234"),
+        assertNotEquals(LockscreenCredential.createPassword("1234"),
                 createPattern("1234"));
-        assertNotSame(LockscreenCredential.createPassword("1234"),
+        assertNotEquals(LockscreenCredential.createPassword("1234"),
                 LockscreenCredential.createPin("1234"));
 
-        assertNotSame(LockscreenCredential.createPin("1111"),
+        assertNotEquals(LockscreenCredential.createPin("1111"),
                 LockscreenCredential.createNone());
-        assertNotSame(LockscreenCredential.createPin("1111"),
+        assertNotEquals(LockscreenCredential.createPin("1111"),
                 LockscreenCredential.createPin("2222"));
-        assertNotSame(LockscreenCredential.createPin("1111"),
+        assertNotEquals(LockscreenCredential.createPin("1111"),
                 createPattern("1111"));
-        assertNotSame(LockscreenCredential.createPin("1111"),
+        assertNotEquals(LockscreenCredential.createPin("1111"),
                 LockscreenCredential.createPassword("1111"));
 
-        assertNotSame(createPattern("5678"),
+        assertNotEquals(createPattern("5678"),
                 LockscreenCredential.createNone());
-        assertNotSame(createPattern("5678"),
+        assertNotEquals(createPattern("5678"),
                 createPattern("1234"));
-        assertNotSame(createPattern("5678"),
+        assertNotEquals(createPattern("5678"),
                 LockscreenCredential.createPassword("5678"));
-        assertNotSame(createPattern("5678"),
+        assertNotEquals(createPattern("5678"),
                 LockscreenCredential.createPin("5678"));
+
+        // Test that mHasInvalidChars is compared.  To do this, compare two passwords that map to
+        // the same byte[] (due to the truncation bug) but different values of mHasInvalidChars.
+        assertNotEquals(LockscreenCredential.createPassword("™™™™"),
+                LockscreenCredential.createPassword("\"\"\"\""));
     }
 
+    @Test
     public void testDuplicate() {
         LockscreenCredential credential;
 
@@ -175,8 +256,13 @@
         assertEquals(credential, credential.duplicate());
         credential = createPattern("5678");
         assertEquals(credential, credential.duplicate());
+
+        // Test that mHasInvalidChars is duplicated.
+        credential = LockscreenCredential.createPassword("™™™™");
+        assertEquals(credential, credential.duplicate());
     }
 
+    @Test
     public void testPasswordToHistoryHash() {
         String password = "1234";
         LockscreenCredential credential = LockscreenCredential.createPassword(password);
@@ -193,6 +279,7 @@
                 .isEqualTo(expectedHash);
     }
 
+    @Test
     public void testPasswordToHistoryHashInvalidInput() {
         String password = "1234";
         LockscreenCredential credential = LockscreenCredential.createPassword(password);
@@ -221,6 +308,7 @@
                 .isNull();
     }
 
+    @Test
     public void testLegacyPasswordToHash() {
         String password = "1234";
         String salt = "6d5331dd120077a0";
@@ -233,6 +321,7 @@
                 .isEqualTo(expectedHash);
     }
 
+    @Test
     public void testLegacyPasswordToHashInvalidInput() {
         String password = "1234";
         String salt = "6d5331dd120077a0";
diff --git a/core/tests/ddm/Android.bp b/core/tests/ddm/Android.bp
new file mode 100644
index 0000000..818ea8b
--- /dev/null
+++ b/core/tests/ddm/Android.bp
@@ -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 {
+    // 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"],
+}
+
+java_test_host {
+    name: "frameworks-base-ddm-unittests",
+    srcs: [
+        "java/android/os/DdmSyncStateTest.java",
+        ":framework-android-os-unit-testable-src",
+    ],
+    static_libs: [
+        "junit",
+    ],
+    test_options: {
+        unit_test: true,
+    },
+    test_suites: [
+        "cts",
+    ],
+}
diff --git a/core/tests/ddm/java/android/os/DdmSyncStateTest.java b/core/tests/ddm/java/android/os/DdmSyncStateTest.java
new file mode 100644
index 0000000..8274ce4
--- /dev/null
+++ b/core/tests/ddm/java/android/os/DdmSyncStateTest.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
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.test;
+
+import android.os.DdmSyncState;
+import android.os.DdmSyncState.Stage;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * Test DdmSyncState, the Android app stage boot sync system for DDM Client.
+ */
+
+public class DdmSyncStateTest {
+
+    @Test
+    public void testNoCycle() {
+        DdmSyncState.reset();
+        try {
+            DdmSyncState.next(Stage.Attach);
+            DdmSyncState.next(Stage.Bind);
+            DdmSyncState.next(Stage.Named);
+            DdmSyncState.next(Stage.Debugger);
+            DdmSyncState.next(Stage.Running);
+
+            // Cycling back here which is not allowed
+            DdmSyncState.next(Stage.Attach);
+            Assert.fail("Going back to attach should have failed");
+        } catch (IllegalStateException ignored) {
+
+        }
+    }
+
+    @Test
+    public void testDebuggerFlow() {
+        DdmSyncState.reset();
+        DdmSyncState.next(Stage.Attach);
+        DdmSyncState.next(Stage.Bind);
+        DdmSyncState.next(Stage.Named);
+        DdmSyncState.next(Stage.Debugger);
+        DdmSyncState.next(Stage.Running);
+        Assert.assertEquals(Stage.Running, DdmSyncState.getStage());
+
+    }
+
+    @Test
+    public void testNoDebugFlow() {
+        DdmSyncState.reset();
+        DdmSyncState.next(Stage.Attach);
+        DdmSyncState.next(Stage.Bind);
+        DdmSyncState.next(Stage.Named);
+        // Notice how Stage.Debugger stage is skipped
+        DdmSyncState.next(Stage.Running);
+        Assert.assertEquals(Stage.Running, DdmSyncState.getStage());
+    }
+}
diff --git a/core/tests/fuzzers/ParcelFuzzer/Android.bp b/core/tests/fuzzers/ParcelFuzzer/Android.bp
index b71a06e..eff1985 100644
--- a/core/tests/fuzzers/ParcelFuzzer/Android.bp
+++ b/core/tests/fuzzers/ParcelFuzzer/Android.bp
@@ -34,6 +34,7 @@
             "smoreland@google.com",
             "waghpawan@google.com",
         ],
+        triage_assignee: "cobark@google.com", // TODO(b/280770893)
         // Adds bugs to hotlist "AIDL fuzzers bugs" on buganizer
         hotlists: ["4637097"],
     },
diff --git a/core/tests/fuzzers/java_service_fuzzer/Android.bp b/core/tests/fuzzers/java_service_fuzzer/Android.bp
index 6acb198..97538a5 100644
--- a/core/tests/fuzzers/java_service_fuzzer/Android.bp
+++ b/core/tests/fuzzers/java_service_fuzzer/Android.bp
@@ -42,6 +42,7 @@
             "smoreland@google.com",
             "waghpawan@google.com",
         ],
+        triage_assignee: "cobark@google.com", // TODO(b/261539788)
         // Adds bugs to hotlist "AIDL fuzzers bugs" on buganizer
         hotlists: ["4637097"],
     },
diff --git a/core/tests/mockingcoretests/Android.bp b/core/tests/mockingcoretests/Android.bp
index 96811be..fde7c08 100644
--- a/core/tests/mockingcoretests/Android.bp
+++ b/core/tests/mockingcoretests/Android.bp
@@ -40,7 +40,6 @@
         "platform-test-annotations",
         "truth-prebuilt",
         "testables",
-        "ub-uiautomator",
     ],
 
     libs: [
@@ -56,7 +55,10 @@
     ],
 
     platform_apis: true,
-    test_suites: ["device-tests"],
+    test_suites: [
+        "device-tests",
+        "automotive-tests",
+    ],
 
     certificate: "platform",
 }
diff --git a/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java b/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java
index be2c27d..3e0e36d 100644
--- a/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java
+++ b/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java
@@ -181,7 +181,7 @@
 
             // Verify for ON_START state. Activity should be relaunched.
             getInstrumentation().runOnMainSync(() -> clientSession.startActivity(r));
-            recreateAndVerifyRelaunched(activityThread, activity[0], r, ON_START);
+            recreateAndVerifyRelaunched(activityThread, activity[0], r, ON_PAUSE);
 
             // Verify for ON_RESUME state. Activity should be relaunched.
             getInstrumentation().runOnMainSync(() -> clientSession.resumeActivity(r));
diff --git a/core/tests/utiltests/src/android/util/AtomicFileTest.java b/core/tests/utiltests/src/android/util/AtomicFileTest.java
index 8c13579..6f59714 100644
--- a/core/tests/utiltests/src/android/util/AtomicFileTest.java
+++ b/core/tests/utiltests/src/android/util/AtomicFileTest.java
@@ -20,9 +20,12 @@
 
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.longThat;
+import static org.mockito.Mockito.spy;
 
 import android.app.Instrumentation;
 import android.content.Context;
+import android.os.SystemClock;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -33,6 +36,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
+import org.mockito.Mockito;
 
 import java.io.ByteArrayOutputStream;
 import java.io.File;
@@ -269,6 +273,29 @@
         assertThat(toString).contains(mBaseFile.getAbsolutePath());
     }
 
+    @Test
+    public void testTimeLogging() throws Exception {
+        var logger = spy(new SystemConfigFileCommitEventLogger("name"));
+        var file = new AtomicFile(mBaseFile, logger);
+        var startTime1 = SystemClock.uptimeMillis();
+        try (var writer = file.startWrite()) {
+            file.finishWrite(writer);
+        }
+        var endTime1 = SystemClock.uptimeMillis();
+        SystemClock.sleep(10);
+        var startTime2 = SystemClock.uptimeMillis();
+        try (var writer = file.startWrite()) {
+            file.finishWrite(writer);
+        }
+        var endTime2 = SystemClock.uptimeMillis();
+
+        var inOrder = Mockito.inOrder(logger);
+        inOrder.verify(logger).writeLogRecord(longThat(
+                l -> l <= endTime1 - startTime1));
+        inOrder.verify(logger).writeLogRecord(longThat(
+                l -> l <= endTime2 - startTime2 && l < endTime2 - startTime1));
+    }
+
     private static void writeBytes(@NonNull File file, @NonNull byte[] bytes) throws IOException {
         try (FileOutputStream outputStream = new FileOutputStream(file)) {
             outputStream.write(bytes);
diff --git a/core/tests/utiltests/src/android/util/SystemConfigFileCommitEventLoggerTest.java b/core/tests/utiltests/src/android/util/SystemConfigFileCommitEventLoggerTest.java
new file mode 100644
index 0000000..5f6c201
--- /dev/null
+++ b/core/tests/utiltests/src/android/util/SystemConfigFileCommitEventLoggerTest.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.util;
+
+import static org.mockito.ArgumentMatchers.longThat;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+
+import android.os.SystemClock;
+
+import org.junit.Test;
+import org.mockito.Mockito;
+
+
+public class SystemConfigFileCommitEventLoggerTest {
+    @Test
+    public void testSimple() throws Exception {
+        var logger = spy(new SystemConfigFileCommitEventLogger("name"));
+        var startTime = SystemClock.uptimeMillis();
+        logger.onStartWrite();
+        logger.onFinishWrite();
+        var endTime = SystemClock.uptimeMillis();
+        Mockito.verify(logger, times(1)).writeLogRecord(
+                longThat(l -> l <= endTime - startTime));
+    }
+
+    @Test
+    public void testMultipleWrong() throws Exception {
+        var logger = spy(new SystemConfigFileCommitEventLogger("name"));
+        var startTime = SystemClock.uptimeMillis();
+        logger.onStartWrite();
+        logger.onFinishWrite();
+        var endTime1 = SystemClock.uptimeMillis();
+        SystemClock.sleep(10);
+        logger.onStartWrite();
+        logger.onFinishWrite();
+        var endTime2 = SystemClock.uptimeMillis();
+        var inOrder = Mockito.inOrder(logger);
+        inOrder.verify(logger).writeLogRecord(longThat(
+                l -> l <= endTime1 - startTime));
+        inOrder.verify(logger).writeLogRecord(longThat(
+                l -> l > endTime1 - startTime && l <= endTime2 - startTime));
+    }
+
+    @Test
+    public void testMultipleRight() throws Exception {
+        var logger = spy(new SystemConfigFileCommitEventLogger("name"));
+        var startTime = SystemClock.uptimeMillis();
+        logger.onStartWrite();
+        logger.onFinishWrite();
+        var endTime1 = SystemClock.uptimeMillis();
+        SystemClock.sleep(10);
+        logger.setStartTime(0);
+        logger.onStartWrite();
+        logger.onFinishWrite();
+        var endTime2 = SystemClock.uptimeMillis();
+        var inOrder = Mockito.inOrder(logger);
+        inOrder.verify(logger).writeLogRecord(longThat(
+                l -> l <= endTime1 - startTime));
+        inOrder.verify(logger).writeLogRecord(longThat(
+                l -> l <= endTime2 - endTime1));
+    }
+}
diff --git a/core/xsd/Android.bp b/core/xsd/Android.bp
index 5387f85..f49a159 100644
--- a/core/xsd/Android.bp
+++ b/core/xsd/Android.bp
@@ -13,3 +13,10 @@
     api_dir: "schema",
     package_name: "com.android.xml.permission.configfile",
 }
+
+xsd_config {
+    name: "xsd-vibrator-persistence",
+    srcs: ["vibrator/vibration/vibration.xsd"],
+    api_dir: "vibrator/vibration/schema",
+    package_name: "com.android.internal.vibrator.persistence",
+}
diff --git a/core/xsd/vibrator/OWNERS b/core/xsd/vibrator/OWNERS
new file mode 100644
index 0000000..d073e2b
--- /dev/null
+++ b/core/xsd/vibrator/OWNERS
@@ -0,0 +1 @@
+include platform/frameworks/base:/services/core/java/com/android/server/vibrator/OWNERS
diff --git a/core/xsd/vibrator/vibration/schema/current.txt b/core/xsd/vibrator/vibration/schema/current.txt
new file mode 100644
index 0000000..121a228
--- /dev/null
+++ b/core/xsd/vibrator/vibration/schema/current.txt
@@ -0,0 +1,83 @@
+// Signature format: 2.0
+package com.android.internal.vibrator.persistence {
+
+  public class PredefinedEffect {
+    ctor public PredefinedEffect();
+    method public com.android.internal.vibrator.persistence.PredefinedEffectName getName();
+    method public void setName(com.android.internal.vibrator.persistence.PredefinedEffectName);
+  }
+
+  public enum PredefinedEffectName {
+    method public String getRawName();
+    enum_constant public static final com.android.internal.vibrator.persistence.PredefinedEffectName click;
+    enum_constant public static final com.android.internal.vibrator.persistence.PredefinedEffectName double_click;
+    enum_constant public static final com.android.internal.vibrator.persistence.PredefinedEffectName heavy_click;
+    enum_constant public static final com.android.internal.vibrator.persistence.PredefinedEffectName tick;
+  }
+
+  public class PrimitiveEffect {
+    ctor public PrimitiveEffect();
+    method public java.math.BigInteger getDelayMs();
+    method public com.android.internal.vibrator.persistence.PrimitiveEffectName getName();
+    method public float getScale();
+    method public void setDelayMs(java.math.BigInteger);
+    method public void setName(com.android.internal.vibrator.persistence.PrimitiveEffectName);
+    method public void setScale(float);
+  }
+
+  public enum PrimitiveEffectName {
+    method public String getRawName();
+    enum_constant public static final com.android.internal.vibrator.persistence.PrimitiveEffectName click;
+    enum_constant public static final com.android.internal.vibrator.persistence.PrimitiveEffectName low_tick;
+    enum_constant public static final com.android.internal.vibrator.persistence.PrimitiveEffectName quick_fall;
+    enum_constant public static final com.android.internal.vibrator.persistence.PrimitiveEffectName quick_rise;
+    enum_constant public static final com.android.internal.vibrator.persistence.PrimitiveEffectName slow_rise;
+    enum_constant public static final com.android.internal.vibrator.persistence.PrimitiveEffectName spin;
+    enum_constant public static final com.android.internal.vibrator.persistence.PrimitiveEffectName thud;
+    enum_constant public static final com.android.internal.vibrator.persistence.PrimitiveEffectName tick;
+  }
+
+  public class Vibration {
+    ctor public Vibration();
+    method public com.android.internal.vibrator.persistence.PredefinedEffect getPredefinedEffect_optional();
+    method public com.android.internal.vibrator.persistence.PrimitiveEffect getPrimitiveEffect_optional();
+    method public com.android.internal.vibrator.persistence.WaveformEffect getWaveformEffect_optional();
+    method public void setPredefinedEffect_optional(com.android.internal.vibrator.persistence.PredefinedEffect);
+    method public void setPrimitiveEffect_optional(com.android.internal.vibrator.persistence.PrimitiveEffect);
+    method public void setWaveformEffect_optional(com.android.internal.vibrator.persistence.WaveformEffect);
+  }
+
+  public enum WaveformAmplitudeDefault {
+    method public String getRawName();
+    enum_constant public static final com.android.internal.vibrator.persistence.WaveformAmplitudeDefault _default;
+  }
+
+  public class WaveformEffect {
+    ctor public WaveformEffect();
+    method public com.android.internal.vibrator.persistence.WaveformEffect.Repeating getRepeating();
+    method public java.util.List<com.android.internal.vibrator.persistence.WaveformEntry> getWaveformEntry();
+    method public void setRepeating(com.android.internal.vibrator.persistence.WaveformEffect.Repeating);
+  }
+
+  public static class WaveformEffect.Repeating {
+    ctor public WaveformEffect.Repeating();
+    method public java.util.List<com.android.internal.vibrator.persistence.WaveformEntry> getWaveformEntry();
+  }
+
+  public class WaveformEntry {
+    ctor public WaveformEntry();
+    method public String getAmplitude();
+    method public java.math.BigInteger getDurationMs();
+    method public void setAmplitude(String);
+    method public void setDurationMs(java.math.BigInteger);
+  }
+
+  public class XmlParser {
+    ctor public XmlParser();
+    method public static com.android.internal.vibrator.persistence.Vibration read(java.io.InputStream) throws javax.xml.datatype.DatatypeConfigurationException, java.io.IOException, org.xmlpull.v1.XmlPullParserException;
+    method public static String readText(org.xmlpull.v1.XmlPullParser) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
+    method public static void skip(org.xmlpull.v1.XmlPullParser) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
+  }
+
+}
+
diff --git a/core/xsd/vibrator/vibration/schema/last_current.txt b/core/xsd/vibrator/vibration/schema/last_current.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/core/xsd/vibrator/vibration/schema/last_current.txt
diff --git a/core/xsd/vibrator/vibration/schema/last_removed.txt b/core/xsd/vibrator/vibration/schema/last_removed.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/core/xsd/vibrator/vibration/schema/last_removed.txt
diff --git a/core/xsd/vibrator/vibration/schema/removed.txt b/core/xsd/vibrator/vibration/schema/removed.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/core/xsd/vibrator/vibration/schema/removed.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/core/xsd/vibrator/vibration/vibration-plus-hidden-apis.xsd b/core/xsd/vibrator/vibration/vibration-plus-hidden-apis.xsd
new file mode 100644
index 0000000..cca1359
--- /dev/null
+++ b/core/xsd/vibrator/vibration/vibration-plus-hidden-apis.xsd
@@ -0,0 +1,157 @@
+<?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.
+  -->
+<!--
+    This defines the format of the XML file used to define VibrationEffect created via public and
+    hidden APIs. This format is not enforced in a backwards compatible way, and should only be used
+    on device-specific vibration files.
+-->
+<xs:schema version="2.0"
+           elementFormDefault="qualified"
+           xmlns:xs="http://www.w3.org/2001/XMLSchema">
+
+    <xs:element name="vibration" type="Vibration"/>
+
+    <!-- Type definitions -->
+
+    <xs:complexType name="Vibration">
+        <xs:choice>
+
+            <!-- Waveform vibration effect -->
+            <xs:element name="waveform-effect" type="WaveformEffect"/>
+
+            <!-- Predefined vibration effect -->
+            <xs:element name="predefined-effect" type="PredefinedEffect"/>
+
+            <!-- Primitive composition effect -->
+            <xs:sequence>
+                <xs:element name="primitive-effect" type="PrimitiveEffect"/>
+            </xs:sequence>
+
+        </xs:choice>
+    </xs:complexType>
+
+    <xs:complexType name="WaveformEffect">
+        <xs:sequence>
+
+            <!-- Optional preamble, zero or more entries -->
+            <xs:element name="waveform-entry" type="WaveformEntry"
+                        minOccurs="0" maxOccurs="unbounded"/>
+
+            <!-- Repeating element, with one or more entries -->
+            <xs:element name="repeating" minOccurs="0">
+                <xs:complexType>
+                    <xs:sequence>
+                        <xs:element name="waveform-entry" type="WaveformEntry"
+                                    maxOccurs="unbounded"/>
+                    </xs:sequence>
+                </xs:complexType>
+            </xs:element>
+
+        </xs:sequence>
+        <!-- Unsupported by xsd_config, but will be used in validation:
+        <xs:assert test="fn:count(./waveform-entry) > 0 or fn:count(./repeating) > 0)"/>
+        -->
+    </xs:complexType>
+
+    <xs:complexType name="WaveformEntry">
+        <xs:attribute name="durationMs" type="xs:nonNegativeInteger" use="required"/>
+        <xs:attribute name="amplitude" type="WaveformAmplitude" use="required"/>
+    </xs:complexType>
+
+    <xs:simpleType name="WaveformAmplitude">
+        <xs:union memberTypes="WaveformAmplitudeInt WaveformAmplitudeDefault"/>
+    </xs:simpleType>
+
+    <!-- Amplitude int in [0,255] -->
+    <xs:simpleType name="WaveformAmplitudeInt">
+        <xs:restriction base="xs:int">
+            <xs:minInclusive value="0"/>
+            <xs:maxInclusive value="255"/>
+        </xs:restriction>
+    </xs:simpleType>
+
+    <!-- Default amplitude as defined by VibrationEffect.DEFAULT_AMPLITUDE -->
+    <xs:simpleType  name="WaveformAmplitudeDefault">
+        <xs:restriction base="xs:string">
+            <xs:enumeration value="default"/>
+        </xs:restriction>
+    </xs:simpleType>
+
+    <xs:complexType name="PredefinedEffect">
+        <xs:attribute name="name" type="PredefinedEffectName" use="required"/>
+        <xs:attribute name="fallback" type="xs:boolean"/><!-- hidden -->
+    </xs:complexType>
+
+    <!-- Predefined effect names as defined by VibrationEffect.EFFECT_* -->
+    <xs:simpleType  name="PredefinedEffectName">
+        <xs:restriction base="xs:string">
+            <!-- Public effects -->
+            <xs:enumeration value="tick"/>
+            <xs:enumeration value="click"/>
+            <xs:enumeration value="heavy_click"/>
+            <xs:enumeration value="double_click"/>
+            <!-- Hidden effects -->
+            <xs:enumeration value="texture_tick"/>
+            <xs:enumeration value="thud"/>
+            <xs:enumeration value="pop"/>
+            <xs:enumeration value="ringtone_1"/>
+            <xs:enumeration value="ringtone_2"/>
+            <xs:enumeration value="ringtone_3"/>
+            <xs:enumeration value="ringtone_4"/>
+            <xs:enumeration value="ringtone_5"/>
+            <xs:enumeration value="ringtone_6"/>
+            <xs:enumeration value="ringtone_7"/>
+            <xs:enumeration value="ringtone_8"/>
+            <xs:enumeration value="ringtone_9"/>
+            <xs:enumeration value="ringtone_10"/>
+            <xs:enumeration value="ringtone_11"/>
+            <xs:enumeration value="ringtone_12"/>
+            <xs:enumeration value="ringtone_13"/>
+            <xs:enumeration value="ringtone_14"/>
+            <xs:enumeration value="ringtone_15"/>
+        </xs:restriction>
+    </xs:simpleType>
+
+    <xs:complexType name="PrimitiveEffect">
+        <xs:attribute name="name" type="PrimitiveEffectName" use="required"/>
+        <xs:attribute name="scale" type="PrimitiveScale"/>
+        <xs:attribute name="delayMs" type="xs:nonNegativeInteger"/>
+    </xs:complexType>
+
+    <!-- Primitive names as defined by VibrationEffect.Composition.PRIMITIVE_* -->
+    <xs:simpleType  name="PrimitiveEffectName">
+        <xs:restriction base="xs:string">
+            <xs:enumeration value="low_tick"/>
+            <xs:enumeration value="tick"/>
+            <xs:enumeration value="click"/>
+            <xs:enumeration value="slow_rise"/>
+            <xs:enumeration value="quick_rise"/>
+            <xs:enumeration value="quick_fall"/>
+            <xs:enumeration value="spin"/>
+            <xs:enumeration value="thud"/>
+        </xs:restriction>
+    </xs:simpleType>
+
+    <!-- Primitive scale float in [0,1] -->
+    <xs:simpleType name="PrimitiveScale">
+        <xs:restriction base="xs:float">
+            <xs:minInclusive value="0"/>
+            <xs:maxInclusive value="1"/>
+        </xs:restriction>
+    </xs:simpleType>
+
+</xs:schema>
diff --git a/core/xsd/vibrator/vibration/vibration.xsd b/core/xsd/vibrator/vibration/vibration.xsd
new file mode 100644
index 0000000..b1a815a
--- /dev/null
+++ b/core/xsd/vibrator/vibration/vibration.xsd
@@ -0,0 +1,134 @@
+<?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.
+  -->
+<!--
+    This defines the format of the XML file used to define VibrationEffect created via public APIs
+-->
+<xs:schema version="2.0"
+           elementFormDefault="qualified"
+           xmlns:xs="http://www.w3.org/2001/XMLSchema">
+
+    <xs:element name="vibration" type="Vibration"/>
+
+    <!-- Type definitions -->
+
+    <xs:complexType name="Vibration">
+        <xs:choice>
+
+            <!-- Waveform vibration effect -->
+            <xs:element name="waveform-effect" type="WaveformEffect"/>
+
+            <!-- Predefined vibration effect -->
+            <xs:element name="predefined-effect" type="PredefinedEffect"/>
+
+            <!-- Primitive composition effect -->
+            <xs:sequence>
+                <xs:element name="primitive-effect" type="PrimitiveEffect"/>
+            </xs:sequence>
+
+        </xs:choice>
+    </xs:complexType>
+
+    <xs:complexType name="WaveformEffect">
+        <xs:sequence>
+
+            <!-- Optional preamble, zero or more entries -->
+            <xs:element name="waveform-entry" type="WaveformEntry"
+                        minOccurs="0" maxOccurs="unbounded"/>
+
+            <!-- Repeating element, with one or more entries -->
+            <xs:element name="repeating" minOccurs="0">
+                <xs:complexType>
+                    <xs:sequence>
+                        <xs:element name="waveform-entry" type="WaveformEntry"
+                                    maxOccurs="unbounded"/>
+                    </xs:sequence>
+                </xs:complexType>
+            </xs:element>
+
+        </xs:sequence>
+        <!-- Unsupported by xsd_config, but will be used in validation:
+        <xs:assert test="fn:count(./waveform-entry) > 0 or fn:count(./repeating) > 0)"/>
+        -->
+    </xs:complexType>
+
+    <xs:complexType name="WaveformEntry">
+        <xs:attribute name="durationMs" type="xs:nonNegativeInteger" use="required"/>
+        <xs:attribute name="amplitude" type="WaveformAmplitude" use="required"/>
+    </xs:complexType>
+
+    <xs:simpleType name="WaveformAmplitude">
+        <xs:union memberTypes="WaveformAmplitudeInt WaveformAmplitudeDefault"/>
+    </xs:simpleType>
+
+    <!-- Amplitude int in [0,255] -->
+    <xs:simpleType name="WaveformAmplitudeInt">
+        <xs:restriction base="xs:int">
+            <xs:minInclusive value="0"/>
+            <xs:maxInclusive value="255"/>
+        </xs:restriction>
+    </xs:simpleType>
+
+    <!-- Default amplitude as defined by VibrationEffect.DEFAULT_AMPLITUDE -->
+    <xs:simpleType  name="WaveformAmplitudeDefault">
+        <xs:restriction base="xs:string">
+            <xs:enumeration value="default"/>
+        </xs:restriction>
+    </xs:simpleType>
+
+    <xs:complexType name="PredefinedEffect">
+        <xs:attribute name="name" type="PredefinedEffectName" use="required"/>
+    </xs:complexType>
+
+    <!-- Predefined effect names as defined by VibrationEffect.EFFECT_* -->
+    <xs:simpleType  name="PredefinedEffectName">
+        <xs:restriction base="xs:string">
+            <xs:enumeration value="tick"/>
+            <xs:enumeration value="click"/>
+            <xs:enumeration value="heavy_click"/>
+            <xs:enumeration value="double_click"/>
+        </xs:restriction>
+    </xs:simpleType>
+
+    <xs:complexType name="PrimitiveEffect">
+        <xs:attribute name="name" type="PrimitiveEffectName" use="required"/>
+        <xs:attribute name="scale" type="PrimitiveScale"/>
+        <xs:attribute name="delayMs" type="xs:nonNegativeInteger"/>
+    </xs:complexType>
+
+    <!-- Primitive names as defined by VibrationEffect.Composition.PRIMITIVE_* -->
+    <xs:simpleType  name="PrimitiveEffectName">
+        <xs:restriction base="xs:string">
+            <xs:enumeration value="low_tick"/>
+            <xs:enumeration value="tick"/>
+            <xs:enumeration value="click"/>
+            <xs:enumeration value="slow_rise"/>
+            <xs:enumeration value="quick_rise"/>
+            <xs:enumeration value="quick_fall"/>
+            <xs:enumeration value="spin"/>
+            <xs:enumeration value="thud"/>
+        </xs:restriction>
+    </xs:simpleType>
+
+    <!-- Primitive scale float in [0,1] -->
+    <xs:simpleType name="PrimitiveScale">
+        <xs:restriction base="xs:float">
+            <xs:minInclusive value="0"/>
+            <xs:maxInclusive value="1"/>
+        </xs:restriction>
+    </xs:simpleType>
+
+</xs:schema>
diff --git a/core/xsd/vts/OWNERS b/core/xsd/vts/OWNERS
new file mode 100644
index 0000000..9af2eba
--- /dev/null
+++ b/core/xsd/vts/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 151862
+sundongahn@google.com
diff --git a/data/etc/com.android.networkstack.xml b/data/etc/com.android.networkstack.xml
index 06fec1c..003ca53 100644
--- a/data/etc/com.android.networkstack.xml
+++ b/data/etc/com.android.networkstack.xml
@@ -25,6 +25,7 @@
         <permission name="android.permission.LOCAL_MAC_ADDRESS"/>
         <permission name="android.permission.MANAGE_SUBSCRIPTION_PLANS"/>
         <permission name="android.permission.MANAGE_USB"/>
+        <permission name="android.permission.MANAGE_USERS"/>
         <permission name="android.permission.NETWORK_BYPASS_PRIVATE_DNS"/>
         <permission name="android.permission.PACKET_KEEPALIVE_OFFLOAD"/>
         <permission name="android.permission.READ_NETWORK_USAGE_HISTORY"/>
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index 2c85fe4..c4530f6 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -346,4 +346,8 @@
 
     <!-- Allow IMS service entitlement app to schedule jobs to run when app in background. -->
     <allow-in-power-save-except-idle package="com.android.imsserviceentitlement" />
+
+    <!-- Allow device lock controller app to schedule jobs and alarms when app in background,
+        otherwise, it may not be able to enforce provision for managed devices. -->
+    <allow-in-power-save package="com.android.devicelockcontroller" />
 </permissions>
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index a044602..3206dd2 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -257,6 +257,7 @@
         <permission name="android.permission.CLEAR_APP_CACHE"/>
         <permission name="android.permission.ACCESS_INSTANT_APPS" />
         <permission name="android.permission.CONNECTIVITY_INTERNAL"/>
+        <permission name="android.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS" />
         <permission name="android.permission.DELETE_CACHE_FILES"/>
         <permission name="android.permission.DELETE_PACKAGES"/>
         <permission name="android.permission.DUMP"/>
@@ -535,6 +536,10 @@
         <permission name="android.permission.INTERACT_ACROSS_USERS"/>
     </privapp-permissions>
 
+    <privapp-permissions package="com.android.soundpicker">
+        <permission name="android.permission.INTERACT_ACROSS_USERS" />
+    </privapp-permissions>
+
     <privapp-permissions package="com.android.tv">
         <permission name="android.permission.CHANGE_HDMI_CEC_ACTIVE_SOURCE"/>
         <permission name="android.permission.DVB_DEVICE"/>
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 93e44f1..94e23e7 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -3385,6 +3385,12 @@
       "group": "WM_DEBUG_REMOTE_ANIMATIONS",
       "at": "com\/android\/server\/wm\/RemoteAnimationController.java"
     },
+    "975028389": {
+      "message": "unable to call receiver for empty keyboard shortcuts",
+      "level": "ERROR",
+      "group": "WM_ERROR",
+      "at": "com\/android\/server\/wm\/WindowManagerService.java"
+    },
     "975275467": {
       "message": "Set animatingExit: reason=remove\/isAnimating win=%s",
       "level": "VERBOSE",
diff --git a/data/fonts/Android.bp b/data/fonts/Android.bp
index f90a74d..3dd9ba9 100644
--- a/data/fonts/Android.bp
+++ b/data/fonts/Android.bp
@@ -52,3 +52,8 @@
     name: "fonts.xml",
     src: "fonts.xml",
 }
+
+prebuilt_etc {
+    name: "font_fallback.xml",
+    src: "font_fallback.xml",
+}
diff --git a/data/fonts/font_fallback.xml b/data/fonts/font_fallback.xml
new file mode 100644
index 0000000..1e97fce
--- /dev/null
+++ b/data/fonts/font_fallback.xml
@@ -0,0 +1,1630 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    In this file, all fonts without names are added to the default list.
+    Fonts are chosen based on a match: full BCP-47 language tag including
+    script, then just language, and finally order (the first font containing
+    the glyph).
+
+    Order of appearance is also the tiebreaker for weight matching. This is
+    the reason why the 900 weights of Roboto precede the 700 weights - we
+    prefer the former when an 800 weight is requested. Since bold spans
+    effectively add 300 to the weight, this ensures that 900 is the bold
+    paired with the 500 weight, ensuring adequate contrast.
+
+    TODO(rsheeter) update comment; ordering to match 800 to 900 is no longer required
+-->
+<familyset version="23">
+    <!-- first font is default -->
+    <family name="sans-serif">
+        <font weight="100" style="normal">Roboto-Regular.ttf
+          <axis tag="ital" stylevalue="0" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="100" />
+        </font>
+        <font weight="200" style="normal">Roboto-Regular.ttf
+          <axis tag="ital" stylevalue="0" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="200" />
+        </font>
+        <font weight="300" style="normal">Roboto-Regular.ttf
+          <axis tag="ital" stylevalue="0" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="300" />
+        </font>
+        <font weight="400" style="normal">Roboto-Regular.ttf
+          <axis tag="ital" stylevalue="0" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="400" />
+        </font>
+        <font weight="500" style="normal">Roboto-Regular.ttf
+          <axis tag="ital" stylevalue="0" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="500" />
+        </font>
+        <font weight="600" style="normal">Roboto-Regular.ttf
+          <axis tag="ital" stylevalue="0" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="600" />
+        </font>
+        <font weight="700" style="normal">Roboto-Regular.ttf
+          <axis tag="ital" stylevalue="0" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="700" />
+        </font>
+        <font weight="800" style="normal">Roboto-Regular.ttf
+          <axis tag="ital" stylevalue="0" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="800" />
+        </font>
+        <font weight="900" style="normal">Roboto-Regular.ttf
+          <axis tag="ital" stylevalue="0" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="900" />
+        </font>
+        <font weight="100" style="italic">Roboto-Regular.ttf
+          <axis tag="ital" stylevalue="1" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="100" />
+        </font>
+        <font weight="200" style="italic">Roboto-Regular.ttf
+          <axis tag="ital" stylevalue="1" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="200" />
+        </font>
+        <font weight="300" style="italic">Roboto-Regular.ttf
+          <axis tag="ital" stylevalue="1" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="300" />
+        </font>
+        <font weight="400" style="italic">Roboto-Regular.ttf
+          <axis tag="ital" stylevalue="1" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="400" />
+        </font>
+        <font weight="500" style="italic">Roboto-Regular.ttf
+          <axis tag="ital" stylevalue="1" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="500" />
+        </font>
+        <font weight="600" style="italic">Roboto-Regular.ttf
+          <axis tag="ital" stylevalue="1" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="600" />
+        </font>
+        <font weight="700" style="italic">Roboto-Regular.ttf
+          <axis tag="ital" stylevalue="1" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="700" />
+        </font>
+        <font weight="800" style="italic">Roboto-Regular.ttf
+          <axis tag="ital" stylevalue="1" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="800" />
+        </font>
+        <font weight="900" style="italic">Roboto-Regular.ttf
+          <axis tag="ital" stylevalue="1" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="900" />
+        </font>
+   </family>
+
+
+    <!-- Note that aliases must come after the fonts they reference. -->
+    <alias name="sans-serif-thin" to="sans-serif" weight="100" />
+    <alias name="sans-serif-light" to="sans-serif" weight="300" />
+    <alias name="sans-serif-medium" to="sans-serif" weight="500" />
+    <alias name="sans-serif-black" to="sans-serif" weight="900" />
+    <alias name="arial" to="sans-serif" />
+    <alias name="helvetica" to="sans-serif" />
+    <alias name="tahoma" to="sans-serif" />
+    <alias name="verdana" to="sans-serif" />
+
+    <family name="sans-serif-condensed">
+      <font weight="100" style="normal">Roboto-Regular.ttf
+        <axis tag="ital" stylevalue="0" />
+        <axis tag="wdth" stylevalue="75" />
+        <axis tag="wght" stylevalue="100" />
+      </font>
+      <font weight="200" style="normal">Roboto-Regular.ttf
+        <axis tag="ital" stylevalue="0" />
+        <axis tag="wdth" stylevalue="75" />
+        <axis tag="wght" stylevalue="200" />
+      </font>
+      <font weight="300" style="normal">Roboto-Regular.ttf
+        <axis tag="ital" stylevalue="0" />
+        <axis tag="wdth" stylevalue="75" />
+        <axis tag="wght" stylevalue="300" />
+      </font>
+      <font weight="400" style="normal">Roboto-Regular.ttf
+        <axis tag="ital" stylevalue="0" />
+        <axis tag="wdth" stylevalue="75" />
+        <axis tag="wght" stylevalue="400" />
+      </font>
+      <font weight="500" style="normal">Roboto-Regular.ttf
+        <axis tag="ital" stylevalue="0" />
+        <axis tag="wdth" stylevalue="75" />
+        <axis tag="wght" stylevalue="500" />
+      </font>
+      <font weight="600" style="normal">Roboto-Regular.ttf
+        <axis tag="ital" stylevalue="0" />
+        <axis tag="wdth" stylevalue="75" />
+        <axis tag="wght" stylevalue="600" />
+      </font>
+      <font weight="700" style="normal">Roboto-Regular.ttf
+        <axis tag="ital" stylevalue="0" />
+        <axis tag="wdth" stylevalue="75" />
+        <axis tag="wght" stylevalue="700" />
+      </font>
+      <font weight="800" style="normal">Roboto-Regular.ttf
+        <axis tag="ital" stylevalue="0" />
+        <axis tag="wdth" stylevalue="75" />
+        <axis tag="wght" stylevalue="800" />
+      </font>
+      <font weight="900" style="normal">Roboto-Regular.ttf
+        <axis tag="ital" stylevalue="0" />
+        <axis tag="wdth" stylevalue="75" />
+        <axis tag="wght" stylevalue="900" />
+      </font>
+      <font weight="100" style="italic">Roboto-Regular.ttf
+        <axis tag="ital" stylevalue="1" />
+        <axis tag="wdth" stylevalue="75" />
+        <axis tag="wght" stylevalue="100" />
+      </font>
+      <font weight="200" style="italic">Roboto-Regular.ttf
+        <axis tag="ital" stylevalue="1" />
+        <axis tag="wdth" stylevalue="75" />
+        <axis tag="wght" stylevalue="200" />
+      </font>
+      <font weight="300" style="italic">Roboto-Regular.ttf
+        <axis tag="ital" stylevalue="1" />
+        <axis tag="wdth" stylevalue="75" />
+        <axis tag="wght" stylevalue="300" />
+      </font>
+      <font weight="400" style="italic">Roboto-Regular.ttf
+        <axis tag="ital" stylevalue="1" />
+        <axis tag="wdth" stylevalue="75" />
+        <axis tag="wght" stylevalue="400" />
+      </font>
+      <font weight="500" style="italic">Roboto-Regular.ttf
+        <axis tag="ital" stylevalue="1" />
+        <axis tag="wdth" stylevalue="75" />
+        <axis tag="wght" stylevalue="500" />
+      </font>
+      <font weight="600" style="italic">Roboto-Regular.ttf
+        <axis tag="ital" stylevalue="1" />
+        <axis tag="wdth" stylevalue="75" />
+        <axis tag="wght" stylevalue="600" />
+      </font>
+      <font weight="700" style="italic">Roboto-Regular.ttf
+        <axis tag="ital" stylevalue="1" />
+        <axis tag="wdth" stylevalue="75" />
+        <axis tag="wght" stylevalue="700" />
+      </font>
+      <font weight="800" style="italic">Roboto-Regular.ttf
+        <axis tag="ital" stylevalue="1" />
+        <axis tag="wdth" stylevalue="75" />
+        <axis tag="wght" stylevalue="800" />
+      </font>
+      <font weight="900" style="italic">Roboto-Regular.ttf
+        <axis tag="ital" stylevalue="1" />
+        <axis tag="wdth" stylevalue="75" />
+        <axis tag="wght" stylevalue="900" />
+      </font>
+    </family>
+    <alias name="sans-serif-condensed-light" to="sans-serif-condensed" weight="300" />
+    <alias name="sans-serif-condensed-medium" to="sans-serif-condensed" weight="500" />
+
+    <family name="serif">
+        <font weight="400" style="normal" postScriptName="NotoSerif">NotoSerif-Regular.ttf</font>
+        <font weight="700" style="normal">NotoSerif-Bold.ttf</font>
+        <font weight="400" style="italic">NotoSerif-Italic.ttf</font>
+        <font weight="700" style="italic">NotoSerif-BoldItalic.ttf</font>
+    </family>
+    <alias name="serif-bold" to="serif" weight="700" />
+    <alias name="times" to="serif" />
+    <alias name="times new roman" to="serif" />
+    <alias name="palatino" to="serif" />
+    <alias name="georgia" to="serif" />
+    <alias name="baskerville" to="serif" />
+    <alias name="goudy" to="serif" />
+    <alias name="fantasy" to="serif" />
+    <alias name="ITC Stone Serif" to="serif" />
+
+    <family name="monospace">
+        <font weight="400" style="normal">DroidSansMono.ttf</font>
+    </family>
+    <alias name="sans-serif-monospace" to="monospace" />
+    <alias name="monaco" to="monospace" />
+
+    <family name="serif-monospace">
+        <font weight="400" style="normal" postScriptName="CutiveMono-Regular">CutiveMono.ttf</font>
+    </family>
+    <alias name="courier" to="serif-monospace" />
+    <alias name="courier new" to="serif-monospace" />
+
+    <family name="casual">
+        <font weight="400" style="normal" postScriptName="ComingSoon-Regular">ComingSoon.ttf</font>
+    </family>
+
+    <family name="cursive">
+      <font weight="400" style="normal">DancingScript-Regular.ttf
+        <axis tag="wght" stylevalue="400" />
+      </font>
+      <font weight="700" style="normal">DancingScript-Regular.ttf
+        <axis tag="wght" stylevalue="700" />
+      </font>
+    </family>
+
+    <family name="sans-serif-smallcaps">
+        <font weight="400" style="normal">CarroisGothicSC-Regular.ttf</font>
+    </family>
+
+    <family name="source-sans-pro">
+        <font weight="400" style="normal">SourceSansPro-Regular.ttf</font>
+        <font weight="400" style="italic">SourceSansPro-Italic.ttf</font>
+        <font weight="600" style="normal">SourceSansPro-SemiBold.ttf</font>
+        <font weight="600" style="italic">SourceSansPro-SemiBoldItalic.ttf</font>
+        <font weight="700" style="normal">SourceSansPro-Bold.ttf</font>
+        <font weight="700" style="italic">SourceSansPro-BoldItalic.ttf</font>
+    </family>
+    <alias name="source-sans-pro-semi-bold" to="source-sans-pro" weight="600"/>
+
+    <family name="roboto-flex">
+        <font weight="100" style="normal">RobotoFlex-Regular.ttf
+          <axis tag="slnt" stylevalue="0" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="100" />
+        </font>
+        <font weight="200" style="normal">RobotoFlex-Regular.ttf
+          <axis tag="slnt" stylevalue="0" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="200" />
+        </font>
+        <font weight="300" style="normal">RobotoFlex-Regular.ttf
+          <axis tag="slnt" stylevalue="0" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="300" />
+        </font>
+        <font weight="400" style="normal">RobotoFlex-Regular.ttf
+          <axis tag="slnt" stylevalue="0" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="400" />
+        </font>
+        <font weight="500" style="normal">RobotoFlex-Regular.ttf
+          <axis tag="slnt" stylevalue="0" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="500" />
+        </font>
+        <font weight="600" style="normal">RobotoFlex-Regular.ttf
+          <axis tag="slnt" stylevalue="0" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="600" />
+        </font>
+        <font weight="700" style="normal">RobotoFlex-Regular.ttf
+          <axis tag="slnt" stylevalue="0" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="700" />
+        </font>
+        <font weight="800" style="normal">RobotoFlex-Regular.ttf
+          <axis tag="slnt" stylevalue="0" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="800" />
+        </font>
+        <font weight="900" style="normal">RobotoFlex-Regular.ttf
+          <axis tag="slnt" stylevalue="0" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="900" />
+        </font>
+        <font weight="100" style="italic">RobotoFlex-Regular.ttf
+          <axis tag="slnt" stylevalue="-10" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="100" />
+        </font>
+        <font weight="200" style="italic">RobotoFlex-Regular.ttf
+          <axis tag="slnt" stylevalue="-10" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="200" />
+        </font>
+        <font weight="300" style="italic">RobotoFlex-Regular.ttf
+          <axis tag="slnt" stylevalue="-10" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="300" />
+        </font>
+        <font weight="400" style="italic">RobotoFlex-Regular.ttf
+          <axis tag="slnt" stylevalue="-10" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="400" />
+        </font>
+        <font weight="500" style="italic">RobotoFlex-Regular.ttf
+          <axis tag="slnt" stylevalue="-10" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="500" />
+        </font>
+        <font weight="600" style="italic">RobotoFlex-Regular.ttf
+          <axis tag="slnt" stylevalue="-10" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="600" />
+        </font>
+        <font weight="700" style="italic">RobotoFlex-Regular.ttf
+          <axis tag="slnt" stylevalue="-10" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="700" />
+        </font>
+        <font weight="800" style="italic">RobotoFlex-Regular.ttf
+          <axis tag="slnt" stylevalue="-10" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="800" />
+        </font>
+        <font weight="900" style="italic">RobotoFlex-Regular.ttf
+          <axis tag="slnt" stylevalue="-10" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="900" />
+        </font>
+    </family>
+
+    <!-- fallback fonts -->
+    <family lang="und-Arab" variant="elegant">
+        <font weight="400" style="normal" postScriptName="NotoNaskhArabic">
+            NotoNaskhArabic-Regular.ttf
+        </font>
+        <font weight="700" style="normal">NotoNaskhArabic-Bold.ttf</font>
+    </family>
+    <family lang="und-Arab" variant="compact">
+        <font weight="400" style="normal" postScriptName="NotoNaskhArabicUI">
+            NotoNaskhArabicUI-Regular.ttf
+        </font>
+        <font weight="700" style="normal">NotoNaskhArabicUI-Bold.ttf</font>
+    </family>
+    <family lang="und-Ethi">
+        <font weight="400" style="normal" postScriptName="NotoSansEthiopic-Regular">
+            NotoSansEthiopic-VF.ttf
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="500" style="normal" postScriptName="NotoSansEthiopic-Regular">
+            NotoSansEthiopic-VF.ttf
+            <axis tag="wght" stylevalue="500"/>
+        </font>
+        <font weight="600" style="normal" postScriptName="NotoSansEthiopic-Regular">
+            NotoSansEthiopic-VF.ttf
+            <axis tag="wght" stylevalue="600"/>
+        </font>
+        <font weight="700" style="normal" postScriptName="NotoSansEthiopic-Regular">
+            NotoSansEthiopic-VF.ttf
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+        <font weight="400" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifEthiopic-Regular">NotoSerifEthiopic-VF.ttf
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="500" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifEthiopic-Regular">NotoSerifEthiopic-VF.ttf
+            <axis tag="wght" stylevalue="500"/>
+        </font>
+        <font weight="600" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifEthiopic-Regular">NotoSerifEthiopic-VF.ttf
+            <axis tag="wght" stylevalue="600"/>
+        </font>
+        <font weight="700" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifEthiopic-Regular">NotoSerifEthiopic-VF.ttf
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+    </family>
+    <family lang="und-Hebr">
+        <font weight="400" style="normal" postScriptName="NotoSansHebrew">
+            NotoSansHebrew-Regular.ttf
+        </font>
+        <font weight="700" style="normal">NotoSansHebrew-Bold.ttf</font>
+        <font weight="400" style="normal" fallbackFor="serif">NotoSerifHebrew-Regular.ttf</font>
+        <font weight="700" style="normal" fallbackFor="serif">NotoSerifHebrew-Bold.ttf</font>
+    </family>
+    <family lang="und-Thai" variant="elegant">
+        <font weight="400" style="normal" postScriptName="NotoSansThai">NotoSansThai-Regular.ttf
+        </font>
+        <font weight="700" style="normal">NotoSansThai-Bold.ttf</font>
+        <font weight="400" style="normal" fallbackFor="serif">
+            NotoSerifThai-Regular.ttf
+        </font>
+        <font weight="700" style="normal" fallbackFor="serif">NotoSerifThai-Bold.ttf</font>
+    </family>
+    <family lang="und-Thai" variant="compact">
+        <font weight="400" style="normal" postScriptName="NotoSansThaiUI">
+            NotoSansThaiUI-Regular.ttf
+        </font>
+        <font weight="700" style="normal">NotoSansThaiUI-Bold.ttf</font>
+    </family>
+    <family lang="und-Armn">
+        <font weight="400" style="normal" postScriptName="NotoSansArmenian-Regular">
+            NotoSansArmenian-VF.ttf
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="500" style="normal" postScriptName="NotoSansArmenian-Regular">
+            NotoSansArmenian-VF.ttf
+            <axis tag="wght" stylevalue="500"/>
+        </font>
+        <font weight="600" style="normal" postScriptName="NotoSansArmenian-Regular">
+            NotoSansArmenian-VF.ttf
+            <axis tag="wght" stylevalue="600"/>
+        </font>
+        <font weight="700" style="normal" postScriptName="NotoSansArmenian-Regular">
+            NotoSansArmenian-VF.ttf
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+        <font weight="400" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifArmenian-Regular">NotoSerifArmenian-VF.ttf
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="500" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifArmenian-Regular">NotoSerifArmenian-VF.ttf
+            <axis tag="wght" stylevalue="500"/>
+        </font>
+        <font weight="600" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifArmenian-Regular">NotoSerifArmenian-VF.ttf
+            <axis tag="wght" stylevalue="600"/>
+        </font>
+        <font weight="700" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifArmenian-Regular">NotoSerifArmenian-VF.ttf
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+    </family>
+    <family lang="und-Geor,und-Geok">
+        <font weight="400" style="normal" postScriptName="NotoSansGeorgian-Regular">
+            NotoSansGeorgian-VF.ttf
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="500" style="normal" postScriptName="NotoSansGeorgian-Regular">
+            NotoSansGeorgian-VF.ttf
+            <axis tag="wght" stylevalue="500"/>
+        </font>
+        <font weight="600" style="normal" postScriptName="NotoSansGeorgian-Regular">
+            NotoSansGeorgian-VF.ttf
+            <axis tag="wght" stylevalue="600"/>
+        </font>
+        <font weight="700" style="normal" postScriptName="NotoSansGeorgian-Regular">
+            NotoSansGeorgian-VF.ttf
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+        <font weight="400" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifGeorgian-Regular">NotoSerifGeorgian-VF.ttf
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="500" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifGeorgian-Regular">NotoSerifGeorgian-VF.ttf
+            <axis tag="wght" stylevalue="500"/>
+        </font>
+        <font weight="600" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifGeorgian-Regular">NotoSerifGeorgian-VF.ttf
+            <axis tag="wght" stylevalue="600"/>
+        </font>
+        <font weight="700" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifGeorgian-Regular">NotoSerifGeorgian-VF.ttf
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+    </family>
+    <family lang="und-Deva" variant="elegant">
+        <font weight="400" style="normal" postScriptName="NotoSansDevanagari-Regular">
+            NotoSansDevanagari-VF.ttf
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="500" style="normal" postScriptName="NotoSansDevanagari-Regular">
+            NotoSansDevanagari-VF.ttf
+            <axis tag="wght" stylevalue="500"/>
+        </font>
+        <font weight="600" style="normal" postScriptName="NotoSansDevanagari-Regular">
+            NotoSansDevanagari-VF.ttf
+            <axis tag="wght" stylevalue="600"/>
+        </font>
+        <font weight="700" style="normal" postScriptName="NotoSansDevanagari-Regular">
+            NotoSansDevanagari-VF.ttf
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+        <font weight="400" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifDevanagari-Regular">NotoSerifDevanagari-VF.ttf
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="500" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifDevanagari-Regular">NotoSerifDevanagari-VF.ttf
+            <axis tag="wght" stylevalue="500"/>
+        </font>
+        <font weight="600" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifDevanagari-Regular">NotoSerifDevanagari-VF.ttf
+            <axis tag="wght" stylevalue="600"/>
+        </font>
+        <font weight="700" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifDevanagari-Regular">NotoSerifDevanagari-VF.ttf
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+    </family>
+    <family lang="und-Deva" variant="compact">
+        <font weight="400" style="normal" postScriptName="NotoSansDevanagariUI-Regular">
+            NotoSansDevanagariUI-VF.ttf
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="500" style="normal" postScriptName="NotoSansDevanagariUI-Regular">
+            NotoSansDevanagariUI-VF.ttf
+            <axis tag="wght" stylevalue="500"/>
+        </font>
+        <font weight="600" style="normal" postScriptName="NotoSansDevanagariUI-Regular">
+            NotoSansDevanagariUI-VF.ttf
+            <axis tag="wght" stylevalue="600"/>
+        </font>
+        <font weight="700" style="normal" postScriptName="NotoSansDevanagariUI-Regular">
+            NotoSansDevanagariUI-VF.ttf
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+    </family>
+
+    <!-- All scripts of India should come after Devanagari, due to shared
+         danda characters.
+    -->
+    <family lang="und-Gujr" variant="elegant">
+        <font weight="400" style="normal" postScriptName="NotoSansGujarati">
+            NotoSansGujarati-Regular.ttf
+        </font>
+        <font weight="700" style="normal">NotoSansGujarati-Bold.ttf</font>
+        <font weight="400" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifGujarati-Regular">NotoSerifGujarati-VF.ttf
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="500" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifGujarati-Regular">NotoSerifGujarati-VF.ttf
+            <axis tag="wght" stylevalue="500"/>
+        </font>
+        <font weight="600" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifGujarati-Regular">NotoSerifGujarati-VF.ttf
+            <axis tag="wght" stylevalue="600"/>
+        </font>
+        <font weight="700" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifGujarati-Regular">NotoSerifGujarati-VF.ttf
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+    </family>
+    <family lang="und-Gujr" variant="compact">
+        <font weight="400" style="normal" postScriptName="NotoSansGujaratiUI">
+            NotoSansGujaratiUI-Regular.ttf
+        </font>
+        <font weight="700" style="normal">NotoSansGujaratiUI-Bold.ttf</font>
+    </family>
+    <family lang="und-Guru" variant="elegant">
+        <font weight="400" style="normal" postScriptName="NotoSansGurmukhi-Regular">
+            NotoSansGurmukhi-VF.ttf
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="500" style="normal" postScriptName="NotoSansGurmukhi-Regular">
+            NotoSansGurmukhi-VF.ttf
+            <axis tag="wght" stylevalue="500"/>
+        </font>
+        <font weight="600" style="normal" postScriptName="NotoSansGurmukhi-Regular">
+            NotoSansGurmukhi-VF.ttf
+            <axis tag="wght" stylevalue="600"/>
+        </font>
+        <font weight="700" style="normal" postScriptName="NotoSansGurmukhi-Regular">
+            NotoSansGurmukhi-VF.ttf
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+        <font weight="400" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifGurmukhi-Regular">NotoSerifGurmukhi-VF.ttf
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="500" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifGurmukhi-Regular">NotoSerifGurmukhi-VF.ttf
+            <axis tag="wght" stylevalue="500"/>
+        </font>
+        <font weight="600" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifGurmukhi-Regular">NotoSerifGurmukhi-VF.ttf
+            <axis tag="wght" stylevalue="600"/>
+        </font>
+        <font weight="700" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifGurmukhi-Regular">NotoSerifGurmukhi-VF.ttf
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+    </family>
+    <family lang="und-Guru" variant="compact">
+        <font weight="400" style="normal" postScriptName="NotoSansGurmukhiUI-Regular">
+            NotoSansGurmukhiUI-VF.ttf
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="500" style="normal" postScriptName="NotoSansGurmukhiUI-Regular">
+            NotoSansGurmukhiUI-VF.ttf
+            <axis tag="wght" stylevalue="500"/>
+        </font>
+        <font weight="600" style="normal" postScriptName="NotoSansGurmukhiUI-Regular">
+            NotoSansGurmukhiUI-VF.ttf
+            <axis tag="wght" stylevalue="600"/>
+        </font>
+        <font weight="700" style="normal" postScriptName="NotoSansGurmukhiUI-Regular">
+            NotoSansGurmukhiUI-VF.ttf
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+    </family>
+    <family lang="und-Taml" variant="elegant">
+        <font weight="400" style="normal" postScriptName="NotoSansTamil-Regular">
+            NotoSansTamil-VF.ttf
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="500" style="normal" postScriptName="NotoSansTamil-Regular">
+            NotoSansTamil-VF.ttf
+            <axis tag="wght" stylevalue="500"/>
+        </font>
+        <font weight="600" style="normal" postScriptName="NotoSansTamil-Regular">
+            NotoSansTamil-VF.ttf
+            <axis tag="wght" stylevalue="600"/>
+        </font>
+        <font weight="700" style="normal" postScriptName="NotoSansTamil-Regular">
+            NotoSansTamil-VF.ttf
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+        <font weight="400" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifTamil-Regular">NotoSerifTamil-VF.ttf
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="500" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifTamil-Regular">NotoSerifTamil-VF.ttf
+            <axis tag="wght" stylevalue="500"/>
+        </font>
+        <font weight="600" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifTamil-Regular">NotoSerifTamil-VF.ttf
+            <axis tag="wght" stylevalue="600"/>
+        </font>
+        <font weight="700" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifTamil-Regular">NotoSerifTamil-VF.ttf
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+    </family>
+    <family lang="und-Taml" variant="compact">
+        <font weight="400" style="normal" postScriptName="NotoSansTamilUI-Regular">
+            NotoSansTamilUI-VF.ttf
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="500" style="normal" postScriptName="NotoSansTamilUI-Regular">
+            NotoSansTamilUI-VF.ttf
+            <axis tag="wght" stylevalue="500"/>
+        </font>
+        <font weight="600" style="normal" postScriptName="NotoSansTamilUI-Regular">
+            NotoSansTamilUI-VF.ttf
+            <axis tag="wght" stylevalue="600"/>
+        </font>
+        <font weight="700" style="normal" postScriptName="NotoSansTamilUI-Regular">
+            NotoSansTamilUI-VF.ttf
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+    </family>
+    <family lang="und-Mlym" variant="elegant">
+        <font weight="400" style="normal" postScriptName="NotoSansMalayalam-Regular">
+            NotoSansMalayalam-VF.ttf
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="500" style="normal" postScriptName="NotoSansMalayalam-Regular">
+            NotoSansMalayalam-VF.ttf
+            <axis tag="wght" stylevalue="500"/>
+        </font>
+        <font weight="600" style="normal" postScriptName="NotoSansMalayalam-Regular">
+            NotoSansMalayalam-VF.ttf
+            <axis tag="wght" stylevalue="600"/>
+        </font>
+        <font weight="700" style="normal" postScriptName="NotoSansMalayalam-Regular">
+            NotoSansMalayalam-VF.ttf
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+        <font weight="400" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifMalayalam-Regular">NotoSerifMalayalam-VF.ttf
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="500" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifMalayalam-Regular">NotoSerifMalayalam-VF.ttf
+            <axis tag="wght" stylevalue="500"/>
+        </font>
+        <font weight="600" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifMalayalam-Regular">NotoSerifMalayalam-VF.ttf
+            <axis tag="wght" stylevalue="600"/>
+        </font>
+        <font weight="700" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifMalayalam-Regular">NotoSerifMalayalam-VF.ttf
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+    </family>
+    <family lang="und-Mlym" variant="compact">
+        <font weight="400" style="normal" postScriptName="NotoSansMalayalamUI-Regular">
+            NotoSansMalayalamUI-VF.ttf
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="500" style="normal" postScriptName="NotoSansMalayalamUI-Regular">
+            NotoSansMalayalamUI-VF.ttf
+            <axis tag="wght" stylevalue="500"/>
+        </font>
+        <font weight="600" style="normal" postScriptName="NotoSansMalayalamUI-Regular">
+            NotoSansMalayalamUI-VF.ttf
+            <axis tag="wght" stylevalue="600"/>
+        </font>
+        <font weight="700" style="normal" postScriptName="NotoSansMalayalamUI-Regular">
+            NotoSansMalayalamUI-VF.ttf
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+    </family>
+    <family lang="und-Beng" variant="elegant">
+        <font weight="400" style="normal" postScriptName="NotoSansBengali-Regular">
+            NotoSansBengali-VF.ttf
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="500" style="normal" postScriptName="NotoSansBengali-Regular">
+            NotoSansBengali-VF.ttf
+            <axis tag="wght" stylevalue="500"/>
+        </font>
+        <font weight="600" style="normal" postScriptName="NotoSansBengali-Regular">
+            NotoSansBengali-VF.ttf
+            <axis tag="wght" stylevalue="600"/>
+        </font>
+        <font weight="700" style="normal" postScriptName="NotoSansBengali-Regular">
+            NotoSansBengali-VF.ttf
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+        <font weight="400" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifBengali-Regular">NotoSerifBengali-VF.ttf
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="500" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifBengali-Regular">NotoSerifBengali-VF.ttf
+            <axis tag="wght" stylevalue="500"/>
+        </font>
+        <font weight="600" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifBengali-Regular">NotoSerifBengali-VF.ttf
+            <axis tag="wght" stylevalue="600"/>
+        </font>
+        <font weight="700" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifBengali-Regular">NotoSerifBengali-VF.ttf
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+    </family>
+    <family lang="und-Beng" variant="compact">
+        <font weight="400" style="normal" postScriptName="NotoSansBengaliUI-Regular">
+            NotoSansBengaliUI-VF.ttf
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="500" style="normal" postScriptName="NotoSansBengaliUI-Regular">
+            NotoSansBengaliUI-VF.ttf
+            <axis tag="wght" stylevalue="500"/>
+        </font>
+        <font weight="600" style="normal" postScriptName="NotoSansBengaliUI-Regular">
+            NotoSansBengaliUI-VF.ttf
+            <axis tag="wght" stylevalue="600"/>
+        </font>
+        <font weight="700" style="normal" postScriptName="NotoSansBengaliUI-Regular">
+            NotoSansBengaliUI-VF.ttf
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+    </family>
+    <family lang="und-Telu" variant="elegant">
+        <font weight="400" style="normal" postScriptName="NotoSansTelugu-Regular">
+            NotoSansTelugu-VF.ttf
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="500" style="normal" postScriptName="NotoSansTelugu-Regular">
+            NotoSansTelugu-VF.ttf
+            <axis tag="wght" stylevalue="500"/>
+        </font>
+        <font weight="600" style="normal" postScriptName="NotoSansTelugu-Regular">
+            NotoSansTelugu-VF.ttf
+            <axis tag="wght" stylevalue="600"/>
+        </font>
+        <font weight="700" style="normal" postScriptName="NotoSansTelugu-Regular">
+            NotoSansTelugu-VF.ttf
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+        <font weight="400" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifTelugu-Regular">NotoSerifTelugu-VF.ttf
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="500" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifTelugu-Regular">NotoSerifTelugu-VF.ttf
+            <axis tag="wght" stylevalue="500"/>
+        </font>
+        <font weight="600" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifTelugu-Regular">NotoSerifTelugu-VF.ttf
+            <axis tag="wght" stylevalue="600"/>
+        </font>
+        <font weight="700" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifTelugu-Regular">NotoSerifTelugu-VF.ttf
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+    </family>
+    <family lang="und-Telu" variant="compact">
+        <font weight="400" style="normal" postScriptName="NotoSansTeluguUI-Regular">
+            NotoSansTeluguUI-VF.ttf
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="500" style="normal" postScriptName="NotoSansTeluguUI-Regular">
+            NotoSansTeluguUI-VF.ttf
+            <axis tag="wght" stylevalue="500"/>
+        </font>
+        <font weight="600" style="normal" postScriptName="NotoSansTeluguUI-Regular">
+            NotoSansTeluguUI-VF.ttf
+            <axis tag="wght" stylevalue="600"/>
+        </font>
+        <font weight="700" style="normal" postScriptName="NotoSansTeluguUI-Regular">
+            NotoSansTeluguUI-VF.ttf
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+    </family>
+    <family lang="und-Knda" variant="elegant">
+        <font weight="400" style="normal" postScriptName="NotoSansKannada-Regular">
+            NotoSansKannada-VF.ttf
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="500" style="normal" postScriptName="NotoSansKannada-Regular">
+            NotoSansKannada-VF.ttf
+            <axis tag="wght" stylevalue="500"/>
+        </font>
+        <font weight="600" style="normal" postScriptName="NotoSansKannada-Regular">
+            NotoSansKannada-VF.ttf
+            <axis tag="wght" stylevalue="600"/>
+        </font>
+        <font weight="700" style="normal" postScriptName="NotoSansKannada-Regular">
+            NotoSansKannada-VF.ttf
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+        <font weight="400" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifKannada-Regular">NotoSerifKannada-VF.ttf
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="500" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifKannada-Regular">NotoSerifKannada-VF.ttf
+            <axis tag="wght" stylevalue="500"/>
+        </font>
+        <font weight="600" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifKannada-Regular">NotoSerifKannada-VF.ttf
+            <axis tag="wght" stylevalue="600"/>
+        </font>
+        <font weight="700" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifKannada-Regular">NotoSerifKannada-VF.ttf
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+    </family>
+    <family lang="und-Knda" variant="compact">
+        <font weight="400" style="normal" postScriptName="NotoSansKannadaUI-Regular">
+            NotoSansKannadaUI-VF.ttf
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="500" style="normal" postScriptName="NotoSansKannadaUI-Regular">
+            NotoSansKannadaUI-VF.ttf
+            <axis tag="wght" stylevalue="500"/>
+        </font>
+        <font weight="600" style="normal" postScriptName="NotoSansKannadaUI-Regular">
+            NotoSansKannadaUI-VF.ttf
+            <axis tag="wght" stylevalue="600"/>
+        </font>
+        <font weight="700" style="normal" postScriptName="NotoSansKannadaUI-Regular">
+            NotoSansKannadaUI-VF.ttf
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+    </family>
+    <family lang="und-Orya" variant="elegant">
+        <font weight="400" style="normal" postScriptName="NotoSansOriya">NotoSansOriya-Regular.ttf
+        </font>
+        <font weight="700" style="normal">NotoSansOriya-Bold.ttf</font>
+    </family>
+    <family lang="und-Orya" variant="compact">
+        <font weight="400" style="normal" postScriptName="NotoSansOriyaUI">
+            NotoSansOriyaUI-Regular.ttf
+        </font>
+        <font weight="700" style="normal">NotoSansOriyaUI-Bold.ttf</font>
+    </family>
+    <family lang="und-Sinh" variant="elegant">
+        <font weight="400" style="normal" postScriptName="NotoSansSinhala-Regular">
+            NotoSansSinhala-VF.ttf
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="500" style="normal" postScriptName="NotoSansSinhala-Regular">
+            NotoSansSinhala-VF.ttf
+            <axis tag="wght" stylevalue="500"/>
+        </font>
+        <font weight="600" style="normal" postScriptName="NotoSansSinhala-Regular">
+            NotoSansSinhala-VF.ttf
+            <axis tag="wght" stylevalue="600"/>
+        </font>
+        <font weight="700" style="normal" postScriptName="NotoSansSinhala-Regular">
+            NotoSansSinhala-VF.ttf
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+        <font weight="400" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifSinhala-Regular">NotoSerifSinhala-VF.ttf
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="500" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifSinhala-Regular">NotoSerifSinhala-VF.ttf
+            <axis tag="wght" stylevalue="500"/>
+        </font>
+        <font weight="600" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifSinhala-Regular">NotoSerifSinhala-VF.ttf
+            <axis tag="wght" stylevalue="600"/>
+        </font>
+        <font weight="700" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifSinhala-Regular">NotoSerifSinhala-VF.ttf
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+    </family>
+    <family lang="und-Sinh" variant="compact">
+        <font weight="400" style="normal" postScriptName="NotoSansSinhalaUI-Regular">
+            NotoSansSinhalaUI-VF.ttf
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="500" style="normal" postScriptName="NotoSansSinhalaUI-Regular">
+            NotoSansSinhalaUI-VF.ttf
+            <axis tag="wght" stylevalue="500"/>
+        </font>
+        <font weight="600" style="normal" postScriptName="NotoSansSinhalaUI-Regular">
+            NotoSansSinhalaUI-VF.ttf
+            <axis tag="wght" stylevalue="600"/>
+        </font>
+        <font weight="700" style="normal" postScriptName="NotoSansSinhalaUI-Regular">
+            NotoSansSinhalaUI-VF.ttf
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+    </family>
+    <family lang="und-Khmr" variant="elegant">
+        <font weight="100" style="normal" postScriptName="NotoSansKhmer-Regular">
+            NotoSansKhmer-VF.ttf
+            <axis tag="wdth" stylevalue="100.0"/>
+            <axis tag="wght" stylevalue="26.0"/>
+        </font>
+        <font weight="200" style="normal" postScriptName="NotoSansKhmer-Regular">
+            NotoSansKhmer-VF.ttf
+            <axis tag="wdth" stylevalue="100.0"/>
+            <axis tag="wght" stylevalue="39.0"/>
+        </font>
+        <font weight="300" style="normal" postScriptName="NotoSansKhmer-Regular">
+            NotoSansKhmer-VF.ttf
+            <axis tag="wdth" stylevalue="100.0"/>
+            <axis tag="wght" stylevalue="58.0"/>
+        </font>
+        <font weight="400" style="normal" postScriptName="NotoSansKhmer-Regular">
+            NotoSansKhmer-VF.ttf
+            <axis tag="wdth" stylevalue="100.0"/>
+            <axis tag="wght" stylevalue="90.0"/>
+        </font>
+        <font weight="500" style="normal" postScriptName="NotoSansKhmer-Regular">
+            NotoSansKhmer-VF.ttf
+            <axis tag="wdth" stylevalue="100.0"/>
+            <axis tag="wght" stylevalue="108.0"/>
+        </font>
+        <font weight="600" style="normal" postScriptName="NotoSansKhmer-Regular">
+            NotoSansKhmer-VF.ttf
+            <axis tag="wdth" stylevalue="100.0"/>
+            <axis tag="wght" stylevalue="128.0"/>
+        </font>
+        <font weight="700" style="normal" postScriptName="NotoSansKhmer-Regular">
+            NotoSansKhmer-VF.ttf
+            <axis tag="wdth" stylevalue="100.0"/>
+            <axis tag="wght" stylevalue="151.0"/>
+        </font>
+        <font weight="800" style="normal" postScriptName="NotoSansKhmer-Regular">
+            NotoSansKhmer-VF.ttf
+            <axis tag="wdth" stylevalue="100.0"/>
+            <axis tag="wght" stylevalue="169.0"/>
+        </font>
+        <font weight="900" style="normal" postScriptName="NotoSansKhmer-Regular">
+            NotoSansKhmer-VF.ttf
+            <axis tag="wdth" stylevalue="100.0"/>
+            <axis tag="wght" stylevalue="190.0"/>
+        </font>
+        <font weight="400" style="normal" fallbackFor="serif">NotoSerifKhmer-Regular.otf</font>
+        <font weight="700" style="normal" fallbackFor="serif">NotoSerifKhmer-Bold.otf</font>
+    </family>
+    <family lang="und-Khmr" variant="compact">
+        <font weight="400" style="normal" postScriptName="NotoSansKhmerUI">
+            NotoSansKhmerUI-Regular.ttf
+        </font>
+        <font weight="700" style="normal">NotoSansKhmerUI-Bold.ttf</font>
+    </family>
+    <family lang="und-Laoo" variant="elegant">
+        <font weight="400" style="normal">NotoSansLao-Regular.ttf
+        </font>
+        <font weight="700" style="normal">NotoSansLao-Bold.ttf</font>
+        <font weight="400" style="normal" fallbackFor="serif">
+            NotoSerifLao-Regular.ttf
+        </font>
+        <font weight="700" style="normal" fallbackFor="serif">NotoSerifLao-Bold.ttf</font>
+    </family>
+    <family lang="und-Laoo" variant="compact">
+        <font weight="400" style="normal" postScriptName="NotoSansLaoUI">NotoSansLaoUI-Regular.ttf
+        </font>
+        <font weight="700" style="normal">NotoSansLaoUI-Bold.ttf</font>
+    </family>
+    <family lang="und-Mymr" variant="elegant">
+        <font weight="400" style="normal">NotoSansMyanmar-Regular.otf</font>
+        <font weight="500" style="normal">NotoSansMyanmar-Medium.otf</font>
+        <font weight="700" style="normal">NotoSansMyanmar-Bold.otf</font>
+        <font weight="400" style="normal" fallbackFor="serif">NotoSerifMyanmar-Regular.otf</font>
+        <font weight="700" style="normal" fallbackFor="serif">NotoSerifMyanmar-Bold.otf</font>
+    </family>
+    <family lang="und-Mymr" variant="compact">
+        <font weight="400" style="normal">NotoSansMyanmarUI-Regular.otf</font>
+        <font weight="500" style="normal">NotoSansMyanmarUI-Medium.otf</font>
+        <font weight="700" style="normal">NotoSansMyanmarUI-Bold.otf</font>
+    </family>
+    <family lang="und-Thaa">
+        <font weight="400" style="normal" postScriptName="NotoSansThaana">
+            NotoSansThaana-Regular.ttf
+        </font>
+        <font weight="700" style="normal">NotoSansThaana-Bold.ttf</font>
+    </family>
+    <family lang="und-Cham">
+        <font weight="400" style="normal" postScriptName="NotoSansCham">NotoSansCham-Regular.ttf
+        </font>
+        <font weight="700" style="normal">NotoSansCham-Bold.ttf</font>
+    </family>
+    <family lang="und-Ahom">
+        <font weight="400" style="normal">NotoSansAhom-Regular.otf</font>
+    </family>
+    <family lang="und-Adlm">
+        <font weight="400" style="normal" postScriptName="NotoSansAdlam-Regular">
+            NotoSansAdlam-VF.ttf
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="500" style="normal" postScriptName="NotoSansAdlam-Regular">
+            NotoSansAdlam-VF.ttf
+            <axis tag="wght" stylevalue="500"/>
+        </font>
+        <font weight="600" style="normal" postScriptName="NotoSansAdlam-Regular">
+            NotoSansAdlam-VF.ttf
+            <axis tag="wght" stylevalue="600"/>
+        </font>
+        <font weight="700" style="normal" postScriptName="NotoSansAdlam-Regular">
+            NotoSansAdlam-VF.ttf
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+    </family>
+    <family lang="und-Avst">
+        <font weight="400" style="normal" postScriptName="NotoSansAvestan">
+            NotoSansAvestan-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Bali">
+        <font weight="400" style="normal" postScriptName="NotoSansBalinese">
+            NotoSansBalinese-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Bamu">
+        <font weight="400" style="normal" postScriptName="NotoSansBamum">NotoSansBamum-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Batk">
+        <font weight="400" style="normal" postScriptName="NotoSansBatak">NotoSansBatak-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Brah">
+        <font weight="400" style="normal" postScriptName="NotoSansBrahmi">
+            NotoSansBrahmi-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Bugi">
+        <font weight="400" style="normal" postScriptName="NotoSansBuginese">
+            NotoSansBuginese-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Buhd">
+        <font weight="400" style="normal" postScriptName="NotoSansBuhid">NotoSansBuhid-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Cans">
+        <font weight="400" style="normal">
+            NotoSansCanadianAboriginal-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Cari">
+        <font weight="400" style="normal" postScriptName="NotoSansCarian">
+            NotoSansCarian-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Cakm">
+        <font weight="400" style="normal">NotoSansChakma-Regular.otf</font>
+    </family>
+    <family lang="und-Cher">
+        <font weight="400" style="normal">NotoSansCherokee-Regular.ttf</font>
+    </family>
+    <family lang="und-Copt">
+        <font weight="400" style="normal" postScriptName="NotoSansCoptic">
+            NotoSansCoptic-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Xsux">
+        <font weight="400" style="normal" postScriptName="NotoSansCuneiform">
+            NotoSansCuneiform-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Cprt">
+        <font weight="400" style="normal" postScriptName="NotoSansCypriot">
+            NotoSansCypriot-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Dsrt">
+        <font weight="400" style="normal" postScriptName="NotoSansDeseret">
+            NotoSansDeseret-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Egyp">
+        <font weight="400" style="normal" postScriptName="NotoSansEgyptianHieroglyphs">
+            NotoSansEgyptianHieroglyphs-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Elba">
+        <font weight="400" style="normal">NotoSansElbasan-Regular.otf</font>
+    </family>
+    <family lang="und-Glag">
+        <font weight="400" style="normal" postScriptName="NotoSansGlagolitic">
+            NotoSansGlagolitic-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Goth">
+        <font weight="400" style="normal" postScriptName="NotoSansGothic">
+            NotoSansGothic-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Hano">
+        <font weight="400" style="normal" postScriptName="NotoSansHanunoo">
+            NotoSansHanunoo-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Armi">
+        <font weight="400" style="normal" postScriptName="NotoSansImperialAramaic">
+            NotoSansImperialAramaic-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Phli">
+        <font weight="400" style="normal" postScriptName="NotoSansInscriptionalPahlavi">
+            NotoSansInscriptionalPahlavi-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Prti">
+        <font weight="400" style="normal" postScriptName="NotoSansInscriptionalParthian">
+            NotoSansInscriptionalParthian-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Java">
+        <font weight="400" style="normal">NotoSansJavanese-Regular.otf</font>
+    </family>
+    <family lang="und-Kthi">
+        <font weight="400" style="normal" postScriptName="NotoSansKaithi">
+            NotoSansKaithi-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Kali">
+        <font weight="400" style="normal" postScriptName="NotoSansKayahLi">
+            NotoSansKayahLi-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Khar">
+        <font weight="400" style="normal" postScriptName="NotoSansKharoshthi">
+            NotoSansKharoshthi-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Lepc">
+        <font weight="400" style="normal" postScriptName="NotoSansLepcha">
+            NotoSansLepcha-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Limb">
+        <font weight="400" style="normal" postScriptName="NotoSansLimbu">NotoSansLimbu-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Linb">
+        <font weight="400" style="normal" postScriptName="NotoSansLinearB">
+            NotoSansLinearB-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Lisu">
+        <font weight="400" style="normal" postScriptName="NotoSansLisu">NotoSansLisu-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Lyci">
+        <font weight="400" style="normal" postScriptName="NotoSansLycian">
+            NotoSansLycian-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Lydi">
+        <font weight="400" style="normal" postScriptName="NotoSansLydian">
+            NotoSansLydian-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Mand">
+        <font weight="400" style="normal" postScriptName="NotoSansMandaic">
+            NotoSansMandaic-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Mtei">
+        <font weight="400" style="normal" postScriptName="NotoSansMeeteiMayek">
+            NotoSansMeeteiMayek-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Talu">
+        <font weight="400" style="normal" postScriptName="NotoSansNewTaiLue">
+            NotoSansNewTaiLue-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Nkoo">
+        <font weight="400" style="normal" postScriptName="NotoSansNKo">NotoSansNKo-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Ogam">
+        <font weight="400" style="normal" postScriptName="NotoSansOgham">NotoSansOgham-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Olck">
+        <font weight="400" style="normal" postScriptName="NotoSansOlChiki">
+            NotoSansOlChiki-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Ital">
+        <font weight="400" style="normal" postScriptName="NotoSansOldItalic">
+            NotoSansOldItalic-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Xpeo">
+        <font weight="400" style="normal" postScriptName="NotoSansOldPersian">
+            NotoSansOldPersian-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Sarb">
+        <font weight="400" style="normal" postScriptName="NotoSansOldSouthArabian">
+            NotoSansOldSouthArabian-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Orkh">
+        <font weight="400" style="normal" postScriptName="NotoSansOldTurkic">
+            NotoSansOldTurkic-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Osge">
+        <font weight="400" style="normal">NotoSansOsage-Regular.ttf</font>
+    </family>
+    <family lang="und-Osma">
+        <font weight="400" style="normal" postScriptName="NotoSansOsmanya">
+            NotoSansOsmanya-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Phnx">
+        <font weight="400" style="normal" postScriptName="NotoSansPhoenician">
+            NotoSansPhoenician-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Rjng">
+        <font weight="400" style="normal" postScriptName="NotoSansRejang">
+            NotoSansRejang-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Runr">
+        <font weight="400" style="normal" postScriptName="NotoSansRunic">NotoSansRunic-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Samr">
+        <font weight="400" style="normal" postScriptName="NotoSansSamaritan">
+            NotoSansSamaritan-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Saur">
+        <font weight="400" style="normal" postScriptName="NotoSansSaurashtra">
+            NotoSansSaurashtra-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Shaw">
+        <font weight="400" style="normal" postScriptName="NotoSansShavian">
+            NotoSansShavian-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Sund">
+        <font weight="400" style="normal" postScriptName="NotoSansSundanese">
+            NotoSansSundanese-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Sylo">
+        <font weight="400" style="normal" postScriptName="NotoSansSylotiNagri">
+            NotoSansSylotiNagri-Regular.ttf
+        </font>
+    </family>
+    <!-- Esrangela should precede Eastern and Western Syriac, since it's our default form. -->
+    <family lang="und-Syre">
+        <font weight="400" style="normal" postScriptName="NotoSansSyriacEstrangela">
+            NotoSansSyriacEstrangela-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Syrn">
+        <font weight="400" style="normal" postScriptName="NotoSansSyriacEastern">
+            NotoSansSyriacEastern-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Syrj">
+        <font weight="400" style="normal" postScriptName="NotoSansSyriacWestern">
+            NotoSansSyriacWestern-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Tglg">
+        <font weight="400" style="normal" postScriptName="NotoSansTagalog">
+            NotoSansTagalog-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Tagb">
+        <font weight="400" style="normal" postScriptName="NotoSansTagbanwa">
+            NotoSansTagbanwa-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Lana">
+        <font weight="400" style="normal" postScriptName="NotoSansTaiTham">
+            NotoSansTaiTham-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Tavt">
+        <font weight="400" style="normal" postScriptName="NotoSansTaiViet">
+            NotoSansTaiViet-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Tibt">
+        <font weight="400" style="normal" postScriptName="NotoSerifTibetan-Regular">
+            NotoSerifTibetan-VF.ttf
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="500" style="normal" postScriptName="NotoSerifTibetan-Regular">
+            NotoSerifTibetan-VF.ttf
+            <axis tag="wght" stylevalue="500"/>
+        </font>
+        <font weight="600" style="normal" postScriptName="NotoSerifTibetan-Regular">
+            NotoSerifTibetan-VF.ttf
+            <axis tag="wght" stylevalue="600"/>
+        </font>
+        <font weight="700" style="normal" postScriptName="NotoSerifTibetan-Regular">
+            NotoSerifTibetan-VF.ttf
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+    </family>
+    <family lang="und-Tfng">
+        <font weight="400" style="normal">NotoSansTifinagh-Regular.otf</font>
+    </family>
+    <family lang="und-Ugar">
+        <font weight="400" style="normal" postScriptName="NotoSansUgaritic">
+            NotoSansUgaritic-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Vaii">
+        <font weight="400" style="normal" postScriptName="NotoSansVai">NotoSansVai-Regular.ttf
+        </font>
+    </family>
+    <family>
+        <font weight="400" style="normal">NotoSansSymbols-Regular-Subsetted.ttf</font>
+    </family>
+    <family lang="zh-Hans">
+        <font weight="400" style="normal" index="2" postScriptName="NotoSansCJKjp-Regular">
+            NotoSansCJK-Regular.ttc
+        </font>
+        <font weight="400" style="normal" index="2" fallbackFor="serif"
+              postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc
+        </font>
+    </family>
+    <family lang="zh-Hant,zh-Bopo">
+        <font weight="400" style="normal" index="3" postScriptName="NotoSansCJKjp-Regular">
+            NotoSansCJK-Regular.ttc
+        </font>
+        <font weight="400" style="normal" index="3" fallbackFor="serif"
+              postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc
+        </font>
+    </family>
+    <family lang="ja">
+        <font weight="400" style="normal" index="0" postScriptName="NotoSansCJKjp-Regular">
+            NotoSansCJK-Regular.ttc
+        </font>
+        <font weight="400" style="normal" index="0" fallbackFor="serif"
+              postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc
+        </font>
+    </family>
+    <family lang="ko">
+        <font weight="400" style="normal" index="1" postScriptName="NotoSansCJKjp-Regular">
+            NotoSansCJK-Regular.ttc
+        </font>
+        <font weight="400" style="normal" index="1" fallbackFor="serif"
+              postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc
+        </font>
+    </family>
+    <family lang="und-Zsye">
+        <font weight="400" style="normal">NotoColorEmoji.ttf</font>
+    </family>
+    <family lang="und-Zsye">
+        <font weight="400" style="normal">NotoColorEmojiFlags.ttf</font>
+    </family>
+    <family lang="und-Zsym">
+        <font weight="400" style="normal">NotoSansSymbols-Regular-Subsetted2.ttf</font>
+    </family>
+    <!--
+        Tai Le, Yi, Mongolian, and Phags-pa are intentionally kept last, to make sure they don't
+        override the East Asian punctuation for Chinese.
+    -->
+    <family lang="und-Tale">
+        <font weight="400" style="normal" postScriptName="NotoSansTaiLe">NotoSansTaiLe-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Yiii">
+        <font weight="400" style="normal" postScriptName="NotoSansYi">NotoSansYi-Regular.ttf</font>
+    </family>
+    <family lang="und-Mong">
+        <font weight="400" style="normal" postScriptName="NotoSansMongolian">
+            NotoSansMongolian-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Phag">
+        <font weight="400" style="normal" postScriptName="NotoSansPhagsPa">
+            NotoSansPhagsPa-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Hluw">
+        <font weight="400" style="normal">NotoSansAnatolianHieroglyphs-Regular.otf</font>
+    </family>
+    <family lang="und-Bass">
+        <font weight="400" style="normal">NotoSansBassaVah-Regular.otf</font>
+    </family>
+    <family lang="und-Bhks">
+        <font weight="400" style="normal">NotoSansBhaiksuki-Regular.otf</font>
+    </family>
+    <family lang="und-Hatr">
+        <font weight="400" style="normal">NotoSansHatran-Regular.otf</font>
+    </family>
+    <family lang="und-Lina">
+        <font weight="400" style="normal">NotoSansLinearA-Regular.otf</font>
+    </family>
+    <family lang="und-Mani">
+        <font weight="400" style="normal">NotoSansManichaean-Regular.otf</font>
+    </family>
+    <family lang="und-Marc">
+        <font weight="400" style="normal">NotoSansMarchen-Regular.otf</font>
+    </family>
+    <family lang="und-Merc">
+        <font weight="400" style="normal">NotoSansMeroitic-Regular.otf</font>
+    </family>
+    <family lang="und-Plrd">
+        <font weight="400" style="normal">NotoSansMiao-Regular.otf</font>
+    </family>
+    <family lang="und-Mroo">
+        <font weight="400" style="normal">NotoSansMro-Regular.otf</font>
+    </family>
+    <family lang="und-Mult">
+        <font weight="400" style="normal">NotoSansMultani-Regular.otf</font>
+    </family>
+    <family lang="und-Nbat">
+        <font weight="400" style="normal">NotoSansNabataean-Regular.otf</font>
+    </family>
+    <family lang="und-Newa">
+        <font weight="400" style="normal">NotoSansNewa-Regular.otf</font>
+    </family>
+    <family lang="und-Narb">
+        <font weight="400" style="normal">NotoSansOldNorthArabian-Regular.otf</font>
+    </family>
+    <family lang="und-Perm">
+        <font weight="400" style="normal">NotoSansOldPermic-Regular.otf</font>
+    </family>
+    <family lang="und-Hmng">
+        <font weight="400" style="normal">NotoSansPahawhHmong-Regular.otf</font>
+    </family>
+    <family lang="und-Palm">
+        <font weight="400" style="normal">NotoSansPalmyrene-Regular.otf</font>
+    </family>
+    <family lang="und-Pauc">
+        <font weight="400" style="normal">NotoSansPauCinHau-Regular.otf</font>
+    </family>
+    <family lang="und-Shrd">
+        <font weight="400" style="normal">NotoSansSharada-Regular.otf</font>
+    </family>
+    <family lang="und-Sora">
+        <font weight="400" style="normal">NotoSansSoraSompeng-Regular.otf</font>
+    </family>
+    <family lang="und-Gong">
+        <font weight="400" style="normal">NotoSansGunjalaGondi-Regular.otf</font>
+    </family>
+    <family lang="und-Rohg">
+        <font weight="400" style="normal">NotoSansHanifiRohingya-Regular.otf</font>
+    </family>
+    <family lang="und-Khoj">
+        <font weight="400" style="normal">NotoSansKhojki-Regular.otf</font>
+    </family>
+    <family lang="und-Gonm">
+        <font weight="400" style="normal">NotoSansMasaramGondi-Regular.otf</font>
+    </family>
+    <family lang="und-Wcho">
+        <font weight="400" style="normal">NotoSansWancho-Regular.otf</font>
+    </family>
+    <family lang="und-Wara">
+        <font weight="400" style="normal">NotoSansWarangCiti-Regular.otf</font>
+    </family>
+    <family lang="und-Gran">
+        <font weight="400" style="normal">NotoSansGrantha-Regular.ttf</font>
+    </family>
+    <family lang="und-Modi">
+        <font weight="400" style="normal">NotoSansModi-Regular.ttf</font>
+    </family>
+    <family lang="und-Dogr">
+        <font weight="400" style="normal">NotoSerifDogra-Regular.ttf</font>
+    </family>
+    <family lang="und-Medf">
+        <font weight="400" style="normal" postScriptName="NotoSansMedefaidrin-Regular">
+            NotoSansMedefaidrin-VF.ttf
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="500" style="normal" postScriptName="NotoSansMedefaidrin-Regular">
+            NotoSansMedefaidrin-VF.ttf
+            <axis tag="wght" stylevalue="500"/>
+        </font>
+        <font weight="600" style="normal" postScriptName="NotoSansMedefaidrin-Regular">
+            NotoSansMedefaidrin-VF.ttf
+            <axis tag="wght" stylevalue="600"/>
+        </font>
+        <font weight="700" style="normal" postScriptName="NotoSansMedefaidrin-Regular">
+            NotoSansMedefaidrin-VF.ttf
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+    </family>
+    <family lang="und-Soyo">
+        <font weight="400" style="normal" postScriptName="NotoSansSoyombo-Regular">
+            NotoSansSoyombo-VF.ttf
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="500" style="normal" postScriptName="NotoSansSoyombo-Regular">
+            NotoSansSoyombo-VF.ttf
+            <axis tag="wght" stylevalue="500"/>
+        </font>
+        <font weight="600" style="normal" postScriptName="NotoSansSoyombo-Regular">
+            NotoSansSoyombo-VF.ttf
+            <axis tag="wght" stylevalue="600"/>
+        </font>
+        <font weight="700" style="normal" postScriptName="NotoSansSoyombo-Regular">
+            NotoSansSoyombo-VF.ttf
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+    </family>
+    <family lang="und-Takr">
+        <font weight="400" style="normal" postScriptName="NotoSansTakri-Regular">
+            NotoSansTakri-VF.ttf
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="500" style="normal" postScriptName="NotoSansTakri-Regular">
+            NotoSansTakri-VF.ttf
+            <axis tag="wght" stylevalue="500"/>
+        </font>
+        <font weight="600" style="normal" postScriptName="NotoSansTakri-Regular">
+            NotoSansTakri-VF.ttf
+            <axis tag="wght" stylevalue="600"/>
+        </font>
+        <font weight="700" style="normal" postScriptName="NotoSansTakri-Regular">
+            NotoSansTakri-VF.ttf
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+    </family>
+    <family lang="und-Hmnp">
+        <font weight="400" style="normal" postScriptName="NotoSerifHmongNyiakeng-Regular">
+            NotoSerifNyiakengPuachueHmong-VF.ttf
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="500" style="normal" postScriptName="NotoSerifHmongNyiakeng-Regular">
+            NotoSerifNyiakengPuachueHmong-VF.ttf
+            <axis tag="wght" stylevalue="500"/>
+        </font>
+        <font weight="600" style="normal" postScriptName="NotoSerifHmongNyiakeng-Regular">
+            NotoSerifNyiakengPuachueHmong-VF.ttf
+            <axis tag="wght" stylevalue="600"/>
+        </font>
+        <font weight="700" style="normal" postScriptName="NotoSerifHmongNyiakeng-Regular">
+            NotoSerifNyiakengPuachueHmong-VF.ttf
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+    </family>
+    <family lang="und-Yezi">
+        <font weight="400" style="normal" postScriptName="NotoSerifYezidi-Regular">
+            NotoSerifYezidi-VF.ttf
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="500" style="normal" postScriptName="NotoSerifYezidi-Regular">
+            NotoSerifYezidi-VF.ttf
+            <axis tag="wght" stylevalue="500"/>
+        </font>
+        <font weight="600" style="normal" postScriptName="NotoSerifYezidi-Regular">
+            NotoSerifYezidi-VF.ttf
+            <axis tag="wght" stylevalue="600"/>
+        </font>
+        <font weight="700" style="normal" postScriptName="NotoSerifYezidi-Regular">
+            NotoSerifYezidi-VF.ttf
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+    </family>
+</familyset>
diff --git a/data/fonts/fonts.mk b/data/fonts/fonts.mk
index e884f2f..5d1cc66 100644
--- a/data/fonts/fonts.mk
+++ b/data/fonts/fonts.mk
@@ -17,4 +17,5 @@
 PRODUCT_PACKAGES := \
     DroidSansMono.ttf \
     AndroidClock.ttf \
+    font_fallback.xml \
     fonts.xml
diff --git a/data/fonts/fonts.xml b/data/fonts/fonts.xml
index 0563519..9320c14 100644
--- a/data/fonts/fonts.xml
+++ b/data/fonts/fonts.xml
@@ -1,5 +1,17 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
+    DEPRECATED: This XML file is no longer a source of the font files installed
+    in the system.
+
+    For the device vendors: please add your font configurations to the
+    platform/frameworks/base/data/font_fallback.xml and also add it to this XML
+    file as much as possible for apps that reads this XML file.
+
+    For the application developers: please stop reading this XML file and use
+    android.graphics.fonts.SystemFonts#getAvailableFonts Java API or
+    ASystemFontIterator_open NDK API for getting list of system installed
+    font files.
+
     WARNING: Parsing of this file by third-party apps is not supported. The
     file, and the font files it refers to, will be renamed and/or moved out
     from their respective location in the next Android release, and/or the
@@ -277,6 +289,99 @@
     </family>
     <alias name="source-sans-pro-semi-bold" to="source-sans-pro" weight="600"/>
 
+    <family name="roboto-flex">
+        <font weight="100" style="normal">RobotoFlex-Regular.ttf
+          <axis tag="slnt" stylevalue="0" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="100" />
+        </font>
+        <font weight="200" style="normal">RobotoFlex-Regular.ttf
+          <axis tag="slnt" stylevalue="0" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="200" />
+        </font>
+        <font weight="300" style="normal">RobotoFlex-Regular.ttf
+          <axis tag="slnt" stylevalue="0" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="300" />
+        </font>
+        <font weight="400" style="normal">RobotoFlex-Regular.ttf
+          <axis tag="slnt" stylevalue="0" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="400" />
+        </font>
+        <font weight="500" style="normal">RobotoFlex-Regular.ttf
+          <axis tag="slnt" stylevalue="0" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="500" />
+        </font>
+        <font weight="600" style="normal">RobotoFlex-Regular.ttf
+          <axis tag="slnt" stylevalue="0" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="600" />
+        </font>
+        <font weight="700" style="normal">RobotoFlex-Regular.ttf
+          <axis tag="slnt" stylevalue="0" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="700" />
+        </font>
+        <font weight="800" style="normal">RobotoFlex-Regular.ttf
+          <axis tag="slnt" stylevalue="0" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="800" />
+        </font>
+        <font weight="900" style="normal">RobotoFlex-Regular.ttf
+          <axis tag="slnt" stylevalue="0" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="900" />
+        </font>
+        <font weight="100" style="italic">RobotoFlex-Regular.ttf
+          <axis tag="slnt" stylevalue="-10" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="100" />
+        </font>
+        <font weight="200" style="italic">RobotoFlex-Regular.ttf
+          <axis tag="slnt" stylevalue="-10" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="200" />
+        </font>
+        <font weight="300" style="italic">RobotoFlex-Regular.ttf
+          <axis tag="slnt" stylevalue="-10" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="300" />
+        </font>
+        <font weight="400" style="italic">RobotoFlex-Regular.ttf
+          <axis tag="slnt" stylevalue="-10" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="400" />
+        </font>
+        <font weight="500" style="italic">RobotoFlex-Regular.ttf
+          <axis tag="slnt" stylevalue="-10" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="500" />
+        </font>
+        <font weight="600" style="italic">RobotoFlex-Regular.ttf
+          <axis tag="slnt" stylevalue="-10" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="600" />
+        </font>
+        <font weight="700" style="italic">RobotoFlex-Regular.ttf
+          <axis tag="slnt" stylevalue="-10" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="700" />
+        </font>
+        <font weight="800" style="italic">RobotoFlex-Regular.ttf
+          <axis tag="slnt" stylevalue="-10" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="800" />
+        </font>
+        <font weight="900" style="italic">RobotoFlex-Regular.ttf
+          <axis tag="slnt" stylevalue="-10" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="900" />
+        </font>
+    </family>
+
     <!-- fallback fonts -->
     <family lang="und-Arab" variant="elegant">
         <font weight="400" style="normal" postScriptName="NotoNaskhArabic">
diff --git a/data/keyboards/Vendor_0957_Product_0001.kl b/data/keyboards/Vendor_0957_Product_0001.kl
index 5d7fd85..1da3ba6 100644
--- a/data/keyboards/Vendor_0957_Product_0001.kl
+++ b/data/keyboards/Vendor_0957_Product_0001.kl
@@ -45,7 +45,7 @@
 
 # custom keys
 key usage 0x000c01BB    TV_INPUT
-key usage 0x000c0186    MACRO_1
+key usage 0x000c0186    MACRO_1     WAKE
 
 key usage 0x000c0185    TV_TELETEXT
 key usage 0x000c0061    CAPTIONS
diff --git a/errorprone/Android.bp b/errorprone/Android.bp
index 8f32f0e..ad08026 100644
--- a/errorprone/Android.bp
+++ b/errorprone/Android.bp
@@ -21,6 +21,8 @@
     srcs: ["java/**/*.java"],
 
     static_libs: [
+        "annotations",
+        "framework-annotations-lib",
         "//external/error_prone:error_prone_core",
     ],
 
diff --git a/errorprone/java/android/annotation/RequiresNoPermission.java b/errorprone/java/android/annotation/RequiresNoPermission.java
deleted file mode 100644
index 6ff4d6e..0000000
--- a/errorprone/java/android/annotation/RequiresNoPermission.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.annotation;
-
-import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
-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.PARAMETER;
-import static java.lang.annotation.RetentionPolicy.CLASS;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.Target;
-
-/**
- * Denotes that the annotated element requires no permissions.
- *
- * @hide
- */
-@Retention(CLASS)
-@Target({ANNOTATION_TYPE,METHOD,CONSTRUCTOR,FIELD,PARAMETER})
-public @interface RequiresNoPermission {
-}
diff --git a/errorprone/java/android/annotation/RequiresPermission.java b/errorprone/java/android/annotation/RequiresPermission.java
deleted file mode 100644
index 303ab41..0000000
--- a/errorprone/java/android/annotation/RequiresPermission.java
+++ /dev/null
@@ -1,136 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.annotation;
-
-import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
-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.PARAMETER;
-import static java.lang.annotation.RetentionPolicy.CLASS;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.Target;
-
-/**
- * Denotes that the annotated element requires (or may require) one or more permissions.
- * <p/>
- * Example of requiring a single permission:
- * <pre>{@code
- *   {@literal @}RequiresPermission(Manifest.permission.SET_WALLPAPER)
- *   public abstract void setWallpaper(Bitmap bitmap) throws IOException;
- *
- *   {@literal @}RequiresPermission(ACCESS_COARSE_LOCATION)
- *   public abstract Location getLastKnownLocation(String provider);
- * }</pre>
- * Example of requiring at least one permission from a set:
- * <pre>{@code
- *   {@literal @}RequiresPermission(anyOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION})
- *   public abstract Location getLastKnownLocation(String provider);
- * }</pre>
- * Example of requiring multiple permissions:
- * <pre>{@code
- *   {@literal @}RequiresPermission(allOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION})
- *   public abstract Location getLastKnownLocation(String provider);
- * }</pre>
- * Example of requiring separate read and write permissions for a content provider:
- * <pre>{@code
- *   {@literal @}RequiresPermission.Read(@RequiresPermission(READ_HISTORY_BOOKMARKS))
- *   {@literal @}RequiresPermission.Write(@RequiresPermission(WRITE_HISTORY_BOOKMARKS))
- *   public static final Uri BOOKMARKS_URI = Uri.parse("content://browser/bookmarks");
- * }</pre>
- * <p>
- * When specified on a parameter, the annotation indicates that the method requires
- * a permission which depends on the value of the parameter. For example, consider
- * {@link android.app.Activity#startActivity(android.content.Intent)
- * Activity#startActivity(Intent)}:
- * <pre>{@code
- *   public void startActivity(@RequiresPermission Intent intent) { ... }
- * }</pre>
- * Notice how there are no actual permission names listed in the annotation. The actual
- * permissions required will depend on the particular intent passed in. For example,
- * the code may look like this:
- * <pre>{@code
- *   Intent intent = new Intent(Intent.ACTION_CALL);
- *   startActivity(intent);
- * }</pre>
- * and the actual permission requirement for this particular intent is described on
- * the Intent name itself:
- * <pre>{@code
- *   {@literal @}RequiresPermission(Manifest.permission.CALL_PHONE)
- *   public static final String ACTION_CALL = "android.intent.action.CALL";
- * }</pre>
- *
- * @hide
- */
-@Retention(CLASS)
-@Target({ANNOTATION_TYPE,METHOD,CONSTRUCTOR,FIELD,PARAMETER})
-public @interface RequiresPermission {
-    /**
-     * The name of the permission that is required, if precisely one permission
-     * is required. If more than one permission is required, specify either
-     * {@link #allOf()} or {@link #anyOf()} instead.
-     * <p>
-     * If specified, {@link #anyOf()} and {@link #allOf()} must both be null.
-     */
-    String value() default "";
-
-    /**
-     * Specifies a list of permission names that are all required.
-     * <p>
-     * If specified, {@link #anyOf()} and {@link #value()} must both be null.
-     */
-    String[] allOf() default {};
-
-    /**
-     * Specifies a list of permission names where at least one is required
-     * <p>
-     * If specified, {@link #allOf()} and {@link #value()} must both be null.
-     */
-    String[] anyOf() default {};
-
-    /**
-     * If true, the permission may not be required in all cases (e.g. it may only be
-     * enforced on certain platforms, or for certain call parameters, etc.
-     */
-    boolean conditional() default false;
-
-    /**
-     * Specifies that the given permission is required for read operations.
-     * <p>
-     * When specified on a parameter, the annotation indicates that the method requires
-     * a permission which depends on the value of the parameter (and typically
-     * the corresponding field passed in will be one of a set of constants which have
-     * been annotated with a <code>@RequiresPermission</code> annotation.)
-     */
-    @Target({FIELD, METHOD, PARAMETER})
-    @interface Read {
-        RequiresPermission value() default @RequiresPermission;
-    }
-
-    /**
-     * Specifies that the given permission is required for write operations.
-     * <p>
-     * When specified on a parameter, the annotation indicates that the method requires
-     * a permission which depends on the value of the parameter (and typically
-     * the corresponding field passed in will be one of a set of constants which have
-     * been annotated with a <code>@RequiresPermission</code> annotation.)
-     */
-    @Target({FIELD, METHOD, PARAMETER})
-    @interface Write {
-        RequiresPermission value() default @RequiresPermission;
-    }
-}
diff --git a/errorprone/java/android/annotation/SdkConstant.java b/errorprone/java/android/annotation/SdkConstant.java
deleted file mode 100644
index 0a53186..0000000
--- a/errorprone/java/android/annotation/SdkConstant.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.annotation;
-
-import java.lang.annotation.Target;
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-/**
- * Indicates a constant field value should be exported to be used in the SDK tools.
- * @hide
- */
-@Target({ ElementType.FIELD })
-@Retention(RetentionPolicy.SOURCE)
-public @interface SdkConstant {
-    public static enum SdkConstantType {
-        ACTIVITY_INTENT_ACTION, BROADCAST_INTENT_ACTION, SERVICE_ACTION, INTENT_CATEGORY, FEATURE;
-    }
-
-    SdkConstantType value();
-}
diff --git a/errorprone/java/android/annotation/SuppressLint.java b/errorprone/java/android/annotation/SuppressLint.java
deleted file mode 100644
index 2d3456b..0000000
--- a/errorprone/java/android/annotation/SuppressLint.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.annotation;
-
-import static java.lang.annotation.ElementType.CONSTRUCTOR;
-import static java.lang.annotation.ElementType.FIELD;
-import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
-import static java.lang.annotation.ElementType.METHOD;
-import static java.lang.annotation.ElementType.PARAMETER;
-import static java.lang.annotation.ElementType.TYPE;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/** Indicates that Lint should ignore the specified warnings for the annotated element. */
-@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
-@Retention(RetentionPolicy.CLASS)
-public @interface SuppressLint {
-    /**
-     * The set of warnings (identified by the lint issue id) that should be
-     * ignored by lint. It is not an error to specify an unrecognized name.
-     */
-    String[] value();
-}
diff --git a/errorprone/java/com/google/errorprone/bugpatterns/android/RequiresPermissionChecker.java b/errorprone/java/com/google/errorprone/bugpatterns/android/RequiresPermissionChecker.java
index d39d4b4..7c7cb18 100644
--- a/errorprone/java/com/google/errorprone/bugpatterns/android/RequiresPermissionChecker.java
+++ b/errorprone/java/com/google/errorprone/bugpatterns/android/RequiresPermissionChecker.java
@@ -27,6 +27,7 @@
 import static com.google.errorprone.matchers.Matchers.methodIsNamed;
 import static com.google.errorprone.matchers.Matchers.staticMethod;
 
+import android.annotation.EnforcePermission;
 import android.annotation.RequiresPermission;
 import android.annotation.SuppressLint;
 
@@ -290,6 +291,13 @@
             if (perm.anyOf() != null) this.anyOf.addAll(Arrays.asList(perm.anyOf()));
         }
 
+        public void addAll(EnforcePermission perm) {
+            if (perm == null) return;
+            if (!perm.value().isEmpty()) this.allOf.add(perm.value());
+            if (perm.allOf() != null) this.allOf.addAll(Arrays.asList(perm.allOf()));
+            if (perm.anyOf() != null) this.anyOf.addAll(Arrays.asList(perm.anyOf()));
+        }
+
         public void addConstValue(Tree tree) {
             final Object value = ASTHelpers.constValue(tree);
             if (value != null) {
@@ -416,14 +424,20 @@
             final ParsedRequiresPermission res = new ParsedRequiresPermission();
             res.allOf.add(String.valueOf(ASTHelpers.constValue(tree.getArguments().get(0))));
             return res;
-        } else if (ENFORCE_VIA_CHECKER.matches(tree, state) && tree.getArguments().size() > 1) {
+        }
+        if (ENFORCE_VIA_CHECKER.matches(tree, state) && tree.getArguments().size() > 1) {
             final ParsedRequiresPermission res = new ParsedRequiresPermission();
             res.allOf.add(String.valueOf(ASTHelpers.constValue(tree.getArguments().get(1))));
             return res;
-        } else {
-            final MethodSymbol method = ASTHelpers.getSymbol(tree);
-            return parseRequiresPermissionRecursively(method, state);
         }
+        final MethodSymbol method = ASTHelpers.getSymbol(tree);
+        final EnforcePermission enforced = method.getAnnotation(EnforcePermission.class);
+        if (enforced != null) {
+            final ParsedRequiresPermission res = new ParsedRequiresPermission();
+            res.addAll(enforced);
+            return res;
+        }
+        return parseRequiresPermissionRecursively(method, state);
     }
 
     /**
diff --git a/errorprone/tests/java/com/google/errorprone/bugpatterns/android/RequiresPermissionCheckerTest.java b/errorprone/tests/java/com/google/errorprone/bugpatterns/android/RequiresPermissionCheckerTest.java
index 38831b1..e53372d 100644
--- a/errorprone/tests/java/com/google/errorprone/bugpatterns/android/RequiresPermissionCheckerTest.java
+++ b/errorprone/tests/java/com/google/errorprone/bugpatterns/android/RequiresPermissionCheckerTest.java
@@ -438,4 +438,43 @@
                         "}")
                 .doTest();
     }
+
+    @Test
+    public void testEnforce() {
+        compilationHelper
+                .addSourceFile("/android/annotation/EnforcePermission.java")
+                .addSourceFile("/android/content/Context.java")
+                .addSourceFile("/android/foo/IBarService.java")
+                .addSourceFile("/android/os/IInterface.java")
+                .addSourceLines("BarService.java",
+                        "import android.annotation.EnforcePermission;",
+                        "import android.foo.IBarService;",
+                        "class BarService extends IBarService.Stub {",
+                        "  @Override",
+                        "  @EnforcePermission(\"INTERNET\")",
+                        "  public void bar() {",
+                        "    bar_enforcePermission();",
+                        "  }",
+                        "}")
+                .addSourceLines("BarManager.java",
+                        "import android.annotation.RequiresPermission;",
+                        "class BarManager {",
+                        "  BarService mService;",
+                        "  @RequiresPermission(\"INTERNET\")",
+                        "  public void callBar() {",
+                        "    mService.bar();",
+                        "  }",
+                        "  @RequiresPermission(\"NONE\")",
+                        "  public void callBarDifferent() {",
+                        "    // BUG: Diagnostic contains:",
+                        "    mService.bar();",
+                        "  }",
+                        "  public void callBarMissing() {",
+                        "    // BUG: Diagnostic contains:",
+                        "    mService.bar();",
+                        "  }",
+                        "}")
+                .doTest();
+    }
+
 }
diff --git a/errorprone/tests/res/android/annotation/EnforcePermission.java b/errorprone/tests/res/android/annotation/EnforcePermission.java
new file mode 100644
index 0000000..26644a2
--- /dev/null
+++ b/errorprone/tests/res/android/annotation/EnforcePermission.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 android.annotation;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.CLASS;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+@Retention(CLASS)
+@Target({METHOD})
+public @interface EnforcePermission {
+    String value() default "";
+    String[] allOf() default {};
+    String[] anyOf() default {};
+}
diff --git a/errorprone/tests/res/android/foo/IBarService.java b/errorprone/tests/res/android/foo/IBarService.java
new file mode 100644
index 0000000..058d026
--- /dev/null
+++ b/errorprone/tests/res/android/foo/IBarService.java
@@ -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 android.foo;
+
+import android.annotation.EnforcePermission;
+
+public interface IBarService extends android.os.IInterface {
+    @EnforcePermission("INTERNET")
+    void bar();
+
+    abstract class Stub implements IBarService {
+        public void bar_enforcePermission() { }
+    }
+}
diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java
index b9d3756..a4c655c8c 100644
--- a/graphics/java/android/graphics/Bitmap.java
+++ b/graphics/java/android/graphics/Bitmap.java
@@ -782,10 +782,13 @@
     @Nullable
     public static Bitmap wrapHardwareBuffer(@NonNull HardwareBuffer hardwareBuffer,
             @Nullable ColorSpace colorSpace) {
-        if ((hardwareBuffer.getUsage() & HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE) == 0) {
+        final long usage = hardwareBuffer.getUsage();
+        if ((usage & HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE) == 0) {
             throw new IllegalArgumentException("usage flags must contain USAGE_GPU_SAMPLED_IMAGE.");
         }
-        int format = hardwareBuffer.getFormat();
+        if ((usage & HardwareBuffer.USAGE_PROTECTED_CONTENT) != 0) {
+            throw new IllegalArgumentException("Bitmap is not compatible with protected buffers");
+        }
         if (colorSpace == null) {
             colorSpace = ColorSpace.get(ColorSpace.Named.SRGB);
         }
diff --git a/graphics/java/android/graphics/ColorFilter.java b/graphics/java/android/graphics/ColorFilter.java
index 8fd6f7f..7050325 100644
--- a/graphics/java/android/graphics/ColorFilter.java
+++ b/graphics/java/android/graphics/ColorFilter.java
@@ -41,21 +41,11 @@
      * Current native SkColorFilter instance.
      */
     private long mNativeInstance;
-    // Runnable to do immediate destruction
-    private Runnable mCleaner;
 
     long createNativeInstance() {
         return 0;
     }
 
-    synchronized final void discardNativeInstance() {
-        if (mNativeInstance != 0) {
-            mCleaner.run();
-            mCleaner = null;
-            mNativeInstance = 0;
-        }
-    }
-
     /** @hide */
     public synchronized final long getNativeInstance() {
         if (mNativeInstance == 0) {
@@ -65,8 +55,7 @@
                 // Note: we must check for null here, since it's possible for createNativeInstance()
                 // to return nullptr if the native SkColorFilter would be a no-op at draw time.
                 // See native implementations of subclass create methods for more info.
-                mCleaner = NoImagePreloadHolder.sRegistry.registerNativeAllocation(
-                        this, mNativeInstance);
+                NoImagePreloadHolder.sRegistry.registerNativeAllocation(this, mNativeInstance);
             }
         }
         return mNativeInstance;
diff --git a/graphics/java/android/graphics/ColorMatrixColorFilter.java b/graphics/java/android/graphics/ColorMatrixColorFilter.java
index 90ff189..bfdf318 100644
--- a/graphics/java/android/graphics/ColorMatrixColorFilter.java
+++ b/graphics/java/android/graphics/ColorMatrixColorFilter.java
@@ -81,12 +81,12 @@
      */
     @UnsupportedAppUsage
     public void setColorMatrix(@Nullable ColorMatrix matrix) {
-        discardNativeInstance();
         if (matrix == null) {
             mMatrix.reset();
         } else {
             mMatrix.set(matrix);
         }
+        nativeSetColorMatrix(getNativeInstance(), mMatrix.getArray());
     }
 
     /**
@@ -111,7 +111,6 @@
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     public void setColorMatrixArray(@Nullable float[] array) {
         // called '...Array' so that passing null isn't ambiguous
-        discardNativeInstance();
         if (array == null) {
             mMatrix.reset();
         } else {
@@ -120,6 +119,7 @@
             }
             mMatrix.set(array);
         }
+        nativeSetColorMatrix(getNativeInstance(), mMatrix.getArray());
     }
 
     @Override
@@ -128,4 +128,6 @@
     }
 
     private static native long nativeColorMatrixFilter(float[] array);
+
+    private static native void nativeSetColorMatrix(long colorMatrixColorFilter, float[] array);
 }
diff --git a/graphics/java/android/graphics/LightingColorFilter.java b/graphics/java/android/graphics/LightingColorFilter.java
index df91c5d..0aa6f12 100644
--- a/graphics/java/android/graphics/LightingColorFilter.java
+++ b/graphics/java/android/graphics/LightingColorFilter.java
@@ -78,7 +78,7 @@
     public void setColorMultiply(@ColorInt int mul) {
         if (mMul != mul) {
             mMul = mul;
-            discardNativeInstance();
+            native_SetLightingFilterMul(getNativeInstance(), mul);
         }
     }
 
@@ -104,7 +104,7 @@
     public void setColorAdd(@ColorInt int add) {
         if (mAdd != add) {
             mAdd = add;
-            discardNativeInstance();
+            native_SetLightingFilterAdd(getNativeInstance(), add);
         }
     }
 
@@ -114,4 +114,8 @@
     }
 
     private static native long native_CreateLightingFilter(int mul, int add);
+
+    private static native void native_SetLightingFilterAdd(long lightingFilter, int add);
+
+    private static native void native_SetLightingFilterMul(long lightingFilter, int mul);
 }
diff --git a/graphics/java/android/graphics/RenderNode.java b/graphics/java/android/graphics/RenderNode.java
index 2e91c24..15d26eb 100644
--- a/graphics/java/android/graphics/RenderNode.java
+++ b/graphics/java/android/graphics/RenderNode.java
@@ -971,6 +971,23 @@
     }
 
     /**
+     * Configure the {@link android.graphics.RenderEffect} to apply to the backdrop contents of
+     * this RenderNode. This will apply a visual effect to the result of the backdrop contents
+     * of this RenderNode before the RenderNode is drawn into the destination. For example if
+     * {@link RenderEffect#createBlurEffect(float, float, RenderEffect, Shader.TileMode)}
+     * is provided, the previous content behind this RenderNode will be blurred before the
+     * RenderNode is drawn in to the destination.
+     * @param renderEffect to be applied to the backdrop contents of this RenderNode. Passing
+     *          null clears all previously configured RenderEffects
+     * @return True if the value changed, false if the new value was the same as the previous value.
+     * @hide
+     */
+    public boolean setBackdropRenderEffect(@Nullable RenderEffect renderEffect) {
+        return nSetBackdropRenderEffect(mNativeRenderNode,
+                renderEffect != null ? renderEffect.getNativeInstance() : 0);
+    }
+
+    /**
      * Returns the translucency level of this display list.
      *
      * @return A value between 0.0f and 1.0f
@@ -1797,6 +1814,9 @@
     private static native boolean nSetRenderEffect(long renderNode, long renderEffect);
 
     @CriticalNative
+    private static native boolean nSetBackdropRenderEffect(long renderNode, long renderEffect);
+
+    @CriticalNative
     private static native boolean nSetHasOverlappingRendering(long renderNode,
             boolean hasOverlappingRendering);
 
diff --git a/graphics/java/android/graphics/fonts/FontFamily.java b/graphics/java/android/graphics/fonts/FontFamily.java
index bf79b1b..7cca7f1 100644
--- a/graphics/java/android/graphics/fonts/FontFamily.java
+++ b/graphics/java/android/graphics/fonts/FontFamily.java
@@ -30,6 +30,7 @@
 import libcore.util.NativeAllocationRegistry;
 
 import java.util.ArrayList;
+import java.util.Set;
 
 /**
  * A font family class can be used for creating Typeface.
@@ -58,6 +59,7 @@
  *
  */
 public final class FontFamily {
+
     private static final String TAG = "FontFamily";
 
     /**
@@ -73,6 +75,7 @@
         // initial capacity.
         private final SparseIntArray mStyles = new SparseIntArray(4);
 
+
         /**
          * Constructs a builder.
          *
@@ -110,23 +113,63 @@
         }
 
         /**
+         * Build a variable font family that automatically adjust the `wght` and `ital` axes value
+         * for the requested weight/italic style values.
+         *
+         * To build a variable font family, added fonts must meet one of following conditions.
+         *
+         * If two font files are added, both font files must support `wght` axis and one font must
+         * support {@link FontStyle#FONT_SLANT_UPRIGHT} and another font must support
+         * {@link FontStyle#FONT_SLANT_ITALIC}. If the requested weight value is lower than minimum
+         * value of the supported `wght` axis, the minimum supported `wght` value is used. If the
+         * requested weight value is larger than maximum value of the supported `wght` axis, the
+         * maximum supported `wght` value is used. The weight values of the fonts are ignored.
+         *
+         * If one font file is added, that font must support the `wght` axis. If that font support
+         * `ital` axis, that `ital` value is set to 1 when the italic style is requested. If that
+         * font doesn't support `ital` axis, synthetic italic may be used. If the requested
+         * weight value is lower than minimum value of the supported `wght` axis, the minimum
+         * supported `wght` value is used. If the requested weight value is larger than maximum
+         * value of the supported `wght`axis, the maximum supported `wght` value is used. The weight
+         * value of the font is ignored.
+         *
+         * If none of the above conditions are met, this function return {@code null}.
+         *
+         * @return A variable font family. null if a variable font cannot be built from the given
+         *         fonts.
+         */
+        public @Nullable FontFamily buildVariableFamily() {
+            int variableFamilyType = analyzeAndResolveVariableType(mFonts);
+            if (variableFamilyType == VARIABLE_FONT_FAMILY_TYPE_UNKNOWN) {
+                return null;
+            }
+            return build("", FontConfig.FontFamily.VARIANT_DEFAULT,
+                    true /* isCustomFallback */,
+                    false /* isDefaultFallback */,
+                    variableFamilyType);
+        }
+
+        /**
          * Build the font family
          * @return a font family
          */
         public @NonNull FontFamily build() {
-            return build("", FontConfig.FontFamily.VARIANT_DEFAULT, true /* isCustomFallback */,
-                    false /* isDefaultFallback */);
+            return build("", FontConfig.FontFamily.VARIANT_DEFAULT,
+                    true /* isCustomFallback */,
+                    false /* isDefaultFallback */,
+                    VARIABLE_FONT_FAMILY_TYPE_NONE);
         }
 
         /** @hide */
         public @NonNull FontFamily build(@NonNull String langTags, int variant,
-                boolean isCustomFallback, boolean isDefaultFallback) {
+                boolean isCustomFallback, boolean isDefaultFallback, int variableFamilyType) {
+
             final long builderPtr = nInitBuilder();
             for (int i = 0; i < mFonts.size(); ++i) {
                 nAddFont(builderPtr, mFonts.get(i).getNativePtr());
             }
             final long ptr = nBuild(builderPtr, langTags, variant, isCustomFallback,
-                    isDefaultFallback);
+                    isDefaultFallback, variableFamilyType);
             final FontFamily family = new FontFamily(ptr);
             sFamilyRegistory.registerNativeAllocation(family, ptr);
             return family;
@@ -136,11 +179,94 @@
             return font.getStyle().getWeight() | (font.getStyle().getSlant()  << 16);
         }
 
+        /**
+         * @see #buildVariableFamily()
+         * @hide
+         */
+        public static final int VARIABLE_FONT_FAMILY_TYPE_UNKNOWN = -1;
+
+        /**
+         * @see #buildVariableFamily()
+         * @hide
+         */
+        public static final int VARIABLE_FONT_FAMILY_TYPE_NONE = 0;
+        /**
+         * @see #buildVariableFamily()
+         * @hide
+         */
+        public static final int VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ONLY = 1;
+        /**
+         * @see #buildVariableFamily()
+         * @hide
+         */
+        public static final int VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ITAL = 2;
+        /**
+         * @see #buildVariableFamily()
+         * @hide
+         */
+        public static final int VARIABLE_FONT_FAMILY_TYPE_TWO_FONTS_WGHT = 3;
+
+        /**
+         * The registered italic axis used for adjusting requested style.
+         * https://learn.microsoft.com/en-us/typography/opentype/spec/dvaraxistag_ital
+         */
+        private static final int TAG_ital = 0x6974616C;  // i(0x69), t(0x74), a(0x61), l(0x6c)
+
+        /**
+         * The registered weight axis used for adjusting requested style.
+         * https://learn.microsoft.com/en-us/typography/opentype/spec/dvaraxistag_wght
+         */
+        private static final int TAG_wght = 0x77676874;  // w(0x77), g(0x67), h(0x68), t(0x74)
+
+        private static int analyzeAndResolveVariableType(ArrayList<Font> fonts) {
+            if (fonts.size() > 2) {
+                return VARIABLE_FONT_FAMILY_TYPE_UNKNOWN;
+            }
+
+            if (fonts.size() == 1) {
+                Font font = fonts.get(0);
+                Set<Integer> supportedAxes =
+                        FontFileUtil.getSupportedAxes(font.getBuffer(), font.getTtcIndex());
+                if (supportedAxes.contains(TAG_wght)) {
+                    if (supportedAxes.contains(TAG_ital)) {
+                        return VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ITAL;
+                    } else {
+                        return VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ONLY;
+                    }
+                } else {
+                    return VARIABLE_FONT_FAMILY_TYPE_UNKNOWN;
+                }
+            } else {
+                for (int i = 0; i < fonts.size(); ++i) {
+                    Font font = fonts.get(i);
+                    Set<Integer> supportedAxes =
+                            FontFileUtil.getSupportedAxes(font.getBuffer(), font.getTtcIndex());
+                    if (!supportedAxes.contains(TAG_wght)) {
+                        return VARIABLE_FONT_FAMILY_TYPE_UNKNOWN;
+                    }
+                }
+                boolean italic1 = fonts.get(0).getStyle().getSlant() == FontStyle.FONT_SLANT_ITALIC;
+                boolean italic2 = fonts.get(1).getStyle().getSlant() == FontStyle.FONT_SLANT_ITALIC;
+
+                if (italic1 == italic2) {
+                    return VARIABLE_FONT_FAMILY_TYPE_UNKNOWN;
+                } else {
+                    if (italic1) {
+                        // Swap fonts to make the first font upright, second font italic.
+                        Font firstFont = fonts.get(0);
+                        fonts.set(0, fonts.get(1));
+                        fonts.set(1, firstFont);
+                    }
+                    return VARIABLE_FONT_FAMILY_TYPE_TWO_FONTS_WGHT;
+                }
+            }
+        }
+
         private static native long nInitBuilder();
         @CriticalNative
         private static native void nAddFont(long builderPtr, long fontPtr);
         private static native long nBuild(long builderPtr, String langTags, int variant,
-                boolean isCustomFallback, boolean isDefaultFallback);
+                boolean isCustomFallback, boolean isDefaultFallback, int variableFamilyType);
         @CriticalNative
         private static native long nGetReleaseNativeFamily();
     }
diff --git a/graphics/java/android/graphics/fonts/FontFileUtil.java b/graphics/java/android/graphics/fonts/FontFileUtil.java
index 917eef2..ff38282 100644
--- a/graphics/java/android/graphics/fonts/FontFileUtil.java
+++ b/graphics/java/android/graphics/fonts/FontFileUtil.java
@@ -19,11 +19,14 @@
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.util.ArraySet;
 
 import dalvik.annotation.optimization.FastNative;
 
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
+import java.util.Collections;
+import java.util.Set;
 
 /**
  * Provides a utility for font file operations.
@@ -62,6 +65,7 @@
     private static final int SFNT_VERSION_OTTO = 0x4F54544F;
     private static final int TTC_TAG = 0x74746366;
     private static final int OS2_TABLE_TAG = 0x4F532F32;
+    private static final int FVAR_TABLE_TAG = 0x66766172;
 
     private static final int ANALYZE_ERROR = 0xFFFFFFFF;
 
@@ -200,6 +204,73 @@
         }
     }
 
+    private static int getUInt16(ByteBuffer buffer, int offset) {
+        return ((int) buffer.getShort(offset)) & 0xFFFF;
+    }
+
+    /**
+     * Returns supported axes of font
+     *
+     * @param buffer A buffer of the entire font file.
+     * @param index A font index in case of font collection. Must be 0 otherwise.
+     * @return set of supported axes tag. Returns empty set on error.
+     */
+    public static Set<Integer> getSupportedAxes(@NonNull ByteBuffer buffer, int index) {
+        ByteOrder originalOrder = buffer.order();
+        buffer.order(ByteOrder.BIG_ENDIAN);
+        try {
+            int fontFileOffset = 0;
+            int magicNumber = buffer.getInt(0);
+            if (magicNumber == TTC_TAG) {
+                // TTC file.
+                if (index >= buffer.getInt(8 /* offset to number of fonts in TTC */)) {
+                    return Collections.EMPTY_SET;
+                }
+                fontFileOffset = buffer.getInt(
+                        12 /* offset to array of offsets of font files */ + 4 * index);
+            }
+            int sfntVersion = buffer.getInt(fontFileOffset);
+
+            if (sfntVersion != SFNT_VERSION_1 && sfntVersion != SFNT_VERSION_OTTO) {
+                return Collections.EMPTY_SET;
+            }
+
+            int numTables = buffer.getShort(fontFileOffset + 4 /* offset to number of tables */);
+            int fvarTableOffset = -1;
+            for (int i = 0; i < numTables; ++i) {
+                int tableOffset = fontFileOffset + 12 /* size of offset table */
+                        + i * 16 /* size of table record */;
+                if (buffer.getInt(tableOffset) == FVAR_TABLE_TAG) {
+                    fvarTableOffset = buffer.getInt(tableOffset + 8 /* offset to the table */);
+                    break;
+                }
+            }
+
+            if (fvarTableOffset == -1) {
+                // Couldn't find OS/2 table. use regular style
+                return Collections.EMPTY_SET;
+            }
+
+            if (buffer.getShort(fvarTableOffset) != 1
+                    || buffer.getShort(fvarTableOffset + 2) != 0) {
+                return Collections.EMPTY_SET;
+            }
+
+            int axesArrayOffset = getUInt16(buffer, fvarTableOffset + 4);
+            int axisCount = getUInt16(buffer, fvarTableOffset + 8);
+            int axisSize = getUInt16(buffer, fvarTableOffset + 10);
+
+            ArraySet<Integer> axes = new ArraySet<>();
+            for (int i = 0; i < axisCount; ++i) {
+                axes.add(buffer.getInt(fvarTableOffset + axesArrayOffset + axisSize * i));
+            }
+
+            return axes;
+        } finally {
+            buffer.order(originalOrder);
+        }
+    }
+
     @FastNative
     private static native long nGetFontRevision(@NonNull ByteBuffer buffer,
             @IntRange(from = 0) int index);
diff --git a/graphics/java/android/graphics/fonts/SystemFonts.java b/graphics/java/android/graphics/fonts/SystemFonts.java
index 8fe28ae..98629a2 100644
--- a/graphics/java/android/graphics/fonts/SystemFonts.java
+++ b/graphics/java/android/graphics/fonts/SystemFonts.java
@@ -47,7 +47,7 @@
 public final class SystemFonts {
     private static final String TAG = "SystemFonts";
 
-    private static final String FONTS_XML = "/system/etc/fonts.xml";
+    private static final String FONTS_XML = "/system/etc/font_fallback.xml";
     /** @hide */
     public static final String SYSTEM_FONT_DIR = "/system/fonts/";
     private static final String OEM_XML = "/product/etc/fonts_customization.xml";
@@ -194,7 +194,7 @@
             }
         }
         return b == null ? null : b.build(languageTags, variant, false /* isCustomFallback */,
-                isDefaultFallback);
+                isDefaultFallback, FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_NONE);
     }
 
     private static void appendNamedFamilyList(@NonNull FontConfig.NamedFamilyList namedFamilyList,
diff --git a/graphics/java/android/graphics/text/LineBreakConfig.java b/graphics/java/android/graphics/text/LineBreakConfig.java
index d0327159..7b204f2 100644
--- a/graphics/java/android/graphics/text/LineBreakConfig.java
+++ b/graphics/java/android/graphics/text/LineBreakConfig.java
@@ -18,6 +18,7 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -33,6 +34,32 @@
 public final class LineBreakConfig {
 
     /**
+     * No line break style is specified.
+     *
+     * This is a special value of line break style indicating no style value is specified.
+     * When overriding a {@link LineBreakConfig} with another {@link LineBreakConfig} with
+     * {@link Builder#merge(LineBreakConfig)} function, the line break style of overridden config
+     * will be kept if the line break style of overriding config is
+     * {@link #LINE_BREAK_STYLE_UNSPECIFIED}.
+     *
+     * <pre>
+     *     val override = LineBreakConfig.Builder()
+     *          .setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_WORD_STYLE_PHRASE)
+     *          .build();  // UNSPECIFIED if no setLineBreakStyle is called.
+     *     val config = LineBreakConfig.Builder()
+     *          .setLineBreakStyle(LineBreakConfig.LINE_BREAK_STYLE_STRICT)
+     *          .merge(override)
+     *          .build()
+     *     // Here, config has LINE_BREAK_STYLE_STRICT for line break config and
+     *     // LINE_BREAK_WORD_STYLE_PHRASE for line break word style.
+     * </pre>
+     *
+     * This value is resolved to {@link #LINE_BREAK_STYLE_NONE} if this value is used for text
+     * layout/rendering.
+     */
+    public static final int LINE_BREAK_STYLE_UNSPECIFIED = -1;
+
+    /**
      * No line-break rules are used for line breaking.
      */
     public static final int LINE_BREAK_STYLE_NONE = 0;
@@ -56,12 +83,38 @@
     /** @hide */
     @IntDef(prefix = { "LINE_BREAK_STYLE_" }, value = {
             LINE_BREAK_STYLE_NONE, LINE_BREAK_STYLE_LOOSE, LINE_BREAK_STYLE_NORMAL,
-            LINE_BREAK_STYLE_STRICT
+            LINE_BREAK_STYLE_STRICT, LINE_BREAK_STYLE_UNSPECIFIED
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface LineBreakStyle {}
 
     /**
+     * No line break word style is specified.
+     *
+     * This is a special value of line break word style indicating no style value is specified.
+     * When overriding a {@link LineBreakConfig} with another {@link LineBreakConfig} with
+     * {@link Builder#merge(LineBreakConfig)} function, the line break word style of overridden
+     * config will be kept if the line break word style of overriding config is
+     * {@link #LINE_BREAK_WORD_STYLE_UNSPECIFIED}.
+     *
+     * <pre>
+     *     val override = LineBreakConfig.Builder()
+     *          .setLineBreakStyle(LineBreakConfig.LINE_BREAK_STYLE_STRICT)
+     *          .build();  // UNSPECIFIED if no setLineBreakWordStyle is called.
+     *     val config = LineBreakConfig.Builder()
+     *          .setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_WORD_STYLE_PHRASE)
+     *          .merge(override)
+     *          .build()
+     *     // Here, config has LINE_BREAK_STYLE_STRICT for line break config and
+     *     // LINE_BREAK_WORD_STYLE_PHRASE for line break word style.
+     * </pre>
+     *
+     * This value is resolved to {@link #LINE_BREAK_WORD_STYLE_NONE} if this value is used for
+     * text layout/rendering.
+     */
+    public static final int LINE_BREAK_WORD_STYLE_UNSPECIFIED = -1;
+
+    /**
      * No line-break word style is used for line breaking.
      */
     public static final int LINE_BREAK_WORD_STYLE_NONE = 0;
@@ -78,7 +131,7 @@
 
     /** @hide */
     @IntDef(prefix = { "LINE_BREAK_WORD_STYLE_" }, value = {
-        LINE_BREAK_WORD_STYLE_NONE, LINE_BREAK_WORD_STYLE_PHRASE
+        LINE_BREAK_WORD_STYLE_NONE, LINE_BREAK_WORD_STYLE_PHRASE, LINE_BREAK_WORD_STYLE_UNSPECIFIED
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface LineBreakWordStyle {}
@@ -88,26 +141,80 @@
      */
     public static final class Builder {
         // The line break style for the LineBreakConfig.
-        private @LineBreakStyle int mLineBreakStyle = LineBreakConfig.LINE_BREAK_STYLE_NONE;
+        private @LineBreakStyle int mLineBreakStyle = LineBreakConfig.LINE_BREAK_STYLE_UNSPECIFIED;
 
         // The line break word style for the LineBreakConfig.
         private @LineBreakWordStyle int mLineBreakWordStyle =
-                LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE;
-
-        // Whether or not enabling phrase breaking automatically.
-        // TODO(b/226012260): Remove this and add LINE_BREAK_WORD_STYLE_PHRASE_AUTO after
-        // the experiment.
-        private boolean mAutoPhraseBreaking = false;
+                LineBreakConfig.LINE_BREAK_WORD_STYLE_UNSPECIFIED;
 
         /**
          * Builder constructor.
          */
         public Builder() {
+            reset(null);
+        }
+
+        /**
+         * Merges line break config with other config
+         *
+         * Update the internal configurations with passed {@code config}. If the config values of
+         * passed {@code config} are unspecified, the original config values are kept. For example,
+         * the following code passes {@code config} that has {@link #LINE_BREAK_STYLE_UNSPECIFIED}.
+         * This code generates {@link LineBreakConfig} that has line break config
+         * {@link #LINE_BREAK_STYLE_STRICT}.
+         *
+         * <pre>
+         *     val override = LineBreakConfig.Builder()
+         *          .setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_WORD_STYLE_PHRASE)
+         *          .build();  // UNSPECIFIED if no setLineBreakStyle is called.
+         *     val config = LineBreakConfig.Builder()
+         *          .setLineBreakStyle(LineBreakConfig.LINE_BREAK_STYLE_STRICT)
+         *          .merge(override)
+         *          .build()
+         *     // Here, config has LINE_BREAK_STYLE_STRICT of line break config and
+         *     // LINE_BREAK_WORD_STYLE_PHRASE of line break word style.
+         * </pre>
+         *
+         * @see #LINE_BREAK_STYLE_UNSPECIFIED
+         * @see #LINE_BREAK_WORD_STYLE_UNSPECIFIED
+         *
+         * @param config an override line break config
+         * @return This {@code Builder}.
+         */
+        public @NonNull Builder merge(@NonNull LineBreakConfig config) {
+            if (config.mLineBreakStyle != LINE_BREAK_STYLE_UNSPECIFIED) {
+                mLineBreakStyle = config.mLineBreakStyle;
+            }
+            if (config.mLineBreakWordStyle != LINE_BREAK_WORD_STYLE_UNSPECIFIED) {
+                mLineBreakWordStyle = config.mLineBreakWordStyle;
+            }
+            return this;
+        }
+
+        /**
+         * Resets this builder to the given config state.
+         *
+         * @return This {@code Builder}.
+         * @hide
+         */
+        public @NonNull Builder reset(@Nullable LineBreakConfig config) {
+            if (config == null) {
+                mLineBreakStyle = LINE_BREAK_STYLE_UNSPECIFIED;
+                mLineBreakWordStyle = LINE_BREAK_WORD_STYLE_UNSPECIFIED;
+            } else {
+                mLineBreakStyle = config.mLineBreakStyle;
+                mLineBreakWordStyle = config.mLineBreakWordStyle;
+            }
+            return this;
         }
 
         /**
          * Sets the line-break style.
          *
+         * Note: different from {@link #merge(LineBreakConfig)} if this function is called with
+         * {@link #LINE_BREAK_STYLE_UNSPECIFIED}, the line break style is reset to
+         * {@link #LINE_BREAK_STYLE_UNSPECIFIED}.
+         *
          * @param lineBreakStyle The new line-break style.
          * @return This {@code Builder}.
          */
@@ -119,6 +226,10 @@
         /**
          * Sets the line-break word style.
          *
+         * Note: different from {@link #merge(LineBreakConfig)} method, if this function is called
+         * with {@link #LINE_BREAK_WORD_STYLE_UNSPECIFIED}, the line break style is reset to
+         * {@link #LINE_BREAK_WORD_STYLE_UNSPECIFIED}.
+         *
          * @param lineBreakWordStyle The new line-break word style.
          * @return This {@code Builder}.
          */
@@ -128,22 +239,15 @@
         }
 
         /**
-         * Enables or disables the automation of {@link LINE_BREAK_WORD_STYLE_PHRASE}.
-         *
-         * @hide
-         */
-        public @NonNull Builder setAutoPhraseBreaking(boolean autoPhraseBreaking) {
-            mAutoPhraseBreaking = autoPhraseBreaking;
-            return this;
-        }
-
-        /**
          * Builds a {@link LineBreakConfig} instance.
          *
+         * This method can be called multiple times for generating multiple {@link LineBreakConfig}
+         * instances.
+         *
          * @return The {@code LineBreakConfig} instance.
          */
         public @NonNull LineBreakConfig build() {
-            return new LineBreakConfig(mLineBreakStyle, mLineBreakWordStyle, mAutoPhraseBreaking);
+            return new LineBreakConfig(mLineBreakStyle, mLineBreakWordStyle);
         }
     }
 
@@ -164,23 +268,6 @@
                 .build();
     }
 
-    /**
-     * Create the LineBreakConfig instance.
-     *
-     * @param lineBreakStyle the line break style for text wrapping.
-     * @param lineBreakWordStyle the line break word style for text wrapping.
-     * @return the {@link LineBreakConfig} instance.     *
-     * @hide
-     */
-    public static @NonNull LineBreakConfig getLineBreakConfig(@LineBreakStyle int lineBreakStyle,
-            @LineBreakWordStyle int lineBreakWordStyle, boolean autoPhraseBreaking) {
-        LineBreakConfig.Builder builder = new LineBreakConfig.Builder();
-        return builder.setLineBreakStyle(lineBreakStyle)
-                .setLineBreakWordStyle(lineBreakWordStyle)
-                .setAutoPhraseBreaking(autoPhraseBreaking)
-                .build();
-    }
-
     /** @hide */
     public static final LineBreakConfig NONE =
             new Builder().setLineBreakStyle(LINE_BREAK_STYLE_NONE)
@@ -188,7 +275,6 @@
 
     private final @LineBreakStyle int mLineBreakStyle;
     private final @LineBreakWordStyle int mLineBreakWordStyle;
-    private final boolean mAutoPhraseBreaking;
 
     /**
      * Constructor with line-break parameters.
@@ -197,10 +283,9 @@
      * {@code LineBreakConfig} instance.
      */
     private LineBreakConfig(@LineBreakStyle int lineBreakStyle,
-            @LineBreakWordStyle int lineBreakWordStyle, boolean autoPhraseBreaking) {
+            @LineBreakWordStyle int lineBreakWordStyle) {
         mLineBreakStyle = lineBreakStyle;
         mLineBreakWordStyle = lineBreakWordStyle;
-        mAutoPhraseBreaking = autoPhraseBreaking;
     }
 
     /**
@@ -213,6 +298,22 @@
     }
 
     /**
+     * Gets the resolved line break style.
+     *
+     * This method never returns {@link #LINE_BREAK_STYLE_UNSPECIFIED}.
+     *
+     * @return The line break style.
+     * @hide
+     */
+    public static @LineBreakStyle int getResolvedLineBreakStyle(@Nullable LineBreakConfig config) {
+        if (config == null) {
+            return LINE_BREAK_STYLE_NONE;
+        }
+        return config.mLineBreakStyle == LINE_BREAK_STYLE_UNSPECIFIED
+                ? LINE_BREAK_STYLE_NONE : config.mLineBreakStyle;
+    }
+
+    /**
      * Gets the current line-break word style.
      *
      * @return The line-break word style to be used for text wrapping.
@@ -222,14 +323,50 @@
     }
 
     /**
-     * Used to identify if the automation of {@link LINE_BREAK_WORD_STYLE_PHRASE} is enabled.
+     * Gets the resolved line break style.
      *
-     * @return The result that records whether or not the automation of
-     * {@link LINE_BREAK_WORD_STYLE_PHRASE} is enabled.
+     * This method never returns {@link #LINE_BREAK_WORD_STYLE_UNSPECIFIED}.
+     *
+     * @return The line break word style.
      * @hide
      */
-    public boolean getAutoPhraseBreaking() {
-        return mAutoPhraseBreaking;
+    public static @LineBreakWordStyle int getResolvedLineBreakWordStyle(
+            @Nullable LineBreakConfig config) {
+        if (config == null) {
+            return LINE_BREAK_WORD_STYLE_NONE;
+        }
+        return config.mLineBreakWordStyle == LINE_BREAK_WORD_STYLE_UNSPECIFIED
+                ? LINE_BREAK_WORD_STYLE_NONE : config.mLineBreakWordStyle;
+    }
+
+    /**
+     * Generates a new {@link LineBreakConfig} instance merged with given {@code config}.
+     *
+     * If values of passing {@code config} are unspecified, the original values are kept. For
+     * example, the following code shows how line break config is merged.
+     *
+     * <pre>
+     *     val override = LineBreakConfig.Builder()
+     *          .setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_WORD_STYLE_PHRASE)
+     *          .build();  // UNSPECIFIED if no setLineBreakStyle is called.
+     *     val config = LineBreakConfig.Builder()
+     *          .setLineBreakStyle(LineBreakConfig.LINE_BREAK_STYLE_STRICT)
+     *          .build();
+     *
+     *     val newConfig = config.merge(override)
+     *     // newConfig has LINE_BREAK_STYLE_STRICT of line break style and
+     *     LINE_BREAK_WORD_STYLE_PHRASE of line break word style.
+     * </pre>
+     *
+     * @param config an overriding config.
+     * @return newly created instance that is current style merged with passed config.
+     */
+    public @NonNull LineBreakConfig merge(@NonNull LineBreakConfig config) {
+        return new LineBreakConfig(
+                config.mLineBreakStyle == LINE_BREAK_STYLE_UNSPECIFIED
+                        ? mLineBreakStyle : config.mLineBreakStyle,
+                config.mLineBreakWordStyle == LINE_BREAK_WORD_STYLE_UNSPECIFIED
+                        ? mLineBreakWordStyle : config.mLineBreakWordStyle);
     }
 
     @Override
@@ -239,12 +376,19 @@
         if (!(o instanceof LineBreakConfig)) return false;
         LineBreakConfig that = (LineBreakConfig) o;
         return (mLineBreakStyle == that.mLineBreakStyle)
-                && (mLineBreakWordStyle == that.mLineBreakWordStyle)
-                && (mAutoPhraseBreaking == that.mAutoPhraseBreaking);
+                && (mLineBreakWordStyle == that.mLineBreakWordStyle);
     }
 
     @Override
     public int hashCode() {
         return Objects.hash(mLineBreakStyle, mLineBreakWordStyle);
     }
+
+    @Override
+    public String toString() {
+        return "LineBreakConfig{"
+                + "mLineBreakStyle=" + mLineBreakStyle
+                + ", mLineBreakWordStyle=" + mLineBreakWordStyle
+                + '}';
+    }
 }
diff --git a/graphics/java/android/graphics/text/MeasuredText.java b/graphics/java/android/graphics/text/MeasuredText.java
index 3f7f088..fd08c8b 100644
--- a/graphics/java/android/graphics/text/MeasuredText.java
+++ b/graphics/java/android/graphics/text/MeasuredText.java
@@ -296,10 +296,8 @@
             Preconditions.checkArgument(length > 0, "length can not be negative");
             final int end = mCurrentOffset + length;
             Preconditions.checkArgument(end <= mText.length, "Style exceeds the text length");
-            int lbStyle = (lineBreakConfig != null) ? lineBreakConfig.getLineBreakStyle() :
-                    LineBreakConfig.LINE_BREAK_STYLE_NONE;
-            int lbWordStyle = (lineBreakConfig != null) ? lineBreakConfig.getLineBreakWordStyle() :
-                    LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE;
+            int lbStyle = LineBreakConfig.getResolvedLineBreakStyle(lineBreakConfig);
+            int lbWordStyle = LineBreakConfig.getResolvedLineBreakWordStyle(lineBreakConfig);
             nAddStyleRun(mNativePtr, paint.getNativeInstance(), lbStyle, lbWordStyle,
                     mCurrentOffset, end, isRtl);
             mCurrentOffset = end;
diff --git a/graphics/java/android/graphics/text/PositionedGlyphs.java b/graphics/java/android/graphics/text/PositionedGlyphs.java
index 8d20e9c..49e9d0c 100644
--- a/graphics/java/android/graphics/text/PositionedGlyphs.java
+++ b/graphics/java/android/graphics/text/PositionedGlyphs.java
@@ -165,6 +165,68 @@
     }
 
     /**
+     * Returns true if the fake bold option used for drawing, otherwise false.
+     *
+     * @param index the glyph index
+     * @return true if the fake bold option is on, otherwise off.
+     */
+    public boolean getFakeBold(@IntRange(from = 0) int index) {
+        Preconditions.checkArgumentInRange(index, 0, glyphCount() - 1, "index");
+        return nGetFakeBold(mLayoutPtr, index);
+    }
+
+    /**
+     * Returns true if the fake italic option used for drawing, otherwise false.
+     *
+     * @param index the glyph index
+     * @return true if the fake italic option is on, otherwise off.
+     */
+    public boolean getFakeItalic(@IntRange(from = 0) int index) {
+        Preconditions.checkArgumentInRange(index, 0, glyphCount() - 1, "index");
+        return nGetFakeItalic(mLayoutPtr, index);
+    }
+
+    /**
+     * A special value returned by {@link #getWeightOverride(int)} and
+     * {@link #getItalicOverride(int)} that indicates no font variation setting is overridden.
+     */
+    public static final float NO_OVERRIDE = Float.MIN_VALUE;
+
+    /**
+     * Returns overridden weight value if the font is variable font and `wght` value is overridden
+     * for drawing. Otherwise returns {@link #NO_OVERRIDE}.
+     *
+     * @param index the glyph index
+     * @return overridden weight value or {@link #NO_OVERRIDE}.
+     */
+    public float getWeightOverride(@IntRange(from = 0) int index) {
+        Preconditions.checkArgumentInRange(index, 0, glyphCount() - 1, "index");
+        float value = nGetWeightOverride(mLayoutPtr, index);
+        if (value == -1) {
+            return NO_OVERRIDE;
+        } else {
+            return value;
+        }
+    }
+
+    /**
+     * Returns overridden italic value if the font is variable font and `ital` value is overridden
+     * for drawing. Otherwise returns {@link #NO_OVERRIDE}.
+     *
+     * @param index the glyph index
+     * @return overridden weight value or {@link #NO_OVERRIDE}.
+     */
+    public float getItalicOverride(@IntRange(from = 0) int index) {
+        Preconditions.checkArgumentInRange(index, 0, glyphCount() - 1, "index");
+        float value = nGetItalicOverride(mLayoutPtr, index);
+        if (value == -1) {
+            return NO_OVERRIDE;
+        } else {
+            return value;
+        }
+    }
+
+    /**
      * Create single style layout from native result.
      *
      * @hide
@@ -210,6 +272,14 @@
     private static native long nGetFont(long minikinLayout, int i);
     @CriticalNative
     private static native long nReleaseFunc();
+    @CriticalNative
+    private static native boolean nGetFakeBold(long minikinLayout, int i);
+    @CriticalNative
+    private static native boolean nGetFakeItalic(long minikinLayout, int i);
+    @CriticalNative
+    private static native float nGetWeightOverride(long minikinLayout, int i);
+    @CriticalNative
+    private static native float nGetItalicOverride(long minikinLayout, int i);
 
     @Override
     public boolean equals(Object o) {
diff --git a/keystore/java/android/security/KeyStoreException.java b/keystore/java/android/security/KeyStoreException.java
index cb75779..253d704 100644
--- a/keystore/java/android/security/KeyStoreException.java
+++ b/keystore/java/android/security/KeyStoreException.java
@@ -614,9 +614,23 @@
                 KEYMINT_UNIMPLEMENTED_ERROR);
         sErrorCodeToFailureInfo.put(KeymasterDefs.KM_ERROR_HARDWARE_TYPE_UNAVAILABLE,
                 KEYMINT_UNIMPLEMENTED_ERROR);
+        sErrorCodeToFailureInfo.put(KeymasterDefs.KM_ERROR_PROOF_OF_PRESENCE_REQUIRED,
+                KEYMINT_INCORRECT_USAGE_ERROR);
+        sErrorCodeToFailureInfo.put(KeymasterDefs.KM_ERROR_CONCURRENT_PROOF_OF_PRESENCE_REQUESTED,
+                KEYMINT_INCORRECT_USAGE_ERROR);
+        sErrorCodeToFailureInfo.put(KeymasterDefs.KM_ERROR_NO_USER_CONFIRMATION,
+                KEYMINT_INCORRECT_USAGE_ERROR);
         sErrorCodeToFailureInfo.put(KeymasterDefs.KM_ERROR_DEVICE_LOCKED,
                 new PublicErrorInformation(IS_SYSTEM_ERROR | REQUIRES_USER_AUTHENTICATION,
                         ERROR_USER_AUTHENTICATION_REQUIRED));
+        sErrorCodeToFailureInfo.put(KeymasterDefs.KM_ERROR_EARLY_BOOT_ENDED,
+                GENERAL_KEYMINT_ERROR);
+        sErrorCodeToFailureInfo.put(KeymasterDefs.KM_ERROR_ATTESTATION_KEYS_NOT_PROVISIONED,
+                GENERAL_KEYMINT_ERROR);
+        sErrorCodeToFailureInfo.put(KeymasterDefs.KM_ERROR_ATTESTATION_IDS_NOT_PROVISIONED,
+                GENERAL_KEYMINT_ERROR);
+        sErrorCodeToFailureInfo.put(KeymasterDefs.KM_ERROR_INVALID_OPERATION,
+                GENERAL_KEYMINT_ERROR);
         sErrorCodeToFailureInfo.put(KeymasterDefs.KM_ERROR_STORAGE_KEY_UNSUPPORTED,
                 KEYMINT_UNIMPLEMENTED_ERROR);
         sErrorCodeToFailureInfo.put(KeymasterDefs.KM_ERROR_INCOMPATIBLE_MGF_DIGEST,
@@ -627,6 +641,12 @@
                 KEYMINT_INCORRECT_USAGE_ERROR);
         sErrorCodeToFailureInfo.put(KeymasterDefs.KM_ERROR_MISSING_NOT_AFTER,
                 KEYMINT_INCORRECT_USAGE_ERROR);
+        sErrorCodeToFailureInfo.put(KeymasterDefs.KM_ERROR_MISSING_ISSUER_SUBJECT,
+                KEYMINT_INCORRECT_USAGE_ERROR);
+        sErrorCodeToFailureInfo.put(KeymasterDefs.KM_ERROR_INVALID_ISSUER_SUBJECT,
+                KEYMINT_INCORRECT_USAGE_ERROR);
+        sErrorCodeToFailureInfo.put(KeymasterDefs.KM_ERROR_BOOT_LEVEL_EXCEEDED,
+                KEYMINT_INCORRECT_USAGE_ERROR);
         // This should not be exposed to apps as it's handled by Keystore.
         sErrorCodeToFailureInfo.put(KeymasterDefs.KM_ERROR_HARDWARE_NOT_YET_AVAILABLE,
                 GENERAL_KEYMINT_ERROR);
diff --git a/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
index fe5432f..fc5f7d6 100644
--- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java
+++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
@@ -1351,7 +1351,9 @@
          * the key, it is also irreversibly invalidated once a new biometric is enrolled or once\
          * no more biometrics are enrolled, unless {@link
          * #setInvalidatedByBiometricEnrollment(boolean)} is used to allow validity after
-         * enrollment. Attempts to initialize cryptographic operations using such keys will throw
+         * enrollment, or {@code KeyProperties.AUTH_DEVICE_CREDENTIAL} is specified as part of
+         * the parameters to {@link #setUserAuthenticationParameters}.
+         * Attempts to initialize cryptographic operations using such keys will throw
          * {@link KeyPermanentlyInvalidatedException}.</li>
          * </ul>
          *
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java
index 76e0e1e..55eabb0 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java
@@ -48,7 +48,7 @@
     // TODO(b/241126279) Introduce constants to better version functionality
     @Override
     public int getVendorApiLevel() {
-        return 3;
+        return 4;
     }
 
     @NonNull
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java
index 18497ad..381e9d4 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java
@@ -32,7 +32,7 @@
  */
 class SplitContainer {
     @NonNull
-    private final TaskFragmentContainer mPrimaryContainer;
+    private TaskFragmentContainer mPrimaryContainer;
     @NonNull
     private final TaskFragmentContainer mSecondaryContainer;
     @NonNull
@@ -46,17 +46,35 @@
     @NonNull
     private final IBinder mToken;
 
+    /**
+     * Whether the selection of which container is primary can be changed at runtime. Runtime
+     * updates is currently possible only for {@link SplitPinContainer}
+     *
+     * @see SplitPinContainer
+     */
+    private final boolean mIsPrimaryContainerMutable;
+
     SplitContainer(@NonNull TaskFragmentContainer primaryContainer,
             @NonNull Activity primaryActivity,
             @NonNull TaskFragmentContainer secondaryContainer,
             @NonNull SplitRule splitRule,
             @NonNull SplitAttributes splitAttributes) {
+        this(primaryContainer, primaryActivity, secondaryContainer, splitRule, splitAttributes,
+                false /* isPrimaryContainerMutable */);
+    }
+
+    SplitContainer(@NonNull TaskFragmentContainer primaryContainer,
+            @NonNull Activity primaryActivity,
+            @NonNull TaskFragmentContainer secondaryContainer,
+            @NonNull SplitRule splitRule,
+            @NonNull SplitAttributes splitAttributes, boolean isPrimaryContainerMutable) {
         mPrimaryContainer = primaryContainer;
         mSecondaryContainer = secondaryContainer;
         mSplitRule = splitRule;
         mDefaultSplitAttributes = splitRule.getDefaultSplitAttributes();
         mCurrentSplitAttributes = splitAttributes;
         mToken = new Binder("SplitContainer");
+        mIsPrimaryContainerMutable = isPrimaryContainerMutable;
 
         if (shouldFinishPrimaryWithSecondary(splitRule)) {
             if (mPrimaryContainer.getRunningActivityCount() == 1
@@ -74,6 +92,13 @@
         }
     }
 
+    void setPrimaryContainer(@NonNull TaskFragmentContainer primaryContainer) {
+        if (!mIsPrimaryContainerMutable) {
+            throw new IllegalStateException("Cannot update primary TaskFragmentContainer");
+        }
+        mPrimaryContainer = primaryContainer;
+    }
+
     @NonNull
     TaskFragmentContainer getPrimaryContainer() {
         return mPrimaryContainer;
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index 89f4890..a2f75e0 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -213,6 +213,56 @@
     }
 
     @Override
+    public boolean pinTopActivityStack(int taskId, @NonNull SplitPinRule splitPinRule) {
+        synchronized (mLock) {
+            final TaskContainer task = getTaskContainer(taskId);
+            if (task == null) {
+                Log.e(TAG, "Cannot find the task for id: " + taskId);
+                return false;
+            }
+
+            final TaskFragmentContainer topContainer =
+                    task.getTopNonFinishingTaskFragmentContainer();
+            // Cannot pin the TaskFragment if no other TaskFragment behind it.
+            if (topContainer == null || task.indexOf(topContainer) <= 0) {
+                Log.w(TAG, "Cannot find an ActivityStack to pin or split");
+                return false;
+            }
+            // Abort if the top container is already pinned.
+            if (task.getSplitPinContainer() != null) {
+                Log.w(TAG, "There is already a pinned ActivityStack.");
+                return false;
+            }
+
+            // Find a valid adjacent TaskFragmentContainer
+            final TaskFragmentContainer primaryContainer =
+                    task.getNonFinishingTaskFragmentContainerBelow(topContainer);
+            if (primaryContainer == null) {
+                Log.w(TAG, "Cannot find another ActivityStack to split");
+                return false;
+            }
+
+            // Registers a Split
+            final SplitPinContainer splitPinContainer = new SplitPinContainer(primaryContainer,
+                    topContainer, splitPinRule, splitPinRule.getDefaultSplitAttributes());
+            task.addSplitContainer(splitPinContainer);
+
+            // Updates the Split
+            final TransactionRecord transactionRecord = mTransactionManager.startNewTransaction();
+            final WindowContainerTransaction wct = transactionRecord.getTransaction();
+            mPresenter.updateSplitContainer(splitPinContainer, wct);
+            transactionRecord.apply(false /* shouldApplyIndependently */);
+            updateCallbackIfNecessary();
+            return true;
+        }
+    }
+
+    @Override
+    public void unpinTopActivityStack(int taskId){
+        // TODO
+    }
+
+    @Override
     public void setSplitAttributesCalculator(
             @NonNull Function<SplitAttributesCalculatorParams, SplitAttributes> calculator) {
         synchronized (mLock) {
@@ -308,7 +358,8 @@
 
             forAllTaskContainers(taskContainer -> {
                 synchronized (mLock) {
-                    final List<TaskFragmentContainer> containers = taskContainer.mContainers;
+                    final List<TaskFragmentContainer> containers =
+                            taskContainer.getTaskFragmentContainers();
                     // Clean up the TaskFragmentContainers by the z-order from the lowest.
                     for (int i = 0; i < containers.size(); i++) {
                         final TaskFragmentContainer container = containers.get(i);
@@ -611,8 +662,7 @@
             @NonNull TaskContainer taskContainer) {
         // Update all TaskFragments in the Task. Make a copy of the list since some may be
         // removed on updating.
-        final List<TaskFragmentContainer> containers =
-                new ArrayList<>(taskContainer.mContainers);
+        final List<TaskFragmentContainer> containers = taskContainer.getTaskFragmentContainers();
         for (int i = containers.size() - 1; i >= 0; i--) {
             final TaskFragmentContainer container = containers.get(i);
             // Wait until onTaskFragmentAppeared to update new container.
@@ -672,7 +722,7 @@
         if (targetContainer == null) {
             // When there is no embedding rule matched, try to place it in the top container
             // like a normal launch.
-            targetContainer = taskContainer.getTopTaskFragmentContainer();
+            targetContainer = taskContainer.getTopNonFinishingTaskFragmentContainer();
         }
         if (targetContainer == null) {
             return;
@@ -791,7 +841,8 @@
 
         final TaskFragmentContainer container = getContainerWithActivity(activity);
         if (!isOnReparent && container != null
-                && container.getTaskContainer().getTopTaskFragmentContainer() != container) {
+                && container.getTaskContainer().getTopNonFinishingTaskFragmentContainer()
+                        != container) {
             // Do not resolve if the launched activity is not the top-most container in the Task.
             return true;
         }
@@ -888,7 +939,8 @@
         if (taskContainer == null) {
             return;
         }
-        final TaskFragmentContainer targetContainer = taskContainer.getTopTaskFragmentContainer();
+        final TaskFragmentContainer targetContainer =
+                taskContainer.getTopNonFinishingTaskFragmentContainer();
         if (targetContainer == null) {
             return;
         }
@@ -1213,11 +1265,13 @@
 
         // 3. Whether the top activity (if any) should be split with the new activity intent.
         final TaskContainer taskContainer = getTaskContainer(taskId);
-        if (taskContainer == null || taskContainer.getTopTaskFragmentContainer() == null) {
+        if (taskContainer == null
+                || taskContainer.getTopNonFinishingTaskFragmentContainer() == null) {
             // There is no other activity in the Task to check split with.
             return null;
         }
-        final TaskFragmentContainer topContainer = taskContainer.getTopTaskFragmentContainer();
+        final TaskFragmentContainer topContainer =
+                taskContainer.getTopNonFinishingTaskFragmentContainer();
         final Activity topActivity = topContainer.getTopNonFinishingActivity();
         if (topActivity != null && topActivity != launchingActivity) {
             final TaskFragmentContainer container = getSecondaryContainerForSplitIfAny(wct,
@@ -1331,7 +1385,8 @@
         // Check pending appeared activity first because there can be a delay for the server
         // update.
         for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
-            final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i).mContainers;
+            final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i)
+                    .getTaskFragmentContainers();
             for (int j = containers.size() - 1; j >= 0; j--) {
                 final TaskFragmentContainer container = containers.get(j);
                 if (container.hasPendingAppearedActivity(activityToken)) {
@@ -1342,7 +1397,8 @@
 
         // Check appeared activity if there is no such pending appeared activity.
         for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
-            final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i).mContainers;
+            final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i)
+                    .getTaskFragmentContainers();
             for (int j = containers.size() - 1; j >= 0; j--) {
                 final TaskFragmentContainer container = containers.get(j);
                 if (container.hasAppearedActivity(activityToken)) {
@@ -1418,7 +1474,7 @@
         if (splitRule instanceof SplitPairRule && ((SplitPairRule) splitRule).shouldClearTop()) {
             removeExistingSecondaryContainers(wct, primaryContainer);
         }
-        primaryContainer.getTaskContainer().mSplitContainers.add(splitContainer);
+        primaryContainer.getTaskContainer().addSplitContainer(splitContainer);
     }
 
     /** Cleanups all the dependencies when the TaskFragment is entering PIP. */
@@ -1430,8 +1486,9 @@
             return;
         }
         final List<SplitContainer> splitsToRemove = new ArrayList<>();
+        final List<SplitContainer> splitContainers = taskContainer.getSplitContainers();
         final Set<TaskFragmentContainer> containersToUpdate = new ArraySet<>();
-        for (SplitContainer splitContainer : taskContainer.mSplitContainers) {
+        for (SplitContainer splitContainer : splitContainers) {
             if (splitContainer.getPrimaryContainer() != container
                     && splitContainer.getSecondaryContainer() != container) {
                 continue;
@@ -1449,7 +1506,7 @@
             }
         }
         container.resetDependencies();
-        taskContainer.mSplitContainers.removeAll(splitsToRemove);
+        taskContainer.removeSplitContainers(splitsToRemove);
         // If there is any TaskFragment split with the PIP TaskFragment, update their presentations
         // since the split is dismissed.
         // We don't want to close any of them even if they are dependencies of the PIP TaskFragment.
@@ -1471,7 +1528,7 @@
     void removeContainers(@NonNull TaskContainer taskContainer,
             @NonNull List<TaskFragmentContainer> containers) {
         // Remove all split containers that included this one
-        taskContainer.mContainers.removeAll(containers);
+        taskContainer.removeTaskFragmentContainers(containers);
         // Marked as a pending removal which will be removed after it is actually removed on the
         // server side (#onTaskFragmentVanished).
         // In this way, we can keep track of the Task bounds until we no longer have any
@@ -1481,7 +1538,8 @@
 
         // Cleanup any split references.
         final List<SplitContainer> containersToRemove = new ArrayList<>();
-        for (SplitContainer splitContainer : taskContainer.mSplitContainers) {
+        final List<SplitContainer> splitContainers = taskContainer.getSplitContainers();
+        for (SplitContainer splitContainer : splitContainers) {
             if (containersToRemove.contains(splitContainer)) {
                 // Don't need to check because it has been in the remove list.
                 continue;
@@ -1492,10 +1550,12 @@
                 containersToRemove.add(splitContainer);
             }
         }
-        taskContainer.mSplitContainers.removeAll(containersToRemove);
+        taskContainer.removeSplitContainers(containersToRemove);
 
         // Cleanup any dependent references.
-        for (TaskFragmentContainer containerToUpdate : taskContainer.mContainers) {
+        final List<TaskFragmentContainer> taskFragmentContainers =
+                taskContainer.getTaskFragmentContainers();
+        for (TaskFragmentContainer containerToUpdate : taskFragmentContainers) {
             containerToUpdate.removeContainersToFinishOnExit(containers);
         }
     }
@@ -1534,8 +1594,9 @@
         if (taskContainer == null) {
             return null;
         }
-        for (int i = taskContainer.mContainers.size() - 1; i >= 0; i--) {
-            final TaskFragmentContainer container = taskContainer.mContainers.get(i);
+        final List<TaskFragmentContainer> containers = taskContainer.getTaskFragmentContainers();
+        for (int i = containers.size() - 1; i >= 0; i--) {
+            final TaskFragmentContainer container = containers.get(i);
             if (!container.isFinished() && (container.getRunningActivityCount() > 0
                     // We may be waiting for the top TaskFragment to become non-empty after
                     // creation. In that case, we don't want to treat the TaskFragment below it as
@@ -1560,6 +1621,12 @@
             // background.
             return;
         }
+        final SplitContainer splitContainer = getActiveSplitForContainer(container);
+        if (splitContainer instanceof SplitPinContainer
+                && updateSplitContainerIfNeeded(splitContainer, wct, null /* splitAttributes */)) {
+            // A SplitPinContainer exists and is updated.
+            return;
+        }
         if (launchPlaceholderIfNecessary(wct, container)) {
             // Placeholder was launched, the positions will be updated when the activity is added
             // to the secondary container.
@@ -1572,7 +1639,6 @@
             // If the info is not available yet the task fragment will be expanded when it's ready
             return;
         }
-        SplitContainer splitContainer = getActiveSplitForContainer(container);
         if (splitContainer == null) {
             return;
         }
@@ -1629,7 +1695,7 @@
     /** Whether the given split is the topmost split in the Task. */
     private boolean isTopMostSplit(@NonNull SplitContainer splitContainer) {
         final List<SplitContainer> splitContainers = splitContainer.getPrimaryContainer()
-                .getTaskContainer().mSplitContainers;
+                .getTaskContainer().getSplitContainers();
         return splitContainer == splitContainers.get(splitContainers.size() - 1);
     }
 
@@ -1641,7 +1707,8 @@
         if (container == null) {
             return null;
         }
-        final List<SplitContainer> splitContainers = container.getTaskContainer().mSplitContainers;
+        final List<SplitContainer> splitContainers =
+                container.getTaskContainer().getSplitContainers();
         if (splitContainers.isEmpty()) {
             return null;
         }
@@ -1665,7 +1732,7 @@
             @NonNull TaskFragmentContainer firstContainer,
             @NonNull TaskFragmentContainer secondContainer) {
         final List<SplitContainer> splitContainers = firstContainer.getTaskContainer()
-                .mSplitContainers;
+                .getSplitContainers();
         for (int i = splitContainers.size() - 1; i >= 0; i--) {
             final SplitContainer splitContainer = splitContainers.get(i);
             final TaskFragmentContainer primary = splitContainer.getPrimaryContainer();
@@ -1930,7 +1997,8 @@
     @GuardedBy("mLock")
     TaskFragmentContainer getContainer(@NonNull IBinder fragmentToken) {
         for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
-            final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i).mContainers;
+            final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i)
+                    .getTaskFragmentContainers();
             for (TaskFragmentContainer container : containers) {
                 if (container.getTaskFragmentToken().equals(fragmentToken)) {
                     return container;
@@ -1945,7 +2013,7 @@
     @GuardedBy("mLock")
     SplitContainer getSplitContainer(@NonNull IBinder token) {
         for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
-            final List<SplitContainer> containers = mTaskContainers.valueAt(i).mSplitContainers;
+            final List<SplitContainer> containers = mTaskContainers.valueAt(i).getSplitContainers();
             for (SplitContainer container : containers) {
                 if (container.getToken().equals(token)) {
                     return container;
@@ -2091,7 +2159,7 @@
                 }
                 for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
                     final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i)
-                            .mContainers;
+                            .getTaskFragmentContainers();
                     for (int j = containers.size() - 1; j >= 0; j--) {
                         final TaskFragmentContainer container = containers.get(j);
                         if (!container.hasActivity(activityToken)
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPinContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPinContainer.java
new file mode 100644
index 0000000..03c77a0
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPinContainer.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 androidx.window.extensions.embedding;
+
+import androidx.annotation.NonNull;
+
+/**
+ * Client-side descriptor of a split that holds two containers while the secondary
+ * container is pinned on top of the Task and the primary container is the container that is
+ * currently below the secondary container. The primary container could be updated to
+ * another container whenever the existing primary container is removed or no longer
+ * be the container that's right behind the secondary container.
+ */
+class SplitPinContainer extends SplitContainer {
+
+    SplitPinContainer(@NonNull TaskFragmentContainer primaryContainer,
+            @NonNull TaskFragmentContainer secondaryContainer,
+            @NonNull SplitPinRule splitPinRule,
+            @NonNull SplitAttributes splitAttributes) {
+        super(primaryContainer, primaryContainer.getTopNonFinishingActivity(), secondaryContainer,
+                splitPinRule, splitAttributes, true /* isPrimaryContainerMutable */);
+    }
+
+    @Override
+    public String toString() {
+        return "SplitPinContainer{"
+                + " primaryContainer=" + getPrimaryContainer()
+                + " secondaryContainer=" + getSecondaryContainer()
+                + " splitPinRule=" + getSplitRule()
+                + " splitAttributes" + getCurrentSplitAttributes()
+                + "}";
+    }
+}
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 53d39d9..4dafbd1 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -17,6 +17,7 @@
 package androidx.window.extensions.embedding;
 
 import static android.content.pm.PackageManager.MATCH_ALL;
+import static android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_FRONT;
 
 import android.app.Activity;
 import android.app.ActivityThread;
@@ -39,6 +40,7 @@
 import android.view.WindowMetrics;
 import android.window.TaskFragmentAnimationParams;
 import android.window.TaskFragmentCreationParams;
+import android.window.TaskFragmentOperation;
 import android.window.WindowContainerTransaction;
 
 import androidx.annotation.IntDef;
@@ -336,10 +338,6 @@
         // value.
         final SplitRule rule = splitContainer.getSplitRule();
         final TaskFragmentContainer primaryContainer = splitContainer.getPrimaryContainer();
-        final Activity activity = primaryContainer.getTopNonFinishingActivity();
-        if (activity == null) {
-            return;
-        }
         final TaskContainer taskContainer = splitContainer.getTaskContainer();
         final TaskProperties taskProperties = taskContainer.getTaskProperties();
         final SplitAttributes splitAttributes = splitContainer.getCurrentSplitAttributes();
@@ -424,6 +422,16 @@
         container.setLastRequestedBounds(fragmentOptions.getInitialRelativeBounds());
         container.setLastRequestedWindowingMode(fragmentOptions.getWindowingMode());
         super.createTaskFragment(wct, fragmentOptions);
+
+        // Reorders the pinned TaskFragment to front to ensure it is the front-most TaskFragment.
+        final SplitPinContainer pinnedContainer =
+                container.getTaskContainer().getSplitPinContainer();
+        if (pinnedContainer != null) {
+            final TaskFragmentOperation operation = new TaskFragmentOperation.Builder(
+                    OP_TYPE_REORDER_TO_FRONT).build();
+            wct.addTaskFragmentOperation(
+                    pinnedContainer.getSecondaryContainer().getTaskFragmentToken(), operation);
+        }
     }
 
     @Override
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
index 4b15bb1..969e3ed 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
@@ -51,11 +51,15 @@
 
     /** Active TaskFragments in this Task. */
     @NonNull
-    final List<TaskFragmentContainer> mContainers = new ArrayList<>();
+    private final List<TaskFragmentContainer> mContainers = new ArrayList<>();
 
     /** Active split pairs in this Task. */
     @NonNull
-    final List<SplitContainer> mSplitContainers = new ArrayList<>();
+    private final List<SplitContainer> mSplitContainers = new ArrayList<>();
+
+    /** Active pin split pair in this Task. */
+    @Nullable
+    private SplitPinContainer mSplitPinContainer;
 
     @NonNull
     private final Configuration mConfiguration;
@@ -174,11 +178,28 @@
     }
 
     @Nullable
-    TaskFragmentContainer getTopTaskFragmentContainer() {
-        if (mContainers.isEmpty()) {
-            return null;
+    TaskFragmentContainer getTopNonFinishingTaskFragmentContainer() {
+        for (int i = mContainers.size() - 1; i >= 0; i--) {
+            final TaskFragmentContainer container = mContainers.get(i);
+            if (!container.isFinished()) {
+                return container;
+            }
         }
-        return mContainers.get(mContainers.size() - 1);
+        return null;
+    }
+
+    /** Gets a non-finishing container below the given one. */
+    @Nullable
+    TaskFragmentContainer getNonFinishingTaskFragmentContainerBelow(
+            @NonNull TaskFragmentContainer current) {
+        final int index = mContainers.indexOf(current);
+        for (int i = index - 1; i >= 0; i--) {
+            final TaskFragmentContainer container = mContainers.get(i);
+            if (!container.isFinished()) {
+                return container;
+            }
+        }
+        return null;
     }
 
     @Nullable
@@ -207,6 +228,107 @@
         return false;
     }
 
+    /**
+     * Returns a list of {@link SplitContainer}. Do not modify the containers directly on the
+     * returned list. Use {@link #addSplitContainer} or {@link #removeSplitContainers} instead.
+     */
+    @NonNull
+    List<SplitContainer> getSplitContainers() {
+        return mSplitContainers;
+    }
+
+    void addSplitContainer(@NonNull SplitContainer splitContainer) {
+        if (splitContainer instanceof SplitPinContainer) {
+            mSplitPinContainer = (SplitPinContainer) splitContainer;
+            mSplitContainers.add(splitContainer);
+            return;
+        }
+
+        // Keeps the SplitPinContainer on the top of the list.
+        mSplitContainers.remove(mSplitPinContainer);
+        mSplitContainers.add(splitContainer);
+        if (mSplitPinContainer != null) {
+            mSplitContainers.add(mSplitPinContainer);
+        }
+    }
+
+    void removeSplitContainers(@NonNull List<SplitContainer> containers) {
+        mSplitContainers.removeAll(containers);
+    }
+
+    void removeSplitPinContainer() {
+        mSplitContainers.remove(mSplitPinContainer);
+        mSplitPinContainer = null;
+    }
+
+    @Nullable
+    SplitPinContainer getSplitPinContainer() {
+        return mSplitPinContainer;
+    }
+
+    void addTaskFragmentContainer(@NonNull TaskFragmentContainer taskFragmentContainer) {
+        mContainers.add(taskFragmentContainer);
+        onTaskFragmentContainerUpdated();
+    }
+
+    void addTaskFragmentContainer(int index, @NonNull TaskFragmentContainer taskFragmentContainer) {
+        mContainers.add(index, taskFragmentContainer);
+        onTaskFragmentContainerUpdated();
+    }
+
+    void removeTaskFragmentContainer(@NonNull TaskFragmentContainer taskFragmentContainer) {
+        mContainers.remove(taskFragmentContainer);
+        onTaskFragmentContainerUpdated();
+    }
+
+    void removeTaskFragmentContainers(@NonNull List<TaskFragmentContainer> taskFragmentContainer) {
+        mContainers.removeAll(taskFragmentContainer);
+        onTaskFragmentContainerUpdated();
+    }
+
+    void clearTaskFragmentContainer() {
+        mContainers.clear();
+        onTaskFragmentContainerUpdated();
+    }
+
+    /**
+     * Returns a list of {@link TaskFragmentContainer}. Do not modify the containers directly on
+     * the returned list. Use {@link #addTaskFragmentContainer},
+     * {@link #removeTaskFragmentContainer} or other related methods instead.
+     */
+    @NonNull
+    List<TaskFragmentContainer> getTaskFragmentContainers() {
+        return mContainers;
+    }
+
+    private void onTaskFragmentContainerUpdated() {
+        if (mSplitPinContainer == null) {
+            return;
+        }
+
+        final TaskFragmentContainer pinnedContainer = mSplitPinContainer.getSecondaryContainer();
+        final int pinnedContainerIndex = mContainers.indexOf(pinnedContainer);
+        if (pinnedContainerIndex <= 0) {
+            removeSplitPinContainer();
+            return;
+        }
+
+        // Ensure the pinned container is top-most.
+        if (pinnedContainerIndex != mContainers.size() - 1) {
+            mContainers.remove(pinnedContainer);
+            mContainers.add(pinnedContainer);
+        }
+
+        // Update the primary container adjacent to the pinned container if needed.
+        final TaskFragmentContainer adjacentContainer =
+                getNonFinishingTaskFragmentContainerBelow(pinnedContainer);
+        if (adjacentContainer == null) {
+            removeSplitPinContainer();
+        } else if (mSplitPinContainer.getPrimaryContainer() != adjacentContainer) {
+            mSplitPinContainer.setPrimaryContainer(adjacentContainer);
+        }
+    }
+
     /** Adds the descriptors of split states in this Task to {@code outSplitStates}. */
     void getSplitStates(@NonNull List<SplitInfo> outSplitStates) {
         for (SplitContainer container : mSplitContainers) {
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
index 60be9d1..61df335 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
@@ -180,23 +180,25 @@
                 throw new IllegalArgumentException(
                         "pairedPrimaryContainer must be in the same Task");
             }
-            final int primaryIndex = taskContainer.mContainers.indexOf(pairedPrimaryContainer);
-            taskContainer.mContainers.add(primaryIndex + 1, this);
+            final int primaryIndex = taskContainer.indexOf(pairedPrimaryContainer);
+            taskContainer.addTaskFragmentContainer(primaryIndex + 1, this);
         } else if (pendingAppearedActivity != null) {
             // The TaskFragment will be positioned right above the pending appeared Activity. If any
             // existing TaskFragment is empty with pending Intent, it is likely that the Activity of
             // the pending Intent hasn't been created yet, so the new Activity should be below the
             // empty TaskFragment.
-            int i = taskContainer.mContainers.size() - 1;
+            final List<TaskFragmentContainer> containers =
+                    taskContainer.getTaskFragmentContainers();
+            int i = containers.size() - 1;
             for (; i >= 0; i--) {
-                final TaskFragmentContainer container = taskContainer.mContainers.get(i);
+                final TaskFragmentContainer container = containers.get(i);
                 if (!container.isEmpty() || container.getPendingAppearedIntent() == null) {
                     break;
                 }
             }
-            taskContainer.mContainers.add(i + 1, this);
+            taskContainer.addTaskFragmentContainer(i + 1, this);
         } else {
-            taskContainer.mContainers.add(this);
+            taskContainer.addTaskFragmentContainer(this);
         }
         if (pendingAppearedActivity != null) {
             addPendingAppearedActivity(pendingAppearedActivity);
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
index a45a8a1..ccf9552 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
@@ -98,14 +98,6 @@
         mTaskFragmentOrganizer = taskFragmentOrganizer;
     }
 
-    /** Registers to listen to {@link CommonFoldingFeature} changes */
-    public void addFoldingStateChangedCallback(
-            java.util.function.Consumer<List<CommonFoldingFeature>> consumer) {
-        synchronized (mLock) {
-            mFoldingFeatureProducer.addDataChangedCallback(consumer);
-        }
-    }
-
     /**
      * Adds a listener interested in receiving updates to {@link WindowLayoutInfo}
      *
@@ -304,7 +296,7 @@
      * {@link IllegalArgumentException} since this can cause negative UI effects down stream.
      *
      * @param context a proxy for the {@link android.view.Window} that contains the
-     * {@link DisplayFeature}.
+     *                {@link DisplayFeature}.
      * @return a {@link List}  of {@link DisplayFeature}s that are within the
      * {@link android.view.Window} of the {@link Activity}
      */
@@ -336,10 +328,32 @@
             rotateRectToDisplayRotation(displayId, featureRect);
             transformToWindowSpaceRect(windowConfiguration, featureRect);
 
-            if (!isZero(featureRect)) {
+            if (isZero(featureRect)) {
                 // TODO(b/228641877): Remove guarding when fixed.
-                features.add(new FoldingFeature(featureRect, baseFeature.getType(), state));
+                continue;
             }
+            if (featureRect.left != 0 && featureRect.top != 0) {
+                throw new IllegalArgumentException("Bounding rectangle must start at the top or "
+                        + "left of the window. BaseFeatureRect: " + baseFeature.getRect()
+                        + ", FeatureRect: " + featureRect
+                        + ", WindowConfiguration: " + windowConfiguration);
+
+            }
+            if (featureRect.left == 0
+                    && featureRect.width() != windowConfiguration.getBounds().width()) {
+                throw new IllegalArgumentException("Horizontal FoldingFeature must have full width."
+                        + " BaseFeatureRect: " + baseFeature.getRect()
+                        + ", FeatureRect: " + featureRect
+                        + ", WindowConfiguration: " + windowConfiguration);
+            }
+            if (featureRect.top == 0
+                    && featureRect.height() != windowConfiguration.getBounds().height()) {
+                throw new IllegalArgumentException("Vertical FoldingFeature must have full height."
+                        + " BaseFeatureRect: " + baseFeature.getRect()
+                        + ", FeatureRect: " + featureRect
+                        + ", WindowConfiguration: " + windowConfiguration);
+            }
+            features.add(new FoldingFeature(featureRect, baseFeature.getType(), state));
         }
         return features;
     }
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
index ff08782..9af1fe91 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
@@ -183,23 +183,23 @@
         // tf2 has running activity so is active.
         final TaskFragmentContainer tf2 = mock(TaskFragmentContainer.class);
         doReturn(1).when(tf2).getRunningActivityCount();
-        taskContainer.mContainers.add(tf2);
+        taskContainer.addTaskFragmentContainer(tf2);
         // tf3 is finished so is not active.
         final TaskFragmentContainer tf3 = mock(TaskFragmentContainer.class);
         doReturn(true).when(tf3).isFinished();
         doReturn(false).when(tf3).isWaitingActivityAppear();
-        taskContainer.mContainers.add(tf3);
+        taskContainer.addTaskFragmentContainer(tf3);
         mSplitController.mTaskContainers.put(TASK_ID, taskContainer);
 
         assertWithMessage("Must return tf2 because tf3 is not active.")
                 .that(mSplitController.getTopActiveContainer(TASK_ID)).isEqualTo(tf2);
 
-        taskContainer.mContainers.remove(tf3);
+        taskContainer.removeTaskFragmentContainer(tf3);
 
         assertWithMessage("Must return tf2 because tf2 has running activity.")
                 .that(mSplitController.getTopActiveContainer(TASK_ID)).isEqualTo(tf2);
 
-        taskContainer.mContainers.remove(tf2);
+        taskContainer.removeTaskFragmentContainer(tf2);
 
         assertWithMessage("Must return tf because we are waiting for tf1 to appear.")
                 .that(mSplitController.getTopActiveContainer(TASK_ID)).isEqualTo(tf1);
@@ -320,11 +320,11 @@
         doReturn(tf).when(splitContainer).getSecondaryContainer();
         doReturn(createTestTaskContainer()).when(splitContainer).getTaskContainer();
         doReturn(createSplitRule(mActivity, mActivity)).when(splitContainer).getSplitRule();
-        final List<SplitContainer> splitContainers =
-                mSplitController.getTaskContainer(TASK_ID).mSplitContainers;
-        splitContainers.add(splitContainer);
+        final TaskContainer taskContainer = mSplitController.getTaskContainer(TASK_ID);
+        taskContainer.addSplitContainer(splitContainer);
         // Add a mock SplitContainer on top of splitContainer
-        splitContainers.add(1, mock(SplitContainer.class));
+        final SplitContainer splitContainer2 = mock(SplitContainer.class);
+        taskContainer.addSplitContainer(splitContainer2);
 
         mSplitController.updateContainer(mTransaction, tf);
 
@@ -332,7 +332,9 @@
 
         // Verify if one or both containers in the top SplitContainer are finished,
         // dismissPlaceholder() won't be called.
-        splitContainers.remove(1);
+        final ArrayList<SplitContainer> splitContainersToRemove = new ArrayList<>();
+        splitContainersToRemove.add(splitContainer2);
+        taskContainer.removeSplitContainers(splitContainersToRemove);
         doReturn(true).when(tf).isFinished();
 
         mSplitController.updateContainer(mTransaction, tf);
@@ -363,7 +365,8 @@
         final Activity r1 = createMockActivity();
         addSplitTaskFragments(r0, r1);
         final TaskContainer taskContainer = mSplitController.getTaskContainer(TASK_ID);
-        final TaskFragmentContainer taskFragmentContainer = taskContainer.mContainers.get(0);
+        final TaskFragmentContainer taskFragmentContainer =
+                taskContainer.getTaskFragmentContainers().get(0);
         spyOn(taskContainer);
 
         // No update when the Task is invisible.
@@ -377,7 +380,7 @@
         doReturn(true).when(taskContainer).isVisible();
         mSplitController.updateContainer(mTransaction, taskFragmentContainer);
 
-        verify(mSplitPresenter).updateSplitContainer(taskContainer.mSplitContainers.get(0),
+        verify(mSplitPresenter).updateSplitContainer(taskContainer.getSplitContainers().get(0),
                 mTransaction);
     }
 
@@ -1090,8 +1093,8 @@
         verify(mTransaction).finishActivity(mActivity.getActivityToken());
         verify(mTransaction).finishActivity(secondaryActivity0.getActivityToken());
         verify(mTransaction).finishActivity(secondaryActivity1.getActivityToken());
-        assertTrue(taskContainer.mContainers.isEmpty());
-        assertTrue(taskContainer.mSplitContainers.isEmpty());
+        assertTrue(taskContainer.getTaskFragmentContainers().isEmpty());
+        assertTrue(taskContainer.getSplitContainers().isEmpty());
     }
 
     @Test
@@ -1363,15 +1366,13 @@
         TaskFragmentContainer tf = mSplitController.newContainer(mActivity, TASK_ID);
         tf.setInfo(mTransaction, createMockTaskFragmentInfo(tf, mActivity));
 
-        List<TaskFragmentContainer> containers = mSplitController.mTaskContainers.get(TASK_ID)
-                .mContainers;
-
-        assertEquals(containers.get(0), tf);
+        final TaskContainer taskContainer = mSplitController.mTaskContainers.get(TASK_ID);
+        assertEquals(taskContainer.getTaskFragmentContainers().get(0), tf);
 
         mSplitController.finishActivityStacks(Collections.singleton(tf.getTaskFragmentToken()));
 
         verify(mSplitPresenter).deleteTaskFragment(any(), eq(tf.getTaskFragmentToken()));
-        assertTrue(containers.isEmpty());
+        assertTrue(taskContainer.getTaskFragmentContainers().isEmpty());
     }
 
     @Test
@@ -1381,10 +1382,8 @@
         bottomTf.setInfo(mTransaction, createMockTaskFragmentInfo(bottomTf, mActivity));
         topTf.setInfo(mTransaction, createMockTaskFragmentInfo(topTf, createMockActivity()));
 
-        List<TaskFragmentContainer> containers = mSplitController.mTaskContainers.get(TASK_ID)
-                .mContainers;
-
-        assertEquals(containers.size(), 2);
+        final TaskContainer taskContainer = mSplitController.mTaskContainers.get(TASK_ID);
+        assertEquals(taskContainer.getTaskFragmentContainers().size(), 2);
 
         Set<IBinder> activityStackTokens = new ArraySet<>(new IBinder[]{
                 topTf.getTaskFragmentToken(), bottomTf.getTaskFragmentToken()});
@@ -1403,7 +1402,7 @@
                         + "regardless of the order in ActivityStack set",
                 topTf.getTaskFragmentToken(), fragmentTokens.get(1));
 
-        assertTrue(containers.isEmpty());
+        assertTrue(taskContainer.getTaskFragmentContainers().isEmpty());
     }
 
     @Test
@@ -1463,6 +1462,51 @@
         verify(testRecord).apply(eq(false));
     }
 
+    @Test
+    public void testPinTopActivityStack() {
+        // Create two activities.
+        final Activity primaryActivity = createMockActivity();
+        final Activity secondaryActivity = createMockActivity();
+
+        // Unable to pin if not being embedded.
+        SplitPinRule splitPinRule = new SplitPinRule.Builder(new SplitAttributes.Builder().build(),
+                parentWindowMetrics -> true /* parentWindowMetricsPredicate */).build();
+        assertFalse(mSplitController.pinTopActivityStack(TASK_ID, splitPinRule));
+
+        // Split the two activities.
+        addSplitTaskFragments(primaryActivity, secondaryActivity);
+        final TaskFragmentContainer primaryContainer =
+                mSplitController.getContainerWithActivity(primaryActivity);
+        spyOn(primaryContainer);
+
+        // Unable to pin if no valid TaskFragment.
+        doReturn(true).when(primaryContainer).isFinished();
+        assertFalse(mSplitController.pinTopActivityStack(TASK_ID, splitPinRule));
+
+        // Otherwise, should pin successfully.
+        doReturn(false).when(primaryContainer).isFinished();
+        assertTrue(mSplitController.pinTopActivityStack(TASK_ID, splitPinRule));
+
+        // Unable to pin if there is already a pinned TaskFragment
+        assertFalse(mSplitController.pinTopActivityStack(TASK_ID, splitPinRule));
+
+        // Unable to pin on an unknown Task.
+        assertFalse(mSplitController.pinTopActivityStack(TASK_ID + 1, splitPinRule));
+
+        // Gets the current size of all the SplitContainers.
+        final TaskContainer taskContainer = mSplitController.getTaskContainer(TASK_ID);
+        final int splitContainerCount = taskContainer.getSplitContainers().size();
+
+        // Create another activity and split with primary activity.
+        final Activity thirdActivity = createMockActivity();
+        addSplitTaskFragments(primaryActivity, thirdActivity);
+
+        // Ensure another SplitContainer is added and the pinned TaskFragment still on top
+        assertTrue(taskContainer.getSplitContainers().size() == splitContainerCount + +1);
+        assertTrue(mSplitController.getTopActiveContainer(TASK_ID).getTopNonFinishingActivity()
+                == secondaryActivity);
+    }
+
     /** Creates a mock activity in the organizer process. */
     private Activity createMockActivity() {
         return createMockActivity(TASK_ID);
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java
index 13e7092..000c65a 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java
@@ -127,7 +127,7 @@
         assertFalse(taskContainer.isEmpty());
 
         taskContainer.mFinishedContainer.add(tf.getTaskFragmentToken());
-        taskContainer.mContainers.clear();
+        taskContainer.clearTaskFragmentContainer();
 
         assertFalse(taskContainer.isEmpty());
     }
@@ -135,15 +135,15 @@
     @Test
     public void testGetTopTaskFragmentContainer() {
         final TaskContainer taskContainer = createTestTaskContainer();
-        assertNull(taskContainer.getTopTaskFragmentContainer());
+        assertNull(taskContainer.getTopNonFinishingTaskFragmentContainer());
 
         final TaskFragmentContainer tf0 = new TaskFragmentContainer(null /* activity */,
                 new Intent(), taskContainer, mController, null /* pairedPrimaryContainer */);
-        assertEquals(tf0, taskContainer.getTopTaskFragmentContainer());
+        assertEquals(tf0, taskContainer.getTopNonFinishingTaskFragmentContainer());
 
         final TaskFragmentContainer tf1 = new TaskFragmentContainer(null /* activity */,
                 new Intent(), taskContainer, mController, null /* pairedPrimaryContainer */);
-        assertEquals(tf1, taskContainer.getTopTaskFragmentContainer());
+        assertEquals(tf1, taskContainer.getTopNonFinishingTaskFragmentContainer());
     }
 
     @Test
@@ -152,13 +152,13 @@
         assertNull(taskContainer.getTopNonFinishingActivity());
 
         final TaskFragmentContainer tf0 = mock(TaskFragmentContainer.class);
-        taskContainer.mContainers.add(tf0);
+        taskContainer.addTaskFragmentContainer(tf0);
         final Activity activity0 = mock(Activity.class);
         doReturn(activity0).when(tf0).getTopNonFinishingActivity();
         assertEquals(activity0, taskContainer.getTopNonFinishingActivity());
 
         final TaskFragmentContainer tf1 = mock(TaskFragmentContainer.class);
-        taskContainer.mContainers.add(tf1);
+        taskContainer.addTaskFragmentContainer(tf1);
         assertEquals(activity0, taskContainer.getTopNonFinishingActivity());
 
         final Activity activity1 = mock(Activity.class);
diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp
index 54978bd..a605e2b 100644
--- a/libs/WindowManager/Shell/Android.bp
+++ b/libs/WindowManager/Shell/Android.bp
@@ -42,16 +42,19 @@
 filegroup {
     name: "wm_shell_util-sources",
     srcs: [
-        "src/com/android/wm/shell/util/**/*.java",
-        "src/com/android/wm/shell/common/split/SplitScreenConstants.java",
-        "src/com/android/wm/shell/sysui/ShellSharedConstants.java",
-        "src/com/android/wm/shell/common/TransactionPool.java",
-        "src/com/android/wm/shell/common/bubbles/*.java",
-        "src/com/android/wm/shell/common/TriangleShape.java",
         "src/com/android/wm/shell/animation/Interpolators.java",
+        "src/com/android/wm/shell/animation/PhysicsAnimator.kt",
+        "src/com/android/wm/shell/common/bubbles/*.kt",
+        "src/com/android/wm/shell/common/bubbles/*.java",
+        "src/com/android/wm/shell/common/magnetictarget/MagnetizedObject.kt",
+        "src/com/android/wm/shell/common/split/SplitScreenConstants.java",
+        "src/com/android/wm/shell/common/TransactionPool.java",
+        "src/com/android/wm/shell/common/TriangleShape.java",
+        "src/com/android/wm/shell/draganddrop/DragAndDropConstants.java",
         "src/com/android/wm/shell/pip/PipContentOverlay.java",
         "src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java",
-        "src/com/android/wm/shell/draganddrop/DragAndDropConstants.java",
+        "src/com/android/wm/shell/sysui/ShellSharedConstants.java",
+        "src/com/android/wm/shell/util/**/*.java",
     ],
     path: "src",
 }
@@ -112,8 +115,7 @@
     name: "protolog.json.gz",
     srcs: [":generate-wm_shell_protolog.json"],
     out: ["wmshell.protolog.json.gz"],
-    cmd: "$(location minigzip) -c < $(in) > $(out)",
-    tools: ["minigzip"],
+    cmd: "gzip -c < $(in) > $(out)",
 }
 
 prebuilt_etc {
@@ -155,7 +157,6 @@
         "kotlinx-coroutines-android",
         "kotlinx-coroutines-core",
         "iconloader_base",
-        "protolog-lib",
         "WindowManager-Shell-proto",
         "dagger2",
         "jsr330",
diff --git a/libs/WindowManager/Shell/res/drawable/bubble_manage_menu_section.xml b/libs/WindowManager/Shell/res/drawable/bubble_manage_menu_section.xml
new file mode 100644
index 0000000..d99d64d
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/bubble_manage_menu_section.xml
@@ -0,0 +1,24 @@
+<?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.
+  -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_pressed="true">
+        <ripple android:color="#99999999">
+            <item android:drawable="@drawable/bubble_manage_menu_bg" />
+        </ripple>
+    </item>
+    <item android:drawable="@drawable/bubble_manage_menu_bg" />
+</selector>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/decor_handle_dark.xml b/libs/WindowManager/Shell/res/drawable/decor_handle_dark.xml
index 5d77713..ce24275 100644
--- a/libs/WindowManager/Shell/res/drawable/decor_handle_dark.xml
+++ b/libs/WindowManager/Shell/res/drawable/decor_handle_dark.xml
@@ -13,13 +13,20 @@
   ~ 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"
-    android:viewportHeight="24">
-    <group android:translateY="8.0">
+<vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="128dp"
+    android:height="4dp"
+    android:viewportWidth="128"
+    android:viewportHeight="4"
+    >
+    <group>
+        <clip-path
+            android:pathData="M2 0H126C127.105 0 128 0.895431 128 2C128 3.10457 127.105 4 126 4H2C0.895431 4 0 3.10457 0 2C0 0.895431 0.895431 0 2 0Z"
+            />
         <path
-            android:fillColor="@android:color/black" android:pathData="M3,5V3H21V5Z"/>
+            android:pathData="M0 0V4H128V0"
+            android:fillColor="@android:color/black"
+            />
     </group>
 </vector>
diff --git a/libs/WindowManager/Shell/res/drawable/ic_expand_less.xml b/libs/WindowManager/Shell/res/drawable/ic_expand_less.xml
new file mode 100644
index 0000000..f450846
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/ic_expand_less.xml
@@ -0,0 +1,25 @@
+<?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"
+    android:viewportHeight="24">
+  <path
+      android:pathData="M18.59,16.41L20,15L12,7L4,15L5.41,16.41L12,9.83"
+      android:fillColor="#5F6368"/>
+</vector>
diff --git a/libs/WindowManager/Shell/res/layout/bubble_bar_menu_item.xml b/libs/WindowManager/Shell/res/layout/bubble_bar_menu_item.xml
new file mode 100644
index 0000000..ddcd5c6
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/bubble_bar_menu_item.xml
@@ -0,0 +1,41 @@
+<?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.wm.shell.bubbles.bar.BubbleBarMenuItemView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="horizontal"
+    android:layout_width="match_parent"
+    android:layout_height="@dimen/bubble_bar_manage_menu_item_height"
+    android:gravity="center_vertical"
+    android:paddingStart="@dimen/bubble_menu_padding"
+    android:paddingEnd="@dimen/bubble_menu_padding"
+    android:background="@drawable/bubble_manage_menu_row">
+
+    <ImageView
+        android:id="@+id/bubble_bar_menu_item_icon"
+        android:layout_width="@dimen/bubble_bar_manage_menu_item_icon_size"
+        android:layout_height="@dimen/bubble_bar_manage_menu_item_icon_size"
+        android:contentDescription="@null"/>
+
+    <TextView
+        android:id="@+id/bubble_bar_menu_item_title"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="16dp"
+        android:textColor="?android:attr/textColorPrimary"
+        android:textAppearance="@*android:style/TextAppearance.DeviceDefault" />
+
+</com.android.wm.shell.bubbles.bar.BubbleBarMenuItemView>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/bubble_bar_menu_view.xml b/libs/WindowManager/Shell/res/layout/bubble_bar_menu_view.xml
new file mode 100644
index 0000000..82e5aee
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/bubble_bar_menu_view.xml
@@ -0,0 +1,77 @@
+<?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.wm.shell.bubbles.bar.BubbleBarMenuView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:layout_gravity="center_horizontal"
+    android:minWidth="@dimen/bubble_bar_manage_menu_min_width"
+    android:orientation="vertical"
+    android:elevation="@dimen/bubble_manage_menu_elevation"
+    android:paddingTop="@dimen/bubble_bar_manage_menu_padding_top"
+    android:paddingHorizontal="@dimen/bubble_bar_manage_menu_padding"
+    android:paddingBottom="@dimen/bubble_bar_manage_menu_padding"
+    android:clipToPadding="false">
+
+    <LinearLayout
+        android:id="@+id/bubble_bar_manage_menu_bubble_section"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/bubble_bar_manage_menu_item_height"
+        android:orientation="horizontal"
+        android:gravity="center_vertical"
+        android:paddingStart="14dp"
+        android:paddingEnd="12dp"
+        android:background="@drawable/bubble_manage_menu_section"
+        android:elevation="@dimen/bubble_manage_menu_elevation">
+
+        <ImageView
+            android:id="@+id/bubble_bar_manage_menu_bubble_icon"
+            android:layout_width="@dimen/bubble_menu_icon_size"
+            android:layout_height="@dimen/bubble_menu_icon_size"
+            android:contentDescription="@null" />
+
+        <TextView
+            android:id="@+id/bubble_bar_manage_menu_bubble_title"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="8dp"
+            android:layout_weight="1"
+            android:textColor="?android:attr/textColorPrimary"
+            android:textAppearance="@*android:style/TextAppearance.DeviceDefault" />
+
+        <ImageView
+            android:id="@+id/bubble_bar_manage_menu_dismiss_icon"
+            android:layout_width="@dimen/bubble_bar_manage_menu_dismiss_icon_size"
+            android:layout_height="@dimen/bubble_bar_manage_menu_dismiss_icon_size"
+            android:layout_marginStart="8dp"
+            android:contentDescription="@null"
+            android:src="@drawable/ic_expand_less"
+            app:tint="?android:attr/textColorPrimary" />
+
+    </LinearLayout>
+
+    <LinearLayout
+        android:id="@+id/bubble_bar_manage_menu_actions_section"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical"
+        android:layout_marginTop="@dimen/bubble_bar_manage_menu_section_spacing"
+        android:background="@drawable/bubble_manage_menu_bg"
+        android:elevation="@dimen/bubble_manage_menu_elevation" />
+
+</com.android.wm.shell.bubbles.bar.BubbleBarMenuView>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_focused_window_decor.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_focused_window_decor.xml
index 1d6864c..0ca912e 100644
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_focused_window_decor.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_focused_window_decor.xml
@@ -28,6 +28,7 @@
         android:layout_width="176dp"
         android:layout_height="42dp"
         android:paddingHorizontal="24dp"
+        android:paddingVertical="19dp"
         android:contentDescription="@string/handle_text"
         android:src="@drawable/decor_handle_dark"
         tools:tint="@color/desktop_mode_caption_handle_bar_dark"
diff --git a/libs/WindowManager/Shell/res/values-af/strings.xml b/libs/WindowManager/Shell/res/values-af/strings.xml
index ee00e26..de4a225 100644
--- a/libs/WindowManager/Shell/res/values-af/strings.xml
+++ b/libs/WindowManager/Shell/res/values-af/strings.xml
@@ -32,13 +32,13 @@
     <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Verander grootte"</string>
     <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Hou vas"</string>
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Laat los"</string>
-    <string name="dock_forced_resizable" msgid="1749750436092293116">"Program sal dalk nie met verdeelde skerm werk nie."</string>
-    <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Program steun nie verdeelde skerm nie."</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>
     <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"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="703810061635792791">"Skermverdeler"</string>
-    <string name="divider_title" msgid="5482989479865361192">"Skermverdeler"</string>
+    <string name="accessibility_divider" msgid="6407584574218956849">"Skermverdeler"</string>
+    <string name="divider_title" msgid="1963391955593749442">"Skermverdeler"</string>
     <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Volskerm links"</string>
     <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Links 70%"</string>
     <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Links 50%"</string>
@@ -49,14 +49,10 @@
     <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Bo 50%"</string>
     <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Bo 30%"</string>
     <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Volskerm onder"</string>
-    <!-- no translation found for accessibility_split_left (1713683765575562458) -->
-    <skip />
-    <!-- no translation found for accessibility_split_right (8441001008181296837) -->
-    <skip />
-    <!-- no translation found for accessibility_split_top (2789329702027147146) -->
-    <skip />
-    <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
-    <skip />
+    <string name="accessibility_split_left" msgid="1713683765575562458">"Verdeel links"</string>
+    <string name="accessibility_split_right" msgid="8441001008181296837">"Verdeel regs"</string>
+    <string name="accessibility_split_top" msgid="2789329702027147146">"Verdeel bo"</string>
+    <string name="accessibility_split_bottom" msgid="8694551025220868191">"Verdeel onder"</string>
     <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Gebruik eenhandmodus"</string>
     <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Swiep van die onderkant van die skerm af op of tik enige plek bo die program om uit te gaan"</string>
     <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Begin eenhandmodus"</string>
@@ -88,7 +84,7 @@
     <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Nie opgelos nie?\nTik om terug te stel"</string>
     <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Geen kamerakwessies nie? Tik om toe te maak."</string>
     <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Sien en doen meer"</string>
-    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Sleep ’n ander program in vir verdeelde skerm"</string>
+    <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Sleep ’n ander app in vir verdeelde skerm"</string>
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Dubbeltik buite ’n program om dit te herposisioneer"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"Het dit"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Vou uit vir meer inligting."</string>
@@ -97,14 +93,21 @@
     <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Kanselleer"</string>
     <string name="letterbox_restart_restart" msgid="8529976234412442973">"Herbegin"</string>
     <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Moenie weer wys nie"</string>
+    <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Dubbeltik om\nhierdie app te skuif"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"Maksimeer"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Maak klein"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Maak toe"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Terug"</string>
     <string name="handle_text" msgid="1766582106752184456">"Handvatsel"</string>
+    <string name="app_icon_text" msgid="2823268023931811747">"Appikoon"</string>
     <string name="fullscreen_text" msgid="1162316685217676079">"Volskerm"</string>
     <string name="desktop_text" msgid="1077633567027630454">"Rekenaarmodus"</string>
     <string name="split_screen_text" msgid="1396336058129570886">"Verdeelde skerm"</string>
     <string name="more_button_text" msgid="3655388105592893530">"Meer"</string>
     <string name="float_button_text" msgid="9221657008391364581">"Sweef"</string>
+    <string name="select_text" msgid="5139083974039906583">"Kies"</string>
+    <string name="screenshot_text" msgid="1477704010087786671">"Skermskoot"</string>
+    <string name="close_text" msgid="4986518933445178928">"Maak toe"</string>
+    <string name="collapse_menu_text" msgid="7515008122450342029">"Maak kieslys toe"</string>
+    <string name="expand_menu_text" msgid="3847736164494181168">"Maak kieslys oop"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-am/strings.xml b/libs/WindowManager/Shell/res/values-am/strings.xml
index 781038f..9cb4435 100644
--- a/libs/WindowManager/Shell/res/values-am/strings.xml
+++ b/libs/WindowManager/Shell/res/values-am/strings.xml
@@ -32,31 +32,27 @@
     <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"መጠን ይቀይሩ"</string>
     <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Stash"</string>
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Unstash"</string>
-    <string name="dock_forced_resizable" msgid="1749750436092293116">"መተግበሪያ ከተከፈለ ማያ ገጽ ጋር ላይሠራ ይችላል"</string>
-    <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"መተግበሪያው የተከፈለ ማያ ገጽን አይደግፍም።"</string>
+    <string name="dock_forced_resizable" msgid="7429086980048964687">"መተግበሪያ ከተከፈለ ማያ ገፅ ጋር ላይሠራ ይችላል"</string>
+    <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"መተግበሪያው የተከፈለ ማያ ገጽን አይደግፍም"</string>
     <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"ይህ መተግበሪያ መከፈት የሚችለው በ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="703810061635792791">"የተከፈለ የማያ ገጽ ከፋይ"</string>
-    <string name="divider_title" msgid="5482989479865361192">"የተከፈለ የማያ ገጽ ከፋይ"</string>
-    <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"የግራ ሙሉ ማያ ገጽ"</string>
+    <string name="accessibility_divider" msgid="6407584574218956849">"የተከፈለ የማያ ገፅ ከፋይ"</string>
+    <string name="divider_title" msgid="1963391955593749442">"የተከፈለ የማያ ገፅ ከፋይ"</string>
+    <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"የግራ ሙሉ ማያ ገፅ"</string>
     <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"ግራ 70%"</string>
     <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"ግራ 50%"</string>
     <string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"ግራ 30%"</string>
-    <string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"የቀኝ ሙሉ ማያ ገጽ"</string>
-    <string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"የላይ ሙሉ ማያ ገጽ"</string>
+    <string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"የቀኝ ሙሉ ማያ ገፅ"</string>
+    <string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"የላይ ሙሉ ማያ ገፅ"</string>
     <string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"ከላይ 70%"</string>
     <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"ከላይ 50%"</string>
     <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"ከላይ 30%"</string>
-    <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"የታች ሙሉ ማያ ገጽ"</string>
-    <!-- no translation found for accessibility_split_left (1713683765575562458) -->
-    <skip />
-    <!-- no translation found for accessibility_split_right (8441001008181296837) -->
-    <skip />
-    <!-- no translation found for accessibility_split_top (2789329702027147146) -->
-    <skip />
-    <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
-    <skip />
+    <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"የታች ሙሉ ማያ ገፅ"</string>
+    <string name="accessibility_split_left" msgid="1713683765575562458">"ወደ ግራ ከፋፍል"</string>
+    <string name="accessibility_split_right" msgid="8441001008181296837">"ወደ ቀኝ ከፋፍል"</string>
+    <string name="accessibility_split_top" msgid="2789329702027147146">"ወደ ላይ ከፋፍል"</string>
+    <string name="accessibility_split_bottom" msgid="8694551025220868191">"ወደ ታች ከፋፍል"</string>
     <string name="one_handed_tutorial_title" msgid="4583241688067426350">"ባለአንድ እጅ ሁነታን በመጠቀም ላይ"</string>
     <string name="one_handed_tutorial_description" msgid="3486582858591353067">"ለመውጣት ከማያው ግርጌ ወደ ላይ ይጥረጉ ወይም ከመተግበሪያው በላይ ማንኛውም ቦታ ላይ መታ ያድርጉ"</string>
     <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"ባለአንድ እጅ ሁነታ ጀምር"</string>
@@ -88,7 +84,7 @@
     <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"አልተስተካከለም?\nለማህደር መታ ያድርጉ"</string>
     <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"ምንም የካሜራ ችግሮች የሉም? ለማሰናበት መታ ያድርጉ።"</string>
     <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"ተጨማሪ ይመልከቱ እና ያድርጉ"</string>
-    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"ለተከፈለ ማያ ገጽ ሌላ መተግበሪያ ይጎትቱ"</string>
+    <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"ለተከፈለ ማያ ገፅ ሌላ መተግበሪያ ይጎትቱ"</string>
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"ቦታውን ለመቀየር ከመተግበሪያው ውጪ ሁለቴ መታ ያድርጉ"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"ገባኝ"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"ለተጨማሪ መረጃ ይዘርጉ።"</string>
@@ -97,14 +93,21 @@
     <string name="letterbox_restart_cancel" msgid="1342209132692537805">"ይቅር"</string>
     <string name="letterbox_restart_restart" msgid="8529976234412442973">"እንደገና ያስጀምሩ"</string>
     <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"ዳግም አታሳይ"</string>
+    <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"ይህን መተግበሪያ\nለማንቀሳቀስ ሁለቴ መታ ያድርጉ"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"አስፋ"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"አሳንስ"</string>
     <string name="close_button_text" msgid="2913281996024033299">"ዝጋ"</string>
     <string name="back_button_text" msgid="1469718707134137085">"ተመለስ"</string>
     <string name="handle_text" msgid="1766582106752184456">"መያዣ"</string>
+    <string name="app_icon_text" msgid="2823268023931811747">"የመተግበሪያ አዶ"</string>
     <string name="fullscreen_text" msgid="1162316685217676079">"ሙሉ ማያ"</string>
     <string name="desktop_text" msgid="1077633567027630454">"የዴስክቶፕ ሁነታ"</string>
-    <string name="split_screen_text" msgid="1396336058129570886">"የተከፈለ ማያ ገጽ"</string>
+    <string name="split_screen_text" msgid="1396336058129570886">"የተከፈለ ማያ ገፅ"</string>
     <string name="more_button_text" msgid="3655388105592893530">"ተጨማሪ"</string>
     <string name="float_button_text" msgid="9221657008391364581">"ተንሳፋፊ"</string>
+    <string name="select_text" msgid="5139083974039906583">"ምረጥ"</string>
+    <string name="screenshot_text" msgid="1477704010087786671">"ቅጽበታዊ ገፅ እይታ"</string>
+    <string name="close_text" msgid="4986518933445178928">"ዝጋ"</string>
+    <string name="collapse_menu_text" msgid="7515008122450342029">"ምናሌ ዝጋ"</string>
+    <string name="expand_menu_text" msgid="3847736164494181168">"ምናሌን ክፈት"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-am/strings_tv.xml b/libs/WindowManager/Shell/res/values-am/strings_tv.xml
index a6be578..84c1c67 100644
--- a/libs/WindowManager/Shell/res/values-am/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-am/strings_tv.xml
@@ -20,7 +20,7 @@
     <string name="notification_channel_tv_pip" msgid="2576686079160402435">"ሥዕል-ላይ-ሥዕል"</string>
     <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(ርዕስ የሌለው ፕሮግራም)"</string>
     <string name="pip_close" msgid="2955969519031223530">"ዝጋ"</string>
-    <string name="pip_fullscreen" msgid="7278047353591302554">"ሙሉ ማያ ገጽ"</string>
+    <string name="pip_fullscreen" msgid="7278047353591302554">"ሙሉ ማያ ገፅ"</string>
     <string name="pip_move" msgid="158770205886688553">"ውሰድ"</string>
     <string name="pip_expand" msgid="1051966011679297308">"ዘርጋ"</string>
     <string name="pip_collapse" msgid="3903295106641385962">"ሰብስብ"</string>
diff --git a/libs/WindowManager/Shell/res/values-ar/strings.xml b/libs/WindowManager/Shell/res/values-ar/strings.xml
index 7325da1..a714b49 100644
--- a/libs/WindowManager/Shell/res/values-ar/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ar/strings.xml
@@ -32,13 +32,13 @@
     <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"تغيير الحجم"</string>
     <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"إخفاء"</string>
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"إظهار"</string>
-    <string name="dock_forced_resizable" msgid="1749750436092293116">"قد لا يعمل التطبيق بشكل سليم في وضع \"تقسيم الشاشة\"."</string>
-    <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"التطبيق لا يتيح تقسيم الشاشة."</string>
+    <string name="dock_forced_resizable" msgid="7429086980048964687">"قد لا يعمل التطبيق بشكل سليم في وضع تقسيم الشاشة."</string>
+    <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"لا يعمل التطبيق في وضع تقسيم الشاشة."</string>
     <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"لا يمكن فتح هذا التطبيق إلا في نافذة واحدة."</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="703810061635792791">"أداة تقسيم الشاشة"</string>
-    <string name="divider_title" msgid="5482989479865361192">"أداة تقسيم الشاشة"</string>
+    <string name="accessibility_divider" msgid="6407584574218956849">"أداة تقسيم الشاشة"</string>
+    <string name="divider_title" msgid="1963391955593749442">"أداة تقسيم الشاشة"</string>
     <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"عرض النافذة اليسرى بملء الشاشة"</string>
     <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"ضبط حجم النافذة اليسرى ليكون ٧٠%"</string>
     <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"ضبط حجم النافذة اليسرى ليكون ٥٠%"</string>
@@ -49,14 +49,10 @@
     <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"ضبط حجم النافذة العلوية ليكون ٥٠%"</string>
     <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"ضبط حجم النافذة العلوية ليكون ٣٠%"</string>
     <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"عرض النافذة السفلية بملء الشاشة"</string>
-    <!-- no translation found for accessibility_split_left (1713683765575562458) -->
-    <skip />
-    <!-- no translation found for accessibility_split_right (8441001008181296837) -->
-    <skip />
-    <!-- no translation found for accessibility_split_top (2789329702027147146) -->
-    <skip />
-    <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
-    <skip />
+    <string name="accessibility_split_left" msgid="1713683765575562458">"تقسيم لليسار"</string>
+    <string name="accessibility_split_right" msgid="8441001008181296837">"تقسيم لليمين"</string>
+    <string name="accessibility_split_top" msgid="2789329702027147146">"تقسيم للأعلى"</string>
+    <string name="accessibility_split_bottom" msgid="8694551025220868191">"تقسيم للأسفل"</string>
     <string name="one_handed_tutorial_title" msgid="4583241688067426350">"استخدام وضع \"التصفح بيد واحدة\""</string>
     <string name="one_handed_tutorial_description" msgid="3486582858591353067">"للخروج، مرِّر سريعًا من أسفل الشاشة إلى أعلاها أو انقر في أي مكان فوق التطبيق."</string>
     <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"بدء وضع \"التصفح بيد واحدة\""</string>
@@ -88,7 +84,7 @@
     <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"ألم يتم حل المشكلة؟\nانقر للعودة"</string>
     <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"أليس هناك مشاكل في الكاميرا؟ انقر للإغلاق."</string>
     <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"استخدام تطبيقات متعدّدة في وقت واحد"</string>
-    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"اسحب تطبيقًا آخر لاستخدام وضع تقسيم الشاشة."</string>
+    <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"اسحب تطبيقًا آخر لاستخدام وضع تقسيم الشاشة."</string>
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"انقر مرّتين خارج تطبيق لتغيير موضعه."</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"حسنًا"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"التوسيع للحصول على مزيد من المعلومات"</string>
@@ -97,14 +93,21 @@
     <string name="letterbox_restart_cancel" msgid="1342209132692537805">"إلغاء"</string>
     <string name="letterbox_restart_restart" msgid="8529976234412442973">"إعادة التشغيل"</string>
     <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"عدم عرض مربّع حوار التأكيد مجددًا"</string>
+    <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"انقر مرّتَين لنقل\nهذا التطبيق."</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"تكبير"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"تصغير"</string>
     <string name="close_button_text" msgid="2913281996024033299">"إغلاق"</string>
     <string name="back_button_text" msgid="1469718707134137085">"رجوع"</string>
     <string name="handle_text" msgid="1766582106752184456">"مقبض"</string>
+    <string name="app_icon_text" msgid="2823268023931811747">"رمز التطبيق"</string>
     <string name="fullscreen_text" msgid="1162316685217676079">"ملء الشاشة"</string>
     <string name="desktop_text" msgid="1077633567027630454">"وضع سطح المكتب"</string>
     <string name="split_screen_text" msgid="1396336058129570886">"تقسيم الشاشة"</string>
     <string name="more_button_text" msgid="3655388105592893530">"المزيد"</string>
     <string name="float_button_text" msgid="9221657008391364581">"نافذة عائمة"</string>
+    <string name="select_text" msgid="5139083974039906583">"اختيار"</string>
+    <string name="screenshot_text" msgid="1477704010087786671">"لقطة شاشة"</string>
+    <string name="close_text" msgid="4986518933445178928">"إغلاق"</string>
+    <string name="collapse_menu_text" msgid="7515008122450342029">"إغلاق القائمة"</string>
+    <string name="expand_menu_text" msgid="3847736164494181168">"فتح القائمة"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-as/strings.xml b/libs/WindowManager/Shell/res/values-as/strings.xml
index 3fd705e..6d86747 100644
--- a/libs/WindowManager/Shell/res/values-as/strings.xml
+++ b/libs/WindowManager/Shell/res/values-as/strings.xml
@@ -32,13 +32,13 @@
     <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"আকাৰ সলনি কৰক"</string>
     <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"লুকুৱাওক"</string>
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"দেখুৱাওক"</string>
-    <string name="dock_forced_resizable" msgid="1749750436092293116">"এপ্‌টোৱে বিভাজিত স্ক্ৰীনৰ সৈতে কাম নকৰিব পাৰে।"</string>
-    <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"এপ্‌টোৱে বিভাজিত স্ক্ৰীন সমৰ্থন নকৰে।"</string>
+    <string name="dock_forced_resizable" msgid="7429086980048964687">"এপ্‌টোৱে বিভাজিত স্ক্ৰীনৰ সৈতে কাম নকৰিব পাৰে"</string>
+    <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"এপ্‌টোৱে বিভাজিত স্ক্ৰীন সমৰ্থন নকৰে"</string>
     <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"এই এপ্‌টো কেৱল ১ খন ৱিণ্ড’ত খুলিব পাৰি।"</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="703810061635792791">"স্প্লিট স্ক্ৰীনৰ বিভাজক"</string>
-    <string name="divider_title" msgid="5482989479865361192">"বিভাজিত স্ক্ৰীনৰ বিভাজক"</string>
+    <string name="accessibility_divider" msgid="6407584574218956849">"স্প্লিট স্ক্ৰীনৰ বিভাজক"</string>
+    <string name="divider_title" msgid="1963391955593749442">"স্প্লিট স্ক্ৰীনৰ বিভাজক"</string>
     <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"বাওঁফালৰ স্ক্ৰীনখন সম্পূৰ্ণ স্ক্ৰীন কৰক"</string>
     <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"বাওঁফালৰ স্ক্ৰীনখন ৭০% কৰক"</string>
     <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"বাওঁফালৰ স্ক্ৰীনখন ৫০% কৰক"</string>
@@ -49,14 +49,10 @@
     <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"শীর্ষ স্ক্ৰীনখন ৫০% কৰক"</string>
     <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"শীর্ষ স্ক্ৰীনখন ৩০% কৰক"</string>
     <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"তলৰ স্ক্ৰীনখন সম্পূৰ্ণ স্ক্ৰীন কৰক"</string>
-    <!-- no translation found for accessibility_split_left (1713683765575562458) -->
-    <skip />
-    <!-- no translation found for accessibility_split_right (8441001008181296837) -->
-    <skip />
-    <!-- no translation found for accessibility_split_top (2789329702027147146) -->
-    <skip />
-    <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
-    <skip />
+    <string name="accessibility_split_left" msgid="1713683765575562458">"বাওঁফালে বিভাজন কৰক"</string>
+    <string name="accessibility_split_right" msgid="8441001008181296837">"সোঁফালে বিভাজন কৰক"</string>
+    <string name="accessibility_split_top" msgid="2789329702027147146">"একেবাৰে ওপৰৰফালে বিভাজন কৰক"</string>
+    <string name="accessibility_split_bottom" msgid="8694551025220868191">"একেবাৰে তলৰফালে বিভাজন কৰক"</string>
     <string name="one_handed_tutorial_title" msgid="4583241688067426350">"এখন হাতেৰে ব্যৱহাৰ কৰা ম’ড ব্যৱহাৰ কৰা"</string>
     <string name="one_handed_tutorial_description" msgid="3486582858591353067">"বাহিৰ হ’বলৈ স্ক্ৰীনখনৰ একেবাৰে তলৰ পৰা ওপৰলৈ ছোৱাইপ কৰক অথবা এপ্‌টোৰ ওপৰত যিকোনো ঠাইত টিপক"</string>
     <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"এখন হাতেৰে ব্যৱহাৰ কৰা ম\'ডটো আৰম্ভ কৰক"</string>
@@ -88,7 +84,7 @@
     <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"এইটো সমাধান কৰা নাই নেকি?\nপূৰ্বাৱস্থালৈ নিবলৈ টিপক"</string>
     <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"কেমেৰাৰ কোনো সমস্যা নাই নেকি? অগ্ৰাহ্য কৰিবলৈ টিপক।"</string>
     <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"চাওক আৰু অধিক কৰক"</string>
-    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"বিভাজিত স্ক্ৰীনৰ বাবে অন্য এটা এপ্‌ টানি আনি এৰক"</string>
+    <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"বিভাজিত স্ক্ৰীনৰ বাবে অন্য এটা এপ্‌ টানি আনি এৰক"</string>
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"এপ্‌টোৰ স্থান সলনি কৰিবলৈ ইয়াৰ বাহিৰত দুবাৰ টিপক"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"বুজি পালোঁ"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"অধিক তথ্যৰ বাবে বিস্তাৰ কৰক।"</string>
@@ -97,14 +93,21 @@
     <string name="letterbox_restart_cancel" msgid="1342209132692537805">"বাতিল কৰক"</string>
     <string name="letterbox_restart_restart" msgid="8529976234412442973">"ৰিষ্টাৰ্ট কৰক"</string>
     <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"পুনৰাই নেদেখুৱাব"</string>
+    <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"এই এপ্‌টো\nস্থানান্তৰ কৰিবলৈ দুবাৰ টিপক"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"সৰ্বাধিক মাত্ৰালৈ বঢ়াওক"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"মিনিমাইজ কৰক"</string>
     <string name="close_button_text" msgid="2913281996024033299">"বন্ধ কৰক"</string>
     <string name="back_button_text" msgid="1469718707134137085">"উভতি যাওক"</string>
     <string name="handle_text" msgid="1766582106752184456">"হেণ্ডেল"</string>
+    <string name="app_icon_text" msgid="2823268023931811747">"এপৰ চিহ্ন"</string>
     <string name="fullscreen_text" msgid="1162316685217676079">"সম্পূৰ্ণ স্ক্ৰীন"</string>
     <string name="desktop_text" msgid="1077633567027630454">"ডেস্কটপ ম’ড"</string>
     <string name="split_screen_text" msgid="1396336058129570886">"বিভাজিত স্ক্ৰীন"</string>
     <string name="more_button_text" msgid="3655388105592893530">"অধিক"</string>
     <string name="float_button_text" msgid="9221657008391364581">"ওপঙা"</string>
+    <string name="select_text" msgid="5139083974039906583">"বাছনি কৰক"</string>
+    <string name="screenshot_text" msgid="1477704010087786671">"স্ক্ৰীনশ্বট"</string>
+    <string name="close_text" msgid="4986518933445178928">"বন্ধ কৰক"</string>
+    <string name="collapse_menu_text" msgid="7515008122450342029">"মেনু বন্ধ কৰক"</string>
+    <string name="expand_menu_text" msgid="3847736164494181168">"মেনু খোলক"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-az/strings.xml b/libs/WindowManager/Shell/res/values-az/strings.xml
index a31b1e7..7c66282 100644
--- a/libs/WindowManager/Shell/res/values-az/strings.xml
+++ b/libs/WindowManager/Shell/res/values-az/strings.xml
@@ -32,13 +32,13 @@
     <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Ölçüsünü dəyişin"</string>
     <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Güvənli məkanda saxlayın"</string>
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Güvənli məkandan çıxarın"</string>
-    <string name="dock_forced_resizable" msgid="1749750436092293116">"Tətbiq bölünmüş ekran ilə işləməyə bilər."</string>
-    <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Tətbiq ekran bölünməsini dəstəkləmir."</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>
     <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"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="703810061635792791">"Bölünmüş ekran ayırıcısı"</string>
-    <string name="divider_title" msgid="5482989479865361192">"Bölünmüş ekran ayırıcısı"</string>
+    <string name="accessibility_divider" msgid="6407584574218956849">"Bölünmüş ekran ayırıcısı"</string>
+    <string name="divider_title" msgid="1963391955593749442">"Bölünmüş ekran ayırıcısı"</string>
     <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Sol tam ekran"</string>
     <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Sol 70%"</string>
     <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Sol 50%"</string>
@@ -49,14 +49,10 @@
     <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Yuxarı 50%"</string>
     <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Yuxarı 30%"</string>
     <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Aşağı tam ekran"</string>
-    <!-- no translation found for accessibility_split_left (1713683765575562458) -->
-    <skip />
-    <!-- no translation found for accessibility_split_right (8441001008181296837) -->
-    <skip />
-    <!-- no translation found for accessibility_split_top (2789329702027147146) -->
-    <skip />
-    <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
-    <skip />
+    <string name="accessibility_split_left" msgid="1713683765575562458">"Sola ayırın"</string>
+    <string name="accessibility_split_right" msgid="8441001008181296837">"Sağa ayırın"</string>
+    <string name="accessibility_split_top" msgid="2789329702027147146">"Yuxarı ayırın"</string>
+    <string name="accessibility_split_bottom" msgid="8694551025220868191">"Aşağı ayırın"</string>
     <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Birəlli rejim istifadəsi"</string>
     <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Çıxmaq üçün ekranın aşağısından yuxarıya doğru sürüşdürün və ya tətbiqin yuxarısında istənilən yerə toxunun"</string>
     <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Birəlli rejim başlasın"</string>
@@ -88,7 +84,7 @@
     <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Düzəltməmisiniz?\nGeri qaytarmaq üçün toxunun"</string>
     <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Kamera problemi yoxdur? Qapatmaq üçün toxunun."</string>
     <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Ardını görün və edin"</string>
-    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Bölünmüş ekrandan istifadə etmək üçün başqa tətbiqi sürüşdürüb gətirin"</string>
+    <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Bölünmüş ekran üçün başqa tətbiq sürüşdürün"</string>
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Tətbiqin yerini dəyişmək üçün kənarına iki dəfə toxunun"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"Anladım"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Ətraflı məlumat üçün genişləndirin."</string>
@@ -97,14 +93,21 @@
     <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Ləğv edin"</string>
     <string name="letterbox_restart_restart" msgid="8529976234412442973">"Yenidən başladın"</string>
     <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Yenidən göstərməyin"</string>
+    <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Tətbiqi köçürmək üçün\niki dəfə toxunun"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"Böyüdün"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Kiçildin"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Bağlayın"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Geriyə"</string>
     <string name="handle_text" msgid="1766582106752184456">"Hər kəsə açıq istifadəçi adı"</string>
+    <string name="app_icon_text" msgid="2823268023931811747">"Tətbiq ikonası"</string>
     <string name="fullscreen_text" msgid="1162316685217676079">"Tam Ekran"</string>
     <string name="desktop_text" msgid="1077633567027630454">"Masaüstü Rejimi"</string>
     <string name="split_screen_text" msgid="1396336058129570886">"Bölünmüş Ekran"</string>
     <string name="more_button_text" msgid="3655388105592893530">"Ardı"</string>
     <string name="float_button_text" msgid="9221657008391364581">"Üzən pəncərə"</string>
+    <string name="select_text" msgid="5139083974039906583">"Seçin"</string>
+    <string name="screenshot_text" msgid="1477704010087786671">"Skrinşot"</string>
+    <string name="close_text" msgid="4986518933445178928">"Bağlayın"</string>
+    <string name="collapse_menu_text" msgid="7515008122450342029">"Menyunu bağlayın"</string>
+    <string name="expand_menu_text" msgid="3847736164494181168">"Menyunu açın"</string>
 </resources>
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 c0e4789..c415c86 100644
--- a/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml
@@ -32,13 +32,13 @@
     <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Promenite veličinu"</string>
     <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Stavite u tajnu memoriju"</string>
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Uklonite iz tajne memorije"</string>
-    <string name="dock_forced_resizable" msgid="1749750436092293116">"Aplikacija možda neće raditi sa podeljenim ekranom."</string>
-    <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Aplikacija ne podržava podeljeni ekran."</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>
     <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"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="703810061635792791">"Razdelnik podeljenog ekrana"</string>
-    <string name="divider_title" msgid="5482989479865361192">"Razdelnik podeljenog ekrana"</string>
+    <string name="accessibility_divider" msgid="6407584574218956849">"Razdelnik podeljenog ekrana"</string>
+    <string name="divider_title" msgid="1963391955593749442">"Razdelnik podeljenog ekrana"</string>
     <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Režim celog ekrana za levi ekran"</string>
     <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Levi ekran 70%"</string>
     <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Levi ekran 50%"</string>
@@ -49,14 +49,10 @@
     <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Gornji ekran 50%"</string>
     <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Gornji ekran 30%"</string>
     <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Režim celog ekrana za donji ekran"</string>
-    <!-- no translation found for accessibility_split_left (1713683765575562458) -->
-    <skip />
-    <!-- no translation found for accessibility_split_right (8441001008181296837) -->
-    <skip />
-    <!-- no translation found for accessibility_split_top (2789329702027147146) -->
-    <skip />
-    <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
-    <skip />
+    <string name="accessibility_split_left" msgid="1713683765575562458">"Podelite levo"</string>
+    <string name="accessibility_split_right" msgid="8441001008181296837">"Podelite desno"</string>
+    <string name="accessibility_split_top" msgid="2789329702027147146">"Podelite u vrhu"</string>
+    <string name="accessibility_split_bottom" msgid="8694551025220868191">"Podelite u dnu"</string>
     <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Korišćenje režima jednom rukom"</string>
     <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Da biste izašli, prevucite nagore od dna ekrana ili dodirnite bilo gde iznad aplikacije"</string>
     <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Pokrenite režim jednom rukom"</string>
@@ -88,23 +84,30 @@
     <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Problem nije rešen?\nDodirnite da biste vratili"</string>
     <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Nemate problema sa kamerom? Dodirnite da biste odbacili."</string>
     <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Vidite i uradite više"</string>
-    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Prevucite drugu aplikaciju da biste koristili podeljeni ekran"</string>
+    <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Prevucite drugu aplikaciju da biste koristili podeljeni ekran"</string>
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Dvaput dodirnite izvan aplikacije da biste promenili njenu poziciju"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"Važi"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Proširite za još informacija."</string>
     <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Želite li da restartujete radi boljeg prikaza?"</string>
-    <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Možete da restartujete aplikaciju da bi izgledala bolje na ekranu, s tim što možete da izgubite ono što ste uradili ili nesačuvane promene, ako ih ima"</string>
+    <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Možete da restartujete aplikaciju da bi izgledala bolje na ekranu, ali možete da izgubite napredak ili nesačuvane promene"</string>
     <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Otkaži"</string>
     <string name="letterbox_restart_restart" msgid="8529976234412442973">"Restartuj"</string>
     <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Ne prikazuj ponovo"</string>
+    <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Dvaput dodirnite da biste\npremestili ovu aplikaciju"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"Uvećajte"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Umanjite"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Zatvorite"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Nazad"</string>
     <string name="handle_text" msgid="1766582106752184456">"Identifikator"</string>
+    <string name="app_icon_text" msgid="2823268023931811747">"Ikona aplikacije"</string>
     <string name="fullscreen_text" msgid="1162316685217676079">"Preko celog ekrana"</string>
     <string name="desktop_text" msgid="1077633567027630454">"Režim za računare"</string>
     <string name="split_screen_text" msgid="1396336058129570886">"Podeljeni ekran"</string>
     <string name="more_button_text" msgid="3655388105592893530">"Još"</string>
     <string name="float_button_text" msgid="9221657008391364581">"Plutajuće"</string>
+    <string name="select_text" msgid="5139083974039906583">"Izaberite"</string>
+    <string name="screenshot_text" msgid="1477704010087786671">"Snimak ekrana"</string>
+    <string name="close_text" msgid="4986518933445178928">"Zatvorite"</string>
+    <string name="collapse_menu_text" msgid="7515008122450342029">"Zatvorite meni"</string>
+    <string name="expand_menu_text" msgid="3847736164494181168">"Otvorite meni"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-be/strings.xml b/libs/WindowManager/Shell/res/values-be/strings.xml
index b67e2cd..3d99514 100644
--- a/libs/WindowManager/Shell/res/values-be/strings.xml
+++ b/libs/WindowManager/Shell/res/values-be/strings.xml
@@ -32,13 +32,13 @@
     <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Змяніць памер"</string>
     <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Схаваць"</string>
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Паказаць"</string>
-    <string name="dock_forced_resizable" msgid="1749750436092293116">"Праграма можа не працаваць у рэжыме падзеленага экрана."</string>
-    <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Праграма не падтрымлівае функцыю дзялення экрана."</string>
+    <string name="dock_forced_resizable" msgid="7429086980048964687">"Праграма можа не працаваць у рэжыме падзеленага экрана"</string>
+    <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Праграма не падтрымлівае рэжым падзеленага экрана"</string>
     <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Гэту праграму можна адкрыць толькі ў адным акне."</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="703810061635792791">"Раздзяляльнік падзеленага экрана"</string>
-    <string name="divider_title" msgid="5482989479865361192">"Раздзяляльнік падзеленага экрана"</string>
+    <string name="accessibility_divider" msgid="6407584574218956849">"Раздзяляльнік падзеленага экрана"</string>
+    <string name="divider_title" msgid="1963391955593749442">"Раздзяляльнік падзеленага экрана"</string>
     <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Левы экран – поўнаэкранны рэжым"</string>
     <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Левы экран – 70%"</string>
     <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Левы экран – 50%"</string>
@@ -49,14 +49,10 @@
     <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Верхні экран – 50%"</string>
     <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Верхні экран – 30%"</string>
     <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Ніжні экран – поўнаэкранны рэжым"</string>
-    <!-- no translation found for accessibility_split_left (1713683765575562458) -->
-    <skip />
-    <!-- no translation found for accessibility_split_right (8441001008181296837) -->
-    <skip />
-    <!-- no translation found for accessibility_split_top (2789329702027147146) -->
-    <skip />
-    <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
-    <skip />
+    <string name="accessibility_split_left" msgid="1713683765575562458">"Падзяліць злева"</string>
+    <string name="accessibility_split_right" msgid="8441001008181296837">"Падзяліць справа"</string>
+    <string name="accessibility_split_top" msgid="2789329702027147146">"Падзяліць уверсе"</string>
+    <string name="accessibility_split_bottom" msgid="8694551025220868191">"Падзяліць унізе"</string>
     <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Выкарыстоўваецца рэжым кіравання адной рукой"</string>
     <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Каб выйсці, правядзіце па экране пальцам знізу ўверх або націсніце ў любым месцы над праграмай"</string>
     <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Запусціць рэжым кіравання адной рукой"</string>
@@ -88,7 +84,7 @@
     <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Не ўдалося выправіць?\nНацісніце, каб аднавіць"</string>
     <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Ніякіх праблем з камерай? Націсніце, каб адхіліць."</string>
     <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Адначасова выконвайце розныя задачы"</string>
-    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Перацягніце іншую праграму, каб выкарыстоўваць падзелены экран"</string>
+    <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Перацягніце іншую праграму, каб выкарыстоўваць падзелены экран"</string>
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Двойчы націсніце экран па-за праграмай, каб перамясціць яе"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"Зразумела"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Разгарнуць для дадатковай інфармацыі"</string>
@@ -97,14 +93,21 @@
     <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Скасаваць"</string>
     <string name="letterbox_restart_restart" msgid="8529976234412442973">"Перазапусціць"</string>
     <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Больш не паказваць"</string>
+    <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Каб перамясціць праграму,\nнацісніце двойчы"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"Разгарнуць"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Згарнуць"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Закрыць"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Назад"</string>
     <string name="handle_text" msgid="1766582106752184456">"Маркер"</string>
+    <string name="app_icon_text" msgid="2823268023931811747">"Значок праграмы"</string>
     <string name="fullscreen_text" msgid="1162316685217676079">"На ўвесь экран"</string>
     <string name="desktop_text" msgid="1077633567027630454">"Рэжым працоўнага стала"</string>
     <string name="split_screen_text" msgid="1396336058129570886">"Падзяліць экран"</string>
     <string name="more_button_text" msgid="3655388105592893530">"Яшчэ"</string>
     <string name="float_button_text" msgid="9221657008391364581">"Зрабіць рухомым акном"</string>
+    <string name="select_text" msgid="5139083974039906583">"Выбраць"</string>
+    <string name="screenshot_text" msgid="1477704010087786671">"Здымак экрана"</string>
+    <string name="close_text" msgid="4986518933445178928">"Закрыць"</string>
+    <string name="collapse_menu_text" msgid="7515008122450342029">"Закрыць меню"</string>
+    <string name="expand_menu_text" msgid="3847736164494181168">"Адкрыць меню"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-bg/strings.xml b/libs/WindowManager/Shell/res/values-bg/strings.xml
index 9bf396d..0473f27 100644
--- a/libs/WindowManager/Shell/res/values-bg/strings.xml
+++ b/libs/WindowManager/Shell/res/values-bg/strings.xml
@@ -32,13 +32,13 @@
     <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Преоразмеряване"</string>
     <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Съхраняване"</string>
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Отмяна на съхраняването"</string>
-    <string name="dock_forced_resizable" msgid="1749750436092293116">"Приложението може да не работи в режим на разделен екран."</string>
-    <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Приложението не поддържа разделен екран."</string>
+    <string name="dock_forced_resizable" msgid="7429086980048964687">"Приложението може да не работи в режим на разделен екран"</string>
+    <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Приложението не поддържа разделен екран"</string>
     <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Това приложение може да се отвори само в 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="703810061635792791">"Разделител в режима за разделен екран"</string>
-    <string name="divider_title" msgid="5482989479865361192">"Разделител в режима за разделен екран"</string>
+    <string name="accessibility_divider" msgid="6407584574218956849">"Разделител в режима за разделен екран"</string>
+    <string name="divider_title" msgid="1963391955593749442">"Разделител в режима за разделен екран"</string>
     <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Ляв екран: Показване на цял екран"</string>
     <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Ляв екран: 70%"</string>
     <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Ляв екран: 50%"</string>
@@ -49,14 +49,10 @@
     <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Горен екран: 50%"</string>
     <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Горен екран: 30%"</string>
     <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Долен екран: Показване на цял екран"</string>
-    <!-- no translation found for accessibility_split_left (1713683765575562458) -->
-    <skip />
-    <!-- no translation found for accessibility_split_right (8441001008181296837) -->
-    <skip />
-    <!-- no translation found for accessibility_split_top (2789329702027147146) -->
-    <skip />
-    <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
-    <skip />
+    <string name="accessibility_split_left" msgid="1713683765575562458">"Разделяне в лявата част"</string>
+    <string name="accessibility_split_right" msgid="8441001008181296837">"Разделяне в дясната част"</string>
+    <string name="accessibility_split_top" msgid="2789329702027147146">"Разделяне в горната част"</string>
+    <string name="accessibility_split_bottom" msgid="8694551025220868191">"Разделяне в долната част"</string>
     <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Използване на режима за работа с една ръка"</string>
     <string name="one_handed_tutorial_description" msgid="3486582858591353067">"За изход прекарайте пръст нагоре от долната част на екрана или докоснете произволно място над приложението"</string>
     <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Стартиране на режима за работа с една ръка"</string>
@@ -88,7 +84,7 @@
     <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Проблемът не се отстрани?\nДокоснете за връщане в предишното състояние"</string>
     <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Нямате проблеми с камерата? Докоснете, за да отхвърлите."</string>
     <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Преглеждайте и правете повече неща"</string>
-    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Преместете друго приложение с плъзгане, за да преминете в режим за разделен екран"</string>
+    <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Преместете друго приложение с плъзгане, за да преминете в режим за разделен екран"</string>
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Докоснете два пъти извън дадено приложение, за да промените позицията му"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"Разбрах"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Разгъване за още информация."</string>
@@ -97,14 +93,21 @@
     <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Отказ"</string>
     <string name="letterbox_restart_restart" msgid="8529976234412442973">"Рестартиране"</string>
     <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Да не се показва отново"</string>
+    <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Докоснете двукратно, за да\nпреместите това приложение"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"Увеличаване"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Намаляване"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Затваряне"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Назад"</string>
     <string name="handle_text" msgid="1766582106752184456">"Манипулатор"</string>
+    <string name="app_icon_text" msgid="2823268023931811747">"Икона на приложението"</string>
     <string name="fullscreen_text" msgid="1162316685217676079">"Цял екран"</string>
     <string name="desktop_text" msgid="1077633567027630454">"Режим за настолни компютри"</string>
     <string name="split_screen_text" msgid="1396336058129570886">"Разделяне на екрана"</string>
     <string name="more_button_text" msgid="3655388105592893530">"Още"</string>
     <string name="float_button_text" msgid="9221657008391364581">"Плаващо"</string>
+    <string name="select_text" msgid="5139083974039906583">"Избиране"</string>
+    <string name="screenshot_text" msgid="1477704010087786671">"Екранна снимка"</string>
+    <string name="close_text" msgid="4986518933445178928">"Затваряне"</string>
+    <string name="collapse_menu_text" msgid="7515008122450342029">"Затваряне на менюто"</string>
+    <string name="expand_menu_text" msgid="3847736164494181168">"Отваряне на менюто"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-bn/strings.xml b/libs/WindowManager/Shell/res/values-bn/strings.xml
index affb62e..4fe1be0 100644
--- a/libs/WindowManager/Shell/res/values-bn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-bn/strings.xml
@@ -32,13 +32,13 @@
     <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"রিসাইজ করুন"</string>
     <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"স্ট্যাস করুন"</string>
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"আনস্ট্যাস করুন"</string>
-    <string name="dock_forced_resizable" msgid="1749750436092293116">"অ্যাপটি স্প্লিট স্ক্রিনে কাজ নাও করতে পারে।"</string>
-    <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"অ্যাপ্লিকেশান বিভক্ত-স্ক্রিন সমর্থন করে না৷"</string>
+    <string name="dock_forced_resizable" msgid="7429086980048964687">"স্প্লিট স্ক্রিনে এই অ্যাপ নাও কাজ করতে পারে"</string>
+    <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"স্প্লিট স্ক্রিনে এই অ্যাপ কাজ করে না"</string>
     <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"এই অ্যাপটি শুধু ১টি উইন্ডোয় খোলা যেতে পারে।"</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="703810061635792791">"বিভক্ত-স্ক্রিন বিভাজক"</string>
-    <string name="divider_title" msgid="5482989479865361192">"স্প্লিট স্ক্রিন বিভাজক"</string>
+    <string name="accessibility_divider" msgid="6407584574218956849">"স্প্লিট স্ক্রিন বিভাজক"</string>
+    <string name="divider_title" msgid="1963391955593749442">"স্প্লিট স্ক্রিন বিভাজক"</string>
     <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"বাঁ দিকের অংশ নিয়ে পূর্ণ স্ক্রিন"</string>
     <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"৭০% বাকি আছে"</string>
     <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"৫০% বাকি আছে"</string>
@@ -49,14 +49,10 @@
     <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"শীর্ষ ৫০%"</string>
     <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"শীর্ষ ৩০%"</string>
     <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"নীচের অংশ নিয়ে পূর্ণ স্ক্রিন"</string>
-    <!-- no translation found for accessibility_split_left (1713683765575562458) -->
-    <skip />
-    <!-- no translation found for accessibility_split_right (8441001008181296837) -->
-    <skip />
-    <!-- no translation found for accessibility_split_top (2789329702027147146) -->
-    <skip />
-    <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
-    <skip />
+    <string name="accessibility_split_left" msgid="1713683765575562458">"স্ক্রিনের বাঁদিকে স্প্লিট করুন"</string>
+    <string name="accessibility_split_right" msgid="8441001008181296837">"স্ক্রিনের ডানদিকে স্প্লিট করুন"</string>
+    <string name="accessibility_split_top" msgid="2789329702027147146">"স্ক্রিনের উপরের দিকে স্প্লিট করুন"</string>
+    <string name="accessibility_split_bottom" msgid="8694551025220868191">"স্প্লিট করার বোতাম"</string>
     <string name="one_handed_tutorial_title" msgid="4583241688067426350">"\'এক হাতে ব্যবহার করার মোড\'-এর ব্যবহার"</string>
     <string name="one_handed_tutorial_description" msgid="3486582858591353067">"বেরিয়ে আসার জন্য, স্ক্রিনের নিচ থেকে উপরের দিকে সোয়াইপ করুন অথবা অ্যাপ আইকনের উপরে যেকোনও জায়গায় ট্যাপ করুন"</string>
     <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"\'এক হাতে ব্যবহার করার মোড\' শুরু করুন"</string>
@@ -88,7 +84,7 @@
     <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"এখনও সমাধান হয়নি?\nরিভার্ট করার জন্য ট্যাপ করুন"</string>
     <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"ক্যামেরা সংক্রান্ত সমস্যা নেই? বাতিল করতে ট্যাপ করুন।"</string>
     <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"দেখুন ও আরও অনেক কিছু করুন"</string>
-    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"স্প্লিট স্ক্রিনের জন্য অন্য অ্যাপে টেনে আনুন"</string>
+    <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"স্প্লিট স্ক্রিনের ক্ষেত্রে অন্য কোনও অ্যাপ টেনে আনুন"</string>
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"কোনও অ্যাপের স্থান পরিবর্তন করতে তার বাইরে ডবল ট্যাপ করুন"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"বুঝেছি"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"আরও তথ্যের জন্য বড় করুন।"</string>
@@ -97,14 +93,21 @@
     <string name="letterbox_restart_cancel" msgid="1342209132692537805">"বাতিল করুন"</string>
     <string name="letterbox_restart_restart" msgid="8529976234412442973">"রিস্টার্ট করুন"</string>
     <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"আর দেখতে চাই না"</string>
+    <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"এই অ্যাপ সরাতে\nডবল ট্যাপ করুন"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"বড় করুন"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"ছোট করুন"</string>
     <string name="close_button_text" msgid="2913281996024033299">"বন্ধ করুন"</string>
     <string name="back_button_text" msgid="1469718707134137085">"ফিরে যান"</string>
     <string name="handle_text" msgid="1766582106752184456">"হাতল"</string>
+    <string name="app_icon_text" msgid="2823268023931811747">"অ্যাপ আইকন"</string>
     <string name="fullscreen_text" msgid="1162316685217676079">"ফুলস্ক্রিন"</string>
     <string name="desktop_text" msgid="1077633567027630454">"ডেস্কটপ মোড"</string>
     <string name="split_screen_text" msgid="1396336058129570886">"স্প্লিট স্ক্রিন"</string>
     <string name="more_button_text" msgid="3655388105592893530">"আরও"</string>
     <string name="float_button_text" msgid="9221657008391364581">"ফ্লোট"</string>
+    <string name="select_text" msgid="5139083974039906583">"বেছে নিন"</string>
+    <string name="screenshot_text" msgid="1477704010087786671">"স্ক্রিনশট"</string>
+    <string name="close_text" msgid="4986518933445178928">"বন্ধ করুন"</string>
+    <string name="collapse_menu_text" msgid="7515008122450342029">"\'মেনু\' বন্ধ করুন"</string>
+    <string name="expand_menu_text" msgid="3847736164494181168">"মেনু খুলুন"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-bs/strings.xml b/libs/WindowManager/Shell/res/values-bs/strings.xml
index dd2f871..b39b497 100644
--- a/libs/WindowManager/Shell/res/values-bs/strings.xml
+++ b/libs/WindowManager/Shell/res/values-bs/strings.xml
@@ -32,13 +32,13 @@
     <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Promjena veličine"</string>
     <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Stavljanje u stash"</string>
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Vađenje iz stasha"</string>
-    <string name="dock_forced_resizable" msgid="1749750436092293116">"Aplikacija možda neće raditi na podijeljenom ekranu."</string>
-    <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Aplikacija ne podržava dijeljenje ekrana."</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>
     <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"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="703810061635792791">"Razdjelnik podijeljenog ekrana"</string>
-    <string name="divider_title" msgid="5482989479865361192">"Razdjelnik podijeljenog ekrana"</string>
+    <string name="accessibility_divider" msgid="6407584574218956849">"Razdjelnik podijeljenog ekrana"</string>
+    <string name="divider_title" msgid="1963391955593749442">"Razdjelnik podijeljenog ekrana"</string>
     <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Lijevo cijeli ekran"</string>
     <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Lijevo 70%"</string>
     <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Lijevo 50%"</string>
@@ -49,14 +49,10 @@
     <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Gore 50%"</string>
     <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Gore 30%"</string>
     <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Donji ekran kao cijeli ekran"</string>
-    <!-- no translation found for accessibility_split_left (1713683765575562458) -->
-    <skip />
-    <!-- no translation found for accessibility_split_right (8441001008181296837) -->
-    <skip />
-    <!-- no translation found for accessibility_split_top (2789329702027147146) -->
-    <skip />
-    <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
-    <skip />
+    <string name="accessibility_split_left" msgid="1713683765575562458">"Podjela ulijevo"</string>
+    <string name="accessibility_split_right" msgid="8441001008181296837">"Podjela udesno"</string>
+    <string name="accessibility_split_top" msgid="2789329702027147146">"Podjela nagore"</string>
+    <string name="accessibility_split_bottom" msgid="8694551025220868191">"Podjela nadolje"</string>
     <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Korištenje načina rada jednom rukom"</string>
     <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Da izađete, prevucite s dna ekrana prema gore ili dodirnite bilo gdje iznad aplikacije"</string>
     <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Započinjanje načina rada jednom rukom"</string>
@@ -88,7 +84,7 @@
     <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Nije popravljeno?\nDodirnite da vratite"</string>
     <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Nema problema s kamerom? Dodirnite da odbacite."</string>
     <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Pogledajte i učinite više"</string>
-    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Prevucite još jednu aplikaciju za podijeljeni ekran"</string>
+    <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Prevucite još jednu aplikaciju za podijeljeni ekran"</string>
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Dvaput dodirnite izvan aplikacije da promijenite njen položaj"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"Razumijem"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Proširite za više informacija."</string>
@@ -97,14 +93,21 @@
     <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Otkaži"</string>
     <string name="letterbox_restart_restart" msgid="8529976234412442973">"Ponovo pokreni"</string>
     <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Ne prikazuj ponovo"</string>
+    <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Dodirnite dvaput da\npomjerite aplikaciju"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"Maksimiziranje"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimiziranje"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Zatvaranje"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Nazad"</string>
     <string name="handle_text" msgid="1766582106752184456">"Identifikator"</string>
+    <string name="app_icon_text" msgid="2823268023931811747">"Ikona aplikacije"</string>
     <string name="fullscreen_text" msgid="1162316685217676079">"Cijeli ekran"</string>
     <string name="desktop_text" msgid="1077633567027630454">"Način rada radne površine"</string>
     <string name="split_screen_text" msgid="1396336058129570886">"Podijeljeni ekran"</string>
     <string name="more_button_text" msgid="3655388105592893530">"Više"</string>
     <string name="float_button_text" msgid="9221657008391364581">"Lebdeći"</string>
+    <string name="select_text" msgid="5139083974039906583">"Odabir"</string>
+    <string name="screenshot_text" msgid="1477704010087786671">"Snimak ekrana"</string>
+    <string name="close_text" msgid="4986518933445178928">"Zatvaranje"</string>
+    <string name="collapse_menu_text" msgid="7515008122450342029">"Zatvaranje menija"</string>
+    <string name="expand_menu_text" msgid="3847736164494181168">"Otvaranje menija"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-ca/strings.xml b/libs/WindowManager/Shell/res/values-ca/strings.xml
index 937e0d9..fe76e73 100644
--- a/libs/WindowManager/Shell/res/values-ca/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ca/strings.xml
@@ -32,13 +32,13 @@
     <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Canvia la mida"</string>
     <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Amaga"</string>
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Deixa d\'amagar"</string>
-    <string name="dock_forced_resizable" msgid="1749750436092293116">"És possible que l\'aplicació no funcioni amb la pantalla dividida."</string>
-    <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"L\'aplicació no admet la pantalla dividida."</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>
     <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"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="703810061635792791">"Divisor de pantalles"</string>
-    <string name="divider_title" msgid="5482989479865361192">"Separador de pantalla dividida"</string>
+    <string name="accessibility_divider" msgid="6407584574218956849">"Separador de pantalla dividida"</string>
+    <string name="divider_title" msgid="1963391955593749442">"Separador de pantalla dividida"</string>
     <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Pantalla esquerra completa"</string>
     <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Pantalla esquerra al 70%"</string>
     <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Pantalla esquerra al 50%"</string>
@@ -49,20 +49,16 @@
     <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Pantalla superior al 50%"</string>
     <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Pantalla superior al 30%"</string>
     <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Pantalla inferior completa"</string>
-    <!-- no translation found for accessibility_split_left (1713683765575562458) -->
-    <skip />
-    <!-- no translation found for accessibility_split_right (8441001008181296837) -->
-    <skip />
-    <!-- no translation found for accessibility_split_top (2789329702027147146) -->
-    <skip />
-    <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
-    <skip />
+    <string name="accessibility_split_left" msgid="1713683765575562458">"Divideix a l\'esquerra"</string>
+    <string name="accessibility_split_right" msgid="8441001008181296837">"Divideix a la dreta"</string>
+    <string name="accessibility_split_top" msgid="2789329702027147146">"Divideix a la part superior"</string>
+    <string name="accessibility_split_bottom" msgid="8694551025220868191">"Divideix a la part inferior"</string>
     <string name="one_handed_tutorial_title" msgid="4583241688067426350">"S\'està utilitzant el mode d\'una mà"</string>
     <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Per sortir, llisca cap amunt des de la part inferior de la pantalla o toca qualsevol lloc a sobre de l\'aplicació"</string>
     <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Inicia el mode d\'una mà"</string>
     <string name="accessibility_action_stop_one_handed" msgid="1369940261782179442">"Surt del mode d\'una mà"</string>
     <string name="bubbles_settings_button_description" msgid="1301286017420516912">"Configuració de les bombolles: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
-    <string name="bubble_overflow_button_content_description" msgid="8160974472718594382">"Menú addicional"</string>
+    <string name="bubble_overflow_button_content_description" msgid="8160974472718594382">"Menú de desbordament"</string>
     <string name="bubble_accessibility_action_add_back" msgid="1830101076853540953">"Torna a afegir a la pila"</string>
     <string name="bubble_content_description_single" msgid="8495748092720065813">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> de: <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
     <string name="bubble_content_description_stack" msgid="8071515017164630429">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> (<xliff:g id="APP_NAME">%2$s</xliff:g>) i <xliff:g id="BUBBLE_COUNT">%3$d</xliff:g> més"</string>
@@ -88,7 +84,7 @@
     <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"El problema no s\'ha resolt?\nToca per desfer els canvis"</string>
     <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"No tens cap problema amb la càmera? Toca per ignorar."</string>
     <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Consulta i fes més coses"</string>
-    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Arrossega una altra aplicació per utilitzar la pantalla dividida"</string>
+    <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Arrossega una altra aplicació per utilitzar la pantalla dividida"</string>
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Fes doble toc fora d\'una aplicació per canviar-ne la posició"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"Entesos"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Desplega per obtenir més informació."</string>
@@ -97,14 +93,21 @@
     <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Cancel·la"</string>
     <string name="letterbox_restart_restart" msgid="8529976234412442973">"Reinicia"</string>
     <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"No ho tornis a mostrar"</string>
+    <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Fes doble toc per\nmoure aquesta aplicació"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"Maximitza"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimitza"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Tanca"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Enrere"</string>
     <string name="handle_text" msgid="1766582106752184456">"Ansa"</string>
+    <string name="app_icon_text" msgid="2823268023931811747">"Icona de l\'aplicació"</string>
     <string name="fullscreen_text" msgid="1162316685217676079">"Pantalla completa"</string>
     <string name="desktop_text" msgid="1077633567027630454">"Mode d\'escriptori"</string>
     <string name="split_screen_text" msgid="1396336058129570886">"Pantalla dividida"</string>
     <string name="more_button_text" msgid="3655388105592893530">"Més"</string>
     <string name="float_button_text" msgid="9221657008391364581">"Flotant"</string>
+    <string name="select_text" msgid="5139083974039906583">"Selecciona"</string>
+    <string name="screenshot_text" msgid="1477704010087786671">"Captura de pantalla"</string>
+    <string name="close_text" msgid="4986518933445178928">"Tanca"</string>
+    <string name="collapse_menu_text" msgid="7515008122450342029">"Tanca el menú"</string>
+    <string name="expand_menu_text" msgid="3847736164494181168">"Obre el menú"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-cs/strings.xml b/libs/WindowManager/Shell/res/values-cs/strings.xml
index a358895..ac22b85 100644
--- a/libs/WindowManager/Shell/res/values-cs/strings.xml
+++ b/libs/WindowManager/Shell/res/values-cs/strings.xml
@@ -32,13 +32,13 @@
     <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Změnit velikost"</string>
     <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Uložit"</string>
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Zrušit uložení"</string>
-    <string name="dock_forced_resizable" msgid="1749750436092293116">"Aplikace v režimu rozdělené obrazovky nemusí fungovat."</string>
-    <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Aplikace nepodporuje režim rozdělené obrazovky."</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>
     <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Tuto aplikaci lze otevřít jen na 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="703810061635792791">"Čára rozdělující obrazovku"</string>
-    <string name="divider_title" msgid="5482989479865361192">"Čára rozdělující obrazovku"</string>
+    <string name="accessibility_divider" msgid="6407584574218956849">"Čára rozdělující obrazovku"</string>
+    <string name="divider_title" msgid="1963391955593749442">"Čára rozdělující obrazovku"</string>
     <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Levá část na celou obrazovku"</string>
     <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"70 % vlevo"</string>
     <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"50 % vlevo"</string>
@@ -49,14 +49,10 @@
     <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"50 % nahoře"</string>
     <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"30 % nahoře"</string>
     <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Dolní část na celou obrazovku"</string>
-    <!-- no translation found for accessibility_split_left (1713683765575562458) -->
-    <skip />
-    <!-- no translation found for accessibility_split_right (8441001008181296837) -->
-    <skip />
-    <!-- no translation found for accessibility_split_top (2789329702027147146) -->
-    <skip />
-    <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
-    <skip />
+    <string name="accessibility_split_left" msgid="1713683765575562458">"Rozdělit vlevo"</string>
+    <string name="accessibility_split_right" msgid="8441001008181296837">"Rozdělit vpravo"</string>
+    <string name="accessibility_split_top" msgid="2789329702027147146">"Rozdělit nahoře"</string>
+    <string name="accessibility_split_bottom" msgid="8694551025220868191">"Rozdělit dole"</string>
     <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Používání režimu jedné ruky"</string>
     <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Režim ukončíte, když přejedete prstem z dolní části obrazovky nahoru nebo klepnete kamkoli nad aplikaci"</string>
     <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Spustit režim jedné ruky"</string>
@@ -83,12 +79,12 @@
     <string name="notification_bubble_title" msgid="6082910224488253378">"Bublina"</string>
     <string name="manage_bubbles_text" msgid="7730624269650594419">"Spravovat"</string>
     <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Bublina byla zavřena."</string>
-    <string name="restart_button_description" msgid="6712141648865547958">"Klepnutím tuto aplikaci kvůli lepšímu zobrazení restartujete."</string>
+    <string name="restart_button_description" msgid="6712141648865547958">"Pokud je problém se zobrazením aplikace, klepněte na ni a restartujte ji."</string>
     <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Problémy s fotoaparátem?\nKlepnutím vyřešíte"</string>
     <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Nepomohlo to?\nKlepnutím se vrátíte"</string>
     <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Žádné problémy s fotoaparátem? Klepnutím zavřete."</string>
     <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Lepší zobrazení a více možností"</string>
-    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Přetáhnutím druhé aplikace použijete rozdělenou obrazovku"</string>
+    <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Přetáhnutím druhé aplikace použijete rozdělenou obrazovku"</string>
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Dvojitým klepnutím mimo aplikaci změníte její umístění"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Rozbalením zobrazíte další informace."</string>
@@ -97,14 +93,21 @@
     <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Zrušit"</string>
     <string name="letterbox_restart_restart" msgid="8529976234412442973">"Restartovat"</string>
     <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Tuto zprávu příště nezobrazovat"</string>
+    <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Dvojitým klepnutím\npřesunete aplikaci"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"Maximalizovat"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimalizovat"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Zavřít"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Zpět"</string>
     <string name="handle_text" msgid="1766582106752184456">"Úchyt"</string>
+    <string name="app_icon_text" msgid="2823268023931811747">"Ikona aplikace"</string>
     <string name="fullscreen_text" msgid="1162316685217676079">"Celá obrazovka"</string>
     <string name="desktop_text" msgid="1077633567027630454">"Režim počítače"</string>
     <string name="split_screen_text" msgid="1396336058129570886">"Rozdělená obrazovka"</string>
     <string name="more_button_text" msgid="3655388105592893530">"Více"</string>
     <string name="float_button_text" msgid="9221657008391364581">"Plovoucí"</string>
+    <string name="select_text" msgid="5139083974039906583">"Vybrat"</string>
+    <string name="screenshot_text" msgid="1477704010087786671">"Snímek obrazovky"</string>
+    <string name="close_text" msgid="4986518933445178928">"Zavřít"</string>
+    <string name="collapse_menu_text" msgid="7515008122450342029">"Zavřít nabídku"</string>
+    <string name="expand_menu_text" msgid="3847736164494181168">"Otevřít nabídku"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-da/strings.xml b/libs/WindowManager/Shell/res/values-da/strings.xml
index 7a40efd..c91cd7a 100644
--- a/libs/WindowManager/Shell/res/values-da/strings.xml
+++ b/libs/WindowManager/Shell/res/values-da/strings.xml
@@ -32,13 +32,13 @@
     <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Rediger størrelse"</string>
     <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Skjul"</string>
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Vis"</string>
-    <string name="dock_forced_resizable" msgid="1749750436092293116">"Appen fungerer muligvis ikke i opdelt skærm."</string>
-    <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Appen understøtter ikke opdelt skærm."</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>
     <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"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="703810061635792791">"Adskiller til opdelt skærm"</string>
-    <string name="divider_title" msgid="5482989479865361192">"Adskiller til opdelt skærm"</string>
+    <string name="accessibility_divider" msgid="6407584574218956849">"Adskiller til opdelt skærm"</string>
+    <string name="divider_title" msgid="1963391955593749442">"Adskiller til opdelt skærm"</string>
     <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Vis venstre del i fuld skærm"</string>
     <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Venstre 70 %"</string>
     <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Venstre 50 %"</string>
@@ -49,14 +49,10 @@
     <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Øverste 50 %"</string>
     <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Øverste 30 %"</string>
     <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Vis nederste del i fuld skærm"</string>
-    <!-- no translation found for accessibility_split_left (1713683765575562458) -->
-    <skip />
-    <!-- no translation found for accessibility_split_right (8441001008181296837) -->
-    <skip />
-    <!-- no translation found for accessibility_split_top (2789329702027147146) -->
-    <skip />
-    <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
-    <skip />
+    <string name="accessibility_split_left" msgid="1713683765575562458">"Vis i venstre side"</string>
+    <string name="accessibility_split_right" msgid="8441001008181296837">"Vis i højre side"</string>
+    <string name="accessibility_split_top" msgid="2789329702027147146">"Vis øverst"</string>
+    <string name="accessibility_split_bottom" msgid="8694551025220868191">"Vis nederst"</string>
     <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Brug af enhåndstilstand"</string>
     <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Du kan afslutte ved at stryge opad fra bunden af skærmen eller trykke et vilkårligt sted over appen"</string>
     <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Start enhåndstilstand"</string>
@@ -88,7 +84,7 @@
     <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Løste det ikke problemet?\nTryk for at fortryde"</string>
     <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Har du ingen problemer med dit kamera? Tryk for at afvise."</string>
     <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Se og gør mere"</string>
-    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Træk en anden app hertil for at bruge opdelt skærm"</string>
+    <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Træk en anden app hertil for at bruge opdelt skærm"</string>
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Tryk to gange uden for en app for at justere dens placering"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Udvid for at få flere oplysninger."</string>
@@ -97,14 +93,21 @@
     <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Annuller"</string>
     <string name="letterbox_restart_restart" msgid="8529976234412442973">"Genstart"</string>
     <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Vis ikke igen"</string>
+    <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Tryk to gange\nfor at flytte appen"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"Maksimér"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimer"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Luk"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Tilbage"</string>
     <string name="handle_text" msgid="1766582106752184456">"Håndtag"</string>
+    <string name="app_icon_text" msgid="2823268023931811747">"Appikon"</string>
     <string name="fullscreen_text" msgid="1162316685217676079">"Fuld skærm"</string>
     <string name="desktop_text" msgid="1077633567027630454">"Computertilstand"</string>
     <string name="split_screen_text" msgid="1396336058129570886">"Opdelt skærm"</string>
     <string name="more_button_text" msgid="3655388105592893530">"Mere"</string>
     <string name="float_button_text" msgid="9221657008391364581">"Svævende"</string>
+    <string name="select_text" msgid="5139083974039906583">"Vælg"</string>
+    <string name="screenshot_text" msgid="1477704010087786671">"Screenshot"</string>
+    <string name="close_text" msgid="4986518933445178928">"Luk"</string>
+    <string name="collapse_menu_text" msgid="7515008122450342029">"Luk menu"</string>
+    <string name="expand_menu_text" msgid="3847736164494181168">"Åbn menu"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-de/strings.xml b/libs/WindowManager/Shell/res/values-de/strings.xml
index 8f62752..c17f97f 100644
--- a/libs/WindowManager/Shell/res/values-de/strings.xml
+++ b/libs/WindowManager/Shell/res/values-de/strings.xml
@@ -20,7 +20,7 @@
     <string name="pip_phone_close" msgid="5783752637260411309">"Schließen"</string>
     <string name="pip_phone_expand" msgid="2579292903468287504">"Maximieren"</string>
     <string name="pip_phone_settings" msgid="5468987116750491918">"Einstellungen"</string>
-    <string name="pip_phone_enter_split" msgid="7042877263880641911">"„Geteilter Bildschirm“ aktivieren"</string>
+    <string name="pip_phone_enter_split" msgid="7042877263880641911">"Splitscreen aktivieren"</string>
     <string name="pip_menu_title" msgid="5393619322111827096">"Menü"</string>
     <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Menü „Bild im Bild“"</string>
     <string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> ist in Bild im Bild"</string>
@@ -32,13 +32,13 @@
     <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Größe anpassen"</string>
     <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"In Stash legen"</string>
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Aus Stash entfernen"</string>
-    <string name="dock_forced_resizable" msgid="1749750436092293116">"Die App funktioniert unter Umständen im Modus für geteilten Bildschirm nicht."</string>
-    <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Das Teilen des Bildschirms wird in dieser App nicht unterstützt."</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>
     <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"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="703810061635792791">"Bildschirmteiler"</string>
-    <string name="divider_title" msgid="5482989479865361192">"Bildschirmteiler"</string>
+    <string name="accessibility_divider" msgid="6407584574218956849">"Bildschirmteiler"</string>
+    <string name="divider_title" msgid="1963391955593749442">"Bildschirmteiler"</string>
     <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Vollbild links"</string>
     <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"70 % links"</string>
     <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"50 % links"</string>
@@ -49,14 +49,10 @@
     <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"50 % oben"</string>
     <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"30 % oben"</string>
     <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Vollbild unten"</string>
-    <!-- no translation found for accessibility_split_left (1713683765575562458) -->
-    <skip />
-    <!-- no translation found for accessibility_split_right (8441001008181296837) -->
-    <skip />
-    <!-- no translation found for accessibility_split_top (2789329702027147146) -->
-    <skip />
-    <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
-    <skip />
+    <string name="accessibility_split_left" msgid="1713683765575562458">"Links teilen"</string>
+    <string name="accessibility_split_right" msgid="8441001008181296837">"Rechts teilen"</string>
+    <string name="accessibility_split_top" msgid="2789329702027147146">"Oben teilen"</string>
+    <string name="accessibility_split_bottom" msgid="8694551025220868191">"Unten teilen"</string>
     <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Einhandmodus wird verwendet"</string>
     <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Wenn du die App schließen möchtest, wische vom unteren Rand des Displays nach oben oder tippe auf eine beliebige Stelle oberhalb der App"</string>
     <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Einhandmodus starten"</string>
@@ -88,7 +84,7 @@
     <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Das Problem ist nicht behoben?\nZum Rückgängigmachen tippen."</string>
     <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Keine Probleme mit der Kamera? Zum Schließen tippen."</string>
     <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Mehr sehen und erledigen"</string>
-    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Weitere App hineinziehen, um den Bildschirm zu teilen"</string>
+    <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Für Splitscreen-Modus weitere App hineinziehen"</string>
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Außerhalb einer App doppeltippen, um die Position zu ändern"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"Ok"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Für weitere Informationen maximieren."</string>
@@ -97,14 +93,21 @@
     <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Abbrechen"</string>
     <string name="letterbox_restart_restart" msgid="8529976234412442973">"Neu starten"</string>
     <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Nicht mehr anzeigen"</string>
+    <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Zum Verschieben\ndoppeltippen"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"Maximieren"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimieren"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Schließen"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Zurück"</string>
     <string name="handle_text" msgid="1766582106752184456">"Ziehpunkt"</string>
+    <string name="app_icon_text" msgid="2823268023931811747">"App-Symbol"</string>
     <string name="fullscreen_text" msgid="1162316685217676079">"Vollbild"</string>
     <string name="desktop_text" msgid="1077633567027630454">"Desktopmodus"</string>
-    <string name="split_screen_text" msgid="1396336058129570886">"Geteilter Bildschirm"</string>
+    <string name="split_screen_text" msgid="1396336058129570886">"Splitscreen"</string>
     <string name="more_button_text" msgid="3655388105592893530">"Mehr"</string>
     <string name="float_button_text" msgid="9221657008391364581">"Frei schwebend"</string>
+    <string name="select_text" msgid="5139083974039906583">"Auswählen"</string>
+    <string name="screenshot_text" msgid="1477704010087786671">"Screenshot"</string>
+    <string name="close_text" msgid="4986518933445178928">"Schließen"</string>
+    <string name="collapse_menu_text" msgid="7515008122450342029">"Menü schließen"</string>
+    <string name="expand_menu_text" msgid="3847736164494181168">"Menü öffnen"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-el/strings.xml b/libs/WindowManager/Shell/res/values-el/strings.xml
index 8d0faeb..ab5c44e 100644
--- a/libs/WindowManager/Shell/res/values-el/strings.xml
+++ b/libs/WindowManager/Shell/res/values-el/strings.xml
@@ -32,13 +32,13 @@
     <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Αλλαγή μεγέθους"</string>
     <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Απόκρυψη"</string>
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Κατάργηση απόκρυψης"</string>
-    <string name="dock_forced_resizable" msgid="1749750436092293116">"Η εφαρμογή ενδέχεται να μην λειτουργεί με διαχωρισμό οθόνης."</string>
-    <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Η εφαρμογή δεν υποστηρίζει διαχωρισμό οθόνης."</string>
+    <string name="dock_forced_resizable" msgid="7429086980048964687">"Η εφαρμογή ενδέχεται να μην λειτουργεί με διαχωρισμό οθόνης."</string>
+    <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Η εφαρμογή δεν υποστηρίζει διαχωρισμό οθόνης."</string>
     <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Αυτή η εφαρμογή μπορεί να ανοιχθεί μόνο σε 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="703810061635792791">"Διαχωριστικό οθόνης"</string>
-    <string name="divider_title" msgid="5482989479865361192">"Διαχωριστικό οθόνης"</string>
+    <string name="accessibility_divider" msgid="6407584574218956849">"Διαχωριστικό οθόνης"</string>
+    <string name="divider_title" msgid="1963391955593749442">"Διαχωριστικό οθόνης"</string>
     <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Αριστερή πλήρης οθόνη"</string>
     <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Αριστερή 70%"</string>
     <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Αριστερή 50%"</string>
@@ -49,14 +49,10 @@
     <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Πάνω 50%"</string>
     <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Πάνω 30%"</string>
     <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Κάτω πλήρης οθόνη"</string>
-    <!-- no translation found for accessibility_split_left (1713683765575562458) -->
-    <skip />
-    <!-- no translation found for accessibility_split_right (8441001008181296837) -->
-    <skip />
-    <!-- no translation found for accessibility_split_top (2789329702027147146) -->
-    <skip />
-    <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
-    <skip />
+    <string name="accessibility_split_left" msgid="1713683765575562458">"Διαχωρισμός αριστερά"</string>
+    <string name="accessibility_split_right" msgid="8441001008181296837">"Διαχωρισμός δεξιά"</string>
+    <string name="accessibility_split_top" msgid="2789329702027147146">"Διαχωρισμός επάνω"</string>
+    <string name="accessibility_split_bottom" msgid="8694551025220868191">"Διαχωρισμός κάτω"</string>
     <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Χρήση λειτουργίας ενός χεριού"</string>
     <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Για έξοδο, σύρετε προς τα πάνω από το κάτω μέρος της οθόνης ή πατήστε οπουδήποτε πάνω από την εφαρμογή."</string>
     <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Έναρξη λειτουργίας ενός χεριού"</string>
@@ -88,7 +84,7 @@
     <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Δεν διορθώθηκε;\nΠατήστε για επαναφορά."</string>
     <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Δεν αντιμετωπίζετε προβλήματα με την κάμερα; Πατήστε για παράβλεψη."</string>
     <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Δείτε και κάντε περισσότερα"</string>
-    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Σύρετε σε μια άλλη εφαρμογή για διαχωρισμό οθόνης"</string>
+    <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Σύρετε σε μια άλλη εφαρμογή για διαχωρισμό οθόνης."</string>
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Πατήστε δύο φορές έξω από μια εφαρμογή για να αλλάξετε τη θέση της"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"Το κατάλαβα"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Ανάπτυξη για περισσότερες πληροφορίες."</string>
@@ -97,14 +93,21 @@
     <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Ακύρωση"</string>
     <string name="letterbox_restart_restart" msgid="8529976234412442973">"Επανεκκίνηση"</string>
     <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Να μην εμφανιστεί ξανά"</string>
+    <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Πατήστε δύο φορές για\nμετακίνηση αυτής της εφαρμογής"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"Μεγιστοποίηση"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Ελαχιστοποίηση"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Κλείσιμο"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Πίσω"</string>
     <string name="handle_text" msgid="1766582106752184456">"Λαβή"</string>
+    <string name="app_icon_text" msgid="2823268023931811747">"Εικονίδιο εφαρμογής"</string>
     <string name="fullscreen_text" msgid="1162316685217676079">"Πλήρης οθόνη"</string>
     <string name="desktop_text" msgid="1077633567027630454">"Λειτουργία επιφάνειας εργασίας"</string>
     <string name="split_screen_text" msgid="1396336058129570886">"Διαχωρισμός οθόνης"</string>
     <string name="more_button_text" msgid="3655388105592893530">"Περισσότερα"</string>
     <string name="float_button_text" msgid="9221657008391364581">"Κινούμενο"</string>
+    <string name="select_text" msgid="5139083974039906583">"Επιλογή"</string>
+    <string name="screenshot_text" msgid="1477704010087786671">"Στιγμιότυπο οθόνης"</string>
+    <string name="close_text" msgid="4986518933445178928">"Κλείσιμο"</string>
+    <string name="collapse_menu_text" msgid="7515008122450342029">"Κλείσιμο μενού"</string>
+    <string name="expand_menu_text" msgid="3847736164494181168">"Άνοιγμα μενού"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-en-rAU/strings.xml b/libs/WindowManager/Shell/res/values-en-rAU/strings.xml
index a108e89..ea91014 100644
--- a/libs/WindowManager/Shell/res/values-en-rAU/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rAU/strings.xml
@@ -32,13 +32,13 @@
     <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Resize"</string>
     <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Stash"</string>
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Unstash"</string>
-    <string name="dock_forced_resizable" msgid="1749750436092293116">"App may not work with split-screen."</string>
-    <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"App does not support split-screen."</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>
     <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"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="703810061635792791">"Split screen divider"</string>
-    <string name="divider_title" msgid="5482989479865361192">"Split screen divider"</string>
+    <string name="accessibility_divider" msgid="6407584574218956849">"Split screen divider"</string>
+    <string name="divider_title" msgid="1963391955593749442">"Split screen divider"</string>
     <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Left full screen"</string>
     <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Left 70%"</string>
     <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Left 50%"</string>
@@ -84,7 +84,7 @@
     <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Didn’t fix it?\nTap to revert"</string>
     <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"No camera issues? Tap to dismiss."</string>
     <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"See and do more"</string>
-    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Drag in another app for split-screen"</string>
+    <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Drag in another app for split screen"</string>
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Double-tap outside an app to reposition it"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"Got it"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Expand for more information."</string>
@@ -93,14 +93,21 @@
     <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Cancel"</string>
     <string name="letterbox_restart_restart" msgid="8529976234412442973">"Restart"</string>
     <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Don\'t show again"</string>
+    <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Double-tap to\nmove this app"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"Maximise"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimise"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Close"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Back"</string>
     <string name="handle_text" msgid="1766582106752184456">"Handle"</string>
+    <string name="app_icon_text" msgid="2823268023931811747">"App icon"</string>
     <string name="fullscreen_text" msgid="1162316685217676079">"Full screen"</string>
     <string name="desktop_text" msgid="1077633567027630454">"Desktop mode"</string>
     <string name="split_screen_text" msgid="1396336058129570886">"Split screen"</string>
     <string name="more_button_text" msgid="3655388105592893530">"More"</string>
     <string name="float_button_text" msgid="9221657008391364581">"Float"</string>
+    <string name="select_text" msgid="5139083974039906583">"Select"</string>
+    <string name="screenshot_text" msgid="1477704010087786671">"Screenshot"</string>
+    <string name="close_text" msgid="4986518933445178928">"Close"</string>
+    <string name="collapse_menu_text" msgid="7515008122450342029">"Close menu"</string>
+    <string name="expand_menu_text" msgid="3847736164494181168">"Open menu"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-en-rCA/strings.xml b/libs/WindowManager/Shell/res/values-en-rCA/strings.xml
index cfa9abc..01bdf95 100644
--- a/libs/WindowManager/Shell/res/values-en-rCA/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rCA/strings.xml
@@ -32,13 +32,13 @@
     <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Resize"</string>
     <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Stash"</string>
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Unstash"</string>
-    <string name="dock_forced_resizable" msgid="1749750436092293116">"App may not work with split-screen."</string>
-    <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"App does not support split-screen."</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>
     <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"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="703810061635792791">"Split-screen divider"</string>
-    <string name="divider_title" msgid="5482989479865361192">"Split-screen divider"</string>
+    <string name="accessibility_divider" msgid="6407584574218956849">"Split screen divider"</string>
+    <string name="divider_title" msgid="1963391955593749442">"Split screen divider"</string>
     <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Left full screen"</string>
     <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Left 70%"</string>
     <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Left 50%"</string>
@@ -84,7 +84,7 @@
     <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Didn’t fix it?\nTap to revert"</string>
     <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"No camera issues? Tap to dismiss."</string>
     <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"See and do more"</string>
-    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Drag in another app for split-screen"</string>
+    <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Drag in another app for split screen"</string>
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Double-tap outside an app to reposition it"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"Got it"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Expand for more information."</string>
@@ -93,14 +93,21 @@
     <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Cancel"</string>
     <string name="letterbox_restart_restart" msgid="8529976234412442973">"Restart"</string>
     <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Don’t show again"</string>
+    <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Double-tap to\nmove this app"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"Maximize"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimize"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Close"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Back"</string>
     <string name="handle_text" msgid="1766582106752184456">"Handle"</string>
+    <string name="app_icon_text" msgid="2823268023931811747">"App Icon"</string>
     <string name="fullscreen_text" msgid="1162316685217676079">"Fullscreen"</string>
     <string name="desktop_text" msgid="1077633567027630454">"Desktop Mode"</string>
     <string name="split_screen_text" msgid="1396336058129570886">"Split Screen"</string>
     <string name="more_button_text" msgid="3655388105592893530">"More"</string>
     <string name="float_button_text" msgid="9221657008391364581">"Float"</string>
+    <string name="select_text" msgid="5139083974039906583">"Select"</string>
+    <string name="screenshot_text" msgid="1477704010087786671">"Screenshot"</string>
+    <string name="close_text" msgid="4986518933445178928">"Close"</string>
+    <string name="collapse_menu_text" msgid="7515008122450342029">"Close Menu"</string>
+    <string name="expand_menu_text" msgid="3847736164494181168">"Open Menu"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-en-rGB/strings.xml b/libs/WindowManager/Shell/res/values-en-rGB/strings.xml
index a108e89..ea91014 100644
--- a/libs/WindowManager/Shell/res/values-en-rGB/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rGB/strings.xml
@@ -32,13 +32,13 @@
     <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Resize"</string>
     <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Stash"</string>
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Unstash"</string>
-    <string name="dock_forced_resizable" msgid="1749750436092293116">"App may not work with split-screen."</string>
-    <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"App does not support split-screen."</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>
     <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"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="703810061635792791">"Split screen divider"</string>
-    <string name="divider_title" msgid="5482989479865361192">"Split screen divider"</string>
+    <string name="accessibility_divider" msgid="6407584574218956849">"Split screen divider"</string>
+    <string name="divider_title" msgid="1963391955593749442">"Split screen divider"</string>
     <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Left full screen"</string>
     <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Left 70%"</string>
     <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Left 50%"</string>
@@ -84,7 +84,7 @@
     <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Didn’t fix it?\nTap to revert"</string>
     <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"No camera issues? Tap to dismiss."</string>
     <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"See and do more"</string>
-    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Drag in another app for split-screen"</string>
+    <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Drag in another app for split screen"</string>
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Double-tap outside an app to reposition it"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"Got it"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Expand for more information."</string>
@@ -93,14 +93,21 @@
     <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Cancel"</string>
     <string name="letterbox_restart_restart" msgid="8529976234412442973">"Restart"</string>
     <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Don\'t show again"</string>
+    <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Double-tap to\nmove this app"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"Maximise"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimise"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Close"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Back"</string>
     <string name="handle_text" msgid="1766582106752184456">"Handle"</string>
+    <string name="app_icon_text" msgid="2823268023931811747">"App icon"</string>
     <string name="fullscreen_text" msgid="1162316685217676079">"Full screen"</string>
     <string name="desktop_text" msgid="1077633567027630454">"Desktop mode"</string>
     <string name="split_screen_text" msgid="1396336058129570886">"Split screen"</string>
     <string name="more_button_text" msgid="3655388105592893530">"More"</string>
     <string name="float_button_text" msgid="9221657008391364581">"Float"</string>
+    <string name="select_text" msgid="5139083974039906583">"Select"</string>
+    <string name="screenshot_text" msgid="1477704010087786671">"Screenshot"</string>
+    <string name="close_text" msgid="4986518933445178928">"Close"</string>
+    <string name="collapse_menu_text" msgid="7515008122450342029">"Close menu"</string>
+    <string name="expand_menu_text" msgid="3847736164494181168">"Open menu"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-en-rIN/strings.xml b/libs/WindowManager/Shell/res/values-en-rIN/strings.xml
index a108e89..ea91014 100644
--- a/libs/WindowManager/Shell/res/values-en-rIN/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rIN/strings.xml
@@ -32,13 +32,13 @@
     <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Resize"</string>
     <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Stash"</string>
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Unstash"</string>
-    <string name="dock_forced_resizable" msgid="1749750436092293116">"App may not work with split-screen."</string>
-    <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"App does not support split-screen."</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>
     <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"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="703810061635792791">"Split screen divider"</string>
-    <string name="divider_title" msgid="5482989479865361192">"Split screen divider"</string>
+    <string name="accessibility_divider" msgid="6407584574218956849">"Split screen divider"</string>
+    <string name="divider_title" msgid="1963391955593749442">"Split screen divider"</string>
     <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Left full screen"</string>
     <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Left 70%"</string>
     <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Left 50%"</string>
@@ -84,7 +84,7 @@
     <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Didn’t fix it?\nTap to revert"</string>
     <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"No camera issues? Tap to dismiss."</string>
     <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"See and do more"</string>
-    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Drag in another app for split-screen"</string>
+    <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Drag in another app for split screen"</string>
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Double-tap outside an app to reposition it"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"Got it"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Expand for more information."</string>
@@ -93,14 +93,21 @@
     <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Cancel"</string>
     <string name="letterbox_restart_restart" msgid="8529976234412442973">"Restart"</string>
     <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Don\'t show again"</string>
+    <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Double-tap to\nmove this app"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"Maximise"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimise"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Close"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Back"</string>
     <string name="handle_text" msgid="1766582106752184456">"Handle"</string>
+    <string name="app_icon_text" msgid="2823268023931811747">"App icon"</string>
     <string name="fullscreen_text" msgid="1162316685217676079">"Full screen"</string>
     <string name="desktop_text" msgid="1077633567027630454">"Desktop mode"</string>
     <string name="split_screen_text" msgid="1396336058129570886">"Split screen"</string>
     <string name="more_button_text" msgid="3655388105592893530">"More"</string>
     <string name="float_button_text" msgid="9221657008391364581">"Float"</string>
+    <string name="select_text" msgid="5139083974039906583">"Select"</string>
+    <string name="screenshot_text" msgid="1477704010087786671">"Screenshot"</string>
+    <string name="close_text" msgid="4986518933445178928">"Close"</string>
+    <string name="collapse_menu_text" msgid="7515008122450342029">"Close menu"</string>
+    <string name="expand_menu_text" msgid="3847736164494181168">"Open menu"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-en-rXC/strings.xml b/libs/WindowManager/Shell/res/values-en-rXC/strings.xml
index 65af778..f6dac79 100644
--- a/libs/WindowManager/Shell/res/values-en-rXC/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rXC/strings.xml
@@ -32,13 +32,13 @@
     <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‎‎‎‎‏‎‏‎‏‏‏‎‏‎‎‎‎‎‏‎‏‎‏‏‎‎‎‏‏‎‎‏‏‏‎‎‎‎‏‎‏‏‎‎‎‎‏‏‏‏‎‎‎‎‏‏‏‎‎Resize‎‏‎‎‏‎"</string>
     <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‎‎‎‏‎‏‏‎‏‎‏‏‎‎‎‏‎‏‎‏‏‎‎‎‎‏‏‎‏‏‏‏‏‎‎‎‎‏‏‎‎‎‏‎‎‎‏‎‏‏‎‏‏‏‎‎‎‏‎Stash‎‏‎‎‏‎"</string>
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‏‏‏‎‏‎‎‎‎‏‏‏‏‎‎‎‏‏‎‎‎‏‎‏‎‏‏‎‏‏‏‎‎‏‏‏‏‏‎‎‎‏‏‎‏‏‏‎‎‎‎‎‎‎‏‏‏‎‎Unstash‎‏‎‎‏‎"</string>
-    <string name="dock_forced_resizable" msgid="1749750436092293116">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‎‎‎‏‎‎‏‎‎‎‎‏‎‏‏‏‎‎‏‏‎‎‎‎‎‎‎‏‎‎‎‏‎‎‏‏‎‏‏‏‎‏‏‎‏‎‏‏‏‏‏‏‏‏‏‏‎‎‎App may not work with split-screen.‎‏‎‎‏‎"</string>
-    <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‏‎‏‏‎‎‏‏‏‏‏‏‏‎‏‎‎‏‎‏‎‎‏‎‏‎‎‏‏‏‎‎‏‎‏‎‏‏‎‎‏‏‎‎‏‎‎‎‎‎‏‏‎‏‏‏‎‏‎App does not support split-screen.‎‏‎‎‏‎"</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>
     <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‎‎‏‏‎‎‎‎‏‎‎‏‏‎‏‎‎‏‎‎‎‏‏‎‎‎‏‎‎‏‏‏‏‎‎‎‏‎‏‎‎‏‏‏‎‎‎‎‎‏‏‎‏‏‎‎‎‏‎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="703810061635792791">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‎‏‏‏‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‎‎‎‎‏‎‏‏‎‏‎‏‎‎‏‎‎‏‎‎‏‎‏‏‎‏‎‏‏‏‏‏‎‎‏‎‏‏‏‎Split-screen divider‎‏‎‎‏‎"</string>
-    <string name="divider_title" msgid="5482989479865361192">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‏‎‎‎‎‎‏‎‏‏‏‎‏‏‏‏‏‎‏‏‏‏‎‎‎‎‏‏‎‏‏‏‏‏‎‏‎‎‏‎‏‎‎‎‎‏‎‎‎‏‏‎‎‏‎‏‎‎‎‎Split-screen divider‎‏‎‎‏‎"</string>
+    <string name="accessibility_divider" msgid="6407584574218956849">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‎‎‏‏‏‎‏‏‎‎‎‏‎‏‎‎‎‎‎‎‏‏‏‎‎‎‏‎‏‎‏‏‏‎‏‎‎‏‎‏‏‏‏‏‏‏‎‎‎‎‎‎‏‏‎‎‎‏‎Split screen divider‎‏‎‎‏‎"</string>
+    <string name="divider_title" msgid="1963391955593749442">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‏‏‎‎‏‏‏‏‏‏‎‏‎‏‏‏‏‎‏‎‎‏‎‏‎‎‏‏‎‎‎‏‎‎‎‎‎‎‎‏‏‎‎‏‏‎‎‎‏‏‏‏‎‎‎‎‏‎‎Split screen divider‎‏‎‎‏‎"</string>
     <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‎‎‏‏‎‏‏‏‏‏‏‎‎‏‎‎‏‏‏‏‎‎‎‏‏‎‎‎‏‏‏‎‏‎‎‎‏‎‏‎‎‏‎‎‏‎‎‎‎‏‏‎‎‏‏‎‎‎‎Left full screen‎‏‎‎‏‎"</string>
     <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‎‏‎‏‏‏‏‎‏‎‎‎‏‏‏‏‏‏‎‎‎‏‎‎‎‎‎‏‎‎‏‎‏‎‏‏‎‏‏‏‎‏‎‎‎‎‎‏‎‏‏‎‎‏‏‎‎‏‎‎Left 70%‎‏‎‎‏‎"</string>
     <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‎‎‎‏‏‎‏‎‎‎‏‏‏‏‏‏‎‏‏‏‎‏‏‎‏‎‎‎‎‏‏‎‎‎‏‎‏‏‎‎‏‎‏‏‎‏‏‎‏‎‏‎‎‏‏‎‎‏‎Left 50%‎‏‎‎‏‎"</string>
@@ -84,7 +84,7 @@
     <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‎‎‏‏‎‏‏‎‏‏‏‎‏‏‏‏‏‏‎‏‎‎‏‎‏‏‏‎‏‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‏‎‎‎‎‏‏‎‎‏‎Didn’t fix it?‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎Tap to revert‎‏‎‎‏‎"</string>
     <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‏‎‏‏‎‎‏‎‏‏‎‎‏‎‎‎‎‏‎‎‏‎‎‏‎‎‎‏‎‎‏‏‎‎‏‏‎‎‎‎‎‏‏‎‎‎‏‏‏‏‎‎‏‎‎‏‏‏‎No camera issues? Tap to dismiss.‎‏‎‎‏‎"</string>
     <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‏‏‎‏‏‎‏‎‎‏‏‎‏‎‎‎‎‏‏‏‎‏‎‎‎‏‎‎‎‏‏‏‏‎‎‏‏‎‏‎‎‎‎‏‎‎‎‏‏‏‎‏‏‏‏‏‏‎‎See and do more‎‏‎‎‏‎"</string>
-    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‏‎‎‎‏‎‎‎‎‏‎‏‎‏‏‎‎‎‏‏‏‎‎‎‎‎‏‏‎‎‏‏‏‏‎‏‏‎‎‏‎‎‏‏‏‎‏‏‎‏‎‏‏‎‏‏‏‎‎Drag in another app for split-screen‎‏‎‎‏‎"</string>
+    <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‏‏‏‎‎‎‏‏‏‎‏‏‏‏‏‏‏‏‏‏‎‎‎‏‏‎‎‏‎‎‎‎‎‏‏‎‏‎‏‎‏‎‏‏‎‏‏‎‏‎‎‏‏‏‎‎‎‎‏‏‎Drag in another app for split screen‎‏‎‎‏‎"</string>
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‏‏‏‎‏‏‎‎‏‎‏‏‎‎‏‏‎‏‏‏‎‏‏‎‎‎‎‏‏‏‎‏‎‏‏‎‎‏‎‏‎‎‎‎‎‎‎‎‎‏‎‎‏‏‎‏‎‏‎Double-tap outside an app to reposition it‎‏‎‎‏‎"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‎‎‎‏‎‎‏‏‏‏‏‎‎‏‏‏‎‏‎‎‏‎‎‏‎‎‏‏‎‏‏‎‎‎‏‏‎‏‎‎‎‎‏‏‎‎‏‎‎‎‎‏‏‎‏‎‎‏‎Got it‎‏‎‎‏‎"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‎‎‎‎‎‎‎‎‎‏‎‏‏‏‏‎‎‎‏‏‎‏‎‎‎‎‎‎‎‏‏‎‏‏‏‏‎‎‎‎‎‎‏‏‎‎‎‏‎‎‎‏‏‎‏‎‏‎‎Expand for more information.‎‏‎‎‏‎"</string>
@@ -93,14 +93,21 @@
     <string name="letterbox_restart_cancel" msgid="1342209132692537805">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‎‏‎‏‎‏‎‎‎‎‎‎‏‏‏‏‏‎‎‎‎‎‏‏‎‎‎‎‏‎‎‏‎‎‎‎‏‏‎‏‏‎‎‏‎‎‎‎‎‎‏‏‏‎‎‏‏‎‏‎Cancel‎‏‎‎‏‎"</string>
     <string name="letterbox_restart_restart" msgid="8529976234412442973">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‏‎‎‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‎‎‏‏‎‎‎‏‎‎‎‎‎‎‏‎‏‎‏‎‏‏‎‏‏‎‎‎‎‏‎‏‎‏‎‏‏‏‎‏‎Restart‎‏‎‎‏‎"</string>
     <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‎‎‏‏‏‎‎‏‏‎‎‎‎‏‏‏‎‏‎‎‎‏‏‎‎‎‏‏‏‎‎‏‎‏‎‏‏‏‏‎‏‏‏‎‏‎‎‏‎‎‏‎‎‎‎‎‏‎‎Don’t show again‎‏‎‎‏‎"</string>
+    <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‎‎‏‏‏‎‎‎‎‎‏‏‎‎‏‏‏‎‏‏‎‎‎‎‎‏‏‏‎‏‏‏‏‎‏‏‎‎‏‏‎‏‏‎‎‎‎‎‏‏‎‎‏‎‏‏‎‎‎Double-tap to‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎move this app‎‏‎‎‏‎"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‏‎‏‏‏‎‏‎‎‏‎‎‎‎‎‏‏‏‏‎‏‏‎‎‎‏‎‏‎‎‎‎‎‏‎‎‎‏‎‎‏‎‎‎‎‎‎‎‎‎‎‎‎‎‏‎‏‏‎Maximize‎‏‎‎‏‎"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‎‏‏‏‏‎‎‎‏‎‎‏‏‏‎‎‎‏‏‏‏‏‏‏‎‏‏‏‎‏‏‏‏‏‏‎‏‎‏‏‎‏‏‏‏‏‎‏‏‎‏‏‏‎‏‏‎‎‏‎Minimize‎‏‎‎‏‎"</string>
     <string name="close_button_text" msgid="2913281996024033299">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‎‎‎‏‏‎‏‏‏‎‎‎‎‎‏‏‏‎‏‎‎‎‏‎‏‎‎‏‎‎‎‏‏‏‏‎‎‎‏‏‎‏‏‎‏‏‎‎‎‎‎‎‎‏‎‎‏‏‎Close‎‏‎‎‏‎"</string>
     <string name="back_button_text" msgid="1469718707134137085">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‎‎‎‏‏‎‎‏‎‏‎‏‏‏‏‏‎‏‎‏‏‎‎‎‎‎‏‎‎‏‎‎‏‎‎‏‏‏‏‎‏‎‎‎‎‎‎‎‏‎‏‏‏‏‏‏‎‏‎Back‎‏‎‎‏‎"</string>
     <string name="handle_text" msgid="1766582106752184456">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‎‎‏‎‎‎‎‏‎‎‎‎‏‎‏‎‎‏‎‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‎‎‎‏‏‎‎‏‏‏‎‎‎‎‏‎‎‎‏‎‎‎‎Handle‎‏‎‎‏‎"</string>
+    <string name="app_icon_text" msgid="2823268023931811747">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‏‏‎‎‏‎‏‏‏‎‎‏‎‎‎‎‏‏‎‏‎‎‏‏‏‏‎‎‏‏‏‎‎‎‎‎‏‎‏‏‎‎‏‎‏‏‎‏‏‏‏‎‏‎‎‎‏‏‎App Icon‎‏‎‎‏‎"</string>
     <string name="fullscreen_text" msgid="1162316685217676079">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‎‎‎‎‎‏‎‎‎‎‏‎‏‏‎‎‎‎‎‏‏‎‏‏‏‎‏‏‏‏‏‎‎‏‎‏‏‏‎‏‎‎‎‏‎‏‏‏‎‏‏‎‎‏‎‏‏‏‏‎Fullscreen‎‏‎‎‏‎"</string>
     <string name="desktop_text" msgid="1077633567027630454">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‏‎‏‏‏‏‎‏‎‎‏‎‎‎‎‏‏‎‎‎‎‎‎‏‎‏‎‎‎‎‏‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‎‏‏‏‎‏‏‎‎Desktop Mode‎‏‎‎‏‎"</string>
     <string name="split_screen_text" msgid="1396336058129570886">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‎‏‏‎‏‏‎‎‎‎‎‏‏‎‎‏‎‎‎‎‎‏‏‏‏‏‏‎‎‏‎‏‎‏‏‏‏‏‎‎‎‎‏‏‏‎‏‏‏‎‎‎‏‎‎‎‏‏‎‎Split Screen‎‏‎‎‏‎"</string>
     <string name="more_button_text" msgid="3655388105592893530">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‏‎‏‎‏‏‏‎‏‎‏‎‎‎‏‏‎‎‎‎‎‏‏‏‎‏‎‏‏‎‏‏‏‎‎‎‎‎‏‏‎‏‎‏‏‏‎‎‎‎‎‏‎‏‏‎‏‎‎More‎‏‎‎‏‎"</string>
     <string name="float_button_text" msgid="9221657008391364581">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‎‏‏‏‏‎‏‎‎‎‎‎‏‏‎‎‎‎‏‏‎‏‎‎‎‏‏‎‏‎‏‎‎‎‏‎‎‏‏‏‏‏‏‏‏‎‎‏‎‏‎Float‎‏‎‎‏‎"</string>
+    <string name="select_text" msgid="5139083974039906583">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‏‏‏‎‏‎‏‎‎‎‏‏‎‏‏‎‎‎‏‏‎‏‎‎‏‎‏‎‏‏‏‎‏‏‏‏‎‎‎‏‏‎‏‎‎‏‎‏‎‎‏‎‎‎‏‎‏‏‏‎Select‎‏‎‎‏‎"</string>
+    <string name="screenshot_text" msgid="1477704010087786671">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‎‎‏‎‎‎‎‎‎‏‏‏‎‏‏‎‏‏‏‏‏‏‎‏‏‏‏‏‎‎‏‏‎‎‎‏‏‏‎‎‏‏‏‏‏‎‏‎‎‎‏‎‏‎‏‏‏‏‎Screenshot‎‏‎‎‏‎"</string>
+    <string name="close_text" msgid="4986518933445178928">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‏‎‏‎‎‏‏‎‎‏‏‏‎‏‎‏‏‎‎‏‎‎‎‏‏‎‎‏‏‎‏‏‏‎‏‏‏‎‎‎‎‏‎‎‏‏‏‎‏‏‎‎‎‏‏‎‎‎‎‎Close‎‏‎‎‏‎"</string>
+    <string name="collapse_menu_text" msgid="7515008122450342029">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‎‎‎‏‎‎‏‎‏‎‏‎‏‎‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‏‎‏‏‎‏‏‎‏‎‎‏‏‏‏‏‏‎‎‎‎‏‎‎‎‏‏‎‏‎Close Menu‎‏‎‎‏‎"</string>
+    <string name="expand_menu_text" msgid="3847736164494181168">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‎‏‎‏‏‎‎‏‎‏‏‏‏‎‎‏‏‏‏‎‏‎‎‏‏‏‏‏‎‎‏‎‏‎‎‎‎‏‏‎‎‏‏‎‎‎‎‎‏‏‎‎‏‏‎‎‎‎‎Open Menu‎‏‎‎‏‎"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-es-rUS/strings.xml b/libs/WindowManager/Shell/res/values-es-rUS/strings.xml
index c26532e..0190ea8 100644
--- a/libs/WindowManager/Shell/res/values-es-rUS/strings.xml
+++ b/libs/WindowManager/Shell/res/values-es-rUS/strings.xml
@@ -32,13 +32,13 @@
     <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Cambiar el tamaño"</string>
     <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Almacenar de manera segura"</string>
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Dejar de almacenar de manera segura"</string>
-    <string name="dock_forced_resizable" msgid="1749750436092293116">"Es posible que la app no funcione en el modo de pantalla dividida."</string>
-    <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"La app no es compatible con la función de pantalla dividida."</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>
     <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"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="703810061635792791">"Divisor de pantalla dividida"</string>
-    <string name="divider_title" msgid="5482989479865361192">"Divisor de pantalla dividida"</string>
+    <string name="accessibility_divider" msgid="6407584574218956849">"Divisor de pantalla dividida"</string>
+    <string name="divider_title" msgid="1963391955593749442">"Divisor de pantalla dividida"</string>
     <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Pantalla izquierda completa"</string>
     <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Izquierda: 70%"</string>
     <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Izquierda: 50%"</string>
@@ -49,14 +49,10 @@
     <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Superior: 50%"</string>
     <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Superior: 30%"</string>
     <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Pantalla inferior completa"</string>
-    <!-- no translation found for accessibility_split_left (1713683765575562458) -->
-    <skip />
-    <!-- no translation found for accessibility_split_right (8441001008181296837) -->
-    <skip />
-    <!-- no translation found for accessibility_split_top (2789329702027147146) -->
-    <skip />
-    <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
-    <skip />
+    <string name="accessibility_split_left" msgid="1713683765575562458">"Dividir a la izquierda"</string>
+    <string name="accessibility_split_right" msgid="8441001008181296837">"Dividir a la derecha"</string>
+    <string name="accessibility_split_top" msgid="2789329702027147146">"Dividir en la parte superior"</string>
+    <string name="accessibility_split_bottom" msgid="8694551025220868191">"Dividir en la parte inferior"</string>
     <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Cómo usar el modo de una mano"</string>
     <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Para salir, desliza el dedo hacia arriba desde la parte inferior de la pantalla o presiona cualquier parte arriba de la app"</string>
     <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Iniciar el modo de una mano"</string>
@@ -88,7 +84,7 @@
     <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"¿No se resolvió?\nPresiona para revertir los cambios"</string>
     <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"¿No tienes problemas con la cámara? Presionar para descartar."</string>
     <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Aprovecha más"</string>
-    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Arrastra otra app para el modo de pantalla dividida"</string>
+    <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Arrastra otra app para el modo de pantalla dividida"</string>
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Presiona dos veces fuera de una app para cambiar su ubicación"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"Entendido"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Expande para obtener más información."</string>
@@ -97,14 +93,21 @@
     <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Cancelar"</string>
     <string name="letterbox_restart_restart" msgid="8529976234412442973">"Reiniciar"</string>
     <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"No volver a mostrar"</string>
+    <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Presiona dos veces\npara mover esta app"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"Maximizar"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimizar"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Cerrar"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Atrás"</string>
     <string name="handle_text" msgid="1766582106752184456">"Controlador"</string>
+    <string name="app_icon_text" msgid="2823268023931811747">"Ícono de la app"</string>
     <string name="fullscreen_text" msgid="1162316685217676079">"Pantalla completa"</string>
     <string name="desktop_text" msgid="1077633567027630454">"Modo de escritorio"</string>
     <string name="split_screen_text" msgid="1396336058129570886">"Pantalla dividida"</string>
     <string name="more_button_text" msgid="3655388105592893530">"Más"</string>
     <string name="float_button_text" msgid="9221657008391364581">"Flotante"</string>
+    <string name="select_text" msgid="5139083974039906583">"Seleccionar"</string>
+    <string name="screenshot_text" msgid="1477704010087786671">"Captura de pantalla"</string>
+    <string name="close_text" msgid="4986518933445178928">"Cerrar"</string>
+    <string name="collapse_menu_text" msgid="7515008122450342029">"Cerrar menú"</string>
+    <string name="expand_menu_text" msgid="3847736164494181168">"Abrir el menú"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-es/strings.xml b/libs/WindowManager/Shell/res/values-es/strings.xml
index 58deed5..9c5e0c4 100644
--- a/libs/WindowManager/Shell/res/values-es/strings.xml
+++ b/libs/WindowManager/Shell/res/values-es/strings.xml
@@ -32,13 +32,13 @@
     <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Cambiar tamaño"</string>
     <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Esconder"</string>
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"No esconder"</string>
-    <string name="dock_forced_resizable" msgid="1749750436092293116">"Es posible que la aplicación no funcione con la pantalla dividida."</string>
-    <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"La aplicación no admite la pantalla dividida."</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>
     <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"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="703810061635792791">"Dividir la pantalla"</string>
-    <string name="divider_title" msgid="5482989479865361192">"Divisor de pantalla dividida"</string>
+    <string name="accessibility_divider" msgid="6407584574218956849">"Divisor de pantalla dividida"</string>
+    <string name="divider_title" msgid="1963391955593749442">"Divisor de pantalla dividida"</string>
     <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Pantalla izquierda completa"</string>
     <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Izquierda 70%"</string>
     <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Izquierda 50%"</string>
@@ -49,14 +49,10 @@
     <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Superior 50%"</string>
     <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Superior 30%"</string>
     <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Pantalla inferior completa"</string>
-    <!-- no translation found for accessibility_split_left (1713683765575562458) -->
-    <skip />
-    <!-- no translation found for accessibility_split_right (8441001008181296837) -->
-    <skip />
-    <!-- no translation found for accessibility_split_top (2789329702027147146) -->
-    <skip />
-    <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
-    <skip />
+    <string name="accessibility_split_left" msgid="1713683765575562458">"Dividir en la parte izquierda"</string>
+    <string name="accessibility_split_right" msgid="8441001008181296837">"Dividir en la parte derecha"</string>
+    <string name="accessibility_split_top" msgid="2789329702027147146">"Dividir en la parte superior"</string>
+    <string name="accessibility_split_bottom" msgid="8694551025220868191">"Dividir en la parte inferior"</string>
     <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Usar modo Una mano"</string>
     <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Para salir, desliza el dedo hacia arriba desde la parte inferior de la pantalla o toca cualquier zona que haya encima de la aplicación"</string>
     <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Iniciar modo Una mano"</string>
@@ -83,12 +79,12 @@
     <string name="notification_bubble_title" msgid="6082910224488253378">"Burbuja"</string>
     <string name="manage_bubbles_text" msgid="7730624269650594419">"Gestionar"</string>
     <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Burbuja cerrada."</string>
-    <string name="restart_button_description" msgid="6712141648865547958">"Toca para reiniciar esta aplicación y obtener una mejor vista."</string>
+    <string name="restart_button_description" msgid="6712141648865547958">"Toca para reiniciar esta aplicación y verlo mejor."</string>
     <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"¿Problemas con la cámara?\nToca para reajustar"</string>
     <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"¿No se ha solucionado?\nToca para revertir"</string>
     <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"¿No hay problemas con la cámara? Toca para cerrar."</string>
     <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Consulta más información y haz más"</string>
-    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Arrastra otra aplicación para activar la pantalla dividida"</string>
+    <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Arrastra otra aplicación para activar la pantalla dividida"</string>
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Toca dos veces fuera de una aplicación para cambiarla de posición"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"Entendido"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Mostrar más información"</string>
@@ -97,14 +93,21 @@
     <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Cancelar"</string>
     <string name="letterbox_restart_restart" msgid="8529976234412442973">"Reiniciar"</string>
     <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"No volver a mostrar"</string>
+    <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Toca dos veces para\nmover esta aplicación"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"Maximizar"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimizar"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Cerrar"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Atrás"</string>
     <string name="handle_text" msgid="1766582106752184456">"Controlador"</string>
+    <string name="app_icon_text" msgid="2823268023931811747">"Icono de la aplicación"</string>
     <string name="fullscreen_text" msgid="1162316685217676079">"Pantalla completa"</string>
     <string name="desktop_text" msgid="1077633567027630454">"Modo Escritorio"</string>
     <string name="split_screen_text" msgid="1396336058129570886">"Pantalla dividida"</string>
     <string name="more_button_text" msgid="3655388105592893530">"Más"</string>
     <string name="float_button_text" msgid="9221657008391364581">"Flotante"</string>
+    <string name="select_text" msgid="5139083974039906583">"Seleccionar"</string>
+    <string name="screenshot_text" msgid="1477704010087786671">"Captura de pantalla"</string>
+    <string name="close_text" msgid="4986518933445178928">"Cerrar"</string>
+    <string name="collapse_menu_text" msgid="7515008122450342029">"Cerrar menú"</string>
+    <string name="expand_menu_text" msgid="3847736164494181168">"Abrir menú"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-et/strings.xml b/libs/WindowManager/Shell/res/values-et/strings.xml
index c0d0588..fb23d11 100644
--- a/libs/WindowManager/Shell/res/values-et/strings.xml
+++ b/libs/WindowManager/Shell/res/values-et/strings.xml
@@ -32,13 +32,13 @@
     <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Suuruse muutmine"</string>
     <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Pane hoidlasse"</string>
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Eemalda hoidlast"</string>
-    <string name="dock_forced_resizable" msgid="1749750436092293116">"Rakendus ei pruugi poolitatud ekraaniga töötada."</string>
-    <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Rakendus ei toeta jagatud ekraani."</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>
     <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"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="703810061635792791">"Ekraanijagaja"</string>
-    <string name="divider_title" msgid="5482989479865361192">"Ekraanijagaja"</string>
+    <string name="accessibility_divider" msgid="6407584574218956849">"Jagatud ekraanikuva jaotur"</string>
+    <string name="divider_title" msgid="1963391955593749442">"Jagatud ekraanikuva jaotur"</string>
     <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Vasak täisekraan"</string>
     <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Vasak: 70%"</string>
     <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Vasak: 50%"</string>
@@ -49,14 +49,10 @@
     <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Ülemine: 50%"</string>
     <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Ülemine: 30%"</string>
     <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Alumine täisekraan"</string>
-    <!-- no translation found for accessibility_split_left (1713683765575562458) -->
-    <skip />
-    <!-- no translation found for accessibility_split_right (8441001008181296837) -->
-    <skip />
-    <!-- no translation found for accessibility_split_top (2789329702027147146) -->
-    <skip />
-    <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
-    <skip />
+    <string name="accessibility_split_left" msgid="1713683765575562458">"Jaga vasakule"</string>
+    <string name="accessibility_split_right" msgid="8441001008181296837">"Jaga paremale"</string>
+    <string name="accessibility_split_top" msgid="2789329702027147146">"Jaga üles"</string>
+    <string name="accessibility_split_bottom" msgid="8694551025220868191">"Jaga alla"</string>
     <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Ühekäerežiimi kasutamine"</string>
     <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Väljumiseks pühkige ekraani alaosast üles või puudutage rakenduse kohal olevat ala"</string>
     <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Ühekäerežiimi käivitamine"</string>
@@ -88,23 +84,30 @@
     <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Kas probleemi ei lahendatud?\nPuudutage ennistamiseks."</string>
     <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Kas kaameraprobleeme pole? Puudutage loobumiseks."</string>
     <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Vaadake ja tehke rohkem"</string>
-    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Lohistage muusse rakendusse, et jagatud ekraanikuva kasutada"</string>
+    <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Lohistage muusse rakendusse, et jagatud ekraanikuva kasutada"</string>
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Topeltpuudutage rakendusest väljaspool, et selle asendit muuta"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"Selge"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Laiendage lisateabe saamiseks."</string>
     <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Kas taaskäivitada parema vaate saavutamiseks?"</string>
-    <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Saate rakenduse taaskäivitada, et see näeks ekraanikuval parem välja, kuid võite kaotada edenemise või salvestamata muudatused"</string>
+    <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Saate rakenduse taaskäivitada, et see näeks ekraanikuval parem välja, kuid võite kaotada edenemise või salvestamata muudatused."</string>
     <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Tühista"</string>
     <string name="letterbox_restart_restart" msgid="8529976234412442973">"Taaskäivita"</string>
     <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Ära kuva uuesti"</string>
+    <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Rakenduse teisaldamiseks\ntopeltpuudutage"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"Maksimeeri"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimeeri"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Sule"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Tagasi"</string>
     <string name="handle_text" msgid="1766582106752184456">"Käepide"</string>
+    <string name="app_icon_text" msgid="2823268023931811747">"Rakenduse ikoon"</string>
     <string name="fullscreen_text" msgid="1162316685217676079">"Täisekraan"</string>
     <string name="desktop_text" msgid="1077633567027630454">"Lauaarvuti režiim"</string>
     <string name="split_screen_text" msgid="1396336058129570886">"Jagatud ekraanikuva"</string>
     <string name="more_button_text" msgid="3655388105592893530">"Rohkem"</string>
     <string name="float_button_text" msgid="9221657008391364581">"Hõljuv"</string>
+    <string name="select_text" msgid="5139083974039906583">"Vali"</string>
+    <string name="screenshot_text" msgid="1477704010087786671">"Ekraanipilt"</string>
+    <string name="close_text" msgid="4986518933445178928">"Sule"</string>
+    <string name="collapse_menu_text" msgid="7515008122450342029">"Sule menüü"</string>
+    <string name="expand_menu_text" msgid="3847736164494181168">"Ava menüü"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-eu/strings.xml b/libs/WindowManager/Shell/res/values-eu/strings.xml
index 6a1f457..de27a80 100644
--- a/libs/WindowManager/Shell/res/values-eu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-eu/strings.xml
@@ -32,13 +32,13 @@
     <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Aldatu tamaina"</string>
     <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Gorde"</string>
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Ez gorde"</string>
-    <string name="dock_forced_resizable" msgid="1749750436092293116">"Baliteke aplikazioak ez funtzionatzea pantaila zatituan."</string>
-    <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Aplikazioak ez du onartzen pantaila zatitua"</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>
     <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"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 abiarazi bigarren mailako pantailatan."</string>
-    <string name="accessibility_divider" msgid="703810061635792791">"Pantaila-zatitzailea"</string>
-    <string name="divider_title" msgid="5482989479865361192">"Pantaila-zatitzailea"</string>
+    <string name="accessibility_divider" msgid="6407584574218956849">"Pantaila-zatitzailea"</string>
+    <string name="divider_title" msgid="1963391955593749442">"Pantaila-zatitzailea"</string>
     <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Ezarri ezkerraldea pantaila osoan"</string>
     <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Ezarri ezkerraldea % 70en"</string>
     <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Ezarri ezkerraldea % 50en"</string>
@@ -49,14 +49,10 @@
     <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Ezarri goialdea % 50en"</string>
     <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Ezarri goialdea % 30en"</string>
     <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Ezarri behealdea pantaila osoan"</string>
-    <!-- no translation found for accessibility_split_left (1713683765575562458) -->
-    <skip />
-    <!-- no translation found for accessibility_split_right (8441001008181296837) -->
-    <skip />
-    <!-- no translation found for accessibility_split_top (2789329702027147146) -->
-    <skip />
-    <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
-    <skip />
+    <string name="accessibility_split_left" msgid="1713683765575562458">"Zatitu ezkerraldean"</string>
+    <string name="accessibility_split_right" msgid="8441001008181296837">"Zatitu eskuinaldean"</string>
+    <string name="accessibility_split_top" msgid="2789329702027147146">"Zatitu goialdean"</string>
+    <string name="accessibility_split_bottom" msgid="8694551025220868191">"Zatitu behealdean"</string>
     <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Esku bakarreko modua erabiltzea"</string>
     <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Irteteko, pasatu hatza pantailaren behealdetik gora edo sakatu aplikazioaren gainaldea"</string>
     <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Abiarazi esku bakarreko modua"</string>
@@ -88,7 +84,7 @@
     <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Ez al da konpondu?\nLeheneratzeko, sakatu hau."</string>
     <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Ez daukazu arazorik kamerarekin? Baztertzeko, sakatu hau."</string>
     <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Ikusi eta egin gauza gehiago"</string>
-    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Pantaila zatituta ikusteko, arrastatu beste aplikazio bat"</string>
+    <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Pantaila zatitua ikusteko, arrastatu beste aplikazio bat"</string>
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Aplikazioaren posizioa aldatzeko, sakatu birritan haren kanpoaldea"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"Ados"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Informazio gehiago lortzeko, zabaldu hau."</string>
@@ -97,14 +93,21 @@
     <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Utzi"</string>
     <string name="letterbox_restart_restart" msgid="8529976234412442973">"Berrabiarazi"</string>
     <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Ez erakutsi berriro"</string>
+    <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Sakatu birritan\naplikazioa mugitzeko"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"Maximizatu"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimizatu"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Itxi"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Atzera"</string>
     <string name="handle_text" msgid="1766582106752184456">"Kontu-izena"</string>
+    <string name="app_icon_text" msgid="2823268023931811747">"Aplikazioaren ikonoa"</string>
     <string name="fullscreen_text" msgid="1162316685217676079">"Pantaila osoa"</string>
     <string name="desktop_text" msgid="1077633567027630454">"Ordenagailuetarako modua"</string>
     <string name="split_screen_text" msgid="1396336058129570886">"Pantaila zatitua"</string>
     <string name="more_button_text" msgid="3655388105592893530">"Gehiago"</string>
     <string name="float_button_text" msgid="9221657008391364581">"Leiho gainerakorra"</string>
+    <string name="select_text" msgid="5139083974039906583">"Hautatu"</string>
+    <string name="screenshot_text" msgid="1477704010087786671">"Pantaila-argazkia"</string>
+    <string name="close_text" msgid="4986518933445178928">"Itxi"</string>
+    <string name="collapse_menu_text" msgid="7515008122450342029">"Itxi menua"</string>
+    <string name="expand_menu_text" msgid="3847736164494181168">"Ireki menua"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-fa/strings.xml b/libs/WindowManager/Shell/res/values-fa/strings.xml
index 59c119d..edff47a 100644
--- a/libs/WindowManager/Shell/res/values-fa/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fa/strings.xml
@@ -32,13 +32,13 @@
     <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"تغییر اندازه"</string>
     <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"مخفی‌سازی"</string>
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"لغو مخفی‌سازی"</string>
-    <string name="dock_forced_resizable" msgid="1749750436092293116">"ممکن است برنامه با «صفحهٔ دونیمه» کار نکند."</string>
-    <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"برنامه از تقسیم صفحه پشتیبانی نمی‌کند."</string>
+    <string name="dock_forced_resizable" msgid="7429086980048964687">"ممکن است برنامه با صفحهٔ دونیمه کار نکند"</string>
+    <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"برنامه از صفحهٔ دونیمه پشتیبانی نمی‌کند"</string>
     <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"این برنامه فقط در ۱ پنجره می‌تواند باز شود."</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="703810061635792791">"تقسیم‌کننده صفحه"</string>
-    <string name="divider_title" msgid="5482989479865361192">"تقسیم‌کننده صفحهٔ دونیمه"</string>
+    <string name="accessibility_divider" msgid="6407584574218956849">"تقسیم‌کننده صفحهٔ دونیمه"</string>
+    <string name="divider_title" msgid="1963391955593749442">"تقسیم‌کننده صفحهٔ دونیمه"</string>
     <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"تمام‌صفحه چپ"</string>
     <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"٪۷۰ چپ"</string>
     <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"٪۵۰ چپ"</string>
@@ -49,21 +49,17 @@
     <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"٪۵۰ بالا"</string>
     <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"٪۳۰ بالا"</string>
     <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"تمام‌صفحه پایین"</string>
-    <!-- no translation found for accessibility_split_left (1713683765575562458) -->
-    <skip />
-    <!-- no translation found for accessibility_split_right (8441001008181296837) -->
-    <skip />
-    <!-- no translation found for accessibility_split_top (2789329702027147146) -->
-    <skip />
-    <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
-    <skip />
+    <string name="accessibility_split_left" msgid="1713683765575562458">"تقسیم از چپ"</string>
+    <string name="accessibility_split_right" msgid="8441001008181296837">"تقسیم از راست"</string>
+    <string name="accessibility_split_top" msgid="2789329702027147146">"تقسیم از بالا"</string>
+    <string name="accessibility_split_bottom" msgid="8694551025220868191">"تقسیم از پایین"</string>
     <string name="one_handed_tutorial_title" msgid="4583241688067426350">"استفاده از حالت یک‌دستی"</string>
     <string name="one_handed_tutorial_description" msgid="3486582858591353067">"برای خارج شدن، از پایین صفحه‌نمایش تند به‌طرف بالا بکشید یا در هر جایی از بالای برنامه که می‌خواهید ضربه بزنید"</string>
     <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"آغاز «حالت یک‌دستی»"</string>
     <string name="accessibility_action_stop_one_handed" msgid="1369940261782179442">"خروج از «حالت یک‌دستی»"</string>
     <string name="bubbles_settings_button_description" msgid="1301286017420516912">"تنظیمات برای حبابک‌های <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
-    <string name="bubble_overflow_button_content_description" msgid="8160974472718594382">"لبریزشده"</string>
-    <string name="bubble_accessibility_action_add_back" msgid="1830101076853540953">"افزودن برگشت به پشته"</string>
+    <string name="bubble_overflow_button_content_description" msgid="8160974472718594382">"سرریز"</string>
+    <string name="bubble_accessibility_action_add_back" msgid="1830101076853540953">"افزودن برگشتن به پشته"</string>
     <string name="bubble_content_description_single" msgid="8495748092720065813">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> از <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
     <string name="bubble_content_description_stack" msgid="8071515017164630429">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> از <xliff:g id="APP_NAME">%2$s</xliff:g> و <xliff:g id="BUBBLE_COUNT">%3$d</xliff:g> مورد بیشتر"</string>
     <string name="bubble_accessibility_action_move_top_left" msgid="2644118920500782758">"انتقال به بالا سمت راست"</string>
@@ -88,7 +84,7 @@
     <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"مشکل برطرف نشد؟\nبرای برگرداندن ضربه بزنید"</string>
     <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"دوربین مشکلی ندارد؟ برای بستن ضربه بزنید."</string>
     <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"از چندین برنامه به‌طور هم‌زمان استفاده کنید"</string>
-    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"برای حالت صفحهٔ دونیمه، در برنامه‌ای دیگر بکشید"</string>
+    <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"برای حالت صفحهٔ دونیمه، در برنامه‌ای دیگر بکشید"</string>
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"برای جابه‌جا کردن برنامه، بیرون از آن دوضربه بزنید"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"متوجه‌ام"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"برای اطلاعات بیشتر، گسترده کنید."</string>
@@ -97,14 +93,21 @@
     <string name="letterbox_restart_cancel" msgid="1342209132692537805">"لغو کردن"</string>
     <string name="letterbox_restart_restart" msgid="8529976234412442973">"بازراه‌اندازی"</string>
     <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"دوباره نشان داده نشود"</string>
+    <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"برای جابه‌جا کردن این برنامه\nدوضربه بزنید"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"بزرگ کردن"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"کوچک کردن"</string>
     <string name="close_button_text" msgid="2913281996024033299">"بستن"</string>
     <string name="back_button_text" msgid="1469718707134137085">"برگشتن"</string>
     <string name="handle_text" msgid="1766582106752184456">"دستگیره"</string>
+    <string name="app_icon_text" msgid="2823268023931811747">"نماد برنامه"</string>
     <string name="fullscreen_text" msgid="1162316685217676079">"تمام‌صفحه"</string>
     <string name="desktop_text" msgid="1077633567027630454">"حالت رایانه"</string>
     <string name="split_screen_text" msgid="1396336058129570886">"صفحهٔ دونیمه"</string>
     <string name="more_button_text" msgid="3655388105592893530">"بیشتر"</string>
     <string name="float_button_text" msgid="9221657008391364581">"شناور"</string>
+    <string name="select_text" msgid="5139083974039906583">"انتخاب"</string>
+    <string name="screenshot_text" msgid="1477704010087786671">"نماگرفت"</string>
+    <string name="close_text" msgid="4986518933445178928">"بستن"</string>
+    <string name="collapse_menu_text" msgid="7515008122450342029">"بستن منو"</string>
+    <string name="expand_menu_text" msgid="3847736164494181168">"باز کردن منو"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-fi/strings.xml b/libs/WindowManager/Shell/res/values-fi/strings.xml
index edb1dae..92fa760 100644
--- a/libs/WindowManager/Shell/res/values-fi/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fi/strings.xml
@@ -32,13 +32,13 @@
     <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Muuta kokoa"</string>
     <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Lisää turvasäilytykseen"</string>
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Poista turvasäilytyksestä"</string>
-    <string name="dock_forced_resizable" msgid="1749750436092293116">"Sovellus ei ehkä toimi jaetulla näytöllä."</string>
-    <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Sovellus ei tue jaetun näytön tilaa."</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>
     <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"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="703810061635792791">"Näytön jakaja"</string>
-    <string name="divider_title" msgid="5482989479865361192">"Näytönjakaja"</string>
+    <string name="accessibility_divider" msgid="6407584574218956849">"Näytönjakaja"</string>
+    <string name="divider_title" msgid="1963391955593749442">"Näytönjakaja"</string>
     <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Vasen koko näytölle"</string>
     <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Vasen 70 %"</string>
     <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Vasen 50 %"</string>
@@ -49,14 +49,10 @@
     <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Yläosa 50 %"</string>
     <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Yläosa 30 %"</string>
     <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Alaosa koko näytölle"</string>
-    <!-- no translation found for accessibility_split_left (1713683765575562458) -->
-    <skip />
-    <!-- no translation found for accessibility_split_right (8441001008181296837) -->
-    <skip />
-    <!-- no translation found for accessibility_split_top (2789329702027147146) -->
-    <skip />
-    <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
-    <skip />
+    <string name="accessibility_split_left" msgid="1713683765575562458">"Vasemmalla"</string>
+    <string name="accessibility_split_right" msgid="8441001008181296837">"Oikealla"</string>
+    <string name="accessibility_split_top" msgid="2789329702027147146">"Ylhäällä"</string>
+    <string name="accessibility_split_bottom" msgid="8694551025220868191">"Alhaalla"</string>
     <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Yhden käden moodin käyttö"</string>
     <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Poistu pyyhkäisemällä ylös näytön alareunasta tai napauttamalla sovelluksen yllä"</string>
     <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Käynnistä yhden käden moodi"</string>
@@ -88,7 +84,7 @@
     <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Eikö ongelma ratkennut?\nKumoa napauttamalla"</string>
     <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Ei ongelmia kameran kanssa? Hylkää napauttamalla."</string>
     <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Näe ja tee enemmän"</string>
-    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Käytä jaettua näyttöä vetämällä tähän toinen sovellus"</string>
+    <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Käytä jaettua näyttöä vetämällä tähän toinen sovellus"</string>
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Kaksoisnapauta sovelluksen ulkopuolella, jos haluat siirtää sitä"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Katso lisätietoja laajentamalla."</string>
@@ -97,14 +93,21 @@
     <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Peru"</string>
     <string name="letterbox_restart_restart" msgid="8529976234412442973">"Käynnistä uudelleen"</string>
     <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Älä näytä uudelleen"</string>
+    <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Kaksoisnapauta, jos\nhaluat siirtää sovellusta"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"Suurenna"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Pienennä"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Sulje"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Takaisin"</string>
     <string name="handle_text" msgid="1766582106752184456">"Kahva"</string>
+    <string name="app_icon_text" msgid="2823268023931811747">"Sovelluskuvake"</string>
     <string name="fullscreen_text" msgid="1162316685217676079">"Koko näyttö"</string>
     <string name="desktop_text" msgid="1077633567027630454">"Työpöytätila"</string>
     <string name="split_screen_text" msgid="1396336058129570886">"Jaettu näyttö"</string>
     <string name="more_button_text" msgid="3655388105592893530">"Lisää"</string>
     <string name="float_button_text" msgid="9221657008391364581">"Kelluva ikkuna"</string>
+    <string name="select_text" msgid="5139083974039906583">"Valitse"</string>
+    <string name="screenshot_text" msgid="1477704010087786671">"Kuvakaappaus"</string>
+    <string name="close_text" msgid="4986518933445178928">"Sulje"</string>
+    <string name="collapse_menu_text" msgid="7515008122450342029">"Sulje valikko"</string>
+    <string name="expand_menu_text" msgid="3847736164494181168">"Avaa valikko"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml b/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml
index ee6e7be..6d19e55 100644
--- a/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml
@@ -32,13 +32,13 @@
     <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Redimensionner"</string>
     <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Ajouter à la réserve"</string>
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Retirer de la réserve"</string>
-    <string name="dock_forced_resizable" msgid="1749750436092293116">"Il est possible que l\'application ne fonctionne pas en mode Écran partagé."</string>
-    <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"L\'application n\'est pas compatible avec l\'écran partagé."</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>
     <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Cette application ne peut être ouverte que dans une 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="703810061635792791">"Séparateur d\'écran partagé"</string>
-    <string name="divider_title" msgid="5482989479865361192">"Séparateur d\'écran partagé"</string>
+    <string name="accessibility_divider" msgid="6407584574218956849">"Séparateur d\'écran partagé"</string>
+    <string name="divider_title" msgid="1963391955593749442">"Séparateur d\'écran partagé"</string>
     <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Plein écran à la gauche"</string>
     <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"70 % à la gauche"</string>
     <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"50 % à la gauche"</string>
@@ -49,14 +49,10 @@
     <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"50 % dans le haut"</string>
     <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"30 % dans le haut"</string>
     <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Plein écran dans le bas"</string>
-    <!-- no translation found for accessibility_split_left (1713683765575562458) -->
-    <skip />
-    <!-- no translation found for accessibility_split_right (8441001008181296837) -->
-    <skip />
-    <!-- no translation found for accessibility_split_top (2789329702027147146) -->
-    <skip />
-    <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
-    <skip />
+    <string name="accessibility_split_left" msgid="1713683765575562458">"Diviser à gauche"</string>
+    <string name="accessibility_split_right" msgid="8441001008181296837">"Diviser à droite"</string>
+    <string name="accessibility_split_top" msgid="2789329702027147146">"Diviser dans la partie supérieure"</string>
+    <string name="accessibility_split_bottom" msgid="8694551025220868191">"Diviser dans la partie inférieure"</string>
     <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Utiliser le mode Une main"</string>
     <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Pour quitter, balayez l\'écran du bas vers le haut, ou touchez n\'importe où sur l\'écran en haut de l\'application"</string>
     <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Démarrer le mode Une main"</string>
@@ -83,12 +79,12 @@
     <string name="notification_bubble_title" msgid="6082910224488253378">"Bulle"</string>
     <string name="manage_bubbles_text" msgid="7730624269650594419">"Gérer"</string>
     <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Bulle ignorée."</string>
-    <string name="restart_button_description" msgid="6712141648865547958">"Touchez pour redémarrer cette application afin d\'obtenir un meilleur affichage."</string>
+    <string name="restart_button_description" msgid="6712141648865547958">"Pour obtenir un meilleur affichage, touchez pour redémarrer cette application."</string>
     <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Problèmes d\'appareil photo?\nTouchez pour réajuster"</string>
     <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Problème non résolu?\nTouchez pour rétablir"</string>
     <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Aucun problème d\'appareil photo? Touchez pour ignorer."</string>
     <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Voir et en faire plus"</string>
-    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Faites glisser une autre application pour utiliser l\'écran partagé"</string>
+    <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Faites glisser une autre application pour utiliser l\'écran partagé"</string>
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Touchez deux fois à côté d\'une application pour la repositionner"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Développer pour en savoir plus."</string>
@@ -97,14 +93,21 @@
     <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Annuler"</string>
     <string name="letterbox_restart_restart" msgid="8529976234412442973">"Redémarrer"</string>
     <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Ne plus afficher"</string>
+    <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Toucher deux fois pour\ndéplacer cette application"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"Agrandir"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Réduire"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Fermer"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Retour"</string>
     <string name="handle_text" msgid="1766582106752184456">"Identifiant"</string>
+    <string name="app_icon_text" msgid="2823268023931811747">"Icône de l\'application"</string>
     <string name="fullscreen_text" msgid="1162316685217676079">"Plein écran"</string>
     <string name="desktop_text" msgid="1077633567027630454">"Mode Bureau"</string>
     <string name="split_screen_text" msgid="1396336058129570886">"Écran partagé"</string>
     <string name="more_button_text" msgid="3655388105592893530">"Plus"</string>
     <string name="float_button_text" msgid="9221657008391364581">"Flottant"</string>
+    <string name="select_text" msgid="5139083974039906583">"Sélectionner"</string>
+    <string name="screenshot_text" msgid="1477704010087786671">"Capture d\'écran"</string>
+    <string name="close_text" msgid="4986518933445178928">"Fermer"</string>
+    <string name="collapse_menu_text" msgid="7515008122450342029">"Fermer le menu"</string>
+    <string name="expand_menu_text" msgid="3847736164494181168">"Ouvrir le menu"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-fr/strings.xml b/libs/WindowManager/Shell/res/values-fr/strings.xml
index c9fc061..5fb91f7 100644
--- a/libs/WindowManager/Shell/res/values-fr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fr/strings.xml
@@ -32,13 +32,13 @@
     <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Redimensionner"</string>
     <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Stash"</string>
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Unstash"</string>
-    <string name="dock_forced_resizable" msgid="1749750436092293116">"Il est possible que l\'application ne fonctionne pas en mode Écran partagé."</string>
-    <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Application incompatible avec l\'écran partagé."</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>
     <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"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="703810061635792791">"Séparateur d\'écran partagé"</string>
-    <string name="divider_title" msgid="5482989479865361192">"Séparateur d\'écran partagé"</string>
+    <string name="accessibility_divider" msgid="6407584574218956849">"Séparateur d\'écran partagé"</string>
+    <string name="divider_title" msgid="1963391955593749442">"Séparateur d\'écran partagé"</string>
     <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Écran de gauche en plein écran"</string>
     <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Écran de gauche à 70 %"</string>
     <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Écran de gauche à 50 %"</string>
@@ -49,14 +49,10 @@
     <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Écran du haut à 50 %"</string>
     <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Écran du haut à 30 %"</string>
     <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Écran du bas en plein écran"</string>
-    <!-- no translation found for accessibility_split_left (1713683765575562458) -->
-    <skip />
-    <!-- no translation found for accessibility_split_right (8441001008181296837) -->
-    <skip />
-    <!-- no translation found for accessibility_split_top (2789329702027147146) -->
-    <skip />
-    <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
-    <skip />
+    <string name="accessibility_split_left" msgid="1713683765575562458">"Affichée à gauche"</string>
+    <string name="accessibility_split_right" msgid="8441001008181296837">"Affichée à droite"</string>
+    <string name="accessibility_split_top" msgid="2789329702027147146">"Affichée en haut"</string>
+    <string name="accessibility_split_bottom" msgid="8694551025220868191">"Affichée en haut"</string>
     <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Utiliser le mode une main"</string>
     <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Pour quitter, balayez l\'écran de bas en haut ou appuyez n\'importe où au-dessus de l\'application"</string>
     <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Démarrer le mode une main"</string>
@@ -83,28 +79,35 @@
     <string name="notification_bubble_title" msgid="6082910224488253378">"Bulle"</string>
     <string name="manage_bubbles_text" msgid="7730624269650594419">"Gérer"</string>
     <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Bulle fermée."</string>
-    <string name="restart_button_description" msgid="6712141648865547958">"Appuyez pour redémarrer cette appli et avoir une meilleure vue."</string>
+    <string name="restart_button_description" msgid="6712141648865547958">"Pour un meilleur affichage, appuyez pour redémarrer cette appli."</string>
     <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Problèmes d\'appareil photo ?\nAppuyez pour réajuster"</string>
     <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Problème non résolu ?\nAppuyez pour rétablir"</string>
     <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Aucun problème d\'appareil photo ? Appuyez pour ignorer."</string>
     <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Voir et interagir plus"</string>
-    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Faites glisser une autre appli pour utiliser l\'écran partagé"</string>
+    <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Faites glisser une autre appli pour utiliser l\'écran partagé"</string>
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Appuyez deux fois en dehors d\'une appli pour la repositionner"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Développez pour obtenir plus d\'informations"</string>
     <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Redémarrer pour améliorer l\'affichage ?"</string>
-    <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Vous pouvez redémarrer l\'appli pour en améliorer son aspect sur votre écran, mais vous risquez de perdre votre progression ou les modifications non enregistrées"</string>
+    <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Vous pouvez redémarrer l\'appli pour un meilleur rendu sur votre écran, mais il se peut que vous perdiez votre progression ou les modifications non enregistrées"</string>
     <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Annuler"</string>
     <string name="letterbox_restart_restart" msgid="8529976234412442973">"Redémarrer"</string>
     <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Ne plus afficher"</string>
+    <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Appuyez deux fois\npour déplacer cette appli"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"Agrandir"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Réduire"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Fermer"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Retour"</string>
     <string name="handle_text" msgid="1766582106752184456">"Poignée"</string>
+    <string name="app_icon_text" msgid="2823268023931811747">"Icône d\'application"</string>
     <string name="fullscreen_text" msgid="1162316685217676079">"Plein écran"</string>
     <string name="desktop_text" msgid="1077633567027630454">"Mode ordinateur"</string>
     <string name="split_screen_text" msgid="1396336058129570886">"Écran partagé"</string>
     <string name="more_button_text" msgid="3655388105592893530">"Plus"</string>
     <string name="float_button_text" msgid="9221657008391364581">"Flottante"</string>
+    <string name="select_text" msgid="5139083974039906583">"Sélectionner"</string>
+    <string name="screenshot_text" msgid="1477704010087786671">"Capture d\'écran"</string>
+    <string name="close_text" msgid="4986518933445178928">"Fermer"</string>
+    <string name="collapse_menu_text" msgid="7515008122450342029">"Fermer le menu"</string>
+    <string name="expand_menu_text" msgid="3847736164494181168">"Ouvrir le menu"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-gl/strings.xml b/libs/WindowManager/Shell/res/values-gl/strings.xml
index 3e1a93f..c08cff8 100644
--- a/libs/WindowManager/Shell/res/values-gl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-gl/strings.xml
@@ -32,13 +32,13 @@
     <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Cambiar tamaño"</string>
     <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Esconder"</string>
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Non esconder"</string>
-    <string name="dock_forced_resizable" msgid="1749750436092293116">"Pode que a aplicación non funcione coa pantalla dividida."</string>
-    <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"A aplicación non é compatible coa función de pantalla dividida."</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>
     <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"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="703810061635792791">"Divisor de pantalla dividida"</string>
-    <string name="divider_title" msgid="5482989479865361192">"Divisor de pantalla dividida"</string>
+    <string name="accessibility_divider" msgid="6407584574218956849">"Divisor de pantalla dividida"</string>
+    <string name="divider_title" msgid="1963391955593749442">"Divisor de pantalla dividida"</string>
     <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Pantalla completa á esquerda"</string>
     <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"70 % á esquerda"</string>
     <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"50 % á esquerda"</string>
@@ -49,14 +49,10 @@
     <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"50 % arriba"</string>
     <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"30 % arriba"</string>
     <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Pantalla completa abaixo"</string>
-    <!-- no translation found for accessibility_split_left (1713683765575562458) -->
-    <skip />
-    <!-- no translation found for accessibility_split_right (8441001008181296837) -->
-    <skip />
-    <!-- no translation found for accessibility_split_top (2789329702027147146) -->
-    <skip />
-    <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
-    <skip />
+    <string name="accessibility_split_left" msgid="1713683765575562458">"Dividir (esquerda)"</string>
+    <string name="accessibility_split_right" msgid="8441001008181296837">"Dividir (dereita)"</string>
+    <string name="accessibility_split_top" msgid="2789329702027147146">"Dividir (arriba)"</string>
+    <string name="accessibility_split_bottom" msgid="8694551025220868191">"Dividir (abaixo)"</string>
     <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Como se usa o modo dunha soa man?"</string>
     <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Para saír, pasa o dedo cara arriba desde a parte inferior da pantalla ou toca calquera lugar da zona situada encima da aplicación"</string>
     <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Iniciar modo dunha soa man"</string>
@@ -88,7 +84,7 @@
     <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Non se solucionaron os problemas?\nToca para reverter o seu tratamento"</string>
     <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Non hai problemas coa cámara? Tocar para ignorar."</string>
     <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Ver e facer máis"</string>
-    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Arrastra outra aplicación para usar a pantalla dividida"</string>
+    <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Arrastra outra aplicación para usar a pantalla dividida"</string>
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Toca dúas veces fóra da aplicación para cambiala de posición"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"Entendido"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Despregar para obter máis información."</string>
@@ -97,14 +93,21 @@
     <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Cancelar"</string>
     <string name="letterbox_restart_restart" msgid="8529976234412442973">"Reiniciar"</string>
     <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Non mostrar outra vez"</string>
+    <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Toca dúas veces para\nmover esta aplicación"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"Maximizar"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimizar"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Pechar"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Atrás"</string>
     <string name="handle_text" msgid="1766582106752184456">"Controlador"</string>
+    <string name="app_icon_text" msgid="2823268023931811747">"Icona de aplicación"</string>
     <string name="fullscreen_text" msgid="1162316685217676079">"Pantalla completa"</string>
     <string name="desktop_text" msgid="1077633567027630454">"Modo de escritorio"</string>
     <string name="split_screen_text" msgid="1396336058129570886">"Pantalla dividida"</string>
     <string name="more_button_text" msgid="3655388105592893530">"Máis"</string>
     <string name="float_button_text" msgid="9221657008391364581">"Flotante"</string>
+    <string name="select_text" msgid="5139083974039906583">"Seleccionar"</string>
+    <string name="screenshot_text" msgid="1477704010087786671">"Captura de pantalla"</string>
+    <string name="close_text" msgid="4986518933445178928">"Pechar"</string>
+    <string name="collapse_menu_text" msgid="7515008122450342029">"Pechar o menú"</string>
+    <string name="expand_menu_text" msgid="3847736164494181168">"Abrir menú"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-gu/strings.xml b/libs/WindowManager/Shell/res/values-gu/strings.xml
index 24950f7..2a52199 100644
--- a/libs/WindowManager/Shell/res/values-gu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-gu/strings.xml
@@ -32,13 +32,13 @@
     <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"કદ બદલો"</string>
     <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"છુપાવો"</string>
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"બતાવો"</string>
-    <string name="dock_forced_resizable" msgid="1749750436092293116">"વિભાજિત-સ્ક્રીન સાથે ઍપ કદાચ કામ ન કરે."</string>
-    <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"ઍપ્લિકેશન સ્ક્રીન-વિભાજનનું સમર્થન કરતી નથી."</string>
+    <string name="dock_forced_resizable" msgid="7429086980048964687">"વિભાજિત સ્ક્રીન સાથે ઍપ કદાચ કામ ન કરે"</string>
+    <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"ઍપ વિભાજિત સ્ક્રીનને સપોર્ટ કરતી નથી"</string>
     <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"આ ઍપ માત્ર 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="703810061635792791">"સ્પ્લિટ-સ્ક્રીન વિભાજક"</string>
-    <string name="divider_title" msgid="5482989479865361192">"સ્ક્રીનને વિભાજિત કરતું વિભાજક"</string>
+    <string name="accessibility_divider" msgid="6407584574218956849">"સ્ક્રીનને વિભાજિત કરતું વિભાજક"</string>
+    <string name="divider_title" msgid="1963391955593749442">"સ્ક્રીનને વિભાજિત કરતું વિભાજક"</string>
     <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"ડાબી પૂર્ણ સ્ક્રીન"</string>
     <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"ડાબે 70%"</string>
     <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"ડાબે 50%"</string>
@@ -49,14 +49,10 @@
     <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"શીર્ષ 50%"</string>
     <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"શીર્ષ 30%"</string>
     <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"તળિયાની પૂર્ણ સ્ક્રીન"</string>
-    <!-- no translation found for accessibility_split_left (1713683765575562458) -->
-    <skip />
-    <!-- no translation found for accessibility_split_right (8441001008181296837) -->
-    <skip />
-    <!-- no translation found for accessibility_split_top (2789329702027147146) -->
-    <skip />
-    <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
-    <skip />
+    <string name="accessibility_split_left" msgid="1713683765575562458">"ડાબે વિભાજિત કરો"</string>
+    <string name="accessibility_split_right" msgid="8441001008181296837">"જમણે વિભાજિત કરો"</string>
+    <string name="accessibility_split_top" msgid="2789329702027147146">"ઉપર વિભાજિત કરો"</string>
+    <string name="accessibility_split_bottom" msgid="8694551025220868191">"નીચે વિભાજિત કરો"</string>
     <string name="one_handed_tutorial_title" msgid="4583241688067426350">"એક-હાથે વાપરો મોડનો ઉપયોગ કરી રહ્યાં છીએ"</string>
     <string name="one_handed_tutorial_description" msgid="3486582858591353067">"બહાર નીકળવા માટે, સ્ક્રીનની નીચેના ભાગથી ઉપરની તરફ સ્વાઇપ કરો અથવા ઍપના આઇકન પર ગમે ત્યાં ટૅપ કરો"</string>
     <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"એક-હાથે વાપરો મોડ શરૂ કરો"</string>
@@ -88,7 +84,7 @@
     <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"સુધારો નથી થયો?\nપહેલાંના પર પાછું ફેરવવા માટે ટૅપ કરો"</string>
     <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"કૅમેરામાં કોઈ સમસ્યા નથી? છોડી દેવા માટે ટૅપ કરો."</string>
     <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"જુઓ અને બીજું ઘણું કરો"</string>
-    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"સ્ક્રીન વિભાજન માટે કોઈ અન્ય ઍપમાં ખેંચો"</string>
+    <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"વિભાજિત સ્ક્રીન માટે કોઈ અન્ય ઍપમાં ખેંચો"</string>
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"કોઈ ઍપની જગ્યા બદલવા માટે, તેની બહાર બે વાર ટૅપ કરો"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"સમજાઈ ગયું"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"વધુ માહિતી માટે મોટું કરો."</string>
@@ -97,14 +93,21 @@
     <string name="letterbox_restart_cancel" msgid="1342209132692537805">"રદ કરો"</string>
     <string name="letterbox_restart_restart" msgid="8529976234412442973">"ફરી શરૂ કરો"</string>
     <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"ફરીથી બતાવશો નહીં"</string>
+    <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"આ ઍપને ખસેડવા માટે\nબે વાર ટૅપ કરો"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"મોટું કરો"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"નાનું કરો"</string>
     <string name="close_button_text" msgid="2913281996024033299">"બંધ કરો"</string>
     <string name="back_button_text" msgid="1469718707134137085">"પાછળ"</string>
     <string name="handle_text" msgid="1766582106752184456">"હૅન્ડલ"</string>
+    <string name="app_icon_text" msgid="2823268023931811747">"ઍપનું આઇકન"</string>
     <string name="fullscreen_text" msgid="1162316685217676079">"પૂર્ણસ્ક્રીન"</string>
     <string name="desktop_text" msgid="1077633567027630454">"ડેસ્કટૉપ મોડ"</string>
     <string name="split_screen_text" msgid="1396336058129570886">"સ્ક્રીનને વિભાજિત કરો"</string>
     <string name="more_button_text" msgid="3655388105592893530">"વધુ"</string>
     <string name="float_button_text" msgid="9221657008391364581">"ફ્લોટિંગ વિન્ડો"</string>
+    <string name="select_text" msgid="5139083974039906583">"પસંદ કરો"</string>
+    <string name="screenshot_text" msgid="1477704010087786671">"સ્ક્રીનશૉટ"</string>
+    <string name="close_text" msgid="4986518933445178928">"બંધ કરો"</string>
+    <string name="collapse_menu_text" msgid="7515008122450342029">"મેનૂ બંધ કરો"</string>
+    <string name="expand_menu_text" msgid="3847736164494181168">"મેનૂ ખોલો"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-hi/strings.xml b/libs/WindowManager/Shell/res/values-hi/strings.xml
index 9f36799..b0b0e9c 100644
--- a/libs/WindowManager/Shell/res/values-hi/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hi/strings.xml
@@ -32,13 +32,13 @@
     <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"आकार बदलें"</string>
     <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"छिपाएं"</string>
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"दिखाएं"</string>
-    <string name="dock_forced_resizable" msgid="1749750436092293116">"ऐप्लिकेशन शायद स्प्लिट स्क्रीन मोड में काम न करे."</string>
-    <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"ऐप विभाजित स्‍क्रीन का समर्थन नहीं करता है."</string>
+    <string name="dock_forced_resizable" msgid="7429086980048964687">"मुमकिन है कि ऐप्लिकेशन, स्प्लिट स्क्रीन मोड में काम न करे"</string>
+    <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"यह ऐप्लिकेशन, स्प्लिट स्क्रीन मोड पर काम नहीं करता"</string>
     <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"इस ऐप्लिकेशन को सिर्फ़ एक विंडो में खोला जा सकता है."</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="703810061635792791">"विभाजित स्क्रीन विभाजक"</string>
-    <string name="divider_title" msgid="5482989479865361192">"स्प्लिट स्क्रीन डिवाइडर"</string>
+    <string name="accessibility_divider" msgid="6407584574218956849">"स्प्लिट स्क्रीन डिवाइडर मोड"</string>
+    <string name="divider_title" msgid="1963391955593749442">"स्प्लिट स्क्रीन डिवाइडर मोड"</string>
     <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"बाईं स्क्रीन को फ़ुल स्क्रीन बनाएं"</string>
     <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"बाईं स्क्रीन को 70% बनाएं"</string>
     <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"बाईं स्क्रीन को 50% बनाएं"</string>
@@ -49,14 +49,10 @@
     <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"ऊपर की स्क्रीन को 50% बनाएं"</string>
     <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"ऊपर की स्क्रीन को 30% बनाएं"</string>
     <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"नीचे की स्क्रीन को फ़ुल स्क्रीन बनाएं"</string>
-    <!-- no translation found for accessibility_split_left (1713683765575562458) -->
-    <skip />
-    <!-- no translation found for accessibility_split_right (8441001008181296837) -->
-    <skip />
-    <!-- no translation found for accessibility_split_top (2789329702027147146) -->
-    <skip />
-    <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
-    <skip />
+    <string name="accessibility_split_left" msgid="1713683765575562458">"स्क्रीन को बाएं हिस्से में स्प्लिट करें"</string>
+    <string name="accessibility_split_right" msgid="8441001008181296837">"स्क्रीन को दाएं हिस्से में स्प्लिट करें"</string>
+    <string name="accessibility_split_top" msgid="2789329702027147146">"स्क्रीन को ऊपर के हिस्से में स्प्लिट करें"</string>
+    <string name="accessibility_split_bottom" msgid="8694551025220868191">"स्क्रीन को सबसे नीचे वाले हिस्से में स्प्लिट करें"</string>
     <string name="one_handed_tutorial_title" msgid="4583241688067426350">"वन-हैंडेड मोड का इस्तेमाल करना"</string>
     <string name="one_handed_tutorial_description" msgid="3486582858591353067">"इस मोड से बाहर निकलने के लिए, स्क्रीन के सबसे निचले हिस्से से ऊपर की ओर स्वाइप करें या ऐप्लिकेशन के बाहर कहीं भी टैप करें"</string>
     <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"वन-हैंडेड मोड चालू करें"</string>
@@ -88,23 +84,30 @@
     <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"क्या समस्या ठीक नहीं हुई?\nपहले जैसा करने के लिए टैप करें"</string>
     <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"क्या कैमरे से जुड़ी कोई समस्या नहीं है? खारिज करने के लिए टैप करें."</string>
     <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"पूरी जानकारी लेकर, बेहतर तरीके से काम करें"</string>
-    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"स्प्लिट स्क्रीन के लिए, दूसरे ऐप्लिकेशन को खींचें और छोड़ें"</string>
+    <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"स्प्लिट स्क्रीन का इस्तेमाल करने के लिए, किसी अन्य ऐप्लिकेशन को खींचें और छोड़ें"</string>
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"किसी ऐप्लिकेशन की जगह बदलने के लिए, उसके बाहर दो बार टैप करें"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"ठीक है"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"ज़्यादा जानकारी के लिए बड़ा करें."</string>
     <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"बेहतर व्यू पाने के लिए ऐप्लिकेशन को रीस्टार्ट करना है?"</string>
-    <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"स्क्रीन पर ऐप्लिकेशन का बेहतर व्यू पाने के लिए उसे रीस्टार्ट करें. हालांकि, आपने जो बदलाव सेव नहीं किए हैं या अब तक जो काम किए हैं उनका डेटा, ऐप्लिकेशन रीस्टार्ट करने पर मिट सकता है"</string>
+    <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"स्क्रीन पर ऐप्लिकेशन का बेहतर व्यू पाने के लिए उसे रीस्टार्ट करें. हालांकि, इससे अब तक किया गया काम और सेव न किए गए बदलाव मिट सकते हैं"</string>
     <string name="letterbox_restart_cancel" msgid="1342209132692537805">"रद्द करें"</string>
     <string name="letterbox_restart_restart" msgid="8529976234412442973">"रीस्टार्ट करें"</string>
     <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"फिर से न दिखाएं"</string>
+    <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"ऐप्लिकेशन की जगह बदलने के लिए\nदो बार टैप करें"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"बड़ा करें"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"विंडो छोटी करें"</string>
     <string name="close_button_text" msgid="2913281996024033299">"बंद करें"</string>
     <string name="back_button_text" msgid="1469718707134137085">"वापस जाएं"</string>
     <string name="handle_text" msgid="1766582106752184456">"हैंडल"</string>
+    <string name="app_icon_text" msgid="2823268023931811747">"ऐप्लिकेशन आइकॉन"</string>
     <string name="fullscreen_text" msgid="1162316685217676079">"फ़ुलस्क्रीन"</string>
     <string name="desktop_text" msgid="1077633567027630454">"डेस्कटॉप मोड"</string>
     <string name="split_screen_text" msgid="1396336058129570886">"स्प्लिट स्क्रीन मोड"</string>
     <string name="more_button_text" msgid="3655388105592893530">"ज़्यादा देखें"</string>
     <string name="float_button_text" msgid="9221657008391364581">"फ़्लोट"</string>
+    <string name="select_text" msgid="5139083974039906583">"चुनें"</string>
+    <string name="screenshot_text" msgid="1477704010087786671">"स्क्रीनशॉट"</string>
+    <string name="close_text" msgid="4986518933445178928">"बंद करें"</string>
+    <string name="collapse_menu_text" msgid="7515008122450342029">"मेन्यू बंद करें"</string>
+    <string name="expand_menu_text" msgid="3847736164494181168">"मेन्यू खोलें"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-hi/strings_tv.xml b/libs/WindowManager/Shell/res/values-hi/strings_tv.xml
index 595435b..e2f272c 100644
--- a/libs/WindowManager/Shell/res/values-hi/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-hi/strings_tv.xml
@@ -18,7 +18,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="notification_channel_tv_pip" msgid="2576686079160402435">"पिक्चर में पिक्चर"</string>
-    <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(कोई शीर्षक कार्यक्रम नहीं)"</string>
+    <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(कोई टाइटल कार्यक्रम नहीं)"</string>
     <string name="pip_close" msgid="2955969519031223530">"बंद करें"</string>
     <string name="pip_fullscreen" msgid="7278047353591302554">"फ़ुल स्‍क्रीन"</string>
     <string name="pip_move" msgid="158770205886688553">"ले जाएं"</string>
diff --git a/libs/WindowManager/Shell/res/values-hr/strings.xml b/libs/WindowManager/Shell/res/values-hr/strings.xml
index 9459e4c..08721f0 100644
--- a/libs/WindowManager/Shell/res/values-hr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hr/strings.xml
@@ -32,13 +32,13 @@
     <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Promjena veličine"</string>
     <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Sakrijte"</string>
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Poništite sakrivanje"</string>
-    <string name="dock_forced_resizable" msgid="1749750436092293116">"Aplikacija možda neće funkcionirati s podijeljenim zaslonom."</string>
-    <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Aplikacija ne podržava podijeljeni zaslon."</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>
     <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"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="703810061635792791">"Razdjelnik podijeljenog zaslona"</string>
-    <string name="divider_title" msgid="5482989479865361192">"Razdjelnik podijeljenog zaslona"</string>
+    <string name="accessibility_divider" msgid="6407584574218956849">"Razdjelnik podijeljenog zaslona"</string>
+    <string name="divider_title" msgid="1963391955593749442">"Razdjelnik podijeljenog zaslona"</string>
     <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Lijevi zaslon u cijeli zaslon"</string>
     <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Lijevi zaslon na 70%"</string>
     <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Lijevi zaslon na 50%"</string>
@@ -49,14 +49,10 @@
     <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Gornji zaslon na 50%"</string>
     <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Gornji zaslon na 30%"</string>
     <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Donji zaslon u cijeli zaslon"</string>
-    <!-- no translation found for accessibility_split_left (1713683765575562458) -->
-    <skip />
-    <!-- no translation found for accessibility_split_right (8441001008181296837) -->
-    <skip />
-    <!-- no translation found for accessibility_split_top (2789329702027147146) -->
-    <skip />
-    <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
-    <skip />
+    <string name="accessibility_split_left" msgid="1713683765575562458">"Podijeli lijevo"</string>
+    <string name="accessibility_split_right" msgid="8441001008181296837">"Podijeli desno"</string>
+    <string name="accessibility_split_top" msgid="2789329702027147146">"Podijeli gore"</string>
+    <string name="accessibility_split_bottom" msgid="8694551025220868191">"Podijeli dolje"</string>
     <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Korištenje načina rada jednom rukom"</string>
     <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Za izlaz prijeđite prstom od dna zaslona prema gore ili dodirnite bio gdje iznad aplikacije"</string>
     <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Pokretanje načina rada jednom rukom"</string>
@@ -83,12 +79,12 @@
     <string name="notification_bubble_title" msgid="6082910224488253378">"Oblačić"</string>
     <string name="manage_bubbles_text" msgid="7730624269650594419">"Upravljanje"</string>
     <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Oblačić odbačen."</string>
-    <string name="restart_button_description" msgid="6712141648865547958">"Dodirnite da biste ponovo pokrenuli tu aplikaciju kako biste bolje vidjeli."</string>
+    <string name="restart_button_description" msgid="6712141648865547958">"Dodirnite za ponovno pokretanje te aplikacije i bolji prikaz."</string>
     <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Problemi s fotoaparatom?\nDodirnite za popravak"</string>
     <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Problem nije riješen?\nDodirnite za vraćanje"</string>
     <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Nemate problema s fotoaparatom? Dodirnite za odbacivanje."</string>
     <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Gledajte i učinite više"</string>
-    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Povucite drugu aplikaciju unutra da biste podijelili zaslon"</string>
+    <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Povucite drugu aplikaciju unutra da biste podijelili zaslon"</string>
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Dvaput dodirnite izvan aplikacije da biste je premjestili"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"Shvaćam"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Proširite da biste saznali više."</string>
@@ -97,14 +93,21 @@
     <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Odustani"</string>
     <string name="letterbox_restart_restart" msgid="8529976234412442973">"Pokreni ponovno"</string>
     <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Ne prikazuj ponovno"</string>
+    <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Dvaput dodirnite da biste\npremjestili ovu aplikaciju"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"Maksimiziraj"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimiziraj"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Zatvori"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Natrag"</string>
     <string name="handle_text" msgid="1766582106752184456">"Pokazivač"</string>
+    <string name="app_icon_text" msgid="2823268023931811747">"Ikona aplikacije"</string>
     <string name="fullscreen_text" msgid="1162316685217676079">"Puni zaslon"</string>
     <string name="desktop_text" msgid="1077633567027630454">"Stolni način rada"</string>
     <string name="split_screen_text" msgid="1396336058129570886">"Razdvojeni zaslon"</string>
     <string name="more_button_text" msgid="3655388105592893530">"Više"</string>
     <string name="float_button_text" msgid="9221657008391364581">"Plutajući"</string>
+    <string name="select_text" msgid="5139083974039906583">"Odaberite"</string>
+    <string name="screenshot_text" msgid="1477704010087786671">"Snimka zaslona"</string>
+    <string name="close_text" msgid="4986518933445178928">"Zatvorite"</string>
+    <string name="collapse_menu_text" msgid="7515008122450342029">"Zatvorite izbornik"</string>
+    <string name="expand_menu_text" msgid="3847736164494181168">"Otvaranje izbornika"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-hu/strings.xml b/libs/WindowManager/Shell/res/values-hu/strings.xml
index fc9341b..7566439 100644
--- a/libs/WindowManager/Shell/res/values-hu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hu/strings.xml
@@ -32,13 +32,13 @@
     <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Átméretezés"</string>
     <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Félretevés"</string>
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Félretevés megszüntetése"</string>
-    <string name="dock_forced_resizable" msgid="1749750436092293116">"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="7408396418008948957">"Az alkalmazás nem támogatja az osztott képernyős nézetet."</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>
     <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"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="703810061635792791">"Elválasztó az osztott nézetben"</string>
-    <string name="divider_title" msgid="5482989479865361192">"Elválasztó az osztott nézetben"</string>
+    <string name="accessibility_divider" msgid="6407584574218956849">"Elválasztó az osztott képernyős nézetben"</string>
+    <string name="divider_title" msgid="1963391955593749442">"Elválasztó az osztott képernyős nézetben"</string>
     <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Bal oldali teljes képernyőre"</string>
     <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Bal oldali 70%-ra"</string>
     <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Bal oldali 50%-ra"</string>
@@ -49,14 +49,10 @@
     <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Felső 50%-ra"</string>
     <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Felső 30%-ra"</string>
     <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Alsó teljes képernyőre"</string>
-    <!-- no translation found for accessibility_split_left (1713683765575562458) -->
-    <skip />
-    <!-- no translation found for accessibility_split_right (8441001008181296837) -->
-    <skip />
-    <!-- no translation found for accessibility_split_top (2789329702027147146) -->
-    <skip />
-    <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
-    <skip />
+    <string name="accessibility_split_left" msgid="1713683765575562458">"Osztás a képernyő bal oldalán"</string>
+    <string name="accessibility_split_right" msgid="8441001008181296837">"Osztás a képernyő jobb oldalán"</string>
+    <string name="accessibility_split_top" msgid="2789329702027147146">"Osztás a képernyő tetején"</string>
+    <string name="accessibility_split_bottom" msgid="8694551025220868191">"Osztás alul"</string>
     <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Egykezes mód használata"</string>
     <string name="one_handed_tutorial_description" msgid="3486582858591353067">"A kilépéshez csúsztasson felfelé a képernyő aljáról, vagy koppintson az alkalmazás felett a képernyő bármelyik részére"</string>
     <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Egykezes mód indítása"</string>
@@ -88,7 +84,7 @@
     <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Nem sikerült a hiba kijavítása?\nKoppintson a visszaállításhoz."</string>
     <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Nincsenek problémái kamerával? Koppintson az elvetéshez."</string>
     <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Több mindent láthat és tehet"</string>
-    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Húzzon ide egy másik alkalmazást az osztott képernyő használatához"</string>
+    <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Húzzon ide egy másik alkalmazást az osztott képernyő használatához"</string>
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Koppintson duplán az alkalmazáson kívül az áthelyezéséhez"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"Értem"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Kibontással további információkhoz juthat."</string>
@@ -97,14 +93,21 @@
     <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Mégse"</string>
     <string name="letterbox_restart_restart" msgid="8529976234412442973">"Újraindítás"</string>
     <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Ne jelenjen meg többé"</string>
+    <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Koppintson duplán\naz alkalmazás áthelyezéséhez"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"Teljes méret"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Kis méret"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Bezárás"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Vissza"</string>
     <string name="handle_text" msgid="1766582106752184456">"Fogópont"</string>
+    <string name="app_icon_text" msgid="2823268023931811747">"Alkalmazásikon"</string>
     <string name="fullscreen_text" msgid="1162316685217676079">"Teljes képernyő"</string>
     <string name="desktop_text" msgid="1077633567027630454">"Asztali üzemmód"</string>
     <string name="split_screen_text" msgid="1396336058129570886">"Osztott képernyő"</string>
     <string name="more_button_text" msgid="3655388105592893530">"Továbbiak"</string>
     <string name="float_button_text" msgid="9221657008391364581">"Lebegő"</string>
+    <string name="select_text" msgid="5139083974039906583">"Kiválasztás"</string>
+    <string name="screenshot_text" msgid="1477704010087786671">"Képernyőkép"</string>
+    <string name="close_text" msgid="4986518933445178928">"Bezárás"</string>
+    <string name="collapse_menu_text" msgid="7515008122450342029">"Menü bezárása"</string>
+    <string name="expand_menu_text" msgid="3847736164494181168">"Menü megnyitása"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-hy/strings.xml b/libs/WindowManager/Shell/res/values-hy/strings.xml
index 6943532..2b20870 100644
--- a/libs/WindowManager/Shell/res/values-hy/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hy/strings.xml
@@ -32,13 +32,13 @@
     <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Փոխել չափը"</string>
     <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Թաքցնել"</string>
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Ցուցադրել"</string>
-    <string name="dock_forced_resizable" msgid="1749750436092293116">"Հավելվածը չի կարող աշխատել տրոհված էկրանի ռեժիմում։"</string>
-    <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Հավելվածը չի աջակցում էկրանի տրոհումը:"</string>
+    <string name="dock_forced_resizable" msgid="7429086980048964687">"Հավելվածը չի կարող աշխատել տրոհված էկրանի ռեժիմում"</string>
+    <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Հավելվածը չի աջակցում էկրանի տրոհումը"</string>
     <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Այս հավելվածը հնարավոր է բացել միայն մեկ պատուհանում։"</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="703810061635792791">"Տրոհված էկրանի բաժանիչ"</string>
-    <string name="divider_title" msgid="5482989479865361192">"Տրոհված էկրանի բաժանիչ"</string>
+    <string name="accessibility_divider" msgid="6407584574218956849">"Տրոհված էկրանի բաժանիչ"</string>
+    <string name="divider_title" msgid="1963391955593749442">"Տրոհված էկրանի բաժանիչ"</string>
     <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Ձախ էկրանը՝ լիաէկրան"</string>
     <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Ձախ էկրանը՝ 70%"</string>
     <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Ձախ էկրանը՝ 50%"</string>
@@ -49,14 +49,10 @@
     <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Վերևի էկրանը՝ 50%"</string>
     <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Վերևի էկրանը՝ 30%"</string>
     <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Ներքևի էկրանը՝ լիաէկրան"</string>
-    <!-- no translation found for accessibility_split_left (1713683765575562458) -->
-    <skip />
-    <!-- no translation found for accessibility_split_right (8441001008181296837) -->
-    <skip />
-    <!-- no translation found for accessibility_split_top (2789329702027147146) -->
-    <skip />
-    <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
-    <skip />
+    <string name="accessibility_split_left" msgid="1713683765575562458">"Հավելվածը ձախ կողմում"</string>
+    <string name="accessibility_split_right" msgid="8441001008181296837">"Հավելվածը աջ կողմում"</string>
+    <string name="accessibility_split_top" msgid="2789329702027147146">"Հավելվածը վերևում"</string>
+    <string name="accessibility_split_bottom" msgid="8694551025220868191">"Հավելվածը ներքևում"</string>
     <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Ինչպես օգտվել մեկ ձեռքի ռեժիմից"</string>
     <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Դուրս գալու համար մատը սահեցրեք էկրանի ներքևից վերև կամ հպեք հավելվածի վերևում որևէ տեղ։"</string>
     <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Գործարկել մեկ ձեռքի ռեժիմը"</string>
@@ -88,7 +84,7 @@
     <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Չհաջողվե՞ց շտկել։\nՀպեք՝ փոփոխությունները չեղարկելու համար։"</string>
     <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Տեսախցիկի հետ կապված խնդիրներ չկա՞ն։ Փակելու համար հպեք։"</string>
     <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Միաժամանակ կատարեք մի քանի առաջադրանք"</string>
-    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Քաշեք մյուս հավելվածի մեջ՝ էկրանի տրոհումն օգտագործելու համար"</string>
+    <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Քաշեք մյուս հավելվածի մեջ՝ էկրանի տրոհումն օգտագործելու համար"</string>
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Կրկնակի հպեք հավելվածի կողքին՝ այն տեղափոխելու համար"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"Եղավ"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Ծավալեք՝ ավելին իմանալու համար։"</string>
@@ -97,14 +93,21 @@
     <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Չեղարկել"</string>
     <string name="letterbox_restart_restart" msgid="8529976234412442973">"Վերագործարկել"</string>
     <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Այլևս ցույց չտալ"</string>
+    <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Կրկնակի հպեք՝\nհավելվածը տեղափոխելու համար"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"Ծավալել"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Ծալել"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Փակել"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Հետ"</string>
     <string name="handle_text" msgid="1766582106752184456">"Նշիչ"</string>
+    <string name="app_icon_text" msgid="2823268023931811747">"Հավելվածի պատկերակ"</string>
     <string name="fullscreen_text" msgid="1162316685217676079">"Լիաէկրան"</string>
     <string name="desktop_text" msgid="1077633567027630454">"Համակարգչի ռեժիմ"</string>
     <string name="split_screen_text" msgid="1396336058129570886">"Տրոհված էկրան"</string>
     <string name="more_button_text" msgid="3655388105592893530">"Ավելին"</string>
     <string name="float_button_text" msgid="9221657008391364581">"Լողացող պատուհան"</string>
+    <string name="select_text" msgid="5139083974039906583">"Ընտրել"</string>
+    <string name="screenshot_text" msgid="1477704010087786671">"Սքրինշոթ"</string>
+    <string name="close_text" msgid="4986518933445178928">"Փակել"</string>
+    <string name="collapse_menu_text" msgid="7515008122450342029">"Փակել ընտրացանկը"</string>
+    <string name="expand_menu_text" msgid="3847736164494181168">"Բացել ընտրացանկը"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-in/strings.xml b/libs/WindowManager/Shell/res/values-in/strings.xml
index 4fca32d..3f6d9c5 100644
--- a/libs/WindowManager/Shell/res/values-in/strings.xml
+++ b/libs/WindowManager/Shell/res/values-in/strings.xml
@@ -32,13 +32,13 @@
     <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Ubah ukuran"</string>
     <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Stash"</string>
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Batalkan stash"</string>
-    <string name="dock_forced_resizable" msgid="1749750436092293116">"Aplikasi mungkin tidak berfungsi dengan layar terpisah."</string>
-    <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"App tidak mendukung layar terpisah."</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>
     <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"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="703810061635792791">"Pembagi layar terpisah"</string>
-    <string name="divider_title" msgid="5482989479865361192">"Pembagi layar terpisah"</string>
+    <string name="accessibility_divider" msgid="6407584574218956849">"Pembagi layar terpisah"</string>
+    <string name="divider_title" msgid="1963391955593749442">"Pembagi layar terpisah"</string>
     <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Layar penuh di kiri"</string>
     <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Kiri 70%"</string>
     <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Kiri 50%"</string>
@@ -49,14 +49,10 @@
     <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Atas 50%"</string>
     <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Atas 30%"</string>
     <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Layar penuh di bawah"</string>
-    <!-- no translation found for accessibility_split_left (1713683765575562458) -->
-    <skip />
-    <!-- no translation found for accessibility_split_right (8441001008181296837) -->
-    <skip />
-    <!-- no translation found for accessibility_split_top (2789329702027147146) -->
-    <skip />
-    <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
-    <skip />
+    <string name="accessibility_split_left" msgid="1713683765575562458">"Pisahkan ke kiri"</string>
+    <string name="accessibility_split_right" msgid="8441001008181296837">"Pisahkan ke kanan"</string>
+    <string name="accessibility_split_top" msgid="2789329702027147146">"Pisahkan ke atas"</string>
+    <string name="accessibility_split_bottom" msgid="8694551025220868191">"Pisahkan ke bawah"</string>
     <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Menggunakan mode satu tangan"</string>
     <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Untuk keluar, geser layar dari bawah ke atas atau ketuk di mana saja di atas aplikasi"</string>
     <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Mulai mode satu tangan"</string>
@@ -88,23 +84,30 @@
     <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Tidak dapat diperbaiki?\nKetuk untuk mengembalikan"</string>
     <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Tidak ada masalah kamera? Ketuk untuk menutup."</string>
     <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Lihat dan lakukan lebih banyak hal"</string>
-    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Tarik aplikasi lain untuk menggunakan layar terpisah"</string>
+    <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Tarik aplikasi lain untuk menggunakan layar terpisah"</string>
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Ketuk dua kali di luar aplikasi untuk mengubah posisinya"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"Oke"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Luaskan untuk melihat informasi selengkapnya."</string>
-    <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Mulai ulang untuk tampilan yang lebih baik?"</string>
+    <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Mulai ulang untuk melihat tampilan yang lebih baik?"</string>
     <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Anda dapat memulai ulang aplikasi agar terlihat lebih baik di layar, tetapi Anda mungkin kehilangan progres atau perubahan yang belum disimpan"</string>
     <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Batal"</string>
     <string name="letterbox_restart_restart" msgid="8529976234412442973">"Mulai ulang"</string>
     <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Jangan tampilkan lagi"</string>
+    <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Ketuk dua kali untuk\nmemindahkan aplikasi ini"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"Maksimalkan"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimalkan"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Tutup"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Kembali"</string>
     <string name="handle_text" msgid="1766582106752184456">"Tuas"</string>
+    <string name="app_icon_text" msgid="2823268023931811747">"Ikon Aplikasi"</string>
     <string name="fullscreen_text" msgid="1162316685217676079">"Layar Penuh"</string>
     <string name="desktop_text" msgid="1077633567027630454">"Mode Desktop"</string>
     <string name="split_screen_text" msgid="1396336058129570886">"Layar Terpisah"</string>
     <string name="more_button_text" msgid="3655388105592893530">"Lainnya"</string>
     <string name="float_button_text" msgid="9221657008391364581">"Mengambang"</string>
+    <string name="select_text" msgid="5139083974039906583">"Pilih"</string>
+    <string name="screenshot_text" msgid="1477704010087786671">"Screenshot"</string>
+    <string name="close_text" msgid="4986518933445178928">"Tutup"</string>
+    <string name="collapse_menu_text" msgid="7515008122450342029">"Tutup Menu"</string>
+    <string name="expand_menu_text" msgid="3847736164494181168">"Buka Menu"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-is/strings.xml b/libs/WindowManager/Shell/res/values-is/strings.xml
index 1bc019e..20c16be 100644
--- a/libs/WindowManager/Shell/res/values-is/strings.xml
+++ b/libs/WindowManager/Shell/res/values-is/strings.xml
@@ -32,13 +32,13 @@
     <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Breyta stærð"</string>
     <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Geymsla"</string>
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Taka úr geymslu"</string>
-    <string name="dock_forced_resizable" msgid="1749750436092293116">"Hugsanlega virkar forritið ekki með skjáskiptingu."</string>
-    <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Forritið styður ekki að skjánum sé skipt."</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>
     <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"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="703810061635792791">"Skjáskipting"</string>
-    <string name="divider_title" msgid="5482989479865361192">"Skjáskipting"</string>
+    <string name="accessibility_divider" msgid="6407584574218956849">"Skilrúm skjáskiptingar"</string>
+    <string name="divider_title" msgid="1963391955593749442">"Skilrúm skjáskiptingar"</string>
     <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Vinstri á öllum skjánum"</string>
     <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Vinstri 70%"</string>
     <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Vinstri 50%"</string>
@@ -49,14 +49,10 @@
     <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Efri 50%"</string>
     <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Efri 30%"</string>
     <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Neðri á öllum skjánum"</string>
-    <!-- no translation found for accessibility_split_left (1713683765575562458) -->
-    <skip />
-    <!-- no translation found for accessibility_split_right (8441001008181296837) -->
-    <skip />
-    <!-- no translation found for accessibility_split_top (2789329702027147146) -->
-    <skip />
-    <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
-    <skip />
+    <string name="accessibility_split_left" msgid="1713683765575562458">"Skipta vinstra megin"</string>
+    <string name="accessibility_split_right" msgid="8441001008181296837">"Skipta hægra megin"</string>
+    <string name="accessibility_split_top" msgid="2789329702027147146">"Skipta efst"</string>
+    <string name="accessibility_split_bottom" msgid="8694551025220868191">"Skipta neðst"</string>
     <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Notkun einhentrar stillingar"</string>
     <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Til að loka skaltu strjúka upp frá neðri hluta skjásins eða ýta hvar sem er fyrir ofan forritið"</string>
     <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Ræsa einhenta stillingu"</string>
@@ -83,12 +79,12 @@
     <string name="notification_bubble_title" msgid="6082910224488253378">"Blaðra"</string>
     <string name="manage_bubbles_text" msgid="7730624269650594419">"Stjórna"</string>
     <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Blöðru lokað."</string>
-    <string name="restart_button_description" msgid="6712141648865547958">"Ýta til að endurræsa forritið og fá betri sýn."</string>
+    <string name="restart_button_description" msgid="6712141648865547958">"Ýttu til að endurræsa forritið og fá betri sýn."</string>
     <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Myndavélavesen?\nÝttu til að breyta stærð"</string>
     <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Ennþá vesen?\nÝttu til að afturkalla"</string>
     <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Ekkert myndavélavesen? Ýttu til að hunsa."</string>
     <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Sjáðu og gerðu meira"</string>
-    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Dragðu annað forrit inn til að nota skjáskiptingu"</string>
+    <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Dragðu annað forrit inn til að nota skjáskiptingu"</string>
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Ýttu tvisvar utan við forrit til að færa það"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"Ég skil"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Stækka til að sjá frekari upplýsingar."</string>
@@ -97,14 +93,21 @@
     <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Hætta við"</string>
     <string name="letterbox_restart_restart" msgid="8529976234412442973">"Endurræsa"</string>
     <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Ekki sýna þetta aftur"</string>
+    <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Ýttu tvisvar til\nað færa þetta forrit"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"Stækka"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minnka"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Loka"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Til baka"</string>
     <string name="handle_text" msgid="1766582106752184456">"Handfang"</string>
+    <string name="app_icon_text" msgid="2823268023931811747">"Tákn forrits"</string>
     <string name="fullscreen_text" msgid="1162316685217676079">"Allur skjárinn"</string>
     <string name="desktop_text" msgid="1077633567027630454">"Skjáborðsstilling"</string>
     <string name="split_screen_text" msgid="1396336058129570886">"Skjáskipting"</string>
     <string name="more_button_text" msgid="3655388105592893530">"Meira"</string>
     <string name="float_button_text" msgid="9221657008391364581">"Reikult"</string>
+    <string name="select_text" msgid="5139083974039906583">"Velja"</string>
+    <string name="screenshot_text" msgid="1477704010087786671">"Skjámynd"</string>
+    <string name="close_text" msgid="4986518933445178928">"Loka"</string>
+    <string name="collapse_menu_text" msgid="7515008122450342029">"Loka valmynd"</string>
+    <string name="expand_menu_text" msgid="3847736164494181168">"Opna valmynd"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-it/strings.xml b/libs/WindowManager/Shell/res/values-it/strings.xml
index 8d2715a..025646c 100644
--- a/libs/WindowManager/Shell/res/values-it/strings.xml
+++ b/libs/WindowManager/Shell/res/values-it/strings.xml
@@ -32,13 +32,13 @@
     <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Ridimensiona"</string>
     <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Accantona"</string>
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Annulla accantonamento"</string>
-    <string name="dock_forced_resizable" msgid="1749750436092293116">"L\'app potrebbe non funzionare con lo schermo diviso."</string>
-    <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"L\'app non supporta la modalità Schermo diviso."</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>
     <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"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="703810061635792791">"Strumento per schermo diviso"</string>
-    <string name="divider_title" msgid="5482989479865361192">"Strumento per schermo diviso"</string>
+    <string name="accessibility_divider" msgid="6407584574218956849">"Strumento per schermo diviso"</string>
+    <string name="divider_title" msgid="1963391955593749442">"Strumento per schermo diviso"</string>
     <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Schermata sinistra a schermo intero"</string>
     <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Schermata sinistra al 70%"</string>
     <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Schermata sinistra al 50%"</string>
@@ -49,14 +49,10 @@
     <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Schermata superiore al 50%"</string>
     <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Schermata superiore al 30%"</string>
     <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Schermata inferiore a schermo intero"</string>
-    <!-- no translation found for accessibility_split_left (1713683765575562458) -->
-    <skip />
-    <!-- no translation found for accessibility_split_right (8441001008181296837) -->
-    <skip />
-    <!-- no translation found for accessibility_split_top (2789329702027147146) -->
-    <skip />
-    <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
-    <skip />
+    <string name="accessibility_split_left" msgid="1713683765575562458">"Dividi a sinistra"</string>
+    <string name="accessibility_split_right" msgid="8441001008181296837">"Dividi a destra"</string>
+    <string name="accessibility_split_top" msgid="2789329702027147146">"Dividi in alto"</string>
+    <string name="accessibility_split_bottom" msgid="8694551025220868191">"Dividi in basso"</string>
     <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Usare la modalità a una mano"</string>
     <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Per uscire, scorri verso l\'alto dalla parte inferiore dello schermo oppure tocca un punto qualsiasi sopra l\'app"</string>
     <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Avvia la modalità a una mano"</string>
@@ -88,7 +84,7 @@
     <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Il problema non si è risolto?\nTocca per ripristinare"</string>
     <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Nessun problema con la fotocamera? Tocca per ignorare."</string>
     <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Visualizza più contenuti e fai di più"</string>
-    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Trascina in un\'altra app per usare lo schermo diviso"</string>
+    <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Trascina in un\'altra app per usare lo schermo diviso"</string>
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Tocca due volte fuori da un\'app per riposizionarla"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Espandi per avere ulteriori informazioni."</string>
@@ -97,14 +93,21 @@
     <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Annulla"</string>
     <string name="letterbox_restart_restart" msgid="8529976234412442973">"Riavvia"</string>
     <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Non mostrare più"</string>
+    <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Tocca due volte per\nspostare questa app"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"Ingrandisci"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Riduci a icona"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Chiudi"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Indietro"</string>
     <string name="handle_text" msgid="1766582106752184456">"Handle"</string>
+    <string name="app_icon_text" msgid="2823268023931811747">"Icona dell\'app"</string>
     <string name="fullscreen_text" msgid="1162316685217676079">"Schermo intero"</string>
     <string name="desktop_text" msgid="1077633567027630454">"Modalità desktop"</string>
     <string name="split_screen_text" msgid="1396336058129570886">"Schermo diviso"</string>
     <string name="more_button_text" msgid="3655388105592893530">"Altro"</string>
     <string name="float_button_text" msgid="9221657008391364581">"Mobile"</string>
+    <string name="select_text" msgid="5139083974039906583">"Seleziona"</string>
+    <string name="screenshot_text" msgid="1477704010087786671">"Screenshot"</string>
+    <string name="close_text" msgid="4986518933445178928">"Chiudi"</string>
+    <string name="collapse_menu_text" msgid="7515008122450342029">"Chiudi il menu"</string>
+    <string name="expand_menu_text" msgid="3847736164494181168">"Apri menu"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-iw/strings.xml b/libs/WindowManager/Shell/res/values-iw/strings.xml
index 26f3236..bb3845b 100644
--- a/libs/WindowManager/Shell/res/values-iw/strings.xml
+++ b/libs/WindowManager/Shell/res/values-iw/strings.xml
@@ -32,13 +32,13 @@
     <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"שינוי גודל"</string>
     <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"הסתרה זמנית"</string>
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"ביטול ההסתרה הזמנית"</string>
-    <string name="dock_forced_resizable" msgid="1749750436092293116">"ייתכן שהאפליקציה לא תפעל במסך מפוצל."</string>
-    <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"האפליקציה אינה תומכת במסך מפוצל."</string>
+    <string name="dock_forced_resizable" msgid="7429086980048964687">"יכול להיות שהאפליקציה לא תפעל עם מסך מפוצל"</string>
+    <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"האפליקציה לא תומכת במסך מפוצל"</string>
     <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"ניתן לפתוח את האפליקציה הזו רק בחלון אחד."</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="703810061635792791">"מחלק מסך מפוצל"</string>
-    <string name="divider_title" msgid="5482989479865361192">"מחלק מסך מפוצל"</string>
+    <string name="accessibility_divider" msgid="6407584574218956849">"מחלק מסך מפוצל"</string>
+    <string name="divider_title" msgid="1963391955593749442">"מחלק מסך מפוצל"</string>
     <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"מסך שמאלי מלא"</string>
     <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"שמאלה 70%"</string>
     <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"שמאלה 50%"</string>
@@ -49,14 +49,10 @@
     <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"עליון 50%"</string>
     <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"למעלה 30%"</string>
     <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"מסך תחתון מלא"</string>
-    <!-- no translation found for accessibility_split_left (1713683765575562458) -->
-    <skip />
-    <!-- no translation found for accessibility_split_right (8441001008181296837) -->
-    <skip />
-    <!-- no translation found for accessibility_split_top (2789329702027147146) -->
-    <skip />
-    <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
-    <skip />
+    <string name="accessibility_split_left" msgid="1713683765575562458">"פיצול שמאלה"</string>
+    <string name="accessibility_split_right" msgid="8441001008181296837">"פיצול ימינה"</string>
+    <string name="accessibility_split_top" msgid="2789329702027147146">"פיצול למעלה"</string>
+    <string name="accessibility_split_bottom" msgid="8694551025220868191">"פיצול למטה"</string>
     <string name="one_handed_tutorial_title" msgid="4583241688067426350">"איך להשתמש בתכונה \'מצב שימוש ביד אחת\'"</string>
     <string name="one_handed_tutorial_description" msgid="3486582858591353067">"כדי לצאת, יש להחליק למעלה מתחתית המסך או להקיש במקום כלשהו במסך מעל האפליקציה"</string>
     <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"הפעלה של מצב שימוש ביד אחת"</string>
@@ -88,7 +84,7 @@
     <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"הבעיה לא נפתרה?\nאפשר להקיש כדי לחזור לגרסה הקודמת"</string>
     <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"אין בעיות במצלמה? אפשר להקיש כדי לסגור."</string>
     <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"רוצה לראות ולעשות יותר?"</string>
-    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"צריך לגרור אפליקציה אחרת כדי להשתמש במסך מפוצל"</string>
+    <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"צריך לגרור אפליקציה אחרת כדי להשתמש במסך המפוצל"</string>
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"צריך להקיש הקשה כפולה מחוץ לאפליקציה כדי למקם אותה מחדש"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"הבנתי"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"מרחיבים כדי לקבל מידע נוסף."</string>
@@ -96,15 +92,22 @@
     <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"אפשר להפעיל מחדש את האפליקציה כדי שהיא תוצג באופן טוב יותר במסך, אבל ייתכן שההתקדמות שלך או כל שינוי שלא נשמר יאבדו"</string>
     <string name="letterbox_restart_cancel" msgid="1342209132692537805">"ביטול"</string>
     <string name="letterbox_restart_restart" msgid="8529976234412442973">"הפעלה מחדש"</string>
-    <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"אין צורך להציג את זה שוב"</string>
+    <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"אין להציג שוב"</string>
+    <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"אפשר להקיש הקשה כפולה כדי\nלהעביר את האפליקציה למקום אחר"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"הגדלה"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"מזעור"</string>
     <string name="close_button_text" msgid="2913281996024033299">"סגירה"</string>
     <string name="back_button_text" msgid="1469718707134137085">"חזרה"</string>
     <string name="handle_text" msgid="1766582106752184456">"נקודת אחיזה"</string>
+    <string name="app_icon_text" msgid="2823268023931811747">"סמל האפליקציה"</string>
     <string name="fullscreen_text" msgid="1162316685217676079">"מסך מלא"</string>
     <string name="desktop_text" msgid="1077633567027630454">"ממשק המחשב"</string>
     <string name="split_screen_text" msgid="1396336058129570886">"מסך מפוצל"</string>
     <string name="more_button_text" msgid="3655388105592893530">"עוד"</string>
     <string name="float_button_text" msgid="9221657008391364581">"בלונים"</string>
+    <string name="select_text" msgid="5139083974039906583">"בחירה"</string>
+    <string name="screenshot_text" msgid="1477704010087786671">"צילום מסך"</string>
+    <string name="close_text" msgid="4986518933445178928">"סגירה"</string>
+    <string name="collapse_menu_text" msgid="7515008122450342029">"סגירת התפריט"</string>
+    <string name="expand_menu_text" msgid="3847736164494181168">"פתיחת התפריט"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-ja/strings.xml b/libs/WindowManager/Shell/res/values-ja/strings.xml
index 91fd724..8f2f6d8 100644
--- a/libs/WindowManager/Shell/res/values-ja/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ja/strings.xml
@@ -32,13 +32,13 @@
     <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"サイズ変更"</string>
     <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"非表示"</string>
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"表示"</string>
-    <string name="dock_forced_resizable" msgid="1749750436092293116">"アプリは分割画面では動作しないことがあります。"</string>
-    <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"アプリで分割画面がサポートされていません。"</string>
+    <string name="dock_forced_resizable" msgid="7429086980048964687">"アプリは分割画面では動作しないことがあります"</string>
+    <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"アプリで分割画面がサポートされていません"</string>
     <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"このアプリはウィンドウが 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="703810061635792791">"分割画面の分割線"</string>
-    <string name="divider_title" msgid="5482989479865361192">"分割画面の分割線"</string>
+    <string name="accessibility_divider" msgid="6407584574218956849">"分割画面の分割線"</string>
+    <string name="divider_title" msgid="1963391955593749442">"分割画面の分割線"</string>
     <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"左全画面"</string>
     <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"左 70%"</string>
     <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"左 50%"</string>
@@ -49,16 +49,12 @@
     <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"上 50%"</string>
     <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"上 30%"</string>
     <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"下部全画面"</string>
-    <!-- no translation found for accessibility_split_left (1713683765575562458) -->
-    <skip />
-    <!-- no translation found for accessibility_split_right (8441001008181296837) -->
-    <skip />
-    <!-- no translation found for accessibility_split_top (2789329702027147146) -->
-    <skip />
-    <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
-    <skip />
+    <string name="accessibility_split_left" msgid="1713683765575562458">"左に分割"</string>
+    <string name="accessibility_split_right" msgid="8441001008181296837">"右に分割"</string>
+    <string name="accessibility_split_top" msgid="2789329702027147146">"上に分割"</string>
+    <string name="accessibility_split_bottom" msgid="8694551025220868191">"下に分割"</string>
     <string name="one_handed_tutorial_title" msgid="4583241688067426350">"片手モードの使用"</string>
-    <string name="one_handed_tutorial_description" msgid="3486582858591353067">"終了するには、画面を下から上にスワイプするか、アプリの任意の場所をタップします"</string>
+    <string name="one_handed_tutorial_description" msgid="3486582858591353067">"終了するには、画面を下から上にスワイプするか、アプリの上側の任意の場所をタップします"</string>
     <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"片手モードを開始します"</string>
     <string name="accessibility_action_stop_one_handed" msgid="1369940261782179442">"片手モードを終了します"</string>
     <string name="bubbles_settings_button_description" msgid="1301286017420516912">"<xliff:g id="APP_NAME">%1$s</xliff:g> のバブルの設定"</string>
@@ -83,28 +79,35 @@
     <string name="notification_bubble_title" msgid="6082910224488253378">"バブル"</string>
     <string name="manage_bubbles_text" msgid="7730624269650594419">"管理"</string>
     <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"ふきだしが非表示になっています。"</string>
-    <string name="restart_button_description" msgid="6712141648865547958">"タップしてこのアプリを再起動すると、表示が適切になります。"</string>
+    <string name="restart_button_description" msgid="6712141648865547958">"タップしてこのアプリを再起動すると、より見やすく表示されます。"</string>
     <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"カメラに関する問題の場合は、\nタップすると修正できます"</string>
     <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"修正されなかった場合は、\nタップすると元に戻ります"</string>
     <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"カメラに関する問題でない場合は、タップすると閉じます。"</string>
     <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"表示を拡大して機能を強化"</string>
-    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"分割画面にするにはもう 1 つのアプリをドラッグしてください"</string>
+    <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"分割画面にするにはもう 1 つのアプリをドラッグしてください"</string>
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"位置を変えるにはアプリの外側をダブルタップしてください"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"開くと詳細が表示されます。"</string>
-    <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"再起動して画面をすっきりさせますか?"</string>
-    <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"アプリを再起動して画面をすっきりさせることはできますが、進捗状況が失われ、保存されていない変更が消える可能性があります"</string>
+    <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"アプリを再起動して画面表示を最適化しますか?"</string>
+    <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"アプリを再起動することにより表示を最適化できますが、保存されていない変更は失われる可能性があります"</string>
     <string name="letterbox_restart_cancel" msgid="1342209132692537805">"キャンセル"</string>
     <string name="letterbox_restart_restart" msgid="8529976234412442973">"再起動"</string>
     <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"次回から表示しない"</string>
+    <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"ダブルタップすると\nこのアプリを移動できます"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"最大化"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"最小化"</string>
     <string name="close_button_text" msgid="2913281996024033299">"閉じる"</string>
     <string name="back_button_text" msgid="1469718707134137085">"戻る"</string>
     <string name="handle_text" msgid="1766582106752184456">"ハンドル"</string>
+    <string name="app_icon_text" msgid="2823268023931811747">"アプリのアイコン"</string>
     <string name="fullscreen_text" msgid="1162316685217676079">"全画面表示"</string>
     <string name="desktop_text" msgid="1077633567027630454">"デスクトップ モード"</string>
     <string name="split_screen_text" msgid="1396336058129570886">"分割画面"</string>
     <string name="more_button_text" msgid="3655388105592893530">"その他"</string>
     <string name="float_button_text" msgid="9221657008391364581">"フローティング"</string>
+    <string name="select_text" msgid="5139083974039906583">"選択"</string>
+    <string name="screenshot_text" msgid="1477704010087786671">"スクリーンショット"</string>
+    <string name="close_text" msgid="4986518933445178928">"閉じる"</string>
+    <string name="collapse_menu_text" msgid="7515008122450342029">"メニューを閉じる"</string>
+    <string name="expand_menu_text" msgid="3847736164494181168">"メニューを開く"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-ka/strings.xml b/libs/WindowManager/Shell/res/values-ka/strings.xml
index a711afd..e58e67a 100644
--- a/libs/WindowManager/Shell/res/values-ka/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ka/strings.xml
@@ -32,13 +32,13 @@
     <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"ზომის შეცვლა"</string>
     <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"გადანახვა"</string>
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"გადანახვის გაუქმება"</string>
-    <string name="dock_forced_resizable" msgid="1749750436092293116">"აპმა შეიძლება არ იმუშაოს გაყოფილი ეკრანის რეჟიმში."</string>
-    <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"ეკრანის გაყოფა არ არის მხარდაჭერილი აპის მიერ."</string>
+    <string name="dock_forced_resizable" msgid="7429086980048964687">"აპმა შეიძლება არ იმუშაოს გაყოფილი ეკრანის რეჟიმში"</string>
+    <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"ეკრანის გაყოფა არ არის მხარდაჭერილი აპის მიერ"</string>
     <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"ამ აპის გახსნა შესაძლებელია მხოლოდ 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="703810061635792791">"გაყოფილი ეკრანის რეჟიმის გამყოფი"</string>
-    <string name="divider_title" msgid="5482989479865361192">"ეკრანის გაყოფის გამყოფი"</string>
+    <string name="accessibility_divider" msgid="6407584574218956849">"ეკრანის გაყოფის გამყოფი"</string>
+    <string name="divider_title" msgid="1963391955593749442">"ეკრანის გაყოფის გამყოფი"</string>
     <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"მარცხენა ნაწილის სრულ ეკრანზე გაშლა"</string>
     <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"მარცხენა ეკრანი — 70%"</string>
     <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"მარცხენა ეკრანი — 50%"</string>
@@ -49,14 +49,10 @@
     <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"ზედა ეკრანი — 50%"</string>
     <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"ზედა ეკრანი — 30%"</string>
     <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"ქვედა ნაწილის სრულ ეკრანზე გაშლა"</string>
-    <!-- no translation found for accessibility_split_left (1713683765575562458) -->
-    <skip />
-    <!-- no translation found for accessibility_split_right (8441001008181296837) -->
-    <skip />
-    <!-- no translation found for accessibility_split_top (2789329702027147146) -->
-    <skip />
-    <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
-    <skip />
+    <string name="accessibility_split_left" msgid="1713683765575562458">"გაყოფა მარცხნივ"</string>
+    <string name="accessibility_split_right" msgid="8441001008181296837">"გაყოფა მარჯვნივ"</string>
+    <string name="accessibility_split_top" msgid="2789329702027147146">"გაყოფა ზემოთ"</string>
+    <string name="accessibility_split_bottom" msgid="8694551025220868191">"გაყოფა ქვემოთ"</string>
     <string name="one_handed_tutorial_title" msgid="4583241688067426350">"ცალი ხელის რეჟიმის გამოყენება"</string>
     <string name="one_handed_tutorial_description" msgid="3486582858591353067">"გასასვლელად გადაფურცლეთ ეკრანის ქვედა კიდიდან ზემოთ ან შეეხეთ ნებისმიერ ადგილას აპის ზემოთ"</string>
     <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"ცალი ხელის რეჟიმის დაწყება"</string>
@@ -88,7 +84,7 @@
     <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"არ გამოსწორდა?\nშეეხეთ წინა ვერსიის დასაბრუნებლად"</string>
     <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"კამერას პრობლემები არ აქვს? შეეხეთ უარყოფისთვის."</string>
     <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"მეტის ნახვა და გაკეთება"</string>
-    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"ეკრანის გასაყოფად ჩავლებით გადაიტანეთ სხვა აპში"</string>
+    <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"ეკრანის გასაყოფად ჩავლებით გადაიტანეთ სხვა აპში"</string>
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"ორმაგად შეეხეთ აპის გარშემო სივრცეს, რათა ის სხვაგან გადაიტანოთ"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"გასაგებია"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"დამატებითი ინფორმაციისთვის გააფართოეთ."</string>
@@ -97,14 +93,21 @@
     <string name="letterbox_restart_cancel" msgid="1342209132692537805">"გაუქმება"</string>
     <string name="letterbox_restart_restart" msgid="8529976234412442973">"გადატვირთვა"</string>
     <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"აღარ გამოჩნდეს"</string>
+    <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"ამ აპის გადასატანად\nორმაგად შეეხეთ მას"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"მაქსიმალურად გაშლა"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"ჩაკეცვა"</string>
     <string name="close_button_text" msgid="2913281996024033299">"დახურვა"</string>
     <string name="back_button_text" msgid="1469718707134137085">"უკან"</string>
     <string name="handle_text" msgid="1766582106752184456">"იდენტიფიკატორი"</string>
+    <string name="app_icon_text" msgid="2823268023931811747">"აპის ხატულა"</string>
     <string name="fullscreen_text" msgid="1162316685217676079">"სრულ ეკრანზე"</string>
     <string name="desktop_text" msgid="1077633567027630454">"დესკტოპის რეჟიმი"</string>
     <string name="split_screen_text" msgid="1396336058129570886">"ეკრანის გაყოფა"</string>
     <string name="more_button_text" msgid="3655388105592893530">"სხვა"</string>
     <string name="float_button_text" msgid="9221657008391364581">"ფარფატი"</string>
+    <string name="select_text" msgid="5139083974039906583">"არჩევა"</string>
+    <string name="screenshot_text" msgid="1477704010087786671">"ეკრანის ანაბეჭდი"</string>
+    <string name="close_text" msgid="4986518933445178928">"დახურვა"</string>
+    <string name="collapse_menu_text" msgid="7515008122450342029">"მენიუს დახურვა"</string>
+    <string name="expand_menu_text" msgid="3847736164494181168">"მენიუს გახსნა"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-kk/strings.xml b/libs/WindowManager/Shell/res/values-kk/strings.xml
index a2e8688..c40cd2f 100644
--- a/libs/WindowManager/Shell/res/values-kk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-kk/strings.xml
@@ -32,13 +32,13 @@
     <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Өлшемін өзгерту"</string>
     <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Жасыру"</string>
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Көрсету"</string>
-    <string name="dock_forced_resizable" msgid="1749750436092293116">"Қолданба экранды бөлу режимінде жұмыс істемеуі мүмкін."</string>
-    <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Қодланба бөлінген экранды қолдамайды."</string>
+    <string name="dock_forced_resizable" msgid="7429086980048964687">"Қолданба экранды бөлу режимінде жұмыс істемеуі мүмкін."</string>
+    <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Қолданбада экранды бөлу мүмкін емес."</string>
     <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Бұл қолданбаны тек 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="703810061635792791">"Бөлінген экран бөлгіші"</string>
-    <string name="divider_title" msgid="5482989479865361192">"Бөлінген экран бөлгіші"</string>
+    <string name="accessibility_divider" msgid="6407584574218956849">"Экранды бөлу режимінің бөлгіші"</string>
+    <string name="divider_title" msgid="1963391955593749442">"Экранды бөлу режимінің бөлгіші"</string>
     <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Сол жағын толық экранға шығару"</string>
     <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"70% сол жақта"</string>
     <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"50% сол жақта"</string>
@@ -49,14 +49,10 @@
     <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"50% жоғарғы жақта"</string>
     <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"30% жоғарғы жақта"</string>
     <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Төменгісін толық экранға шығару"</string>
-    <!-- no translation found for accessibility_split_left (1713683765575562458) -->
-    <skip />
-    <!-- no translation found for accessibility_split_right (8441001008181296837) -->
-    <skip />
-    <!-- no translation found for accessibility_split_top (2789329702027147146) -->
-    <skip />
-    <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
-    <skip />
+    <string name="accessibility_split_left" msgid="1713683765575562458">"Сол жақтан шығару"</string>
+    <string name="accessibility_split_right" msgid="8441001008181296837">"Оң жақтан шығару"</string>
+    <string name="accessibility_split_top" msgid="2789329702027147146">"Жоғарыдан шығару"</string>
+    <string name="accessibility_split_bottom" msgid="8694551025220868191">"Астынан шығару"</string>
     <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Бір қолмен енгізу режимін пайдалану"</string>
     <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Шығу үшін экранның төменгі жағынан жоғары қарай сырғытыңыз немесе қолданбаның үстінен кез келген жерден түртіңіз."</string>
     <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Бір қолмен енгізу режимін іске қосу"</string>
@@ -83,12 +79,12 @@
     <string name="notification_bubble_title" msgid="6082910224488253378">"Көпіршік"</string>
     <string name="manage_bubbles_text" msgid="7730624269650594419">"Басқару"</string>
     <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Қалқыма хабар жабылды."</string>
-    <string name="restart_button_description" msgid="6712141648865547958">"Ыңғайлы көріністі реттеу үшін қолданбаны түртіп, өшіріп қосыңыз."</string>
+    <string name="restart_button_description" msgid="6712141648865547958">"Түртсеңіз, қолданба жабылып, ыңғайлы көрініспен қайта ашылады."</string>
     <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Камерада қателер шықты ма?\nЖөндеу үшін түртіңіз."</string>
     <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Жөнделмеді ме?\nҚайтару үшін түртіңіз."</string>
     <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Камерада қателер шықпады ма? Жабу үшін түртіңіз."</string>
     <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Қосымша ақпаратты қарап, әрекеттер жасау"</string>
-    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Экранды бөлу үшін басқа қолданбаға сүйреңіз."</string>
+    <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Экранды бөлу үшін басқа қолданбаға өтіңіз."</string>
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Қолданбаның орнын өзгерту үшін одан тыс жерді екі рет түртіңіз."</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"Түсінікті"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Толығырақ ақпарат алу үшін терезені жайыңыз."</string>
@@ -97,14 +93,21 @@
     <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Бас тарту"</string>
     <string name="letterbox_restart_restart" msgid="8529976234412442973">"Өшіріп қосу"</string>
     <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Қайта көрсетілмесін"</string>
+    <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Бұл қолданбаны басқа орынға\nжылжыту үшін екі рет түртіңіз."</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"Жаю"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Кішірейту"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Жабу"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Артқа"</string>
     <string name="handle_text" msgid="1766582106752184456">"Идентификатор"</string>
+    <string name="app_icon_text" msgid="2823268023931811747">"Қолданба белгішесі"</string>
     <string name="fullscreen_text" msgid="1162316685217676079">"Толық экран"</string>
     <string name="desktop_text" msgid="1077633567027630454">"Компьютер режимі"</string>
     <string name="split_screen_text" msgid="1396336058129570886">"Экранды бөлу"</string>
     <string name="more_button_text" msgid="3655388105592893530">"Қосымша"</string>
     <string name="float_button_text" msgid="9221657008391364581">"Қалқыма"</string>
+    <string name="select_text" msgid="5139083974039906583">"Таңдау"</string>
+    <string name="screenshot_text" msgid="1477704010087786671">"Скриншот"</string>
+    <string name="close_text" msgid="4986518933445178928">"Жабу"</string>
+    <string name="collapse_menu_text" msgid="7515008122450342029">"Мәзірді жабу"</string>
+    <string name="expand_menu_text" msgid="3847736164494181168">"Мәзірді ашу"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-km/strings.xml b/libs/WindowManager/Shell/res/values-km/strings.xml
index 7a486b8..4530267 100644
--- a/libs/WindowManager/Shell/res/values-km/strings.xml
+++ b/libs/WindowManager/Shell/res/values-km/strings.xml
@@ -32,13 +32,13 @@
     <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"ប្ដូរ​ទំហំ"</string>
     <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"លាក់ជាបណ្ដោះអាសន្ន"</string>
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"ឈប់លាក់ជាបណ្ដោះអាសន្ន"</string>
-    <string name="dock_forced_resizable" msgid="1749750436092293116">"កម្មវិធី​អាចនឹងមិន​ដំណើរការ​ជាមួយ​មុខងារបំបែកអេក្រង់​ទេ។"</string>
-    <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"កម្មវិធីមិនគាំទ្រអេក្រង់បំបែកជាពីរទេ"</string>
+    <string name="dock_forced_resizable" msgid="7429086980048964687">"កម្មវិធី​អាចមិន​ដំណើរការ​ជាមួយ​មុខងារបំបែកអេក្រង់​ទេ"</string>
+    <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"កម្មវិធីមិនអាចប្រើមុខងារ​បំបែកអេក្រង់បានទេ"</string>
     <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"កម្មវិធីនេះអាចបើកនៅក្នុងវិនដូតែ 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="703810061635792791">"កម្មវិធីចែកអេក្រង់បំបែក"</string>
-    <string name="divider_title" msgid="5482989479865361192">"បន្ទាត់ខណ្ឌចែកអេក្រង់បំបែក"</string>
+    <string name="accessibility_divider" msgid="6407584574218956849">"បន្ទាត់ខណ្ឌចែកក្នុងមុខងារ​បំបែកអេក្រង់"</string>
+    <string name="divider_title" msgid="1963391955593749442">"បន្ទាត់ខណ្ឌចែកក្នុងមុខងារ​បំបែកអេក្រង់"</string>
     <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"អេក្រង់ពេញខាងឆ្វេង"</string>
     <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"ឆ្វេង 70%"</string>
     <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"ឆ្វេង 50%"</string>
@@ -49,14 +49,10 @@
     <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"ខាងលើ 50%"</string>
     <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"ខាងលើ 30%"</string>
     <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"អេក្រង់ពេញខាងក្រោម"</string>
-    <!-- no translation found for accessibility_split_left (1713683765575562458) -->
-    <skip />
-    <!-- no translation found for accessibility_split_right (8441001008181296837) -->
-    <skip />
-    <!-- no translation found for accessibility_split_top (2789329702027147146) -->
-    <skip />
-    <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
-    <skip />
+    <string name="accessibility_split_left" msgid="1713683765575562458">"បំបែក​ខាងឆ្វេង"</string>
+    <string name="accessibility_split_right" msgid="8441001008181296837">"បំបែក​ខាងស្ដាំ"</string>
+    <string name="accessibility_split_top" msgid="2789329702027147146">"បំបែក​ខាងលើ"</string>
+    <string name="accessibility_split_bottom" msgid="8694551025220868191">"បំបែក​ខាងក្រោម"</string>
     <string name="one_handed_tutorial_title" msgid="4583241688067426350">"កំពុងប្រើ​មុខងារប្រើដៃម្ខាង"</string>
     <string name="one_handed_tutorial_description" msgid="3486582858591353067">"ដើម្បីចាកចេញ សូមអូសឡើងលើ​ពីផ្នែកខាងក្រោមអេក្រង់ ឬចុចផ្នែកណាមួយ​នៅខាងលើកម្មវិធី"</string>
     <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"ចាប់ផ្ដើម​មុខងារប្រើដៃម្ខាង"</string>
@@ -88,7 +84,7 @@
     <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"មិនបាន​ដោះស្រាយ​បញ្ហានេះទេឬ?\nចុចដើម្បី​ត្រឡប់"</string>
     <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"មិនមាន​បញ្ហាពាក់ព័ន្ធនឹង​កាមេរ៉ាទេឬ? ចុចដើម្បី​ច្រានចោល។"</string>
     <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"មើលឃើញ និងធ្វើបានកាន់តែច្រើន"</string>
-    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"អូស​កម្មវិធី​មួយ​ទៀត​ចូល ដើម្បី​ប្រើ​មុខងារ​បំបែកអេក្រង់"</string>
+    <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"អូស​កម្មវិធី​មួយ​ទៀត​ចូល ដើម្បី​ប្រើ​មុខងារ​បំបែកអេក្រង់"</string>
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"ចុចពីរដង​នៅ​ក្រៅ​កម្មវិធី ដើម្បី​ប្ដូរ​ទីតាំង​កម្មវិធី​នោះ"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"យល់ហើយ"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"ពង្រីកដើម្បីទទួលបានព័ត៌មានបន្ថែម។"</string>
@@ -97,14 +93,21 @@
     <string name="letterbox_restart_cancel" msgid="1342209132692537805">"បោះបង់"</string>
     <string name="letterbox_restart_restart" msgid="8529976234412442973">"ចាប់ផ្ដើម​ឡើង​វិញ"</string>
     <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"កុំ​បង្ហាញ​ម្ដង​ទៀត"</string>
+    <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"ចុចពីរដងដើម្បី\nផ្លាស់ទីកម្មវិធីនេះ"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"ពង្រីក"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"បង្រួម"</string>
     <string name="close_button_text" msgid="2913281996024033299">"បិទ"</string>
     <string name="back_button_text" msgid="1469718707134137085">"ថយក្រោយ"</string>
     <string name="handle_text" msgid="1766582106752184456">"ឈ្មោះអ្នកប្រើប្រាស់"</string>
+    <string name="app_icon_text" msgid="2823268023931811747">"រូប​កម្មវិធី"</string>
     <string name="fullscreen_text" msgid="1162316685217676079">"អេក្រង់​ពេញ"</string>
     <string name="desktop_text" msgid="1077633567027630454">"មុខងារកុំព្យូទ័រ"</string>
     <string name="split_screen_text" msgid="1396336058129570886">"មុខងារ​បំបែក​អេក្រង់"</string>
     <string name="more_button_text" msgid="3655388105592893530">"ច្រើនទៀត"</string>
     <string name="float_button_text" msgid="9221657008391364581">"អណ្ដែត"</string>
+    <string name="select_text" msgid="5139083974039906583">"ជ្រើសរើស"</string>
+    <string name="screenshot_text" msgid="1477704010087786671">"រូបថតអេក្រង់"</string>
+    <string name="close_text" msgid="4986518933445178928">"បិទ"</string>
+    <string name="collapse_menu_text" msgid="7515008122450342029">"បិទ​ម៉ឺនុយ"</string>
+    <string name="expand_menu_text" msgid="3847736164494181168">"បើកម៉ឺនុយ"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-kn/strings.xml b/libs/WindowManager/Shell/res/values-kn/strings.xml
index 5fcdcf2..2dfbad4 100644
--- a/libs/WindowManager/Shell/res/values-kn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-kn/strings.xml
@@ -24,7 +24,7 @@
     <string name="pip_menu_title" msgid="5393619322111827096">"ಮೆನು"</string>
     <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"ಚಿತ್ರದಲ್ಲಿ ಚಿತ್ರ ಮೆನು"</string>
     <string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> ಚಿತ್ರದಲ್ಲಿ ಚಿತ್ರವಾಗಿದೆ"</string>
-    <string name="pip_notification_message" msgid="8854051911700302620">"<xliff:g id="NAME">%s</xliff:g> ಈ ವೈಶಿಷ್ಟ್ಯ ಬಳಸುವುದನ್ನು ನೀವು ಬಯಸದಿದ್ದರೆ, ಸೆಟ್ಟಿಂಗ್‌ಗಳನ್ನು ತೆರೆಯಲು ಮತ್ತು ಅದನ್ನು ಆಫ್ ಮಾಡಲು ಟ್ಯಾಪ್ ಮಾಡಿ."</string>
+    <string name="pip_notification_message" msgid="8854051911700302620">"<xliff:g id="NAME">%s</xliff:g> ಈ ಫೀಚರ್ ಬಳಸುವುದನ್ನು ನೀವು ಬಯಸದಿದ್ದರೆ, ಸೆಟ್ಟಿಂಗ್‌ಗಳನ್ನು ತೆರೆಯಲು ಮತ್ತು ಅದನ್ನು ಆಫ್ ಮಾಡಲು ಟ್ಯಾಪ್ ಮಾಡಿ."</string>
     <string name="pip_play" msgid="3496151081459417097">"ಪ್ಲೇ"</string>
     <string name="pip_pause" msgid="690688849510295232">"ವಿರಾಮಗೊಳಿಸಿ"</string>
     <string name="pip_skip_to_next" msgid="8403429188794867653">"ಮುಂದಕ್ಕೆ ಸ್ಕಿಪ್‌ ಮಾಡಿ"</string>
@@ -32,13 +32,13 @@
     <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"ಮರುಗಾತ್ರಗೊಳಿಸಿ"</string>
     <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"ಸ್ಟ್ಯಾಶ್ ಮಾಡಿ"</string>
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"ಅನ್‌ಸ್ಟ್ಯಾಶ್ ಮಾಡಿ"</string>
-    <string name="dock_forced_resizable" msgid="1749750436092293116">"ವಿಭಜಿಸಿದ ಸ್ಕ್ರೀನ್‌ನಲ್ಲಿ ಆ್ಯಪ್ ಕೆಲಸ ಮಾಡದೇ ಇರಬಹುದು."</string>
-    <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"ಅಪ್ಲಿಕೇಶನ್ ಸ್ಪ್ಲಿಟ್ ಸ್ಕ್ರೀನ್ ಅನ್ನು ಬೆಂಬಲಿಸುವುದಿಲ್ಲ."</string>
+    <string name="dock_forced_resizable" msgid="7429086980048964687">"ಸ್ಪ್ಲಿಟ್‌ ಸ್ಕ್ರೀನ್‌ನಲ್ಲಿ ಆ್ಯಪ್ ಕೆಲಸ ಮಾಡದೇ ಇರಬಹುದು"</string>
+    <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"ಸ್ಪ್ಲಿಟ್‌ ಸ್ಕ್ರೀನ್‌ ಅನ್ನು ಆ್ಯಪ್ ಬೆಂಬಲಿಸುವುದಿಲ್ಲ"</string>
     <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"ಈ ಆ್ಯಪ್ ಅನ್ನು 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="703810061635792791">"ಸ್ಪ್ಲಿಟ್-ಪರದೆ ಡಿವೈಡರ್"</string>
-    <string name="divider_title" msgid="5482989479865361192">"ಸ್ಪ್ಲಿಟ್-ಪರದೆ ಡಿವೈಡರ್"</string>
+    <string name="accessibility_divider" msgid="6407584574218956849">"ಸ್ಪ್ಲಿಟ್‌ ಸ್ಕ್ರೀನ್ ಡಿವೈಡರ್"</string>
+    <string name="divider_title" msgid="1963391955593749442">"ಸ್ಪ್ಲಿಟ್‌ ಸ್ಕ್ರೀನ್ ಡಿವೈಡರ್"</string>
     <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"ಎಡ ಪೂರ್ಣ ಪರದೆ"</string>
     <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"70% ಎಡಕ್ಕೆ"</string>
     <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"50% ಎಡಕ್ಕೆ"</string>
@@ -49,14 +49,10 @@
     <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"50% ಮೇಲಕ್ಕೆ"</string>
     <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"30% ಮೇಲಕ್ಕೆ"</string>
     <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"ಕೆಳಗಿನ ಪೂರ್ಣ ಪರದೆ"</string>
-    <!-- no translation found for accessibility_split_left (1713683765575562458) -->
-    <skip />
-    <!-- no translation found for accessibility_split_right (8441001008181296837) -->
-    <skip />
-    <!-- no translation found for accessibility_split_top (2789329702027147146) -->
-    <skip />
-    <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
-    <skip />
+    <string name="accessibility_split_left" msgid="1713683765575562458">"ಎಡಕ್ಕೆ ವಿಭಜಿಸಿ"</string>
+    <string name="accessibility_split_right" msgid="8441001008181296837">"ಬಲಕ್ಕೆ ವಿಭಜಿಸಿ"</string>
+    <string name="accessibility_split_top" msgid="2789329702027147146">"ಮೇಲಕ್ಕೆ ವಿಭಜಿಸಿ"</string>
+    <string name="accessibility_split_bottom" msgid="8694551025220868191">"ಕೆಳಕ್ಕೆ ವಿಭಜಿಸಿ"</string>
     <string name="one_handed_tutorial_title" msgid="4583241688067426350">"ಒಂದು ಕೈ ಮೋಡ್ ಬಳಸುವುದರ ಬಗ್ಗೆ"</string>
     <string name="one_handed_tutorial_description" msgid="3486582858591353067">"ನಿರ್ಗಮಿಸಲು, ಸ್ಕ್ರೀನ್‌ನ ಕೆಳಗಿನಿಂದ ಮೇಲಕ್ಕೆ ಸ್ವೈಪ್ ಮಾಡಿ ಅಥವಾ ಆ್ಯಪ್‌ನ ಮೇಲೆ ಎಲ್ಲಿಯಾದರೂ ಟ್ಯಾಪ್ ಮಾಡಿ"</string>
     <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"ಒಂದು ಕೈ ಮೋಡ್ ಅನ್ನು ಪ್ರಾರಂಭಿಸಿ"</string>
@@ -88,7 +84,7 @@
     <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"ಅದನ್ನು ಸರಿಪಡಿಸಲಿಲ್ಲವೇ?\nಹಿಂತಿರುಗಿಸಲು ಟ್ಯಾಪ್ ಮಾಡಿ"</string>
     <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"ಕ್ಯಾಮರಾ ಸಮಸ್ಯೆಗಳಿಲ್ಲವೇ? ವಜಾಗೊಳಿಸಲು ಟ್ಯಾಪ್ ಮಾಡಿ."</string>
     <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"ನೋಡಿ ಮತ್ತು ಹೆಚ್ಚಿನದನ್ನು ಮಾಡಿ"</string>
-    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"ಸ್ಪ್ಲಿಟ್ ಸ್ಕ್ರೀನ್‌ಗಾಗಿ ಮತ್ತೊಂದು ಆ್ಯಪ್‌ನಲ್ಲಿ ಎಳೆಯಿರಿ"</string>
+    <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"ಸ್ಪ್ಲಿಟ್‌ ಸ್ಕ್ರೀನ್‌ಗಾಗಿ ಮತ್ತೊಂದು ಆ್ಯಪ್‌ನಲ್ಲಿ ಡ್ರ್ಯಾಗ್ ಮಾಡಿ"</string>
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"ಆ್ಯಪ್ ಒಂದರ ಸ್ಥಾನವನ್ನು ಬದಲಾಯಿಸಲು ಅದರ ಹೊರಗೆ ಡಬಲ್-ಟ್ಯಾಪ್ ಮಾಡಿ"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"ಸರಿ"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"ಇನ್ನಷ್ಟು ಮಾಹಿತಿಗಾಗಿ ವಿಸ್ತೃತಗೊಳಿಸಿ."</string>
@@ -97,14 +93,21 @@
     <string name="letterbox_restart_cancel" msgid="1342209132692537805">"ರದ್ದುಮಾಡಿ"</string>
     <string name="letterbox_restart_restart" msgid="8529976234412442973">"ಮರುಪ್ರಾರಂಭಿಸಿ"</string>
     <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"ಮತ್ತೊಮ್ಮೆ ತೋರಿಸಬೇಡಿ"</string>
+    <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"ಈ ಆ್ಯಪ್ ಅನ್ನು ಸರಿಸಲು\nಡಬಲ್-ಟ್ಯಾಪ್ ಮಾಡಿ"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"ಹಿಗ್ಗಿಸಿ"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"ಕುಗ್ಗಿಸಿ"</string>
     <string name="close_button_text" msgid="2913281996024033299">"ಮುಚ್ಚಿರಿ"</string>
     <string name="back_button_text" msgid="1469718707134137085">"ಹಿಂದಕ್ಕೆ"</string>
     <string name="handle_text" msgid="1766582106752184456">"ಹ್ಯಾಂಡಲ್"</string>
+    <string name="app_icon_text" msgid="2823268023931811747">"ಆ್ಯಪ್ ಐಕಾನ್"</string>
     <string name="fullscreen_text" msgid="1162316685217676079">"ಫುಲ್‌ಸ್ಕ್ರೀನ್"</string>
     <string name="desktop_text" msgid="1077633567027630454">"ಡೆಸ್ಕ್‌ಟಾಪ್ ಮೋಡ್"</string>
     <string name="split_screen_text" msgid="1396336058129570886">"ಸ್ಪ್ಲಿಟ್ ಸ್ಕ್ರೀನ್"</string>
     <string name="more_button_text" msgid="3655388105592893530">"ಇನ್ನಷ್ಟು"</string>
     <string name="float_button_text" msgid="9221657008391364581">"ಫ್ಲೋಟ್"</string>
+    <string name="select_text" msgid="5139083974039906583">"ಆಯ್ಕೆಮಾಡಿ"</string>
+    <string name="screenshot_text" msgid="1477704010087786671">"ಸ್ಕ್ರೀನ್‌ಶಾಟ್"</string>
+    <string name="close_text" msgid="4986518933445178928">"ಮುಚ್ಚಿ"</string>
+    <string name="collapse_menu_text" msgid="7515008122450342029">"ಮೆನು ಮುಚ್ಚಿ"</string>
+    <string name="expand_menu_text" msgid="3847736164494181168">"ಮೆನು ತೆರೆಯಿರಿ"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-ko/strings.xml b/libs/WindowManager/Shell/res/values-ko/strings.xml
index 74cff1f..55697ca 100644
--- a/libs/WindowManager/Shell/res/values-ko/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ko/strings.xml
@@ -32,13 +32,13 @@
     <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"크기 조절"</string>
     <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"숨기기"</string>
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"숨기기 취소"</string>
-    <string name="dock_forced_resizable" msgid="1749750436092293116">"앱이 분할 화면에서 작동하지 않을 수 있습니다."</string>
-    <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"앱이 화면 분할을 지원하지 않습니다."</string>
+    <string name="dock_forced_resizable" msgid="7429086980048964687">"앱이 화면 분할 모드로는 작동하지 않을 수 있습니다"</string>
+    <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"앱이 화면 분할을 지원하지 않습니다"</string>
     <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"이 앱은 창 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="703810061635792791">"화면 분할기"</string>
-    <string name="divider_title" msgid="5482989479865361192">"화면 분할기"</string>
+    <string name="accessibility_divider" msgid="6407584574218956849">"화면 분할기"</string>
+    <string name="divider_title" msgid="1963391955593749442">"화면 분할기"</string>
     <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"왼쪽 화면 전체화면"</string>
     <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"왼쪽 화면 70%"</string>
     <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"왼쪽 화면 50%"</string>
@@ -49,14 +49,10 @@
     <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"위쪽 화면 50%"</string>
     <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"위쪽 화면 30%"</string>
     <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"아래쪽 화면 전체화면"</string>
-    <!-- no translation found for accessibility_split_left (1713683765575562458) -->
-    <skip />
-    <!-- no translation found for accessibility_split_right (8441001008181296837) -->
-    <skip />
-    <!-- no translation found for accessibility_split_top (2789329702027147146) -->
-    <skip />
-    <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
-    <skip />
+    <string name="accessibility_split_left" msgid="1713683765575562458">"왼쪽으로 분할"</string>
+    <string name="accessibility_split_right" msgid="8441001008181296837">"오른쪽으로 분할"</string>
+    <string name="accessibility_split_top" msgid="2789329702027147146">"위쪽으로 분할"</string>
+    <string name="accessibility_split_bottom" msgid="8694551025220868191">"아래쪽으로 분할"</string>
     <string name="one_handed_tutorial_title" msgid="4583241688067426350">"한 손 사용 모드 사용하기"</string>
     <string name="one_handed_tutorial_description" msgid="3486582858591353067">"화면 하단에서 위로 스와이프하거나 앱 상단을 탭하여 종료합니다."</string>
     <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"한 손 사용 모드 시작"</string>
@@ -83,12 +79,12 @@
     <string name="notification_bubble_title" msgid="6082910224488253378">"버블"</string>
     <string name="manage_bubbles_text" msgid="7730624269650594419">"관리"</string>
     <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"대화창을 닫았습니다."</string>
-    <string name="restart_button_description" msgid="6712141648865547958">"보기를 개선하려면 탭하여 앱을 다시 시작합니다."</string>
+    <string name="restart_button_description" msgid="6712141648865547958">"더 편하게 보기를 원하면 탭하여 앱을 다시 시작하세요."</string>
     <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"카메라 문제가 있나요?\n해결하려면 탭하세요."</string>
     <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"해결되지 않았나요?\n되돌리려면 탭하세요."</string>
     <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"카메라에 문제가 없나요? 닫으려면 탭하세요."</string>
     <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"더 많은 정보를 보고 더 많은 작업을 처리하세요"</string>
-    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"화면 분할을 사용하려면 다른 앱을 드래그해 가져옵니다."</string>
+    <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"화면 분할을 사용하려면 다른 앱을 드래그해 가져옵니다."</string>
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"앱 위치를 조정하려면 앱 외부를 두 번 탭합니다."</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"확인"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"추가 정보는 펼쳐서 확인하세요."</string>
@@ -97,14 +93,21 @@
     <string name="letterbox_restart_cancel" msgid="1342209132692537805">"취소"</string>
     <string name="letterbox_restart_restart" msgid="8529976234412442973">"다시 시작"</string>
     <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"다시 표시 안함"</string>
+    <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"두 번 탭하여\n이 앱 이동"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"최대화"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"최소화"</string>
     <string name="close_button_text" msgid="2913281996024033299">"닫기"</string>
     <string name="back_button_text" msgid="1469718707134137085">"뒤로"</string>
     <string name="handle_text" msgid="1766582106752184456">"핸들"</string>
+    <string name="app_icon_text" msgid="2823268023931811747">"앱 아이콘"</string>
     <string name="fullscreen_text" msgid="1162316685217676079">"전체 화면"</string>
     <string name="desktop_text" msgid="1077633567027630454">"데스크톱 모드"</string>
     <string name="split_screen_text" msgid="1396336058129570886">"화면 분할"</string>
     <string name="more_button_text" msgid="3655388105592893530">"더보기"</string>
     <string name="float_button_text" msgid="9221657008391364581">"플로팅"</string>
+    <string name="select_text" msgid="5139083974039906583">"선택"</string>
+    <string name="screenshot_text" msgid="1477704010087786671">"스크린샷"</string>
+    <string name="close_text" msgid="4986518933445178928">"닫기"</string>
+    <string name="collapse_menu_text" msgid="7515008122450342029">"메뉴 닫기"</string>
+    <string name="expand_menu_text" msgid="3847736164494181168">"메뉴 열기"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-ky/strings.xml b/libs/WindowManager/Shell/res/values-ky/strings.xml
index 477c65d..19df267 100644
--- a/libs/WindowManager/Shell/res/values-ky/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ky/strings.xml
@@ -24,7 +24,7 @@
     <string name="pip_menu_title" msgid="5393619322111827096">"Меню"</string>
     <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"Сүрөт ичиндеги сүрөт менюсу"</string>
     <string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> – сүрөт ичиндеги сүрөт"</string>
-    <string name="pip_notification_message" msgid="8854051911700302620">"Эгер <xliff:g id="NAME">%s</xliff:g> колдонмосу бул функцияны пайдаланбасын десеңиз, жөндөөлөрдү ачып туруп, аны өчүрүп коюңуз."</string>
+    <string name="pip_notification_message" msgid="8854051911700302620">"Эгер <xliff:g id="NAME">%s</xliff:g> колдонмосу бул функцияны пайдаланбасын десеңиз, параметрлерди ачып туруп, аны өчүрүп коюңуз."</string>
     <string name="pip_play" msgid="3496151081459417097">"Ойнотуу"</string>
     <string name="pip_pause" msgid="690688849510295232">"Тындыруу"</string>
     <string name="pip_skip_to_next" msgid="8403429188794867653">"Кийинкисине өткөрүп жиберүү"</string>
@@ -32,13 +32,13 @@
     <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Өлчөмүн өзгөртүү"</string>
     <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Сейфке салуу"</string>
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Сейфтен чыгаруу"</string>
-    <string name="dock_forced_resizable" msgid="1749750436092293116">"Колдонмодо экран бөлүнбөшү мүмкүн."</string>
-    <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Колдонмодо экран бөлүнбөйт."</string>
+    <string name="dock_forced_resizable" msgid="7429086980048964687">"Колдонмодо экран бөлүнбөшү мүмкүн"</string>
+    <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Колдонмодо экран бөлүнбөйт"</string>
     <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Бул колдонмону 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="703810061635792791">"Экранды бөлгүч"</string>
-    <string name="divider_title" msgid="5482989479865361192">"Экранды бөлгүч"</string>
+    <string name="accessibility_divider" msgid="6407584574218956849">"Экранды бөлгүч"</string>
+    <string name="divider_title" msgid="1963391955593749442">"Экранды бөлгүч"</string>
     <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Сол жактагы экранды толук экран режимине өткөрүү"</string>
     <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Сол жактагы экранды 70%"</string>
     <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Сол жактагы экранды 50%"</string>
@@ -49,14 +49,10 @@
     <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Үстүнкү экранды 50%"</string>
     <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Үстүнкү экранды 30%"</string>
     <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Ылдыйкы экранды толук экран режимине өткөрүү"</string>
-    <!-- no translation found for accessibility_split_left (1713683765575562458) -->
-    <skip />
-    <!-- no translation found for accessibility_split_right (8441001008181296837) -->
-    <skip />
-    <!-- no translation found for accessibility_split_top (2789329702027147146) -->
-    <skip />
-    <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
-    <skip />
+    <string name="accessibility_split_left" msgid="1713683765575562458">"Солго бөлүү"</string>
+    <string name="accessibility_split_right" msgid="8441001008181296837">"Оңго бөлүү"</string>
+    <string name="accessibility_split_top" msgid="2789329702027147146">"Өйдө бөлүү"</string>
+    <string name="accessibility_split_bottom" msgid="8694551025220868191">"Ылдый бөлүү"</string>
     <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Бир кол режимин колдонуу"</string>
     <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Чыгуу үчүн экранды ылдый жагынан өйдө сүрүңүз же колдонмонун өйдө жагын басыңыз"</string>
     <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Бир кол режимин баштоо"</string>
@@ -88,7 +84,7 @@
     <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Оңдолгон жокпу?\nАртка кайтаруу үчүн таптаңыз"</string>
     <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Камерада маселе жокпу? Этибарга албоо үчүн таптаңыз."</string>
     <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Көрүп, көбүрөөк нерселерди жасаңыз"</string>
-    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Экранды бөлүү үчүн башка колдонмону сүйрөңүз"</string>
+    <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Экранды бөлүү үчүн башка колдонмону сүйрөңүз"</string>
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Колдонмону жылдыруу үчүн сырт жагын эки жолу таптаңыз"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"Түшүндүм"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Толук маалымат алуу үчүн жайып көрүңүз."</string>
@@ -97,14 +93,21 @@
     <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Токтотуу"</string>
     <string name="letterbox_restart_restart" msgid="8529976234412442973">"Өчүрүп күйгүзүү"</string>
     <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Экинчи көрүнбөсүн"</string>
+    <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Бул колдонмону жылдыруу үчүн\nэки жолу таптаңыз"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"Чоңойтуу"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Кичирейтүү"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Жабуу"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Артка"</string>
     <string name="handle_text" msgid="1766582106752184456">"Маркер"</string>
+    <string name="app_icon_text" msgid="2823268023931811747">"Колдонмонун сүрөтчөсү"</string>
     <string name="fullscreen_text" msgid="1162316685217676079">"Толук экран"</string>
     <string name="desktop_text" msgid="1077633567027630454">"Компьютер режими"</string>
     <string name="split_screen_text" msgid="1396336058129570886">"Экранды бөлүү"</string>
     <string name="more_button_text" msgid="3655388105592893530">"Дагы"</string>
     <string name="float_button_text" msgid="9221657008391364581">"Калкыма"</string>
+    <string name="select_text" msgid="5139083974039906583">"Тандоо"</string>
+    <string name="screenshot_text" msgid="1477704010087786671">"Скриншот"</string>
+    <string name="close_text" msgid="4986518933445178928">"Жабуу"</string>
+    <string name="collapse_menu_text" msgid="7515008122450342029">"Менюну жабуу"</string>
+    <string name="expand_menu_text" msgid="3847736164494181168">"Менюну ачуу"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-lo/strings.xml b/libs/WindowManager/Shell/res/values-lo/strings.xml
index 81b0826..a25699f 100644
--- a/libs/WindowManager/Shell/res/values-lo/strings.xml
+++ b/libs/WindowManager/Shell/res/values-lo/strings.xml
@@ -32,13 +32,13 @@
     <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"ປ່ຽນຂະໜາດ"</string>
     <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"ເກັບໄວ້ບ່ອນເກັບສ່ວນຕົວ"</string>
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"ເອົາອອກຈາກບ່ອນເກັບສ່ວນຕົວ"</string>
-    <string name="dock_forced_resizable" msgid="1749750436092293116">"ແອັບອາດໃຊ້ບໍ່ໄດ້ກັບການແບ່ງໜ້າຈໍ."</string>
-    <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"ແອັບບໍ່ຮອງຮັບໜ້າຈໍແບບແຍກກັນ."</string>
+    <string name="dock_forced_resizable" msgid="7429086980048964687">"ແອັບອາດໃຊ້ບໍ່ໄດ້ກັບໂໝດແບ່ງໜ້າຈໍ"</string>
+    <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"ແອັບບໍ່ຮອງຮັບການແບ່ງໜ້າຈໍ"</string>
     <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"ແອັບນີ້ສາມາດເປີດໄດ້ໃນ 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="703810061635792791">"ຕົວຂັ້ນການແບ່ງໜ້າຈໍ"</string>
-    <string name="divider_title" msgid="5482989479865361192">"ຕົວຂັ້ນການແບ່ງໜ້າຈໍ"</string>
+    <string name="accessibility_divider" msgid="6407584574218956849">"ເສັ້ນແບ່ງໜ້າຈໍ"</string>
+    <string name="divider_title" msgid="1963391955593749442">"ເສັ້ນແບ່ງໜ້າຈໍ"</string>
     <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"ເຕັມໜ້າຈໍຊ້າຍ"</string>
     <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"ຊ້າຍ 70%"</string>
     <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"ຊ້າຍ 50%"</string>
@@ -49,14 +49,10 @@
     <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"ເທິງສຸດ 50%"</string>
     <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"ເທິງສຸດ 30%"</string>
     <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"ເຕັມໜ້າຈໍລຸ່ມສຸດ"</string>
-    <!-- no translation found for accessibility_split_left (1713683765575562458) -->
-    <skip />
-    <!-- no translation found for accessibility_split_right (8441001008181296837) -->
-    <skip />
-    <!-- no translation found for accessibility_split_top (2789329702027147146) -->
-    <skip />
-    <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
-    <skip />
+    <string name="accessibility_split_left" msgid="1713683765575562458">"ແຍກຊ້າຍ"</string>
+    <string name="accessibility_split_right" msgid="8441001008181296837">"ແຍກຂວາ"</string>
+    <string name="accessibility_split_top" msgid="2789329702027147146">"ແຍກເທິງ"</string>
+    <string name="accessibility_split_bottom" msgid="8694551025220868191">"ແຍກລຸ່ມ"</string>
     <string name="one_handed_tutorial_title" msgid="4583241688067426350">"ກຳລັງໃຊ້ໂໝດມືດຽວ"</string>
     <string name="one_handed_tutorial_description" msgid="3486582858591353067">"ເພື່ອອອກ, ໃຫ້ປັດຂຶ້ນຈາກລຸ່ມສຸດຂອງໜ້າຈໍ ຫຼື ແຕະບ່ອນໃດກໍໄດ້ຢູ່ເໜືອແອັບ"</string>
     <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"ເລີ່ມໂໝດມືດຽວ"</string>
@@ -88,7 +84,7 @@
     <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"ບໍ່ໄດ້ແກ້ໄຂມັນບໍ?\nແຕະເພື່ອແປງກັບຄືນ"</string>
     <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"ບໍ່ມີບັນຫາກ້ອງຖ່າຍຮູບບໍ? ແຕະເພື່ອ​ປິດ​ໄວ້."</string>
     <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"ເບິ່ງ ແລະ ເຮັດຫຼາຍຂຶ້ນ"</string>
-    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"ລາກແອັບອື່ນເຂົ້າມາເພື່ອແບ່ງໜ້າຈໍ"</string>
+    <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"ລາກໄປໄວ້ໃນແອັບອື່ນເພື່ອແບ່ງໜ້າຈໍ"</string>
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"ແຕະສອງເທື່ອໃສ່ນອກແອັບໃດໜຶ່ງເພື່ອຈັດຕຳແໜ່ງຂອງມັນຄືນໃໝ່"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"ເຂົ້າໃຈແລ້ວ"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"ຂະຫຍາຍເພື່ອເບິ່ງຂໍ້ມູນເພີ່ມເຕີມ."</string>
@@ -97,14 +93,21 @@
     <string name="letterbox_restart_cancel" msgid="1342209132692537805">"ຍົກເລີກ"</string>
     <string name="letterbox_restart_restart" msgid="8529976234412442973">"ຣີສະຕາດ"</string>
     <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"ບໍ່ຕ້ອງສະແດງອີກ"</string>
+    <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"ແຕະສອງເທື່ອເພື່ອ\nຍ້າຍແອັບນີ້"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"ຂະຫຍາຍໃຫຍ່ສຸດ"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"ຫຍໍ້ລົງ"</string>
     <string name="close_button_text" msgid="2913281996024033299">"ປິດ"</string>
     <string name="back_button_text" msgid="1469718707134137085">"ກັບຄືນ"</string>
     <string name="handle_text" msgid="1766582106752184456">"ມືບັງຄັບ"</string>
+    <string name="app_icon_text" msgid="2823268023931811747">"ໄອຄອນແອັບ"</string>
     <string name="fullscreen_text" msgid="1162316685217676079">"ເຕັມຈໍ"</string>
     <string name="desktop_text" msgid="1077633567027630454">"ໂໝດເດັສທັອບ"</string>
     <string name="split_screen_text" msgid="1396336058129570886">"ແບ່ງໜ້າຈໍ"</string>
     <string name="more_button_text" msgid="3655388105592893530">"ເພີ່ມເຕີມ"</string>
     <string name="float_button_text" msgid="9221657008391364581">"ລອຍ"</string>
+    <string name="select_text" msgid="5139083974039906583">"ເລືອກ"</string>
+    <string name="screenshot_text" msgid="1477704010087786671">"ຮູບໜ້າຈໍ"</string>
+    <string name="close_text" msgid="4986518933445178928">"ປິດ"</string>
+    <string name="collapse_menu_text" msgid="7515008122450342029">"ປິດເມນູ"</string>
+    <string name="expand_menu_text" msgid="3847736164494181168">"ເປີດເມນູ"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-lt/strings.xml b/libs/WindowManager/Shell/res/values-lt/strings.xml
index 0447ec7..d893fcf 100644
--- a/libs/WindowManager/Shell/res/values-lt/strings.xml
+++ b/libs/WindowManager/Shell/res/values-lt/strings.xml
@@ -32,13 +32,13 @@
     <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Pakeisti dydį"</string>
     <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Paslėpti"</string>
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Nebeslėpti"</string>
-    <string name="dock_forced_resizable" msgid="1749750436092293116">"Programa gali neveikti naudojant išskaidyto ekrano režimą."</string>
-    <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Programoje nepalaikomas skaidytas ekranas."</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>
     <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Š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="703810061635792791">"Skaidyto ekrano daliklis"</string>
-    <string name="divider_title" msgid="5482989479865361192">"Skaidyto ekrano daliklis"</string>
+    <string name="accessibility_divider" msgid="6407584574218956849">"Išskaidyto ekrano režimo daliklis"</string>
+    <string name="divider_title" msgid="1963391955593749442">"Išskaidyto ekrano režimo daliklis"</string>
     <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Kairysis ekranas viso ekrano režimu"</string>
     <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Kairysis ekranas 70 %"</string>
     <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Kairysis ekranas 50 %"</string>
@@ -49,14 +49,10 @@
     <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Viršutinis ekranas 50 %"</string>
     <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Viršutinis ekranas 30 %"</string>
     <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Apatinis ekranas viso ekrano režimu"</string>
-    <!-- no translation found for accessibility_split_left (1713683765575562458) -->
-    <skip />
-    <!-- no translation found for accessibility_split_right (8441001008181296837) -->
-    <skip />
-    <!-- no translation found for accessibility_split_top (2789329702027147146) -->
-    <skip />
-    <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
-    <skip />
+    <string name="accessibility_split_left" msgid="1713683765575562458">"Išskaidyti kairėn"</string>
+    <string name="accessibility_split_right" msgid="8441001008181296837">"Išskaidyti dešinėn"</string>
+    <string name="accessibility_split_top" msgid="2789329702027147146">"Išskaidyti viršuje"</string>
+    <string name="accessibility_split_bottom" msgid="8694551025220868191">"Išskaidyti apačioje"</string>
     <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Vienos rankos režimo naudojimas"</string>
     <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Jei norite išeiti, perbraukite aukštyn nuo ekrano apačios arba palieskite bet kur virš programos"</string>
     <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Pradėti vienos rankos režimą"</string>
@@ -88,7 +84,7 @@
     <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Nepavyko pataisyti?\nPalieskite, kad grąžintumėte"</string>
     <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Nėra jokių problemų dėl kameros? Palieskite, kad atsisakytumėte."</string>
     <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Daugiau turinio ir funkcijų"</string>
-    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Vilkite kitoje programoje, kad galėtumėte naudoti išskaidyto ekrano režimą"</string>
+    <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Vilkite kitoje programoje, kad galėtumėte naudoti išskaidyto ekrano režimą"</string>
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Dukart palieskite už programos ribų, kad pakeistumėte jos poziciją"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"Supratau"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Išskleiskite, jei reikia daugiau informacijos."</string>
@@ -97,14 +93,21 @@
     <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Atšaukti"</string>
     <string name="letterbox_restart_restart" msgid="8529976234412442973">"Paleisti iš naujo"</string>
     <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Daugiau neberodyti"</string>
+    <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Dukart palieskite, kad\nperkeltumėte šią programą"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"Padidinti"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Sumažinti"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Uždaryti"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Atgal"</string>
     <string name="handle_text" msgid="1766582106752184456">"Rankenėlė"</string>
+    <string name="app_icon_text" msgid="2823268023931811747">"Programos piktograma"</string>
     <string name="fullscreen_text" msgid="1162316685217676079">"Visas ekranas"</string>
     <string name="desktop_text" msgid="1077633567027630454">"Stalinio kompiuterio režimas"</string>
     <string name="split_screen_text" msgid="1396336058129570886">"Išskaidyto ekrano režimas"</string>
     <string name="more_button_text" msgid="3655388105592893530">"Daugiau"</string>
     <string name="float_button_text" msgid="9221657008391364581">"Slankusis langas"</string>
+    <string name="select_text" msgid="5139083974039906583">"Pasirinkti"</string>
+    <string name="screenshot_text" msgid="1477704010087786671">"Ekrano kopija"</string>
+    <string name="close_text" msgid="4986518933445178928">"Uždaryti"</string>
+    <string name="collapse_menu_text" msgid="7515008122450342029">"Uždaryti meniu"</string>
+    <string name="expand_menu_text" msgid="3847736164494181168">"Atidaryti meniu"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-lv/strings.xml b/libs/WindowManager/Shell/res/values-lv/strings.xml
index e612ddd..a1fbcdd 100644
--- a/libs/WindowManager/Shell/res/values-lv/strings.xml
+++ b/libs/WindowManager/Shell/res/values-lv/strings.xml
@@ -32,13 +32,13 @@
     <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Mainīt lielumu"</string>
     <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Paslēpt"</string>
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Rādīt"</string>
-    <string name="dock_forced_resizable" msgid="1749750436092293116">"Iespējams, lietotne nedarbosies ekrāna sadalīšanas režīmā."</string>
-    <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Lietotnē netiek atbalstīta ekrāna sadalīšana."</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>
     <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Š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="703810061635792791">"Ekrāna sadalītājs"</string>
-    <string name="divider_title" msgid="5482989479865361192">"Ekrāna sadalītājs"</string>
+    <string name="accessibility_divider" msgid="6407584574218956849">"Ekrāna sadalītājs"</string>
+    <string name="divider_title" msgid="1963391955593749442">"Ekrāna sadalītājs"</string>
     <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Kreisā daļa pa visu ekrānu"</string>
     <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Pa kreisi 70%"</string>
     <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Pa kreisi 50%"</string>
@@ -49,14 +49,10 @@
     <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Augšdaļa 50%"</string>
     <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Augšdaļa 30%"</string>
     <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Apakšdaļu pa visu ekrānu"</string>
-    <!-- no translation found for accessibility_split_left (1713683765575562458) -->
-    <skip />
-    <!-- no translation found for accessibility_split_right (8441001008181296837) -->
-    <skip />
-    <!-- no translation found for accessibility_split_top (2789329702027147146) -->
-    <skip />
-    <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
-    <skip />
+    <string name="accessibility_split_left" msgid="1713683765575562458">"Sadalījums pa kreisi"</string>
+    <string name="accessibility_split_right" msgid="8441001008181296837">"Sadalījums pa labi"</string>
+    <string name="accessibility_split_top" msgid="2789329702027147146">"Sadalījums augšdaļā"</string>
+    <string name="accessibility_split_bottom" msgid="8694551025220868191">"Sadalījums apakšdaļā"</string>
     <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Vienas rokas režīma izmantošana"</string>
     <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Lai izietu, velciet augšup no ekrāna apakšdaļas vai pieskarieties jebkurā vietā virs lietotnes"</string>
     <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Pāriet vienas rokas režīmā"</string>
@@ -88,7 +84,7 @@
     <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Vai problēma netika novērsta?\nPieskarieties, lai atjaunotu."</string>
     <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Vai nav problēmu ar kameru? Pieskarieties, lai nerādītu."</string>
     <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Uzziniet un paveiciet vairāk"</string>
-    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Lai izmantotu sadalītu ekrānu, ievelciet vēl vienu lietotni"</string>
+    <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Lai izmantotu sadalītu ekrānu, ievelciet vēl vienu lietotni"</string>
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Lai pārvietotu lietotni, veiciet dubultskārienu ārpus lietotnes"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"Labi"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Izvērsiet, lai iegūtu plašāku informāciju."</string>
@@ -97,14 +93,21 @@
     <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Atcelt"</string>
     <string name="letterbox_restart_restart" msgid="8529976234412442973">"Restartēt"</string>
     <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Vairs nerādīt"</string>
+    <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Veiciet dubultskārienu,\nlai pārvietotu šo lietotni"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"Maksimizēt"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimizēt"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Aizvērt"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Atpakaļ"</string>
     <string name="handle_text" msgid="1766582106752184456">"Turis"</string>
+    <string name="app_icon_text" msgid="2823268023931811747">"Lietotnes ikona"</string>
     <string name="fullscreen_text" msgid="1162316685217676079">"Pilnekrāna režīms"</string>
     <string name="desktop_text" msgid="1077633567027630454">"Darbvirsmas režīms"</string>
     <string name="split_screen_text" msgid="1396336058129570886">"Sadalīt ekrānu"</string>
     <string name="more_button_text" msgid="3655388105592893530">"Vairāk"</string>
     <string name="float_button_text" msgid="9221657008391364581">"Peldošs"</string>
+    <string name="select_text" msgid="5139083974039906583">"Atlasīt"</string>
+    <string name="screenshot_text" msgid="1477704010087786671">"Ekrānuzņēmums"</string>
+    <string name="close_text" msgid="4986518933445178928">"Aizvērt"</string>
+    <string name="collapse_menu_text" msgid="7515008122450342029">"Aizvērt izvēlni"</string>
+    <string name="expand_menu_text" msgid="3847736164494181168">"Atvērt izvēlni"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-mk/strings.xml b/libs/WindowManager/Shell/res/values-mk/strings.xml
index 3c5449c..1567d61 100644
--- a/libs/WindowManager/Shell/res/values-mk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-mk/strings.xml
@@ -32,13 +32,13 @@
     <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Промени големина"</string>
     <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Сокријте"</string>
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Прикажете"</string>
-    <string name="dock_forced_resizable" msgid="1749750436092293116">"Апликацијата може да не работи со поделен екран."</string>
-    <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Апликацијата не поддржува поделен екран."</string>
+    <string name="dock_forced_resizable" msgid="7429086980048964687">"Апликацијата можеби нема да работи со поделен екран"</string>
+    <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Апликацијата не поддржува поделен екран"</string>
     <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Апликацијава може да се отвори само во еден прозорец."</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="703810061635792791">"Разделник на поделен екран"</string>
-    <string name="divider_title" msgid="5482989479865361192">"Разделник на поделен екран"</string>
+    <string name="accessibility_divider" msgid="6407584574218956849">"Разделник на поделен екран"</string>
+    <string name="divider_title" msgid="1963391955593749442">"Разделник на поделен екран"</string>
     <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Левиот на цел екран"</string>
     <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Левиот 70%"</string>
     <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Левиот 50%"</string>
@@ -49,14 +49,10 @@
     <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Горниот 50%"</string>
     <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Горниот 30%"</string>
     <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Долниот на цел екран"</string>
-    <!-- no translation found for accessibility_split_left (1713683765575562458) -->
-    <skip />
-    <!-- no translation found for accessibility_split_right (8441001008181296837) -->
-    <skip />
-    <!-- no translation found for accessibility_split_top (2789329702027147146) -->
-    <skip />
-    <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
-    <skip />
+    <string name="accessibility_split_left" msgid="1713683765575562458">"Подели налево"</string>
+    <string name="accessibility_split_right" msgid="8441001008181296837">"Подели надесно"</string>
+    <string name="accessibility_split_top" msgid="2789329702027147146">"Подели нагоре"</string>
+    <string name="accessibility_split_bottom" msgid="8694551025220868191">"Подели долу"</string>
     <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Користење на режимот со една рака"</string>
     <string name="one_handed_tutorial_description" msgid="3486582858591353067">"За да излезете, повлечете нагоре од дното на екранот или допрете каде било над апликацијата"</string>
     <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Започни го режимот со една рака"</string>
@@ -83,12 +79,12 @@
     <string name="notification_bubble_title" msgid="6082910224488253378">"Балонче"</string>
     <string name="manage_bubbles_text" msgid="7730624269650594419">"Управувајте"</string>
     <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Балончето е отфрлено."</string>
-    <string name="restart_button_description" msgid="6712141648865547958">"Допрете за да ја рестартирате апликацијава за подобар приказ."</string>
+    <string name="restart_button_description" msgid="6712141648865547958">"За подобар приказ, допрете за да ја рестартирате апликацијава."</string>
     <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Проблеми со камерата?\nДопрете за да се совпадне повторно"</string>
     <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Не се поправи?\nДопрете за враќање"</string>
     <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Нема проблеми со камерата? Допрете за отфрлање."</string>
     <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Погледнете и направете повеќе"</string>
-    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Повлечете во друга апликација за поделен екран"</string>
+    <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Повлечете друга апликација за поделен екран"</string>
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Допрете двапати надвор од некоја апликација за да ја преместите"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"Сфатив"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Проширете за повеќе информации."</string>
@@ -97,14 +93,21 @@
     <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Откажи"</string>
     <string name="letterbox_restart_restart" msgid="8529976234412442973">"Рестартирај"</string>
     <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Не прикажувај повторно"</string>
+    <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Допрете двапати за да ја\nпоместите апликацијава"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"Зголеми"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Минимизирај"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Затвори"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Назад"</string>
     <string name="handle_text" msgid="1766582106752184456">"Прекар"</string>
+    <string name="app_icon_text" msgid="2823268023931811747">"Икона на апликацијата"</string>
     <string name="fullscreen_text" msgid="1162316685217676079">"Цел екран"</string>
     <string name="desktop_text" msgid="1077633567027630454">"Режим за компјутер"</string>
     <string name="split_screen_text" msgid="1396336058129570886">"Поделен екран"</string>
     <string name="more_button_text" msgid="3655388105592893530">"Повеќе"</string>
     <string name="float_button_text" msgid="9221657008391364581">"Лебдечко"</string>
+    <string name="select_text" msgid="5139083974039906583">"Изберете"</string>
+    <string name="screenshot_text" msgid="1477704010087786671">"Слика од екранот"</string>
+    <string name="close_text" msgid="4986518933445178928">"Затворете"</string>
+    <string name="collapse_menu_text" msgid="7515008122450342029">"Затворете го менито"</string>
+    <string name="expand_menu_text" msgid="3847736164494181168">"Отвори го менито"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-ml/strings.xml b/libs/WindowManager/Shell/res/values-ml/strings.xml
index 0df5c3a..5cca248 100644
--- a/libs/WindowManager/Shell/res/values-ml/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ml/strings.xml
@@ -32,13 +32,13 @@
     <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"വലുപ്പം മാറ്റുക"</string>
     <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"സ്റ്റാഷ് ചെയ്യൽ"</string>
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"അൺസ്റ്റാഷ് ചെയ്യൽ"</string>
-    <string name="dock_forced_resizable" msgid="1749750436092293116">"സ്‌ക്രീൻ വിഭജന മോഡിൽ ആപ്പ് പ്രവർത്തിച്ചേക്കില്ല."</string>
-    <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"സ്പ്ലിറ്റ്-സ്ക്രീനിനെ ആപ്പ് പിന്തുണയ്ക്കുന്നില്ല."</string>
+    <string name="dock_forced_resizable" msgid="7429086980048964687">"സ്‌ക്രീൻ വിഭജന മോഡിൽ ആപ്പ് പ്രവർത്തിച്ചേക്കില്ല"</string>
+    <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"സ്‌ക്രീൻ വിഭജന മോഡിനെ ആപ്പ് പിന്തുണയ്ക്കുന്നില്ല"</string>
     <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"ഈ ആപ്പ് ഒരു വിൻഡോയിൽ മാത്രമേ തുറക്കാനാകൂ."</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="703810061635792791">"സ്പ്ലിറ്റ്-സ്ക്രീൻ ഡിവൈഡർ"</string>
-    <string name="divider_title" msgid="5482989479865361192">"സ്‌ക്രീൻ വിഭജന മോഡ് ഡിവൈഡർ"</string>
+    <string name="accessibility_divider" msgid="6407584574218956849">"സ്‌ക്രീൻ വിഭജന മോഡ് ഡിവൈഡർ"</string>
+    <string name="divider_title" msgid="1963391955593749442">"സ്‌ക്രീൻ വിഭജന മോഡ് ഡിവൈഡർ"</string>
     <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"ഇടത് പൂർണ്ണ സ്ക്രീൻ"</string>
     <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"ഇടത് 70%"</string>
     <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"ഇടത് 50%"</string>
@@ -49,14 +49,10 @@
     <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"മുകളിൽ 50%"</string>
     <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"മുകളിൽ 30%"</string>
     <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"താഴെ പൂർണ്ണ സ്ക്രീൻ"</string>
-    <!-- no translation found for accessibility_split_left (1713683765575562458) -->
-    <skip />
-    <!-- no translation found for accessibility_split_right (8441001008181296837) -->
-    <skip />
-    <!-- no translation found for accessibility_split_top (2789329702027147146) -->
-    <skip />
-    <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
-    <skip />
+    <string name="accessibility_split_left" msgid="1713683765575562458">"ഇടത് ഭാഗത്തേക്ക് വിഭജിക്കുക"</string>
+    <string name="accessibility_split_right" msgid="8441001008181296837">"വലത് ഭാഗത്തേക്ക് വിഭജിക്കുക"</string>
+    <string name="accessibility_split_top" msgid="2789329702027147146">"മുകളിലേക്ക് വിഭജിക്കുക"</string>
+    <string name="accessibility_split_bottom" msgid="8694551025220868191">"താഴേക്ക് വിഭജിക്കുക"</string>
     <string name="one_handed_tutorial_title" msgid="4583241688067426350">"ഒറ്റക്കൈ മോഡ് എങ്ങനെ ഉപയോഗിക്കാം"</string>
     <string name="one_handed_tutorial_description" msgid="3486582858591353067">"പുറത്ത് കടക്കാൻ, സ്ക്രീനിന്റെ ചുവടെ നിന്ന് മുകളിലേക്ക് സ്വൈപ്പ് ചെയ്യുക അല്ലെങ്കിൽ ആപ്പിന് മുകളിലായി എവിടെയെങ്കിലും ടാപ്പ് ചെയ്യുക"</string>
     <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"ഒറ്റക്കൈ മോഡ് ആരംഭിച്ചു"</string>
@@ -88,7 +84,7 @@
     <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"അത് പരിഹരിച്ചില്ലേ?\nപുനഃസ്ഥാപിക്കാൻ ടാപ്പ് ചെയ്യുക"</string>
     <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"ക്യാമറാ പ്രശ്നങ്ങളൊന്നുമില്ലേ? നിരസിക്കാൻ ടാപ്പ് ചെയ്യുക."</string>
     <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"കൂടുതൽ കാണുക, ചെയ്യുക"</string>
-    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"സ്‌ക്രീൻ വിഭജന മോഡിന്, മറ്റൊരു ആപ്പ് വലിച്ചിടുക"</string>
+    <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"സ്‌ക്രീൻ വിഭജന മോഡിന്, മറ്റൊരു ആപ്പ് വലിച്ചിടുക"</string>
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"ആപ്പിന്റെ സ്ഥാനം മാറ്റാൻ അതിന് പുറത്ത് ഡബിൾ ടാപ്പ് ചെയ്യുക"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"മനസ്സിലായി"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"കൂടുതൽ വിവരങ്ങൾക്ക് വികസിപ്പിക്കുക."</string>
@@ -97,14 +93,21 @@
     <string name="letterbox_restart_cancel" msgid="1342209132692537805">"റദ്ദാക്കുക"</string>
     <string name="letterbox_restart_restart" msgid="8529976234412442973">"റീസ്റ്റാർട്ട് ചെയ്യൂ"</string>
     <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"വീണ്ടും കാണിക്കരുത്"</string>
+    <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"ഈ ആപ്പ് നീക്കാൻ\nഡബിൾ ടാപ്പ് ചെയ്യുക"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"വലുതാക്കുക"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"ചെറുതാക്കുക"</string>
     <string name="close_button_text" msgid="2913281996024033299">"അടയ്ക്കുക"</string>
     <string name="back_button_text" msgid="1469718707134137085">"മടങ്ങുക"</string>
     <string name="handle_text" msgid="1766582106752184456">"ഹാൻഡിൽ"</string>
+    <string name="app_icon_text" msgid="2823268023931811747">"ആപ്പ് ഐക്കൺ"</string>
     <string name="fullscreen_text" msgid="1162316685217676079">"പൂർണ്ണസ്ക്രീൻ"</string>
     <string name="desktop_text" msgid="1077633567027630454">"ഡെസ്‌ക്ടോപ്പ് മോഡ്"</string>
     <string name="split_screen_text" msgid="1396336058129570886">"സ്‌ക്രീൻ വിഭജനം"</string>
     <string name="more_button_text" msgid="3655388105592893530">"കൂടുതൽ"</string>
     <string name="float_button_text" msgid="9221657008391364581">"ഫ്ലോട്ട്"</string>
+    <string name="select_text" msgid="5139083974039906583">"തിരഞ്ഞെടുക്കുക"</string>
+    <string name="screenshot_text" msgid="1477704010087786671">"സ്ക്രീൻഷോട്ട്"</string>
+    <string name="close_text" msgid="4986518933445178928">"അടയ്ക്കുക"</string>
+    <string name="collapse_menu_text" msgid="7515008122450342029">"മെനു അടയ്ക്കുക"</string>
+    <string name="expand_menu_text" msgid="3847736164494181168">"മെനു തുറക്കുക"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-mn/strings.xml b/libs/WindowManager/Shell/res/values-mn/strings.xml
index 5f9d3db..72e54fc 100644
--- a/libs/WindowManager/Shell/res/values-mn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-mn/strings.xml
@@ -32,13 +32,13 @@
     <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Хэмжээг өөрчлөх"</string>
     <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Нуух"</string>
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Ил гаргах"</string>
-    <string name="dock_forced_resizable" msgid="1749750436092293116">"Апп хуваагдсан дэлгэц дээр ажиллахгүй байж болзошгүй."</string>
-    <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Энэ апп нь дэлгэц хуваах тохиргоог дэмждэггүй."</string>
+    <string name="dock_forced_resizable" msgid="7429086980048964687">"Апп дэлгэцийг хуваах горимтой ажиллахгүй байж магадгүй"</string>
+    <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Апп дэлгэцийг хуваах горимыг дэмждэггүй"</string>
     <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Энэ аппыг зөвхөн 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="703810061635792791">"\"Дэлгэц хуваах\" хуваагч"</string>
-    <string name="divider_title" msgid="5482989479865361192">"\"Дэлгэцийг хуваах\" хуваагч"</string>
+    <string name="accessibility_divider" msgid="6407584574218956849">"Дэлгэцийг хуваах хуваагч"</string>
+    <string name="divider_title" msgid="1963391955593749442">"Дэлгэцийг хуваах хуваагч"</string>
     <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Зүүн талын бүтэн дэлгэц"</string>
     <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Зүүн 70%"</string>
     <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Зүүн 50%"</string>
@@ -49,14 +49,10 @@
     <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Дээд 50%"</string>
     <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Дээд 30%"</string>
     <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Доод бүтэн дэлгэц"</string>
-    <!-- no translation found for accessibility_split_left (1713683765575562458) -->
-    <skip />
-    <!-- no translation found for accessibility_split_right (8441001008181296837) -->
-    <skip />
-    <!-- no translation found for accessibility_split_top (2789329702027147146) -->
-    <skip />
-    <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
-    <skip />
+    <string name="accessibility_split_left" msgid="1713683765575562458">"Зүүн талд хуваах"</string>
+    <string name="accessibility_split_right" msgid="8441001008181296837">"Баруун талд хуваах"</string>
+    <string name="accessibility_split_top" msgid="2789329702027147146">"Дээд талд хуваах"</string>
+    <string name="accessibility_split_bottom" msgid="8694551025220868191">"Доод талд хуваах"</string>
     <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Нэг гарын горимыг ашиглаж байна"</string>
     <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Гарахын тулд дэлгэцийн доод хэсгээс дээш шударч эсвэл апп дээр хүссэн газраа товшино уу"</string>
     <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Нэг гарын горимыг эхлүүлэх"</string>
@@ -88,7 +84,7 @@
     <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Үүнийг засаагүй юу?\nБуцаахын тулд товшино уу"</string>
     <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Камерын асуудал байхгүй юу? Хаахын тулд товшино уу."</string>
     <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Харж илүү ихийг хий"</string>
-    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Дэлгэцийг хуваахын тулд өөр апп руу чирнэ үү"</string>
+    <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Дэлгэц хуваах горимд ашиглахын тулд өөр аппыг чирнэ үү"</string>
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Аппыг дахин байрлуулахын тулд гадна талд нь хоёр товшино"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"Ойлголоо"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Нэмэлт мэдээлэл авах бол дэлгэнэ үү."</string>
@@ -97,14 +93,21 @@
     <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Цуцлах"</string>
     <string name="letterbox_restart_restart" msgid="8529976234412442973">"Дахин эхлүүлэх"</string>
     <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Дахиж бүү харуул"</string>
+    <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Энэ аппыг зөөхийн тулд\nхоёр товшино уу"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"Томруулах"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Багасгах"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Хаах"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Буцах"</string>
     <string name="handle_text" msgid="1766582106752184456">"Бариул"</string>
+    <string name="app_icon_text" msgid="2823268023931811747">"Aппын дүрс тэмдэг"</string>
     <string name="fullscreen_text" msgid="1162316685217676079">"Бүтэн дэлгэц"</string>
     <string name="desktop_text" msgid="1077633567027630454">"Дэлгэцийн горим"</string>
     <string name="split_screen_text" msgid="1396336058129570886">"Дэлгэцийг хуваах"</string>
     <string name="more_button_text" msgid="3655388105592893530">"Бусад"</string>
     <string name="float_button_text" msgid="9221657008391364581">"Хөвөгч"</string>
+    <string name="select_text" msgid="5139083974039906583">"Сонгох"</string>
+    <string name="screenshot_text" msgid="1477704010087786671">"Дэлгэцийн агшин"</string>
+    <string name="close_text" msgid="4986518933445178928">"Хаах"</string>
+    <string name="collapse_menu_text" msgid="7515008122450342029">"Цэсийг хаах"</string>
+    <string name="expand_menu_text" msgid="3847736164494181168">"Цэс нээх"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-mr/strings.xml b/libs/WindowManager/Shell/res/values-mr/strings.xml
index 4e29c11b..a9e6319a 100644
--- a/libs/WindowManager/Shell/res/values-mr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-mr/strings.xml
@@ -32,13 +32,13 @@
     <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"आकार बदला"</string>
     <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"स्टॅश करा"</string>
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"अनस्टॅश करा"</string>
-    <string name="dock_forced_resizable" msgid="1749750436092293116">"अ‍ॅप कदाचित स्प्लिट स्क्रीनसह काम करू शकत नाही."</string>
-    <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"अ‍ॅप स्क्रीन-विभाजनास समर्थन देत नाही."</string>
+    <string name="dock_forced_resizable" msgid="7429086980048964687">"अ‍ॅप कदाचित स्प्लिट स्क्रीनसह काम करणार नाही"</string>
+    <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"अ‍ॅप हे स्प्लिट स्क्रीनला सपोर्ट करत नाही"</string>
     <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"हे अ‍ॅप फक्त एका विंडोमध्ये उघडले जाऊ शकते."</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="703810061635792791">"विभाजित-स्क्रीन विभाजक"</string>
-    <string name="divider_title" msgid="5482989479865361192">"स्प्लिट-स्क्रीन विभाजक"</string>
+    <string name="accessibility_divider" msgid="6407584574218956849">"स्प्लिट स्क्रीन विभाजक"</string>
+    <string name="divider_title" msgid="1963391955593749442">"स्प्लिट स्क्रीन विभाजक"</string>
     <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"डावी फुल स्क्रीन"</string>
     <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"डावी 70%"</string>
     <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"डावी 50%"</string>
@@ -49,14 +49,10 @@
     <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"शीर्ष 50%"</string>
     <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"शीर्ष 10"</string>
     <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"तळाशी फुल स्क्रीन"</string>
-    <!-- no translation found for accessibility_split_left (1713683765575562458) -->
-    <skip />
-    <!-- no translation found for accessibility_split_right (8441001008181296837) -->
-    <skip />
-    <!-- no translation found for accessibility_split_top (2789329702027147146) -->
-    <skip />
-    <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
-    <skip />
+    <string name="accessibility_split_left" msgid="1713683765575562458">"डावीकडे स्प्लिट करा"</string>
+    <string name="accessibility_split_right" msgid="8441001008181296837">"उजवीकडे स्प्लिट करा"</string>
+    <string name="accessibility_split_top" msgid="2789329702027147146">"सर्वात वरती स्प्लिट करा"</string>
+    <string name="accessibility_split_bottom" msgid="8694551025220868191">"खालती स्प्लिट करा"</string>
     <string name="one_handed_tutorial_title" msgid="4583241688067426350">"एकहाती मोड वापरणे"</string>
     <string name="one_handed_tutorial_description" msgid="3486582858591353067">"बाहेर पडण्यासाठी स्क्रीनच्या खालून वरच्या दिशेने स्वाइप करा किंवा ॲपवर कोठेही टॅप करा"</string>
     <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"एकहाती मोड सुरू करा"</string>
@@ -88,7 +84,7 @@
     <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"निराकरण झाले नाही?\nरिव्हर्ट करण्यासाठी कृपया टॅप करा"</string>
     <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"कॅमेराशी संबंधित कोणत्याही समस्या नाहीत का? डिसमिस करण्‍यासाठी टॅप करा."</string>
     <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"पहा आणि आणखी बरेच काही करा"</string>
-    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"स्प्लिट-स्क्रीन वापरण्यासाठी दुसऱ्या ॲपमध्ये ड्रॅग करा"</string>
+    <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"स्प्लिट स्क्रीन वापरण्यासाठी दुसरे ॲप ड्रॅग करा"</string>
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"ॲपची स्थिती पुन्हा बदलण्यासाठी, त्याच्या बाहेर दोनदा टॅप करा"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"समजले"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"अधिक माहितीसाठी विस्तार करा."</string>
@@ -97,14 +93,21 @@
     <string name="letterbox_restart_cancel" msgid="1342209132692537805">"रद्द करा"</string>
     <string name="letterbox_restart_restart" msgid="8529976234412442973">"रीस्टार्ट करा"</string>
     <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"पुन्हा दाखवू नका"</string>
+    <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"हे ॲप हलवण्यासाठी\nदोनदा टॅप करा"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"मोठे करा"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"लहान करा"</string>
     <string name="close_button_text" msgid="2913281996024033299">"बंद करा"</string>
     <string name="back_button_text" msgid="1469718707134137085">"मागे जा"</string>
     <string name="handle_text" msgid="1766582106752184456">"हँडल"</string>
+    <string name="app_icon_text" msgid="2823268023931811747">"अ‍ॅप आयकन"</string>
     <string name="fullscreen_text" msgid="1162316685217676079">"फुलस्‍क्रीन"</string>
     <string name="desktop_text" msgid="1077633567027630454">"डेस्कटॉप मोड"</string>
     <string name="split_screen_text" msgid="1396336058129570886">"स्प्लिट स्क्रीन"</string>
     <string name="more_button_text" msgid="3655388105592893530">"आणखी"</string>
     <string name="float_button_text" msgid="9221657008391364581">"फ्लोट"</string>
+    <string name="select_text" msgid="5139083974039906583">"निवडा"</string>
+    <string name="screenshot_text" msgid="1477704010087786671">"स्क्रीनशॉट"</string>
+    <string name="close_text" msgid="4986518933445178928">"बंद करा"</string>
+    <string name="collapse_menu_text" msgid="7515008122450342029">"मेनू बंद करा"</string>
+    <string name="expand_menu_text" msgid="3847736164494181168">"मेनू उघडा"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-mr/strings_tv.xml b/libs/WindowManager/Shell/res/values-mr/strings_tv.xml
index 8a89779..89654d0 100644
--- a/libs/WindowManager/Shell/res/values-mr/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-mr/strings_tv.xml
@@ -24,7 +24,7 @@
     <string name="pip_move" msgid="158770205886688553">"हलवा"</string>
     <string name="pip_expand" msgid="1051966011679297308">"विस्तार करा"</string>
     <string name="pip_collapse" msgid="3903295106641385962">"कोलॅप्स करा"</string>
-    <string name="pip_edu_text" msgid="7930546669915337998">"नियंत्रणांसाठी "<annotation icon="home_icon">"होम"</annotation>" दोनदा दाबा"</string>
+    <string name="pip_edu_text" msgid="7930546669915337998">"नियंत्रणांसाठी "<annotation icon="home_icon">"होम"</annotation>" दोनदा प्रेस करा"</string>
     <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"चित्रात-चित्र मेनू."</string>
     <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"डावीकडे हलवा"</string>
     <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"उजवीकडे हलवा"</string>
diff --git a/libs/WindowManager/Shell/res/values-ms/strings.xml b/libs/WindowManager/Shell/res/values-ms/strings.xml
index 2784472..b475317 100644
--- a/libs/WindowManager/Shell/res/values-ms/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ms/strings.xml
@@ -32,13 +32,13 @@
     <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Ubah saiz"</string>
     <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Sembunyikan"</string>
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Tunjukkan"</string>
-    <string name="dock_forced_resizable" msgid="1749750436092293116">"Apl mungkin tidak berfungsi dengan skrin pisah."</string>
-    <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Apl tidak menyokong skrin pisah."</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>
     <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"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="703810061635792791">"Pembahagi skrin pisah"</string>
-    <string name="divider_title" msgid="5482989479865361192">"Pembahagi skrin pisah"</string>
+    <string name="accessibility_divider" msgid="6407584574218956849">"Pembahagi skrin pisah"</string>
+    <string name="divider_title" msgid="1963391955593749442">"Pembahagi skrin pisah"</string>
     <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Skrin penuh kiri"</string>
     <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Kiri 70%"</string>
     <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Kiri 50%"</string>
@@ -49,14 +49,10 @@
     <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Atas 50%"</string>
     <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Atas 30%"</string>
     <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Skrin penuh bawah"</string>
-    <!-- no translation found for accessibility_split_left (1713683765575562458) -->
-    <skip />
-    <!-- no translation found for accessibility_split_right (8441001008181296837) -->
-    <skip />
-    <!-- no translation found for accessibility_split_top (2789329702027147146) -->
-    <skip />
-    <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
-    <skip />
+    <string name="accessibility_split_left" msgid="1713683765575562458">"Pisah kiri"</string>
+    <string name="accessibility_split_right" msgid="8441001008181296837">"Pisah kanan"</string>
+    <string name="accessibility_split_top" msgid="2789329702027147146">"Pisah atas"</string>
+    <string name="accessibility_split_bottom" msgid="8694551025220868191">"Pisah bawah"</string>
     <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Menggunakan mod sebelah tangan"</string>
     <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Untuk keluar, leret ke atas daripada bahagian bawah skrin atau ketik pada mana-mana di bahagian atas apl"</string>
     <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Mulakan mod sebelah tangan"</string>
@@ -88,7 +84,7 @@
     <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Isu tidak dibetulkan?\nKetik untuk kembali"</string>
     <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Tiada isu kamera? Ketik untuk mengetepikan."</string>
     <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Lihat dan lakukan lebih"</string>
-    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Seret apl lain untuk skrin pisah"</string>
+    <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Seret masuk apl lain untuk menggunakan skrin pisah"</string>
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Ketik dua kali di luar apl untuk menempatkan semula apl itu"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Kembangkan untuk mendapatkan maklumat lanjut."</string>
@@ -97,14 +93,21 @@
     <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Batal"</string>
     <string name="letterbox_restart_restart" msgid="8529976234412442973">"Mulakan semula"</string>
     <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Jangan tunjukkan lagi"</string>
+    <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Ketik dua kali untuk\nalih apl ini"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"Maksimumkan"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimumkan"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Tutup"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Kembali"</string>
     <string name="handle_text" msgid="1766582106752184456">"Pemegang"</string>
+    <string name="app_icon_text" msgid="2823268023931811747">"Ikon Apl"</string>
     <string name="fullscreen_text" msgid="1162316685217676079">"Skrin penuh"</string>
     <string name="desktop_text" msgid="1077633567027630454">"Mod Desktop"</string>
     <string name="split_screen_text" msgid="1396336058129570886">"Skrin Pisah"</string>
     <string name="more_button_text" msgid="3655388105592893530">"Lagi"</string>
     <string name="float_button_text" msgid="9221657008391364581">"Terapung"</string>
+    <string name="select_text" msgid="5139083974039906583">"Pilih"</string>
+    <string name="screenshot_text" msgid="1477704010087786671">"Tangkapan skrin"</string>
+    <string name="close_text" msgid="4986518933445178928">"Tutup"</string>
+    <string name="collapse_menu_text" msgid="7515008122450342029">"Tutup Menu"</string>
+    <string name="expand_menu_text" msgid="3847736164494181168">"Buka Menu"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-my/strings.xml b/libs/WindowManager/Shell/res/values-my/strings.xml
index 2b78106..cb6a1df 100644
--- a/libs/WindowManager/Shell/res/values-my/strings.xml
+++ b/libs/WindowManager/Shell/res/values-my/strings.xml
@@ -32,13 +32,13 @@
     <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"အရွယ်အစားပြောင်းရန်"</string>
     <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"သိုဝှက်ရန်"</string>
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"မသိုဝှက်ရန်"</string>
-    <string name="dock_forced_resizable" msgid="1749750436092293116">"မျက်နှာပြင် ခွဲ၍ပြသခြင်းဖြင့် အက်ပ်သည် အလုပ်မလုပ်ပါ။"</string>
-    <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"အက်ပ်သည် မျက်နှာပြင်ခွဲပြရန် ပံ့ပိုးထားခြင်းမရှိပါ။"</string>
+    <string name="dock_forced_resizable" msgid="7429086980048964687">"မျက်နှာပြင် ခွဲ၍ပြသခြင်းဖြင့် အက်ပ်သည် အလုပ်မလုပ်ပါ"</string>
+    <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"အက်ပ်တွင် မျက်နှာပြင် ခွဲ၍ပြသခြင်းကို မပံ့ပိုးပါ"</string>
     <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"ဤအက်ပ်ကို ဝင်းဒိုး ၁ ခုတွင်သာ ဖွင့်နိုင်သည်။"</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="703810061635792791">"မျက်နှာပြင်ခွဲခြမ်း ပိုင်းခြားပေးသည့်စနစ်"</string>
-    <string name="divider_title" msgid="5482989479865361192">"မျက်နှာပြင်ခွဲ၍ပြသသည့် စနစ်"</string>
+    <string name="accessibility_divider" msgid="6407584574218956849">"မျက်နှာပြင် ခွဲ၍ပြသခြင်း ပိုင်းခြားစနစ်"</string>
+    <string name="divider_title" msgid="1963391955593749442">"မျက်နှာပြင် ခွဲ၍ပြသခြင်း ပိုင်းခြားစနစ်"</string>
     <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"ဘယ်ဘက် မျက်နှာပြင်အပြည့်"</string>
     <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"ဘယ်ဘက်မျက်နှာပြင် ၇၀%"</string>
     <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"ဘယ်ဘက် မျက်နှာပြင် ၅၀%"</string>
@@ -49,14 +49,10 @@
     <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"အပေါ်ဘက် မျက်နှာပြင် ၅၀%"</string>
     <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"အပေါ်ဘက် မျက်နှာပြင် ၃၀%"</string>
     <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"အောက်ခြေ မျက်နှာပြင်အပြည့်"</string>
-    <!-- no translation found for accessibility_split_left (1713683765575562458) -->
-    <skip />
-    <!-- no translation found for accessibility_split_right (8441001008181296837) -->
-    <skip />
-    <!-- no translation found for accessibility_split_top (2789329702027147146) -->
-    <skip />
-    <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
-    <skip />
+    <string name="accessibility_split_left" msgid="1713683765575562458">"ဘယ်ဘက်ကို ခွဲရန်"</string>
+    <string name="accessibility_split_right" msgid="8441001008181296837">"ညာဘက်ကို ခွဲရန်"</string>
+    <string name="accessibility_split_top" msgid="2789329702027147146">"ထိပ်ပိုင်းကို ခွဲရန်"</string>
+    <string name="accessibility_split_bottom" msgid="8694551025220868191">"အောက်ခြေကို ခွဲရန်"</string>
     <string name="one_handed_tutorial_title" msgid="4583241688067426350">"လက်တစ်ဖက်သုံးမုဒ် အသုံးပြုခြင်း"</string>
     <string name="one_handed_tutorial_description" msgid="3486582858591353067">"ထွက်ရန် ဖန်သားပြင်၏အောက်ခြေမှ အပေါ်သို့ပွတ်ဆွဲပါ သို့မဟုတ် အက်ပ်အပေါ်ဘက် မည်သည့်နေရာတွင်မဆို တို့ပါ"</string>
     <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"လက်တစ်ဖက်သုံးမုဒ်ကို စတင်လိုက်သည်"</string>
@@ -88,7 +84,7 @@
     <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"ကောင်းမသွားဘူးလား။\nပြန်ပြောင်းရန် တို့ပါ"</string>
     <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"ကင်မရာပြဿနာ မရှိဘူးလား။ ပယ်ရန် တို့ပါ။"</string>
     <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"ကြည့်ပြီး ပိုမိုလုပ်ဆောင်ပါ"</string>
-    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"မျက်နှာပြင် ခွဲ၍ပြသနိုင်ရန် နောက်အက်ပ်တစ်ခုကို ဖိဆွဲပါ"</string>
+    <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"မျက်နှာပြင် ခွဲ၍ပြသခြင်းအတွက် အက်ပ်နောက်တစ်ခုကို ဖိဆွဲပါ"</string>
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"နေရာပြန်ချရန် အက်ပ်အပြင်ဘက်ကို နှစ်ချက်တို့ပါ"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"နားလည်ပြီ"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"နောက်ထပ်အချက်အလက်များအတွက် ချဲ့နိုင်သည်။"</string>
@@ -97,14 +93,21 @@
     <string name="letterbox_restart_cancel" msgid="1342209132692537805">"မလုပ်တော့"</string>
     <string name="letterbox_restart_restart" msgid="8529976234412442973">"ပြန်စရန်"</string>
     <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"နောက်ထပ်မပြပါနှင့်"</string>
+    <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"ဤအက်ပ်ကို ရွှေ့ရန်\nနှစ်ချက်တို့ပါ"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"ချဲ့ရန်"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"ချုံ့ရန်"</string>
     <string name="close_button_text" msgid="2913281996024033299">"ပိတ်ရန်"</string>
     <string name="back_button_text" msgid="1469718707134137085">"နောက်သို့"</string>
     <string name="handle_text" msgid="1766582106752184456">"သုံးသူအမည်"</string>
+    <string name="app_icon_text" msgid="2823268023931811747">"အက်ပ်သင်္ကေတ"</string>
     <string name="fullscreen_text" msgid="1162316685217676079">"ဖန်သားပြင်အပြည့်"</string>
     <string name="desktop_text" msgid="1077633567027630454">"ဒက်စ်တော့မုဒ်"</string>
     <string name="split_screen_text" msgid="1396336058129570886">"မျက်နှာပြင် ခွဲ၍ပြသရန်"</string>
     <string name="more_button_text" msgid="3655388105592893530">"ပိုပြပါ"</string>
     <string name="float_button_text" msgid="9221657008391364581">"မျှောရန်"</string>
+    <string name="select_text" msgid="5139083974039906583">"ရွေးရန်"</string>
+    <string name="screenshot_text" msgid="1477704010087786671">"ဖန်သားပြင်ဓာတ်ပုံ"</string>
+    <string name="close_text" msgid="4986518933445178928">"ပိတ်ရန်"</string>
+    <string name="collapse_menu_text" msgid="7515008122450342029">"မီနူး ပိတ်ရန်"</string>
+    <string name="expand_menu_text" msgid="3847736164494181168">"မီနူး ဖွင့်ရန်"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-nb/strings.xml b/libs/WindowManager/Shell/res/values-nb/strings.xml
index 3e7c63a..6c80144 100644
--- a/libs/WindowManager/Shell/res/values-nb/strings.xml
+++ b/libs/WindowManager/Shell/res/values-nb/strings.xml
@@ -32,13 +32,13 @@
     <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Endre størrelse"</string>
     <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Oppbevar"</string>
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Avslutt oppbevaring"</string>
-    <string name="dock_forced_resizable" msgid="1749750436092293116">"Det kan hende at appen ikke fungerer med delt skjerm."</string>
-    <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Appen støtter ikke delt skjerm."</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>
     <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"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="703810061635792791">"Skilleelement for delt skjerm"</string>
-    <string name="divider_title" msgid="5482989479865361192">"Skilleelement for delt skjerm"</string>
+    <string name="accessibility_divider" msgid="6407584574218956849">"Skilleelement for delt skjerm"</string>
+    <string name="divider_title" msgid="1963391955593749442">"Skilleelement for delt skjerm"</string>
     <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Utvid den venstre delen av skjermen til hele skjermen"</string>
     <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Sett størrelsen på den venstre delen av skjermen til 70 %"</string>
     <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Sett størrelsen på den venstre delen av skjermen til 50 %"</string>
@@ -49,14 +49,10 @@
     <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Sett størrelsen på den øverste delen av skjermen til 50 %"</string>
     <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Sett størrelsen på den øverste delen av skjermen til 30 %"</string>
     <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Utvid den nederste delen av skjermen til hele skjermen"</string>
-    <!-- no translation found for accessibility_split_left (1713683765575562458) -->
-    <skip />
-    <!-- no translation found for accessibility_split_right (8441001008181296837) -->
-    <skip />
-    <!-- no translation found for accessibility_split_top (2789329702027147146) -->
-    <skip />
-    <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
-    <skip />
+    <string name="accessibility_split_left" msgid="1713683765575562458">"Del opp til venstre"</string>
+    <string name="accessibility_split_right" msgid="8441001008181296837">"Del opp til høyre"</string>
+    <string name="accessibility_split_top" msgid="2789329702027147146">"Del opp øverst"</string>
+    <string name="accessibility_split_bottom" msgid="8694551025220868191">"Del opp nederst"</string>
     <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Bruk av enhåndsmodus"</string>
     <string name="one_handed_tutorial_description" msgid="3486582858591353067">"For å avslutte, sveip opp fra bunnen av skjermen eller trykk hvor som helst over appen"</string>
     <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Start enhåndsmodus"</string>
@@ -88,7 +84,7 @@
     <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Ble ikke problemet løst?\nTrykk for å gå tilbake"</string>
     <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Har du ingen kameraproblemer? Trykk for å lukke."</string>
     <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Se og gjør mer"</string>
-    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Dra inn en annen app for å bruke delt skjerm"</string>
+    <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Dra inn en annen app for å bruke delt skjerm"</string>
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Dobbelttrykk utenfor en app for å flytte den"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"Greit"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Vis for å få mer informasjon."</string>
@@ -97,14 +93,21 @@
     <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Avbryt"</string>
     <string name="letterbox_restart_restart" msgid="8529976234412442973">"Start på nytt"</string>
     <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Ikke vis dette igjen"</string>
+    <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Dobbelttrykk for\nå flytte denne appen"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"Maksimer"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimer"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Lukk"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Tilbake"</string>
     <string name="handle_text" msgid="1766582106752184456">"Håndtak"</string>
+    <string name="app_icon_text" msgid="2823268023931811747">"Appikon"</string>
     <string name="fullscreen_text" msgid="1162316685217676079">"Fullskjerm"</string>
     <string name="desktop_text" msgid="1077633567027630454">"Skrivebordmodus"</string>
     <string name="split_screen_text" msgid="1396336058129570886">"Delt skjerm"</string>
     <string name="more_button_text" msgid="3655388105592893530">"Mer"</string>
     <string name="float_button_text" msgid="9221657008391364581">"Svevende"</string>
+    <string name="select_text" msgid="5139083974039906583">"Velg"</string>
+    <string name="screenshot_text" msgid="1477704010087786671">"Skjermdump"</string>
+    <string name="close_text" msgid="4986518933445178928">"Lukk"</string>
+    <string name="collapse_menu_text" msgid="7515008122450342029">"Lukk menyen"</string>
+    <string name="expand_menu_text" msgid="3847736164494181168">"Åpne menyen"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-ne/strings.xml b/libs/WindowManager/Shell/res/values-ne/strings.xml
index 1865ee5..f9f5805 100644
--- a/libs/WindowManager/Shell/res/values-ne/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ne/strings.xml
@@ -32,13 +32,13 @@
     <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"आकार बदल्नुहोस्"</string>
     <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"स्ट्यास गर्नुहोस्"</string>
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"अनस्ट्यास गर्नुहोस्"</string>
-    <string name="dock_forced_resizable" msgid="1749750436092293116">"एप विभाजित स्क्रिनमा काम नगर्न सक्छ।"</string>
-    <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"अनुप्रयोगले विभाजित-स्क्रिनलाई समर्थन गर्दैन।"</string>
+    <string name="dock_forced_resizable" msgid="7429086980048964687">"यो एपले स्प्लिट स्क्रिन मोडमा काम नगर्न सक्छ"</string>
+    <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"यो एप स्प्लिट स्क्रिन मोडमा प्रयोग गर्न मिल्दैन"</string>
     <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"यो एप एउटा विन्डोमा मात्र खोल्न मिल्छ।"</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="703810061635792791">"विभाजित-स्क्रिन छुट्याउने"</string>
-    <string name="divider_title" msgid="5482989479865361192">"स्प्लिट स्क्रिन डिभाइडर"</string>
+    <string name="accessibility_divider" msgid="6407584574218956849">"स्प्लिट स्क्रिन डिभाइडर"</string>
+    <string name="divider_title" msgid="1963391955593749442">"स्प्लिट स्क्रिन डिभाइडर"</string>
     <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"बायाँ भाग फुल स्क्रिन"</string>
     <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"बायाँ भाग ७०%"</string>
     <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"बायाँ भाग ५०%"</string>
@@ -49,14 +49,10 @@
     <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"माथिल्लो भाग ५०%"</string>
     <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"माथिल्लो भाग ३०%"</string>
     <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"तल्लो भाग फुल स्क्रिन"</string>
-    <!-- no translation found for accessibility_split_left (1713683765575562458) -->
-    <skip />
-    <!-- no translation found for accessibility_split_right (8441001008181296837) -->
-    <skip />
-    <!-- no translation found for accessibility_split_top (2789329702027147146) -->
-    <skip />
-    <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
-    <skip />
+    <string name="accessibility_split_left" msgid="1713683765575562458">"बायाँतिर स्प्लिट गर्नुहोस्"</string>
+    <string name="accessibility_split_right" msgid="8441001008181296837">"दायाँतिर स्प्लिट गर्नुहोस्"</string>
+    <string name="accessibility_split_top" msgid="2789329702027147146">"सिरानतिर स्प्लिट गर्नुहोस्"</string>
+    <string name="accessibility_split_bottom" msgid="8694551025220868191">"पुछारतिर स्प्लिट गर्नुहोस्"</string>
     <string name="one_handed_tutorial_title" msgid="4583241688067426350">"एक हाते मोड प्रयोग गरिँदै छ"</string>
     <string name="one_handed_tutorial_description" msgid="3486582858591353067">"बाहिर निस्कन, स्क्रिनको पुछारबाट माथितिर स्वाइप गर्नुहोस् वा एपभन्दा माथि जुनसुकै ठाउँमा ट्याप गर्नुहोस्"</string>
     <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"एक हाते मोड सुरु गर्नुहोस्"</string>
@@ -88,7 +84,7 @@
     <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"समस्या हल भएन?\nपहिलेको जस्तै बनाउन ट्याप गर्नुहोस्"</string>
     <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"क्यामेरासम्बन्धी कुनै पनि समस्या छैन? खारेज गर्न ट्याप गर्नुहोस्।"</string>
     <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"थप कुरा हेर्नुहोस् र गर्नुहोस्"</string>
-    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"स्प्लिट स्क्रिन मोड प्रयोग गर्न अर्को एप ड्रयाग एन्ड ड्रप गर्नुहोस्"</string>
+    <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"स्प्लिट स्क्रिन मोड प्रयोग गर्न अर्को एप ड्रयाग एन्ड ड्रप गर्नुहोस्"</string>
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"तपाईं जुन एपको स्थिति मिलाउन चाहनुहुन्छ सोही एपको बाहिर डबल ट्याप गर्नुहोस्"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"बुझेँ"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"थप जानकारी प्राप्त गर्न चाहनुहुन्छ भने एक्स्पान्ड गर्नुहोस्।"</string>
@@ -97,14 +93,21 @@
     <string name="letterbox_restart_cancel" msgid="1342209132692537805">"रद्द गर्नुहोस्"</string>
     <string name="letterbox_restart_restart" msgid="8529976234412442973">"रिस्टार्ट गर्नुहोस्"</string>
     <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"फेरि नदेखाइयोस्"</string>
+    <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"यो एप सार्न डबल\nट्याप गर्नुहोस्"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"ठुलो बनाउनुहोस्"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"मिनिमाइज गर्नुहोस्"</string>
     <string name="close_button_text" msgid="2913281996024033299">"बन्द गर्नुहोस्"</string>
     <string name="back_button_text" msgid="1469718707134137085">"पछाडि"</string>
     <string name="handle_text" msgid="1766582106752184456">"ह्यान्डल"</string>
+    <string name="app_icon_text" msgid="2823268023931811747">"एपको आइकन"</string>
     <string name="fullscreen_text" msgid="1162316685217676079">"फुल स्क्रिन"</string>
     <string name="desktop_text" msgid="1077633567027630454">"डेस्कटप मोड"</string>
     <string name="split_screen_text" msgid="1396336058129570886">"स्प्लिट स्क्रिन"</string>
     <string name="more_button_text" msgid="3655388105592893530">"थप"</string>
     <string name="float_button_text" msgid="9221657008391364581">"फ्लोट"</string>
+    <string name="select_text" msgid="5139083974039906583">"चयन गर्नुहोस्"</string>
+    <string name="screenshot_text" msgid="1477704010087786671">"स्क्रिनसट"</string>
+    <string name="close_text" msgid="4986518933445178928">"बन्द गर्नुहोस्"</string>
+    <string name="collapse_menu_text" msgid="7515008122450342029">"मेनु बन्द गर्नुहोस्"</string>
+    <string name="expand_menu_text" msgid="3847736164494181168">"मेनु खोल्नुहोस्"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-nl/strings.xml b/libs/WindowManager/Shell/res/values-nl/strings.xml
index 91437ac..3064ccc 100644
--- a/libs/WindowManager/Shell/res/values-nl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-nl/strings.xml
@@ -32,13 +32,13 @@
     <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Formaat aanpassen"</string>
     <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Verbergen"</string>
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Niet meer verbergen"</string>
-    <string name="dock_forced_resizable" msgid="1749750436092293116">"De app werkt mogelijk niet met gesplitst scherm."</string>
-    <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"App biedt geen ondersteuning voor gesplitst scherm."</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>
     <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Deze app kan slechts 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="703810061635792791">"Scheiding voor gesplitst scherm"</string>
-    <string name="divider_title" msgid="5482989479865361192">"Scheiding voor gesplitst scherm"</string>
+    <string name="accessibility_divider" msgid="6407584574218956849">"Scheiding voor gesplitst scherm"</string>
+    <string name="divider_title" msgid="1963391955593749442">"Scheiding voor gesplitst scherm"</string>
     <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Linkerscherm op volledig scherm"</string>
     <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Linkerscherm 70%"</string>
     <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Linkerscherm 50%"</string>
@@ -49,14 +49,10 @@
     <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Bovenste scherm 50%"</string>
     <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Bovenste scherm 30%"</string>
     <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Onderste scherm op volledig scherm"</string>
-    <!-- no translation found for accessibility_split_left (1713683765575562458) -->
-    <skip />
-    <!-- no translation found for accessibility_split_right (8441001008181296837) -->
-    <skip />
-    <!-- no translation found for accessibility_split_top (2789329702027147146) -->
-    <skip />
-    <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
-    <skip />
+    <string name="accessibility_split_left" msgid="1713683765575562458">"Gesplitst scherm links"</string>
+    <string name="accessibility_split_right" msgid="8441001008181296837">"Gesplitst scherm rechts"</string>
+    <string name="accessibility_split_top" msgid="2789329702027147146">"Gesplitst scherm boven"</string>
+    <string name="accessibility_split_bottom" msgid="8694551025220868191">"Gesplitst scherm onder"</string>
     <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Bediening met 1 hand gebruiken"</string>
     <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Als je wilt afsluiten, swipe je omhoog vanaf de onderkant van het scherm of tik je ergens boven de app"</string>
     <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Bediening met 1 hand starten"</string>
@@ -88,7 +84,7 @@
     <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Is dit geen oplossing?\nTik om terug te zetten."</string>
     <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Geen cameraproblemen? Tik om te sluiten."</string>
     <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Zie en doe meer"</string>
-    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Sleep een andere app hier naartoe om het scherm te splitsen"</string>
+    <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Sleep een andere app hier naartoe om het scherm te splitsen"</string>
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Dubbeltik naast een app om deze opnieuw te positioneren"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Uitvouwen voor meer informatie."</string>
@@ -97,14 +93,21 @@
     <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Annuleren"</string>
     <string name="letterbox_restart_restart" msgid="8529976234412442973">"Opnieuw opstarten"</string>
     <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Niet opnieuw tonen"</string>
+    <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Dubbeltik om\ndeze app te verplaatsen"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"Maximaliseren"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimaliseren"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Sluiten"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Terug"</string>
     <string name="handle_text" msgid="1766582106752184456">"Gebruikersnaam"</string>
+    <string name="app_icon_text" msgid="2823268023931811747">"App-icoon"</string>
     <string name="fullscreen_text" msgid="1162316685217676079">"Volledig scherm"</string>
     <string name="desktop_text" msgid="1077633567027630454">"Desktopmodus"</string>
     <string name="split_screen_text" msgid="1396336058129570886">"Gesplitst scherm"</string>
     <string name="more_button_text" msgid="3655388105592893530">"Meer"</string>
     <string name="float_button_text" msgid="9221657008391364581">"Zwevend"</string>
+    <string name="select_text" msgid="5139083974039906583">"Selecteren"</string>
+    <string name="screenshot_text" msgid="1477704010087786671">"Screenshot"</string>
+    <string name="close_text" msgid="4986518933445178928">"Sluiten"</string>
+    <string name="collapse_menu_text" msgid="7515008122450342029">"Menu sluiten"</string>
+    <string name="expand_menu_text" msgid="3847736164494181168">"Menu openen"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-or/strings.xml b/libs/WindowManager/Shell/res/values-or/strings.xml
index d330749..267b8a3 100644
--- a/libs/WindowManager/Shell/res/values-or/strings.xml
+++ b/libs/WindowManager/Shell/res/values-or/strings.xml
@@ -26,19 +26,19 @@
     <string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> \"ଛବି-ଭିତରେ-ଛବି\"ରେ ଅଛି"</string>
     <string name="pip_notification_message" msgid="8854051911700302620">"ଏହି ବୈଶିଷ୍ଟ୍ୟ <xliff:g id="NAME">%s</xliff:g> ବ୍ୟବହାର ନକରିବାକୁ ଯଦି ଆପଣ ଚାହାଁନ୍ତି, ସେଟିଙ୍ଗ ଖୋଲିବାକୁ ଟାପ୍‍ କରନ୍ତୁ ଏବଂ ଏହା ଅଫ୍‍ କରିଦିଅନ୍ତୁ।"</string>
     <string name="pip_play" msgid="3496151081459417097">"ପ୍ଲେ କରନ୍ତୁ"</string>
-    <string name="pip_pause" msgid="690688849510295232">"ପଜ୍‍ କରନ୍ତୁ"</string>
+    <string name="pip_pause" msgid="690688849510295232">"ବିରତ କରନ୍ତୁ"</string>
     <string name="pip_skip_to_next" msgid="8403429188794867653">"ପରବର୍ତ୍ତୀକୁ ଯାଆନ୍ତୁ"</string>
     <string name="pip_skip_to_prev" msgid="7172158111196394092">"ପୂର୍ବବର୍ତ୍ତୀକୁ ଛାଡ଼ନ୍ତୁ"</string>
     <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"ରିସାଇଜ୍ କରନ୍ତୁ"</string>
     <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"ଲୁଚାନ୍ତୁ"</string>
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"ଦେଖାନ୍ତୁ"</string>
-    <string name="dock_forced_resizable" msgid="1749750436092293116">"ସ୍ପ୍ଲିଟ୍-ସ୍କ୍ରିନରେ ଆପ୍ କାମ କରିନପାରେ।"</string>
-    <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"ଆପ୍‍ ସ୍ପ୍ଲିଟ୍‍-ସ୍କ୍ରୀନକୁ ସପୋର୍ଟ କରେ ନାହିଁ।"</string>
+    <string name="dock_forced_resizable" msgid="7429086980048964687">"ସ୍ପ୍ଲିଟ ସ୍କ୍ରିନରେ ଆପ କାମ କରିନପାରେ"</string>
+    <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"ସ୍ପ୍ଲିଟ ସ୍କ୍ରିନକୁ ଆପ ସମର୍ଥନ କରେ ନାହିଁ"</string>
     <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"ଏହି ଆପକୁ କେବଳ 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="703810061635792791">"ସ୍ପ୍ଲିଟ୍‍-ସ୍କ୍ରୀନ ବିଭାଜକ"</string>
-    <string name="divider_title" msgid="5482989479865361192">"ସ୍ପ୍ଲିଟ-ସ୍କ୍ରିନ ଡିଭାଇଡର"</string>
+    <string name="accessibility_divider" msgid="6407584574218956849">"ସ୍ପ୍ଲିଟ ସ୍କ୍ରିନ ଡିଭାଇଡର"</string>
+    <string name="divider_title" msgid="1963391955593749442">"ସ୍ପ୍ଲିଟ ସ୍କ୍ରିନ ଡିଭାଇଡର"</string>
     <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"ବାମ ପଟକୁ ପୂର୍ଣ୍ଣ ସ୍କ୍ରୀନ୍‍ କରନ୍ତୁ"</string>
     <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"ବାମ ପଟକୁ 70% କରନ୍ତୁ"</string>
     <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"ବାମ ପଟକୁ 50% କରନ୍ତୁ"</string>
@@ -49,14 +49,10 @@
     <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"ଉପର ଆଡ଼କୁ 50% କରନ୍ତୁ"</string>
     <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"ଉପର ଆଡ଼କୁ 30% କରନ୍ତୁ"</string>
     <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"ତଳ ଅଂଶର ପୂର୍ଣ୍ଣ ସ୍କ୍ରୀନ୍‍"</string>
-    <!-- no translation found for accessibility_split_left (1713683765575562458) -->
-    <skip />
-    <!-- no translation found for accessibility_split_right (8441001008181296837) -->
-    <skip />
-    <!-- no translation found for accessibility_split_top (2789329702027147146) -->
-    <skip />
-    <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
-    <skip />
+    <string name="accessibility_split_left" msgid="1713683765575562458">"ବାମପଟକୁ ସ୍ପ୍ଲିଟ କରନ୍ତୁ"</string>
+    <string name="accessibility_split_right" msgid="8441001008181296837">"ଡାହାଣପଟକୁ ସ୍ପ୍ଲିଟ କରନ୍ତୁ"</string>
+    <string name="accessibility_split_top" msgid="2789329702027147146">"ଶୀର୍ଷକୁ ସ୍ପ୍ଲିଟ କରନ୍ତୁ"</string>
+    <string name="accessibility_split_bottom" msgid="8694551025220868191">"ନିମ୍ନକୁ ସ୍ଲିଟ କରନ୍ତୁ"</string>
     <string name="one_handed_tutorial_title" msgid="4583241688067426350">"ଏକ-ହାତ ମୋଡ୍ ବ୍ୟବହାର କରି"</string>
     <string name="one_handed_tutorial_description" msgid="3486582858591353067">"ବାହାରି ଯିବା ପାଇଁ, ସ୍କ୍ରିନର ତଳୁ ଉପରକୁ ସ୍ୱାଇପ୍ କରନ୍ତୁ କିମ୍ବା ଆପରେ ଯେ କୌଣସି ସ୍ଥାନରେ ଟାପ୍ କରନ୍ତୁ"</string>
     <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"ଏକ-ହାତ ମୋଡ୍ ଆରମ୍ଭ କରନ୍ତୁ"</string>
@@ -88,7 +84,7 @@
     <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"ଏହାର ସମାଧାନ ହୋଇନାହିଁ?\nଫେରିଯିବା ପାଇଁ ଟାପ କରନ୍ତୁ"</string>
     <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"କ୍ୟାମେରାରେ କିଛି ସମସ୍ୟା ନାହିଁ? ଖାରଜ କରିବାକୁ ଟାପ କରନ୍ତୁ।"</string>
     <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"ଦେଖନ୍ତୁ ଏବଂ ଆହୁରି ଅନେକ କିଛି କରନ୍ତୁ"</string>
-    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"ସ୍ପ୍ଲିଟ-ସ୍କ୍ରିନ ପାଇଁ ଅନ୍ୟ ଏକ ଆପକୁ ଡ୍ରାଗ କରନ୍ତୁ"</string>
+    <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"ସ୍ପ୍ଲିଟ ସ୍କ୍ରିନ ପାଇଁ ଅନ୍ୟ ଏକ ଆପକୁ ଡ୍ରାଗ କରନ୍ତୁ"</string>
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"ଏକ ଆପକୁ ରିପୋଜିସନ କରିବା ପାଇଁ ଏହାର ବାହାରେ ଦୁଇଥର-ଟାପ କରନ୍ତୁ"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"ବୁଝିଗଲି"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"ଅଧିକ ସୂଚନା ପାଇଁ ବିସ୍ତାର କରନ୍ତୁ।"</string>
@@ -97,14 +93,21 @@
     <string name="letterbox_restart_cancel" msgid="1342209132692537805">"ବାତିଲ କରନ୍ତୁ"</string>
     <string name="letterbox_restart_restart" msgid="8529976234412442973">"ରିଷ୍ଟାର୍ଟ କରନ୍ତୁ"</string>
     <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"ପୁଣି ଦେଖାନ୍ତୁ ନାହିଁ"</string>
+    <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"ଏହି ଆପକୁ ମୁଭ\nକରିବା ପାଇଁ ଦୁଇଥର-ଟାପ କରନ୍ତୁ"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"ବଡ଼ କରନ୍ତୁ"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"ଛୋଟ କରନ୍ତୁ"</string>
     <string name="close_button_text" msgid="2913281996024033299">"ବନ୍ଦ କରନ୍ତୁ"</string>
     <string name="back_button_text" msgid="1469718707134137085">"ପଛକୁ ଫେରନ୍ତୁ"</string>
     <string name="handle_text" msgid="1766582106752184456">"ହେଣ୍ଡେଲ"</string>
+    <string name="app_icon_text" msgid="2823268023931811747">"ଆପ ଆଇକନ"</string>
     <string name="fullscreen_text" msgid="1162316685217676079">"ପୂର୍ଣ୍ଣସ୍କ୍ରିନ"</string>
     <string name="desktop_text" msgid="1077633567027630454">"ଡେସ୍କଟପ ମୋଡ"</string>
     <string name="split_screen_text" msgid="1396336058129570886">"ସ୍ପ୍ଲିଟ ସ୍କ୍ରିନ"</string>
     <string name="more_button_text" msgid="3655388105592893530">"ଅଧିକ"</string>
     <string name="float_button_text" msgid="9221657008391364581">"ଫ୍ଲୋଟ"</string>
+    <string name="select_text" msgid="5139083974039906583">"ଚୟନ କରନ୍ତୁ"</string>
+    <string name="screenshot_text" msgid="1477704010087786671">"ସ୍କ୍ରିନସଟ"</string>
+    <string name="close_text" msgid="4986518933445178928">"ବନ୍ଦ କରନ୍ତୁ"</string>
+    <string name="collapse_menu_text" msgid="7515008122450342029">"ମେନୁ ବନ୍ଦ କରନ୍ତୁ"</string>
+    <string name="expand_menu_text" msgid="3847736164494181168">"ମେନୁ ଖୋଲନ୍ତୁ"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-pa/strings.xml b/libs/WindowManager/Shell/res/values-pa/strings.xml
index 51d491b..d9f7f34 100644
--- a/libs/WindowManager/Shell/res/values-pa/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pa/strings.xml
@@ -32,13 +32,13 @@
     <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"ਆਕਾਰ ਬਦਲੋ"</string>
     <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"ਸਟੈਸ਼"</string>
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"ਅਣਸਟੈਸ਼"</string>
-    <string name="dock_forced_resizable" msgid="1749750436092293116">"ਹੋ ਸਕਦਾ ਹੈ ਕਿ ਐਪ ਸਪਲਿਟ-ਸਕ੍ਰੀਨ ਨਾਲ ਕੰਮ ਨਾ ਕਰੇ।"</string>
-    <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"ਐਪ ਸਪਲਿਟ-ਸਕ੍ਰੀਨ ਨੂੰ ਸਮਰਥਨ ਨਹੀਂ ਕਰਦੀ।"</string>
+    <string name="dock_forced_resizable" msgid="7429086980048964687">"ਹੋ ਸਕਦਾ ਹੈ ਕਿ ਐਪ ਸਪਲਿਟ ਸਕ੍ਰੀਨ ਨਾਲ ਕੰਮ ਨਾ ਕਰੇ"</string>
+    <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"ਐਪ ਸਪਲਿਟ ਸਕ੍ਰੀਨ ਦਾ ਸਮਰਥਨ ਨਹੀਂ ਕਰਦੀ"</string>
     <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"ਇਹ ਐਪ ਸਿਰਫ਼ 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="703810061635792791">"ਸਪਲਿਟ-ਸਕ੍ਰੀਨ ਡਿਵਾਈਡਰ"</string>
-    <string name="divider_title" msgid="5482989479865361192">"ਸਪਲਿਟ-ਸਕ੍ਰੀਨ ਵਿਭਾਜਕ"</string>
+    <string name="accessibility_divider" msgid="6407584574218956849">"ਸਪਲਿਟ ਸਕ੍ਰੀਨ ਵਿਭਾਜਕ"</string>
+    <string name="divider_title" msgid="1963391955593749442">"ਸਪਲਿਟ ਸਕ੍ਰੀਨ ਵਿਭਾਜਕ"</string>
     <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"ਖੱਬੇ ਪੂਰੀ ਸਕ੍ਰੀਨ"</string>
     <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"ਖੱਬੇ 70%"</string>
     <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"ਖੱਬੇ 50%"</string>
@@ -49,14 +49,10 @@
     <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"ਉੱਪਰ 50%"</string>
     <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"ਉੱਪਰ 30%"</string>
     <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"ਹੇਠਾਂ ਪੂਰੀ ਸਕ੍ਰੀਨ"</string>
-    <!-- no translation found for accessibility_split_left (1713683765575562458) -->
-    <skip />
-    <!-- no translation found for accessibility_split_right (8441001008181296837) -->
-    <skip />
-    <!-- no translation found for accessibility_split_top (2789329702027147146) -->
-    <skip />
-    <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
-    <skip />
+    <string name="accessibility_split_left" msgid="1713683765575562458">"ਖੱਬੇ ਪਾਸੇ ਵੰਡੋ"</string>
+    <string name="accessibility_split_right" msgid="8441001008181296837">"ਸੱਜੇ ਪਾਸੇ ਵੰਡੋ"</string>
+    <string name="accessibility_split_top" msgid="2789329702027147146">"ਸਿਖਰ \'ਤੇ ਵੰਡੋ"</string>
+    <string name="accessibility_split_bottom" msgid="8694551025220868191">"ਹੇਠਾਂ ਵੰਡੋ"</string>
     <string name="one_handed_tutorial_title" msgid="4583241688067426350">"ਇੱਕ ਹੱਥ ਮੋਡ ਵਰਤਣਾ"</string>
     <string name="one_handed_tutorial_description" msgid="3486582858591353067">"ਬਾਹਰ ਜਾਣ ਲਈ, ਸਕ੍ਰੀਨ ਦੇ ਹੇਠਾਂ ਤੋਂ ਉੱਪਰ ਵੱਲ ਸਵਾਈਪ ਕਰੋ ਜਾਂ ਐਪ \'ਤੇ ਕਿਤੇ ਵੀ ਟੈਪ ਕਰੋ"</string>
     <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"ਇੱਕ ਹੱਥ ਮੋਡ ਸ਼ੁਰੂ ਕਰੋ"</string>
@@ -88,7 +84,7 @@
     <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"ਕੀ ਇਹ ਠੀਕ ਨਹੀਂ ਹੋਈ?\nਵਾਪਸ ਉਹੀ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ"</string>
     <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"ਕੀ ਕੈਮਰੇ ਸੰਬੰਧੀ ਕੋਈ ਸਮੱਸਿਆ ਨਹੀਂ ਹੈ? ਖਾਰਜ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ।"</string>
     <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"ਦੇਖੋ ਅਤੇ ਹੋਰ ਬਹੁਤ ਕੁਝ ਕਰੋ"</string>
-    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"ਸਪਲਿਟ ਸਕ੍ਰੀਨ ਦੇ ਲਈ ਕਿਸੇ ਹੋਰ ਐਪ ਵਿੱਚ ਘਸੀਟੋ"</string>
+    <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"ਸਪਲਿਟ ਸਕ੍ਰੀਨ ਦੇ ਲਈ ਕਿਸੇ ਹੋਰ ਐਪ ਵਿੱਚ ਘਸੀਟੋ"</string>
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"ਕਿਸੇ ਐਪ ਦੀ ਜਗ੍ਹਾ ਬਦਲਣ ਲਈ ਉਸ ਦੇ ਬਾਹਰ ਡਬਲ ਟੈਪ ਕਰੋ"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"ਸਮਝ ਲਿਆ"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"ਹੋਰ ਜਾਣਕਾਰੀ ਲਈ ਵਿਸਤਾਰ ਕਰੋ।"</string>
@@ -97,14 +93,21 @@
     <string name="letterbox_restart_cancel" msgid="1342209132692537805">"ਰੱਦ ਕਰੋ"</string>
     <string name="letterbox_restart_restart" msgid="8529976234412442973">"ਮੁੜ-ਸ਼ੁਰੂ ਕਰੋ"</string>
     <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"ਦੁਬਾਰਾ ਨਾ ਦਿਖਾਓ"</string>
+    <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"ਇਸ ਐਪ ਦਾ ਟਿਕਾਣਾ ਬਦਲਣ ਲਈ\nਡਬਲ ਟੈਪ ਕਰੋ"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"ਵੱਡਾ ਕਰੋ"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"ਛੋਟਾ ਕਰੋ"</string>
     <string name="close_button_text" msgid="2913281996024033299">"ਬੰਦ ਕਰੋ"</string>
     <string name="back_button_text" msgid="1469718707134137085">"ਪਿੱਛੇ"</string>
     <string name="handle_text" msgid="1766582106752184456">"ਹੈਂਡਲ"</string>
+    <string name="app_icon_text" msgid="2823268023931811747">"ਐਪ ਪ੍ਰਤੀਕ"</string>
     <string name="fullscreen_text" msgid="1162316685217676079">"ਪੂਰੀ-ਸਕ੍ਰੀਨ"</string>
     <string name="desktop_text" msgid="1077633567027630454">"ਡੈਸਕਟਾਪ ਮੋਡ"</string>
     <string name="split_screen_text" msgid="1396336058129570886">"ਸਪਲਿਟ ਸਕ੍ਰੀਨ"</string>
     <string name="more_button_text" msgid="3655388105592893530">"ਹੋਰ"</string>
     <string name="float_button_text" msgid="9221657008391364581">"ਫ਼ਲੋਟ"</string>
+    <string name="select_text" msgid="5139083974039906583">"ਚੁਣੋ"</string>
+    <string name="screenshot_text" msgid="1477704010087786671">"ਸਕ੍ਰੀਨਸ਼ਾਟ"</string>
+    <string name="close_text" msgid="4986518933445178928">"ਬੰਦ ਕਰੋ"</string>
+    <string name="collapse_menu_text" msgid="7515008122450342029">"ਮੀਨੂ ਬੰਦ ਕਰੋ"</string>
+    <string name="expand_menu_text" msgid="3847736164494181168">"ਮੀਨੂ ਖੋਲ੍ਹੋ"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-pl/strings.xml b/libs/WindowManager/Shell/res/values-pl/strings.xml
index 32e9840..0699f5d 100644
--- a/libs/WindowManager/Shell/res/values-pl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pl/strings.xml
@@ -32,13 +32,13 @@
     <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Zmień rozmiar"</string>
     <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Przenieś do schowka"</string>
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Zabierz ze schowka"</string>
-    <string name="dock_forced_resizable" msgid="1749750436092293116">"Aplikacja może nie działać przy podzielonym ekranie."</string>
-    <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Aplikacja nie obsługuje dzielonego ekranu."</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>
     <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"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="703810061635792791">"Linia dzielenia ekranu"</string>
-    <string name="divider_title" msgid="5482989479865361192">"Linia dzielenia ekranu"</string>
+    <string name="accessibility_divider" msgid="6407584574218956849">"Linia dzielenia ekranu"</string>
+    <string name="divider_title" msgid="1963391955593749442">"Linia dzielenia ekranu"</string>
     <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Lewa część ekranu na pełnym ekranie"</string>
     <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"70% lewej części ekranu"</string>
     <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"50% lewej części ekranu"</string>
@@ -49,14 +49,10 @@
     <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"50% górnej części ekranu"</string>
     <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"30% górnej części ekranu"</string>
     <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Dolna część ekranu na pełnym ekranie"</string>
-    <!-- no translation found for accessibility_split_left (1713683765575562458) -->
-    <skip />
-    <!-- no translation found for accessibility_split_right (8441001008181296837) -->
-    <skip />
-    <!-- no translation found for accessibility_split_top (2789329702027147146) -->
-    <skip />
-    <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
-    <skip />
+    <string name="accessibility_split_left" msgid="1713683765575562458">"Podziel po lewej"</string>
+    <string name="accessibility_split_right" msgid="8441001008181296837">"Podziel po prawej"</string>
+    <string name="accessibility_split_top" msgid="2789329702027147146">"Podziel u góry"</string>
+    <string name="accessibility_split_bottom" msgid="8694551025220868191">"Podziel u dołu"</string>
     <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Korzystanie z trybu jednej ręki"</string>
     <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Aby zamknąć, przesuń palcem z dołu ekranu w górę lub kliknij dowolne miejsce nad aplikacją"</string>
     <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Uruchom tryb jednej ręki"</string>
@@ -88,7 +84,7 @@
     <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Naprawa się nie udała?\nKliknij, aby cofnąć"</string>
     <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Brak problemów z aparatem? Kliknij, aby zamknąć"</string>
     <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Zobacz i zrób więcej"</string>
-    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Przeciągnij drugą aplikację, aby podzielić ekran"</string>
+    <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Aby podzielić ekran, przeciągnij drugą aplikację"</string>
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Kliknij dwukrotnie poza aplikacją, aby ją przenieść"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Rozwiń, aby wyświetlić więcej informacji."</string>
@@ -97,14 +93,21 @@
     <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Anuluj"</string>
     <string name="letterbox_restart_restart" msgid="8529976234412442973">"Uruchom ponownie"</string>
     <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Nie pokazuj ponownie"</string>
+    <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Aby przenieść aplikację,\nkliknij dwukrotnie"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"Maksymalizuj"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimalizuj"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Zamknij"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Wstecz"</string>
     <string name="handle_text" msgid="1766582106752184456">"Uchwyt"</string>
+    <string name="app_icon_text" msgid="2823268023931811747">"Ikona aplikacji"</string>
     <string name="fullscreen_text" msgid="1162316685217676079">"Pełny ekran"</string>
     <string name="desktop_text" msgid="1077633567027630454">"Tryb pulpitu"</string>
     <string name="split_screen_text" msgid="1396336058129570886">"Podzielony ekran"</string>
     <string name="more_button_text" msgid="3655388105592893530">"Więcej"</string>
     <string name="float_button_text" msgid="9221657008391364581">"Pływające"</string>
+    <string name="select_text" msgid="5139083974039906583">"Wybierz"</string>
+    <string name="screenshot_text" msgid="1477704010087786671">"Zrzut ekranu"</string>
+    <string name="close_text" msgid="4986518933445178928">"Zamknij"</string>
+    <string name="collapse_menu_text" msgid="7515008122450342029">"Zamknij menu"</string>
+    <string name="expand_menu_text" msgid="3847736164494181168">"Otwórz menu"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml b/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml
index 1a29af2..eea9be2 100644
--- a/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml
@@ -32,13 +32,13 @@
     <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Redimensionar"</string>
     <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Ocultar"</string>
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Exibir"</string>
-    <string name="dock_forced_resizable" msgid="1749750436092293116">"É possível que o app não funcione com a tela dividida."</string>
-    <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"O app não é compatível com a divisão de tela."</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>
     <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Este 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="703810061635792791">"Divisor de tela"</string>
-    <string name="divider_title" msgid="5482989479865361192">"Divisor de tela"</string>
+    <string name="accessibility_divider" msgid="6407584574218956849">"Divisor de tela"</string>
+    <string name="divider_title" msgid="1963391955593749442">"Divisor de tela"</string>
     <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Lado esquerdo em tela cheia"</string>
     <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Esquerda a 70%"</string>
     <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Esquerda a 50%"</string>
@@ -49,14 +49,10 @@
     <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Parte superior a 50%"</string>
     <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Parte superior a 30%"</string>
     <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Parte inferior em tela cheia"</string>
-    <!-- no translation found for accessibility_split_left (1713683765575562458) -->
-    <skip />
-    <!-- no translation found for accessibility_split_right (8441001008181296837) -->
-    <skip />
-    <!-- no translation found for accessibility_split_top (2789329702027147146) -->
-    <skip />
-    <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
-    <skip />
+    <string name="accessibility_split_left" msgid="1713683765575562458">"Dividir para a esquerda"</string>
+    <string name="accessibility_split_right" msgid="8441001008181296837">"Dividir para a direita"</string>
+    <string name="accessibility_split_top" msgid="2789329702027147146">"Dividir para cima"</string>
+    <string name="accessibility_split_bottom" msgid="8694551025220868191">"Dividir para baixo"</string>
     <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Como usar o modo para uma mão"</string>
     <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Para sair, deslize de baixo para cima na tela ou toque em qualquer lugar acima do app"</string>
     <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Iniciar o modo para uma mão"</string>
@@ -88,7 +84,7 @@
     <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"O problema não foi corrigido?\nToque para reverter"</string>
     <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Não tem problemas com a câmera? Toque para dispensar."</string>
     <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Veja e faça mais"</string>
-    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Arraste outro app para a tela dividida"</string>
+    <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Arraste outro app para dividir a tela"</string>
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Toque duas vezes fora de um app para reposicionar"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"Entendi"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Abra para ver mais informações."</string>
@@ -97,14 +93,21 @@
     <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Cancelar"</string>
     <string name="letterbox_restart_restart" msgid="8529976234412442973">"Reiniciar"</string>
     <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Não mostrar novamente"</string>
+    <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Toque duas vezes para\nmover o app"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"Maximizar"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimizar"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Fechar"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Voltar"</string>
     <string name="handle_text" msgid="1766582106752184456">"Alça"</string>
+    <string name="app_icon_text" msgid="2823268023931811747">"Ícone do app"</string>
     <string name="fullscreen_text" msgid="1162316685217676079">"Tela cheia"</string>
     <string name="desktop_text" msgid="1077633567027630454">"Modo área de trabalho"</string>
     <string name="split_screen_text" msgid="1396336058129570886">"Tela dividida"</string>
     <string name="more_button_text" msgid="3655388105592893530">"Mais"</string>
     <string name="float_button_text" msgid="9221657008391364581">"Ponto flutuante"</string>
+    <string name="select_text" msgid="5139083974039906583">"Selecionar"</string>
+    <string name="screenshot_text" msgid="1477704010087786671">"Captura de tela"</string>
+    <string name="close_text" msgid="4986518933445178928">"Fechar"</string>
+    <string name="collapse_menu_text" msgid="7515008122450342029">"Fechar menu"</string>
+    <string name="expand_menu_text" msgid="3847736164494181168">"Abrir o menu"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml b/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml
index b2d8e08..04ee540 100644
--- a/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml
@@ -32,13 +32,13 @@
     <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Redimensionar"</string>
     <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Armazenar"</string>
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Remover do armazenamento"</string>
-    <string name="dock_forced_resizable" msgid="1749750436092293116">"A app pode não funcionar com o ecrã dividido."</string>
-    <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"A app não é compatível com o ecrã dividido."</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>
     <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"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="703810061635792791">"Divisor do ecrã dividido"</string>
-    <string name="divider_title" msgid="5482989479865361192">"Divisor do ecrã dividido"</string>
+    <string name="accessibility_divider" msgid="6407584574218956849">"Divisor do ecrã dividido"</string>
+    <string name="divider_title" msgid="1963391955593749442">"Divisor do ecrã dividido"</string>
     <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Ecrã esquerdo inteiro"</string>
     <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"70% no ecrã esquerdo"</string>
     <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"50% no ecrã esquerdo"</string>
@@ -49,14 +49,10 @@
     <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"50% no ecrã superior"</string>
     <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"30% no ecrã superior"</string>
     <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Ecrã inferior inteiro"</string>
-    <!-- no translation found for accessibility_split_left (1713683765575562458) -->
-    <skip />
-    <!-- no translation found for accessibility_split_right (8441001008181296837) -->
-    <skip />
-    <!-- no translation found for accessibility_split_top (2789329702027147146) -->
-    <skip />
-    <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
-    <skip />
+    <string name="accessibility_split_left" msgid="1713683765575562458">"Divisão à esquerda"</string>
+    <string name="accessibility_split_right" msgid="8441001008181296837">"Divisão à direita"</string>
+    <string name="accessibility_split_top" msgid="2789329702027147146">"Divisão na parte superior"</string>
+    <string name="accessibility_split_bottom" msgid="8694551025220868191">"Divisão na parte inferior"</string>
     <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Utilize o modo para uma mão"</string>
     <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Para sair, deslize rapidamente para cima a partir da parte inferior do ecrã ou toque em qualquer ponto acima da app."</string>
     <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Iniciar o modo para uma mão"</string>
@@ -83,12 +79,12 @@
     <string name="notification_bubble_title" msgid="6082910224488253378">"Balão"</string>
     <string name="manage_bubbles_text" msgid="7730624269650594419">"Gerir"</string>
     <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Balão ignorado."</string>
-    <string name="restart_button_description" msgid="6712141648865547958">"Toque para reiniciar esta app e ficar com uma melhor visão."</string>
+    <string name="restart_button_description" msgid="6712141648865547958">"Toque para reiniciar esta app e ver melhor."</string>
     <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Problemas com a câmara?\nToque aqui para reajustar"</string>
     <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Não foi corrigido?\nToque para reverter"</string>
     <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Nenhum problema com a câmara? Toque para ignorar."</string>
     <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Veja e faça mais"</string>
-    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Arraste outra app para usar o ecrã dividido"</string>
+    <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Arraste outra app para usar o ecrã dividido"</string>
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Toque duas vezes fora de uma app para a reposicionar"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Expandir para obter mais informações"</string>
@@ -97,14 +93,21 @@
     <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Cancelar"</string>
     <string name="letterbox_restart_restart" msgid="8529976234412442973">"Reiniciar"</string>
     <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Não mostrar de novo"</string>
+    <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Toque duas vezes\npara mover esta app"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"Maximizar"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimizar"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Fechar"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Anterior"</string>
     <string name="handle_text" msgid="1766582106752184456">"Indicador"</string>
+    <string name="app_icon_text" msgid="2823268023931811747">"Ícone da app"</string>
     <string name="fullscreen_text" msgid="1162316685217676079">"Ecrã inteiro"</string>
     <string name="desktop_text" msgid="1077633567027630454">"Modo de ambiente de trabalho"</string>
     <string name="split_screen_text" msgid="1396336058129570886">"Ecrã dividido"</string>
     <string name="more_button_text" msgid="3655388105592893530">"Mais"</string>
     <string name="float_button_text" msgid="9221657008391364581">"Flutuar"</string>
+    <string name="select_text" msgid="5139083974039906583">"Selecionar"</string>
+    <string name="screenshot_text" msgid="1477704010087786671">"Captura de ecrã"</string>
+    <string name="close_text" msgid="4986518933445178928">"Fechar"</string>
+    <string name="collapse_menu_text" msgid="7515008122450342029">"Fechar menu"</string>
+    <string name="expand_menu_text" msgid="3847736164494181168">"Abrir menu"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-pt/strings.xml b/libs/WindowManager/Shell/res/values-pt/strings.xml
index 1a29af2..eea9be2 100644
--- a/libs/WindowManager/Shell/res/values-pt/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pt/strings.xml
@@ -32,13 +32,13 @@
     <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Redimensionar"</string>
     <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Ocultar"</string>
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Exibir"</string>
-    <string name="dock_forced_resizable" msgid="1749750436092293116">"É possível que o app não funcione com a tela dividida."</string>
-    <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"O app não é compatível com a divisão de tela."</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>
     <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Este 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="703810061635792791">"Divisor de tela"</string>
-    <string name="divider_title" msgid="5482989479865361192">"Divisor de tela"</string>
+    <string name="accessibility_divider" msgid="6407584574218956849">"Divisor de tela"</string>
+    <string name="divider_title" msgid="1963391955593749442">"Divisor de tela"</string>
     <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Lado esquerdo em tela cheia"</string>
     <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Esquerda a 70%"</string>
     <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Esquerda a 50%"</string>
@@ -49,14 +49,10 @@
     <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Parte superior a 50%"</string>
     <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Parte superior a 30%"</string>
     <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Parte inferior em tela cheia"</string>
-    <!-- no translation found for accessibility_split_left (1713683765575562458) -->
-    <skip />
-    <!-- no translation found for accessibility_split_right (8441001008181296837) -->
-    <skip />
-    <!-- no translation found for accessibility_split_top (2789329702027147146) -->
-    <skip />
-    <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
-    <skip />
+    <string name="accessibility_split_left" msgid="1713683765575562458">"Dividir para a esquerda"</string>
+    <string name="accessibility_split_right" msgid="8441001008181296837">"Dividir para a direita"</string>
+    <string name="accessibility_split_top" msgid="2789329702027147146">"Dividir para cima"</string>
+    <string name="accessibility_split_bottom" msgid="8694551025220868191">"Dividir para baixo"</string>
     <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Como usar o modo para uma mão"</string>
     <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Para sair, deslize de baixo para cima na tela ou toque em qualquer lugar acima do app"</string>
     <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Iniciar o modo para uma mão"</string>
@@ -88,7 +84,7 @@
     <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"O problema não foi corrigido?\nToque para reverter"</string>
     <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Não tem problemas com a câmera? Toque para dispensar."</string>
     <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Veja e faça mais"</string>
-    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Arraste outro app para a tela dividida"</string>
+    <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Arraste outro app para dividir a tela"</string>
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Toque duas vezes fora de um app para reposicionar"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"Entendi"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Abra para ver mais informações."</string>
@@ -97,14 +93,21 @@
     <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Cancelar"</string>
     <string name="letterbox_restart_restart" msgid="8529976234412442973">"Reiniciar"</string>
     <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Não mostrar novamente"</string>
+    <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Toque duas vezes para\nmover o app"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"Maximizar"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimizar"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Fechar"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Voltar"</string>
     <string name="handle_text" msgid="1766582106752184456">"Alça"</string>
+    <string name="app_icon_text" msgid="2823268023931811747">"Ícone do app"</string>
     <string name="fullscreen_text" msgid="1162316685217676079">"Tela cheia"</string>
     <string name="desktop_text" msgid="1077633567027630454">"Modo área de trabalho"</string>
     <string name="split_screen_text" msgid="1396336058129570886">"Tela dividida"</string>
     <string name="more_button_text" msgid="3655388105592893530">"Mais"</string>
     <string name="float_button_text" msgid="9221657008391364581">"Ponto flutuante"</string>
+    <string name="select_text" msgid="5139083974039906583">"Selecionar"</string>
+    <string name="screenshot_text" msgid="1477704010087786671">"Captura de tela"</string>
+    <string name="close_text" msgid="4986518933445178928">"Fechar"</string>
+    <string name="collapse_menu_text" msgid="7515008122450342029">"Fechar menu"</string>
+    <string name="expand_menu_text" msgid="3847736164494181168">"Abrir o menu"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-ro/strings.xml b/libs/WindowManager/Shell/res/values-ro/strings.xml
index 8f60979..58ad60a 100644
--- a/libs/WindowManager/Shell/res/values-ro/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ro/strings.xml
@@ -32,13 +32,13 @@
     <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Redimensionează"</string>
     <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Stochează"</string>
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Anulează stocarea"</string>
-    <string name="dock_forced_resizable" msgid="1749750436092293116">"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="7408396418008948957">"Aplicația nu acceptă ecranul împărțit."</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>
     <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Aplicația poate fi deschisă î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="703810061635792791">"Separator pentru ecranul împărțit"</string>
-    <string name="divider_title" msgid="5482989479865361192">"Separator pentru ecranul împărțit"</string>
+    <string name="accessibility_divider" msgid="6407584574218956849">"Separator pentru ecranul împărțit"</string>
+    <string name="divider_title" msgid="1963391955593749442">"Separator pentru ecranul împărțit"</string>
     <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Partea stângă pe ecran complet"</string>
     <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Partea stângă: 70%"</string>
     <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Partea stângă: 50%"</string>
@@ -49,14 +49,10 @@
     <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Partea de sus: 50%"</string>
     <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Partea de sus: 30%"</string>
     <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Partea de jos pe ecran complet"</string>
-    <!-- no translation found for accessibility_split_left (1713683765575562458) -->
-    <skip />
-    <!-- no translation found for accessibility_split_right (8441001008181296837) -->
-    <skip />
-    <!-- no translation found for accessibility_split_top (2789329702027147146) -->
-    <skip />
-    <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
-    <skip />
+    <string name="accessibility_split_left" msgid="1713683765575562458">"Împarte în stânga"</string>
+    <string name="accessibility_split_right" msgid="8441001008181296837">"Împarte în dreapta"</string>
+    <string name="accessibility_split_top" msgid="2789329702027147146">"Împarte în sus"</string>
+    <string name="accessibility_split_bottom" msgid="8694551025220868191">"Împarte în jos"</string>
     <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Folosirea modului cu o mână"</string>
     <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Pentru a ieși, glisează în sus din partea de jos a ecranului sau atinge oriunde deasupra ferestrei aplicației"</string>
     <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Activează modul cu o mână"</string>
@@ -83,12 +79,12 @@
     <string name="notification_bubble_title" msgid="6082910224488253378">"Balon"</string>
     <string name="manage_bubbles_text" msgid="7730624269650594419">"Gestionează"</string>
     <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Balonul a fost respins."</string>
-    <string name="restart_button_description" msgid="6712141648865547958">"Atinge ca să repornești aplicația pentru o vizualizare mai bună."</string>
+    <string name="restart_button_description" msgid="6712141648865547958">"Atinge ca să repornești aplicația pentru o afișare mai bună."</string>
     <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Ai probleme cu camera foto?\nAtinge pentru a reîncadra"</string>
     <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Nu ai remediat problema?\nAtinge pentru a reveni"</string>
     <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Nu ai probleme cu camera foto? Atinge pentru a închide."</string>
     <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Vezi și fă mai multe"</string>
-    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Trage în altă aplicație pentru a folosi ecranul împărțit"</string>
+    <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Trage în altă aplicație pentru a folosi ecranul împărțit"</string>
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Atinge de două ori lângă o aplicație pentru a o repoziționa"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Extinde pentru mai multe informații"</string>
@@ -97,14 +93,21 @@
     <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Anulează"</string>
     <string name="letterbox_restart_restart" msgid="8529976234412442973">"Repornește"</string>
     <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Nu mai afișa"</string>
+    <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Atinge de două ori\nca să muți aplicația"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"Maximizează"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimizează"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Închide"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Înapoi"</string>
     <string name="handle_text" msgid="1766582106752184456">"Ghidaj"</string>
+    <string name="app_icon_text" msgid="2823268023931811747">"Pictograma aplicației"</string>
     <string name="fullscreen_text" msgid="1162316685217676079">"Ecran complet"</string>
     <string name="desktop_text" msgid="1077633567027630454">"Modul desktop"</string>
     <string name="split_screen_text" msgid="1396336058129570886">"Ecran împărțit"</string>
     <string name="more_button_text" msgid="3655388105592893530">"Mai multe"</string>
     <string name="float_button_text" msgid="9221657008391364581">"Flotantă"</string>
+    <string name="select_text" msgid="5139083974039906583">"Selectează"</string>
+    <string name="screenshot_text" msgid="1477704010087786671">"Captură de ecran"</string>
+    <string name="close_text" msgid="4986518933445178928">"Închide"</string>
+    <string name="collapse_menu_text" msgid="7515008122450342029">"Închide meniul"</string>
+    <string name="expand_menu_text" msgid="3847736164494181168">"Deschide meniul"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-ru/strings.xml b/libs/WindowManager/Shell/res/values-ru/strings.xml
index 0c1e4a9..a7db44d 100644
--- a/libs/WindowManager/Shell/res/values-ru/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ru/strings.xml
@@ -32,13 +32,13 @@
     <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Изменить размер"</string>
     <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Скрыть"</string>
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Показать"</string>
-    <string name="dock_forced_resizable" msgid="1749750436092293116">"В режиме разделения экрана приложение может работать нестабильно."</string>
-    <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Приложение не поддерживает разделение экрана."</string>
+    <string name="dock_forced_resizable" msgid="7429086980048964687">"Когда включено разделение экрана, приложение может работать нестабильно."</string>
+    <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Приложение не поддерживает разделение экрана."</string>
     <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Это приложение можно открыть только в одном окне."</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="703810061635792791">"Разделитель экрана"</string>
-    <string name="divider_title" msgid="5482989479865361192">"Разделитель экрана"</string>
+    <string name="accessibility_divider" msgid="6407584574218956849">"Разделитель экрана"</string>
+    <string name="divider_title" msgid="1963391955593749442">"Разделитель экрана"</string>
     <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Левый во весь экран"</string>
     <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Левый на 70%"</string>
     <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Левый на 50%"</string>
@@ -49,14 +49,10 @@
     <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Верхний на 50%"</string>
     <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Верхний на 30%"</string>
     <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Нижний во весь экран"</string>
-    <!-- no translation found for accessibility_split_left (1713683765575562458) -->
-    <skip />
-    <!-- no translation found for accessibility_split_right (8441001008181296837) -->
-    <skip />
-    <!-- no translation found for accessibility_split_top (2789329702027147146) -->
-    <skip />
-    <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
-    <skip />
+    <string name="accessibility_split_left" msgid="1713683765575562458">"Приложение слева"</string>
+    <string name="accessibility_split_right" msgid="8441001008181296837">"Приложение справа"</string>
+    <string name="accessibility_split_top" msgid="2789329702027147146">"Приложение сверху"</string>
+    <string name="accessibility_split_bottom" msgid="8694551025220868191">"Приложение снизу"</string>
     <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Использование режима управления одной рукой"</string>
     <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Чтобы выйти, проведите по экрану снизу вверх или коснитесь области за пределами приложения."</string>
     <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Запустить режим управления одной рукой"</string>
@@ -88,7 +84,7 @@
     <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Не помогло?\nНажмите, чтобы отменить изменения."</string>
     <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Нет проблем с камерой? Нажмите, чтобы закрыть."</string>
     <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Выполняйте несколько задач одновременно"</string>
-    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Перетащите сюда другое приложение, чтобы использовать разделение экрана."</string>
+    <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Перетащите сюда другое приложение, чтобы использовать разделение экрана."</string>
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Чтобы переместить приложение, дважды нажмите рядом с ним."</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"ОК"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Развернуть, чтобы узнать больше."</string>
@@ -97,14 +93,21 @@
     <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Отмена"</string>
     <string name="letterbox_restart_restart" msgid="8529976234412442973">"Перезапустить"</string>
     <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Больше не показывать"</string>
+    <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Дважды нажмите, чтобы\nпереместить приложение."</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"Развернуть"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Свернуть"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Закрыть"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Назад"</string>
     <string name="handle_text" msgid="1766582106752184456">"Маркер"</string>
+    <string name="app_icon_text" msgid="2823268023931811747">"Значок приложения"</string>
     <string name="fullscreen_text" msgid="1162316685217676079">"Полноэкранный режим"</string>
     <string name="desktop_text" msgid="1077633567027630454">"Режим компьютера"</string>
     <string name="split_screen_text" msgid="1396336058129570886">"Разделить экран"</string>
     <string name="more_button_text" msgid="3655388105592893530">"Ещё"</string>
     <string name="float_button_text" msgid="9221657008391364581">"Плавающее окно"</string>
+    <string name="select_text" msgid="5139083974039906583">"Выбрать"</string>
+    <string name="screenshot_text" msgid="1477704010087786671">"Скриншот"</string>
+    <string name="close_text" msgid="4986518933445178928">"Закрыть"</string>
+    <string name="collapse_menu_text" msgid="7515008122450342029">"Закрыть меню"</string>
+    <string name="expand_menu_text" msgid="3847736164494181168">"Открыть меню"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-si/strings.xml b/libs/WindowManager/Shell/res/values-si/strings.xml
index 13ac518..4153ce2 100644
--- a/libs/WindowManager/Shell/res/values-si/strings.xml
+++ b/libs/WindowManager/Shell/res/values-si/strings.xml
@@ -32,13 +32,13 @@
     <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"ප්‍රතිප්‍රමාණ කරන්න"</string>
     <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"සඟවා තබන්න"</string>
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"සඟවා තැබීම ඉවත් කරන්න"</string>
-    <string name="dock_forced_resizable" msgid="1749750436092293116">"යෙදුම බෙදුම් තිරය සමග ක්‍රියා නොකළ හැකිය"</string>
-    <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"යෙදුම බෙදුණු-තිරය සඳහා සහාය නොදක්වයි."</string>
+    <string name="dock_forced_resizable" msgid="7429086980048964687">"යෙදුම බෙදීම් තිරය සමග ක්‍රියා නොකළ හැක"</string>
+    <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"යෙදුම බෙදුම් තිරයට සහාය නොදක්වයි"</string>
     <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"මෙම යෙදුම විවෘත කළ හැක්කේ 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="703810061635792791">"බෙදුම්-තිර වෙන්කරණය"</string>
-    <string name="divider_title" msgid="5482989479865361192">"බෙදුම්-තිර වෙන්කරණය"</string>
+    <string name="accessibility_divider" msgid="6407584574218956849">"බෙදුම් තිර වෙන්කරණය"</string>
+    <string name="divider_title" msgid="1963391955593749442">"බෙදුම් තිර වෙන්කරණය"</string>
     <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"වම් පූර්ණ තිරය"</string>
     <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"වම් 70%"</string>
     <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"වම් 50%"</string>
@@ -49,14 +49,10 @@
     <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"ඉහළම 50%"</string>
     <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"ඉහළම 30%"</string>
     <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"පහළ පූර්ණ තිරය"</string>
-    <!-- no translation found for accessibility_split_left (1713683765575562458) -->
-    <skip />
-    <!-- no translation found for accessibility_split_right (8441001008181296837) -->
-    <skip />
-    <!-- no translation found for accessibility_split_top (2789329702027147146) -->
-    <skip />
-    <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
-    <skip />
+    <string name="accessibility_split_left" msgid="1713683765575562458">"වම බෙදන්න"</string>
+    <string name="accessibility_split_right" msgid="8441001008181296837">"දකුණ බෙදන්න"</string>
+    <string name="accessibility_split_top" msgid="2789329702027147146">"ඉහළ බෙදන්න"</string>
+    <string name="accessibility_split_bottom" msgid="8694551025220868191">"පහළ බෙදන්න"</string>
     <string name="one_handed_tutorial_title" msgid="4583241688067426350">"තනි-අත් ප්‍රකාරය භාවිත කරමින්"</string>
     <string name="one_handed_tutorial_description" msgid="3486582858591353067">"පිටවීමට, තිරයේ පහළ සිට ඉහළට ස්වයිප් කරන්න හෝ යෙදුමට ඉහළින් ඕනෑම තැනක තට්ටු කරන්න"</string>
     <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"තනි අත් ප්‍රකාරය ආරම්භ කරන්න"</string>
@@ -88,7 +84,7 @@
     <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"එය විසඳුවේ නැතිද?\nප්‍රතිවර්තනය කිරීමට තට්ටු කරන්න"</string>
     <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"කැමරා ගැටලු නොමැතිද? ඉවත දැමීමට තට්ටු කරන්න"</string>
     <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"බලන්න සහ තවත් දේ කරන්න"</string>
-    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"බෙදුම් තිරය සඳහා වෙනත් යෙදුමකට අදින්න"</string>
+    <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"බෙදුම් තිරය සඳහා වෙනත් යෙදුමකට අදින්න"</string>
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"යෙදුමක් නැවත ස්ථානගත කිරීමට පිටතින් දෙවරක් තට්ටු කරන්න"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"තේරුණා"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"වැඩිදුර තොරතුරු සඳහා දිග හරින්න"</string>
@@ -97,14 +93,21 @@
     <string name="letterbox_restart_cancel" msgid="1342209132692537805">"අවලංගු කරන්න"</string>
     <string name="letterbox_restart_restart" msgid="8529976234412442973">"යළි අරඹන්න"</string>
     <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"නැවත නොපෙන්වන්න"</string>
+    <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"මෙම යෙදුම ගෙන යාමට\nදෙවරක් තට්ටු කරන්න"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"විහිදන්න"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"කුඩා කරන්න"</string>
     <string name="close_button_text" msgid="2913281996024033299">"වසන්න"</string>
     <string name="back_button_text" msgid="1469718707134137085">"ආපසු"</string>
     <string name="handle_text" msgid="1766582106752184456">"හැඬලය"</string>
+    <string name="app_icon_text" msgid="2823268023931811747">"යෙදුම් නිරූපකය"</string>
     <string name="fullscreen_text" msgid="1162316685217676079">"පූර්ණ තිරය"</string>
     <string name="desktop_text" msgid="1077633567027630454">"ඩෙස්ක්ටොප් ප්‍රකාරය"</string>
     <string name="split_screen_text" msgid="1396336058129570886">"බෙදුම් තිරය"</string>
     <string name="more_button_text" msgid="3655388105592893530">"තව"</string>
     <string name="float_button_text" msgid="9221657008391364581">"පාවෙන"</string>
+    <string name="select_text" msgid="5139083974039906583">"තෝරන්න"</string>
+    <string name="screenshot_text" msgid="1477704010087786671">"තිර රුව"</string>
+    <string name="close_text" msgid="4986518933445178928">"වසන්න"</string>
+    <string name="collapse_menu_text" msgid="7515008122450342029">"මෙනුව වසන්න"</string>
+    <string name="expand_menu_text" msgid="3847736164494181168">"මෙනුව විවෘත කරන්න"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-sk/strings.xml b/libs/WindowManager/Shell/res/values-sk/strings.xml
index c91856c..4e38943 100644
--- a/libs/WindowManager/Shell/res/values-sk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sk/strings.xml
@@ -32,13 +32,13 @@
     <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Zmeniť veľkosť"</string>
     <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Skryť"</string>
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Zrušiť skrytie"</string>
-    <string name="dock_forced_resizable" msgid="1749750436092293116">"Aplikácia nemusí fungovať s rozdelenou obrazovkou."</string>
-    <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Aplikácia nepodporuje rozdelenú obrazovku."</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>
     <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"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="703810061635792791">"Rozdeľovač obrazovky"</string>
-    <string name="divider_title" msgid="5482989479865361192">"Rozdeľovač obrazovky"</string>
+    <string name="accessibility_divider" msgid="6407584574218956849">"Rozdeľovač obrazovky"</string>
+    <string name="divider_title" msgid="1963391955593749442">"Rozdeľovač obrazovky"</string>
     <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Ľavá – na celú obrazovku"</string>
     <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Ľavá – 70 %"</string>
     <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Ľavá – 50 %"</string>
@@ -49,14 +49,10 @@
     <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Horná – 50 %"</string>
     <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Horná – 30 %"</string>
     <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Dolná – na celú obrazovku"</string>
-    <!-- no translation found for accessibility_split_left (1713683765575562458) -->
-    <skip />
-    <!-- no translation found for accessibility_split_right (8441001008181296837) -->
-    <skip />
-    <!-- no translation found for accessibility_split_top (2789329702027147146) -->
-    <skip />
-    <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
-    <skip />
+    <string name="accessibility_split_left" msgid="1713683765575562458">"Rozdeliť vľavo"</string>
+    <string name="accessibility_split_right" msgid="8441001008181296837">"Rozdeliť vpravo"</string>
+    <string name="accessibility_split_top" msgid="2789329702027147146">"Rozdeliť hore"</string>
+    <string name="accessibility_split_bottom" msgid="8694551025220868191">"Rozdeliť dole"</string>
     <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Používanie režimu jednej ruky"</string>
     <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Ukončíte potiahnutím z dolnej časti obrazovky nahor alebo klepnutím kdekoľvek nad aplikáciu"</string>
     <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Spustiť režim jednej ruky"</string>
@@ -88,7 +84,7 @@
     <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Nevyriešilo sa to?\nKlepnutím sa vráťte."</string>
     <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Nemáte problémy s kamerou? Klepnutím zatvoríte."</string>
     <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Zobrazte si a zvládnite toho viac"</string>
-    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Rozdelenú obrazovku aktivujete presunutím ďalšie aplikácie"</string>
+    <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Rozdelenú obrazovku môžete použiť presunutím do inej aplikácie"</string>
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Dvojitým klepnutím mimo aplikácie zmeníte jej pozíciu"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"Dobre"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Po rozbalení sa dozviete viac."</string>
@@ -97,14 +93,21 @@
     <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Zrušiť"</string>
     <string name="letterbox_restart_restart" msgid="8529976234412442973">"Reštartovať"</string>
     <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Už nezobrazovať"</string>
+    <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Túto aplikáciu\npresuniete dvojitým klepnutím"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"Maximalizovať"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimalizovať"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Zavrieť"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Späť"</string>
     <string name="handle_text" msgid="1766582106752184456">"Rukoväť"</string>
+    <string name="app_icon_text" msgid="2823268023931811747">"Ikona aplikácie"</string>
     <string name="fullscreen_text" msgid="1162316685217676079">"Celá obrazovka"</string>
     <string name="desktop_text" msgid="1077633567027630454">"Režim počítača"</string>
     <string name="split_screen_text" msgid="1396336058129570886">"Rozdelená obrazovka"</string>
     <string name="more_button_text" msgid="3655388105592893530">"Viac"</string>
     <string name="float_button_text" msgid="9221657008391364581">"Plávajúce"</string>
+    <string name="select_text" msgid="5139083974039906583">"Vybrať"</string>
+    <string name="screenshot_text" msgid="1477704010087786671">"Snímka obrazovky"</string>
+    <string name="close_text" msgid="4986518933445178928">"Zavrieť"</string>
+    <string name="collapse_menu_text" msgid="7515008122450342029">"Zavrieť ponuku"</string>
+    <string name="expand_menu_text" msgid="3847736164494181168">"Otvoriť ponuku"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-sl/strings.xml b/libs/WindowManager/Shell/res/values-sl/strings.xml
index 744492c..b0e67a7 100644
--- a/libs/WindowManager/Shell/res/values-sl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sl/strings.xml
@@ -32,13 +32,13 @@
     <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Spremeni velikost"</string>
     <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Zakrij"</string>
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Razkrij"</string>
-    <string name="dock_forced_resizable" msgid="1749750436092293116">"Aplikacija morda ne deluje v načinu razdeljenega zaslona."</string>
-    <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Aplikacija ne podpira načina razdeljenega zaslona."</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>
     <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"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="703810061635792791">"Razdelilnik zaslonov"</string>
-    <string name="divider_title" msgid="5482989479865361192">"Razdelilnik zaslonov"</string>
+    <string name="accessibility_divider" msgid="6407584574218956849">"Razdelilnik zaslonov"</string>
+    <string name="divider_title" msgid="1963391955593749442">"Razdelilnik zaslonov"</string>
     <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Levi v celozaslonski način"</string>
     <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Levi 70 %"</string>
     <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Levi 50 %"</string>
@@ -49,14 +49,10 @@
     <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Zgornji 50 %"</string>
     <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Zgornji 30 %"</string>
     <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Spodnji v celozaslonski način"</string>
-    <!-- no translation found for accessibility_split_left (1713683765575562458) -->
-    <skip />
-    <!-- no translation found for accessibility_split_right (8441001008181296837) -->
-    <skip />
-    <!-- no translation found for accessibility_split_top (2789329702027147146) -->
-    <skip />
-    <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
-    <skip />
+    <string name="accessibility_split_left" msgid="1713683765575562458">"Delitev levo"</string>
+    <string name="accessibility_split_right" msgid="8441001008181296837">"Delitev desno"</string>
+    <string name="accessibility_split_top" msgid="2789329702027147146">"Delitev zgoraj"</string>
+    <string name="accessibility_split_bottom" msgid="8694551025220868191">"Delitev spodaj"</string>
     <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Uporaba enoročnega načina"</string>
     <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Za izhod povlecite z dna zaslona navzgor ali se dotaknite na poljubnem mestu nad aplikacijo"</string>
     <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Zagon enoročnega načina"</string>
@@ -88,7 +84,7 @@
     <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"To ni odpravilo težave?\nDotaknite se za povrnitev"</string>
     <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Nimate težav s fotoaparatom? Dotaknite se za opustitev."</string>
     <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Oglejte si in naredite več"</string>
-    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Za razdeljeni zaslon povlecite sem še eno aplikacijo."</string>
+    <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Za razdeljeni zaslon povlecite sem še eno aplikacijo."</string>
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Dvakrat se dotaknite zunaj aplikacije, če jo želite prestaviti."</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"V redu"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Razširitev za več informacij"</string>
@@ -97,14 +93,21 @@
     <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Prekliči"</string>
     <string name="letterbox_restart_restart" msgid="8529976234412442973">"Znova zaženi"</string>
     <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Ne prikaži več"</string>
+    <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Dvakrat se dotaknite\nza premik te aplikacije"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"Maksimiraj"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimiraj"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Zapri"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Nazaj"</string>
     <string name="handle_text" msgid="1766582106752184456">"Ročica"</string>
+    <string name="app_icon_text" msgid="2823268023931811747">"Ikona aplikacije"</string>
     <string name="fullscreen_text" msgid="1162316685217676079">"Celozaslonsko"</string>
     <string name="desktop_text" msgid="1077633567027630454">"Namizni način"</string>
     <string name="split_screen_text" msgid="1396336058129570886">"Razdeljen zaslon"</string>
     <string name="more_button_text" msgid="3655388105592893530">"Več"</string>
     <string name="float_button_text" msgid="9221657008391364581">"Lebdeče"</string>
+    <string name="select_text" msgid="5139083974039906583">"Izberi"</string>
+    <string name="screenshot_text" msgid="1477704010087786671">"Posnetek zaslona"</string>
+    <string name="close_text" msgid="4986518933445178928">"Zapri"</string>
+    <string name="collapse_menu_text" msgid="7515008122450342029">"Zapri meni"</string>
+    <string name="expand_menu_text" msgid="3847736164494181168">"Odpri meni"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-sq/strings.xml b/libs/WindowManager/Shell/res/values-sq/strings.xml
index 9afbbba..29bfb92 100644
--- a/libs/WindowManager/Shell/res/values-sq/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sq/strings.xml
@@ -32,13 +32,13 @@
     <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Ndrysho përmasat"</string>
     <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Fshih"</string>
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Mos e fshih"</string>
-    <string name="dock_forced_resizable" msgid="1749750436092293116">"Aplikacioni mund të mos funksionojë me ekranin e ndarë."</string>
-    <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Aplikacioni nuk mbështet ekranin e ndarë."</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>
     <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"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="703810061635792791">"Ndarësi i ekranit të ndarë"</string>
-    <string name="divider_title" msgid="5482989479865361192">"Ndarësi i ekranit të ndarë"</string>
+    <string name="accessibility_divider" msgid="6407584574218956849">"Ndarësi i ekranit të ndarë"</string>
+    <string name="divider_title" msgid="1963391955593749442">"Ndarësi i ekranit të ndarë"</string>
     <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Ekrani i plotë majtas"</string>
     <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Majtas 70%"</string>
     <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Majtas 50%"</string>
@@ -49,14 +49,10 @@
     <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Lart 50%"</string>
     <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Lart 30%"</string>
     <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Ekrani i plotë poshtë"</string>
-    <!-- no translation found for accessibility_split_left (1713683765575562458) -->
-    <skip />
-    <!-- no translation found for accessibility_split_right (8441001008181296837) -->
-    <skip />
-    <!-- no translation found for accessibility_split_top (2789329702027147146) -->
-    <skip />
-    <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
-    <skip />
+    <string name="accessibility_split_left" msgid="1713683765575562458">"Ndaj majtas"</string>
+    <string name="accessibility_split_right" msgid="8441001008181296837">"Ndaj djathtas"</string>
+    <string name="accessibility_split_top" msgid="2789329702027147146">"Ndaj lart"</string>
+    <string name="accessibility_split_bottom" msgid="8694551025220868191">"Ndaj në fund"</string>
     <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Po përdor modalitetin e përdorimit me një dorë"</string>
     <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Për të dalë, rrëshqit lart nga fundi i ekranit ose trokit diku mbi aplikacion"</string>
     <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Modaliteti i përdorimit me një dorë"</string>
@@ -88,7 +84,7 @@
     <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Nuk u rregullua?\nTrokit për ta rikthyer"</string>
     <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Nuk ka probleme me kamerën? Trokit për ta shpërfillur."</string>
     <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Shiko dhe bëj më shumë"</string>
-    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Zvarrite në një aplikacion tjetër për ekranin e ndarë"</string>
+    <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Zvarrite në një aplikacion tjetër për ekranin e ndarë"</string>
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Trokit dy herë jashtë një aplikacioni për ta ripozicionuar"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"E kuptova"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Zgjeroje për më shumë informacion."</string>
@@ -97,14 +93,21 @@
     <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Anulo"</string>
     <string name="letterbox_restart_restart" msgid="8529976234412442973">"Rinis"</string>
     <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Mos e shfaq përsëri"</string>
+    <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Trokit dy herë për të\nlëvizur këtë aplikacion"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"Maksimizo"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimizo"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Mbyll"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Pas"</string>
     <string name="handle_text" msgid="1766582106752184456">"Emërtimi"</string>
+    <string name="app_icon_text" msgid="2823268023931811747">"Ikona e aplikacionit"</string>
     <string name="fullscreen_text" msgid="1162316685217676079">"Ekrani i plotë"</string>
     <string name="desktop_text" msgid="1077633567027630454">"Modaliteti i desktopit"</string>
     <string name="split_screen_text" msgid="1396336058129570886">"Ekrani i ndarë"</string>
     <string name="more_button_text" msgid="3655388105592893530">"Më shumë"</string>
     <string name="float_button_text" msgid="9221657008391364581">"Pluskuese"</string>
+    <string name="select_text" msgid="5139083974039906583">"Zgjidh"</string>
+    <string name="screenshot_text" msgid="1477704010087786671">"Pamja e ekranit"</string>
+    <string name="close_text" msgid="4986518933445178928">"Mbyll"</string>
+    <string name="collapse_menu_text" msgid="7515008122450342029">"Mbyll menynë"</string>
+    <string name="expand_menu_text" msgid="3847736164494181168">"Hap menynë"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-sr/strings.xml b/libs/WindowManager/Shell/res/values-sr/strings.xml
index c252fd7..85798cf 100644
--- a/libs/WindowManager/Shell/res/values-sr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sr/strings.xml
@@ -32,13 +32,13 @@
     <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Промените величину"</string>
     <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Ставите у тајну меморију"</string>
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Уклоните из тајне меморије"</string>
-    <string name="dock_forced_resizable" msgid="1749750436092293116">"Апликација можда неће радити са подељеним екраном."</string>
-    <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Апликација не подржава подељени екран."</string>
+    <string name="dock_forced_resizable" msgid="7429086980048964687">"Апликација можда неће радити са подељеним екраном."</string>
+    <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Апликација не подржава подељени екран."</string>
     <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Ова апликација може да се отвори само у једном прозору."</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="703810061635792791">"Разделник подељеног екрана"</string>
-    <string name="divider_title" msgid="5482989479865361192">"Разделник подељеног екрана"</string>
+    <string name="accessibility_divider" msgid="6407584574218956849">"Разделник подељеног екрана"</string>
+    <string name="divider_title" msgid="1963391955593749442">"Разделник подељеног екрана"</string>
     <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Режим целог екрана за леви екран"</string>
     <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Леви екран 70%"</string>
     <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Леви екран 50%"</string>
@@ -49,14 +49,10 @@
     <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Горњи екран 50%"</string>
     <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Горњи екран 30%"</string>
     <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Режим целог екрана за доњи екран"</string>
-    <!-- no translation found for accessibility_split_left (1713683765575562458) -->
-    <skip />
-    <!-- no translation found for accessibility_split_right (8441001008181296837) -->
-    <skip />
-    <!-- no translation found for accessibility_split_top (2789329702027147146) -->
-    <skip />
-    <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
-    <skip />
+    <string name="accessibility_split_left" msgid="1713683765575562458">"Поделите лево"</string>
+    <string name="accessibility_split_right" msgid="8441001008181296837">"Поделите десно"</string>
+    <string name="accessibility_split_top" msgid="2789329702027147146">"Поделите у врху"</string>
+    <string name="accessibility_split_bottom" msgid="8694551025220868191">"Поделите у дну"</string>
     <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Коришћење режима једном руком"</string>
     <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Да бисте изашли, превуците нагоре од дна екрана или додирните било где изнад апликације"</string>
     <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Покрените режим једном руком"</string>
@@ -88,23 +84,30 @@
     <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Проблем није решен?\nДодирните да бисте вратили"</string>
     <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Немате проблема са камером? Додирните да бисте одбацили."</string>
     <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Видите и урадите више"</string>
-    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Превуците другу апликацију да бисте користили подељени екран"</string>
+    <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Превуците другу апликацију да бисте користили подељени екран"</string>
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Двапут додирните изван апликације да бисте променили њену позицију"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"Важи"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Проширите за још информација."</string>
     <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Желите ли да рестартујете ради бољег приказа?"</string>
-    <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Можете да рестартујете апликацију да би изгледала боље на екрану, с тим што можете да изгубите оно што сте урадили или несачуване промене, ако их има"</string>
+    <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Можете да рестартујете апликацију да би изгледала боље на екрану, али можете да изгубите напредак или несачуване промене"</string>
     <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Откажи"</string>
     <string name="letterbox_restart_restart" msgid="8529976234412442973">"Рестартуј"</string>
     <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Не приказуј поново"</string>
+    <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Двапут додирните да бисте\nпреместили ову апликацију"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"Увећајте"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Умањите"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Затворите"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Назад"</string>
     <string name="handle_text" msgid="1766582106752184456">"Идентификатор"</string>
+    <string name="app_icon_text" msgid="2823268023931811747">"Икона апликације"</string>
     <string name="fullscreen_text" msgid="1162316685217676079">"Преко целог екрана"</string>
     <string name="desktop_text" msgid="1077633567027630454">"Режим за рачунаре"</string>
     <string name="split_screen_text" msgid="1396336058129570886">"Подељени екран"</string>
     <string name="more_button_text" msgid="3655388105592893530">"Још"</string>
     <string name="float_button_text" msgid="9221657008391364581">"Плутајуће"</string>
+    <string name="select_text" msgid="5139083974039906583">"Изаберите"</string>
+    <string name="screenshot_text" msgid="1477704010087786671">"Снимак екрана"</string>
+    <string name="close_text" msgid="4986518933445178928">"Затворите"</string>
+    <string name="collapse_menu_text" msgid="7515008122450342029">"Затворите мени"</string>
+    <string name="expand_menu_text" msgid="3847736164494181168">"Отворите мени"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-sv/strings.xml b/libs/WindowManager/Shell/res/values-sv/strings.xml
index 92622cb..33652cd 100644
--- a/libs/WindowManager/Shell/res/values-sv/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sv/strings.xml
@@ -32,13 +32,13 @@
     <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Ändra storlek"</string>
     <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Utför stash"</string>
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Återställ stash"</string>
-    <string name="dock_forced_resizable" msgid="1749750436092293116">"Appen kanske inte fungerar med delad skärm."</string>
-    <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Appen har inte stöd för delad skärm."</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>
     <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"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="703810061635792791">"Avdelare för delad skärm"</string>
-    <string name="divider_title" msgid="5482989479865361192">"Avdelare för delad skärm"</string>
+    <string name="accessibility_divider" msgid="6407584574218956849">"Avdelare för delad skärm"</string>
+    <string name="divider_title" msgid="1963391955593749442">"Avdelare för delad skärm"</string>
     <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Helskärm på vänster skärm"</string>
     <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Vänster 70 %"</string>
     <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Vänster 50 %"</string>
@@ -49,14 +49,10 @@
     <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Övre 50 %"</string>
     <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Övre 30 %"</string>
     <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Helskärm på nedre skärm"</string>
-    <!-- no translation found for accessibility_split_left (1713683765575562458) -->
-    <skip />
-    <!-- no translation found for accessibility_split_right (8441001008181296837) -->
-    <skip />
-    <!-- no translation found for accessibility_split_top (2789329702027147146) -->
-    <skip />
-    <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
-    <skip />
+    <string name="accessibility_split_left" msgid="1713683765575562458">"Till vänster på delad skärm"</string>
+    <string name="accessibility_split_right" msgid="8441001008181296837">"Till höger på delad skärm"</string>
+    <string name="accessibility_split_top" msgid="2789329702027147146">"Upptill på delad skärm"</string>
+    <string name="accessibility_split_bottom" msgid="8694551025220868191">"Nedtill på delad skärm"</string>
     <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Använda enhandsläge"</string>
     <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Avsluta genom att svepa uppåt från skärmens nederkant eller trycka ovanför appen"</string>
     <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Starta enhandsläge"</string>
@@ -88,7 +84,7 @@
     <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Löstes inte problemet?\nTryck för att återställa"</string>
     <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Inga problem med kameran? Tryck för att ignorera."</string>
     <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Se och gör mer"</string>
-    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Dra till en annan app för läget Delad skärm"</string>
+    <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Dra till en annan app för att dela upp skärmen"</string>
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Tryck snabbt två gånger utanför en app för att flytta den"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Utöka för mer information."</string>
@@ -97,14 +93,21 @@
     <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Avbryt"</string>
     <string name="letterbox_restart_restart" msgid="8529976234412442973">"Starta om"</string>
     <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Visa inte igen"</string>
+    <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Tryck snabbt två gånger\nför att flytta denna app"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"Utöka"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimera"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Stäng"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Tillbaka"</string>
     <string name="handle_text" msgid="1766582106752184456">"Handtag"</string>
+    <string name="app_icon_text" msgid="2823268023931811747">"Appikon"</string>
     <string name="fullscreen_text" msgid="1162316685217676079">"Helskärm"</string>
     <string name="desktop_text" msgid="1077633567027630454">"Datorläge"</string>
     <string name="split_screen_text" msgid="1396336058129570886">"Delad skärm"</string>
     <string name="more_button_text" msgid="3655388105592893530">"Mer"</string>
     <string name="float_button_text" msgid="9221657008391364581">"Svävande"</string>
+    <string name="select_text" msgid="5139083974039906583">"Välj"</string>
+    <string name="screenshot_text" msgid="1477704010087786671">"Skärmbild"</string>
+    <string name="close_text" msgid="4986518933445178928">"Stäng"</string>
+    <string name="collapse_menu_text" msgid="7515008122450342029">"Stäng menyn"</string>
+    <string name="expand_menu_text" msgid="3847736164494181168">"Öppna menyn"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-sw/strings.xml b/libs/WindowManager/Shell/res/values-sw/strings.xml
index 6d92040..fe2ad1f 100644
--- a/libs/WindowManager/Shell/res/values-sw/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sw/strings.xml
@@ -32,13 +32,13 @@
     <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Badilisha ukubwa"</string>
     <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Ficha"</string>
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Fichua"</string>
-    <string name="dock_forced_resizable" msgid="1749750436092293116">"Huenda programu isifanye kazi kwenye skrini inayogawanywa."</string>
-    <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Programu haiwezi kutumia skrini iliyogawanywa."</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>
     <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"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="703810061635792791">"Kitenganishi cha skrini inayogawanywa"</string>
-    <string name="divider_title" msgid="5482989479865361192">"Kitenganishi cha kugawa skrini"</string>
+    <string name="accessibility_divider" msgid="6407584574218956849">"Kitenganishi cha kugawa skrini"</string>
+    <string name="divider_title" msgid="1963391955593749442">"Kitenganishi cha kugawa skrini"</string>
     <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Skrini nzima ya kushoto"</string>
     <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Kushoto 70%"</string>
     <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Kushoto 50%"</string>
@@ -49,14 +49,10 @@
     <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Juu 50%"</string>
     <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Juu 30%"</string>
     <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Skrini nzima ya chini"</string>
-    <!-- no translation found for accessibility_split_left (1713683765575562458) -->
-    <skip />
-    <!-- no translation found for accessibility_split_right (8441001008181296837) -->
-    <skip />
-    <!-- no translation found for accessibility_split_top (2789329702027147146) -->
-    <skip />
-    <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
-    <skip />
+    <string name="accessibility_split_left" msgid="1713683765575562458">"Gawanya sehemu ya kushoto"</string>
+    <string name="accessibility_split_right" msgid="8441001008181296837">"Gawanya sehemu ya kulia"</string>
+    <string name="accessibility_split_top" msgid="2789329702027147146">"Gawanya sehemu ya juu"</string>
+    <string name="accessibility_split_bottom" msgid="8694551025220868191">"Gawanya sehemu ya chini"</string>
     <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Kutumia hali ya kutumia kwa mkono mmoja"</string>
     <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Ili ufunge, telezesha kidole juu kutoka sehemu ya chini ya skrini au uguse mahali popote juu ya programu"</string>
     <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Anzisha hali ya kutumia kwa mkono mmoja"</string>
@@ -88,7 +84,7 @@
     <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Umeshindwa kurekebisha?\nGusa ili urejeshe nakala ya awali"</string>
     <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Je, hakuna hitilafu za kamera? Gusa ili uondoe."</string>
     <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Angalia na ufanye zaidi"</string>
-    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Buruta ndani programu nyingine ili utumie hali ya skrini iliyogawanywa"</string>
+    <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Buruta katika programu nyingine ili utumie skrini iliyogawanywa"</string>
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Gusa mara mbili nje ya programu ili uihamishe"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"Nimeelewa"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Panua ili upate maelezo zaidi."</string>
@@ -97,14 +93,21 @@
     <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Ghairi"</string>
     <string name="letterbox_restart_restart" msgid="8529976234412442973">"Zima kisha uwashe"</string>
     <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Usionyeshe tena"</string>
+    <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Gusa mara mbili ili\nusogeze programu hii"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"Panua"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Punguza"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Funga"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Rudi nyuma"</string>
     <string name="handle_text" msgid="1766582106752184456">"Ncha"</string>
+    <string name="app_icon_text" msgid="2823268023931811747">"Aikoni ya Programu"</string>
     <string name="fullscreen_text" msgid="1162316685217676079">"Skrini nzima"</string>
     <string name="desktop_text" msgid="1077633567027630454">"Hali ya Kompyuta ya mezani"</string>
     <string name="split_screen_text" msgid="1396336058129570886">"Gawa Skrini"</string>
     <string name="more_button_text" msgid="3655388105592893530">"Zaidi"</string>
     <string name="float_button_text" msgid="9221657008391364581">"Inayoelea"</string>
+    <string name="select_text" msgid="5139083974039906583">"Chagua"</string>
+    <string name="screenshot_text" msgid="1477704010087786671">"Picha ya skrini"</string>
+    <string name="close_text" msgid="4986518933445178928">"Funga"</string>
+    <string name="collapse_menu_text" msgid="7515008122450342029">"Funga Menyu"</string>
+    <string name="expand_menu_text" msgid="3847736164494181168">"Fungua Menyu"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-ta/strings.xml b/libs/WindowManager/Shell/res/values-ta/strings.xml
index 8cf631b..5bb4c27 100644
--- a/libs/WindowManager/Shell/res/values-ta/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ta/strings.xml
@@ -32,13 +32,13 @@
     <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"அளவு மாற்று"</string>
     <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Stash"</string>
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Unstash"</string>
-    <string name="dock_forced_resizable" msgid="1749750436092293116">"திரைப் பிரிப்பு அம்சத்தில் ஆப்ஸ் செயல்படாமல் போகக்கூடும்."</string>
-    <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"திரையைப் பிரிப்பதைப் ஆப்ஸ் ஆதரிக்கவில்லை."</string>
+    <string name="dock_forced_resizable" msgid="7429086980048964687">"திரைப் பிரிப்புப் பயன்முறையில் ஆப்ஸ் செயல்படாமல் போகக்கூடும்"</string>
+    <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"திரைப் பிரிப்புப் பயன்முறையை ஆப்ஸ் ஆதரிக்காது"</string>
     <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"இந்த ஆப்ஸை 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="703810061635792791">"திரையைப் பிரிக்கும் பிரிப்பான்"</string>
-    <string name="divider_title" msgid="5482989479865361192">"திரைப் பிரிப்பான்"</string>
+    <string name="accessibility_divider" msgid="6407584574218956849">"திரைப் பிரிப்பான்"</string>
+    <string name="divider_title" msgid="1963391955593749442">"திரைப் பிரிப்பான்"</string>
     <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"இடது புறம் முழுத் திரை"</string>
     <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"இடது புறம் 70%"</string>
     <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"இடது புறம் 50%"</string>
@@ -49,14 +49,10 @@
     <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"மேலே 50%"</string>
     <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"மேலே 30%"</string>
     <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"கீழ்ப்புறம் முழுத் திரை"</string>
-    <!-- no translation found for accessibility_split_left (1713683765575562458) -->
-    <skip />
-    <!-- no translation found for accessibility_split_right (8441001008181296837) -->
-    <skip />
-    <!-- no translation found for accessibility_split_top (2789329702027147146) -->
-    <skip />
-    <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
-    <skip />
+    <string name="accessibility_split_left" msgid="1713683765575562458">"இடதுபுறமாகப் பிரிக்கும்"</string>
+    <string name="accessibility_split_right" msgid="8441001008181296837">"வலதுபுறமாகப் பிரிக்கும்"</string>
+    <string name="accessibility_split_top" msgid="2789329702027147146">"மேற்புறமாகப் பிரிக்கும்"</string>
+    <string name="accessibility_split_bottom" msgid="8694551025220868191">"கீழ்புறமாகப் பிரிக்கும்"</string>
     <string name="one_handed_tutorial_title" msgid="4583241688067426350">"ஒற்றைக் கைப் பயன்முறையைப் பயன்படுத்துதல்"</string>
     <string name="one_handed_tutorial_description" msgid="3486582858591353067">"வெளியேற, திரையின் கீழிருந்து மேல்நோக்கி ஸ்வைப் செய்யவும் அல்லது ஆப்ஸுக்கு மேலே ஏதேனும் ஓர் இடத்தில் தட்டவும்"</string>
     <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"ஒற்றைக் கைப் பயன்முறையைத் தொடங்கும்"</string>
@@ -83,12 +79,12 @@
     <string name="notification_bubble_title" msgid="6082910224488253378">"பபிள்"</string>
     <string name="manage_bubbles_text" msgid="7730624269650594419">"நிர்வகி"</string>
     <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"குமிழ் நிராகரிக்கப்பட்டது."</string>
-    <string name="restart_button_description" msgid="6712141648865547958">"இங்கு தட்டுவதன் மூலம் இந்த ஆப்ஸை மீண்டும் தொடங்கி, ஆப்ஸ் காட்டப்படும் விதத்தை இன்னும் சிறப்பாக்கலாம்."</string>
+    <string name="restart_button_description" msgid="6712141648865547958">"இங்கு தட்டி ஆப்ஸை மீண்டும் தொடங்கி, ஆப்ஸ் காட்சியை இன்னும் சிறப்பாக்கலாம்."</string>
     <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"கேமரா தொடர்பான சிக்கல்களா?\nமீண்டும் பொருத்த தட்டவும்"</string>
     <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"சிக்கல்கள் சரிசெய்யப்படவில்லையா?\nமாற்றியமைக்க தட்டவும்"</string>
     <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"கேமரா தொடர்பான சிக்கல்கள் எதுவும் இல்லையா? நிராகரிக்க தட்டவும்."</string>
     <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"பலவற்றைப் பார்த்தல் மற்றும் செய்தல்"</string>
-    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"திரைப் பிரிப்புக்கு மற்றொரு ஆப்ஸை இழுக்கலாம்"</string>
+    <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"திரைப் பிரிப்புக்கு மற்றொரு ஆப்ஸை இழுக்கலாம்"</string>
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"ஆப்ஸை இடம் மாற்ற அதன் வெளியில் இருமுறை தட்டலாம்"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"சரி"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"கூடுதல் தகவல்களுக்கு விரிவாக்கலாம்."</string>
@@ -97,14 +93,21 @@
     <string name="letterbox_restart_cancel" msgid="1342209132692537805">"ரத்துசெய்"</string>
     <string name="letterbox_restart_restart" msgid="8529976234412442973">"மீண்டும் தொடங்கு"</string>
     <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"மீண்டும் காட்டாதே"</string>
+    <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"இந்த ஆப்ஸை நகர்த்த\nஇருமுறை தட்டவும்"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"பெரிதாக்கும்"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"சிறிதாக்கும்"</string>
     <string name="close_button_text" msgid="2913281996024033299">"மூடும்"</string>
     <string name="back_button_text" msgid="1469718707134137085">"பின்செல்லும்"</string>
     <string name="handle_text" msgid="1766582106752184456">"ஹேண்டில்"</string>
+    <string name="app_icon_text" msgid="2823268023931811747">"ஆப்ஸ் ஐகான்"</string>
     <string name="fullscreen_text" msgid="1162316685217676079">"முழுத்திரை"</string>
     <string name="desktop_text" msgid="1077633567027630454">"டெஸ்க்டாப் பயன்முறை"</string>
     <string name="split_screen_text" msgid="1396336058129570886">"திரையைப் பிரிக்கும்"</string>
     <string name="more_button_text" msgid="3655388105592893530">"கூடுதல் விருப்பத்தேர்வுகள்"</string>
     <string name="float_button_text" msgid="9221657008391364581">"மிதக்கும் சாளரம்"</string>
+    <string name="select_text" msgid="5139083974039906583">"தேர்ந்தெடுக்கும்"</string>
+    <string name="screenshot_text" msgid="1477704010087786671">"ஸ்கிரீன்ஷாட்"</string>
+    <string name="close_text" msgid="4986518933445178928">"மூடும்"</string>
+    <string name="collapse_menu_text" msgid="7515008122450342029">"மெனுவை மூடும்"</string>
+    <string name="expand_menu_text" msgid="3847736164494181168">"மெனுவைத் திற"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-te/strings.xml b/libs/WindowManager/Shell/res/values-te/strings.xml
index a4dcd95..6f95aa9 100644
--- a/libs/WindowManager/Shell/res/values-te/strings.xml
+++ b/libs/WindowManager/Shell/res/values-te/strings.xml
@@ -32,13 +32,13 @@
     <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"సైజ్‌ మార్చు"</string>
     <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"స్టాచ్"</string>
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"ఆన్‌స్టాచ్"</string>
-    <string name="dock_forced_resizable" msgid="1749750436092293116">"స్క్రీన్ విభజనతో యాప్‌ పని చేయకపోవచ్చు."</string>
-    <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"యాప్‌లో స్క్రీన్ విభజనకు మద్దతు లేదు."</string>
+    <string name="dock_forced_resizable" msgid="7429086980048964687">"స్ప్లిట్ స్క్రీన్‌తో యాప్ పని చేయకపోవచ్చు"</string>
+    <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"యాప్‌లో స్ప్లిట్ స్క్రీన్‌కు సపోర్ట్ లేదు"</string>
     <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"ఈ యాప్‌ను 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="703810061635792791">"విభజన స్క్రీన్ విభాగిని"</string>
-    <string name="divider_title" msgid="5482989479865361192">"స్ప్లిట్ స్క్రీన్ డివైడర్"</string>
+    <string name="accessibility_divider" msgid="6407584574218956849">"స్ప్లిట్ స్క్రీన్ డివైడర్"</string>
+    <string name="divider_title" msgid="1963391955593749442">"స్ప్లిట్ స్క్రీన్ డివైడర్"</string>
     <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"ఎడమవైపు ఫుల్-స్క్రీన్‌"</string>
     <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"ఎడమవైపు 70%"</string>
     <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"ఎడమవైపు 50%"</string>
@@ -49,14 +49,10 @@
     <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"ఎగువ 50%"</string>
     <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"ఎగువ 30%"</string>
     <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"దిగువ ఫుల్-స్క్రీన్‌"</string>
-    <!-- no translation found for accessibility_split_left (1713683765575562458) -->
-    <skip />
-    <!-- no translation found for accessibility_split_right (8441001008181296837) -->
-    <skip />
-    <!-- no translation found for accessibility_split_top (2789329702027147146) -->
-    <skip />
-    <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
-    <skip />
+    <string name="accessibility_split_left" msgid="1713683765575562458">"ఎడమ వైపున్న భాగంలో విభజించండి"</string>
+    <string name="accessibility_split_right" msgid="8441001008181296837">"కుడి వైపున్న భాగంలో విభజించండి"</string>
+    <string name="accessibility_split_top" msgid="2789329702027147146">"ఎగువ భాగంలో విభజించండి"</string>
+    <string name="accessibility_split_bottom" msgid="8694551025220868191">"దిగువ భాగంలో విభజించండి"</string>
     <string name="one_handed_tutorial_title" msgid="4583241688067426350">"వన్-హ్యాండెడ్ మోడ్‌ను ఉపయోగించడం"</string>
     <string name="one_handed_tutorial_description" msgid="3486582858591353067">"నిష్క్రమించడానికి, స్క్రీన్ కింది భాగం నుండి పైకి స్వైప్ చేయండి లేదా యాప్ పైన ఎక్కడైనా ట్యాప్ చేయండి"</string>
     <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"వన్-హ్యాండెడ్ మోడ్‌ను ప్రారంభిస్తుంది"</string>
@@ -88,7 +84,7 @@
     <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"దాని సమస్యను పరిష్కరించలేదా?\nపూర్వస్థితికి మార్చడానికి ట్యాప్ చేయండి"</string>
     <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"కెమెరా సమస్యలు లేవా? తీసివేయడానికి ట్యాప్ చేయండి."</string>
     <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"చూసి, మరిన్ని చేయండి"</string>
-    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"స్ప్లిట్-స్క్రీన్ కోసం మరొక యాప్‌లోకి లాగండి"</string>
+    <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"స్ప్లిట్ స్క్రీన్ కోసం మరొక యాప్‌లోకి లాగండి"</string>
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"యాప్ స్థానాన్ని మార్చడానికి దాని వెలుపల డబుల్-ట్యాప్ చేయండి"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"అర్థమైంది"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"మరింత సమాచారం కోసం విస్తరించండి."</string>
@@ -97,14 +93,21 @@
     <string name="letterbox_restart_cancel" msgid="1342209132692537805">"రద్దు చేయండి"</string>
     <string name="letterbox_restart_restart" msgid="8529976234412442973">"రీస్టార్ట్ చేయండి"</string>
     <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"మళ్లీ చూపవద్దు"</string>
+    <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"ఈ యాప్‌ను తరలించడానికి\nడబుల్-ట్యాప్ చేయండి"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"గరిష్టీకరించండి"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"కుదించండి"</string>
     <string name="close_button_text" msgid="2913281996024033299">"మూసివేయండి"</string>
     <string name="back_button_text" msgid="1469718707134137085">"వెనుకకు"</string>
     <string name="handle_text" msgid="1766582106752184456">"హ్యాండిల్"</string>
+    <string name="app_icon_text" msgid="2823268023931811747">"యాప్ చిహ్నం"</string>
     <string name="fullscreen_text" msgid="1162316685217676079">"ఫుల్-స్క్రీన్"</string>
     <string name="desktop_text" msgid="1077633567027630454">"డెస్క్‌టాప్ మోడ్"</string>
     <string name="split_screen_text" msgid="1396336058129570886">"స్ప్లిట్ స్క్రీన్"</string>
     <string name="more_button_text" msgid="3655388105592893530">"మరిన్ని"</string>
     <string name="float_button_text" msgid="9221657008391364581">"ఫ్లోట్"</string>
+    <string name="select_text" msgid="5139083974039906583">"ఎంచుకోండి"</string>
+    <string name="screenshot_text" msgid="1477704010087786671">"స్క్రీన్‌షాట్"</string>
+    <string name="close_text" msgid="4986518933445178928">"మూసివేయండి"</string>
+    <string name="collapse_menu_text" msgid="7515008122450342029">"మెనూను మూసివేయండి"</string>
+    <string name="expand_menu_text" msgid="3847736164494181168">"మెనూను తెరవండి"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-th/strings.xml b/libs/WindowManager/Shell/res/values-th/strings.xml
index 1e900d8..6733940 100644
--- a/libs/WindowManager/Shell/res/values-th/strings.xml
+++ b/libs/WindowManager/Shell/res/values-th/strings.xml
@@ -32,13 +32,13 @@
     <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"ปรับขนาด"</string>
     <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"เก็บเข้าที่เก็บส่วนตัว"</string>
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"เอาออกจากที่เก็บส่วนตัว"</string>
-    <string name="dock_forced_resizable" msgid="1749750436092293116">"แอปอาจใช้ไม่ได้กับโหมดแบ่งหน้าจอ"</string>
-    <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"แอปไม่สนับสนุนการแยกหน้าจอ"</string>
+    <string name="dock_forced_resizable" msgid="7429086980048964687">"แอปอาจใช้ไม่ได้กับโหมดแยกหน้าจอ"</string>
+    <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"แอปไม่รองรับการแยกหน้าจอ"</string>
     <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"แอปนี้เปิดได้ใน 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="703810061635792791">"เส้นแบ่งหน้าจอ"</string>
-    <string name="divider_title" msgid="5482989479865361192">"เส้นแยกหน้าจอ"</string>
+    <string name="accessibility_divider" msgid="6407584574218956849">"เส้นแยกหน้าจอ"</string>
+    <string name="divider_title" msgid="1963391955593749442">"เส้นแยกหน้าจอ"</string>
     <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"เต็มหน้าจอทางซ้าย"</string>
     <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"ซ้าย 70%"</string>
     <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"ซ้าย 50%"</string>
@@ -49,14 +49,10 @@
     <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"ด้านบน 50%"</string>
     <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"ด้านบน 30%"</string>
     <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"เต็มหน้าจอด้านล่าง"</string>
-    <!-- no translation found for accessibility_split_left (1713683765575562458) -->
-    <skip />
-    <!-- no translation found for accessibility_split_right (8441001008181296837) -->
-    <skip />
-    <!-- no translation found for accessibility_split_top (2789329702027147146) -->
-    <skip />
-    <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
-    <skip />
+    <string name="accessibility_split_left" msgid="1713683765575562458">"แยกไปทางซ้าย"</string>
+    <string name="accessibility_split_right" msgid="8441001008181296837">"แยกไปทางขวา"</string>
+    <string name="accessibility_split_top" msgid="2789329702027147146">"แยกไปด้านบน"</string>
+    <string name="accessibility_split_bottom" msgid="8694551025220868191">"แยกไปด้านล่าง"</string>
     <string name="one_handed_tutorial_title" msgid="4583241688067426350">"การใช้โหมดมือเดียว"</string>
     <string name="one_handed_tutorial_description" msgid="3486582858591353067">"หากต้องการออก ให้เลื่อนขึ้นจากด้านล่างของหน้าจอหรือแตะที่ใดก็ได้เหนือแอป"</string>
     <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"เริ่มโหมดมือเดียว"</string>
@@ -88,7 +84,7 @@
     <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"หากไม่ได้แก้ไข\nแตะเพื่อเปลี่ยนกลับ"</string>
     <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"หากไม่พบปัญหากับกล้อง แตะเพื่อปิด"</string>
     <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"รับชมและทำสิ่งต่างๆ ได้มากขึ้น"</string>
-    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"ลากไปไว้ในแอปอื่นเพื่อแยกหน้าจอ"</string>
+    <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"ลากไปไว้ในแอปอื่นเพื่อแยกหน้าจอ"</string>
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"แตะสองครั้งด้านนอกแอปเพื่อเปลี่ยนตำแหน่ง"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"รับทราบ"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"ขยายเพื่อดูข้อมูลเพิ่มเติม"</string>
@@ -97,14 +93,21 @@
     <string name="letterbox_restart_cancel" msgid="1342209132692537805">"ยกเลิก"</string>
     <string name="letterbox_restart_restart" msgid="8529976234412442973">"รีสตาร์ท"</string>
     <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"ไม่ต้องแสดงข้อความนี้อีก"</string>
+    <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"แตะสองครั้ง\nเพื่อย้ายแอปนี้"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"ขยายใหญ่สุด"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"ย่อ"</string>
     <string name="close_button_text" msgid="2913281996024033299">"ปิด"</string>
     <string name="back_button_text" msgid="1469718707134137085">"กลับ"</string>
     <string name="handle_text" msgid="1766582106752184456">"แฮนเดิล"</string>
+    <string name="app_icon_text" msgid="2823268023931811747">"ไอคอนแอป"</string>
     <string name="fullscreen_text" msgid="1162316685217676079">"เต็มหน้าจอ"</string>
     <string name="desktop_text" msgid="1077633567027630454">"โหมดเดสก์ท็อป"</string>
     <string name="split_screen_text" msgid="1396336058129570886">"แยกหน้าจอ"</string>
     <string name="more_button_text" msgid="3655388105592893530">"เพิ่มเติม"</string>
     <string name="float_button_text" msgid="9221657008391364581">"ล่องลอย"</string>
+    <string name="select_text" msgid="5139083974039906583">"เลือก"</string>
+    <string name="screenshot_text" msgid="1477704010087786671">"ภาพหน้าจอ"</string>
+    <string name="close_text" msgid="4986518933445178928">"ปิด"</string>
+    <string name="collapse_menu_text" msgid="7515008122450342029">"ปิดเมนู"</string>
+    <string name="expand_menu_text" msgid="3847736164494181168">"เปิดเมนู"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-tl/strings.xml b/libs/WindowManager/Shell/res/values-tl/strings.xml
index 8d5d0ed..8cf4eb484 100644
--- a/libs/WindowManager/Shell/res/values-tl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-tl/strings.xml
@@ -32,13 +32,13 @@
     <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"I-resize"</string>
     <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"I-stash"</string>
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"I-unstash"</string>
-    <string name="dock_forced_resizable" msgid="1749750436092293116">"Posibleng hindi gumana ang app sa split screen."</string>
-    <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Hindi sinusuportahan ng app ang split-screen."</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>
     <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"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="703810061635792791">"Divider ng split-screen"</string>
-    <string name="divider_title" msgid="5482989479865361192">"Divider ng split-screen"</string>
+    <string name="accessibility_divider" msgid="6407584574218956849">"Divider ng split screen"</string>
+    <string name="divider_title" msgid="1963391955593749442">"Divider ng split screen"</string>
     <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"I-full screen ang nasa kaliwa"</string>
     <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Gawing 70% ang nasa kaliwa"</string>
     <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Gawing 50% ang nasa kaliwa"</string>
@@ -49,14 +49,10 @@
     <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Gawing 50% ang nasa itaas"</string>
     <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Gawing 30% ang nasa itaas"</string>
     <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"I-full screen ang nasa ibaba"</string>
-    <!-- no translation found for accessibility_split_left (1713683765575562458) -->
-    <skip />
-    <!-- no translation found for accessibility_split_right (8441001008181296837) -->
-    <skip />
-    <!-- no translation found for accessibility_split_top (2789329702027147146) -->
-    <skip />
-    <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
-    <skip />
+    <string name="accessibility_split_left" msgid="1713683765575562458">"Hatiin sa kaliwa"</string>
+    <string name="accessibility_split_right" msgid="8441001008181296837">"Hatiin sa kanan"</string>
+    <string name="accessibility_split_top" msgid="2789329702027147146">"Hatiin sa itaas"</string>
+    <string name="accessibility_split_bottom" msgid="8694551025220868191">"Hatiin sa ilalim"</string>
     <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Paggamit ng one-hand mode"</string>
     <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Para lumabas, mag-swipe pataas mula sa ibaba ng screen o mag-tap kahit saan sa itaas ng app"</string>
     <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Simulan ang one-hand mode"</string>
@@ -88,7 +84,7 @@
     <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Hindi ito naayos?\nI-tap para i-revert"</string>
     <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Walang isyu sa camera? I-tap para i-dismiss."</string>
     <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Tumingin at gumawa ng higit pa"</string>
-    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Mag-drag ng ibang app para sa split screen"</string>
+    <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Mag-drag ng isa pang app para sa split screen"</string>
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Mag-double tap sa labas ng app para baguhin ang posisyon nito"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"I-expand para sa higit pang impormasyon."</string>
@@ -97,14 +93,21 @@
     <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Kanselahin"</string>
     <string name="letterbox_restart_restart" msgid="8529976234412442973">"I-restart"</string>
     <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Huwag nang ipakita ulit"</string>
+    <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"I-double tap para\nilipat ang app na ito"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"I-maximize"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"I-minimize"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Isara"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Bumalik"</string>
     <string name="handle_text" msgid="1766582106752184456">"Handle"</string>
+    <string name="app_icon_text" msgid="2823268023931811747">"Icon ng App"</string>
     <string name="fullscreen_text" msgid="1162316685217676079">"Fullscreen"</string>
     <string name="desktop_text" msgid="1077633567027630454">"Desktop Mode"</string>
     <string name="split_screen_text" msgid="1396336058129570886">"Split Screen"</string>
     <string name="more_button_text" msgid="3655388105592893530">"Higit pa"</string>
     <string name="float_button_text" msgid="9221657008391364581">"Float"</string>
+    <string name="select_text" msgid="5139083974039906583">"Piliin"</string>
+    <string name="screenshot_text" msgid="1477704010087786671">"Screenshot"</string>
+    <string name="close_text" msgid="4986518933445178928">"Isara"</string>
+    <string name="collapse_menu_text" msgid="7515008122450342029">"Isara ang Menu"</string>
+    <string name="expand_menu_text" msgid="3847736164494181168">"Buksan ang Menu"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-tr/strings.xml b/libs/WindowManager/Shell/res/values-tr/strings.xml
index 341d8f1..1454435 100644
--- a/libs/WindowManager/Shell/res/values-tr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-tr/strings.xml
@@ -32,13 +32,13 @@
     <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Yeniden boyutlandır"</string>
     <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Depola"</string>
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Depolama"</string>
-    <string name="dock_forced_resizable" msgid="1749750436092293116">"Uygulama bölünmüş ekranda çalışmayabilir."</string>
-    <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Uygulama bölünmüş ekranı desteklemiyor."</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>
     <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"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="703810061635792791">"Bölünmüş ekran ayırıcı"</string>
-    <string name="divider_title" msgid="5482989479865361192">"Bölünmüş ekran ayırıcı"</string>
+    <string name="accessibility_divider" msgid="6407584574218956849">"Bölünmüş ekran ayırıcı"</string>
+    <string name="divider_title" msgid="1963391955593749442">"Bölünmüş ekran ayırıcı"</string>
     <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Solda tam ekran"</string>
     <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Solda %70"</string>
     <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Solda %50"</string>
@@ -49,14 +49,10 @@
     <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Üstte %50"</string>
     <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Üstte %30"</string>
     <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Altta tam ekran"</string>
-    <!-- no translation found for accessibility_split_left (1713683765575562458) -->
-    <skip />
-    <!-- no translation found for accessibility_split_right (8441001008181296837) -->
-    <skip />
-    <!-- no translation found for accessibility_split_top (2789329702027147146) -->
-    <skip />
-    <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
-    <skip />
+    <string name="accessibility_split_left" msgid="1713683765575562458">"Sol tarafta böl"</string>
+    <string name="accessibility_split_right" msgid="8441001008181296837">"Sağ tarafta böl"</string>
+    <string name="accessibility_split_top" msgid="2789329702027147146">"Üst tarafta böl"</string>
+    <string name="accessibility_split_bottom" msgid="8694551025220868191">"Alt tarafta böl"</string>
     <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Tek el modunu kullanma"</string>
     <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Çıkmak için ekranın alt kısmından yukarı kaydırın veya uygulamanın üzerinde herhangi bir yere dokunun"</string>
     <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Tek el modunu başlat"</string>
@@ -88,7 +84,7 @@
     <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Bu işlem sorunu düzeltmedi mi?\nİşlemi geri almak için dokunun"</string>
     <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Kameranızda sorun yok mu? Kapatmak için dokunun."</string>
     <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Daha fazlasını görün ve yapın"</string>
-    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Bölünmüş ekran için başka bir uygulamayı sürükleyin"</string>
+    <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Bölünmüş ekran için başka bir uygulamayı sürükleyin"</string>
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Yeniden konumlandırmak için uygulamanın dışına iki kez dokunun"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"Anladım"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Daha fazla bilgi için genişletin."</string>
@@ -97,14 +93,21 @@
     <string name="letterbox_restart_cancel" msgid="1342209132692537805">"İptal"</string>
     <string name="letterbox_restart_restart" msgid="8529976234412442973">"Yeniden başlat"</string>
     <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Bir daha gösterme"</string>
+    <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Bu uygulamayı taşımak için\niki kez dokunun"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"Ekranı Kapla"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Küçült"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Kapat"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Geri"</string>
     <string name="handle_text" msgid="1766582106752184456">"Herkese açık kullanıcı adı"</string>
+    <string name="app_icon_text" msgid="2823268023931811747">"Uygulama Simgesi"</string>
     <string name="fullscreen_text" msgid="1162316685217676079">"Tam Ekran"</string>
     <string name="desktop_text" msgid="1077633567027630454">"Masaüstü Modu"</string>
     <string name="split_screen_text" msgid="1396336058129570886">"Bölünmüş Ekran"</string>
     <string name="more_button_text" msgid="3655388105592893530">"Daha Fazla"</string>
     <string name="float_button_text" msgid="9221657008391364581">"Havada Süzülen"</string>
+    <string name="select_text" msgid="5139083974039906583">"Seç"</string>
+    <string name="screenshot_text" msgid="1477704010087786671">"Ekran görüntüsü"</string>
+    <string name="close_text" msgid="4986518933445178928">"Kapat"</string>
+    <string name="collapse_menu_text" msgid="7515008122450342029">"Menüyü kapat"</string>
+    <string name="expand_menu_text" msgid="3847736164494181168">"Menüyü Aç"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-uk/strings.xml b/libs/WindowManager/Shell/res/values-uk/strings.xml
index 5b017c6..78df129 100644
--- a/libs/WindowManager/Shell/res/values-uk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-uk/strings.xml
@@ -32,13 +32,13 @@
     <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Змінити розмір"</string>
     <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Сховати"</string>
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Показати"</string>
-    <string name="dock_forced_resizable" msgid="1749750436092293116">"Додаток може не працювати в режимі розділеного екрана."</string>
-    <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Додаток не підтримує розділення екрана."</string>
+    <string name="dock_forced_resizable" msgid="7429086980048964687">"Додаток може не працювати в режимі розділення екрана"</string>
+    <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"Додаток не підтримує розділення екрана"</string>
     <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Цей додаток можна відкрити лише в одному вікні."</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="703810061635792791">"Розділювач екрана"</string>
-    <string name="divider_title" msgid="5482989479865361192">"Розділювач екрана"</string>
+    <string name="accessibility_divider" msgid="6407584574218956849">"Розділювач екрана"</string>
+    <string name="divider_title" msgid="1963391955593749442">"Розділювач екрана"</string>
     <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Ліве вікно на весь екран"</string>
     <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Ліве вікно на 70%"</string>
     <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Ліве вікно на 50%"</string>
@@ -49,14 +49,10 @@
     <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Верхнє вікно на 50%"</string>
     <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Верхнє вікно на 30%"</string>
     <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Нижнє вікно на весь екран"</string>
-    <!-- no translation found for accessibility_split_left (1713683765575562458) -->
-    <skip />
-    <!-- no translation found for accessibility_split_right (8441001008181296837) -->
-    <skip />
-    <!-- no translation found for accessibility_split_top (2789329702027147146) -->
-    <skip />
-    <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
-    <skip />
+    <string name="accessibility_split_left" msgid="1713683765575562458">"Розділити зліва"</string>
+    <string name="accessibility_split_right" msgid="8441001008181296837">"Розділити справа"</string>
+    <string name="accessibility_split_top" msgid="2789329702027147146">"Розділити вгорі"</string>
+    <string name="accessibility_split_bottom" msgid="8694551025220868191">"Розділити внизу"</string>
     <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Як користуватися режимом керування однією рукою"</string>
     <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Щоб вийти, проведіть пальцем по екрану знизу вгору або торкніться екрана над додатком"</string>
     <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Увімкнути режим керування однією рукою"</string>
@@ -88,7 +84,7 @@
     <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Проблему не вирішено?\nНатисніть, щоб скасувати зміни"</string>
     <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Немає проблем із камерою? Торкніться, щоб закрити."</string>
     <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Більше простору та можливостей"</string>
-    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Щоб перейти в режим розділення екрана, перетягніть сюди інший додаток"</string>
+    <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Щоб перейти в режим розділення екрана, перетягніть сюди інший додаток"</string>
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Щоб перемістити додаток, двічі торкніться області поза ним"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"ОK"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Розгорніть, щоб дізнатися більше."</string>
@@ -97,14 +93,21 @@
     <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Скасувати"</string>
     <string name="letterbox_restart_restart" msgid="8529976234412442973">"Перезапустити"</string>
     <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Більше не показувати"</string>
+    <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Двічі торкніться, щоб\nперемістити цей додаток"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"Збільшити"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Згорнути"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Закрити"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Назад"</string>
     <string name="handle_text" msgid="1766582106752184456">"Маркер"</string>
+    <string name="app_icon_text" msgid="2823268023931811747">"Значок додатка"</string>
     <string name="fullscreen_text" msgid="1162316685217676079">"На весь екран"</string>
     <string name="desktop_text" msgid="1077633567027630454">"Режим комп’ютера"</string>
     <string name="split_screen_text" msgid="1396336058129570886">"Розділити екран"</string>
     <string name="more_button_text" msgid="3655388105592893530">"Більше"</string>
     <string name="float_button_text" msgid="9221657008391364581">"Плаваюче вікно"</string>
+    <string name="select_text" msgid="5139083974039906583">"Вибрати"</string>
+    <string name="screenshot_text" msgid="1477704010087786671">"Знімок екрана"</string>
+    <string name="close_text" msgid="4986518933445178928">"Закрити"</string>
+    <string name="collapse_menu_text" msgid="7515008122450342029">"Закрити меню"</string>
+    <string name="expand_menu_text" msgid="3847736164494181168">"Відкрити меню"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-ur/strings.xml b/libs/WindowManager/Shell/res/values-ur/strings.xml
index 6493569..ca16424 100644
--- a/libs/WindowManager/Shell/res/values-ur/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ur/strings.xml
@@ -21,8 +21,8 @@
     <string name="pip_phone_expand" msgid="2579292903468287504">"پھیلائیں"</string>
     <string name="pip_phone_settings" msgid="5468987116750491918">"ترتیبات"</string>
     <string name="pip_phone_enter_split" msgid="7042877263880641911">"اسپلٹ اسکرین تک رسائی"</string>
-    <string name="pip_menu_title" msgid="5393619322111827096">"مینو"</string>
-    <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"تصویر میں تصویر کا مینو"</string>
+    <string name="pip_menu_title" msgid="5393619322111827096">"مینیو"</string>
+    <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"تصویر میں تصویر کا مینیو"</string>
     <string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> تصویر میں تصویر میں ہے"</string>
     <string name="pip_notification_message" msgid="8854051911700302620">"اگر آپ نہیں چاہتے ہیں کہ <xliff:g id="NAME">%s</xliff:g> اس خصوصیت کا استعمال کرے تو ترتیبات کھولنے کے لیے تھپتھپا کر اسے آف کرے۔"</string>
     <string name="pip_play" msgid="3496151081459417097">"چلائیں"</string>
@@ -32,13 +32,13 @@
     <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"سائز تبدیل کریں"</string>
     <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Stash"</string>
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Unstash"</string>
-    <string name="dock_forced_resizable" msgid="1749750436092293116">"ممکن ہے کہ ایپ اسپلٹ اسکرین کے ساتھ کام نہ کرے۔"</string>
-    <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"ایپ سپلٹ اسکرین کو سپورٹ نہیں کرتی۔"</string>
+    <string name="dock_forced_resizable" msgid="7429086980048964687">"ممکن ہے کہ ایپ اسپلٹ اسکرین کے ساتھ کام نہ کرے"</string>
+    <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"ایپ اسپلٹ اسکرین کو سپورٹ نہیں کرتی ہے"</string>
     <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"یہ ایپ صرف 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="703810061635792791">"سپلٹ اسکرین تقسیم کار"</string>
-    <string name="divider_title" msgid="5482989479865361192">"اسپلٹ اسکرین ڈیوائیڈر"</string>
+    <string name="accessibility_divider" msgid="6407584574218956849">"اسپلٹ اسکرین ڈیوائیڈر"</string>
+    <string name="divider_title" msgid="1963391955593749442">"اسپلٹ اسکرین ڈیوائیڈر"</string>
     <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"بائیں فل اسکرین"</string>
     <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"بائیں %70"</string>
     <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"بائیں %50"</string>
@@ -49,14 +49,10 @@
     <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"اوپر %50"</string>
     <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"اوپر %30"</string>
     <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"نچلی فل اسکرین"</string>
-    <!-- no translation found for accessibility_split_left (1713683765575562458) -->
-    <skip />
-    <!-- no translation found for accessibility_split_right (8441001008181296837) -->
-    <skip />
-    <!-- no translation found for accessibility_split_top (2789329702027147146) -->
-    <skip />
-    <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
-    <skip />
+    <string name="accessibility_split_left" msgid="1713683765575562458">"دائیں طرف تقسیم کریں"</string>
+    <string name="accessibility_split_right" msgid="8441001008181296837">"بائیں طرف تقسیم کریں"</string>
+    <string name="accessibility_split_top" msgid="2789329702027147146">"اوپر کی طرف تقسیم کریں"</string>
+    <string name="accessibility_split_bottom" msgid="8694551025220868191">"نیچے کی طرف تقسیم کریں"</string>
     <string name="one_handed_tutorial_title" msgid="4583241688067426350">"ایک ہاتھ کی وضع کا استعمال کرنا"</string>
     <string name="one_handed_tutorial_description" msgid="3486582858591353067">"باہر نکلنے کیلئے، اسکرین کے نیچے سے اوپر کی طرف سوائپ کریں یا ایپ کے اوپر کہیں بھی تھپتھپائیں"</string>
     <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"ایک ہاتھ کی وضع شروع کریں"</string>
@@ -88,7 +84,7 @@
     <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"یہ حل نہیں ہوا؟\nلوٹانے کیلئے تھپتھپائیں"</string>
     <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"کوئی کیمرے کا مسئلہ نہیں ہے؟ برخاست کرنے کیلئے تھپتھپائیں۔"</string>
     <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"دیکھیں اور بہت کچھ کریں"</string>
-    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"اسپلٹ اسکرین کے ليے دوسری ایپ میں گھسیٹیں"</string>
+    <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"اسپلٹ اسکرین کے ليے دوسری ایپ میں گھسیٹیں"</string>
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"کسی ایپ کی پوزیشن تبدیل کرنے کے لیے اس ایپ کے باہر دو بار تھپتھپائیں"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"سمجھ آ گئی"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"مزید معلومات کے لیے پھیلائیں۔"</string>
@@ -97,14 +93,21 @@
     <string name="letterbox_restart_cancel" msgid="1342209132692537805">"منسوخ کریں"</string>
     <string name="letterbox_restart_restart" msgid="8529976234412442973">"ری اسٹارٹ کریں"</string>
     <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"دوبارہ نہ دکھائیں"</string>
+    <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"اس ایپ کو منتقل کرنے کیلئے\nدو بار تھپتھپائیں"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"بڑا کریں"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"چھوٹا کریں"</string>
     <string name="close_button_text" msgid="2913281996024033299">"بند کریں"</string>
     <string name="back_button_text" msgid="1469718707134137085">"پیچھے"</string>
     <string name="handle_text" msgid="1766582106752184456">"ہینڈل"</string>
+    <string name="app_icon_text" msgid="2823268023931811747">"ایپ کا آئیکن"</string>
     <string name="fullscreen_text" msgid="1162316685217676079">"مکمل اسکرین"</string>
     <string name="desktop_text" msgid="1077633567027630454">"ڈیسک ٹاپ موڈ"</string>
     <string name="split_screen_text" msgid="1396336058129570886">"اسپلٹ اسکرین"</string>
     <string name="more_button_text" msgid="3655388105592893530">"مزید"</string>
     <string name="float_button_text" msgid="9221657008391364581">"فلوٹ"</string>
+    <string name="select_text" msgid="5139083974039906583">"منتخب کریں"</string>
+    <string name="screenshot_text" msgid="1477704010087786671">"اسکرین شاٹ"</string>
+    <string name="close_text" msgid="4986518933445178928">"بند کریں"</string>
+    <string name="collapse_menu_text" msgid="7515008122450342029">"مینیو بند کریں"</string>
+    <string name="expand_menu_text" msgid="3847736164494181168">"مینو کھولیں"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-ur/strings_tv.xml b/libs/WindowManager/Shell/res/values-ur/strings_tv.xml
index 42b9564..d0f011c 100644
--- a/libs/WindowManager/Shell/res/values-ur/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-ur/strings_tv.xml
@@ -25,7 +25,7 @@
     <string name="pip_expand" msgid="1051966011679297308">"پھیلائیں"</string>
     <string name="pip_collapse" msgid="3903295106641385962">"سکیڑیں"</string>
     <string name="pip_edu_text" msgid="7930546669915337998">"کنٹرولز کے لیے "<annotation icon="home_icon">"ہوم "</annotation>" کو دو بار دبائیں"</string>
-    <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"تصویر میں تصویر کا مینو۔"</string>
+    <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"تصویر میں تصویر کا مینیو۔"</string>
     <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"دائیں منتقل کریں"</string>
     <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"بائیں منتقل کریں"</string>
     <string name="a11y_action_pip_move_up" msgid="98502616918621959">"اوپر منتقل کریں"</string>
diff --git a/libs/WindowManager/Shell/res/values-uz/strings.xml b/libs/WindowManager/Shell/res/values-uz/strings.xml
index fdca0d6..c0dc033 100644
--- a/libs/WindowManager/Shell/res/values-uz/strings.xml
+++ b/libs/WindowManager/Shell/res/values-uz/strings.xml
@@ -32,13 +32,13 @@
     <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Oʻlchamini oʻzgartirish"</string>
     <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Berkitish"</string>
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Chiqarish"</string>
-    <string name="dock_forced_resizable" msgid="1749750436092293116">"Bu ilova ekranni ikkiga ajratish rejimini dastaklamaydi."</string>
-    <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Bu ilova ekranni bo‘lish xususiyatini qo‘llab-quvvatlamaydi."</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>
     <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"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="703810061635792791">"Ekranni ikkiga bo‘lish chizig‘i"</string>
-    <string name="divider_title" msgid="5482989479865361192">"Ekranni ikkiga ajratish chizigʻi"</string>
+    <string name="accessibility_divider" msgid="6407584574218956849">"Ekranni ikkiga ajratish chizigʻi"</string>
+    <string name="divider_title" msgid="1963391955593749442">"Ekranni ikkiga ajratish chizigʻi"</string>
     <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Chapda to‘liq ekran"</string>
     <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Chapda 70%"</string>
     <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Chapda 50%"</string>
@@ -49,14 +49,10 @@
     <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Tepada 50%"</string>
     <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Tepada 30%"</string>
     <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Pastda to‘liq ekran"</string>
-    <!-- no translation found for accessibility_split_left (1713683765575562458) -->
-    <skip />
-    <!-- no translation found for accessibility_split_right (8441001008181296837) -->
-    <skip />
-    <!-- no translation found for accessibility_split_top (2789329702027147146) -->
-    <skip />
-    <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
-    <skip />
+    <string name="accessibility_split_left" msgid="1713683765575562458">"Chapga ajratish"</string>
+    <string name="accessibility_split_right" msgid="8441001008181296837">"Oʻngga ajratish"</string>
+    <string name="accessibility_split_top" msgid="2789329702027147146">"Yuqoriga ajratish"</string>
+    <string name="accessibility_split_bottom" msgid="8694551025220868191">"Pastga ajratish"</string>
     <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Ixcham rejimdan foydalanish"</string>
     <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Chiqish uchun ekran pastidan tepaga suring yoki ilovaning tepasidagi istalgan joyga bosing."</string>
     <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Ixcham rejimni ishga tushirish"</string>
@@ -88,7 +84,7 @@
     <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Tuzatilmadimi?\nQaytarish uchun bosing"</string>
     <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Kamera muammosizmi? Yopish uchun bosing."</string>
     <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Yana boshqa amallar"</string>
-    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Ekranni ikkiga ajratish uchun boshqa ilovani bu yerga torting"</string>
+    <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Ekranni ikkiga ajratish uchun boshqa ilovani bu yerga torting"</string>
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Qayta joylash uchun ilova tashqarisiga ikki marta bosing"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Batafsil axborot olish uchun kengaytiring."</string>
@@ -97,14 +93,21 @@
     <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Bekor qilish"</string>
     <string name="letterbox_restart_restart" msgid="8529976234412442973">"Qaytadan"</string>
     <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Boshqa chiqmasin"</string>
+    <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Bu ilovani siljitish uchun\nikki marta bosing"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"Yoyish"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Kichraytirish"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Yopish"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Orqaga"</string>
     <string name="handle_text" msgid="1766582106752184456">"Identifikator"</string>
+    <string name="app_icon_text" msgid="2823268023931811747">"Ilova belgisi"</string>
     <string name="fullscreen_text" msgid="1162316685217676079">"Butun ekran"</string>
     <string name="desktop_text" msgid="1077633567027630454">"Desktop rejimi"</string>
     <string name="split_screen_text" msgid="1396336058129570886">"Ekranni ikkiga ajratish"</string>
     <string name="more_button_text" msgid="3655388105592893530">"Yana"</string>
     <string name="float_button_text" msgid="9221657008391364581">"Pufakli"</string>
+    <string name="select_text" msgid="5139083974039906583">"Tanlash"</string>
+    <string name="screenshot_text" msgid="1477704010087786671">"Skrinshot"</string>
+    <string name="close_text" msgid="4986518933445178928">"Yopish"</string>
+    <string name="collapse_menu_text" msgid="7515008122450342029">"Menyuni yopish"</string>
+    <string name="expand_menu_text" msgid="3847736164494181168">"Menyuni ochish"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-vi/strings.xml b/libs/WindowManager/Shell/res/values-vi/strings.xml
index 8fd2551..0281c1c 100644
--- a/libs/WindowManager/Shell/res/values-vi/strings.xml
+++ b/libs/WindowManager/Shell/res/values-vi/strings.xml
@@ -32,13 +32,13 @@
     <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Đổi kích thước"</string>
     <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Ẩn"</string>
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Hiện"</string>
-    <string name="dock_forced_resizable" msgid="1749750436092293116">"Ứng dụng có thể không hoạt động với tính năng chia đôi màn hình."</string>
-    <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Ứng dụng không hỗ trợ chia đôi màn hình."</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>
     <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Ứng dụng này chỉ có thể mở 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="703810061635792791">"Bộ chia chia đôi màn hình"</string>
-    <string name="divider_title" msgid="5482989479865361192">"Bộ chia màn hình"</string>
+    <string name="accessibility_divider" msgid="6407584574218956849">"Trình chia đôi màn hình"</string>
+    <string name="divider_title" msgid="1963391955593749442">"Trình chia đôi màn hình"</string>
     <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Toàn màn hình bên trái"</string>
     <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Trái 70%"</string>
     <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Trái 50%"</string>
@@ -49,14 +49,10 @@
     <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Trên 50%"</string>
     <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Trên 30%"</string>
     <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Toàn màn hình phía dưới"</string>
-    <!-- no translation found for accessibility_split_left (1713683765575562458) -->
-    <skip />
-    <!-- no translation found for accessibility_split_right (8441001008181296837) -->
-    <skip />
-    <!-- no translation found for accessibility_split_top (2789329702027147146) -->
-    <skip />
-    <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
-    <skip />
+    <string name="accessibility_split_left" msgid="1713683765575562458">"Chia đôi màn hình về bên trái"</string>
+    <string name="accessibility_split_right" msgid="8441001008181296837">"Chia đôi màn hình về bên phải"</string>
+    <string name="accessibility_split_top" msgid="2789329702027147146">"Chia đôi màn hình lên trên cùng"</string>
+    <string name="accessibility_split_bottom" msgid="8694551025220868191">"Chia đôi màn hình xuống dưới cùng"</string>
     <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Cách dùng chế độ một tay"</string>
     <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Để thoát, hãy vuốt lên từ cuối màn hình hoặc nhấn vào vị trí bất kỳ phía trên ứng dụng"</string>
     <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Bắt đầu chế độ một tay"</string>
@@ -83,12 +79,12 @@
     <string name="notification_bubble_title" msgid="6082910224488253378">"Bong bóng"</string>
     <string name="manage_bubbles_text" msgid="7730624269650594419">"Quản lý"</string>
     <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Đã đóng bong bóng."</string>
-    <string name="restart_button_description" msgid="6712141648865547958">"Nhấn để khởi động lại ứng dụng này để xem tốt hơn."</string>
+    <string name="restart_button_description" msgid="6712141648865547958">"Nhấn để khởi động lại ứng dụng để có trải nghiệm xem tốt hơn."</string>
     <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Có vấn đề với máy ảnh?\nHãy nhấn để sửa lỗi"</string>
     <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Bạn chưa khắc phục vấn đề?\nHãy nhấn để hủy bỏ"</string>
     <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Không có vấn đề với máy ảnh? Hãy nhấn để đóng."</string>
     <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Xem và làm được nhiều việc hơn"</string>
-    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Kéo vào một ứng dụng khác để chia đôi màn hình"</string>
+    <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Kéo một ứng dụng khác vào để chia đôi màn hình"</string>
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Nhấn đúp bên ngoài ứng dụng để đặt lại vị trí"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Mở rộng để xem thêm thông tin."</string>
@@ -97,14 +93,21 @@
     <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Huỷ"</string>
     <string name="letterbox_restart_restart" msgid="8529976234412442973">"Khởi động lại"</string>
     <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Không hiện lại"</string>
+    <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Nhấn đúp để\ndi chuyển ứng dụng này"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"Phóng to"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Thu nhỏ"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Đóng"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Quay lại"</string>
     <string name="handle_text" msgid="1766582106752184456">"Xử lý"</string>
+    <string name="app_icon_text" msgid="2823268023931811747">"Biểu tượng ứng dụng"</string>
     <string name="fullscreen_text" msgid="1162316685217676079">"Toàn màn hình"</string>
     <string name="desktop_text" msgid="1077633567027630454">"Chế độ máy tính"</string>
     <string name="split_screen_text" msgid="1396336058129570886">"Chia đôi màn hình"</string>
     <string name="more_button_text" msgid="3655388105592893530">"Tuỳ chọn khác"</string>
     <string name="float_button_text" msgid="9221657008391364581">"Nổi"</string>
+    <string name="select_text" msgid="5139083974039906583">"Chọn"</string>
+    <string name="screenshot_text" msgid="1477704010087786671">"Ảnh chụp màn hình"</string>
+    <string name="close_text" msgid="4986518933445178928">"Đóng"</string>
+    <string name="collapse_menu_text" msgid="7515008122450342029">"Đóng trình đơn"</string>
+    <string name="expand_menu_text" msgid="3847736164494181168">"Mở Trình đơn"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-watch/colors.xml b/libs/WindowManager/Shell/res/values-watch/colors.xml
new file mode 100644
index 0000000..82492bf
--- /dev/null
+++ b/libs/WindowManager/Shell/res/values-watch/colors.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * Copyright 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.
+ */
+-->
+<resources>
+    <color name="splash_window_background_default">@color/splash_screen_bg_dark</color>
+</resources>
+
diff --git a/libs/WindowManager/Shell/res/values-watch/dimen.xml b/libs/WindowManager/Shell/res/values-watch/dimen.xml
new file mode 100644
index 0000000..362e72c
--- /dev/null
+++ b/libs/WindowManager/Shell/res/values-watch/dimen.xml
@@ -0,0 +1,22 @@
+<?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.
+-->
+<resources>
+    <!-- The acceptable area ratio of fg icon area/bg icon area, i.e. (48 X 48) / (72 x 72) -->
+    <item type="dimen" format="float" name="splash_icon_enlarge_foreground_threshold">0.44</item>
+    <!-- Scaling factor applied to splash icons without provided background i.e. (60 / 48) -->
+    <item type="dimen" format="float" name="splash_icon_no_background_scale_factor">1.25</item>
+</resources>
diff --git a/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml b/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml
index ba78d1b..d1f50db 100644
--- a/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml
@@ -32,13 +32,13 @@
     <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"调整大小"</string>
     <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"隐藏"</string>
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"取消隐藏"</string>
-    <string name="dock_forced_resizable" msgid="1749750436092293116">"应用可能无法在分屏模式下正常运行。"</string>
-    <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"应用不支持分屏。"</string>
+    <string name="dock_forced_resizable" msgid="7429086980048964687">"应用可能无法在分屏模式下正常运行"</string>
+    <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"应用不支持分屏"</string>
     <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"此应用只能在 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="703810061635792791">"分屏分隔线"</string>
-    <string name="divider_title" msgid="5482989479865361192">"分屏分隔线"</string>
+    <string name="accessibility_divider" msgid="6407584574218956849">"分屏分隔线"</string>
+    <string name="divider_title" msgid="1963391955593749442">"分屏分隔线"</string>
     <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"左侧全屏"</string>
     <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"左侧 70%"</string>
     <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"左侧 50%"</string>
@@ -49,14 +49,10 @@
     <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"顶部 50%"</string>
     <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"顶部 30%"</string>
     <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"底部全屏"</string>
-    <!-- no translation found for accessibility_split_left (1713683765575562458) -->
-    <skip />
-    <!-- no translation found for accessibility_split_right (8441001008181296837) -->
-    <skip />
-    <!-- no translation found for accessibility_split_top (2789329702027147146) -->
-    <skip />
-    <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
-    <skip />
+    <string name="accessibility_split_left" msgid="1713683765575562458">"左分屏"</string>
+    <string name="accessibility_split_right" msgid="8441001008181296837">"右分屏"</string>
+    <string name="accessibility_split_top" msgid="2789329702027147146">"上分屏"</string>
+    <string name="accessibility_split_bottom" msgid="8694551025220868191">"下分屏"</string>
     <string name="one_handed_tutorial_title" msgid="4583241688067426350">"使用单手模式"</string>
     <string name="one_handed_tutorial_description" msgid="3486582858591353067">"如需退出,请从屏幕底部向上滑动,或点按应用上方的任意位置"</string>
     <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"启动单手模式"</string>
@@ -88,7 +84,7 @@
     <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"没有解决此问题?\n点按即可恢复"</string>
     <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"相机没有问题?点按即可忽略。"</string>
     <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"查看和处理更多任务"</string>
-    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"拖入另一个应用,即可使用分屏模式"</string>
+    <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"拖入另一个应用,即可使用分屏模式"</string>
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"在某个应用外连续点按两次,即可调整它的位置"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"知道了"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"展开即可了解详情。"</string>
@@ -97,14 +93,21 @@
     <string name="letterbox_restart_cancel" msgid="1342209132692537805">"取消"</string>
     <string name="letterbox_restart_restart" msgid="8529976234412442973">"重启"</string>
     <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"不再显示"</string>
+    <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"点按两次\n即可移动此应用"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"最大化"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"最小化"</string>
     <string name="close_button_text" msgid="2913281996024033299">"关闭"</string>
     <string name="back_button_text" msgid="1469718707134137085">"返回"</string>
     <string name="handle_text" msgid="1766582106752184456">"处理"</string>
+    <string name="app_icon_text" msgid="2823268023931811747">"应用图标"</string>
     <string name="fullscreen_text" msgid="1162316685217676079">"全屏"</string>
     <string name="desktop_text" msgid="1077633567027630454">"桌面模式"</string>
     <string name="split_screen_text" msgid="1396336058129570886">"分屏"</string>
     <string name="more_button_text" msgid="3655388105592893530">"更多"</string>
     <string name="float_button_text" msgid="9221657008391364581">"悬浮"</string>
+    <string name="select_text" msgid="5139083974039906583">"选择"</string>
+    <string name="screenshot_text" msgid="1477704010087786671">"屏幕截图"</string>
+    <string name="close_text" msgid="4986518933445178928">"关闭"</string>
+    <string name="collapse_menu_text" msgid="7515008122450342029">"关闭菜单"</string>
+    <string name="expand_menu_text" msgid="3847736164494181168">"打开菜单"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml b/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml
index b3bc5b6..3d33eca 100644
--- a/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml
@@ -24,7 +24,7 @@
     <string name="pip_menu_title" msgid="5393619322111827096">"選單"</string>
     <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"畫中畫選單"</string>
     <string name="pip_notification_title" msgid="1347104727641353453">"「<xliff:g id="NAME">%s</xliff:g>」目前在畫中畫模式"</string>
-    <string name="pip_notification_message" msgid="8854051911700302620">"如果您不想「<xliff:g id="NAME">%s</xliff:g>」使用此功能,請輕按以開啟設定,然後停用此功能。"</string>
+    <string name="pip_notification_message" msgid="8854051911700302620">"如果你不想「<xliff:g id="NAME">%s</xliff:g>」使用此功能,請輕按以開啟設定,然後停用此功能。"</string>
     <string name="pip_play" msgid="3496151081459417097">"播放"</string>
     <string name="pip_pause" msgid="690688849510295232">"暫停"</string>
     <string name="pip_skip_to_next" msgid="8403429188794867653">"跳到下一個"</string>
@@ -32,13 +32,13 @@
     <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"調整大小"</string>
     <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"保護"</string>
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"取消保護"</string>
-    <string name="dock_forced_resizable" msgid="1749750436092293116">"應用程式可能無法在分割畫面中運作。"</string>
-    <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"應用程式不支援分割畫面。"</string>
+    <string name="dock_forced_resizable" msgid="7429086980048964687">"應用程式可能無法在分割螢幕中運作"</string>
+    <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"應用程式不支援分割螢幕"</string>
     <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"此應用程式只可在 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="703810061635792791">"分割畫面分隔線"</string>
-    <string name="divider_title" msgid="5482989479865361192">"分割螢幕分隔線"</string>
+    <string name="accessibility_divider" msgid="6407584574218956849">"分割螢幕分隔線"</string>
+    <string name="divider_title" msgid="1963391955593749442">"分割螢幕分隔線"</string>
     <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"左邊全螢幕"</string>
     <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"左邊 70%"</string>
     <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"左邊 50%"</string>
@@ -49,14 +49,10 @@
     <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"頂部 50%"</string>
     <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"頂部 30%"</string>
     <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"底部全螢幕"</string>
-    <!-- no translation found for accessibility_split_left (1713683765575562458) -->
-    <skip />
-    <!-- no translation found for accessibility_split_right (8441001008181296837) -->
-    <skip />
-    <!-- no translation found for accessibility_split_top (2789329702027147146) -->
-    <skip />
-    <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
-    <skip />
+    <string name="accessibility_split_left" msgid="1713683765575562458">"分割左側區域"</string>
+    <string name="accessibility_split_right" msgid="8441001008181296837">"分割右側區域"</string>
+    <string name="accessibility_split_top" msgid="2789329702027147146">"分割上方區域"</string>
+    <string name="accessibility_split_bottom" msgid="8694551025220868191">"分割下方區域"</string>
     <string name="one_handed_tutorial_title" msgid="4583241688067426350">"使用單手模式"</string>
     <string name="one_handed_tutorial_description" msgid="3486582858591353067">"如要退出,請從螢幕底部向上滑動,或輕按應用程式上方的任何位置"</string>
     <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"開始單手模式"</string>
@@ -88,23 +84,30 @@
     <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"未能修正問題?\n輕按即可還原"</string>
     <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"相機冇問題?㩒一下就可以即可閂咗佢。"</string>
     <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"瀏覽更多內容及執行更多操作"</string>
-    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"拖入另一個應用程式即可分割螢幕"</string>
+    <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"拖入另一個應用程式即可分割螢幕"</string>
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"在應用程式外輕按兩下即可調整位置"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"知道了"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"展開即可查看詳情。"</string>
-    <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"要重新啟動改善檢視畫面嗎?"</string>
-    <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"您可重新啟動應用程式,讓系統更新檢視畫面;但系統可能不會儲存目前進度及您作出的任何變更"</string>
+    <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"要重新啟動以改善檢視畫面嗎?"</string>
+    <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"你可重新啟動應用程式,讓系統更新檢視畫面;但系統可能不會儲存目前進度及你作出的任何變更"</string>
     <string name="letterbox_restart_cancel" msgid="1342209132692537805">"取消"</string>
     <string name="letterbox_restart_restart" msgid="8529976234412442973">"重新啟動"</string>
     <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"不要再顯示"</string>
+    <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"輕按兩下\n即可移動此應用程式"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"最大化"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"最小化"</string>
     <string name="close_button_text" msgid="2913281996024033299">"關閉"</string>
     <string name="back_button_text" msgid="1469718707134137085">"返去"</string>
     <string name="handle_text" msgid="1766582106752184456">"控點"</string>
+    <string name="app_icon_text" msgid="2823268023931811747">"應用程式圖示"</string>
     <string name="fullscreen_text" msgid="1162316685217676079">"全螢幕"</string>
     <string name="desktop_text" msgid="1077633567027630454">"桌面模式"</string>
     <string name="split_screen_text" msgid="1396336058129570886">"分割螢幕"</string>
     <string name="more_button_text" msgid="3655388105592893530">"更多"</string>
     <string name="float_button_text" msgid="9221657008391364581">"浮動"</string>
+    <string name="select_text" msgid="5139083974039906583">"選取"</string>
+    <string name="screenshot_text" msgid="1477704010087786671">"螢幕截圖"</string>
+    <string name="close_text" msgid="4986518933445178928">"關閉"</string>
+    <string name="collapse_menu_text" msgid="7515008122450342029">"關閉選單"</string>
+    <string name="expand_menu_text" msgid="3847736164494181168">"打開選單"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml b/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml
index e744461..4ca49e1 100644
--- a/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml
@@ -32,13 +32,13 @@
     <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"調整大小"</string>
     <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"暫時隱藏"</string>
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"取消暫時隱藏"</string>
-    <string name="dock_forced_resizable" msgid="1749750436092293116">"應用程式可能無法在分割畫面中運作。"</string>
-    <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"這個應用程式不支援分割畫面。"</string>
+    <string name="dock_forced_resizable" msgid="7429086980048964687">"應用程式可能無法在分割畫面中運作"</string>
+    <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"這個應用程式不支援分割畫面"</string>
     <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"這個應用程式只能在 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="703810061635792791">"分割畫面分隔線"</string>
-    <string name="divider_title" msgid="5482989479865361192">"分割畫面分隔線"</string>
+    <string name="accessibility_divider" msgid="6407584574218956849">"分割畫面分隔線"</string>
+    <string name="divider_title" msgid="1963391955593749442">"分割畫面分隔線"</string>
     <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"以全螢幕顯示左側畫面"</string>
     <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"以 70% 的螢幕空間顯示左側畫面"</string>
     <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"以 50% 的螢幕空間顯示左側畫面"</string>
@@ -49,14 +49,10 @@
     <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"以 50% 的螢幕空間顯示頂端畫面"</string>
     <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"以 30% 的螢幕空間顯示頂端畫面"</string>
     <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"以全螢幕顯示底部畫面"</string>
-    <!-- no translation found for accessibility_split_left (1713683765575562458) -->
-    <skip />
-    <!-- no translation found for accessibility_split_right (8441001008181296837) -->
-    <skip />
-    <!-- no translation found for accessibility_split_top (2789329702027147146) -->
-    <skip />
-    <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
-    <skip />
+    <string name="accessibility_split_left" msgid="1713683765575562458">"分割左側區域"</string>
+    <string name="accessibility_split_right" msgid="8441001008181296837">"分割右側區域"</string>
+    <string name="accessibility_split_top" msgid="2789329702027147146">"分割上方區域"</string>
+    <string name="accessibility_split_bottom" msgid="8694551025220868191">"分割下方區域"</string>
     <string name="one_handed_tutorial_title" msgid="4583241688067426350">"使用單手模式"</string>
     <string name="one_handed_tutorial_description" msgid="3486582858591353067">"如要退出,請從螢幕底部向上滑動,或輕觸應用程式上方的任何位置"</string>
     <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"啟動單手模式"</string>
@@ -88,7 +84,7 @@
     <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"未修正問題嗎?\n輕觸即可還原"</string>
     <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"相機沒問題嗎?輕觸即可關閉。"</string>
     <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"瀏覽更多內容及執行更多操作"</string>
-    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"拖進另一個應用程式即可使用分割畫面模式"</string>
+    <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"拖進另一個應用程式即可使用分割畫面模式"</string>
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"在應用程式外輕觸兩下即可調整位置"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"我知道了"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"展開即可查看詳細資訊。"</string>
@@ -97,14 +93,21 @@
     <string name="letterbox_restart_cancel" msgid="1342209132692537805">"取消"</string>
     <string name="letterbox_restart_restart" msgid="8529976234412442973">"重新啟動"</string>
     <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"不要再顯示"</string>
+    <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"輕觸兩下即可\n移動這個應用程式"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"最大化"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"最小化"</string>
     <string name="close_button_text" msgid="2913281996024033299">"關閉"</string>
     <string name="back_button_text" msgid="1469718707134137085">"返回"</string>
     <string name="handle_text" msgid="1766582106752184456">"控點"</string>
+    <string name="app_icon_text" msgid="2823268023931811747">"應用程式圖示"</string>
     <string name="fullscreen_text" msgid="1162316685217676079">"全螢幕"</string>
     <string name="desktop_text" msgid="1077633567027630454">"電腦模式"</string>
     <string name="split_screen_text" msgid="1396336058129570886">"分割畫面"</string>
     <string name="more_button_text" msgid="3655388105592893530">"更多"</string>
     <string name="float_button_text" msgid="9221657008391364581">"浮動"</string>
+    <string name="select_text" msgid="5139083974039906583">"選取"</string>
+    <string name="screenshot_text" msgid="1477704010087786671">"螢幕截圖"</string>
+    <string name="close_text" msgid="4986518933445178928">"關閉"</string>
+    <string name="collapse_menu_text" msgid="7515008122450342029">"關閉選單"</string>
+    <string name="expand_menu_text" msgid="3847736164494181168">"開啟選單"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-zu/strings.xml b/libs/WindowManager/Shell/res/values-zu/strings.xml
index 913e68f..478b5a6 100644
--- a/libs/WindowManager/Shell/res/values-zu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zu/strings.xml
@@ -32,13 +32,13 @@
     <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Shintsha usayizi"</string>
     <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Yenza isiteshi"</string>
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Susa isiteshi"</string>
-    <string name="dock_forced_resizable" msgid="1749750436092293116">"Izinhlelo zokusebenza kungenzeka zingasebenzi ngesikrini esihlukanisiwe."</string>
-    <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Uhlelo lokusebenza alusekeli isikrini esihlukanisiwe."</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>
     <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"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="703810061635792791">"Isihlukanisi sokuhlukanisa isikrini"</string>
-    <string name="divider_title" msgid="5482989479865361192">"Isihlukanisi sokuhlukanisa isikrini"</string>
+    <string name="accessibility_divider" msgid="6407584574218956849">"Isihlukanisi sokuhlukanisa isikrini"</string>
+    <string name="divider_title" msgid="1963391955593749442">"Isihlukanisi sokuhlukanisa isikrini"</string>
     <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Isikrini esigcwele esingakwesokunxele"</string>
     <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Kwesokunxele ngo-70%"</string>
     <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Kwesokunxele ngo-50%"</string>
@@ -49,14 +49,10 @@
     <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Okuphezulu okungu-50%"</string>
     <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Okuphezulu okungu-30%"</string>
     <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Ngaphansi kwesikrini esigcwele"</string>
-    <!-- no translation found for accessibility_split_left (1713683765575562458) -->
-    <skip />
-    <!-- no translation found for accessibility_split_right (8441001008181296837) -->
-    <skip />
-    <!-- no translation found for accessibility_split_top (2789329702027147146) -->
-    <skip />
-    <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
-    <skip />
+    <string name="accessibility_split_left" msgid="1713683765575562458">"Hlukanisa ngakwesobunxele"</string>
+    <string name="accessibility_split_right" msgid="8441001008181296837">"Hlukanisa ngakwesokudla"</string>
+    <string name="accessibility_split_top" msgid="2789329702027147146">"Hlukanisa phezulu"</string>
+    <string name="accessibility_split_bottom" msgid="8694551025220868191">"Hlukanisa phansi"</string>
     <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Ukusebenzisa imodi yesandla esisodwa"</string>
     <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Ukuze uphume, swayipha ngaphezulu kusuka ngezansi kwesikrini noma thepha noma kuphi ngenhla kohlelo lokusebenza"</string>
     <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Qalisa imodi yesandla esisodwa"</string>
@@ -88,7 +84,7 @@
     <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Akuyilungisanga?\nThepha ukuze ubuyele"</string>
     <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Azikho izinkinga zekhamera? Thepha ukuze ucashise."</string>
     <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Bona futhi wenze okuningi"</string>
-    <string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"Hudula kwenye i-app mayelana nokuhlukanisa isikrini"</string>
+    <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Hudula kwenye i-app mayelana nokuhlukanisa isikrini"</string>
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Thepha kabili ngaphandle kwe-app ukuze uyimise kabusha"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"Ngiyezwa"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Nweba ukuze uthole ulwazi olwengeziwe"</string>
@@ -97,14 +93,21 @@
     <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Khansela"</string>
     <string name="letterbox_restart_restart" msgid="8529976234412442973">"Qala kabusha"</string>
     <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Ungabonisi futhi"</string>
+    <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Thepha kabili ukuze\nuhambise le-app"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"Khulisa"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Nciphisa"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Vala"</string>
     <string name="back_button_text" msgid="1469718707134137085">"Emuva"</string>
     <string name="handle_text" msgid="1766582106752184456">"Isibambo"</string>
+    <string name="app_icon_text" msgid="2823268023931811747">"Isithonjana Se-app"</string>
     <string name="fullscreen_text" msgid="1162316685217676079">"Isikrini esigcwele"</string>
     <string name="desktop_text" msgid="1077633567027630454">"Imodi Yedeskithophu"</string>
     <string name="split_screen_text" msgid="1396336058129570886">"Hlukanisa isikrini"</string>
     <string name="more_button_text" msgid="3655388105592893530">"Okwengeziwe"</string>
     <string name="float_button_text" msgid="9221657008391364581">"Iflowuthi"</string>
+    <string name="select_text" msgid="5139083974039906583">"Khetha"</string>
+    <string name="screenshot_text" msgid="1477704010087786671">"Isithombe-skrini"</string>
+    <string name="close_text" msgid="4986518933445178928">"Vala"</string>
+    <string name="collapse_menu_text" msgid="7515008122450342029">"Vala Imenyu"</string>
+    <string name="expand_menu_text" msgid="3847736164494181168">"Vula Imenyu"</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values/colors.xml b/libs/WindowManager/Shell/res/values/colors.xml
index 171a6b2..f2a0785 100644
--- a/libs/WindowManager/Shell/res/values/colors.xml
+++ b/libs/WindowManager/Shell/res/values/colors.xml
@@ -30,6 +30,9 @@
     <color name="bubbles_light">#FFFFFF</color>
     <color name="bubbles_dark">@color/GM2_grey_800</color>
     <color name="bubbles_icon_tint">@color/GM2_grey_700</color>
+    <color name="bubble_bar_expanded_view_handle_light">#EBffffff</color>
+    <color name="bubble_bar_expanded_view_handle_dark">#99000000</color>
+    <color name="bubble_bar_expanded_view_menu_close">#DC362E</color>
 
     <!-- PiP -->
     <color name="pip_custom_close_bg">#D93025</color>
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 2be34c9..ac73e1d 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -226,10 +226,26 @@
     <dimen name="bubble_user_education_padding_end">58dp</dimen>
     <!-- Padding between the bubble and the user education text. -->
     <dimen name="bubble_user_education_stack_padding">16dp</dimen>
-    <!-- Size of the bubble bar (height), should match transient_taskbar_size in Launcher. -->
-    <dimen name="bubblebar_size">72dp</dimen>
-    <!-- The size of the drag handle / menu shown along with a bubble bar expanded view. -->
-    <dimen name="bubblebar_expanded_view_menu_size">16dp</dimen>
+    <!-- The size of the caption bar inset at the top of bubble bar expanded view. -->
+    <dimen name="bubble_bar_expanded_view_caption_height">32dp</dimen>
+    <!-- The height of the dots shown for the caption menu in the bubble bar expanded view.. -->
+    <dimen name="bubble_bar_expanded_view_caption_dot_size">4dp</dimen>
+    <!-- The spacing between the dots for the caption menu in the bubble bar expanded view.. -->
+    <dimen name="bubble_bar_expanded_view_caption_dot_spacing">4dp</dimen>
+    <!-- Minimum width of the bubble bar manage menu. -->
+    <dimen name="bubble_bar_manage_menu_min_width">200dp</dimen>
+    <!-- Size of the dismiss icon in the bubble bar manage menu. -->
+    <dimen name="bubble_bar_manage_menu_dismiss_icon_size">16dp</dimen>
+    <!-- Padding of the bubble bar manage menu, provides space for menu shadows -->
+    <dimen name="bubble_bar_manage_menu_padding">8dp</dimen>
+    <!-- Top padding of the bubble bar manage menu -->
+    <dimen name="bubble_bar_manage_menu_padding_top">2dp</dimen>
+    <!-- Spacing between sections of the bubble bar manage menu -->
+    <dimen name="bubble_bar_manage_menu_section_spacing">2dp</dimen>
+    <!-- Height of an item in the bubble bar manage menu. -->
+    <dimen name="bubble_bar_manage_menu_item_height">52dp</dimen>
+    <!-- Size of the icons in the bubble bar manage menu. -->
+    <dimen name="bubble_bar_manage_menu_item_icon_size">20dp</dimen>
 
     <!-- Bottom and end margin for compat buttons. -->
     <dimen name="compat_button_margin">24dp</dimen>
@@ -398,7 +414,16 @@
     <!-- The radius of the caption menu shadow. -->
     <dimen name="desktop_mode_handle_menu_shadow_radius">2dp</dimen>
 
-    <dimen name="freeform_resize_handle">30dp</dimen>
+    <dimen name="freeform_resize_handle">15dp</dimen>
 
     <dimen name="freeform_resize_corner">44dp</dimen>
+
+    <!-- The height of the area at the top of the screen where a freeform task will transition to
+    fullscreen if dragged until the top bound of the task is within the area. -->
+    <dimen name="desktop_mode_transition_area_height">16dp</dimen>
+
+    <!-- The acceptable area ratio of fg icon area/bg icon area, i.e. (72 x 72) / (108 x 108) -->
+    <item type="dimen" format="float" name="splash_icon_enlarge_foreground_threshold">0.44</item>
+    <!-- Scaling factor applied to splash icons without provided background i.e. (192 / 160) -->
+    <item type="dimen" format="float" name="splash_icon_no_background_scale_factor">1.2</item>
 </resources>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
index 102f2cb..7e09c98 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
@@ -422,6 +422,7 @@
         }
         if (mBubbleBarExpandedView != null) {
             mBubbleBarExpandedView.cleanUpExpandedState();
+            mBubbleBarExpandedView = null;
         }
         if (mIntent != null) {
             mIntent.unregisterCancelListener(mIntentCancelListener);
@@ -549,10 +550,10 @@
     /**
      * Set visibility of bubble in the expanded state.
      *
-     * @param visibility {@code true} if the expanded bubble should be visible on the screen.
-     *
-     * Note that this contents visibility doesn't affect visibility at {@link android.view.View},
+     * <p>Note that this contents visibility doesn't affect visibility at {@link android.view.View},
      * and setting {@code false} actually means rendering the expanded view in transparent.
+     *
+     * @param visibility {@code true} if the expanded bubble should be visible on the screen.
      */
     @Override
     public void setTaskViewVisibility(boolean visibility) {
@@ -855,7 +856,8 @@
         return mIsAppBubble;
     }
 
-    Intent getSettingsIntent(final Context context) {
+    /** Creates open app settings intent */
+    public Intent getSettingsIntent(final Context context) {
         final Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_BUBBLE_SETTINGS);
         intent.putExtra(Settings.EXTRA_APP_PACKAGE, getPackageName());
         final int uid = getUid(context);
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 0fdfbb8..e69825f2 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
@@ -64,7 +64,6 @@
 import android.os.Handler;
 import android.os.RemoteException;
 import android.os.ServiceManager;
-import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.service.notification.NotificationListenerService;
@@ -92,6 +91,7 @@
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.WindowManagerShellWrapper;
 import com.android.wm.shell.bubbles.bar.BubbleBarLayerView;
+import com.android.wm.shell.bubbles.properties.BubbleProperties;
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.ExternalInterfaceBinder;
 import com.android.wm.shell.common.FloatingContentCoordinator;
@@ -143,16 +143,6 @@
     private static final String SYSTEM_DIALOG_REASON_KEY = "reason";
     private static final String SYSTEM_DIALOG_REASON_GESTURE_NAV = "gestureNav";
 
-    // TODO(b/256873975) Should use proper flag when available to shell/launcher
-    /**
-     * Whether bubbles are showing in the bubble bar from launcher. This is only available
-     * on large screens and {@link BubbleController#isShowingAsBubbleBar()} should be used
-     * to check all conditions that indicate if the bubble bar is in use.
-     */
-    private static final boolean BUBBLE_BAR_ENABLED =
-            SystemProperties.getBoolean("persist.wm.debug.bubble_bar", false);
-
-
     /**
      * Common interface to send updates to bubble views.
      */
@@ -195,6 +185,7 @@
     private final ShellController mShellController;
     private final ShellCommandHandler mShellCommandHandler;
     private final IWindowManager mWmService;
+    private final BubbleProperties mBubbleProperties;
 
     // Used to post to main UI thread
     private final ShellExecutor mMainExecutor;
@@ -291,7 +282,8 @@
             @ShellBackgroundThread ShellExecutor bgExecutor,
             TaskViewTransitions taskViewTransitions,
             SyncTransactionQueue syncQueue,
-            IWindowManager wmService) {
+            IWindowManager wmService,
+            BubbleProperties bubbleProperties) {
         mContext = context;
         mShellCommandHandler = shellCommandHandler;
         mShellController = shellController;
@@ -328,6 +320,7 @@
         mDragAndDropController = dragAndDropController;
         mSyncQueue = syncQueue;
         mWmService = wmService;
+        mBubbleProperties = bubbleProperties;
         shellInit.addInitCallback(this::onInit, this);
     }
 
@@ -336,16 +329,20 @@
                 new OneHandedTransitionCallback() {
                     @Override
                     public void onStartFinished(Rect bounds) {
-                        if (mStackView != null) {
-                            mStackView.onVerticalOffsetChanged(bounds.top);
-                        }
+                        mMainExecutor.execute(() -> {
+                            if (mStackView != null) {
+                                mStackView.onVerticalOffsetChanged(bounds.top);
+                            }
+                        });
                     }
 
                     @Override
                     public void onStopFinished(Rect bounds) {
-                        if (mStackView != null) {
-                            mStackView.onVerticalOffsetChanged(bounds.top);
-                        }
+                        mMainExecutor.execute(() -> {
+                            if (mStackView != null) {
+                                mStackView.onVerticalOffsetChanged(bounds.top);
+                            }
+                        });
                     }
                 });
     }
@@ -518,11 +515,14 @@
     /**
      * Sets a listener to be notified of bubble updates. This is used by launcher so that
      * it may render bubbles in itself. Only one listener is supported.
+     *
+     * <p>If bubble bar is supported, bubble views will be updated to switch to bar mode.
      */
     public void registerBubbleStateListener(Bubbles.BubbleStateListener listener) {
-        if (isShowingAsBubbleBar()) {
-            // Only set the listener if bubble bar is showing.
+        if (canShowAsBubbleBar() && listener != null) {
+            // Only set the listener if we can show the bubble bar.
             mBubbleStateListener = listener;
+            setUpBubbleViewsForMode();
             sendInitialListenerUpdate();
         } else {
             mBubbleStateListener = null;
@@ -531,9 +531,15 @@
 
     /**
      * Unregisters the {@link Bubbles.BubbleStateListener}.
+     *
+     * <p>If there's an existing listener, then we're switching back to stack mode and bubble views
+     * will be updated accordingly.
      */
     public void unregisterBubbleStateListener() {
-        mBubbleStateListener = null;
+        if (mBubbleStateListener != null) {
+            mBubbleStateListener = null;
+            setUpBubbleViewsForMode();
+        }
     }
 
     /**
@@ -645,8 +651,12 @@
 
     /** Whether bubbles are showing in the bubble bar. */
     public boolean isShowingAsBubbleBar() {
-        // TODO(b/269670598): should also check that we're in gesture nav
-        return BUBBLE_BAR_ENABLED && mBubblePositioner.isLargeScreen();
+        return canShowAsBubbleBar() && mBubbleStateListener != null;
+    }
+
+    /** Whether the current configuration supports showing as bubble bar. */
+    private boolean canShowAsBubbleBar() {
+        return mBubbleProperties.isBubbleBarEnabled() && mBubblePositioner.isLargeScreen();
     }
 
     /** Whether this userId belongs to the current user. */
@@ -719,6 +729,7 @@
             // TODO(b/273312602): consider foldables where we do need a stack view when folded
             if (mLayerView == null) {
                 mLayerView = new BubbleBarLayerView(mContext, this);
+                mLayerView.setUnBubbleConversationCallback(mSysuiProxy::onUnbubbleConversation);
             }
         } else {
             if (mStackView == null) {
@@ -773,12 +784,12 @@
         try {
             mAddedToWindowManager = true;
             registerBroadcastReceiver();
-            mBubbleData.getOverflow().initialize(this);
+            mBubbleData.getOverflow().initialize(this, isShowingAsBubbleBar());
             // (TODO: b/273314541) some duplication in the inset listener
             if (isShowingAsBubbleBar()) {
                 mWindowManager.addView(mLayerView, mWmLayoutParams);
                 mLayerView.setOnApplyWindowInsetsListener((view, windowInsets) -> {
-                    if (!windowInsets.equals(mWindowInsets)) {
+                    if (!windowInsets.equals(mWindowInsets) && mLayerView != null) {
                         mWindowInsets = windowInsets;
                         mBubblePositioner.update();
                         mLayerView.onDisplaySizeChanged();
@@ -788,7 +799,7 @@
             } else {
                 mWindowManager.addView(mStackView, mWmLayoutParams);
                 mStackView.setOnApplyWindowInsetsListener((view, windowInsets) -> {
-                    if (!windowInsets.equals(mWindowInsets)) {
+                    if (!windowInsets.equals(mWindowInsets) && mStackView != null) {
                         mWindowInsets = windowInsets;
                         mBubblePositioner.update();
                         mStackView.onDisplaySizeChanged();
@@ -1065,9 +1076,19 @@
      * Expands and selects the provided bubble as long as it already exists in the stack or the
      * overflow.
      *
-     * This is used by external callers (launcher).
+     * <p>This is used by external callers (launcher).
      */
-    public void expandStackAndSelectBubbleFromLauncher(String key) {
+    @VisibleForTesting
+    public void expandStackAndSelectBubbleFromLauncher(String key, int bubbleBarOffsetX,
+            int bubbleBarOffsetY) {
+        mBubblePositioner.setBubbleBarPosition(bubbleBarOffsetX, bubbleBarOffsetY);
+
+        if (BubbleOverflow.KEY.equals(key)) {
+            mBubbleData.setSelectedBubbleFromLauncher(mBubbleData.getOverflow());
+            mLayerView.showExpandedView(mBubbleData.getOverflow());
+            return;
+        }
+
         Bubble b = mBubbleData.getAnyBubbleWithkey(key);
         if (b == null) {
             return;
@@ -1220,6 +1241,13 @@
     }
 
     /**
+     * Dismiss bubble if it exists and remove it from the stack
+     */
+    public void dismissBubble(Bubble bubble, @Bubbles.DismissReason int reason) {
+        mBubbleData.dismissBubbleWithKey(bubble.getKey(), reason);
+    }
+
+    /**
      * Performs a screenshot that may exclude the bubble layer, if one is present. The screenshot
      * can be access via the supplied {@link SynchronousScreenCaptureListener#getBuffer()}
      * asynchronously.
@@ -1259,7 +1287,9 @@
             return;
         }
         mOverflowDataLoadNeeded = false;
-        mDataRepository.loadBubbles(mCurrentUserId, (bubbles) -> {
+        List<UserInfo> users = mUserManager.getAliveUsers();
+        List<Integer> userIds = users.stream().map(userInfo -> userInfo.id).toList();
+        mDataRepository.loadBubbles(mCurrentUserId, userIds, (bubbles) -> {
             bubbles.forEach(bubble -> {
                 if (mBubbleData.hasAnyBubbleWithKey(bubble.getKey())) {
                     // if the bubble is already active, there's no need to push it to overflow
@@ -1278,6 +1308,50 @@
         });
     }
 
+    void setUpBubbleViewsForMode() {
+        mBubbleViewCallback = isShowingAsBubbleBar()
+                ? mBubbleBarViewCallback
+                : mBubbleStackViewCallback;
+
+        // reset the overflow so that it can be re-added later if needed.
+        if (mStackView != null) {
+            mStackView.resetOverflowView();
+            mStackView.removeAllViews();
+        }
+        // cleanup existing bubble views so they can be recreated later if needed.
+        mBubbleData.getBubbles().forEach(Bubble::cleanupViews);
+
+        // remove the current bubble container from window manager, null it out, and create a new
+        // container based on the current mode.
+        removeFromWindowManagerMaybe();
+        mLayerView = null;
+        mStackView = null;
+        ensureBubbleViewsAndWindowCreated();
+
+        // inflate bubble views
+        BubbleViewInfoTask.Callback callback = null;
+        if (!isShowingAsBubbleBar()) {
+            callback = b -> {
+                if (mStackView != null) {
+                    mStackView.addBubble(b);
+                    mStackView.setSelectedBubble(b);
+                } else {
+                    Log.w(TAG, "Tried to add a bubble to the stack but the stack is null");
+                }
+            };
+        }
+        for (int i = mBubbleData.getBubbles().size() - 1; i >= 0; i--) {
+            Bubble bubble = mBubbleData.getBubbles().get(i);
+            bubble.inflate(callback,
+                    mContext,
+                    this,
+                    mStackView,
+                    mLayerView,
+                    mBubbleIconFactory,
+                    false /* skipInflation */);
+        }
+    }
+
     /**
      * Adds or updates a bubble associated with the provided notification entry.
      *
@@ -1738,7 +1812,7 @@
             // Update the cached state for queries from SysUI
             mImpl.mCachedState.update(update);
 
-            if (isShowingAsBubbleBar() && mBubbleStateListener != null) {
+            if (isShowingAsBubbleBar()) {
                 BubbleBarUpdate bubbleBarUpdate = update.toBubbleBarUpdate();
                 // Some updates aren't relevant to the bubble bar so check first.
                 if (bubbleBarUpdate.anythingChanged()) {
@@ -1846,7 +1920,7 @@
             if (mStackView != null) {
                 mStackView.setVisibility(VISIBLE);
             }
-            if (mLayerView != null && isStackExpanded()) {
+            if (mLayerView != null) {
                 mLayerView.setVisibility(VISIBLE);
             }
         }
@@ -1860,10 +1934,17 @@
     }
 
     @VisibleForTesting
+    @Nullable
     public BubbleStackView getStackView() {
         return mStackView;
     }
 
+    @VisibleForTesting
+    @Nullable
+    public BubbleBarLayerView getLayerView() {
+        return mLayerView;
+    }
+
     /**
      * Check if notification panel is in an expanded state.
      * Makes a call to System UI process and delivers the result via {@code callback} on the
@@ -2002,22 +2083,19 @@
 
         @Override
         public void registerBubbleListener(IBubblesListener listener) {
-            mMainExecutor.execute(() -> {
-                mListener.register(listener);
-            });
+            mMainExecutor.execute(() -> mListener.register(listener));
         }
 
         @Override
         public void unregisterBubbleListener(IBubblesListener listener) {
-            mMainExecutor.execute(() -> mListener.unregister());
+            mMainExecutor.execute(mListener::unregister);
         }
 
         @Override
-        public void showBubble(String key, boolean onLauncherHome) {
-            mMainExecutor.execute(() -> {
-                mBubblePositioner.setShowingInBubbleBar(onLauncherHome);
-                mController.expandStackAndSelectBubbleFromLauncher(key);
-            });
+        public void showBubble(String key, int bubbleBarOffsetX, int bubbleBarOffsetY) {
+            mMainExecutor.execute(
+                    () -> mController.expandStackAndSelectBubbleFromLauncher(
+                            key, bubbleBarOffsetX, bubbleBarOffsetY));
         }
 
         @Override
@@ -2029,11 +2107,6 @@
         public void collapseBubbles() {
             mMainExecutor.execute(() -> mController.collapseStack());
         }
-
-        @Override
-        public void onTaskbarStateChanged(int newState) {
-            // TODO (b/269670598)
-        }
     }
 
     private class BubblesImpl implements Bubbles {
@@ -2144,7 +2217,7 @@
                     pw.println("   suppressing: " + key);
                 }
 
-                pw.print("mAppBubbleTaskIds: " + mAppBubbleTaskIds.values());
+                pw.println("mAppBubbleTaskIds: " + mAppBubbleTaskIds.values());
             }
         }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDataRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDataRepository.kt
index e37c785..896a334 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDataRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDataRepository.kt
@@ -17,7 +17,6 @@
 
 import android.annotation.SuppressLint
 import android.annotation.UserIdInt
-import android.content.Context
 import android.content.pm.LauncherApps
 import android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_CACHED
 import android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC
@@ -25,6 +24,8 @@
 import android.content.pm.UserInfo
 import android.os.UserHandle
 import android.util.Log
+import android.util.SparseArray
+import com.android.internal.annotations.VisibleForTesting
 import com.android.wm.shell.bubbles.Bubbles.BubbleMetadataFlagListener
 import com.android.wm.shell.bubbles.storage.BubbleEntity
 import com.android.wm.shell.bubbles.storage.BubblePersistentRepository
@@ -33,19 +34,19 @@
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.Job
+import kotlinx.coroutines.SupervisorJob
 import kotlinx.coroutines.cancelAndJoin
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.yield
 
-internal class BubbleDataRepository(
-    context: Context,
+class BubbleDataRepository(
     private val launcherApps: LauncherApps,
-    private val mainExecutor: ShellExecutor
+    private val mainExecutor: ShellExecutor,
+    private val persistentRepository: BubblePersistentRepository,
 ) {
     private val volatileRepository = BubbleVolatileRepository(launcherApps)
-    private val persistentRepository = BubblePersistentRepository(context)
 
-    private val ioScope = CoroutineScope(Dispatchers.IO)
+    private val coroutineScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
     private var job: Job? = null
 
     // For use in Bubble construction.
@@ -98,6 +99,43 @@
         if (volatileRepository.sanitizeBubbles(userIds)) persistToDisk()
     }
 
+    /**
+     * Removes all entities that don't have a user in the activeUsers list, if any entities were
+     * removed it persists the new list to disk.
+     */
+    private fun filterForActiveUsersAndPersist(
+            activeUsers: List<Int>,
+            entitiesByUser: SparseArray<List<BubbleEntity>>
+    ): SparseArray<List<BubbleEntity>> {
+        val validEntitiesByUser = SparseArray<List<BubbleEntity>>()
+        var entitiesChanged = false
+        for (i in 0 until entitiesByUser.size()) {
+            val parentUserId = entitiesByUser.keyAt(i)
+            if (activeUsers.contains(parentUserId)) {
+                val validEntities = mutableListOf<BubbleEntity>()
+                // Check if each of the bubbles in the top-level user still has a valid user
+                // as it could belong to a profile and have a different id from the parent.
+                for (entity in entitiesByUser.get(parentUserId)) {
+                    if (activeUsers.contains(entity.userId)) {
+                        validEntities.add(entity)
+                    } else {
+                        entitiesChanged = true
+                    }
+                }
+                if (validEntities.isNotEmpty()) {
+                    validEntitiesByUser.put(parentUserId, validEntities)
+                }
+            } else {
+                entitiesChanged = true
+            }
+        }
+        if (entitiesChanged) {
+            persistToDisk(validEntitiesByUser)
+            return validEntitiesByUser
+        }
+        return entitiesByUser
+    }
+
     private fun transform(bubbles: List<Bubble>): List<BubbleEntity> {
         return bubbles.mapNotNull { b ->
             BubbleEntity(
@@ -129,15 +167,17 @@
      * Job C resumes and reaches yield() and is then cancelled
      * Job D resumes and performs another blocking I/O
      */
-    private fun persistToDisk() {
+    private fun persistToDisk(
+            entitiesByUser: SparseArray<List<BubbleEntity>> = volatileRepository.bubbles
+    ) {
         val prev = job
-        job = ioScope.launch {
+        job = coroutineScope.launch {
             // if there was an ongoing disk I/O operation, they can be cancelled
             prev?.cancelAndJoin()
             // check for cancellation before disk I/O
             yield()
             // save to disk
-            persistentRepository.persistsToDisk(volatileRepository.bubbles)
+            persistentRepository.persistsToDisk(entitiesByUser)
         }
     }
 
@@ -148,7 +188,12 @@
      *           bubbles.
      */
     @SuppressLint("WrongConstant")
-    fun loadBubbles(userId: Int, cb: (List<Bubble>) -> Unit) = ioScope.launch {
+    @VisibleForTesting
+    fun loadBubbles(
+            userId: Int,
+            currentUsers: List<Int>,
+            cb: (List<Bubble>) -> Unit
+    ) = coroutineScope.launch {
         /**
          * Load BubbleEntity from disk.
          * e.g.
@@ -159,7 +204,12 @@
          * ]
          */
         val entitiesByUser = persistentRepository.readFromDisk()
-        val entities = entitiesByUser.get(userId) ?: return@launch
+
+        // Before doing anything, validate that the entities we loaded are valid & have an existing
+        // user.
+        val validEntitiesByUser = filterForActiveUsersAndPersist(currentUsers, entitiesByUser)
+
+        val entities = validEntitiesByUser.get(userId) ?: return@launch
         volatileRepository.addBubbles(userId, entities)
         /**
          * Extract userId/packageName from these entities.
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 e1a3f3a..6718565 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
@@ -948,9 +948,9 @@
             mTaskView.onLocationChanged();
         }
         if (mIsOverflow) {
-            post(() -> {
-                mOverflowView.show();
-            });
+            // post this to the looper so that the view has a chance to be laid out before it can
+            // calculate row and column sizes correctly.
+            post(() -> mOverflowView.show());
         }
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
index df7f5b4..df19757 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
@@ -44,6 +44,7 @@
 
     private val inflater: LayoutInflater = LayoutInflater.from(context)
     private var expandedView: BubbleExpandedView?
+    private var bubbleBarExpandedView: BubbleBarExpandedView? = null
     private var overflowBtn: BadgedImageView?
 
     init {
@@ -53,19 +54,26 @@
     }
 
     /** Call before use and again if cleanUpExpandedState was called. */
-    fun initialize(controller: BubbleController) {
-        createExpandedView()
-        getExpandedView()?.initialize(controller, controller.stackView, true /* isOverflow */)
+    fun initialize(controller: BubbleController, forBubbleBar: Boolean) {
+        if (forBubbleBar) {
+            createBubbleBarExpandedView().initialize(controller, true /* isOverflow */)
+        } else {
+            createExpandedView()
+                .initialize(controller, controller.stackView, true /* isOverflow */)
+        }
     }
 
     fun cleanUpExpandedState() {
         expandedView?.cleanUpExpandedState()
         expandedView = null
+        bubbleBarExpandedView?.cleanUpExpandedState()
+        bubbleBarExpandedView = null
     }
 
     fun update() {
         updateResources()
         getExpandedView()?.applyThemeAttrs()
+        getBubbleBarExpandedView()?.applyThemeAttrs()
         // Apply inset and new style to fresh icon drawable.
         getIconView()?.setIconImageResource(R.drawable.bubble_ic_overflow_button)
         updateBtnTheme()
@@ -151,26 +159,39 @@
         overflowBtn?.updateDotVisibility(true /* animate */)
     }
 
-    fun createExpandedView(): BubbleExpandedView? {
-        expandedView =
+    /** Creates the expanded view for bubbles showing in the stack view. */
+    private fun createExpandedView(): BubbleExpandedView {
+        val view =
             inflater.inflate(
                 R.layout.bubble_expanded_view,
                 null /* root */,
                 false /* attachToRoot */
             ) as BubbleExpandedView
-        expandedView?.applyThemeAttrs()
+        view.applyThemeAttrs()
+        expandedView = view
         updateResources()
-        return expandedView
+        return view
     }
 
     override fun getExpandedView(): BubbleExpandedView? {
         return expandedView
     }
 
-    override fun getBubbleBarExpandedView(): BubbleBarExpandedView? {
-        return null
+    /** Creates the expanded view for bubbles showing in the bubble bar. */
+    private fun createBubbleBarExpandedView(): BubbleBarExpandedView {
+        val view =
+            inflater.inflate(
+                R.layout.bubble_bar_expanded_view,
+                null, /* root */
+                false /* attachToRoot*/
+            ) as BubbleBarExpandedView
+        view.applyThemeAttrs()
+        bubbleBarExpandedView = view
+        return view
     }
 
+    override fun getBubbleBarExpandedView(): BubbleBarExpandedView? = bubbleBarExpandedView
+
     override fun getDotColor(): Int {
         return dotColor
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
index d101b0c..ee6996d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
@@ -22,6 +22,7 @@
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.graphics.Insets;
+import android.graphics.Point;
 import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.RectF;
@@ -102,10 +103,7 @@
     private int[] mPaddings = new int[4];
 
     private boolean mShowingInBubbleBar;
-    private boolean mBubblesOnHome;
-    private int mBubbleBarSize;
-    private int mBubbleBarHomeAdjustment;
-    private final PointF mBubbleBarPosition = new PointF();
+    private final Point mBubbleBarPosition = new Point();
 
     public BubblePositioner(Context context, WindowManager windowManager) {
         mContext = context;
@@ -166,11 +164,9 @@
         mSpacingBetweenBubbles = res.getDimensionPixelSize(R.dimen.bubble_spacing);
         mDefaultMaxBubbles = res.getInteger(R.integer.bubbles_max_rendered);
         mExpandedViewPadding = res.getDimensionPixelSize(R.dimen.bubble_expanded_view_padding);
-        mBubbleBarHomeAdjustment = mExpandedViewPadding / 2;
         mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top);
         mBubbleOffscreenAmount = res.getDimensionPixelSize(R.dimen.bubble_stack_offscreen);
         mStackOffset = res.getDimensionPixelSize(R.dimen.bubble_stack_offset);
-        mBubbleBarSize = res.getDimensionPixelSize(R.dimen.bubblebar_size);
 
         if (mShowingInBubbleBar) {
             mExpandedViewLargeScreenWidth = isLandscape()
@@ -723,27 +719,36 @@
     }
 
     /**
-     * Sets whether bubbles are showing on launcher home, in which case positions are different.
+     * Sets the position of the bubble bar in screen coordinates.
+     *
+     * @param offsetX the offset of the bubble bar from the edge of the screen on the X axis
+     * @param offsetY the offset of the bubble bar from the edge of the screen on the Y axis
      */
-    public void setBubblesOnHome(boolean bubblesOnHome) {
-        mBubblesOnHome = bubblesOnHome;
+    public void setBubbleBarPosition(int offsetX, int offsetY) {
+        mBubbleBarPosition.set(
+                getAvailableRect().width() - offsetX,
+                getAvailableRect().height() + mInsets.top + mInsets.bottom - offsetY);
     }
 
     /**
      * How wide the expanded view should be when showing from the bubble bar.
      */
-    public int getExpandedViewWidthForBubbleBar() {
-        return mExpandedViewLargeScreenWidth;
+    public int getExpandedViewWidthForBubbleBar(boolean isOverflow) {
+        return isOverflow ? mOverflowWidth : mExpandedViewLargeScreenWidth;
     }
 
     /**
      * How tall the expanded view should be when showing from the bubble bar.
      */
-    public int getExpandedViewHeightForBubbleBar() {
-        return getAvailableRect().height()
-                - mBubbleBarSize
-                - mExpandedViewPadding * 2
-                - getBubbleBarHomeAdjustment();
+    public int getExpandedViewHeightForBubbleBar(boolean isOverflow) {
+        return isOverflow
+                ? mOverflowHeight
+                : getExpandedViewBottomForBubbleBar() - mInsets.top - mExpandedViewPadding;
+    }
+
+    /** The bottom position of the expanded view when showing above the bubble bar. */
+    public int getExpandedViewBottomForBubbleBar() {
+        return mBubbleBarPosition.y - mExpandedViewPadding;
     }
 
     /**
@@ -756,19 +761,7 @@
     /**
      * Returns the on screen co-ordinates of the bubble bar.
      */
-    public PointF getBubbleBarPosition() {
-        mBubbleBarPosition.set(getAvailableRect().width() - mBubbleBarSize,
-                getAvailableRect().height() - mBubbleBarSize
-                        - mExpandedViewPadding - getBubbleBarHomeAdjustment());
+    public Point getBubbleBarPosition() {
         return mBubbleBarPosition;
     }
-
-    /**
-     * When bubbles are shown on launcher home, there's an extra bit of padding that needs to
-     * be applied between the expanded view and the bubble bar. This returns the adjustment value
-     * if bubbles are showing on home.
-     */
-    private int getBubbleBarHomeAdjustment() {
-        return mBubblesOnHome ? mBubbleBarHomeAdjustment : 0;
-    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index 68fea41..282db9e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -88,6 +88,8 @@
 import com.android.wm.shell.bubbles.animation.StackAnimationController;
 import com.android.wm.shell.common.FloatingContentCoordinator;
 import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.bubbles.DismissView;
+import com.android.wm.shell.common.bubbles.RelativeTouchListener;
 import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
 
 import java.io.PrintWriter;
@@ -307,6 +309,7 @@
 
         String bubblesOnScreen = BubbleDebugConfig.formatBubblesString(
                 getBubblesOnScreen(), getExpandedBubble());
+        pw.print("  stack visibility :       "); pw.println(getVisibility());
         pw.print("  bubbles on screen:       "); pw.println(bubblesOnScreen);
         pw.print("  gestureInProgress:       "); pw.println(mIsGestureInProgress);
         pw.print("  showingDismiss:          "); pw.println(mDismissView.isShowing());
@@ -968,6 +971,8 @@
         mBubbleContainer.bringToFront();
 
         mBubbleOverflow = mBubbleData.getOverflow();
+
+        resetOverflowView();
         mBubbleContainer.addView(mBubbleOverflow.getIconView(),
                 mBubbleContainer.getChildCount() /* index */,
                 new FrameLayout.LayoutParams(mPositioner.getBubbleSize(),
@@ -1179,6 +1184,7 @@
             removeView(mDismissView);
         }
         mDismissView = new DismissView(getContext());
+        DismissViewUtils.setup(mDismissView);
         int elevation = getResources().getDimensionPixelSize(R.dimen.bubble_elevation);
 
         addView(mDismissView);
@@ -1494,9 +1500,6 @@
         getViewTreeObserver().removeOnPreDrawListener(mViewUpdater);
         getViewTreeObserver().removeOnDrawListener(mSystemGestureExcludeUpdater);
         getViewTreeObserver().removeOnComputeInternalInsetsListener(this);
-        if (mBubbleOverflow != null) {
-            mBubbleOverflow.cleanUpExpandedState();
-        }
     }
 
     @Override
@@ -3411,6 +3414,19 @@
     }
 
     /**
+     * Removes the overflow view from the stack. This allows for re-adding it later to a new stack.
+     */
+    void resetOverflowView() {
+        BadgedImageView overflowIcon = mBubbleOverflow.getIconView();
+        if (overflowIcon != null) {
+            PhysicsAnimationLayout parent = (PhysicsAnimationLayout) overflowIcon.getParent();
+            if (parent != null) {
+                parent.removeViewNoAnimation(overflowIcon);
+            }
+        }
+    }
+
+    /**
      * Holds some commonly queried information about the stack.
      */
     public static class StackViewState {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java
index 7a58159..da4a989 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java
@@ -15,6 +15,7 @@
  */
 package com.android.wm.shell.bubbles;
 
+import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
 import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
@@ -110,6 +111,9 @@
                 try {
                     options.setTaskAlwaysOnTop(true);
                     options.setLaunchedFromBubble(true);
+                    options.setPendingIntentBackgroundActivityStartMode(
+                            MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
+                    options.setPendingIntentBackgroundActivityLaunchAllowedByPermission(true);
 
                     Intent fillInIntent = new Intent();
                     // Apply flags to make behaviour match documentLaunchMode=always.
@@ -117,11 +121,19 @@
                     fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
 
                     if (mBubble.isAppBubble()) {
-                        PendingIntent pi = PendingIntent.getActivity(mContext, 0,
-                                mBubble.getAppBubbleIntent(),
-                                PendingIntent.FLAG_MUTABLE,
-                                null);
-                        mTaskView.startActivity(pi, fillInIntent, options, launchBounds);
+                        Context context =
+                                mContext.createContextAsUser(
+                                        mBubble.getUser(), Context.CONTEXT_RESTRICTED);
+                        PendingIntent pi = PendingIntent.getActivity(
+                                context,
+                                /* requestCode= */ 0,
+                                mBubble.getAppBubbleIntent()
+                                        .addFlags(FLAG_ACTIVITY_NEW_DOCUMENT)
+                                        .addFlags(FLAG_ACTIVITY_MULTIPLE_TASK),
+                                PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT,
+                                /* options= */ null);
+                        mTaskView.startActivity(pi, /* fillInIntent= */ null, options,
+                                launchBounds);
                     } else if (mBubble.hasMetadataShortcutId()) {
                         options.setApplyActivityFlagsForBubbles(true);
                         mTaskView.startShortcutActivity(mBubble.getShortcutInfo(),
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java
index 8ab9841..66e6930 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java
@@ -104,7 +104,11 @@
 
     @Override
     protected BubbleViewInfo doInBackground(Void... voids) {
-        if (mController.get().isShowingAsBubbleBar()) {
+        if (!verifyState()) {
+            // If we're in an inconsistent state, then switched modes and should just bail now.
+            return null;
+        }
+        if (mLayerView.get() != null) {
             return BubbleViewInfo.populateForBubbleBar(mContext.get(), mController.get(),
                     mLayerView.get(), mIconFactory, mBubble, mSkipInflation);
         } else {
@@ -118,7 +122,11 @@
         if (isCancelled() || viewInfo == null) {
             return;
         }
+
         mMainExecutor.execute(() -> {
+            if (!verifyState()) {
+                return;
+            }
             mBubble.setViewInfo(viewInfo);
             if (mCallback != null) {
                 mCallback.onBubbleViewsReady(mBubble);
@@ -126,6 +134,14 @@
         });
     }
 
+    private boolean verifyState() {
+        if (mController.get().isShowingAsBubbleBar()) {
+            return mLayerView.get() != null;
+        } else {
+            return mStackView.get() != null;
+        }
+    }
+
     /**
      * Info necessary to render a bubble.
      */
@@ -160,39 +176,14 @@
                 LayoutInflater inflater = LayoutInflater.from(c);
                 info.bubbleBarExpandedView = (BubbleBarExpandedView) inflater.inflate(
                         R.layout.bubble_bar_expanded_view, layerView, false /* attachToRoot */);
-                info.bubbleBarExpandedView.initialize(controller);
+                info.bubbleBarExpandedView.initialize(controller, false /* isOverflow */);
             }
 
-            if (b.getShortcutInfo() != null) {
-                info.shortcutInfo = b.getShortcutInfo();
-            }
-
-            // App name & app icon
-            PackageManager pm = BubbleController.getPackageManagerForUser(c,
-                    b.getUser().getIdentifier());
-            ApplicationInfo appInfo;
-            Drawable badgedIcon;
-            Drawable appIcon;
-            try {
-                appInfo = pm.getApplicationInfo(
-                        b.getPackageName(),
-                        PackageManager.MATCH_UNINSTALLED_PACKAGES
-                                | PackageManager.MATCH_DISABLED_COMPONENTS
-                                | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
-                                | PackageManager.MATCH_DIRECT_BOOT_AWARE);
-                if (appInfo != null) {
-                    info.appName = String.valueOf(pm.getApplicationLabel(appInfo));
-                }
-                appIcon = pm.getApplicationIcon(b.getPackageName());
-                badgedIcon = pm.getUserBadgedIcon(appIcon, b.getUser());
-            } catch (PackageManager.NameNotFoundException exception) {
-                // If we can't find package... don't think we should show the bubble.
-                Log.w(TAG, "Unable to find package: " + b.getPackageName());
+            if (!populateCommonInfo(info, c, b, iconFactory)) {
+                // if we failed to update common fields return null
                 return null;
             }
 
-            info.rawBadgeBitmap = iconFactory.getBadgeBitmap(badgedIcon, false).icon;
-
             return info;
         }
 
@@ -215,66 +206,11 @@
                 info.expandedView.initialize(controller, stackView, false /* isOverflow */);
             }
 
-            if (b.getShortcutInfo() != null) {
-                info.shortcutInfo = b.getShortcutInfo();
-            }
-
-            // App name & app icon
-            PackageManager pm = BubbleController.getPackageManagerForUser(c,
-                    b.getUser().getIdentifier());
-            ApplicationInfo appInfo;
-            Drawable badgedIcon;
-            Drawable appIcon;
-            try {
-                appInfo = pm.getApplicationInfo(
-                        b.getPackageName(),
-                        PackageManager.MATCH_UNINSTALLED_PACKAGES
-                                | PackageManager.MATCH_DISABLED_COMPONENTS
-                                | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
-                                | PackageManager.MATCH_DIRECT_BOOT_AWARE);
-                if (appInfo != null) {
-                    info.appName = String.valueOf(pm.getApplicationLabel(appInfo));
-                }
-                appIcon = pm.getApplicationIcon(b.getPackageName());
-                badgedIcon = pm.getUserBadgedIcon(appIcon, b.getUser());
-            } catch (PackageManager.NameNotFoundException exception) {
-                // If we can't find package... don't think we should show the bubble.
-                Log.w(TAG, "Unable to find package: " + b.getPackageName());
+            if (!populateCommonInfo(info, c, b, iconFactory)) {
+                // if we failed to update common fields return null
                 return null;
             }
 
-            // Badged bubble image
-            Drawable bubbleDrawable = iconFactory.getBubbleDrawable(c, info.shortcutInfo,
-                    b.getIcon());
-            if (bubbleDrawable == null) {
-                // Default to app icon
-                bubbleDrawable = appIcon;
-            }
-
-            BitmapInfo badgeBitmapInfo = iconFactory.getBadgeBitmap(badgedIcon,
-                    b.isImportantConversation());
-            info.badgeBitmap = badgeBitmapInfo.icon;
-            // Raw badge bitmap never includes the important conversation ring
-            info.rawBadgeBitmap = b.isImportantConversation()
-                    ? iconFactory.getBadgeBitmap(badgedIcon, false).icon
-                    : badgeBitmapInfo.icon;
-
-            float[] bubbleBitmapScale = new float[1];
-            info.bubbleBitmap = iconFactory.getBubbleBitmap(bubbleDrawable, bubbleBitmapScale);
-
-            // Dot color & placement
-            Path iconPath = PathParser.createPathFromPathData(
-                    c.getResources().getString(com.android.internal.R.string.config_icon_mask));
-            Matrix matrix = new Matrix();
-            float scale = bubbleBitmapScale[0];
-            float radius = DEFAULT_PATH_SIZE / 2f;
-            matrix.setScale(scale /* x scale */, scale /* y scale */, radius /* pivot x */,
-                    radius /* pivot y */);
-            iconPath.transform(matrix);
-            info.dotPath = iconPath;
-            info.dotColor = ColorUtils.blendARGB(badgeBitmapInfo.color,
-                    Color.WHITE, WHITE_SCRIM_ALPHA);
-
             // Flyout
             info.flyoutMessage = b.getFlyoutMessage();
             if (info.flyoutMessage != null) {
@@ -285,6 +221,75 @@
         }
     }
 
+    /**
+     * Modifies the given {@code info} object and populates common fields in it.
+     *
+     * <p>This method returns {@code true} if the update was successful and {@code false} otherwise.
+     * Callers should assume that the info object is unusable if the update was unsuccessful.
+     */
+    private static boolean populateCommonInfo(
+            BubbleViewInfo info, Context c, Bubble b, BubbleIconFactory iconFactory) {
+        if (b.getShortcutInfo() != null) {
+            info.shortcutInfo = b.getShortcutInfo();
+        }
+
+        // App name & app icon
+        PackageManager pm = BubbleController.getPackageManagerForUser(c,
+                b.getUser().getIdentifier());
+        ApplicationInfo appInfo;
+        Drawable badgedIcon;
+        Drawable appIcon;
+        try {
+            appInfo = pm.getApplicationInfo(
+                    b.getPackageName(),
+                    PackageManager.MATCH_UNINSTALLED_PACKAGES
+                            | PackageManager.MATCH_DISABLED_COMPONENTS
+                            | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
+                            | PackageManager.MATCH_DIRECT_BOOT_AWARE);
+            if (appInfo != null) {
+                info.appName = String.valueOf(pm.getApplicationLabel(appInfo));
+            }
+            appIcon = pm.getApplicationIcon(b.getPackageName());
+            badgedIcon = pm.getUserBadgedIcon(appIcon, b.getUser());
+        } catch (PackageManager.NameNotFoundException exception) {
+            // If we can't find package... don't think we should show the bubble.
+            Log.w(TAG, "Unable to find package: " + b.getPackageName());
+            return false;
+        }
+
+        // Badged bubble image
+        Drawable bubbleDrawable = iconFactory.getBubbleDrawable(c, info.shortcutInfo, b.getIcon());
+        if (bubbleDrawable == null) {
+            // Default to app icon
+            bubbleDrawable = appIcon;
+        }
+
+        BitmapInfo badgeBitmapInfo = iconFactory.getBadgeBitmap(badgedIcon,
+                b.isImportantConversation());
+        info.badgeBitmap = badgeBitmapInfo.icon;
+        // Raw badge bitmap never includes the important conversation ring
+        info.rawBadgeBitmap = b.isImportantConversation() // is this needed for bar?
+                ? iconFactory.getBadgeBitmap(badgedIcon, false).icon
+                : badgeBitmapInfo.icon;
+
+        float[] bubbleBitmapScale = new float[1];
+        info.bubbleBitmap = iconFactory.getBubbleBitmap(bubbleDrawable, bubbleBitmapScale);
+
+        // Dot color & placement
+        Path iconPath = PathParser.createPathFromPathData(
+                c.getResources().getString(com.android.internal.R.string.config_icon_mask));
+        Matrix matrix = new Matrix();
+        float scale = bubbleBitmapScale[0];
+        float radius = DEFAULT_PATH_SIZE / 2f;
+        matrix.setScale(scale /* x scale */, scale /* y scale */, radius /* pivot x */,
+                radius /* pivot y */);
+        iconPath.transform(matrix);
+        info.dotPath = iconPath;
+        info.dotColor = ColorUtils.blendARGB(badgeBitmapInfo.color,
+                Color.WHITE, WHITE_SCRIM_ALPHA);
+        return true;
+    }
+
     @Nullable
     static Drawable loadSenderAvatar(@NonNull final Context context, @Nullable final Icon icon) {
         Objects.requireNonNull(context);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
index 4d329dd..759246e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
@@ -60,9 +60,11 @@
             DISMISS_NOTIF_CANCEL, DISMISS_ACCESSIBILITY_ACTION, DISMISS_NO_LONGER_BUBBLE,
             DISMISS_USER_CHANGED, DISMISS_GROUP_CANCELLED, DISMISS_INVALID_INTENT,
             DISMISS_OVERFLOW_MAX_REACHED, DISMISS_SHORTCUT_REMOVED, DISMISS_PACKAGE_REMOVED,
-            DISMISS_NO_BUBBLE_UP, DISMISS_RELOAD_FROM_DISK, DISMISS_USER_REMOVED})
+            DISMISS_NO_BUBBLE_UP, DISMISS_RELOAD_FROM_DISK, DISMISS_USER_REMOVED,
+            DISMISS_SWITCH_TO_STACK})
     @Target({FIELD, LOCAL_VARIABLE, PARAMETER})
-    @interface DismissReason {}
+    @interface DismissReason {
+    }
 
     int DISMISS_USER_GESTURE = 1;
     int DISMISS_AGED = 2;
@@ -80,6 +82,7 @@
     int DISMISS_NO_BUBBLE_UP = 14;
     int DISMISS_RELOAD_FROM_DISK = 15;
     int DISMISS_USER_REMOVED = 16;
+    int DISMISS_SWITCH_TO_STACK = 17;
 
     /** Returns a binder that can be passed to an external process to manipulate Bubbles. */
     default IBubbles createExternalInterface() {
@@ -120,8 +123,8 @@
 
     /**
      * This method has different behavior depending on:
-     *    - if an app bubble exists
-     *    - if an app bubble is expanded
+     * - if an app bubble exists
+     * - if an app bubble is expanded
      *
      * If no app bubble exists, this will add and expand a bubble with the provided intent. The
      * intent must be explicit (i.e. include a package name or fully qualified component class name)
@@ -135,13 +138,13 @@
      * the bubble or bubble stack.
      *
      * Some notes:
-     *    - Only one app bubble is supported at a time, regardless of users. Multi-users support is
-     *      tracked in b/273533235.
-     *    - Calling this method with a different intent than the existing app bubble will do nothing
+     * - Only one app bubble is supported at a time, regardless of users. Multi-users support is
+     * tracked in b/273533235.
+     * - Calling this method with a different intent than the existing app bubble will do nothing
      *
      * @param intent the intent to display in the bubble expanded view.
-     * @param user the {@link UserHandle} of the user to start this activity for.
-     * @param icon the {@link Icon} to use for the bubble view.
+     * @param user   the {@link UserHandle} of the user to start this activity for.
+     * @param icon   the {@link Icon} to use for the bubble view.
      */
     void showOrHideAppBubble(Intent intent, UserHandle user, @Nullable Icon icon);
 
@@ -172,13 +175,12 @@
      * {@link Bubble#setSuppressNotification}.  For the case of suppressed summaries, we also add
      * {@link BubbleData#addSummaryToSuppress}.
      *
-     * @param entry the notification of the BubbleEntry should be removed.
-     * @param children the list of child notification of the BubbleEntry from 1st param entry,
-     *                 this will be null if entry does have no children.
+     * @param entry          the notification of the BubbleEntry should be removed.
+     * @param children       the list of child notification of the BubbleEntry from 1st param entry,
+     *                       this will be null if entry does have no children.
      * @param removeCallback the remove callback for SystemUI side to remove notification, the int
      *                       number should be list position of children list and use -1 for
      *                       removing the parent notification.
-     *
      * @return true if we want to intercept the dismissal of the entry, else false.
      */
     boolean handleDismissalInterception(BubbleEntry entry, @Nullable List<BubbleEntry> children,
@@ -200,9 +202,9 @@
     /**
      * Called when new notification entry updated.
      *
-     * @param entry the {@link BubbleEntry} by the notification.
+     * @param entry          the {@link BubbleEntry} by the notification.
      * @param shouldBubbleUp {@code true} if this notification should bubble up.
-     * @param fromSystem {@code true} if this update is from NotificationManagerService.
+     * @param fromSystem     {@code true} if this update is from NotificationManagerService.
      */
     void onEntryUpdated(BubbleEntry entry, boolean shouldBubbleUp, boolean fromSystem);
 
@@ -218,7 +220,7 @@
      * filtering and sorting. This is used to dismiss or create bubbles based on changes in
      * permissions on the notification channel or the global setting.
      *
-     * @param rankingMap the updated ranking map from NotificationListenerService
+     * @param rankingMap     the updated ranking map from NotificationListenerService
      * @param entryDataByKey a map of ranking key to bubble entry and whether the entry should
      *                       bubble up
      */
@@ -230,9 +232,9 @@
      * Called when a notification channel is modified, in response to
      * {@link NotificationListenerService#onNotificationChannelModified}.
      *
-     * @param pkg the package the notification channel belongs to.
-     * @param user the user the notification channel belongs to.
-     * @param channel the channel being modified.
+     * @param pkg              the package the notification channel belongs to.
+     * @param user             the user the notification channel belongs to.
+     * @param channel          the channel being modified.
      * @param modificationType the type of modification that occurred to the channel.
      */
     void onNotificationChannelModified(
@@ -300,7 +302,7 @@
          * Called when the expansion state of the bubble stack changes.
          *
          * @param isExpanding whether it's expanding or collapsing
-         * @param key the notification key associated with bubble being expanded
+         * @param key         the notification key associated with bubble being expanded
          */
         void onBubbleExpandChanged(boolean isExpanding, String key);
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/DismissView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/DismissView.kt
deleted file mode 100644
index 67ecb91..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/DismissView.kt
+++ /dev/null
@@ -1,159 +0,0 @@
-/*
- * 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.
- */
-
-package com.android.wm.shell.bubbles
-
-import android.animation.ObjectAnimator
-import android.content.Context
-import android.graphics.Color
-import android.graphics.drawable.GradientDrawable
-import android.util.IntProperty
-import android.view.Gravity
-import android.view.View
-import android.view.ViewGroup
-import android.view.WindowInsets
-import android.view.WindowManager
-import android.widget.FrameLayout
-import androidx.dynamicanimation.animation.DynamicAnimation
-import androidx.dynamicanimation.animation.SpringForce.DAMPING_RATIO_LOW_BOUNCY
-import androidx.dynamicanimation.animation.SpringForce.STIFFNESS_LOW
-import com.android.wm.shell.R
-import com.android.wm.shell.animation.PhysicsAnimator
-import com.android.wm.shell.common.DismissCircleView
-
-/*
- * View that handles interactions between DismissCircleView and BubbleStackView.
- */
-class DismissView(context: Context) : FrameLayout(context) {
-
-    var circle = DismissCircleView(context)
-    var isShowing = false
-    var targetSizeResId: Int
-
-    private val animator = PhysicsAnimator.getInstance(circle)
-    private val spring = PhysicsAnimator.SpringConfig(STIFFNESS_LOW, DAMPING_RATIO_LOW_BOUNCY)
-    private val DISMISS_SCRIM_FADE_MS = 200L
-    private var wm: WindowManager =
-            context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
-    private var gradientDrawable = createGradient()
-
-    private val GRADIENT_ALPHA: IntProperty<GradientDrawable> =
-            object : IntProperty<GradientDrawable>("alpha") {
-        override fun setValue(d: GradientDrawable, percent: Int) {
-            d.alpha = percent
-        }
-        override fun get(d: GradientDrawable): Int {
-            return d.alpha
-        }
-    }
-
-    init {
-        setLayoutParams(LayoutParams(
-            ViewGroup.LayoutParams.MATCH_PARENT,
-            resources.getDimensionPixelSize(R.dimen.floating_dismiss_gradient_height),
-            Gravity.BOTTOM))
-        updatePadding()
-        setClipToPadding(false)
-        setClipChildren(false)
-        setVisibility(View.INVISIBLE)
-        setBackgroundDrawable(gradientDrawable)
-
-        targetSizeResId = R.dimen.dismiss_circle_size
-        val targetSize: Int = resources.getDimensionPixelSize(targetSizeResId)
-        addView(circle, LayoutParams(targetSize, targetSize,
-                Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL))
-        // start with circle offscreen so it's animated up
-        circle.setTranslationY(resources.getDimensionPixelSize(
-                R.dimen.floating_dismiss_gradient_height).toFloat())
-    }
-
-    /**
-     * Animates this view in.
-     */
-    fun show() {
-        if (isShowing) return
-        isShowing = true
-        setVisibility(View.VISIBLE)
-        val alphaAnim = ObjectAnimator.ofInt(gradientDrawable, GRADIENT_ALPHA,
-                gradientDrawable.alpha, 255)
-        alphaAnim.setDuration(DISMISS_SCRIM_FADE_MS)
-        alphaAnim.start()
-
-        animator.cancel()
-        animator
-            .spring(DynamicAnimation.TRANSLATION_Y, 0f, spring)
-            .start()
-    }
-
-    /**
-     * Animates this view out, as well as the circle that encircles the bubbles, if they
-     * were dragged into the target and encircled.
-     */
-    fun hide() {
-        if (!isShowing) return
-        isShowing = false
-        val alphaAnim = ObjectAnimator.ofInt(gradientDrawable, GRADIENT_ALPHA,
-                gradientDrawable.alpha, 0)
-        alphaAnim.setDuration(DISMISS_SCRIM_FADE_MS)
-        alphaAnim.start()
-        animator
-            .spring(DynamicAnimation.TRANSLATION_Y, height.toFloat(),
-                spring)
-            .withEndActions({ setVisibility(View.INVISIBLE) })
-            .start()
-    }
-
-    /**
-     * Cancels the animator for the dismiss target.
-     */
-    fun cancelAnimators() {
-        animator.cancel()
-    }
-
-    fun updateResources() {
-        updatePadding()
-        layoutParams.height = resources.getDimensionPixelSize(
-                R.dimen.floating_dismiss_gradient_height)
-
-        val targetSize = resources.getDimensionPixelSize(targetSizeResId)
-        circle.layoutParams.width = targetSize
-        circle.layoutParams.height = targetSize
-        circle.requestLayout()
-    }
-
-    private fun createGradient(): GradientDrawable {
-        val gradientColor = context.resources.getColor(android.R.color.system_neutral1_900)
-        val alpha = 0.7f * 255
-        val gradientColorWithAlpha = Color.argb(alpha.toInt(),
-                Color.red(gradientColor),
-                Color.green(gradientColor),
-                Color.blue(gradientColor))
-        val gd = GradientDrawable(
-                GradientDrawable.Orientation.BOTTOM_TOP,
-                intArrayOf(gradientColorWithAlpha, Color.TRANSPARENT))
-        gd.setDither(true)
-        gd.setAlpha(0)
-        return gd
-    }
-
-    private fun updatePadding() {
-        val insets: WindowInsets = wm.getCurrentWindowMetrics().getWindowInsets()
-        val navInset = insets.getInsetsIgnoringVisibility(
-                WindowInsets.Type.navigationBars())
-        setPadding(0, 0, 0, navInset.bottom +
-                resources.getDimensionPixelSize(R.dimen.floating_dismiss_bottom_margin))
-    }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/DismissViewExt.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/DismissViewExt.kt
new file mode 100644
index 0000000..ed36240
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/DismissViewExt.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.
+ */
+@file:JvmName("DismissViewUtils")
+
+package com.android.wm.shell.bubbles
+
+import com.android.wm.shell.R
+import com.android.wm.shell.common.bubbles.DismissView
+
+fun DismissView.setup() {
+    setup(DismissView.Config(
+            targetSizeResId = R.dimen.dismiss_circle_size,
+            iconSizeResId = R.dimen.dismiss_target_x_size,
+            bottomMarginResId = R.dimen.floating_dismiss_bottom_margin,
+            floatingGradientHeightResId = R.dimen.floating_dismiss_gradient_height,
+            floatingGradientColorResId = android.R.color.system_neutral1_900,
+            backgroundResId = R.drawable.dismiss_circle_background,
+            iconResId = R.drawable.pip_ic_close_white
+    ))
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
index 862e818..351319f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
@@ -29,12 +29,10 @@
 
     oneway void unregisterBubbleListener(in IBubblesListener listener) = 2;
 
-    oneway void showBubble(in String key, in boolean onLauncherHome) = 3;
+    oneway void showBubble(in String key, in int bubbleBarOffsetX, in int bubbleBarOffsetY) = 3;
 
     oneway void removeBubble(in String key, in int reason) = 4;
 
     oneway void collapseBubbles() = 5;
 
-    oneway void onTaskbarStateChanged(in int newState) = 6;
-
 }
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/RelativeTouchListener.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/RelativeTouchListener.kt
deleted file mode 100644
index ea9d065..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/RelativeTouchListener.kt
+++ /dev/null
@@ -1,168 +0,0 @@
-/*
- * 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.
- */
-
-package com.android.wm.shell.bubbles
-
-import android.graphics.PointF
-import android.view.MotionEvent
-import android.view.VelocityTracker
-import android.view.View
-import android.view.ViewConfiguration
-import kotlin.math.hypot
-
-/**
- * Listener which receives [onDown], [onMove], and [onUp] events, with relevant information about
- * the coordinates of the touch and the view relative to the initial ACTION_DOWN event and the
- * view's initial position.
- */
-abstract class RelativeTouchListener : View.OnTouchListener {
-
-    /**
-     * Called when an ACTION_DOWN event is received for the given view.
-     *
-     * @return False if the object is not interested in MotionEvents at this time, or true if we
-     * should consume this event and subsequent events, and begin calling [onMove].
-     */
-    abstract fun onDown(v: View, ev: MotionEvent): Boolean
-
-    /**
-     * Called when an ACTION_MOVE event is received for the given view. This signals that the view
-     * is being dragged.
-     *
-     * @param viewInitialX The view's translationX value when this touch gesture started.
-     * @param viewInitialY The view's translationY value when this touch gesture started.
-     * @param dx Horizontal distance covered since the initial ACTION_DOWN event, in pixels.
-     * @param dy Vertical distance covered since the initial ACTION_DOWN event, in pixels.
-     */
-    abstract fun onMove(
-        v: View,
-        ev: MotionEvent,
-        viewInitialX: Float,
-        viewInitialY: Float,
-        dx: Float,
-        dy: Float
-    )
-
-    /**
-     * Called when an ACTION_UP event is received for the given view. This signals that a drag or
-     * fling gesture has completed.
-     *
-     * @param viewInitialX The view's translationX value when this touch gesture started.
-     * @param viewInitialY The view's translationY value when this touch gesture started.
-     * @param dx Horizontal distance covered, in pixels.
-     * @param dy Vertical distance covered, in pixels.
-     * @param velX The final horizontal velocity of the gesture, in pixels/second.
-     * @param velY The final vertical velocity of the gesture, in pixels/second.
-     */
-    abstract fun onUp(
-        v: View,
-        ev: MotionEvent,
-        viewInitialX: Float,
-        viewInitialY: Float,
-        dx: Float,
-        dy: Float,
-        velX: Float,
-        velY: Float
-    )
-
-    /** The raw coordinates of the last ACTION_DOWN event. */
-    private val touchDown = PointF()
-
-    /** The coordinates of the view, at the time of the last ACTION_DOWN event. */
-    private val viewPositionOnTouchDown = PointF()
-
-    private val velocityTracker = VelocityTracker.obtain()
-
-    private var touchSlop: Int = -1
-    private var movedEnough = false
-
-    private var performedLongClick = false
-
-    @Suppress("UNCHECKED_CAST")
-    override fun onTouch(v: View, ev: MotionEvent): Boolean {
-        addMovement(ev)
-
-        val dx = ev.rawX - touchDown.x
-        val dy = ev.rawY - touchDown.y
-
-        when (ev.action) {
-            MotionEvent.ACTION_DOWN -> {
-                if (!onDown(v, ev)) {
-                    return false
-                }
-
-                // Grab the touch slop, it might have changed if the config changed since the
-                // last gesture.
-                touchSlop = ViewConfiguration.get(v.context).scaledTouchSlop
-
-                touchDown.set(ev.rawX, ev.rawY)
-                viewPositionOnTouchDown.set(v.translationX, v.translationY)
-
-                performedLongClick = false
-                v.handler.postDelayed({
-                    if (v.isLongClickable) {
-                        performedLongClick = v.performLongClick()
-                    }
-                }, ViewConfiguration.getLongPressTimeout().toLong())
-            }
-
-            MotionEvent.ACTION_MOVE -> {
-                if (!movedEnough && hypot(dx, dy) > touchSlop && !performedLongClick) {
-                    movedEnough = true
-                    v.handler.removeCallbacksAndMessages(null)
-                }
-
-                if (movedEnough) {
-                    onMove(v, ev, viewPositionOnTouchDown.x, viewPositionOnTouchDown.y, dx, dy)
-                }
-            }
-
-            MotionEvent.ACTION_UP -> {
-                if (movedEnough) {
-                    velocityTracker.computeCurrentVelocity(1000 /* units */)
-                    onUp(v, ev, viewPositionOnTouchDown.x, viewPositionOnTouchDown.y, dx, dy,
-                            velocityTracker.xVelocity, velocityTracker.yVelocity)
-                } else if (!performedLongClick) {
-                    v.performClick()
-                } else {
-                    v.handler.removeCallbacksAndMessages(null)
-                }
-
-                velocityTracker.clear()
-                movedEnough = false
-            }
-
-            MotionEvent.ACTION_CANCEL -> {
-                v.handler.removeCallbacksAndMessages(null)
-                velocityTracker.clear()
-                movedEnough = false
-            }
-        }
-
-        return true
-    }
-
-    /**
-     * Adds a movement to the velocity tracker using raw screen coordinates.
-     */
-    private fun addMovement(event: MotionEvent) {
-        val deltaX = event.rawX - event.x
-        val deltaY = event.rawY - event.y
-        event.offsetLocation(deltaX, deltaY)
-        velocityTracker.addMovement(event)
-        event.offsetLocation(-deltaX, -deltaY)
-    }
-}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/PhysicsAnimationLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/PhysicsAnimationLayout.java
index beb1c5f..f3cc514 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/PhysicsAnimationLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/PhysicsAnimationLayout.java
@@ -327,6 +327,12 @@
         addViewInternal(child, index, params, false /* isReorder */);
     }
 
+    /** Removes the child view immediately. */
+    public void removeViewNoAnimation(View view) {
+        super.removeView(view);
+        view.setTag(R.id.physics_animator_tag, null);
+    }
+
     @Override
     public void removeView(View view) {
         if (mController != null) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
index 23f65f9..b3602b3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
@@ -21,12 +21,15 @@
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
 import android.content.Context;
-import android.graphics.PointF;
+import android.graphics.Point;
 import android.util.Log;
 import android.widget.FrameLayout;
 
+import androidx.annotation.Nullable;
+
 import com.android.wm.shell.animation.Interpolators;
 import com.android.wm.shell.animation.PhysicsAnimator;
+import com.android.wm.shell.bubbles.BubbleOverflow;
 import com.android.wm.shell.bubbles.BubblePositioner;
 import com.android.wm.shell.bubbles.BubbleViewProvider;
 import com.android.wm.shell.bubbles.animation.AnimatableScaleMatrix;
@@ -110,7 +113,8 @@
     /**
      * Animates the provided bubble's expanded view to the expanded state.
      */
-    public void animateExpansion(BubbleViewProvider expandedBubble) {
+    public void animateExpansion(BubbleViewProvider expandedBubble,
+            @Nullable Runnable afterAnimation) {
         mExpandedBubble = expandedBubble;
         if (mExpandedBubble == null) {
             return;
@@ -132,7 +136,7 @@
         bev.setVisibility(VISIBLE);
 
         // Set the pivot point for the scale, so the view animates out from the bubble bar.
-        PointF bubbleBarPosition = mPositioner.getBubbleBarPosition();
+        Point bubbleBarPosition = mPositioner.getBubbleBarPosition();
         mExpandedViewContainerMatrix.setScale(
                 1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT,
                 1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT,
@@ -159,6 +163,9 @@
                     bev.setAnimationMatrix(null);
                     updateExpandedView();
                     bev.setSurfaceZOrderedOnTop(false);
+                    if (afterAnimation != null) {
+                        afterAnimation.run();
+                    }
                 })
                 .start();
     }
@@ -215,9 +222,10 @@
         }
         BubbleBarExpandedView bbev = mExpandedBubble.getBubbleBarExpandedView();
 
+        boolean isOverflowExpanded = mExpandedBubble.getKey().equals(BubbleOverflow.KEY);
         final int padding = mPositioner.getBubbleBarExpandedViewPadding();
-        final int width = mPositioner.getExpandedViewWidthForBubbleBar();
-        final int height = mPositioner.getExpandedViewHeightForBubbleBar();
+        final int width = mPositioner.getExpandedViewWidthForBubbleBar(isOverflowExpanded);
+        final int height = mPositioner.getExpandedViewHeightForBubbleBar(isOverflowExpanded);
         FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) bbev.getLayoutParams();
         lp.width = width;
         lp.height = height;
@@ -227,7 +235,8 @@
         } else {
             bbev.setX(mPositioner.getAvailableRect().width() - width - padding);
         }
-        bbev.setY(mPositioner.getInsets().top + padding);
+        bbev.setY(mPositioner.getExpandedViewBottomForBubbleBar() - height);
         bbev.updateLocation();
+        bbev.maybeShowOverflow();
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
index b8f049b..6b6d6ba 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
@@ -16,12 +16,16 @@
 
 package com.android.wm.shell.bubbles.bar;
 
+import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.graphics.Color;
+import android.graphics.Insets;
 import android.graphics.Outline;
+import android.graphics.Rect;
 import android.util.AttributeSet;
+import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewOutlineProvider;
 import android.widget.FrameLayout;
@@ -30,9 +34,14 @@
 import com.android.wm.shell.R;
 import com.android.wm.shell.bubbles.Bubble;
 import com.android.wm.shell.bubbles.BubbleController;
+import com.android.wm.shell.bubbles.BubbleOverflowContainerView;
 import com.android.wm.shell.bubbles.BubbleTaskViewHelper;
+import com.android.wm.shell.bubbles.Bubbles;
 import com.android.wm.shell.taskview.TaskView;
 
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
 /**
  * Expanded view of a bubble when it's part of the bubble bar.
  *
@@ -44,12 +53,18 @@
     private static final int INVALID_TASK_ID = -1;
 
     private BubbleController mController;
+    private boolean mIsOverflow;
     private BubbleTaskViewHelper mBubbleTaskViewHelper;
+    private BubbleBarMenuViewController mMenuViewController;
+    private @Nullable Supplier<Rect> mLayerBoundsSupplier;
+    private @Nullable Consumer<String> mUnBubbleConversationCallback;
 
-    private HandleView mMenuView;
-    private TaskView mTaskView;
+    private BubbleBarHandleView mHandleView = new BubbleBarHandleView(getContext());
+    private @Nullable TaskView mTaskView;
+    private @Nullable BubbleOverflowContainerView mOverflowView;
 
-    private int mMenuHeight;
+    private int mCaptionHeight;
+
     private int mBackgroundColor;
     private float mCornerRadius = 0f;
 
@@ -83,11 +98,9 @@
         super.onFinishInflate();
         Context context = getContext();
         setElevation(getResources().getDimensionPixelSize(R.dimen.bubble_elevation));
-        mMenuHeight = context.getResources().getDimensionPixelSize(
-                R.dimen.bubblebar_expanded_view_menu_size);
-        mMenuView = new HandleView(context);
-        addView(mMenuView);
-
+        mCaptionHeight = context.getResources().getDimensionPixelSize(
+                R.dimen.bubble_bar_expanded_view_caption_height);
+        addView(mHandleView);
         applyThemeAttrs();
         setClipToOutline(true);
         setOutlineProvider(new ViewOutlineProvider() {
@@ -98,23 +111,78 @@
         });
     }
 
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        // Hide manage menu when view disappears
+        mMenuViewController.hideMenu(false /* animated */);
+    }
+
     /** Set the BubbleController on the view, must be called before doing anything else. */
-    public void initialize(BubbleController controller) {
+    public void initialize(BubbleController controller, boolean isOverflow) {
         mController = controller;
-        mBubbleTaskViewHelper = new BubbleTaskViewHelper(mContext, mController,
-                /* listener= */ this,
-                /* viewParent= */ this);
-        mTaskView = mBubbleTaskViewHelper.getTaskView();
-        addView(mTaskView);
-        mTaskView.setEnableSurfaceClipping(true);
-        mTaskView.setCornerRadius(mCornerRadius);
+        mIsOverflow = isOverflow;
+
+        if (mIsOverflow) {
+            mOverflowView = (BubbleOverflowContainerView) LayoutInflater.from(getContext()).inflate(
+                    R.layout.bubble_overflow_container, null /* root */);
+            mOverflowView.setBubbleController(mController);
+            addView(mOverflowView);
+        } else {
+
+            mBubbleTaskViewHelper = new BubbleTaskViewHelper(mContext, mController,
+                    /* listener= */ this,
+                    /* viewParent= */ this);
+            mTaskView = mBubbleTaskViewHelper.getTaskView();
+            addView(mTaskView);
+            mTaskView.setEnableSurfaceClipping(true);
+            mTaskView.setCornerRadius(mCornerRadius);
+
+            // Handle view needs to draw on top of task view.
+            bringChildToFront(mHandleView);
+        }
+        mMenuViewController = new BubbleBarMenuViewController(mContext, this);
+        mMenuViewController.setListener(new BubbleBarMenuViewController.Listener() {
+            @Override
+            public void onMenuVisibilityChanged(boolean visible) {
+                if (mTaskView == null || mLayerBoundsSupplier == null) return;
+                // Updates the obscured touchable region for the task surface.
+                mTaskView.setObscuredTouchRect(visible ? mLayerBoundsSupplier.get() : null);
+            }
+
+            @Override
+            public void onUnBubbleConversation(Bubble bubble) {
+                if (mUnBubbleConversationCallback != null) {
+                    mUnBubbleConversationCallback.accept(bubble.getKey());
+                }
+            }
+
+            @Override
+            public void onOpenAppSettings(Bubble bubble) {
+                mController.collapseStack();
+                mContext.startActivityAsUser(bubble.getSettingsIntent(mContext), bubble.getUser());
+            }
+
+            @Override
+            public void onDismissBubble(Bubble bubble) {
+                mController.dismissBubble(bubble, Bubbles.DISMISS_USER_REMOVED);
+            }
+        });
+        mHandleView.setOnClickListener(view -> {
+            mMenuViewController.showMenu(true /* animated */);
+        });
+    }
+
+    public BubbleBarHandleView getHandleView() {
+        return mHandleView;
     }
 
     // TODO (b/275087636): call this when theme/config changes
-    void applyThemeAttrs() {
+    /** Updates the view based on the current theme. */
+    public void applyThemeAttrs() {
         boolean supportsRoundedCorners = ScreenDecorationsUtils.supportsRoundedCornersOnWindows(
                 mContext.getResources());
-        final TypedArray ta = mContext.obtainStyledAttributes(new int[] {
+        final TypedArray ta = mContext.obtainStyledAttributes(new int[]{
                 android.R.attr.dialogCornerRadius,
                 android.R.attr.colorBackgroundFloating});
         mCornerRadius = supportsRoundedCorners ? ta.getDimensionPixelSize(0, 0) : 0;
@@ -123,14 +191,12 @@
 
         ta.recycle();
 
-        mMenuView.setCornerRadius(mCornerRadius);
-        mMenuHeight = getResources().getDimensionPixelSize(
-                R.dimen.bubblebar_expanded_view_menu_size);
+        mCaptionHeight = getResources().getDimensionPixelSize(
+                R.dimen.bubble_bar_expanded_view_caption_height);
 
         if (mTaskView != null) {
             mTaskView.setCornerRadius(mCornerRadius);
-            mTaskView.setElevation(150);
-            updateMenuColor();
+            updateHandleColor(true /* animated */);
         }
     }
 
@@ -138,35 +204,33 @@
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
         int height = MeasureSpec.getSize(heightMeasureSpec);
-
-        // Add corner radius here so that the menu extends behind the rounded corners of TaskView.
-        int menuViewHeight = Math.min((int) (mMenuHeight + mCornerRadius), height);
-        measureChild(mMenuView, widthMeasureSpec, MeasureSpec.makeMeasureSpec(menuViewHeight,
+        int menuViewHeight = Math.min(mCaptionHeight, height);
+        measureChild(mHandleView, widthMeasureSpec, MeasureSpec.makeMeasureSpec(menuViewHeight,
                 MeasureSpec.getMode(heightMeasureSpec)));
 
         if (mTaskView != null) {
-            int taskViewHeight = height - menuViewHeight;
-            measureChild(mTaskView, widthMeasureSpec, MeasureSpec.makeMeasureSpec(taskViewHeight,
+            measureChild(mTaskView, widthMeasureSpec, MeasureSpec.makeMeasureSpec(height,
                     MeasureSpec.getMode(heightMeasureSpec)));
         }
     }
 
     @Override
     protected void onLayout(boolean changed, int l, int t, int r, int b) {
-        // Drag handle above
-        final int dragHandleBottom = t + mMenuView.getMeasuredHeight();
-        mMenuView.layout(l, t, r, dragHandleBottom);
+        super.onLayout(changed, l, t, r, b);
+        final int captionBottom = t + mCaptionHeight;
         if (mTaskView != null) {
-            // Subtract radius so that the menu extends behind the rounded corners of TaskView.
-            mTaskView.layout(l, (int) (dragHandleBottom - mCornerRadius), r,
-                    dragHandleBottom + mTaskView.getMeasuredHeight());
+            mTaskView.layout(l, t, r,
+                    t + mTaskView.getMeasuredHeight());
+            mTaskView.setCaptionInsets(Insets.of(0, mCaptionHeight, 0, 0));
         }
+        // Handle draws on top of task view in the caption area.
+        mHandleView.layout(l, t, r, captionBottom);
     }
 
     @Override
     public void onTaskCreated() {
         setContentVisibility(true);
-        updateMenuColor();
+        updateHandleColor(false /* animated */);
     }
 
     @Override
@@ -187,11 +251,13 @@
             }
             mBubbleTaskViewHelper.cleanUpTaskView();
         }
+        mMenuViewController.hideMenu(false /* animated */);
     }
 
-    /** Updates the bubble shown in this task view. */
+    /** Updates the bubble shown in the expanded view. */
     public void update(Bubble bubble) {
         mBubbleTaskViewHelper.update(bubble);
+        mMenuViewController.updateMenu(bubble);
     }
 
     /** The task id of the activity shown in the task view, if it exists. */
@@ -199,12 +265,33 @@
         return mBubbleTaskViewHelper != null ? mBubbleTaskViewHelper.getTaskId() : INVALID_TASK_ID;
     }
 
+    /** Sets layer bounds supplier used for obscured touchable region of task view */
+    void setLayerBoundsSupplier(@Nullable Supplier<Rect> supplier) {
+        mLayerBoundsSupplier = supplier;
+    }
+
+    /** Sets the function to call to un-bubble the given conversation. */
+    public void setUnBubbleConversationCallback(
+            @Nullable Consumer<String> unBubbleConversationCallback) {
+        mUnBubbleConversationCallback = unBubbleConversationCallback;
+    }
+
     /**
      * Call when the location or size of the view has changed to update TaskView.
      */
     public void updateLocation() {
-        if (mTaskView == null) return;
-        mTaskView.onLocationChanged();
+        if (mTaskView != null) {
+            mTaskView.onLocationChanged();
+        }
+    }
+
+    /** Shows the expanded view for the overflow if it exists. */
+    void maybeShowOverflow() {
+        if (mOverflowView != null) {
+            // post this to the looper so that the view has a chance to be laid out before it can
+            // calculate row and column sizes correctly.
+            post(() -> mOverflowView.show());
+        }
     }
 
     /** Sets the alpha of the task view. */
@@ -218,17 +305,21 @@
         }
     }
 
-    /** Updates the menu bar to be the status bar color specified by the app. */
-    private void updateMenuColor() {
-        if (mTaskView == null) return;
-        ActivityManager.RunningTaskInfo info = mTaskView.getTaskInfo();
-        final int taskBgColor = info.taskDescription.getStatusBarColor();
-        final int color = Color.valueOf(taskBgColor == -1 ? Color.WHITE : taskBgColor).toArgb();
-        if (color != -1) {
-            mMenuView.setBackgroundColor(color);
-        } else {
-            mMenuView.setBackgroundColor(mBackgroundColor);
+    /**
+     * Updates the handle color based on the task view status bar or background color; if those
+     * are transparent it defaults to the background color pulled from system theme attributes.
+     */
+    private void updateHandleColor(boolean animated) {
+        if (mTaskView == null || mTaskView.getTaskInfo() == null) return;
+        int color = mBackgroundColor;
+        ActivityManager.TaskDescription taskDescription = mTaskView.getTaskInfo().taskDescription;
+        if (taskDescription.getStatusBarColor() != Color.TRANSPARENT) {
+            color = taskDescription.getStatusBarColor();
+        } else if (taskDescription.getBackgroundColor() != Color.TRANSPARENT) {
+            color = taskDescription.getBackgroundColor();
         }
+        final boolean isRegionDark = Color.luminance(color) <= 0.5;
+        mHandleView.updateHandleColor(isRegionDark, animated);
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java
new file mode 100644
index 0000000..2b7a070
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.bubbles.bar;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.graphics.Outline;
+import android.graphics.Path;
+import android.graphics.RectF;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewOutlineProvider;
+
+import androidx.annotation.ColorInt;
+import androidx.core.content.ContextCompat;
+
+import com.android.wm.shell.R;
+
+/**
+ * Handle view to show at the top of a bubble bar expanded view.
+ */
+public class BubbleBarHandleView extends View {
+    private static final long COLOR_CHANGE_DURATION = 120;
+
+    // The handle view is currently rendered as 3 evenly spaced dots.
+    private int mDotSize;
+    private int mDotSpacing;
+    // Path used to draw the dots
+    private final Path mPath = new Path();
+
+    private @ColorInt int mHandleLightColor;
+    private @ColorInt int mHandleDarkColor;
+    private @Nullable ObjectAnimator mColorChangeAnim;
+
+    public BubbleBarHandleView(Context context) {
+        this(context, null /* attrs */);
+    }
+
+    public BubbleBarHandleView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0 /* defStyleAttr */);
+    }
+
+    public BubbleBarHandleView(Context context, AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0 /* defStyleRes */);
+    }
+
+    public BubbleBarHandleView(Context context, AttributeSet attrs, int defStyleAttr,
+            int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+        mDotSize = getResources().getDimensionPixelSize(
+                R.dimen.bubble_bar_expanded_view_caption_dot_size);
+        mDotSpacing = getResources().getDimensionPixelSize(
+                R.dimen.bubble_bar_expanded_view_caption_dot_spacing);
+        mHandleLightColor = ContextCompat.getColor(getContext(),
+                R.color.bubble_bar_expanded_view_handle_light);
+        mHandleDarkColor = ContextCompat.getColor(getContext(),
+                R.color.bubble_bar_expanded_view_handle_dark);
+
+        setClipToOutline(true);
+        setOutlineProvider(new ViewOutlineProvider() {
+            @Override
+            public void getOutline(View view, Outline outline) {
+                final int handleCenterX = view.getWidth() / 2;
+                final int handleCenterY = view.getHeight() / 2;
+                final int handleTotalWidth = mDotSize * 3 + mDotSpacing * 2;
+                final int handleLeft = handleCenterX - handleTotalWidth / 2;
+                final int handleTop = handleCenterY - mDotSize / 2;
+                final int handleBottom = handleTop + mDotSize;
+                RectF dot1 = new RectF(
+                        handleLeft, handleTop,
+                        handleLeft + mDotSize, handleBottom);
+                RectF dot2 = new RectF(
+                        dot1.right + mDotSpacing, handleTop,
+                        dot1.right + mDotSpacing + mDotSize, handleBottom
+                );
+                RectF dot3 = new RectF(
+                        dot2.right + mDotSpacing, handleTop,
+                        dot2.right + mDotSpacing + mDotSize, handleBottom
+                );
+                mPath.reset();
+                mPath.addOval(dot1, Path.Direction.CW);
+                mPath.addOval(dot2, Path.Direction.CW);
+                mPath.addOval(dot3, Path.Direction.CW);
+                outline.setPath(mPath);
+            }
+        });
+    }
+
+    /**
+     * Updates the handle color.
+     *
+     * @param isRegionDark Whether the background behind the handle is dark, and thus the handle
+     *                     should be light (and vice versa).
+     * @param animated      Whether to animate the change, or apply it immediately.
+     */
+    public void updateHandleColor(boolean isRegionDark, boolean animated) {
+        int newColor = isRegionDark ? mHandleLightColor : mHandleDarkColor;
+        if (mColorChangeAnim != null) {
+            mColorChangeAnim.cancel();
+        }
+        if (animated) {
+            mColorChangeAnim = ObjectAnimator.ofArgb(this, "backgroundColor", newColor);
+            mColorChangeAnim.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    mColorChangeAnim = null;
+                }
+            });
+            mColorChangeAnim.setDuration(COLOR_CHANGE_DURATION);
+            mColorChangeAnim.start();
+        } else {
+            setBackgroundColor(newColor);
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
index b1a725b..8ead18b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
@@ -24,14 +24,18 @@
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.graphics.drawable.ColorDrawable;
+import android.view.TouchDelegate;
 import android.view.View;
 import android.view.ViewTreeObserver;
 import android.widget.FrameLayout;
 
 import com.android.wm.shell.bubbles.BubbleController;
+import com.android.wm.shell.bubbles.BubbleOverflow;
 import com.android.wm.shell.bubbles.BubblePositioner;
 import com.android.wm.shell.bubbles.BubbleViewProvider;
 
+import java.util.function.Consumer;
+
 /**
  * Similar to {@link com.android.wm.shell.bubbles.BubbleStackView}, this view is added to window
  * manager to display bubbles. However, it is only used when bubbles are being displayed in
@@ -53,6 +57,7 @@
     @Nullable
     private BubbleViewProvider mExpandedBubble;
     private BubbleBarExpandedView mExpandedView;
+    private @Nullable Consumer<String> mUnBubbleConversationCallback;
 
     // TODO(b/273310265) - currently the view is always on the right, need to update for RTL.
     /** Whether the expanded view is displaying on the left of the screen or not. */
@@ -64,6 +69,10 @@
     private final Region mTouchableRegion = new Region();
     private final Rect mTempRect = new Rect();
 
+    // Used to ensure touch target size for the menu shown on a bubble expanded view
+    private TouchDelegate mHandleTouchDelegate;
+    private final Rect mHandleTouchBounds = new Rect();
+
     public BubbleBarLayerView(Context context, BubbleController controller) {
         super(context);
         mBubbleController = controller;
@@ -143,15 +152,34 @@
         if (mExpandedView == null) {
             mExpandedBubble = b;
             mExpandedView = expandedView;
-            final int width = mPositioner.getExpandedViewWidthForBubbleBar();
-            final int height = mPositioner.getExpandedViewHeightForBubbleBar();
+            boolean isOverflowExpanded = b.getKey().equals(BubbleOverflow.KEY);
+            final int width = mPositioner.getExpandedViewWidthForBubbleBar(isOverflowExpanded);
+            final int height = mPositioner.getExpandedViewHeightForBubbleBar(isOverflowExpanded);
             mExpandedView.setVisibility(GONE);
+            mExpandedView.setUnBubbleConversationCallback(mUnBubbleConversationCallback);
+            mExpandedView.setLayerBoundsSupplier(() -> new Rect(0, 0, getWidth(), getHeight()));
+            mExpandedView.setUnBubbleConversationCallback(bubbleKey -> {
+                if (mUnBubbleConversationCallback != null) {
+                    mUnBubbleConversationCallback.accept(bubbleKey);
+                }
+            });
+            mExpandedView.setY(mPositioner.getExpandedViewBottomForBubbleBar() - height);
             addView(mExpandedView, new FrameLayout.LayoutParams(width, height));
         }
 
         mIsExpanded = true;
         mBubbleController.getSysuiProxy().onStackExpandChanged(true);
-        mAnimationHelper.animateExpansion(mExpandedBubble);
+        mAnimationHelper.animateExpansion(mExpandedBubble, () -> {
+            if (mExpandedView == null) return;
+            // Touch delegate for the menu
+            BubbleBarHandleView view = mExpandedView.getHandleView();
+            view.getBoundsOnScreen(mHandleTouchBounds);
+            mHandleTouchBounds.top -= mPositioner.getBubblePaddingTop();
+            mHandleTouchDelegate = new TouchDelegate(mHandleTouchBounds,
+                    mExpandedView.getHandleView());
+            setTouchDelegate(mHandleTouchDelegate);
+        });
+
         showScrim(true);
     }
 
@@ -162,15 +190,23 @@
         mAnimationHelper.animateCollapse(() -> removeView(viewToRemove));
         mBubbleController.getSysuiProxy().onStackExpandChanged(false);
         mExpandedView = null;
+        setTouchDelegate(null);
         showScrim(false);
     }
 
+    /** Sets the function to call to un-bubble the given conversation. */
+    public void setUnBubbleConversationCallback(
+            @Nullable Consumer<String> unBubbleConversationCallback) {
+        mUnBubbleConversationCallback = unBubbleConversationCallback;
+    }
+
     /** Updates the expanded view size and position. */
     private void updateExpandedView() {
         if (mExpandedView == null) return;
+        boolean isOverflowExpanded = mExpandedBubble.getKey().equals(BubbleOverflow.KEY);
         final int padding = mPositioner.getBubbleBarExpandedViewPadding();
-        final int width = mPositioner.getExpandedViewWidthForBubbleBar();
-        final int height = mPositioner.getExpandedViewHeightForBubbleBar();
+        final int width = mPositioner.getExpandedViewWidthForBubbleBar(isOverflowExpanded);
+        final int height = mPositioner.getExpandedViewHeightForBubbleBar(isOverflowExpanded);
         FrameLayout.LayoutParams lp = (LayoutParams) mExpandedView.getLayoutParams();
         lp.width = width;
         lp.height = height;
@@ -180,7 +216,7 @@
         } else {
             mExpandedView.setX(mPositioner.getAvailableRect().width() - width - padding);
         }
-        mExpandedView.setY(mPositioner.getInsets().top + padding);
+        mExpandedView.setY(mPositioner.getExpandedViewBottomForBubbleBar() - height);
         mExpandedView.updateLocation();
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuItemView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuItemView.java
new file mode 100644
index 0000000..00b9777
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuItemView.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.wm.shell.bubbles.bar;
+
+import android.annotation.ColorInt;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Color;
+import android.graphics.drawable.Icon;
+import android.util.AttributeSet;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.wm.shell.R;
+
+/**
+ * Bubble bar expanded view menu item view to display menu action details
+ */
+public class BubbleBarMenuItemView extends LinearLayout {
+    private ImageView mImageView;
+    private TextView mTextView;
+
+    public BubbleBarMenuItemView(Context context) {
+        this(context, null /* attrs */);
+    }
+
+    public BubbleBarMenuItemView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0 /* defStyleAttr */);
+    }
+
+    public BubbleBarMenuItemView(Context context, AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0 /* defStyleRes */);
+    }
+
+    public BubbleBarMenuItemView(Context context, AttributeSet attrs, int defStyleAttr,
+            int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mImageView = findViewById(R.id.bubble_bar_menu_item_icon);
+        mTextView = findViewById(R.id.bubble_bar_menu_item_title);
+    }
+
+    /**
+     * Update menu item with the details and tint color
+     */
+    void update(Icon icon, String title, @ColorInt int tint) {
+        if (tint == Color.TRANSPARENT) {
+            final TypedArray typedArray = getContext().obtainStyledAttributes(
+                    new int[]{android.R.attr.textColorPrimary});
+            mTextView.setTextColor(typedArray.getColor(0, Color.BLACK));
+        } else {
+            icon.setTint(tint);
+            mTextView.setTextColor(tint);
+        }
+
+        mImageView.setImageIcon(icon);
+        mTextView.setText(title);
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuView.java
new file mode 100644
index 0000000..211fe0d
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuView.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.bubbles.bar;
+
+import android.annotation.ColorInt;
+import android.content.Context;
+import android.graphics.Color;
+import android.graphics.drawable.Icon;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.wm.shell.R;
+import com.android.wm.shell.bubbles.Bubble;
+
+import java.util.ArrayList;
+
+/**
+ * Bubble bar expanded view menu
+ */
+public class BubbleBarMenuView extends LinearLayout {
+    private ViewGroup mBubbleSectionView;
+    private ViewGroup mActionsSectionView;
+    private ImageView mBubbleIconView;
+    private TextView mBubbleTitleView;
+
+    public BubbleBarMenuView(Context context) {
+        this(context, null /* attrs */);
+    }
+
+    public BubbleBarMenuView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0 /* defStyleAttr */);
+    }
+
+    public BubbleBarMenuView(Context context, AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0 /* defStyleRes */);
+    }
+
+    public BubbleBarMenuView(Context context, AttributeSet attrs, int defStyleAttr,
+            int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mBubbleSectionView = findViewById(R.id.bubble_bar_manage_menu_bubble_section);
+        mActionsSectionView = findViewById(R.id.bubble_bar_manage_menu_actions_section);
+        mBubbleIconView = findViewById(R.id.bubble_bar_manage_menu_bubble_icon);
+        mBubbleTitleView = findViewById(R.id.bubble_bar_manage_menu_bubble_title);
+    }
+
+    /** Update menu details with bubble info */
+    void updateInfo(Bubble bubble) {
+        if (bubble.getIcon() != null) {
+            mBubbleIconView.setImageIcon(bubble.getIcon());
+        } else {
+            mBubbleIconView.setImageBitmap(bubble.getBubbleIcon());
+        }
+        mBubbleTitleView.setText(bubble.getTitle());
+    }
+
+    /**
+     * Update menu action items views
+     * @param actions used to populate menu item views
+     */
+    void updateActions(ArrayList<MenuAction> actions) {
+        mActionsSectionView.removeAllViews();
+        LayoutInflater inflater = LayoutInflater.from(mContext);
+
+        for (MenuAction action : actions) {
+            BubbleBarMenuItemView itemView = (BubbleBarMenuItemView) inflater.inflate(
+                    R.layout.bubble_bar_menu_item, mActionsSectionView, false);
+            itemView.update(action.mIcon, action.mTitle, action.mTint);
+            itemView.setOnClickListener(action.mOnClick);
+            mActionsSectionView.addView(itemView);
+        }
+    }
+
+    /** Sets on close menu listener */
+    void setOnCloseListener(Runnable onClose) {
+        mBubbleSectionView.setOnClickListener(view -> {
+            onClose.run();
+        });
+    }
+
+    /**
+     * Overridden to proxy to section views alpha.
+     * @implNote
+     * If animate alpha on the parent (menu container) view, section view shadows get distorted.
+     * To prevent distortion and artifacts alpha changes applied directly on the section views.
+     */
+    @Override
+    public void setAlpha(float alpha) {
+        mBubbleSectionView.setAlpha(alpha);
+        mActionsSectionView.setAlpha(alpha);
+    }
+
+    /**
+     * Overridden to proxy section view alpha value.
+     * @implNote
+     * The assumption is that both section views have the same alpha value
+     */
+    @Override
+    public float getAlpha() {
+        return mBubbleSectionView.getAlpha();
+    }
+
+    /**
+     * Menu action details used to create menu items
+     */
+    static class MenuAction {
+        private Icon mIcon;
+        private @ColorInt int mTint;
+        private String mTitle;
+        private OnClickListener mOnClick;
+
+        MenuAction(Icon icon, String title, OnClickListener onClick) {
+            this(icon, title, Color.TRANSPARENT, onClick);
+        }
+
+        MenuAction(Icon icon, String title, @ColorInt int tint, OnClickListener onClick) {
+            this.mIcon = icon;
+            this.mTitle = title;
+            this.mTint = tint;
+            this.mOnClick = onClick;
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuViewController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuViewController.java
new file mode 100644
index 0000000..8be140c
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuViewController.java
@@ -0,0 +1,242 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.bubbles.bar;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.drawable.Icon;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.core.content.ContextCompat;
+import androidx.dynamicanimation.animation.DynamicAnimation;
+import androidx.dynamicanimation.animation.SpringForce;
+
+import com.android.wm.shell.R;
+import com.android.wm.shell.animation.PhysicsAnimator;
+import com.android.wm.shell.bubbles.Bubble;
+
+import java.util.ArrayList;
+
+/**
+ * Manages bubble bar expanded view menu presentation and animations
+ */
+class BubbleBarMenuViewController {
+    private static final float MENU_INITIAL_SCALE = 0.5f;
+    private final Context mContext;
+    private final ViewGroup mRootView;
+    private @Nullable Listener mListener;
+    private @Nullable Bubble mBubble;
+    private @Nullable BubbleBarMenuView mMenuView;
+    /** A transparent view used to intercept touches to collapse menu when presented */
+    private @Nullable View mScrimView;
+    private @Nullable PhysicsAnimator<BubbleBarMenuView> mMenuAnimator;
+    private PhysicsAnimator.SpringConfig mMenuSpringConfig;
+
+    BubbleBarMenuViewController(Context context, ViewGroup rootView) {
+        mContext = context;
+        mRootView = rootView;
+        mMenuSpringConfig = new PhysicsAnimator.SpringConfig(
+                SpringForce.STIFFNESS_MEDIUM, SpringForce.DAMPING_RATIO_LOW_BOUNCY);
+    }
+
+    /** Sets menu actions listener */
+    void setListener(@Nullable Listener listener) {
+        mListener = listener;
+    }
+
+    /** Update menu with bubble */
+    void updateMenu(@NonNull Bubble bubble) {
+        mBubble = bubble;
+    }
+
+    /**
+     * Show bubble bar expanded view menu
+     * @param animated if should animate transition
+     */
+    void showMenu(boolean animated) {
+        if (mMenuView == null || mScrimView == null) {
+            setupMenu();
+        }
+        cancelAnimations();
+        mMenuView.setVisibility(View.VISIBLE);
+        mScrimView.setVisibility(View.VISIBLE);
+        Runnable endActions = () -> {
+            mMenuView.getChildAt(0).requestAccessibilityFocus();
+            if (mListener != null) {
+                mListener.onMenuVisibilityChanged(true /* isShown */);
+            }
+        };
+        if (animated) {
+            animateTransition(true /* show */, endActions);
+        } else {
+            endActions.run();
+        }
+    }
+
+    /**
+     * Hide bubble bar expanded view menu
+     * @param animated if should animate transition
+     */
+    void hideMenu(boolean animated) {
+        if (mMenuView == null || mScrimView == null) return;
+        cancelAnimations();
+        Runnable endActions = () -> {
+            mMenuView.setVisibility(View.GONE);
+            mScrimView.setVisibility(View.GONE);
+            if (mListener != null) {
+                mListener.onMenuVisibilityChanged(false /* isShown */);
+            }
+        };
+        if (animated) {
+            animateTransition(false /* show */, endActions);
+        } else {
+            endActions.run();
+        }
+    }
+
+    /**
+     * Animate show/hide menu transition
+     * @param show if should show or hide the menu
+     * @param endActions will be called when animation ends
+     */
+    private void animateTransition(boolean show, Runnable endActions) {
+        if (mMenuView == null) return;
+        mMenuAnimator = PhysicsAnimator.getInstance(mMenuView);
+        mMenuAnimator.setDefaultSpringConfig(mMenuSpringConfig);
+        mMenuAnimator
+                .spring(DynamicAnimation.ALPHA, show ? 1f : 0f)
+                .spring(DynamicAnimation.SCALE_Y, show ? 1f : MENU_INITIAL_SCALE)
+                .withEndActions(() -> {
+                    mMenuAnimator = null;
+                    endActions.run();
+                })
+                .start();
+    }
+
+    /** Cancel running animations */
+    private void cancelAnimations() {
+        if (mMenuAnimator != null) {
+            mMenuAnimator.cancel();
+            mMenuAnimator = null;
+        }
+    }
+
+    /** Sets up and inflate menu views */
+    private void setupMenu() {
+        // Menu view setup
+        mMenuView = (BubbleBarMenuView) LayoutInflater.from(mContext).inflate(
+                R.layout.bubble_bar_menu_view, mRootView, false);
+        mMenuView.setAlpha(0f);
+        mMenuView.setPivotY(0f);
+        mMenuView.setScaleY(MENU_INITIAL_SCALE);
+        mMenuView.setOnCloseListener(() -> hideMenu(true  /* animated */));
+        if (mBubble != null) {
+            mMenuView.updateInfo(mBubble);
+            mMenuView.updateActions(createMenuActions(mBubble));
+        }
+        // Scrim view setup
+        mScrimView = new View(mContext);
+        mScrimView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
+        mScrimView.setOnClickListener(view -> hideMenu(true  /* animated */));
+        // Attach to root view
+        mRootView.addView(mScrimView);
+        mRootView.addView(mMenuView);
+    }
+
+    /**
+     * Creates menu actions to populate menu view
+     * @param bubble used to create actions depending on bubble type
+     */
+    private ArrayList<BubbleBarMenuView.MenuAction> createMenuActions(Bubble bubble) {
+        ArrayList<BubbleBarMenuView.MenuAction> menuActions = new ArrayList<>();
+        Resources resources = mContext.getResources();
+
+        if (bubble.isConversation()) {
+            // Don't bubble conversation action
+            menuActions.add(new BubbleBarMenuView.MenuAction(
+                    Icon.createWithResource(mContext, R.drawable.bubble_ic_stop_bubble),
+                    resources.getString(R.string.bubbles_dont_bubble_conversation),
+                    view -> {
+                        hideMenu(true /* animated */);
+                        if (mListener != null) {
+                            mListener.onUnBubbleConversation(bubble);
+                        }
+                    }
+            ));
+            // Open settings action
+            Icon appIcon = bubble.getRawAppBadge() != null ? Icon.createWithBitmap(
+                    bubble.getRawAppBadge()) : null;
+            menuActions.add(new BubbleBarMenuView.MenuAction(
+                    appIcon,
+                    resources.getString(R.string.bubbles_app_settings, bubble.getAppName()),
+                    view -> {
+                        hideMenu(true /* animated */);
+                        if (mListener != null) {
+                            mListener.onOpenAppSettings(bubble);
+                        }
+                    }
+            ));
+        }
+
+        // Dismiss bubble action
+        menuActions.add(new BubbleBarMenuView.MenuAction(
+                Icon.createWithResource(resources, R.drawable.ic_remove_no_shadow),
+                resources.getString(R.string.bubble_dismiss_text),
+                ContextCompat.getColor(mContext, R.color.bubble_bar_expanded_view_menu_close),
+                view -> {
+                    hideMenu(true /* animated */);
+                    if (mListener != null) {
+                        mListener.onDismissBubble(bubble);
+                    }
+                }
+        ));
+
+        return menuActions;
+    }
+
+    /**
+     * Bubble bar expanded view menu actions listener
+     */
+    interface Listener {
+        /**
+         * Called when manage menu is shown/hidden
+         * If animated will be called when animation ends
+         */
+        void onMenuVisibilityChanged(boolean visible);
+
+        /**
+         * Un-bubbles conversation and removes the bubble from the stack
+         * This conversation will not be bubbled with new messages
+         * @see com.android.wm.shell.bubbles.BubbleController
+         */
+        void onUnBubbleConversation(Bubble bubble);
+
+        /**
+         * Launches app notification bubble settings for the bubble with intent created in:
+         * {@code Bubble.getSettingsIntent}
+         */
+        void onOpenAppSettings(Bubble bubble);
+
+        /**
+         * Dismiss bubble and remove it from the bubble stack
+         */
+        void onDismissBubble(Bubble bubble);
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/HandleView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/HandleView.java
deleted file mode 100644
index 9ee8a9d..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/HandleView.java
+++ /dev/null
@@ -1,42 +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.wm.shell.bubbles.bar;
-
-import android.content.Context;
-import android.view.Gravity;
-import android.widget.LinearLayout;
-
-/**
- * Handle / menu view to show at the top of a bubble bar expanded view.
- */
-public class HandleView extends LinearLayout {
-
-    // TODO(b/273307221): implement the manage menu in this view.
-    public HandleView(Context context) {
-        super(context);
-        setOrientation(LinearLayout.HORIZONTAL);
-        setGravity(Gravity.CENTER);
-    }
-
-    /**
-     * The menu extends past the top of the TaskView because of the rounded corners. This means
-     * to center content in the menu we must subtract the radius (i.e. the amount of space covered
-     * by TaskView).
-     */
-    public void setCornerRadius(float radius) {
-        setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), (int) radius);
-    }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/properties/BubbleProperties.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/properties/BubbleProperties.kt
new file mode 100644
index 0000000..85aaa8e
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/properties/BubbleProperties.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.bubbles.properties
+
+/**
+ * An interface for exposing bubble properties via flags which can be controlled easily in tests.
+ */
+interface BubbleProperties {
+    /**
+     * Whether bubble bar is enabled.
+     *
+     * When this is `true`, depending on additional factors, such as screen size and taskbar state,
+     * bubbles will be displayed in the bubble bar instead of floating.
+     *
+     * When this is `false`, bubbles will be floating.
+     */
+    val isBubbleBarEnabled: Boolean
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/properties/ProdBubbleProperties.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/properties/ProdBubbleProperties.kt
new file mode 100644
index 0000000..9d8b9a6
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/properties/ProdBubbleProperties.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.wm.shell.bubbles.properties
+
+import android.os.SystemProperties
+
+/** Provides bubble properties in production. */
+object ProdBubbleProperties : BubbleProperties {
+
+    // TODO(b/256873975) Should use proper flag when available to shell/launcher
+    override val isBubbleBarEnabled =
+        SystemProperties.getBoolean("persist.wm.debug.bubble_bar", false)
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DismissCircleView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DismissCircleView.java
deleted file mode 100644
index e0c782d..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DismissCircleView.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * 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.
- */
-
-package com.android.wm.shell.common;
-
-import android.content.Context;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.view.Gravity;
-import android.widget.FrameLayout;
-import android.widget.ImageView;
-
-import com.android.wm.shell.R;
-
-/**
- * Circular view with a semitransparent, circular background with an 'X' inside it.
- *
- * This is used by both Bubbles and PIP as the dismiss target.
- */
-public class DismissCircleView extends FrameLayout {
-
-    private final ImageView mIconView = new ImageView(getContext());
-
-    public DismissCircleView(Context context) {
-        super(context);
-        final Resources res = getResources();
-
-        setBackground(res.getDrawable(R.drawable.dismiss_circle_background));
-
-        mIconView.setImageDrawable(res.getDrawable(R.drawable.pip_ic_close_white));
-        addView(mIconView);
-
-        setViewSizes();
-    }
-
-    @Override
-    protected void onConfigurationChanged(Configuration newConfig) {
-        super.onConfigurationChanged(newConfig);
-        final Resources res = getResources();
-        setBackground(res.getDrawable(R.drawable.dismiss_circle_background));
-        setViewSizes();
-    }
-
-    /** Retrieves the current dimensions for the icon and circle and applies them. */
-    private void setViewSizes() {
-        final Resources res = getResources();
-        final int iconSize = res.getDimensionPixelSize(R.dimen.dismiss_target_x_size);
-        mIconView.setLayoutParams(
-                new FrameLayout.LayoutParams(iconSize, iconSize, Gravity.CENTER));
-    }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/LaunchAdjacentController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/LaunchAdjacentController.kt
new file mode 100644
index 0000000..81592c3
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/LaunchAdjacentController.kt
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.common
+
+import android.window.WindowContainerToken
+import android.window.WindowContainerTransaction
+import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TASK_ORG
+import com.android.wm.shell.util.KtProtoLog
+
+/**
+ * Controller to manage behavior of activities launched with
+ * [android.content.Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT].
+ */
+class LaunchAdjacentController(private val syncQueue: SyncTransactionQueue) {
+
+    /** Allows to temporarily disable launch adjacent handling */
+    var launchAdjacentEnabled: Boolean = true
+        set(value) {
+            if (field != value) {
+                KtProtoLog.d(WM_SHELL_TASK_ORG, "set launch adjacent flag root enabled=%b", value)
+                field = value
+                container?.let { c ->
+                    if (value) {
+                        enableContainer(c)
+                    } else {
+                        disableContainer((c))
+                    }
+                }
+            }
+        }
+    private var container: WindowContainerToken? = null
+
+    /**
+     * Set [container] as the new launch adjacent flag root container.
+     *
+     * If launch adjacent handling is disabled through [setLaunchAdjacentEnabled], won't set the
+     * container until after it is enabled again.
+     *
+     * @see WindowContainerTransaction.setLaunchAdjacentFlagRoot
+     */
+    fun setLaunchAdjacentRoot(container: WindowContainerToken) {
+        KtProtoLog.d(WM_SHELL_TASK_ORG, "set new launch adjacent flag root container")
+        this.container = container
+        if (launchAdjacentEnabled) {
+            enableContainer(container)
+        }
+    }
+
+    /**
+     * Clear a container previously set through [setLaunchAdjacentRoot].
+     *
+     * Always clears the container, regardless of [launchAdjacentEnabled] value.
+     *
+     * @see WindowContainerTransaction.clearLaunchAdjacentFlagRoot
+     */
+    fun clearLaunchAdjacentRoot() {
+        KtProtoLog.d(WM_SHELL_TASK_ORG, "clear launch adjacent flag root container")
+        container?.let {
+            disableContainer(it)
+            container = null
+        }
+    }
+
+    private fun enableContainer(container: WindowContainerToken) {
+        KtProtoLog.v(WM_SHELL_TASK_ORG, "enable launch adjacent flag root container")
+        val wct = WindowContainerTransaction()
+        wct.setLaunchAdjacentFlagRoot(container)
+        syncQueue.queue(wct)
+    }
+
+    private fun disableContainer(container: WindowContainerToken) {
+        KtProtoLog.v(WM_SHELL_TASK_ORG, "disable launch adjacent flag root container")
+        val wct = WindowContainerTransaction()
+        wct.clearLaunchAdjacentFlagRoot(container)
+        syncQueue.queue(wct)
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/common/OWNERS
new file mode 100644
index 0000000..7af0389
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/OWNERS
@@ -0,0 +1 @@
+madym@google.com
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleInfo.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleInfo.java
index 21355a3..24608d6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleInfo.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleInfo.java
@@ -129,6 +129,11 @@
         return (mFlags & Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION) != 0;
     }
 
+    /** Sets the flags for this bubble. */
+    public void setFlags(int flags) {
+        mFlags = flags;
+    }
+
     @Override
     public boolean equals(Object o) {
         if (this == o) return true;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/DismissCircleView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/DismissCircleView.java
new file mode 100644
index 0000000..7c5bb21
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/DismissCircleView.java
@@ -0,0 +1,76 @@
+/*
+ * 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.
+ */
+
+package com.android.wm.shell.common.bubbles;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.view.Gravity;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+
+import androidx.annotation.DimenRes;
+import androidx.annotation.DrawableRes;
+import androidx.core.content.ContextCompat;
+
+/**
+ * Circular view with a semitransparent, circular background with an 'X' inside it.
+ *
+ * This is used by both Bubbles and PIP as the dismiss target.
+ */
+public class DismissCircleView extends FrameLayout {
+    @DrawableRes int mBackgroundResId;
+    @DimenRes int mIconSizeResId;
+
+    private final ImageView mIconView = new ImageView(getContext());
+
+    public DismissCircleView(Context context) {
+        super(context);
+        addView(mIconView);
+    }
+
+    @Override
+    protected void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        setBackground(ContextCompat.getDrawable(getContext(), mBackgroundResId));
+        setViewSizes();
+    }
+
+    /**
+     * Sets up view with the provided resource ids.
+     * Decouples resource dependency in order to be used externally (e.g. Launcher)
+     *
+     * @param backgroundResId drawable resource id of the circle background
+     * @param iconResId drawable resource id of the icon for the dismiss view
+     * @param iconSizeResId dimen resource id of the icon size
+     */
+    public void setup(@DrawableRes int backgroundResId, @DrawableRes int iconResId,
+            @DimenRes int iconSizeResId) {
+        mBackgroundResId = backgroundResId;
+        mIconSizeResId = iconSizeResId;
+
+        setBackground(ContextCompat.getDrawable(getContext(), backgroundResId));
+        mIconView.setImageDrawable(ContextCompat.getDrawable(getContext(), iconResId));
+        setViewSizes();
+    }
+
+    /** Retrieves the current dimensions for the icon and circle and applies them. */
+    private void setViewSizes() {
+        final int iconSize = getResources().getDimensionPixelSize(mIconSizeResId);
+        mIconView.setLayoutParams(
+                new FrameLayout.LayoutParams(iconSize, iconSize, Gravity.CENTER));
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/DismissView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/DismissView.kt
new file mode 100644
index 0000000..d275a0b
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/DismissView.kt
@@ -0,0 +1,222 @@
+/*
+ * 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.
+ */
+
+package com.android.wm.shell.common.bubbles
+
+import android.animation.ObjectAnimator
+import android.content.Context
+import android.graphics.Color
+import android.graphics.drawable.GradientDrawable
+import android.util.IntProperty
+import android.util.Log
+import android.view.Gravity
+import android.view.View
+import android.view.ViewGroup
+import android.view.WindowInsets
+import android.view.WindowManager
+import android.widget.FrameLayout
+import androidx.annotation.ColorRes
+import androidx.annotation.DimenRes
+import androidx.annotation.DrawableRes
+import androidx.core.content.ContextCompat
+import androidx.dynamicanimation.animation.DynamicAnimation
+import androidx.dynamicanimation.animation.SpringForce.DAMPING_RATIO_LOW_BOUNCY
+import androidx.dynamicanimation.animation.SpringForce.STIFFNESS_LOW
+import com.android.wm.shell.animation.PhysicsAnimator
+
+/**
+ * View that handles interactions between DismissCircleView and BubbleStackView.
+ *
+ * @note [setup] method should be called after initialisation
+ */
+class DismissView(context: Context) : FrameLayout(context) {
+    /**
+     * The configuration is used to provide module specific resource ids
+     *
+     * @see [setup] method
+     */
+    data class Config(
+            /** dimen resource id of the dismiss target circle view size */
+            @DimenRes val targetSizeResId: Int,
+            /** dimen resource id of the icon size in the dismiss target */
+            @DimenRes val iconSizeResId: Int,
+            /** dimen resource id of the bottom margin for the dismiss target */
+            @DimenRes var bottomMarginResId: Int,
+            /** dimen resource id of the height for dismiss area gradient */
+            @DimenRes val floatingGradientHeightResId: Int,
+            /** color resource id of the dismiss area gradient color */
+            @ColorRes val floatingGradientColorResId: Int,
+            /** drawable resource id of the dismiss target background */
+            @DrawableRes val backgroundResId: Int,
+            /** drawable resource id of the icon for the dismiss target */
+            @DrawableRes val iconResId: Int
+    )
+
+    companion object {
+        private const val SHOULD_SETUP =
+                "The view isn't ready. Should be called after `setup`"
+        private val TAG = DismissView::class.simpleName
+    }
+
+    var circle = DismissCircleView(context)
+    var isShowing = false
+    var config: Config? = null
+
+    private val animator = PhysicsAnimator.getInstance(circle)
+    private val spring = PhysicsAnimator.SpringConfig(STIFFNESS_LOW, DAMPING_RATIO_LOW_BOUNCY)
+    private val DISMISS_SCRIM_FADE_MS = 200L
+    private var wm: WindowManager =
+            context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
+    private var gradientDrawable: GradientDrawable? = null
+
+    private val GRADIENT_ALPHA: IntProperty<GradientDrawable> =
+            object : IntProperty<GradientDrawable>("alpha") {
+        override fun setValue(d: GradientDrawable, percent: Int) {
+            d.alpha = percent
+        }
+        override fun get(d: GradientDrawable): Int {
+            return d.alpha
+        }
+    }
+
+    init {
+        setClipToPadding(false)
+        setClipChildren(false)
+        setVisibility(View.INVISIBLE)
+        addView(circle)
+    }
+
+    /**
+     * Sets up view with the provided resource ids.
+     *
+     * Decouples resource dependency in order to be used externally (e.g. Launcher). Usually called
+     * with default params in module specific extension:
+     * @see [DismissView.setup] in DismissViewExt.kt
+     */
+    fun setup(config: Config) {
+        this.config = config
+
+        // Setup layout
+        layoutParams = LayoutParams(
+                ViewGroup.LayoutParams.MATCH_PARENT,
+                resources.getDimensionPixelSize(config.floatingGradientHeightResId),
+                Gravity.BOTTOM)
+        updatePadding()
+
+        // Setup gradient
+        gradientDrawable = createGradient(color = config.floatingGradientColorResId)
+        setBackgroundDrawable(gradientDrawable)
+
+        // Setup DismissCircleView
+        circle.setup(config.backgroundResId, config.iconResId, config.iconSizeResId)
+        val targetSize: Int = resources.getDimensionPixelSize(config.targetSizeResId)
+        circle.layoutParams = LayoutParams(targetSize, targetSize,
+                Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL)
+        // Initial position with circle offscreen so it's animated up
+        circle.translationY = resources.getDimensionPixelSize(config.floatingGradientHeightResId)
+                .toFloat()
+    }
+
+    /**
+     * Animates this view in.
+     */
+    fun show() {
+        if (isShowing) return
+        val gradientDrawable = checkExists(gradientDrawable) ?: return
+        isShowing = true
+        setVisibility(View.VISIBLE)
+        val alphaAnim = ObjectAnimator.ofInt(gradientDrawable, GRADIENT_ALPHA,
+                gradientDrawable.alpha, 255)
+        alphaAnim.setDuration(DISMISS_SCRIM_FADE_MS)
+        alphaAnim.start()
+
+        animator.cancel()
+        animator
+            .spring(DynamicAnimation.TRANSLATION_Y, 0f, spring)
+            .start()
+    }
+
+    /**
+     * Animates this view out, as well as the circle that encircles the bubbles, if they
+     * were dragged into the target and encircled.
+     */
+    fun hide() {
+        if (!isShowing) return
+        val gradientDrawable = checkExists(gradientDrawable) ?: return
+        isShowing = false
+        val alphaAnim = ObjectAnimator.ofInt(gradientDrawable, GRADIENT_ALPHA,
+                gradientDrawable.alpha, 0)
+        alphaAnim.setDuration(DISMISS_SCRIM_FADE_MS)
+        alphaAnim.start()
+        animator
+            .spring(DynamicAnimation.TRANSLATION_Y, height.toFloat(),
+                spring)
+            .withEndActions({ setVisibility(View.INVISIBLE) })
+            .start()
+    }
+
+    /**
+     * Cancels the animator for the dismiss target.
+     */
+    fun cancelAnimators() {
+        animator.cancel()
+    }
+
+    fun updateResources() {
+        val config = checkExists(config) ?: return
+        updatePadding()
+        layoutParams.height = resources.getDimensionPixelSize(config.floatingGradientHeightResId)
+        val targetSize = resources.getDimensionPixelSize(config.targetSizeResId)
+        circle.layoutParams.width = targetSize
+        circle.layoutParams.height = targetSize
+        circle.requestLayout()
+    }
+
+    private fun createGradient(@ColorRes color: Int): GradientDrawable {
+        val gradientColor = ContextCompat.getColor(context, color)
+        val alpha = 0.7f * 255
+        val gradientColorWithAlpha = Color.argb(alpha.toInt(),
+                Color.red(gradientColor),
+                Color.green(gradientColor),
+                Color.blue(gradientColor))
+        val gd = GradientDrawable(
+                GradientDrawable.Orientation.BOTTOM_TOP,
+                intArrayOf(gradientColorWithAlpha, Color.TRANSPARENT))
+        gd.setDither(true)
+        gd.setAlpha(0)
+        return gd
+    }
+
+    private fun updatePadding() {
+        val config = checkExists(config) ?: return
+        val insets: WindowInsets = wm.getCurrentWindowMetrics().getWindowInsets()
+        val navInset = insets.getInsetsIgnoringVisibility(
+                WindowInsets.Type.navigationBars())
+        setPadding(0, 0, 0, navInset.bottom +
+                resources.getDimensionPixelSize(config.bottomMarginResId))
+    }
+
+    /**
+     * Checks if the value is set up and exists, if not logs an exception.
+     * Used for convenient logging in case `setup` wasn't called before
+     *
+     * @return value provided as argument
+     */
+    private fun <T>checkExists(value: T?): T? {
+        if (value == null) Log.e(TAG, SHOULD_SETUP)
+        return value
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/RelativeTouchListener.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/RelativeTouchListener.kt
new file mode 100644
index 0000000..cc37bd3a4
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/RelativeTouchListener.kt
@@ -0,0 +1,168 @@
+/*
+ * 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.
+ */
+
+package com.android.wm.shell.common.bubbles
+
+import android.graphics.PointF
+import android.view.MotionEvent
+import android.view.VelocityTracker
+import android.view.View
+import android.view.ViewConfiguration
+import kotlin.math.hypot
+
+/**
+ * Listener which receives [onDown], [onMove], and [onUp] events, with relevant information about
+ * the coordinates of the touch and the view relative to the initial ACTION_DOWN event and the
+ * view's initial position.
+ */
+abstract class RelativeTouchListener : View.OnTouchListener {
+
+    /**
+     * Called when an ACTION_DOWN event is received for the given view.
+     *
+     * @return False if the object is not interested in MotionEvents at this time, or true if we
+     * should consume this event and subsequent events, and begin calling [onMove].
+     */
+    abstract fun onDown(v: View, ev: MotionEvent): Boolean
+
+    /**
+     * Called when an ACTION_MOVE event is received for the given view. This signals that the view
+     * is being dragged.
+     *
+     * @param viewInitialX The view's translationX value when this touch gesture started.
+     * @param viewInitialY The view's translationY value when this touch gesture started.
+     * @param dx Horizontal distance covered since the initial ACTION_DOWN event, in pixels.
+     * @param dy Vertical distance covered since the initial ACTION_DOWN event, in pixels.
+     */
+    abstract fun onMove(
+        v: View,
+        ev: MotionEvent,
+        viewInitialX: Float,
+        viewInitialY: Float,
+        dx: Float,
+        dy: Float
+    )
+
+    /**
+     * Called when an ACTION_UP event is received for the given view. This signals that a drag or
+     * fling gesture has completed.
+     *
+     * @param viewInitialX The view's translationX value when this touch gesture started.
+     * @param viewInitialY The view's translationY value when this touch gesture started.
+     * @param dx Horizontal distance covered, in pixels.
+     * @param dy Vertical distance covered, in pixels.
+     * @param velX The final horizontal velocity of the gesture, in pixels/second.
+     * @param velY The final vertical velocity of the gesture, in pixels/second.
+     */
+    abstract fun onUp(
+        v: View,
+        ev: MotionEvent,
+        viewInitialX: Float,
+        viewInitialY: Float,
+        dx: Float,
+        dy: Float,
+        velX: Float,
+        velY: Float
+    )
+
+    /** The raw coordinates of the last ACTION_DOWN event. */
+    private val touchDown = PointF()
+
+    /** The coordinates of the view, at the time of the last ACTION_DOWN event. */
+    private val viewPositionOnTouchDown = PointF()
+
+    private val velocityTracker = VelocityTracker.obtain()
+
+    private var touchSlop: Int = -1
+    private var movedEnough = false
+
+    private var performedLongClick = false
+
+    @Suppress("UNCHECKED_CAST")
+    override fun onTouch(v: View, ev: MotionEvent): Boolean {
+        addMovement(ev)
+
+        val dx = ev.rawX - touchDown.x
+        val dy = ev.rawY - touchDown.y
+
+        when (ev.action) {
+            MotionEvent.ACTION_DOWN -> {
+                if (!onDown(v, ev)) {
+                    return false
+                }
+
+                // Grab the touch slop, it might have changed if the config changed since the
+                // last gesture.
+                touchSlop = ViewConfiguration.get(v.context).scaledTouchSlop
+
+                touchDown.set(ev.rawX, ev.rawY)
+                viewPositionOnTouchDown.set(v.translationX, v.translationY)
+
+                performedLongClick = false
+                v.handler.postDelayed({
+                    if (v.isLongClickable) {
+                        performedLongClick = v.performLongClick()
+                    }
+                }, ViewConfiguration.getLongPressTimeout().toLong())
+            }
+
+            MotionEvent.ACTION_MOVE -> {
+                if (!movedEnough && hypot(dx, dy) > touchSlop && !performedLongClick) {
+                    movedEnough = true
+                    v.handler.removeCallbacksAndMessages(null)
+                }
+
+                if (movedEnough) {
+                    onMove(v, ev, viewPositionOnTouchDown.x, viewPositionOnTouchDown.y, dx, dy)
+                }
+            }
+
+            MotionEvent.ACTION_UP -> {
+                if (movedEnough) {
+                    velocityTracker.computeCurrentVelocity(1000 /* units */)
+                    onUp(v, ev, viewPositionOnTouchDown.x, viewPositionOnTouchDown.y, dx, dy,
+                            velocityTracker.xVelocity, velocityTracker.yVelocity)
+                } else if (!performedLongClick) {
+                    v.performClick()
+                } else {
+                    v.handler.removeCallbacksAndMessages(null)
+                }
+
+                velocityTracker.clear()
+                movedEnough = false
+            }
+
+            MotionEvent.ACTION_CANCEL -> {
+                v.handler.removeCallbacksAndMessages(null)
+                velocityTracker.clear()
+                movedEnough = false
+            }
+        }
+
+        return true
+    }
+
+    /**
+     * Adds a movement to the velocity tracker using raw screen coordinates.
+     */
+    private fun addMovement(event: MotionEvent) {
+        val deltaX = event.rawX - event.x
+        val deltaY = event.rawY - event.y
+        event.offsetLocation(deltaX, deltaY)
+        velocityTracker.addMovement(event)
+        event.offsetLocation(-deltaX, -deltaY)
+    }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerHandleView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerHandleView.java
index c76937d..ec26800 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerHandleView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerHandleView.java
@@ -76,6 +76,9 @@
     private int mCurrentHeight;
     private AnimatorSet mAnimator;
     private boolean mTouching;
+    private boolean mHovering;
+    private final int mHoveringWidth;
+    private final int mHoveringHeight;
 
     public DividerHandleView(Context context, @Nullable AttributeSet attrs) {
         super(context, attrs);
@@ -87,6 +90,8 @@
         mCurrentHeight = mHeight;
         mTouchingWidth = mWidth > mHeight ? mWidth / 2 : mWidth;
         mTouchingHeight = mHeight > mWidth ? mHeight / 2 : mHeight;
+        mHoveringWidth = mWidth > mHeight ? ((int) (mWidth * 1.5f)) : mWidth;
+        mHoveringHeight = mHeight > mWidth ? ((int) (mHeight * 1.5f)) : mHeight;
     }
 
     /** Sets touching state for this handle view. */
@@ -94,24 +99,32 @@
         if (touching == mTouching) {
             return;
         }
+        setInputState(touching, animate, mTouchingWidth, mTouchingHeight);
+        mTouching = touching;
+    }
+
+    /** Sets hovering state for this handle view. */
+    public void setHovering(boolean hovering, boolean animate) {
+        if (hovering == mHovering) {
+            return;
+        }
+        setInputState(hovering, animate, mHoveringWidth, mHoveringHeight);
+        mHovering = hovering;
+    }
+
+    private void setInputState(boolean stateOn, boolean animate, int stateWidth, int stateHeight) {
         if (mAnimator != null) {
             mAnimator.cancel();
             mAnimator = null;
         }
         if (!animate) {
-            if (touching) {
-                mCurrentWidth = mTouchingWidth;
-                mCurrentHeight = mTouchingHeight;
-            } else {
-                mCurrentWidth = mWidth;
-                mCurrentHeight = mHeight;
-            }
+            mCurrentWidth = stateOn ? stateWidth : mWidth;
+            mCurrentHeight = stateOn ? stateHeight : mHeight;
             invalidate();
         } else {
-            animateToTarget(touching ? mTouchingWidth : mWidth,
-                    touching ? mTouchingHeight : mHeight, touching);
+            animateToTarget(stateOn ? stateWidth : mWidth,
+                    stateOn ? stateHeight : mHeight, stateOn);
         }
-        mTouching = touching;
     }
 
     private void animateToTarget(int targetWidth, int targetHeight, boolean touching) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
index 69f0bad..2dbc444 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
@@ -17,14 +17,19 @@
 package com.android.wm.shell.common.split;
 
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+import static android.view.PointerIcon.TYPE_HORIZONTAL_DOUBLE_ARROW;
+import static android.view.PointerIcon.TYPE_VERTICAL_DOUBLE_ARROW;
 import static android.view.WindowManager.LayoutParams.FLAG_SLIPPERY;
 
+import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.CURSOR_HOVER_STATES_ENABLED;
+
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ObjectAnimator;
 import android.content.Context;
 import android.graphics.Rect;
 import android.os.Bundle;
+import android.provider.DeviceConfig;
 import android.util.AttributeSet;
 import android.util.Property;
 import android.view.GestureDetector;
@@ -32,6 +37,7 @@
 import android.view.InsetsSource;
 import android.view.InsetsState;
 import android.view.MotionEvent;
+import android.view.PointerIcon;
 import android.view.SurfaceControlViewHost;
 import android.view.VelocityTracker;
 import android.view.View;
@@ -46,6 +52,7 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.policy.DividerSnapAlgorithm;
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.wm.shell.R;
@@ -222,7 +229,7 @@
             for (int i = insetsState.sourceSize() - 1; i >= 0; i--) {
                 final InsetsSource source = insetsState.sourceAt(i);
                 if (source.getType() == WindowInsets.Type.navigationBars()
-                        && source.insetsRoundedCornerFrame()) {
+                        && source.hasFlags(InsetsSource.FLAG_INSETS_ROUNDED_CORNER)) {
                     mTempRect.inset(source.calculateVisibleInsets(mTempRect));
                 }
             }
@@ -270,6 +277,12 @@
     }
 
     @Override
+    public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) {
+        return PointerIcon.getSystemIcon(getContext(),
+                isLandscape() ? TYPE_HORIZONTAL_DOUBLE_ARROW : TYPE_VERTICAL_DOUBLE_ARROW);
+    }
+
+    @Override
     public boolean onTouch(View v, MotionEvent event) {
         if (mSplitLayout == null || !mInteractive) {
             return false;
@@ -371,6 +384,43 @@
         mViewHost.relayout(lp);
     }
 
+    @Override
+    public boolean onHoverEvent(MotionEvent event) {
+        if (!DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI, CURSOR_HOVER_STATES_ENABLED,
+                /* defaultValue = */ false)) {
+            return false;
+        }
+
+        if (event.getAction() == MotionEvent.ACTION_HOVER_ENTER) {
+            setHovering();
+            return true;
+        } else if (event.getAction() == MotionEvent.ACTION_HOVER_EXIT) {
+            releaseHovering();
+            return true;
+        }
+        return false;
+    }
+
+    @VisibleForTesting
+    void setHovering() {
+        mHandle.setHovering(true, true);
+        mHandle.animate()
+                .setInterpolator(Interpolators.TOUCH_RESPONSE)
+                .setDuration(TOUCH_ANIMATION_DURATION)
+                .translationZ(mTouchElevation)
+                .start();
+    }
+
+    @VisibleForTesting
+    void releaseHovering() {
+        mHandle.setHovering(false, true);
+        mHandle.animate()
+                .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
+                .setDuration(TOUCH_RELEASE_ANIMATION_DURATION)
+                .translationZ(0)
+                .start();
+    }
+
     /**
      * Set divider should interactive to user or not.
      *
@@ -384,7 +434,7 @@
                 "Set divider bar %s from %s", interactive ? "interactive" : "non-interactive",
                 from);
         mInteractive = interactive;
-        if (!mInteractive && mMoving) {
+        if (!mInteractive && hideHandle && mMoving) {
             final int position = mSplitLayout.getDividePosition();
             mSplitLayout.flingDividePosition(
                     mLastDraggingPosition,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
index a9ccdf6..2b10377 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
@@ -323,7 +323,11 @@
             }
         }
         if (mShown) {
-            fadeOutDecor(()-> animFinishedCallback.accept(true));
+            fadeOutDecor(()-> {
+                if (mRunningAnimationCount == 0 && animFinishedCallback != null) {
+                    animFinishedCallback.accept(true);
+                }
+            });
         } else {
             // Decor surface is hidden so release it directly.
             releaseDecor(t);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index f70d3ae..e8fa638 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -593,9 +593,6 @@
     void flingDividePosition(int from, int to, int duration,
             @Nullable Runnable flingFinishedCallback) {
         if (from == to) {
-            // No animation run, still callback to stop resizing.
-            mSplitLayoutHandler.onLayoutSizeChanged(this);
-
             if (flingFinishedCallback != null) {
                 flingFinishedCallback.run();
             }
@@ -773,15 +770,13 @@
             ActivityManager.RunningTaskInfo task1, ActivityManager.RunningTaskInfo task2) {
         boolean boundsChanged = false;
         if (!mBounds1.equals(mWinBounds1) || !task1.token.equals(mWinToken1)) {
-            wct.setBounds(task1.token, mBounds1);
-            wct.setSmallestScreenWidthDp(task1.token, getSmallestWidthDp(mBounds1));
+            setTaskBounds(wct, task1, mBounds1);
             mWinBounds1.set(mBounds1);
             mWinToken1 = task1.token;
             boundsChanged = true;
         }
         if (!mBounds2.equals(mWinBounds2) || !task2.token.equals(mWinToken2)) {
-            wct.setBounds(task2.token, mBounds2);
-            wct.setSmallestScreenWidthDp(task2.token, getSmallestWidthDp(mBounds2));
+            setTaskBounds(wct, task2, mBounds2);
             mWinBounds2.set(mBounds2);
             mWinToken2 = task2.token;
             boundsChanged = true;
@@ -789,6 +784,13 @@
         return boundsChanged;
     }
 
+    /** Set bounds to the {@link WindowContainerTransaction} for single task. */
+    public void setTaskBounds(WindowContainerTransaction wct,
+            ActivityManager.RunningTaskInfo task, Rect bounds) {
+        wct.setBounds(task.token, bounds);
+        wct.setSmallestScreenWidthDp(task.token, getSmallestWidthDp(bounds));
+    }
+
     private int getSmallestWidthDp(Rect bounds) {
         mTempRect.set(bounds);
         mTempRect.inset(getDisplayStableInsets(mContext));
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java
index 0289da9..d7ea1c0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java
@@ -89,4 +89,9 @@
             int userId1, int userId2) {
         return (packageName1 != null && packageName1.equals(packageName2)) && (userId1 == userId2);
     }
+
+    /** Generates a common log message for split screen failures */
+    public static String splitFailureMessage(String caller, String reason) {
+        return "(" + caller + ") Splitscreen aborted: " + reason;
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java
index 12d51f5..47d58af 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java
@@ -25,6 +25,7 @@
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.DisplayImeController;
 import com.android.wm.shell.common.DisplayInsetsController;
+import com.android.wm.shell.common.LaunchAdjacentController;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.SystemWindows;
@@ -86,13 +87,14 @@
             TransactionPool transactionPool,
             IconProvider iconProvider,
             Optional<RecentTasksController> recentTasks,
+            LaunchAdjacentController launchAdjacentController,
             @ShellMainThread ShellExecutor mainExecutor,
             Handler mainHandler,
             SystemWindows systemWindows) {
         return new TvSplitScreenController(context, shellInit, shellCommandHandler, shellController,
                 shellTaskOrganizer, syncQueue, rootTDAOrganizer, displayController,
                 displayImeController, displayInsetsController, dragAndDropController, transitions,
-                transactionPool, iconProvider, recentTasks, mainExecutor, mainHandler,
-                systemWindows);
+                transactionPool, iconProvider, recentTasks, launchAdjacentController, mainExecutor,
+                mainHandler, systemWindows);
     }
 }
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 c491fed..34a6e0a 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
@@ -46,6 +46,7 @@
 import com.android.wm.shell.common.DisplayLayout;
 import com.android.wm.shell.common.DockStateReader;
 import com.android.wm.shell.common.FloatingContentCoordinator;
+import com.android.wm.shell.common.LaunchAdjacentController;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.SystemWindows;
@@ -275,6 +276,13 @@
         return new WindowManagerShellWrapper(mainExecutor);
     }
 
+    @WMSingleton
+    @Provides
+    static LaunchAdjacentController provideLaunchAdjacentController(
+            SyncTransactionQueue syncQueue) {
+        return new LaunchAdjacentController(syncQueue);
+    }
+
     //
     // Back animation
     //
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index cff3172..20c3bd2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -36,17 +36,21 @@
 import com.android.wm.shell.bubbles.BubbleDataRepository;
 import com.android.wm.shell.bubbles.BubbleLogger;
 import com.android.wm.shell.bubbles.BubblePositioner;
+import com.android.wm.shell.bubbles.properties.ProdBubbleProperties;
+import com.android.wm.shell.bubbles.storage.BubblePersistentRepository;
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.DisplayImeController;
 import com.android.wm.shell.common.DisplayInsetsController;
 import com.android.wm.shell.common.DisplayLayout;
 import com.android.wm.shell.common.FloatingContentCoordinator;
+import com.android.wm.shell.common.LaunchAdjacentController;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.SystemWindows;
 import com.android.wm.shell.common.TabletopModeController;
 import com.android.wm.shell.common.TaskStackListenerImpl;
 import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.common.annotations.ShellAnimationThread;
 import com.android.wm.shell.common.annotations.ShellBackgroundThread;
 import com.android.wm.shell.common.annotations.ShellMainThread;
 import com.android.wm.shell.desktopmode.DesktopModeController;
@@ -55,6 +59,7 @@
 import com.android.wm.shell.desktopmode.DesktopTasksController;
 import com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler;
 import com.android.wm.shell.desktopmode.ExitDesktopTaskTransitionHandler;
+import com.android.wm.shell.desktopmode.ToggleResizeDesktopTaskTransitionHandler;
 import com.android.wm.shell.draganddrop.DragAndDropController;
 import com.android.wm.shell.freeform.FreeformComponents;
 import com.android.wm.shell.freeform.FreeformTaskListener;
@@ -180,11 +185,12 @@
             IWindowManager wmService) {
         return new BubbleController(context, shellInit, shellCommandHandler, shellController, data,
                 null /* synchronizer */, floatingContentCoordinator,
-                new BubbleDataRepository(context, launcherApps, mainExecutor),
+                new BubbleDataRepository(launcherApps, mainExecutor,
+                        new BubblePersistentRepository(context)),
                 statusBarService, windowManager, windowManagerShellWrapper, userManager,
                 launcherApps, logger, taskStackListener, organizer, positioner, displayController,
                 oneHandedOptional, dragAndDropController, mainExecutor, mainHandler, bgExecutor,
-                taskViewTransitions, syncQueue, wmService);
+                taskViewTransitions, syncQueue, wmService, ProdBubbleProperties.INSTANCE);
     }
 
     //
@@ -202,8 +208,7 @@
             SyncTransactionQueue syncQueue,
             Transitions transitions,
             Optional<DesktopModeController> desktopModeController,
-            Optional<DesktopTasksController> desktopTasksController,
-            Optional<SplitScreenController> splitScreenController) {
+            Optional<DesktopTasksController> desktopTasksController) {
         if (DesktopModeStatus.isAnyEnabled()) {
             return new DesktopModeWindowDecorViewModel(
                     context,
@@ -214,16 +219,15 @@
                     syncQueue,
                     transitions,
                     desktopModeController,
-                    desktopTasksController,
-                    splitScreenController);
+                    desktopTasksController);
         }
         return new CaptionWindowDecorViewModel(
-                    context,
-                    mainHandler,
-                    mainChoreographer,
-                    taskOrganizer,
-                    displayController,
-                    syncQueue);
+                context,
+                mainHandler,
+                mainChoreographer,
+                taskOrganizer,
+                displayController,
+                syncQueue);
     }
 
     //
@@ -263,8 +267,13 @@
     static FreeformTaskTransitionHandler provideFreeformTaskTransitionHandler(
             ShellInit shellInit,
             Transitions transitions,
-            WindowDecorViewModel windowDecorViewModel) {
-        return new FreeformTaskTransitionHandler(shellInit, transitions, windowDecorViewModel);
+            Context context,
+            WindowDecorViewModel windowDecorViewModel,
+            DisplayController displayController,
+            @ShellMainThread ShellExecutor mainExecutor,
+            @ShellAnimationThread ShellExecutor animExecutor) {
+        return new FreeformTaskTransitionHandler(shellInit, transitions, context,
+                windowDecorViewModel, displayController, mainExecutor, animExecutor);
     }
 
     @WMSingleton
@@ -327,11 +336,14 @@
             TransactionPool transactionPool,
             IconProvider iconProvider,
             Optional<RecentTasksController> recentTasks,
+            LaunchAdjacentController launchAdjacentController,
+            Optional<WindowDecorViewModel> windowDecorViewModel,
             @ShellMainThread ShellExecutor mainExecutor) {
         return new SplitScreenController(context, shellInit, shellCommandHandler, shellController,
                 shellTaskOrganizer, syncQueue, rootTaskDisplayAreaOrganizer, displayController,
                 displayImeController, displayInsetsController, dragAndDropController, transitions,
-                transactionPool, iconProvider, recentTasks, mainExecutor);
+                transactionPool, iconProvider, recentTasks, launchAdjacentController,
+                windowDecorViewModel, mainExecutor);
     }
 
     //
@@ -535,11 +547,13 @@
             Optional<PipTouchHandler> pipTouchHandlerOptional,
             Optional<RecentsTransitionHandler> recentsTransitionHandler,
             KeyguardTransitionHandler keyguardTransitionHandler,
+            Optional<DesktopModeController> desktopModeController,
+            Optional<DesktopTasksController> desktopTasksController,
             Optional<UnfoldTransitionHandler> unfoldHandler,
             Transitions transitions) {
         return new DefaultMixedHandler(shellInit, transitions, splitScreenOptional,
                 pipTouchHandlerOptional, recentsTransitionHandler, keyguardTransitionHandler,
-                unfoldHandler);
+                desktopModeController, desktopTasksController, unfoldHandler);
     }
 
     @WMSingleton
@@ -573,13 +587,13 @@
         animators.add(fullscreenAnimator);
 
         return new UnfoldAnimationController(
-                        shellInit,
-                        transactionPool,
-                        progressProvider.get(),
-                        animators,
-                        unfoldTransitionHandler,
-                        mainExecutor
-                );
+                shellInit,
+                transactionPool,
+                progressProvider.get(),
+                animators,
+                unfoldTransitionHandler,
+                mainExecutor
+        );
     }
 
     @Provides
@@ -671,6 +685,7 @@
     static DesktopTasksController provideDesktopTasksController(
             Context context,
             ShellInit shellInit,
+            ShellCommandHandler shellCommandHandler,
             ShellController shellController,
             DisplayController displayController,
             ShellTaskOrganizer shellTaskOrganizer,
@@ -679,13 +694,16 @@
             Transitions transitions,
             EnterDesktopTaskTransitionHandler enterDesktopTransitionHandler,
             ExitDesktopTaskTransitionHandler exitDesktopTransitionHandler,
+            ToggleResizeDesktopTaskTransitionHandler toggleResizeDesktopTaskTransitionHandler,
             @DynamicOverride DesktopModeTaskRepository desktopModeTaskRepository,
+            LaunchAdjacentController launchAdjacentController,
             @ShellMainThread ShellExecutor mainExecutor
     ) {
-        return new DesktopTasksController(context, shellInit, shellController, displayController,
-                shellTaskOrganizer, syncQueue, rootTaskDisplayAreaOrganizer, transitions,
-                enterDesktopTransitionHandler, exitDesktopTransitionHandler,
-                desktopModeTaskRepository, mainExecutor);
+        return new DesktopTasksController(context, shellInit, shellCommandHandler, shellController,
+                displayController, shellTaskOrganizer, syncQueue, rootTaskDisplayAreaOrganizer,
+                transitions, enterDesktopTransitionHandler, exitDesktopTransitionHandler,
+                toggleResizeDesktopTaskTransitionHandler, desktopModeTaskRepository,
+                launchAdjacentController, mainExecutor);
     }
 
     @WMSingleton
@@ -697,6 +715,13 @@
 
     @WMSingleton
     @Provides
+    static ToggleResizeDesktopTaskTransitionHandler provideToggleResizeDesktopTaskTransitionHandler(
+            Transitions transitions) {
+        return new ToggleResizeDesktopTaskTransitionHandler(transitions);
+    }
+
+    @WMSingleton
+    @Provides
     static ExitDesktopTaskTransitionHandler provideExitDesktopTaskTransitionHandler(
             Transitions transitions,
             Context context
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
index b9d2be2..db6c258 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
@@ -33,6 +33,7 @@
 import android.app.ActivityManager.RunningTaskInfo;
 import android.app.WindowConfiguration;
 import android.content.Context;
+import android.content.res.TypedArray;
 import android.database.ContentObserver;
 import android.graphics.Region;
 import android.net.Uri;
@@ -414,6 +415,25 @@
     }
 
     /**
+     * Applies the proper surface states (rounded corners) to tasks when desktop mode is active.
+     * This is intended to be used when desktop mode is part of another animation but isn't, itself,
+     * animating.
+     */
+    public void syncSurfaceState(@NonNull TransitionInfo info,
+            SurfaceControl.Transaction finishTransaction) {
+        // Add rounded corners to freeform windows
+        final TypedArray ta = mContext.obtainStyledAttributes(
+                new int[]{android.R.attr.dialogCornerRadius});
+        final int cornerRadius = ta.getDimensionPixelSize(0, 0);
+        ta.recycle();
+        for (TransitionInfo.Change change: info.getChanges()) {
+            if (change.getTaskInfo().getWindowingMode() == WINDOWING_MODE_FREEFORM) {
+                finishTransaction.setCornerRadius(change.getLeash(), cornerRadius);
+            }
+        }
+    }
+
+    /**
      * A {@link ContentObserver} for listening to changes to {@link Settings.System#DESKTOP_MODE}
      */
     private final class SettingsObserver extends ContentObserver {
@@ -500,6 +520,11 @@
         }
 
         @Override
+        public void showDesktopApp(int taskId) throws RemoteException {
+            // TODO
+        }
+
+        @Override
         public int getVisibleTaskCount(int displayId) throws RemoteException {
             int[] result = new int[1];
             executeRemoteCallWithTaskPermission(mController, "getVisibleTaskCount",
@@ -508,5 +533,20 @@
             );
             return result[0];
         }
+
+        @Override
+        public void stashDesktopApps(int displayId) throws RemoteException {
+            // Stashing of desktop apps not needed. Apps always launch on desktop
+        }
+
+        @Override
+        public void hideStashedDesktopApps(int displayId) throws RemoteException {
+            // Stashing of desktop apps not needed. Apps always launch on desktop
+        }
+
+        @Override
+        public void setTaskListener(IDesktopTaskListener listener) throws RemoteException {
+            // TODO(b/261234402): move visibility from sysui state to listener
+        }
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
index 3ab175d..711df0d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
@@ -25,6 +25,7 @@
 import androidx.core.util.valueIterator
 import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
 import com.android.wm.shell.util.KtProtoLog
+import java.io.PrintWriter
 import java.util.concurrent.Executor
 import java.util.function.Consumer
 
@@ -43,6 +44,7 @@
          */
         val activeTasks: ArraySet<Int> = ArraySet(),
         val visibleTasks: ArraySet<Int> = ArraySet(),
+        var stashed: Boolean = false
     )
 
     // Tasks currently in freeform mode, ordered from top to bottom (top is at index 0).
@@ -85,8 +87,10 @@
         visibleTasksListeners[visibleTasksListener] = executor
         displayData.keyIterator().forEach { displayId ->
             val visibleTasks = getVisibleTaskCount(displayId)
+            val stashed = isStashed(displayId)
             executor.execute {
                 visibleTasksListener.onVisibilityChanged(displayId, visibleTasks > 0)
+                visibleTasksListener.onStashedChanged(displayId, stashed)
             }
         }
     }
@@ -312,6 +316,52 @@
     }
 
     /**
+     * Update stashed status on display with id [displayId]
+     */
+    fun setStashed(displayId: Int, stashed: Boolean) {
+        val data = displayData.getOrCreate(displayId)
+        val oldValue = data.stashed
+        data.stashed = stashed
+        if (oldValue != stashed) {
+            KtProtoLog.d(
+                    WM_SHELL_DESKTOP_MODE,
+                    "DesktopTaskRepo: mark stashed=%b displayId=%d",
+                    stashed,
+                    displayId
+            )
+            visibleTasksListeners.forEach { (listener, executor) ->
+                executor.execute { listener.onStashedChanged(displayId, stashed) }
+            }
+        }
+    }
+
+    /**
+     * Check if display with id [displayId] has desktop tasks stashed
+     */
+    fun isStashed(displayId: Int): Boolean {
+        return displayData[displayId]?.stashed ?: false
+    }
+
+    internal fun dump(pw: PrintWriter, prefix: String) {
+        val innerPrefix = "$prefix  "
+        pw.println("${prefix}DesktopModeTaskRepository")
+        dumpDisplayData(pw, innerPrefix)
+        pw.println("${innerPrefix}freeformTasksInZOrder=${freeformTasksInZOrder.toDumpString()}")
+        pw.println("${innerPrefix}activeTasksListeners=${activeTasksListeners.size}")
+        pw.println("${innerPrefix}visibleTasksListeners=${visibleTasksListeners.size}")
+    }
+
+    private fun dumpDisplayData(pw: PrintWriter, prefix: String) {
+        val innerPrefix = "$prefix  "
+        displayData.forEach { displayId, data ->
+            pw.println("${prefix}Display $displayId:")
+            pw.println("${innerPrefix}activeTasks=${data.activeTasks.toDumpString()}")
+            pw.println("${innerPrefix}visibleTasks=${data.visibleTasks.toDumpString()}")
+            pw.println("${innerPrefix}stashed=${data.stashed}")
+        }
+    }
+
+    /**
      * Defines interface for classes that can listen to changes for active tasks in desktop mode.
      */
     interface ActiveTasksListener {
@@ -331,5 +381,15 @@
          */
         @JvmDefault
         fun onVisibilityChanged(displayId: Int, hasVisibleFreeformTasks: Boolean) {}
+
+        /**
+         * Called when the desktop stashed status changes.
+         */
+        @JvmDefault
+        fun onStashedChanged(displayId: Int, stashed: Boolean) {}
     }
 }
+
+private fun <T> Iterable<T>.toDumpString(): String {
+    return joinToString(separator = ", ", prefix = "[", postfix = "]")
+}
\ No newline at end of file
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 91bb155..f8d7b6b 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
@@ -16,6 +16,7 @@
 
 package com.android.wm.shell.desktopmode
 
+import android.R
 import android.app.ActivityManager.RunningTaskInfo
 import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME
 import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
@@ -24,19 +25,21 @@
 import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
 import android.app.WindowConfiguration.WindowingMode
 import android.content.Context
+import android.content.res.TypedArray
 import android.graphics.Point
 import android.graphics.Rect
 import android.graphics.Region
 import android.os.IBinder
 import android.os.SystemProperties
+import android.util.DisplayMetrics.DENSITY_DEFAULT
 import android.view.SurfaceControl
+import android.view.SurfaceControl.Transaction
 import android.view.WindowManager.TRANSIT_CHANGE
 import android.view.WindowManager.TRANSIT_NONE
 import android.view.WindowManager.TRANSIT_OPEN
 import android.view.WindowManager.TRANSIT_TO_FRONT
 import android.window.TransitionInfo
 import android.window.TransitionRequestInfo
-import android.window.WindowContainerToken
 import android.window.WindowContainerTransaction
 import androidx.annotation.BinderThread
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer
@@ -44,18 +47,24 @@
 import com.android.wm.shell.common.DisplayController
 import com.android.wm.shell.common.ExecutorUtils
 import com.android.wm.shell.common.ExternalInterfaceBinder
+import com.android.wm.shell.common.LaunchAdjacentController
 import com.android.wm.shell.common.RemoteCallable
 import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.common.SingleInstanceRemoteListener
 import com.android.wm.shell.common.SyncTransactionQueue
 import com.android.wm.shell.common.annotations.ExternalThread
 import com.android.wm.shell.common.annotations.ShellMainThread
 import com.android.wm.shell.desktopmode.DesktopModeTaskRepository.VisibleTasksListener
 import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
+import com.android.wm.shell.sysui.ShellCommandHandler
 import com.android.wm.shell.sysui.ShellController
 import com.android.wm.shell.sysui.ShellInit
 import com.android.wm.shell.sysui.ShellSharedConstants
 import com.android.wm.shell.transition.Transitions
 import com.android.wm.shell.util.KtProtoLog
+import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration
+import com.android.wm.shell.windowdecor.MoveToDesktopAnimator
+import java.io.PrintWriter
 import java.util.concurrent.Executor
 import java.util.function.Consumer
 
@@ -63,6 +72,7 @@
 class DesktopTasksController(
         private val context: Context,
         shellInit: ShellInit,
+        private val shellCommandHandler: ShellCommandHandler,
         private val shellController: ShellController,
         private val displayController: DisplayController,
         private val shellTaskOrganizer: ShellTaskOrganizer,
@@ -71,7 +81,10 @@
         private val transitions: Transitions,
         private val enterDesktopTaskTransitionHandler: EnterDesktopTaskTransitionHandler,
         private val exitDesktopTaskTransitionHandler: ExitDesktopTaskTransitionHandler,
+        private val toggleResizeDesktopTaskTransitionHandler:
+        ToggleResizeDesktopTaskTransitionHandler,
         private val desktopModeTaskRepository: DesktopModeTaskRepository,
+        private val launchAdjacentController: LaunchAdjacentController,
         @ShellMainThread private val mainExecutor: ShellExecutor
 ) : RemoteCallable<DesktopTasksController>, Transitions.TransitionHandler {
 
@@ -82,6 +95,15 @@
         visualIndicator?.releaseVisualIndicator(t)
         visualIndicator = null
     }
+    private val taskVisibilityListener = object : VisibleTasksListener {
+        override fun onVisibilityChanged(displayId: Int, hasVisibleFreeformTasks: Boolean) {
+            launchAdjacentController.launchAdjacentEnabled = !hasVisibleFreeformTasks
+        }
+    }
+
+    private val transitionAreaHeight
+        get() = context.resources.getDimensionPixelSize(
+                com.android.wm.shell.R.dimen.desktop_mode_transition_area_height)
 
     init {
         desktopMode = DesktopModeImpl()
@@ -92,12 +114,14 @@
 
     private fun onInit() {
         KtProtoLog.d(WM_SHELL_DESKTOP_MODE, "Initialize DesktopTasksController")
+        shellCommandHandler.addDumpCallback(this::dump, this)
         shellController.addExternalInterface(
             ShellSharedConstants.KEY_EXTRA_SHELL_DESKTOP_MODE,
             { createExternalInterface() },
             this
         )
         transitions.addHandler(this)
+        desktopModeTaskRepository.addVisibleTasksListener(taskVisibilityListener, mainExecutor)
     }
 
     /** Show all tasks, that are part of the desktop, on top of launcher */
@@ -118,27 +142,58 @@
         }
     }
 
+    /**
+     * Stash desktop tasks on display with id [displayId].
+     *
+     * When desktop tasks are stashed, launcher home screen icons are fully visible. New apps
+     * launched in this state will be added to the desktop. Existing desktop tasks will be brought
+     * back to front during the launch.
+     */
+    fun stashDesktopApps(displayId: Int) {
+        KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: stashDesktopApps")
+        desktopModeTaskRepository.setStashed(displayId, true)
+    }
+
+    /**
+     * Clear the stashed state for the given display
+     */
+    fun hideStashedDesktopApps(displayId: Int) {
+        KtProtoLog.v(
+                WM_SHELL_DESKTOP_MODE,
+                "DesktopTasksController: hideStashedApps displayId=%d",
+                displayId
+        )
+        desktopModeTaskRepository.setStashed(displayId, false)
+    }
+
     /** Get number of tasks that are marked as visible */
     fun getVisibleTaskCount(displayId: Int): Int {
         return desktopModeTaskRepository.getVisibleTaskCount(displayId)
     }
 
     /** Move a task with given `taskId` to desktop */
-    fun moveToDesktop(taskId: Int) {
-        shellTaskOrganizer.getRunningTaskInfo(taskId)?.let { task -> moveToDesktop(task) }
+    fun moveToDesktop(taskId: Int, wct: WindowContainerTransaction = WindowContainerTransaction()) {
+        shellTaskOrganizer.getRunningTaskInfo(taskId)?.let {
+            task -> moveToDesktop(task, wct)
+        }
     }
 
-    /** Move a task to desktop */
-    fun moveToDesktop(task: RunningTaskInfo) {
+    /**
+     * Move a task to desktop
+     */
+    fun moveToDesktop(
+            task: RunningTaskInfo,
+            wct: WindowContainerTransaction = WindowContainerTransaction()
+    ) {
         KtProtoLog.v(
             WM_SHELL_DESKTOP_MODE,
             "DesktopTasksController: moveToDesktop taskId=%d",
             task.taskId
         )
-        val wct = WindowContainerTransaction()
         // Bring other apps to front first
         bringDesktopAppsToFront(task.displayId, wct)
-        addMoveToDesktopChanges(wct, task.token)
+        addMoveToDesktopChanges(wct, task)
+
         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
             transitions.startTransition(TRANSIT_CHANGE, wct, null /* handler */)
         } else {
@@ -147,43 +202,51 @@
     }
 
     /**
-     * Moves a single task to freeform and sets the taskBounds to the passed in bounds,
-     * startBounds
+     * The first part of the animated move to desktop transition. Applies the changes to move task
+     * to desktop mode and sets the taskBounds to the passed in bounds, startBounds. This is
+     * followed with a call to {@link finishMoveToDesktop} or {@link cancelMoveToDesktop}.
      */
-    fun moveToFreeform(taskInfo: RunningTaskInfo, startBounds: Rect) {
+    fun startMoveToDesktop(
+            taskInfo: RunningTaskInfo,
+            startBounds: Rect,
+            dragToDesktopValueAnimator: MoveToDesktopAnimator
+    ) {
         KtProtoLog.v(
             WM_SHELL_DESKTOP_MODE,
-            "DesktopTasksController: moveToFreeform with bounds taskId=%d",
+            "DesktopTasksController: startMoveToDesktop taskId=%d",
             taskInfo.taskId
         )
         val wct = WindowContainerTransaction()
         moveHomeTaskToFront(wct)
-        addMoveToDesktopChanges(wct, taskInfo.getToken())
+        addMoveToDesktopChanges(wct, taskInfo)
         wct.setBounds(taskInfo.token, startBounds)
 
         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
-            enterDesktopTaskTransitionHandler.startTransition(
-                    Transitions.TRANSIT_ENTER_FREEFORM, wct, mOnAnimationFinishedCallback)
+            enterDesktopTaskTransitionHandler.startMoveToDesktop(wct, dragToDesktopValueAnimator,
+                    mOnAnimationFinishedCallback)
         } else {
             shellTaskOrganizer.applyTransaction(wct)
         }
     }
 
-    /** Brings apps to front and sets freeform task bounds */
-    private fun moveToDesktopWithAnimation(taskInfo: RunningTaskInfo, freeformBounds: Rect) {
+    /**
+     * The second part of the animated move to desktop transition, called after
+     * {@link startMoveToDesktop}. Brings apps to front and sets freeform task bounds.
+     */
+    private fun finalizeMoveToDesktop(taskInfo: RunningTaskInfo, freeformBounds: Rect) {
         KtProtoLog.v(
             WM_SHELL_DESKTOP_MODE,
-            "DesktopTasksController: moveToDesktop with animation taskId=%d",
+            "DesktopTasksController: finalizeMoveToDesktop taskId=%d",
             taskInfo.taskId
         )
         val wct = WindowContainerTransaction()
         bringDesktopAppsToFront(taskInfo.displayId, wct)
-        addMoveToDesktopChanges(wct, taskInfo.getToken())
+        addMoveToDesktopChanges(wct, taskInfo)
         wct.setBounds(taskInfo.token, freeformBounds)
 
         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
-            enterDesktopTaskTransitionHandler.startTransition(
-                Transitions.TRANSIT_ENTER_DESKTOP_MODE, wct, mOnAnimationFinishedCallback)
+            enterDesktopTaskTransitionHandler.finalizeMoveToDesktop(wct,
+                    mOnAnimationFinishedCallback)
         } else {
             shellTaskOrganizer.applyTransaction(wct)
             releaseVisualIndicator()
@@ -204,7 +267,7 @@
         )
 
         val wct = WindowContainerTransaction()
-        addMoveToFullscreenChanges(wct, task.token)
+        addMoveToFullscreenChanges(wct, task)
         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
             transitions.startTransition(TRANSIT_CHANGE, wct, null /* handler */)
         } else {
@@ -213,21 +276,30 @@
     }
 
     /**
-     * Move a task to fullscreen after being dragged from fullscreen and released back into
-     * status bar area
+     * The second part of the animated move to desktop transition, called after
+     * {@link startMoveToDesktop}. Move a task to fullscreen after being dragged from fullscreen
+     * and released back into status bar area.
      */
-    fun cancelMoveToFreeform(task: RunningTaskInfo, position: Point) {
+    fun cancelMoveToDesktop(task: RunningTaskInfo, moveToDesktopAnimator: MoveToDesktopAnimator) {
         KtProtoLog.v(
-                WM_SHELL_DESKTOP_MODE,
-                "DesktopTasksController: cancelMoveToFreeform taskId=%d",
-                task.taskId
+            WM_SHELL_DESKTOP_MODE,
+            "DesktopTasksController: cancelMoveToDesktop taskId=%d",
+            task.taskId
         )
         val wct = WindowContainerTransaction()
-        addMoveToFullscreenChanges(wct, task.token)
+        wct.setBounds(task.token, null)
+
         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
-            enterDesktopTaskTransitionHandler.startCancelMoveToDesktopMode(wct, position,
-                    mOnAnimationFinishedCallback)
+            enterDesktopTaskTransitionHandler.startCancelMoveToDesktopMode(wct,
+                    moveToDesktopAnimator) { t ->
+                val callbackWCT = WindowContainerTransaction()
+                visualIndicator?.releaseVisualIndicator(t)
+                visualIndicator = null
+                addMoveToFullscreenChanges(callbackWCT, task)
+                transitions.startTransition(TRANSIT_CHANGE, callbackWCT, null /* handler */)
+            }
         } else {
+            addMoveToFullscreenChanges(wct, task)
             shellTaskOrganizer.applyTransaction(wct)
             releaseVisualIndicator()
         }
@@ -240,7 +312,7 @@
                 task.taskId
         )
         val wct = WindowContainerTransaction()
-        addMoveToFullscreenChanges(wct, task.token)
+        addMoveToFullscreenChanges(wct, task)
 
         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
             exitDesktopTaskTransitionHandler.startTransition(
@@ -252,6 +324,11 @@
     }
 
     /** Move a task to the front */
+    fun moveTaskToFront(taskId: Int) {
+        shellTaskOrganizer.getRunningTaskInfo(taskId)?.let { task -> moveTaskToFront(task) }
+    }
+
+    /** Move a task to the front */
     fun moveTaskToFront(taskInfo: RunningTaskInfo) {
         KtProtoLog.v(
             WM_SHELL_DESKTOP_MODE,
@@ -331,6 +408,49 @@
         }
     }
 
+    /** Quick-resizes a desktop task, toggling between the stable bounds and the default bounds. */
+    fun toggleDesktopTaskSize(taskInfo: RunningTaskInfo, windowDecor: DesktopModeWindowDecoration) {
+        val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return
+
+        val stableBounds = Rect()
+        displayLayout.getStableBounds(stableBounds)
+        val destinationBounds = Rect()
+        if (taskInfo.configuration.windowConfiguration.bounds == stableBounds) {
+            // The desktop task is currently occupying the whole stable bounds, toggle to the
+            // default bounds.
+            getDefaultDesktopTaskBounds(
+                density = taskInfo.configuration.densityDpi.toFloat() / DENSITY_DEFAULT,
+                stableBounds = stableBounds,
+                outBounds = destinationBounds
+            )
+        } else {
+            // Toggle to the stable bounds.
+            destinationBounds.set(stableBounds)
+        }
+
+        val wct = WindowContainerTransaction().setBounds(taskInfo.token, destinationBounds)
+        if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+            toggleResizeDesktopTaskTransitionHandler.startTransition(
+                wct,
+                taskInfo.taskId,
+                windowDecor
+            )
+        } else {
+            shellTaskOrganizer.applyTransaction(wct)
+        }
+    }
+
+    private fun getDefaultDesktopTaskBounds(density: Float, stableBounds: Rect, outBounds: Rect) {
+        val width = (DESKTOP_MODE_DEFAULT_WIDTH_DP * density + 0.5f).toInt()
+        val height = (DESKTOP_MODE_DEFAULT_HEIGHT_DP * density + 0.5f).toInt()
+        outBounds.set(0, 0, width, height)
+        // Center the task in stable bounds
+        outBounds.offset(
+            stableBounds.centerX() - outBounds.centerX(),
+            stableBounds.centerY() - outBounds.centerY()
+        )
+    }
+
     /**
      * Get windowing move for a given `taskId`
      *
@@ -397,83 +517,169 @@
         transition: IBinder,
         request: TransitionRequestInfo
     ): WindowContainerTransaction? {
+        KtProtoLog.v(
+            WM_SHELL_DESKTOP_MODE,
+            "DesktopTasksController: handleRequest request=%s",
+            request
+        )
         // Check if we should skip handling this transition
+        var reason = ""
         val shouldHandleRequest =
             when {
                 // Only handle open or to front transitions
-                request.type != TRANSIT_OPEN && request.type != TRANSIT_TO_FRONT -> false
+                request.type != TRANSIT_OPEN && request.type != TRANSIT_TO_FRONT -> {
+                    reason = "transition type not handled (${request.type})"
+                    false
+                }
                 // Only handle when it is a task transition
-                request.triggerTask == null -> false
+                request.triggerTask == null -> {
+                    reason = "triggerTask is null"
+                    false
+                }
                 // Only handle standard type tasks
-                request.triggerTask.activityType != ACTIVITY_TYPE_STANDARD -> false
+                request.triggerTask.activityType != ACTIVITY_TYPE_STANDARD -> {
+                    reason = "activityType not handled (${request.triggerTask.activityType})"
+                    false
+                }
                 // Only handle fullscreen or freeform tasks
                 request.triggerTask.windowingMode != WINDOWING_MODE_FULLSCREEN &&
-                        request.triggerTask.windowingMode != WINDOWING_MODE_FREEFORM -> false
+                        request.triggerTask.windowingMode != WINDOWING_MODE_FREEFORM -> {
+                    reason = "windowingMode not handled (${request.triggerTask.windowingMode})"
+                    false
+                }
                 // Otherwise process it
                 else -> true
             }
 
         if (!shouldHandleRequest) {
+            KtProtoLog.v(
+                    WM_SHELL_DESKTOP_MODE,
+                    "DesktopTasksController: skipping handleRequest reason=%s",
+                    reason
+            )
             return null
         }
 
         val task: RunningTaskInfo = request.triggerTask
-        val activeTasks = desktopModeTaskRepository.getActiveTasks(task.displayId)
 
-        // Check if we should switch a fullscreen task to freeform
-        if (task.windowingMode == WINDOWING_MODE_FULLSCREEN) {
-            // If there are any visible desktop tasks, switch the task to freeform
-            if (activeTasks.any { desktopModeTaskRepository.isVisibleTask(it) }) {
-                KtProtoLog.d(
-                    WM_SHELL_DESKTOP_MODE,
-                    "DesktopTasksController: switch fullscreen task to freeform on transition" +
-                        " taskId=%d",
-                    task.taskId
-                )
-                return WindowContainerTransaction().also { wct ->
-                    addMoveToDesktopChanges(wct, task.token)
-                }
+        val result = when {
+            // If display has tasks stashed, handle as stashed launch
+            desktopModeTaskRepository.isStashed(task.displayId) -> handleStashedTaskLaunch(task)
+            // Check if fullscreen task should be updated
+            task.windowingMode == WINDOWING_MODE_FULLSCREEN -> handleFullscreenTaskLaunch(task)
+            // Check if freeform task should be updated
+            task.windowingMode == WINDOWING_MODE_FREEFORM -> handleFreeformTaskLaunch(task)
+            else -> {
+                null
             }
         }
+        KtProtoLog.v(
+                WM_SHELL_DESKTOP_MODE,
+                "DesktopTasksController: handleRequest result=%s",
+                result ?: "null"
+        )
+        return result
+    }
 
-        // CHeck if we should switch a freeform task to fullscreen
-        if (task.windowingMode == WINDOWING_MODE_FREEFORM) {
-            // If no visible desktop tasks, switch this task to freeform as the transition came
-            // outside of this controller
-            if (activeTasks.none { desktopModeTaskRepository.isVisibleTask(it) }) {
-                KtProtoLog.d(
+    /**
+     * Applies the proper surface states (rounded corners) to tasks when desktop mode is active.
+     * This is intended to be used when desktop mode is part of another animation but isn't, itself,
+     * animating.
+     */
+    fun syncSurfaceState(
+            info: TransitionInfo,
+            finishTransaction: SurfaceControl.Transaction
+    ) {
+        // Add rounded corners to freeform windows
+        val ta: TypedArray = context.obtainStyledAttributes(
+                intArrayOf(R.attr.dialogCornerRadius))
+        val cornerRadius = ta.getDimensionPixelSize(0, 0).toFloat()
+        ta.recycle()
+        info.changes
+                .filter { it.taskInfo?.windowingMode == WINDOWING_MODE_FREEFORM }
+                .forEach { finishTransaction.setCornerRadius(it.leash, cornerRadius) }
+    }
+
+    private fun handleFreeformTaskLaunch(task: RunningTaskInfo): WindowContainerTransaction? {
+        KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: handleFreeformTaskLaunch")
+        val activeTasks = desktopModeTaskRepository.getActiveTasks(task.displayId)
+        if (activeTasks.none { desktopModeTaskRepository.isVisibleTask(it) }) {
+            KtProtoLog.d(
                     WM_SHELL_DESKTOP_MODE,
                     "DesktopTasksController: switch freeform task to fullscreen oon transition" +
-                        " taskId=%d",
+                            " taskId=%d",
                     task.taskId
-                )
-                return WindowContainerTransaction().also { wct ->
-                    addMoveToFullscreenChanges(wct, task.token)
-                }
+            )
+            return WindowContainerTransaction().also { wct ->
+                addMoveToFullscreenChanges(wct, task)
             }
         }
         return null
     }
 
+    private fun handleFullscreenTaskLaunch(task: RunningTaskInfo): WindowContainerTransaction? {
+        KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: handleFullscreenTaskLaunch")
+        val activeTasks = desktopModeTaskRepository.getActiveTasks(task.displayId)
+        if (activeTasks.any { desktopModeTaskRepository.isVisibleTask(it) }) {
+            KtProtoLog.d(
+                    WM_SHELL_DESKTOP_MODE,
+                    "DesktopTasksController: switch fullscreen task to freeform on transition" +
+                            " taskId=%d",
+                    task.taskId
+            )
+            return WindowContainerTransaction().also { wct ->
+                addMoveToDesktopChanges(wct, task)
+            }
+        }
+        return null
+    }
+
+    private fun handleStashedTaskLaunch(task: RunningTaskInfo): WindowContainerTransaction {
+        KtProtoLog.d(
+                WM_SHELL_DESKTOP_MODE,
+                "DesktopTasksController: launch apps with stashed on transition taskId=%d",
+                task.taskId
+        )
+        val wct = WindowContainerTransaction()
+        bringDesktopAppsToFront(task.displayId, wct)
+        addMoveToDesktopChanges(wct, task)
+        desktopModeTaskRepository.setStashed(task.displayId, false)
+        return wct
+    }
+
     private fun addMoveToDesktopChanges(
         wct: WindowContainerTransaction,
-        token: WindowContainerToken
+        taskInfo: RunningTaskInfo
     ) {
-        wct.setWindowingMode(token, WINDOWING_MODE_FREEFORM)
-        wct.reorder(token, true /* onTop */)
+        val displayWindowingMode = taskInfo.configuration.windowConfiguration.displayWindowingMode
+        val targetWindowingMode = if (displayWindowingMode == WINDOWING_MODE_FREEFORM) {
+            // Display windowing is freeform, set to undefined and inherit it
+            WINDOWING_MODE_UNDEFINED
+        } else {
+            WINDOWING_MODE_FREEFORM
+        }
+        wct.setWindowingMode(taskInfo.token, targetWindowingMode)
+        wct.reorder(taskInfo.token, true /* onTop */)
         if (isDesktopDensityOverrideSet()) {
-            wct.setDensityDpi(token, getDesktopDensityDpi())
+            wct.setDensityDpi(taskInfo.token, getDesktopDensityDpi())
         }
     }
 
     private fun addMoveToFullscreenChanges(
         wct: WindowContainerTransaction,
-        token: WindowContainerToken
+        taskInfo: RunningTaskInfo
     ) {
-        wct.setWindowingMode(token, WINDOWING_MODE_FULLSCREEN)
-        wct.setBounds(token, null)
+        val displayWindowingMode = taskInfo.configuration.windowConfiguration.displayWindowingMode
+        val targetWindowingMode = if (displayWindowingMode == WINDOWING_MODE_FULLSCREEN) {
+            // Display windowing is fullscreen, set to undefined and inherit it
+            WINDOWING_MODE_UNDEFINED
+        } else {
+            WINDOWING_MODE_FULLSCREEN
+        }
+        wct.setWindowingMode(taskInfo.token, targetWindowingMode)
+        wct.setBounds(taskInfo.token, null)
         if (isDesktopDensityOverrideSet()) {
-            wct.setDensityDpi(token, getFullscreenDensityDpi())
+            wct.setDensityDpi(taskInfo.token, getFullscreenDensityDpi())
         }
     }
 
@@ -508,13 +714,12 @@
             y: Float
     ) {
         if (taskInfo.windowingMode == WINDOWING_MODE_FREEFORM) {
-            val statusBarHeight = getStatusBarHeight(taskInfo)
-            if (y <= statusBarHeight && visualIndicator == null) {
+            if (y <= transitionAreaHeight && visualIndicator == null) {
                 visualIndicator = DesktopModeVisualIndicator(syncQueue, taskInfo,
                         displayController, context, taskSurface, shellTaskOrganizer,
                         rootTaskDisplayAreaOrganizer)
                 visualIndicator?.createFullscreenIndicatorWithAnimatedBounds()
-            } else if (y > statusBarHeight && visualIndicator != null) {
+            } else if (y > transitionAreaHeight && visualIndicator != null) {
                 releaseVisualIndicator()
             }
         }
@@ -524,14 +729,18 @@
      * Perform checks required on drag end. Move to fullscreen if drag ends in status bar area.
      *
      * @param taskInfo the task being dragged.
-     * @param position position of surface when drag ends
+     * @param position position of surface when drag ends.
+     * @param y the Y position of the motion event.
+     * @param windowDecor the window decoration for the task being dragged
      */
     fun onDragPositioningEnd(
             taskInfo: RunningTaskInfo,
-            position: Point
+            position: Point,
+            y: Float,
+            windowDecor: DesktopModeWindowDecoration
     ) {
-        val statusBarHeight = getStatusBarHeight(taskInfo)
-        if (position.y <= statusBarHeight && taskInfo.windowingMode == WINDOWING_MODE_FREEFORM) {
+        if (y <= transitionAreaHeight && taskInfo.windowingMode == WINDOWING_MODE_FREEFORM) {
+            windowDecor.incrementRelayoutBlock()
             moveToFullscreenWithAnimation(taskInfo, position)
         }
     }
@@ -549,9 +758,9 @@
             taskSurface: SurfaceControl,
             y: Float
     ) {
-        // If the motion event is above the status bar, return since we do not need to show the
-        // visual indicator at this point.
-        if (y < getStatusBarHeight(taskInfo)) {
+        // If the motion event is above the status bar and the visual indicator is not yet visible,
+        // return since we do not need to show the visual indicator at this point.
+        if (y < getStatusBarHeight(taskInfo) && visualIndicator == null) {
             return
         }
         if (visualIndicator == null) {
@@ -580,7 +789,7 @@
             taskInfo: RunningTaskInfo,
             freeformBounds: Rect
     ) {
-        moveToDesktopWithAnimation(taskInfo, freeformBounds)
+        finalizeMoveToDesktop(taskInfo, freeformBounds)
     }
 
     private fun getStatusBarHeight(taskInfo: RunningTaskInfo): Int {
@@ -632,6 +841,12 @@
         desktopModeTaskRepository.setTaskCornerListener(listener, callbackExecutor)
     }
 
+    private fun dump(pw: PrintWriter, prefix: String) {
+        val innerPrefix = "$prefix  "
+        pw.println("${prefix}DesktopTasksController")
+        desktopModeTaskRepository.dump(pw, innerPrefix)
+    }
+
     /** The interface for calls from outside the shell, within the host process. */
     @ExternalThread
     private inner class DesktopModeImpl : DesktopMode {
@@ -658,8 +873,51 @@
     @BinderThread
     private class IDesktopModeImpl(private var controller: DesktopTasksController?) :
         IDesktopMode.Stub(), ExternalInterfaceBinder {
+
+        private lateinit var remoteListener:
+                SingleInstanceRemoteListener<DesktopTasksController, IDesktopTaskListener>
+
+        private val listener: VisibleTasksListener = object : VisibleTasksListener {
+            override fun onVisibilityChanged(displayId: Int, visible: Boolean) {
+                KtProtoLog.v(
+                        WM_SHELL_DESKTOP_MODE,
+                        "IDesktopModeImpl: onVisibilityChanged display=%d visible=%b",
+                        displayId,
+                        visible
+                )
+                remoteListener.call { l -> l.onVisibilityChanged(displayId, visible) }
+            }
+
+            override fun onStashedChanged(displayId: Int, stashed: Boolean) {
+                KtProtoLog.v(
+                        WM_SHELL_DESKTOP_MODE,
+                        "IDesktopModeImpl: onStashedChanged display=%d stashed=%b",
+                        displayId,
+                        stashed
+                )
+                remoteListener.call { l -> l.onStashedChanged(displayId, stashed) }
+            }
+        }
+
+        init {
+            remoteListener =
+                    SingleInstanceRemoteListener<DesktopTasksController, IDesktopTaskListener>(
+                            controller,
+                            { c ->
+                                c.desktopModeTaskRepository.addVisibleTasksListener(
+                                        listener,
+                                        c.mainExecutor
+                                )
+                            },
+                            { c ->
+                                c.desktopModeTaskRepository.removeVisibleTasksListener(listener)
+                            }
+                    )
+        }
+
         /** Invalidates this instance, preventing future calls from updating the controller. */
         override fun invalidate() {
+            remoteListener.unregister()
             controller = null
         }
 
@@ -670,6 +928,27 @@
             ) { c -> c.showDesktopApps(displayId) }
         }
 
+        override fun stashDesktopApps(displayId: Int) {
+            ExecutorUtils.executeRemoteCallWithTaskPermission(
+                    controller,
+                    "stashDesktopApps"
+            ) { c -> c.stashDesktopApps(displayId) }
+        }
+
+        override fun hideStashedDesktopApps(displayId: Int) {
+            ExecutorUtils.executeRemoteCallWithTaskPermission(
+                    controller,
+                    "hideStashedDesktopApps"
+            ) { c -> c.hideStashedDesktopApps(displayId) }
+        }
+
+        override fun showDesktopApp(taskId: Int) {
+            ExecutorUtils.executeRemoteCallWithTaskPermission(
+                    controller,
+                    "showDesktopApp"
+            ) { c -> c.moveTaskToFront(taskId) }
+        }
+
         override fun getVisibleTaskCount(displayId: Int): Int {
             val result = IntArray(1)
             ExecutorUtils.executeRemoteCallWithTaskPermission(
@@ -680,13 +959,33 @@
             )
             return result[0]
         }
+
+        override fun setTaskListener(listener: IDesktopTaskListener?) {
+            KtProtoLog.v(
+                    WM_SHELL_DESKTOP_MODE,
+                    "IDesktopModeImpl: set task listener=%s",
+                    listener ?: "null"
+            )
+            ExecutorUtils.executeRemoteCallWithTaskPermission(
+                    controller,
+                    "setTaskListener"
+            ) { _ -> listener?.let { remoteListener.register(it) } ?: remoteListener.unregister() }
+        }
     }
 
     companion object {
         private val DESKTOP_DENSITY_OVERRIDE =
-            SystemProperties.getInt("persist.wm.debug.desktop_mode_density", 0)
+            SystemProperties.getInt("persist.wm.debug.desktop_mode_density", 284)
         private val DESKTOP_DENSITY_ALLOWED_RANGE = (100..1000)
 
+        // Override default freeform task width when desktop mode is enabled. In dips.
+        private val DESKTOP_MODE_DEFAULT_WIDTH_DP =
+            SystemProperties.getInt("persist.wm.debug.desktop_mode.default_width", 840)
+
+        // Override default freeform task height when desktop mode is enabled. In dips.
+        private val DESKTOP_MODE_DEFAULT_HEIGHT_DP =
+            SystemProperties.getInt("persist.wm.debug.desktop_mode.default_height", 630)
+
         /**
          * Check if desktop density override is enabled
          */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
index 3733b91..1acf783 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
@@ -17,15 +17,15 @@
 package com.android.wm.shell.desktopmode;
 
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
 import android.app.ActivityManager;
-import android.graphics.Point;
+import android.graphics.PointF;
 import android.graphics.Rect;
 import android.os.IBinder;
+import android.util.Slog;
 import android.view.SurfaceControl;
 import android.view.WindowManager;
 import android.window.TransitionInfo;
@@ -36,6 +36,7 @@
 import androidx.annotation.Nullable;
 
 import com.android.wm.shell.transition.Transitions;
+import com.android.wm.shell.windowdecor.MoveToDesktopAnimator;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -48,18 +49,17 @@
  */
 public class EnterDesktopTaskTransitionHandler implements Transitions.TransitionHandler {
 
+    private static final String TAG = "EnterDesktopTaskTransitionHandler";
     private final Transitions mTransitions;
     private final Supplier<SurfaceControl.Transaction> mTransactionSupplier;
 
-    // The size of the screen during drag relative to the fullscreen size
-    public static final float DRAG_FREEFORM_SCALE = 0.4f;
     // The size of the screen after drag relative to the fullscreen size
     public static final float FINAL_FREEFORM_SCALE = 0.6f;
     public static final int FREEFORM_ANIMATION_DURATION = 336;
 
     private final List<IBinder> mPendingTransitionTokens = new ArrayList<>();
-    private Point mPosition;
     private Consumer<SurfaceControl.Transaction> mOnAnimationFinishedCallback;
+    private MoveToDesktopAnimator mMoveToDesktopAnimator;
 
     public EnterDesktopTaskTransitionHandler(
             Transitions transitions) {
@@ -79,7 +79,7 @@
      * @param wct WindowContainerTransaction for transition
      * @param onAnimationEndCallback to be called after animation
      */
-    public void startTransition(@WindowManager.TransitionType int type,
+    private void startTransition(@WindowManager.TransitionType int type,
             @NonNull WindowContainerTransaction wct,
             Consumer<SurfaceControl.Transaction> onAnimationEndCallback) {
         mOnAnimationFinishedCallback = onAnimationEndCallback;
@@ -88,15 +88,42 @@
     }
 
     /**
+     * Starts Transition of type TRANSIT_START_MOVE_TO_DESKTOP_MODE
+     * @param wct WindowContainerTransaction for transition
+     * @param moveToDesktopAnimator Animator that shrinks and positions task during two part move
+     *                              to desktop animation
+     * @param onAnimationEndCallback to be called after animation
+     */
+    public void startMoveToDesktop(@NonNull WindowContainerTransaction wct,
+            @NonNull MoveToDesktopAnimator moveToDesktopAnimator,
+            Consumer<SurfaceControl.Transaction> onAnimationEndCallback) {
+        mMoveToDesktopAnimator = moveToDesktopAnimator;
+        startTransition(Transitions.TRANSIT_START_MOVE_TO_DESKTOP_MODE, wct,
+                onAnimationEndCallback);
+    }
+
+    /**
+     * Starts Transition of type TRANSIT_FINALIZE_MOVE_TO_DESKTOP_MODE
+     * @param wct WindowContainerTransaction for transition
+     * @param onAnimationEndCallback to be called after animation
+     */
+    public void finalizeMoveToDesktop(@NonNull WindowContainerTransaction wct,
+            Consumer<SurfaceControl.Transaction> onAnimationEndCallback) {
+        startTransition(Transitions.TRANSIT_FINALIZE_MOVE_TO_DESKTOP_MODE, wct,
+                onAnimationEndCallback);
+    }
+
+    /**
      * Starts Transition of type TRANSIT_CANCEL_ENTERING_DESKTOP_MODE
      * @param wct WindowContainerTransaction for transition
-     * @param position Position of task when transition is triggered
+     * @param moveToDesktopAnimator Animator that shrinks and positions task during two part move
+     *                              to desktop animation
      * @param onAnimationEndCallback to be called after animation
      */
     public void startCancelMoveToDesktopMode(@NonNull WindowContainerTransaction wct,
-            Point position,
+            MoveToDesktopAnimator moveToDesktopAnimator,
             Consumer<SurfaceControl.Transaction> onAnimationEndCallback) {
-        mPosition = position;
+        mMoveToDesktopAnimator = moveToDesktopAnimator;
         startTransition(Transitions.TRANSIT_CANCEL_ENTERING_DESKTOP_MODE, wct,
                 onAnimationEndCallback);
     }
@@ -140,20 +167,34 @@
         }
 
         final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
-        if (type == Transitions.TRANSIT_ENTER_FREEFORM
+        if (type == Transitions.TRANSIT_START_MOVE_TO_DESKTOP_MODE
                 && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
             // Transitioning to freeform but keeping fullscreen bounds, so the crop is set
             // to null and we don't require an animation
             final SurfaceControl sc = change.getLeash();
             startT.setWindowCrop(sc, null);
+
+            if (mMoveToDesktopAnimator == null
+                    || mMoveToDesktopAnimator.getTaskId() != change.getTaskInfo().taskId) {
+                Slog.e(TAG, "No animator available for this transition");
+                return false;
+            }
+
+            // Calculate and set position of the task
+            final PointF position = mMoveToDesktopAnimator.getPosition();
+            startT.setPosition(sc, position.x, position.y);
+            finishT.setPosition(sc, position.x, position.y);
+
             startT.apply();
+
             mTransitions.getMainExecutor().execute(
                     () -> finishCallback.onTransitionFinished(null, null));
+
             return true;
         }
 
         Rect endBounds = change.getEndAbsBounds();
-        if (type == Transitions.TRANSIT_ENTER_DESKTOP_MODE
+        if (type == Transitions.TRANSIT_FINALIZE_MOVE_TO_DESKTOP_MODE
                 && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM
                 && !endBounds.isEmpty()) {
             // This Transition animates a task to freeform bounds after being dragged into freeform
@@ -163,12 +204,18 @@
                     endBounds.height());
             startT.apply();
 
+            // End the animation that shrinks the window when task is first dragged from fullscreen
+            if (mMoveToDesktopAnimator != null) {
+                mMoveToDesktopAnimator.endAnimator();
+            }
+
             // We want to find the scale of the current bounds relative to the end bounds. The
             // task is currently scaled to DRAG_FREEFORM_SCALE and the final bounds will be
             // scaled to FINAL_FREEFORM_SCALE. So, it is scaled to
             // DRAG_FREEFORM_SCALE / FINAL_FREEFORM_SCALE relative to the freeform bounds
             final ValueAnimator animator =
-                    ValueAnimator.ofFloat(DRAG_FREEFORM_SCALE / FINAL_FREEFORM_SCALE, 1f);
+                    ValueAnimator.ofFloat(
+                            MoveToDesktopAnimator.DRAG_FREEFORM_SCALE / FINAL_FREEFORM_SCALE, 1f);
             animator.setDuration(FREEFORM_ANIMATION_DURATION);
             final SurfaceControl.Transaction t = mTransactionSupplier.get();
             animator.addUpdateListener(animation -> {
@@ -200,8 +247,7 @@
         }
 
         if (type == Transitions.TRANSIT_CANCEL_ENTERING_DESKTOP_MODE
-                && taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN
-                && mPosition != null) {
+                && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
             // This Transition animates a task to fullscreen after being dragged from the status
             // bar and then released back into the status bar area
             final SurfaceControl sc = change.getLeash();
@@ -211,13 +257,27 @@
                     .setWindowCrop(sc, endBounds.width(), endBounds.height())
                     .apply();
 
+            if (mMoveToDesktopAnimator == null
+                    || mMoveToDesktopAnimator.getTaskId() != change.getTaskInfo().taskId) {
+                Slog.e(TAG, "No animator available for this transition");
+                return false;
+            }
+
+            // End the animation that shrinks the window when task is first dragged from fullscreen
+            mMoveToDesktopAnimator.endAnimator();
+
             final ValueAnimator animator = new ValueAnimator();
-            animator.setFloatValues(DRAG_FREEFORM_SCALE, 1f);
+            animator.setFloatValues(MoveToDesktopAnimator.DRAG_FREEFORM_SCALE, 1f);
             animator.setDuration(FREEFORM_ANIMATION_DURATION);
             final SurfaceControl.Transaction t = mTransactionSupplier.get();
+
+            // Get position of the task
+            final float x = mMoveToDesktopAnimator.getPosition().x;
+            final float y = mMoveToDesktopAnimator.getPosition().y;
+
             animator.addUpdateListener(animation -> {
                 final float scale = (float) animation.getAnimatedValue();
-                t.setPosition(sc, mPosition.x * (1 - scale), mPosition.y * (1 - scale))
+                t.setPosition(sc, x * (1 - scale), y * (1 - scale))
                         .setScale(sc, scale, scale)
                         .show(sc)
                         .apply();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl
index 899d672..ee3a080 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl
@@ -16,6 +16,8 @@
 
 package com.android.wm.shell.desktopmode;
 
+import com.android.wm.shell.desktopmode.IDesktopTaskListener;
+
 /**
  * Interface that is exposed to remote callers to manipulate desktop mode features.
  */
@@ -24,6 +26,18 @@
     /** Show apps on the desktop on the given display */
     void showDesktopApps(int displayId);
 
+    /** Stash apps on the desktop to allow launching another app from home screen */
+    void stashDesktopApps(int displayId);
+
+    /** Hide apps that may be stashed */
+    void hideStashedDesktopApps(int displayId);
+
+    /** Bring task with the given id to front */
+    oneway void showDesktopApp(int taskId);
+
     /** Get count of visible desktop tasks on the given display */
     int getVisibleTaskCount(int displayId);
+
+    /** Set listener that will receive callbacks about updates to desktop tasks */
+    oneway void setTaskListener(IDesktopTaskListener listener);
 }
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopTaskListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopTaskListener.aidl
new file mode 100644
index 0000000..39128a8
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopTaskListener.aidl
@@ -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.wm.shell.desktopmode;
+
+/**
+ * Allows external processes to register a listener in WMShell to get updates about desktop task
+ * state.
+ */
+interface IDesktopTaskListener {
+
+    /** Desktop task visibility has change. Visible if at least 1 task is visible. */
+    oneway void onVisibilityChanged(int displayId, boolean visible);
+
+    /** Desktop task stashed status has changed. */
+    oneway void onStashedChanged(int displayId, boolean stashed);
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt
new file mode 100644
index 0000000..94788e4
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.desktopmode
+
+import android.animation.Animator
+import android.animation.RectEvaluator
+import android.animation.ValueAnimator
+import android.graphics.Rect
+import android.os.IBinder
+import android.util.SparseArray
+import android.view.SurfaceControl
+import android.view.WindowManager.TRANSIT_CHANGE
+import android.window.TransitionInfo
+import android.window.TransitionRequestInfo
+import android.window.WindowContainerTransaction
+import androidx.core.animation.addListener
+import com.android.wm.shell.transition.Transitions
+import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_TOGGLE_RESIZE
+import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration
+import java.util.function.Supplier
+
+/** Handles the animation of quick resizing of desktop tasks. */
+class ToggleResizeDesktopTaskTransitionHandler(
+    private val transitions: Transitions,
+    private val transactionSupplier: Supplier<SurfaceControl.Transaction>
+) : Transitions.TransitionHandler {
+
+    private val rectEvaluator = RectEvaluator(Rect())
+    private val taskToDecorationMap = SparseArray<DesktopModeWindowDecoration>()
+
+    private var boundsAnimator: Animator? = null
+
+    constructor(
+        transitions: Transitions
+    ) : this(transitions, Supplier { SurfaceControl.Transaction() })
+
+    /** Starts a quick resize transition. */
+    fun startTransition(
+        wct: WindowContainerTransaction,
+        taskId: Int,
+        windowDecoration: DesktopModeWindowDecoration
+    ) {
+        // Pause relayout until the transition animation finishes.
+        windowDecoration.incrementRelayoutBlock()
+        transitions.startTransition(TRANSIT_DESKTOP_MODE_TOGGLE_RESIZE, wct, this)
+        taskToDecorationMap.put(taskId, windowDecoration)
+    }
+
+    override fun startAnimation(
+        transition: IBinder,
+        info: TransitionInfo,
+        startTransaction: SurfaceControl.Transaction,
+        finishTransaction: SurfaceControl.Transaction,
+        finishCallback: Transitions.TransitionFinishCallback
+    ): Boolean {
+        val change = findRelevantChange(info)
+        val leash = change.leash
+        val taskId = change.taskInfo.taskId
+        val startBounds = change.startAbsBounds
+        val endBounds = change.endAbsBounds
+        val windowDecor =
+            taskToDecorationMap.removeReturnOld(taskId)
+                ?: throw IllegalStateException("Window decoration not found for task $taskId")
+
+        val tx = transactionSupplier.get()
+        boundsAnimator?.cancel()
+        boundsAnimator =
+            ValueAnimator.ofObject(rectEvaluator, startBounds, endBounds)
+                .setDuration(RESIZE_DURATION_MS)
+                .apply {
+                    addListener(
+                        onStart = {
+                            startTransaction
+                                .setPosition(
+                                    leash,
+                                    startBounds.left.toFloat(),
+                                    startBounds.top.toFloat()
+                                )
+                                .setWindowCrop(leash, startBounds.width(), startBounds.height())
+                                .show(leash)
+                            windowDecor.showResizeVeil(startTransaction, startBounds)
+                        },
+                        onEnd = {
+                            finishTransaction
+                                .setPosition(
+                                    leash,
+                                    endBounds.left.toFloat(),
+                                    endBounds.top.toFloat()
+                                )
+                                .setWindowCrop(leash, endBounds.width(), endBounds.height())
+                                .show(leash)
+                            windowDecor.hideResizeVeil()
+                            finishCallback.onTransitionFinished(null, null)
+                            boundsAnimator = null
+                        }
+                    )
+                    addUpdateListener { anim ->
+                        val rect = anim.animatedValue as Rect
+                        tx.setPosition(leash, rect.left.toFloat(), rect.top.toFloat())
+                            .setWindowCrop(leash, rect.width(), rect.height())
+                            .show(leash)
+                        windowDecor.updateResizeVeil(tx, rect)
+                    }
+                    start()
+                }
+        return true
+    }
+
+    override fun handleRequest(
+        transition: IBinder,
+        request: TransitionRequestInfo
+    ): WindowContainerTransaction? {
+        return null
+    }
+
+    private fun findRelevantChange(info: TransitionInfo): TransitionInfo.Change {
+        val matchingChanges =
+            info.changes.filter { c ->
+                !isWallpaper(c) && isValidTaskChange(c) && c.mode == TRANSIT_CHANGE
+            }
+        if (matchingChanges.size != 1) {
+            throw IllegalStateException(
+                "Expected 1 relevant change but found: ${matchingChanges.size}"
+            )
+        }
+        return matchingChanges.first()
+    }
+
+    private fun isWallpaper(change: TransitionInfo.Change): Boolean {
+        return (change.flags and TransitionInfo.FLAG_IS_WALLPAPER) != 0
+    }
+
+    private fun isValidTaskChange(change: TransitionInfo.Change): Boolean {
+        return change.taskInfo != null && change.taskInfo?.taskId != -1
+    }
+
+    companion object {
+        private const val RESIZE_DURATION_MS = 300L
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md
index 99922fb..f9ea1d4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md
@@ -21,11 +21,17 @@
 building to check the log state (is enabled) before printing the print format style log.
 
 **Notes**
-- ProtoLogs currently only work from soong builds (ie. via make/mp). We need to reimplement the
-  tool for use with SysUI-studio
+- ProtoLogs are only fully supported from soong builds (ie. via make/mp). In SysUI-studio it falls
+  back to log via Logcat
 - Non-text ProtoLogs are not currently supported with the Shell library (you can't view them with
   traces in Winscope)
 
+### Kotlin
+
+Protolog tool does not yet have support for Kotlin code (see [b/168581922](https://b.corp.google.com/issues/168581922)).
+For logging in Kotlin, use the [KtProtoLog](frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/util/KtProtoLog.kt)
+class which has a similar API to the Java ProtoLog class.
+
 ### Enabling ProtoLog command line logging
 Run these commands to enable protologs for both WM Core and WM Shell to print to logcat.
 ```shell
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
index be2489d..0bf8ec3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
@@ -16,9 +16,6 @@
 
 package com.android.wm.shell.draganddrop;
 
-import static android.content.ClipDescription.MIMETYPE_APPLICATION_ACTIVITY;
-import static android.content.ClipDescription.MIMETYPE_APPLICATION_SHORTCUT;
-import static android.content.ClipDescription.MIMETYPE_APPLICATION_TASK;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.DragEvent.ACTION_DRAG_ENDED;
 import static android.view.DragEvent.ACTION_DRAG_ENTERED;
@@ -38,6 +35,7 @@
 import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
 import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_DRAG_AND_DROP;
 
+import android.app.ActivityTaskManager;
 import android.content.ClipDescription;
 import android.content.ComponentCallbacks2;
 import android.content.Context;
@@ -205,8 +203,6 @@
         final Context context = mDisplayController.getDisplayContext(displayId)
                 .createWindowContext(TYPE_APPLICATION_OVERLAY, null);
         final WindowManager wm = context.getSystemService(WindowManager.class);
-
-        // TODO(b/169894807): Figure out the right layer for this, needs to be below the task bar
         final WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(
                 ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT,
                 TYPE_APPLICATION_OVERLAY,
@@ -279,15 +275,11 @@
         }
 
         if (event.getAction() == ACTION_DRAG_STARTED) {
-            final boolean hasValidClipData = event.getClipData().getItemCount() > 0
-                    && (description.hasMimeType(MIMETYPE_APPLICATION_ACTIVITY)
-                            || description.hasMimeType(MIMETYPE_APPLICATION_SHORTCUT)
-                            || description.hasMimeType(MIMETYPE_APPLICATION_TASK));
-            pd.isHandlingDrag = hasValidClipData;
+            pd.isHandlingDrag = DragUtils.canHandleDrag(event);
             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
                     "Clip description: handlingDrag=%b itemCount=%d mimeTypes=%s",
                     pd.isHandlingDrag, event.getClipData().getItemCount(),
-                    getMimeTypes(description));
+                    DragUtils.getMimeTypesConcatenated(description));
         }
 
         if (!pd.isHandlingDrag) {
@@ -300,10 +292,13 @@
                     Slog.w(TAG, "Unexpected drag start during an active drag");
                     return false;
                 }
+                // TODO(b/290391688): Also update the session data with task stack changes
                 InstanceId loggerSessionId = mLogger.logStart(event);
                 pd.activeDragCount++;
-                pd.dragLayout.prepare(mDisplayController.getDisplayLayout(displayId),
-                        event.getClipData(), loggerSessionId);
+                pd.dragSession = new DragSession(mContext, ActivityTaskManager.getInstance(),
+                        mDisplayController.getDisplayLayout(displayId), event.getClipData());
+                pd.dragSession.update();
+                pd.dragLayout.prepare(pd.dragSession, loggerSessionId);
                 setDropTargetWindowVisibility(pd, View.VISIBLE);
                 notifyDragStarted();
                 break;
@@ -324,7 +319,7 @@
                 break;
             }
             case ACTION_DRAG_ENDED:
-                // TODO(b/169894807): Ensure sure it's not possible to get ENDED without DROP
+                // TODO(b/290391688): Ensure sure it's not possible to get ENDED without DROP
                 // or EXITED
                 if (pd.dragLayout.hasDropped()) {
                     mLogger.logDrop();
@@ -362,17 +357,6 @@
         pd.setWindowVisibility(visibility);
     }
 
-    private String getMimeTypes(ClipDescription description) {
-        String mimeTypes = "";
-        for (int i = 0; i < description.getMimeTypeCount(); i++) {
-            if (i > 0) {
-                mimeTypes += ", ";
-            }
-            mimeTypes += description.getMimeType(i);
-        }
-        return mimeTypes;
-    }
-
     /**
      * Returns if any displays are currently ready to handle a drag/drop.
      */
@@ -462,6 +446,8 @@
         // A count of the number of active drags in progress to ensure that we only hide the window
         // when all the drag animations have completed
         int activeDragCount;
+        // The active drag session
+        DragSession dragSession;
 
         PerDisplay(int dispId, Context c, WindowManager w, FrameLayout rv, DragLayout dl) {
             displayId = dispId;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
index fb08c87..e70768b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
@@ -21,7 +21,6 @@
 import static android.app.ComponentOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.content.ClipDescription.EXTRA_ACTIVITY_OPTIONS;
 import static android.content.ClipDescription.EXTRA_PENDING_INTENT;
 import static android.content.ClipDescription.MIMETYPE_APPLICATION_SHORTCUT;
@@ -41,16 +40,13 @@
 import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_RIGHT;
 import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_TOP;
 
-import android.app.ActivityManager;
 import android.app.ActivityTaskManager;
 import android.app.PendingIntent;
-import android.app.WindowConfiguration;
 import android.content.ActivityNotFoundException;
 import android.content.ClipData;
 import android.content.ClipDescription;
 import android.content.Context;
 import android.content.Intent;
-import android.content.pm.ActivityInfo;
 import android.content.pm.LauncherApps;
 import android.graphics.Insets;
 import android.graphics.Rect;
@@ -67,14 +63,12 @@
 
 import com.android.internal.logging.InstanceId;
 import com.android.wm.shell.R;
-import com.android.wm.shell.common.DisplayLayout;
 import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
 import com.android.wm.shell.splitscreen.SplitScreenController;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
-import java.util.List;
 
 /**
  * The policy for handling drag and drop operations to shell.
@@ -84,7 +78,6 @@
     private static final String TAG = DragAndDropPolicy.class.getSimpleName();
 
     private final Context mContext;
-    private final ActivityTaskManager mActivityTaskManager;
     private final Starter mStarter;
     private final SplitScreenController mSplitScreen;
     private final ArrayList<DragAndDropPolicy.Target> mTargets = new ArrayList<>();
@@ -94,14 +87,12 @@
     private DragSession mSession;
 
     public DragAndDropPolicy(Context context, SplitScreenController splitScreen) {
-        this(context, ActivityTaskManager.getInstance(), splitScreen, new DefaultStarter(context));
+        this(context, splitScreen, new DefaultStarter(context));
     }
 
     @VisibleForTesting
-    DragAndDropPolicy(Context context, ActivityTaskManager activityTaskManager,
-            SplitScreenController splitScreen, Starter starter) {
+    DragAndDropPolicy(Context context, SplitScreenController splitScreen, Starter starter) {
         mContext = context;
-        mActivityTaskManager = activityTaskManager;
         mSplitScreen = splitScreen;
         mStarter = mSplitScreen != null ? mSplitScreen : starter;
     }
@@ -109,11 +100,9 @@
     /**
      * Starts a new drag session with the given initial drag data.
      */
-    void start(DisplayLayout displayLayout, ClipData data, InstanceId loggerSessionId) {
+    void start(DragSession session, InstanceId loggerSessionId) {
         mLoggerSessionId = loggerSessionId;
-        mSession = new DragSession(mActivityTaskManager, displayLayout, data);
-        // TODO(b/169894807): Also update the session data with task stack changes
-        mSession.update();
+        mSession = session;
         RectF disallowHitRegion = (RectF) mSession.dragData.getExtra(EXTRA_DISALLOW_HIT_REGION);
         if (disallowHitRegion == null) {
             mDisallowHitRegion.setEmpty();
@@ -123,13 +112,6 @@
     }
 
     /**
-     * Returns the last running task.
-     */
-    ActivityManager.RunningTaskInfo getLatestRunningTask() {
-        return mSession.runningTaskInfo;
-    }
-
-    /**
      * Returns the number of targets.
      */
     int getNumTargets() {
@@ -286,49 +268,6 @@
     }
 
     /**
-     * Per-drag session data.
-     */
-    private static class DragSession {
-        private final ActivityTaskManager mActivityTaskManager;
-        private final ClipData mInitialDragData;
-
-        final DisplayLayout displayLayout;
-        Intent dragData;
-        ActivityManager.RunningTaskInfo runningTaskInfo;
-        @WindowConfiguration.WindowingMode
-        int runningTaskWinMode = WINDOWING_MODE_UNDEFINED;
-        @WindowConfiguration.ActivityType
-        int runningTaskActType = ACTIVITY_TYPE_STANDARD;
-        boolean dragItemSupportsSplitscreen;
-
-        DragSession(ActivityTaskManager activityTaskManager,
-                DisplayLayout dispLayout, ClipData data) {
-            mActivityTaskManager = activityTaskManager;
-            mInitialDragData = data;
-            displayLayout = dispLayout;
-        }
-
-        /**
-         * Updates the session data based on the current state of the system.
-         */
-        void update() {
-            List<ActivityManager.RunningTaskInfo> tasks =
-                    mActivityTaskManager.getTasks(1, false /* filterOnlyVisibleRecents */);
-            if (!tasks.isEmpty()) {
-                final ActivityManager.RunningTaskInfo task = tasks.get(0);
-                runningTaskInfo = task;
-                runningTaskWinMode = task.getWindowingMode();
-                runningTaskActType = task.getActivityType();
-            }
-
-            final ActivityInfo info = mInitialDragData.getItemAt(0).getActivityInfo();
-            dragItemSupportsSplitscreen = info == null
-                    || ActivityInfo.isResizeableMode(info.resizeMode);
-            dragData = mInitialDragData.getItemAt(0).getIntent();
-        }
-    }
-
-    /**
      * Interface for actually committing the task launches.
      */
     public interface Starter {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
index fe42822a..205a455 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
@@ -35,7 +35,6 @@
 import android.annotation.SuppressLint;
 import android.app.ActivityManager;
 import android.app.StatusBarManager;
-import android.content.ClipData;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.graphics.Color;
@@ -53,14 +52,13 @@
 import com.android.launcher3.icons.IconProvider;
 import com.android.wm.shell.R;
 import com.android.wm.shell.animation.Interpolators;
-import com.android.wm.shell.common.DisplayLayout;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 import com.android.wm.shell.splitscreen.SplitScreenController;
 
 import java.util.ArrayList;
 
 /**
- * Coordinates the visible drop targets for the current drag.
+ * Coordinates the visible drop targets for the current drag within a single display.
  */
 public class DragLayout extends LinearLayout {
 
@@ -86,6 +84,7 @@
 
     private boolean mIsShowing;
     private boolean mHasDropped;
+    private DragSession mSession;
 
     @SuppressLint("WrongConstant")
     public DragLayout(Context context, SplitScreenController splitScreenController,
@@ -182,16 +181,19 @@
         return mHasDropped;
     }
 
-    public void prepare(DisplayLayout displayLayout, ClipData initialData,
-            InstanceId loggerSessionId) {
-        mPolicy.start(displayLayout, initialData, loggerSessionId);
+    /**
+     * Called when a new drag is started.
+     */
+    public void prepare(DragSession session, InstanceId loggerSessionId) {
+        mPolicy.start(session, loggerSessionId);
+        mSession = session;
         mHasDropped = false;
         mCurrentTarget = null;
 
         boolean alreadyInSplit = mSplitScreenController != null
                 && mSplitScreenController.isSplitScreenVisible();
         if (!alreadyInSplit) {
-            ActivityManager.RunningTaskInfo taskInfo1 = mPolicy.getLatestRunningTask();
+            ActivityManager.RunningTaskInfo taskInfo1 = mSession.runningTaskInfo;
             if (taskInfo1 != null) {
                 final int activityType = taskInfo1.getActivityType();
                 if (activityType == ACTIVITY_TYPE_STANDARD) {
@@ -356,7 +358,16 @@
      */
     public void hide(DragEvent event, Runnable hideCompleteCallback) {
         mIsShowing = false;
-        animateSplitContainers(false, hideCompleteCallback);
+        animateSplitContainers(false, () -> {
+            if (hideCompleteCallback != null) {
+                hideCompleteCallback.run();
+            }
+            switch (event.getAction()) {
+                case DragEvent.ACTION_DROP:
+                case DragEvent.ACTION_DRAG_ENDED:
+                    mSession = null;
+            }
+        });
         // Reset the state if we previously force-ignore the bottom margin
         mDropZoneView1.setForceIgnoreBottomMargin(false);
         mDropZoneView2.setForceIgnoreBottomMargin(false);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java
new file mode 100644
index 0000000..478b6a9
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.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
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.draganddrop;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.content.ClipDescription.EXTRA_PENDING_INTENT;
+import static android.content.ClipDescription.MIMETYPE_APPLICATION_ACTIVITY;
+import static android.content.ClipDescription.MIMETYPE_APPLICATION_SHORTCUT;
+import static android.content.ClipDescription.MIMETYPE_APPLICATION_TASK;
+import static android.content.Intent.EXTRA_USER;
+
+import android.app.ActivityManager;
+import android.app.ActivityTaskManager;
+import android.app.PendingIntent;
+import android.app.WindowConfiguration;
+import android.content.ClipData;
+import android.content.ClipDescription;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.net.Uri;
+import android.os.UserHandle;
+
+import androidx.annotation.IntDef;
+import androidx.annotation.Nullable;
+
+import com.android.wm.shell.common.DisplayLayout;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.List;
+
+/**
+ * Per-drag session data.
+ */
+public class DragSession {
+    private final ActivityTaskManager mActivityTaskManager;
+    private final ClipData mInitialDragData;
+
+    final DisplayLayout displayLayout;
+    Intent dragData;
+    ActivityManager.RunningTaskInfo runningTaskInfo;
+    @WindowConfiguration.WindowingMode
+    int runningTaskWinMode = WINDOWING_MODE_UNDEFINED;
+    @WindowConfiguration.ActivityType
+    int runningTaskActType = ACTIVITY_TYPE_STANDARD;
+    boolean dragItemSupportsSplitscreen;
+
+    DragSession(Context context, ActivityTaskManager activityTaskManager,
+            DisplayLayout dispLayout, ClipData data) {
+        mActivityTaskManager = activityTaskManager;
+        mInitialDragData = data;
+        displayLayout = dispLayout;
+    }
+
+    /**
+     * Updates the session data based on the current state of the system.
+     */
+    void update() {
+        List<ActivityManager.RunningTaskInfo> tasks =
+                mActivityTaskManager.getTasks(1, false /* filterOnlyVisibleRecents */);
+        if (!tasks.isEmpty()) {
+            final ActivityManager.RunningTaskInfo task = tasks.get(0);
+            runningTaskInfo = task;
+            runningTaskWinMode = task.getWindowingMode();
+            runningTaskActType = task.getActivityType();
+        }
+
+        final ActivityInfo info = mInitialDragData.getItemAt(0).getActivityInfo();
+        dragItemSupportsSplitscreen = info == null
+                || ActivityInfo.isResizeableMode(info.resizeMode);
+        dragData = mInitialDragData.getItemAt(0).getIntent();
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragUtils.java
new file mode 100644
index 0000000..7c0883d
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragUtils.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.draganddrop;
+
+import static android.content.ClipDescription.MIMETYPE_APPLICATION_ACTIVITY;
+import static android.content.ClipDescription.MIMETYPE_APPLICATION_SHORTCUT;
+import static android.content.ClipDescription.MIMETYPE_APPLICATION_TASK;
+
+import android.content.ClipDescription;
+import android.view.DragEvent;
+
+/** Collection of utility classes for handling drag and drop. */
+public class DragUtils {
+    private static final String TAG = "DragUtils";
+
+    /**
+     * Returns whether we can handle this particular drag.
+     */
+    public static boolean canHandleDrag(DragEvent event) {
+        return event.getClipData().getItemCount() > 0
+                && (isAppDrag(event.getClipDescription()));
+    }
+
+    /**
+     * Returns whether this clip data description represents an app drag.
+     */
+    public static boolean isAppDrag(ClipDescription description) {
+        return description.hasMimeType(MIMETYPE_APPLICATION_ACTIVITY)
+                || description.hasMimeType(MIMETYPE_APPLICATION_SHORTCUT)
+                || description.hasMimeType(MIMETYPE_APPLICATION_TASK);
+    }
+
+    /**
+     * Returns a list of the mime types provided in the clip description.
+     */
+    public static String getMimeTypesConcatenated(ClipDescription description) {
+        String mimeTypes = "";
+        for (int i = 0; i < description.getMimeTypeCount(); i++) {
+            if (i > 0) {
+                mimeTypes += ", ";
+            }
+            mimeTypes += description.getMimeType(i);
+        }
+        return mimeTypes;
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropOutlineDrawable.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropOutlineDrawable.java
deleted file mode 100644
index 73deea5..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropOutlineDrawable.java
+++ /dev/null
@@ -1,165 +0,0 @@
-/*
- * 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.
- */
-
-package com.android.wm.shell.draganddrop;
-
-import android.animation.ObjectAnimator;
-import android.animation.RectEvaluator;
-import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.ColorFilter;
-import android.graphics.Paint;
-import android.graphics.PixelFormat;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.util.IntProperty;
-import android.util.Property;
-import android.view.animation.Interpolator;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.android.internal.graphics.ColorUtils;
-import com.android.internal.policy.ScreenDecorationsUtils;
-import com.android.internal.protolog.common.ProtoLog;
-import com.android.wm.shell.protolog.ShellProtoLogGroup;
-import com.android.wm.shell.R;
-
-/**
- * Drawable to draw the region that the target will have once it is dropped.
- */
-public class DropOutlineDrawable extends Drawable {
-
-    private static final int BOUNDS_DURATION = 200;
-    private static final int ALPHA_DURATION = 135;
-
-    private final IntProperty<DropOutlineDrawable> ALPHA =
-            new IntProperty<DropOutlineDrawable>("alpha") {
-        @Override
-        public void setValue(DropOutlineDrawable d, int alpha) {
-            d.setAlpha(alpha);
-        }
-
-        @Override
-        public Integer get(DropOutlineDrawable d) {
-            return d.getAlpha();
-        }
-    };
-
-    private final Property<DropOutlineDrawable, Rect> BOUNDS =
-            new Property<DropOutlineDrawable, Rect>(Rect.class, "bounds") {
-        @Override
-        public void set(DropOutlineDrawable d, Rect bounds) {
-            d.setRegionBounds(bounds);
-        }
-
-        @Override
-        public Rect get(DropOutlineDrawable d) {
-            return d.getRegionBounds();
-        }
-    };
-
-    private final RectEvaluator mRectEvaluator = new RectEvaluator(new Rect());
-    private ObjectAnimator mBoundsAnimator;
-    private ObjectAnimator mAlphaAnimator;
-
-    private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
-    private final Rect mBounds = new Rect();
-    private final float mCornerRadius;
-    private final int mMaxAlpha;
-    private int mColor;
-
-    public DropOutlineDrawable(Context context) {
-        super();
-        // TODO(b/169894807): Use corner specific radii and maybe lower radius for non-edge corners
-        mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context);
-        mColor = context.getColor(R.color.drop_outline_background);
-        mMaxAlpha = Color.alpha(mColor);
-        // Initialize as hidden
-        ALPHA.set(this, 0);
-    }
-
-    @Override
-    public void setColorFilter(@Nullable ColorFilter colorFilter) {
-        // Do nothing
-    }
-
-    @Override
-    public void setAlpha(int alpha) {
-        mColor = ColorUtils.setAlphaComponent(mColor, alpha);
-        mPaint.setColor(mColor);
-        invalidateSelf();
-    }
-
-    @Override
-    public int getAlpha() {
-        return Color.alpha(mColor);
-    }
-
-    @Override
-    public int getOpacity() {
-        return PixelFormat.TRANSLUCENT;
-    }
-
-    @Override
-    protected void onBoundsChange(Rect bounds) {
-        invalidateSelf();
-    }
-
-    @Override
-    public void draw(@NonNull Canvas canvas) {
-        canvas.drawRoundRect(mBounds.left, mBounds.top, mBounds.right, mBounds.bottom,
-                mCornerRadius, mCornerRadius, mPaint);
-    }
-
-    public void setRegionBounds(Rect bounds) {
-        mBounds.set(bounds);
-        invalidateSelf();
-    }
-
-    public Rect getRegionBounds() {
-        return mBounds;
-    }
-
-    ObjectAnimator startBoundsAnimation(Rect toBounds, Interpolator interpolator) {
-        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "Animate bounds: from=%s to=%s",
-                mBounds, toBounds);
-        if (mBoundsAnimator != null) {
-            mBoundsAnimator.cancel();
-        }
-        mBoundsAnimator = ObjectAnimator.ofObject(this, BOUNDS, mRectEvaluator,
-                mBounds, toBounds);
-        mBoundsAnimator.setDuration(BOUNDS_DURATION);
-        mBoundsAnimator.setInterpolator(interpolator);
-        mBoundsAnimator.start();
-        return mBoundsAnimator;
-    }
-
-    ObjectAnimator startVisibilityAnimation(boolean visible, Interpolator interpolator) {
-        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "Animate alpha: from=%d to=%d",
-                Color.alpha(mColor), visible ? mMaxAlpha : 0);
-        if (mAlphaAnimator != null) {
-            mAlphaAnimator.cancel();
-        }
-        mAlphaAnimator = ObjectAnimator.ofInt(this, ALPHA, Color.alpha(mColor),
-                visible ? mMaxAlpha : 0);
-        mAlphaAnimator.setDuration(ALPHA_DURATION);
-        mAlphaAnimator.setInterpolator(interpolator);
-        mAlphaAnimator.start();
-        return mAlphaAnimator;
-    }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java
index 04fc79a..55e34fe 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java
@@ -19,9 +19,15 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
 import android.app.ActivityManager;
 import android.app.WindowConfiguration;
+import android.content.Context;
+import android.graphics.Rect;
 import android.os.IBinder;
+import android.util.ArrayMap;
 import android.view.SurfaceControl;
 import android.view.WindowManager;
 import android.window.TransitionInfo;
@@ -31,6 +37,8 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.transition.Transitions;
 import com.android.wm.shell.windowdecor.WindowDecorViewModel;
@@ -39,23 +47,37 @@
 import java.util.List;
 
 /**
- * The {@link Transitions.TransitionHandler} that handles freeform task maximizing and restoring
- * transitions.
+ * The {@link Transitions.TransitionHandler} that handles freeform task maximizing, closing, and
+ * restoring transitions.
  */
 public class FreeformTaskTransitionHandler
         implements Transitions.TransitionHandler, FreeformTaskTransitionStarter {
-
+    private static final int CLOSE_ANIM_DURATION = 400;
+    private final Context mContext;
     private final Transitions mTransitions;
     private final WindowDecorViewModel mWindowDecorViewModel;
+    private final DisplayController mDisplayController;
+    private final ShellExecutor mMainExecutor;
+    private final ShellExecutor mAnimExecutor;
 
     private final List<IBinder> mPendingTransitionTokens = new ArrayList<>();
 
+    private final ArrayMap<IBinder, ArrayList<Animator>> mAnimations = new ArrayMap<>();
+
     public FreeformTaskTransitionHandler(
             ShellInit shellInit,
             Transitions transitions,
-            WindowDecorViewModel windowDecorViewModel) {
+            Context context,
+            WindowDecorViewModel windowDecorViewModel,
+            DisplayController displayController,
+            ShellExecutor mainExecutor,
+            ShellExecutor animExecutor) {
         mTransitions = transitions;
+        mContext = context;
         mWindowDecorViewModel = windowDecorViewModel;
+        mDisplayController = displayController;
+        mMainExecutor = mainExecutor;
+        mAnimExecutor = animExecutor;
         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
             shellInit.addInitCallback(this::onInit, this);
         }
@@ -103,6 +125,14 @@
             @NonNull SurfaceControl.Transaction finishT,
             @NonNull Transitions.TransitionFinishCallback finishCallback) {
         boolean transitionHandled = false;
+        final ArrayList<Animator> animations = new ArrayList<>();
+        final Runnable onAnimFinish = () -> {
+            if (!animations.isEmpty()) return;
+            mMainExecutor.execute(() -> {
+                mAnimations.remove(transition);
+                finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */);
+            });
+        };
         for (TransitionInfo.Change change : info.getChanges()) {
             if ((change.getFlags() & TransitionInfo.FLAG_IS_WALLPAPER) != 0) {
                 continue;
@@ -121,21 +151,45 @@
                 case WindowManager.TRANSIT_TO_BACK:
                     transitionHandled |= startMinimizeTransition(transition);
                     break;
+                case WindowManager.TRANSIT_CLOSE:
+                    if (change.getTaskInfo().getWindowingMode() == WINDOWING_MODE_FREEFORM) {
+                        transitionHandled |= startCloseTransition(transition, change,
+                                finishT, animations, onAnimFinish);
+                    }
+                    break;
             }
         }
-
-        mPendingTransitionTokens.remove(transition);
-
         if (!transitionHandled) {
             return false;
         }
-
+        mAnimations.put(transition, animations);
+        // startT must be applied before animations start.
         startT.apply();
-        mTransitions.getMainExecutor().execute(
-                () -> finishCallback.onTransitionFinished(null, null));
+        mAnimExecutor.execute(() -> {
+            for (Animator anim : animations) {
+                anim.start();
+            }
+        });
+        // Run this here in case no animators are created.
+        onAnimFinish.run();
+        mPendingTransitionTokens.remove(transition);
         return true;
     }
 
+    @Override
+    public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
+            @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
+            @NonNull Transitions.TransitionFinishCallback finishCallback) {
+        ArrayList<Animator> animations = mAnimations.get(mergeTarget);
+        if (animations == null) return;
+        mAnimExecutor.execute(() -> {
+            for (Animator anim : animations) {
+                anim.end();
+            }
+        });
+
+    }
+
     private boolean startChangeTransition(
             IBinder transition,
             int type,
@@ -165,6 +219,36 @@
         return mPendingTransitionTokens.contains(transition);
     }
 
+    private boolean startCloseTransition(IBinder transition, TransitionInfo.Change change,
+            SurfaceControl.Transaction finishT, ArrayList<Animator> animations,
+            Runnable onAnimFinish) {
+        if (!mPendingTransitionTokens.contains(transition)) return false;
+        int screenHeight = mDisplayController
+                .getDisplayLayout(change.getTaskInfo().displayId).height();
+        ValueAnimator animator = new ValueAnimator();
+        animator.setDuration(CLOSE_ANIM_DURATION)
+                .setFloatValues(0f, 1f);
+        SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+        SurfaceControl sc = change.getLeash();
+        finishT.hide(sc);
+        Rect startBounds = new Rect(change.getTaskInfo().configuration.windowConfiguration
+                .getBounds());
+        animator.addUpdateListener(animation -> {
+            t.setPosition(sc, startBounds.left,
+                    startBounds.top + (animation.getAnimatedFraction() * screenHeight));
+            t.apply();
+        });
+        animator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                animations.remove(animator);
+                onAnimFinish.run();
+            }
+        });
+        animations.add(animator);
+        return true;
+    }
+
     @Nullable
     @Override
     public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java
index 4a06d84..8c2879e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java
@@ -383,6 +383,9 @@
         }
 
         @Override
-    public void attachAccessibilityOverlayToWindow(SurfaceControl sc) {}
+        public void attachAccessibilityOverlayToWindow(
+                SurfaceControl sc,
+                int interactionId,
+                IAccessibilityInteractionConnectionCallback callback) {}
 }
 }
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java
index 9729a40..da455f8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java
@@ -33,9 +33,10 @@
 import androidx.annotation.NonNull;
 
 import com.android.wm.shell.R;
-import com.android.wm.shell.bubbles.DismissView;
-import com.android.wm.shell.common.DismissCircleView;
+import com.android.wm.shell.bubbles.DismissViewUtils;
 import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.bubbles.DismissCircleView;
+import com.android.wm.shell.common.bubbles.DismissView;
 import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
 import com.android.wm.shell.pip.PipUiEventLogger;
 
@@ -106,6 +107,7 @@
         }
 
         mTargetViewContainer = new DismissView(mContext);
+        DismissViewUtils.setup(mTargetViewContainer);
         mTargetView = mTargetViewContainer.getCircle();
         mTargetViewContainer.setOnApplyWindowInsetsListener((view, windowInsets) -> {
             if (!windowInsets.equals(mWindowInsets)) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java
index 7971c04..c6e5cf2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java
@@ -123,18 +123,19 @@
             final int totalVerticalPadding = getInsetBounds().top
                     + (getDisplayBounds().height() - getInsetBounds().bottom);
 
-            final int shorterLength = (int) (1f * Math.min(
-                    getDisplayBounds().width() - totalHorizontalPadding,
-                    getDisplayBounds().height() - totalVerticalPadding));
+            final int shorterLength = Math.min(getDisplayBounds().width() - totalHorizontalPadding,
+                    getDisplayBounds().height() - totalVerticalPadding);
 
             int maxWidth, maxHeight;
 
             // use the optimized max sizing logic only within a certain aspect ratio range
             if (aspectRatio >= mOptimizedAspectRatio && aspectRatio <= 1 / mOptimizedAspectRatio) {
                 // this formula and its derivation is explained in b/198643358#comment16
-                maxWidth = (int) (mOptimizedAspectRatio * shorterLength
+                maxWidth = Math.round(mOptimizedAspectRatio * shorterLength
                         + shorterLength * (aspectRatio - mOptimizedAspectRatio) / (1
                         + aspectRatio));
+                // make sure the max width doesn't go beyond shorter screen length after rounding
+                maxWidth = Math.min(maxWidth, shorterLength);
                 maxHeight = Math.round(maxWidth / aspectRatio);
             } else {
                 if (aspectRatio > 1f) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
index 2c4f76b..5000943 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
@@ -591,6 +591,13 @@
             return true;
         }
 
+        // Ignore the motion event When the entry animation is waiting to be started
+        if (!mTouchState.isUserInteracting() && mPipTaskOrganizer.isEntryScheduled()) {
+            ProtoLog.wtf(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                    "%s: Waiting to start the entry animation, skip the motion event.", TAG);
+            return true;
+        }
+
         // Update the touch state
         mTouchState.onTouchEvent(ev);
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipAction.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipAction.java
index 5f6b3fe..fc0b876 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipAction.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipAction.java
@@ -77,6 +77,19 @@
         return mActionType;
     }
 
+    static String getActionTypeString(@ActionType int actionType) {
+        switch (actionType) {
+            case ACTION_FULLSCREEN: return "ACTION_FULLSCREEN";
+            case ACTION_CLOSE: return "ACTION_CLOSE";
+            case ACTION_MOVE: return "ACTION_MOVE";
+            case ACTION_EXPAND_COLLAPSE: return "ACTION_EXPAND_COLLAPSE";
+            case ACTION_CUSTOM: return "ACTION_CUSTOM";
+            case ACTION_CUSTOM_CLOSE: return "ACTION_CUSTOM_CLOSE";
+            default:
+                return "UNDEFINED";
+        }
+    }
+
     abstract void populateButton(@NonNull TvWindowMenuActionButton button, Handler mainHandler);
 
     abstract PendingIntent getPendingIntent();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipActionsProvider.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipActionsProvider.java
index 3b44f10..11c2665 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipActionsProvider.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipActionsProvider.java
@@ -56,8 +56,10 @@
     private final List<Listener> mListeners = new ArrayList<>();
     private final TvPipAction.SystemActionsHandler mSystemActionsHandler;
 
-    private final List<TvPipAction> mActionsList;
+    private final List<TvPipAction> mActionsList = new ArrayList<>();
+    private final TvPipSystemAction mFullscreenAction;
     private final TvPipSystemAction mDefaultCloseAction;
+    private final TvPipSystemAction mMoveAction;
     private final TvPipSystemAction mExpandCollapseAction;
 
     private final List<RemoteAction> mMediaActions = new ArrayList<>();
@@ -67,26 +69,27 @@
             TvPipAction.SystemActionsHandler systemActionsHandler) {
         mSystemActionsHandler = systemActionsHandler;
 
-        mActionsList = new ArrayList<>();
-        mActionsList.add(new TvPipSystemAction(ACTION_FULLSCREEN, R.string.pip_fullscreen,
+        mFullscreenAction = new TvPipSystemAction(ACTION_FULLSCREEN, R.string.pip_fullscreen,
                 R.drawable.pip_ic_fullscreen_white, ACTION_TO_FULLSCREEN, context,
-                mSystemActionsHandler));
-
+                mSystemActionsHandler);
         mDefaultCloseAction = new TvPipSystemAction(ACTION_CLOSE, R.string.pip_close,
                 R.drawable.pip_ic_close_white, ACTION_CLOSE_PIP, context, mSystemActionsHandler);
-        mActionsList.add(mDefaultCloseAction);
-
-        mActionsList.add(new TvPipSystemAction(ACTION_MOVE, R.string.pip_move,
-                R.drawable.pip_ic_move_white, ACTION_MOVE_PIP, context, mSystemActionsHandler));
-
+        mMoveAction = new TvPipSystemAction(ACTION_MOVE, R.string.pip_move,
+                R.drawable.pip_ic_move_white, ACTION_MOVE_PIP, context, mSystemActionsHandler);
         mExpandCollapseAction = new TvPipSystemAction(ACTION_EXPAND_COLLAPSE, R.string.pip_collapse,
                 R.drawable.pip_ic_collapse, ACTION_TOGGLE_EXPANDED_PIP, context,
                 mSystemActionsHandler);
-        mActionsList.add(mExpandCollapseAction);
+        initActions();
 
         pipMediaController.addActionListener(this::onMediaActionsChanged);
     }
 
+    private void initActions() {
+        mActionsList.add(mFullscreenAction);
+        mActionsList.add(mDefaultCloseAction);
+        mActionsList.add(mMoveAction);
+    }
+
     @Override
     public void executeAction(@TvPipAction.ActionType int actionType) {
         if (mSystemActionsHandler != null) {
@@ -199,6 +202,14 @@
         }
     }
 
+    void reset() {
+        mActionsList.clear();
+        mMediaActions.clear();
+        mAppActions.clear();
+
+        initActions();
+    }
+
     List<TvPipAction> getActionsList() {
         return mActionsList;
     }
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 e1737ec..3f3951a 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
@@ -81,6 +81,7 @@
         super(context, pipSizeSpecHandler, pipDisplayLayoutState);
         mContext = context;
         updateDefaultGravity();
+        mTvPipGravity = mDefaultGravity;
         mPreviousCollapsedGravity = mDefaultGravity;
         mIsTvExpandedPipSupported = context.getPackageManager().hasSystemFeature(
                 PackageManager.FEATURE_EXPANDED_PICTURE_IN_PICTURE);
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 02eeb2a..2482acf 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
@@ -478,6 +478,7 @@
         mActionBroadcastReceiver.unregister();
 
         mTvPipMenuController.closeMenu();
+        mTvPipActionsProvider.reset();
         mTvPipBoundsState.resetTvPipState();
         mTvPipBoundsController.reset();
         setState(STATE_NO_PIP);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
index 2a61445..3af1b75 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
@@ -35,7 +35,7 @@
     WM_SHELL_RECENTS_TRANSITION(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true,
             "ShellRecents"),
     WM_SHELL_DRAG_AND_DROP(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true,
-            Consts.TAG_WM_SHELL),
+            "ShellDragAndDrop"),
     WM_SHELL_STARTING_WINDOW(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
             Consts.TAG_WM_STARTING_WINDOW),
     WM_SHELL_BACK_PREVIEW(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true,
@@ -49,7 +49,7 @@
             Consts.TAG_WM_SPLIT_SCREEN),
     WM_SHELL_SYSUI_EVENTS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
             Consts.TAG_WM_SHELL),
-    WM_SHELL_DESKTOP_MODE(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
+    WM_SHELL_DESKTOP_MODE(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true,
             Consts.TAG_WM_SHELL),
     WM_SHELL_FLOATING_APPS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
             Consts.TAG_WM_SHELL),
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
index 843e5af..39b6675 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
@@ -567,15 +567,18 @@
                         && taskInfo.configuration.windowConfiguration.isAlwaysOnTop()) {
                     // Tasks that are always on top (e.g. bubbles), will handle their own transition
                     // as they are on top of everything else. So cancel the merge here.
-                    cancel("task #" + taskInfo.taskId + " is always_on_top");
+                    cancel(false /* toHome */, false /* withScreenshots */,
+                            "task #" + taskInfo.taskId + " is always_on_top");
                     return;
                 }
                 final boolean isRootTask = taskInfo != null
                         && TransitionInfo.isIndependent(change, info);
+                final boolean isRecentsTask = mRecentsTask != null
+                        && mRecentsTask.equals(change.getContainer());
                 hasTaskChange = hasTaskChange || isRootTask;
                 final boolean isLeafTask = leafTaskFilter.test(change);
                 if (TransitionUtil.isOpeningType(change.getMode())) {
-                    if (mRecentsTask != null && mRecentsTask.equals(change.getContainer())) {
+                    if (isRecentsTask) {
                         recentsOpening = change;
                     } else if (isRootTask || isLeafTask) {
                         if (isLeafTask && taskInfo.topActivityType == ACTIVITY_TYPE_HOME) {
@@ -590,7 +593,7 @@
                         openingTaskIsLeafs.add(isLeafTask ? 1 : 0);
                     }
                 } else if (TransitionUtil.isClosingType(change.getMode())) {
-                    if (mRecentsTask != null && mRecentsTask.equals(change.getContainer())) {
+                    if (isRecentsTask) {
                         foundRecentsClosing = true;
                     } else if (isRootTask || isLeafTask) {
                         if (closingTasks == null) {
@@ -611,7 +614,7 @@
                     if (!TransitionUtil.isOrderOnly(change) && isLeafTask) {
                         hasChangingApp = true;
                     } else if (isLeafTask && taskInfo.topActivityType == ACTIVITY_TYPE_HOME
-                            && !mRecentsTask.equals(change.getContainer())) {
+                            && !isRecentsTask ) {
                         // Unless it is a 3p launcher. This means that the 3p launcher was already
                         // visible (eg. the "pausing" task is translucent over the 3p launcher).
                         // Treat it as if we are "re-opening" the 3p launcher.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java
index 89538cb..e52235f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java
@@ -24,6 +24,9 @@
 import com.android.launcher3.icons.IconProvider;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.windowdecor.WindowDecorViewModel;
+
+import java.util.Optional;
 
 /**
  * Main stage for split-screen mode. When split-screen is active all standard activity types launch
@@ -35,9 +38,10 @@
 
     MainStage(Context context, ShellTaskOrganizer taskOrganizer, int displayId,
             StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue,
-            SurfaceSession surfaceSession, IconProvider iconProvider) {
+            SurfaceSession surfaceSession, IconProvider iconProvider,
+            Optional<WindowDecorViewModel> windowDecorViewModel) {
         super(context, taskOrganizer, displayId, callbacks, syncQueue, surfaceSession,
-                iconProvider);
+                iconProvider, windowDecorViewModel);
     }
 
     boolean isActive() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java
index 8639b36..9903113 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java
@@ -25,6 +25,9 @@
 import com.android.launcher3.icons.IconProvider;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.windowdecor.WindowDecorViewModel;
+
+import java.util.Optional;
 
 /**
  * Side stage for split-screen mode. Only tasks that are explicitly pinned to this stage show up
@@ -37,9 +40,10 @@
 
     SideStage(Context context, ShellTaskOrganizer taskOrganizer, int displayId,
             StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue,
-            SurfaceSession surfaceSession, IconProvider iconProvider) {
+            SurfaceSession surfaceSession, IconProvider iconProvider,
+            Optional<WindowDecorViewModel> windowDecorViewModel) {
         super(context, taskOrganizer, displayId, callbacks, syncQueue, surfaceSession,
-                iconProvider);
+                iconProvider, windowDecorViewModel);
     }
 
     boolean removeAllTasks(WindowContainerTransaction wct, boolean toTop) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index e7a367f..af8ef17 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -31,6 +31,7 @@
 import static com.android.wm.shell.common.split.SplitScreenUtils.isValidToSplit;
 import static com.android.wm.shell.common.split.SplitScreenUtils.reverseSplitPosition;
 import static com.android.wm.shell.common.split.SplitScreenUtils.samePackage;
+import static com.android.wm.shell.common.split.SplitScreenUtils.splitFailureMessage;
 import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
 import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_SPLIT_SCREEN;
 import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
@@ -50,6 +51,7 @@
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.util.ArrayMap;
+import android.util.Log;
 import android.util.Slog;
 import android.view.IRemoteAnimationFinishedCallback;
 import android.view.IRemoteAnimationRunner;
@@ -76,6 +78,7 @@
 import com.android.wm.shell.common.DisplayImeController;
 import com.android.wm.shell.common.DisplayInsetsController;
 import com.android.wm.shell.common.ExternalInterfaceBinder;
+import com.android.wm.shell.common.LaunchAdjacentController;
 import com.android.wm.shell.common.RemoteCallable;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SingleInstanceRemoteListener;
@@ -94,6 +97,7 @@
 import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.transition.Transitions;
+import com.android.wm.shell.windowdecor.WindowDecorViewModel;
 
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
@@ -171,6 +175,8 @@
     private final TransactionPool mTransactionPool;
     private final IconProvider mIconProvider;
     private final Optional<RecentTasksController> mRecentTasksOptional;
+    private final LaunchAdjacentController mLaunchAdjacentController;
+    private final Optional<WindowDecorViewModel> mWindowDecorViewModel;
     private final SplitScreenShellCommandHandler mSplitScreenShellCommandHandler;
     private final String[] mAppsSupportMultiInstances;
 
@@ -197,6 +203,8 @@
             TransactionPool transactionPool,
             IconProvider iconProvider,
             Optional<RecentTasksController> recentTasks,
+            LaunchAdjacentController launchAdjacentController,
+            Optional<WindowDecorViewModel> windowDecorViewModel,
             ShellExecutor mainExecutor) {
         mShellCommandHandler = shellCommandHandler;
         mShellController = shellController;
@@ -213,6 +221,8 @@
         mTransactionPool = transactionPool;
         mIconProvider = iconProvider;
         mRecentTasksOptional = recentTasks;
+        mLaunchAdjacentController = launchAdjacentController;
+        mWindowDecorViewModel = windowDecorViewModel;
         mSplitScreenShellCommandHandler = new SplitScreenShellCommandHandler(this);
         // TODO(b/238217847): Temporarily add this check here until we can remove the dynamic
         //                    override for this controller from the base module
@@ -242,6 +252,8 @@
             TransactionPool transactionPool,
             IconProvider iconProvider,
             RecentTasksController recentTasks,
+            LaunchAdjacentController launchAdjacentController,
+            WindowDecorViewModel windowDecorViewModel,
             ShellExecutor mainExecutor,
             StageCoordinator stageCoordinator) {
         mShellCommandHandler = shellCommandHandler;
@@ -259,6 +271,8 @@
         mTransactionPool = transactionPool;
         mIconProvider = iconProvider;
         mRecentTasksOptional = Optional.of(recentTasks);
+        mLaunchAdjacentController = launchAdjacentController;
+        mWindowDecorViewModel = Optional.of(windowDecorViewModel);
         mStageCoordinator = stageCoordinator;
         mSplitScreenShellCommandHandler = new SplitScreenShellCommandHandler(this);
         shellInit.addInitCallback(this::onInit, this);
@@ -291,13 +305,15 @@
             mStageCoordinator = createStageCoordinator();
         }
         mDragAndDropController.ifPresent(controller -> controller.setSplitScreenController(this));
+        mWindowDecorViewModel.ifPresent(viewModel -> viewModel.setSplitScreenController(this));
     }
 
     protected StageCoordinator createStageCoordinator() {
         return new StageCoordinator(mContext, DEFAULT_DISPLAY, mSyncQueue,
                 mTaskOrganizer, mDisplayController, mDisplayImeController,
-                mDisplayInsetsController, mTransitions, mTransactionPool,
-                mIconProvider, mMainExecutor, mRecentTasksOptional);
+                mDisplayInsetsController, mTransitions, mTransactionPool, mIconProvider,
+                mMainExecutor, mRecentTasksOptional, mLaunchAdjacentController,
+                mWindowDecorViewModel);
     }
 
     @Override
@@ -537,6 +553,8 @@
             } else {
                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
                         "Cancel entering split as not supporting multi-instances");
+                Log.w(TAG, splitFailureMessage("startShortcut",
+                        "app package " + packageName + " does not support multi-instance"));
                 Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text,
                         Toast.LENGTH_SHORT).show();
                 return;
@@ -566,6 +584,8 @@
                 taskId = INVALID_TASK_ID;
                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
                         "Cancel entering split as not supporting multi-instances");
+                Log.w(TAG, splitFailureMessage("startShortcutAndTaskWithLegacyTransition",
+                        "app package " + packageName1 + " does not support multi-instance"));
                 Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text,
                         Toast.LENGTH_SHORT).show();
             }
@@ -598,12 +618,14 @@
                 taskId = INVALID_TASK_ID;
                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
                         "Cancel entering split as not supporting multi-instances");
+                Log.w(TAG, splitFailureMessage("startShortcutAndTask",
+                        "app package " + packageName1 + " does not support multi-instance"));
                 Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text,
                         Toast.LENGTH_SHORT).show();
             }
         }
-        mStageCoordinator.startShortcutAndTask(shortcutInfo, options1, taskId, options2,
-                splitPosition, splitRatio, remoteTransition, instanceId);
+        mStageCoordinator.startShortcutAndTask(shortcutInfo, activityOptions.toBundle(), taskId,
+                options2, splitPosition, splitRatio, remoteTransition, instanceId);
     }
 
     /**
@@ -633,6 +655,8 @@
                 taskId = INVALID_TASK_ID;
                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
                         "Cancel entering split as not supporting multi-instances");
+                Log.w(TAG, splitFailureMessage("startIntentAndTaskWithLegacyTransition",
+                        "app package " + packageName1 + " does not support multi-instance"));
                 Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text,
                         Toast.LENGTH_SHORT).show();
             }
@@ -663,6 +687,8 @@
                 taskId = INVALID_TASK_ID;
                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
                         "Cancel entering split as not supporting multi-instances");
+                Log.w(TAG, splitFailureMessage("startIntentAndTask",
+                        "app package " + packageName1 + " does not support multi-instance"));
                 Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text,
                         Toast.LENGTH_SHORT).show();
             }
@@ -691,6 +717,8 @@
                 pendingIntent2 = null;
                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
                         "Cancel entering split as not supporting multi-instances");
+                Log.w(TAG, splitFailureMessage("startIntentsWithLegacyTransition",
+                        "app package " + packageName1 + " does not support multi-instance"));
                 Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text,
                         Toast.LENGTH_SHORT).show();
             }
@@ -720,6 +748,8 @@
                 pendingIntent2 = null;
                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
                         "Cancel entering split as not supporting multi-instances");
+                Log.w(TAG, splitFailureMessage("startIntents",
+                        "app package " + packageName1 + " does not support multi-instance"));
                 Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text,
                         Toast.LENGTH_SHORT).show();
             }
@@ -766,6 +796,8 @@
             } else {
                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
                         "Cancel entering split as not supporting multi-instances");
+                Log.w(TAG, splitFailureMessage("startIntent",
+                        "app package " + packageName1 + " does not support multi-instance"));
                 Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text,
                         Toast.LENGTH_SHORT).show();
                 return;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 7699e2f..7d62f58 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -28,6 +28,7 @@
 import static android.view.RemoteAnimationTarget.MODE_OPENING;
 import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
 import static android.view.WindowManager.TRANSIT_CHANGE;
+import static android.view.WindowManager.TRANSIT_KEYGUARD_OCCLUDE;
 import static android.view.WindowManager.TRANSIT_TO_BACK;
 import static android.view.WindowManager.TRANSIT_TO_FRONT;
 import static android.view.WindowManager.transitTypeToString;
@@ -41,6 +42,7 @@
 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
 import static com.android.wm.shell.common.split.SplitScreenConstants.splitPositionToString;
 import static com.android.wm.shell.common.split.SplitScreenUtils.reverseSplitPosition;
+import static com.android.wm.shell.common.split.SplitScreenUtils.splitFailureMessage;
 import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_MAIN;
 import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_SIDE;
 import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
@@ -121,6 +123,7 @@
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.DisplayImeController;
 import com.android.wm.shell.common.DisplayInsetsController;
+import com.android.wm.shell.common.LaunchAdjacentController;
 import com.android.wm.shell.common.ScreenshotUtils;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
@@ -138,6 +141,7 @@
 import com.android.wm.shell.transition.Transitions;
 import com.android.wm.shell.util.SplitBounds;
 import com.android.wm.shell.util.TransitionUtil;
+import com.android.wm.shell.windowdecor.WindowDecorViewModel;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -193,6 +197,8 @@
     // if user is opening another task(s).
     private final ArrayList<Integer> mPausingTasks = new ArrayList<>();
     private final Optional<RecentTasksController> mRecentTasks;
+    private final LaunchAdjacentController mLaunchAdjacentController;
+    private final Optional<WindowDecorViewModel> mWindowDecorViewModel;
 
     private final Rect mTempRect1 = new Rect();
     private final Rect mTempRect2 = new Rect();
@@ -213,6 +219,8 @@
     private boolean mIsDropEntering;
     private boolean mIsExiting;
     private boolean mIsRootTranslucent;
+    @VisibleForTesting
+    int mTopStageAfterFoldDismiss;
 
     private DefaultMixedHandler mMixedHandler;
     private final Toast mSplitUnsupportedToast;
@@ -270,7 +278,9 @@
             DisplayInsetsController displayInsetsController, Transitions transitions,
             TransactionPool transactionPool,
             IconProvider iconProvider, ShellExecutor mainExecutor,
-            Optional<RecentTasksController> recentTasks) {
+            Optional<RecentTasksController> recentTasks,
+            LaunchAdjacentController launchAdjacentController,
+            Optional<WindowDecorViewModel> windowDecorViewModel) {
         mContext = context;
         mDisplayId = displayId;
         mSyncQueue = syncQueue;
@@ -278,6 +288,8 @@
         mLogger = new SplitscreenEventLogger();
         mMainExecutor = mainExecutor;
         mRecentTasks = recentTasks;
+        mLaunchAdjacentController = launchAdjacentController;
+        mWindowDecorViewModel = windowDecorViewModel;
 
         taskOrganizer.createRootTask(displayId, WINDOWING_MODE_FULLSCREEN, this /* listener */);
 
@@ -288,7 +300,8 @@
                 mMainStageListener,
                 mSyncQueue,
                 mSurfaceSession,
-                iconProvider);
+                iconProvider,
+                mWindowDecorViewModel);
         mSideStage = new SideStage(
                 mContext,
                 mTaskOrganizer,
@@ -296,7 +309,8 @@
                 mSideStageListener,
                 mSyncQueue,
                 mSurfaceSession,
-                iconProvider);
+                iconProvider,
+                mWindowDecorViewModel);
         mDisplayController = displayController;
         mDisplayImeController = displayImeController;
         mDisplayInsetsController = displayInsetsController;
@@ -323,7 +337,9 @@
             DisplayInsetsController displayInsetsController, SplitLayout splitLayout,
             Transitions transitions, TransactionPool transactionPool,
             ShellExecutor mainExecutor,
-            Optional<RecentTasksController> recentTasks) {
+            Optional<RecentTasksController> recentTasks,
+            LaunchAdjacentController launchAdjacentController,
+            Optional<WindowDecorViewModel> windowDecorViewModel) {
         mContext = context;
         mDisplayId = displayId;
         mSyncQueue = syncQueue;
@@ -340,6 +356,8 @@
         mLogger = new SplitscreenEventLogger();
         mMainExecutor = mainExecutor;
         mRecentTasks = recentTasks;
+        mLaunchAdjacentController = launchAdjacentController;
+        mWindowDecorViewModel = windowDecorViewModel;
         mDisplayController.addDisplayWindowListener(this);
         transitions.addHandler(this);
         mSplitUnsupportedToast = Toast.makeText(mContext,
@@ -459,6 +477,8 @@
                 if (isEnteringSplit && mSideStage.getChildCount() == 0) {
                     mMainExecutor.execute(() -> exitSplitScreen(
                             null /* childrenToTop */, EXIT_REASON_UNKNOWN));
+                    Log.w(TAG, splitFailureMessage("startShortcut",
+                            "side stage was not populated"));
                     mSplitUnsupportedToast.show();
                 }
 
@@ -546,6 +566,8 @@
                 if (isEnteringSplit && mSideStage.getChildCount() == 0) {
                     mMainExecutor.execute(() -> exitSplitScreen(
                             null /* childrenToTop */, EXIT_REASON_UNKNOWN));
+                    Log.w(TAG, splitFailureMessage("startIntentLegacy",
+                            "side stage was not populated"));
                     mSplitUnsupportedToast.show();
                 }
 
@@ -718,12 +740,12 @@
             mMainStage.activate(wct, false /* reparent */);
         }
 
+        setSideStagePosition(splitPosition, wct);
         mSplitLayout.setDivideRatio(splitRatio);
         updateWindowBounds(mSplitLayout, wct);
         wct.reorder(mRootTaskInfo.token, true);
         setRootForceTranslucent(false, wct);
 
-        setSideStagePosition(splitPosition, wct);
         options1 = options1 != null ? options1 : new Bundle();
         addActivityOptions(options1, mSideStage);
         if (shortcutInfo1 != null) {
@@ -1076,6 +1098,8 @@
             mMainExecutor.execute(() ->
                     exitSplitScreen(mMainStage.getChildCount() == 0
                             ? mSideStage : mMainStage, EXIT_REASON_UNKNOWN));
+            Log.w(TAG, splitFailureMessage("onRemoteAnimationFinishedOrCancelled",
+                    "main or side stage was not populated."));
             mSplitUnsupportedToast.show();
         } else {
             mSyncQueue.queue(evictWct);
@@ -1095,6 +1119,8 @@
         if (mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0) {
             mMainExecutor.execute(() -> exitSplitScreen(mMainStage.getChildCount() == 0
                     ? mSideStage : mMainStage, EXIT_REASON_UNKNOWN));
+            Log.w(TAG, splitFailureMessage("onRemoteAnimationFinished",
+                    "main or side stage was not populated"));
             mSplitUnsupportedToast.show();
             return;
         }
@@ -1279,20 +1305,30 @@
         final boolean mainStageVisible = mMainStage.mRootTaskInfo.isVisible;
         final boolean oneStageVisible =
                 mMainStage.mRootTaskInfo.isVisible != mSideStage.mRootTaskInfo.isVisible;
-        if (oneStageVisible) {
+        if (oneStageVisible && !ENABLE_SHELL_TRANSITIONS) {
             // Dismiss split because there's show-when-locked activity showing on top of keyguard.
             // Also make sure the task contains show-when-locked activity remains on top after split
             // dismissed.
-            if (!ENABLE_SHELL_TRANSITIONS) {
-                final StageTaskListener toTop = mainStageVisible ? mMainStage : mSideStage;
-                exitSplitScreen(toTop, EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP);
-            } else {
-                final int dismissTop = mainStageVisible ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE;
+            final StageTaskListener toTop = mainStageVisible ? mMainStage : mSideStage;
+            exitSplitScreen(toTop, EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP);
+        }
+
+        // Dismiss split if the flag record any side of stages.
+        if (mTopStageAfterFoldDismiss != STAGE_TYPE_UNDEFINED) {
+            if (ENABLE_SHELL_TRANSITIONS) {
+                // Need manually clear here due to this transition might be aborted due to keyguard
+                // on top and lead to no visible change.
+                clearSplitPairedInRecents(EXIT_REASON_DEVICE_FOLDED);
                 final WindowContainerTransaction wct = new WindowContainerTransaction();
-                prepareExitSplitScreen(dismissTop, wct);
-                mSplitTransitions.startDismissTransition(wct, this, dismissTop,
-                        EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP);
+                prepareExitSplitScreen(mTopStageAfterFoldDismiss, wct);
+                mSplitTransitions.startDismissTransition(wct, this,
+                        mTopStageAfterFoldDismiss, EXIT_REASON_DEVICE_FOLDED);
+            } else {
+                exitSplitScreen(
+                        mTopStageAfterFoldDismiss == STAGE_TYPE_MAIN ? mMainStage : mSideStage,
+                        EXIT_REASON_DEVICE_FOLDED);
             }
+            mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED;
         }
     }
 
@@ -1330,15 +1366,8 @@
         if (!mMainStage.isActive() || mIsExiting) return;
 
         onSplitScreenExit();
+        clearSplitPairedInRecents(exitReason);
 
-        mRecentTasks.ifPresent(recentTasks -> {
-            // Notify recents if we are exiting in a way that breaks the pair, and disable further
-            // updates to splits in the recents until we enter split again
-            if (shouldBreakPairedTaskInRecents(exitReason) && mShouldUpdateRecents) {
-                recentTasks.removeSplitPair(mMainStage.getTopVisibleChildTaskId());
-                recentTasks.removeSplitPair(mSideStage.getTopVisibleChildTaskId());
-            }
-        });
         mShouldUpdateRecents = false;
         mIsDividerRemoteAnimating = false;
         mSplitRequest = null;
@@ -1465,6 +1494,17 @@
         }
     }
 
+    private void clearSplitPairedInRecents(@ExitReason int exitReason) {
+        if (!shouldBreakPairedTaskInRecents(exitReason) || !mShouldUpdateRecents) return;
+
+        mRecentTasks.ifPresent(recentTasks -> {
+            // Notify recents if we are exiting in a way that breaks the pair, and disable further
+            // updates to splits in the recents until we enter split again
+            mMainStage.doForAllChildTasks(taskId -> recentTasks.removeSplitPair(taskId));
+            mSideStage.doForAllChildTasks(taskId -> recentTasks.removeSplitPair(taskId));
+        });
+    }
+
     /**
      * Unlike exitSplitScreen, this takes a stagetype vs an actual stage-reference and populates
      * an existing WindowContainerTransaction (rather than applying immediately). This is intended
@@ -1547,6 +1587,8 @@
             // split bounds.
             wct.setSmallestScreenWidthDp(mMainStage.mRootTaskInfo.token,
                     SMALLEST_SCREEN_WIDTH_DP_UNDEFINED);
+            mSplitLayout.getInvisibleBounds(mTempRect1);
+            mSplitLayout.setTaskBounds(wct, mSideStage.mRootTaskInfo, mTempRect1);
         }
         wct.reorder(mRootTaskInfo.token, true);
         setRootForceTranslucent(false, wct);
@@ -1733,7 +1775,7 @@
         if (mRootTaskInfo == null || mRootTaskInfo.taskId != taskInfo.taskId) {
             throw new IllegalArgumentException(this + "\n Unknown task info changed: " + taskInfo);
         }
-
+        mWindowDecorViewModel.ifPresent(viewModel -> viewModel.onTaskInfoChanged(taskInfo));
         mRootTaskInfo = taskInfo;
         if (mSplitLayout != null
                 && mSplitLayout.updateConfiguration(mRootTaskInfo.configuration)
@@ -1781,7 +1823,6 @@
         wct.reparent(mSideStage.mRootTaskInfo.token, mRootTaskInfo.token, true);
         // Make the stages adjacent to each other so they occlude what's behind them.
         wct.setAdjacentRoots(mMainStage.mRootTaskInfo.token, mSideStage.mRootTaskInfo.token);
-        wct.setLaunchAdjacentFlagRoot(mSideStage.mRootTaskInfo.token);
         setRootForceTranslucent(true, wct);
         mSplitLayout.getInvisibleBounds(mTempRect1);
         wct.setBounds(mSideStage.mRootTaskInfo.token, mTempRect1);
@@ -1789,6 +1830,7 @@
         mSyncQueue.runInSync(t -> {
             t.setPosition(mSideStage.mRootLeash, mTempRect1.left, mTempRect1.top);
         });
+        mLaunchAdjacentController.setLaunchAdjacentRoot(mSideStage.mRootTaskInfo.token);
     }
 
     /** Callback when split roots have child task appeared under it, this is a little different from
@@ -1818,9 +1860,7 @@
 
     private void onRootTaskVanished() {
         final WindowContainerTransaction wct = new WindowContainerTransaction();
-        if (mRootTaskInfo != null) {
-            wct.clearLaunchAdjacentFlagRoot(mRootTaskInfo.token);
-        }
+        mLaunchAdjacentController.clearLaunchAdjacentRoot();
         applyExitSplitScreen(null /* childrenToTop */, wct, EXIT_REASON_ROOT_TASK_VANISHED);
         mDisplayInsetsController.removeInsetsChangedListener(mDisplayId, mSplitLayout);
     }
@@ -2192,7 +2232,11 @@
         }
     }
 
-    void updateSurfaces(SurfaceControl.Transaction transaction) {
+    /**
+     * Update surfaces of the split screen layout based on the current state
+     * @param transaction to write the updates to
+     */
+    public void updateSurfaces(SurfaceControl.Transaction transaction) {
         updateSurfaceBounds(mSplitLayout, transaction, /* applyResizingOffset */ false);
         mSplitLayout.update(transaction);
     }
@@ -2211,26 +2255,18 @@
 
     @VisibleForTesting
     void onFoldedStateChanged(boolean folded) {
-        int topStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED;
+        mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED;
         if (!folded) return;
 
-        if (!mMainStage.isActive()) return;
+        if (!isSplitActive() || !isSplitScreenVisible()) return;
 
+        // To avoid split dismiss when user fold the device and unfold to use later, we only
+        // record the flag here and try to dismiss on wakeUp callback to ensure split dismiss
+        // when user interact on phone folded.
         if (mMainStage.isFocused()) {
-            topStageAfterFoldDismiss = STAGE_TYPE_MAIN;
+            mTopStageAfterFoldDismiss = STAGE_TYPE_MAIN;
         } else if (mSideStage.isFocused()) {
-            topStageAfterFoldDismiss = STAGE_TYPE_SIDE;
-        }
-
-        if (ENABLE_SHELL_TRANSITIONS) {
-            final WindowContainerTransaction wct = new WindowContainerTransaction();
-            prepareExitSplitScreen(topStageAfterFoldDismiss, wct);
-            mSplitTransitions.startDismissTransition(wct, this,
-                    topStageAfterFoldDismiss, EXIT_REASON_DEVICE_FOLDED);
-        } else {
-            exitSplitScreen(
-                    topStageAfterFoldDismiss == STAGE_TYPE_MAIN ? mMainStage : mSideStage,
-                    EXIT_REASON_DEVICE_FOLDED);
+            mTopStageAfterFoldDismiss = STAGE_TYPE_SIDE;
         }
     }
 
@@ -2364,6 +2400,15 @@
                     // so appends operations to exit split.
                     prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, out);
                 }
+            } else if (type == TRANSIT_KEYGUARD_OCCLUDE && triggerTask.topActivity != null
+                    && isSplitScreenVisible()) {
+                // Split include show when lock activity case, check the top activity under which
+                // stage and move it to the top.
+                int top = triggerTask.topActivity.equals(mMainStage.mRootTaskInfo.topActivity)
+                        ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE;
+                prepareExitSplitScreen(top, out);
+                mSplitTransitions.setDismissTransition(transition, top,
+                        EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP);
             }
 
             // When split in the background, it should be only opening/dismissing transition and
@@ -2519,6 +2564,9 @@
                 // so don't handle it.
                 Log.e(TAG, "Somehow removed the last task in a stage outside of a proper "
                         + "transition.");
+                // This new transition would be merged to current one so we need to clear
+                // tile manually here.
+                clearSplitPairedInRecents(EXIT_REASON_APP_FINISHED);
                 final WindowContainerTransaction wct = new WindowContainerTransaction();
                 final int dismissTop = (dismissStages.size() == 1
                         && getStageType(dismissStages.valueAt(0)) == STAGE_TYPE_MAIN)
@@ -2695,12 +2743,19 @@
             }
         } else {
             if (mainChild == null || sideChild == null) {
-                Log.w(TAG, "Launched 2 tasks in split, but didn't receive"
-                        + " 2 tasks in transition. Possibly one of them failed to launch");
                 final int dismissTop = mainChild != null ? STAGE_TYPE_MAIN :
                         (sideChild != null ? STAGE_TYPE_SIDE : STAGE_TYPE_UNDEFINED);
                 mSplitTransitions.mPendingEnter.cancel(
                         (cancelWct, cancelT) -> prepareExitSplitScreen(dismissTop, cancelWct));
+                Log.w(TAG, splitFailureMessage("startPendingEnterAnimation",
+                        "launched 2 tasks in split, but didn't receive "
+                        + "2 tasks in transition. Possibly one of them failed to launch"));
+                if (mRecentTasks.isPresent() && mainChild != null) {
+                    mRecentTasks.get().removeSplitPair(mainChild.getTaskInfo().taskId);
+                }
+                if (mRecentTasks.isPresent() && sideChild != null) {
+                    mRecentTasks.get().removeSplitPair(sideChild.getTaskInfo().taskId);
+                }
                 mSplitUnsupportedToast.show();
                 return true;
             }
@@ -3158,7 +3213,7 @@
         }
 
         @Override
-        public void onNoLongerSupportMultiWindow() {
+        public void onNoLongerSupportMultiWindow(ActivityManager.RunningTaskInfo taskInfo) {
             if (mMainStage.isActive()) {
                 final boolean isMainStage = mMainStageListener == this;
                 if (!ENABLE_SHELL_TRANSITIONS) {
@@ -3173,6 +3228,9 @@
                 prepareExitSplitScreen(stageType, wct);
                 mSplitTransitions.startDismissTransition(wct, StageCoordinator.this, stageType,
                         EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW);
+                Log.w(TAG, splitFailureMessage("onNoLongerSupportMultiWindow",
+                        "app package " + taskInfo.baseActivity.getPackageName()
+                        + " does not support splitscreen, or is a controlled activity type"));
                 mSplitUnsupportedToast.show();
             }
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
index a01eddb..af7bf36 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
@@ -51,8 +51,11 @@
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.split.SplitDecorManager;
 import com.android.wm.shell.splitscreen.SplitScreen.StageType;
+import com.android.wm.shell.windowdecor.WindowDecorViewModel;
 
 import java.io.PrintWriter;
+import java.util.Optional;
+import java.util.function.Consumer;
 import java.util.function.Predicate;
 
 /**
@@ -79,7 +82,7 @@
 
         void onRootTaskVanished();
 
-        void onNoLongerSupportMultiWindow();
+        void onNoLongerSupportMultiWindow(ActivityManager.RunningTaskInfo taskInfo);
     }
 
     private final Context mContext;
@@ -87,6 +90,7 @@
     private final SurfaceSession mSurfaceSession;
     private final SyncTransactionQueue mSyncQueue;
     private final IconProvider mIconProvider;
+    private final Optional<WindowDecorViewModel> mWindowDecorViewModel;
 
     protected ActivityManager.RunningTaskInfo mRootTaskInfo;
     protected SurfaceControl mRootLeash;
@@ -98,12 +102,14 @@
 
     StageTaskListener(Context context, ShellTaskOrganizer taskOrganizer, int displayId,
             StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue,
-            SurfaceSession surfaceSession, IconProvider iconProvider) {
+            SurfaceSession surfaceSession, IconProvider iconProvider,
+            Optional<WindowDecorViewModel> windowDecorViewModel) {
         mContext = context;
         mCallbacks = callbacks;
         mSyncQueue = syncQueue;
         mSurfaceSession = surfaceSession;
         mIconProvider = iconProvider;
+        mWindowDecorViewModel = windowDecorViewModel;
         taskOrganizer.createRootTask(displayId, WINDOWING_MODE_MULTI_WINDOW, this);
     }
 
@@ -202,6 +208,7 @@
     @Override
     @CallSuper
     public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
+        mWindowDecorViewModel.ifPresent(viewModel -> viewModel.onTaskInfoChanged(taskInfo));
         if (mRootTaskInfo.taskId == taskInfo.taskId) {
             // Inflates split decor view only when the root task is visible.
             if (!ENABLE_SHELL_TRANSITIONS && mRootTaskInfo.isVisible != taskInfo.isVisible) {
@@ -220,7 +227,7 @@
                     taskInfo.getWindowingMode())) {
                 // Leave split screen if the task no longer supports multi window or have
                 // uncontrolled task.
-                mCallbacks.onNoLongerSupportMultiWindow();
+                mCallbacks.onNoLongerSupportMultiWindow(taskInfo);
                 return;
             }
             mChildrenTaskInfo.put(taskInfo.taskId, taskInfo);
@@ -341,6 +348,13 @@
         wct.reorder(mChildrenTaskInfo.get(taskId).token, onTop /* onTop */);
     }
 
+    void doForAllChildTasks(Consumer<Integer> consumer) {
+        for (int i = mChildrenTaskInfo.size() - 1; i >= 0; i--) {
+            final ActivityManager.RunningTaskInfo taskInfo = mChildrenTaskInfo.valueAt(i);
+            consumer.accept(taskInfo.taskId);
+        }
+    }
+
     /** Collects all the current child tasks and prepares transaction to evict them to display. */
     void evictAllChildren(WindowContainerTransaction wct) {
         for (int i = mChildrenTaskInfo.size() - 1; i >= 0; i--) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java
index 27d520d..a2301b1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java
@@ -27,6 +27,7 @@
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.DisplayImeController;
 import com.android.wm.shell.common.DisplayInsetsController;
+import com.android.wm.shell.common.LaunchAdjacentController;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.SystemWindows;
@@ -58,6 +59,7 @@
     private final TransactionPool mTransactionPool;
     private final IconProvider mIconProvider;
     private final Optional<RecentTasksController> mRecentTasksOptional;
+    private final LaunchAdjacentController mLaunchAdjacentController;
 
     private final Handler mMainHandler;
     private final SystemWindows mSystemWindows;
@@ -77,13 +79,15 @@
             TransactionPool transactionPool,
             IconProvider iconProvider,
             Optional<RecentTasksController> recentTasks,
+            LaunchAdjacentController launchAdjacentController,
             ShellExecutor mainExecutor,
             Handler mainHandler,
             SystemWindows systemWindows) {
         super(context, shellInit, shellCommandHandler, shellController, shellTaskOrganizer,
                 syncQueue, rootTDAOrganizer, displayController, displayImeController,
                 displayInsetsController, dragAndDropController, transitions, transactionPool,
-                iconProvider, recentTasks, mainExecutor);
+                iconProvider, recentTasks, launchAdjacentController, Optional.empty(),
+                mainExecutor);
 
         mTaskOrganizer = shellTaskOrganizer;
         mSyncQueue = syncQueue;
@@ -96,6 +100,7 @@
         mTransactionPool = transactionPool;
         mIconProvider = iconProvider;
         mRecentTasksOptional = recentTasks;
+        mLaunchAdjacentController = launchAdjacentController;
 
         mMainHandler = mainHandler;
         mSystemWindows = systemWindows;
@@ -111,7 +116,7 @@
                 mTaskOrganizer, mDisplayController, mDisplayImeController,
                 mDisplayInsetsController, mTransitions, mTransactionPool,
                 mIconProvider, mMainExecutor, mMainHandler,
-                mRecentTasksOptional, mSystemWindows);
+                mRecentTasksOptional, mLaunchAdjacentController, mSystemWindows);
     }
 
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvStageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvStageCoordinator.java
index 4d563fb..7947691 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvStageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvStageCoordinator.java
@@ -24,6 +24,7 @@
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.DisplayImeController;
 import com.android.wm.shell.common.DisplayInsetsController;
+import com.android.wm.shell.common.LaunchAdjacentController;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.SystemWindows;
@@ -51,10 +52,11 @@
             IconProvider iconProvider, ShellExecutor mainExecutor,
             Handler mainHandler,
             Optional<RecentTasksController> recentTasks,
+            LaunchAdjacentController launchAdjacentController,
             SystemWindows systemWindows) {
         super(context, displayId, syncQueue, taskOrganizer, displayController, displayImeController,
                 displayInsetsController, transitions, transactionPool, iconProvider,
-                mainExecutor, recentTasks);
+                mainExecutor, recentTasks, launchAdjacentController, Optional.empty());
 
         mTvSplitMenuController = new TvSplitMenuController(context, this,
                 systemWindows, mainHandler);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
index dc91a11..84dcd4d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
@@ -112,28 +112,15 @@
      */
     static final long MAX_ANIMATION_DURATION = MINIMAL_ANIMATION_DURATION + TIME_WINDOW_DURATION;
 
-    // The acceptable area ratio of foreground_icon_area/background_icon_area, if there is an
-    // icon which it's non-transparent foreground area is similar to it's background area, then
-    // do not enlarge the foreground drawable.
-    // For example, an icon with the foreground 108*108 opaque pixels and it's background
-    // also 108*108 pixels, then do not enlarge this icon if only need to show foreground icon.
-    private static final float ENLARGE_FOREGROUND_ICON_THRESHOLD = (72f * 72f) / (108f * 108f);
-
-    /**
-     * If the developer doesn't specify a background for the icon, we slightly scale it up.
-     *
-     * The background is either manually specified in the theme or the Adaptive Icon
-     * background is used if it's different from the window background.
-     */
-    private static final float NO_BACKGROUND_SCALE = 192f / 160;
     private final Context mContext;
     private final HighResIconProvider mHighResIconProvider;
-
     private int mIconSize;
     private int mDefaultIconSize;
     private int mBrandingImageWidth;
     private int mBrandingImageHeight;
     private int mMainWindowShiftLength;
+    private float mEnlargeForegroundIconThreshold;
+    private float mNoBackgroundScale;
     private int mLastPackageContextConfigHash;
     private final TransactionPool mTransactionPool;
     private final SplashScreenWindowAttrs mTmpAttrs = new SplashScreenWindowAttrs();
@@ -336,6 +323,10 @@
                 com.android.wm.shell.R.dimen.starting_surface_brand_image_height);
         mMainWindowShiftLength = mContext.getResources().getDimensionPixelSize(
                 com.android.wm.shell.R.dimen.starting_surface_exit_animation_window_shift_length);
+        mEnlargeForegroundIconThreshold = mContext.getResources().getFloat(
+                com.android.wm.shell.R.dimen.splash_icon_enlarge_foreground_threshold);
+        mNoBackgroundScale = mContext.getResources().getFloat(
+                com.android.wm.shell.R.dimen.splash_icon_no_background_scale_factor);
     }
 
     /**
@@ -604,14 +595,14 @@
                 // There is no background below the icon, so scale the icon up
                 if (mTmpAttrs.mIconBgColor == Color.TRANSPARENT
                         || mTmpAttrs.mIconBgColor == mThemeColor) {
-                    mFinalIconSize *= NO_BACKGROUND_SCALE;
+                    mFinalIconSize *= mNoBackgroundScale;
                 }
                 createIconDrawable(iconDrawable, false /* legacy */, false /* loadInDetail */);
             } else {
                 final float iconScale = (float) mIconSize / (float) mDefaultIconSize;
                 final int densityDpi = mContext.getResources().getConfiguration().densityDpi;
                 final int scaledIconDpi =
-                        (int) (0.5f + iconScale * densityDpi * NO_BACKGROUND_SCALE);
+                        (int) (0.5f + iconScale * densityDpi * mNoBackgroundScale);
                 Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "getIcon");
                 iconDrawable = mHighResIconProvider.getIcon(
                         mActivityInfo, densityDpi, scaledIconDpi);
@@ -693,8 +684,8 @@
                 // Reference AdaptiveIcon description, outer is 108 and inner is 72, so we
                 // scale by 192/160 if we only draw adaptiveIcon's foreground.
                 final float noBgScale =
-                        iconColor.mFgNonTranslucentRatio < ENLARGE_FOREGROUND_ICON_THRESHOLD
-                                ? NO_BACKGROUND_SCALE : 1f;
+                        iconColor.mFgNonTranslucentRatio < mEnlargeForegroundIconThreshold
+                                ? mNoBackgroundScale : 1f;
                 // Using AdaptiveIconDrawable here can help keep the shape consistent with the
                 // current settings.
                 mFinalIconSize = (int) (0.5f + mIconSize * noBgScale);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellSharedConstants.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellSharedConstants.java
index 5f54f58..56c0d0e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellSharedConstants.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellSharedConstants.java
@@ -38,8 +38,6 @@
     public static final String KEY_EXTRA_SHELL_RECENT_TASKS = "extra_shell_recent_tasks";
     // See IBackAnimation.aidl
     public static final String KEY_EXTRA_SHELL_BACK_ANIMATION = "extra_shell_back_animation";
-    // See IFloatingTasks.aidl
-    public static final String KEY_EXTRA_SHELL_FLOATING_TASKS = "extra_shell_floating_tasks";
     // See IDesktopMode.aidl
     public static final String KEY_EXTRA_SHELL_DESKTOP_MODE = "extra_shell_desktop_mode";
     // See IDragAndDrop.aidl
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 4faa929..0d77a2e 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
@@ -26,6 +26,7 @@
 import android.content.Intent;
 import android.content.pm.LauncherApps;
 import android.content.pm.ShortcutInfo;
+import android.graphics.Insets;
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.view.SurfaceControl;
@@ -69,8 +70,10 @@
     private final Rect mTmpRect = new Rect();
     private final Rect mTmpRootRect = new Rect();
     private final int[] mTmpLocation = new int[2];
+    private final Rect mBoundsOnScreen = new Rect();
     private final TaskViewTaskController mTaskViewTaskController;
     private Region mObscuredTouchRegion;
+    private Insets mCaptionInsets;
 
     public TaskView(Context context, TaskViewTaskController taskViewTaskController) {
         super(context, null, 0, 0, true /* disableBackgroundLayer */);
@@ -169,6 +172,25 @@
     }
 
     /**
+     * Sets a region of the task to inset to allow for a caption bar. Currently only top insets
+     * are supported.
+     * <p>
+     * This region will be factored in as an area of taskview that is not touchable activity
+     * content (i.e. you don't need to additionally set {@link #setObscuredTouchRect(Rect)} for
+     * the caption area).
+     *
+     * @param captionInsets the insets to apply to task view.
+     */
+    public void setCaptionInsets(Insets captionInsets) {
+        mCaptionInsets = captionInsets;
+        if (captionInsets == null) {
+            // If captions are null we can set them now; otherwise they'll get set in
+            // onComputeInternalInsets.
+            mTaskViewTaskController.setCaptionInsets(null);
+        }
+    }
+
+    /**
      * Call when view position or size has changed. Do not call when animating.
      */
     public void onLocationChanged() {
@@ -230,6 +252,15 @@
         getLocationInWindow(mTmpLocation);
         mTmpRect.set(mTmpLocation[0], mTmpLocation[1],
                 mTmpLocation[0] + getWidth(), mTmpLocation[1] + getHeight());
+        if (mCaptionInsets != null) {
+            mTmpRect.inset(mCaptionInsets);
+            getBoundsOnScreen(mBoundsOnScreen);
+            mTaskViewTaskController.setCaptionInsets(new Rect(
+                    mBoundsOnScreen.left,
+                    mBoundsOnScreen.top,
+                    mBoundsOnScreen.right + getWidth(),
+                    mBoundsOnScreen.top + mCaptionInsets.top));
+        }
         inoutInfo.touchableRegion.op(mTmpRect, Region.Op.DIFFERENCE);
 
         if (mObscuredTouchRegion != null) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
index 163cf50..064af04 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
@@ -33,6 +33,7 @@
 import android.util.CloseGuard;
 import android.util.Slog;
 import android.view.SurfaceControl;
+import android.view.WindowInsets;
 import android.window.WindowContainerToken;
 import android.window.WindowContainerTransaction;
 
@@ -82,6 +83,10 @@
     private TaskView.Listener mListener;
     private Executor mListenerExecutor;
 
+    /** Used to inset the activity content to allow space for a caption bar. */
+    private final Binder mCaptionInsetsOwner = new Binder();
+    private Rect mCaptionInsets;
+
     public TaskViewTaskController(Context context, ShellTaskOrganizer organizer,
             TaskViewTransitions taskViewTransitions, SyncTransactionQueue syncQueue) {
         mContext = context;
@@ -436,6 +441,32 @@
         mTaskViewTransitions.closeTaskView(wct, this);
     }
 
+    /**
+     * Sets a region of the task to inset to allow for a caption bar.
+     *
+     * @param captionInsets the rect for the insets in screen coordinates.
+     */
+    void setCaptionInsets(Rect captionInsets) {
+        if (mCaptionInsets != null && mCaptionInsets.equals(captionInsets)) {
+            return;
+        }
+        mCaptionInsets = captionInsets;
+        applyCaptionInsetsIfNeeded();
+    }
+
+    void applyCaptionInsetsIfNeeded() {
+        if (mTaskToken == null) return;
+        WindowContainerTransaction wct = new WindowContainerTransaction();
+        if (mCaptionInsets != null) {
+            wct.addInsetsSource(mTaskToken, mCaptionInsetsOwner, 0,
+                    WindowInsets.Type.captionBar(), mCaptionInsets);
+        } else {
+            wct.removeInsetsSource(mTaskToken, mCaptionInsetsOwner, 0,
+                    WindowInsets.Type.captionBar());
+        }
+        mTaskOrganizer.applyTransaction(wct);
+    }
+
     /** Should be called when the client surface is destroyed. */
     public void surfaceDestroyed() {
         mSurfaceCreated = false;
@@ -564,6 +595,7 @@
             mTaskViewTransitions.updateBoundsState(this, boundsOnScreen);
             mTaskViewTransitions.updateVisibilityState(this, true /* visible */);
             wct.setBounds(mTaskToken, boundsOnScreen);
+            applyCaptionInsetsIfNeeded();
         } else {
             // The surface has already been destroyed before the task has appeared,
             // so go ahead and hide the task entirely
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
index 5baf2e3..daf8be6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
@@ -165,19 +165,6 @@
         return null;
     }
 
-    /**
-     * Returns all the pending transitions for a given `taskView`.
-     * @param taskView the pending transition should be for this.
-     */
-    ArrayList<PendingTransition> findAllPending(TaskViewTaskController taskView) {
-        ArrayList<PendingTransition> list = new ArrayList<>();
-        for (int i = mPending.size() - 1; i >= 0; --i) {
-            if (mPending.get(i).mTaskView != taskView) continue;
-            list.add(mPending.get(i));
-        }
-        return list;
-    }
-
     private PendingTransition findPending(IBinder claimed) {
         for (int i = 0; i < mPending.size(); ++i) {
             if (mPending.get(i).mClaimed != claimed) continue;
@@ -202,15 +189,10 @@
         if (taskView == null) return null;
         // Opening types should all be initiated by shell
         if (!TransitionUtil.isClosingType(request.getType())) return null;
-        PendingTransition pending = findPendingCloseTransition(taskView);
-        if (pending == null) {
-            pending = new PendingTransition(request.getType(), null, taskView, null /* cookie */);
-        }
-        if (pending.mClaimed != null) {
-            throw new IllegalStateException("Task is closing in 2 collecting transitions?"
-                    + " This state doesn't make sense");
-        }
+        PendingTransition pending = new PendingTransition(request.getType(), null,
+                taskView, null /* cookie */);
         pending.mClaimed = transition;
+        mPending.add(pending);
         return new WindowContainerTransaction();
     }
 
@@ -278,10 +260,9 @@
             // Task view isn't visible, the bounds will next visibility update.
             return;
         }
-        PendingTransition pendingOpen = findPendingOpeningTransition(taskView);
-        if (pendingOpen != null) {
-            // There is already an opening transition in-flight, the window bounds will be
-            // set in prepareOpenAnimation (via the window crop) if needed.
+        if (hasPending()) {
+            // There is already a transition in-flight, the window bounds will be set in
+            // prepareOpenAnimation.
             return;
         }
         WindowContainerTransaction wct = new WindowContainerTransaction();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
index d0a361a..c5c22de 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
@@ -44,6 +44,9 @@
 
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.wm.shell.common.split.SplitScreenUtils;
+import com.android.wm.shell.desktopmode.DesktopModeController;
+import com.android.wm.shell.desktopmode.DesktopModeStatus;
+import com.android.wm.shell.desktopmode.DesktopTasksController;
 import com.android.wm.shell.keyguard.KeyguardTransitionHandler;
 import com.android.wm.shell.pip.PipTransitionController;
 import com.android.wm.shell.pip.phone.PipTouchHandler;
@@ -70,6 +73,8 @@
     private RecentsTransitionHandler mRecentsHandler;
     private StageCoordinator mSplitHandler;
     private final KeyguardTransitionHandler mKeyguardHandler;
+    private DesktopModeController mDesktopModeController;
+    private DesktopTasksController mDesktopTasksController;
     private UnfoldTransitionHandler mUnfoldHandler;
 
     private static class MixedTransition {
@@ -87,8 +92,11 @@
         /** Keyguard exit/occlude/unocclude transition. */
         static final int TYPE_KEYGUARD = 5;
 
+        /** Recents Transition while in desktop mode. */
+        static final int TYPE_RECENTS_DURING_DESKTOP = 6;
+
         /** Fuld/Unfold transition. */
-        static final int TYPE_UNFOLD = 6;
+        static final int TYPE_UNFOLD = 7;
 
         /** The default animation for this mixed transition. */
         static final int ANIM_TYPE_DEFAULT = 0;
@@ -142,6 +150,8 @@
             Optional<PipTouchHandler> pipTouchHandlerOptional,
             Optional<RecentsTransitionHandler> recentsHandlerOptional,
             KeyguardTransitionHandler keyguardHandler,
+            Optional<DesktopModeController> desktopModeControllerOptional,
+            Optional<DesktopTasksController> desktopTasksControllerOptional,
             Optional<UnfoldTransitionHandler> unfoldHandler) {
         mPlayer = player;
         mKeyguardHandler = keyguardHandler;
@@ -159,6 +169,8 @@
                 if (mRecentsHandler != null) {
                     mRecentsHandler.addMixer(this);
                 }
+                mDesktopModeController = desktopModeControllerOptional.orElse(null);
+                mDesktopTasksController = desktopTasksControllerOptional.orElse(null);
                 mUnfoldHandler = unfoldHandler.orElse(null);
             }, this);
         }
@@ -239,7 +251,8 @@
 
     @Override
     public Transitions.TransitionHandler handleRecentsRequest(WindowContainerTransaction outWCT) {
-        if (mRecentsHandler != null && mSplitHandler.isSplitScreenVisible()) {
+        if (mRecentsHandler != null && (mSplitHandler.isSplitScreenVisible()
+                || DesktopModeStatus.isActive(mPlayer.getContext()))) {
             return this;
         }
         return null;
@@ -254,6 +267,13 @@
                     MixedTransition.TYPE_RECENTS_DURING_SPLIT, transition);
             mixed.mLeftoversHandler = mRecentsHandler;
             mActiveTransitions.add(mixed);
+        } else if (DesktopModeStatus.isActive(mPlayer.getContext())) {
+            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Got a recents request while "
+                    + "desktop mode is active, so treat it as Mixed.");
+            final MixedTransition mixed = new MixedTransition(
+                    MixedTransition.TYPE_RECENTS_DURING_DESKTOP, transition);
+            mixed.mLeftoversHandler = mRecentsHandler;
+            mActiveTransitions.add(mixed);
         } else {
             throw new IllegalStateException("Accepted a recents transition but don't know how to"
                     + " handle it");
@@ -340,6 +360,9 @@
         } else if (mixed.mType == MixedTransition.TYPE_KEYGUARD) {
             return animateKeyguard(mixed, info, startTransaction, finishTransaction,
                     finishCallback);
+        } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_DESKTOP) {
+            return animateRecentsDuringDesktop(mixed, info, startTransaction, finishTransaction,
+                    finishCallback);
         } else if (mixed.mType == MixedTransition.TYPE_UNFOLD) {
             return animateUnfold(mixed, info, startTransaction, finishTransaction, finishCallback);
         } else {
@@ -641,6 +664,30 @@
         return true;
     }
 
+    private boolean animateRecentsDuringDesktop(@NonNull final MixedTransition mixed,
+            @NonNull TransitionInfo info,
+            @NonNull SurfaceControl.Transaction startTransaction,
+            @NonNull SurfaceControl.Transaction finishTransaction,
+            @NonNull Transitions.TransitionFinishCallback finishCallback) {
+        boolean consumed = mRecentsHandler.startAnimation(
+                mixed.mTransition, info, startTransaction, finishTransaction, finishCallback);
+        if (!consumed) {
+            return false;
+        }
+        //Sync desktop mode state (proto 1)
+        if (mDesktopModeController != null) {
+            mDesktopModeController.syncSurfaceState(info, finishTransaction);
+            return true;
+        }
+        //Sync desktop mode state (proto 2)
+        if (mDesktopTasksController != null) {
+            mDesktopTasksController.syncSurfaceState(info, finishTransaction);
+            return true;
+        }
+
+        return false;
+    }
+
     private boolean animateUnfold(@NonNull final MixedTransition mixed,
             @NonNull TransitionInfo info,
             @NonNull SurfaceControl.Transaction startTransaction,
@@ -657,6 +704,9 @@
         if (mPipHandler != null) {
             mPipHandler.syncPipSurfaceState(info, startTransaction, finishTransaction);
         }
+        if (mSplitHandler != null && mSplitHandler.isSplitActive()) {
+            mSplitHandler.updateSurfaces(startTransaction);
+        }
         return mUnfoldHandler.startAnimation(
                 mixed.mTransition, info, startTransaction, finishTransaction, finishCB);
     }
@@ -727,6 +777,9 @@
                         finishCallback);
             } else if (mixed.mType == MixedTransition.TYPE_KEYGUARD) {
                 mKeyguardHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
+            } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_DESKTOP) {
+                mixed.mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget,
+                        finishCallback);
             } else if (mixed.mType == MixedTransition.TYPE_UNFOLD) {
                 mUnfoldHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
             } else {
@@ -754,6 +807,8 @@
             mixed.mLeftoversHandler.onTransitionConsumed(transition, aborted, finishT);
         } else if (mixed.mType == MixedTransition.TYPE_KEYGUARD) {
             mKeyguardHandler.onTransitionConsumed(transition, aborted, finishT);
+        } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_DESKTOP) {
+            mixed.mLeftoversHandler.onTransitionConsumed(transition, aborted, finishT);
         } else if (mixed.mType == MixedTransition.TYPE_UNFOLD) {
             mUnfoldHandler.onTransitionConsumed(transition, aborted, finishT);
         }
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 2327d86..4ca383f 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
@@ -27,6 +27,7 @@
 import static android.view.WindowManager.TRANSIT_TO_BACK;
 import static android.view.WindowManager.TRANSIT_TO_FRONT;
 import static android.view.WindowManager.fixScale;
+import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW;
 import static android.window.TransitionInfo.FLAG_IS_OCCLUDED;
 import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
 import static android.window.TransitionInfo.FLAG_MOVED_TO_TOP;
@@ -148,11 +149,13 @@
     /** Transition type for maximize to freeform transition. */
     public static final int TRANSIT_RESTORE_FROM_MAXIMIZE = WindowManager.TRANSIT_FIRST_CUSTOM + 9;
 
-    /** Transition type to freeform in desktop mode. */
-    public static final int TRANSIT_ENTER_FREEFORM = WindowManager.TRANSIT_FIRST_CUSTOM + 10;
+    /** Transition type for starting the move to desktop mode. */
+    public static final int TRANSIT_START_MOVE_TO_DESKTOP_MODE =
+            WindowManager.TRANSIT_FIRST_CUSTOM + 10;
 
-    /** Transition type to freeform in desktop mode. */
-    public static final int TRANSIT_ENTER_DESKTOP_MODE = WindowManager.TRANSIT_FIRST_CUSTOM + 11;
+    /** Transition type for finalizing the move to desktop mode. */
+    public static final int TRANSIT_FINALIZE_MOVE_TO_DESKTOP_MODE =
+            WindowManager.TRANSIT_FIRST_CUSTOM + 11;
 
     /** Transition type to fullscreen from desktop mode. */
     public static final int TRANSIT_EXIT_DESKTOP_MODE = WindowManager.TRANSIT_FIRST_CUSTOM + 12;
@@ -161,6 +164,10 @@
     public static final int TRANSIT_CANCEL_ENTERING_DESKTOP_MODE =
             WindowManager.TRANSIT_FIRST_CUSTOM + 13;
 
+    /** Transition type to animate the toggle resize between the max and default desktop sizes. */
+    public static final int TRANSIT_DESKTOP_MODE_TOGGLE_RESIZE =
+            WindowManager.TRANSIT_FIRST_CUSTOM + 14;
+
     private final WindowOrganizer mOrganizer;
     private final Context mContext;
     private final ShellExecutor mMainExecutor;
@@ -724,11 +731,15 @@
         final int changeSize = info.getChanges().size();
         boolean taskChange = false;
         boolean transferStartingWindow = false;
+        int noAnimationBehindStartingWindow = 0;
         boolean allOccluded = changeSize > 0;
         for (int i = changeSize - 1; i >= 0; --i) {
             final TransitionInfo.Change change = info.getChanges().get(i);
             taskChange |= change.getTaskInfo() != null;
             transferStartingWindow |= change.hasFlags(FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT);
+            if (change.hasAllFlags(FLAG_IS_BEHIND_STARTING_WINDOW | FLAG_NO_ANIMATION)) {
+                noAnimationBehindStartingWindow++;
+            }
             if (!change.hasFlags(FLAG_IS_OCCLUDED)) {
                 allOccluded = false;
             }
@@ -736,9 +747,11 @@
         // There does not need animation when:
         // A. Transfer starting window. Apply transfer starting window directly if there is no other
         // task change. Since this is an activity->activity situation, we can detect it by selecting
-        // transitions with only 2 changes where neither are tasks and one is a starting-window
-        // recipient.
-        if (!taskChange && transferStartingWindow && changeSize == 2
+        // transitions with only 2 changes where
+        // 1. neither are tasks, and
+        // 2. one is a starting-window recipient, or all change is behind starting window.
+        if (!taskChange && (transferStartingWindow || noAnimationBehindStartingWindow == changeSize)
+                && changeSize == 2
                 // B. It's visibility change if the TRANSIT_TO_BACK/TO_FRONT happened when all
                 // changes are underneath another change.
                 || ((info.getType() == TRANSIT_TO_BACK || info.getType() == TRANSIT_TO_FRONT)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/FullscreenUnfoldTaskAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/FullscreenUnfoldTaskAnimator.java
index f81fc6f..6bba0d1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/FullscreenUnfoldTaskAnimator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/FullscreenUnfoldTaskAnimator.java
@@ -110,7 +110,7 @@
         for (int i = state.sourceSize() - 1; i >= 0; i--) {
             final InsetsSource source = state.sourceAt(i);
             if (source.getType() == WindowInsets.Type.navigationBars()
-                    && source.insetsRoundedCornerFrame()) {
+                    && source.hasFlags(InsetsSource.FLAG_INSETS_ROUNDED_CORNER)) {
                 return source;
             }
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java
index a4cf149..bb5d546 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java
@@ -50,11 +50,11 @@
 import com.android.wm.shell.unfold.UnfoldAnimationController;
 import com.android.wm.shell.unfold.UnfoldBackgroundController;
 
+import dagger.Lazy;
+
 import java.util.Optional;
 import java.util.concurrent.Executor;
 
-import dagger.Lazy;
-
 /**
  * This helper class contains logic that calculates scaling and cropping parameters
  * for the folding/unfolding animation. As an input it receives TaskInfo objects and
@@ -149,7 +149,7 @@
         for (int i = state.sourceSize() - 1; i >= 0; i--) {
             final InsetsSource source = state.sourceAt(i);
             if (source.getType() == WindowInsets.Type.navigationBars()
-                    && source.insetsRoundedCornerFrame()) {
+                    && source.hasFlags(InsetsSource.FLAG_INSETS_ROUNDED_CORNER)) {
                 return source;
             }
         }
@@ -270,7 +270,6 @@
     @Override
     public void prepareStartTransaction(Transaction transaction) {
         mUnfoldBackgroundController.ensureBackground(transaction);
-        mSplitScreenController.get().get().updateSplitScreenSurfaces(transaction);
     }
 
     @Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index 39fb793..cf16920 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -33,11 +33,14 @@
 import android.window.WindowContainerToken;
 import android.window.WindowContainerTransaction;
 
+import androidx.annotation.Nullable;
+
 import com.android.wm.shell.R;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
+import com.android.wm.shell.splitscreen.SplitScreenController;
 import com.android.wm.shell.transition.Transitions;
 
 /**
@@ -89,6 +92,9 @@
     }
 
     @Override
+    public void setSplitScreenController(SplitScreenController splitScreenController) {}
+
+    @Override
     public boolean onTaskOpening(
             RunningTaskInfo taskInfo,
             SurfaceControl taskSurface,
@@ -187,7 +193,7 @@
 
         final DragPositioningCallback dragPositioningCallback =
                 new FluidResizeTaskPositioner(mTaskOrganizer, windowDecoration, mDisplayController,
-                        null /* disallowedAreaForEndBounds */);
+                        0 /* disallowedAreaForEndBoundsHeight */);
         final CaptionTouchEventListener touchEventListener =
                 new CaptionTouchEventListener(taskInfo, dragPositioningCallback);
         windowDecoration.setCaptionListeners(touchEventListener, touchEventListener);
@@ -254,7 +260,7 @@
          * @return {@code true} if a drag is happening; or {@code false} if it is not
          */
         @Override
-        public boolean handleMotionEvent(MotionEvent e) {
+        public boolean handleMotionEvent(@Nullable View v, MotionEvent e) {
             final RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
             if (taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
                 return false;
@@ -268,7 +274,10 @@
                     return false;
                 }
                 case MotionEvent.ACTION_MOVE: {
-                    int dragPointerIdx = e.findPointerIndex(mDragPointerId);
+                    if (e.findPointerIndex(mDragPointerId) == -1) {
+                        mDragPointerId = e.getPointerId(0);
+                    }
+                    final int dragPointerIdx = e.findPointerIndex(mDragPointerId);
                     mDragPositioningCallback.onDragPositioningMove(
                             e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
                     mIsDragging = true;
@@ -276,7 +285,10 @@
                 }
                 case MotionEvent.ACTION_UP:
                 case MotionEvent.ACTION_CANCEL: {
-                    int dragPointerIdx = e.findPointerIndex(mDragPointerId);
+                    if (e.findPointerIndex(mDragPointerId) == -1) {
+                        mDragPointerId = e.getPointerId(0);
+                    }
+                    final int dragPointerIdx = e.findPointerIndex(mDragPointerId);
                     mDragPositioningCallback.onDragPositioningEnd(
                             e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
                     final boolean wasDragging = mIsDragging;
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 116af70..ce81910 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
@@ -113,7 +113,7 @@
         mRelayoutParams.reset();
         mRelayoutParams.mRunningTaskInfo = taskInfo;
         mRelayoutParams.mLayoutResId = R.layout.caption_window_decor;
-        mRelayoutParams.mCaptionHeightId = R.dimen.freeform_decor_caption_height;
+        mRelayoutParams.mCaptionHeightId = getCaptionHeightId();
         mRelayoutParams.mShadowRadiusId = shadowRadiusID;
         mRelayoutParams.mApplyStartTransactionOnDraw = applyStartTransactionOnDraw;
 
@@ -143,8 +143,11 @@
                     mHandler,
                     mChoreographer,
                     mDisplay.getDisplayId(),
+                    0 /* taskCornerRadius */,
                     mDecorationContainerSurface,
-                    mDragPositioningCallback);
+                    mDragPositioningCallback,
+                    mSurfaceControlBuilderSupplier,
+                    mSurfaceControlTransactionSupplier);
         }
 
         final int touchSlop = ViewConfiguration.get(mResult.mRootView.getContext())
@@ -221,4 +224,9 @@
         closeDragResizeListener();
         super.close();
     }
+
+    @Override
+    int getCaptionHeightId() {
+        return R.dimen.freeform_decor_caption_height;
+    }
 }
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 9fd57d7..80cf96a 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
@@ -19,12 +19,14 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 
 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
-import static com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler.DRAG_FREEFORM_SCALE;
 import static com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler.FINAL_FREEFORM_SCALE;
 import static com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler.FREEFORM_ANIMATION_DURATION;
+import static com.android.wm.shell.windowdecor.MoveToDesktopAnimator.DRAG_FREEFORM_SCALE;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -42,6 +44,7 @@
 import android.os.Looper;
 import android.util.SparseArray;
 import android.view.Choreographer;
+import android.view.GestureDetector;
 import android.view.InputChannel;
 import android.view.InputEvent;
 import android.view.InputEventReceiver;
@@ -53,6 +56,7 @@
 import android.view.WindowManager;
 import android.window.TransitionInfo;
 import android.window.WindowContainerToken;
+import android.window.WindowContainerTransaction;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -106,11 +110,10 @@
     private final Supplier<SurfaceControl.Transaction> mTransactionFactory;
     private final Transitions mTransitions;
 
-    private Optional<SplitScreenController> mSplitScreenController;
+    private SplitScreenController mSplitScreenController;
 
-    private ValueAnimator mDragToDesktopValueAnimator;
+    private MoveToDesktopAnimator mMoveToDesktopAnimator;
     private final Rect mDragToDesktopAnimationStartBounds = new Rect();
-    private boolean mDragToDesktopAnimationStarted;
 
     public DesktopModeWindowDecorViewModel(
             Context context,
@@ -121,8 +124,7 @@
             SyncTransactionQueue syncQueue,
             Transitions transitions,
             Optional<DesktopModeController> desktopModeController,
-            Optional<DesktopTasksController> desktopTasksController,
-            Optional<SplitScreenController> splitScreenController) {
+            Optional<DesktopTasksController> desktopTasksController) {
         this(
                 context,
                 mainHandler,
@@ -133,7 +135,6 @@
                 transitions,
                 desktopModeController,
                 desktopTasksController,
-                splitScreenController,
                 new DesktopModeWindowDecoration.Factory(),
                 new InputMonitorFactory(),
                 SurfaceControl.Transaction::new);
@@ -150,7 +151,6 @@
             Transitions transitions,
             Optional<DesktopModeController> desktopModeController,
             Optional<DesktopTasksController> desktopTasksController,
-            Optional<SplitScreenController> splitScreenController,
             DesktopModeWindowDecoration.Factory desktopModeWindowDecorFactory,
             InputMonitorFactory inputMonitorFactory,
             Supplier<SurfaceControl.Transaction> transactionFactory) {
@@ -160,7 +160,6 @@
         mActivityTaskManager = mContext.getSystemService(ActivityTaskManager.class);
         mTaskOrganizer = taskOrganizer;
         mDisplayController = displayController;
-        mSplitScreenController = splitScreenController;
         mSyncQueue = syncQueue;
         mTransitions = transitions;
         mDesktopModeController = desktopModeController;
@@ -177,6 +176,11 @@
     }
 
     @Override
+    public void setSplitScreenController(SplitScreenController splitScreenController) {
+        mSplitScreenController = splitScreenController;
+    }
+
+    @Override
     public boolean onTaskOpening(
             ActivityManager.RunningTaskInfo taskInfo,
             SurfaceControl taskSurface,
@@ -193,7 +197,10 @@
             @NonNull TransitionInfo info,
             @NonNull TransitionInfo.Change change) {
         if (change.getMode() == WindowManager.TRANSIT_CHANGE
-                && (info.getType() == Transitions.TRANSIT_ENTER_DESKTOP_MODE)) {
+                && (info.getType() == Transitions.TRANSIT_FINALIZE_MOVE_TO_DESKTOP_MODE
+                || info.getType() == Transitions.TRANSIT_CANCEL_ENTERING_DESKTOP_MODE
+                || info.getType() == Transitions.TRANSIT_EXIT_DESKTOP_MODE
+                || info.getType() == Transitions.TRANSIT_DESKTOP_MODE_TOGGLE_RESIZE)) {
             mWindowDecorByTaskId.get(change.getTaskInfo().taskId)
                     .addTransitionPausingRelayout(transition);
         }
@@ -225,7 +232,6 @@
             removeTaskFromEventReceiver(oldTaskInfo.displayId);
             incrementEventReceiverTasks(taskInfo.displayId);
         }
-
         decoration.relayout(taskInfo);
     }
 
@@ -236,7 +242,6 @@
             SurfaceControl.Transaction startT,
             SurfaceControl.Transaction finishT) {
         final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskInfo.taskId);
-
         if (!shouldShowWindowDecor(taskInfo)) {
             if (decoration != null) {
                 destroyWindowDecoration(taskInfo);
@@ -275,15 +280,17 @@
         }
     }
 
-    private class DesktopModeTouchEventListener implements
-            View.OnClickListener, View.OnTouchListener, DragDetector.MotionEventHandler {
+    private class DesktopModeTouchEventListener extends GestureDetector.SimpleOnGestureListener
+            implements View.OnClickListener, View.OnTouchListener, DragDetector.MotionEventHandler {
 
         private final int mTaskId;
         private final WindowContainerToken mTaskToken;
         private final DragPositioningCallback mDragPositioningCallback;
         private final DragDetector mDragDetector;
+        private final GestureDetector mGestureDetector;
 
         private boolean mIsDragging;
+        private boolean mShouldClick;
         private int mDragPointerId = -1;
 
         private DesktopModeTouchEventListener(
@@ -293,6 +300,7 @@
             mTaskToken = taskInfo.token;
             mDragPositioningCallback = dragPositioningCallback;
             mDragDetector = new DragDetector(this);
+            mGestureDetector = new GestureDetector(mContext, this);
         }
 
         @Override
@@ -301,14 +309,14 @@
             final int id = v.getId();
             if (id == R.id.close_window || id == R.id.close_button) {
                 mTaskOperations.closeTask(mTaskToken);
-                if (mSplitScreenController.isPresent()
-                        && mSplitScreenController.get().isSplitScreenVisible()) {
-                    int remainingTaskPosition = mTaskId == mSplitScreenController.get()
+                if (mSplitScreenController != null
+                        && mSplitScreenController.isSplitScreenVisible()) {
+                    int remainingTaskPosition = mTaskId == mSplitScreenController
                             .getTaskInfo(SPLIT_POSITION_TOP_OR_LEFT).taskId
                             ? SPLIT_POSITION_BOTTOM_OR_RIGHT : SPLIT_POSITION_TOP_OR_LEFT;
-                    ActivityManager.RunningTaskInfo remainingTask = mSplitScreenController.get()
+                    ActivityManager.RunningTaskInfo remainingTask = mSplitScreenController
                             .getTaskInfo(remainingTaskPosition);
-                    mSplitScreenController.get().moveTaskToFullscreen(remainingTask.taskId);
+                    mSplitScreenController.moveTaskToFullscreen(remainingTask.taskId);
                 }
             } else if (id == R.id.back_button) {
                 mTaskOperations.injectBackKey();
@@ -321,7 +329,13 @@
                 }
             } else if (id == R.id.desktop_button) {
                 mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(true));
-                mDesktopTasksController.ifPresent(c -> c.moveToDesktop(mTaskId));
+                if (mDesktopTasksController.isPresent()) {
+                    final WindowContainerTransaction wct = new WindowContainerTransaction();
+                    // App sometimes draws before the insets from WindowDecoration#relayout have
+                    // been added, so they must be added here
+                    mWindowDecorByTaskId.get(mTaskId).addCaptionInset(wct);
+                    mDesktopTasksController.get().moveToDesktop(mTaskId, wct);
+                }
                 decoration.closeHandleMenu();
             } else if (id == R.id.fullscreen_button) {
                 mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(false));
@@ -347,7 +361,7 @@
                 return false;
             }
             moveTaskToFront(mTaskOrganizer.getRunningTaskInfo(mTaskId));
-            return mDragDetector.onMotionEvent(e);
+            return mDragDetector.onMotionEvent(v, e);
         }
 
         private void moveTaskToFront(RunningTaskInfo taskInfo) {
@@ -362,7 +376,7 @@
          * @return {@code true} if the motion event is handled.
          */
         @Override
-        public boolean handleMotionEvent(MotionEvent e) {
+        public boolean handleMotionEvent(@Nullable View v, MotionEvent e) {
             final RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
             if (DesktopModeStatus.isProto2Enabled()
                     && taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
@@ -373,6 +387,9 @@
                     == WINDOWING_MODE_FULLSCREEN) {
                 return false;
             }
+            if (mGestureDetector.onTouchEvent(e)) {
+                return true;
+            }
             switch (e.getActionMasked()) {
                 case MotionEvent.ACTION_DOWN: {
                     mDragPointerId = e.getPointerId(0);
@@ -380,21 +397,38 @@
                             0 /* ctrlType */, e.getRawX(0),
                             e.getRawY(0));
                     mIsDragging = false;
-                    return false;
+                    mShouldClick = true;
+                    return true;
                 }
                 case MotionEvent.ACTION_MOVE: {
                     final DesktopModeWindowDecoration decoration =
                             mWindowDecorByTaskId.get(mTaskId);
+                    if (e.findPointerIndex(mDragPointerId) == -1) {
+                        mDragPointerId = e.getPointerId(0);
+                    }
                     final int dragPointerIdx = e.findPointerIndex(mDragPointerId);
                     mDesktopTasksController.ifPresent(c -> c.onDragPositioningMove(taskInfo,
                             decoration.mTaskSurface, e.getRawY(dragPointerIdx)));
                     mDragPositioningCallback.onDragPositioningMove(
                             e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
                     mIsDragging = true;
+                    mShouldClick = false;
                     return true;
                 }
                 case MotionEvent.ACTION_UP:
                 case MotionEvent.ACTION_CANCEL: {
+                    final boolean wasDragging = mIsDragging;
+                    if (!wasDragging) {
+                        if (mShouldClick && v != null) {
+                            v.performClick();
+                            mShouldClick = false;
+                            return true;
+                        }
+                        return false;
+                    }
+                    if (e.findPointerIndex(mDragPointerId) == -1) {
+                        mDragPointerId = e.getPointerId(0);
+                    }
                     final int dragPointerIdx = e.findPointerIndex(mDragPointerId);
                     // Position of the task is calculated by subtracting the raw location of the
                     // motion event (the location of the motion relative to the display) by the
@@ -405,14 +439,22 @@
                     mDragPositioningCallback.onDragPositioningEnd(
                             e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
                     mDesktopTasksController.ifPresent(c -> c.onDragPositioningEnd(taskInfo,
-                            position));
-                    final boolean wasDragging = mIsDragging;
+                            position, e.getRawY(), mWindowDecorByTaskId.get(mTaskId)));
                     mIsDragging = false;
-                    return wasDragging;
+                    return true;
                 }
             }
             return true;
         }
+
+        @Override
+        public boolean onDoubleTap(@NonNull MotionEvent e) {
+            final RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
+            mDesktopTasksController.ifPresent(c -> {
+                c.toggleDesktopTaskSize(taskInfo, mWindowDecorByTaskId.get(taskInfo.taskId));
+            });
+            return true;
+        }
     }
 
     // InputEventReceiver to listen for touch input outside of caption bounds
@@ -540,9 +582,11 @@
                             relevantDecor.mTaskInfo.configuration.windowConfiguration.getBounds());
                     boolean dragFromStatusBarAllowed = false;
                     if (DesktopModeStatus.isProto2Enabled()) {
-                        // In proto2 any full screen task can be dragged to freeform
-                        dragFromStatusBarAllowed = relevantDecor.mTaskInfo.getWindowingMode()
-                                == WINDOWING_MODE_FULLSCREEN;
+                        // In proto2 any full screen or multi-window task can be dragged to
+                        // freeform.
+                        final int windowingMode = relevantDecor.mTaskInfo.getWindowingMode();
+                        dragFromStatusBarAllowed = windowingMode == WINDOWING_MODE_FULLSCREEN
+                                || windowingMode == WINDOWING_MODE_MULTI_WINDOW;
                     }
 
                     if (dragFromStatusBarAllowed && relevantDecor.checkTouchEventInHandle(ev)) {
@@ -553,7 +597,7 @@
             }
             case MotionEvent.ACTION_UP: {
                 if (relevantDecor == null) {
-                    mDragToDesktopAnimationStarted = false;
+                    mMoveToDesktopAnimator = null;
                     mTransitionDragActive = false;
                     return;
                 }
@@ -567,14 +611,14 @@
                         } else if (DesktopModeStatus.isProto1Enabled()) {
                             mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(true));
                         }
-                        mDragToDesktopAnimationStarted = false;
+                        mMoveToDesktopAnimator = null;
                         return;
-                    } else if (mDragToDesktopAnimationStarted) {
-                        Point position = new Point((int) ev.getX(), (int) ev.getY());
+                    } else if (mMoveToDesktopAnimator != null) {
+                        relevantDecor.incrementRelayoutBlock();
                         mDesktopTasksController.ifPresent(
-                                c -> c.cancelMoveToFreeform(relevantDecor.mTaskInfo,
-                                        position));
-                        mDragToDesktopAnimationStarted = false;
+                                c -> c.cancelMoveToDesktop(relevantDecor.mTaskInfo,
+                                        mMoveToDesktopAnimator));
+                        mMoveToDesktopAnimator = null;
                         return;
                     }
                 }
@@ -594,21 +638,19 @@
                     final int statusBarHeight = getStatusBarHeight(
                             relevantDecor.mTaskInfo.displayId);
                     if (ev.getY() > statusBarHeight) {
-                        if (!mDragToDesktopAnimationStarted) {
-                            mDragToDesktopAnimationStarted = true;
+                        if (mMoveToDesktopAnimator == null) {
+                            mMoveToDesktopAnimator = new MoveToDesktopAnimator(
+                                    mDragToDesktopAnimationStartBounds, relevantDecor.mTaskInfo,
+                                    relevantDecor.mTaskSurface);
                             mDesktopTasksController.ifPresent(
-                                    c -> c.moveToFreeform(relevantDecor.mTaskInfo,
-                                            mDragToDesktopAnimationStartBounds));
-                            startAnimation(relevantDecor);
+                                    c -> c.startMoveToDesktop(relevantDecor.mTaskInfo,
+                                            mDragToDesktopAnimationStartBounds,
+                                            mMoveToDesktopAnimator));
+                            mMoveToDesktopAnimator.startAnimation();
                         }
                     }
-                    if (mDragToDesktopAnimationStarted) {
-                        Transaction t = mTransactionFactory.get();
-                        float width = (float) mDragToDesktopValueAnimator.getAnimatedValue()
-                                * mDragToDesktopAnimationStartBounds.width();
-                        float x = ev.getX() - (width / 2);
-                        t.setPosition(relevantDecor.mTaskSurface, x, ev.getY());
-                        t.apply();
+                    if (mMoveToDesktopAnimator != null) {
+                        mMoveToDesktopAnimator.updatePosition(ev);
                     }
                 }
                 break;
@@ -616,7 +658,7 @@
 
             case MotionEvent.ACTION_CANCEL: {
                 mTransitionDragActive = false;
-                mDragToDesktopAnimationStarted = false;
+                mMoveToDesktopAnimator = null;
             }
         }
     }
@@ -683,24 +725,9 @@
         animator.start();
     }
 
-    private void startAnimation(@NonNull DesktopModeWindowDecoration focusedDecor) {
-        mDragToDesktopValueAnimator = ValueAnimator.ofFloat(1f, DRAG_FREEFORM_SCALE);
-        mDragToDesktopValueAnimator.setDuration(FREEFORM_ANIMATION_DURATION);
-        final Transaction t = mTransactionFactory.get();
-        mDragToDesktopValueAnimator.addUpdateListener(animation -> {
-            final float animatorValue = (float) animation.getAnimatedValue();
-            SurfaceControl sc = focusedDecor.mTaskSurface;
-            t.setScale(sc, animatorValue, animatorValue);
-            t.apply();
-        });
-
-        mDragToDesktopValueAnimator.start();
-    }
-
     @Nullable
     private DesktopModeWindowDecoration getRelevantWindowDecor(MotionEvent ev) {
-        if (mSplitScreenController.isPresent()
-                && mSplitScreenController.get().isSplitScreenVisible()) {
+        if (mSplitScreenController != null && mSplitScreenController.isSplitScreenVisible()) {
             // We can't look at focused task here as only one task will have focus.
             return getSplitScreenDecor(ev);
         } else {
@@ -711,9 +738,9 @@
     @Nullable
     private DesktopModeWindowDecoration getSplitScreenDecor(MotionEvent ev) {
         ActivityManager.RunningTaskInfo topOrLeftTask =
-                mSplitScreenController.get().getTaskInfo(SPLIT_POSITION_TOP_OR_LEFT);
+                mSplitScreenController.getTaskInfo(SPLIT_POSITION_TOP_OR_LEFT);
         ActivityManager.RunningTaskInfo bottomOrRightTask =
-                mSplitScreenController.get().getTaskInfo(SPLIT_POSITION_BOTTOM_OR_RIGHT);
+                mSplitScreenController.getTaskInfo(SPLIT_POSITION_BOTTOM_OR_RIGHT);
         if (topOrLeftTask != null && topOrLeftTask.getConfiguration()
                 .windowConfiguration.getBounds().contains((int) ev.getX(), (int) ev.getY())) {
             return mWindowDecorByTaskId.get(topOrLeftTask.taskId);
@@ -765,12 +792,14 @@
 
     private boolean shouldShowWindowDecor(RunningTaskInfo taskInfo) {
         if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) return true;
-        if (mSplitScreenController.isPresent()
-                && mSplitScreenController.get().isTaskRootOrStageRoot(taskInfo.taskId)) {
+        if (mSplitScreenController != null
+                && mSplitScreenController.isTaskRootOrStageRoot(taskInfo.taskId)) {
             return false;
         }
         return DesktopModeStatus.isProto2Enabled()
+                && taskInfo.getWindowingMode() != WINDOWING_MODE_PINNED
                 && taskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD
+                && !taskInfo.configuration.windowConfiguration.isAlwaysOnTop()
                 && mDisplayController.getDisplayContext(taskInfo.displayId)
                 .getResources().getConfiguration().smallestScreenWidthDp >= 600;
     }
@@ -796,9 +825,10 @@
                         mMainChoreographer,
                         mSyncQueue);
         mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration);
+        windowDecoration.createResizeVeil();
 
         final DragPositioningCallback dragPositioningCallback = createDragPositioningCallback(
-                windowDecoration, taskInfo);
+                windowDecoration);
         final DesktopModeTouchEventListener touchEventListener =
                 new DesktopModeTouchEventListener(taskInfo, dragPositioningCallback);
 
@@ -811,20 +841,17 @@
         incrementEventReceiverTasks(taskInfo.displayId);
     }
     private DragPositioningCallback createDragPositioningCallback(
-            @NonNull DesktopModeWindowDecoration windowDecoration,
-            @NonNull RunningTaskInfo taskInfo) {
-        final int screenWidth = mDisplayController.getDisplayLayout(taskInfo.displayId).width();
-        final Rect disallowedAreaForEndBounds = new Rect(0, 0, screenWidth,
-                getStatusBarHeight(taskInfo.displayId));
+            @NonNull DesktopModeWindowDecoration windowDecoration) {
+        final int transitionAreaHeight = mContext.getResources().getDimensionPixelSize(
+                R.dimen.desktop_mode_transition_area_height);
         if (!DesktopModeStatus.isVeiledResizeEnabled()) {
             return new FluidResizeTaskPositioner(mTaskOrganizer, windowDecoration,
-                    mDisplayController, disallowedAreaForEndBounds, mDragStartListener,
-                    mTransactionFactory);
+                    mDisplayController, mDragStartListener, mTransactionFactory,
+                    transitionAreaHeight);
         } else {
-            windowDecoration.createResizeVeil();
             return new VeiledResizeTaskPositioner(mTaskOrganizer, windowDecoration,
-                    mDisplayController, disallowedAreaForEndBounds, mDragStartListener,
-                    mTransitions);
+                    mDisplayController, mDragStartListener, mTransitions,
+                    transitionAreaHeight);
         }
     }
 
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 ce11b26..a359395 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
@@ -23,7 +23,6 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.res.Configuration;
-import android.content.res.TypedArray;
 import android.graphics.Point;
 import android.graphics.PointF;
 import android.graphics.Rect;
@@ -39,6 +38,7 @@
 import android.view.ViewConfiguration;
 import android.window.WindowContainerTransaction;
 
+import com.android.internal.policy.ScreenDecorationsUtils;
 import com.android.launcher3.icons.IconProvider;
 import com.android.wm.shell.R;
 import com.android.wm.shell.ShellTaskOrganizer;
@@ -178,15 +178,12 @@
         mRelayoutParams.reset();
         mRelayoutParams.mRunningTaskInfo = taskInfo;
         mRelayoutParams.mLayoutResId = windowDecorLayoutId;
-        mRelayoutParams.mCaptionHeightId = R.dimen.freeform_decor_caption_height;
+        mRelayoutParams.mCaptionHeightId = getCaptionHeightId();
         mRelayoutParams.mShadowRadiusId = shadowRadiusID;
         mRelayoutParams.mApplyStartTransactionOnDraw = applyStartTransactionOnDraw;
 
-        final TypedArray ta = mContext.obtainStyledAttributes(
-                new int[]{android.R.attr.dialogCornerRadius});
-        mRelayoutParams.mCornerRadius = ta.getDimensionPixelSize(0, 0);
-        ta.recycle();
-
+        mRelayoutParams.mCornerRadius =
+                (int) ScreenDecorationsUtils.getWindowCornerRadius(mContext);
         relayout(mRelayoutParams, startT, finishT, wct, oldRootView, mResult);
         // After this line, mTaskInfo is up-to-date and should be used instead of taskInfo
 
@@ -235,8 +232,11 @@
                     mHandler,
                     mChoreographer,
                     mDisplay.getDisplayId(),
+                    mRelayoutParams.mCornerRadius,
                     mDecorationContainerSurface,
-                    mDragPositioningCallback);
+                    mDragPositioningCallback,
+                    mSurfaceControlBuilderSupplier,
+                    mSurfaceControlTransactionSupplier);
         }
 
         final int touchSlop = ViewConfiguration.get(mResult.mRootView.getContext())
@@ -294,23 +294,37 @@
     }
 
     /**
-     * Fade in the resize veil
+     * Show the resize veil.
      */
-    void showResizeVeil(Rect taskBounds) {
+    public void showResizeVeil(Rect taskBounds) {
         mResizeVeil.showVeil(mTaskSurface, taskBounds);
     }
 
     /**
+     * Show the resize veil.
+     */
+    public void showResizeVeil(SurfaceControl.Transaction tx, Rect taskBounds) {
+        mResizeVeil.showVeil(tx, mTaskSurface, taskBounds, false /* fadeIn */);
+    }
+
+    /**
      * Set new bounds for the resize veil
      */
-    void updateResizeVeil(Rect newBounds) {
+    public void updateResizeVeil(Rect newBounds) {
         mResizeVeil.updateResizeVeil(newBounds);
     }
 
     /**
+     * Set new bounds for the resize veil
+     */
+    public void updateResizeVeil(SurfaceControl.Transaction tx, Rect newBounds) {
+        mResizeVeil.updateResizeVeil(tx, newBounds);
+    }
+
+    /**
      * Fade the resize veil out.
      */
-    void hideResizeVeil() {
+    public void hideResizeVeil() {
         mResizeVeil.hideVeil();
     }
 
@@ -479,6 +493,11 @@
         }
     }
 
+    @Override
+    int getCaptionHeightId() {
+        return R.dimen.freeform_decor_caption_height;
+    }
+
     /**
      * Add transition to mTransitionsPausingRelayout
      */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java
index 65b5a7a..da26898 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java
@@ -24,6 +24,9 @@
 
 import android.graphics.PointF;
 import android.view.MotionEvent;
+import android.view.View;
+
+import androidx.annotation.Nullable;
 
 /**
  * A detector for touch inputs that differentiates between drag and click inputs. It receives a flow
@@ -54,14 +57,24 @@
      *
      * @return the result returned by {@link #mEventHandler}, or the result when
      * {@link #mEventHandler} handles the previous down event if the event shouldn't be passed
-    */
+     */
     boolean onMotionEvent(MotionEvent ev) {
+        return onMotionEvent(null /* view */, ev);
+    }
+
+    /**
+     * The receiver of the {@link MotionEvent} flow.
+     *
+     * @return the result returned by {@link #mEventHandler}, or the result when
+     * {@link #mEventHandler} handles the previous down event if the event shouldn't be passed
+    */
+    boolean onMotionEvent(View v, MotionEvent ev) {
         final boolean isTouchScreen =
                 (ev.getSource() & SOURCE_TOUCHSCREEN) == SOURCE_TOUCHSCREEN;
         if (!isTouchScreen) {
             // Only touches generate noisy moves, so mouse/trackpad events don't need to filtered
             // to take the slop threshold into consideration.
-            return mEventHandler.handleMotionEvent(ev);
+            return mEventHandler.handleMotionEvent(v, ev);
         }
         switch (ev.getActionMasked()) {
             case ACTION_DOWN: {
@@ -69,12 +82,15 @@
                 float rawX = ev.getRawX(0);
                 float rawY = ev.getRawY(0);
                 mInputDownPoint.set(rawX, rawY);
-                mResultOfDownAction = mEventHandler.handleMotionEvent(ev);
+                mResultOfDownAction = mEventHandler.handleMotionEvent(v, ev);
                 return mResultOfDownAction;
             }
             case ACTION_MOVE: {
+                if (ev.findPointerIndex(mDragPointerId) == -1) {
+                    mDragPointerId = ev.getPointerId(0);
+                }
+                final int dragPointerIndex = ev.findPointerIndex(mDragPointerId);
                 if (!mIsDragEvent) {
-                    int dragPointerIndex = ev.findPointerIndex(mDragPointerId);
                     float dx = ev.getRawX(dragPointerIndex) - mInputDownPoint.x;
                     float dy = ev.getRawY(dragPointerIndex) - mInputDownPoint.y;
                     // Touches generate noisy moves, so only once the move is past the touch
@@ -84,7 +100,7 @@
                 // The event handler should only be notified about 'move' events if a drag has been
                 // detected.
                 if (mIsDragEvent) {
-                    return mEventHandler.handleMotionEvent(ev);
+                    return mEventHandler.handleMotionEvent(v, ev);
                 } else {
                     return mResultOfDownAction;
                 }
@@ -92,10 +108,10 @@
             case ACTION_UP:
             case ACTION_CANCEL: {
                 resetState();
-                return mEventHandler.handleMotionEvent(ev);
+                return mEventHandler.handleMotionEvent(v, ev);
             }
             default:
-                return mEventHandler.handleMotionEvent(ev);
+                return mEventHandler.handleMotionEvent(v, ev);
         }
     }
 
@@ -111,6 +127,6 @@
     }
 
     interface MotionEventHandler {
-        boolean handleMotionEvent(MotionEvent ev);
+        boolean handleMotionEvent(@Nullable View v, MotionEvent ev);
     }
 }
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 4e98f0c..941617d 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
@@ -22,7 +22,9 @@
  * Callback called when receiving drag-resize or drag-move related input events.
  */
 public interface DragPositioningCallback {
-    @IntDef({CTRL_TYPE_UNDEFINED, CTRL_TYPE_LEFT, CTRL_TYPE_RIGHT, CTRL_TYPE_TOP, CTRL_TYPE_BOTTOM})
+    @IntDef(flag = true, value = {
+            CTRL_TYPE_UNDEFINED, CTRL_TYPE_LEFT, CTRL_TYPE_RIGHT, CTRL_TYPE_TOP, CTRL_TYPE_BOTTOM
+    })
     @interface CtrlType {}
 
     int CTRL_TYPE_UNDEFINED = 0;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java
index 09e29bc..e32bd42 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java
@@ -83,8 +83,6 @@
         // Make sure the new resizing destination in any direction falls within the stable bounds.
         // If not, set the bounds back to the old location that was valid to avoid conflicts with
         // some regions such as the gesture area.
-        displayController.getDisplayLayout(windowDecoration.mDisplay.getDisplayId())
-                .getStableBounds(stableBounds);
         if ((ctrlType & CTRL_TYPE_LEFT) != 0) {
             final int candidateLeft = repositionTaskBounds.left + (int) delta.x;
             repositionTaskBounds.left = (candidateLeft > stableBounds.left)
@@ -136,7 +134,7 @@
                 repositionTaskBounds.top);
     }
 
-    static void updateTaskBounds(Rect repositionTaskBounds, Rect taskBoundsAtDragStart,
+    private static void updateTaskBounds(Rect repositionTaskBounds, Rect taskBoundsAtDragStart,
             PointF repositionStartPoint, float x, float y) {
         final float deltaX = x - repositionStartPoint.x;
         final float deltaY = y - repositionStartPoint.y;
@@ -145,6 +143,23 @@
     }
 
     /**
+     * Updates repositionTaskBounds to the final bounds of the task after the drag is finished. If
+     * the bounds are outside of the stable bounds, they are shifted to place task at the top of the
+     * stable bounds.
+     */
+    static void onDragEnd(Rect repositionTaskBounds, Rect taskBoundsAtDragStart, Rect stableBounds,
+            PointF repositionStartPoint, float x, float y)  {
+        updateTaskBounds(repositionTaskBounds, taskBoundsAtDragStart, repositionStartPoint,
+                x, y);
+
+        // If task is outside of stable bounds (in the status bar area), shift the task down.
+        if (stableBounds.top > repositionTaskBounds.top) {
+            final int yShift =  stableBounds.top - repositionTaskBounds.top;
+            repositionTaskBounds.offset(0, yShift);
+        }
+    }
+
+    /**
      * Apply a bounds change to a task.
      * @param windowDecoration decor of task we are changing bounds for
      * @param taskBounds new bounds of this task
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 287d861..7c6fb99 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
@@ -18,8 +18,11 @@
 
 import static android.view.InputDevice.SOURCE_TOUCHSCREEN;
 import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL;
+import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_SPY;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
+import static android.view.WindowManager.LayoutParams.TYPE_INPUT_CONSUMER;
 
 import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_BOTTOM;
 import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_LEFT;
@@ -42,11 +45,14 @@
 import android.view.MotionEvent;
 import android.view.PointerIcon;
 import android.view.SurfaceControl;
+import android.view.View;
 import android.view.ViewConfiguration;
 import android.view.WindowManagerGlobal;
 
 import com.android.internal.view.BaseIWindow;
 
+import java.util.function.Supplier;
+
 /**
  * An input event listener registered to InputDispatcher to receive input events on task edges and
  * and corners. Converts them to drag resize requests.
@@ -55,11 +61,11 @@
  */
 class DragResizeInputListener implements AutoCloseable {
     private static final String TAG = "DragResizeInputListener";
-
     private final IWindowSession mWindowSession = WindowManagerGlobal.getWindowSession();
     private final Handler mHandler;
     private final Choreographer mChoreographer;
     private final InputManager mInputManager;
+    private final Supplier<SurfaceControl.Transaction> mSurfaceControlTransactionSupplier;
 
     private final int mDisplayId;
     private final BaseIWindow mFakeWindow;
@@ -69,10 +75,15 @@
     private final TaskResizeInputEventReceiver mInputEventReceiver;
     private final DragPositioningCallback mCallback;
 
+    private final SurfaceControl mInputSinkSurface;
+    private final BaseIWindow mFakeSinkWindow;
+    private final InputChannel mSinkInputChannel;
+
     private int mTaskWidth;
     private int mTaskHeight;
     private int mResizeHandleThickness;
     private int mCornerSize;
+    private int mTaskCornerRadius;
 
     private Rect mLeftTopCornerBounds;
     private Rect mRightTopCornerBounds;
@@ -87,15 +98,20 @@
             Handler handler,
             Choreographer choreographer,
             int displayId,
+            int taskCornerRadius,
             SurfaceControl decorationSurface,
-            DragPositioningCallback callback) {
+            DragPositioningCallback callback,
+            Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier,
+            Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier) {
         mInputManager = context.getSystemService(InputManager.class);
         mHandler = handler;
         mChoreographer = choreographer;
+        mSurfaceControlTransactionSupplier = surfaceControlTransactionSupplier;
         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.
+        // 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);
         mFocusGrantToken = new Binder();
@@ -108,7 +124,7 @@
                     null /* hostInputToken */,
                     FLAG_NOT_FOCUSABLE,
                     PRIVATE_FLAG_TRUSTED_OVERLAY,
-                    0 /* inputFeatures */,
+                    INPUT_FEATURE_SPY,
                     TYPE_APPLICATION,
                     null /* windowToken */,
                     mFocusGrantToken,
@@ -123,15 +139,39 @@
         mCallback = callback;
         mDragDetector = new DragDetector(mInputEventReceiver);
         mDragDetector.setTouchSlop(ViewConfiguration.get(context).getScaledTouchSlop());
+
+        mInputSinkSurface = surfaceControlBuilderSupplier.get()
+                .setName("TaskInputSink of " + decorationSurface)
+                .setContainerLayer()
+                .setParent(mDecorationSurface)
+                .build();
+        mSurfaceControlTransactionSupplier.get()
+                .setLayer(mInputSinkSurface, WindowDecoration.INPUT_SINK_Z_ORDER)
+                .show(mInputSinkSurface)
+                .apply();
+        mFakeSinkWindow = new BaseIWindow();
+        mSinkInputChannel = new InputChannel();
+        try {
+            mWindowSession.grantInputChannel(
+                    mDisplayId,
+                    mInputSinkSurface,
+                    mFakeSinkWindow,
+                    null /* hostInputToken */,
+                    FLAG_NOT_FOCUSABLE,
+                    0 /* privateFlags */,
+                    INPUT_FEATURE_NO_INPUT_CHANNEL,
+                    TYPE_INPUT_CONSUMER,
+                    null /* windowToken */,
+                    mFocusGrantToken,
+                    "TaskInputSink of " + decorationSurface,
+                    mSinkInputChannel);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
     }
 
     /**
-     * Updates geometry of this drag resize handler. Needs to be called every time there is a size
-     * change to notify the input event receiver it's ready to take the next input event. Otherwise
-     * it'll keep batching move events and the drag resize process is stalled.
-     *
-     * This is also used to update the touch regions of this handler every event dispatched here is
-     * a potential resize request.
+     * Updates the geometry (the touch region) of this drag resize handler.
      *
      * @param taskWidth The width of the task.
      * @param taskHeight The height of the task.
@@ -221,7 +261,35 @@
                     mDecorationSurface,
                     FLAG_NOT_FOCUSABLE,
                     PRIVATE_FLAG_TRUSTED_OVERLAY,
-                    0 /* inputFeatures */,
+                    INPUT_FEATURE_SPY,
+                    touchRegion);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+
+        mSurfaceControlTransactionSupplier.get()
+                .setWindowCrop(mInputSinkSurface, mTaskWidth, mTaskHeight)
+                .apply();
+        // The touch region of the TaskInputSink should be the touch region of this
+        // DragResizeInputHandler minus the task bounds. Pilfering events isn't enough to prevent
+        // input windows from handling down events, which will bring tasks in the back to front.
+        //
+        // Note not the entire touch region responds to both mouse and touchscreen events.
+        // Therefore, in the region that only responds to one of them, it would be a no-op to
+        // perform a gesture in the other type of events. We currently only have a mouse-only region
+        // out of the task bounds, and due to the roughness of touchscreen events, it's not a severe
+        // 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();
@@ -250,6 +318,16 @@
         } catch (RemoteException e) {
             e.rethrowFromSystemServer();
         }
+
+        mSinkInputChannel.dispose();
+        try {
+            mWindowSession.remove(mFakeSinkWindow);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+        mSurfaceControlTransactionSupplier.get()
+                .remove(mInputSinkSurface)
+                .apply();
     }
 
     private class TaskResizeInputEventReceiver extends InputEventReceiver
@@ -258,6 +336,7 @@
         private final Runnable mConsumeBatchEventRunnable;
         private boolean mConsumeBatchEventScheduled;
         private boolean mShouldHandleEvents;
+        private int mLastCursorType = PointerIcon.TYPE_DEFAULT;
 
         private TaskResizeInputEventReceiver(
                 InputChannel inputChannel, Handler handler, Choreographer choreographer) {
@@ -303,7 +382,7 @@
         }
 
         @Override
-        public boolean handleMotionEvent(MotionEvent e) {
+        public boolean handleMotionEvent(View v, MotionEvent e) {
             boolean result = false;
             // Check if this is a touch event vs mouse event.
             // Touch events are tracked in four corners. Other events are tracked in resize edges.
@@ -318,6 +397,8 @@
                         mShouldHandleEvents = isInResizeHandleBounds(x, y);
                     }
                     if (mShouldHandleEvents) {
+                        mInputManager.pilferPointers(mInputChannel.getToken());
+
                         mDragPointerId = e.getPointerId(0);
                         float rawX = e.getRawX(0);
                         float rawY = e.getRawY(0);
@@ -357,7 +438,6 @@
                     break;
                 }
                 case MotionEvent.ACTION_HOVER_EXIT:
-                    mInputManager.setPointerIconType(PointerIcon.TYPE_DEFAULT);
                     result = true;
                     break;
             }
@@ -383,19 +463,71 @@
         @DragPositioningCallback.CtrlType
         private int calculateResizeHandlesCtrlType(float x, float y) {
             int ctrlType = 0;
-            if (x < 0) {
+            // mTaskCornerRadius is only used in comparing with corner regions. Comparisons with
+            // sides will use the bounds specified in setGeometry and not go into task bounds.
+            if (x < mTaskCornerRadius) {
                 ctrlType |= CTRL_TYPE_LEFT;
             }
-            if (x > mTaskWidth) {
+            if (x > mTaskWidth - mTaskCornerRadius) {
                 ctrlType |= CTRL_TYPE_RIGHT;
             }
-            if (y < 0) {
+            if (y < mTaskCornerRadius) {
                 ctrlType |= CTRL_TYPE_TOP;
             }
-            if (y > mTaskHeight) {
+            if (y > mTaskHeight - mTaskCornerRadius) {
                 ctrlType |= CTRL_TYPE_BOTTOM;
             }
-            return ctrlType;
+            // Check distances from the center if it's in one of four corners.
+            if ((ctrlType & (CTRL_TYPE_LEFT | CTRL_TYPE_RIGHT)) != 0
+                    && (ctrlType & (CTRL_TYPE_TOP | CTRL_TYPE_BOTTOM)) != 0) {
+                return checkDistanceFromCenter(ctrlType, x, y);
+            }
+            // Otherwise, we should make sure we don't resize tasks inside task bounds.
+            return (x < 0 || y < 0 || x >= mTaskWidth || y >= mTaskHeight) ? ctrlType : 0;
+        }
+
+        // If corner input is not within appropriate distance of corner radius, do not use it.
+        // If input is not on a corner or is within valid distance, return ctrlType.
+        @DragPositioningCallback.CtrlType
+        private int checkDistanceFromCenter(@DragPositioningCallback.CtrlType int ctrlType,
+                float x, float y) {
+            int centerX;
+            int centerY;
+
+            // Determine center of rounded corner circle; this is simply the corner if radius is 0.
+            switch (ctrlType) {
+                case CTRL_TYPE_LEFT | CTRL_TYPE_TOP: {
+                    centerX = mTaskCornerRadius;
+                    centerY = mTaskCornerRadius;
+                    break;
+                }
+                case CTRL_TYPE_LEFT | CTRL_TYPE_BOTTOM: {
+                    centerX = mTaskCornerRadius;
+                    centerY = mTaskHeight - mTaskCornerRadius;
+                    break;
+                }
+                case CTRL_TYPE_RIGHT | CTRL_TYPE_TOP: {
+                    centerX = mTaskWidth - mTaskCornerRadius;
+                    centerY = mTaskCornerRadius;
+                    break;
+                }
+                case CTRL_TYPE_RIGHT | CTRL_TYPE_BOTTOM: {
+                    centerX = mTaskWidth - mTaskCornerRadius;
+                    centerY = mTaskHeight - mTaskCornerRadius;
+                    break;
+                }
+                default: {
+                    throw new IllegalArgumentException("ctrlType should be complex, but it's 0x"
+                            + Integer.toHexString(ctrlType));
+                }
+            }
+            double distanceFromCenter = Math.hypot(x - centerX, y - centerY);
+
+            if (distanceFromCenter < mTaskCornerRadius + mResizeHandleThickness
+                    && distanceFromCenter >= mTaskCornerRadius) {
+                return ctrlType;
+            }
+            return 0;
         }
 
         @DragPositioningCallback.CtrlType
@@ -439,7 +571,19 @@
                     cursorType = PointerIcon.TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW;
                     break;
             }
-            mInputManager.setPointerIconType(cursorType);
+            // Only update the cursor type to default once so that views behind the decor container
+            // layer that aren't in the active resizing regions have chances to update the cursor
+            // type. We would like to enforce the cursor type by setting the cursor type multilple
+            // times in active regions because we shouldn't allow the views behind to change it, as
+            // we'll pilfer the gesture initiated in this area. This is necessary because 1) we
+            // should allow the views behind regions only for touches to set the cursor type; and 2)
+            // there is a small region out of each rounded corner that's inside the task bounds,
+            // where views in the task can receive input events because we can't set touch regions
+            // of input sinks to have rounded corners.
+            if (mLastCursorType != cursorType || cursorType != PointerIcon.TYPE_DEFAULT) {
+                mInputManager.setPointerIconType(cursorType);
+                mLastCursorType = cursorType;
+            }
         }
     }
 }
\ No newline at end of file
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 9082323..e1b6db5 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
@@ -21,8 +21,6 @@
 import android.view.SurfaceControl;
 import android.window.WindowContainerTransaction;
 
-import androidx.annotation.Nullable;
-
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.common.DisplayController;
 
@@ -42,28 +40,29 @@
     private final Rect mTaskBoundsAtDragStart = new Rect();
     private final PointF mRepositionStartPoint = new PointF();
     private final Rect mRepositionTaskBounds = new Rect();
-    // If a task move (not resize) finishes in this region, the positioner will not attempt to
+    // If a task move (not resize) finishes with the positions y less than this value, do not
     // finalize the bounds there using WCT#setBounds
-    private final Rect mDisallowedAreaForEndBounds;
+    private final int mDisallowedAreaForEndBoundsHeight;
     private boolean mHasDragResized;
     private int mCtrlType;
 
     FluidResizeTaskPositioner(ShellTaskOrganizer taskOrganizer, WindowDecoration windowDecoration,
-            DisplayController displayController, @Nullable Rect disallowedAreaForEndBounds) {
-        this(taskOrganizer, windowDecoration, displayController, disallowedAreaForEndBounds,
-                dragStartListener -> {}, SurfaceControl.Transaction::new);
+            DisplayController displayController, int disallowedAreaForEndBoundsHeight) {
+        this(taskOrganizer, windowDecoration, displayController, dragStartListener -> {},
+                SurfaceControl.Transaction::new, disallowedAreaForEndBoundsHeight);
     }
 
     FluidResizeTaskPositioner(ShellTaskOrganizer taskOrganizer, WindowDecoration windowDecoration,
-            DisplayController displayController, @Nullable Rect disallowedAreaForEndBounds,
+            DisplayController displayController,
             DragPositioningCallbackUtility.DragStartListener dragStartListener,
-            Supplier<SurfaceControl.Transaction> supplier) {
+            Supplier<SurfaceControl.Transaction> supplier,
+            int disallowedAreaForEndBoundsHeight) {
         mTaskOrganizer = taskOrganizer;
         mWindowDecoration = windowDecoration;
         mDisplayController = displayController;
-        mDisallowedAreaForEndBounds = new Rect(disallowedAreaForEndBounds);
         mDragStartListener = dragStartListener;
         mTransactionSupplier = supplier;
+        mDisallowedAreaForEndBoundsHeight = disallowedAreaForEndBoundsHeight;
     }
 
     @Override
@@ -79,6 +78,10 @@
             mTaskOrganizer.applyTransaction(wct);
         }
         mRepositionTaskBounds.set(mTaskBoundsAtDragStart);
+        if (mStableBounds.isEmpty()) {
+            mDisplayController.getDisplayLayout(mWindowDecoration.mDisplay.getDisplayId())
+                    .getStableBounds(mStableBounds);
+        }
     }
 
     @Override
@@ -121,10 +124,10 @@
             }
             mTaskOrganizer.applyTransaction(wct);
         } else if (mCtrlType == CTRL_TYPE_UNDEFINED
-                && !mDisallowedAreaForEndBounds.contains((int) x, (int) y)) {
+                && y > mDisallowedAreaForEndBoundsHeight) {
             final WindowContainerTransaction wct = new WindowContainerTransaction();
-            DragPositioningCallbackUtility.updateTaskBounds(mRepositionTaskBounds,
-                    mTaskBoundsAtDragStart, mRepositionStartPoint, x, y);
+            DragPositioningCallbackUtility.onDragEnd(mRepositionTaskBounds,
+                    mTaskBoundsAtDragStart, mStableBounds, mRepositionStartPoint, x, y);
             wct.setBounds(mWindowDecoration.mTaskInfo.token, mRepositionTaskBounds);
             mTaskOrganizer.applyTransaction(wct);
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MoveToDesktopAnimator.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MoveToDesktopAnimator.kt
new file mode 100644
index 0000000..b2267dd
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MoveToDesktopAnimator.kt
@@ -0,0 +1,72 @@
+package com.android.wm.shell.windowdecor
+
+import android.animation.ValueAnimator
+import android.app.ActivityManager.RunningTaskInfo
+import android.graphics.PointF
+import android.graphics.Rect
+import android.view.MotionEvent
+import android.view.SurfaceControl
+
+/**
+ * Creates an animator to shrink and position task after a user drags a fullscreen task from
+ * the top of the screen to transition it into freeform and before the user releases the task. The
+ * MoveToDesktopAnimator object also holds information about the state of the task that are
+ * accessed by the EnterDesktopTaskTransitionHandler.
+ */
+class MoveToDesktopAnimator @JvmOverloads constructor(
+        private val startBounds: Rect,
+        private val taskInfo: RunningTaskInfo,
+        private val taskSurface: SurfaceControl,
+        private val transactionFactory: () -> SurfaceControl.Transaction =
+                SurfaceControl::Transaction
+) {
+    companion object {
+        // The size of the screen during drag relative to the fullscreen size
+        const val DRAG_FREEFORM_SCALE: Float = 0.4f
+        const val ANIMATION_DURATION = 336
+    }
+
+    private val animatedTaskWidth
+        get() = dragToDesktopAnimator.animatedValue as Float * startBounds.width()
+    private val dragToDesktopAnimator: ValueAnimator = ValueAnimator.ofFloat(1f,
+            DRAG_FREEFORM_SCALE)
+            .setDuration(ANIMATION_DURATION.toLong())
+            .apply {
+                val t = SurfaceControl.Transaction()
+                addUpdateListener { animation ->
+                    val animatorValue = animation.animatedValue as Float
+                    t.setScale(taskSurface, animatorValue, animatorValue)
+                            .apply()
+                }
+            }
+
+    val taskId get() = taskInfo.taskId
+    val position: PointF = PointF(0.0f, 0.0f)
+
+    /**
+     * Starts the animation that scales the task down.
+     */
+    fun startAnimation() {
+        dragToDesktopAnimator.start()
+    }
+
+    /**
+     * Uses the position of the motion event and the current scale of the task as defined by the
+     * ValueAnimator to update the local position variable and set the task surface's position
+     */
+    fun updatePosition(ev: MotionEvent) {
+        position.x = ev.x - animatedTaskWidth / 2
+        position.y = ev.y
+
+        val t = transactionFactory()
+        t.setPosition(taskSurface, position.x, position.y)
+        t.apply()
+    }
+
+    /**
+     * Ends the animation, setting the scale and position to the final animation value
+     */
+    fun endAnimator() {
+        dragToDesktopAnimator.end()
+    }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java
index 8277109..bfce72b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java
@@ -102,11 +102,17 @@
     }
 
     /**
-     * Animate veil's alpha to 1, fading it in.
+     * Shows the veil surface/view.
+     *
+     * @param t the transaction to apply in sync with the veil draw
+     * @param parentSurface the surface that the veil should be a child of
+     * @param taskBounds the bounds of the task that owns the veil
+     * @param fadeIn if true, the veil will fade-in with an animation, if false, it will be shown
+     *               immediately
      */
-    public void showVeil(SurfaceControl parentSurface, Rect taskBounds) {
+    public void showVeil(SurfaceControl.Transaction t, SurfaceControl parentSurface,
+            Rect taskBounds, boolean fadeIn) {
         // Parent surface can change, ensure it is up to date.
-        SurfaceControl.Transaction t = mSurfaceControlTransactionSupplier.get();
         if (!parentSurface.equals(mParentSurface)) {
             t.reparent(mVeilSurface, parentSurface);
             mParentSurface = parentSurface;
@@ -115,22 +121,36 @@
         int backgroundColorId = getBackgroundColorId();
         mViewHost.getView().setBackgroundColor(mContext.getColor(backgroundColorId));
 
-        final ValueAnimator animator = new ValueAnimator();
-        animator.setFloatValues(0f, 1f);
-        animator.setDuration(RESIZE_ALPHA_DURATION);
-        animator.addUpdateListener(animation -> {
-            t.setAlpha(mVeilSurface, animator.getAnimatedFraction());
-            t.apply();
-        });
-
         relayout(taskBounds, t);
-        t.show(mVeilSurface)
-                .addTransactionCommittedListener(mContext.getMainExecutor(), () -> animator.start())
-                .setAlpha(mVeilSurface, 0);
+        if (fadeIn) {
+            final ValueAnimator animator = new ValueAnimator();
+            animator.setFloatValues(0f, 1f);
+            animator.setDuration(RESIZE_ALPHA_DURATION);
+            animator.addUpdateListener(animation -> {
+                t.setAlpha(mVeilSurface, animator.getAnimatedFraction());
+                t.apply();
+            });
+
+            t.show(mVeilSurface)
+                    .addTransactionCommittedListener(
+                            mContext.getMainExecutor(), () -> animator.start())
+                    .setAlpha(mVeilSurface, 0);
+        } else {
+            // Show the veil immediately at full opacity.
+            t.show(mVeilSurface).setAlpha(mVeilSurface, 1);
+        }
         mViewHost.getView().getViewRootImpl().applyTransactionOnDraw(t);
     }
 
     /**
+     * Animate veil's alpha to 1, fading it in.
+     */
+    public void showVeil(SurfaceControl parentSurface, Rect taskBounds) {
+        SurfaceControl.Transaction t = mSurfaceControlTransactionSupplier.get();
+        showVeil(t, parentSurface, taskBounds, true /* fadeIn */);
+    }
+
+    /**
      * Update veil bounds to match bounds changes.
      * @param newBounds bounds to update veil to.
      */
@@ -147,6 +167,16 @@
      */
     public void updateResizeVeil(Rect newBounds) {
         SurfaceControl.Transaction t = mSurfaceControlTransactionSupplier.get();
+        updateResizeVeil(t, newBounds);
+    }
+
+    /**
+     * Calls relayout to update task and veil bounds.
+     *
+     * @param t a transaction to be applied in sync with the veil draw.
+     * @param newBounds bounds to update veil to.
+     */
+    public void updateResizeVeil(SurfaceControl.Transaction t, Rect newBounds) {
         relayout(newBounds, t);
         mViewHost.getView().getViewRootImpl().applyTransactionOnDraw(t);
     }
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 58c78e6..ae3b5eb 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
@@ -53,33 +53,33 @@
     private final Rect mTaskBoundsAtDragStart = new Rect();
     private final PointF mRepositionStartPoint = new PointF();
     private final Rect mRepositionTaskBounds = new Rect();
-    // If a task move (not resize) finishes in this region, the positioner will not attempt to
+    // If a task move (not resize) finishes with the positions y less than this value, do not
     // finalize the bounds there using WCT#setBounds
-    private final Rect mDisallowedAreaForEndBounds = new Rect();
+    private final int mDisallowedAreaForEndBoundsHeight;
     private final Supplier<SurfaceControl.Transaction> mTransactionSupplier;
     private int mCtrlType;
 
     public VeiledResizeTaskPositioner(ShellTaskOrganizer taskOrganizer,
             DesktopModeWindowDecoration windowDecoration, DisplayController displayController,
-            Rect disallowedAreaForEndBounds,
             DragPositioningCallbackUtility.DragStartListener dragStartListener,
-            Transitions transitions) {
-        this(taskOrganizer, windowDecoration, displayController, disallowedAreaForEndBounds,
-                dragStartListener, SurfaceControl.Transaction::new, transitions);
+            Transitions transitions,
+            int disallowedAreaForEndBoundsHeight) {
+        this(taskOrganizer, windowDecoration, displayController, dragStartListener,
+                SurfaceControl.Transaction::new, transitions, disallowedAreaForEndBoundsHeight);
     }
 
     public VeiledResizeTaskPositioner(ShellTaskOrganizer taskOrganizer,
             DesktopModeWindowDecoration windowDecoration, DisplayController displayController,
-            Rect disallowedAreaForEndBounds,
             DragPositioningCallbackUtility.DragStartListener dragStartListener,
-            Supplier<SurfaceControl.Transaction> supplier, Transitions transitions) {
+            Supplier<SurfaceControl.Transaction> supplier, Transitions transitions,
+            int disallowedAreaForEndBoundsHeight) {
         mTaskOrganizer = taskOrganizer;
         mDesktopWindowDecoration = windowDecoration;
         mDisplayController = displayController;
         mDragStartListener = dragStartListener;
-        mDisallowedAreaForEndBounds.set(disallowedAreaForEndBounds);
         mTransactionSupplier = supplier;
         mTransitions = transitions;
+        mDisallowedAreaForEndBoundsHeight = disallowedAreaForEndBoundsHeight;
     }
 
     @Override
@@ -98,6 +98,10 @@
         }
         mDragStartListener.onDragStart(mDesktopWindowDecoration.mTaskInfo.taskId);
         mRepositionTaskBounds.set(mTaskBoundsAtDragStart);
+        if (mStableBounds.isEmpty()) {
+            mDisplayController.getDisplayLayout(mDesktopWindowDecoration.mDisplay.getDisplayId())
+                    .getStableBounds(mStableBounds);
+        }
     }
 
     @Override
@@ -110,8 +114,7 @@
         } else if (mCtrlType == CTRL_TYPE_UNDEFINED) {
             final SurfaceControl.Transaction t = mTransactionSupplier.get();
             DragPositioningCallbackUtility.setPositionOnDrag(mDesktopWindowDecoration,
-                    mRepositionTaskBounds, mTaskBoundsAtDragStart, mRepositionStartPoint, t,
-                    x, y);
+                    mRepositionTaskBounds, mTaskBoundsAtDragStart, mRepositionStartPoint, t, x, y);
             t.apply();
         }
     }
@@ -138,9 +141,9 @@
                 // won't be called.
                 mDesktopWindowDecoration.hideResizeVeil();
             }
-        } else if (!mDisallowedAreaForEndBounds.contains((int) x, (int) y)) {
-            DragPositioningCallbackUtility.updateTaskBounds(mRepositionTaskBounds,
-                    mTaskBoundsAtDragStart, mRepositionStartPoint, x, y);
+        } else if (y > mDisallowedAreaForEndBoundsHeight) {
+            DragPositioningCallbackUtility.onDragEnd(mRepositionTaskBounds,
+                    mTaskBoundsAtDragStart, mStableBounds, mRepositionStartPoint, x, y);
             DragPositioningCallbackUtility.applyTaskBoundsChange(new WindowContainerTransaction(),
                     mDesktopWindowDecoration, mRepositionTaskBounds, mTaskOrganizer);
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java
index 9f03d9a..ae1a3d9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java
@@ -22,6 +22,7 @@
 import android.window.TransitionInfo;
 
 import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
+import com.android.wm.shell.splitscreen.SplitScreenController;
 
 /**
  * The interface used by some {@link com.android.wm.shell.ShellTaskOrganizer.TaskListener} to help
@@ -39,6 +40,11 @@
     void setFreeformTaskTransitionStarter(FreeformTaskTransitionStarter transitionStarter);
 
     /**
+     * Sets the {@link SplitScreenController} if available.
+     */
+    void setSplitScreenController(SplitScreenController splitScreenController);
+
+    /**
      * Creates a window decoration for the given task. Can be {@code null} for Fullscreen tasks but
      * not Freeform ones.
      *
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index ac5ff20..0b0d9d5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -64,6 +64,24 @@
         implements AutoCloseable {
 
     /**
+     * The Z-order of {@link #mCaptionContainerSurface}.
+     * <p>
+     * We use {@link #mDecorationContainerSurface} to define input window for task resizing; by
+     * layering it in front of {@link #mCaptionContainerSurface}, we can allow it to handle input
+     * prior to caption view itself, treating corner inputs as resize events rather than
+     * repositioning.
+     */
+    static final int CAPTION_LAYER_Z_ORDER = -1;
+    /**
+     * The Z-order of the task input sink in {@link DragPositioningCallback}.
+     * <p>
+     * This task input sink is used to prevent undesired dispatching of motion events out of task
+     * bounds; by layering it behind {@link #mCaptionContainerSurface}, we allow captions to handle
+     * input events first.
+     */
+    static final int INPUT_SINK_Z_ORDER = -2;
+
+    /**
      * System-wide context. Only used to create context with overridden configurations.
      */
     final Context mContext;
@@ -237,7 +255,9 @@
 
         final int captionHeight = loadDimensionPixelSize(resources, params.mCaptionHeightId);
         final int captionWidth = taskBounds.width();
+
         startT.setWindowCrop(mCaptionContainerSurface, captionWidth, captionHeight)
+                .setLayer(mCaptionContainerSurface, CAPTION_LAYER_Z_ORDER)
                 .show(mCaptionContainerSurface);
 
         if (ViewRootImpl.CAPTION_ON_SHELL) {
@@ -248,6 +268,9 @@
             mCaptionInsetsRect.bottom = mCaptionInsetsRect.top + captionHeight + params.mCaptionY;
             wct.addInsetsSource(mTaskInfo.token,
                     mOwner, 0 /* index */, WindowInsets.Type.captionBar(), mCaptionInsetsRect);
+            wct.addInsetsSource(mTaskInfo.token,
+                    mOwner, 0 /* index */, WindowInsets.Type.mandatorySystemGestures(),
+                    mCaptionInsetsRect);
         } else {
             startT.hide(mCaptionContainerSurface);
         }
@@ -264,6 +287,7 @@
                 .setColor(mTaskSurface, mTmpColor)
                 .show(mTaskSurface);
         finishT.setPosition(mTaskSurface, taskPosition.x, taskPosition.y)
+                .setShadowRadius(mTaskSurface, shadowRadius)
                 .setWindowCrop(mTaskSurface, outResult.mWidth, outResult.mHeight);
         if (mTaskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
             startT.setCornerRadius(mTaskSurface, params.mCornerRadius);
@@ -301,6 +325,10 @@
         }
     }
 
+    int getCaptionHeightId() {
+        return Resources.ID_NULL;
+    }
+
     /**
      * Obtains the {@link Display} instance for the display ID in {@link #mTaskInfo} if it exists or
      * registers {@link #mOnDisplaysChangedListener} if it doesn't.
@@ -345,6 +373,8 @@
         final WindowContainerTransaction wct = mWindowContainerTransactionSupplier.get();
         wct.removeInsetsSource(mTaskInfo.token,
                 mOwner, 0 /* index */, WindowInsets.Type.captionBar());
+        wct.removeInsetsSource(mTaskInfo.token,
+                mOwner, 0 /* index */, WindowInsets.Type.mandatorySystemGestures());
         mTaskOrganizer.applyTransaction(wct);
     }
 
@@ -413,6 +443,21 @@
                 mSurfaceControlTransactionSupplier);
     }
 
+    /**
+     * Adds caption inset source to a WCT
+     */
+    public void addCaptionInset(WindowContainerTransaction wct) {
+        final int captionHeightId = getCaptionHeightId();
+        if (!ViewRootImpl.CAPTION_ON_SHELL || captionHeightId == Resources.ID_NULL) {
+            return;
+        }
+
+        final int captionHeight = loadDimensionPixelSize(mContext.getResources(), captionHeightId);
+        final Rect captionInsets = new Rect(0, 0, 0, captionHeight);
+        wct.addInsetsSource(mTaskInfo.token, mOwner, 0 /* index */, WindowInsets.Type.captionBar(),
+                captionInsets);
+    }
+
     static class RelayoutParams {
         RunningTaskInfo mRunningTaskInfo;
         int mLayoutResId;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeWindowDecorationViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeWindowDecorationViewHolder.kt
index 514ea52..d293cf7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeWindowDecorationViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeWindowDecorationViewHolder.kt
@@ -1,28 +1,35 @@
 package com.android.wm.shell.windowdecor.viewholder
 
 import android.app.ActivityManager.RunningTaskInfo
+import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
 import android.content.Context
 import android.graphics.Color
 import android.view.View
+import android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS
 
 /**
  * Encapsulates the root [View] of a window decoration and its children to facilitate looking up
  * children (via findViewById) and updating to the latest data from [RunningTaskInfo].
  */
 internal abstract class DesktopModeWindowDecorationViewHolder(rootView: View) {
-    val context: Context = rootView.context
+  val context: Context = rootView.context
 
-    /**
-     * A signal to the view holder that new data is available and that the views should be updated
-     * to reflect it.
-     */
-    abstract fun bindData(taskInfo: RunningTaskInfo)
+  /**
+   * A signal to the view holder that new data is available and that the views should be updated to
+   * reflect it.
+   */
+  abstract fun bindData(taskInfo: RunningTaskInfo)
 
-    /**
-     * Whether the caption items should use the 'light' color variant so that there's good contrast
-     * with the caption background color.
-     */
-    protected fun shouldUseLightCaptionColors(taskInfo: RunningTaskInfo): Boolean {
-        return Color.valueOf(taskInfo.taskDescription.statusBarColor).luminance() < 0.5
+  /**
+   * Whether the caption items should use the 'light' color variant so that there's good contrast
+   * with the caption background color.
+   */
+  protected fun shouldUseLightCaptionColors(taskInfo: RunningTaskInfo): Boolean {
+    return if (Color.alpha(taskInfo.taskDescription.statusBarColor) != 0 &&
+        taskInfo.windowingMode == WINDOWING_MODE_FREEFORM) {
+      Color.valueOf(taskInfo.taskDescription.statusBarColor).luminance() < 0.5
+    } else {
+      taskInfo.taskDescription.statusBarAppearance and APPEARANCE_LIGHT_STATUS_BARS == 0
     }
+  }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/Android.bp b/libs/WindowManager/Shell/tests/flicker/Android.bp
index b6696c7..208ae84 100644
--- a/libs/WindowManager/Shell/tests/flicker/Android.bp
+++ b/libs/WindowManager/Shell/tests/flicker/Android.bp
@@ -23,14 +23,40 @@
     default_applicable_licenses: ["frameworks_base_license"],
 }
 
-android_test {
-    name: "WMShellFlickerTests",
+filegroup {
+    name: "WMShellFlickerTestsBase-src",
+    srcs: ["src/com/android/wm/shell/flicker/*.kt"],
+}
+
+filegroup {
+    name: "WMShellFlickerTestsBubbles-src",
+    srcs: ["src/com/android/wm/shell/flicker/bubble/*.kt"],
+}
+
+filegroup {
+    name: "WMShellFlickerTestsPip-src",
+    srcs: ["src/com/android/wm/shell/flicker/pip/*.kt"],
+}
+
+filegroup {
+    name: "WMShellFlickerTestsSplitScreen-src",
     srcs: [
-        "src/**/*.java",
-        "src/**/*.kt",
+        "src/com/android/wm/shell/flicker/splitscreen/*.kt",
+        "src/com/android/wm/shell/flicker/splitscreen/benchmark/*.kt",
     ],
-    manifest: "AndroidManifest.xml",
-    test_config: "AndroidTest.xml",
+}
+
+filegroup {
+    name: "WMShellFlickerServiceTests-src",
+    srcs: [
+        "src/com/android/wm/shell/flicker/service/**/*.kt",
+    ],
+}
+
+java_defaults {
+    name: "WMShellFlickerTestsDefault",
+    manifest: "manifests/AndroidManifest.xml",
+    test_config_template: "AndroidTestTemplate.xml",
     platform_apis: true,
     certificate: "platform",
     optimize: {
@@ -40,16 +66,83 @@
     libs: ["android.test.runner"],
     static_libs: [
         "androidx.test.ext.junit",
+        "flickertestapplib",
         "flickerlib",
-        "flickerlib-apphelpers",
         "flickerlib-helpers",
-        "truth-prebuilt",
-        "app-helpers-core",
+        "platform-test-annotations",
+        "wm-flicker-common-app-helpers",
+        "wm-flicker-common-assertions",
         "launcher-helper-lib",
         "launcher-aosp-tapl",
-        "wm-flicker-common-assertions",
-        "wm-flicker-common-app-helpers",
-        "platform-test-annotations",
-        "flickertestapplib",
+    ],
+    data: [
+        ":FlickerTestApp",
+        "trace_config/*",
+    ],
+}
+
+android_test {
+    name: "WMShellFlickerTestsOther",
+    defaults: ["WMShellFlickerTestsDefault"],
+    additional_manifests: ["manifests/AndroidManifestOther.xml"],
+    package_name: "com.android.wm.shell.flicker",
+    instrumentation_target_package: "com.android.wm.shell.flicker",
+    srcs: [
+        "src/**/*.java",
+        "src/**/*.kt",
+    ],
+    exclude_srcs: [
+        ":WMShellFlickerTestsBubbles-src",
+        ":WMShellFlickerTestsPip-src",
+        ":WMShellFlickerTestsSplitScreen-src",
+        ":WMShellFlickerServiceTests-src",
+    ],
+}
+
+android_test {
+    name: "WMShellFlickerTestsBubbles",
+    defaults: ["WMShellFlickerTestsDefault"],
+    additional_manifests: ["manifests/AndroidManifestBubbles.xml"],
+    package_name: "com.android.wm.shell.flicker.bubbles",
+    instrumentation_target_package: "com.android.wm.shell.flicker.bubbles",
+    srcs: [
+        ":WMShellFlickerTestsBase-src",
+        ":WMShellFlickerTestsBubbles-src",
+    ],
+}
+
+android_test {
+    name: "WMShellFlickerTestsPip",
+    defaults: ["WMShellFlickerTestsDefault"],
+    additional_manifests: ["manifests/AndroidManifestPip.xml"],
+    package_name: "com.android.wm.shell.flicker.pip",
+    instrumentation_target_package: "com.android.wm.shell.flicker.pip",
+    srcs: [
+        ":WMShellFlickerTestsBase-src",
+        ":WMShellFlickerTestsPip-src",
+    ],
+}
+
+android_test {
+    name: "WMShellFlickerTestsSplitScreen",
+    defaults: ["WMShellFlickerTestsDefault"],
+    additional_manifests: ["manifests/AndroidManifestSplitScreen.xml"],
+    package_name: "com.android.wm.shell.flicker.splitscreen",
+    instrumentation_target_package: "com.android.wm.shell.flicker.splitscreen",
+    srcs: [
+        ":WMShellFlickerTestsBase-src",
+        ":WMShellFlickerTestsSplitScreen-src",
+    ],
+}
+
+android_test {
+    name: "WMShellFlickerServiceTests",
+    defaults: ["WMShellFlickerTestsDefault"],
+    additional_manifests: ["manifests/AndroidManifestService.xml"],
+    package_name: "com.android.wm.shell.flicker.service",
+    instrumentation_target_package: "com.android.wm.shell.flicker.service",
+    srcs: [
+        ":WMShellFlickerTestsBase-src",
+        ":WMShellFlickerServiceTests-src",
     ],
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/AndroidManifest.xml b/libs/WindowManager/Shell/tests/flicker/AndroidManifest.xml
deleted file mode 100644
index 4721741..0000000
--- a/libs/WindowManager/Shell/tests/flicker/AndroidManifest.xml
+++ /dev/null
@@ -1,66 +0,0 @@
-<?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.wm.shell.flicker">
-
-    <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29"/>
-    <!-- Read and write traces from external storage -->
-    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
-    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
-    <!-- Allow the test to write directly to /sdcard/ -->
-    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
-    <!-- Write secure settings -->
-    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
-    <!-- Capture screen contents -->
-    <uses-permission android:name="android.permission.ACCESS_SURFACE_FLINGER" />
-    <!-- Enable / Disable tracing !-->
-    <uses-permission android:name="android.permission.DUMP" />
-    <!-- Run layers trace -->
-    <uses-permission android:name="android.permission.HARDWARE_TEST"/>
-    <!-- Capture screen recording -->
-    <uses-permission android:name="android.permission.CAPTURE_VIDEO_OUTPUT"/>
-    <!-- Workaround grant runtime permission exception from b/152733071 -->
-    <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS"/>
-    <uses-permission android:name="android.permission.READ_LOGS"/>
-    <!-- Force-stop test apps -->
-    <uses-permission android:name="android.permission.FORCE_STOP_PACKAGES"/>
-    <!-- Control test app's media session -->
-    <uses-permission android:name="android.permission.MEDIA_CONTENT_CONTROL"/>
-    <!-- ATM.removeRootTasksWithActivityTypes() -->
-    <uses-permission android:name="android.permission.MANAGE_ACTIVITY_TASKS" />
-    <!-- Enable bubble notification-->
-    <uses-permission android:name="android.permission.STATUS_BAR_SERVICE" />
-
-    <!-- Allow the test to write directly to /sdcard/ -->
-    <application android:requestLegacyExternalStorage="true" android:largeHeap="true">
-        <uses-library android:name="android.test.runner"/>
-
-        <service android:name=".NotificationListener"
-                 android:exported="true"
-                 android:label="WMShellTestsNotificationListenerService"
-                 android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
-            <intent-filter>
-                <action android:name="android.service.notification.NotificationListenerService" />
-            </intent-filter>
-        </service>
-    </application>
-
-    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="com.android.wm.shell.flicker"
-                     android:label="WindowManager Shell Flicker Tests">
-    </instrumentation>
-</manifest>
diff --git a/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml b/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml
deleted file mode 100644
index b5937ae..0000000
--- a/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml
+++ /dev/null
@@ -1,53 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- * Copyright 2020 Google Inc. All Rights Reserved.
- -->
-<configuration description="Runs WindowManager Shell Flicker Tests">
-    <option name="test-tag" value="FlickerTests" />
-    <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
-        <!-- keeps the screen on during tests -->
-        <option name="screen-always-on" value="on" />
-        <!-- prevents the phone from restarting -->
-        <option name="force-skip-system-props" value="true" />
-        <!-- set WM tracing verbose level to all -->
-        <option name="run-command" value="cmd window tracing level all" />
-        <!-- set WM tracing to frame (avoid incomplete states) -->
-        <option name="run-command" value="cmd window tracing frame" />
-        <!-- disable betterbug as it's log collection dialogues cause flakes in e2e tests -->
-        <option name="run-command" value="pm disable com.google.android.internal.betterbug" />
-        <!-- ensure lock screen mode is swipe -->
-        <option name="run-command" value="locksettings set-disabled false" />
-        <!-- restart launcher to activate TAPL -->
-        <option name="run-command" value="setprop ro.test_harness 1 ; am force-stop com.google.android.apps.nexuslauncher" />
-        <!-- Ensure output directory is empty at the start -->
-        <option name="run-command" value="rm -rf /sdcard/flicker" />
-        <!-- Increase trace size: 20mb for WM and 80mb for SF -->
-        <option name="run-command" value="cmd window tracing size 20480" />
-        <option name="run-command" value="su root service call SurfaceFlinger 1029 i32 81920" />
-    </target_preparer>
-    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
-        <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="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" />
-    </target_preparer>
-    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
-        <option name="cleanup-apks" value="true"/>
-        <option name="test-file-name" value="WMShellFlickerTests.apk"/>
-        <option name="test-file-name" value="FlickerTestApp.apk" />
-    </target_preparer>
-    <test class="com.android.tradefed.testtype.AndroidJUnitTest">
-        <option name="package" value="com.android.wm.shell.flicker"/>
-        <option name="shell-timeout" value="6600s" />
-        <option name="test-timeout" value="6000s" />
-        <option name="hidden-api-checks" value="false" />
-    </test>
-    <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
-        <option name="directory-keys" value="/sdcard/flicker" />
-        <option name="collect-on-run-ended-only" value="true" />
-        <option name="clean-up" value="true" />
-    </metrics_collector>
-</configuration>
diff --git a/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml
new file mode 100644
index 0000000..87b20ed
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml
@@ -0,0 +1,101 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<configuration description="Runs WindowManager Shell Flicker Tests {MODULE}">
+    <option name="test-tag" value="FlickerTests"/>
+    <!-- Needed for storing the perfetto trace files in the sdcard/test_results-->
+    <option name="isolated-storage" value="false"/>
+
+    <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+        <!-- keeps the screen on during tests -->
+        <option name="screen-always-on" value="on"/>
+        <!-- prevents the phone from restarting -->
+        <option name="force-skip-system-props" value="true"/>
+        <!-- set WM tracing verbose level to all -->
+        <option name="run-command" value="cmd window tracing level all"/>
+        <!-- set WM tracing to frame (avoid incomplete states) -->
+        <option name="run-command" value="cmd window tracing frame"/>
+        <!-- disable betterbug as it's log collection dialogues cause flakes in e2e tests -->
+        <option name="run-command" value="pm disable com.google.android.internal.betterbug"/>
+        <!-- ensure lock screen mode is swipe -->
+        <option name="run-command" value="locksettings set-disabled false"/>
+        <!-- restart launcher to activate TAPL -->
+        <option name="run-command"
+                value="setprop ro.test_harness 1 ; am force-stop com.google.android.apps.nexuslauncher"/>
+        <!-- Increase trace size: 20mb for WM and 80mb for SF -->
+        <option name="run-command" value="cmd window tracing size 20480"/>
+        <option name="run-command" value="su root service call SurfaceFlinger 1029 i32 81920"/>
+    </target_preparer>
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <option name="test-user-token" value="%TEST_USER%"/>
+        <option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/>
+        <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="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"/>
+    </target_preparer>
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true"/>
+        <option name="test-file-name" value="{MODULE}.apk"/>
+        <option name="test-file-name" value="FlickerTestApp.apk"/>
+    </target_preparer>
+    <!-- Needed for pushing the trace config file -->
+    <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
+    <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+        <option name="push-file"
+                key="trace_config.textproto"
+                value="/data/misc/perfetto-traces/trace_config.textproto"
+        />
+        <!--Install the content provider automatically when we push some file in sdcard folder.-->
+        <!--Needed to avoid the installation during the test suite.-->
+        <option name="push-file" key="trace_config.textproto" value="/sdcard/sample.textproto"/>
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+        <option name="package" value="{PACKAGE}"/>
+        <option name="shell-timeout" value="6600s"/>
+        <option name="test-timeout" value="6000s"/>
+        <option name="hidden-api-checks" value="false"/>
+        <option name="device-listeners" value="android.device.collectors.PerfettoListener"/>
+        <!-- PerfettoListener related arguments -->
+        <option name="instrumentation-arg" key="perfetto_config_text_proto" value="true"/>
+        <option name="instrumentation-arg"
+                key="perfetto_config_file"
+                value="trace_config.textproto"
+        />
+        <option name="instrumentation-arg" key="per_run" value="true"/>
+    </test>
+    <!-- Needed for pulling the collected trace config on to the host -->
+    <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+        <option name="pull-pattern-keys" value="perfetto_file_path"/>
+        <option name="directory-keys"
+                value="/data/user/0/com.android.wm.shell.flicker/files"/>
+        <option name="directory-keys"
+                value="/data/user/0/com.android.wm.shell.flicker.bubbles/files"/>
+        <option name="directory-keys"
+                value="/data/user/0/com.android.wm.shell.flicker.pip/files"/>
+        <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.server.wm.flicker.service/files"/>
+        <option name="collect-on-run-ended-only" value="true"/>
+        <option name="clean-up" value="true"/>
+    </metrics_collector>
+</configuration>
diff --git a/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifest.xml b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifest.xml
new file mode 100644
index 0000000..6a87de4
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifest.xml
@@ -0,0 +1,68 @@
+<?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"
+          xmlns:tools="http://schemas.android.com/tools"
+          package="com.android.wm.shell.flicker">
+
+    <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29"/>
+    <!-- Read and write traces from external storage -->
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+    <!-- Allow the test to write directly to /sdcard/ -->
+    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
+    <!-- Write secure settings -->
+    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
+    <!-- Capture screen contents -->
+    <uses-permission android:name="android.permission.ACCESS_SURFACE_FLINGER" />
+    <!-- Enable / Disable tracing !-->
+    <uses-permission android:name="android.permission.DUMP" />
+    <!-- Run layers trace -->
+    <uses-permission android:name="android.permission.HARDWARE_TEST"/>
+    <!-- Capture screen recording -->
+    <uses-permission android:name="android.permission.CAPTURE_VIDEO_OUTPUT"/>
+    <!-- Workaround grant runtime permission exception from b/152733071 -->
+    <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS"/>
+    <uses-permission android:name="android.permission.READ_LOGS"/>
+    <!-- Force-stop test apps -->
+    <uses-permission android:name="android.permission.FORCE_STOP_PACKAGES"/>
+    <!-- Control test app's media session -->
+    <uses-permission android:name="android.permission.MEDIA_CONTENT_CONTROL"/>
+    <!-- ATM.removeRootTasksWithActivityTypes() -->
+    <uses-permission android:name="android.permission.MANAGE_ACTIVITY_TASKS" />
+    <!-- Enable bubble notification-->
+    <uses-permission android:name="android.permission.STATUS_BAR_SERVICE" />
+
+    <!-- Allow the test to write directly to /sdcard/ -->
+    <application android:requestLegacyExternalStorage="true" android:largeHeap="true">
+        <uses-library android:name="android.test.runner"/>
+
+        <service android:name=".NotificationListener"
+                 android:exported="true"
+                 android:label="WMShellTestsNotificationListenerService"
+                 android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
+            <intent-filter>
+                <action android:name="android.service.notification.NotificationListenerService" />
+            </intent-filter>
+        </service>
+
+        <!-- (b/197936012) Remove startup provider due to test timeout issue -->
+        <provider
+            android:name="androidx.startup.InitializationProvider"
+            android:authorities="${applicationId}.androidx-startup"
+            tools:node="remove" />
+    </application>
+</manifest>
diff --git a/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestBubbles.xml b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestBubbles.xml
new file mode 100644
index 0000000..437871f
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestBubbles.xml
@@ -0,0 +1,24 @@
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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.wm.shell.flicker.bubble">
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.wm.shell.flicker.bubble"
+                     android:label="WindowManager Flicker Tests">
+    </instrumentation>
+</manifest>
diff --git a/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestOther.xml b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestOther.xml
new file mode 100644
index 0000000..cf642f6
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestOther.xml
@@ -0,0 +1,24 @@
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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.wm.shell.flicker">
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.wm.shell.flicker"
+                     android:label="WindowManager Flicker Tests">
+    </instrumentation>
+</manifest>
diff --git a/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestPip.xml b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestPip.xml
new file mode 100644
index 0000000..5a8155a
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestPip.xml
@@ -0,0 +1,24 @@
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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.wm.shell.flicker.pip">
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.wm.shell.flicker.pip"
+                     android:label="WindowManager Flicker Tests">
+    </instrumentation>
+</manifest>
diff --git a/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestService.xml b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestService.xml
new file mode 100644
index 0000000..c7aca1a
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestService.xml
@@ -0,0 +1,24 @@
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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.wm.shell.flicker.service">
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.wm.shell.flicker.service"
+                     android:label="WindowManager Flicker Service Tests">
+    </instrumentation>
+</manifest>
diff --git a/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestSplitScreen.xml b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestSplitScreen.xml
new file mode 100644
index 0000000..887d8db
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestSplitScreen.xml
@@ -0,0 +1,24 @@
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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.wm.shell.flicker.splitscreen">
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.wm.shell.flicker.splitscreen"
+                     android:label="WindowManager Flicker Tests">
+    </instrumentation>
+</manifest>
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseBenchmarkTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseBenchmarkTest.kt
index e06e074..0f3e0f5 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseBenchmarkTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseBenchmarkTest.kt
@@ -19,14 +19,14 @@
 import android.app.Instrumentation
 import android.tools.device.flicker.junit.FlickerBuilderProvider
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTest
 import androidx.test.platform.app.InstrumentationRegistry
 import com.android.launcher3.tapl.LauncherInstrumentation
 
 abstract class BaseBenchmarkTest
 @JvmOverloads
 constructor(
-    protected open val flicker: FlickerTest,
+    protected open val flicker: LegacyFlickerTest,
     protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation(),
     protected val tapl: LauncherInstrumentation = LauncherInstrumentation()
 ) {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt
index c98c5a0..d2fe9fe 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt
@@ -18,7 +18,7 @@
 
 import android.app.Instrumentation
 import android.tools.common.traces.component.ComponentNameMatcher
-import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTest
 import androidx.test.platform.app.InstrumentationRegistry
 import com.android.launcher3.tapl.LauncherInstrumentation
 
@@ -30,7 +30,7 @@
 abstract class BaseTest
 @JvmOverloads
 constructor(
-    override val flicker: FlickerTest,
+    override val flicker: LegacyFlickerTest,
     instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation(),
     tapl: LauncherInstrumentation = LauncherInstrumentation()
 ) : BaseBenchmarkTest(flicker, instrumentation, tapl), ICommonAssertions
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
index 798cc95..9cc03a5 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
@@ -23,18 +23,18 @@
 import android.tools.common.flicker.subject.layers.LayerTraceEntrySubject
 import android.tools.common.flicker.subject.layers.LayersTraceSubject
 import android.tools.common.traces.component.IComponentMatcher
-import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.helpers.WindowUtils
 
-fun FlickerTest.appPairsDividerIsVisibleAtEnd() {
+fun LegacyFlickerTest.appPairsDividerIsVisibleAtEnd() {
     assertLayersEnd { this.isVisible(APP_PAIR_SPLIT_DIVIDER_COMPONENT) }
 }
 
-fun FlickerTest.appPairsDividerIsInvisibleAtEnd() {
+fun LegacyFlickerTest.appPairsDividerIsInvisibleAtEnd() {
     assertLayersEnd { this.notContains(APP_PAIR_SPLIT_DIVIDER_COMPONENT) }
 }
 
-fun FlickerTest.appPairsDividerBecomesVisible() {
+fun LegacyFlickerTest.appPairsDividerBecomesVisible() {
     assertLayers {
         this.isInvisible(DOCKED_STACK_DIVIDER_COMPONENT)
             .then()
@@ -42,7 +42,7 @@
     }
 }
 
-fun FlickerTest.splitScreenEntered(
+fun LegacyFlickerTest.splitScreenEntered(
     component1: IComponentMatcher,
     component2: IComponentMatcher,
     fromOtherApp: Boolean,
@@ -69,7 +69,7 @@
     splitScreenDividerIsVisibleAtEnd()
 }
 
-fun FlickerTest.splitScreenDismissed(
+fun LegacyFlickerTest.splitScreenDismissed(
     component1: IComponentMatcher,
     component2: IComponentMatcher,
     toHome: Boolean
@@ -87,27 +87,27 @@
     splitScreenDividerIsInvisibleAtEnd()
 }
 
-fun FlickerTest.splitScreenDividerIsVisibleAtStart() {
+fun LegacyFlickerTest.splitScreenDividerIsVisibleAtStart() {
     assertLayersStart { this.isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT) }
 }
 
-fun FlickerTest.splitScreenDividerIsVisibleAtEnd() {
+fun LegacyFlickerTest.splitScreenDividerIsVisibleAtEnd() {
     assertLayersEnd { this.isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT) }
 }
 
-fun FlickerTest.splitScreenDividerIsInvisibleAtStart() {
+fun LegacyFlickerTest.splitScreenDividerIsInvisibleAtStart() {
     assertLayersStart { this.isInvisible(SPLIT_SCREEN_DIVIDER_COMPONENT) }
 }
 
-fun FlickerTest.splitScreenDividerIsInvisibleAtEnd() {
+fun LegacyFlickerTest.splitScreenDividerIsInvisibleAtEnd() {
     assertLayersEnd { this.isInvisible(SPLIT_SCREEN_DIVIDER_COMPONENT) }
 }
 
-fun FlickerTest.splitScreenDividerBecomesVisible() {
+fun LegacyFlickerTest.splitScreenDividerBecomesVisible() {
     layerBecomesVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
 }
 
-fun FlickerTest.splitScreenDividerBecomesInvisible() {
+fun LegacyFlickerTest.splitScreenDividerBecomesInvisible() {
     assertLayers {
         this.isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
             .then()
@@ -115,23 +115,23 @@
     }
 }
 
-fun FlickerTest.layerBecomesVisible(component: IComponentMatcher) {
+fun LegacyFlickerTest.layerBecomesVisible(component: IComponentMatcher) {
     assertLayers { this.isInvisible(component).then().isVisible(component) }
 }
 
-fun FlickerTest.layerBecomesInvisible(component: IComponentMatcher) {
+fun LegacyFlickerTest.layerBecomesInvisible(component: IComponentMatcher) {
     assertLayers { this.isVisible(component).then().isInvisible(component) }
 }
 
-fun FlickerTest.layerIsVisibleAtEnd(component: IComponentMatcher) {
+fun LegacyFlickerTest.layerIsVisibleAtEnd(component: IComponentMatcher) {
     assertLayersEnd { this.isVisible(component) }
 }
 
-fun FlickerTest.layerKeepVisible(component: IComponentMatcher) {
+fun LegacyFlickerTest.layerKeepVisible(component: IComponentMatcher) {
     assertLayers { this.isVisible(component) }
 }
 
-fun FlickerTest.splitAppLayerBoundsBecomesVisible(
+fun LegacyFlickerTest.splitAppLayerBoundsBecomesVisible(
     component: IComponentMatcher,
     landscapePosLeft: Boolean,
     portraitPosTop: Boolean
@@ -150,7 +150,7 @@
     }
 }
 
-fun FlickerTest.splitAppLayerBoundsBecomesVisibleByDrag(component: IComponentMatcher) {
+fun LegacyFlickerTest.splitAppLayerBoundsBecomesVisibleByDrag(component: IComponentMatcher) {
     assertLayers {
         this.notContains(SPLIT_SCREEN_DIVIDER_COMPONENT.or(component), isOptional = true)
             .then()
@@ -161,7 +161,7 @@
     }
 }
 
-fun FlickerTest.splitAppLayerBoundsBecomesInvisible(
+fun LegacyFlickerTest.splitAppLayerBoundsBecomesInvisible(
     component: IComponentMatcher,
     landscapePosLeft: Boolean,
     portraitPosTop: Boolean
@@ -180,7 +180,7 @@
     }
 }
 
-fun FlickerTest.splitAppLayerBoundsIsVisibleAtEnd(
+fun LegacyFlickerTest.splitAppLayerBoundsIsVisibleAtEnd(
     component: IComponentMatcher,
     landscapePosLeft: Boolean,
     portraitPosTop: Boolean
@@ -195,7 +195,7 @@
     }
 }
 
-fun FlickerTest.splitAppLayerBoundsKeepVisible(
+fun LegacyFlickerTest.splitAppLayerBoundsKeepVisible(
     component: IComponentMatcher,
     landscapePosLeft: Boolean,
     portraitPosTop: Boolean
@@ -210,7 +210,7 @@
     }
 }
 
-fun FlickerTest.splitAppLayerBoundsChanges(
+fun LegacyFlickerTest.splitAppLayerBoundsChanges(
     component: IComponentMatcher,
     landscapePosLeft: Boolean,
     portraitPosTop: Boolean
@@ -304,7 +304,7 @@
     }
 }
 
-fun FlickerTest.appWindowBecomesVisible(component: IComponentMatcher) {
+fun LegacyFlickerTest.appWindowBecomesVisible(component: IComponentMatcher) {
     assertWm {
         this.isAppWindowInvisible(component)
             .then()
@@ -316,39 +316,39 @@
     }
 }
 
-fun FlickerTest.appWindowBecomesInvisible(component: IComponentMatcher) {
+fun LegacyFlickerTest.appWindowBecomesInvisible(component: IComponentMatcher) {
     assertWm { this.isAppWindowVisible(component).then().isAppWindowInvisible(component) }
 }
 
-fun FlickerTest.appWindowIsVisibleAtStart(component: IComponentMatcher) {
+fun LegacyFlickerTest.appWindowIsVisibleAtStart(component: IComponentMatcher) {
     assertWmStart { this.isAppWindowVisible(component) }
 }
 
-fun FlickerTest.appWindowIsVisibleAtEnd(component: IComponentMatcher) {
+fun LegacyFlickerTest.appWindowIsVisibleAtEnd(component: IComponentMatcher) {
     assertWmEnd { this.isAppWindowVisible(component) }
 }
 
-fun FlickerTest.appWindowIsInvisibleAtStart(component: IComponentMatcher) {
+fun LegacyFlickerTest.appWindowIsInvisibleAtStart(component: IComponentMatcher) {
     assertWmStart { this.isAppWindowInvisible(component) }
 }
 
-fun FlickerTest.appWindowIsInvisibleAtEnd(component: IComponentMatcher) {
+fun LegacyFlickerTest.appWindowIsInvisibleAtEnd(component: IComponentMatcher) {
     assertWmEnd { this.isAppWindowInvisible(component) }
 }
 
-fun FlickerTest.appWindowIsNotContainAtStart(component: IComponentMatcher) {
+fun LegacyFlickerTest.appWindowIsNotContainAtStart(component: IComponentMatcher) {
     assertWmStart { this.notContains(component) }
 }
 
-fun FlickerTest.appWindowKeepVisible(component: IComponentMatcher) {
+fun LegacyFlickerTest.appWindowKeepVisible(component: IComponentMatcher) {
     assertWm { this.isAppWindowVisible(component) }
 }
 
-fun FlickerTest.dockedStackDividerIsVisibleAtEnd() {
+fun LegacyFlickerTest.dockedStackDividerIsVisibleAtEnd() {
     assertLayersEnd { this.isVisible(DOCKED_STACK_DIVIDER_COMPONENT) }
 }
 
-fun FlickerTest.dockedStackDividerBecomesVisible() {
+fun LegacyFlickerTest.dockedStackDividerBecomesVisible() {
     assertLayers {
         this.isInvisible(DOCKED_STACK_DIVIDER_COMPONENT)
             .then()
@@ -356,7 +356,7 @@
     }
 }
 
-fun FlickerTest.dockedStackDividerBecomesInvisible() {
+fun LegacyFlickerTest.dockedStackDividerBecomesInvisible() {
     assertLayers {
         this.isVisible(DOCKED_STACK_DIVIDER_COMPONENT)
             .then()
@@ -364,11 +364,11 @@
     }
 }
 
-fun FlickerTest.dockedStackDividerNotExistsAtEnd() {
+fun LegacyFlickerTest.dockedStackDividerNotExistsAtEnd() {
     assertLayersEnd { this.notContains(DOCKED_STACK_DIVIDER_COMPONENT) }
 }
 
-fun FlickerTest.appPairsPrimaryBoundsIsVisibleAtEnd(
+fun LegacyFlickerTest.appPairsPrimaryBoundsIsVisibleAtEnd(
     rotation: Rotation,
     primaryComponent: IComponentMatcher
 ) {
@@ -380,7 +380,7 @@
     }
 }
 
-fun FlickerTest.dockedStackPrimaryBoundsIsVisibleAtEnd(
+fun LegacyFlickerTest.dockedStackPrimaryBoundsIsVisibleAtEnd(
     rotation: Rotation,
     primaryComponent: IComponentMatcher
 ) {
@@ -392,7 +392,7 @@
     }
 }
 
-fun FlickerTest.appPairsSecondaryBoundsIsVisibleAtEnd(
+fun LegacyFlickerTest.appPairsSecondaryBoundsIsVisibleAtEnd(
     rotation: Rotation,
     secondaryComponent: IComponentMatcher
 ) {
@@ -404,7 +404,7 @@
     }
 }
 
-fun FlickerTest.dockedStackSecondaryBoundsIsVisibleAtEnd(
+fun LegacyFlickerTest.dockedStackSecondaryBoundsIsVisibleAtEnd(
     rotation: Rotation,
     secondaryComponent: IComponentMatcher
 ) {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/ICommonAssertions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/ICommonAssertions.kt
index 02d9a056..7b32901 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/ICommonAssertions.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/ICommonAssertions.kt
@@ -18,7 +18,7 @@
 
 import android.platform.test.annotations.Presubmit
 import android.tools.common.traces.component.ComponentNameMatcher
-import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTest
 import com.android.server.wm.flicker.entireScreenCovered
 import com.android.server.wm.flicker.navBarLayerIsVisibleAtStartAndEnd
 import com.android.server.wm.flicker.navBarLayerPositionAtStartAndEnd
@@ -32,7 +32,7 @@
 import org.junit.Test
 
 interface ICommonAssertions {
-    val flicker: FlickerTest
+    val flicker: LegacyFlickerTest
 
     /** Checks that all parts of the screen are covered during the transition */
     @Presubmit @Test fun entireScreenCovered() = flicker.entireScreenCovered()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/SplitScreenUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/SplitScreenUtils.kt
new file mode 100644
index 0000000..8a3c2c9
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/SplitScreenUtils.kt
@@ -0,0 +1,385 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.flicker
+
+import android.app.Instrumentation
+import android.graphics.Point
+import android.os.SystemClock
+import android.tools.common.traces.component.ComponentNameMatcher
+import android.tools.common.traces.component.IComponentMatcher
+import android.tools.common.traces.component.IComponentNameMatcher
+import android.tools.device.apphelpers.StandardAppHelper
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import android.tools.device.traces.parsers.toFlickerComponent
+import android.view.InputDevice
+import android.view.MotionEvent
+import android.view.ViewConfiguration
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.BySelector
+import androidx.test.uiautomator.UiDevice
+import androidx.test.uiautomator.UiObject2
+import androidx.test.uiautomator.Until
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.ImeAppHelper
+import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
+import com.android.server.wm.flicker.helpers.NotificationAppHelper
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.server.wm.flicker.testapp.ActivityOptions
+import com.android.server.wm.flicker.testapp.ActivityOptions.SplitScreen.Primary
+import org.junit.Assert.assertNotNull
+
+object SplitScreenUtils {
+    private const val TIMEOUT_MS = 3_000L
+    private const val DRAG_DURATION_MS = 1_000L
+    private const val NOTIFICATION_SCROLLER = "notification_stack_scroller"
+    private const val DIVIDER_BAR = "docked_divider_handle"
+    private const val OVERVIEW_SNAPSHOT = "snapshot"
+    private const val GESTURE_STEP_MS = 16L
+    private val LONG_PRESS_TIME_MS = ViewConfiguration.getLongPressTimeout() * 2L
+    private val SPLIT_DECOR_MANAGER = ComponentNameMatcher("", "SplitDecorManager#")
+
+    private val notificationScrollerSelector: BySelector
+        get() = By.res(SYSTEM_UI_PACKAGE_NAME, NOTIFICATION_SCROLLER)
+    private val notificationContentSelector: BySelector
+        get() = By.text("Flicker Test Notification")
+    private val dividerBarSelector: BySelector
+        get() = By.res(SYSTEM_UI_PACKAGE_NAME, DIVIDER_BAR)
+    private val overviewSnapshotSelector: BySelector
+        get() = By.res(LAUNCHER_UI_PACKAGE_NAME, OVERVIEW_SNAPSHOT)
+
+    fun getPrimary(instrumentation: Instrumentation): StandardAppHelper =
+        SimpleAppHelper(
+            instrumentation,
+            ActivityOptions.SplitScreen.Primary.LABEL,
+            ActivityOptions.SplitScreen.Primary.COMPONENT.toFlickerComponent()
+        )
+
+    fun getSecondary(instrumentation: Instrumentation): StandardAppHelper =
+        SimpleAppHelper(
+            instrumentation,
+            ActivityOptions.SplitScreen.Secondary.LABEL,
+            ActivityOptions.SplitScreen.Secondary.COMPONENT.toFlickerComponent()
+        )
+
+    fun getNonResizeable(instrumentation: Instrumentation): NonResizeableAppHelper =
+        NonResizeableAppHelper(instrumentation)
+
+    fun getSendNotification(instrumentation: Instrumentation): NotificationAppHelper =
+        NotificationAppHelper(instrumentation)
+
+    fun getIme(instrumentation: Instrumentation): ImeAppHelper = ImeAppHelper(instrumentation)
+
+    fun waitForSplitComplete(
+        wmHelper: WindowManagerStateHelper,
+        primaryApp: IComponentMatcher,
+        secondaryApp: IComponentMatcher,
+    ) {
+        wmHelper
+            .StateSyncBuilder()
+            .withWindowSurfaceAppeared(primaryApp)
+            .withWindowSurfaceAppeared(secondaryApp)
+            .withSplitDividerVisible()
+            .waitForAndVerify()
+    }
+
+    fun enterSplit(
+        wmHelper: WindowManagerStateHelper,
+        tapl: LauncherInstrumentation,
+        device: UiDevice,
+        primaryApp: StandardAppHelper,
+        secondaryApp: StandardAppHelper
+    ) {
+        primaryApp.launchViaIntent(wmHelper)
+        secondaryApp.launchViaIntent(wmHelper)
+        tapl.goHome()
+        wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
+        splitFromOverview(tapl, device)
+        waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+    }
+
+    fun enterSplitViaIntent(
+        wmHelper: WindowManagerStateHelper,
+        primaryApp: StandardAppHelper,
+        secondaryApp: StandardAppHelper
+    ) {
+        val stringExtras = mapOf(Primary.EXTRA_LAUNCH_ADJACENT to "true")
+        primaryApp.launchViaIntent(wmHelper, null, null, stringExtras)
+        waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+    }
+
+    fun splitFromOverview(tapl: LauncherInstrumentation, device: UiDevice) {
+        // Note: The initial split position in landscape is different between tablet and phone.
+        // In landscape, tablet will let the first app split to right side, and phone will
+        // split to left side.
+        if (tapl.isTablet) {
+            // TAPL's currentTask on tablet is sometimes not what we expected if the overview
+            // contains more than 3 task views. We need to use uiautomator directly to find the
+            // second task to split.
+            tapl.workspace.switchToOverview().overviewActions.clickSplit()
+            val snapshots = device.wait(Until.findObjects(overviewSnapshotSelector), TIMEOUT_MS)
+            if (snapshots == null || snapshots.size < 1) {
+                error("Fail to find a overview snapshot to split.")
+            }
+
+            // Find the second task in the upper right corner in split select mode by sorting
+            // 'left' in descending order and 'top' in ascending order.
+            snapshots.sortWith { t1: UiObject2, t2: UiObject2 ->
+                t2.getVisibleBounds().left - t1.getVisibleBounds().left
+            }
+            snapshots.sortWith { t1: UiObject2, t2: UiObject2 ->
+                t1.getVisibleBounds().top - t2.getVisibleBounds().top
+            }
+            snapshots[0].click()
+        } else {
+            tapl.workspace
+                .switchToOverview()
+                .currentTask
+                .tapMenu()
+                .tapSplitMenuItem()
+                .currentTask
+                .open()
+        }
+        SystemClock.sleep(TIMEOUT_MS)
+    }
+
+    fun dragFromNotificationToSplit(
+        instrumentation: Instrumentation,
+        device: UiDevice,
+        wmHelper: WindowManagerStateHelper
+    ) {
+        val displayBounds =
+            wmHelper.currentState.layerState.displays.firstOrNull { !it.isVirtual }?.layerStackSpace
+                ?: error("Display not found")
+
+        // Pull down the notifications
+        device.swipe(
+            displayBounds.centerX(),
+            5,
+            displayBounds.centerX(),
+            displayBounds.bottom,
+            50 /* steps */
+        )
+        SystemClock.sleep(TIMEOUT_MS)
+
+        // Find the target notification
+        val notificationScroller =
+            device.wait(Until.findObject(notificationScrollerSelector), TIMEOUT_MS)
+                ?: error("Unable to find view $notificationScrollerSelector")
+        var notificationContent = notificationScroller.findObject(notificationContentSelector)
+
+        while (notificationContent == null) {
+            device.swipe(
+                displayBounds.centerX(),
+                displayBounds.centerY(),
+                displayBounds.centerX(),
+                displayBounds.centerY() - 150,
+                20 /* steps */
+            )
+            notificationContent = notificationScroller.findObject(notificationContentSelector)
+        }
+
+        // Drag to split
+        val dragStart = notificationContent.visibleCenter
+        val dragMiddle = Point(dragStart.x + 50, dragStart.y)
+        val dragEnd = Point(displayBounds.width / 4, displayBounds.width / 4)
+        val downTime = SystemClock.uptimeMillis()
+
+        touch(instrumentation, MotionEvent.ACTION_DOWN, downTime, downTime, TIMEOUT_MS, dragStart)
+        // It needs a horizontal movement to trigger the drag
+        touchMove(
+            instrumentation,
+            downTime,
+            SystemClock.uptimeMillis(),
+            DRAG_DURATION_MS,
+            dragStart,
+            dragMiddle
+        )
+        touchMove(
+            instrumentation,
+            downTime,
+            SystemClock.uptimeMillis(),
+            DRAG_DURATION_MS,
+            dragMiddle,
+            dragEnd
+        )
+        // Wait for a while to start splitting
+        SystemClock.sleep(TIMEOUT_MS)
+        touch(
+            instrumentation,
+            MotionEvent.ACTION_UP,
+            downTime,
+            SystemClock.uptimeMillis(),
+            GESTURE_STEP_MS,
+            dragEnd
+        )
+        SystemClock.sleep(TIMEOUT_MS)
+    }
+
+    fun touch(
+        instrumentation: Instrumentation,
+        action: Int,
+        downTime: Long,
+        eventTime: Long,
+        duration: Long,
+        point: Point
+    ) {
+        val motionEvent =
+            MotionEvent.obtain(downTime, eventTime, action, point.x.toFloat(), point.y.toFloat(), 0)
+        motionEvent.source = InputDevice.SOURCE_TOUCHSCREEN
+        instrumentation.uiAutomation.injectInputEvent(motionEvent, true)
+        motionEvent.recycle()
+        SystemClock.sleep(duration)
+    }
+
+    fun touchMove(
+        instrumentation: Instrumentation,
+        downTime: Long,
+        eventTime: Long,
+        duration: Long,
+        from: Point,
+        to: Point
+    ) {
+        val steps: Long = duration / GESTURE_STEP_MS
+        var currentTime = eventTime
+        var currentX = from.x.toFloat()
+        var currentY = from.y.toFloat()
+        val stepX = (to.x.toFloat() - from.x.toFloat()) / steps.toFloat()
+        val stepY = (to.y.toFloat() - from.y.toFloat()) / steps.toFloat()
+
+        for (i in 1..steps) {
+            val motionMove =
+                MotionEvent.obtain(
+                    downTime,
+                    currentTime,
+                    MotionEvent.ACTION_MOVE,
+                    currentX,
+                    currentY,
+                    0
+                )
+            motionMove.source = InputDevice.SOURCE_TOUCHSCREEN
+            instrumentation.uiAutomation.injectInputEvent(motionMove, true)
+            motionMove.recycle()
+
+            currentTime += GESTURE_STEP_MS
+            if (i == steps - 1) {
+                currentX = to.x.toFloat()
+                currentY = to.y.toFloat()
+            } else {
+                currentX += stepX
+                currentY += stepY
+            }
+            SystemClock.sleep(GESTURE_STEP_MS)
+        }
+    }
+
+    fun createShortcutOnHotseatIfNotExist(tapl: LauncherInstrumentation, appName: String) {
+        tapl.workspace.deleteAppIcon(tapl.workspace.getHotseatAppIcon(0))
+        val allApps = tapl.workspace.switchToAllApps()
+        allApps.freeze()
+        try {
+            allApps.getAppIcon(appName).dragToHotseat(0)
+        } finally {
+            allApps.unfreeze()
+        }
+    }
+
+    fun dragDividerToResizeAndWait(device: UiDevice, wmHelper: WindowManagerStateHelper) {
+        val displayBounds =
+            wmHelper.currentState.layerState.displays.firstOrNull { !it.isVirtual }?.layerStackSpace
+                ?: error("Display not found")
+        val dividerBar = device.wait(Until.findObject(dividerBarSelector), TIMEOUT_MS)
+        dividerBar.drag(Point(displayBounds.width * 1 / 3, displayBounds.height * 2 / 3), 200)
+
+        wmHelper
+            .StateSyncBuilder()
+            .withWindowSurfaceDisappeared(SPLIT_DECOR_MANAGER)
+            .waitForAndVerify()
+    }
+
+    fun dragDividerToDismissSplit(
+        device: UiDevice,
+        wmHelper: WindowManagerStateHelper,
+        dragToRight: Boolean,
+        dragToBottom: Boolean
+    ) {
+        val displayBounds =
+            wmHelper.currentState.layerState.displays.firstOrNull { !it.isVirtual }?.layerStackSpace
+                ?: error("Display not found")
+        val dividerBar = device.wait(Until.findObject(dividerBarSelector), TIMEOUT_MS)
+        dividerBar.drag(
+            Point(
+                if (dragToRight) {
+                    displayBounds.right
+                } else {
+                    displayBounds.left
+                },
+                if (dragToBottom) {
+                    displayBounds.bottom
+                } else {
+                    displayBounds.top
+                }
+            )
+        )
+    }
+
+    fun doubleTapDividerToSwitch(device: UiDevice) {
+        val dividerBar = device.wait(Until.findObject(dividerBarSelector), TIMEOUT_MS)
+        val interval =
+            (ViewConfiguration.getDoubleTapTimeout() + ViewConfiguration.getDoubleTapMinTime()) / 2
+        dividerBar.click()
+        SystemClock.sleep(interval.toLong())
+        dividerBar.click()
+    }
+
+    fun copyContentInSplit(
+        instrumentation: Instrumentation,
+        device: UiDevice,
+        sourceApp: IComponentNameMatcher,
+        destinationApp: IComponentNameMatcher,
+    ) {
+        // Copy text from sourceApp
+        val textView =
+            device.wait(
+                Until.findObject(By.res(sourceApp.packageName, "SplitScreenTest")),
+                TIMEOUT_MS
+            )
+        assertNotNull("Unable to find the TextView", textView)
+        textView.click(LONG_PRESS_TIME_MS)
+
+        val copyBtn = device.wait(Until.findObject(By.text("Copy")), TIMEOUT_MS)
+        assertNotNull("Unable to find the copy button", copyBtn)
+        copyBtn.click()
+
+        // Paste text to destinationApp
+        val editText =
+            device.wait(
+                Until.findObject(By.res(destinationApp.packageName, "plain_text_input")),
+                TIMEOUT_MS
+            )
+        assertNotNull("Unable to find the EditText", editText)
+        editText.click(LONG_PRESS_TIME_MS)
+
+        val pasteBtn = device.wait(Until.findObject(By.text("Paste")), TIMEOUT_MS)
+        assertNotNull("Unable to find the paste button", pasteBtn)
+        pasteBtn.click()
+
+        // Verify text
+        if (!textView.text.contentEquals(editText.text)) {
+            error("Fail to copy content in split")
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/BaseAppCompat.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/BaseAppCompat.kt
index 6178156..69c8ecd 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/BaseAppCompat.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/BaseAppCompat.kt
@@ -17,28 +17,29 @@
 package com.android.wm.shell.flicker.appcompat
 
 import android.content.Context
-import android.system.helpers.CommandsHelper
 import android.tools.common.traces.component.ComponentNameMatcher
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import com.android.server.wm.flicker.helpers.setRotation
+import android.tools.device.flicker.legacy.FlickerTestData
+import android.tools.device.flicker.legacy.LegacyFlickerTest
 import com.android.server.wm.flicker.helpers.LetterboxAppHelper
-import android.tools.device.flicker.legacy.FlickerTestFactory
-import android.tools.device.flicker.legacy.IFlickerTestData
+import com.android.server.wm.flicker.helpers.setRotation
 import com.android.wm.shell.flicker.BaseTest
-import com.android.wm.shell.flicker.appWindowIsVisibleAtStart
 import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
+import com.android.wm.shell.flicker.appWindowIsVisibleAtStart
+import com.android.wm.shell.flicker.appWindowKeepVisible
 import com.android.wm.shell.flicker.layerKeepVisible
-import org.junit.After
+
 import org.junit.Assume
 import org.junit.Before
-import org.junit.runners.Parameterized
+import org.junit.Rule
 
-abstract class BaseAppCompat(flicker: FlickerTest) : BaseTest(flicker) {
+abstract class BaseAppCompat(flicker: LegacyFlickerTest) : BaseTest(flicker) {
     protected val context: Context = instrumentation.context
     protected val letterboxApp = LetterboxAppHelper(instrumentation)
-    lateinit var cmdHelper: CommandsHelper
-    private lateinit var letterboxStyle: HashMap<String, String>
+
+    @JvmField
+    @Rule
+    val letterboxRule: LetterboxRule = LetterboxRule()
 
     /** {@inheritDoc} */
     override val transition: FlickerBuilder.() -> Unit
@@ -48,62 +49,17 @@
                 letterboxApp.launchViaIntent(wmHelper)
                 setEndRotation()
             }
-            teardown {
-                letterboxApp.exit(wmHelper)
-            }
+            teardown { letterboxApp.exit(wmHelper) }
         }
 
     @Before
     fun before() {
-        cmdHelper = CommandsHelper.getInstance(instrumentation)
-        Assume.assumeTrue(tapl.isTablet && isIgnoreOrientationRequest())
-        letterboxStyle = mapLetterboxStyle()
-        setLetterboxEducationEnabled(false)
+        Assume.assumeTrue(tapl.isTablet && letterboxRule.isIgnoreOrientationRequest)
     }
 
-    @After
-    fun after() {
-        resetLetterboxEducationEnabled()
-    }
+    fun FlickerTestData.setStartRotation() = setRotation(flicker.scenario.startRotation)
 
-    private fun mapLetterboxStyle(): HashMap<String, String> {
-        val res = cmdHelper.executeShellCommand("wm get-letterbox-style")
-        val lines = res.lines()
-        val map = HashMap<String, String>()
-        for (line in lines) {
-            val keyValuePair = line.split(":")
-            if (keyValuePair.size == 2) {
-                val key = keyValuePair[0].trim()
-                map[key] = keyValuePair[1].trim()
-            }
-        }
-        return map
-    }
-
-    private fun getLetterboxStyle(): HashMap<String, String> {
-        if (!::letterboxStyle.isInitialized) {
-            letterboxStyle = mapLetterboxStyle()
-        }
-        return letterboxStyle
-    }
-
-    private fun resetLetterboxEducationEnabled() {
-        val enabled = getLetterboxStyle().getValue("Is education enabled")
-        cmdHelper.executeShellCommand("wm set-letterbox-style --isEducationEnabled $enabled")
-    }
-
-    private fun setLetterboxEducationEnabled(enabled: Boolean) {
-        cmdHelper.executeShellCommand("wm set-letterbox-style --isEducationEnabled $enabled")
-    }
-
-    private fun isIgnoreOrientationRequest(): Boolean {
-        val res = cmdHelper.executeShellCommand("wm get-ignore-orientation-request")
-        return res != null && res.contains("true")
-    }
-
-    fun IFlickerTestData.setStartRotation() = setRotation(flicker.scenario.startRotation)
-
-    fun IFlickerTestData.setEndRotation() = setRotation(flicker.scenario.endRotation)
+    fun FlickerTestData.setEndRotation() = setRotation(flicker.scenario.endRotation)
 
     /** Checks that app entering letterboxed state have rounded corners */
     fun assertLetterboxAppAtStartHasRoundedCorners() {
@@ -118,7 +74,7 @@
 
     /** Only run on tests with config_letterboxActivityCornersRadius != 0 in devices */
     private fun assumeLetterboxRoundedCornersEnabled() {
-        Assume.assumeTrue(getLetterboxStyle().getValue("Corner radius") != "0")
+        Assume.assumeTrue(letterboxRule.hasCornerRadius)
     }
 
     fun assertLetterboxAppVisibleAtStartAndEnd() {
@@ -126,25 +82,21 @@
         flicker.appWindowIsVisibleAtEnd(letterboxApp)
     }
 
+    fun assertLetterboxAppKeepVisible() {
+        assertLetterboxAppWindowKeepVisible()
+        assertLetterboxAppLayerKeepVisible()
+    }
+
     fun assertAppLetterboxedAtEnd() =
-            flicker.assertLayersEnd { isVisible(ComponentNameMatcher.LETTERBOX) }
+        flicker.assertLayersEnd { isVisible(ComponentNameMatcher.LETTERBOX) }
 
     fun assertAppLetterboxedAtStart() =
-            flicker.assertLayersStart { isVisible(ComponentNameMatcher.LETTERBOX) }
+        flicker.assertLayersStart { isVisible(ComponentNameMatcher.LETTERBOX) }
+
+    fun assertAppStaysLetterboxed() =
+        flicker.assertLayers { isVisible(ComponentNameMatcher.LETTERBOX) }
 
     fun assertLetterboxAppLayerKeepVisible() = flicker.layerKeepVisible(letterboxApp)
 
-    companion object {
-        /**
-         * Creates the test configurations.
-         *
-         * See [FlickerTestFactory.rotationTests] for configuring screen orientation and
-         * navigation modes.
-         */
-        @Parameterized.Parameters(name = "{0}")
-        @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.rotationTests()
-        }
-    }
+    fun assertLetterboxAppWindowKeepVisible() = flicker.appWindowKeepVisible(letterboxApp)
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/LetterboxRule.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/LetterboxRule.kt
new file mode 100644
index 0000000..5a1136f
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/LetterboxRule.kt
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.flicker.appcompat
+
+import android.app.Instrumentation
+import android.system.helpers.CommandsHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+
+/**
+ * JUnit Rule to handle letterboxStyles and states
+ */
+class LetterboxRule(
+        private val withLetterboxEducationEnabled: Boolean = false,
+        private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation(),
+        private val cmdHelper: CommandsHelper = CommandsHelper.getInstance(instrumentation)
+) : TestRule {
+
+    private val execAdb: (String) -> String = {cmd -> cmdHelper.executeShellCommand(cmd)}
+    private lateinit var _letterboxStyle: MutableMap<String, String>
+
+    val letterboxStyle: Map<String, String>
+        get() {
+            if (!::_letterboxStyle.isInitialized) {
+                _letterboxStyle = mapLetterboxStyle()
+            }
+            return _letterboxStyle
+        }
+
+    val cornerRadius: Int?
+        get() = asInt(letterboxStyle["Corner radius"])
+
+    val hasCornerRadius: Boolean
+        get() {
+            val radius = cornerRadius
+            return radius != null && radius > 0
+        }
+
+    val isIgnoreOrientationRequest: Boolean
+        get() = execAdb("wm get-ignore-orientation-request")?.contains("true") ?: false
+
+    override fun apply(base: Statement?, description: Description?): Statement {
+        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() {
+                    base!!.evaluate()
+                }
+            }
+        } finally {
+            if (hasLetterboxEducationStateChanged) {
+                execAdb("wm set-letterbox-style --isEducationEnabled " +
+                        isLetterboxEducationEnabled
+                )
+            }
+            resetLetterboxStyle()
+        }
+    }
+
+    private fun mapLetterboxStyle(): HashMap<String, String> {
+        val res = execAdb("wm get-letterbox-style")
+        val lines = res.lines()
+        val map = HashMap<String, String>()
+        for (line in lines) {
+            val keyValuePair = line.split(":")
+            if (keyValuePair.size == 2) {
+                val key = keyValuePair[0].trim()
+                map[key] = keyValuePair[1].trim()
+            }
+        }
+        return map
+    }
+
+    private fun resetLetterboxStyle() {
+        execAdb("wm reset-letterbox-style")
+    }
+
+    private fun asInt(str: String?): Int? = try {
+        str?.toInt()
+    } catch (e: NumberFormatException) {
+        null
+    }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt
index c2141a3..67d5718 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt
@@ -17,10 +17,12 @@
 package com.android.wm.shell.flicker.appcompat
 
 import android.platform.test.annotations.Postsubmit
+import android.tools.common.flicker.assertions.FlickerTest
 import android.tools.common.traces.component.ComponentNameMatcher
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -29,7 +31,7 @@
 /**
  * Test launching app in size compat mode.
  *
- * To run this test: `atest WMShellFlickerTests:OpenAppInSizeCompatModeTest`
+ * To run this test: `atest WMShellFlickerTestsOther:OpenAppInSizeCompatModeTest`
  *
  * Actions:
  * ```
@@ -45,7 +47,7 @@
 @RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-class OpenAppInSizeCompatModeTest(flicker: FlickerTest) : BaseAppCompat(flicker) {
+class OpenAppInSizeCompatModeTest(flicker: LegacyFlickerTest) : BaseAppCompat(flicker) {
 
     /** {@inheritDoc} */
     override val transition: FlickerBuilder.() -> Unit
@@ -70,9 +72,7 @@
     @Test
     fun letterboxedAppHasRoundedCorners() = assertLetterboxAppAtEndHasRoundedCorners()
 
-    @Postsubmit
-    @Test
-    fun appIsLetterboxedAtEnd() = assertAppLetterboxedAtEnd()
+    @Postsubmit @Test fun appIsLetterboxedAtEnd() = assertAppLetterboxedAtEnd()
 
     /**
      * Checks that the [ComponentNameMatcher.ROTATION] layer appears during the transition, doesn't
@@ -90,4 +90,18 @@
                 .isInvisible(ComponentNameMatcher.ROTATION)
         }
     }
+
+    companion object {
+        /**
+         * Creates the test configurations.
+         *
+         * See [LegacyFlickerTestFactory.rotationTests] for configuring screen orientation and
+         * navigation modes.
+         */
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): Collection<FlickerTest> {
+            return LegacyFlickerTestFactory.rotationTests()
+        }
+    }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenTransparentActivityTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenTransparentActivityTest.kt
new file mode 100644
index 0000000..e6ca261
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenTransparentActivityTest.kt
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.flicker.appcompat
+
+import android.platform.test.annotations.Postsubmit
+import android.tools.common.Rotation
+import android.tools.common.flicker.assertions.FlickerTest
+import android.tools.common.traces.component.ComponentNameMatcher
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.RequiresDevice
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+/**
+ * Test launching app in size compat mode.
+ *
+ * To run this test: `atest WMShellFlickerTestsOther:OpenTransparentActivityTest`
+ *
+ * Actions:
+ * ```
+ *     Launch a letteboxed app and then a transparent activity from it. We test the bounds
+ *     are the same.
+ * ```
+ *
+ * Notes:
+ * ```
+ *     Some default assertions (e.g., nav bar, status bar and screen covered)
+ *     are inherited [BaseTest]
+ * ```
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+class OpenTransparentActivityTest(flicker: LegacyFlickerTest) : TransparentBaseAppCompat(flicker) {
+
+    /** {@inheritDoc} */
+    override val transition: FlickerBuilder.() -> Unit
+        get() = {
+            setup {
+                letterboxTranslucentLauncherApp.launchViaIntent(wmHelper)
+            }
+            transitions {
+                waitAndGetLaunchTransparent()?.click() ?: error("Launch Transparent not found")
+            }
+            teardown {
+                letterboxTranslucentApp.exit(wmHelper)
+                letterboxTranslucentLauncherApp.exit(wmHelper)
+            }
+        }
+
+    /**
+     * Checks the transparent activity is launched on top of the opaque one
+     */
+    @Postsubmit
+    @Test
+    fun translucentActivityIsLaunchedOnTopOfOpaqueActivity() {
+        flicker.assertWm {
+            this.isAppWindowOnTop(letterboxTranslucentLauncherApp)
+                .then()
+                .isAppWindowOnTop(letterboxTranslucentApp)
+        }
+    }
+
+    /**
+     * Checks that the activity is letterboxed
+     */
+    @Postsubmit
+    @Test
+    fun translucentActivityIsLetterboxed() {
+        flicker.assertLayers { isVisible(ComponentNameMatcher.LETTERBOX) }
+    }
+
+    /**
+     * Checks that the translucent activity inherits bounds from the opaque one.
+     */
+    @Postsubmit
+    @Test
+    fun translucentActivityInheritsBoundsFromOpaqueActivity() {
+        flicker.assertLayersEnd {
+            this.visibleRegion(letterboxTranslucentApp)
+                .coversExactly(visibleRegion(letterboxTranslucentLauncherApp).region)
+        }
+    }
+
+    /**
+     * Checks that the translucent activity has rounded corners
+     */
+    @Postsubmit
+    @Test
+    fun translucentActivityHasRoundedCorners() {
+        flicker.assertLayersEnd {
+            this.hasRoundedCorners(letterboxTranslucentApp)
+        }
+    }
+
+    companion object {
+        /**
+         * Creates the test configurations.
+         *
+         * See [FlickerTestFactory.rotationTests] for configuring screen orientation and
+         * navigation modes.
+         */
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): Collection<FlickerTest> {
+            return LegacyFlickerTestFactory
+                .nonRotationTests(supportedRotations = listOf(Rotation.ROTATION_90))
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt
new file mode 100644
index 0000000..47a7e65
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt
@@ -0,0 +1,275 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.flicker.appcompat
+
+import android.platform.test.annotations.Postsubmit
+import android.platform.test.annotations.RequiresDevice
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.common.datatypes.Rect
+import android.tools.common.flicker.assertions.FlickerTest
+import android.tools.common.traces.component.ComponentNameMatcher
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test quick switching to letterboxed app from launcher
+ *
+ * To run this test: `atest WMShellFlickerTestsOther:QuickSwitchLauncherToLetterboxAppTest`
+ *
+ * Actions:
+ * ```
+ *     Launch a letterboxed app
+ *     Navigate home to show launcher
+ *     Swipe right from the bottom of the screen to quick switch back to the app
+ * ```
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class QuickSwitchLauncherToLetterboxAppTest(flicker: LegacyFlickerTest) :
+    BaseAppCompat(flicker) {
+
+    /** {@inheritDoc} */
+    override val transition: FlickerBuilder.() -> Unit = {
+        setup {
+            tapl.setExpectedRotationCheckEnabled(false)
+
+            tapl.setExpectedRotation(flicker.scenario.startRotation.value)
+
+            letterboxApp.launchViaIntent(wmHelper)
+            tapl.goHome()
+            wmHelper
+                .StateSyncBuilder()
+                .withHomeActivityVisible()
+                .withWindowSurfaceDisappeared(letterboxApp)
+                .waitForAndVerify()
+
+            startDisplayBounds =
+                wmHelper.currentState.layerState.physicalDisplayBounds ?: error("Display not found")
+        }
+        transitions {
+            tapl.workspace.quickSwitchToPreviousApp()
+            wmHelper
+                .StateSyncBuilder()
+                .withFullScreenApp(letterboxApp)
+                .withNavOrTaskBarVisible()
+                .withStatusBarVisible()
+                .waitForAndVerify()
+        }
+        teardown { letterboxApp.exit(wmHelper) }
+    }
+
+    /**
+     * Checks that [letterboxApp] is the top window at the end of the transition once we have fully
+     * quick switched from the launcher back to the [letterboxApp].
+     */
+    @Postsubmit
+    @Test
+    fun endsWithAppBeingOnTop() {
+        flicker.assertWmEnd { this.isAppWindowOnTop(letterboxApp) }
+    }
+
+    /** Checks that the transition starts with the home activity being tagged as visible. */
+    @Postsubmit
+    @Test
+    fun startsWithHomeActivityFlaggedVisible() {
+        flicker.assertWmStart { this.isHomeActivityVisible() }
+    }
+
+    /**
+     * Checks that the transition starts with the [ComponentNameMatcher.LAUNCHER] windows
+     * filling/covering exactly display size
+     */
+    @Postsubmit
+    @Test
+    fun startsWithLauncherWindowsCoverFullScreen() {
+        flicker.assertWmStart {
+            this.visibleRegion(ComponentNameMatcher.LAUNCHER).coversExactly(startDisplayBounds)
+        }
+    }
+
+    /**
+     * Checks that the transition starts with the [ComponentNameMatcher.LAUNCHER] layers
+     * filling/covering exactly the display size.
+     */
+    @Postsubmit
+    @Test
+    fun startsWithLauncherLayersCoverFullScreen() {
+        flicker.assertLayersStart {
+            this.visibleRegion(ComponentNameMatcher.LAUNCHER).coversExactly(startDisplayBounds)
+        }
+    }
+
+    /**
+     * Checks that the transition starts with the [ComponentNameMatcher.LAUNCHER] being the top
+     * window.
+     */
+    @Postsubmit
+    @Test
+    fun startsWithLauncherBeingOnTop() {
+        flicker.assertWmStart { this.isAppWindowOnTop(ComponentNameMatcher.LAUNCHER) }
+    }
+
+    /**
+     * Checks that the transition ends with the home activity being flagged as not visible. By this
+     * point we should have quick switched away from the launcher back to the [letterboxApp].
+     */
+    @Postsubmit
+    @Test
+    fun endsWithHomeActivityFlaggedInvisible() {
+        flicker.assertWmEnd { this.isHomeActivityInvisible() }
+    }
+
+    /**
+     * Checks that [letterboxApp]'s window starts off invisible and becomes visible at some point
+     * before the end of the transition and then stays visible until the end of the transition.
+     */
+    @Postsubmit
+    @Test
+    fun appWindowBecomesAndStaysVisible() {
+        flicker.assertWm {
+            this.isAppWindowInvisible(letterboxApp).then().isAppWindowVisible(letterboxApp)
+        }
+    }
+
+    /**
+     * Checks that [letterboxApp]'s layer starts off invisible and becomes visible at some point
+     * before the end of the transition and then stays visible until the end of the transition.
+     */
+    @Postsubmit
+    @Test
+    fun appLayerBecomesAndStaysVisible() {
+        flicker.assertLayers { this.isInvisible(letterboxApp).then().isVisible(letterboxApp) }
+    }
+
+    /**
+     * Checks that the [ComponentNameMatcher.LAUNCHER] window starts off visible and becomes
+     * invisible at some point before the end of the transition and then stays invisible until the
+     * end of the transition.
+     */
+    @Postsubmit
+    @Test
+    fun launcherWindowBecomesAndStaysInvisible() {
+        flicker.assertWm {
+            this.isAppWindowOnTop(ComponentNameMatcher.LAUNCHER)
+                .then()
+                .isAppWindowNotOnTop(ComponentNameMatcher.LAUNCHER)
+        }
+    }
+
+    /**
+     * Checks that the [ComponentNameMatcher.LAUNCHER] layer starts off visible and becomes
+     * invisible at some point before the end of the transition and then stays invisible until the
+     * end of the transition.
+     */
+    @Postsubmit
+    @Test
+    fun launcherLayerBecomesAndStaysInvisible() {
+        flicker.assertLayers {
+            this.isVisible(ComponentNameMatcher.LAUNCHER)
+                .then()
+                .isInvisible(ComponentNameMatcher.LAUNCHER)
+        }
+    }
+
+    /**
+     * Checks that the [ComponentNameMatcher.LAUNCHER] window is visible at least until the app
+     * window is visible. Ensures that at any point, either the launcher or [letterboxApp] windows
+     * are at least partially visible.
+     */
+    @Postsubmit
+    @Test
+    fun appWindowIsVisibleOnceLauncherWindowIsInvisible() {
+        flicker.assertWm {
+            this.isAppWindowOnTop(ComponentNameMatcher.LAUNCHER)
+                .then()
+                .isAppWindowVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true)
+                .then()
+                .isAppWindowVisible(letterboxApp)
+        }
+    }
+
+    /**
+     * Checks that the [ComponentNameMatcher.LAUNCHER] layer is visible at least until the app layer
+     * is visible. Ensures that at any point, either the launcher or [letterboxApp] layers are at
+     * least partially visible.
+     */
+    @Postsubmit
+    @Test
+    fun appLayerIsVisibleOnceLauncherLayerIsInvisible() {
+        flicker.assertLayers {
+            this.isVisible(ComponentNameMatcher.LAUNCHER)
+                .then()
+                .isVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true)
+                .then()
+                .isVisible(letterboxApp)
+        }
+    }
+
+    /**
+     * Checks that the [ComponentNameMatcher.LETTERBOX] layer is visible as soon as the
+     * [letterboxApp] layer is visible at the end of the transition once we have fully quick
+     * switched from the launcher back to the [letterboxApp].
+     */
+    @Postsubmit
+    @Test
+    fun appAndLetterboxLayersBothVisibleOnceLauncherIsInvisible() {
+        flicker.assertLayers {
+            this.isVisible(ComponentNameMatcher.LAUNCHER)
+                .then()
+                .isVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true)
+                .then()
+                .isVisible(letterboxApp)
+                .isVisible(ComponentNameMatcher.LETTERBOX)
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Postsubmit
+    @Test
+    override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd()
+
+    /** {@inheritDoc} */
+    @Postsubmit
+    @Test
+    override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
+        super.visibleLayersShownMoreThanOneConsecutiveEntry()
+    }
+
+    companion object {
+        /** {@inheritDoc} */
+        private var startDisplayBounds = Rect.EMPTY
+
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): Collection<FlickerTest> {
+            return LegacyFlickerTestFactory.nonRotationTests(
+                supportedNavigationModes = listOf(NavBar.MODE_GESTURAL),
+                supportedRotations = listOf(Rotation.ROTATION_90)
+            )
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RepositionFixedPortraitAppTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RepositionFixedPortraitAppTest.kt
new file mode 100644
index 0000000..68fa8d2
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RepositionFixedPortraitAppTest.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.wm.shell.flicker.appcompat
+
+import android.platform.test.annotations.Postsubmit
+import android.tools.common.Rotation
+import android.tools.common.flicker.assertions.FlickerTest
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+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.WindowUtils
+import androidx.test.filters.RequiresDevice
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+/**
+ * Test launching a fixed portrait letterboxed app in landscape and repositioning to the right.
+ *
+ * To run this test: `atest WMShellFlickerTestsOther:RepositionFixedPortraitAppTest`
+ *
+ * Actions:
+ *
+ *  ```
+ *      Launch a fixed portrait app in landscape to letterbox app
+ *      Double tap to the right to reposition app and wait for app to move
+ *  ```
+ *
+ * Notes:
+ *
+ *  ```
+ *      Some default assertions (e.g., nav bar, status bar and screen covered)
+ *      are inherited [BaseTest]
+ *  ```
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+class RepositionFixedPortraitAppTest(flicker: LegacyFlickerTest) : BaseAppCompat(flicker) {
+
+    val displayBounds = WindowUtils.getDisplayBounds(flicker.scenario.startRotation).bounds
+    /** {@inheritDoc} */
+    override val transition: FlickerBuilder.() -> Unit
+        get() = {
+            setup {
+                setStartRotation()
+                letterboxApp.launchViaIntent(wmHelper)
+            }
+            transitions {
+                letterboxApp.repositionHorizontally(displayBounds, true)
+                letterboxApp.waitForAppToMoveHorizontallyTo(wmHelper, displayBounds, true)
+            }
+            teardown {
+                letterboxApp.repositionHorizontally(displayBounds, false)
+                letterboxApp.exit(wmHelper)
+            }
+        }
+
+    @Postsubmit
+    @Test
+    fun letterboxedAppHasRoundedCorners() = assertLetterboxAppAtEndHasRoundedCorners()
+
+    @Postsubmit @Test fun letterboxAppLayerKeepVisible() = assertLetterboxAppLayerKeepVisible()
+
+    @Postsubmit @Test fun appStaysLetterboxed() = assertAppStaysLetterboxed()
+
+    @Postsubmit @Test fun appKeepVisible() = assertLetterboxAppKeepVisible()
+
+    companion object {
+        /**
+         * Creates the test configurations.
+         *
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * navigation modes.
+         */
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): Collection<FlickerTest> {
+            return LegacyFlickerTestFactory.nonRotationTests(
+                supportedRotations = listOf(Rotation.ROTATION_90)
+            )
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RestartAppInSizeCompatModeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RestartAppInSizeCompatModeTest.kt
index b0e1a42..fcb6931a 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RestartAppInSizeCompatModeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RestartAppInSizeCompatModeTest.kt
@@ -17,9 +17,11 @@
 package com.android.wm.shell.flicker.appcompat
 
 import android.platform.test.annotations.Postsubmit
+import android.tools.common.flicker.assertions.FlickerTest
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import android.tools.device.helpers.WindowUtils
 import androidx.test.filters.RequiresDevice
 import org.junit.Test
@@ -29,7 +31,7 @@
 /**
  * Test restarting app in size compat mode.
  *
- * To run this test: `atest WMShellFlickerTests:RestartAppInSizeCompatModeTest`
+ * To run this test: `atest WMShellFlickerTestsOther:RestartAppInSizeCompatModeTest`
  *
  * Actions:
  * ```
@@ -46,7 +48,7 @@
 @RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-class RestartAppInSizeCompatModeTest(flicker: FlickerTest) : BaseAppCompat(flicker) {
+class RestartAppInSizeCompatModeTest(flicker: LegacyFlickerTest) : BaseAppCompat(flicker) {
 
     /** {@inheritDoc} */
     override val transition: FlickerBuilder.() -> Unit
@@ -69,13 +71,9 @@
         }
     }
 
-    @Postsubmit
-    @Test
-    fun appLayerKeepVisible() = assertLetterboxAppLayerKeepVisible()
+    @Postsubmit @Test fun appLayerKeepVisible() = assertLetterboxAppLayerKeepVisible()
 
-    @Postsubmit
-    @Test
-    fun appIsLetterboxedAtStart() = assertAppLetterboxedAtStart()
+    @Postsubmit @Test fun appIsLetterboxedAtStart() = assertAppLetterboxedAtStart()
 
     @Postsubmit
     @Test
@@ -88,4 +86,18 @@
         val displayBounds = WindowUtils.getDisplayBounds(flicker.scenario.endRotation)
         flicker.assertLayersEnd { visibleRegion(letterboxApp).coversAtMost(displayBounds) }
     }
+
+    companion object {
+        /**
+         * Creates the test configurations.
+         *
+         * See [LegacyFlickerTestFactory.rotationTests] for configuring screen orientation and
+         * navigation modes.
+         */
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): Collection<FlickerTest> {
+            return LegacyFlickerTestFactory.rotationTests()
+        }
+    }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/TransparentBaseAppCompat.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/TransparentBaseAppCompat.kt
new file mode 100644
index 0000000..ea0392c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/TransparentBaseAppCompat.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.wm.shell.flicker.appcompat
+
+import android.content.Context
+import android.tools.device.flicker.legacy.FlickerTestData
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.helpers.FIND_TIMEOUT
+import android.tools.device.traces.parsers.toFlickerComponent
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.UiObject2
+import androidx.test.uiautomator.Until
+import com.android.server.wm.flicker.helpers.LetterboxAppHelper
+import com.android.server.wm.flicker.testapp.ActivityOptions
+import com.android.wm.shell.flicker.BaseTest
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Rule
+
+abstract class TransparentBaseAppCompat(flicker: LegacyFlickerTest) : BaseTest(flicker) {
+    protected val context: Context = instrumentation.context
+    protected val letterboxTranslucentLauncherApp = LetterboxAppHelper(
+        instrumentation,
+        launcherName = ActivityOptions.LaunchTransparentActivity.LABEL,
+        component = ActivityOptions.LaunchTransparentActivity.COMPONENT.toFlickerComponent()
+    )
+    protected val letterboxTranslucentApp = LetterboxAppHelper(
+        instrumentation,
+        launcherName = ActivityOptions.TransparentActivity.LABEL,
+        component = ActivityOptions.TransparentActivity.COMPONENT.toFlickerComponent()
+    )
+
+    @JvmField
+    @Rule
+    val letterboxRule: LetterboxRule = LetterboxRule()
+
+    @Before
+    fun before() {
+        Assume.assumeTrue(tapl.isTablet && letterboxRule.isIgnoreOrientationRequest)
+    }
+
+    protected fun FlickerTestData.waitAndGetLaunchTransparent(): UiObject2? =
+        device.wait(
+            Until.findObject(By.text("Launch Transparent")),
+            FIND_TIMEOUT
+        )
+
+    protected fun FlickerTestData.goBack() = device.pressBack()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt
index bab81d7..5c7d1d8 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt
@@ -23,9 +23,9 @@
 import android.os.ServiceManager
 import android.tools.common.Rotation
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
-import android.tools.device.flicker.legacy.IFlickerTestData
+import android.tools.device.flicker.legacy.FlickerTestData
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import android.tools.device.helpers.SYSTEMUI_PACKAGE
 import androidx.test.uiautomator.By
 import androidx.test.uiautomator.UiObject2
@@ -35,7 +35,7 @@
 import org.junit.runners.Parameterized
 
 /** Base configurations for Bubble flicker tests */
-abstract class BaseBubbleScreen(flicker: FlickerTest) : BaseTest(flicker) {
+abstract class BaseBubbleScreen(flicker: LegacyFlickerTest) : BaseTest(flicker) {
 
     protected val context: Context = instrumentation.context
     protected val testApp = LaunchBubbleHelper(instrumentation)
@@ -72,28 +72,31 @@
                     uid,
                     NotificationManager.BUBBLE_PREFERENCE_NONE
                 )
-                testApp.exit()
+                device.wait(
+                    Until.gone(By.res(SYSTEM_UI_PACKAGE, BUBBLE_RES_NAME)),
+                    FIND_OBJECT_TIMEOUT
+                )
+                testApp.exit(wmHelper)
             }
 
             extraSpec(this)
         }
     }
 
-    protected fun IFlickerTestData.waitAndGetAddBubbleBtn(): UiObject2? =
+    protected fun FlickerTestData.waitAndGetAddBubbleBtn(): UiObject2? =
         device.wait(Until.findObject(By.text("Add Bubble")), FIND_OBJECT_TIMEOUT)
-    protected fun IFlickerTestData.waitAndGetCancelAllBtn(): UiObject2? =
+    protected fun FlickerTestData.waitAndGetCancelAllBtn(): UiObject2? =
         device.wait(Until.findObject(By.text("Cancel All Bubble")), FIND_OBJECT_TIMEOUT)
 
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
                 supportedRotations = listOf(Rotation.ROTATION_0)
             )
-        }
 
-        const val FIND_OBJECT_TIMEOUT = 2000L
+        const val FIND_OBJECT_TIMEOUT = 4000L
         const val SYSTEM_UI_PACKAGE = SYSTEMUI_PACKAGE
         const val BUBBLE_RES_NAME = "bubble_view"
     }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTest.kt
index 2474ecf..f165cb1 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTest.kt
@@ -21,8 +21,7 @@
 import android.platform.test.annotations.Presubmit
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import androidx.test.filters.RequiresDevice
+import android.tools.device.flicker.legacy.LegacyFlickerTest
 import androidx.test.uiautomator.By
 import androidx.test.uiautomator.UiObject2
 import androidx.test.uiautomator.Until
@@ -40,11 +39,11 @@
  *     Switch in different bubble notifications
  * ```
  */
-@RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FlakyTest(bugId = 217777115)
-open class ChangeActiveActivityFromBubbleTest(flicker: FlickerTest) : BaseBubbleScreen(flicker) {
+class ChangeActiveActivityFromBubbleTest(flicker: LegacyFlickerTest) :
+    BaseBubbleScreen(flicker) {
     /** {@inheritDoc} */
     override val transition: FlickerBuilder.() -> Unit
         get() = buildTransition {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTestCfArm.kt
deleted file mode 100644
index bdfdad5..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTestCfArm.kt
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.flicker.bubble
-
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
-import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
-
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-open class ChangeActiveActivityFromBubbleTestCfArm(flicker: FlickerTest) :
-    ChangeActiveActivityFromBubbleTest(flicker)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTest.kt
index 8474ce0..9ca7bf1 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTest.kt
@@ -19,12 +19,13 @@
 import android.content.Context
 import android.graphics.Point
 import android.platform.test.annotations.Presubmit
+import android.tools.common.flicker.subject.layers.LayersTraceSubject
+import android.tools.common.traces.component.ComponentNameMatcher
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.util.DisplayMetrics
 import android.view.WindowManager
-import androidx.test.filters.RequiresDevice
 import androidx.test.uiautomator.By
 import androidx.test.uiautomator.Until
 import org.junit.Test
@@ -41,10 +42,9 @@
  *     Dismiss a bubble notification
  * ```
  */
-@RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-open class DragToDismissBubbleScreenTest(flicker: FlickerTest) : BaseBubbleScreen(flicker) {
+class DragToDismissBubbleScreenTest(flicker: LegacyFlickerTest) : BaseBubbleScreen(flicker) {
 
     private val wm = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
     private val displaySize = DisplayMetrics()
@@ -73,4 +73,15 @@
     open fun testAppIsAlwaysVisible() {
         flicker.assertLayers { this.isVisible(testApp) }
     }
+
+    @Presubmit
+    @Test
+    override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
+        flicker.assertLayers {
+            this.visibleLayersShownMoreThanOneConsecutiveEntry(
+                LayersTraceSubject.VISIBLE_FOR_MORE_THAN_ONE_ENTRY_IGNORE_LAYERS +
+                    listOf(testApp, ComponentNameMatcher(className = "Bubbles!#"))
+            )
+        }
+    }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTestCfArm.kt
deleted file mode 100644
index 62fa7b4..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTestCfArm.kt
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.flicker.bubble
-
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
-import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
-
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-class DragToDismissBubbleScreenTestCfArm(flicker: FlickerTest) :
-    DragToDismissBubbleScreenTest(flicker)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLocksreenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLocksreenTest.kt
index 889e177..26aca18 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLocksreenTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLocksreenTest.kt
@@ -21,7 +21,7 @@
 import android.tools.common.traces.component.ComponentNameMatcher
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.view.WindowInsets
 import android.view.WindowManager
 import androidx.test.filters.RequiresDevice
@@ -48,7 +48,8 @@
 @RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-class OpenActivityFromBubbleOnLocksreenTest(flicker: FlickerTest) : BaseBubbleScreen(flicker) {
+class OpenActivityFromBubbleOnLocksreenTest(flicker: LegacyFlickerTest) :
+    BaseBubbleScreen(flicker) {
 
     /** {@inheritDoc} */
     override val transition: FlickerBuilder.() -> Unit
@@ -80,7 +81,7 @@
                 instrumentation.uiAutomation.syncInputTransactions()
                 val showBubble =
                     device.wait(
-                        Until.findObject(By.res("com.android.systemui", "bubble_view")),
+                        Until.findObject(By.res(SYSTEM_UI_PACKAGE, BUBBLE_RES_NAME)),
                         FIND_OBJECT_TIMEOUT
                     )
                 showBubble?.click() ?: error("Bubble notify not found")
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTest.kt
index 07ba4133..4959672 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTest.kt
@@ -19,8 +19,7 @@
 import android.platform.test.annotations.Presubmit
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import androidx.test.filters.RequiresDevice
+import android.tools.device.flicker.legacy.LegacyFlickerTest
 import androidx.test.uiautomator.By
 import androidx.test.uiautomator.Until
 import org.junit.Test
@@ -39,10 +38,9 @@
  *     The activity for the bubble is launched
  * ```
  */
-@RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-open class OpenActivityFromBubbleTest(flicker: FlickerTest) : BaseBubbleScreen(flicker) {
+class OpenActivityFromBubbleTest(flicker: LegacyFlickerTest) : BaseBubbleScreen(flicker) {
 
     /** {@inheritDoc} */
     override val transition: FlickerBuilder.() -> Unit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTestCfArm.kt
deleted file mode 100644
index 6c61710..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTestCfArm.kt
+++ /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.
- */
-
-package com.android.wm.shell.flicker.bubble
-
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
-import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
-
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-class OpenActivityFromBubbleTestCfArm(flicker: FlickerTest) : OpenActivityFromBubbleTest(flicker)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTest.kt
index 29f76d0..0d95574 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTest.kt
@@ -19,8 +19,7 @@
 import android.platform.test.annotations.Presubmit
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import androidx.test.filters.RequiresDevice
+import android.tools.device.flicker.legacy.LegacyFlickerTest
 import androidx.test.uiautomator.By
 import androidx.test.uiautomator.Until
 import org.junit.Test
@@ -38,10 +37,9 @@
  *     Send a bubble notification
  * ```
  */
-@RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-open class SendBubbleNotificationTest(flicker: FlickerTest) : BaseBubbleScreen(flicker) {
+class SendBubbleNotificationTest(flicker: LegacyFlickerTest) : BaseBubbleScreen(flicker) {
 
     /** {@inheritDoc} */
     override val transition: FlickerBuilder.() -> Unit
@@ -55,6 +53,7 @@
                     FIND_OBJECT_TIMEOUT
                 )
                     ?: error("No bubbles found")
+                device.waitForIdle()
             }
         }
 
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTestCfArm.kt
deleted file mode 100644
index e323ebf..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTestCfArm.kt
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.flicker.bubble
-
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
-import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
-
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-open class SendBubbleNotificationTestCfArm(flicker: FlickerTest) :
-    SendBubbleNotificationTest(flicker)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipFromSplitScreenOnGoToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipFromSplitScreenOnGoToHomeTest.kt
index f7ce870..8bd44c3 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipFromSplitScreenOnGoToHomeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipFromSplitScreenOnGoToHomeTest.kt
@@ -16,26 +16,19 @@
 
 package com.android.wm.shell.flicker.pip
 
-import android.app.Instrumentation
-import android.os.SystemClock
 import android.platform.test.annotations.Presubmit
 import android.tools.common.NavBar
 import android.tools.common.Rotation
-import android.tools.device.apphelpers.StandardAppHelper
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import android.tools.device.helpers.WindowUtils
 import android.tools.device.traces.parsers.toFlickerComponent
 import androidx.test.filters.RequiresDevice
-import androidx.test.uiautomator.By
-import androidx.test.uiautomator.BySelector
-import androidx.test.uiautomator.UiObject2
-import androidx.test.uiautomator.Until
 import com.android.server.wm.flicker.helpers.SimpleAppHelper
 import com.android.server.wm.flicker.testapp.ActivityOptions
-import com.android.wm.shell.flicker.LAUNCHER_UI_PACKAGE_NAME
+import com.android.wm.shell.flicker.SplitScreenUtils
 import org.junit.Assume
 import org.junit.FixMethodOrder
 import org.junit.Test
@@ -69,17 +62,16 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class AutoEnterPipFromSplitScreenOnGoToHomeTest(flicker: FlickerTest) :
-        AutoEnterPipOnGoToHomeTest(flicker) {
+class AutoEnterPipFromSplitScreenOnGoToHomeTest(flicker: LegacyFlickerTest) :
+    AutoEnterPipOnGoToHomeTest(flicker) {
     private val portraitDisplayBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_0)
     /** Second app used to enter split screen mode */
-    protected val secondAppForSplitScreen = getSplitScreenApp(instrumentation)
-    fun getSplitScreenApp(instrumentation: Instrumentation): StandardAppHelper =
-            SimpleAppHelper(
-                    instrumentation,
-                    ActivityOptions.SplitScreen.Primary.LABEL,
-                    ActivityOptions.SplitScreen.Primary.COMPONENT.toFlickerComponent()
-            )
+    private val secondAppForSplitScreen =
+        SimpleAppHelper(
+            instrumentation,
+            ActivityOptions.SplitScreen.Primary.LABEL,
+            ActivityOptions.SplitScreen.Primary.COMPONENT.toFlickerComponent()
+        )
 
     /** Defines the transition used to run the test */
     override val transition: FlickerBuilder.() -> Unit
@@ -88,14 +80,7 @@
                 secondAppForSplitScreen.launchViaIntent(wmHelper)
                 pipApp.launchViaIntent(wmHelper)
                 tapl.goHome()
-                enterSplitScreen()
-                // wait until split screen is established
-                wmHelper
-                        .StateSyncBuilder()
-                        .withWindowSurfaceAppeared(pipApp)
-                        .withWindowSurfaceAppeared(secondAppForSplitScreen)
-                        .withSplitDividerVisible()
-                        .waitForAndVerify()
+                SplitScreenUtils.enterSplit(wmHelper, tapl, device, pipApp, secondAppForSplitScreen)
                 pipApp.enableAutoEnterForPipActivity()
             }
             teardown {
@@ -107,46 +92,6 @@
             transitions { tapl.goHome() }
         }
 
-    // TODO(b/285400227) merge the code in a common utility - this is copied from SplitScreenUtils
-    private val TIMEOUT_MS = 3_000L
-    private val overviewSnapshotSelector: BySelector
-        get() = By.res(LAUNCHER_UI_PACKAGE_NAME, "snapshot")
-    private fun enterSplitScreen() {
-        // Note: The initial split position in landscape is different between tablet and phone.
-        // In landscape, tablet will let the first app split to right side, and phone will
-        // split to left side.
-        if (tapl.isTablet) {
-            // TAPL's currentTask on tablet is sometimes not what we expected if the overview
-            // contains more than 3 task views. We need to use uiautomator directly to find the
-            // second task to split.
-            tapl.workspace.switchToOverview().overviewActions.clickSplit()
-            val snapshots = tapl.device.wait(Until.findObjects(overviewSnapshotSelector),
-                    TIMEOUT_MS)
-            if (snapshots == null || snapshots.size < 1) {
-                error("Fail to find a overview snapshot to split.")
-            }
-
-            // Find the second task in the upper right corner in split select mode by sorting
-            // 'left' in descending order and 'top' in ascending order.
-            snapshots.sortWith { t1: UiObject2, t2: UiObject2 ->
-                t2.getVisibleBounds().left - t1.getVisibleBounds().left
-            }
-            snapshots.sortWith { t1: UiObject2, t2: UiObject2 ->
-                t1.getVisibleBounds().top - t2.getVisibleBounds().top
-            }
-            snapshots[0].click()
-        } else {
-            tapl.workspace
-                    .switchToOverview()
-                    .currentTask
-                    .tapMenu()
-                    .tapSplitMenuItem()
-                    .currentTask
-                    .open()
-        }
-        SystemClock.sleep(TIMEOUT_MS)
-    }
-
     @Presubmit
     @Test
     override fun pipOverlayLayerAppearThenDisappear() {
@@ -190,11 +135,10 @@
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
-                    // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
-                    supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
+                // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
+                supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
             )
-        }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
index b95732e..2f7a25e 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
@@ -19,7 +19,7 @@
 import android.platform.test.annotations.Presubmit
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTest
 import androidx.test.filters.RequiresDevice
 import org.junit.Assume
 import org.junit.FixMethodOrder
@@ -53,10 +53,9 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class AutoEnterPipOnGoToHomeTest(flicker: FlickerTest) : EnterPipViaAppUiButtonTest(flicker) {
-    override val thisTransition: FlickerBuilder.() -> Unit = {
-        transitions { tapl.goHome() }
-    }
+open class AutoEnterPipOnGoToHomeTest(flicker: LegacyFlickerTest) :
+    EnterPipViaAppUiButtonTest(flicker) {
+    override val thisTransition: FlickerBuilder.() -> Unit = { transitions { tapl.goHome() } }
 
     override val defaultEnterPip: FlickerBuilder.() -> Unit = {
         setup {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt
index afcc172..ca28f52 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt
@@ -20,8 +20,7 @@
 import android.tools.common.traces.component.ComponentNameMatcher
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import androidx.test.filters.RequiresDevice
+import android.tools.device.flicker.legacy.LegacyFlickerTest
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -49,11 +48,10 @@
  *        apps are running before setup
  * ```
  */
-@RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class ClosePipBySwipingDownTest(flicker: FlickerTest) : ClosePipTransition(flicker) {
+class ClosePipBySwipingDownTest(flicker: LegacyFlickerTest) : ClosePipTransition(flicker) {
     override val thisTransition: FlickerBuilder.() -> Unit = {
         transitions {
             val pipRegion = wmHelper.getWindowRegion(pipApp).bounds
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTestCfArm.kt
deleted file mode 100644
index 02f6010..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTestCfArm.kt
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.flicker.pip
-
-import android.tools.common.Rotation
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
-import org.junit.FixMethodOrder
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class ClosePipBySwipingDownTestCfArm(flicker: FlickerTest) : ClosePipBySwipingDownTest(flicker) {
-    companion object {
-        /**
-         * Creates the test configurations.
-         *
-         * See [FlickerTestFactory.nonRotationTests] for configuring repetitions, screen orientation
-         * and navigation modes.
-         */
-        @Parameterized.Parameters(name = "{0}")
-        @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
-                supportedRotations = listOf(Rotation.ROTATION_0)
-            )
-        }
-    }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipTransition.kt
index e52b71e..a17144b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipTransition.kt
@@ -20,14 +20,14 @@
 import android.tools.common.Rotation
 import android.tools.common.traces.component.ComponentNameMatcher.Companion.LAUNCHER
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import com.android.server.wm.flicker.helpers.setRotation
 import org.junit.Test
 import org.junit.runners.Parameterized
 
 /** Base class for exiting pip (closing pip window) without returning to the app */
-abstract class ClosePipTransition(flicker: FlickerTest) : PipTransition(flicker) {
+abstract class ClosePipTransition(flicker: LegacyFlickerTest) : PipTransition(flicker) {
     override val thisTransition: FlickerBuilder.() -> Unit = {
         setup { this.setRotation(flicker.scenario.startRotation) }
         teardown { this.setRotation(Rotation.ROTATION_0) }
@@ -74,15 +74,14 @@
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestFactory.nonRotationTests] for configuring repetitions, screen orientation
-         * and navigation modes.
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring repetitions, screen
+         * orientation and navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
                 supportedRotations = listOf(Rotation.ROTATION_0)
             )
-        }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt
index 86fe583..4da628c 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt
@@ -19,8 +19,7 @@
 import android.platform.test.annotations.Presubmit
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import androidx.test.filters.RequiresDevice
+import android.tools.device.flicker.legacy.LegacyFlickerTest
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -49,11 +48,10 @@
  *        apps are running before setup
  * ```
  */
-@RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class ClosePipWithDismissButtonTest(flicker: FlickerTest) : ClosePipTransition(flicker) {
+class ClosePipWithDismissButtonTest(flicker: LegacyFlickerTest) : ClosePipTransition(flicker) {
     override val thisTransition: FlickerBuilder.() -> Unit = {
         transitions { pipApp.closePipWindow(wmHelper) }
     }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTestCfArm.kt
deleted file mode 100644
index 05262fe..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTestCfArm.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.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.flicker.pip
-
-import android.tools.common.Rotation
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
-import org.junit.FixMethodOrder
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class ClosePipWithDismissButtonTestCfArm(flicker: FlickerTest) :
-    ClosePipWithDismissButtonTest(flicker) {
-    companion object {
-        /**
-         * Creates the test configurations.
-         *
-         * See [FlickerTestFactory.nonRotationTests] for configuring repetitions, screen orientation
-         * and navigation modes.
-         */
-        @Parameterized.Parameters(name = "{0}")
-        @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
-                supportedRotations = listOf(Rotation.ROTATION_0)
-            )
-        }
-    }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt
index 01d67cc..e0b18de 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt
@@ -19,8 +19,7 @@
 import android.platform.test.annotations.Presubmit
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import androidx.test.filters.RequiresDevice
+import android.tools.device.flicker.legacy.LegacyFlickerTest
 import org.junit.Assume
 import org.junit.FixMethodOrder
 import org.junit.Test
@@ -40,14 +39,11 @@
  *     Press Home button or swipe up to go Home and put [pipApp] in pip mode
  * ```
  */
-@RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class EnterPipOnUserLeaveHintTest(flicker: FlickerTest) : EnterPipTransition(flicker) {
-    override val thisTransition: FlickerBuilder.() -> Unit = {
-        transitions { tapl.goHome() }
-    }
+class EnterPipOnUserLeaveHintTest(flicker: LegacyFlickerTest) : EnterPipTransition(flicker) {
+    override val thisTransition: FlickerBuilder.() -> Unit = { transitions { tapl.goHome() } }
 
     override val defaultEnterPip: FlickerBuilder.() -> Unit = {
         setup {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTestCfArm.kt
deleted file mode 100644
index 90f99c0..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTestCfArm.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.wm.shell.flicker.pip
-
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
-import org.junit.FixMethodOrder
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/** This test will fail because of b/264261596 */
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class EnterPipOnUserLeaveHintTestCfArm(flicker: FlickerTest) : EnterPipOnUserLeaveHintTest(flicker)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt
index 5480144..c003da6 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt
@@ -21,13 +21,13 @@
 import android.platform.test.annotations.Postsubmit
 import android.platform.test.annotations.Presubmit
 import android.tools.common.Rotation
+import android.tools.common.flicker.assertions.FlickerTest
 import android.tools.common.traces.component.ComponentNameMatcher
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import android.tools.device.helpers.WindowUtils
-import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.entireScreenCovered
 import com.android.server.wm.flicker.helpers.FixedOrientationAppHelper
 import com.android.server.wm.flicker.testapp.ActivityOptions.Pip.ACTION_ENTER_PIP
@@ -64,19 +64,16 @@
  *        apps are running before setup
  * ```
  */
-@RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class EnterPipToOtherOrientation(flicker: FlickerTest) : PipTransition(flicker) {
+class EnterPipToOtherOrientation(flicker: LegacyFlickerTest) : PipTransition(flicker) {
     private val testApp = FixedOrientationAppHelper(instrumentation)
     private val startingBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_90)
     private val endingBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_0)
 
     override val thisTransition: FlickerBuilder.() -> Unit = {
-        teardown {
-            testApp.exit(wmHelper)
-        }
+        teardown { testApp.exit(wmHelper) }
         transitions {
             // Enter PiP, and assert that the PiP is within bounds now that the device is back
             // in portrait
@@ -95,14 +92,13 @@
         setup {
             // Launch a portrait only app on the fullscreen stack
             testApp.launchViaIntent(
-                    wmHelper,
-                    stringExtras = mapOf(EXTRA_FIXED_ORIENTATION to ORIENTATION_PORTRAIT.toString())
+                wmHelper,
+                stringExtras = mapOf(EXTRA_FIXED_ORIENTATION to ORIENTATION_PORTRAIT.toString())
             )
             // Launch the PiP activity fixed as landscape, but don't enter PiP
             pipApp.launchViaIntent(
-                    wmHelper,
-                    stringExtras =
-                    mapOf(EXTRA_FIXED_ORIENTATION to ORIENTATION_LANDSCAPE.toString())
+                wmHelper,
+                stringExtras = mapOf(EXTRA_FIXED_ORIENTATION to ORIENTATION_LANDSCAPE.toString())
             )
         }
     }
@@ -207,13 +203,13 @@
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
          * navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
         fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+            return LegacyFlickerTestFactory.nonRotationTests(
                 supportedRotations = listOf(Rotation.ROTATION_0)
             )
         }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationCfArm.kt
deleted file mode 100644
index 5841666..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationCfArm.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.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.flicker.pip
-
-import android.tools.common.Rotation
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
-import org.junit.FixMethodOrder
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/** This test fails because of b/264261596 */
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class EnterPipToOtherOrientationCfArm(flicker: FlickerTest) :
-    EnterPipToOtherOrientation(flicker) {
-    companion object {
-        /**
-         * Creates the test configurations.
-         *
-         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
-         * navigation modes.
-         */
-        @Parameterized.Parameters(name = "{0}")
-        @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
-                supportedRotations = listOf(Rotation.ROTATION_0)
-            )
-        }
-    }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTransition.kt
index cdbdb85..6d20740 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTransition.kt
@@ -20,16 +20,14 @@
 import android.tools.common.Rotation
 import android.tools.common.traces.component.ComponentNameMatcher
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import org.junit.Test
 import org.junit.runners.Parameterized
 
-abstract class EnterPipTransition(flicker: FlickerTest) : PipTransition(flicker) {
+abstract class EnterPipTransition(flicker: LegacyFlickerTest) : PipTransition(flicker) {
     override val defaultEnterPip: FlickerBuilder.() -> Unit = {
-        setup {
-            pipApp.launchViaIntent(wmHelper)
-        }
+        setup { pipApp.launchViaIntent(wmHelper) }
     }
 
     /** Checks [pipApp] window remains visible throughout the animation */
@@ -126,15 +124,14 @@
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestFactory.nonRotationTests] for configuring repetitions, screen orientation
-         * and navigation modes.
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring repetitions, screen
+         * orientation and navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
                 supportedRotations = listOf(Rotation.ROTATION_0)
             )
-        }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt
index 95725b6..f9efffe 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt
@@ -18,8 +18,7 @@
 
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import androidx.test.filters.RequiresDevice
+import android.tools.device.flicker.legacy.LegacyFlickerTest
 import org.junit.FixMethodOrder
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
@@ -46,11 +45,10 @@
  *        apps are running before setup
  * ```
  */
-@RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class EnterPipViaAppUiButtonTest(flicker: FlickerTest) : EnterPipTransition(flicker) {
+open class EnterPipViaAppUiButtonTest(flicker: LegacyFlickerTest) : EnterPipTransition(flicker) {
     override val thisTransition: FlickerBuilder.() -> Unit = {
         transitions { pipApp.clickEnterPipButton(wmHelper) }
     }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTestCfArm.kt
deleted file mode 100644
index 4390f0b..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTestCfArm.kt
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.flicker.pip
-
-import android.tools.common.Rotation
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
-import org.junit.FixMethodOrder
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class EnterPipViaAppUiButtonTestCfArm(flicker: FlickerTest) : EnterPipViaAppUiButtonTest(flicker) {
-    companion object {
-        /**
-         * Creates the test configurations.
-         *
-         * See [FlickerTestFactory.nonRotationTests] for configuring repetitions, screen orientation
-         * and navigation modes.
-         */
-        @Parameterized.Parameters(name = "{0}")
-        @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
-                supportedRotations = listOf(Rotation.ROTATION_0)
-            )
-        }
-    }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt
index 5ac9829..dfffba8 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt
@@ -19,14 +19,14 @@
 import android.platform.test.annotations.Presubmit
 import android.tools.common.Rotation
 import android.tools.common.traces.component.ComponentNameMatcher
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import com.android.server.wm.flicker.helpers.SimpleAppHelper
 import org.junit.Test
 import org.junit.runners.Parameterized
 
 /** Base class for pip expand tests */
-abstract class ExitPipToAppTransition(flicker: FlickerTest) : PipTransition(flicker) {
+abstract class ExitPipToAppTransition(flicker: LegacyFlickerTest) : PipTransition(flicker) {
     protected val testApp = SimpleAppHelper(instrumentation)
 
     /**
@@ -130,15 +130,14 @@
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
          * navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
                 supportedRotations = listOf(Rotation.ROTATION_0)
             )
-        }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt
index 0b3d16a..c4e63c3 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt
@@ -18,8 +18,7 @@
 
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import androidx.test.filters.RequiresDevice
+import android.tools.device.flicker.legacy.LegacyFlickerTest
 import org.junit.FixMethodOrder
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
@@ -48,11 +47,11 @@
  *        apps are running before setup
  * ```
  */
-@RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class ExitPipToAppViaExpandButtonTest(flicker: FlickerTest) : ExitPipToAppTransition(flicker) {
+class ExitPipToAppViaExpandButtonTest(flicker: LegacyFlickerTest) :
+    ExitPipToAppTransition(flicker) {
     override val thisTransition: FlickerBuilder.() -> Unit = {
         setup {
             // launch an app behind the pip one
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTestCfArm.kt
deleted file mode 100644
index eccb85d..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTestCfArm.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.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.flicker.pip
-
-import android.tools.common.Rotation
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
-import org.junit.FixMethodOrder
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class ExitPipToAppViaExpandButtonTestCfArm(flicker: FlickerTest) :
-    ExitPipToAppViaExpandButtonTest(flicker) {
-    companion object {
-        /**
-         * Creates the test configurations.
-         *
-         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
-         * navigation modes.
-         */
-        @Parameterized.Parameters(name = "{0}")
-        @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
-                supportedRotations = listOf(Rotation.ROTATION_0)
-            )
-        }
-    }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt
index bb2d40b..839bbd4 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt
@@ -18,8 +18,7 @@
 
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import androidx.test.filters.RequiresDevice
+import android.tools.device.flicker.legacy.LegacyFlickerTest
 import org.junit.FixMethodOrder
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
@@ -47,11 +46,10 @@
  *        apps are running before setup
  * ```
  */
-@RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class ExitPipToAppViaIntentTest(flicker: FlickerTest) : ExitPipToAppTransition(flicker) {
+class ExitPipToAppViaIntentTest(flicker: LegacyFlickerTest) : ExitPipToAppTransition(flicker) {
     override val thisTransition: FlickerBuilder.() -> Unit = {
         setup {
             // launch an app behind the pip one
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTestCfArm.kt
deleted file mode 100644
index 6ab6a1f..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTestCfArm.kt
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.flicker.pip
-
-import android.tools.common.Rotation
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
-import org.junit.FixMethodOrder
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class ExitPipToAppViaIntentTestCfArm(flicker: FlickerTest) : ExitPipToAppViaIntentTest(flicker) {
-    companion object {
-        /**
-         * Creates the test configurations.
-         *
-         * See [FlickerTestFactory.nonRotationTests] for configuring repetitions, screen orientation
-         * and navigation modes.
-         */
-        @Parameterized.Parameters(name = "{0}")
-        @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
-                supportedRotations = listOf(Rotation.ROTATION_0)
-            )
-        }
-    }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
index fd16b6e..ea67e3d 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
@@ -21,9 +21,8 @@
 import android.tools.common.traces.component.ComponentNameMatcher
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
-import androidx.test.filters.RequiresDevice
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -51,11 +50,10 @@
  *        apps are running before setup
  * ```
  */
-@RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class ExpandPipOnDoubleClickTest(flicker: FlickerTest) : PipTransition(flicker) {
+class ExpandPipOnDoubleClickTest(flicker: LegacyFlickerTest) : PipTransition(flicker) {
     override val thisTransition: FlickerBuilder.() -> Unit = {
         transitions { pipApp.doubleClickPipWindow(wmHelper) }
     }
@@ -142,15 +140,14 @@
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
          * navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
                 supportedRotations = listOf(Rotation.ROTATION_0)
             )
-        }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTestTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTestTestCfArm.kt
deleted file mode 100644
index c096234..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTestTestCfArm.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.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.flicker.pip
-
-import android.tools.common.Rotation
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
-import org.junit.FixMethodOrder
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class ExpandPipOnDoubleClickTestTestCfArm(flicker: FlickerTest) :
-    ExpandPipOnDoubleClickTest(flicker) {
-    companion object {
-        /**
-         * Creates the test configurations.
-         *
-         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
-         * navigation modes.
-         */
-        @Parameterized.Parameters(name = "{0}")
-        @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
-                supportedRotations = listOf(Rotation.ROTATION_0)
-            )
-        }
-    }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTest.kt
index 253aa4c..0f30cef 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTest.kt
@@ -20,9 +20,8 @@
 import android.tools.common.Rotation
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
-import androidx.test.filters.RequiresDevice
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -30,11 +29,10 @@
 import org.junit.runners.Parameterized
 
 /** Test expanding a pip window via pinch out gesture. */
-@RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class ExpandPipOnPinchOpenTest(flicker: FlickerTest) : PipTransition(flicker) {
+class ExpandPipOnPinchOpenTest(flicker: LegacyFlickerTest) : PipTransition(flicker) {
     override val thisTransition: FlickerBuilder.() -> Unit = {
         transitions { pipApp.pinchOpenPipWindow(wmHelper, 0.25f, 30) }
     }
@@ -55,15 +53,14 @@
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
          * navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
                 supportedRotations = listOf(Rotation.ROTATION_0)
             )
-        }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTestCfArm.kt
deleted file mode 100644
index e064bf2..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTestCfArm.kt
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.flicker.pip
-
-import android.tools.common.Rotation
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
-import org.junit.FixMethodOrder
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class ExpandPipOnPinchOpenTestCfArm(flicker: FlickerTest) : ExpandPipOnPinchOpenTest(flicker) {
-    companion object {
-        /**
-         * Creates the test configurations.
-         *
-         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
-         * navigation modes.
-         */
-        @Parameterized.Parameters(name = "{0}")
-        @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
-                supportedRotations = listOf(Rotation.ROTATION_0)
-            )
-        }
-    }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt
index 094060f..4f88184 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt
@@ -19,7 +19,7 @@
 import android.platform.test.annotations.Presubmit
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTest
 import androidx.test.filters.RequiresDevice
 import com.android.wm.shell.flicker.Direction
 import org.junit.FixMethodOrder
@@ -55,7 +55,8 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class MovePipDownOnShelfHeightChange(flicker: FlickerTest) : MovePipShelfHeightTransition(flicker) {
+class MovePipDownOnShelfHeightChange(flicker: LegacyFlickerTest) :
+    MovePipShelfHeightTransition(flicker) {
     override val thisTransition: FlickerBuilder.() -> Unit = {
         teardown { testApp.exit(wmHelper) }
         transitions { testApp.launchViaIntent(wmHelper) }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt
index ff51c27..c10860a 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt
@@ -18,13 +18,13 @@
 
 import android.platform.test.annotations.Presubmit
 import android.tools.common.Rotation
+import android.tools.common.flicker.assertions.FlickerTest
 import android.tools.common.traces.component.ComponentNameMatcher
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import android.tools.device.helpers.WindowUtils
-import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.helpers.ImeAppHelper
 import com.android.server.wm.flicker.helpers.setRotation
 import org.junit.FixMethodOrder
@@ -34,11 +34,10 @@
 import org.junit.runners.Parameterized
 
 /** Test Pip launch. To run this test: `atest WMShellFlickerTests:PipKeyboardTest` */
-@RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class MovePipOnImeVisibilityChangeTest(flicker: FlickerTest) : PipTransition(flicker) {
+class MovePipOnImeVisibilityChangeTest(flicker: LegacyFlickerTest) : PipTransition(flicker) {
     private val imeApp = ImeAppHelper(instrumentation)
 
     override val thisTransition: FlickerBuilder.() -> Unit = {
@@ -80,7 +79,7 @@
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
         fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+            return LegacyFlickerTestFactory.nonRotationTests(
                 supportedRotations = listOf(Rotation.ROTATION_0)
             )
         }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTestCfArm.kt
deleted file mode 100644
index d3d77d2..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTestCfArm.kt
+++ /dev/null
@@ -1,44 +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.wm.shell.flicker.pip
-
-import android.tools.common.Rotation
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
-import org.junit.FixMethodOrder
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class MovePipOnImeVisibilityChangeTestCfArm(flicker: FlickerTest) :
-    MovePipOnImeVisibilityChangeTest(flicker) {
-    companion object {
-        private const val TAG_IME_VISIBLE = "imeIsVisible"
-
-        @Parameterized.Parameters(name = "{0}")
-        @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
-                supportedRotations = listOf(Rotation.ROTATION_0)
-            )
-        }
-    }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipShelfHeightTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipShelfHeightTransition.kt
index 109354a..9a2fa09 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipShelfHeightTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipShelfHeightTransition.kt
@@ -19,15 +19,15 @@
 import android.platform.test.annotations.Presubmit
 import android.tools.common.Rotation
 import android.tools.common.flicker.subject.region.RegionSubject
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import com.android.server.wm.flicker.helpers.FixedOrientationAppHelper
 import com.android.wm.shell.flicker.Direction
 import org.junit.Test
 import org.junit.runners.Parameterized
 
 /** Base class for pip tests with Launcher shelf height change */
-abstract class MovePipShelfHeightTransition(flicker: FlickerTest) : PipTransition(flicker) {
+abstract class MovePipShelfHeightTransition(flicker: LegacyFlickerTest) : PipTransition(flicker) {
     protected val testApp = FixedOrientationAppHelper(instrumentation)
 
     /** Checks [pipApp] window remains visible throughout the animation */
@@ -111,15 +111,14 @@
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
          * navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
                 supportedRotations = listOf(Rotation.ROTATION_0)
             )
-        }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt
index 27b061b..afb4af6 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt
@@ -19,7 +19,7 @@
 import android.platform.test.annotations.Presubmit
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTest
 import androidx.test.filters.RequiresDevice
 import com.android.wm.shell.flicker.Direction
 import org.junit.FixMethodOrder
@@ -55,14 +55,13 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class MovePipUpOnShelfHeightChangeTest(flicker: FlickerTest) :
+open class MovePipUpOnShelfHeightChangeTest(flicker: LegacyFlickerTest) :
     MovePipShelfHeightTransition(flicker) {
-    override val thisTransition: FlickerBuilder.() -> Unit =
-        {
-            setup { testApp.launchViaIntent(wmHelper) }
-            transitions { tapl.pressHome() }
-            teardown { testApp.exit(wmHelper) }
-        }
+    override val thisTransition: FlickerBuilder.() -> Unit = {
+        setup { testApp.launchViaIntent(wmHelper) }
+        transitions { tapl.pressHome() }
+        teardown { testApp.exit(wmHelper) }
+    }
 
     /** Checks that the visible region of [pipApp] window always moves up during the animation. */
     @Presubmit @Test fun pipWindowMovesUp() = pipWindowMoves(Direction.UP)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragTest.kt
index 9f81ba8..7085d55 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragTest.kt
@@ -20,8 +20,8 @@
 import android.platform.test.annotations.RequiresDevice
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import com.android.server.wm.flicker.testapp.ActivityOptions
 import org.junit.FixMethodOrder
 import org.junit.Test
@@ -34,7 +34,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class PipDragTest(flicker: FlickerTest) : PipTransition(flicker) {
+class PipDragTest(flicker: LegacyFlickerTest) : PipTransition(flicker) {
     private var isDraggedLeft: Boolean = true
 
     override val thisTransition: FlickerBuilder.() -> Unit = {
@@ -81,13 +81,11 @@
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
          * navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests()
-        }
+        fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt
index 9fe9f52..2b87766 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt
@@ -21,8 +21,8 @@
 import android.tools.common.Rotation
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.helpers.setRotation
@@ -38,7 +38,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class PipDragThenSnapTest(flicker: FlickerTest) : PipTransition(flicker) {
+class PipDragThenSnapTest(flicker: LegacyFlickerTest) : PipTransition(flicker) {
     // represents the direction in which the pip window should be snapping
     private var willSnapRight: Boolean = true
 
@@ -99,15 +99,14 @@
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
          * navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
                 supportedRotations = listOf(Rotation.ROTATION_0)
             )
-        }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt
index 60bf5ff..adc5ee3 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt
@@ -21,8 +21,8 @@
 import android.tools.common.Rotation
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
 import org.junit.FixMethodOrder
 import org.junit.Test
@@ -36,7 +36,7 @@
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 @FlakyTest(bugId = 270677470)
-class PipPinchInTest(flicker: FlickerTest) : PipTransition(flicker) {
+class PipPinchInTest(flicker: LegacyFlickerTest) : PipTransition(flicker) {
     override val thisTransition: FlickerBuilder.() -> Unit = {
         transitions { pipApp.pinchInPipWindow(wmHelper, 0.4f, 30) }
     }
@@ -57,15 +57,14 @@
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
          * navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
                 supportedRotations = listOf(Rotation.ROTATION_0)
             )
-        }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt
index 17a178f..096af39 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt
@@ -22,7 +22,7 @@
 import android.tools.common.Rotation
 import android.tools.common.traces.component.ComponentNameMatcher
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule.Companion.removeAllTasksButHome
 import android.tools.device.helpers.WindowUtils
 import com.android.server.wm.flicker.helpers.PipAppHelper
@@ -32,7 +32,7 @@
 import com.google.common.truth.Truth
 import org.junit.Test
 
-abstract class PipTransition(flicker: FlickerTest) : BaseTest(flicker) {
+abstract class PipTransition(flicker: LegacyFlickerTest) : BaseTest(flicker) {
     protected val pipApp = PipAppHelper(instrumentation)
     protected val displayBounds = WindowUtils.getDisplayBounds(flicker.scenario.startRotation)
     protected val broadcastActionTrigger = BroadcastActionTrigger(instrumentation)
@@ -78,16 +78,16 @@
     /** Defines the default method of entering PiP */
     protected open val defaultEnterPip: FlickerBuilder.() -> Unit = {
         setup {
-            pipApp.launchViaIntentAndWaitForPip(wmHelper,
-                    stringExtras = mapOf(ActivityOptions.Pip.EXTRA_ENTER_PIP to "true"))
+            pipApp.launchViaIntentAndWaitForPip(
+                wmHelper,
+                stringExtras = mapOf(ActivityOptions.Pip.EXTRA_ENTER_PIP to "true")
+            )
         }
     }
 
     /** Defines the default teardown required to clean up after the test */
     protected open val defaultTeardown: FlickerBuilder.() -> Unit = {
-        teardown {
-            pipApp.exit(wmHelper)
-        }
+        teardown { pipApp.exit(wmHelper) }
     }
 
     @Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt
index c618e5a..c315e74 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt
@@ -21,10 +21,11 @@
 import android.platform.test.annotations.Postsubmit
 import android.platform.test.annotations.Presubmit
 import android.tools.common.Rotation
+import android.tools.common.flicker.assertions.FlickerTest
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import android.tools.device.helpers.WindowUtils
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.testapp.ActivityOptions
@@ -46,7 +47,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class SetRequestedOrientationWhilePinned(flicker: FlickerTest) : PipTransition(flicker) {
+open class SetRequestedOrientationWhilePinned(flicker: LegacyFlickerTest) : PipTransition(flicker) {
     private val startingBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_0)
     private val endingBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_90)
 
@@ -69,20 +70,19 @@
         setup {
             // Launch the PiP activity fixed as landscape.
             pipApp.launchViaIntent(
-                    wmHelper,
-                    stringExtras =
-                    mapOf(EXTRA_FIXED_ORIENTATION to ORIENTATION_LANDSCAPE.toString())
+                wmHelper,
+                stringExtras = mapOf(EXTRA_FIXED_ORIENTATION to ORIENTATION_LANDSCAPE.toString())
             )
             // Enter PiP.
             broadcastActionTrigger.doAction(ActivityOptions.Pip.ACTION_ENTER_PIP)
             // System bar may fade out during fixed rotation.
             wmHelper
-                    .StateSyncBuilder()
-                    .withPipShown()
-                    .withRotation(Rotation.ROTATION_0)
-                    .withNavOrTaskBarVisible()
-                    .withStatusBarVisible()
-                    .waitForAndVerify()
+                .StateSyncBuilder()
+                .withPipShown()
+                .withRotation(Rotation.ROTATION_0)
+                .withNavOrTaskBarVisible()
+                .withStatusBarVisible()
+                .waitForAndVerify()
         }
     }
 
@@ -150,7 +150,7 @@
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
         fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+            return LegacyFlickerTestFactory.nonRotationTests(
                 supportedRotations = listOf(Rotation.ROTATION_0)
             )
         }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt
index 43d6c8f..e588f87 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt
@@ -17,12 +17,12 @@
 package com.android.wm.shell.flicker.pip
 
 import android.platform.test.annotations.Presubmit
+import android.tools.common.flicker.assertions.FlickerTest
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import android.tools.device.helpers.WindowUtils
-import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.helpers.SimpleAppHelper
 import com.android.server.wm.flicker.helpers.setRotation
 import org.junit.FixMethodOrder
@@ -54,11 +54,10 @@
  *        apps are running before setup
  * ```
  */
-@RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class ShowPipAndRotateDisplay(flicker: FlickerTest) : PipTransition(flicker) {
+class ShowPipAndRotateDisplay(flicker: LegacyFlickerTest) : PipTransition(flicker) {
     private val testApp = SimpleAppHelper(instrumentation)
     private val screenBoundsStart = WindowUtils.getDisplayBounds(flicker.scenario.startRotation)
     private val screenBoundsEnd = WindowUtils.getDisplayBounds(flicker.scenario.endRotation)
@@ -154,13 +153,13 @@
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestFactory.nonRotationTests] for configuring repetitions, screen orientation
-         * and navigation modes.
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring repetitions, screen
+         * orientation and navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
         fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.rotationTests()
+            return LegacyFlickerTestFactory.rotationTests()
         }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplayCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplayCfArm.kt
deleted file mode 100644
index b7a2c47..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplayCfArm.kt
+++ /dev/null
@@ -1,44 +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.wm.shell.flicker.pip
-
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
-import org.junit.FixMethodOrder
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class ShowPipAndRotateDisplayCfArm(flicker: FlickerTest) : ShowPipAndRotateDisplay(flicker) {
-    companion object {
-        /**
-         * Creates the test configurations.
-         *
-         * See [FlickerTestFactory.nonRotationTests] for configuring repetitions, screen orientation
-         * and navigation modes.
-         */
-        @Parameterized.Parameters(name = "{0}")
-        @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.rotationTests()
-        }
-    }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/Utils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/Utils.kt
new file mode 100644
index 0000000..610cede
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/Utils.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.wm.shell.flicker.service
+
+import android.app.Instrumentation
+import android.platform.test.rule.NavigationModeRule
+import android.platform.test.rule.PressHomeRule
+import android.platform.test.rule.UnlockScreenRule
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.apphelpers.MessagingAppHelper
+import android.tools.device.flicker.rules.ChangeDisplayOrientationRule
+import android.tools.device.flicker.rules.LaunchAppRule
+import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule
+import androidx.test.platform.app.InstrumentationRegistry
+import org.junit.rules.RuleChain
+
+object Utils {
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+
+    fun testSetupRule(navigationMode: NavBar, rotation: Rotation): RuleChain {
+        return RuleChain.outerRule(UnlockScreenRule())
+            .around(
+                NavigationModeRule(navigationMode.value, /* changeNavigationModeAfterTest */ false)
+            )
+            .around(
+                LaunchAppRule(MessagingAppHelper(instrumentation), clearCacheAfterParsing = false)
+            )
+            .around(RemoveAllTasksButHomeRule())
+            .around(
+                ChangeDisplayOrientationRule(
+                    rotation,
+                    resetOrientationAfterTest = false,
+                    clearCacheAfterParsing = false
+                )
+            )
+            .around(PressHomeRule())
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/OWNERS b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/OWNERS
new file mode 100644
index 0000000..3ab6a1e
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/OWNERS
@@ -0,0 +1,2 @@
+# Android > Android OS & Apps > Framework (Java + Native) > Window Manager > WM Shell > Split Screen
+# Bug component: 928697
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/SplitScreenUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/SplitScreenUtils.kt
new file mode 100644
index 0000000..e640dc4
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/SplitScreenUtils.kt
@@ -0,0 +1,387 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen
+
+import android.app.Instrumentation
+import android.graphics.Point
+import android.os.SystemClock
+import android.tools.common.traces.component.ComponentNameMatcher
+import android.tools.common.traces.component.IComponentMatcher
+import android.tools.common.traces.component.IComponentNameMatcher
+import android.tools.device.apphelpers.StandardAppHelper
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import android.tools.device.traces.parsers.toFlickerComponent
+import android.view.InputDevice
+import android.view.MotionEvent
+import android.view.ViewConfiguration
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.BySelector
+import androidx.test.uiautomator.UiDevice
+import androidx.test.uiautomator.UiObject2
+import androidx.test.uiautomator.Until
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.ImeAppHelper
+import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
+import com.android.server.wm.flicker.helpers.NotificationAppHelper
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.server.wm.flicker.testapp.ActivityOptions
+import com.android.wm.shell.flicker.LAUNCHER_UI_PACKAGE_NAME
+import com.android.wm.shell.flicker.SYSTEM_UI_PACKAGE_NAME
+import org.junit.Assert.assertNotNull
+
+object SplitScreenUtils {
+    private const val TIMEOUT_MS = 3_000L
+    private const val DRAG_DURATION_MS = 1_000L
+    private const val NOTIFICATION_SCROLLER = "notification_stack_scroller"
+    private const val DIVIDER_BAR = "docked_divider_handle"
+    private const val OVERVIEW_SNAPSHOT = "snapshot"
+    private const val GESTURE_STEP_MS = 16L
+    private val LONG_PRESS_TIME_MS = ViewConfiguration.getLongPressTimeout() * 2L
+    private val SPLIT_DECOR_MANAGER = ComponentNameMatcher("", "SplitDecorManager#")
+
+    private val notificationScrollerSelector: BySelector
+        get() = By.res(SYSTEM_UI_PACKAGE_NAME, NOTIFICATION_SCROLLER)
+    private val notificationContentSelector: BySelector
+        get() = By.text("Flicker Test Notification")
+    private val dividerBarSelector: BySelector
+        get() = By.res(SYSTEM_UI_PACKAGE_NAME, DIVIDER_BAR)
+    private val overviewSnapshotSelector: BySelector
+        get() = By.res(LAUNCHER_UI_PACKAGE_NAME, OVERVIEW_SNAPSHOT)
+
+    fun getPrimary(instrumentation: Instrumentation): StandardAppHelper =
+        SimpleAppHelper(
+            instrumentation,
+            ActivityOptions.SplitScreen.Primary.LABEL,
+            ActivityOptions.SplitScreen.Primary.COMPONENT.toFlickerComponent()
+        )
+
+    fun getSecondary(instrumentation: Instrumentation): StandardAppHelper =
+        SimpleAppHelper(
+            instrumentation,
+            ActivityOptions.SplitScreen.Secondary.LABEL,
+            ActivityOptions.SplitScreen.Secondary.COMPONENT.toFlickerComponent()
+        )
+
+    fun getNonResizeable(instrumentation: Instrumentation): NonResizeableAppHelper =
+        NonResizeableAppHelper(instrumentation)
+
+    fun getSendNotification(instrumentation: Instrumentation): NotificationAppHelper =
+        NotificationAppHelper(instrumentation)
+
+    fun getIme(instrumentation: Instrumentation): ImeAppHelper = ImeAppHelper(instrumentation)
+
+    fun waitForSplitComplete(
+        wmHelper: WindowManagerStateHelper,
+        primaryApp: IComponentMatcher,
+        secondaryApp: IComponentMatcher,
+    ) {
+        wmHelper
+            .StateSyncBuilder()
+            .withWindowSurfaceAppeared(primaryApp)
+            .withWindowSurfaceAppeared(secondaryApp)
+            .withSplitDividerVisible()
+            .waitForAndVerify()
+    }
+
+    fun enterSplit(
+        wmHelper: WindowManagerStateHelper,
+        tapl: LauncherInstrumentation,
+        device: UiDevice,
+        primaryApp: StandardAppHelper,
+        secondaryApp: StandardAppHelper
+    ) {
+        primaryApp.launchViaIntent(wmHelper)
+        secondaryApp.launchViaIntent(wmHelper)
+        tapl.goHome()
+        wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
+        splitFromOverview(tapl, device)
+        waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+    }
+
+    fun splitFromOverview(tapl: LauncherInstrumentation, device: UiDevice) {
+        // Note: The initial split position in landscape is different between tablet and phone.
+        // In landscape, tablet will let the first app split to right side, and phone will
+        // split to left side.
+        if (tapl.isTablet) {
+            // TAPL's currentTask on tablet is sometimes not what we expected if the overview
+            // contains more than 3 task views. We need to use uiautomator directly to find the
+            // second task to split.
+            tapl.workspace.switchToOverview().overviewActions.clickSplit()
+            val snapshots = device.wait(Until.findObjects(overviewSnapshotSelector), TIMEOUT_MS)
+            if (snapshots == null || snapshots.size < 1) {
+                error("Fail to find a overview snapshot to split.")
+            }
+
+            // Find the second task in the upper right corner in split select mode by sorting
+            // 'left' in descending order and 'top' in ascending order.
+            snapshots.sortWith { t1: UiObject2, t2: UiObject2 ->
+                t2.getVisibleBounds().left - t1.getVisibleBounds().left
+            }
+            snapshots.sortWith { t1: UiObject2, t2: UiObject2 ->
+                t1.getVisibleBounds().top - t2.getVisibleBounds().top
+            }
+            snapshots[0].click()
+        } else {
+            tapl.workspace
+                .switchToOverview()
+                .currentTask
+                .tapMenu()
+                .tapSplitMenuItem()
+                .currentTask
+                .open()
+        }
+        SystemClock.sleep(TIMEOUT_MS)
+    }
+
+    fun enterSplitViaIntent(
+        wmHelper: WindowManagerStateHelper,
+        primaryApp: StandardAppHelper,
+        secondaryApp: StandardAppHelper
+    ) {
+        val stringExtras =
+            mapOf(ActivityOptions.SplitScreen.Primary.EXTRA_LAUNCH_ADJACENT to "true")
+        primaryApp.launchViaIntent(wmHelper, null, null, stringExtras)
+        SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+    }
+
+    fun dragFromNotificationToSplit(
+        instrumentation: Instrumentation,
+        device: UiDevice,
+        wmHelper: WindowManagerStateHelper
+    ) {
+        val displayBounds =
+            wmHelper.currentState.layerState.displays.firstOrNull { !it.isVirtual }?.layerStackSpace
+                ?: error("Display not found")
+
+        // Pull down the notifications
+        device.swipe(
+            displayBounds.centerX(),
+            5,
+            displayBounds.centerX(),
+            displayBounds.bottom,
+            50 /* steps */
+        )
+        SystemClock.sleep(TIMEOUT_MS)
+
+        // Find the target notification
+        val notificationScroller =
+            device.wait(Until.findObject(notificationScrollerSelector), TIMEOUT_MS)
+                ?: error("Unable to find view $notificationScrollerSelector")
+        var notificationContent = notificationScroller.findObject(notificationContentSelector)
+
+        while (notificationContent == null) {
+            device.swipe(
+                displayBounds.centerX(),
+                displayBounds.centerY(),
+                displayBounds.centerX(),
+                displayBounds.centerY() - 150,
+                20 /* steps */
+            )
+            notificationContent = notificationScroller.findObject(notificationContentSelector)
+        }
+
+        // Drag to split
+        val dragStart = notificationContent.visibleCenter
+        val dragMiddle = Point(dragStart.x + 50, dragStart.y)
+        val dragEnd = Point(displayBounds.width / 4, displayBounds.width / 4)
+        val downTime = SystemClock.uptimeMillis()
+
+        touch(instrumentation, MotionEvent.ACTION_DOWN, downTime, downTime, TIMEOUT_MS, dragStart)
+        // It needs a horizontal movement to trigger the drag
+        touchMove(
+            instrumentation,
+            downTime,
+            SystemClock.uptimeMillis(),
+            DRAG_DURATION_MS,
+            dragStart,
+            dragMiddle
+        )
+        touchMove(
+            instrumentation,
+            downTime,
+            SystemClock.uptimeMillis(),
+            DRAG_DURATION_MS,
+            dragMiddle,
+            dragEnd
+        )
+        // Wait for a while to start splitting
+        SystemClock.sleep(TIMEOUT_MS)
+        touch(
+            instrumentation,
+            MotionEvent.ACTION_UP,
+            downTime,
+            SystemClock.uptimeMillis(),
+            GESTURE_STEP_MS,
+            dragEnd
+        )
+        SystemClock.sleep(TIMEOUT_MS)
+    }
+
+    fun touch(
+        instrumentation: Instrumentation,
+        action: Int,
+        downTime: Long,
+        eventTime: Long,
+        duration: Long,
+        point: Point
+    ) {
+        val motionEvent =
+            MotionEvent.obtain(downTime, eventTime, action, point.x.toFloat(), point.y.toFloat(), 0)
+        motionEvent.source = InputDevice.SOURCE_TOUCHSCREEN
+        instrumentation.uiAutomation.injectInputEvent(motionEvent, true)
+        motionEvent.recycle()
+        SystemClock.sleep(duration)
+    }
+
+    fun touchMove(
+        instrumentation: Instrumentation,
+        downTime: Long,
+        eventTime: Long,
+        duration: Long,
+        from: Point,
+        to: Point
+    ) {
+        val steps: Long = duration / GESTURE_STEP_MS
+        var currentTime = eventTime
+        var currentX = from.x.toFloat()
+        var currentY = from.y.toFloat()
+        val stepX = (to.x.toFloat() - from.x.toFloat()) / steps.toFloat()
+        val stepY = (to.y.toFloat() - from.y.toFloat()) / steps.toFloat()
+
+        for (i in 1..steps) {
+            val motionMove =
+                MotionEvent.obtain(
+                    downTime,
+                    currentTime,
+                    MotionEvent.ACTION_MOVE,
+                    currentX,
+                    currentY,
+                    0
+                )
+            motionMove.source = InputDevice.SOURCE_TOUCHSCREEN
+            instrumentation.uiAutomation.injectInputEvent(motionMove, true)
+            motionMove.recycle()
+
+            currentTime += GESTURE_STEP_MS
+            if (i == steps - 1) {
+                currentX = to.x.toFloat()
+                currentY = to.y.toFloat()
+            } else {
+                currentX += stepX
+                currentY += stepY
+            }
+            SystemClock.sleep(GESTURE_STEP_MS)
+        }
+    }
+
+    fun createShortcutOnHotseatIfNotExist(tapl: LauncherInstrumentation, appName: String) {
+        tapl.workspace.deleteAppIcon(tapl.workspace.getHotseatAppIcon(0))
+        val allApps = tapl.workspace.switchToAllApps()
+        allApps.freeze()
+        try {
+            allApps.getAppIcon(appName).dragToHotseat(0)
+        } finally {
+            allApps.unfreeze()
+        }
+    }
+
+    fun dragDividerToResizeAndWait(device: UiDevice, wmHelper: WindowManagerStateHelper) {
+        val displayBounds =
+            wmHelper.currentState.layerState.displays.firstOrNull { !it.isVirtual }?.layerStackSpace
+                ?: error("Display not found")
+        val dividerBar = device.wait(Until.findObject(dividerBarSelector), TIMEOUT_MS)
+        dividerBar.drag(Point(displayBounds.width * 1 / 3, displayBounds.height * 2 / 3), 200)
+
+        wmHelper
+            .StateSyncBuilder()
+            .withWindowSurfaceDisappeared(SPLIT_DECOR_MANAGER)
+            .waitForAndVerify()
+    }
+
+    fun dragDividerToDismissSplit(
+        device: UiDevice,
+        wmHelper: WindowManagerStateHelper,
+        dragToRight: Boolean,
+        dragToBottom: Boolean
+    ) {
+        val displayBounds =
+            wmHelper.currentState.layerState.displays.firstOrNull { !it.isVirtual }?.layerStackSpace
+                ?: error("Display not found")
+        val dividerBar = device.wait(Until.findObject(dividerBarSelector), TIMEOUT_MS)
+        dividerBar.drag(
+            Point(
+                if (dragToRight) {
+                    displayBounds.width * 4 / 5
+                } else {
+                    displayBounds.width * 1 / 5
+                },
+                if (dragToBottom) {
+                    displayBounds.height * 4 / 5
+                } else {
+                    displayBounds.height * 1 / 5
+                }
+            )
+        )
+    }
+
+    fun doubleTapDividerToSwitch(device: UiDevice) {
+        val dividerBar = device.wait(Until.findObject(dividerBarSelector), TIMEOUT_MS)
+        val interval =
+            (ViewConfiguration.getDoubleTapTimeout() + ViewConfiguration.getDoubleTapMinTime()) / 2
+        dividerBar.click()
+        SystemClock.sleep(interval.toLong())
+        dividerBar.click()
+    }
+
+    fun copyContentInSplit(
+        instrumentation: Instrumentation,
+        device: UiDevice,
+        sourceApp: IComponentNameMatcher,
+        destinationApp: IComponentNameMatcher,
+    ) {
+        // Copy text from sourceApp
+        val textView =
+            device.wait(
+                Until.findObject(By.res(sourceApp.packageName, "SplitScreenTest")),
+                TIMEOUT_MS
+            )
+        assertNotNull("Unable to find the TextView", textView)
+        textView.click(LONG_PRESS_TIME_MS)
+
+        val copyBtn = device.wait(Until.findObject(By.text("Copy")), TIMEOUT_MS)
+        assertNotNull("Unable to find the copy button", copyBtn)
+        copyBtn.click()
+
+        // Paste text to destinationApp
+        val editText =
+            device.wait(
+                Until.findObject(By.res(destinationApp.packageName, "plain_text_input")),
+                TIMEOUT_MS
+            )
+        assertNotNull("Unable to find the EditText", editText)
+        editText.click(LONG_PRESS_TIME_MS)
+
+        val pasteBtn = device.wait(Until.findObject(By.text("Paste")), TIMEOUT_MS)
+        assertNotNull("Unable to find the paste button", pasteBtn)
+        pasteBtn.click()
+
+        // Verify text
+        if (!textView.text.contentEquals(editText.text)) {
+            error("Fail to copy content in split")
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavLandscape.kt
new file mode 100644
index 0000000..2494054
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavLandscape.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.wm.shell.flicker.service.splitscreen.platinum
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.CopyContentInSplit
+import org.junit.Test
+
+class CopyContentInSplitGesturalNavLandscape : CopyContentInSplit(Rotation.ROTATION_90) {
+    @PlatinumTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    override fun copyContentInSplit() = super.copyContentInSplit()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavPortrait.kt
new file mode 100644
index 0000000..57943ec
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavPortrait.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.wm.shell.flicker.service.splitscreen.platinum
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.CopyContentInSplit
+import org.junit.Test
+
+class CopyContentInSplitGesturalNavPortrait : CopyContentInSplit(Rotation.ROTATION_0) {
+    @PlatinumTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    override fun copyContentInSplit() = super.copyContentInSplit()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavLandscape.kt
new file mode 100644
index 0000000..6f0e202
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavLandscape.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.wm.shell.flicker.service.splitscreen.platinum
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByDivider
+import org.junit.Test
+
+class DismissSplitScreenByDividerGesturalNavLandscape :
+    DismissSplitScreenByDivider(Rotation.ROTATION_90) {
+    @PlatinumTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    override fun dismissSplitScreenByDivider() = super.dismissSplitScreenByDivider()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavPortrait.kt
new file mode 100644
index 0000000..dac8fa2
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavPortrait.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.wm.shell.flicker.service.splitscreen.platinum
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByDivider
+import org.junit.Test
+
+class DismissSplitScreenByDividerGesturalNavPortrait :
+    DismissSplitScreenByDivider(Rotation.ROTATION_0) {
+    @PlatinumTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    override fun dismissSplitScreenByDivider() = super.dismissSplitScreenByDivider()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavLandscape.kt
new file mode 100644
index 0000000..baecc16
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavLandscape.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.wm.shell.flicker.service.splitscreen.platinum
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByGoHome
+import org.junit.Test
+
+class DismissSplitScreenByGoHomeGesturalNavLandscape :
+    DismissSplitScreenByGoHome(Rotation.ROTATION_90) {
+    @PlatinumTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    override fun dismissSplitScreenByGoHome() = super.dismissSplitScreenByGoHome()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavPortrait.kt
new file mode 100644
index 0000000..3063ea5
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavPortrait.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.wm.shell.flicker.service.splitscreen.platinum
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByGoHome
+import org.junit.Test
+
+class DismissSplitScreenByGoHomeGesturalNavPortrait :
+    DismissSplitScreenByGoHome(Rotation.ROTATION_0) {
+    @PlatinumTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    override fun dismissSplitScreenByGoHome() = super.dismissSplitScreenByGoHome()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavLandscape.kt
new file mode 100644
index 0000000..41660ba
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavLandscape.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.wm.shell.flicker.service.splitscreen.platinum
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.DragDividerToResize
+import org.junit.Test
+
+class DragDividerToResizeGesturalNavLandscape : DragDividerToResize(Rotation.ROTATION_90) {
+    @PlatinumTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    override fun dragDividerToResize() = super.dragDividerToResize()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavPortrait.kt
new file mode 100644
index 0000000..c41ffb7
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavPortrait.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.wm.shell.flicker.service.splitscreen.platinum
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.DragDividerToResize
+import org.junit.Test
+
+class DragDividerToResizeGesturalNavPortrait : DragDividerToResize(Rotation.ROTATION_0) {
+    @PlatinumTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    override fun dragDividerToResize() = super.dragDividerToResize()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt
new file mode 100644
index 0000000..afde55b
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.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.wm.shell.flicker.service.splitscreen.platinum
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromAllApps
+import org.junit.Test
+
+class EnterSplitScreenByDragFromAllAppsGesturalNavLandscape :
+    EnterSplitScreenByDragFromAllApps(Rotation.ROTATION_90) {
+    @PlatinumTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    override fun enterSplitScreenByDragFromAllApps() = super.enterSplitScreenByDragFromAllApps()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt
new file mode 100644
index 0000000..3765fc4
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.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.wm.shell.flicker.service.splitscreen.platinum
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromAllApps
+import org.junit.Test
+
+class EnterSplitScreenByDragFromAllAppsGesturalNavPortrait :
+    EnterSplitScreenByDragFromAllApps(Rotation.ROTATION_0) {
+    @PlatinumTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    override fun enterSplitScreenByDragFromAllApps() = super.enterSplitScreenByDragFromAllApps()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt
new file mode 100644
index 0000000..1e128fd
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.platinum
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromNotification
+import org.junit.Test
+
+class EnterSplitScreenByDragFromNotificationGesturalNavLandscape :
+    EnterSplitScreenByDragFromNotification(Rotation.ROTATION_90) {
+    @PlatinumTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    override fun enterSplitScreenByDragFromNotification() =
+        super.enterSplitScreenByDragFromNotification()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt
new file mode 100644
index 0000000..7767872
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.platinum
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromNotification
+import org.junit.Test
+
+class EnterSplitScreenByDragFromNotificationGesturalNavPortrait :
+    EnterSplitScreenByDragFromNotification(Rotation.ROTATION_0) {
+    @PlatinumTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    override fun enterSplitScreenByDragFromNotification() =
+        super.enterSplitScreenByDragFromNotification()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt
new file mode 100644
index 0000000..4ca4bd1
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.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.wm.shell.flicker.service.splitscreen.platinum
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromShortcut
+import org.junit.Test
+
+class EnterSplitScreenByDragFromShortcutGesturalNavLandscape :
+    EnterSplitScreenByDragFromShortcut(Rotation.ROTATION_90) {
+    @PlatinumTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    override fun enterSplitScreenByDragFromShortcut() = super.enterSplitScreenByDragFromShortcut()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt
new file mode 100644
index 0000000..2d9d258
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.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.wm.shell.flicker.service.splitscreen.platinum
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromShortcut
+import org.junit.Test
+
+class EnterSplitScreenByDragFromShortcutGesturalNavPortrait :
+    EnterSplitScreenByDragFromShortcut(Rotation.ROTATION_0) {
+    @PlatinumTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    override fun enterSplitScreenByDragFromShortcut() = super.enterSplitScreenByDragFromShortcut()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt
new file mode 100644
index 0000000..c9282ac
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.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.wm.shell.flicker.service.splitscreen.platinum
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromTaskbar
+import org.junit.Test
+
+class EnterSplitScreenByDragFromTaskbarGesturalNavLandscape :
+    EnterSplitScreenByDragFromTaskbar(Rotation.ROTATION_90) {
+    @PlatinumTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    override fun enterSplitScreenByDragFromTaskbar() = super.enterSplitScreenByDragFromTaskbar()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt
new file mode 100644
index 0000000..68c6d6c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.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.wm.shell.flicker.service.splitscreen.platinum
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromTaskbar
+import org.junit.Test
+
+class EnterSplitScreenByDragFromTaskbarGesturalNavPortrait :
+    EnterSplitScreenByDragFromTaskbar(Rotation.ROTATION_0) {
+    @PlatinumTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    override fun enterSplitScreenByDragFromTaskbar() = super.enterSplitScreenByDragFromTaskbar()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavLandscape.kt
new file mode 100644
index 0000000..304529e
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavLandscape.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.wm.shell.flicker.service.splitscreen.platinum
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenFromOverview
+import org.junit.Test
+
+class EnterSplitScreenFromOverviewGesturalNavLandscape :
+    EnterSplitScreenFromOverview(Rotation.ROTATION_90) {
+    @PlatinumTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    override fun enterSplitScreenFromOverview() = super.enterSplitScreenFromOverview()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavPortrait.kt
new file mode 100644
index 0000000..53a6b44
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavPortrait.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.wm.shell.flicker.service.splitscreen.platinum
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenFromOverview
+import org.junit.Test
+
+class EnterSplitScreenFromOverviewGesturalNavPortrait :
+    EnterSplitScreenFromOverview(Rotation.ROTATION_0) {
+    @PlatinumTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    override fun enterSplitScreenFromOverview() = super.enterSplitScreenFromOverview()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt
new file mode 100644
index 0000000..a467830
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavLandscape.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.wm.shell.flicker.service.splitscreen.platinum
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchAppByDoubleTapDivider
+import org.junit.Test
+
+class SwitchAppByDoubleTapDividerGesturalNavLandscape :
+    SwitchAppByDoubleTapDivider(Rotation.ROTATION_90) {
+    @PlatinumTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    override fun switchAppByDoubleTapDivider() = super.switchAppByDoubleTapDivider()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt
new file mode 100644
index 0000000..1524233
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavPortrait.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.wm.shell.flicker.service.splitscreen.platinum
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchAppByDoubleTapDivider
+import org.junit.Test
+
+class SwitchAppByDoubleTapDividerGesturalNavPortrait :
+    SwitchAppByDoubleTapDivider(Rotation.ROTATION_0) {
+    @PlatinumTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    override fun switchAppByDoubleTapDivider() = super.switchAppByDoubleTapDivider()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt
new file mode 100644
index 0000000..0389659
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.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.wm.shell.flicker.service.splitscreen.platinum
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromAnotherApp
+import org.junit.Test
+
+class SwitchBackToSplitFromAnotherAppGesturalNavLandscape :
+    SwitchBackToSplitFromAnotherApp(Rotation.ROTATION_90) {
+    @PlatinumTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    override fun switchBackToSplitFromAnotherApp() = super.switchBackToSplitFromAnotherApp()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt
new file mode 100644
index 0000000..7fadf18
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.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.wm.shell.flicker.service.splitscreen.platinum
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromAnotherApp
+import org.junit.Test
+
+class SwitchBackToSplitFromAnotherAppGesturalNavPortrait :
+    SwitchBackToSplitFromAnotherApp(Rotation.ROTATION_0) {
+    @PlatinumTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    override fun switchBackToSplitFromAnotherApp() = super.switchBackToSplitFromAnotherApp()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavLandscape.kt
new file mode 100644
index 0000000..148cc52
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavLandscape.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.wm.shell.flicker.service.splitscreen.platinum
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromHome
+import org.junit.Test
+
+class SwitchBackToSplitFromHomeGesturalNavLandscape :
+    SwitchBackToSplitFromHome(Rotation.ROTATION_90) {
+    @PlatinumTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    override fun switchBackToSplitFromHome() = super.switchBackToSplitFromHome()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavPortrait.kt
new file mode 100644
index 0000000..0641fb0
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavPortrait.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.wm.shell.flicker.service.splitscreen.platinum
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromHome
+import org.junit.Test
+
+class SwitchBackToSplitFromHomeGesturalNavPortrait :
+    SwitchBackToSplitFromHome(Rotation.ROTATION_0) {
+    @PlatinumTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    override fun switchBackToSplitFromHome() = super.switchBackToSplitFromHome()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavLandscape.kt
new file mode 100644
index 0000000..741f871
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavLandscape.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.wm.shell.flicker.service.splitscreen.platinum
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromRecent
+import org.junit.Test
+
+class SwitchBackToSplitFromRecentGesturalNavLandscape :
+    SwitchBackToSplitFromRecent(Rotation.ROTATION_90) {
+    @PlatinumTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    override fun switchBackToSplitFromRecent() = super.switchBackToSplitFromRecent()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavPortrait.kt
new file mode 100644
index 0000000..778e2d6
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavPortrait.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.wm.shell.flicker.service.splitscreen.platinum
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromRecent
+import org.junit.Test
+
+class SwitchBackToSplitFromRecentGesturalNavPortrait :
+    SwitchBackToSplitFromRecent(Rotation.ROTATION_0) {
+    @PlatinumTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    override fun switchBackToSplitFromRecent() = super.switchBackToSplitFromRecent()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavLandscape.kt
new file mode 100644
index 0000000..dcdca70
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavLandscape.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.wm.shell.flicker.service.splitscreen.platinum
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBetweenSplitPairs
+import org.junit.Test
+
+class SwitchBetweenSplitPairsGesturalNavLandscape : SwitchBetweenSplitPairs(Rotation.ROTATION_90) {
+    @PlatinumTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    override fun switchBetweenSplitPairs() = super.switchBetweenSplitPairs()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavPortrait.kt
new file mode 100644
index 0000000..3c69311
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavPortrait.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.wm.shell.flicker.service.splitscreen.platinum
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBetweenSplitPairs
+import org.junit.Test
+
+class SwitchBetweenSplitPairsGesturalNavPortrait : SwitchBetweenSplitPairs(Rotation.ROTATION_0) {
+    @PlatinumTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    override fun switchBetweenSplitPairs() = super.switchBetweenSplitPairs()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt
new file mode 100644
index 0000000..c6566f5
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.platinum
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.UnlockKeyguardToSplitScreen
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+@RunWith(BlockJUnit4ClassRunner::class)
+class UnlockKeyguardToSplitScreenGesturalNavLandscape : UnlockKeyguardToSplitScreen() {
+    @PlatinumTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    override fun unlockKeyguardToSplitScreen() = super.unlockKeyguardToSplitScreen()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt
new file mode 100644
index 0000000..bb1a502
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.platinum
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.UnlockKeyguardToSplitScreen
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+@RunWith(BlockJUnit4ClassRunner::class)
+class UnlockKeyguardToSplitScreenGesturalNavPortrait : UnlockKeyguardToSplitScreen() {
+    @PlatinumTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    override fun unlockKeyguardToSplitScreen() = super.unlockKeyguardToSplitScreen()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/CopyContentInSplit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/CopyContentInSplit.kt
new file mode 100644
index 0000000..5bfc889
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/CopyContentInSplit.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.wm.shell.flicker.service.splitscreen.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.wm.shell.flicker.service.Utils
+import org.junit.After
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Base Test Class")
+abstract class CopyContentInSplit
+@JvmOverloads
+constructor(val rotation: Rotation = Rotation.ROTATION_0) {
+
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val tapl = LauncherInstrumentation()
+    private val wmHelper = WindowManagerStateHelper(instrumentation)
+    private val device = UiDevice.getInstance(instrumentation)
+    private val primaryApp = SplitScreenUtils.getPrimary(instrumentation)
+    private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
+    private val textEditApp = SplitScreenUtils.getIme(instrumentation)
+
+    @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+
+    @Before
+    fun setup() {
+        tapl.setEnableRotation(true)
+        tapl.setExpectedRotation(rotation.value)
+
+        SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, textEditApp)
+    }
+
+    @Test
+    open fun copyContentInSplit() {
+        SplitScreenUtils.copyContentInSplit(instrumentation, device, primaryApp, textEditApp)
+    }
+
+    @After
+    fun teardown() {
+        primaryApp.exit(wmHelper)
+        secondaryApp.exit(wmHelper)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByDivider.kt
new file mode 100644
index 0000000..d07daff
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByDivider.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.wm.shell.flicker.service.Utils
+import org.junit.After
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Base Test Class")
+abstract class DismissSplitScreenByDivider
+@JvmOverloads
+constructor(val rotation: Rotation = Rotation.ROTATION_0) {
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val tapl = LauncherInstrumentation()
+    private val wmHelper = WindowManagerStateHelper(instrumentation)
+    private val device = UiDevice.getInstance(instrumentation)
+    private val primaryApp = SplitScreenUtils.getPrimary(instrumentation)
+    private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
+
+    @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+
+    @Before
+    fun setup() {
+        tapl.setEnableRotation(true)
+        tapl.setExpectedRotation(rotation.value)
+
+        SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
+    }
+
+    @Test
+    open fun dismissSplitScreenByDivider() {
+        if (tapl.isTablet) {
+            SplitScreenUtils.dragDividerToDismissSplit(
+                device,
+                wmHelper,
+                dragToRight = false,
+                dragToBottom = true
+            )
+        } else {
+            SplitScreenUtils.dragDividerToDismissSplit(
+                device,
+                wmHelper,
+                dragToRight = true,
+                dragToBottom = true
+            )
+        }
+        wmHelper.StateSyncBuilder().withFullScreenApp(secondaryApp).waitForAndVerify()
+    }
+
+    @After
+    fun teardown() {
+        primaryApp.exit(wmHelper)
+        secondaryApp.exit(wmHelper)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByGoHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByGoHome.kt
new file mode 100644
index 0000000..d428dea
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByGoHome.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.wm.shell.flicker.service.splitscreen.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.wm.shell.flicker.service.Utils
+import org.junit.After
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Base Test Class")
+abstract class DismissSplitScreenByGoHome
+@JvmOverloads
+constructor(val rotation: Rotation = Rotation.ROTATION_0) {
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val tapl = LauncherInstrumentation()
+    private val wmHelper = WindowManagerStateHelper(instrumentation)
+    private val device = UiDevice.getInstance(instrumentation)
+    private val primaryApp = SplitScreenUtils.getPrimary(instrumentation)
+    private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
+
+    @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+
+    @Before
+    fun setup() {
+        tapl.setEnableRotation(true)
+        tapl.setExpectedRotation(rotation.value)
+
+        SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
+    }
+
+    @Test
+    open fun dismissSplitScreenByGoHome() {
+        tapl.goHome()
+        wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
+    }
+
+    @After
+    fun teardown() {
+        primaryApp.exit(wmHelper)
+        secondaryApp.exit(wmHelper)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt
new file mode 100644
index 0000000..dc2a6ac
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt
@@ -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.wm.shell.flicker.service.splitscreen.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.wm.shell.flicker.service.Utils
+import org.junit.After
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Base Test Class")
+abstract class DragDividerToResize
+@JvmOverloads
+constructor(val rotation: Rotation = Rotation.ROTATION_0) {
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val tapl = LauncherInstrumentation()
+    private val wmHelper = WindowManagerStateHelper(instrumentation)
+    private val device = UiDevice.getInstance(instrumentation)
+    private val primaryApp = SplitScreenUtils.getPrimary(instrumentation)
+    private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
+
+    @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+
+    @Before
+    fun setup() {
+        tapl.setEnableRotation(true)
+        tapl.setExpectedRotation(rotation.value)
+
+        SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
+    }
+
+    @Test
+    open fun dragDividerToResize() {
+        SplitScreenUtils.dragDividerToResizeAndWait(device, wmHelper)
+    }
+
+    @After
+    fun teardown() {
+        primaryApp.exit(wmHelper)
+        secondaryApp.exit(wmHelper)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromAllApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromAllApps.kt
new file mode 100644
index 0000000..74f441d
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromAllApps.kt
@@ -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.wm.shell.flicker.service.splitscreen.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.wm.shell.flicker.service.Utils
+import org.junit.After
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Base Test Class")
+abstract class EnterSplitScreenByDragFromAllApps
+@JvmOverloads
+constructor(val rotation: Rotation = Rotation.ROTATION_0) {
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val tapl = LauncherInstrumentation()
+    private val wmHelper = WindowManagerStateHelper(instrumentation)
+    private val device = UiDevice.getInstance(instrumentation)
+    private val primaryApp = SplitScreenUtils.getPrimary(instrumentation)
+    private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
+
+    @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+
+    @Before
+    fun setup() {
+        Assume.assumeTrue(tapl.isTablet)
+
+        tapl.setEnableRotation(true)
+        tapl.setExpectedRotation(rotation.value)
+
+        tapl.goHome()
+        primaryApp.launchViaIntent(wmHelper)
+    }
+
+    @Test
+    open fun enterSplitScreenByDragFromAllApps() {
+        tapl.launchedAppState.taskbar
+            .openAllApps()
+            .getAppIcon(secondaryApp.appName)
+            .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
+        SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+    }
+
+    @After
+    fun teardown() {
+        primaryApp.exit(wmHelper)
+        secondaryApp.exit(wmHelper)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt
new file mode 100644
index 0000000..8d93c0d
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.wm.shell.flicker.service.Utils
+import org.junit.After
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Base Test Class")
+abstract class EnterSplitScreenByDragFromNotification
+@JvmOverloads
+constructor(val rotation: Rotation = Rotation.ROTATION_0) {
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val tapl = LauncherInstrumentation()
+    private val wmHelper = WindowManagerStateHelper(instrumentation)
+    private val device = UiDevice.getInstance(instrumentation)
+    private val primaryApp = SplitScreenUtils.getPrimary(instrumentation)
+    private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
+    private val sendNotificationApp = SplitScreenUtils.getSendNotification(instrumentation)
+
+    @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+
+    @Before
+    fun setup() {
+        Assume.assumeTrue(tapl.isTablet)
+
+        tapl.setEnableRotation(true)
+        tapl.setExpectedRotation(rotation.value)
+
+        // Send a notification
+        sendNotificationApp.launchViaIntent(wmHelper)
+        sendNotificationApp.postNotification(wmHelper)
+        tapl.goHome()
+        primaryApp.launchViaIntent(wmHelper)
+    }
+
+    @Test
+    open fun enterSplitScreenByDragFromNotification() {
+        SplitScreenUtils.dragFromNotificationToSplit(instrumentation, device, wmHelper)
+        SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, sendNotificationApp)
+    }
+
+    @After
+    fun teardown() {
+        primaryApp.exit(wmHelper)
+        secondaryApp.exit(wmHelper)
+        sendNotificationApp.exit(wmHelper)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt
new file mode 100644
index 0000000..36a458f
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.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.wm.shell.flicker.service.splitscreen.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.wm.shell.flicker.service.Utils
+import org.junit.After
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Base Test Class")
+abstract class EnterSplitScreenByDragFromShortcut
+@JvmOverloads
+constructor(val rotation: Rotation = Rotation.ROTATION_0) {
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val tapl = LauncherInstrumentation()
+    private val wmHelper = WindowManagerStateHelper(instrumentation)
+    private val device = UiDevice.getInstance(instrumentation)
+    private val primaryApp = SplitScreenUtils.getPrimary(instrumentation)
+    private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
+
+    @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+
+    @Before
+    fun setup() {
+        Assume.assumeTrue(tapl.isTablet)
+
+        tapl.setEnableRotation(true)
+        tapl.setExpectedRotation(rotation.value)
+
+        tapl.goHome()
+        SplitScreenUtils.createShortcutOnHotseatIfNotExist(tapl, secondaryApp.appName)
+        primaryApp.launchViaIntent(wmHelper)
+    }
+
+    @Test
+    open fun enterSplitScreenByDragFromShortcut() {
+        tapl.launchedAppState.taskbar
+            .getAppIcon(secondaryApp.appName)
+            .openDeepShortcutMenu()
+            .getMenuItem("Split Screen Secondary Activity")
+            .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
+        SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+
+        // TODO: Do we want this check in here? Add to the other tests?
+        //        flicker.splitScreenEntered(
+        //                primaryApp,
+        //                secondaryApp,
+        //                fromOtherApp = false,
+        //                appExistAtStart = false
+        //        )
+    }
+
+    @After
+    fun teardwon() {
+        primaryApp.exit(wmHelper)
+        secondaryApp.exit(wmHelper)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt
new file mode 100644
index 0000000..ed08041
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt
@@ -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.wm.shell.flicker.service.splitscreen.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.wm.shell.flicker.service.Utils
+import org.junit.After
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Base Test Class")
+abstract class EnterSplitScreenByDragFromTaskbar
+@JvmOverloads
+constructor(val rotation: Rotation = Rotation.ROTATION_0) {
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val tapl = LauncherInstrumentation()
+    private val wmHelper = WindowManagerStateHelper(instrumentation)
+    private val device = UiDevice.getInstance(instrumentation)
+    private val primaryApp = SplitScreenUtils.getPrimary(instrumentation)
+    private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
+
+    @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+
+    @Before
+    fun setup() {
+        Assume.assumeTrue(tapl.isTablet)
+
+        tapl.setEnableRotation(true)
+        tapl.setExpectedRotation(rotation.value)
+
+        tapl.goHome()
+        SplitScreenUtils.createShortcutOnHotseatIfNotExist(tapl, secondaryApp.appName)
+        primaryApp.launchViaIntent(wmHelper)
+    }
+
+    @Test
+    open fun enterSplitScreenByDragFromTaskbar() {
+        tapl.launchedAppState.taskbar
+            .getAppIcon(secondaryApp.appName)
+            .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
+        SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+    }
+
+    @After
+    fun teardown() {
+        primaryApp.exit(wmHelper)
+        secondaryApp.exit(wmHelper)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenFromOverview.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenFromOverview.kt
new file mode 100644
index 0000000..f164451
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenFromOverview.kt
@@ -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.wm.shell.flicker.service.splitscreen.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.wm.shell.flicker.service.Utils
+import org.junit.After
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Base Test Class")
+abstract class EnterSplitScreenFromOverview
+@JvmOverloads
+constructor(val rotation: Rotation = Rotation.ROTATION_0) {
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val tapl = LauncherInstrumentation()
+    private val wmHelper = WindowManagerStateHelper(instrumentation)
+    private val device = UiDevice.getInstance(instrumentation)
+    private val primaryApp = SplitScreenUtils.getPrimary(instrumentation)
+    private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
+
+    @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+
+    @Before
+    fun setup() {
+        tapl.setEnableRotation(true)
+        tapl.setExpectedRotation(rotation.value)
+
+        primaryApp.launchViaIntent(wmHelper)
+        secondaryApp.launchViaIntent(wmHelper)
+        tapl.goHome()
+        wmHelper
+            .StateSyncBuilder()
+            .withAppTransitionIdle()
+            .withHomeActivityVisible()
+            .waitForAndVerify()
+    }
+
+    @Test
+    open fun enterSplitScreenFromOverview() {
+        SplitScreenUtils.splitFromOverview(tapl, device)
+        SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+    }
+
+    @After
+    fun teardown() {
+        primaryApp.exit(wmHelper)
+        secondaryApp.exit(wmHelper)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SplitScreenUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SplitScreenUtils.kt
new file mode 100644
index 0000000..3831c65
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SplitScreenUtils.kt
@@ -0,0 +1,387 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.scenarios
+
+import android.app.Instrumentation
+import android.graphics.Point
+import android.os.SystemClock
+import android.tools.common.traces.component.ComponentNameMatcher
+import android.tools.common.traces.component.IComponentMatcher
+import android.tools.common.traces.component.IComponentNameMatcher
+import android.tools.device.apphelpers.StandardAppHelper
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import android.tools.device.traces.parsers.toFlickerComponent
+import android.view.InputDevice
+import android.view.MotionEvent
+import android.view.ViewConfiguration
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.BySelector
+import androidx.test.uiautomator.UiDevice
+import androidx.test.uiautomator.UiObject2
+import androidx.test.uiautomator.Until
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.ImeAppHelper
+import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
+import com.android.server.wm.flicker.helpers.NotificationAppHelper
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.server.wm.flicker.testapp.ActivityOptions
+import com.android.wm.shell.flicker.LAUNCHER_UI_PACKAGE_NAME
+import com.android.wm.shell.flicker.SYSTEM_UI_PACKAGE_NAME
+import org.junit.Assert.assertNotNull
+
+object SplitScreenUtils {
+    private const val TIMEOUT_MS = 3_000L
+    private const val DRAG_DURATION_MS = 1_000L
+    private const val NOTIFICATION_SCROLLER = "notification_stack_scroller"
+    private const val DIVIDER_BAR = "docked_divider_handle"
+    private const val OVERVIEW_SNAPSHOT = "snapshot"
+    private const val GESTURE_STEP_MS = 16L
+    private val LONG_PRESS_TIME_MS = ViewConfiguration.getLongPressTimeout() * 2L
+    private val SPLIT_DECOR_MANAGER = ComponentNameMatcher("", "SplitDecorManager#")
+
+    private val notificationScrollerSelector: BySelector
+        get() = By.res(SYSTEM_UI_PACKAGE_NAME, NOTIFICATION_SCROLLER)
+    private val notificationContentSelector: BySelector
+        get() = By.text("Flicker Test Notification")
+    private val dividerBarSelector: BySelector
+        get() = By.res(SYSTEM_UI_PACKAGE_NAME, DIVIDER_BAR)
+    private val overviewSnapshotSelector: BySelector
+        get() = By.res(LAUNCHER_UI_PACKAGE_NAME, OVERVIEW_SNAPSHOT)
+
+    fun getPrimary(instrumentation: Instrumentation): StandardAppHelper =
+        SimpleAppHelper(
+            instrumentation,
+            ActivityOptions.SplitScreen.Primary.LABEL,
+            ActivityOptions.SplitScreen.Primary.COMPONENT.toFlickerComponent()
+        )
+
+    fun getSecondary(instrumentation: Instrumentation): StandardAppHelper =
+        SimpleAppHelper(
+            instrumentation,
+            ActivityOptions.SplitScreen.Secondary.LABEL,
+            ActivityOptions.SplitScreen.Secondary.COMPONENT.toFlickerComponent()
+        )
+
+    fun getNonResizeable(instrumentation: Instrumentation): NonResizeableAppHelper =
+        NonResizeableAppHelper(instrumentation)
+
+    fun getSendNotification(instrumentation: Instrumentation): NotificationAppHelper =
+        NotificationAppHelper(instrumentation)
+
+    fun getIme(instrumentation: Instrumentation): ImeAppHelper = ImeAppHelper(instrumentation)
+
+    fun waitForSplitComplete(
+        wmHelper: WindowManagerStateHelper,
+        primaryApp: IComponentMatcher,
+        secondaryApp: IComponentMatcher,
+    ) {
+        wmHelper
+            .StateSyncBuilder()
+            .withWindowSurfaceAppeared(primaryApp)
+            .withWindowSurfaceAppeared(secondaryApp)
+            .withSplitDividerVisible()
+            .waitForAndVerify()
+    }
+
+    fun enterSplit(
+        wmHelper: WindowManagerStateHelper,
+        tapl: LauncherInstrumentation,
+        device: UiDevice,
+        primaryApp: StandardAppHelper,
+        secondaryApp: StandardAppHelper
+    ) {
+        primaryApp.launchViaIntent(wmHelper)
+        secondaryApp.launchViaIntent(wmHelper)
+        tapl.goHome()
+        wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
+        splitFromOverview(tapl, device)
+        waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+    }
+
+    fun splitFromOverview(tapl: LauncherInstrumentation, device: UiDevice) {
+        // Note: The initial split position in landscape is different between tablet and phone.
+        // In landscape, tablet will let the first app split to right side, and phone will
+        // split to left side.
+        if (tapl.isTablet) {
+            // TAPL's currentTask on tablet is sometimes not what we expected if the overview
+            // contains more than 3 task views. We need to use uiautomator directly to find the
+            // second task to split.
+            tapl.workspace.switchToOverview().overviewActions.clickSplit()
+            val snapshots = device.wait(Until.findObjects(overviewSnapshotSelector), TIMEOUT_MS)
+            if (snapshots == null || snapshots.size < 1) {
+                error("Fail to find a overview snapshot to split.")
+            }
+
+            // Find the second task in the upper right corner in split select mode by sorting
+            // 'left' in descending order and 'top' in ascending order.
+            snapshots.sortWith { t1: UiObject2, t2: UiObject2 ->
+                t2.getVisibleBounds().left - t1.getVisibleBounds().left
+            }
+            snapshots.sortWith { t1: UiObject2, t2: UiObject2 ->
+                t1.getVisibleBounds().top - t2.getVisibleBounds().top
+            }
+            snapshots[0].click()
+        } else {
+            tapl.workspace
+                .switchToOverview()
+                .currentTask
+                .tapMenu()
+                .tapSplitMenuItem()
+                .currentTask
+                .open()
+        }
+        SystemClock.sleep(TIMEOUT_MS)
+    }
+
+    fun enterSplitViaIntent(
+        wmHelper: WindowManagerStateHelper,
+        primaryApp: StandardAppHelper,
+        secondaryApp: StandardAppHelper
+    ) {
+        val stringExtras =
+            mapOf(ActivityOptions.SplitScreen.Primary.EXTRA_LAUNCH_ADJACENT to "true")
+        primaryApp.launchViaIntent(wmHelper, null, null, stringExtras)
+        SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+    }
+
+    fun dragFromNotificationToSplit(
+        instrumentation: Instrumentation,
+        device: UiDevice,
+        wmHelper: WindowManagerStateHelper
+    ) {
+        val displayBounds =
+            wmHelper.currentState.layerState.displays.firstOrNull { !it.isVirtual }?.layerStackSpace
+                ?: error("Display not found")
+
+        // Pull down the notifications
+        device.swipe(
+            displayBounds.centerX(),
+            5,
+            displayBounds.centerX(),
+            displayBounds.bottom,
+            50 /* steps */
+        )
+        SystemClock.sleep(TIMEOUT_MS)
+
+        // Find the target notification
+        val notificationScroller =
+            device.wait(Until.findObject(notificationScrollerSelector), TIMEOUT_MS)
+                ?: error("Unable to find view $notificationScrollerSelector")
+        var notificationContent = notificationScroller.findObject(notificationContentSelector)
+
+        while (notificationContent == null) {
+            device.swipe(
+                displayBounds.centerX(),
+                displayBounds.centerY(),
+                displayBounds.centerX(),
+                displayBounds.centerY() - 150,
+                20 /* steps */
+            )
+            notificationContent = notificationScroller.findObject(notificationContentSelector)
+        }
+
+        // Drag to split
+        val dragStart = notificationContent.visibleCenter
+        val dragMiddle = Point(dragStart.x + 50, dragStart.y)
+        val dragEnd = Point(displayBounds.width / 4, displayBounds.width / 4)
+        val downTime = SystemClock.uptimeMillis()
+
+        touch(instrumentation, MotionEvent.ACTION_DOWN, downTime, downTime, TIMEOUT_MS, dragStart)
+        // It needs a horizontal movement to trigger the drag
+        touchMove(
+            instrumentation,
+            downTime,
+            SystemClock.uptimeMillis(),
+            DRAG_DURATION_MS,
+            dragStart,
+            dragMiddle
+        )
+        touchMove(
+            instrumentation,
+            downTime,
+            SystemClock.uptimeMillis(),
+            DRAG_DURATION_MS,
+            dragMiddle,
+            dragEnd
+        )
+        // Wait for a while to start splitting
+        SystemClock.sleep(TIMEOUT_MS)
+        touch(
+            instrumentation,
+            MotionEvent.ACTION_UP,
+            downTime,
+            SystemClock.uptimeMillis(),
+            GESTURE_STEP_MS,
+            dragEnd
+        )
+        SystemClock.sleep(TIMEOUT_MS)
+    }
+
+    fun touch(
+        instrumentation: Instrumentation,
+        action: Int,
+        downTime: Long,
+        eventTime: Long,
+        duration: Long,
+        point: Point
+    ) {
+        val motionEvent =
+            MotionEvent.obtain(downTime, eventTime, action, point.x.toFloat(), point.y.toFloat(), 0)
+        motionEvent.source = InputDevice.SOURCE_TOUCHSCREEN
+        instrumentation.uiAutomation.injectInputEvent(motionEvent, true)
+        motionEvent.recycle()
+        SystemClock.sleep(duration)
+    }
+
+    fun touchMove(
+        instrumentation: Instrumentation,
+        downTime: Long,
+        eventTime: Long,
+        duration: Long,
+        from: Point,
+        to: Point
+    ) {
+        val steps: Long = duration / GESTURE_STEP_MS
+        var currentTime = eventTime
+        var currentX = from.x.toFloat()
+        var currentY = from.y.toFloat()
+        val stepX = (to.x.toFloat() - from.x.toFloat()) / steps.toFloat()
+        val stepY = (to.y.toFloat() - from.y.toFloat()) / steps.toFloat()
+
+        for (i in 1..steps) {
+            val motionMove =
+                MotionEvent.obtain(
+                    downTime,
+                    currentTime,
+                    MotionEvent.ACTION_MOVE,
+                    currentX,
+                    currentY,
+                    0
+                )
+            motionMove.source = InputDevice.SOURCE_TOUCHSCREEN
+            instrumentation.uiAutomation.injectInputEvent(motionMove, true)
+            motionMove.recycle()
+
+            currentTime += GESTURE_STEP_MS
+            if (i == steps - 1) {
+                currentX = to.x.toFloat()
+                currentY = to.y.toFloat()
+            } else {
+                currentX += stepX
+                currentY += stepY
+            }
+            SystemClock.sleep(GESTURE_STEP_MS)
+        }
+    }
+
+    fun createShortcutOnHotseatIfNotExist(tapl: LauncherInstrumentation, appName: String) {
+        tapl.workspace.deleteAppIcon(tapl.workspace.getHotseatAppIcon(0))
+        val allApps = tapl.workspace.switchToAllApps()
+        allApps.freeze()
+        try {
+            allApps.getAppIcon(appName).dragToHotseat(0)
+        } finally {
+            allApps.unfreeze()
+        }
+    }
+
+    fun dragDividerToResizeAndWait(device: UiDevice, wmHelper: WindowManagerStateHelper) {
+        val displayBounds =
+            wmHelper.currentState.layerState.displays.firstOrNull { !it.isVirtual }?.layerStackSpace
+                ?: error("Display not found")
+        val dividerBar = device.wait(Until.findObject(dividerBarSelector), TIMEOUT_MS)
+        dividerBar.drag(Point(displayBounds.width * 1 / 3, displayBounds.height * 2 / 3), 200)
+
+        wmHelper
+            .StateSyncBuilder()
+            .withWindowSurfaceDisappeared(SPLIT_DECOR_MANAGER)
+            .waitForAndVerify()
+    }
+
+    fun dragDividerToDismissSplit(
+        device: UiDevice,
+        wmHelper: WindowManagerStateHelper,
+        dragToRight: Boolean,
+        dragToBottom: Boolean
+    ) {
+        val displayBounds =
+            wmHelper.currentState.layerState.displays.firstOrNull { !it.isVirtual }?.layerStackSpace
+                ?: error("Display not found")
+        val dividerBar = device.wait(Until.findObject(dividerBarSelector), TIMEOUT_MS)
+        dividerBar.drag(
+            Point(
+                if (dragToRight) {
+                    displayBounds.width * 4 / 5
+                } else {
+                    displayBounds.width * 1 / 5
+                },
+                if (dragToBottom) {
+                    displayBounds.height * 4 / 5
+                } else {
+                    displayBounds.height * 1 / 5
+                }
+            )
+        )
+    }
+
+    fun doubleTapDividerToSwitch(device: UiDevice) {
+        val dividerBar = device.wait(Until.findObject(dividerBarSelector), TIMEOUT_MS)
+        val interval =
+            (ViewConfiguration.getDoubleTapTimeout() + ViewConfiguration.getDoubleTapMinTime()) / 2
+        dividerBar.click()
+        SystemClock.sleep(interval.toLong())
+        dividerBar.click()
+    }
+
+    fun copyContentInSplit(
+        instrumentation: Instrumentation,
+        device: UiDevice,
+        sourceApp: IComponentNameMatcher,
+        destinationApp: IComponentNameMatcher,
+    ) {
+        // Copy text from sourceApp
+        val textView =
+            device.wait(
+                Until.findObject(By.res(sourceApp.packageName, "SplitScreenTest")),
+                TIMEOUT_MS
+            )
+        assertNotNull("Unable to find the TextView", textView)
+        textView.click(LONG_PRESS_TIME_MS)
+
+        val copyBtn = device.wait(Until.findObject(By.text("Copy")), TIMEOUT_MS)
+        assertNotNull("Unable to find the copy button", copyBtn)
+        copyBtn.click()
+
+        // Paste text to destinationApp
+        val editText =
+            device.wait(
+                Until.findObject(By.res(destinationApp.packageName, "plain_text_input")),
+                TIMEOUT_MS
+            )
+        assertNotNull("Unable to find the EditText", editText)
+        editText.click(LONG_PRESS_TIME_MS)
+
+        val pasteBtn = device.wait(Until.findObject(By.text("Paste")), TIMEOUT_MS)
+        assertNotNull("Unable to find the paste button", pasteBtn)
+        pasteBtn.click()
+
+        // Verify text
+        if (!textView.text.contentEquals(editText.text)) {
+            error("Fail to copy content in split")
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt
new file mode 100644
index 0000000..805d987
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.flicker.service.splitscreen.scenarios
+
+import android.app.Instrumentation
+import android.graphics.Point
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.helpers.WindowUtils
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.wm.shell.flicker.service.Utils
+import org.junit.After
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Base Test Class")
+abstract class SwitchAppByDoubleTapDivider
+@JvmOverloads
+constructor(val rotation: Rotation = Rotation.ROTATION_0) {
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val tapl = LauncherInstrumentation()
+    private val wmHelper = WindowManagerStateHelper(instrumentation)
+    private val device = UiDevice.getInstance(instrumentation)
+    private val primaryApp = SplitScreenUtils.getPrimary(instrumentation)
+    private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
+
+    @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+
+    @Before
+    fun setup() {
+        tapl.setEnableRotation(true)
+        tapl.setExpectedRotation(rotation.value)
+        tapl.workspace.switchToOverview().dismissAllTasks()
+
+        SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
+    }
+
+    @Test
+    open fun switchAppByDoubleTapDivider() {
+        SplitScreenUtils.doubleTapDividerToSwitch(device)
+        wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify()
+
+        waitForLayersToSwitch(wmHelper)
+        waitForWindowsToSwitch(wmHelper)
+    }
+
+    @After
+    fun teardown() {
+        primaryApp.exit(wmHelper)
+        secondaryApp.exit(wmHelper)
+    }
+
+    private fun waitForWindowsToSwitch(wmHelper: WindowManagerStateHelper) {
+        wmHelper
+            .StateSyncBuilder()
+            .add("appWindowsSwitched") {
+                val primaryAppWindow =
+                    it.wmState.visibleWindows.firstOrNull { window ->
+                        primaryApp.windowMatchesAnyOf(window)
+                    }
+                        ?: return@add false
+                val secondaryAppWindow =
+                    it.wmState.visibleWindows.firstOrNull { window ->
+                        secondaryApp.windowMatchesAnyOf(window)
+                    }
+                        ?: return@add false
+
+                if (isLandscape(rotation)) {
+                    return@add if (isTablet()) {
+                        secondaryAppWindow.frame.right <= primaryAppWindow.frame.left
+                    } else {
+                        primaryAppWindow.frame.right <= secondaryAppWindow.frame.left
+                    }
+                } else {
+                    return@add if (isTablet()) {
+                        primaryAppWindow.frame.bottom <= secondaryAppWindow.frame.top
+                    } else {
+                        primaryAppWindow.frame.bottom <= secondaryAppWindow.frame.top
+                    }
+                }
+            }
+            .waitForAndVerify()
+    }
+
+    private fun waitForLayersToSwitch(wmHelper: WindowManagerStateHelper) {
+        wmHelper
+            .StateSyncBuilder()
+            .add("appLayersSwitched") {
+                val primaryAppLayer =
+                    it.layerState.visibleLayers.firstOrNull { window ->
+                        primaryApp.layerMatchesAnyOf(window)
+                    }
+                        ?: return@add false
+                val secondaryAppLayer =
+                    it.layerState.visibleLayers.firstOrNull { window ->
+                        secondaryApp.layerMatchesAnyOf(window)
+                    }
+                        ?: return@add false
+
+                val primaryVisibleRegion = primaryAppLayer.visibleRegion?.bounds ?: return@add false
+                val secondaryVisibleRegion =
+                    secondaryAppLayer.visibleRegion?.bounds ?: return@add false
+
+                if (isLandscape(rotation)) {
+                    return@add if (isTablet()) {
+                        secondaryVisibleRegion.right <= primaryVisibleRegion.left
+                    } else {
+                        primaryVisibleRegion.right <= secondaryVisibleRegion.left
+                    }
+                } else {
+                    return@add if (isTablet()) {
+                        primaryVisibleRegion.bottom <= secondaryVisibleRegion.top
+                    } else {
+                        primaryVisibleRegion.bottom <= secondaryVisibleRegion.top
+                    }
+                }
+            }
+            .waitForAndVerify()
+    }
+
+    private fun isLandscape(rotation: Rotation): Boolean {
+        val displayBounds = WindowUtils.getDisplayBounds(rotation)
+        return displayBounds.width > displayBounds.height
+    }
+
+    private fun isTablet(): Boolean {
+        val sizeDp: Point = device.displaySizeDp
+        val LARGE_SCREEN_DP_THRESHOLD = 600
+        return sizeDp.x >= LARGE_SCREEN_DP_THRESHOLD && sizeDp.y >= LARGE_SCREEN_DP_THRESHOLD
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromAnotherApp.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromAnotherApp.kt
new file mode 100644
index 0000000..4229ebb
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromAnotherApp.kt
@@ -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.wm.shell.flicker.service.splitscreen.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.wm.shell.flicker.service.Utils
+import org.junit.After
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Base Test Class")
+abstract class SwitchBackToSplitFromAnotherApp
+@JvmOverloads
+constructor(val rotation: Rotation = Rotation.ROTATION_0) {
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val tapl = LauncherInstrumentation()
+    private val wmHelper = WindowManagerStateHelper(instrumentation)
+    private val device = UiDevice.getInstance(instrumentation)
+    private val primaryApp = SplitScreenUtils.getPrimary(instrumentation)
+    private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
+    private val thirdApp = SplitScreenUtils.getNonResizeable(instrumentation)
+
+    @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+
+    @Before
+    fun setup() {
+        tapl.setEnableRotation(true)
+        tapl.setExpectedRotation(rotation.value)
+
+        SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
+
+        thirdApp.launchViaIntent(wmHelper)
+        wmHelper.StateSyncBuilder().withWindowSurfaceAppeared(thirdApp).waitForAndVerify()
+    }
+
+    @Test
+    open fun switchBackToSplitFromAnotherApp() {
+        tapl.launchedAppState.quickSwitchToPreviousApp()
+        SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+    }
+
+    @After
+    fun teardown() {
+        primaryApp.exit(wmHelper)
+        secondaryApp.exit(wmHelper)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromHome.kt
new file mode 100644
index 0000000..f2d56b9
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromHome.kt
@@ -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.wm.shell.flicker.service.splitscreen.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.wm.shell.flicker.service.Utils
+import org.junit.After
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Base Test Class")
+abstract class SwitchBackToSplitFromHome
+@JvmOverloads
+constructor(val rotation: Rotation = Rotation.ROTATION_0) {
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val tapl = LauncherInstrumentation()
+    private val wmHelper = WindowManagerStateHelper(instrumentation)
+    private val device = UiDevice.getInstance(instrumentation)
+    private val primaryApp = SplitScreenUtils.getPrimary(instrumentation)
+    private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
+
+    @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+
+    @Before
+    fun setup() {
+        tapl.setEnableRotation(true)
+        tapl.setExpectedRotation(rotation.value)
+
+        SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
+
+        tapl.goHome()
+        wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
+    }
+
+    @Test
+    open fun switchBackToSplitFromHome() {
+        tapl.workspace.quickSwitchToPreviousApp()
+        SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+    }
+
+    @After
+    fun teardown() {
+        primaryApp.exit(wmHelper)
+        secondaryApp.exit(wmHelper)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt
new file mode 100644
index 0000000..9f9d4bb
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt
@@ -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.wm.shell.flicker.service.splitscreen.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.wm.shell.flicker.service.Utils
+import org.junit.After
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Base Test Class")
+abstract class SwitchBackToSplitFromRecent
+@JvmOverloads
+constructor(val rotation: Rotation = Rotation.ROTATION_0) {
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val tapl = LauncherInstrumentation()
+    private val wmHelper = WindowManagerStateHelper(instrumentation)
+    private val device = UiDevice.getInstance(instrumentation)
+    private val primaryApp = SplitScreenUtils.getPrimary(instrumentation)
+    private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
+
+    @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+
+    @Before
+    fun setup() {
+        tapl.setEnableRotation(true)
+        tapl.setExpectedRotation(rotation.value)
+        tapl.workspace.switchToOverview().dismissAllTasks()
+
+        SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
+
+        tapl.goHome()
+        wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
+    }
+
+    @Test
+    open fun switchBackToSplitFromRecent() {
+        tapl.workspace.switchToOverview().currentTask.open()
+        SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+    }
+
+    @After
+    fun teardown() {
+        primaryApp.exit(wmHelper)
+        secondaryApp.exit(wmHelper)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBetweenSplitPairs.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBetweenSplitPairs.kt
new file mode 100644
index 0000000..e2c6ca6
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBetweenSplitPairs.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.wm.shell.flicker.service.splitscreen.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.wm.shell.flicker.service.Utils
+import org.junit.After
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Base Test Class")
+abstract class SwitchBetweenSplitPairs
+@JvmOverloads
+constructor(val rotation: Rotation = Rotation.ROTATION_0) {
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val tapl = LauncherInstrumentation()
+    private val wmHelper = WindowManagerStateHelper(instrumentation)
+    private val device = UiDevice.getInstance(instrumentation)
+    private val primaryApp = SplitScreenUtils.getPrimary(instrumentation)
+    private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
+    private val thirdApp = SplitScreenUtils.getIme(instrumentation)
+    private val fourthApp = SplitScreenUtils.getSendNotification(instrumentation)
+
+    @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+
+    @Before
+    fun setup() {
+        tapl.setEnableRotation(true)
+        tapl.setExpectedRotation(rotation.value)
+
+        SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
+        SplitScreenUtils.enterSplit(wmHelper, tapl, device, thirdApp, fourthApp)
+        SplitScreenUtils.waitForSplitComplete(wmHelper, thirdApp, fourthApp)
+    }
+
+    @Test
+    open fun switchBetweenSplitPairs() {
+        tapl.launchedAppState.quickSwitchToPreviousApp()
+        SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+    }
+
+    @After
+    fun teardown() {
+        primaryApp.exit(wmHelper)
+        secondaryApp.exit(wmHelper)
+        thirdApp.exit(wmHelper)
+        fourthApp.exit(wmHelper)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/UnlockKeyguardToSplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/UnlockKeyguardToSplitScreen.kt
new file mode 100644
index 0000000..df98d8f
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/UnlockKeyguardToSplitScreen.kt
@@ -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.wm.shell.flicker.service.splitscreen.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.wm.shell.flicker.service.Utils
+import org.junit.After
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Base Test Class")
+abstract class UnlockKeyguardToSplitScreen {
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val tapl = LauncherInstrumentation()
+    private val wmHelper = WindowManagerStateHelper(instrumentation)
+    private val device = UiDevice.getInstance(instrumentation)
+    private val primaryApp = SplitScreenUtils.getPrimary(instrumentation)
+    private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
+
+    @Rule
+    @JvmField
+    val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, Rotation.ROTATION_0)
+
+    @Before
+    fun setup() {
+        tapl.setEnableRotation(true)
+        tapl.setExpectedRotation(Rotation.ROTATION_0.value)
+
+        SplitScreenUtils.enterSplitViaIntent(wmHelper, primaryApp, secondaryApp)
+    }
+
+    @Test
+    open fun unlockKeyguardToSplitScreen() {
+        device.sleep()
+        wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify()
+        device.wakeUp()
+        device.pressMenu()
+        wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify()
+    }
+
+    @After
+    fun teardown() {
+        primaryApp.exit(wmHelper)
+        secondaryApp.exit(wmHelper)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
index d1f0980..1d4c4d2 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
@@ -17,24 +17,19 @@
 package com.android.wm.shell.flicker.splitscreen
 
 import android.platform.test.annotations.FlakyTest
-import android.platform.test.annotations.PlatinumTest
 import android.platform.test.annotations.Presubmit
 import android.tools.common.traces.component.ComponentNameMatcher
 import android.tools.common.traces.component.EdgeExtensionComponentMatcher
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
 import com.android.wm.shell.flicker.ICommonAssertions
 import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
-import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
-import com.android.wm.shell.flicker.appWindowIsVisibleAtStart
 import com.android.wm.shell.flicker.appWindowKeepVisible
 import com.android.wm.shell.flicker.layerKeepVisible
 import com.android.wm.shell.flicker.splitAppLayerBoundsKeepVisible
-import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtEnd
-import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtStart
 import com.android.wm.shell.flicker.splitscreen.benchmark.CopyContentInSplitBenchmark
 import org.junit.FixMethodOrder
 import org.junit.Test
@@ -45,13 +40,13 @@
 /**
  * Test copy content from the left to the right side of the split-screen.
  *
- * To run this test: `atest WMShellFlickerTests:CopyContentInSplit`
+ * To run this test: `atest WMShellFlickerTestsSplitScreen:CopyContentInSplit`
  */
 @RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class CopyContentInSplit(override val flicker: FlickerTest) :
+class CopyContentInSplit(override val flicker: LegacyFlickerTest) :
     CopyContentInSplitBenchmark(flicker), ICommonAssertions {
     override val transition: FlickerBuilder.() -> Unit
         get() = {
@@ -60,21 +55,6 @@
             thisTransition(this)
         }
 
-    @PlatinumTest(focusArea = "sysui")
-    @Presubmit
-    @Test
-    override fun cujCompleted() {
-        flicker.appWindowIsVisibleAtStart(primaryApp)
-        flicker.appWindowIsVisibleAtStart(textEditApp)
-        flicker.splitScreenDividerIsVisibleAtStart()
-
-        flicker.appWindowIsVisibleAtEnd(primaryApp)
-        flicker.appWindowIsVisibleAtEnd(textEditApp)
-        flicker.splitScreenDividerIsVisibleAtEnd()
-
-        // The validation of copied text is already done in SplitScreenUtils.copyContentInSplit()
-    }
-
     @Presubmit
     @Test
     fun splitScreenDividerKeepVisible() = flicker.layerKeepVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
@@ -136,8 +116,6 @@
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests()
-        }
+        fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt
index 4505b99..0b8f109 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt
@@ -21,7 +21,7 @@
 import android.platform.test.annotations.Presubmit
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.helpers.WindowUtils
 import androidx.test.filters.RequiresDevice
 import com.android.wm.shell.flicker.ICommonAssertions
@@ -41,13 +41,13 @@
 /**
  * Test dismiss split screen by dragging the divider bar.
  *
- * To run this test: `atest WMShellFlickerTests:DismissSplitScreenByDivider`
+ * To run this test: `atest WMShellFlickerTestsSplitScreen:DismissSplitScreenByDivider`
  */
 @RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class DismissSplitScreenByDivider(override val flicker: FlickerTest) :
+class DismissSplitScreenByDivider(override val flicker: LegacyFlickerTest) :
     DismissSplitScreenByDividerBenchmark(flicker), ICommonAssertions {
 
     override val transition: FlickerBuilder.() -> Unit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
index e05b221..38d4b40 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
@@ -20,8 +20,8 @@
 import android.platform.test.annotations.Presubmit
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
 import com.android.wm.shell.flicker.ICommonAssertions
 import com.android.wm.shell.flicker.appWindowBecomesInvisible
@@ -38,13 +38,13 @@
 /**
  * Test dismiss split screen by go home.
  *
- * To run this test: `atest WMShellFlickerTests:DismissSplitScreenByGoHome`
+ * To run this test: `atest WMShellFlickerTestsSplitScreen:DismissSplitScreenByGoHome`
  */
 @RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class DismissSplitScreenByGoHome(override val flicker: FlickerTest) :
+class DismissSplitScreenByGoHome(override val flicker: LegacyFlickerTest) :
     DismissSplitScreenByGoHomeBenchmark(flicker), ICommonAssertions {
     override val transition: FlickerBuilder.() -> Unit
         get() = {
@@ -154,8 +154,6 @@
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests()
-        }
+        fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
index 63c5d14..0d967eb 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
@@ -17,22 +17,17 @@
 package com.android.wm.shell.flicker.splitscreen
 
 import android.platform.test.annotations.FlakyTest
-import android.platform.test.annotations.PlatinumTest
 import android.platform.test.annotations.Presubmit
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
 import com.android.wm.shell.flicker.ICommonAssertions
 import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
-import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
-import com.android.wm.shell.flicker.appWindowIsVisibleAtStart
 import com.android.wm.shell.flicker.appWindowKeepVisible
 import com.android.wm.shell.flicker.layerKeepVisible
 import com.android.wm.shell.flicker.splitAppLayerBoundsChanges
-import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtEnd
-import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtStart
 import com.android.wm.shell.flicker.splitscreen.benchmark.DragDividerToResizeBenchmark
 import org.junit.FixMethodOrder
 import org.junit.Test
@@ -43,13 +38,13 @@
 /**
  * Test resize split by dragging the divider bar.
  *
- * To run this test: `atest WMShellFlickerTests:DragDividerToResize`
+ * To run this test: `atest WMShellFlickerTestsSplitScreen:DragDividerToResize`
  */
 @RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class DragDividerToResize(override val flicker: FlickerTest) :
+class DragDividerToResize(override val flicker: LegacyFlickerTest) :
     DragDividerToResizeBenchmark(flicker), ICommonAssertions {
     override val transition: FlickerBuilder.() -> Unit
         get() = {
@@ -58,19 +53,6 @@
             thisTransition(this)
         }
 
-    @PlatinumTest(focusArea = "sysui")
-    @Presubmit
-    @Test
-    override fun cujCompleted() {
-        flicker.appWindowIsVisibleAtStart(primaryApp)
-        flicker.appWindowIsVisibleAtStart(secondaryApp)
-        flicker.splitScreenDividerIsVisibleAtStart()
-
-        flicker.appWindowIsVisibleAtEnd(primaryApp)
-        flicker.appWindowIsVisibleAtEnd(secondaryApp)
-        flicker.splitScreenDividerIsVisibleAtEnd()
-    }
-
     @Presubmit
     @Test
     fun splitScreenDividerKeepVisible() = flicker.layerKeepVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
@@ -127,8 +109,6 @@
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests()
-        }
+        fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
index e558686..05c0480 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
@@ -22,8 +22,8 @@
 import android.tools.common.NavBar
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
 import com.android.wm.shell.flicker.ICommonAssertions
 import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
@@ -45,13 +45,13 @@
  * Test enter split screen by dragging app icon from all apps. This test is only for large screen
  * devices.
  *
- * To run this test: `atest WMShellFlickerTests:EnterSplitScreenByDragFromAllApps`
+ * To run this test: `atest WMShellFlickerTestsSplitScreen:EnterSplitScreenByDragFromAllApps`
  */
 @RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class EnterSplitScreenByDragFromAllApps(override val flicker: FlickerTest) :
+class EnterSplitScreenByDragFromAllApps(override val flicker: LegacyFlickerTest) :
     EnterSplitScreenByDragFromAllAppsBenchmark(flicker), ICommonAssertions {
     override val transition: FlickerBuilder.() -> Unit
         get() = {
@@ -160,11 +160,10 @@
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
                 // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
                 supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
             )
-        }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt
index ab8ecc5..3a75fa6 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt
@@ -22,8 +22,8 @@
 import android.tools.common.NavBar
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
 import com.android.wm.shell.flicker.ICommonAssertions
 import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
@@ -44,13 +44,13 @@
  * Test enter split screen by dragging app icon from notification. This test is only for large
  * screen devices.
  *
- * To run this test: `atest WMShellFlickerTests:EnterSplitScreenByDragFromNotification`
+ * To run this test: `atest WMShellFlickerTestsSplitScreen:EnterSplitScreenByDragFromNotification`
  */
 @RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class EnterSplitScreenByDragFromNotification(override val flicker: FlickerTest) :
+class EnterSplitScreenByDragFromNotification(override val flicker: LegacyFlickerTest) :
     EnterSplitScreenByDragFromNotificationBenchmark(flicker), ICommonAssertions {
     /** {@inheritDoc} */
     override val transition: FlickerBuilder.() -> Unit
@@ -162,11 +162,10 @@
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
                 // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
                 supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
             )
-        }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt
index 516ca97..6d73f92 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt
@@ -21,8 +21,8 @@
 import android.tools.common.NavBar
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
 import com.android.wm.shell.flicker.ICommonAssertions
 import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
@@ -41,13 +41,13 @@
 /**
  * Test enter split screen by dragging a shortcut. This test is only for large screen devices.
  *
- * To run this test: `atest WMShellFlickerTests:EnterSplitScreenByDragFromShortcut`
+ * To run this test: `atest WMShellFlickerTestsSplitScreen:EnterSplitScreenByDragFromShortcut`
  */
 @RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class EnterSplitScreenByDragFromShortcut(override val flicker: FlickerTest) :
+class EnterSplitScreenByDragFromShortcut(override val flicker: LegacyFlickerTest) :
     EnterSplitScreenByDragFromShortcutBenchmark(flicker), ICommonAssertions {
 
     override val transition: FlickerBuilder.() -> Unit
@@ -105,11 +105,10 @@
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
                 // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
                 supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
             )
-        }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
index 4af7e24..15cae69 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
@@ -22,8 +22,8 @@
 import android.tools.common.NavBar
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
 import com.android.wm.shell.flicker.ICommonAssertions
 import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
@@ -45,13 +45,13 @@
  * Test enter split screen by dragging app icon from taskbar. This test is only for large screen
  * devices.
  *
- * To run this test: `atest WMShellFlickerTests:EnterSplitScreenByDragFromTaskbar`
+ * To run this test: `atest WMShellFlickerTestsSplitScreen:EnterSplitScreenByDragFromTaskbar`
  */
 @RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class EnterSplitScreenByDragFromTaskbar(override val flicker: FlickerTest) :
+class EnterSplitScreenByDragFromTaskbar(override val flicker: LegacyFlickerTest) :
     EnterSplitScreenByDragFromTaskbarBenchmark(flicker), ICommonAssertions {
     /** {@inheritDoc} */
     override val transition: FlickerBuilder.() -> Unit
@@ -163,10 +163,9 @@
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
                 supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
             )
-        }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt
index faad9e8..90399fc 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt
@@ -20,8 +20,8 @@
 import android.platform.test.annotations.Presubmit
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
 import com.android.wm.shell.flicker.ICommonAssertions
 import com.android.wm.shell.flicker.appWindowBecomesVisible
@@ -40,13 +40,13 @@
 /**
  * Test enter split screen from Overview.
  *
- * To run this test: `atest WMShellFlickerTests:EnterSplitScreenFromOverview`
+ * To run this test: `atest WMShellFlickerTestsSplitScreen:EnterSplitScreenFromOverview`
  */
 @RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class EnterSplitScreenFromOverview(override val flicker: FlickerTest) :
+class EnterSplitScreenFromOverview(override val flicker: LegacyFlickerTest) :
     EnterSplitScreenFromOverviewBenchmark(flicker), ICommonAssertions {
     override val transition: FlickerBuilder.() -> Unit
         get() = {
@@ -106,8 +106,6 @@
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests()
-        }
+        fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/OWNERS b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/OWNERS
new file mode 100644
index 0000000..3ab6a1e
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/OWNERS
@@ -0,0 +1,2 @@
+# Android > Android OS & Apps > Framework (Java + Native) > Window Manager > WM Shell > Split Screen
+# Bug component: 928697
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt
index 195b73a..3064bd7 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt
@@ -18,11 +18,12 @@
 
 import android.content.Context
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTest
 import com.android.server.wm.flicker.helpers.setRotation
 import com.android.wm.shell.flicker.BaseBenchmarkTest
+import com.android.wm.shell.flicker.SplitScreenUtils
 
-abstract class SplitScreenBase(flicker: FlickerTest) : BaseBenchmarkTest(flicker) {
+abstract class SplitScreenBase(flicker: LegacyFlickerTest) : BaseBenchmarkTest(flicker) {
     protected val context: Context = instrumentation.context
     protected val primaryApp = SplitScreenUtils.getPrimary(instrumentation)
     protected val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
@@ -42,11 +43,4 @@
             secondaryApp.exit(wmHelper)
         }
     }
-
-    protected open val withoutTracing: FlickerBuilder.() -> Unit = {
-        withoutLayerTracing()
-        withoutWindowManagerTracing()
-        withoutTransitionTracing()
-        withoutTransactionsTracing()
-    }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt
deleted file mode 100644
index 1063dfd..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt
+++ /dev/null
@@ -1,376 +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.wm.shell.flicker.splitscreen
-
-import android.app.Instrumentation
-import android.graphics.Point
-import android.os.SystemClock
-import android.tools.common.traces.component.ComponentNameMatcher
-import android.tools.common.traces.component.IComponentMatcher
-import android.tools.common.traces.component.IComponentNameMatcher
-import android.tools.device.apphelpers.StandardAppHelper
-import android.tools.device.traces.parsers.WindowManagerStateHelper
-import android.tools.device.traces.parsers.toFlickerComponent
-import android.view.InputDevice
-import android.view.MotionEvent
-import android.view.ViewConfiguration
-import androidx.test.uiautomator.By
-import androidx.test.uiautomator.BySelector
-import androidx.test.uiautomator.UiDevice
-import androidx.test.uiautomator.UiObject2
-import androidx.test.uiautomator.Until
-import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.server.wm.flicker.helpers.ImeAppHelper
-import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
-import com.android.server.wm.flicker.helpers.NotificationAppHelper
-import com.android.server.wm.flicker.helpers.SimpleAppHelper
-import com.android.server.wm.flicker.testapp.ActivityOptions
-import com.android.wm.shell.flicker.LAUNCHER_UI_PACKAGE_NAME
-import com.android.wm.shell.flicker.SYSTEM_UI_PACKAGE_NAME
-import org.junit.Assert.assertNotNull
-
-internal object SplitScreenUtils {
-    private const val TIMEOUT_MS = 3_000L
-    private const val DRAG_DURATION_MS = 1_000L
-    private const val NOTIFICATION_SCROLLER = "notification_stack_scroller"
-    private const val DIVIDER_BAR = "docked_divider_handle"
-    private const val OVERVIEW_SNAPSHOT = "snapshot"
-    private const val GESTURE_STEP_MS = 16L
-    private val LONG_PRESS_TIME_MS = ViewConfiguration.getLongPressTimeout() * 2L
-    private val SPLIT_DECOR_MANAGER = ComponentNameMatcher("", "SplitDecorManager#")
-
-    private val notificationScrollerSelector: BySelector
-        get() = By.res(SYSTEM_UI_PACKAGE_NAME, NOTIFICATION_SCROLLER)
-    private val notificationContentSelector: BySelector
-        get() = By.text("Flicker Test Notification")
-    private val dividerBarSelector: BySelector
-        get() = By.res(SYSTEM_UI_PACKAGE_NAME, DIVIDER_BAR)
-    private val overviewSnapshotSelector: BySelector
-        get() = By.res(LAUNCHER_UI_PACKAGE_NAME, OVERVIEW_SNAPSHOT)
-
-    fun getPrimary(instrumentation: Instrumentation): StandardAppHelper =
-        SimpleAppHelper(
-            instrumentation,
-            ActivityOptions.SplitScreen.Primary.LABEL,
-            ActivityOptions.SplitScreen.Primary.COMPONENT.toFlickerComponent()
-        )
-
-    fun getSecondary(instrumentation: Instrumentation): StandardAppHelper =
-        SimpleAppHelper(
-            instrumentation,
-            ActivityOptions.SplitScreen.Secondary.LABEL,
-            ActivityOptions.SplitScreen.Secondary.COMPONENT.toFlickerComponent()
-        )
-
-    fun getNonResizeable(instrumentation: Instrumentation): NonResizeableAppHelper =
-        NonResizeableAppHelper(instrumentation)
-
-    fun getSendNotification(instrumentation: Instrumentation): NotificationAppHelper =
-        NotificationAppHelper(instrumentation)
-
-    fun getIme(instrumentation: Instrumentation): ImeAppHelper = ImeAppHelper(instrumentation)
-
-    fun waitForSplitComplete(
-        wmHelper: WindowManagerStateHelper,
-        primaryApp: IComponentMatcher,
-        secondaryApp: IComponentMatcher,
-    ) {
-        wmHelper
-            .StateSyncBuilder()
-            .withWindowSurfaceAppeared(primaryApp)
-            .withWindowSurfaceAppeared(secondaryApp)
-            .withSplitDividerVisible()
-            .waitForAndVerify()
-    }
-
-    fun enterSplit(
-        wmHelper: WindowManagerStateHelper,
-        tapl: LauncherInstrumentation,
-        device: UiDevice,
-        primaryApp: StandardAppHelper,
-        secondaryApp: StandardAppHelper
-    ) {
-        primaryApp.launchViaIntent(wmHelper)
-        secondaryApp.launchViaIntent(wmHelper)
-        tapl.goHome()
-        wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
-        splitFromOverview(tapl, device)
-        waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
-    }
-
-    fun splitFromOverview(tapl: LauncherInstrumentation, device: UiDevice) {
-        // Note: The initial split position in landscape is different between tablet and phone.
-        // In landscape, tablet will let the first app split to right side, and phone will
-        // split to left side.
-        if (tapl.isTablet) {
-            // TAPL's currentTask on tablet is sometimes not what we expected if the overview
-            // contains more than 3 task views. We need to use uiautomator directly to find the
-            // second task to split.
-            tapl.workspace.switchToOverview().overviewActions.clickSplit()
-            val snapshots = device.wait(Until.findObjects(overviewSnapshotSelector), TIMEOUT_MS)
-            if (snapshots == null || snapshots.size < 1) {
-                error("Fail to find a overview snapshot to split.")
-            }
-
-            // Find the second task in the upper right corner in split select mode by sorting
-            // 'left' in descending order and 'top' in ascending order.
-            snapshots.sortWith { t1: UiObject2, t2: UiObject2 ->
-                t2.getVisibleBounds().left - t1.getVisibleBounds().left
-            }
-            snapshots.sortWith { t1: UiObject2, t2: UiObject2 ->
-                t1.getVisibleBounds().top - t2.getVisibleBounds().top
-            }
-            snapshots[0].click()
-        } else {
-            tapl.workspace
-                .switchToOverview()
-                .currentTask
-                .tapMenu()
-                .tapSplitMenuItem()
-                .currentTask
-                .open()
-        }
-        SystemClock.sleep(TIMEOUT_MS)
-    }
-
-    fun dragFromNotificationToSplit(
-        instrumentation: Instrumentation,
-        device: UiDevice,
-        wmHelper: WindowManagerStateHelper
-    ) {
-        val displayBounds =
-            wmHelper.currentState.layerState.displays.firstOrNull { !it.isVirtual }?.layerStackSpace
-                ?: error("Display not found")
-
-        // Pull down the notifications
-        device.swipe(
-            displayBounds.centerX(),
-            5,
-            displayBounds.centerX(),
-            displayBounds.bottom,
-            50 /* steps */
-        )
-        SystemClock.sleep(TIMEOUT_MS)
-
-        // Find the target notification
-        val notificationScroller =
-            device.wait(Until.findObject(notificationScrollerSelector), TIMEOUT_MS)
-                ?: error("Unable to find view $notificationScrollerSelector")
-        var notificationContent = notificationScroller.findObject(notificationContentSelector)
-
-        while (notificationContent == null) {
-            device.swipe(
-                displayBounds.centerX(),
-                displayBounds.centerY(),
-                displayBounds.centerX(),
-                displayBounds.centerY() - 150,
-                20 /* steps */
-            )
-            notificationContent = notificationScroller.findObject(notificationContentSelector)
-        }
-
-        // Drag to split
-        val dragStart = notificationContent.visibleCenter
-        val dragMiddle = Point(dragStart.x + 50, dragStart.y)
-        val dragEnd = Point(displayBounds.width / 4, displayBounds.width / 4)
-        val downTime = SystemClock.uptimeMillis()
-
-        touch(instrumentation, MotionEvent.ACTION_DOWN, downTime, downTime, TIMEOUT_MS, dragStart)
-        // It needs a horizontal movement to trigger the drag
-        touchMove(
-            instrumentation,
-            downTime,
-            SystemClock.uptimeMillis(),
-            DRAG_DURATION_MS,
-            dragStart,
-            dragMiddle
-        )
-        touchMove(
-            instrumentation,
-            downTime,
-            SystemClock.uptimeMillis(),
-            DRAG_DURATION_MS,
-            dragMiddle,
-            dragEnd
-        )
-        // Wait for a while to start splitting
-        SystemClock.sleep(TIMEOUT_MS)
-        touch(
-            instrumentation,
-            MotionEvent.ACTION_UP,
-            downTime,
-            SystemClock.uptimeMillis(),
-            GESTURE_STEP_MS,
-            dragEnd
-        )
-        SystemClock.sleep(TIMEOUT_MS)
-    }
-
-    fun touch(
-        instrumentation: Instrumentation,
-        action: Int,
-        downTime: Long,
-        eventTime: Long,
-        duration: Long,
-        point: Point
-    ) {
-        val motionEvent =
-            MotionEvent.obtain(downTime, eventTime, action, point.x.toFloat(), point.y.toFloat(), 0)
-        motionEvent.source = InputDevice.SOURCE_TOUCHSCREEN
-        instrumentation.uiAutomation.injectInputEvent(motionEvent, true)
-        motionEvent.recycle()
-        SystemClock.sleep(duration)
-    }
-
-    fun touchMove(
-        instrumentation: Instrumentation,
-        downTime: Long,
-        eventTime: Long,
-        duration: Long,
-        from: Point,
-        to: Point
-    ) {
-        val steps: Long = duration / GESTURE_STEP_MS
-        var currentTime = eventTime
-        var currentX = from.x.toFloat()
-        var currentY = from.y.toFloat()
-        val stepX = (to.x.toFloat() - from.x.toFloat()) / steps.toFloat()
-        val stepY = (to.y.toFloat() - from.y.toFloat()) / steps.toFloat()
-
-        for (i in 1..steps) {
-            val motionMove =
-                MotionEvent.obtain(
-                    downTime,
-                    currentTime,
-                    MotionEvent.ACTION_MOVE,
-                    currentX,
-                    currentY,
-                    0
-                )
-            motionMove.source = InputDevice.SOURCE_TOUCHSCREEN
-            instrumentation.uiAutomation.injectInputEvent(motionMove, true)
-            motionMove.recycle()
-
-            currentTime += GESTURE_STEP_MS
-            if (i == steps - 1) {
-                currentX = to.x.toFloat()
-                currentY = to.y.toFloat()
-            } else {
-                currentX += stepX
-                currentY += stepY
-            }
-            SystemClock.sleep(GESTURE_STEP_MS)
-        }
-    }
-
-    fun createShortcutOnHotseatIfNotExist(tapl: LauncherInstrumentation, appName: String) {
-        tapl.workspace.deleteAppIcon(tapl.workspace.getHotseatAppIcon(0))
-        val allApps = tapl.workspace.switchToAllApps()
-        allApps.freeze()
-        try {
-            allApps.getAppIcon(appName).dragToHotseat(0)
-        } finally {
-            allApps.unfreeze()
-        }
-    }
-
-    fun dragDividerToResizeAndWait(device: UiDevice, wmHelper: WindowManagerStateHelper) {
-        val displayBounds =
-            wmHelper.currentState.layerState.displays.firstOrNull { !it.isVirtual }?.layerStackSpace
-                ?: error("Display not found")
-        val dividerBar = device.wait(Until.findObject(dividerBarSelector), TIMEOUT_MS)
-        dividerBar.drag(Point(displayBounds.width * 1 / 3, displayBounds.height * 2 / 3), 200)
-
-        wmHelper
-            .StateSyncBuilder()
-            .withWindowSurfaceDisappeared(SPLIT_DECOR_MANAGER)
-            .waitForAndVerify()
-    }
-
-    fun dragDividerToDismissSplit(
-        device: UiDevice,
-        wmHelper: WindowManagerStateHelper,
-        dragToRight: Boolean,
-        dragToBottom: Boolean
-    ) {
-        val displayBounds =
-            wmHelper.currentState.layerState.displays.firstOrNull { !it.isVirtual }?.layerStackSpace
-                ?: error("Display not found")
-        val dividerBar = device.wait(Until.findObject(dividerBarSelector), TIMEOUT_MS)
-        dividerBar.drag(
-            Point(
-                if (dragToRight) {
-                    displayBounds.width * 4 / 5
-                } else {
-                    displayBounds.width * 1 / 5
-                },
-                if (dragToBottom) {
-                    displayBounds.height * 4 / 5
-                } else {
-                    displayBounds.height * 1 / 5
-                }
-            )
-        )
-    }
-
-    fun doubleTapDividerToSwitch(device: UiDevice) {
-        val dividerBar = device.wait(Until.findObject(dividerBarSelector), TIMEOUT_MS)
-        val interval =
-            (ViewConfiguration.getDoubleTapTimeout() + ViewConfiguration.getDoubleTapMinTime()) / 2
-        dividerBar.click()
-        SystemClock.sleep(interval.toLong())
-        dividerBar.click()
-    }
-
-    fun copyContentInSplit(
-        instrumentation: Instrumentation,
-        device: UiDevice,
-        sourceApp: IComponentNameMatcher,
-        destinationApp: IComponentNameMatcher,
-    ) {
-        // Copy text from sourceApp
-        val textView =
-            device.wait(
-                Until.findObject(By.res(sourceApp.packageName, "SplitScreenTest")),
-                TIMEOUT_MS
-            )
-        assertNotNull("Unable to find the TextView", textView)
-        textView.click(LONG_PRESS_TIME_MS)
-
-        val copyBtn = device.wait(Until.findObject(By.text("Copy")), TIMEOUT_MS)
-        assertNotNull("Unable to find the copy button", copyBtn)
-        copyBtn.click()
-
-        // Paste text to destinationApp
-        val editText =
-            device.wait(
-                Until.findObject(By.res(destinationApp.packageName, "plain_text_input")),
-                TIMEOUT_MS
-            )
-        assertNotNull("Unable to find the EditText", editText)
-        editText.click(LONG_PRESS_TIME_MS)
-
-        val pasteBtn = device.wait(Until.findObject(By.text("Paste")), TIMEOUT_MS)
-        assertNotNull("Unable to find the paste button", pasteBtn)
-        pasteBtn.click()
-
-        // Verify text
-        if (!textView.text.contentEquals(editText.text)) {
-            error("Fail to copy content in split")
-        }
-    }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt
index 03b8a75..f236c2d 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt
@@ -16,24 +16,20 @@
 
 package com.android.wm.shell.flicker.splitscreen
 
-import android.platform.test.annotations.PlatinumTest
 import android.platform.test.annotations.Postsubmit
 import android.platform.test.annotations.Presubmit
 import android.tools.common.NavBar
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
 import com.android.wm.shell.flicker.ICommonAssertions
 import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
 import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
-import com.android.wm.shell.flicker.appWindowIsVisibleAtStart
 import com.android.wm.shell.flicker.layerIsVisibleAtEnd
 import com.android.wm.shell.flicker.layerKeepVisible
 import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
-import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtEnd
-import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtStart
 import com.android.wm.shell.flicker.splitscreen.benchmark.SwitchAppByDoubleTapDividerBenchmark
 import org.junit.FixMethodOrder
 import org.junit.Test
@@ -44,13 +40,13 @@
 /**
  * Test double tap the divider bar to switch the two apps.
  *
- * To run this test: `atest WMShellFlickerTests:SwitchAppByDoubleTapDivider`
+ * To run this test: `atest WMShellFlickerTestsSplitScreen:SwitchAppByDoubleTapDivider`
  */
 @RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class SwitchAppByDoubleTapDivider(override val flicker: FlickerTest) :
+class SwitchAppByDoubleTapDivider(override val flicker: LegacyFlickerTest) :
     SwitchAppByDoubleTapDividerBenchmark(flicker), ICommonAssertions {
     override val transition: FlickerBuilder.() -> Unit
         get() = {
@@ -59,19 +55,6 @@
             thisTransition(this)
         }
 
-    @PlatinumTest(focusArea = "sysui")
-    @Presubmit
-    @Test
-    override fun cujCompleted() {
-        flicker.appWindowIsVisibleAtStart(primaryApp)
-        flicker.appWindowIsVisibleAtStart(secondaryApp)
-        flicker.splitScreenDividerIsVisibleAtStart()
-
-        flicker.appWindowIsVisibleAtEnd(primaryApp)
-        flicker.appWindowIsVisibleAtEnd(secondaryApp)
-        flicker.splitScreenDividerIsVisibleAtEnd()
-    }
-
     @Presubmit
     @Test
     fun splitScreenDividerKeepVisible() = flicker.layerKeepVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
@@ -120,11 +103,10 @@
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
                 // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
                 supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
             )
-        }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt
index 078d95d..a406009 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt
@@ -21,8 +21,8 @@
 import android.tools.common.NavBar
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
 import com.android.wm.shell.flicker.ICommonAssertions
 import com.android.wm.shell.flicker.appWindowBecomesVisible
@@ -39,13 +39,13 @@
 /**
  * Test quick switch to split pair from another app.
  *
- * To run this test: `atest WMShellFlickerTests:SwitchBackToSplitFromAnotherApp`
+ * To run this test: `atest WMShellFlickerTestsSplitScreen:SwitchBackToSplitFromAnotherApp`
  */
 @RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class SwitchBackToSplitFromAnotherApp(override val flicker: FlickerTest) :
+class SwitchBackToSplitFromAnotherApp(override val flicker: LegacyFlickerTest) :
     SwitchBackToSplitFromAnotherAppBenchmark(flicker), ICommonAssertions {
     override val transition: FlickerBuilder.() -> Unit
         get() = {
@@ -149,11 +149,10 @@
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
                 // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
                 supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
             )
-        }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt
index 7c84243..251bd10 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt
@@ -21,8 +21,8 @@
 import android.tools.common.NavBar
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
 import com.android.wm.shell.flicker.ICommonAssertions
 import com.android.wm.shell.flicker.appWindowBecomesVisible
@@ -39,13 +39,13 @@
 /**
  * Test quick switch to split pair from home.
  *
- * To run this test: `atest WMShellFlickerTests:SwitchBackToSplitFromHome`
+ * To run this test: `atest WMShellFlickerTestsSplitScreen:SwitchBackToSplitFromHome`
  */
 @RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class SwitchBackToSplitFromHome(override val flicker: FlickerTest) :
+class SwitchBackToSplitFromHome(override val flicker: LegacyFlickerTest) :
     SwitchBackToSplitFromHomeBenchmark(flicker), ICommonAssertions {
     override val transition: FlickerBuilder.() -> Unit
         get() = {
@@ -149,11 +149,10 @@
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
                 // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
                 supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
             )
-        }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt
index 7c46d3e..1dd45fe 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt
@@ -21,8 +21,8 @@
 import android.tools.common.NavBar
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
 import com.android.wm.shell.flicker.ICommonAssertions
 import com.android.wm.shell.flicker.appWindowBecomesVisible
@@ -39,13 +39,13 @@
 /**
  * Test switch back to split pair from recent.
  *
- * To run this test: `atest WMShellFlickerTests:SwitchBackToSplitFromRecent`
+ * To run this test: `atest WMShellFlickerTestsSplitScreen:SwitchBackToSplitFromRecent`
  */
 @RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class SwitchBackToSplitFromRecent(override val flicker: FlickerTest) :
+class SwitchBackToSplitFromRecent(override val flicker: LegacyFlickerTest) :
     SwitchBackToSplitFromRecentBenchmark(flicker), ICommonAssertions {
     override val transition: FlickerBuilder.() -> Unit
         get() = {
@@ -149,11 +149,10 @@
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
                 // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
                 supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
             )
-        }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt
index 674ba40..8aaa98a 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt
@@ -17,26 +17,20 @@
 package com.android.wm.shell.flicker.splitscreen
 
 import android.platform.test.annotations.FlakyTest
-import android.platform.test.annotations.PlatinumTest
 import android.platform.test.annotations.Presubmit
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
 import com.android.wm.shell.flicker.ICommonAssertions
 import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
 import com.android.wm.shell.flicker.appWindowBecomesInvisible
 import com.android.wm.shell.flicker.appWindowBecomesVisible
-import com.android.wm.shell.flicker.appWindowIsInvisibleAtEnd
-import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
-import com.android.wm.shell.flicker.appWindowIsVisibleAtStart
 import com.android.wm.shell.flicker.layerBecomesInvisible
 import com.android.wm.shell.flicker.layerBecomesVisible
 import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
 import com.android.wm.shell.flicker.splitAppLayerBoundsSnapToDivider
-import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtEnd
-import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtStart
 import com.android.wm.shell.flicker.splitscreen.benchmark.SwitchBetweenSplitPairsBenchmark
 import org.junit.FixMethodOrder
 import org.junit.Test
@@ -47,13 +41,13 @@
 /**
  * Test quick switch between two split pairs.
  *
- * To run this test: `atest WMShellFlickerTests:SwitchBetweenSplitPairs`
+ * To run this test: `atest WMShellFlickerTestsSplitScreen:SwitchBetweenSplitPairs`
  */
 @RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class SwitchBetweenSplitPairs(override val flicker: FlickerTest) :
+class SwitchBetweenSplitPairs(override val flicker: LegacyFlickerTest) :
     SwitchBetweenSplitPairsBenchmark(flicker), ICommonAssertions {
     override val transition: FlickerBuilder.() -> Unit
         get() = {
@@ -62,21 +56,6 @@
             thisTransition(this)
         }
 
-    @PlatinumTest(focusArea = "sysui")
-    @Presubmit
-    @Test
-    override fun cujCompleted() {
-        flicker.appWindowIsVisibleAtStart(thirdApp)
-        flicker.appWindowIsVisibleAtStart(fourthApp)
-        flicker.splitScreenDividerIsVisibleAtStart()
-
-        flicker.appWindowIsVisibleAtEnd(primaryApp)
-        flicker.appWindowIsVisibleAtEnd(secondaryApp)
-        flicker.appWindowIsInvisibleAtEnd(thirdApp)
-        flicker.appWindowIsInvisibleAtEnd(fourthApp)
-        flicker.splitScreenDividerIsVisibleAtEnd()
-    }
-
     @Presubmit
     @Test
     fun splitScreenDividerInvisibleAtMiddle() =
@@ -223,8 +202,6 @@
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests()
-        }
+        fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/UnlockKeyguardToSplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/UnlockKeyguardToSplitScreen.kt
index 676c150..994d6cb 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/UnlockKeyguardToSplitScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/UnlockKeyguardToSplitScreen.kt
@@ -21,8 +21,8 @@
 import android.tools.common.flicker.subject.region.RegionSubject
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
 import com.android.wm.shell.flicker.ICommonAssertions
 import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
@@ -37,21 +37,21 @@
 import org.junit.runners.Parameterized
 
 /**
- * Test unlocking insecure keyguard to back to split screen tasks and verify the transition behavior.
+ * Test unlocking insecure keyguard to back to split screen tasks and verify the transition
+ * behavior.
  *
- * To run this test: `atest WMShellFlickerTests:UnlockKeyguardToSplitScreen`
+ * To run this test: `atest WMShellFlickerTestsSplitScreen:UnlockKeyguardToSplitScreen`
  */
 @RequiresDevice
 @Postsubmit
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class UnlockKeyguardToSplitScreen(override val flicker: FlickerTest) :
-        UnlockKeyguardToSplitScreenBenchmark(flicker), ICommonAssertions {
+class UnlockKeyguardToSplitScreen(override val flicker: LegacyFlickerTest) :
+    UnlockKeyguardToSplitScreenBenchmark(flicker), ICommonAssertions {
     /** {@inheritDoc} */
     override val transition: FlickerBuilder.() -> Unit
         get() = {
-            defaultSetup(this)
             defaultTeardown(this)
             thisTransition(this)
         }
@@ -65,33 +65,35 @@
 
     @Test
     fun primaryAppBoundsIsVisibleAtEnd() =
-            flicker.splitAppLayerBoundsIsVisibleAtEnd(
-                    primaryApp,
-                    landscapePosLeft = false,
-                    portraitPosTop = false
-            )
+        flicker.splitAppLayerBoundsIsVisibleAtEnd(
+            primaryApp,
+            landscapePosLeft = false,
+            portraitPosTop = false
+        )
 
     @Test
     fun secondaryAppBoundsIsVisibleAtEnd() =
-            flicker.splitAppLayerBoundsIsVisibleAtEnd(
-                    secondaryApp,
-                    landscapePosLeft = true,
-                    portraitPosTop = true
-            )
+        flicker.splitAppLayerBoundsIsVisibleAtEnd(
+            secondaryApp,
+            landscapePosLeft = true,
+            portraitPosTop = true
+        )
 
-    @Test
-    fun primaryAppWindowIsVisibleAtEnd() = flicker.appWindowIsVisibleAtEnd(primaryApp)
+    @Test fun primaryAppWindowIsVisibleAtEnd() = flicker.appWindowIsVisibleAtEnd(primaryApp)
 
-    @Test
-    fun secondaryAppWindowIsVisibleAtEnd() = flicker.appWindowIsVisibleAtEnd(secondaryApp)
+    @Test fun secondaryAppWindowIsVisibleAtEnd() = flicker.appWindowIsVisibleAtEnd(secondaryApp)
 
     @Test
     fun notOverlapsForPrimaryAndSecondaryAppLayers() {
         flicker.assertLayers {
             this.invoke("notOverlapsForPrimaryAndSecondaryLayers") {
-                val primaryAppRegions = it.subjects.filter { subject ->
-                    subject.name.contains(primaryApp.toLayerName()) && subject.isVisible
-                }.mapNotNull { primaryApp -> primaryApp.layer.visibleRegion }.toTypedArray()
+                val primaryAppRegions =
+                    it.subjects
+                        .filter { subject ->
+                            subject.name.contains(primaryApp.toLayerName()) && subject.isVisible
+                        }
+                        .mapNotNull { primaryApp -> primaryApp.layer.visibleRegion }
+                        .toTypedArray()
 
                 val primaryAppRegionArea = RegionSubject(primaryAppRegions, it.timestamp)
                 it.visibleRegion(secondaryApp).notOverlaps(primaryAppRegionArea.region)
@@ -102,10 +104,9 @@
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
-                    supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
+                supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
             )
-        }
     }
-}
\ No newline at end of file
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt
index c3c5f88..d9d22de 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt
@@ -16,18 +16,15 @@
 
 package com.android.wm.shell.flicker.splitscreen.benchmark
 
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
 import android.tools.common.traces.component.ComponentNameMatcher
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.SplitScreenUtils
 import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
-import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
 import org.junit.FixMethodOrder
-import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
 import org.junit.runners.Parameterized
@@ -36,7 +33,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class CopyContentInSplitBenchmark(override val flicker: FlickerTest) :
+abstract class CopyContentInSplitBenchmark(override val flicker: LegacyFlickerTest) :
     SplitScreenBase(flicker) {
     protected val textEditApp = SplitScreenUtils.getIme(instrumentation)
     protected val magnifierLayer = ComponentNameMatcher("", "magnifier surface bbq wrapper#")
@@ -54,26 +51,9 @@
             }
         }
 
-    override val transition: FlickerBuilder.() -> Unit
-        get() = {
-            withoutTracing(this)
-            defaultSetup(this)
-            defaultTeardown(this)
-            thisTransition(this)
-        }
-
-    @PlatinumTest(focusArea = "sysui")
-    @Presubmit
-    @Test
-    open fun cujCompleted() {
-        // The validation of copied text is already done in SplitScreenUtils.copyContentInSplit()
-    }
-
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests()
-        }
+        fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt
index 37cd18f..7e8d60b4 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt
@@ -16,18 +16,14 @@
 
 package com.android.wm.shell.flicker.splitscreen.benchmark
 
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitScreenDismissed
+import com.android.wm.shell.flicker.SplitScreenUtils
 import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
-import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
 import org.junit.FixMethodOrder
-import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
 import org.junit.runners.Parameterized
@@ -36,7 +32,8 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class DismissSplitScreenByDividerBenchmark(flicker: FlickerTest) : SplitScreenBase(flicker) {
+abstract class DismissSplitScreenByDividerBenchmark(override val flicker: LegacyFlickerTest) :
+    SplitScreenBase(flicker) {
     protected val thisTransition: FlickerBuilder.() -> Unit
         get() = {
             setup { SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) }
@@ -60,24 +57,9 @@
             }
         }
 
-    override val transition: FlickerBuilder.() -> Unit
-        get() = {
-            withoutTracing(this)
-            defaultSetup(this)
-            defaultTeardown(this)
-            thisTransition(this)
-        }
-
-    @PlatinumTest(focusArea = "sysui")
-    @Presubmit
-    @Test
-    fun cujCompleted() = flicker.splitScreenDismissed(primaryApp, secondaryApp, toHome = false)
-
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests()
-        }
+        fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt
index 0ec6dc9..770e032 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt
@@ -16,18 +16,14 @@
 
 package com.android.wm.shell.flicker.splitscreen.benchmark
 
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitScreenDismissed
+import com.android.wm.shell.flicker.SplitScreenUtils
 import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
-import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
 import org.junit.FixMethodOrder
-import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
 import org.junit.runners.Parameterized
@@ -36,7 +32,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class DismissSplitScreenByGoHomeBenchmark(override val flicker: FlickerTest) :
+abstract class DismissSplitScreenByGoHomeBenchmark(override val flicker: LegacyFlickerTest) :
     SplitScreenBase(flicker) {
     protected val thisTransition: FlickerBuilder.() -> Unit
         get() = {
@@ -47,24 +43,9 @@
             }
         }
 
-    override val transition: FlickerBuilder.() -> Unit
-        get() = {
-            withoutTracing(this)
-            defaultSetup(this)
-            defaultTeardown(this)
-            thisTransition(this)
-        }
-
-    @PlatinumTest(focusArea = "sysui")
-    @Presubmit
-    @Test
-    fun cujCompleted() = flicker.splitScreenDismissed(primaryApp, secondaryApp, toHome = true)
-
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests()
-        }
+        fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt
index 190e2e7..46570fd 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt
@@ -16,19 +16,16 @@
 
 package com.android.wm.shell.flicker.splitscreen.benchmark
 
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.SplitScreenUtils
 import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
-import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
 import org.junit.Assume
 import org.junit.Before
 import org.junit.FixMethodOrder
-import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
 import org.junit.runners.Parameterized
@@ -37,7 +34,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class DragDividerToResizeBenchmark(override val flicker: FlickerTest) :
+abstract class DragDividerToResizeBenchmark(override val flicker: LegacyFlickerTest) :
     SplitScreenBase(flicker) {
     protected val thisTransition: FlickerBuilder.() -> Unit
         get() = {
@@ -45,32 +42,14 @@
             transitions { SplitScreenUtils.dragDividerToResizeAndWait(device, wmHelper) }
         }
 
-    override val transition: FlickerBuilder.() -> Unit
-        get() = {
-            withoutTracing(this)
-            defaultSetup(this)
-            defaultTeardown(this)
-            thisTransition(this)
-        }
-
     @Before
     fun before() {
         Assume.assumeTrue(tapl.isTablet || !flicker.scenario.isLandscapeOrSeascapeAtStart)
     }
 
-    @PlatinumTest(focusArea = "sysui")
-    @Presubmit
-    @Test
-    open fun cujCompleted() {
-        // TODO(b/246490534): Add validation for resized app after withAppTransitionIdle is
-        // robust enough to get the correct end state.
-    }
-
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests()
-        }
+        fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt
index 3a1d1a4..5c3d4ff 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt
@@ -16,21 +16,17 @@
 
 package com.android.wm.shell.flicker.splitscreen.benchmark
 
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
 import android.tools.common.NavBar
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitScreenEntered
+import com.android.wm.shell.flicker.SplitScreenUtils
 import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
-import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
 import org.junit.Assume
 import org.junit.Before
 import org.junit.FixMethodOrder
-import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
 import org.junit.runners.Parameterized
@@ -39,7 +35,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class EnterSplitScreenByDragFromAllAppsBenchmark(override val flicker: FlickerTest) :
+abstract class EnterSplitScreenByDragFromAllAppsBenchmark(override val flicker: LegacyFlickerTest) :
     SplitScreenBase(flicker) {
 
     protected val thisTransition: FlickerBuilder.() -> Unit
@@ -57,38 +53,18 @@
             }
         }
 
-    override val transition: FlickerBuilder.() -> Unit
-        get() = {
-            withoutTracing(this)
-            defaultSetup(this)
-            defaultTeardown(this)
-            thisTransition(this)
-        }
-
     @Before
     fun before() {
         Assume.assumeTrue(tapl.isTablet)
     }
 
-    @PlatinumTest(focusArea = "sysui")
-    @Presubmit
-    @Test
-    fun cujCompleted() =
-        flicker.splitScreenEntered(
-            primaryApp,
-            secondaryApp,
-            fromOtherApp = false,
-            appExistAtStart = false
-        )
-
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
                 // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
                 supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
             )
-        }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt
index 2033b7d..6b122c6 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt
@@ -16,21 +16,17 @@
 
 package com.android.wm.shell.flicker.splitscreen.benchmark
 
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
 import android.tools.common.NavBar
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitScreenEntered
+import com.android.wm.shell.flicker.SplitScreenUtils
 import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
-import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
 import org.junit.Assume
 import org.junit.Before
 import org.junit.FixMethodOrder
-import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
 import org.junit.runners.Parameterized
@@ -39,8 +35,9 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class EnterSplitScreenByDragFromNotificationBenchmark(override val flicker: FlickerTest) :
-    SplitScreenBase(flicker) {
+abstract class EnterSplitScreenByDragFromNotificationBenchmark(
+    override val flicker: LegacyFlickerTest
+) : SplitScreenBase(flicker) {
     protected val sendNotificationApp = SplitScreenUtils.getSendNotification(instrumentation)
     protected val thisTransition: FlickerBuilder.() -> Unit
         get() = {
@@ -58,21 +55,6 @@
             teardown { sendNotificationApp.exit(wmHelper) }
         }
 
-    /** {@inheritDoc} */
-    override val transition: FlickerBuilder.() -> Unit
-        get() = {
-            withoutTracing(this)
-            defaultSetup(this)
-            defaultTeardown(this)
-            thisTransition(this)
-        }
-
-    @PlatinumTest(focusArea = "sysui")
-    @Presubmit
-    @Test
-    fun cujCompleted() =
-        flicker.splitScreenEntered(primaryApp, sendNotificationApp, fromOtherApp = false)
-
     @Before
     fun before() {
         Assume.assumeTrue(tapl.isTablet)
@@ -81,11 +63,10 @@
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
                 // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
                 supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
             )
-        }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt
index b7a7110..78f9bab 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt
@@ -16,21 +16,17 @@
 
 package com.android.wm.shell.flicker.splitscreen.benchmark
 
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
 import android.tools.common.NavBar
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitScreenEntered
+import com.android.wm.shell.flicker.SplitScreenUtils
 import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
-import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
 import org.junit.Assume
 import org.junit.Before
 import org.junit.FixMethodOrder
-import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
 import org.junit.runners.Parameterized
@@ -39,8 +35,9 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class EnterSplitScreenByDragFromShortcutBenchmark(flicker: FlickerTest) :
-    SplitScreenBase(flicker) {
+abstract class EnterSplitScreenByDragFromShortcutBenchmark(
+    override val flicker: LegacyFlickerTest
+) : SplitScreenBase(flicker) {
     @Before
     fun before() {
         Assume.assumeTrue(tapl.isTablet)
@@ -62,33 +59,13 @@
         }
     }
 
-    override val transition: FlickerBuilder.() -> Unit
-        get() = {
-            withoutTracing(this)
-            defaultSetup(this)
-            defaultTeardown(this)
-            thisTransition(this)
-        }
-
-    @PlatinumTest(focusArea = "sysui")
-    @Presubmit
-    @Test
-    fun cujCompleted() =
-        flicker.splitScreenEntered(
-            primaryApp,
-            secondaryApp,
-            fromOtherApp = false,
-            appExistAtStart = false
-        )
-
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
                 // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
                 supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
             )
-        }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt
index b1ce62f..78907f0 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt
@@ -16,21 +16,17 @@
 
 package com.android.wm.shell.flicker.splitscreen.benchmark
 
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
 import android.tools.common.NavBar
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitScreenEntered
+import com.android.wm.shell.flicker.SplitScreenUtils
 import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
-import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
 import org.junit.Assume
 import org.junit.Before
 import org.junit.FixMethodOrder
-import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
 import org.junit.runners.Parameterized
@@ -39,7 +35,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class EnterSplitScreenByDragFromTaskbarBenchmark(override val flicker: FlickerTest) :
+abstract class EnterSplitScreenByDragFromTaskbarBenchmark(override val flicker: LegacyFlickerTest) :
     SplitScreenBase(flicker) {
     protected val thisTransition: FlickerBuilder.() -> Unit
         get() = {
@@ -56,26 +52,6 @@
             }
         }
 
-    /** {@inheritDoc} */
-    override val transition: FlickerBuilder.() -> Unit
-        get() = {
-            withoutTracing(this)
-            defaultSetup(this)
-            defaultTeardown(this)
-            thisTransition(this)
-        }
-
-    @PlatinumTest(focusArea = "sysui")
-    @Presubmit
-    @Test
-    fun cujCompleted() =
-        flicker.splitScreenEntered(
-            primaryApp,
-            secondaryApp,
-            fromOtherApp = false,
-            appExistAtStart = false
-        )
-
     @Before
     fun before() {
         Assume.assumeTrue(tapl.isTablet)
@@ -84,10 +60,9 @@
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
                 supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
             )
-        }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt
index 14f0745..2c91e84 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt
@@ -16,18 +16,14 @@
 
 package com.android.wm.shell.flicker.splitscreen.benchmark
 
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitScreenEntered
+import com.android.wm.shell.flicker.SplitScreenUtils
 import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
-import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
 import org.junit.FixMethodOrder
-import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
 import org.junit.runners.Parameterized
@@ -36,7 +32,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class EnterSplitScreenFromOverviewBenchmark(override val flicker: FlickerTest) :
+abstract class EnterSplitScreenFromOverviewBenchmark(override val flicker: LegacyFlickerTest) :
     SplitScreenBase(flicker) {
     protected val thisTransition: FlickerBuilder.() -> Unit
         get() = {
@@ -56,24 +52,9 @@
             }
         }
 
-    override val transition: FlickerBuilder.() -> Unit
-        get() = {
-            withoutTracing(this)
-            defaultSetup(this)
-            defaultTeardown(this)
-            thisTransition(this)
-        }
-
-    @PlatinumTest(focusArea = "sysui")
-    @Presubmit
-    @Test
-    fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true)
-
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests()
-        }
+        fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt
index 65fb135..fa09c2e 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt
@@ -16,21 +16,18 @@
 
 package com.android.wm.shell.flicker.splitscreen.benchmark
 
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
 import android.tools.common.NavBar
 import android.tools.common.Rotation
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import android.tools.device.helpers.WindowUtils
 import android.tools.device.traces.parsers.WindowManagerStateHelper
 import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.SplitScreenUtils
 import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
-import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
 import org.junit.FixMethodOrder
-import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
 import org.junit.runners.Parameterized
@@ -39,7 +36,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class SwitchAppByDoubleTapDividerBenchmark(override val flicker: FlickerTest) :
+abstract class SwitchAppByDoubleTapDividerBenchmark(override val flicker: LegacyFlickerTest) :
     SplitScreenBase(flicker) {
     protected val thisTransition: FlickerBuilder.() -> Unit
         get() = {
@@ -53,14 +50,6 @@
             }
         }
 
-    override val transition: FlickerBuilder.() -> Unit
-        get() = {
-            withoutTracing(this)
-            defaultSetup(this)
-            defaultTeardown(this)
-            thisTransition(this)
-        }
-
     private fun waitForWindowsToSwitch(wmHelper: WindowManagerStateHelper) {
         wmHelper
             .StateSyncBuilder()
@@ -134,22 +123,13 @@
         return displayBounds.width > displayBounds.height
     }
 
-    @PlatinumTest(focusArea = "sysui")
-    @Presubmit
-    @Test
-    open fun cujCompleted() {
-        // TODO(b/246490534): Add validation for switched app after withAppTransitionIdle is
-        // robust enough to get the correct end state.
-    }
-
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
                 // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
                 supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
             )
-        }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt
index b333aba..ff22006 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt
@@ -16,19 +16,15 @@
 
 package com.android.wm.shell.flicker.splitscreen.benchmark
 
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
 import android.tools.common.NavBar
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitScreenEntered
+import com.android.wm.shell.flicker.SplitScreenUtils
 import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
-import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
 import org.junit.FixMethodOrder
-import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
 import org.junit.runners.Parameterized
@@ -37,7 +33,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class SwitchBackToSplitFromAnotherAppBenchmark(override val flicker: FlickerTest) :
+abstract class SwitchBackToSplitFromAnotherAppBenchmark(override val flicker: LegacyFlickerTest) :
     SplitScreenBase(flicker) {
     private val thirdApp = SplitScreenUtils.getNonResizeable(instrumentation)
 
@@ -55,27 +51,13 @@
             }
         }
 
-    override val transition: FlickerBuilder.() -> Unit
-        get() = {
-            withoutTracing(this)
-            defaultSetup(this)
-            defaultTeardown(this)
-            thisTransition(this)
-        }
-
-    @PlatinumTest(focusArea = "sysui")
-    @Presubmit
-    @Test
-    fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true)
-
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
                 // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
                 supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
             )
-        }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt
index a27540e..5787b02 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt
@@ -16,19 +16,15 @@
 
 package com.android.wm.shell.flicker.splitscreen.benchmark
 
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
 import android.tools.common.NavBar
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitScreenEntered
+import com.android.wm.shell.flicker.SplitScreenUtils
 import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
-import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
 import org.junit.FixMethodOrder
-import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
 import org.junit.runners.Parameterized
@@ -37,7 +33,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class SwitchBackToSplitFromHomeBenchmark(override val flicker: FlickerTest) :
+abstract class SwitchBackToSplitFromHomeBenchmark(override val flicker: LegacyFlickerTest) :
     SplitScreenBase(flicker) {
     protected val thisTransition: FlickerBuilder.() -> Unit
         get() = {
@@ -53,27 +49,13 @@
             }
         }
 
-    override val transition: FlickerBuilder.() -> Unit
-        get() = {
-            withoutTracing(this)
-            defaultSetup(this)
-            defaultTeardown(this)
-            thisTransition(this)
-        }
-
-    @PlatinumTest(focusArea = "sysui")
-    @Presubmit
-    @Test
-    fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true)
-
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
                 // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
                 supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
             )
-        }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt
index 18bf4ff..b2d5091 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt
@@ -16,19 +16,15 @@
 
 package com.android.wm.shell.flicker.splitscreen.benchmark
 
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
 import android.tools.common.NavBar
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitScreenEntered
+import com.android.wm.shell.flicker.SplitScreenUtils
 import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
-import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
 import org.junit.FixMethodOrder
-import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
 import org.junit.runners.Parameterized
@@ -37,7 +33,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class SwitchBackToSplitFromRecentBenchmark(override val flicker: FlickerTest) :
+abstract class SwitchBackToSplitFromRecentBenchmark(override val flicker: LegacyFlickerTest) :
     SplitScreenBase(flicker) {
     protected val thisTransition: FlickerBuilder.() -> Unit
         get() = {
@@ -53,27 +49,13 @@
             }
         }
 
-    override val transition: FlickerBuilder.() -> Unit
-        get() = {
-            withoutTracing(this)
-            defaultSetup(this)
-            defaultTeardown(this)
-            thisTransition(this)
-        }
-
-    @PlatinumTest(focusArea = "sysui")
-    @Presubmit
-    @Test
-    fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true)
-
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
                 // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
                 supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
             )
-        }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt
index c5fe61e..f234e46 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt
@@ -16,17 +16,14 @@
 
 package com.android.wm.shell.flicker.splitscreen.benchmark
 
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.SplitScreenUtils
 import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
-import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
 import org.junit.FixMethodOrder
-import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
 import org.junit.runners.Parameterized
@@ -35,7 +32,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class SwitchBetweenSplitPairsBenchmark(override val flicker: FlickerTest) :
+abstract class SwitchBetweenSplitPairsBenchmark(override val flicker: LegacyFlickerTest) :
     SplitScreenBase(flicker) {
     protected val thirdApp = SplitScreenUtils.getIme(instrumentation)
     protected val fourthApp = SplitScreenUtils.getSendNotification(instrumentation)
@@ -64,14 +61,9 @@
             thisTransition(this)
         }
 
-    @PlatinumTest(focusArea = "sysui")
-    @Presubmit @Test open fun cujCompleted() {}
-
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests()
-        }
+        fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/UnlockKeyguardToSplitScreenBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/UnlockKeyguardToSplitScreenBenchmark.kt
index 5f16e5b..61c3679 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/UnlockKeyguardToSplitScreenBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/UnlockKeyguardToSplitScreenBenchmark.kt
@@ -19,11 +19,11 @@
 import android.tools.common.NavBar
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.SplitScreenUtils
 import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
-import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
 import org.junit.FixMethodOrder
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
@@ -33,11 +33,11 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class UnlockKeyguardToSplitScreenBenchmark(override val flicker: FlickerTest) :
-        SplitScreenBase(flicker) {
+abstract class UnlockKeyguardToSplitScreenBenchmark(override val flicker: LegacyFlickerTest) :
+    SplitScreenBase(flicker) {
     protected val thisTransition: FlickerBuilder.() -> Unit
         get() = {
-            setup { SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) }
+            setup { SplitScreenUtils.enterSplitViaIntent(wmHelper, primaryApp, secondaryApp) }
             transitions {
                 device.sleep()
                 wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify()
@@ -47,21 +47,12 @@
             }
         }
 
-    /** {@inheritDoc} */
-    override val transition: FlickerBuilder.() -> Unit
-        get() = {
-            defaultSetup(this)
-            defaultTeardown(this)
-            thisTransition(this)
-        }
-
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
-                    supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
+                supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
             )
-        }
     }
-}
\ No newline at end of file
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/trace_config/trace_config.textproto b/libs/WindowManager/Shell/tests/flicker/trace_config/trace_config.textproto
new file mode 100644
index 0000000..406ada9
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/trace_config/trace_config.textproto
@@ -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.
+
+# proto-message: TraceConfig
+
+# Enable periodic flushing of the trace buffer into the output file.
+write_into_file: true
+
+# Writes the userspace buffer into the file every 1s.
+file_write_period_ms: 2500
+
+# See b/126487238 - we need to guarantee ordering of events.
+flush_period_ms: 30000
+
+# The trace buffers needs to be big enough to hold |file_write_period_ms| of
+# trace data. The trace buffer sizing depends on the number of trace categories
+# enabled and the device activity.
+
+# RSS events
+buffers: {
+  size_kb: 63488
+  fill_policy: RING_BUFFER
+}
+
+data_sources {
+  config {
+    name: "linux.process_stats"
+    target_buffer: 0
+    # polled per-process memory counters and process/thread names.
+    # If you don't want the polled counters, remove the "process_stats_config"
+    # section, but keep the data source itself as it still provides on-demand
+    # thread/process naming for ftrace data below.
+    process_stats_config {
+      scan_all_processes_on_start: true
+    }
+  }
+}
+
+data_sources: {
+  config {
+    name: "linux.ftrace"
+    ftrace_config {
+      ftrace_events: "ftrace/print"
+      ftrace_events: "task/task_newtask"
+      ftrace_events: "task/task_rename"
+      atrace_categories: "ss"
+      atrace_categories: "wm"
+      atrace_categories: "am"
+      atrace_categories: "aidl"
+      atrace_categories: "input"
+      atrace_categories: "binder_driver"
+      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/unittest/src/com/android/wm/shell/BubbleOverflowTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/BubbleOverflowTest.java
index 8278c67..0dc16f4 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/BubbleOverflowTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/BubbleOverflowTest.java
@@ -64,18 +64,27 @@
     }
 
     @Test
-    public void test_initialize() {
+    public void test_initialize_forStack() {
         assertThat(mOverflow.getExpandedView()).isNull();
 
-        mOverflow.initialize(mBubbleController);
+        mOverflow.initialize(mBubbleController, /* forBubbleBar= */ false);
 
         assertThat(mOverflow.getExpandedView()).isNotNull();
         assertThat(mOverflow.getExpandedView().getBubbleKey()).isEqualTo(BubbleOverflow.KEY);
+        assertThat(mOverflow.getBubbleBarExpandedView()).isNull();
+    }
+
+    @Test
+    public void test_initialize_forBubbleBar() {
+        mOverflow.initialize(mBubbleController, /* forBubbleBar= */ true);
+
+        assertThat(mOverflow.getBubbleBarExpandedView()).isNotNull();
+        assertThat(mOverflow.getExpandedView()).isNull();
     }
 
     @Test
     public void test_cleanUpExpandedState() {
-        mOverflow.createExpandedView();
+        mOverflow.initialize(mBubbleController, /* forBubbleBar= */ false);
         assertThat(mOverflow.getExpandedView()).isNotNull();
 
         mOverflow.cleanUpExpandedState();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/MockToken.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/MockToken.java
new file mode 100644
index 0000000..a97c19f
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/MockToken.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.os.IBinder;
+import android.window.WindowContainerToken;
+
+/**
+ * {@link WindowContainerToken} wrapper that supports a mock binder
+ */
+public class MockToken {
+    private final WindowContainerToken mToken;
+
+    public MockToken() {
+        mToken = mock(WindowContainerToken.class);
+        IBinder binder = mock(IBinder.class);
+        when(mToken.asBinder()).thenReturn(binder);
+    }
+
+    public WindowContainerToken token() {
+        return mToken;
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataRepositoryTest.kt
new file mode 100644
index 0000000..6d9d62d
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataRepositoryTest.kt
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.bubbles
+
+import android.app.ActivityTaskManager
+import android.content.pm.LauncherApps
+import android.content.pm.ShortcutInfo
+import android.util.SparseArray
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.bubbles.storage.BubbleEntity
+import com.android.wm.shell.bubbles.storage.BubblePersistentRepository
+import com.android.wm.shell.common.ShellExecutor
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.spy
+
+class BubbleDataRepositoryTest : ShellTestCase() {
+
+    private val user0BubbleEntities = listOf(
+        BubbleEntity(
+            userId = 0,
+            packageName = "com.example.messenger",
+            shortcutId = "shortcut-1",
+            key = "0k1",
+            desiredHeight = 120,
+            desiredHeightResId = 0,
+            title = null,
+            taskId = 1,
+            locus = null,
+            isDismissable = true
+        ),
+        BubbleEntity(
+            userId = 10,
+            packageName = "com.example.chat",
+            shortcutId = "alice and bob",
+            key = "0k2",
+            desiredHeight = 0,
+            desiredHeightResId = 16537428,
+            title = "title",
+            taskId = 2,
+            locus = null
+        ),
+        BubbleEntity(
+            userId = 0,
+            packageName = "com.example.messenger",
+            shortcutId = "shortcut-2",
+            key = "0k3",
+            desiredHeight = 120,
+            desiredHeightResId = 0,
+            title = null,
+            taskId = ActivityTaskManager.INVALID_TASK_ID,
+            locus = null
+        )
+    )
+
+    private val user1BubbleEntities = listOf(
+        BubbleEntity(
+            userId = 1,
+            packageName = "com.example.messenger",
+            shortcutId = "shortcut-1",
+            key = "1k1",
+            desiredHeight = 120,
+            desiredHeightResId = 0,
+            title = null,
+            taskId = 3,
+            locus = null,
+            isDismissable = true
+        ),
+        BubbleEntity(
+            userId = 12,
+            packageName = "com.example.chat",
+            shortcutId = "alice and bob",
+            key = "1k2",
+            desiredHeight = 0,
+            desiredHeightResId = 16537428,
+            title = "title",
+            taskId = 4,
+            locus = null
+        ),
+        BubbleEntity(
+            userId = 1,
+            packageName = "com.example.messenger",
+            shortcutId = "shortcut-2",
+            key = "1k3",
+            desiredHeight = 120,
+            desiredHeightResId = 0,
+            title = null,
+            taskId = ActivityTaskManager.INVALID_TASK_ID,
+            locus = null
+        ),
+        BubbleEntity(
+            userId = 12,
+            packageName = "com.example.chat",
+            shortcutId = "alice",
+            key = "1k4",
+            desiredHeight = 0,
+            desiredHeightResId = 16537428,
+            title = "title",
+            taskId = 5,
+            locus = null
+        )
+    )
+
+    private val mainExecutor = mock(ShellExecutor::class.java)
+    private val launcherApps = mock(LauncherApps::class.java)
+
+    private val persistedBubbles = SparseArray<List<BubbleEntity>>()
+
+    private lateinit var dataRepository: BubbleDataRepository
+    private lateinit var persistentRepository: BubblePersistentRepository
+
+    @Before
+    fun setup() {
+        persistentRepository = spy(BubblePersistentRepository(mContext))
+        dataRepository = BubbleDataRepository(launcherApps, mainExecutor, persistentRepository)
+
+        // Add the bubbles to the persistent repository
+        persistedBubbles.put(0, user0BubbleEntities)
+        persistedBubbles.put(1, user1BubbleEntities)
+        persistentRepository.persistsToDisk(persistedBubbles)
+    }
+
+    @After
+    fun teardown() {
+        // Clean up any persisted bubbles for the next run
+        persistentRepository.persistsToDisk(SparseArray())
+    }
+
+    @Test
+    fun testLoadBubbles_invalidParent() {
+        val activeUserIds = listOf(10, 1, 12) // Missing user 0 in persistedBubbles
+        dataRepository.loadBubbles(1, activeUserIds) {
+            // Verify that user 0 has been removed from the persisted list
+            val entitiesByUser = persistentRepository.readFromDisk()
+            assertThat(entitiesByUser.get(0)).isNull()
+        }
+    }
+
+    @Test
+    fun testLoadBubbles_invalidChild() {
+        val activeUserIds = listOf(0, 10, 1) // Missing user 1's child user 12
+        dataRepository.loadBubbles(1, activeUserIds) {
+            // Build a list to compare against
+            val user1BubblesWithoutUser12 = mutableListOf<Bubble>()
+            val user1EntitiesWithoutUser12 = mutableListOf<BubbleEntity>()
+            for (entity in user1BubbleEntities) {
+                if (entity.userId != 12) {
+                    user1BubblesWithoutUser12.add(entity.toBubble())
+                    user1EntitiesWithoutUser12.add(entity)
+                }
+            }
+
+            // Verify that user 12 has been removed from the persisted list
+            val entitiesByUser = persistentRepository.readFromDisk()
+            assertThat(entitiesByUser.get(1)).isEqualTo(user1EntitiesWithoutUser12)
+        }
+    }
+
+    private fun BubbleEntity.toBubble(): Bubble {
+        return Bubble(
+            key,
+            mock(ShortcutInfo::class.java),
+            desiredHeight,
+            desiredHeightResId,
+            title,
+            taskId,
+            locus,
+            isDismissable,
+            mainExecutor,
+            mock(Bubbles.BubbleMetadataFlagListener::class.java)
+        )
+    }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleViewTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleViewTest.java
new file mode 100644
index 0000000..d38b848
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleViewTest.java
@@ -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.wm.shell.bubbles.bar;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.graphics.drawable.ColorDrawable;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import androidx.core.content.ContextCompat;
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.R;
+import com.android.wm.shell.ShellTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class BubbleBarHandleViewTest extends ShellTestCase {
+    private BubbleBarHandleView mHandleView;
+
+    @Before
+    public void setup() {
+        mHandleView = new BubbleBarHandleView(mContext);
+    }
+
+    @Test
+    public void testUpdateHandleColor_lightBg() {
+        mHandleView.updateHandleColor(false /* isRegionDark */, false /* animated */);
+
+        assertTrue(mHandleView.getClipToOutline());
+        assertTrue(mHandleView.getBackground() instanceof ColorDrawable);
+        ColorDrawable bgDrawable = (ColorDrawable) mHandleView.getBackground();
+        assertEquals(bgDrawable.getColor(),
+                ContextCompat.getColor(mContext, R.color.bubble_bar_expanded_view_handle_dark));
+    }
+
+    @Test
+    public void testUpdateHandleColor_darkBg() {
+        mHandleView.updateHandleColor(true /* isRegionDark */, false /* animated */);
+
+        assertTrue(mHandleView.getClipToOutline());
+        assertTrue(mHandleView.getBackground() instanceof ColorDrawable);
+        ColorDrawable bgDrawable = (ColorDrawable) mHandleView.getBackground();
+        assertEquals(bgDrawable.getColor(),
+                ContextCompat.getColor(mContext, R.color.bubble_bar_expanded_view_handle_light));
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/LaunchAdjacentControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/LaunchAdjacentControllerTest.kt
new file mode 100644
index 0000000..9dc816b
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/LaunchAdjacentControllerTest.kt
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.common
+
+import android.os.IBinder
+import android.testing.AndroidTestingRunner
+import android.window.WindowContainerTransaction
+import android.window.WindowContainerTransaction.HierarchyOp
+import androidx.test.filters.SmallTest
+import com.android.wm.shell.MockToken
+import com.android.wm.shell.ShellTestCase
+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.Mock
+import org.mockito.Mockito.any
+import org.mockito.Mockito.atLeastOnce
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class LaunchAdjacentControllerTest : ShellTestCase() {
+
+    private lateinit var controller: LaunchAdjacentController
+
+    @Mock private lateinit var syncQueue: SyncTransactionQueue
+
+    @Before
+    fun setUp() {
+        controller = LaunchAdjacentController(syncQueue)
+    }
+
+    @Test
+    fun newInstance_enabledByDefault() {
+        assertThat(controller.launchAdjacentEnabled).isTrue()
+    }
+
+    @Test
+    fun setLaunchAdjacentRoot_launchAdjacentEnabled_setsFlagRoot() {
+        val token = MockToken().token()
+        controller.setLaunchAdjacentRoot(token)
+        val wct = getLatestTransactionOrFail()
+        assertThat(wct.getSetLaunchAdjacentFlagRootContainer()).isEqualTo(token.asBinder())
+    }
+
+    @Test
+    fun setLaunchAdjacentRoot_launchAdjacentDisabled_doesNotUpdateFlagRoot() {
+        val token = MockToken().token()
+        controller.launchAdjacentEnabled = false
+        controller.setLaunchAdjacentRoot(token)
+        verify(syncQueue, never()).queue(any())
+    }
+
+    @Test
+    fun clearLaunchAdjacentRoot_launchAdjacentEnabled_clearsFlagRoot() {
+        val token = MockToken().token()
+        controller.setLaunchAdjacentRoot(token)
+        controller.clearLaunchAdjacentRoot()
+        val wct = getLatestTransactionOrFail()
+        assertThat(wct.getClearLaunchAdjacentFlagRootContainer()).isEqualTo(token.asBinder())
+    }
+
+    @Test
+    fun clearLaunchAdjacentRoot_launchAdjacentDisabled_clearsFlagRoot() {
+        val token = MockToken().token()
+        controller.setLaunchAdjacentRoot(token)
+        controller.launchAdjacentEnabled = false
+        clearInvocations(syncQueue)
+
+        controller.clearLaunchAdjacentRoot()
+        val wct = getLatestTransactionOrFail()
+        assertThat(wct.getClearLaunchAdjacentFlagRootContainer()).isEqualTo(token.asBinder())
+    }
+
+    @Test
+    fun setLaunchAdjacentEnabled_wasDisabledWithContainerSet_setsFlagRoot() {
+        val token = MockToken().token()
+        controller.setLaunchAdjacentRoot(token)
+        controller.launchAdjacentEnabled = false
+        clearInvocations(syncQueue)
+
+        controller.launchAdjacentEnabled = true
+        val wct = getLatestTransactionOrFail()
+        assertThat(wct.getSetLaunchAdjacentFlagRootContainer()).isEqualTo(token.asBinder())
+    }
+
+    @Test
+    fun setLaunchAdjacentEnabled_containerNotSet_doesNotUpdateFlagRoot() {
+        controller.launchAdjacentEnabled = false
+        controller.launchAdjacentEnabled = true
+        verify(syncQueue, never()).queue(any())
+    }
+
+    @Test
+    fun setLaunchAdjacentEnabled_multipleTimes_setsFlagRootOnce() {
+        val token = MockToken().token()
+        controller.setLaunchAdjacentRoot(token)
+        controller.launchAdjacentEnabled = true
+        controller.launchAdjacentEnabled = true
+        // Only execute once
+        verify(syncQueue).queue(any())
+    }
+
+    @Test
+    fun setLaunchAdjacentDisabled_containerSet_clearsFlagRoot() {
+        val token = MockToken().token()
+        controller.setLaunchAdjacentRoot(token)
+        controller.launchAdjacentEnabled = false
+        val wct = getLatestTransactionOrFail()
+        assertThat(wct.getClearLaunchAdjacentFlagRootContainer()).isEqualTo(token.asBinder())
+    }
+
+    @Test
+    fun setLaunchAdjacentDisabled_containerNotSet_doesNotUpdateFlagRoot() {
+        controller.launchAdjacentEnabled = false
+        verify(syncQueue, never()).queue(any())
+    }
+
+    @Test
+    fun setLaunchAdjacentDisabled_multipleTimes_setsFlagRootOnce() {
+        val token = MockToken().token()
+        controller.setLaunchAdjacentRoot(token)
+        clearInvocations(syncQueue)
+        controller.launchAdjacentEnabled = false
+        controller.launchAdjacentEnabled = false
+        // Only execute once
+        verify(syncQueue).queue(any())
+    }
+
+    private fun getLatestTransactionOrFail(): WindowContainerTransaction {
+        val arg = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+        verify(syncQueue, atLeastOnce()).queue(arg.capture())
+        return arg.allValues.last().also { assertThat(it).isNotNull() }
+    }
+}
+
+private fun WindowContainerTransaction.getSetLaunchAdjacentFlagRootContainer(): IBinder {
+    return hierarchyOps
+        // Find the operation with the correct type
+        .filter { op -> op.type == HierarchyOp.HIERARCHY_OP_TYPE_SET_LAUNCH_ADJACENT_FLAG_ROOT }
+        // For set flag root operation, toTop is false
+        .filter { op -> !op.toTop }
+        .map { it.container }
+        .first()
+}
+
+private fun WindowContainerTransaction.getClearLaunchAdjacentFlagRootContainer(): IBinder {
+    return hierarchyOps
+        // Find the operation with the correct type
+        .filter { op -> op.type == HierarchyOp.HIERARCHY_OP_TYPE_SET_LAUNCH_ADJACENT_FLAG_ROOT }
+        // For clear flag root operation, toTop is true
+        .filter { op -> op.toTop }
+        .map { it.container }
+        .first()
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java
new file mode 100644
index 0000000..145c8f0
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.common.split;
+
+import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+
+import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.CURSOR_HOVER_STATES_ENABLED;
+
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.os.SystemClock;
+import android.provider.DeviceConfig;
+import android.view.InputDevice;
+import android.view.InsetsState;
+import android.view.MotionEvent;
+
+import androidx.test.annotation.UiThreadTest;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.DisplayImeController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/** Tests for {@link DividerView} */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class DividerViewTest extends ShellTestCase {
+    private @Mock SplitWindowManager.ParentContainerCallbacks mCallbacks;
+    private @Mock SplitLayout.SplitLayoutHandler mSplitLayoutHandler;
+    private @Mock DisplayController mDisplayController;
+    private @Mock DisplayImeController mDisplayImeController;
+    private @Mock ShellTaskOrganizer mTaskOrganizer;
+    private SplitLayout mSplitLayout;
+    private DividerView mDividerView;
+
+    @Before
+    @UiThreadTest
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        Configuration configuration = getConfiguration();
+        mSplitLayout = new SplitLayout("TestSplitLayout", mContext, configuration,
+                mSplitLayoutHandler, mCallbacks, mDisplayController, mDisplayImeController,
+                mTaskOrganizer, SplitLayout.PARALLAX_NONE);
+        SplitWindowManager splitWindowManager = new SplitWindowManager("TestSplitWindowManager",
+                mContext,
+                configuration, mCallbacks);
+        splitWindowManager.init(mSplitLayout, new InsetsState());
+        mDividerView = spy((DividerView) splitWindowManager.getDividerView());
+    }
+
+    @Test
+    @UiThreadTest
+    public void testHoverDividerView() {
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, CURSOR_HOVER_STATES_ENABLED,
+                "true", false);
+
+        Rect dividerBounds = mSplitLayout.getDividerBounds();
+        int x = dividerBounds.centerX();
+        int y = dividerBounds.centerY();
+        long downTime = SystemClock.uptimeMillis();
+        mDividerView.onHoverEvent(getMotionEvent(downTime, MotionEvent.ACTION_HOVER_ENTER, x, y));
+
+        verify(mDividerView, times(1)).setHovering();
+
+        mDividerView.onHoverEvent(getMotionEvent(downTime, MotionEvent.ACTION_HOVER_EXIT, x, y));
+
+        verify(mDividerView, times(1)).releaseHovering();
+
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, CURSOR_HOVER_STATES_ENABLED,
+                "false", false);
+    }
+
+    private static MotionEvent getMotionEvent(long eventTime, int action, float x, float y) {
+        MotionEvent.PointerProperties properties = new MotionEvent.PointerProperties();
+        properties.id = 0;
+        properties.toolType = MotionEvent.TOOL_TYPE_UNKNOWN;
+
+        MotionEvent.PointerCoords coords = new MotionEvent.PointerCoords();
+        coords.pressure = 1;
+        coords.size = 1;
+        coords.x = x;
+        coords.y = y;
+
+        return MotionEvent.obtain(eventTime, eventTime, action, 1,
+                new MotionEvent.PointerProperties[]{properties},
+                new MotionEvent.PointerCoords[]{coords}, 0, 0, 1.0f, 1.0f, 0, 0,
+                InputDevice.SOURCE_TOUCHSCREEN, 0);
+    }
+
+    private static Configuration getConfiguration() {
+        final Configuration configuration = new Configuration();
+        configuration.unset();
+        configuration.orientation = ORIENTATION_LANDSCAPE;
+        configuration.windowConfiguration.setRotation(0);
+        configuration.windowConfiguration.setBounds(new Rect(0, 0, 1080, 2160));
+        return configuration;
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
index d6387ee..605a762 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
@@ -59,6 +59,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.dx.mockito.inline.extended.StaticMockitoSession;
+import com.android.wm.shell.MockToken;
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.ShellTestCase;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
index 3bc2f0e..3fe78ef 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
@@ -129,6 +129,18 @@
     }
 
     @Test
+    fun addListener_notifiesStashed() {
+        repo.setStashed(DEFAULT_DISPLAY, true)
+        val listener = TestVisibilityListener()
+        val executor = TestShellExecutor()
+        repo.addVisibleTasksListener(listener, executor)
+        executor.flushAll()
+
+        assertThat(listener.stashedOnDefaultDisplay).isTrue()
+        assertThat(listener.stashedChangesOnDefaultDisplay).isEqualTo(1)
+    }
+
+    @Test
     fun addListener_tasksOnDifferentDisplay_doesNotNotify() {
         repo.updateVisibleFreeformTasks(SECOND_DISPLAY, taskId = 1, visible = true)
         val listener = TestVisibilityListener()
@@ -313,6 +325,65 @@
         assertThat(tasks.first()).isEqualTo(6)
     }
 
+    @Test
+    fun setStashed_stateIsUpdatedForTheDisplay() {
+        repo.setStashed(DEFAULT_DISPLAY, true)
+        assertThat(repo.isStashed(DEFAULT_DISPLAY)).isTrue()
+        assertThat(repo.isStashed(SECOND_DISPLAY)).isFalse()
+
+        repo.setStashed(DEFAULT_DISPLAY, false)
+        assertThat(repo.isStashed(DEFAULT_DISPLAY)).isFalse()
+    }
+
+    @Test
+    fun setStashed_notifyListener() {
+        val listener = TestVisibilityListener()
+        val executor = TestShellExecutor()
+        repo.addVisibleTasksListener(listener, executor)
+        repo.setStashed(DEFAULT_DISPLAY, true)
+        executor.flushAll()
+        assertThat(listener.stashedOnDefaultDisplay).isTrue()
+        assertThat(listener.stashedChangesOnDefaultDisplay).isEqualTo(1)
+
+        repo.setStashed(DEFAULT_DISPLAY, false)
+        executor.flushAll()
+        assertThat(listener.stashedOnDefaultDisplay).isFalse()
+        assertThat(listener.stashedChangesOnDefaultDisplay).isEqualTo(2)
+    }
+
+    @Test
+    fun setStashed_secondCallDoesNotNotify() {
+        val listener = TestVisibilityListener()
+        val executor = TestShellExecutor()
+        repo.addVisibleTasksListener(listener, executor)
+        repo.setStashed(DEFAULT_DISPLAY, true)
+        repo.setStashed(DEFAULT_DISPLAY, true)
+        executor.flushAll()
+        assertThat(listener.stashedChangesOnDefaultDisplay).isEqualTo(1)
+    }
+
+    @Test
+    fun setStashed_tracksPerDisplay() {
+        val listener = TestVisibilityListener()
+        val executor = TestShellExecutor()
+        repo.addVisibleTasksListener(listener, executor)
+
+        repo.setStashed(DEFAULT_DISPLAY, true)
+        executor.flushAll()
+        assertThat(listener.stashedOnDefaultDisplay).isTrue()
+        assertThat(listener.stashedOnSecondaryDisplay).isFalse()
+
+        repo.setStashed(SECOND_DISPLAY, true)
+        executor.flushAll()
+        assertThat(listener.stashedOnDefaultDisplay).isTrue()
+        assertThat(listener.stashedOnSecondaryDisplay).isTrue()
+
+        repo.setStashed(DEFAULT_DISPLAY, false)
+        executor.flushAll()
+        assertThat(listener.stashedOnDefaultDisplay).isFalse()
+        assertThat(listener.stashedOnSecondaryDisplay).isTrue()
+    }
+
     class TestListener : DesktopModeTaskRepository.ActiveTasksListener {
         var activeChangesOnDefaultDisplay = 0
         var activeChangesOnSecondaryDisplay = 0
@@ -332,6 +403,12 @@
         var visibleChangesOnDefaultDisplay = 0
         var visibleChangesOnSecondaryDisplay = 0
 
+        var stashedOnDefaultDisplay = false
+        var stashedOnSecondaryDisplay = false
+
+        var stashedChangesOnDefaultDisplay = 0
+        var stashedChangesOnSecondaryDisplay = 0
+
         override fun onVisibilityChanged(displayId: Int, hasVisibleFreeformTasks: Boolean) {
             when (displayId) {
                 DEFAULT_DISPLAY -> {
@@ -345,6 +422,20 @@
                 else -> fail("Visible task listener received unexpected display id: $displayId")
             }
         }
+
+        override fun onStashedChanged(displayId: Int, stashed: Boolean) {
+            when (displayId) {
+                DEFAULT_DISPLAY -> {
+                    stashedOnDefaultDisplay = stashed
+                    stashedChangesOnDefaultDisplay++
+                }
+                SECOND_DISPLAY -> {
+                    stashedOnSecondaryDisplay = stashed
+                    stashedChangesOnDefaultDisplay++
+                }
+                else -> fail("Visible task listener received unexpected display id: $displayId")
+            }
+        }
     }
 
     companion object {
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 1335ebf..1477cf7 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
@@ -39,17 +39,20 @@
 import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
 import com.android.dx.mockito.inline.extended.ExtendedMockito.never
 import com.android.dx.mockito.inline.extended.StaticMockitoSession
+import com.android.wm.shell.MockToken
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer
 import com.android.wm.shell.ShellTaskOrganizer
 import com.android.wm.shell.ShellTestCase
 import com.android.wm.shell.TestRunningTaskInfoBuilder
 import com.android.wm.shell.TestShellExecutor
 import com.android.wm.shell.common.DisplayController
+import com.android.wm.shell.common.LaunchAdjacentController
 import com.android.wm.shell.common.ShellExecutor
 import com.android.wm.shell.common.SyncTransactionQueue
 import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreeformTask
 import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFullscreenTask
 import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createHomeTask
+import com.android.wm.shell.sysui.ShellCommandHandler
 import com.android.wm.shell.sysui.ShellController
 import com.android.wm.shell.sysui.ShellInit
 import com.android.wm.shell.transition.Transitions
@@ -77,6 +80,7 @@
 class DesktopTasksControllerTest : ShellTestCase() {
 
     @Mock lateinit var testExecutor: ShellExecutor
+    @Mock lateinit var shellCommandHandler: ShellCommandHandler
     @Mock lateinit var shellController: ShellController
     @Mock lateinit var displayController: DisplayController
     @Mock lateinit var shellTaskOrganizer: ShellTaskOrganizer
@@ -85,12 +89,16 @@
     @Mock lateinit var transitions: Transitions
     @Mock lateinit var exitDesktopTransitionHandler: ExitDesktopTaskTransitionHandler
     @Mock lateinit var enterDesktopTransitionHandler: EnterDesktopTaskTransitionHandler
+    @Mock lateinit var mToggleResizeDesktopTaskTransitionHandler:
+            ToggleResizeDesktopTaskTransitionHandler
+    @Mock lateinit var launchAdjacentController: LaunchAdjacentController
 
     private lateinit var mockitoSession: StaticMockitoSession
     private lateinit var controller: DesktopTasksController
     private lateinit var shellInit: ShellInit
     private lateinit var desktopModeTaskRepository: DesktopModeTaskRepository
 
+    private val shellExecutor = TestShellExecutor()
     // Mock running tasks are registered here so we can get the list from mock shell task organizer
     private val runningTasks = mutableListOf<RunningTaskInfo>()
 
@@ -114,6 +122,7 @@
         return DesktopTasksController(
             context,
             shellInit,
+            shellCommandHandler,
             shellController,
             displayController,
             shellTaskOrganizer,
@@ -122,8 +131,10 @@
             transitions,
             enterDesktopTransitionHandler,
             exitDesktopTransitionHandler,
+            mToggleResizeDesktopTaskTransitionHandler,
             desktopModeTaskRepository,
-            TestShellExecutor()
+            launchAdjacentController,
+            shellExecutor
         )
     }
 
@@ -262,8 +273,9 @@
     }
 
     @Test
-    fun moveToDesktop() {
+    fun moveToDesktop_displayFullscreen_windowingModeSetToFreeform() {
         val task = setUpFullscreenTask()
+        task.configuration.windowConfiguration.displayWindowingMode = WINDOWING_MODE_FULLSCREEN
         controller.moveToDesktop(task)
         val wct = getLatestWct(expectTransition = TRANSIT_CHANGE)
         assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
@@ -271,6 +283,16 @@
     }
 
     @Test
+    fun moveToDesktop_displayFreeform_windowingModeSetToUndefined() {
+        val task = setUpFullscreenTask()
+        task.configuration.windowConfiguration.displayWindowingMode = WINDOWING_MODE_FREEFORM
+        controller.moveToDesktop(task)
+        val wct = getLatestWct(expectTransition = TRANSIT_CHANGE)
+        assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
+                .isEqualTo(WINDOWING_MODE_UNDEFINED)
+    }
+
+    @Test
     fun moveToDesktop_nonExistentTask_doesNothing() {
         controller.moveToDesktop(999)
         verifyWCTNotExecuted()
@@ -317,12 +339,23 @@
     }
 
     @Test
-    fun moveToFullscreen() {
+    fun moveToFullscreen_displayFullscreen_windowingModeSetToUndefined() {
         val task = setUpFreeformTask()
+        task.configuration.windowConfiguration.displayWindowingMode = WINDOWING_MODE_FULLSCREEN
         controller.moveToFullscreen(task)
         val wct = getLatestWct(expectTransition = TRANSIT_CHANGE)
         assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
-            .isEqualTo(WINDOWING_MODE_FULLSCREEN)
+            .isEqualTo(WINDOWING_MODE_UNDEFINED)
+    }
+
+    @Test
+    fun moveToFullscreen_displayFreeform_windowingModeSetToFullscreen() {
+        val task = setUpFreeformTask()
+        task.configuration.windowConfiguration.displayWindowingMode = WINDOWING_MODE_FREEFORM
+        controller.moveToFullscreen(task)
+        val wct = getLatestWct(expectTransition = TRANSIT_CHANGE)
+        assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
+                .isEqualTo(WINDOWING_MODE_FULLSCREEN)
     }
 
     @Test
@@ -345,6 +378,18 @@
     }
 
     @Test
+    fun moveTaskToFront_postsWctWithReorderOp() {
+        val task1 = setUpFreeformTask()
+        setUpFreeformTask()
+
+        controller.moveTaskToFront(task1)
+
+        val wct = getLatestWct(expectTransition = TRANSIT_TO_FRONT)
+        assertThat(wct.hierarchyOps).hasSize(1)
+        wct.assertReorderAt(index = 0, task1)
+    }
+
+    @Test
     fun moveToNextDisplay_noOtherDisplays() {
         whenever(rootTaskDisplayAreaOrganizer.displayIds).thenReturn(intArrayOf(DEFAULT_DISPLAY))
         val task = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
@@ -451,6 +496,27 @@
     }
 
     @Test
+    fun handleRequest_fullscreenTask_desktopStashed_returnWCTWithAllAppsBroughtToFront() {
+        assumeTrue(ENABLE_SHELL_TRANSITIONS)
+
+        val stashedFreeformTask = setUpFreeformTask(DEFAULT_DISPLAY)
+        markTaskHidden(stashedFreeformTask)
+
+        val fullscreenTask = createFullscreenTask(DEFAULT_DISPLAY)
+
+        controller.stashDesktopApps(DEFAULT_DISPLAY)
+
+        val result = controller.handleRequest(Binder(), createTransition(fullscreenTask))
+        assertThat(result).isNotNull()
+        result!!.assertReorderSequence(stashedFreeformTask, fullscreenTask)
+        assertThat(result.changes[fullscreenTask.token.asBinder()]?.windowingMode)
+                .isEqualTo(WINDOWING_MODE_FREEFORM)
+
+        // Stashed state should be cleared
+        assertThat(desktopModeTaskRepository.isStashed(DEFAULT_DISPLAY)).isFalse()
+    }
+
+    @Test
     fun handleRequest_freeformTask_freeformVisible_returnNull() {
         assumeTrue(ENABLE_SHELL_TRANSITIONS)
 
@@ -501,6 +567,25 @@
     }
 
     @Test
+    fun handleRequest_freeformTask_desktopStashed_returnWCTWithAllAppsBroughtToFront() {
+        assumeTrue(ENABLE_SHELL_TRANSITIONS)
+
+        val stashedFreeformTask = setUpFreeformTask(DEFAULT_DISPLAY)
+        markTaskHidden(stashedFreeformTask)
+
+        val freeformTask = createFreeformTask(DEFAULT_DISPLAY)
+
+        controller.stashDesktopApps(DEFAULT_DISPLAY)
+
+        val result = controller.handleRequest(Binder(), createTransition(freeformTask))
+        assertThat(result).isNotNull()
+        result?.assertReorderSequence(stashedFreeformTask, freeformTask)
+
+        // Stashed state should be cleared
+        assertThat(desktopModeTaskRepository.isStashed(DEFAULT_DISPLAY)).isFalse()
+    }
+
+    @Test
     fun handleRequest_notOpenOrToFrontTransition_returnNull() {
         assumeTrue(ENABLE_SHELL_TRANSITIONS)
 
@@ -539,6 +624,46 @@
         assertThat(controller.handleRequest(Binder(), createTransition(task))).isNull()
     }
 
+    @Test
+    fun stashDesktopApps_stateUpdates() {
+        controller.stashDesktopApps(DEFAULT_DISPLAY)
+
+        assertThat(desktopModeTaskRepository.isStashed(DEFAULT_DISPLAY)).isTrue()
+        assertThat(desktopModeTaskRepository.isStashed(SECOND_DISPLAY)).isFalse()
+    }
+
+    @Test
+    fun hideStashedDesktopApps_stateUpdates() {
+        desktopModeTaskRepository.setStashed(DEFAULT_DISPLAY, true)
+        desktopModeTaskRepository.setStashed(SECOND_DISPLAY, true)
+        controller.hideStashedDesktopApps(DEFAULT_DISPLAY)
+
+        assertThat(desktopModeTaskRepository.isStashed(DEFAULT_DISPLAY)).isFalse()
+        // Check that second display is not affected
+        assertThat(desktopModeTaskRepository.isStashed(SECOND_DISPLAY)).isTrue()
+    }
+
+    @Test
+    fun desktopTasksVisibilityChange_visible_setLaunchAdjacentDisabled() {
+        val task = setUpFreeformTask()
+        clearInvocations(launchAdjacentController)
+
+        markTaskVisible(task)
+        shellExecutor.flushAll()
+        verify(launchAdjacentController).launchAdjacentEnabled = false
+    }
+
+    @Test
+    fun desktopTasksVisibilityChange_invisible_setLaunchAdjacentEnabled() {
+        val task = setUpFreeformTask()
+        markTaskVisible(task)
+        clearInvocations(launchAdjacentController)
+
+        markTaskHidden(task)
+        shellExecutor.flushAll()
+        verify(launchAdjacentController).launchAdjacentEnabled = true
+    }
+
     private fun setUpFreeformTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo {
         val task = createFreeformTask(displayId)
         whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt
index cf1ff32..29a757c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt
@@ -22,6 +22,7 @@
 import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
 import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
 import android.view.Display.DEFAULT_DISPLAY
+import com.android.wm.shell.MockToken
 import com.android.wm.shell.TestRunningTaskInfoBuilder
 
 class DesktopTestHelpers {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandlerTest.java
index 8592dea..885ae38 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandlerTest.java
@@ -28,6 +28,7 @@
 import android.annotation.NonNull;
 import android.app.ActivityManager;
 import android.app.WindowConfiguration;
+import android.graphics.PointF;
 import android.graphics.Rect;
 import android.os.IBinder;
 import android.view.SurfaceControl;
@@ -41,6 +42,7 @@
 
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.transition.Transitions;
+import com.android.wm.shell.windowdecor.MoveToDesktopAnimator;
 
 import junit.framework.AssertionFailedError;
 
@@ -73,6 +75,10 @@
     ShellExecutor mExecutor;
     @Mock
     SurfaceControl mSurfaceControl;
+    @Mock
+    MoveToDesktopAnimator mMoveToDesktopAnimator;
+    @Mock
+    PointF mPosition;
 
     private EnterDesktopTaskTransitionHandler mEnterDesktopTaskTransitionHandler;
 
@@ -82,6 +88,7 @@
 
         doReturn(mExecutor).when(mTransitions).getMainExecutor();
         doReturn(mAnimationT).when(mTransactionFactory).get();
+        doReturn(mPosition).when(mMoveToDesktopAnimator).getPosition();
 
         mEnterDesktopTaskTransitionHandler = new EnterDesktopTaskTransitionHandler(mTransitions,
                 mTransactionFactory);
@@ -89,16 +96,20 @@
 
     @Test
     public void testEnterFreeformAnimation() {
-        final int transitionType = Transitions.TRANSIT_ENTER_FREEFORM;
         final int taskId = 1;
         WindowContainerTransaction wct = new WindowContainerTransaction();
         doReturn(mToken).when(mTransitions)
-                .startTransition(transitionType, wct, mEnterDesktopTaskTransitionHandler);
-        mEnterDesktopTaskTransitionHandler.startTransition(transitionType, wct, null);
+                .startTransition(Transitions.TRANSIT_START_MOVE_TO_DESKTOP_MODE, wct,
+                        mEnterDesktopTaskTransitionHandler);
+        doReturn(taskId).when(mMoveToDesktopAnimator).getTaskId();
+
+        mEnterDesktopTaskTransitionHandler.startMoveToDesktop(wct,
+                mMoveToDesktopAnimator, null);
 
         TransitionInfo.Change change =
                 createChange(WindowManager.TRANSIT_CHANGE, taskId, WINDOWING_MODE_FREEFORM);
-        TransitionInfo info = createTransitionInfo(Transitions.TRANSIT_ENTER_FREEFORM, change);
+        TransitionInfo info = createTransitionInfo(Transitions.TRANSIT_START_MOVE_TO_DESKTOP_MODE,
+                change);
 
 
         assertTrue(mEnterDesktopTaskTransitionHandler
@@ -110,17 +121,18 @@
 
     @Test
     public void testTransitEnterDesktopModeAnimation() throws Throwable {
-        final int transitionType = Transitions.TRANSIT_ENTER_DESKTOP_MODE;
+        final int transitionType = Transitions.TRANSIT_FINALIZE_MOVE_TO_DESKTOP_MODE;
         final int taskId = 1;
         WindowContainerTransaction wct = new WindowContainerTransaction();
         doReturn(mToken).when(mTransitions)
                 .startTransition(transitionType, wct, mEnterDesktopTaskTransitionHandler);
-        mEnterDesktopTaskTransitionHandler.startTransition(transitionType, wct, null);
+        mEnterDesktopTaskTransitionHandler.finalizeMoveToDesktop(wct, null);
 
         TransitionInfo.Change change =
                 createChange(WindowManager.TRANSIT_CHANGE, taskId, WINDOWING_MODE_FREEFORM);
         change.setEndAbsBounds(new Rect(0, 0, 1, 1));
-        TransitionInfo info = createTransitionInfo(Transitions.TRANSIT_ENTER_DESKTOP_MODE, change);
+        TransitionInfo info = createTransitionInfo(
+                Transitions.TRANSIT_FINALIZE_MOVE_TO_DESKTOP_MODE, change);
 
         runOnUiThread(() -> {
             try {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/MockToken.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/MockToken.java
deleted file mode 100644
index 09d474d..0000000
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/MockToken.java
+++ /dev/null
@@ -1,40 +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.wm.shell.desktopmode;
-
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import android.os.IBinder;
-import android.window.WindowContainerToken;
-
-/**
- * {@link WindowContainerToken} wrapper that supports a mock binder
- */
-class MockToken {
-    private final WindowContainerToken mToken;
-
-    MockToken() {
-        mToken = mock(WindowContainerToken.class);
-        IBinder binder = mock(IBinder.class);
-        when(mToken.asBinder()).thenReturn(binder);
-    }
-
-    WindowContainerToken token() {
-        return mToken;
-    }
-}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java
index 7c1da35..527dc01 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java
@@ -133,8 +133,7 @@
         mPortraitDisplayLayout = new DisplayLayout(info2, res, false, false);
         mInsets = Insets.of(0, 0, 0, 0);
 
-        mPolicy = spy(new DragAndDropPolicy(
-                mContext, mActivityTaskManager, mSplitScreenStarter, mSplitScreenStarter));
+        mPolicy = spy(new DragAndDropPolicy(mContext, mSplitScreenStarter, mSplitScreenStarter));
         mActivityClipData = createClipData(MIMETYPE_APPLICATION_ACTIVITY);
         mNonResizeableActivityClipData = createClipData(MIMETYPE_APPLICATION_ACTIVITY);
         setClipDataResizeable(mNonResizeableActivityClipData, false);
@@ -206,7 +205,10 @@
     @Test
     public void testDragAppOverFullscreenHome_expectOnlyFullscreenTarget() {
         setRunningTask(mHomeTask);
-        mPolicy.start(mLandscapeDisplayLayout, mActivityClipData, mLoggerSessionId);
+        DragSession dragSession = new DragSession(mContext, mActivityTaskManager,
+                mLandscapeDisplayLayout, mActivityClipData);
+        dragSession.update();
+        mPolicy.start(dragSession, mLoggerSessionId);
         ArrayList<Target> targets = assertExactTargetTypes(
                 mPolicy.getTargets(mInsets), TYPE_FULLSCREEN);
 
@@ -218,7 +220,10 @@
     @Test
     public void testDragAppOverFullscreenApp_expectSplitScreenTargets() {
         setRunningTask(mFullscreenAppTask);
-        mPolicy.start(mLandscapeDisplayLayout, mActivityClipData, mLoggerSessionId);
+        DragSession dragSession = new DragSession(mContext, mActivityTaskManager,
+                mLandscapeDisplayLayout, mActivityClipData);
+        dragSession.update();
+        mPolicy.start(dragSession, mLoggerSessionId);
         ArrayList<Target> targets = assertExactTargetTypes(
                 mPolicy.getTargets(mInsets), TYPE_SPLIT_LEFT, TYPE_SPLIT_RIGHT);
 
@@ -235,7 +240,10 @@
     @Test
     public void testDragAppOverFullscreenAppPhone_expectVerticalSplitScreenTargets() {
         setRunningTask(mFullscreenAppTask);
-        mPolicy.start(mPortraitDisplayLayout, mActivityClipData, mLoggerSessionId);
+        DragSession dragSession = new DragSession(mContext, mActivityTaskManager,
+                mPortraitDisplayLayout, mActivityClipData);
+        dragSession.update();
+        mPolicy.start(dragSession, mLoggerSessionId);
         ArrayList<Target> targets = assertExactTargetTypes(
                 mPolicy.getTargets(mInsets), TYPE_SPLIT_TOP, TYPE_SPLIT_BOTTOM);
 
@@ -252,7 +260,10 @@
     @Test
     public void testTargetHitRects() {
         setRunningTask(mFullscreenAppTask);
-        mPolicy.start(mLandscapeDisplayLayout, mActivityClipData, mLoggerSessionId);
+        DragSession dragSession = new DragSession(mContext, mActivityTaskManager,
+                mLandscapeDisplayLayout, mActivityClipData);
+        dragSession.update();
+        mPolicy.start(dragSession, mLoggerSessionId);
         ArrayList<Target> targets = mPolicy.getTargets(mInsets);
         for (Target t : targets) {
             assertTrue(mPolicy.getTargetAtLocation(t.hitRegion.left, t.hitRegion.top) == t);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipSizeSpecHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipSizeSpecHandlerTest.java
index 1379aed..528c23c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipSizeSpecHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipSizeSpecHandlerTest.java
@@ -110,17 +110,17 @@
         sExpectedDefaultSizes.put(16f / 9, new Size(600, 338));
         sExpectedMinSizes.put(16f / 9, new Size(501, 282));
 
-        sExpectedMaxSizes.put(4f / 3, new Size(892, 669));
-        sExpectedDefaultSizes.put(4f / 3, new Size(535, 401));
+        sExpectedMaxSizes.put(4f / 3, new Size(893, 670));
+        sExpectedDefaultSizes.put(4f / 3, new Size(536, 402));
         sExpectedMinSizes.put(4f / 3, new Size(447, 335));
 
-        sExpectedMaxSizes.put(3f / 4, new Size(669, 892));
-        sExpectedDefaultSizes.put(3f / 4, new Size(401, 535));
+        sExpectedMaxSizes.put(3f / 4, new Size(670, 893));
+        sExpectedDefaultSizes.put(3f / 4, new Size(402, 536));
         sExpectedMinSizes.put(3f / 4, new Size(335, 447));
 
-        sExpectedMaxSizes.put(9f / 16, new Size(562, 999));
-        sExpectedDefaultSizes.put(9f / 16, new Size(337, 599));
-        sExpectedMinSizes.put(9f / 16, new Size(281, 500));
+        sExpectedMaxSizes.put(9f / 16, new Size(563, 1001));
+        sExpectedDefaultSizes.put(9f / 16, new Size(338, 601));
+        sExpectedMinSizes.put(9f / 16, new Size(282, 501));
     }
 
     private void forEveryTestCaseCheck(Map<Float, Size> expectedSizes,
@@ -192,7 +192,7 @@
         // an initial size with 16:9 aspect ratio
         Size initSize = new Size(600, 337);
 
-        Size expectedSize = new Size(337, 599);
+        Size expectedSize = new Size(338, 601);
         Size actualSize = mPipSizeSpecHandler.getSizeForAspectRatio(initSize, 9f / 16);
 
         Assert.assertEquals(expectedSize, actualSize);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipActionProviderTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipActionProviderTest.java
index 02e6b8c..ec84d7e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipActionProviderTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipActionProviderTest.java
@@ -23,7 +23,9 @@
 import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_FULLSCREEN;
 import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_MOVE;
 
-import static org.junit.Assert.assertTrue;
+import static java.util.Collections.EMPTY_LIST;
+
+import static org.junit.Assert.assertEquals;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -34,7 +36,6 @@
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
-import android.util.Log;
 
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.pip.PipMediaController;
@@ -46,7 +47,9 @@
 import org.mockito.MockitoAnnotations;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
+import java.util.stream.Collectors;
 
 /**
  * Unit tests for {@link TvPipActionsProvider}
@@ -69,35 +72,38 @@
     @Mock
     private PendingIntent mMockPendingIntent;
 
-    private RemoteAction createRemoteAction(int identifier) {
+    private int mNumberOfRemoteActionsCreated = 0;
+
+    private RemoteAction createRemoteAction() {
+        final int identifier = mNumberOfRemoteActionsCreated++;
         return new RemoteAction(mMockIcon, "" + identifier, "" + identifier, mMockPendingIntent);
     }
 
     private List<RemoteAction> createRemoteActions(int numberOfActions) {
         List<RemoteAction> actions = new ArrayList<>();
         for (int i = 0; i < numberOfActions; i++) {
-            actions.add(createRemoteAction(i));
+            actions.add(createRemoteAction());
         }
         return actions;
     }
 
-    private boolean checkActionsMatch(List<TvPipAction> actions, int[] actionTypes) {
-        for (int i = 0; i < actions.size(); i++) {
-            int type = actions.get(i).getActionType();
-            if (type != actionTypes[i]) {
-                Log.e(TAG, "Action at index " + i + ": found " + type
-                        + ", expected " + actionTypes[i]);
-                return false;
-            }
-        }
-        return true;
+    private void assertActionTypes(List<Integer> expected, List<Integer> actual) {
+        assertEquals(getActionTypesStrings(expected), getActionTypesStrings(actual));
+    }
+
+    private static List<String> getActionTypesStrings(List<Integer> actionTypes) {
+        return actionTypes.stream().map(a -> TvPipAction.getActionTypeString(a))
+                .collect(Collectors.toList());
+    }
+
+    private List<Integer> getActionsTypes() {
+        return mActionsProvider.getActionsList().stream().map(a -> a.getActionType())
+                .collect(Collectors.toList());
     }
 
     @Before
     public void setUp() {
-        if (!isTelevision()) {
-            return;
-        }
+        assumeTelevision();
         MockitoAnnotations.initMocks(this);
         mActionsProvider = new TvPipActionsProvider(mContext, mMockPipMediaController,
                 mMockSystemActionsHandler);
@@ -105,57 +111,51 @@
 
     @Test
     public void defaultSystemActions_regularPip() {
-        assumeTelevision();
-        mActionsProvider.updateExpansionEnabled(false);
-        assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
-                new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE}));
+        assertActionTypes(Arrays.asList(ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE),
+                          getActionsTypes());
     }
 
     @Test
     public void defaultSystemActions_expandedPip() {
-        assumeTelevision();
         mActionsProvider.updateExpansionEnabled(true);
-        assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
-                new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE, ACTION_EXPAND_COLLAPSE}));
+        assertActionTypes(
+                Arrays.asList(ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE, ACTION_EXPAND_COLLAPSE),
+                getActionsTypes());
     }
 
     @Test
     public void expandedPip_enableExpansion_enable() {
-        assumeTelevision();
         // PiP has expanded PiP disabled.
-        mActionsProvider.updateExpansionEnabled(false);
-
         mActionsProvider.addListener(mMockListener);
         mActionsProvider.updateExpansionEnabled(true);
 
-        assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
-                new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE, ACTION_EXPAND_COLLAPSE}));
+        assertActionTypes(
+                Arrays.asList(ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE, ACTION_EXPAND_COLLAPSE),
+                getActionsTypes());
         verify(mMockListener).onActionsChanged(/* added= */ 1, /* updated= */ 0, /* index= */ 3);
     }
 
     @Test
     public void expandedPip_enableExpansion_disable() {
-        assumeTelevision();
         mActionsProvider.updateExpansionEnabled(true);
 
         mActionsProvider.addListener(mMockListener);
         mActionsProvider.updateExpansionEnabled(false);
 
-        assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
-                new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE}));
+        assertActionTypes(
+                Arrays.asList(ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE),
+                getActionsTypes());
         verify(mMockListener).onActionsChanged(/* added= */ -1, /* updated= */ 0, /* index= */ 3);
     }
 
     @Test
     public void expandedPip_enableExpansion_AlreadyEnabled() {
-        assumeTelevision();
-        mActionsProvider.updateExpansionEnabled(true);
-
         mActionsProvider.addListener(mMockListener);
         mActionsProvider.updateExpansionEnabled(true);
 
-        assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
-                new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE, ACTION_EXPAND_COLLAPSE}));
+        assertActionTypes(
+                Arrays.asList(ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE, ACTION_EXPAND_COLLAPSE),
+                getActionsTypes());
     }
 
     private void check_expandedPip_updateExpansionState(
@@ -167,8 +167,9 @@
         mActionsProvider.addListener(mMockListener);
         mActionsProvider.updatePipExpansionState(endExpansion);
 
-        assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
-                new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE, ACTION_EXPAND_COLLAPSE}));
+        assertActionTypes(
+                Arrays.asList(ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE, ACTION_EXPAND_COLLAPSE),
+                getActionsTypes());
 
         if (updateExpected) {
             verify(mMockListener).onActionsChanged(0, 1, 3);
@@ -180,7 +181,6 @@
 
     @Test
     public void expandedPip_toggleExpansion_collapse() {
-        assumeTelevision();
         check_expandedPip_updateExpansionState(
                 /* startExpansion= */ true,
                 /* endExpansion= */ false,
@@ -189,7 +189,6 @@
 
     @Test
     public void expandedPip_toggleExpansion_expand() {
-        assumeTelevision();
         check_expandedPip_updateExpansionState(
                 /* startExpansion= */ false,
                 /* endExpansion= */ true,
@@ -198,7 +197,6 @@
 
     @Test
     public void expandedPiP_updateExpansionState_alreadyExpanded() {
-        assumeTelevision();
         check_expandedPip_updateExpansionState(
                 /* startExpansion= */ true,
                 /* endExpansion= */ true,
@@ -207,7 +205,6 @@
 
     @Test
     public void expandedPiP_updateExpansionState_alreadyCollapsed() {
-        assumeTelevision();
         check_expandedPip_updateExpansionState(
                 /* startExpansion= */ false,
                 /* endExpansion= */ false,
@@ -216,8 +213,6 @@
 
     @Test
     public void regularPiP_updateExpansionState_setCollapsed() {
-        assumeTelevision();
-        mActionsProvider.updateExpansionEnabled(false);
         mActionsProvider.updatePipExpansionState(/* expanded= */ false);
 
         mActionsProvider.addListener(mMockListener);
@@ -229,153 +224,207 @@
 
     @Test
     public void customActions_added() {
-        assumeTelevision();
-        mActionsProvider.updateExpansionEnabled(false);
         mActionsProvider.addListener(mMockListener);
 
         mActionsProvider.setAppActions(createRemoteActions(2), null);
 
-        assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
-                new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM,
-                        ACTION_MOVE}));
+        assertActionTypes(
+                Arrays.asList(ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM,
+                    ACTION_MOVE),
+                getActionsTypes());
         verify(mMockListener).onActionsChanged(/* added= */ 2, /* updated= */ 0, /* index= */ 2);
     }
 
     @Test
     public void customActions_replacedMore() {
-        assumeTelevision();
-        mActionsProvider.updateExpansionEnabled(false);
         mActionsProvider.setAppActions(createRemoteActions(2), null);
 
         mActionsProvider.addListener(mMockListener);
         mActionsProvider.setAppActions(createRemoteActions(3), null);
 
-        assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
-                new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM,
-                        ACTION_CUSTOM, ACTION_MOVE}));
+        assertActionTypes(
+                Arrays.asList(ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM,
+                    ACTION_CUSTOM, ACTION_MOVE),
+                getActionsTypes());
         verify(mMockListener).onActionsChanged(/* added= */ 1, /* updated= */ 2, /* index= */ 2);
     }
 
     @Test
     public void customActions_replacedLess() {
-        assumeTelevision();
-        mActionsProvider.updateExpansionEnabled(false);
         mActionsProvider.setAppActions(createRemoteActions(2), null);
 
         mActionsProvider.addListener(mMockListener);
-        mActionsProvider.setAppActions(createRemoteActions(0), null);
+        mActionsProvider.setAppActions(EMPTY_LIST, null);
 
-        assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
-                new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE}));
+        assertActionTypes(
+                Arrays.asList(ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE),
+                getActionsTypes());
         verify(mMockListener).onActionsChanged(/* added= */ -2, /* updated= */ 0, /* index= */ 2);
     }
 
     @Test
     public void customCloseAdded() {
-        assumeTelevision();
-        mActionsProvider.updateExpansionEnabled(false);
-
         List<RemoteAction> customActions = new ArrayList<>();
         mActionsProvider.setAppActions(customActions, null);
 
         mActionsProvider.addListener(mMockListener);
-        mActionsProvider.setAppActions(customActions, createRemoteAction(0));
+        mActionsProvider.setAppActions(customActions, createRemoteAction());
 
-        assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
-                new int[]{ACTION_FULLSCREEN, ACTION_CUSTOM_CLOSE, ACTION_MOVE}));
+        assertActionTypes(
+                Arrays.asList(ACTION_FULLSCREEN, ACTION_CUSTOM_CLOSE, ACTION_MOVE),
+                getActionsTypes());
         verify(mMockListener).onActionsChanged(/* added= */ 0, /* updated= */ 1, /* index= */ 1);
     }
 
     @Test
     public void customClose_matchesOtherCustomAction() {
-        assumeTelevision();
-        mActionsProvider.updateExpansionEnabled(false);
-
         List<RemoteAction> customActions = createRemoteActions(2);
-        RemoteAction customClose = createRemoteAction(/* id= */ 10);
+        RemoteAction customClose = createRemoteAction();
         customActions.add(customClose);
 
         mActionsProvider.addListener(mMockListener);
         mActionsProvider.setAppActions(customActions, customClose);
 
-        assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
-                new int[]{ACTION_FULLSCREEN, ACTION_CUSTOM_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM,
-                        ACTION_MOVE}));
+        assertActionTypes(
+                Arrays.asList(ACTION_FULLSCREEN, ACTION_CUSTOM_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM,
+                    ACTION_MOVE),
+                getActionsTypes());
         verify(mMockListener).onActionsChanged(/* added= */ 0, /* updated= */ 1, /* index= */ 1);
         verify(mMockListener).onActionsChanged(/* added= */ 2, /* updated= */ 0, /* index= */ 2);
     }
 
     @Test
     public void mediaActions_added_whileCustomActionsExist() {
-        assumeTelevision();
-        mActionsProvider.updateExpansionEnabled(false);
         mActionsProvider.setAppActions(createRemoteActions(2), null);
 
         mActionsProvider.addListener(mMockListener);
         mActionsProvider.onMediaActionsChanged(createRemoteActions(3));
 
-        assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
-                new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM,
-                        ACTION_MOVE}));
+        assertActionTypes(
+                Arrays.asList(ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM,
+                    ACTION_MOVE),
+                getActionsTypes());
         verify(mMockListener, times(0)).onActionsChanged(anyInt(), anyInt(), anyInt());
     }
 
     @Test
     public void customActions_removed_whileMediaActionsExist() {
-        assumeTelevision();
-        mActionsProvider.updateExpansionEnabled(false);
         mActionsProvider.onMediaActionsChanged(createRemoteActions(2));
         mActionsProvider.setAppActions(createRemoteActions(3), null);
 
-        mActionsProvider.addListener(mMockListener);
-        mActionsProvider.setAppActions(createRemoteActions(0), null);
+        assertActionTypes(
+                Arrays.asList(ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM,
+                    ACTION_CUSTOM, ACTION_MOVE),
+                getActionsTypes());
 
-        assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
-                new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM,
-                        ACTION_MOVE}));
+        mActionsProvider.addListener(mMockListener);
+        mActionsProvider.setAppActions(EMPTY_LIST, null);
+
+        assertActionTypes(
+                Arrays.asList(ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM,
+                    ACTION_MOVE),
+                getActionsTypes());
         verify(mMockListener).onActionsChanged(/* added= */ -1, /* updated= */ 2, /* index= */ 2);
     }
 
     @Test
     public void customCloseOnly_mediaActionsShowing() {
-        assumeTelevision();
-        mActionsProvider.updateExpansionEnabled(false);
         mActionsProvider.onMediaActionsChanged(createRemoteActions(2));
 
         mActionsProvider.addListener(mMockListener);
-        mActionsProvider.setAppActions(createRemoteActions(0), createRemoteAction(5));
+        mActionsProvider.setAppActions(EMPTY_LIST, createRemoteAction());
 
-        assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
-                new int[]{ACTION_FULLSCREEN, ACTION_CUSTOM_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM,
-                        ACTION_MOVE}));
+        assertActionTypes(
+                Arrays.asList(ACTION_FULLSCREEN, ACTION_CUSTOM_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM,
+                    ACTION_MOVE),
+                getActionsTypes());
         verify(mMockListener).onActionsChanged(/* added= */ 0, /* updated= */ 1, /* index= */ 1);
     }
 
     @Test
     public void customActions_showDisabledActions() {
-        assumeTelevision();
-        mActionsProvider.updateExpansionEnabled(false);
-
         List<RemoteAction> customActions = createRemoteActions(2);
         customActions.get(0).setEnabled(false);
         mActionsProvider.setAppActions(customActions, null);
 
-        assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
-                new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM,
-                        ACTION_MOVE}));
+        assertActionTypes(
+                Arrays.asList(ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM,
+                    ACTION_MOVE),
+                getActionsTypes());
     }
 
     @Test
     public void mediaActions_hideDisabledActions() {
-        assumeTelevision();
-        mActionsProvider.updateExpansionEnabled(false);
-
         List<RemoteAction> customActions = createRemoteActions(2);
         customActions.get(0).setEnabled(false);
         mActionsProvider.onMediaActionsChanged(customActions);
 
-        assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
-                new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_CUSTOM, ACTION_MOVE}));
+        assertActionTypes(
+                Arrays.asList(ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_CUSTOM, ACTION_MOVE),
+                getActionsTypes());
+    }
+
+    @Test
+    public void reset_mediaActions() {
+        List<RemoteAction> customActions = createRemoteActions(2);
+        customActions.get(0).setEnabled(false);
+        mActionsProvider.onMediaActionsChanged(customActions);
+
+        assertActionTypes(
+                Arrays.asList(ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_CUSTOM, ACTION_MOVE),
+                getActionsTypes());
+
+        mActionsProvider.reset();
+        assertActionTypes(
+                Arrays.asList(ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE),
+                getActionsTypes());
+    }
+
+    @Test
+    public void reset_customActions() {
+        List<RemoteAction> customActions = createRemoteActions(2);
+        customActions.get(0).setEnabled(false);
+        mActionsProvider.setAppActions(customActions, null);
+
+        assertActionTypes(
+                Arrays.asList(ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM,
+                    ACTION_MOVE),
+                getActionsTypes());
+
+        mActionsProvider.reset();
+        assertActionTypes(
+                Arrays.asList(ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE),
+                getActionsTypes());
+    }
+
+    @Test
+    public void reset_customClose() {
+        mActionsProvider.setAppActions(EMPTY_LIST, createRemoteAction());
+
+        assertActionTypes(
+                Arrays.asList(ACTION_FULLSCREEN, ACTION_CUSTOM_CLOSE, ACTION_MOVE),
+                getActionsTypes());
+
+        mActionsProvider.reset();
+        assertActionTypes(
+                Arrays.asList(ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE),
+                getActionsTypes());
+    }
+
+    @Test
+    public void reset_All() {
+        mActionsProvider.setAppActions(createRemoteActions(2), createRemoteAction());
+        mActionsProvider.onMediaActionsChanged(createRemoteActions(3));
+
+        assertActionTypes(
+                Arrays.asList(ACTION_FULLSCREEN, ACTION_CUSTOM_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM,
+                    ACTION_MOVE),
+                getActionsTypes());
+
+        mActionsProvider.reset();
+        assertActionTypes(
+                Arrays.asList(ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE),
+                getActionsTypes());
     }
 
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipGravityTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipGravityTest.java
index f9b7723..91ff3cb 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipGravityTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipGravityTest.java
@@ -52,9 +52,8 @@
 
     @Before
     public void setUp() {
-        if (!isTelevision()) {
-            return;
-        }
+        assumeTelevision();
+
         MockitoAnnotations.initMocks(this);
         mPipDisplayLayoutState = new PipDisplayLayoutState(mContext);
         mPipSizeSpecHandler = new PipSizeSpecHandler(mContext, mPipDisplayLayoutState);
@@ -100,20 +99,22 @@
 
     @Test
     public void regularPip_defaultGravity() {
-        assumeTelevision();
         checkGravity(mTvPipBoundsState.getDefaultGravity(), Gravity.RIGHT | Gravity.BOTTOM);
     }
 
     @Test
+    public void regularPip_defaultTvPipGravity() {
+        checkGravity(mTvPipBoundsState.getTvPipGravity(), Gravity.RIGHT | Gravity.BOTTOM);
+    }
+
+    @Test
     public void regularPip_defaultGravity_RTL() {
-        assumeTelevision();
         setRTL(true);
         checkGravity(mTvPipBoundsState.getDefaultGravity(), Gravity.LEFT | Gravity.BOTTOM);
     }
 
     @Test
     public void updateGravity_expand_vertical() {
-        assumeTelevision();
         // Vertical expanded PiP.
         mTvPipBoundsState.setDesiredTvExpandedAspectRatio(VERTICAL_EXPANDED_ASPECT_RATIO, true);
 
@@ -129,7 +130,6 @@
 
     @Test
     public void updateGravity_expand_horizontal() {
-        assumeTelevision();
         // Horizontal expanded PiP.
         mTvPipBoundsState.setDesiredTvExpandedAspectRatio(HORIZONTAL_EXPANDED_ASPECT_RATIO, true);
 
@@ -145,7 +145,6 @@
 
     @Test
     public void updateGravity_collapse() {
-        assumeTelevision();
         // Vertical expansion
         mTvPipBoundsState.setDesiredTvExpandedAspectRatio(VERTICAL_EXPANDED_ASPECT_RATIO, true);
         assertGravityAfterCollapse(Gravity.CENTER_VERTICAL | Gravity.RIGHT,
@@ -163,7 +162,6 @@
 
     @Test
     public void updateGravity_collapse_RTL() {
-        assumeTelevision();
         setRTL(true);
 
         // Horizontal expansion
@@ -176,7 +174,6 @@
 
     @Test
     public void updateGravity_expand_collapse() {
-        assumeTelevision();
         // Vertical expanded PiP.
         mTvPipBoundsState.setDesiredTvExpandedAspectRatio(VERTICAL_EXPANDED_ASPECT_RATIO, true);
 
@@ -196,7 +193,6 @@
 
     @Test
     public void updateGravity_expand_move_collapse() {
-        assumeTelevision();
         // Vertical expanded PiP.
         mTvPipBoundsState.setDesiredTvExpandedAspectRatio(VERTICAL_EXPANDED_ASPECT_RATIO, true);
         expandMoveCollapseCheck(Gravity.TOP | Gravity.RIGHT, KEYCODE_DPAD_LEFT,
@@ -229,7 +225,6 @@
 
     @Test
     public void updateGravity_move_regular_valid() {
-        assumeTelevision();
         mTvPipBoundsState.setTvPipGravity(Gravity.BOTTOM | Gravity.RIGHT);
         // clockwise
         moveAndCheckGravity(KEYCODE_DPAD_LEFT, Gravity.BOTTOM | Gravity.LEFT, true);
@@ -245,7 +240,6 @@
 
     @Test
     public void updateGravity_move_expanded_valid() {
-        assumeTelevision();
         mTvPipBoundsState.setTvPipExpanded(true);
 
         // Vertical expanded PiP.
@@ -263,7 +257,6 @@
 
     @Test
     public void updateGravity_move_regular_invalid() {
-        assumeTelevision();
         int gravity = Gravity.BOTTOM | Gravity.RIGHT;
         mTvPipBoundsState.setTvPipGravity(gravity);
         moveAndCheckGravity(KEYCODE_DPAD_DOWN, gravity, false);
@@ -287,7 +280,6 @@
 
     @Test
     public void updateGravity_move_expanded_invalid() {
-        assumeTelevision();
         mTvPipBoundsState.setTvPipExpanded(true);
 
         // Vertical expanded PiP.
@@ -317,7 +309,6 @@
 
     @Test
     public void previousCollapsedGravity_defaultValue() {
-        assumeTelevision();
         assertEquals(mTvPipBoundsState.getTvPipPreviousCollapsedGravity(),
                 mTvPipBoundsState.getDefaultGravity());
         setRTL(true);
@@ -327,7 +318,6 @@
 
     @Test
     public void previousCollapsedGravity_changes_on_RTL() {
-        assumeTelevision();
         mTvPipBoundsState.setTvPipPreviousCollapsedGravity(Gravity.TOP | Gravity.LEFT);
         setRTL(true);
         assertEquals(mTvPipBoundsState.getTvPipPreviousCollapsedGravity(),
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/MainStageTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/MainStageTests.java
index 68cb57c..b1befc4 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/MainStageTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/MainStageTests.java
@@ -41,6 +41,8 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.Optional;
+
 /** Tests for {@link MainStage} */
 @SmallTest
 @RunWith(AndroidJUnit4.class)
@@ -61,7 +63,7 @@
         MockitoAnnotations.initMocks(this);
         mRootTaskInfo = new TestRunningTaskInfoBuilder().build();
         mMainStage = new MainStage(mContext, mTaskOrganizer, DEFAULT_DISPLAY, mCallbacks,
-                mSyncQueue, mSurfaceSession, mIconProvider);
+                mSyncQueue, mSurfaceSession, mIconProvider, Optional.empty());
         mMainStage.onTaskAppeared(mRootTaskInfo, mRootLeash);
     }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SideStageTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SideStageTests.java
index 3b42a48..549bd3f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SideStageTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SideStageTests.java
@@ -46,6 +46,8 @@
 import org.mockito.MockitoAnnotations;
 import org.mockito.Spy;
 
+import java.util.Optional;
+
 /** Tests for {@link SideStage} */
 @SmallTest
 @RunWith(AndroidJUnit4.class)
@@ -66,7 +68,7 @@
         MockitoAnnotations.initMocks(this);
         mRootTask = new TestRunningTaskInfoBuilder().build();
         mSideStage = new SideStage(mContext, mTaskOrganizer, DEFAULT_DISPLAY, mCallbacks,
-                mSyncQueue, mSurfaceSession, mIconProvider);
+                mSyncQueue, mSurfaceSession, mIconProvider, Optional.empty());
         mSideStage.onTaskAppeared(mRootTask, mRootLeash);
     }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
index fb17d87..e8a1e91 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
@@ -61,6 +61,7 @@
 import com.android.wm.shell.common.DisplayImeController;
 import com.android.wm.shell.common.DisplayInsetsController;
 import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.LaunchAdjacentController;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.TransactionPool;
@@ -71,6 +72,7 @@
 import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.sysui.ShellSharedConstants;
 import com.android.wm.shell.transition.Transitions;
+import com.android.wm.shell.windowdecor.WindowDecorViewModel;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -102,6 +104,8 @@
     @Mock IconProvider mIconProvider;
     @Mock StageCoordinator mStageCoordinator;
     @Mock RecentTasksController mRecentTasks;
+    @Mock LaunchAdjacentController mLaunchAdjacentController;
+    @Mock WindowDecorViewModel mWindowDecorViewModel;
     @Captor ArgumentCaptor<Intent> mIntentCaptor;
 
     private ShellController mShellController;
@@ -117,7 +121,8 @@
                 mShellCommandHandler, mShellController, mTaskOrganizer, mSyncQueue,
                 mRootTDAOrganizer, mDisplayController, mDisplayImeController,
                 mDisplayInsetsController, mDragAndDropController, mTransitions, mTransactionPool,
-                mIconProvider, mRecentTasks, mMainExecutor, mStageCoordinator));
+                mIconProvider, mRecentTasks, mLaunchAdjacentController, mWindowDecorViewModel,
+                mMainExecutor, mStageCoordinator));
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java
index 4e446c6..a3009a5 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java
@@ -31,12 +31,14 @@
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.DisplayImeController;
 import com.android.wm.shell.common.DisplayInsetsController;
+import com.android.wm.shell.common.LaunchAdjacentController;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.TransactionPool;
 import com.android.wm.shell.common.split.SplitLayout;
 import com.android.wm.shell.recents.RecentTasksController;
 import com.android.wm.shell.transition.Transitions;
+import com.android.wm.shell.windowdecor.WindowDecorViewModel;
 
 import java.util.Optional;
 
@@ -76,10 +78,13 @@
                 DisplayInsetsController insetsController, SplitLayout splitLayout,
                 Transitions transitions, TransactionPool transactionPool,
                 ShellExecutor mainExecutor,
-                Optional<RecentTasksController> recentTasks) {
+                Optional<RecentTasksController> recentTasks,
+                LaunchAdjacentController launchAdjacentController,
+                Optional<WindowDecorViewModel> windowDecorViewModel) {
             super(context, displayId, syncQueue, taskOrganizer, mainStage,
                     sideStage, displayController, imeController, insetsController, splitLayout,
-                    transitions, transactionPool, mainExecutor, recentTasks);
+                    transitions, transactionPool, mainExecutor, recentTasks,
+                    launchAdjacentController, windowDecorViewModel);
 
             // Prepare root task for testing.
             mRootTask = new TestRunningTaskInfoBuilder().build();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
index 44c76de..5efd9ad 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
@@ -70,6 +70,7 @@
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.DisplayImeController;
 import com.android.wm.shell.common.DisplayInsetsController;
+import com.android.wm.shell.common.LaunchAdjacentController;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.TransactionPool;
@@ -77,6 +78,7 @@
 import com.android.wm.shell.common.split.SplitLayout;
 import com.android.wm.shell.transition.DefaultMixedHandler;
 import com.android.wm.shell.transition.Transitions;
+import com.android.wm.shell.windowdecor.WindowDecorViewModel;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -101,7 +103,9 @@
     @Mock private Transitions mTransitions;
     @Mock private SurfaceSession mSurfaceSession;
     @Mock private IconProvider mIconProvider;
+    @Mock private WindowDecorViewModel mWindowDecorViewModel;
     @Mock private ShellExecutor mMainExecutor;
+    @Mock private LaunchAdjacentController mLaunchAdjacentController;
     @Mock private DefaultMixedHandler mMixedHandler;
     private SplitLayout mSplitLayout;
     private MainStage mMainStage;
@@ -123,16 +127,17 @@
         mSplitLayout = SplitTestUtils.createMockSplitLayout();
         mMainStage = spy(new MainStage(mContext, mTaskOrganizer, DEFAULT_DISPLAY, mock(
                 StageTaskListener.StageListenerCallbacks.class), mSyncQueue, mSurfaceSession,
-                mIconProvider));
+                mIconProvider, Optional.of(mWindowDecorViewModel)));
         mMainStage.onTaskAppeared(new TestRunningTaskInfoBuilder().build(), createMockSurface());
         mSideStage = spy(new SideStage(mContext, mTaskOrganizer, DEFAULT_DISPLAY, mock(
                 StageTaskListener.StageListenerCallbacks.class), mSyncQueue, mSurfaceSession,
-                mIconProvider));
+                mIconProvider, Optional.of(mWindowDecorViewModel)));
         mSideStage.onTaskAppeared(new TestRunningTaskInfoBuilder().build(), createMockSurface());
         mStageCoordinator = new SplitTestUtils.TestStageCoordinator(mContext, DEFAULT_DISPLAY,
                 mSyncQueue, mTaskOrganizer, mMainStage, mSideStage, mDisplayController,
                 mDisplayImeController, mDisplayInsetsController, mSplitLayout, mTransitions,
-                mTransactionPool, mMainExecutor, Optional.empty());
+                mTransactionPool, mMainExecutor, Optional.empty(),
+                mLaunchAdjacentController, Optional.empty());
         mStageCoordinator.setMixedHandler(mMixedHandler);
         mSplitScreenTransitions = mStageCoordinator.getSplitTransitions();
         doAnswer((Answer<IBinder>) invocation -> mock(IBinder.class))
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
index 2dcdc74..cc4db22 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
@@ -65,6 +65,7 @@
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.DisplayImeController;
 import com.android.wm.shell.common.DisplayInsetsController;
+import com.android.wm.shell.common.LaunchAdjacentController;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.TransactionPool;
@@ -107,6 +108,8 @@
     private DisplayInsetsController mDisplayInsetsController;
     @Mock
     private TransactionPool mTransactionPool;
+    @Mock
+    private LaunchAdjacentController mLaunchAdjacentController;
 
     private final Rect mBounds1 = new Rect(10, 20, 30, 40);
     private final Rect mBounds2 = new Rect(5, 10, 15, 20);
@@ -130,7 +133,7 @@
         mStageCoordinator = spy(new StageCoordinator(mContext, DEFAULT_DISPLAY, mSyncQueue,
                 mTaskOrganizer, mMainStage, mSideStage, mDisplayController, mDisplayImeController,
                 mDisplayInsetsController, mSplitLayout, mTransitions, mTransactionPool,
-                mMainExecutor, Optional.empty()));
+                mMainExecutor, Optional.empty(), mLaunchAdjacentController, Optional.empty()));
         mDividerLeash = new SurfaceControl.Builder(mSurfaceSession).setName("fakeDivider").build();
 
         when(mSplitLayout.getBounds1()).thenReturn(mBounds1);
@@ -344,13 +347,20 @@
     }
 
     @Test
-    public void testExitSplitScreenAfterFolded() {
-        when(mMainStage.isActive()).thenReturn(true);
+    public void testExitSplitScreenAfterFoldedAndWakeUp() {
         when(mMainStage.isFocused()).thenReturn(true);
         when(mMainStage.getTopVisibleChildTaskId()).thenReturn(INVALID_TASK_ID);
+        mSideStage.mRootTaskInfo = new TestRunningTaskInfoBuilder().setVisible(true).build();
+        mMainStage.mRootTaskInfo = new TestRunningTaskInfoBuilder().setVisible(true).build();
+        when(mStageCoordinator.isSplitActive()).thenReturn(true);
+        when(mStageCoordinator.isSplitScreenVisible()).thenReturn(true);
 
         mStageCoordinator.onFoldedStateChanged(true);
 
+        assertEquals(mStageCoordinator.mTopStageAfterFoldDismiss, STAGE_TYPE_MAIN);
+
+        mStageCoordinator.onFinishedWakingUp();
+
         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
             verify(mTaskOrganizer).startNewTransition(eq(TRANSIT_SPLIT_DISMISS), notNull());
         } else {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java
index 1a1bebd..946a7ef 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java
@@ -43,6 +43,7 @@
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.TestRunningTaskInfoBuilder;
 import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.windowdecor.WindowDecorViewModel;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -52,6 +53,8 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.Optional;
+
 /**
  * Tests for {@link StageTaskListener}
  * Build/Install/Run:
@@ -71,6 +74,8 @@
     private SyncTransactionQueue mSyncQueue;
     @Mock
     private IconProvider mIconProvider;
+    @Mock
+    private WindowDecorViewModel mWindowDecorViewModel;
     @Captor
     private ArgumentCaptor<SyncTransactionQueue.TransactionRunnable> mRunnableCaptor;
     private SurfaceSession mSurfaceSession = new SurfaceSession();
@@ -89,7 +94,8 @@
                 mCallbacks,
                 mSyncQueue,
                 mSurfaceSession,
-                mIconProvider);
+                mIconProvider,
+                Optional.of(mWindowDecorViewModel));
         mRootTask = new TestRunningTaskInfoBuilder().build();
         mRootTask.parentTaskId = INVALID_TASK_ID;
         mSurfaceControl = new SurfaceControl.Builder(mSurfaceSession).setName("test").build();
@@ -155,7 +161,7 @@
         childTask.supportsMultiWindow = false;
 
         mStageTaskListener.onTaskInfoChanged(childTask);
-        verify(mCallbacks).onNoLongerSupportMultiWindow();
+        verify(mCallbacks).onNoLongerSupportMultiWindow(childTask);
     }
 
     @Test
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 1b38956..50435a0 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.Insets;
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.testing.AndroidTestingRunner;
@@ -563,4 +564,47 @@
         mTaskViewTaskController.onTaskAppeared(mTaskInfo, mLeash);
         verify(mTaskViewTaskController, never()).cleanUpPendingTask();
     }
+
+    @Test
+    public void testSetCaptionInsets_noTaskInitially() {
+        assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
+
+        Rect insets = new Rect(0, 400, 0, 0);
+        mTaskView.setCaptionInsets(Insets.of(insets));
+        mTaskView.onComputeInternalInsets(new ViewTreeObserver.InternalInsetsInfo());
+
+        verify(mTaskViewTaskController).applyCaptionInsetsIfNeeded();
+        verify(mOrganizer, never()).applyTransaction(any());
+
+        mTaskView.surfaceCreated(mock(SurfaceHolder.class));
+        reset(mOrganizer);
+        reset(mTaskViewTaskController);
+        WindowContainerTransaction wct = new WindowContainerTransaction();
+        mTaskViewTaskController.prepareOpenAnimation(true /* newTask */,
+                new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mTaskInfo,
+                mLeash, wct);
+        mTaskView.onComputeInternalInsets(new ViewTreeObserver.InternalInsetsInfo());
+
+        verify(mTaskViewTaskController).applyCaptionInsetsIfNeeded();
+        verify(mOrganizer).applyTransaction(any());
+    }
+
+    @Test
+    public void testSetCaptionInsets_withTask() {
+        assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
+
+        mTaskView.surfaceCreated(mock(SurfaceHolder.class));
+        WindowContainerTransaction wct = new WindowContainerTransaction();
+        mTaskViewTaskController.prepareOpenAnimation(true /* newTask */,
+                new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mTaskInfo,
+                mLeash, wct);
+        reset(mTaskViewTaskController);
+        reset(mOrganizer);
+
+        Rect insets = new Rect(0, 400, 0, 0);
+        mTaskView.setCaptionInsets(Insets.of(insets));
+        mTaskView.onComputeInternalInsets(new ViewTreeObserver.InternalInsetsInfo());
+        verify(mTaskViewTaskController).applyCaptionInsetsIfNeeded();
+        verify(mOrganizer).applyTransaction(any());
+    }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTransitionsTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTransitionsTest.java
index 03ed18c..0504439 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTransitionsTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTransitionsTest.java
@@ -65,12 +65,6 @@
     ActivityManager.RunningTaskInfo mTaskInfo;
     @Mock
     WindowContainerToken mToken;
-    @Mock
-    TaskViewTaskController mTaskViewTaskController2;
-    @Mock
-    ActivityManager.RunningTaskInfo mTaskInfo2;
-    @Mock
-    WindowContainerToken mToken2;
 
     TaskViewTransitions mTaskViewTransitions;
 
@@ -86,16 +80,10 @@
         mTaskInfo.token = mToken;
         mTaskInfo.taskId = 314;
         mTaskInfo.taskDescription = mock(ActivityManager.TaskDescription.class);
-        when(mTaskViewTaskController.getTaskInfo()).thenReturn(mTaskInfo);
-
-        mTaskInfo2 = new ActivityManager.RunningTaskInfo();
-        mTaskInfo2.token = mToken2;
-        mTaskInfo2.taskId = 315;
-        mTaskInfo2.taskDescription = mock(ActivityManager.TaskDescription.class);
-        when(mTaskViewTaskController2.getTaskInfo()).thenReturn(mTaskInfo2);
 
         mTaskViewTransitions = spy(new TaskViewTransitions(mTransitions));
         mTaskViewTransitions.addTaskView(mTaskViewTaskController);
+        when(mTaskViewTaskController.getTaskInfo()).thenReturn(mTaskInfo);
     }
 
     @Test
@@ -138,7 +126,7 @@
     }
 
     @Test
-    public void testSetTaskBounds_taskVisibleWithPendingOpen_noTransaction() {
+    public void testSetTaskBounds_taskVisibleWithPending_noTransaction() {
         assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
 
         mTaskViewTransitions.setTaskViewVisible(mTaskViewTaskController, true);
@@ -154,43 +142,6 @@
     }
 
     @Test
-    public void testSetTaskBounds_taskVisibleWithPendingChange_transition() {
-        assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
-
-        mTaskViewTransitions.setTaskViewVisible(mTaskViewTaskController, true);
-
-        // Consume the pending transition from visibility change
-        TaskViewTransitions.PendingTransition pending =
-                mTaskViewTransitions.findPending(mTaskViewTaskController, TRANSIT_TO_FRONT);
-        assertThat(pending).isNotNull();
-        mTaskViewTransitions.startAnimation(pending.mClaimed,
-                mock(TransitionInfo.class),
-                new SurfaceControl.Transaction(),
-                new SurfaceControl.Transaction(),
-                mock(Transitions.TransitionFinishCallback.class));
-        // Verify it was consumed
-        TaskViewTransitions.PendingTransition checkPending =
-                mTaskViewTransitions.findPending(mTaskViewTaskController, TRANSIT_TO_FRONT);
-        assertThat(checkPending).isNull();
-
-        // Test that set bounds creates a new transition
-        mTaskViewTransitions.setTaskBounds(mTaskViewTaskController,
-                new Rect(0, 0, 100, 100));
-        assertThat(mTaskViewTransitions.findPending(mTaskViewTaskController, TRANSIT_CHANGE))
-                .isNotNull();
-
-        // Test that set bounds again (with different bounds) creates another transition
-        mTaskViewTransitions.setTaskBounds(mTaskViewTaskController,
-                new Rect(0, 0, 300, 200));
-        List<TaskViewTransitions.PendingTransition> pendingList =
-                mTaskViewTransitions.findAllPending(mTaskViewTaskController)
-                        .stream()
-                        .filter(pendingTransition -> pendingTransition.mType == TRANSIT_CHANGE)
-                        .toList();
-        assertThat(pendingList.size()).isEqualTo(2);
-    }
-
-    @Test
     public void testSetTaskBounds_sameBounds_noTransaction() {
         assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
 
@@ -217,16 +168,6 @@
                 mTaskViewTransitions.findPending(mTaskViewTaskController, TRANSIT_CHANGE);
         assertThat(pendingBounds).isNotNull();
 
-        // Test that setting same bounds with in-flight transition doesn't cause another one
-        mTaskViewTransitions.setTaskBounds(mTaskViewTaskController,
-                new Rect(0, 0, 100, 100));
-        List<TaskViewTransitions.PendingTransition> pendingList =
-                mTaskViewTransitions.findAllPending(mTaskViewTaskController)
-                        .stream()
-                        .filter(pendingTransition -> pendingTransition.mType == TRANSIT_CHANGE)
-                        .toList();
-        assertThat(pendingList.size()).isEqualTo(1);
-
         // Consume the pending bounds transaction
         mTaskViewTransitions.startAnimation(pendingBounds.mClaimed,
                 mock(TransitionInfo.class),
@@ -246,42 +187,6 @@
         assertThat(pendingBounds2).isNull();
     }
 
-
-    @Test
-    public void testSetTaskBounds_taskVisibleWithDifferentTaskViewPendingChange_transition() {
-        assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
-
-        mTaskViewTransitions.addTaskView(mTaskViewTaskController2);
-
-        mTaskViewTransitions.setTaskViewVisible(mTaskViewTaskController, true);
-
-        // Consume the pending transition from visibility change
-        TaskViewTransitions.PendingTransition pending =
-                mTaskViewTransitions.findPending(mTaskViewTaskController, TRANSIT_TO_FRONT);
-        assertThat(pending).isNotNull();
-        mTaskViewTransitions.startAnimation(pending.mClaimed,
-                mock(TransitionInfo.class),
-                new SurfaceControl.Transaction(),
-                new SurfaceControl.Transaction(),
-                mock(Transitions.TransitionFinishCallback.class));
-        // Verify it was consumed
-        TaskViewTransitions.PendingTransition checkPending =
-                mTaskViewTransitions.findPending(mTaskViewTaskController, TRANSIT_TO_FRONT);
-        assertThat(checkPending).isNull();
-
-        // Set the second taskview as visible & check that it has a pending transition
-        mTaskViewTransitions.setTaskViewVisible(mTaskViewTaskController2, true);
-        TaskViewTransitions.PendingTransition pending2 =
-                mTaskViewTransitions.findPending(mTaskViewTaskController2, TRANSIT_TO_FRONT);
-        assertThat(pending2).isNotNull();
-
-        // Test that set bounds on the first taskview will create a new transition
-        mTaskViewTransitions.setTaskBounds(mTaskViewTaskController,
-                new Rect(0, 0, 100, 100));
-        assertThat(mTaskViewTransitions.findPending(mTaskViewTaskController, TRANSIT_CHANGE))
-                .isNotNull();
-    }
-
     @Test
     public void testSetTaskVisibility_taskRemoved_noNPE() {
         mTaskViewTransitions.removeTaskView(mTaskViewTaskController);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.java
index 41bab95..adc2a6f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.java
@@ -53,7 +53,6 @@
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.desktopmode.DesktopModeController;
 import com.android.wm.shell.desktopmode.DesktopTasksController;
-import com.android.wm.shell.splitscreen.SplitScreenController;
 import com.android.wm.shell.transition.Transitions;
 
 import org.junit.Before;
@@ -82,7 +81,6 @@
     @Mock private ShellTaskOrganizer mTaskOrganizer;
     @Mock private DisplayController mDisplayController;
     @Mock private DisplayLayout mDisplayLayout;
-    @Mock private SplitScreenController mSplitScreenController;
     @Mock private SyncTransactionQueue mSyncQueue;
     @Mock private DesktopModeController mDesktopModeController;
     @Mock private DesktopTasksController mDesktopTasksController;
@@ -92,6 +90,7 @@
     @Mock private DesktopModeWindowDecorViewModel.InputMonitorFactory mMockInputMonitorFactory;
     @Mock private Supplier<SurfaceControl.Transaction> mTransactionFactory;
     @Mock private SurfaceControl.Transaction mTransaction;
+    @Mock private Display mDisplay;
     private final List<InputManager> mMockInputManagers = new ArrayList<>();
 
     private DesktopModeWindowDecorViewModel mDesktopModeWindowDecorViewModel;
@@ -111,7 +110,6 @@
                 mTransitions,
                 Optional.of(mDesktopModeController),
                 Optional.of(mDesktopTasksController),
-                Optional.of(mSplitScreenController),
                 mDesktopModeWindowDecorFactory,
                 mMockInputMonitorFactory,
                 mTransactionFactory
@@ -129,6 +127,9 @@
         final InputChannel[] inputChannels = InputChannel.openInputChannelPair(TAG);
         inputChannels[0].dispose();
         when(mInputMonitor.getInputChannel()).thenReturn(inputChannels[1]);
+
+        mDesktopModeWindowDecoration.mDisplay = mDisplay;
+        doReturn(Display.DEFAULT_DISPLAY).when(mDisplay).getDisplayId();
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragDetectorTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragDetectorTest.kt
index 8f84008..3fbab0f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragDetectorTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragDetectorTest.kt
@@ -55,7 +55,7 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
 
-        `when`(eventHandler.handleMotionEvent(any())).thenReturn(true)
+        `when`(eventHandler.handleMotionEvent(any(), any())).thenReturn(true)
 
         dragDetector = DragDetector(eventHandler)
         dragDetector.setTouchSlop(SLOP)
@@ -72,13 +72,13 @@
     @Test
     fun testNoMove_passesDownAndUp() {
         assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_DOWN)))
-        verify(eventHandler).handleMotionEvent(argThat {
+        verify(eventHandler).handleMotionEvent(any(), argThat {
             return@argThat it.action == MotionEvent.ACTION_DOWN && it.x == X && it.y == Y &&
                     it.source == InputDevice.SOURCE_TOUCHSCREEN
         })
 
         assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_UP)))
-        verify(eventHandler).handleMotionEvent(argThat {
+        verify(eventHandler).handleMotionEvent(any(), argThat {
             return@argThat it.action == MotionEvent.ACTION_UP && it.x == X && it.y == Y &&
                     it.source == InputDevice.SOURCE_TOUCHSCREEN
         })
@@ -86,12 +86,12 @@
 
     @Test
     fun testMoveInSlop_touch_passesDownAndUp() {
-        `when`(eventHandler.handleMotionEvent(argThat {
+        `when`(eventHandler.handleMotionEvent(any(), argThat {
             return@argThat it.action == MotionEvent.ACTION_DOWN
         })).thenReturn(false)
 
         assertFalse(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_DOWN)))
-        verify(eventHandler).handleMotionEvent(argThat {
+        verify(eventHandler).handleMotionEvent(any(), argThat {
             return@argThat it.action == MotionEvent.ACTION_DOWN && it.x == X && it.y == Y &&
                     it.source == InputDevice.SOURCE_TOUCHSCREEN
         })
@@ -99,12 +99,12 @@
         val newX = X + SLOP - 1
         assertFalse(
                 dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_MOVE, newX, Y)))
-        verify(eventHandler, never()).handleMotionEvent(argThat {
+        verify(eventHandler, never()).handleMotionEvent(any(), argThat {
             return@argThat it.action == MotionEvent.ACTION_MOVE
         })
 
         assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_UP, newX, Y)))
-        verify(eventHandler).handleMotionEvent(argThat {
+        verify(eventHandler).handleMotionEvent(any(), argThat {
             return@argThat it.action == MotionEvent.ACTION_UP && it.x == newX && it.y == Y &&
                     it.source == InputDevice.SOURCE_TOUCHSCREEN
         })
@@ -112,13 +112,13 @@
 
     @Test
     fun testMoveInSlop_mouse_passesDownMoveAndUp() {
-        `when`(eventHandler.handleMotionEvent(argThat {
+        `when`(eventHandler.handleMotionEvent(any(), argThat {
             it.action == MotionEvent.ACTION_DOWN
         })).thenReturn(false)
 
         assertFalse(dragDetector.onMotionEvent(
                 createMotionEvent(MotionEvent.ACTION_DOWN, isTouch = false)))
-        verify(eventHandler).handleMotionEvent(argThat {
+        verify(eventHandler).handleMotionEvent(any(), argThat {
             return@argThat it.action == MotionEvent.ACTION_DOWN && it.x == X && it.y == Y &&
                     it.source == InputDevice.SOURCE_MOUSE
         })
@@ -126,14 +126,14 @@
         val newX = X + SLOP - 1
         assertTrue(dragDetector.onMotionEvent(
                 createMotionEvent(MotionEvent.ACTION_MOVE, newX, Y, isTouch = false)))
-        verify(eventHandler).handleMotionEvent(argThat {
+        verify(eventHandler).handleMotionEvent(any(), argThat {
             return@argThat it.action == MotionEvent.ACTION_MOVE && it.x == newX && it.y == Y &&
                     it.source == InputDevice.SOURCE_MOUSE
         })
 
         assertTrue(dragDetector.onMotionEvent(
                 createMotionEvent(MotionEvent.ACTION_UP, newX, Y, isTouch = false)))
-        verify(eventHandler).handleMotionEvent(argThat {
+        verify(eventHandler).handleMotionEvent(any(), argThat {
             return@argThat it.action == MotionEvent.ACTION_UP && it.x == newX && it.y == Y &&
                     it.source == InputDevice.SOURCE_MOUSE
         })
@@ -141,25 +141,25 @@
 
     @Test
     fun testMoveBeyondSlop_passesDownMoveAndUp() {
-        `when`(eventHandler.handleMotionEvent(argThat {
+        `when`(eventHandler.handleMotionEvent(any(), argThat {
             it.action == MotionEvent.ACTION_DOWN
         })).thenReturn(false)
 
         assertFalse(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_DOWN)))
-        verify(eventHandler).handleMotionEvent(argThat {
+        verify(eventHandler).handleMotionEvent(any(), argThat {
             return@argThat it.action == MotionEvent.ACTION_DOWN && it.x == X && it.y == Y &&
                     it.source == InputDevice.SOURCE_TOUCHSCREEN
         })
 
         val newX = X + SLOP + 1
         assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_MOVE, newX, Y)))
-        verify(eventHandler).handleMotionEvent(argThat {
+        verify(eventHandler).handleMotionEvent(any(), argThat {
             return@argThat it.action == MotionEvent.ACTION_MOVE && it.x == newX && it.y == Y &&
                     it.source == InputDevice.SOURCE_TOUCHSCREEN
         })
 
         assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_UP, newX, Y)))
-        verify(eventHandler).handleMotionEvent(argThat {
+        verify(eventHandler).handleMotionEvent(any(), argThat {
             return@argThat it.action == MotionEvent.ACTION_UP && it.x == newX && it.y == Y &&
                     it.source == InputDevice.SOURCE_TOUCHSCREEN
         })
@@ -167,12 +167,12 @@
 
     @Test
     fun testPassesHoverEnter() {
-        `when`(eventHandler.handleMotionEvent(argThat {
+        `when`(eventHandler.handleMotionEvent(any(), argThat {
             it.action == MotionEvent.ACTION_HOVER_ENTER
         })).thenReturn(false)
 
         assertFalse(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_HOVER_ENTER)))
-        verify(eventHandler).handleMotionEvent(argThat {
+        verify(eventHandler).handleMotionEvent(any(), argThat {
             return@argThat it.action == MotionEvent.ACTION_HOVER_ENTER && it.x == X && it.y == Y
         })
     }
@@ -180,7 +180,7 @@
     @Test
     fun testPassesHoverMove() {
         assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_HOVER_MOVE)))
-        verify(eventHandler).handleMotionEvent(argThat {
+        verify(eventHandler).handleMotionEvent(any(), argThat {
             return@argThat it.action == MotionEvent.ACTION_HOVER_MOVE && it.x == X && it.y == Y
         })
     }
@@ -188,7 +188,7 @@
     @Test
     fun testPassesHoverExit() {
         assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_HOVER_EXIT)))
-        verify(eventHandler).handleMotionEvent(argThat {
+        verify(eventHandler).handleMotionEvent(any(), argThat {
             return@argThat it.action == MotionEvent.ACTION_HOVER_EXIT && it.x == X && it.y == Y
         })
     }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
index 69604dd..6f0599a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
@@ -22,6 +22,7 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.eq
 import org.mockito.Mock
 import org.mockito.Mockito.any
 import org.mockito.Mockito.argThat
@@ -71,16 +72,6 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
 
-        taskPositioner =
-            FluidResizeTaskPositioner(
-                mockShellTaskOrganizer,
-                mockWindowDecoration,
-                mockDisplayController,
-                DISALLOWED_AREA_FOR_END_BOUNDS,
-                mockDragStartListener,
-                mockTransactionFactory
-        )
-
         whenever(taskToken.asBinder()).thenReturn(taskBinder)
         whenever(mockDisplayController.getDisplayLayout(DISPLAY_ID)).thenReturn(mockDisplayLayout)
         whenever(mockDisplayLayout.densityDpi()).thenReturn(DENSITY_DPI)
@@ -101,6 +92,15 @@
         }
         mockWindowDecoration.mDisplay = mockDisplay
         whenever(mockDisplay.displayId).thenAnswer { DISPLAY_ID }
+
+        taskPositioner = FluidResizeTaskPositioner(
+                mockShellTaskOrganizer,
+                mockWindowDecoration,
+                mockDisplayController,
+                mockDragStartListener,
+                mockTransactionFactory,
+                DISALLOWED_AREA_FOR_END_BOUNDS_HEIGHT
+        )
     }
 
     @Test
@@ -544,7 +544,7 @@
         )
 
         val newX = STARTING_BOUNDS.right.toFloat() + 5
-        val newY = STARTING_BOUNDS.top.toFloat() + 5
+        val newY = DISALLOWED_AREA_FOR_END_BOUNDS_HEIGHT.toFloat() - 1
         taskPositioner.onDragPositioningMove(
                 newX,
                 newY
@@ -614,6 +614,38 @@
         })
     }
 
+    @Test
+    fun testDragResize_drag_taskPositionedInStableBounds() {
+        taskPositioner.onDragPositioningStart(
+                CTRL_TYPE_UNDEFINED, // drag
+                STARTING_BOUNDS.left.toFloat(),
+                STARTING_BOUNDS.top.toFloat()
+        )
+
+        val newX = STARTING_BOUNDS.left.toFloat()
+        val newY = STABLE_BOUNDS.top.toFloat() - 5
+        taskPositioner.onDragPositioningMove(
+                newX,
+                newY
+        )
+        verify(mockTransaction).setPosition(any(), eq(newX), eq(newY))
+
+        taskPositioner.onDragPositioningEnd(
+                newX,
+                newY
+        )
+        // Verify task's top bound is set to stable bounds top since dragged outside stable bounds
+        // but not in disallowed end bounds area.
+        verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
+            return@argThat wct.changes.any { (token, change) ->
+                token == taskBinder &&
+                        (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
+                        change.configuration.windowConfiguration.bounds.top ==
+                        STABLE_BOUNDS.top
+            }
+        })
+    }
+
     companion object {
         private const val TASK_ID = 5
         private const val MIN_WIDTH = 10
@@ -622,10 +654,11 @@
         private const val DEFAULT_MIN = 40
         private const val DISPLAY_ID = 1
         private const val NAVBAR_HEIGHT = 50
+        private const val CAPTION_HEIGHT = 50
+        private const val DISALLOWED_AREA_FOR_END_BOUNDS_HEIGHT = 10
         private val DISPLAY_BOUNDS = Rect(0, 0, 2400, 1600)
-        private val STARTING_BOUNDS = Rect(0, 0, 100, 100)
+        private val STARTING_BOUNDS = Rect(100, 100, 200, 200)
         private val STABLE_INSETS = Rect(0, 50, 0, 0)
-        private val DISALLOWED_AREA_FOR_END_BOUNDS = Rect(0, 0, 300, 300)
         private val DISALLOWED_RESIZE_AREA = Rect(
                 DISPLAY_BOUNDS.left,
                 DISPLAY_BOUNDS.bottom - NAVBAR_HEIGHT,
@@ -633,7 +666,7 @@
                 DISPLAY_BOUNDS.bottom)
         private val STABLE_BOUNDS = Rect(
                 DISPLAY_BOUNDS.left,
-                DISPLAY_BOUNDS.top,
+                DISPLAY_BOUNDS.top + CAPTION_HEIGHT,
                 DISPLAY_BOUNDS.right,
                 DISPLAY_BOUNDS.bottom - NAVBAR_HEIGHT
         )
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
index 4147dd8..3465ddd 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
@@ -89,17 +89,6 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
 
-        taskPositioner =
-            VeiledResizeTaskPositioner(
-                mockShellTaskOrganizer,
-                mockDesktopWindowDecoration,
-                mockDisplayController,
-                DISALLOWED_AREA_FOR_END_BOUNDS,
-                mockDragStartListener,
-                mockTransactionFactory,
-                mockTransitions
-            )
-
         whenever(taskToken.asBinder()).thenReturn(taskBinder)
         whenever(mockDisplayController.getDisplayLayout(DISPLAY_ID)).thenReturn(mockDisplayLayout)
         whenever(mockDisplayLayout.densityDpi()).thenReturn(DENSITY_DPI)
@@ -119,6 +108,17 @@
         }
         mockDesktopWindowDecoration.mDisplay = mockDisplay
         whenever(mockDisplay.displayId).thenAnswer { DISPLAY_ID }
+
+        taskPositioner =
+                VeiledResizeTaskPositioner(
+                        mockShellTaskOrganizer,
+                        mockDesktopWindowDecoration,
+                        mockDisplayController,
+                        mockDragStartListener,
+                        mockTransactionFactory,
+                        mockTransitions,
+                        DISALLOWED_AREA_FOR_END_BOUNDS_HEIGHT
+                )
     }
 
     @Test
@@ -269,7 +269,7 @@
         )
 
         val newX = STARTING_BOUNDS.left.toFloat() + 5
-        val newY = STARTING_BOUNDS.top.toFloat() + 5
+        val newY = DISALLOWED_AREA_FOR_END_BOUNDS_HEIGHT.toFloat() - 1
         taskPositioner.onDragPositioningMove(
                 newX,
                 newY
@@ -334,6 +334,38 @@
         })
     }
 
+    @Test
+    fun testDragResize_drag_taskPositionedInStableBounds() {
+        taskPositioner.onDragPositioningStart(
+                CTRL_TYPE_UNDEFINED, // drag
+                STARTING_BOUNDS.left.toFloat(),
+                STARTING_BOUNDS.top.toFloat()
+        )
+
+        val newX = STARTING_BOUNDS.left.toFloat()
+        val newY = STABLE_BOUNDS.top.toFloat() - 5
+        taskPositioner.onDragPositioningMove(
+                newX,
+                newY
+        )
+        verify(mockTransaction).setPosition(any(), eq(newX), eq(newY))
+
+        taskPositioner.onDragPositioningEnd(
+                newX,
+                newY
+        )
+        // Verify task's top bound is set to stable bounds top since dragged outside stable bounds
+        // but not in disallowed end bounds area.
+        verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
+            return@argThat wct.changes.any { (token, change) ->
+                token == taskBinder &&
+                        (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
+                        change.configuration.windowConfiguration.bounds.top ==
+                        STABLE_BOUNDS.top
+            }
+        })
+    }
+
     companion object {
         private const val TASK_ID = 5
         private const val MIN_WIDTH = 10
@@ -342,12 +374,13 @@
         private const val DEFAULT_MIN = 40
         private const val DISPLAY_ID = 1
         private const val NAVBAR_HEIGHT = 50
+        private const val CAPTION_HEIGHT = 50
+        private const val DISALLOWED_AREA_FOR_END_BOUNDS_HEIGHT = 10
         private val DISPLAY_BOUNDS = Rect(0, 0, 2400, 1600)
-        private val STARTING_BOUNDS = Rect(0, 0, 100, 100)
-        private val DISALLOWED_AREA_FOR_END_BOUNDS = Rect(0, 0, 50, 50)
+        private val STARTING_BOUNDS = Rect(100, 100, 200, 200)
         private val STABLE_BOUNDS = Rect(
             DISPLAY_BOUNDS.left,
-            DISPLAY_BOUNDS.top,
+            DISPLAY_BOUNDS.top + CAPTION_HEIGHT,
             DISPLAY_BOUNDS.right,
             DISPLAY_BOUNDS.bottom - NAVBAR_HEIGHT
         )
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
index 5a2326b..7fc1c99 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
@@ -57,7 +57,6 @@
 import android.window.WindowContainerTransaction;
 
 import androidx.test.filters.SmallTest;
-import androidx.test.platform.app.InstrumentationRegistry;
 
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.ShellTestCase;
@@ -316,7 +315,8 @@
         releaseOrder.verify(t).remove(captionContainerSurface);
         releaseOrder.verify(t).remove(decorContainerSurface);
         releaseOrder.verify(t).apply();
-        verify(mMockWindowContainerTransaction)
+        // Expect to remove two insets sources, the caption insets and the mandatory gesture insets.
+        verify(mMockWindowContainerTransaction, Mockito.times(2))
                 .removeInsetsSource(eq(taskInfo.token), any(), anyInt(), anyInt());
     }
 
@@ -410,15 +410,17 @@
         verify(additionalWindowSurfaceBuilder).build();
         verify(mMockSurfaceControlAddWindowT).setPosition(additionalWindowSurface, 0, 0);
         final int width = WindowDecoration.loadDimensionPixelSize(
-                mContext.getResources(), mCaptionMenuWidthId);
+                windowDecor.mDecorWindowContext.getResources(), mCaptionMenuWidthId);
         final int height = WindowDecoration.loadDimensionPixelSize(
-                mContext.getResources(), mRelayoutParams.mCaptionHeightId);
+                windowDecor.mDecorWindowContext.getResources(), mRelayoutParams.mCaptionHeightId);
         verify(mMockSurfaceControlAddWindowT).setWindowCrop(additionalWindowSurface, width, height);
-        final int shadowRadius = WindowDecoration.loadDimensionPixelSize(mContext.getResources(),
+        final int shadowRadius = WindowDecoration.loadDimensionPixelSize(
+                windowDecor.mDecorWindowContext.getResources(),
                 mCaptionMenuShadowRadiusId);
         verify(mMockSurfaceControlAddWindowT)
                 .setShadowRadius(additionalWindowSurface, shadowRadius);
-        final int cornerRadius = WindowDecoration.loadDimensionPixelSize(mContext.getResources(),
+        final int cornerRadius = WindowDecoration.loadDimensionPixelSize(
+                windowDecor.mDecorWindowContext.getResources(),
                 mCaptionMenuCornerRadiusId);
         verify(mMockSurfaceControlAddWindowT)
                 .setCornerRadius(additionalWindowSurface, cornerRadius);
@@ -513,8 +515,7 @@
 
     private TestWindowDecoration createWindowDecoration(
             ActivityManager.RunningTaskInfo taskInfo, SurfaceControl testSurface) {
-        return new TestWindowDecoration(InstrumentationRegistry.getInstrumentation().getContext(),
-                mMockDisplayController, mMockShellTaskOrganizer,
+        return new TestWindowDecoration(mContext, mMockDisplayController, mMockShellTaskOrganizer,
                 taskInfo, testSurface,
                 new MockObjectSupplier<>(mMockSurfaceControlBuilders,
                         () -> createMockSurfaceControlBuilder(mock(SurfaceControl.class))),
diff --git a/libs/androidfw/Android.bp b/libs/androidfw/Android.bp
index 28bda72..fa9447a 100644
--- a/libs/androidfw/Android.bp
+++ b/libs/androidfw/Android.bp
@@ -232,6 +232,7 @@
         "tests/AssetManager2_bench.cpp",
         "tests/AttributeResolution_bench.cpp",
         "tests/CursorWindow_bench.cpp",
+        "tests/Generic_bench.cpp",
         "tests/SparseEntry_bench.cpp",
         "tests/Theme_bench.cpp",
     ],
diff --git a/libs/androidfw/ApkAssets.cpp b/libs/androidfw/ApkAssets.cpp
index 15aaae2..f0c6395 100644
--- a/libs/androidfw/ApkAssets.cpp
+++ b/libs/androidfw/ApkAssets.cpp
@@ -27,39 +27,34 @@
 
 constexpr const char* kResourcesArsc = "resources.arsc";
 
-ApkAssets::ApkAssets(std::unique_ptr<Asset> resources_asset,
+ApkAssets::ApkAssets(PrivateConstructorUtil, std::unique_ptr<Asset> resources_asset,
                      std::unique_ptr<LoadedArsc> loaded_arsc,
-                     std::unique_ptr<AssetsProvider> assets,
-                     package_property_t property_flags,
-                     std::unique_ptr<Asset> idmap_asset,
-                     std::unique_ptr<LoadedIdmap> loaded_idmap)
+                     std::unique_ptr<AssetsProvider> assets, package_property_t property_flags,
+                     std::unique_ptr<Asset> idmap_asset, std::unique_ptr<LoadedIdmap> loaded_idmap)
     : resources_asset_(std::move(resources_asset)),
       loaded_arsc_(std::move(loaded_arsc)),
       assets_provider_(std::move(assets)),
       property_flags_(property_flags),
       idmap_asset_(std::move(idmap_asset)),
-      loaded_idmap_(std::move(loaded_idmap)) {}
+      loaded_idmap_(std::move(loaded_idmap)) {
+}
 
-std::unique_ptr<ApkAssets> ApkAssets::Load(const std::string& path, package_property_t flags) {
+ApkAssetsPtr ApkAssets::Load(const std::string& path, package_property_t flags) {
   return Load(ZipAssetsProvider::Create(path, flags), flags);
 }
 
-std::unique_ptr<ApkAssets> ApkAssets::LoadFromFd(base::unique_fd fd,
-                                                 const std::string& debug_name,
-                                                 package_property_t flags,
-                                                 off64_t offset,
-                                                 off64_t len) {
+ApkAssetsPtr ApkAssets::LoadFromFd(base::unique_fd fd, const std::string& debug_name,
+                                   package_property_t flags, off64_t offset, off64_t len) {
   return Load(ZipAssetsProvider::Create(std::move(fd), debug_name, offset, len), flags);
 }
 
-std::unique_ptr<ApkAssets> ApkAssets::Load(std::unique_ptr<AssetsProvider> assets,
-                                           package_property_t flags) {
+ApkAssetsPtr ApkAssets::Load(std::unique_ptr<AssetsProvider> assets, package_property_t flags) {
   return LoadImpl(std::move(assets), flags, nullptr /* idmap_asset */, nullptr /* loaded_idmap */);
 }
 
-std::unique_ptr<ApkAssets> ApkAssets::LoadTable(std::unique_ptr<Asset> resources_asset,
-                                                std::unique_ptr<AssetsProvider> assets,
-                                                package_property_t flags) {
+ApkAssetsPtr ApkAssets::LoadTable(std::unique_ptr<Asset> resources_asset,
+                                  std::unique_ptr<AssetsProvider> assets,
+                                  package_property_t flags) {
   if (resources_asset == nullptr) {
     return {};
   }
@@ -67,8 +62,7 @@
                   nullptr /* loaded_idmap */);
 }
 
-std::unique_ptr<ApkAssets> ApkAssets::LoadOverlay(const std::string& idmap_path,
-                                                  package_property_t flags) {
+ApkAssetsPtr ApkAssets::LoadOverlay(const std::string& idmap_path, package_property_t flags) {
   CHECK((flags & PROPERTY_LOADER) == 0U) << "Cannot load RROs through loaders";
   auto idmap_asset = AssetsProvider::CreateAssetFromFile(idmap_path);
   if (idmap_asset == nullptr) {
@@ -103,10 +97,10 @@
                   std::move(loaded_idmap));
 }
 
-std::unique_ptr<ApkAssets> ApkAssets::LoadImpl(std::unique_ptr<AssetsProvider> assets,
-                                               package_property_t property_flags,
-                                               std::unique_ptr<Asset> idmap_asset,
-                                               std::unique_ptr<LoadedIdmap> loaded_idmap) {
+ApkAssetsPtr ApkAssets::LoadImpl(std::unique_ptr<AssetsProvider> assets,
+                                 package_property_t property_flags,
+                                 std::unique_ptr<Asset> idmap_asset,
+                                 std::unique_ptr<LoadedIdmap> loaded_idmap) {
   if (assets == nullptr) {
     return {};
   }
@@ -125,11 +119,11 @@
                   std::move(idmap_asset), std::move(loaded_idmap));
 }
 
-std::unique_ptr<ApkAssets> ApkAssets::LoadImpl(std::unique_ptr<Asset> resources_asset,
-                                               std::unique_ptr<AssetsProvider> assets,
-                                               package_property_t property_flags,
-                                               std::unique_ptr<Asset> idmap_asset,
-                                               std::unique_ptr<LoadedIdmap> loaded_idmap) {
+ApkAssetsPtr ApkAssets::LoadImpl(std::unique_ptr<Asset> resources_asset,
+                                 std::unique_ptr<AssetsProvider> assets,
+                                 package_property_t property_flags,
+                                 std::unique_ptr<Asset> idmap_asset,
+                                 std::unique_ptr<LoadedIdmap> loaded_idmap) {
   if (assets == nullptr ) {
     return {};
   }
@@ -155,10 +149,9 @@
     return {};
   }
 
-  return std::unique_ptr<ApkAssets>(new ApkAssets(std::move(resources_asset),
-                                                  std::move(loaded_arsc), std::move(assets),
-                                                  property_flags, std::move(idmap_asset),
-                                                  std::move(loaded_idmap)));
+  return ApkAssetsPtr::make(PrivateConstructorUtil{}, std::move(resources_asset),
+                            std::move(loaded_arsc), std::move(assets), property_flags,
+                            std::move(idmap_asset), std::move(loaded_idmap));
 }
 
 std::optional<std::string_view> ApkAssets::GetPath() const {
@@ -174,4 +167,5 @@
   return IsLoader() || ((!loaded_idmap_ || loaded_idmap_->IsUpToDate())
                         && assets_provider_->IsUpToDate());
 }
+
 }  // namespace android
diff --git a/libs/androidfw/ApkParsing.cpp b/libs/androidfw/ApkParsing.cpp
index 32d2c5b..7eedfdb 100644
--- a/libs/androidfw/ApkParsing.cpp
+++ b/libs/androidfw/ApkParsing.cpp
@@ -56,6 +56,11 @@
         return nullptr;
     }
 
+    // Make sure file starts with 'lib/' prefix.
+    if (strncmp(fileName, APK_LIB.data(), APK_LIB_LEN) != 0) {
+        return nullptr;
+    }
+
     // Make sure there aren't subdirectories by checking if the next / after lib/ is the last slash
     if (memchr(fileName + APK_LIB_LEN, '/', fileNameLen - APK_LIB_LEN) != lastSlash) {
         return nullptr;
diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp
index 68f5e4a8..dcbaaec 100644
--- a/libs/androidfw/AssetManager2.cpp
+++ b/libs/androidfw/AssetManager2.cpp
@@ -91,13 +91,14 @@
   StringPoolRef entry_string_ref;
 };
 
-AssetManager2::AssetManager2() {
-  memset(&configuration_, 0, sizeof(configuration_));
+AssetManager2::AssetManager2(ApkAssetsList apk_assets, const ResTable_config& configuration)
+    : configuration_(configuration) {
+  // Don't invalidate caches here as there's nothing cached yet.
+  SetApkAssets(apk_assets, false);
 }
 
-bool AssetManager2::SetApkAssets(std::vector<const ApkAssets*> apk_assets, bool invalidate_caches) {
-  apk_assets_ = std::move(apk_assets);
-  BuildDynamicRefTable();
+bool AssetManager2::SetApkAssets(ApkAssetsList apk_assets, bool invalidate_caches) {
+  BuildDynamicRefTable(apk_assets);
   RebuildFilterList();
   if (invalidate_caches) {
     InvalidateCaches(static_cast<uint32_t>(-1));
@@ -105,7 +106,21 @@
   return true;
 }
 
-void AssetManager2::BuildDynamicRefTable() {
+bool AssetManager2::SetApkAssets(std::initializer_list<ApkAssetsPtr> apk_assets,
+                                 bool invalidate_caches) {
+  return SetApkAssets(ApkAssetsList(apk_assets.begin(), apk_assets.size()), invalidate_caches);
+}
+
+void AssetManager2::BuildDynamicRefTable(ApkAssetsList apk_assets) {
+  auto op = StartOperation();
+
+  apk_assets_.resize(apk_assets.size());
+  for (size_t i = 0; i != apk_assets.size(); ++i) {
+    apk_assets_[i].first = apk_assets[i];
+    // Let's populate the locked assets right away as we're going to need them here later.
+    apk_assets_[i].second = apk_assets[i];
+  }
+
   package_groups_.clear();
   package_ids_.fill(0xff);
 
@@ -116,16 +131,19 @@
 
   // Overlay resources are not directly referenced by an application so their resource ids
   // can change throughout the application's lifetime. Assign overlay package ids last.
-  std::vector<const ApkAssets*> sorted_apk_assets(apk_assets_);
-  std::stable_partition(sorted_apk_assets.begin(), sorted_apk_assets.end(), [](const ApkAssets* a) {
-    return !a->IsOverlay();
-  });
+  std::vector<const ApkAssets*> sorted_apk_assets;
+  sorted_apk_assets.reserve(apk_assets.size());
+  for (auto& asset : apk_assets) {
+    sorted_apk_assets.push_back(asset.get());
+  }
+  std::stable_partition(sorted_apk_assets.begin(), sorted_apk_assets.end(),
+                        [](auto a) { return !a->IsOverlay(); });
 
   // The assets cookie must map to the position of the apk assets in the unsorted apk assets list.
   std::unordered_map<const ApkAssets*, ApkAssetsCookie> apk_assets_cookies;
-  apk_assets_cookies.reserve(apk_assets_.size());
-  for (size_t i = 0, n = apk_assets_.size(); i < n; i++) {
-    apk_assets_cookies[apk_assets_[i]] = static_cast<ApkAssetsCookie>(i);
+  apk_assets_cookies.reserve(apk_assets.size());
+  for (size_t i = 0, n = apk_assets.size(); i < n; i++) {
+    apk_assets_cookies[apk_assets[i].get()] = static_cast<ApkAssetsCookie>(i);
   }
 
   // 0x01 is reserved for the android package.
@@ -240,9 +258,11 @@
 void AssetManager2::DumpToLog() const {
   LOG(INFO) << base::StringPrintf("AssetManager2(this=%p)", this);
 
+  auto op = StartOperation();
   std::string list;
-  for (const auto& apk_assets : apk_assets_) {
-    base::StringAppendF(&list, "%s,", apk_assets->GetDebugName().c_str());
+  for (size_t i = 0, s = apk_assets_.size(); i < s; ++i) {
+    const auto& assets = GetApkAssets(i);
+    base::StringAppendF(&list, "%s,", assets ? assets->GetDebugName().c_str() : "nullptr");
   }
   LOG(INFO) << "ApkAssets: " << list;
 
@@ -279,7 +299,9 @@
   if (cookie < 0 || static_cast<size_t>(cookie) >= apk_assets_.size()) {
     return nullptr;
   }
-  return apk_assets_[cookie]->GetLoadedArsc()->GetStringPool();
+  auto op = StartOperation();
+  const auto& assets = GetApkAssets(cookie);
+  return assets ? assets->GetLoadedArsc()->GetStringPool() : nullptr;
 }
 
 const DynamicRefTable* AssetManager2::GetDynamicRefTableForPackage(uint32_t package_id) const {
@@ -329,9 +351,14 @@
 
 bool AssetManager2::GetOverlayablesToString(android::StringPiece package_name,
                                             std::string* out) const {
+  auto op = StartOperation();
   uint8_t package_id = 0U;
-  for (const auto& apk_assets : apk_assets_) {
-    const LoadedArsc* loaded_arsc = apk_assets->GetLoadedArsc();
+  for (size_t i = 0, s = apk_assets_.size(); i != s; ++i) {
+    const auto& assets = GetApkAssets(i);
+    if (!assets) {
+      continue;
+    }
+    const LoadedArsc* loaded_arsc = assets->GetLoadedArsc();
     if (loaded_arsc == nullptr) {
       continue;
     }
@@ -384,8 +411,14 @@
 }
 
 bool AssetManager2::ContainsAllocatedTable() const {
-  return std::find_if(apk_assets_.begin(), apk_assets_.end(),
-                      std::mem_fn(&ApkAssets::IsTableAllocated)) != apk_assets_.end();
+  auto op = StartOperation();
+  for (size_t i = 0, s = apk_assets_.size(); i != s; ++i) {
+    const auto& assets = GetApkAssets(i);
+    if (assets && assets->IsTableAllocated()) {
+      return true;
+    }
+  }
+  return false;
 }
 
 void AssetManager2::SetConfiguration(const ResTable_config& configuration) {
@@ -398,8 +431,8 @@
   }
 }
 
-std::set<const ApkAssets*> AssetManager2::GetNonSystemOverlays() const {
-  std::set<const ApkAssets*> non_system_overlays;
+std::set<AssetManager2::ApkAssetsPtr> AssetManager2::GetNonSystemOverlays() const {
+  std::set<ApkAssetsPtr> non_system_overlays;
   for (const PackageGroup& package_group : package_groups_) {
     bool found_system_package = false;
     for (const ConfiguredPackage& package : package_group.packages_) {
@@ -410,8 +443,11 @@
     }
 
     if (!found_system_package) {
+      auto op = StartOperation();
       for (const ConfiguredOverlay& overlay : package_group.overlays_) {
-        non_system_overlays.insert(apk_assets_[overlay.cookie]);
+        if (const auto& asset = GetApkAssets(overlay.cookie)) {
+          non_system_overlays.insert(std::move(asset));
+        }
       }
     }
   }
@@ -422,22 +458,27 @@
 base::expected<std::set<ResTable_config>, IOError> AssetManager2::GetResourceConfigurations(
     bool exclude_system, bool exclude_mipmap) const {
   ATRACE_NAME("AssetManager::GetResourceConfigurations");
+  auto op = StartOperation();
+
   const auto non_system_overlays =
-      (exclude_system) ? GetNonSystemOverlays() : std::set<const ApkAssets*>();
+      exclude_system ? GetNonSystemOverlays() : std::set<ApkAssetsPtr>();
 
   std::set<ResTable_config> configurations;
   for (const PackageGroup& package_group : package_groups_) {
     for (size_t i = 0; i < package_group.packages_.size(); i++) {
       const ConfiguredPackage& package = package_group.packages_[i];
-      if (exclude_system && package.loaded_package_->IsSystem()) {
-        continue;
-      }
-
-      auto apk_assets = apk_assets_[package_group.cookies_[i]];
-      if (exclude_system && apk_assets->IsOverlay() &&
-          non_system_overlays.find(apk_assets) == non_system_overlays.end()) {
-        // Exclude overlays that target system resources.
-        continue;
+      if (exclude_system) {
+        if (package.loaded_package_->IsSystem()) {
+          continue;
+        }
+        if (!non_system_overlays.empty()) {
+          // Exclude overlays that target only system resources.
+          const auto& apk_assets = GetApkAssets(package_group.cookies_[i]);
+          if (apk_assets && apk_assets->IsOverlay() &&
+              non_system_overlays.find(apk_assets) == non_system_overlays.end()) {
+            continue;
+          }
+        }
       }
 
       auto result = package.loaded_package_->CollectConfigurations(exclude_mipmap, &configurations);
@@ -452,22 +493,27 @@
 std::set<std::string> AssetManager2::GetResourceLocales(bool exclude_system,
                                                         bool merge_equivalent_languages) const {
   ATRACE_NAME("AssetManager::GetResourceLocales");
+  auto op = StartOperation();
+
   std::set<std::string> locales;
   const auto non_system_overlays =
-      (exclude_system) ? GetNonSystemOverlays() : std::set<const ApkAssets*>();
+      exclude_system ? GetNonSystemOverlays() : std::set<ApkAssetsPtr>();
 
   for (const PackageGroup& package_group : package_groups_) {
     for (size_t i = 0; i < package_group.packages_.size(); i++) {
       const ConfiguredPackage& package = package_group.packages_[i];
-      if (exclude_system && package.loaded_package_->IsSystem()) {
-        continue;
-      }
-
-      auto apk_assets = apk_assets_[package_group.cookies_[i]];
-      if (exclude_system && apk_assets->IsOverlay() &&
-          non_system_overlays.find(apk_assets) == non_system_overlays.end()) {
-        // Exclude overlays that target system resources.
-        continue;
+      if (exclude_system) {
+        if (package.loaded_package_->IsSystem()) {
+          continue;
+        }
+        if (!non_system_overlays.empty()) {
+          // Exclude overlays that target only system resources.
+          const auto& apk_assets = GetApkAssets(package_group.cookies_[i]);
+          if (apk_assets && apk_assets->IsOverlay() &&
+              non_system_overlays.find(apk_assets) == non_system_overlays.end()) {
+            continue;
+          }
+        }
       }
 
       package.loaded_package_->CollectLocales(merge_equivalent_languages, &locales);
@@ -490,15 +536,15 @@
 
 std::unique_ptr<AssetDir> AssetManager2::OpenDir(const std::string& dirname) const {
   ATRACE_NAME("AssetManager::OpenDir");
+  auto op = StartOperation();
 
   std::string full_path = "assets/" + dirname;
-  std::unique_ptr<SortedVector<AssetDir::FileInfo>> files =
-      util::make_unique<SortedVector<AssetDir::FileInfo>>();
+  auto files = util::make_unique<SortedVector<AssetDir::FileInfo>>();
 
   // Start from the back.
-  for (auto iter = apk_assets_.rbegin(); iter != apk_assets_.rend(); ++iter) {
-    const ApkAssets* apk_assets = *iter;
-    if (apk_assets->IsOverlay()) {
+  for (size_t i = apk_assets_.size(); i > 0; --i) {
+    const auto& apk_assets = GetApkAssets(i - 1);
+    if (!apk_assets || apk_assets->IsOverlay()) {
       continue;
     }
 
@@ -526,15 +572,17 @@
 std::unique_ptr<Asset> AssetManager2::OpenNonAsset(const std::string& filename,
                                                    Asset::AccessMode mode,
                                                    ApkAssetsCookie* out_cookie) const {
-  for (int32_t i = apk_assets_.size() - 1; i >= 0; i--) {
+  auto op = StartOperation();
+  for (size_t i = apk_assets_.size(); i > 0; i--) {
+    const auto& assets = GetApkAssets(i - 1);
     // Prevent RRO from modifying assets and other entries accessed by file
     // path. Explicitly asking for a path in a given package (denoted by a
     // cookie) is still OK.
-    if (apk_assets_[i]->IsOverlay()) {
+    if (!assets || assets->IsOverlay()) {
       continue;
     }
 
-    std::unique_ptr<Asset> asset = apk_assets_[i]->GetAssetsProvider()->Open(filename, mode);
+    std::unique_ptr<Asset> asset = assets->GetAssetsProvider()->Open(filename, mode);
     if (asset) {
       if (out_cookie != nullptr) {
         *out_cookie = i;
@@ -555,7 +603,9 @@
   if (cookie < 0 || static_cast<size_t>(cookie) >= apk_assets_.size()) {
     return {};
   }
-  return apk_assets_[cookie]->GetAssetsProvider()->Open(filename, mode);
+  auto op = StartOperation();
+  const auto& assets = GetApkAssets(cookie);
+  return assets ? assets->GetAssetsProvider()->Open(filename, mode) : nullptr;
 }
 
 base::expected<FindEntryResult, NullOrIOError> AssetManager2::FindEntry(
@@ -568,6 +618,8 @@
     last_resolution_.resid = resid;
   }
 
+  auto op = StartOperation();
+
   // Might use this if density_override != 0.
   ResTable_config density_override_config;
 
@@ -603,90 +655,97 @@
   }
 
   bool overlaid = false;
-  if (!stop_at_first_match && !ignore_configuration && !apk_assets_[result->cookie]->IsLoader()) {
-    for (const auto& id_map : package_group.overlays_) {
-      auto overlay_entry = id_map.overlay_res_maps_.Lookup(resid);
-      if (!overlay_entry) {
-        // No id map entry exists for this target resource.
-        continue;
-      }
-      if (overlay_entry.IsInlineValue()) {
-        // The target resource is overlaid by an inline value not represented by a resource.
-        ConfigDescription best_frro_config;
-        Res_value best_frro_value;
-        bool frro_found = false;
-        for( const auto& [config, value] : overlay_entry.GetInlineValue()) {
-          if ((!frro_found || config.isBetterThan(best_frro_config, desired_config))
-              && config.match(*desired_config)) {
-            frro_found = true;
-            best_frro_config = config;
-            best_frro_value = value;
-          }
-        }
-        if (!frro_found) {
+  if (!stop_at_first_match && !ignore_configuration) {
+    const auto& assets = GetApkAssets(result->cookie);
+    if (!assets) {
+      ALOGE("Found expired ApkAssets #%d for resource ID 0x%08x.", result->cookie, resid);
+      return base::unexpected(std::nullopt);
+    }
+    if (!assets->IsLoader()) {
+      for (const auto& id_map : package_group.overlays_) {
+        auto overlay_entry = id_map.overlay_res_maps_.Lookup(resid);
+        if (!overlay_entry) {
+          // No id map entry exists for this target resource.
           continue;
         }
-        result->entry = best_frro_value;
+        if (overlay_entry.IsInlineValue()) {
+          // The target resource is overlaid by an inline value not represented by a resource.
+          ConfigDescription best_frro_config;
+          Res_value best_frro_value;
+          bool frro_found = false;
+          for( const auto& [config, value] : overlay_entry.GetInlineValue()) {
+            if ((!frro_found || config.isBetterThan(best_frro_config, desired_config))
+                && config.match(*desired_config)) {
+              frro_found = true;
+              best_frro_config = config;
+              best_frro_value = value;
+            }
+          }
+          if (!frro_found) {
+            continue;
+          }
+          result->entry = best_frro_value;
+          result->dynamic_ref_table = id_map.overlay_res_maps_.GetOverlayDynamicRefTable();
+          result->cookie = id_map.cookie;
+
+          if (UNLIKELY(logging_enabled)) {
+            last_resolution_.steps.push_back(
+                Resolution::Step{Resolution::Step::Type::OVERLAID_INLINE, result->cookie, String8()});
+            if (auto path = assets->GetPath()) {
+              const std::string overlay_path = path->data();
+              if (IsFabricatedOverlay(overlay_path)) {
+                // FRRO don't have package name so we use the creating package here.
+                String8 frro_name = String8("FRRO");
+                // Get the first part of it since the expected one should be like
+                // {overlayPackageName}-{overlayName}-{4 alphanumeric chars}.frro
+                // under /data/resource-cache/.
+                const std::string name = overlay_path.substr(overlay_path.rfind('/') + 1);
+                const size_t end = name.find('-');
+                if (frro_name.size() != overlay_path.size() && end != std::string::npos) {
+                  frro_name.append(base::StringPrintf(" created by %s",
+                                                      name.substr(0 /* pos */,
+                                                                  end).c_str()).c_str());
+                }
+                last_resolution_.best_package_name = frro_name;
+              } else {
+                last_resolution_.best_package_name = result->package_name->c_str();
+              }
+            }
+            overlaid = true;
+          }
+          continue;
+        }
+
+        auto overlay_result = FindEntry(overlay_entry.GetResourceId(), density_override,
+                                        false /* stop_at_first_match */,
+                                        false /* ignore_configuration */);
+        if (UNLIKELY(IsIOError(overlay_result))) {
+          return base::unexpected(overlay_result.error());
+        }
+        if (!overlay_result.has_value()) {
+          continue;
+        }
+
+        if (!overlay_result->config.isBetterThan(result->config, desired_config)
+            && overlay_result->config.compare(result->config) != 0) {
+          // The configuration of the entry for the overlay must be equal to or better than the target
+          // configuration to be chosen as the better value.
+          continue;
+        }
+
+        result->cookie = overlay_result->cookie;
+        result->entry = overlay_result->entry;
+        result->config = overlay_result->config;
         result->dynamic_ref_table = id_map.overlay_res_maps_.GetOverlayDynamicRefTable();
-        result->cookie = id_map.cookie;
 
         if (UNLIKELY(logging_enabled)) {
           last_resolution_.steps.push_back(
-              Resolution::Step{Resolution::Step::Type::OVERLAID_INLINE, String8(), result->cookie});
-          if (auto path = apk_assets_[result->cookie]->GetPath()) {
-            const std::string overlay_path = path->data();
-            if (IsFabricatedOverlay(overlay_path)) {
-              // FRRO don't have package name so we use the creating package here.
-              String8 frro_name = String8("FRRO");
-              // Get the first part of it since the expected one should be like
-              // {overlayPackageName}-{overlayName}-{4 alphanumeric chars}.frro
-              // under /data/resource-cache/.
-              const std::string name = overlay_path.substr(overlay_path.rfind('/') + 1);
-              const size_t end = name.find('-');
-              if (frro_name.size() != overlay_path.size() && end != std::string::npos) {
-                frro_name.append(base::StringPrintf(" created by %s",
-                                                    name.substr(0 /* pos */,
-                                                                end).c_str()).c_str());
-              }
-              last_resolution_.best_package_name = frro_name;
-            } else {
-              last_resolution_.best_package_name = result->package_name->c_str();
-            }
-          }
+              Resolution::Step{Resolution::Step::Type::OVERLAID, overlay_result->cookie,
+                               overlay_result->config.toString()});
+          last_resolution_.best_package_name =
+              overlay_result->package_name->c_str();
           overlaid = true;
         }
-        continue;
-      }
-
-      auto overlay_result = FindEntry(overlay_entry.GetResourceId(), density_override,
-                                      false /* stop_at_first_match */,
-                                      false /* ignore_configuration */);
-      if (UNLIKELY(IsIOError(overlay_result))) {
-        return base::unexpected(overlay_result.error());
-      }
-      if (!overlay_result.has_value()) {
-        continue;
-      }
-
-      if (!overlay_result->config.isBetterThan(result->config, desired_config)
-          && overlay_result->config.compare(result->config) != 0) {
-        // The configuration of the entry for the overlay must be equal to or better than the target
-        // configuration to be chosen as the better value.
-        continue;
-      }
-
-      result->cookie = overlay_result->cookie;
-      result->entry = overlay_result->entry;
-      result->config = overlay_result->config;
-      result->dynamic_ref_table = id_map.overlay_res_maps_.GetOverlayDynamicRefTable();
-
-      if (UNLIKELY(logging_enabled)) {
-        last_resolution_.steps.push_back(
-            Resolution::Step{Resolution::Step::Type::OVERLAID, overlay_result->config.toString(),
-                             overlay_result->cookie});
-        last_resolution_.best_package_name =
-            overlay_result->package_name->c_str();
-        overlaid = true;
       }
     }
   }
@@ -769,8 +828,7 @@
       } else {
         if (UNLIKELY(logging_enabled)) {
           last_resolution_.steps.push_back(Resolution::Step{Resolution::Step::Type::SKIPPED,
-                                                      this_config.toString(),
-                                                      cookie});
+                                                            cookie, this_config.toString()});
         }
         continue;
       }
@@ -786,8 +844,7 @@
       if (!offset.has_value()) {
         if (UNLIKELY(logging_enabled)) {
           last_resolution_.steps.push_back(Resolution::Step{Resolution::Step::Type::NO_ENTRY,
-                                                      this_config.toString(),
-                                                      cookie});
+                                                            cookie, this_config.toString()});
         }
         continue;
       }
@@ -800,8 +857,7 @@
 
       if (UNLIKELY(logging_enabled)) {
         last_resolution_.steps.push_back(Resolution::Step{resolution_type,
-                                                          this_config.toString(),
-                                                          cookie});
+                                                          cookie, this_config.toString()});
       }
 
       // Any configuration will suffice, so break.
@@ -839,13 +895,7 @@
 }
 
 void AssetManager2::ResetResourceResolution() const {
-  last_resolution_.cookie = kInvalidCookie;
-  last_resolution_.resid = 0;
-  last_resolution_.steps.clear();
-  last_resolution_.type_string_ref = StringPoolRef();
-  last_resolution_.entry_string_ref = StringPoolRef();
-  last_resolution_.best_config_name.clear();
-  last_resolution_.best_package_name.clear();
+  last_resolution_ = Resolution{};
 }
 
 void AssetManager2::SetResourceResolutionLoggingEnabled(bool enabled) {
@@ -867,8 +917,12 @@
     return {};
   }
 
+  auto op = StartOperation();
+
   const uint32_t resid = last_resolution_.resid;
-  const auto package = apk_assets_[cookie]->GetLoadedArsc()->GetPackageById(get_package_id(resid));
+  const auto& assets = GetApkAssets(cookie);
+  const auto package =
+      assets ? assets->GetLoadedArsc()->GetPackageById(get_package_id(resid)) : nullptr;
 
   std::string resource_name_string;
   if (package != nullptr) {
@@ -885,21 +939,23 @@
                                    configuration_.toString().c_str());
 
   for (const Resolution::Step& step : last_resolution_.steps) {
-    const static std::unordered_map<Resolution::Step::Type, const char*> kStepStrings = {
-        {Resolution::Step::Type::INITIAL,         "Found initial"},
-        {Resolution::Step::Type::BETTER_MATCH,    "Found better"},
-        {Resolution::Step::Type::OVERLAID,        "Overlaid"},
-        {Resolution::Step::Type::OVERLAID_INLINE, "Overlaid inline"},
-        {Resolution::Step::Type::SKIPPED,         "Skipped"},
-        {Resolution::Step::Type::NO_ENTRY,        "No entry"}
+    constexpr static std::array kStepStrings = {
+        "Found initial",
+        "Found better",
+        "Overlaid",
+        "Overlaid inline",
+        "Skipped",
+        "No entry"
     };
 
-    const auto prefix = kStepStrings.find(step.type);
-    if (prefix == kStepStrings.end()) {
+    if (step.type < Resolution::Step::Type::INITIAL
+        || step.type > Resolution::Step::Type::NO_ENTRY) {
       continue;
     }
-
-    log_stream << "\n\t" << prefix->second << ": " << apk_assets_[step.cookie]->GetDebugName();
+    const auto prefix = kStepStrings[int(step.type) - int(Resolution::Step::Type::INITIAL)];
+    const auto& assets = GetApkAssets(step.cookie);
+    log_stream << "\n\t" << prefix << ": " << (assets ? assets->GetDebugName() : "<null>")
+               << " #" << step.cookie;
     if (!step.config_name.isEmpty()) {
       log_stream << " - " << step.config_name;
     }
@@ -1036,16 +1092,19 @@
   }
 }
 
-const std::vector<uint32_t> AssetManager2::GetBagResIdStack(uint32_t resid) const {
-  auto cached_iter = cached_bag_resid_stacks_.find(resid);
-  if (cached_iter != cached_bag_resid_stacks_.end()) {
-    return cached_iter->second;
+base::expected<const std::vector<uint32_t>*, NullOrIOError> AssetManager2::GetBagResIdStack(
+    uint32_t resid) const {
+  auto it = cached_bag_resid_stacks_.find(resid);
+  if (it != cached_bag_resid_stacks_.end()) {
+    return &it->second;
+  }
+  std::vector<uint32_t> stacks;
+  if (auto maybe_bag = GetBag(resid, stacks); UNLIKELY(IsIOError(maybe_bag))) {
+    return base::unexpected(maybe_bag.error());
   }
 
-  std::vector<uint32_t> found_resids;
-  GetBag(resid, found_resids);
-  cached_bag_resid_stacks_.emplace(resid, found_resids);
-  return found_resids;
+  it = cached_bag_resid_stacks_.emplace(resid, std::move(stacks)).first;
+  return &it->second;
 }
 
 base::expected<const ResolvedBag*, NullOrIOError> AssetManager2::ResolveBag(
@@ -1062,9 +1121,15 @@
 }
 
 base::expected<const ResolvedBag*, NullOrIOError> AssetManager2::GetBag(uint32_t resid) const {
-  std::vector<uint32_t> found_resids;
-  const auto bag = GetBag(resid, found_resids);
-  cached_bag_resid_stacks_.emplace(resid, std::move(found_resids));
+  auto resid_stacks_it = cached_bag_resid_stacks_.find(resid);
+  if (resid_stacks_it == cached_bag_resid_stacks_.end()) {
+    resid_stacks_it = cached_bag_resid_stacks_.emplace(resid, std::vector<uint32_t>{}).first;
+  }
+  const auto bag = GetBag(resid, resid_stacks_it->second);
+  if (UNLIKELY(IsIOError(bag))) {
+    cached_bag_resid_stacks_.erase(resid_stacks_it);
+    return base::unexpected(bag.error());
+  }
   return bag;
 }
 
@@ -1377,25 +1442,40 @@
 }
 
 void AssetManager2::InvalidateCaches(uint32_t diff) {
-  cached_bag_resid_stacks_.clear();
+  cached_resolved_values_.clear();
 
   if (diff == 0xffffffffu) {
     // Everything must go.
     cached_bags_.clear();
+    cached_bag_resid_stacks_.clear();
     return;
   }
 
   // Be more conservative with what gets purged. Only if the bag has other possible
   // variations with respect to what changed (diff) should we remove it.
-  for (auto iter = cached_bags_.cbegin(); iter != cached_bags_.cend();) {
-    if (diff & iter->second->type_spec_flags) {
-      iter = cached_bags_.erase(iter);
+  for (auto stack_it = cached_bag_resid_stacks_.begin();
+       stack_it != cached_bag_resid_stacks_.end();) {
+    const auto it = cached_bags_.find(stack_it->first);
+    if (it == cached_bags_.end()) {
+      stack_it = cached_bag_resid_stacks_.erase(stack_it);
+    } else if ((diff & it->second->type_spec_flags) != 0) {
+      cached_bags_.erase(it);
+      stack_it = cached_bag_resid_stacks_.erase(stack_it);
     } else {
-      ++iter;
+      ++stack_it;  // Keep the item in both caches.
     }
   }
 
-  cached_resolved_values_.clear();
+  // Need to ensure that both bag caches are consistent, as we populate them in the same function.
+  // Iterate over the cached bags to erase the items without the corresponding resid_stack cache
+  // items.
+  for (auto it = cached_bags_.begin(); it != cached_bags_.end();) {
+    if ((diff & it->second->type_spec_flags) != 0) {
+      it = cached_bags_.erase(it);
+    } else {
+      ++it;
+    }
+  }
 }
 
 uint8_t AssetManager2::GetAssignedPackageId(const LoadedPackage* package) const {
@@ -1429,6 +1509,37 @@
   }
 }
 
+AssetManager2::ScopedOperation AssetManager2::StartOperation() const {
+  ++number_of_running_scoped_operations_;
+  return ScopedOperation(*this);
+}
+
+void AssetManager2::FinishOperation() const {
+  if (number_of_running_scoped_operations_ < 1) {
+    ALOGW("Invalid FinishOperation() call when there's none happening");
+    return;
+  }
+  if (--number_of_running_scoped_operations_ == 0) {
+    for (auto&& [_, assets] : apk_assets_) {
+      assets.clear();
+    }
+  }
+}
+
+const AssetManager2::ApkAssetsPtr& AssetManager2::GetApkAssets(ApkAssetsCookie cookie) const {
+  DCHECK(number_of_running_scoped_operations_ > 0) << "Must have an operation running";
+
+  if (cookie < 0 || cookie >= apk_assets_.size()) {
+    static const ApkAssetsPtr empty{};
+    return empty;
+  }
+  auto& [wptr, res] = apk_assets_[cookie];
+  if (!res) {
+    res = wptr.promote();
+  }
+  return res;
+}
+
 Theme::Theme(AssetManager2* asset_manager) : asset_manager_(asset_manager) {
 }
 
@@ -1561,14 +1672,16 @@
     using SourceToDestinationRuntimePackageMap = std::unordered_map<int, int>;
     std::unordered_map<ApkAssetsCookie, SourceToDestinationRuntimePackageMap> src_asset_cookie_id_map;
 
-    // Determine which ApkAssets are loaded in both theme AssetManagers.
-    const auto& src_assets = source.asset_manager_->GetApkAssets();
-    for (size_t i = 0; i < src_assets.size(); i++) {
-      const ApkAssets* src_asset = src_assets[i];
+    auto op_src = source.asset_manager_->StartOperation();
+    auto op_dst = asset_manager_->StartOperation();
 
-      const auto& dest_assets = asset_manager_->GetApkAssets();
-      for (size_t j = 0; j < dest_assets.size(); j++) {
-        const ApkAssets* dest_asset = dest_assets[j];
+    for (size_t i = 0; i < source.asset_manager_->GetApkAssetsCount(); i++) {
+      const auto& src_asset = source.asset_manager_->GetApkAssets(i);
+      if (!src_asset) {
+        continue;
+      }
+      for (int j = 0; j < asset_manager_->GetApkAssetsCount(); j++) {
+        const auto& dest_asset = asset_manager_->GetApkAssets(j);
         if (src_asset != dest_asset) {
           // ResourcesManager caches and reuses ApkAssets when the same apk must be present in
           // multiple AssetManagers. Two ApkAssets point to the same version of the same resources
@@ -1694,4 +1807,11 @@
   }
 }
 
+AssetManager2::ScopedOperation::ScopedOperation(const AssetManager2& am) : am_(am) {
+}
+
+AssetManager2::ScopedOperation::~ScopedOperation() {
+  am_.FinishOperation();
+}
+
 }  // namespace android
diff --git a/libs/androidfw/Idmap.cpp b/libs/androidfw/Idmap.cpp
index 8983574..5f98b8f 100644
--- a/libs/androidfw/Idmap.cpp
+++ b/libs/androidfw/Idmap.cpp
@@ -294,14 +294,14 @@
                                dtohl(header->version), kIdmapCurrentVersion);
     return {};
   }
+  std::optional<std::string_view> target_path = ReadString(&data_ptr, &data_size, "target path");
+    if (!target_path) {
+      return {};
+    }
   std::optional<std::string_view> overlay_path = ReadString(&data_ptr, &data_size, "overlay path");
   if (!overlay_path) {
     return {};
   }
-  std::optional<std::string_view> target_path = ReadString(&data_ptr, &data_size, "target path");
-  if (!target_path) {
-    return {};
-  }
   if (!ReadString(&data_ptr, &data_size, "target name") ||
       !ReadString(&data_ptr, &data_size, "debug info")) {
     return {};
@@ -364,7 +364,7 @@
   return std::unique_ptr<LoadedIdmap>(
       new LoadedIdmap(std::string(idmap_path), header, data_header, target_entries,
                       target_inline_entries, target_inline_entry_values, configurations,
-                      overlay_entries, std::move(idmap_string_pool), *target_path, *overlay_path));
+                      overlay_entries, std::move(idmap_string_pool), *overlay_path, *target_path));
 }
 
 bool LoadedIdmap::IsUpToDate() const {
diff --git a/libs/androidfw/LoadedArsc.cpp b/libs/androidfw/LoadedArsc.cpp
index c0fdfe2..c9d5e07 100644
--- a/libs/androidfw/LoadedArsc.cpp
+++ b/libs/androidfw/LoadedArsc.cpp
@@ -323,7 +323,7 @@
 }
 
 base::expected<std::monostate, IOError> LoadedPackage::CollectConfigurations(
-    bool exclude_mipmap, std::set<ResTable_config>* out_configs) const {\
+    bool exclude_mipmap, std::set<ResTable_config>* out_configs) const {
   for (const auto& type_spec : type_specs_) {
     if (exclude_mipmap) {
       const int type_idx = type_spec.first - 1;
@@ -494,6 +494,8 @@
   util::ReadUtf16StringFromDevice(header->name, arraysize(header->name),
                                   &loaded_package->package_name_);
 
+  const bool only_overlayable = (property_flags & PROPERTY_ONLY_OVERLAYABLES) != 0;
+
   // A map of TypeSpec builders, each associated with an type index.
   // We use these to accumulate the set of Types available for a TypeSpec, and later build a single,
   // contiguous block of memory that holds all the Types together with the TypeSpec.
@@ -502,6 +504,9 @@
   ChunkIterator iter(chunk.data_ptr(), chunk.data_size());
   while (iter.HasNext()) {
     const Chunk child_chunk = iter.Next();
+    if (only_overlayable && child_chunk.type() != RES_TABLE_OVERLAYABLE_TYPE) {
+      continue;
+    }
     switch (child_chunk.type()) {
       case RES_STRING_POOL_TYPE: {
         const auto pool_address = child_chunk.header<ResChunk_header>();
@@ -655,6 +660,9 @@
                      << name_to_actor_it->first << "'.";
           return {};
         }
+        if (only_overlayable) {
+          break;
+        }
 
         // Iterate over the overlayable policy chunks contained within the overlayable chunk data
         ChunkIterator overlayable_iter(child_chunk.data_ptr(), child_chunk.data_size());
@@ -800,14 +808,21 @@
     global_string_pool_ = util::make_unique<OverlayStringPool>(loaded_idmap);
   }
 
+  const bool only_overlayable = (property_flags & PROPERTY_ONLY_OVERLAYABLES) != 0;
+
   const size_t package_count = dtohl(header->packageCount);
   size_t packages_seen = 0;
 
-  packages_.reserve(package_count);
+  if (!only_overlayable) {
+    packages_.reserve(package_count);
+  }
 
   ChunkIterator iter(chunk.data_ptr(), chunk.data_size());
   while (iter.HasNext()) {
     const Chunk child_chunk = iter.Next();
+    if (only_overlayable && child_chunk.type() != RES_TABLE_PACKAGE_TYPE) {
+      continue;
+    }
     switch (child_chunk.type()) {
       case RES_STRING_POOL_TYPE:
         // Only use the first string pool. Ignore others.
@@ -837,6 +852,10 @@
           return false;
         }
         packages_.push_back(std::move(loaded_package));
+        if (only_overlayable) {
+          // Overlayable is always in the first package, no need to process anything else.
+          return true;
+        }
       } break;
 
       default:
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index 29d33da..5a63612 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -1769,13 +1769,21 @@
 
 status_t ResXMLTree::setTo(const void* data, size_t size, bool copyData)
 {
+    const ResChunk_header* chunk = nullptr;
+    const ResChunk_header* lastChunk = nullptr;
+
     uninit();
     mEventCode = START_DOCUMENT;
 
     if (!data || !size) {
         return (mError=BAD_TYPE);
     }
-
+    if (size < sizeof(ResXMLTree_header)) {
+        ALOGW("Bad XML block: total size %d is less than the header size %d\n",
+              int(size), int(sizeof(ResXMLTree_header)));
+        mError = BAD_TYPE;
+        goto done;
+    }
     if (copyData) {
         mOwnedData = malloc(size);
         if (mOwnedData == NULL) {
@@ -1792,9 +1800,15 @@
              (int)dtohs(mHeader->header.headerSize),
              (int)dtohl(mHeader->header.size), (int)size);
         mError = BAD_TYPE;
-        restart();
-        return mError;
+        goto done;
     }
+    if (dtohs(mHeader->header.type) != RES_XML_TYPE) {
+        ALOGW("Bad XML block: expected root block type %d, got %d\n",
+            int(RES_XML_TYPE), int(dtohs(mHeader->header.type)));
+        mError = BAD_TYPE;
+        goto done;
+    }
+
     mDataEnd = ((const uint8_t*)mHeader) + mSize;
 
     mStrings.uninit();
@@ -1804,9 +1818,8 @@
 
     // First look for a couple interesting chunks: the string block
     // and first XML node.
-    const ResChunk_header* chunk =
-        (const ResChunk_header*)(((const uint8_t*)mHeader) + dtohs(mHeader->header.headerSize));
-    const ResChunk_header* lastChunk = chunk;
+    chunk = (const ResChunk_header*)(((const uint8_t*)mHeader) + dtohs(mHeader->header.headerSize));
+    lastChunk = chunk;
     while (((const uint8_t*)chunk) < (mDataEnd-sizeof(ResChunk_header)) &&
            ((const uint8_t*)chunk) < (mDataEnd-dtohl(chunk->size))) {
         status_t err = validate_chunk(chunk, sizeof(ResChunk_header), mDataEnd, "XML");
@@ -1860,7 +1873,11 @@
     mError = mStrings.getError();
 
 done:
-    restart();
+    if (mError) {
+        uninit();
+    } else {
+        restart();
+    }
     return mError;
 }
 
@@ -5436,37 +5453,66 @@
     return U16StringToInt(s, len, outValue);
 }
 
-bool ResTable::stringToFloat(const char16_t* s, size_t len, Res_value* outValue)
-{
-    while (len > 0 && isspace16(*s)) {
-        s++;
-        len--;
+template <typename T>
+bool parseFloatingPoint(const char16_t* inBuf, size_t inLen, char* tempBuf,
+                                  const char** outEnd, T& out){
+    while (inLen > 0 && isspace16(*inBuf)) {
+        inBuf++;
+        inLen--;
     }
 
-    if (len <= 0) {
+    if (inLen <= 0) {
         return false;
     }
 
-    char buf[128];
     int i=0;
-    while (len > 0 && *s != 0 && i < 126) {
-        if (*s > 255) {
+    while (inLen > 0 && *inBuf != 0 && i < 126) {
+        if (*inBuf > 255) {
             return false;
         }
-        buf[i++] = *s++;
-        len--;
+        tempBuf[i++] = *inBuf++;
+        inLen--;
     }
 
-    if (len > 0) {
+    if (inLen > 0) {
         return false;
     }
-    if ((buf[0] < '0' || buf[0] > '9') && buf[0] != '.' && buf[0] != '-' && buf[0] != '+') {
+    if ((tempBuf[0] < '0' || tempBuf[0] > '9') && tempBuf[0] != '.' && tempBuf[0] != '-' && tempBuf[0] != '+') {
         return false;
     }
 
-    buf[i] = 0;
-    const char* end;
-    float f = strtof(buf, (char**)&end);
+    tempBuf[i] = 0;
+    if constexpr(std::is_same_v<T, float>) {
+        out = strtof(tempBuf, (char**)outEnd);
+    } else {
+        out = strtod(tempBuf, (char**)outEnd);
+    }
+    return true;
+}
+
+bool ResTable::stringToDouble(const char16_t* s, size_t len, double& d){
+    char buf[128];
+    const char* end = nullptr;
+    if (!parseFloatingPoint(s, len, buf, &end, d)) {
+        return false;
+    }
+
+    while (*end != 0 && isspace((unsigned char)*end)) {
+        end++;
+    }
+
+    return *end == 0;
+}
+
+bool ResTable::stringToFloat(const char16_t* s, size_t len, Res_value* outValue)
+{
+    char buf[128];
+    const char* end = nullptr;
+    float f;
+
+    if (!parseFloatingPoint(s, len, buf, &end, f)) {
+        return false;
+    }
 
     if (*end != 0 && !isspace((unsigned char)*end)) {
         // Might be a unit...
diff --git a/libs/androidfw/ZipFileRO.cpp b/libs/androidfw/ZipFileRO.cpp
index 52e7a70..d7b5914 100644
--- a/libs/androidfw/ZipFileRO.cpp
+++ b/libs/androidfw/ZipFileRO.cpp
@@ -40,17 +40,24 @@
 public:
     ZipEntry entry;
     std::string_view name;
-    void *cookie;
+    void *cookie = nullptr;
 
-    _ZipEntryRO() : cookie(NULL) {}
+    _ZipEntryRO() = default;
 
     ~_ZipEntryRO() {
-      EndIteration(cookie);
+        EndIteration(cookie);
+    }
+
+    android::ZipEntryRO convertToPtr() {
+        _ZipEntryRO* result = new _ZipEntryRO;
+        result->entry = std::move(this->entry);
+        result->name = std::move(this->name);
+        result->cookie = std::exchange(this->cookie, nullptr);
+        return result;
     }
 
 private:
-    _ZipEntryRO(const _ZipEntryRO& other);
-    _ZipEntryRO& operator=(const _ZipEntryRO& other);
+    DISALLOW_COPY_AND_ASSIGN(_ZipEntryRO);
 };
 
 ZipFileRO::~ZipFileRO() {
@@ -94,17 +101,15 @@
 
 ZipEntryRO ZipFileRO::findEntryByName(const char* entryName) const
 {
-    _ZipEntryRO* data = new _ZipEntryRO;
+    _ZipEntryRO data;
+    data.name = entryName;
 
-    data->name = entryName;
-
-    const int32_t error = FindEntry(mHandle, entryName, &(data->entry));
+    const int32_t error = FindEntry(mHandle, entryName, &(data.entry));
     if (error) {
-        delete data;
-        return NULL;
+        return nullptr;
     }
 
-    return (ZipEntryRO) data;
+    return data.convertToPtr();
 }
 
 /*
@@ -143,35 +148,50 @@
 }
 
 bool ZipFileRO::startIteration(void** cookie) {
-  return startIteration(cookie, NULL, NULL);
+  return startIteration(cookie, nullptr, nullptr);
 }
 
-bool ZipFileRO::startIteration(void** cookie, const char* prefix, const char* suffix)
-{
-    _ZipEntryRO* ze = new _ZipEntryRO;
-    int32_t error = StartIteration(mHandle, &(ze->cookie),
+bool ZipFileRO::startIteration(void** cookie, const char* prefix, const char* suffix) {
+    auto result = startIterationOrError(prefix, suffix);
+    if (!result.ok()) {
+        return false;
+    }
+    *cookie = result.value();
+    return true;
+}
+
+base::expected<void*, int32_t>
+ZipFileRO::startIterationOrError(const char* prefix, const char* suffix) {
+    _ZipEntryRO ze;
+    int32_t error = StartIteration(mHandle, &(ze.cookie),
                                    prefix ? prefix : "", suffix ? suffix : "");
     if (error) {
         ALOGW("Could not start iteration over %s: %s", mFileName != NULL ? mFileName : "<null>",
                 ErrorCodeString(error));
-        delete ze;
-        return false;
+        return base::unexpected(error);
     }
 
-    *cookie = ze;
-    return true;
+    return ze.convertToPtr();
 }
 
-ZipEntryRO ZipFileRO::nextEntry(void* cookie)
-{
+ZipEntryRO ZipFileRO::nextEntry(void* cookie) {
+    auto result = nextEntryOrError(cookie);
+    if (!result.ok()) {
+        return nullptr;
+    }
+    return result.value();
+}
+
+base::expected<ZipEntryRO, int32_t> ZipFileRO::nextEntryOrError(void* cookie) {
     _ZipEntryRO* ze = reinterpret_cast<_ZipEntryRO*>(cookie);
     int32_t error = Next(ze->cookie, &(ze->entry), &(ze->name));
     if (error) {
         if (error != -1) {
             ALOGW("Error iteration over %s: %s", mFileName != NULL ? mFileName : "<null>",
                     ErrorCodeString(error));
+            return base::unexpected(error);
         }
-        return NULL;
+        return nullptr;
     }
 
     return &(ze->entry);
diff --git a/libs/androidfw/include/androidfw/ApkAssets.h b/libs/androidfw/include/androidfw/ApkAssets.h
index 6f88f41..1fa6752 100644
--- a/libs/androidfw/include/androidfw/ApkAssets.h
+++ b/libs/androidfw/include/androidfw/ApkAssets.h
@@ -17,12 +17,13 @@
 #ifndef APKASSETS_H_
 #define APKASSETS_H_
 
+#include <utils/RefBase.h>
+
 #include <memory>
 #include <string>
 
 #include "android-base/macros.h"
 #include "android-base/unique_fd.h"
-
 #include "androidfw/Asset.h"
 #include "androidfw/AssetsProvider.h"
 #include "androidfw/Idmap.h"
@@ -31,34 +32,33 @@
 
 namespace android {
 
+class ApkAssets;
+
+using ApkAssetsPtr = sp<ApkAssets>;
+
 // Holds an APK.
-class ApkAssets {
+class ApkAssets : public RefBase {
  public:
   // Creates an ApkAssets from a path on device.
-  static std::unique_ptr<ApkAssets> Load(const std::string& path,
-                                         package_property_t flags = 0U);
+  static ApkAssetsPtr Load(const std::string& path, package_property_t flags = 0U);
 
   // Creates an ApkAssets from an open file descriptor.
-  static std::unique_ptr<ApkAssets> LoadFromFd(base::unique_fd fd,
-                                               const std::string& debug_name,
-                                               package_property_t flags = 0U,
-                                               off64_t offset = 0,
-                                               off64_t len = AssetsProvider::kUnknownLength);
+  static ApkAssetsPtr LoadFromFd(base::unique_fd fd, const std::string& debug_name,
+                                 package_property_t flags = 0U, off64_t offset = 0,
+                                 off64_t len = AssetsProvider::kUnknownLength);
 
   // Creates an ApkAssets from an AssetProvider.
   // The ApkAssets will take care of destroying the AssetsProvider when it is destroyed.
-  static std::unique_ptr<ApkAssets> Load(std::unique_ptr<AssetsProvider> assets,
-                                         package_property_t flags = 0U);
+  static ApkAssetsPtr Load(std::unique_ptr<AssetsProvider> assets, package_property_t flags = 0U);
 
   // Creates an ApkAssets from the given asset file representing a resources.arsc.
-  static std::unique_ptr<ApkAssets> LoadTable(std::unique_ptr<Asset> resources_asset,
-                                              std::unique_ptr<AssetsProvider> assets,
-                                              package_property_t flags = 0U);
+  static ApkAssetsPtr LoadTable(std::unique_ptr<Asset> resources_asset,
+                                std::unique_ptr<AssetsProvider> assets,
+                                package_property_t flags = 0U);
 
   // Creates an ApkAssets from an IDMAP, which contains the original APK path, and the overlay
   // data.
-  static std::unique_ptr<ApkAssets> LoadOverlay(const std::string& idmap_path,
-                                                package_property_t flags = 0U);
+  static ApkAssetsPtr LoadOverlay(const std::string& idmap_path, package_property_t flags = 0U);
 
   // Path to the contents of the ApkAssets on disk. The path could represent an APk, a directory,
   // or some other file type.
@@ -95,22 +95,27 @@
   bool IsUpToDate() const;
 
  private:
-  static std::unique_ptr<ApkAssets> LoadImpl(std::unique_ptr<AssetsProvider> assets,
-                                             package_property_t property_flags,
-                                             std::unique_ptr<Asset> idmap_asset,
-                                             std::unique_ptr<LoadedIdmap> loaded_idmap);
+  static ApkAssetsPtr LoadImpl(std::unique_ptr<AssetsProvider> assets,
+                               package_property_t property_flags,
+                               std::unique_ptr<Asset> idmap_asset,
+                               std::unique_ptr<LoadedIdmap> loaded_idmap);
 
-  static std::unique_ptr<ApkAssets> LoadImpl(std::unique_ptr<Asset> resources_asset,
-                                             std::unique_ptr<AssetsProvider> assets,
-                                             package_property_t property_flags,
-                                             std::unique_ptr<Asset> idmap_asset,
-                                             std::unique_ptr<LoadedIdmap> loaded_idmap);
+  static ApkAssetsPtr LoadImpl(std::unique_ptr<Asset> resources_asset,
+                               std::unique_ptr<AssetsProvider> assets,
+                               package_property_t property_flags,
+                               std::unique_ptr<Asset> idmap_asset,
+                               std::unique_ptr<LoadedIdmap> loaded_idmap);
 
-  ApkAssets(std::unique_ptr<Asset> resources_asset,
-            std::unique_ptr<LoadedArsc> loaded_arsc,
-            std::unique_ptr<AssetsProvider> assets,
-            package_property_t property_flags,
-            std::unique_ptr<Asset> idmap_asset,
+  // Allows us to make it possible to call make_shared from inside the class but still keeps the
+  // ctor 'private' for all means and purposes.
+  struct PrivateConstructorUtil {
+    explicit PrivateConstructorUtil() = default;
+  };
+
+ public:
+  ApkAssets(PrivateConstructorUtil, std::unique_ptr<Asset> resources_asset,
+            std::unique_ptr<LoadedArsc> loaded_arsc, std::unique_ptr<AssetsProvider> assets,
+            package_property_t property_flags, std::unique_ptr<Asset> idmap_asset,
             std::unique_ptr<LoadedIdmap> loaded_idmap);
 
   std::unique_ptr<Asset> resources_asset_;
diff --git a/libs/androidfw/include/androidfw/AssetManager2.h b/libs/androidfw/include/androidfw/AssetManager2.h
index f10cb9b..f611d0d 100644
--- a/libs/androidfw/include/androidfw/AssetManager2.h
+++ b/libs/androidfw/include/androidfw/AssetManager2.h
@@ -17,14 +17,16 @@
 #ifndef ANDROIDFW_ASSETMANAGER2_H_
 #define ANDROIDFW_ASSETMANAGER2_H_
 
-#include "android-base/function_ref.h"
-#include "android-base/macros.h"
+#include <utils/RefBase.h>
 
 #include <array>
 #include <limits>
 #include <set>
+#include <span>
 #include <unordered_map>
 
+#include "android-base/function_ref.h"
+#include "android-base/macros.h"
 #include "androidfw/ApkAssets.h"
 #include "androidfw/Asset.h"
 #include "androidfw/AssetManager.h"
@@ -94,8 +96,25 @@
     size_t entry_len = 0u;
   };
 
-  AssetManager2();
+  using ApkAssetsPtr = sp<const ApkAssets>;
+  using ApkAssetsWPtr = wp<const ApkAssets>;
+  using ApkAssetsList = std::span<const ApkAssetsPtr>;
+
+  AssetManager2() = default;
   explicit AssetManager2(AssetManager2&& other) = default;
+  AssetManager2(ApkAssetsList apk_assets, const ResTable_config& configuration);
+
+  struct ScopedOperation {
+    DISALLOW_COPY_AND_ASSIGN(ScopedOperation);
+    friend AssetManager2;
+    const AssetManager2& am_;
+    ScopedOperation(const AssetManager2& am);
+
+   public:
+    ~ScopedOperation();
+  };
+
+  [[nodiscard]] ScopedOperation StartOperation() const;
 
   // Sets/resets the underlying ApkAssets for this AssetManager. The ApkAssets
   // are not owned by the AssetManager, and must have a longer lifetime.
@@ -103,10 +122,12 @@
   // Only pass invalidate_caches=false when it is known that the structure
   // change in ApkAssets is due to a safe addition of resources with completely
   // new resource IDs.
-  bool SetApkAssets(std::vector<const ApkAssets*> apk_assets, bool invalidate_caches = true);
+  bool SetApkAssets(ApkAssetsList apk_assets, bool invalidate_caches = true);
+  bool SetApkAssets(std::initializer_list<ApkAssetsPtr> apk_assets, bool invalidate_caches = true);
 
-  inline const std::vector<const ApkAssets*>& GetApkAssets() const {
-    return apk_assets_;
+  const ApkAssetsPtr& GetApkAssets(ApkAssetsCookie cookie) const;
+  int GetApkAssetsCount() const {
+    return int(apk_assets_.size());
   }
 
   // Returns the string pool for the given asset cookie.
@@ -222,9 +243,14 @@
     friend AssetManager2;
     friend Theme;
     SelectedValue() = default;
-    SelectedValue(const ResolvedBag* bag, const ResolvedBag::Entry& entry) :
-        cookie(entry.cookie), data(entry.value.data), type(entry.value.dataType),
-        flags(bag->type_spec_flags), resid(0U), config({}) {};
+    SelectedValue(const ResolvedBag* bag, const ResolvedBag::Entry& entry)
+        : cookie(entry.cookie),
+          data(entry.value.data),
+          type(entry.value.dataType),
+          flags(bag->type_spec_flags),
+          resid(0U),
+          config() {
+    }
 
     // The cookie representing the ApkAssets in which the value resides.
     ApkAssetsCookie cookie = kInvalidCookie;
@@ -306,7 +332,8 @@
   // resource data failed.
   base::expected<uint32_t, NullOrIOError> GetResourceTypeSpecFlags(uint32_t resid) const;
 
-  const std::vector<uint32_t> GetBagResIdStack(uint32_t resid) const;
+  base::expected<const std::vector<uint32_t>*, NullOrIOError> GetBagResIdStack(
+      uint32_t resid) const;
 
   // Resets the resource resolution structures in preparation for the next resource retrieval.
   void ResetResourceResolution() const;
@@ -399,7 +426,7 @@
 
   // Assigns package IDs to all shared library ApkAssets.
   // Should be called whenever the ApkAssets are changed.
-  void BuildDynamicRefTable();
+  void BuildDynamicRefTable(ApkAssetsList assets);
 
   // Purge all resources that are cached and vary by the configuration axis denoted by the
   // bitmask `diff`.
@@ -410,16 +437,23 @@
   void RebuildFilterList();
 
   // Retrieves the APK paths of overlays that overlay non-system packages.
-  std::set<const ApkAssets*> GetNonSystemOverlays() const;
+  std::set<ApkAssetsPtr> GetNonSystemOverlays() const;
 
   // AssetManager2::GetBag(resid) wraps this function to track which resource ids have already
   // been seen while traversing bag parents.
   base::expected<const ResolvedBag*, NullOrIOError> GetBag(
       uint32_t resid, std::vector<uint32_t>& child_resids) const;
 
+  // Finish an operation that was running with the current asset manager, and clean up the
+  // promoted apk assets when the last operation ends.
+  void FinishOperation() const;
+
   // The ordered list of ApkAssets to search. These are not owned by the AssetManager, and must
   // have a longer lifetime.
-  std::vector<const ApkAssets*> apk_assets_;
+  // The second pair element is the promoted version of the assets, that is held for the duration
+  // of the currently running operation. FinishOperation() clears all promoted assets to make sure
+  // they can be released when the system needs that.
+  mutable std::vector<std::pair<ApkAssetsWPtr, ApkAssetsPtr>> apk_assets_;
 
   // DynamicRefTables for shared library package resolution.
   // These are ordered according to apk_assets_. The mappings may change depending on what is
@@ -433,7 +467,7 @@
 
   // The current configuration set for this AssetManager. When this changes, cached resources
   // may need to be purged.
-  ResTable_config configuration_;
+  ResTable_config configuration_ = {};
 
   // Cached set of bags. These are cached because they can inherit keys from parent bags,
   // which involves some calculation.
@@ -446,6 +480,10 @@
   // Cached set of resolved resource values.
   mutable std::unordered_map<uint32_t, SelectedValue> cached_resolved_values_;
 
+  // Tracking the number of the started operations running with the current AssetManager.
+  // Finishing the last one clears all promoted apk assets.
+  mutable int number_of_running_scoped_operations_ = 0;
+
   // Whether or not to save resource resolution steps
   bool resource_resolution_logging_enabled_ = false;
 
@@ -463,10 +501,10 @@
       // Marks what kind of override this step was.
       Type type;
 
+      ApkAssetsCookie cookie = kInvalidCookie;
+
       // Built name of configuration for this step.
       String8 config_name;
-
-      ApkAssetsCookie cookie = kInvalidCookie;
     };
 
     // Last resolved resource ID.
diff --git a/libs/androidfw/include/androidfw/Errors.h b/libs/androidfw/include/androidfw/Errors.h
index 948162d..6667747 100644
--- a/libs/androidfw/include/androidfw/Errors.h
+++ b/libs/androidfw/include/androidfw/Errors.h
@@ -34,7 +34,7 @@
 
 // Checks whether the result holds an unexpected I/O error.
 template <typename T>
-static inline bool IsIOError(const base::expected<T, NullOrIOError> result) {
+static inline bool IsIOError(const base::expected<T, NullOrIOError>& result) {
   return !result.has_value() && std::holds_alternative<IOError>(result.error());
 }
 
diff --git a/libs/androidfw/include/androidfw/IDiagnostics.h b/libs/androidfw/include/androidfw/IDiagnostics.h
index 4d5844e..865a298 100644
--- a/libs/androidfw/include/androidfw/IDiagnostics.h
+++ b/libs/androidfw/include/androidfw/IDiagnostics.h
@@ -86,6 +86,17 @@
     DiagMessageActual actual = message.Build();
     Log(Level::Note, actual);
   }
+
+  virtual void SetVerbose(bool val) {
+    verbose_ = val;
+  }
+
+  virtual bool IsVerbose() {
+    return verbose_;
+  }
+
+  private:
+    bool verbose_ = false;
 };
 
 class SourcePathDiagnostics : public IDiagnostics {
@@ -105,6 +116,14 @@
     return error;
   }
 
+  void SetVerbose(bool val) override {
+    diag_->SetVerbose(val);
+  }
+
+  bool IsVerbose() override {
+    return diag_->IsVerbose();
+  }
+
  private:
   Source source_;
   IDiagnostics* diag_;
diff --git a/libs/androidfw/include/androidfw/LoadedArsc.h b/libs/androidfw/include/androidfw/LoadedArsc.h
index 4d12885..3a72871 100644
--- a/libs/androidfw/include/androidfw/LoadedArsc.h
+++ b/libs/androidfw/include/androidfw/LoadedArsc.h
@@ -96,6 +96,9 @@
   // The apk assets is owned by the application running in this process and incremental crash
   // protections for this APK must be disabled.
   PROPERTY_DISABLE_INCREMENTAL_HARDENING = 1U << 4U,
+
+  // The apk assets only contain the overlayable declarations information.
+  PROPERTY_ONLY_OVERLAYABLES = 1U << 5U,
 };
 
 struct OverlayableInfo {
diff --git a/libs/androidfw/include/androidfw/MutexGuard.h b/libs/androidfw/include/androidfw/MutexGuard.h
index 6fc6d64..b6093db 100644
--- a/libs/androidfw/include/androidfw/MutexGuard.h
+++ b/libs/androidfw/include/androidfw/MutexGuard.h
@@ -14,12 +14,12 @@
  * limitations under the License.
  */
 
-#ifndef ANDROIDFW_MUTEXGUARD_H
-#define ANDROIDFW_MUTEXGUARD_H
+#pragma once
 
 #include <mutex>
 #include <optional>
 #include <type_traits>
+#include <utility>
 
 #include "android-base/macros.h"
 
@@ -45,20 +45,25 @@
 //
 template <typename T>
 class Guarded {
-  static_assert(!std::is_pointer<T>::value, "T must not be a raw pointer");
+  static_assert(!std::is_pointer_v<T>, "T must not be a raw pointer");
 
  public:
-  Guarded() : guarded_(std::in_place, T()) {
+  Guarded() : guarded_(std::in_place) {
   }
 
   explicit Guarded(const T& guarded) : guarded_(std::in_place, guarded) {
   }
 
-  explicit Guarded(T&& guarded) : guarded_(std::in_place, std::forward<T>(guarded)) {
+  explicit Guarded(T&& guarded) : guarded_(std::in_place, std::move(guarded)) {
   }
 
-  ~Guarded() {
-    std::lock_guard<std::mutex> scoped_lock(lock_);
+  // Unfortunately, some legacy designs make even class deletion race-prone, where some other
+  // thread may have not finished working with the same object. For those cases one may destroy the
+  // object under a lock (but please fix your code, at least eventually!).
+  template <class Func>
+  void safeDelete(Func f) {
+    std::lock_guard scoped_lock(lock_);
+    f(guarded_ ? &guarded_.value() : nullptr);
     guarded_.reset();
   }
 
@@ -96,5 +101,3 @@
 };
 
 }  // namespace android
-
-#endif  // ANDROIDFW_MUTEXGUARD_H
diff --git a/libs/androidfw/include/androidfw/ObbFile.h b/libs/androidfw/include/androidfw/ObbFile.h
index 3dbf997d..38ece5c1 100644
--- a/libs/androidfw/include/androidfw/ObbFile.h
+++ b/libs/androidfw/include/androidfw/ObbFile.h
@@ -43,10 +43,6 @@
     bool removeFrom(const char* filename);
     bool removeFrom(int fd);
 
-    const char* getFileName() const {
-        return mFileName;
-    }
-
     const String8 getPackageName() const {
         return mPackageName;
     }
@@ -127,8 +123,6 @@
     /* The encryption salt. */
     unsigned char mSalt[8];
 
-    const char* mFileName;
-
     size_t mFooterStart;
 
     bool parseObbFile(int fd);
diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h
index 631bda4..4eb1d7a 100644
--- a/libs/androidfw/include/androidfw/ResourceTypes.h
+++ b/libs/androidfw/include/androidfw/ResourceTypes.h
@@ -2162,6 +2162,7 @@
 
     static bool stringToInt(const char16_t* s, size_t len, Res_value* outValue);
     static bool stringToFloat(const char16_t* s, size_t len, Res_value* outValue);
+    static bool stringToDouble(const char16_t* s, size_t len, double& outValue);
 
     // Used with stringToValue.
     class Accessor
diff --git a/libs/androidfw/include/androidfw/ZipFileRO.h b/libs/androidfw/include/androidfw/ZipFileRO.h
index 10f6d06..be1f98f 100644
--- a/libs/androidfw/include/androidfw/ZipFileRO.h
+++ b/libs/androidfw/include/androidfw/ZipFileRO.h
@@ -37,6 +37,8 @@
 #include <unistd.h>
 #include <time.h>
 
+#include <android-base/expected.h>
+
 #include <util/map_ptr.h>
 
 #include <utils/Compat.h>
@@ -102,6 +104,11 @@
      */
     bool startIteration(void** cookie);
     bool startIteration(void** cookie, const char* prefix, const char* suffix);
+    /*
+     * Same as above, but returns the error code in case of failure.
+     * #see libziparchive/zip_error.h.
+     */
+    base::expected<void*, int32_t> startIterationOrError(const char* prefix, const char* suffix);
 
     /**
      * Return the next entry in iteration order, or NULL if there are no more
@@ -109,6 +116,12 @@
      */
     ZipEntryRO nextEntry(void* cookie);
 
+    /**
+     * Same as above, but returns the error code in case of failure.
+     * #see libziparchive/zip_error.h.
+     */
+    base::expected<ZipEntryRO, int32_t> nextEntryOrError(void* cookie);
+
     void endIteration(void* cookie);
 
     void releaseEntry(ZipEntryRO entry) const;
diff --git a/libs/androidfw/tests/ApkAssets_test.cpp b/libs/androidfw/tests/ApkAssets_test.cpp
index 19db25c..70326b7 100644
--- a/libs/androidfw/tests/ApkAssets_test.cpp
+++ b/libs/androidfw/tests/ApkAssets_test.cpp
@@ -35,8 +35,7 @@
 namespace android {
 
 TEST(ApkAssetsTest, LoadApk) {
-  std::unique_ptr<const ApkAssets> loaded_apk =
-      ApkAssets::Load(GetTestDataPath() + "/basic/basic.apk");
+  auto loaded_apk = ApkAssets::Load(GetTestDataPath() + "/basic/basic.apk");
   ASSERT_THAT(loaded_apk, NotNull());
 
   const LoadedArsc* loaded_arsc = loaded_apk->GetLoadedArsc();
@@ -50,7 +49,7 @@
   unique_fd fd(::open(path.c_str(), O_RDONLY | O_BINARY));
   ASSERT_THAT(fd.get(), Ge(0));
 
-  std::unique_ptr<const ApkAssets> loaded_apk = ApkAssets::LoadFromFd(std::move(fd), path);
+  auto loaded_apk = ApkAssets::LoadFromFd(std::move(fd), path);
   ASSERT_THAT(loaded_apk, NotNull());
 
   const LoadedArsc* loaded_arsc = loaded_apk->GetLoadedArsc();
@@ -60,8 +59,7 @@
 }
 
 TEST(ApkAssetsTest, LoadApkAsSharedLibrary) {
-  std::unique_ptr<const ApkAssets> loaded_apk =
-      ApkAssets::Load(GetTestDataPath() + "/appaslib/appaslib.apk");
+  auto loaded_apk = ApkAssets::Load(GetTestDataPath() + "/appaslib/appaslib.apk");
   ASSERT_THAT(loaded_apk, NotNull());
 
   const LoadedArsc* loaded_arsc = loaded_apk->GetLoadedArsc();
@@ -79,8 +77,7 @@
 }
 
 TEST(ApkAssetsTest, CreateAndDestroyAssetKeepsApkAssetsOpen) {
-  std::unique_ptr<const ApkAssets> loaded_apk =
-      ApkAssets::Load(GetTestDataPath() + "/basic/basic.apk");
+  auto loaded_apk = ApkAssets::Load(GetTestDataPath() + "/basic/basic.apk");
   ASSERT_THAT(loaded_apk, NotNull());
 
   { ASSERT_THAT(loaded_apk->GetAssetsProvider()->Open("res/layout/main.xml",
@@ -91,8 +88,7 @@
 }
 
 TEST(ApkAssetsTest, OpenUncompressedAssetFd) {
-  std::unique_ptr<const ApkAssets> loaded_apk =
-      ApkAssets::Load(GetTestDataPath() + "/basic/basic.apk");
+  auto loaded_apk = ApkAssets::Load(GetTestDataPath() + "/basic/basic.apk");
   ASSERT_THAT(loaded_apk, NotNull());
 
   auto asset = loaded_apk->GetAssetsProvider()->Open("assets/uncompressed.txt",
diff --git a/libs/androidfw/tests/ApkParsing_test.cpp b/libs/androidfw/tests/ApkParsing_test.cpp
index 62e88c6..ac1dc9b 100644
--- a/libs/androidfw/tests/ApkParsing_test.cpp
+++ b/libs/androidfw/tests/ApkParsing_test.cpp
@@ -74,4 +74,10 @@
   auto lastSlash = util::ValidLibraryPathLastSlash(path, false, false);
   ASSERT_THAT(lastSlash, IsNull());
 }
+
+TEST(ApkParsingTest, InvalidPrefix) {
+  const char* path = "assets/libhello.so";
+  auto lastSlash = util::ValidLibraryPathLastSlash(path, false, false);
+  ASSERT_THAT(lastSlash, IsNull());
+}
 }
\ No newline at end of file
diff --git a/libs/androidfw/tests/AssetManager2_bench.cpp b/libs/androidfw/tests/AssetManager2_bench.cpp
index c7ae618..6fae72a 100644
--- a/libs/androidfw/tests/AssetManager2_bench.cpp
+++ b/libs/androidfw/tests/AssetManager2_bench.cpp
@@ -38,9 +38,9 @@
 static void BM_AssetManagerLoadAssets(benchmark::State& state) {
   std::string path = GetTestDataPath() + "/basic/basic.apk";
   while (state.KeepRunning()) {
-    std::unique_ptr<const ApkAssets> apk = ApkAssets::Load(path);
+    auto apk = ApkAssets::Load(path);
     AssetManager2 assets;
-    assets.SetApkAssets({apk.get()});
+    assets.SetApkAssets({apk});
   }
 }
 BENCHMARK(BM_AssetManagerLoadAssets);
@@ -61,9 +61,9 @@
 static void BM_AssetManagerLoadFrameworkAssets(benchmark::State& state) {
   std::string path = kFrameworkPath;
   while (state.KeepRunning()) {
-    std::unique_ptr<const ApkAssets> apk = ApkAssets::Load(path);
+    auto apk = ApkAssets::Load(path);
     AssetManager2 assets;
-    assets.SetApkAssets({apk.get()});
+    assets.SetApkAssets({apk});
   }
 }
 BENCHMARK(BM_AssetManagerLoadFrameworkAssets);
@@ -129,14 +129,14 @@
 BENCHMARK(BM_AssetManagerGetResourceFrameworkLocaleOld);
 
 static void BM_AssetManagerGetBag(benchmark::State& state) {
-  std::unique_ptr<const ApkAssets> apk = ApkAssets::Load(GetTestDataPath() + "/styles/styles.apk");
+  auto apk = ApkAssets::Load(GetTestDataPath() + "/styles/styles.apk");
   if (apk == nullptr) {
     state.SkipWithError("Failed to load assets");
     return;
   }
 
   AssetManager2 assets;
-  assets.SetApkAssets({apk.get()});
+  assets.SetApkAssets({apk});
 
   while (state.KeepRunning()) {
     auto bag = assets.GetBag(app::R::style::StyleTwo);
@@ -181,14 +181,14 @@
 BENCHMARK(BM_AssetManagerGetBagOld);
 
 static void BM_AssetManagerGetResourceLocales(benchmark::State& state) {
-  std::unique_ptr<const ApkAssets> apk = ApkAssets::Load(kFrameworkPath);
+  auto apk = ApkAssets::Load(kFrameworkPath);
   if (apk == nullptr) {
     state.SkipWithError("Failed to load assets");
     return;
   }
 
   AssetManager2 assets;
-  assets.SetApkAssets({apk.get()});
+  assets.SetApkAssets({apk});
 
   while (state.KeepRunning()) {
     std::set<std::string> locales =
@@ -217,14 +217,14 @@
 BENCHMARK(BM_AssetManagerGetResourceLocalesOld);
 
 static void BM_AssetManagerSetConfigurationFramework(benchmark::State& state) {
-  std::unique_ptr<const ApkAssets> apk = ApkAssets::Load(kFrameworkPath);
+  auto apk = ApkAssets::Load(kFrameworkPath);
   if (apk == nullptr) {
     state.SkipWithError("Failed to load assets");
     return;
   }
 
   AssetManager2 assets;
-  assets.SetApkAssets({apk.get()});
+  assets.SetApkAssets({apk});
 
   ResTable_config config;
   memset(&config, 0, sizeof(config));
diff --git a/libs/androidfw/tests/AssetManager2_test.cpp b/libs/androidfw/tests/AssetManager2_test.cpp
index 4394740..df3fa02 100644
--- a/libs/androidfw/tests/AssetManager2_test.cpp
+++ b/libs/androidfw/tests/AssetManager2_test.cpp
@@ -91,19 +91,19 @@
   }
 
  protected:
-  std::unique_ptr<const ApkAssets> basic_assets_;
-  std::unique_ptr<const ApkAssets> basic_de_fr_assets_;
-  std::unique_ptr<const ApkAssets> basic_xhdpi_assets_;
-  std::unique_ptr<const ApkAssets> basic_xxhdpi_assets_;
-  std::unique_ptr<const ApkAssets> style_assets_;
-  std::unique_ptr<const ApkAssets> lib_one_assets_;
-  std::unique_ptr<const ApkAssets> lib_two_assets_;
-  std::unique_ptr<const ApkAssets> libclient_assets_;
-  std::unique_ptr<const ApkAssets> appaslib_assets_;
-  std::unique_ptr<const ApkAssets> system_assets_;
-  std::unique_ptr<const ApkAssets> app_assets_;
-  std::unique_ptr<const ApkAssets> overlay_assets_;
-  std::unique_ptr<const ApkAssets> overlayable_assets_;
+  AssetManager2::ApkAssetsPtr basic_assets_;
+  AssetManager2::ApkAssetsPtr basic_de_fr_assets_;
+  AssetManager2::ApkAssetsPtr basic_xhdpi_assets_;
+  AssetManager2::ApkAssetsPtr basic_xxhdpi_assets_;
+  AssetManager2::ApkAssetsPtr style_assets_;
+  AssetManager2::ApkAssetsPtr lib_one_assets_;
+  AssetManager2::ApkAssetsPtr lib_two_assets_;
+  AssetManager2::ApkAssetsPtr libclient_assets_;
+  AssetManager2::ApkAssetsPtr appaslib_assets_;
+  AssetManager2::ApkAssetsPtr system_assets_;
+  AssetManager2::ApkAssetsPtr app_assets_;
+  AssetManager2::ApkAssetsPtr overlay_assets_;
+  AssetManager2::ApkAssetsPtr overlayable_assets_;
 };
 
 TEST_F(AssetManager2Test, FindsResourceFromSingleApkAssets) {
@@ -114,7 +114,7 @@
 
   AssetManager2 assetmanager;
   assetmanager.SetConfiguration(desired_config);
-  assetmanager.SetApkAssets({basic_assets_.get()});
+  assetmanager.SetApkAssets({basic_assets_});
 
   auto value = assetmanager.GetResource(basic::R::string::test1);
   ASSERT_TRUE(value.has_value());
@@ -138,7 +138,7 @@
 
   AssetManager2 assetmanager;
   assetmanager.SetConfiguration(desired_config);
-  assetmanager.SetApkAssets({basic_assets_.get(), basic_de_fr_assets_.get()});
+  assetmanager.SetApkAssets({basic_assets_, basic_de_fr_assets_});
 
   auto value = assetmanager.GetResource(basic::R::string::test1);
   ASSERT_TRUE(value.has_value());
@@ -159,8 +159,7 @@
 
   // libclient is built with lib_one and then lib_two in order.
   // Reverse the order to test that proper package ID re-assignment is happening.
-  assetmanager.SetApkAssets(
-      {lib_two_assets_.get(), lib_one_assets_.get(), libclient_assets_.get()});
+  assetmanager.SetApkAssets({lib_two_assets_, lib_one_assets_, libclient_assets_});
 
   auto value = assetmanager.GetResource(libclient::R::string::foo_one);
   ASSERT_TRUE(value.has_value());
@@ -195,7 +194,7 @@
 
 TEST_F(AssetManager2Test, FindsResourceFromAppLoadedAsSharedLibrary) {
   AssetManager2 assetmanager;
-  assetmanager.SetApkAssets({appaslib_assets_.get()});
+  assetmanager.SetApkAssets({appaslib_assets_});
 
   // The appaslib package will have been assigned the package ID 0x02.
   auto value = assetmanager.GetResource(fix_package_id(appaslib::R::integer::number1, 0x02));
@@ -206,27 +205,26 @@
 
 TEST_F(AssetManager2Test, AssignsOverlayPackageIdLast) {
   AssetManager2 assetmanager;
-  assetmanager.SetApkAssets(
-      {overlayable_assets_.get(), overlay_assets_.get(), lib_one_assets_.get()});
+  assetmanager.SetApkAssets({overlayable_assets_, overlay_assets_, lib_one_assets_});
 
-  auto apk_assets = assetmanager.GetApkAssets();
-  ASSERT_EQ(3, apk_assets.size());
-  ASSERT_EQ(overlayable_assets_.get(), apk_assets[0]);
-  ASSERT_EQ(overlay_assets_.get(), apk_assets[1]);
-  ASSERT_EQ(lib_one_assets_.get(), apk_assets[2]);
+  ASSERT_EQ(3, assetmanager.GetApkAssetsCount());
+  auto op = assetmanager.StartOperation();
+  ASSERT_EQ(overlayable_assets_, assetmanager.GetApkAssets(0));
+  ASSERT_EQ(overlay_assets_, assetmanager.GetApkAssets(1));
+  ASSERT_EQ(lib_one_assets_, assetmanager.GetApkAssets(2));
 
-  auto get_first_package_id = [&assetmanager](const ApkAssets* apkAssets) -> uint8_t {
+  auto get_first_package_id = [&assetmanager](auto apkAssets) -> uint8_t {
     return assetmanager.GetAssignedPackageId(apkAssets->GetLoadedArsc()->GetPackages()[0].get());
   };
 
-  ASSERT_EQ(0x7f, get_first_package_id(overlayable_assets_.get()));
-  ASSERT_EQ(0x03, get_first_package_id(overlay_assets_.get()));
-  ASSERT_EQ(0x02, get_first_package_id(lib_one_assets_.get()));
+  ASSERT_EQ(0x7f, get_first_package_id(overlayable_assets_));
+  ASSERT_EQ(0x03, get_first_package_id(overlay_assets_));
+  ASSERT_EQ(0x02, get_first_package_id(lib_one_assets_));
 }
 
 TEST_F(AssetManager2Test, GetSharedLibraryResourceName) {
   AssetManager2 assetmanager;
-  assetmanager.SetApkAssets({lib_one_assets_.get()});
+  assetmanager.SetApkAssets({lib_one_assets_});
 
   auto name = assetmanager.GetResourceName(lib_one::R::string::foo);
   ASSERT_TRUE(name.has_value());
@@ -235,7 +233,7 @@
 
 TEST_F(AssetManager2Test, GetResourceNameNonMatchingConfig) {
   AssetManager2 assetmanager;
-  assetmanager.SetApkAssets({basic_de_fr_assets_.get()});
+  assetmanager.SetApkAssets({basic_de_fr_assets_});
 
   auto value = assetmanager.GetResourceName(basic::R::string::test1);
   ASSERT_TRUE(value.has_value());
@@ -244,7 +242,7 @@
 
 TEST_F(AssetManager2Test, GetResourceTypeSpecFlags) {
   AssetManager2 assetmanager;
-  assetmanager.SetApkAssets({basic_de_fr_assets_.get()});
+  assetmanager.SetApkAssets({basic_de_fr_assets_});
 
   auto value = assetmanager.GetResourceTypeSpecFlags(basic::R::string::test1);
   ASSERT_TRUE(value.has_value());
@@ -253,7 +251,7 @@
 
 TEST_F(AssetManager2Test, FindsBagResourceFromSingleApkAssets) {
   AssetManager2 assetmanager;
-  assetmanager.SetApkAssets({basic_assets_.get()});
+  assetmanager.SetApkAssets({basic_assets_});
 
   auto bag = assetmanager.GetBag(basic::R::array::integerArray1);
   ASSERT_TRUE(bag.has_value());
@@ -280,8 +278,7 @@
 
   // libclient is built with lib_one and then lib_two in order.
   // Reverse the order to test that proper package ID re-assignment is happening.
-  assetmanager.SetApkAssets(
-      {lib_two_assets_.get(), lib_one_assets_.get(), libclient_assets_.get()});
+  assetmanager.SetApkAssets({lib_two_assets_, lib_one_assets_, libclient_assets_});
 
   auto bag = assetmanager.GetBag(fix_package_id(lib_one::R::style::Theme, 0x03));
   ASSERT_TRUE(bag.has_value());
@@ -300,8 +297,7 @@
 
   // libclient is built with lib_one and then lib_two in order.
   // Reverse the order to test that proper package ID re-assignment is happening.
-  assetmanager.SetApkAssets(
-      {lib_two_assets_.get(), lib_one_assets_.get(), libclient_assets_.get()});
+  assetmanager.SetApkAssets({lib_two_assets_, lib_one_assets_, libclient_assets_});
 
   auto bag = assetmanager.GetBag(libclient::R::style::ThemeMultiLib);
   ASSERT_TRUE(bag.has_value());
@@ -321,8 +317,7 @@
 
   // libclient is built with lib_one and then lib_two in order.
   // Reverse the order to test that proper package ID re-assignment is happening.
-  assetmanager.SetApkAssets(
-      {lib_two_assets_.get(), lib_one_assets_.get(), libclient_assets_.get()});
+  assetmanager.SetApkAssets({lib_two_assets_, lib_one_assets_, libclient_assets_});
 
   auto bag = assetmanager.GetBag(libclient::R::style::Theme);
   ASSERT_TRUE(bag.has_value());
@@ -337,7 +332,7 @@
 
 TEST_F(AssetManager2Test, MergesStylesWithParentFromSingleApkAssets) {
   AssetManager2 assetmanager;
-  assetmanager.SetApkAssets({style_assets_.get()});
+  assetmanager.SetApkAssets({style_assets_});
 
   auto bag_one = assetmanager.GetBag(app::R::style::StyleOne);
   ASSERT_TRUE(bag_one.has_value());
@@ -401,7 +396,7 @@
 
 TEST_F(AssetManager2Test, MergeStylesCircularDependency) {
   AssetManager2 assetmanager;
-  assetmanager.SetApkAssets({style_assets_.get()});
+  assetmanager.SetApkAssets({style_assets_});
 
   // GetBag should stop traversing the parents of styles when a circular
   // dependency is detected
@@ -412,7 +407,7 @@
 
 TEST_F(AssetManager2Test, ResolveReferenceToResource) {
   AssetManager2 assetmanager;
-  assetmanager.SetApkAssets({basic_assets_.get()});
+  assetmanager.SetApkAssets({basic_assets_});
 
   auto value = assetmanager.GetResource(basic::R::integer::ref1);
   ASSERT_TRUE(value.has_value());
@@ -428,7 +423,7 @@
 
 TEST_F(AssetManager2Test, ResolveReferenceToBag) {
   AssetManager2 assetmanager;
-  assetmanager.SetApkAssets({basic_assets_.get()});
+  assetmanager.SetApkAssets({basic_assets_});
 
   auto value = assetmanager.GetResource(basic::R::integer::number2, true /*may_be_bag*/);
   ASSERT_TRUE(value.has_value());
@@ -444,7 +439,7 @@
 
 TEST_F(AssetManager2Test, ResolveDeepIdReference) {
   AssetManager2 assetmanager;
-  assetmanager.SetApkAssets({basic_assets_.get()});
+  assetmanager.SetApkAssets({basic_assets_});
 
   // Set up the resource ids
   auto high_ref = assetmanager.GetResourceId("@id/high_ref", "values", "com.android.basic");
@@ -470,8 +465,7 @@
 
 TEST_F(AssetManager2Test, DensityOverride) {
   AssetManager2 assetmanager;
-  assetmanager.SetApkAssets({basic_assets_.get(), basic_xhdpi_assets_.get(),
-                             basic_xxhdpi_assets_.get()});
+  assetmanager.SetApkAssets({basic_assets_, basic_xhdpi_assets_, basic_xxhdpi_assets_});
   assetmanager.SetConfiguration({
     .density = ResTable_config::DENSITY_XHIGH,
     .sdkVersion = 21,
@@ -493,7 +487,7 @@
 
 TEST_F(AssetManager2Test, KeepLastReferenceIdUnmodifiedIfNoReferenceIsResolved) {
   AssetManager2 assetmanager;
-  assetmanager.SetApkAssets({basic_assets_.get()});
+  assetmanager.SetApkAssets({basic_assets_});
 
   // Create some kind of value that is NOT a reference.
   AssetManager2::SelectedValue value{};
@@ -509,7 +503,7 @@
 
 TEST_F(AssetManager2Test, ResolveReferenceMissingResourceDoNotCacheFlags) {
   AssetManager2 assetmanager;
-  assetmanager.SetApkAssets({basic_assets_.get()});
+  assetmanager.SetApkAssets({basic_assets_});
   {
     AssetManager2::SelectedValue value{};
     value.data = basic::R::string::test1;
@@ -540,7 +534,7 @@
 
 TEST_F(AssetManager2Test, ResolveReferenceMissingResource) {
   AssetManager2 assetmanager;
-  assetmanager.SetApkAssets({basic_assets_.get()});
+  assetmanager.SetApkAssets({basic_assets_});
 
   const uint32_t kMissingResId = 0x8001ffff;
   AssetManager2::SelectedValue value{};
@@ -558,7 +552,7 @@
 
 TEST_F(AssetManager2Test, ResolveReferenceMissingResourceLib) {
   AssetManager2 assetmanager;
-  assetmanager.SetApkAssets({libclient_assets_.get()});
+  assetmanager.SetApkAssets({libclient_assets_});
 
   AssetManager2::SelectedValue value{};
   value.type = Res_value::TYPE_REFERENCE;
@@ -580,7 +574,7 @@
 
 TEST_F(AssetManager2Test, GetResourceConfigurations) {
   AssetManager2 assetmanager;
-  assetmanager.SetApkAssets({system_assets_.get(), basic_de_fr_assets_.get()});
+  assetmanager.SetApkAssets({system_assets_, basic_de_fr_assets_});
 
   auto configurations = assetmanager.GetResourceConfigurations();
   ASSERT_TRUE(configurations.has_value());
@@ -625,7 +619,7 @@
 
 TEST_F(AssetManager2Test, GetResourceLocales) {
   AssetManager2 assetmanager;
-  assetmanager.SetApkAssets({system_assets_.get(), basic_de_fr_assets_.get()});
+  assetmanager.SetApkAssets({system_assets_, basic_de_fr_assets_});
 
   std::set<std::string> locales = assetmanager.GetResourceLocales();
 
@@ -644,7 +638,7 @@
 
 TEST_F(AssetManager2Test, GetResourceId) {
   AssetManager2 assetmanager;
-  assetmanager.SetApkAssets({basic_assets_.get()});
+  assetmanager.SetApkAssets({basic_assets_});
 
   auto resid = assetmanager.GetResourceId("com.android.basic:layout/main", "", "");
   ASSERT_TRUE(resid.has_value());
@@ -661,7 +655,7 @@
 
 TEST_F(AssetManager2Test, OpensFileFromSingleApkAssets) {
   AssetManager2 assetmanager;
-  assetmanager.SetApkAssets({system_assets_.get()});
+  assetmanager.SetApkAssets({system_assets_});
 
   std::unique_ptr<Asset> asset = assetmanager.Open("file.txt", Asset::ACCESS_BUFFER);
   ASSERT_THAT(asset, NotNull());
@@ -673,7 +667,7 @@
 
 TEST_F(AssetManager2Test, OpensFileFromMultipleApkAssets) {
   AssetManager2 assetmanager;
-  assetmanager.SetApkAssets({system_assets_.get(), app_assets_.get()});
+  assetmanager.SetApkAssets({system_assets_, app_assets_});
 
   std::unique_ptr<Asset> asset = assetmanager.Open("file.txt", Asset::ACCESS_BUFFER);
   ASSERT_THAT(asset, NotNull());
@@ -685,7 +679,7 @@
 
 TEST_F(AssetManager2Test, OpenDir) {
   AssetManager2 assetmanager;
-  assetmanager.SetApkAssets({system_assets_.get()});
+  assetmanager.SetApkAssets({system_assets_});
 
   std::unique_ptr<AssetDir> asset_dir = assetmanager.OpenDir("");
   ASSERT_THAT(asset_dir, NotNull());
@@ -707,7 +701,7 @@
 
 TEST_F(AssetManager2Test, OpenDirFromManyApks) {
   AssetManager2 assetmanager;
-  assetmanager.SetApkAssets({system_assets_.get(), app_assets_.get()});
+  assetmanager.SetApkAssets({system_assets_, app_assets_});
 
   std::unique_ptr<AssetDir> asset_dir = assetmanager.OpenDir("");
   ASSERT_THAT(asset_dir, NotNull());
@@ -728,7 +722,7 @@
 
   AssetManager2 assetmanager;
   assetmanager.SetConfiguration(desired_config);
-  assetmanager.SetApkAssets({basic_assets_.get()});
+  assetmanager.SetApkAssets({basic_assets_});
   assetmanager.SetResourceResolutionLoggingEnabled(false);
 
   auto value = assetmanager.GetResource(basic::R::string::test1);
@@ -743,7 +737,7 @@
 
   AssetManager2 assetmanager;
   assetmanager.SetConfiguration(desired_config);
-  assetmanager.SetApkAssets({basic_assets_.get()});
+  assetmanager.SetApkAssets({basic_assets_});
 
   auto result = assetmanager.GetLastResourceResolution();
   EXPECT_EQ("", result);
@@ -758,17 +752,18 @@
   AssetManager2 assetmanager;
   assetmanager.SetResourceResolutionLoggingEnabled(true);
   assetmanager.SetConfiguration(desired_config);
-  assetmanager.SetApkAssets({basic_assets_.get()});
+  assetmanager.SetApkAssets({basic_assets_});
 
   auto value = assetmanager.GetResource(basic::R::string::test1);
   ASSERT_TRUE(value.has_value());
 
   auto result = assetmanager.GetLastResourceResolution();
-  EXPECT_EQ("Resolution for 0x7f030000 com.android.basic:string/test1\n"
-            "\tFor config - de\n"
-            "\tFound initial: basic/basic.apk\n"
-            "Best matching is from default configuration of com.android.basic",
-            result);
+  EXPECT_EQ(
+      "Resolution for 0x7f030000 com.android.basic:string/test1\n"
+      "\tFor config - de\n"
+      "\tFound initial: basic/basic.apk #0\n"
+      "Best matching is from default configuration of com.android.basic",
+      result);
 }
 
 TEST_F(AssetManager2Test, GetLastPathWithMultipleApkAssets) {
@@ -780,18 +775,19 @@
   AssetManager2 assetmanager;
   assetmanager.SetResourceResolutionLoggingEnabled(true);
   assetmanager.SetConfiguration(desired_config);
-  assetmanager.SetApkAssets({basic_assets_.get(), basic_de_fr_assets_.get()});
+  assetmanager.SetApkAssets({basic_assets_, basic_de_fr_assets_});
 
   auto value = assetmanager.GetResource(basic::R::string::test1);
   ASSERT_TRUE(value.has_value());
 
   auto result = assetmanager.GetLastResourceResolution();
-  EXPECT_EQ("Resolution for 0x7f030000 com.android.basic:string/test1\n"
-            "\tFor config - de\n"
-            "\tFound initial: basic/basic.apk\n"
-            "\tFound better: basic/basic_de_fr.apk - de\n"
-            "Best matching is from de configuration of com.android.basic",
-            result);
+  EXPECT_EQ(
+      "Resolution for 0x7f030000 com.android.basic:string/test1\n"
+      "\tFor config - de\n"
+      "\tFound initial: basic/basic.apk #0\n"
+      "\tFound better: basic/basic_de_fr.apk #1 - de\n"
+      "Best matching is from de configuration of com.android.basic",
+      result);
 }
 
 TEST_F(AssetManager2Test, GetLastPathAfterDisablingReturnsEmpty) {
@@ -801,7 +797,7 @@
   AssetManager2 assetmanager;
   assetmanager.SetResourceResolutionLoggingEnabled(true);
   assetmanager.SetConfiguration(desired_config);
-  assetmanager.SetApkAssets({basic_assets_.get()});
+  assetmanager.SetApkAssets({basic_assets_});
 
   auto value = assetmanager.GetResource(basic::R::string::test1);
   ASSERT_TRUE(value.has_value());
@@ -822,7 +818,7 @@
   AssetManager2 assetmanager;
   assetmanager.SetResourceResolutionLoggingEnabled(true);
   assetmanager.SetConfiguration(desired_config);
-  assetmanager.SetApkAssets({overlayable_assets_.get()});
+  assetmanager.SetApkAssets({overlayable_assets_});
 
   const auto map = assetmanager.GetOverlayableMapForPackage(0x7f);
   ASSERT_NE(nullptr, map);
@@ -838,4 +834,26 @@
             std::string::npos);
 }
 
+TEST_F(AssetManager2Test, GetApkAssets) {
+  AssetManager2 assetmanager;
+  assetmanager.SetApkAssets({overlayable_assets_, overlay_assets_, lib_one_assets_});
+
+  ASSERT_EQ(3, assetmanager.GetApkAssetsCount());
+  EXPECT_EQ(1, overlayable_assets_->getStrongCount());
+  EXPECT_EQ(1, overlay_assets_->getStrongCount());
+  EXPECT_EQ(1, lib_one_assets_->getStrongCount());
+
+  {
+    auto op = assetmanager.StartOperation();
+    ASSERT_EQ(overlayable_assets_, assetmanager.GetApkAssets(0));
+    ASSERT_EQ(overlay_assets_, assetmanager.GetApkAssets(1));
+    EXPECT_EQ(2, overlayable_assets_->getStrongCount());
+    EXPECT_EQ(2, overlay_assets_->getStrongCount());
+    EXPECT_EQ(1, lib_one_assets_->getStrongCount());
+  }
+  EXPECT_EQ(1, overlayable_assets_->getStrongCount());
+  EXPECT_EQ(1, overlay_assets_->getStrongCount());
+  EXPECT_EQ(1, lib_one_assets_->getStrongCount());
+}
+
 }  // namespace android
diff --git a/libs/androidfw/tests/AttributeResolution_bench.cpp b/libs/androidfw/tests/AttributeResolution_bench.cpp
index 1c89c61..384f4a7 100644
--- a/libs/androidfw/tests/AttributeResolution_bench.cpp
+++ b/libs/androidfw/tests/AttributeResolution_bench.cpp
@@ -36,15 +36,14 @@
 constexpr const static uint32_t Theme_Material_Light = 0x01030237u;
 
 static void BM_ApplyStyle(benchmark::State& state) {
-  std::unique_ptr<const ApkAssets> styles_apk =
-      ApkAssets::Load(GetTestDataPath() + "/styles/styles.apk");
+  auto styles_apk = ApkAssets::Load(GetTestDataPath() + "/styles/styles.apk");
   if (styles_apk == nullptr) {
     state.SkipWithError("failed to load assets");
     return;
   }
 
   AssetManager2 assetmanager;
-  assetmanager.SetApkAssets({styles_apk.get()});
+  assetmanager.SetApkAssets({styles_apk});
 
   std::unique_ptr<Asset> asset =
       assetmanager.OpenNonAsset("res/layout/layout.xml", Asset::ACCESS_BUFFER);
@@ -80,21 +79,20 @@
 BENCHMARK(BM_ApplyStyle);
 
 static void BM_ApplyStyleFramework(benchmark::State& state) {
-  std::unique_ptr<const ApkAssets> framework_apk = ApkAssets::Load(kFrameworkPath);
+  auto framework_apk = ApkAssets::Load(kFrameworkPath);
   if (framework_apk == nullptr) {
     state.SkipWithError("failed to load framework assets");
     return;
   }
 
-  std::unique_ptr<const ApkAssets> basic_apk =
-      ApkAssets::Load(GetTestDataPath() + "/basic/basic.apk");
+  auto basic_apk = ApkAssets::Load(GetTestDataPath() + "/basic/basic.apk");
   if (basic_apk == nullptr) {
     state.SkipWithError("failed to load assets");
     return;
   }
 
   AssetManager2 assetmanager;
-  assetmanager.SetApkAssets({framework_apk.get(), basic_apk.get()});
+  assetmanager.SetApkAssets({framework_apk, basic_apk});
 
   ResTable_config device_config;
   memset(&device_config, 0, sizeof(device_config));
diff --git a/libs/androidfw/tests/AttributeResolution_test.cpp b/libs/androidfw/tests/AttributeResolution_test.cpp
index bb9129a..329830f 100644
--- a/libs/androidfw/tests/AttributeResolution_test.cpp
+++ b/libs/androidfw/tests/AttributeResolution_test.cpp
@@ -36,11 +36,11 @@
   virtual void SetUp() override {
     styles_assets_ = ApkAssets::Load(GetTestDataPath() + "/styles/styles.apk");
     ASSERT_NE(nullptr, styles_assets_);
-    assetmanager_.SetApkAssets({styles_assets_.get()});
+    assetmanager_.SetApkAssets({styles_assets_});
   }
 
  protected:
-  std::unique_ptr<const ApkAssets> styles_assets_;
+  AssetManager2::ApkAssetsPtr styles_assets_;
   AssetManager2 assetmanager_;
 };
 
@@ -69,7 +69,7 @@
   AssetManager2 assetmanager;
   auto apk_assets = ApkAssets::Load(GetTestDataPath() + "/styles/styles.apk", PROPERTY_DYNAMIC);
   ASSERT_NE(nullptr, apk_assets);
-  assetmanager.SetApkAssets({apk_assets.get()});
+  assetmanager.SetApkAssets({apk_assets});
 
   std::unique_ptr<Theme> theme = assetmanager.NewTheme();
 
diff --git a/libs/androidfw/tests/BenchmarkHelpers.cpp b/libs/androidfw/tests/BenchmarkHelpers.cpp
index 0fa0573..b97dd96 100644
--- a/libs/androidfw/tests/BenchmarkHelpers.cpp
+++ b/libs/androidfw/tests/BenchmarkHelpers.cpp
@@ -53,20 +53,18 @@
 
 void GetResourceBenchmark(const std::vector<std::string>& paths, const ResTable_config* config,
                           uint32_t resid, benchmark::State& state) {
-  std::vector<std::unique_ptr<const ApkAssets>> apk_assets;
-  std::vector<const ApkAssets*> apk_assets_ptrs;
+  std::vector<AssetManager2::ApkAssetsPtr> apk_assets;
   for (const std::string& path : paths) {
-    std::unique_ptr<const ApkAssets> apk = ApkAssets::Load(path);
+    auto apk = ApkAssets::Load(path);
     if (apk == nullptr) {
       state.SkipWithError(base::StringPrintf("Failed to load assets %s", path.c_str()).c_str());
       return;
     }
-    apk_assets_ptrs.push_back(apk.get());
     apk_assets.push_back(std::move(apk));
   }
 
   AssetManager2 assetmanager;
-  assetmanager.SetApkAssets(apk_assets_ptrs);
+  assetmanager.SetApkAssets(apk_assets);
   if (config != nullptr) {
     assetmanager.SetConfiguration(*config);
   }
diff --git a/libs/androidfw/tests/Generic_bench.cpp b/libs/androidfw/tests/Generic_bench.cpp
new file mode 100644
index 0000000..4c978e8
--- /dev/null
+++ b/libs/androidfw/tests/Generic_bench.cpp
@@ -0,0 +1,146 @@
+/*
+ * 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.
+ */
+
+#include <stdint.h>
+
+#include <map>
+#include <unordered_map>
+
+#include "benchmark/benchmark.h"
+
+namespace android {
+
+template <class Map = std::unordered_map<uint32_t, std::vector<uint32_t>>>
+static Map prepare_map() {
+  Map map;
+  std::vector<uint32_t> vec;
+  for (int i = 0; i < 1000; ++i) {
+    map.emplace(i, vec);
+  }
+  return map;
+}
+
+static void BM_hashmap_emplace_same(benchmark::State& state) {
+  auto map = prepare_map<>();
+  auto val = map.size() - 1;
+  std::vector<uint32_t> vec;
+  for (auto&& _ : state) {
+    benchmark::DoNotOptimize(map.emplace(val, vec));
+  }
+}
+BENCHMARK(BM_hashmap_emplace_same);
+static void BM_hashmap_try_emplace_same(benchmark::State& state) {
+  auto map = prepare_map();
+  auto val = map.size() - 1;
+  for (auto&& _ : state) {
+    benchmark::DoNotOptimize(map.try_emplace(val));
+  }
+}
+BENCHMARK(BM_hashmap_try_emplace_same);
+static void BM_hashmap_find(benchmark::State& state) {
+  auto map = prepare_map<>();
+  auto val = map.size() - 1;
+  for (auto&& _ : state) {
+    benchmark::DoNotOptimize(map.find(val));
+  }
+}
+BENCHMARK(BM_hashmap_find);
+
+static void BM_hashmap_emplace_diff(benchmark::State& state) {
+  auto map = prepare_map<>();
+  std::vector<uint32_t> vec;
+  auto i = map.size();
+  for (auto&& _ : state) {
+    map.emplace(i++, vec);
+  }
+}
+BENCHMARK(BM_hashmap_emplace_diff);
+static void BM_hashmap_try_emplace_diff(benchmark::State& state) {
+  auto map = prepare_map();
+  auto i = map.size();
+  for (auto&& _ : state) {
+    map.try_emplace(i++);
+  }
+}
+BENCHMARK(BM_hashmap_try_emplace_diff);
+static void BM_hashmap_find_emplace_diff(benchmark::State& state) {
+  auto map = prepare_map<>();
+  std::vector<uint32_t> vec;
+  auto i = map.size();
+  for (auto&& _ : state) {
+    if (map.find(i++) == map.end()) {
+      map.emplace(i - 1, vec);
+    }
+  }
+}
+BENCHMARK(BM_hashmap_find_emplace_diff);
+
+static void BM_treemap_emplace_same(benchmark::State& state) {
+  auto map = prepare_map<std::map<uint32_t, std::vector<uint32_t>>>();
+  auto val = map.size() - 1;
+  std::vector<uint32_t> vec;
+  for (auto&& _ : state) {
+    benchmark::DoNotOptimize(map.emplace(val, vec));
+  }
+}
+BENCHMARK(BM_treemap_emplace_same);
+static void BM_treemap_try_emplace_same(benchmark::State& state) {
+  auto map = prepare_map<std::map<uint32_t, std::vector<uint32_t>>>();
+  auto val = map.size() - 1;
+  for (auto&& _ : state) {
+    benchmark::DoNotOptimize(map.try_emplace(val));
+  }
+}
+BENCHMARK(BM_treemap_try_emplace_same);
+static void BM_treemap_find(benchmark::State& state) {
+  auto map = prepare_map<std::map<uint32_t, std::vector<uint32_t>>>();
+  auto val = map.size() - 1;
+  for (auto&& _ : state) {
+    benchmark::DoNotOptimize(map.find(val));
+  }
+}
+BENCHMARK(BM_treemap_find);
+
+static void BM_treemap_emplace_diff(benchmark::State& state) {
+  auto map = prepare_map<std::map<uint32_t, std::vector<uint32_t>>>();
+  std::vector<uint32_t> vec;
+  auto i = map.size();
+  for (auto&& _ : state) {
+    map.emplace(i++, vec);
+  }
+}
+BENCHMARK(BM_treemap_emplace_diff);
+static void BM_treemap_try_emplace_diff(benchmark::State& state) {
+  auto map = prepare_map();
+  auto i = map.size();
+  for (auto&& _ : state) {
+    map.try_emplace(i++);
+  }
+}
+BENCHMARK(BM_treemap_try_emplace_diff);
+static void BM_treemap_find_emplace_diff(benchmark::State& state) {
+  auto map = prepare_map();
+  std::vector<uint32_t> vec;
+  auto i = map.size();
+  for (auto&& _ : state) {
+    if (map.find(i++) == map.end()) {
+      map.emplace(i - 1, vec);
+    }
+  }
+}
+BENCHMARK(BM_treemap_find_emplace_diff);
+
+}  // namespace android
\ No newline at end of file
diff --git a/libs/androidfw/tests/Idmap_test.cpp b/libs/androidfw/tests/Idmap_test.cpp
index b434915..60aa7d8 100644
--- a/libs/androidfw/tests/Idmap_test.cpp
+++ b/libs/androidfw/tests/Idmap_test.cpp
@@ -59,15 +59,16 @@
 
  protected:
   std::string original_path;
-  std::unique_ptr<const ApkAssets> system_assets_;
-  std::unique_ptr<const ApkAssets> overlay_assets_;
-  std::unique_ptr<const ApkAssets> overlayable_assets_;
+  AssetManager2::ApkAssetsPtr system_assets_;
+  AssetManager2::ApkAssetsPtr overlay_assets_;
+  AssetManager2::ApkAssetsPtr overlayable_assets_;
 };
 
 std::string GetStringFromApkAssets(const AssetManager2& asset_manager,
                                    const AssetManager2::SelectedValue& value) {
-  auto assets = asset_manager.GetApkAssets();
-  const ResStringPool* string_pool = assets[value.cookie]->GetLoadedArsc()->GetStringPool();
+  auto op = asset_manager.StartOperation();
+  const ResStringPool* string_pool =
+      asset_manager.GetApkAssets(value.cookie)->GetLoadedArsc()->GetStringPool();
   return GetStringFromPool(string_pool, value.data);
 }
 
@@ -75,8 +76,7 @@
 
 TEST_F(IdmapTest, OverlayOverridesResourceValue) {
   AssetManager2 asset_manager;
-  asset_manager.SetApkAssets({system_assets_.get(), overlayable_assets_.get(),
-                              overlay_assets_.get()});
+  asset_manager.SetApkAssets({system_assets_, overlayable_assets_, overlay_assets_});
 
   auto value = asset_manager.GetResource(overlayable::R::string::overlayable5);
   ASSERT_TRUE(value.has_value());
@@ -87,8 +87,7 @@
 
 TEST_F(IdmapTest, OverlayOverridesResourceValueUsingDifferentPackage) {
   AssetManager2 asset_manager;
-  asset_manager.SetApkAssets({system_assets_.get(), overlayable_assets_.get(),
-                              overlay_assets_.get()});
+  asset_manager.SetApkAssets({system_assets_, overlayable_assets_, overlay_assets_});
 
   auto value = asset_manager.GetResource(overlayable::R::string::overlayable10);
   ASSERT_TRUE(value.has_value());
@@ -99,8 +98,7 @@
 
 TEST_F(IdmapTest, OverlayOverridesResourceValueUsingInternalResource) {
   AssetManager2 asset_manager;
-  asset_manager.SetApkAssets({system_assets_.get(), overlayable_assets_.get(),
-                              overlay_assets_.get()});
+  asset_manager.SetApkAssets({system_assets_, overlayable_assets_, overlay_assets_});
 
   auto value = asset_manager.GetResource(overlayable::R::string::overlayable8);
   ASSERT_TRUE(value.has_value());
@@ -111,8 +109,7 @@
 
 TEST_F(IdmapTest, OverlayOverridesResourceValueUsingInlineInteger) {
   AssetManager2 asset_manager;
-  asset_manager.SetApkAssets({system_assets_.get(), overlayable_assets_.get(),
-                              overlay_assets_.get()});
+  asset_manager.SetApkAssets({system_assets_, overlayable_assets_, overlay_assets_});
 
   auto value = asset_manager.GetResource(overlayable::R::integer::config_integer);
   ASSERT_TRUE(value.has_value());
@@ -123,8 +120,7 @@
 
 TEST_F(IdmapTest, OverlayOverridesResourceValueUsingInlineString) {
   AssetManager2 asset_manager;
-  asset_manager.SetApkAssets({system_assets_.get(), overlayable_assets_.get(),
-                              overlay_assets_.get()});
+  asset_manager.SetApkAssets({system_assets_, overlayable_assets_, overlay_assets_});
 
   auto value = asset_manager.GetResource(overlayable::R::string::overlayable11);
   ASSERT_TRUE(value.has_value());
@@ -135,8 +131,7 @@
 
 TEST_F(IdmapTest, OverlayOverridesResourceValueUsingOverlayingResource) {
   AssetManager2 asset_manager;
-  asset_manager.SetApkAssets({system_assets_.get(), overlayable_assets_.get(),
-                              overlay_assets_.get()});
+  asset_manager.SetApkAssets({system_assets_, overlayable_assets_, overlay_assets_});
 
   auto value = asset_manager.GetResource(overlayable::R::string::overlayable9);
   ASSERT_TRUE(value.has_value());
@@ -147,8 +142,7 @@
 
 TEST_F(IdmapTest, OverlayOverridesXmlParser) {
   AssetManager2 asset_manager;
-  asset_manager.SetApkAssets({system_assets_.get(), overlayable_assets_.get(),
-                              overlay_assets_.get()});
+  asset_manager.SetApkAssets({system_assets_, overlayable_assets_, overlay_assets_});
 
   auto value = asset_manager.GetResource(overlayable::R::layout::hello_view);
   ASSERT_TRUE(value.has_value());
@@ -186,8 +180,7 @@
 
 TEST_F(IdmapTest, OverlaidResourceHasSameName) {
   AssetManager2 asset_manager;
-  asset_manager.SetApkAssets({system_assets_.get(), overlayable_assets_.get(),
-                              overlay_assets_.get()});
+  asset_manager.SetApkAssets({system_assets_, overlayable_assets_, overlay_assets_});
 
   auto name = asset_manager.GetResourceName(overlayable::R::string::overlayable9);
   ASSERT_TRUE(name.has_value());
@@ -203,8 +196,7 @@
   auto loader_assets = ApkAssets::LoadTable(std::move(asset), EmptyAssetsProvider::Create(),
       PROPERTY_LOADER);
   AssetManager2 asset_manager;
-  asset_manager.SetApkAssets({overlayable_assets_.get(), loader_assets.get(),
-                              overlay_assets_.get()});
+  asset_manager.SetApkAssets({overlayable_assets_, loader_assets, overlay_assets_});
 
   auto value = asset_manager.GetResource(overlayable::R::string::overlayable11);
   ASSERT_TRUE(value.has_value());
diff --git a/libs/androidfw/tests/Theme_bench.cpp b/libs/androidfw/tests/Theme_bench.cpp
index f3d60bb..dfbb5a7 100644
--- a/libs/androidfw/tests/Theme_bench.cpp
+++ b/libs/androidfw/tests/Theme_bench.cpp
@@ -28,14 +28,14 @@
 constexpr const static uint32_t kAttrId = 0x01010030u;   // android:attr/colorForeground
 
 static void BM_ThemeApplyStyleFramework(benchmark::State& state) {
-  std::unique_ptr<const ApkAssets> apk = ApkAssets::Load(kFrameworkPath);
+  auto apk = ApkAssets::Load(kFrameworkPath);
   if (apk == nullptr) {
     state.SkipWithError("Failed to load assets");
     return;
   }
 
   AssetManager2 assets;
-  assets.SetApkAssets({apk.get()});
+  assets.SetApkAssets({apk});
 
   while (state.KeepRunning()) {
     auto theme = assets.NewTheme();
@@ -62,10 +62,10 @@
 BENCHMARK(BM_ThemeApplyStyleFrameworkOld);
 
 static void BM_ThemeGetAttribute(benchmark::State& state) {
-  std::unique_ptr<const ApkAssets> apk = ApkAssets::Load(kFrameworkPath);
+  auto apk = ApkAssets::Load(kFrameworkPath);
 
   AssetManager2 assets;
-  assets.SetApkAssets({apk.get()});
+  assets.SetApkAssets({apk});
 
   auto theme = assets.NewTheme();
   theme->ApplyStyle(kStyleId, false /* force */);
diff --git a/libs/androidfw/tests/Theme_test.cpp b/libs/androidfw/tests/Theme_test.cpp
index 77114f2..e08a6a7 100644
--- a/libs/androidfw/tests/Theme_test.cpp
+++ b/libs/androidfw/tests/Theme_test.cpp
@@ -53,16 +53,16 @@
   }
 
  protected:
-  std::unique_ptr<const ApkAssets> system_assets_;
-  std::unique_ptr<const ApkAssets> style_assets_;
-  std::unique_ptr<const ApkAssets> libclient_assets_;
-  std::unique_ptr<const ApkAssets> lib_one_assets_;
-  std::unique_ptr<const ApkAssets> lib_two_assets_;
+  AssetManager2::ApkAssetsPtr system_assets_;
+  AssetManager2::ApkAssetsPtr style_assets_;
+  AssetManager2::ApkAssetsPtr libclient_assets_;
+  AssetManager2::ApkAssetsPtr lib_one_assets_;
+  AssetManager2::ApkAssetsPtr lib_two_assets_;
 };
 
 TEST_F(ThemeTest, EmptyTheme) {
   AssetManager2 assetmanager;
-  assetmanager.SetApkAssets({style_assets_.get()});
+  assetmanager.SetApkAssets({style_assets_});
 
   std::unique_ptr<Theme> theme = assetmanager.NewTheme();
   EXPECT_EQ(0u, theme->GetChangingConfigurations());
@@ -72,7 +72,7 @@
 
 TEST_F(ThemeTest, SingleThemeNoParent) {
   AssetManager2 assetmanager;
-  assetmanager.SetApkAssets({style_assets_.get()});
+  assetmanager.SetApkAssets({style_assets_});
 
   std::unique_ptr<Theme> theme = assetmanager.NewTheme();
   ASSERT_TRUE(theme->ApplyStyle(app::R::style::StyleOne).has_value());
@@ -92,7 +92,7 @@
 
 TEST_F(ThemeTest, SingleThemeWithParent) {
   AssetManager2 assetmanager;
-  assetmanager.SetApkAssets({style_assets_.get()});
+  assetmanager.SetApkAssets({style_assets_});
 
   std::unique_ptr<Theme> theme = assetmanager.NewTheme();
   ASSERT_TRUE(theme->ApplyStyle(app::R::style::StyleTwo).has_value());
@@ -121,7 +121,7 @@
 
 TEST_F(ThemeTest, TryToUseBadResourceId) {
   AssetManager2 assetmanager;
-  assetmanager.SetApkAssets({style_assets_.get()});
+  assetmanager.SetApkAssets({style_assets_});
 
   std::unique_ptr<Theme> theme = assetmanager.NewTheme();
   ASSERT_TRUE(theme->ApplyStyle(app::R::style::StyleTwo).has_value());
@@ -130,7 +130,7 @@
 
 TEST_F(ThemeTest, MultipleThemesOverlaidNotForce) {
   AssetManager2 assetmanager;
-  assetmanager.SetApkAssets({style_assets_.get()});
+  assetmanager.SetApkAssets({style_assets_});
 
   std::unique_ptr<Theme> theme = assetmanager.NewTheme();
   ASSERT_TRUE(theme->ApplyStyle(app::R::style::StyleTwo).has_value());
@@ -160,7 +160,7 @@
 
 TEST_F(ThemeTest, MultipleThemesOverlaidForced) {
   AssetManager2 assetmanager;
-  assetmanager.SetApkAssets({style_assets_.get()});
+  assetmanager.SetApkAssets({style_assets_});
 
   std::unique_ptr<Theme> theme = assetmanager.NewTheme();
   ASSERT_TRUE(theme->ApplyStyle(app::R::style::StyleTwo).has_value());
@@ -190,8 +190,7 @@
 
 TEST_F(ThemeTest, ResolveDynamicAttributesAndReferencesToSharedLibrary) {
   AssetManager2 assetmanager;
-  assetmanager.SetApkAssets(
-      {lib_two_assets_.get(), lib_one_assets_.get(), libclient_assets_.get()});
+  assetmanager.SetApkAssets({lib_two_assets_, lib_one_assets_, libclient_assets_});
 
   std::unique_ptr<Theme> theme = assetmanager.NewTheme();
   ASSERT_TRUE(theme->ApplyStyle(libclient::R::style::Theme, false /*force*/).has_value());
@@ -216,7 +215,7 @@
 
 TEST_F(ThemeTest, CopyThemeSameAssetManager) {
   AssetManager2 assetmanager;
-  assetmanager.SetApkAssets({style_assets_.get()});
+  assetmanager.SetApkAssets({style_assets_});
 
   std::unique_ptr<Theme> theme_one = assetmanager.NewTheme();
   ASSERT_TRUE(theme_one->ApplyStyle(app::R::style::StyleOne).has_value());
@@ -253,10 +252,10 @@
 
 TEST_F(ThemeTest, ThemeRebase) {
   AssetManager2 am;
-  am.SetApkAssets({style_assets_.get()});
+  am.SetApkAssets({style_assets_});
 
   AssetManager2 am_night;
-  am_night.SetApkAssets({style_assets_.get()});
+  am_night.SetApkAssets({style_assets_});
 
   ResTable_config night{};
   night.uiMode = ResTable_config::UI_MODE_NIGHT_YES;
@@ -327,12 +326,11 @@
 
 TEST_F(ThemeTest, OnlyCopySameAssetsThemeWhenAssetManagersDiffer) {
   AssetManager2 assetmanager_dst;
-  assetmanager_dst.SetApkAssets({system_assets_.get(), lib_one_assets_.get(), style_assets_.get(),
-                                 libclient_assets_.get()});
+  assetmanager_dst.SetApkAssets(
+      {system_assets_, lib_one_assets_, style_assets_, libclient_assets_});
 
   AssetManager2 assetmanager_src;
-  assetmanager_src.SetApkAssets({system_assets_.get(), lib_two_assets_.get(), lib_one_assets_.get(),
-                                 style_assets_.get()});
+  assetmanager_src.SetApkAssets({system_assets_, lib_two_assets_, lib_one_assets_, style_assets_});
 
   auto theme_dst = assetmanager_dst.NewTheme();
   ASSERT_TRUE(theme_dst->ApplyStyle(app::R::style::StyleOne).has_value());
@@ -376,10 +374,10 @@
 
 TEST_F(ThemeTest, CopyNonReferencesWhenPackagesDiffer) {
   AssetManager2 assetmanager_dst;
-  assetmanager_dst.SetApkAssets({system_assets_.get()});
+  assetmanager_dst.SetApkAssets({system_assets_});
 
   AssetManager2 assetmanager_src;
-  assetmanager_src.SetApkAssets({system_assets_.get(), style_assets_.get()});
+  assetmanager_src.SetApkAssets({system_assets_, style_assets_});
 
   auto theme_dst = assetmanager_dst.NewTheme();
   auto theme_src = assetmanager_src.NewTheme();
diff --git a/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightDreamManager.kt b/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightDreamManager.kt
index 96bfb78..d1dd8df 100644
--- a/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightDreamManager.kt
+++ b/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightDreamManager.kt
@@ -23,6 +23,7 @@
 import android.util.Log
 import com.android.dream.lowlight.dagger.LowLightDreamModule
 import com.android.dream.lowlight.dagger.qualifiers.Application
+import kotlinx.coroutines.CancellationException
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.TimeoutCancellationException
@@ -103,6 +104,11 @@
                 )
             } catch (ex: TimeoutCancellationException) {
                 Log.e(TAG, "timed out while waiting for low light animation", ex)
+            } catch (ex: CancellationException) {
+                Log.w(TAG, "low light transition animation cancelled")
+                // Catch the cancellation so that we still set the system dream component if the
+                // animation is cancelled, such as by a user tapping to wake as the transition to
+                // low light happens.
             }
             dreamManager.setSystemDreamComponent(
                 if (shouldEnterLowLight) lowLightDreamComponent else null
diff --git a/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightTransitionCoordinator.kt b/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightTransitionCoordinator.kt
index 4736030..de1aee5 100644
--- a/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightTransitionCoordinator.kt
+++ b/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightTransitionCoordinator.kt
@@ -110,15 +110,5 @@
                 }
             }
             animator.addListener(listener)
-            continuation.invokeOnCancellation {
-                try {
-                    animator.removeListener(listener)
-                    animator.cancel()
-                } catch (exception: IndexOutOfBoundsException) {
-                    // TODO(b/285666217): remove this try/catch once a proper fix is implemented.
-                    // Cancelling the animator can cause an exception since we may be removing a
-                    // listener during the cancellation. See b/285666217 for more details.
-                }
-            }
         }
 }
diff --git a/libs/dream/lowlight/src/com/android/dream/lowlight/util/TruncatedInterpolator.kt b/libs/dream/lowlight/src/com/android/dream/lowlight/util/TruncatedInterpolator.kt
new file mode 100644
index 0000000..f69c84d
--- /dev/null
+++ b/libs/dream/lowlight/src/com/android/dream/lowlight/util/TruncatedInterpolator.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dream.lowlight.util
+
+import android.view.animation.Interpolator
+
+/**
+ * Interpolator wrapper that shortens another interpolator from its original duration to a portion
+ * of that duration.
+ *
+ * For example, an `originalDuration` of 1000 and a `newDuration` of 200 results in an animation
+ * that when played for 200ms is the exact same as the first 200ms of a 1000ms animation if using
+ * the original interpolator.
+ *
+ * This is useful for the transition between the user dream and the low light clock as some
+ * animations are defined in the spec to be longer than the total duration of the animation. For
+ * example, the low light clock exit translation animation is defined to last >1s while the actual
+ * fade out of the low light clock is only 250ms, meaning the clock isn't visible anymore after
+ * 250ms.
+ *
+ * Since the dream framework currently only allows one dream to be visible and running, we use this
+ * interpolator to play just the first 250ms of the translation animation. Simply reducing the
+ * duration of the animation would result in the text exiting much faster than intended, so a custom
+ * interpolator is needed.
+ */
+class TruncatedInterpolator(
+    private val baseInterpolator: Interpolator,
+    originalDuration: Float,
+    newDuration: Float
+) : Interpolator {
+    private val scaleFactor: Float
+
+    init {
+        scaleFactor = newDuration / originalDuration
+    }
+
+    override fun getInterpolation(input: Float): Float {
+        return baseInterpolator.getInterpolation(input * scaleFactor)
+    }
+}
diff --git a/libs/dream/lowlight/tests/Android.bp b/libs/dream/lowlight/tests/Android.bp
index 2d79090..64b53cb 100644
--- a/libs/dream/lowlight/tests/Android.bp
+++ b/libs/dream/lowlight/tests/Android.bp
@@ -27,6 +27,7 @@
         "androidx.test.runner",
         "androidx.test.rules",
         "androidx.test.ext.junit",
+        "animationlib",
         "frameworks-base-testutils",
         "junit",
         "kotlinx_coroutines_test",
diff --git a/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightDreamManagerTest.kt b/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightDreamManagerTest.kt
index 2a886bc..de84adb 100644
--- a/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightDreamManagerTest.kt
+++ b/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightDreamManagerTest.kt
@@ -152,6 +152,21 @@
         verify(mDreamManager).setSystemDreamComponent(DREAM_COMPONENT)
     }
 
+    @Test
+    fun setAmbientLightMode_animationCancelled_SetsSystemDream() = testScope.runTest {
+        mLowLightDreamManager.setAmbientLightMode(LowLightDreamManager.AMBIENT_LIGHT_MODE_LOW_LIGHT)
+        runCurrent()
+        cancelEnterAnimations()
+        runCurrent()
+        // Animation never finishes, but we should still set the system dream
+        verify(mDreamManager).setSystemDreamComponent(DREAM_COMPONENT)
+    }
+
+    private fun cancelEnterAnimations() {
+        val listener = withArgCaptor { verify(mEnterAnimator).addListener(capture()) }
+        listener.onAnimationCancel(mEnterAnimator)
+    }
+
     private fun completeEnterAnimations() {
         val listener = withArgCaptor { verify(mEnterAnimator).addListener(capture()) }
         listener.onAnimationEnd(mEnterAnimator)
diff --git a/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightTransitionCoordinatorTest.kt b/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightTransitionCoordinatorTest.kt
index 4c526a6..9ae304f 100644
--- a/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightTransitionCoordinatorTest.kt
+++ b/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightTransitionCoordinatorTest.kt
@@ -158,26 +158,6 @@
         assertThat(job.isCancelled).isTrue()
     }
 
-    @Test
-    fun shouldCancelAnimatorWhenJobCancelled() = testScope.runTest {
-        whenever(mEnterListener.onBeforeEnterLowLight()).thenReturn(mAnimator)
-        val coordinator = LowLightTransitionCoordinator()
-        coordinator.setLowLightEnterListener(mEnterListener)
-        val job = launch {
-            coordinator.waitForLowLightTransitionAnimation(timeout = TIMEOUT, entering = true)
-        }
-        runCurrent()
-        // Animator listener is added and the runnable is not run yet.
-        verify(mAnimator).addListener(mAnimatorListenerCaptor.capture())
-        verify(mAnimator, never()).cancel()
-        assertThat(job.isCompleted).isFalse()
-
-        job.cancel()
-        // We should have removed the listener and cancelled the animator
-        verify(mAnimator).removeListener(mAnimatorListenerCaptor.value)
-        verify(mAnimator).cancel()
-    }
-
     companion object {
         private val TIMEOUT = 1.toDuration(DurationUnit.SECONDS)
     }
diff --git a/libs/dream/lowlight/tests/src/com/android/dream/lowlight/util/TruncatedInterpolatorTest.kt b/libs/dream/lowlight/tests/src/com/android/dream/lowlight/util/TruncatedInterpolatorTest.kt
new file mode 100644
index 0000000..190f02e
--- /dev/null
+++ b/libs/dream/lowlight/tests/src/com/android/dream/lowlight/util/TruncatedInterpolatorTest.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.dream.lowlight.util
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.app.animation.Interpolators
+import com.google.common.truth.Truth
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class TruncatedInterpolatorTest {
+    @Test
+    fun truncatedInterpolator_matchesRegularInterpolator() {
+        val originalInterpolator = Interpolators.EMPHASIZED
+        val truncatedInterpolator =
+            TruncatedInterpolator(originalInterpolator, ORIGINAL_DURATION_MS, NEW_DURATION_MS)
+
+        // Both interpolators should start at the same value.
+        var animationPercent = 0f
+        Truth.assertThat(truncatedInterpolator.getInterpolation(animationPercent))
+            .isEqualTo(originalInterpolator.getInterpolation(animationPercent))
+
+        animationPercent = 1f
+        Truth.assertThat(truncatedInterpolator.getInterpolation(animationPercent))
+            .isEqualTo(originalInterpolator.getInterpolation(animationPercent * DURATION_RATIO))
+
+        animationPercent = 0.25f
+        Truth.assertThat(truncatedInterpolator.getInterpolation(animationPercent))
+            .isEqualTo(originalInterpolator.getInterpolation(animationPercent * DURATION_RATIO))
+    }
+
+    companion object {
+        private const val ORIGINAL_DURATION_MS: Float = 1000f
+        private const val NEW_DURATION_MS: Float = 200f
+        private const val DURATION_RATIO: Float = NEW_DURATION_MS / ORIGINAL_DURATION_MS
+    }
+}
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index db58147..b5e6f94 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -514,6 +514,7 @@
         "canvas/CanvasOpRasterizer.cpp",
         "effects/StretchEffect.cpp",
         "effects/GainmapRenderer.cpp",
+        "pipeline/skia/BackdropFilterDrawable.cpp",
         "pipeline/skia/HolePunch.cpp",
         "pipeline/skia/SkiaDisplayList.cpp",
         "pipeline/skia/SkiaRecordingCanvas.cpp",
diff --git a/libs/hwui/AutoBackendTextureRelease.cpp b/libs/hwui/AutoBackendTextureRelease.cpp
index b656b6a..d237cc2 100644
--- a/libs/hwui/AutoBackendTextureRelease.cpp
+++ b/libs/hwui/AutoBackendTextureRelease.cpp
@@ -16,6 +16,11 @@
 
 #include "AutoBackendTextureRelease.h"
 
+#include <SkImage.h>
+#include <include/gpu/ganesh/SkImageGanesh.h>
+#include <include/gpu/GrBackendSurfaceMutableState.h>
+#include <include/gpu/GrDirectContext.h>
+#include <include/gpu/GrBackendSurface.h>
 #include "renderthread/RenderThread.h"
 #include "utils/Color.h"
 #include "utils/PaintUtils.h"
@@ -70,7 +75,7 @@
 
 // releaseProc is invoked by SkImage, when texture is no longer in use.
 // "releaseContext" contains an "AutoBackendTextureRelease*".
-static void releaseProc(SkImage::ReleaseContext releaseContext) {
+static void releaseProc(SkImages::ReleaseContext releaseContext) {
     AutoBackendTextureRelease* textureRelease =
             reinterpret_cast<AutoBackendTextureRelease*>(releaseContext);
     textureRelease->unref(false);
@@ -83,10 +88,10 @@
     AHardwareBuffer_describe(buffer, &desc);
     SkColorType colorType = GrAHardwareBufferUtils::GetSkColorTypeFromBufferFormat(desc.format);
     // The following ref will be counteracted by Skia calling releaseProc, either during
-    // MakeFromTexture if there is a failure, or later when SkImage is discarded. It must
-    // be called before MakeFromTexture, otherwise Skia may remove HWUI's ref on failure.
+    // BorrowTextureFrom if there is a failure, or later when SkImage is discarded. It must
+    // be called before BorrowTextureFrom, otherwise Skia may remove HWUI's ref on failure.
     ref();
-    mImage = SkImage::MakeFromTexture(
+    mImage = SkImages::BorrowTextureFrom(
             context, mBackendTexture, kTopLeft_GrSurfaceOrigin, colorType, kPremul_SkAlphaType,
             uirenderer::DataSpaceToColorSpace(dataspace), releaseProc, this);
 }
diff --git a/libs/hwui/ColorFilter.h b/libs/hwui/ColorFilter.h
new file mode 100644
index 0000000..1a5b938
--- /dev/null
+++ b/libs/hwui/ColorFilter.h
@@ -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.
+ */
+
+#ifndef COLORFILTER_H_
+#define COLORFILTER_H_
+
+#include <stdint.h>
+
+#include <memory>
+
+#include "GraphicsJNI.h"
+#include "SkColorFilter.h"
+#include "SkiaWrapper.h"
+
+namespace android {
+namespace uirenderer {
+
+class ColorFilter : public SkiaWrapper<SkColorFilter> {
+public:
+    static ColorFilter* fromJava(jlong handle) { return reinterpret_cast<ColorFilter*>(handle); }
+
+protected:
+    ColorFilter() = default;
+};
+
+class BlendModeColorFilter : public ColorFilter {
+public:
+    BlendModeColorFilter(SkColor color, SkBlendMode mode) : mColor(color), mMode(mode) {}
+
+private:
+    sk_sp<SkColorFilter> createInstance() override { return SkColorFilters::Blend(mColor, mMode); }
+
+private:
+    const SkColor mColor;
+    const SkBlendMode mMode;
+};
+
+class LightingFilter : public ColorFilter {
+public:
+    LightingFilter(SkColor mul, SkColor add) : mMul(mul), mAdd(add) {}
+
+    void setMul(SkColor mul) {
+        mMul = mul;
+        discardInstance();
+    }
+
+    void setAdd(SkColor add) {
+        mAdd = add;
+        discardInstance();
+    }
+
+private:
+    sk_sp<SkColorFilter> createInstance() override { return SkColorFilters::Lighting(mMul, mAdd); }
+
+private:
+    SkColor mMul;
+    SkColor mAdd;
+};
+
+class ColorMatrixColorFilter : public ColorFilter {
+public:
+    ColorMatrixColorFilter(std::vector<float>&& matrix) : mMatrix(std::move(matrix)) {}
+
+    void setMatrix(std::vector<float>&& matrix) {
+        mMatrix = std::move(matrix);
+        discardInstance();
+    }
+
+private:
+    sk_sp<SkColorFilter> createInstance() override {
+        return SkColorFilters::Matrix(mMatrix.data());
+    }
+
+private:
+    std::vector<float> mMatrix;
+};
+
+}  // namespace uirenderer
+}  // namespace android
+
+#endif  // COLORFILTER_H_
diff --git a/libs/hwui/DisplayListOps.in b/libs/hwui/DisplayListOps.in
index a18ba1c..d21f07ef 100644
--- a/libs/hwui/DisplayListOps.in
+++ b/libs/hwui/DisplayListOps.in
@@ -14,7 +14,6 @@
  * limitations under the License.
  */
 
-X(Flush)
 X(Save)
 X(Restore)
 X(SaveLayer)
diff --git a/libs/hwui/HardwareBitmapUploader.cpp b/libs/hwui/HardwareBitmapUploader.cpp
index b7e9999..19a1dfa 100644
--- a/libs/hwui/HardwareBitmapUploader.cpp
+++ b/libs/hwui/HardwareBitmapUploader.cpp
@@ -25,6 +25,7 @@
 #include <SkBitmap.h>
 #include <SkCanvas.h>
 #include <SkImage.h>
+#include <SkImageAndroid.h>
 #include <SkImageInfo.h>
 #include <SkRefCnt.h>
 #include <gui/TraceUtils.h>
@@ -262,7 +263,8 @@
           }
 
           sk_sp<SkImage> image =
-              SkImage::MakeFromAHardwareBufferWithData(mGrContext.get(), bitmap.pixmap(), ahb);
+              SkImages::TextureFromAHardwareBufferWithData(mGrContext.get(), bitmap.pixmap(),
+                                                           ahb);
           mGrContext->submit(true);
 
           uploadSucceeded = (image.get() != nullptr);
diff --git a/libs/hwui/MemoryPolicy.cpp b/libs/hwui/MemoryPolicy.cpp
index ca1312e7..21f4ca7 100644
--- a/libs/hwui/MemoryPolicy.cpp
+++ b/libs/hwui/MemoryPolicy.cpp
@@ -28,7 +28,10 @@
 constexpr static MemoryPolicy sDefaultMemoryPolicy;
 constexpr static MemoryPolicy sPersistentOrSystemPolicy{
         .contextTimeout = 10_s,
+        .minimumResourceRetention = 1_s,
+        .maximumResourceRetention = 10_s,
         .useAlternativeUiHidden = true,
+        .purgeScratchOnly = false,
 };
 constexpr static MemoryPolicy sLowRamPolicy{
         .useAlternativeUiHidden = true,
diff --git a/libs/hwui/MemoryPolicy.h b/libs/hwui/MemoryPolicy.h
index 347daf34..e10dda9 100644
--- a/libs/hwui/MemoryPolicy.h
+++ b/libs/hwui/MemoryPolicy.h
@@ -53,6 +53,8 @@
     // The minimum amount of time to hold onto items in the resource cache
     // The actual time used will be the max of this & when frames were actually rendered
     nsecs_t minimumResourceRetention = 10_s;
+    // The maximum amount of time to hold onto items in the resource cache
+    nsecs_t maximumResourceRetention = 100000_s;
     // If false, use only TRIM_UI_HIDDEN to drive background cache limits;
     // If true, use all signals (such as all contexts are stopped) to drive the limits
     bool useAlternativeUiHidden = true;
diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp
index 06aed63..5e5eb4a 100644
--- a/libs/hwui/Properties.cpp
+++ b/libs/hwui/Properties.cpp
@@ -20,7 +20,7 @@
 #ifdef __ANDROID__
 #include "HWUIProperties.sysprop.h"
 #endif
-#include "SkTraceEventCommon.h"
+#include "src/core/SkTraceEventCommon.h"
 
 #include <algorithm>
 #include <cstdlib>
diff --git a/libs/hwui/Readback.cpp b/libs/hwui/Readback.cpp
index 045de35..afe4c38 100644
--- a/libs/hwui/Readback.cpp
+++ b/libs/hwui/Readback.cpp
@@ -21,6 +21,7 @@
 #include <SkCanvas.h>
 #include <SkColorSpace.h>
 #include <SkImage.h>
+#include <SkImageAndroid.h>
 #include <SkImageInfo.h>
 #include <SkMatrix.h>
 #include <SkPaint.h>
@@ -29,6 +30,7 @@
 #include <SkSamplingOptions.h>
 #include <SkSurface.h>
 #include "include/gpu/GpuTypes.h" // from Skia
+#include <include/gpu/ganesh/SkSurfaceGanesh.h>
 #include <gui/TraceUtils.h>
 #include <private/android/AHardwareBufferHelpers.h>
 #include <shaders/shaders.h>
@@ -108,7 +110,8 @@
     sk_sp<SkColorSpace> colorSpace =
             DataSpaceToColorSpace(static_cast<android_dataspace>(dataspace));
     sk_sp<SkImage> image =
-            SkImage::MakeFromAHardwareBuffer(sourceBuffer.get(), kPremul_SkAlphaType, colorSpace);
+            SkImages::DeferredFromAHardwareBuffer(sourceBuffer.get(), kPremul_SkAlphaType, 
+                                                  colorSpace);
 
     if (!image.get()) {
         return request->onCopyFinished(CopyResult::UnknownError);
@@ -171,16 +174,16 @@
     SkBitmap skBitmap = request->getDestinationBitmap(srcRect.width(), srcRect.height());
     SkBitmap* bitmap = &skBitmap;
     sk_sp<SkSurface> tmpSurface =
-            SkSurface::MakeRenderTarget(mRenderThread.getGrContext(), skgpu::Budgeted::kYes,
-                                        bitmap->info(), 0, kTopLeft_GrSurfaceOrigin, nullptr);
+            SkSurfaces::RenderTarget(mRenderThread.getGrContext(), skgpu::Budgeted::kYes,
+                                     bitmap->info(), 0, kTopLeft_GrSurfaceOrigin, nullptr);
 
     // if we can't generate a GPU surface that matches the destination bitmap (e.g. 565) then we
     // attempt to do the intermediate rendering step in 8888
     if (!tmpSurface.get()) {
         SkImageInfo tmpInfo = bitmap->info().makeColorType(SkColorType::kN32_SkColorType);
-        tmpSurface = SkSurface::MakeRenderTarget(mRenderThread.getGrContext(),
-                                                 skgpu::Budgeted::kYes,
-                                                 tmpInfo, 0, kTopLeft_GrSurfaceOrigin, nullptr);
+        tmpSurface = SkSurfaces::RenderTarget(mRenderThread.getGrContext(),
+                                              skgpu::Budgeted::kYes,
+                                              tmpInfo, 0, kTopLeft_GrSurfaceOrigin, nullptr);
         if (!tmpSurface.get()) {
             ALOGW("Unable to generate GPU buffer in a format compatible with the provided bitmap");
             return request->onCopyFinished(CopyResult::UnknownError);
@@ -346,19 +349,19 @@
      * a scaling issue (b/62262733) that was encountered when sampling from an EGLImage into a
      * software buffer.
      */
-    sk_sp<SkSurface> tmpSurface = SkSurface::MakeRenderTarget(mRenderThread.getGrContext(),
-                                                              skgpu::Budgeted::kYes,
-                                                              bitmap->info(),
-                                                              0,
-                                                              kTopLeft_GrSurfaceOrigin, nullptr);
+    sk_sp<SkSurface> tmpSurface = SkSurfaces::RenderTarget(mRenderThread.getGrContext(),
+                                                           skgpu::Budgeted::kYes,
+                                                           bitmap->info(),
+                                                           0,
+                                                           kTopLeft_GrSurfaceOrigin, nullptr);
 
     // if we can't generate a GPU surface that matches the destination bitmap (e.g. 565) then we
     // attempt to do the intermediate rendering step in 8888
     if (!tmpSurface.get()) {
         SkImageInfo tmpInfo = bitmap->info().makeColorType(SkColorType::kN32_SkColorType);
-        tmpSurface = SkSurface::MakeRenderTarget(mRenderThread.getGrContext(),
-                                                 skgpu::Budgeted::kYes,
-                                                 tmpInfo, 0, kTopLeft_GrSurfaceOrigin, nullptr);
+        tmpSurface = SkSurfaces::RenderTarget(mRenderThread.getGrContext(),
+                                              skgpu::Budgeted::kYes,
+                                              tmpInfo, 0, kTopLeft_GrSurfaceOrigin, nullptr);
         if (!tmpSurface.get()) {
             ALOGW("Unable to generate GPU buffer in a format compatible with the provided bitmap");
             return false;
diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
index 924fbd6..3cd0e75 100644
--- a/libs/hwui/RecordingCanvas.cpp
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -107,11 +107,6 @@
 };
 static_assert(sizeof(Op) == 4, "");
 
-struct Flush final : Op {
-    static const auto kType = Type::Flush;
-    void draw(SkCanvas* c, const SkMatrix&) const { c->flush(); }
-};
-
 struct Save final : Op {
     static const auto kType = Type::Save;
     void draw(SkCanvas* c, const SkMatrix&) const { c->save(); }
@@ -675,12 +670,11 @@
             // because the webview functor still doesn't respect the canvas clip stack.
             const SkIRect deviceBounds = c->getDeviceClipBounds();
             if (mLayerSurface == nullptr || c->imageInfo() != mLayerImageInfo) {
-                GrRecordingContext* directContext = c->recordingContext();
                 mLayerImageInfo =
                         c->imageInfo().makeWH(deviceBounds.width(), deviceBounds.height());
-                mLayerSurface = SkSurface::MakeRenderTarget(directContext, skgpu::Budgeted::kYes,
-                                                            mLayerImageInfo, 0,
-                                                            kTopLeft_GrSurfaceOrigin, nullptr);
+                // SkCanvas::makeSurface returns a new surface that will be GPU-backed if
+                // canvas was also.
+                mLayerSurface = c->makeSurface(mLayerImageInfo);
             }
 
             SkCanvas* layerCanvas = mLayerSurface->getCanvas();
@@ -752,10 +746,6 @@
     }
 }
 
-void DisplayListData::flush() {
-    this->push<Flush>(0);
-}
-
 void DisplayListData::save() {
     this->push<Save>(0);
 }
@@ -1047,10 +1037,6 @@
     return nullptr;
 }
 
-void RecordingCanvas::onFlush() {
-    fDL->flush();
-}
-
 void RecordingCanvas::willSave() {
     mSaveCount++;
     fDL->save();
diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h
index 1f4ba5d..4f54ee2 100644
--- a/libs/hwui/RecordingCanvas.h
+++ b/libs/hwui/RecordingCanvas.h
@@ -127,8 +127,6 @@
 private:
     friend class RecordingCanvas;
 
-    void flush();
-
     void save();
     void saveLayer(const SkRect*, const SkPaint*, const SkImageFilter*, SkCanvas::SaveLayerFlags);
     void saveBehind(const SkRect*);
@@ -208,8 +206,6 @@
     void willRestore() override;
     bool onDoSaveBehind(const SkRect*) override;
 
-    void onFlush() override;
-
     void didConcat44(const SkM44&) override;
     void didSetM44(const SkM44&) override;
     void didScale(SkScalar, SkScalar) override;
diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp
index 1c39db3..1dd22cf 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -260,6 +260,12 @@
         pushStagingDisplayListChanges(observer, info);
     }
 
+    // always damageSelf when filtering backdrop content, or else the BackdropFilterDrawable will
+    // get a wrong snapshot of previous content.
+    if (mProperties.layerProperties().getBackdropImageFilter()) {
+        damageSelf(info);
+    }
+
     if (mDisplayList) {
         info.out.hasFunctors |= mDisplayList.hasFunctor();
         mHasHolePunches = mDisplayList.hasHolePunches();
diff --git a/libs/hwui/RenderProperties.cpp b/libs/hwui/RenderProperties.cpp
index 0589f13..c537123 100644
--- a/libs/hwui/RenderProperties.cpp
+++ b/libs/hwui/RenderProperties.cpp
@@ -55,6 +55,12 @@
     return true;
 }
 
+bool LayerProperties::setBackdropImageFilter(SkImageFilter* imageFilter) {
+    if (mBackdropImageFilter.get() == imageFilter) return false;
+    mBackdropImageFilter = sk_ref_sp(imageFilter);
+    return true;
+}
+
 bool LayerProperties::setFromPaint(const SkPaint* paint) {
     bool changed = false;
     changed |= setAlpha(static_cast<uint8_t>(PaintUtils::getAlphaDirect(paint)));
@@ -70,6 +76,7 @@
     setXferMode(other.xferMode());
     setColorFilter(other.getColorFilter());
     setImageFilter(other.getImageFilter());
+    setBackdropImageFilter(other.getBackdropImageFilter());
     mStretchEffect = other.mStretchEffect;
     return *this;
 }
diff --git a/libs/hwui/RenderProperties.h b/libs/hwui/RenderProperties.h
index 064ba7a..e358b57 100644
--- a/libs/hwui/RenderProperties.h
+++ b/libs/hwui/RenderProperties.h
@@ -97,8 +97,12 @@
 
     bool setImageFilter(SkImageFilter* imageFilter);
 
+    bool setBackdropImageFilter(SkImageFilter* imageFilter);
+
     SkImageFilter* getImageFilter() const { return mImageFilter.get(); }
 
+    SkImageFilter* getBackdropImageFilter() const { return mBackdropImageFilter.get(); }
+
     const StretchEffect& getStretchEffect() const { return mStretchEffect; }
 
     StretchEffect& mutableStretchEffect() { return mStretchEffect; }
@@ -129,6 +133,7 @@
     SkBlendMode mMode;
     sk_sp<SkColorFilter> mColorFilter;
     sk_sp<SkImageFilter> mImageFilter;
+    sk_sp<SkImageFilter> mBackdropImageFilter;
     StretchEffect mStretchEffect;
 };
 
diff --git a/libs/hwui/SkiaInterpolator.cpp b/libs/hwui/SkiaInterpolator.cpp
index b58f517..c67b135 100644
--- a/libs/hwui/SkiaInterpolator.cpp
+++ b/libs/hwui/SkiaInterpolator.cpp
@@ -18,9 +18,8 @@
 
 #include "include/core/SkScalar.h"
 #include "include/core/SkTypes.h"
-#include "include/private/SkFixed.h"
-#include "src/core/SkTSearch.h"
 
+#include <cstdlib>
 #include <log/log.h>
 
 typedef int Dot14;
@@ -41,18 +40,18 @@
     if (x <= 0) {
         return 0;
     }
-    if (x >= SK_Scalar1) {
+    if (x >= 1.0f) {
         return Dot14_ONE;
     }
-    return SkScalarToFixed(x) >> 2;
+    return static_cast<Dot14>(x * Dot14_ONE);
 }
 
 static float SkUnitCubicInterp(float value, float bx, float by, float cx, float cy) {
     // pin to the unit-square, and convert to 2.14
     Dot14 x = pin_and_convert(value);
 
-    if (x == 0) return 0;
-    if (x == Dot14_ONE) return SK_Scalar1;
+    if (x == 0) return 0.0f;
+    if (x == Dot14_ONE) return 1.0f;
 
     Dot14 b = pin_and_convert(bx);
     Dot14 c = pin_and_convert(cx);
@@ -84,7 +83,7 @@
     A = 3 * b;
     B = 3 * (c - 2 * b);
     C = 3 * (b - c) + Dot14_ONE;
-    return SkFixedToScalar(eval_cubic(t, A, B, C) << 2);
+    return Dot14ToFloat(eval_cubic(t, A, B, C));
 }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -104,7 +103,7 @@
     fFlags = 0;
     fElemCount = static_cast<uint8_t>(elemCount);
     fFrameCount = static_cast<int16_t>(frameCount);
-    fRepeat = SK_Scalar1;
+    fRepeat = 1.0f;
     if (fStorage) {
         free(fStorage);
         fStorage = nullptr;
@@ -136,17 +135,46 @@
 
 float SkiaInterpolatorBase::ComputeRelativeT(SkMSec time, SkMSec prevTime, SkMSec nextTime,
                                              const float blend[4]) {
-    SkASSERT(time > prevTime && time < nextTime);
+    LOG_FATAL_IF(time < prevTime || time > nextTime);
 
     float t = (float)(time - prevTime) / (float)(nextTime - prevTime);
     return blend ? SkUnitCubicInterp(t, blend[0], blend[1], blend[2], blend[3]) : t;
 }
 
+// Returns the index of where the item is or the bit not of the index
+// where the item should go in order to keep arr sorted in ascending order.
+int SkiaInterpolatorBase::binarySearch(const SkTimeCode* arr, int count, SkMSec target) {
+    if (count <= 0) {
+        return ~0;
+    }
+
+    int lo = 0;
+    int hi = count - 1;
+
+    while (lo < hi) {
+        int mid = (hi + lo) / 2;
+        SkMSec elem = arr[mid].fTime;
+        if (elem == target) {
+            return mid;
+        } else if (elem < target) {
+            lo = mid + 1;
+        } else {
+            hi = mid;
+        }
+    }
+    // Check to see if target is greater or less than where we stopped
+    if (target < arr[lo].fTime) {
+        return ~lo;
+    }
+    // e.g. it should go at the end.
+    return ~(lo + 1);
+}
+
 SkiaInterpolatorBase::Result SkiaInterpolatorBase::timeToT(SkMSec time, float* T, int* indexPtr,
                                                            bool* exactPtr) const {
-    SkASSERT(fFrameCount > 0);
+    LOG_FATAL_IF(fFrameCount <= 0);
     Result result = kNormal_Result;
-    if (fRepeat != SK_Scalar1) {
+    if (fRepeat != 1.0f) {
         SkMSec startTime = 0, endTime = 0;  // initialize to avoid warning
         this->getDuration(&startTime, &endTime);
         SkMSec totalTime = endTime - startTime;
@@ -168,10 +196,8 @@
         time = offsetTime + startTime;
     }
 
-    int index = SkTSearch<SkMSec>(&fTimes[0].fTime, fFrameCount, time, sizeof(SkTimeCode));
-
+    int index = SkiaInterpolatorBase::binarySearch(fTimes, fFrameCount, time);
     bool exact = true;
-
     if (index < 0) {
         index = ~index;
         if (index == 0) {
@@ -184,10 +210,11 @@
             }
             result = kFreezeEnd_Result;
         } else {
+            // Need to interpolate between two frames.
             exact = false;
         }
     }
-    SkASSERT(index < fFrameCount);
+    LOG_FATAL_IF(index >= fFrameCount);
     const SkTimeCode* nextTime = &fTimes[index];
     SkMSec nextT = nextTime[0].fTime;
     if (exact) {
@@ -207,7 +234,7 @@
 }
 
 SkiaInterpolator::SkiaInterpolator(int elemCount, int frameCount) {
-    SkASSERT(elemCount > 0);
+    LOG_FATAL_IF(elemCount <= 0);
     this->reset(elemCount, frameCount);
 }
 
@@ -221,21 +248,19 @@
     fValues = (float*)((char*)fStorage + sizeof(SkTimeCode) * frameCount);
 }
 
-#define SK_Fixed1Third (SK_Fixed1 / 3)
-#define SK_Fixed2Third (SK_Fixed1 * 2 / 3)
-
 static const float gIdentityBlend[4] = {0.33333333f, 0.33333333f, 0.66666667f, 0.66666667f};
 
 bool SkiaInterpolator::setKeyFrame(int index, SkMSec time, const float values[],
                                    const float blend[4]) {
-    SkASSERT(values != nullptr);
+    LOG_FATAL_IF(values == nullptr);
 
     if (blend == nullptr) {
         blend = gIdentityBlend;
     }
 
-    bool success = ~index == SkTSearch<SkMSec>(&fTimes->fTime, index, time, sizeof(SkTimeCode));
-    SkASSERT(success);
+    // Verify the time should go after all the frames before index
+    bool success = ~index == SkiaInterpolatorBase::binarySearch(fTimes, index, time);
+    LOG_FATAL_IF(!success);
     if (success) {
         SkTimeCode* timeCode = &fTimes[index];
         timeCode->fTime = time;
@@ -257,7 +282,7 @@
         if (exact) {
             memcpy(values, nextSrc, fElemCount * sizeof(float));
         } else {
-            SkASSERT(index > 0);
+            LOG_FATAL_IF(index <= 0);
 
             const float* prevSrc = nextSrc - fElemCount;
 
diff --git a/libs/hwui/SkiaInterpolator.h b/libs/hwui/SkiaInterpolator.h
index 9422cb5..62e6c1e 100644
--- a/libs/hwui/SkiaInterpolator.h
+++ b/libs/hwui/SkiaInterpolator.h
@@ -68,14 +68,16 @@
     enum Flags { kMirror = 1, kReset = 2, kHasBlend = 4 };
     static float ComputeRelativeT(uint32_t time, uint32_t prevTime, uint32_t nextTime,
                                   const float blend[4] = nullptr);
-    int16_t fFrameCount;
-    uint8_t fElemCount;
-    uint8_t fFlags;
-    float fRepeat;
     struct SkTimeCode {
         uint32_t fTime;
         float fBlend[4];
     };
+    static int binarySearch(const SkTimeCode* arr, int count, uint32_t target);
+
+    int16_t fFrameCount;
+    uint8_t fElemCount;
+    uint8_t fFlags;
+    float fRepeat;
     SkTimeCode* fTimes;  // pointer into fStorage
     void* fStorage;
 };
diff --git a/libs/hwui/SkiaWrapper.h b/libs/hwui/SkiaWrapper.h
new file mode 100644
index 0000000..bd0e35a
--- /dev/null
+++ b/libs/hwui/SkiaWrapper.h
@@ -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.
+ */
+
+#ifndef SKIA_WRAPPER_H_
+#define SKIA_WRAPPER_H_
+
+#include <SkRefCnt.h>
+#include <utils/RefBase.h>
+
+namespace android::uirenderer {
+
+template <typename T>
+class SkiaWrapper : public VirtualLightRefBase {
+public:
+    sk_sp<T> getInstance() {
+        if (mInstance != nullptr && shouldDiscardInstance()) {
+            mInstance = nullptr;
+        }
+
+        if (mInstance == nullptr) {
+            mInstance = createInstance();
+            mGenerationId++;
+        }
+        return mInstance;
+    }
+
+    virtual bool shouldDiscardInstance() const { return false; }
+
+    void discardInstance() { mInstance = nullptr; }
+
+    [[nodiscard]] int32_t getGenerationId() const { return mGenerationId; }
+
+protected:
+    virtual sk_sp<T> createInstance() = 0;
+
+private:
+    sk_sp<T> mInstance = nullptr;
+    int32_t mGenerationId = 0;
+};
+
+}  // namespace android::uirenderer
+
+#endif  // SKIA_WRAPPER_H_
diff --git a/libs/hwui/apex/android_canvas.cpp b/libs/hwui/apex/android_canvas.cpp
index 905b123..19f726a 100644
--- a/libs/hwui/apex/android_canvas.cpp
+++ b/libs/hwui/apex/android_canvas.cpp
@@ -45,9 +45,9 @@
     SkImageInfo imageInfo = uirenderer::ANativeWindowToImageInfo(*buffer, cs);
     size_t rowBytes = buffer->stride * imageInfo.bytesPerPixel();
 
-    // If SkSurface::MakeRasterDirect fails then we should as well as we will not be able to
+    // If SkSurfaces::WrapPixels fails then we should as well as we will not be able to
     // draw into the canvas.
-    sk_sp<SkSurface> surface = SkSurface::MakeRasterDirect(imageInfo, buffer->bits, rowBytes);
+    sk_sp<SkSurface> surface = SkSurfaces::WrapPixels(imageInfo, buffer->bits, rowBytes);
     if (surface.get() != nullptr) {
         if (outBitmap) {
             outBitmap->setInfo(imageInfo, rowBytes);
diff --git a/libs/hwui/hwui/AnimatedImageDrawable.cpp b/libs/hwui/hwui/AnimatedImageDrawable.cpp
index 8049dc9..27773a6 100644
--- a/libs/hwui/hwui/AnimatedImageDrawable.cpp
+++ b/libs/hwui/hwui/AnimatedImageDrawable.cpp
@@ -111,7 +111,7 @@
     {
         std::unique_lock lock{mImageLock};
         snap.mDurationMS = adjustFrameDuration(mSkAnimatedImage->decodeNextFrame());
-        snap.mPic.reset(mSkAnimatedImage->newPictureSnapshot());
+        snap.mPic = mSkAnimatedImage->makePictureSnapshot();
     }
 
     return snap;
@@ -123,7 +123,7 @@
     {
         std::unique_lock lock{mImageLock};
         mSkAnimatedImage->reset();
-        snap.mPic.reset(mSkAnimatedImage->newPictureSnapshot());
+        snap.mPic = mSkAnimatedImage->makePictureSnapshot();
         snap.mDurationMS = currentFrameDuration();
     }
 
diff --git a/libs/hwui/hwui/Bitmap.cpp b/libs/hwui/hwui/Bitmap.cpp
index 92d875b..8344a86 100644
--- a/libs/hwui/hwui/Bitmap.cpp
+++ b/libs/hwui/hwui/Bitmap.cpp
@@ -43,12 +43,15 @@
 #include <SkColor.h>
 #include <SkEncodedImageFormat.h>
 #include <SkHighContrastFilter.h>
-#include <SkImageEncoder.h>
+#include <SkImage.h>
+#include <SkImageAndroid.h>
 #include <SkImagePriv.h>
 #include <SkJpegGainmapEncoder.h>
 #include <SkPixmap.h>
 #include <SkRect.h>
 #include <SkStream.h>
+#include <SkJpegEncoder.h>
+#include <SkPngEncoder.h>
 #include <SkWebpEncoder.h>
 
 #include <limits>
@@ -296,7 +299,8 @@
     mPixelStorage.hardware.size = AHardwareBuffer_getAllocationSize(buffer);
     AHardwareBuffer_acquire(buffer);
     setImmutable();  // HW bitmaps are always immutable
-    mImage = SkImage::MakeFromAHardwareBuffer(buffer, mInfo.alphaType(), mInfo.refColorSpace());
+    mImage = SkImages::DeferredFromAHardwareBuffer(buffer, mInfo.alphaType(),
+                                                   mInfo.refColorSpace());
 }
 #endif
 
@@ -407,7 +411,12 @@
         // Note we don't cache in this case, because the raster image holds a pointer to this Bitmap
         // internally and ~Bitmap won't be invoked.
         // TODO: refactor Bitmap to not derive from SkPixelRef, which would allow caching here.
+#ifdef __ANDROID__
+        // pinnable images are only supported with the Ganesh GPU backend compiled in.
+        image = SkImages::PinnableRasterFromBitmap(skiaBitmap);
+#else
         image = SkMakeImageFromRasterBitmap(skiaBitmap, kNever_SkCopyPixelsMode);
+#endif
     }
     return image;
 }
@@ -528,17 +537,25 @@
         return false;
     }
 
-    SkEncodedImageFormat fm;
     switch (format) {
-        case JavaCompressFormat::Jpeg:
-            fm = SkEncodedImageFormat::kJPEG;
-            break;
+        case JavaCompressFormat::Jpeg: {
+            SkJpegEncoder::Options options;
+            options.fQuality = quality;
+            return SkJpegEncoder::Encode(stream, bitmap.pixmap(), options);
+        }
         case JavaCompressFormat::Png:
-            fm = SkEncodedImageFormat::kPNG;
-            break;
-        case JavaCompressFormat::Webp:
-            fm = SkEncodedImageFormat::kWEBP;
-            break;
+            return SkPngEncoder::Encode(stream, bitmap.pixmap(), {});
+        case JavaCompressFormat::Webp: {
+            SkWebpEncoder::Options options;
+            if (quality >= 100) {
+                options.fCompression = SkWebpEncoder::Compression::kLossless;
+                options.fQuality = 75; // This is effort to compress
+            } else {
+                options.fCompression = SkWebpEncoder::Compression::kLossy;
+                options.fQuality = quality;
+            }
+            return SkWebpEncoder::Encode(stream, bitmap.pixmap(), options);
+        }
         case JavaCompressFormat::WebpLossy:
         case JavaCompressFormat::WebpLossless: {
             SkWebpEncoder::Options options;
@@ -548,8 +565,6 @@
             return SkWebpEncoder::Encode(stream, bitmap.pixmap(), options);
         }
     }
-
-    return SkEncodeImage(stream, bitmap, fm, quality);
 }
 
 sp<uirenderer::Gainmap> Bitmap::gainmap() const {
diff --git a/libs/hwui/hwui/Canvas.cpp b/libs/hwui/hwui/Canvas.cpp
index cd8af3d..2351797 100644
--- a/libs/hwui/hwui/Canvas.cpp
+++ b/libs/hwui/hwui/Canvas.cpp
@@ -151,7 +151,7 @@
         memcpy(outPositions, positions, sizeof(float) * 2 * glyphCount);
     };
 
-    const minikin::MinikinFont* minikinFont = font.typeface().get();
+    const minikin::MinikinFont* minikinFont = font.baseTypeface().get();
     SkFont* skfont = &copied.getSkFont();
     MinikinFontSkia::populateSkFont(skfont, minikinFont, minikin::FontFakery());
 
diff --git a/libs/hwui/hwui/MinikinUtils.h b/libs/hwui/hwui/MinikinUtils.h
index 009b84b..51960b0 100644
--- a/libs/hwui/hwui/MinikinUtils.h
+++ b/libs/hwui/hwui/MinikinUtils.h
@@ -76,7 +76,7 @@
         size_t start = 0;
         size_t nGlyphs = layout.nGlyphs();
         for (size_t i = 0; i < nGlyphs; i++) {
-            const minikin::MinikinFont* nextFont = layout.getFont(i)->typeface().get();
+            const minikin::MinikinFont* nextFont = layout.typeface(i).get();
             if (i > 0 && nextFont != curFont) {
                 SkFont* skfont = &paint->getSkFont();
                 MinikinFontSkia::populateSkFont(skfont, curFont, layout.getFakery(start));
diff --git a/libs/hwui/hwui/Typeface.cpp b/libs/hwui/hwui/Typeface.cpp
index 3c67edc..b63ee1b 100644
--- a/libs/hwui/hwui/Typeface.cpp
+++ b/libs/hwui/hwui/Typeface.cpp
@@ -140,9 +140,8 @@
 
         const minikin::FontStyle defaultStyle;
         const minikin::MinikinFont* mf =
-                families.empty()
-                        ? nullptr
-                        : families[0]->getClosestMatch(defaultStyle).font->typeface().get();
+                families.empty() ? nullptr
+                                 : families[0]->getClosestMatch(defaultStyle).typeface().get();
         if (mf != nullptr) {
             SkTypeface* skTypeface = reinterpret_cast<const MinikinFontSkia*>(mf)->GetSkTypeface();
             const SkFontStyle& style = skTypeface->fontStyle();
diff --git a/libs/hwui/jni/AnimatedImageDrawable.cpp b/libs/hwui/jni/AnimatedImageDrawable.cpp
index a7f5aa83..90b1da8 100644
--- a/libs/hwui/jni/AnimatedImageDrawable.cpp
+++ b/libs/hwui/jni/AnimatedImageDrawable.cpp
@@ -14,10 +14,6 @@
  * limitations under the License.
  */
 
-#include "GraphicsJNI.h"
-#include "ImageDecoder.h"
-#include "Utils.h"
-
 #include <SkAndroidCodec.h>
 #include <SkAnimatedImage.h>
 #include <SkColorFilter.h>
@@ -27,10 +23,15 @@
 #include <SkRect.h>
 #include <SkRefCnt.h>
 #include <hwui/AnimatedImageDrawable.h>
-#include <hwui/ImageDecoder.h>
 #include <hwui/Canvas.h>
+#include <hwui/ImageDecoder.h>
 #include <utils/Looper.h>
 
+#include "ColorFilter.h"
+#include "GraphicsJNI.h"
+#include "ImageDecoder.h"
+#include "Utils.h"
+
 using namespace android;
 
 static jclass gAnimatedImageDrawableClass;
@@ -145,8 +146,9 @@
 static void AnimatedImageDrawable_nSetColorFilter(JNIEnv* env, jobject /*clazz*/, jlong nativePtr,
                                                   jlong nativeFilter) {
     auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr);
-    auto* filter = reinterpret_cast<SkColorFilter*>(nativeFilter);
-    drawable->setStagingColorFilter(sk_ref_sp(filter));
+    auto filter = uirenderer::ColorFilter::fromJava(nativeFilter);
+    auto skColorFilter = filter != nullptr ? filter->getInstance() : sk_sp<SkColorFilter>();
+    drawable->setStagingColorFilter(skColorFilter);
 }
 
 static jboolean AnimatedImageDrawable_nIsRunning(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) {
diff --git a/libs/hwui/jni/ColorFilter.cpp b/libs/hwui/jni/ColorFilter.cpp
index 4bd7ef4..0b95148 100644
--- a/libs/hwui/jni/ColorFilter.cpp
+++ b/libs/hwui/jni/ColorFilter.cpp
@@ -15,20 +15,21 @@
 ** limitations under the License.
 */
 
-#include "GraphicsJNI.h"
+#include "ColorFilter.h"
 
+#include "GraphicsJNI.h"
 #include "SkBlendMode.h"
-#include "SkColorFilter.h"
-#include "SkColorMatrixFilter.h"
 
 namespace android {
 
 using namespace uirenderer;
 
-class SkColorFilterGlue {
+class ColorFilterGlue {
 public:
-    static void SafeUnref(SkColorFilter* filter) {
-        SkSafeUnref(filter);
+    static void SafeUnref(ColorFilter* filter) {
+        if (filter) {
+            filter->decStrong(nullptr);
+        }
     }
 
     static jlong GetNativeFinalizer(JNIEnv*, jobject) {
@@ -36,41 +37,75 @@
     }
 
     static jlong CreateBlendModeFilter(JNIEnv* env, jobject, jint srcColor, jint modeHandle) {
-        SkBlendMode mode = static_cast<SkBlendMode>(modeHandle);
-        return reinterpret_cast<jlong>(SkColorFilters::Blend(srcColor, mode).release());
+        auto mode = static_cast<SkBlendMode>(modeHandle);
+        auto* blendModeFilter = new BlendModeColorFilter(srcColor, mode);
+        blendModeFilter->incStrong(nullptr);
+        return static_cast<jlong>(reinterpret_cast<uintptr_t>(blendModeFilter));
     }
 
     static jlong CreateLightingFilter(JNIEnv* env, jobject, jint mul, jint add) {
-        return reinterpret_cast<jlong>(SkColorMatrixFilter::MakeLightingFilter(mul, add).release());
+        auto* lightingFilter = new LightingFilter(mul, add);
+        lightingFilter->incStrong(nullptr);
+        return static_cast<jlong>(reinterpret_cast<uintptr_t>(lightingFilter));
     }
 
-    static jlong CreateColorMatrixFilter(JNIEnv* env, jobject, jfloatArray jarray) {
-        float matrix[20];
-        env->GetFloatArrayRegion(jarray, 0, 20, matrix);
+    static void SetLightingFilterMul(JNIEnv* env, jobject, jlong lightingFilterPtr, jint mul) {
+        auto* filter = reinterpret_cast<LightingFilter*>(lightingFilterPtr);
+        if (filter) {
+            filter->setMul(mul);
+        }
+    }
+
+    static void SetLightingFilterAdd(JNIEnv* env, jobject, jlong lightingFilterPtr, jint add) {
+        auto* filter = reinterpret_cast<LightingFilter*>(lightingFilterPtr);
+        if (filter) {
+            filter->setAdd(add);
+        }
+    }
+
+    static std::vector<float> getMatrixFromJFloatArray(JNIEnv* env, jfloatArray jarray) {
+        std::vector<float> matrix(20);
+        // float matrix[20];
+        env->GetFloatArrayRegion(jarray, 0, 20, matrix.data());
         // java biases the translates by 255, so undo that before calling skia
         matrix[ 4] *= (1.0f/255);
         matrix[ 9] *= (1.0f/255);
         matrix[14] *= (1.0f/255);
         matrix[19] *= (1.0f/255);
-        return reinterpret_cast<jlong>(SkColorFilters::Matrix(matrix).release());
+        return matrix;
+    }
+
+    static jlong CreateColorMatrixFilter(JNIEnv* env, jobject, jfloatArray jarray) {
+        std::vector<float> matrix = getMatrixFromJFloatArray(env, jarray);
+        auto* colorMatrixColorFilter = new ColorMatrixColorFilter(std::move(matrix));
+        colorMatrixColorFilter->incStrong(nullptr);
+        return static_cast<jlong>(reinterpret_cast<uintptr_t>(colorMatrixColorFilter));
+    }
+
+    static void SetColorMatrix(JNIEnv* env, jobject, jlong colorMatrixColorFilterPtr,
+                               jfloatArray jarray) {
+        auto* filter = reinterpret_cast<ColorMatrixColorFilter*>(colorMatrixColorFilterPtr);
+        if (filter) {
+            filter->setMatrix(getMatrixFromJFloatArray(env, jarray));
+        }
     }
 };
 
 static const JNINativeMethod colorfilter_methods[] = {
-    {"nativeGetFinalizer", "()J", (void*) SkColorFilterGlue::GetNativeFinalizer }
-};
+        {"nativeGetFinalizer", "()J", (void*)ColorFilterGlue::GetNativeFinalizer}};
 
 static const JNINativeMethod blendmode_methods[] = {
-    { "native_CreateBlendModeFilter", "(II)J", (void*) SkColorFilterGlue::CreateBlendModeFilter },
+        {"native_CreateBlendModeFilter", "(II)J", (void*)ColorFilterGlue::CreateBlendModeFilter},
 };
 
 static const JNINativeMethod lighting_methods[] = {
-    { "native_CreateLightingFilter", "(II)J", (void*) SkColorFilterGlue::CreateLightingFilter },
-};
+        {"native_CreateLightingFilter", "(II)J", (void*)ColorFilterGlue::CreateLightingFilter},
+        {"native_SetLightingFilterAdd", "(JI)V", (void*)ColorFilterGlue::SetLightingFilterAdd},
+        {"native_SetLightingFilterMul", "(JI)V", (void*)ColorFilterGlue::SetLightingFilterMul}};
 
 static const JNINativeMethod colormatrix_methods[] = {
-    { "nativeColorMatrixFilter", "([F)J", (void*) SkColorFilterGlue::CreateColorMatrixFilter },
-};
+        {"nativeColorMatrixFilter", "([F)J", (void*)ColorFilterGlue::CreateColorMatrixFilter},
+        {"nativeSetColorMatrix", "(J[F)V", (void*)ColorFilterGlue::SetColorMatrix}};
 
 int register_android_graphics_ColorFilter(JNIEnv* env) {
     android::RegisterMethodsOrDie(env, "android/graphics/ColorFilter", colorfilter_methods,
diff --git a/libs/hwui/jni/CreateJavaOutputStreamAdaptor.cpp b/libs/hwui/jni/CreateJavaOutputStreamAdaptor.cpp
index 15e529e..a66d3b8 100644
--- a/libs/hwui/jni/CreateJavaOutputStreamAdaptor.cpp
+++ b/libs/hwui/jni/CreateJavaOutputStreamAdaptor.cpp
@@ -1,11 +1,11 @@
 #include "CreateJavaOutputStreamAdaptor.h"
 #include "SkData.h"
-#include "SkMalloc.h"
 #include "SkRefCnt.h"
 #include "SkStream.h"
 #include "SkTypes.h"
 #include "Utils.h"
 
+#include <cstdlib>
 #include <nativehelper/JNIHelp.h>
 #include <log/log.h>
 #include <memory>
@@ -177,6 +177,10 @@
     return JavaInputStreamAdaptor::Create(env, stream, storage, swallowExceptions);
 }
 
+static void free_pointer_skproc(const void* ptr, void*) {
+    free((void*)ptr);
+}
+
 sk_sp<SkData> CopyJavaInputStream(JNIEnv* env, jobject inputStream, jbyteArray storage) {
     std::unique_ptr<SkStream> stream(CreateJavaInputStreamAdaptor(env, inputStream, storage));
     if (!stream) {
@@ -186,19 +190,31 @@
     size_t bufferSize = 4096;
     size_t streamLen = 0;
     size_t len;
-    char* data = (char*)sk_malloc_throw(bufferSize);
+    char* data = (char*)malloc(bufferSize);
+    LOG_ALWAYS_FATAL_IF(!data);
 
     while ((len = stream->read(data + streamLen,
                                bufferSize - streamLen)) != 0) {
         streamLen += len;
         if (streamLen == bufferSize) {
             bufferSize *= 2;
-            data = (char*)sk_realloc_throw(data, bufferSize);
+            data = (char*)realloc(data, bufferSize);
+            LOG_ALWAYS_FATAL_IF(!data);
         }
     }
-    data = (char*)sk_realloc_throw(data, streamLen);
-
-    return SkData::MakeFromMalloc(data, streamLen);
+    if (streamLen == 0) {
+        // realloc with size 0 is unspecified behavior in C++11
+        free(data);
+        data = nullptr;
+    } else {
+        // Trim down the buffer to the actual size of the data.
+        LOG_FATAL_IF(streamLen > bufferSize);
+        data = (char*)realloc(data, streamLen);
+        LOG_ALWAYS_FATAL_IF(!data);
+    }
+    // Just in case sk_free differs from free, we ask Skia to use
+    // free to cleanup the buffer that SkData wraps.
+    return SkData::MakeWithProc(data, streamLen, free_pointer_skproc, nullptr);
 }
 
 ///////////////////////////////////////////////////////////////////////////////
diff --git a/libs/hwui/jni/FontFamily.cpp b/libs/hwui/jni/FontFamily.cpp
index 28e71d7..af1668f 100644
--- a/libs/hwui/jni/FontFamily.cpp
+++ b/libs/hwui/jni/FontFamily.cpp
@@ -45,6 +45,7 @@
 
 namespace android {
 
+namespace {
 struct NativeFamilyBuilder {
     NativeFamilyBuilder(uint32_t langId, int variant)
         : langId(langId), variant(static_cast<minikin::FamilyVariant>(variant)) {}
@@ -53,6 +54,7 @@
     std::vector<std::shared_ptr<minikin::Font>> fonts;
     std::vector<minikin::FontVariation> axes;
 };
+}  // namespace
 
 static inline NativeFamilyBuilder* toNativeBuilder(jlong ptr) {
     return reinterpret_cast<NativeFamilyBuilder*>(ptr);
@@ -87,7 +89,8 @@
     }
     std::shared_ptr<minikin::FontFamily> family = minikin::FontFamily::create(
             builder->langId, builder->variant, std::move(builder->fonts),
-            true /* isCustomFallback */, false /* isDefaultFallback */);
+            true /* isCustomFallback */, false /* isDefaultFallback */,
+            minikin::VariationFamilyType::None);
     if (family->getCoverage().length() == 0) {
         return 0;
     }
diff --git a/libs/hwui/jni/GraphicsJNI.h b/libs/hwui/jni/GraphicsJNI.h
index 23ab5dd..b9fff36 100644
--- a/libs/hwui/jni/GraphicsJNI.h
+++ b/libs/hwui/jni/GraphicsJNI.h
@@ -125,14 +125,6 @@
     static jobject createBitmapRegionDecoder(JNIEnv* env,
                                              android::BitmapRegionDecoderWrapper* bitmap);
 
-    /**
-     * Given a bitmap we natively allocate a memory block to store the contents
-     * of that bitmap.  The memory is then attached to the bitmap via an
-     * SkPixelRef, which ensures that upon deletion the appropriate caches
-     * are notified.
-     */
-    static bool allocatePixels(JNIEnv* env, SkBitmap* bitmap);
-
     /** Copy the colors in colors[] to the bitmap, convert to the correct
         format along the way.
         Whether to use premultiplied pixels is determined by dstBitmap's alphaType.
diff --git a/libs/hwui/jni/MaskFilter.cpp b/libs/hwui/jni/MaskFilter.cpp
index 048ce02..cbd4520 100644
--- a/libs/hwui/jni/MaskFilter.cpp
+++ b/libs/hwui/jni/MaskFilter.cpp
@@ -1,6 +1,5 @@
 #include "GraphicsJNI.h"
 #include "SkMaskFilter.h"
-#include "SkBlurMask.h"
 #include "SkBlurMaskFilter.h"
 #include "SkBlurTypes.h"
 #include "SkTableMaskFilter.h"
@@ -11,6 +10,13 @@
     }
 }
 
+// From https://skia.googlesource.com/skia/+/d74c99a3cd5eef5f16b2eb226e6b45fe523c8552/src/core/SkBlurMask.cpp#28
+static constexpr float kBLUR_SIGMA_SCALE = 0.57735f;
+
+static float convertRadiusToSigma(float radius) {
+    return radius > 0 ? kBLUR_SIGMA_SCALE * radius + 0.5f : 0.0f;
+}
+
 class SkMaskFilterGlue {
 public:
     static void destructor(JNIEnv* env, jobject, jlong filterHandle) {
@@ -19,7 +25,7 @@
     }
 
     static jlong createBlur(JNIEnv* env, jobject, jfloat radius, jint blurStyle) {
-        SkScalar sigma = SkBlurMask::ConvertRadiusToSigma(radius);
+        SkScalar sigma = convertRadiusToSigma(radius);
         SkMaskFilter* filter = SkMaskFilter::MakeBlur((SkBlurStyle)blurStyle, sigma).release();
         ThrowIAE_IfNull(env, filter);
         return reinterpret_cast<jlong>(filter);
@@ -34,7 +40,7 @@
             direction[i] = values[i];
         }
 
-        SkScalar sigma = SkBlurMask::ConvertRadiusToSigma(radius);
+        SkScalar sigma = convertRadiusToSigma(radius);
         SkMaskFilter* filter =  SkBlurMaskFilter::MakeEmboss(sigma,
                 direction, ambient, specular).release();
         ThrowIAE_IfNull(env, filter);
diff --git a/libs/hwui/jni/Paint.cpp b/libs/hwui/jni/Paint.cpp
index 13357fa..d2a4efe 100644
--- a/libs/hwui/jni/Paint.cpp
+++ b/libs/hwui/jni/Paint.cpp
@@ -18,13 +18,29 @@
 #undef LOG_TAG
 #define LOG_TAG "Paint"
 
-#include <utils/Log.h>
-
-#include "GraphicsJNI.h"
+#include <hwui/BlurDrawLooper.h>
+#include <hwui/MinikinSkia.h>
+#include <hwui/MinikinUtils.h>
+#include <hwui/Paint.h>
+#include <hwui/Typeface.h>
+#include <minikin/GraphemeBreak.h>
+#include <minikin/LocaleList.h>
+#include <minikin/Measurement.h>
+#include <minikin/MinikinPaint.h>
+#include <nativehelper/ScopedPrimitiveArray.h>
 #include <nativehelper/ScopedStringChars.h>
 #include <nativehelper/ScopedUtfChars.h>
-#include <nativehelper/ScopedPrimitiveArray.h>
+#include <unicode/utf16.h>
+#include <utils/Log.h>
 
+#include <cassert>
+#include <cstring>
+#include <memory>
+#include <vector>
+
+#include "ColorFilter.h"
+#include "GraphicsJNI.h"
+#include "SkBlendMode.h"
 #include "SkColorFilter.h"
 #include "SkColorSpace.h"
 #include "SkFont.h"
@@ -35,26 +51,9 @@
 #include "SkPathEffect.h"
 #include "SkPathUtils.h"
 #include "SkShader.h"
-#include "SkBlendMode.h"
 #include "unicode/uloc.h"
 #include "utils/Blur.h"
 
-#include <hwui/BlurDrawLooper.h>
-#include <hwui/MinikinSkia.h>
-#include <hwui/MinikinUtils.h>
-#include <hwui/Paint.h>
-#include <hwui/Typeface.h>
-#include <minikin/GraphemeBreak.h>
-#include <minikin/LocaleList.h>
-#include <minikin/Measurement.h>
-#include <minikin/MinikinPaint.h>
-#include <unicode/utf16.h>
-
-#include <cassert>
-#include <cstring>
-#include <memory>
-#include <vector>
-
 namespace android {
 
 static void getPosTextPath(const SkFont& font, const uint16_t glyphs[], int count,
@@ -584,7 +583,7 @@
         minikin::FakedFont baseFont = typeface->fFontCollection->baseFontFaked(typeface->fStyle);
         float saveSkewX = font->getSkewX();
         bool savefakeBold = font->isEmbolden();
-        MinikinFontSkia::populateSkFont(font, baseFont.font->typeface().get(), baseFont.fakery);
+        MinikinFontSkia::populateSkFont(font, baseFont.typeface().get(), baseFont.fakery);
         SkScalar spacing = font->getMetrics(metrics);
         // The populateSkPaint call may have changed fake bold / text skew
         // because we want to measure with those effects applied, so now
@@ -821,9 +820,11 @@
 
     static jlong setColorFilter(CRITICAL_JNI_PARAMS_COMMA jlong objHandle, jlong filterHandle) {
         Paint* obj = reinterpret_cast<Paint *>(objHandle);
-        SkColorFilter* filter  = reinterpret_cast<SkColorFilter *>(filterHandle);
-        obj->setColorFilter(sk_ref_sp(filter));
-        return reinterpret_cast<jlong>(obj->getColorFilter());
+        auto colorFilter = uirenderer::ColorFilter::fromJava(filterHandle);
+        auto skColorFilter =
+                colorFilter != nullptr ? colorFilter->getInstance() : sk_sp<SkColorFilter>();
+        obj->setColorFilter(skColorFilter);
+        return filterHandle;
     }
 
     static void setXfermode(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle, jint xfermodeHandle) {
diff --git a/libs/hwui/jni/RenderEffect.cpp b/libs/hwui/jni/RenderEffect.cpp
index f3db170..dcd3fa4 100644
--- a/libs/hwui/jni/RenderEffect.cpp
+++ b/libs/hwui/jni/RenderEffect.cpp
@@ -14,13 +14,13 @@
  * limitations under the License.
  */
 #include "Bitmap.h"
+#include "ColorFilter.h"
 #include "GraphicsJNI.h"
 #include "SkBlendMode.h"
 #include "SkImageFilter.h"
 #include "SkImageFilters.h"
 #include "graphics_jni_helpers.h"
 #include "utils/Blur.h"
-#include <utils/Log.h>
 
 using namespace android::uirenderer;
 
@@ -76,11 +76,13 @@
     jlong colorFilterHandle,
     jlong inputFilterHandle
 ) {
-    auto* colorFilter = reinterpret_cast<const SkColorFilter*>(colorFilterHandle);
+    auto colorFilter = android::uirenderer::ColorFilter::fromJava(colorFilterHandle);
+    auto skColorFilter =
+            colorFilter != nullptr ? colorFilter->getInstance() : sk_sp<SkColorFilter>();
     auto* inputFilter = reinterpret_cast<const SkImageFilter*>(inputFilterHandle);
-    sk_sp<SkImageFilter> colorFilterImageFilter = SkImageFilters::ColorFilter(
-            sk_ref_sp(colorFilter), sk_ref_sp(inputFilter), nullptr);
-   return reinterpret_cast<jlong>(colorFilterImageFilter.release());
+    sk_sp<SkImageFilter> colorFilterImageFilter =
+            SkImageFilters::ColorFilter(skColorFilter, sk_ref_sp(inputFilter), nullptr);
+    return reinterpret_cast<jlong>(colorFilterImageFilter.release());
 }
 
 static jlong createBlendModeEffect(
diff --git a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
index d04de37..ee22f7c 100644
--- a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
+++ b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
@@ -27,7 +27,7 @@
 #include <SkColorSpace.h>
 #include <SkData.h>
 #include <SkImage.h>
-#include <SkImagePriv.h>
+#include <SkImageAndroid.h>
 #include <SkPicture.h>
 #include <SkPixmap.h>
 #include <SkSerialProcs.h>
@@ -35,6 +35,7 @@
 #include <SkTypeface.h>
 #include <dlfcn.h>
 #include <gui/TraceUtils.h>
+#include <include/encode/SkPngEncoder.h>
 #include <inttypes.h>
 #include <media/NdkImage.h>
 #include <media/NdkImageReader.h>
@@ -54,6 +55,7 @@
 
 #include <algorithm>
 #include <atomic>
+#include <log/log.h>
 #include <vector>
 
 #include "JvmErrorReporter.h"
@@ -477,7 +479,7 @@
         // actually cross thread boundaries here, make a copy so it's immutable proper
         if (bitmap && !bitmap->isImmutable()) {
             ATRACE_NAME("Copying mutable bitmap");
-            return SkImage::MakeFromBitmap(*bitmap);
+            return SkImages::RasterFromBitmap(*bitmap);
         }
         if (img->isTextureBacked()) {
             ATRACE_NAME("Readback of texture image");
@@ -497,7 +499,7 @@
                 return sk_ref_sp(img);
             }
             bm.setImmutable();
-            return SkMakeImageFromRasterBitmap(bm, kNever_SkCopyPixelsMode);
+            return SkImages::PinnableRasterFromBitmap(bm);
         }
         return sk_ref_sp(img);
     }
@@ -524,7 +526,16 @@
         if (iter != context->mTextureMap.end()) {
             img = iter->second.get();
         }
-        return img->encodeToData();
+        if (!img) {
+            return nullptr;
+        }
+        // The following encode (specifically the pixel readback) will fail on a
+        // texture-backed image. They should already be raster images, but on
+        // the off-chance they aren't, we will just serialize it as nothing.
+        if (img->isTextureBacked()) {
+            return SkData::MakeEmpty();
+        }
+        return SkPngEncoder::Encode(nullptr, img, {});
     }
 
     void serialize(SkWStream* stream) const override {
diff --git a/libs/hwui/jni/android_graphics_RenderNode.cpp b/libs/hwui/jni/android_graphics_RenderNode.cpp
index 8c7b9a4..2a218a2 100644
--- a/libs/hwui/jni/android_graphics_RenderNode.cpp
+++ b/libs/hwui/jni/android_graphics_RenderNode.cpp
@@ -243,6 +243,13 @@
     return SET_AND_DIRTY(mutateLayerProperties().setImageFilter, imageFilter, RenderNode::GENERIC);
 }
 
+static jboolean android_view_RenderNode_setBackdropRenderEffect(
+        CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr, jlong renderEffectPtr) {
+    SkImageFilter* imageFilter = reinterpret_cast<SkImageFilter*>(renderEffectPtr);
+    return SET_AND_DIRTY(mutateLayerProperties().setBackdropImageFilter, imageFilter,
+                         RenderNode::GENERIC);
+}
+
 static jboolean android_view_RenderNode_setHasOverlappingRendering(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr,
         bool hasOverlappingRendering) {
     return SET_AND_DIRTY(setHasOverlappingRendering, hasOverlappingRendering,
@@ -792,6 +799,8 @@
 
         {"nSetAlpha", "(JF)Z", (void*)android_view_RenderNode_setAlpha},
         {"nSetRenderEffect", "(JJ)Z", (void*)android_view_RenderNode_setRenderEffect},
+        {"nSetBackdropRenderEffect", "(JJ)Z",
+         (void*)android_view_RenderNode_setBackdropRenderEffect},
         {"nSetHasOverlappingRendering", "(JZ)Z",
          (void*)android_view_RenderNode_setHasOverlappingRendering},
         {"nSetUsageHint", "(JI)V", (void*)android_view_RenderNode_setUsageHint},
diff --git a/libs/hwui/jni/android_graphics_drawable_VectorDrawable.cpp b/libs/hwui/jni/android_graphics_drawable_VectorDrawable.cpp
index 9cffceb..ade48f2 100644
--- a/libs/hwui/jni/android_graphics_drawable_VectorDrawable.cpp
+++ b/libs/hwui/jni/android_graphics_drawable_VectorDrawable.cpp
@@ -14,13 +14,13 @@
  * limitations under the License.
  */
 
-#include "GraphicsJNI.h"
+#include <hwui/Paint.h>
 
+#include "ColorFilter.h"
+#include "GraphicsJNI.h"
 #include "PathParser.h"
 #include "VectorDrawable.h"
 
-#include <hwui/Paint.h>
-
 namespace android {
 using namespace uirenderer;
 using namespace uirenderer::VectorDrawable;
@@ -108,8 +108,9 @@
     Canvas* canvas = reinterpret_cast<Canvas*>(canvasPtr);
     SkRect rect;
     GraphicsJNI::jrect_to_rect(env, jrect, &rect);
-    SkColorFilter* colorFilter = reinterpret_cast<SkColorFilter*>(colorFilterPtr);
-    return tree->draw(canvas, colorFilter, rect, needsMirroring, canReuseCache);
+    auto colorFilter = ColorFilter::fromJava(colorFilterPtr);
+    auto skColorFilter = colorFilter != nullptr ? colorFilter->getInstance() : nullptr;
+    return tree->draw(canvas, skColorFilter.get(), rect, needsMirroring, canReuseCache);
 }
 
 /**
diff --git a/libs/hwui/jni/fonts/Font.cpp b/libs/hwui/jni/fonts/Font.cpp
index 1af60b2..8cfdeeb7 100644
--- a/libs/hwui/jni/fonts/Font.cpp
+++ b/libs/hwui/jni/fonts/Font.cpp
@@ -127,7 +127,7 @@
 static jlong Font_Builder_clone(JNIEnv* env, jobject clazz, jlong fontPtr, jlong builderPtr,
                                 jint weight, jboolean italic, jint ttcIndex) {
     FontWrapper* font = reinterpret_cast<FontWrapper*>(fontPtr);
-    MinikinFontSkia* minikinSkia = static_cast<MinikinFontSkia*>(font->font->typeface().get());
+    MinikinFontSkia* minikinSkia = static_cast<MinikinFontSkia*>(font->font->baseTypeface().get());
     std::unique_ptr<NativeFontBuilder> builder(toBuilder(builderPtr));
 
     // Reconstruct SkTypeface with different arguments from existing SkTypeface.
@@ -159,7 +159,7 @@
 static jfloat Font_getGlyphBounds(JNIEnv* env, jobject, jlong fontHandle, jint glyphId,
                                   jlong paintHandle, jobject rect) {
     FontWrapper* font = reinterpret_cast<FontWrapper*>(fontHandle);
-    MinikinFontSkia* minikinSkia = static_cast<MinikinFontSkia*>(font->font->typeface().get());
+    MinikinFontSkia* minikinSkia = static_cast<MinikinFontSkia*>(font->font->baseTypeface().get());
     Paint* paint = reinterpret_cast<Paint*>(paintHandle);
 
     SkFont* skFont = &paint->getSkFont();
@@ -179,7 +179,7 @@
 static jfloat Font_getFontMetrics(JNIEnv* env, jobject, jlong fontHandle, jlong paintHandle,
                                   jobject metricsObj) {
     FontWrapper* font = reinterpret_cast<FontWrapper*>(fontHandle);
-    MinikinFontSkia* minikinSkia = static_cast<MinikinFontSkia*>(font->font->typeface().get());
+    MinikinFontSkia* minikinSkia = static_cast<MinikinFontSkia*>(font->font->baseTypeface().get());
     Paint* paint = reinterpret_cast<Paint*>(paintHandle);
 
     SkFont* skFont = &paint->getSkFont();
@@ -209,7 +209,7 @@
 // Fast Native
 static jobject Font_newByteBuffer(JNIEnv* env, jobject, jlong fontPtr) {
     FontWrapper* font = reinterpret_cast<FontWrapper*>(fontPtr);
-    const std::shared_ptr<minikin::MinikinFont>& minikinFont = font->font->typeface();
+    const std::shared_ptr<minikin::MinikinFont>& minikinFont = font->font->baseTypeface();
     return env->NewDirectByteBuffer(const_cast<void*>(minikinFont->GetFontData()),
                                     minikinFont->GetFontSize());
 }
@@ -217,7 +217,7 @@
 // Critical Native
 static jlong Font_getBufferAddress(CRITICAL_JNI_PARAMS_COMMA jlong fontPtr) {
     FontWrapper* font = reinterpret_cast<FontWrapper*>(fontPtr);
-    return reinterpret_cast<jlong>(font->font->typeface()->GetFontData());
+    return reinterpret_cast<jlong>(font->font->baseTypeface()->GetFontData());
 }
 
 // Critical Native
@@ -236,7 +236,7 @@
         }
         return env->NewStringUTF(path.c_str());
     } else {
-        const std::shared_ptr<minikin::MinikinFont>& minikinFont = font->font->typeface();
+        const std::shared_ptr<minikin::MinikinFont>& minikinFont = font->font->baseTypeface();
         const std::string& path = minikinFont->GetFontPath();
         if (path.empty()) {
             return nullptr;
@@ -275,7 +275,7 @@
         reader.skipString();  // fontPath
         return reader.read<int>();
     } else {
-        const std::shared_ptr<minikin::MinikinFont>& minikinFont = font->font->typeface();
+        const std::shared_ptr<minikin::MinikinFont>& minikinFont = font->font->baseTypeface();
         return minikinFont->GetFontIndex();
     }
 }
@@ -289,7 +289,7 @@
         reader.skip<int>();   // fontIndex
         return reader.readArray<minikin::FontVariation>().second;
     } else {
-        const std::shared_ptr<minikin::MinikinFont>& minikinFont = font->font->typeface();
+        const std::shared_ptr<minikin::MinikinFont>& minikinFont = font->font->baseTypeface();
         return minikinFont->GetAxes().size();
     }
 }
@@ -304,7 +304,7 @@
         reader.skip<int>();   // fontIndex
         var = reader.readArray<minikin::FontVariation>().first[index];
     } else {
-        const std::shared_ptr<minikin::MinikinFont>& minikinFont = font->font->typeface();
+        const std::shared_ptr<minikin::MinikinFont>& minikinFont = font->font->baseTypeface();
         var = minikinFont->GetAxes().at(index);
     }
     uint32_t floatBinary = *reinterpret_cast<const uint32_t*>(&var.value);
@@ -314,7 +314,7 @@
 // Critical Native
 static jint Font_getSourceId(CRITICAL_JNI_PARAMS_COMMA jlong fontPtr) {
     FontWrapper* font = reinterpret_cast<FontWrapper*>(fontPtr);
-    return font->font->typeface()->GetSourceId();
+    return font->font->baseTypeface()->GetSourceId();
 }
 
 static jlongArray Font_getAvailableFontSet(JNIEnv* env, jobject) {
diff --git a/libs/hwui/jni/fonts/FontFamily.cpp b/libs/hwui/jni/fonts/FontFamily.cpp
index 897c4d7..1e392b1 100644
--- a/libs/hwui/jni/fonts/FontFamily.cpp
+++ b/libs/hwui/jni/fonts/FontFamily.cpp
@@ -29,9 +29,11 @@
 
 namespace android {
 
+namespace {
 struct NativeFamilyBuilder {
     std::vector<std::shared_ptr<minikin::Font>> fonts;
 };
+}  // namespace
 
 static inline NativeFamilyBuilder* toBuilder(jlong ptr) {
     return reinterpret_cast<NativeFamilyBuilder*>(ptr);
@@ -58,7 +60,7 @@
 // Regular JNI
 static jlong FontFamily_Builder_build(JNIEnv* env, jobject clazz, jlong builderPtr,
                                       jstring langTags, jint variant, jboolean isCustomFallback,
-                                      jboolean isDefaultFallback) {
+                                      jboolean isDefaultFallback, jint variationFamilyType) {
     std::unique_ptr<NativeFamilyBuilder> builder(toBuilder(builderPtr));
     uint32_t localeId;
     if (langTags == nullptr) {
@@ -69,7 +71,8 @@
     }
     std::shared_ptr<minikin::FontFamily> family = minikin::FontFamily::create(
             localeId, static_cast<minikin::FamilyVariant>(variant), std::move(builder->fonts),
-            isCustomFallback, isDefaultFallback);
+            isCustomFallback, isDefaultFallback,
+            static_cast<minikin::VariationFamilyType>(variationFamilyType));
     if (family->getCoverage().length() == 0) {
         // No coverage means minikin rejected given font for some reasons.
         jniThrowException(env, "java/lang/IllegalArgumentException",
@@ -119,7 +122,7 @@
 static const JNINativeMethod gFontFamilyBuilderMethods[] = {
         {"nInitBuilder", "()J", (void*)FontFamily_Builder_initBuilder},
         {"nAddFont", "(JJ)V", (void*)FontFamily_Builder_addFont},
-        {"nBuild", "(JLjava/lang/String;IZZ)J", (void*)FontFamily_Builder_build},
+        {"nBuild", "(JLjava/lang/String;IZZI)J", (void*)FontFamily_Builder_build},
         {"nGetReleaseNativeFamily", "()J", (void*)FontFamily_Builder_GetReleaseFunc},
 };
 
diff --git a/libs/hwui/jni/text/TextShaper.cpp b/libs/hwui/jni/text/TextShaper.cpp
index 8e4dd53..8c377b9 100644
--- a/libs/hwui/jni/text/TextShaper.cpp
+++ b/libs/hwui/jni/text/TextShaper.cpp
@@ -62,7 +62,7 @@
         const minikin::Font* font = layout.getFont(i);
         if (seenFonts.find(font) != seenFonts.end()) continue;
         minikin::MinikinExtent extent = {};
-        font->typeface()->GetFontExtent(&extent, minikinPaint, layout.getFakery(i));
+        layout.typeface(i)->GetFontExtent(&extent, minikinPaint, layout.getFakery(i));
         overallAscent = std::min(overallAscent, extent.ascent);
         overallDescent = std::max(overallDescent, extent.descent);
     }
@@ -148,6 +148,30 @@
 }
 
 // CriticalNative
+static jboolean TextShaper_Result_getFakeBold(CRITICAL_JNI_PARAMS_COMMA jlong ptr, jint i) {
+    const LayoutWrapper* layout = reinterpret_cast<LayoutWrapper*>(ptr);
+    return layout->layout.getFakery(i).isFakeBold();
+}
+
+// CriticalNative
+static jboolean TextShaper_Result_getFakeItalic(CRITICAL_JNI_PARAMS_COMMA jlong ptr, jint i) {
+    const LayoutWrapper* layout = reinterpret_cast<LayoutWrapper*>(ptr);
+    return layout->layout.getFakery(i).isFakeItalic();
+}
+
+// CriticalNative
+static jfloat TextShaper_Result_getWeightOverride(CRITICAL_JNI_PARAMS_COMMA jlong ptr, jint i) {
+    const LayoutWrapper* layout = reinterpret_cast<LayoutWrapper*>(ptr);
+    return layout->layout.getFakery(i).wghtAdjustment();
+}
+
+// CriticalNative
+static jfloat TextShaper_Result_getItalicOverride(CRITICAL_JNI_PARAMS_COMMA jlong ptr, jint i) {
+    const LayoutWrapper* layout = reinterpret_cast<LayoutWrapper*>(ptr);
+    return layout->layout.getFakery(i).italAdjustment();
+}
+
+// CriticalNative
 static jlong TextShaper_Result_getFont(CRITICAL_JNI_PARAMS_COMMA jlong ptr, jint i) {
     const LayoutWrapper* layout = reinterpret_cast<LayoutWrapper*>(ptr);
     std::shared_ptr<minikin::Font> fontRef = layout->layout.getFontRef(i);
@@ -185,15 +209,19 @@
 };
 
 static const JNINativeMethod gResultMethods[] = {
-    { "nGetGlyphCount", "(J)I", (void*)TextShaper_Result_getGlyphCount },
-    { "nGetTotalAdvance", "(J)F", (void*)TextShaper_Result_getTotalAdvance },
-    { "nGetAscent", "(J)F", (void*)TextShaper_Result_getAscent },
-    { "nGetDescent", "(J)F", (void*)TextShaper_Result_getDescent },
-    { "nGetGlyphId", "(JI)I", (void*)TextShaper_Result_getGlyphId },
-    { "nGetX", "(JI)F", (void*)TextShaper_Result_getX },
-    { "nGetY", "(JI)F", (void*)TextShaper_Result_getY },
-    { "nGetFont", "(JI)J", (void*)TextShaper_Result_getFont },
-    { "nReleaseFunc", "()J", (void*)TextShaper_Result_nReleaseFunc },
+        {"nGetGlyphCount", "(J)I", (void*)TextShaper_Result_getGlyphCount},
+        {"nGetTotalAdvance", "(J)F", (void*)TextShaper_Result_getTotalAdvance},
+        {"nGetAscent", "(J)F", (void*)TextShaper_Result_getAscent},
+        {"nGetDescent", "(J)F", (void*)TextShaper_Result_getDescent},
+        {"nGetGlyphId", "(JI)I", (void*)TextShaper_Result_getGlyphId},
+        {"nGetX", "(JI)F", (void*)TextShaper_Result_getX},
+        {"nGetY", "(JI)F", (void*)TextShaper_Result_getY},
+        {"nGetFont", "(JI)J", (void*)TextShaper_Result_getFont},
+        {"nGetFakeBold", "(JI)Z", (void*)TextShaper_Result_getFakeBold},
+        {"nGetFakeItalic", "(JI)Z", (void*)TextShaper_Result_getFakeItalic},
+        {"nGetWeightOverride", "(JI)F", (void*)TextShaper_Result_getWeightOverride},
+        {"nGetItalicOverride", "(JI)F", (void*)TextShaper_Result_getItalicOverride},
+        {"nReleaseFunc", "()J", (void*)TextShaper_Result_nReleaseFunc},
 };
 
 int register_android_graphics_text_TextShaper(JNIEnv* env) {
diff --git a/libs/hwui/pipeline/skia/BackdropFilterDrawable.cpp b/libs/hwui/pipeline/skia/BackdropFilterDrawable.cpp
new file mode 100644
index 0000000..ffad699
--- /dev/null
+++ b/libs/hwui/pipeline/skia/BackdropFilterDrawable.cpp
@@ -0,0 +1,85 @@
+/*
+ * 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.
+ */
+
+#include "BackdropFilterDrawable.h"
+
+#include <SkImage.h>
+#include <SkSurface.h>
+
+#include "RenderNode.h"
+#include "RenderNodeDrawable.h"
+
+namespace android {
+namespace uirenderer {
+namespace skiapipeline {
+
+BackdropFilterDrawable::~BackdropFilterDrawable() {}
+
+bool BackdropFilterDrawable::prepareToDraw(SkCanvas* canvas, const RenderProperties& properties,
+                                           int backdropImageWidth, int backdropImageHeight) {
+    // the drawing bounds for blurred content.
+    mDstBounds.setWH(properties.getWidth(), properties.getHeight());
+
+    float alphaMultiplier = 1.0f;
+    RenderNodeDrawable::setViewProperties(properties, canvas, &alphaMultiplier, true);
+
+    // get proper subset for previous content.
+    canvas->getTotalMatrix().mapRect(&mImageSubset, mDstBounds);
+    SkRect imageSubset(mImageSubset);
+    // ensure the subset is inside bounds of previous content.
+    if (!mImageSubset.intersect(SkRect::MakeWH(backdropImageWidth, backdropImageHeight))) {
+        return false;
+    }
+
+    // correct the drawing bounds if subset was changed.
+    if (mImageSubset != imageSubset) {
+        SkMatrix inverse;
+        if (canvas->getTotalMatrix().invert(&inverse)) {
+            inverse.mapRect(&mDstBounds, mImageSubset);
+        }
+    }
+
+    // follow the alpha from the target RenderNode.
+    mPaint.setAlpha(properties.layerProperties().alpha() * alphaMultiplier);
+    return true;
+}
+
+void BackdropFilterDrawable::onDraw(SkCanvas* canvas) {
+    const RenderProperties& properties = mTargetRenderNode->properties();
+    auto* backdropFilter = properties.layerProperties().getBackdropImageFilter();
+    auto* surface = canvas->getSurface();
+    if (!backdropFilter || !surface) {
+        return;
+    }
+
+    auto backdropImage = surface->makeImageSnapshot();
+    // sync necessary properties from target RenderNode.
+    if (!prepareToDraw(canvas, properties, backdropImage->width(), backdropImage->height())) {
+        return;
+    }
+
+    auto imageSubset = mImageSubset.roundOut();
+    backdropImage =
+            backdropImage->makeWithFilter(canvas->recordingContext(), backdropFilter, imageSubset,
+                                          imageSubset, &mOutSubset, &mOutOffset);
+    canvas->drawImageRect(backdropImage, SkRect::Make(mOutSubset), mDstBounds,
+                          SkSamplingOptions(SkFilterMode::kLinear), &mPaint,
+                          SkCanvas::kStrict_SrcRectConstraint);
+}
+
+}  // namespace skiapipeline
+}  // namespace uirenderer
+}  // namespace android
diff --git a/libs/hwui/pipeline/skia/BackdropFilterDrawable.h b/libs/hwui/pipeline/skia/BackdropFilterDrawable.h
new file mode 100644
index 0000000..9e35837
--- /dev/null
+++ b/libs/hwui/pipeline/skia/BackdropFilterDrawable.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <SkCanvas.h>
+#include <SkDrawable.h>
+#include <SkPaint.h>
+
+namespace android {
+namespace uirenderer {
+
+class RenderNode;
+class RenderProperties;
+
+namespace skiapipeline {
+
+/**
+ * This drawable captures it's backdrop content and render it with a
+ * image filter.
+ */
+class BackdropFilterDrawable : public SkDrawable {
+public:
+    BackdropFilterDrawable(RenderNode* renderNode, SkCanvas* canvas)
+            : mTargetRenderNode(renderNode), mBounds(canvas->getLocalClipBounds()) {}
+
+    ~BackdropFilterDrawable();
+
+private:
+    RenderNode* mTargetRenderNode;
+    SkPaint mPaint;
+
+    SkRect mDstBounds;
+    SkRect mImageSubset;
+    SkIRect mOutSubset;
+    SkIPoint mOutOffset;
+
+    /**
+     * Check all necessary properties before actual drawing.
+     * Return true if ready to draw.
+     */
+    bool prepareToDraw(SkCanvas* canvas, const RenderProperties& properties, int backdropImageWidth,
+                       int backdropImageHeight);
+
+protected:
+    void onDraw(SkCanvas* canvas) override;
+
+    virtual SkRect onGetBounds() override { return mBounds; }
+    const SkRect mBounds;
+};
+
+}  // namespace skiapipeline
+}  // namespace uirenderer
+}  // namespace android
diff --git a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
index 8d5967b..dbd9ef3 100644
--- a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
+++ b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
@@ -21,9 +21,12 @@
 #include "GrBackendSurface.h"
 #include "RenderNode.h"
 #include "SkAndroidFrameworkUtils.h"
+#include "SkCanvas.h"
+#include "SkCanvasAndroid.h"
 #include "SkClipStack.h"
 #include "SkRect.h"
 #include "SkM44.h"
+#include <include/gpu/ganesh/SkSurfaceGanesh.h>
 #include "include/gpu/GpuTypes.h" // from Skia
 #include "utils/GLUtils.h"
 #include <effects/GainmapRenderer.h>
@@ -34,7 +37,7 @@
 namespace skiapipeline {
 
 static void setScissor(int viewportHeight, const SkIRect& clip) {
-    SkASSERT(!clip.isEmpty());
+    LOG_FATAL_IF(clip.isEmpty(), "empty scissor clip");
     // transform to Y-flipped GL space, and prevent negatives
     GLint y = viewportHeight - clip.fBottom;
     GLint height = (viewportHeight - clip.fTop) - y;
@@ -42,7 +45,7 @@
 }
 
 static void GetFboDetails(SkCanvas* canvas, GLuint* outFboID, SkISize* outFboSize) {
-    GrBackendRenderTarget renderTarget = canvas->topLayerBackendRenderTarget();
+    GrBackendRenderTarget renderTarget = skgpu::ganesh::TopLayerBackendRenderTarget(canvas);
     GrGLFramebufferInfo fboInfo;
     LOG_ALWAYS_FATAL_IF(!renderTarget.getGLFramebufferInfo(&fboInfo),
         "getGLFrameBufferInfo failed");
@@ -76,13 +79,13 @@
     }
 
     // flush will create a GrRenderTarget if not already present.
-    canvas->flush();
+    directContext->flushAndSubmit();
 
     GLuint fboID = 0;
     SkISize fboSize;
     GetFboDetails(canvas, &fboID, &fboSize);
 
-    SkIRect surfaceBounds = canvas->topLayerBounds();
+    SkIRect surfaceBounds = skgpu::ganesh::TopLayerBounds(canvas);
     SkIRect clipBounds = canvas->getDeviceClipBounds();
     SkM44 mat4(canvas->getLocalToDevice());
     SkRegion clipRegion;
@@ -95,11 +98,12 @@
         SkImageInfo surfaceInfo =
                 canvas->imageInfo().makeWH(clipBounds.width(), clipBounds.height());
         tmpSurface =
-                SkSurface::MakeRenderTarget(directContext, skgpu::Budgeted::kYes, surfaceInfo);
+                SkSurfaces::RenderTarget(directContext, skgpu::Budgeted::kYes, surfaceInfo);
         tmpSurface->getCanvas()->clear(SK_ColorTRANSPARENT);
 
         GrGLFramebufferInfo fboInfo;
-        if (!tmpSurface->getBackendRenderTarget(SkSurface::kFlushWrite_BackendHandleAccess)
+        if (!SkSurfaces::GetBackendRenderTarget(tmpSurface.get(),
+                                                SkSurfaces::BackendHandleAccess::kFlushWrite)
                      .getGLFramebufferInfo(&fboInfo)) {
             ALOGW("Unable to extract renderTarget info from offscreen canvas; aborting GLFunctor");
             return;
@@ -163,7 +167,7 @@
 
         // GL ops get inserted here if previous flush is missing, which could dirty the stencil
         bool stencilWritten = SkAndroidFrameworkUtils::clipWithStencil(tmpCanvas);
-        tmpCanvas->flush();  // need this flush for the single op that draws into the stencil
+        directContext->flushAndSubmit();  // need this flush for the single op that draws into the stencil
 
         // ensure that the framebuffer that the webview will render into is bound before after we
         // draw into the stencil
diff --git a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
index da4f66d..9d72c23 100644
--- a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
+++ b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
@@ -362,7 +362,7 @@
 }
 
 void RenderNodeDrawable::setViewProperties(const RenderProperties& properties, SkCanvas* canvas,
-                                           float* alphaMultiplier) {
+                                           float* alphaMultiplier, bool ignoreLayer) {
     if (properties.getLeft() != 0 || properties.getTop() != 0) {
         canvas->translate(properties.getLeft(), properties.getTop());
     }
@@ -378,7 +378,8 @@
             canvas->concat(*properties.getTransformMatrix());
         }
     }
-    if (Properties::getStretchEffectBehavior() == StretchEffectBehavior::UniformScale) {
+    if (Properties::getStretchEffectBehavior() == StretchEffectBehavior::UniformScale &&
+        !ignoreLayer) {
         const StretchEffect& stretch = properties.layerProperties().getStretchEffect();
         if (!stretch.isEmpty()) {
             canvas->concat(
@@ -388,10 +389,10 @@
     const bool isLayer = properties.effectiveLayerType() != LayerType::None;
     int clipFlags = properties.getClippingFlags();
     if (properties.getAlpha() < 1) {
-        if (isLayer) {
+        if (isLayer && !ignoreLayer) {
             clipFlags &= ~CLIP_TO_BOUNDS;  // bounds clipping done by layer
         }
-        if (CC_LIKELY(isLayer || !properties.getHasOverlappingRendering())) {
+        if (CC_LIKELY(isLayer || !properties.getHasOverlappingRendering()) || ignoreLayer) {
             *alphaMultiplier = properties.getAlpha();
         } else {
             // savelayer needed to create an offscreen buffer
diff --git a/libs/hwui/pipeline/skia/RenderNodeDrawable.h b/libs/hwui/pipeline/skia/RenderNodeDrawable.h
index c7582e7..818ac45 100644
--- a/libs/hwui/pipeline/skia/RenderNodeDrawable.h
+++ b/libs/hwui/pipeline/skia/RenderNodeDrawable.h
@@ -120,7 +120,7 @@
      * Applies the rendering properties of a view onto a SkCanvas.
      */
     static void setViewProperties(const RenderProperties& properties, SkCanvas* canvas,
-                                  float* alphaMultiplier);
+                                  float* alphaMultiplier, bool ignoreLayer = false);
 
     /**
      * Stores transform on the canvas at time of recording and is used for
@@ -149,6 +149,11 @@
      * display list that is searched for any render nodes with getProjectBackwards==true
      */
     SkiaDisplayList* mProjectedDisplayList = nullptr;
+
+    /**
+     * Allow BackdropFilterDrawable to apply same render properties onto SkCanvas.
+     */
+    friend class BackdropFilterDrawable;
 };
 
 }  // namespace skiapipeline
diff --git a/libs/hwui/pipeline/skia/ShaderCache.cpp b/libs/hwui/pipeline/skia/ShaderCache.cpp
index 00919dc..b870023 100644
--- a/libs/hwui/pipeline/skia/ShaderCache.cpp
+++ b/libs/hwui/pipeline/skia/ShaderCache.cpp
@@ -79,7 +79,7 @@
 
 void ShaderCache::initShaderDiskCache(const void* identity, ssize_t size) {
     ATRACE_NAME("initShaderDiskCache");
-    std::lock_guard<std::mutex> lock(mMutex);
+    std::lock_guard lock(mMutex);
 
     // Emulators can switch between different renders either as part of config
     // or snapshot migration. Also, program binaries may not work well on some
@@ -88,23 +88,21 @@
         mBlobCache.reset(new FileBlobCache(maxKeySize, maxValueSize, maxTotalSize, mFilename));
         validateCache(identity, size);
         mInitialized = true;
+        if (identity != nullptr && size > 0 && mIDHash.size()) {
+            set(&sIDKey, sizeof(sIDKey), mIDHash.data(), mIDHash.size());
+        }
     }
 }
 
 void ShaderCache::setFilename(const char* filename) {
-    std::lock_guard<std::mutex> lock(mMutex);
+    std::lock_guard lock(mMutex);
     mFilename = filename;
 }
 
-BlobCache* ShaderCache::getBlobCacheLocked() {
-    LOG_ALWAYS_FATAL_IF(!mInitialized, "ShaderCache has not been initialized");
-    return mBlobCache.get();
-}
-
 sk_sp<SkData> ShaderCache::load(const SkData& key) {
     ATRACE_NAME("ShaderCache::load");
     size_t keySize = key.size();
-    std::lock_guard<std::mutex> lock(mMutex);
+    std::lock_guard lock(mMutex);
     if (!mInitialized) {
         return nullptr;
     }
@@ -115,8 +113,7 @@
     if (!valueBuffer) {
         return nullptr;
     }
-    BlobCache* bc = getBlobCacheLocked();
-    size_t valueSize = bc->get(key.data(), keySize, valueBuffer, mObservedBlobValueSize);
+    size_t valueSize = mBlobCache->get(key.data(), keySize, valueBuffer, mObservedBlobValueSize);
     int maxTries = 3;
     while (valueSize > mObservedBlobValueSize && maxTries > 0) {
         mObservedBlobValueSize = std::min(valueSize, maxValueSize);
@@ -126,7 +123,7 @@
             return nullptr;
         }
         valueBuffer = newValueBuffer;
-        valueSize = bc->get(key.data(), keySize, valueBuffer, mObservedBlobValueSize);
+        valueSize = mBlobCache->get(key.data(), keySize, valueBuffer, mObservedBlobValueSize);
         maxTries--;
     }
     if (!valueSize) {
@@ -143,16 +140,17 @@
     return SkData::MakeFromMalloc(valueBuffer, valueSize);
 }
 
-namespace {
-// Helper for BlobCache::set to trace the result.
-void set(BlobCache* cache, const void* key, size_t keySize, const void* value, size_t valueSize) {
-    switch (cache->set(key, keySize, value, valueSize)) {
+void ShaderCache::set(const void* key, size_t keySize, const void* value, size_t valueSize) {
+    switch (mBlobCache->set(key, keySize, value, valueSize)) {
         case BlobCache::InsertResult::kInserted:
             // This is what we expect/hope. It means the cache is large enough.
             return;
         case BlobCache::InsertResult::kDidClean: {
             ATRACE_FORMAT("ShaderCache: evicted an entry to fit {key: %lu value %lu}!", keySize,
                           valueSize);
+            if (mIDHash.size()) {
+                set(&sIDKey, sizeof(sIDKey), mIDHash.data(), mIDHash.size());
+            }
             return;
         }
         case BlobCache::InsertResult::kNotEnoughSpace: {
@@ -172,22 +170,22 @@
         }
     }
 }
-}  // namespace
 
 void ShaderCache::saveToDiskLocked() {
     ATRACE_NAME("ShaderCache::saveToDiskLocked");
     if (mInitialized && mBlobCache) {
-        if (mIDHash.size()) {
-            auto key = sIDKey;
-            set(mBlobCache.get(), &key, sizeof(key), mIDHash.data(), mIDHash.size());
-        }
+        // The most straightforward way to make ownership shared
+        mMutex.unlock();
+        mMutex.lock_shared();
         mBlobCache->writeToFile();
+        mMutex.unlock_shared();
+        mMutex.lock();
     }
 }
 
 void ShaderCache::store(const SkData& key, const SkData& data, const SkString& /*description*/) {
     ATRACE_NAME("ShaderCache::store");
-    std::lock_guard<std::mutex> lock(mMutex);
+    std::lock_guard lock(mMutex);
     mNumShadersCachedInRam++;
     ATRACE_FORMAT("HWUI RAM cache: %d shaders", mNumShadersCachedInRam);
 
@@ -204,11 +202,10 @@
 
     const void* value = data.data();
 
-    BlobCache* bc = getBlobCacheLocked();
     if (mInStoreVkPipelineInProgress) {
         if (mOldPipelineCacheSize == -1) {
             // Record the initial pipeline cache size stored in the file.
-            mOldPipelineCacheSize = bc->get(key.data(), keySize, nullptr, 0);
+            mOldPipelineCacheSize = mBlobCache->get(key.data(), keySize, nullptr, 0);
         }
         if (mNewPipelineCacheSize != -1 && mNewPipelineCacheSize == valueSize) {
             // There has not been change in pipeline cache size. Stop trying to save.
@@ -223,13 +220,13 @@
         mNewPipelineCacheSize = -1;
         mTryToStorePipelineCache = true;
     }
-    set(bc, key.data(), keySize, value, valueSize);
+    set(key.data(), keySize, value, valueSize);
 
     if (!mSavePending && mDeferredSaveDelayMs > 0) {
         mSavePending = true;
         std::thread deferredSaveThread([this]() {
             usleep(mDeferredSaveDelayMs * 1000);  // milliseconds to microseconds
-            std::lock_guard<std::mutex> lock(mMutex);
+            std::lock_guard lock(mMutex);
             // Store file on disk if there a new shader or Vulkan pipeline cache size changed.
             if (mCacheDirty || mNewPipelineCacheSize != mOldPipelineCacheSize) {
                 saveToDiskLocked();
@@ -245,11 +242,12 @@
 
 void ShaderCache::onVkFrameFlushed(GrDirectContext* context) {
     {
-        std::lock_guard<std::mutex> lock(mMutex);
-
+        mMutex.lock_shared();
         if (!mInitialized || !mTryToStorePipelineCache) {
+            mMutex.unlock_shared();
             return;
         }
+        mMutex.unlock_shared();
     }
     mInStoreVkPipelineInProgress = true;
     context->storeVkPipelineCacheData();
diff --git a/libs/hwui/pipeline/skia/ShaderCache.h b/libs/hwui/pipeline/skia/ShaderCache.h
index f5506d6..6ccb212 100644
--- a/libs/hwui/pipeline/skia/ShaderCache.h
+++ b/libs/hwui/pipeline/skia/ShaderCache.h
@@ -19,11 +19,14 @@
 #include <GrContextOptions.h>
 #include <SkRefCnt.h>
 #include <cutils/compiler.h>
+#include <ftl/shared_mutex.h>
+#include <utils/Mutex.h>
+
 #include <memory>
-#include <mutex>
 #include <string>
 #include <vector>
 
+class GrDirectContext;
 class SkData;
 
 namespace android {
@@ -94,25 +97,23 @@
     void operator=(const ShaderCache&) = delete;
 
     /**
-     * "getBlobCacheLocked" returns the BlobCache object being used to store the
-     * key/value blob pairs.  If the BlobCache object has not yet been created,
-     * this will do so, loading the serialized cache contents from disk if
-     * possible.
-     */
-    BlobCache* getBlobCacheLocked();
-
-    /**
      * "validateCache" updates the cache to match the given identity.  If the
      * cache currently has the wrong identity, all entries in the cache are cleared.
      */
-    bool validateCache(const void* identity, ssize_t size);
+    bool validateCache(const void* identity, ssize_t size) REQUIRES(mMutex);
 
     /**
-     * "saveToDiskLocked" attemps to save the current contents of the cache to
+     * Helper for BlobCache::set to trace the result and ensure the identity hash
+     * does not get evicted.
+     */
+    void set(const void* key, size_t keySize, const void* value, size_t valueSize) REQUIRES(mMutex);
+
+    /**
+     * "saveToDiskLocked" attempts to save the current contents of the cache to
      * disk. If the identity hash exists, we will insert the identity hash into
      * the cache for next validation.
      */
-    void saveToDiskLocked();
+    void saveToDiskLocked() REQUIRES(mMutex);
 
     /**
      * "mInitialized" indicates whether the ShaderCache is in the initialized
@@ -122,16 +123,14 @@
      * the load and store methods will return without performing any cache
      * operations.
      */
-    bool mInitialized = false;
+    bool mInitialized GUARDED_BY(mMutex) = false;
 
     /**
-     * "mBlobCache" is the cache in which the key/value blob pairs are stored.  It
-     * is initially NULL, and will be initialized by getBlobCacheLocked the
-     * first time it's needed.
-     * The blob cache contains the Android build number. We treat version mismatches as an empty
-     * cache (logic implemented in BlobCache::unflatten).
+     * "mBlobCache" is the cache in which the key/value blob pairs are stored.
+     * The blob cache contains the Android build number. We treat version mismatches
+     * as an empty cache (logic implemented in BlobCache::unflatten).
      */
-    std::unique_ptr<FileBlobCache> mBlobCache;
+    std::unique_ptr<FileBlobCache> mBlobCache GUARDED_BY(mMutex);
 
     /**
      * "mFilename" is the name of the file for storing cache contents in between
@@ -140,7 +139,7 @@
      * empty string indicates that the cache should not be saved to or restored
      * from disk.
      */
-    std::string mFilename;
+    std::string mFilename GUARDED_BY(mMutex);
 
     /**
      * "mIDHash" is the current identity hash for the cache validation. It is
@@ -149,7 +148,7 @@
      * indicates that cache validation is not performed, and the hash should
      * not be stored on disk.
      */
-    std::vector<uint8_t> mIDHash;
+    std::vector<uint8_t> mIDHash GUARDED_BY(mMutex);
 
     /**
      * "mSavePending" indicates whether or not a deferred save operation is
@@ -159,7 +158,7 @@
      * contents to disk, unless mDeferredSaveDelayMs is 0 in which case saving
      * is disabled.
      */
-    bool mSavePending = false;
+    bool mSavePending GUARDED_BY(mMutex) = false;
 
     /**
      *  "mObservedBlobValueSize" is the maximum value size observed by the cache reading function.
@@ -174,16 +173,16 @@
     unsigned int mDeferredSaveDelayMs = 4 * 1000;
 
     /**
-     * "mMutex" is the mutex used to prevent concurrent access to the member
+     * "mMutex" is the shared mutex used to prevent concurrent access to the member
      * variables. It must be locked whenever the member variables are accessed.
      */
-    mutable std::mutex mMutex;
+    mutable ftl::SharedMutex mMutex;
 
     /**
      *  If set to "true", the next call to onVkFrameFlushed, will invoke
      * GrCanvas::storeVkPipelineCacheData. This does not guarantee that data will be stored on disk.
      */
-    bool mTryToStorePipelineCache = true;
+    bool mTryToStorePipelineCache GUARDED_BY(mMutex) = true;
 
     /**
      * This flag is used by "ShaderCache::store" to distinguish between shader data and
@@ -195,16 +194,16 @@
      *  "mNewPipelineCacheSize" has the size of the new Vulkan pipeline cache data. It is used
      *  to prevent unnecessary disk writes, if the pipeline cache size has not changed.
      */
-    size_t mNewPipelineCacheSize = -1;
+    size_t mNewPipelineCacheSize GUARDED_BY(mMutex) = -1;
     /**
      *  "mOldPipelineCacheSize" has the size of the Vulkan pipeline cache data stored on disk.
      */
-    size_t mOldPipelineCacheSize = -1;
+    size_t mOldPipelineCacheSize GUARDED_BY(mMutex) = -1;
 
     /**
      *  "mCacheDirty" is true when there is new shader cache data, which is not saved to disk.
      */
-    bool mCacheDirty = false;
+    bool mCacheDirty GUARDED_BY(mMutex) = false;
 
     /**
      * "sCache" is the singleton ShaderCache object.
@@ -221,7 +220,7 @@
      * interesting to keep track of how many shaders are stored in RAM. This
      * class provides a convenient entry point for that.
      */
-    int mNumShadersCachedInRam = 0;
+    int mNumShadersCachedInRam GUARDED_BY(mMutex) = 0;
 
     friend class ShaderCacheTestUtils;  // used for unit testing
 };
diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
index c4d3f5c..23b3074 100644
--- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
@@ -16,6 +16,7 @@
 
 #include "SkiaOpenGLPipeline.h"
 
+#include <include/gpu/ganesh/SkSurfaceGanesh.h>
 #include <GrBackendSurface.h>
 #include <SkBlendMode.h>
 #include <SkImageInfo.h>
@@ -68,12 +69,15 @@
         return MakeCurrentResult::AlreadyCurrent;
     }
 
-    // Make sure read/draw buffer state of default framebuffer is GL_BACK. Vendor implementations
+    EGLint majorVersion = 0;
+    eglQueryContext(eglGetCurrentDisplay(), eglGetCurrentContext(), EGL_CONTEXT_CLIENT_VERSION, &majorVersion);
+
+    // Make sure read/draw buffer state of default framebuffer is GL_BACK for ES 3.X. Vendor implementations
     // disagree on the draw/read buffer state if the default framebuffer transitions from a surface
     // to EGL_NO_SURFACE and vice-versa. There was a related discussion within Khronos on this topic.
     // See https://cvs.khronos.org/bugzilla/show_bug.cgi?id=13534.
     // The discussion was not resolved with a clear consensus
-    if (error == 0 && wasSurfaceless && mEglSurface != EGL_NO_SURFACE) {
+    if (error == 0 && (majorVersion > 2) && wasSurfaceless && mEglSurface != EGL_NO_SURFACE) {
         GLint curReadFB = 0;
         GLint curDrawFB = 0;
         glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &curReadFB);
@@ -147,9 +151,9 @@
         surface = getBufferSkSurface(bufferParams);
         preTransform = bufferParams.getTransform();
     } else {
-        surface = SkSurface::MakeFromBackendRenderTarget(mRenderThread.getGrContext(), backendRT,
-                                                         getSurfaceOrigin(), colorType,
-                                                         mSurfaceColorSpace, &props);
+        surface = SkSurfaces::WrapBackendRenderTarget(mRenderThread.getGrContext(), backendRT,
+                                                      getSurfaceOrigin(), colorType,
+                                                      mSurfaceColorSpace, &props);
         preTransform = SkMatrix::I();
     }
 
@@ -171,7 +175,7 @@
 
     {
         ATRACE_NAME("flush commands");
-        surface->flushAndSubmit();
+        skgpu::ganesh::FlushAndSubmit(surface);
     }
     layerUpdateQueue->clear();
 
@@ -242,8 +246,7 @@
 
     if (mEglSurface != EGL_NO_SURFACE) {
         const bool preserveBuffer = (swapBehavior != SwapBehavior::kSwap_discardBuffer);
-        const bool isPreserved = mEglManager.setPreserveBuffer(mEglSurface, preserveBuffer);
-        ALOGE_IF(preserveBuffer != isPreserved, "Unable to match the desired swap behavior.");
+        mEglManager.setPreserveBuffer(mEglSurface, preserveBuffer);
         return true;
     }
 
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
index b020e96..3d77877 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
@@ -16,14 +16,15 @@
 
 #include "SkiaPipeline.h"
 
+#include <include/android/SkSurfaceAndroid.h>
+#include <include/gpu/ganesh/SkSurfaceGanesh.h>
 #include <SkCanvas.h>
 #include <SkColor.h>
 #include <SkColorSpace.h>
 #include <SkData.h>
 #include <SkImage.h>
-#include <SkImageEncoder.h>
+#include <SkImageAndroid.h>
 #include <SkImageInfo.h>
-#include <SkImagePriv.h>
 #include <SkMatrix.h>
 #include <SkMultiPictureDocument.h>
 #include <SkOverdrawCanvas.h>
@@ -75,7 +76,7 @@
         return false;
     }
     for (SkImage* image : mutableImages) {
-        if (SkImage_pinAsTexture(image, mRenderThread.getGrContext())) {
+        if (skgpu::ganesh::PinAsTexture(mRenderThread.getGrContext(), image)) {
             mPinnedImages.emplace_back(sk_ref_sp(image));
         } else {
             return false;
@@ -86,7 +87,7 @@
 
 void SkiaPipeline::unpinImages() {
     for (auto& image : mPinnedImages) {
-        SkImage_unpinAsTexture(image.get(), mRenderThread.getGrContext());
+        skgpu::ganesh::UnpinTexture(mRenderThread.getGrContext(), image.get());
     }
     mPinnedImages.clear();
 }
@@ -187,9 +188,9 @@
                                  kPremul_SkAlphaType, getSurfaceColorSpace());
         SkSurfaceProps props(0, kUnknown_SkPixelGeometry);
         SkASSERT(mRenderThread.getGrContext() != nullptr);
-        node->setLayerSurface(SkSurface::MakeRenderTarget(mRenderThread.getGrContext(),
-                                                          skgpu::Budgeted::kYes, info, 0,
-                                                          this->getSurfaceOrigin(), &props));
+        node->setLayerSurface(SkSurfaces::RenderTarget(mRenderThread.getGrContext(),
+                                                       skgpu::Budgeted::kYes, info, 0,
+                                                       this->getSurfaceOrigin(), &props));
         if (node->getLayerSurface()) {
             // update the transform in window of the layer to reset its origin wrt light source
             // position
@@ -222,8 +223,8 @@
         ATRACE_FORMAT("Bitmap#prepareToDraw %dx%d", bitmap->width(), bitmap->height());
         auto image = bitmap->makeImage();
         if (image.get()) {
-            SkImage_pinAsTexture(image.get(), context);
-            SkImage_unpinAsTexture(image.get(), context);
+            skgpu::ganesh::PinAsTexture(context, image.get());
+            skgpu::ganesh::UnpinTexture(context, image.get());
             // A submit is necessary as there may not be a frame coming soon, so without a call
             // to submit these texture uploads can just sit in the queue building up until
             // we run out of RAM
@@ -621,7 +622,7 @@
     auto bufferColorSpace = bufferParams.getColorSpace();
     if (mBufferSurface == nullptr || mBufferColorSpace == nullptr ||
         !SkColorSpace::Equals(mBufferColorSpace.get(), bufferColorSpace.get())) {
-        mBufferSurface = SkSurface::MakeFromAHardwareBuffer(
+        mBufferSurface = SkSurfaces::WrapAndroidHardwareBuffer(
                 mRenderThread.getGrContext(), mHardwareBuffer, kTopLeft_GrSurfaceOrigin,
                 bufferColorSpace, nullptr, true);
         mBufferColorSpace = bufferColorSpace;
diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
index 3ca7eeb..58c14c1 100644
--- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
+++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
@@ -37,6 +37,7 @@
 #include "NinePatchUtils.h"
 #include "RenderNode.h"
 #include "pipeline/skia/AnimatedDrawables.h"
+#include "pipeline/skia/BackdropFilterDrawable.h"
 #ifdef __ANDROID__ // Layoutlib does not support GL, Vulcan etc.
 #include "pipeline/skia/GLFunctorDrawable.h"
 #include "pipeline/skia/VkFunctorDrawable.h"
@@ -168,6 +169,14 @@
         // Put Vulkan WebViews with non-rectangular clips in a HW layer
         renderNode->mutateStagingProperties().setClipMayBeComplex(mRecorder.isClipMayBeComplex());
     }
+
+    // draw backdrop filter drawable if needed.
+    if (renderNode->stagingProperties().layerProperties().getBackdropImageFilter()) {
+        auto* backdropFilterDrawable =
+                mDisplayList->allocateDrawable<BackdropFilterDrawable>(renderNode, asSkCanvas());
+        drawDrawable(backdropFilterDrawable);
+    }
+
     drawDrawable(&renderNodeDrawable);
 
     // use staging property, since recording on UI thread
diff --git a/libs/hwui/pipeline/skia/StretchMask.cpp b/libs/hwui/pipeline/skia/StretchMask.cpp
index cad3703..1676787 100644
--- a/libs/hwui/pipeline/skia/StretchMask.cpp
+++ b/libs/hwui/pipeline/skia/StretchMask.cpp
@@ -18,14 +18,13 @@
 #include "SkBlendMode.h"
 #include "SkCanvas.h"
 #include "SkSurface.h"
-#include "include/gpu/GpuTypes.h" // from Skia
 
 #include "TransformCanvas.h"
 #include "SkiaDisplayList.h"
 
 using android::uirenderer::StretchMask;
 
-void StretchMask::draw(GrRecordingContext* context,
+void StretchMask::draw(GrRecordingContext*,
                        const StretchEffect& stretch,
                        const SkRect& bounds,
                        skiapipeline::SkiaDisplayList* displayList,
@@ -35,16 +34,14 @@
     if (mMaskSurface == nullptr || mMaskSurface->width() != width ||
         mMaskSurface->height() != height) {
         // Create a new surface if we don't have one or our existing size does
-        // not match.
-        mMaskSurface = SkSurface::MakeRenderTarget(
-            context,
-            skgpu::Budgeted::kYes,
-            SkImageInfo::Make(
-                width,
-                height,
-                SkColorType::kAlpha_8_SkColorType,
-                SkAlphaType::kPremul_SkAlphaType)
-        );
+        // not match. SkCanvas::makeSurface returns a new surface that will
+        // be GPU-backed if canvas was also.
+        mMaskSurface = canvas->makeSurface(SkImageInfo::Make(
+            width,
+            height,
+            SkColorType::kAlpha_8_SkColorType,
+            SkAlphaType::kPremul_SkAlphaType
+        ));
         mIsDirty = true;
     }
 
@@ -53,7 +50,7 @@
         // Make sure to apply target transformation to the mask canvas
         // to ensure the replayed drawing commands generate the same result
         auto previousMatrix = displayList->mParentMatrix;
-        displayList->mParentMatrix = maskCanvas->getTotalMatrix();
+        displayList->mParentMatrix = maskCanvas->getLocalToDeviceAs3x3();
         maskCanvas->save();
         maskCanvas->drawColor(0, SkBlendMode::kClear);
         TransformCanvas transformCanvas(maskCanvas, SkBlendMode::kSrcOver);
diff --git a/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp b/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp
index adf3c06..475b110 100644
--- a/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp
+++ b/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp
@@ -35,6 +35,8 @@
 #include "effects/GainmapRenderer.h"
 
 #include <SkBlendMode.h>
+#include <SkImage.h>
+#include <SkImageAndroid.h>
 
 namespace android {
 namespace uirenderer {
@@ -183,9 +185,9 @@
     // drawing into the offscreen surface, so we need to reset it here.
     canvas->resetMatrix();
 
-    auto functorImage = SkImage::MakeFromAHardwareBuffer(mFrameBuffer.get(), kPremul_SkAlphaType,
-                                                         canvas->imageInfo().refColorSpace(),
-                                                         kBottomLeft_GrSurfaceOrigin);
+    auto functorImage = SkImages::DeferredFromAHardwareBuffer(
+        mFrameBuffer.get(), kPremul_SkAlphaType, canvas->imageInfo().refColorSpace(),
+        kBottomLeft_GrSurfaceOrigin);
     canvas->drawImage(functorImage, 0, 0, SkSamplingOptions(), &paint);
     canvas->restore();
 }
diff --git a/libs/hwui/renderthread/CacheManager.cpp b/libs/hwui/renderthread/CacheManager.cpp
index babce88..8f81dba 100644
--- a/libs/hwui/renderthread/CacheManager.cpp
+++ b/libs/hwui/renderthread/CacheManager.cpp
@@ -277,12 +277,13 @@
 
     const nsecs_t now = systemTime(CLOCK_MONOTONIC);
     // Rate limiting
-    if ((now - mLastDeferredCleanup) < 25_ms) {
+    if ((now - mLastDeferredCleanup) > 25_ms) {
         mLastDeferredCleanup = now;
         const nsecs_t frameCompleteNanos = mFrameCompletions[0];
         const nsecs_t frameDiffNanos = now - frameCompleteNanos;
         const nsecs_t cleanupMillis =
-                ns2ms(std::max(frameDiffNanos, mMemoryPolicy.minimumResourceRetention));
+                ns2ms(std::clamp(frameDiffNanos, mMemoryPolicy.minimumResourceRetention,
+                                 mMemoryPolicy.maximumResourceRetention));
         mGrContext->performDeferredCleanup(std::chrono::milliseconds(cleanupMillis),
                                            mMemoryPolicy.purgeScratchOnly);
     }
diff --git a/libs/hwui/renderthread/CacheManager.h b/libs/hwui/renderthread/CacheManager.h
index 5e43ac2..bcfa4f3 100644
--- a/libs/hwui/renderthread/CacheManager.h
+++ b/libs/hwui/renderthread/CacheManager.h
@@ -64,12 +64,13 @@
     void unregisterCanvasContext(CanvasContext* context);
     void onContextStopped(CanvasContext* context);
 
+    bool areAllContextsStopped();
+
 private:
     friend class RenderThread;
 
     explicit CacheManager(RenderThread& thread);
     void setupCacheLimits();
-    bool areAllContextsStopped();
     void checkUiHidden();
     void scheduleDestroyContext();
     void cancelDestroyContext();
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 16b35ff..2ef7802 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -125,6 +125,7 @@
         , mRenderPipeline(std::move(renderPipeline))
         , mHintSessionWrapper(uiThreadId, renderThreadId) {
     mRenderThread.cacheManager().registerCanvasContext(this);
+    mRenderThread.renderState().registerContextCallback(this);
     rootRenderNode->makeRoot();
     mRenderNodes.emplace_back(rootRenderNode);
     mProfiler.setDensity(DeviceInfo::getDensity());
@@ -137,6 +138,7 @@
     }
     mRenderNodes.clear();
     mRenderThread.cacheManager().unregisterCanvasContext(this);
+    mRenderThread.renderState().removeContextCallback(this);
 }
 
 void CanvasContext::addRenderNode(RenderNode* node, bool placeFront) {
@@ -405,8 +407,17 @@
 
     // If the previous frame was dropped we don't need to hold onto it, so
     // just keep using the previous frame's structure instead
-    if (!wasSkipped(mCurrentFrameInfo)) {
+    if (wasSkipped(mCurrentFrameInfo)) {
+        // Use the oldest skipped frame in case we skip more than a single frame
+        if (!mSkippedFrameInfo) {
+            mSkippedFrameInfo.emplace();
+            mSkippedFrameInfo->vsyncId =
+                mCurrentFrameInfo->get(FrameInfoIndex::FrameTimelineVsyncId);
+            mSkippedFrameInfo->startTime = mCurrentFrameInfo->get(FrameInfoIndex::FrameStartTime);
+        }
+    } else {
         mCurrentFrameInfo = mJankTracker.startFrame();
+        mSkippedFrameInfo.reset();
     }
 
     mCurrentFrameInfo->importUiThreadInfo(uiFrameInfo);
@@ -602,10 +613,18 @@
         if (vsyncId != UiFrameInfoBuilder::INVALID_VSYNC_ID) {
             const auto inputEventId =
                     static_cast<int32_t>(mCurrentFrameInfo->get(FrameInfoIndex::InputEventId));
-            native_window_set_frame_timeline_info(
-                    mNativeSurface->getNativeWindow(), frameCompleteNr, vsyncId, inputEventId,
-                    mCurrentFrameInfo->get(FrameInfoIndex::FrameStartTime),
-                    solelyTextureViewUpdates);
+            const ANativeWindowFrameTimelineInfo ftl = {
+                    .frameNumber = frameCompleteNr,
+                    .frameTimelineVsyncId = vsyncId,
+                    .inputEventId = inputEventId,
+                    .startTimeNanos = mCurrentFrameInfo->get(FrameInfoIndex::FrameStartTime),
+                    .useForRefreshRateSelection = solelyTextureViewUpdates,
+                    .skippedFrameVsyncId = mSkippedFrameInfo ? mSkippedFrameInfo->vsyncId
+                                                             : UiFrameInfoBuilder::INVALID_VSYNC_ID,
+                    .skippedFrameStartTimeNanos =
+                            mSkippedFrameInfo ? mSkippedFrameInfo->startTime : 0,
+            };
+            native_window_set_frame_timeline_info(mNativeSurface->getNativeWindow(), ftl);
         }
     }
 
@@ -946,6 +965,10 @@
     }
 }
 
+void CanvasContext::onContextDestroyed() {
+    destroyHardwareResources();
+}
+
 DeferredLayerUpdater* CanvasContext::createTextureLayer() {
     return mRenderPipeline->createTextureLayer();
 }
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index 5219b57..3f02674 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -43,6 +43,7 @@
 #include "Lighting.h"
 #include "ReliableSurface.h"
 #include "RenderNode.h"
+#include "renderstate/RenderState.h"
 #include "renderthread/RenderTask.h"
 #include "renderthread/RenderThread.h"
 #include "utils/RingBuffer.h"
@@ -64,7 +65,7 @@
 // This per-renderer class manages the bridge between the global EGL context
 // and the render surface.
 // TODO: Rename to Renderer or some other per-window, top-level manager
-class CanvasContext : public IFrameCallback {
+class CanvasContext : public IFrameCallback, public IGpuContextCallback {
 public:
     static CanvasContext* create(RenderThread& thread, bool translucent, RenderNode* rootRenderNode,
                                  IContextFactory* contextFactory, pid_t uiThreadId,
@@ -154,6 +155,7 @@
     void markLayerInUse(RenderNode* node);
 
     void destroyHardwareResources();
+    void onContextDestroyed() override;
 
     DeferredLayerUpdater* createTextureLayer();
 
@@ -366,6 +368,12 @@
 
     ColorMode mColorMode = ColorMode::Default;
     float mTargetSdrHdrRatio = 1.f;
+
+    struct SkippedFrameInfo {
+        int64_t vsyncId;
+        int64_t startTime;
+    };
+    std::optional<SkippedFrameInfo> mSkippedFrameInfo;
 };
 
 } /* namespace renderthread */
diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp
index 46698a6..f340945 100644
--- a/libs/hwui/renderthread/VulkanManager.cpp
+++ b/libs/hwui/renderthread/VulkanManager.cpp
@@ -24,6 +24,7 @@
 #include <GrTypes.h>
 #include <android/sync.h>
 #include <gui/TraceUtils.h>
+#include <include/gpu/ganesh/SkSurfaceGanesh.h>
 #include <ui/FatVector.h>
 #include <vk/GrVkExtensions.h>
 #include <vk/GrVkTypes.h>
@@ -518,7 +519,7 @@
                         // The following flush blocks the GPU immediately instead of waiting for
                         // other drawing ops. It seems dequeue_fence is not respected otherwise.
                         // TODO: remove the flush after finding why backendSemaphore is not working.
-                        bufferInfo->skSurface->flushAndSubmit();
+                        skgpu::ganesh::FlushAndSubmit(bufferInfo->skSurface);
                     }
                 }
             }
@@ -586,10 +587,10 @@
     } else {
         semaphore = VK_NULL_HANDLE;
     }
-    GrSemaphoresSubmitted submitted =
-            surface->flush(SkSurface::BackendSurfaceAccess::kPresent, flushInfo);
     GrDirectContext* context = GrAsDirectContext(surface->recordingContext());
     ALOGE_IF(!context, "Surface is not backed by gpu");
+    GrSemaphoresSubmitted submitted = context->flush(
+            surface, SkSurfaces::BackendSurfaceAccess::kPresent, flushInfo);
     context->submit();
     const nsecs_t submissionTime = systemTime();
     if (semaphore != VK_NULL_HANDLE) {
@@ -599,7 +600,8 @@
                 // retrieve VkImage used as render target
                 VkImage image = VK_NULL_HANDLE;
                 GrBackendRenderTarget backendRenderTarget =
-                        surface->getBackendRenderTarget(SkSurface::kFlushRead_BackendHandleAccess);
+                        SkSurfaces::GetBackendRenderTarget(
+                            surface, SkSurfaces::BackendHandleAccess::kFlushRead);
                 if (backendRenderTarget.isValid()) {
                     GrVkImageInfo info;
                     if (backendRenderTarget.getVkImageInfo(&info)) {
diff --git a/libs/hwui/renderthread/VulkanSurface.cpp b/libs/hwui/renderthread/VulkanSurface.cpp
index 3168cb0..b0ba619 100644
--- a/libs/hwui/renderthread/VulkanSurface.cpp
+++ b/libs/hwui/renderthread/VulkanSurface.cpp
@@ -16,6 +16,7 @@
 
 #include "VulkanSurface.h"
 
+#include <include/android/SkSurfaceAndroid.h>
 #include <GrDirectContext.h>
 #include <SkSurface.h>
 #include <algorithm>
@@ -470,12 +471,12 @@
             surfaceProps = SkSurfaceProps(SkSurfaceProps::kAlwaysDither_Flag | surfaceProps.flags(),
                                           surfaceProps.pixelGeometry());
         }
-        bufferInfo->skSurface = SkSurface::MakeFromAHardwareBuffer(
+        bufferInfo->skSurface = SkSurfaces::WrapAndroidHardwareBuffer(
                 mGrContext, ANativeWindowBuffer_getHardwareBuffer(bufferInfo->buffer.get()),
                 kTopLeft_GrSurfaceOrigin, mWindowInfo.colorspace, &surfaceProps,
                 /*from_window=*/true);
         if (bufferInfo->skSurface.get() == nullptr) {
-            ALOGE("SkSurface::MakeFromAHardwareBuffer failed");
+            ALOGE("SkSurfaces::WrapAndroidHardwareBuffer failed");
             mNativeWindow->cancelBuffer(mNativeWindow.get(), buffer,
                                         mNativeBuffers[idx].dequeue_fence.release());
             mNativeBuffers[idx].dequeued = false;
diff --git a/libs/hwui/tests/common/TestUtils.h b/libs/hwui/tests/common/TestUtils.h
index 81ecfe5..7ef82a7 100644
--- a/libs/hwui/tests/common/TestUtils.h
+++ b/libs/hwui/tests/common/TestUtils.h
@@ -307,13 +307,21 @@
         int destroyed = 0;
         int removeOverlays = 0;
         int glesDraw = 0;
+        int vkInitialize = 0;
+        int vkDraw = 0;
+        int vkPostDraw = 0;
     };
 
     static void expectOnRenderThread(const std::string_view& function = "unknown") {
         EXPECT_EQ(gettid(), TestUtils::getRenderThreadTid()) << "Called on wrong thread: " << function;
     }
 
-    static WebViewFunctorCallbacks createMockFunctor(RenderMode mode) {
+    static int createMockFunctor() {
+        const auto renderMode = WebViewFunctor_queryPlatformRenderMode();
+        return WebViewFunctor_create(nullptr, createMockFunctorCallbacks(renderMode), renderMode);
+    }
+
+    static WebViewFunctorCallbacks createMockFunctorCallbacks(RenderMode mode) {
         auto callbacks = WebViewFunctorCallbacks{
                 .onSync =
                         [](int functor, void* client_data, const WebViewSyncData& data) {
@@ -345,9 +353,22 @@
                     sMockFunctorCounts[functor].glesDraw++;
                 };
                 break;
-            default:
-                ADD_FAILURE();
-                return WebViewFunctorCallbacks{};
+            case RenderMode::Vulkan:
+                callbacks.vk.initialize = [](int functor, void* data,
+                                             const VkFunctorInitParams& params) {
+                    expectOnRenderThread("initialize");
+                    sMockFunctorCounts[functor].vkInitialize++;
+                };
+                callbacks.vk.draw = [](int functor, void* data, const VkFunctorDrawParams& params,
+                                       const WebViewOverlayData& overlayParams) {
+                    expectOnRenderThread("draw");
+                    sMockFunctorCounts[functor].vkDraw++;
+                };
+                callbacks.vk.postDraw = [](int functor, void* data) {
+                    expectOnRenderThread("postDraw");
+                    sMockFunctorCounts[functor].vkPostDraw++;
+                };
+                break;
         }
         return callbacks;
     }
diff --git a/libs/hwui/tests/macrobench/main.cpp b/libs/hwui/tests/macrobench/main.cpp
index f3f32eb..e227999 100644
--- a/libs/hwui/tests/macrobench/main.cpp
+++ b/libs/hwui/tests/macrobench/main.cpp
@@ -14,30 +14,32 @@
  * limitations under the License.
  */
 
-#include "tests/common/LeakChecker.h"
-#include "tests/common/TestScene.h"
-
-#include "Properties.h"
-#include "hwui/Typeface.h"
-#include "HardwareBitmapUploader.h"
-#include "renderthread/RenderProxy.h"
-
+#include <android-base/parsebool.h>
 #include <benchmark/benchmark.h>
+#include <errno.h>
+#include <fcntl.h>
 #include <fnmatch.h>
 #include <getopt.h>
 #include <pthread.h>
 #include <stdio.h>
+#include <sys/stat.h>
+#include <sys/types.h>
 #include <unistd.h>
+
+#include <regex>
 #include <string>
 #include <unordered_map>
 #include <vector>
 
-#include <errno.h>
-#include <fcntl.h>
-#include <sys/stat.h>
-#include <sys/types.h>
+#include "HardwareBitmapUploader.h"
+#include "Properties.h"
+#include "hwui/Typeface.h"
+#include "renderthread/RenderProxy.h"
+#include "tests/common/LeakChecker.h"
+#include "tests/common/TestScene.h"
 
 using namespace android;
+using namespace android::base;
 using namespace android::uirenderer;
 using namespace android::uirenderer::test;
 
@@ -69,6 +71,9 @@
   --onscreen           Render tests on device screen. By default tests
                        are offscreen rendered
   --benchmark_format   Set output format. Possible values are tabular, json, csv
+  --benchmark_list_tests Lists the tests that would run but does not run them
+  --benchmark_filter=<regex> Filters the test set to the given regex. If prefixed with `-` and test
+                       that doesn't match the given regex is run
   --renderer=TYPE      Sets the render pipeline to use. May be skiagl or skiavk
   --skip-leak-check    Skips the memory leak check
   --report-gpu-memory[=verbose]  Dumps the GPU memory usage after each test run
@@ -140,6 +145,9 @@
     if (!strcmp(format, "tabular")) {
         gBenchmarkReporter.reset(new benchmark::ConsoleReporter());
     } else if (!strcmp(format, "json")) {
+        // We cannot print the leak check if outputing to JSON as that will break
+        // JSON parsers since it's not JSON-formatted
+        gRunLeakCheck = false;
         gBenchmarkReporter.reset(new benchmark::JSONReporter());
     } else {
         fprintf(stderr, "Unknown format '%s'\n", format);
@@ -160,6 +168,24 @@
     return true;
 }
 
+static void addTestsThatMatchFilter(std::string spec) {
+    if (spec.empty() || spec == "all") {
+        spec = ".";  // Regexp that matches all benchmarks
+    }
+    bool isNegativeFilter = false;
+    if (spec[0] == '-') {
+        spec.replace(0, 1, "");
+        isNegativeFilter = true;
+    }
+    std::regex re(spec, std::regex_constants::extended);
+    for (auto& iter : TestScene::testMap()) {
+        if ((isNegativeFilter && !std::regex_search(iter.first, re)) ||
+            (!isNegativeFilter && std::regex_search(iter.first, re))) {
+            gRunTests.push_back(iter.second);
+        }
+    }
+}
+
 // 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 {
@@ -170,6 +196,8 @@
     ReportFrametime,
     CpuSet,
     BenchmarkFormat,
+    BenchmarkListTests,
+    BenchmarkFilter,
     Onscreen,
     Offscreen,
     Renderer,
@@ -179,14 +207,16 @@
 }
 
 static const struct option LONG_OPTIONS[] = {
-        {"frames", required_argument, nullptr, 'f'},
-        {"repeat", required_argument, nullptr, 'r'},
+        {"count", required_argument, nullptr, 'c'},
+        {"runs", required_argument, nullptr, 'r'},
         {"help", no_argument, nullptr, 'h'},
         {"list", no_argument, nullptr, LongOpts::List},
         {"wait-for-gpu", no_argument, nullptr, LongOpts::WaitForGpu},
         {"report-frametime", optional_argument, nullptr, LongOpts::ReportFrametime},
         {"cpuset", required_argument, nullptr, LongOpts::CpuSet},
         {"benchmark_format", required_argument, nullptr, LongOpts::BenchmarkFormat},
+        {"benchmark_list_tests", optional_argument, nullptr, LongOpts::BenchmarkListTests},
+        {"benchmark_filter", required_argument, nullptr, LongOpts::BenchmarkFilter},
         {"onscreen", no_argument, nullptr, LongOpts::Onscreen},
         {"offscreen", no_argument, nullptr, LongOpts::Offscreen},
         {"renderer", required_argument, nullptr, LongOpts::Renderer},
@@ -197,8 +227,12 @@
 static const char* SHORT_OPTIONS = "c:r:h";
 
 void parseOptions(int argc, char* argv[]) {
+    benchmark::BenchmarkReporter::Context::executable_name = (argc > 0) ? argv[0] : "unknown";
+
     int c;
     bool error = false;
+    bool listTestsOnly = false;
+    bool testsAreFiltered = false;
     opterr = 0;
 
     while (true) {
@@ -272,6 +306,21 @@
                 }
                 break;
 
+            case LongOpts::BenchmarkListTests:
+                if (!optarg || ParseBool(optarg) == ParseBoolResult::kTrue) {
+                    listTestsOnly = true;
+                }
+                break;
+
+            case LongOpts::BenchmarkFilter:
+                if (!optarg) {
+                    error = true;
+                    break;
+                }
+                addTestsThatMatchFilter(optarg);
+                testsAreFiltered = true;
+                break;
+
             case LongOpts::Renderer:
                 if (!optarg) {
                     error = true;
@@ -346,11 +395,18 @@
                 }
             }
         } while (optind < argc);
-    } else {
+    } else if (gRunTests.empty() && !testsAreFiltered) {
         for (auto& iter : TestScene::testMap()) {
             gRunTests.push_back(iter.second);
         }
     }
+
+    if (listTestsOnly) {
+        for (auto& iter : gRunTests) {
+            std::cout << iter.name << std::endl;
+        }
+        exit(EXIT_SUCCESS);
+    }
 }
 
 int main(int argc, char* argv[]) {
diff --git a/libs/hwui/tests/unit/AutoBackendTextureReleaseTests.cpp b/libs/hwui/tests/unit/AutoBackendTextureReleaseTests.cpp
index 138b3efd..b8b3f0a 100644
--- a/libs/hwui/tests/unit/AutoBackendTextureReleaseTests.cpp
+++ b/libs/hwui/tests/unit/AutoBackendTextureReleaseTests.cpp
@@ -46,7 +46,7 @@
 
     EXPECT_EQ(1, TestUtils::getUsageCount(textureRelease));
 
-    // SkImage::MakeFromTexture should fail if given null GrDirectContext.
+    // SkImages::BorrowTextureFrom should fail if given null GrDirectContext.
     textureRelease->makeImage(buffer, HAL_DATASPACE_UNKNOWN, /*context = */ nullptr);
 
     EXPECT_EQ(1, TestUtils::getUsageCount(textureRelease));
diff --git a/libs/hwui/tests/unit/CacheManagerTests.cpp b/libs/hwui/tests/unit/CacheManagerTests.cpp
index 2b90bda..cc7d34b 100644
--- a/libs/hwui/tests/unit/CacheManagerTests.cpp
+++ b/libs/hwui/tests/unit/CacheManagerTests.cpp
@@ -20,7 +20,8 @@
 #include "renderthread/EglManager.h"
 #include "tests/common/TestUtils.h"
 
-#include <SkImagePriv.h>
+#include <SkImageAndroid.h>
+#include <include/gpu/ganesh/SkSurfaceGanesh.h>
 #include "include/gpu/GpuTypes.h" // from Skia
 
 using namespace android;
@@ -46,8 +47,8 @@
 
     while (getCacheUsage(grContext) <= renderThread.cacheManager().getBackgroundCacheSize()) {
         SkImageInfo info = SkImageInfo::MakeA8(width, height);
-        sk_sp<SkSurface> surface = SkSurface::MakeRenderTarget(grContext, skgpu::Budgeted::kYes,
-                                                               info);
+        sk_sp<SkSurface> surface = SkSurfaces::RenderTarget(grContext, skgpu::Budgeted::kYes,
+                                                            info);
         surface->getCanvas()->drawColor(SK_AlphaTRANSPARENT);
 
         grContext->flushAndSubmit();
@@ -57,9 +58,8 @@
 
     // create an image and pin it so that we have something with a unique key in the cache
     sk_sp<Bitmap> bitmap = Bitmap::allocateHeapBitmap(SkImageInfo::MakeA8(width, height));
-    sk_sp<SkImage> image = bitmap->makeImage();
-    ASSERT_TRUE(SkImage_pinAsTexture(image.get(), grContext));
-
+    sk_sp<SkImage> image = bitmap->makeImage(); // calls skgpu::ganesh::PinAsTexture under the hood.
+    ASSERT_TRUE(skgpu::ganesh::PinAsTexture(grContext, image.get()));
     // attempt to trim all memory while we still hold strong refs
     renderThread.cacheManager().trimMemory(TrimLevel::COMPLETE);
     ASSERT_TRUE(0 == grContext->getResourceCachePurgeableBytes());
@@ -71,7 +71,7 @@
     }
 
     // unpin the image which should add a unique purgeable key to the cache
-    SkImage_unpinAsTexture(image.get(), grContext);
+    skgpu::ganesh::UnpinTexture(grContext, image.get());
 
     // verify that we have enough purgeable bytes
     const size_t purgeableBytes = grContext->getResourceCachePurgeableBytes();
diff --git a/libs/hwui/tests/unit/CanvasContextTests.cpp b/libs/hwui/tests/unit/CanvasContextTests.cpp
index 9e376e3..47a4105 100644
--- a/libs/hwui/tests/unit/CanvasContextTests.cpp
+++ b/libs/hwui/tests/unit/CanvasContextTests.cpp
@@ -19,6 +19,7 @@
 #include "AnimationContext.h"
 #include "IContextFactory.h"
 #include "renderthread/CanvasContext.h"
+#include "renderthread/VulkanManager.h"
 #include "tests/common/TestUtils.h"
 
 using namespace android;
@@ -42,3 +43,38 @@
 
     canvasContext->destroy();
 }
+
+RENDERTHREAD_TEST(CanvasContext, buildLayerDoesntLeak) {
+    auto node = TestUtils::createNode(0, 0, 200, 400, [](RenderProperties& props, Canvas& canvas) {
+        canvas.drawColor(0xFFFF0000, SkBlendMode::kSrc);
+    });
+    ASSERT_TRUE(node->isValid());
+    EXPECT_EQ(LayerType::None, node->stagingProperties().effectiveLayerType());
+    node->mutateStagingProperties().mutateLayerProperties().setType(LayerType::RenderLayer);
+
+    auto& cacheManager = renderThread.cacheManager();
+    EXPECT_TRUE(cacheManager.areAllContextsStopped());
+    ContextFactory contextFactory;
+    std::unique_ptr<CanvasContext> canvasContext(
+            CanvasContext::create(renderThread, false, node.get(), &contextFactory, 0, 0));
+    canvasContext->buildLayer(node.get());
+    EXPECT_TRUE(node->hasLayer());
+    if (Properties::getRenderPipelineType() == RenderPipelineType::SkiaVulkan) {
+        auto instance = VulkanManager::peekInstance();
+        if (instance) {
+            EXPECT_TRUE(instance->hasVkContext());
+        } else {
+            ADD_FAILURE() << "VulkanManager wasn't initialized to buildLayer?";
+        }
+    }
+    renderThread.destroyRenderingContext();
+    EXPECT_FALSE(node->hasLayer()) << "Node still has a layer after rendering context destroyed";
+
+    if (Properties::getRenderPipelineType() == RenderPipelineType::SkiaVulkan) {
+        auto instance = VulkanManager::peekInstance();
+        if (instance) {
+            ADD_FAILURE() << "VulkanManager still exists";
+            EXPECT_FALSE(instance->hasVkContext());
+        }
+    }
+}
diff --git a/libs/hwui/tests/unit/DeferredLayerUpdaterTests.cpp b/libs/hwui/tests/unit/DeferredLayerUpdaterTests.cpp
index 0c389bfe8..cfa18ae 100644
--- a/libs/hwui/tests/unit/DeferredLayerUpdaterTests.cpp
+++ b/libs/hwui/tests/unit/DeferredLayerUpdaterTests.cpp
@@ -40,7 +40,7 @@
     // push the deferred updates to the layer
     SkBitmap bitmap;
     bitmap.allocN32Pixels(16, 16);
-    sk_sp<SkImage> layerImage = SkImage::MakeFromBitmap(bitmap);
+    sk_sp<SkImage> layerImage = SkImages::RasterFromBitmap(bitmap);
     layerUpdater->updateLayer(true, layerImage, 0, SkRect::MakeEmpty());
 
     // the backing layer should now have all the properties applied.
diff --git a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
index 596bd37..1e055c2 100644
--- a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
+++ b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
@@ -14,20 +14,22 @@
  * limitations under the License.
  */
 
-#include <VectorDrawable.h>
-#include <gtest/gtest.h>
-
 #include <SkBlendMode.h>
 #include <SkClipStack.h>
 #include <SkSurface_Base.h>
+#include <VectorDrawable.h>
+#include <gtest/gtest.h>
+#include <include/effects/SkImageFilters.h>
 #include <string.h>
+
 #include "AnimationContext.h"
 #include "DamageAccumulator.h"
 #include "FatalTestCanvas.h"
 #include "IContextFactory.h"
-#include "hwui/Paint.h"
 #include "RecordingCanvas.h"
 #include "SkiaCanvas.h"
+#include "hwui/Paint.h"
+#include "pipeline/skia/BackdropFilterDrawable.h"
 #include "pipeline/skia/SkiaDisplayList.h"
 #include "pipeline/skia/SkiaOpenGLPipeline.h"
 #include "pipeline/skia/SkiaPipeline.h"
@@ -141,7 +143,7 @@
 }
 
 TEST(RenderNodeDrawable, composeOnLayer) {
-    auto surface = SkSurface::MakeRasterN32Premul(1, 1);
+    auto surface = SkSurfaces::Raster(SkImageInfo::MakeN32Premul(1, 1));
     SkCanvas& canvas = *surface->getCanvas();
     canvas.drawColor(SK_ColorBLUE, SkBlendMode::kSrcOver);
     ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE);
@@ -152,7 +154,7 @@
             });
 
     // attach a layer to the render node
-    auto surfaceLayer = SkSurface::MakeRasterN32Premul(1, 1);
+    auto surfaceLayer = SkSurfaces::Raster(SkImageInfo::MakeN32Premul(1, 1));
     auto canvas2 = surfaceLayer->getCanvas();
     canvas2->drawColor(SK_ColorWHITE, SkBlendMode::kSrcOver);
     rootNode->setLayerSurface(surfaceLayer);
@@ -187,7 +189,7 @@
 }
 
 TEST(RenderNodeDrawable, saveLayerClipAndMatrixRestore) {
-    auto surface = SkSurface::MakeRasterN32Premul(400, 800);
+    auto surface = SkSurfaces::Raster(SkImageInfo::MakeN32Premul(400, 800));
     SkCanvas& canvas = *surface->getCanvas();
     canvas.drawColor(SK_ColorWHITE, SkBlendMode::kSrcOver);
     ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorWHITE);
@@ -1074,7 +1076,8 @@
             });
 
     layerNode->animatorProperties().mutateLayerProperties().setType(LayerType::RenderLayer);
-    layerNode->setLayerSurface(SkSurface::MakeRasterN32Premul(LAYER_WIDTH, LAYER_HEIGHT));
+    layerNode->setLayerSurface(SkSurfaces::Raster(SkImageInfo::MakeN32Premul(LAYER_WIDTH, 
+                                                                             LAYER_HEIGHT)));
 
     FrameTestCanvas canvas;
     RenderNodeDrawable drawable(layerNode.get(), &canvas, true);
@@ -1210,3 +1213,77 @@
     canvas.drawDrawable(&drawable);
     EXPECT_EQ(2, canvas.mDrawCounter);
 }
+
+// Verify drawing logics for BackdropFilterDrawable
+RENDERTHREAD_TEST(BackdropFilterDrawable, drawing) {
+    static const int CANVAS_WIDTH = 100;
+    static const int CANVAS_HEIGHT = 200;
+    class SimpleTestCanvas : public TestCanvasBase {
+    public:
+        SkRect mDstBounds;
+        SimpleTestCanvas() : TestCanvasBase(CANVAS_WIDTH, CANVAS_HEIGHT) {}
+        void onDrawRect(const SkRect& rect, const SkPaint& paint) override {
+            // did nothing.
+        }
+
+        // called when BackdropFilterDrawable is drawn.
+        void onDrawImageRect2(const SkImage*, const SkRect& src, const SkRect& dst,
+                              const SkSamplingOptions&, const SkPaint*,
+                              SrcRectConstraint) override {
+            mDrawCounter++;
+            mDstBounds = dst;
+        }
+    };
+    class SimpleLayer : public SkSurface_Base {
+    public:
+        SimpleLayer()
+                : SkSurface_Base(SkImageInfo::MakeN32Premul(CANVAS_WIDTH, CANVAS_HEIGHT), nullptr) {
+        }
+        virtual sk_sp<SkImage> onNewImageSnapshot(const SkIRect* bounds) override {
+            SkBitmap bitmap;
+            bitmap.allocN32Pixels(CANVAS_WIDTH, CANVAS_HEIGHT);
+            bitmap.setImmutable();
+            return bitmap.asImage();
+        }
+        SkCanvas* onNewCanvas() override { return new SimpleTestCanvas(); }
+        sk_sp<SkSurface> onNewSurface(const SkImageInfo&) override { return nullptr; }
+        bool onCopyOnWrite(ContentChangeMode) override { return true; }
+        void onWritePixels(const SkPixmap&, int x, int y) {}
+    };
+
+    auto node = TestUtils::createSkiaNode(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT,
+                                          [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+                                              canvas.drawRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT,
+                                                              Paint());
+                                          });
+
+    sk_sp<SkSurface> surface(new SimpleLayer());
+    auto* canvas = reinterpret_cast<SimpleTestCanvas*>(surface->getCanvas());
+    RenderNodeDrawable drawable(node.get(), canvas, true);
+    BackdropFilterDrawable backdropDrawable(node.get(), canvas);
+    canvas->drawDrawable(&drawable);
+    canvas->drawDrawable(&backdropDrawable);
+    // no backdrop filter, skip drawing.
+    EXPECT_EQ(0, canvas->mDrawCounter);
+
+    sk_sp<SkImageFilter> filter(SkImageFilters::Blur(3, 3, nullptr));
+    node->animatorProperties().mutateLayerProperties().setBackdropImageFilter(filter.get());
+    canvas->drawDrawable(&drawable);
+    canvas->drawDrawable(&backdropDrawable);
+    // backdrop filter is set, ok to draw.
+    EXPECT_EQ(1, canvas->mDrawCounter);
+    EXPECT_EQ(SkRect::MakeLTRB(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT), canvas->mDstBounds);
+
+    canvas->translate(30, 30);
+    canvas->drawDrawable(&drawable);
+    canvas->drawDrawable(&backdropDrawable);
+    // the drawable is still visible, ok to draw.
+    EXPECT_EQ(2, canvas->mDrawCounter);
+    EXPECT_EQ(SkRect::MakeLTRB(0, 0, CANVAS_WIDTH - 30, CANVAS_HEIGHT - 30), canvas->mDstBounds);
+
+    canvas->translate(CANVAS_WIDTH, CANVAS_HEIGHT);
+    canvas->drawDrawable(&drawable);
+    canvas->drawDrawable(&backdropDrawable);
+    // the drawable is invisible, skip drawing.
+    EXPECT_EQ(2, canvas->mDrawCounter);
+}
diff --git a/libs/hwui/tests/unit/RenderNodeTests.cpp b/libs/hwui/tests/unit/RenderNodeTests.cpp
index 80796f4..8273524 100644
--- a/libs/hwui/tests/unit/RenderNodeTests.cpp
+++ b/libs/hwui/tests/unit/RenderNodeTests.cpp
@@ -231,8 +231,7 @@
 }
 
 TEST(RenderNode, releasedCallback) {
-    int functor = WebViewFunctor_create(
-            nullptr, TestUtils::createMockFunctor(RenderMode::OpenGL_ES), RenderMode::OpenGL_ES);
+    int functor = TestUtils::createMockFunctor();
 
     auto node = TestUtils::createNode(0, 0, 200, 400, [&](RenderProperties& props, Canvas& canvas) {
         canvas.drawWebViewFunctor(functor);
diff --git a/libs/hwui/tests/unit/ShaderCacheTests.cpp b/libs/hwui/tests/unit/ShaderCacheTests.cpp
index 7bcd45c..9aa2e1d 100644
--- a/libs/hwui/tests/unit/ShaderCacheTests.cpp
+++ b/libs/hwui/tests/unit/ShaderCacheTests.cpp
@@ -49,7 +49,7 @@
      */
     static void reinitializeAllFields(ShaderCache& cache) {
         ShaderCache newCache = ShaderCache();
-        std::lock_guard<std::mutex> lock(cache.mMutex);
+        std::lock_guard lock(cache.mMutex), newLock(newCache.mMutex);
         // By order of declaration
         cache.mInitialized = newCache.mInitialized;
         cache.mBlobCache.reset(nullptr);
@@ -72,7 +72,7 @@
      * manually, as seen in the "terminate" testing helper function.
      */
     static void setSaveDelayMs(ShaderCache& cache, unsigned int saveDelayMs) {
-        std::lock_guard<std::mutex> lock(cache.mMutex);
+        std::lock_guard lock(cache.mMutex);
         cache.mDeferredSaveDelayMs = saveDelayMs;
     }
 
@@ -81,7 +81,7 @@
      * Next call to "initShaderDiskCache" will load again the in-memory cache from disk.
      */
     static void terminate(ShaderCache& cache, bool saveContent) {
-        std::lock_guard<std::mutex> lock(cache.mMutex);
+        std::lock_guard lock(cache.mMutex);
         if (saveContent) {
             cache.saveToDiskLocked();
         }
@@ -93,6 +93,7 @@
      */
     template <typename T>
     static bool validateCache(ShaderCache& cache, std::vector<T> hash) {
+        std::lock_guard lock(cache.mMutex);
         return cache.validateCache(hash.data(), hash.size() * sizeof(T));
     }
 
@@ -108,7 +109,7 @@
      */
     static void waitForPendingSave(ShaderCache& cache, const int timeoutMs = 50) {
         {
-            std::lock_guard<std::mutex> lock(cache.mMutex);
+            std::lock_guard lock(cache.mMutex);
             ASSERT_TRUE(cache.mSavePending);
         }
         bool saving = true;
@@ -123,7 +124,7 @@
             usleep(delayMicroseconds);
             elapsedMilliseconds += (float)delayMicroseconds / 1000;
 
-            std::lock_guard<std::mutex> lock(cache.mMutex);
+            std::lock_guard lock(cache.mMutex);
             saving = cache.mSavePending;
         }
     }
diff --git a/libs/hwui/tests/unit/SkiaCanvasTests.cpp b/libs/hwui/tests/unit/SkiaCanvasTests.cpp
index 87c5216..e53fcaa 100644
--- a/libs/hwui/tests/unit/SkiaCanvasTests.cpp
+++ b/libs/hwui/tests/unit/SkiaCanvasTests.cpp
@@ -36,7 +36,7 @@
 using namespace android::uirenderer;
 
 TEST(SkiaCanvas, drawShadowLayer) {
-    auto surface = SkSurface::MakeRasterN32Premul(10, 10);
+    auto surface = SkSurfaces::Raster(SkImageInfo::MakeN32Premul(10, 10));
     SkiaCanvas canvas(surface->getCanvas());
 
     // clear to white
diff --git a/libs/hwui/tests/unit/SkiaDisplayListTests.cpp b/libs/hwui/tests/unit/SkiaDisplayListTests.cpp
index f825d7c..1a1ce1e 100644
--- a/libs/hwui/tests/unit/SkiaDisplayListTests.cpp
+++ b/libs/hwui/tests/unit/SkiaDisplayListTests.cpp
@@ -48,8 +48,7 @@
     SkCanvas dummyCanvas;
     RenderNodeDrawable drawable(nullptr, &dummyCanvas);
     skiaDL->mChildNodes.emplace_back(nullptr, &dummyCanvas);
-    int functor1 = WebViewFunctor_create(
-            nullptr, TestUtils::createMockFunctor(RenderMode::OpenGL_ES), RenderMode::OpenGL_ES);
+    int functor1 = TestUtils::createMockFunctor();
     GLFunctorDrawable functorDrawable{functor1, &dummyCanvas};
     WebViewFunctor_release(functor1);
     skiaDL->mChildFunctors.push_back(&functorDrawable);
@@ -101,8 +100,7 @@
 
     SkCanvas dummyCanvas;
 
-    int functor1 = WebViewFunctor_create(
-            nullptr, TestUtils::createMockFunctor(RenderMode::OpenGL_ES), RenderMode::OpenGL_ES);
+    int functor1 = TestUtils::createMockFunctor();
     auto& counts = TestUtils::countsForFunctor(functor1);
     skiaDL.mChildFunctors.push_back(
             skiaDL.allocateDrawable<GLFunctorDrawable>(functor1, &dummyCanvas));
diff --git a/libs/hwui/tests/unit/SkiaPipelineTests.cpp b/libs/hwui/tests/unit/SkiaPipelineTests.cpp
index 4d0595e..6f180e7 100644
--- a/libs/hwui/tests/unit/SkiaPipelineTests.cpp
+++ b/libs/hwui/tests/unit/SkiaPipelineTests.cpp
@@ -54,7 +54,7 @@
     bool opaque = true;
     android::uirenderer::Rect contentDrawBounds(0, 0, 1, 1);
     auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread);
-    auto surface = SkSurface::MakeRasterN32Premul(1, 1);
+    auto surface = SkSurfaces::Raster(SkImageInfo::MakeN32Premul(1, 1));
     surface->getCanvas()->drawColor(SK_ColorBLUE, SkBlendMode::kSrcOver);
     ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE);
     pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface,
@@ -76,7 +76,7 @@
     renderNodes.push_back(halfGreenNode);
     android::uirenderer::Rect contentDrawBounds(0, 0, 2, 2);
     auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread);
-    auto surface = SkSurface::MakeRasterN32Premul(2, 2);
+    auto surface = SkSurfaces::Raster(SkImageInfo::MakeN32Premul(2, 2));
     surface->getCanvas()->drawColor(SK_ColorBLUE, SkBlendMode::kSrcOver);
     ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE);
     pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, true, contentDrawBounds, surface,
@@ -100,7 +100,7 @@
     renderNodes.push_back(redNode);
     android::uirenderer::Rect contentDrawBounds(0, 0, 2, 2);
     auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread);
-    auto surface = SkSurface::MakeRasterN32Premul(2, 2);
+    auto surface = SkSurfaces::Raster(SkImageInfo::MakeN32Premul(2, 2));
     surface->getCanvas()->drawColor(SK_ColorBLUE, SkBlendMode::kSrcOver);
     ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE);
     pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, true, contentDrawBounds, surface,
@@ -116,7 +116,7 @@
             0, 0, 1, 1, [](RenderProperties& props, SkiaRecordingCanvas& redCanvas) {
                 redCanvas.drawColor(SK_ColorRED, SkBlendMode::kSrcOver);
             });
-    auto surfaceLayer1 = SkSurface::MakeRasterN32Premul(1, 1);
+    auto surfaceLayer1 = SkSurfaces::Raster(SkImageInfo::MakeN32Premul(1, 1));
     surfaceLayer1->getCanvas()->drawColor(SK_ColorWHITE, SkBlendMode::kSrcOver);
     ASSERT_EQ(TestUtils::getColor(surfaceLayer1, 0, 0), SK_ColorWHITE);
     redNode->setLayerSurface(surfaceLayer1);
@@ -127,7 +127,7 @@
             0, 0, 2, 2, [](RenderProperties& props, SkiaRecordingCanvas& blueCanvas) {
                 blueCanvas.drawColor(SK_ColorBLUE, SkBlendMode::kSrcOver);
             });
-    auto surfaceLayer2 = SkSurface::MakeRasterN32Premul(2, 2);
+    auto surfaceLayer2 = SkSurfaces::Raster(SkImageInfo::MakeN32Premul(2, 2));
     surfaceLayer2->getCanvas()->drawColor(SK_ColorWHITE, SkBlendMode::kSrcOver);
     ASSERT_EQ(TestUtils::getColor(surfaceLayer2, 0, 0), SK_ColorWHITE);
     blueNode->setLayerSurface(surfaceLayer2);
@@ -169,7 +169,7 @@
     // empty contentDrawBounds is avoiding backdrop/content logic, which would lead to less overdraw
     android::uirenderer::Rect contentDrawBounds(0, 0, 0, 0);
     auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread);
-    auto surface = SkSurface::MakeRasterN32Premul(1, 1);
+    auto surface = SkSurfaces::Raster(SkImageInfo::MakeN32Premul(1, 1));
 
     // Initialize the canvas to blue.
     surface->getCanvas()->drawColor(SK_ColorBLUE, SkBlendMode::kSrcOver);
@@ -428,7 +428,7 @@
     renderNodes.push_back(redNode);
     bool opaque = true;
     android::uirenderer::Rect contentDrawBounds(0, 0, 1, 1);
-    auto surface = SkSurface::MakeRasterN32Premul(1, 1);
+    auto surface = SkSurfaces::Raster(SkImageInfo::MakeN32Premul(1, 1));
     pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface,
                           SkMatrix::I());
 
diff --git a/libs/hwui/tests/unit/WebViewFunctorManagerTests.cpp b/libs/hwui/tests/unit/WebViewFunctorManagerTests.cpp
index e1fb8b7..5e8f13d 100644
--- a/libs/hwui/tests/unit/WebViewFunctorManagerTests.cpp
+++ b/libs/hwui/tests/unit/WebViewFunctorManagerTests.cpp
@@ -26,9 +26,15 @@
 using namespace android;
 using namespace android::uirenderer;
 
+#define ASSUME_GLES()                                                      \
+    if (WebViewFunctor_queryPlatformRenderMode() != RenderMode::OpenGL_ES) \
+    GTEST_SKIP() << "Not in GLES, skipping test"
+
 TEST(WebViewFunctor, createDestroyGLES) {
+    ASSUME_GLES();
     int functor = WebViewFunctor_create(
-            nullptr, TestUtils::createMockFunctor(RenderMode::OpenGL_ES), RenderMode::OpenGL_ES);
+            nullptr, TestUtils::createMockFunctorCallbacks(RenderMode::OpenGL_ES),
+            RenderMode::OpenGL_ES);
     ASSERT_NE(-1, functor);
     WebViewFunctor_release(functor);
     TestUtils::runOnRenderThreadUnmanaged([](renderthread::RenderThread&) {
@@ -41,8 +47,10 @@
 }
 
 TEST(WebViewFunctor, createSyncHandleGLES) {
+    ASSUME_GLES();
     int functor = WebViewFunctor_create(
-            nullptr, TestUtils::createMockFunctor(RenderMode::OpenGL_ES), RenderMode::OpenGL_ES);
+            nullptr, TestUtils::createMockFunctorCallbacks(RenderMode::OpenGL_ES),
+            RenderMode::OpenGL_ES);
     ASSERT_NE(-1, functor);
     auto handle = WebViewFunctorManager::instance().handleFor(functor);
     ASSERT_TRUE(handle);
@@ -82,8 +90,10 @@
 }
 
 TEST(WebViewFunctor, createSyncDrawGLES) {
+    ASSUME_GLES();
     int functor = WebViewFunctor_create(
-            nullptr, TestUtils::createMockFunctor(RenderMode::OpenGL_ES), RenderMode::OpenGL_ES);
+            nullptr, TestUtils::createMockFunctorCallbacks(RenderMode::OpenGL_ES),
+            RenderMode::OpenGL_ES);
     ASSERT_NE(-1, functor);
     auto handle = WebViewFunctorManager::instance().handleFor(functor);
     ASSERT_TRUE(handle);
@@ -108,9 +118,11 @@
     EXPECT_EQ(1, counts.destroyed);
 }
 
-TEST(WebViewFunctor, contextDestroyed) {
+TEST(WebViewFunctor, contextDestroyedGLES) {
+    ASSUME_GLES();
     int functor = WebViewFunctor_create(
-            nullptr, TestUtils::createMockFunctor(RenderMode::OpenGL_ES), RenderMode::OpenGL_ES);
+            nullptr, TestUtils::createMockFunctorCallbacks(RenderMode::OpenGL_ES),
+            RenderMode::OpenGL_ES);
     ASSERT_NE(-1, functor);
     auto handle = WebViewFunctorManager::instance().handleFor(functor);
     ASSERT_TRUE(handle);
diff --git a/libs/incident/src/IncidentReportArgs.cpp b/libs/incident/src/IncidentReportArgs.cpp
index db495cf..d344a981 100644
--- a/libs/incident/src/IncidentReportArgs.cpp
+++ b/libs/incident/src/IncidentReportArgs.cpp
@@ -133,13 +133,12 @@
         mSections.insert(section);
     }
 
-    int32_t headerCount;
-    err = in->readInt32(&headerCount);
+    err = in->resizeOutVector<vector<uint8_t>>(&mHeaders);
     if (err != NO_ERROR) {
         return err;
     }
-    mHeaders.resize(headerCount);
-    for (int i=0; i<headerCount; i++) {
+
+    for (int i=0; i<mHeaders.size(); i++) {
         err = in->readByteVector(&mHeaders[i]);
         if (err != NO_ERROR) {
             return err;
diff --git a/libs/input/OWNERS b/libs/input/OWNERS
index d701f23..4c20c4d 100644
--- a/libs/input/OWNERS
+++ b/libs/input/OWNERS
@@ -1 +1 @@
-include /core/java/android/hardware/input/OWNERS
+include /INPUT_OWNERS
diff --git a/libs/services/Android.bp b/libs/services/Android.bp
index f656ebf..0b7e7d3 100644
--- a/libs/services/Android.bp
+++ b/libs/services/Android.bp
@@ -26,8 +26,6 @@
 cc_library_shared {
     name: "libservices",
     srcs: [
-        ":IDropBoxManagerService.aidl",
-        ":ILogcatManagerService_aidl",
         "src/content/ComponentName.cpp",
         "src/os/DropBoxManager.cpp",
     ],
@@ -42,7 +40,10 @@
         "libbase_headers",
     ],
     aidl: {
-        include_dirs: ["frameworks/base/core/java/"],
+        libs: [
+            "ILogcatManagerService_aidl",
+            "IDropBoxManagerService_aidl",
+        ],
     },
 
     export_include_dirs: ["include"],
diff --git a/location/java/android/location/GnssMeasurementRequest.java b/location/java/android/location/GnssMeasurementRequest.java
index 3813e97..3f3ad75 100644
--- a/location/java/android/location/GnssMeasurementRequest.java
+++ b/location/java/android/location/GnssMeasurementRequest.java
@@ -135,8 +135,12 @@
     public String toString() {
         StringBuilder s = new StringBuilder();
         s.append("GnssMeasurementRequest[");
-        s.append("@");
-        TimeUtils.formatDuration(mIntervalMillis, s);
+        if (mIntervalMillis == PASSIVE_INTERVAL) {
+            s.append("passive");
+        } else {
+            s.append("@");
+            TimeUtils.formatDuration(mIntervalMillis, s);
+        }
         if (mFullTracking) {
             s.append(", FullTracking");
         }
diff --git a/location/java/android/location/ILocationManager.aidl b/location/java/android/location/ILocationManager.aidl
index 42b72d4..72761ef 100644
--- a/location/java/android/location/ILocationManager.aidl
+++ b/location/java/android/location/ILocationManager.aidl
@@ -98,12 +98,16 @@
     void addGnssAntennaInfoListener(in IGnssAntennaInfoListener listener, String packageName, @nullable String attributionTag, String listenerId);
     void removeGnssAntennaInfoListener(in IGnssAntennaInfoListener listener);
 
+    @EnforcePermission("INTERACT_ACROSS_USERS")
     void addProviderRequestListener(in IProviderRequestListener listener);
     void removeProviderRequestListener(in IProviderRequestListener listener);
 
     int getGnssBatchSize();
+    @EnforcePermission("LOCATION_HARDWARE")
     void startGnssBatch(long periodNanos, in ILocationListener listener, String packageName, @nullable String attributionTag, String listenerId);
+    @EnforcePermission("LOCATION_HARDWARE")
     void flushGnssBatch();
+    @EnforcePermission("LOCATION_HARDWARE")
     void stopGnssBatch();
 
     boolean hasProvider(String provider);
@@ -111,7 +115,9 @@
     List<String> getProviders(in Criteria criteria, boolean enabledOnly);
     String getBestProvider(in Criteria criteria, boolean enabledOnly);
     ProviderProperties getProviderProperties(String provider);
+    @EnforcePermission("READ_DEVICE_CONFIG")
     boolean isProviderPackage(@nullable String provider, String packageName, @nullable String attributionTag);
+    @EnforcePermission("READ_DEVICE_CONFIG")
     List<String> getProviderPackages(String provider);
 
     @EnforcePermission("LOCATION_HARDWARE")
diff --git a/location/java/android/location/altitude/AltitudeConverter.java b/location/java/android/location/altitude/AltitudeConverter.java
index eb73b69..3dc024ef 100644
--- a/location/java/android/location/altitude/AltitudeConverter.java
+++ b/location/java/android/location/altitude/AltitudeConverter.java
@@ -31,6 +31,14 @@
 /**
  * Converts altitudes reported above the World Geodetic System 1984 (WGS84) reference ellipsoid
  * into ones above Mean Sea Level.
+ *
+ * <p>Reference:
+ *
+ * <pre>
+ * Brian Julian and Michael Angermann.
+ * "Resource efficient and accurate altitude conversion to Mean Sea Level."
+ * To appear in 2023 IEEE/ION Position, Location and Navigation Symposium (PLANS).
+ * </pre>
  */
 public final class AltitudeConverter {
 
@@ -81,27 +89,47 @@
         long s2CellId = S2CellIdUtils.fromLatLngDegrees(location.getLatitude(),
                 location.getLongitude());
 
-        // (0,0) cell.
+        // Cell-space properties and coordinates.
+        int sizeIj = 1 << (S2CellIdUtils.MAX_LEVEL - params.mapS2Level);
+        int maxIj = 1 << S2CellIdUtils.MAX_LEVEL;
         long s0 = S2CellIdUtils.getParent(s2CellId, params.mapS2Level);
+        int f0 = S2CellIdUtils.getFace(s2CellId);
+        int i0 = S2CellIdUtils.getI(s2CellId);
+        int j0 = S2CellIdUtils.getJ(s2CellId);
+        int i1 = i0 + sizeIj;
+        int j1 = j0 + sizeIj;
+
+        // Non-boundary region calculation - simplest and most common case.
+        if (i1 < maxIj && j1 < maxIj) {
+            return new long[]{
+                    s0,
+                    S2CellIdUtils.getParent(S2CellIdUtils.fromFij(f0, i1, j0), params.mapS2Level),
+                    S2CellIdUtils.getParent(S2CellIdUtils.fromFij(f0, i0, j1), params.mapS2Level),
+                    S2CellIdUtils.getParent(S2CellIdUtils.fromFij(f0, i1, j1), params.mapS2Level)
+            };
+        }
+
+        // Boundary region calculation.
         long[] edgeNeighbors = new long[4];
         S2CellIdUtils.getEdgeNeighbors(s0, edgeNeighbors);
-
-        // (1,0) cell.
-        int i1 = S2CellIdUtils.getI(s2CellId) > S2CellIdUtils.getI(s0) ? -1 : 1;
-        long s1 = edgeNeighbors[i1 + 2];
-
-        // (0,1) cell.
-        int i2 = S2CellIdUtils.getJ(s2CellId) > S2CellIdUtils.getJ(s0) ? 1 : -1;
-        long s2 = edgeNeighbors[i2 + 1];
-
-        // (1,1) cell.
-        S2CellIdUtils.getEdgeNeighbors(s1, edgeNeighbors);
-        long s3 = 0;
-        for (int i = 0; i < edgeNeighbors.length; i++) {
-            if (edgeNeighbors[i] == s0) {
-                int i3 = (i + i1 * i2 + edgeNeighbors.length) % edgeNeighbors.length;
-                s3 = edgeNeighbors[i3] == s2 ? 0 : edgeNeighbors[i3];
-                break;
+        long s1 = edgeNeighbors[1];
+        long s2 = edgeNeighbors[2];
+        long s3;
+        if (f0 % 2 == 1) {
+            S2CellIdUtils.getEdgeNeighbors(s1, edgeNeighbors);
+            if (i1 < maxIj) {
+                s3 = edgeNeighbors[2];
+            } else {
+                s3 = s1;
+                s1 = edgeNeighbors[1];
+            }
+        } else {
+            S2CellIdUtils.getEdgeNeighbors(s2, edgeNeighbors);
+            if (j1 < maxIj) {
+                s3 = edgeNeighbors[1];
+            } else {
+                s3 = s2;
+                s2 = edgeNeighbors[3];
             }
         }
 
@@ -118,13 +146,12 @@
      * Mean Sea Level altitude accuracy is added if the {@code location} has a valid vertical
      * accuracy; otherwise, does not add a corresponding accuracy.
      */
-    private static void addMslAltitude(@NonNull MapParamsProto params, @NonNull long[] s2CellIds,
+    private static void addMslAltitude(@NonNull MapParamsProto params,
             @NonNull double[] geoidHeightsMeters, @NonNull Location location) {
-        long s0 = s2CellIds[0];
         double h0 = geoidHeightsMeters[0];
         double h1 = geoidHeightsMeters[1];
         double h2 = geoidHeightsMeters[2];
-        double h3 = s2CellIds[3] == 0 ? h0 : geoidHeightsMeters[3];
+        double h3 = geoidHeightsMeters[3];
 
         // Bilinear interpolation on an S2 square of size equal to that of a map cell. wi and wj
         // are the normalized [0,1] weights in the i and j directions, respectively, allowing us to
@@ -132,8 +159,8 @@
         long s2CellId = S2CellIdUtils.fromLatLngDegrees(location.getLatitude(),
                 location.getLongitude());
         double sizeIj = 1 << (S2CellIdUtils.MAX_LEVEL - params.mapS2Level);
-        double wi = Math.abs(S2CellIdUtils.getI(s2CellId) - S2CellIdUtils.getI(s0)) / sizeIj;
-        double wj = Math.abs(S2CellIdUtils.getJ(s2CellId) - S2CellIdUtils.getJ(s0)) / sizeIj;
+        double wi = (S2CellIdUtils.getI(s2CellId) % sizeIj) / sizeIj;
+        double wj = (S2CellIdUtils.getJ(s2CellId) % sizeIj) / sizeIj;
         double offsetMeters = h0 + (h1 - h0) * wi + (h2 - h0) * wj + (h3 - h1 - h2 + h0) * wi * wj;
 
         location.setMslAltitudeMeters(location.getAltitude() - offsetMeters);
@@ -167,7 +194,7 @@
         MapParamsProto params = GeoidHeightMap.getParams(context);
         long[] s2CellIds = findMapSquare(params, location);
         double[] geoidHeightsMeters = mGeoidHeightMap.readGeoidHeights(params, context, s2CellIds);
-        addMslAltitude(params, s2CellIds, geoidHeightsMeters, location);
+        addMslAltitude(params, geoidHeightsMeters, location);
     }
 
     /**
@@ -190,7 +217,7 @@
             return false;
         }
 
-        addMslAltitude(params, s2CellIds, geoidHeightsMeters, location);
+        addMslAltitude(params, geoidHeightsMeters, location);
         return true;
     }
 }
diff --git a/location/java/com/android/internal/location/altitude/GeoidHeightMap.java b/location/java/com/android/internal/location/altitude/GeoidHeightMap.java
index 73b6ab5..8067050 100644
--- a/location/java/com/android/internal/location/altitude/GeoidHeightMap.java
+++ b/location/java/com/android/internal/location/altitude/GeoidHeightMap.java
@@ -99,8 +99,8 @@
 
     /**
      * Adds to {@code values} values in the unit interval [0, 1] for the map cells identified by
-     * {@code s2CellIds}. Returns true if values are present for all non-zero IDs; otherwise,
-     * returns false and adds NaNs for absent values.
+     * {@code s2CellIds}. Returns true if values are present for all IDs; otherwise, returns false
+     * and adds NaNs for absent values.
      */
     private static boolean getUnitIntervalValues(@NonNull MapParamsProto params,
             @NonNull TileFunction tileFunction,
@@ -109,10 +109,8 @@
 
         S2TileProto[] tiles = new S2TileProto[len];
         for (int i = 0; i < len; i++) {
-            if (s2CellIds[i] != 0) {
-                long cacheKey = getCacheKey(params, s2CellIds[i]);
-                tiles[i] = tileFunction.getTile(cacheKey);
-            }
+            long cacheKey = getCacheKey(params, s2CellIds[i]);
+            tiles[i] = tileFunction.getTile(cacheKey);
             values[i] = Double.NaN;
         }
 
@@ -128,9 +126,6 @@
 
         boolean allFound = true;
         for (int i = 0; i < len; i++) {
-            if (s2CellIds[i] == 0) {
-                continue;
-            }
             if (Double.isNaN(values[i])) {
                 allFound = false;
             } else {
@@ -195,7 +190,7 @@
         }
 
         for (int i = tileIndex; i < tiles.length; i++) {
-            if (s2CellIds[i] == 0 || tiles[i] != tiles[tileIndex]) {
+            if (tiles[i] != tiles[tileIndex]) {
                 continue;
             }
 
@@ -226,15 +221,14 @@
     private static void validate(@NonNull MapParamsProto params, @NonNull long[] s2CellIds) {
         Preconditions.checkArgument(s2CellIds.length == 4);
         for (long s2CellId : s2CellIds) {
-            Preconditions.checkArgument(
-                    s2CellId == 0 || S2CellIdUtils.getLevel(s2CellId) == params.mapS2Level);
+            Preconditions.checkArgument(S2CellIdUtils.getLevel(s2CellId) == params.mapS2Level);
         }
     }
 
     /**
      * Returns the geoid heights in meters associated with the map cells identified by
-     * {@code s2CellIds}. Throws an {@link IOException} if a geoid height cannot be calculated for a
-     * non-zero ID.
+     * {@code s2CellIds}. Throws an {@link IOException} if a geoid height cannot be calculated for
+     * an ID.
      */
     @NonNull
     public double[] readGeoidHeights(@NonNull MapParamsProto params, @NonNull Context context,
@@ -254,8 +248,8 @@
 
     /**
      * Same as {@link #readGeoidHeights(MapParamsProto, Context, long[])} except that data will not
-     * be loaded from raw assets. Returns the heights if present for all non-zero IDs; otherwise,
-     * returns null.
+     * be loaded from raw assets. Returns the heights if present for all IDs; otherwise, returns
+     * null.
      */
     @Nullable
     public double[] readGeoidHeights(@NonNull MapParamsProto params, @NonNull long[] s2CellIds) {
@@ -269,8 +263,8 @@
 
     /**
      * Adds to {@code heightsMeters} the geoid heights in meters associated with the map cells
-     * identified by {@code s2CellIds}. Returns true if heights are present for all non-zero IDs;
-     * otherwise, returns false and adds NaNs for absent heights.
+     * identified by {@code s2CellIds}. Returns true if heights are present for all IDs; otherwise,
+     * returns false and adds NaNs for absent heights.
      */
     private boolean getGeoidHeights(@NonNull MapParamsProto params,
             @NonNull TileFunction tileFunction, @NonNull long[] s2CellIds,
@@ -292,9 +286,6 @@
         // Enable batch loading by finding all cache keys upfront.
         long[] cacheKeys = new long[len];
         for (int i = 0; i < len; i++) {
-            if (s2CellIds[i] == 0) {
-                continue;
-            }
             cacheKeys[i] = getCacheKey(params, s2CellIds[i]);
         }
 
@@ -302,7 +293,7 @@
         S2TileProto[] loadedTiles = new S2TileProto[len];
         String[] diskTokens = new String[len];
         for (int i = 0; i < len; i++) {
-            if (s2CellIds[i] == 0 || diskTokens[i] != null) {
+            if (diskTokens[i] != null) {
                 continue;
             }
             loadedTiles[i] = mCacheTiles.get(cacheKeys[i]);
@@ -319,7 +310,7 @@
 
         // Attempt to load tiles from disk.
         for (int i = 0; i < len; i++) {
-            if (s2CellIds[i] == 0 || loadedTiles[i] != null) {
+            if (loadedTiles[i] != null) {
                 continue;
             }
 
diff --git a/location/java/com/android/internal/location/altitude/S2CellIdUtils.java b/location/java/com/android/internal/location/altitude/S2CellIdUtils.java
index 5f11387..08bcda4 100644
--- a/location/java/com/android/internal/location/altitude/S2CellIdUtils.java
+++ b/location/java/com/android/internal/location/altitude/S2CellIdUtils.java
@@ -70,6 +70,34 @@
         return fromLatLngRadians(Math.toRadians(latDegrees), Math.toRadians(lngDegrees));
     }
 
+    /** Returns the leaf S2 cell ID of the specified (face, i, j) coordinate. */
+    public static long fromFij(int face, int i, int j) {
+        int bits = (face & SWAP_MASK);
+        // Update most significant bits.
+        long msb = ((long) face) << (POS_BITS - 33);
+        for (int k = 7; k >= 4; --k) {
+            bits = lookupBits(i, j, k, bits);
+            msb = updateBits(msb, k, bits);
+            bits = maskBits(bits);
+        }
+        // Update least significant bits.
+        long lsb = 0;
+        for (int k = 3; k >= 0; --k) {
+            bits = lookupBits(i, j, k, bits);
+            lsb = updateBits(lsb, k, bits);
+            bits = maskBits(bits);
+        }
+        return (((msb << 32) + lsb) << 1) + 1;
+    }
+
+    /**
+     * Returns the face of the specified S2 cell. The returned face is in [0, 5] for valid S2 cell
+     * IDs. Behavior is undefined for invalid S2 cell IDs.
+     */
+    public static int getFace(long s2CellId) {
+        return (int) (s2CellId >>> POS_BITS);
+    }
+
     /**
      * Returns the ID of the parent of the specified S2 cell at the specified parent level.
      * Behavior is undefined for invalid S2 cell IDs or parent levels not in
@@ -219,26 +247,6 @@
         return fromFij(face, i, j);
     }
 
-    /** Returns the leaf S2 cell ID of the specified (face, i, j) coordinate. */
-    private static long fromFij(int face, int i, int j) {
-        int bits = (face & SWAP_MASK);
-        // Update most significant bits.
-        long msb = ((long) face) << (POS_BITS - 33);
-        for (int k = 7; k >= 4; --k) {
-            bits = lookupBits(i, j, k, bits);
-            msb = updateBits(msb, k, bits);
-            bits = maskBits(bits);
-        }
-        // Update least significant bits.
-        long lsb = 0;
-        for (int k = 3; k >= 0; --k) {
-            bits = lookupBits(i, j, k, bits);
-            lsb = updateBits(lsb, k, bits);
-            bits = maskBits(bits);
-        }
-        return (((msb << 32) + lsb) << 1) + 1;
-    }
-
     private static long fromFijWrap(int face, int i, int j) {
         double u = iToU(i);
         double v = jToV(j);
@@ -314,10 +322,6 @@
         return bits & (SWAP_MASK | INVERT_MASK);
     }
 
-    private static int getFace(long s2CellId) {
-        return (int) (s2CellId >>> POS_BITS);
-    }
-
     private static boolean isLeaf(long s2CellId) {
         return ((int) s2CellId & LEAF_MASK) != 0;
     }
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index 3123ee6..3f013de 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -1342,6 +1342,11 @@
     }
 
     /** @hide */
+    public static boolean isRemoteSubmixDevice(int deviceType) {
+        return deviceType == DEVICE_IN_REMOTE_SUBMIX || deviceType == DEVICE_OUT_REMOTE_SUBMIX;
+    }
+
+    /** @hide */
     public static final String LEGACY_REMOTE_SUBMIX_ADDRESS = "0";
 
     // device states, must match AudioSystem::device_connection_state
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 7ce189b..02f765a 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -221,6 +221,7 @@
 
     boolean isSurroundFormatEnabled(int audioFormat);
 
+    @EnforcePermission("WRITE_SETTINGS")
     boolean setEncodedSurroundMode(int mode);
 
     int getEncodedSurroundMode(int targetSdkVersion);
@@ -260,6 +261,7 @@
 
     void forceVolumeControlStream(int streamType, IBinder cb);
 
+    @EnforcePermission("REMOTE_AUDIO_PLAYBACK")
     void setRingtonePlayer(IRingtonePlayer player);
     IRingtonePlayer getRingtonePlayer();
     int getUiSoundsStreamType();
@@ -364,6 +366,7 @@
 
     oneway void playerHasOpPlayAudio(in int piid, in boolean hasOpPlayAudio);
 
+    @EnforcePermission("BLUETOOTH_STACK")
     void handleBluetoothActiveDeviceChanged(in BluetoothDevice newDevice,
             in BluetoothDevice previousDevice, in BluetoothProfileConnectionInfo info);
 
diff --git a/media/java/android/media/IRingtonePlayer.aidl b/media/java/android/media/IRingtonePlayer.aidl
index 5a7ff7f..b3f72a1 100644
--- a/media/java/android/media/IRingtonePlayer.aidl
+++ b/media/java/android/media/IRingtonePlayer.aidl
@@ -21,6 +21,7 @@
 import android.net.Uri;
 import android.os.ParcelFileDescriptor;
 import android.os.UserHandle;
+import android.os.VibrationEffect;
 
 /**
  * @hide
@@ -29,12 +30,15 @@
     /** Used for Ringtone.java playback */
     @UnsupportedAppUsage
     oneway void play(IBinder token, in Uri uri, in AudioAttributes aa, float volume, boolean looping);
-    oneway void playWithVolumeShaping(IBinder token, in Uri uri, in AudioAttributes aa,
-        float volume, boolean looping, in @nullable VolumeShaper.Configuration volumeShaperConfig);
+    oneway void playRemoteRingtone(IBinder token, in Uri uri, in AudioAttributes aa,
+        boolean useExactAudioAttributes, int enabledMedia, in @nullable VibrationEffect ve,
+        float volume, boolean looping, boolean hapticGeneratorEnabled,
+        in @nullable VolumeShaper.Configuration volumeShaperConfig);
     oneway void stop(IBinder token);
     boolean isPlaying(IBinder token);
-    oneway void setPlaybackProperties(IBinder token, float volume, boolean looping,
-        boolean hapticGeneratorEnabled);
+    oneway void setLooping(IBinder token, boolean looping);
+    oneway void setVolume(IBinder token, float volume);
+    oneway void setHapticGeneratorEnabled(IBinder token, boolean hapticGeneratorEnabled);
 
     /** Used for Notification sound playback. */
     oneway void playAsync(in Uri uri, in UserHandle user, boolean looping, in AudioAttributes aa);
diff --git a/media/java/android/media/LocalRingtonePlayer.java b/media/java/android/media/LocalRingtonePlayer.java
new file mode 100644
index 0000000..d0169b9
--- /dev/null
+++ b/media/java/android/media/LocalRingtonePlayer.java
@@ -0,0 +1,408 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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 android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.res.AssetFileDescriptor;
+import android.media.audiofx.HapticGenerator;
+import android.net.Uri;
+import android.os.Trace;
+import android.os.VibrationAttributes;
+import android.os.VibrationEffect;
+import android.os.Vibrator;
+import android.util.Log;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Objects;
+
+/**
+ * Plays a ringtone on the local process.
+ * @hide
+ */
+public class LocalRingtonePlayer
+        implements Ringtone.RingtonePlayer, MediaPlayer.OnCompletionListener {
+    private static final String TAG = "LocalRingtonePlayer";
+
+    // keep references on active Ringtones until stopped or completion listener called.
+    private static final ArrayList<LocalRingtonePlayer> sActiveMediaPlayers = new ArrayList<>();
+
+    private final MediaPlayer mMediaPlayer;
+    private final AudioAttributes mAudioAttributes;
+    private final Ringtone.RingtonePlayer mVibrationPlayer;
+    private final Ringtone.Injectables mInjectables;
+    private final AudioManager mAudioManager;
+    private final VolumeShaper mVolumeShaper;
+    private HapticGenerator mHapticGenerator;
+
+    private LocalRingtonePlayer(@NonNull MediaPlayer mediaPlayer,
+            @NonNull AudioAttributes audioAttributes, @NonNull Ringtone.Injectables injectables,
+            @NonNull AudioManager audioManager, @Nullable HapticGenerator hapticGenerator,
+            @Nullable VolumeShaper volumeShaper,
+            @Nullable Ringtone.RingtonePlayer vibrationPlayer) {
+        Objects.requireNonNull(mediaPlayer);
+        Objects.requireNonNull(audioAttributes);
+        Objects.requireNonNull(injectables);
+        Objects.requireNonNull(audioManager);
+        mMediaPlayer = mediaPlayer;
+        mAudioAttributes = audioAttributes;
+        mInjectables = injectables;
+        mAudioManager = audioManager;
+        mVolumeShaper = volumeShaper;
+        mVibrationPlayer = vibrationPlayer;
+        mHapticGenerator = hapticGenerator;
+    }
+
+    /**
+     * Creates a {@link LocalRingtonePlayer} for a Uri, returning null if the Uri can't be
+     * loaded in the local player.
+     */
+    @Nullable
+    static Ringtone.RingtonePlayer create(@NonNull Context context,
+            @NonNull AudioManager audioManager, @NonNull Vibrator vibrator,
+            @NonNull Uri soundUri,
+            @NonNull AudioAttributes audioAttributes,
+            boolean isVibrationOnly,
+            @Nullable VibrationEffect vibrationEffect,
+            @NonNull Ringtone.Injectables injectables,
+            @Nullable VolumeShaper.Configuration volumeShaperConfig,
+            @Nullable AudioDeviceInfo preferredDevice, boolean initialHapticGeneratorEnabled,
+            boolean initialLooping, float initialVolume) {
+        Objects.requireNonNull(context);
+        Objects.requireNonNull(soundUri);
+        Objects.requireNonNull(audioAttributes);
+        Trace.beginSection("createLocalMediaPlayer");
+        MediaPlayer mediaPlayer = injectables.newMediaPlayer();
+        HapticGenerator hapticGenerator = null;
+        try {
+            mediaPlayer.setDataSource(context, soundUri);
+            mediaPlayer.setAudioAttributes(audioAttributes);
+            mediaPlayer.setPreferredDevice(preferredDevice);
+            mediaPlayer.setLooping(initialLooping);
+            mediaPlayer.setVolume(isVibrationOnly ? 0 : initialVolume);
+            if (initialHapticGeneratorEnabled) {
+                hapticGenerator = injectables.createHapticGenerator(mediaPlayer);
+                if (hapticGenerator != null) {
+                    // In practise, this should always be non-null because the initial value is
+                    // not true unless it's available.
+                    hapticGenerator.setEnabled(true);
+                    vibrationEffect = null;  // Don't play the VibrationEffect.
+                }
+            }
+            VolumeShaper volumeShaper = null;
+            if (volumeShaperConfig != null) {
+                volumeShaper = mediaPlayer.createVolumeShaper(volumeShaperConfig);
+            }
+            mediaPlayer.prepare();
+            if (vibrationEffect != null && !audioAttributes.areHapticChannelsMuted()) {
+                if (injectables.hasHapticChannels(mediaPlayer)) {
+                    // Don't play the Vibration effect if the URI has haptic channels.
+                    vibrationEffect = null;
+                }
+            }
+            VibrationEffectPlayer vibrationEffectPlayer = (vibrationEffect == null) ? null :
+                    new VibrationEffectPlayer(
+                            vibrationEffect, audioAttributes, vibrator, initialLooping);
+            if (isVibrationOnly && vibrationEffectPlayer != null) {
+                // Abandon the media player now that it's confirmed to not have haptic channels.
+                mediaPlayer.release();
+                return vibrationEffectPlayer;
+            }
+            return new LocalRingtonePlayer(mediaPlayer, audioAttributes, injectables, audioManager,
+                    hapticGenerator, volumeShaper, vibrationEffectPlayer);
+        } catch (SecurityException | IOException e) {
+            if (hapticGenerator != null) {
+                hapticGenerator.release();
+            }
+            // volume shaper closes with media player
+            mediaPlayer.release();
+            return null;
+        } finally {
+            Trace.endSection();
+        }
+    }
+
+    /**
+     * Creates a {@link LocalRingtonePlayer} for an externally referenced file descriptor. This is
+     * intended for loading a fallback from an internal resource, rather than via a Uri.
+     */
+    @Nullable
+    static LocalRingtonePlayer createForFallback(
+            @NonNull AudioManager audioManager, @NonNull Vibrator vibrator,
+            @NonNull AssetFileDescriptor afd,
+            @NonNull AudioAttributes audioAttributes,
+            @Nullable VibrationEffect vibrationEffect,
+            @NonNull Ringtone.Injectables injectables,
+            @Nullable VolumeShaper.Configuration volumeShaperConfig,
+            @Nullable AudioDeviceInfo preferredDevice,
+            boolean initialLooping, float initialVolume) {
+        // Haptic generator not supported for fallback.
+        Objects.requireNonNull(audioManager);
+        Objects.requireNonNull(afd);
+        Objects.requireNonNull(audioAttributes);
+        Trace.beginSection("createFallbackLocalMediaPlayer");
+
+        MediaPlayer mediaPlayer = injectables.newMediaPlayer();
+        try {
+            if (afd.getDeclaredLength() < 0) {
+                mediaPlayer.setDataSource(afd.getFileDescriptor());
+            } else {
+                mediaPlayer.setDataSource(afd.getFileDescriptor(),
+                        afd.getStartOffset(),
+                        afd.getDeclaredLength());
+            }
+            mediaPlayer.setAudioAttributes(audioAttributes);
+            mediaPlayer.setPreferredDevice(preferredDevice);
+            mediaPlayer.setLooping(initialLooping);
+            mediaPlayer.setVolume(initialVolume);
+            VolumeShaper volumeShaper = null;
+            if (volumeShaperConfig != null) {
+                volumeShaper = mediaPlayer.createVolumeShaper(volumeShaperConfig);
+            }
+            mediaPlayer.prepare();
+            if (vibrationEffect != null && !audioAttributes.areHapticChannelsMuted()) {
+                if (injectables.hasHapticChannels(mediaPlayer)) {
+                    // Don't play the Vibration effect if the URI has haptic channels.
+                    vibrationEffect = null;
+                }
+            }
+            VibrationEffectPlayer vibrationEffectPlayer = (vibrationEffect == null) ? null :
+                    new VibrationEffectPlayer(
+                            vibrationEffect, audioAttributes, vibrator, initialLooping);
+            return new LocalRingtonePlayer(mediaPlayer, audioAttributes,  injectables, audioManager,
+                    /* hapticGenerator= */ null, volumeShaper, vibrationEffectPlayer);
+        } catch (SecurityException | IOException e) {
+            Log.e(TAG, "Failed to open fallback ringtone");
+            // TODO: vibration-effect-only / no-sound LocalRingtonePlayer.
+            mediaPlayer.release();
+            return null;
+        } finally {
+            Trace.endSection();
+        }
+    }
+
+    @Override
+    public boolean play() {
+        // Play ringtones if stream volume is over 0 or if it is a haptic-only ringtone
+        // (typically because ringer mode is vibrate).
+        if (mAudioManager.getStreamVolume(AudioAttributes.toLegacyStreamType(mAudioAttributes))
+                == 0 && (mAudioAttributes.areHapticChannelsMuted() || !hasHapticChannels())) {
+            maybeStartVibration();
+            return true;  // Successfully played while muted.
+        }
+        synchronized (sActiveMediaPlayers) {
+            // We keep-alive when a mediaplayer is active, since its finalizer would stop the
+            // ringtone. This isn't necessary for vibrations in the vibrator service
+            // (i.e. maybeStartVibration in the muted case, above).
+            sActiveMediaPlayers.add(this);
+        }
+
+        mMediaPlayer.setOnCompletionListener(this);
+        mMediaPlayer.start();
+        if (mVolumeShaper != null) {
+            mVolumeShaper.apply(VolumeShaper.Operation.PLAY);
+        }
+        maybeStartVibration();
+        return true;
+    }
+
+    private void maybeStartVibration() {
+        if (mVibrationPlayer != null) {
+            mVibrationPlayer.play();
+        }
+    }
+
+    @Override
+    public boolean isPlaying() {
+        return mMediaPlayer.isPlaying();
+    }
+
+    @Override
+    public void stopAndRelease() {
+        synchronized (sActiveMediaPlayers) {
+            sActiveMediaPlayers.remove(this);
+        }
+        try {
+            mMediaPlayer.stop();
+        } finally {
+            if (mVibrationPlayer != null) {
+                try {
+                    mVibrationPlayer.stopAndRelease();
+                } catch (Exception e) {
+                    Log.e(TAG, "Exception stopping ringtone vibration", e);
+                }
+            }
+            if (mHapticGenerator != null) {
+                mHapticGenerator.release();
+            }
+            mMediaPlayer.setOnCompletionListener(null);
+            mMediaPlayer.reset();
+            mMediaPlayer.release();
+        }
+    }
+
+    @Override
+    public void setPreferredDevice(@Nullable AudioDeviceInfo audioDeviceInfo) {
+        mMediaPlayer.setPreferredDevice(audioDeviceInfo);
+    }
+
+    @Override
+    public void setLooping(boolean looping) {
+        boolean wasLooping = mMediaPlayer.isLooping();
+        if (wasLooping == looping) {
+            return;
+        }
+        mMediaPlayer.setLooping(looping);
+        if (mVibrationPlayer != null) {
+            mVibrationPlayer.setLooping(looping);
+        }
+    }
+
+    @Override
+    public void setHapticGeneratorEnabled(boolean enabled) {
+        if (mVibrationPlayer != null) {
+            // Ignore haptic generator changes if a vibration player is present. The decision to
+            // use one or the other happens before this object is constructed.
+            return;
+        }
+        if (enabled && mHapticGenerator == null && !hasHapticChannels()) {
+            mHapticGenerator = mInjectables.createHapticGenerator(mMediaPlayer);
+        }
+        if (mHapticGenerator != null) {
+            mHapticGenerator.setEnabled(enabled);
+        }
+    }
+
+    @Override
+    public void setVolume(float volume) {
+        mMediaPlayer.setVolume(volume);
+        // no effect on vibration player
+    }
+
+    @Override
+    public boolean hasHapticChannels() {
+        return mInjectables.hasHapticChannels(mMediaPlayer);
+    }
+
+    @Override
+    public void onCompletion(MediaPlayer mp) {
+        synchronized (sActiveMediaPlayers) {
+            sActiveMediaPlayers.remove(this);
+        }
+        mp.setOnCompletionListener(null); // Help the Java GC: break the refcount cycle.
+        // No effect on vibration: either it's looping and this callback only happens when stopped,
+        // or it's not looping, in which case the vibration should play to its own completion.
+    }
+
+    /** A RingtonePlayer that only plays a VibrationEffect. */
+    static class VibrationEffectPlayer implements Ringtone.RingtonePlayer {
+        private static final int VIBRATION_LOOP_DELAY_MS = 200;
+        private final VibrationEffect mVibrationEffect;
+        private final VibrationAttributes mVibrationAttributes;
+        private final Vibrator mVibrator;
+        private boolean mIsLooping;
+        private boolean mStartedVibration;
+
+        VibrationEffectPlayer(@NonNull VibrationEffect vibrationEffect,
+                @NonNull AudioAttributes audioAttributes,
+                @NonNull Vibrator vibrator, boolean initialLooping) {
+            mVibrationEffect = vibrationEffect;
+            mVibrationAttributes = new VibrationAttributes.Builder(audioAttributes).build();
+            mVibrator = vibrator;
+            mIsLooping = initialLooping;
+        }
+
+        @Override
+        public boolean play() {
+            if (!mStartedVibration) {
+                try {
+                    // Adjust the vibration effect to loop.
+                    VibrationEffect loopAdjustedEffect =
+                            mVibrationEffect.applyRepeatingIndefinitely(
+                                mIsLooping, VIBRATION_LOOP_DELAY_MS);
+                    mVibrator.vibrate(loopAdjustedEffect, mVibrationAttributes);
+                    mStartedVibration = true;
+                } catch (Exception e) {
+                    // Catch exceptions widely, because we don't want to "leak" looping sounds or
+                    // vibrations if something goes wrong.
+                    Log.e(TAG, "Problem starting " + (mIsLooping ? "looping " : "") + "vibration "
+                            + "for ringtone: " + mVibrationEffect, e);
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        @Override
+        public boolean isPlaying() {
+            return mStartedVibration;
+        }
+
+        @Override
+        public void stopAndRelease() {
+            if (mStartedVibration) {
+                try {
+                    mVibrator.cancel(mVibrationAttributes.getUsage());
+                    mStartedVibration = false;
+                } catch (Exception e) {
+                    // Catch exceptions widely, because we don't want to "leak" looping sounds or
+                    // vibrations if something goes wrong.
+                    Log.e(TAG, "Problem stopping vibration for ringtone", e);
+                }
+            }
+        }
+
+        @Override
+        public void setPreferredDevice(AudioDeviceInfo audioDeviceInfo) {
+            // no-op
+        }
+
+        @Override
+        public void setLooping(boolean looping) {
+            if (looping == mIsLooping) {
+                return;
+            }
+            mIsLooping = looping;
+            if (mStartedVibration) {
+                if (!mIsLooping) {
+                    // Was looping, stop looping
+                    stopAndRelease();
+                }
+                // Else was not looping, but can't interfere with a running vibration without
+                // restarting it, and don't know if it was finished. So do nothing: apps shouldn't
+                // toggle looping after calling play anyway.
+            }
+        }
+
+        @Override
+        public void setHapticGeneratorEnabled(boolean enabled) {
+            // n/a
+        }
+
+        @Override
+        public void setVolume(float volume) {
+            // n/a
+        }
+
+        @Override
+        public boolean hasHapticChannels() {
+            return false;
+        }
+    }
+}
diff --git a/media/java/android/media/MediaRoute2Info.java b/media/java/android/media/MediaRoute2Info.java
index 651c732..91fa873 100644
--- a/media/java/android/media/MediaRoute2Info.java
+++ b/media/java/android/media/MediaRoute2Info.java
@@ -284,7 +284,6 @@
      * routing being done by the system.
      *
      * @see #getType
-     * @hide
      */
     public static final int TYPE_REMOTE_TABLET = 1004;
 
@@ -295,7 +294,6 @@
      * routing being done by the system.
      *
      * @see #getType
-     * @hide
      */
     public static final int TYPE_REMOTE_TABLET_DOCKED = 1005;
 
@@ -306,7 +304,6 @@
      * routing being done by the system.
      *
      * @see #getType
-     * @hide
      */
     public static final int TYPE_REMOTE_COMPUTER = 1006;
 
@@ -317,7 +314,6 @@
      * routing being done by the system.
      *
      * @see #getType
-     * @hide
      */
     public static final int TYPE_REMOTE_GAME_CONSOLE = 1007;
 
@@ -328,7 +324,6 @@
      * routing being done by the system.
      *
      * @see #getType
-     * @hide
      */
     public static final int TYPE_REMOTE_CAR = 1008;
 
@@ -339,7 +334,6 @@
      * routing being done by the system.
      *
      * @see #getType
-     * @hide
      */
     public static final int TYPE_REMOTE_SMARTWATCH = 1009;
 
@@ -350,7 +344,6 @@
      * routing being done by the system.
      *
      * @see #getType
-     * @hide
      */
     public static final int TYPE_REMOTE_SMARTPHONE = 1010;
 
@@ -457,27 +450,27 @@
     public static final String FEATURE_REMOTE_GROUP_PLAYBACK =
             "android.media.route.feature.REMOTE_GROUP_PLAYBACK";
 
-    final String mId;
-    final CharSequence mName;
-    final List<String> mFeatures;
+    private final String mId;
+    private final CharSequence mName;
+    private final List<String> mFeatures;
     @Type
-    final int mType;
-    final boolean mIsSystem;
-    final Uri mIconUri;
-    final CharSequence mDescription;
+    private final int mType;
+    private final boolean mIsSystem;
+    private final Uri mIconUri;
+    private final CharSequence mDescription;
     @ConnectionState
-    final int mConnectionState;
-    final String mClientPackageName;
-    final String mPackageName;
-    final int mVolumeHandling;
-    final int mVolumeMax;
-    final int mVolume;
-    final String mAddress;
-    final Set<String> mDeduplicationIds;
-    final Bundle mExtras;
-    final String mProviderId;
-    final boolean mIsVisibilityRestricted;
-    final Set<String> mAllowedPackages;
+    private final int mConnectionState;
+    private final String mClientPackageName;
+    private final String mPackageName;
+    private final int mVolumeHandling;
+    private final int mVolumeMax;
+    private final int mVolume;
+    private final String mAddress;
+    private final Set<String> mDeduplicationIds;
+    private final Bundle mExtras;
+    private final String mProviderId;
+    private final boolean mIsVisibilityRestricted;
+    private final Set<String> mAllowedPackages;
 
     MediaRoute2Info(@NonNull Builder builder) {
         mId = builder.mId;
@@ -956,28 +949,28 @@
      * Builder for {@link MediaRoute2Info media route info}.
      */
     public static final class Builder {
-        final String mId;
-        final CharSequence mName;
-        final List<String> mFeatures;
+        private final String mId;
+        private final CharSequence mName;
+        private final List<String> mFeatures;
 
         @Type
-        int mType = TYPE_UNKNOWN;
-        boolean mIsSystem;
-        Uri mIconUri;
-        CharSequence mDescription;
+        private int mType = TYPE_UNKNOWN;
+        private boolean mIsSystem;
+        private Uri mIconUri;
+        private CharSequence mDescription;
         @ConnectionState
-        int mConnectionState;
-        String mClientPackageName;
-        String mPackageName;
-        int mVolumeHandling = PLAYBACK_VOLUME_FIXED;
-        int mVolumeMax;
-        int mVolume;
-        String mAddress;
-        Set<String> mDeduplicationIds;
-        Bundle mExtras;
-        String mProviderId;
-        boolean mIsVisibilityRestricted;
-        Set<String> mAllowedPackages;
+        private int mConnectionState;
+        private String mClientPackageName;
+        private String mPackageName;
+        private int mVolumeHandling = PLAYBACK_VOLUME_FIXED;
+        private int mVolumeMax;
+        private int mVolume;
+        private String mAddress;
+        private Set<String> mDeduplicationIds;
+        private Bundle mExtras;
+        private String mProviderId;
+        private boolean mIsVisibilityRestricted;
+        private Set<String> mAllowedPackages;
 
         /**
          * Constructor for builder to create {@link MediaRoute2Info}.
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index c620229..f27a8de 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -29,6 +29,7 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
+import android.os.Message;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.text.TextUtils;
@@ -75,9 +76,7 @@
     private static final long MANAGER_REQUEST_ID_NONE = MediaRoute2ProviderService.REQUEST_ID_NONE;
 
     @GuardedBy("sSystemRouterLock")
-    private static Map<String, MediaRouter2> sSystemMediaRouter2Map = new ArrayMap<>();
-
-    private static MediaRouter2Manager sManager;
+    private static final Map<String, MediaRouter2> sSystemMediaRouter2Map = new ArrayMap<>();
 
     @GuardedBy("sRouterLock")
     private static MediaRouter2 sInstance;
@@ -85,9 +84,12 @@
     private final Context mContext;
     private final IMediaRouterService mMediaRouterService;
     private final Object mLock = new Object();
+    private final MediaRouter2Impl mImpl;
 
     private final CopyOnWriteArrayList<RouteCallbackRecord> mRouteCallbackRecords =
             new CopyOnWriteArrayList<>();
+    private final CopyOnWriteArrayList<RouteListingPreferenceCallbackRecord>
+            mListingPreferenceCallbackRecords = new CopyOnWriteArrayList<>();
     private final CopyOnWriteArrayList<TransferCallbackRecord> mTransferCallbackRecords =
             new CopyOnWriteArrayList<>();
     private final CopyOnWriteArrayList<ControllerCallbackRecord> mControllerCallbackRecords =
@@ -96,12 +98,6 @@
     private final CopyOnWriteArrayList<ControllerCreationRequest> mControllerCreationRequests =
             new CopyOnWriteArrayList<>();
 
-    // TODO: Specify the fields that are only used (or not used) by system media router.
-    private final String mClientPackageName;
-    final ManagerCallback mManagerCallback;
-
-    private final String mPackageName;
-
     /**
      * Stores the latest copy of all routes received from the system server, without any filtering,
      * sorting, or deduplication.
@@ -128,7 +124,6 @@
     private final Map<String, RoutingController> mNonSystemRoutingControllers = new ArrayMap<>();
 
     private final AtomicInteger mNextRequestId = new AtomicInteger(1);
-    private final AtomicBoolean mIsScanning = new AtomicBoolean(/* initialValue= */ false);
 
     final Handler mHandler;
 
@@ -178,7 +173,7 @@
      *
      * <ul>
      *   <li>{@link #getControllers()}
-     *   <li>{@link #getController(String)}}
+     *   <li>{@link #getController(String)}
      *   <li>{@link TransferCallback#onTransfer(RoutingController, RoutingController)}
      *   <li>{@link TransferCallback#onStop(RoutingController)}
      *   <li>{@link ControllerCallback#onControllerUpdated(RoutingController)}
@@ -191,7 +186,8 @@
      * <p>Finally, it will have no effect to call {@link #setOnGetControllerHintsListener}.
      *
      * @param clientPackageName the package name of the app to control
-     * @throws SecurityException if the caller doesn't have MODIFY_AUDIO_ROUTING permission.
+     * @throws SecurityException if the caller doesn't have {@link
+     *     Manifest.permission#MEDIA_CONTENT_CONTROL MEDIA_CONTENT_CONTROL} permission.
      * @hide
      */
     @SystemApi
@@ -221,14 +217,8 @@
         synchronized (sSystemRouterLock) {
             MediaRouter2 instance = sSystemMediaRouter2Map.get(clientPackageName);
             if (instance == null) {
-                if (sManager == null) {
-                    sManager = MediaRouter2Manager.getInstance(context.getApplicationContext());
-                }
                 instance = new MediaRouter2(context, clientPackageName);
                 sSystemMediaRouter2Map.put(clientPackageName, instance);
-                // Using direct executor here, since MediaRouter2Manager also posts
-                // to the main handler.
-                sManager.registerCallback(Runnable::run, instance.mManagerCallback);
             }
             return instance;
         }
@@ -255,11 +245,7 @@
     @SystemApi
     @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
     public void startScan() {
-        if (isSystemRouter()) {
-            if (!mIsScanning.getAndSet(true)) {
-                sManager.registerScanRequest();
-            }
-        }
+        mImpl.startScan();
     }
 
     /**
@@ -283,11 +269,7 @@
     @SystemApi
     @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
     public void stopScan() {
-        if (isSystemRouter()) {
-            if (mIsScanning.getAndSet(false)) {
-                sManager.unregisterScanRequest();
-            }
-        }
+        mImpl.stopScan();
     }
 
     private MediaRouter2(Context appContext) {
@@ -295,14 +277,12 @@
         mMediaRouterService =
                 IMediaRouterService.Stub.asInterface(
                         ServiceManager.getService(Context.MEDIA_ROUTER_SERVICE));
-        mPackageName = mContext.getPackageName();
+        mImpl = new LocalMediaRouter2Impl(mContext.getPackageName());
         mHandler = new Handler(Looper.getMainLooper());
 
         List<MediaRoute2Info> currentSystemRoutes = null;
-        RoutingSessionInfo currentSystemSessionInfo = null;
         try {
             currentSystemRoutes = mMediaRouterService.getSystemRoutes();
-            currentSystemSessionInfo = mMediaRouterService.getSystemSessionInfo();
         } catch (RemoteException ex) {
             ex.rethrowFromSystemServer();
         }
@@ -311,6 +291,7 @@
             throw new RuntimeException("Null or empty currentSystemRoutes. Something is wrong.");
         }
 
+        RoutingSessionInfo currentSystemSessionInfo = mImpl.getSystemSessionInfo();
         if (currentSystemSessionInfo == null) {
             throw new RuntimeException("Null currentSystemSessionInfo. Something is wrong.");
         }
@@ -319,27 +300,17 @@
             mRoutes.put(route.getId(), route);
         }
         mSystemController = new SystemRoutingController(currentSystemSessionInfo);
-
-        // Only used by system MediaRouter2.
-        mClientPackageName = null;
-        mManagerCallback = null;
     }
 
     private MediaRouter2(Context context, String clientPackageName) {
         mContext = context;
-        mClientPackageName = clientPackageName;
-        mManagerCallback = new ManagerCallback();
         mHandler = new Handler(Looper.getMainLooper());
-        mSystemController =
-                new SystemRoutingController(
-                        ensureClientPackageNameForSystemSession(
-                                sManager.getSystemRoutingSession(clientPackageName)));
-        mDiscoveryPreference = sManager.getDiscoveryPreference(clientPackageName);
-        updateAllRoutesFromManager();
+        mMediaRouterService =
+                IMediaRouterService.Stub.asInterface(
+                        ServiceManager.getService(Context.MEDIA_ROUTER_SERVICE));
 
-        // Only used by non-system MediaRouter2.
-        mMediaRouterService = null;
-        mPackageName = null;
+        mImpl = new ProxyMediaRouter2Impl(context, clientPackageName);
+        mSystemController = new SystemRoutingController(mImpl.getSystemSessionInfo());
     }
 
     /**
@@ -368,7 +339,7 @@
     @SystemApi
     @Nullable
     public String getClientPackageName() {
-        return mClientPackageName;
+        return mImpl.getClientPackageName();
     }
 
     /**
@@ -384,39 +355,16 @@
         Objects.requireNonNull(executor, "executor must not be null");
         Objects.requireNonNull(routeCallback, "callback must not be null");
         Objects.requireNonNull(preference, "preference must not be null");
-        if (isSystemRouter()) {
-            preference = RouteDiscoveryPreference.EMPTY;
-        }
 
-        RouteCallbackRecord record = new RouteCallbackRecord(executor, routeCallback, preference);
+        RouteCallbackRecord record =
+                mImpl.createRouteCallbackRecord(executor, routeCallback, preference);
 
         mRouteCallbackRecords.remove(record);
         // It can fail to add the callback record if another registration with the same callback
         // is happening but it's okay because either this or the other registration should be done.
         mRouteCallbackRecords.addIfAbsent(record);
 
-        if (isSystemRouter()) {
-            return;
-        }
-
-        synchronized (mLock) {
-            if (mStub == null) {
-                MediaRouter2Stub stub = new MediaRouter2Stub();
-                try {
-                    mMediaRouterService.registerRouter2(stub, mPackageName);
-                    mStub = stub;
-                } catch (RemoteException ex) {
-                    ex.rethrowFromSystemServer();
-                }
-            }
-            if (mStub != null && updateDiscoveryPreferenceIfNeededLocked()) {
-                try {
-                    mMediaRouterService.setDiscoveryRequestWithRouter2(mStub, mDiscoveryPreference);
-                } catch (RemoteException ex) {
-                    ex.rethrowFromSystemServer();
-                }
-            }
-        }
+        mImpl.registerRouteCallback();
     }
 
     /**
@@ -434,29 +382,43 @@
             return;
         }
 
-        if (isSystemRouter()) {
-            return;
-        }
+        mImpl.unregisterRouteCallback();
+    }
 
-        synchronized (mLock) {
-            if (mStub == null) {
-                return;
-            }
-            if (updateDiscoveryPreferenceIfNeededLocked()) {
-                try {
-                    mMediaRouterService.setDiscoveryRequestWithRouter2(mStub, mDiscoveryPreference);
-                } catch (RemoteException ex) {
-                    Log.e(TAG, "unregisterRouteCallback: Unable to set discovery request.", ex);
-                }
-            }
-            if (mRouteCallbackRecords.isEmpty() && mNonSystemRoutingControllers.isEmpty()) {
-                try {
-                    mMediaRouterService.unregisterRouter2(mStub);
-                } catch (RemoteException ex) {
-                    ex.rethrowFromSystemServer();
-                }
-                mStub = null;
-            }
+    /**
+     * Registers callback to be invoked when the {@link RouteListingPreference} of the target
+     * router changes.
+     *
+     * <p>Calls using a previously registered callback will overwrite the callback record.
+     *
+     * @see #setRouteListingPreference(RouteListingPreference)
+     * @hide
+     */
+    public void registerRouteListingPreferenceCallback(
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull RouteListingPreferenceCallback routeListingPreferenceCallback) {
+        Objects.requireNonNull(executor, "executor must not be null");
+        Objects.requireNonNull(routeListingPreferenceCallback, "callback must not be null");
+
+        RouteListingPreferenceCallbackRecord record =
+                new RouteListingPreferenceCallbackRecord(executor, routeListingPreferenceCallback);
+
+        mListingPreferenceCallbackRecords.remove(record);
+        mListingPreferenceCallbackRecords.add(record);
+    }
+
+    /**
+     * Unregisters the given callback to not receive {@link RouteListingPreference} change events.
+     *
+     * @hide
+     */
+    public void unregisterRouteListingPreferenceCallback(
+            @NonNull RouteListingPreferenceCallback callback) {
+        Objects.requireNonNull(callback, "callback must not be null");
+
+        if (!mListingPreferenceCallbackRecords.remove(
+                new RouteListingPreferenceCallbackRecord(/* executor */ null, callback))) {
+            Log.w(TAG, "unregisterRouteListingPreferenceCallback: Ignoring an unknown callback");
         }
     }
 
@@ -479,7 +441,7 @@
     public boolean showSystemOutputSwitcher() {
         synchronized (mLock) {
             try {
-                return mMediaRouterService.showMediaOutputSwitcher(mPackageName);
+                return mMediaRouterService.showMediaOutputSwitcher(mImpl.getPackageName());
             } catch (RemoteException ex) {
                 ex.rethrowFromSystemServer();
             }
@@ -523,13 +485,27 @@
             try {
                 if (mStub == null) {
                     MediaRouter2Stub stub = new MediaRouter2Stub();
-                    mMediaRouterService.registerRouter2(stub, mPackageName);
+                    mMediaRouterService.registerRouter2(stub, mImpl.getPackageName());
                     mStub = stub;
                 }
                 mMediaRouterService.setRouteListingPreference(mStub, mRouteListingPreference);
             } catch (RemoteException ex) {
                 ex.rethrowFromSystemServer();
             }
+            notifyRouteListingPreferenceUpdated(routeListingPreference);
+        }
+    }
+
+    /**
+     * Returns the current {@link RouteListingPreference} of the target router.
+     *
+     * @see #setRouteListingPreference(RouteListingPreference)
+     * @hide
+     */
+    @Nullable
+    public RouteListingPreference getRouteListingPreference() {
+        synchronized (mLock) {
+            return mRouteListingPreference;
         }
     }
 
@@ -558,10 +534,7 @@
     @SystemApi
     @NonNull
     public List<MediaRoute2Info> getAllRoutes() {
-        if (isSystemRouter()) {
-            return sManager.getAllRoutes();
-        }
-        return Collections.emptyList();
+        return mImpl.getAllRoutes();
     }
 
     /**
@@ -596,7 +569,6 @@
         TransferCallbackRecord record = new TransferCallbackRecord(executor, callback);
         if (!mTransferCallbackRecords.addIfAbsent(record)) {
             Log.w(TAG, "registerTransferCallback: Ignoring the same callback");
-            return;
         }
     }
 
@@ -612,7 +584,6 @@
 
         if (!mTransferCallbackRecords.remove(new TransferCallbackRecord(null, callback))) {
             Log.w(TAG, "unregisterTransferCallback: Ignoring an unknown callback");
-            return;
         }
     }
 
@@ -630,7 +601,6 @@
         ControllerCallbackRecord record = new ControllerCallbackRecord(executor, callback);
         if (!mControllerCallbackRecords.addIfAbsent(record)) {
             Log.w(TAG, "registerControllerCallback: Ignoring the same callback");
-            return;
         }
     }
 
@@ -645,7 +615,6 @@
 
         if (!mControllerCallbackRecords.remove(new ControllerCallbackRecord(null, callback))) {
             Log.w(TAG, "unregisterControllerCallback: Ignoring an unknown callback");
-            return;
         }
     }
 
@@ -658,10 +627,7 @@
      *     {@code null} for unset.
      */
     public void setOnGetControllerHintsListener(@Nullable OnGetControllerHintsListener listener) {
-        if (isSystemRouter()) {
-            return;
-        }
-        mOnGetControllerHintsListener = listener;
+        mImpl.setOnGetControllerHintsListener(listener);
     }
 
     /**
@@ -674,30 +640,7 @@
      * @see TransferCallback#onTransferFailure
      */
     public void transferTo(@NonNull MediaRoute2Info route) {
-        if (isSystemRouter()) {
-            sManager.transfer(mClientPackageName, route);
-            return;
-        }
-
-        Log.v(TAG, "Transferring to route: " + route);
-
-        boolean routeFound;
-        synchronized (mLock) {
-            // TODO: Check thread-safety
-            routeFound = mRoutes.containsKey(route.getId());
-        }
-        if (!routeFound) {
-            notifyTransferFailure(route);
-            return;
-        }
-
-        RoutingController controller = getCurrentController();
-        if (controller.getRoutingSessionInfo().getTransferableRoutes().contains(route.getId())) {
-            controller.transferToRoute(route);
-            return;
-        }
-
-        requestCreateController(controller, route, MANAGER_REQUEST_ID_NONE);
+        mImpl.transferTo(route);
     }
 
     /**
@@ -705,13 +648,7 @@
      * controls the media routing, this method is a no-op.
      */
     public void stop() {
-        if (isSystemRouter()) {
-            List<RoutingSessionInfo> sessionInfos = sManager.getRoutingSessions(mClientPackageName);
-            RoutingSessionInfo sessionToRelease = sessionInfos.get(sessionInfos.size() - 1);
-            sManager.releaseSession(sessionToRelease);
-            return;
-        }
-        getCurrentController().release();
+        mImpl.stop();
     }
 
     /**
@@ -726,10 +663,7 @@
     @SystemApi
     @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
     public void transfer(@NonNull RoutingController controller, @NonNull MediaRoute2Info route) {
-        if (isSystemRouter()) {
-            sManager.transfer(controller.getRoutingSessionInfo(), route);
-            return;
-        }
+        mImpl.transfer(controller.getRoutingSessionInfo(), route);
     }
 
     void requestCreateController(
@@ -820,31 +754,7 @@
      */
     @NonNull
     public List<RoutingController> getControllers() {
-        List<RoutingController> result = new ArrayList<>();
-
-        if (isSystemRouter()) {
-            // Unlike non-system MediaRouter2, controller instances cannot be kept,
-            // since the transfer events initiated from other apps will not come through manager.
-            List<RoutingSessionInfo> sessions = sManager.getRoutingSessions(mClientPackageName);
-            for (RoutingSessionInfo session : sessions) {
-                RoutingController controller;
-                if (session.isSystemSession()) {
-                    mSystemController.setRoutingSessionInfo(
-                            ensureClientPackageNameForSystemSession(session));
-                    controller = mSystemController;
-                } else {
-                    controller = new RoutingController(session);
-                }
-                result.add(controller);
-            }
-            return result;
-        }
-
-        result.add(0, mSystemController);
-        synchronized (mLock) {
-            result.addAll(mNonSystemRoutingControllers.values());
-        }
-        return result;
+        return mImpl.getControllers();
     }
 
     /**
@@ -862,11 +772,7 @@
     public void setRouteVolume(@NonNull MediaRoute2Info route, int volume) {
         Objects.requireNonNull(route, "route must not be null");
 
-        if (isSystemRouter()) {
-            sManager.setRouteVolume(route, volume);
-            return;
-        }
-        // If this API needs to be public, use IMediaRouterService#setRouteVolumeWithRouter2()
+        mImpl.setRouteVolume(route, volume);
     }
 
     void syncRoutesOnHandler(
@@ -1130,31 +1036,6 @@
         requestCreateController(controller, route, managerRequestId);
     }
 
-    /**
-     * Returns whether this router is created with {@link #getInstance(Context, String)}. This kind
-     * of router can control the target app's media routing.
-     */
-    private boolean isSystemRouter() {
-        return mClientPackageName != null;
-    }
-
-    /**
-     * Returns a {@link RoutingSessionInfo} which has the client package name. The client package
-     * name is set only when the given sessionInfo doesn't have it. Should only used for system
-     * media routers.
-     */
-    private RoutingSessionInfo ensureClientPackageNameForSystemSession(
-            @NonNull RoutingSessionInfo sessionInfo) {
-        if (!sessionInfo.isSystemSession()
-                || !TextUtils.isEmpty(sessionInfo.getClientPackageName())) {
-            return sessionInfo;
-        }
-
-        return new RoutingSessionInfo.Builder(sessionInfo)
-                .setClientPackageName(mClientPackageName)
-                .build();
-    }
-
     private List<MediaRoute2Info> getSortedRoutes(
             List<MediaRoute2Info> routes, List<String> packageOrder) {
         if (packageOrder.isEmpty()) {
@@ -1203,47 +1084,20 @@
         return filteredRoutes;
     }
 
-    private List<MediaRoute2Info> filterRoutesWithIndividualPreference(
-            List<MediaRoute2Info> routes, RouteDiscoveryPreference discoveryPreference) {
-        List<MediaRoute2Info> filteredRoutes = new ArrayList<>();
-        if (isSystemRouter()) {
-            // Individual discovery preferences do not apply for the system router.
-            filteredRoutes.addAll(routes);
-            return filteredRoutes;
-        }
-        for (MediaRoute2Info route : routes) {
-            if (!route.hasAnyFeatures(discoveryPreference.getPreferredFeatures())) {
-                continue;
-            }
-            if (!discoveryPreference.getAllowedPackages().isEmpty()
-                    && (route.getPackageName() == null
-                            || !discoveryPreference
-                                    .getAllowedPackages()
-                                    .contains(route.getPackageName()))) {
-                continue;
-            }
-            filteredRoutes.add(route);
-        }
-        return filteredRoutes;
-    }
-
-    private void updateAllRoutesFromManager() {
-        if (!isSystemRouter()) {
-            return;
-        }
+    @NonNull
+    private List<MediaRoute2Info> getRoutesWithIds(@NonNull List<String> routeIds) {
         synchronized (mLock) {
-            mRoutes.clear();
-            for (MediaRoute2Info route : sManager.getAllRoutes()) {
-                mRoutes.put(route.getId(), route);
-            }
-            updateFilteredRoutesLocked();
+            return routeIds.stream()
+                    .map(mRoutes::get)
+                    .filter(Objects::nonNull)
+                    .collect(Collectors.toList());
         }
     }
 
     private void notifyRoutesAdded(List<MediaRoute2Info> routes) {
         for (RouteCallbackRecord record : mRouteCallbackRecords) {
             List<MediaRoute2Info> filteredRoutes =
-                    filterRoutesWithIndividualPreference(routes, record.mPreference);
+                    mImpl.filterRoutesWithIndividualPreference(routes, record.mPreference);
             if (!filteredRoutes.isEmpty()) {
                 record.mExecutor.execute(() -> record.mRouteCallback.onRoutesAdded(filteredRoutes));
             }
@@ -1253,7 +1107,7 @@
     private void notifyRoutesRemoved(List<MediaRoute2Info> routes) {
         for (RouteCallbackRecord record : mRouteCallbackRecords) {
             List<MediaRoute2Info> filteredRoutes =
-                    filterRoutesWithIndividualPreference(routes, record.mPreference);
+                    mImpl.filterRoutesWithIndividualPreference(routes, record.mPreference);
             if (!filteredRoutes.isEmpty()) {
                 record.mExecutor.execute(
                         () -> record.mRouteCallback.onRoutesRemoved(filteredRoutes));
@@ -1264,7 +1118,7 @@
     private void notifyRoutesChanged(List<MediaRoute2Info> routes) {
         for (RouteCallbackRecord record : mRouteCallbackRecords) {
             List<MediaRoute2Info> filteredRoutes =
-                    filterRoutesWithIndividualPreference(routes, record.mPreference);
+                    mImpl.filterRoutesWithIndividualPreference(routes, record.mPreference);
             if (!filteredRoutes.isEmpty()) {
                 record.mExecutor.execute(
                         () -> record.mRouteCallback.onRoutesChanged(filteredRoutes));
@@ -1275,7 +1129,7 @@
     private void notifyRoutesUpdated(List<MediaRoute2Info> routes) {
         for (RouteCallbackRecord record : mRouteCallbackRecords) {
             List<MediaRoute2Info> filteredRoutes =
-                    filterRoutesWithIndividualPreference(routes, record.mPreference);
+                    mImpl.filterRoutesWithIndividualPreference(routes, record.mPreference);
             record.mExecutor.execute(() -> record.mRouteCallback.onRoutesUpdated(filteredRoutes));
         }
     }
@@ -1287,6 +1141,15 @@
         }
     }
 
+    private void notifyRouteListingPreferenceUpdated(@Nullable RouteListingPreference preference) {
+        for (RouteListingPreferenceCallbackRecord record : mListingPreferenceCallbackRecords) {
+            record.mExecutor.execute(
+                    () ->
+                            record.mRouteListingPreferenceCallback.onRouteListingPreferenceChanged(
+                                    preference));
+        }
+    }
+
     private void notifyTransfer(RoutingController oldController, RoutingController newController) {
         for (TransferCallbackRecord record : mTransferCallbackRecords) {
             record.mExecutor.execute(
@@ -1300,6 +1163,12 @@
         }
     }
 
+    private void notifyRequestFailed(int reason) {
+        for (TransferCallbackRecord record : mTransferCallbackRecords) {
+            record.mExecutor.execute(() -> record.mTransferCallback.onRequestFailed(reason));
+        }
+    }
+
     private void notifyStop(RoutingController controller) {
         for (TransferCallbackRecord record : mTransferCallbackRecords) {
             record.mExecutor.execute(() -> record.mTransferCallback.onStop(controller));
@@ -1364,6 +1233,12 @@
         public void onPreferredFeaturesChanged(@NonNull List<String> preferredFeatures) {}
     }
 
+    /** @hide */
+    public abstract static class RouteListingPreferenceCallback {
+        /** @hide */
+        public void onRouteListingPreferenceChanged(@Nullable RouteListingPreference preference) {}
+    }
+
     /** Callback for receiving events on media transfer. */
     public abstract static class TransferCallback {
         /**
@@ -1400,6 +1275,15 @@
          * @param controller the controller that controlled the stopped media routing
          */
         public void onStop(@NonNull RoutingController controller) {}
+
+        /**
+         * Called when a routing request fails.
+         *
+         * @param reason Reason for failure as per {@link
+         *     android.media.MediaRoute2ProviderService.Reason}
+         * @hide
+         */
+        public void onRequestFailed(int reason) {}
     }
 
     /**
@@ -1604,7 +1488,7 @@
         /**
          * Selects a route for the remote session. After a route is selected, the media is expected
          * to be played to the all the selected routes. This is different from {@link
-         * MediaRouter2#transferTo(MediaRoute2Info)} transferring to a route}, where the media is
+         * MediaRouter2#transferTo(MediaRoute2Info) transferring to a route}, where the media is
          * expected to 'move' from one route to another.
          *
          * <p>The given route must satisfy all of the following conditions:
@@ -1640,22 +1524,7 @@
                 return;
             }
 
-            if (isSystemRouter()) {
-                sManager.selectRoute(getRoutingSessionInfo(), route);
-                return;
-            }
-
-            MediaRouter2Stub stub;
-            synchronized (mLock) {
-                stub = mStub;
-            }
-            if (stub != null) {
-                try {
-                    mMediaRouterService.selectRouteWithRouter2(stub, getId(), route);
-                } catch (RemoteException ex) {
-                    Log.e(TAG, "Unable to select route for session.", ex);
-                }
-            }
+            mImpl.selectRoute(route, getRoutingSessionInfo());
         }
 
         /**
@@ -1694,22 +1563,7 @@
                 return;
             }
 
-            if (isSystemRouter()) {
-                sManager.deselectRoute(getRoutingSessionInfo(), route);
-                return;
-            }
-
-            MediaRouter2Stub stub;
-            synchronized (mLock) {
-                stub = mStub;
-            }
-            if (stub != null) {
-                try {
-                    mMediaRouterService.deselectRouteWithRouter2(stub, getId(), route);
-                } catch (RemoteException ex) {
-                    Log.e(TAG, "Unable to deselect route from session.", ex);
-                }
-            }
+            mImpl.deselectRoute(route, getRoutingSessionInfo());
         }
 
         /**
@@ -1769,22 +1623,7 @@
                 return;
             }
 
-            if (isSystemRouter()) {
-                sManager.setSessionVolume(getRoutingSessionInfo(), volume);
-                return;
-            }
-
-            MediaRouter2Stub stub;
-            synchronized (mLock) {
-                stub = mStub;
-            }
-            if (stub != null) {
-                try {
-                    mMediaRouterService.setSessionVolumeWithRouter2(stub, getId(), volume);
-                } catch (RemoteException ex) {
-                    Log.e(TAG, "setVolume: Failed to deliver request.", ex);
-                }
-            }
+            mImpl.setSessionVolume(volume, getRoutingSessionInfo());
         }
 
         /**
@@ -1839,41 +1678,7 @@
                 mState = CONTROLLER_STATE_RELEASED;
             }
 
-            if (isSystemRouter()) {
-                sManager.releaseSession(getRoutingSessionInfo());
-                return;
-            }
-
-            synchronized (mLock) {
-                mNonSystemRoutingControllers.remove(getId(), this);
-
-                if (shouldReleaseSession && mStub != null) {
-                    try {
-                        mMediaRouterService.releaseSessionWithRouter2(mStub, getId());
-                    } catch (RemoteException ex) {
-                        ex.rethrowFromSystemServer();
-                    }
-                }
-
-                if (shouldNotifyStop) {
-                    mHandler.sendMessage(
-                            obtainMessage(
-                                    MediaRouter2::notifyStop,
-                                    MediaRouter2.this,
-                                    RoutingController.this));
-                }
-
-                if (mRouteCallbackRecords.isEmpty()
-                        && mNonSystemRoutingControllers.isEmpty()
-                        && mStub != null) {
-                    try {
-                        mMediaRouterService.unregisterRouter2(mStub);
-                    } catch (RemoteException ex) {
-                        ex.rethrowFromSystemServer();
-                    }
-                    mStub = null;
-                }
-            }
+            mImpl.releaseSession(shouldReleaseSession, shouldNotifyStop, this);
         }
 
         @Override
@@ -1916,20 +1721,6 @@
             }
         }
 
-        private List<MediaRoute2Info> getRoutesWithIds(List<String> routeIds) {
-            if (isSystemRouter()) {
-                return getRoutes().stream()
-                        .filter(r -> routeIds.contains(r.getId()))
-                        .collect(Collectors.toList());
-            }
-
-            synchronized (mLock) {
-                return routeIds.stream()
-                        .map(mRoutes::get)
-                        .filter(Objects::nonNull)
-                        .collect(Collectors.toList());
-            }
-        }
     }
 
     class SystemRoutingController extends RoutingController {
@@ -1986,6 +1777,35 @@
         }
     }
 
+    private static final class RouteListingPreferenceCallbackRecord {
+        public final Executor mExecutor;
+        public final RouteListingPreferenceCallback mRouteListingPreferenceCallback;
+
+        /* package */ RouteListingPreferenceCallbackRecord(
+                @NonNull Executor executor,
+                @NonNull RouteListingPreferenceCallback routeListingPreferenceCallback) {
+            mExecutor = executor;
+            mRouteListingPreferenceCallback = routeListingPreferenceCallback;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj) {
+                return true;
+            }
+            if (!(obj instanceof RouteListingPreferenceCallbackRecord)) {
+                return false;
+            }
+            return mRouteListingPreferenceCallback
+                    == ((RouteListingPreferenceCallbackRecord) obj).mRouteListingPreferenceCallback;
+        }
+
+        @Override
+        public int hashCode() {
+            return mRouteListingPreferenceCallback.hashCode();
+        }
+    }
+
     static final class TransferCallbackRecord {
         public final Executor mExecutor;
         public final TransferCallback mTransferCallback;
@@ -2118,24 +1938,585 @@
         }
     }
 
-    // Note: All methods are run on main thread.
-    class ManagerCallback implements MediaRouter2Manager.Callback {
+    /**
+     * Provides a common interface for separating {@link LocalMediaRouter2Impl local} and {@link
+     * ProxyMediaRouter2Impl proxy} {@link MediaRouter2} instances.
+     */
+    private interface MediaRouter2Impl {
+        void startScan();
 
-        @Override
-        public void onRoutesUpdated() {
-            updateAllRoutesFromManager();
+        void stopScan();
+
+        String getClientPackageName();
+
+        String getPackageName();
+
+        RoutingSessionInfo getSystemSessionInfo();
+
+        RouteCallbackRecord createRouteCallbackRecord(
+                @NonNull @CallbackExecutor Executor executor,
+                @NonNull RouteCallback routeCallback,
+                @NonNull RouteDiscoveryPreference preference);
+
+        void registerRouteCallback();
+
+        void unregisterRouteCallback();
+
+        List<MediaRoute2Info> getAllRoutes();
+
+        void setOnGetControllerHintsListener(OnGetControllerHintsListener listener);
+
+        void transferTo(MediaRoute2Info route);
+
+        void stop();
+
+        void transfer(RoutingSessionInfo sessionInfo, MediaRoute2Info route);
+
+        List<RoutingController> getControllers();
+
+        void setRouteVolume(MediaRoute2Info route, int volume);
+
+        List<MediaRoute2Info> filterRoutesWithIndividualPreference(
+                List<MediaRoute2Info> routes, RouteDiscoveryPreference discoveryPreference);
+
+        // RoutingController methods.
+        void setSessionVolume(int volume, RoutingSessionInfo sessionInfo);
+
+        void selectRoute(MediaRoute2Info route, RoutingSessionInfo sessionInfo);
+
+        void deselectRoute(MediaRoute2Info route, RoutingSessionInfo sessionInfo);
+
+        void releaseSession(
+                boolean shouldReleaseSession,
+                boolean shouldNotifyStop,
+                RoutingController controller);
+
+    }
+
+    /**
+     * Implements logic specific to proxy {@link MediaRouter2} instances.
+     *
+     * <p>A proxy {@link MediaRouter2} instance controls the routing of a different package and can
+     * be obtained by calling {@link #getInstance(Context, String)}. This requires {@link
+     * Manifest.permission#MEDIA_CONTENT_CONTROL MEDIA_CONTENT_CONTROL} permission.
+     *
+     * <p>Proxy routers behave differently than local routers. See {@link #getInstance(Context,
+     * String)} for more details.
+     */
+    private class ProxyMediaRouter2Impl implements MediaRouter2Impl {
+        // Fields originating from MediaRouter2Manager.
+        private final MediaRouter2Manager mManager;
+        private final IMediaRouter2Manager.Stub mClient;
+        private final CopyOnWriteArrayList<MediaRouter2Manager.TransferRequest>
+                mTransferRequests = new CopyOnWriteArrayList<>();
+
+        // Fields originating from MediaRouter2.
+        @NonNull private final String mClientPackageName;
+
+        // TODO(b/281072508): Implement scan request counting when MediaRouter2Manager is removed.
+        private final AtomicBoolean mIsScanning = new AtomicBoolean(/* initialValue= */ false);
+
+        ProxyMediaRouter2Impl(@NonNull Context context, @NonNull String clientPackageName) {
+            mManager = MediaRouter2Manager.getInstance(context.getApplicationContext());
+            mClientPackageName = clientPackageName;
+            mClient = new Client();
+
+            try {
+                mMediaRouterService.registerManager(
+                        mClient, context.getApplicationContext().getPackageName());
+            } catch (RemoteException ex) {
+                throw ex.rethrowFromSystemServer();
+            }
+
+            mDiscoveryPreference = RouteDiscoveryPreference.EMPTY;
         }
 
         @Override
-        public void onTransferred(
+        public void startScan() {
+            if (!mIsScanning.getAndSet(true)) {
+                mManager.registerScanRequest();
+            }
+        }
+
+        @Override
+        public void stopScan() {
+            if (mIsScanning.getAndSet(false)) {
+                mManager.unregisterScanRequest();
+            }
+        }
+
+        @Override
+        public String getClientPackageName() {
+            return mClientPackageName;
+        }
+
+        /**
+         * Returns {@code null}. This refers to the package name of the caller app, which is only
+         * relevant for local routers.
+         */
+        @Override
+        public String getPackageName() {
+            return null;
+        }
+
+        @Override
+        public RoutingSessionInfo getSystemSessionInfo() {
+            RoutingSessionInfo result;
+            try {
+                result =
+                        mMediaRouterService.getSystemSessionInfoForPackage(
+                                mClient, mClientPackageName);
+            } catch (RemoteException ex) {
+                throw ex.rethrowFromSystemServer();
+            }
+            return ensureClientPackageNameForSystemSession(result);
+        }
+
+        /**
+         * {@link RouteDiscoveryPreference Discovery preferences} are ignored for proxy routers, as
+         * their callbacks should receive events related to the media app's preferences. This is
+         * equivalent to setting {@link RouteDiscoveryPreference#EMPTY empty preferences}.
+         */
+        @Override
+        public RouteCallbackRecord createRouteCallbackRecord(
+                Executor executor,
+                RouteCallback routeCallback,
+                RouteDiscoveryPreference preference) {
+            return new RouteCallbackRecord(executor, routeCallback, RouteDiscoveryPreference.EMPTY);
+        }
+
+        /**
+         * No-op. Only local routers communicate directly with {@link
+         * com.android.server.media.MediaRouter2ServiceImpl MediaRouter2ServiceImpl} and modify
+         * {@link RouteDiscoveryPreference}. Proxy routers receive callbacks from {@link
+         * MediaRouter2Manager}.
+         */
+        @Override
+        public void registerRouteCallback() {
+            // Do nothing.
+        }
+
+        /** No-op. See {@link ProxyMediaRouter2Impl#registerRouteCallback()}. */
+        @Override
+        public void unregisterRouteCallback() {
+            // Do nothing.
+        }
+
+        /** Gets the list of all discovered routes. */
+        @Override
+        public List<MediaRoute2Info> getAllRoutes() {
+            synchronized (mLock) {
+                return new ArrayList<>(mRoutes.values());
+            }
+        }
+
+        /** No-op. Controller hints can only be provided by the media app through a local router. */
+        @Override
+        public void setOnGetControllerHintsListener(OnGetControllerHintsListener listener) {
+            // Do nothing.
+        }
+
+        /**
+         * Transfers the current {@link RoutingSessionInfo routing session} associated with the
+         * router's {@link #mClientPackageName client package name} to a specified {@link
+         * MediaRoute2Info route}.
+         *
+         * <p>This method is equivalent to {@link #transfer(RoutingSessionInfo, MediaRoute2Info)},
+         * except that the {@link RoutingSessionInfo routing session} is resolved based on the
+         * router's {@link #mClientPackageName client package name}.
+         *
+         * @param route The route to transfer to.
+         */
+        @Override
+        public void transferTo(MediaRoute2Info route) {
+            Objects.requireNonNull(route, "route must not be null");
+
+            List<RoutingSessionInfo> sessionInfos = getRoutingSessions();
+            RoutingSessionInfo targetSession = sessionInfos.get(sessionInfos.size() - 1);
+            transfer(targetSession, route);
+        }
+
+        @Override
+        public void stop() {
+            List<RoutingSessionInfo> sessionInfos = getRoutingSessions();
+            RoutingSessionInfo sessionToRelease = sessionInfos.get(sessionInfos.size() - 1);
+            releaseSession(sessionToRelease);
+        }
+
+        /**
+         * Transfers a {@link RoutingSessionInfo routing session} to a {@link MediaRoute2Info
+         * route}.
+         *
+         * <p>{@link #onTransferred} is called on success or {@link #onTransferFailed} is called if
+         * the request fails.
+         *
+         * <p>This method will default for in-session transfer if the {@link MediaRoute2Info route}
+         * is a {@link RoutingSessionInfo#getTransferableRoutes() transferable route}. Otherwise, it
+         * will attempt an out-of-session transfer.
+         *
+         * @param sessionInfo The {@link RoutingSessionInfo routing session} to transfer.
+         * @param route The {@link MediaRoute2Info route} to transfer to.
+         * @see #transferToRoute(RoutingSessionInfo, MediaRoute2Info)
+         * @see #requestCreateSession(RoutingSessionInfo, MediaRoute2Info)
+         */
+        @Override
+        public void transfer(
+                @NonNull RoutingSessionInfo sessionInfo, @NonNull MediaRoute2Info route) {
+            Objects.requireNonNull(sessionInfo, "sessionInfo must not be null");
+            Objects.requireNonNull(route, "route must not be null");
+
+            Log.v(
+                    TAG,
+                    "Transferring routing session. session= " + sessionInfo + ", route=" + route);
+
+            boolean isUnknownRoute;
+            synchronized (mLock) {
+                isUnknownRoute = !mRoutes.containsKey(route.getId());
+            }
+
+            if (isUnknownRoute) {
+                Log.w(TAG, "transfer: Ignoring an unknown route id=" + route.getId());
+                this.onTransferFailed(sessionInfo, route);
+                return;
+            }
+
+            if (sessionInfo.getTransferableRoutes().contains(route.getId())) {
+                transferToRoute(sessionInfo, route);
+            } else {
+                requestCreateSession(sessionInfo, route);
+            }
+        }
+
+        /**
+         * Requests an in-session transfer of a {@link RoutingSessionInfo routing session} to a
+         * {@link MediaRoute2Info route}.
+         *
+         * <p>The provided {@link MediaRoute2Info route} must be listed in the {@link
+         * RoutingSessionInfo routing session's} {@link RoutingSessionInfo#getTransferableRoutes()
+         * transferable routes list}. Otherwise, the request will fail.
+         *
+         * <p>Use {@link #requestCreateSession(RoutingSessionInfo, MediaRoute2Info)} to request
+         * an out-of-session transfer.
+         *
+         * @param session The {@link RoutingSessionInfo routing session} to transfer.
+         * @param route The {@link MediaRoute2Info route} to transfer to. Must be one of the {@link
+         *     RoutingSessionInfo routing session's} {@link
+         *     RoutingSessionInfo#getTransferableRoutes() transferable routes}.
+         */
+        private void transferToRoute(
+                @NonNull RoutingSessionInfo session, @NonNull MediaRoute2Info route) {
+            int requestId = createTransferRequest(session, route);
+
+            try {
+                mMediaRouterService.transferToRouteWithManager(
+                        mClient, requestId, session.getId(), route);
+            } catch (RemoteException ex) {
+                throw ex.rethrowFromSystemServer();
+            }
+        }
+
+        /**
+         * Requests an out-of-session transfer of a {@link RoutingSessionInfo routing session} to a
+         * {@link MediaRoute2Info route}.
+         *
+         * <p>This request creates a new {@link RoutingSessionInfo routing session} regardless of
+         * whether the {@link MediaRoute2Info route} is one of the {@link RoutingSessionInfo current
+         * session's} {@link RoutingSessionInfo#getTransferableRoutes() transferable routes}.
+         *
+         * <p>Use {@link #transferToRoute(RoutingSessionInfo, MediaRoute2Info)} to request an
+         * in-session transfer.
+         *
+         * @param oldSession The {@link RoutingSessionInfo routing session} to transfer.
+         * @param route The {@link MediaRoute2Info route} to transfer to.
+         */
+        private void requestCreateSession(
+                @NonNull RoutingSessionInfo oldSession, @NonNull MediaRoute2Info route) {
+            if (TextUtils.isEmpty(oldSession.getClientPackageName())) {
+                Log.w(TAG, "requestCreateSession: Can't create a session without package name.");
+                this.onTransferFailed(oldSession, route);
+                return;
+            }
+
+            int requestId = createTransferRequest(oldSession, route);
+
+            try {
+                mMediaRouterService.requestCreateSessionWithManager(
+                        mClient, requestId, oldSession, route);
+            } catch (RemoteException ex) {
+                throw ex.rethrowFromSystemServer();
+            }
+        }
+
+        @Override
+        public List<RoutingController> getControllers() {
+            List<RoutingController> result = new ArrayList<>();
+
+            /* Unlike local MediaRouter2 instances, controller instances cannot be kept because
+            transfer events initiated from other apps will not come through manager.*/
+            List<RoutingSessionInfo> sessions = getRoutingSessions();
+            for (RoutingSessionInfo session : sessions) {
+                RoutingController controller;
+                if (session.isSystemSession()) {
+                    mSystemController.setRoutingSessionInfo(session);
+                    controller = mSystemController;
+                } else {
+                    controller = new RoutingController(session);
+                }
+                result.add(controller);
+            }
+            return result;
+        }
+
+        /**
+         * Requests a volume change for a {@link MediaRoute2Info route}.
+         *
+         * <p>It may have no effect if the {@link MediaRoute2Info route} is not currently selected.
+         *
+         * @param volume The desired volume value between 0 and {@link
+         *     MediaRoute2Info#getVolumeMax()} (inclusive).
+         */
+        @Override
+        public void setRouteVolume(@NonNull MediaRoute2Info route, int volume) {
+            if (route.getVolumeHandling() == MediaRoute2Info.PLAYBACK_VOLUME_FIXED) {
+                Log.w(TAG, "setRouteVolume: the route has fixed volume. Ignoring.");
+                return;
+            }
+            if (volume < 0 || volume > route.getVolumeMax()) {
+                Log.w(TAG, "setRouteVolume: the target volume is out of range. Ignoring");
+                return;
+            }
+
+            try {
+                int requestId = mNextRequestId.getAndIncrement();
+                mMediaRouterService.setRouteVolumeWithManager(mClient, requestId, route, volume);
+            } catch (RemoteException ex) {
+                throw ex.rethrowFromSystemServer();
+            }
+        }
+
+        /**
+         * Requests a volume change for a {@link RoutingSessionInfo routing session}.
+         *
+         * @param volume The desired volume value between 0 and {@link
+         *     RoutingSessionInfo#getVolumeMax()} (inclusive).
+         */
+        @Override
+        public void setSessionVolume(int volume, RoutingSessionInfo sessionInfo) {
+            Objects.requireNonNull(sessionInfo, "sessionInfo must not be null");
+
+            if (sessionInfo.getVolumeHandling() == MediaRoute2Info.PLAYBACK_VOLUME_FIXED) {
+                Log.w(TAG, "setSessionVolume: the route has fixed volume. Ignoring.");
+                return;
+            }
+            if (volume < 0 || volume > sessionInfo.getVolumeMax()) {
+                Log.w(TAG, "setSessionVolume: the target volume is out of range. Ignoring");
+                return;
+            }
+
+            try {
+                int requestId = mNextRequestId.getAndIncrement();
+                mMediaRouterService.setSessionVolumeWithManager(
+                        mClient, requestId, sessionInfo.getId(), volume);
+            } catch (RemoteException ex) {
+                throw ex.rethrowFromSystemServer();
+            }
+        }
+
+        /**
+         * Returns an exact copy of the routes. Individual {@link RouteDiscoveryPreference
+         * preferences} do not apply to proxy routers.
+         */
+        @Override
+        public List<MediaRoute2Info> filterRoutesWithIndividualPreference(
+                List<MediaRoute2Info> routes, RouteDiscoveryPreference discoveryPreference) {
+            // Individual discovery preferences do not apply for the system router.
+            return new ArrayList<>(routes);
+        }
+
+        /**
+         * Adds a {@linkplain MediaRoute2Info route} to the routing session's {@linkplain
+         * RoutingSessionInfo#getSelectedRoutes() selected route list}.
+         *
+         * <p>Upon success, {@link #onSessionUpdated(RoutingSessionInfo)} is invoked. Failed
+         * requests are silently ignored.
+         *
+         * <p>The {@linkplain RoutingSessionInfo#getSelectedRoutes() selected routes list} of a
+         * routing session contains the group of devices playing media for that {@linkplain
+         * RoutingSessionInfo session}.
+         *
+         * <p>The given route must not be already selected and must be listed in the session's
+         * {@linkplain RoutingSessionInfo#getSelectableRoutes() selectable routes}. Otherwise, the
+         * request will be ignored.
+         *
+         * <p>This method should not be confused with {@link #transfer(RoutingSessionInfo,
+         * MediaRoute2Info)}.
+         *
+         * @see RoutingSessionInfo#getSelectedRoutes()
+         * @see RoutingSessionInfo#getSelectableRoutes()
+         */
+        @Override
+        public void selectRoute(MediaRoute2Info route, RoutingSessionInfo sessionInfo) {
+            Objects.requireNonNull(sessionInfo, "sessionInfo must not be null");
+            Objects.requireNonNull(route, "route must not be null");
+
+            if (sessionInfo.getSelectedRoutes().contains(route.getId())) {
+                Log.w(TAG, "Ignoring selecting a route that is already selected. route=" + route);
+                return;
+            }
+
+            if (!sessionInfo.getSelectableRoutes().contains(route.getId())) {
+                Log.w(TAG, "Ignoring selecting a non-selectable route=" + route);
+                return;
+            }
+
+            try {
+                int requestId = mNextRequestId.getAndIncrement();
+                mMediaRouterService.selectRouteWithManager(
+                        mClient, requestId, sessionInfo.getId(), route);
+            } catch (RemoteException ex) {
+                throw ex.rethrowFromSystemServer();
+            }
+        }
+
+        /**
+         * Removes a route from a session's {@linkplain RoutingSessionInfo#getSelectedRoutes()
+         * selected routes list}. Calls {@link #onSessionUpdated(RoutingSessionInfo)} on success.
+         *
+         * <p>The given route must be selected and must be listed in the session's {@linkplain
+         * RoutingSessionInfo#getDeselectableRoutes() deselectable route list}. Otherwise, the
+         * request will be ignored.
+         *
+         * @see RoutingSessionInfo#getSelectedRoutes()
+         * @see RoutingSessionInfo#getDeselectableRoutes()
+         */
+        @Override
+        public void deselectRoute(MediaRoute2Info route, RoutingSessionInfo sessionInfo) {
+            Objects.requireNonNull(sessionInfo, "sessionInfo must not be null");
+            Objects.requireNonNull(route, "route must not be null");
+
+            if (!sessionInfo.getSelectedRoutes().contains(route.getId())) {
+                Log.w(TAG, "Ignoring deselecting a route that is not selected. route=" + route);
+                return;
+            }
+
+            if (!sessionInfo.getDeselectableRoutes().contains(route.getId())) {
+                Log.w(TAG, "Ignoring deselecting a non-deselectable route=" + route);
+                return;
+            }
+
+            try {
+                int requestId = mNextRequestId.getAndIncrement();
+                mMediaRouterService.deselectRouteWithManager(
+                        mClient, requestId, sessionInfo.getId(), route);
+            } catch (RemoteException ex) {
+                throw ex.rethrowFromSystemServer();
+            }
+        }
+
+        @Override
+        public void releaseSession(
+                boolean shouldReleaseSession,
+                boolean shouldNotifyStop,
+                RoutingController controller) {
+            releaseSession(controller.getRoutingSessionInfo());
+        }
+
+        /**
+         * Sets the routing session's {@linkplain RoutingSessionInfo#getClientPackageName() client
+         * package name} to {@link #mClientPackageName} if empty and returns the session.
+         *
+         * <p>This method must only be used for {@linkplain RoutingSessionInfo#isSystemSession()
+         * system routing sessions}.
+         */
+        private RoutingSessionInfo ensureClientPackageNameForSystemSession(
+                RoutingSessionInfo sessionInfo) {
+            if (!sessionInfo.isSystemSession()
+                    || !TextUtils.isEmpty(sessionInfo.getClientPackageName())) {
+                return sessionInfo;
+            }
+
+            return new RoutingSessionInfo.Builder(sessionInfo)
+                    .setClientPackageName(mClientPackageName)
+                    .build();
+        }
+
+        /**
+         * Requests the release of a {@linkplain RoutingSessionInfo routing session}. Calls {@link
+         * #onSessionReleasedOnHandler(RoutingSessionInfo)} on success.
+         *
+         * <p>Once released, a routing session ignores incoming requests.
+         */
+        private void releaseSession(@NonNull RoutingSessionInfo sessionInfo) {
+            Objects.requireNonNull(sessionInfo, "sessionInfo must not be null");
+
+            try {
+                int requestId = mNextRequestId.getAndIncrement();
+                mMediaRouterService.releaseSessionWithManager(
+                        mClient, requestId, sessionInfo.getId());
+            } catch (RemoteException ex) {
+                throw ex.rethrowFromSystemServer();
+            }
+        }
+
+        private int createTransferRequest(
+                @NonNull RoutingSessionInfo session, @NonNull MediaRoute2Info route) {
+            int requestId = mNextRequestId.getAndIncrement();
+            MediaRouter2Manager.TransferRequest transferRequest =
+                    new MediaRouter2Manager.TransferRequest(requestId, session, route);
+            mTransferRequests.add(transferRequest);
+
+            Message timeoutMessage =
+                    obtainMessage(
+                            ProxyMediaRouter2Impl::handleTransferTimeout, this, transferRequest);
+            mHandler.sendMessageDelayed(timeoutMessage, TRANSFER_TIMEOUT_MS);
+            return requestId;
+        }
+
+        private void handleTransferTimeout(MediaRouter2Manager.TransferRequest request) {
+            boolean removed = mTransferRequests.remove(request);
+            if (removed) {
+                this.onTransferFailed(request.mOldSessionInfo, request.mTargetRoute);
+            }
+        }
+
+        /**
+         * Returns the {@linkplain RoutingSessionInfo routing sessions} associated with {@link
+         * #mClientPackageName}. The first element of the returned list is the {@linkplain
+         * #getSystemSessionInfo() system routing session}.
+         *
+         * @see #getSystemSessionInfo()
+         */
+        @NonNull
+        private List<RoutingSessionInfo> getRoutingSessions() {
+            List<RoutingSessionInfo> sessions = new ArrayList<>();
+            sessions.add(getSystemSessionInfo());
+
+            List<RoutingSessionInfo> remoteSessions;
+            try {
+                remoteSessions = mMediaRouterService.getRemoteSessions(mClient);
+            } catch (RemoteException ex) {
+                throw ex.rethrowFromSystemServer();
+            }
+
+            for (RoutingSessionInfo sessionInfo : remoteSessions) {
+                if (TextUtils.equals(sessionInfo.getClientPackageName(), mClientPackageName)) {
+                    sessions.add(sessionInfo);
+                }
+            }
+            return sessions;
+        }
+
+        private void onTransferred(
                 @NonNull RoutingSessionInfo oldSession, @NonNull RoutingSessionInfo newSession) {
             if (!oldSession.isSystemSession()
-                    && !TextUtils.equals(mClientPackageName, oldSession.getClientPackageName())) {
+                    && !TextUtils.equals(
+                            getClientPackageName(), oldSession.getClientPackageName())) {
                 return;
             }
 
             if (!newSession.isSystemSession()
-                    && !TextUtils.equals(mClientPackageName, newSession.getClientPackageName())) {
+                    && !TextUtils.equals(
+                            getClientPackageName(), newSession.getClientPackageName())) {
                 return;
             }
 
@@ -2165,20 +2546,18 @@
             notifyTransfer(oldController, newController);
         }
 
-        @Override
-        public void onTransferFailed(
+        private void onTransferFailed(
                 @NonNull RoutingSessionInfo session, @NonNull MediaRoute2Info route) {
             if (!session.isSystemSession()
-                    && !TextUtils.equals(mClientPackageName, session.getClientPackageName())) {
+                    && !TextUtils.equals(getClientPackageName(), session.getClientPackageName())) {
                 return;
             }
             notifyTransferFailure(route);
         }
 
-        @Override
-        public void onSessionUpdated(@NonNull RoutingSessionInfo session) {
+        private void onSessionUpdated(@NonNull RoutingSessionInfo session) {
             if (!session.isSystemSession()
-                    && !TextUtils.equals(mClientPackageName, session.getClientPackageName())) {
+                    && !TextUtils.equals(getClientPackageName(), session.getClientPackageName())) {
                 return;
             }
 
@@ -2193,37 +2572,500 @@
             notifyControllerUpdated(controller);
         }
 
-        @Override
-        public void onSessionReleased(@NonNull RoutingSessionInfo session) {
-            if (session.isSystemSession()) {
-                Log.e(TAG, "onSessionReleased: Called on system session. Ignoring.");
+        private void onSessionCreatedOnHandler(
+                int requestId, @NonNull RoutingSessionInfo sessionInfo) {
+            MediaRouter2Manager.TransferRequest matchingRequest = null;
+            for (MediaRouter2Manager.TransferRequest request : mTransferRequests) {
+                if (request.mRequestId == requestId) {
+                    matchingRequest = request;
+                    break;
+                }
+            }
+
+            if (matchingRequest == null) {
                 return;
             }
 
-            if (!TextUtils.equals(mClientPackageName, session.getClientPackageName())) {
+            mTransferRequests.remove(matchingRequest);
+
+            MediaRoute2Info requestedRoute = matchingRequest.mTargetRoute;
+
+            if (!sessionInfo.getSelectedRoutes().contains(requestedRoute.getId())) {
+                Log.w(
+                        TAG,
+                        "The session does not contain the requested route. "
+                                + "(requestedRouteId="
+                                + requestedRoute.getId()
+                                + ", actualRoutes="
+                                + sessionInfo.getSelectedRoutes()
+                                + ")");
+                this.onTransferFailed(matchingRequest.mOldSessionInfo, requestedRoute);
+            } else if (!TextUtils.equals(
+                    requestedRoute.getProviderId(), sessionInfo.getProviderId())) {
+                Log.w(
+                        TAG,
+                        "The session's provider ID does not match the requested route's. "
+                                + "(requested route's providerId="
+                                + requestedRoute.getProviderId()
+                                + ", actual providerId="
+                                + sessionInfo.getProviderId()
+                                + ")");
+                this.onTransferFailed(matchingRequest.mOldSessionInfo, requestedRoute);
+            } else {
+                this.onTransferred(matchingRequest.mOldSessionInfo, sessionInfo);
+            }
+        }
+
+        private void onSessionUpdatedOnHandler(@NonNull RoutingSessionInfo sessionInfo) {
+            for (MediaRouter2Manager.TransferRequest request : mTransferRequests) {
+                String sessionId = request.mOldSessionInfo.getId();
+                if (!TextUtils.equals(sessionId, sessionInfo.getId())) {
+                    continue;
+                }
+                if (sessionInfo.getSelectedRoutes().contains(request.mTargetRoute.getId())) {
+                    mTransferRequests.remove(request);
+                    this.onTransferred(request.mOldSessionInfo, sessionInfo);
+                    break;
+                }
+            }
+            this.onSessionUpdated(sessionInfo);
+        }
+
+        private void onSessionReleasedOnHandler(@NonNull RoutingSessionInfo session) {
+            if (session.isSystemSession()) {
+                Log.e(TAG, "onSessionReleasedOnHandler: Called on system session. Ignoring.");
+                return;
+            }
+
+            if (!TextUtils.equals(getClientPackageName(), session.getClientPackageName())) {
                 return;
             }
 
             notifyStop(new RoutingController(session, RoutingController.CONTROLLER_STATE_RELEASED));
         }
 
-        @Override
-        public void onDiscoveryPreferenceChanged(
-                @NonNull String packageName, @NonNull RouteDiscoveryPreference preference) {
-            if (!TextUtils.equals(mClientPackageName, packageName)) {
+        private void onDiscoveryPreferenceChangedOnHandler(
+                @NonNull String packageName, @Nullable RouteDiscoveryPreference preference) {
+            if (!TextUtils.equals(getClientPackageName(), packageName)) {
+                return;
+            }
+
+            if (preference == null) {
+                return;
+            }
+            synchronized (mLock) {
+                if (Objects.equals(preference, mDiscoveryPreference)) {
+                    return;
+                }
+                mDiscoveryPreference = preference;
+                updateFilteredRoutesLocked();
+            }
+            notifyPreferredFeaturesChanged(preference.getPreferredFeatures());
+        }
+
+        private void onRouteListingPreferenceChangedOnHandler(
+                @NonNull String packageName,
+                @Nullable RouteListingPreference routeListingPreference) {
+            if (!TextUtils.equals(getClientPackageName(), packageName)) {
                 return;
             }
 
             synchronized (mLock) {
-                mDiscoveryPreference = preference;
+                if (Objects.equals(mRouteListingPreference, routeListingPreference)) {
+                    return;
+                }
+
+                mRouteListingPreference = routeListingPreference;
             }
-            updateAllRoutesFromManager();
-            notifyPreferredFeaturesChanged(preference.getPreferredFeatures());
+
+            notifyRouteListingPreferenceUpdated(routeListingPreference);
+        }
+
+        private void onRoutesUpdatedOnHandler(@NonNull List<MediaRoute2Info> routes) {
+            synchronized (mLock) {
+                mRoutes.clear();
+                for (MediaRoute2Info route : routes) {
+                    mRoutes.put(route.getId(), route);
+                }
+                updateFilteredRoutesLocked();
+            }
+        }
+
+        private void onRequestFailedOnHandler(int requestId, int reason) {
+            MediaRouter2Manager.TransferRequest matchingRequest = null;
+            for (MediaRouter2Manager.TransferRequest request : mTransferRequests) {
+                if (request.mRequestId == requestId) {
+                    matchingRequest = request;
+                    break;
+                }
+            }
+
+            if (matchingRequest != null) {
+                mTransferRequests.remove(matchingRequest);
+                onTransferFailed(matchingRequest.mOldSessionInfo, matchingRequest.mTargetRoute);
+            } else {
+                notifyRequestFailed(reason);
+            }
+        }
+
+        private class Client extends IMediaRouter2Manager.Stub {
+
+            @Override
+            public void notifySessionCreated(int requestId, RoutingSessionInfo routingSessionInfo) {
+                mHandler.sendMessage(
+                        obtainMessage(
+                                ProxyMediaRouter2Impl::onSessionCreatedOnHandler,
+                                ProxyMediaRouter2Impl.this,
+                                requestId,
+                                routingSessionInfo));
+            }
+
+            @Override
+            public void notifySessionUpdated(RoutingSessionInfo routingSessionInfo) {
+                mHandler.sendMessage(
+                        obtainMessage(
+                                ProxyMediaRouter2Impl::onSessionUpdatedOnHandler,
+                                ProxyMediaRouter2Impl.this,
+                                routingSessionInfo));
+            }
+
+            @Override
+            public void notifySessionReleased(RoutingSessionInfo routingSessionInfo) {
+                mHandler.sendMessage(
+                        obtainMessage(
+                                ProxyMediaRouter2Impl::onSessionReleasedOnHandler,
+                                ProxyMediaRouter2Impl.this,
+                                routingSessionInfo));
+            }
+
+            @Override
+            public void notifyDiscoveryPreferenceChanged(
+                    String packageName, RouteDiscoveryPreference routeDiscoveryPreference) {
+                mHandler.sendMessage(
+                        obtainMessage(
+                                ProxyMediaRouter2Impl::onDiscoveryPreferenceChangedOnHandler,
+                                ProxyMediaRouter2Impl.this,
+                                packageName,
+                                routeDiscoveryPreference));
+            }
+
+            @Override
+            public void notifyRouteListingPreferenceChange(
+                    String packageName, RouteListingPreference routeListingPreference) {
+                mHandler.sendMessage(
+                        obtainMessage(
+                                ProxyMediaRouter2Impl::onRouteListingPreferenceChangedOnHandler,
+                                ProxyMediaRouter2Impl.this,
+                                packageName,
+                                routeListingPreference));
+            }
+
+            @Override
+            public void notifyRoutesUpdated(List<MediaRoute2Info> routes) {
+                mHandler.sendMessage(
+                        obtainMessage(
+                                ProxyMediaRouter2Impl::onRoutesUpdatedOnHandler,
+                                ProxyMediaRouter2Impl.this,
+                                routes));
+            }
+
+            @Override
+            public void notifyRequestFailed(int requestId, int reason) {
+                mHandler.sendMessage(
+                        obtainMessage(
+                                ProxyMediaRouter2Impl::onRequestFailedOnHandler,
+                                ProxyMediaRouter2Impl.this,
+                                requestId,
+                                reason));
+            }
+        }
+    }
+
+    /**
+     * Implements logic specific to local {@link MediaRouter2} instances.
+     *
+     * <p>Local routers allow an app to control its own routing without any special permissions.
+     * Apps can obtain an instance by calling {@link #getInstance(Context)}.
+     */
+    private class LocalMediaRouter2Impl implements MediaRouter2Impl {
+        private final String mPackageName;
+
+        LocalMediaRouter2Impl(@NonNull String packageName) {
+            mPackageName = packageName;
+        }
+
+        /**
+         * No-op. Local routers cannot explicitly control route scanning.
+         *
+         * <p>Local routers can control scanning indirectly through {@link
+         * #registerRouteCallback(Executor, RouteCallback, RouteDiscoveryPreference)}.
+         */
+        @Override
+        public void startScan() {
+            // Do nothing.
+        }
+
+        /**
+         * No-op. Local routers cannot explicitly control route scanning.
+         *
+         * <p>Local routers can control scanning indirectly through {@link
+         * #registerRouteCallback(Executor, RouteCallback, RouteDiscoveryPreference)}.
+         */
+        @Override
+        public void stopScan() {
+            // Do nothing.
+        }
+
+        /**
+         * Returns {@code null}. The client package name is only associated to proxy {@link
+         * MediaRouter2} instances.
+         */
+        @Override
+        public String getClientPackageName() {
+            return null;
         }
 
         @Override
-        public void onRequestFailed(int reason) {
-            // Does nothing.
+        public String getPackageName() {
+            return mPackageName;
         }
+
+        @Override
+        public RoutingSessionInfo getSystemSessionInfo() {
+            RoutingSessionInfo currentSystemSessionInfo = null;
+            try {
+                currentSystemSessionInfo = mMediaRouterService.getSystemSessionInfo();
+            } catch (RemoteException ex) {
+                ex.rethrowFromSystemServer();
+            }
+            return currentSystemSessionInfo;
+        }
+
+        @Override
+        public RouteCallbackRecord createRouteCallbackRecord(
+                Executor executor,
+                RouteCallback routeCallback,
+                RouteDiscoveryPreference preference) {
+            return new RouteCallbackRecord(executor, routeCallback, preference);
+        }
+
+        @Override
+        public void registerRouteCallback() {
+            synchronized (mLock) {
+                try {
+                    if (mStub == null) {
+                        MediaRouter2Stub stub = new MediaRouter2Stub();
+                        mMediaRouterService.registerRouter2(stub, mPackageName);
+                        mStub = stub;
+                    }
+
+                    if (updateDiscoveryPreferenceIfNeededLocked()) {
+                        mMediaRouterService.setDiscoveryRequestWithRouter2(
+                                mStub, mDiscoveryPreference);
+                    }
+                } catch (RemoteException ex) {
+                    ex.rethrowFromSystemServer();
+                }
+            }
+        }
+
+        @Override
+        public void unregisterRouteCallback() {
+            synchronized (mLock) {
+                if (mStub == null) {
+                    return;
+                }
+
+                try {
+                    if (updateDiscoveryPreferenceIfNeededLocked()) {
+                        mMediaRouterService.setDiscoveryRequestWithRouter2(
+                                mStub, mDiscoveryPreference);
+                    }
+
+                    if (mRouteCallbackRecords.isEmpty() && mNonSystemRoutingControllers.isEmpty()) {
+                        mMediaRouterService.unregisterRouter2(mStub);
+                        mStub = null;
+                    }
+                } catch (RemoteException ex) {
+                    Log.e(TAG, "unregisterRouteCallback: Unable to set discovery request.", ex);
+                }
+            }
+        }
+
+        /**
+         * Returns {@link Collections#emptyList()}. Local routes can only access routes related to
+         * their {@link RouteDiscoveryPreference} through {@link #getRoutes()}.
+         */
+        @Override
+        public List<MediaRoute2Info> getAllRoutes() {
+            return Collections.emptyList();
+        }
+
+        @Override
+        public void setOnGetControllerHintsListener(OnGetControllerHintsListener listener) {
+            mOnGetControllerHintsListener = listener;
+        }
+
+        @Override
+        public void transferTo(MediaRoute2Info route) {
+            Log.v(TAG, "Transferring to route: " + route);
+
+            boolean routeFound;
+            synchronized (mLock) {
+                // TODO: Check thread-safety
+                routeFound = mRoutes.containsKey(route.getId());
+            }
+            if (!routeFound) {
+                notifyTransferFailure(route);
+                return;
+            }
+
+            RoutingController controller = getCurrentController();
+            if (controller
+                    .getRoutingSessionInfo()
+                    .getTransferableRoutes()
+                    .contains(route.getId())) {
+                controller.transferToRoute(route);
+                return;
+            }
+
+            requestCreateController(controller, route, MANAGER_REQUEST_ID_NONE);
+        }
+
+        @Override
+        public void stop() {
+            getCurrentController().release();
+        }
+
+        /**
+         * No-op. Local routers cannot request transfers of specific {@link RoutingSessionInfo}.
+         * This operation is only available to proxy routers.
+         *
+         * <p>Local routers can only transfer the current {@link RoutingSessionInfo} using {@link
+         * #transferTo(MediaRoute2Info)}.
+         */
+        @Override
+        public void transfer(RoutingSessionInfo sessionInfo, MediaRoute2Info route) {
+            // Do nothing.
+        }
+
+        @Override
+        public List<RoutingController> getControllers() {
+            List<RoutingController> result = new ArrayList<>();
+
+            result.add(0, mSystemController);
+            synchronized (mLock) {
+                result.addAll(mNonSystemRoutingControllers.values());
+            }
+            return result;
+        }
+
+        /** No-op. Local routers cannot modify the volume of specific routes. */
+        @Override
+        public void setRouteVolume(MediaRoute2Info route, int volume) {
+            // Do nothing.
+            // If this API needs to be public, use IMediaRouterService#setRouteVolumeWithRouter2()
+        }
+
+        @Override
+        public void setSessionVolume(int volume, RoutingSessionInfo sessionInfo) {
+            MediaRouter2Stub stub;
+            synchronized (mLock) {
+                stub = mStub;
+            }
+            if (stub != null) {
+                try {
+                    mMediaRouterService.setSessionVolumeWithRouter2(
+                            stub, sessionInfo.getId(), volume);
+                } catch (RemoteException ex) {
+                    Log.e(TAG, "setVolume: Failed to deliver request.", ex);
+                }
+            }
+        }
+
+        @Override
+        public List<MediaRoute2Info> filterRoutesWithIndividualPreference(
+                List<MediaRoute2Info> routes, RouteDiscoveryPreference discoveryPreference) {
+            List<MediaRoute2Info> filteredRoutes = new ArrayList<>();
+            for (MediaRoute2Info route : routes) {
+                if (!route.hasAnyFeatures(discoveryPreference.getPreferredFeatures())) {
+                    continue;
+                }
+                if (!discoveryPreference.getAllowedPackages().isEmpty()
+                        && (route.getPackageName() == null
+                                || !discoveryPreference
+                                        .getAllowedPackages()
+                                        .contains(route.getPackageName()))) {
+                    continue;
+                }
+                filteredRoutes.add(route);
+            }
+            return filteredRoutes;
+        }
+
+        @Override
+        public void selectRoute(MediaRoute2Info route, RoutingSessionInfo sessionInfo) {
+            MediaRouter2Stub stub;
+            synchronized (mLock) {
+                stub = mStub;
+            }
+            if (stub != null) {
+                try {
+                    mMediaRouterService.selectRouteWithRouter2(stub, sessionInfo.getId(), route);
+                } catch (RemoteException ex) {
+                    Log.e(TAG, "Unable to select route for session.", ex);
+                }
+            }
+        }
+
+        @Override
+        public void deselectRoute(MediaRoute2Info route, RoutingSessionInfo sessionInfo) {
+            MediaRouter2Stub stub;
+            synchronized (mLock) {
+                stub = mStub;
+            }
+            if (stub != null) {
+                try {
+                    mMediaRouterService.deselectRouteWithRouter2(stub, sessionInfo.getId(), route);
+                } catch (RemoteException ex) {
+                    Log.e(TAG, "Unable to deselect route from session.", ex);
+                }
+            }
+        }
+
+        @Override
+        public void releaseSession(
+                boolean shouldReleaseSession,
+                boolean shouldNotifyStop,
+                RoutingController controller) {
+            synchronized (mLock) {
+                mNonSystemRoutingControllers.remove(controller.getId(), controller);
+
+                if (shouldReleaseSession && mStub != null) {
+                    try {
+                        mMediaRouterService.releaseSessionWithRouter2(mStub, controller.getId());
+                    } catch (RemoteException ex) {
+                        ex.rethrowFromSystemServer();
+                    }
+                }
+
+                if (shouldNotifyStop) {
+                    mHandler.sendMessage(
+                            obtainMessage(MediaRouter2::notifyStop, MediaRouter2.this, controller));
+                }
+
+                if (mRouteCallbackRecords.isEmpty()
+                        && mNonSystemRoutingControllers.isEmpty()
+                        && mStub != null) {
+                    try {
+                        mMediaRouterService.unregisterRouter2(mStub);
+                    } catch (RemoteException ex) {
+                        ex.rethrowFromSystemServer();
+                    }
+                    mStub = null;
+                }
+            }
+        }
+
     }
 }
diff --git a/media/java/android/media/OWNERS b/media/java/android/media/OWNERS
index 1f9a51d..6d6a9f8 100644
--- a/media/java/android/media/OWNERS
+++ b/media/java/android/media/OWNERS
@@ -8,4 +8,7 @@
 # go/android-fwk-media-solutions for info on areas of ownership.
 include platform/frameworks/av:/media/janitors/media_solutions_OWNERS
 
-per-file *Image* = file:/graphics/java/android/graphics/OWNERS
\ No newline at end of file
+per-file *Image* = file:/graphics/java/android/graphics/OWNERS
+
+# Haptics team also works on Ringtone
+per-file *Ringtone* = file:/services/core/java/com/android/server/vibrator/OWNERS
diff --git a/media/java/android/media/Ringtone.java b/media/java/android/media/Ringtone.java
index e78dc31..3a6b398 100644
--- a/media/java/android/media/Ringtone.java
+++ b/media/java/android/media/Ringtone.java
@@ -16,27 +16,36 @@
 
 package android.media;
 
+import android.Manifest;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ContentProvider;
 import android.content.ContentResolver;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.content.res.AssetFileDescriptor;
 import android.content.res.Resources.NotFoundException;
 import android.database.Cursor;
 import android.media.audiofx.HapticGenerator;
 import android.net.Uri;
 import android.os.Binder;
-import android.os.Build;
+import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.Trace;
+import android.os.VibrationEffect;
+import android.os.Vibrator;
 import android.provider.MediaStore;
 import android.provider.MediaStore.MediaColumns;
 import android.provider.Settings;
 import android.util.Log;
+
 import com.android.internal.annotations.VisibleForTesting;
+
 import java.io.IOException;
-import java.util.ArrayList;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 
 /**
  * Ringtone provides a quick method for playing a ringtone, notification, or
@@ -49,7 +58,39 @@
  */
 public class Ringtone {
     private static final String TAG = "Ringtone";
-    private static final boolean LOGD = true;
+
+    /**
+     * The ringtone should only play sound. Any vibration is managed externally.
+     * @hide
+     */
+    public static final int MEDIA_SOUND = 1;
+    /**
+     * The ringtone should only play vibration. Any sound is managed externally.
+     * Requires the {@link android.Manifest.permission#VIBRATE} permission.
+     * @hide
+     */
+    public static final int MEDIA_VIBRATION = 1 << 1;
+    /**
+     * The ringtone should play sound and vibration.
+     * @hide
+     */
+    public static final int MEDIA_SOUND_AND_VIBRATION = MEDIA_SOUND | MEDIA_VIBRATION;
+
+    // This is not a public value, because apps shouldn't enable "all" media - that wouldn't be
+    // safe if new media types were added.
+    static final int MEDIA_ALL = MEDIA_SOUND | MEDIA_VIBRATION;
+
+    /**
+     * Declares the types of media that this Ringtone is allowed to play.
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = "MEDIA_", value = {
+            MEDIA_SOUND,
+            MEDIA_VIBRATION,
+            MEDIA_SOUND_AND_VIBRATION,
+    })
+    public @interface RingtoneMedia {}
 
     private static final String[] MEDIA_COLUMNS = new String[] {
         MediaStore.Audio.Media._ID,
@@ -59,51 +100,68 @@
     private static final String MEDIA_SELECTION = MediaColumns.MIME_TYPE + " LIKE 'audio/%' OR "
             + MediaColumns.MIME_TYPE + " IN ('application/ogg', 'application/x-flac')";
 
-    // keep references on active Ringtones until stopped or completion listener called.
-    private static final ArrayList<Ringtone> sActiveRingtones = new ArrayList<Ringtone>();
-
     private final Context mContext;
+    private final Vibrator mVibrator;
     private final AudioManager mAudioManager;
     private VolumeShaper.Configuration mVolumeShaperConfig;
-    private VolumeShaper mVolumeShaper;
 
     /**
      * Flag indicating if we're allowed to fall back to remote playback using
-     * {@link #mRemotePlayer}. Typically this is false when we're the remote
+     * {@link #mRemoteRingtoneService}. Typically this is false when we're the remote
      * player and there is nobody else to delegate to.
      */
     private final boolean mAllowRemote;
-    private final IRingtonePlayer mRemotePlayer;
-    private final Binder mRemoteToken;
+    private final IRingtonePlayer mRemoteRingtoneService;
+    private final Injectables mInjectables;
 
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    private MediaPlayer mLocalPlayer;
-    private final MyOnCompletionListener mCompletionListener = new MyOnCompletionListener();
-    private HapticGenerator mHapticGenerator;
+    private final int mEnabledMedia;
 
-    @UnsupportedAppUsage
-    private Uri mUri;
+    private final Uri mUri;
     private String mTitle;
 
-    private AudioAttributes mAudioAttributes = new AudioAttributes.Builder()
-            .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
-            .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
-            .build();
+    private AudioAttributes mAudioAttributes;
+    private boolean mUseExactAudioAttributes;
     private boolean mPreferBuiltinDevice;
+    private RingtonePlayer mActivePlayer;
     // playback properties, use synchronized with mPlaybackSettingsLock
-    private boolean mIsLooping = false;
-    private float mVolume = 1.0f;
-    private boolean mHapticGeneratorEnabled = false;
+    private boolean mIsLooping;
+    private float mVolume;
+    private boolean mHapticGeneratorEnabled;
     private final Object mPlaybackSettingsLock = new Object();
+    private final VibrationEffect mVibrationEffect;
 
-    /** {@hide} */
-    @UnsupportedAppUsage
-    public Ringtone(Context context, boolean allowRemote) {
-        mContext = context;
-        mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
-        mAllowRemote = allowRemote;
-        mRemotePlayer = allowRemote ? mAudioManager.getRingtonePlayer() : null;
-        mRemoteToken = allowRemote ? new Binder() : null;
+    private Ringtone(Builder builder, @Ringtone.RingtoneMedia int effectiveEnabledMedia,
+            @NonNull AudioAttributes effectiveAudioAttributes,
+            @Nullable VibrationEffect effectiveVibrationEffect,
+            boolean effectiveHapticGeneratorEnabled) {
+        // Context
+        mContext = builder.mContext;
+        mInjectables = builder.mInjectables;
+        //mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+        mAudioManager = mContext.getSystemService(AudioManager.class);
+        mRemoteRingtoneService = builder.mAllowRemote ? mAudioManager.getRingtonePlayer() : null;
+        mVibrator = mContext.getSystemService(Vibrator.class);
+
+        // Local-only (not propagated to remote).
+        mPreferBuiltinDevice = builder.mPreferBuiltinDevice;  // System-only
+        mAllowRemote = (mRemoteRingtoneService != null);  // Always false for remote.
+
+        // Properties potentially propagated to remote player.
+        mEnabledMedia = effectiveEnabledMedia;
+        mUri = builder.mUri;
+        mVolumeShaperConfig = builder.mVolumeShaperConfig;
+        mVolume = builder.mInitialSoundVolume;
+        mIsLooping = builder.mLooping;
+        mVibrationEffect = effectiveVibrationEffect;
+        mAudioAttributes = effectiveAudioAttributes;
+        mUseExactAudioAttributes = builder.mUseExactAudioAttributes;
+        mHapticGeneratorEnabled = effectiveHapticGeneratorEnabled;
+    }
+
+    /** @hide */
+    @RingtoneMedia
+    public int getEnabledMedia() {
+        return mEnabledMedia;
     }
 
     /**
@@ -114,10 +172,15 @@
      */
     @Deprecated
     public void setStreamType(int streamType) {
-        PlayerBase.deprecateStreamTypeForPlayback(streamType, "Ringtone", "setStreamType()");
-        setAudioAttributes(new AudioAttributes.Builder()
+        setAudioAttributes(
+                getAudioAttributesForLegacyStreamType(streamType, "setStreamType()"));
+    }
+
+    private AudioAttributes getAudioAttributesForLegacyStreamType(int streamType, String originOp) {
+        PlayerBase.deprecateStreamTypeForPlayback(streamType, "Ringtone", originOp);
+        return new AudioAttributes.Builder()
                 .setInternalLegacyStreamType(streamType)
-                .build());
+                .build();
     }
 
     /**
@@ -138,23 +201,55 @@
      */
     public void setAudioAttributes(AudioAttributes attributes)
             throws IllegalArgumentException {
-        setAudioAttributesField(attributes);
-        // The audio attributes have to be set before the media player is prepared.
-        // Re-initialize it.
-        setUri(mUri, mVolumeShaperConfig);
-        createLocalMediaPlayer();
-    }
-
-    /**
-     * Same as {@link #setAudioAttributes(AudioAttributes)} except this one does not create
-     * the media player.
-     * @hide
-     */
-    public void setAudioAttributesField(@Nullable AudioAttributes attributes) {
+        // TODO: deprecate this method - it will be done with a builder.
         if (attributes == null) {
             throw new IllegalArgumentException("Invalid null AudioAttributes for Ringtone");
         }
         mAudioAttributes = attributes;
+        // Setting the audio attributes requires re-initializing the player.
+        if (mActivePlayer != null) {
+            // The audio attributes have to be set before the media player is prepared.
+            // Re-initialize it.
+            reinitializeActivePlayer();
+        }
+    }
+
+    /**
+     * Returns the vibration effect that this ringtone was created with, if vibration is enabled.
+     * Otherwise, returns null.
+     * @hide
+     */
+    @Nullable
+    public VibrationEffect getVibrationEffect() {
+        return mVibrationEffect;
+    }
+
+    /** @hide */
+    @VisibleForTesting
+    public boolean getPreferBuiltinDevice() {
+        return mPreferBuiltinDevice;
+    }
+
+    /** @hide */
+    @VisibleForTesting
+    public VolumeShaper.Configuration getVolumeShaperConfig() {
+        return mVolumeShaperConfig;
+    }
+
+    /**
+     * Returns whether this player is local only, or can defer to the remote player. The
+     * result may differ from the builder if there is no remote player available at all.
+     * @hide
+     */
+    @VisibleForTesting
+    public boolean isLocalOnly() {
+        return !mAllowRemote;
+    }
+
+    /** @hide */
+    @VisibleForTesting
+    public boolean isUsingRemotePlayer() {
+        return mActivePlayer instanceof RemoteRingtonePlayer;
     }
 
     /**
@@ -176,94 +271,98 @@
     }
 
     /**
-     * Sets the preferred device of the ringtong playback to the built-in device.
-     *
-     * @hide
-     */
-    public boolean preferBuiltinDevice(boolean enable) {
-        mPreferBuiltinDevice = enable;
-        if (mLocalPlayer == null) {
-            return true;
-        }
-        return mLocalPlayer.setPreferredDevice(getBuiltinDevice(mAudioManager));
-    }
-
-    /**
      * Creates a local media player for the ringtone using currently set attributes.
      * @return true if media player creation succeeded or is deferred,
      * false if it did not succeed and can't be tried remotely.
      * @hide
      */
-    public boolean createLocalMediaPlayer() {
-        Trace.beginSection("createLocalMediaPlayer");
-        if (mUri == null) {
-            Log.e(TAG, "Could not create media player as no URI was provided.");
-            return mAllowRemote && mRemotePlayer != null;
-        }
-        destroyLocalPlayer();
-        // try opening uri locally before delegating to remote player
-        mLocalPlayer = new MediaPlayer();
+    public boolean reinitializeActivePlayer() {
+        // Try creating a local media player, or fallback to creating a remote one.
+        Trace.beginSection("reinitializeActivePlayer");
         try {
-            mLocalPlayer.setDataSource(mContext, mUri);
-            mLocalPlayer.setAudioAttributes(mAudioAttributes);
-            mLocalPlayer.setPreferredDevice(
-                    mPreferBuiltinDevice ? getBuiltinDevice(mAudioManager) : null);
-            synchronized (mPlaybackSettingsLock) {
-                applyPlaybackProperties_sync();
+            if (mActivePlayer != null) {
+                // This would only happen if calling the deprecated setAudioAttributes after
+                // building the Ringtone.
+                stopAndReleaseActivePlayer();
             }
-            if (mVolumeShaperConfig != null) {
-                mVolumeShaper = mLocalPlayer.createVolumeShaper(mVolumeShaperConfig);
-            }
-            mLocalPlayer.prepare();
 
-        } catch (SecurityException | IOException e) {
-            destroyLocalPlayer();
-            if (!mAllowRemote) {
-                Log.w(TAG, "Remote playback not allowed: " + e);
-            }
-        }
+            boolean vibrationOnly = (mEnabledMedia & MEDIA_ALL) == MEDIA_VIBRATION;
+            // Vibration can come from the audio file if using haptic generator or if haptic
+            // channels are a possibility.
+            boolean maybeAudioVibration = mUri != null && mInjectables.isHapticPlaybackSupported()
+                    && (mHapticGeneratorEnabled || !mAudioAttributes.areHapticChannelsMuted());
 
-        if (LOGD) {
-            if (mLocalPlayer != null) {
-                Log.d(TAG, "Successfully created local player");
+            // VibrationEffect only, use the simplified player without checking for haptic channels.
+            if (vibrationOnly && !maybeAudioVibration && mVibrationEffect != null) {
+                mActivePlayer = new LocalRingtonePlayer.VibrationEffectPlayer(
+                        mVibrationEffect, mAudioAttributes, mVibrator, mIsLooping);
+                return true;
+            }
+
+            AudioDeviceInfo preferredDevice =
+                    mPreferBuiltinDevice ? getBuiltinDevice(mAudioManager) : null;
+            if (mUri != null) {
+                mActivePlayer = LocalRingtonePlayer.create(mContext, mAudioManager, mVibrator, mUri,
+                        mAudioAttributes, vibrationOnly, mVibrationEffect, mInjectables,
+                        mVolumeShaperConfig, preferredDevice, mHapticGeneratorEnabled, mIsLooping,
+                        mVolume);
             } else {
-                Log.d(TAG, "Problem opening; delegating to remote player");
+                // Using the remote player won't help play a null Uri. Revert straight to fallback.
+                // The vibration-only case was already covered above.
+                mActivePlayer = createFallbackRingtonePlayer();
+                // Fall through to attempting remote fallback play if null.
             }
+
+            if (mActivePlayer == null && mAllowRemote) {
+                mActivePlayer = new RemoteRingtonePlayer(mRemoteRingtoneService, mUri,
+                        mAudioAttributes, mUseExactAudioAttributes, mEnabledMedia, mVibrationEffect,
+                        mVolumeShaperConfig, mHapticGeneratorEnabled, mIsLooping, mVolume);
+            }
+
+            return mActivePlayer != null;
+        } finally {
+            Trace.endSection();
         }
-        Trace.endSection();
-        return mLocalPlayer != null || (mAllowRemote && mRemotePlayer != null);
+    }
+
+    @Nullable
+    private LocalRingtonePlayer createFallbackRingtonePlayer() {
+        int ringtoneType = RingtoneManager.getDefaultType(mUri);
+        if (ringtoneType != -1
+                && RingtoneManager.getActualDefaultRingtoneUri(mContext, ringtoneType) == null) {
+            Log.w(TAG, "not playing fallback for " + mUri);
+            return null;
+        }
+        // Default ringtone, try fallback ringtone.
+        try (AssetFileDescriptor afd = mContext.getResources().openRawResourceFd(
+                    com.android.internal.R.raw.fallbackring)) {
+            if (afd == null) {
+                Log.e(TAG, "Could not load fallback ringtone");
+                return null;
+            }
+
+            AudioDeviceInfo preferredDevice =
+                    mPreferBuiltinDevice ? getBuiltinDevice(mAudioManager) : null;
+            return LocalRingtonePlayer.createForFallback(mAudioManager, mVibrator, afd,
+                    mAudioAttributes, mVibrationEffect, mInjectables, mVolumeShaperConfig,
+                    preferredDevice, mIsLooping, mVolume);
+        } catch (NotFoundException nfe) {
+            Log.e(TAG, "Fallback ringtone does not exist");
+            return null;
+        } catch (IOException e) {
+            // As with the above messages, not including much information about the
+            // failure so as not to expose details of the fallback ringtone resource.
+            Log.e(TAG, "Exception reading fallback ringtone");
+            return null;
+        }
     }
 
     /**
      * Same as AudioManager.hasHapticChannels except it assumes an already created ringtone.
-     * If the ringtone has not been created, it will load based on URI provided at {@link #setUri}
-     * and if not URI has been set, it will assume no haptic channels are present.
      * @hide
      */
     public boolean hasHapticChannels() {
-        // FIXME: support remote player, or internalize haptic channels support and remove entirely.
-        try {
-            android.os.Trace.beginSection("Ringtone.hasHapticChannels");
-            if (mLocalPlayer != null) {
-                for(MediaPlayer.TrackInfo trackInfo : mLocalPlayer.getTrackInfo()) {
-                    if (trackInfo.hasHapticChannels()) {
-                        return true;
-                    }
-                }
-            }
-        } finally {
-            android.os.Trace.endSection();
-        }
-        return false;
-    }
-
-    /**
-     * Returns whether a local player has been created for this ringtone.
-     * @hide
-     */
-    @VisibleForTesting
-    public boolean hasLocalPlayer() {
-        return mLocalPlayer != null;
+        return (mActivePlayer == null) ? false : mActivePlayer.hasHapticChannels();
     }
 
     /**
@@ -282,7 +381,9 @@
     public void setLooping(boolean looping) {
         synchronized (mPlaybackSettingsLock) {
             mIsLooping = looping;
-            applyPlaybackProperties_sync();
+            if (mActivePlayer != null) {
+                mActivePlayer.setLooping(looping);
+            }
         }
     }
 
@@ -302,11 +403,21 @@
      *   corresponds to no attenuation being applied.
      */
     public void setVolume(float volume) {
+        // Ignore if sound not enabled.
+        if ((mEnabledMedia & MEDIA_SOUND) == 0) {
+            return;
+        }
+        if (volume < 0.0f) {
+            volume = 0.0f;
+        } else if (volume > 1.0f) {
+            volume = 1.0f;
+        }
+
         synchronized (mPlaybackSettingsLock) {
-            if (volume < 0.0f) { volume = 0.0f; }
-            if (volume > 1.0f) { volume = 1.0f; }
             mVolume = volume;
-            applyPlaybackProperties_sync();
+            if (mActivePlayer != null) {
+                mActivePlayer.setVolume(volume);
+            }
         }
     }
 
@@ -328,12 +439,14 @@
      * @see android.media.audiofx.HapticGenerator#isAvailable()
      */
     public boolean setHapticGeneratorEnabled(boolean enabled) {
-        if (!HapticGenerator.isAvailable()) {
+        if (!mInjectables.isHapticGeneratorAvailable()) {
             return false;
         }
         synchronized (mPlaybackSettingsLock) {
             mHapticGeneratorEnabled = enabled;
-            applyPlaybackProperties_sync();
+            if (mActivePlayer != null) {
+                mActivePlayer.setHapticGeneratorEnabled(enabled);
+            }
         }
         return true;
     }
@@ -349,32 +462,6 @@
     }
 
     /**
-     * Must be called synchronized on mPlaybackSettingsLock
-     */
-    private void applyPlaybackProperties_sync() {
-        if (mLocalPlayer != null) {
-            mLocalPlayer.setVolume(mVolume);
-            mLocalPlayer.setLooping(mIsLooping);
-            if (mHapticGenerator == null && mHapticGeneratorEnabled) {
-                mHapticGenerator = HapticGenerator.create(mLocalPlayer.getAudioSessionId());
-            }
-            if (mHapticGenerator != null) {
-                mHapticGenerator.setEnabled(mHapticGeneratorEnabled);
-            }
-        } else if (mAllowRemote && (mRemotePlayer != null)) {
-            try {
-                mRemotePlayer.setPlaybackProperties(
-                        mRemoteToken, mVolume, mIsLooping, mHapticGeneratorEnabled);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Problem setting playback properties: ", e);
-            }
-        } else {
-            Log.w(TAG,
-                    "Neither local nor remote player available when applying playback properties");
-        }
-    }
-
-    /**
      * Returns a human-presentable title for ringtone. Looks in media
      * content provider. If not in either, uses the filename
      *
@@ -456,39 +543,6 @@
         return title;
     }
 
-    /**
-     * Set {@link Uri} to be used for ringtone playback.
-     * {@link IRingtonePlayer}.
-     *
-     * @hide
-     */
-    @UnsupportedAppUsage
-    public void setUri(Uri uri) {
-        setUri(uri, null);
-    }
-
-    /**
-     * @hide
-     */
-    public void setVolumeShaperConfig(@Nullable VolumeShaper.Configuration volumeShaperConfig) {
-        mVolumeShaperConfig = volumeShaperConfig;
-    }
-
-    /**
-     * Set {@link Uri} to be used for ringtone playback. Attempts to open
-     * locally, otherwise will delegate playback to remote
-     * {@link IRingtonePlayer}. Add {@link VolumeShaper} if required.
-     *
-     * @hide
-     */
-    public void setUri(Uri uri, @Nullable VolumeShaper.Configuration volumeShaperConfig) {
-        mVolumeShaperConfig = volumeShaperConfig;
-        mUri = uri;
-        if (mUri == null) {
-            destroyLocalPlayer();
-        }
-    }
-
     /** {@hide} */
     @UnsupportedAppUsage
     public Uri getUri() {
@@ -499,36 +553,16 @@
      * Plays the ringtone.
      */
     public void play() {
-        if (mLocalPlayer != null) {
-            // Play ringtones if stream volume is over 0 or if it is a haptic-only ringtone
-            // (typically because ringer mode is vibrate).
-            if (mAudioManager.getStreamVolume(AudioAttributes.toLegacyStreamType(mAudioAttributes))
-                    != 0) {
-                startLocalPlayer();
-            } else if (!mAudioAttributes.areHapticChannelsMuted() && hasHapticChannels()) {
-                // is haptic only ringtone
-                startLocalPlayer();
+        if (mActivePlayer != null) {
+            if (mActivePlayer.play()) {
+                return;
+            } else {
+                // Discard active player: play() is only meant to be called once.
+                stopAndReleaseActivePlayer();
             }
-        } else if (mAllowRemote && (mRemotePlayer != null) && (mUri != null)) {
-            final Uri canonicalUri = mUri.getCanonicalUri();
-            final boolean looping;
-            final float volume;
-            synchronized (mPlaybackSettingsLock) {
-                looping = mIsLooping;
-                volume = mVolume;
-            }
-            try {
-                mRemotePlayer.playWithVolumeShaping(mRemoteToken, canonicalUri, mAudioAttributes,
-                        volume, looping, mVolumeShaperConfig);
-            } catch (RemoteException e) {
-                if (!playFallbackRingtone()) {
-                    Log.w(TAG, "Problem playing ringtone: " + e);
-                }
-            }
-        } else {
-            if (!playFallbackRingtone()) {
-                Log.w(TAG, "Neither local nor remote playback available");
-            }
+        }
+        if (!playFallbackRingtone()) {
+            Log.w(TAG, "Neither local nor remote playback available");
         }
     }
 
@@ -536,45 +570,13 @@
      * Stops a playing ringtone.
      */
     public void stop() {
-        if (mLocalPlayer != null) {
-            destroyLocalPlayer();
-        } else if (mAllowRemote && (mRemotePlayer != null)) {
-            try {
-                mRemotePlayer.stop(mRemoteToken);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Problem stopping ringtone: " + e);
-            }
-        }
+        stopAndReleaseActivePlayer();
     }
 
-    private void destroyLocalPlayer() {
-        if (mLocalPlayer != null) {
-            if (mHapticGenerator != null) {
-                mHapticGenerator.release();
-                mHapticGenerator = null;
-            }
-            mLocalPlayer.setOnCompletionListener(null);
-            mLocalPlayer.reset();
-            mLocalPlayer.release();
-            mLocalPlayer = null;
-            mVolumeShaper = null;
-            synchronized (sActiveRingtones) {
-                sActiveRingtones.remove(this);
-            }
-        }
-    }
-
-    private void startLocalPlayer() {
-        if (mLocalPlayer == null) {
-            return;
-        }
-        synchronized (sActiveRingtones) {
-            sActiveRingtones.add(this);
-        }
-        mLocalPlayer.setOnCompletionListener(mCompletionListener);
-        mLocalPlayer.start();
-        if (mVolumeShaper != null) {
-            mVolumeShaper.apply(VolumeShaper.Operation.PLAY);
+    private void stopAndReleaseActivePlayer() {
+        if (mActivePlayer != null) {
+            mActivePlayer.stopAndRelease();
+            mActivePlayer = null;
         }
     }
 
@@ -584,87 +586,489 @@
      * @return True if playing, false otherwise.
      */
     public boolean isPlaying() {
-        if (mLocalPlayer != null) {
-            return mLocalPlayer.isPlaying();
-        } else if (mAllowRemote && (mRemotePlayer != null)) {
-            try {
-                return mRemotePlayer.isPlaying(mRemoteToken);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Problem checking ringtone: " + e);
-                return false;
-            }
+        if (mActivePlayer != null) {
+            return mActivePlayer.isPlaying();
         } else {
-            Log.w(TAG, "Neither local nor remote playback available");
+            Log.w(TAG, "No active ringtone player");
             return false;
         }
     }
 
+    /**
+     * Fallback during the play stage rather than initialization, typically due to an issue
+     * communicating with the remote player.
+     */
     private boolean playFallbackRingtone() {
+        if (mActivePlayer != null) {
+            Log.wtf(TAG, "Playing fallback ringtone with another active player");
+            stopAndReleaseActivePlayer();
+        }
         int streamType = AudioAttributes.toLegacyStreamType(mAudioAttributes);
         if (mAudioManager.getStreamVolume(streamType) == 0) {
+            // TODO: Return true? If volume is off, this is a successful play.
             return false;
         }
-        int ringtoneType = RingtoneManager.getDefaultType(mUri);
-        if (ringtoneType != -1 &&
-                RingtoneManager.getActualDefaultRingtoneUri(mContext, ringtoneType) == null) {
-            Log.w(TAG, "not playing fallback for " + mUri);
+        mActivePlayer = createFallbackRingtonePlayer();
+        if (mActivePlayer == null) {
+            return false;  // the create method logs if it returns null.
+        } else if (mActivePlayer.play()) {
+            return true;
+        } else {
+            stopAndReleaseActivePlayer();
             return false;
         }
-        // Default ringtone, try fallback ringtone.
-        try {
-            AssetFileDescriptor afd = mContext.getResources().openRawResourceFd(
-                    com.android.internal.R.raw.fallbackring);
-            if (afd == null) {
-                Log.e(TAG, "Could not load fallback ringtone");
-                return false;
-            }
-            mLocalPlayer = new MediaPlayer();
-            if (afd.getDeclaredLength() < 0) {
-                mLocalPlayer.setDataSource(afd.getFileDescriptor());
-            } else {
-                mLocalPlayer.setDataSource(afd.getFileDescriptor(),
-                        afd.getStartOffset(),
-                        afd.getDeclaredLength());
-            }
-            mLocalPlayer.setAudioAttributes(mAudioAttributes);
-            synchronized (mPlaybackSettingsLock) {
-                applyPlaybackProperties_sync();
-            }
-            if (mVolumeShaperConfig != null) {
-                mVolumeShaper = mLocalPlayer.createVolumeShaper(mVolumeShaperConfig);
-            }
-            mLocalPlayer.prepare();
-            startLocalPlayer();
-            afd.close();
-        } catch (IOException ioe) {
-            destroyLocalPlayer();
-            Log.e(TAG, "Failed to open fallback ringtone");
-            return false;
-        } catch (NotFoundException nfe) {
-            Log.e(TAG, "Fallback ringtone does not exist");
-            return false;
-        }
-        return true;
     }
 
     void setTitle(String title) {
         mTitle = title;
     }
 
-    @Override
-    protected void finalize() {
-        if (mLocalPlayer != null) {
-            mLocalPlayer.release();
+    /**
+     * Build a {@link Ringtone} to easily play sounds for ringtones, alarms and notifications.
+     *
+     * TODO: when un-hide, deprecate Ringtone: setAudioAttributes, setLooping,
+     *       setHapticGeneratorEnabled (no-effect if MEDIA_VIBRATION),
+     *       static RingtoneManager.getRingtone.
+     * @hide
+     */
+    public static final class Builder {
+        private final Context mContext;
+        private final int mEnabledMedia;
+        private Uri mUri;
+        private final AudioAttributes mAudioAttributes;
+        private boolean mUseExactAudioAttributes = false;
+        // Not a static default since it doesn't really need to be in memory forever.
+        private Injectables mInjectables = new Injectables();
+        private VolumeShaper.Configuration mVolumeShaperConfig;
+        private boolean mPreferBuiltinDevice = false;
+        private boolean mAllowRemote = true;
+        private boolean mHapticGeneratorEnabled = false;
+        private float mInitialSoundVolume = 1.0f;
+        private boolean mLooping = false;
+        private VibrationEffect mVibrationEffect;
+
+        /**
+         * Constructs a builder to play the given media types from the mediaUri. If the mediaUri
+         * is null (for example, an unset-setting), then fallback logic will dictate what plays.
+         *
+         * <p>When built, if the ringtone is already known to be a no-op, such as explicitly
+         * silent, then the {@link #build} may return null.
+         *
+         * @param context The context for playing the ringtone.
+         * @param enabledMedia Which media to play. Media not included is implicitly muted. Device
+         *                     settings such as volume and vibrate-only may also affect which
+         *                     media is played.
+         * @param audioAttributes The attributes to use for playback, which affects the volumes and
+         *                        settings that are applied.
+         */
+        public Builder(@NonNull Context context, @RingtoneMedia int enabledMedia,
+                @NonNull AudioAttributes audioAttributes) {
+            mContext = context;
+            mEnabledMedia = enabledMedia;
+            mAudioAttributes = audioAttributes;
+        }
+
+        /**
+         * Inject test intercepters for static methods.
+         * @hide
+         */
+        @NonNull
+        public Builder setInjectables(Injectables injectables) {
+            mInjectables = injectables;
+            return this;
+        }
+
+        /**
+         * The uri for the ringtone media to play. This is typically a user's preference for the
+         * sound. If null, then it is treated as though the user's preference is unset and
+         * fallback behavior, such as using the default ringtone setting, are used instead.
+         *
+         * When sound media is enabled, this is assumed to be a sound URI.
+         */
+        @NonNull
+        public Builder setUri(@Nullable Uri uri) {
+            mUri = uri;
+            return this;
+        }
+
+        /**
+         * Sets the VibrationEffect to use if vibration is enabled on this ringtone. The caller
+         * should use {@link android.os.Vibrator#areVibrationFeaturesSupported} to ensure
+         * that the effect is usable on this device, otherwise system defaults will be used.
+         *
+         * <p>Vibration will only happen if the Builder was created with media type
+         * {@link Ringtone#MEDIA_VIBRATION} or {@link Ringtone#MEDIA_SOUND_AND_VIBRATION}, and
+         * the application has the {@link android.Manifest.permission#VIBRATE} permission.
+         *
+         * <p>If the Ringtone is looping when it is played, then the VibrationEffect will be
+         * modified to loop. Similarly, if the ringtone is not looping, a repeating
+         * VibrationEffect will be modified to be non-repeating when the ringtone is played. Calls
+         * to {@link Ringtone#setLooping} after the ringtone has started playing will stop a looping
+         * vibration, but has no effect otherwise: specifically it will not restart vibration.
+         */
+        @NonNull
+        public Builder setVibrationEffect(@NonNull VibrationEffect effect) {
+            mVibrationEffect = effect;
+            return this;
+        }
+
+        /**
+         * Sets whether the resulting ringtone should loop until {@link Ringtone#stop()} is called,
+         * or just play once.
+         */
+        @NonNull
+        public Builder setLooping(boolean looping) {
+            mLooping = looping;
+            return this;
+        }
+
+        /**
+         * Sets the VolumeShaper.Configuration to apply to the ringtone.
+         * @hide
+         */
+        @NonNull
+        public Builder setVolumeShaperConfig(
+                @Nullable VolumeShaper.Configuration volumeShaperConfig) {
+            mVolumeShaperConfig = volumeShaperConfig;
+            return this;
+        }
+
+        /**
+         * Whether to enable or disable the haptic generator.
+         * @hide
+         */
+        @NonNull
+        public Builder setEnableHapticGenerator(boolean enabled) {
+            // Note that this property is mutable (but deprecated) on the Ringtone class itself.
+            mHapticGeneratorEnabled = enabled;
+            return this;
+        }
+
+        /**
+         * Sets the initial sound volume for the ringtone.
+         */
+        @NonNull
+        public Builder setInitialSoundVolume(float initialSoundVolume) {
+            mInitialSoundVolume = initialSoundVolume;
+            return this;
+        }
+
+        /**
+         * Sets the preferred device of the ringtone playback to the built-in device. This is
+         * only for use by the system server with known-good Uris.
+         * @hide
+         */
+        @NonNull
+        public Builder setPreferBuiltinDevice() {
+            mPreferBuiltinDevice = true;
+            mAllowRemote = false;  // Already in system.
+            return this;
+        }
+
+        /**
+         * Indicates that {@link AudioAttributes#areHapticChannelsMuted()} on the builder's
+         * AudioAttributes should not be overridden. This is used to enable legacy behavior of
+         * calling {@link Ringtone#setAudioAttributes} on an already-created ringtone, and can in
+         * turn cause vibration during a "sound-only" session or can suppress audio-coupled
+         * haptics that would usually take priority (therefore potentially falling back to
+         * the VibrationEffect or system defaults).
+         *
+         * <p>Without this setting, the haptic channels will be automatically muted or not by the
+         * Ringtone according to whether vibration is enabled or not.
+         *
+         * <p>This is for internal-use only. New applications should configure the vibration
+         * behavior explicitly with the (TODO: future RingtoneSetting.setVibrationSource).
+         * Handling haptic channels outside Ringtone leads to extra loads of the sound uri.
+         * @hide
+         */
+        @NonNull
+        public Builder setUseExactAudioAttributes(boolean useExactAttrs) {
+            mUseExactAudioAttributes = useExactAttrs;
+            return this;
+        }
+
+        /**
+         * Prevent fallback to the remote service. This is primarily intended for use within the
+         * remote IRingtonePlayer service itself, to avoid loops.
+         * @hide
+         */
+        @NonNull
+        public Builder setLocalOnly() {
+            mAllowRemote = false;
+            return this;
+        }
+
+        private boolean isVibrationEnabledAndAvailable() {
+            if ((mEnabledMedia & MEDIA_VIBRATION) == 0) {
+                return false;
+            }
+            Vibrator vibrator = mContext.getSystemService(Vibrator.class);
+            if (!vibrator.hasVibrator()) {
+                return false;
+            }
+            if (mContext.checkSelfPermission(Manifest.permission.VIBRATE)
+                    != PackageManager.PERMISSION_GRANTED) {
+                Log.w(TAG, "Ringtone requests vibration enabled, but no VIBRATE permission");
+                return false;
+            }
+            return true;
+        }
+
+        /**
+         * Returns the built Ringtone, or null if there was a problem loading the Uri and there
+         * are no fallback options available.
+         */
+        @Nullable
+        public Ringtone build() {
+            @Ringtone.RingtoneMedia int effectiveEnabledMedia = mEnabledMedia;
+            VibrationEffect effectiveVibrationEffect = mVibrationEffect;
+
+            // Normalize media to that supported on this SDK level.
+            if (effectiveEnabledMedia != (effectiveEnabledMedia & MEDIA_ALL)) {
+                Log.e(TAG, "Unsupported media type: " + effectiveEnabledMedia);
+                effectiveEnabledMedia = effectiveEnabledMedia & MEDIA_ALL;
+            }
+            final boolean effectiveHapticGenerator;
+            final boolean hapticChannelsSupported;
+            AudioAttributes effectiveAudioAttributes = mAudioAttributes;
+            final boolean hapticChannelsMuted = mAudioAttributes.areHapticChannelsMuted();
+            if (!isVibrationEnabledAndAvailable()) {
+                // Vibration isn't active: turn off everything that might cause extra work.
+                effectiveEnabledMedia &= ~MEDIA_VIBRATION;
+                effectiveHapticGenerator = false;
+                effectiveVibrationEffect = null;
+                if (!mUseExactAudioAttributes && !hapticChannelsMuted) {
+                    effectiveAudioAttributes = new AudioAttributes.Builder(effectiveAudioAttributes)
+                            .setHapticChannelsMuted(true)
+                            .build();
+                }
+            } else {
+                // Vibration is active.
+                effectiveHapticGenerator =
+                        mHapticGeneratorEnabled && mInjectables.isHapticGeneratorAvailable();
+                hapticChannelsSupported = mInjectables.isHapticPlaybackSupported();
+                // Haptic channels are preferred if they are available, and not explicitly muted.
+                // We won't know if haptic channels are available until loading the media player,
+                // and since the media player needs to be reset to change audio attributes, then
+                // we proactively enable the channels - it won't matter if they aren't present.
+                if (!mUseExactAudioAttributes) {
+                    boolean shouldBeMuted = effectiveHapticGenerator || !hapticChannelsSupported;
+                    if (shouldBeMuted != hapticChannelsMuted) {
+                        effectiveAudioAttributes =
+                                new AudioAttributes.Builder(effectiveAudioAttributes)
+                                .setHapticChannelsMuted(shouldBeMuted)
+                                .build();
+                    }
+                }
+                // If no contextual vibration, then try loading the default one for the URI.
+                if (mVibrationEffect == null && mUri != null) {
+                    effectiveVibrationEffect = VibrationEffect.get(mUri, mContext);
+                }
+            }
+            try {
+                Ringtone ringtone = new Ringtone(this, effectiveEnabledMedia,
+                        effectiveAudioAttributes, effectiveVibrationEffect,
+                        effectiveHapticGenerator);
+                if (ringtone.reinitializeActivePlayer()) {
+                    return ringtone;
+                } else {
+                    Log.e(TAG, "Failed to open ringtone " + mUri);
+                    return null;
+                }
+            } catch (Exception ex) {
+                // Catching Exception isn't great, but was done in the old
+                // RingtoneManager.getRingtone and hides errors like DocumentsProvider throwing
+                // IllegalArgumentException instead of FileNotFoundException, and also robolectric
+                // failures when ShadowMediaPlayer wasn't pre-informed of the ringtone.
+                Log.e(TAG, "Failed while opening ringtone " + mUri, ex);
+                return null;
+            }
         }
     }
 
-    class MyOnCompletionListener implements MediaPlayer.OnCompletionListener {
-        @Override
-        public void onCompletion(MediaPlayer mp) {
-            synchronized (sActiveRingtones) {
-                sActiveRingtones.remove(Ringtone.this);
-            }
-            mp.setOnCompletionListener(null); // Help the Java GC: break the refcount cycle.
+    /**
+     * Play a specific ringtone. This interface is implemented by either local (this process) or
+     * proxied-remote playback via AudioManager.getRingtonePlayer, so that the caller
+     * (Ringtone class) can just use a single player after the initial creation.
+     * @hide
+     */
+    interface RingtonePlayer {
+        /**
+         * Start playing the ringtone, returning false if there was a problem that
+         * requires falling back to the fallback ringtone resource.
+         */
+        boolean play();
+        boolean isPlaying();
+        void stopAndRelease();
+
+        // Mutating playback methods.
+        void setPreferredDevice(@Nullable AudioDeviceInfo audioDeviceInfo);
+        void setLooping(boolean looping);
+        void setHapticGeneratorEnabled(boolean enabled);
+        void setVolume(float volume);
+
+        boolean hasHapticChannels();
+    }
+
+    /**
+     * Remote RingtonePlayer. All operations are delegated via the IRingtonePlayer interface, which
+     * should ultimately be backed by a RingtoneLocalPlayer within the system services.
+     */
+    static class RemoteRingtonePlayer implements RingtonePlayer {
+        private final IBinder mRemoteToken = new Binder();
+        private final IRingtonePlayer mRemoteRingtoneService;
+        private final Uri mCanonicalUri;
+        private final int mEnabledMedia;
+        private final VibrationEffect mVibrationEffect;
+        private final VolumeShaper.Configuration mVolumeShaperConfig;
+        private final AudioAttributes mAudioAttributes;
+        private final boolean mUseExactAudioAttributes;
+        private boolean mIsLooping;
+        private float mVolume;
+        private boolean mHapticGeneratorEnabled;
+
+        RemoteRingtonePlayer(@NonNull IRingtonePlayer remoteRingtoneService,
+                @NonNull Uri uri, @NonNull AudioAttributes audioAttributes,
+                boolean useExactAudioAttributes,
+                @RingtoneMedia int enabledMedia, @Nullable VibrationEffect vibrationEffect,
+                @Nullable VolumeShaper.Configuration volumeShaperConfig,
+                boolean hapticGeneratorEnabled, boolean initialIsLooping, float initialVolume) {
+            mRemoteRingtoneService = remoteRingtoneService;
+            mCanonicalUri = (uri == null) ? null : uri.getCanonicalUri();
+            mAudioAttributes = audioAttributes;
+            mUseExactAudioAttributes = useExactAudioAttributes;
+            mEnabledMedia = enabledMedia;
+            mVibrationEffect = vibrationEffect;
+            mVolumeShaperConfig = volumeShaperConfig;
+            mHapticGeneratorEnabled = hapticGeneratorEnabled;
+            mIsLooping = initialIsLooping;
+            mVolume = initialVolume;
         }
+
+        @Override
+        public boolean play() {
+            try {
+                mRemoteRingtoneService.playRemoteRingtone(mRemoteToken, mCanonicalUri,
+                        mAudioAttributes, mUseExactAudioAttributes, mEnabledMedia, mVibrationEffect,
+                        mVolume, mIsLooping, mHapticGeneratorEnabled, mVolumeShaperConfig);
+                return true;
+            } catch (RemoteException e) {
+                Log.w(TAG, "Problem playing ringtone: " + e);
+                return false;
+            }
+        }
+
+        @Override
+        public boolean isPlaying() {
+            try {
+                return mRemoteRingtoneService.isPlaying(mRemoteToken);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Problem checking ringtone isPlaying: " + e);
+                return false;
+            }
+        }
+
+        @Override
+        public void stopAndRelease() {
+            try {
+                mRemoteRingtoneService.stop(mRemoteToken);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Problem stopping ringtone: " + e);
+            }
+        }
+
+        @Override
+        public void setPreferredDevice(@Nullable AudioDeviceInfo audioDeviceInfo) {
+            // un-implemented for remote (but not used outside system).
+        }
+
+        @Override
+        public void setLooping(boolean looping) {
+            mIsLooping = looping;
+            try {
+                mRemoteRingtoneService.setLooping(mRemoteToken, looping);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Problem setting looping: " + e);
+            }
+        }
+
+        @Override
+        public void setHapticGeneratorEnabled(boolean enabled) {
+            mHapticGeneratorEnabled = enabled;
+            try {
+                mRemoteRingtoneService.setHapticGeneratorEnabled(mRemoteToken, enabled);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Problem setting hapticGeneratorEnabled: " + e);
+            }
+        }
+
+        @Override
+        public void setVolume(float volume) {
+            mVolume = volume;
+            try {
+                mRemoteRingtoneService.setVolume(mRemoteToken, volume);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Problem setting volume: " + e);
+            }
+        }
+
+        @Override
+        public boolean hasHapticChannels() {
+            // FIXME: support remote player, or internalize haptic channels support and remove
+            // entirely.
+            return false;
+        }
+    }
+
+    /**
+     * Interface for intercepting static methods and constructors, for unit testing only.
+     * @hide
+     */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public static class Injectables {
+        /** Intercept {@code new MediaPlayer()}. */
+        @NonNull
+        public MediaPlayer newMediaPlayer() {
+            return new MediaPlayer();
+        }
+
+        /** Intercept {@link HapticGenerator#isAvailable}. */
+        public boolean isHapticGeneratorAvailable() {
+            return HapticGenerator.isAvailable();
+        }
+
+        /**
+         * Intercept {@link HapticGenerator#create} using
+         * {@link MediaPlayer#getAudioSessionId()} from the given media player.
+         */
+        @Nullable
+        public HapticGenerator createHapticGenerator(@NonNull MediaPlayer mediaPlayer) {
+            return HapticGenerator.create(mediaPlayer.getAudioSessionId());
+        }
+
+        /** Returns the result of {@link AudioManager#isHapticPlaybackSupported()}. */
+        public boolean isHapticPlaybackSupported() {
+            return AudioManager.isHapticPlaybackSupported();
+        }
+
+        /**
+         * Returns whether the MediaPlayer tracks have haptic channels. This is the same as
+         * AudioManager.hasHapticChannels, except it uses an already prepared MediaPlayer to avoid
+         * loading the metadata a second time.
+         */
+        public boolean hasHapticChannels(MediaPlayer mp) {
+            try {
+                Trace.beginSection("Ringtone.hasHapticChannels");
+                for (MediaPlayer.TrackInfo trackInfo : mp.getTrackInfo()) {
+                    if (trackInfo.hasHapticChannels()) {
+                        return true;
+                    }
+                }
+            } finally {
+                Trace.endSection();
+            }
+            return false;
+        }
+
     }
 }
diff --git a/media/java/android/media/RingtoneManager.java b/media/java/android/media/RingtoneManager.java
index d2b21ae..12766fb 100644
--- a/media/java/android/media/RingtoneManager.java
+++ b/media/java/android/media/RingtoneManager.java
@@ -16,7 +16,6 @@
 
 package android.media;
 
-import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
@@ -39,10 +38,7 @@
 import android.os.Build;
 import android.os.Environment;
 import android.os.FileUtils;
-import android.os.IBinder;
 import android.os.ParcelFileDescriptor;
-import android.os.RemoteException;
-import android.os.ServiceManager;
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.UserManager;
@@ -357,6 +353,25 @@
         }
     }
 
+    /** @hide */
+    @NonNull
+    public static AudioAttributes getDefaultAudioAttributes(int ringtoneType) {
+        AudioAttributes.Builder builder = new AudioAttributes.Builder();
+        switch (ringtoneType) {
+            case TYPE_ALARM:
+                builder.setUsage(AudioAttributes.USAGE_ALARM);
+                break;
+            case TYPE_NOTIFICATION:
+                builder.setUsage(AudioAttributes.USAGE_NOTIFICATION);
+                break;
+            default:  // ringtone or all
+                builder.setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE);
+                break;
+        }
+        builder.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION);
+        return builder.build();
+    }
+
     /**
      * Whether retrieving another {@link Ringtone} will stop playing the
      * previously retrieved {@link Ringtone}.
@@ -481,8 +496,10 @@
             mPreviousRingtone.stop();
         }
 
-        mPreviousRingtone =
-                getRingtone(mContext, getRingtoneUri(position), inferStreamType(), true);
+        mPreviousRingtone = new Ringtone.Builder(
+                mContext, Ringtone.MEDIA_SOUND, getDefaultAudioAttributes(mType))
+                .setUri(getRingtoneUri(position))
+                .build();
         return mPreviousRingtone;
     }
 
@@ -677,40 +694,9 @@
      * @return A {@link Ringtone} for the given URI, or null.
      */
     public static Ringtone getRingtone(final Context context, Uri ringtoneUri) {
-        // Don't set the stream type
-        return getRingtone(context, ringtoneUri, -1, true);
-    }
-
-    /**
-     * Returns a {@link Ringtone} with {@link VolumeShaper} if required for a given sound URI.
-     * <p>
-     * If the given URI cannot be opened for any reason, this method will
-     * attempt to fallback on another sound. If it cannot find any, it will
-     * return null.
-     *
-     * @param context A context used to query.
-     * @param ringtoneUri The {@link Uri} of a sound or ringtone.
-     * @param volumeShaperConfig config for volume shaper of the ringtone if applied.
-     * @return A {@link Ringtone} for the given URI, or null.
-     *
-     * @hide
-     */
-    public static Ringtone getRingtone(
-            final Context context, Uri ringtoneUri,
-            @Nullable VolumeShaper.Configuration volumeShaperConfig) {
-        // Don't set the stream type
-        return getRingtone(context, ringtoneUri, -1 /* streamType */, volumeShaperConfig, true);
-    }
-
-    /**
-     * @hide
-     */
-    public static Ringtone getRingtone(final Context context, Uri ringtoneUri,
-            @Nullable VolumeShaper.Configuration volumeShaperConfig,
-            boolean createLocalMediaPlayer) {
-        // Don't set the stream type
-        return getRingtone(context, ringtoneUri, -1 /* streamType */, volumeShaperConfig,
-                createLocalMediaPlayer);
+        return new Ringtone.Builder(context, Ringtone.MEDIA_SOUND, getDefaultAudioAttributes(-1))
+                .setUri(ringtoneUri)
+                .build();
     }
 
     /**
@@ -719,64 +705,12 @@
     public static Ringtone getRingtone(final Context context, Uri ringtoneUri,
             @Nullable VolumeShaper.Configuration volumeShaperConfig,
             AudioAttributes audioAttributes) {
-        // Don't set the stream type
-        Ringtone ringtone = getRingtone(context, ringtoneUri, -1 /* streamType */,
-                volumeShaperConfig, false);
-        if (ringtone != null) {
-            ringtone.setAudioAttributesField(audioAttributes);
-            if (!ringtone.createLocalMediaPlayer()) {
-                Log.e(TAG, "Failed to open ringtone " + ringtoneUri);
-                return null;
-            }
-        }
-        return ringtone;
-    }
-
-    //FIXME bypass the notion of stream types within the class
-    /**
-     * Returns a {@link Ringtone} for a given sound URI on the given stream
-     * type. Normally, if you change the stream type on the returned
-     * {@link Ringtone}, it will re-create the {@link MediaPlayer}. This is just
-     * an optimized route to avoid that.
-     *
-     * @param streamType The stream type for the ringtone, or -1 if it should
-     *            not be set (and the default used instead).
-     * @param createLocalMediaPlayer when true, the ringtone returned will be fully
-     *      created otherwise, it will require the caller to create the media player manually
-     *      {@link Ringtone#createLocalMediaPlayer()} in order to play the Ringtone.
-     * @see #getRingtone(Context, Uri)
-     */
-    @UnsupportedAppUsage
-    private static Ringtone getRingtone(final Context context, Uri ringtoneUri, int streamType,
-            boolean createLocalMediaPlayer) {
-        return getRingtone(context, ringtoneUri, streamType, null /* volumeShaperConfig */,
-                createLocalMediaPlayer);
-    }
-
-    private static Ringtone getRingtone(final Context context, Uri ringtoneUri, int streamType,
-            @Nullable VolumeShaper.Configuration volumeShaperConfig,
-            boolean createLocalMediaPlayer) {
-        try {
-            final Ringtone r = new Ringtone(context, true);
-            if (streamType >= 0) {
-                //FIXME deprecated call
-                r.setStreamType(streamType);
-            }
-
-            r.setVolumeShaperConfig(volumeShaperConfig);
-            r.setUri(ringtoneUri, volumeShaperConfig);
-            if (createLocalMediaPlayer) {
-                if (!r.createLocalMediaPlayer()) {
-                    Log.e(TAG, "Failed to open ringtone " + ringtoneUri);
-                    return null;
-                }
-            }
-            return r;
-        } catch (Exception ex) {
-            Log.e(TAG, "Failed to open ringtone " + ringtoneUri + ": " + ex);
-        }
-
-        return null;
+        // TODO: move caller(s) away from this method: inline the builder call.
+        return new Ringtone.Builder(context, Ringtone.MEDIA_SOUND, audioAttributes)
+                .setUri(ringtoneUri)
+                .setVolumeShaperConfig(volumeShaperConfig)
+                .setUseExactAudioAttributes(true)  // May be using audio-coupled via attrs
+                .build();
     }
 
     /**
diff --git a/media/java/android/media/RingtoneSelection.java b/media/java/android/media/RingtoneSelection.java
new file mode 100644
index 0000000..74f7276
--- /dev/null
+++ b/media/java/android/media/RingtoneSelection.java
@@ -0,0 +1,603 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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 java.util.Objects.requireNonNull;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.TestApi;
+import android.content.ContentResolver;
+import android.net.Uri;
+import android.provider.MediaStore;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Immutable representation a desired ringtone, usually originating from a user preference.
+ * Unlike sound-only Uris, a "silent" setting is an explicit selection value, rather than null.
+ *
+ * <p>This representation can be converted into (or from) a URI form for storing within a string
+ * preference or when using the ringtone picker via {@link RingtoneManager#ACTION_RINGTONE_PICKER}.
+ * It does not carry any actual media data - it only references the components that make
+ * up the preference. Initial selections can be built using {@link RingtoneSelection.Builder}.
+ *
+ * <p>A RingtoneSelection is typically played by passing into a {@link Ringtone.Builder}, and
+ * supplementing with contextual defaults from the application. Bad Uris are handled by the
+ * {@link Ringtone} class - the RingtoneSelection doesn't validate the target of the Uri.
+ *
+ * <p>When a RingtoneSelection is created/loaded, the values of its properties are modified
+ * to be internally consistent and reflect effective values - with the exception of not verifying
+ * the actual URI content. For example, loading a selection Uri that sets a sound source to
+ * {@link #SOUND_SOURCE_URI}, but doesn't also have a sound Uri set, will result in this class
+ * instead returning {@link #SOUND_SOURCE_DEFAULT} from {@link #getSoundSource}.
+ *
+ * <h2>Storing preferences</h2>
+ *
+ * <p>A ringtone preference can have several states: either unset, set to a ringtone selection Uri,
+ * or, from prior to the introduction of {@code RingtoneSelection}, set to a sound-only Uri or
+ * explicitly set to null to indicate silent.
+ *
+ * @hide
+ */
+@TestApi
+public final class RingtoneSelection {
+
+    /**
+     * The sound source was specified but its value was not recognized. This value is used
+     * internally for not stripping unrecognised (possibly future) values during processing.
+     * @hide
+     */
+    public static final int SOUND_SOURCE_UNKNOWN = -1;
+
+    /**
+     * The sound source is not explicitly specified, so it can follow default behavior for its
+     * context.
+     */
+    public static final int SOUND_SOURCE_DEFAULT = 0;
+
+    /**
+     * Sound is explicitly disabled, such as the user having selected "Silent" in the sound picker.
+     */
+    public static final int SOUND_SOURCE_OFF = 1;
+
+    /**
+     * The sound Uri should be used as the source of sound.
+     */
+    public static final int SOUND_SOURCE_URI = 2;
+
+    /**
+     * Directive for how to make sound.
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = "SOUND_SOURCE_", value = {
+            SOUND_SOURCE_UNKNOWN,
+            SOUND_SOURCE_DEFAULT,
+            SOUND_SOURCE_OFF,
+            SOUND_SOURCE_URI,
+    })
+    public @interface SoundSource {}
+
+    /**
+     * The vibration source was specified but its value was not recognized.
+     * This value is used internally for not stripping unrecognised (possibly
+     * future) values during processing.
+     * @hide
+     */
+    public static final int VIBRATION_SOURCE_UNKNOWN = -1;
+
+    /**
+     * Vibration source is not explicitly specified. If vibration is enabled, this will use the
+     * first available of {@link #VIBRATION_SOURCE_AUDIO_CHANNEL},
+     * {@link #VIBRATION_SOURCE_APPLICATION_PROVIDED}, or system default vibration.
+     */
+    public static final int VIBRATION_SOURCE_DEFAULT = 0;
+
+    /** Specifies that vibration is explicitly disabled for this ringtone. */
+    public static final int VIBRATION_SOURCE_OFF = 1;
+
+    /** The vibration Uri should be used as the source of vibration. */
+    public static final int VIBRATION_SOURCE_URI = 2;
+
+    /**
+     * Specifies that vibration should use the vibration provided by the application. This is
+     * typically the application's own default for the use-case, provided via
+     * {@link Ringtone.Builder#setVibrationEffect}. For notification channels, this is the vibration
+     * effect saved on the notification channel.
+     *
+     * <p>If no vibration is specified by the application, this value behaves if the source was
+     * {@link #VIBRATION_SOURCE_DEFAULT}.
+     */
+    public static final int VIBRATION_SOURCE_APPLICATION_PROVIDED = 3;
+
+    /**
+     * Specifies that vibration should use haptic audio channels from the
+     * sound Uri. If the sound URI doesn't have haptic channels, then reverts to the order specified
+     * by {@link #VIBRATION_SOURCE_DEFAULT}.
+     */
+    // Numeric gap from VIBRATION_SOURCE_APPLICATION_PROVIDED in case we want other common elements.
+    public static final int VIBRATION_SOURCE_AUDIO_CHANNEL = 10;
+
+    /**
+     * Specifies that vibration should generate haptic audio channels from the
+     * audio tracks of the sound Uri.
+     *
+     * If the sound Uri already has haptic channels, then behaves as though
+     * {@link #VIBRATION_SOURCE_AUDIO_CHANNEL} was specified instead.
+     */
+    public static final int VIBRATION_SOURCE_HAPTIC_GENERATOR = 11;
+
+    /**
+     * Directive for how to vibrate.
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = "VIBRATION_SOURCE_", value = {
+            VIBRATION_SOURCE_UNKNOWN,
+            VIBRATION_SOURCE_DEFAULT,
+            VIBRATION_SOURCE_OFF,
+            VIBRATION_SOURCE_URI,
+            VIBRATION_SOURCE_APPLICATION_PROVIDED,
+            VIBRATION_SOURCE_AUDIO_CHANNEL,
+            VIBRATION_SOURCE_HAPTIC_GENERATOR,
+    })
+    public @interface VibrationSource {}
+
+    /**
+     * Configures {@link #RingtoneSelection#fromUri} to treat an unrecognized Uri as the sound Uri
+     * for the returned {@link RingtoneSelection}, with null meaning {@link #SOUND_SOURCE_OFF}.
+     * This behavior is particularly suited to loading values from older settings that may contain
+     * a raw sound Uri or null for silent.
+     *
+     * <p>An unrecognized Uri is one for which {@link #isRingtoneSelectionUri(Uri)} returns false.
+     */
+    public static final int FROM_URI_RINGTONE_SELECTION_OR_SOUND = 1;
+
+    /**
+     * Configures {@link #RingtoneSelection#fromUri} to treat an unrecognized Uri as the vibration
+     * Uri for the returned {@link RingtoneSelection}, with null meaning
+     * {@link #VIBRATION_SOURCE_OFF}.
+     *
+     * <p>An unrecognized Uri is one for which {@link #isRingtoneSelectionUri(Uri)} returns false.
+     */
+    public static final int FROM_URI_RINGTONE_SELECTION_OR_VIBRATION = 2;
+
+    /**
+     * Configures {@link #RingtoneSelection#fromUri} to treat an unrecognized Uri as an invalid
+     * value. Null or an invalid values will revert to default behavior correspnoding to
+     * {@link #DEFAULT_SELECTION_URI_STRING}.
+     *
+     * <p>An unrecognized Uri is one for which {@link #isRingtoneSelectionUri(Uri)} returns false,
+     * which include {@code null}.
+     */
+    public static final int FROM_URI_RINGTONE_SELECTION_ONLY = 3;
+
+    /**
+     * How to treat values in {@link #fromUri}.
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = "FROM_URI_", value = {
+            FROM_URI_RINGTONE_SELECTION_OR_SOUND,
+            FROM_URI_RINGTONE_SELECTION_OR_VIBRATION,
+            FROM_URI_RINGTONE_SELECTION_ONLY
+    })
+    public @interface FromUriBehavior {}
+
+    private static final String BASE_RINGTONE_URI = "content://media/ringtone";
+    /**
+     * String representation of a RingtoneSelection Uri that says to use defaults (equivalent
+     * to {@code new RingtoneSelection.Builder().build()}).
+     */
+    public static final String DEFAULT_SELECTION_URI_STRING = BASE_RINGTONE_URI;
+
+    private static final String MEDIA_URI_RINGTONE_PATH = "/ringtone";
+
+    /* Query param keys. */
+    private static final String URI_PARAM_SOUND_URI = "su";
+    private static final String URI_PARAM_SOUND_SOURCE = "ss";
+    private static final String URI_PARAM_VIBRATION_URI = "vu";
+    private static final String URI_PARAM_VIBRATION_SOURCE = "vs";
+
+    /* Common param values */
+    private static final String SOURCE_OFF_STRING = "off";
+
+    /* Vibration source param values. */
+    private static final String VIBRATION_SOURCE_AUDIO_CHANNEL_STRING = "ac";
+    private static final String VIBRATION_SOURCE_APPLICATION_PROVIDED_STRING = "app";
+    private static final String VIBRATION_SOURCE_HAPTIC_GENERATOR_STRING = "hg";
+
+    @Nullable
+    private final Uri mSoundUri;
+    @SoundSource
+    private final int mSoundSource;
+
+    @Nullable
+    private final Uri mVibrationUri;
+    @VibrationSource
+    private final int mVibrationSource;
+
+    private RingtoneSelection(@Nullable Uri soundUri, @SoundSource int soundSource,
+            @Nullable Uri vibrationUri, @VibrationSource int vibrationSource) {
+        // Enforce guarantees on the source values: revert to unset if they depend on something
+        // that's not set.
+        switch (soundSource) {
+            case SOUND_SOURCE_URI:
+            case SOUND_SOURCE_UNKNOWN:  // Allow unknown to revert to URI before default.
+                mSoundSource = soundUri != null ? SOUND_SOURCE_URI : SOUND_SOURCE_DEFAULT;
+                break;
+            default:
+                mSoundSource = soundSource;
+                break;
+        }
+        switch (vibrationSource) {
+            case VIBRATION_SOURCE_AUDIO_CHANNEL:
+            case VIBRATION_SOURCE_HAPTIC_GENERATOR:
+                mVibrationSource = soundUri != null ? vibrationSource : VIBRATION_SOURCE_DEFAULT;
+                break;
+            case VIBRATION_SOURCE_URI:
+            case VIBRATION_SOURCE_UNKNOWN:  // Allow unknown to revert to URI.
+                mVibrationSource =
+                        vibrationUri != null ? VIBRATION_SOURCE_URI : VIBRATION_SOURCE_DEFAULT;
+                break;
+            default:
+                mVibrationSource = vibrationSource;
+                break;
+        }
+        // Clear Uri values if they're un-used by the source.
+        switch (mSoundSource) {
+            case SOUND_SOURCE_OFF:
+                mSoundUri = null;
+                break;
+            default:
+                // Unset case isn't handled here: the defaulting behavior is left to the player.
+                mSoundUri = soundUri;
+                break;
+        }
+        switch (mVibrationSource) {
+            case VIBRATION_SOURCE_OFF:
+            case VIBRATION_SOURCE_APPLICATION_PROVIDED:
+            case VIBRATION_SOURCE_AUDIO_CHANNEL:
+            case VIBRATION_SOURCE_HAPTIC_GENERATOR:
+                mVibrationUri = null;
+                break;
+            default:
+                // Unset case isn't handled here: the defaulting behavior is left to the player.
+                mVibrationUri = vibrationUri;
+                break;
+        }
+    }
+
+    /**
+     * Returns the stored sound behavior.
+     */
+    @SoundSource
+    public int getSoundSource() {
+        return mSoundSource;
+    }
+
+    /**
+     * Returns the sound Uri for this selection. This is guaranteed to be non-null if
+     * {@link #getSoundSource} returns {@link #SOUND_SOURCE_URI}.
+     */
+    @Nullable
+    public Uri getSoundUri() {
+        return mSoundUri;
+    }
+
+    /**
+     * Returns the selected vibration behavior.
+     */
+    @VibrationSource
+    public int getVibrationSource() {
+        return mVibrationSource;
+    }
+
+    /**
+     * Returns the vibration Uri for this selection. This is guaranteed to be non-null if
+     * {@link #getVibrationSource} returns {@link #SOUND_SOURCE_URI}.
+     */
+    @Nullable
+    public Uri getVibrationUri() {
+        return mVibrationUri;
+    }
+
+    /**
+     * Converts the ringtone selection into a Uri-form, suitable for storing as a user preference
+     * or returning as a result.
+     */
+    @NonNull
+    public Uri toUri() {
+        Uri.Builder builder = new Uri.Builder()
+                .scheme(ContentResolver.SCHEME_CONTENT)
+                .authority(MediaStore.AUTHORITY)
+                .path(MEDIA_URI_RINGTONE_PATH);
+        if (mSoundUri != null) {
+            builder.appendQueryParameter(URI_PARAM_SOUND_URI, mSoundUri.toString());
+        }
+        // Only off is explicit for sound sources
+        String soundSourceStr = soundSourceToString(mSoundSource);
+        if (soundSourceStr != null) {
+            builder.appendQueryParameter(URI_PARAM_SOUND_SOURCE, soundSourceStr);
+        }
+        if (mVibrationUri != null) {
+            builder.appendQueryParameter(URI_PARAM_VIBRATION_URI, mVibrationUri.toString());
+        }
+        String vibrationSourceStr = vibrationSourceToString(mVibrationSource);
+        if (vibrationSourceStr != null) {
+            builder.appendQueryParameter(URI_PARAM_VIBRATION_SOURCE, vibrationSourceStr);
+        }
+        return builder.build();
+    }
+
+    /**
+     * Returns true if the Uri is an encoded {@link RingtoneSelection}. This method doesn't
+     * validate the parameters of the selection.
+     *
+     * @see #fromUri
+     * @see #toUri
+     */
+    public static boolean isRingtoneSelectionUri(@Nullable Uri uri) {
+        if (uri == null) {
+            return false;
+        }
+        // Any URI content://media/ringtone
+        return ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())
+                && MediaStore.AUTHORITY.equals(uri.getAuthority())
+                && MEDIA_URI_RINGTONE_PATH.equals(uri.getPath());
+    }
+
+
+
+    /**
+     * Converts a Uri into a RingtoneSelection.
+     *
+     * <p>Null values and Uris that {@link #isRingtoneSelectionUri(Uri)} returns false will be
+     * treated according to the behaviour specified by the {@code unrecognizedValueBehavior}
+     * parameter.
+     *
+     * @param uri The Uri to convert, potentially null.
+     * @param unrecognizedValueBehavior indicates how to treat values for which
+     *   {@link #isRingtoneSelectionUri(Uri)} returns false (including null).
+     * @return the RingtoneSelection represented by the given uri.
+     */
+    @NonNull
+    public static RingtoneSelection fromUri(@Nullable Uri uri,
+            @FromUriBehavior int unrecognizedValueBehavior) {
+        if (isRingtoneSelectionUri(uri)) {
+            return parseRingtoneSelectionUri(uri);
+        }
+        RingtoneSelection.Builder builder = new RingtoneSelection.Builder();
+        switch (unrecognizedValueBehavior) {
+            case FROM_URI_RINGTONE_SELECTION_ONLY:
+                // Always return use-defaults for unrecognized ringtone selection Uris.
+                return builder.build();
+            case FROM_URI_RINGTONE_SELECTION_OR_SOUND:
+                if (uri == null) {
+                    return builder.setSoundSource(SOUND_SOURCE_OFF).build();
+                } else {
+                    return builder.setSoundSource(uri).build();
+                }
+            case FROM_URI_RINGTONE_SELECTION_OR_VIBRATION:
+                if (uri == null) {
+                    return builder.setVibrationSource(VIBRATION_SOURCE_OFF).build();
+                } else {
+                    return builder.setVibrationSource(uri).build();
+                }
+            default:
+                throw new IllegalArgumentException("Unknown behavior parameter: "
+                        + unrecognizedValueBehavior);
+        }
+    }
+
+    /** Parses the Uri, which has already been checked for {@link #isRingtoneSelectionUri(Uri)}. */
+    @NonNull
+    private static RingtoneSelection parseRingtoneSelectionUri(@NonNull Uri uri) {
+        RingtoneSelection.Builder builder = new RingtoneSelection.Builder();
+        int soundSource = stringToSoundSource(uri.getQueryParameter(URI_PARAM_SOUND_SOURCE));
+        int vibrationSource = stringToVibrationSource(
+                uri.getQueryParameter(URI_PARAM_VIBRATION_SOURCE));
+        Uri soundUri = getParamAsUri(uri, URI_PARAM_SOUND_URI);
+        Uri vibrationUri = getParamAsUri(uri, URI_PARAM_VIBRATION_URI);
+        if (soundUri != null) {
+            builder.setSoundSource(soundUri);
+        }
+        if (vibrationUri != null) {
+            builder.setVibrationSource(vibrationUri);
+        }
+        // Don't set the source if there's a URI and the source is default, because that will
+        // override the Uri source set above. In effect, we prioritise "explicit" sources over
+        // an implicit Uri source - except for "default", which isn't really explicit.
+        if (soundSource != SOUND_SOURCE_DEFAULT || soundUri == null) {
+            builder.setSoundSource(soundSource);
+        }
+        if (vibrationSource != VIBRATION_SOURCE_DEFAULT || vibrationUri == null) {
+            builder.setVibrationSource(vibrationSource);
+        }
+        return builder.build();
+    }
+
+    @Nullable
+    private static Uri getParamAsUri(@NonNull Uri uri, String param) {
+        // This returns the uri-decoded value, no need to further decode.
+        String value = uri.getQueryParameter(param);
+        if (value == null) {
+            return null;
+        }
+        return Uri.parse(value);
+    }
+
+    /**
+     * Converts the {@link SoundSource} to the uri query param value for it, or null
+     * if the sound source is default, unknown, or implicit (uri).
+     */
+    @Nullable
+    private static String soundSourceToString(@SoundSource int soundSource) {
+        switch (soundSource) {
+            case SOUND_SOURCE_OFF: return SOURCE_OFF_STRING;
+            default: return null;
+        }
+    }
+
+    /**
+     * Returns the sound source int corresponding to the query string value. Returns
+     * {@link #SOUND_SOURCE_UNKNOWN} if the value isn't recognised, and
+     * {@link #SOUND_SOURCE_DEFAULT} if the value is {@code null} (not in the Uri).
+     */
+    @SoundSource
+    private static int stringToSoundSource(@Nullable String soundSource) {
+        if (soundSource == null) {
+            return SOUND_SOURCE_DEFAULT;
+        }
+        switch (soundSource) {
+            case SOURCE_OFF_STRING: return SOUND_SOURCE_OFF;
+            default: return SOUND_SOURCE_UNKNOWN;
+        }
+    }
+
+    /**
+     * Converts the {@code vibrationSource} to the uri query param value for it, or null
+     * if the vibration source is default, unknown, or implicit (uri).
+     */
+    @Nullable
+    private static String vibrationSourceToString(@VibrationSource int vibrationSource) {
+        switch (vibrationSource) {
+            case VIBRATION_SOURCE_OFF: return SOURCE_OFF_STRING;
+            case VIBRATION_SOURCE_AUDIO_CHANNEL: return VIBRATION_SOURCE_AUDIO_CHANNEL_STRING;
+            case VIBRATION_SOURCE_HAPTIC_GENERATOR:
+                return VIBRATION_SOURCE_HAPTIC_GENERATOR_STRING;
+            case VIBRATION_SOURCE_APPLICATION_PROVIDED:
+                return VIBRATION_SOURCE_APPLICATION_PROVIDED_STRING;
+            default: return null;
+        }
+    }
+
+    @VibrationSource
+    private static int stringToVibrationSource(@Nullable String vibrationSource) {
+        if (vibrationSource == null) {
+            return VIBRATION_SOURCE_DEFAULT;
+        }
+        switch (vibrationSource) {
+            case SOURCE_OFF_STRING: return VIBRATION_SOURCE_OFF;
+            case VIBRATION_SOURCE_AUDIO_CHANNEL_STRING: return VIBRATION_SOURCE_AUDIO_CHANNEL;
+            case VIBRATION_SOURCE_HAPTIC_GENERATOR_STRING: return VIBRATION_SOURCE_HAPTIC_GENERATOR;
+            case VIBRATION_SOURCE_APPLICATION_PROVIDED_STRING:
+                return VIBRATION_SOURCE_APPLICATION_PROVIDED;
+            default: return VIBRATION_SOURCE_UNKNOWN;
+        }
+    }
+
+    /**
+     * Builder for {@link RingtoneSelection}. In general, this builder will be used by interfaces
+     * allowing the user to configure their selection. Once a selection is stored as a Uri, then
+     * the RingtoneSelection can be loaded directly using {@link RingtoneSelection#fromUri}.
+     */
+    public static final class Builder {
+        private Uri mSoundUri;
+        private Uri mVibrationUri;
+        @SoundSource private int mSoundSource = SOUND_SOURCE_DEFAULT;
+        @VibrationSource private int mVibrationSource = VIBRATION_SOURCE_DEFAULT;
+
+        /**
+         * Creates a new {@link RingtoneSelection} builder. A default ringtone selection has its
+         * sound and vibration source unset, which means they would fall back to system defaults.
+         */
+        public Builder() {}
+
+        /**
+         * Creates a builder initialized with the given ringtone selection.
+         */
+        public Builder(@NonNull RingtoneSelection selection) {
+            requireNonNull(selection);
+            mSoundSource = selection.getSoundSource();
+            mSoundUri = selection.getSoundUri();
+            mVibrationSource = selection.getVibrationSource();
+            mVibrationUri = selection.getVibrationUri();
+        }
+
+        /**
+         * Sets the desired sound source.
+         *
+         * <p>Values other than {@link #SOUND_SOURCE_URI} will clear any previous sound Uri.
+         * For {@link #SOUND_SOURCE_URI}, the {@link #setSoundSource(Uri)} method should be
+         * used instead, as setting it here will have no effect unless the Uri is also set.
+         */
+        @NonNull
+        public Builder setSoundSource(@SoundSource int soundSource) {
+            mSoundSource = soundSource;
+            if (soundSource != SOUND_SOURCE_URI && soundSource != SOUND_SOURCE_UNKNOWN) {
+                // Note that this means the configuration of "silent sound, but use haptic
+                // generator" is currently not supported. Future support could be added by either
+                // using the vibration uri in that case, or by having a special
+                // "setSoundUriForVibrationOnly(Uri)" method that sets sound source to off but
+                // also retains the Uri.
+                mSoundUri = null;
+            }
+            return this;
+        }
+
+        /**
+         * Sets the sound source to {@link #SOUND_SOURCE_URI}, and the sound Uri to the
+         * specified {@link Uri}.
+         */
+        @NonNull
+        public Builder setSoundSource(@NonNull Uri soundUri) {
+            mSoundUri = requireNonNull(soundUri);
+            mSoundSource = SOUND_SOURCE_URI;
+            return this;
+        }
+
+        /**
+         * Sets the vibration source to the specified value.
+         *
+         * <p>Values other than {@link #VIBRATION_SOURCE_URI} will clear any previous vibration Uri.
+         * For {@link #VIBRATION_SOURCE_URI}, the {@link #setVibrationSource(Uri)} method should be
+         * used instead, as setting it here will have no effect unless the Uri is also set.
+         */
+        @NonNull
+        public Builder setVibrationSource(@VibrationSource int vibrationSource) {
+            mVibrationSource = vibrationSource;
+            if (vibrationSource != VIBRATION_SOURCE_URI
+                    && vibrationSource != VIBRATION_SOURCE_UNKNOWN) {
+                mVibrationUri = null;
+            }
+            return this;
+        }
+
+        /**
+         * Sets the vibration source to {@link #VIBRATION_SOURCE_URI}, and the vibration Uri to the
+         * specified {@link Uri}.
+         */
+        @NonNull
+        public Builder setVibrationSource(@NonNull Uri vibrationUri) {
+            mVibrationUri = requireNonNull(vibrationUri);
+            mVibrationSource = VIBRATION_SOURCE_URI;
+            return this;
+        }
+
+        /**
+         * Returns the ringtone Uri that was configured.
+         */
+        @NonNull
+        public RingtoneSelection build() {
+            return new RingtoneSelection(mSoundUri, mSoundSource, mVibrationUri, mVibrationSource);
+        }
+    }
+}
diff --git a/media/java/android/media/RoutingSessionInfo.java b/media/java/android/media/RoutingSessionInfo.java
index e1af909..a77c943 100644
--- a/media/java/android/media/RoutingSessionInfo.java
+++ b/media/java/android/media/RoutingSessionInfo.java
@@ -55,15 +55,23 @@
     private static final String KEY_GROUP_ROUTE = "androidx.mediarouter.media.KEY_GROUP_ROUTE";
     private static final String KEY_VOLUME_HANDLING = "volumeHandling";
 
+    @NonNull
     final String mId;
+    @Nullable
     final CharSequence mName;
+    @Nullable
     final String mOwnerPackageName;
+    @NonNull
     final String mClientPackageName;
     @Nullable
     final String mProviderId;
+    @NonNull
     final List<String> mSelectedRoutes;
+    @NonNull
     final List<String> mSelectableRoutes;
+    @NonNull
     final List<String> mDeselectableRoutes;
+    @NonNull
     final List<String> mTransferableRoutes;
 
     final int mVolumeHandling;
@@ -100,8 +108,12 @@
 
         boolean volumeAdjustmentForRemoteGroupSessions = Resources.getSystem().getBoolean(
                 com.android.internal.R.bool.config_volumeAdjustmentForRemoteGroupSessions);
-        mVolumeHandling = defineVolumeHandling(builder.mVolumeHandling, mSelectedRoutes,
-                volumeAdjustmentForRemoteGroupSessions);
+        mVolumeHandling =
+                defineVolumeHandling(
+                        mIsSystemSession,
+                        builder.mVolumeHandling,
+                        mSelectedRoutes,
+                        volumeAdjustmentForRemoteGroupSessions);
 
         mControlHints = updateVolumeHandlingInHints(builder.mControlHints, mVolumeHandling);
     }
@@ -130,7 +142,9 @@
         mIsSystemSession = src.readBoolean();
     }
 
-    private static Bundle updateVolumeHandlingInHints(Bundle controlHints, int volumeHandling) {
+    @Nullable
+    private static Bundle updateVolumeHandlingInHints(@Nullable Bundle controlHints,
+            int volumeHandling) {
         // Workaround to preserve retro-compatibility with androidx.
         // See b/228021646 for more details.
         if (controlHints != null && controlHints.containsKey(KEY_GROUP_ROUTE)) {
@@ -150,19 +164,26 @@
         return controlHints;
     }
 
-    private static int defineVolumeHandling(int volumeHandling, List<String> selectedRoutes,
+    private static int defineVolumeHandling(
+            boolean isSystemSession,
+            int volumeHandling,
+            List<String> selectedRoutes,
             boolean volumeAdjustmentForRemoteGroupSessions) {
-        if (!volumeAdjustmentForRemoteGroupSessions && selectedRoutes.size() > 1) {
+        if (!isSystemSession
+                && !volumeAdjustmentForRemoteGroupSessions
+                && selectedRoutes.size() > 1) {
             return MediaRoute2Info.PLAYBACK_VOLUME_FIXED;
         }
         return volumeHandling;
     }
 
-    private static String ensureString(String str) {
+    @NonNull
+    private static String ensureString(@Nullable String str) {
         return str != null ? str : "";
     }
 
-    private static <T> List<T> ensureList(List<? extends T> list) {
+    @NonNull
+    private static <T> List<T> ensureList(@Nullable List<? extends T> list) {
         if (list != null) {
             return Collections.unmodifiableList(list);
         }
@@ -361,11 +382,15 @@
     }
 
     @Override
-    public boolean equals(Object obj) {
+    public boolean equals(@Nullable Object obj) {
+        if (obj == null) {
+            return false;
+        }
+
         if (this == obj) {
             return true;
         }
-        if (!(obj instanceof RoutingSessionInfo)) {
+        if (getClass() != obj.getClass()) {
             return false;
         }
 
@@ -445,21 +470,30 @@
      * Builder class for {@link RoutingSessionInfo}.
      */
     public static final class Builder {
-        // TODO: Reorder these (important ones first)
-        final String mId;
-        CharSequence mName;
-        String mOwnerPackageName;
-        String mClientPackageName;
-        String mProviderId;
-        final List<String> mSelectedRoutes;
-        final List<String> mSelectableRoutes;
-        final List<String> mDeselectableRoutes;
-        final List<String> mTransferableRoutes;
-        int mVolumeHandling = MediaRoute2Info.PLAYBACK_VOLUME_FIXED;
-        int mVolumeMax;
-        int mVolume;
-        Bundle mControlHints;
-        boolean mIsSystemSession;
+        @NonNull
+        private final String mId;
+        @Nullable
+        private CharSequence mName;
+        @Nullable
+        private String mOwnerPackageName;
+        @NonNull
+        private String mClientPackageName;
+        @Nullable
+        private String mProviderId;
+        @NonNull
+        private final List<String> mSelectedRoutes;
+        @NonNull
+        private final List<String> mSelectableRoutes;
+        @NonNull
+        private final List<String> mDeselectableRoutes;
+        @NonNull
+        private final List<String> mTransferableRoutes;
+        private int mVolumeHandling = MediaRoute2Info.PLAYBACK_VOLUME_FIXED;
+        private int mVolumeMax;
+        private int mVolume;
+        @Nullable
+        private Bundle mControlHints;
+        private boolean mIsSystemSession;
 
         /**
          * Constructor for builder to create {@link RoutingSessionInfo}.
diff --git a/media/java/android/media/audiofx/Visualizer.java b/media/java/android/media/audiofx/Visualizer.java
index f3dfeb6..2795cfe 100644
--- a/media/java/android/media/audiofx/Visualizer.java
+++ b/media/java/android/media/audiofx/Visualizer.java
@@ -455,11 +455,13 @@
      * a number of consecutive 8-bit (unsigned) mono PCM samples equal to the capture size returned
      * by {@link #getCaptureSize()}.
      * <p>This method must be called when the Visualizer is enabled.
-     * @param waveform array of bytes where the waveform should be returned
+     * @param waveform array of bytes where the waveform should be returned, array length must be
+     * at least equals to the capture size returned by {@link #getCaptureSize()}.
      * @return {@link #SUCCESS} in case of success,
      * {@link #ERROR_NO_MEMORY}, {@link #ERROR_INVALID_OPERATION} or {@link #ERROR_DEAD_OBJECT}
      * in case of failure.
      * @throws IllegalStateException
+     * @throws IllegalArgumentException
      */
     public int getWaveForm(byte[] waveform)
     throws IllegalStateException {
@@ -467,6 +469,12 @@
             if (mState != STATE_ENABLED) {
                 throw(new IllegalStateException("getWaveForm() called in wrong state: "+mState));
             }
+            int captureSize = getCaptureSize();
+            if (captureSize > waveform.length) {
+                throw(new IllegalArgumentException("getWaveForm() called with illegal size: "
+                                                   + waveform.length + " expecting at least "
+                                                   + captureSize + " bytes"));
+            }
             return native_getWaveForm(waveform);
         }
     }
diff --git a/media/java/android/media/audiopolicy/AudioMix.java b/media/java/android/media/audiopolicy/AudioMix.java
index ed68d1e..d0270d3 100644
--- a/media/java/android/media/audiopolicy/AudioMix.java
+++ b/media/java/android/media/audiopolicy/AudioMix.java
@@ -16,6 +16,9 @@
 
 package android.media.audiopolicy;
 
+import static android.media.AudioSystem.getDeviceName;
+import static android.media.AudioSystem.isRemoteSubmixDevice;
+
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
@@ -445,36 +448,36 @@
                     // CHANNEL_IN_FRONT_BACK is hidden, should not appear.
                 }
             }
-            if ((mDeviceSystemType != AudioSystem.DEVICE_NONE)
-                    && (mDeviceSystemType != AudioSystem.DEVICE_OUT_REMOTE_SUBMIX)
-                    && (mDeviceSystemType != AudioSystem.DEVICE_IN_REMOTE_SUBMIX)) {
-                if ((mRouteFlags & ROUTE_FLAG_RENDER) == 0) {
-                    throw new IllegalArgumentException(
-                            "Can't have audio device without flag ROUTE_FLAG_RENDER");
+
+            if ((mRouteFlags & ROUTE_FLAG_LOOP_BACK) == ROUTE_FLAG_LOOP_BACK) {
+                if (mDeviceSystemType == AudioSystem.DEVICE_NONE) {
+                    // If there was no device type explicitly set, configure it based on mix type.
+                    mDeviceSystemType = getLoopbackDeviceSystemTypeForAudioMixingRule(mRule);
+                } else if (!isRemoteSubmixDevice(mDeviceSystemType)) {
+                    // Loopback mode only supports remote submix devices.
+                    throw new IllegalArgumentException("Device " + getDeviceName(mDeviceSystemType)
+                            + "is not supported for loopback mix.");
                 }
-                if (mRule.getTargetMixType() != AudioMix.MIX_TYPE_PLAYERS) {
-                    throw new IllegalArgumentException("Unsupported device on non-playback mix");
-                }
-            } else if (mDeviceSystemType == AudioSystem.DEVICE_OUT_REMOTE_SUBMIX) {
-                if (mRule.getTargetMixType() != AudioMix.MIX_TYPE_PLAYERS) {
-                    throw new IllegalArgumentException(
-                            "DEVICE_OUT_REMOTE_SUBMIX device is not supported on non-playback mix");
-                }
-            } else {
-                if ((mRouteFlags & ROUTE_FLAG_SUPPORTED) == ROUTE_FLAG_RENDER) {
+            }
+
+            if ((mRouteFlags & ROUTE_FLAG_RENDER) == ROUTE_FLAG_RENDER) {
+                if (mDeviceSystemType == AudioSystem.DEVICE_NONE) {
                     throw new IllegalArgumentException(
                             "Can't have flag ROUTE_FLAG_RENDER without an audio device");
                 }
-                if ((mRouteFlags & ROUTE_FLAG_LOOP_BACK) == ROUTE_FLAG_LOOP_BACK) {
-                    if (mRule.getTargetMixType() == MIX_TYPE_PLAYERS) {
-                        mDeviceSystemType = AudioSystem.DEVICE_OUT_REMOTE_SUBMIX;
-                    } else if (mRule.getTargetMixType() == MIX_TYPE_RECORDERS) {
-                        mDeviceSystemType = AudioSystem.DEVICE_IN_REMOTE_SUBMIX;
-                    } else {
-                        throw new IllegalArgumentException("Unknown mixing rule type");
-                    }
+
+                if (AudioSystem.DEVICE_IN_ALL_SET.contains(mDeviceSystemType)) {
+                    throw new IllegalArgumentException(
+                            "Input device is not supported with ROUTE_FLAG_RENDER");
+                }
+
+                if (mRule.getTargetMixType() == MIX_TYPE_RECORDERS) {
+                    throw new IllegalArgumentException(
+                            "ROUTE_FLAG_RENDER/ROUTE_FLAG_LOOP_BACK_RENDER is not supported for "
+                                    + "non-playback mix rule");
                 }
             }
+
             if (mRule.allowPrivilegedMediaPlaybackCapture()) {
                 String error = AudioMix.canBeUsedForPrivilegedMediaCapture(mFormat);
                 if (error != null) {
@@ -484,5 +487,18 @@
             return new AudioMix(mRule, mFormat, mRouteFlags, mCallbackFlags, mDeviceSystemType,
                     mDeviceAddress);
         }
+
+        private int getLoopbackDeviceSystemTypeForAudioMixingRule(AudioMixingRule rule) {
+            switch (mRule.getTargetMixType()) {
+                case MIX_TYPE_PLAYERS:
+                    return AudioSystem.DEVICE_OUT_REMOTE_SUBMIX;
+                case MIX_TYPE_RECORDERS:
+                    return AudioSystem.DEVICE_IN_REMOTE_SUBMIX;
+                default:
+                    throw new IllegalArgumentException(
+                            "Unknown mixing rule type - 0x" + Integer.toHexString(
+                                    rule.getTargetMixType()));
+            }
+        }
     }
 }
diff --git a/media/java/android/media/audiopolicy/AudioPolicy.java b/media/java/android/media/audiopolicy/AudioPolicy.java
index c1ee74a..3e5de82 100644
--- a/media/java/android/media/audiopolicy/AudioPolicy.java
+++ b/media/java/android/media/audiopolicy/AudioPolicy.java
@@ -865,7 +865,7 @@
                 for (final WeakReference<AudioTrack> weakTrack : mInjectors) {
                     final AudioTrack track = weakTrack.get();
                     if (track == null) {
-                        break;
+                        continue;
                     }
                     try {
                         // TODO: add synchronous versions
@@ -876,12 +876,13 @@
                         // released by the user of the AudioPolicy
                     }
                 }
+                mInjectors.clear();
             }
             if (mCaptors != null) {
                 for (final WeakReference<AudioRecord> weakRecord : mCaptors) {
                     final AudioRecord record = weakRecord.get();
                     if (record == null) {
-                        break;
+                        continue;
                     }
                     try {
                         // TODO: if needed: implement an invalidate method
@@ -891,6 +892,7 @@
                         // released by the user of the AudioPolicy
                     }
                 }
+                mCaptors.clear();
             }
         }
     }
diff --git a/media/java/android/media/audiopolicy/AudioProductStrategy.java b/media/java/android/media/audiopolicy/AudioProductStrategy.java
index 3394dd0..e8adfaf 100644
--- a/media/java/android/media/audiopolicy/AudioProductStrategy.java
+++ b/media/java/android/media/audiopolicy/AudioProductStrategy.java
@@ -134,9 +134,11 @@
 
     /**
      * @hide
-     * @param audioAttributes to identify AudioProductStrategy with
-     * @return legacy stream type associated with matched AudioProductStrategy
-     *              Defaults to STREAM_MUSIC if no match is found, or if matches is STREAM_DEFAULT
+     * @param audioAttributes to identify {@link AudioProductStrategy} with
+     * @return legacy stream type associated with matched {@link AudioProductStrategy}. If no
+     *              strategy found or found {@link AudioProductStrategy} does not have associated
+     *              legacy stream (i.e. associated with {@link AudioSystem#STREAM_DEFAULT}) defaults
+     *              to {@link AudioSystem#STREAM_MUSIC}
      */
     public static int getLegacyStreamTypeForStrategyWithAudioAttributes(
             @NonNull AudioAttributes audioAttributes) {
@@ -147,9 +149,9 @@
                 int streamType = productStrategy.getLegacyStreamTypeForAudioAttributes(
                         audioAttributes);
                 if (streamType == AudioSystem.STREAM_DEFAULT) {
-                    Log.w(TAG, "Attributes " + audioAttributes.toString() + " ported by strategy "
-                            + productStrategy.getId() + " has no stream type associated, "
-                            + "DO NOT USE STREAM TO CONTROL THE VOLUME");
+                    Log.w(TAG, "Attributes " + audioAttributes + " supported by strategy "
+                            + productStrategy.getId() + " have no associated stream type, "
+                            + "therefore falling back to STREAM_MUSIC");
                     return AudioSystem.STREAM_MUSIC;
                 }
                 if (streamType < AudioSystem.getNumStreamTypes()) {
diff --git a/media/java/android/media/musicrecognition/OWNERS b/media/java/android/media/musicrecognition/OWNERS
index 58f5d40..037b048 100644
--- a/media/java/android/media/musicrecognition/OWNERS
+++ b/media/java/android/media/musicrecognition/OWNERS
@@ -1,6 +1,5 @@
 # Bug component: 830636
 
-joannechung@google.com
 oni@google.com
 volnov@google.com
 
diff --git a/media/java/android/media/projection/IMediaProjection.aidl b/media/java/android/media/projection/IMediaProjection.aidl
index e3829e6..388b2c5 100644
--- a/media/java/android/media/projection/IMediaProjection.aidl
+++ b/media/java/android/media/projection/IMediaProjection.aidl
@@ -28,6 +28,7 @@
     boolean canProjectVideo();
     boolean canProjectSecureVideo();
 
+    @EnforcePermission("MANAGE_MEDIA_PROJECTION")
     @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
             + ".permission.MANAGE_MEDIA_PROJECTION)")
     int applyVirtualDisplayFlags(int flags);
@@ -40,6 +41,7 @@
      * Returns the {@link android.os.IBinder} identifying the task to record, or {@code null} if
      * there is none.
      */
+    @EnforcePermission("MANAGE_MEDIA_PROJECTION")
     @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
             + ".permission.MANAGE_MEDIA_PROJECTION)")
     IBinder getLaunchCookie();
@@ -48,6 +50,7 @@
      * Updates the {@link android.os.IBinder} identifying the task to record, or {@code null} if
      * there is none.
      */
+    @EnforcePermission("MANAGE_MEDIA_PROJECTION")
     @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
             + ".permission.MANAGE_MEDIA_PROJECTION)")
     void setLaunchCookie(in IBinder launchCookie);
@@ -62,6 +65,7 @@
      * @throws IllegalStateException If the caller's target SDK is at least {@code U} and the
      * projection is not valid.
      */
+    @EnforcePermission("MANAGE_MEDIA_PROJECTION")
     @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
             + ".permission.MANAGE_MEDIA_PROJECTION)")
     boolean isValid();
@@ -70,6 +74,7 @@
      * Sets that {@link MediaProjection#createVirtualDisplay} has been invoked with this token (it
      * should only be called once).
      */
+    @EnforcePermission("MANAGE_MEDIA_PROJECTION")
     @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
             + ".permission.MANAGE_MEDIA_PROJECTION)")
     void notifyVirtualDisplayCreated(int displayId);
diff --git a/media/java/android/media/projection/IMediaProjectionManager.aidl b/media/java/android/media/projection/IMediaProjectionManager.aidl
index a3cd623..304eecb 100644
--- a/media/java/android/media/projection/IMediaProjectionManager.aidl
+++ b/media/java/android/media/projection/IMediaProjectionManager.aidl
@@ -67,6 +67,7 @@
      * Returns {@code true} if the given {@link IMediaProjection} corresponds to the current
      * projection, or {@code false} otherwise.
      */
+    @EnforcePermission("MANAGE_MEDIA_PROJECTION")
     @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
             + ".permission.MANAGE_MEDIA_PROJECTION)")
     boolean isCurrentProjection(IMediaProjection projection);
@@ -83,6 +84,7 @@
      *
      * <p>Returns immediately but waits to start recording until user has reviewed their consent.
      */
+    @EnforcePermission("MANAGE_MEDIA_PROJECTION")
     @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
             + ".permission.MANAGE_MEDIA_PROJECTION)")
     void requestConsentForInvalidProjection(in IMediaProjection projection);
@@ -91,14 +93,17 @@
             + ".permission.MANAGE_MEDIA_PROJECTION)")
     MediaProjectionInfo getActiveProjectionInfo();
 
+    @EnforcePermission("MANAGE_MEDIA_PROJECTION")
     @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
             + ".permission.MANAGE_MEDIA_PROJECTION)")
     void stopActiveProjection();
 
+    @EnforcePermission("MANAGE_MEDIA_PROJECTION")
     @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
             + ".permission.MANAGE_MEDIA_PROJECTION)")
     void notifyActiveProjectionCapturedContentResized(int width, int height);
 
+    @EnforcePermission("MANAGE_MEDIA_PROJECTION")
     @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
                 + ".permission.MANAGE_MEDIA_PROJECTION)")
     void notifyActiveProjectionCapturedContentVisibilityChanged(boolean isVisible);
@@ -123,6 +128,7 @@
      * @param projection      the non-null projection the session describes
      * @throws SecurityException If the provided projection is not current.
      */
+  @EnforcePermission("MANAGE_MEDIA_PROJECTION")
   @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
             + ".permission.MANAGE_MEDIA_PROJECTION)")
     boolean setContentRecordingSession(in ContentRecordingSession incomingSession,
diff --git a/media/java/android/media/projection/IMediaProjectionWatcherCallback.aidl b/media/java/android/media/projection/IMediaProjectionWatcherCallback.aidl
index 2231ce1..e46d34e 100644
--- a/media/java/android/media/projection/IMediaProjectionWatcherCallback.aidl
+++ b/media/java/android/media/projection/IMediaProjectionWatcherCallback.aidl
@@ -17,9 +17,22 @@
 package android.media.projection;
 
 import android.media.projection.MediaProjectionInfo;
+import android.view.ContentRecordingSession;
 
 /** {@hide} */
 oneway interface IMediaProjectionWatcherCallback {
     void onStart(in MediaProjectionInfo info);
     void onStop(in MediaProjectionInfo info);
+    /**
+     * Called when the {@link ContentRecordingSession} was set for the current media
+     * projection.
+     *
+     * @param info    always present and contains information about the media projection host.
+     * @param session the recording session for the current media projection. Can be
+     *                {@code null} when the recording will stop.
+     */
+    void onRecordingSessionSet(
+        in MediaProjectionInfo info,
+        in @nullable ContentRecordingSession session
+    );
 }
diff --git a/media/java/android/media/projection/MediaProjectionManager.java b/media/java/android/media/projection/MediaProjectionManager.java
index 5703c42..5a68c53 100644
--- a/media/java/android/media/projection/MediaProjectionManager.java
+++ b/media/java/android/media/projection/MediaProjectionManager.java
@@ -29,6 +29,7 @@
 import android.os.ServiceManager;
 import android.util.ArrayMap;
 import android.util.Log;
+import android.view.ContentRecordingSession;
 import android.view.Surface;
 
 import java.util.Map;
@@ -300,7 +301,22 @@
     /** @hide */
     public static abstract class Callback {
         public abstract void onStart(MediaProjectionInfo info);
+
         public abstract void onStop(MediaProjectionInfo info);
+
+        /**
+         * Called when the {@link ContentRecordingSession} was set for the current media
+         * projection.
+         *
+         * @param info    always present and contains information about the media projection host.
+         * @param session the recording session for the current media projection. Can be
+         *                {@code null} when the recording will stop.
+         */
+        public void onRecordingSessionSet(
+                @NonNull MediaProjectionInfo info,
+                @Nullable ContentRecordingSession session
+        ) {
+        }
     }
 
     /** @hide */
@@ -335,5 +351,13 @@
                 }
             });
         }
+
+        @Override
+        public void onRecordingSessionSet(
+                @NonNull final MediaProjectionInfo info,
+                @Nullable final ContentRecordingSession session
+        ) {
+            mHandler.post(() -> mCallback.onRecordingSessionSet(info, session));
+        }
     }
 }
diff --git a/media/java/android/media/projection/OWNERS b/media/java/android/media/projection/OWNERS
index 2273f81..cc9be9c 100644
--- a/media/java/android/media/projection/OWNERS
+++ b/media/java/android/media/projection/OWNERS
@@ -1,3 +1,5 @@
+# Bug component: 1345447
+
 michaelwr@google.com
 santoscordon@google.com
 chaviw@google.com
diff --git a/media/java/android/media/projection/TEST_MAPPING b/media/java/android/media/projection/TEST_MAPPING
index 4324930..a792498 100644
--- a/media/java/android/media/projection/TEST_MAPPING
+++ b/media/java/android/media/projection/TEST_MAPPING
@@ -13,20 +13,6 @@
           "exclude-annotation": "org.junit.Ignore"
         }
       ]
-    },
-    {
-      "name": "CtsMediaProjectionTestCases",
-      "options": [
-        {
-          "exclude-annotation": "android.platform.test.annotations.FlakyTest"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        },
-        {
-          "exclude-annotation": "org.junit.Ignore"
-        }
-      ]
     }
   ]
 }
diff --git a/media/java/android/media/session/MediaSession.java b/media/java/android/media/session/MediaSession.java
index 29e8716..f664fdc 100644
--- a/media/java/android/media/session/MediaSession.java
+++ b/media/java/android/media/session/MediaSession.java
@@ -267,25 +267,33 @@
     }
 
     /**
-     * Set a pending intent for your media button receiver to allow restarting
-     * playback after the session has been stopped. If your app is started in
-     * this way an {@link Intent#ACTION_MEDIA_BUTTON} intent will be sent via
-     * the pending intent.
-     * <p>
-     * The pending intent is recommended to be explicit to follow the security recommendation of
-     * {@link PendingIntent#getActivity}.
+     * Set a pending intent for your media button receiver to allow restarting playback after the
+     * session has been stopped.
+     *
+     * <p>If your app is started in this way an {@link Intent#ACTION_MEDIA_BUTTON} intent will be
+     * sent via the pending intent.
+     *
+     * <p>The provided {@link PendingIntent} must not target an activity. On apps targeting Android
+     * V and above, passing an activity pending intent to this method causes an {@link
+     * IllegalArgumentException}. On apps targeting Android U and below, passing an activity pending
+     * intent causes the call to be ignored. Refer to this <a
+     * href="https://developer.android.com/guide/components/activities/background-starts">guide</a>
+     * for more information.
+     *
+     * <p>The pending intent is recommended to be explicit to follow the security recommendation of
+     * {@link PendingIntent#getService}.
      *
      * @param mbr The {@link PendingIntent} to send the media button event to.
-     * @see PendingIntent#getActivity
-     *
      * @deprecated Use {@link #setMediaButtonBroadcastReceiver(ComponentName)} instead.
+     * @throws IllegalArgumentException if the pending intent targets an activity on apps targeting
+     * Android V and above.
      */
     @Deprecated
     public void setMediaButtonReceiver(@Nullable PendingIntent mbr) {
         try {
             mBinder.setMediaButtonReceiver(mbr);
         } catch (RemoteException e) {
-            Log.wtf(TAG, "Failure in setMediaButtonReceiver.", e);
+            e.rethrowFromSystemServer();
         }
     }
 
@@ -315,7 +323,7 @@
             }
             mBinder.setMediaButtonBroadcastReceiver(broadcastReceiver);
         } catch (RemoteException e) {
-            Log.wtf(TAG, "Failure in setMediaButtonBroadcastReceiver.", e);
+            e.rethrowFromSystemServer();
         }
     }
 
diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java
index 5a56945..d81843d 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -1565,6 +1565,10 @@
         mFrontendCiCamLock.lock();
         mFrontendLock.lock();
         try {
+            if (mFrontendHandle == null) {
+                Log.d(TAG, "Operation cannot be done without frontend");
+                return RESULT_INVALID_STATE;
+            }
             if (mFeOwnerTuner != null) {
                 Log.d(TAG, "Operation cannot be done by sharee of tuner");
                 return RESULT_INVALID_STATE;
@@ -1632,6 +1636,10 @@
     public int disconnectFrontendToCiCam(int ciCamId) {
         acquireTRMSLock("disconnectFrontendToCiCam()");
         try {
+            if (mFrontendHandle == null) {
+                Log.d(TAG, "Operation cannot be done without frontend");
+                return RESULT_INVALID_STATE;
+            }
             if (mFeOwnerTuner != null) {
                 Log.d(TAG, "Operation cannot be done by sharee of tuner");
                 return RESULT_INVALID_STATE;
diff --git a/media/java/android/mtp/OWNERS b/media/java/android/mtp/OWNERS
index 1928ba8..6b5336e 100644
--- a/media/java/android/mtp/OWNERS
+++ b/media/java/android/mtp/OWNERS
@@ -1,6 +1,9 @@
 set noparent
 
-marcone@google.com
+aprasath@google.com
+anothermark@google.com
+kumarashishg@google.com
+sarup@google.com
 jsharkey@android.com
 jameswei@google.com
 rmojumder@google.com
diff --git a/media/jni/OWNERS b/media/jni/OWNERS
index 445672b..96894d1 100644
--- a/media/jni/OWNERS
+++ b/media/jni/OWNERS
@@ -1,5 +1,5 @@
 # extra for MTP related files
-per-file android_mtp_*.cpp=marcone@google.com,jsharkey@android.com,jameswei@google.com,rmojumder@google.com
+per-file android_mtp_*.cpp=aprasath@google.com,anothermark@google.com,kumarashishg@google.com,sarup@google.com,jsharkey@android.com,jameswei@google.com,rmojumder@google.com
 
 # extra for TV related files
 per-file android_media_tv_*=hgchen@google.com,quxiangfang@google.com
diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp
index 1b04f18..feb914f 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -1468,6 +1468,10 @@
         return (int)Result::INVALID_STATE;
     }
 
+    if (mDemuxClient != NULL) {
+        mDemuxClient->setFrontendDataSourceById(feId);
+    }
+
     mSharedFeId = feId;
     return (int)Result::SUCCESS;
 }
diff --git a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioMixUnitTests.java b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioMixUnitTests.java
index ac8a7f3..a26398a 100644
--- a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioMixUnitTests.java
+++ b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioMixUnitTests.java
@@ -117,7 +117,7 @@
         // --- Equality group 3
         final AudioMix recordingAudioMixWithSessionId42AndUid123Render =
                 new AudioMix.Builder(new AudioMixingRule.Builder()
-                        .setTargetMixRole(MIX_ROLE_INJECTOR)
+                        .setTargetMixRole(MIX_ROLE_PLAYERS)
                         .addMixRule(RULE_MATCH_AUDIO_SESSION_ID, 42)
                         .addMixRule(RULE_MATCH_UID, 123).build())
                         .setFormat(INPUT_FORMAT_MONO_16KHZ_PCM)
@@ -205,7 +205,19 @@
     }
 
     @Test
-    public void buildLoopbackWithDevice_throws() {
+    public void buildLoopbackForInjectorMix_success() {
+        final AudioMix audioMix = new AudioMix.Builder(new AudioMixingRule.Builder()
+                .setTargetMixRole(MIX_ROLE_INJECTOR)
+                .addMixRule(RULE_MATCH_UID, 42).build())
+                .setFormat(OUTPUT_FORMAT_MONO_16KHZ_PCM)
+                .setRouteFlags(AudioMix.ROUTE_FLAG_LOOP_BACK).build();
+
+        assertEquals(OUTPUT_FORMAT_MONO_16KHZ_PCM, audioMix.getFormat());
+        assertEquals(AudioMix.ROUTE_FLAG_LOOP_BACK, audioMix.getRouteFlags());
+    }
+
+    @Test
+    public void buildLoopbackWithIncompatibleDevice_throws() {
         assertThrows(IllegalArgumentException.class, () -> new AudioMix.Builder(
                 new AudioMixingRule.Builder()
                         .setTargetMixRole(MIX_ROLE_PLAYERS)
@@ -225,6 +237,28 @@
                 .setRouteFlags(AudioMix.ROUTE_FLAG_RENDER).build());
     }
 
+    @Test
+    public void buildRenderWithInputDevice_throws() {
+        assertThrows(IllegalArgumentException.class, () -> new AudioMix.Builder(
+                new AudioMixingRule.Builder()
+                        .setTargetMixRole(MIX_ROLE_PLAYERS)
+                        .addMixRule(RULE_MATCH_UID, 42).build())
+                .setFormat(OUTPUT_FORMAT_MONO_16KHZ_PCM)
+                .setRouteFlags(AudioMix.ROUTE_FLAG_RENDER)
+                .setDevice(AudioSystem.DEVICE_IN_BUILTIN_MIC, /*address=*/"").build());
+    }
+
+    @Test
+    public void buildRenderWithInjectorMix_throws() {
+        assertThrows(IllegalArgumentException.class, () -> new AudioMix.Builder(
+                new AudioMixingRule.Builder()
+                        .setTargetMixRole(MIX_ROLE_INJECTOR)
+                        .addMixRule(RULE_MATCH_UID, 42).build())
+                .setFormat(OUTPUT_FORMAT_MONO_16KHZ_PCM)
+                .setRouteFlags(AudioMix.ROUTE_FLAG_RENDER)
+                .setDevice(AudioSystem.DEVICE_OUT_SPEAKER, /*address=*/"").build());
+    }
+
 
 
     private static AudioMix writeToAndFromParcel(AudioMix audioMix) {
diff --git a/media/tests/MediaFrameworkTest/Android.bp b/media/tests/MediaFrameworkTest/Android.bp
index 06ec949e..ca20225 100644
--- a/media/tests/MediaFrameworkTest/Android.bp
+++ b/media/tests/MediaFrameworkTest/Android.bp
@@ -20,7 +20,9 @@
         "androidx.test.ext.junit",
         "androidx.test.rules",
         "android-ex-camera2",
+        "testables",
         "testng",
+        "truth-prebuilt",
     ],
     jni_libs: [
         "libdexmakerjvmtiagent",
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/OWNERS b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/OWNERS
new file mode 100644
index 0000000..6d5f82c
--- /dev/null
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/OWNERS
@@ -0,0 +1,2 @@
+# Haptics team also works on Ringtone
+per-file *Ringtone* = file:/services/core/java/com/android/server/vibrator/OWNERS
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/RingtoneTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/RingtoneTest.java
new file mode 100644
index 0000000..3c0c684
--- /dev/null
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/RingtoneTest.java
@@ -0,0 +1,840 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.mediaframeworktest.unit;
+
+import static android.media.Ringtone.MEDIA_SOUND;
+import static android.media.Ringtone.MEDIA_SOUND_AND_VIBRATION;
+import static android.media.Ringtone.MEDIA_VIBRATION;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.doCallRealMethod;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+import android.Manifest;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.res.AssetFileDescriptor;
+import android.media.AudioAttributes;
+import android.media.AudioManager;
+import android.media.IRingtonePlayer;
+import android.media.MediaPlayer;
+import android.media.Ringtone;
+import android.media.audiofx.HapticGenerator;
+import android.net.Uri;
+import android.os.IBinder;
+import android.os.VibrationAttributes;
+import android.os.VibrationEffect;
+import android.os.Vibrator;
+import android.testing.TestableContext;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.mediaframeworktest.R;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runner.RunWith;
+import org.junit.runners.model.Statement;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.io.FileNotFoundException;
+import java.util.ArrayDeque;
+import java.util.Map;
+import java.util.Queue;
+
+@RunWith(AndroidJUnit4.class)
+public class RingtoneTest {
+
+    private static final Uri SOUND_URI = Uri.parse("content://fake-sound-uri");
+
+    private static final AudioAttributes RINGTONE_ATTRIBUTES =
+            audioAttributes(AudioAttributes.USAGE_NOTIFICATION_RINGTONE);
+    private static final AudioAttributes RINGTONE_ATTRIBUTES_WITH_HC =
+            new AudioAttributes.Builder(RINGTONE_ATTRIBUTES).setHapticChannelsMuted(false).build();
+    private static final VibrationAttributes RINGTONE_VIB_ATTRIBUTES =
+            new VibrationAttributes.Builder(RINGTONE_ATTRIBUTES).build();
+
+    private static final VibrationEffect VIBRATION_EFFECT =
+            VibrationEffect.createWaveform(new long[] { 0, 100, 50, 100}, -1);
+    private static final VibrationEffect VIBRATION_EFFECT_REPEATING =
+            VibrationEffect.createWaveform(new long[] { 0, 100, 50, 100, 50}, 1);
+
+    @Rule
+    public final RingtoneInjectablesTrackingTestRule
+            mMediaPlayerRule = new RingtoneInjectablesTrackingTestRule();
+
+    @Captor private ArgumentCaptor<IBinder> mIBinderCaptor;
+    @Mock private IRingtonePlayer mMockRemotePlayer;
+    @Mock private Vibrator mMockVibrator;
+    private AudioManager mSpyAudioManager;
+    private TestableContext mContext;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        TestableContext testContext =
+                new TestableContext(InstrumentationRegistry.getTargetContext(), null);
+        testContext.getTestablePermissions().setPermission(Manifest.permission.VIBRATE,
+                PackageManager.PERMISSION_GRANTED);
+        AudioManager realAudioManager = testContext.getSystemService(AudioManager.class);
+        mSpyAudioManager = spy(realAudioManager);
+        when(mSpyAudioManager.getRingtonePlayer()).thenReturn(mMockRemotePlayer);
+        testContext.addMockSystemService(AudioManager.class, mSpyAudioManager);
+        testContext.addMockSystemService(Vibrator.class, mMockVibrator);
+
+        mContext = spy(testContext);
+    }
+
+    @Test
+    public void testRingtone_fullLifecycleUsingLocalMediaPlayer() throws Exception {
+        MediaPlayer mockMediaPlayer = mMediaPlayerRule.expectLocalMediaPlayer();
+        Ringtone ringtone =
+                newBuilder(MEDIA_SOUND, RINGTONE_ATTRIBUTES).setUri(SOUND_URI).build();
+        assertThat(ringtone).isNotNull();
+        assertThat(ringtone.isUsingRemotePlayer()).isFalse();
+
+        // Verify all the properties.
+        assertThat(ringtone.getEnabledMedia()).isEqualTo(MEDIA_SOUND);
+        assertThat(ringtone.getUri()).isEqualTo(SOUND_URI);
+        assertThat(ringtone.getAudioAttributes()).isEqualTo(RINGTONE_ATTRIBUTES);
+        assertThat(ringtone.getVolume()).isEqualTo(1.0f);
+        assertThat(ringtone.isLooping()).isEqualTo(false);
+        assertThat(ringtone.isHapticGeneratorEnabled()).isEqualTo(false);
+        assertThat(ringtone.getPreferBuiltinDevice()).isFalse();
+        assertThat(ringtone.getVolumeShaperConfig()).isNull();
+        assertThat(ringtone.isLocalOnly()).isFalse();
+
+        // Prepare
+        verifyLocalPlayerSetup(mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES);
+        verify(mockMediaPlayer).setVolume(1.0f);
+        verify(mockMediaPlayer).setLooping(false);
+        verify(mockMediaPlayer).prepare();
+
+        // Play
+        ringtone.play();
+        verifyLocalPlay(mockMediaPlayer);
+
+        // Verify dynamic controls.
+        ringtone.setVolume(0.8f);
+        verify(mockMediaPlayer).setVolume(0.8f);
+        when(mockMediaPlayer.isLooping()).thenReturn(false);
+        ringtone.setLooping(true);
+        verify(mockMediaPlayer).isLooping();
+        verify(mockMediaPlayer).setLooping(true);
+        HapticGenerator mockHapticGenerator =
+                mMediaPlayerRule.expectHapticGenerator(mockMediaPlayer);
+        ringtone.setHapticGeneratorEnabled(true);
+        verify(mockHapticGenerator).setEnabled(true);
+
+        // Release
+        ringtone.stop();
+        verifyLocalStop(mockMediaPlayer);
+
+        // This test is intended to strictly verify all interactions with MediaPlayer in a local
+        // playback case. This shouldn't be necessary in other tests that have the same basic
+        // setup.
+        verifyNoMoreInteractions(mockMediaPlayer);
+        verify(mockHapticGenerator).release();
+        verifyNoMoreInteractions(mockHapticGenerator);
+        verifyZeroInteractions(mMockRemotePlayer);
+        verifyZeroInteractions(mMockVibrator);
+    }
+
+    @Test
+    public void testRingtone_localMediaPlayerWithAudioCoupledOverride() throws Exception {
+        // Audio coupled playback is enabled in the incoming attributes, plus an instruction
+        // to leave the attributes alone. This test verifies that the attributes reach the
+        // media player without changing.
+        final AudioAttributes audioAttributes = RINGTONE_ATTRIBUTES_WITH_HC;
+        MediaPlayer mockMediaPlayer = mMediaPlayerRule.expectLocalMediaPlayer();
+        mMediaPlayerRule.setHasHapticChannels(mockMediaPlayer, true);
+        Ringtone ringtone =
+                newBuilder(MEDIA_SOUND, audioAttributes)
+                        .setUri(SOUND_URI)
+                        .setUseExactAudioAttributes(true)
+                        .build();
+        assertThat(ringtone).isNotNull();
+        assertThat(ringtone.isUsingRemotePlayer()).isFalse();
+
+        // Verify all the properties.
+        assertThat(ringtone.getEnabledMedia()).isEqualTo(MEDIA_SOUND);
+        assertThat(ringtone.getUri()).isEqualTo(SOUND_URI);
+        assertThat(ringtone.getAudioAttributes()).isEqualTo(audioAttributes);
+
+        // Prepare
+        verifyLocalPlayerSetup(mockMediaPlayer, SOUND_URI, audioAttributes);
+        verify(mockMediaPlayer).prepare();
+
+        // Play
+        ringtone.play();
+        verifyLocalPlay(mockMediaPlayer);
+
+        // Release
+        ringtone.stop();
+        verifyLocalStop(mockMediaPlayer);
+
+        verifyZeroInteractions(mMockRemotePlayer);
+        verifyZeroInteractions(mMockVibrator);
+    }
+
+    @Test
+    public void testRingtone_fullLifecycleUsingRemoteMediaPlayer() throws Exception {
+        MediaPlayer mockMediaPlayer = mMediaPlayerRule.expectLocalMediaPlayer();
+        setupFileNotFound(mockMediaPlayer, SOUND_URI);
+        Ringtone ringtone =
+                newBuilder(MEDIA_SOUND, RINGTONE_ATTRIBUTES)
+                .setUri(SOUND_URI)
+                .build();
+        assertThat(ringtone).isNotNull();
+        assertThat(ringtone.isUsingRemotePlayer()).isTrue();
+
+        // Verify all the properties.
+        assertThat(ringtone.getEnabledMedia()).isEqualTo(MEDIA_SOUND);
+        assertThat(ringtone.getUri()).isEqualTo(SOUND_URI);
+        assertThat(ringtone.getAudioAttributes()).isEqualTo(RINGTONE_ATTRIBUTES);
+        assertThat(ringtone.getVolume()).isEqualTo(1.0f);
+        assertThat(ringtone.isLooping()).isEqualTo(false);
+        assertThat(ringtone.isHapticGeneratorEnabled()).isEqualTo(false);
+        assertThat(ringtone.getPreferBuiltinDevice()).isFalse();
+        assertThat(ringtone.getVolumeShaperConfig()).isNull();
+        assertThat(ringtone.isLocalOnly()).isFalse();
+
+        // Initialization did try to create a local media player.
+        verify(mockMediaPlayer).setDataSource(mContext, SOUND_URI);
+        // setDataSource throws file not found, so nothing else will happen on the local player.
+        verify(mockMediaPlayer).release();
+
+        // Delegates to remote media player.
+        ringtone.play();
+        verify(mMockRemotePlayer).playRemoteRingtone(mIBinderCaptor.capture(), eq(SOUND_URI),
+                eq(RINGTONE_ATTRIBUTES), eq(false), eq(MEDIA_SOUND), isNull(),
+                eq(1.0f), eq(false), eq(false), isNull());
+        IBinder remoteToken = mIBinderCaptor.getValue();
+
+        // Verify dynamic controls.
+        ringtone.setVolume(0.8f);
+        verify(mMockRemotePlayer).setVolume(remoteToken, 0.8f);
+        ringtone.setLooping(true);
+        verify(mMockRemotePlayer).setLooping(remoteToken, true);
+        ringtone.setHapticGeneratorEnabled(true);
+        verify(mMockRemotePlayer).setHapticGeneratorEnabled(remoteToken, true);
+
+        ringtone.stop();
+        verify(mMockRemotePlayer).stop(remoteToken);
+        verifyNoMoreInteractions(mMockRemotePlayer);
+        verifyNoMoreInteractions(mockMediaPlayer);
+        verifyZeroInteractions(mMockVibrator);
+    }
+
+    @Test
+    public void testRingtone_localMediaWithVibration() throws Exception {
+        MediaPlayer mockMediaPlayer = mMediaPlayerRule.expectLocalMediaPlayer();
+        when(mMockVibrator.hasVibrator()).thenReturn(true);
+        Ringtone ringtone =
+                newBuilder(MEDIA_SOUND_AND_VIBRATION, RINGTONE_ATTRIBUTES)
+                        .setUri(SOUND_URI)
+                        .setVibrationEffect(VIBRATION_EFFECT)
+                        .build();
+        assertThat(ringtone).isNotNull();
+        assertThat(ringtone.isUsingRemotePlayer()).isFalse();
+        verify(mMockVibrator).hasVibrator();
+
+        // Verify all the properties.
+        assertThat(ringtone.getEnabledMedia()).isEqualTo(MEDIA_SOUND_AND_VIBRATION);
+        assertThat(ringtone.getUri()).isEqualTo(SOUND_URI);
+        assertThat(ringtone.getVibrationEffect()).isEqualTo(VIBRATION_EFFECT);
+
+        // Prepare
+        // Uses attributes with haptic channels enabled, but will use the effect when there aren't
+        // any present.
+        verifyLocalPlayerSetup(mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES_WITH_HC);
+        verify(mockMediaPlayer).setVolume(1.0f);
+        verify(mockMediaPlayer).setLooping(false);
+        verify(mockMediaPlayer).prepare();
+
+        // Play
+        ringtone.play();
+
+        verifyLocalPlay(mockMediaPlayer);
+        verify(mMockVibrator).vibrate(VIBRATION_EFFECT, RINGTONE_VIB_ATTRIBUTES);
+
+        // Verify dynamic controls.
+        ringtone.setVolume(0.8f);
+        verify(mockMediaPlayer).setVolume(0.8f);
+
+        // Set looping doesn't affect an already-started vibration.
+        when(mockMediaPlayer.isLooping()).thenReturn(false);  // Checks original
+        ringtone.setLooping(true);
+        verify(mockMediaPlayer).isLooping();
+        verify(mockMediaPlayer).setLooping(true);
+
+        // This is ignored because there's a vibration effect being used.
+        ringtone.setHapticGeneratorEnabled(true);
+
+        // Release
+        ringtone.stop();
+        verifyLocalStop(mockMediaPlayer);
+        verify(mMockVibrator).cancel(VibrationAttributes.USAGE_RINGTONE);
+
+        // This test is intended to strictly verify all interactions with MediaPlayer in a local
+        // playback case. This shouldn't be necessary in other tests that have the same basic
+        // setup.
+        verifyNoMoreInteractions(mockMediaPlayer);
+        verifyZeroInteractions(mMockRemotePlayer);
+        verifyNoMoreInteractions(mMockVibrator);
+    }
+
+    @Test
+    public void testRingtone_localMediaWithVibrationOnly() throws Exception {
+        when(mMockVibrator.hasVibrator()).thenReturn(true);
+        Ringtone ringtone =
+                newBuilder(MEDIA_VIBRATION, RINGTONE_ATTRIBUTES)
+                        // TODO: set sound uri too in diff test
+                        .setVibrationEffect(VIBRATION_EFFECT)
+                        .build();
+        assertThat(ringtone).isNotNull();
+        assertThat(ringtone.isUsingRemotePlayer()).isFalse();
+        verify(mMockVibrator).hasVibrator();
+
+        // Verify all the properties.
+        assertThat(ringtone.getEnabledMedia()).isEqualTo(MEDIA_VIBRATION);
+        assertThat(ringtone.getUri()).isNull();
+        assertThat(ringtone.getVibrationEffect()).isEqualTo(VIBRATION_EFFECT);
+
+        // Play
+        ringtone.play();
+
+        verify(mMockVibrator).vibrate(VIBRATION_EFFECT, RINGTONE_VIB_ATTRIBUTES);
+
+        // Verify dynamic controls (no-op without sound)
+        ringtone.setVolume(0.8f);
+
+        // Set looping doesn't affect an already-started vibration.
+        ringtone.setLooping(true);
+
+        // This is ignored because there's a vibration effect being used and no sound.
+        ringtone.setHapticGeneratorEnabled(true);
+
+        // Release
+        ringtone.stop();
+        verify(mMockVibrator).cancel(VibrationAttributes.USAGE_RINGTONE);
+
+        // This test is intended to strictly verify all interactions with MediaPlayer in a local
+        // playback case. This shouldn't be necessary in other tests that have the same basic
+        // setup.
+        verifyZeroInteractions(mMockRemotePlayer);
+        verifyNoMoreInteractions(mMockVibrator);
+    }
+
+    @Test
+    public void testRingtone_localMediaWithVibrationOnlyAndSoundUriNoHapticChannels()
+            throws Exception {
+        // A media player will still be created for vibration-only because the vibration can come
+        // from haptic channels on the sound file (although in this case it doesn't).
+        MediaPlayer mockMediaPlayer = mMediaPlayerRule.expectLocalMediaPlayer();
+        mMediaPlayerRule.setHasHapticChannels(mockMediaPlayer, false);
+        when(mMockVibrator.hasVibrator()).thenReturn(true);
+        Ringtone ringtone =
+                newBuilder(MEDIA_VIBRATION, RINGTONE_ATTRIBUTES)
+                        .setUri(SOUND_URI)
+                        .setVibrationEffect(VIBRATION_EFFECT)
+                        .build();
+        assertThat(ringtone).isNotNull();
+        assertThat(ringtone.isUsingRemotePlayer()).isFalse();
+        verify(mMockVibrator).hasVibrator();
+
+        // Verify all the properties.
+        assertThat(ringtone.getEnabledMedia()).isEqualTo(MEDIA_VIBRATION);
+        assertThat(ringtone.getUri()).isEqualTo(SOUND_URI);
+        assertThat(ringtone.getVibrationEffect()).isEqualTo(VIBRATION_EFFECT);
+
+        // Prepare
+        // Uses attributes with haptic channels enabled, but will abandon the MediaPlayer when it
+        // knows there aren't any.
+        verifyLocalPlayerSetup(mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES_WITH_HC);
+        verify(mockMediaPlayer).setVolume(0.0f);  // Vibration-only: sound muted.
+        verify(mockMediaPlayer).setLooping(false);
+        verify(mockMediaPlayer).prepare();
+        verify(mockMediaPlayer).release();  // abandoned: no haptic channels.
+
+        // Play
+        ringtone.play();
+
+        verify(mMockVibrator).vibrate(VIBRATION_EFFECT, RINGTONE_VIB_ATTRIBUTES);
+
+        // Verify dynamic controls (no-op without sound)
+        ringtone.setVolume(0.8f);
+
+        // Set looping doesn't affect an already-started vibration.
+        ringtone.setLooping(true);
+
+        // This is ignored because there's a vibration effect being used and no sound.
+        ringtone.setHapticGeneratorEnabled(true);
+
+        // Release
+        ringtone.stop();
+        verify(mMockVibrator).cancel(VibrationAttributes.USAGE_RINGTONE);
+
+        // This test is intended to strictly verify all interactions with MediaPlayer in a local
+        // playback case. This shouldn't be necessary in other tests that have the same basic
+        // setup.
+        verifyZeroInteractions(mMockRemotePlayer);
+        verifyNoMoreInteractions(mMockVibrator);
+        verifyNoMoreInteractions(mockMediaPlayer);
+    }
+
+    @Test
+    public void testRingtone_localMediaWithVibrationOnlyAndSoundUriWithHapticChannels()
+            throws Exception {
+        MediaPlayer mockMediaPlayer = mMediaPlayerRule.expectLocalMediaPlayer();
+        when(mMockVibrator.hasVibrator()).thenReturn(true);
+        mMediaPlayerRule.setHasHapticChannels(mockMediaPlayer, true);
+        Ringtone ringtone =
+                newBuilder(MEDIA_VIBRATION, RINGTONE_ATTRIBUTES)
+                        .setUri(SOUND_URI)
+                        .setVibrationEffect(VIBRATION_EFFECT)
+                        .build();
+        assertThat(ringtone).isNotNull();
+        assertThat(ringtone.isUsingRemotePlayer()).isFalse();
+        verify(mMockVibrator).hasVibrator();
+
+        // Verify all the properties.
+        assertThat(ringtone.getEnabledMedia()).isEqualTo(MEDIA_VIBRATION);
+        assertThat(ringtone.getUri()).isEqualTo(SOUND_URI);
+        assertThat(ringtone.getVibrationEffect()).isEqualTo(VIBRATION_EFFECT);
+
+        // Prepare
+        // Uses attributes with haptic channels enabled, but will use the effect when there aren't
+        // any present.
+        verifyLocalPlayerSetup(mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES_WITH_HC);
+        verify(mockMediaPlayer).setVolume(0.0f);  // Vibration-only: sound muted.
+        verify(mockMediaPlayer).setLooping(false);
+        verify(mockMediaPlayer).prepare();
+
+        // Play
+        ringtone.play();
+        // Vibrator.vibrate isn't called because the vibration comes from the sound.
+        verifyLocalPlay(mockMediaPlayer);
+
+        // Verify dynamic controls (no-op without sound)
+        ringtone.setVolume(0.8f);
+
+        when(mockMediaPlayer.isLooping()).thenReturn(false);  // Checks original
+        ringtone.setLooping(true);
+        verify(mockMediaPlayer).isLooping();
+        verify(mockMediaPlayer).setLooping(true);
+
+        // This is ignored because it's using haptic channels.
+        ringtone.setHapticGeneratorEnabled(true);
+
+        // Release
+        ringtone.stop();
+        verifyLocalStop(mockMediaPlayer);
+
+        // This test is intended to strictly verify all interactions with MediaPlayer in a local
+        // playback case. This shouldn't be necessary in other tests that have the same basic
+        // setup.
+        verifyZeroInteractions(mMockRemotePlayer);
+        verifyZeroInteractions(mMockVibrator);
+    }
+
+    @Test
+    public void testRingtone_localMediaWithVibrationPrefersHapticChannels() throws Exception {
+        MediaPlayer mockMediaPlayer = mMediaPlayerRule.expectLocalMediaPlayer();
+        mMediaPlayerRule.setHasHapticChannels(mockMediaPlayer, true);
+        when(mMockVibrator.hasVibrator()).thenReturn(true);
+        Ringtone ringtone =
+                newBuilder(MEDIA_SOUND_AND_VIBRATION, RINGTONE_ATTRIBUTES)
+                        .setUri(SOUND_URI)
+                        .setVibrationEffect(VIBRATION_EFFECT)
+                        .build();
+        assertThat(ringtone).isNotNull();
+        assertThat(ringtone.isUsingRemotePlayer()).isFalse();
+        verify(mMockVibrator).hasVibrator();
+
+        // Verify all the properties.
+        assertThat(ringtone.getEnabledMedia()).isEqualTo(MEDIA_SOUND_AND_VIBRATION);
+        assertThat(ringtone.getUri()).isEqualTo(SOUND_URI);
+        assertThat(ringtone.getVibrationEffect()).isEqualTo(VIBRATION_EFFECT);
+
+        // Prepare
+        // The attributes here have haptic channels enabled (unlike above)
+        verifyLocalPlayerSetup(mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES_WITH_HC);
+        verify(mockMediaPlayer).prepare();
+
+        // Play
+        ringtone.play();
+        when(mockMediaPlayer.isPlaying()).thenReturn(true);
+        verifyLocalPlay(mockMediaPlayer);
+
+        // Release
+        ringtone.stop();
+        verifyLocalStop(mockMediaPlayer);
+
+        verifyZeroInteractions(mMockRemotePlayer);
+        // Nothing after the initial hasVibrator - it uses audio-coupled.
+        verifyNoMoreInteractions(mMockVibrator);
+    }
+
+    @Test
+    public void testRingtone_localMediaWithVibrationButSoundMuted() throws Exception {
+        MediaPlayer mockMediaPlayer = mMediaPlayerRule.expectLocalMediaPlayer();
+        mMediaPlayerRule.setHasHapticChannels(mockMediaPlayer, false);
+        doReturn(0).when(mSpyAudioManager)
+                .getStreamVolume(AudioAttributes.toLegacyStreamType(RINGTONE_ATTRIBUTES));
+        when(mMockVibrator.hasVibrator()).thenReturn(true);
+        Ringtone ringtone =
+                newBuilder(MEDIA_SOUND_AND_VIBRATION, RINGTONE_ATTRIBUTES)
+                        .setUri(SOUND_URI)
+                        .setVibrationEffect(VIBRATION_EFFECT)
+                        .build();
+        assertThat(ringtone).isNotNull();
+        assertThat(ringtone.isUsingRemotePlayer()).isFalse();
+        verify(mMockVibrator).hasVibrator();
+
+        // Verify all the properties.
+        assertThat(ringtone.getEnabledMedia()).isEqualTo(MEDIA_SOUND_AND_VIBRATION);
+        assertThat(ringtone.getUri()).isEqualTo(SOUND_URI);
+        assertThat(ringtone.getVibrationEffect()).isEqualTo(VIBRATION_EFFECT);
+
+        // Prepare
+        // The attributes here have haptic channels enabled (unlike above)
+        verifyLocalPlayerSetup(mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES_WITH_HC);
+        verify(mockMediaPlayer).prepare();
+
+        // Play
+        ringtone.play();
+        // The media player is never played, because sound is muted.
+        verify(mockMediaPlayer, never()).start();
+        when(mockMediaPlayer.isPlaying()).thenReturn(true);
+        verify(mMockVibrator).vibrate(VIBRATION_EFFECT, RINGTONE_VIB_ATTRIBUTES);
+
+        // Release
+        ringtone.stop();
+        verify(mockMediaPlayer).release();
+        verify(mMockVibrator).cancel(VibrationAttributes.USAGE_RINGTONE);
+
+        verifyZeroInteractions(mMockRemotePlayer);
+        // Nothing after the initial hasVibrator - it uses audio-coupled.
+        verifyNoMoreInteractions(mMockVibrator);
+    }
+
+    @Test
+    public void testRingtone_nullMediaOnBuilderUsesFallback() throws Exception {
+        AssetFileDescriptor testResourceFd =
+                mContext.getResources().openRawResourceFd(R.raw.shortmp3);
+        // Ensure it will flow as expected.
+        assertThat(testResourceFd).isNotNull();
+        assertThat(testResourceFd.getDeclaredLength()).isAtLeast(0);
+        mContext.getOrCreateTestableResources()
+                .addOverride(com.android.internal.R.raw.fallbackring, testResourceFd);
+
+        MediaPlayer mockMediaPlayer = mMediaPlayerRule.expectLocalMediaPlayer();
+        Ringtone ringtone = newBuilder(MEDIA_SOUND, RINGTONE_ATTRIBUTES)
+                .setUri(null)
+                .build();
+        assertThat(ringtone).isNotNull();
+        assertThat(ringtone.isUsingRemotePlayer()).isFalse();
+
+        // Delegates straight to fallback in local player.
+        // Prepare
+        verifyLocalPlayerFallbackSetup(mockMediaPlayer, testResourceFd, RINGTONE_ATTRIBUTES);
+        verify(mockMediaPlayer).setVolume(1.0f);
+        verify(mockMediaPlayer).setLooping(false);
+        verify(mockMediaPlayer).prepare();
+
+        // Play
+        ringtone.play();
+        verifyLocalPlay(mockMediaPlayer);
+
+        // Release
+        ringtone.stop();
+        verifyLocalStop(mockMediaPlayer);
+
+        verifyNoMoreInteractions(mockMediaPlayer);
+        verifyNoMoreInteractions(mMockRemotePlayer);
+    }
+
+    @Test
+    public void testRingtone_nullMediaOnBuilderUsesFallbackViaRemote() throws Exception {
+        mContext.getOrCreateTestableResources()
+                .addOverride(com.android.internal.R.raw.fallbackring, null);
+        Ringtone ringtone = newBuilder(MEDIA_SOUND, RINGTONE_ATTRIBUTES)
+                .setUri(null)
+                .setLooping(true) // distinct from haptic generator, to match plumbing
+                .build();
+        assertThat(ringtone).isNotNull();
+        // Local player fallback fails as the resource isn't found (no media player creation is
+        // attempted), and then goes on to create the remote player.
+        assertThat(ringtone.isUsingRemotePlayer()).isTrue();
+
+        ringtone.play();
+        verify(mMockRemotePlayer).playRemoteRingtone(mIBinderCaptor.capture(), isNull(),
+                eq(RINGTONE_ATTRIBUTES), eq(false),
+                eq(MEDIA_SOUND), isNull(),
+                eq(1.0f), eq(true), eq(false), isNull());
+        ringtone.stop();
+        verify(mMockRemotePlayer).stop(mIBinderCaptor.getValue());
+        verifyNoMoreInteractions(mMockRemotePlayer);
+    }
+
+    @Test
+    public void testRingtone_noMediaSetOnBuilderFallbackFailsAndNoRemote() throws Exception {
+        mContext.getOrCreateTestableResources()
+                .addOverride(com.android.internal.R.raw.fallbackring, null);
+        Ringtone ringtone = newBuilder(MEDIA_SOUND, RINGTONE_ATTRIBUTES)
+                .setUri(null)
+                .setLocalOnly()
+                .build();
+        // Local player fallback fails as the resource isn't found (no media player creation is
+        // attempted), and since there is no local player, the ringtone ends up having nothing to
+        // do.
+        assertThat(ringtone).isNull();
+    }
+
+    private Ringtone.Builder newBuilder(@Ringtone.RingtoneMedia int ringtoneMedia,
+            AudioAttributes audioAttributes) {
+        return new Ringtone.Builder(mContext, ringtoneMedia, audioAttributes)
+                .setInjectables(mMediaPlayerRule.injectables);
+    }
+
+    private static AudioAttributes audioAttributes(int audioUsage) {
+        return new AudioAttributes.Builder()
+                .setUsage(audioUsage)
+                .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
+                .build();
+    }
+
+    /** Makes the mock get some sort of file access problem. */
+    private void setupFileNotFound(MediaPlayer mockMediaPlayer, Uri uri) throws Exception {
+        doThrow(new FileNotFoundException("Fake file not found"))
+                .when(mockMediaPlayer).setDataSource(any(Context.class), eq(uri));
+    }
+
+    private void verifyLocalPlayerSetup(MediaPlayer mockPlayer, Uri expectedUri,
+            AudioAttributes expectedAudioAttributes) throws Exception {
+        verify(mockPlayer).setDataSource(mContext, expectedUri);
+        verify(mockPlayer).setAudioAttributes(expectedAudioAttributes);
+        verify(mockPlayer).setPreferredDevice(null);
+        verify(mockPlayer).prepare();
+    }
+
+    private void verifyLocalPlayerFallbackSetup(MediaPlayer mockPlayer, AssetFileDescriptor afd,
+            AudioAttributes expectedAudioAttributes) throws Exception {
+        // This is very specific but it's a simple way to test that the test resource matches.
+        if (afd.getDeclaredLength() < 0) {
+            verify(mockPlayer).setDataSource(afd.getFileDescriptor());
+        } else {
+            verify(mockPlayer).setDataSource(afd.getFileDescriptor(),
+                    afd.getStartOffset(),
+                    afd.getDeclaredLength());
+        }
+        verify(mockPlayer).setAudioAttributes(expectedAudioAttributes);
+        verify(mockPlayer).setPreferredDevice(null);
+        verify(mockPlayer).prepare();
+    }
+
+    private void verifyLocalPlay(MediaPlayer mockMediaPlayer) {
+        verify(mockMediaPlayer).setOnCompletionListener(any());
+        verify(mockMediaPlayer).start();
+    }
+
+    private void verifyLocalStop(MediaPlayer mockMediaPlayer) {
+        verify(mockMediaPlayer).stop();
+        verify(mockMediaPlayer).setOnCompletionListener(isNull());
+        verify(mockMediaPlayer).reset();
+        verify(mockMediaPlayer).release();
+    }
+
+    /**
+     * This rule ensures that all expected media player creations from the factory do actually
+     * occur. The reason for this level of control is that creating a media player is fairly
+     * expensive and blocking, so we do want unit tests of this class to "declare" interactions
+     * of all created media players.
+     *
+     * This needs to be a TestRule so that the teardown assertions can be skipped if the test has
+     * failed (and media player assertions may just be a distracting side effect). Otherwise, the
+     * teardown failures hide the real test ones.
+     */
+    public static class RingtoneInjectablesTrackingTestRule implements TestRule {
+        public Ringtone.Injectables injectables = new TestInjectables();
+        public boolean hapticGeneratorAvailable = true;
+
+        // Queue of (local) media players, in order of expected creation. Enqueue using
+        // expectNewMediaPlayer(), dequeued by the media player factory passed to Ringtone.
+        // This queue is asserted to be empty at the end of the test.
+        private Queue<MediaPlayer> mMockMediaPlayerQueue = new ArrayDeque<>();
+
+        // Similar to media players, but for haptic generator, which also needs releasing.
+        private Map<MediaPlayer, HapticGenerator> mMockHapticGeneratorMap = new ArrayMap<>();
+
+        // Media players with haptic channels.
+        private ArraySet<MediaPlayer> mHapticChannels = new ArraySet<>();
+
+        @Override
+        public Statement apply(Statement base, Description description) {
+            return new Statement() {
+                @Override
+                public void evaluate() throws Throwable {
+                    base.evaluate();
+                    // Only assert if the test didn't fail (base.evaluate() would throw).
+                    assertWithMessage("Test setup an expectLocalMediaPlayer but it wasn't consumed")
+                            .that(mMockMediaPlayerQueue).isEmpty();
+                    // Only assert if the test didn't fail (base.evaluate() would throw).
+                    assertWithMessage(
+                            "Test setup an expectLocalHapticGenerator but it wasn't consumed")
+                            .that(mMockHapticGeneratorMap).isEmpty();
+                }
+            };
+        }
+
+        private TestMediaPlayer expectLocalMediaPlayer() {
+            TestMediaPlayer mockMediaPlayer = Mockito.mock(TestMediaPlayer.class);
+            // Delegate to simulated methods. This means they can be verified but also reflect
+            // realistic transitions from the TestMediaPlayer.
+            doCallRealMethod().when(mockMediaPlayer).start();
+            doCallRealMethod().when(mockMediaPlayer).stop();
+            doCallRealMethod().when(mockMediaPlayer).setLooping(anyBoolean());
+            when(mockMediaPlayer.isLooping()).thenCallRealMethod();
+            when(mockMediaPlayer.isLooping()).thenCallRealMethod();
+            mMockMediaPlayerQueue.add(mockMediaPlayer);
+            return mockMediaPlayer;
+        }
+
+        private HapticGenerator expectHapticGenerator(MediaPlayer mockMediaPlayer) {
+            HapticGenerator mockHapticGenerator = Mockito.mock(HapticGenerator.class);
+            // A test should never want this.
+            assertWithMessage("Can't expect a second haptic generator created "
+                    + "for one media player")
+                    .that(mMockHapticGeneratorMap.put(mockMediaPlayer, mockHapticGenerator))
+                    .isNull();
+            return mockHapticGenerator;
+        }
+
+        private void setHasHapticChannels(MediaPlayer mp, boolean hasHapticChannels) {
+            if (hasHapticChannels) {
+                mHapticChannels.add(mp);
+            } else {
+                mHapticChannels.remove(mp);
+            }
+        }
+
+        private class TestInjectables extends Ringtone.Injectables {
+            @Override
+            public MediaPlayer newMediaPlayer() {
+                assertWithMessage(
+                        "Unexpected MediaPlayer creation. Bug or need expectNewMediaPlayer")
+                        .that(mMockMediaPlayerQueue)
+                        .isNotEmpty();
+                return mMockMediaPlayerQueue.remove();
+            }
+
+            @Override
+            public boolean isHapticGeneratorAvailable() {
+                return hapticGeneratorAvailable;
+            }
+
+            @Override
+            public HapticGenerator createHapticGenerator(MediaPlayer mediaPlayer) {
+                HapticGenerator mockHapticGenerator = mMockHapticGeneratorMap.remove(mediaPlayer);
+                assertWithMessage("Unexpected HapticGenerator creation. "
+                        + "Bug or need expectHapticGenerator")
+                        .that(mockHapticGenerator)
+                        .isNotNull();
+                return mockHapticGenerator;
+            }
+
+            @Override
+            public boolean isHapticPlaybackSupported() {
+                return true;
+            }
+
+            @Override
+            public boolean hasHapticChannels(MediaPlayer mp) {
+                return mHapticChannels.contains(mp);
+            }
+        }
+    }
+
+    /**
+     * MediaPlayer relies on a native backend and so its necessary to intercept calls from
+     * fake usage hitting them.
+     *
+     * Mocks don't work directly on native calls, but if they're overridden then it does work.
+     * Some basic state faking is also done to make the mocks more realistic.
+     */
+    private static class TestMediaPlayer extends MediaPlayer {
+        private boolean mIsPlaying = false;
+        private boolean mIsLooping = false;
+
+        @Override
+        public void start() {
+            mIsPlaying = true;
+        }
+
+        @Override
+        public void stop() {
+            mIsPlaying = false;
+        }
+
+        @Override
+        public void setLooping(boolean value) {
+            mIsLooping = value;
+        }
+
+        @Override
+        public boolean isLooping() {
+            return mIsLooping;
+        }
+
+        @Override
+        public boolean isPlaying() {
+            return mIsPlaying;
+        }
+
+        void simulatePlayingFinished() {
+            if (!mIsPlaying) {
+                throw new IllegalStateException(
+                        "Attempted to pretend playing finished when not playing");
+            }
+            mIsPlaying = false;
+        }
+    }
+}
diff --git a/media/tests/MtpTests/OWNERS b/media/tests/MtpTests/OWNERS
index 1928ba8..6b5336e 100644
--- a/media/tests/MtpTests/OWNERS
+++ b/media/tests/MtpTests/OWNERS
@@ -1,6 +1,9 @@
 set noparent
 
-marcone@google.com
+aprasath@google.com
+anothermark@google.com
+kumarashishg@google.com
+sarup@google.com
 jsharkey@android.com
 jameswei@google.com
 rmojumder@google.com
diff --git a/mime/java-res/android.mime.types b/mime/java-res/android.mime.types
index cb74cfc..fd785a4 100644
--- a/mime/java-res/android.mime.types
+++ b/mime/java-res/android.mime.types
@@ -52,6 +52,7 @@
 ?application/sdp sdp
 ?application/smil+xml smil
 ?application/ttml+xml ttml dfxp
+?application/vnd.android.haptics.vibration+xml ahv
 ?application/vnd.android.ota ota
 ?application/vnd.apple.mpegurl m3u8
 ?application/vnd.ms-pki.stl stl
diff --git a/native/android/storage_manager.cpp b/native/android/storage_manager.cpp
index 9e0a6eb..294ca9c 100644
--- a/native/android/storage_manager.cpp
+++ b/native/android/storage_manager.cpp
@@ -86,7 +86,7 @@
             return nullptr;
         }
 
-        String16 fileName(obbFile->getFileName());
+        String16 fileName(canonicalPath);
         String16 packageName(obbFile->getPackageName());
         size_t length;
         const unsigned char* salt = obbFile->getSalt(&length);
diff --git a/native/android/system_fonts.cpp b/native/android/system_fonts.cpp
index fe3132e..de7ea70 100644
--- a/native/android/system_fonts.cpp
+++ b/native/android/system_fonts.cpp
@@ -251,14 +251,15 @@
                         locale.emplace(minikin::getLocaleString(localeId));
                     }
                     std::vector<std::pair<uint32_t, float>> axes;
-                    for (const auto& [tag, value] : font->typeface()->GetAxes()) {
+                    for (const auto& [tag, value] : font->baseTypeface()->GetAxes()) {
                         axes.push_back(std::make_pair(tag, value));
                     }
 
-                    fonts.insert({font->typeface()->GetFontPath(), std::move(locale),
+                    fonts.insert({font->baseTypeface()->GetFontPath(), std::move(locale),
                                   font->style().weight(),
                                   font->style().slant() == minikin::FontStyle::Slant::ITALIC,
-                                  static_cast<uint32_t>(font->typeface()->GetFontIndex()), axes});
+                                  static_cast<uint32_t>(font->baseTypeface()->GetFontIndex()),
+                                  axes});
                 }
             });
 
@@ -323,7 +324,7 @@
                     .font;
     std::unique_ptr<AFont> result = std::make_unique<AFont>();
     const android::MinikinFontSkia* minikinFontSkia =
-            reinterpret_cast<android::MinikinFontSkia*>(font->typeface().get());
+            reinterpret_cast<android::MinikinFontSkia*>(font->baseTypeface().get());
     result->mFilePath = minikinFontSkia->getFilePath();
     result->mWeight = font->style().weight();
     result->mItalic = font->style().slant() == minikin::FontStyle::Slant::ITALIC;
diff --git a/native/webview/OWNERS b/native/webview/OWNERS
index 580bb0f..7e27dc8 100644
--- a/native/webview/OWNERS
+++ b/native/webview/OWNERS
@@ -1,4 +1,4 @@
+# Bug component: 76427
 boliu@google.com
-changwan@google.com
-tobiasjs@google.com
+ntfschr@google.com
 torne@google.com
diff --git a/packages/CarrierDefaultApp/assets/slice_purchase_test.html b/packages/CarrierDefaultApp/assets/slice_purchase_test.html
index 67d2184..ad18a9d 100644
--- a/packages/CarrierDefaultApp/assets/slice_purchase_test.html
+++ b/packages/CarrierDefaultApp/assets/slice_purchase_test.html
@@ -20,7 +20,7 @@
     <meta charset="UTF-8">
     <meta name="description" content="
     This is a HTML page that calls and verifies responses from the @JavascriptInterface functions of
-    SlicePurchaseWebInterface. Test slice purchase application behavior using ADB shell commands and
+    DataBoostWebServiceFlow. Test slice purchase application behavior using ADB shell commands and
     the APIs below:
 
     FROM TERMINAL:
@@ -65,8 +65,8 @@
     <p id="requested_capability"></p>
 
     <h2>Notify purchase successful</h2>
-    <button type="button" onclick="testNotifyPurchaseSuccessful(60000)">
-        Notify purchase successful for 1 minute
+    <button type="button" onclick="testNotifyPurchaseSuccessful()">
+        Notify purchase successful
     </button>
     <p id="purchase_successful"></p>
 
@@ -75,5 +75,13 @@
         Notify purchase failed
     </button>
     <p id="purchase_failed"></p>
+
+    <h2>Dismiss flow</h2>
+    <button type="button" onclick="testDismissFlow()">
+        Dismiss flow
+    </button>
+    <p id="dismiss_flow"></p>
+
+    <h2>Test <a href="http://www.google.com">hyperlink</a></h2>
 </body>
 </html>
diff --git a/packages/CarrierDefaultApp/assets/slice_purchase_test.js b/packages/CarrierDefaultApp/assets/slice_purchase_test.js
index 02c4fea..be397a1 100644
--- a/packages/CarrierDefaultApp/assets/slice_purchase_test.js
+++ b/packages/CarrierDefaultApp/assets/slice_purchase_test.js
@@ -15,19 +15,25 @@
  */
 
 function testGetRequestedCapability() {
-    let capability = SlicePurchaseWebInterface.getRequestedCapability();
+    let capability = DataBoostWebServiceFlow.getRequestedCapability();
     document.getElementById("requested_capability").innerHTML =
             "Premium capability requested: " + capability;
 }
 
-function testNotifyPurchaseSuccessful(duration_ms_long = 0) {
-    SlicePurchaseWebInterface.notifyPurchaseSuccessful(duration_ms_long);
+function testNotifyPurchaseSuccessful() {
+    DataBoostWebServiceFlow.notifyPurchaseSuccessful();
     document.getElementById("purchase_successful").innerHTML =
-            "Notified purchase success for duration: " + duration_ms_long;
+            "Notified purchase successful.";
 }
 
 function testNotifyPurchaseFailed(failure_code = 0, failure_reason = "unknown") {
-    SlicePurchaseWebInterface.notifyPurchaseFailed(failure_code, failure_reason);
+    DataBoostWebServiceFlow.notifyPurchaseFailed(failure_code, failure_reason);
     document.getElementById("purchase_failed").innerHTML =
             "Notified purchase failed.";
 }
+
+function testDismissFlow() {
+    DataBoostWebServiceFlow.dismissFlow();
+    document.getElementById("dismiss_flow").innerHTML =
+            "Called dismiss flow.";
+}
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/DataBoostWebServiceFlow.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/DataBoostWebServiceFlow.java
new file mode 100644
index 0000000..4500a22
--- /dev/null
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/DataBoostWebServiceFlow.java
@@ -0,0 +1,108 @@
+/*
+ * 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.carrierdefaultapp;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.telephony.TelephonyManager;
+import android.webkit.JavascriptInterface;
+
+import com.android.phone.slice.SlicePurchaseController;
+
+/**
+ * Data boost web service flow interface allowing carrier websites to send responses back to the
+ * slice purchase application using JavaScript.
+ */
+public class DataBoostWebServiceFlow {
+    @NonNull SlicePurchaseActivity mActivity;
+
+    public DataBoostWebServiceFlow(@NonNull SlicePurchaseActivity activity) {
+        mActivity = activity;
+    }
+
+    /**
+     * Interface method allowing the carrier website to get the premium capability
+     * that was requested to purchase.
+     *
+     * This can be called using the JavaScript below:
+     * <script type="text/javascript">
+     *     function getRequestedCapability(duration) {
+     *         DataBoostWebServiceFlow.getRequestedCapability();
+     *     }
+     * </script>
+     */
+    @JavascriptInterface
+    @TelephonyManager.PremiumCapability public int getRequestedCapability() {
+        return mActivity.mCapability;
+    }
+
+    /**
+     * Interface method allowing the carrier website to notify the slice purchase application of
+     * a successful premium capability purchase and the duration for which the premium capability is
+     * purchased.
+     *
+     * This can be called using the JavaScript below:
+     * <script type="text/javascript">
+     *     function notifyPurchaseSuccessful() {
+     *         DataBoostWebServiceFlow.notifyPurchaseSuccessful();
+     *     }
+     * </script>
+     */
+    @JavascriptInterface
+    public void notifyPurchaseSuccessful() {
+        mActivity.onPurchaseSuccessful();
+    }
+
+    /**
+     * Interface method allowing the carrier website to notify the slice purchase application of
+     * a failed premium capability purchase.
+     *
+     * This can be called using the JavaScript below:
+     * <script type="text/javascript">
+     *     function notifyPurchaseFailed(failure_code = 0, failure_reason = "unknown") {
+     *         DataBoostWebServiceFlow.notifyPurchaseFailed();
+     *     }
+     * </script>
+     *
+     * @param failureCode The failure code.
+     * @param failureReason If the failure code is
+     *                      {@link SlicePurchaseController#FAILURE_CODE_UNKNOWN},
+     *                      the human-readable reason for failure.
+     */
+    @JavascriptInterface
+    public void notifyPurchaseFailed(@SlicePurchaseController.FailureCode int failureCode,
+            @Nullable String failureReason) {
+        mActivity.onPurchaseFailed(failureCode, failureReason);
+    }
+
+    /**
+     * Interface method allowing the carrier website to notify the slice purchase application that
+     * the service flow ended prematurely. This can be due to user action, an error in the
+     * web sheet logic, or an error on the network side.
+     *
+     * This can be called using the JavaScript below:
+     * <script type="text/javascript">
+     *     function dismissFlow() {
+     *         DataBoostWebServiceFlow.dismissFlow();
+     *     }
+     * </script>
+     */
+    @JavascriptInterface
+    public void dismissFlow() {
+        mActivity.onDismissFlow();
+    }
+}
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java
index 946185a..fcc4ec1 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java
@@ -27,26 +27,28 @@
 import android.text.TextUtils;
 import android.util.Log;
 import android.view.KeyEvent;
+import android.webkit.CookieManager;
 import android.webkit.WebView;
+import android.webkit.WebViewClient;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.phone.slice.SlicePurchaseController;
 
 import java.net.URL;
-import java.util.concurrent.TimeUnit;
+import java.util.Base64;
 
 /**
  * Activity that launches when the user clicks on the performance boost notification.
  * This will open a {@link WebView} for the carrier website to allow the user to complete the
  * premium capability purchase.
  * The carrier website can get the requested premium capability using the JavaScript interface
- * method {@code SlicePurchaseWebInterface.getRequestedCapability()}.
+ * method {@code DataBoostWebServiceFlow.getRequestedCapability()}.
  * If the purchase is successful, the carrier website shall notify the slice purchase application
  * using the JavaScript interface method
- * {@code SlicePurchaseWebInterface.notifyPurchaseSuccessful(duration)}, where {@code duration} is
- * the optional duration of the performance boost.
+ * {@code DataBoostWebServiceFlow.notifyPurchaseSuccessful()}.
  * If the purchase was not successful, the carrier website shall notify the slice purchase
  * application using the JavaScript interface method
- * {@code SlicePurchaseWebInterface.notifyPurchaseFailed(code, reason)}, where {@code code} is the
+ * {@code DataBoostWebServiceFlow.notifyPurchaseFailed(code, reason)}, where {@code code} is the
  * {@link SlicePurchaseController.FailureCode} indicating the reason for failure and {@code reason}
  * is the human-readable reason for failure if the failure code is
  * {@link SlicePurchaseController#FAILURE_CODE_UNKNOWN}.
@@ -56,11 +58,17 @@
 public class SlicePurchaseActivity extends Activity {
     private static final String TAG = "SlicePurchaseActivity";
 
+    private static final int CONTENTS_TYPE_UNSPECIFIED = 0;
+    private static final int CONTENTS_TYPE_JSON = 1;
+    private static final int CONTENTS_TYPE_XML = 2;
+
     @NonNull private WebView mWebView;
     @NonNull private Context mApplicationContext;
     @NonNull private Intent mIntent;
     @NonNull private URL mUrl;
     @TelephonyManager.PremiumCapability protected int mCapability;
+    @Nullable private String mUserData;
+    private int mContentsType;
     private boolean mIsUserTriggeredFinish;
 
     @Override
@@ -72,6 +80,7 @@
         mCapability = mIntent.getIntExtra(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY,
                 SlicePurchaseController.PREMIUM_CAPABILITY_INVALID);
         String url = mIntent.getStringExtra(SlicePurchaseController.EXTRA_PURCHASE_URL);
+        mUserData = mIntent.getStringExtra(SlicePurchaseController.EXTRA_USER_DATA);
         mApplicationContext = getApplicationContext();
         mIsUserTriggeredFinish = true;
         logd("onCreate: subId=" + subId + ", capability="
@@ -81,7 +90,17 @@
         SlicePurchaseBroadcastReceiver.cancelNotification(mApplicationContext, mCapability);
 
         // Verify purchase URL is valid
-        mUrl = SlicePurchaseBroadcastReceiver.getPurchaseUrl(url);
+        String contentsType = mIntent.getStringExtra(SlicePurchaseController.EXTRA_CONTENTS_TYPE);
+        mContentsType = CONTENTS_TYPE_UNSPECIFIED;
+        if (!TextUtils.isEmpty(contentsType)) {
+            if (contentsType.equals("json")) {
+                mContentsType = CONTENTS_TYPE_JSON;
+            } else if (contentsType.equals("xml")) {
+                mContentsType = CONTENTS_TYPE_XML;
+            }
+        }
+        mUrl = SlicePurchaseBroadcastReceiver.getPurchaseUrl(url, mUserData,
+                mContentsType == CONTENTS_TYPE_UNSPECIFIED);
         if (mUrl == null) {
             String error = "Unable to create a purchase URL.";
             loge(error);
@@ -95,6 +114,20 @@
             return;
         }
 
+        // Verify user data exists if contents type is specified
+        if (mContentsType != CONTENTS_TYPE_UNSPECIFIED && TextUtils.isEmpty(mUserData)) {
+            String error = "Contents type was specified but user data does not exist.";
+            loge(error);
+            Intent data = new Intent();
+            data.putExtra(SlicePurchaseController.EXTRA_FAILURE_CODE,
+                    SlicePurchaseController.FAILURE_CODE_NO_USER_DATA);
+            data.putExtra(SlicePurchaseController.EXTRA_FAILURE_REASON, error);
+            SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponseWithData(mApplicationContext,
+                    mIntent, SlicePurchaseController.EXTRA_INTENT_CARRIER_ERROR, data);
+            finishAndRemoveTask();
+            return;
+        }
+
         // Verify intent is valid
         if (!SlicePurchaseBroadcastReceiver.isIntentValid(mIntent)) {
             loge("Not starting SlicePurchaseActivity with an invalid Intent: " + mIntent);
@@ -114,19 +147,15 @@
             return;
         }
 
-        // Create and configure WebView
-        setupWebView();
+        // Clear any cookies that might be persisted from previous sessions before loading WebView
+        CookieManager.getInstance().removeAllCookies(value -> setupWebView());
     }
 
-    protected void onPurchaseSuccessful(long duration) {
+    protected void onPurchaseSuccessful() {
         logd("onPurchaseSuccessful: Carrier website indicated successfully purchased premium "
-                + "capability " + TelephonyManager.convertPremiumCapabilityToString(mCapability)
-                + (duration > 0 ? " for " + TimeUnit.MILLISECONDS.toMinutes(duration) + " minutes."
-                : "."));
-        Intent intent = new Intent();
-        intent.putExtra(SlicePurchaseController.EXTRA_PURCHASE_DURATION, duration);
-        SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponseWithData(mApplicationContext,
-                mIntent, SlicePurchaseController.EXTRA_INTENT_SUCCESS, intent);
+                + "capability " + TelephonyManager.convertPremiumCapabilityToString(mCapability));
+        SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponse(
+                mIntent, SlicePurchaseController.EXTRA_INTENT_SUCCESS);
         finishAndRemoveTask();
     }
 
@@ -143,6 +172,14 @@
         finishAndRemoveTask();
     }
 
+    protected void onDismissFlow() {
+        logd("onDismissFlow: Dismiss flow called while purchasing premium capability "
+                + TelephonyManager.convertPremiumCapabilityToString(mCapability));
+        SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponse(
+                mIntent, SlicePurchaseController.EXTRA_INTENT_REQUEST_FAILED);
+        finishAndRemoveTask();
+    }
+
     @Override
     public boolean onKeyDown(int keyCode, @NonNull KeyEvent event) {
         // Pressing back in the WebView will go to the previous page instead of closing
@@ -173,24 +210,57 @@
     private void setupWebView() {
         // Create WebView
         mWebView = new WebView(this);
+        mWebView.setWebViewClient(new WebViewClient());
 
         // Enable JavaScript for the carrier purchase website to send results back to
         //  the slice purchase application.
         mWebView.getSettings().setJavaScriptEnabled(true);
         mWebView.addJavascriptInterface(
-                new SlicePurchaseWebInterface(this), "SlicePurchaseWebInterface");
+                new DataBoostWebServiceFlow(this), "DataBoostWebServiceFlow");
 
         // Display WebView
         setContentView(mWebView);
 
-        // Load the URL
-        String userData = mIntent.getStringExtra(SlicePurchaseController.EXTRA_USER_DATA);
-        if (TextUtils.isEmpty(userData)) {
-            logd("Starting WebView with url: " + mUrl.toString());
-            mWebView.loadUrl(mUrl.toString());
+        // Start the WebView
+        startWebView(mWebView, mUrl.toString(), mContentsType, mUserData);
+    }
+
+    /**
+     * Send the URL to the WebView as either a GET or POST request, based on the contents type:
+     * <ul>
+     *     <li>
+     *         CONTENTS_TYPE_UNSPECIFIED:
+     *         If the user data exists, append it to the purchase URL and load it as a GET request.
+     *         If the user data does not exist, load just the purchase URL as a GET request.
+     *     </li>
+     *     <li>
+     *         CONTENTS_TYPE_JSON or CONTENTS_TYPE_XML:
+     *         The user data must exist. Send the JSON or XML formatted user data in a POST request.
+     *         If the user data is encoded, it must be prefaced by {@code encodedValue=} and will be
+     *         encoded in Base64. Decode the user data and send it in the POST request.
+     *     </li>
+     * </ul>
+     * @param webView The WebView to start.
+     * @param url The URL to start the WebView with.
+     * @param contentsType The contents type of the userData.
+     * @param userData The user data to send with the GET or POST request, if it exists.
+     */
+    @VisibleForTesting
+    public static void startWebView(@NonNull WebView webView, @NonNull String url, int contentsType,
+            @Nullable String userData) {
+        if (contentsType == CONTENTS_TYPE_UNSPECIFIED) {
+            logd("Starting WebView GET with url: " + url);
+            webView.loadUrl(url);
         } else {
-            logd("Starting WebView with url: " + mUrl.toString() + ", userData=" + userData);
-            mWebView.postUrl(mUrl.toString(), userData.getBytes());
+            byte[] data = userData.getBytes();
+            String[] split = userData.split("encodedValue=");
+            if (split.length > 1) {
+                logd("Decoding encoded value: " + split[1]);
+                data = Base64.getDecoder().decode(split[1]);
+            }
+            logd("Starting WebView POST with url: " + url + ", contentsType: " + contentsType
+                    + ", data: " + new String(data));
+            webView.postUrl(url, data);
         }
     }
 
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiver.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiver.java
index 23b9766..eccf604 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiver.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiver.java
@@ -173,7 +173,9 @@
         }
 
         String purchaseUrl = intent.getStringExtra(SlicePurchaseController.EXTRA_PURCHASE_URL);
-        if (getPurchaseUrl(purchaseUrl) == null) {
+        String userData = intent.getStringExtra(SlicePurchaseController.EXTRA_USER_DATA);
+        String contentsType = intent.getStringExtra(SlicePurchaseController.EXTRA_CONTENTS_TYPE);
+        if (getPurchaseUrl(purchaseUrl, userData, TextUtils.isEmpty(contentsType)) == null) {
             loge("isIntentValid: invalid purchase URL: " + purchaseUrl);
             return false;
         }
@@ -189,18 +191,47 @@
                 && isPendingIntentValid(intent, SlicePurchaseController.EXTRA_INTENT_REQUEST_FAILED)
                 && isPendingIntentValid(intent,
                         SlicePurchaseController.EXTRA_INTENT_NOT_DEFAULT_DATA_SUBSCRIPTION)
+                && isPendingIntentValid(intent,
+                        SlicePurchaseController.EXTRA_INTENT_NOTIFICATIONS_DISABLED)
                 && isPendingIntentValid(intent, SlicePurchaseController.EXTRA_INTENT_SUCCESS)
                 && isPendingIntentValid(intent,
                         SlicePurchaseController.EXTRA_INTENT_NOTIFICATION_SHOWN);
     }
 
     /**
+     * Get the {@link URL} from the given purchase URL String and user data, if it is valid.
+     *
+     * @param purchaseUrl The purchase URL String to use to create the URL.
+     * @param userData The user data parameter from the entitlement server.
+     * @param shouldAppendUserData If this is {@code true} and the {@code userData} exists,
+     *        the {@code userData} should be appended to the {@code purchaseUrl} to create the URL.
+     *        If this is false, only the {@code purchaseUrl} should be used and the {@code userData}
+     *        will be sent as data to the POST request instead.
+     * @return The URL from the given purchase URL and user data or {@code null} if it is invalid.
+     */
+    @Nullable public static URL getPurchaseUrl(@Nullable String purchaseUrl,
+            @Nullable String userData, boolean shouldAppendUserData) {
+        if (purchaseUrl == null) {
+            return null;
+        }
+        // Only append user data if it exists, otherwise just return the purchase URL
+        if (!shouldAppendUserData || TextUtils.isEmpty(userData)) {
+            return getPurchaseUrl(purchaseUrl);
+        }
+        URL url = getPurchaseUrl(purchaseUrl + "?" + userData);
+        if (url == null) {
+            url = getPurchaseUrl(purchaseUrl);
+        }
+        return url;
+    }
+
+    /**
      * Get the {@link URL} from the given purchase URL String, if it is valid.
      *
      * @param purchaseUrl The purchase URL String to use to create the URL.
      * @return The purchase URL from the given String or {@code null} if it is invalid.
      */
-    @Nullable public static URL getPurchaseUrl(@Nullable String purchaseUrl) {
+    @Nullable private static URL getPurchaseUrl(@Nullable String purchaseUrl) {
         if (!URLUtil.isValidUrl(purchaseUrl)) {
             return null;
         }
@@ -247,6 +278,8 @@
             case SlicePurchaseController.EXTRA_INTENT_REQUEST_FAILED: return "request failed";
             case SlicePurchaseController.EXTRA_INTENT_NOT_DEFAULT_DATA_SUBSCRIPTION:
                 return "not default data subscription";
+            case SlicePurchaseController.EXTRA_INTENT_NOTIFICATIONS_DISABLED:
+                return "notifications disabled";
             case SlicePurchaseController.EXTRA_INTENT_SUCCESS: return "success";
             case SlicePurchaseController.EXTRA_INTENT_NOTIFICATION_SHOWN:
                 return "notification shown";
@@ -292,26 +325,45 @@
     }
 
     private void onDisplayPerformanceBoostNotification(@NonNull Context context,
-            @NonNull Intent intent, boolean repeat) {
-        if (!repeat && !isIntentValid(intent)) {
+            @NonNull Intent intent, boolean localeChanged) {
+        if (!localeChanged && !isIntentValid(intent)) {
             sendSlicePurchaseAppResponse(intent,
                     SlicePurchaseController.EXTRA_INTENT_REQUEST_FAILED);
             return;
         }
 
         Resources res = getResources(context);
-        NotificationChannel channel = new NotificationChannel(
-                PERFORMANCE_BOOST_NOTIFICATION_CHANNEL_ID,
-                res.getString(R.string.performance_boost_notification_channel),
-                NotificationManager.IMPORTANCE_DEFAULT);
-        // CarrierDefaultApp notifications are unblockable by default. Make this channel blockable
-        //  to allow users to disable notifications posted to this channel without affecting other
-        //  notifications in this application.
-        channel.setBlockable(true);
-        context.getSystemService(NotificationManager.class).createNotificationChannel(channel);
+        NotificationManager notificationManager =
+                context.getSystemService(NotificationManager.class);
+        NotificationChannel channel = notificationManager.getNotificationChannel(
+                PERFORMANCE_BOOST_NOTIFICATION_CHANNEL_ID);
+        if (channel == null) {
+            channel = new NotificationChannel(
+                    PERFORMANCE_BOOST_NOTIFICATION_CHANNEL_ID,
+                    res.getString(R.string.performance_boost_notification_channel),
+                    NotificationManager.IMPORTANCE_DEFAULT);
+            // CarrierDefaultApp notifications are unblockable by default.
+            // Make this channel blockable to allow users to disable notifications posted to this
+            // channel without affecting other notifications in this application.
+            channel.setBlockable(true);
+            context.getSystemService(NotificationManager.class).createNotificationChannel(channel);
+        } else if (localeChanged) {
+            // If the channel already exists but the locale has changed, update the channel name.
+            channel.setName(res.getString(R.string.performance_boost_notification_channel));
+        }
+
+        boolean channelNotificationsDisabled =
+                channel.getImportance() == NotificationManager.IMPORTANCE_NONE;
+        if (channelNotificationsDisabled || !notificationManager.areNotificationsEnabled()) {
+            // If notifications are disabled for the app or channel, fail the purchase request.
+            logd("Purchase request failed because notifications are disabled for the "
+                    + (channelNotificationsDisabled ? "channel." : "application."));
+            sendSlicePurchaseAppResponse(intent,
+                    SlicePurchaseController.EXTRA_INTENT_NOTIFICATIONS_DISABLED);
+            return;
+        }
 
         String carrier = intent.getStringExtra(SlicePurchaseController.EXTRA_CARRIER);
-
         Notification notification =
                 new Notification.Builder(context, PERFORMANCE_BOOST_NOTIFICATION_CHANNEL_ID)
                         .setContentTitle(res.getString(
@@ -340,11 +392,12 @@
 
         int capability = intent.getIntExtra(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY,
                 SlicePurchaseController.PREMIUM_CAPABILITY_INVALID);
-        logd((repeat ? "Update" : "Display") + " the performance boost notification for capability "
+        logd((localeChanged ? "Update" : "Display")
+                + " the performance boost notification for capability "
                 + TelephonyManager.convertPremiumCapabilityToString(capability));
         context.getSystemService(NotificationManager.class).notifyAsUser(
                 PERFORMANCE_BOOST_NOTIFICATION_TAG, capability, notification, UserHandle.ALL);
-        if (!repeat) {
+        if (!localeChanged) {
             sIntents.put(capability, intent);
             sendSlicePurchaseAppResponse(intent,
                     SlicePurchaseController.EXTRA_INTENT_NOTIFICATION_SHOWN);
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseWebInterface.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseWebInterface.java
deleted file mode 100644
index 8547898..0000000
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseWebInterface.java
+++ /dev/null
@@ -1,93 +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.carrierdefaultapp;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.telephony.TelephonyManager;
-import android.webkit.JavascriptInterface;
-
-import com.android.phone.slice.SlicePurchaseController;
-
-/**
- * Slice purchase web interface class allowing carrier websites to send responses back to the
- * slice purchase application using JavaScript.
- */
-public class SlicePurchaseWebInterface {
-    @NonNull SlicePurchaseActivity mActivity;
-
-    public SlicePurchaseWebInterface(@NonNull SlicePurchaseActivity activity) {
-        mActivity = activity;
-    }
-
-    /**
-     * Interface method allowing the carrier website to get the premium capability
-     * that was requested to purchase.
-     *
-     * This can be called using the JavaScript below:
-     * <script type="text/javascript">
-     *     function getRequestedCapability(duration) {
-     *         SlicePurchaseWebInterface.getRequestedCapability();
-     *     }
-     * </script>
-     */
-    @JavascriptInterface
-    @TelephonyManager.PremiumCapability public int getRequestedCapability() {
-        return mActivity.mCapability;
-    }
-
-    /**
-     * Interface method allowing the carrier website to notify the slice purchase application of
-     * a successful premium capability purchase and the duration for which the premium capability is
-     * purchased.
-     *
-     * This can be called using the JavaScript below:
-     * <script type="text/javascript">
-     *     function notifyPurchaseSuccessful(duration_ms_long = 0) {
-     *         SlicePurchaseWebInterface.notifyPurchaseSuccessful(duration_ms_long);
-     *     }
-     * </script>
-     *
-     * @param duration The duration for which the premium capability is purchased in milliseconds.
-     */
-    @JavascriptInterface
-    public void notifyPurchaseSuccessful(long duration) {
-        mActivity.onPurchaseSuccessful(duration);
-    }
-
-    /**
-     * Interface method allowing the carrier website to notify the slice purchase application of
-     * a failed premium capability purchase.
-     *
-     * This can be called using the JavaScript below:
-     * <script type="text/javascript">
-     *     function notifyPurchaseFailed(failure_code = 0, failure_reason = "unknown") {
-     *         SlicePurchaseWebInterface.notifyPurchaseFailed();
-     *     }
-     * </script>
-     *
-     * @param failureCode The failure code.
-     * @param failureReason If the failure code is
-     *                      {@link SlicePurchaseController#FAILURE_CODE_UNKNOWN},
-     *                      the human-readable reason for failure.
-     */
-    @JavascriptInterface
-    public void notifyPurchaseFailed(@SlicePurchaseController.FailureCode int failureCode,
-            @Nullable String failureReason) {
-        mActivity.onPurchaseFailed(failureCode, failureReason);
-    }
-}
diff --git a/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseActivityTest.java b/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseActivityTest.java
index daf0b42..1ec180b 100644
--- a/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseActivityTest.java
+++ b/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseActivityTest.java
@@ -21,6 +21,7 @@
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
 import android.app.NotificationManager;
@@ -33,6 +34,7 @@
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.test.ActivityUnitTestCase;
+import android.webkit.WebView;
 
 import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
@@ -46,6 +48,8 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.Base64;
+
 @RunWith(AndroidJUnit4.class)
 public class SlicePurchaseActivityTest extends ActivityUnitTestCase<SlicePurchaseActivity> {
     private static final String CARRIER = "Some Carrier";
@@ -53,10 +57,13 @@
     private static final int PHONE_ID = 0;
 
     @Mock PendingIntent mPendingIntent;
+    @Mock PendingIntent mSuccessfulIntent;
     @Mock PendingIntent mCanceledIntent;
+    @Mock PendingIntent mRequestFailedIntent;
     @Mock CarrierConfigManager mCarrierConfigManager;
     @Mock NotificationManager mNotificationManager;
     @Mock PersistableBundle mPersistableBundle;
+    @Mock WebView mWebView;
 
     private SlicePurchaseActivity mSlicePurchaseActivity;
     private Context mContext;
@@ -107,25 +114,28 @@
         doReturn(true).when(mCanceledIntent).isBroadcast();
         doReturn(mCanceledIntent).when(spiedIntent).getParcelableExtra(
                 eq(SlicePurchaseController.EXTRA_INTENT_CANCELED), eq(PendingIntent.class));
+        doReturn(TelephonyManager.PHONE_PROCESS_NAME).when(mSuccessfulIntent).getCreatorPackage();
+        doReturn(true).when(mSuccessfulIntent).isBroadcast();
+        doReturn(mSuccessfulIntent).when(spiedIntent).getParcelableExtra(
+                eq(SlicePurchaseController.EXTRA_INTENT_SUCCESS), eq(PendingIntent.class));
+        doReturn(TelephonyManager.PHONE_PROCESS_NAME).when(mRequestFailedIntent)
+                .getCreatorPackage();
+        doReturn(true).when(mRequestFailedIntent).isBroadcast();
+        doReturn(mRequestFailedIntent).when(spiedIntent).getParcelableExtra(
+                eq(SlicePurchaseController.EXTRA_INTENT_REQUEST_FAILED), eq(PendingIntent.class));
 
         mSlicePurchaseActivity = startActivity(spiedIntent, null, null);
     }
 
     @Test
     public void testOnPurchaseSuccessful() throws Exception {
-        int duration = 5 * 60 * 1000; // 5 minutes
-        int invalidDuration = -1;
-        mSlicePurchaseActivity.onPurchaseSuccessful(duration);
-        ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
-        verify(mPendingIntent).send(eq(mContext), eq(0), intentCaptor.capture());
-        Intent intent = intentCaptor.getValue();
-        assertEquals(duration, intent.getLongExtra(
-                SlicePurchaseController.EXTRA_PURCHASE_DURATION, invalidDuration));
+        mSlicePurchaseActivity.onPurchaseSuccessful();
+        verify(mSuccessfulIntent).send();
     }
 
     @Test
     public void testOnPurchaseFailed() throws Exception {
-        int failureCode = SlicePurchaseController.FAILURE_CODE_SERVER_UNREACHABLE;
+        int failureCode = SlicePurchaseController.FAILURE_CODE_CARRIER_URL_UNAVAILABLE;
         String failureReason = "Server unreachable";
         mSlicePurchaseActivity.onPurchaseFailed(failureCode, failureReason);
         ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
@@ -142,4 +152,29 @@
         mSlicePurchaseActivity.onDestroy();
         verify(mCanceledIntent).send();
     }
+
+    @Test
+    public void testOnDismissFlow() throws Exception {
+        mSlicePurchaseActivity.onDismissFlow();
+        verify(mRequestFailedIntent).send();
+    }
+
+    @Test
+    public void testStartWebView() {
+        // unspecified contents type
+        SlicePurchaseActivity.startWebView(mWebView, URL, 0 /* CONTENTS_TYPE_UNSPECIFIED */, null);
+        verify(mWebView).loadUrl(eq(URL));
+
+        // specified contents type with user data
+        String userData = "userData";
+        byte[] userDataBytes = userData.getBytes();
+        SlicePurchaseActivity.startWebView(mWebView, URL, 1 /* CONTENTS_TYPE_JSON */, userData);
+        verify(mWebView).postUrl(eq(URL), eq(userDataBytes));
+
+        // specified contents type with encoded user data
+        byte[] encodedUserData = Base64.getEncoder().encode(userDataBytes);
+        userData = "encodedValue=" + new String(encodedUserData);
+        SlicePurchaseActivity.startWebView(mWebView, URL, 1 /* CONTENTS_TYPE_JSON */, userData);
+        verify(mWebView, times(2)).postUrl(eq(URL), eq(userDataBytes));
+    }
 }
diff --git a/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiverTest.java b/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiverTest.java
index 952789c..3c8ef6e 100644
--- a/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiverTest.java
+++ b/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiverTest.java
@@ -32,6 +32,7 @@
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 
+import android.annotation.NonNull;
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
@@ -72,6 +73,7 @@
     @Mock PendingIntent mContentIntent1;
     @Mock PendingIntent mContentIntent2;
     @Mock PendingIntent mNotificationShownIntent;
+    @Mock PendingIntent mNotificationsDisabledIntent;
     @Mock Context mContext;
     @Mock Resources mResources;
     @Mock Configuration mConfiguration;
@@ -90,6 +92,7 @@
         doReturn("").when(mResources).getString(anyInt());
         doReturn(mNotificationManager).when(mContext)
                 .getSystemService(eq(NotificationManager.class));
+        doReturn(true).when(mNotificationManager).areNotificationsEnabled();
         doReturn(mApplicationInfo).when(mContext).getApplicationInfo();
         doReturn(mPackageManager).when(mContext).getPackageManager();
         doReturn(mSpiedResources).when(mContext).getResources();
@@ -160,14 +163,35 @@
                 "file:///android_asset/slice_store_test.html"
         };
 
+        // test invalid URLs
         for (String url : invalidUrls) {
-            URL purchaseUrl = SlicePurchaseBroadcastReceiver.getPurchaseUrl(url);
+            URL purchaseUrl = SlicePurchaseBroadcastReceiver.getPurchaseUrl(url, null, false);
             assertNull(purchaseUrl);
         }
 
+        // test asset URL
         assertEquals(SlicePurchaseController.SLICE_PURCHASE_TEST_FILE,
                 SlicePurchaseBroadcastReceiver.getPurchaseUrl(
-                        SlicePurchaseController.SLICE_PURCHASE_TEST_FILE).toString());
+                        SlicePurchaseController.SLICE_PURCHASE_TEST_FILE, null, false).toString());
+
+        // test normal URL
+        String validUrl = "http://www.google.com";
+        assertEquals(validUrl,
+                SlicePurchaseBroadcastReceiver.getPurchaseUrl(validUrl, null, false).toString());
+
+        // test normal URL with user data but no append
+        String userData = "encryptedUserData=data";
+        assertEquals(validUrl,
+                SlicePurchaseBroadcastReceiver.getPurchaseUrl(validUrl, userData, false)
+                        .toString());
+
+        // test normal URL with user data and append
+        assertEquals(validUrl + "?" + userData,
+                SlicePurchaseBroadcastReceiver.getPurchaseUrl(validUrl, userData, true).toString());
+
+        // test normal URL without user data and append
+        assertEquals(validUrl,
+                SlicePurchaseBroadcastReceiver.getPurchaseUrl(validUrl, null, true).toString());
     }
 
     @Test
@@ -200,12 +224,10 @@
         doReturn(true).when(mPendingIntent).isBroadcast();
         doReturn(mPendingIntent).when(mIntent).getParcelableExtra(
                 anyString(), eq(PendingIntent.class));
-        doReturn(TelephonyManager.PHONE_PROCESS_NAME).when(mNotificationShownIntent)
-                .getCreatorPackage();
-        doReturn(true).when(mNotificationShownIntent).isBroadcast();
-        doReturn(mNotificationShownIntent).when(mIntent).getParcelableExtra(
-                eq(SlicePurchaseController.EXTRA_INTENT_NOTIFICATION_SHOWN),
-                eq(PendingIntent.class));
+        createValidPendingIntent(mNotificationShownIntent,
+                SlicePurchaseController.EXTRA_INTENT_NOTIFICATION_SHOWN);
+        createValidPendingIntent(mNotificationsDisabledIntent,
+                SlicePurchaseController.EXTRA_INTENT_NOTIFICATIONS_DISABLED);
 
         // spy notification intents to prevent PendingIntent issues
         doReturn(mContentIntent1).when(mSlicePurchaseBroadcastReceiver).createContentIntent(
@@ -232,6 +254,12 @@
         mSlicePurchaseBroadcastReceiver.onReceive(mContext, mIntent);
     }
 
+    private void createValidPendingIntent(@NonNull PendingIntent intent, @NonNull String extra) {
+        doReturn(TelephonyManager.PHONE_PROCESS_NAME).when(intent).getCreatorPackage();
+        doReturn(true).when(intent).isBroadcast();
+        doReturn(intent).when(mIntent).getParcelableExtra(eq(extra), eq(PendingIntent.class));
+    }
+
     @Test
     public void testNotificationCanceled() {
         // send ACTION_NOTIFICATION_CANCELED
@@ -314,4 +342,22 @@
         clearInvocations(mConfiguration);
         return captor.getValue();
     }
+
+    @Test
+    public void testNotificationsDisabled() throws Exception {
+        doReturn(false).when(mNotificationManager).areNotificationsEnabled();
+
+        displayPerformanceBoostNotification();
+
+        // verify notification was not shown
+        verify(mNotificationManager, never()).notifyAsUser(
+                eq(SlicePurchaseBroadcastReceiver.PERFORMANCE_BOOST_NOTIFICATION_TAG),
+                eq(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY),
+                any(),
+                eq(UserHandle.ALL));
+        verify(mNotificationShownIntent, never()).send();
+
+        // verify SlicePurchaseController was notified that notifications are disabled
+        verify(mNotificationsDisabledIntent).send();
+    }
 }
diff --git a/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml b/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml
index aae30df..a0b3469 100644
--- a/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml
+++ b/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml
@@ -144,7 +144,7 @@
                         android:visibility="gone"
                         android:duplicateParentState="true"
                         android:clickable="false"
-                        android:text="@string/consent_no" />
+                        android:text="@string/consent_cancel" />
 
                 </LinearLayout>
 
diff --git a/packages/CompanionDeviceManager/res/values-af/strings.xml b/packages/CompanionDeviceManager/res/values-af/strings.xml
index d621ca2..6c54f70 100644
--- a/packages/CompanionDeviceManager/res/values-af/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-af/strings.xml
@@ -17,36 +17,36 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Metgeseltoestel-bestuurder"</string>
-    <string name="confirmation_title" msgid="8024993972587946678">"Gee &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; toegang tot &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"Gee die app &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; toegang tot &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"horlosie"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Kies \'n <xliff:g id="PROFILE_NAME">%1$s</xliff:g> om deur &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; bestuur te word"</string>
-    <string name="summary_watch" msgid="6566922405914995759">"Hierdie app is nodig om jou <xliff:g id="DEVICE_NAME">%1$s</xliff:g> te bestuur. <xliff:g id="APP_NAME">%2$s</xliff:g> sal toegelaat word om inligting te sinkroniseer, soos die naam van iemand wat bel, interaksie met jou kennisgewings te hê, en sal toegang tot jou Foon-, SMS-, Kontakte-, Mikrofoon-, en Toestelle in die Omtrek-toestemmings hê."</string>
-    <string name="summary_watch_single_device" msgid="7443464525873186735">"Hierdie app is nodig om jou <xliff:g id="DEVICE_NAME">%1$s</xliff:g> te bestuur. <xliff:g id="APP_NAME">%2$s</xliff:g> sal toegelaat word om inligting te sinkroniseer, soos die naam van iemand wat bel, en toegang tot hierdie toestemmings:"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"bril"</string>
-    <string name="summary_glasses" msgid="3808267780579061241">"Hierdie app is nodig om <xliff:g id="DEVICE_NAME">%1$s</xliff:g> te bestuur. <xliff:g id="APP_NAME">%2$s</xliff:g> sal toegelaat word om interaksie met jou kennisgewings te hê en sal toegang tot jou Foon-, SMS-, Kontakte-, Mikrofoon-, en Toestelle in die Omtrek-toestemmings hê."</string>
-    <string name="summary_glasses_single_device" msgid="7051392780285915640">"Hierdie app is nodig om <xliff:g id="DEVICE_NAME">%1$s</xliff:g> te bestuur. <xliff:g id="APP_NAME">%2$s</xliff:g> sal toegelaat word om interaksie met die volgende toestemmings te hê:"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"Kies ’n toestel wat &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; moet bestuur"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Kies ’n <xliff:g id="PROFILE_NAME">%1$s</xliff:g> om op te stel"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Hierdie app sal toegelaat word om inligting te sinkroniseer, soos die naam van iemand wat bel, en sal toegang tot hierdie toestemmings op jou <xliff:g id="DEVICE_NAME">%1$s</xliff:g> hê"</string>
+    <string name="confirmation_title_glasses" msgid="8288346850537727333">"Laat &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; toe om &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; te bestuur?"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"toestel"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Hierdie app sal toegang tot hierdie toestemmings op jou <xliff:g id="DEVICE_NAME">%1$s</xliff:g> hê"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Gee &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; toegang tot hierdie inligting op jou foon"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Oorkruistoestel-dienste"</string>
-    <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> versoek tans namens jou <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> toestemming om programme tussen jou toestelle te stroom"</string>
+    <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g> versoek tans namens jou <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> toestemming om apps tussen jou toestelle te stroom"</string>
     <string name="title_automotive_projection" msgid="3296005598978412847"></string>
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"Gee &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; toegang tot hierdie inligting op jou foon"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Google Play Dienste"</string>
-    <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> versoek tans namens jou <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> toegang tot jou foon se foto\'s, media en kennisgewings"</string>
-    <string name="title_nearby_device_streaming" msgid="179278282547719200">"Laat &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; toe om hierdie handeling op jou foon uit te voer"</string>
-    <string name="helper_title_nearby_device_streaming" msgid="6124438217620593669">"Oorkruistoesteldienste"</string>
-    <string name="helper_summary_nearby_device_streaming" msgid="5538329403511524333">"<xliff:g id="APP_NAME">%1$s</xliff:g> versoek tans namens jou <xliff:g id="DEVICE_TYPE">%2$s</xliff:g>-toestemming om inhoud na toestelle in die omtrek te stroom"</string>
+    <string name="helper_summary_computer" msgid="8774832742608187072">"<xliff:g id="APP_NAME">%1$s</xliff:g> versoek tans namens jou <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> toegang tot jou foon se foto’s, media en kennisgewings"</string>
+    <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Laat &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; toe om hierdie handeling uit te voer?"</string>
+    <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> versoek tans namens jou <xliff:g id="DEVICE_NAME">%2$s</xliff:g> toestemming om apps en ander stelselkenmerke na toestelle in die omtrek te stroom"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"toestel"</string>
-    <string name="summary_generic_single_device" msgid="4735072202474939111">"Hierdie app sal inligting kan sinkroniseer, soos die naam van iemand wat bel, tussen jou foon en <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string>
-    <string name="summary_generic" msgid="4988130802522924650">"Hierdie app sal inligting kan sinkroniseer, soos die naam van iemand wat bel, tussen jou foon en die gekose toestel."</string>
+    <string name="summary_generic" msgid="1761976003668044801">"Hierdie app sal inligting kan sinkroniseer, soos die naam van iemand wat bel, tussen jou foon en die gekose toestel"</string>
     <string name="consent_yes" msgid="8344487259618762872">"Laat toe"</string>
     <string name="consent_no" msgid="2640796915611404382">"Moenie toelaat nie"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Kanselleer"</string>
     <string name="consent_back" msgid="2560683030046918882">"Terug"</string>
+    <string name="permission_expand" msgid="893185038020887411">"Vou <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> uit"</string>
+    <string name="permission_collapse" msgid="3320833884220844084">"Vou <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> in"</string>
     <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Gee programme op &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; dieselfde toestemmings as op &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="permission_sync_summary" msgid="765497944331294275">"Dit kan &lt;strong&gt;Mikrofoon-&lt;/strong&gt;, &lt;strong&gt;Kamera-&lt;/strong&gt;, &lt;strong&gt;Liggingtoegang-&lt;/strong&gt; en ander sensitiewe toestemmings op &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt; insluit. &lt;br/&gt;&lt;br/&gt;Jy kan hierdie toestemmings enige tyd in jou Instellings op &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt; verander."</string>
-    <string name="vendor_icon_description" msgid="4445875290032225965">"Program-ikoon"</string>
-    <string name="vendor_header_button_description" msgid="6566660389500630608">"Meer Inligting-knoppie"</string>
+    <string name="vendor_header_button_description" msgid="7994879208461111473">"Meer inligting"</string>
     <string name="permission_phone" msgid="2661081078692784919">"Foon"</string>
     <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
     <string name="permission_contacts" msgid="3858319347208004438">"Kontakte"</string>
@@ -57,16 +57,18 @@
     <string name="permission_storage" msgid="6831099350839392343">"Foto\'s en media"</string>
     <string name="permission_notification" msgid="693762568127741203">"Kennisgewings"</string>
     <string name="permission_app_streaming" msgid="6009695219091526422">"Apps"</string>
-    <string name="permission_nearby_device_streaming" msgid="5868108148065023161">"Toestel in die Omtrek-stroming"</string>
+    <string name="permission_nearby_device_streaming" msgid="1023325519477349499">"Stroming"</string>
     <string name="permission_phone_summary" msgid="6684396967861278044">"Kan foonoproepe maak en bestuur"</string>
     <string name="permission_call_logs_summary" msgid="6186103394658755022">"Kan foonoproeprekord lees en skryf"</string>
     <string name="permission_sms_summary" msgid="3508442683678912017">"Kan SMS-boodskappe stuur en ontvang"</string>
     <string name="permission_contacts_summary" msgid="675861979475628708">"Kan by jou kontakte ingaan"</string>
     <string name="permission_calendar_summary" msgid="6460000922511766226">"Kan by jou kalender ingaan"</string>
-    <string name="permission_microphone_summary" msgid="4241354865859396558">"Kan die mikrofoon gebruik om oudio op te neem"</string>
+    <string name="permission_microphone_summary" msgid="3692091540613093394">"Kan oudio opneem"</string>
     <string name="permission_nearby_devices_summary" msgid="931940524460876655">"Kan toestelle in die omtrek opspoor, aan hulle koppel en hul relatiewe posisie bepaal"</string>
     <string name="permission_notification_summary" msgid="884075314530071011">"Kan alle kennisgewings lees, insluitend inligting soos kontakte, boodskappe en foto\'s"</string>
     <string name="permission_app_streaming_summary" msgid="606923325679670624">"Stroom jou foon se apps"</string>
     <string name="permission_storage_summary" msgid="3918240895519506417"></string>
-    <string name="permission_nearby_device_streaming_summary" msgid="5776807830582725074">"Stroom inhoud na ’n toestel in die omtrek"</string>
+    <string name="permission_nearby_device_streaming_summary" msgid="8280824871197081246">"Stroom apps en ander stelselkenmerke van jou foon af"</string>
+    <string name="device_type" product="default" msgid="8268703872070046263">"foon"</string>
+    <string name="device_type" product="tablet" msgid="5038791954983067774">"tablet"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-am/strings.xml b/packages/CompanionDeviceManager/res/values-am/strings.xml
index 86d8652..d9ddf71 100644
--- a/packages/CompanionDeviceManager/res/values-am/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-am/strings.xml
@@ -17,43 +17,36 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"አጃቢ የመሣሪያ አስተዳዳሪ"</string>
-    <!-- no translation found for confirmation_title (8024993972587946678) -->
-    <skip />
+    <string name="confirmation_title" msgid="2244241995958340998">"መተግበሪያው &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;ን እንዲደርስ ይፈቀድለት?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"ሰዓት"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"በ&lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; የሚተዳደር <xliff:g id="PROFILE_NAME">%1$s</xliff:g> ይምረጡ"</string>
-    <!-- no translation found for summary_watch (6566922405914995759) -->
-    <skip />
-    <!-- no translation found for summary_watch_single_device (7443464525873186735) -->
-    <skip />
-    <string name="profile_name_glasses" msgid="8488394059007275998">"መነጽሮች"</string>
-    <!-- no translation found for summary_glasses (3808267780579061241) -->
-    <skip />
-    <!-- no translation found for summary_glasses_single_device (7051392780285915640) -->
-    <skip />
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; የሚያስተዳድረው መሣሪያ ይምረጡ"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"የሚያዋቅሩት <xliff:g id="PROFILE_NAME">%1$s</xliff:g> ይምረጡ"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"ይህ መተግበሪያ እንደ የሚደውል ሰው ስም ያለ መረጃን እንዲያሰምር እና እነዚህን ፈቃዶች በእርስዎ <xliff:g id="DEVICE_NAME">%1$s</xliff:g> ላይ እንዲደርስ ይፈቀድለታል"</string>
+    <string name="confirmation_title_glasses" msgid="8288346850537727333">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;ን እንዲያስተዳድር ይፈቅዳሉ?"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"መሣሪያ"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"ይህ መተግበሪያ በእርስዎ <xliff:g id="DEVICE_NAME">%1$s</xliff:g> ላይ እነዚህን ፈቃዶች እንዲደርስ ይፈቀድለታል"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ይህን መረጃ ከስልክዎ እንዲደርስበት ይፍቀዱለት"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"መሣሪያ ተሻጋሪ አገልግሎቶች"</string>
-    <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> በእርስዎ መሣሪያዎች መካከል መተግበሪያዎችን በዥረት ለመልቀቅ የእርስዎን <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> ወክሎ ፈቃድ እየጠየቀ ነው"</string>
+    <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g> በእርስዎ መሣሪያዎች መካከል መተግበሪያዎችን በዥረት ለመልቀቅ የእርስዎን <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> ወክሎ ፈቃድ እየጠየቀ ነው"</string>
     <string name="title_automotive_projection" msgid="3296005598978412847"></string>
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ይህን መረጃ ከስልክዎ ላይ እንዲደርስ ይፍቀዱለት"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"የGoogle Play አገልግሎቶች"</string>
-    <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> የስልክዎን ፎቶዎች፣ ሚዲያ እና ማሳወቂያዎች ለመድረስ የእርስዎን <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> ወክሎ ፈቃድ እየጠየቀ ነው"</string>
-    <string name="title_nearby_device_streaming" msgid="179278282547719200">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ይህን እርምጃ ከስልክዎ እንዲያከናውን ይፍቀዱ"</string>
-    <string name="helper_title_nearby_device_streaming" msgid="6124438217620593669">"መሣሪያ ተሻጋሪ አገልግሎቶች"</string>
-    <string name="helper_summary_nearby_device_streaming" msgid="5538329403511524333">"<xliff:g id="APP_NAME">%1$s</xliff:g> በአቅራቢያ ወዳሉ መሣሪያዎች ይዘትን በዥረት ለመልቀቅ የእርስዎን <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> ወክሎ ፈቃድ እየጠየቀ ነው"</string>
+    <string name="helper_summary_computer" msgid="8774832742608187072">"<xliff:g id="APP_NAME">%1$s</xliff:g> የስልክዎን ፎቶዎች፣ ሚዲያ እና ማሳወቂያዎች ለመድረስ የእርስዎን <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> ወክሎ ፈቃድ እየጠየቀ ነው"</string>
+    <string name="title_nearby_device_streaming" msgid="7269956847378799794">"&lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; ይህን እርምጃ እንዲወስድ ፈቃድ ይሰጠው?"</string>
+    <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> የእርስዎን <xliff:g id="DEVICE_NAME">%2$s</xliff:g> በመወከል በአቅራቢያ ላሉ መሣሪያዎች መተግበሪያዎች እና ሌሎች የስርዓት ባህሪያትን በዥረት ለመልቀቅ ፈቃድ እየጠየቀ ነው"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"መሣሪያ"</string>
-    <!-- no translation found for summary_generic_single_device (4735072202474939111) -->
-    <skip />
-    <!-- no translation found for summary_generic (4988130802522924650) -->
-    <skip />
+    <string name="summary_generic" msgid="1761976003668044801">"ይህ መተግበሪያ እንደ የሚደውል ሰው ስም ያለ መረጃን በስልክዎ እና በተመረጠው መሣሪያ መካከል ማስመር ይችላል"</string>
     <string name="consent_yes" msgid="8344487259618762872">"ፍቀድ"</string>
     <string name="consent_no" msgid="2640796915611404382">"አትፍቀድ"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"ይቅር"</string>
     <string name="consent_back" msgid="2560683030046918882">"ተመለስ"</string>
+    <string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>ን ዘርጋ"</string>
+    <string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>ን ሰብስብ"</string>
     <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"በ&lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; ላይ ላሉ መተግበሪያዎች በ&lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; ላይ ካሉት ጋር ተመሳሳይ ፈቃዶች ይሰጣቸው?"</string>
     <string name="permission_sync_summary" msgid="765497944331294275">"ይህ &lt;strong&gt;ማይክሮፎን&lt;/strong&gt;፣ &lt;strong&gt;ካሜራ&lt;/strong&gt; እና &lt;strong&gt;የአካባቢ መዳረሻ&lt;/strong&gt; እና ሌሎች አደገኛ ፈቃዶችን &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt; ላይ ሊያካትት ይችላል። &lt;br/&gt;&lt;br/&gt;እነዚህን ቅንብሮች በማንኛውም ጊዜ ቅንብሮችዎ ውስጥ &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt; ላይ መቀየር ይችላሉ።"</string>
-    <string name="vendor_icon_description" msgid="4445875290032225965">"የመተግበሪያ አዶ"</string>
-    <string name="vendor_header_button_description" msgid="6566660389500630608">"የተጨማሪ መረጃ አዝራር"</string>
+    <string name="vendor_header_button_description" msgid="7994879208461111473">"ተጨማሪ መረጃ"</string>
     <string name="permission_phone" msgid="2661081078692784919">"ስልክ"</string>
     <string name="permission_sms" msgid="6337141296535774786">"ኤስኤምኤስ"</string>
     <string name="permission_contacts" msgid="3858319347208004438">"ዕውቂያዎች"</string>
@@ -64,16 +57,18 @@
     <string name="permission_storage" msgid="6831099350839392343">"ፎቶዎች እና ሚዲያ"</string>
     <string name="permission_notification" msgid="693762568127741203">"ማሳወቂያዎች"</string>
     <string name="permission_app_streaming" msgid="6009695219091526422">"መተግበሪያዎች"</string>
-    <string name="permission_nearby_device_streaming" msgid="5868108148065023161">"በአቅራቢያ ያለ መሣሪያ በዥረት መልቀቅ"</string>
+    <string name="permission_nearby_device_streaming" msgid="1023325519477349499">"በዥረት መልቀቅ"</string>
     <string name="permission_phone_summary" msgid="6684396967861278044">"የስልክ ጥሪዎችን ማድረግ እና ማስተዳደር ይችላል"</string>
     <string name="permission_call_logs_summary" msgid="6186103394658755022">"የስልክ ጥሪ ምዝገባ ማስታወሻን ማንበብ እና መጻፍ ይችላል"</string>
     <string name="permission_sms_summary" msgid="3508442683678912017">"የኤስኤምኤስ መልዕክቶችን መላክ እና ማየት ይችላል"</string>
     <string name="permission_contacts_summary" msgid="675861979475628708">"ዕውቂያዎችዎን መድረስ ይችላል"</string>
     <string name="permission_calendar_summary" msgid="6460000922511766226">"የቀን መቁጠሪያዎን መድረስ ይችላል"</string>
-    <string name="permission_microphone_summary" msgid="4241354865859396558">"ማይክሮፎኑን በመጠቀም ኦዲዮ መቅዳት ይችላል"</string>
+    <string name="permission_microphone_summary" msgid="3692091540613093394">"ኦዲዮ መቅዳት ይችላል"</string>
     <string name="permission_nearby_devices_summary" msgid="931940524460876655">"በአቅራቢያ ያሉ መሣሪያዎችን ማግኘት፣ ከእነሱ ጋር መገናኘት እና አንጻራዊ ቦታቸውን መወሰን ይችላል"</string>
     <string name="permission_notification_summary" msgid="884075314530071011">"እንደ እውቂያዎች፣ መልዕክቶች እና ፎቶዎች ያሉ መረጃዎችን ጨምሮ ሁሉንም ማሳወቂያዎች ማንበብ ይችላል"</string>
     <string name="permission_app_streaming_summary" msgid="606923325679670624">"የስልክዎን መተግበሪያዎች በዥረት ይልቀቁ"</string>
     <string name="permission_storage_summary" msgid="3918240895519506417"></string>
-    <string name="permission_nearby_device_streaming_summary" msgid="5776807830582725074">"ይዘትን በአቅራቢያ ወዳለ መሣሪያ በዥረት ይልቀቁ"</string>
+    <string name="permission_nearby_device_streaming_summary" msgid="8280824871197081246">"ከስልክዎ ሆነው መተግበሪያዎች እና ሌሎች የስርዓት ባህሪያትን በዥረት ይልቀቁ"</string>
+    <string name="device_type" product="default" msgid="8268703872070046263">"ስልክ"</string>
+    <string name="device_type" product="tablet" msgid="5038791954983067774">"ጡባዊ"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-ar/strings.xml b/packages/CompanionDeviceManager/res/values-ar/strings.xml
index 5864ec9..8a6f227 100644
--- a/packages/CompanionDeviceManager/res/values-ar/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ar/strings.xml
@@ -17,43 +17,36 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"تطبيق \"مدير الجهاز المصاحب\""</string>
-    <!-- no translation found for confirmation_title (8024993972587946678) -->
-    <skip />
+    <string name="confirmation_title" msgid="2244241995958340998">"‏هل تريد السماح لـ &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; بالوصول إلى &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;؟"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"الساعة"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"‏اختَر <xliff:g id="PROFILE_NAME">%1$s</xliff:g> ليديرها تطبيق &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <!-- no translation found for summary_watch (6566922405914995759) -->
-    <skip />
-    <!-- no translation found for summary_watch_single_device (7443464525873186735) -->
-    <skip />
-    <string name="profile_name_glasses" msgid="8488394059007275998">"النظارة"</string>
-    <!-- no translation found for summary_glasses (3808267780579061241) -->
-    <skip />
-    <!-- no translation found for summary_glasses_single_device (7051392780285915640) -->
-    <skip />
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"‏اختيار جهاز ليديره تطبيق &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"اختيار \"<xliff:g id="PROFILE_NAME">%1$s</xliff:g>\" لإعداده"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"سيتم السماح لهذا التطبيق بمزامنة المعلومات، مثلاً اسم المتصل، والوصول إلى هذه الأذونات على \"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>\"."</string>
+    <string name="confirmation_title_glasses" msgid="8288346850537727333">"‏السماح لتطبيق &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; بإدارة &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"جهاز"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"سيتم السماح لهذا التطبيق بالوصول إلى هذه الأذونات على \"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>\"."</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"‏السماح لتطبيق &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; بالوصول إلى هذه المعلومات من هاتفك"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"الخدمات التي تعمل بين الأجهزة"</string>
-    <string name="helper_summary_app_streaming" msgid="5977509499890099">"يطلب تطبيق <xliff:g id="APP_NAME">%1$s</xliff:g> الحصول على إذن نيابةً عن <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> لمشاركة التطبيقات بين أجهزتك."</string>
+    <string name="helper_summary_app_streaming" msgid="2396773196949578425">"يطلب تطبيق \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" الحصول على إذن نيابةً عن \"<xliff:g id="DISPLAY_NAME">%2$s</xliff:g>\" لبثّ محتوى التطبيقات بين أجهزتك."</string>
     <string name="title_automotive_projection" msgid="3296005598978412847"></string>
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"‏السماح لتطبيق &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; بالوصول إلى هذه المعلومات من هاتفك"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"‏خدمات Google Play"</string>
-    <string name="helper_summary_computer" msgid="9050724687678157852">"يطلب تطبيق <xliff:g id="APP_NAME">%1$s</xliff:g> الحصول على إذن نيابةً عن <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> للوصول إلى الصور والوسائط والإشعارات في هاتفك."</string>
-    <string name="title_nearby_device_streaming" msgid="179278282547719200">"‏السماح للتطبيق &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; بتنفيذ هذا الإجراء من هاتفك"</string>
-    <string name="helper_title_nearby_device_streaming" msgid="6124438217620593669">"خدمات تعمل على عدة أجهزة"</string>
-    <string name="helper_summary_nearby_device_streaming" msgid="5538329403511524333">"يطلب \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" الحصول على إذن نيابةً عن \"<xliff:g id="DEVICE_TYPE">%2$s</xliff:g>\" لبثّ محتوى إلى أجهزتك المجاورة."</string>
+    <string name="helper_summary_computer" msgid="8774832742608187072">"يطلب تطبيق \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" الحصول على إذن نيابةً عن \"<xliff:g id="DISPLAY_NAME">%2$s</xliff:g>\" للوصول إلى الصور والوسائط والإشعارات في هاتفك."</string>
+    <string name="title_nearby_device_streaming" msgid="7269956847378799794">"‏هل تريد السماح للتطبيق &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; باتّخاذ هذا الإجراء؟"</string>
+    <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"يطلب \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" الحصول على إذن نيابةً عن \"<xliff:g id="DEVICE_NAME">%2$s</xliff:g>\" لبثّ التطبيقات وميزات النظام الأخرى إلى أجهزتك المجاورة."</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"جهاز"</string>
-    <!-- no translation found for summary_generic_single_device (4735072202474939111) -->
-    <skip />
-    <!-- no translation found for summary_generic (4988130802522924650) -->
-    <skip />
+    <string name="summary_generic" msgid="1761976003668044801">"سيتمكّن هذا التطبيق من مزامنة المعلومات، مثل اسم المتصل، بين هاتفك والجهاز المحدّد."</string>
     <string name="consent_yes" msgid="8344487259618762872">"السماح"</string>
     <string name="consent_no" msgid="2640796915611404382">"عدم السماح"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"إلغاء"</string>
     <string name="consent_back" msgid="2560683030046918882">"رجوع"</string>
+    <string name="permission_expand" msgid="893185038020887411">"توسيع <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
+    <string name="permission_collapse" msgid="3320833884220844084">"تصغير <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"‏هل تريد منح التطبيقات على &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; نفس الأذونات على &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;؟"</string>
     <string name="permission_sync_summary" msgid="765497944331294275">"‏قد يتضمّن هذا الوصول إلى &lt;strong&gt;الميكروفون&lt;/strong&gt; و&lt;strong&gt;الكاميرا&lt;/strong&gt; و&lt;strong&gt;الموقع الجغرافي&lt;/strong&gt; وأذونات الوصول إلى المعلومات الحساسة الأخرى في &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;. &lt;br/&gt;&lt;br/&gt;يمكنك تغيير هذه الأذونات في أي وقت في إعداداتك على &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;."</string>
-    <string name="vendor_icon_description" msgid="4445875290032225965">"رمز التطبيق"</string>
-    <string name="vendor_header_button_description" msgid="6566660389500630608">"زر مزيد من المعلومات"</string>
+    <string name="vendor_header_button_description" msgid="7994879208461111473">"مزيد من المعلومات"</string>
     <string name="permission_phone" msgid="2661081078692784919">"الهاتف"</string>
     <string name="permission_sms" msgid="6337141296535774786">"الرسائل القصيرة"</string>
     <string name="permission_contacts" msgid="3858319347208004438">"جهات الاتصال"</string>
@@ -64,16 +57,18 @@
     <string name="permission_storage" msgid="6831099350839392343">"الصور والوسائط"</string>
     <string name="permission_notification" msgid="693762568127741203">"الإشعارات"</string>
     <string name="permission_app_streaming" msgid="6009695219091526422">"التطبيقات"</string>
-    <string name="permission_nearby_device_streaming" msgid="5868108148065023161">"بثّ محتوى إلى الأجهزة المجاورة"</string>
+    <string name="permission_nearby_device_streaming" msgid="1023325519477349499">"البثّ"</string>
     <string name="permission_phone_summary" msgid="6684396967861278044">"يمكن إجراء المكالمات الهاتفية وإدارتها."</string>
     <string name="permission_call_logs_summary" msgid="6186103394658755022">"يمكن قراءة سجلّ المكالمات الهاتفية والكتابة فيه."</string>
     <string name="permission_sms_summary" msgid="3508442683678912017">"يمكن إرسال الرسائل القصيرة وعرضها."</string>
     <string name="permission_contacts_summary" msgid="675861979475628708">"يمكن الوصول إلى جهات الاتصال."</string>
     <string name="permission_calendar_summary" msgid="6460000922511766226">"يمكن الوصول إلى التقويم."</string>
-    <string name="permission_microphone_summary" msgid="4241354865859396558">"يمكن تسجيل الصوت باستخدام الميكروفون."</string>
+    <string name="permission_microphone_summary" msgid="3692091540613093394">"يمكنه تسجيل الصوت."</string>
     <string name="permission_nearby_devices_summary" msgid="931940524460876655">"يمكن العثور على الموضع النسبي للأجهزة المجاورة والربط بها وتحديدها."</string>
     <string name="permission_notification_summary" msgid="884075314530071011">"يمكن لهذا الملف الشخصي قراءة جميع الإشعارات، بما في ذلك المعلومات، مثل جهات الاتصال والرسائل والصور."</string>
     <string name="permission_app_streaming_summary" msgid="606923325679670624">"بث تطبيقات هاتفك"</string>
     <string name="permission_storage_summary" msgid="3918240895519506417"></string>
-    <string name="permission_nearby_device_streaming_summary" msgid="5776807830582725074">"بثّ محتوى إلى جهاز مجاور"</string>
+    <string name="permission_nearby_device_streaming_summary" msgid="8280824871197081246">"بثّ التطبيقات وميزات النظام الأخرى من هاتفك"</string>
+    <string name="device_type" product="default" msgid="8268703872070046263">"هاتف"</string>
+    <string name="device_type" product="tablet" msgid="5038791954983067774">"جهاز لوحي"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-as/strings.xml b/packages/CompanionDeviceManager/res/values-as/strings.xml
index 38be80f..28a06e6 100644
--- a/packages/CompanionDeviceManager/res/values-as/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-as/strings.xml
@@ -17,43 +17,36 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"কম্পেনিয়ন ডিভাইচ মেনেজাৰ"</string>
-    <!-- no translation found for confirmation_title (8024993972587946678) -->
-    <skip />
+    <string name="confirmation_title" msgid="2244241995958340998">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; এপ্‌টোক &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; এক্সেছ কৰিবলৈ দিবনে?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"ঘড়ী"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"&lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;এ পৰিচালনা কৰিব লগা এটা <xliff:g id="PROFILE_NAME">%1$s</xliff:g> বাছনি কৰক"</string>
-    <!-- no translation found for summary_watch (6566922405914995759) -->
-    <skip />
-    <!-- no translation found for summary_watch_single_device (7443464525873186735) -->
-    <skip />
-    <string name="profile_name_glasses" msgid="8488394059007275998">"চছ্‌মা"</string>
-    <!-- no translation found for summary_glasses (3808267780579061241) -->
-    <skip />
-    <!-- no translation found for summary_glasses_single_device (7051392780285915640) -->
-    <skip />
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;এ পৰিচালনা কৰিবলগীয়া এটা ডিভাইচ বাছনি কৰক"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"ছেট আপ কৰিবলৈ এটা <xliff:g id="PROFILE_NAME">%1$s</xliff:g> বাছনি কৰক"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"এই এপ্‌টোক ফ’ন কৰা লোকৰ নামৰ দৰে তথ্য ছিংক কৰিবলৈ আৰু আপোনাৰ <xliff:g id="DEVICE_NAME">%1$s</xliff:g>ত এই অনুমতিসমূহ এক্সেছ কৰিবলৈ অনুমতি দিয়া হ’ব"</string>
+    <string name="confirmation_title_glasses" msgid="8288346850537727333">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;ক &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; পৰিচালনা কৰিবলৈ দিবনে?"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"ডিভাইচ"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"এই এপ্‌টোক আপোনাৰ <xliff:g id="DEVICE_NAME">%1$s</xliff:g>ত এই অনুমতিসমূহ এক্সেছ কৰিবলৈ অনুমতি দিয়া হ’ব"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;ক আপোনাৰ ফ’নৰ পৰা এই তথ্যখিনি এক্সেছ কৰাৰ অনুমতি দিয়ক"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"ক্ৰছ-ডিভাইচ সেৱাসমূহ"</string>
-    <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g>এ আপোনাৰ <xliff:g id="DEVICE_TYPE">%2$s</xliff:g>ৰ হৈ আপোনাৰ ডিভাইচসমূহৰ মাজত এপ্‌ ষ্ট্ৰীম কৰাৰ বাবে অনুৰোধ জনাইছে"</string>
+    <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g>এ আপোনাৰ <xliff:g id="DISPLAY_NAME">%2$s</xliff:g>ৰ হৈ আপোনাৰ ডিভাইচসমূহৰ মাজত এপ্‌ ষ্ট্ৰীম কৰাৰ বাবে অনুৰোধ জনাইছে"</string>
     <string name="title_automotive_projection" msgid="3296005598978412847"></string>
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;ক আপোনাৰ ফ’নৰ পৰা এই তথ্যখিনি এক্সেছ কৰাৰ অনুমতি দিয়ক"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Google Play সেৱা"</string>
-    <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g>এ আপোনাৰ <xliff:g id="DEVICE_TYPE">%2$s</xliff:g>ৰ হৈ আপোনাৰ ফ’নৰ ফট’, মিডিয়া আৰু জাননী এক্সেছ কৰাৰ বাবে অনুৰোধ জনাইছে"</string>
-    <string name="title_nearby_device_streaming" msgid="179278282547719200">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;ক আপোনাৰ ফ’নৰ পৰা এই কাৰ্যটো সম্পাদন কৰিবলৈ দিয়ক"</string>
-    <string name="helper_title_nearby_device_streaming" msgid="6124438217620593669">"ক্ৰছ-ডিভাইচ সেৱাসমূহ"</string>
-    <string name="helper_summary_nearby_device_streaming" msgid="5538329403511524333">"<xliff:g id="APP_NAME">%1$s</xliff:g>এ আপোনাৰ <xliff:g id="DEVICE_TYPE">%2$s</xliff:g>ৰ হৈ নিকটৱৰ্তী ডিভাইচত সমল ষ্ট্ৰীম কৰাৰ অনুমতি দিবলৈ অনুৰোধ জনাইছে"</string>
+    <string name="helper_summary_computer" msgid="8774832742608187072">"<xliff:g id="APP_NAME">%1$s</xliff:g>এ আপোনাৰ <xliff:g id="DISPLAY_NAME">%2$s</xliff:g>ৰ হৈ আপোনাৰ ফ’নৰ ফট’, মিডিয়া আৰু জাননী এক্সেছ কৰাৰ বাবে অনুৰোধ জনাইছে"</string>
+    <string name="title_nearby_device_streaming" msgid="7269956847378799794">"&lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt;ক এই কাৰ্যটো সম্পাদন কৰিবলৈ দিবনে?"</string>
+    <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g>এ আপোনাৰ <xliff:g id="DEVICE_NAME">%2$s</xliff:g>ৰ হৈ নিকটৱৰ্তী ডিভাইচত এপ্‌ আৰু ছিষ্টেমৰ অন্য সুবিধাসমূহ ষ্ট্ৰীম কৰাৰ অনুমতি দিবলৈ অনুৰোধ জনাইছে"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"ডিভাইচ"</string>
-    <!-- no translation found for summary_generic_single_device (4735072202474939111) -->
-    <skip />
-    <!-- no translation found for summary_generic (4988130802522924650) -->
-    <skip />
+    <string name="summary_generic" msgid="1761976003668044801">"এই এপ্‌টোৱে আপোনাৰ ফ’ন আৰু বাছনি কৰা ডিভাইচটোৰ মাজত কল কৰোঁতাৰ নামৰ দৰে তথ্য ছিংক কৰিব পাৰিব"</string>
     <string name="consent_yes" msgid="8344487259618762872">"অনুমতি দিয়ক"</string>
     <string name="consent_no" msgid="2640796915611404382">"অনুমতি নিদিব"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"বাতিল কৰক"</string>
     <string name="consent_back" msgid="2560683030046918882">"উভতি যাওক"</string>
+    <string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> বিস্তাৰ কৰক"</string>
+    <string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> সংকোচন কৰক"</string>
     <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"এপ্‌সমূহক &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;ত দিয়াৰ দৰে &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt;তো একে অনুমতি প্ৰদান কৰিবনে?"</string>
     <string name="permission_sync_summary" msgid="765497944331294275">"এইটোত &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;ৰ &lt;strong&gt;মাইক্ৰ’ফ’ন&lt;/strong&gt;, &lt;strong&gt;কেমেৰা&lt;/strong&gt;, আৰু &lt;strong&gt;অৱস্থানৰ এক্সেছ&lt;/strong&gt;, আৰু অন্য সংবেদনশীল অনুমতিসমূহ অন্তৰ্ভুক্ত হ’ব পাৰে। &lt;br/&gt;&lt;br/&gt;আপুনি যিকোনো সময়তে &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;ত থকা আপোনাৰ ছেটিঙত এই অনুমতিসমূহ সলনি কৰিব পাৰে।"</string>
-    <string name="vendor_icon_description" msgid="4445875290032225965">"এপৰ চিহ্ন"</string>
-    <string name="vendor_header_button_description" msgid="6566660389500630608">"অধিক তথ্যৰ বুটাম"</string>
+    <string name="vendor_header_button_description" msgid="7994879208461111473">"অধিক তথ্য"</string>
     <string name="permission_phone" msgid="2661081078692784919">"ফ’ন"</string>
     <string name="permission_sms" msgid="6337141296535774786">"এছএমএছ"</string>
     <string name="permission_contacts" msgid="3858319347208004438">"সম্পৰ্ক"</string>
@@ -64,16 +57,18 @@
     <string name="permission_storage" msgid="6831099350839392343">"ফট’ আৰু মিডিয়া"</string>
     <string name="permission_notification" msgid="693762568127741203">"জাননী"</string>
     <string name="permission_app_streaming" msgid="6009695219091526422">"এপ্"</string>
-    <string name="permission_nearby_device_streaming" msgid="5868108148065023161">"নিকটৱৰ্তী ডিভাইচত ষ্ট্ৰীম কৰা"</string>
+    <string name="permission_nearby_device_streaming" msgid="1023325519477349499">"ষ্ট্ৰীম কৰি থকা হৈছে"</string>
     <string name="permission_phone_summary" msgid="6684396967861278044">"ফ’ন কল কৰিব আৰু পৰিচালনা কৰিব পাৰে"</string>
     <string name="permission_call_logs_summary" msgid="6186103394658755022">"ফ’নৰ কল লগ পঢ়িব আৰু লিখিব পাৰে"</string>
     <string name="permission_sms_summary" msgid="3508442683678912017">"এছএমএছ বাৰ্তা পঠিয়াব আৰু চাব পাৰে"</string>
     <string name="permission_contacts_summary" msgid="675861979475628708">"আপোনাৰ সম্পৰ্কসূচী এক্সেছ কৰিব পাৰে"</string>
     <string name="permission_calendar_summary" msgid="6460000922511766226">"আপোনাৰ কেলেণ্ডাৰ এক্সেছ কৰিব পাৰে"</string>
-    <string name="permission_microphone_summary" msgid="4241354865859396558">"মাইক্ৰ’ফ’ন ব্যৱহাৰ কৰি অডিঅ’ ৰেকৰ্ড কৰিব পাৰে"</string>
+    <string name="permission_microphone_summary" msgid="3692091540613093394">"অডিঅ’ ৰেকৰ্ড কৰিব পাৰে"</string>
     <string name="permission_nearby_devices_summary" msgid="931940524460876655">"নিকটৱৰ্তী ডিভাইচসমূহ বিচাৰিব, সেইসমূহৰ সৈতে সংযুক্ত হ’ব আৰু সেইসমূহৰ আপেক্ষিক স্থান নিৰ্ধাৰণ কৰিব পাৰে"</string>
     <string name="permission_notification_summary" msgid="884075314530071011">"সম্পৰ্কসূচী, বাৰ্তা আৰু ফট’ৰ দৰে তথ্যকে ধৰি আটাইবোৰ জাননী পঢ়িব পাৰে"</string>
     <string name="permission_app_streaming_summary" msgid="606923325679670624">"আপোনাৰ ফ’নৰ এপ্ ষ্ট্ৰীম কৰক"</string>
     <string name="permission_storage_summary" msgid="3918240895519506417"></string>
-    <string name="permission_nearby_device_streaming_summary" msgid="5776807830582725074">"এটা নিকটৱৰ্তী ডিভাইচত সমল ষ্ট্ৰীম কৰক"</string>
+    <string name="permission_nearby_device_streaming_summary" msgid="8280824871197081246">"আপোনাৰ ফ’নৰ পৰা এপ্‌ আৰু ছিষ্টেমৰ অন্য সুবিধাসমূহ ষ্ট্ৰীম কৰক"</string>
+    <string name="device_type" product="default" msgid="8268703872070046263">"ফ’ন"</string>
+    <string name="device_type" product="tablet" msgid="5038791954983067774">"টেবলেট"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-az/strings.xml b/packages/CompanionDeviceManager/res/values-az/strings.xml
index e333c51..18ac2ae 100644
--- a/packages/CompanionDeviceManager/res/values-az/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-az/strings.xml
@@ -17,43 +17,36 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Kompanyon Cihaz Meneceri"</string>
-    <!-- no translation found for confirmation_title (8024993972587946678) -->
-    <skip />
+    <string name="confirmation_title" msgid="2244241995958340998">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; tətbiqinə &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; cihazına daxil olmaq icazəsi verilsin?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"izləyin"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"&lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; tərəfindən idarə ediləcək <xliff:g id="PROFILE_NAME">%1$s</xliff:g> seçin"</string>
-    <!-- no translation found for summary_watch (6566922405914995759) -->
-    <skip />
-    <!-- no translation found for summary_watch_single_device (7443464525873186735) -->
-    <skip />
-    <string name="profile_name_glasses" msgid="8488394059007275998">"eynək"</string>
-    <!-- no translation found for summary_glasses (3808267780579061241) -->
-    <skip />
-    <!-- no translation found for summary_glasses_single_device (7051392780285915640) -->
-    <skip />
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; tərəfindən idarə ediləcək cihaz seçin"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Ayarlamaq üçün <xliff:g id="PROFILE_NAME">%1$s</xliff:g> seçin"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Bu tətbiq zəng edənin adı kimi məlumatları sinxronlaşdıra, <xliff:g id="DEVICE_NAME">%1$s</xliff:g> bu icazələrə daxil ola biləcək"</string>
+    <string name="confirmation_title_glasses" msgid="8288346850537727333">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; tətbiqinə &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; cihazını idarə etmək icazəsi verilsin?"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"cihazda"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Bu tətbiq <xliff:g id="DEVICE_NAME">%1$s</xliff:g> bu icazələrə daxil ola biləcək"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; tətbiqinə telefonunuzdan bu məlumata giriş icazəsi verin"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Cihazlararası xidmətlər"</string>
-    <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> tətbiqi <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> adından cihazlarınız arasında tətbiqləri yayımlamaq üçün icazə istəyir"</string>
+    <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> adından cihazlar arasında tətbiqləri yayımlamaq icazəsi istəyir"</string>
     <string name="title_automotive_projection" msgid="3296005598978412847"></string>
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; tətbiqinə telefonunuzdan bu məlumata giriş icazəsi verin"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Google Play xidmətləri"</string>
-    <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> tətbiqi <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> adından telefonunuzun fotoları, mediası və bildirişlərinə giriş üçün icazə istəyir"</string>
-    <string name="title_nearby_device_streaming" msgid="179278282547719200">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; tətbiqinə telefonunuzdan bu əməliyyatı icra etməyə icazə verin"</string>
-    <string name="helper_title_nearby_device_streaming" msgid="6124438217620593669">"Cihazlararası xidmətlər"</string>
-    <string name="helper_summary_nearby_device_streaming" msgid="5538329403511524333">"<xliff:g id="APP_NAME">%1$s</xliff:g> tətbiqi <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> adından yaxınlıqdakı cihazlarda yayımlamaq üçün icazə istəyir"</string>
+    <string name="helper_summary_computer" msgid="8774832742608187072">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> adından telefonun foto, media və bildirişlərinə giriş icazəsi istəyir"</string>
+    <string name="title_nearby_device_streaming" msgid="7269956847378799794">"&lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; cihazına bu əməliyyatı yerinə yetirmək icazəsi verilsin?"</string>
+    <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="DEVICE_NAME">%2$s</xliff:g> adından tətbiq və digər sistem funksiyalarını yaxınlıqdakı cihazlara yayımlamaq icazəsi sitəyir"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"cihaz"</string>
-    <!-- no translation found for summary_generic_single_device (4735072202474939111) -->
-    <skip />
-    <!-- no translation found for summary_generic (4988130802522924650) -->
-    <skip />
+    <string name="summary_generic" msgid="1761976003668044801">"Tətbiq zəng edənin adı kimi məlumatları telefon ilə seçilmiş cihaz arasında sinxronlaşdıracaq"</string>
     <string name="consent_yes" msgid="8344487259618762872">"İcazə verin"</string>
     <string name="consent_no" msgid="2640796915611404382">"İcazə verməyin"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Ləğv edin"</string>
     <string name="consent_back" msgid="2560683030046918882">"Geriyə"</string>
+    <string name="permission_expand" msgid="893185038020887411">"Genişləndirin: <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
+    <string name="permission_collapse" msgid="3320833884220844084">"Yığcamlaşdırın: <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"&lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; cihazındakı tətbiqlərə &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; cihazındakılarla eyni icazələr verilsin?"</string>
     <string name="permission_sync_summary" msgid="765497944331294275">"Buraya &lt;strong&gt;Mikrofon&lt;/strong&gt;, &lt;strong&gt;Kamera&lt;/strong&gt; və &lt;strong&gt;Məkana giriş&lt;/strong&gt;, eləcə də &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt; cihazında digər həssas icazələr daxil ola bilər. Bu icazələri istənilən vaxt &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt; cihazında ayarlarınızda dəyişə bilərsiniz.&lt;/p&gt;."</string>
-    <string name="vendor_icon_description" msgid="4445875290032225965">"Tətbiq İkonası"</string>
-    <string name="vendor_header_button_description" msgid="6566660389500630608">"Ətraflı Məlumat Düyməsi"</string>
+    <string name="vendor_header_button_description" msgid="7994879208461111473">"Ətraflı məlumat"</string>
     <string name="permission_phone" msgid="2661081078692784919">"Telefon"</string>
     <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
     <string name="permission_contacts" msgid="3858319347208004438">"Kontakt"</string>
@@ -64,16 +57,18 @@
     <string name="permission_storage" msgid="6831099350839392343">"Foto və media"</string>
     <string name="permission_notification" msgid="693762568127741203">"Bildirişlər"</string>
     <string name="permission_app_streaming" msgid="6009695219091526422">"Tətbiqlər"</string>
-    <string name="permission_nearby_device_streaming" msgid="5868108148065023161">"Yaxınlıqdakı Cihazlarda Yayım"</string>
+    <string name="permission_nearby_device_streaming" msgid="1023325519477349499">"Yayım"</string>
     <string name="permission_phone_summary" msgid="6684396967861278044">"Telefon zəngi edə və onları idarə edə bilər"</string>
     <string name="permission_call_logs_summary" msgid="6186103394658755022">"Telefonun zəng qeydini oxuya və yaza bilər"</string>
     <string name="permission_sms_summary" msgid="3508442683678912017">"SMS mesajları göndərə və baxa bilər"</string>
     <string name="permission_contacts_summary" msgid="675861979475628708">"Kontaktlarınıza giriş edə bilər"</string>
     <string name="permission_calendar_summary" msgid="6460000922511766226">"Təqviminizə giriş edə bilər"</string>
-    <string name="permission_microphone_summary" msgid="4241354865859396558">"Mikrofonunuzdan istifadə edərək audio yaza bilər."</string>
+    <string name="permission_microphone_summary" msgid="3692091540613093394">"Audio qeydə ala bilər"</string>
     <string name="permission_nearby_devices_summary" msgid="931940524460876655">"Yaxınlıqdakı cihazları tapa, qoşula və nisbi mövqeyi təyin edə bilər"</string>
     <string name="permission_notification_summary" msgid="884075314530071011">"Bütün bildirişləri, o cümlədən kontaktlar, mesajlar və fotolar kimi məlumatları oxuya bilər"</string>
     <string name="permission_app_streaming_summary" msgid="606923325679670624">"Telefonunuzun tətbiqlərini yayımlayın"</string>
     <string name="permission_storage_summary" msgid="3918240895519506417"></string>
-    <string name="permission_nearby_device_streaming_summary" msgid="5776807830582725074">"Məzmunu yaxınlıqdakı cihazda yayımlayın"</string>
+    <string name="permission_nearby_device_streaming_summary" msgid="8280824871197081246">"Telefondan tətbiq və digər sistem funksiyalarını yayımlayın"</string>
+    <string name="device_type" product="default" msgid="8268703872070046263">"telefonda"</string>
+    <string name="device_type" product="tablet" msgid="5038791954983067774">"planşetdə"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-b+sr+Latn/strings.xml b/packages/CompanionDeviceManager/res/values-b+sr+Latn/strings.xml
index 8564793..b37ab4b9 100644
--- a/packages/CompanionDeviceManager/res/values-b+sr+Latn/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-b+sr+Latn/strings.xml
@@ -17,43 +17,36 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Menadžer pridruženog uređaja"</string>
-    <!-- no translation found for confirmation_title (8024993972587946678) -->
-    <skip />
+    <string name="confirmation_title" msgid="2244241995958340998">"Dozvolite da aplikacija &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; pristupa uređaju &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"sat"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Odaberite <xliff:g id="PROFILE_NAME">%1$s</xliff:g> kojim će upravljati aplikacija &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <!-- no translation found for summary_watch (6566922405914995759) -->
-    <skip />
-    <!-- no translation found for summary_watch_single_device (7443464525873186735) -->
-    <skip />
-    <string name="profile_name_glasses" msgid="8488394059007275998">"naočare"</string>
-    <!-- no translation found for summary_glasses (3808267780579061241) -->
-    <skip />
-    <!-- no translation found for summary_glasses_single_device (7051392780285915640) -->
-    <skip />
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"Odaberite uređaj kojim će upravljati aplikacija &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Odaberite profil <xliff:g id="PROFILE_NAME">%1$s</xliff:g> koji želite da podesite"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Ovoj aplikaciji će biti dozvoljeno da sinhronizuje podatke, poput imena osobe koja upućuje poziv, i pristupa tim dozvolama na vašem uređaju (<xliff:g id="DEVICE_NAME">%1$s</xliff:g>)"</string>
+    <string name="confirmation_title_glasses" msgid="8288346850537727333">"Želite li da dozvolite da &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; upravlja uređajem &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"uređaj"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Ovoj aplikaciji će biti dozvoljeno da pristupa ovim dozvolama na vašem uređaju (<xliff:g id="DEVICE_NAME">%1$s</xliff:g>)"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Dozvolite da &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; pristupa ovim informacijama sa telefona"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Usluge na više uređaja"</string>
-    <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> zahteva dozvolu u ime uređaja <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> za strimovanje aplikacija između uređaja"</string>
+    <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g> zahteva dozvolu u ime uređaja <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> za strimovanje aplikacija između uređaja"</string>
     <string name="title_automotive_projection" msgid="3296005598978412847"></string>
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"Dozvolite da &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; pristupa ovim informacijama sa telefona"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Google Play usluge"</string>
-    <string name="helper_summary_computer" msgid="9050724687678157852">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> zahteva dozvolu u ime uređaja <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> za pristup slikama, medijskom sadržaju i obaveštenjima sa telefona"</string>
-    <string name="title_nearby_device_streaming" msgid="179278282547719200">"Dozvolite da &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; obavlja ovu radnju sa telefona"</string>
-    <string name="helper_title_nearby_device_streaming" msgid="6124438217620593669">"Usluge na više uređaja"</string>
-    <string name="helper_summary_nearby_device_streaming" msgid="5538329403511524333">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> zahteva dozvolu u ime uređaja <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> da strimuje sadržaj na uređaje u blizini"</string>
+    <string name="helper_summary_computer" msgid="8774832742608187072">"<xliff:g id="APP_NAME">%1$s</xliff:g> zahteva dozvolu u ime uređaja <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> za pristup slikama, medijskom sadržaju i obaveštenjima sa telefona"</string>
+    <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Želite li da dozvolite da &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; obavi ovu radnju?"</string>
+    <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> zahteva dozvolu u ime uređaja <xliff:g id="DEVICE_NAME">%2$s</xliff:g> da strimuje aplikacije i druge sistemske funkcije na uređaje u blizini"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"uređaj"</string>
-    <!-- no translation found for summary_generic_single_device (4735072202474939111) -->
-    <skip />
-    <!-- no translation found for summary_generic (4988130802522924650) -->
-    <skip />
+    <string name="summary_generic" msgid="1761976003668044801">"Ova aplikacija će moći da sinhronizuje podatke, poput imena osobe koja upućuje poziv, između telefona i odabranog uređaja"</string>
     <string name="consent_yes" msgid="8344487259618762872">"Dozvoli"</string>
     <string name="consent_no" msgid="2640796915611404382">"Ne dozvoli"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Otkaži"</string>
     <string name="consent_back" msgid="2560683030046918882">"Nazad"</string>
+    <string name="permission_expand" msgid="893185038020887411">"Proširi <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
+    <string name="permission_collapse" msgid="3320833884220844084">"Skupi <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Aplikcijama na uređaju &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; dajete sve dozvole kao na uređaju &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="permission_sync_summary" msgid="765497944331294275">"To može da obuhvata pristup &lt;strong&gt;mikrofonu&lt;/strong&gt;, &lt;strong&gt;kameri&lt;/strong&gt;, i &lt;strong&gt;lokaciji&lt;/strong&gt;, i druge osetljive dozvole na uređaju &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;. &lt;br/&gt;&lt;br/&gt;Možete da promenite te dozvole u bilo kom trenutku u Podešavanjima na uređaju &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;."</string>
-    <string name="vendor_icon_description" msgid="4445875290032225965">"Ikona aplikacije"</string>
-    <string name="vendor_header_button_description" msgid="6566660389500630608">"Dugme za više informacija"</string>
+    <string name="vendor_header_button_description" msgid="7994879208461111473">"Još informacija"</string>
     <string name="permission_phone" msgid="2661081078692784919">"Telefon"</string>
     <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
     <string name="permission_contacts" msgid="3858319347208004438">"Kontakti"</string>
@@ -64,16 +57,18 @@
     <string name="permission_storage" msgid="6831099350839392343">"Slike i mediji"</string>
     <string name="permission_notification" msgid="693762568127741203">"Obaveštenja"</string>
     <string name="permission_app_streaming" msgid="6009695219091526422">"Aplikacije"</string>
-    <string name="permission_nearby_device_streaming" msgid="5868108148065023161">"Strimovanje, uređaji u blizini"</string>
+    <string name="permission_nearby_device_streaming" msgid="1023325519477349499">"Striming"</string>
     <string name="permission_phone_summary" msgid="6684396967861278044">"Može da upućuje telefonske pozive i upravlja njima"</string>
     <string name="permission_call_logs_summary" msgid="6186103394658755022">"Može da čita i piše evidenciju poziva na telefonu"</string>
     <string name="permission_sms_summary" msgid="3508442683678912017">"Može da šalje i pregleda SMS poruke"</string>
     <string name="permission_contacts_summary" msgid="675861979475628708">"Može da pristupa kontaktima"</string>
     <string name="permission_calendar_summary" msgid="6460000922511766226">"Može da pristupa kalendaru"</string>
-    <string name="permission_microphone_summary" msgid="4241354865859396558">"Može da snima zvuk pomoću mikrofona"</string>
+    <string name="permission_microphone_summary" msgid="3692091540613093394">"Može da snima zvuk"</string>
     <string name="permission_nearby_devices_summary" msgid="931940524460876655">"Može da pronalazi i utvrđuje relativnu poziciju uređaja u blizini, kao i da se povezuje sa njima"</string>
     <string name="permission_notification_summary" msgid="884075314530071011">"Može da čita sva obaveštenja, uključujući informacije poput kontakata, poruka i slika"</string>
     <string name="permission_app_streaming_summary" msgid="606923325679670624">"Strimujte aplikacije na telefonu"</string>
     <string name="permission_storage_summary" msgid="3918240895519506417"></string>
-    <string name="permission_nearby_device_streaming_summary" msgid="5776807830582725074">"Strimujte sadržaj na uređaj u blizini"</string>
+    <string name="permission_nearby_device_streaming_summary" msgid="8280824871197081246">"Strimujte aplikacije i druge sistemske funkcije sa telefona"</string>
+    <string name="device_type" product="default" msgid="8268703872070046263">"telefonu"</string>
+    <string name="device_type" product="tablet" msgid="5038791954983067774">"tabletu"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-be/strings.xml b/packages/CompanionDeviceManager/res/values-be/strings.xml
index 071dd6f..41889fd 100644
--- a/packages/CompanionDeviceManager/res/values-be/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-be/strings.xml
@@ -17,43 +17,36 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Менеджар спадарожнай прылады"</string>
-    <!-- no translation found for confirmation_title (8024993972587946678) -->
-    <skip />
+    <string name="confirmation_title" msgid="2244241995958340998">"Дазволіць праграме &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; доступ да прылады &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"гадзіннік"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Выберыце прыладу (<xliff:g id="PROFILE_NAME">%1$s</xliff:g>), якой будзе кіраваць праграма &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <!-- no translation found for summary_watch (6566922405914995759) -->
-    <skip />
-    <!-- no translation found for summary_watch_single_device (7443464525873186735) -->
-    <skip />
-    <string name="profile_name_glasses" msgid="8488394059007275998">"акуляры"</string>
-    <!-- no translation found for summary_glasses (3808267780579061241) -->
-    <skip />
-    <!-- no translation found for summary_glasses_single_device (7051392780285915640) -->
-    <skip />
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"Выберыце прыладу (<xliff:g id="APP_NAME">%1$s</xliff:g>), якой будзе кіраваць праграма &lt;strong&gt;&lt;/strong&gt;"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Выберыце імя <xliff:g id="PROFILE_NAME">%1$s</xliff:g> для наладжвання"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Гэтая праграма зможа сінхранізаваць інфармацыю (напрыклад, імя таго, хто звоніць) на вашай прыладзе \"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>\" і атрымае наступныя дазволы"</string>
+    <string name="confirmation_title_glasses" msgid="8288346850537727333">"Дазволіць праграме &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; кіраваць прыладай &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"прылада"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Гэтая праграма будзе мець на вашай прыладзе \"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>\" наступныя дазволы"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Дазвольце праграме &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; мець доступ да гэтай інфармацыі з вашага тэлефона"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Сэрвісы для некалькіх прылад"</string>
-    <string name="helper_summary_app_streaming" msgid="5977509499890099">"Праграма \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" запытвае дазвол ад імя вашай прылады \"<xliff:g id="DEVICE_TYPE">%2$s</xliff:g>\" на трансляцыю праграм паміж вашымі прыладамі"</string>
+    <string name="helper_summary_app_streaming" msgid="2396773196949578425">"Праграма \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" запытвае дазвол ад імя вашай прылады \"<xliff:g id="DISPLAY_NAME">%2$s</xliff:g>\" на трансляцыю праграм паміж вашымі прыладамі"</string>
     <string name="title_automotive_projection" msgid="3296005598978412847"></string>
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"Дазвольце праграме &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; мець доступ да гэтай інфармацыі з вашага тэлефона"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Сэрвісы Google Play"</string>
-    <string name="helper_summary_computer" msgid="9050724687678157852">"Праграма \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" запытвае дазвол ад імя вашай прылады \"<xliff:g id="DEVICE_TYPE">%2$s</xliff:g>\" на доступ да фота, медыяфайлаў і апавяшчэнняў на вашым тэлефоне"</string>
-    <string name="title_nearby_device_streaming" msgid="179278282547719200">"Дазволіць праграме &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; выконваць гэта дзеянне з вашага тэлефона"</string>
-    <string name="helper_title_nearby_device_streaming" msgid="6124438217620593669">"Сэрвісы для некалькіх прылад"</string>
-    <string name="helper_summary_nearby_device_streaming" msgid="5538329403511524333">"Праграма \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" запытвае дазвол ад імя вашай прылады \"<xliff:g id="DEVICE_TYPE">%2$s</xliff:g>\" на перадачу змесціва плынню на прылады паблізу."</string>
+    <string name="helper_summary_computer" msgid="8774832742608187072">"Праграма \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" запытвае дазвол ад імя вашай прылады \"<xliff:g id="DISPLAY_NAME">%2$s</xliff:g>\" на доступ да фота, медыяфайлаў і апавяшчэнняў на вашым тэлефоне"</string>
+    <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Дазволіць прыладзе &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; выканаць гэта дзеянне?"</string>
+    <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"Праграма \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" запытвае дазвол ад імя вашай прылады \"<xliff:g id="DEVICE_NAME">%2$s</xliff:g>\" на перадачу плынню змесціва праграм і іншых функцый сістэмы на прылады паблізу"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"прылада"</string>
-    <!-- no translation found for summary_generic_single_device (4735072202474939111) -->
-    <skip />
-    <!-- no translation found for summary_generic (4988130802522924650) -->
-    <skip />
+    <string name="summary_generic" msgid="1761976003668044801">"Гэта праграма зможа сінхранізаваць інфармацыю (напрыклад, імя таго, хто звоніць) паміж тэлефонам і выбранай прыладай"</string>
     <string name="consent_yes" msgid="8344487259618762872">"Дазволіць"</string>
     <string name="consent_no" msgid="2640796915611404382">"Не дазваляць"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Скасаваць"</string>
     <string name="consent_back" msgid="2560683030046918882">"Назад"</string>
+    <string name="permission_expand" msgid="893185038020887411">"Разгарнуць <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
+    <string name="permission_collapse" msgid="3320833884220844084">"Згарнуць <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Даць праграмам на прыладзе &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; такія самыя дазволы, што і на прыладзе &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="permission_sync_summary" msgid="765497944331294275">"Праграмы змогуць атрымліваць доступ да &lt;strong&gt;мікрафона&lt;/strong&gt;, &lt;strong&gt;камеры&lt;/strong&gt; і &lt;strong&gt;даных пра месцазнаходжанне&lt;/strong&gt;, а таксама да іншай канфідэнцыяльнай інфармацыі на прыладзе &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;. &lt;br/&gt;&lt;br/&gt;Вы можаце ў любы час змяніць гэтыя дазволы ў Наладах на прыладзе &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;."</string>
-    <string name="vendor_icon_description" msgid="4445875290032225965">"Значок праграмы"</string>
-    <string name="vendor_header_button_description" msgid="6566660389500630608">"Кнопка \"Даведацца больш\""</string>
+    <string name="vendor_header_button_description" msgid="7994879208461111473">"Дадатковая інфармацыя"</string>
     <string name="permission_phone" msgid="2661081078692784919">"Тэлефон"</string>
     <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
     <string name="permission_contacts" msgid="3858319347208004438">"Кантакты"</string>
@@ -64,16 +57,18 @@
     <string name="permission_storage" msgid="6831099350839392343">"Фота і медыяфайлы"</string>
     <string name="permission_notification" msgid="693762568127741203">"Апавяшчэнні"</string>
     <string name="permission_app_streaming" msgid="6009695219091526422">"Праграмы"</string>
-    <string name="permission_nearby_device_streaming" msgid="5868108148065023161">"Перадача плынню для прылады паблізу"</string>
+    <string name="permission_nearby_device_streaming" msgid="1023325519477349499">"Перадача плынню"</string>
     <string name="permission_phone_summary" msgid="6684396967861278044">"Можа рабіць тэлефонныя выклікі і кіраваць імі"</string>
     <string name="permission_call_logs_summary" msgid="6186103394658755022">"Можа счытваць і запісваць даныя ў журнале тэлефонных выклікаў"</string>
     <string name="permission_sms_summary" msgid="3508442683678912017">"Можа адпраўляць і праглядаць SMS-паведамленні"</string>
     <string name="permission_contacts_summary" msgid="675861979475628708">"Можа атрымліваць доступ да вашых кантактаў"</string>
     <string name="permission_calendar_summary" msgid="6460000922511766226">"Можа атрымліваць доступ да вашага календара"</string>
-    <string name="permission_microphone_summary" msgid="4241354865859396558">"Можа запісваць аўдыя з выкарыстаннем мікрафона"</string>
+    <string name="permission_microphone_summary" msgid="3692091540613093394">"Можа запісваць аўдыя"</string>
     <string name="permission_nearby_devices_summary" msgid="931940524460876655">"Можа знаходзіць прылады паблізу, падключацца да іх і вызначаць іх прыблізнае месцазнаходжанне"</string>
     <string name="permission_notification_summary" msgid="884075314530071011">"Можа счытваць усе апавяшчэнні, уключаючы паведамленні, фота і інфармацыю пра кантакты"</string>
     <string name="permission_app_streaming_summary" msgid="606923325679670624">"Трансляцыя змесціва праграм з вашага тэлефона"</string>
     <string name="permission_storage_summary" msgid="3918240895519506417"></string>
-    <string name="permission_nearby_device_streaming_summary" msgid="5776807830582725074">"Перадача змесціва плынню на прыладу паблізу"</string>
+    <string name="permission_nearby_device_streaming_summary" msgid="8280824871197081246">"Перадача плынню змесціва праграм і іншых функцый сістэмы з вашага тэлефона"</string>
+    <string name="device_type" product="default" msgid="8268703872070046263">"тэлефон"</string>
+    <string name="device_type" product="tablet" msgid="5038791954983067774">"планшэт"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-bg/strings.xml b/packages/CompanionDeviceManager/res/values-bg/strings.xml
index 780924a..bb53c5c 100644
--- a/packages/CompanionDeviceManager/res/values-bg/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-bg/strings.xml
@@ -17,43 +17,36 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Companion Device Manager"</string>
-    <!-- no translation found for confirmation_title (8024993972587946678) -->
-    <skip />
+    <string name="confirmation_title" msgid="2244241995958340998">"Да се разреши ли на приложението &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; да осъществява достъп до устройството &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"часовник"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Изберете устройство (<xliff:g id="PROFILE_NAME">%1$s</xliff:g>), което да се управлява от &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <!-- no translation found for summary_watch (6566922405914995759) -->
-    <skip />
-    <!-- no translation found for summary_watch_single_device (7443464525873186735) -->
-    <skip />
-    <string name="profile_name_glasses" msgid="8488394059007275998">"очилата"</string>
-    <!-- no translation found for summary_glasses (3808267780579061241) -->
-    <skip />
-    <!-- no translation found for summary_glasses_single_device (7051392780285915640) -->
-    <skip />
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"Изберете устройство, което да се управлява от &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Изберете <xliff:g id="PROFILE_NAME">%1$s</xliff:g>, за да го настроите"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Това приложение ще получи право да синхронизира различна информация, като например името на обаждащия се, и достъп до следните разрешения за вашия <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
+    <string name="confirmation_title_glasses" msgid="8288346850537727333">"Разрешавате ли на &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; да управлява устройството &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"устройство"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Това приложение ще има достъп до следните разрешения за вашето <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Разрешете на &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; да осъществява достъп до тази информация от телефона ви"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Услуги за различни устройства"</string>
-    <string name="helper_summary_app_streaming" msgid="5977509499890099">"„<xliff:g id="APP_NAME">%1$s</xliff:g>“ иска разрешение от името на <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> да предава поточно приложения между устройствата ви"</string>
+    <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g> иска разрешение от името на <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> да предава поточно приложения между устройствата ви"</string>
     <string name="title_automotive_projection" msgid="3296005598978412847"></string>
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"Разрешете на &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; да осъществява достъп до тази информация от телефона ви"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Услуги за Google Play"</string>
-    <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> иска разрешение от името на <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> за достъп до снимките, мултимедията и известията на телефона ви"</string>
-    <string name="title_nearby_device_streaming" msgid="179278282547719200">"Разрешаване на &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; да изпълнява това действие от телефона ви"</string>
-    <string name="helper_title_nearby_device_streaming" msgid="6124438217620593669">"Услуги за различни устройства"</string>
-    <string name="helper_summary_nearby_device_streaming" msgid="5538329403511524333">"<xliff:g id="APP_NAME">%1$s</xliff:g> иска разрешение от името на <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> да предава поточно съдържание към устройства в близост"</string>
+    <string name="helper_summary_computer" msgid="8774832742608187072">"<xliff:g id="APP_NAME">%1$s</xliff:g> иска разрешение от името на <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> за достъп до снимките, мултимедията и известията на телефона ви"</string>
+    <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Разрешавате ли на &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; да предприема това действие?"</string>
+    <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> иска разрешение от името на <xliff:g id="DEVICE_NAME">%2$s</xliff:g> да предава поточно приложения и други системни функции към устройства в близост"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"устройство"</string>
-    <!-- no translation found for summary_generic_single_device (4735072202474939111) -->
-    <skip />
-    <!-- no translation found for summary_generic (4988130802522924650) -->
-    <skip />
+    <string name="summary_generic" msgid="1761976003668044801">"Това приложение ще може да синхронизира различна информация, като например името на обаждащия се, между телефона ви и избраното устройство"</string>
     <string name="consent_yes" msgid="8344487259618762872">"Разрешаване"</string>
     <string name="consent_no" msgid="2640796915611404382">"Забраняване"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Отказ"</string>
     <string name="consent_back" msgid="2560683030046918882">"Назад"</string>
+    <string name="permission_expand" msgid="893185038020887411">"Разгъване на <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
+    <string name="permission_collapse" msgid="3320833884220844084">"Свиване на <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Искате ли да дадете на приложенията на &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; същите разрешения както на &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="permission_sync_summary" msgid="765497944331294275">"Това може да включва достъп до &lt;strong&gt;микрофона&lt;/strong&gt;, &lt;strong&gt;камерата&lt;/strong&gt; и &lt;strong&gt;местоположението&lt;/strong&gt;, както и други разрешения за достъп до поверителна информация на &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;. &lt;br/&gt;&lt;br/&gt;Имате възможност да промените тези разрешения по всяко време от настройките на &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;."</string>
-    <string name="vendor_icon_description" msgid="4445875290032225965">"Икона на приложението"</string>
-    <string name="vendor_header_button_description" msgid="6566660389500630608">"Бутон за още информация"</string>
+    <string name="vendor_header_button_description" msgid="7994879208461111473">"Още информация"</string>
     <string name="permission_phone" msgid="2661081078692784919">"Телефон"</string>
     <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
     <string name="permission_contacts" msgid="3858319347208004438">"Контакти"</string>
@@ -64,16 +57,18 @@
     <string name="permission_storage" msgid="6831099350839392343">"Снимки и мултимедия"</string>
     <string name="permission_notification" msgid="693762568127741203">"Известия"</string>
     <string name="permission_app_streaming" msgid="6009695219091526422">"Приложения"</string>
-    <string name="permission_nearby_device_streaming" msgid="5868108148065023161">"Пот. предав. към у-ва наблизо"</string>
+    <string name="permission_nearby_device_streaming" msgid="1023325519477349499">"Поточно предаване"</string>
     <string name="permission_phone_summary" msgid="6684396967861278044">"Може да извършва и управлява телефонни обаждания"</string>
     <string name="permission_call_logs_summary" msgid="6186103394658755022">"Може да чете списъка с телефонните обаждания и да записва в него"</string>
     <string name="permission_sms_summary" msgid="3508442683678912017">"Може да изпраща и преглежда SMS съобщения"</string>
     <string name="permission_contacts_summary" msgid="675861979475628708">"Може да осъществява достъп до контактите ви"</string>
     <string name="permission_calendar_summary" msgid="6460000922511766226">"Може да осъществява достъп до календара ви"</string>
-    <string name="permission_microphone_summary" msgid="4241354865859396558">"Може да записва аудио посредством микрофона"</string>
+    <string name="permission_microphone_summary" msgid="3692091540613093394">"Може да записва звук"</string>
     <string name="permission_nearby_devices_summary" msgid="931940524460876655">"Може да намира и да се свързва с устройства в близост, както и да определя относителната им позиция"</string>
     <string name="permission_notification_summary" msgid="884075314530071011">"Може да чете всички известия, включително различна информация, като например контакти, съобщения и снимки"</string>
     <string name="permission_app_streaming_summary" msgid="606923325679670624">"Поточно предаване на приложенията на телефона ви"</string>
     <string name="permission_storage_summary" msgid="3918240895519506417"></string>
-    <string name="permission_nearby_device_streaming_summary" msgid="5776807830582725074">"Поточно предаване на съдържание към устройства в близост"</string>
+    <string name="permission_nearby_device_streaming_summary" msgid="8280824871197081246">"Поточно предаване на приложения и други системни функции от телефона ви"</string>
+    <string name="device_type" product="default" msgid="8268703872070046263">"телефон"</string>
+    <string name="device_type" product="tablet" msgid="5038791954983067774">"таблет"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-bn/strings.xml b/packages/CompanionDeviceManager/res/values-bn/strings.xml
index 88e334e..bc0af0b 100644
--- a/packages/CompanionDeviceManager/res/values-bn/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-bn/strings.xml
@@ -17,43 +17,36 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Companion Device Manager"</string>
-    <!-- no translation found for confirmation_title (8024993972587946678) -->
-    <skip />
+    <string name="confirmation_title" msgid="2244241995958340998">"&lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; অ্যাক্সেস করার জন্য &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; অ্যাপকে কি অনুমতি দিতে চান?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"ঘড়ি"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"<xliff:g id="PROFILE_NAME">%1$s</xliff:g> বেছে নিন যেটি &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; ম্যানেজ করবে"</string>
-    <!-- no translation found for summary_watch (6566922405914995759) -->
-    <skip />
-    <!-- no translation found for summary_watch_single_device (7443464525873186735) -->
-    <skip />
-    <string name="profile_name_glasses" msgid="8488394059007275998">"চশমা"</string>
-    <!-- no translation found for summary_glasses (3808267780579061241) -->
-    <skip />
-    <!-- no translation found for summary_glasses_single_device (7051392780285915640) -->
-    <skip />
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ম্যানেজ করা যাবে এমন একটি ডিভাইস বেছে নিন"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"সেট-আপ করতে কোনও <xliff:g id="PROFILE_NAME">%1$s</xliff:g> বেছে নিন"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"এই অ্যাপকে, কল করছেন এমন কোনও ব্যক্তির নামের মতো তথ্য সিঙ্ক করতে এবং আপনার <xliff:g id="DEVICE_NAME">%1$s</xliff:g>-এ এইসব অনুমতি অ্যাক্সেস করতে দেওয়া হবে"</string>
+    <string name="confirmation_title_glasses" msgid="8288346850537727333">"আপনি কি &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ম্যানেজ করার জন্য &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;-কে অনুমতি দেবেন?"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"ডিভাইস"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"এই অ্যাপ আপনার <xliff:g id="DEVICE_NAME">%1$s</xliff:g>-এ এইসব অনুমতি অ্যাক্সেস করতে পারবে"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"আপনার ফোন থেকে &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; অ্যাপকে এই তথ্য অ্যাক্সেস করার অনুমতি দিন"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"ক্রস-ডিভাইস পরিষেবা"</string>
-    <string name="helper_summary_app_streaming" msgid="5977509499890099">"আপনার ডিভাইসগুলির মধ্যে অ্যাপ স্ট্রিম করার জন্য <xliff:g id="APP_NAME">%1$s</xliff:g>, <xliff:g id="DEVICE_TYPE">%2$s</xliff:g>-এর হয়ে অনুমতি চাইছে"</string>
+    <string name="helper_summary_app_streaming" msgid="2396773196949578425">"আপনার ডিভাইসগুলির মধ্যে অ্যাপ স্ট্রিম করার জন্য <xliff:g id="APP_NAME">%1$s</xliff:g>, <xliff:g id="DISPLAY_NAME">%2$s</xliff:g>-এর হয়ে অনুমতি চাইছে"</string>
     <string name="title_automotive_projection" msgid="3296005598978412847"></string>
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"আপনার ফোন থেকে &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;-কে এই তথ্য অ্যাক্সেস করার অনুমতি দিন"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Google Play পরিষেবা"</string>
-    <string name="helper_summary_computer" msgid="9050724687678157852">"আপনার ফোনের ফটো, মিডিয়া এবং তথ্য অ্যাক্সেস করার জন্য <xliff:g id="APP_NAME">%1$s</xliff:g>, <xliff:g id="DEVICE_TYPE">%2$s</xliff:g>-এর হয়ে অনুমতি চাইছে"</string>
-    <string name="title_nearby_device_streaming" msgid="179278282547719200">"আপনার ফোন থেকে এই অ্যাকশন পারফর্ম করার জন্য &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>-কে&lt;/strong&gt; অনুমতি দিন"</string>
-    <string name="helper_title_nearby_device_streaming" msgid="6124438217620593669">"ক্রস-ডিভাইস পরিষেবা"</string>
-    <string name="helper_summary_nearby_device_streaming" msgid="5538329403511524333">"আশেপাশের ডিভাইসে কন্টেন্ট স্ট্রিম করার জন্য আপনার <xliff:g id="DEVICE_TYPE">%2$s</xliff:g>-এর হয়ে <xliff:g id="APP_NAME">%1$s</xliff:g> অনুমতি চাইছে"</string>
+    <string name="helper_summary_computer" msgid="8774832742608187072">"আপনার ফোনের ফটো, মিডিয়া এবং তথ্য অ্যাক্সেস করার জন্য <xliff:g id="APP_NAME">%1$s</xliff:g>, <xliff:g id="DISPLAY_NAME">%2$s</xliff:g>-এর হয়ে অনুমতি চাইছে"</string>
+    <string name="title_nearby_device_streaming" msgid="7269956847378799794">"&lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt;-কে এই কাজটি করতে দেবেন?"</string>
+    <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"আশেপাশের ডিভাইসে অ্যাপ ও অন্যান্য সিস্টেম ফিচার স্ট্রিম করার জন্য আপনার <xliff:g id="DEVICE_NAME">%2$s</xliff:g>-এর হয়ে <xliff:g id="APP_NAME">%1$s</xliff:g> অনুমতি চেয়ে অনুরোধ করছে"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"ডিভাইস"</string>
-    <!-- no translation found for summary_generic_single_device (4735072202474939111) -->
-    <skip />
-    <!-- no translation found for summary_generic (4988130802522924650) -->
-    <skip />
+    <string name="summary_generic" msgid="1761976003668044801">"এই অ্যাপ, আপনার ফোন এবং বেছে নেওয়া ডিভাইসের মধ্যে তথ্য সিঙ্ক করতে পারবে, যেমন কোনও কলারের নাম"</string>
     <string name="consent_yes" msgid="8344487259618762872">"অনুমতি দিন"</string>
     <string name="consent_no" msgid="2640796915611404382">"অনুমতি দেবেন না"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"বাতিল করুন"</string>
     <string name="consent_back" msgid="2560683030046918882">"ফিরুন"</string>
+    <string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> বড় করুন"</string>
+    <string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> আড়াল করুন"</string>
     <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"&lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;-এ যে অনুমতি দেওয়া আছে &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt;-এও সেই একই অনুমতি দিতে চান?"</string>
     <string name="permission_sync_summary" msgid="765497944331294275">"এর মধ্যে &lt;strong&gt;মাইক্রোফোন&lt;/strong&gt;, &lt;strong&gt;ক্যামেরা&lt;/strong&gt;, ও &lt;strong&gt;লোকেশন সংক্রান্ত অ্যাক্সেস &lt;/strong&gt;, এবং &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;.-এর অন্যান্য সংবেদনশীল অনুমতি অন্তর্ভুক্ত থাকতে পারে &lt;br/&gt;&lt;br/&gt;আপনি যেকোনও সময়&lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;.-এর সেটিংস থেকে এইসব অনুমতি পরিবর্তন করতে পারবেন"</string>
-    <string name="vendor_icon_description" msgid="4445875290032225965">"অ্যাপের আইকন"</string>
-    <string name="vendor_header_button_description" msgid="6566660389500630608">"আরও তথ্য সংক্রান্ত বোতাম"</string>
+    <string name="vendor_header_button_description" msgid="7994879208461111473">"আরও তথ্য"</string>
     <string name="permission_phone" msgid="2661081078692784919">"ফোন"</string>
     <string name="permission_sms" msgid="6337141296535774786">"এসএমএস"</string>
     <string name="permission_contacts" msgid="3858319347208004438">"পরিচিতি"</string>
@@ -64,16 +57,18 @@
     <string name="permission_storage" msgid="6831099350839392343">"ফটো ও মিডিয়া"</string>
     <string name="permission_notification" msgid="693762568127741203">"বিজ্ঞপ্তি"</string>
     <string name="permission_app_streaming" msgid="6009695219091526422">"অ্যাপ"</string>
-    <string name="permission_nearby_device_streaming" msgid="5868108148065023161">"আশেপাশের ডিভাইসে স্ট্রিম করা"</string>
+    <string name="permission_nearby_device_streaming" msgid="1023325519477349499">"স্ট্রিমিং"</string>
     <string name="permission_phone_summary" msgid="6684396967861278044">"ফোন কল করতে ও ম্যানেজ করতে পারবে"</string>
     <string name="permission_call_logs_summary" msgid="6186103394658755022">"ফোনের কল লগ পড়তে ও লিখতে পারবে"</string>
     <string name="permission_sms_summary" msgid="3508442683678912017">"এসএমএস মেসেজ পাঠাতে ও দেখতে পারবে"</string>
     <string name="permission_contacts_summary" msgid="675861979475628708">"আপনার পরিচিতি অ্যাক্সেস করতে পারবে"</string>
     <string name="permission_calendar_summary" msgid="6460000922511766226">"আপনার ক্যালেন্ডার অ্যাক্সেস করতে পারবে"</string>
-    <string name="permission_microphone_summary" msgid="4241354865859396558">"মাইক্রোফোন ব্যবহার করে অডিও রেকর্ড করতে পারবেন"</string>
+    <string name="permission_microphone_summary" msgid="3692091540613093394">"অডিও রেকর্ড করতে পারে"</string>
     <string name="permission_nearby_devices_summary" msgid="931940524460876655">"আশেপাশের ডিভাইস খুঁজে দেখতে, তার সাথে কানেক্ট করতে এবং তার আপেক্ষিক অবস্থান নির্ধারণ করতে পারবে"</string>
     <string name="permission_notification_summary" msgid="884075314530071011">"সব বিজ্ঞপ্তি পড়তে পারবে, যার মধ্যে পরিচিতি, মেসেজ ও ফটোর মতো তথ্য অন্তর্ভুক্ত"</string>
     <string name="permission_app_streaming_summary" msgid="606923325679670624">"আপনার ফোনের অ্যাপ স্ট্রিম করুন"</string>
     <string name="permission_storage_summary" msgid="3918240895519506417"></string>
-    <string name="permission_nearby_device_streaming_summary" msgid="5776807830582725074">"আশেপাশের ডিভাইসে কন্টেন্ট স্ট্রিম করুন"</string>
+    <string name="permission_nearby_device_streaming_summary" msgid="8280824871197081246">"আপনার ফোন থেকে অ্যাপ ও অন্যান্য সিস্টেম ফিচার স্ট্রিম করে"</string>
+    <string name="device_type" product="default" msgid="8268703872070046263">"ফোন"</string>
+    <string name="device_type" product="tablet" msgid="5038791954983067774">"ট্যাবলেট"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-bs/strings.xml b/packages/CompanionDeviceManager/res/values-bs/strings.xml
index 4f8d37a..538ee35 100644
--- a/packages/CompanionDeviceManager/res/values-bs/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-bs/strings.xml
@@ -17,36 +17,36 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Prateći upravitelj uređaja"</string>
-    <string name="confirmation_title" msgid="8024993972587946678">"Dopustite aplikaciji &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; da pristupa uređaju &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"Dozvoliti aplikaciji &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; da pristupa uređaju &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"sat"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Odaberite uređaj \"<xliff:g id="PROFILE_NAME">%1$s</xliff:g>\" kojim će upravljati aplikacija &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="6566922405914995759">"Aplikacija je potrebna za upravljanje vašim uređajem <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. Aplikacija <xliff:g id="APP_NAME">%2$s</xliff:g> moći će sinkronizirati podatke, primjerice ime pozivatelja, stupati u interakciju s vašim obavijestima i pristupati vašim dopuštenjima za telefon, SMS-ove, kontakte, kalendar, zapisnike poziva i uređaje u blizini."</string>
-    <string name="summary_watch_single_device" msgid="7443464525873186735">"Aplikacija je potrebna za upravljanje vašim uređajem <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. Aplikacija <xliff:g id="APP_NAME">%2$s</xliff:g> moći će sinkronizirati podatke, primjerice ime pozivatelja i pristupati sljedećim dopuštenjima:"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"naočale"</string>
-    <string name="summary_glasses" msgid="3808267780579061241">"Ta je aplikacija potrebna za upravljanje uređajem <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. Aplikacija <xliff:g id="APP_NAME">%2$s</xliff:g> moći će stupati u interakciju s vašim obavijestima i pristupati vašim dopuštenjima za telefon, SMS-ove, kontakte, mikrofon i uređaje u blizini."</string>
-    <string name="summary_glasses_single_device" msgid="7051392780285915640">"Aplikacija je potrebna za upravljanje uređajem <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. Aplikacija <xliff:g id="APP_NAME">%2$s</xliff:g> moći će stupati u interakciju s ovim dopuštenjima:"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"Odaberite uređaj kojim će upravljati aplikacija &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Odaberite <xliff:g id="PROFILE_NAME">%1$s</xliff:g> da postavite"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Aplikaciji će biti dozvoljeni sinhroniziranje informacija, kao što je ime osobe koja upućuje poziv i pristup ovim odobrenjima na uređaju <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
+    <string name="confirmation_title_glasses" msgid="8288346850537727333">"Dozvoliti aplikaciji &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; da upravlja uređajem &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"uređaj"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Aplikaciji će biti dozvoljen pristup ovim odobrenjima na uređaju <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Dozvolite da aplikacija &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; pristupa ovim informacijama s telefona"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Usluga na više uređaja"</string>
-    <string name="helper_summary_app_streaming" msgid="5977509499890099">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> u ime uređaja <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> zahtijeva odobrenje da prenosi aplikacije između vaših uređaja"</string>
+    <string name="helper_summary_app_streaming" msgid="2396773196949578425">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> u ime uređaja <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> zahtijeva odobrenje da prenosi aplikacije između vaših uređaja"</string>
     <string name="title_automotive_projection" msgid="3296005598978412847"></string>
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"Dozvolite aplikaciji &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; da pristupa ovim informacijama s vašeg telefona"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Google Play usluge"</string>
-    <string name="helper_summary_computer" msgid="9050724687678157852">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> u ime uređaja <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> zahtijeva odobrenje da pristupi fotografijama, medijima i odobrenjima na vašem telefonu"</string>
-    <string name="title_nearby_device_streaming" msgid="179278282547719200">"Dozvolite aplikaciji &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; da obavi ovu radnju s telefona"</string>
-    <string name="helper_title_nearby_device_streaming" msgid="6124438217620593669">"Usluga na više uređaja"</string>
-    <string name="helper_summary_nearby_device_streaming" msgid="5538329403511524333">"<xliff:g id="APP_NAME">%1$s</xliff:g> zahtijeva odobrenje u ime uređaja <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> da prenosi sadržaj na uređajima u blizini"</string>
+    <string name="helper_summary_computer" msgid="8774832742608187072">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> u ime uređaja <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> zahtijeva odobrenje da pristupi fotografijama, medijima i obavještenjima na telefonu"</string>
+    <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Dozvoliti uređaju &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; da poduzme ovu radnju?"</string>
+    <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> u ime uređaja <xliff:g id="DEVICE_NAME">%2$s</xliff:g> traži odobrenje da prenosi aplikacije i druge funkcije sistema na uređajima u blizini"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"uređaj"</string>
-    <string name="summary_generic_single_device" msgid="4735072202474939111">"Ta će aplikacija moći sinkronizirati podatke između vašeg telefona i uređaja <xliff:g id="DEVICE_NAME">%1$s</xliff:g>, primjerice ime pozivatelja."</string>
-    <string name="summary_generic" msgid="4988130802522924650">"Ta će aplikacija moći sinkronizirati podatke između vašeg telefona i odabranog uređaja, primjerice ime pozivatelja."</string>
+    <string name="summary_generic" msgid="1761976003668044801">"Ova aplikacija će moći sinhronizirati informacije, kao što je ime osobe koja upućuje poziv, između vašeg telefona i odabranog uređaja"</string>
     <string name="consent_yes" msgid="8344487259618762872">"Dozvoli"</string>
     <string name="consent_no" msgid="2640796915611404382">"Nemoj dozvoliti"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Otkaži"</string>
     <string name="consent_back" msgid="2560683030046918882">"Nazad"</string>
+    <string name="permission_expand" msgid="893185038020887411">"Proširivanje stavke <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
+    <string name="permission_collapse" msgid="3320833884220844084">"Sužavanje stavke <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Dati aplikacijama na uređaju &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; ista odobrenja kao na uređaju &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="permission_sync_summary" msgid="765497944331294275">"Ovo može uključivati odobrenja za pristup &lt;strong&gt;mikrofonu&lt;/strong&gt;, &lt;strong&gt;kameri&lt;/strong&gt; i &lt;strong&gt;lokaciji&lt;/strong&gt; te druga osjetljiva odobrenja na uređaju &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;. &lt;br/&gt;&lt;br/&gt;Ova odobrenja možete bilo kada promijeniti u Postavkama na uređaju &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;."</string>
-    <string name="vendor_icon_description" msgid="4445875290032225965">"Ikona aplikacije"</string>
-    <string name="vendor_header_button_description" msgid="6566660389500630608">"Dugme Više informacija"</string>
+    <string name="vendor_header_button_description" msgid="7994879208461111473">"Više informacija"</string>
     <string name="permission_phone" msgid="2661081078692784919">"Telefon"</string>
     <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
     <string name="permission_contacts" msgid="3858319347208004438">"Kontakti"</string>
@@ -57,16 +57,18 @@
     <string name="permission_storage" msgid="6831099350839392343">"Fotografije i mediji"</string>
     <string name="permission_notification" msgid="693762568127741203">"Obavještenja"</string>
     <string name="permission_app_streaming" msgid="6009695219091526422">"Aplikacije"</string>
-    <string name="permission_nearby_device_streaming" msgid="5868108148065023161">"Prijenos na uređajima u blizini"</string>
+    <string name="permission_nearby_device_streaming" msgid="1023325519477349499">"Prijenos"</string>
     <string name="permission_phone_summary" msgid="6684396967861278044">"Može uspostavljati telefonske pozive i upravljati njima"</string>
     <string name="permission_call_logs_summary" msgid="6186103394658755022">"Može čitati i zapisivati zapisnik telefonskih poziva"</string>
     <string name="permission_sms_summary" msgid="3508442683678912017">"Može slati i prikazivati SMS poruke"</string>
     <string name="permission_contacts_summary" msgid="675861979475628708">"Može pristupiti kontaktima"</string>
     <string name="permission_calendar_summary" msgid="6460000922511766226">"Može pristupiti kalendaru"</string>
-    <string name="permission_microphone_summary" msgid="4241354865859396558">"Može snimati zvuk pomoću mikrofona"</string>
+    <string name="permission_microphone_summary" msgid="3692091540613093394">"Može snimati zvuk"</string>
     <string name="permission_nearby_devices_summary" msgid="931940524460876655">"Može pronaći uređaje u blizini, povezati se s njima i odrediti im relativan položaj"</string>
     <string name="permission_notification_summary" msgid="884075314530071011">"Može čitati sva obavještenja, uključujući informacije kao što su kontakti, poruke i fotografije"</string>
     <string name="permission_app_streaming_summary" msgid="606923325679670624">"Prenosite aplikacije s telefona"</string>
     <string name="permission_storage_summary" msgid="3918240895519506417"></string>
-    <string name="permission_nearby_device_streaming_summary" msgid="5776807830582725074">"Prijenos sadržaja na uređaju u blizini"</string>
+    <string name="permission_nearby_device_streaming_summary" msgid="8280824871197081246">"Prijenos aplikacija i drugih funkcija sistema s vašeg telefona"</string>
+    <string name="device_type" product="default" msgid="8268703872070046263">"telefon"</string>
+    <string name="device_type" product="tablet" msgid="5038791954983067774">"tablet"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-ca/strings.xml b/packages/CompanionDeviceManager/res/values-ca/strings.xml
index c407a1c..b6f182d 100644
--- a/packages/CompanionDeviceManager/res/values-ca/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ca/strings.xml
@@ -17,43 +17,36 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Gestor de dispositius complementaris"</string>
-    <!-- no translation found for confirmation_title (8024993972587946678) -->
-    <skip />
+    <string name="confirmation_title" msgid="2244241995958340998">"Vols permetre que l\'aplicació &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; accedeixi a &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"rellotge"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Tria un <xliff:g id="PROFILE_NAME">%1$s</xliff:g> perquè el gestioni &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <!-- no translation found for summary_watch (6566922405914995759) -->
-    <skip />
-    <!-- no translation found for summary_watch_single_device (7443464525873186735) -->
-    <skip />
-    <string name="profile_name_glasses" msgid="8488394059007275998">"ulleres"</string>
-    <!-- no translation found for summary_glasses (3808267780579061241) -->
-    <skip />
-    <!-- no translation found for summary_glasses_single_device (7051392780285915640) -->
-    <skip />
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"Tria un dispositiu perquè el gestioni &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Tria un <xliff:g id="PROFILE_NAME">%1$s</xliff:g> per configurar"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Aquesta aplicació podrà sincronitzar informació, com ara el nom d\'algú que truca, i accedir a aquests permisos al dispositiu (<xliff:g id="DEVICE_NAME">%1$s</xliff:g>)"</string>
+    <string name="confirmation_title_glasses" msgid="8288346850537727333">"Permet que &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; gestioni &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"dispositiu"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Aquesta aplicació podrà accedir a aquests permisos del dispositiu (<xliff:g id="DEVICE_NAME">%1$s</xliff:g>)"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Permet que &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; accedeixi a aquesta informació del telèfon"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Serveis multidispositiu"</string>
-    <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> demana permís en nom del teu <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> per reproduir en continu aplicacions entre els dispositius"</string>
+    <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g> demana permís en nom del teu dispositiu (<xliff:g id="DISPLAY_NAME">%2$s</xliff:g>) per reproduir en continu aplicacions entre els dispositius"</string>
     <string name="title_automotive_projection" msgid="3296005598978412847"></string>
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"Permet que &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; accedeixi a aquesta informació del telèfon"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Serveis de Google Play"</string>
-    <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> demana permís en nom del teu <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> per accedir a les fotos, el contingut multimèdia i les notificacions del telèfon"</string>
-    <string name="title_nearby_device_streaming" msgid="179278282547719200">"Permet que &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; dugui a terme aquesta acció des del telèfon"</string>
-    <string name="helper_title_nearby_device_streaming" msgid="6124438217620593669">"Serveis multidispositiu"</string>
-    <string name="helper_summary_nearby_device_streaming" msgid="5538329403511524333">"<xliff:g id="APP_NAME">%1$s</xliff:g> demana permís en nom del teu dispositiu (<xliff:g id="DEVICE_TYPE">%2$s</xliff:g>) per reproduir contingut en continu en dispositius propers"</string>
+    <string name="helper_summary_computer" msgid="8774832742608187072">"<xliff:g id="APP_NAME">%1$s</xliff:g> demana permís en nom del teu dispositiu (<xliff:g id="DISPLAY_NAME">%2$s</xliff:g>) per accedir a les fotos, el contingut multimèdia i les notificacions del telèfon"</string>
+    <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Vols permetre que &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; dugui a terme aquesta acció?"</string>
+    <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> sol·licita permís en nom del teu dispositiu (<xliff:g id="DEVICE_NAME">%2$s</xliff:g>) per reproduir en continu aplicacions i altres funcions del sistema en dispositius propers"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"dispositiu"</string>
-    <!-- no translation found for summary_generic_single_device (4735072202474939111) -->
-    <skip />
-    <!-- no translation found for summary_generic (4988130802522924650) -->
-    <skip />
+    <string name="summary_generic" msgid="1761976003668044801">"Aquesta aplicació podrà sincronitzar informació, com ara el nom d\'algú que truca, entre el teu telèfon i el dispositiu triat"</string>
     <string name="consent_yes" msgid="8344487259618762872">"Permet"</string>
     <string name="consent_no" msgid="2640796915611404382">"No permetis"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Cancel·la"</string>
     <string name="consent_back" msgid="2560683030046918882">"Enrere"</string>
+    <string name="permission_expand" msgid="893185038020887411">"Desplega <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
+    <string name="permission_collapse" msgid="3320833884220844084">"Replega <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Vols concedir a les aplicacions del dispositiu &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; els mateixos permisos que tenen a &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="permission_sync_summary" msgid="765497944331294275">"Això pot incloure l\'accés al &lt;strong&gt;micròfon&lt;/strong&gt;, a la &lt;strong&gt;càmera&lt;/strong&gt; i a la &lt;strong&gt;ubicació&lt;/strong&gt;, així com altres permisos sensibles a &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;. &lt;br/&gt;&lt;br/&gt;Pots canviar aquestes permisos en qualsevol moment a Configuració, a &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;."</string>
-    <string name="vendor_icon_description" msgid="4445875290032225965">"Icona de l\'aplicació"</string>
-    <string name="vendor_header_button_description" msgid="6566660389500630608">"Botó Més informació"</string>
+    <string name="vendor_header_button_description" msgid="7994879208461111473">"Més informació"</string>
     <string name="permission_phone" msgid="2661081078692784919">"Telèfon"</string>
     <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
     <string name="permission_contacts" msgid="3858319347208004438">"Contactes"</string>
@@ -64,16 +57,18 @@
     <string name="permission_storage" msgid="6831099350839392343">"Fotos i contingut multimèdia"</string>
     <string name="permission_notification" msgid="693762568127741203">"Notificacions"</string>
     <string name="permission_app_streaming" msgid="6009695219091526422">"Aplicacions"</string>
-    <string name="permission_nearby_device_streaming" msgid="5868108148065023161">"Reproducció en disp. propers"</string>
+    <string name="permission_nearby_device_streaming" msgid="1023325519477349499">"Reproducció en continu"</string>
     <string name="permission_phone_summary" msgid="6684396967861278044">"Pot fer i gestionar trucades"</string>
     <string name="permission_call_logs_summary" msgid="6186103394658755022">"Pot llegir i escriure el registre de trucades del telèfon"</string>
     <string name="permission_sms_summary" msgid="3508442683678912017">"Pot enviar i consultar missatges SMS"</string>
     <string name="permission_contacts_summary" msgid="675861979475628708">"Pot accedir als contactes"</string>
     <string name="permission_calendar_summary" msgid="6460000922511766226">"Pot accedir al calendari"</string>
-    <string name="permission_microphone_summary" msgid="4241354865859396558">"Pots gravar àudios amb el micròfon"</string>
+    <string name="permission_microphone_summary" msgid="3692091540613093394">"Pot gravar àudio"</string>
     <string name="permission_nearby_devices_summary" msgid="931940524460876655">"Pot determinar la posició relativa dels dispositius propers, cercar-los i connectar-s\'hi"</string>
     <string name="permission_notification_summary" msgid="884075314530071011">"Pot llegir totes les notificacions, inclosa informació com ara els contactes, els missatges i les fotos"</string>
     <string name="permission_app_streaming_summary" msgid="606923325679670624">"Reprodueix en continu aplicacions del telèfon"</string>
     <string name="permission_storage_summary" msgid="3918240895519506417"></string>
-    <string name="permission_nearby_device_streaming_summary" msgid="5776807830582725074">"Reproduir contingut en continu en dispositius propers"</string>
+    <string name="permission_nearby_device_streaming_summary" msgid="8280824871197081246">"Reprodueix en continu aplicacions i altres funcions del sistema des del telèfon"</string>
+    <string name="device_type" product="default" msgid="8268703872070046263">"telèfon"</string>
+    <string name="device_type" product="tablet" msgid="5038791954983067774">"tauleta"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-cs/strings.xml b/packages/CompanionDeviceManager/res/values-cs/strings.xml
index 7ef6846..b62496d 100644
--- a/packages/CompanionDeviceManager/res/values-cs/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-cs/strings.xml
@@ -17,36 +17,36 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Správce doprovodných zařízení"</string>
-    <string name="confirmation_title" msgid="8024993972587946678">"Povolit aplikaci &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; přístup k zařízení &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"Povolit aplikaci &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; přístup k &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"hodinky"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Vyberte zařízení <xliff:g id="PROFILE_NAME">%1$s</xliff:g>, které chcete spravovat pomocí aplikace &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="6566922405914995759">"Aplikace je nutná ke správě zařízení <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> bude moci synchronizovat údaje, jako je jméno volajícího, interagovat s vašimi oznámeními a získat přístup k vašim oprávněním k telefonu, SMS, kontaktům, kalendáři, seznamům hovorů a zařízením v okolí."</string>
-    <string name="summary_watch_single_device" msgid="7443464525873186735">"Aplikace je nutná ke správě zařízení <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> bude moci synchronizovat údaje, jako je jméno volajícího, a získat přístup k těmto oprávněním:"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"brýle"</string>
-    <string name="summary_glasses" msgid="3808267780579061241">"Tato aplikace je nutná ke správě zařízení <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> bude moci interagovat s vašimi oznámeními a získat přístup k vašim oprávněním k telefonu, SMS, kontaktům, mikrofonu a zařízením v okolí."</string>
-    <string name="summary_glasses_single_device" msgid="7051392780285915640">"Aplikace je nutná ke správě zařízení <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. Aplikace <xliff:g id="APP_NAME">%2$s</xliff:g> bude moci interagovat s těmito oprávněními:"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"Vyberte zařízení, které chcete spravovat pomocí aplikace &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Vyberte zařízení <xliff:g id="PROFILE_NAME">%1$s</xliff:g>, které chcete nastavit"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Tato aplikace bude moci synchronizovat údaje, jako je jméno volajícího, a získat na zařízení <xliff:g id="DEVICE_NAME">%1$s</xliff:g> přístup k těmto oprávněním"</string>
+    <string name="confirmation_title_glasses" msgid="8288346850537727333">"Povolit aplikaci &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; spravovat zařízení &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"zařízení"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Tato aplikace bude mít ve vašem <xliff:g id="DEVICE_NAME">%1$s</xliff:g> povolený přístup k těmto oprávněním"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Povolte aplikaci &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; přístup k těmto informacím z vašeho telefonu"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Služby pro více zařízení"</string>
-    <string name="helper_summary_app_streaming" msgid="5977509499890099">"Aplikace <xliff:g id="APP_NAME">%1$s</xliff:g> požaduje za vaše zařízení <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> oprávnění ke streamování aplikací mezi zařízeními"</string>
+    <string name="helper_summary_app_streaming" msgid="2396773196949578425">"Aplikace <xliff:g id="APP_NAME">%1$s</xliff:g> požaduje za vaše zařízení <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> oprávnění ke streamování aplikací mezi zařízeními"</string>
     <string name="title_automotive_projection" msgid="3296005598978412847"></string>
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"Povolte aplikaci &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; přístup k těmto informacím z vašeho telefonu"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Služby Google Play"</string>
-    <string name="helper_summary_computer" msgid="9050724687678157852">"Aplikace <xliff:g id="APP_NAME">%1$s</xliff:g> požaduje za vaše zařízení <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> oprávnění k přístupu k fotkám, médiím a oznámením v telefonu"</string>
-    <string name="title_nearby_device_streaming" msgid="179278282547719200">"Povolte aplikaci &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; provádět tuto akci z vašeho telefonu"</string>
-    <string name="helper_title_nearby_device_streaming" msgid="6124438217620593669">"Služby pro více zařízení"</string>
-    <string name="helper_summary_nearby_device_streaming" msgid="5538329403511524333">"Aplikace <xliff:g id="APP_NAME">%1$s</xliff:g> požaduje za vaše zařízení <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> oprávnění ke streamování obsahu do zařízení v okolí"</string>
+    <string name="helper_summary_computer" msgid="8774832742608187072">"Aplikace <xliff:g id="APP_NAME">%1$s</xliff:g> požaduje za vaše zařízení <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> oprávnění k přístupu k fotkám, médiím a oznámením v telefonu"</string>
+    <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Povolit zařízení &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; podniknout tuto akci?"</string>
+    <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"Aplikace <xliff:g id="APP_NAME">%1$s</xliff:g> žádá jménem vašeho zařízení <xliff:g id="DEVICE_NAME">%2$s</xliff:g> o oprávnění streamovat aplikace a další systémové funkce do zařízení v okolí"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"zařízení"</string>
-    <string name="summary_generic_single_device" msgid="4735072202474939111">"Tato aplikace bude moci synchronizovat údaje, jako je jméno volajícího, mezi vaším telefonem a zařízením <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string>
-    <string name="summary_generic" msgid="4988130802522924650">"Tato aplikace bude moci synchronizovat údaje, jako je jméno volajícího, mezi vaším telefonem a vybraným zařízením."</string>
+    <string name="summary_generic" msgid="1761976003668044801">"Tato aplikace bude moci synchronizovat údaje, jako je jméno volajícího, mezi vaším telefonem a vybraným zařízením"</string>
     <string name="consent_yes" msgid="8344487259618762872">"Povolit"</string>
     <string name="consent_no" msgid="2640796915611404382">"Nepovolovat"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Zrušit"</string>
     <string name="consent_back" msgid="2560683030046918882">"Zpět"</string>
+    <string name="permission_expand" msgid="893185038020887411">"Rozbalit sekci <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
+    <string name="permission_collapse" msgid="3320833884220844084">"Sbalit sekci <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Udělit aplikacím v zařízení &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; stejné oprávnění, jako mají v zařízení &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="permission_sync_summary" msgid="765497944331294275">"To může zahrnovat oprávnění &lt;strong&gt;Mikrofon&lt;/strong&gt;, &lt;strong&gt;Fotoparát&lt;/strong&gt; a &lt;strong&gt;Přístup k poloze&lt;/strong&gt; a další citlivá oprávnění na zařízení &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;. &lt;br/&gt;&lt;br/&gt;Tato oprávnění můžete kdykoli změnit v Nastavení na zařízení &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;."</string>
-    <string name="vendor_icon_description" msgid="4445875290032225965">"Ikona aplikace"</string>
-    <string name="vendor_header_button_description" msgid="6566660389500630608">"Tlačítko Další informace"</string>
+    <string name="vendor_header_button_description" msgid="7994879208461111473">"Další informace"</string>
     <string name="permission_phone" msgid="2661081078692784919">"Telefon"</string>
     <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
     <string name="permission_contacts" msgid="3858319347208004438">"Kontakty"</string>
@@ -57,16 +57,18 @@
     <string name="permission_storage" msgid="6831099350839392343">"Fotky a média"</string>
     <string name="permission_notification" msgid="693762568127741203">"Oznámení"</string>
     <string name="permission_app_streaming" msgid="6009695219091526422">"Aplikace"</string>
-    <string name="permission_nearby_device_streaming" msgid="5868108148065023161">"Streamování do zařízení v okolí"</string>
+    <string name="permission_nearby_device_streaming" msgid="1023325519477349499">"Streamování"</string>
     <string name="permission_phone_summary" msgid="6684396967861278044">"Může uskutečňovat a spravovat telefonní hovory"</string>
     <string name="permission_call_logs_summary" msgid="6186103394658755022">"Může číst seznam telefonních hovorů a zapisovat do něj"</string>
     <string name="permission_sms_summary" msgid="3508442683678912017">"Může odesílat a číst zprávy SMS"</string>
     <string name="permission_contacts_summary" msgid="675861979475628708">"Má přístup k vašim kontaktům"</string>
     <string name="permission_calendar_summary" msgid="6460000922511766226">"Má přístup k vašemu kalendáři"</string>
-    <string name="permission_microphone_summary" msgid="4241354865859396558">"Může nahrávat zvuk pomocí mikrofonu"</string>
+    <string name="permission_microphone_summary" msgid="3692091540613093394">"Může nahrávat zvuk"</string>
     <string name="permission_nearby_devices_summary" msgid="931940524460876655">"Může nacházet zařízení v okolí, připojovat se k nim a zjišťovat jejich relativní polohu"</string>
     <string name="permission_notification_summary" msgid="884075314530071011">"Může číst veškerá oznámení včetně informací, jako jsou kontakty, zprávy a fotky"</string>
     <string name="permission_app_streaming_summary" msgid="606923325679670624">"Streamujte aplikace v telefonu"</string>
     <string name="permission_storage_summary" msgid="3918240895519506417"></string>
-    <string name="permission_nearby_device_streaming_summary" msgid="5776807830582725074">"Streamování obsahu do zařízení v okolí"</string>
+    <string name="permission_nearby_device_streaming_summary" msgid="8280824871197081246">"Streamování aplikací a dalších systémových funkcí z telefonu"</string>
+    <string name="device_type" product="default" msgid="8268703872070046263">"telefonu"</string>
+    <string name="device_type" product="tablet" msgid="5038791954983067774">"tabletu"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-da/strings.xml b/packages/CompanionDeviceManager/res/values-da/strings.xml
index f1bf633..df63e6c 100644
--- a/packages/CompanionDeviceManager/res/values-da/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-da/strings.xml
@@ -17,43 +17,36 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Medfølgende enhedsadministrator"</string>
-    <!-- no translation found for confirmation_title (8024993972587946678) -->
-    <skip />
+    <string name="confirmation_title" msgid="2244241995958340998">"Vil du give appen &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; adgang til &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"ur"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Vælg det <xliff:g id="PROFILE_NAME">%1$s</xliff:g>, som skal administreres af &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <!-- no translation found for summary_watch (6566922405914995759) -->
-    <skip />
-    <!-- no translation found for summary_watch_single_device (7443464525873186735) -->
-    <skip />
-    <string name="profile_name_glasses" msgid="8488394059007275998">"briller"</string>
-    <!-- no translation found for summary_glasses (3808267780579061241) -->
-    <skip />
-    <!-- no translation found for summary_glasses_single_device (7051392780285915640) -->
-    <skip />
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"Vælg en enhed, som skal administreres af &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Vælg en <xliff:g id="PROFILE_NAME">%1$s</xliff:g>, som du vil konfigurere"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Denne app får tilladelse til at synkronisere oplysninger, f.eks. navne på dem, der ringer, og adgang til disse tilladelser på din <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
+    <string name="confirmation_title_glasses" msgid="8288346850537727333">"Vil du tillade, at &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; administrerer &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"enhed"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Denne app får adgang til disse tilladelser på din <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Giv &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; adgang til disse oplysninger fra din telefon"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Tjenester, som kan tilsluttes en anden enhed"</string>
-    <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> anmoder om tilladelse på vegne af din <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> til at streame apps mellem dine enheder"</string>
+    <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g> anmoder om tilladelse på vegne af din <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> til at streame apps mellem dine enheder"</string>
     <string name="title_automotive_projection" msgid="3296005598978412847"></string>
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"Tillad, at &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; får adgang til disse oplysninger fra din telefon"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Google Play-tjenester"</string>
-    <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> anmoder om tilladelse på vegne af din <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> til at få adgang til din telefons billeder, medier og notifikationer"</string>
-    <string name="title_nearby_device_streaming" msgid="179278282547719200">"Giv &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; tilladelse til at udføre denne handling på din telefon"</string>
-    <string name="helper_title_nearby_device_streaming" msgid="6124438217620593669">"Tjenester til flere enheder"</string>
-    <string name="helper_summary_nearby_device_streaming" msgid="5538329403511524333">"<xliff:g id="APP_NAME">%1$s</xliff:g> anmoder om tilladelse på vegne af din <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> til at streame indhold til enheder i nærheden"</string>
+    <string name="helper_summary_computer" msgid="8774832742608187072">"<xliff:g id="APP_NAME">%1$s</xliff:g> anmoder om tilladelse på vegne af din <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> til at få adgang til din telefons billeder, medier og notifikationer"</string>
+    <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Vil du tillade, at &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; foretager denne handling?"</string>
+    <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> anmoder om tilladelse på vegne af din <xliff:g id="DEVICE_NAME">%2$s</xliff:g> til at streame apps og andre systemfunktioner til enheder i nærheden"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"enhed"</string>
-    <!-- no translation found for summary_generic_single_device (4735072202474939111) -->
-    <skip />
-    <!-- no translation found for summary_generic (4988130802522924650) -->
-    <skip />
+    <string name="summary_generic" msgid="1761976003668044801">"Denne app vil kunne synkronisere oplysninger som f.eks. navnet på en person, der ringer, mellem din telefon og den valgte enhed"</string>
     <string name="consent_yes" msgid="8344487259618762872">"Tillad"</string>
     <string name="consent_no" msgid="2640796915611404382">"Tillad ikke"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Annuller"</string>
     <string name="consent_back" msgid="2560683030046918882">"Tilbage"</string>
+    <string name="permission_expand" msgid="893185038020887411">"Udvid <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
+    <string name="permission_collapse" msgid="3320833884220844084">"Skjul <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Vil du give apps på &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; de samme tilladelser som på &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="permission_sync_summary" msgid="765497944331294275">"Dette kan omfatte &lt;strong&gt;mikrofon-&lt;/strong&gt;, &lt;strong&gt;kamera-&lt;/strong&gt; og &lt;strong&gt;lokationsadgang&lt;/strong&gt; samt andre tilladelser til at tilgå følsomme oplysninger på &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;. &lt;br/&gt;&lt;br/&gt;Du kan til enhver tid ændre disse tilladelser under Indstillinger på &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;."</string>
-    <string name="vendor_icon_description" msgid="4445875290032225965">"Appikon"</string>
-    <string name="vendor_header_button_description" msgid="6566660389500630608">"Knappen Flere oplysninger"</string>
+    <string name="vendor_header_button_description" msgid="7994879208461111473">"Flere oplysninger"</string>
     <string name="permission_phone" msgid="2661081078692784919">"Telefon"</string>
     <string name="permission_sms" msgid="6337141296535774786">"Sms"</string>
     <string name="permission_contacts" msgid="3858319347208004438">"Kontakter"</string>
@@ -64,16 +57,18 @@
     <string name="permission_storage" msgid="6831099350839392343">"Billeder og medier"</string>
     <string name="permission_notification" msgid="693762568127741203">"Notifikationer"</string>
     <string name="permission_app_streaming" msgid="6009695219091526422">"Apps"</string>
-    <string name="permission_nearby_device_streaming" msgid="5868108148065023161">"Streaming til enhed i nærheden"</string>
+    <string name="permission_nearby_device_streaming" msgid="1023325519477349499">"Streaming"</string>
     <string name="permission_phone_summary" msgid="6684396967861278044">"Kan foretage og administrere telefonopkald"</string>
     <string name="permission_call_logs_summary" msgid="6186103394658755022">"Kan læse og redigere opkaldshistorik"</string>
     <string name="permission_sms_summary" msgid="3508442683678912017">"Kan sende og se sms-beskeder"</string>
     <string name="permission_contacts_summary" msgid="675861979475628708">"Kan tilgå dine kontakter"</string>
     <string name="permission_calendar_summary" msgid="6460000922511766226">"Kan tilgå din kalender"</string>
-    <string name="permission_microphone_summary" msgid="4241354865859396558">"Kan optage lyd via mikrofonen"</string>
+    <string name="permission_microphone_summary" msgid="3692091540613093394">"Kan optage lyd"</string>
     <string name="permission_nearby_devices_summary" msgid="931940524460876655">"Kan finde, oprette forbindelse til og fastslå den omtrentlige lokation af enheder i nærheden"</string>
     <string name="permission_notification_summary" msgid="884075314530071011">"Kan læse alle notifikationer, herunder oplysninger som f.eks. kontakter, beskeder og billeder"</string>
     <string name="permission_app_streaming_summary" msgid="606923325679670624">"Stream din telefons apps"</string>
     <string name="permission_storage_summary" msgid="3918240895519506417"></string>
-    <string name="permission_nearby_device_streaming_summary" msgid="5776807830582725074">"Stream indhold til en enhed i nærheden"</string>
+    <string name="permission_nearby_device_streaming_summary" msgid="8280824871197081246">"Stream apps og andre systemfunktioner fra din telefon"</string>
+    <string name="device_type" product="default" msgid="8268703872070046263">"telefon"</string>
+    <string name="device_type" product="tablet" msgid="5038791954983067774">"tablet"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-de/strings.xml b/packages/CompanionDeviceManager/res/values-de/strings.xml
index b914f2f..f487f94 100644
--- a/packages/CompanionDeviceManager/res/values-de/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-de/strings.xml
@@ -17,43 +17,36 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Begleitgerät-Manager"</string>
-    <!-- no translation found for confirmation_title (8024993972587946678) -->
-    <skip />
+    <string name="confirmation_title" msgid="2244241995958340998">"Zulassen, dass &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; auf &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; zugreifen darf?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"Smartwatch"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Gerät „<xliff:g id="PROFILE_NAME">%1$s</xliff:g>“ auswählen, das von &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; verwaltet werden soll"</string>
-    <!-- no translation found for summary_watch (6566922405914995759) -->
-    <skip />
-    <!-- no translation found for summary_watch_single_device (7443464525873186735) -->
-    <skip />
-    <string name="profile_name_glasses" msgid="8488394059007275998">"Glass-Geräte"</string>
-    <!-- no translation found for summary_glasses (3808267780579061241) -->
-    <skip />
-    <!-- no translation found for summary_glasses_single_device (7051392780285915640) -->
-    <skip />
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"Gerät auswählen, das von &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; verwaltet werden soll"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"<xliff:g id="PROFILE_NAME">%1$s</xliff:g> zum Einrichten auswählen"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Diese App darf dann Daten wie den Namen eines Anrufers synchronisieren und auf diese Berechtigungen auf deinem <xliff:g id="DEVICE_NAME">%1$s</xliff:g> zugreifen"</string>
+    <string name="confirmation_title_glasses" msgid="8288346850537727333">"Zulassen, dass &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; das Gerät &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; verwalten darf"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"Gerät"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Diese App darf dann auf diese Berechtigungen auf deinem <xliff:g id="DEVICE_NAME">%1$s</xliff:g> zugreifen:"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; Zugriff auf diese Informationen von deinem Smartphone gewähren"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Geräteübergreifende Dienste"</string>
-    <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> bittet für dein <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> um die Berechtigung zum Streamen von Apps zwischen deinen Geräten"</string>
+    <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g> bittet für dein <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> um die Berechtigung zum Streamen von Apps zwischen deinen Geräten"</string>
     <string name="title_automotive_projection" msgid="3296005598978412847"></string>
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; Zugriff auf diese Informationen von deinem Smartphone gewähren"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Google Play-Dienste"</string>
-    <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> bittet im Namen deines <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> um die Berechtigung zum Zugriff auf die Fotos, Medien und Benachrichtigungen deines Smartphones"</string>
-    <string name="title_nearby_device_streaming" msgid="179278282547719200">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; darf diese Aktion von deinem Smartphone ausführen"</string>
-    <string name="helper_title_nearby_device_streaming" msgid="6124438217620593669">"Geräteübergreifende Dienste"</string>
-    <string name="helper_summary_nearby_device_streaming" msgid="5538329403511524333">"<xliff:g id="APP_NAME">%1$s</xliff:g> bittet für dein Gerät (<xliff:g id="DEVICE_TYPE">%2$s</xliff:g>) um die Berechtigung zum Streamen von Inhalten auf Geräte in der Nähe"</string>
+    <string name="helper_summary_computer" msgid="8774832742608187072">"<xliff:g id="APP_NAME">%1$s</xliff:g> bittet im Namen deines <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> um die Berechtigung zum Zugriff auf die Fotos, Medien und Benachrichtigungen deines Smartphones"</string>
+    <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Darf das Gerät &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; diese Aktion ausführen?"</string>
+    <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> bittet für dein Gerät (<xliff:g id="DEVICE_NAME">%2$s</xliff:g>) um die Berechtigung, Apps und andere Systemfunktionen auf Geräte in der Nähe zu streamen"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"Gerät"</string>
-    <!-- no translation found for summary_generic_single_device (4735072202474939111) -->
-    <skip />
-    <!-- no translation found for summary_generic (4988130802522924650) -->
-    <skip />
+    <string name="summary_generic" msgid="1761976003668044801">"Diese App kann dann Daten wie den Namen eines Anrufers zwischen deinem Smartphone und dem ausgewählten Gerät synchronisieren"</string>
     <string name="consent_yes" msgid="8344487259618762872">"Zulassen"</string>
     <string name="consent_no" msgid="2640796915611404382">"Nicht zulassen"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Abbrechen"</string>
     <string name="consent_back" msgid="2560683030046918882">"Zurück"</string>
+    <string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> maximieren"</string>
+    <string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> minimieren"</string>
     <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Apps auf &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; die gleichen Berechtigungen geben wie auf &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="permission_sync_summary" msgid="765497944331294275">"Dazu können &lt;strong&gt;Mikrofon&lt;/strong&gt;, &lt;strong&gt;Kamera&lt;/strong&gt; und &lt;strong&gt;Standortzugriff&lt;/strong&gt; sowie weitere vertrauliche Berechtigungen auf &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt; gehören. &lt;br/&gt;&lt;br/&gt;Du kannst diese Berechtigungen jederzeit in den Einstellungen von &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt; ändern."</string>
-    <string name="vendor_icon_description" msgid="4445875290032225965">"App-Symbol"</string>
-    <string name="vendor_header_button_description" msgid="6566660389500630608">"Weitere-Infos-Schaltfläche"</string>
+    <string name="vendor_header_button_description" msgid="7994879208461111473">"Weitere Informationen"</string>
     <string name="permission_phone" msgid="2661081078692784919">"Telefon"</string>
     <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
     <string name="permission_contacts" msgid="3858319347208004438">"Kontakte"</string>
@@ -64,16 +57,18 @@
     <string name="permission_storage" msgid="6831099350839392343">"Fotos und Medien"</string>
     <string name="permission_notification" msgid="693762568127741203">"Benachrichtigungen"</string>
     <string name="permission_app_streaming" msgid="6009695219091526422">"Apps"</string>
-    <string name="permission_nearby_device_streaming" msgid="5868108148065023161">"Streamen an Geräte in der Nähe"</string>
+    <string name="permission_nearby_device_streaming" msgid="1023325519477349499">"Streaming"</string>
     <string name="permission_phone_summary" msgid="6684396967861278044">"Kann Anrufe tätigen und verwalten"</string>
     <string name="permission_call_logs_summary" msgid="6186103394658755022">"Kann auf die Anrufliste zugreifen und sie bearbeiten"</string>
     <string name="permission_sms_summary" msgid="3508442683678912017">"Kann SMS senden und abrufen"</string>
     <string name="permission_contacts_summary" msgid="675861979475628708">"Kann auf deine Kontakte zugreifen"</string>
     <string name="permission_calendar_summary" msgid="6460000922511766226">"Kann auf deinen Kalender zugreifen"</string>
-    <string name="permission_microphone_summary" msgid="4241354865859396558">"Mit dem Mikrofon dürfen Audioaufnahmen gemacht werden"</string>
+    <string name="permission_microphone_summary" msgid="3692091540613093394">"Darf Audio aufnehmen"</string>
     <string name="permission_nearby_devices_summary" msgid="931940524460876655">"Kann Geräte in der Nähe finden, eine Verbindung zu ihnen herstellen und ihren relativen Standort ermitteln"</string>
     <string name="permission_notification_summary" msgid="884075314530071011">"Kann alle Benachrichtigungen lesen, einschließlich Informationen wie Kontakten, Nachrichten und Fotos"</string>
     <string name="permission_app_streaming_summary" msgid="606923325679670624">"Smartphone-Apps streamen"</string>
     <string name="permission_storage_summary" msgid="3918240895519506417"></string>
-    <string name="permission_nearby_device_streaming_summary" msgid="5776807830582725074">"Inhalte dürfen auf ein Gerät in der Nähe gestreamt werden"</string>
+    <string name="permission_nearby_device_streaming_summary" msgid="8280824871197081246">"Apps und andere Systemfunktionen von deinem Smartphone streamen"</string>
+    <string name="device_type" product="default" msgid="8268703872070046263">"Smartphone"</string>
+    <string name="device_type" product="tablet" msgid="5038791954983067774">"Tablet"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-el/strings.xml b/packages/CompanionDeviceManager/res/values-el/strings.xml
index f817de0..1c8ecb7 100644
--- a/packages/CompanionDeviceManager/res/values-el/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-el/strings.xml
@@ -17,36 +17,36 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Διαχείριση συνοδευτικής εφαρμογής"</string>
-    <string name="confirmation_title" msgid="8024993972587946678">"Επιτρέψτε στην εφαρμογή &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; να έχει πρόσβαση στη συσκευή &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"Να επιτρέπεται στην εφαρμογή &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; να έχει πρόσβαση στη συσκευή &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;;"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"ρολόι"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Επιλέξτε ένα προφίλ <xliff:g id="PROFILE_NAME">%1$s</xliff:g> για διαχείριση από την εφαρμογή &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="6566922405914995759">"Η εφαρμογή είναι απαραίτητη για τη διαχείριση της συσκευής <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. Η εφαρμογή <xliff:g id="APP_NAME">%2$s</xliff:g> θα μπορεί να συγχρονίζει πληροφορίες, όπως το όνομα ενός ατόμου που σας καλεί, να αλληλεπιδρά με τις ειδοποιήσεις σας και να αποκτά πρόσβαση στις άδειες Τηλέφωνο, SMS, Επαφές, Ημερολόγιο, Αρχεία καταγρ. κλήσ. και Συσκευές σε κοντινή απόσταση."</string>
-    <string name="summary_watch_single_device" msgid="7443464525873186735">"Η εφαρμογή είναι απαραίτητη για τη διαχείριση της συσκευής <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. Η εφαρμογή <xliff:g id="APP_NAME">%2$s</xliff:g> θα μπορεί να συγχρονίζει πληροφορίες, όπως το όνομα ενός ατόμου που σας καλεί, και να αποκτά πρόσβαση σε αυτές τις άδειες:"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"γυαλιά"</string>
-    <string name="summary_glasses" msgid="3808267780579061241">"Αυτή η εφαρμογή είναι απαραίτητη για τη διαχείριση της συσκευής <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. Θα επιτρέπεται στην εφαρμογή <xliff:g id="APP_NAME">%2$s</xliff:g> να αλληλεπιδρά με τις ειδοποιήσεις σας και να αποκτά πρόσβαση στις άδειες για το Τηλέφωνο, τα SMS, τις Επαφές, το Μικρόφωνο και τις Συσκευές σε κοντινή απόσταση."</string>
-    <string name="summary_glasses_single_device" msgid="7051392780285915640">"Η εφαρμογή είναι απαραίτητη για τη διαχείριση της συσκευής <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. Η εφαρμογή <xliff:g id="APP_NAME">%2$s</xliff:g> θα επιτρέπεται να αλληλεπιδρά με τις εξής άδειες:"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"Επιλέξτε μια συσκευή για διαχείριση μέσω της εφαρμογής &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Επιλέξτε ένα προφίλ <xliff:g id="PROFILE_NAME">%1$s</xliff:g> για ρύθμιση"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Αυτή η εφαρμογή θα μπορεί να συγχρονίζει πληροφορίες, όπως το όνομα ενός ατόμου που σας καλεί, και να αποκτά πρόσβαση σε αυτές τις άδειες στη συσκευή <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
+    <string name="confirmation_title_glasses" msgid="8288346850537727333">"Να επιτρέπεται στην εφαρμογή &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; να διαχειρίζεται τη συσκευή &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; ;"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"συσκευή"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Αυτή η εφαρμογή θα μπορεί να έχει πρόσβαση σε αυτές τις άδειες στη συσκευή <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Να επιτρέπεται στο &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; η πρόσβαση σε αυτές τις πληροφορίες από το τηλέφωνό σας."</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Υπηρεσίες πολλών συσκευών"</string>
-    <string name="helper_summary_app_streaming" msgid="5977509499890099">"Η εφαρμογή <xliff:g id="APP_NAME">%1$s</xliff:g> ζητά εκ μέρους της συσκευής σας <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> άδεια για ροή εφαρμογών μεταξύ των συσκευών σας"</string>
+    <string name="helper_summary_app_streaming" msgid="2396773196949578425">"Η εφαρμογή <xliff:g id="APP_NAME">%1$s</xliff:g> ζητά εκ μέρους της συσκευής σας <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> άδεια για ροή εφαρμογών μεταξύ των συσκευών σας"</string>
     <string name="title_automotive_projection" msgid="3296005598978412847"></string>
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"Επιτρέψτε στην εφαρμογή &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; να έχει πρόσβαση σε αυτές τις πληροφορίες από το τηλέφωνό σας"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Υπηρεσίες Google Play"</string>
-    <string name="helper_summary_computer" msgid="9050724687678157852">"Η εφαρμογή <xliff:g id="APP_NAME">%1$s</xliff:g> ζητά εκ μέρους της συσκευής σας <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> άδεια για πρόσβαση στις φωτογραφίες, τα αρχεία μέσων και τις ειδοποιήσεις του τηλεφώνου σας"</string>
-    <string name="title_nearby_device_streaming" msgid="179278282547719200">"Να επιτρέπεται στην εφαρμογή &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; να εκτελεί αυτήν την ενέργεια στο τηλέφωνό σας"</string>
-    <string name="helper_title_nearby_device_streaming" msgid="6124438217620593669">"Υπηρεσίες πολλών συσκευών"</string>
-    <string name="helper_summary_nearby_device_streaming" msgid="5538329403511524333">"Η εφαρμογή <xliff:g id="APP_NAME">%1$s</xliff:g> ζητά άδεια εκ μέρους της συσκευής <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> για ροή περιεχομένου σε συσκευές σε κοντινή απόσταση."</string>
+    <string name="helper_summary_computer" msgid="8774832742608187072">"Η εφαρμογή <xliff:g id="APP_NAME">%1$s</xliff:g> ζητά εκ μέρους της συσκευής σας <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> άδεια για πρόσβαση στις φωτογραφίες, τα αρχεία μέσων και τις ειδοποιήσεις του τηλεφώνου σας"</string>
+    <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Να επιτρέπεται στη συσκευή &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; να εκτελεί αυτήν την ενέργεια;"</string>
+    <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"Η εφαρμογή <xliff:g id="APP_NAME">%1$s</xliff:g> ζητά άδεια εκ μέρους της συσκευής σας <xliff:g id="DEVICE_NAME">%2$s</xliff:g> για ροή εφαρμογών και άλλων λειτουργιών του συστήματος σε συσκευές σε κοντινή απόσταση"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"συσκευή"</string>
-    <string name="summary_generic_single_device" msgid="4735072202474939111">"Αυτή η εφαρμογή θα μπορεί να συγχρονίζει πληροφορίες μεταξύ του τηλεφώνου και της συσκευής <xliff:g id="DEVICE_NAME">%1$s</xliff:g>, όπως το όνομα ενός ατόμου που σας καλεί."</string>
-    <string name="summary_generic" msgid="4988130802522924650">"Αυτή η εφαρμογή θα μπορεί να συγχρονίζει πληροφορίες μεταξύ του τηλεφώνου και της επιλεγμένης συσκευής σας, όπως το όνομα ενός ατόμου που σας καλεί."</string>
+    <string name="summary_generic" msgid="1761976003668044801">"Αυτή η εφαρμογή θα μπορεί να συγχρονίζει πληροφορίες μεταξύ του τηλεφώνου και της επιλεγμένης συσκευής σας, όπως το όνομα ενός ατόμου που σας καλεί."</string>
     <string name="consent_yes" msgid="8344487259618762872">"Να επιτρέπεται"</string>
     <string name="consent_no" msgid="2640796915611404382">"Να μην επιτρέπεται"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Ακύρωση"</string>
     <string name="consent_back" msgid="2560683030046918882">"Πίσω"</string>
+    <string name="permission_expand" msgid="893185038020887411">"Ανάπτυξη <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
+    <string name="permission_collapse" msgid="3320833884220844084">"Σύμπτυξη <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Παραχώρηση των ίδιων αδειών στις εφαρμογές στη συσκευή &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; όπως στη συσκευή &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;;"</string>
     <string name="permission_sync_summary" msgid="765497944331294275">"Μπορεί να περιλαμβάνει την πρόσβαση στο &lt;strong&gt;Μικρόφωνο&lt;/strong&gt;, την &lt;strong&gt;Κάμερα&lt;/strong&gt;, και την &lt;strong&gt;Τοποθεσία&lt;/strong&gt;, καθώς και άλλες άδειες πρόσβασης σε ευαίσθητες πληροφορίες στη συσκευή &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;. &lt;br/&gt;&lt;br/&gt;Μπορείτε να αλλάξετε αυτές τις άδειες ανά πάσα στιγμή από τις Ρυθμίσεις της συσκευής &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;."</string>
-    <string name="vendor_icon_description" msgid="4445875290032225965">"Εικονίδιο εφαρμογής"</string>
-    <string name="vendor_header_button_description" msgid="6566660389500630608">"Κουμπί περισσότερων πληροφορ."</string>
+    <string name="vendor_header_button_description" msgid="7994879208461111473">"Περισσότερες πληροφορίες"</string>
     <string name="permission_phone" msgid="2661081078692784919">"Τηλέφωνο"</string>
     <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
     <string name="permission_contacts" msgid="3858319347208004438">"Επαφές"</string>
@@ -57,16 +57,18 @@
     <string name="permission_storage" msgid="6831099350839392343">"Φωτογραφίες και μέσα"</string>
     <string name="permission_notification" msgid="693762568127741203">"Ειδοποιήσεις"</string>
     <string name="permission_app_streaming" msgid="6009695219091526422">"Εφαρμογές"</string>
-    <string name="permission_nearby_device_streaming" msgid="5868108148065023161">"Ροή σε κοντινή συσκευή"</string>
+    <string name="permission_nearby_device_streaming" msgid="1023325519477349499">"Ροή"</string>
     <string name="permission_phone_summary" msgid="6684396967861278044">"Μπορεί να πραγματοποιήσει και να διαχειριστεί τηλεφωνικές κλήσεις"</string>
     <string name="permission_call_logs_summary" msgid="6186103394658755022">"Έχει άδεια ανάγνωσης και εγγραφής στο αρχείο καταγραφής κλήσεων του τηλεφώνου"</string>
     <string name="permission_sms_summary" msgid="3508442683678912017">"Μπορεί να στείλει και να προβάλλει μηνύματα SMS"</string>
     <string name="permission_contacts_summary" msgid="675861979475628708">"Έχει πρόσβαση στις επαφές σας"</string>
     <string name="permission_calendar_summary" msgid="6460000922511766226">"Έχει πρόσβαση στο ημερολόγιό σας"</string>
-    <string name="permission_microphone_summary" msgid="4241354865859396558">"Μπορεί να εγγράφει ήχο χρησιμοποιώντας το μικρόφωνο"</string>
+    <string name="permission_microphone_summary" msgid="3692091540613093394">"Μπορεί να κάνει εγγραφή ήχου"</string>
     <string name="permission_nearby_devices_summary" msgid="931940524460876655">"Δεν μπορεί να βρει, να συνδεθεί και να προσδιορίσει τη σχετική τοποθεσία των κοντινών συσκευών"</string>
     <string name="permission_notification_summary" msgid="884075314530071011">"Μπορεί να διαβάσει όλες τις ειδοποιήσεις, συμπεριλαμβανομένων πληροφοριών όπως επαφές, μηνύματα και φωτογραφίες"</string>
     <string name="permission_app_streaming_summary" msgid="606923325679670624">"Μεταδώστε σε ροή τις εφαρμογές του τηλεφώνου σας"</string>
     <string name="permission_storage_summary" msgid="3918240895519506417"></string>
-    <string name="permission_nearby_device_streaming_summary" msgid="5776807830582725074">"Ροή περιεχομένου σε συσκευή σε κοντινή απόσταση"</string>
+    <string name="permission_nearby_device_streaming_summary" msgid="8280824871197081246">"Ροή εφαρμογών και άλλων λειτουργιών του συστήματος από το τηλέφωνό σας"</string>
+    <string name="device_type" product="default" msgid="8268703872070046263">"τηλέφωνο"</string>
+    <string name="device_type" product="tablet" msgid="5038791954983067774">"tablet"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-en-rAU/strings.xml b/packages/CompanionDeviceManager/res/values-en-rAU/strings.xml
index 5708fdc..ca12145 100644
--- a/packages/CompanionDeviceManager/res/values-en-rAU/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-en-rAU/strings.xml
@@ -17,36 +17,36 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Companion Device Manager"</string>
-    <string name="confirmation_title" msgid="8024993972587946678">"Allow &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; to access &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"Allow the app &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; to access &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"watch"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Choose a <xliff:g id="PROFILE_NAME">%1$s</xliff:g> to be managed by &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="6566922405914995759">"The app is needed to manage your <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> will be allowed to sync info, like the name of someone calling, interact with your notifications and access your Phone, SMS, Contacts, Calendar, Call logs and Nearby devices permissions."</string>
-    <string name="summary_watch_single_device" msgid="7443464525873186735">"The app is needed to manage your <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> will be allowed to sync info, like the name of someone calling, and access these permissions:"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"glasses"</string>
-    <string name="summary_glasses" msgid="3808267780579061241">"This app is needed to manage <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> will be allowed to interact with your notifications and access your phone, SMS, contacts, microphone and Nearby devices permissions."</string>
-    <string name="summary_glasses_single_device" msgid="7051392780285915640">"The app is needed to manage <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> will be allowed to interact with these permissions:"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"Choose a device to be managed by &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Choose a <xliff:g id="PROFILE_NAME">%1$s</xliff:g> to set up"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"This app will be allowed to sync info, like the name of someone calling, and access these permissions on your <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
+    <string name="confirmation_title_glasses" msgid="8288346850537727333">"Allow &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; to manage &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"device"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"This app will be allowed to access these permissions on your <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Allow &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; to access this information from your phone"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Cross-device services"</string>
-    <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> to stream apps between your devices"</string>
+    <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> to stream apps between your devices"</string>
     <string name="title_automotive_projection" msgid="3296005598978412847"></string>
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"Allow &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; to access this information from your phone"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Google Play services"</string>
-    <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> to access your phone’s photos, media and notifications"</string>
-    <string name="title_nearby_device_streaming" msgid="179278282547719200">"Allow &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; to perform this action from your phone"</string>
-    <string name="helper_title_nearby_device_streaming" msgid="6124438217620593669">"Cross-device services"</string>
-    <string name="helper_summary_nearby_device_streaming" msgid="5538329403511524333">"<xliff:g id="APP_NAME">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> to stream content to nearby devices"</string>
+    <string name="helper_summary_computer" msgid="8774832742608187072">"<xliff:g id="APP_NAME">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> to access your phone’s photos, media and notifications"</string>
+    <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Allow &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; to take this action?"</string>
+    <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="DEVICE_NAME">%2$s</xliff:g> to stream apps and other system features to nearby devices"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"device"</string>
-    <string name="summary_generic_single_device" msgid="4735072202474939111">"This app will be able to sync info, like the name of someone calling, between your phone and <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string>
-    <string name="summary_generic" msgid="4988130802522924650">"This app will be able to sync info, like the name of someone calling, between your phone and the chosen device."</string>
+    <string name="summary_generic" msgid="1761976003668044801">"This app will be able to sync info, like the name of someone calling, between your phone and the chosen device"</string>
     <string name="consent_yes" msgid="8344487259618762872">"Allow"</string>
     <string name="consent_no" msgid="2640796915611404382">"Don\'t allow"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Cancel"</string>
     <string name="consent_back" msgid="2560683030046918882">"Back"</string>
+    <string name="permission_expand" msgid="893185038020887411">"Expand <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
+    <string name="permission_collapse" msgid="3320833884220844084">"Collapse <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Give apps on &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; the same permissions as on &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="permission_sync_summary" msgid="765497944331294275">"This may include &lt;strong&gt;Microphone&lt;/strong&gt;, &lt;strong&gt;Camera&lt;/strong&gt;, and &lt;strong&gt;Location access&lt;/strong&gt;, and other sensitive permissions on &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;. &lt;br/&gt;&lt;br/&gt;You can change these permissions any time in your Settings on &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;."</string>
-    <string name="vendor_icon_description" msgid="4445875290032225965">"App icon"</string>
-    <string name="vendor_header_button_description" msgid="6566660389500630608">"More information button"</string>
+    <string name="vendor_header_button_description" msgid="7994879208461111473">"More information"</string>
     <string name="permission_phone" msgid="2661081078692784919">"Phone"</string>
     <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
     <string name="permission_contacts" msgid="3858319347208004438">"Contacts"</string>
@@ -57,16 +57,18 @@
     <string name="permission_storage" msgid="6831099350839392343">"Photos and media"</string>
     <string name="permission_notification" msgid="693762568127741203">"Notifications"</string>
     <string name="permission_app_streaming" msgid="6009695219091526422">"Apps"</string>
-    <string name="permission_nearby_device_streaming" msgid="5868108148065023161">"Nearby device streaming"</string>
+    <string name="permission_nearby_device_streaming" msgid="1023325519477349499">"Streaming"</string>
     <string name="permission_phone_summary" msgid="6684396967861278044">"Can make and manage phone calls"</string>
     <string name="permission_call_logs_summary" msgid="6186103394658755022">"Can read and write phone call log"</string>
     <string name="permission_sms_summary" msgid="3508442683678912017">"Can send and view SMS messages"</string>
     <string name="permission_contacts_summary" msgid="675861979475628708">"Can access your contacts"</string>
     <string name="permission_calendar_summary" msgid="6460000922511766226">"Can access your calendar"</string>
-    <string name="permission_microphone_summary" msgid="4241354865859396558">"Can record audio using the microphone"</string>
+    <string name="permission_microphone_summary" msgid="3692091540613093394">"Can record audio"</string>
     <string name="permission_nearby_devices_summary" msgid="931940524460876655">"Can find, connect to and determine the relative position of nearby devices"</string>
     <string name="permission_notification_summary" msgid="884075314530071011">"Can read all notifications, including information like contacts, messages and photos"</string>
     <string name="permission_app_streaming_summary" msgid="606923325679670624">"Stream your phone’s apps"</string>
     <string name="permission_storage_summary" msgid="3918240895519506417"></string>
-    <string name="permission_nearby_device_streaming_summary" msgid="5776807830582725074">"Stream content to a nearby device"</string>
+    <string name="permission_nearby_device_streaming_summary" msgid="8280824871197081246">"Stream apps and other system features from your phone"</string>
+    <string name="device_type" product="default" msgid="8268703872070046263">"phone"</string>
+    <string name="device_type" product="tablet" msgid="5038791954983067774">"tablet"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-en-rCA/strings.xml b/packages/CompanionDeviceManager/res/values-en-rCA/strings.xml
index ea0ceb2..bd0342d 100644
--- a/packages/CompanionDeviceManager/res/values-en-rCA/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-en-rCA/strings.xml
@@ -17,36 +17,36 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Companion Device Manager"</string>
-    <string name="confirmation_title" msgid="8024993972587946678">"Allow &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; to access &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"Allow the app &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; to access &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"watch"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Choose a <xliff:g id="PROFILE_NAME">%1$s</xliff:g> to be managed by &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="6566922405914995759">"The app is needed to manage your <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> will be allowed to sync info, like the name of someone calling, interact with your notifications and access your Phone, SMS, Contacts, Calendar, Call logs and Nearby devices permissions."</string>
-    <string name="summary_watch_single_device" msgid="7443464525873186735">"The app is needed to manage your <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> will be allowed to sync info, like the name of someone calling, and access these permissions:"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"glasses"</string>
-    <string name="summary_glasses" msgid="3808267780579061241">"This app is needed to manage <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> will be allowed to interact with your notifications and access your Phone, SMS, Contacts, Microphone and Nearby devices permissions."</string>
-    <string name="summary_glasses_single_device" msgid="7051392780285915640">"The app is needed to manage <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> will be allowed to interact with these permissions:"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"Choose a device to be managed by &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Choose a <xliff:g id="PROFILE_NAME">%1$s</xliff:g> to set up"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"This app will be allowed to sync info, like the name of someone calling, and access these permissions on your <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
+    <string name="confirmation_title_glasses" msgid="8288346850537727333">"Allow &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; to manage &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"device"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"This app will be allowed to access these permissions on your <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Allow &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; to access this information from your phone"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Cross-device services"</string>
-    <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> to stream apps between your devices"</string>
+    <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> to stream apps between your devices"</string>
     <string name="title_automotive_projection" msgid="3296005598978412847"></string>
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"Allow &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; to access this information from your phone"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Google Play services"</string>
-    <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> to access your phone’s photos, media, and notifications"</string>
-    <string name="title_nearby_device_streaming" msgid="179278282547719200">"Allow &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; to perform this action from your phone"</string>
-    <string name="helper_title_nearby_device_streaming" msgid="6124438217620593669">"Cross-device services"</string>
-    <string name="helper_summary_nearby_device_streaming" msgid="5538329403511524333">"<xliff:g id="APP_NAME">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> to stream content to nearby devices"</string>
+    <string name="helper_summary_computer" msgid="8774832742608187072">"<xliff:g id="APP_NAME">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> to access your phone’s photos, media, and notifications"</string>
+    <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Allow &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; to take this action?"</string>
+    <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="DEVICE_NAME">%2$s</xliff:g> to stream apps and other system features to nearby devices"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"device"</string>
-    <string name="summary_generic_single_device" msgid="4735072202474939111">"This app will be able to sync info, like the name of someone calling, between your phone and <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string>
-    <string name="summary_generic" msgid="4988130802522924650">"This app will be able to sync info, like the name of someone calling, between your phone and the chosen device."</string>
+    <string name="summary_generic" msgid="1761976003668044801">"This app will be able to sync info, like the name of someone calling, between your phone and the chosen device"</string>
     <string name="consent_yes" msgid="8344487259618762872">"Allow"</string>
-    <string name="consent_no" msgid="2640796915611404382">"Don’t allow"</string>
+    <string name="consent_no" msgid="2640796915611404382">"Don\'t allow"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Cancel"</string>
     <string name="consent_back" msgid="2560683030046918882">"Back"</string>
+    <string name="permission_expand" msgid="893185038020887411">"Expand <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
+    <string name="permission_collapse" msgid="3320833884220844084">"Collapse <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Give apps on &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; the same permissions as on &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="permission_sync_summary" msgid="765497944331294275">"This may include &lt;strong&gt;Microphone&lt;/strong&gt;, &lt;strong&gt;Camera&lt;/strong&gt;, and &lt;strong&gt;Location access&lt;/strong&gt;, and other sensitive permissions on &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;. &lt;br/&gt;&lt;br/&gt;You can change these permissions any time in your Settings on &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;."</string>
-    <string name="vendor_icon_description" msgid="4445875290032225965">"App Icon"</string>
-    <string name="vendor_header_button_description" msgid="6566660389500630608">"More Information Button"</string>
+    <string name="vendor_header_button_description" msgid="7994879208461111473">"More Information"</string>
     <string name="permission_phone" msgid="2661081078692784919">"Phone"</string>
     <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
     <string name="permission_contacts" msgid="3858319347208004438">"Contacts"</string>
@@ -57,16 +57,18 @@
     <string name="permission_storage" msgid="6831099350839392343">"Photos and media"</string>
     <string name="permission_notification" msgid="693762568127741203">"Notifications"</string>
     <string name="permission_app_streaming" msgid="6009695219091526422">"Apps"</string>
-    <string name="permission_nearby_device_streaming" msgid="5868108148065023161">"Nearby Device Streaming"</string>
+    <string name="permission_nearby_device_streaming" msgid="1023325519477349499">"Streaming"</string>
     <string name="permission_phone_summary" msgid="6684396967861278044">"Can make and manage phone calls"</string>
     <string name="permission_call_logs_summary" msgid="6186103394658755022">"Can read and write phone call log"</string>
     <string name="permission_sms_summary" msgid="3508442683678912017">"Can send and view SMS messages"</string>
     <string name="permission_contacts_summary" msgid="675861979475628708">"Can access your contacts"</string>
     <string name="permission_calendar_summary" msgid="6460000922511766226">"Can access your calendar"</string>
-    <string name="permission_microphone_summary" msgid="4241354865859396558">"Can record audio using the microphone"</string>
+    <string name="permission_microphone_summary" msgid="3692091540613093394">"Can record audio"</string>
     <string name="permission_nearby_devices_summary" msgid="931940524460876655">"Can find, connect to, and determine the relative position of nearby devices"</string>
     <string name="permission_notification_summary" msgid="884075314530071011">"Can read all notifications, including information like contacts, messages, and photos"</string>
     <string name="permission_app_streaming_summary" msgid="606923325679670624">"Stream your phone’s apps"</string>
     <string name="permission_storage_summary" msgid="3918240895519506417"></string>
-    <string name="permission_nearby_device_streaming_summary" msgid="5776807830582725074">"Stream content to a nearby device"</string>
+    <string name="permission_nearby_device_streaming_summary" msgid="8280824871197081246">"Stream apps and other system features from your phone"</string>
+    <string name="device_type" product="default" msgid="8268703872070046263">"phone"</string>
+    <string name="device_type" product="tablet" msgid="5038791954983067774">"tablet"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-en-rGB/strings.xml b/packages/CompanionDeviceManager/res/values-en-rGB/strings.xml
index 5708fdc..ca12145 100644
--- a/packages/CompanionDeviceManager/res/values-en-rGB/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-en-rGB/strings.xml
@@ -17,36 +17,36 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Companion Device Manager"</string>
-    <string name="confirmation_title" msgid="8024993972587946678">"Allow &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; to access &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"Allow the app &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; to access &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"watch"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Choose a <xliff:g id="PROFILE_NAME">%1$s</xliff:g> to be managed by &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="6566922405914995759">"The app is needed to manage your <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> will be allowed to sync info, like the name of someone calling, interact with your notifications and access your Phone, SMS, Contacts, Calendar, Call logs and Nearby devices permissions."</string>
-    <string name="summary_watch_single_device" msgid="7443464525873186735">"The app is needed to manage your <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> will be allowed to sync info, like the name of someone calling, and access these permissions:"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"glasses"</string>
-    <string name="summary_glasses" msgid="3808267780579061241">"This app is needed to manage <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> will be allowed to interact with your notifications and access your phone, SMS, contacts, microphone and Nearby devices permissions."</string>
-    <string name="summary_glasses_single_device" msgid="7051392780285915640">"The app is needed to manage <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> will be allowed to interact with these permissions:"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"Choose a device to be managed by &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Choose a <xliff:g id="PROFILE_NAME">%1$s</xliff:g> to set up"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"This app will be allowed to sync info, like the name of someone calling, and access these permissions on your <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
+    <string name="confirmation_title_glasses" msgid="8288346850537727333">"Allow &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; to manage &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"device"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"This app will be allowed to access these permissions on your <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Allow &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; to access this information from your phone"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Cross-device services"</string>
-    <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> to stream apps between your devices"</string>
+    <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> to stream apps between your devices"</string>
     <string name="title_automotive_projection" msgid="3296005598978412847"></string>
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"Allow &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; to access this information from your phone"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Google Play services"</string>
-    <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> to access your phone’s photos, media and notifications"</string>
-    <string name="title_nearby_device_streaming" msgid="179278282547719200">"Allow &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; to perform this action from your phone"</string>
-    <string name="helper_title_nearby_device_streaming" msgid="6124438217620593669">"Cross-device services"</string>
-    <string name="helper_summary_nearby_device_streaming" msgid="5538329403511524333">"<xliff:g id="APP_NAME">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> to stream content to nearby devices"</string>
+    <string name="helper_summary_computer" msgid="8774832742608187072">"<xliff:g id="APP_NAME">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> to access your phone’s photos, media and notifications"</string>
+    <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Allow &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; to take this action?"</string>
+    <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="DEVICE_NAME">%2$s</xliff:g> to stream apps and other system features to nearby devices"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"device"</string>
-    <string name="summary_generic_single_device" msgid="4735072202474939111">"This app will be able to sync info, like the name of someone calling, between your phone and <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string>
-    <string name="summary_generic" msgid="4988130802522924650">"This app will be able to sync info, like the name of someone calling, between your phone and the chosen device."</string>
+    <string name="summary_generic" msgid="1761976003668044801">"This app will be able to sync info, like the name of someone calling, between your phone and the chosen device"</string>
     <string name="consent_yes" msgid="8344487259618762872">"Allow"</string>
     <string name="consent_no" msgid="2640796915611404382">"Don\'t allow"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Cancel"</string>
     <string name="consent_back" msgid="2560683030046918882">"Back"</string>
+    <string name="permission_expand" msgid="893185038020887411">"Expand <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
+    <string name="permission_collapse" msgid="3320833884220844084">"Collapse <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Give apps on &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; the same permissions as on &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="permission_sync_summary" msgid="765497944331294275">"This may include &lt;strong&gt;Microphone&lt;/strong&gt;, &lt;strong&gt;Camera&lt;/strong&gt;, and &lt;strong&gt;Location access&lt;/strong&gt;, and other sensitive permissions on &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;. &lt;br/&gt;&lt;br/&gt;You can change these permissions any time in your Settings on &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;."</string>
-    <string name="vendor_icon_description" msgid="4445875290032225965">"App icon"</string>
-    <string name="vendor_header_button_description" msgid="6566660389500630608">"More information button"</string>
+    <string name="vendor_header_button_description" msgid="7994879208461111473">"More information"</string>
     <string name="permission_phone" msgid="2661081078692784919">"Phone"</string>
     <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
     <string name="permission_contacts" msgid="3858319347208004438">"Contacts"</string>
@@ -57,16 +57,18 @@
     <string name="permission_storage" msgid="6831099350839392343">"Photos and media"</string>
     <string name="permission_notification" msgid="693762568127741203">"Notifications"</string>
     <string name="permission_app_streaming" msgid="6009695219091526422">"Apps"</string>
-    <string name="permission_nearby_device_streaming" msgid="5868108148065023161">"Nearby device streaming"</string>
+    <string name="permission_nearby_device_streaming" msgid="1023325519477349499">"Streaming"</string>
     <string name="permission_phone_summary" msgid="6684396967861278044">"Can make and manage phone calls"</string>
     <string name="permission_call_logs_summary" msgid="6186103394658755022">"Can read and write phone call log"</string>
     <string name="permission_sms_summary" msgid="3508442683678912017">"Can send and view SMS messages"</string>
     <string name="permission_contacts_summary" msgid="675861979475628708">"Can access your contacts"</string>
     <string name="permission_calendar_summary" msgid="6460000922511766226">"Can access your calendar"</string>
-    <string name="permission_microphone_summary" msgid="4241354865859396558">"Can record audio using the microphone"</string>
+    <string name="permission_microphone_summary" msgid="3692091540613093394">"Can record audio"</string>
     <string name="permission_nearby_devices_summary" msgid="931940524460876655">"Can find, connect to and determine the relative position of nearby devices"</string>
     <string name="permission_notification_summary" msgid="884075314530071011">"Can read all notifications, including information like contacts, messages and photos"</string>
     <string name="permission_app_streaming_summary" msgid="606923325679670624">"Stream your phone’s apps"</string>
     <string name="permission_storage_summary" msgid="3918240895519506417"></string>
-    <string name="permission_nearby_device_streaming_summary" msgid="5776807830582725074">"Stream content to a nearby device"</string>
+    <string name="permission_nearby_device_streaming_summary" msgid="8280824871197081246">"Stream apps and other system features from your phone"</string>
+    <string name="device_type" product="default" msgid="8268703872070046263">"phone"</string>
+    <string name="device_type" product="tablet" msgid="5038791954983067774">"tablet"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-en-rIN/strings.xml b/packages/CompanionDeviceManager/res/values-en-rIN/strings.xml
index 5708fdc..ca12145 100644
--- a/packages/CompanionDeviceManager/res/values-en-rIN/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-en-rIN/strings.xml
@@ -17,36 +17,36 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Companion Device Manager"</string>
-    <string name="confirmation_title" msgid="8024993972587946678">"Allow &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; to access &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"Allow the app &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; to access &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"watch"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Choose a <xliff:g id="PROFILE_NAME">%1$s</xliff:g> to be managed by &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="6566922405914995759">"The app is needed to manage your <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> will be allowed to sync info, like the name of someone calling, interact with your notifications and access your Phone, SMS, Contacts, Calendar, Call logs and Nearby devices permissions."</string>
-    <string name="summary_watch_single_device" msgid="7443464525873186735">"The app is needed to manage your <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> will be allowed to sync info, like the name of someone calling, and access these permissions:"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"glasses"</string>
-    <string name="summary_glasses" msgid="3808267780579061241">"This app is needed to manage <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> will be allowed to interact with your notifications and access your phone, SMS, contacts, microphone and Nearby devices permissions."</string>
-    <string name="summary_glasses_single_device" msgid="7051392780285915640">"The app is needed to manage <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> will be allowed to interact with these permissions:"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"Choose a device to be managed by &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Choose a <xliff:g id="PROFILE_NAME">%1$s</xliff:g> to set up"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"This app will be allowed to sync info, like the name of someone calling, and access these permissions on your <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
+    <string name="confirmation_title_glasses" msgid="8288346850537727333">"Allow &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; to manage &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"device"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"This app will be allowed to access these permissions on your <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Allow &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; to access this information from your phone"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Cross-device services"</string>
-    <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> to stream apps between your devices"</string>
+    <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> to stream apps between your devices"</string>
     <string name="title_automotive_projection" msgid="3296005598978412847"></string>
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"Allow &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; to access this information from your phone"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Google Play services"</string>
-    <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> to access your phone’s photos, media and notifications"</string>
-    <string name="title_nearby_device_streaming" msgid="179278282547719200">"Allow &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; to perform this action from your phone"</string>
-    <string name="helper_title_nearby_device_streaming" msgid="6124438217620593669">"Cross-device services"</string>
-    <string name="helper_summary_nearby_device_streaming" msgid="5538329403511524333">"<xliff:g id="APP_NAME">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> to stream content to nearby devices"</string>
+    <string name="helper_summary_computer" msgid="8774832742608187072">"<xliff:g id="APP_NAME">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> to access your phone’s photos, media and notifications"</string>
+    <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Allow &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; to take this action?"</string>
+    <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="DEVICE_NAME">%2$s</xliff:g> to stream apps and other system features to nearby devices"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"device"</string>
-    <string name="summary_generic_single_device" msgid="4735072202474939111">"This app will be able to sync info, like the name of someone calling, between your phone and <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string>
-    <string name="summary_generic" msgid="4988130802522924650">"This app will be able to sync info, like the name of someone calling, between your phone and the chosen device."</string>
+    <string name="summary_generic" msgid="1761976003668044801">"This app will be able to sync info, like the name of someone calling, between your phone and the chosen device"</string>
     <string name="consent_yes" msgid="8344487259618762872">"Allow"</string>
     <string name="consent_no" msgid="2640796915611404382">"Don\'t allow"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Cancel"</string>
     <string name="consent_back" msgid="2560683030046918882">"Back"</string>
+    <string name="permission_expand" msgid="893185038020887411">"Expand <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
+    <string name="permission_collapse" msgid="3320833884220844084">"Collapse <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Give apps on &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; the same permissions as on &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="permission_sync_summary" msgid="765497944331294275">"This may include &lt;strong&gt;Microphone&lt;/strong&gt;, &lt;strong&gt;Camera&lt;/strong&gt;, and &lt;strong&gt;Location access&lt;/strong&gt;, and other sensitive permissions on &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;. &lt;br/&gt;&lt;br/&gt;You can change these permissions any time in your Settings on &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;."</string>
-    <string name="vendor_icon_description" msgid="4445875290032225965">"App icon"</string>
-    <string name="vendor_header_button_description" msgid="6566660389500630608">"More information button"</string>
+    <string name="vendor_header_button_description" msgid="7994879208461111473">"More information"</string>
     <string name="permission_phone" msgid="2661081078692784919">"Phone"</string>
     <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
     <string name="permission_contacts" msgid="3858319347208004438">"Contacts"</string>
@@ -57,16 +57,18 @@
     <string name="permission_storage" msgid="6831099350839392343">"Photos and media"</string>
     <string name="permission_notification" msgid="693762568127741203">"Notifications"</string>
     <string name="permission_app_streaming" msgid="6009695219091526422">"Apps"</string>
-    <string name="permission_nearby_device_streaming" msgid="5868108148065023161">"Nearby device streaming"</string>
+    <string name="permission_nearby_device_streaming" msgid="1023325519477349499">"Streaming"</string>
     <string name="permission_phone_summary" msgid="6684396967861278044">"Can make and manage phone calls"</string>
     <string name="permission_call_logs_summary" msgid="6186103394658755022">"Can read and write phone call log"</string>
     <string name="permission_sms_summary" msgid="3508442683678912017">"Can send and view SMS messages"</string>
     <string name="permission_contacts_summary" msgid="675861979475628708">"Can access your contacts"</string>
     <string name="permission_calendar_summary" msgid="6460000922511766226">"Can access your calendar"</string>
-    <string name="permission_microphone_summary" msgid="4241354865859396558">"Can record audio using the microphone"</string>
+    <string name="permission_microphone_summary" msgid="3692091540613093394">"Can record audio"</string>
     <string name="permission_nearby_devices_summary" msgid="931940524460876655">"Can find, connect to and determine the relative position of nearby devices"</string>
     <string name="permission_notification_summary" msgid="884075314530071011">"Can read all notifications, including information like contacts, messages and photos"</string>
     <string name="permission_app_streaming_summary" msgid="606923325679670624">"Stream your phone’s apps"</string>
     <string name="permission_storage_summary" msgid="3918240895519506417"></string>
-    <string name="permission_nearby_device_streaming_summary" msgid="5776807830582725074">"Stream content to a nearby device"</string>
+    <string name="permission_nearby_device_streaming_summary" msgid="8280824871197081246">"Stream apps and other system features from your phone"</string>
+    <string name="device_type" product="default" msgid="8268703872070046263">"phone"</string>
+    <string name="device_type" product="tablet" msgid="5038791954983067774">"tablet"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-en-rXC/strings.xml b/packages/CompanionDeviceManager/res/values-en-rXC/strings.xml
index ffad25b..70af915 100644
--- a/packages/CompanionDeviceManager/res/values-en-rXC/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-en-rXC/strings.xml
@@ -17,36 +17,36 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‏‎‎‎‎‎‏‎‏‏‎‏‏‎‏‏‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‎‎‎‏‎‏‎‏‎‎‏‏‏‏‏‏‎‎‎‏‏‏‎‎‏‎‏‎Companion Device Manager‎‏‎‎‏‎"</string>
-    <string name="confirmation_title" msgid="8024993972587946678">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‏‏‎‏‎‏‏‏‏‎‏‎‎‎‎‎‎‏‎‏‏‏‏‏‏‏‏‏‏‏‎‏‎‎‏‏‎‎‎‎‎‏‎‏‏‎‎‎‏‎‏‎‏‏‎‏‏‎‎Allow &lt;strong&gt;‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎&lt;/strong&gt; to access &lt;strong&gt;‎‏‎‎‏‏‎<xliff:g id="DEVICE_NAME">%2$s</xliff:g>‎‏‎‎‏‏‏‎&lt;/strong&gt;‎‏‎‎‏‎"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‏‏‎‎‏‎‎‏‎‏‎‎‏‎‎‏‏‎‎‎‏‏‎‏‎‎‎‏‏‎‏‏‏‏‎‎‎‏‏‏‎‎‎‏‎‎‏‏‎‏‏‎‎‎‎‏‏‎‎Allow the app &lt;strong&gt;‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎&lt;/strong&gt; to access &lt;strong&gt;‎‏‎‎‏‏‎<xliff:g id="DEVICE_NAME">%2$s</xliff:g>‎‏‎‎‏‏‏‎&lt;/strong&gt;?‎‏‎‎‏‎"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‏‎‎‏‎‏‎‏‎‏‏‏‏‏‏‏‎‎‏‏‏‎‎‏‎‎‎‏‎‏‎‏‏‏‏‏‏‏‎‎‏‎‏‎‎‎‎watch‎‏‎‎‏‎"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‏‏‎‏‏‎‎‏‎‏‎‏‎‎‏‎‎‎‏‎‎‎‎‏‏‎‏‎‎‎‏‎‎‏‏‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‏‏‎‎‏‏‏‏‎Choose a ‎‏‎‎‏‏‎<xliff:g id="PROFILE_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎ to be managed by &lt;strong&gt;‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%2$s</xliff:g>‎‏‎‎‏‏‏‎&lt;/strong&gt;‎‏‎‎‏‎"</string>
-    <string name="summary_watch" msgid="6566922405914995759">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‏‏‎‎‏‎‎‎‏‎‎‏‏‎‎‏‎‏‎‎‏‎‎‎‏‎‏‎‎‏‏‎‎‎‎‎‎‏‏‏‎‏‏‏‏‎‏‏‎‎‎‎‏‎‏‏‏‏‎The app is needed to manage your ‎‏‎‎‏‏‎<xliff:g id="DEVICE_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎. ‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%2$s</xliff:g>‎‏‎‎‏‏‏‎ will be allowed to sync info, like the name of someone calling, interact with your notifications and access your Phone, SMS, Contacts, Calendar, Call logs and Nearby devices permissions.‎‏‎‎‏‎"</string>
-    <string name="summary_watch_single_device" msgid="7443464525873186735">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‏‏‎‏‎‎‏‏‎‎‎‏‏‏‏‏‏‏‏‎‎‎‏‏‎‎‏‎‎‏‎‎‏‎‏‏‏‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‎‏‏‏‏‎The app is needed to manage your ‎‏‎‎‏‏‎<xliff:g id="DEVICE_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎. ‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%2$s</xliff:g>‎‏‎‎‏‏‏‎ will be allowed to sync info, like the name of someone calling, and access these permissions:‎‏‎‎‏‎"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‎‏‏‏‎‎‏‏‎‎‏‏‎‏‎‏‎‏‎‏‏‎‏‏‎‎‎‏‎‏‏‎‎‎‏‎‏‎‏‎‏‎‎‎‏‏‎‎‏‏‏‏‎‏‏‏‏‎‎glasses‎‏‎‎‏‎"</string>
-    <string name="summary_glasses" msgid="3808267780579061241">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‎‎‏‏‎‏‏‎‎‏‏‎‏‎‏‏‏‏‎‏‎‏‏‏‏‎‏‎‎‎‏‏‏‎‏‏‏‏‏‎‏‎‏‏‏‎‏‏‎‏‏‏‏‏‏‎‎‏‎This app is needed to manage ‎‏‎‎‏‏‎<xliff:g id="DEVICE_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎. ‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%2$s</xliff:g>‎‏‎‎‏‏‏‎ will be allowed to interact with your notifications and access your Phone, SMS, Contacts, Microphone and Nearby devices permissions.‎‏‎‎‏‎"</string>
-    <string name="summary_glasses_single_device" msgid="7051392780285915640">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‎‎‏‏‏‎‏‏‎‏‏‏‎‎‏‎‏‎‎‎‏‎‏‏‏‏‏‏‎‏‏‎‎‎‎‏‎‏‏‎‎‏‎‎‎‏‏‎‎‎‏‏‏‏‏‏‎‎‎‎The app is needed to manage ‎‏‎‎‏‏‎<xliff:g id="DEVICE_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎. ‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%2$s</xliff:g>‎‏‎‎‏‏‏‎ will be allowed to interact with these permissions:‎‏‎‎‏‎"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‏‏‏‏‎‎‎‎‎‎‏‎‏‏‎‏‏‎‎‏‎‎‎‏‏‎‎‏‏‏‏‎‏‏‏‏‎‎‎‏‏‏‎‏‎‎‎‏‎‎‏‎‏‎‏‎‎‎‎Choose a device to be managed by &lt;strong&gt;‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎&lt;/strong&gt;‎‏‎‎‏‎"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‏‏‎‎‎‎‎‏‏‏‎‎‏‏‏‎‏‎‎‏‏‎‎‎‎‏‎‎‏‏‎‏‏‏‏‏‏‏‏‏‎‎‎‏‏‎‎‏‏‎‏‏‏‏‎‏‎‏‎Choose a ‎‏‎‎‏‏‎<xliff:g id="PROFILE_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎ to set up‎‏‎‎‏‎"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‏‎‎‏‏‏‏‏‏‎‏‏‎‎‎‎‏‎‎‏‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‎‎‎‎‏‎‎‎‎‏‎‎‎‏‎‏‏‎‏‏‎‎This app will be allowed to sync info, like the name of someone calling, and access these permissions on your ‎‏‎‎‏‏‎<xliff:g id="DEVICE_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
+    <string name="confirmation_title_glasses" msgid="8288346850537727333">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‏‏‎‎‎‎‎‏‏‎‎‎‎‏‏‏‏‏‏‎‎‎‏‏‎‎‎‎‎‎‏‎‎‏‏‎‎‎‎‏‏‏‎‏‏‎‎‏‎‏‎‏‏‎‎‏‎‏‎Allow &lt;strong&gt;‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎&lt;/strong&gt; to manage &lt;strong&gt;‎‏‎‎‏‏‎<xliff:g id="DEVICE_NAME">%2$s</xliff:g>‎‏‎‎‏‏‏‎&lt;/strong&gt;?‎‏‎‎‏‎"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‎‎‏‎‏‎‏‎‎‏‏‎‎‏‏‎‏‏‏‎‏‏‎‎‎‎‎‏‏‏‏‏‏‎‎‎‎‏‎‏‏‎‏‎‎‏‏‎‎‎‏‎‏‏‏‏‎‏‎device‎‏‎‎‏‎"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‏‏‏‏‎‏‏‏‎‎‎‏‎‎‏‏‎‎‎‏‏‏‎‏‏‎‏‎‏‎‏‏‎‏‏‎‏‏‎‏‏‏‎‎‎‏‎‏‎‏‎‎‎‏‎‎‏‏‎This app will be allowed to access these permissions on your ‎‏‎‎‏‏‎<xliff:g id="DEVICE_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‏‏‏‎‎‎‎‎‎‏‏‏‎‏‎‏‏‎‎‎‎‎‏‎‎‏‏‏‎‎‎‏‎‏‎‏‏‎‏‎‎‎‎‏‏‏‎‎‏‎‎‏‏‎‎‏‏‎‎Allow &lt;strong&gt;‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎&lt;/strong&gt; to access this information from your phone‎‏‎‎‏‎"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‎‏‏‎‎‏‏‏‎‏‏‏‎‎‎‎‎‏‎‏‎‏‎‏‎‏‎‏‎‎‎‎‏‎‏‎‏‎‎‏‎‏‎‎‏‏‎‏‎‏‏‏‏‎‎‏‎‏‎Cross-device services‎‏‎‎‏‎"</string>
-    <string name="helper_summary_app_streaming" msgid="5977509499890099">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‎‏‎‏‏‎‏‎‏‎‎‏‏‏‏‎‎‏‎‎‎‎‎‏‏‎‏‏‎‎‎‏‎‎‏‏‎‎‎‎‏‏‏‏‎‏‎‎‏‏‎‏‏‎‎‏‏‎‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎ is requesting permission on behalf of your ‎‏‎‎‏‏‎<xliff:g id="DEVICE_TYPE">%2$s</xliff:g>‎‏‎‎‏‏‏‎ to stream apps between your devices‎‏‎‎‏‎"</string>
+    <string name="helper_summary_app_streaming" msgid="2396773196949578425">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‎‎‏‎‏‎‎‎‎‏‏‎‎‎‎‏‏‎‎‏‎‎‎‎‏‏‎‎‎‏‎‎‎‏‏‎‏‎‏‏‎‏‏‎‏‏‎‎‎‏‎‏‎‏‏‏‎‎‏‎‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎ is requesting permission on behalf of your ‎‏‎‎‏‏‎<xliff:g id="DISPLAY_NAME">%2$s</xliff:g>‎‏‎‎‏‏‏‎ to stream apps between your devices‎‏‎‎‏‎"</string>
     <string name="title_automotive_projection" msgid="3296005598978412847"></string>
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‏‎‏‏‎‏‏‎‎‎‎‏‎‎‏‎‏‎‎‏‎‏‎‎‎‏‏‏‎‏‏‏‏‏‎‎‏‏‎‎‎‎‎‏‎‎‏‎‏‎Allow &lt;strong&gt;‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎&lt;/strong&gt; to access this information from your phone‎‏‎‎‏‎"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‎‎‏‏‎‏‎‎‏‎‏‏‏‏‏‎‏‎‎‏‏‏‏‎‏‏‏‏‏‏‏‎‎‏‏‏‎‏‎‏‏‏‎‎‏‎‏‏‏‎‏‎‏‎‎‏‎‏‎Google Play services‎‏‎‎‏‎"</string>
-    <string name="helper_summary_computer" msgid="9050724687678157852">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‎‏‏‎‎‏‏‎‏‎‏‎‏‎‎‎‏‎‎‎‏‎‎‏‏‎‏‎‎‏‎‎‎‎‏‏‏‎‏‎‏‎‎‎‏‏‎‏‎‎‎‎‎‏‏‏‎‎‎‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎ is requesting permission on behalf of your ‎‏‎‎‏‏‎<xliff:g id="DEVICE_TYPE">%2$s</xliff:g>‎‏‎‎‏‏‏‎ to access your phone’s photos, media, and notifications‎‏‎‎‏‎"</string>
-    <string name="title_nearby_device_streaming" msgid="179278282547719200">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‎‏‎‎‏‏‏‏‏‎‎‏‏‏‎‏‏‎‎‏‎‏‎‎‏‎‏‏‏‏‎‏‎‏‏‎‎‏‏‎‎‏‎‏‏‏‎‏‏‎‎‎‎‏‎‎‎‎‎‎Allow &lt;strong&gt;‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎&lt;/strong&gt; to perform this action from your phone‎‏‎‎‏‎"</string>
-    <string name="helper_title_nearby_device_streaming" msgid="6124438217620593669">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‎‎‏‏‏‏‏‏‏‎‎‏‏‎‎‎‎‎‎‎‎‏‏‏‎‎‎‎‏‏‏‎‏‎‎‎‎‏‏‎‏‎‎‎‎‏‏‏‎‎‎‎‎‎‎‏‎‏‎Cross-device services‎‏‎‎‏‎"</string>
-    <string name="helper_summary_nearby_device_streaming" msgid="5538329403511524333">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‏‎‎‏‏‎‏‏‏‎‎‎‎‎‏‏‎‎‏‎‎‏‏‏‏‏‏‏‏‎‎‏‎‎‏‎‏‏‎‎‏‏‏‏‏‎‎‏‎‏‏‏‏‏‎‏‏‎‏‎‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎ is requesting permission on behalf of your ‎‏‎‎‏‏‎<xliff:g id="DEVICE_TYPE">%2$s</xliff:g>‎‏‎‎‏‏‏‎ to stream content to nearby devices‎‏‎‎‏‎"</string>
+    <string name="helper_summary_computer" msgid="8774832742608187072">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‎‎‏‏‏‎‎‎‏‏‎‎‏‏‏‎‏‏‏‏‏‏‎‎‎‏‏‎‏‏‎‏‎‏‎‏‎‏‎‎‎‎‎‏‎‏‎‏‏‏‎‏‏‎‎‎‎‎‎‎‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎ is requesting permission on behalf of your ‎‏‎‎‏‏‎<xliff:g id="DISPLAY_NAME">%2$s</xliff:g>‎‏‎‎‏‏‏‎ to access your phone’s photos, media, and notifications‎‏‎‎‏‎"</string>
+    <string name="title_nearby_device_streaming" msgid="7269956847378799794">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‎‎‏‏‏‎‎‏‎‎‎‎‎‏‎‎‏‏‎‎‏‏‏‏‎‎‎‎‎‏‏‏‎‎‎‏‏‎‏‏‏‎‏‎‎‏‏‏‎‎‏‎‏‏‎‎‏‎‎Allow &lt;strong&gt;‎‏‎‎‏‏‎<xliff:g id="DEVICE_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎&lt;/strong&gt; to take this action?‎‏‎‎‏‎"</string>
+    <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‎‎‏‎‏‎‎‏‎‎‏‎‏‎‏‏‎‏‎‏‎‎‏‎‏‏‏‏‏‏‎‎‏‎‎‎‎‎‎‎‎‏‎‎‏‎‏‎‏‎‎‏‎‎‏‏‎‎‎‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎ is requesting permission on behalf of your ‎‏‎‎‏‏‎<xliff:g id="DEVICE_NAME">%2$s</xliff:g>‎‏‎‎‏‏‏‎ to stream apps and other system features to nearby devices‎‏‎‎‏‎"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‏‏‎‎‎‏‎‎‏‏‏‎‏‏‏‏‏‎‎‏‎‎‏‎‎‏‏‏‏‎‎‎‏‏‏‎‏‏‏‎‎‎‎‎‎‎‎‏‏‏‎‏‏‎‏‏‎‎‎device‎‏‎‎‏‎"</string>
-    <string name="summary_generic_single_device" msgid="4735072202474939111">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‎‏‏‎‏‏‎‏‏‎‎‏‎‏‏‎‏‏‎‎‎‏‎‏‎‎‏‎‏‎‎‏‏‎‎‏‎‏‏‎‎‎‎‏‏‎‎‎‏‎‏‏‏‎‎‏‏‏‎This app will be able to sync info, like the name of someone calling, between your phone and ‎‏‎‎‏‏‎<xliff:g id="DEVICE_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎.‎‏‎‎‏‎"</string>
-    <string name="summary_generic" msgid="4988130802522924650">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‏‎‏‎‎‏‏‏‎‎‏‎‏‏‎‎‏‏‎‏‎‎‎‏‎‎‏‎‏‏‎‎‎‎‏‏‎‎‏‎‎‎‎‎‎‏‎‎‏‏‎‎‏‏‎‏‎‏‎‎This app will be able to sync info, like the name of someone calling, between your phone and the chosen device.‎‏‎‎‏‎"</string>
+    <string name="summary_generic" msgid="1761976003668044801">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‎‎‎‏‏‏‎‎‏‏‏‏‎‎‏‎‏‏‏‏‎‏‎‏‏‏‎‎‎‎‏‎‎‏‎‏‏‎‎‏‎‏‎‏‏‎‏‏‎‎‎‎‎‎‎‎‎‏‎This app will be able to sync info, like the name of someone calling, between your phone and the chosen device‎‏‎‎‏‎"</string>
     <string name="consent_yes" msgid="8344487259618762872">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‏‏‏‏‎‎‏‏‎‏‏‎‎‏‎‎‏‎‏‏‏‏‎‎‏‏‏‎‎‏‏‏‏‎‎‏‎‏‏‎‎‏‎‏‏‎‎‎‎‎‎‏‏‏‏‎‎‎‎Allow‎‏‎‎‏‎"</string>
     <string name="consent_no" msgid="2640796915611404382">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‎‎‏‎‏‎‎‏‎‏‏‏‏‏‏‏‏‎‏‏‎‎‏‏‎‏‏‎‎‏‏‏‏‎‏‏‏‏‎‏‏‏‎‎‎‎‏‎‎‎‎‏‎‏‏‏‏‎‎Don’t allow‎‏‎‎‏‎"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‏‏‎‎‏‏‏‏‎‏‎‏‎‎‏‏‏‎‏‏‎‎‏‎‎‎‏‎‎‎‎‏‎‎‏‏‏‏‎‏‎‏‎‏‏‏‎‎‎‎‏‎‏‎‏‎‎‎‏‎Cancel‎‏‎‎‏‎"</string>
     <string name="consent_back" msgid="2560683030046918882">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‎‏‏‏‎‎‎‏‎‎‏‎‏‎‏‏‏‏‏‏‎‏‎‎‏‏‎‎‎‎‏‎‏‎‎‎‏‎‏‎‎‎‏‎‏‎‎‏‎‎‎‏‏‏‎‎‎‏‎‎Back‎‏‎‎‏‎"</string>
+    <string name="permission_expand" msgid="893185038020887411">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‎‎‎‏‏‎‎‏‎‏‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‎‏‎‎‏‏‎‏‏‏‏‎‏‏‎‏‏‏‎‎‎‎‏‏‎‏‏‏‎‎‏‏‎Expand ‎‏‎‎‏‏‎<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
+    <string name="permission_collapse" msgid="3320833884220844084">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‏‏‎‎‎‎‏‎‏‎‏‏‏‏‏‏‎‎‎‏‏‎‏‎‎‏‎‏‎‏‏‏‎‏‏‎‎‎‏‎‎‎‎‎‏‏‎‏‎‎‎‎‎‏‏‎‏‎‎‎Collapse ‎‏‎‎‏‏‎<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
     <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‎‏‎‎‏‏‎‎‏‎‎‎‏‎‎‎‎‎‎‎‎‎‎‎‎‏‎‎‏‎‎‏‎‏‎‎‎‎‎‏‎‎‏‎‎‎‏‏‏‎‏‎‏‏‏‏‏‎‎Give apps on &lt;strong&gt;‎‏‎‎‏‏‎<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎&lt;/strong&gt; the same permissions as on &lt;strong&gt;‎‏‎‎‏‏‎<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>‎‏‎‎‏‏‏‎&lt;/strong&gt;?‎‏‎‎‏‎"</string>
     <string name="permission_sync_summary" msgid="765497944331294275">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‏‎‏‎‎‏‏‏‏‏‏‎‎‏‏‎‎‎‎‏‎‏‎‎‏‏‎‎‎‏‏‎‎‎‎‎‏‎‏‎‎‎‏‎‎‏‎‏‏‎‎‏‎‎‎‎‏‏‎This may include &lt;strong&gt;Microphone&lt;/strong&gt;, &lt;strong&gt;Camera&lt;/strong&gt;, and &lt;strong&gt;Location access&lt;/strong&gt;, and other sensitive permissions on &lt;strong&gt;‎‏‎‎‏‏‎<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>‎‏‎‎‏‏‏‎&lt;/strong&gt;. &lt;br/&gt;&lt;br/&gt;You can change these permissions any time in your Settings on &lt;strong&gt;‎‏‎‎‏‏‎<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>‎‏‎‎‏‏‏‎&lt;/strong&gt;.‎‏‎‎‏‎"</string>
-    <string name="vendor_icon_description" msgid="4445875290032225965">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‎‏‏‎‏‏‎‎‏‎‏‏‏‎‏‏‎‎‎‎‎‎‎‏‎‏‎‏‏‎‎‎‏‎‎‎‎‎‏‏‏‎‏‎‏‏‏‏‏‎‏‎‏‎‏‏‎‏‎App Icon‎‏‎‎‏‎"</string>
-    <string name="vendor_header_button_description" msgid="6566660389500630608">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‏‏‎‎‏‎‎‎‎‏‎‏‏‏‎‏‏‎‏‏‎‏‎‏‎‏‎‎‏‎‎‏‎‎‏‏‎‏‎‎‎‎‎‎‏‏‎‏‏‎‎‏‎‏‎‎‎‎‎More Information Button‎‏‎‎‏‎"</string>
+    <string name="vendor_header_button_description" msgid="7994879208461111473">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‏‎‏‏‏‏‎‎‏‏‏‎‎‎‎‏‎‎‎‏‎‎‏‎‎‎‎‎‎‎‏‎‏‎‎‏‏‏‏‎‏‏‎‎‎‏‎‎‎‎‏‎‏‏‎‎‎‏‎More Information‎‏‎‎‏‎"</string>
     <string name="permission_phone" msgid="2661081078692784919">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‎‎‏‏‏‎‏‏‏‎‎‎‎‎‏‏‏‏‎‎‏‎‎‏‎‎‎‏‎‏‏‏‎‏‏‏‏‏‏‏‏‏‎‎‏‎‏‎‏‏‎‎‎‏‎‏‏‏‎Phone‎‏‎‎‏‎"</string>
     <string name="permission_sms" msgid="6337141296535774786">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‏‏‏‏‏‏‎‎‏‎‎‎‎‎‏‏‎‎‎‏‏‎‏‏‏‎‏‏‏‏‏‏‏‏‎‏‏‏‏‎‎‏‎‎‏‎‎‏‏‎‎‏‎‎‎‎‏‎‎SMS‎‏‎‎‏‎"</string>
     <string name="permission_contacts" msgid="3858319347208004438">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‎‏‏‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‎‎‎‎‏‎‎‎‎‏‎‎‎‎‏‎‏‏‎‎‏‏‎‏‎‎‎‏‏‎‏‎‏‎‏‏‎‎Contacts‎‏‎‎‏‎"</string>
@@ -57,16 +57,18 @@
     <string name="permission_storage" msgid="6831099350839392343">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‏‎‏‏‎‎‏‏‎‎‏‏‏‏‎‎‎‎‏‎‏‎‏‎‏‎‏‎‏‏‏‎‎‎‎‎‏‎‏‏‏‎‏‏‏‏‏‎‎‎‎‏‎‏‎‏‏‏‎Photos and media‎‏‎‎‏‎"</string>
     <string name="permission_notification" msgid="693762568127741203">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‎‏‏‎‏‎‎‎‎‎‏‎‏‏‏‏‎‏‎‏‏‎‎‎‎‏‎‏‎‎‎‏‏‏‏‎‏‏‎‏‏‏‎‏‏‏‏‏‎‏‎‎‎‏‎‎‏‏‎Notifications‎‏‎‎‏‎"</string>
     <string name="permission_app_streaming" msgid="6009695219091526422">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‏‏‎‏‏‎‎‏‏‎‏‎‏‏‏‎‎‏‏‏‏‏‎‏‏‎‎‎‎‎‎‏‏‎‎‏‎‏‎‎‎‎‎‏‎‏‎‎‏‏‎‎‎‏‎‏‏‎‎Apps‎‏‎‎‏‎"</string>
-    <string name="permission_nearby_device_streaming" msgid="5868108148065023161">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‎‏‎‏‏‎‏‏‏‏‏‎‏‏‎‏‎‏‎‏‎‎‎‏‎‏‎‎‏‎‎‏‎‎‏‏‏‏‏‏‎‏‏‎‏‏‎‏‎‎‏‎‏‏‏‎‎‏‎Nearby Device Streaming‎‏‎‎‏‎"</string>
+    <string name="permission_nearby_device_streaming" msgid="1023325519477349499">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‏‎‎‎‏‏‎‎‏‏‏‎‎‏‎‏‎‏‎‎‏‎‎‎‏‏‎‏‎‏‏‏‏‎‎‎‎‎‏‎‏‎‎‎‎‏‏‏‎‎‎‏‏‏‏‎‏‏‎Streaming‎‏‎‎‏‎"</string>
     <string name="permission_phone_summary" msgid="6684396967861278044">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‎‎‏‏‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‎‏‎‎‎‎‎‎‏‏‏‎‏‏‏‎‎‎‏‎‎‎‏‏‏‎‏‎‏‎‏‎‏‎‏‏‏‎‎‎Can make and manage phone calls‎‏‎‎‏‎"</string>
     <string name="permission_call_logs_summary" msgid="6186103394658755022">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‎‏‏‏‎‏‏‎‎‏‎‏‏‏‎‏‎‎‎‏‎‎‎‎‏‏‎‎‎‏‎‏‏‎‎‏‏‏‏‎‎‏‎‎‏‎‎‎‎‏‏‏‎‎‏‏‏‎‎Can read and write phone call log‎‏‎‎‏‎"</string>
     <string name="permission_sms_summary" msgid="3508442683678912017">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‎‎‏‎‏‏‎‎‎‎‎‏‏‏‏‏‏‎‎‎‎‎‏‎‎‎‎‏‎‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‏‎‏‏‏‎‎‎‎‏‎‎‎‏‎Can send and view SMS messages‎‏‎‎‏‎"</string>
     <string name="permission_contacts_summary" msgid="675861979475628708">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‎‏‎‏‏‎‎‎‎‏‎‎‏‎‎‏‎‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‏‏‏‏‎‏‎‏‏‎‎‏‎‏‎‎‏‎‏‎‏‎‎‏‎‎‎Can access your contacts‎‏‎‎‏‎"</string>
     <string name="permission_calendar_summary" msgid="6460000922511766226">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‎‏‏‎‏‎‎‏‏‎‏‎‎‎‏‎‎‎‏‎‎‏‏‏‎‎‏‏‎‎‏‎‏‏‎‎‎‎‎‎‏‎‎‏‏‏‎‎‏‎‏‏‎‏‎‎‏‎‎Can access your calendar‎‏‎‎‏‎"</string>
-    <string name="permission_microphone_summary" msgid="4241354865859396558">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‏‎‏‏‎‏‏‏‎‎‎‏‎‏‎‎‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‎‏‏‎‎‏‎‎‎‎‎‏‏‏‏‎‎‏‏‏‎‎Can record audio using the microphone‎‏‎‎‏‎"</string>
+    <string name="permission_microphone_summary" msgid="3692091540613093394">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‏‏‎‎‏‏‏‏‎‎‏‏‏‏‎‎‎‏‏‎‏‏‎‎‎‏‏‏‎‏‎‏‎‎‎‏‏‏‎‏‏‏‏‏‎‏‏‎‎‎‎‎‎‏‎‎‏‎‎Can record audio‎‏‎‎‏‎"</string>
     <string name="permission_nearby_devices_summary" msgid="931940524460876655">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‎‎‏‏‏‎‏‏‏‎‏‏‏‎‏‎‏‎‏‏‏‏‏‎‎‎‎‎‏‎‏‎‎‎‎‏‎‎‏‎‎‎‎‎‏‎‎‎‏‏‎‏‏‎‏‏‏‏‎Can find, connect to, and determine the relative position of nearby devices‎‏‎‎‏‎"</string>
     <string name="permission_notification_summary" msgid="884075314530071011">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‎‎‎‏‎‎‎‏‎‎‏‏‎‏‏‏‎‏‏‏‎‏‎‎‎‎‏‎‎‎‎‏‎‏‎‏‎‎‎‎‎‏‏‎‏‏‎‎‎‏‏‏‏‎‎‎‏‏‎Can read all notifications, including information like contacts, messages, and photos‎‏‎‎‏‎"</string>
     <string name="permission_app_streaming_summary" msgid="606923325679670624">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‎‎‎‏‏‎‏‏‎‎‎‎‏‏‏‎‎‏‏‎‎‎‏‏‎‎‏‎‎‏‎‎‎‏‎‎‎‎‏‎‎‏‎‎‎‏‎‏‎‏‎‏‏‎‎‎‎‎‎Stream your phone’s apps‎‏‎‎‏‎"</string>
     <string name="permission_storage_summary" msgid="3918240895519506417"></string>
-    <string name="permission_nearby_device_streaming_summary" msgid="5776807830582725074">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‎‎‎‎‏‎‏‎‏‏‎‏‎‏‏‎‎‎‎‎‎‏‏‏‎‏‎‏‏‏‎‏‎‏‏‎‎‏‎‏‎‎‎‎‎‎‏‎‎‏‏‏‎‏‎‎‏‎‎Stream content to a nearby device‎‏‎‎‏‎"</string>
+    <string name="permission_nearby_device_streaming_summary" msgid="8280824871197081246">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‏‎‏‏‏‎‏‎‏‏‎‏‏‎‎‏‏‎‎‏‎‏‏‎‎‎‏‎‏‏‏‏‏‎‏‏‏‎‏‎‏‏‏‏‏‏‏‏‏‎‏‎‎‏‏‏‏‎‎Stream apps and other system features from your phone‎‏‎‎‏‎"</string>
+    <string name="device_type" product="default" msgid="8268703872070046263">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‏‎‏‏‎‎‎‎‎‎‎‏‎‏‎‏‏‎‎‏‎‏‏‏‎‎‏‎‏‏‎‏‏‏‏‎‏‎‎‎‎‏‏‏‎‏‎‎‏‎‎‎‏‏‎‏‏‏‎phone‎‏‎‎‏‎"</string>
+    <string name="device_type" product="tablet" msgid="5038791954983067774">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‏‎‏‏‏‏‎‏‏‎‏‎‏‏‎‎‎‏‎‏‎‎‏‎‏‏‎‎‎‎‏‏‏‎‏‏‏‎‏‏‏‎‎‏‎‏‎‎‎‎‎‎‏‏‏‏‏‏‎‎tablet‎‏‎‎‏‎"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-es-rUS/strings.xml b/packages/CompanionDeviceManager/res/values-es-rUS/strings.xml
index 85333a9..cba0b46 100644
--- a/packages/CompanionDeviceManager/res/values-es-rUS/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-es-rUS/strings.xml
@@ -17,43 +17,36 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Administrador de dispositivo complementario"</string>
-    <!-- no translation found for confirmation_title (8024993972587946678) -->
-    <skip />
+    <string name="confirmation_title" msgid="2244241995958340998">"¿Quieres permitir que la app de &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; acceda a &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"reloj"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Elige un <xliff:g id="PROFILE_NAME">%1$s</xliff:g> para que la app &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; lo administre"</string>
-    <!-- no translation found for summary_watch (6566922405914995759) -->
-    <skip />
-    <!-- no translation found for summary_watch_single_device (7443464525873186735) -->
-    <skip />
-    <string name="profile_name_glasses" msgid="8488394059007275998">"Gafas"</string>
-    <!-- no translation found for summary_glasses (3808267780579061241) -->
-    <skip />
-    <!-- no translation found for summary_glasses_single_device (7051392780285915640) -->
-    <skip />
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"Elige un dispositivo para que &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; lo administre"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Elige un <xliff:g id="PROFILE_NAME">%1$s</xliff:g> para configurar"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Esta app podrá sincronizar información, como el nombre de alguien cuando te llame, y acceder a los siguientes permisos en tu <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
+    <string name="confirmation_title_glasses" msgid="8288346850537727333">"Permite que &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; administre &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"dispositivo"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Esta app podrá acceder a los siguientes permisos en tu <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Permite que &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; acceda a esta información de tu teléfono"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Servicios multidispositivo"</string>
-    <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> solicita tu permiso en nombre de <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> para transmitir apps entre dispositivos"</string>
+    <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g> solicita tu permiso en nombre de <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> para transmitir apps entre dispositivos"</string>
     <string name="title_automotive_projection" msgid="3296005598978412847"></string>
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"Permite que &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; acceda a esta información de tu teléfono"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Servicios de Google Play"</string>
-    <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> solicita tu permiso en nombre de <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> para acceder a las fotos, el contenido multimedia y las notificaciones de tu teléfono"</string>
-    <string name="title_nearby_device_streaming" msgid="179278282547719200">"Permite que &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; realice esta acción desde tu teléfono"</string>
-    <string name="helper_title_nearby_device_streaming" msgid="6124438217620593669">"Servicios multidispositivo"</string>
-    <string name="helper_summary_nearby_device_streaming" msgid="5538329403511524333">"<xliff:g id="APP_NAME">%1$s</xliff:g> solicita tu permiso en nombre de <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> para transmitir contenido a dispositivos cercanos"</string>
+    <string name="helper_summary_computer" msgid="8774832742608187072">"<xliff:g id="APP_NAME">%1$s</xliff:g> solicita tu permiso en nombre de <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> para acceder a las fotos, el contenido multimedia y las notificaciones de tu teléfono"</string>
+    <string name="title_nearby_device_streaming" msgid="7269956847378799794">"¿Permites que &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; realice esta acción?"</string>
+    <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> está solicitando permiso en nombre de tu <xliff:g id="DEVICE_NAME">%2$s</xliff:g> para transmitir apps y otras funciones del sistema a dispositivos cercanos"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"dispositivo"</string>
-    <!-- no translation found for summary_generic_single_device (4735072202474939111) -->
-    <skip />
-    <!-- no translation found for summary_generic (4988130802522924650) -->
-    <skip />
+    <string name="summary_generic" msgid="1761976003668044801">"Esta app podrá sincronizar información, como el nombre de la persona que llama, entre el teléfono y el dispositivo elegido"</string>
     <string name="consent_yes" msgid="8344487259618762872">"Permitir"</string>
     <string name="consent_no" msgid="2640796915611404382">"No permitir"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Cancelar"</string>
     <string name="consent_back" msgid="2560683030046918882">"Atrás"</string>
+    <string name="permission_expand" msgid="893185038020887411">"Expandir <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
+    <string name="permission_collapse" msgid="3320833884220844084">"Contraer <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"¿Dar a las apps de &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; los mismos permisos que tienen en &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="permission_sync_summary" msgid="765497944331294275">"Esto puede incluir &lt;strong&gt;Micrófono&lt;/strong&gt;, &lt;strong&gt;Cámara&lt;/strong&gt;, y &lt;strong&gt;Acceso a la ubicación&lt;/strong&gt;, así como otros permisos sensibles en &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;. &lt;br/&gt;&lt;br/&gt;Puedes cambiar estos permisos en cualquier momento desde la Configuración de &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;."</string>
-    <string name="vendor_icon_description" msgid="4445875290032225965">"Ícono de la app"</string>
-    <string name="vendor_header_button_description" msgid="6566660389500630608">"Botón Más información"</string>
+    <string name="vendor_header_button_description" msgid="7994879208461111473">"Más información"</string>
     <string name="permission_phone" msgid="2661081078692784919">"Teléfono"</string>
     <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
     <string name="permission_contacts" msgid="3858319347208004438">"Contactos"</string>
@@ -64,16 +57,18 @@
     <string name="permission_storage" msgid="6831099350839392343">"Fotos y contenido multimedia"</string>
     <string name="permission_notification" msgid="693762568127741203">"Notificaciones"</string>
     <string name="permission_app_streaming" msgid="6009695219091526422">"Apps"</string>
-    <string name="permission_nearby_device_streaming" msgid="5868108148065023161">"Transmisión a disp. cercanos"</string>
+    <string name="permission_nearby_device_streaming" msgid="1023325519477349499">"Transmisión"</string>
     <string name="permission_phone_summary" msgid="6684396967861278044">"Puede hacer y administrar llamadas telefónicas"</string>
     <string name="permission_call_logs_summary" msgid="6186103394658755022">"Puede leer y escribir el registro de llamadas telefónicas"</string>
     <string name="permission_sms_summary" msgid="3508442683678912017">"Puede enviar y ver mensajes SMS"</string>
     <string name="permission_contacts_summary" msgid="675861979475628708">"Puede acceder a los contactos"</string>
     <string name="permission_calendar_summary" msgid="6460000922511766226">"Puede acceder al calendario"</string>
-    <string name="permission_microphone_summary" msgid="4241354865859396558">"Puede grabar audio con el micrófono"</string>
+    <string name="permission_microphone_summary" msgid="3692091540613093394">"Puede grabar audio"</string>
     <string name="permission_nearby_devices_summary" msgid="931940524460876655">"Puede encontrar, conectarse con y determinar la ubicación relativa de los dispositivos cercanos"</string>
     <string name="permission_notification_summary" msgid="884075314530071011">"Puede leer todas las notificaciones, incluso con información como contactos, mensajes y fotos"</string>
     <string name="permission_app_streaming_summary" msgid="606923325679670624">"Transmitir las apps de tu teléfono"</string>
     <string name="permission_storage_summary" msgid="3918240895519506417"></string>
-    <string name="permission_nearby_device_streaming_summary" msgid="5776807830582725074">"Transmitir contenido a un dispositivo cercano"</string>
+    <string name="permission_nearby_device_streaming_summary" msgid="8280824871197081246">"Transmite apps y otras funciones del sistema desde tu teléfono"</string>
+    <string name="device_type" product="default" msgid="8268703872070046263">"teléfono"</string>
+    <string name="device_type" product="tablet" msgid="5038791954983067774">"tablet"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-es/strings.xml b/packages/CompanionDeviceManager/res/values-es/strings.xml
index c5127e8..50d8806 100644
--- a/packages/CompanionDeviceManager/res/values-es/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-es/strings.xml
@@ -17,43 +17,36 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Gestor de dispositivos complementario"</string>
-    <!-- no translation found for confirmation_title (8024993972587946678) -->
-    <skip />
+    <string name="confirmation_title" msgid="2244241995958340998">"¿Permitir que la aplicación &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; acceda a &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"reloj"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Elige un <xliff:g id="PROFILE_NAME">%1$s</xliff:g> para gestionarlo con &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <!-- no translation found for summary_watch (6566922405914995759) -->
-    <skip />
-    <!-- no translation found for summary_watch_single_device (7443464525873186735) -->
-    <skip />
-    <string name="profile_name_glasses" msgid="8488394059007275998">"gafas"</string>
-    <!-- no translation found for summary_glasses (3808267780579061241) -->
-    <skip />
-    <!-- no translation found for summary_glasses_single_device (7051392780285915640) -->
-    <skip />
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"Elige un dispositivo para que lo gestione &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Elige el <xliff:g id="PROFILE_NAME">%1$s</xliff:g> que quieras configurar"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Esta aplicación podrá sincronizar información, como el nombre de la persona que llama, y acceder a estos permisos de tu <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
+    <string name="confirmation_title_glasses" msgid="8288346850537727333">"¿Permitir que &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; gestione &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"dispositivo"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Esta aplicación podrá acceder a estos permisos de tu <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Permitir que &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; acceda a esta información de tu teléfono"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Servicios multidispositivo"</string>
-    <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> está pidiendo permiso en nombre de tu <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> para emitir aplicaciones en otros dispositivos tuyos"</string>
+    <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g> está pidiendo permiso en nombre de tu <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> para emitir aplicaciones en otros dispositivos tuyos"</string>
     <string name="title_automotive_projection" msgid="3296005598978412847"></string>
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"Permitir que &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; acceda a esta información de tu teléfono"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Servicios de Google Play"</string>
-    <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> está pidiendo permiso en nombre de tu <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> para acceder a las fotos, los archivos multimedia y las notificaciones de tu teléfono"</string>
-    <string name="title_nearby_device_streaming" msgid="179278282547719200">"Permite que &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; realice esta acción desde tu teléfono"</string>
-    <string name="helper_title_nearby_device_streaming" msgid="6124438217620593669">"Servicios multidispositivo"</string>
-    <string name="helper_summary_nearby_device_streaming" msgid="5538329403511524333">"<xliff:g id="APP_NAME">%1$s</xliff:g> está pidiendo permiso en nombre de tu <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> para reproducir contenido en dispositivos cercanos"</string>
+    <string name="helper_summary_computer" msgid="8774832742608187072">"<xliff:g id="APP_NAME">%1$s</xliff:g> está pidiendo permiso en nombre de tu <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> para acceder a las fotos, los archivos multimedia y las notificaciones de tu teléfono"</string>
+    <string name="title_nearby_device_streaming" msgid="7269956847378799794">"¿Permitir que &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; realice esta acción?"</string>
+    <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> está pidiendo permiso en nombre de tu <xliff:g id="DEVICE_NAME">%2$s</xliff:g> para emitir aplicaciones y otras funciones del sistema en dispositivos cercanos"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"dispositivo"</string>
-    <!-- no translation found for summary_generic_single_device (4735072202474939111) -->
-    <skip />
-    <!-- no translation found for summary_generic (4988130802522924650) -->
-    <skip />
+    <string name="summary_generic" msgid="1761976003668044801">"Esta aplicación podrá sincronizar información (por ejemplo, el nombre de la persona que te llama) entre tu teléfono y el dispositivo que elijas"</string>
     <string name="consent_yes" msgid="8344487259618762872">"Permitir"</string>
     <string name="consent_no" msgid="2640796915611404382">"No permitir"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Cancelar"</string>
     <string name="consent_back" msgid="2560683030046918882">"Atrás"</string>
+    <string name="permission_expand" msgid="893185038020887411">"Desplegar <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
+    <string name="permission_collapse" msgid="3320833884220844084">"Contraer <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"¿Dar a las aplicaciones de &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; los mismos permisos que &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="permission_sync_summary" msgid="765497944331294275">"Esta acción puede dar acceso al &lt;strong&gt;micrófono&lt;/strong&gt;, la &lt;strong&gt;cámara&lt;/strong&gt; y la &lt;strong&gt;ubicación&lt;/strong&gt;, así como a otros permisos sensibles en &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;. &lt;br/&gt;&lt;br/&gt;Puedes cambiar estos permisos cuando quieras en los ajustes de &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;."</string>
-    <string name="vendor_icon_description" msgid="4445875290032225965">"Icono de la aplicación"</string>
-    <string name="vendor_header_button_description" msgid="6566660389500630608">"Botón Más información"</string>
+    <string name="vendor_header_button_description" msgid="7994879208461111473">"Más información"</string>
     <string name="permission_phone" msgid="2661081078692784919">"Teléfono"</string>
     <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
     <string name="permission_contacts" msgid="3858319347208004438">"Contactos"</string>
@@ -64,16 +57,18 @@
     <string name="permission_storage" msgid="6831099350839392343">"Fotos y elementos multimedia"</string>
     <string name="permission_notification" msgid="693762568127741203">"Notificaciones"</string>
     <string name="permission_app_streaming" msgid="6009695219091526422">"Aplicaciones"</string>
-    <string name="permission_nearby_device_streaming" msgid="5868108148065023161">"Streaming en dispositivos cercanos"</string>
+    <string name="permission_nearby_device_streaming" msgid="1023325519477349499">"Emitir"</string>
     <string name="permission_phone_summary" msgid="6684396967861278044">"Puede hacer y gestionar llamadas"</string>
     <string name="permission_call_logs_summary" msgid="6186103394658755022">"Puede leer y escribir en el registro de llamadas del teléfono"</string>
     <string name="permission_sms_summary" msgid="3508442683678912017">"Puede enviar y ver mensajes SMS"</string>
     <string name="permission_contacts_summary" msgid="675861979475628708">"Puede acceder a tus contactos"</string>
     <string name="permission_calendar_summary" msgid="6460000922511766226">"Puede acceder a tu calendario"</string>
-    <string name="permission_microphone_summary" msgid="4241354865859396558">"Puede grabar audio usando el micrófono"</string>
+    <string name="permission_microphone_summary" msgid="3692091540613093394">"Puede grabar audio"</string>
     <string name="permission_nearby_devices_summary" msgid="931940524460876655">"Puede buscar, conectarse y determinar la posición relativa de dispositivos cercanos"</string>
     <string name="permission_notification_summary" msgid="884075314530071011">"Puede leer todas las notificaciones, incluida información como contactos, mensajes y fotos"</string>
     <string name="permission_app_streaming_summary" msgid="606923325679670624">"Muestra en streaming las aplicaciones de tu teléfono"</string>
     <string name="permission_storage_summary" msgid="3918240895519506417"></string>
-    <string name="permission_nearby_device_streaming_summary" msgid="5776807830582725074">"Reproduce contenido en un dispositivo cercano"</string>
+    <string name="permission_nearby_device_streaming_summary" msgid="8280824871197081246">"Emite aplicaciones y otras funciones del sistema desde tu teléfono"</string>
+    <string name="device_type" product="default" msgid="8268703872070046263">"teléfono"</string>
+    <string name="device_type" product="tablet" msgid="5038791954983067774">"tablet"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-et/strings.xml b/packages/CompanionDeviceManager/res/values-et/strings.xml
index cd362de..2eff7eb 100644
--- a/packages/CompanionDeviceManager/res/values-et/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-et/strings.xml
@@ -17,43 +17,36 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Kaasseadme haldur"</string>
-    <!-- no translation found for confirmation_title (8024993972587946678) -->
-    <skip />
+    <string name="confirmation_title" msgid="2244241995958340998">"Kas anda rakendusele &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; juurdepääs seadmele &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"käekell"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Valige <xliff:g id="PROFILE_NAME">%1$s</xliff:g>, mida haldab rakendus &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <!-- no translation found for summary_watch (6566922405914995759) -->
-    <skip />
-    <!-- no translation found for summary_watch_single_device (7443464525873186735) -->
-    <skip />
-    <string name="profile_name_glasses" msgid="8488394059007275998">"prillid"</string>
-    <!-- no translation found for summary_glasses (3808267780579061241) -->
-    <skip />
-    <!-- no translation found for summary_glasses_single_device (7051392780285915640) -->
-    <skip />
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"Valige seade, mida haldab rakendus &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Valige <xliff:g id="PROFILE_NAME">%1$s</xliff:g>, mis seadistada"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Sellel rakendusel lubatakse sünkroonida teavet (nt helistaja nime) ja antakse need load teie seadmes <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
+    <string name="confirmation_title_glasses" msgid="8288346850537727333">"Lubage rakendusel &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; hallata seadet &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"seade"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Sellele rakendusele antakse need load teie seadmes <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Lubage rakendusel &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; pääseda teie telefonis juurde sellele teabele"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Seadmeülesed teenused"</string>
-    <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> taotleb teie seadme <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> nimel luba teie seadmete vahel rakendusi voogesitada"</string>
+    <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g> taotleb teie seadme <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> nimel luba teie seadmete vahel rakendusi voogesitada"</string>
     <string name="title_automotive_projection" msgid="3296005598978412847"></string>
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"Lubage rakendusel &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; pääseda teie telefonis juurde sellele teabele"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Google Play teenused"</string>
-    <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> taotleb teie seadme <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> nimel luba pääseda juurde telefoni fotodele, meediale ja märguannetele"</string>
-    <string name="title_nearby_device_streaming" msgid="179278282547719200">"Lubage rakendusel &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; teha teie telefonis seda toimingut"</string>
-    <string name="helper_title_nearby_device_streaming" msgid="6124438217620593669">"Seadmeülesed teenused"</string>
-    <string name="helper_summary_nearby_device_streaming" msgid="5538329403511524333">"<xliff:g id="APP_NAME">%1$s</xliff:g> taotleb teie seadme <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> nimel luba voogesitada sisu läheduses olevates seadmetes"</string>
+    <string name="helper_summary_computer" msgid="8774832742608187072">"<xliff:g id="APP_NAME">%1$s</xliff:g> taotleb teie seadme <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> nimel luba pääseda juurde telefoni fotodele, meediale ja märguannetele"</string>
+    <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Kas lubada seadmel &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; teha seda toimingut?"</string>
+    <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> taotleb teie seadme <xliff:g id="DEVICE_NAME">%2$s</xliff:g> nimel luba voogesitada rakendusi ja muid süsteemi funktsioone läheduses olevatesse seadmetesse"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"seade"</string>
-    <!-- no translation found for summary_generic_single_device (4735072202474939111) -->
-    <skip />
-    <!-- no translation found for summary_generic (4988130802522924650) -->
-    <skip />
+    <string name="summary_generic" msgid="1761976003668044801">"See rakendus saab sünkroonida teavet, näiteks helistaja nime, teie telefoni ja valitud seadme vahel"</string>
     <string name="consent_yes" msgid="8344487259618762872">"Luba"</string>
     <string name="consent_no" msgid="2640796915611404382">"Ära luba"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Tühista"</string>
     <string name="consent_back" msgid="2560683030046918882">"Tagasi"</string>
+    <string name="permission_expand" msgid="893185038020887411">"Laienda: <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
+    <string name="permission_collapse" msgid="3320833884220844084">"Ahenda: <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Kas anda rakendustele seadmes &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; samad load, mis seadmes &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="permission_sync_summary" msgid="765497944331294275">"See võib hõlmata &lt;strong&gt;mikrofoni&lt;/strong&gt;, &lt;strong&gt;kaamerat&lt;/strong&gt; ja &lt;strong&gt;juurdepääsu asukohale&lt;/strong&gt; ning muid tundlikke lube seadmes &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;. &lt;br/&gt;&lt;br/&gt;Võite neid lube seadme &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt; seadetes igal ajal muuta."</string>
-    <string name="vendor_icon_description" msgid="4445875290032225965">"Rakenduse ikoon"</string>
-    <string name="vendor_header_button_description" msgid="6566660389500630608">"Nupp Lisateave"</string>
+    <string name="vendor_header_button_description" msgid="7994879208461111473">"Lisateave"</string>
     <string name="permission_phone" msgid="2661081078692784919">"Telefon"</string>
     <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
     <string name="permission_contacts" msgid="3858319347208004438">"Kontaktid"</string>
@@ -64,16 +57,18 @@
     <string name="permission_storage" msgid="6831099350839392343">"Fotod ja meedia"</string>
     <string name="permission_notification" msgid="693762568127741203">"Märguanded"</string>
     <string name="permission_app_streaming" msgid="6009695219091526422">"Rakendused"</string>
-    <string name="permission_nearby_device_streaming" msgid="5868108148065023161">"Läheduses olevas seadmes esit."</string>
+    <string name="permission_nearby_device_streaming" msgid="1023325519477349499">"Voogesitus"</string>
     <string name="permission_phone_summary" msgid="6684396967861278044">"Saab teha ja hallata telefonikõnesid"</string>
     <string name="permission_call_logs_summary" msgid="6186103394658755022">"Saab telefoni kõnelogi lugeda ja sinna kirjutada"</string>
     <string name="permission_sms_summary" msgid="3508442683678912017">"Saab saata ja vaadata SMS-sõnumeid"</string>
     <string name="permission_contacts_summary" msgid="675861979475628708">"Pääseb juurde teie kontaktidele"</string>
     <string name="permission_calendar_summary" msgid="6460000922511766226">"Pääseb juurde teie kalendrile"</string>
-    <string name="permission_microphone_summary" msgid="4241354865859396558">"Saab mikrofoni abil heli salvestada"</string>
+    <string name="permission_microphone_summary" msgid="3692091540613093394">"Saab salvestada heli"</string>
     <string name="permission_nearby_devices_summary" msgid="931940524460876655">"Leiab läheduses olevaid seadmeid, saab nendega ühenduse luua ja määrata nende suhtelise asendi"</string>
     <string name="permission_notification_summary" msgid="884075314530071011">"Kõikide märguannete, sealhulgas teabe, nagu kontaktid, sõnumid ja fotod, lugemine"</string>
     <string name="permission_app_streaming_summary" msgid="606923325679670624">"Telefoni rakenduste voogesitamine"</string>
     <string name="permission_storage_summary" msgid="3918240895519506417"></string>
-    <string name="permission_nearby_device_streaming_summary" msgid="5776807830582725074">"Sisu voogesitamine läheduses olevas seadmes"</string>
+    <string name="permission_nearby_device_streaming_summary" msgid="8280824871197081246">"Rakenduste ja muude süsteemi funktsioonide voogesitamine teie telefonist"</string>
+    <string name="device_type" product="default" msgid="8268703872070046263">"telefon"</string>
+    <string name="device_type" product="tablet" msgid="5038791954983067774">"tahvelarvuti"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-eu/strings.xml b/packages/CompanionDeviceManager/res/values-eu/strings.xml
index d2a7d98..e130074 100644
--- a/packages/CompanionDeviceManager/res/values-eu/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-eu/strings.xml
@@ -17,43 +17,36 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Gailu osagarriaren kudeatzailea"</string>
-    <!-- no translation found for confirmation_title (8024993972587946678) -->
-    <skip />
+    <string name="confirmation_title" msgid="2244241995958340998">"&lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; gailua erabiltzeko baimena eman nahi diozu &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; aplikazioari?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"erlojua"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Aukeratu &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; aplikazioak kudeatu beharreko <xliff:g id="PROFILE_NAME">%1$s</xliff:g>"</string>
-    <!-- no translation found for summary_watch (6566922405914995759) -->
-    <skip />
-    <!-- no translation found for summary_watch_single_device (7443464525873186735) -->
-    <skip />
-    <string name="profile_name_glasses" msgid="8488394059007275998">"betaurrekoak"</string>
-    <!-- no translation found for summary_glasses (3808267780579061241) -->
-    <skip />
-    <!-- no translation found for summary_glasses_single_device (7051392780285915640) -->
-    <skip />
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"Aukeratu &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; aplikazioak kudeatu behar duen gailua"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Aukeratu konfiguratu nahi duzun <xliff:g id="PROFILE_NAME">%1$s</xliff:g>"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Informazioa sinkronizatu (esate baterako, deitzaileen izenak) eta baimen hauek erabili ahalko ditu <xliff:g id="DEVICE_NAME">%1$s</xliff:g>n aplikazioak"</string>
+    <string name="confirmation_title_glasses" msgid="8288346850537727333">"&lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; kudeatzeko baimena eman nahi diozu &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; aplikazioari?"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"gailua"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Baimen hauek erabili ahalko ditu <xliff:g id="DEVICE_NAME">%1$s</xliff:g>n aplikazioak:"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Eman informazioa telefonotik hartzeko baimena &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; aplikazioari"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Gailu baterako baino gehiagotarako zerbitzuak"</string>
-    <string name="helper_summary_app_streaming" msgid="5977509499890099">"Gailu batetik bestera aplikazioak igortzeko baimena eskatzen ari da <xliff:g id="APP_NAME">%1$s</xliff:g>, <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> gailuaren izenean"</string>
+    <string name="helper_summary_app_streaming" msgid="2396773196949578425">"Gailu batetik bestera aplikazioak igortzeko baimena eskatzen ari da <xliff:g id="APP_NAME">%1$s</xliff:g>, <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> gailuaren izenean"</string>
     <string name="title_automotive_projection" msgid="3296005598978412847"></string>
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"Eman telefonoko informazio hau erabiltzeko baimena &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; aplikazioari"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Google Play Services"</string>
-    <string name="helper_summary_computer" msgid="9050724687678157852">"Telefonoko argazkiak, multimedia-edukia eta jakinarazpenak erabiltzeko baimena eskatzen ari da <xliff:g id="APP_NAME">%1$s</xliff:g>, <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> gailuaren izenean"</string>
-    <string name="title_nearby_device_streaming" msgid="179278282547719200">"Eman telefonotik ekintza hau gauzatzeko baimena &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; aplikazioari"</string>
-    <string name="helper_title_nearby_device_streaming" msgid="6124438217620593669">"Gailu baterako baino gehiagotarako zerbitzuak"</string>
-    <string name="helper_summary_nearby_device_streaming" msgid="5538329403511524333">"Inguruko gailuetara edukia igortzeko baimena eskatzen ari da <xliff:g id="APP_NAME">%1$s</xliff:g>, <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> gailuaren izenean"</string>
+    <string name="helper_summary_computer" msgid="8774832742608187072">"Telefonoko argazkiak, multimedia-edukia eta jakinarazpenak erabiltzeko baimena eskatzen ari da <xliff:g id="APP_NAME">%1$s</xliff:g>, <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> gailuaren izenean"</string>
+    <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Ekintza hau gauzatzeko baimena eman nahi diozu &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; aplikazioari?"</string>
+    <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"Aplikazioak eta sistemaren beste eginbide batzuk inguruko gailuetara igortzeko baimena eskatzen ari da <xliff:g id="APP_NAME">%1$s</xliff:g>, <xliff:g id="DEVICE_NAME">%2$s</xliff:g> gailuaren izenean"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"gailua"</string>
-    <!-- no translation found for summary_generic_single_device (4735072202474939111) -->
-    <skip />
-    <!-- no translation found for summary_generic (4988130802522924650) -->
-    <skip />
+    <string name="summary_generic" msgid="1761976003668044801">"Telefonoaren eta hautatutako gailuaren artean informazioa sinkronizatzeko gai izango da aplikazioa (esate baterako, deitzaileen izenak)"</string>
     <string name="consent_yes" msgid="8344487259618762872">"Eman baimena"</string>
     <string name="consent_no" msgid="2640796915611404382">"Ez eman baimenik"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Utzi"</string>
     <string name="consent_back" msgid="2560683030046918882">"Atzera"</string>
+    <string name="permission_expand" msgid="893185038020887411">"Zabaldu <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
+    <string name="permission_collapse" msgid="3320833884220844084">"Tolestu <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"&lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; gailuan dituzten baimen berberak eman nahi dizkiezu &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; gailuko aplikazioei?"</string>
     <string name="permission_sync_summary" msgid="765497944331294275">"Baliteke &lt;strong&gt;mikrofonoa&lt;/strong&gt;, &lt;strong&gt;kamera&lt;/strong&gt; eta &lt;strong&gt;kokapena&lt;/strong&gt; erabiltzeko baimenak barne hartzea, baita kontuzko informazioa erabiltzeko &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt; gailuko beste baimen batzuk ere. &lt;br/&gt;&lt;br/&gt;Baimen horiek aldatzeko, joan &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt; gailuaren ezarpenetara."</string>
-    <string name="vendor_icon_description" msgid="4445875290032225965">"Aplikazioaren ikonoa"</string>
-    <string name="vendor_header_button_description" msgid="6566660389500630608">"Informazio gehiagorako botoia"</string>
+    <string name="vendor_header_button_description" msgid="7994879208461111473">"Informazio gehiago"</string>
     <string name="permission_phone" msgid="2661081078692784919">"Telefonoa"</string>
     <string name="permission_sms" msgid="6337141296535774786">"SMSak"</string>
     <string name="permission_contacts" msgid="3858319347208004438">"Kontaktuak"</string>
@@ -64,16 +57,18 @@
     <string name="permission_storage" msgid="6831099350839392343">"Argazkiak eta multimedia-edukia"</string>
     <string name="permission_notification" msgid="693762568127741203">"Jakinarazpenak"</string>
     <string name="permission_app_streaming" msgid="6009695219091526422">"Aplikazioak"</string>
-    <string name="permission_nearby_device_streaming" msgid="5868108148065023161">"Inguruko gailuetara igortzeko baimena"</string>
+    <string name="permission_nearby_device_streaming" msgid="1023325519477349499">"Igortzea"</string>
     <string name="permission_phone_summary" msgid="6684396967861278044">"Telefono-deiak egin eta kudea ditzake"</string>
     <string name="permission_call_logs_summary" msgid="6186103394658755022">"Telefonoko deien erregistroa irakurri, eta bertan idatz dezake"</string>
     <string name="permission_sms_summary" msgid="3508442683678912017">"SMS mezuak bidali eta ikus ditzake"</string>
     <string name="permission_contacts_summary" msgid="675861979475628708">"Kontaktuak atzi ditzake"</string>
     <string name="permission_calendar_summary" msgid="6460000922511766226">"Egutegia atzi dezake"</string>
-    <string name="permission_microphone_summary" msgid="4241354865859396558">"Audioa graba dezake mikrofonoa erabilita"</string>
+    <string name="permission_microphone_summary" msgid="3692091540613093394">"Audioa graba dezake"</string>
     <string name="permission_nearby_devices_summary" msgid="931940524460876655">"Inguruko gailuak aurki ditzake, haietara konekta daiteke eta haien posizio erlatiboa zehatz dezake"</string>
     <string name="permission_notification_summary" msgid="884075314530071011">"Jakinarazpen guztiak irakur ditzake; besteak beste, kontaktuak, mezuak, argazkiak eta antzeko informazioa"</string>
     <string name="permission_app_streaming_summary" msgid="606923325679670624">"Igorri zuzenean telefonoko aplikazioak"</string>
     <string name="permission_storage_summary" msgid="3918240895519506417"></string>
-    <string name="permission_nearby_device_streaming_summary" msgid="5776807830582725074">"Inguruko gailu batera edukia igor dezake"</string>
+    <string name="permission_nearby_device_streaming_summary" msgid="8280824871197081246">"Igorri aplikazioak eta sistemaren beste eginbide batzuk telefonotik"</string>
+    <string name="device_type" product="default" msgid="8268703872070046263">"Telefonoa"</string>
+    <string name="device_type" product="tablet" msgid="5038791954983067774">"Tableta"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-fa/strings.xml b/packages/CompanionDeviceManager/res/values-fa/strings.xml
index 1d283f0..5e78aa5 100644
--- a/packages/CompanionDeviceManager/res/values-fa/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-fa/strings.xml
@@ -17,43 +17,36 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"مدیر دستگاه مرتبط"</string>
-    <!-- no translation found for confirmation_title (8024993972587946678) -->
-    <skip />
+    <string name="confirmation_title" msgid="2244241995958340998">"‏به برنامه &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; اجازه داده شود به &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; دسترسی پیدا کند؟"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"ساعت"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"‏انتخاب <xliff:g id="PROFILE_NAME">%1$s</xliff:g> برای مدیریت کردن با &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>‏&lt;/strong&gt;"</string>
-    <!-- no translation found for summary_watch (6566922405914995759) -->
-    <skip />
-    <!-- no translation found for summary_watch_single_device (7443464525873186735) -->
-    <skip />
-    <string name="profile_name_glasses" msgid="8488394059007275998">"عینک"</string>
-    <!-- no translation found for summary_glasses (3808267780579061241) -->
-    <skip />
-    <!-- no translation found for summary_glasses_single_device (7051392780285915640) -->
-    <skip />
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"‏انتخاب دستگاه برای مدیریت کردن با &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"انتخاب <xliff:g id="PROFILE_NAME">%1$s</xliff:g> برای راه‌اندازی"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"به این برنامه اجازه داده می‌شود اطلاعاتی مثل نام تماس‌گیرنده را همگام‌سازی کند و به این اجازه‌ها در <xliff:g id="DEVICE_NAME">%1$s</xliff:g> شما دسترسی داشته باشد"</string>
+    <string name="confirmation_title_glasses" msgid="8288346850537727333">"‏به &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; اجازه داده شود &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; را مدیریت کند؟"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"دستگاه"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"این برنامه مجاز می‌شود به این اجازه‌ها در <xliff:g id="DEVICE_NAME">%1$s</xliff:g> شما دسترسی پیدا کند"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"‏اجازه دادن به &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; برای دسترسی به اطلاعات تلفن"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"سرویس‌های بین‌دستگاهی"</string>
-    <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> اجازه می‌خواهد ازطرف <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> برنامه‌ها را بین دستگاه‌های شما جاری‌سازی کند"</string>
+    <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g> ازطرف <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> اجازه می‌خواهد برنامه‌ها را بین دستگاه‌های شما جاری‌سازی کند"</string>
     <string name="title_automotive_projection" msgid="3296005598978412847"></string>
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"‏به &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; اجازه دسترسی به این اطلاعات در دستگاهتان داده شود"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"‏خدمات Google Play"</string>
-    <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> اجازه می‌خواهد ازطرف <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> به عکس‌ها، رسانه‌ها، و اعلان‌های تلفن شما دسترسی پیدا کند"</string>
-    <string name="title_nearby_device_streaming" msgid="179278282547719200">"‏به &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; اجازه داده می‌شود این کنش را در تلفنتان اجرا کند"</string>
-    <string name="helper_title_nearby_device_streaming" msgid="6124438217620593669">"سرویس‌های بین‌دستگاهی"</string>
-    <string name="helper_summary_nearby_device_streaming" msgid="5538329403511524333">"<xliff:g id="APP_NAME">%1$s</xliff:g> ازطرف <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> اجازه می‌خواهد تا در دستگاه‌های اطراف محتوا جاری‌سازی کند."</string>
+    <string name="helper_summary_computer" msgid="8774832742608187072">"<xliff:g id="APP_NAME">%1$s</xliff:g> ازطرف <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> اجازه می‌خواهد به عکس‌ها، رسانه‌ها، و اعلان‌های تلفن شما دسترسی پیدا کند"</string>
+    <string name="title_nearby_device_streaming" msgid="7269956847378799794">"‏به &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; اجازه داده شود این اقدام را انجام دهد؟"</string>
+    <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> ازطرف <xliff:g id="DEVICE_NAME">%2$s</xliff:g> اجازه می‌خواهد تا برنامه‌ها و دیگر ویژگی‌های سیستم را در دستگاه‌های اطراف جاری‌سازی کند."</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"دستگاه"</string>
-    <!-- no translation found for summary_generic_single_device (4735072202474939111) -->
-    <skip />
-    <!-- no translation found for summary_generic (4988130802522924650) -->
-    <skip />
+    <string name="summary_generic" msgid="1761976003668044801">"این برنامه مجاز می‌شود اطلاعتی مثل نام شخصی را که تماس می‌گیرد بین تلفن شما و دستگاه انتخاب‌شده همگام‌سازی کند"</string>
     <string name="consent_yes" msgid="8344487259618762872">"اجازه دادن"</string>
     <string name="consent_no" msgid="2640796915611404382">"اجازه ندادن"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"لغو"</string>
     <string name="consent_back" msgid="2560683030046918882">"برگشتن"</string>
+    <string name="permission_expand" msgid="893185038020887411">"ازهم بازکردن <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
+    <string name="permission_collapse" msgid="3320833884220844084">"جمع کردن <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"‏به برنامه‌های موجود در &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; همان اجازه‌های &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; داده شود؟"</string>
     <string name="permission_sync_summary" msgid="765497944331294275">"‏این مورد ممکن است شامل دسترسی به &lt;strong&gt;میکروفون&lt;/strong&gt;، &lt;strong&gt;دوربین&lt;/strong&gt;، و &lt;strong&gt;مکان&lt;/strong&gt;، و دیگر اجازه‌های حساس در &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt; شود. &lt;br/&gt;&lt;br/&gt;هر زمان خواستید می‌توانید این اجازه‌ها را در «تنظیمات» &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt; تغییر دهید."</string>
-    <string name="vendor_icon_description" msgid="4445875290032225965">"نماد برنامه"</string>
-    <string name="vendor_header_button_description" msgid="6566660389500630608">"دکمه اطلاعات بیشتر"</string>
+    <string name="vendor_header_button_description" msgid="7994879208461111473">"اطلاعات بیشتر"</string>
     <string name="permission_phone" msgid="2661081078692784919">"تلفن"</string>
     <string name="permission_sms" msgid="6337141296535774786">"پیامک"</string>
     <string name="permission_contacts" msgid="3858319347208004438">"مخاطبین"</string>
@@ -64,16 +57,18 @@
     <string name="permission_storage" msgid="6831099350839392343">"عکس‌ها و رسانه‌ها"</string>
     <string name="permission_notification" msgid="693762568127741203">"اعلان‌ها"</string>
     <string name="permission_app_streaming" msgid="6009695219091526422">"برنامه‌ها"</string>
-    <string name="permission_nearby_device_streaming" msgid="5868108148065023161">"جاری‌سازی دستگاه‌های اطراف"</string>
+    <string name="permission_nearby_device_streaming" msgid="1023325519477349499">"جاری‌سازی"</string>
     <string name="permission_phone_summary" msgid="6684396967861278044">"می‌تواند تماس تلفنی برقرار کند و این تماس‌ها را مدیریت کند"</string>
     <string name="permission_call_logs_summary" msgid="6186103394658755022">"می‌تواند گزارش تماس‌های تلفنی را بنویسد و بخواند"</string>
     <string name="permission_sms_summary" msgid="3508442683678912017">"می‌تواند پیامک ارسال کند و متن پیامک را مشاهده کند"</string>
     <string name="permission_contacts_summary" msgid="675861979475628708">"می‌تواند به مخاطبین شما دسترسی داشته باشد"</string>
     <string name="permission_calendar_summary" msgid="6460000922511766226">"می‌تواند به تقویم شما دسترسی داشته باشد"</string>
-    <string name="permission_microphone_summary" msgid="4241354865859396558">"می‌تواند بااستفاده از میکروفون صدا ضبط کند"</string>
+    <string name="permission_microphone_summary" msgid="3692091540613093394">"می‌تواند صدا ضبط کند"</string>
     <string name="permission_nearby_devices_summary" msgid="931940524460876655">"می‌تواند دستگاه‌های اطراف را پیدا کند، به آن‌ها متصل شود، و موقعیت نسبی آن‌ها را تعیین کند"</string>
     <string name="permission_notification_summary" msgid="884075314530071011">"می‌تواند همه اعلان‌ها، ازجمله اطلاعاتی مثل مخاطبین، پیام‌ها، و عکس‌ها را بخواند"</string>
     <string name="permission_app_streaming_summary" msgid="606923325679670624">"جاری‌سازی برنامه‌های تلفن"</string>
     <string name="permission_storage_summary" msgid="3918240895519506417"></string>
-    <string name="permission_nearby_device_streaming_summary" msgid="5776807830582725074">"جاری‌سازی کردن محتوا در دستگاه‌های اطراف"</string>
+    <string name="permission_nearby_device_streaming_summary" msgid="8280824871197081246">"برنامه‌ها و دیگر ویژگی‌های سیستم را از تلفن شما جاری‌سازی می‌کند"</string>
+    <string name="device_type" product="default" msgid="8268703872070046263">"تلفن"</string>
+    <string name="device_type" product="tablet" msgid="5038791954983067774">"رایانه لوحی"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-fi/strings.xml b/packages/CompanionDeviceManager/res/values-fi/strings.xml
index 9f5c57f..552c473 100644
--- a/packages/CompanionDeviceManager/res/values-fi/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-fi/strings.xml
@@ -17,36 +17,36 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Companion Device Manager"</string>
-    <string name="confirmation_title" msgid="8024993972587946678">"Salli, että &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; saa pääsyn laitteeseen: &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"Sallitaanko, että soellus (&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;) saa pääsyn laitteeseen: &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"kello"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Valitse <xliff:g id="PROFILE_NAME">%1$s</xliff:g>, jota &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; hallinnoi"</string>
-    <string name="summary_watch" msgid="6566922405914995759">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> edellyttää ylläpitoon tätä sovellusta. <xliff:g id="APP_NAME">%2$s</xliff:g> saa luvan synkronoida tietoja (esimerkiksi soittajan nimen), hallinnoida ilmoituksiasi sekä pääsyn puhelimeen, tekstiviesteihin, yhteystietoihin, kalenteriin, puhelulokeihin ja lähellä olevat laitteet ‑lupiin."</string>
-    <string name="summary_watch_single_device" msgid="7443464525873186735">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> edellyttää ylläpitoon tätä sovellusta. <xliff:g id="APP_NAME">%2$s</xliff:g> saa luvan synkronoida tietoja (esimerkiksi soittajan nimen) ja pääsyn näihin lupiin:"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"lasit"</string>
-    <string name="summary_glasses" msgid="3808267780579061241">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> edellyttää ylläpitoon tätä sovellusta. <xliff:g id="APP_NAME">%2$s</xliff:g> saa luvan hallinnoida ilmoituksiasi sekä pääsyn puhelimeen, tekstiviesteihin, yhteystietoihin, mikrofoniin ja lähellä olevat laitteet ‑lupiin."</string>
-    <string name="summary_glasses_single_device" msgid="7051392780285915640">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> edellyttää ylläpitoon tätä sovellusta. <xliff:g id="APP_NAME">%2$s</xliff:g> saa käyttää näitä lupia:"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"Valitse laite, jota &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; hallinnoi"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Valitse <xliff:g id="PROFILE_NAME">%1$s</xliff:g>, niin voit suorittaa käyttöönoton"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Sovellus saa luvan synkronoida tietoja (esimerkiksi soittajan nimen) ja pääsyn näihin lupiin laitteella (<xliff:g id="DEVICE_NAME">%1$s</xliff:g>)"</string>
+    <string name="confirmation_title_glasses" msgid="8288346850537727333">"Salli, että &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; saa ylläpitää laitetta: &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"laite"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Tämä sovellus saa käyttää näitä lupia laitteella (<xliff:g id="DEVICE_NAME">%1$s</xliff:g>)"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Salli, että &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; saa pääsyn näihin puhelimesi tietoihin"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Laitteidenväliset palvelut"</string>
-    <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> pyytää laitteesi (<xliff:g id="DEVICE_TYPE">%2$s</xliff:g>) puolesta lupaa striimata sovelluksia laitteidesi välillä"</string>
+    <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g> pyytää laitteesi (<xliff:g id="DISPLAY_NAME">%2$s</xliff:g>) puolesta lupaa striimata sovelluksia laitteidesi välillä"</string>
     <string name="title_automotive_projection" msgid="3296005598978412847"></string>
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"Salli pääsy tähän tietoon puhelimellasi: &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Google Play Palvelut"</string>
-    <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> pyytää laitteesi (<xliff:g id="DEVICE_TYPE">%2$s</xliff:g>) puolesta lupaa päästä puhelimesi kuviin, mediaan ja ilmoituksiin"</string>
-    <string name="title_nearby_device_streaming" msgid="179278282547719200">"Myönnä lupa suorittaa tämä toiminto puhelimeltasi: &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="helper_title_nearby_device_streaming" msgid="6124438217620593669">"Laitteidenväliset palvelut"</string>
-    <string name="helper_summary_nearby_device_streaming" msgid="5538329403511524333">"<xliff:g id="APP_NAME">%1$s</xliff:g> pyytää laitteesi (<xliff:g id="DEVICE_TYPE">%2$s</xliff:g>) puolesta lupaa striimata sisältöä lähellä oleviin laitteisiin"</string>
+    <string name="helper_summary_computer" msgid="8774832742608187072">"<xliff:g id="APP_NAME">%1$s</xliff:g> pyytää laitteesi (<xliff:g id="DISPLAY_NAME">%2$s</xliff:g>) puolesta lupaa päästä puhelimesi kuviin, mediaan ja ilmoituksiin"</string>
+    <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Sallitko, että &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; voi suorittaa tämän toiminnon?"</string>
+    <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> pyytää laitteesi (<xliff:g id="DEVICE_NAME">%2$s</xliff:g>) puolesta lupaa striimata sovelluksia ja muita järjestelmän ominaisuuksia lähellä oleviin laitteisiin."</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"laite"</string>
-    <string name="summary_generic_single_device" msgid="4735072202474939111">"Sovellus voi synkronoida tietoja (esimerkiksi soittajan nimen) puhelimesi ja laitteen (<xliff:g id="DEVICE_NAME">%1$s</xliff:g>) välillä."</string>
-    <string name="summary_generic" msgid="4988130802522924650">"Sovellus voi synkronoida tietoja (esimerkiksi soittajan nimen) puhelimesi ja valitun laitteen välillä."</string>
+    <string name="summary_generic" msgid="1761976003668044801">"Sovellus voi synkronoida tietoja (esimerkiksi soittajan nimen) puhelimesi ja valitun laitteen välillä"</string>
     <string name="consent_yes" msgid="8344487259618762872">"Salli"</string>
     <string name="consent_no" msgid="2640796915611404382">"Älä salli"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Peruuta"</string>
     <string name="consent_back" msgid="2560683030046918882">"Takaisin"</string>
+    <string name="permission_expand" msgid="893185038020887411">"Laajenna <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
+    <string name="permission_collapse" msgid="3320833884220844084">"Tiivistä <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Anna laitteen &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; sovelluksille samat luvat kuin laitteella &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="permission_sync_summary" msgid="765497944331294275">"Tähän voi kuulua pääsy &lt;strong&gt;mikrofoniin&lt;/strong&gt;, &lt;strong&gt;kameraan&lt;/strong&gt;, ja &lt;strong&gt;sijaintiin &lt;/strong&gt;, ja muihin arkaluontoisiin lupiin laitteella&lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;. &lt;br/&gt;&lt;br/&gt;Voit muuttaa lupia milloin tahansa laitteen &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt; asetuksissa."</string>
-    <string name="vendor_icon_description" msgid="4445875290032225965">"Sovelluskuvake"</string>
-    <string name="vendor_header_button_description" msgid="6566660389500630608">"Lisätietopainike"</string>
+    <string name="vendor_header_button_description" msgid="7994879208461111473">"Lisätietoa"</string>
     <string name="permission_phone" msgid="2661081078692784919">"Puhelin"</string>
     <string name="permission_sms" msgid="6337141296535774786">"Tekstiviesti"</string>
     <string name="permission_contacts" msgid="3858319347208004438">"Yhteystiedot"</string>
@@ -57,16 +57,18 @@
     <string name="permission_storage" msgid="6831099350839392343">"Kuvat ja media"</string>
     <string name="permission_notification" msgid="693762568127741203">"Ilmoitukset"</string>
     <string name="permission_app_streaming" msgid="6009695219091526422">"Sovellukset"</string>
-    <string name="permission_nearby_device_streaming" msgid="5868108148065023161">"Striimaus muille laitteille"</string>
+    <string name="permission_nearby_device_streaming" msgid="1023325519477349499">"Striimaus"</string>
     <string name="permission_phone_summary" msgid="6684396967861278044">"Voi soittaa ja hallinnoida puheluita"</string>
     <string name="permission_call_logs_summary" msgid="6186103394658755022">"Voi lukea puhelulokia ja kirjoittaa siihen"</string>
     <string name="permission_sms_summary" msgid="3508442683678912017">"Voi lähettää ja nähdä tekstiviestejä"</string>
     <string name="permission_contacts_summary" msgid="675861979475628708">"Voi nähdä yhteystietosi"</string>
     <string name="permission_calendar_summary" msgid="6460000922511766226">"Voi nähdä kalenterisi"</string>
-    <string name="permission_microphone_summary" msgid="4241354865859396558">"Voi tallentaa audiota mikrofonilla"</string>
+    <string name="permission_microphone_summary" msgid="3692091540613093394">"Voi tallentaa audiota"</string>
     <string name="permission_nearby_devices_summary" msgid="931940524460876655">"Voi löytää lähellä olevia laitteita, muodostaa niihin yhteyden ja määrittää niiden suhteellisen sijainnin"</string>
     <string name="permission_notification_summary" msgid="884075314530071011">"Voi lukea kaikkia ilmoituksia, esim. kontakteihin, viesteihin ja kuviin liittyviä tietoja"</string>
     <string name="permission_app_streaming_summary" msgid="606923325679670624">"Striimaa puhelimen sovelluksia"</string>
     <string name="permission_storage_summary" msgid="3918240895519506417"></string>
-    <string name="permission_nearby_device_streaming_summary" msgid="5776807830582725074">"Striimaa sisältöä lähellä olevalle laitteelle"</string>
+    <string name="permission_nearby_device_streaming_summary" msgid="8280824871197081246">"Striimaa sovelluksia ja muita järjestelmän ominaisuuksia puhelimesta"</string>
+    <string name="device_type" product="default" msgid="8268703872070046263">"puhelin"</string>
+    <string name="device_type" product="tablet" msgid="5038791954983067774">"tabletti"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-fr-rCA/strings.xml b/packages/CompanionDeviceManager/res/values-fr-rCA/strings.xml
index 3f9ec0b..753875b 100644
--- a/packages/CompanionDeviceManager/res/values-fr-rCA/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-fr-rCA/strings.xml
@@ -17,43 +17,36 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Gestionnaire d\'appareil compagnon"</string>
-    <!-- no translation found for confirmation_title (8024993972587946678) -->
-    <skip />
+    <string name="confirmation_title" msgid="2244241995958340998">"Autoriser l\'application &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; à accéder à &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"montre"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Choisissez un(e) <xliff:g id="PROFILE_NAME">%1$s</xliff:g> qui sera géré(e) par &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <!-- no translation found for summary_watch (6566922405914995759) -->
-    <skip />
-    <!-- no translation found for summary_watch_single_device (7443464525873186735) -->
-    <skip />
-    <string name="profile_name_glasses" msgid="8488394059007275998">"lunettes"</string>
-    <!-- no translation found for summary_glasses (3808267780579061241) -->
-    <skip />
-    <!-- no translation found for summary_glasses_single_device (7051392780285915640) -->
-    <skip />
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"Choisir un appareil qui sera géré par &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Choisir un appareil (<xliff:g id="PROFILE_NAME">%1$s</xliff:g>) pour le configurer"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Cette application sera autorisée à synchroniser des informations, comme le nom de l\'appelant, et à accéder à ces autorisations sur votre <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
+    <string name="confirmation_title_glasses" msgid="8288346850537727333">"Autoriser &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; à gérer &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"appareil"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Cette application pourra accéder à ces autorisations sur votre <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Autorisez &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; à accéder à ces informations à partir de votre téléphone"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Services multiappareils"</string>
-    <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> demande l\'autorisation au nom de votre <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> pour diffuser des applications entre vos appareils"</string>
+    <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g> demande l\'autorisation au nom de votre <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> pour diffuser des applications entre vos appareils"</string>
     <string name="title_automotive_projection" msgid="3296005598978412847"></string>
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"Autorisez &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; à accéder à ces informations à partir de votre téléphone"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Services Google Play"</string>
-    <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> demande l\'autorisation au nom de votre <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> pour accéder aux photos, aux fichiers multimédias et aux notifications de votre téléphone"</string>
-    <string name="title_nearby_device_streaming" msgid="179278282547719200">"Permettre à &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; d\'accomplir cette action à partir de votre téléphone"</string>
-    <string name="helper_title_nearby_device_streaming" msgid="6124438217620593669">"Services multiappareils"</string>
-    <string name="helper_summary_nearby_device_streaming" msgid="5538329403511524333">"<xliff:g id="APP_NAME">%1$s</xliff:g> demande l\'autorisation au nom de votre <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> de diffuser du contenu aux appareils à proximité"</string>
+    <string name="helper_summary_computer" msgid="8774832742608187072">"<xliff:g id="APP_NAME">%1$s</xliff:g> demande l\'autorisation au nom de votre <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> pour accéder aux photos, aux fichiers multimédias et aux notifications de votre téléphone"</string>
+    <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Autoriser &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; à effectuer cette action?"</string>
+    <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> demande l\'autorisation, au nom de votre <xliff:g id="DEVICE_NAME">%2$s</xliff:g>, de diffuser des applications et d\'autres fonctionnalités du système sur des appareils à proximité"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"appareil"</string>
-    <!-- no translation found for summary_generic_single_device (4735072202474939111) -->
-    <skip />
-    <!-- no translation found for summary_generic (4988130802522924650) -->
-    <skip />
+    <string name="summary_generic" msgid="1761976003668044801">"Cette application pourra synchroniser des informations, comme le nom de l\'appelant, entre votre téléphone et l\'appareil sélectionné"</string>
     <string name="consent_yes" msgid="8344487259618762872">"Autoriser"</string>
     <string name="consent_no" msgid="2640796915611404382">"Ne pas autoriser"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Annuler"</string>
     <string name="consent_back" msgid="2560683030046918882">"Retour"</string>
+    <string name="permission_expand" msgid="893185038020887411">"Développer <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
+    <string name="permission_collapse" msgid="3320833884220844084">"Réduire <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Accorder aux applications sur &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; les autorisations déjà accordées sur &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="permission_sync_summary" msgid="765497944331294275">"Cela peut inclure l\'accès &lt;strong&gt;au microphone&lt;/strong&gt;, &lt;strong&gt;à l\'appareil photo&lt;/strong&gt;, et &lt;strong&gt;à la position&lt;/strong&gt;, ainsi que d\'autres autorisations sensibles sur &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;. &lt;br/&gt;&lt;br/&gt;Vous pouvez modifier ces autorisations à tout moment dans vos paramètres sur &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;."</string>
-    <string name="vendor_icon_description" msgid="4445875290032225965">"Icône de l\'application"</string>
-    <string name="vendor_header_button_description" msgid="6566660389500630608">"Bouton En savoir plus"</string>
+    <string name="vendor_header_button_description" msgid="7994879208461111473">"Plus de renseignements"</string>
     <string name="permission_phone" msgid="2661081078692784919">"Téléphone"</string>
     <string name="permission_sms" msgid="6337141296535774786">"Messages texte"</string>
     <string name="permission_contacts" msgid="3858319347208004438">"Contacts"</string>
@@ -64,16 +57,18 @@
     <string name="permission_storage" msgid="6831099350839392343">"Photos et fichiers multimédias"</string>
     <string name="permission_notification" msgid="693762568127741203">"Notifications"</string>
     <string name="permission_app_streaming" msgid="6009695219091526422">"Applications"</string>
-    <string name="permission_nearby_device_streaming" msgid="5868108148065023161">"Diffusion en cours à proximité"</string>
+    <string name="permission_nearby_device_streaming" msgid="1023325519477349499">"Diffusion en continu"</string>
     <string name="permission_phone_summary" msgid="6684396967861278044">"Peut passer et gérer des appels téléphoniques"</string>
     <string name="permission_call_logs_summary" msgid="6186103394658755022">"Peut lire et écrire les journaux d\'appels téléphoniques"</string>
     <string name="permission_sms_summary" msgid="3508442683678912017">"Peut envoyer et voir les messages texte"</string>
     <string name="permission_contacts_summary" msgid="675861979475628708">"Peut accéder à vos contacts"</string>
     <string name="permission_calendar_summary" msgid="6460000922511766226">"Peut accéder à votre agenda"</string>
-    <string name="permission_microphone_summary" msgid="4241354865859396558">"Il est possible d\'enregistrer du contenu audio en utilisant le microphone"</string>
+    <string name="permission_microphone_summary" msgid="3692091540613093394">"Peut enregistrer des données audio"</string>
     <string name="permission_nearby_devices_summary" msgid="931940524460876655">"Peut trouver et déterminer la position relative des appareils à proximité, et s\'y connecter"</string>
     <string name="permission_notification_summary" msgid="884075314530071011">"Peut lire toutes les notifications, y compris les renseignements tels que les contacts, les messages et les photos"</string>
     <string name="permission_app_streaming_summary" msgid="606923325679670624">"Diffusez les applications de votre téléphone"</string>
     <string name="permission_storage_summary" msgid="3918240895519506417"></string>
-    <string name="permission_nearby_device_streaming_summary" msgid="5776807830582725074">"Diffuser du contenu à un appareil à proximité"</string>
+    <string name="permission_nearby_device_streaming_summary" msgid="8280824871197081246">"Diffusez des applications et d\'autres fonctionnalités du système à partir de votre téléphone"</string>
+    <string name="device_type" product="default" msgid="8268703872070046263">"téléphone"</string>
+    <string name="device_type" product="tablet" msgid="5038791954983067774">"tablette"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-fr/strings.xml b/packages/CompanionDeviceManager/res/values-fr/strings.xml
index d05087d..d3704a0 100644
--- a/packages/CompanionDeviceManager/res/values-fr/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-fr/strings.xml
@@ -17,43 +17,36 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Gestionnaire d\'appareils associés"</string>
-    <!-- no translation found for confirmation_title (8024993972587946678) -->
-    <skip />
+    <string name="confirmation_title" msgid="2244241995958340998">"Autoriser l\'appli &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; à accéder à &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; ?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"montre"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Sélectionnez le/la <xliff:g id="PROFILE_NAME">%1$s</xliff:g> qui sera géré(e) par &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <!-- no translation found for summary_watch (6566922405914995759) -->
-    <skip />
-    <!-- no translation found for summary_watch_single_device (7443464525873186735) -->
-    <skip />
-    <string name="profile_name_glasses" msgid="8488394059007275998">"lunettes"</string>
-    <!-- no translation found for summary_glasses (3808267780579061241) -->
-    <skip />
-    <!-- no translation found for summary_glasses_single_device (7051392780285915640) -->
-    <skip />
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"Sélectionner l\'appareil qui sera géré par &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Sélectionner votre <xliff:g id="PROFILE_NAME">%1$s</xliff:g> à configurer"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Cette appli sera autorisée à synchroniser des infos (comme le nom de l\'appelant) et disposera de ces autorisations sur votre <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
+    <string name="confirmation_title_glasses" msgid="8288346850537727333">"Autoriser &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; à gérer &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; ?"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"appareil"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Cette appli sera autorisée à accéder à ces autorisations sur votre <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Autoriser &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; à accéder à ces informations depuis votre téléphone"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Services inter-appareils"</string>
-    <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> demande l\'autorisation au nom de votre <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> pour caster des applis d\'un appareil à l\'autre"</string>
+    <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g> demande l\'autorisation au nom de votre <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> pour caster des applis d\'un appareil à l\'autre"</string>
     <string name="title_automotive_projection" msgid="3296005598978412847"></string>
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"Autoriser &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; à accéder à ces informations depuis votre téléphone"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Services Google Play"</string>
-    <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> demande l\'autorisation au nom de votre <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> pour accéder aux photos, contenus multimédias et notifications de votre téléphone"</string>
-    <string name="title_nearby_device_streaming" msgid="179278282547719200">"Autorisez &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; à effectuer cette action sur votre téléphone"</string>
-    <string name="helper_title_nearby_device_streaming" msgid="6124438217620593669">"Services inter-appareils"</string>
-    <string name="helper_summary_nearby_device_streaming" msgid="5538329403511524333">"<xliff:g id="APP_NAME">%1$s</xliff:g> demande l\'autorisation au nom de votre <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> de diffuser du contenu en streaming sur les appareils à proximité"</string>
+    <string name="helper_summary_computer" msgid="8774832742608187072">"<xliff:g id="APP_NAME">%1$s</xliff:g> demande l\'autorisation au nom de votre <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> pour accéder aux photos, contenus multimédias et notifications de votre téléphone"</string>
+    <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Autoriser &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; à effectuer cette action ?"</string>
+    <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> demande l\'autorisation au nom de votre <xliff:g id="DEVICE_NAME">%2$s</xliff:g> de diffuser des applis et d\'autres fonctionnalités système en streaming sur des appareils à proximité"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"appareil"</string>
-    <!-- no translation found for summary_generic_single_device (4735072202474939111) -->
-    <skip />
-    <!-- no translation found for summary_generic (4988130802522924650) -->
-    <skip />
+    <string name="summary_generic" msgid="1761976003668044801">"Cette appli pourra synchroniser des infos, comme le nom de l\'appelant, entre votre téléphone et l\'appareil choisi"</string>
     <string name="consent_yes" msgid="8344487259618762872">"Autoriser"</string>
     <string name="consent_no" msgid="2640796915611404382">"Ne pas autoriser"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Annuler"</string>
     <string name="consent_back" msgid="2560683030046918882">"Retour"</string>
+    <string name="permission_expand" msgid="893185038020887411">"Développer <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
+    <string name="permission_collapse" msgid="3320833884220844084">"Réduire <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Accorder les mêmes autorisations aux applis sur &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; que sur &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; ?"</string>
     <string name="permission_sync_summary" msgid="765497944331294275">"Ceci peut inclure l\'accès au &lt;strong&gt;micro&lt;/strong&gt;, à l\'&lt;strong&gt;appareil photo&lt;/strong&gt; et à la &lt;strong&gt;position&lt;/strong&gt;, ainsi que d\'autres autorisations sensibles sur &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;. &lt;br/&gt;&lt;br/&gt;Vous pouvez modifier ces autorisations à tout moment dans vos paramètres sur &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;."</string>
-    <string name="vendor_icon_description" msgid="4445875290032225965">"Icône d\'application"</string>
-    <string name="vendor_header_button_description" msgid="6566660389500630608">"Bouton Plus d\'informations"</string>
+    <string name="vendor_header_button_description" msgid="7994879208461111473">"En savoir plus"</string>
     <string name="permission_phone" msgid="2661081078692784919">"Téléphone"</string>
     <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
     <string name="permission_contacts" msgid="3858319347208004438">"Contacts"</string>
@@ -64,16 +57,18 @@
     <string name="permission_storage" msgid="6831099350839392343">"Photos et contenus multimédias"</string>
     <string name="permission_notification" msgid="693762568127741203">"Notifications"</string>
     <string name="permission_app_streaming" msgid="6009695219091526422">"Applis"</string>
-    <string name="permission_nearby_device_streaming" msgid="5868108148065023161">"Streaming appareil à proximité"</string>
+    <string name="permission_nearby_device_streaming" msgid="1023325519477349499">"Streaming"</string>
     <string name="permission_phone_summary" msgid="6684396967861278044">"Peut passer des appels téléphoniques et les gérer"</string>
     <string name="permission_call_logs_summary" msgid="6186103394658755022">"Peut consulter et modifier les journaux d\'appels du téléphone"</string>
     <string name="permission_sms_summary" msgid="3508442683678912017">"Peut envoyer et afficher des SMS"</string>
     <string name="permission_contacts_summary" msgid="675861979475628708">"Peut accéder à vos contacts"</string>
     <string name="permission_calendar_summary" msgid="6460000922511766226">"Peut accéder à votre agenda"</string>
-    <string name="permission_microphone_summary" msgid="4241354865859396558">"Peut enregistrer de l\'audio à l\'aide du micro"</string>
+    <string name="permission_microphone_summary" msgid="3692091540613093394">"Impossible d\'enregistrer l\'audio"</string>
     <string name="permission_nearby_devices_summary" msgid="931940524460876655">"Peut trouver les appareils à proximité, s\'y connecter et déterminer leur position relative"</string>
     <string name="permission_notification_summary" msgid="884075314530071011">"Peut lire toutes les notifications, y compris des informations comme les contacts, messages et photos"</string>
     <string name="permission_app_streaming_summary" msgid="606923325679670624">"Diffuser en streaming les applis de votre téléphone"</string>
     <string name="permission_storage_summary" msgid="3918240895519506417"></string>
-    <string name="permission_nearby_device_streaming_summary" msgid="5776807830582725074">"Diffuse du contenu sur un appareil à proximité"</string>
+    <string name="permission_nearby_device_streaming_summary" msgid="8280824871197081246">"Diffusez des applis et d\'autres fonctionnalités système en streaming depuis votre téléphone"</string>
+    <string name="device_type" product="default" msgid="8268703872070046263">"téléphone"</string>
+    <string name="device_type" product="tablet" msgid="5038791954983067774">"tablette"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-gl/strings.xml b/packages/CompanionDeviceManager/res/values-gl/strings.xml
index f3e2d43..a7aa72d 100644
--- a/packages/CompanionDeviceManager/res/values-gl/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-gl/strings.xml
@@ -17,43 +17,36 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Xestor de dispositivos complementarios"</string>
-    <!-- no translation found for confirmation_title (8024993972587946678) -->
-    <skip />
+    <string name="confirmation_title" msgid="2244241995958340998">"Queres permitir que a aplicación &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; acceda ao dispositivo &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"reloxo"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Escolle un dispositivo (<xliff:g id="PROFILE_NAME">%1$s</xliff:g>) para que o xestione a aplicación &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <!-- no translation found for summary_watch (6566922405914995759) -->
-    <skip />
-    <!-- no translation found for summary_watch_single_device (7443464525873186735) -->
-    <skip />
-    <string name="profile_name_glasses" msgid="8488394059007275998">"lentes"</string>
-    <!-- no translation found for summary_glasses (3808267780579061241) -->
-    <skip />
-    <!-- no translation found for summary_glasses_single_device (7051392780285915640) -->
-    <skip />
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"Escolle un dispositivo para que o xestione a aplicación &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Escolle o perfil (<xliff:g id="PROFILE_NAME">%1$s</xliff:g>) que queiras configurar"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Esta aplicación poderá sincronizar información (por exemplo, o nome de quen chama) e acceder a estes permisos do dispositivo (<xliff:g id="DEVICE_NAME">%1$s</xliff:g>)"</string>
+    <string name="confirmation_title_glasses" msgid="8288346850537727333">"Queres permitir que &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; xestione o dispositivo (&lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;)?"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"dispositivo"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Esta aplicación poderá acceder a estes permisos do dispositivo (<xliff:g id="DEVICE_NAME">%1$s</xliff:g>)"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Permitir que a aplicación &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; acceda a esta información desde o teu teléfono"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Servizos multidispositivo"</string>
-    <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> está solicitando permiso en nome do teu dispositivo (<xliff:g id="DEVICE_TYPE">%2$s</xliff:g>) para emitir contido de aplicacións entre os teus aparellos"</string>
+    <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g> está solicitando permiso en nome do teu dispositivo (<xliff:g id="DISPLAY_NAME">%2$s</xliff:g>) para emitir contido de aplicacións entre os teus aparellos"</string>
     <string name="title_automotive_projection" msgid="3296005598978412847"></string>
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"Permitir que &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; acceda a esta información do teu teléfono"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Servizos de Google Play"</string>
-    <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> está solicitando permiso en nome do teu dispositivo (<xliff:g id="DEVICE_TYPE">%2$s</xliff:g>) para acceder ás fotos, ao contido multimedia e ás notificacións do teléfono"</string>
-    <string name="title_nearby_device_streaming" msgid="179278282547719200">"Permitir que &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; realice esta acción no teléfono"</string>
-    <string name="helper_title_nearby_device_streaming" msgid="6124438217620593669">"Servizos multidispositivo"</string>
-    <string name="helper_summary_nearby_device_streaming" msgid="5538329403511524333">"<xliff:g id="APP_NAME">%1$s</xliff:g> está solicitando permiso en nome do teu dispositivo (<xliff:g id="DEVICE_TYPE">%2$s</xliff:g>) para emitir contido aos dispositivos próximos"</string>
+    <string name="helper_summary_computer" msgid="8774832742608187072">"<xliff:g id="APP_NAME">%1$s</xliff:g> está solicitando permiso en nome do teu dispositivo (<xliff:g id="DISPLAY_NAME">%2$s</xliff:g>) para acceder ás fotos, ao contido multimedia e ás notificacións do teléfono"</string>
+    <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Queres permitir que &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; leve a cabo esta acción?"</string>
+    <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> está solicitando permiso en nome do teu dispositivo (<xliff:g id="DEVICE_NAME">%2$s</xliff:g>) para emitir o contido das aplicacións e doutras funcións do sistema en dispositivos próximos"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"dispositivo"</string>
-    <!-- no translation found for summary_generic_single_device (4735072202474939111) -->
-    <skip />
-    <!-- no translation found for summary_generic (4988130802522924650) -->
-    <skip />
+    <string name="summary_generic" msgid="1761976003668044801">"Esta aplicación poderá sincronizar información (por exemplo, o nome de quen chama) entre o teléfono e o dispositivo escollido"</string>
     <string name="consent_yes" msgid="8344487259618762872">"Permitir"</string>
     <string name="consent_no" msgid="2640796915611404382">"Non permitir"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Cancelar"</string>
     <string name="consent_back" msgid="2560683030046918882">"Atrás"</string>
+    <string name="permission_expand" msgid="893185038020887411">"Despregar <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
+    <string name="permission_collapse" msgid="3320833884220844084">"Contraer <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Queres darlles ás aplicacións de &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; os mesmos permisos que teñen as de &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="permission_sync_summary" msgid="765497944331294275">"Con esta acción podes conceder acceso a &lt;strong&gt;Micrófono&lt;/strong&gt;, &lt;strong&gt;Cámara&lt;/strong&gt;, e &lt;strong&gt;Acceso á localización&lt;/strong&gt;, así como outros permisos de acceso á información confidencial de &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;. &lt;br/&gt;&lt;br/&gt;Podes cambiar estes permisos en calquera momento na configuración de &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;."</string>
-    <string name="vendor_icon_description" msgid="4445875290032225965">"Icona de aplicación"</string>
-    <string name="vendor_header_button_description" msgid="6566660389500630608">"Botón de máis información"</string>
+    <string name="vendor_header_button_description" msgid="7994879208461111473">"Máis información"</string>
     <string name="permission_phone" msgid="2661081078692784919">"Teléfono"</string>
     <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
     <string name="permission_contacts" msgid="3858319347208004438">"Contactos"</string>
@@ -64,16 +57,18 @@
     <string name="permission_storage" msgid="6831099350839392343">"Fotos e contido multimedia"</string>
     <string name="permission_notification" msgid="693762568127741203">"Notificacións"</string>
     <string name="permission_app_streaming" msgid="6009695219091526422">"Aplicacións"</string>
-    <string name="permission_nearby_device_streaming" msgid="5868108148065023161">"Emitir a dispositivos próximos"</string>
+    <string name="permission_nearby_device_streaming" msgid="1023325519477349499">"Reprodución en tempo real"</string>
     <string name="permission_phone_summary" msgid="6684396967861278044">"Pode facer e xestionar chamadas"</string>
     <string name="permission_call_logs_summary" msgid="6186103394658755022">"Pode ler e editar o rexistro de chamadas do teléfono"</string>
     <string name="permission_sms_summary" msgid="3508442683678912017">"Pode enviar e ver mensaxes SMS"</string>
     <string name="permission_contacts_summary" msgid="675861979475628708">"Pode acceder aos contactos"</string>
     <string name="permission_calendar_summary" msgid="6460000922511766226">"Pode acceder ao calendario"</string>
-    <string name="permission_microphone_summary" msgid="4241354865859396558">"Pode gravar audio usando o micrófono"</string>
+    <string name="permission_microphone_summary" msgid="3692091540613093394">"Pode gravar audio"</string>
     <string name="permission_nearby_devices_summary" msgid="931940524460876655">"Pode atopar dispositivos próximos, conectarse a eles e determinar a súa posición relativa"</string>
     <string name="permission_notification_summary" msgid="884075314530071011">"Pode ler todas as notificacións (que poden incluír información como contactos, mensaxes e fotos)"</string>
     <string name="permission_app_streaming_summary" msgid="606923325679670624">"Emite as aplicacións do teu teléfono"</string>
     <string name="permission_storage_summary" msgid="3918240895519506417"></string>
-    <string name="permission_nearby_device_streaming_summary" msgid="5776807830582725074">"Emite contido a un dispositivo próximo"</string>
+    <string name="permission_nearby_device_streaming_summary" msgid="8280824871197081246">"Emite o contido das aplicacións e doutras funcións do sistema desde o teléfono"</string>
+    <string name="device_type" product="default" msgid="8268703872070046263">"teléfono"</string>
+    <string name="device_type" product="tablet" msgid="5038791954983067774">"tableta"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-gu/strings.xml b/packages/CompanionDeviceManager/res/values-gu/strings.xml
index 5e21ccb..443fb4b 100644
--- a/packages/CompanionDeviceManager/res/values-gu/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-gu/strings.xml
@@ -17,43 +17,36 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"કમ્પેનિયન ડિવાઇસ મેનેજર"</string>
-    <!-- no translation found for confirmation_title (8024993972587946678) -->
-    <skip />
+    <string name="confirmation_title" msgid="2244241995958340998">"શું &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;ને ઍક્સેસ કરવા માટે, &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ઍપને મંજૂરી આપીએ?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"સ્માર્ટવૉચ"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"&lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; દ્વારા મેનેજ કરવા માટે કોઈ <xliff:g id="PROFILE_NAME">%1$s</xliff:g> પસંદ કરો"</string>
-    <!-- no translation found for summary_watch (6566922405914995759) -->
-    <skip />
-    <!-- no translation found for summary_watch_single_device (7443464525873186735) -->
-    <skip />
-    <string name="profile_name_glasses" msgid="8488394059007275998">"ચશ્માં"</string>
-    <!-- no translation found for summary_glasses (3808267780579061241) -->
-    <skip />
-    <!-- no translation found for summary_glasses_single_device (7051392780285915640) -->
-    <skip />
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; દ્વારા મેનેજ કરવા માટે કોઈ ડિવાઇસ પસંદ કરો"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"સેટઅપ કરવા માટે કોઈ <xliff:g id="PROFILE_NAME">%1$s</xliff:g> પસંદ કરો"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"આ ઍપને, કૉલ કરનાર વ્યક્તિનું નામ જેવી માહિતી સિંક કરવાની અને તમારા <xliff:g id="DEVICE_NAME">%1$s</xliff:g> પર આ પરવાનગીઓ ઍક્સેસ કરવાની મંજૂરી આપવામાં આવશે"</string>
+    <string name="confirmation_title_glasses" msgid="8288346850537727333">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;ને &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; મેનેજ કરવા માટે મંજૂરી આપીએ?"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"ડિવાઇસ"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"આ ઍપને તમારા <xliff:g id="DEVICE_NAME">%1$s</xliff:g> પર આ પરવાનગીઓ ઍક્સેસ કરવાની મંજૂરી મળશે"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"તમારા ફોનમાંથી આ માહિતી ઍક્સેસ કરવા માટે, &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;ને મંજૂરી આપો"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"ક્રોસ-ડિવાઇસ સેવાઓ"</string>
-    <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> તમારા <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> વતી તમારા ડિવાઇસ વચ્ચે ઍપ સ્ટ્રીમ કરવાની પરવાનગીની વિનંતી કરી રહી છે"</string>
+    <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g> તમારા <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> વતી તમારા ડિવાઇસ વચ્ચે ઍપ સ્ટ્રીમ કરવાની પરવાનગીની વિનંતી કરી રહી છે"</string>
     <string name="title_automotive_projection" msgid="3296005598978412847"></string>
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"તમારા ફોનમાંથી આ માહિતી ઍક્સેસ કરવા માટે, &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;ને મંજૂરી આપો"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Google Play સેવાઓ"</string>
-    <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> તમારા <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> વતી તમારા ફોનના ફોટા, મીડિયા અને નોટિફિકેશન ઍક્સેસ કરવાની પરવાનગીની વિનંતી કરી રહી છે"</string>
-    <string name="title_nearby_device_streaming" msgid="179278282547719200">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;ને તમારા ફોન પરથી આ ક્રિયા કરવાની મંજૂરી આપો"</string>
-    <string name="helper_title_nearby_device_streaming" msgid="6124438217620593669">"ક્રૉસ-ડિવાઇસ સેવાઓ"</string>
-    <string name="helper_summary_nearby_device_streaming" msgid="5538329403511524333">"<xliff:g id="APP_NAME">%1$s</xliff:g> નજીકના ડિવાઇસ પર કન્ટેન્ટ સ્ટ્રીમ કરવા માટે તમારા <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> વતી પરવાનગીની વિનંતી કરી રહી છે"</string>
+    <string name="helper_summary_computer" msgid="8774832742608187072">"<xliff:g id="APP_NAME">%1$s</xliff:g> તમારા <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> વતી તમારા ફોનના ફોટા, મીડિયા અને નોટિફિકેશન ઍક્સેસ કરવાની પરવાનગીની વિનંતી કરી રહી છે"</string>
+    <string name="title_nearby_device_streaming" msgid="7269956847378799794">"&lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt;ને આ પગલું ભરવાની મંજૂરી આપીએ?"</string>
+    <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> નજીકના ડિવાઇસ પર ઍપ અને સિસ્ટમની અન્ય સુવિધાઓ સ્ટ્રીમ કરવા તમારા <xliff:g id="DEVICE_NAME">%2$s</xliff:g> વતી પરવાનગીની વિનંતી કરી રહી છે"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"ડિવાઇસ"</string>
-    <!-- no translation found for summary_generic_single_device (4735072202474939111) -->
-    <skip />
-    <!-- no translation found for summary_generic (4988130802522924650) -->
-    <skip />
+    <string name="summary_generic" msgid="1761976003668044801">"આ ઍપ તમારા ફોન અને પસંદ કરેલા ડિવાઇસ વચ્ચે, કૉલ કરનાર કોઈ વ્યક્તિનું નામ જેવી માહિતી સિંક કરી શકશે"</string>
     <string name="consent_yes" msgid="8344487259618762872">"મંજૂરી આપો"</string>
     <string name="consent_no" msgid="2640796915611404382">"મંજૂરી આપશો નહીં"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"રદ કરો"</string>
     <string name="consent_back" msgid="2560683030046918882">"પાછળ"</string>
+    <string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>ને મોટું કરો"</string>
+    <string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>ને નાનું કરો"</string>
     <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"&lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; પરની ઍપને &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; પર છે તે જ પરવાનગીઓ આપીએ?"</string>
     <string name="permission_sync_summary" msgid="765497944331294275">"આમાં &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt; પરના &lt;strong&gt;માઇક્રોફોન&lt;/strong&gt;, &lt;strong&gt;કૅમેરા&lt;/strong&gt; અને &lt;strong&gt;લોકેશનના ઍક્સેસ&lt;/strong&gt; તથા અન્ય સંવેદનશીલ માહિતીની પરવાનગીઓ શામેલ હોઈ શકે છે. &lt;br/&gt;&lt;br/&gt;તમે કોઈપણ સમયે &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g> પર તમારા સેટિંગમાં આ પરવાનગીઓમાં ફેરફાર કરી શકો છો&lt;/strong&gt;."</string>
-    <string name="vendor_icon_description" msgid="4445875290032225965">"ઍપનું આઇકન"</string>
-    <string name="vendor_header_button_description" msgid="6566660389500630608">"વધુ માહિતી માટેનું બટન"</string>
+    <string name="vendor_header_button_description" msgid="7994879208461111473">"વધુ માહિતી"</string>
     <string name="permission_phone" msgid="2661081078692784919">"ફોન"</string>
     <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
     <string name="permission_contacts" msgid="3858319347208004438">"સંપર્કો"</string>
@@ -64,16 +57,18 @@
     <string name="permission_storage" msgid="6831099350839392343">"ફોટા અને મીડિયા"</string>
     <string name="permission_notification" msgid="693762568127741203">"નોટિફિકેશન"</string>
     <string name="permission_app_streaming" msgid="6009695219091526422">"ઍપ"</string>
-    <string name="permission_nearby_device_streaming" msgid="5868108148065023161">"નજીકના ડિવાઇસ પર સ્ટ્રીમિંગ"</string>
+    <string name="permission_nearby_device_streaming" msgid="1023325519477349499">"સ્ટ્રીમિંગ"</string>
     <string name="permission_phone_summary" msgid="6684396967861278044">"ફોન કૉલ કરી શકે છે અને તેને મેનેજ કરી શકે છે"</string>
     <string name="permission_call_logs_summary" msgid="6186103394658755022">"ફોન કૉલ લૉગ વાંચી અને લખી શકે છે"</string>
     <string name="permission_sms_summary" msgid="3508442683678912017">"SMS મેસેજ મોકલી શકે છે અને જોઈ શકે છે"</string>
     <string name="permission_contacts_summary" msgid="675861979475628708">"તમારા સંપર્કો ઍક્સેસ કરી શકે છે"</string>
     <string name="permission_calendar_summary" msgid="6460000922511766226">"તમારા કૅલેન્ડરનો ઍક્સેસ કરી શકે છે"</string>
-    <string name="permission_microphone_summary" msgid="4241354865859396558">"માઇક્રોફોનનો ઉપયોગ કરીને ઑડિયો રેકોર્ડ કરી શકાય છે"</string>
+    <string name="permission_microphone_summary" msgid="3692091540613093394">"ઑડિયો રેકોર્ડ કરી શકે છે"</string>
     <string name="permission_nearby_devices_summary" msgid="931940524460876655">"નજીકના ડિવાઇસ શોધી શકે છે, તેમની સાથે કનેક્ટ કરી શકે છે અને તેમની સંબંધિત સ્થિતિ નિર્ધારિત કરી શકે છે"</string>
     <string name="permission_notification_summary" msgid="884075314530071011">"સંપર્કો, મેસેજ અને ફોટા જેવી માહિતી સહિતના બધા નોટિફિકેશન વાંચી શકે છે"</string>
     <string name="permission_app_streaming_summary" msgid="606923325679670624">"તમારા ફોનની ઍપ સ્ટ્રીમ કરો"</string>
     <string name="permission_storage_summary" msgid="3918240895519506417"></string>
-    <string name="permission_nearby_device_streaming_summary" msgid="5776807830582725074">"નજીકના ડિવાઇસ પર કન્ટેન્ટ સ્ટ્રીમ કરો"</string>
+    <string name="permission_nearby_device_streaming_summary" msgid="8280824871197081246">"તમારા ફોન પરથી ઍપ અને સિસ્ટમની અન્ય સુવિધાઓ સ્ટ્રીમ કરો"</string>
+    <string name="device_type" product="default" msgid="8268703872070046263">"ફોન"</string>
+    <string name="device_type" product="tablet" msgid="5038791954983067774">"ટૅબ્લેટ"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-hi/strings.xml b/packages/CompanionDeviceManager/res/values-hi/strings.xml
index 4c52ad7..494af37 100644
--- a/packages/CompanionDeviceManager/res/values-hi/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-hi/strings.xml
@@ -17,43 +17,36 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"सहयोगी डिवाइस मैनेजर"</string>
-    <!-- no translation found for confirmation_title (8024993972587946678) -->
-    <skip />
+    <string name="confirmation_title" msgid="2244241995958340998">"क्या &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; को ऐक्सेस करने के लिए &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ऐप्लिकेशन को अनुमति देनी है?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"स्मार्टवॉच"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"कोई <xliff:g id="PROFILE_NAME">%1$s</xliff:g> चुनें, ताकि उसे &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; की मदद से मैनेज किया जा सके"</string>
-    <!-- no translation found for summary_watch (6566922405914995759) -->
-    <skip />
-    <!-- no translation found for summary_watch_single_device (7443464525873186735) -->
-    <skip />
-    <string name="profile_name_glasses" msgid="8488394059007275998">"चश्मा"</string>
-    <!-- no translation found for summary_glasses (3808267780579061241) -->
-    <skip />
-    <!-- no translation found for summary_glasses_single_device (7051392780285915640) -->
-    <skip />
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; से मैनेज किया जाने वाला डिवाइस चुनें"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"सेट अप करने के लिए कोई <xliff:g id="PROFILE_NAME">%1$s</xliff:g> चुनें"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"यह ऐप्लिकेशन, आपके <xliff:g id="DEVICE_NAME">%1$s</xliff:g> पर इन अनुमतियों को ऐक्सेस करने के साथ-साथ कॉल करने वाले व्यक्ति के नाम जैसी जानकारी सिंक कर पाएगा"</string>
+    <string name="confirmation_title_glasses" msgid="8288346850537727333">"क्या &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; को &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; मैनेज करने की अनुमति देनी है?"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"डिवाइस"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"यह ऐप्लिकेशन, आपके <xliff:g id="DEVICE_NAME">%1$s</xliff:g> पर इन अनुमतियों को ऐक्सेस कर पाएगा"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; को अपने फ़ोन से यह जानकारी ऐक्सेस करने की अनुमति दें"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"क्रॉस-डिवाइस से जुड़ी सेवाएं"</string>
-    <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> आपके <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> की ओर से, आपके डिवाइसों के बीच ऐप्लिकेशन को स्ट्रीम करने की अनुमति मांग रहा है"</string>
+    <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g> आपके <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> की ओर से, आपके डिवाइसों के बीच ऐप्लिकेशन स्ट्रीम करने की अनुमति मांग रहा है"</string>
     <string name="title_automotive_projection" msgid="3296005598978412847"></string>
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; को अपने फ़ोन से यह जानकारी ऐक्सेस करने की अनुमति दें"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Google Play services"</string>
-    <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> आपके <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> की ओर से, फ़ोन में मौजूद फ़ोटो, मीडिया, और सूचनाओं को ऐक्सेस करने की अनुमति मांग रहा है"</string>
-    <string name="title_nearby_device_streaming" msgid="179278282547719200">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; को अपने फ़ोन से यह कार्रवाई करने की अनुमति दें"</string>
-    <string name="helper_title_nearby_device_streaming" msgid="6124438217620593669">"क्रॉस-डिवाइस से जुड़ी सेवाएं"</string>
-    <string name="helper_summary_nearby_device_streaming" msgid="5538329403511524333">"<xliff:g id="APP_NAME">%1$s</xliff:g> आपके <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> की ओर से, कॉन्टेंट को आस-पास मौजूद डिवाइसों पर स्ट्रीम करने की अनुमति मांग रहा है"</string>
+    <string name="helper_summary_computer" msgid="8774832742608187072">"<xliff:g id="APP_NAME">%1$s</xliff:g> आपके <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> की ओर से, आपने फ़ोन में मौजूद फ़ोटो, मीडिया, और सूचनाओं को ऐक्सेस करने की अनुमति मांग रहा है"</string>
+    <string name="title_nearby_device_streaming" msgid="7269956847378799794">"क्या &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; को यह कार्रवाई करने की अनुमति देनी है?"</string>
+    <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> आपके <xliff:g id="DEVICE_NAME">%2$s</xliff:g> की ओर से, ऐप्लिकेशन और दूसरे सिस्टम की सुविधाओं को आस-पास मौजूद डिवाइसों पर स्ट्रीम करने की अनुमति मांग रहा है"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"डिवाइस"</string>
-    <!-- no translation found for summary_generic_single_device (4735072202474939111) -->
-    <skip />
-    <!-- no translation found for summary_generic (4988130802522924650) -->
-    <skip />
+    <string name="summary_generic" msgid="1761976003668044801">"यह ऐप्लिकेशन, आपके फ़ोन और चुने हुए डिवाइस के बीच जानकारी सिंक करेगा. जैसे, कॉल करने वाले व्यक्ति का नाम"</string>
     <string name="consent_yes" msgid="8344487259618762872">"अनुमति दें"</string>
     <string name="consent_no" msgid="2640796915611404382">"अनुमति न दें"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"रद्द करें"</string>
     <string name="consent_back" msgid="2560683030046918882">"वापस जाएं"</string>
+    <string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> को बड़ा करें"</string>
+    <string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> को छोटा करें"</string>
     <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"क्या &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; पर ऐप्लिकेशन को वही अनुमतियां देनी हैं जो &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; पर दी हैं?"</string>
     <string name="permission_sync_summary" msgid="765497944331294275">"इसमें &lt;strong&gt;माइक्रोफ़ोन&lt;/strong&gt;, &lt;strong&gt;कैमरा&lt;/strong&gt;, &lt;strong&gt;जगह की जानकारी&lt;/strong&gt;, के ऐक्सेस के साथ-साथ &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt; पर संवेदनशील जानकारी ऐक्सेस करने की अन्य अनुमतियां भी शामिल हो सकती हैं. &lt;br/&gt;&lt;br/&gt;इन अनुमतियों को &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt; में जाकर कभी-भी बदला जा सकता है."</string>
-    <string name="vendor_icon_description" msgid="4445875290032225965">"ऐप्लिकेशन आइकॉन"</string>
-    <string name="vendor_header_button_description" msgid="6566660389500630608">"ज़्यादा जानकारी वाला बटन"</string>
+    <string name="vendor_header_button_description" msgid="7994879208461111473">"ज़्यादा जानकारी"</string>
     <string name="permission_phone" msgid="2661081078692784919">"फ़ोन"</string>
     <string name="permission_sms" msgid="6337141296535774786">"मैसेज (एसएमएस)"</string>
     <string name="permission_contacts" msgid="3858319347208004438">"संपर्क"</string>
@@ -64,16 +57,18 @@
     <string name="permission_storage" msgid="6831099350839392343">"फ़ोटो और मीडिया"</string>
     <string name="permission_notification" msgid="693762568127741203">"सूचनाएं"</string>
     <string name="permission_app_streaming" msgid="6009695219091526422">"ऐप्लिकेशन"</string>
-    <string name="permission_nearby_device_streaming" msgid="5868108148065023161">"आस-पास के डिवाइस पर स्ट्रीमिंग"</string>
+    <string name="permission_nearby_device_streaming" msgid="1023325519477349499">"स्ट्रीमिंग"</string>
     <string name="permission_phone_summary" msgid="6684396967861278044">"फ़ोन कॉल करने और उन्हें मैनेज करने की अनुमति है"</string>
     <string name="permission_call_logs_summary" msgid="6186103394658755022">"कॉल लॉग देखने और उसमें बदलाव करने की अनुमति है"</string>
     <string name="permission_sms_summary" msgid="3508442683678912017">"एसएमएस भेजने और उन्हें देखने की अनुमति है"</string>
     <string name="permission_contacts_summary" msgid="675861979475628708">"आपकी संपर्क सूची को ऐक्सेस करने की अनुमति है"</string>
     <string name="permission_calendar_summary" msgid="6460000922511766226">"कैलेंडर को ऐक्सेस करने की अनुमति है"</string>
-    <string name="permission_microphone_summary" msgid="4241354865859396558">"माइक्रोफ़ोन का इस्तेमाल करके ऑडियो रिकॉर्ड किया जा सकता है"</string>
+    <string name="permission_microphone_summary" msgid="3692091540613093394">"ऑडियो को रिकॉर्ड किया जा सकता है"</string>
     <string name="permission_nearby_devices_summary" msgid="931940524460876655">"आपको आस-पास मौजूद डिवाइसों को खोजने, उनसे कनेक्ट करने, और उनकी जगह की जानकारी का पता लगाने की अनुमति है"</string>
     <string name="permission_notification_summary" msgid="884075314530071011">"इससे सभी सूचनाएं देखी जा सकती हैं. इनमें संपर्क, मैसेज, और फ़ोटो जैसी जानकारी शामिल होती है"</string>
     <string name="permission_app_streaming_summary" msgid="606923325679670624">"अपने फ़ोन पर मौजूद ऐप्लिकेशन स्ट्रीम करें"</string>
     <string name="permission_storage_summary" msgid="3918240895519506417"></string>
-    <string name="permission_nearby_device_streaming_summary" msgid="5776807830582725074">"कॉन्टेंट को आस-पास मौजूद डिवाइस पर स्ट्रीम करने की अनुमति"</string>
+    <string name="permission_nearby_device_streaming_summary" msgid="8280824871197081246">"अपने फ़ोन से ऐप्लिकेशन और सिस्टम की दूसरी सुविधाओं को स्ट्रीम करें"</string>
+    <string name="device_type" product="default" msgid="8268703872070046263">"फ़ोन"</string>
+    <string name="device_type" product="tablet" msgid="5038791954983067774">"टैबलेट"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-hr/strings.xml b/packages/CompanionDeviceManager/res/values-hr/strings.xml
index c901d70..5e175bc 100644
--- a/packages/CompanionDeviceManager/res/values-hr/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-hr/strings.xml
@@ -17,36 +17,36 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Companion Device Manager"</string>
-    <string name="confirmation_title" msgid="8024993972587946678">"Dopustite aplikaciji &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; da pristupa uređaju &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"Želite li dopustiti aplikaciji &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; da pristupa &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"satom"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Odaberite <xliff:g id="PROFILE_NAME">%1$s</xliff:g> kojim će upravljati aplikacija &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="6566922405914995759">"Aplikacija je potrebna za upravljanje vašim uređajem <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. Aplikacija <xliff:g id="APP_NAME">%2$s</xliff:g> moći će sinkronizirati podatke, primjerice ime pozivatelja, stupati u interakciju s vašim obavijestima i pristupati vašim dopuštenjima za telefon, SMS-ove, kontakte, kalendar, zapisnike poziva i uređaje u blizini."</string>
-    <string name="summary_watch_single_device" msgid="7443464525873186735">"Aplikacija je potrebna za upravljanje vašim uređajem <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. Aplikacija <xliff:g id="APP_NAME">%2$s</xliff:g> moći će sinkronizirati podatke, primjerice ime pozivatelja i pristupati sljedećim dopuštenjima:"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"naočale"</string>
-    <string name="summary_glasses" msgid="3808267780579061241">"Ta je aplikacija potrebna za upravljanje uređajem <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. Aplikacija <xliff:g id="APP_NAME">%2$s</xliff:g> moći će stupati u interakciju s vašim obavijestima i pristupati vašim dopuštenjima za telefon, SMS-ove, kontakte, mikrofon i uređaje u blizini."</string>
-    <string name="summary_glasses_single_device" msgid="7051392780285915640">"Aplikacija je potrebna za upravljanje uređajem <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. Aplikacija <xliff:g id="APP_NAME">%2$s</xliff:g> moći će stupati u interakciju s ovim dopuštenjima:"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"Odaberite uređaj kojim će upravljati aplikacija &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Odaberite profil <xliff:g id="PROFILE_NAME">%1$s</xliff:g> koji želite postaviti"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Aplikacija će moći sinkronizirati podatke kao što je ime pozivatelja i pristupiti tim dopuštenjima na vašem uređaju <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
+    <string name="confirmation_title_glasses" msgid="8288346850537727333">"Dopustiti aplikaciji &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; da upravlja uređajem &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"uređaj"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Aplikacija će moći pristupati ovim dopuštenjima na vašem uređaju <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Omogućite aplikaciji &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; da pristupa informacijama s vašeg telefona"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Usluge na različitim uređajima"</string>
-    <string name="helper_summary_app_streaming" msgid="5977509499890099">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> zahtijeva dopuštenje u ime vašeg uređaja <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> za emitiranje aplikacija između vaših uređaja"</string>
+    <string name="helper_summary_app_streaming" msgid="2396773196949578425">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> zahtijeva dopuštenje u ime vašeg uređaja <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> za stream aplikacija s jednog uređaja na drugi"</string>
     <string name="title_automotive_projection" msgid="3296005598978412847"></string>
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"Omogućite aplikaciji &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; da pristupa informacijama s vašeg telefona"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Usluge za Google Play"</string>
-    <string name="helper_summary_computer" msgid="9050724687678157852">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> zahtijeva dopuštenje u ime vašeg uređaja <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> za pristup fotografijama, medijskim sadržajima i obavijestima na telefonu"</string>
-    <string name="title_nearby_device_streaming" msgid="179278282547719200">"Omogućite aplikaciji &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; da izvede tu radnju na vašem telefonu"</string>
-    <string name="helper_title_nearby_device_streaming" msgid="6124438217620593669">"Usluge na različitim uređajima"</string>
-    <string name="helper_summary_nearby_device_streaming" msgid="5538329403511524333">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> zahtijeva dopuštenje u ime vašeg uređaja <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> za streamanje sadržaja na uređaje u blizini"</string>
+    <string name="helper_summary_computer" msgid="8774832742608187072">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> zahtijeva dopuštenje u ime vašeg uređaja <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> za pristup fotografijama, medijskim sadržajima i obavijestima na telefonu"</string>
+    <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Želite li uređaju &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; dopustiti da izvrši tu radnju?"</string>
+    <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> zahtijeva dopuštenje u ime vašeg uređaja <xliff:g id="DEVICE_NAME">%2$s</xliff:g> za emitiranje aplikacija i drugih značajki sustava na uređajima u blizini"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"uređaj"</string>
-    <string name="summary_generic_single_device" msgid="4735072202474939111">"Ta će aplikacija moći sinkronizirati podatke između vašeg telefona i uređaja <xliff:g id="DEVICE_NAME">%1$s</xliff:g>, primjerice ime pozivatelja."</string>
-    <string name="summary_generic" msgid="4988130802522924650">"Ta će aplikacija moći sinkronizirati podatke između vašeg telefona i odabranog uređaja, primjerice ime pozivatelja."</string>
+    <string name="summary_generic" msgid="1761976003668044801">"Ta će aplikacija moći sinkronizirati podatke između vašeg telefona i odabranog uređaja, primjerice ime pozivatelja"</string>
     <string name="consent_yes" msgid="8344487259618762872">"Dopusti"</string>
     <string name="consent_no" msgid="2640796915611404382">"Nemoj dopustiti"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Odustani"</string>
     <string name="consent_back" msgid="2560683030046918882">"Natrag"</string>
+    <string name="permission_expand" msgid="893185038020887411">"Proširi <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
+    <string name="permission_collapse" msgid="3320833884220844084">"Sažmi <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Dati jednaka dopuštenja aplikacijama na uređaju &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; kao i na uređaju &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="permission_sync_summary" msgid="765497944331294275">"To može uključivati &lt;strong&gt;pristup mikrofonu&lt;/strong&gt;, &lt;strong&gt;kameri&lt;/strong&gt; i &lt;strong&gt;lokaciji&lt;/strong&gt; te druga dopuštenja za osjetljive podatke na uređaju &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;. &lt;br/&gt;&lt;br/&gt;Ta dopuštenja uvijek možete promijeniti u postavkama na uređaju &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;."</string>
-    <string name="vendor_icon_description" msgid="4445875290032225965">"Ikona aplikacije"</string>
-    <string name="vendor_header_button_description" msgid="6566660389500630608">"Gumb Više informacija"</string>
+    <string name="vendor_header_button_description" msgid="7994879208461111473">"Više informacija"</string>
     <string name="permission_phone" msgid="2661081078692784919">"Telefon"</string>
     <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
     <string name="permission_contacts" msgid="3858319347208004438">"Kontakti"</string>
@@ -57,16 +57,18 @@
     <string name="permission_storage" msgid="6831099350839392343">"Fotografije i mediji"</string>
     <string name="permission_notification" msgid="693762568127741203">"Obavijesti"</string>
     <string name="permission_app_streaming" msgid="6009695219091526422">"Aplikacije"</string>
-    <string name="permission_nearby_device_streaming" msgid="5868108148065023161">"Streamanje uređaja u blizini"</string>
+    <string name="permission_nearby_device_streaming" msgid="1023325519477349499">"Streaming"</string>
     <string name="permission_phone_summary" msgid="6684396967861278044">"Može uspostavljati telefonske pozive i upravljati njima"</string>
     <string name="permission_call_logs_summary" msgid="6186103394658755022">"Može čitati i pisati zapisnik poziva telefona"</string>
     <string name="permission_sms_summary" msgid="3508442683678912017">"Može slati i pregledavati SMS poruke"</string>
     <string name="permission_contacts_summary" msgid="675861979475628708">"Može pristupiti vašim kontaktima"</string>
     <string name="permission_calendar_summary" msgid="6460000922511766226">"Može pristupiti vašem kalendaru"</string>
-    <string name="permission_microphone_summary" msgid="4241354865859396558">"Može snimiti zvuk pomoću mikrofona"</string>
+    <string name="permission_microphone_summary" msgid="3692091540613093394">"Može snimati zvuk"</string>
     <string name="permission_nearby_devices_summary" msgid="931940524460876655">"Može pronaći i odrediti relativni položaj uređaja u blizini i povezati se s njima"</string>
     <string name="permission_notification_summary" msgid="884075314530071011">"Može čitati sve obavijesti, uključujući informacije kao što su kontakti, poruke i fotografije"</string>
     <string name="permission_app_streaming_summary" msgid="606923325679670624">"Streaming aplikacija vašeg telefona"</string>
     <string name="permission_storage_summary" msgid="3918240895519506417"></string>
-    <string name="permission_nearby_device_streaming_summary" msgid="5776807830582725074">"Streamanje sadržaja na uređaj u blizini"</string>
+    <string name="permission_nearby_device_streaming_summary" msgid="8280824871197081246">"Emitirajte stream aplikacija i drugih značajki sustava s vašeg telefona"</string>
+    <string name="device_type" product="default" msgid="8268703872070046263">"telefonu"</string>
+    <string name="device_type" product="tablet" msgid="5038791954983067774">"tabletu"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-hu/strings.xml b/packages/CompanionDeviceManager/res/values-hu/strings.xml
index cfd6ad4..ba480f6 100644
--- a/packages/CompanionDeviceManager/res/values-hu/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-hu/strings.xml
@@ -17,43 +17,36 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Társeszközök kezelője"</string>
-    <!-- no translation found for confirmation_title (8024993972587946678) -->
-    <skip />
+    <string name="confirmation_title" msgid="2244241995958340998">"Engedélyezi a(z) &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; alkalmazás számára, hogy hozzáférjen a következőhöz: &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"óra"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"A(z) &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; alkalmazással kezelni kívánt <xliff:g id="PROFILE_NAME">%1$s</xliff:g> kiválasztása"</string>
-    <!-- no translation found for summary_watch (6566922405914995759) -->
-    <skip />
-    <!-- no translation found for summary_watch_single_device (7443464525873186735) -->
-    <skip />
-    <string name="profile_name_glasses" msgid="8488394059007275998">"szemüveg"</string>
-    <!-- no translation found for summary_glasses (3808267780579061241) -->
-    <skip />
-    <!-- no translation found for summary_glasses_single_device (7051392780285915640) -->
-    <skip />
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"A(z) &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; alkalmazással kezelni kívánt eszköz kiválasztása"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Válassza ki a beállítani kívánt <xliff:g id="PROFILE_NAME">%1$s</xliff:g> nevet."</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Ez az alkalmazás képes lesz szinkronizálni információkat (például a hívó fél nevét), és hozzáférhet majd ezekhez az engedélyekhez az Ön <xliff:g id="DEVICE_NAME">%1$s</xliff:g> eszközén"</string>
+    <string name="confirmation_title_glasses" msgid="8288346850537727333">"Engedélyezi, hogy a(z) &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; kezelje a következő eszközt: &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"eszköz"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Az alkalmazás hozzáférhet majd ezekhez az engedélyekhez az Ön <xliff:g id="DEVICE_NAME">%1$s</xliff:g> eszközén"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Engedélyezi a(z) „<xliff:g id="APP_NAME">%1$s</xliff:g>” alkalmazás számára az információhoz való hozzáférést a telefonról"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Többeszközös szolgáltatások"</string>
-    <string name="helper_summary_app_streaming" msgid="5977509499890099">"A(z) <xliff:g id="APP_NAME">%1$s</xliff:g> engedélyt kér a(z) <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> nevében az alkalmazások eszközök közötti streameléséhez"</string>
+    <string name="helper_summary_app_streaming" msgid="2396773196949578425">"A(z) <xliff:g id="APP_NAME">%1$s</xliff:g> engedélyt kér a(z) <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> nevében az alkalmazások eszközök közötti streameléséhez"</string>
     <string name="title_automotive_projection" msgid="3296005598978412847"></string>
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"Engedélyezi a(z) „<xliff:g id="APP_NAME">%1$s</xliff:g>” alkalmazás számára az információhoz való hozzáférést a telefonról"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Google Play-szolgáltatások"</string>
-    <string name="helper_summary_computer" msgid="9050724687678157852">"A(z) <xliff:g id="APP_NAME">%1$s</xliff:g> engedélyt kér a(z) <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> nevében a telefonon tárolt fotókhoz, médiatartalmakhoz és értesítésekhez való hozzáféréshez"</string>
-    <string name="title_nearby_device_streaming" msgid="179278282547719200">"Engedélyezi a(z) &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; alkalmazás számára ennek a műveletnek az elvégzését a telefonjáról."</string>
-    <string name="helper_title_nearby_device_streaming" msgid="6124438217620593669">"Többeszközös szolgáltatások"</string>
-    <string name="helper_summary_nearby_device_streaming" msgid="5538329403511524333">"A(z) <xliff:g id="APP_NAME">%1$s</xliff:g> engedélyt kér a(z) <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> nevében a közeli eszközökre való streameléséhez."</string>
+    <string name="helper_summary_computer" msgid="8774832742608187072">"A(z) <xliff:g id="APP_NAME">%1$s</xliff:g> engedélyt kér a(z) <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> nevében a telefonon tárolt fotókhoz, médiatartalmakhoz és értesítésekhez való hozzáféréshez"</string>
+    <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Engedélyezi a(z) &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; számára ennek a műveletnek a végrehajtását?"</string>
+    <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"A(z) <xliff:g id="APP_NAME">%1$s</xliff:g> engedélyt kér a(z) <xliff:g id="DEVICE_NAME">%2$s</xliff:g> nevében az alkalmazások és más rendszerfunkciók közeli eszközökre történő streamelésére"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"eszköz"</string>
-    <!-- no translation found for summary_generic_single_device (4735072202474939111) -->
-    <skip />
-    <!-- no translation found for summary_generic (4988130802522924650) -->
-    <skip />
+    <string name="summary_generic" msgid="1761976003668044801">"Ez az alkalmazás képes lesz szinkronizálni az olyan információkat a telefon és a kiválasztott eszköz között, mint például a hívó fél neve."</string>
     <string name="consent_yes" msgid="8344487259618762872">"Engedélyezés"</string>
     <string name="consent_no" msgid="2640796915611404382">"Tiltás"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Mégse"</string>
     <string name="consent_back" msgid="2560683030046918882">"Vissza"</string>
+    <string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> kibontása"</string>
+    <string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> összecsukása"</string>
     <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Ugyanolyan engedélyeket ad a(z) &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; eszközön található alkalmazásoknak, mint a(z) &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; eszköz esetén?"</string>
     <string name="permission_sync_summary" msgid="765497944331294275">"Ide tartozhat a &lt;strong&gt;mikrofonhoz&lt;/strong&gt;, a &lt;strong&gt;kamerához&lt;/strong&gt; és a &lt;strong&gt;helyadatokhoz&lt;/strong&gt; való hozzáférés, valamint a(z) &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt; eszközön érvényes egyéb bizalmas engedélyek is. &lt;br/&gt;&lt;br/&gt;Ezeket az engedélyeket bármikor módosíthatja a(z) &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt; eszköz beállításai között."</string>
-    <string name="vendor_icon_description" msgid="4445875290032225965">"Alkalmazás ikonja"</string>
-    <string name="vendor_header_button_description" msgid="6566660389500630608">"További információ gomb"</string>
+    <string name="vendor_header_button_description" msgid="7994879208461111473">"További információ"</string>
     <string name="permission_phone" msgid="2661081078692784919">"Telefon"</string>
     <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
     <string name="permission_contacts" msgid="3858319347208004438">"Címtár"</string>
@@ -64,16 +57,18 @@
     <string name="permission_storage" msgid="6831099350839392343">"Fotók és médiatartalmak"</string>
     <string name="permission_notification" msgid="693762568127741203">"Értesítések"</string>
     <string name="permission_app_streaming" msgid="6009695219091526422">"Alkalmazások"</string>
-    <string name="permission_nearby_device_streaming" msgid="5868108148065023161">"Streamelés közeli eszközökre"</string>
+    <string name="permission_nearby_device_streaming" msgid="1023325519477349499">"Streamelés"</string>
     <string name="permission_phone_summary" msgid="6684396967861278044">"Hívásokat indíthat és kezelhet"</string>
     <string name="permission_call_logs_summary" msgid="6186103394658755022">"Olvashatja és írhatja a telefon hívásnaplóját"</string>
     <string name="permission_sms_summary" msgid="3508442683678912017">"SMS-üzeneteket küldhet és tekinthet meg"</string>
     <string name="permission_contacts_summary" msgid="675861979475628708">"Hozzáférhet a névjegyekhez"</string>
     <string name="permission_calendar_summary" msgid="6460000922511766226">"Hozzáférhet a naptárhoz"</string>
-    <string name="permission_microphone_summary" msgid="4241354865859396558">"Hangfelvételt készíthet a mikrofon használatával."</string>
+    <string name="permission_microphone_summary" msgid="3692091540613093394">"Rögzíthet hangot"</string>
     <string name="permission_nearby_devices_summary" msgid="931940524460876655">"Megkeresheti a közeli eszközöket, meghatározhatja viszonylagos helyzetüket és csatlakozhat hozzájuk"</string>
     <string name="permission_notification_summary" msgid="884075314530071011">"Elolvashat minden értesítést, ideértve az olyan információkat, mint a névjegyek, az üzenetek és a fotók"</string>
     <string name="permission_app_streaming_summary" msgid="606923325679670624">"A telefon alkalmazásainak streamelése"</string>
     <string name="permission_storage_summary" msgid="3918240895519506417"></string>
-    <string name="permission_nearby_device_streaming_summary" msgid="5776807830582725074">"Tartalmat streamelhet közeli eszközökre."</string>
+    <string name="permission_nearby_device_streaming_summary" msgid="8280824871197081246">"Alkalmazások és más rendszerfunkciók streamelése a telefonról"</string>
+    <string name="device_type" product="default" msgid="8268703872070046263">"telefonján"</string>
+    <string name="device_type" product="tablet" msgid="5038791954983067774">"táblagépén"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-hy/strings.xml b/packages/CompanionDeviceManager/res/values-hy/strings.xml
index 719496c..9385fd1 100644
--- a/packages/CompanionDeviceManager/res/values-hy/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-hy/strings.xml
@@ -17,36 +17,36 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Companion Device Manager"</string>
-    <string name="confirmation_title" msgid="8024993972587946678">"Թույլատրեք &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; հավելվածին կառավարել &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; սարքը"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"Թույլատրե՞լ &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; հավելվածին կառավարել &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; սարքը"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"ժամացույց"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Ընտրեք <xliff:g id="PROFILE_NAME">%1$s</xliff:g>ը, որը պետք է կառավարվի &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; հավելվածի կողմից"</string>
-    <string name="summary_watch" msgid="6566922405914995759">"Հավելվածն անհրաժեշտ է ձեր <xliff:g id="DEVICE_NAME">%1$s</xliff:g> սարքը կառավարելու համար։ <xliff:g id="APP_NAME">%2$s</xliff:g> հավելվածը կկարողանա համաժամացնել տվյալները, օր․՝ զանգողի անունը, փոխազդել ձեր ծանուցումների հետ և կստանա «Հեռախոս», «SMS», «Կոնտակտներ», «Օրացույց», «Կանչերի ցուցակ» և «Մոտակա սարքեր» թույլտվությունները։"</string>
-    <string name="summary_watch_single_device" msgid="7443464525873186735">"Հավելվածն անհրաժեշտ է ձեր <xliff:g id="DEVICE_NAME">%1$s</xliff:g> սարքը կառավարելու համար։ <xliff:g id="APP_NAME">%2$s</xliff:g> հավելվածը կկարողանա համաժամացնել տվյալները, օր․՝ զանգողի անունը, և կստանա հետևյալ թույլտվությունները․"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"ակնոց"</string>
-    <string name="summary_glasses" msgid="3808267780579061241">"Այս հավելվածն անհրաժեշտ է <xliff:g id="DEVICE_NAME">%1$s</xliff:g> սարքը կառավարելու համար։ <xliff:g id="APP_NAME">%2$s</xliff:g> հավելվածը կկարողանա փոխազդել ձեր ծանուցումների հետ և կստանա «Հեռախոս», «SMS», «Կոնտակտներ», «Խոսափող» և «Մոտակա սարքեր» թույլտվությունները։"</string>
-    <string name="summary_glasses_single_device" msgid="7051392780285915640">"Հավելվածն անհրաժեշտ է <xliff:g id="DEVICE_NAME">%1$s</xliff:g> սարքը կառավարելու համար։ <xliff:g id="APP_NAME">%2$s</xliff:g> հավելվածին կթույլատրվի օգտվել այս թույլտվություններից․"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"Ընտրեք սարքը, որը պետք է կառավարվի &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; հավելվածի միջոցով"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Կարգավորելու համար ընտրեք <xliff:g id="PROFILE_NAME">%1$s</xliff:g>ը"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Այս հավելվածը կկարողանա համաժամացնել տվյալները, օր․՝ զանգողի անունը, և կստանա հետևյալ թույլտվությունները ձեր <xliff:g id="DEVICE_NAME">%1$s</xliff:g>ում"</string>
+    <string name="confirmation_title_glasses" msgid="8288346850537727333">"Թույլատրե՞լ &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; հավելվածին կառավարել &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; սարքը"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"սարք"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Այս հավելվածը կստանա հետևյալ թույլտվությունները ձեր <xliff:g id="DEVICE_NAME">%1$s</xliff:g>ում"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Թույլատրեք &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; հավելվածին օգտագործել այս տեղեկությունները ձեր հեռախոսից"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Միջսարքային ծառայություններ"</string>
-    <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> հավելվածը ձեր <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> սարքի անունից թույլտվություն է խնդրում՝ ձեր սարքերի միջև հավելվածներ հեռարձակելու համար"</string>
+    <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g> հավելվածը ձեր <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> սարքի անունից թույլտվություն է խնդրում՝ ձեր սարքերի միջև հավելվածներ հեռարձակելու համար"</string>
     <string name="title_automotive_projection" msgid="3296005598978412847"></string>
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"Թույլատրեք &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; հավելվածին օգտագործել այս տեղեկությունները ձեր հեռախոսից"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Google Play ծառայություններ"</string>
-    <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> հավելվածը ձեր <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> սարքի անունից թույլտվություն է խնդրում՝ ձեր հեռախոսի լուսանկարները, մեդիաֆայլերն ու ծանուցումները տեսնելու համար"</string>
-    <string name="title_nearby_device_streaming" msgid="179278282547719200">"Թույլատրել &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; հավելվածին կատարել այս գործողությունը ձեր հեռախոսում"</string>
-    <string name="helper_title_nearby_device_streaming" msgid="6124438217620593669">"Միջսարքային ծառայություններ"</string>
-    <string name="helper_summary_nearby_device_streaming" msgid="5538329403511524333">"<xliff:g id="APP_NAME">%1$s</xliff:g> հավելվածը ձեր <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> սարքի անունից թույլտվություն է խնդրում՝ մոտակա սարքերին բովանդակություն հեռարձակելու համար"</string>
+    <string name="helper_summary_computer" msgid="8774832742608187072">"<xliff:g id="APP_NAME">%1$s</xliff:g> հավելվածը ձեր <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> սարքի անունից թույլտվություն է խնդրում՝ ձեր հեռախոսի լուսանկարները, մեդիաֆայլերն ու ծանուցումները տեսնելու համար"</string>
+    <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Թույլատրե՞լ &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; հավելվածին կատարել այս գործողությունը"</string>
+    <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> հավելվածը ձեր <xliff:g id="DEVICE_NAME">%2$s</xliff:g> սարքի անունից թույլտվություն է խնդրում՝ մոտակա սարքերին հավելվածներ և համակարգի այլ գործառույթներ հեռարձակելու համար"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"սարք"</string>
-    <string name="summary_generic_single_device" msgid="4735072202474939111">"Այս հավելվածը կկարողանա համաժամացնել ձեր հեռախոսի և <xliff:g id="DEVICE_NAME">%1$s</xliff:g> սարքի տվյալները, օր․՝ զանգողի անունը։"</string>
-    <string name="summary_generic" msgid="4988130802522924650">"Այս հավելվածը կկարողանա համաժամացնել ձեր հեռախոսի և ընտրված սարքի տվյալները, օր․՝ զանգողի անունը։"</string>
+    <string name="summary_generic" msgid="1761976003668044801">"Այս հավելվածը կկարողանա համաժամացնել ձեր հեռախոսի և ընտրված սարքի տվյալները, օր․՝ զանգողի անունը"</string>
     <string name="consent_yes" msgid="8344487259618762872">"Թույլատրել"</string>
     <string name="consent_no" msgid="2640796915611404382">"Չթույլատրել"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Չեղարկել"</string>
     <string name="consent_back" msgid="2560683030046918882">"Հետ"</string>
+    <string name="permission_expand" msgid="893185038020887411">"Ծավալել «<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>» բաժինը"</string>
+    <string name="permission_collapse" msgid="3320833884220844084">"Ծալել «<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>» բաժինը"</string>
     <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"&lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt;-ում հավելվածներին տա՞լ նույն թույլտվությունները, ինչ &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;-ում"</string>
     <string name="permission_sync_summary" msgid="765497944331294275">"Սա կարող է ներառել &lt;strong&gt;խոսափողի&lt;/strong&gt;, &lt;strong&amp;gtտեսախցիկի&lt;/strong&gt;, &lt;strong&gt;տեղադրության&lt;/strong&gt; և այլ կոնֆիդենցիալ տվյալների օգտագործման թույլտվությունները &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt; սարքում։ &lt;br/&gt;&lt;br/&gt;Այդ թույլտվությունները ցանկացած ժամանակ կարող եք փոխել &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt; սարքի ձեր կարգավորումներում։"</string>
-    <string name="vendor_icon_description" msgid="4445875290032225965">"Հավելվածի պատկերակ"</string>
-    <string name="vendor_header_button_description" msgid="6566660389500630608">"«Այլ տեղեկություններ» կոճակ"</string>
+    <string name="vendor_header_button_description" msgid="7994879208461111473">"Լրացուցիչ տեղեկություններ"</string>
     <string name="permission_phone" msgid="2661081078692784919">"Հեռախոս"</string>
     <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
     <string name="permission_contacts" msgid="3858319347208004438">"Կոնտակտներ"</string>
@@ -57,16 +57,18 @@
     <string name="permission_storage" msgid="6831099350839392343">"Լուսանկարներ և մուլտիմեդիա"</string>
     <string name="permission_notification" msgid="693762568127741203">"Ծանուցումներ"</string>
     <string name="permission_app_streaming" msgid="6009695219091526422">"Հավելվածներ"</string>
-    <string name="permission_nearby_device_streaming" msgid="5868108148065023161">"Հեռարձակում մոտակա սարքերին"</string>
+    <string name="permission_nearby_device_streaming" msgid="1023325519477349499">"Հեռարձակում"</string>
     <string name="permission_phone_summary" msgid="6684396967861278044">"Կարող է կատարել հեռախոսազանգեր և կառավարել դրանք"</string>
     <string name="permission_call_logs_summary" msgid="6186103394658755022">"Կարող է դիտել և գրանցել կանչերը"</string>
     <string name="permission_sms_summary" msgid="3508442683678912017">"Կարող է ուղարկել SMS հաղորդագրություններ և դիտել դրանք"</string>
     <string name="permission_contacts_summary" msgid="675861979475628708">"Կարող է օգտագործել ձեր կոնտակտները"</string>
     <string name="permission_calendar_summary" msgid="6460000922511766226">"Կարող է օգտագործել ձեր օրացույցը"</string>
-    <string name="permission_microphone_summary" msgid="4241354865859396558">"Կարող է օգտագործել խոսափողը՝ ձայնագրություններ անելու համար"</string>
+    <string name="permission_microphone_summary" msgid="3692091540613093394">"Կարող է ձայնագրել"</string>
     <string name="permission_nearby_devices_summary" msgid="931940524460876655">"Կարող է գտնել և որոշել մոտակա սարքերի մոտավոր դիրքը և միանալ դրանց"</string>
     <string name="permission_notification_summary" msgid="884075314530071011">"Կարող է կարդալ բոլոր ծանուցումները, ներառյալ տեղեկությունները, օրինակ՝ կոնտակտները, հաղորդագրությունները և լուսանկարները"</string>
     <string name="permission_app_streaming_summary" msgid="606923325679670624">"Հեռարձակել հեռախոսի հավելվածները"</string>
     <string name="permission_storage_summary" msgid="3918240895519506417"></string>
-    <string name="permission_nearby_device_streaming_summary" msgid="5776807830582725074">"Հեռարձակել բովանդակություն մոտակա սարքերին"</string>
+    <string name="permission_nearby_device_streaming_summary" msgid="8280824871197081246">"Հեռարձակել հավելվածներ և համակարգի այլ գործառույթներ հեռախոսում"</string>
+    <string name="device_type" product="default" msgid="8268703872070046263">"հեռախոս"</string>
+    <string name="device_type" product="tablet" msgid="5038791954983067774">"պլանշետ"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-in/strings.xml b/packages/CompanionDeviceManager/res/values-in/strings.xml
index 5394808..c03a5ab 100644
--- a/packages/CompanionDeviceManager/res/values-in/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-in/strings.xml
@@ -17,43 +17,36 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Pengelola Perangkat Pendamping"</string>
-    <!-- no translation found for confirmation_title (8024993972587946678) -->
-    <skip />
+    <string name="confirmation_title" msgid="2244241995958340998">"Izinkan aplikasi &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; mengakses &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"smartwatch"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Pilih <xliff:g id="PROFILE_NAME">%1$s</xliff:g> untuk dikelola oleh &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <!-- no translation found for summary_watch (6566922405914995759) -->
-    <skip />
-    <!-- no translation found for summary_watch_single_device (7443464525873186735) -->
-    <skip />
-    <string name="profile_name_glasses" msgid="8488394059007275998">"glasses"</string>
-    <!-- no translation found for summary_glasses (3808267780579061241) -->
-    <skip />
-    <!-- no translation found for summary_glasses_single_device (7051392780285915640) -->
-    <skip />
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"Pilih perangkat untuk dikelola oleh &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Pilih <xliff:g id="PROFILE_NAME">%1$s</xliff:g> untuk disiapkan"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Aplikasi ini akan diizinkan menyinkronkan info, seperti nama penelepon, dan mengakses izin ini di <xliff:g id="DEVICE_NAME">%1$s</xliff:g> Anda"</string>
+    <string name="confirmation_title_glasses" msgid="8288346850537727333">"Izinkan &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; mengelola &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"perangkat"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Aplikasi ini akan diizinkan mengakses izin ini di <xliff:g id="DEVICE_NAME">%1$s</xliff:g> Anda"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Izinkan &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; untuk mengakses informasi ini dari ponsel Anda"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Layanan lintas perangkat"</string>
-    <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> meminta izin atas nama <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> untuk menstreaming aplikasi di antara perangkat Anda"</string>
+    <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g> meminta izin atas nama <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> untuk menstreaming aplikasi di antara perangkat Anda"</string>
     <string name="title_automotive_projection" msgid="3296005598978412847"></string>
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"Izinkan &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; mengakses informasi ini dari ponsel Anda"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Layanan Google Play"</string>
-    <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> meminta izin atas nama <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> untuk mengakses foto, media, dan notifikasi ponsel Anda"</string>
-    <string name="title_nearby_device_streaming" msgid="179278282547719200">"Izinkan &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; melakukan tindakan ini dari ponsel"</string>
-    <string name="helper_title_nearby_device_streaming" msgid="6124438217620593669">"Layanan lintas perangkat"</string>
-    <string name="helper_summary_nearby_device_streaming" msgid="5538329403511524333">"<xliff:g id="APP_NAME">%1$s</xliff:g> meminta izin atas nama <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> untuk menstreaming konten ke perangkat di sekitar"</string>
+    <string name="helper_summary_computer" msgid="8774832742608187072">"<xliff:g id="APP_NAME">%1$s</xliff:g> meminta izin atas nama <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> untuk mengakses foto, media, dan notifikasi ponsel Anda"</string>
+    <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Izinkan &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; melakukan tindakan ini?"</string>
+    <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> meminta izin atas nama <xliff:g id="DEVICE_NAME">%2$s</xliff:g> untuk menstreaming aplikasi dan fitur sistem lainnya ke perangkat di sekitar"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"perangkat"</string>
-    <!-- no translation found for summary_generic_single_device (4735072202474939111) -->
-    <skip />
-    <!-- no translation found for summary_generic (4988130802522924650) -->
-    <skip />
+    <string name="summary_generic" msgid="1761976003668044801">"Aplikasi ini akan dapat menyinkronkan info, seperti nama penelepon, antara ponsel dan perangkat yang dipilih"</string>
     <string name="consent_yes" msgid="8344487259618762872">"Izinkan"</string>
     <string name="consent_no" msgid="2640796915611404382">"Jangan izinkan"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Batalkan"</string>
     <string name="consent_back" msgid="2560683030046918882">"Kembali"</string>
+    <string name="permission_expand" msgid="893185038020887411">"Luaskan <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
+    <string name="permission_collapse" msgid="3320833884220844084">"Ciutkan <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Berikan aplikasi di &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; izin yang sama seperti di &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="permission_sync_summary" msgid="765497944331294275">"Ini bisa termasuk &lt;strong&gt;Mikrofon&lt;/strong&gt;, &lt;strong&gt;Kamera&lt;/strong&gt;, dan &lt;strong&gt;Akses lokasi&lt;/strong&gt;, serta izin sensitif lainnya di &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;. &lt;br/&gt;&lt;br/&gt;Anda dapat mengubah izin ini kapan saja di Setelan &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;."</string>
-    <string name="vendor_icon_description" msgid="4445875290032225965">"Ikon Aplikasi"</string>
-    <string name="vendor_header_button_description" msgid="6566660389500630608">"Tombol Informasi Lainnya"</string>
+    <string name="vendor_header_button_description" msgid="7994879208461111473">"Info Selengkapnya"</string>
     <string name="permission_phone" msgid="2661081078692784919">"Telepon"</string>
     <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
     <string name="permission_contacts" msgid="3858319347208004438">"Kontak"</string>
@@ -64,16 +57,18 @@
     <string name="permission_storage" msgid="6831099350839392343">"Foto dan media"</string>
     <string name="permission_notification" msgid="693762568127741203">"Notifikasi"</string>
     <string name="permission_app_streaming" msgid="6009695219091526422">"Aplikasi"</string>
-    <string name="permission_nearby_device_streaming" msgid="5868108148065023161">"Streaming Perangkat di Sekitar"</string>
+    <string name="permission_nearby_device_streaming" msgid="1023325519477349499">"Streaming"</string>
     <string name="permission_phone_summary" msgid="6684396967861278044">"Dapat melakukan dan mengelola panggilan telepon"</string>
     <string name="permission_call_logs_summary" msgid="6186103394658755022">"Dapat membaca dan menulis log panggilan telepon"</string>
     <string name="permission_sms_summary" msgid="3508442683678912017">"Dapat mengirim dan melihat pesan SMS"</string>
     <string name="permission_contacts_summary" msgid="675861979475628708">"Dapat mengakses kontak Anda"</string>
     <string name="permission_calendar_summary" msgid="6460000922511766226">"Dapat mengakses kalender Anda"</string>
-    <string name="permission_microphone_summary" msgid="4241354865859396558">"Dapat merekam audio menggunakan mikrofon"</string>
-    <string name="permission_nearby_devices_summary" msgid="931940524460876655">"Dapat menemukan, menghubungkan, dan menentukan posisi relatif dari perangkat di sekitar"</string>
+    <string name="permission_microphone_summary" msgid="3692091540613093394">"Dapat merekam audio"</string>
+    <string name="permission_nearby_devices_summary" msgid="931940524460876655">"Dapat menemukan, terhubung ke, dan menentukan posisi relatif perangkat di sekitar"</string>
     <string name="permission_notification_summary" msgid="884075314530071011">"Dapat membaca semua notifikasi, termasuk informasi seperti kontak, pesan, dan foto"</string>
     <string name="permission_app_streaming_summary" msgid="606923325679670624">"Streaming aplikasi ponsel"</string>
     <string name="permission_storage_summary" msgid="3918240895519506417"></string>
-    <string name="permission_nearby_device_streaming_summary" msgid="5776807830582725074">"Menstreaming konten ke perangkat di sekitar"</string>
+    <string name="permission_nearby_device_streaming_summary" msgid="8280824871197081246">"Menstreaming aplikasi dan fitur sistem lainnya dari ponsel Anda"</string>
+    <string name="device_type" product="default" msgid="8268703872070046263">"ponsel"</string>
+    <string name="device_type" product="tablet" msgid="5038791954983067774">"tablet"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-is/strings.xml b/packages/CompanionDeviceManager/res/values-is/strings.xml
index 81c7864..fc9f5a1 100644
--- a/packages/CompanionDeviceManager/res/values-is/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-is/strings.xml
@@ -17,43 +17,36 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Stjórnun fylgdartækja"</string>
-    <!-- no translation found for confirmation_title (8024993972587946678) -->
-    <skip />
+    <string name="confirmation_title" msgid="2244241995958340998">"Veita forritinu &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; aðgang að &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"úr"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Velja <xliff:g id="PROFILE_NAME">%1$s</xliff:g> sem &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; á að stjórna"</string>
-    <!-- no translation found for summary_watch (6566922405914995759) -->
-    <skip />
-    <!-- no translation found for summary_watch_single_device (7443464525873186735) -->
-    <skip />
-    <string name="profile_name_glasses" msgid="8488394059007275998">"gleraugu"</string>
-    <!-- no translation found for summary_glasses (3808267780579061241) -->
-    <skip />
-    <!-- no translation found for summary_glasses_single_device (7051392780285915640) -->
-    <skip />
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"Veldu tæki sem &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; á að stjórna"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Veldu <xliff:g id="PROFILE_NAME">%1$s</xliff:g> til að setja upp"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Þetta forrit fær heimild til að samstilla upplýsingar, t.d. nafn þess sem hringir, og fær aðgang að eftirfarandi heimildum í <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
+    <string name="confirmation_title_glasses" msgid="8288346850537727333">"Leyfa &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; að stjórna &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"tæki"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Þetta forrit fær aðgang að eftirfarandi heimildum í <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Veita &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; aðgang að þessum upplýsingum úr símanum þínum"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Þjónustur á milli tækja"</string>
-    <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> sendir beiðni um heimild til straumspilunar forrita á milli tækjanna þinna fyrir hönd <xliff:g id="DEVICE_TYPE">%2$s</xliff:g>"</string>
+    <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g> biður um heimild fyrir <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> til að streyma forritum á milli tækjanna þinna"</string>
     <string name="title_automotive_projection" msgid="3296005598978412847"></string>
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"Veita &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; aðgang að þessum upplýsingum úr símanum þínum"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Þjónusta Google Play"</string>
-    <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> sendir beiðni um aðgang að myndum, margmiðlunarefni og tilkynningum símans þíns fyrir hönd <xliff:g id="DEVICE_TYPE">%2$s</xliff:g>"</string>
-    <string name="title_nearby_device_streaming" msgid="179278282547719200">"Leyfa &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; að framkvæma þessa aðgerð í símanum"</string>
-    <string name="helper_title_nearby_device_streaming" msgid="6124438217620593669">"Þjónustur á milli tækja"</string>
-    <string name="helper_summary_nearby_device_streaming" msgid="5538329403511524333">"<xliff:g id="APP_NAME">%1$s</xliff:g> biður um heimild fyrir <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> til að streyma efni í nálægum tækjum"</string>
+    <string name="helper_summary_computer" msgid="8774832742608187072">"<xliff:g id="APP_NAME">%1$s</xliff:g> sendir beiðni um aðgang að myndum, margmiðlunarefni og tilkynningum símans þíns fyrir hönd <xliff:g id="DISPLAY_NAME">%2$s</xliff:g>"</string>
+    <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Leyfa &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; að framkvæma þessa aðgerð?"</string>
+    <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> biður um heimild fyrir <xliff:g id="DEVICE_NAME">%2$s</xliff:g> til að streyma forritum og öðrum kerfiseiginleikum í nálægum tækjum"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"tæki"</string>
-    <!-- no translation found for summary_generic_single_device (4735072202474939111) -->
-    <skip />
-    <!-- no translation found for summary_generic (4988130802522924650) -->
-    <skip />
+    <string name="summary_generic" msgid="1761976003668044801">"Þetta forrit mun geta samstillt upplýsingar, t.d. nafn þess sem hringir, á milli símans og valins tækis"</string>
     <string name="consent_yes" msgid="8344487259618762872">"Leyfa"</string>
     <string name="consent_no" msgid="2640796915611404382">"Ekki leyfa"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Hætta við"</string>
     <string name="consent_back" msgid="2560683030046918882">"Til baka"</string>
+    <string name="permission_expand" msgid="893185038020887411">"Stækka <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
+    <string name="permission_collapse" msgid="3320833884220844084">"Minnka <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Veita forritum í &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; sömu heimildir og í &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="permission_sync_summary" msgid="765497944331294275">"Þetta getur átt við um &lt;strong&gt;hljóðnema&lt;/strong&gt;, &lt;strong&gt;myndavél&lt;/strong&gt;, &lt;strong&gt;aðgang að staðsetningu&lt;/strong&gt; og aðrar heimildir fyrir viðkvæmu efni í &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;. &lt;br/&gt;&lt;br/&gt;Þú getur breytt þessum heimildum hvenær sem er í stillingunum í &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;."</string>
-    <string name="vendor_icon_description" msgid="4445875290032225965">"Tákn forrits"</string>
-    <string name="vendor_header_button_description" msgid="6566660389500630608">"Hnappur fyrir upplýsingar"</string>
+    <string name="vendor_header_button_description" msgid="7994879208461111473">"Frekari upplýsingar"</string>
     <string name="permission_phone" msgid="2661081078692784919">"Sími"</string>
     <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
     <string name="permission_contacts" msgid="3858319347208004438">"Tengiliðir"</string>
@@ -64,16 +57,18 @@
     <string name="permission_storage" msgid="6831099350839392343">"Myndir og efni"</string>
     <string name="permission_notification" msgid="693762568127741203">"Tilkynningar"</string>
     <string name="permission_app_streaming" msgid="6009695219091526422">"Forrit"</string>
-    <string name="permission_nearby_device_streaming" msgid="5868108148065023161">"Streymi í nálægum tækjum"</string>
+    <string name="permission_nearby_device_streaming" msgid="1023325519477349499">"Streymi"</string>
     <string name="permission_phone_summary" msgid="6684396967861278044">"Getur hringt og stjórnað símtölum"</string>
     <string name="permission_call_logs_summary" msgid="6186103394658755022">"Getur lesið og skrifað símtalaskrá símans"</string>
     <string name="permission_sms_summary" msgid="3508442683678912017">"Getur sent og skoðað SMS-skilaboð"</string>
     <string name="permission_contacts_summary" msgid="675861979475628708">"Hefur aðgang að tengiliðum"</string>
     <string name="permission_calendar_summary" msgid="6460000922511766226">"Hefur aðgang að dagatalinu"</string>
-    <string name="permission_microphone_summary" msgid="4241354865859396558">"Getur tekið upp hljóð með hljóðnemanum"</string>
+    <string name="permission_microphone_summary" msgid="3692091540613093394">"Getur tekið upp hljóð"</string>
     <string name="permission_nearby_devices_summary" msgid="931940524460876655">"Getur fundið, tengst og áætlað staðsetningu nálægra tækja"</string>
     <string name="permission_notification_summary" msgid="884075314530071011">"Getur lesið allar tilkynningar, þar á meðal upplýsingar á borð við tengiliði, skilaboð og myndir"</string>
     <string name="permission_app_streaming_summary" msgid="606923325679670624">"Streymdu forritum símans"</string>
     <string name="permission_storage_summary" msgid="3918240895519506417"></string>
-    <string name="permission_nearby_device_streaming_summary" msgid="5776807830582725074">"Streyma efni í nálægum tækjum"</string>
+    <string name="permission_nearby_device_streaming_summary" msgid="8280824871197081246">"Streymdu forritum og öðrum kerfiseiginleikum úr símanum"</string>
+    <string name="device_type" product="default" msgid="8268703872070046263">"símanum"</string>
+    <string name="device_type" product="tablet" msgid="5038791954983067774">"spjaldtölvunni"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-it/strings.xml b/packages/CompanionDeviceManager/res/values-it/strings.xml
index 0d2d9ad..be431b6 100644
--- a/packages/CompanionDeviceManager/res/values-it/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-it/strings.xml
@@ -16,37 +16,37 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_label" msgid="4470785958457506021">"Gestione dispositivi companion"</string>
-    <string name="confirmation_title" msgid="8024993972587946678">"Consenti all\'app &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; di accedere a &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="app_label" msgid="4470785958457506021">"Gestione dispositivi associati"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"Vuoi consentire all\'app &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; di accedere a &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"orologio"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Scegli un <xliff:g id="PROFILE_NAME">%1$s</xliff:g> da gestire con &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="6566922405914995759">"Questa app è necessaria per gestire <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> potrà sincronizzare informazioni, ad esempio il nome di un chiamante, interagire con le tue notifiche e accedere alle autorizzazioni Telefono, SMS, Contatti, Calendario, Registri chiamate e Dispositivi nelle vicinanze."</string>
-    <string name="summary_watch_single_device" msgid="7443464525873186735">"Questa app è necessaria per gestire <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> potrà sincronizzare informazioni, ad esempio il nome di un chiamante, e accedere alle seguenti autorizzazioni:"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"occhiali"</string>
-    <string name="summary_glasses" msgid="3808267780579061241">"Questa app è necessaria per gestire <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> potrà interagire con le tue notifiche e accedere alle autorizzazioni Telefono, SMS, Contatti, Microfono e Dispositivi nelle vicinanze."</string>
-    <string name="summary_glasses_single_device" msgid="7051392780285915640">"Questa app è necessaria per gestire <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> potrà interagire con le seguenti autorizzazioni:"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"Scegli un dispositivo che sia gestito da &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Scegli un <xliff:g id="PROFILE_NAME">%1$s</xliff:g> da configurare"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Questa app potrà sincronizzare informazioni, ad esempio il nome di un chiamante, e accedere alle seguenti autorizzazioni su <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
+    <string name="confirmation_title_glasses" msgid="8288346850537727333">"Vuoi consentire all\'app &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; di gestire &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"dispositivo"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Questa app potrà accedere alle seguenti autorizzazioni su <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Consenti a &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; di accedere a queste informazioni dal tuo telefono"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Servizi cross-device"</string>
-    <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> richiede per conto del tuo <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> l\'autorizzazione a trasmettere app in streaming tra i dispositivi"</string>
+    <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g> richiede per conto del tuo <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> l\'autorizzazione a trasmettere app in streaming tra i dispositivi"</string>
     <string name="title_automotive_projection" msgid="3296005598978412847"></string>
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"Consenti a &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; di accedere a questa informazione dal tuo telefono"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Google Play Services"</string>
-    <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> richiede per conto del tuo <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> l\'autorizzazione ad accedere a foto, contenuti multimediali e notifiche del telefono"</string>
-    <string name="title_nearby_device_streaming" msgid="179278282547719200">"Consenti all\'app &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; di compiere questa azione dal tuo telefono"</string>
-    <string name="helper_title_nearby_device_streaming" msgid="6124438217620593669">"Servizi cross-device"</string>
-    <string name="helper_summary_nearby_device_streaming" msgid="5538329403511524333">"<xliff:g id="APP_NAME">%1$s</xliff:g> richiede per conto di <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> l\'autorizzazione a trasmettere contenuti in streaming ai dispositivi nelle vicinanze"</string>
+    <string name="helper_summary_computer" msgid="8774832742608187072">"<xliff:g id="APP_NAME">%1$s</xliff:g> richiede per conto del tuo <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> l\'autorizzazione ad accedere a foto, contenuti multimediali e notifiche del telefono"</string>
+    <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Vuoi consentire a &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; di compiere questa azione?"</string>
+    <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> richiede per conto di <xliff:g id="DEVICE_NAME">%2$s</xliff:g> l\'autorizzazione a trasmettere in streaming app e altre funzionalità di sistema ai dispositivi nelle vicinanze"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"dispositivo"</string>
-    <string name="summary_generic_single_device" msgid="4735072202474939111">"Questa app potrà sincronizzare informazioni, ad esempio il nome di un chiamante, tra il telefono e <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string>
-    <string name="summary_generic" msgid="4988130802522924650">"Questa app potrà sincronizzare informazioni, ad esempio il nome di un chiamante, tra il telefono e il dispositivo scelto."</string>
+    <string name="summary_generic" msgid="1761976003668044801">"Questa app potrà sincronizzare informazioni, ad esempio il nome di un chiamante, tra il telefono e il dispositivo scelto"</string>
     <string name="consent_yes" msgid="8344487259618762872">"Consenti"</string>
     <string name="consent_no" msgid="2640796915611404382">"Non consentire"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Annulla"</string>
     <string name="consent_back" msgid="2560683030046918882">"Indietro"</string>
+    <string name="permission_expand" msgid="893185038020887411">"Espandi <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
+    <string name="permission_collapse" msgid="3320833884220844084">"Comprimi <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Vuoi dare alle app su &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; le stesse autorizzazioni che hai dato su &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="permission_sync_summary" msgid="765497944331294275">"Potrebbero essere incluse le autorizzazioni &lt;strong&gt;Microfono&lt;/strong&gt;, &lt;strong&gt;Fotocamera&lt;/strong&gt; e &lt;strong&gt;Accesso alla posizione&lt;/strong&gt;, oltre ad altre autorizzazioni sensibili su &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;. &lt;br/&gt;&lt;br/&gt;Puoi cambiare queste autorizzazioni in qualsiasi momento nelle Impostazioni su &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;."</string>
-    <string name="vendor_icon_description" msgid="4445875290032225965">"Icona dell\'app"</string>
-    <string name="vendor_header_button_description" msgid="6566660389500630608">"Pulsante Altre informazioni"</string>
+    <string name="vendor_header_button_description" msgid="7994879208461111473">"Maggiori informazioni"</string>
     <string name="permission_phone" msgid="2661081078692784919">"Telefono"</string>
     <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
     <string name="permission_contacts" msgid="3858319347208004438">"Contatti"</string>
@@ -57,16 +57,18 @@
     <string name="permission_storage" msgid="6831099350839392343">"Foto e contenuti multimediali"</string>
     <string name="permission_notification" msgid="693762568127741203">"Notifiche"</string>
     <string name="permission_app_streaming" msgid="6009695219091526422">"App"</string>
-    <string name="permission_nearby_device_streaming" msgid="5868108148065023161">"Streaming dispos. in vicinanze"</string>
+    <string name="permission_nearby_device_streaming" msgid="1023325519477349499">"Streaming"</string>
     <string name="permission_phone_summary" msgid="6684396967861278044">"Consente di effettuare e gestire telefonate"</string>
     <string name="permission_call_logs_summary" msgid="6186103394658755022">"Consente di leggere e modificare il registro chiamate del telefono"</string>
     <string name="permission_sms_summary" msgid="3508442683678912017">"Consente di inviare e visualizzare SMS"</string>
     <string name="permission_contacts_summary" msgid="675861979475628708">"Consente di accedere ai tuoi contatti"</string>
     <string name="permission_calendar_summary" msgid="6460000922511766226">"Consente di accedere al tuo calendario"</string>
-    <string name="permission_microphone_summary" msgid="4241354865859396558">"Consente di registrare audio usando il microfono"</string>
+    <string name="permission_microphone_summary" msgid="3692091540613093394">"Consente di registrare audio"</string>
     <string name="permission_nearby_devices_summary" msgid="931940524460876655">"Consente di trovare e connettersi a dispositivi nelle vicinanze, nonché di stabilirne la posizione relativa"</string>
     <string name="permission_notification_summary" msgid="884075314530071011">"Puoi leggere tutte le notifiche, incluse le informazioni come contatti, messaggi e foto"</string>
     <string name="permission_app_streaming_summary" msgid="606923325679670624">"Trasmetti in streaming le app del tuo telefono"</string>
     <string name="permission_storage_summary" msgid="3918240895519506417"></string>
-    <string name="permission_nearby_device_streaming_summary" msgid="5776807830582725074">"Consente di trasmettere contenuti in streaming a un dispositivo nelle vicinanze"</string>
+    <string name="permission_nearby_device_streaming_summary" msgid="8280824871197081246">"Trasmettere in streaming app e altre funzionalità di sistema dal telefono"</string>
+    <string name="device_type" product="default" msgid="8268703872070046263">"telefono"</string>
+    <string name="device_type" product="tablet" msgid="5038791954983067774">"tablet"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-iw/strings.xml b/packages/CompanionDeviceManager/res/values-iw/strings.xml
index df7ab6d..8982660 100644
--- a/packages/CompanionDeviceManager/res/values-iw/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-iw/strings.xml
@@ -17,36 +17,36 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"ניהול מכשיר מותאם"</string>
-    <string name="confirmation_title" msgid="8024993972587946678">"‏אישור לאפליקציה ‎&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&amp;g;‎‏ לגשת אל ‎&lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;‎‏"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"‏לאפשר לאפליקציה &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; לגשת אל &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"שעון"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"‏בחירת <xliff:g id="PROFILE_NAME">%1$s</xliff:g> לניהול באמצעות &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="6566922405914995759">"‏האפליקציה הזו נחוצה כדי לנהל את <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. האפליקציה <xliff:g id="APP_NAME">%2$s</xliff:g> תוכל לסנכרן מידע, כמו השם של מישהו שמתקשר, לבצע פעולות בהתראות ולקבל הרשאות גישה לטלפון, ל-SMS לאנשי הקשר, למיקרופון ולמכשירים בקרבת מקום."</string>
-    <string name="summary_watch_single_device" msgid="7443464525873186735">"האפליקציה הזו נחוצה כדי לנהל את <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. האפליקציה <xliff:g id="APP_NAME">%2$s</xliff:g> תוכל לסנכרן מידע, כמו השם של מישהו שמתקשר, ולקיים אינטראקציה עם ההרשאות הבאות:"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"משקפיים"</string>
-    <string name="summary_glasses" msgid="3808267780579061241">"‏האפליקציה הזו נחוצה כדי לנהל את <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. האפליקציה <xliff:g id="APP_NAME">%2$s</xliff:g> תוכל לבצע פעולות בהתראות ותקבל הרשאות גישה לטלפון, ל-SMS לאנשי הקשר, למיקרופון ולמכשירים בקרבת מקום."</string>
-    <string name="summary_glasses_single_device" msgid="7051392780285915640">"האפליקציה הזו נחוצה כדי לנהל את <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. האפליקציה <xliff:g id="APP_NAME">%2$s</xliff:g> תוכל לקיים אינטראקציה עם ההרשאות הבאות:"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"‏בחירה של מכשיר לניהול באמצעות &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"בחירת <xliff:g id="PROFILE_NAME">%1$s</xliff:g> להגדרה"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"האפליקציה הזו תוכל לסנכרן מידע, כמו השם של מישהו שמתקשר, ולגשת להרשאות האלה ב<xliff:g id="DEVICE_NAME">%1$s</xliff:g> שלך"</string>
+    <string name="confirmation_title_glasses" msgid="8288346850537727333">"‏מתן הרשאה לאפליקציה ‎&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&amp;g;‎‏ לנהל את ‎&lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;‎‏"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"מכשיר"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"האפליקציה הזו תוכל לגשת להרשאות האלה ב<xliff:g id="DEVICE_NAME">%1$s</xliff:g> שלך"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"‏מתן אישור לאפליקציה &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; לגשת למידע הזה מהטלפון שלך"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"שירותים למספר מכשירים"</string>
-    <string name="helper_summary_app_streaming" msgid="5977509499890099">"האפליקציה <xliff:g id="APP_NAME">%1$s</xliff:g> מבקשת הרשאה עבור מכשיר <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> כדי לשדר אפליקציות בין המכשירים שלך"</string>
+    <string name="helper_summary_app_streaming" msgid="2396773196949578425">"האפליקציה <xliff:g id="APP_NAME">%1$s</xliff:g> מבקשת הרשאה עבור המכשיר <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> כדי לשדר אפליקציות בין המכשירים שלך"</string>
     <string name="title_automotive_projection" msgid="3296005598978412847"></string>
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"‏מתן אישור לאפליקציה &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; לגשת למידע הזה מהטלפון שלך"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Google Play Services"</string>
-    <string name="helper_summary_computer" msgid="9050724687678157852">"האפליקציה <xliff:g id="APP_NAME">%1$s</xliff:g> מבקשת הרשאה עבור מכשיר <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> כדי לגשת לתמונות, למדיה ולהתראות בטלפון שלך"</string>
-    <string name="title_nearby_device_streaming" msgid="179278282547719200">"‏הרשאה לאפליקציה &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; לבצע את הפעולה הזו מהטלפון"</string>
-    <string name="helper_title_nearby_device_streaming" msgid="6124438217620593669">"שירותים למספר מכשירים"</string>
-    <string name="helper_summary_nearby_device_streaming" msgid="5538329403511524333">"האפליקציה <xliff:g id="APP_NAME">%1$s</xliff:g> מבקשת הרשאה עבור מכשיר <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> כדי להעביר תוכן בסטרימינג למכשירים בקרבת מקום."</string>
+    <string name="helper_summary_computer" msgid="8774832742608187072">"האפליקציה <xliff:g id="APP_NAME">%1$s</xliff:g> מבקשת הרשאה עבור המכשיר <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> כדי לגשת לתמונות, למדיה ולהתראות בטלפון שלך"</string>
+    <string name="title_nearby_device_streaming" msgid="7269956847378799794">"‏לתת הרשאה למכשיר &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; לבצע את הפעולה הזו?"</string>
+    <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"האפליקציה <xliff:g id="APP_NAME">%1$s</xliff:g> מבקשת הרשאה עבור <xliff:g id="DEVICE_NAME">%2$s</xliff:g> כדי להעביר אפליקציות ותכונות מערכת אחרות בסטרימינג למכשירים בקרבת מקום"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"מכשיר"</string>
-    <string name="summary_generic_single_device" msgid="4735072202474939111">"האפליקציה הזו תוכל לסנכרן מידע, כמו השם של מישהו שמתקשר, מהטלפון שלך ל-<xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string>
-    <string name="summary_generic" msgid="4988130802522924650">"האפליקציה הזו תוכל לסנכרן מידע, כמו השם של מישהו שמתקשר, מהטלפון שלך למכשיר שבחרת."</string>
+    <string name="summary_generic" msgid="1761976003668044801">"האפליקציה הזו תוכל לסנכרן מידע, כמו השם של מישהו שמתקשר, מהטלפון שלך למכשיר שבחרת"</string>
     <string name="consent_yes" msgid="8344487259618762872">"יש אישור"</string>
     <string name="consent_no" msgid="2640796915611404382">"אין אישור"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"ביטול"</string>
     <string name="consent_back" msgid="2560683030046918882">"חזרה"</string>
-    <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"‏האם לתת לאפליקציות ב-‎&lt;strong&gt;‎‏<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>‏‎&lt;/strong&gt;‎‏את אותן הרשאות כמו ב-‏‎&lt;strong&gt;‎‏<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>‏‎&lt;/strong&gt;‎‏?"</string>
+    <string name="permission_expand" msgid="893185038020887411">"הרחבה של <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
+    <string name="permission_collapse" msgid="3320833884220844084">"כיווץ של <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
+    <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"‏האם לתת לאפליקציות ב-‎&lt;strong&gt;‎‏<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>‏‎&lt;/strong&gt;‎‏ את אותן הרשאות כמו ב-‏‎&lt;strong&gt;‎‏<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>‏‎&lt;/strong&gt;‎‏?"</string>
     <string name="permission_sync_summary" msgid="765497944331294275">"‏ההרשאות עשויות לכלול גישה ל&lt;strong&gt;מיקרופון&lt;/strong&gt;, ל&lt;strong&gt;מצלמה&lt;/strong&gt;, ול&lt;strong&gt;מיקום&lt;/strong&gt;, וכן גישה למידע רגיש אחר ב-&lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;. &lt;br/&gt;&lt;br/&gt;אפשר לשנות את ההרשאות האלה בכל שלב בהגדרות של &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;."</string>
-    <string name="vendor_icon_description" msgid="4445875290032225965">"סמל האפליקציה"</string>
-    <string name="vendor_header_button_description" msgid="6566660389500630608">"לחצן מידע נוסף"</string>
+    <string name="vendor_header_button_description" msgid="7994879208461111473">"מידע נוסף"</string>
     <string name="permission_phone" msgid="2661081078692784919">"טלפון"</string>
     <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
     <string name="permission_contacts" msgid="3858319347208004438">"אנשי קשר"</string>
@@ -57,16 +57,18 @@
     <string name="permission_storage" msgid="6831099350839392343">"תמונות ומדיה"</string>
     <string name="permission_notification" msgid="693762568127741203">"התראות"</string>
     <string name="permission_app_streaming" msgid="6009695219091526422">"אפליקציות"</string>
-    <string name="permission_nearby_device_streaming" msgid="5868108148065023161">"סטרימינג למכשירים בקרבת מקום"</string>
+    <string name="permission_nearby_device_streaming" msgid="1023325519477349499">"סטרימינג"</string>
     <string name="permission_phone_summary" msgid="6684396967861278044">"אפשרות לבצע ולנהל שיחות טלפון"</string>
     <string name="permission_call_logs_summary" msgid="6186103394658755022">"אפשרות ולקרוא ולכתוב נתונים ביומן השיחות של הטלפון"</string>
     <string name="permission_sms_summary" msgid="3508442683678912017">"‏אפשרות לשלוח הודעות SMS ולצפות בהן"</string>
     <string name="permission_contacts_summary" msgid="675861979475628708">"גישה לאנשי הקשר"</string>
     <string name="permission_calendar_summary" msgid="6460000922511766226">"אפשרות לגשת ליומן"</string>
-    <string name="permission_microphone_summary" msgid="4241354865859396558">"הרשאה להשתמש במיקרופון כדי להקליט אודיו"</string>
+    <string name="permission_microphone_summary" msgid="3692091540613093394">"המיקרופון יכול להקליט אודיו"</string>
     <string name="permission_nearby_devices_summary" msgid="931940524460876655">"אפשרות למצוא מכשירים בקרבת מקום, להתחבר אליהם ולהעריך את המיקום היחסי שלהם"</string>
     <string name="permission_notification_summary" msgid="884075314530071011">"גישת קריאה לכל ההתראות, כולל מידע כמו אנשי קשר, הודעות ותמונות."</string>
     <string name="permission_app_streaming_summary" msgid="606923325679670624">"שידור אפליקציות מהטלפון"</string>
     <string name="permission_storage_summary" msgid="3918240895519506417"></string>
-    <string name="permission_nearby_device_streaming_summary" msgid="5776807830582725074">"העברת תוכן בסטרימינג למכשירים בקרבת מקום"</string>
+    <string name="permission_nearby_device_streaming_summary" msgid="8280824871197081246">"העברה של אפליקציות ותכונות מערכת אחרות בסטרימינג מהטלפון"</string>
+    <string name="device_type" product="default" msgid="8268703872070046263">"טלפון"</string>
+    <string name="device_type" product="tablet" msgid="5038791954983067774">"טאבלט"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-ja/strings.xml b/packages/CompanionDeviceManager/res/values-ja/strings.xml
index 6eda60b..186944a 100644
--- a/packages/CompanionDeviceManager/res/values-ja/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ja/strings.xml
@@ -17,43 +17,36 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"コンパニオン デバイス マネージャー"</string>
-    <!-- no translation found for confirmation_title (8024993972587946678) -->
-    <skip />
+    <string name="confirmation_title" msgid="2244241995958340998">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; アプリに &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; へのアクセスを許可しますか?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"ウォッチ"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"&lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; の管理対象となる<xliff:g id="PROFILE_NAME">%1$s</xliff:g>の選択"</string>
-    <!-- no translation found for summary_watch (6566922405914995759) -->
-    <skip />
-    <!-- no translation found for summary_watch_single_device (7443464525873186735) -->
-    <skip />
-    <string name="profile_name_glasses" msgid="8488394059007275998">"眼鏡"</string>
-    <!-- no translation found for summary_glasses (3808267780579061241) -->
-    <skip />
-    <!-- no translation found for summary_glasses_single_device (7051392780285915640) -->
-    <skip />
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; の管理対象となるデバイスの選択"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"設定する<xliff:g id="PROFILE_NAME">%1$s</xliff:g>の選択"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"このアプリは、通話相手の名前などの情報を同期したり、<xliff:g id="DEVICE_NAME">%1$s</xliff:g>の以下の権限にアクセスしたりできるようになります"</string>
+    <string name="confirmation_title_glasses" msgid="8288346850537727333">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; に &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; の管理を許可しますか?"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"デバイス"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"このアプリは、<xliff:g id="DEVICE_NAME">%1$s</xliff:g>の以下の権限にアクセスできるようになります"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"スマートフォンのこの情報へのアクセスを &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; に許可"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"クロスデバイス サービス"</string>
-    <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> が <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> に代わってデバイス間でアプリをストリーミングする権限をリクエストしています"</string>
+    <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g> が <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> に代わってデバイス間でアプリをストリーミングする権限をリクエストしています"</string>
     <string name="title_automotive_projection" msgid="3296005598978412847"></string>
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"スマートフォンのこの情報へのアクセスを &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; に許可"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Google Play 開発者サービス"</string>
-    <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> が <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> に代わってスマートフォンの写真、メディア、通知にアクセスする権限をリクエストしています"</string>
-    <string name="title_nearby_device_streaming" msgid="179278282547719200">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; に、スマートフォンからこの操作を実行することを許可します"</string>
-    <string name="helper_title_nearby_device_streaming" msgid="6124438217620593669">"クロスデバイス サービス"</string>
-    <string name="helper_summary_nearby_device_streaming" msgid="5538329403511524333">"<xliff:g id="APP_NAME">%1$s</xliff:g> が <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> に代わって付近のデバイスにコンテンツをストリーミングする権限をリクエストしています"</string>
+    <string name="helper_summary_computer" msgid="8774832742608187072">"<xliff:g id="APP_NAME">%1$s</xliff:g> が <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> に代わってスマートフォンの写真、メディア、通知にアクセスする権限をリクエストしています"</string>
+    <string name="title_nearby_device_streaming" msgid="7269956847378799794">"&lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; にこの操作の実行を許可しますか?"</string>
+    <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> が <xliff:g id="DEVICE_NAME">%2$s</xliff:g> に代わって、アプリやその他のシステム機能を付近のデバイスにストリーミングする権限をリクエストしています"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"デバイス"</string>
-    <!-- no translation found for summary_generic_single_device (4735072202474939111) -->
-    <skip />
-    <!-- no translation found for summary_generic (4988130802522924650) -->
-    <skip />
+    <string name="summary_generic" msgid="1761976003668044801">"このアプリは、あなたのスマートフォンと選択したデバイスとの間で、通話相手の名前などの情報を同期できるようになります"</string>
     <string name="consent_yes" msgid="8344487259618762872">"許可"</string>
     <string name="consent_no" msgid="2640796915611404382">"許可しない"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"キャンセル"</string>
     <string name="consent_back" msgid="2560683030046918882">"戻る"</string>
+    <string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>を開く"</string>
+    <string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>を閉じる"</string>
     <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"&lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; のアプリに &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; の場合と同じ権限を付与しますか?"</string>
     <string name="permission_sync_summary" msgid="765497944331294275">"これには、&lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt; の&lt;strong&gt;マイク&lt;/strong&gt;、&lt;strong&gt;カメラ&lt;/strong&gt;、&lt;strong&gt;位置情報へのアクセス&lt;/strong&gt;や、その他の機密情報に関わる権限が含まれる可能性があります。&lt;br/&gt;&lt;br/&gt;これらの権限は &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt; の [設定] でいつでも変更できます。"</string>
-    <string name="vendor_icon_description" msgid="4445875290032225965">"アプリのアイコン"</string>
-    <string name="vendor_header_button_description" msgid="6566660389500630608">"詳細情報ボタン"</string>
+    <string name="vendor_header_button_description" msgid="7994879208461111473">"詳細情報"</string>
     <string name="permission_phone" msgid="2661081078692784919">"電話"</string>
     <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
     <string name="permission_contacts" msgid="3858319347208004438">"連絡先"</string>
@@ -64,16 +57,18 @@
     <string name="permission_storage" msgid="6831099350839392343">"写真とメディア"</string>
     <string name="permission_notification" msgid="693762568127741203">"通知"</string>
     <string name="permission_app_streaming" msgid="6009695219091526422">"アプリ"</string>
-    <string name="permission_nearby_device_streaming" msgid="5868108148065023161">"付近のデバイスへのストリーミング"</string>
+    <string name="permission_nearby_device_streaming" msgid="1023325519477349499">"ストリーミング"</string>
     <string name="permission_phone_summary" msgid="6684396967861278044">"電話の発信と管理を行えます"</string>
     <string name="permission_call_logs_summary" msgid="6186103394658755022">"通話履歴の読み取りと書き込みを行えます"</string>
     <string name="permission_sms_summary" msgid="3508442683678912017">"SMS メッセージの送信、表示を行えます"</string>
     <string name="permission_contacts_summary" msgid="675861979475628708">"連絡先にアクセスできます"</string>
     <string name="permission_calendar_summary" msgid="6460000922511766226">"カレンダーにアクセスできます"</string>
-    <string name="permission_microphone_summary" msgid="4241354865859396558">"マイクを使って録音できます"</string>
+    <string name="permission_microphone_summary" msgid="3692091540613093394">"録音できる"</string>
     <string name="permission_nearby_devices_summary" msgid="931940524460876655">"付近のデバイスの検出、接続、相対位置の特定を行えます"</string>
     <string name="permission_notification_summary" msgid="884075314530071011">"連絡先、メッセージ、写真に関する情報を含め、すべての通知を読み取ることができます"</string>
     <string name="permission_app_streaming_summary" msgid="606923325679670624">"スマートフォンのアプリをストリーミングします"</string>
     <string name="permission_storage_summary" msgid="3918240895519506417"></string>
-    <string name="permission_nearby_device_streaming_summary" msgid="5776807830582725074">"付近のデバイスへのコンテンツのストリーミング"</string>
+    <string name="permission_nearby_device_streaming_summary" msgid="8280824871197081246">"アプリやその他のシステム機能をスマートフォンからストリーミングする"</string>
+    <string name="device_type" product="default" msgid="8268703872070046263">"スマートフォン"</string>
+    <string name="device_type" product="tablet" msgid="5038791954983067774">"タブレット"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-ka/strings.xml b/packages/CompanionDeviceManager/res/values-ka/strings.xml
index 21b703d..a7c6042 100644
--- a/packages/CompanionDeviceManager/res/values-ka/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ka/strings.xml
@@ -17,36 +17,36 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"კომპანიონი მოწყობილობების მენეჯერი"</string>
-    <string name="confirmation_title" msgid="8024993972587946678">"დაუშვით &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>-ის&lt;/strong&gt; წვდომა &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>-ზე&lt;/strong&gt;"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"ნებას რთავთ &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; აპს, წვდომა ჰქონდეს &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; მოწყობილობაზე?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"საათი"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"აირჩიეთ <xliff:g id="PROFILE_NAME">%1$s</xliff:g>, რომელიც უნდა მართოს &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;-მა"</string>
-    <string name="summary_watch" msgid="6566922405914995759">"ეს აპი საჭიროა თქვენი <xliff:g id="DEVICE_NAME">%1$s</xliff:g>-ის სამართავად. <xliff:g id="APP_NAME">%2$s</xliff:g>-ს ექნება უფლება, მოახდინოს ისეთი ინფორმაციის სინქრონიზაცია, როგორიც იმ ადამიანის სახელია, რომელიც რეკავს, მოახდინოს ინტერაქცია თქვენს შეტყობინებებთან და ჰქონდეს წვდომა თქვენს ტელეფონზე, SMS-ებზე, კონტაქტებზე, კალენდარზე, ზარების ჟურნალებზე და ახლომახლო მოწყობილობების ნებართვებზე."</string>
-    <string name="summary_watch_single_device" msgid="7443464525873186735">"ეს აპი საჭიროა თქვენი <xliff:g id="DEVICE_NAME">%1$s</xliff:g>-ის სამართავად. <xliff:g id="APP_NAME">%2$s</xliff:g>-ს ექნება უფლება, მოახდინოს ისეთი ინფორმციის სინქრონიზაცია, როგორიც იმ ადამიანის სახელია, რომელიც რეკავს, და ჰქონდეს წვდომა შემდეგ ნებართვებზე:"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"სათვალე"</string>
-    <string name="summary_glasses" msgid="3808267780579061241">"ეს აპი საჭიროა თქვენი <xliff:g id="DEVICE_NAME">%1$s</xliff:g>-ის სამართავად. <xliff:g id="APP_NAME">%2$s</xliff:g> შეძლებს თქვენს შეტყობინებებთან ინტერაქციას და თქვენს ტელეფონზე, SMS-ებზე, კონტაქტებზე, მიკროფონსა და ახლომახლო მოწყობილობების ნებართვებზე წვდომას."</string>
-    <string name="summary_glasses_single_device" msgid="7051392780285915640">"ეს აპი საჭიროა <xliff:g id="DEVICE_NAME">%1$s</xliff:g>-ის სამართავად. <xliff:g id="APP_NAME">%2$s</xliff:g> შეძლებს ინტერაქციას შემდეგი ნებართვებით:"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"აირჩიეთ მოწყობილობა, რომელიც უნდა მართოს &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; აპმა"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"აირჩიეთ <xliff:g id="PROFILE_NAME">%1$s</xliff:g> დასაყენებლად"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"ეს აპი შეძლებს, დაასინქრონოს ინფორმაცია, მაგალითად, შემომავალი ზარის ავტორის სახელი და წვდომა იქონიოს ამ ნებართვებზე თქვენს <xliff:g id="DEVICE_NAME">%1$s</xliff:g>-ში"</string>
+    <string name="confirmation_title_glasses" msgid="8288346850537727333">"ნება დართეთ &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>-ს&lt;/strong&gt; მართოს &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"მოწყობილობა"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"ეს აპი შეძლებს ამ ნებართვებზე წვდომას თქვენს <xliff:g id="DEVICE_NAME">%1$s</xliff:g>-ში"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"ნება დართეთ, რომ &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; აპს ჰქონდეს ამ ინფორმაციაზე წვდომა თქვენი ტელეფონიდან"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"მოწყობილობათშორისი სერვისები"</string>
-    <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> ითხოვს უფლებას თქვენი <xliff:g id="DEVICE_TYPE">%2$s</xliff:g>-ის სახელით, რომ მოწყობილობებს შორის აპების სტრიმინგი შეძლოს"</string>
+    <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g> ითხოვს უფლებას თქვენი <xliff:g id="DISPLAY_NAME">%2$s</xliff:g>-ის სახელით, რომ მოწყობილობებს შორის სტრიმინგი შეძლოს"</string>
     <string name="title_automotive_projection" msgid="3296005598978412847"></string>
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"ნება დართეთ, რომ &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; აპს ჰქონდეს ამ ინფორმაციაზე წვდომა თქვენი ტელეფონიდან"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Google Play services"</string>
-    <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> ითხოვს უფლებას თქვენი <xliff:g id="DEVICE_TYPE">%2$s</xliff:g>-ის სახელით, რომ წვდომა ჰქონდეს თქვენი ტელეფონის ფოტოებზე, მედიასა და შეტყობინებებზე"</string>
-    <string name="title_nearby_device_streaming" msgid="179278282547719200">"ნება მიეცით <xliff:g id="APP_NAME">%1$s</xliff:g> თქვენი ტელეფონიდან ამ მოქმედების შესასრულებლად"</string>
-    <string name="helper_title_nearby_device_streaming" msgid="6124438217620593669">"მოწყობილობათშორისი სერვისები"</string>
-    <string name="helper_summary_nearby_device_streaming" msgid="5538329403511524333">"<xliff:g id="APP_NAME">%1$s</xliff:g> ითხოვს ნებართვას თქვენი სახელით<xliff:g id="DEVICE_TYPE">%2$s</xliff:g> კონტენტის სტრიმინგისთვის ახლომდებარე მოწყობილობებზე"</string>
+    <string name="helper_summary_computer" msgid="8774832742608187072">"<xliff:g id="APP_NAME">%1$s</xliff:g> ითხოვს უფლებას თქვენი <xliff:g id="DISPLAY_NAME">%2$s</xliff:g>-ის სახელით, რომ წვდომა ჰქონდეს თქვენი ტელეფონის ფოტოებზე, მედიასა და შეტყობინებებზე"</string>
+    <string name="title_nearby_device_streaming" msgid="7269956847378799794">"გსურთ ნება მისცეთ &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>-ს&lt;/strong&gt; ამ მოქმედების შესასრულებლად?"</string>
+    <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> ითხოვს თქვენი <xliff:g id="DEVICE_NAME">%2$s</xliff:g>-ის სახელით აპების და სისტემის სხვა ფუნქციების ახლომახლო მოწყობილობებზე სტრიმინგის ნებართვას"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"მოწყობილობა"</string>
-    <string name="summary_generic_single_device" msgid="4735072202474939111">"ეს აპი შეძლებს, მოახდინოს ისეთი ინფორმაციის სინქრონიზაცია, როგორიც იმ ადამიანის სახელია, რომელიც რეკავს, თქვენს ტელეფონსა და <xliff:g id="DEVICE_NAME">%1$s</xliff:g>-ს შორის."</string>
-    <string name="summary_generic" msgid="4988130802522924650">"ეს აპი შეძლებს, მოახდინოს ისეთი ინფორმაციის სინქრონიზაცია, როგორიც იმ ადამიანის სახელია, რომელიც რეკავს, თქვენს ტელეფონსა და არჩეულ მოწყობილობას შორის."</string>
+    <string name="summary_generic" msgid="1761976003668044801">"ეს აპი შეძლებს ინფორმაციის სინქრონიზებას თქვენს ტელეფონსა და თქვენ მიერ არჩეულ მოწყობილობას შორის, მაგალითად, იმ ადამიანის სახელის, რომელიც გირეკავთ"</string>
     <string name="consent_yes" msgid="8344487259618762872">"დაშვება"</string>
     <string name="consent_no" msgid="2640796915611404382">"არ დაიშვას"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"გაუქმება"</string>
     <string name="consent_back" msgid="2560683030046918882">"უკან"</string>
+    <string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>-ის გაფართოება"</string>
+    <string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>-ის ჩაკეცვა"</string>
     <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"გსურთ აპებს &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt;-ზე იგივე ნებართვები მიანიჭოთ, როგორიც აქვს &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;-ზე?"</string>
     <string name="permission_sync_summary" msgid="765497944331294275">"ეს შესაძლოა მოიცავდეს შემდეგს: &lt;strong&gt;მიკროფონი&lt;/strong&gt;, &lt;strong&gt;კამერა&lt;/strong&gt; და &lt;strong&gt;მდებარეობაზე წვდომა&lt;/strong&gt; და &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;-ის სხვა ნებართვა სენსიტიურ ინფორმაციაზე. &lt;br/&gt;&lt;br/&gt;ამ ნებართვების შეცვლა ნებისმიერ დროს შეგიძლიათ &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;-ის პარამეტრებიდან."</string>
-    <string name="vendor_icon_description" msgid="4445875290032225965">"აპის ხატულა"</string>
-    <string name="vendor_header_button_description" msgid="6566660389500630608">"დამატებითი ინფორმაციის ღილაკი"</string>
+    <string name="vendor_header_button_description" msgid="7994879208461111473">"მეტი ინფორმაცია"</string>
     <string name="permission_phone" msgid="2661081078692784919">"Phone"</string>
     <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
     <string name="permission_contacts" msgid="3858319347208004438">"კონტაქტები"</string>
@@ -57,16 +57,18 @@
     <string name="permission_storage" msgid="6831099350839392343">"ფოტოები და მედია"</string>
     <string name="permission_notification" msgid="693762568127741203">"შეტყობინებები"</string>
     <string name="permission_app_streaming" msgid="6009695219091526422">"აპები"</string>
-    <string name="permission_nearby_device_streaming" msgid="5868108148065023161">"ახლომდებარე მოწყობილობის სტრიმინგი"</string>
+    <string name="permission_nearby_device_streaming" msgid="1023325519477349499">"სტრიმინგი"</string>
     <string name="permission_phone_summary" msgid="6684396967861278044">"შეძლოს სატელეფონო ზარების განხორციელება და მართვა"</string>
     <string name="permission_call_logs_summary" msgid="6186103394658755022">"შეეძლოს ზარების ჟურნალის წაკითხვა და მასში ჩაწერა"</string>
     <string name="permission_sms_summary" msgid="3508442683678912017">"შეძლოს SMS ტექსტური შეტყობინებების გაგზავნა და მიღება"</string>
     <string name="permission_contacts_summary" msgid="675861979475628708">"ჰქონდეს თქვენს კონტაქტებზე წვდომის საშუალება"</string>
     <string name="permission_calendar_summary" msgid="6460000922511766226">"ჰქონდეს თქვენს კალენდარზე წვდომის საშუალება"</string>
-    <string name="permission_microphone_summary" msgid="4241354865859396558">"შეუძლია აუდიოს ჩაწერა მიკროფონის გამოყენებით"</string>
+    <string name="permission_microphone_summary" msgid="3692091540613093394">"შეიძლებოდეს აუდიოს ჩაწერა"</string>
     <string name="permission_nearby_devices_summary" msgid="931940524460876655">"შეძლოს ახლომახლო მოწყობილობების აღმოჩენა, მათთან დაკავშირება და მათი შედარებითი პოზიციის დადგენა"</string>
     <string name="permission_notification_summary" msgid="884075314530071011">"შეუძლია წაიკითხოს ყველა შეტყობინება, მათ შორის ისეთი ინფორმაცია, როგორიცაა კონტაქტები, ტექსტური შეტყობინებები და ფოტოები"</string>
     <string name="permission_app_streaming_summary" msgid="606923325679670624">"თქვენი ტელეფონის აპების სტრიმინგი"</string>
     <string name="permission_storage_summary" msgid="3918240895519506417"></string>
-    <string name="permission_nearby_device_streaming_summary" msgid="5776807830582725074">"კონტენტის სტრიმინგი ახლომდებარე მოწყობილობაზე"</string>
+    <string name="permission_nearby_device_streaming_summary" msgid="8280824871197081246">"აწარმოეთ აპების და სისტემის სხვა ფუნქციების სტრიმინგი თქვენი ტელეფონიდან"</string>
+    <string name="device_type" product="default" msgid="8268703872070046263">"ტელეფონი"</string>
+    <string name="device_type" product="tablet" msgid="5038791954983067774">"ტაბლეტი"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-kk/strings.xml b/packages/CompanionDeviceManager/res/values-kk/strings.xml
index 622834b..5c6d24a 100644
--- a/packages/CompanionDeviceManager/res/values-kk/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-kk/strings.xml
@@ -17,43 +17,36 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Companion Device Manager"</string>
-    <!-- no translation found for confirmation_title (8024993972587946678) -->
-    <skip />
+    <string name="confirmation_title" msgid="2244241995958340998">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; қолданбасына &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;? құрылғысына кіруге рұқсат беріңіз"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"сағат"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"&lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; арқылы басқарылатын <xliff:g id="PROFILE_NAME">%1$s</xliff:g> құрылғысын таңдаңыз"</string>
-    <!-- no translation found for summary_watch (6566922405914995759) -->
-    <skip />
-    <!-- no translation found for summary_watch_single_device (7443464525873186735) -->
-    <skip />
-    <string name="profile_name_glasses" msgid="8488394059007275998">"көзілдірік"</string>
-    <!-- no translation found for summary_glasses (3808267780579061241) -->
-    <skip />
-    <!-- no translation found for summary_glasses_single_device (7051392780285915640) -->
-    <skip />
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; арқылы басқарылатын құрылғыны таңдаңыз"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Реттеу үшін <xliff:g id="PROFILE_NAME">%1$s</xliff:g> таңдаңыз"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Бұл қолданба қоңырау шалушының аты сияқты деректі синхрондай алады және <xliff:g id="DEVICE_NAME">%1$s</xliff:g> құрылғысындағы мына рұқсаттарды пайдалана алады."</string>
+    <string name="confirmation_title_glasses" msgid="8288346850537727333">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; қолданбасына &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; құрылғысын басқаруға рұқсат беру керек пе?"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"құрылғы"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Бұл қолданба <xliff:g id="DEVICE_NAME">%1$s</xliff:g> құрылғысында осы рұқсаттарды пайдалана алады."</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; қолданбасына телефоныңыздағы осы ақпаратты пайдалануға рұқсат беріңіз."</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Аралық құрылғы қызметтері"</string>
-    <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> қолданбасы <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> атынан құрылғылар арасында қолданбалар трансляциялау үшін рұқсат сұрайды."</string>
+    <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g> қолданбасы <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> атынан құрылғылар арасында қолданбалар трансляциялау үшін рұқсат сұрайды."</string>
     <string name="title_automotive_projection" msgid="3296005598978412847"></string>
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; қолданбасына телефоныңыздағы осы ақпаратты пайдалануға рұқсат беріңіз."</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Google Play қызметтері"</string>
-    <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> қолданбасы <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> атынан телефондағы фотосуреттерді, медиафайлдар мен хабарландыруларды пайдалану үшін рұқсат сұрайды."</string>
-    <string name="title_nearby_device_streaming" msgid="179278282547719200">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; қолданбасына осы әрекетті телефоныңыздан орындауға рұқсат беріңіз."</string>
-    <string name="helper_title_nearby_device_streaming" msgid="6124438217620593669">"Аралық құрылғы қызметтері"</string>
-    <string name="helper_summary_nearby_device_streaming" msgid="5538329403511524333">"<xliff:g id="APP_NAME">%1$s</xliff:g> қолданбасы маңайдағы құрылғыларға <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> атынан контент трансляциялау үшін рұқсат сұрайды."</string>
+    <string name="helper_summary_computer" msgid="8774832742608187072">"<xliff:g id="APP_NAME">%1$s</xliff:g> қолданбасы <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> атынан телефондағы фотосуреттерді, медиафайлдар мен хабарландыруларды пайдалану үшін рұқсат сұрайды."</string>
+    <string name="title_nearby_device_streaming" msgid="7269956847378799794">"&lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; құрылғысына бұл әрекетті орындауға рұқсат беру керек пе?"</string>
+    <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> қолданбасы <xliff:g id="DEVICE_NAME">%2$s</xliff:g> атынан қолданбалар мен басқа да жүйе функцияларын маңайдағы құрылғыларға трансляциялау рұқсатын сұрап тұр."</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"құрылғы"</string>
-    <!-- no translation found for summary_generic_single_device (4735072202474939111) -->
-    <skip />
-    <!-- no translation found for summary_generic (4988130802522924650) -->
-    <skip />
+    <string name="summary_generic" msgid="1761976003668044801">"Бұл қолданба телефон мен таңдалған құрылғы арасында деректі (мысалы, қоңырау шалушының атын) синхрондай алады."</string>
     <string name="consent_yes" msgid="8344487259618762872">"Рұқсат беру"</string>
     <string name="consent_no" msgid="2640796915611404382">"Рұқсат бермеу"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Бас тарту"</string>
     <string name="consent_back" msgid="2560683030046918882">"Артқа"</string>
+    <string name="permission_expand" msgid="893185038020887411">"\"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>\" панелін жаю"</string>
+    <string name="permission_collapse" msgid="3320833884220844084">"\"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>\" панелін жию"</string>
     <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"&lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; құрылғысындағы қолданбаларға &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; құрылғысындағыдай рұқсаттар берілсін бе?"</string>
     <string name="permission_sync_summary" msgid="765497944331294275">"Оған &lt;strong&gt;микрофонды&lt;/strong&gt;, &lt;strong&gt;камераны&lt;/strong&gt; және &lt;strong&gt;локацияны пайдалану рұқсаттары&lt;/strong&gt;, сондай-ақ &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt; құрылғысындағы басқа да құпия ақпарат рұқсаттары кіруі мүмкін. &lt;br/&gt;&lt;br/&gt;Бұл рұқсаттарды кез келген уақытта &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt; құрылғысындағы параметрлерден өзгертуіңізге болады."</string>
-    <string name="vendor_icon_description" msgid="4445875290032225965">"Қолданба белгішесі"</string>
-    <string name="vendor_header_button_description" msgid="6566660389500630608">"\"Қосымша ақпарат\" түймесі"</string>
+    <string name="vendor_header_button_description" msgid="7994879208461111473">"Толық ақпарат"</string>
     <string name="permission_phone" msgid="2661081078692784919">"Телефон"</string>
     <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
     <string name="permission_contacts" msgid="3858319347208004438">"Контактілер"</string>
@@ -64,16 +57,18 @@
     <string name="permission_storage" msgid="6831099350839392343">"Фотосуреттер мен медиафайлдар"</string>
     <string name="permission_notification" msgid="693762568127741203">"Хабарландырулар"</string>
     <string name="permission_app_streaming" msgid="6009695219091526422">"Қолданбалар"</string>
-    <string name="permission_nearby_device_streaming" msgid="5868108148065023161">"Маңайдағы құрылғыға трансляция"</string>
+    <string name="permission_nearby_device_streaming" msgid="1023325519477349499">"Трансляция"</string>
     <string name="permission_phone_summary" msgid="6684396967861278044">"Қоңырау шалып, оларды басқара алады."</string>
     <string name="permission_call_logs_summary" msgid="6186103394658755022">"Телефонның қоңыраулар журналын оқып, жаза алады."</string>
     <string name="permission_sms_summary" msgid="3508442683678912017">"SMS хабарларды көріп, жібере алады."</string>
     <string name="permission_contacts_summary" msgid="675861979475628708">"Контактілеріңізді пайдалана алады."</string>
     <string name="permission_calendar_summary" msgid="6460000922511766226">"Күнтізбеңізді пайдалана алады."</string>
-    <string name="permission_microphone_summary" msgid="4241354865859396558">"Микрофон пайдалану арқылы аудио жаза алады."</string>
+    <string name="permission_microphone_summary" msgid="3692091540613093394">"Аудио жаза алады."</string>
     <string name="permission_nearby_devices_summary" msgid="931940524460876655">"Маңайдағы құрылғыларды тауып, олармен байланысып, бір-біріне қатысты локациясын анықтайды."</string>
     <string name="permission_notification_summary" msgid="884075314530071011">"Барлық хабарландыруды, соның ішінде контактілер, хабарлар және фотосуреттер сияқты ақпаратты оқи алады."</string>
     <string name="permission_app_streaming_summary" msgid="606923325679670624">"Телефон қолданбаларын трансляциялайды."</string>
     <string name="permission_storage_summary" msgid="3918240895519506417"></string>
-    <string name="permission_nearby_device_streaming_summary" msgid="5776807830582725074">"Маңайдағы құрылғыға контент трансляциялайды."</string>
+    <string name="permission_nearby_device_streaming_summary" msgid="8280824871197081246">"Қолданбалар мен басқа да жүйе функцияларын телефоннан трансляциялау"</string>
+    <string name="device_type" product="default" msgid="8268703872070046263">"телефон"</string>
+    <string name="device_type" product="tablet" msgid="5038791954983067774">"планшет"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-km/strings.xml b/packages/CompanionDeviceManager/res/values-km/strings.xml
index 455f882..c734cb6 100644
--- a/packages/CompanionDeviceManager/res/values-km/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-km/strings.xml
@@ -17,43 +17,36 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"កម្មវិធី​គ្រប់​គ្រង​ឧបករណ៍ដៃគូ"</string>
-    <!-- no translation found for confirmation_title (8024993972587946678) -->
-    <skip />
+    <string name="confirmation_title" msgid="2244241995958340998">"អនុញ្ញាតឱ្យកម្មវិធី &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ចូលប្រើ &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; ឬ?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"នាឡិកា"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"ជ្រើសរើស <xliff:g id="PROFILE_NAME">%1$s</xliff:g> ដើម្បីឱ្យស្ថិតក្រោម​ការគ្រប់គ្រងរបស់ &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <!-- no translation found for summary_watch (6566922405914995759) -->
-    <skip />
-    <!-- no translation found for summary_watch_single_device (7443464525873186735) -->
-    <skip />
-    <string name="profile_name_glasses" msgid="8488394059007275998">"វ៉ែនតា"</string>
-    <!-- no translation found for summary_glasses (3808267780579061241) -->
-    <skip />
-    <!-- no translation found for summary_glasses_single_device (7051392780285915640) -->
-    <skip />
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"ជ្រើសរើសឧបករណ៍ ដើម្បីដាក់ក្រោម​ការគ្រប់គ្រងរបស់ &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"ជ្រើសរើស <xliff:g id="PROFILE_NAME">%1$s</xliff:g> ដើម្បីរៀបចំ"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"កម្មវិធីនេះនឹងត្រូវបានអនុញ្ញាតឱ្យធ្វើសមកាលកម្មព័ត៌មាន ដូចជាឈ្មោះមនុស្សដែលហៅទូរសព្ទជាដើម និងចូលប្រើប្រាស់ការអនុញ្ញាតទាំងនេះនៅលើ <xliff:g id="DEVICE_NAME">%1$s</xliff:g> របស់អ្នក"</string>
+    <string name="confirmation_title_glasses" msgid="8288346850537727333">"អនុញ្ញាតឱ្យ &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; គ្រប់គ្រង &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; ឬ?"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"ឧបករណ៍"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"កម្មវិធីនេះ​នឹងត្រូវបានអនុញ្ញាតឱ្យ​ចូលប្រើប្រាស់ការអនុញ្ញាតទាំងនេះ​នៅលើ <xliff:g id="DEVICE_NAME">%1$s</xliff:g> របស់អ្នក"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"អនុញ្ញាតឱ្យ &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ចូលប្រើព័ត៌មាននេះពីទូរសព្ទរបស់អ្នក"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"សេវាកម្មឆ្លងកាត់ឧបករណ៍"</string>
-    <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> កំពុងស្នើសុំការអនុញ្ញាតជំនួសឱ្យ <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> របស់អ្នក ដើម្បីបញ្ចាំងកម្មវិធីរវាងឧបករណ៍របស់អ្នក"</string>
+    <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g> កំពុងស្នើសុំការអនុញ្ញាតជំនួសឱ្យ <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> របស់អ្នក ដើម្បីបញ្ចាំងកម្មវិធីរវាងឧបករណ៍របស់អ្នក"</string>
     <string name="title_automotive_projection" msgid="3296005598978412847"></string>
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"អនុញ្ញាតឱ្យ &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ចូលមើលព័ត៌មាននេះពីទូរសព្ទរបស់អ្នក"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"សេវាកម្ម Google Play"</string>
-    <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> កំពុងស្នើសុំការអនុញ្ញាតជំនួសឱ្យ <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> របស់អ្នក ដើម្បីចូលប្រើរូបថត មេឌៀ និងការជូនដំណឹងរបស់ទូរសព្ទអ្នក"</string>
-    <string name="title_nearby_device_streaming" msgid="179278282547719200">"អនុញ្ញាតឱ្យ &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ធ្វើសកម្មភាពនេះពីទូរសព្ទរបស់អ្នក"</string>
-    <string name="helper_title_nearby_device_streaming" msgid="6124438217620593669">"សេវាកម្មឆ្លងកាត់ឧបករណ៍"</string>
-    <string name="helper_summary_nearby_device_streaming" msgid="5538329403511524333">"<xliff:g id="APP_NAME">%1$s</xliff:g> កំពុងស្នើសុំការអនុញ្ញាតជំនួសឱ្យ <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> របស់អ្នក ដើម្បីផ្សាយខ្លឹមសារទៅឧបករណ៍នៅជិត"</string>
+    <string name="helper_summary_computer" msgid="8774832742608187072">"<xliff:g id="APP_NAME">%1$s</xliff:g> កំពុងស្នើសុំការអនុញ្ញាតជំនួសឱ្យ <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> របស់អ្នក ដើម្បីចូលប្រើរូបថត មេឌៀ និងការជូនដំណឹងរបស់ទូរសព្ទអ្នក"</string>
+    <string name="title_nearby_device_streaming" msgid="7269956847378799794">"អនុញ្ញាតឱ្យ &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; ធ្វើសកម្មភាពនេះឬ?"</string>
+    <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> កំពុងស្នើសុំ​ការអនុញ្ញាតជំនួសឱ្យ <xliff:g id="DEVICE_NAME">%2$s</xliff:g> របស់អ្នក ដើម្បីចាក់ផ្សាយកម្មវិធី និងមុខងារប្រព័ន្ធផ្សេងទៀត​ទៅកាន់​ឧបករណ៍នៅជិត"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"ឧបករណ៍"</string>
-    <!-- no translation found for summary_generic_single_device (4735072202474939111) -->
-    <skip />
-    <!-- no translation found for summary_generic (4988130802522924650) -->
-    <skip />
+    <string name="summary_generic" msgid="1761976003668044801">"កម្មវិធីនេះនឹងអាច​ធ្វើសមកាលកម្មព័ត៌មាន ដូចជាឈ្មោះមនុស្សដែលហៅទូរសព្ទជាដើម​ រវាងឧបករណ៍ដែលបានជ្រើសរើស និងទូរសព្ទរបស់អ្នក"</string>
     <string name="consent_yes" msgid="8344487259618762872">"អនុញ្ញាត"</string>
     <string name="consent_no" msgid="2640796915611404382">"មិនអនុញ្ញាត"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"បោះបង់"</string>
     <string name="consent_back" msgid="2560683030046918882">"ថយក្រោយ"</string>
+    <string name="permission_expand" msgid="893185038020887411">"ពង្រីក <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
+    <string name="permission_collapse" msgid="3320833884220844084">"បង្រួម <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"ផ្ដល់​ការអនុញ្ញាតឱ្យ​កម្មវិធីនៅលើ &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; ដូចនៅលើ &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; ឬ?"</string>
     <string name="permission_sync_summary" msgid="765497944331294275">"សកម្មភាពនេះ​អាចរួមបញ្ចូល&lt;strong&gt;មីក្រូហ្វូន&lt;/strong&gt; &lt;strong&gt;កាមេរ៉ា&lt;/strong&gt; និង&lt;strong&gt;សិទ្ធិចូលប្រើទីតាំង&lt;/strong&gt; និងការអនុញ្ញាត​ដែលមានលក្ខណៈរសើបផ្សេងទៀតនៅលើ &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;។ &lt;br/&gt;&lt;br/&gt;អ្នកអាចប្ដូរ​ការអនុញ្ញាតទាំងនេះ​បានគ្រប់ពេល​នៅក្នុងការកំណត់​របស់អ្នកនៅលើ &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;។"</string>
-    <string name="vendor_icon_description" msgid="4445875290032225965">"រូប​កម្មវិធី"</string>
-    <string name="vendor_header_button_description" msgid="6566660389500630608">"ប៊ូតុងព័ត៌មានបន្ថែម"</string>
+    <string name="vendor_header_button_description" msgid="7994879208461111473">"ព័ត៌មាន​បន្ថែម"</string>
     <string name="permission_phone" msgid="2661081078692784919">"ទូរសព្ទ"</string>
     <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
     <string name="permission_contacts" msgid="3858319347208004438">"Contacts"</string>
@@ -64,16 +57,18 @@
     <string name="permission_storage" msgid="6831099350839392343">"រូបថត និងមេឌៀ"</string>
     <string name="permission_notification" msgid="693762568127741203">"ការ​ជូនដំណឹង"</string>
     <string name="permission_app_streaming" msgid="6009695219091526422">"កម្មវិធី"</string>
-    <string name="permission_nearby_device_streaming" msgid="5868108148065023161">"ការផ្សាយទៅឧបករណ៍នៅជិត"</string>
+    <string name="permission_nearby_device_streaming" msgid="1023325519477349499">"ការផ្សាយ"</string>
     <string name="permission_phone_summary" msgid="6684396967861278044">"អាចហៅទូរសព្ទ និងគ្រប់គ្រងការហៅទូរសព្ទ"</string>
     <string name="permission_call_logs_summary" msgid="6186103394658755022">"អាចអាន និងសរសេរ​កំណត់​ហេតុ​ហៅ​ទូរសព្ទ"</string>
     <string name="permission_sms_summary" msgid="3508442683678912017">"អាចផ្ញើ និងមើលសារ SMS"</string>
     <string name="permission_contacts_summary" msgid="675861979475628708">"អាចចូលប្រើទំនាក់ទំនងរបស់អ្នក"</string>
     <string name="permission_calendar_summary" msgid="6460000922511766226">"អាចចូលប្រើប្រតិទិនរបស់អ្នក"</string>
-    <string name="permission_microphone_summary" msgid="4241354865859396558">"អាចថតសំឡេងដោយប្រើមីក្រូហ្វូន"</string>
+    <string name="permission_microphone_summary" msgid="3692091540613093394">"អាចថតសំឡេង"</string>
     <string name="permission_nearby_devices_summary" msgid="931940524460876655">"អាចស្វែងរក ភ្ជាប់ទៅ និងកំណត់​ចម្ងាយពាក់ព័ន្ធ​រវាងឧបករណ៍​ដែលនៅជិត"</string>
     <string name="permission_notification_summary" msgid="884075314530071011">"អាចអាន​ការជូនដំណឹង​ទាំងអស់ រួមទាំង​ព័ត៌មាន​ដូចជាទំនាក់ទំនង សារ និងរូបថត"</string>
     <string name="permission_app_streaming_summary" msgid="606923325679670624">"ផ្សាយកម្មវិធីរបស់ទូរសព្ទអ្នក"</string>
     <string name="permission_storage_summary" msgid="3918240895519506417"></string>
-    <string name="permission_nearby_device_streaming_summary" msgid="5776807830582725074">"ផ្សាយខ្លឹមសារទៅឧបករណ៍នៅជិត"</string>
+    <string name="permission_nearby_device_streaming_summary" msgid="8280824871197081246">"ចាក់ផ្សាយ​កម្មវិធី និងមុខងារប្រព័ន្ធ​ផ្សេងទៀត​ពីទូរសព្ទ​របស់អ្នក"</string>
+    <string name="device_type" product="default" msgid="8268703872070046263">"ទូរសព្ទ"</string>
+    <string name="device_type" product="tablet" msgid="5038791954983067774">"ថេប្លេត"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-kn/strings.xml b/packages/CompanionDeviceManager/res/values-kn/strings.xml
index 0bc7175..5f04cd3 100644
--- a/packages/CompanionDeviceManager/res/values-kn/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-kn/strings.xml
@@ -17,36 +17,36 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"ಕಂಪ್ಯಾನಿಯನ್ ಸಾಧನ ನಿರ್ವಾಹಕರು"</string>
-    <string name="confirmation_title" msgid="8024993972587946678">"&lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; ಅನ್ನು ಪ್ರವೇಶಿಸಲು &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ಗೆ ಅನುಮತಿಸಿ"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"&lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; ಅನ್ನು ಆ್ಯಕ್ಸೆಸ್ ಮಾಡಲು &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ಆ್ಯಪ್‌ಗೆ ಅನುಮತಿಸಬೇಕೆ?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"ವೀಕ್ಷಿಸಿ"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"&lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; ಮೂಲಕ ನಿರ್ವಹಿಸಬೇಕಾದ <xliff:g id="PROFILE_NAME">%1$s</xliff:g> ಅನ್ನು ಆಯ್ಕೆಮಾಡಿ"</string>
-    <string name="summary_watch" msgid="6566922405914995759">"ನಿಮ್ಮ <xliff:g id="DEVICE_NAME">%1$s</xliff:g> ಅನ್ನು ನಿರ್ವಹಿಸಲು ಆ್ಯಪ್‌ನ ಅಗತ್ಯವಿದೆ. ಕರೆ ಮಾಡುವವರ ಹೆಸರು, ನಿಮ್ಮ ಅಧಿಸೂಚನೆಗಳೊಂದಿಗೆ ಸಂವಹನ ನಡೆಸಲು ಮತ್ತು ಫೋನ್, SMS, ಸಂಪರ್ಕಗಳು, ಕ್ಯಾಲೆಂಡರ್, ಕರೆ ಲಾಗ್‌ಗಳು ಮತ್ತು ಸಮೀಪದಲ್ಲಿರುವ ಸಾಧನಗಳ ದೃಢೀಕರಣಗಳಂತಹ ಮಾಹಿತಿಯನ್ನು ಸಿಂಕ್ ಮಾಡಲು <xliff:g id="APP_NAME">%2$s</xliff:g> ಗೆ ಸಾಧ್ಯವಾಗುತ್ತದೆ."</string>
-    <string name="summary_watch_single_device" msgid="7443464525873186735">"ನಿಮ್ಮ <xliff:g id="DEVICE_NAME">%1$s</xliff:g> ಅನ್ನು ನಿರ್ವಹಿಸಲು ಆ್ಯಪ್‌ನ ಅಗತ್ಯವಿದೆ. ಕರೆ ಮಾಡುವವರ ಹೆಸರಿನಂತಹ ಮಾಹಿತಿಯನ್ನು ಸಿಂಕ್ ಮಾಡಲು ಮತ್ತು ಈ ಅನುಮತಿಗಳನ್ನು ಆ್ಯಕ್ಸೆಸ್ ಮಾಡಲು <xliff:g id="APP_NAME">%2$s</xliff:g> ಗೆ ಅನುಮತಿಸಲಾಗುತ್ತದೆ:"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"ಗ್ಲಾಸ್‌ಗಳು"</string>
-    <string name="summary_glasses" msgid="3808267780579061241">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> ಅನ್ನು ನಿರ್ವಹಿಸಲು ಈ ಆ್ಯಪ್‌ನ ಅಗತ್ಯವಿದೆ. ನಿಮ್ಮ ಫೋನ್, SMS, ಸಂಪರ್ಕಗಳು, ಮೈಕ್ರೊಫೋನ್ ಮತ್ತು ಸಮೀಪದಲ್ಲಿರುವ ಸಾಧನಗಳ ಅನುಮತಿಗಳನ್ನು ಆ್ಯಕ್ಸೆಸ್ ಮಾಡಲು ಹಾಗೂ ನಿಮ್ಮ ಅಧಿಸೂಚನೆಗಳೊಂದಿಗೆ ಸಂವಹನ ನಡೆಸಲು <xliff:g id="APP_NAME">%2$s</xliff:g> ಗೆ ಅನುಮತಿಸಲಾಗುತ್ತದೆ."</string>
-    <string name="summary_glasses_single_device" msgid="7051392780285915640">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> ಅನ್ನು ನಿರ್ವಹಿಸಲು ಆ್ಯಪ್‌ನ ಅಗತ್ಯವಿದೆ. <xliff:g id="APP_NAME">%2$s</xliff:g> ಗೆ ಈ ಅನುಮತಿಗಳ ಜೊತೆಗೆ ಸಂವಹನ ನಡೆಸಲು ಅನುಮತಿಸಲಾಗುತ್ತದೆ:"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ಮೂಲಕ ನಿರ್ವಹಿಸಬೇಕಾದ ಸಾಧನವನ್ನು ಆಯ್ಕೆಮಾಡಿ"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"ಸೆಟಪ್ ಮಾಡಲು <xliff:g id="PROFILE_NAME">%1$s</xliff:g> ಅನ್ನು ಆರಿಸಿ"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"ಕರೆ ಮಾಡುವವರ ಹೆಸರಿನಂತಹ ಮಾಹಿತಿಯನ್ನು ಸಿಂಕ್ ಮಾಡಲು ಮತ್ತು ಈ ಅನುಮತಿಗಳನ್ನು ನಿಮ್ಮ <xliff:g id="DEVICE_NAME">%1$s</xliff:g> ನಲ್ಲಿ ಆ್ಯಕ್ಸೆಸ್ ಮಾಡಲು ಈ ಆ್ಯಪ್‌ಗೆ ಅನುಮತಿಸಲಾಗುತ್ತದೆ"</string>
+    <string name="confirmation_title_glasses" msgid="8288346850537727333">"&lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;? ನಿರ್ವಹಿಸಲು &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ಗೆ ಅನುಮತಿಸಬೇಕೇ?"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"ಸಾಧನ"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"ನಿಮ್ಮ <xliff:g id="DEVICE_NAME">%1$s</xliff:g> ನಲ್ಲಿ ಈ ಅನುಮತಿಗಳನ್ನು ಆ್ಯಕ್ಸೆಸ್ ಮಾಡಲು ಈ ಆ್ಯಪ್‌ಗೆ ಅನುಮತಿಸಲಾಗುತ್ತದೆ"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"ನಿಮ್ಮ ಫೋನ್ ಮೂಲಕ ಈ ಮಾಹಿತಿಯನ್ನು ಆ್ಯಕ್ಸೆಸ್ ಮಾಡಲು &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ಗೆ ಅನುಮತಿಸಿ"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"ಕ್ರಾಸ್-ಡಿವೈಸ್ ಸೇವೆಗಳು"</string>
-    <string name="helper_summary_app_streaming" msgid="5977509499890099">"ನಿಮ್ಮ ಸಾಧನಗಳ ನಡುವೆ ಆ್ಯಪ್‌ಗಳನ್ನು ಸ್ಟ್ರೀಮ್ ಮಾಡಲು ನಿಮ್ಮ <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> ನ ಪರವಾಗಿ <xliff:g id="APP_NAME">%1$s</xliff:g> ಅನುಮತಿಯನ್ನು ವಿನಂತಿಸಿಕೊಳ್ಳುತ್ತಿದೆ"</string>
+    <string name="helper_summary_app_streaming" msgid="2396773196949578425">"ನಿಮ್ಮ ಸಾಧನಗಳ ನಡುವೆ ಆ್ಯಪ್‌ಗಳನ್ನು ಸ್ಟ್ರೀಮ್ ಮಾಡಲು ನಿಮ್ಮ <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> ನ ಪರವಾಗಿ <xliff:g id="APP_NAME">%1$s</xliff:g> ಅನುಮತಿಯನ್ನು ವಿನಂತಿಸಿಕೊಳ್ಳುತ್ತಿದೆ"</string>
     <string name="title_automotive_projection" msgid="3296005598978412847"></string>
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"ನಿಮ್ಮ ಫೋನ್ ಮೂಲಕ ಈ ಮಾಹಿತಿಯನ್ನು ಪ್ರವೇಶಿಸಲು &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ಗೆ ಅನುಮತಿಸಿ"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Google Play ಸೇವೆಗಳು"</string>
-    <string name="helper_summary_computer" msgid="9050724687678157852">"ನಿಮ್ಮ ಫೋನ್‌ನ ಫೋಟೋಗಳು, ಮೀಡಿಯಾ ಮತ್ತು ಅಧಿಸೂಚನೆಗಳನ್ನು ಪ್ರವೇಶಿಸಲು ನಿಮ್ಮ <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> ನ ಪರವಾಗಿ <xliff:g id="APP_NAME">%1$s</xliff:g> ಅನುಮತಿಯನ್ನು ವಿನಂತಿಸಿಕೊಳ್ಳುತ್ತಿದೆ"</string>
-    <string name="title_nearby_device_streaming" msgid="179278282547719200">"ಈ ಕ್ರಿಯೆಯನ್ನು ನಿಮ್ಮ ಫೋನ್‌ನಿಂದ ನಿರ್ವಹಿಸಲು &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ಗೆ ಅನುಮತಿಸಿ"</string>
-    <string name="helper_title_nearby_device_streaming" msgid="6124438217620593669">"ಕ್ರಾಸ್-ಡಿವೈಸ್ ಸೇವೆಗಳು"</string>
-    <string name="helper_summary_nearby_device_streaming" msgid="5538329403511524333">"ಕಂಟೆಂಟ್ ಅನ್ನು ಸಮೀಪದಲ್ಲಿರುವ ಸಾಧನಗಳಿಗೆ ಸ್ಟ್ರೀಮ್ ಮಾಡಲು ನಿಮ್ಮ <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> ನ ಪರವಾಗಿ <xliff:g id="APP_NAME">%1$s</xliff:g> ಅನುಮತಿಗಾಗಿ ವಿನಂತಿಸುತ್ತಿದೆ"</string>
+    <string name="helper_summary_computer" msgid="8774832742608187072">"ನಿಮ್ಮ ಫೋನ್‌ನ ಫೋಟೋಗಳು, ಮೀಡಿಯಾ ಮತ್ತು ಅಧಿಸೂಚನೆಗಳನ್ನು ಆ್ಯಕ್ಸೆಸ್ ಮಾಡಲು ನಿಮ್ಮ <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> ನ ಪರವಾಗಿ <xliff:g id="APP_NAME">%1$s</xliff:g> ಅನುಮತಿಯನ್ನು ವಿನಂತಿಸಿಕೊಳ್ಳುತ್ತಿದೆ"</string>
+    <string name="title_nearby_device_streaming" msgid="7269956847378799794">"ಈ ಆ್ಯಕ್ಷನ್ ಅನ್ನು ತೆಗೆದುಕೊಳ್ಳಲು &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; ಅನುಮತಿಸಬೇಕೇ?"</string>
+    <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"ಸಮೀಪದಲ್ಲಿರುವ ಸಾಧನಗಳಿಗೆ ಆ್ಯಪ್‌ಗಳು ಮತ್ತು ಇತರ ಸಿಸ್ಟಂ ಫೀಚರ್‌ಗಳನ್ನು ಸ್ಟ್ರೀಮ್ ಮಾಡಲು ನಿಮ್ಮ <xliff:g id="DEVICE_NAME">%2$s</xliff:g> ರ ಪರವಾಗಿ <xliff:g id="APP_NAME">%1$s</xliff:g> ಅನುಮತಿಯನ್ನು ವಿನಂತಿಸುತ್ತಿದೆ"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"ಸಾಧನ"</string>
-    <string name="summary_generic_single_device" msgid="4735072202474939111">"ಈ ಆ್ಯಪ್, ಮೊಬೈಲ್ ಫೋನ್ ಮತ್ತು <xliff:g id="DEVICE_NAME">%1$s</xliff:g> ಸಾಧನದ ನಡುವೆ ಕರೆ ಮಾಡುವವರ ಹೆಸರಿನಂತಹ ಮಾಹಿತಿಯನ್ನು ಸಿಂಕ್ ಮಾಡಲು ಸಾಧ್ಯವಾಗುತ್ತದೆ."</string>
-    <string name="summary_generic" msgid="4988130802522924650">"ಈ ಆ್ಯಪ್, ಮೊಬೈಲ್ ಫೋನ್ ಮತ್ತು ಆಯ್ಕೆಮಾಡಿದ ಸಾಧನದ ನಡುವೆ ಕರೆ ಮಾಡುವವರ ಹೆಸರಿನಂತಹ ಮಾಹಿತಿಯನ್ನು ಸಿಂಕ್ ಮಾಡಲು ಸಾಧ್ಯವಾಗುತ್ತದೆ."</string>
+    <string name="summary_generic" msgid="1761976003668044801">"ಮೊಬೈಲ್ ಫೋನ್ ಮತ್ತು ಆಯ್ಕೆಮಾಡಿದ ಸಾಧನದ ನಡುವೆ, ಕರೆ ಮಾಡುವವರ ಹೆಸರಿನಂತಹ ಮಾಹಿತಿಯನ್ನು ಸಿಂಕ್ ಮಾಡಲು ಈ ಆ್ಯಪ್‌ಗೆ ಸಾಧ್ಯವಾಗುತ್ತದೆ"</string>
     <string name="consent_yes" msgid="8344487259618762872">"ಅನುಮತಿಸಿ"</string>
     <string name="consent_no" msgid="2640796915611404382">"ಅನುಮತಿಸಬೇಡಿ"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"ರದ್ದುಮಾಡಿ"</string>
     <string name="consent_back" msgid="2560683030046918882">"ಹಿಂದೆ"</string>
+    <string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> ಅನ್ನು ವಿಸ್ತರಿಸಿ"</string>
+    <string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> ಅನ್ನು ಕುಗ್ಗಿಸಿ"</string>
     <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"&lt;/strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; ನಲ್ಲಿನ ಅನುಮತಿಗಳನ್ನೇ &lt;/strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; ನಲ್ಲಿನ ಆ್ಯಪ್‌ಗಳಿಗೆ ನೀಡಬೇಕೆ?"</string>
     <string name="permission_sync_summary" msgid="765497944331294275">"ಇದು &lt;strong&gt;ಮೈಕ್ರೋಫೋನ್&lt;/strong&gt;, &lt;strong&gt;ಕ್ಯಾಮರಾ&lt;/strong&gt;, and &lt;strong&gt;ಸ್ಥಳದ ಆ್ಯಕ್ಸೆಸ್&lt;/strong&gt;, ಮತ್ತು &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt; ನಲ್ಲಿ ಸೂಕ್ಷ್ಮ ಮಾಹಿತಿಗಾಗಿ ಇತರ ಅನುಮತಿಗಳನ್ನು ಒಳಗೊಂಡಿರಬಹುದು. &lt;br/&gt;&lt;br/&gt;ನೀವು &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt; ನಲ್ಲಿನ ನಿಮ್ಮ ಸೆಟ್ಟಿಂಗ್‌ಗಳಲ್ಲಿ ಈ ಅನುಮತಿಗಳನ್ನು ಯಾವಾಗ ಬೇಕಾದರೂ ಬದಲಾಯಿಸಬಹುದು."</string>
-    <string name="vendor_icon_description" msgid="4445875290032225965">"ಆ್ಯಪ್ ಐಕಾನ್"</string>
-    <string name="vendor_header_button_description" msgid="6566660389500630608">"ಹೆಚ್ಚಿನ ಮಾಹಿತಿಯ ಬಟನ್"</string>
+    <string name="vendor_header_button_description" msgid="7994879208461111473">"ಇನ್ನಷ್ಟು ಮಾಹಿತಿ"</string>
     <string name="permission_phone" msgid="2661081078692784919">"ಫೋನ್"</string>
     <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
     <string name="permission_contacts" msgid="3858319347208004438">"ಸಂಪರ್ಕಗಳು"</string>
@@ -57,16 +57,18 @@
     <string name="permission_storage" msgid="6831099350839392343">"ಫೋಟೋಗಳು ಮತ್ತು ಮಾಧ್ಯಮ"</string>
     <string name="permission_notification" msgid="693762568127741203">"ಅಧಿಸೂಚನೆಗಳು"</string>
     <string name="permission_app_streaming" msgid="6009695219091526422">"ಆ್ಯಪ್‌ಗಳು"</string>
-    <string name="permission_nearby_device_streaming" msgid="5868108148065023161">"ಸಮೀಪದಲ್ಲಿರುವ ಸಾಧನದ ಸ್ಟ್ರೀಮಿಂಗ್"</string>
+    <string name="permission_nearby_device_streaming" msgid="1023325519477349499">"ಸ್ಟ್ರೀಮಿಂಗ್"</string>
     <string name="permission_phone_summary" msgid="6684396967861278044">"ಫೋನ್ ಕರೆಗಳನ್ನು ಮಾಡಬಹುದು ಮತ್ತು ನಿರ್ವಹಿಸಬಹುದು"</string>
     <string name="permission_call_logs_summary" msgid="6186103394658755022">"ಪೋನ್‌ ಕರೆಯ ಲಾಗ್‌ ಅನ್ನು ಓದಬಹುದು ಮತ್ತು ಬರೆಯಬಹುದು"</string>
     <string name="permission_sms_summary" msgid="3508442683678912017">"SMS ಸಂದೇಶಗಳನ್ನು ಕಳುಹಿಸಬಹುದು ಮತ್ತು ವೀಕ್ಷಿಸಬಹುದು"</string>
     <string name="permission_contacts_summary" msgid="675861979475628708">"ನಿಮ್ಮ ಸಂಪರ್ಕಗಳನ್ನು ಆ್ಯಕ್ಸೆಸ್ ಮಾಡಬಹುದು"</string>
     <string name="permission_calendar_summary" msgid="6460000922511766226">"ನಿಮ್ಮ ಕ್ಯಾಲೆಂಡರ್ ಅನ್ನು ಆ್ಯಕ್ಸೆಸ್ ಮಾಡಬಹುದು"</string>
-    <string name="permission_microphone_summary" msgid="4241354865859396558">"ಮೈಕ್ರೊಫೋನ್ ಅನ್ನು ಬಳಸಿಕೊಂಡು ಆಡಿಯೋವನ್ನು ರೆಕಾರ್ಡ್ ಮಾಡಬಹುದು"</string>
+    <string name="permission_microphone_summary" msgid="3692091540613093394">"ಆಡಿಯೋ ರೆಕಾರ್ಡ್ ಮಾಡಬಹುದು"</string>
     <string name="permission_nearby_devices_summary" msgid="931940524460876655">"ಸಮೀಪದಲ್ಲಿರುವ ಸಾಧನಗಳನ್ನು ಹುಡುಕಬಹುದು, ಅವುಗಳಿಗೆ ಕನೆಕ್ಟ್ ಆಗಬಹುದು ಮತ್ತು ಅವುಗಳ ಸಂಬಂಧಿತ ಸ್ಥಾನವನ್ನು ನಿರ್ಧರಿಸಬಹುದು"</string>
     <string name="permission_notification_summary" msgid="884075314530071011">"ಸಂಪರ್ಕಗಳು, ಸಂದೇಶಗಳು ಮತ್ತು ಫೋಟೋಗಳಂತಹ ಮಾಹಿತಿಯನ್ನು ಒಳಗೊಂಡಂತೆ ಎಲ್ಲಾ ಅಧಿಸೂಚನೆಗಳನ್ನು ಓದಬಹುದು"</string>
     <string name="permission_app_streaming_summary" msgid="606923325679670624">"ನಿಮ್ಮ ಫೋನ್‍ನ ಆ್ಯಪ್‌ಗಳನ್ನು ಸ್ಟ್ರೀಮ್ ಮಾಡಿ"</string>
     <string name="permission_storage_summary" msgid="3918240895519506417"></string>
-    <string name="permission_nearby_device_streaming_summary" msgid="5776807830582725074">"ಕಂಟೆಂಟ್ ಅನ್ನು ಸಮೀಪದಲ್ಲಿರುವ ಸಾಧನಕ್ಕೆ ಸ್ಟ್ರೀಮ್ ಮಾಡಿ"</string>
+    <string name="permission_nearby_device_streaming_summary" msgid="8280824871197081246">"ನಿಮ್ಮ ಫೋನ್‌ನಿಂದ ಆ್ಯಪ್‌ಗಳು ಮತ್ತು ಇತರ ಸಿಸ್ಟಂ ಫೀಚರ್‌ಗಳನ್ನು ಸ್ಟ್ರೀಮ್ ಮಾಡಿ"</string>
+    <string name="device_type" product="default" msgid="8268703872070046263">"ಫೋನ್"</string>
+    <string name="device_type" product="tablet" msgid="5038791954983067774">"ಟ್ಯಾಬ್ಲೆಟ್"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-ko/strings.xml b/packages/CompanionDeviceManager/res/values-ko/strings.xml
index 38740f5..e4c56b2 100644
--- a/packages/CompanionDeviceManager/res/values-ko/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ko/strings.xml
@@ -17,43 +17,36 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"부속 기기 관리자"</string>
-    <!-- no translation found for confirmation_title (8024993972587946678) -->
-    <skip />
+    <string name="confirmation_title" msgid="2244241995958340998">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;앱에서 &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;에 액세스하도록 허용하시겠습니까?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"시계"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"&lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;에서 관리할 <xliff:g id="PROFILE_NAME">%1$s</xliff:g>을(를) 선택"</string>
-    <!-- no translation found for summary_watch (6566922405914995759) -->
-    <skip />
-    <!-- no translation found for summary_watch_single_device (7443464525873186735) -->
-    <skip />
-    <string name="profile_name_glasses" msgid="8488394059007275998">"안경"</string>
-    <!-- no translation found for summary_glasses (3808267780579061241) -->
-    <skip />
-    <!-- no translation found for summary_glasses_single_device (7051392780285915640) -->
-    <skip />
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;에서 관리할 기기 선택"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"설정할 <xliff:g id="PROFILE_NAME">%1$s</xliff:g> 선택"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"이 앱이 정보(예: 발신자 이름)를 동기화하고 <xliff:g id="DEVICE_NAME">%1$s</xliff:g>에서 이러한 권한에 액세스할 수 있게 됩니다."</string>
+    <string name="confirmation_title_glasses" msgid="8288346850537727333">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;에서 &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;? 기기를 관리하도록 허용"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"기기"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"앱이 <xliff:g id="DEVICE_NAME">%1$s</xliff:g>에서 이러한 권한에 액세스할 수 있게 됩니다."</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;이 휴대전화의 이 정보에 액세스하도록 허용합니다."</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"교차 기기 서비스"</string>
-    <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g>에서 <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> 대신 기기 간에 앱을 스트리밍할 수 있는 권한을 요청하고 있습니다."</string>
+    <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g>에서 <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> 대신 기기 간에 앱을 스트리밍할 수 있는 권한을 요청하고 있습니다."</string>
     <string name="title_automotive_projection" msgid="3296005598978412847"></string>
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
-    <string name="title_computer" msgid="4693714143506569253">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; 앱이 휴대전화에서 이 정보에 액세스하도록 허용"</string>
+    <string name="title_computer" msgid="4693714143506569253">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;이 휴대전화에서 이 정보에 액세스하도록 허용"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Google Play 서비스"</string>
-    <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g>에서 <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> 대신 휴대전화의 사진, 미디어, 알림에 액세스할 수 있는 권한을 요청하고 있습니다."</string>
-    <string name="title_nearby_device_streaming" msgid="179278282547719200">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; 앱이 휴대전화에서 이 작업을 수행하도록 허용합니다."</string>
-    <string name="helper_title_nearby_device_streaming" msgid="6124438217620593669">"교차 기기 서비스"</string>
-    <string name="helper_summary_nearby_device_streaming" msgid="5538329403511524333">"<xliff:g id="APP_NAME">%1$s</xliff:g>에서 <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> 대신 근처 기기로 콘텐츠를 스트리밍할 수 있는 권한을 요청하고 있습니다."</string>
+    <string name="helper_summary_computer" msgid="8774832742608187072">"<xliff:g id="APP_NAME">%1$s</xliff:g>에서 <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> 대신 휴대전화의 사진, 미디어, 알림에 액세스할 수 있는 권한을 요청하고 있습니다."</string>
+    <string name="title_nearby_device_streaming" msgid="7269956847378799794">"&lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; 기기가 이 작업을 수행하도록 허용하시겠습니까?"</string>
+    <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g>에서 <xliff:g id="DEVICE_NAME">%2$s</xliff:g> 대신 근처 기기로 앱 및 기타 시스템 기능을 스트리밍할 권한을 요청하고 있습니다."</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"기기"</string>
-    <!-- no translation found for summary_generic_single_device (4735072202474939111) -->
-    <skip />
-    <!-- no translation found for summary_generic (4988130802522924650) -->
-    <skip />
+    <string name="summary_generic" msgid="1761976003668044801">"이 앱에서 휴대전화와 선택한 기기 간에 정보(예: 발신자 이름)를 동기화할 수 있게 됩니다."</string>
     <string name="consent_yes" msgid="8344487259618762872">"허용"</string>
     <string name="consent_no" msgid="2640796915611404382">"허용 안함"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"취소"</string>
     <string name="consent_back" msgid="2560683030046918882">"뒤로"</string>
+    <string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> 펼치기"</string>
+    <string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> 접기"</string>
     <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"&lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt;에 설치된 앱에 &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;에 설치된 앱과 동일한 권한을 부여하시겠습니까?"</string>
     <string name="permission_sync_summary" msgid="765497944331294275">"여기에는 &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;의 &lt;strong&gt;마이크&lt;/strong&gt;, &lt;strong&gt;카메라&lt;/strong&gt;, &lt;strong&gt;위치 정보 액세스&lt;/strong&gt; 및 기타 민감한 권한이 포함될 수 있습니다. &lt;br/&gt;&lt;br/&gt;언제든지 &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;의 설정에서 이러한 권한을 변경할 수 있습니다."</string>
-    <string name="vendor_icon_description" msgid="4445875290032225965">"앱 아이콘"</string>
-    <string name="vendor_header_button_description" msgid="6566660389500630608">"추가 정보 버튼"</string>
+    <string name="vendor_header_button_description" msgid="7994879208461111473">"자세히 알아보기"</string>
     <string name="permission_phone" msgid="2661081078692784919">"전화"</string>
     <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
     <string name="permission_contacts" msgid="3858319347208004438">"연락처"</string>
@@ -64,16 +57,18 @@
     <string name="permission_storage" msgid="6831099350839392343">"사진 및 미디어"</string>
     <string name="permission_notification" msgid="693762568127741203">"알림"</string>
     <string name="permission_app_streaming" msgid="6009695219091526422">"앱"</string>
-    <string name="permission_nearby_device_streaming" msgid="5868108148065023161">"근처 기기 스트리밍"</string>
+    <string name="permission_nearby_device_streaming" msgid="1023325519477349499">"스트리밍"</string>
     <string name="permission_phone_summary" msgid="6684396967861278044">"전화를 걸고 통화를 관리할 수 있습니다."</string>
     <string name="permission_call_logs_summary" msgid="6186103394658755022">"통화 기록을 읽고 쓸 수 있습니다."</string>
     <string name="permission_sms_summary" msgid="3508442683678912017">"SMS 메시지를 전송하고 볼 수 있습니다."</string>
     <string name="permission_contacts_summary" msgid="675861979475628708">"연락처에 액세스할 수 있습니다."</string>
     <string name="permission_calendar_summary" msgid="6460000922511766226">"캘린더에 액세스할 수 있습니다."</string>
-    <string name="permission_microphone_summary" msgid="4241354865859396558">"마이크를 사용하여 오디오를 녹음할 수 있습니다."</string>
+    <string name="permission_microphone_summary" msgid="3692091540613093394">"오디오 녹음 가능"</string>
     <string name="permission_nearby_devices_summary" msgid="931940524460876655">"근처 기기를 찾아 연결하고 기기 간 상대적 위치를 파악할 수 있습니다."</string>
     <string name="permission_notification_summary" msgid="884075314530071011">"연락처, 메시지, 사진 등의 정보를 포함한 모든 알림을 읽을 수 있습니다."</string>
     <string name="permission_app_streaming_summary" msgid="606923325679670624">"휴대전화의 앱을 스트리밍합니다."</string>
     <string name="permission_storage_summary" msgid="3918240895519506417"></string>
-    <string name="permission_nearby_device_streaming_summary" msgid="5776807830582725074">"근처 기기로 콘텐츠를 스트리밍합니다."</string>
+    <string name="permission_nearby_device_streaming_summary" msgid="8280824871197081246">"내 휴대전화의 앱 및 기타 시스템 기능 스트리밍"</string>
+    <string name="device_type" product="default" msgid="8268703872070046263">"스마트폰"</string>
+    <string name="device_type" product="tablet" msgid="5038791954983067774">"태블릿"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-ky/strings.xml b/packages/CompanionDeviceManager/res/values-ky/strings.xml
index 5a30ae1..d33cced 100644
--- a/packages/CompanionDeviceManager/res/values-ky/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ky/strings.xml
@@ -17,36 +17,36 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Companion Device Manager"</string>
-    <string name="confirmation_title" msgid="8024993972587946678">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; колдонмосуна &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; түзмөгүнө кирүүгө уруксат бериңиз"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; колдонмосуна &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; түзмөгүнө кирүүгө уруксат бересизби?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"саат"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"<xliff:g id="PROFILE_NAME">%1$s</xliff:g> &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; тарабынан башкарылсын"</string>
-    <string name="summary_watch" msgid="6566922405914995759">"Бул колдонмо <xliff:g id="DEVICE_NAME">%1$s</xliff:g> түзмөгүңүздү тескөө үчүн керек. <xliff:g id="APP_NAME">%2$s</xliff:g> маалыматты шайкештирип, мисалы, билдирмелериңизди көрүп, телефонуңуз, SMS билдирүүлөр, байланыштар, жылнаама, чалуулар тизмеси жана жакын жердеги түзмөктөргө болгон уруксаттарды пайдалана алат."</string>
-    <string name="summary_watch_single_device" msgid="7443464525873186735">"Бул колдонмо <xliff:g id="DEVICE_NAME">%1$s</xliff:g> түзмөгүңүздү тескөө үчүн керек. <xliff:g id="APP_NAME">%2$s</xliff:g> маалыматты шайкештире алат, мисалы, чалып жаткан кишинин атын шайкештирет жана анын төмөнкү уруксаттары болот:"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"көз айнектер"</string>
-    <string name="summary_glasses" msgid="3808267780579061241">"Бул колдонмо <xliff:g id="DEVICE_NAME">%1$s</xliff:g> түзмөгүн башкаруу үчүн керек. <xliff:g id="APP_NAME">%2$s</xliff:g> билдирмелериңизди көрүп, телефонуңуз, SMS билдирүүлөр, Байланыштар, Микрофон жана Жакын жердеги түзмөктөргө болгон уруксаттарды пайдалана алат."</string>
-    <string name="summary_glasses_single_device" msgid="7051392780285915640">"Бул колдонмо <xliff:g id="DEVICE_NAME">%1$s</xliff:g> түзмөгүн тескөө үчүн керек. <xliff:g id="APP_NAME">%2$s</xliff:g> төмөнкү уруксаттарды пайдалана алат:"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; аркылуу башкарыла турган түзмөктү тандаңыз"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Тууралоо үчүн <xliff:g id="PROFILE_NAME">%1$s</xliff:g> тандаңыз"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Бул колдонмого маалыматты, мисалы, чалып жаткан адамдын аты-жөнүн шайкештирүүгө жана <xliff:g id="DEVICE_NAME">%1$s</xliff:g> түзмөгүңүздө төмөнкүлөрдү аткарууга уруксат берилет"</string>
+    <string name="confirmation_title_glasses" msgid="8288346850537727333">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; колдонмосуна &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; түзмөгүн тескөөгө уруксат бересизби?"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"түзмөк"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Бул колдонмого <xliff:g id="DEVICE_NAME">%1$s</xliff:g> түзмөгүңүздө төмөнкүлөрдү аткарууга уруксат берилет"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; колдонмосуна телефонуңуздагы ушул маалыматты көрүүгө уруксат бериңиз"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Түзмөктөр аралык кызматтар"</string>
-    <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> түзмөгүңүздүн атынан түзмөктөрүңүздүн ортосунда колдонмолорду өткөрүүгө уруксат сурап жатат"</string>
+    <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> түзмөгүңүздүн атынан түзмөктөрүңүздүн ортосунда колдонмолорду алып ойнотууга уруксат сурап жатат"</string>
     <string name="title_automotive_projection" msgid="3296005598978412847"></string>
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; колдонмосуна телефонуңуздагы ушул маалыматты көрүүгө уруксат бериңиз"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Google Play кызматтары"</string>
-    <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> колдонмосу <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> түзмөгүңүздүн атынан телефондогу сүрөттөрдү, медиа файлдарды жана билдирмелерди колдонууга уруксат сурап жатат"</string>
-    <string name="title_nearby_device_streaming" msgid="179278282547719200">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; колдонмосуна бул аракетти телефонуңуздан аткарууга уруксат бериңиз"</string>
-    <string name="helper_title_nearby_device_streaming" msgid="6124438217620593669">"Түзмөктөр аралык кызматтар"</string>
-    <string name="helper_summary_nearby_device_streaming" msgid="5538329403511524333">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> түзмөгүңүздүн атынан жакын жердеги түзмөктөрдө контентти алып ойнотууга уруксат сурап жатат"</string>
+    <string name="helper_summary_computer" msgid="8774832742608187072">"<xliff:g id="APP_NAME">%1$s</xliff:g> колдонмосу <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> түзмөгүңүздүн атынан телефондогу сүрөттөрдү, медиа файлдарды жана билдирмелерди колдонууга уруксат сурап жатат"</string>
+    <string name="title_nearby_device_streaming" msgid="7269956847378799794">"&lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; түзмөгүнө бул аракетти аткарууга уруксат бересизби?"</string>
+    <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="DEVICE_NAME">%2$s</xliff:g> түзмөгүңүздүн атынан жакын жердеги түзмөктөрдө колдонмолорду жана системанын башка функцияларын алып ойнотууга уруксат сурап жатат"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"түзмөк"</string>
-    <string name="summary_generic_single_device" msgid="4735072202474939111">"Бул колдонмо маалыматты шайкештире алат, мисалы, чалып жаткан кишинин атын телефон жана <xliff:g id="DEVICE_NAME">%1$s</xliff:g> түзмөгү менен шайкештирет."</string>
-    <string name="summary_generic" msgid="4988130802522924650">"Бул колдонмо маалыматты шайкештире алат, мисалы, чалып жаткан кишинин атын телефон жана тандалган түзмөк менен шайкештирет."</string>
+    <string name="summary_generic" msgid="1761976003668044801">"Бул колдонмо маалыматты шайкештире алат, мисалы, чалып жаткан кишинин атын телефон жана тандалган түзмөк менен шайкештирет"</string>
     <string name="consent_yes" msgid="8344487259618762872">"Ооба"</string>
     <string name="consent_no" msgid="2640796915611404382">"Уруксат берилбесин"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Жокко чыгаруу"</string>
     <string name="consent_back" msgid="2560683030046918882">"Артка"</string>
+    <string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> жайып көрсөтүү"</string>
+    <string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> жыйыштыруу"</string>
     <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"&lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; түзмөгүнө да &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; түзмөгүнө берилген уруксаттар берилсинби?"</string>
     <string name="permission_sync_summary" msgid="765497944331294275">"Буга &lt;strong&gt;Микрофонду&lt;/strong&gt;, &lt;strong&gt;Камераны&lt;/strong&gt; пайдалануу жана &lt;strong&gt;Жайгашкан жерди аныктоо&lt;/strong&gt;, ошондой эле &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt; түзмөгүндөгү башка купуя маалыматты көрүүгө уруксаттар кириши мүмкүн. &lt;br/&gt;&lt;br/&gt;Каалаган убакта &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt; түзмөгүндөгү параметрлерге өтүп, бул уруксаттарды өзгөртө аласыз."</string>
-    <string name="vendor_icon_description" msgid="4445875290032225965">"Колдонмонун сүрөтчөсү"</string>
-    <string name="vendor_header_button_description" msgid="6566660389500630608">"Дагы маалымат баскычы"</string>
+    <string name="vendor_header_button_description" msgid="7994879208461111473">"Кеңири маалымат"</string>
     <string name="permission_phone" msgid="2661081078692784919">"Телефон"</string>
     <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
     <string name="permission_contacts" msgid="3858319347208004438">"Байланыштар"</string>
@@ -57,16 +57,18 @@
     <string name="permission_storage" msgid="6831099350839392343">"Сүрөттөр жана медиафайлдар"</string>
     <string name="permission_notification" msgid="693762568127741203">"Билдирмелер"</string>
     <string name="permission_app_streaming" msgid="6009695219091526422">"Колдонмолор"</string>
-    <string name="permission_nearby_device_streaming" msgid="5868108148065023161">"Жакын жердеги түзмөктөрдө алып ойнотуу"</string>
+    <string name="permission_nearby_device_streaming" msgid="1023325519477349499">"Алып ойнотуу"</string>
     <string name="permission_phone_summary" msgid="6684396967861278044">"Телефон чалууларды аткарып жана тескей алат"</string>
     <string name="permission_call_logs_summary" msgid="6186103394658755022">"Телефондогу чалуулар тизмесин окуп жана жаза алат"</string>
     <string name="permission_sms_summary" msgid="3508442683678912017">"SMS билдирүүлөрдү жөнөтүп жана көрө алат"</string>
     <string name="permission_contacts_summary" msgid="675861979475628708">"Байланыштарыңызга кире алат"</string>
     <string name="permission_calendar_summary" msgid="6460000922511766226">"Жылнаамаңызга кире алат"</string>
-    <string name="permission_microphone_summary" msgid="4241354865859396558">"Микрофон аркылуу аудио жаздыра алат"</string>
+    <string name="permission_microphone_summary" msgid="3692091540613093394">"Аудио жаздыра алат"</string>
     <string name="permission_nearby_devices_summary" msgid="931940524460876655">"Жакын жердеги түзмөктөрдү таап, аларга туташып, абалын аныктай алат"</string>
     <string name="permission_notification_summary" msgid="884075314530071011">"Бардык билдирмелерди, анын ичинде байланыштар, билдирүүлөр жана сүрөттөр сыяктуу маалыматты окуй алат"</string>
     <string name="permission_app_streaming_summary" msgid="606923325679670624">"Телефондогу колдонмолорду алып ойнотуу"</string>
     <string name="permission_storage_summary" msgid="3918240895519506417"></string>
-    <string name="permission_nearby_device_streaming_summary" msgid="5776807830582725074">"Контентти жакын жердеги түзмөктө алып ойнотуу"</string>
+    <string name="permission_nearby_device_streaming_summary" msgid="8280824871197081246">"Телефонуңуздагы колдонмолорду жана системанын башка функцияларын алып ойнотуу"</string>
+    <string name="device_type" product="default" msgid="8268703872070046263">"телефон"</string>
+    <string name="device_type" product="tablet" msgid="5038791954983067774">"планшет"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-lo/strings.xml b/packages/CompanionDeviceManager/res/values-lo/strings.xml
index 5787bb1..0072e4b 100644
--- a/packages/CompanionDeviceManager/res/values-lo/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-lo/strings.xml
@@ -17,36 +17,36 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"ຕົວຈັດການອຸປະກອນປະກອບ"</string>
-    <string name="confirmation_title" msgid="8024993972587946678">"ອະນຸຍາດ &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ໃຫ້ເຂົ້າເຖິງ &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; ໄດ້"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"ອະນຸຍາດໃຫ້ແອັບ &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ເຂົ້າເຖິງ &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; ບໍ?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"ໂມງ"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"ເລືອກ <xliff:g id="PROFILE_NAME">%1$s</xliff:g> ເພື່ອໃຫ້ຖືກຈັດການໂດຍ &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="6566922405914995759">"ຕ້ອງໃຊ້ແອັບດັ່ງກ່າວເພື່ອຈັດການ <xliff:g id="DEVICE_NAME">%1$s</xliff:g> ຂອງທ່ານ. <xliff:g id="APP_NAME">%2$s</xliff:g> ຈະໄດ້ຮັບອະນຸຍາດໃຫ້ຊິງ​ຂໍ້​ມູນ​ເຊັ່ນ: ຊື່​ຂອງ​ບາງ​ຄົນ​ທີ່​ກຳ​ລັງ​ໂທ, ໂຕ້​ຕອບ​ກັບການແຈ້ງເຕືອນຂອງທ່ານ ແລະ ເຂົ້າເຖິງການ​ອະ​ນຸ​ຍາດໂທລະສັບ, SMS, ລາຍຊື່ຜູ້ຕິດຕໍ່, ປະ​ຕິ​ທິນ, ບັນ​ທຶກ​ການ​ໂທ ແລະ ອຸປະກອນທີ່ຢູ່ໃກ້ຄຽງຂອງທ່ານ."</string>
-    <string name="summary_watch_single_device" msgid="7443464525873186735">"ຕ້ອງໃຊ້ແອັບດັ່ງກ່າວເພື່ອຈັດການ <xliff:g id="DEVICE_NAME">%1$s</xliff:g> ຂອງທ່ານ. <xliff:g id="APP_NAME">%2$s</xliff:g> ຈະໄດ້ຮັບອະນຸຍາດໃຫ້ຊິງ​ຂໍ້​ມູນ​ເຊັ່ນ: ຊື່​ຂອງ​ບາງ​ຄົນ​ທີ່​ກຳ​ລັງ​ໂທ ແລະ ເຂົ້າ​ເຖິງ​ການ​ອະ​ນຸ​ຍາດ​ເຫຼົ່າ​ນີ້:"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"ແວ່ນຕາ"</string>
-    <string name="summary_glasses" msgid="3808267780579061241">"ຕ້ອງໃຊ້ແອັບນີ້ເພື່ອຈັດການ <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> ຈະໄດ້ຮັບອະນຸຍາດໃຫ້ໂຕ້ຕອບກັບການແຈ້ງເຕືອນຂອງທ່ານ ແລະ ການອະນຸຍາດສິດເຂົ້າເຖິງໂທລະສັບ, SMS, ລາຍຊື່ຜູ້ຕິດຕໍ່, ໄມໂຄຣໂຟນ ແລະ ອຸປະກອນທີ່ຢູ່ໃກ້ຄຽງຂອງທ່ານ."</string>
-    <string name="summary_glasses_single_device" msgid="7051392780285915640">"ຕ້ອງໃຊ້ແອັບດັ່ງກ່າວເພື່ອຈັດການ <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> ຈະໄດ້ຮັບອະນຸຍາດໃຫ້ໂຕ້ຕອບກັບການອະນຸຍາດເຫຼົ່ານີ້:"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"ເລືອກອຸປະກອນທີ່ຈະໃຫ້ມີການຈັດການໂດຍ &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"ເລືອກ <xliff:g id="PROFILE_NAME">%1$s</xliff:g> ທີ່ຈະຕັ້ງຄ່າ"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"ແອັບນີ້ຈະໄດ້ຮັບອະນຸຍາດໃຫ້ຊິ້ງຂໍ້ມູນ, ເຊັ່ນ: ຊື່ຂອງຄົນທີ່ໂທເຂົ້າ ແລະ ສິດເຂົ້າເຖິງການອະນຸຍາດເຫຼົ່ານີ້ຢູ່ <xliff:g id="DEVICE_NAME">%1$s</xliff:g> ຂອງທ່ານ"</string>
+    <string name="confirmation_title_glasses" msgid="8288346850537727333">"ອະນຸຍາດ &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ຈັດການ &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; ບໍ?"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"ອຸປະກອນ"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"ແອັບນີ້ຈະໄດ້ຮັບສິດເຂົ້າເຖິງການອະນຸຍາດເຫຼົ່ານີ້ຢູ່ <xliff:g id="DEVICE_NAME">%1$s</xliff:g> ຂອງທ່ານ"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"ອະນຸຍາດ &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ໃຫ້ເຂົ້າເຖິງຂໍ້ມູນນີ້ຈາກໂທລະສັບຂອງທ່ານໄດ້"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"ບໍລິການຂ້າມອຸປະກອນ"</string>
-    <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> ກຳລັງຮ້ອງຂໍການອະນຸຍາດໃນນາມຂອງ <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> ເພື່ອສະຕຣີມແອັບລະຫວ່າງອຸປະກອນຂອງທ່ານ"</string>
+    <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g> ກຳລັງຮ້ອງຂໍການອະນຸຍາດໃນນາມຂອງ <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> ເພື່ອສະຕຣີມແອັບລະຫວ່າງອຸປະກອນຕ່າງໆຂອງທ່ານ"</string>
     <string name="title_automotive_projection" msgid="3296005598978412847"></string>
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"ອະນຸຍາດ &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ໃຫ້ເຂົ້າເຖິງຂໍ້ມູນນີ້ຈາກໂທລະສັບຂອງທ່ານໄດ້"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"ບໍລິການ Google Play"</string>
-    <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> ກຳລັງຮ້ອງຂໍການອະນຸຍາດໃນນາມຂອງ <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> ເພື່ອເຂົ້າເຖິງຮູບພາບ, ມີເດຍ ແລະ ການແຈ້ງເຕືອນຂອງໂທລະສັບທ່ານ"</string>
-    <string name="title_nearby_device_streaming" msgid="179278282547719200">"ອະນຸຍາດ &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ໃຫ້ດຳເນີນການນີ້ຈາກໂທລະສັບຂອງທ່ານ"</string>
-    <string name="helper_title_nearby_device_streaming" msgid="6124438217620593669">"ບໍລິການຂ້າມອຸປະກອນ"</string>
-    <string name="helper_summary_nearby_device_streaming" msgid="5538329403511524333">"<xliff:g id="APP_NAME">%1$s</xliff:g> ກຳລັງຮ້ອງຂໍການອະນຸຍາດໃນນາມຂອງ <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> ຂອງທ່ານເພື່ອສະຕຣີມເນື້ອຫາໄປຫາອຸປະກອນທີ່ຢູ່ໃກ້ຄຽງ"</string>
+    <string name="helper_summary_computer" msgid="8774832742608187072">"<xliff:g id="APP_NAME">%1$s</xliff:g> ກຳລັງຮ້ອງຂໍການອະນຸຍາດໃນນາມຂອງ <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> ເພື່ອເຂົ້າເຖິງຮູບພາບ, ສື່ ແລະ ການແຈ້ງເຕືອນໃນໂທລະສັບຂອງທ່ານ"</string>
+    <string name="title_nearby_device_streaming" msgid="7269956847378799794">"ອະນຸຍາດ &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; ເພື່ອດຳເນີນຄຳສັ່ງນີ້ບໍ?"</string>
+    <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> ກໍາລັງຮ້ອງຂໍການອະນຸຍາດໃນນາມ <xliff:g id="DEVICE_NAME">%2$s</xliff:g> ຂອງທ່ານເພື່ອສະຕຣີມແອັບ ແລະ ຄຸນສົມບັດລະບົບອື່ນໆໄປຫາອຸປະກອນທີ່ຢູ່ໃກ້ຄຽງ"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"ອຸປະກອນ"</string>
-    <string name="summary_generic_single_device" msgid="4735072202474939111">"ແອັບ​ນີ້​ຈະ​ສາ​ມາດ​ຊິງ​ຂໍ້​ມູນ​ເຊັ່ນ: ຊື່​ຂອງ​ບາງ​ຄົນ​ທີ່​ກຳ​ລັງ​ໂທ​ຢູ່​ລະ​ຫວ່າງ​ໂທ​ລະ​ສັບ​ຂອງ​ທ່ານ ແລະ <xliff:g id="DEVICE_NAME">%1$s</xliff:g> ໄດ້."</string>
-    <string name="summary_generic" msgid="4988130802522924650">"ແອັບ​ນີ້​ຈະ​ສາ​ມາດ​ຊິງ​ຂໍ້​ມູນ​ເຊັ່ນ: ຊື່​ຂອງ​ບາງ​ຄົນ​ທີ່​ກຳ​ລັງ​ໂທ​ຢູ່​ລະ​ຫວ່າງ​ໂທ​ລະ​ສັບ​ຂອງ​ທ່ານ ແລະ ອຸ​ປະ​ກອນ​ທີ່​ເລືອກ​ໄດ້."</string>
+    <string name="summary_generic" msgid="1761976003668044801">"ແອັບນີ້ຈະສາມາດຊິ້ງຂໍ້ມູນ ເຊັ່ນ: ຊື່ຂອງຄົນທີ່ໂທເຂົ້າ, ລະຫວ່າງໂທລະສັບຂອງທ່ານ ແລະ ອຸປະກອນທີ່ເລືອກໄວ້ໄດ້"</string>
     <string name="consent_yes" msgid="8344487259618762872">"ອະນຸຍາດ"</string>
     <string name="consent_no" msgid="2640796915611404382">"ບໍ່ອະນຸຍາດ"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"ຍົກເລີກ"</string>
     <string name="consent_back" msgid="2560683030046918882">"ກັບຄືນ"</string>
+    <string name="permission_expand" msgid="893185038020887411">"ຂະຫຍາຍ <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
+    <string name="permission_collapse" msgid="3320833884220844084">"ຫຍໍ້ <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> ລົງ"</string>
     <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"ໃຫ້ການອະນຸຍາດແອັບຢູ່ &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; ເປັນການອະນຸຍາດດຽວກັນກັບຢູ່ &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; ບໍ?"</string>
     <string name="permission_sync_summary" msgid="765497944331294275">"ສິ່ງນີ້ອາດຮວມມີສິດເຂົ້າເຖິງ &lt;strong&gt;ໄມໂຄຣໂຟນ&lt;/strong&gt;, &lt;strong&gt;ກ້ອງຖ່າຍຮູບ&lt;/strong&gt; ແລະ &lt;strong&gt;ສະຖານທີ່&lt;/strong&gt; ພ້ອມທັງການອະນຸຍາດທີ່ລະອຽດອ່ອນອື່ນໆຢູ່ໃນ &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;. &lt;br/&gt;&lt;br/&gt;ທ່ານສາມາດປ່ຽນແປງສິດການອະນຸຍາດເຫຼົ່ານີ້ໄດ້ທຸກເວລາໃນການຕັ້ງຄ່າຂອງທ່ານຢູ່ &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;."</string>
-    <string name="vendor_icon_description" msgid="4445875290032225965">"ໄອຄອນແອັບ"</string>
-    <string name="vendor_header_button_description" msgid="6566660389500630608">"ປຸ່ມຂໍ້ມູນເພີ່ມເຕີມ"</string>
+    <string name="vendor_header_button_description" msgid="7994879208461111473">"ຂໍ້ມູນເພີ່ມເຕີມ"</string>
     <string name="permission_phone" msgid="2661081078692784919">"ໂທລະສັບ"</string>
     <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
     <string name="permission_contacts" msgid="3858319347208004438">"ລາຍຊື່ຜູ້ຕິດຕໍ່"</string>
@@ -57,16 +57,18 @@
     <string name="permission_storage" msgid="6831099350839392343">"ຮູບພາບ ແລະ ມີເດຍ"</string>
     <string name="permission_notification" msgid="693762568127741203">"ການແຈ້ງເຕືອນ"</string>
     <string name="permission_app_streaming" msgid="6009695219091526422">"ແອັບ"</string>
-    <string name="permission_nearby_device_streaming" msgid="5868108148065023161">"ການສະຕຣີມອຸປະກອນທີ່ຢູ່ໃກ້ຄຽງ"</string>
+    <string name="permission_nearby_device_streaming" msgid="1023325519477349499">"ກຳລັງສະຕຣີມ"</string>
     <string name="permission_phone_summary" msgid="6684396967861278044">"ສາມາດໂທອອກ ແລະ ຈັດການການໂທໄດ້"</string>
     <string name="permission_call_logs_summary" msgid="6186103394658755022">"ສາມາດອ່ານ ແລະ ຂຽນບັນທຶກການໂທຂອງໂທລະສັບ"</string>
     <string name="permission_sms_summary" msgid="3508442683678912017">"ສາມາດສົ່ງ ແລະ ເບິ່ງຂໍ້ຄວາມ SMS"</string>
     <string name="permission_contacts_summary" msgid="675861979475628708">"ສາມາດເຂົ້າເຖິງລາຍຊື່ຜູ້ຕິດຕໍ່ຂອງທ່ານ"</string>
     <string name="permission_calendar_summary" msgid="6460000922511766226">"ສາມາດເຂົ້າເຖິງປະຕິທິນຂອງທ່ານ"</string>
-    <string name="permission_microphone_summary" msgid="4241354865859396558">"ສາມາດບັນທຶກສຽງໂດຍນຳໃຊ້ໄມໂຄຣໂຟນໄດ້"</string>
+    <string name="permission_microphone_summary" msgid="3692091540613093394">"ສາມາດບັນທຶກສຽງໄດ້"</string>
     <string name="permission_nearby_devices_summary" msgid="931940524460876655">"ສາມາດຊອກຫາ, ເຊື່ອມຕໍ່ ແລະ ລະບຸສະຖານທີ່ທີ່ກ່ຽວຂ້ອງກັນຂອງອຸປະກອນທີ່ຢູ່ໃກ້ຄຽງ"</string>
     <string name="permission_notification_summary" msgid="884075314530071011">"ສາມາດອ່ານການແຈ້ງເຕືອນທັງໝົດ, ຮວມທັງຂໍ້ມູນ ເຊັ່ນ: ລາຍຊື່ຜູ້ຕິດຕໍ່, ຂໍ້ຄວາມ ແລະ ຮູບພາບ"</string>
     <string name="permission_app_streaming_summary" msgid="606923325679670624">"ສະຕຣີມແອັບຂອງໂທລະສັບທ່ານ"</string>
     <string name="permission_storage_summary" msgid="3918240895519506417"></string>
-    <string name="permission_nearby_device_streaming_summary" msgid="5776807830582725074">"ສະຕຣີມເນື້ອຫາໄປຫາອຸປະກອນທີ່ຢູ່ໃກ້ຄຽງ"</string>
+    <string name="permission_nearby_device_streaming_summary" msgid="8280824871197081246">"ສະຕຣີມແອັບ ແລະ ຄຸນສົມບັດລະບົບອື່ນໆຈາກໂທລະສັບຂອງທ່ານ"</string>
+    <string name="device_type" product="default" msgid="8268703872070046263">"ໂທລະສັບ"</string>
+    <string name="device_type" product="tablet" msgid="5038791954983067774">"ແທັບເລັດ"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-lt/strings.xml b/packages/CompanionDeviceManager/res/values-lt/strings.xml
index 77f89ad..65a266d 100644
--- a/packages/CompanionDeviceManager/res/values-lt/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-lt/strings.xml
@@ -17,36 +17,36 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Companion Device Manager"</string>
-    <string name="confirmation_title" msgid="8024993972587946678">"Leisti &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; pasiekti &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"Leisti programai &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; pasiekti &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"laikrodį"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Jūsų <xliff:g id="PROFILE_NAME">%1$s</xliff:g>, kurį valdys &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; (pasirinkite)"</string>
-    <string name="summary_watch" msgid="6566922405914995759">"Programa reikalinga norint tvarkyti jūsų įrenginį „<xliff:g id="DEVICE_NAME">%1$s</xliff:g>“. Programai „<xliff:g id="APP_NAME">%2$s</xliff:g>“ bus leidžiama sinchronizuoti tam tikrą informaciją, pvz., skambinančio asmens vardą, sąveikauti su jūsų pranešimais ir pasiekti jūsų leidimus „Telefonas“, „SMS“, „Kontaktai“, „Kalendorius“, „Skambučių žurnalai“ ir „Įrenginiai netoliese“."</string>
-    <string name="summary_watch_single_device" msgid="7443464525873186735">"Programa reikalinga norint tvarkyti jūsų įrenginį „<xliff:g id="DEVICE_NAME">%1$s</xliff:g>“. Programai „<xliff:g id="APP_NAME">%2$s</xliff:g>“ bus leidžiama sinchronizuoti tam tikrą informaciją, pvz., skambinančio asmens vardą, ir pasiekti toliau nurodytus leidimus."</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"akiniai"</string>
-    <string name="summary_glasses" msgid="3808267780579061241">"Ši programa reikalinga norint tvarkyti įrenginį „<xliff:g id="DEVICE_NAME">%1$s</xliff:g>“. Programai „<xliff:g id="APP_NAME">%2$s</xliff:g>“ bus leidžiama sąveikauti su jūsų pranešimais ir pasiekti jūsų leidimus „Telefonas“, „SMS“, „Kontaktai“, „Mikrofonas“ ir „Įrenginiai netoliese“."</string>
-    <string name="summary_glasses_single_device" msgid="7051392780285915640">"Programa reikalinga norint tvarkyti įrenginį „<xliff:g id="DEVICE_NAME">%1$s</xliff:g>“. „<xliff:g id="APP_NAME">%2$s</xliff:g>“ bus leidžiama sąveikauti su toliau nurodytais leidimais."</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"Įrenginio, kuris bus valdomas naudojant programą &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;, pasirinkimas"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Norimo nustatyti <xliff:g id="PROFILE_NAME">%1$s</xliff:g> pasirinkimas"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Šiai programai bus leidžiama sinchronizuoti tam tikrą informaciją, pvz., skambinančio asmens vardą, ir pasiekti toliau nurodytus <xliff:g id="DEVICE_NAME">%1$s</xliff:g> leidimus"</string>
+    <string name="confirmation_title_glasses" msgid="8288346850537727333">"Leisti &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; valdyti &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"įrenginio"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Šiai programai bus leidžiama pasiekti toliau nurodytus <xliff:g id="DEVICE_NAME">%1$s</xliff:g> leidimus."</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Leisti &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; pasiekti šią informaciją iš jūsų telefono"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Pasl. keliuose įrenginiuose"</string>
-    <string name="helper_summary_app_streaming" msgid="5977509499890099">"Programa „<xliff:g id="APP_NAME">%1$s</xliff:g>“ prašo leidimo jūsų „<xliff:g id="DEVICE_TYPE">%2$s</xliff:g>“ vardu, kad galėtų srautu perduoti programas iš vieno įrenginio į kitą"</string>
+    <string name="helper_summary_app_streaming" msgid="2396773196949578425">"Programa „<xliff:g id="APP_NAME">%1$s</xliff:g>“ prašo leidimo jūsų „<xliff:g id="DISPLAY_NAME">%2$s</xliff:g>“ vardu, kad galėtų srautu perduoti programas iš vieno įrenginio į kitą"</string>
     <string name="title_automotive_projection" msgid="3296005598978412847"></string>
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"Leisti &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; pasiekti šią informaciją iš jūsų telefono"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"„Google Play“ paslaugos"</string>
-    <string name="helper_summary_computer" msgid="9050724687678157852">"Programa „<xliff:g id="APP_NAME">%1$s</xliff:g>“ prašo leidimo jūsų „<xliff:g id="DEVICE_TYPE">%2$s</xliff:g>“ vardu, kad galėtų pasiekti telefono nuotraukas, mediją ir pranešimus"</string>
-    <string name="title_nearby_device_streaming" msgid="179278282547719200">"Leisti programai &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; atlikti šį veiksmą iš jūsų telefono"</string>
-    <string name="helper_title_nearby_device_streaming" msgid="6124438217620593669">"Keliais įreng. naud. paslaugos"</string>
-    <string name="helper_summary_nearby_device_streaming" msgid="5538329403511524333">"„<xliff:g id="APP_NAME">%1$s</xliff:g>“ prašo leidimo jūsų „<xliff:g id="DEVICE_TYPE">%2$s</xliff:g>“ vardu, kad galėtų srautu perduoti turinį įrenginiams netoliese"</string>
+    <string name="helper_summary_computer" msgid="8774832742608187072">"Programa „<xliff:g id="APP_NAME">%1$s</xliff:g>“ prašo leidimo jūsų „<xliff:g id="DISPLAY_NAME">%2$s</xliff:g>“ vardu, kad galėtų pasiekti telefono nuotraukas, mediją ir pranešimus"</string>
+    <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Leisti &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; atlikti šį veiksmą?"</string>
+    <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"„<xliff:g id="APP_NAME">%1$s</xliff:g>“ prašo leidimo jūsų „<xliff:g id="DEVICE_NAME">%2$s</xliff:g>“ vardu, kad galėtų srautu perduoti programas ir kitas sistemos funkcijas įrenginiams netoliese"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"įrenginys"</string>
-    <string name="summary_generic_single_device" msgid="4735072202474939111">"Ši programa galės sinchronizuoti tam tikrą informaciją, pvz., skambinančio asmens vardą, su jūsų telefonu ir įrenginiu „<xliff:g id="DEVICE_NAME">%1$s</xliff:g>“."</string>
-    <string name="summary_generic" msgid="4988130802522924650">"Ši programa galės sinchronizuoti tam tikrą informaciją, pvz., skambinančio asmens vardą, su jūsų telefonu ir pasirinktu įrenginiu."</string>
+    <string name="summary_generic" msgid="1761976003668044801">"Ši programa galės sinchronizuoti tam tikrą informaciją, pvz., skambinančio asmens vardą, su jūsų telefonu ir pasirinktu įrenginiu"</string>
     <string name="consent_yes" msgid="8344487259618762872">"Leisti"</string>
     <string name="consent_no" msgid="2640796915611404382">"Neleisti"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Atšaukti"</string>
     <string name="consent_back" msgid="2560683030046918882">"Atgal"</string>
+    <string name="permission_expand" msgid="893185038020887411">"Išskleisti „<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>“"</string>
+    <string name="permission_collapse" msgid="3320833884220844084">"Sutraukti „<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>“"</string>
     <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Suteikti &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; esančioms programoms tuos pačius leidimus kaip &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; esančioms programoms?"</string>
     <string name="permission_sync_summary" msgid="765497944331294275">"Tai gali apimti &lt;strong&gt;mikrofono&lt;/strong&gt;, &lt;strong&gt;fotoaparato&lt;/strong&gt;, ir &lt;strong&gt;prieigos prie vietovės&lt;/strong&gt;, leidimus bei kitus leidimus pasiekti neskelbtiną informaciją įrenginyje &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;. &lt;br/&gt;&lt;br/&gt;Šiuos leidimus galite bet kada pakeisti įrenginio &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt; nustatymų skiltyje."</string>
-    <string name="vendor_icon_description" msgid="4445875290032225965">"Programos piktograma"</string>
-    <string name="vendor_header_button_description" msgid="6566660389500630608">"Mygtukas „Daugiau informacijos“"</string>
+    <string name="vendor_header_button_description" msgid="7994879208461111473">"Daugiau informacijos"</string>
     <string name="permission_phone" msgid="2661081078692784919">"Telefonas"</string>
     <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
     <string name="permission_contacts" msgid="3858319347208004438">"Kontaktai"</string>
@@ -57,16 +57,18 @@
     <string name="permission_storage" msgid="6831099350839392343">"Nuotraukos ir medija"</string>
     <string name="permission_notification" msgid="693762568127741203">"Pranešimai"</string>
     <string name="permission_app_streaming" msgid="6009695219091526422">"Programos"</string>
-    <string name="permission_nearby_device_streaming" msgid="5868108148065023161">"Perdav. įrenginiams netoliese"</string>
+    <string name="permission_nearby_device_streaming" msgid="1023325519477349499">"Srautinis perdavimas"</string>
     <string name="permission_phone_summary" msgid="6684396967861278044">"Gali atlikti ir tvarkyti telefono skambučius"</string>
     <string name="permission_call_logs_summary" msgid="6186103394658755022">"Gali skaityti ir rašyti telefono skambučių žurnalą"</string>
     <string name="permission_sms_summary" msgid="3508442683678912017">"Gali siųsti ir peržiūrėti SMS pranešimus"</string>
     <string name="permission_contacts_summary" msgid="675861979475628708">"Gali pasiekti jūsų kontaktus"</string>
     <string name="permission_calendar_summary" msgid="6460000922511766226">"Gali pasiekti jūsų kalendorių"</string>
-    <string name="permission_microphone_summary" msgid="4241354865859396558">"Naudojant šį mikrofoną negalima įrašyti garso"</string>
+    <string name="permission_microphone_summary" msgid="3692091540613093394">"Gali įrašyti garsą"</string>
     <string name="permission_nearby_devices_summary" msgid="931940524460876655">"Gali rasti apytikslę netoliese esančių įrenginių poziciją, aptikti juos ir prisijungti prie jų"</string>
     <string name="permission_notification_summary" msgid="884075314530071011">"Galima skaityti visus pranešimus, įskaitant tokią informaciją kaip kontaktai, pranešimai ir nuotraukos"</string>
     <string name="permission_app_streaming_summary" msgid="606923325679670624">"Telefono programų perdavimas srautu"</string>
     <string name="permission_storage_summary" msgid="3918240895519506417"></string>
-    <string name="permission_nearby_device_streaming_summary" msgid="5776807830582725074">"Perduokite turinį įrenginiams netoliese"</string>
+    <string name="permission_nearby_device_streaming_summary" msgid="8280824871197081246">"Srautu perduokite programas ir kitas sistemos funkcijas iš telefono"</string>
+    <string name="device_type" product="default" msgid="8268703872070046263">"telefone"</string>
+    <string name="device_type" product="tablet" msgid="5038791954983067774">"planšetiniame kompiuteryje"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-lv/strings.xml b/packages/CompanionDeviceManager/res/values-lv/strings.xml
index 581c84a..1d29edc 100644
--- a/packages/CompanionDeviceManager/res/values-lv/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-lv/strings.xml
@@ -17,43 +17,36 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Palīgierīču pārzinis"</string>
-    <!-- no translation found for confirmation_title (8024993972587946678) -->
-    <skip />
+    <string name="confirmation_title" msgid="2244241995958340998">"Vai atļaut lietotnei &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; piekļūt lietotnei &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"pulkstenis"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Profila (<xliff:g id="PROFILE_NAME">%1$s</xliff:g>) izvēle, ko pārvaldīt lietotnē &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <!-- no translation found for summary_watch (6566922405914995759) -->
-    <skip />
-    <!-- no translation found for summary_watch_single_device (7443464525873186735) -->
-    <skip />
-    <string name="profile_name_glasses" msgid="8488394059007275998">"brilles"</string>
-    <!-- no translation found for summary_glasses (3808267780579061241) -->
-    <skip />
-    <!-- no translation found for summary_glasses_single_device (7051392780285915640) -->
-    <skip />
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"Izvēlieties ierīci, ko pārvaldīt lietotnē &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Jāizvēlas <xliff:g id="PROFILE_NAME">%1$s</xliff:g> iestatīšanai"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Šī lietotne drīkstēs sinhronizēt informāciju, piemēram, zvanītāja vārdu, un piekļūt norādītajām atļaujām jūsu ierīcē (<xliff:g id="DEVICE_NAME">%1$s</xliff:g>)."</string>
+    <string name="confirmation_title_glasses" msgid="8288346850537727333">"Vai atļaut lietotnei &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; piekļūt ierīcei &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"ierīce"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Šai lietotnei tiks sniegta piekļuve norādītajām atļaujām jūsu ierīcē (<xliff:g id="DEVICE_NAME">%1$s</xliff:g>)."</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Atļaut lietotnei &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; piekļūt šai informācijai no jūsu tālruņa"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Vairāku ierīču pakalpojumi"</string>
-    <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> pieprasa atļauju straumēt lietotnes starp jūsu ierīcēm šīs ierīces vārdā: <xliff:g id="DEVICE_TYPE">%2$s</xliff:g>"</string>
+    <string name="helper_summary_app_streaming" msgid="2396773196949578425">"Lietotne <xliff:g id="APP_NAME">%1$s</xliff:g> pieprasa atļauju straumēt lietotnes starp jūsu ierīcēm šīs ierīces vārdā: <xliff:g id="DISPLAY_NAME">%2$s</xliff:g>."</string>
     <string name="title_automotive_projection" msgid="3296005598978412847"></string>
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"Atļaut lietotnei &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; piekļūt šai informācijai no jūsu tālruņa"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Google Play pakalpojumi"</string>
-    <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> pieprasa atļauju piekļūt jūsu tālruņa fotoattēliem, multivides saturam un paziņojumiem šīs ierīces vārdā: <xliff:g id="DEVICE_TYPE">%2$s</xliff:g>"</string>
-    <string name="title_nearby_device_streaming" msgid="179278282547719200">"Atļaut lietotnei &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; veikt šo darbību no jūsu tālruņa"</string>
-    <string name="helper_title_nearby_device_streaming" msgid="6124438217620593669">"Vairāku ierīču pakalpojumi"</string>
-    <string name="helper_summary_nearby_device_streaming" msgid="5538329403511524333">"<xliff:g id="APP_NAME">%1$s</xliff:g> pieprasa atļauju straumēt saturu tuvumā esošās ierīcēs šīs ierīces vārdā: <xliff:g id="DEVICE_TYPE">%2$s</xliff:g>"</string>
+    <string name="helper_summary_computer" msgid="8774832742608187072">"Lietotne <xliff:g id="APP_NAME">%1$s</xliff:g> pieprasa atļauju piekļūt jūsu tālruņa fotoattēliem, multivides saturam un paziņojumiem šīs ierīces vārdā: <xliff:g id="DISPLAY_NAME">%2$s</xliff:g>."</string>
+    <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Vai atļaut ierīcei &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; veikt šo darbību?"</string>
+    <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> pieprasa atļauju tuvumā esošās ierīcēs straumēt lietotnes un citas sistēmas funkcijas šīs ierīces vārdā: <xliff:g id="DEVICE_NAME">%2$s</xliff:g>"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"ierīce"</string>
-    <!-- no translation found for summary_generic_single_device (4735072202474939111) -->
-    <skip />
-    <!-- no translation found for summary_generic (4988130802522924650) -->
-    <skip />
+    <string name="summary_generic" msgid="1761976003668044801">"Šī lietotne varēs sinhronizēt informāciju (piemēram, zvanītāja vārdu) starp jūsu tālruni un izvēlēto ierīci"</string>
     <string name="consent_yes" msgid="8344487259618762872">"Atļaut"</string>
     <string name="consent_no" msgid="2640796915611404382">"Neatļaut"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Atcelt"</string>
     <string name="consent_back" msgid="2560683030046918882">"Atpakaļ"</string>
+    <string name="permission_expand" msgid="893185038020887411">"Izvērst: <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
+    <string name="permission_collapse" msgid="3320833884220844084">"Sakļaut: <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Vai lietotnēm ierīcē &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; piešķirt tādas pašas atļaujas kā ierīcē &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="permission_sync_summary" msgid="765497944331294275">"Tās var būt &lt;strong&gt;mikrofona&lt;/strong&gt;, &lt;strong&gt;kameras&lt;/strong&gt;, &lt;strong&gt;atrašanās vietas piekļuves&lt;/strong&gt; atļaujas, kā arī citas sensitīvas atļaujas ierīcē &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;. &lt;br/&gt;&lt;br/&gt;Atļaujas varat jebkurā brīdī mainīt ierīces &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt; iestatījumos."</string>
-    <string name="vendor_icon_description" msgid="4445875290032225965">"Lietotnes ikona"</string>
-    <string name="vendor_header_button_description" msgid="6566660389500630608">"Plašākas informācijas poga"</string>
+    <string name="vendor_header_button_description" msgid="7994879208461111473">"Plašāka informācija"</string>
     <string name="permission_phone" msgid="2661081078692784919">"Tālrunis"</string>
     <string name="permission_sms" msgid="6337141296535774786">"Īsziņas"</string>
     <string name="permission_contacts" msgid="3858319347208004438">"Kontaktpersonas"</string>
@@ -64,16 +57,18 @@
     <string name="permission_storage" msgid="6831099350839392343">"Fotoattēli un multivides faili"</string>
     <string name="permission_notification" msgid="693762568127741203">"Paziņojumi"</string>
     <string name="permission_app_streaming" msgid="6009695219091526422">"Lietotnes"</string>
-    <string name="permission_nearby_device_streaming" msgid="5868108148065023161">"Straumēšana ierīcēs tuvumā"</string>
+    <string name="permission_nearby_device_streaming" msgid="1023325519477349499">"Straumēšana"</string>
     <string name="permission_phone_summary" msgid="6684396967861278044">"Var veikt un pārvaldīt tālruņa zvanus"</string>
     <string name="permission_call_logs_summary" msgid="6186103394658755022">"Var lasīt un rakstīt tālruņa zvanu žurnālu"</string>
     <string name="permission_sms_summary" msgid="3508442683678912017">"Var sūtīt un skatīt īsziņas"</string>
     <string name="permission_contacts_summary" msgid="675861979475628708">"Var piekļūt jūsu kontaktpersonām"</string>
     <string name="permission_calendar_summary" msgid="6460000922511766226">"Var piekļūt jūsu kalendāram"</string>
-    <string name="permission_microphone_summary" msgid="4241354865859396558">"Var ierakstīt audio, izmantojot mikrofonu"</string>
+    <string name="permission_microphone_summary" msgid="3692091540613093394">"Var ierakstīt audio"</string>
     <string name="permission_nearby_devices_summary" msgid="931940524460876655">"Var atrast tuvumā esošas ierīces, izveidot ar tām savienojumu un noteikt to relatīvo atrašanās vietu"</string>
     <string name="permission_notification_summary" msgid="884075314530071011">"Var lasīt visus paziņojumus, tostarp tādu informāciju kā kontaktpersonas, ziņojumi un fotoattēli."</string>
     <string name="permission_app_streaming_summary" msgid="606923325679670624">"Straumēt jūsu tālruņa lietotnes"</string>
     <string name="permission_storage_summary" msgid="3918240895519506417"></string>
-    <string name="permission_nearby_device_streaming_summary" msgid="5776807830582725074">"Satura straumēšana tuvumā esošā ierīcē"</string>
+    <string name="permission_nearby_device_streaming_summary" msgid="8280824871197081246">"No sava tālruņa straumējiet lietotnes un citas sistēmas funkcijas"</string>
+    <string name="device_type" product="default" msgid="8268703872070046263">"tālrunī"</string>
+    <string name="device_type" product="tablet" msgid="5038791954983067774">"planšetdatorā"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-mk/strings.xml b/packages/CompanionDeviceManager/res/values-mk/strings.xml
index 2768d56..e72e960 100644
--- a/packages/CompanionDeviceManager/res/values-mk/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-mk/strings.xml
@@ -17,43 +17,36 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Companion Device Manager"</string>
-    <!-- no translation found for confirmation_title (8024993972587946678) -->
-    <skip />
+    <string name="confirmation_title" msgid="2244241995958340998">"Дозволувате апликацијата &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; да пристапува до &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"часовник"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Изберете <xliff:g id="PROFILE_NAME">%1$s</xliff:g> со којшто ќе управува &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <!-- no translation found for summary_watch (6566922405914995759) -->
-    <skip />
-    <!-- no translation found for summary_watch_single_device (7443464525873186735) -->
-    <skip />
-    <string name="profile_name_glasses" msgid="8488394059007275998">"очила"</string>
-    <!-- no translation found for summary_glasses (3808267780579061241) -->
-    <skip />
-    <!-- no translation found for summary_glasses_single_device (7051392780285915640) -->
-    <skip />
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"Изберете уред со којшто ќе управува &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Изберете <xliff:g id="PROFILE_NAME">%1$s</xliff:g> за поставување"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Апликацијава ќе има дозвола да ги синхронизира податоците како што се имињата на јавувачите и да пристапува до следниве дозволи на вашиот <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
+    <string name="confirmation_title_glasses" msgid="8288346850537727333">"Ќе дозволите &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; да управува со &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"уред"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Апликацијава ќе може да пристапува до овие дозволи на <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Овозможете &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; да пристапува до овие податоци на телефонот"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Повеќенаменски услуги"</string>
-    <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> бара дозвола во име на вашиот <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> за да стримува апликации помеѓу вашите уреди"</string>
+    <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g> бара дозвола во име на вашиот <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> за да стримува апликации помеѓу вашите уреди"</string>
     <string name="title_automotive_projection" msgid="3296005598978412847"></string>
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"Дозволете &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; да пристапува до овие податоци на телефонот"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Услуги на Google Play"</string>
-    <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> бара дозвола во име на вашиот <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> за да пристапува до фотографиите, аудиовизуелните содржини и известувањата на телефонот"</string>
-    <string name="title_nearby_device_streaming" msgid="179278282547719200">"Дозволете &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; да го извршува ова дејство од телефонот"</string>
-    <string name="helper_title_nearby_device_streaming" msgid="6124438217620593669">"Услуги на повеќе уреди"</string>
-    <string name="helper_summary_nearby_device_streaming" msgid="5538329403511524333">"<xliff:g id="APP_NAME">%1$s</xliff:g> бара дозвола во име на вашиот <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> за да стримува содржини на уредите во близина"</string>
+    <string name="helper_summary_computer" msgid="8774832742608187072">"<xliff:g id="APP_NAME">%1$s</xliff:g> бара дозвола во име на вашиот <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> за да пристапува до фотографиите, аудиовизуелните содржини и известувањата на телефонот"</string>
+    <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Ќе дозволите &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; да го преземе ова дејство?"</string>
+    <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> бара дозвола во име на вашиот <xliff:g id="DEVICE_NAME">%2$s</xliff:g> за да стримува апликации и други системски функции на уредите во близина"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"уред"</string>
-    <!-- no translation found for summary_generic_single_device (4735072202474939111) -->
-    <skip />
-    <!-- no translation found for summary_generic (4988130802522924650) -->
-    <skip />
+    <string name="summary_generic" msgid="1761976003668044801">"Оваа апликација ќе може да ги синхронизира податоците како што се имињата на јавувачите помеѓу вашиот телефон и избраниот уред"</string>
     <string name="consent_yes" msgid="8344487259618762872">"Дозволи"</string>
     <string name="consent_no" msgid="2640796915611404382">"Не дозволувај"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Откажи"</string>
     <string name="consent_back" msgid="2560683030046918882">"Назад"</string>
+    <string name="permission_expand" msgid="893185038020887411">"Прошири <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
+    <string name="permission_collapse" msgid="3320833884220844084">"Собери <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Дасе дадат исти дозволи на апликациите на &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; како на &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="permission_sync_summary" msgid="765497944331294275">"Ова може да вклучува дозволи за пристап до &lt;strong&gt;микрофонот&lt;/strong&gt;, &lt;strong&gt;камерата&lt;/strong&gt; и &lt;strong&gt;локацијата&lt;/strong&gt;, како и други чувствителни дозволи на &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;. &lt;br/&gt;&lt;br/&gt;Дозволиве може да ги промените во секое време во „Поставки“ на &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;."</string>
-    <string name="vendor_icon_description" msgid="4445875290032225965">"Икона на апликацијата"</string>
-    <string name="vendor_header_button_description" msgid="6566660389500630608">"Копче за повеќе информации"</string>
+    <string name="vendor_header_button_description" msgid="7994879208461111473">"Повеќе детали"</string>
     <string name="permission_phone" msgid="2661081078692784919">"Телефон"</string>
     <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
     <string name="permission_contacts" msgid="3858319347208004438">"Контакти"</string>
@@ -64,16 +57,18 @@
     <string name="permission_storage" msgid="6831099350839392343">"Аудиовизуелни содржини"</string>
     <string name="permission_notification" msgid="693762568127741203">"Известувања"</string>
     <string name="permission_app_streaming" msgid="6009695219091526422">"Апликации"</string>
-    <string name="permission_nearby_device_streaming" msgid="5868108148065023161">"Стриминг на уреди во близина"</string>
+    <string name="permission_nearby_device_streaming" msgid="1023325519477349499">"Стриминг"</string>
     <string name="permission_phone_summary" msgid="6684396967861278044">"Може да упатува и управува со телефонски повици"</string>
     <string name="permission_call_logs_summary" msgid="6186103394658755022">"Може да чита и пишува евиденција на повици во телефонот"</string>
     <string name="permission_sms_summary" msgid="3508442683678912017">"Може да испраќа и гледа SMS-пораки"</string>
     <string name="permission_contacts_summary" msgid="675861979475628708">"Може да пристапува до вашите контакти"</string>
     <string name="permission_calendar_summary" msgid="6460000922511766226">"Може да пристапува до вашиот календар"</string>
-    <string name="permission_microphone_summary" msgid="4241354865859396558">"Може да снима аудио со микрофонот"</string>
+    <string name="permission_microphone_summary" msgid="3692091540613093394">"Може да снима аудио"</string>
     <string name="permission_nearby_devices_summary" msgid="931940524460876655">"Може да наоѓа и да се поврзува со уреди во близина и да ја утврдува нивната релативна положба"</string>
     <string name="permission_notification_summary" msgid="884075314530071011">"може да ги чита сите известувања, вклучително и податоци како контакти, пораки и фотографии"</string>
     <string name="permission_app_streaming_summary" msgid="606923325679670624">"Стримувајте ги апликациите на телефонот"</string>
     <string name="permission_storage_summary" msgid="3918240895519506417"></string>
-    <string name="permission_nearby_device_streaming_summary" msgid="5776807830582725074">"Стримување содржини на уреди во близина"</string>
+    <string name="permission_nearby_device_streaming_summary" msgid="8280824871197081246">"Апликации за стриминг и други системски функции од вашиот телефон"</string>
+    <string name="device_type" product="default" msgid="8268703872070046263">"Телефон"</string>
+    <string name="device_type" product="tablet" msgid="5038791954983067774">"Таблет"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-ml/strings.xml b/packages/CompanionDeviceManager/res/values-ml/strings.xml
index 920cf83..1703742 100644
--- a/packages/CompanionDeviceManager/res/values-ml/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ml/strings.xml
@@ -17,36 +17,36 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"കമ്പാനിയൻ ഉപകരണ മാനേജർ"</string>
-    <string name="confirmation_title" msgid="8024993972587946678">"&lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; ആക്‌സസ് ചെയ്യാൻ &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; എന്നതിനെ അനുവദിക്കുക"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"&lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; ആക്‌സസ് ചെയ്യാൻ &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; എന്ന ആപ്പിനെ അനുവദിക്കണോ?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"വാച്ച്"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"&lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; ഉപയോഗിച്ച് മാനേജ് ചെയ്യുന്നതിന് ഒരു <xliff:g id="PROFILE_NAME">%1$s</xliff:g> തിരഞ്ഞെടുക്കുക"</string>
-    <string name="summary_watch" msgid="6566922405914995759">"നിങ്ങളുടെ <xliff:g id="DEVICE_NAME">%1$s</xliff:g> മാനേജ് ചെയ്യാൻ ആപ്പ് ആവശ്യമാണ്. വിളിക്കുന്നയാളുടെ പേര് പോലുള്ള വിവരങ്ങൾ സമന്വയിപ്പിക്കുന്നതിനും നിങ്ങളുടെ അറിയിപ്പുകളുമായി സംവദിക്കാനും നിങ്ങളുടെ ഫോൺ, SMS, Contacts, Calendar, കോൾ ചരിത്രം, സമീപമുള്ള ഉപകരണങ്ങളുടെ അനുമതികൾ എന്നിവ ആക്‌സസ് ചെയ്യാനും <xliff:g id="APP_NAME">%2$s</xliff:g> ആപ്പിനെ അനുവദിക്കും."</string>
-    <string name="summary_watch_single_device" msgid="7443464525873186735">"നിങ്ങളുടെ <xliff:g id="DEVICE_NAME">%1$s</xliff:g> മാനേജ് ചെയ്യാൻ ആപ്പ് ആവശ്യമാണ്. വിളിക്കുന്നയാളുടെ പേര് പോലുള്ള വിവരങ്ങൾ സമന്വയിപ്പിക്കുന്നതിനും ഈ അനുമതികൾ ആക്സസ് ചെയ്യുന്നതിനും <xliff:g id="APP_NAME">%2$s</xliff:g> എന്നതിനെ അനുവദിക്കും:"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"ഗ്ലാസുകൾ"</string>
-    <string name="summary_glasses" msgid="3808267780579061241">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> മാനേജ് ചെയ്യാൻ ഈ ആപ്പ് ആവശ്യമാണ്. നിങ്ങളുടെ അറിയിപ്പുകളുമായി സംവദിക്കാനും ഫോൺ, SMS, കോൺടാക്റ്റുകൾ, മൈക്രോഫോൺ, സമീപമുള്ള ഉപകരണങ്ങളുടെ അനുമതികൾ എന്നിവ ആക്‌സസ് ചെയ്യാനും <xliff:g id="APP_NAME">%2$s</xliff:g> എന്നതിനെ അനുവദിക്കും."</string>
-    <string name="summary_glasses_single_device" msgid="7051392780285915640">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> മാനേജ് ചെയ്യാൻ ആപ്പ് ആവശ്യമാണ്. ഇനിപ്പറയുന്ന അനുമതികളുമായി സംവദിക്കാൻ <xliff:g id="APP_NAME">%2$s</xliff:g> ആപ്പിനെ അനുവദിക്കും:"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ഉപയോഗിച്ച് മാനേജ് ചെയ്യുന്നതിന് ഒരു ഉപകരണം തിരഞ്ഞെടുക്കുക"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"സജ്ജീകരിക്കാൻ <xliff:g id="PROFILE_NAME">%1$s</xliff:g> തിരഞ്ഞെടുക്കുക"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"വിളിക്കുന്നയാളുടെ പേര് പോലുള്ള വിവരങ്ങൾ സമന്വയിപ്പിക്കാനും നിങ്ങളുടെ <xliff:g id="DEVICE_NAME">%1$s</xliff:g> എന്നതിൽ ഈ അനുമതികൾ ആക്സസ് ചെയ്യാനും ഈ ആപ്പിനെ അനുവദിക്കും"</string>
+    <string name="confirmation_title_glasses" msgid="8288346850537727333">"&lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;? മാനേജ് ചെയ്യാൻ, &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; എന്നതിനെ അനുവദിക്കുക"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"ഉപകരണം"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"നിങ്ങളുടെ <xliff:g id="DEVICE_NAME">%1$s</xliff:g> എന്നതിൽ ഇനിപ്പറയുന്ന അനുമതികൾ ആക്സസ് ചെയ്യാൻ ഈ ആപ്പിനെ അനുവദിക്കും"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"നിങ്ങളുടെ ഫോണിൽ നിന്ന് ഈ വിവരങ്ങൾ ആക്‌സസ് ചെയ്യാൻ &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ആപ്പിനെ അനുവദിക്കുക"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"ക്രോസ്-ഉപകരണ സേവനങ്ങൾ"</string>
-    <string name="helper_summary_app_streaming" msgid="5977509499890099">"നിങ്ങളുടെ ഉപകരണങ്ങളിൽ ഒന്നിൽ നിന്ന് അടുത്തതിലേക്ക് ആപ്പുകൾ സ്ട്രീം ചെയ്യാൻ <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> ഉപകരണത്തിന് വേണ്ടി <xliff:g id="APP_NAME">%1$s</xliff:g> അനുമതി അഭ്യർത്ഥിക്കുന്നു"</string>
+    <string name="helper_summary_app_streaming" msgid="2396773196949578425">"നിങ്ങളുടെ ഉപകരണങ്ങളിൽ ഒന്നിൽ നിന്ന് അടുത്തതിലേക്ക് ആപ്പുകൾ സ്ട്രീം ചെയ്യാൻ <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> എന്ന ഉപകരണത്തിന് വേണ്ടി <xliff:g id="APP_NAME">%1$s</xliff:g> എന്നത് അനുമതി അഭ്യർത്ഥിക്കുന്നു"</string>
     <string name="title_automotive_projection" msgid="3296005598978412847"></string>
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"നിങ്ങളുടെ ഫോണിൽ നിന്ന് ഈ വിവരങ്ങൾ ആക്‌സസ് ചെയ്യാൻ &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ആപ്പിനെ അനുവദിക്കുക"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Google Play സേവനങ്ങൾ"</string>
-    <string name="helper_summary_computer" msgid="9050724687678157852">"നിങ്ങളുടെ ഫോണിലെ ഫോട്ടോകൾ, മീഡിയ, അറിയിപ്പുകൾ എന്നിവ ആക്സസ് ചെയ്യാൻ <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> ഉപകരണത്തിന് വേണ്ടി <xliff:g id="APP_NAME">%1$s</xliff:g> അനുമതി അഭ്യർത്ഥിക്കുന്നു"</string>
-    <string name="title_nearby_device_streaming" msgid="179278282547719200">"നിങ്ങളുടെ ഫോണിൽ നിന്ന് ഈ പ്രവർത്തനം നടത്താൻ &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ആപ്പിനെ അനുവദിക്കുക"</string>
-    <string name="helper_title_nearby_device_streaming" msgid="6124438217620593669">"ക്രോസ്-ഉപകരണ സേവനങ്ങൾ"</string>
-    <string name="helper_summary_nearby_device_streaming" msgid="5538329403511524333">"സമീപമുള്ള ഉപകരണങ്ങളിൽ ഉള്ളടക്കം സ്ട്രീം ചെയ്യാൻ നിങ്ങളുടെ <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> എന്നതിന് വേണ്ടി <xliff:g id="APP_NAME">%1$s</xliff:g> അനുമതി അഭ്യർത്ഥിക്കുന്നു"</string>
+    <string name="helper_summary_computer" msgid="8774832742608187072">"നിങ്ങളുടെ ഫോണിലെ ഫോട്ടോകൾ, മീഡിയ, അറിയിപ്പുകൾ എന്നിവ ആക്സസ് ചെയ്യാൻ <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> എന്ന ഉപകരണത്തിന് വേണ്ടി <xliff:g id="APP_NAME">%1$s</xliff:g> അനുമതി അഭ്യർത്ഥിക്കുന്നു"</string>
+    <string name="title_nearby_device_streaming" msgid="7269956847378799794">"ഈ പ്രവർത്തനം നടത്താൻ &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; എന്നതിനെ അനുവദിക്കണോ?"</string>
+    <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"സമീപമുള്ള ഉപകരണങ്ങളിൽ ആപ്പുകളും മറ്റ് സിസ്റ്റം ഫീച്ചറുകളും സ്ട്രീം ചെയ്യാൻ നിങ്ങളുടെ <xliff:g id="DEVICE_NAME">%2$s</xliff:g> എന്നതിന് വേണ്ടി <xliff:g id="APP_NAME">%1$s</xliff:g> അനുമതി അഭ്യർത്ഥിക്കുന്നു"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"ഉപകരണം"</string>
-    <string name="summary_generic_single_device" msgid="4735072202474939111">"വിളിക്കുന്നയാളുടെ പേര് പോലുള്ള വിവരങ്ങൾ നിങ്ങളുടെ ഫോണിനും <xliff:g id="DEVICE_NAME">%1$s</xliff:g> എന്നതിനും ഇടയിൽ സമന്വയിപ്പിക്കുന്നതിന് ഈ ആപ്പിന് കഴിയും."</string>
-    <string name="summary_generic" msgid="4988130802522924650">"വിളിക്കുന്നയാളുടെ പേര് പോലുള്ള വിവരങ്ങൾ നിങ്ങളുടെ ഫോണിനും തിരഞ്ഞെടുത്ത ഉപകരണത്തിനും ഇടയിൽ സമന്വയിപ്പിക്കുന്നതിന് ഈ ആപ്പിന് കഴിയും."</string>
+    <string name="summary_generic" msgid="1761976003668044801">"വിളിക്കുന്നയാളുടെ പേര് പോലുള്ള വിവരങ്ങൾ നിങ്ങളുടെ ഫോണിനും തിരഞ്ഞെടുത്ത ഉപകരണത്തിനും ഇടയിൽ സമന്വയിപ്പിക്കുന്നതിന് ഈ ആപ്പിന് കഴിയും"</string>
     <string name="consent_yes" msgid="8344487259618762872">"അനുവദിക്കുക"</string>
     <string name="consent_no" msgid="2640796915611404382">"അനുവദിക്കരുത്"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"റദ്ദാക്കുക"</string>
     <string name="consent_back" msgid="2560683030046918882">"മടങ്ങുക"</string>
+    <string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> വികസിപ്പിക്കുക"</string>
+    <string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> ചുരുക്കുക"</string>
     <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"&lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; എന്നതിലെ അതേ അനുമതികൾ &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; എന്നതിലെ ആപ്പുകൾക്ക് നൽകണോ?"</string>
     <string name="permission_sync_summary" msgid="765497944331294275">"&lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;. എന്നതിലെ &lt;strong&gt;മൈക്രോഫോൺ&lt;/strong&gt;, &lt;strong&gt;ക്യാമറ&lt;/strong&gt;, and &lt;strong&gt;ലൊക്കേഷൻ ആക്‌സസ്&lt;/strong&gt;, സെൻസിറ്റീവ് വിവരങ്ങൾക്കുള്ള മറ്റ് അനുമതികൾ എന്നിവയും ഇതിൽ ഉൾപ്പെട്ടേക്കാം. &lt;br/&gt;&lt;br/&gt;നിങ്ങൾക്ക് &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt; എന്നതിലെ ക്രമീകരണത്തിൽ ഏതുസമയത്തും ഈ അനുമതികൾ മാറ്റാം."</string>
-    <string name="vendor_icon_description" msgid="4445875290032225965">"ആപ്പ് ഐക്കൺ"</string>
-    <string name="vendor_header_button_description" msgid="6566660389500630608">"കൂടുതൽ വിവരങ്ങൾ ബട്ടൺ"</string>
+    <string name="vendor_header_button_description" msgid="7994879208461111473">"കൂടുതൽ വിവരങ്ങൾ"</string>
     <string name="permission_phone" msgid="2661081078692784919">"ഫോൺ"</string>
     <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
     <string name="permission_contacts" msgid="3858319347208004438">"Contacts"</string>
@@ -57,16 +57,18 @@
     <string name="permission_storage" msgid="6831099350839392343">"ഫോട്ടോകളും മീഡിയയും"</string>
     <string name="permission_notification" msgid="693762568127741203">"അറിയിപ്പുകൾ"</string>
     <string name="permission_app_streaming" msgid="6009695219091526422">"ആപ്പുകൾ"</string>
-    <string name="permission_nearby_device_streaming" msgid="5868108148065023161">"സമീപമുള്ള ഉപകരണ സ്ട്രീമിംഗ്"</string>
+    <string name="permission_nearby_device_streaming" msgid="1023325519477349499">"സ്ട്രീമിംഗ്"</string>
     <string name="permission_phone_summary" msgid="6684396967861278044">"ഫോൺ കോളുകൾ ചെയ്യാനും അവ മാനേജ് ചെയ്യാനും കഴിയും"</string>
     <string name="permission_call_logs_summary" msgid="6186103394658755022">"ഫോൺ കോൾ ചരിത്രം റീഡ് ചെയ്യാനും റൈറ്റ് ചെയ്യാനും കഴിയും"</string>
     <string name="permission_sms_summary" msgid="3508442683678912017">"SMS സന്ദേശങ്ങൾ അയയ്‌ക്കാനും കാണാനും കഴിയും"</string>
     <string name="permission_contacts_summary" msgid="675861979475628708">"നിങ്ങളുടെ കോൺടാക്‌റ്റുകൾ ആക്‌സസ് ചെയ്യാൻ കഴിയും"</string>
     <string name="permission_calendar_summary" msgid="6460000922511766226">"നിങ്ങളുടെ കലണ്ടർ ആക്‌സസ് ചെയ്യാൻ കഴിയും"</string>
-    <string name="permission_microphone_summary" msgid="4241354865859396558">"മൈക്രോഫോൺ ഉപയോഗിച്ച് ഓഡിയോ റെക്കോർഡ് ചെയ്യാം"</string>
+    <string name="permission_microphone_summary" msgid="3692091540613093394">"ഓഡിയോ റെക്കോർഡ് ചെയ്യാം"</string>
     <string name="permission_nearby_devices_summary" msgid="931940524460876655">"സമീപമുള്ള ഉപകരണങ്ങൾ കണ്ടെത്താനും അവയിലേക്ക് കണക്റ്റ് ചെയ്യാനും അവയുടെ ആപേക്ഷിക സ്ഥാനം നിർണ്ണയിക്കാനും കഴിയും"</string>
     <string name="permission_notification_summary" msgid="884075314530071011">"കോൺടാക്‌റ്റുകൾ, സന്ദേശങ്ങൾ, ഫോട്ടോകൾ മുതലായ വിവരങ്ങൾ ഉൾപ്പെടെയുള്ള എല്ലാ അറിയിപ്പുകളും വായിക്കാനാകും"</string>
     <string name="permission_app_streaming_summary" msgid="606923325679670624">"നിങ്ങളുടെ ഫോണിലെ ആപ്പുകൾ സ്‌ട്രീം ചെയ്യുക"</string>
     <string name="permission_storage_summary" msgid="3918240895519506417"></string>
-    <string name="permission_nearby_device_streaming_summary" msgid="5776807830582725074">"സമീപമുള്ള ഉപകരണത്തിൽ ഉള്ളടക്കം സ്ട്രീം ചെയ്യുക"</string>
+    <string name="permission_nearby_device_streaming_summary" msgid="8280824871197081246">"നിങ്ങളുടെ ഫോണിൽ നിന്ന് ആപ്പുകളും മറ്റ് സിസ്റ്റം ഫീച്ചറുകളും സ്ട്രീം ചെയ്യാം"</string>
+    <string name="device_type" product="default" msgid="8268703872070046263">"ഫോൺ"</string>
+    <string name="device_type" product="tablet" msgid="5038791954983067774">"ടാബ്‌ലെറ്റ്"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-mn/strings.xml b/packages/CompanionDeviceManager/res/values-mn/strings.xml
index 9ce4124..613c9aa 100644
--- a/packages/CompanionDeviceManager/res/values-mn/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-mn/strings.xml
@@ -17,43 +17,36 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Companion Device Manager"</string>
-    <!-- no translation found for confirmation_title (8024993972587946678) -->
-    <skip />
+    <string name="confirmation_title" msgid="2244241995958340998">"&lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;-д &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; аппыг хандахыг зөвшөөрөх үү?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"цаг"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"&lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;-н удирдах<xliff:g id="PROFILE_NAME">%1$s</xliff:g>-г сонгоно уу"</string>
-    <!-- no translation found for summary_watch (6566922405914995759) -->
-    <skip />
-    <!-- no translation found for summary_watch_single_device (7443464525873186735) -->
-    <skip />
-    <string name="profile_name_glasses" msgid="8488394059007275998">"нүдний шил"</string>
-    <!-- no translation found for summary_glasses (3808267780579061241) -->
-    <skip />
-    <!-- no translation found for summary_glasses_single_device (7051392780285915640) -->
-    <skip />
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;-н удирдах төхөөрөмжийг сонгоно уу"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Тохируулахын тулд <xliff:g id="PROFILE_NAME">%1$s</xliff:g> сонгоно уу"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Энэ аппад залгаж буй хүний нэр зэрэг мэдээллийг синк хийх болон таны <xliff:g id="DEVICE_NAME">%1$s</xliff:g>-н эдгээр зөвшөөрөлд хандахыг зөвшөөрнө"</string>
+    <string name="confirmation_title_glasses" msgid="8288346850537727333">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;-д &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;-г удирдахыг зөвшөөрөх үү?"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"төхөөрөмж"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Энэ апп таны <xliff:g id="DEVICE_NAME">%1$s</xliff:g>-н эдгээр зөвшөөрөлд хандах эрхтэй байх болно"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;-д таны утаснаас энэ мэдээлэлд хандахыг зөвшөөрнө үү"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Төхөөрөмж хоорондын үйлчилгээ"</string>
-    <string name="helper_summary_app_streaming" msgid="5977509499890099">"Таны төхөөрөмжүүд хооронд апп дамжуулахын тулд <xliff:g id="APP_NAME">%1$s</xliff:g> таны <xliff:g id="DEVICE_TYPE">%2$s</xliff:g>-н өмнөөс зөвшөөрөл хүсэж байна"</string>
+    <string name="helper_summary_app_streaming" msgid="2396773196949578425">"Таны төхөөрөмжүүд хооронд апп дамжуулахын тулд <xliff:g id="APP_NAME">%1$s</xliff:g> таны <xliff:g id="DISPLAY_NAME">%2$s</xliff:g>-н өмнөөс зөвшөөрөл хүсэж байна"</string>
     <string name="title_automotive_projection" msgid="3296005598978412847"></string>
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;-д таны утаснаас энэ мэдээлэлд хандахыг зөвшөөрнө үү"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Google Play үйлчилгээ"</string>
-    <string name="helper_summary_computer" msgid="9050724687678157852">"Таны утасны зураг, медиа болон мэдэгдэлд хандахын тулд <xliff:g id="APP_NAME">%1$s</xliff:g> таны <xliff:g id="DEVICE_TYPE">%2$s</xliff:g>-н өмнөөс зөвшөөрөл хүсэж байна"</string>
-    <string name="title_nearby_device_streaming" msgid="179278282547719200">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;-д энэ үйлдлийг таны утаснаас гүйцэтгэхийг зөвшөөрнө үү"</string>
-    <string name="helper_title_nearby_device_streaming" msgid="6124438217620593669">"Төхөөрөмж хоорондын үйлчилгээ"</string>
-    <string name="helper_summary_nearby_device_streaming" msgid="5538329403511524333">"<xliff:g id="APP_NAME">%1$s</xliff:g> таны <xliff:g id="DEVICE_TYPE">%2$s</xliff:g>-н өмнөөс ойролцоох төхөөрөмжүүд рүү контент дамжуулах зөвшөөрөл хүсэж байна"</string>
+    <string name="helper_summary_computer" msgid="8774832742608187072">"Таны утасны зураг, медиа болон мэдэгдэлд хандахын тулд <xliff:g id="APP_NAME">%1$s</xliff:g> таны <xliff:g id="DISPLAY_NAME">%2$s</xliff:g>-н өмнөөс зөвшөөрөл хүсэж байна"</string>
+    <string name="title_nearby_device_streaming" msgid="7269956847378799794">"&lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt;-д энэ үйлдлийг хийхийг зөвшөөрөх үү?"</string>
+    <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> таны <xliff:g id="DEVICE_NAME">%2$s</xliff:g>-н өмнөөс аппууд болон системийн бусад онцлогийг ойролцоох төхөөрөмжүүд рүү дамжуулах зөвшөөрөл хүсэж байна"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"төхөөрөмж"</string>
-    <!-- no translation found for summary_generic_single_device (4735072202474939111) -->
-    <skip />
-    <!-- no translation found for summary_generic (4988130802522924650) -->
-    <skip />
+    <string name="summary_generic" msgid="1761976003668044801">"Энэ апп залгаж буй хүний нэр зэрэг мэдээллийг таны утас болон сонгосон төхөөрөмжийн хооронд синк хийх боломжтой болно"</string>
     <string name="consent_yes" msgid="8344487259618762872">"Зөвшөөрөх"</string>
     <string name="consent_no" msgid="2640796915611404382">"Бүү зөвшөөр"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Цуцлах"</string>
     <string name="consent_back" msgid="2560683030046918882">"Буцах"</string>
+    <string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>-г дэлгэх"</string>
+    <string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>-г хураах"</string>
     <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"&lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; дээрх аппуудад &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; дээрхтэй адил зөвшөөрөл өгөх үү?"</string>
     <string name="permission_sync_summary" msgid="765497944331294275">"Үүнд &lt;strong&gt;Микрофон&lt;/strong&gt;, &lt;strong&gt;Камер&lt;/strong&gt;,, &lt;strong&gt;Байршлын хандалт&lt;/strong&gt; болон &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt; дээрх бусад эмзэг зөвшөөрөл багтаж болно. &lt;br/&gt;&lt;br/&gt;Та эдгээр зөвшөөрлийг &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt; дээрх Тохиргоондоо хүссэн үедээ өөрчлөх боломжтой."</string>
-    <string name="vendor_icon_description" msgid="4445875290032225965">"Aппын дүрс тэмдэг"</string>
-    <string name="vendor_header_button_description" msgid="6566660389500630608">"Дэлгэрэнгүй мэдээллийн товчлуур"</string>
+    <string name="vendor_header_button_description" msgid="7994879208461111473">"Дэлгэрэнгүй мэдээлэл"</string>
     <string name="permission_phone" msgid="2661081078692784919">"Утас"</string>
     <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
     <string name="permission_contacts" msgid="3858319347208004438">"Харилцагчид"</string>
@@ -64,16 +57,18 @@
     <string name="permission_storage" msgid="6831099350839392343">"Зураг болон медиа"</string>
     <string name="permission_notification" msgid="693762568127741203">"Мэдэгдэл"</string>
     <string name="permission_app_streaming" msgid="6009695219091526422">"Аппууд"</string>
-    <string name="permission_nearby_device_streaming" msgid="5868108148065023161">"Ойролцоох төхөөрөмжид дамжуул"</string>
+    <string name="permission_nearby_device_streaming" msgid="1023325519477349499">"Дамжуулах"</string>
     <string name="permission_phone_summary" msgid="6684396967861278044">"Дуудлага хийх, удирдах боломжтой"</string>
     <string name="permission_call_logs_summary" msgid="6186103394658755022">"Утасны дуудлагын жагсаалтыг уншиж, бичих боломжтой"</string>
     <string name="permission_sms_summary" msgid="3508442683678912017">"SMS мессеж илгээх, үзэх боломжтой"</string>
     <string name="permission_contacts_summary" msgid="675861979475628708">"Таны харилцагчдад хандах боломжтой"</string>
     <string name="permission_calendar_summary" msgid="6460000922511766226">"Таны календарьт хандах боломжтой"</string>
-    <string name="permission_microphone_summary" msgid="4241354865859396558">"Микрофоныг ашиглан аудио бичих боломжтой"</string>
+    <string name="permission_microphone_summary" msgid="3692091540613093394">"Аудио бичих боломжтой"</string>
     <string name="permission_nearby_devices_summary" msgid="931940524460876655">"Ойролцоох төхөөрөмжүүдийн харьцангуй байршлыг тодорхойлох, холбох, олох боломжтой"</string>
     <string name="permission_notification_summary" msgid="884075314530071011">"Харилцагчид, мессеж болон зураг зэрэг мэдээллийг оруулаад бүх мэдэгдлийг унших боломжтой"</string>
     <string name="permission_app_streaming_summary" msgid="606923325679670624">"Утасныхаа аппуудыг дамжуулаарай"</string>
     <string name="permission_storage_summary" msgid="3918240895519506417"></string>
-    <string name="permission_nearby_device_streaming_summary" msgid="5776807830582725074">"Ойролцоох төхөөрөмжид контент дамжуулах"</string>
+    <string name="permission_nearby_device_streaming_summary" msgid="8280824871197081246">"Утаснаасаа аппууд болон системийн бусад онцлогийг дамжуулаарай"</string>
+    <string name="device_type" product="default" msgid="8268703872070046263">"утас"</string>
+    <string name="device_type" product="tablet" msgid="5038791954983067774">"таблет"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-mr/strings.xml b/packages/CompanionDeviceManager/res/values-mr/strings.xml
index 2c4d6f3..50ab307 100644
--- a/packages/CompanionDeviceManager/res/values-mr/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-mr/strings.xml
@@ -17,43 +17,36 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"सहयोगी डिव्हाइस व्यवस्थापक"</string>
-    <!-- no translation found for confirmation_title (8024993972587946678) -->
-    <skip />
+    <string name="confirmation_title" msgid="2244241995958340998">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; अ‍ॅपला &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; अ‍ॅक्सेस करण्याची अनुमती द्यायची आहे का?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"वॉच"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"&lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; द्वारे व्यवस्थापित करण्यासाठी <xliff:g id="PROFILE_NAME">%1$s</xliff:g> निवडा"</string>
-    <!-- no translation found for summary_watch (6566922405914995759) -->
-    <skip />
-    <!-- no translation found for summary_watch_single_device (7443464525873186735) -->
-    <skip />
-    <string name="profile_name_glasses" msgid="8488394059007275998">"Glasses"</string>
-    <!-- no translation found for summary_glasses (3808267780579061241) -->
-    <skip />
-    <!-- no translation found for summary_glasses_single_device (7051392780285915640) -->
-    <skip />
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; द्वारे व्यवस्थापित करण्यासाठी डिव्हाइस निवडा"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"सेट करण्यासाठी <xliff:g id="PROFILE_NAME">%1$s</xliff:g> निवडा"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"या अ‍ॅपला कॉल करत असलेल्या एखाद्या व्यक्तीचे नाव यासारखी माहिती सिंक करण्याची आणि तुमच्या <xliff:g id="DEVICE_NAME">%1$s</xliff:g> वर या परवानग्या अ‍ॅक्सेस करण्याची अनुमती असेल"</string>
+    <string name="confirmation_title_glasses" msgid="8288346850537727333">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ला &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; व्यवस्थापित करण्याची अनुमती द्यायची आहे?"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"डिव्हाइस"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"या अ‍ॅपला तुमच्या <xliff:g id="DEVICE_NAME">%1$s</xliff:g> वर या परवानग्या अ‍ॅक्सेस करण्याची अनुमती असेल"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ला ही माहिती तुमच्या फोनवरून अ‍ॅक्सेस करण्यासाठी अनुमती द्या"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"क्रॉस-डिव्हाइस सेवा"</string>
-    <string name="helper_summary_app_streaming" msgid="5977509499890099">"तुमच्या डिव्हाइसदरम्यान ॲप्स स्ट्रीम करण्यासाठी <xliff:g id="APP_NAME">%1$s</xliff:g> हे तुमच्या <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> च्या वतीने परवानगीची विनंती करत आहे"</string>
+    <string name="helper_summary_app_streaming" msgid="2396773196949578425">"तुमच्या डिव्हाइसदरम्यान ॲप्स स्ट्रीम करण्यासाठी <xliff:g id="APP_NAME">%1$s</xliff:g> हे तुमच्या <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> च्या वतीने परवानगीची विनंती करत आहे"</string>
     <string name="title_automotive_projection" msgid="3296005598978412847"></string>
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ला ही माहिती तुमच्या फोनवरून अ‍ॅक्सेस करण्यासाठी अनुमती द्या"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Google Play सेवा"</string>
-    <string name="helper_summary_computer" msgid="9050724687678157852">"तुमच्या फोनमधील फोटो, मीडिया आणि सूचना ॲक्सेस करण्यासाठी <xliff:g id="APP_NAME">%1$s</xliff:g> हे तुमच्या <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> च्या वतीने परवानगीची विनंती करत आहे"</string>
-    <string name="title_nearby_device_streaming" msgid="179278282547719200">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ला तुमच्या फोनवरून ही कृती करण्याची अनुमती द्या"</string>
-    <string name="helper_title_nearby_device_streaming" msgid="6124438217620593669">"क्रॉस-डिव्हाइस सेवा"</string>
-    <string name="helper_summary_nearby_device_streaming" msgid="5538329403511524333">"<xliff:g id="APP_NAME">%1$s</xliff:g> हे जवळपासच्या डिव्हाइसवर आशय स्ट्रीम करण्यासाठी तुमच्या <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> च्या वतीने परवानगीची विनंती करत आहे"</string>
+    <string name="helper_summary_computer" msgid="8774832742608187072">"तुमच्या फोनमधील फोटो, मीडिया आणि सूचना ॲक्सेस करण्यासाठी <xliff:g id="APP_NAME">%1$s</xliff:g> हे तुमच्या <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> च्या वतीने परवानगीची विनंती करत आहे"</string>
+    <string name="title_nearby_device_streaming" msgid="7269956847378799794">"&lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; ला ही कृती करण्याची अनुमती द्यायची आहे का?"</string>
+    <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> हे जवळपासच्या डिव्हाइसवर अ‍ॅप्स आणि इतर सिस्टीम वैशिष्‍ट्ये स्ट्रीम करण्यासाठी तुमच्या <xliff:g id="DEVICE_NAME">%2$s</xliff:g> च्या वतीने परवानगीची विनंती करा"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"डिव्हाइस"</string>
-    <!-- no translation found for summary_generic_single_device (4735072202474939111) -->
-    <skip />
-    <!-- no translation found for summary_generic (4988130802522924650) -->
-    <skip />
+    <string name="summary_generic" msgid="1761976003668044801">"हे ॲप तुमचा फोन आणि निवडलेल्या डिव्‍हाइसदरम्यान कॉल करत असलेल्‍या एखाद्या व्यक्तीचे नाव यासारखी माहिती सिंक करू शकेल"</string>
     <string name="consent_yes" msgid="8344487259618762872">"अनुमती द्या"</string>
     <string name="consent_no" msgid="2640796915611404382">"अनुमती देऊ नका"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"रद्द करा"</string>
     <string name="consent_back" msgid="2560683030046918882">"मागे जा"</string>
+    <string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> चा विस्तार करा"</string>
+    <string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> कोलॅप्स करा"</string>
     <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"&lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; वरील अ‍ॅप्सना &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; प्रमाणेच परवानग्या द्यायच्या आहेत का?"</string>
     <string name="permission_sync_summary" msgid="765497944331294275">"यामध्ये पुढील गोष्टी समाविष्ट असू शकतात &lt;strong&gt;मायक्रोफोन&lt;/strong&gt;, &lt;strong&gt;कॅमेरा&lt;/strong&gt;, and &lt;strong&gt;स्थान अ‍ॅक्सेस&lt;/strong&gt;, आणि &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt; वरील इतर संवेदनशील परवानग्या. &lt;br/&gt;&lt;br/&gt;तुम्ही &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt; वर तुमच्या सेटिंग्ज मध्ये कोणत्याही वेळेला या परवानग्या बदलू शकता."</string>
-    <string name="vendor_icon_description" msgid="4445875290032225965">"अ‍ॅप आयकन"</string>
-    <string name="vendor_header_button_description" msgid="6566660389500630608">"अधिक माहिती बटण"</string>
+    <string name="vendor_header_button_description" msgid="7994879208461111473">"अधिक माहिती"</string>
     <string name="permission_phone" msgid="2661081078692784919">"फोन"</string>
     <string name="permission_sms" msgid="6337141296535774786">"एसएमएस"</string>
     <string name="permission_contacts" msgid="3858319347208004438">"Contacts"</string>
@@ -64,16 +57,18 @@
     <string name="permission_storage" msgid="6831099350839392343">"फोटो आणि मीडिया"</string>
     <string name="permission_notification" msgid="693762568127741203">"सूचना"</string>
     <string name="permission_app_streaming" msgid="6009695219091526422">"ॲप्स"</string>
-    <string name="permission_nearby_device_streaming" msgid="5868108148065023161">"जवळपासच्या डिव्हाइसवरील स्ट्रीमिंग"</string>
+    <string name="permission_nearby_device_streaming" msgid="1023325519477349499">"स्ट्रीमिंग"</string>
     <string name="permission_phone_summary" msgid="6684396967861278044">"फोन कॉल करू आणि व्यवस्थापित करू शकते"</string>
     <string name="permission_call_logs_summary" msgid="6186103394658755022">"फोन कॉल लॉग रीड अँड राइट करू शकते"</string>
     <string name="permission_sms_summary" msgid="3508442683678912017">"एसएमएस मेसेज पाठवू आणि पाहू शकते"</string>
     <string name="permission_contacts_summary" msgid="675861979475628708">"तुमचे संपर्क अ‍ॅक्सेस करू शकते"</string>
     <string name="permission_calendar_summary" msgid="6460000922511766226">"तुमचे कॅलेंडर अ‍ॅक्सेस करू शकते"</string>
-    <string name="permission_microphone_summary" msgid="4241354865859396558">"मायक्रोफोन वापरून ऑडिओ रेकॉर्ड करता येईल"</string>
+    <string name="permission_microphone_summary" msgid="3692091540613093394">"ऑडिओ रेकॉर्ड करू शकते"</string>
     <string name="permission_nearby_devices_summary" msgid="931940524460876655">"जवळील डिव्हाइस शोधू शकते, त्यांच्याशी कनेक्ट करू शकते आणि त्यांचे संबंधित स्थान निर्धारित करू शकते"</string>
     <string name="permission_notification_summary" msgid="884075314530071011">"संपर्क, मेसेज आणि फोटो यांसारख्या माहितीचा समावेश असलेल्या सर्व सूचना वाचू शकते"</string>
     <string name="permission_app_streaming_summary" msgid="606923325679670624">"तुमच्या फोनवरील ॲप्स स्ट्रीम करा"</string>
     <string name="permission_storage_summary" msgid="3918240895519506417"></string>
-    <string name="permission_nearby_device_streaming_summary" msgid="5776807830582725074">"जवळपासच्या डिव्हाइसवर आशय स्ट्रीम करा"</string>
+    <string name="permission_nearby_device_streaming_summary" msgid="8280824871197081246">"तुमच्या फोनवरून अ‍ॅप्स आणि इतर सिस्टीम वैशिष्‍ट्ये स्ट्रीम करा"</string>
+    <string name="device_type" product="default" msgid="8268703872070046263">"फोन"</string>
+    <string name="device_type" product="tablet" msgid="5038791954983067774">"टॅबलेट"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-ms/strings.xml b/packages/CompanionDeviceManager/res/values-ms/strings.xml
index 9fdedff..ef110b6 100644
--- a/packages/CompanionDeviceManager/res/values-ms/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ms/strings.xml
@@ -17,36 +17,36 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Pengurus Peranti Rakan"</string>
-    <string name="confirmation_title" msgid="8024993972587946678">"Benarkan &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; untuk mengakses &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"Benarkan apl &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; mengakses &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"jam tangan"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Pilih <xliff:g id="PROFILE_NAME">%1$s</xliff:g> untuk diurus oleh &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="6566922405914995759">"Apl ini diperlukan untuk mengurus <xliff:g id="DEVICE_NAME">%1$s</xliff:g> anda. <xliff:g id="APP_NAME">%2$s</xliff:g> akan dibenarkan untuk menyegerakkan maklumat seperti nama individu yang memanggil, berinteraksi dengan pemberitahuan anda dan mengakses kebenaran Telefon, SMS, Kenalan, Kalendar, Log panggilan dan Peranti berdekatan anda."</string>
-    <string name="summary_watch_single_device" msgid="7443464525873186735">"Apl ini diperlukan untuk mengurus <xliff:g id="DEVICE_NAME">%1$s</xliff:g> anda. <xliff:g id="APP_NAME">%2$s</xliff:g> akan dibenarkan untuk menyegerakkan maklumat seperti nama individu yang memanggil dan mengakses kebenaran ini:"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"cermin mata"</string>
-    <string name="summary_glasses" msgid="3808267780579061241">"Apl ini diperlukan untuk mengurus <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> akan dibenarkan untuk berinteraksi dengan pemberitahuan anda dan mengakses kebenaran Telefon, SMS, Kenalan, Mikrofon dan Peranti berdekatan anda."</string>
-    <string name="summary_glasses_single_device" msgid="7051392780285915640">"Apl ini diperlukan untuk mengurus <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> akan dibenarkan untuk berinteraksi dengan kebenaran ini:"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"Pilih peranti untuk diurus oleh &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Pilih <xliff:g id="PROFILE_NAME">%1$s</xliff:g> untuk disediakan"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Apl ini akan dibenarkan untuk menyegerakkan maklumat seperti nama individu yang membuat panggilan dan mengakses kebenaran ini pada <xliff:g id="DEVICE_NAME">%1$s</xliff:g> anda"</string>
+    <string name="confirmation_title_glasses" msgid="8288346850537727333">"Benarkan &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; mengurus &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"peranti"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Apl ini akan dibenarkan untuk mengakses kebenaran yang berikut pada <xliff:g id="DEVICE_NAME">%1$s</xliff:g> anda"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Benarkan &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; mengakses maklumat ini daripada telefon anda"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Perkhidmatan silang peranti"</string>
-    <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> sedang meminta kebenaran bagi pihak <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> anda untuk menstrim apl antara peranti anda"</string>
+    <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g> sedang meminta kebenaran bagi pihak <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> anda untuk menstrim apl antara peranti anda"</string>
     <string name="title_automotive_projection" msgid="3296005598978412847"></string>
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"Benarkan &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; untuk mengakses maklumat ini daripada telefon anda"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Perkhidmatan Google Play"</string>
-    <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> sedang meminta kebenaran bagi pihak <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> anda untuk mengakses foto, media dan pemberitahuan telefon anda"</string>
-    <string name="title_nearby_device_streaming" msgid="179278282547719200">"Benarkan &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; melakukan tindakan ini daripada telefon anda"</string>
-    <string name="helper_title_nearby_device_streaming" msgid="6124438217620593669">"Perkhidmatan silang peranti"</string>
-    <string name="helper_summary_nearby_device_streaming" msgid="5538329403511524333">"<xliff:g id="APP_NAME">%1$s</xliff:g> sedang meminta kebenaran bagi pihak <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> anda untuk menstrim kandungan kepada peranti berdekatan"</string>
+    <string name="helper_summary_computer" msgid="8774832742608187072">"<xliff:g id="APP_NAME">%1$s</xliff:g> sedang meminta kebenaran bagi pihak <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> anda untuk mengakses foto, media dan pemberitahuan telefon anda"</string>
+    <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Benarkan &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; mengambil tindakan ini?"</string>
+    <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> sedang meminta kebenaran bagi pihak <xliff:g id="DEVICE_NAME">%2$s</xliff:g> anda untuk menstrim apl dan ciri sistem yang lain pada peranti berdekatan"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"peranti"</string>
-    <string name="summary_generic_single_device" msgid="4735072202474939111">"Apl ini akan dapat menyegerakkan maklumat seperti nama individu yang memanggil, antara telefon anda dengan <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string>
-    <string name="summary_generic" msgid="4988130802522924650">"Apl ini akan dapat menyegerakkan maklumat seperti nama individu yang memanggil, antara telefon anda dengan peranti yang dipilih."</string>
+    <string name="summary_generic" msgid="1761976003668044801">"Apl ini akan dapat menyegerakkan maklumat seperti nama individu yang memanggil, antara telefon anda dengan peranti yang dipilih"</string>
     <string name="consent_yes" msgid="8344487259618762872">"Benarkan"</string>
     <string name="consent_no" msgid="2640796915611404382">"Jangan benarkan"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Batal"</string>
     <string name="consent_back" msgid="2560683030046918882">"Kembali"</string>
+    <string name="permission_expand" msgid="893185038020887411">"Kembangkan <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
+    <string name="permission_collapse" msgid="3320833884220844084">"Kuncupkan <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Beri apl pada &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; kebenaran yang sama seperti pada &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="permission_sync_summary" msgid="765497944331294275">"Ini mungkin termasuk &lt;strong&gt;Mikrofon&lt;/strong&gt;, &lt;strong&gt;Kamera&lt;/strong&gt; dan &lt;strong&gt;Akses lokasi&lt;/strong&gt; serta kebenaran sensitif lain pada &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;. &lt;br/&gt;&lt;br/&gt;Anda boleh menukar kebenaran ini pada bila-bila masa dalam Tetapan anda pada &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;."</string>
-    <string name="vendor_icon_description" msgid="4445875290032225965">"Ikon Apl"</string>
-    <string name="vendor_header_button_description" msgid="6566660389500630608">"Butang Maklumat Lagi"</string>
+    <string name="vendor_header_button_description" msgid="7994879208461111473">"Maklumat Lanjut"</string>
     <string name="permission_phone" msgid="2661081078692784919">"Telefon"</string>
     <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
     <string name="permission_contacts" msgid="3858319347208004438">"Kenalan"</string>
@@ -57,16 +57,18 @@
     <string name="permission_storage" msgid="6831099350839392343">"Foto dan media"</string>
     <string name="permission_notification" msgid="693762568127741203">"Pemberitahuan"</string>
     <string name="permission_app_streaming" msgid="6009695219091526422">"Apl"</string>
-    <string name="permission_nearby_device_streaming" msgid="5868108148065023161">"Penstriman Peranti Berdekatan"</string>
+    <string name="permission_nearby_device_streaming" msgid="1023325519477349499">"Penstriman"</string>
     <string name="permission_phone_summary" msgid="6684396967861278044">"Boleh membuat dan mengurus panggilan telefon"</string>
     <string name="permission_call_logs_summary" msgid="6186103394658755022">"Boleh membaca dan menulis log panggilan telefon"</string>
     <string name="permission_sms_summary" msgid="3508442683678912017">"Boleh menghantar dan melihat mesej SMS"</string>
     <string name="permission_contacts_summary" msgid="675861979475628708">"Boleh mengakses kenalan anda"</string>
     <string name="permission_calendar_summary" msgid="6460000922511766226">"Boleh mengakses kalendar anda"</string>
-    <string name="permission_microphone_summary" msgid="4241354865859396558">"Boleh merakam audio menggunakan mikrofon"</string>
+    <string name="permission_microphone_summary" msgid="3692091540613093394">"Boleh merakamkan audio"</string>
     <string name="permission_nearby_devices_summary" msgid="931940524460876655">"Boleh mencari, menyambung dan menentukan kedudukan relatif peranti berdekatan"</string>
     <string name="permission_notification_summary" msgid="884075314530071011">"Boleh membaca semua pemberitahuan, termasuk maklumat seperti kenalan, mesej dan foto"</string>
     <string name="permission_app_streaming_summary" msgid="606923325679670624">"Strim apl telefon anda"</string>
     <string name="permission_storage_summary" msgid="3918240895519506417"></string>
-    <string name="permission_nearby_device_streaming_summary" msgid="5776807830582725074">"Strim kandungan kepada peranti berdekatan"</string>
+    <string name="permission_nearby_device_streaming_summary" msgid="8280824871197081246">"Strim apl dan ciri sistem yang lain daripada telefon anda"</string>
+    <string name="device_type" product="default" msgid="8268703872070046263">"telefon"</string>
+    <string name="device_type" product="tablet" msgid="5038791954983067774">"tablet"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-my/strings.xml b/packages/CompanionDeviceManager/res/values-my/strings.xml
index 406e91c..57f2e23 100644
--- a/packages/CompanionDeviceManager/res/values-my/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-my/strings.xml
@@ -17,36 +17,36 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"တွဲဖက်ကိရိယာ မန်နေဂျာ"</string>
-    <string name="confirmation_title" msgid="8024993972587946678">"&lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; ကို သုံးရန် &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ကို ခွင့်ပြုပါ"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"&lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; ကို သုံးရန် &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; အက်ပ်ကို ခွင့်ပြုမလား။"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"နာရီ"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"&lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; က စီမံခန့်ခွဲရန် <xliff:g id="PROFILE_NAME">%1$s</xliff:g> ကို ရွေးချယ်ပါ"</string>
-    <string name="summary_watch" msgid="6566922405914995759">"သင်၏ <xliff:g id="DEVICE_NAME">%1$s</xliff:g> ကို စီမံခန့်ခွဲရန် ဤအက်ပ်လိုအပ်သည်။ ခေါ်ဆိုသူ၏အမည်ကဲ့သို့ အချက်အလက်ကို စင့်ခ်လုပ်ရန်၊ သင်၏ဖုန်း၊ SMS စာတိုစနစ်၊ အဆက်အသွယ်များ၊ ပြက္ခဒိန်၊ ခေါ်ဆိုမှတ်တမ်းနှင့် အနီးတစ်ဝိုက်ရှိ စက်များဆိုင်ရာ ခွင့်ပြုချက်များသုံးရန်၊ အကြောင်းကြားချက်များနှင့် ပြန်လှန်တုံ့ပြန်ရန် <xliff:g id="APP_NAME">%2$s</xliff:g> ကို ခွင့်ပြုမည်။"</string>
-    <string name="summary_watch_single_device" msgid="7443464525873186735">"သင်၏ <xliff:g id="DEVICE_NAME">%1$s</xliff:g> ကို စီမံခန့်ခွဲရန် ဤအက်ပ်လိုအပ်သည်။ ခေါ်ဆိုသူ၏အမည်ကဲ့သို့ အချက်အလက်ကို စင့်ခ်လုပ်ရန်နှင့် ဤခွင့်ပြုချက်များသုံးရန် <xliff:g id="APP_NAME">%2$s</xliff:g> ကို ခွင့်ပြုမည်-"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"မျက်မှန်"</string>
-    <string name="summary_glasses" msgid="3808267780579061241">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> ကို စီမံခန့်ခွဲရန် ဤအက်ပ်လိုအပ်သည်။ သင်၏ဖုန်း၊ SMS စာတိုစနစ်၊ အဆက်အသွယ်များ၊ မိုက်ခရိုဖုန်းနှင့် အနီးတစ်ဝိုက်ရှိ စက်များဆိုင်ရာ ခွင့်ပြုချက်များသုံးရန်၊ အကြောင်းကြားချက်များနှင့် ပြန်လှန်တုံ့ပြန်ရန် <xliff:g id="APP_NAME">%2$s</xliff:g> ကို ခွင့်ပြုမည်။"</string>
-    <string name="summary_glasses_single_device" msgid="7051392780285915640">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> ကို စီမံခန့်ခွဲရန် ဤအက်ပ်လိုအပ်သည်။ ဤခွင့်ပြုချက်များနှင့် ပြန်လှန်တုံ့ပြန်ရန် <xliff:g id="APP_NAME">%2$s</xliff:g> ကို ခွင့်ပြုမည်-"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; က စီမံခန့်ခွဲရန် စက်တစ်ခုကို ရွေးပါ"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"စနစ်ထည့်သွင်းရန် <xliff:g id="PROFILE_NAME">%1$s</xliff:g> ကို ရွေးပါ"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"ခေါ်ဆိုသူ၏အမည်ကဲ့သို့ အချက်အလက်ကို စင့်ခ်လုပ်ရန်နှင့် သင့် <xliff:g id="DEVICE_NAME">%1$s</xliff:g> တွင် ၎င်းခွင့်ပြုချက်များရယူရန် ဤအက်ပ်ကိုခွင့်ပြုမည်"</string>
+    <string name="confirmation_title_glasses" msgid="8288346850537727333">"&lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; ကို &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; အား စီမံခွင့်ပြုမလား။"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"စက်"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"သင့် <xliff:g id="DEVICE_NAME">%1$s</xliff:g> တွင် ၎င်းခွင့်ပြုချက်များရယူရန် ဤအက်ပ်ကိုခွင့်ပြုမည်"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ကို သင့်ဖုန်းမှ ဤအချက်အလက် သုံးခွင့်ပြုမည်"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"စက်များကြားသုံး ဝန်ဆောင်မှုများ"</string>
-    <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> သည် သင်၏စက်များအကြား အက်ပ်များတိုက်ရိုက်လွှင့်ရန် <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> ကိုယ်စား ခွင့်ပြုချက်တောင်းနေသည်"</string>
+    <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g> သည် သင်၏စက်များအကြား အက်ပ်များတိုက်ရိုက်လွှင့်ရန် <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> ကိုယ်စား ခွင့်ပြုချက်တောင်းနေသည်"</string>
     <string name="title_automotive_projection" msgid="3296005598978412847"></string>
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; အား သင့်ဖုန်းမှ ဤအချက်အလက် သုံးခွင့်ပြုခြင်း"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Google Play ဝန်ဆောင်မှုများ"</string>
-    <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> သည် သင့်ဖုန်း၏ ဓာတ်ပုံ၊ မီဒီယာနှင့် အကြောင်းကြားချက်များသုံးရန် <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> ကိုယ်စား ခွင့်ပြုချက်တောင်းနေသည်"</string>
-    <string name="title_nearby_device_streaming" msgid="179278282547719200">"သင့်ဖုန်းမှ ဤလုပ်ဆောင်ချက်ပြုလုပ်ရန် &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ကို ခွင့်ပြုနိုင်သည်"</string>
-    <string name="helper_title_nearby_device_streaming" msgid="6124438217620593669">"စက်များကြားသုံး ဝန်ဆောင်မှု"</string>
-    <string name="helper_summary_nearby_device_streaming" msgid="5538329403511524333">"<xliff:g id="APP_NAME">%1$s</xliff:g> သည် အနီးတစ်ဝိုက်ရှိ စက်များတွင် အကြောင်းအရာတိုက်ရိုက်ဖွင့်ရန် သင့် <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> ကိုယ်စား ခွင့်ပြုချက်တောင်းနေသည်"</string>
+    <string name="helper_summary_computer" msgid="8774832742608187072">"<xliff:g id="APP_NAME">%1$s</xliff:g> သည် သင့်ဖုန်း၏ ဓာတ်ပုံ၊ မီဒီယာနှင့် အကြောင်းကြားချက်များသုံးရန် <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> ကိုယ်စား ခွင့်ပြုချက်တောင်းနေသည်"</string>
+    <string name="title_nearby_device_streaming" msgid="7269956847378799794">"&lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; ကို ဤသို့လုပ်ဆောင်ခွင့်ပြုမလား။"</string>
+    <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> သည် အနီးတစ်ဝိုက်ရှိ အက်ပ်များနှင့် အခြားစနစ်အင်္ဂါရပ်များကို တိုက်ရိုက်ဖွင့်ရန် သင့် <xliff:g id="DEVICE_NAME">%2$s</xliff:g> ကိုယ်စား ခွင့်ပြုချက်တောင်းနေသည်"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"စက်"</string>
-    <string name="summary_generic_single_device" msgid="4735072202474939111">"ဤအက်ပ်သည် သင့်ဖုန်းနှင့် <xliff:g id="DEVICE_NAME">%1$s</xliff:g> အကြား ခေါ်ဆိုသူ၏အမည်ကဲ့သို့ အချက်အလက်ကို စင့်ခ်လုပ်နိုင်ပါမည်။"</string>
-    <string name="summary_generic" msgid="4988130802522924650">"ဤအက်ပ်သည် သင့်ဖုန်းနှင့် ရွေးထားသောစက်အကြား ခေါ်ဆိုသူ၏အမည်ကဲ့သို့ အချက်အလက်ကို စင့်ခ်လုပ်နိုင်ပါမည်။"</string>
+    <string name="summary_generic" msgid="1761976003668044801">"ဤအက်ပ်သည် သင့်ဖုန်းနှင့် ရွေးထားသောစက်အကြား ခေါ်ဆိုသူ၏အမည်ကဲ့သို့ အချက်အလက်ကို စင့်ခ်လုပ်နိုင်ပါမည်"</string>
     <string name="consent_yes" msgid="8344487259618762872">"ခွင့်ပြုရန်"</string>
     <string name="consent_no" msgid="2640796915611404382">"ခွင့်မပြုပါ"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"မလုပ်တော့"</string>
     <string name="consent_back" msgid="2560683030046918882">"နောက်သို့"</string>
+    <string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> ကို ပိုပြရန်"</string>
+    <string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> ကို လျှော့ပြရန်"</string>
     <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"အက်ပ်များကို &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; တွင်ပေးထားသည့် ခွင့်ပြုချက်များအတိုင်း &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; တွင် ပေးမလား။"</string>
     <string name="permission_sync_summary" msgid="765497944331294275">"&lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt; တွင် &lt;strong&gt;မိုက်ခရိုဖုန်း&lt;/strong&gt;၊ &lt;strong&gt;ကင်မရာ&lt;/strong&gt;၊ &lt;strong&gt;တည်နေရာသုံးခွင့်&lt;/strong&gt; နှင့် အခြားသတိထားရမည့် ခွင့်ပြုချက်များ ပါဝင်နိုင်သည်။ &lt;br/&gt;&lt;br/&gt;ဤခွင့်ပြုချက်များကို &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt; ရှိ ဆက်တင်များတွင် အချိန်မရွေး ပြောင်းနိုင်သည်။"</string>
-    <string name="vendor_icon_description" msgid="4445875290032225965">"အက်ပ်သင်္ကေတ"</string>
-    <string name="vendor_header_button_description" msgid="6566660389500630608">"နောက်ထပ်အချက်အလက်များ ခလုတ်"</string>
+    <string name="vendor_header_button_description" msgid="7994879208461111473">"နောက်ထပ်အချက်အလက်"</string>
     <string name="permission_phone" msgid="2661081078692784919">"ဖုန်း"</string>
     <string name="permission_sms" msgid="6337141296535774786">"SMS စာတိုစနစ်"</string>
     <string name="permission_contacts" msgid="3858319347208004438">"အဆက်အသွယ်များ"</string>
@@ -57,16 +57,18 @@
     <string name="permission_storage" msgid="6831099350839392343">"ဓာတ်ပုံနှင့် မီဒီယာများ"</string>
     <string name="permission_notification" msgid="693762568127741203">"အကြောင်းကြားချက်များ"</string>
     <string name="permission_app_streaming" msgid="6009695219091526422">"အက်ပ်များ"</string>
-    <string name="permission_nearby_device_streaming" msgid="5868108148065023161">"အနီးရှိစက်တိုက်ရိုက်ဖွင့်ခြင်း"</string>
+    <string name="permission_nearby_device_streaming" msgid="1023325519477349499">"တိုက်ရိုက်ဖွင့်ခြင်း"</string>
     <string name="permission_phone_summary" msgid="6684396967861278044">"ဖုန်းခေါ်ဆိုမှုများကို ပြုလုပ်ခြင်းနှင့် စီမံခြင်းတို့ လုပ်နိုင်သည်"</string>
     <string name="permission_call_logs_summary" msgid="6186103394658755022">"ဖုန်းခေါ်ဆိုမှတ်တမ်းကို ဖတ်ခြင်းနှင့် ရေးခြင်းတို့ လုပ်နိုင်သည်"</string>
     <string name="permission_sms_summary" msgid="3508442683678912017">"SMS မက်ဆေ့ဂျ်များကို ပို့ခြင်းနှင့် ကြည့်ရှုခြင်းတို့ လုပ်နိုင်သည်"</string>
     <string name="permission_contacts_summary" msgid="675861979475628708">"သင့်အဆက်အသွယ်များကို ဝင်ကြည့်နိုင်သည်"</string>
     <string name="permission_calendar_summary" msgid="6460000922511766226">"သင့်ပြက္ခဒိန်ကို သုံးနိုင်သည်"</string>
-    <string name="permission_microphone_summary" msgid="4241354865859396558">"မိုက်ခရိုဖုန်းသုံးပြီး အသံဖမ်းနိုင်သည်"</string>
+    <string name="permission_microphone_summary" msgid="3692091540613093394">"အသံဖမ်းနိုင်သည်"</string>
     <string name="permission_nearby_devices_summary" msgid="931940524460876655">"အနီးတစ်ဝိုက်ရှိ စက်များ၏ ဆက်စပ်နေရာကို ရှာခြင်း၊ ချိတ်ဆက်ခြင်းနှင့် သတ်မှတ်ခြင်းတို့ လုပ်နိုင်သည်"</string>
     <string name="permission_notification_summary" msgid="884075314530071011">"အဆက်အသွယ်၊ မက်ဆေ့ဂျ်နှင့် ဓာတ်ပုံကဲ့သို့ အချက်အလက်များအပါအဝင် အကြောင်းကြားချက်အားလုံးကို ဖတ်နိုင်သည်"</string>
     <string name="permission_app_streaming_summary" msgid="606923325679670624">"သင့်ဖုန်းရှိအက်ပ်များကို တိုက်ရိုက်ဖွင့်နိုင်သည်"</string>
     <string name="permission_storage_summary" msgid="3918240895519506417"></string>
-    <string name="permission_nearby_device_streaming_summary" msgid="5776807830582725074">"အနီးတစ်ဝိုက်ရှိစက်တွင် အကြောင်းအရာ တိုက်ရိုက်ဖွင့်နိုင်သည်"</string>
+    <string name="permission_nearby_device_streaming_summary" msgid="8280824871197081246">"သင့်ဖုန်းမှ အက်ပ်များနှင့် အခြားစနစ်အင်္ဂါရပ်များကို တိုက်ရိုက်ဖွင့်သည်"</string>
+    <string name="device_type" product="default" msgid="8268703872070046263">"ဖုန်း"</string>
+    <string name="device_type" product="tablet" msgid="5038791954983067774">"တက်ဘလက်"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-nb/strings.xml b/packages/CompanionDeviceManager/res/values-nb/strings.xml
index 8833591..083ec99 100644
--- a/packages/CompanionDeviceManager/res/values-nb/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-nb/strings.xml
@@ -17,43 +17,36 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Companion Device Manager"</string>
-    <!-- no translation found for confirmation_title (8024993972587946678) -->
-    <skip />
+    <string name="confirmation_title" msgid="2244241995958340998">"Vil du gi &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;-appen tilgang til &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"klokke"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Velg <xliff:g id="PROFILE_NAME">%1$s</xliff:g> som skal administreres av &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <!-- no translation found for summary_watch (6566922405914995759) -->
-    <skip />
-    <!-- no translation found for summary_watch_single_device (7443464525873186735) -->
-    <skip />
-    <string name="profile_name_glasses" msgid="8488394059007275998">"briller"</string>
-    <!-- no translation found for summary_glasses (3808267780579061241) -->
-    <skip />
-    <!-- no translation found for summary_glasses_single_device (7051392780285915640) -->
-    <skip />
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"Velg en enhet som skal administreres av &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Velg <xliff:g id="PROFILE_NAME">%1$s</xliff:g> som skal konfigureres"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Denne appen får tillatelse til å synkronisere informasjon som navnet til noen som ringer, og har disse tillatelsene på <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
+    <string name="confirmation_title_glasses" msgid="8288346850537727333">"Vil du la &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; administrere &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"enheten"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Denne appen får disse tillatelsene på <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Gi &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; tilgang til denne informasjonen fra telefonen din"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Tjenester på flere enheter"</string>
-    <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> ber om tillatelse til å strømme apper mellom enhetene dine, på vegne av <xliff:g id="DEVICE_TYPE">%2$s</xliff:g>"</string>
+    <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g> ber om tillatelse til å strømme apper mellom enhetene dine, på vegne av <xliff:g id="DISPLAY_NAME">%2$s</xliff:g>"</string>
     <string name="title_automotive_projection" msgid="3296005598978412847"></string>
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"Gi &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; tilgang til denne informasjonen fra telefonen din"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Google Play-tjenester"</string>
-    <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> ber om tillatelse til å få tilgang til bilder, medier og varsler på telefonen din, på vegne av <xliff:g id="DEVICE_TYPE">%2$s</xliff:g>"</string>
-    <string name="title_nearby_device_streaming" msgid="179278282547719200">"Gi &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; tillatelse til å utføre denne handlingen fra telefonen din"</string>
-    <string name="helper_title_nearby_device_streaming" msgid="6124438217620593669">"Tjenester på flere enheter"</string>
-    <string name="helper_summary_nearby_device_streaming" msgid="5538329403511524333">"<xliff:g id="APP_NAME">%1$s</xliff:g> ber om tillatelse på vegne av <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> til å strømme innhold til enheter i nærheten"</string>
+    <string name="helper_summary_computer" msgid="8774832742608187072">"<xliff:g id="APP_NAME">%1$s</xliff:g> ber om tillatelse til å få tilgang til bilder, medier og varsler på telefonen din, på vegne av <xliff:g id="DISPLAY_NAME">%2$s</xliff:g>"</string>
+    <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Vil du la &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; gjøre dette?"</string>
+    <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> ber om tillatelse på vegne av <xliff:g id="DEVICE_NAME">%2$s</xliff:g> til å strømme apper og andre systemfunksjoner til enheter i nærheten"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"enhet"</string>
-    <!-- no translation found for summary_generic_single_device (4735072202474939111) -->
-    <skip />
-    <!-- no translation found for summary_generic (4988130802522924650) -->
-    <skip />
+    <string name="summary_generic" msgid="1761976003668044801">"Denne appen kan synkronisere informasjon som navnet til noen som ringer, mellom telefonen og den valgte enheten"</string>
     <string name="consent_yes" msgid="8344487259618762872">"Tillat"</string>
     <string name="consent_no" msgid="2640796915611404382">"Ikke tillat"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Avbryt"</string>
     <string name="consent_back" msgid="2560683030046918882">"Tilbake"</string>
+    <string name="permission_expand" msgid="893185038020887411">"Vis <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
+    <string name="permission_collapse" msgid="3320833884220844084">"Skjul <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Vil du gi apper på &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; de samme tillatelsene som på &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="permission_sync_summary" msgid="765497944331294275">"Dette kan inkludere &lt;strong&gt;mikrofon&lt;/strong&gt;-, &lt;strong&gt;kamera&lt;/strong&gt;- og &lt;strong&gt;posisjonstilgang&lt;/strong&gt; samt andre sensitive tillatelser på &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;. &lt;br/&gt;&lt;br/&gt;Du kan når som helst endre disse tillatelsene i innstillingene på &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;."</string>
-    <string name="vendor_icon_description" msgid="4445875290032225965">"Appikon"</string>
-    <string name="vendor_header_button_description" msgid="6566660389500630608">"Mer informasjon-knapp"</string>
+    <string name="vendor_header_button_description" msgid="7994879208461111473">"Mer informasjon"</string>
     <string name="permission_phone" msgid="2661081078692784919">"Telefon"</string>
     <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
     <string name="permission_contacts" msgid="3858319347208004438">"Kontakter"</string>
@@ -64,16 +57,18 @@
     <string name="permission_storage" msgid="6831099350839392343">"Bilder og medier"</string>
     <string name="permission_notification" msgid="693762568127741203">"Varsler"</string>
     <string name="permission_app_streaming" msgid="6009695219091526422">"Apper"</string>
-    <string name="permission_nearby_device_streaming" msgid="5868108148065023161">"Strøm til enheter i nærheten"</string>
+    <string name="permission_nearby_device_streaming" msgid="1023325519477349499">"Strømming"</string>
     <string name="permission_phone_summary" msgid="6684396967861278044">"Kan ringe ut og administrere anrop"</string>
     <string name="permission_call_logs_summary" msgid="6186103394658755022">"Kan lese og skrive samtaleloggen"</string>
     <string name="permission_sms_summary" msgid="3508442683678912017">"Kan sende og lese SMS-meldinger"</string>
     <string name="permission_contacts_summary" msgid="675861979475628708">"Kan bruke kontaktene dine"</string>
     <string name="permission_calendar_summary" msgid="6460000922511766226">"Kan bruke kalenderen din"</string>
-    <string name="permission_microphone_summary" msgid="4241354865859396558">"Kan ta opp lyd med mikrofonen"</string>
+    <string name="permission_microphone_summary" msgid="3692091540613093394">"Kan ta opp lyd"</string>
     <string name="permission_nearby_devices_summary" msgid="931940524460876655">"Kan finne, koble til og fastslå den relative posisjonen til enheter i nærheten"</string>
     <string name="permission_notification_summary" msgid="884075314530071011">"Kan lese alle varsler, inkludert informasjon som kontakter, meldinger og bilder"</string>
     <string name="permission_app_streaming_summary" msgid="606923325679670624">"Strøm appene på telefonen"</string>
     <string name="permission_storage_summary" msgid="3918240895519506417"></string>
-    <string name="permission_nearby_device_streaming_summary" msgid="5776807830582725074">"Strøm innhold til en enhet i nærheten"</string>
+    <string name="permission_nearby_device_streaming_summary" msgid="8280824871197081246">"Strøm apper og andre systemfunksjoner fra telefonen"</string>
+    <string name="device_type" product="default" msgid="8268703872070046263">"telefon"</string>
+    <string name="device_type" product="tablet" msgid="5038791954983067774">"nettbrett"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-ne/strings.xml b/packages/CompanionDeviceManager/res/values-ne/strings.xml
index f494e79..7a1df5f 100644
--- a/packages/CompanionDeviceManager/res/values-ne/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ne/strings.xml
@@ -17,36 +17,36 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"सहयोगी डिभाइसको प्रबन्धक"</string>
-    <string name="confirmation_title" msgid="8024993972587946678">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; लाई &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; प्रयोग गर्ने अनुमति दिनुहोस्"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; एपलाई &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; प्रयोग गर्ने अनुमति दिने हो?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"घडी"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"आफूले &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; प्रयोग गरी व्यवस्थापन गर्न चाहेको <xliff:g id="PROFILE_NAME">%1$s</xliff:g> चयन गर्नुहोस्"</string>
-    <string name="summary_watch" msgid="6566922405914995759">"तपाईंको <xliff:g id="DEVICE_NAME">%1$s</xliff:g> व्यवस्थापन गर्न यो एप चाहिन्छ। <xliff:g id="APP_NAME">%2$s</xliff:g> लाई कल गर्ने व्यक्तिको नाम जस्ता जानकारी सिंक गर्ने, तपाईंका सूचना हेर्ने र फोन, SMS, कन्ट्याक्ट, पात्रो, कल लग तथा नजिकैका डिभाइससम्बन्धी अनुमतिहरू हेर्ने तथा प्रयोग गर्ने अनुमति दिइने छ।"</string>
-    <string name="summary_watch_single_device" msgid="7443464525873186735">"तपाईंको <xliff:g id="DEVICE_NAME">%1$s</xliff:g> व्यवस्थापन गर्न यो एप चाहिन्छ। <xliff:g id="APP_NAME">%2$s</xliff:g> लाई कल गर्ने व्यक्तिको नाम जस्ता जानकारी सिंक गर्ने र यी अनुमतिहरू हेर्ने तथा प्रयोग गर्ने अनुमति दिइने छ:"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"चस्मा"</string>
-    <string name="summary_glasses" msgid="3808267780579061241">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> व्यवस्थापन गर्न यो एप चाहिन्छ। <xliff:g id="APP_NAME">%2$s</xliff:g> लाई तपाईंका सूचना हेर्ने र फोन, SMS, कन्ट्याक्ट, माइक्रोफोन तथा नजिकैका डिभाइससम्बन्धी अनुमतिहरू हेर्ने तथा प्रयोग गर्ने अनुमति दिइने छ।"</string>
-    <string name="summary_glasses_single_device" msgid="7051392780285915640">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> व्यवस्थापन गर्न यो एप चाहिन्छ। <xliff:g id="APP_NAME">%2$s</xliff:g> लाई निम्न अनुमतिहरू फेरबदल गर्न दिइने छः"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"आफूले &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; प्रयोग गरी व्यवस्थापन गर्न चाहेको डिभाइस चयन गर्नुहोस्"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"सेट अप गर्नका लागि <xliff:g id="PROFILE_NAME">%1$s</xliff:g> छनौट गर्नुहोस्"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"तपाईंको <xliff:g id="DEVICE_NAME">%1$s</xliff:g> मा यो एपलाई कल गर्ने व्यक्तिको नाम जस्ता जानकारी सिंक गर्ने र यी कुराहरू गर्ने अनुमति दिइने छ"</string>
+    <string name="confirmation_title_glasses" msgid="8288346850537727333">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; लाई &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; व्यवस्थापन गर्ने अनुमति दिने हो?"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"डिभाइस"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"तपाईंको <xliff:g id="DEVICE_NAME">%1$s</xliff:g> मा यो एपलाई निम्न अनुमति दिइने छ:"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; लाई तपाईंको फोनमा भएको यो जानकारी हेर्ने तथा प्रयोग गर्ने अनुमति दिनुहोस्"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"क्रस-डिभाइस सेवाहरू"</string>
-    <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> तपाईंको डिभाइस <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> को तर्फबाट तपाईंका कुनै एउटा डिभाइसबाट अर्को डिभाइसमा एप स्ट्रिम गर्ने अनुमति माग्दै छ"</string>
+    <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g> तपाईंको डिभाइस <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> को तर्फबाट तपाईंका कुनै एउटा डिभाइसबाट अर्को डिभाइसमा एप स्ट्रिम गर्ने अनुमति माग्दै छ"</string>
     <string name="title_automotive_projection" msgid="3296005598978412847"></string>
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; लाई तपाईंको फोनमा भएको यो जानकारी हेर्ने तथा प्रयोग गर्ने अनुमति दिनुहोस्"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Google Play services"</string>
-    <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> तपाईंको डिभाइस <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> को तर्फबाट तपाईंको फोनमा भएका फोटो, मिडिया र सूचनाहरू हेर्ने तथा प्रयोग गर्ने अनुमति माग्दै छ"</string>
-    <string name="title_nearby_device_streaming" msgid="179278282547719200">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; लाई तपाईंको फोनबाट यो कार्य गर्ने अनुमति दिनुहोस्"</string>
-    <string name="helper_title_nearby_device_streaming" msgid="6124438217620593669">"क्रस-डिभाइस सेवाहरू"</string>
-    <string name="helper_summary_nearby_device_streaming" msgid="5538329403511524333">"<xliff:g id="APP_NAME">%1$s</xliff:g> तपाईंको <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> को तर्फबाट नजिकैका डिभाइसहरूमा सामग्री स्ट्रिम गर्ने अनुमति माग्दै छ"</string>
+    <string name="helper_summary_computer" msgid="8774832742608187072">"<xliff:g id="APP_NAME">%1$s</xliff:g> तपाईंको डिभाइस <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> को तर्फबाट तपाईंको फोनमा भएका फोटो, मिडिया र सूचनाहरू हेर्ने तथा प्रयोग गर्ने अनुमति माग्दै छ"</string>
+    <string name="title_nearby_device_streaming" msgid="7269956847378799794">"&lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; लाई यो कार्य गर्ने अनुमति दिने हो?"</string>
+    <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> तपाईंको डिभाइस <xliff:g id="DEVICE_NAME">%2$s</xliff:g> को तर्फबाट नजिकैका डिभाइसहरूमा एप र सिस्टमका अन्य सुविधाहरू स्ट्रिम गर्ने अनुमति माग्दै छ"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"यन्त्र"</string>
-    <string name="summary_generic_single_device" msgid="4735072202474939111">"यो एपले तपाईंको फोन र तपाईंले छनौट गर्ने <xliff:g id="DEVICE_NAME">%1$s</xliff:g> का बिचमा कल गर्ने व्यक्तिको नाम जस्ता जानकारी सिंक गर्न सक्ने छ।"</string>
-    <string name="summary_generic" msgid="4988130802522924650">"यो एपले तपाईंको फोन र तपाईंले छनौट गर्ने डिभाइसका बिचमा कल गर्ने व्यक्तिको नाम जस्ता जानकारी सिंक गर्न सक्ने छ।"</string>
+    <string name="summary_generic" msgid="1761976003668044801">"यो एपले तपाईंको फोन र तपाईंले छनौट गर्ने डिभाइसका बिचमा कल गर्ने व्यक्तिको नाम जस्ता जानकारी सिंक गर्न सक्ने छ।"</string>
     <string name="consent_yes" msgid="8344487259618762872">"अनुमति दिनुहोस्"</string>
     <string name="consent_no" msgid="2640796915611404382">"अनुमति नदिनुहोस्"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"रद्द गर्नुहोस्"</string>
     <string name="consent_back" msgid="2560683030046918882">"पछाडि"</string>
+    <string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> एक्स्पान्ड गर्नुहोस्"</string>
+    <string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> कोल्याप्स गर्नुहोस्"</string>
     <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"&lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; मा भएका एपहरूलाई पनि &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; मा दिइएकै अनुमति दिने हो?"</string>
     <string name="permission_sync_summary" msgid="765497944331294275">"यसअन्तर्गत &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt; का &lt;strong&gt;माइक्रोफोन&lt;/strong&gt;, &lt;strong&gt;क्यामेरा&lt;/strong&gt; र &lt;strong&gt;लोकेसन प्रयोग गर्ने अनुमति&lt;/strong&gt; तथा अन्य संवेदनशील अनुमतिहरू समावेश हुन्छन्। &lt;br/&gt;&lt;br/&gt;तपाईं जुनसुकै बेला &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt; का सेटिङमा गई यी अनुमति परिवर्तन गर्न सक्नुहुन्छ।"</string>
-    <string name="vendor_icon_description" msgid="4445875290032225965">"एपको आइकन"</string>
-    <string name="vendor_header_button_description" msgid="6566660389500630608">"थप जानकारी देखाउने बटन"</string>
+    <string name="vendor_header_button_description" msgid="7994879208461111473">"थप जानकारी"</string>
     <string name="permission_phone" msgid="2661081078692784919">"फोन"</string>
     <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
     <string name="permission_contacts" msgid="3858319347208004438">"Contacts"</string>
@@ -57,16 +57,18 @@
     <string name="permission_storage" msgid="6831099350839392343">"फोटो र मिडिया"</string>
     <string name="permission_notification" msgid="693762568127741203">"सूचनाहरू"</string>
     <string name="permission_app_streaming" msgid="6009695219091526422">"एपहरू"</string>
-    <string name="permission_nearby_device_streaming" msgid="5868108148065023161">"नजिकैको डिभाइसमा स्ट्रिम गरिँदै छ"</string>
+    <string name="permission_nearby_device_streaming" msgid="1023325519477349499">"स्ट्रिमिङ"</string>
     <string name="permission_phone_summary" msgid="6684396967861278044">"फोन कल गर्न र कलहरू व्यवस्थापन गर्न सक्छ"</string>
     <string name="permission_call_logs_summary" msgid="6186103394658755022">"फोनको कल लग रिड र राइट गर्न सक्छ"</string>
     <string name="permission_sms_summary" msgid="3508442683678912017">"SMS म्यासेजहरू पठाउन र हेर्न सक्छ"</string>
     <string name="permission_contacts_summary" msgid="675861979475628708">"तपाईंका कन्ट्याक्टहरू हेर्न सक्छ"</string>
     <string name="permission_calendar_summary" msgid="6460000922511766226">"तपाईंको पात्रो हेर्न सक्छ"</string>
-    <string name="permission_microphone_summary" msgid="4241354865859396558">"यसका सहायताले माइक्रोफोन प्रयोग गरी अडियो रेकर्ड गर्न सकिन्छ"</string>
+    <string name="permission_microphone_summary" msgid="3692091540613093394">"अडियो रेकर्ड गर्न सक्छ"</string>
     <string name="permission_nearby_devices_summary" msgid="931940524460876655">"नजिकैका डिभाइसहरू भेट्टाउन, ती डिभाइससँग कनेक्ट गर्न र तिनको सापेक्ष स्थिति निर्धारण गर्न सक्छ"</string>
     <string name="permission_notification_summary" msgid="884075314530071011">"कन्ट्याक्ट, म्यासेज र फोटोलगायतका जानकारीसहित सबै सूचनाहरू पढ्न सक्छ"</string>
     <string name="permission_app_streaming_summary" msgid="606923325679670624">"आफ्नो फोनका एपहरू प्रयोग गर्नुहोस्"</string>
     <string name="permission_storage_summary" msgid="3918240895519506417"></string>
-    <string name="permission_nearby_device_streaming_summary" msgid="5776807830582725074">"नजिकैको डिभाइसमा सामग्री स्ट्रिम गर्नुहोस्"</string>
+    <string name="permission_nearby_device_streaming_summary" msgid="8280824871197081246">"आफ्नो फोनबाट एप र सिस्टमका अन्य सुविधाहरू स्ट्रिम गर्नुहोस्"</string>
+    <string name="device_type" product="default" msgid="8268703872070046263">"फोन"</string>
+    <string name="device_type" product="tablet" msgid="5038791954983067774">"ट्याब्लेट"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-nl/strings.xml b/packages/CompanionDeviceManager/res/values-nl/strings.xml
index bec7e405..71d14e7 100644
--- a/packages/CompanionDeviceManager/res/values-nl/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-nl/strings.xml
@@ -17,36 +17,36 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Companion Device Manager"</string>
-    <string name="confirmation_title" msgid="8024993972587946678">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; toegang geven tot &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"De app &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; toegang geven tot &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"smartwatch"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Een <xliff:g id="PROFILE_NAME">%1$s</xliff:g> kiezen om te beheren met &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="6566922405914995759">"De app is nodig om je <xliff:g id="DEVICE_NAME">%1$s</xliff:g> te beheren. <xliff:g id="APP_NAME">%2$s</xliff:g> mag informatie (zoals de naam van iemand die belt) synchroniseren, interactie hebben met je meldingen en krijgt toegang tot de rechten Telefoon, Sms, Contacten, Agenda, Gesprekslijsten en Apparaten in de buurt."</string>
-    <string name="summary_watch_single_device" msgid="7443464525873186735">"De app is nodig om je <xliff:g id="DEVICE_NAME">%1$s</xliff:g> te beheren. <xliff:g id="APP_NAME">%2$s</xliff:g> mag informatie (zoals de naam van iemand die belt) synchroniseren en krijgt toegang tot deze rechten:"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"brillen"</string>
-    <string name="summary_glasses" msgid="3808267780579061241">"Deze app is nodig om <xliff:g id="DEVICE_NAME">%1$s</xliff:g> te beheren. <xliff:g id="APP_NAME">%2$s</xliff:g> mag interactie hebben met je meldingen en krijgt toegang tot de rechten Telefoon, Sms, Contacten, Microfoon en Apparaten in de buurt."</string>
-    <string name="summary_glasses_single_device" msgid="7051392780285915640">"De app is nodig om <xliff:g id="DEVICE_NAME">%1$s</xliff:g> te beheren. <xliff:g id="APP_NAME">%2$s</xliff:g> mag interactie hebben met deze rechten:"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"Een apparaat kiezen om te beheren met &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Een <xliff:g id="PROFILE_NAME">%1$s</xliff:g> kiezen om in te stellen"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Deze app kan informatie synchroniseren (zoals de naam van iemand die belt) en krijgt toegang tot deze rechten op je <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
+    <string name="confirmation_title_glasses" msgid="8288346850537727333">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; toestaan &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; te beheren?"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"apparaat"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Deze app krijgt toegang tot deze rechten op je <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; toegang geven tot deze informatie op je telefoon"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Cross-device-services"</string>
-    <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> vraagt namens jouw <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> toestemming om apps te streamen tussen je apparaten"</string>
+    <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g> vraagt namens jouw <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> toestemming om apps te streamen tussen je apparaten"</string>
     <string name="title_automotive_projection" msgid="3296005598978412847"></string>
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; toegang geven tot deze informatie op je telefoon"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Google Play-services"</string>
-    <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> vraagt namens jouw <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> toegang tot de foto\'s, media en meldingen van je telefoon"</string>
-    <string name="title_nearby_device_streaming" msgid="179278282547719200">"Sta &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; toe om deze actie uit te voeren via je telefoon"</string>
-    <string name="helper_title_nearby_device_streaming" msgid="6124438217620593669">"Cross-device-services"</string>
-    <string name="helper_summary_nearby_device_streaming" msgid="5538329403511524333">"<xliff:g id="APP_NAME">%1$s</xliff:g> vraagt namens je <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> toestemming om content te streamen naar apparaten in de buurt"</string>
+    <string name="helper_summary_computer" msgid="8774832742608187072">"<xliff:g id="APP_NAME">%1$s</xliff:g> vraagt namens jouw <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> toegang tot de foto\'s, media en meldingen van je telefoon"</string>
+    <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Toestaan dat &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; deze actie uitvoert?"</string>
+    <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> vraagt namens je <xliff:g id="DEVICE_NAME">%2$s</xliff:g> toestemming om apps en andere systeemfuncties naar apparaten in de buurt te streamen"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"apparaat"</string>
-    <string name="summary_generic_single_device" msgid="4735072202474939111">"Deze app kan informatie, zoals de naam van iemand die belt, synchroniseren tussen je telefoon en <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string>
-    <string name="summary_generic" msgid="4988130802522924650">"Deze app kan informatie (zoals de naam van iemand die belt) synchroniseren tussen je telefoon en het gekozen apparaat."</string>
+    <string name="summary_generic" msgid="1761976003668044801">"Deze app kan informatie, zoals de naam van iemand die belt, synchroniseren tussen je telefoon en het gekozen apparaat"</string>
     <string name="consent_yes" msgid="8344487259618762872">"Toestaan"</string>
     <string name="consent_no" msgid="2640796915611404382">"Niet toestaan"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Annuleren"</string>
     <string name="consent_back" msgid="2560683030046918882">"Terug"</string>
+    <string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> uitvouwen"</string>
+    <string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> samenvouwen"</string>
     <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Apps op de &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; dezelfde rechten geven als op de &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="permission_sync_summary" msgid="765497944331294275">"Dit kan &lt;strong&gt;Microfoon&lt;/strong&gt;, &lt;strong&gt;Camera&lt;/strong&gt; en &lt;strong&gt;Locatietoegang&lt;/strong&gt; en andere gevoelige rechten op de &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt; omvatten. &lt;br/&gt;&lt;br/&gt;Je kunt deze rechten altijd wijzigen in je Instellingen op de &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;."</string>
-    <string name="vendor_icon_description" msgid="4445875290032225965">"App-icoon"</string>
-    <string name="vendor_header_button_description" msgid="6566660389500630608">"Knop Meer informatie"</string>
+    <string name="vendor_header_button_description" msgid="7994879208461111473">"Meer informatie"</string>
     <string name="permission_phone" msgid="2661081078692784919">"Telefoon"</string>
     <string name="permission_sms" msgid="6337141296535774786">"Sms"</string>
     <string name="permission_contacts" msgid="3858319347208004438">"Contacten"</string>
@@ -57,16 +57,18 @@
     <string name="permission_storage" msgid="6831099350839392343">"Foto\'s en media"</string>
     <string name="permission_notification" msgid="693762568127741203">"Meldingen"</string>
     <string name="permission_app_streaming" msgid="6009695219091526422">"Apps"</string>
-    <string name="permission_nearby_device_streaming" msgid="5868108148065023161">"Streaming op apparaten in de buurt"</string>
+    <string name="permission_nearby_device_streaming" msgid="1023325519477349499">"Streaming"</string>
     <string name="permission_phone_summary" msgid="6684396967861278044">"Kan telefoongesprekken starten en beheren"</string>
     <string name="permission_call_logs_summary" msgid="6186103394658755022">"Kan gesprekslijst lezen en ernaar schrijven"</string>
     <string name="permission_sms_summary" msgid="3508442683678912017">"Kan sms-berichten sturen en bekijken"</string>
     <string name="permission_contacts_summary" msgid="675861979475628708">"Heeft toegang tot je contacten"</string>
     <string name="permission_calendar_summary" msgid="6460000922511766226">"Heeft toegang tot je agenda"</string>
-    <string name="permission_microphone_summary" msgid="4241354865859396558">"Kan audio opnemen met de microfoon"</string>
+    <string name="permission_microphone_summary" msgid="3692091540613093394">"Kan audio opnemen"</string>
     <string name="permission_nearby_devices_summary" msgid="931940524460876655">"Kan apparaten in de buurt vinden, er verbinding mee maken en de relatieve positie ervan bepalen"</string>
     <string name="permission_notification_summary" msgid="884075314530071011">"Kan alle meldingen lezen, waaronder informatie zoals contacten, berichten en foto\'s"</string>
     <string name="permission_app_streaming_summary" msgid="606923325679670624">"Stream de apps van je telefoon"</string>
     <string name="permission_storage_summary" msgid="3918240895519506417"></string>
-    <string name="permission_nearby_device_streaming_summary" msgid="5776807830582725074">"Stream content naar een apparaat in de buurt"</string>
+    <string name="permission_nearby_device_streaming_summary" msgid="8280824871197081246">"Apps en andere systeemfuncties streamen vanaf je telefoon"</string>
+    <string name="device_type" product="default" msgid="8268703872070046263">"telefoon"</string>
+    <string name="device_type" product="tablet" msgid="5038791954983067774">"tablet"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-or/strings.xml b/packages/CompanionDeviceManager/res/values-or/strings.xml
index b9794c4..a058374 100644
--- a/packages/CompanionDeviceManager/res/values-or/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-or/strings.xml
@@ -17,43 +17,36 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"ସହଯୋଗୀ ଡିଭାଇସ୍ ପରିଚାଳକ"</string>
-    <!-- no translation found for confirmation_title (8024993972587946678) -->
-    <skip />
+    <string name="confirmation_title" msgid="2244241995958340998">"&lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;କୁ ଆକ୍ସେସ କରିବା ପାଇଁ &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ଆପକୁ ଅନୁମତି ଦେବେ?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"ୱାଚ୍"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"&lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; ଦ୍ୱାରା ପରିଚାଳିତ ହେବା ପାଇଁ ଏକ <xliff:g id="PROFILE_NAME">%1$s</xliff:g>କୁ ବାଛନ୍ତୁ"</string>
-    <!-- no translation found for summary_watch (6566922405914995759) -->
-    <skip />
-    <!-- no translation found for summary_watch_single_device (7443464525873186735) -->
-    <skip />
-    <string name="profile_name_glasses" msgid="8488394059007275998">"ଚଷମା"</string>
-    <!-- no translation found for summary_glasses (3808267780579061241) -->
-    <skip />
-    <!-- no translation found for summary_glasses_single_device (7051392780285915640) -->
-    <skip />
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ଦ୍ୱାରା ପରିଚାଳିତ ହେବା ପାଇଁ ଏକ ଡିଭାଇସ ବାଛନ୍ତୁ"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"ସେଟ ଅପ କରିବାକୁ ଏକ <xliff:g id="PROFILE_NAME">%1$s</xliff:g> ବାଛନ୍ତୁ"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"କଲ କରୁଥିବା ଯେ କୌଣସି ବ୍ୟକ୍ତିଙ୍କ ନାମ ପରି ସୂଚନା ସିଙ୍କ କରିବାକୁ ଏବଂ ଆପଣଙ୍କ <xliff:g id="DEVICE_NAME">%1$s</xliff:g>ରେ ଏହି ଅନୁମତିଗୁଡ଼ିକୁ ଆକ୍ସେସ କରିବା ପାଇଁ ଏହି ଆପକୁ ଅନୁମତି ଦିଆଯିବ"</string>
+    <string name="confirmation_title_glasses" msgid="8288346850537727333">"&lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;କୁ ପରିଚାଳନା କରିବା ପାଇଁ &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;କୁ ଅନୁମତି ଦେବେ?"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"ଡିଭାଇସ"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"ଆପଣଙ୍କ <xliff:g id="DEVICE_NAME">%1$s</xliff:g>ରେ ଏହି ଅନୁମତିଗୁଡ଼ିକୁ ଆକ୍ସେସ କରିବା ପାଇଁ ଏହି ଆପକୁ ଅନୁମତି ଦିଆଯିବ"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"ଆପଣଙ୍କ ଫୋନରୁ ଏହି ସୂଚନାକୁ ଆକ୍ସେସ କରିବା ପାଇଁ &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;କୁ ଅନୁମତି ଦିଅନ୍ତୁ"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"କ୍ରସ-ଡିଭାଇସ ସେବାଗୁଡ଼ିକ"</string>
-    <string name="helper_summary_app_streaming" msgid="5977509499890099">"ଆପଣଙ୍କ ଡିଭାଇସଗୁଡ଼ିକ ମଧ୍ୟରେ ଆପ୍ସକୁ ଷ୍ଟ୍ରିମ କରିବା ପାଇଁ <xliff:g id="APP_NAME">%1$s</xliff:g> ଆପଣଙ୍କର <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> ତରଫରୁ ଅନୁମତି ପାଇଁ ଅନୁରୋଧ କରୁଛି"</string>
+    <string name="helper_summary_app_streaming" msgid="2396773196949578425">"ଆପଣଙ୍କ ଡିଭାଇସଗୁଡ଼ିକ ମଧ୍ୟରେ ଆପ୍ସକୁ ଷ୍ଟ୍ରିମ କରିବା ପାଇଁ <xliff:g id="APP_NAME">%1$s</xliff:g> ଆପଣଙ୍କର <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> ତରଫରୁ ଅନୁମତି ପାଇଁ ଅନୁରୋଧ କରୁଛି"</string>
     <string name="title_automotive_projection" msgid="3296005598978412847"></string>
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"ଆପଣଙ୍କ ଫୋନରୁ ଏହି ସୂଚନାକୁ ଆକ୍ସେସ କରିବା ପାଇଁ &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;କୁ ଅନୁମତି ଦିଅନ୍ତୁ"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Google Play ସେବାଗୁଡ଼ିକ"</string>
-    <string name="helper_summary_computer" msgid="9050724687678157852">"ଆପଣଙ୍କ ଫୋନର ଫଟୋ, ମିଡିଆ ଏବଂ ବିଜ୍ଞପ୍ତିଗୁଡ଼ିକୁ ଆକ୍ସେସ କରିବା ପାଇଁ <xliff:g id="APP_NAME">%1$s</xliff:g> ଆପଣଙ୍କର <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> ତରଫରୁ ଅନୁମତି ପାଇଁ ଅନୁରୋଧ କରୁଛି"</string>
-    <string name="title_nearby_device_streaming" msgid="179278282547719200">"ଆପଣଙ୍କ ଫୋନରେ ଏହି କାର୍ଯ୍ୟ କରିବା ପାଇଁ &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;କୁ ଅନୁମତି ଦିଅନ୍ତୁ"</string>
-    <string name="helper_title_nearby_device_streaming" msgid="6124438217620593669">"କ୍ରସ-ଡିଭାଇସ ସେବାଗୁଡ଼ିକ"</string>
-    <string name="helper_summary_nearby_device_streaming" msgid="5538329403511524333">"ଆଖପାଖର ଡିଭାଇସଗୁଡ଼ିକରେ ବିଷୟବସ୍ତୁକୁ ଷ୍ଟ୍ରିମ କରିବା ପାଇଁ <xliff:g id="APP_NAME">%1$s</xliff:g> ଆପଣଙ୍କର <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> ତରଫରୁ ଅନୁମତି ପାଇଁ ଅନୁରୋଧ କରୁଛି"</string>
+    <string name="helper_summary_computer" msgid="8774832742608187072">"ଆପଣଙ୍କ ଫୋନର ଫଟୋ, ମିଡିଆ ଏବଂ ବିଜ୍ଞପ୍ତିଗୁଡ଼ିକୁ ଆକ୍ସେସ କରିବା ପାଇଁ <xliff:g id="APP_NAME">%1$s</xliff:g> ଆପଣଙ୍କର <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> ତରଫରୁ ଅନୁମତି ପାଇଁ ଅନୁରୋଧ କରୁଛି"</string>
+    <string name="title_nearby_device_streaming" msgid="7269956847378799794">"ଏହି ପଦକ୍ଷେପ ନେବା ପାଇଁ &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt;କୁ ଅନୁମତି ଦେବେ?"</string>
+    <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"ଆଖପାଖର ଡିଭାଇସଗୁଡ଼ିକରେ ଆପ୍ସ ଏବଂ ଅନ୍ୟ ସିଷ୍ଟମ ଫିଚରଗୁଡ଼ିକୁ ଷ୍ଟ୍ରିମ କରିବା ପାଇଁ <xliff:g id="APP_NAME">%1$s</xliff:g> ଆପଣଙ୍କ <xliff:g id="DEVICE_NAME">%2$s</xliff:g> ତରଫରୁ ଅନୁମତି ପାଇଁ ଅନୁରୋଧ କରୁଛି"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"ଡିଭାଇସ୍"</string>
-    <!-- no translation found for summary_generic_single_device (4735072202474939111) -->
-    <skip />
-    <!-- no translation found for summary_generic (4988130802522924650) -->
-    <skip />
+    <string name="summary_generic" msgid="1761976003668044801">"ଆପଣଙ୍କ ଫୋନ ଏବଂ ବଛାଯାଇଥିବା ଡିଭାଇସ ମଧ୍ୟରେ, କଲ କରୁଥିବା ଯେ କୌଣସି ବ୍ୟକ୍ତିଙ୍କ ନାମ ପରି ସୂଚନା ସିଙ୍କ କରିବାକୁ ଏହି ଆପ ସକ୍ଷମ ହେବ"</string>
     <string name="consent_yes" msgid="8344487259618762872">"ଅନୁମତି ଦିଅନ୍ତୁ"</string>
     <string name="consent_no" msgid="2640796915611404382">"ଅନୁମତି ଦିଅନ୍ତୁ ନାହିଁ"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"ବାତିଲ କରନ୍ତୁ"</string>
     <string name="consent_back" msgid="2560683030046918882">"ପଛକୁ ଫେରନ୍ତୁ"</string>
+    <string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>କୁ ବିସ୍ତାର କରନ୍ତୁ"</string>
+    <string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>କୁ ସଙ୍କୁଚିତ କରନ୍ତୁ"</string>
     <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"&lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;ପରି &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt;ରେ ଥିବା ଆପ୍ସକୁ ସମାନ ଅନୁମତିଗୁଡ଼ିକ ଦେବେ?"</string>
     <string name="permission_sync_summary" msgid="765497944331294275">"ଏହା &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;ରେ &lt;strong&gt;ମାଇକ୍ରୋଫୋନ&lt;/strong&gt;, &lt;strong&gt;କେମେରା&lt;/strong&gt;, ଏବଂ &lt;strong&gt;ଲୋକେସନ ଆକ୍ସେସ&lt;/strong&gt; ଏବଂ ଅନ୍ୟ ସମ୍ବେଦନଶୀଳ ଅନୁମତିଗୁଡ଼ିକୁ ଅନ୍ତର୍ଭୁକ୍ତ କରିପାରେ। &lt;br/&gt;&lt;br/&gt;ଆପଣ &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;ରେ ଯେ କୌଣସି ସମୟରେ ଆପଣଙ୍କ ସେଟିଂସରେ ଏହି ଅନୁମତିଗୁଡ଼ିକୁ ପରିବର୍ତ୍ତନ କରିପାରିବେ।"</string>
-    <string name="vendor_icon_description" msgid="4445875290032225965">"ଆପ ଆଇକନ"</string>
-    <string name="vendor_header_button_description" msgid="6566660389500630608">"ଅଧିକ ସୂଚନା ବଟନ"</string>
+    <string name="vendor_header_button_description" msgid="7994879208461111473">"ଅଧିକ ସୂଚନା"</string>
     <string name="permission_phone" msgid="2661081078692784919">"ଫୋନ"</string>
     <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
     <string name="permission_contacts" msgid="3858319347208004438">"କଣ୍ଟାକ୍ଟଗୁଡ଼ିକ"</string>
@@ -64,16 +57,18 @@
     <string name="permission_storage" msgid="6831099350839392343">"ଫଟୋ ଏବଂ ମିଡିଆ"</string>
     <string name="permission_notification" msgid="693762568127741203">"ବିଜ୍ଞପ୍ତିଗୁଡ଼ିକ"</string>
     <string name="permission_app_streaming" msgid="6009695219091526422">"ଆପ୍ସ"</string>
-    <string name="permission_nearby_device_streaming" msgid="5868108148065023161">"ଆଖପାଖର ଡିଭାଇସରେ ଷ୍ଟ୍ରିମିଂ"</string>
+    <string name="permission_nearby_device_streaming" msgid="1023325519477349499">"ଷ୍ଟ୍ରିମିଂ"</string>
     <string name="permission_phone_summary" msgid="6684396967861278044">"ଫୋନ କଲଗୁଡ଼ିକ କରିପାରିବ ଏବଂ ସେଗୁଡ଼ିକୁ ପରିଚାଳନା କରିପାରିବ"</string>
     <string name="permission_call_logs_summary" msgid="6186103394658755022">"ଫୋନ କଲ ଲଗକୁ ପଢ଼ିପାରିବ ଏବଂ ଲେଖିପାରିବ"</string>
     <string name="permission_sms_summary" msgid="3508442683678912017">"SMS ମେସେଜଗୁଡ଼ିକ ପଠାଇପାରିବ ଏବଂ ଦେଖିପାରିବ"</string>
     <string name="permission_contacts_summary" msgid="675861979475628708">"ଆପଣଙ୍କ କଣ୍ଟାକ୍ଟଗୁଡ଼ିକୁ ଆକ୍ସେସ କରିପାରିବ"</string>
     <string name="permission_calendar_summary" msgid="6460000922511766226">"ଆପଣଙ୍କ କେଲେଣ୍ଡରକୁ ଆକ୍ସେସ କରିପାରିବ"</string>
-    <string name="permission_microphone_summary" msgid="4241354865859396558">"ମାଇକ୍ରୋଫୋନକୁ ବ୍ୟବହାର କରି ଅଡିଓ ରେକର୍ଡ କରାଯାଇପାରିବ"</string>
+    <string name="permission_microphone_summary" msgid="3692091540613093394">"ଅଡିଓ ରେକର୍ଡ କରିପାରିବ"</string>
     <string name="permission_nearby_devices_summary" msgid="931940524460876655">"ଆଖପାଖର ଡିଭାଇସଗୁଡ଼ିକୁ ଖୋଜିପାରିବ, କନେକ୍ଟ କରିପାରିବ ଏବଂ ସେଗୁଡ଼ିକର ଆପେକ୍ଷିକ ଅବସ୍ଥିତିକୁ ନିର୍ଦ୍ଧାରଣ କରିପାରିବ"</string>
     <string name="permission_notification_summary" msgid="884075314530071011">"ଯୋଗାଯୋଗ, ମେସେଜ ଏବଂ ଫଟୋଗୁଡ଼ିକ ପରି ସୂଚନା ସମେତ ସମସ୍ତ ବିଜ୍ଞପ୍ତିକୁ ପଢ଼ିପାରିବ"</string>
     <string name="permission_app_streaming_summary" msgid="606923325679670624">"ଆପଣଙ୍କ ଫୋନର ଆପ୍ସକୁ ଷ୍ଟ୍ରିମ କରନ୍ତୁ"</string>
     <string name="permission_storage_summary" msgid="3918240895519506417"></string>
-    <string name="permission_nearby_device_streaming_summary" msgid="5776807830582725074">"ଆଖପାଖର ଏକ ଡିଭାଇସରେ ବିଷୟବସ୍ତୁକୁ ଷ୍ଟ୍ରିମ କରନ୍ତୁ"</string>
+    <string name="permission_nearby_device_streaming_summary" msgid="8280824871197081246">"ଆପଣଙ୍କ ଫୋନରୁ ଆପ୍ସ ଏବଂ ଅନ୍ୟ ସିଷ୍ଟମ ଫିଚରଗୁଡ଼ିକୁ ଷ୍ଟ୍ରିମ କରନ୍ତୁ"</string>
+    <string name="device_type" product="default" msgid="8268703872070046263">"ଫୋନ"</string>
+    <string name="device_type" product="tablet" msgid="5038791954983067774">"ଟାବଲେଟ"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-pa/strings.xml b/packages/CompanionDeviceManager/res/values-pa/strings.xml
index f933c9a..984ec48 100644
--- a/packages/CompanionDeviceManager/res/values-pa/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-pa/strings.xml
@@ -17,43 +17,36 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"ਸੰਬੰਧੀ ਡੀਵਾਈਸ ਪ੍ਰਬੰਧਕ"</string>
-    <!-- no translation found for confirmation_title (8024993972587946678) -->
-    <skip />
+    <string name="confirmation_title" msgid="2244241995958340998">"ਕੀ &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ਐਪ ਨੂੰ &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; ਤੱਕ ਪਹੁੰਚ ਕਰਨ ਦੀ ਆਗਿਆ ਦੇਣੀ ਹੈ?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"ਸਮਾਰਟ-ਵਾਚ"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"&lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; ਵੱਲੋਂ ਪ੍ਰਬੰਧਿਤ ਕੀਤੇ ਜਾਣ ਲਈ <xliff:g id="PROFILE_NAME">%1$s</xliff:g> ਚੁਣੋ"</string>
-    <!-- no translation found for summary_watch (6566922405914995759) -->
-    <skip />
-    <!-- no translation found for summary_watch_single_device (7443464525873186735) -->
-    <skip />
-    <string name="profile_name_glasses" msgid="8488394059007275998">"ਐਨਕਾਂ"</string>
-    <!-- no translation found for summary_glasses (3808267780579061241) -->
-    <skip />
-    <!-- no translation found for summary_glasses_single_device (7051392780285915640) -->
-    <skip />
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ਵੱਲੋਂ ਪ੍ਰਬੰਧਿਤ ਕੀਤੇ ਜਾਣ ਲਈ ਕੋਈ ਡੀਵਾਈਸ ਚੁਣੋ"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"ਸੈੱਟਅੱਪ ਕਰਨ ਲਈ <xliff:g id="PROFILE_NAME">%1$s</xliff:g> ਚੁਣੋ"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"ਇਸ ਐਪ ਨੂੰ ਤੁਹਾਡੇ <xliff:g id="DEVICE_NAME">%1$s</xliff:g> \'ਤੇ ਕਾਲਰ ਦੇ ਨਾਮ ਵਰਗੀ ਜਾਣਕਾਰੀ ਨੂੰ ਸਿੰਕ ਕਰਨ ਅਤੇ ਇਨ੍ਹਾਂ ਇਜਾਜ਼ਤਾਂ ਤੱਕ ਪਹੁੰਚ ਕਰਨ ਦੀ ਆਗਿਆ ਹੋਵੇਗੀ"</string>
+    <string name="confirmation_title_glasses" msgid="8288346850537727333">"ਕੀ &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ਨੂੰ &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; ਦਾ ਪ੍ਰਬੰਧਨ ਕਰਨ ਦੀ ਆਗਿਆ ਦੇਣੀ ਹੈ?"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"ਡੀਵਾਈਸ"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"ਇਸ ਐਪ ਨੂੰ ਤੁਹਾਡੇ <xliff:g id="DEVICE_NAME">%1$s</xliff:g> \'ਤੇ ਇਨ੍ਹਾਂ ਇਜਾਜ਼ਤਾਂ ਤੱਕ ਪਹੁੰਚ ਕਰਨ ਦੀ ਆਗਿਆ ਹੋਵੇਗੀ"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ਨੂੰ ਤੁਹਾਡੇ ਫ਼ੋਨ ਤੋਂ ਇਸ ਜਾਣਕਾਰੀ ਤੱਕ ਪਹੁੰਚ ਕਰਨ ਦੀ ਆਗਿਆ ਦਿਓ"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"ਕ੍ਰਾਸ-ਡੀਵਾਈਸ ਸੇਵਾਵਾਂ"</string>
-    <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> ਤੁਹਾਡੇ <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> ਦੀ ਤਰਫ਼ੋਂ ਤੁਹਾਡੇ ਡੀਵਾਈਸਾਂ ਵਿਚਕਾਰ ਐਪਾਂ ਨੂੰ ਸਟ੍ਰੀਮ ਕਰਨ ਦੀ ਇਜਾਜ਼ਤ ਮੰਗ ਰਹੀ ਹੈ"</string>
+    <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g> ਤੁਹਾਡੇ <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> ਦੀ ਤਰਫ਼ੋਂ ਤੁਹਾਡੇ ਡੀਵਾਈਸਾਂ ਵਿਚਕਾਰ ਐਪਾਂ ਨੂੰ ਸਟ੍ਰੀਮ ਕਰਨ ਦੀ ਇਜਾਜ਼ਤ ਮੰਗ ਰਹੀ ਹੈ"</string>
     <string name="title_automotive_projection" msgid="3296005598978412847"></string>
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ਨੂੰ ਤੁਹਾਡੇ ਫ਼ੋਨ ਤੋਂ ਇਸ ਜਾਣਕਾਰੀ ਤੱਕ ਪਹੁੰਚ ਕਰਨ ਦੀ ਆਗਿਆ ਦਿਓ"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Google Play ਸੇਵਾਵਾਂ"</string>
-    <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> ਤੁਹਾਡੇ <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> ਦੀ ਤਰਫ਼ੋਂ ਤੁਹਾਡੇ ਫ਼ੋਨ ਦੀਆਂ ਫ਼ੋਟੋਆਂ, ਮੀਡੀਆ ਅਤੇ ਸੂਚਨਾਵਾਂ ਤੱਕ ਪਹੁੰਚ ਕਰਨ ਦੀ ਇਜਾਜ਼ਤ ਮੰਗ ਰਹੀ ਹੈ"</string>
-    <string name="title_nearby_device_streaming" msgid="179278282547719200">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ਨੂੰ ਆਪਣੇ ਫ਼ੋਨ ਤੋਂ ਇਹ ਕਾਰਵਾਈ ਕਰਨ ਦੀ ਇਜਾਜ਼ਤ ਦਿਓ"</string>
-    <string name="helper_title_nearby_device_streaming" msgid="6124438217620593669">"ਕ੍ਰਾਸ-ਡੀਵਾਈਸ ਸੇਵਾਵਾਂ"</string>
-    <string name="helper_summary_nearby_device_streaming" msgid="5538329403511524333">"<xliff:g id="APP_NAME">%1$s</xliff:g> ਤੁਹਾਡੇ <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> ਦੀ ਤਰਫ਼ੋਂ ਨਜ਼ਦੀਕੀ ਡੀਵਾਈਸਾਂ ਲਈ ਸਮੱਗਰੀ ਨੂੰ ਸਟ੍ਰੀਮ ਕਰਨ ਦੀ ਇਜਾਜ਼ਤ ਮੰਗ ਰਹੀ ਹੈ"</string>
+    <string name="helper_summary_computer" msgid="8774832742608187072">"<xliff:g id="APP_NAME">%1$s</xliff:g> ਤੁਹਾਡੇ <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> ਦੀ ਤਰਫ਼ੋਂ ਤੁਹਾਡੇ ਫ਼ੋਨ ਦੀਆਂ ਫ਼ੋਟੋਆਂ, ਮੀਡੀਆ ਅਤੇ ਸੂਚਨਾਵਾਂ ਤੱਕ ਪਹੁੰਚ ਕਰਨ ਦੀ ਇਜਾਜ਼ਤ ਮੰਗ ਰਹੀ ਹੈ"</string>
+    <string name="title_nearby_device_streaming" msgid="7269956847378799794">"ਕੀ &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; ਨੂੰ ਇਹ ਕਾਰਵਾਈ ਕਰਨ ਦੀ ਆਗਿਆ ਦੇਣੀ ਹੈ?"</string>
+    <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> ਤੁਹਾਡੇ <xliff:g id="DEVICE_NAME">%2$s</xliff:g> ਦੀ ਤਰਫ਼ੋਂ ਨਜ਼ਦੀਕੀ ਡੀਵਾਈਸਾਂ \'ਤੇ ਐਪਾਂ ਅਤੇ ਹੋਰ ਸਿਸਟਮ ਸੰਬੰਧੀ ਵਿਸ਼ੇਸ਼ਤਾਵਾਂ ਨੂੰ ਸਟ੍ਰੀਮ ਕਰਨ ਦੀ ਇਜਾਜ਼ਤ ਮੰਗ ਰਹੀ ਹੈ"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"ਡੀਵਾਈਸ"</string>
-    <!-- no translation found for summary_generic_single_device (4735072202474939111) -->
-    <skip />
-    <!-- no translation found for summary_generic (4988130802522924650) -->
-    <skip />
+    <string name="summary_generic" msgid="1761976003668044801">"ਇਹ ਐਪ ਤੁਹਾਡੇ ਫ਼ੋਨ ਅਤੇ ਚੁਣੇ ਗਏ ਡੀਵਾਈਸ ਵਿਚਕਾਰ ਕਾਲਰ ਦੇ ਨਾਮ ਵਰਗੀ ਜਾਣਕਾਰੀ ਨੂੰ ਸਿੰਕ ਕਰ ਸਕੇਗੀ"</string>
     <string name="consent_yes" msgid="8344487259618762872">"ਆਗਿਆ ਦਿਓ"</string>
     <string name="consent_no" msgid="2640796915611404382">"ਆਗਿਆ ਨਾ ਦਿਓ"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"ਰੱਦ ਕਰੋ"</string>
     <string name="consent_back" msgid="2560683030046918882">"ਪਿੱਛੇ"</string>
+    <string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> ਦਾ ਵਿਸਤਾਰ ਕਰੋ"</string>
+    <string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> ਨੂੰ ਸਮੇਟੋ"</string>
     <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"ਕੀ &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; \'ਤੇ ਮੌਜੂਦ ਐਪਾਂ ਨੂੰ &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; \'ਤੇ ਮੌਜੂਦ ਐਪਾਂ ਵਾਂਗ ਇਜਾਜ਼ਤਾਂ ਦੇਣੀਆਂ ਹਨ?"</string>
     <string name="permission_sync_summary" msgid="765497944331294275">"ਇਸ ਵਿੱਚ &lt;strong&gt;ਮਾਈਕ੍ਰੋਫ਼ੋਨ&lt;/strong&gt;, &lt;strong&gt;ਕੈਮਰਾ&lt;/strong&gt;, ਅਤੇ &lt;strong&gt;ਟਿਕਾਣਾ ਪਹੁੰਚ&lt;/strong&gt;, ਅਤੇ &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt; \'ਤੇ ਮੌਜੂਦ ਹੋਰ ਸੰਵੇਦਨਸ਼ੀਲ ਜਾਣਕਾਰੀ ਤੱਕ ਪਹੁੰਚ ਸੰਬੰਧੀ ਇਜਾਜ਼ਤਾਂ ਸ਼ਾਮਲ ਹੋ ਸਕਦੀਆਂ ਹਨ। &lt;br/&gt;&lt;br/&gt;ਤੁਸੀਂ &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt; \'ਤੇ ਮੌਜੂਦ ਆਪਣੀਆਂ ਸੈਟਿੰਗਾਂ ਵਿੱਚ ਜਾ ਕੇ ਕਿਸੇ ਵੀ ਵੇਲੇ ਇਨ੍ਹਾਂ ਇਜਾਜ਼ਤਾਂ ਨੂੰ ਬਦਲ ਸਕਦੇ ਹੋ।"</string>
-    <string name="vendor_icon_description" msgid="4445875290032225965">"ਐਪ ਪ੍ਰਤੀਕ"</string>
-    <string name="vendor_header_button_description" msgid="6566660389500630608">"ਹੋਰ ਜਾਣਕਾਰੀ ਬਟਨ"</string>
+    <string name="vendor_header_button_description" msgid="7994879208461111473">"ਹੋਰ ਜਾਣਕਾਰੀ"</string>
     <string name="permission_phone" msgid="2661081078692784919">"ਫ਼ੋਨ"</string>
     <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
     <string name="permission_contacts" msgid="3858319347208004438">"ਸੰਪਰਕ"</string>
@@ -64,16 +57,18 @@
     <string name="permission_storage" msgid="6831099350839392343">"ਫ਼ੋਟੋਆਂ ਅਤੇ ਮੀਡੀਆ"</string>
     <string name="permission_notification" msgid="693762568127741203">"ਸੂਚਨਾਵਾਂ"</string>
     <string name="permission_app_streaming" msgid="6009695219091526422">"ਐਪਾਂ"</string>
-    <string name="permission_nearby_device_streaming" msgid="5868108148065023161">"ਨਜ਼ਦੀਕੀ ਡੀਵਾਈਸਾਂ \'ਤੇ ਸਟ੍ਰੀਮਿੰਗ"</string>
+    <string name="permission_nearby_device_streaming" msgid="1023325519477349499">"ਸਟ੍ਰੀਮਿੰਗ"</string>
     <string name="permission_phone_summary" msgid="6684396967861278044">"ਫ਼ੋਨ ਕਾਲਾਂ ਕਰਨ ਅਤੇ ਉਨ੍ਹਾਂ ਦਾ ਪ੍ਰਬੰਧਨ ਕਰਨ ਦੀ ਇਜਾਜ਼ਤ ਹੈ"</string>
     <string name="permission_call_logs_summary" msgid="6186103394658755022">"ਫ਼ੋਨ ਦੇ ਕਾਲ ਲੌਗ ਨੂੰ ਪੜ੍ਹਣ ਅਤੇ ਲਿਖਣ ਦੀ ਇਜਾਜ਼ਤ ਹੈ"</string>
     <string name="permission_sms_summary" msgid="3508442683678912017">"SMS ਸੁਨੇਹੇ ਭੇਜਣ ਅਤੇ ਦੇਖਣ ਦੀ ਇਜਾਜ਼ਤ ਹੈ"</string>
     <string name="permission_contacts_summary" msgid="675861979475628708">"ਆਪਣੇ ਸੰਪਰਕਾਂ ਤੱਕ ਪਹੁੰਚ ਕਰਨ ਦੀ ਇਜਾਜ਼ਤ ਹੈ"</string>
     <string name="permission_calendar_summary" msgid="6460000922511766226">"ਆਪਣੇ ਕੈਲੰਡਰ ਤੱਕ ਪਹੁੰਚ ਕਰਨ ਦੀ ਇਜਾਜ਼ਤ ਹੈ"</string>
-    <string name="permission_microphone_summary" msgid="4241354865859396558">"ਮਾਈਕ੍ਰੋਫ਼ੋਨ ਦੀ ਵਰਤੋਂ ਕਰ ਕੇ ਆਡੀਓ ਰਿਕਾਰਡ ਕਰ ਸਕਦੇ ਹੋ"</string>
+    <string name="permission_microphone_summary" msgid="3692091540613093394">"ਆਡੀਓ ਰਿਕਾਰਡ ਕੀਤੀ ਜਾ ਸਕਦੀ ਹੈ"</string>
     <string name="permission_nearby_devices_summary" msgid="931940524460876655">"ਨਜ਼ਦੀਕੀ ਡੀਵਾਈਸਾਂ ਨੂੰ ਲੱਭਣ, ਉਨ੍ਹਾਂ ਨਾਲ ਕਨੈਕਟ ਕਰਨ ਅਤੇ ਸੰਬੰਧਿਤ ਸਥਿਤੀ ਨਿਰਧਾਰਿਤ ਕਰਨ ਦੀ ਇਜਾਜ਼ਤ ਹੈ"</string>
     <string name="permission_notification_summary" msgid="884075314530071011">"ਤੁਸੀਂ ਸਾਰੀਆਂ ਸੂਚਨਾਵਾਂ ਪੜ੍ਹ ਸਕਦੇ ਹੋ, ਜਿਨ੍ਹਾਂ ਵਿੱਚ ਸੰਪਰਕਾਂ, ਸੁਨੇਹਿਆਂ ਅਤੇ ਫ਼ੋਟੋਆਂ ਵਰਗੀ ਜਾਣਕਾਰੀ ਸ਼ਾਮਲ ਹੁੰਦੀ ਹੈ"</string>
     <string name="permission_app_streaming_summary" msgid="606923325679670624">"ਆਪਣੇ ਫ਼ੋਨ ਦੀਆਂ ਐਪਾਂ ਨੂੰ ਸਟ੍ਰੀਮ ਕਰੋ"</string>
     <string name="permission_storage_summary" msgid="3918240895519506417"></string>
-    <string name="permission_nearby_device_streaming_summary" msgid="5776807830582725074">"ਸਮੱਗਰੀ ਨੂੰ ਨਜ਼ਦੀਕੀ ਡੀਵਾਈਸ \'ਤੇ ਸਟ੍ਰੀਮ ਕਰੋ"</string>
+    <string name="permission_nearby_device_streaming_summary" msgid="8280824871197081246">"ਆਪਣੇ ਫ਼ੋਨ ਤੋਂ ਐਪਾਂ ਅਤੇ ਹੋਰ ਸਿਸਟਮ ਸੰਬੰਧੀ ਵਿਸ਼ੇਸ਼ਤਾਵਾਂ ਨੂੰ ਸਟ੍ਰੀਮ ਕਰੋ"</string>
+    <string name="device_type" product="default" msgid="8268703872070046263">"ਫ਼ੋਨ"</string>
+    <string name="device_type" product="tablet" msgid="5038791954983067774">"ਟੈਬਲੈੱਟ"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-pl/strings.xml b/packages/CompanionDeviceManager/res/values-pl/strings.xml
index 6b71592..7ca172e 100644
--- a/packages/CompanionDeviceManager/res/values-pl/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-pl/strings.xml
@@ -17,43 +17,36 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Menedżer urządzeń towarzyszących"</string>
-    <!-- no translation found for confirmation_title (8024993972587946678) -->
-    <skip />
+    <string name="confirmation_title" msgid="2244241995958340998">"Zezwolić aplikacji &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; na dostęp do urządzenia &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"zegarek"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Wybierz profil <xliff:g id="PROFILE_NAME">%1$s</xliff:g>, którym ma zarządzać aplikacja &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <!-- no translation found for summary_watch (6566922405914995759) -->
-    <skip />
-    <!-- no translation found for summary_watch_single_device (7443464525873186735) -->
-    <skip />
-    <string name="profile_name_glasses" msgid="8488394059007275998">"Okulary"</string>
-    <!-- no translation found for summary_glasses (3808267780579061241) -->
-    <skip />
-    <!-- no translation found for summary_glasses_single_device (7051392780285915640) -->
-    <skip />
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"Wybierz urządzenie, którym ma zarządzać aplikacja &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Wybierz profil <xliff:g id="PROFILE_NAME">%1$s</xliff:g>, aby go skonfigurować"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Aplikacja będzie mogła synchronizować informacje takie jak nazwa dzwoniącego oraz korzystać z tych uprawnień na Twoim urządzeniu (<xliff:g id="DEVICE_NAME">%1$s</xliff:g>)"</string>
+    <string name="confirmation_title_glasses" msgid="8288346850537727333">"Zezwolić na dostęp aplikacji &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; do urządzenia &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"urządzenie"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Aplikacja będzie miała dostęp do tych uprawnień na Twoim urządzeniu (<xliff:g id="DEVICE_NAME">%1$s</xliff:g>)"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Zezwól urządzeniu &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; na dostęp do tych informacji na Twoim telefonie"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Usługi na innym urządzeniu"</string>
-    <string name="helper_summary_app_streaming" msgid="5977509499890099">"Aplikacja <xliff:g id="APP_NAME">%1$s</xliff:g> prosi w imieniu urządzenia <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> o uprawnienia dotyczące strumieniowego odtwarzania treści z aplikacji na innym urządzeniu"</string>
+    <string name="helper_summary_app_streaming" msgid="2396773196949578425">"Aplikacja <xliff:g id="APP_NAME">%1$s</xliff:g> prosi w imieniu urządzenia <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> o uprawnienia dotyczące strumieniowego odtwarzania treści z aplikacji na innym urządzeniu"</string>
     <string name="title_automotive_projection" msgid="3296005598978412847"></string>
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"Zezwól aplikacji &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; na dostęp do tych informacji na Twoim telefonie"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Usługi Google Play"</string>
-    <string name="helper_summary_computer" msgid="9050724687678157852">"Aplikacja <xliff:g id="APP_NAME">%1$s</xliff:g> prosi w imieniu urządzenia <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> o uprawnienia dotyczące dostępu do zdjęć, multimediów i powiadomień na telefonie"</string>
-    <string name="title_nearby_device_streaming" msgid="179278282547719200">"Zezwól na wykonywanie tych działań z Twojego telefonu przez aplikację &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="helper_title_nearby_device_streaming" msgid="6124438217620593669">"Usługi na innym urządzeniu"</string>
-    <string name="helper_summary_nearby_device_streaming" msgid="5538329403511524333">"Aplikacja <xliff:g id="APP_NAME">%1$s</xliff:g> prosi w imieniu urządzenia <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> o uprawnienia dotyczące strumieniowego odtwarzania treści na urządzeniach w pobliżu"</string>
+    <string name="helper_summary_computer" msgid="8774832742608187072">"Aplikacja <xliff:g id="APP_NAME">%1$s</xliff:g> prosi w imieniu urządzenia <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> o uprawnienia dotyczące dostępu do zdjęć, multimediów i powiadomień na telefonie"</string>
+    <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Zezwolić urządzeniu &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; na wykonanie tego działania?"</string>
+    <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"Aplikacja <xliff:g id="APP_NAME">%1$s</xliff:g> prosi w imieniu urządzenia <xliff:g id="DEVICE_NAME">%2$s</xliff:g> o uprawnienia do strumieniowego odtwarzania treści i innych funkcji systemowych na urządzeniach w pobliżu"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"urządzenie"</string>
-    <!-- no translation found for summary_generic_single_device (4735072202474939111) -->
-    <skip />
-    <!-- no translation found for summary_generic (4988130802522924650) -->
-    <skip />
+    <string name="summary_generic" msgid="1761976003668044801">"Ta aplikacja może synchronizować informacje takie jak nazwa osoby dzwoniącej między Twoim telefonem i wybranym urządzeniem"</string>
     <string name="consent_yes" msgid="8344487259618762872">"Zezwól"</string>
     <string name="consent_no" msgid="2640796915611404382">"Nie zezwalaj"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Anuluj"</string>
     <string name="consent_back" msgid="2560683030046918882">"Wstecz"</string>
+    <string name="permission_expand" msgid="893185038020887411">"Rozwiń sekcję <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
+    <string name="permission_collapse" msgid="3320833884220844084">"Zwiń sekcję <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Czy aplikacjom na urządzeniu &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; przyznać te same uprawnienia co na urządzeniu &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="permission_sync_summary" msgid="765497944331294275">"Wśród nich mogą być dane dostępu do &lt;strong&gt;Mikrofonu&lt;/strong&gt;, &lt;strong&gt;Aparatu&lt;/strong&gt;, i &lt;strong&gt;Lokalizacji&lt;/strong&gt;, i inne uprawnienia newralgiczne na urządzeniu &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;. &lt;br/&gt;&lt;br/&gt;Możesz w dowolnym momencie zmienić uprawnienia na urządzeniu &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;."</string>
-    <string name="vendor_icon_description" msgid="4445875290032225965">"Ikona aplikacji"</string>
-    <string name="vendor_header_button_description" msgid="6566660389500630608">"Przycisk – więcej informacji"</string>
+    <string name="vendor_header_button_description" msgid="7994879208461111473">"Więcej informacji"</string>
     <string name="permission_phone" msgid="2661081078692784919">"Telefon"</string>
     <string name="permission_sms" msgid="6337141296535774786">"SMS-y"</string>
     <string name="permission_contacts" msgid="3858319347208004438">"Kontakty"</string>
@@ -64,16 +57,18 @@
     <string name="permission_storage" msgid="6831099350839392343">"Zdjęcia i multimedia"</string>
     <string name="permission_notification" msgid="693762568127741203">"Powiadomienia"</string>
     <string name="permission_app_streaming" msgid="6009695219091526422">"Aplikacje"</string>
-    <string name="permission_nearby_device_streaming" msgid="5868108148065023161">"Strumieniowanie danych na urządzenia w pobliżu"</string>
+    <string name="permission_nearby_device_streaming" msgid="1023325519477349499">"Strumieniowanie"</string>
     <string name="permission_phone_summary" msgid="6684396967861278044">"Może wykonywać i odbierać połączenia"</string>
     <string name="permission_call_logs_summary" msgid="6186103394658755022">"Może odczytywać i zapisywać rejestr połączeń telefonicznych"</string>
     <string name="permission_sms_summary" msgid="3508442683678912017">"Może wysyłać i odbierać SMS-y"</string>
     <string name="permission_contacts_summary" msgid="675861979475628708">"Może uzyskać dostęp do kontaktów"</string>
     <string name="permission_calendar_summary" msgid="6460000922511766226">"Może uzyskać dostęp do kalendarza"</string>
-    <string name="permission_microphone_summary" msgid="4241354865859396558">"Może nagrywać dźwięk przy użyciu mikrofonu"</string>
+    <string name="permission_microphone_summary" msgid="3692091540613093394">"Może nagrywać dźwięk"</string>
     <string name="permission_nearby_devices_summary" msgid="931940524460876655">"Może znajdować urządzenia w pobliżu, określać ich względne położenie oraz łączyć się z nimi"</string>
     <string name="permission_notification_summary" msgid="884075314530071011">"Może odczytywać wszystkie powiadomienia, w tym informacje takie jak kontakty, wiadomości i zdjęcia"</string>
     <string name="permission_app_streaming_summary" msgid="606923325679670624">"Odtwarzaj strumieniowo aplikacje z telefonu"</string>
     <string name="permission_storage_summary" msgid="3918240895519506417"></string>
-    <string name="permission_nearby_device_streaming_summary" msgid="5776807830582725074">"Strumieniowe odtwarzanie treści na urządzeniach w pobliżu"</string>
+    <string name="permission_nearby_device_streaming_summary" msgid="8280824871197081246">"Aplikacje do odtwarzania strumieniowego i inne funkcje systemowe na Twoim telefonie"</string>
+    <string name="device_type" product="default" msgid="8268703872070046263">"telefon"</string>
+    <string name="device_type" product="tablet" msgid="5038791954983067774">"tablet"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-pt-rBR/strings.xml b/packages/CompanionDeviceManager/res/values-pt-rBR/strings.xml
index 29f00ca..26647c8 100644
--- a/packages/CompanionDeviceManager/res/values-pt-rBR/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-pt-rBR/strings.xml
@@ -17,36 +17,36 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Gerenciador de dispositivos complementar"</string>
-    <string name="confirmation_title" msgid="8024993972587946678">"Permitir que o app &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; acesse o dispositivo &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"Permitir que o app &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; acesse o dispositivo &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"relógio"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Escolha um <xliff:g id="PROFILE_NAME">%1$s</xliff:g> para ser gerenciado pelo app &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="6566922405914995759">"O app <xliff:g id="APP_NAME">%2$s</xliff:g> é necessário para gerenciar o dispositivo <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. Ele poderá sincronizar informações, como o nome de quem está ligando, interagir com suas notificações e acessar as permissões de telefone, SMS, contatos, agenda, registro de chamadas e dispositivos por perto."</string>
-    <string name="summary_watch_single_device" msgid="7443464525873186735">"O app <xliff:g id="APP_NAME">%2$s</xliff:g> é necessário para gerenciar seu dispositivo <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. Ele poderá sincronizar informações, como o nome de quem está ligando, e acessar estas permissões:"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"óculos"</string>
-    <string name="summary_glasses" msgid="3808267780579061241">"O app <xliff:g id="APP_NAME">%2$s</xliff:g> é necessário para gerenciar o dispositivo <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. Ele poderá interagir com suas notificações e acessar suas permissões de telefone, SMS, contatos, microfone e dispositivos por perto."</string>
-    <string name="summary_glasses_single_device" msgid="7051392780285915640">"O app <xliff:g id="APP_NAME">%2$s</xliff:g> é necessário para gerenciar o dispositivo <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. Ele poderá interagir com estas permissões:"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"Escolha um dispositivo para ser gerenciado pelo app &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Escolha um <xliff:g id="PROFILE_NAME">%1$s</xliff:g> para configurar"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"O app poderá sincronizar informações, como o nome de quem está ligando, e acessar estas permissões no seu <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
+    <string name="confirmation_title_glasses" msgid="8288346850537727333">"Permitir que o app &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; gerencie o dispositivo &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"dispositivo"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"O app poderá acessar estas permissões no seu <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Permitir que o app &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; acesse estas informações do smartphone"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Serviços entre dispositivos"</string>
-    <string name="helper_summary_app_streaming" msgid="5977509499890099">"O app <xliff:g id="APP_NAME">%1$s</xliff:g> está pedindo permissão em nome do seu <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> para fazer streaming de apps entre seus dispositivos"</string>
+    <string name="helper_summary_app_streaming" msgid="2396773196949578425">"O app <xliff:g id="APP_NAME">%1$s</xliff:g> está pedindo permissão em nome do seu dispositivo <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> para fazer streaming de apps entre seus dispositivos"</string>
     <string name="title_automotive_projection" msgid="3296005598978412847"></string>
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"Autorizar que &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; acesse estas informações do smartphone"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Google Play Services"</string>
-    <string name="helper_summary_computer" msgid="9050724687678157852">"O app <xliff:g id="APP_NAME">%1$s</xliff:g> está pedindo permissão em nome do seu <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> para acessar fotos, mídia e notificações do smartphone."</string>
-    <string name="title_nearby_device_streaming" msgid="179278282547719200">"Permitir que &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; realize esta ação pelo smartphone"</string>
-    <string name="helper_title_nearby_device_streaming" msgid="6124438217620593669">"Serviços entre dispositivos"</string>
-    <string name="helper_summary_nearby_device_streaming" msgid="5538329403511524333">"<xliff:g id="APP_NAME">%1$s</xliff:g> está pedindo permissão em nome de <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> para fazer streaming de conteúdo para dispositivos por perto"</string>
+    <string name="helper_summary_computer" msgid="8774832742608187072">"O app <xliff:g id="APP_NAME">%1$s</xliff:g> está pedindo permissão em nome do seu dispositivo <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> para acessar fotos, mídia e notificações do smartphone"</string>
+    <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Permitir que o dispositivo &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; realize esta ação?"</string>
+    <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> está pedindo permissão em nome do seu dispositivo <xliff:g id="DEVICE_NAME">%2$s</xliff:g> para fazer streaming de apps e de outros recursos do sistema para dispositivos por perto"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"dispositivo"</string>
-    <string name="summary_generic_single_device" msgid="4735072202474939111">"O app poderá sincronizar informações, como o nome de quem está ligando, entre seu smartphone e o dispositivo <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string>
-    <string name="summary_generic" msgid="4988130802522924650">"O app poderá sincronizar informações, como o nome de quem está ligando, entre seu smartphone e o dispositivo escolhido."</string>
+    <string name="summary_generic" msgid="1761976003668044801">"O app poderá sincronizar informações, como o nome de quem está ligando, entre seu smartphone e o dispositivo escolhido"</string>
     <string name="consent_yes" msgid="8344487259618762872">"Permitir"</string>
     <string name="consent_no" msgid="2640796915611404382">"Não permitir"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Cancelar"</string>
     <string name="consent_back" msgid="2560683030046918882">"Voltar"</string>
+    <string name="permission_expand" msgid="893185038020887411">"Abrir <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
+    <string name="permission_collapse" msgid="3320833884220844084">"Fechar <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Dar aos apps no dispositivo &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; as mesmas permissões do dispositivo &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="permission_sync_summary" msgid="765497944331294275">"Isso pode incluir acesso ao &lt;strong&gt;Microfone&lt;/strong&gt;, à &lt;strong&gt;Câmera&lt;/strong&gt; e à &lt;strong&gt;Localização&lt;/strong&gt;, além de outras permissões sensíveis no dispositivo &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;. &lt;br/&gt;&lt;br/&gt;Você pode mudar essas permissões a qualquer momento nas Configurações do dispositivo &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;."</string>
-    <string name="vendor_icon_description" msgid="4445875290032225965">"Ícone do app"</string>
-    <string name="vendor_header_button_description" msgid="6566660389500630608">"Botão \"Mais informações\""</string>
+    <string name="vendor_header_button_description" msgid="7994879208461111473">"Mais informações"</string>
     <string name="permission_phone" msgid="2661081078692784919">"Smartphone"</string>
     <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
     <string name="permission_contacts" msgid="3858319347208004438">"Contatos"</string>
@@ -57,16 +57,18 @@
     <string name="permission_storage" msgid="6831099350839392343">"Fotos e mídia"</string>
     <string name="permission_notification" msgid="693762568127741203">"Notificações"</string>
     <string name="permission_app_streaming" msgid="6009695219091526422">"Apps"</string>
-    <string name="permission_nearby_device_streaming" msgid="5868108148065023161">"Streaming em disp. por perto"</string>
+    <string name="permission_nearby_device_streaming" msgid="1023325519477349499">"Streaming"</string>
     <string name="permission_phone_summary" msgid="6684396967861278044">"Pode fazer e gerenciar ligações"</string>
     <string name="permission_call_logs_summary" msgid="6186103394658755022">"Pode ler e gravar o registro de chamadas"</string>
     <string name="permission_sms_summary" msgid="3508442683678912017">"Pode enviar e acessar mensagens SMS"</string>
     <string name="permission_contacts_summary" msgid="675861979475628708">"Pode acessar seus contatos"</string>
     <string name="permission_calendar_summary" msgid="6460000922511766226">"Pode acessar sua agenda"</string>
-    <string name="permission_microphone_summary" msgid="4241354865859396558">"Pode gravar áudio usando o microfone"</string>
+    <string name="permission_microphone_summary" msgid="3692091540613093394">"Pode gravar áudio"</string>
     <string name="permission_nearby_devices_summary" msgid="931940524460876655">"Pode encontrar, determinar o posicionamento relativo e se conectar a dispositivos por perto"</string>
     <string name="permission_notification_summary" msgid="884075314530071011">"Pode ler todas as notificações, incluindo informações como contatos, mensagens e fotos"</string>
     <string name="permission_app_streaming_summary" msgid="606923325679670624">"Fazer transmissão dos apps no seu smartphone"</string>
     <string name="permission_storage_summary" msgid="3918240895519506417"></string>
-    <string name="permission_nearby_device_streaming_summary" msgid="5776807830582725074">"Fazer streaming de conteúdo para dispositivos por perto"</string>
+    <string name="permission_nearby_device_streaming_summary" msgid="8280824871197081246">"Fazer streaming de apps e outros recursos do sistema pelo smartphone"</string>
+    <string name="device_type" product="default" msgid="8268703872070046263">"smartphone"</string>
+    <string name="device_type" product="tablet" msgid="5038791954983067774">"tablet"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-pt-rPT/strings.xml b/packages/CompanionDeviceManager/res/values-pt-rPT/strings.xml
index d5978e2..ba60f56 100644
--- a/packages/CompanionDeviceManager/res/values-pt-rPT/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-pt-rPT/strings.xml
@@ -17,36 +17,36 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Gestor de dispositivos associados"</string>
-    <string name="confirmation_title" msgid="8024993972587946678">"Permita que a app &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; aceda ao dispositivo &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"Permitir que a app &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; aceda ao &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"relógio"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Escolha um <xliff:g id="PROFILE_NAME">%1$s</xliff:g> para ser gerido pela app &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="6566922405914995759">"A app é necessária para gerir o dispositivo <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. A app <xliff:g id="APP_NAME">%2$s</xliff:g> vai poder sincronizar informações, como o nome do autor de uma chamada, interagir com as suas notificações e aceder às autorizações do Telefone, SMS, Contactos, Calendário, Registos de chamadas e Dispositivos próximos."</string>
-    <string name="summary_watch_single_device" msgid="7443464525873186735">"A app é necessária para gerir o dispositivo <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. A app <xliff:g id="APP_NAME">%2$s</xliff:g> vai poder sincronizar informações, como o nome do autor de uma chamada, e aceder às seguintes autorizações:"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"óculos"</string>
-    <string name="summary_glasses" msgid="3808267780579061241">"Esta app é necessária para gerir o dispositivo <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. A app <xliff:g id="APP_NAME">%2$s</xliff:g> vai poder interagir com as suas notificações e aceder às autorizações do Telemóvel, SMS, Contactos, Microfone e Dispositivos próximos."</string>
-    <string name="summary_glasses_single_device" msgid="7051392780285915640">"A app é necessária para gerir o dispositivo <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. A app <xliff:g id="APP_NAME">%2$s</xliff:g> vai poder interagir com estas autorizações:"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"Escolha um dispositivo para ser gerido pela app &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Escolha um perfil de <xliff:g id="PROFILE_NAME">%1$s</xliff:g> para configurar"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Esta app vai poder sincronizar informações, como o nome do autor de uma chamada, e aceder a estas autorizações no seu <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
+    <string name="confirmation_title_glasses" msgid="8288346850537727333">"Permita que a app &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; faça a gestão do dispositivo &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"dispositivo"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Esta app vai poder aceder a estas autorizações no seu <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Permita que a app &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; aceda a estas informações do seu telemóvel"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Serviços entre dispositivos"</string>
-    <string name="helper_summary_app_streaming" msgid="5977509499890099">"A app <xliff:g id="APP_NAME">%1$s</xliff:g> está a pedir autorização em nome do seu dispositivo <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> para fazer stream de apps entre os seus dispositivos"</string>
+    <string name="helper_summary_app_streaming" msgid="2396773196949578425">"A app <xliff:g id="APP_NAME">%1$s</xliff:g> está a pedir autorização em nome do seu dispositivo <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> para fazer stream de apps entre os seus dispositivos"</string>
     <string name="title_automotive_projection" msgid="3296005598978412847"></string>
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"Permita que &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; aceda a estas informações do seu telemóvel"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Serviços do Google Play"</string>
-    <string name="helper_summary_computer" msgid="9050724687678157852">"A app <xliff:g id="APP_NAME">%1$s</xliff:g> está a pedir autorização em nome do seu dispositivo <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> para aceder às fotos, ao conteúdo multimédia e às notificações do seu telemóvel"</string>
-    <string name="title_nearby_device_streaming" msgid="179278282547719200">"Permita que a app &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; realize esta ação a partir do seu telemóvel"</string>
-    <string name="helper_title_nearby_device_streaming" msgid="6124438217620593669">"Serviços entre dispositivos"</string>
-    <string name="helper_summary_nearby_device_streaming" msgid="5538329403511524333">"A app <xliff:g id="APP_NAME">%1$s</xliff:g> está a pedir autorização em nome do dispositivo <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> para fazer stream de conteúdo para dispositivos próximos"</string>
+    <string name="helper_summary_computer" msgid="8774832742608187072">"A app <xliff:g id="APP_NAME">%1$s</xliff:g> está a pedir autorização em nome do seu dispositivo <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> para aceder às fotos, ao conteúdo multimédia e às notificações do seu telemóvel"</string>
+    <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Permitir que o dispositivo &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; faça esta ação?"</string>
+    <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"A app <xliff:g id="APP_NAME">%1$s</xliff:g> está a pedir autorização em nome do dispositivo <xliff:g id="DEVICE_NAME">%2$s</xliff:g> para fazer stream de apps e outras funcionalidades do sistema para dispositivos próximos"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"dispositivo"</string>
-    <string name="summary_generic_single_device" msgid="4735072202474939111">"Esta app vai poder sincronizar informações, como o nome do autor de uma chamada, entre o telemóvel e o dispositivo <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string>
-    <string name="summary_generic" msgid="4988130802522924650">"Esta app vai poder sincronizar informações, como o nome do autor de uma chamada, entre o telemóvel e o dispositivo escolhido."</string>
+    <string name="summary_generic" msgid="1761976003668044801">"Esta app vai poder sincronizar informações, como o nome do autor de uma chamada, entre o telemóvel e o dispositivo escolhido"</string>
     <string name="consent_yes" msgid="8344487259618762872">"Permitir"</string>
     <string name="consent_no" msgid="2640796915611404382">"Não permitir"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Cancelar"</string>
     <string name="consent_back" msgid="2560683030046918882">"Voltar"</string>
+    <string name="permission_expand" msgid="893185038020887411">"Expandir <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
+    <string name="permission_collapse" msgid="3320833884220844084">"Reduzir <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Dar às apps no dispositivo &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; as mesmas autorizações de &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="permission_sync_summary" msgid="765497944331294275">"Isto pode incluir o acesso ao &lt;strong&gt;microfone&lt;/strong&gt;, &lt;strong&gt;câmara&lt;/strong&gt;, e &lt;strong&gt;localização&lt;/strong&gt;, bem como outras autorizações confidenciais no dispositivo &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;. &lt;br/&gt;&lt;br/&gt;Pode alterar estas autorizações em qualquer altura nas Definições do dispositivo &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;."</string>
-    <string name="vendor_icon_description" msgid="4445875290032225965">"Ícone da app"</string>
-    <string name="vendor_header_button_description" msgid="6566660389500630608">"Botão Mais informações"</string>
+    <string name="vendor_header_button_description" msgid="7994879208461111473">"Mais informações"</string>
     <string name="permission_phone" msgid="2661081078692784919">"Telemóvel"</string>
     <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
     <string name="permission_contacts" msgid="3858319347208004438">"Contactos"</string>
@@ -57,16 +57,18 @@
     <string name="permission_storage" msgid="6831099350839392343">"Fotos e multimédia"</string>
     <string name="permission_notification" msgid="693762568127741203">"Notificações"</string>
     <string name="permission_app_streaming" msgid="6009695219091526422">"Apps"</string>
-    <string name="permission_nearby_device_streaming" msgid="5868108148065023161">"Stream de dispositivo próximo"</string>
+    <string name="permission_nearby_device_streaming" msgid="1023325519477349499">"Streaming"</string>
     <string name="permission_phone_summary" msgid="6684396967861278044">"Pode fazer e gerir chamadas telefónicas"</string>
     <string name="permission_call_logs_summary" msgid="6186103394658755022">"Pode ler e escrever o registo de chamadas do telemóvel"</string>
     <string name="permission_sms_summary" msgid="3508442683678912017">"Pode enviar e ver mensagens SMS"</string>
     <string name="permission_contacts_summary" msgid="675861979475628708">"Pode aceder aos seus contactos"</string>
     <string name="permission_calendar_summary" msgid="6460000922511766226">"Pode aceder ao seu calendário"</string>
-    <string name="permission_microphone_summary" msgid="4241354865859396558">"Não é possível gravar áudio através do microfone"</string>
+    <string name="permission_microphone_summary" msgid="3692091540613093394">"Pode gravar áudio"</string>
     <string name="permission_nearby_devices_summary" msgid="931940524460876655">"Pode encontrar, estabelecer ligação e determinar a posição relativa dos dispositivos próximos"</string>
     <string name="permission_notification_summary" msgid="884075314530071011">"Pode ler todas as notificações, incluindo informações como contactos, mensagens e fotos"</string>
     <string name="permission_app_streaming_summary" msgid="606923325679670624">"Faça stream das apps do telemóvel"</string>
     <string name="permission_storage_summary" msgid="3918240895519506417"></string>
-    <string name="permission_nearby_device_streaming_summary" msgid="5776807830582725074">"Faça stream de conteúdo para um dispositivo próximo"</string>
+    <string name="permission_nearby_device_streaming_summary" msgid="8280824871197081246">"Faça stream de apps e outras funcionalidades do sistema a partir do telemóvel"</string>
+    <string name="device_type" product="default" msgid="8268703872070046263">"telemóvel"</string>
+    <string name="device_type" product="tablet" msgid="5038791954983067774">"tablet"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-pt/strings.xml b/packages/CompanionDeviceManager/res/values-pt/strings.xml
index 29f00ca..26647c8 100644
--- a/packages/CompanionDeviceManager/res/values-pt/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-pt/strings.xml
@@ -17,36 +17,36 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Gerenciador de dispositivos complementar"</string>
-    <string name="confirmation_title" msgid="8024993972587946678">"Permitir que o app &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; acesse o dispositivo &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"Permitir que o app &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; acesse o dispositivo &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"relógio"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Escolha um <xliff:g id="PROFILE_NAME">%1$s</xliff:g> para ser gerenciado pelo app &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="6566922405914995759">"O app <xliff:g id="APP_NAME">%2$s</xliff:g> é necessário para gerenciar o dispositivo <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. Ele poderá sincronizar informações, como o nome de quem está ligando, interagir com suas notificações e acessar as permissões de telefone, SMS, contatos, agenda, registro de chamadas e dispositivos por perto."</string>
-    <string name="summary_watch_single_device" msgid="7443464525873186735">"O app <xliff:g id="APP_NAME">%2$s</xliff:g> é necessário para gerenciar seu dispositivo <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. Ele poderá sincronizar informações, como o nome de quem está ligando, e acessar estas permissões:"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"óculos"</string>
-    <string name="summary_glasses" msgid="3808267780579061241">"O app <xliff:g id="APP_NAME">%2$s</xliff:g> é necessário para gerenciar o dispositivo <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. Ele poderá interagir com suas notificações e acessar suas permissões de telefone, SMS, contatos, microfone e dispositivos por perto."</string>
-    <string name="summary_glasses_single_device" msgid="7051392780285915640">"O app <xliff:g id="APP_NAME">%2$s</xliff:g> é necessário para gerenciar o dispositivo <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. Ele poderá interagir com estas permissões:"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"Escolha um dispositivo para ser gerenciado pelo app &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Escolha um <xliff:g id="PROFILE_NAME">%1$s</xliff:g> para configurar"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"O app poderá sincronizar informações, como o nome de quem está ligando, e acessar estas permissões no seu <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
+    <string name="confirmation_title_glasses" msgid="8288346850537727333">"Permitir que o app &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; gerencie o dispositivo &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"dispositivo"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"O app poderá acessar estas permissões no seu <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Permitir que o app &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; acesse estas informações do smartphone"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Serviços entre dispositivos"</string>
-    <string name="helper_summary_app_streaming" msgid="5977509499890099">"O app <xliff:g id="APP_NAME">%1$s</xliff:g> está pedindo permissão em nome do seu <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> para fazer streaming de apps entre seus dispositivos"</string>
+    <string name="helper_summary_app_streaming" msgid="2396773196949578425">"O app <xliff:g id="APP_NAME">%1$s</xliff:g> está pedindo permissão em nome do seu dispositivo <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> para fazer streaming de apps entre seus dispositivos"</string>
     <string name="title_automotive_projection" msgid="3296005598978412847"></string>
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"Autorizar que &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; acesse estas informações do smartphone"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Google Play Services"</string>
-    <string name="helper_summary_computer" msgid="9050724687678157852">"O app <xliff:g id="APP_NAME">%1$s</xliff:g> está pedindo permissão em nome do seu <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> para acessar fotos, mídia e notificações do smartphone."</string>
-    <string name="title_nearby_device_streaming" msgid="179278282547719200">"Permitir que &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; realize esta ação pelo smartphone"</string>
-    <string name="helper_title_nearby_device_streaming" msgid="6124438217620593669">"Serviços entre dispositivos"</string>
-    <string name="helper_summary_nearby_device_streaming" msgid="5538329403511524333">"<xliff:g id="APP_NAME">%1$s</xliff:g> está pedindo permissão em nome de <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> para fazer streaming de conteúdo para dispositivos por perto"</string>
+    <string name="helper_summary_computer" msgid="8774832742608187072">"O app <xliff:g id="APP_NAME">%1$s</xliff:g> está pedindo permissão em nome do seu dispositivo <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> para acessar fotos, mídia e notificações do smartphone"</string>
+    <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Permitir que o dispositivo &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; realize esta ação?"</string>
+    <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> está pedindo permissão em nome do seu dispositivo <xliff:g id="DEVICE_NAME">%2$s</xliff:g> para fazer streaming de apps e de outros recursos do sistema para dispositivos por perto"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"dispositivo"</string>
-    <string name="summary_generic_single_device" msgid="4735072202474939111">"O app poderá sincronizar informações, como o nome de quem está ligando, entre seu smartphone e o dispositivo <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string>
-    <string name="summary_generic" msgid="4988130802522924650">"O app poderá sincronizar informações, como o nome de quem está ligando, entre seu smartphone e o dispositivo escolhido."</string>
+    <string name="summary_generic" msgid="1761976003668044801">"O app poderá sincronizar informações, como o nome de quem está ligando, entre seu smartphone e o dispositivo escolhido"</string>
     <string name="consent_yes" msgid="8344487259618762872">"Permitir"</string>
     <string name="consent_no" msgid="2640796915611404382">"Não permitir"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Cancelar"</string>
     <string name="consent_back" msgid="2560683030046918882">"Voltar"</string>
+    <string name="permission_expand" msgid="893185038020887411">"Abrir <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
+    <string name="permission_collapse" msgid="3320833884220844084">"Fechar <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Dar aos apps no dispositivo &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; as mesmas permissões do dispositivo &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="permission_sync_summary" msgid="765497944331294275">"Isso pode incluir acesso ao &lt;strong&gt;Microfone&lt;/strong&gt;, à &lt;strong&gt;Câmera&lt;/strong&gt; e à &lt;strong&gt;Localização&lt;/strong&gt;, além de outras permissões sensíveis no dispositivo &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;. &lt;br/&gt;&lt;br/&gt;Você pode mudar essas permissões a qualquer momento nas Configurações do dispositivo &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;."</string>
-    <string name="vendor_icon_description" msgid="4445875290032225965">"Ícone do app"</string>
-    <string name="vendor_header_button_description" msgid="6566660389500630608">"Botão \"Mais informações\""</string>
+    <string name="vendor_header_button_description" msgid="7994879208461111473">"Mais informações"</string>
     <string name="permission_phone" msgid="2661081078692784919">"Smartphone"</string>
     <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
     <string name="permission_contacts" msgid="3858319347208004438">"Contatos"</string>
@@ -57,16 +57,18 @@
     <string name="permission_storage" msgid="6831099350839392343">"Fotos e mídia"</string>
     <string name="permission_notification" msgid="693762568127741203">"Notificações"</string>
     <string name="permission_app_streaming" msgid="6009695219091526422">"Apps"</string>
-    <string name="permission_nearby_device_streaming" msgid="5868108148065023161">"Streaming em disp. por perto"</string>
+    <string name="permission_nearby_device_streaming" msgid="1023325519477349499">"Streaming"</string>
     <string name="permission_phone_summary" msgid="6684396967861278044">"Pode fazer e gerenciar ligações"</string>
     <string name="permission_call_logs_summary" msgid="6186103394658755022">"Pode ler e gravar o registro de chamadas"</string>
     <string name="permission_sms_summary" msgid="3508442683678912017">"Pode enviar e acessar mensagens SMS"</string>
     <string name="permission_contacts_summary" msgid="675861979475628708">"Pode acessar seus contatos"</string>
     <string name="permission_calendar_summary" msgid="6460000922511766226">"Pode acessar sua agenda"</string>
-    <string name="permission_microphone_summary" msgid="4241354865859396558">"Pode gravar áudio usando o microfone"</string>
+    <string name="permission_microphone_summary" msgid="3692091540613093394">"Pode gravar áudio"</string>
     <string name="permission_nearby_devices_summary" msgid="931940524460876655">"Pode encontrar, determinar o posicionamento relativo e se conectar a dispositivos por perto"</string>
     <string name="permission_notification_summary" msgid="884075314530071011">"Pode ler todas as notificações, incluindo informações como contatos, mensagens e fotos"</string>
     <string name="permission_app_streaming_summary" msgid="606923325679670624">"Fazer transmissão dos apps no seu smartphone"</string>
     <string name="permission_storage_summary" msgid="3918240895519506417"></string>
-    <string name="permission_nearby_device_streaming_summary" msgid="5776807830582725074">"Fazer streaming de conteúdo para dispositivos por perto"</string>
+    <string name="permission_nearby_device_streaming_summary" msgid="8280824871197081246">"Fazer streaming de apps e outros recursos do sistema pelo smartphone"</string>
+    <string name="device_type" product="default" msgid="8268703872070046263">"smartphone"</string>
+    <string name="device_type" product="tablet" msgid="5038791954983067774">"tablet"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-ro/strings.xml b/packages/CompanionDeviceManager/res/values-ro/strings.xml
index e3a86c1..84182e8 100644
--- a/packages/CompanionDeviceManager/res/values-ro/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ro/strings.xml
@@ -17,43 +17,36 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Manager de dispozitiv Companion"</string>
-    <!-- no translation found for confirmation_title (8024993972587946678) -->
-    <skip />
+    <string name="confirmation_title" msgid="2244241995958340998">"Permiți ca &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; să acceseze dispozitivul &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"ceas"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Alege un profil <xliff:g id="PROFILE_NAME">%1$s</xliff:g> pe care să îl gestioneze &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <!-- no translation found for summary_watch (6566922405914995759) -->
-    <skip />
-    <!-- no translation found for summary_watch_single_device (7443464525873186735) -->
-    <skip />
-    <string name="profile_name_glasses" msgid="8488394059007275998">"ochelari"</string>
-    <!-- no translation found for summary_glasses (3808267780579061241) -->
-    <skip />
-    <!-- no translation found for summary_glasses_single_device (7051392780285915640) -->
-    <skip />
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"Alege un dispozitiv pe care să îl gestioneze &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Alege un <xliff:g id="PROFILE_NAME">%1$s</xliff:g> de configurat"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Aplicația va putea să sincronizeze informații, cum ar fi numele unui apelant, și să acceseze aceste permisiuni pe <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
+    <string name="confirmation_title_glasses" msgid="8288346850537727333">"Permiți ca &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; să gestioneze &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"dispozitiv"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Aplicația va putea să acceseze următoarele permisiuni pe <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Permite ca &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; să acceseze aceste informații de pe telefon"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Servicii pe mai multe dispozitive"</string>
-    <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> solicită permisiunea pentru <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> de a reda în stream aplicații între dispozitivele tale"</string>
+    <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g> solicită permisiunea pentru <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> de a reda în stream aplicații între dispozitivele tale"</string>
     <string name="title_automotive_projection" msgid="3296005598978412847"></string>
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"Permite ca &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; să acceseze aceste informații de pe telefon"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Servicii Google Play"</string>
-    <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> solicită permisiunea pentru <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> de a accesa fotografiile, conținutul media și notificările de pe telefon"</string>
-    <string name="title_nearby_device_streaming" msgid="179278282547719200">"Permite ca &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; să realizeze această acțiune de pe telefon"</string>
-    <string name="helper_title_nearby_device_streaming" msgid="6124438217620593669">"Servicii pe mai multe dispozitive"</string>
-    <string name="helper_summary_nearby_device_streaming" msgid="5538329403511524333">"<xliff:g id="APP_NAME">%1$s</xliff:g> solicită permisiunea pentru <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> de a reda în stream conținut pe dispozitivele din apropiere"</string>
+    <string name="helper_summary_computer" msgid="8774832742608187072">"<xliff:g id="APP_NAME">%1$s</xliff:g> solicită permisiunea pentru <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> de a accesa fotografiile, conținutul media și notificările de pe telefon"</string>
+    <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Permiți ca &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; să realizeze această acțiune?"</string>
+    <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> solicită permisiunea pentru <xliff:g id="DEVICE_NAME">%2$s</xliff:g> de a reda în stream conținut din aplicații și alte funcții de sistem pe dispozitivele din apropiere"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"dispozitiv"</string>
-    <!-- no translation found for summary_generic_single_device (4735072202474939111) -->
-    <skip />
-    <!-- no translation found for summary_generic (4988130802522924650) -->
-    <skip />
+    <string name="summary_generic" msgid="1761976003668044801">"Aplicația va putea să sincronizeze informații, cum ar fi numele unui apelant, între telefonul tău și dispozitivul ales"</string>
     <string name="consent_yes" msgid="8344487259618762872">"Permite"</string>
     <string name="consent_no" msgid="2640796915611404382">"Nu permite"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Anulează"</string>
     <string name="consent_back" msgid="2560683030046918882">"Înapoi"</string>
+    <string name="permission_expand" msgid="893185038020887411">"Extinde <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
+    <string name="permission_collapse" msgid="3320833884220844084">"Restrânge <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Acorzi aplicațiilor de pe &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; aceleași permisiuni ca pe &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="permission_sync_summary" msgid="765497944331294275">"Aici pot fi incluse accesul la &lt;strong&gt;microfon&lt;/strong&gt;, la &lt;strong&gt;camera foto&lt;/strong&gt;, la &lt;strong&gt;locație&lt;/strong&gt; și alte permisiuni de accesare a informațiilor sensibile de pe &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;. &lt;br/&gt;&lt;br/&gt;Poți modifica oricând aceste permisiuni din Setările de pe &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;."</string>
-    <string name="vendor_icon_description" msgid="4445875290032225965">"Pictograma aplicației"</string>
-    <string name="vendor_header_button_description" msgid="6566660389500630608">"Butonul Mai multe informații"</string>
+    <string name="vendor_header_button_description" msgid="7994879208461111473">"Mai multe informații"</string>
     <string name="permission_phone" msgid="2661081078692784919">"Telefon"</string>
     <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
     <string name="permission_contacts" msgid="3858319347208004438">"Agendă"</string>
@@ -64,16 +57,18 @@
     <string name="permission_storage" msgid="6831099350839392343">"Fotografii și media"</string>
     <string name="permission_notification" msgid="693762568127741203">"Notificări"</string>
     <string name="permission_app_streaming" msgid="6009695219091526422">"Aplicații"</string>
-    <string name="permission_nearby_device_streaming" msgid="5868108148065023161">"Streaming pe dispozitivele din apropiere"</string>
-    <string name="permission_phone_summary" msgid="6684396967861278044">"Poate să facă și să gestioneze apeluri telefonice"</string>
+    <string name="permission_nearby_device_streaming" msgid="1023325519477349499">"Streaming"</string>
+    <string name="permission_phone_summary" msgid="6684396967861278044">"Poate să dea și să gestioneze apeluri telefonice"</string>
     <string name="permission_call_logs_summary" msgid="6186103394658755022">"Poate să citească și să scrie în jurnalul de apeluri telefonice"</string>
     <string name="permission_sms_summary" msgid="3508442683678912017">"Poate să trimită și să vadă mesaje SMS"</string>
     <string name="permission_contacts_summary" msgid="675861979475628708">"Poate accesa agenda"</string>
     <string name="permission_calendar_summary" msgid="6460000922511766226">"Poate accesa calendarul"</string>
-    <string name="permission_microphone_summary" msgid="4241354865859396558">"Poate înregistra conținut audio folosind microfonul"</string>
+    <string name="permission_microphone_summary" msgid="3692091540613093394">"Poate să înregistreze conținut audio"</string>
     <string name="permission_nearby_devices_summary" msgid="931940524460876655">"Poate să găsească, să se conecteze la și să determine poziția relativă a dispozitivelor apropiate"</string>
     <string name="permission_notification_summary" msgid="884075314530071011">"Poate să citească toate notificările, inclusiv informații cum ar fi agenda, mesajele și fotografiile"</string>
     <string name="permission_app_streaming_summary" msgid="606923325679670624">"Să redea în stream aplicațiile telefonului"</string>
     <string name="permission_storage_summary" msgid="3918240895519506417"></string>
-    <string name="permission_nearby_device_streaming_summary" msgid="5776807830582725074">"Redă în stream conținut pe un dispozitiv din apropiere"</string>
+    <string name="permission_nearby_device_streaming_summary" msgid="8280824871197081246">"Redă în stream conținut din aplicații și alte funcții de sistem de pe telefon"</string>
+    <string name="device_type" product="default" msgid="8268703872070046263">"telefon"</string>
+    <string name="device_type" product="tablet" msgid="5038791954983067774">"tabletă"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-ru/strings.xml b/packages/CompanionDeviceManager/res/values-ru/strings.xml
index 832bfe9..911bddc 100644
--- a/packages/CompanionDeviceManager/res/values-ru/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ru/strings.xml
@@ -17,36 +17,36 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Управление подключенными устройствами"</string>
-    <string name="confirmation_title" msgid="8024993972587946678">"Разрешите приложению &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; доступ к устройству &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"Предоставить приложению &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; доступ к устройству &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"часы"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Выберите устройство (<xliff:g id="PROFILE_NAME">%1$s</xliff:g>), которым будет управлять приложение &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="6566922405914995759">"Это приложение необходимо для управления устройством \"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>\". Приложение \"<xliff:g id="APP_NAME">%2$s</xliff:g>\" сможет синхронизировать данные, например журнала звонков, а также получит доступ к уведомлениям и разрешения \"Телефон\", \"SMS\", \"Контакты\", \"Микрофон\" и \"Устройства поблизости\"."</string>
-    <string name="summary_watch_single_device" msgid="7443464525873186735">"Это приложение необходимо для управления устройством \"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>\". Приложение \"<xliff:g id="APP_NAME">%2$s</xliff:g>\" сможет синхронизировать данные, например журнала звонков, и получит следующие разрешения:"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"Очки"</string>
-    <string name="summary_glasses" msgid="3808267780579061241">"Это приложение необходимо для управления устройством \"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>\". Приложение \"<xliff:g id="APP_NAME">%2$s</xliff:g>\" получит доступ к уведомлениям, а также разрешения \"Телефон\", SMS, \"Контакты\", \"Микрофон\" и \"Устройства поблизости\"."</string>
-    <string name="summary_glasses_single_device" msgid="7051392780285915640">"Это приложение необходимо для управления устройством \"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>\". Приложение \"<xliff:g id="APP_NAME">%2$s</xliff:g>\" получит следующие разрешения:"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"Выберите устройство, которым будет управлять приложение &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Выберите <xliff:g id="PROFILE_NAME">%1$s</xliff:g> для настройки"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Это приложение сможет синхронизировать данные, например имена вызывающих абонентов, а также получит указанные разрешения на <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string>
+    <string name="confirmation_title_glasses" msgid="8288346850537727333">"Разрешить приложению &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; управлять устройством &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"устройстве"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Это приложение получит указанные разрешения на <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Разрешите приложению &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; получать эту информацию с вашего телефона"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Сервисы стриминга приложений"</string>
-    <string name="helper_summary_app_streaming" msgid="5977509499890099">"Приложение \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" запрашивает разрешение от имени вашего устройства <xliff:g id="DEVICE_TYPE">%2$s</xliff:g>, чтобы транслировать приложения между вашими устройствами."</string>
+    <string name="helper_summary_app_streaming" msgid="2396773196949578425">"Приложение \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" запрашивает разрешение от имени вашего устройства <xliff:g id="DISPLAY_NAME">%2$s</xliff:g>, чтобы транслировать приложения между устройствами."</string>
     <string name="title_automotive_projection" msgid="3296005598978412847"></string>
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"Разрешите приложению &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; получать эту информацию с вашего телефона"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Сервисы Google Play"</string>
-    <string name="helper_summary_computer" msgid="9050724687678157852">"Приложение \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" запрашивает разрешение от имени вашего устройства <xliff:g id="DEVICE_TYPE">%2$s</xliff:g>, чтобы получить доступ к фотографиям, медиаконтенту и уведомлениям на телефоне."</string>
-    <string name="title_nearby_device_streaming" msgid="179278282547719200">"Разрешить приложению &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; выполнять это действие с вашего телефона"</string>
-    <string name="helper_title_nearby_device_streaming" msgid="6124438217620593669">"Сервисы взаимодействия устр-в"</string>
-    <string name="helper_summary_nearby_device_streaming" msgid="5538329403511524333">"Приложение \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" запрашивает разрешение на трансляцию контента на устройства поблизости от имени вашего устройства \"<xliff:g id="DEVICE_TYPE">%2$s</xliff:g>\"."</string>
+    <string name="helper_summary_computer" msgid="8774832742608187072">"Приложение \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" запрашивает разрешение от имени вашего устройства <xliff:g id="DISPLAY_NAME">%2$s</xliff:g>, чтобы получить доступ к фотографиям, медиаконтенту и уведомлениям на телефоне."</string>
+    <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Разрешить приложению &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; выполнять это действие?"</string>
+    <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"Приложение \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" от имени вашего устройства \"<xliff:g id="DEVICE_NAME">%2$s</xliff:g>\" запрашивает разрешение транслировать приложения и системные функции на устройства поблизости."</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"устройство"</string>
-    <string name="summary_generic_single_device" msgid="4735072202474939111">"Приложение сможет синхронизировать информацию между телефоном и устройством \"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>\", например данные из журнала звонков."</string>
-    <string name="summary_generic" msgid="4988130802522924650">"Приложение сможет синхронизировать информацию между телефоном и выбранным устройством, например данные из журнала звонков."</string>
+    <string name="summary_generic" msgid="1761976003668044801">"Приложение сможет синхронизировать информацию между телефоном и выбранным устройством, например данные из журнала звонков."</string>
     <string name="consent_yes" msgid="8344487259618762872">"Разрешить"</string>
     <string name="consent_no" msgid="2640796915611404382">"Запретить"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Отмена"</string>
     <string name="consent_back" msgid="2560683030046918882">"Назад"</string>
+    <string name="permission_expand" msgid="893185038020887411">"Разворачивать список \"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>\"."</string>
+    <string name="permission_collapse" msgid="3320833884220844084">"Сворачивать список \"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>\"."</string>
     <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Предоставить приложениям на устройстве &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; те же разрешения, что на устройстве &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="permission_sync_summary" msgid="765497944331294275">"У приложений может появиться доступ к &lt;strong&gt;микрофону&lt;/strong&gt;, &lt;strong&gt;камере&lt;/strong&gt;, &lt;strong&gt;местоположению&lt;/strong&gt; и другой конфиденциальной информации на устройстве &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;. &lt;br/&gt;&lt;br/&gt;Вы можете в любое время изменить разрешения в настройках на устройстве &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;."</string>
-    <string name="vendor_icon_description" msgid="4445875290032225965">"Значок приложения"</string>
-    <string name="vendor_header_button_description" msgid="6566660389500630608">"Кнопка информации"</string>
+    <string name="vendor_header_button_description" msgid="7994879208461111473">"Дополнительная информация"</string>
     <string name="permission_phone" msgid="2661081078692784919">"Телефон"</string>
     <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
     <string name="permission_contacts" msgid="3858319347208004438">"Контакты"</string>
@@ -57,16 +57,18 @@
     <string name="permission_storage" msgid="6831099350839392343">"Фотографии и медиафайлы"</string>
     <string name="permission_notification" msgid="693762568127741203">"Уведомления"</string>
     <string name="permission_app_streaming" msgid="6009695219091526422">"Приложения"</string>
-    <string name="permission_nearby_device_streaming" msgid="5868108148065023161">"Трансляция на устройства рядом"</string>
+    <string name="permission_nearby_device_streaming" msgid="1023325519477349499">"Потоковая передача"</string>
     <string name="permission_phone_summary" msgid="6684396967861278044">"Приложение сможет совершать вызовы и управлять ими."</string>
     <string name="permission_call_logs_summary" msgid="6186103394658755022">"Приложение сможет читать список вызовов и создавать записи в этом списке."</string>
     <string name="permission_sms_summary" msgid="3508442683678912017">"Приложение сможет отправлять и просматривать SMS."</string>
     <string name="permission_contacts_summary" msgid="675861979475628708">"Приложение сможет получать доступ к вашему списку контактов."</string>
     <string name="permission_calendar_summary" msgid="6460000922511766226">"Приложение сможет получать доступ к вашему календарю."</string>
-    <string name="permission_microphone_summary" msgid="4241354865859396558">"Можно записывать аудио с помощью микрофона"</string>
+    <string name="permission_microphone_summary" msgid="3692091540613093394">"Может записывать аудио"</string>
     <string name="permission_nearby_devices_summary" msgid="931940524460876655">"Приложение сможет находить устройства поблизости, подключаться к ним и определять их относительное местоположение."</string>
     <string name="permission_notification_summary" msgid="884075314530071011">"Чтение всех уведомлений, в том числе сведений о контактах, сообщениях и фотографиях."</string>
     <string name="permission_app_streaming_summary" msgid="606923325679670624">"Трансляция приложений с телефона."</string>
     <string name="permission_storage_summary" msgid="3918240895519506417"></string>
-    <string name="permission_nearby_device_streaming_summary" msgid="5776807830582725074">"Можно транслировать контент на устройства поблизости"</string>
+    <string name="permission_nearby_device_streaming_summary" msgid="8280824871197081246">"Трансляция приложений и системных функций с телефона"</string>
+    <string name="device_type" product="default" msgid="8268703872070046263">"телефоне"</string>
+    <string name="device_type" product="tablet" msgid="5038791954983067774">"планшете"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-si/strings.xml b/packages/CompanionDeviceManager/res/values-si/strings.xml
index fbe4823..daa2058 100644
--- a/packages/CompanionDeviceManager/res/values-si/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-si/strings.xml
@@ -17,43 +17,36 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"සහායක උපාංග කළමනාකරු"</string>
-    <!-- no translation found for confirmation_title (8024993972587946678) -->
-    <skip />
+    <string name="confirmation_title" msgid="2244241995958340998">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; යෙදුමට &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; වෙත ප්‍රවේශ වීමට ඉඩ දෙන්න ද?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"ඔරලෝසුව"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"&lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; මගින් කළමනාකරණය කරනු ලැබීමට <xliff:g id="PROFILE_NAME">%1$s</xliff:g>ක් තෝරන්න"</string>
-    <!-- no translation found for summary_watch (6566922405914995759) -->
-    <skip />
-    <!-- no translation found for summary_watch_single_device (7443464525873186735) -->
-    <skip />
-    <string name="profile_name_glasses" msgid="8488394059007275998">"කණ්ණාඩි"</string>
-    <!-- no translation found for summary_glasses (3808267780579061241) -->
-    <skip />
-    <!-- no translation found for summary_glasses_single_device (7051392780285915640) -->
-    <skip />
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; විසින් කළමනා කරනු ලැබීමට උපාංගයක් තෝරන්න"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"සැකසීමට <xliff:g id="PROFILE_NAME">%1$s</xliff:g>ක් තෝරන්න"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"මෙම යෙදුමට අමතන කෙනෙකුගේ නම වැනි, තොරතුරු සමමුහූර්ත කිරීමට, සහ ඔබේ <xliff:g id="DEVICE_NAME">%1$s</xliff:g> මත මෙම අවසර වෙත ප්‍රවේශ වීමට ඉඩ දෙනු ලැබේ"</string>
+    <string name="confirmation_title_glasses" msgid="8288346850537727333">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; හට &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; කළමනා කිරීමට ඉඩ දෙන්න ද?"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"උපාංගය"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"මෙම යෙදුමට ඔබේ <xliff:g id="DEVICE_NAME">%1$s</xliff:g> මත මෙම අවසර වෙත ප්‍රවේශ වීමට ඉඩ දෙනු ලැබේ"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; හට ඔබගේ දුරකථනයෙන් මෙම තොරතුරුවලට ප්‍රවේශ වීමට ඉඩ දෙන්න"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"හරස්-උපාංග සේවා"</string>
-    <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> ඔබගේ <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> වෙනුවෙන් ඔබගේ උපාංග අතර යෙදුම් ප්‍රවාහ කිරීමට අවසරය ඉල්ලමින් සිටියි"</string>
+    <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g> ඔබේ <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> වෙනුවෙන් ඔබේ උපාංග අතර යෙදුම් ප්‍රවාහ කිරීමට අවසරය ඉල්ලමින් සිටියි"</string>
     <string name="title_automotive_projection" msgid="3296005598978412847"></string>
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; හට ඔබගේ දුරකථනයෙන් මෙම තොරතුරුවලට ප්‍රවේශ වීමට ඉඩ දෙන්න"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Google Play සේවා"</string>
-    <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> ඔබගේ <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> වෙනුවෙන් ඔබගේ දුරකථනයෙහි ඡායාරූප, මාධ්‍ය සහ දැනුම්දීම් වෙත ප්‍රවේශ වීමට අවසරය ඉල්ලමින් සිටියි"</string>
-    <string name="title_nearby_device_streaming" msgid="179278282547719200">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; හට ඔබේ දුරකථනයෙන් මෙම ක්‍රියාව සිදු කිරීමට ඉඩ දෙන්න"</string>
-    <string name="helper_title_nearby_device_streaming" msgid="6124438217620593669">"හරස්-උපාංග සේවා"</string>
-    <string name="helper_summary_nearby_device_streaming" msgid="5538329403511524333">"<xliff:g id="APP_NAME">%1$s</xliff:g> අවට උපාංග වෙත අන්තර්ගතය ප්‍රවාහ කිරීමට ඔබේ <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> වෙනුවෙන් අවසර ඉල්ලයි"</string>
+    <string name="helper_summary_computer" msgid="8774832742608187072">"<xliff:g id="APP_NAME">%1$s</xliff:g> ඔබේ <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> වෙනුවෙන් ඔබේ දුරකථනයේ ඡායාරූප, මාධ්‍ය, සහ දැනුම්දීම් වෙත ප්‍රවේශ වීමට අවසරය ඉල්ලමින් සිටියි"</string>
+    <string name="title_nearby_device_streaming" msgid="7269956847378799794">"මෙම ක්‍රියාව කිරීමට &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; හට ඉඩ දෙන්න ද?"</string>
+    <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> ඔබේ <xliff:g id="DEVICE_NAME">%2$s</xliff:g> වෙනුවෙන් යෙදුම් සහ අනෙකුත් පද්ධති විශේෂාංග අවට උපාංග වෙත ප්‍රවාහ කිරීමට අවසර ඉල්ලයි"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"උපාංගය"</string>
-    <!-- no translation found for summary_generic_single_device (4735072202474939111) -->
-    <skip />
-    <!-- no translation found for summary_generic (4988130802522924650) -->
-    <skip />
+    <string name="summary_generic" msgid="1761976003668044801">"මෙම යෙදුමට ඔබේ දුරකථනය සහ තෝරා ගත් උපාංගය අතර, අමතන කෙනෙකුගේ නම වැනි, තතු සමමුහුර්ත කිරීමට හැකි වනු ඇත"</string>
     <string name="consent_yes" msgid="8344487259618762872">"ඉඩ දෙන්න"</string>
     <string name="consent_no" msgid="2640796915611404382">"ඉඩ නොදෙන්න"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"අවලංගු කරන්න"</string>
     <string name="consent_back" msgid="2560683030046918882">"ආපසු"</string>
+    <string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> විදහන්න"</string>
+    <string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> හකුළන්න"</string>
     <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"&lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; the හි යෙදුම්වලට &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; හි අවසරම දෙන්නද?"</string>
     <string name="permission_sync_summary" msgid="765497944331294275">"මෙයට &lt;strong&gt;මයික්‍රොෆෝනය&lt;/strong&gt;, &lt;strong&gt;කැමරාව&lt;/strong&gt;, සහ &lt;strong&gt;ස්ථාන ප්‍රවේශය&lt;/strong&gt;, සහ &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt; මත අනෙකුත් සංවේදී අවසර ඇතුළත් විය හැක. &lt;br/&gt;&lt;br/&gt;ඔබට &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt; හි ඔබේ සැකසීම් තුළ ඕනෑම වේලාවක මෙම අවසර වෙනස් කළ හැක."</string>
-    <string name="vendor_icon_description" msgid="4445875290032225965">"යෙදුම් නිරූපකය"</string>
-    <string name="vendor_header_button_description" msgid="6566660389500630608">"වැඩිදුර තොරතුරු බොත්තම"</string>
+    <string name="vendor_header_button_description" msgid="7994879208461111473">"තව තොරතුරු"</string>
     <string name="permission_phone" msgid="2661081078692784919">"දුරකථනය"</string>
     <string name="permission_sms" msgid="6337141296535774786">"කෙටිපණිවුඩය"</string>
     <string name="permission_contacts" msgid="3858319347208004438">"සම්බන්‍ධතා"</string>
@@ -64,16 +57,18 @@
     <string name="permission_storage" msgid="6831099350839392343">"ඡායාරූප සහ මාධ්‍ය"</string>
     <string name="permission_notification" msgid="693762568127741203">"දැනුම්දීම්"</string>
     <string name="permission_app_streaming" msgid="6009695219091526422">"යෙදුම්"</string>
-    <string name="permission_nearby_device_streaming" msgid="5868108148065023161">"ආසන්න උපාංග ප්‍රවාහය"</string>
+    <string name="permission_nearby_device_streaming" msgid="1023325519477349499">"ප්‍රවාහ කිරීම"</string>
     <string name="permission_phone_summary" msgid="6684396967861278044">"දුරකථන ඇමතුම් ගැනීමට සහ කළමනාකරණය කිරීමට හැක"</string>
     <string name="permission_call_logs_summary" msgid="6186103394658755022">"දුරකථන ඇමතුම් ලොගය කියවීමට සහ ලිවීමට හැක"</string>
     <string name="permission_sms_summary" msgid="3508442683678912017">"SMS පණිවිඩ යැවීමට සහ බැලීමට හැක"</string>
     <string name="permission_contacts_summary" msgid="675861979475628708">"ඔබේ සම්බන්ධතා වෙත ප්‍රවේශ විය හැක"</string>
     <string name="permission_calendar_summary" msgid="6460000922511766226">"ඔබේ දින දර්ශනයට ප්‍රවේශ විය හැක"</string>
-    <string name="permission_microphone_summary" msgid="4241354865859396558">"මයික්‍රෆෝනය භාවිතයෙන් ශ්‍රව්‍ය පටිගත කළ හැක"</string>
+    <string name="permission_microphone_summary" msgid="3692091540613093394">"ශ්‍රව්‍ය පටිගත කළ හැක"</string>
     <string name="permission_nearby_devices_summary" msgid="931940524460876655">"අවට උපාංගවල සාපේක්ෂ පිහිටීම සොයා ගැනීමට, සම්බන්ධ කිරීමට, සහ තීරණය කිරීමට හැක"</string>
     <string name="permission_notification_summary" msgid="884075314530071011">"සම්බන්ධතා, පණිවිඩ සහ ඡායාරූප වැනි තොරතුරු ඇතුළුව සියලු දැනුම්දීම් කියවිය හැකිය"</string>
     <string name="permission_app_streaming_summary" msgid="606923325679670624">"ඔබේ දුරකථනයේ යෙදුම් ප්‍රවාහ කරන්න"</string>
     <string name="permission_storage_summary" msgid="3918240895519506417"></string>
-    <string name="permission_nearby_device_streaming_summary" msgid="5776807830582725074">"අවට උපාංගයකට අන්තර්ගතය ප්‍රවාහ කරන්න"</string>
+    <string name="permission_nearby_device_streaming_summary" msgid="8280824871197081246">"ඔබේ දුරකථනයෙන් යෙදුම් සහ අනෙකුත් පද්ධති විශේෂාංග ප්‍රවාහ කරන්න"</string>
+    <string name="device_type" product="default" msgid="8268703872070046263">"දුරකථනය"</string>
+    <string name="device_type" product="tablet" msgid="5038791954983067774">"ටැබ්ලටය"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-sk/strings.xml b/packages/CompanionDeviceManager/res/values-sk/strings.xml
index 805c2c8..929b56c 100644
--- a/packages/CompanionDeviceManager/res/values-sk/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-sk/strings.xml
@@ -17,36 +17,36 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Správca sprievodných zariadení"</string>
-    <string name="confirmation_title" msgid="8024993972587946678">"Povoľte aplikácii &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; prístup k zariadeniu &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"Chcete povoliť aplikácii &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; prístup k zariadeniu &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"hodinky"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Vyberte profil <xliff:g id="PROFILE_NAME">%1$s</xliff:g>, ktorý bude spravovať aplikácia &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="6566922405914995759">"Daná aplikácia sa vyžaduje na správu zariadenia <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> bude môcť synchronizovať informácie, napríklad meno volajúceho, interagovať s vašimi upozorneniami a získavať prístup k povoleniam telefónu, SMS, kontaktov, kalendára, zoznamu hovorov a zariadení v okolí."</string>
-    <string name="summary_watch_single_device" msgid="7443464525873186735">"Daná aplikácia sa vyžaduje na správu zariadenia <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> bude môcť synchronizovať informácie, napríklad meno volajúceho, a získavať prístup k týmto povoleniam:"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"okuliare"</string>
-    <string name="summary_glasses" msgid="3808267780579061241">"Táto aplikácia sa vyžaduje na správu zariadenia <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> bude môcť interagovať s vašimi upozorneniami a získa prístup k povoleniam pre telefón, SMS, kontakty, mikrofón a zariadenia v okolí."</string>
-    <string name="summary_glasses_single_device" msgid="7051392780285915640">"Daná aplikácia sa vyžaduje na správu zariadenia <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> bude môcť interagovať s týmito povoleniami:"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"Vyberte zariadenie, ktoré bude spravovať aplikácia &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Vyberte profil <xliff:g id="PROFILE_NAME">%1$s</xliff:g>, ktorý nastavíte"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Táto aplikácia bude môcť synchronizovať informácie, napríklad meno volajúceho, a získavať prístup k týmto povoleniam v zariadení <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
+    <string name="confirmation_title_glasses" msgid="8288346850537727333">"Chcete povoliť aplikácii &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; spravovať zariadenie &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"zariadenie"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Táto aplikácia bude mať prístup k týmto povoleniam v zariadení <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Povoľte aplikácii &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; prístup k týmto informáciám z vášho telefónu"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Služby pre viacero zariadení"</string>
-    <string name="helper_summary_app_streaming" msgid="5977509499890099">"Aplikácia <xliff:g id="APP_NAME">%1$s</xliff:g> vyžaduje povolenie na streamovanie aplikácií medzi vašimi zariadeniami v mene tohto zariadenia (<xliff:g id="DEVICE_TYPE">%2$s</xliff:g>)"</string>
+    <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g> vyžaduje pre zariadenie <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> povolenie streamovať aplikácie medzi vašimi zariadeniami."</string>
     <string name="title_automotive_projection" msgid="3296005598978412847"></string>
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"Povoľte aplikácii &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; prístup k týmto informáciám z vášho telefónu"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Služby Google Play"</string>
-    <string name="helper_summary_computer" msgid="9050724687678157852">"Aplikácia <xliff:g id="APP_NAME">%1$s</xliff:g> vyžaduje povolenie na prístup k fotkám, médiám a upozorneniam vášho telefónu v mene tohto zariadenia (<xliff:g id="DEVICE_TYPE">%2$s</xliff:g>)"</string>
-    <string name="title_nearby_device_streaming" msgid="179278282547719200">"Povoľte aplikácii &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; vykonať túto akciu z vášho telefónu"</string>
-    <string name="helper_title_nearby_device_streaming" msgid="6124438217620593669">"Služby pre viacero zariadení"</string>
-    <string name="helper_summary_nearby_device_streaming" msgid="5538329403511524333">"<xliff:g id="APP_NAME">%1$s</xliff:g> vyžaduje pre zariadenie <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> povolenie streamovať obsah do zariadení v okolí"</string>
+    <string name="helper_summary_computer" msgid="8774832742608187072">"<xliff:g id="APP_NAME">%1$s</xliff:g> vyžaduje pre zariadenie <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> povolenie na prístup k fotkám, médiám a upozorneniam vášho telefónu"</string>
+    <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Chcete povoliť zariadeniu &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; vykonať túto akciu?"</string>
+    <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> vyžaduje pre zariadenie <xliff:g id="DEVICE_NAME">%2$s</xliff:g> povolenie streamovať aplikácie a ďalšie systémové funkcie do zariadení v okolí"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"zariadenie"</string>
-    <string name="summary_generic_single_device" msgid="4735072202474939111">"Táto aplikácia bude môcť synchronizovať informácie, napríklad meno volajúceho, medzi telefónom a zariadením <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string>
-    <string name="summary_generic" msgid="4988130802522924650">"Táto aplikácia bude môcť synchronizovať informácie, napríklad meno volajúceho, medzi telefónom a vybraným zariadením."</string>
+    <string name="summary_generic" msgid="1761976003668044801">"Táto aplikácia bude môcť synchronizovať informácie, napríklad meno volajúceho, medzi telefónom a vybraným zariadením"</string>
     <string name="consent_yes" msgid="8344487259618762872">"Povoliť"</string>
     <string name="consent_no" msgid="2640796915611404382">"Nepovoliť"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Zrušiť"</string>
     <string name="consent_back" msgid="2560683030046918882">"Späť"</string>
+    <string name="permission_expand" msgid="893185038020887411">"Rozbaliť sekciu <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
+    <string name="permission_collapse" msgid="3320833884220844084">"Zbaliť sekciu <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Chcete udeliť aplikáciám v zariadení &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; rovnaké povolenia ako v zariadení &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="permission_sync_summary" msgid="765497944331294275">"Môžu zahŕňať prístup k &lt;strong&gt;mikrofónu&lt;/strong&gt;, &lt;strong&gt;kamere&lt;/strong&gt; a &lt;strong&gt;polohe&lt;/strong&gt;, a ďalšie citlivé povolenia v zariadení &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;. &lt;br/&gt;&lt;br/&gt;Tieto povolenia môžete kedykoľvek zmeniť v nastaveniach zariadenia &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;."</string>
-    <string name="vendor_icon_description" msgid="4445875290032225965">"Ikona aplikácie"</string>
-    <string name="vendor_header_button_description" msgid="6566660389500630608">"Tlačidlo Ďalšie informácie"</string>
+    <string name="vendor_header_button_description" msgid="7994879208461111473">"Ďalšie informácie"</string>
     <string name="permission_phone" msgid="2661081078692784919">"Telefón"</string>
     <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
     <string name="permission_contacts" msgid="3858319347208004438">"Kontakty"</string>
@@ -57,16 +57,18 @@
     <string name="permission_storage" msgid="6831099350839392343">"Fotky a médiá"</string>
     <string name="permission_notification" msgid="693762568127741203">"Upozornenia"</string>
     <string name="permission_app_streaming" msgid="6009695219091526422">"Aplikácie"</string>
-    <string name="permission_nearby_device_streaming" msgid="5868108148065023161">"Streamovať do zariad. v okolí"</string>
+    <string name="permission_nearby_device_streaming" msgid="1023325519477349499">"Streaming"</string>
     <string name="permission_phone_summary" msgid="6684396967861278044">"Môže uskutočňovať a spravovať telefonické hovory"</string>
     <string name="permission_call_logs_summary" msgid="6186103394658755022">"Môže čítať zo zoznamu hovorov telefónu a zapisovať doň"</string>
     <string name="permission_sms_summary" msgid="3508442683678912017">"Môže odosielať a zobrazovať správy SMS"</string>
     <string name="permission_contacts_summary" msgid="675861979475628708">"Má prístup k vašim kontaktom"</string>
     <string name="permission_calendar_summary" msgid="6460000922511766226">"Má prístup k vášmu kalendáru"</string>
-    <string name="permission_microphone_summary" msgid="4241354865859396558">"Môže nahrávať zvuk pomocou mikrofónu"</string>
+    <string name="permission_microphone_summary" msgid="3692091540613093394">"Môže nahrávať zvuk"</string>
     <string name="permission_nearby_devices_summary" msgid="931940524460876655">"Môže vyhľadávať zariadenia v okolí, určovať ich relatívnu pozíciu a pripájať sa k nim"</string>
     <string name="permission_notification_summary" msgid="884075314530071011">"Môže čítať všetky upozornenia vrátane informácií, ako sú kontakty, správy a fotky"</string>
     <string name="permission_app_streaming_summary" msgid="606923325679670624">"Streamovať aplikácie telefónu"</string>
     <string name="permission_storage_summary" msgid="3918240895519506417"></string>
-    <string name="permission_nearby_device_streaming_summary" msgid="5776807830582725074">"Streamovanie obsahu do zariadení v okolí"</string>
+    <string name="permission_nearby_device_streaming_summary" msgid="8280824871197081246">"Streaming aplikácii a ďalších systémových funkcií zo zariadenia"</string>
+    <string name="device_type" product="default" msgid="8268703872070046263">"telefón"</string>
+    <string name="device_type" product="tablet" msgid="5038791954983067774">"tablet"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-sl/strings.xml b/packages/CompanionDeviceManager/res/values-sl/strings.xml
index 6234286..b1b1e91 100644
--- a/packages/CompanionDeviceManager/res/values-sl/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-sl/strings.xml
@@ -17,43 +17,36 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Upravitelj spremljevalnih naprav"</string>
-    <!-- no translation found for confirmation_title (8024993972587946678) -->
-    <skip />
+    <string name="confirmation_title" msgid="2244241995958340998">"Želite aplikaciji &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; dovoliti dostop do naprave &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"ura"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Izbira naprave »<xliff:g id="PROFILE_NAME">%1$s</xliff:g>«, ki jo bo upravljala aplikacija &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <!-- no translation found for summary_watch (6566922405914995759) -->
-    <skip />
-    <!-- no translation found for summary_watch_single_device (7443464525873186735) -->
-    <skip />
-    <string name="profile_name_glasses" msgid="8488394059007275998">"očala"</string>
-    <!-- no translation found for summary_glasses (3808267780579061241) -->
-    <skip />
-    <!-- no translation found for summary_glasses_single_device (7051392780285915640) -->
-    <skip />
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"Izbira naprave, ki jo bo upravljala aplikacija &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Izberite profil naprave »<xliff:g id="PROFILE_NAME">%1$s</xliff:g>« za nastavitev"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Ta aplikacija bo lahko sinhronizirala podatke, na primer ime klicatelja, in dostopala do teh dovoljenj v napravi »<xliff:g id="DEVICE_NAME">%1$s</xliff:g>«."</string>
+    <string name="confirmation_title_glasses" msgid="8288346850537727333">"Želite aplikaciji &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; dovoliti upravljanje naprave &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"naprava"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Ta aplikacija bo lahko dostopala do teh dovoljenj v napravi »<xliff:g id="DEVICE_NAME">%1$s</xliff:g>«."</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Dovolite, da &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; dostopa do teh podatkov v vašem telefonu"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Storitve za zunanje naprave"</string>
-    <string name="helper_summary_app_streaming" msgid="5977509499890099">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> v imenu naprave »<xliff:g id="DEVICE_TYPE">%2$s</xliff:g>« zahteva dovoljenje za pretočno predvajanje aplikacij v vaših napravah."</string>
+    <string name="helper_summary_app_streaming" msgid="2396773196949578425">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> v imenu naprave »<xliff:g id="DISPLAY_NAME">%2$s</xliff:g>« zahteva dovoljenje za pretočno predvajanje aplikacij v vaših napravah."</string>
     <string name="title_automotive_projection" msgid="3296005598978412847"></string>
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"Dovolite, da &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; dostopa do teh podatkov v vašem telefonu"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Storitve Google Play"</string>
-    <string name="helper_summary_computer" msgid="9050724687678157852">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> v imenu naprave »<xliff:g id="DEVICE_TYPE">%2$s</xliff:g>« zahteva dovoljenje za dostop do fotografij, predstavnosti in obvestil v telefonu."</string>
-    <string name="title_nearby_device_streaming" msgid="179278282547719200">"Aplikaciji &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; omogočite izvajanje tega dejanja iz telefona."</string>
-    <string name="helper_title_nearby_device_streaming" msgid="6124438217620593669">"Storitve za zunanje naprave"</string>
-    <string name="helper_summary_nearby_device_streaming" msgid="5538329403511524333">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> v imenu naprave »<xliff:g id="DEVICE_TYPE">%2$s</xliff:g>« zahteva dovoljenje za pretočno predvajanje vsebine v napravah v bližini."</string>
+    <string name="helper_summary_computer" msgid="8774832742608187072">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> v imenu naprave »<xliff:g id="DISPLAY_NAME">%2$s</xliff:g>« zahteva dovoljenje za dostop do fotografij, predstavnosti in obvestil v telefonu."</string>
+    <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Ali napravi &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; dovolite izvedbo tega dejanja?"</string>
+    <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> v imenu naprave »<xliff:g id="DEVICE_NAME">%2$s</xliff:g>« zahteva dovoljenje za pretočno predvajanje aplikacij in drugih sistemskih funkcij v napravah v bližini."</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"naprava"</string>
-    <!-- no translation found for summary_generic_single_device (4735072202474939111) -->
-    <skip />
-    <!-- no translation found for summary_generic (4988130802522924650) -->
-    <skip />
+    <string name="summary_generic" msgid="1761976003668044801">"Ta aplikacija bo lahko sinhronizirala podatke, na primer ime klicatelja, v telefonu in izbrani napravi."</string>
     <string name="consent_yes" msgid="8344487259618762872">"Dovoli"</string>
     <string name="consent_no" msgid="2640796915611404382">"Ne dovoli"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Prekliči"</string>
     <string name="consent_back" msgid="2560683030046918882">"Nazaj"</string>
+    <string name="permission_expand" msgid="893185038020887411">"Razširi dovoljenje »<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>«"</string>
+    <string name="permission_collapse" msgid="3320833884220844084">"Strni dovoljenje »<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>«"</string>
     <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Ali želite aplikacijam v napravi &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; odobriti enaka dovoljenja kot v napravi &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="permission_sync_summary" msgid="765497944331294275">"To lahko vključuje &lt;strong&gt;dostop do mikrofona&lt;/strong&gt;, &lt;strong&gt;fotoaparata&lt;/strong&gt; in &lt;strong&gt;lokacije&lt;/strong&gt; ter druga občutljiva dovoljenja v napravi &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;. &lt;br/&gt;&lt;br/&gt;Ta dovoljenja lahko kadar koli spremenite v nastavitvah v napravi &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;."</string>
-    <string name="vendor_icon_description" msgid="4445875290032225965">"Ikona aplikacije"</string>
-    <string name="vendor_header_button_description" msgid="6566660389500630608">"Gumb za več informacij"</string>
+    <string name="vendor_header_button_description" msgid="7994879208461111473">"Več informacij"</string>
     <string name="permission_phone" msgid="2661081078692784919">"Telefon"</string>
     <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
     <string name="permission_contacts" msgid="3858319347208004438">"Stiki"</string>
@@ -64,16 +57,18 @@
     <string name="permission_storage" msgid="6831099350839392343">"Fotografije in predstavnost"</string>
     <string name="permission_notification" msgid="693762568127741203">"Obvestila"</string>
     <string name="permission_app_streaming" msgid="6009695219091526422">"Aplikacije"</string>
-    <string name="permission_nearby_device_streaming" msgid="5868108148065023161">"Predvajanje v napravi v bližini"</string>
+    <string name="permission_nearby_device_streaming" msgid="1023325519477349499">"Pretočno predvajanje"</string>
     <string name="permission_phone_summary" msgid="6684396967861278044">"Lahko opravlja in upravlja telefonske klice"</string>
     <string name="permission_call_logs_summary" msgid="6186103394658755022">"Lahko bere in zapisuje dnevnik klicev v telefonu"</string>
     <string name="permission_sms_summary" msgid="3508442683678912017">"Lahko pošilja in si ogleduje sporočila SMS"</string>
     <string name="permission_contacts_summary" msgid="675861979475628708">"Lahko dostopa do stikov"</string>
     <string name="permission_calendar_summary" msgid="6460000922511766226">"Lahko dostopa do koledarja"</string>
-    <string name="permission_microphone_summary" msgid="4241354865859396558">"Lahko uporablja mikrofon za snemanje zvoka."</string>
+    <string name="permission_microphone_summary" msgid="3692091540613093394">"Lahko snema zvok"</string>
     <string name="permission_nearby_devices_summary" msgid="931940524460876655">"Lahko išče naprave v bližini, se povezuje z njimi in določa njihov relativni položaj"</string>
     <string name="permission_notification_summary" msgid="884075314530071011">"Lahko bere vsa obvestila, vključno s podatki, kot so stiki, sporočila in fotografije."</string>
     <string name="permission_app_streaming_summary" msgid="606923325679670624">"Pretočno predvajanje aplikacij telefona"</string>
     <string name="permission_storage_summary" msgid="3918240895519506417"></string>
-    <string name="permission_nearby_device_streaming_summary" msgid="5776807830582725074">"Pretočno predvajanje vsebine v napravi v bližini"</string>
+    <string name="permission_nearby_device_streaming_summary" msgid="8280824871197081246">"Pretočno predvajanje aplikacij in drugih sistemskih funkcij iz telefona"</string>
+    <string name="device_type" product="default" msgid="8268703872070046263">"telefon"</string>
+    <string name="device_type" product="tablet" msgid="5038791954983067774">"tablični računalnik"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-sq/strings.xml b/packages/CompanionDeviceManager/res/values-sq/strings.xml
index 0ca0173..037a707 100644
--- a/packages/CompanionDeviceManager/res/values-sq/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-sq/strings.xml
@@ -17,36 +17,36 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Menaxheri i pajisjes shoqëruese"</string>
-    <string name="confirmation_title" msgid="8024993972587946678">"Lejo që &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; të ketë qasje te &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"T\'i lejohet aplikacionit &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; qasja te &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"ora inteligjente"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Zgjidh \"<xliff:g id="PROFILE_NAME">%1$s</xliff:g>\" që do të menaxhohet nga &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="6566922405914995759">"Aplikacioni nevojitet për të menaxhuar profilin tënd të <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> do të lejohet të sinkronizojë informacione, si p.sh. emri i dikujt që po telefonon, të ndërveprojë me njoftimet e tua dhe të ketë qasje te lejet e \"Telefonit\", \"SMS-ve\", \"Kontakteve\", \"Kalendarit\", \"Evidencave të telefonatave\" dhe \"Pajisjeve në afërsi\"."</string>
-    <string name="summary_watch_single_device" msgid="7443464525873186735">"Aplikacioni nevojitet për të menaxhuar profilin tënd të <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> do të lejohet të sinkronizojë informacione, si p.sh. emri i dikujt që po telefonon dhe të ketë qasje te këto leje:"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"syzet"</string>
-    <string name="summary_glasses" msgid="3808267780579061241">"Ky aplikacion nevojitet për të menaxhuar <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> do të lejohet të ndërveprojë me njoftimet e tua dhe të ketë qasje te lejet e \"Telefonit\", \"SMS-ve\", \"Kontakteve\", \"Mikrofonit\" dhe të \"Pajisjeve në afërsi\"."</string>
-    <string name="summary_glasses_single_device" msgid="7051392780285915640">"Aplikacioni nevojitet për të menaxhuar <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> do të lejohet të ndërveprojë me këto leje:"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"Zgjidh një pajisje që do të menaxhohet nga &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Zgjidh një <xliff:g id="PROFILE_NAME">%1$s</xliff:g> për konfigurimin"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Këtij aplikacioni do t\'i lejohet të sinkronizojë informacione, si p.sh. emrin e dikujt që po telefonon, si dhe të ketë qasje në këto leje në <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
+    <string name="confirmation_title_glasses" msgid="8288346850537727333">"Të lejohet që &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; të menaxhojë &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"pajisje"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Këtij aplikacioni do t\'i lejohet qasja te këto leje në <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Lejo që &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; të ketë qasje në këtë informacion nga telefoni yt"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Shërbimet mes pajisjeve"</string>
-    <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> po kërkon leje në emër të <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> për të transmetuar aplikacione ndërmjet pajisjeve të tua"</string>
+    <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g> po kërkon leje në emër të <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> për të transmetuar aplikacione ndërmjet pajisjeve të tua"</string>
     <string name="title_automotive_projection" msgid="3296005598978412847"></string>
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"Lejo që &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; të ketë qasje në këtë informacion nga telefoni yt"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Shërbimet e Google Play"</string>
-    <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> po kërkon leje në emër të <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> për të marrë qasje te fotografitë, media dhe njoftimet e telefonit tënd"</string>
-    <string name="title_nearby_device_streaming" msgid="179278282547719200">"Lejo &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; të kryejë këtë veprim nga telefoni yt"</string>
-    <string name="helper_title_nearby_device_streaming" msgid="6124438217620593669">"Shërbimet mes pajisjeve"</string>
-    <string name="helper_summary_nearby_device_streaming" msgid="5538329403511524333">"<xliff:g id="APP_NAME">%1$s</xliff:g> po kërkon leje në emër të <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> për të transmetuar përmbajtje te pajisjet në afërsi"</string>
+    <string name="helper_summary_computer" msgid="8774832742608187072">"<xliff:g id="APP_NAME">%1$s</xliff:g> po kërkon leje në emër të <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> për të marrë qasje te fotografitë, media dhe njoftimet e telefonit tënd"</string>
+    <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Të lejohet që &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; të ndërmarrë këtë veprim?"</string>
+    <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> po kërkon leje në emër të (<xliff:g id="DEVICE_NAME">%2$s</xliff:g>) tënde për të transmetuar aplikacione dhe veçori të tjera të sistemit te pajisjet në afërsi"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"pajisja"</string>
-    <string name="summary_generic_single_device" msgid="4735072202474939111">"Ky aplikacion do të mund të sinkronizojë informacione, si p.sh emri i dikujt që po telefonon, mes telefonit tënd dhe <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string>
-    <string name="summary_generic" msgid="4988130802522924650">"Ky aplikacion do të mund të sinkronizojë informacione, si p.sh emri i dikujt që po telefonon, mes telefonit tënd dhe pajisjes së zgjedhur."</string>
+    <string name="summary_generic" msgid="1761976003668044801">"Ky aplikacion do të mund të sinkronizojë informacione, si p.sh emrin e dikujt që po telefonon, mes telefonit tënd dhe pajisjes së zgjedhur."</string>
     <string name="consent_yes" msgid="8344487259618762872">"Lejo"</string>
     <string name="consent_no" msgid="2640796915611404382">"Mos lejo"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Anulo"</string>
     <string name="consent_back" msgid="2560683030046918882">"Pas"</string>
+    <string name="permission_expand" msgid="893185038020887411">"Zgjero: <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
+    <string name="permission_collapse" msgid="3320833884220844084">"Palos: <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"T\'i jepen aplikacioneve në &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; të njëjtat leje si në &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="permission_sync_summary" msgid="765497944331294275">"Kjo mund të përfshijë qasjen te &lt;strong&gt;Mikrofoni&lt;/strong&gt;, &lt;strong&gt;Kamera&lt;/strong&gt;, dhe &lt;strong&gt;Vendndodhja&lt;/strong&gt;, dhe leje të tjera për informacione delikate në &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;. &lt;br/&gt;&lt;br/&gt;Ti mund t\'i ndryshosh këto leje në çdo kohë te \"Cilësimet\" në &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;."</string>
-    <string name="vendor_icon_description" msgid="4445875290032225965">"Ikona e aplikacionit"</string>
-    <string name="vendor_header_button_description" msgid="6566660389500630608">"Butoni \"Më shumë informacione\""</string>
+    <string name="vendor_header_button_description" msgid="7994879208461111473">"Më shumë informacione"</string>
     <string name="permission_phone" msgid="2661081078692784919">"Telefoni"</string>
     <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
     <string name="permission_contacts" msgid="3858319347208004438">"Kontaktet"</string>
@@ -57,16 +57,18 @@
     <string name="permission_storage" msgid="6831099350839392343">"Fotografitë dhe media"</string>
     <string name="permission_notification" msgid="693762568127741203">"Njoftimet"</string>
     <string name="permission_app_streaming" msgid="6009695219091526422">"Aplikacionet"</string>
-    <string name="permission_nearby_device_streaming" msgid="5868108148065023161">"Transmetim: Pajisjet në afërsi"</string>
+    <string name="permission_nearby_device_streaming" msgid="1023325519477349499">"Transmetimi"</string>
     <string name="permission_phone_summary" msgid="6684396967861278044">"Mund të bëjë dhe të menaxhojë telefonatat"</string>
     <string name="permission_call_logs_summary" msgid="6186103394658755022">"Mund të lexojë dhe të shkruajë në evidencën e telefonatave"</string>
     <string name="permission_sms_summary" msgid="3508442683678912017">"Mund të dërgojë dhe të shikojë mesazhet SMS"</string>
     <string name="permission_contacts_summary" msgid="675861979475628708">"Mund të ketë qasje te kontaktet e tua"</string>
     <string name="permission_calendar_summary" msgid="6460000922511766226">"Mund të ketë qasje te kalendari"</string>
-    <string name="permission_microphone_summary" msgid="4241354865859396558">"Mund të regjistrojë audio duke përdorur mikrofonin"</string>
+    <string name="permission_microphone_summary" msgid="3692091540613093394">"Mund të regjistrojë audio"</string>
     <string name="permission_nearby_devices_summary" msgid="931940524460876655">"Mund të gjejë, të lidhet dhe të përcaktojë pozicionin e përafërt të pajisjeve në afërsi"</string>
     <string name="permission_notification_summary" msgid="884075314530071011">"Mund të lexojë të gjitha njoftimet, duke përfshirë informacione si kontaktet, mesazhet dhe fotografitë"</string>
     <string name="permission_app_streaming_summary" msgid="606923325679670624">"Transmeto aplikacionet e telefonit tënd"</string>
     <string name="permission_storage_summary" msgid="3918240895519506417"></string>
-    <string name="permission_nearby_device_streaming_summary" msgid="5776807830582725074">"Transmeto përmbajtje te një pajisje në afërsi"</string>
+    <string name="permission_nearby_device_streaming_summary" msgid="8280824871197081246">"Transmeto aplikacionet dhe veçoritë e tjera të sistemit nga telefoni yt"</string>
+    <string name="device_type" product="default" msgid="8268703872070046263">"telefon"</string>
+    <string name="device_type" product="tablet" msgid="5038791954983067774">"tablet"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-sr/strings.xml b/packages/CompanionDeviceManager/res/values-sr/strings.xml
index 30f83e9..3817d58 100644
--- a/packages/CompanionDeviceManager/res/values-sr/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-sr/strings.xml
@@ -17,43 +17,36 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Менаџер придруженог уређаја"</string>
-    <!-- no translation found for confirmation_title (8024993972587946678) -->
-    <skip />
+    <string name="confirmation_title" msgid="2244241995958340998">"Дозволите да апликација &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; приступа уређају &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"сат"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Одаберите <xliff:g id="PROFILE_NAME">%1$s</xliff:g> којим ће управљати апликација &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <!-- no translation found for summary_watch (6566922405914995759) -->
-    <skip />
-    <!-- no translation found for summary_watch_single_device (7443464525873186735) -->
-    <skip />
-    <string name="profile_name_glasses" msgid="8488394059007275998">"наочаре"</string>
-    <!-- no translation found for summary_glasses (3808267780579061241) -->
-    <skip />
-    <!-- no translation found for summary_glasses_single_device (7051392780285915640) -->
-    <skip />
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"Одаберите уређај којим ће управљати апликација &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Одаберите профил <xliff:g id="PROFILE_NAME">%1$s</xliff:g> који желите да подесите"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Овој апликацији ће бити дозвољено да синхронизује податке, попут имена особе која упућује позив, и приступа тим дозволама на вашем уређају (<xliff:g id="DEVICE_NAME">%1$s</xliff:g>)"</string>
+    <string name="confirmation_title_glasses" msgid="8288346850537727333">"Желите ли да дозволите да &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; управља уређајем &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"уређај"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Овој апликацији ће бити дозвољено да приступа овим дозволама на вашем уређају (<xliff:g id="DEVICE_NAME">%1$s</xliff:g>)"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Дозволите да &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; приступа овим информацијама са телефона"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Услуге на више уређаја"</string>
-    <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> захтева дозволу у име уређаја <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> за стримовање апликација између уређаја"</string>
+    <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g> захтева дозволу у име уређаја <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> за стримовање апликација између уређаја"</string>
     <string name="title_automotive_projection" msgid="3296005598978412847"></string>
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"Дозволите да &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; приступа овим информацијама са телефона"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Google Play услуге"</string>
-    <string name="helper_summary_computer" msgid="9050724687678157852">"Апликација <xliff:g id="APP_NAME">%1$s</xliff:g> захтева дозволу у име уређаја <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> за приступ сликама, медијском садржају и обавештењима са телефона"</string>
-    <string name="title_nearby_device_streaming" msgid="179278282547719200">"Дозволите да &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; обавља ову радњу са телефона"</string>
-    <string name="helper_title_nearby_device_streaming" msgid="6124438217620593669">"Услуге на више уређаја"</string>
-    <string name="helper_summary_nearby_device_streaming" msgid="5538329403511524333">"Апликација <xliff:g id="APP_NAME">%1$s</xliff:g> захтева дозволу у име уређаја <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> да стримује садржај на уређаје у близини"</string>
+    <string name="helper_summary_computer" msgid="8774832742608187072">"<xliff:g id="APP_NAME">%1$s</xliff:g> захтева дозволу у име уређаја <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> за приступ сликама, медијском садржају и обавештењима са телефона"</string>
+    <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Желите ли да дозволите да &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; обави ову радњу?"</string>
+    <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"Апликација <xliff:g id="APP_NAME">%1$s</xliff:g> захтева дозволу у име уређаја <xliff:g id="DEVICE_NAME">%2$s</xliff:g> да стримује апликације и друге системске функције на уређаје у близини"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"уређај"</string>
-    <!-- no translation found for summary_generic_single_device (4735072202474939111) -->
-    <skip />
-    <!-- no translation found for summary_generic (4988130802522924650) -->
-    <skip />
+    <string name="summary_generic" msgid="1761976003668044801">"Ова апликација ће моћи да синхронизује податке, попут имена особе која упућује позив, између телефона и одабраног уређаја"</string>
     <string name="consent_yes" msgid="8344487259618762872">"Дозволи"</string>
     <string name="consent_no" msgid="2640796915611404382">"Не дозволи"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Откажи"</string>
     <string name="consent_back" msgid="2560683030046918882">"Назад"</string>
+    <string name="permission_expand" msgid="893185038020887411">"Прошири <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
+    <string name="permission_collapse" msgid="3320833884220844084">"Скупи <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Апликцијама на уређају &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; дајете све дозволе као на уређају &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="permission_sync_summary" msgid="765497944331294275">"То може да обухвата приступ &lt;strong&gt;микрофону&lt;/strong&gt;, &lt;strong&gt;камери&lt;/strong&gt;, и &lt;strong&gt;локацији&lt;/strong&gt;, и друге осетљиве дозволе на уређају &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;. &lt;br/&gt;&lt;br/&gt;Можете да промените те дозволе у било ком тренутку у Подешавањима на уређају &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;."</string>
-    <string name="vendor_icon_description" msgid="4445875290032225965">"Икона апликације"</string>
-    <string name="vendor_header_button_description" msgid="6566660389500630608">"Дугме за више информација"</string>
+    <string name="vendor_header_button_description" msgid="7994879208461111473">"Још информација"</string>
     <string name="permission_phone" msgid="2661081078692784919">"Телефон"</string>
     <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
     <string name="permission_contacts" msgid="3858319347208004438">"Контакти"</string>
@@ -64,16 +57,18 @@
     <string name="permission_storage" msgid="6831099350839392343">"Слике и медији"</string>
     <string name="permission_notification" msgid="693762568127741203">"Обавештења"</string>
     <string name="permission_app_streaming" msgid="6009695219091526422">"Апликације"</string>
-    <string name="permission_nearby_device_streaming" msgid="5868108148065023161">"Стримовање, уређаји у близини"</string>
+    <string name="permission_nearby_device_streaming" msgid="1023325519477349499">"Стриминг"</string>
     <string name="permission_phone_summary" msgid="6684396967861278044">"Може да упућује телефонске позиве и управља њима"</string>
     <string name="permission_call_logs_summary" msgid="6186103394658755022">"Може да чита и пише евиденцију позива на телефону"</string>
     <string name="permission_sms_summary" msgid="3508442683678912017">"Може да шаље и прегледа SMS поруке"</string>
     <string name="permission_contacts_summary" msgid="675861979475628708">"Може да приступа контактима"</string>
     <string name="permission_calendar_summary" msgid="6460000922511766226">"Може да приступа календару"</string>
-    <string name="permission_microphone_summary" msgid="4241354865859396558">"Може да снима звук помоћу микрофона"</string>
+    <string name="permission_microphone_summary" msgid="3692091540613093394">"Може да снима звук"</string>
     <string name="permission_nearby_devices_summary" msgid="931940524460876655">"Може да проналази и утврђује релативну позицију уређаја у близини, као и да се повезује са њима"</string>
     <string name="permission_notification_summary" msgid="884075314530071011">"Може да чита сва обавештења, укључујући информације попут контаката, порука и слика"</string>
     <string name="permission_app_streaming_summary" msgid="606923325679670624">"Стримујте апликације на телефону"</string>
     <string name="permission_storage_summary" msgid="3918240895519506417"></string>
-    <string name="permission_nearby_device_streaming_summary" msgid="5776807830582725074">"Стримујте садржај на уређај у близини"</string>
+    <string name="permission_nearby_device_streaming_summary" msgid="8280824871197081246">"Стримујте апликације и друге системске функције са телефона"</string>
+    <string name="device_type" product="default" msgid="8268703872070046263">"телефону"</string>
+    <string name="device_type" product="tablet" msgid="5038791954983067774">"таблету"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-sv/strings.xml b/packages/CompanionDeviceManager/res/values-sv/strings.xml
index c3c9721..f3be3be 100644
--- a/packages/CompanionDeviceManager/res/values-sv/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-sv/strings.xml
@@ -17,43 +17,36 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Companion Device Manager"</string>
-    <!-- no translation found for confirmation_title (8024993972587946678) -->
-    <skip />
+    <string name="confirmation_title" msgid="2244241995958340998">"Vill du tillåta att appen &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; får åtkomst till &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"klocka"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Välj en <xliff:g id="PROFILE_NAME">%1$s</xliff:g> för hantering av &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <!-- no translation found for summary_watch (6566922405914995759) -->
-    <skip />
-    <!-- no translation found for summary_watch_single_device (7443464525873186735) -->
-    <skip />
-    <string name="profile_name_glasses" msgid="8488394059007275998">"glasögon"</string>
-    <!-- no translation found for summary_glasses (3808267780579061241) -->
-    <skip />
-    <!-- no translation found for summary_glasses_single_device (7051392780285915640) -->
-    <skip />
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"Välj en enhet för hantering av &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Välj en <xliff:g id="PROFILE_NAME">%1$s</xliff:g> för konfigurering"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Appen får tillåtelse att synkronisera information, till exempel namnet på någon som ringer, och få tillgång till dessa behörigheter på din <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
+    <string name="confirmation_title_glasses" msgid="8288346850537727333">"Tillåt att &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; hanterar &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"enhet"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Appen får tillåtelse att använda dessa behörigheter på din <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Ge &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; åtkomstbehörighet till denna information på telefonen"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Tjänster för flera enheter"</string>
-    <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> begär behörighet att låta <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> streama appar mellan enheter"</string>
+    <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g> begär behörighet att låta <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> streama appar mellan enheter"</string>
     <string name="title_automotive_projection" msgid="3296005598978412847"></string>
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"Ge &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; åtkomstbehörighet till denna information på telefonen"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Google Play-tjänster"</string>
-    <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> begär behörighet att ge <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> åtkomst till foton, mediefiler och aviseringar på telefonen"</string>
-    <string name="title_nearby_device_streaming" msgid="179278282547719200">"Tillåt att &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; utför denna åtgärd på din telefon"</string>
-    <string name="helper_title_nearby_device_streaming" msgid="6124438217620593669">"Tjänster för flera enheter"</string>
-    <string name="helper_summary_nearby_device_streaming" msgid="5538329403511524333">"<xliff:g id="APP_NAME">%1$s</xliff:g> begär behörighet för att streama innehåll till enheter i närheten för din <xliff:g id="DEVICE_TYPE">%2$s</xliff:g>"</string>
+    <string name="helper_summary_computer" msgid="8774832742608187072">"<xliff:g id="APP_NAME">%1$s</xliff:g> begär behörighet att ge <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> åtkomst till foton, mediefiler och aviseringar på telefonen"</string>
+    <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Vill du tillåta att &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; utför denna åtgärd?"</string>
+    <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> begär behörighet att streama appar och andra systemfunktioner till enheter i närheten för din <xliff:g id="DEVICE_NAME">%2$s</xliff:g>"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"enhet"</string>
-    <!-- no translation found for summary_generic_single_device (4735072202474939111) -->
-    <skip />
-    <!-- no translation found for summary_generic (4988130802522924650) -->
-    <skip />
+    <string name="summary_generic" msgid="1761976003668044801">"Den här appen kommer att kunna synkronisera information mellan telefonen och den valda enheten, till exempel namnet på någon som ringer"</string>
     <string name="consent_yes" msgid="8344487259618762872">"Tillåt"</string>
     <string name="consent_no" msgid="2640796915611404382">"Tillåt inte"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Avbryt"</string>
     <string name="consent_back" msgid="2560683030046918882">"Tillbaka"</string>
+    <string name="permission_expand" msgid="893185038020887411">"Utöka <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
+    <string name="permission_collapse" msgid="3320833884220844084">"Komprimera <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Vill du ge apparna på &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; samma behörigheter som de har på &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="permission_sync_summary" msgid="765497944331294275">"Detta kan inkludera &lt;strong&gt;Mikrofon-&lt;/strong&gt;, &lt;strong&gt;Kamera-&lt;/strong&gt;, och &lt;strong&gt;Platsåtkomst&lt;/strong&gt;, samt andra känsliga behörigheter på &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;. &lt;br/&gt;&lt;br/&gt;Du kan ändra dessa behörigheter när som helst i inställningarna på &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;."</string>
-    <string name="vendor_icon_description" msgid="4445875290032225965">"Appikon"</string>
-    <string name="vendor_header_button_description" msgid="6566660389500630608">"Knappen Mer information"</string>
+    <string name="vendor_header_button_description" msgid="7994879208461111473">"Mer information"</string>
     <string name="permission_phone" msgid="2661081078692784919">"Telefon"</string>
     <string name="permission_sms" msgid="6337141296535774786">"Sms"</string>
     <string name="permission_contacts" msgid="3858319347208004438">"Kontakter"</string>
@@ -64,16 +57,18 @@
     <string name="permission_storage" msgid="6831099350839392343">"Foton och media"</string>
     <string name="permission_notification" msgid="693762568127741203">"Aviseringar"</string>
     <string name="permission_app_streaming" msgid="6009695219091526422">"Appar"</string>
-    <string name="permission_nearby_device_streaming" msgid="5868108148065023161">"En enhet i närheten streamar"</string>
+    <string name="permission_nearby_device_streaming" msgid="1023325519477349499">"Streaming"</string>
     <string name="permission_phone_summary" msgid="6684396967861278044">"Får skapa och hantera telefonsamtal"</string>
     <string name="permission_call_logs_summary" msgid="6186103394658755022">"Får läsa och skriva samtalslogg"</string>
     <string name="permission_sms_summary" msgid="3508442683678912017">"Får skicka och visa sms"</string>
     <string name="permission_contacts_summary" msgid="675861979475628708">"Får åtkomst till dina kontakter"</string>
     <string name="permission_calendar_summary" msgid="6460000922511766226">"Får åtkomst till din kalender"</string>
-    <string name="permission_microphone_summary" msgid="4241354865859396558">"Kan spela in ljud med mikrofonen"</string>
+    <string name="permission_microphone_summary" msgid="3692091540613093394">"Kan spela in ljud"</string>
     <string name="permission_nearby_devices_summary" msgid="931940524460876655">"Får hitta, ansluta till och avgöra den relativa positionen för enheter i närheten"</string>
     <string name="permission_notification_summary" msgid="884075314530071011">"Kan läsa alla aviseringar, inklusive information som kontakter, meddelanden och foton"</string>
     <string name="permission_app_streaming_summary" msgid="606923325679670624">"Streama telefonens appar"</string>
     <string name="permission_storage_summary" msgid="3918240895519506417"></string>
-    <string name="permission_nearby_device_streaming_summary" msgid="5776807830582725074">"Streamar innehåll till en enhet i närheten"</string>
+    <string name="permission_nearby_device_streaming_summary" msgid="8280824871197081246">"Streama appar och andra systemfunktioner från din telefon"</string>
+    <string name="device_type" product="default" msgid="8268703872070046263">"telefon"</string>
+    <string name="device_type" product="tablet" msgid="5038791954983067774">"surfplatta"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-sw/strings.xml b/packages/CompanionDeviceManager/res/values-sw/strings.xml
index 873c857..7b94239 100644
--- a/packages/CompanionDeviceManager/res/values-sw/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-sw/strings.xml
@@ -17,43 +17,36 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Kidhibiti cha Vifaa Visaidizi"</string>
-    <!-- no translation found for confirmation_title (8024993972587946678) -->
-    <skip />
+    <string name="confirmation_title" msgid="2244241995958340998">"Ungependa kuruhusu &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ifikie &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"saa"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Chagua <xliff:g id="PROFILE_NAME">%1$s</xliff:g> ili idhibitiwe na &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <!-- no translation found for summary_watch (6566922405914995759) -->
-    <skip />
-    <!-- no translation found for summary_watch_single_device (7443464525873186735) -->
-    <skip />
-    <string name="profile_name_glasses" msgid="8488394059007275998">"miwani"</string>
-    <!-- no translation found for summary_glasses (3808267780579061241) -->
-    <skip />
-    <!-- no translation found for summary_glasses_single_device (7051392780285915640) -->
-    <skip />
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"Chagua kifaa cha kudhibitiwa na &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Chagua <xliff:g id="PROFILE_NAME">%1$s</xliff:g> ili uweke mipangilio"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Programu hii itaruhusiwa kusawazisha maelezo, kama vile jina la mtu anayepiga simu na kufikia ruhusa hizi kwenye <xliff:g id="DEVICE_NAME">%1$s</xliff:g> yako"</string>
+    <string name="confirmation_title_glasses" msgid="8288346850537727333">"Ungependa kuruhusu &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; idhibiti &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"kifaa"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Programu hii itaruhusiwa kufikia ruhusa hizi kwenye <xliff:g id="DEVICE_NAME">%1$s</xliff:g> yako"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Ruhusu &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ifikie maelezo haya kutoka kwenye simu yako"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Huduma za kifaa kilichounganishwa kwingine"</string>
-    <string name="helper_summary_app_streaming" msgid="5977509499890099">"Programu ya <xliff:g id="APP_NAME">%1$s</xliff:g> inaomba ruhusa kwa niaba ya <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> yako ili itiririshe programu kati ya vifaa vyako"</string>
+    <string name="helper_summary_app_streaming" msgid="2396773196949578425">"Programu ya <xliff:g id="APP_NAME">%1$s</xliff:g> inaomba ruhusa kwa niaba ya <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> yako ili itiririshe programu kati ya vifaa vyako"</string>
     <string name="title_automotive_projection" msgid="3296005598978412847"></string>
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"Ruhusu &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ifikie maelezo haya kutoka kwenye simu yako"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Huduma za Google Play"</string>
-    <string name="helper_summary_computer" msgid="9050724687678157852">"Programu ya <xliff:g id="APP_NAME">%1$s</xliff:g> inaomba ruhusa kwa niaba ya <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> yako ili ifikie picha, maudhui na arifa za simu yako"</string>
-    <string name="title_nearby_device_streaming" msgid="179278282547719200">"Ruhusu &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; itekeleze kitendo hiki kwenye simu yako"</string>
-    <string name="helper_title_nearby_device_streaming" msgid="6124438217620593669">"Huduma za kifaa kilichounganishwa kwingine"</string>
-    <string name="helper_summary_nearby_device_streaming" msgid="5538329403511524333">"<xliff:g id="APP_NAME">%1$s</xliff:g> inaomba ruhusa kwa niaba ya <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> ili itiririshe maudhui kwenye vifaa vilivyo karibu"</string>
+    <string name="helper_summary_computer" msgid="8774832742608187072">"Programu ya <xliff:g id="APP_NAME">%1$s</xliff:g> inaomba ruhusa kwa niaba ya <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> yako ili ifikie picha, maudhui na arifa za simu yako"</string>
+    <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Ungependa kuruhusu &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; itekeleze kitendo hiki?"</string>
+    <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> inaomba ruhusa kwa niaba ya <xliff:g id="DEVICE_NAME">%2$s</xliff:g> chako ili itiririshe programu na vipengele vingine vya mfumo kwenye vifaa vilivyo karibu"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"kifaa"</string>
-    <!-- no translation found for summary_generic_single_device (4735072202474939111) -->
-    <skip />
-    <!-- no translation found for summary_generic (4988130802522924650) -->
-    <skip />
+    <string name="summary_generic" msgid="1761976003668044801">"Programu hii itaweza kusawazisha maelezo, kama vile jina la mtu anayepiga simu, kati ya simu yako na kifaa ulichochagua"</string>
     <string name="consent_yes" msgid="8344487259618762872">"Ruhusu"</string>
     <string name="consent_no" msgid="2640796915611404382">"Usiruhusu"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Ghairi"</string>
     <string name="consent_back" msgid="2560683030046918882">"Nyuma"</string>
+    <string name="permission_expand" msgid="893185038020887411">"Panua <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
+    <string name="permission_collapse" msgid="3320833884220844084">"Kunja <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Ungependa kuzipa programu katika &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; ruhusa ile ile kama kwenye &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="permission_sync_summary" msgid="765497944331294275">"Hii ni pamoja na &lt;strong&gt;Maikrofoni&lt;/strong&gt;, &lt;strong&gt;Kamera&lt;/strong&gt;, na &lt;strong&gt;Uwezo wa kufikia mahali&lt;/strong&gt;, na ruhusa nyingine nyeti kwenye &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;. &lt;br/&gt;&lt;br/&gt;Unaweza kubadilisha ruhusa hizi wakati wowote katika Mipangilio yako kwenye &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;."</string>
-    <string name="vendor_icon_description" msgid="4445875290032225965">"Aikoni ya Programu"</string>
-    <string name="vendor_header_button_description" msgid="6566660389500630608">"Kitufe cha Maelezo Zaidi"</string>
+    <string name="vendor_header_button_description" msgid="7994879208461111473">"Maelezo Zaidi"</string>
     <string name="permission_phone" msgid="2661081078692784919">"Simu"</string>
     <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
     <string name="permission_contacts" msgid="3858319347208004438">"Anwani"</string>
@@ -64,16 +57,18 @@
     <string name="permission_storage" msgid="6831099350839392343">"Picha na maudhui"</string>
     <string name="permission_notification" msgid="693762568127741203">"Arifa"</string>
     <string name="permission_app_streaming" msgid="6009695219091526422">"Programu"</string>
-    <string name="permission_nearby_device_streaming" msgid="5868108148065023161">"Kutiririsha kwenye Kifaa kilicho Karibu"</string>
+    <string name="permission_nearby_device_streaming" msgid="1023325519477349499">"Kutiririsha maudhui"</string>
     <string name="permission_phone_summary" msgid="6684396967861278044">"Inaweza kupiga na kudhibiti simu"</string>
     <string name="permission_call_logs_summary" msgid="6186103394658755022">"Inaweza kusoma na kuandika rekodi ya nambari za simu"</string>
     <string name="permission_sms_summary" msgid="3508442683678912017">"Inaweza kutuma na kuangalia ujumbe wa SMS"</string>
     <string name="permission_contacts_summary" msgid="675861979475628708">"Inaweza kufikia anwani zako"</string>
     <string name="permission_calendar_summary" msgid="6460000922511766226">"Inaweza kufikia kalenda yako"</string>
-    <string name="permission_microphone_summary" msgid="4241354865859396558">"Inaweza kurekodi sauti ikitumia maikrofoni"</string>
+    <string name="permission_microphone_summary" msgid="3692091540613093394">"Inaweza kurekodi sauti"</string>
     <string name="permission_nearby_devices_summary" msgid="931940524460876655">"Inaweza kutafuta, kuunganisha na kubaini nafasi ya makadirio ya vifaa vilivyo karibu"</string>
     <string name="permission_notification_summary" msgid="884075314530071011">"Inaweza kusoma arifa zote, ikiwa ni pamoja na maelezo kama vile anwani, ujumbe na picha"</string>
     <string name="permission_app_streaming_summary" msgid="606923325679670624">"Tiririsha programu za simu yako"</string>
     <string name="permission_storage_summary" msgid="3918240895519506417"></string>
-    <string name="permission_nearby_device_streaming_summary" msgid="5776807830582725074">"Kutiririsha maudhui kwenye kifaa kilicho karibu"</string>
+    <string name="permission_nearby_device_streaming_summary" msgid="8280824871197081246">"Kutiririsha programu na vipengele vya mfumo kwenye simu yako"</string>
+    <string name="device_type" product="default" msgid="8268703872070046263">"simu"</string>
+    <string name="device_type" product="tablet" msgid="5038791954983067774">"kompyuta kibao"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-ta/strings.xml b/packages/CompanionDeviceManager/res/values-ta/strings.xml
index fbfbc40..c1b8352 100644
--- a/packages/CompanionDeviceManager/res/values-ta/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ta/strings.xml
@@ -17,43 +17,36 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"கம்பேனியன் சாதன நிர்வாகி"</string>
-    <!-- no translation found for confirmation_title (8024993972587946678) -->
-    <skip />
+    <string name="confirmation_title" msgid="2244241995958340998">"&lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; சாதனத்தை அணுக &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ஆப்ஸை அனுமதிக்கவா?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"வாட்ச்"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"&lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; ஆப்ஸ் நிர்வகிக்கக்கூடிய <xliff:g id="PROFILE_NAME">%1$s</xliff:g> தேர்ந்தெடுக்கப்பட வேண்டும்"</string>
-    <!-- no translation found for summary_watch (6566922405914995759) -->
-    <skip />
-    <!-- no translation found for summary_watch_single_device (7443464525873186735) -->
-    <skip />
-    <string name="profile_name_glasses" msgid="8488394059007275998">"கிளாஸஸ்"</string>
-    <!-- no translation found for summary_glasses (3808267780579061241) -->
-    <skip />
-    <!-- no translation found for summary_glasses_single_device (7051392780285915640) -->
-    <skip />
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ஆப்ஸால் நிர்வகிக்கப்பட வேண்டிய சாதனத்தைத் தேர்வுசெய்யுங்கள்"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"அமைக்க <xliff:g id="PROFILE_NAME">%1$s</xliff:g> ஐத் தேர்வுசெய்யவும்"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"அழைப்பவரின் பெயர் போன்ற தகவல்களை ஒத்திசைக்கவும் உங்கள் <xliff:g id="DEVICE_NAME">%1$s</xliff:g> சாதனத்தில் இந்த அனுமதிகளை அணுகவும் இந்த ஆப்ஸ் அனுமதிக்கப்படும்"</string>
+    <string name="confirmation_title_glasses" msgid="8288346850537727333">"&lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&amp;gt சாதனத்தை நிர்வகிக்க &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ஆப்ஸை அனுமதிக்கவா?"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"சாதனம்"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"உங்கள் <xliff:g id="DEVICE_NAME">%1$s</xliff:g> சாதனத்தில் இந்த அனுமதிகளை அணுக இந்த ஆப்ஸ் அனுமதிக்கப்படும்"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"மொபைலில் உள்ள இந்தத் தகவல்களை அணுக, &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ஆப்ஸை அனுமதிக்கவும்"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"பன்முக சாதன சேவைகள்"</string>
-    <string name="helper_summary_app_streaming" msgid="5977509499890099">"உங்கள் சாதனங்களுக்கு இடையே ஆப்ஸை ஸ்ட்ரீம் செய்ய உங்கள் <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> சார்பாக <xliff:g id="APP_NAME">%1$s</xliff:g> ஆப்ஸ் அனுமதியைக் கோருகிறது"</string>
+    <string name="helper_summary_app_streaming" msgid="2396773196949578425">"உங்கள் சாதனங்களுக்கு இடையே ஆப்ஸை ஸ்ட்ரீம் செய்ய உங்கள் <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> சார்பாக <xliff:g id="APP_NAME">%1$s</xliff:g> ஆப்ஸ் அனுமதியைக் கோருகிறது"</string>
     <string name="title_automotive_projection" msgid="3296005598978412847"></string>
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"உங்கள் மொபைலிலிருந்து இந்தத் தகவலை அணுக &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ஆப்ஸை அனுமதியுங்கள்"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Google Play சேவைகள்"</string>
-    <string name="helper_summary_computer" msgid="9050724687678157852">"உங்கள் மொபைலில் உள்ள படங்கள், மீடியா, அறிவிப்புகள் ஆகியவற்றை அணுக உங்கள் <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> சார்பாக <xliff:g id="APP_NAME">%1$s</xliff:g> ஆப்ஸ் அனுமதியைக் கோருகிறது"</string>
-    <string name="title_nearby_device_streaming" msgid="179278282547719200">"உங்கள் மொபைலில் இந்தச் செயலைச் செய்ய &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ஆப்ஸை அனுமதியுங்கள்"</string>
-    <string name="helper_title_nearby_device_streaming" msgid="6124438217620593669">"பன்முக சாதன சேவைகள்"</string>
-    <string name="helper_summary_nearby_device_streaming" msgid="5538329403511524333">"அருகிலுள்ள சாதனங்களுடன் உள்ளடக்கத்தை ஸ்ட்ரீம் செய்ய உங்கள் <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> சார்பாக <xliff:g id="APP_NAME">%1$s</xliff:g> அனுமதி கோருகிறது"</string>
+    <string name="helper_summary_computer" msgid="8774832742608187072">"உங்கள் மொபைலில் உள்ள படங்கள், மீடியா, அறிவிப்புகள் ஆகியவற்றை அணுக உங்கள் <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> சார்பாக <xliff:g id="APP_NAME">%1$s</xliff:g> ஆப்ஸ் அனுமதியைக் கோருகிறது"</string>
+    <string name="title_nearby_device_streaming" msgid="7269956847378799794">"இந்தச் செயலைச் செய்ய &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; ஐ அனுமதிக்கவா?"</string>
+    <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"அருகிலுள்ள சாதனங்களுக்கு ஆப்ஸையும் பிற சிஸ்டம் அம்சங்களையும் ஸ்ட்ரீம் செய்ய உங்கள் <xliff:g id="DEVICE_NAME">%2$s</xliff:g> சார்பாக <xliff:g id="APP_NAME">%1$s</xliff:g> அனுமதி கோருகிறது"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"சாதனம்"</string>
-    <!-- no translation found for summary_generic_single_device (4735072202474939111) -->
-    <skip />
-    <!-- no translation found for summary_generic (4988130802522924650) -->
-    <skip />
+    <string name="summary_generic" msgid="1761976003668044801">"அழைப்பவரின் பெயர் போன்ற தகவலை உங்கள் மொபைல் மற்றும் தேர்வுசெய்த சாதனத்திற்கு இடையில் இந்த ஆப்ஸால் ஒத்திசைக்க முடியும்"</string>
     <string name="consent_yes" msgid="8344487259618762872">"அனுமதி"</string>
     <string name="consent_no" msgid="2640796915611404382">"அனுமதிக்க வேண்டாம்"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"ரத்துசெய்"</string>
     <string name="consent_back" msgid="2560683030046918882">"பின்செல்"</string>
+    <string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> ஐ விரிவாக்கும்"</string>
+    <string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> ஐச் சுருக்கும்"</string>
     <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"&lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; சாதனத்தில் இருக்கும் அதே அனுமதிகளை &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; சாதனத்தில் உள்ள ஆப்ஸுக்கும் வழங்கவா?"</string>
     <string name="permission_sync_summary" msgid="765497944331294275">"&lt;strong&gt;மைக்ரோஃபோன்&lt;/strong&gt;, &lt;strong&gt;கேமரா&lt;/strong&gt;, &lt;strong&gt;இருப்பிட அணுகல்&lt;/strong&gt;, ஆகியவற்றுக்கான அனுமதிகளும் &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt; சாதனத்தில் உள்ள பிற பாதுகாக்கவேண்டிய தகவல்களுக்கான அனுமதிகளும் இதில் அடங்கக்கூடும். &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt; &lt;br/&gt;&lt;br/&gt;சாதனத்தில் உள்ள அமைப்புகளில் இந்த அனுமதிகளை எப்போது வேண்டுமானாலும் மாற்றிக்கொள்ளலாம்."</string>
-    <string name="vendor_icon_description" msgid="4445875290032225965">"ஆப்ஸ் ஐகான்"</string>
-    <string name="vendor_header_button_description" msgid="6566660389500630608">"கூடுதல் தகவல்கள் பட்டன்"</string>
+    <string name="vendor_header_button_description" msgid="7994879208461111473">"கூடுதல் தகவல்கள்"</string>
     <string name="permission_phone" msgid="2661081078692784919">"மொபைல்"</string>
     <string name="permission_sms" msgid="6337141296535774786">"மெசேஜ்"</string>
     <string name="permission_contacts" msgid="3858319347208004438">"தொடர்புகள்"</string>
@@ -64,16 +57,18 @@
     <string name="permission_storage" msgid="6831099350839392343">"படங்கள் மற்றும் மீடியா"</string>
     <string name="permission_notification" msgid="693762568127741203">"அறிவிப்புகள்"</string>
     <string name="permission_app_streaming" msgid="6009695219091526422">"ஆப்ஸ்"</string>
-    <string name="permission_nearby_device_streaming" msgid="5868108148065023161">"அருகிலுள்ள சாதன ஸ்ட்ரீமிங்"</string>
+    <string name="permission_nearby_device_streaming" msgid="1023325519477349499">"ஸ்ட்ரீமிங்"</string>
     <string name="permission_phone_summary" msgid="6684396967861278044">"மொபைல் அழைப்புகளைச் செய்யலாம் நிர்வகிக்கலாம்"</string>
     <string name="permission_call_logs_summary" msgid="6186103394658755022">"மொபைல் அழைப்புப் பதிவைப் படிக்கலாம் எழுதலாம்"</string>
     <string name="permission_sms_summary" msgid="3508442683678912017">"மெசேஜ்களை அனுப்பலாம் பார்க்கலாம்"</string>
     <string name="permission_contacts_summary" msgid="675861979475628708">"உங்கள் தொடர்புகளை அணுகலாம்"</string>
     <string name="permission_calendar_summary" msgid="6460000922511766226">"உங்கள் கேலெண்டரை அணுகலாம்"</string>
-    <string name="permission_microphone_summary" msgid="4241354865859396558">"மைக்ரோஃபோனைப் பயன்படுத்தி ஆடியோவை ரெக்கார்டு செய்யலாம்"</string>
+    <string name="permission_microphone_summary" msgid="3692091540613093394">"ஆடியோவை ரெக்கார்டு செய்யலாம்"</string>
     <string name="permission_nearby_devices_summary" msgid="931940524460876655">"அருகிலுள்ள சாதனங்களைக் கண்டறியலாம் அவற்றுடன் இணையலாம் அவற்றின் தூரத்தைத் தீர்மானிக்கலாம்"</string>
     <string name="permission_notification_summary" msgid="884075314530071011">"தொடர்புகள், மெசேஜ்கள், படங்கள் போன்ற தகவல்கள் உட்பட அனைத்து அறிவிப்புகளையும் படிக்க முடியும்"</string>
     <string name="permission_app_streaming_summary" msgid="606923325679670624">"உங்கள் மொபைல் ஆப்ஸை ஸ்ட்ரீம் செய்யலாம்"</string>
     <string name="permission_storage_summary" msgid="3918240895519506417"></string>
-    <string name="permission_nearby_device_streaming_summary" msgid="5776807830582725074">"அருகிலுள்ள சாதனத்தில் உள்ளடக்கத்தை ஸ்ட்ரீம் செய்யலாம்"</string>
+    <string name="permission_nearby_device_streaming_summary" msgid="8280824871197081246">"உங்கள் மொபைலில் இருந்து ஆப்ஸையும் பிற சிஸ்டம் அம்சங்களையும் ஸ்ட்ரீம் செய்யலாம்"</string>
+    <string name="device_type" product="default" msgid="8268703872070046263">"மொபைல்"</string>
+    <string name="device_type" product="tablet" msgid="5038791954983067774">"டேப்லெட்"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-te/strings.xml b/packages/CompanionDeviceManager/res/values-te/strings.xml
index 7c077ce..52316cd 100644
--- a/packages/CompanionDeviceManager/res/values-te/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-te/strings.xml
@@ -17,43 +17,36 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"సహచర పరికర మేనేజర్"</string>
-    <!-- no translation found for confirmation_title (8024993972587946678) -->
-    <skip />
+    <string name="confirmation_title" msgid="2244241995958340998">"&lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;‌ను యాక్సెస్ చేయడానికి &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; యాప్‌ను అనుమతించాలా?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"వాచ్"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"&lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; ద్వారా మేనేజ్ చేయబడటానికి ఒక <xliff:g id="PROFILE_NAME">%1$s</xliff:g>ను ఎంచుకోండి"</string>
-    <!-- no translation found for summary_watch (6566922405914995759) -->
-    <skip />
-    <!-- no translation found for summary_watch_single_device (7443464525873186735) -->
-    <skip />
-    <string name="profile_name_glasses" msgid="8488394059007275998">"గ్లాసెస్"</string>
-    <!-- no translation found for summary_glasses (3808267780579061241) -->
-    <skip />
-    <!-- no translation found for summary_glasses_single_device (7051392780285915640) -->
-    <skip />
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ద్వారా మేనేజ్ చేయబడే పరికరాన్ని ఎంచుకోండి"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"సెటప్ చేయడానికి <xliff:g id="PROFILE_NAME">%1$s</xliff:g>‌ను ఎంచుకోండి"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"కాల్ చేస్తున్న వారి పేరు వంటి సమాచారాన్ని సింక్ చేయడానికి, మీ <xliff:g id="DEVICE_NAME">%1$s</xliff:g>లో ఈ అనుమతులను యాక్సెస్ చేయడానికి ఈ యాప్ అనుమతించబడుతుంది"</string>
+    <string name="confirmation_title_glasses" msgid="8288346850537727333">"&lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;‌ను మేనేజ్ చేయడానికి &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;‌ను అనుమతించాలా?"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"పరికరం"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"మీ <xliff:g id="DEVICE_NAME">%1$s</xliff:g>లో ఈ అనుమతులను యాక్సెస్ చేయడానికి ఈ యాప్ అనుమతించబడుతుంది"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"మీ ఫోన్ నుండి ఈ సమాచారాన్ని యాక్సెస్ చేయడానికి &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; యాప్‌ను అనుమతించండి"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Cross-device services"</string>
-    <string name="helper_summary_app_streaming" msgid="5977509499890099">"మీ పరికరాల మధ్య యాప్‌లను స్ట్రీమ్ చేయడానికి <xliff:g id="APP_NAME">%1$s</xliff:g> మీ <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> తరఫున అనుమతిని రిక్వెస్ట్ చేస్తోంది"</string>
+    <string name="helper_summary_app_streaming" msgid="2396773196949578425">"మీ పరికరాల మధ్య యాప్‌లను స్ట్రీమ్ చేయడానికి <xliff:g id="APP_NAME">%1$s</xliff:g> మీ <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> తరఫున అనుమతిని రిక్వెస్ట్ చేస్తోంది"</string>
     <string name="title_automotive_projection" msgid="3296005598978412847"></string>
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"మీ ఫోన్ నుండి ఈ సమాచారాన్ని యాక్సెస్ చేయడానికి &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; యాప్‌ను అనుమతించండి"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Google Play సర్వీసులు"</string>
-    <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> మీ ఫోన్‌లోని ఫోటోలను, మీడియాను, ఇంకా నోటిఫికేషన్‌లను యాక్సెస్ చేయడానికి మీ <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> తరఫున అనుమతిని రిక్వెస్ట్ చేస్తోంది"</string>
-    <string name="title_nearby_device_streaming" msgid="179278282547719200">"మీ ఫోన్ నుండి ఈ చర్యను అమలు చేయడానికి &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;‌ను అనుమతించండి"</string>
-    <string name="helper_title_nearby_device_streaming" msgid="6124438217620593669">"క్రాస్-డివైజ్ సర్వీస్‌లు"</string>
-    <string name="helper_summary_nearby_device_streaming" msgid="5538329403511524333">"సమీపంలోని పరికరాలకు కంటెంట్‌ను స్ట్రీమ్ చేయడానికి <xliff:g id="APP_NAME">%1$s</xliff:g> మీ <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> తరఫున అనుమతిని రిక్వెస్ట్ చేస్తోంది"</string>
+    <string name="helper_summary_computer" msgid="8774832742608187072">"<xliff:g id="APP_NAME">%1$s</xliff:g> మీ ఫోన్‌లోని ఫోటోలను, మీడియాను, ఇంకా నోటిఫికేషన్‌లను యాక్సెస్ చేయడానికి మీ <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> తరఫున అనుమతిని రిక్వెస్ట్ చేస్తోంది"</string>
+    <string name="title_nearby_device_streaming" msgid="7269956847378799794">"ఈ చర్యను అమలు చేయడానికి &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt;‌ను అనుమతించాలా?"</string>
+    <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"సమీపంలోని పరికరాలకు యాప్‌లను, ఇతర సిస్టమ్ ఫీచర్‌లను స్ట్రీమ్ చేయడానికి <xliff:g id="APP_NAME">%1$s</xliff:g> మీ <xliff:g id="DEVICE_NAME">%2$s</xliff:g> తరఫున అనుమతిని రిక్వెస్ట్ చేస్తోంది"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"పరికరం"</string>
-    <!-- no translation found for summary_generic_single_device (4735072202474939111) -->
-    <skip />
-    <!-- no translation found for summary_generic (4988130802522924650) -->
-    <skip />
+    <string name="summary_generic" msgid="1761976003668044801">"కాల్ చేస్తున్న వారి పేరు వంటి సమాచారాన్ని ఈ యాప్ మీ ఫోన్ కు, ఎంచుకున్న పరికరానికీ మధ్య సింక్ చేయగలుగుతుంది"</string>
     <string name="consent_yes" msgid="8344487259618762872">"అనుమతించండి"</string>
     <string name="consent_no" msgid="2640796915611404382">"అనుమతించవద్దు"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"రద్దు చేయండి"</string>
     <string name="consent_back" msgid="2560683030046918882">"వెనుకకు"</string>
+    <string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>‌ను విస్తరించండి"</string>
+    <string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>‌ను కుదించండి"</string>
     <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"&lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt;లోని యాప్‌లకు &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;లో ఉన్న అనుమతులను ఇవ్వాలా?"</string>
     <string name="permission_sync_summary" msgid="765497944331294275">"వీటిలో భాగంగా &lt;strong&gt;మైక్రోఫోన్&lt;/strong&gt;, &lt;strong&gt;కెమెరా&lt;/strong&gt;, ఇంకా &lt;strong&gt;లొకేషన్ యాక్సెస్&lt;/strong&gt;, అలాగే &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;పై ఇతర గోప్యమైన సమాచార యాక్సెస్ అనుమతులు ఉండవచ్చు. &lt;br/&gt;&lt;br/&gt;ఈ అనుమతులను మీరు &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;లోని మీ సెట్టింగ్‌లలో ఎప్పుడైనా మార్చవచ్చు."</string>
-    <string name="vendor_icon_description" msgid="4445875290032225965">"యాప్ చిహ్నం"</string>
-    <string name="vendor_header_button_description" msgid="6566660389500630608">"మరింత సమాచారం బటన్"</string>
+    <string name="vendor_header_button_description" msgid="7994879208461111473">"మరింత సమాచారం"</string>
     <string name="permission_phone" msgid="2661081078692784919">"ఫోన్"</string>
     <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
     <string name="permission_contacts" msgid="3858319347208004438">"కాంటాక్ట్‌లు"</string>
@@ -64,16 +57,18 @@
     <string name="permission_storage" msgid="6831099350839392343">"ఫోటోలు, మీడియా"</string>
     <string name="permission_notification" msgid="693762568127741203">"నోటిఫికేషన్‌లు"</string>
     <string name="permission_app_streaming" msgid="6009695219091526422">"యాప్‌లు"</string>
-    <string name="permission_nearby_device_streaming" msgid="5868108148065023161">"సమీపంలోని పరికర స్ట్రీమింగ్"</string>
+    <string name="permission_nearby_device_streaming" msgid="1023325519477349499">"స్ట్రీమింగ్"</string>
     <string name="permission_phone_summary" msgid="6684396967861278044">"ఫోన్ కాల్స్ చేయగలదు, అలాగే మేనేజ్ చేయగలదు"</string>
     <string name="permission_call_logs_summary" msgid="6186103394658755022">"ఫోన్ కాల్ లాగ్‌ను చదవగలదు, రాయగలదు"</string>
     <string name="permission_sms_summary" msgid="3508442683678912017">"SMS మెసేజ్‌లను పంపగలదు, అలాగే చూడగలదు"</string>
     <string name="permission_contacts_summary" msgid="675861979475628708">"మీ కాంటాక్ట్‌లను యాక్సెస్ చేయగలదు"</string>
     <string name="permission_calendar_summary" msgid="6460000922511766226">"మీ క్యాలెండర్‌ను యాక్సెస్ చేయగలదు"</string>
-    <string name="permission_microphone_summary" msgid="4241354865859396558">"మైక్రోఫోన్‌ను ఉపయోగించి ఆడియోను రికార్డ్ చేయవచ్చు"</string>
+    <string name="permission_microphone_summary" msgid="3692091540613093394">"ఆడియోను రికార్డ్ చేయగలదు"</string>
     <string name="permission_nearby_devices_summary" msgid="931940524460876655">"సమీపంలోని పరికరాలను కనుగొనగలదు, వాటికి కనెక్ట్ అవ్వగలదు, అవి ఎంత దూరంలో ఉన్నాయో తెలుసుకొనగలదు"</string>
     <string name="permission_notification_summary" msgid="884075314530071011">"కాంటాక్ట్‌లు, మెసేజ్‌లు, ఫోటోల వంటి సమాచారంతో సహా అన్ని నోటిఫికేషన్‌లను చదవగలదు"</string>
     <string name="permission_app_streaming_summary" msgid="606923325679670624">"మీ ఫోన్‌లోని యాప్‌లను స్ట్రీమ్ చేయండి"</string>
     <string name="permission_storage_summary" msgid="3918240895519506417"></string>
-    <string name="permission_nearby_device_streaming_summary" msgid="5776807830582725074">"సమీపంలోని పరికరానికి కంటెంట్‌ను స్ట్రీమ్ చేయండి"</string>
+    <string name="permission_nearby_device_streaming_summary" msgid="8280824871197081246">"మీ ఫోన్ నుండి యాప్‌లను, ఇతర సిస్టమ్ ఫీచర్‌లను స్ట్రీమ్ చేస్తుంది"</string>
+    <string name="device_type" product="default" msgid="8268703872070046263">"ఫోన్"</string>
+    <string name="device_type" product="tablet" msgid="5038791954983067774">"టాబ్లెట్"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-th/strings.xml b/packages/CompanionDeviceManager/res/values-th/strings.xml
index 7438ab32..cb01f2d 100644
--- a/packages/CompanionDeviceManager/res/values-th/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-th/strings.xml
@@ -17,36 +17,36 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Companion Device Manager"</string>
-    <string name="confirmation_title" msgid="8024993972587946678">"อนุญาตให้ &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; เข้าถึง &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"อนุญาตให้แอป &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; เข้าถึง &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; ใช่ไหม"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"นาฬิกา"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"เลือก<xliff:g id="PROFILE_NAME">%1$s</xliff:g>ที่จะให้มีการจัดการโดย &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="6566922405914995759">"ต้องใช้แอปนี้ในการจัดการ<xliff:g id="DEVICE_NAME">%1$s</xliff:g> <xliff:g id="APP_NAME">%2$s</xliff:g> จะได้รับอนุญาตให้ซิงค์ข้อมูล เช่น ชื่อของบุคคลที่โทรเข้ามา โต้ตอบกับการแจ้งเตือน รวมถึงมีสิทธิ์เข้าถึงโทรศัพท์, SMS, รายชื่อติดต่อ, ปฏิทิน, บันทึกการโทร และอุปกรณ์ที่อยู่ใกล้เคียง"</string>
-    <string name="summary_watch_single_device" msgid="7443464525873186735">"ต้องใช้แอปนี้ในการจัดการ<xliff:g id="DEVICE_NAME">%1$s</xliff:g> <xliff:g id="APP_NAME">%2$s</xliff:g> จะได้รับอนุญาตให้ซิงค์ข้อมูล เช่น ชื่อของบุคคลที่โทรเข้ามา และมีสิทธิ์เข้าถึงสิ่งต่างๆ ต่อไปนี้"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"แว่นตา"</string>
-    <string name="summary_glasses" msgid="3808267780579061241">"ต้องใช้แอปนี้ในการจัดการ<xliff:g id="DEVICE_NAME">%1$s</xliff:g> <xliff:g id="APP_NAME">%2$s</xliff:g> จะได้รับอนุญาตให้โต้ตอบกับการแจ้งเตือนและมีสิทธิ์เข้าถึงโทรศัพท์, SMS, รายชื่อติดต่อ, ไมโครโฟน และอุปกรณ์ที่อยู่ใกล้เคียง"</string>
-    <string name="summary_glasses_single_device" msgid="7051392780285915640">"ต้องใช้แอปนี้ในการจัดการ<xliff:g id="DEVICE_NAME">%1$s</xliff:g> <xliff:g id="APP_NAME">%2$s</xliff:g> จะได้รับอนุญาตให้โต้ตอบกับสิทธิ์เหล่านี้"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"เลือกอุปกรณ์ที่จะให้มีการจัดการโดย &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"เลือก<xliff:g id="PROFILE_NAME">%1$s</xliff:g>ที่จะตั้งค่า"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"แอปนี้จะได้รับอนุญาตให้ซิงค์ข้อมูล เช่น ชื่อของบุคคลที่โทรเข้ามา และมีสิทธิ์เข้าถึงข้อมูลเหล่านี้ใน<xliff:g id="DEVICE_NAME">%1$s</xliff:g>ของคุณ"</string>
+    <string name="confirmation_title_glasses" msgid="8288346850537727333">"อนุญาตให้ &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; จัดการ &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; ไหม"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"อุปกรณ์"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"แอปนี้จะได้รับสิทธิ์ดังต่อไปนี้ใน<xliff:g id="DEVICE_NAME">%1$s</xliff:g>ของคุณ"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"อนุญาตให้ &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; เข้าถึงข้อมูลนี้จากโทรศัพท์ของคุณ"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"บริการหลายอุปกรณ์"</string>
-    <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> กำลังขอสิทธิ์ในนามของ <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> เพื่อสตรีมแอประหว่างอุปกรณ์ต่างๆ ของคุณ"</string>
+    <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g> กำลังขอสิทธิ์ในนามของ <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> เพื่อสตรีมแอประหว่างอุปกรณ์ต่างๆ ของคุณ"</string>
     <string name="title_automotive_projection" msgid="3296005598978412847"></string>
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"อนุญาตให้ &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; เข้าถึงข้อมูลนี้จากโทรศัพท์ของคุณ"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"บริการ Google Play"</string>
-    <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> กำลังขอสิทธิ์ในนามของ <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> เพื่อเข้าถึงรูปภาพ สื่อ และการแจ้งเตือนในโทรศัพท์ของคุณ"</string>
-    <string name="title_nearby_device_streaming" msgid="179278282547719200">"อนุญาตให้ &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ทำงานนี้จากโทรศัพท์"</string>
-    <string name="helper_title_nearby_device_streaming" msgid="6124438217620593669">"บริการหลายอุปกรณ์"</string>
-    <string name="helper_summary_nearby_device_streaming" msgid="5538329403511524333">"<xliff:g id="APP_NAME">%1$s</xliff:g> กำลังขอสิทธิ์ในนามของ <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> เพื่อสตรีมเนื้อหาไปยังอุปกรณ์ที่อยู่ใกล้เคียง"</string>
+    <string name="helper_summary_computer" msgid="8774832742608187072">"<xliff:g id="APP_NAME">%1$s</xliff:g> กำลังขอสิทธิ์ในนามของ <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> เพื่อเข้าถึงรูปภาพ สื่อ และการแจ้งเตือนในโทรศัพท์ของคุณ"</string>
+    <string name="title_nearby_device_streaming" msgid="7269956847378799794">"อนุญาตให้ &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; ทำงานนี้ไหม"</string>
+    <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> กำลังขอสิทธิ์ในนามของ <xliff:g id="DEVICE_NAME">%2$s</xliff:g> เพื่อสตรีมแอปและฟีเจอร์อื่นๆ ของระบบไปยังอุปกรณ์ที่อยู่ใกล้เคียง"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"อุปกรณ์"</string>
-    <string name="summary_generic_single_device" msgid="4735072202474939111">"แอปนี้จะสามารถซิงค์ข้อมูล เช่น ชื่อของบุคคลที่โทรเข้ามา ระหว่างโทรศัพท์ของคุณและ<xliff:g id="DEVICE_NAME">%1$s</xliff:g>ได้"</string>
-    <string name="summary_generic" msgid="4988130802522924650">"แอปนี้จะสามารถซิงค์ข้อมูล เช่น ชื่อของบุคคลที่โทรเข้ามา ระหว่างโทรศัพท์ของคุณและอุปกรณ์ที่เลือกไว้ได้"</string>
+    <string name="summary_generic" msgid="1761976003668044801">"แอปนี้จะสามารถซิงค์ข้อมูล เช่น ชื่อของบุคคลที่โทรเข้ามา ระหว่างโทรศัพท์ของคุณและอุปกรณ์ที่เลือกไว้ได้"</string>
     <string name="consent_yes" msgid="8344487259618762872">"อนุญาต"</string>
     <string name="consent_no" msgid="2640796915611404382">"ไม่อนุญาต"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"ยกเลิก"</string>
     <string name="consent_back" msgid="2560683030046918882">"กลับ"</string>
+    <string name="permission_expand" msgid="893185038020887411">"ขยาย <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
+    <string name="permission_collapse" msgid="3320833884220844084">"ยุบ <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"ให้แอปใน &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; มีสิทธิ์เหมือนกับใน &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; ไหม"</string>
     <string name="permission_sync_summary" msgid="765497944331294275">"โดยอาจรวมถึงสิทธิ์เข้าถึง &lt;strong&gt;ไมโครโฟน&lt;/strong&gt; &lt;strong&gt;กล้อง&lt;/strong&gt; และ&lt;strong&gt;ตำแหน่ง&lt;/strong&gt; ตลอดจนสิทธิ์ที่มีความละเอียดอ่อนอื่นๆ ใน &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt; &lt;br/&gt;&lt;br/&gt;คุณเปลี่ยนแปลงสิทธิ์เหล่านี้ได้ทุกเมื่อในการตั้งค่าบน &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="vendor_icon_description" msgid="4445875290032225965">"ไอคอนแอป"</string>
-    <string name="vendor_header_button_description" msgid="6566660389500630608">"ปุ่มข้อมูลเพิ่มเติม"</string>
+    <string name="vendor_header_button_description" msgid="7994879208461111473">"ข้อมูลเพิ่มเติม"</string>
     <string name="permission_phone" msgid="2661081078692784919">"โทรศัพท์"</string>
     <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
     <string name="permission_contacts" msgid="3858319347208004438">"รายชื่อติดต่อ"</string>
@@ -57,16 +57,18 @@
     <string name="permission_storage" msgid="6831099350839392343">"รูปภาพและสื่อ"</string>
     <string name="permission_notification" msgid="693762568127741203">"การแจ้งเตือน"</string>
     <string name="permission_app_streaming" msgid="6009695219091526422">"แอป"</string>
-    <string name="permission_nearby_device_streaming" msgid="5868108148065023161">"การสตรีมไปยังอุปกรณ์ที่อยู่ใกล้เคียง"</string>
+    <string name="permission_nearby_device_streaming" msgid="1023325519477349499">"สตรีมมิง"</string>
     <string name="permission_phone_summary" msgid="6684396967861278044">"สามารถโทรออกและจัดการการโทร"</string>
     <string name="permission_call_logs_summary" msgid="6186103394658755022">"สามารถอ่านและเขียนบันทึกการโทรของโทรศัพท์"</string>
     <string name="permission_sms_summary" msgid="3508442683678912017">"สามารถส่งและดูข้อความ SMS"</string>
     <string name="permission_contacts_summary" msgid="675861979475628708">"สามารถเข้าถึงรายชื่อติดต่อของคุณ"</string>
     <string name="permission_calendar_summary" msgid="6460000922511766226">"สามารถเข้าถึงปฏิทินของคุณ"</string>
-    <string name="permission_microphone_summary" msgid="4241354865859396558">"บันทึกเสียงโดยใช้ไมโครโฟนได้"</string>
+    <string name="permission_microphone_summary" msgid="3692091540613093394">"บันทึกเสียงได้"</string>
     <string name="permission_nearby_devices_summary" msgid="931940524460876655">"สามารถค้นหา เชื่อมต่อ และระบุตำแหน่งซึ่งสัมพันธ์กับอุปกรณ์ที่อยู่ใกล้เคียง"</string>
     <string name="permission_notification_summary" msgid="884075314530071011">"สามารถอ่านการแจ้งเตือนทั้งหมด รวมถึงข้อมูลอย่างรายชื่อติดต่อ ข้อความ และรูปภาพ"</string>
     <string name="permission_app_streaming_summary" msgid="606923325679670624">"สตรีมแอปของโทรศัพท์คุณ"</string>
     <string name="permission_storage_summary" msgid="3918240895519506417"></string>
-    <string name="permission_nearby_device_streaming_summary" msgid="5776807830582725074">"สตรีมเนื้อหาไปยังอุปกรณ์ที่อยู่ใกล้เคียง"</string>
+    <string name="permission_nearby_device_streaming_summary" msgid="8280824871197081246">"สตรีมแอปและฟีเจอร์อื่นๆ ของระบบจากโทรศัพท์"</string>
+    <string name="device_type" product="default" msgid="8268703872070046263">"โทรศัพท์"</string>
+    <string name="device_type" product="tablet" msgid="5038791954983067774">"แท็บเล็ต"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-tl/strings.xml b/packages/CompanionDeviceManager/res/values-tl/strings.xml
index 2fd2eda..43d5996 100644
--- a/packages/CompanionDeviceManager/res/values-tl/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-tl/strings.xml
@@ -17,43 +17,36 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Kasamang Device Manager"</string>
-    <!-- no translation found for confirmation_title (8024993972587946678) -->
-    <skip />
+    <string name="confirmation_title" msgid="2244241995958340998">"Payagan ang app na &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; na i-access ang &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"relo"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Pumili ng <xliff:g id="PROFILE_NAME">%1$s</xliff:g> para pamahalaan ng &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <!-- no translation found for summary_watch (6566922405914995759) -->
-    <skip />
-    <!-- no translation found for summary_watch_single_device (7443464525873186735) -->
-    <skip />
-    <string name="profile_name_glasses" msgid="8488394059007275998">"salamin"</string>
-    <!-- no translation found for summary_glasses (3808267780579061241) -->
-    <skip />
-    <!-- no translation found for summary_glasses_single_device (7051392780285915640) -->
-    <skip />
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"Pumili ng device na papamahalaan ng &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Pumili ng <xliff:g id="PROFILE_NAME">%1$s</xliff:g> para mag-set up"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Papayagan ang app na ito na mag-sync ng impormasyon, tulad ng pangalan ng taong tumatawag, at i-access ang mga pahintulot na ito sa iyong <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
+    <string name="confirmation_title_glasses" msgid="8288346850537727333">"Payagan ang &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; na pamahalaan ang &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"device"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Papayagan ang app na ito na i-access ang mga pahintulot na ito sa iyong <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Payagan ang &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; na i-access ang impormasyong ito sa iyong telepono"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Mga cross-device na serbisyo"</string>
-    <string name="helper_summary_app_streaming" msgid="5977509499890099">"Ang <xliff:g id="APP_NAME">%1$s</xliff:g> ay humihiling ng pahintulot sa ngalan ng iyong <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> para mag-stream ng mga app sa pagitan ng mga device mo"</string>
+    <string name="helper_summary_app_streaming" msgid="2396773196949578425">"Ang <xliff:g id="APP_NAME">%1$s</xliff:g> ay humihiling ng pahintulot sa ngalan ng iyong <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> para mag-stream ng mga app sa pagitan ng mga device mo"</string>
     <string name="title_automotive_projection" msgid="3296005598978412847"></string>
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"Payagan ang &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; na i-access ang impormasyon sa iyong telepono"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Mga serbisyo ng Google Play"</string>
-    <string name="helper_summary_computer" msgid="9050724687678157852">"Ang <xliff:g id="APP_NAME">%1$s</xliff:g> ay humihiling ng pahintulot sa ngalan ng iyong <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> para i-access ang mga larawan, media, at notification ng telepono mo"</string>
-    <string name="title_nearby_device_streaming" msgid="179278282547719200">"Payagan ang &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; na gawin ang pagkilos na ito mula sa iyong telepono"</string>
-    <string name="helper_title_nearby_device_streaming" msgid="6124438217620593669">"Mga cross-device na serbisyo"</string>
-    <string name="helper_summary_nearby_device_streaming" msgid="5538329403511524333">"Humihiling ng pahintulot ang <xliff:g id="APP_NAME">%1$s</xliff:g> sa ngalan ng iyong <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> para mag-stream ng content sa mga kalapit na device"</string>
+    <string name="helper_summary_computer" msgid="8774832742608187072">"Ang <xliff:g id="APP_NAME">%1$s</xliff:g> ay humihiling ng pahintulot sa ngalan ng iyong <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> para i-access ang mga larawan, media, at notification ng telepono mo"</string>
+    <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Payagan ang &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; na gawin ang pagkilos na ito?"</string>
+    <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"Humihiling ang <xliff:g id="APP_NAME">%1$s</xliff:g> ng pahintulot sa ngalan ng iyong <xliff:g id="DEVICE_NAME">%2$s</xliff:g> para mag-stream ng mga app at iba pang feature ng system sa mga kalapit na device"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"device"</string>
-    <!-- no translation found for summary_generic_single_device (4735072202474939111) -->
-    <skip />
-    <!-- no translation found for summary_generic (4988130802522924650) -->
-    <skip />
+    <string name="summary_generic" msgid="1761976003668044801">"Magagawa ng app na ito na mag-sync ng impormasyon, tulad ng pangalan ng isang taong tumatawag, sa pagitan ng iyong telepono at ng napiling device"</string>
     <string name="consent_yes" msgid="8344487259618762872">"Payagan"</string>
     <string name="consent_no" msgid="2640796915611404382">"Huwag payagan"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Kanselahin"</string>
     <string name="consent_back" msgid="2560683030046918882">"Bumalik"</string>
+    <string name="permission_expand" msgid="893185038020887411">"I-expand ang <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
+    <string name="permission_collapse" msgid="3320833884220844084">"I-collapse ang <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Bigyan ang mga app sa &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; ng mga pahintulot na mayroon din sa &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="permission_sync_summary" msgid="765497944331294275">"Posibleng kasama rito ang &lt;strong&gt;access sa Mikropono&lt;/strong&gt;, &lt;strong&gt;Camera&lt;/strong&gt;, at &lt;strong&gt;Lokasyon&lt;/strong&gt;, at iba pang pahintulot sa sensitibong impormasyon sa &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;. &lt;br/&gt;&lt;br/&gt;Puwede mong baguhin ang mga pahintulot na ito anumang oras sa iyong Mga Setting sa &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;."</string>
-    <string name="vendor_icon_description" msgid="4445875290032225965">"Icon ng App"</string>
-    <string name="vendor_header_button_description" msgid="6566660389500630608">"Button ng Dagdag Impormasyon"</string>
+    <string name="vendor_header_button_description" msgid="7994879208461111473">"Higit Pang Impormasyon"</string>
     <string name="permission_phone" msgid="2661081078692784919">"Telepono"</string>
     <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
     <string name="permission_contacts" msgid="3858319347208004438">"Mga Contact"</string>
@@ -64,16 +57,18 @@
     <string name="permission_storage" msgid="6831099350839392343">"Mga larawan at media"</string>
     <string name="permission_notification" msgid="693762568127741203">"Mga Notification"</string>
     <string name="permission_app_streaming" msgid="6009695219091526422">"Mga App"</string>
-    <string name="permission_nearby_device_streaming" msgid="5868108148065023161">"Streaming sa Kalapit na Device"</string>
+    <string name="permission_nearby_device_streaming" msgid="1023325519477349499">"Streaming"</string>
     <string name="permission_phone_summary" msgid="6684396967861278044">"Puwedeng gumawa at mamahala ng mga tawag sa telepono"</string>
     <string name="permission_call_logs_summary" msgid="6186103394658755022">"Puwedeng magbasa at magsulat ng log ng tawag sa telepono"</string>
     <string name="permission_sms_summary" msgid="3508442683678912017">"Puwedeng magpadala at tumingin ng mga SMS message"</string>
     <string name="permission_contacts_summary" msgid="675861979475628708">"Puwedeng mag-access ng iyong mga contact"</string>
     <string name="permission_calendar_summary" msgid="6460000922511766226">"Puwedeng mag-access ng iyong kalendaryo"</string>
-    <string name="permission_microphone_summary" msgid="4241354865859396558">"Puwedeng mag-record ng audio gamit ang mikropono"</string>
+    <string name="permission_microphone_summary" msgid="3692091540613093394">"Kayang mag-record ng audio"</string>
     <string name="permission_nearby_devices_summary" msgid="931940524460876655">"Puwedeng mahanap ang, kumonekta sa, at tukuyin ang relatibong posisyon ng mga kalapit na device"</string>
     <string name="permission_notification_summary" msgid="884075314530071011">"Magbasa ng lahat ng notification, kabilang ang impormasyon gaya ng mga contact, mensahe, at larawan"</string>
     <string name="permission_app_streaming_summary" msgid="606923325679670624">"I-stream ang mga app ng iyong telepono"</string>
     <string name="permission_storage_summary" msgid="3918240895519506417"></string>
-    <string name="permission_nearby_device_streaming_summary" msgid="5776807830582725074">"Mag-stream ng content sa kalapit na device"</string>
+    <string name="permission_nearby_device_streaming_summary" msgid="8280824871197081246">"Mag-stream ng mga app at iba pang feature ng system mula sa iyong telepono"</string>
+    <string name="device_type" product="default" msgid="8268703872070046263">"telepono"</string>
+    <string name="device_type" product="tablet" msgid="5038791954983067774">"tablet"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-tr/strings.xml b/packages/CompanionDeviceManager/res/values-tr/strings.xml
index 8a73c65..1e8e842 100644
--- a/packages/CompanionDeviceManager/res/values-tr/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-tr/strings.xml
@@ -17,43 +17,36 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Companion Device Manager"</string>
-    <!-- no translation found for confirmation_title (8024993972587946678) -->
-    <skip />
+    <string name="confirmation_title" msgid="2244241995958340998">"&lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; cihazına erişmesi için &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; uygulamasına izin verin"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"saat"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"&lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; tarafından yönetilecek bir <xliff:g id="PROFILE_NAME">%1$s</xliff:g> seçin"</string>
-    <!-- no translation found for summary_watch (6566922405914995759) -->
-    <skip />
-    <!-- no translation found for summary_watch_single_device (7443464525873186735) -->
-    <skip />
-    <string name="profile_name_glasses" msgid="8488394059007275998">"glasses"</string>
-    <!-- no translation found for summary_glasses (3808267780579061241) -->
-    <skip />
-    <!-- no translation found for summary_glasses_single_device (7051392780285915640) -->
-    <skip />
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; tarafından yönetilecek bir cihaz seçin"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Ayarlamak için bir <xliff:g id="PROFILE_NAME">%1$s</xliff:g> seçin"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Bu uygulamanın arayan kişinin adı gibi bilgileri senkronize etmesine ve <xliff:g id="DEVICE_NAME">%1$s</xliff:g> cihazınızda aşağıdaki izinlere erişmesine izin verilir"</string>
+    <string name="confirmation_title_glasses" msgid="8288346850537727333">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; uygulamasına &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; cihazını yönetmesi için izin verilsin mi?"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"Cihaz"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Bu uygulamanın <xliff:g id="DEVICE_NAME">%1$s</xliff:g> cihazınızda şu izinlere erişmesine izin verilecek:"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; uygulamasının, telefonunuzdaki bu bilgilere erişmesine izin verin"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Cihazlar arası hizmetler"</string>
-    <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g>, cihazlarınız arasında uygulama akışı gerçekleştirmek için <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> cihazınız adına izin istiyor"</string>
+    <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g>, cihazlarınız arasında uygulama akışı gerçekleştirmek için <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> cihazınız adına izin istiyor"</string>
     <string name="title_automotive_projection" msgid="3296005598978412847"></string>
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; uygulamasının, telefonunuzdaki bu bilgilere erişmesine izin verin"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
-    <string name="helper_title_computer" msgid="4671071173916176037">"Google Play hizmetleri"</string>
-    <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g>, telefonunuzdaki fotoğraf, medya ve bildirimlere erişmek için <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> cihazınız adına izin istiyor"</string>
-    <string name="title_nearby_device_streaming" msgid="179278282547719200">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; uygulamasının bu işlemi telefonunuzda gerçekleştirmesine izin verin"</string>
-    <string name="helper_title_nearby_device_streaming" msgid="6124438217620593669">"Cihazlar arası hizmetler"</string>
-    <string name="helper_summary_nearby_device_streaming" msgid="5538329403511524333">"<xliff:g id="APP_NAME">%1$s</xliff:g>, yakındaki cihazlarda içerikleri canlı oynatmak için <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> cihazınız adına izin istiyor"</string>
+    <string name="helper_title_computer" msgid="4671071173916176037">"Google Play Hizmetleri"</string>
+    <string name="helper_summary_computer" msgid="8774832742608187072">"<xliff:g id="APP_NAME">%1$s</xliff:g>, telefonunuzdaki fotoğraf, medya ve bildirimlere erişmek için <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> cihazınız adına izin istiyor"</string>
+    <string name="title_nearby_device_streaming" msgid="7269956847378799794">"&lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; cihazının bu işlemi yapmasına izin verilsin mi?"</string>
+    <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> uygulaması <xliff:g id="DEVICE_NAME">%2$s</xliff:g> cihazınız adına uygulamaları ve diğer sistem özelliklerini yakındaki cihazlara aktarmak için izin istiyor"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"cihaz"</string>
-    <!-- no translation found for summary_generic_single_device (4735072202474939111) -->
-    <skip />
-    <!-- no translation found for summary_generic (4988130802522924650) -->
-    <skip />
+    <string name="summary_generic" msgid="1761976003668044801">"Bu uygulama, arayan kişinin adı gibi bilgileri telefonunuz ve seçili cihaz arasında senkronize edebilir"</string>
     <string name="consent_yes" msgid="8344487259618762872">"İzin ver"</string>
     <string name="consent_no" msgid="2640796915611404382">"İzin verme"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"İptal"</string>
     <string name="consent_back" msgid="2560683030046918882">"Geri"</string>
+    <string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> panelini genişlet"</string>
+    <string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> panelini daralt"</string>
     <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"&lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; cihazındaki uygulamalara, &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; cihazındakiyle aynı izinler verilsin mi?"</string>
     <string name="permission_sync_summary" msgid="765497944331294275">"Bu; &lt;strong&gt;Mikrofon&lt;/strong&gt;, &lt;strong&gt;Kamera&lt;/strong&gt; ve &lt;strong&gt;Konum erişimi&lt;/strong&gt; izinlerinin yanı sıra &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt; cihazındaki diğer hassas bilgilere erişim izinlerini içerebilir. &lt;br/&gt;&lt;br/&gt;&lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt; cihazının Ayarlar bölümünden istediğiniz zaman bu izinleri değiştirebilirsiniz."</string>
-    <string name="vendor_icon_description" msgid="4445875290032225965">"Uygulama Simgesi"</string>
-    <string name="vendor_header_button_description" msgid="6566660389500630608">"Daha Fazla Bilgi Düğmesi"</string>
+    <string name="vendor_header_button_description" msgid="7994879208461111473">"Daha Fazla Bilgi"</string>
     <string name="permission_phone" msgid="2661081078692784919">"Telefon"</string>
     <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
     <string name="permission_contacts" msgid="3858319347208004438">"Kişiler"</string>
@@ -64,16 +57,18 @@
     <string name="permission_storage" msgid="6831099350839392343">"Fotoğraflar ve medya"</string>
     <string name="permission_notification" msgid="693762568127741203">"Bildirimler"</string>
     <string name="permission_app_streaming" msgid="6009695219091526422">"Uygulamalar"</string>
-    <string name="permission_nearby_device_streaming" msgid="5868108148065023161">"Yakındaki Cihazda Oynatma"</string>
+    <string name="permission_nearby_device_streaming" msgid="1023325519477349499">"Yayınlama"</string>
     <string name="permission_phone_summary" msgid="6684396967861278044">"Telefon aramaları yapabilir ve telefon aramalarını yönetebilir"</string>
-    <string name="permission_call_logs_summary" msgid="6186103394658755022">"Telefon arama kaydını okuma ve yazma"</string>
+    <string name="permission_call_logs_summary" msgid="6186103394658755022">"Telefon arama kaydını okuyabilir ve yazabilir"</string>
     <string name="permission_sms_summary" msgid="3508442683678912017">"SMS mesajları gönderebilir ve görüntüleyebilir"</string>
     <string name="permission_contacts_summary" msgid="675861979475628708">"Kişilerinize erişebilir"</string>
     <string name="permission_calendar_summary" msgid="6460000922511766226">"Takviminize erişebilir"</string>
-    <string name="permission_microphone_summary" msgid="4241354865859396558">"Mikrofonu kullanarak ses kaydedebilir"</string>
+    <string name="permission_microphone_summary" msgid="3692091540613093394">"Ses kaydedebilir"</string>
     <string name="permission_nearby_devices_summary" msgid="931940524460876655">"Yakındaki cihazları keşfedip bağlanabilir ve bu cihazların göreli konumunu belirleyebilir"</string>
     <string name="permission_notification_summary" msgid="884075314530071011">"Kişiler, mesajlar ve fotoğraflar da dahil olmak üzere tüm bildirimleri okuyabilir"</string>
-    <string name="permission_app_streaming_summary" msgid="606923325679670624">"Telefonunuzun uygulamalarını yayınlama"</string>
+    <string name="permission_app_streaming_summary" msgid="606923325679670624">"Telefonunuzun uygulamalarını yayınlayabilir"</string>
     <string name="permission_storage_summary" msgid="3918240895519506417"></string>
-    <string name="permission_nearby_device_streaming_summary" msgid="5776807830582725074">"Yakındaki bir cihazda içerikleri canlı oynatın"</string>
+    <string name="permission_nearby_device_streaming_summary" msgid="8280824871197081246">"Telefonunuzdan uygulamaları ve diğer sistem özelliklerini yayınlayabilir"</string>
+    <string name="device_type" product="default" msgid="8268703872070046263">"telefon"</string>
+    <string name="device_type" product="tablet" msgid="5038791954983067774">"tablet"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-uk/strings.xml b/packages/CompanionDeviceManager/res/values-uk/strings.xml
index 1ba997e..39a2b1b 100644
--- a/packages/CompanionDeviceManager/res/values-uk/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-uk/strings.xml
@@ -17,43 +17,36 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Диспетчер супутніх пристроїв"</string>
-    <!-- no translation found for confirmation_title (8024993972587946678) -->
-    <skip />
+    <string name="confirmation_title" msgid="2244241995958340998">"Дозволити додатку &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; доступ до інформації на пристрої &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"годинник"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Виберіть <xliff:g id="PROFILE_NAME">%1$s</xliff:g>, яким керуватиме додаток &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <!-- no translation found for summary_watch (6566922405914995759) -->
-    <skip />
-    <!-- no translation found for summary_watch_single_device (7443464525873186735) -->
-    <skip />
-    <string name="profile_name_glasses" msgid="8488394059007275998">"окуляри"</string>
-    <!-- no translation found for summary_glasses (3808267780579061241) -->
-    <skip />
-    <!-- no translation found for summary_glasses_single_device (7051392780285915640) -->
-    <skip />
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"Виберіть пристрій, яким керуватиме додаток &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Виберіть <xliff:g id="PROFILE_NAME">%1$s</xliff:g> для налаштування"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Цей додаток зможе синхронізувати інформацію (наприклад, ім’я абонента, який викликає) і отримає доступ до перелічених нижче дозволів на вашому <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
+    <string name="confirmation_title_glasses" msgid="8288346850537727333">"Дозволити додатку &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; керувати пристроєм &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"пристрій"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Цей додаток матиме доступ до перелічених нижче дозволів на вашому <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Надайте додатку &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; доступ до цієї інформації з телефона"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Сервіси для кількох пристроїв"</string>
-    <string name="helper_summary_app_streaming" msgid="5977509499890099">"Додаток <xliff:g id="APP_NAME">%1$s</xliff:g> від імені вашого пристрою <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> запитує дозвіл на трансляцію додатків між вашими пристроями"</string>
+    <string name="helper_summary_app_streaming" msgid="2396773196949578425">"Додаток <xliff:g id="APP_NAME">%1$s</xliff:g> від імені вашого пристрою \"<xliff:g id="DISPLAY_NAME">%2$s</xliff:g>\" запитує дозвіл на трансляцію додатків між вашими пристроями"</string>
     <string name="title_automotive_projection" msgid="3296005598978412847"></string>
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"Надайте пристрою &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; доступ до цієї інформації з телефона"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Сервіси Google Play"</string>
-    <string name="helper_summary_computer" msgid="9050724687678157852">"Додаток <xliff:g id="APP_NAME">%1$s</xliff:g> від імені вашого пристрою <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> запитує дозвіл на доступ до фотографій, медіафайлів і сповіщень вашого телефона"</string>
-    <string name="title_nearby_device_streaming" msgid="179278282547719200">"Дозволити додатку &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; виконувати цю дію на вашому телефоні"</string>
-    <string name="helper_title_nearby_device_streaming" msgid="6124438217620593669">"Сервіси для кількох пристроїв"</string>
-    <string name="helper_summary_nearby_device_streaming" msgid="5538329403511524333">"Додаток <xliff:g id="APP_NAME">%1$s</xliff:g> від імені вашого пристрою (<xliff:g id="DEVICE_TYPE">%2$s</xliff:g>) запитує дозвіл на трансляцію контенту на пристрої поблизу"</string>
+    <string name="helper_summary_computer" msgid="8774832742608187072">"Додаток <xliff:g id="APP_NAME">%1$s</xliff:g> від імені вашого пристрою \"<xliff:g id="DISPLAY_NAME">%2$s</xliff:g>\" запитує дозвіл на доступ до фотографій, медіафайлів і сповіщень вашого телефона"</string>
+    <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Дозволити додатку &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; виконувати цю дію?"</string>
+    <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"Додаток <xliff:g id="APP_NAME">%1$s</xliff:g> від імені вашого пристрою (<xliff:g id="DEVICE_NAME">%2$s</xliff:g>) запитує дозвіл на трансляцію додатків та інших системних функцій на пристрої поблизу"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"пристрій"</string>
-    <!-- no translation found for summary_generic_single_device (4735072202474939111) -->
-    <skip />
-    <!-- no translation found for summary_generic (4988130802522924650) -->
-    <skip />
+    <string name="summary_generic" msgid="1761976003668044801">"Цей додаток зможе синхронізувати інформацію (наприклад, ім’я абонента, який викликає) між телефоном і вибраним пристроєм"</string>
     <string name="consent_yes" msgid="8344487259618762872">"Дозволити"</string>
     <string name="consent_no" msgid="2640796915611404382">"Не дозволяти"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Скасувати"</string>
     <string name="consent_back" msgid="2560683030046918882">"Назад"</string>
+    <string name="permission_expand" msgid="893185038020887411">"Розгорнути дозвіл \"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>\""</string>
+    <string name="permission_collapse" msgid="3320833884220844084">"Згорнути дозвіл \"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>\""</string>
     <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Надати додаткам на пристрої &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; такі самі дозволи, що й на пристрої &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="permission_sync_summary" msgid="765497944331294275">"Це можуть бути дозволи &lt;strong&gt;Мікрофон&lt;/strong&gt;, &lt;strong&gt;Камера&lt;/strong&gt;, &lt;strong&gt;Геодані&lt;/strong&gt;, а також інші дозволи на доступ до чутливих даних на пристрої &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;. &lt;br/&gt;&lt;br/&gt;Ви можете будь-коли змінити ці дозволи в налаштуваннях на пристрої &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;."</string>
-    <string name="vendor_icon_description" msgid="4445875290032225965">"Значок додатка"</string>
-    <string name="vendor_header_button_description" msgid="6566660389500630608">"Кнопка \"Докладніше\""</string>
+    <string name="vendor_header_button_description" msgid="7994879208461111473">"Докладніше"</string>
     <string name="permission_phone" msgid="2661081078692784919">"Телефон"</string>
     <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
     <string name="permission_contacts" msgid="3858319347208004438">"Контакти"</string>
@@ -64,16 +57,18 @@
     <string name="permission_storage" msgid="6831099350839392343">"Фотографії та медіафайли"</string>
     <string name="permission_notification" msgid="693762568127741203">"Сповіщення"</string>
     <string name="permission_app_streaming" msgid="6009695219091526422">"Додатки"</string>
-    <string name="permission_nearby_device_streaming" msgid="5868108148065023161">"Трансляція на пристрої поблизу"</string>
+    <string name="permission_nearby_device_streaming" msgid="1023325519477349499">"Потокове передавання"</string>
     <string name="permission_phone_summary" msgid="6684396967861278044">"Може здійснювати телефонні виклики й керувати ними"</string>
     <string name="permission_call_logs_summary" msgid="6186103394658755022">"Може переглядати й редагувати журнал викликів телефона"</string>
     <string name="permission_sms_summary" msgid="3508442683678912017">"Може надсилати й переглядати SMS-повідомлення"</string>
     <string name="permission_contacts_summary" msgid="675861979475628708">"Має доступ до ваших контактів"</string>
     <string name="permission_calendar_summary" msgid="6460000922511766226">"Має доступ до вашого календаря"</string>
-    <string name="permission_microphone_summary" msgid="4241354865859396558">"Може записувати звук за допомогою мікрофона"</string>
+    <string name="permission_microphone_summary" msgid="3692091540613093394">"Може записувати аудіо"</string>
     <string name="permission_nearby_devices_summary" msgid="931940524460876655">"Може знаходити пристрої поблизу, підключатися до них і визначати їх відносне розташування"</string>
     <string name="permission_notification_summary" msgid="884075314530071011">"Може читати всі сповіщення, зокрема таку інформацію, як контакти, повідомлення та фотографії"</string>
     <string name="permission_app_streaming_summary" msgid="606923325679670624">"Транслювати додатки телефона"</string>
     <string name="permission_storage_summary" msgid="3918240895519506417"></string>
-    <string name="permission_nearby_device_streaming_summary" msgid="5776807830582725074">"Транслювати контент на пристрої поблизу"</string>
+    <string name="permission_nearby_device_streaming_summary" msgid="8280824871197081246">"Транслюйте додатки й інші системні функції зі свого телефона"</string>
+    <string name="device_type" product="default" msgid="8268703872070046263">"телефоні"</string>
+    <string name="device_type" product="tablet" msgid="5038791954983067774">"планшеті"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-ur/strings.xml b/packages/CompanionDeviceManager/res/values-ur/strings.xml
index f83e216d..ce44941 100644
--- a/packages/CompanionDeviceManager/res/values-ur/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ur/strings.xml
@@ -17,43 +17,36 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"ساتھی آلہ مینیجر"</string>
-    <!-- no translation found for confirmation_title (8024993972587946678) -->
-    <skip />
+    <string name="confirmation_title" msgid="2244241995958340998">"‏ایپ ‎&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>‎&lt;/strong&gt;‎ کو ‎‎&lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;‎ تک رسائی کی اجازت دیں؟"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"دیکھیں"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"‏&lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; کے ذریعے نظم کئے جانے کیلئے <xliff:g id="PROFILE_NAME">%1$s</xliff:g> کو منتخب کریں"</string>
-    <!-- no translation found for summary_watch (6566922405914995759) -->
-    <skip />
-    <!-- no translation found for summary_watch_single_device (7443464525873186735) -->
-    <skip />
-    <string name="profile_name_glasses" msgid="8488394059007275998">"گلاسز"</string>
-    <!-- no translation found for summary_glasses (3808267780579061241) -->
-    <skip />
-    <!-- no translation found for summary_glasses_single_device (7051392780285915640) -->
-    <skip />
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"‏‎&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;‎ کے ذریعے منتخب کیے جانے کیلئے آلہ منتخب کریں"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"سیٹ اپ کرنے کے لیے <xliff:g id="PROFILE_NAME">%1$s</xliff:g> کا انتخاب کریں"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"اس ایپ کو آپ کے <xliff:g id="DEVICE_NAME">%1$s</xliff:g> پر کسی کال کرنے والے کے نام جیسی معلومات کی مطابقت پذیری کرنے اور ان اجازتوں تک رسائی کی اجازت ہوگی"</string>
+    <string name="confirmation_title_glasses" msgid="8288346850537727333">"‏&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; کو &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; کا نظم کرنے کی اجازت دیں؟"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"آلہ"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"اس ایپ کو آپ کے <xliff:g id="DEVICE_NAME">%1$s</xliff:g> پر ان اجازتوں تک رسائی کی اجازت ہوگی"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"‏اپنے فون سے ان معلومات تک رسائی حاصل کرنے کی &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; کو اجازت دیں"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"کراس ڈیوائس سروسز"</string>
-    <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> ایپ آپ کے <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> کی جانب سے آپ کے آلات کے درمیان ایپس کی سلسلہ بندی کرنے کی اجازت کی درخواست کر رہی ہے"</string>
+    <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g> ایپ آپ کے <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> کی جانب سے آپ کے آلات کے درمیان ایپس کی سلسلہ بندی کرنے کی اجازت کی درخواست کر رہی ہے"</string>
     <string name="title_automotive_projection" msgid="3296005598978412847"></string>
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"‏اپنے فون سے اس معلومات تک رسائی حاصل کرنے کی &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; کو اجازت دیں"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"‏Google Play سروسز"</string>
-    <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> ایپ آپ کے <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> کی جانب سے آپ کے فون کی تصاویر، میڈیا اور اطلاعات تک رسائی کی اجازت طلب کر رہی ہے"</string>
-    <string name="title_nearby_device_streaming" msgid="179278282547719200">"‏اپنے فون سے اس کارروائی کو انجام دینے کے لیے ‎&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;‎ کو اجازت دیں"</string>
-    <string name="helper_title_nearby_device_streaming" msgid="6124438217620593669">"کراس آلے کی سروسز"</string>
-    <string name="helper_summary_nearby_device_streaming" msgid="5538329403511524333">"<xliff:g id="APP_NAME">%1$s</xliff:g> ایپ آپ کے <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> کی جانب سے مواد کے قریبی آلات کی سلسلہ بندی کرنے کی اجازت کی درخواست کر رہی ہے"</string>
+    <string name="helper_summary_computer" msgid="8774832742608187072">"<xliff:g id="APP_NAME">%1$s</xliff:g> ایپ آپ کے <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> کی جانب سے آپ کے فون کی تصاویر، میڈیا اور اطلاعات تک رسائی کی اجازت کی درخواست کر رہی ہے"</string>
+    <string name="title_nearby_device_streaming" msgid="7269956847378799794">"‏&lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; کو یہ کارروائی انجام دینے کی اجازت دیں؟"</string>
+    <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> آپ کے <xliff:g id="DEVICE_NAME">%2$s</xliff:g> کی جانب سے ایپس اور سسٹم کی دیگر خصوصیات کی سلسلہ بندی قریبی آلات پر کرنے کی اجازت طلب کر رہی ہے"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"آلہ"</string>
-    <!-- no translation found for summary_generic_single_device (4735072202474939111) -->
-    <skip />
-    <!-- no translation found for summary_generic (4988130802522924650) -->
-    <skip />
+    <string name="summary_generic" msgid="1761976003668044801">"یہ ایپ آپ کے فون اور منتخب کردہ آلے کے درمیان معلومات، جیسے کسی کال کرنے والے کے نام، کی مطابقت پذیری کر سکے گی"</string>
     <string name="consent_yes" msgid="8344487259618762872">"اجازت دیں"</string>
     <string name="consent_no" msgid="2640796915611404382">"اجازت نہ دیں"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"منسوخ کریں"</string>
     <string name="consent_back" msgid="2560683030046918882">"پیچھے"</string>
+    <string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> کو پھیلائیں"</string>
+    <string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> کو سکیڑیں"</string>
     <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"‏ایپس کو &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; پر وہی اجازتیں دیں جو &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; پر دی گئی ہیں؟"</string>
     <string name="permission_sync_summary" msgid="765497944331294275">"‏اس میں ‎&lt;strong&gt;‎مائیکروفون‎&lt;/strong&gt; ،&lt;strong&gt;‎کیمرا‎&lt;/strong&gt;‎ اور ‎&lt;strong&gt;‎مقام تک رسائی‎&lt;/strong&gt;‎ اور ‎&lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;‎ پر دیگر حساس اجازتیں شامل ہو سکتی ہیں۔ ‎&lt;br/&gt;&lt;br/&gt;‎آپ ‎&lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;‎ پر کسی بھی وقت اپنی ترتیبات میں ان اجازتوں کو تبدیل کر سکتے ہیں۔"</string>
-    <string name="vendor_icon_description" msgid="4445875290032225965">"ایپ کا آئیکن"</string>
-    <string name="vendor_header_button_description" msgid="6566660389500630608">"مزید معلومات کا بٹن"</string>
+    <string name="vendor_header_button_description" msgid="7994879208461111473">"مزید معلومات"</string>
     <string name="permission_phone" msgid="2661081078692784919">"فون"</string>
     <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
     <string name="permission_contacts" msgid="3858319347208004438">"رابطے"</string>
@@ -64,16 +57,18 @@
     <string name="permission_storage" msgid="6831099350839392343">"تصاویر اور میڈیا"</string>
     <string name="permission_notification" msgid="693762568127741203">"اطلاعات"</string>
     <string name="permission_app_streaming" msgid="6009695219091526422">"ایپس"</string>
-    <string name="permission_nearby_device_streaming" msgid="5868108148065023161">"قریبی آلات کی سلسلہ بندی"</string>
+    <string name="permission_nearby_device_streaming" msgid="1023325519477349499">"سلسلہ بندی"</string>
     <string name="permission_phone_summary" msgid="6684396967861278044">"فون کالز کر سکتا ہے اور ان کا نظم کر سکتا ہے"</string>
     <string name="permission_call_logs_summary" msgid="6186103394658755022">"فون کال لاگ پڑھ کر لکھ سکتا ہے"</string>
     <string name="permission_sms_summary" msgid="3508442683678912017">"‏SMS پیغامات بھیج اور دیکھ سکتا ہے"</string>
     <string name="permission_contacts_summary" msgid="675861979475628708">"آپ کے رابطوں تک رسائی حاصل کر سکتا ہے"</string>
     <string name="permission_calendar_summary" msgid="6460000922511766226">"آپ کے کیلنڈر تک رسائی حاصل کر سکتا ہے"</string>
-    <string name="permission_microphone_summary" msgid="4241354865859396558">"مائیکروفون کا استعمال کر کے آڈیو ریکارڈ کر سکتے ہیں"</string>
+    <string name="permission_microphone_summary" msgid="3692091540613093394">"آڈیو ریکارڈ کر سکتی ہے"</string>
     <string name="permission_nearby_devices_summary" msgid="931940524460876655">"قریبی آلات کی متعلقہ پوزیشن تلاش کر سکتا ہے، ان سے منسلک ہو سکتا ہے اور اس کا تعین کر سکتا ہے"</string>
     <string name="permission_notification_summary" msgid="884075314530071011">"رابطوں، پیغامات اور تصاویر جیسی معلومات سمیت تمام اطلاعات پڑھ سکتے ہیں"</string>
     <string name="permission_app_streaming_summary" msgid="606923325679670624">"اپنے فون کی ایپس کی سلسلہ بندی کریں"</string>
     <string name="permission_storage_summary" msgid="3918240895519506417"></string>
-    <string name="permission_nearby_device_streaming_summary" msgid="5776807830582725074">"مواد کی قریبی آلے سے سلسلہ بندی کریں"</string>
+    <string name="permission_nearby_device_streaming_summary" msgid="8280824871197081246">"اپنے فون سے ایپس اور سسٹم کی دیگر خصوصیات کی سلسلہ بندی کریں"</string>
+    <string name="device_type" product="default" msgid="8268703872070046263">"فون"</string>
+    <string name="device_type" product="tablet" msgid="5038791954983067774">"ٹیبلیٹ"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-uz/strings.xml b/packages/CompanionDeviceManager/res/values-uz/strings.xml
index 4748e14..82b6937 100644
--- a/packages/CompanionDeviceManager/res/values-uz/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-uz/strings.xml
@@ -17,36 +17,36 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Companion Device Manager"</string>
-    <string name="confirmation_title" msgid="8024993972587946678">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ilovasiga &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; qurilmasidan foydalanishga ruxsat bering"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ilovasiga &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; qurilmasidan foydalanishga ruxsat berilsinmi?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"soat"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"&lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; boshqaradigan <xliff:g id="PROFILE_NAME">%1$s</xliff:g> qurilmasini tanlang"</string>
-    <string name="summary_watch" msgid="6566922405914995759">"Ilova <xliff:g id="DEVICE_NAME">%1$s</xliff:g> qurilmangizni boshqarish uchun kerak. <xliff:g id="APP_NAME">%2$s</xliff:g> ilovasiga chaqiruvchining ismi, bildirishnomalar bilan ishlash va telefon, SMS, kontaktlar, taqvim, chaqiruvlar jurnali va yaqin-atrofdagi qurilmalarni aniqlash kabi maʼlumotlarni sinxronlashga ruxsat beriladi."</string>
-    <string name="summary_watch_single_device" msgid="7443464525873186735">"Ilova <xliff:g id="DEVICE_NAME">%1$s</xliff:g> qurilmangizni boshqarish uchun kerak. <xliff:g id="APP_NAME">%2$s</xliff:g> ilovasiga chaqiruvchining ismi kabi maʼlumotlarni sinxronlash va quyidagi amallarni bajarishga ruxsat beriladi:"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"koʻzoynak"</string>
-    <string name="summary_glasses" msgid="3808267780579061241">"Bu ilova <xliff:g id="DEVICE_NAME">%1$s</xliff:g> qurilmasini boshqarish uchun kerak. <xliff:g id="APP_NAME">%2$s</xliff:g> ilovasiga bildirishnomalar bilan ishlash va telefon, SMS, kontaktlar, mikrofon va yaqin-atrofdagi qurilmalarga kirishga ruxsat beriladi."</string>
-    <string name="summary_glasses_single_device" msgid="7051392780285915640">"Ilova <xliff:g id="DEVICE_NAME">%1$s</xliff:g> qurilmasini boshqarish uchun kerak. <xliff:g id="APP_NAME">%2$s</xliff:g> ilovasiga quyidagi ruxsatlar bilan ishlashga ruxsat beriladi:"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; boshqaradigan qurilmani tanlang"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Sozlash uchun <xliff:g id="PROFILE_NAME">%1$s</xliff:g> profilini tanlang"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Bu ilovaga chaqiruvchining ismi kabi maʼlumotlarni sinxronlash va <xliff:g id="DEVICE_NAME">%1$s</xliff:g> qurilmasida quyidagi amallarni bajarishga ruxsat beriladi"</string>
+    <string name="confirmation_title_glasses" msgid="8288346850537727333">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ilovasiga &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; qurilmasini boshqarish uchun ruxsat berilsinmi?"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"qurilma"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Bu ilova <xliff:g id="DEVICE_NAME">%1$s</xliff:g> qurilmasida quyidagi ruxsatlarni oladi"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ilovasiga telefondagi ushbu maʼlumot uchun ruxsat bering"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Qurilmalararo xizmatlar"</string>
-    <string name="helper_summary_app_streaming" msgid="5977509499890099">"Qurilamalararo ilovalar strimingi uchun <xliff:g id="APP_NAME">%1$s</xliff:g> ilovasi <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> nomidan ruxsat soʻramoqda"</string>
+    <string name="helper_summary_app_streaming" msgid="2396773196949578425">"Qurilamalararo ilovalar strimingi uchun <xliff:g id="APP_NAME">%1$s</xliff:g> ilovasi <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> nomidan ruxsat soʻramoqda"</string>
     <string name="title_automotive_projection" msgid="3296005598978412847"></string>
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ilovasiga telefondagi ushbu maʼlumot uchun ruxsat bering"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Google Play xizmatlari"</string>
-    <string name="helper_summary_computer" msgid="9050724687678157852">"Telefoningizdagi rasm, media va bildirishnomalarga kirish uchun <xliff:g id="APP_NAME">%1$s</xliff:g> ilovasi <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> nomidan ruxsat soʻramoqda"</string>
-    <string name="title_nearby_device_streaming" msgid="179278282547719200">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ilovasi telefonda amallar bajarishiga ruxsat bering"</string>
-    <string name="helper_title_nearby_device_streaming" msgid="6124438217620593669">"Qurilmalararo xizmatlar"</string>
-    <string name="helper_summary_nearby_device_streaming" msgid="5538329403511524333">"<xliff:g id="APP_NAME">%1$s</xliff:g> ilovasi <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> qurilmangizdan nomidan atrofdagi qurilmalarga kontent uzatish uchun ruxsat olmoqchi"</string>
+    <string name="helper_summary_computer" msgid="8774832742608187072">"Telefoningizdagi rasm, media va bildirishnomalarga kirish uchun <xliff:g id="APP_NAME">%1$s</xliff:g> ilovasi <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> nomidan ruxsat soʻramoqda"</string>
+    <string name="title_nearby_device_streaming" msgid="7269956847378799794">"&lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; ilovasiga bu amalni bajarish uchun ruxsat berilsinmi?"</string>
+    <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> ilovasi <xliff:g id="DEVICE_NAME">%2$s</xliff:g> qurilmangizdan nomidan atrofdagi qurilmalarga ilova va boshqa tizim funksiyalarini uzatish uchun ruxsat olmoqchi"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"qurilma"</string>
-    <string name="summary_generic_single_device" msgid="4735072202474939111">"Bu ilova telefoningiz va <xliff:g id="DEVICE_NAME">%1$s</xliff:g> qurilmasida chaqiruvchining ismi kabi maʼlumotlarni sinxronlay oladi."</string>
-    <string name="summary_generic" msgid="4988130802522924650">"Bu ilova telefoningiz va tanlangan qurilmada chaqiruvchining ismi kabi maʼlumotlarni sinxronlay oladi."</string>
+    <string name="summary_generic" msgid="1761976003668044801">"Bu ilova telefoningiz va tanlangan qurilmada chaqiruvchining ismi kabi maʼlumotlarni sinxronlay oladi"</string>
     <string name="consent_yes" msgid="8344487259618762872">"Ruxsat"</string>
     <string name="consent_no" msgid="2640796915611404382">"Ruxsat berilmasin"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Bekor qilish"</string>
     <string name="consent_back" msgid="2560683030046918882">"Orqaga"</string>
+    <string name="permission_expand" msgid="893185038020887411">"Yoyish: <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
+    <string name="permission_collapse" msgid="3320833884220844084">"Yopish: <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"&lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; ilovalariga &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; qurilmasidagi kabi bir xil ruxsatlar berilsinmi?"</string>
     <string name="permission_sync_summary" msgid="765497944331294275">"Ilovada &lt;strong&gt;,ikrofon&lt;/strong&gt;, &lt;strong&gt;kamera&lt;/strong&gt;, &lt;strong&gt;joylashuv axboroti&lt;/strong&gt;, va &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g> qurilmasidagi boshqa shaxsiy maʼlumotlarga kirish imkoni paydo boʻladi&lt;/strong&gt;. &lt;br/&gt;&lt;br/&gt;Bu ruxsatlarni istalgan vaqt &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g> sozlamalari orqali oʻzgartirish mumkin&lt;/strong&gt;."</string>
-    <string name="vendor_icon_description" msgid="4445875290032225965">"Ilova belgisi"</string>
-    <string name="vendor_header_button_description" msgid="6566660389500630608">"Batafsil axborot tugmasi"</string>
+    <string name="vendor_header_button_description" msgid="7994879208461111473">"Batafsil"</string>
     <string name="permission_phone" msgid="2661081078692784919">"Telefon"</string>
     <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
     <string name="permission_contacts" msgid="3858319347208004438">"Kontaktlar"</string>
@@ -57,16 +57,18 @@
     <string name="permission_storage" msgid="6831099350839392343">"Suratlar va media"</string>
     <string name="permission_notification" msgid="693762568127741203">"Bildirishnomalar"</string>
     <string name="permission_app_streaming" msgid="6009695219091526422">"Ilovalar"</string>
-    <string name="permission_nearby_device_streaming" msgid="5868108148065023161">"Atrofdagi qurilmalarga uzatish"</string>
+    <string name="permission_nearby_device_streaming" msgid="1023325519477349499">"Striming"</string>
     <string name="permission_phone_summary" msgid="6684396967861278044">"Telefon chaqiruvlarini bajarishi va boshqarishi mumkin"</string>
     <string name="permission_call_logs_summary" msgid="6186103394658755022">"Telefon chaqiruvlari jurnalini koʻrishi va oʻzgartirishi mumkin"</string>
     <string name="permission_sms_summary" msgid="3508442683678912017">"SMS xabarlarni koʻrishi va yuborishi mumkin"</string>
     <string name="permission_contacts_summary" msgid="675861979475628708">"Kontaktlarga ruxsati bor"</string>
     <string name="permission_calendar_summary" msgid="6460000922511766226">"Taqvimga ruxsati bor"</string>
-    <string name="permission_microphone_summary" msgid="4241354865859396558">"Mikrofon orqali audio yozib olishi mumkin"</string>
+    <string name="permission_microphone_summary" msgid="3692091540613093394">"Audio yozib olish mumkin"</string>
     <string name="permission_nearby_devices_summary" msgid="931940524460876655">"Atrofdagi qurilmalarni qidirishi, joylashuvini aniqlashi va ularga ulanishi mumkin"</string>
     <string name="permission_notification_summary" msgid="884075314530071011">"Barcha bildirishnomalarni, jumladan, kontaktlar, xabarlar va suratlarni oʻqishi mumkin"</string>
     <string name="permission_app_streaming_summary" msgid="606923325679670624">"Telefondagi ilovalarni translatsiya qilish"</string>
     <string name="permission_storage_summary" msgid="3918240895519506417"></string>
-    <string name="permission_nearby_device_streaming_summary" msgid="5776807830582725074">"Atrofdagi qurilmalarga kontent uzatish"</string>
+    <string name="permission_nearby_device_streaming_summary" msgid="8280824871197081246">"Telefoningizdan ilovalar va tizim funksiyalarini translatsiya qilish"</string>
+    <string name="device_type" product="default" msgid="8268703872070046263">"telefon"</string>
+    <string name="device_type" product="tablet" msgid="5038791954983067774">"planshet"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-vi/strings.xml b/packages/CompanionDeviceManager/res/values-vi/strings.xml
index e5f50af..951f429 100644
--- a/packages/CompanionDeviceManager/res/values-vi/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-vi/strings.xml
@@ -16,44 +16,37 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_label" msgid="4470785958457506021">"Companion Device Manager"</string>
-    <!-- no translation found for confirmation_title (8024993972587946678) -->
-    <skip />
+    <string name="app_label" msgid="4470785958457506021">"Trình quản lý thiết bị đồng hành"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"Cho phép ứng dụng &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; truy cập vào &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"đồng hồ"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Chọn một <xliff:g id="PROFILE_NAME">%1$s</xliff:g> sẽ do &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; quản lý"</string>
-    <!-- no translation found for summary_watch (6566922405914995759) -->
-    <skip />
-    <!-- no translation found for summary_watch_single_device (7443464525873186735) -->
-    <skip />
-    <string name="profile_name_glasses" msgid="8488394059007275998">"kính"</string>
-    <!-- no translation found for summary_glasses (3808267780579061241) -->
-    <skip />
-    <!-- no translation found for summary_glasses_single_device (7051392780285915640) -->
-    <skip />
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"Chọn một thiết bị sẽ do &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; quản lý"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Chọn một <xliff:g id="PROFILE_NAME">%1$s</xliff:g> để thiết lập"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Ứng dụng này sẽ được phép đồng bộ hoá thông tin (chẳng hạn như tên của người đang gọi điện) và dùng những quyền sau trên <xliff:g id="DEVICE_NAME">%1$s</xliff:g> của bạn"</string>
+    <string name="confirmation_title_glasses" msgid="8288346850537727333">"Cho phép &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; quản lý &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"thiết bị"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Ứng dụng này sẽ được phép dùng những quyền sau trên <xliff:g id="DEVICE_NAME">%1$s</xliff:g> của bạn"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Cho phép &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; truy cập vào thông tin này trên điện thoại của bạn"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Dịch vụ trên nhiều thiết bị"</string>
-    <string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> đang yêu cầu quyền thay cho <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> để truyền trực tuyến ứng dụng giữa các thiết bị của bạn"</string>
+    <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g> đang yêu cầu quyền thay cho <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> để truyền trực tuyến ứng dụng giữa các thiết bị của bạn"</string>
     <string name="title_automotive_projection" msgid="3296005598978412847"></string>
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"Cho phép &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; truy cập vào thông tin này trên điện thoại của bạn"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Dịch vụ Google Play"</string>
-    <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> đang yêu cầu quyền thay cho <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> để truy cập vào ảnh, nội dung nghe nhìn và thông báo trên điện thoại của bạn."</string>
-    <string name="title_nearby_device_streaming" msgid="179278282547719200">"Cho phép &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; thực hiện thao tác này qua điện thoại của bạn"</string>
-    <string name="helper_title_nearby_device_streaming" msgid="6124438217620593669">"Dịch vụ trên nhiều thiết bị"</string>
-    <string name="helper_summary_nearby_device_streaming" msgid="5538329403511524333">"<xliff:g id="APP_NAME">%1$s</xliff:g> đang thay mặt <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> yêu cầu quyền để truyền nội dung đến các thiết bị ở gần"</string>
+    <string name="helper_summary_computer" msgid="8774832742608187072">"<xliff:g id="APP_NAME">%1$s</xliff:g> đang yêu cầu quyền thay cho <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> để truy cập vào ảnh, nội dung nghe nhìn và thông báo trên điện thoại của bạn"</string>
+    <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Cho phép &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; thực hiện hành động này?"</string>
+    <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> đang thay <xliff:g id="DEVICE_NAME">%2$s</xliff:g> yêu cầu quyền truyền trực tuyến ứng dụng và các tính năng khác của hệ thống đến các thiết bị ở gần"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"thiết bị"</string>
-    <!-- no translation found for summary_generic_single_device (4735072202474939111) -->
-    <skip />
-    <!-- no translation found for summary_generic (4988130802522924650) -->
-    <skip />
+    <string name="summary_generic" msgid="1761976003668044801">"Ứng dụng này sẽ đồng bộ hoá thông tin (ví dụ: tên người gọi) giữa điện thoại của bạn và thiết bị bạn chọn"</string>
     <string name="consent_yes" msgid="8344487259618762872">"Cho phép"</string>
     <string name="consent_no" msgid="2640796915611404382">"Không cho phép"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Huỷ"</string>
     <string name="consent_back" msgid="2560683030046918882">"Quay lại"</string>
+    <string name="permission_expand" msgid="893185038020887411">"Mở rộng <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
+    <string name="permission_collapse" msgid="3320833884220844084">"Thu gọn <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Cấp cho các ứng dụng trên &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; các quyền giống như trên &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="permission_sync_summary" msgid="765497944331294275">"Những quyền này có thể bao gồm quyền truy cập vào &lt;strong&gt;Micrô&lt;/strong&gt;, &lt;strong&gt;Máy ảnh&lt;/strong&gt;, và &lt;strong&gt;Thông tin vị trí&lt;/strong&gt;, cũng như các quyền truy cập thông tin nhạy cảm khác trên &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;. &lt;br/&gt;&lt;br/&gt;Bạn có thể thay đổi những quyền này bất cứ lúc nào trong phần Cài đặt trên &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;."</string>
-    <string name="vendor_icon_description" msgid="4445875290032225965">"Biểu tượng ứng dụng"</string>
-    <string name="vendor_header_button_description" msgid="6566660389500630608">"Nút thông tin khác"</string>
+    <string name="vendor_header_button_description" msgid="7994879208461111473">"Thông tin khác"</string>
     <string name="permission_phone" msgid="2661081078692784919">"Điện thoại"</string>
     <string name="permission_sms" msgid="6337141296535774786">"Tin nhắn SMS"</string>
     <string name="permission_contacts" msgid="3858319347208004438">"Danh bạ"</string>
@@ -64,16 +57,18 @@
     <string name="permission_storage" msgid="6831099350839392343">"Ảnh và nội dung nghe nhìn"</string>
     <string name="permission_notification" msgid="693762568127741203">"Thông báo"</string>
     <string name="permission_app_streaming" msgid="6009695219091526422">"Ứng dụng"</string>
-    <string name="permission_nearby_device_streaming" msgid="5868108148065023161">"Truyền đến thiết bị ở gần"</string>
+    <string name="permission_nearby_device_streaming" msgid="1023325519477349499">"Truyền trực tuyến"</string>
     <string name="permission_phone_summary" msgid="6684396967861278044">"Có thể thực hiện và quản lý các cuộc gọi điện thoại"</string>
     <string name="permission_call_logs_summary" msgid="6186103394658755022">"Có thể đọc và ghi nhật ký cuộc gọi điện thoại"</string>
     <string name="permission_sms_summary" msgid="3508442683678912017">"Có thể gửi và xem tin nhắn SMS"</string>
     <string name="permission_contacts_summary" msgid="675861979475628708">"Có thể truy cập danh bạ"</string>
     <string name="permission_calendar_summary" msgid="6460000922511766226">"Có thể truy cập lịch"</string>
-    <string name="permission_microphone_summary" msgid="4241354865859396558">"Có thể ghi âm bằng micrô"</string>
+    <string name="permission_microphone_summary" msgid="3692091540613093394">"Có thể ghi âm"</string>
     <string name="permission_nearby_devices_summary" msgid="931940524460876655">"Có thể tìm, kết nối và xác định vị trí tương đối của các thiết bị ở gần"</string>
     <string name="permission_notification_summary" msgid="884075314530071011">"Có thể đọc tất cả các thông báo, kể cả những thông tin như danh bạ, tin nhắn và ảnh"</string>
     <string name="permission_app_streaming_summary" msgid="606923325679670624">"Truyền các ứng dụng trên điện thoại của bạn"</string>
     <string name="permission_storage_summary" msgid="3918240895519506417"></string>
-    <string name="permission_nearby_device_streaming_summary" msgid="5776807830582725074">"Truyền nội dung đến thiết bị ở gần"</string>
+    <string name="permission_nearby_device_streaming_summary" msgid="8280824871197081246">"Truyền trực tuyến ứng dụng và các tính năng khác của hệ thống từ điện thoại của bạn"</string>
+    <string name="device_type" product="default" msgid="8268703872070046263">"điện thoại"</string>
+    <string name="device_type" product="tablet" msgid="5038791954983067774">"máy tính bảng"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-zh-rCN/strings.xml b/packages/CompanionDeviceManager/res/values-zh-rCN/strings.xml
index c863fa8..5edf9c0 100644
--- a/packages/CompanionDeviceManager/res/values-zh-rCN/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-zh-rCN/strings.xml
@@ -17,36 +17,36 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"配套设备管理器"</string>
-    <string name="confirmation_title" msgid="8024993972587946678">"允许&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;访问&lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"允许应用&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;访问&lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"手表"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"选择要由&lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;管理的<xliff:g id="PROFILE_NAME">%1$s</xliff:g>"</string>
-    <string name="summary_watch" msgid="6566922405914995759">"需要使用此应用才能管理您的“<xliff:g id="DEVICE_NAME">%1$s</xliff:g>”。<xliff:g id="APP_NAME">%2$s</xliff:g>将能同步信息(例如来电者的姓名),与通知交互,并可获得电话、短信、通讯录、日历、通话记录和附近设备的访问权限。"</string>
-    <string name="summary_watch_single_device" msgid="7443464525873186735">"需要使用此应用才能管理您的“<xliff:g id="DEVICE_NAME">%1$s</xliff:g>”。<xliff:g id="APP_NAME">%2$s</xliff:g>将能同步信息(例如来电者的姓名),并可使用以下权限:"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"眼镜"</string>
-    <string name="summary_glasses" msgid="3808267780579061241">"需要使用此应用才能管理“<xliff:g id="DEVICE_NAME">%1$s</xliff:g>”。<xliff:g id="APP_NAME">%2$s</xliff:g>将能与通知交互,并可获得电话、短信、通讯录、麦克风和附近设备的访问权限。"</string>
-    <string name="summary_glasses_single_device" msgid="7051392780285915640">"需要使用此应用才能管理“<xliff:g id="DEVICE_NAME">%1$s</xliff:g>”。<xliff:g id="APP_NAME">%2$s</xliff:g>将可使用以下权限:"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"选择要由&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;管理的设备"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"选择 <xliff:g id="PROFILE_NAME">%1$s</xliff:g> 进行设置"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"该应用将可以同步信息(例如来电者的姓名),并可以获得您<xliff:g id="DEVICE_NAME">%1$s</xliff:g>上的以下权限"</string>
+    <string name="confirmation_title_glasses" msgid="8288346850537727333">"允许&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;管理&lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"设备"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"该应用将可以获得您<xliff:g id="DEVICE_NAME">%1$s</xliff:g>上的以下权限"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"允许“<xliff:g id="APP_NAME">%1$s</xliff:g>”&lt;strong&gt;&lt;/strong&gt;访问您手机中的这项信息"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"跨设备服务"</string>
-    <string name="helper_summary_app_streaming" msgid="5977509499890099">"“<xliff:g id="APP_NAME">%1$s</xliff:g>”正代表您的<xliff:g id="DEVICE_TYPE">%2$s</xliff:g>请求在您的设备之间流式传输应用内容"</string>
+    <string name="helper_summary_app_streaming" msgid="2396773196949578425">"“<xliff:g id="APP_NAME">%1$s</xliff:g>”正代表您的<xliff:g id="DISPLAY_NAME">%2$s</xliff:g>请求在您的设备之间流式传输应用内容"</string>
     <string name="title_automotive_projection" msgid="3296005598978412847"></string>
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"允许 &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; 访问您手机中的这项信息"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Google Play 服务"</string>
-    <string name="helper_summary_computer" msgid="9050724687678157852">"“<xliff:g id="APP_NAME">%1$s</xliff:g>”正代表您的<xliff:g id="DEVICE_TYPE">%2$s</xliff:g>请求访问您手机上的照片、媒体内容和通知"</string>
-    <string name="title_nearby_device_streaming" msgid="179278282547719200">"允许&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;从您的手机执行此操作"</string>
-    <string name="helper_title_nearby_device_streaming" msgid="6124438217620593669">"跨设备服务"</string>
-    <string name="helper_summary_nearby_device_streaming" msgid="5538329403511524333">"“<xliff:g id="APP_NAME">%1$s</xliff:g>”正代表您的<xliff:g id="DEVICE_TYPE">%2$s</xliff:g>请求向您附近的设备流式传输内容"</string>
+    <string name="helper_summary_computer" msgid="8774832742608187072">"“<xliff:g id="APP_NAME">%1$s</xliff:g>”正代表您的<xliff:g id="DISPLAY_NAME">%2$s</xliff:g>请求访问您手机上的照片、媒体内容和通知"</string>
+    <string name="title_nearby_device_streaming" msgid="7269956847378799794">"允许&lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt;进行此操作?"</string>
+    <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"“<xliff:g id="APP_NAME">%1$s</xliff:g>”正代表您的<xliff:g id="DEVICE_NAME">%2$s</xliff:g>请求将应用和其他系统功能流式传输到附近的设备"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"设备"</string>
-    <string name="summary_generic_single_device" msgid="4735072202474939111">"此应用将能够在您的手机和“<xliff:g id="DEVICE_NAME">%1$s</xliff:g>”之间同步信息,例如来电者的姓名。"</string>
-    <string name="summary_generic" msgid="4988130802522924650">"此应用将能够在您的手机和所选设备之间同步信息,例如来电者的姓名。"</string>
+    <string name="summary_generic" msgid="1761976003668044801">"此应用将能在您的手机和所选设备之间同步信息,例如来电者的姓名"</string>
     <string name="consent_yes" msgid="8344487259618762872">"允许"</string>
     <string name="consent_no" msgid="2640796915611404382">"不允许"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"取消"</string>
     <string name="consent_back" msgid="2560683030046918882">"返回"</string>
+    <string name="permission_expand" msgid="893185038020887411">"展开<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
+    <string name="permission_collapse" msgid="3320833884220844084">"收起<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"要让&lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt;上的应用享有在&lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;上的同等权限吗?"</string>
     <string name="permission_sync_summary" msgid="765497944331294275">"这可能包括&lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;的&lt;strong&gt;麦克风&lt;/strong&gt;、&lt;strong&gt;摄像头&lt;/strong&gt;和&lt;strong&gt;位置信息访问权限&lt;/strong&gt;以及其他敏感权限。&lt;br/&gt;&lt;br/&gt;您随时可以在&lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;的“设置”中更改这些权限。"</string>
-    <string name="vendor_icon_description" msgid="4445875290032225965">"应用图标"</string>
-    <string name="vendor_header_button_description" msgid="6566660389500630608">"更多信息按钮"</string>
+    <string name="vendor_header_button_description" msgid="7994879208461111473">"更多信息"</string>
     <string name="permission_phone" msgid="2661081078692784919">"手机"</string>
     <string name="permission_sms" msgid="6337141296535774786">"短信"</string>
     <string name="permission_contacts" msgid="3858319347208004438">"通讯录"</string>
@@ -57,16 +57,18 @@
     <string name="permission_storage" msgid="6831099350839392343">"照片和媒体内容"</string>
     <string name="permission_notification" msgid="693762568127741203">"通知"</string>
     <string name="permission_app_streaming" msgid="6009695219091526422">"应用"</string>
-    <string name="permission_nearby_device_streaming" msgid="5868108148065023161">"附近的设备流式传输"</string>
+    <string name="permission_nearby_device_streaming" msgid="1023325519477349499">"流式传输"</string>
     <string name="permission_phone_summary" msgid="6684396967861278044">"可以打电话及管理通话"</string>
     <string name="permission_call_logs_summary" msgid="6186103394658755022">"可以读取和写入手机通话记录"</string>
     <string name="permission_sms_summary" msgid="3508442683678912017">"可以发送和查看短信"</string>
     <string name="permission_contacts_summary" msgid="675861979475628708">"可以访问您的通讯录"</string>
     <string name="permission_calendar_summary" msgid="6460000922511766226">"可以访问您的日历"</string>
-    <string name="permission_microphone_summary" msgid="4241354865859396558">"可使用麦克风录音"</string>
+    <string name="permission_microphone_summary" msgid="3692091540613093394">"可以录制音频"</string>
     <string name="permission_nearby_devices_summary" msgid="931940524460876655">"可以查找、连接附近的设备以及确定附近设备的相对位置"</string>
     <string name="permission_notification_summary" msgid="884075314530071011">"可以读取所有通知,包括合同、消息和照片等信息"</string>
     <string name="permission_app_streaming_summary" msgid="606923325679670624">"流式传输手机上的应用"</string>
     <string name="permission_storage_summary" msgid="3918240895519506417"></string>
-    <string name="permission_nearby_device_streaming_summary" msgid="5776807830582725074">"向附近的设备流式传输内容"</string>
+    <string name="permission_nearby_device_streaming_summary" msgid="8280824871197081246">"从您的手机流式传输应用和其他系统功能"</string>
+    <string name="device_type" product="default" msgid="8268703872070046263">"手机"</string>
+    <string name="device_type" product="tablet" msgid="5038791954983067774">"平板电脑"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-zh-rHK/strings.xml b/packages/CompanionDeviceManager/res/values-zh-rHK/strings.xml
index 7620ce4..0878c23 100644
--- a/packages/CompanionDeviceManager/res/values-zh-rHK/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-zh-rHK/strings.xml
@@ -17,43 +17,36 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"隨附裝置管理工具"</string>
-    <!-- no translation found for confirmation_title (8024993972587946678) -->
-    <skip />
+    <string name="confirmation_title" msgid="2244241995958340998">"要允許「<xliff:g id="APP_NAME">%1$s</xliff:g>」應用程式&lt;strong&gt;&lt;/strong&gt;存取「<xliff:g id="DEVICE_NAME">%2$s</xliff:g>」&lt;strong&gt;&lt;/strong&gt;嗎?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"手錶"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"選擇由 &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; 管理的<xliff:g id="PROFILE_NAME">%1$s</xliff:g>"</string>
-    <!-- no translation found for summary_watch (6566922405914995759) -->
-    <skip />
-    <!-- no translation found for summary_watch_single_device (7443464525873186735) -->
-    <skip />
-    <string name="profile_name_glasses" msgid="8488394059007275998">"眼鏡"</string>
-    <!-- no translation found for summary_glasses (3808267780579061241) -->
-    <skip />
-    <!-- no translation found for summary_glasses_single_device (7051392780285915640) -->
-    <skip />
-    <string name="title_app_streaming" msgid="2270331024626446950">"允許「<xliff:g id="APP_NAME">%1$s</xliff:g>」&lt;strong&gt;&lt;/strong&gt;存取您手機中的這項資料"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"選擇要讓 &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; 管理的裝置"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"選擇要設定的<xliff:g id="PROFILE_NAME">%1$s</xliff:g>"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"此應用程式將可同步資訊 (例如來電者的名稱),並可在<xliff:g id="DEVICE_NAME">%1$s</xliff:g>上取得以下權限"</string>
+    <string name="confirmation_title_glasses" msgid="8288346850537727333">"要允許「<xliff:g id="APP_NAME">%1$s</xliff:g>」&lt;strong&gt;&lt;/strong&gt;管理「<xliff:g id="DEVICE_NAME">%2$s</xliff:g>」&lt;strong&gt;&lt;/strong&gt;嗎?"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"裝置"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"此應用程式將可在<xliff:g id="DEVICE_NAME">%1$s</xliff:g>上取得以下權限"</string>
+    <string name="title_app_streaming" msgid="2270331024626446950">"允許「<xliff:g id="APP_NAME">%1$s</xliff:g>」&lt;strong&gt;&lt;/strong&gt;存取你手機中的這項資料"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"跨裝置服務"</string>
-    <string name="helper_summary_app_streaming" msgid="5977509499890099">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」正在為 <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> 要求權限,以在裝置之間串流應用程式內容"</string>
+    <string name="helper_summary_app_streaming" msgid="2396773196949578425">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」正在代表 <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> 要求權限,以便在裝置間串流應用程式的內容"</string>
     <string name="title_automotive_projection" msgid="3296005598978412847"></string>
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
-    <string name="title_computer" msgid="4693714143506569253">"允許「<xliff:g id="APP_NAME">%1$s</xliff:g>」&lt;strong&gt;&lt;/strong&gt;存取您手機中的這項資料"</string>
+    <string name="title_computer" msgid="4693714143506569253">"允許「<xliff:g id="APP_NAME">%1$s</xliff:g>」&lt;strong&gt;&lt;/strong&gt;存取你手機中的這項資料"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Google Play 服務"</string>
-    <string name="helper_summary_computer" msgid="9050724687678157852">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」正在代表 <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> 要求權限,以便存取手機上的相片、媒體和通知"</string>
-    <string name="title_nearby_device_streaming" msgid="179278282547719200">"允許「<xliff:g id="APP_NAME">%1$s</xliff:g>」&lt;strong&gt;&lt;/strong&gt;透過您的手機執行此操作"</string>
-    <string name="helper_title_nearby_device_streaming" msgid="6124438217620593669">"跨裝置服務"</string>
-    <string name="helper_summary_nearby_device_streaming" msgid="5538329403511524333">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」正在代表「<xliff:g id="DEVICE_TYPE">%2$s</xliff:g>」將內容串流至附近的裝置"</string>
+    <string name="helper_summary_computer" msgid="8774832742608187072">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」正在代表 <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> 要求權限,以便存取手機上的相片、媒體和通知"</string>
+    <string name="title_nearby_device_streaming" msgid="7269956847378799794">"要允許「<xliff:g id="DEVICE_NAME">%1$s</xliff:g>」&lt;strong&gt;&lt;/strong&gt;執行此操作嗎?"</string>
+    <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」正在代表「<xliff:g id="DEVICE_NAME">%2$s</xliff:g>」要求權限,才能在附近的裝置上串流播放應用程式和其他系統功能"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"裝置"</string>
-    <!-- no translation found for summary_generic_single_device (4735072202474939111) -->
-    <skip />
-    <!-- no translation found for summary_generic (4988130802522924650) -->
-    <skip />
+    <string name="summary_generic" msgid="1761976003668044801">"此應用程式將可同步手機和所選裝置的資訊,例如來電者的名稱"</string>
     <string name="consent_yes" msgid="8344487259618762872">"允許"</string>
     <string name="consent_no" msgid="2640796915611404382">"不允許"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"取消"</string>
     <string name="consent_back" msgid="2560683030046918882">"返回"</string>
+    <string name="permission_expand" msgid="893185038020887411">"展開<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
+    <string name="permission_collapse" msgid="3320833884220844084">"收合<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"&lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; 上的應用程式可獲在 &lt;strong&gt;<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; 上的相同權限嗎?"</string>
-    <string name="permission_sync_summary" msgid="765497944331294275">"這可能包括 &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt; 的&lt;strong&gt;麥克風&lt;/strong&gt;、&lt;strong&gt;相機&lt;/strong&gt;和&lt;strong&gt;位置資訊存取權&lt;/strong&gt;以及其他敏感資料權限。&lt;br/&gt;&lt;br/&gt;您隨時可透過 &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt; 的「設定」變更這些權限。"</string>
-    <string name="vendor_icon_description" msgid="4445875290032225965">"應用程式圖示"</string>
-    <string name="vendor_header_button_description" msgid="6566660389500630608">"「更多資料」按鈕"</string>
+    <string name="permission_sync_summary" msgid="765497944331294275">"這可能包括 &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt; 的&lt;strong&gt;麥克風&lt;/strong&gt;、&lt;strong&gt;相機&lt;/strong&gt;和&lt;strong&gt;位置資訊存取權&lt;/strong&gt;以及其他敏感資料權限。&lt;br/&gt;&lt;br/&gt;你隨時可透過 &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt; 的「設定」變更這些權限。"</string>
+    <string name="vendor_header_button_description" msgid="7994879208461111473">"瞭解詳情"</string>
     <string name="permission_phone" msgid="2661081078692784919">"手機"</string>
     <string name="permission_sms" msgid="6337141296535774786">"短訊"</string>
     <string name="permission_contacts" msgid="3858319347208004438">"通訊錄"</string>
@@ -64,16 +57,18 @@
     <string name="permission_storage" msgid="6831099350839392343">"相片和媒體"</string>
     <string name="permission_notification" msgid="693762568127741203">"通知"</string>
     <string name="permission_app_streaming" msgid="6009695219091526422">"應用程式"</string>
-    <string name="permission_nearby_device_streaming" msgid="5868108148065023161">"附近的裝置串流"</string>
+    <string name="permission_nearby_device_streaming" msgid="1023325519477349499">"串流"</string>
     <string name="permission_phone_summary" msgid="6684396967861278044">"可撥打電話和管理通話"</string>
     <string name="permission_call_logs_summary" msgid="6186103394658755022">"可讀取及寫入手機通話記錄"</string>
     <string name="permission_sms_summary" msgid="3508442683678912017">"可傳送及查看短訊"</string>
     <string name="permission_contacts_summary" msgid="675861979475628708">"可存取通訊錄"</string>
     <string name="permission_calendar_summary" msgid="6460000922511766226">"可存取日曆"</string>
-    <string name="permission_microphone_summary" msgid="4241354865859396558">"可使用麥克風錄音"</string>
+    <string name="permission_microphone_summary" msgid="3692091540613093394">"可以錄音"</string>
     <string name="permission_nearby_devices_summary" msgid="931940524460876655">"可尋找、連接及判斷附近裝置的相對位置"</string>
     <string name="permission_notification_summary" msgid="884075314530071011">"可以讀取所有通知,包括聯絡人、訊息和電話等資訊"</string>
     <string name="permission_app_streaming_summary" msgid="606923325679670624">"串流播放手機應用程式內容"</string>
     <string name="permission_storage_summary" msgid="3918240895519506417"></string>
-    <string name="permission_nearby_device_streaming_summary" msgid="5776807830582725074">"將內容串流至附近的裝置"</string>
+    <string name="permission_nearby_device_streaming_summary" msgid="8280824871197081246">"串流播放手機中的應用程式和其他系統功能"</string>
+    <string name="device_type" product="default" msgid="8268703872070046263">"手機"</string>
+    <string name="device_type" product="tablet" msgid="5038791954983067774">"平板電腦"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-zh-rTW/strings.xml b/packages/CompanionDeviceManager/res/values-zh-rTW/strings.xml
index a829961..4f50ef5 100644
--- a/packages/CompanionDeviceManager/res/values-zh-rTW/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-zh-rTW/strings.xml
@@ -17,43 +17,36 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"隨附裝置管理工具"</string>
-    <!-- no translation found for confirmation_title (8024993972587946678) -->
-    <skip />
+    <string name="confirmation_title" msgid="2244241995958340998">"要允許「<xliff:g id="APP_NAME">%1$s</xliff:g>」&lt;strong&gt;&lt;/strong&gt;應用程式存取「<xliff:g id="DEVICE_NAME">%2$s</xliff:g>」&lt;strong&gt;&lt;/strong&gt;嗎?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"手錶"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"選擇要讓「<xliff:g id="APP_NAME">%2$s</xliff:g>」&lt;strong&gt;&lt;/strong&gt;管理的<xliff:g id="PROFILE_NAME">%1$s</xliff:g>"</string>
-    <!-- no translation found for summary_watch (6566922405914995759) -->
-    <skip />
-    <!-- no translation found for summary_watch_single_device (7443464525873186735) -->
-    <skip />
-    <string name="profile_name_glasses" msgid="8488394059007275998">"眼鏡"</string>
-    <!-- no translation found for summary_glasses (3808267780579061241) -->
-    <skip />
-    <!-- no translation found for summary_glasses_single_device (7051392780285915640) -->
-    <skip />
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"選擇要讓「<xliff:g id="APP_NAME">%1$s</xliff:g>」&lt;strong&gt;&lt;/strong&gt;管理的裝置"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"選擇要設定的<xliff:g id="PROFILE_NAME">%1$s</xliff:g>"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"這個應用程式將可同步處理資訊 (例如來電者名稱)、取得<xliff:g id="DEVICE_NAME">%1$s</xliff:g>上的這些權限"</string>
+    <string name="confirmation_title_glasses" msgid="8288346850537727333">"要允許「<xliff:g id="APP_NAME">%1$s</xliff:g>」&lt;strong&gt;&lt;/strong&gt;管理「<xliff:g id="DEVICE_NAME">%2$s</xliff:g>」&lt;strong&gt;&lt;/strong&gt;嗎?"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"裝置"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"這個應用程式將可取得<xliff:g id="DEVICE_NAME">%1$s</xliff:g>上的這些權限"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"允許「<xliff:g id="APP_NAME">%1$s</xliff:g>」&lt;strong&gt;&lt;/strong&gt;存取手機中的這項資訊"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"跨裝置服務"</string>
-    <string name="helper_summary_app_streaming" msgid="5977509499890099">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」正在代表你的「<xliff:g id="DEVICE_TYPE">%2$s</xliff:g>」要求必要權限,以便在裝置之間串流傳輸應用程式內容"</string>
+    <string name="helper_summary_app_streaming" msgid="2396773196949578425">"為了在裝置間串流傳輸應用程式內容,「<xliff:g id="APP_NAME">%1$s</xliff:g>」正在代表 <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> 要求相關權限"</string>
     <string name="title_automotive_projection" msgid="3296005598978412847"></string>
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"允許「<xliff:g id="APP_NAME">%1$s</xliff:g>」&lt;strong&gt;&lt;/strong&gt;存取你手機中的這項資訊"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Google Play 服務"</string>
-    <string name="helper_summary_computer" msgid="9050724687678157852">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」正在代表你的「<xliff:g id="DEVICE_TYPE">%2$s</xliff:g>」要求必要權限,以便存取手機上的相片、媒體和通知"</string>
-    <string name="title_nearby_device_streaming" msgid="179278282547719200">"允許「<xliff:g id="APP_NAME">%1$s</xliff:g>」&lt;strong&gt;&lt;/strong&gt;透過你的手機執行這項操作"</string>
-    <string name="helper_title_nearby_device_streaming" msgid="6124438217620593669">"跨裝置服務"</string>
-    <string name="helper_summary_nearby_device_streaming" msgid="5538329403511524333">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」正在代表你的「<xliff:g id="DEVICE_TYPE">%2$s</xliff:g>」將內容串流傳輸到鄰近裝置"</string>
+    <string name="helper_summary_computer" msgid="8774832742608187072">"為了存取手機上的相片、媒體和通知,「<xliff:g id="APP_NAME">%1$s</xliff:g>」正在代表 <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> 要求相關權限"</string>
+    <string name="title_nearby_device_streaming" msgid="7269956847378799794">"要允許「<xliff:g id="DEVICE_NAME">%1$s</xliff:g>」&lt;strong&gt;&lt;/strong&gt;執行這項操作嗎?"</string>
+    <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」正在代表「<xliff:g id="DEVICE_NAME">%2$s</xliff:g>」要求必要權限,才能在鄰近裝置上串流播放應用程式和其他系統功能"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"裝置"</string>
-    <!-- no translation found for summary_generic_single_device (4735072202474939111) -->
-    <skip />
-    <!-- no translation found for summary_generic (4988130802522924650) -->
-    <skip />
+    <string name="summary_generic" msgid="1761976003668044801">"這個應用程式將可在手機和指定裝置間同步資訊,例如來電者名稱"</string>
     <string name="consent_yes" msgid="8344487259618762872">"允許"</string>
     <string name="consent_no" msgid="2640796915611404382">"不允許"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"取消"</string>
     <string name="consent_back" msgid="2560683030046918882">"返回"</string>
+    <string name="permission_expand" msgid="893185038020887411">"展開<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
+    <string name="permission_collapse" msgid="3320833884220844084">"收合<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"要讓「<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>」&lt;strong&gt;&lt;/strong&gt;的應用程式沿用在「<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>」&lt;strong&gt;&lt;/strong&gt;上的權限嗎?"</string>
     <string name="permission_sync_summary" msgid="765497944331294275">"這可能包括 &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt; 的&lt;strong&gt;麥克風&lt;/strong&gt;、&lt;strong&gt;相機&lt;/strong&gt;和&lt;strong&gt;位置資訊存取權&lt;/strong&gt;以及機密權限。&lt;br/&gt;&lt;br/&gt;你隨時可透過 &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt; 的「設定」變更這些權限。"</string>
-    <string name="vendor_icon_description" msgid="4445875290032225965">"應用程式圖示"</string>
-    <string name="vendor_header_button_description" msgid="6566660389500630608">"更多資訊按鈕"</string>
+    <string name="vendor_header_button_description" msgid="7994879208461111473">"更多資訊"</string>
     <string name="permission_phone" msgid="2661081078692784919">"電話"</string>
     <string name="permission_sms" msgid="6337141296535774786">"簡訊"</string>
     <string name="permission_contacts" msgid="3858319347208004438">"聯絡人"</string>
@@ -64,16 +57,18 @@
     <string name="permission_storage" msgid="6831099350839392343">"相片和媒體"</string>
     <string name="permission_notification" msgid="693762568127741203">"通知"</string>
     <string name="permission_app_streaming" msgid="6009695219091526422">"應用程式"</string>
-    <string name="permission_nearby_device_streaming" msgid="5868108148065023161">"鄰近裝置串流"</string>
+    <string name="permission_nearby_device_streaming" msgid="1023325519477349499">"串流"</string>
     <string name="permission_phone_summary" msgid="6684396967861278044">"可撥打及管理通話"</string>
     <string name="permission_call_logs_summary" msgid="6186103394658755022">"可讀取及寫入通話記錄"</string>
     <string name="permission_sms_summary" msgid="3508442683678912017">"可傳送及查看簡訊"</string>
     <string name="permission_contacts_summary" msgid="675861979475628708">"可存取聯絡人"</string>
     <string name="permission_calendar_summary" msgid="6460000922511766226">"可存取日曆"</string>
-    <string name="permission_microphone_summary" msgid="4241354865859396558">"可使用麥克風錄音"</string>
+    <string name="permission_microphone_summary" msgid="3692091540613093394">"可以錄音"</string>
     <string name="permission_nearby_devices_summary" msgid="931940524460876655">"可尋找、連線及判斷鄰近裝置的相對位置"</string>
     <string name="permission_notification_summary" msgid="884075314530071011">"可讀取所有通知,包括聯絡人、訊息和電話等資訊"</string>
     <string name="permission_app_streaming_summary" msgid="606923325679670624">"串流傳輸手機應用程式內容"</string>
     <string name="permission_storage_summary" msgid="3918240895519506417"></string>
-    <string name="permission_nearby_device_streaming_summary" msgid="5776807830582725074">"將內容串流傳輸到鄰近裝置"</string>
+    <string name="permission_nearby_device_streaming_summary" msgid="8280824871197081246">"串流播放手機中的應用程式和其他系統功能"</string>
+    <string name="device_type" product="default" msgid="8268703872070046263">"手機"</string>
+    <string name="device_type" product="tablet" msgid="5038791954983067774">"平板電腦"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-zu/strings.xml b/packages/CompanionDeviceManager/res/values-zu/strings.xml
index 7af3531..7fb5ece 100644
--- a/packages/CompanionDeviceManager/res/values-zu/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-zu/strings.xml
@@ -17,36 +17,36 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Isiphathi sedivayisi esihambisanayo"</string>
-    <string name="confirmation_title" msgid="8024993972587946678">"Vumela i-&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ukuthi ifinyelele i-&lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"Vumela i-app &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ukufinyelela &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"buka"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Khetha i-<xliff:g id="PROFILE_NAME">%1$s</xliff:g> ezophathwa yi-&lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="6566922405914995759">"I-app iyadingeka ukuphatha i-<xliff:g id="DEVICE_NAME">%1$s</xliff:g> yakho. I-<xliff:g id="APP_NAME">%2$s</xliff:g> izovunyelwa ukuvumelanisa ulwazi, njengegama lomuntu othile ofonayo, ukusebenzisana nezaziso zakho futhi ufinyelele Ifoni yakho, i-SMS, Oxhumana Nabo, Ikhalenda, Amarekhodi Amakholi nezimvume zamadivayisi aseduze."</string>
-    <string name="summary_watch_single_device" msgid="7443464525873186735">"I-app iyadingeka ukuphatha i-<xliff:g id="DEVICE_NAME">%1$s</xliff:g> yakho. I-<xliff:g id="APP_NAME">%2$s</xliff:g> izovunyelwa ukuvumelanisa ulwazi, njengegama lomuntu othile ofonayo, futhi ufinyelele lezi zimvume:"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"Izingilazi"</string>
-    <string name="summary_glasses" msgid="3808267780579061241">"Le app iyadingeka ukuphatha i-<xliff:g id="DEVICE_NAME">%1$s</xliff:g>. I-<xliff:g id="APP_NAME">%2$s</xliff:g> izovunyelwa ukuthi ihlanganyele nezaziso zakho futhi ifinyelele kufoni yakho, i-SMS, Oxhumana nabo, Imakrofoni nezimvume zamadivayisi aseduze."</string>
-    <string name="summary_glasses_single_device" msgid="7051392780285915640">"I-app iyadingeka ukuphatha i-<xliff:g id="DEVICE_NAME">%1$s</xliff:g>. I-<xliff:g id="APP_NAME">%2$s</xliff:g> izovunyelwa ukusebenzisana nalezi zimvume:"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"Khetha idivayisi engaphathwa nge-&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Khetha i-<xliff:g id="PROFILE_NAME">%1$s</xliff:g> ukusetha"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Le-app izovunyelwa ukuvumelanisa ulwazi, olufana negama lomuntu ofonayo, iphinde ifinyelele lezi zimvume ku-<xliff:g id="DEVICE_NAME">%1$s</xliff:g> yakho"</string>
+    <string name="confirmation_title_glasses" msgid="8288346850537727333">"Vumela i-&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ukuthi ifinyelele i-&lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"idivayisi"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Le-app izovunyelwa ukufinyelela lezi zimvume ku-<xliff:g id="DEVICE_NAME">%1$s</xliff:g> yakho"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Vumela i-&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ifinyelele lolu lwazi kusukela efonini yakho"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Amasevisi amadivayisi amaningi"</string>
-    <string name="helper_summary_app_streaming" msgid="5977509499890099">"I-<xliff:g id="APP_NAME">%1$s</xliff:g> icela imvume esikhundleni se-<xliff:g id="DEVICE_TYPE">%2$s</xliff:g> yakho ukuze isakaze-bukhoma ama-app phakathi kwamadivayisi akho"</string>
+    <string name="helper_summary_app_streaming" msgid="2396773196949578425">"I-<xliff:g id="APP_NAME">%1$s</xliff:g> icela imvume esikhundleni se-<xliff:g id="DISPLAY_NAME">%2$s</xliff:g> yakho ukuze isakaze-bukhoma ama-app phakathi kwamadivayisi akho"</string>
     <string name="title_automotive_projection" msgid="3296005598978412847"></string>
     <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
     <string name="title_computer" msgid="4693714143506569253">"Vumela &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ukufinyelela lolu lwazi kusuka efonini yakho"</string>
     <string name="summary_computer" msgid="3798467601598297062"></string>
     <string name="helper_title_computer" msgid="4671071173916176037">"Amasevisi we-Google Play"</string>
-    <string name="helper_summary_computer" msgid="9050724687678157852">"I-<xliff:g id="APP_NAME">%1$s</xliff:g> icela imvume esikhundleni se-<xliff:g id="DEVICE_TYPE">%2$s</xliff:g> yakho ukuze ifinyelele izithombe zefoni yakho, imidiya nezaziso"</string>
-    <string name="title_nearby_device_streaming" msgid="179278282547719200">"Vumela i-<xliff:g id="APP_NAME">%1$s</xliff:g> ukwenza lesi senzo ngocingo lwakho"</string>
-    <string name="helper_title_nearby_device_streaming" msgid="6124438217620593669">"Amasevisi amadivayisi amaningi"</string>
-    <string name="helper_summary_nearby_device_streaming" msgid="5538329403511524333">"I-<xliff:g id="APP_NAME">%1$s</xliff:g> icela imvume esikhundleni se-<xliff:g id="DEVICE_TYPE">%2$s</xliff:g> yakho ukuze isakaze okuqukethwe kumadivayisi aseduze"</string>
+    <string name="helper_summary_computer" msgid="8774832742608187072">"I-<xliff:g id="APP_NAME">%1$s</xliff:g> icela imvume esikhundleni se-<xliff:g id="DISPLAY_NAME">%2$s</xliff:g> yakho ukuze ifinyelele izithombe zefoni yakho, imidiya nezaziso"</string>
+    <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Vumela i-<xliff:g id="DEVICE_NAME">%1$s</xliff:g> ukwenza lesi senzo?"</string>
+    <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"I-<xliff:g id="APP_NAME">%1$s</xliff:g> icela imvume esikhundleni se-<xliff:g id="DEVICE_NAME">%2$s</xliff:g> ukusakaza ama-app nezinye izakhi zesistimu kumadivayisi aseduze"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"idivayisi"</string>
-    <string name="summary_generic_single_device" msgid="4735072202474939111">"Le app izokwazi ukuvumelanisa ulwazi, njengegama lomuntu othile ofonayo, phakathi kwefoni yakho ne-<xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string>
-    <string name="summary_generic" msgid="4988130802522924650">"Le app izokwazi ukuvumelanisa ulwazi, njengegama lomuntu othile ofonayo, phakathi kwefoni yakho nedivayisi ekhethiwe."</string>
+    <string name="summary_generic" msgid="1761976003668044801">"Le app izokwazi ukuvumelanisa ulwazi, njengegama lomuntu othile ofonayo, phakathi kwefoni yakho nedivayisi ekhethiwe"</string>
     <string name="consent_yes" msgid="8344487259618762872">"Vumela"</string>
     <string name="consent_no" msgid="2640796915611404382">"Ungavumeli"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Khansela"</string>
     <string name="consent_back" msgid="2560683030046918882">"Emuva"</string>
+    <string name="permission_expand" msgid="893185038020887411">"Nweba i-<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
+    <string name="permission_collapse" msgid="3320833884220844084">"Goqa i-<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Nikeza ama-app &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; izimvume ezifanayot &lt;strong&gt;njengaku-<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="permission_sync_summary" msgid="765497944331294275">"Lokhu kungahilela &lt;strong&gt;Imakrofoni&lt;/strong&gt;, &lt;strong&gt;Ikhamera&lt;/strong&gt;, kanye &lt;strong&gt;Nokufinyelelwa kwendawo&lt;/strong&gt;, nezinye izimvume ezizwelayo &lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g>&lt;/strong&gt;. &lt;br/&gt;&lt;br/&gt;Ungakwazi ukushintsha lezi zimvume noma nini Kumasethingi akho ku-&lt;strong&gt;<xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g>&lt;/strong&gt;."</string>
-    <string name="vendor_icon_description" msgid="4445875290032225965">"Isithonjana Se-app"</string>
-    <string name="vendor_header_button_description" msgid="6566660389500630608">"Inkinobho Yolwazi Olwengeziwe"</string>
+    <string name="vendor_header_button_description" msgid="7994879208461111473">"Olunye ulwazi"</string>
     <string name="permission_phone" msgid="2661081078692784919">"Ifoni"</string>
     <string name="permission_sms" msgid="6337141296535774786">"I-SMS"</string>
     <string name="permission_contacts" msgid="3858319347208004438">"Oxhumana nabo"</string>
@@ -57,16 +57,18 @@
     <string name="permission_storage" msgid="6831099350839392343">"Izithombe nemidiya"</string>
     <string name="permission_notification" msgid="693762568127741203">"Izaziso"</string>
     <string name="permission_app_streaming" msgid="6009695219091526422">"Ama-app"</string>
-    <string name="permission_nearby_device_streaming" msgid="5868108148065023161">"Ukusakazwa Kwedivayisi Eseduze"</string>
+    <string name="permission_nearby_device_streaming" msgid="1023325519477349499">"Iyasakaza"</string>
     <string name="permission_phone_summary" msgid="6684396967861278044">"Ingenza futhi iphathe amakholi wefoni"</string>
     <string name="permission_call_logs_summary" msgid="6186103394658755022">"Ingafunda futhi ibhale irekhodi lamakholi efoni"</string>
     <string name="permission_sms_summary" msgid="3508442683678912017">"Ingathumela futhi ibuke imiyalezo ye-SMS"</string>
     <string name="permission_contacts_summary" msgid="675861979475628708">"Ingakwazi ukufinyelela oxhumana nabo"</string>
     <string name="permission_calendar_summary" msgid="6460000922511766226">"Ingakwazi ukufinyelela ikhalenda lakho"</string>
-    <string name="permission_microphone_summary" msgid="4241354865859396558">"Ingakwazi ukurekhoda umsindo isebenzisa imakrofoni"</string>
+    <string name="permission_microphone_summary" msgid="3692091540613093394">"Ingakwazi ukurekhoda umsindo"</string>
     <string name="permission_nearby_devices_summary" msgid="931940524460876655">"Ingathola, ixhume, futhi inqume indawo ehlobene yamadivayisi aseduze"</string>
     <string name="permission_notification_summary" msgid="884075314530071011">"Ingafunda zonke izaziso, okubandakanya ulwazi olufana noxhumana nabo, imilayezo, nezithombe"</string>
     <string name="permission_app_streaming_summary" msgid="606923325679670624">"Sakaza ama-app wefoni yakho"</string>
     <string name="permission_storage_summary" msgid="3918240895519506417"></string>
-    <string name="permission_nearby_device_streaming_summary" msgid="5776807830582725074">"Sakaza okuqukethwe kudivayisi eseduze"</string>
+    <string name="permission_nearby_device_streaming_summary" msgid="8280824871197081246">"Sakaza ama-app nezinye izakhi zesistimu kusuka kufoni yakho"</string>
+    <string name="device_type" product="default" msgid="8268703872070046263">"ifoni"</string>
+    <string name="device_type" product="tablet" msgid="5038791954983067774">"ithebulethi"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values/strings.xml b/packages/CompanionDeviceManager/res/values/strings.xml
index 2502bbf..7a6fad4 100644
--- a/packages/CompanionDeviceManager/res/values/strings.xml
+++ b/packages/CompanionDeviceManager/res/values/strings.xml
@@ -20,7 +20,7 @@
     <string name="app_label">Companion Device Manager</string>
 
     <!-- Title of the device association confirmation dialog. -->
-    <string name="confirmation_title">Allow &lt;strong&gt;<xliff:g id="app_name" example="Android Wear">%1$s</xliff:g>&lt;/strong&gt; to access &lt;strong&gt;<xliff:g id="device_name" example="ASUS ZenWatch 2">%2$s</xliff:g>&lt;/strong&gt;?</string>
+    <string name="confirmation_title">Allow the app &lt;strong&gt;<xliff:g id="app_name" example="Android Wear">%1$s</xliff:g>&lt;/strong&gt; to access &lt;strong&gt;<xliff:g id="device_name" example="ASUS ZenWatch 2">%2$s</xliff:g>&lt;/strong&gt;?</string>
 
     <!-- ================= DEVICE_PROFILE_WATCH and null profile ================= -->
 
@@ -28,13 +28,13 @@
     <string name="profile_name_watch">watch</string>
 
     <!-- Title of the device selection dialog. -->
-    <string name="chooser_title">Choose a <xliff:g id="profile_name" example="watch">%1$s</xliff:g> to be managed by &lt;strong&gt;<xliff:g id="app_name" example="Android Wear">%2$s</xliff:g>&lt;/strong&gt;</string>
+    <string name="chooser_title_non_profile">Choose a device to be managed by &lt;strong&gt;<xliff:g id="app_name" example="Android Wear">%1$s</xliff:g>&lt;/strong&gt;</string>
 
-    <!-- Description of the privileges the application will get if associated with the companion device of WATCH profile (type) [CHAR LIMIT=NONE] -->
-    <string name="summary_watch">This app is needed to manage your <xliff:g id="device_name" example="My Watch">%1$s</xliff:g>. <xliff:g id="app_name" example="Android Wear">%2$s</xliff:g> will be allowed to sync info, like the name of someone calling, interact with your notifications and access your Phone, SMS, Contacts, Calendar, Call logs and Nearby devices permissions.</string>
+    <!-- Tile of the multiple devices' dialog. -->
+    <string name="chooser_title">Choose a <xliff:g id="profile_name" example="watch">%1$s</xliff:g> to set up</string>
 
-    <!-- Description of the privileges the application will get if associated with the companion device of WATCH profile for singleDevice(type) [CHAR LIMIT=NONE] -->
-    <string name="summary_watch_single_device">This app will be allowed to sync info, like the name of someone calling, and access these permissions on your <xliff:g id="device_type" example="phone">%1$s</xliff:g></string>
+    <!-- Description of the privileges the application will get if associated with the companion device of WATCH profile [CHAR LIMIT=NONE] -->
+    <string name="summary_watch">This app will be allowed to sync info, like the name of someone calling, and access these permissions on your <xliff:g id="device_name" example="phone">%1$s</xliff:g></string>
 
     <!-- ================= DEVICE_PROFILE_GLASSES ================= -->
 
@@ -42,13 +42,10 @@
     <string name="confirmation_title_glasses">Allow &lt;strong&gt;<xliff:g id="app_name" example="Android Wear">%1$s</xliff:g>&lt;/strong&gt; to manage &lt;strong&gt;<xliff:g id="device_name" example="Glasses">%2$s</xliff:g>&lt;/strong&gt;?</string>
 
     <!-- The name of the "glasses" device type [CHAR LIMIT=30] -->
-    <string name="profile_name_glasses">glasses</string>
+    <string name="profile_name_glasses">device</string>
 
-    <!-- Description of the privileges the application will get if associated with the companion device of GLASSES profile (type) [CHAR LIMIT=NONE] -->
-    <string name="summary_glasses_multi_device">This app is needed to manage <xliff:g id="device_name" example="My Glasses">%1$s</xliff:g>. <xliff:g id="app_name" example="Glasses">%2$s</xliff:g> will be allowed to interact with your notifications and access your Phone, SMS, Contacts, Microphone and Nearby devices permissions.</string>
-
-    <!-- Description of the privileges the application will get if associated with the companion device of GLASSES profile for singleDevice(type) [CHAR LIMIT=NONE] -->
-    <string name="summary_glasses_single_device">This app will be allowed to access these permissions on your <xliff:g id="device_type" example="phone">%1$s</xliff:g></string>
+    <!-- Description of the privileges the application will get if associated with the companion device of GLASSES profile [CHAR LIMIT=NONE] -->
+    <string name="summary_glasses">This app will be allowed to access these permissions on your <xliff:g id="device_name" example="phone">%1$s</xliff:g></string>
 
     <!-- ================= DEVICE_PROFILE_APP_STREAMING ================= -->
 
@@ -97,9 +94,6 @@
     <string name="profile_name_generic">device</string>
 
     <!-- Description of the privileges the application will get if associated with the companion device of unspecified profile (type) [CHAR LIMIT=NONE] -->
-    <string name="summary_generic_single_device">This app will be able to sync info, like the name of someone calling, between your phone and <xliff:g id="device_name" example="My Watch">%1$s</xliff:g></string>
-
-    <!-- Description of the privileges the application will get if associated with the companion device of unspecified profile (type) [CHAR LIMIT=NONE] -->
     <string name="summary_generic">This app will be able to sync info, like the name of someone calling, between your phone and the chosen device</string>
 
     <!-- ================= Buttons ================= -->
@@ -110,6 +104,9 @@
     <!-- Negative button for the device-app association consent dialog [CHAR LIMIT=30] -->
     <string name="consent_no">Don\u2019t allow</string>
 
+    <!-- Cancel button for the device chooser dialog [CHAR LIMIT=30] -->
+    <string name="consent_cancel">Cancel</string>
+
     <!-- Back button for the helper consent dialog [CHAR LIMIT=30] -->
     <string name="consent_back">Back</string>
 
diff --git a/packages/CompanionDeviceManager/res/values/styles.xml b/packages/CompanionDeviceManager/res/values/styles.xml
index e85190b..222877b 100644
--- a/packages/CompanionDeviceManager/res/values/styles.xml
+++ b/packages/CompanionDeviceManager/res/values/styles.xml
@@ -69,11 +69,13 @@
 
     <style name="PositiveButton"
            parent="@android:style/Widget.Material.Button.Borderless.Colored">
-        <item name="android:layout_width">300dp</item>
-        <item name="android:layout_height">56dp</item>
+        <item name="android:layout_width">match_parent</item>
+        <item name="android:layout_height">wrap_content</item>
         <item name="android:layout_marginBottom">2dp</item>
         <item name="android:textAllCaps">false</item>
         <item name="android:textSize">14sp</item>
+        <item name="android:layout_marginStart">32dp</item>
+        <item name="android:layout_marginEnd">32dp</item>
         <item name="android:textColor">@android:color/system_neutral1_900</item>
         <item name="android:textAppearance">@android:style/TextAppearance.DeviceDefault.Medium</item>
         <item name="android:background">@drawable/btn_positive_bottom</item>
@@ -81,11 +83,13 @@
 
     <style name="NegativeButton"
            parent="@android:style/Widget.Material.Button.Borderless.Colored">
-        <item name="android:layout_width">300dp</item>
-        <item name="android:layout_height">56dp</item>
+        <item name="android:layout_width">match_parent</item>
+        <item name="android:layout_height">wrap_content</item>
         <item name="android:layout_marginTop">2dp</item>
         <item name="android:textAllCaps">false</item>
         <item name="android:textSize">14sp</item>
+        <item name="android:layout_marginStart">32dp</item>
+        <item name="android:layout_marginEnd">32dp</item>
         <item name="android:textColor">@android:color/system_neutral1_900</item>
         <item name="android:layout_marginTop">4dp</item>
         <item name="android:textAppearance">@android:style/TextAppearance.DeviceDefault.Medium</item>
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
index 4154029..97016f5 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
@@ -27,10 +27,8 @@
 
 import static com.android.companiondevicemanager.CompanionDeviceDiscoveryService.DiscoveryState;
 import static com.android.companiondevicemanager.CompanionDeviceDiscoveryService.DiscoveryState.FINISHED_TIMEOUT;
-import static com.android.companiondevicemanager.CompanionDeviceResources.MULTI_DEVICES_SUMMARIES;
 import static com.android.companiondevicemanager.CompanionDeviceResources.PERMISSION_TYPES;
 import static com.android.companiondevicemanager.CompanionDeviceResources.PROFILES_NAME;
-import static com.android.companiondevicemanager.CompanionDeviceResources.PROFILES_NAME_MULTI;
 import static com.android.companiondevicemanager.CompanionDeviceResources.PROFILE_ICON;
 import static com.android.companiondevicemanager.CompanionDeviceResources.SUMMARIES;
 import static com.android.companiondevicemanager.CompanionDeviceResources.SUPPORTED_PROFILES;
@@ -121,6 +119,9 @@
     private IAssociationRequestCallback mAppCallback;
     private ResultReceiver mCdmServiceReceiver;
 
+    // Present for application's name.
+    private CharSequence mAppLabel;
+
     // Always present widgets.
     private TextView mTitle;
     private TextView mSummary;
@@ -165,8 +166,7 @@
     private @Nullable RecyclerView mDeviceListRecyclerView;
     private @Nullable DeviceListAdapter mDeviceAdapter;
 
-
-    // The recycler view is only shown for selfManaged and singleDevice  association request.
+    // The recycler view is shown for non-null profile association request.
     private @Nullable RecyclerView mPermissionListRecyclerView;
     private @Nullable PermissionListAdapter mPermissionListAdapter;
 
@@ -178,8 +178,6 @@
     // onActivityResult() after the association is created.
     private @Nullable DeviceFilterPair<?> mSelectedDevice;
 
-    private @Nullable List<Integer> mPermissionTypes;
-
     private LinearLayoutManager mPermissionsLayoutManager = new LinearLayoutManager(this);
 
     @Override
@@ -302,6 +300,8 @@
 
         setContentView(R.layout.activity_confirmation);
 
+        mAppLabel = appLabel;
+
         mConstraintList = findViewById(R.id.constraint_list);
         mAssociationConfirmationDialog = findViewById(R.id.association_confirmation);
         mVendorHeader = findViewById(R.id.vendor_header);
@@ -322,7 +322,6 @@
 
         mMultipleDeviceSpinner = findViewById(R.id.spinner_multiple_device);
         mSingleDeviceSpinner = findViewById(R.id.spinner_single_device);
-        mDeviceAdapter = new DeviceListAdapter(this, this::onListItemClick);
 
         mPermissionListRecyclerView = findViewById(R.id.permission_list);
         mPermissionListAdapter = new PermissionListAdapter(this);
@@ -468,8 +467,6 @@
             throw new RuntimeException("Unsupported profile " + deviceProfile);
         }
 
-        mPermissionTypes = new ArrayList<>();
-
         try {
             vendorIcon = getVendorHeaderIcon(this, packageName, userId);
             vendorName = getVendorHeaderName(this, packageName, userId);
@@ -486,17 +483,13 @@
         }
 
         title = getHtmlFromResources(this, TITLES.get(deviceProfile), deviceName);
-        mPermissionTypes.addAll(PERMISSION_TYPES.get(deviceProfile));
+        setupPermissionList(deviceProfile);
 
         // Summary is not needed for selfManaged dialog.
         mSummary.setVisibility(View.GONE);
-
-        setupPermissionList();
-
         mTitle.setText(title);
         mVendorHeaderName.setText(vendorName);
         mVendorHeader.setVisibility(View.VISIBLE);
-        mVendorHeader.setVisibility(View.VISIBLE);
         mProfileIcon.setVisibility(View.GONE);
         mDeviceListRecyclerView.setVisibility(View.GONE);
         // Top and bottom borders should be gone for selfManaged dialog.
@@ -509,7 +502,9 @@
 
         final String deviceProfile = mRequest.getDeviceProfile();
 
-        mPermissionTypes = new ArrayList<>();
+        if (!SUPPORTED_PROFILES.contains(deviceProfile)) {
+            throw new RuntimeException("Unsupported profile " + deviceProfile);
+        }
 
         CompanionDeviceDiscoveryService.getScanResult().observe(this,
                 deviceFilterPairs -> updateSingleDeviceUi(
@@ -529,75 +524,40 @@
         if (deviceFilterPairs.isEmpty()) return;
 
         mSelectedDevice = requireNonNull(deviceFilterPairs.get(0));
-        // No need to show user consent dialog if it is a singleDevice
-        // and isSkipPrompt(true) AssociationRequest.
-        // See AssociationRequestsProcessor#mayAssociateWithoutPrompt.
-        if (mRequest.isSkipPrompt()) {
-            mSingleDeviceSpinner.setVisibility(View.GONE);
-            onUserSelectedDevice(mSelectedDevice);
-            return;
-        }
 
-        final String deviceName = mSelectedDevice.getDisplayName();
-        final Spanned title;
-        final Spanned summary;
-        final Drawable profileIcon;
+        final Drawable profileIcon = getIcon(this, PROFILE_ICON.get(deviceProfile));
 
-        if (!SUPPORTED_PROFILES.contains(deviceProfile)) {
-            throw new RuntimeException("Unsupported profile " + deviceProfile);
-        }
+        updatePermissionUi();
 
-        if (deviceProfile == null) {
-            summary = getHtmlFromResources(this, SUMMARIES.get(null), deviceName);
-            mConstraintList.setVisibility(View.GONE);
-        } else {
-            summary = getHtmlFromResources(
-                    this, SUMMARIES.get(deviceProfile), getString(R.string.device_type));
-            mPermissionTypes.addAll(PERMISSION_TYPES.get(deviceProfile));
-            setupPermissionList();
-        }
-
-        title = getHtmlFromResources(this, TITLES.get(deviceProfile), appLabel, deviceName);
-        profileIcon = getIcon(this, PROFILE_ICON.get(deviceProfile));
-
-        mTitle.setText(title);
-        mSummary.setText(summary);
         mProfileIcon.setImageDrawable(profileIcon);
-        mSingleDeviceSpinner.setVisibility(View.GONE);
         mAssociationConfirmationDialog.setVisibility(View.VISIBLE);
+        mSingleDeviceSpinner.setVisibility(View.GONE);
     }
 
     private void initUiForMultipleDevices(CharSequence appLabel) {
         if (DEBUG) Log.i(TAG, "initUiFor_MultipleDevices()");
 
-        final String deviceProfile = mRequest.getDeviceProfile();
-
-        final String profileName;
-        final String profileNameMulti;
-        final Spanned summary;
         final Drawable profileIcon;
-        final int summaryResourceId;
+        final Spanned title;
+        final String deviceProfile = mRequest.getDeviceProfile();
 
         if (!SUPPORTED_PROFILES.contains(deviceProfile)) {
             throw new RuntimeException("Unsupported profile " + deviceProfile);
         }
 
-        profileName = getString(PROFILES_NAME.get(deviceProfile));
-        profileNameMulti = getString(PROFILES_NAME_MULTI.get(deviceProfile));
         profileIcon = getIcon(this, PROFILE_ICON.get(deviceProfile));
-        summaryResourceId = MULTI_DEVICES_SUMMARIES.get(deviceProfile);
 
         if (deviceProfile == null) {
-            summary = getHtmlFromResources(this, summaryResourceId);
+            title = getHtmlFromResources(this, R.string.chooser_title_non_profile, appLabel);
+            mButtonNotAllowMultipleDevices.setText(R.string.consent_no);
         } else {
-            summary = getHtmlFromResources(this, summaryResourceId, profileName, appLabel);
+            title = getHtmlFromResources(this,
+                    R.string.chooser_title, getString(PROFILES_NAME.get(deviceProfile)));
         }
 
-        final Spanned title = getHtmlFromResources(
-                this, R.string.chooser_title, profileNameMulti, appLabel);
+        mDeviceAdapter = new DeviceListAdapter(this, this::onDeviceClicked);
 
         mTitle.setText(title);
-        mSummary.setText(summary);
         mProfileIcon.setImageDrawable(profileIcon);
 
         mDeviceListRecyclerView.setAdapter(mDeviceAdapter);
@@ -613,6 +573,7 @@
                     mDeviceAdapter.setDevices(deviceFilterPairs);
                 });
 
+        mSummary.setVisibility(View.GONE);
         // "Remove" consent button: users would need to click on the list item.
         mButtonAllow.setVisibility(View.GONE);
         mButtonNotAllow.setVisibility(View.GONE);
@@ -623,11 +584,9 @@
         mMultipleDeviceSpinner.setVisibility(View.VISIBLE);
     }
 
-    private void onListItemClick(int position) {
-        if (DEBUG) Log.d(TAG, "onListItemClick() " + position);
-
+    private void onDeviceClicked(int position) {
         final DeviceFilterPair<?> selectedDevice = mDeviceAdapter.getItem(position);
-
+        // To prevent double tap on the selected device.
         if (mSelectedDevice != null) {
             if (DEBUG) Log.w(TAG, "Already selected.");
             return;
@@ -637,7 +596,47 @@
 
         mSelectedDevice = requireNonNull(selectedDevice);
 
-        onUserSelectedDevice(selectedDevice);
+        Log.d(TAG, "onDeviceClicked(): " + mSelectedDevice.toShortString());
+
+        updatePermissionUi();
+
+        mSummary.setVisibility(View.VISIBLE);
+        mButtonAllow.setVisibility(View.VISIBLE);
+        mButtonNotAllow.setVisibility(View.VISIBLE);
+        mDeviceListRecyclerView.setVisibility(View.GONE);
+        mNotAllowMultipleDevicesLayout.setVisibility(View.GONE);
+    }
+
+    private void updatePermissionUi() {
+        final String deviceProfile = mRequest.getDeviceProfile();
+        final int summaryResourceId = SUMMARIES.get(deviceProfile);
+        final String remoteDeviceName = mSelectedDevice.getDisplayName();
+        final Spanned title = getHtmlFromResources(
+                this, TITLES.get(deviceProfile), mAppLabel, remoteDeviceName);
+        final Spanned summary;
+
+        // No need to show permission consent dialog if it is a isSkipPrompt(true)
+        // AssociationRequest. See AssociationRequestsProcessor#mayAssociateWithoutPrompt.
+        if (mRequest.isSkipPrompt()) {
+            mSingleDeviceSpinner.setVisibility(View.GONE);
+            onUserSelectedDevice(mSelectedDevice);
+            return;
+        }
+
+        if (deviceProfile == null && mRequest.isSingleDevice()) {
+            summary = getHtmlFromResources(this, summaryResourceId, remoteDeviceName);
+            mConstraintList.setVisibility(View.GONE);
+        } else if (deviceProfile == null) {
+            onUserSelectedDevice(mSelectedDevice);
+            return;
+        } else {
+            summary = getHtmlFromResources(
+                    this, summaryResourceId, getString(R.string.device_type));
+            setupPermissionList(deviceProfile);
+        }
+
+        mTitle.setText(title);
+        mSummary.setText(summary);
     }
 
     private void onPositiveButtonClick(View v) {
@@ -680,8 +679,9 @@
     // initiate the layoutManager for the recyclerview, add listeners for monitoring the scrolling
     // and when mPermissionListRecyclerView is fully populated.
     // Lastly, disable the Allow and Don't allow buttons.
-    private void setupPermissionList() {
-        mPermissionListAdapter.setPermissionType(mPermissionTypes);
+    private void setupPermissionList(String deviceProfile) {
+        final List<Integer> permissionTypes = new ArrayList<>(PERMISSION_TYPES.get(deviceProfile));
+        mPermissionListAdapter.setPermissionType(permissionTypes);
         mPermissionListRecyclerView.setAdapter(mPermissionListAdapter);
         mPermissionListRecyclerView.setLayoutManager(mPermissionsLayoutManager);
 
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceResources.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceResources.java
index 7aed139..060c032 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceResources.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceResources.java
@@ -86,21 +86,11 @@
     static final Map<String, Integer> SUMMARIES;
     static {
         final Map<String, Integer> map = new ArrayMap<>();
-        map.put(DEVICE_PROFILE_WATCH, R.string.summary_watch_single_device);
-        map.put(DEVICE_PROFILE_GLASSES, R.string.summary_glasses_single_device);
-        map.put(null, R.string.summary_generic_single_device);
-
-        SUMMARIES = unmodifiableMap(map);
-    }
-
-    static final Map<String, Integer> MULTI_DEVICES_SUMMARIES;
-    static {
-        final Map<String, Integer> map = new ArrayMap<>();
         map.put(DEVICE_PROFILE_WATCH, R.string.summary_watch);
-        map.put(DEVICE_PROFILE_GLASSES, R.string.summary_glasses_multi_device);
+        map.put(DEVICE_PROFILE_GLASSES, R.string.summary_glasses);
         map.put(null, R.string.summary_generic);
 
-        MULTI_DEVICES_SUMMARIES = unmodifiableMap(map);
+        SUMMARIES = unmodifiableMap(map);
     }
 
     static final Map<String, Integer> PROFILES_NAME;
diff --git a/packages/CredentialManager/res/values-af/strings.xml b/packages/CredentialManager/res/values-af/strings.xml
index 2aded3b..0c13cb2 100644
--- a/packages/CredentialManager/res/values-af/strings.xml
+++ b/packages/CredentialManager/res/values-af/strings.xml
@@ -1,10 +1,26 @@
 <?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="4539824758261855508">"Eiebewysbestuurder"</string>
     <string name="string_cancel" msgid="6369133483981306063">"Kanselleer"</string>
     <string name="string_continue" msgid="1346732695941131882">"Gaan voort"</string>
-    <string name="string_more_options" msgid="7990658711962795124">"Meer opsies"</string>
+    <string name="string_more_options" msgid="2763852250269945472">"Stoor op ’n ander manier"</string>
     <string name="string_learn_more" msgid="4541600451688392447">"Kom meer te wete"</string>
     <string name="content_description_show_password" msgid="3283502010388521607">"Wys wagwoord"</string>
     <string name="content_description_hide_password" msgid="6841375971631767996">"Versteek wagwoord"</string>
@@ -33,10 +49,13 @@
     <string name="sign_ins" msgid="4710739369149469208">"aanmeldings"</string>
     <string name="sign_in_info" msgid="2627704710674232328">"aanmeldinligting"</string>
     <string name="save_credential_to_title" msgid="3172811692275634301">"Stoor <xliff:g id="CREDENTIALTYPES">%1$s</xliff:g> in"</string>
-    <string name="create_passkey_in_other_device_title" msgid="9195411122362461390">"Skep wagwoordsleutel op ’n ander toestel?"</string>
+    <string name="create_passkey_in_other_device_title" msgid="2360053098931886245">"Skep wagwoordsleutel op ’n ander toestel?"</string>
+    <string name="save_password_on_other_device_title" msgid="5829084591948321207">"Stoor wagwoord op ’n ander toestel?"</string>
+    <string name="save_sign_in_on_other_device_title" msgid="2827990118560134692">"Stoor aanmelding op ’n ander toestel?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"Gebruik <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> vir al jou aanmeldings?"</string>
-    <string name="use_provider_for_all_description" msgid="8466427781848268490">"Hierdie wagwoordbestuurder sal jou wagwoorde en wagwoordsleutels berg om jou te help om maklik aan te meld"</string>
+    <string name="use_provider_for_all_description" msgid="1998772715863958997">"Hierdie wagwoordbestuurder vir <xliff:g id="USERNAME">%1$s</xliff:g> sal jou wagwoorde en wagwoordsleutels berg om jou te help om maklik aan te meld"</string>
     <string name="set_as_default" msgid="4415328591568654603">"Stel as verstek"</string>
+    <string name="settings" msgid="6536394145760913145">"Instellings"</string>
     <string name="use_once" msgid="9027366575315399714">"Gebruik een keer"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> wagwoorde • <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> wagwoordsleutels"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> wagwoorde"</string>
@@ -47,21 +66,30 @@
     <string name="other_password_manager" msgid="565790221427004141">"Ander wagwoordbestuurders"</string>
     <string name="close_sheet" msgid="1393792015338908262">"Maak sigblad toe"</string>
     <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Gaan terug na die vorige bladsy"</string>
-    <string name="accessibility_close_button" msgid="2953807735590034688">"Maak die Eiebewysbestuurder se handelingvoorstel toe wat onderaan die skerm verskyn"</string>
+    <string name="accessibility_close_button" msgid="1163435587545377687">"Maak toe"</string>
+    <string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"Maak toe"</string>
     <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Gebruik jou gestoorde wagwoordsleutel vir <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
-    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Gebruik jou gestoorde aanmelding vir <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
-    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Kies ’n gestoorde aanmelding vir <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_use_password_for" msgid="625828023234318484">"Gebruik jou gestoorde wagwoord vir <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"Gebruik jou aanmelding vir <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"Ontsluit aanmeldingopsies vir <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"Kies ’n gestoorde wagwoordsleutel vir <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"Kies ’n gestoorde wagwoord vir <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_saved_sign_in_for" msgid="2420298653461652728">"Kies ’n gestoorde aanmelding vir <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="3048870756117876514">"Kies ’n aanmelding vir <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_option_for" msgid="4976380044745029107">"Kies ’n opsie vir <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_info_on" msgid="8863708099535435146">"Gebruik hierdie inligting op <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Meld op ’n ander manier aan"</string>
     <string name="snackbar_action" msgid="37373514216505085">"Bekyk opsies"</string>
     <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Gaan voort"</string>
     <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Aanmeldopsies"</string>
+    <string name="button_label_view_more" msgid="3429098227286495651">"Bekyk meer"</string>
     <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Vir <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Geslote wagwoordbestuurders"</string>
-    <!-- no translation found for locked_credential_entry_label_subtext_tap_to_unlock (6390367581393605009) -->
-    <skip />
-    <!-- no translation found for locked_credential_entry_label_subtext_no_sign_in (8131725029983174901) -->
-    <skip />
+    <string name="locked_credential_entry_label_subtext_tap_to_unlock" msgid="6390367581393605009">"Tik om te ontsluit"</string>
+    <string name="locked_credential_entry_label_subtext_no_sign_in" msgid="8131725029983174901">"Geen aanmeldinligting nie"</string>
+    <string name="no_sign_in_info_in" msgid="2641118151920288356">"Geen aanmeldinligting in <xliff:g id="SOURCE">%1$s</xliff:g> nie"</string>
     <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Bestuur aanmeldings"</string>
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Van ’n ander toestel af"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Gebruik ’n ander toestel"</string>
+    <string name="request_cancelled_by" msgid="3735222326886267820">"Versoek is deur <xliff:g id="APP_NAME">%1$s</xliff:g> gekanselleer"</string>
 </resources>
diff --git a/packages/CredentialManager/res/values-am/strings.xml b/packages/CredentialManager/res/values-am/strings.xml
index 91a26c0..475dcf7 100644
--- a/packages/CredentialManager/res/values-am/strings.xml
+++ b/packages/CredentialManager/res/values-am/strings.xml
@@ -1,18 +1,32 @@
 <?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.
+   -->
+
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="4539824758261855508">"የመግቢያ ማስረጃ አስተዳዳሪ"</string>
     <string name="string_cancel" msgid="6369133483981306063">"ይቅር"</string>
     <string name="string_continue" msgid="1346732695941131882">"ቀጥል"</string>
-    <string name="string_more_options" msgid="7990658711962795124">"ተጨማሪ አማራጮች"</string>
+    <string name="string_more_options" msgid="2763852250269945472">"በሌላ መንገድ አስቀምጥ"</string>
     <string name="string_learn_more" msgid="4541600451688392447">"የበለጠ ለመረዳት"</string>
-    <!-- no translation found for content_description_show_password (3283502010388521607) -->
-    <skip />
-    <!-- no translation found for content_description_hide_password (6841375971631767996) -->
-    <skip />
-    <string name="passkey_creation_intro_title" msgid="4251037543787718844">"በይለፍ ቃል ይበልጥ ደህንነቱ የተጠበቀ"</string>
+    <string name="content_description_show_password" msgid="3283502010388521607">"የይለፍ ቃል አሳይ"</string>
+    <string name="content_description_hide_password" msgid="6841375971631767996">"የይለፍ ቃል ደብቅ"</string>
+    <string name="passkey_creation_intro_title" msgid="4251037543787718844">"በይለፍ ቁልፎች ይበልጥ ደህንነቱ የተጠበቀ"</string>
     <string name="passkey_creation_intro_body_password" msgid="8825872426579958200">"በይለፍ ቁልፎች ውስብስብ የይለፍ ቁልፎችን መፍጠር ወይም ማስታወስ አያስፈልግዎትም"</string>
-    <string name="passkey_creation_intro_body_fingerprint" msgid="7331338631826254055">"የይለፍ ቁልፎች የእርስዎን የጣት አሻራ፣ መልክ ወይም የማያ ገጽ መቆለፊያ በመጠቀም የሚፈጥሯቸው የተመሰጠሩ ዲጂታል ቆልፎች ናቸው"</string>
+    <string name="passkey_creation_intro_body_fingerprint" msgid="7331338631826254055">"የይለፍ ቁልፎች የእርስዎን የጣት አሻራ፣ መልክ ወይም የማያ ገፅ መቆለፊያ በመጠቀም የሚፈጥሯቸው የተመሰጠሩ ዲጂታል ቆልፎች ናቸው"</string>
     <string name="passkey_creation_intro_body_device" msgid="1203796455762131631">"በሌሎች መሣሪያዎች ላይ መግባት እንዲችሉ በሚስጥር ቁልፍ አስተዳዳሪ ላይ ይቀመጣሉ"</string>
     <string name="more_about_passkeys_title" msgid="7797903098728837795">"ስለየይለፍ ቁልፎች ተጨማሪ"</string>
     <string name="passwordless_technology_title" msgid="2497513482056606668">"የይለፍ ቃል የሌለው ቴክኖሎጂ"</string>
@@ -35,10 +49,13 @@
     <string name="sign_ins" msgid="4710739369149469208">"መግቢያዎች"</string>
     <string name="sign_in_info" msgid="2627704710674232328">"የመግቢያ መረጃ"</string>
     <string name="save_credential_to_title" msgid="3172811692275634301">"<xliff:g id="CREDENTIALTYPES">%1$s</xliff:g>ን አስቀምጥ ወደ"</string>
-    <string name="create_passkey_in_other_device_title" msgid="9195411122362461390">"በሌላ መሣሪያ ውስጥ የይለፍ ቁልፍ ይፈጠር?"</string>
+    <string name="create_passkey_in_other_device_title" msgid="2360053098931886245">"በሌላ መሣሪያ ላይ የይለፍ ቁልፍ ይፈጠር?"</string>
+    <string name="save_password_on_other_device_title" msgid="5829084591948321207">"የይለፍ ቃል በሌላ መሣሪያ ላይ ይቀመጥ?"</string>
+    <string name="save_sign_in_on_other_device_title" msgid="2827990118560134692">"በመለያ መግቢያ በሌላ መሣሪያ ላይ ይቀመጥ?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"ለሁሉም መግቢያዎችዎ <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>ን ይጠቀሙ?"</string>
-    <string name="use_provider_for_all_description" msgid="8466427781848268490">"ይህ የሚስጥር ቁልፍ አስተዳዳሪ እርስዎን በቀላሉ በመለያ እንዲገቡ ለማገዝ የእርስዎን የይለፍ ቃላት እና የይለፍ ቁልፎችን ያከማቻል"</string>
+    <string name="use_provider_for_all_description" msgid="1998772715863958997">"ይህ የ<xliff:g id="USERNAME">%1$s</xliff:g> የይለፍ ቃል አስተዳዳሪ በቀላሉ እንዲገቡ ለማገዝ የእርስዎን የይለፍ ቃላት እና የይለፍ ቁልፎች ያከማቻል"</string>
     <string name="set_as_default" msgid="4415328591568654603">"እንደ ነባሪ ያዋቅሩ"</string>
+    <string name="settings" msgid="6536394145760913145">"ቅንብሮች"</string>
     <string name="use_once" msgid="9027366575315399714">"አንዴ ይጠቀሙ"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> የይለፍ ቃሎች • <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> የይለፍ ቁልፎች"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> የይለፍ ቃሎች"</string>
@@ -49,21 +66,30 @@
     <string name="other_password_manager" msgid="565790221427004141">"ሌሎች የይለፍ ቃል አስተዳዳሪዎች"</string>
     <string name="close_sheet" msgid="1393792015338908262">"ሉህን ዝጋ"</string>
     <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"ወደ ቀዳሚው ገፅ ይመለሱ"</string>
-    <string name="accessibility_close_button" msgid="2953807735590034688">"በማያ ገጹ ግርጌ ላይ የሚታየውን የመግቢያ ማስረጃ አስተዳዳሪ የእርምጃ ጥቆማን ዝጋ"</string>
+    <string name="accessibility_close_button" msgid="1163435587545377687">"ዝጋ"</string>
+    <string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"አሰናብት"</string>
     <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"የተቀመጠ የይለፍ ቁልፍዎን ለ<xliff:g id="APP_NAME">%1$s</xliff:g> ይጠቀሙ?"</string>
-    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"የተቀመጠ መግቢያዎን ለ<xliff:g id="APP_NAME">%1$s</xliff:g> ይጠቀሙ?"</string>
-    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"ለ<xliff:g id="APP_NAME">%1$s</xliff:g> የተቀመጠ መግቢያ ይጠቀሙ"</string>
+    <string name="get_dialog_title_use_password_for" msgid="625828023234318484">"ለ<xliff:g id="APP_NAME">%1$s</xliff:g> የተቀመጠውን የይለፍ ቃልዎን ይጠቀሙ?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"ለ<xliff:g id="APP_NAME">%1$s</xliff:g> መግቢያዎ ጥቅም ላይ ይዋል?"</string>
+    <string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"ለ<xliff:g id="APP_NAME">%1$s</xliff:g> የመግቢያ አማራጮች ይከፈቱ?"</string>
+    <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"ለ<xliff:g id="APP_NAME">%1$s</xliff:g> የተቀመጠ የይለፍ ቁልፍ ይምረጡ"</string>
+    <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"ለ<xliff:g id="APP_NAME">%1$s</xliff:g> የተቀመጠ የይለፍ ቃል ይምረጡ"</string>
+    <string name="get_dialog_title_choose_saved_sign_in_for" msgid="2420298653461652728">"ለ<xliff:g id="APP_NAME">%1$s</xliff:g> የተቀመጠ መግቢያ ይጠቀሙ"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="3048870756117876514">"ለ<xliff:g id="APP_NAME">%1$s</xliff:g> መግቢያ ይምረጡ"</string>
+    <string name="get_dialog_title_choose_option_for" msgid="4976380044745029107">"ለ<xliff:g id="APP_NAME">%1$s</xliff:g> አማራጭ ይመረጥ?"</string>
+    <string name="get_dialog_title_use_info_on" msgid="8863708099535435146">"ይህን መረጃ በ<xliff:g id="APP_NAME">%1$s</xliff:g> ላይ ይጠቀማሉ?"</string>
     <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"በሌላ መንገድ ይግቡ"</string>
     <string name="snackbar_action" msgid="37373514216505085">"አማራጮችን አሳይ"</string>
     <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"ቀጥል"</string>
     <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"የመግቢያ አማራጮች"</string>
+    <string name="button_label_view_more" msgid="3429098227286495651">"ተጨማሪ አሳይ"</string>
     <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"ለ<xliff:g id="USERNAME">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"የተቆለፉ የሚስጥር ቁልፍ አስተዳዳሪዎች"</string>
-    <!-- no translation found for locked_credential_entry_label_subtext_tap_to_unlock (6390367581393605009) -->
-    <skip />
-    <!-- no translation found for locked_credential_entry_label_subtext_no_sign_in (8131725029983174901) -->
-    <skip />
+    <string name="locked_credential_entry_label_subtext_tap_to_unlock" msgid="6390367581393605009">"ለመክፈት መታ ያድርጉ"</string>
+    <string name="locked_credential_entry_label_subtext_no_sign_in" msgid="8131725029983174901">"ምንም የመግቢያ ማስረጃ የለም"</string>
+    <string name="no_sign_in_info_in" msgid="2641118151920288356">"<xliff:g id="SOURCE">%1$s</xliff:g> ውስጥ ምንም የመግቢያ መረጃ የለም"</string>
     <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"መግቢያዎችን ያስተዳድሩ"</string>
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"ከሌላ መሣሪያ"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"የተለየ መሣሪያ ይጠቀሙ"</string>
+    <string name="request_cancelled_by" msgid="3735222326886267820">"ጥያቄ በ<xliff:g id="APP_NAME">%1$s</xliff:g> ተሰርዟል"</string>
 </resources>
diff --git a/packages/CredentialManager/res/values-ar/strings.xml b/packages/CredentialManager/res/values-ar/strings.xml
index 8a88f3b..3f85b58 100644
--- a/packages/CredentialManager/res/values-ar/strings.xml
+++ b/packages/CredentialManager/res/values-ar/strings.xml
@@ -1,15 +1,29 @@
 <?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.
+   -->
+
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="4539824758261855508">"مدير بيانات الاعتماد"</string>
     <string name="string_cancel" msgid="6369133483981306063">"إلغاء"</string>
     <string name="string_continue" msgid="1346732695941131882">"متابعة"</string>
-    <string name="string_more_options" msgid="7990658711962795124">"خيارات إضافية"</string>
+    <string name="string_more_options" msgid="2763852250269945472">"الحفظ بطريقة أخرى"</string>
     <string name="string_learn_more" msgid="4541600451688392447">"مزيد من المعلومات"</string>
-    <!-- no translation found for content_description_show_password (3283502010388521607) -->
-    <skip />
-    <!-- no translation found for content_description_hide_password (6841375971631767996) -->
-    <skip />
+    <string name="content_description_show_password" msgid="3283502010388521607">"عرض كلمة المرور"</string>
+    <string name="content_description_hide_password" msgid="6841375971631767996">"إخفاء كلمة المرور"</string>
     <string name="passkey_creation_intro_title" msgid="4251037543787718844">"توفير المزيد من الأمان باستخدام مفاتيح المرور"</string>
     <string name="passkey_creation_intro_body_password" msgid="8825872426579958200">"باستخدام مفاتيح المرور، لا حاجة لإنشاء كلمات مرور معقدة أو تذكّرها."</string>
     <string name="passkey_creation_intro_body_fingerprint" msgid="7331338631826254055">"مفاتيح المرور هي مفاتيح رقمية مشفّرة يمكنك إنشاؤها باستخدام بصمة الإصبع أو التعرّف على الوجه أو قفل الشاشة."</string>
@@ -23,22 +37,25 @@
     <string name="improved_account_security_detail" msgid="9123750251551844860">"يرتبط كل مفتاح حصريًا بالتطبيق أو الموقع الإلكتروني الذي تم إنشاؤه من أجله، لذلك لا يمكن أبدًا أن تسجّل الدخول إلى تطبيق أو موقع إلكتروني احتيالي عن طريق الخطأ. بالإضافة إلى ذلك، تكون عملية الاختراق أكثر صعوبة لأن الخوادم تحتفظ بالمفاتيح العامة فقط."</string>
     <string name="seamless_transition_title" msgid="5335622196351371961">"النقل السلس"</string>
     <string name="seamless_transition_detail" msgid="4475509237171739843">"بينما ننطلق نحو مستقبل بدون كلمات مرور، ستظل كلمات المرور متوفّرة إلى جانب مفاتيح المرور."</string>
-    <string name="choose_provider_title" msgid="8870795677024868108">"اختَر الموقع الذي تريد حفظ <xliff:g id="CREATETYPES">%1$s</xliff:g> فيه"</string>
+    <string name="choose_provider_title" msgid="8870795677024868108">"اختيار المكان الذي تريد حفظ <xliff:g id="CREATETYPES">%1$s</xliff:g> فيه"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"اختَر مدير كلمات مرور لحفظ معلوماتك وتسجيل الدخول بشكل أسرع في المرة القادمة."</string>
-    <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"إنشاء مفتاح مرور لتطبيق \"<xliff:g id="APPNAME">%1$s</xliff:g>\""</string>
+    <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"هل تريد إنشاء مفتاح مرور لتطبيق \"<xliff:g id="APPNAME">%1$s</xliff:g>\"؟"</string>
     <string name="choose_create_option_password_title" msgid="7097275038523578687">"هل تريد حفظ كلمة المرور لتطبيق \"<xliff:g id="APPNAME">%1$s</xliff:g>\"؟"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"هل تريد حفظ معلومات تسجيل الدخول لتطبيق \"<xliff:g id="APPNAME">%1$s</xliff:g>\"؟"</string>
-    <string name="passkey" msgid="632353688396759522">"مفتاح مرور"</string>
+    <string name="passkey" msgid="632353688396759522">"مفتاح المرور"</string>
     <string name="password" msgid="6738570945182936667">"كلمة المرور"</string>
     <string name="passkeys" msgid="5733880786866559847">"مفاتيح المرور"</string>
     <string name="passwords" msgid="5419394230391253816">"كلمات المرور"</string>
     <string name="sign_ins" msgid="4710739369149469208">"عمليات تسجيل الدخول"</string>
     <string name="sign_in_info" msgid="2627704710674232328">"معلومات تسجيل الدخول"</string>
     <string name="save_credential_to_title" msgid="3172811692275634301">"حفظ <xliff:g id="CREDENTIALTYPES">%1$s</xliff:g> في"</string>
-    <string name="create_passkey_in_other_device_title" msgid="9195411122362461390">"هل تريد إنشاء مفتاح المرور في خدمة أخرى؟"</string>
+    <string name="create_passkey_in_other_device_title" msgid="2360053098931886245">"هل تريد إنشاء مفتاح مرور على جهاز آخر؟"</string>
+    <string name="save_password_on_other_device_title" msgid="5829084591948321207">"هل تريد حفظ كلمة المرور على جهاز آخر؟"</string>
+    <string name="save_sign_in_on_other_device_title" msgid="2827990118560134692">"هل تريد حفظ بيانات تسجيل الدخول على جهاز آخر؟"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"هل تريد استخدام \"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>\" لكل عمليات تسجيل الدخول؟"</string>
-    <string name="use_provider_for_all_description" msgid="8466427781848268490">"سيخزِّن مدير كلمات المرور هذا كلمات المرور ومفاتيح المرور لمساعدتك في تسجيل الدخول بسهولة."</string>
+    <string name="use_provider_for_all_description" msgid="1998772715863958997">"ستخزِّن خدمة \"مدير كلمات المرور\" هذه كلمات المرور ومفاتيح المرور للمستخدم <xliff:g id="USERNAME">%1$s</xliff:g> لمساعدتك في تسجيل الدخول بسهولة."</string>
     <string name="set_as_default" msgid="4415328591568654603">"ضبط الخيار كتلقائي"</string>
+    <string name="settings" msgid="6536394145760913145">"الإعدادات"</string>
     <string name="use_once" msgid="9027366575315399714">"الاستخدام مرة واحدة"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> كلمة مرور • <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> مفتاح مرور"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"عدد كلمات المرور: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>"</string>
@@ -49,21 +66,30 @@
     <string name="other_password_manager" msgid="565790221427004141">"خدمات مدراء كلمات المرور الأخرى"</string>
     <string name="close_sheet" msgid="1393792015338908262">"إغلاق ورقة البيانات"</string>
     <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"العودة إلى الصفحة السابقة"</string>
-    <string name="accessibility_close_button" msgid="2953807735590034688">"إغلاق اقتراح إجراء \"مدير بيانات الاعتماد\" الذي يظهر في أسفل الشاشة"</string>
+    <string name="accessibility_close_button" msgid="1163435587545377687">"إغلاق"</string>
+    <string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"إغلاق"</string>
     <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"هل تريد استخدام مفتاح المرور المحفوظ لتطبيق \"<xliff:g id="APP_NAME">%1$s</xliff:g>\"؟"</string>
-    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"هل تريد استخدام بيانات اعتماد تسجيل الدخول المحفوظة لتطبيق \"<xliff:g id="APP_NAME">%1$s</xliff:g>\"؟"</string>
-    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"اختيار بيانات اعتماد تسجيل دخول محفوظة لـ \"<xliff:g id="APP_NAME">%1$s</xliff:g>\""</string>
+    <string name="get_dialog_title_use_password_for" msgid="625828023234318484">"هل تريد استخدام كلمة المرور المحفوظة لتطبيق \"<xliff:g id="APP_NAME">%1$s</xliff:g>\"؟"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"هل تريد استخدام معلومات تسجيل دخولك لتطبيق \"<xliff:g id="APP_NAME">%1$s</xliff:g>\"؟"</string>
+    <string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"هل تريد فتح القفل لاستعادة خيارات تسجيل الدخول لتطبيق \"<xliff:g id="APP_NAME">%1$s</xliff:g>\"؟"</string>
+    <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"اختيار مفتاح مرور محفوظ لتطبيق \"<xliff:g id="APP_NAME">%1$s</xliff:g>\""</string>
+    <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"اختيار كلمة مرور محفوظة لتطبيق \"<xliff:g id="APP_NAME">%1$s</xliff:g>\""</string>
+    <string name="get_dialog_title_choose_saved_sign_in_for" msgid="2420298653461652728">"اختيار بيانات اعتماد تسجيل دخول محفوظة لتطبيق \"<xliff:g id="APP_NAME">%1$s</xliff:g>\""</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="3048870756117876514">"اختيار معلومات تسجيل الدخول لتطبيق \"<xliff:g id="APP_NAME">%1$s</xliff:g>\""</string>
+    <string name="get_dialog_title_choose_option_for" msgid="4976380044745029107">"هل تريد اختيار بيانات الاعتماد لتطبيق \"<xliff:g id="APP_NAME">%1$s</xliff:g>\"؟"</string>
+    <string name="get_dialog_title_use_info_on" msgid="8863708099535435146">"هل تريد استخدام بيانات الاعتماد هذه في \"<xliff:g id="APP_NAME">%1$s</xliff:g>\"؟"</string>
     <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"تسجيل الدخول بطريقة أخرى"</string>
     <string name="snackbar_action" msgid="37373514216505085">"عرض الخيارات"</string>
     <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"متابعة"</string>
     <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"خيارات تسجيل الدخول"</string>
+    <string name="button_label_view_more" msgid="3429098227286495651">"عرض المزيد"</string>
     <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"معلومات تسجيل دخول \"<xliff:g id="USERNAME">%1$s</xliff:g>\""</string>
     <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"خدمات إدارة كلمات المرور المقفولة"</string>
-    <!-- no translation found for locked_credential_entry_label_subtext_tap_to_unlock (6390367581393605009) -->
-    <skip />
-    <!-- no translation found for locked_credential_entry_label_subtext_no_sign_in (8131725029983174901) -->
-    <skip />
+    <string name="locked_credential_entry_label_subtext_tap_to_unlock" msgid="6390367581393605009">"انقر لفتح القفل."</string>
+    <string name="locked_credential_entry_label_subtext_no_sign_in" msgid="8131725029983174901">"ما مِن معلومات تسجيل دخول."</string>
+    <string name="no_sign_in_info_in" msgid="2641118151920288356">"ليس هناك معلومات تسجيل دخول في <xliff:g id="SOURCE">%1$s</xliff:g>."</string>
     <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"إداراة عمليات تسجيل الدخول"</string>
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"من جهاز آخر"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"استخدام جهاز مختلف"</string>
+    <string name="request_cancelled_by" msgid="3735222326886267820">"تم إلغاء الطلب بواسطة \"<xliff:g id="APP_NAME">%1$s</xliff:g>\"."</string>
 </resources>
diff --git a/packages/CredentialManager/res/values-as/strings.xml b/packages/CredentialManager/res/values-as/strings.xml
index 8040c7a..e14b34b 100644
--- a/packages/CredentialManager/res/values-as/strings.xml
+++ b/packages/CredentialManager/res/values-as/strings.xml
@@ -1,15 +1,29 @@
 <?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.
+   -->
+
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="4539824758261855508">"CredentialManager"</string>
     <string name="string_cancel" msgid="6369133483981306063">"বাতিল কৰক"</string>
     <string name="string_continue" msgid="1346732695941131882">"অব্যাহত ৰাখক"</string>
-    <string name="string_more_options" msgid="7990658711962795124">"অধিক বিকল্প"</string>
+    <string name="string_more_options" msgid="2763852250269945472">"অন্য ধৰণে ছেভ কৰক"</string>
     <string name="string_learn_more" msgid="4541600451688392447">"অধিক জানক"</string>
-    <!-- no translation found for content_description_show_password (3283502010388521607) -->
-    <skip />
-    <!-- no translation found for content_description_hide_password (6841375971631767996) -->
-    <skip />
+    <string name="content_description_show_password" msgid="3283502010388521607">"পাছৱৰ্ড দেখুৱাওক"</string>
+    <string name="content_description_hide_password" msgid="6841375971631767996">"পাছৱৰ্ড লুকুৱাওক"</string>
     <string name="passkey_creation_intro_title" msgid="4251037543787718844">"পাছকীৰ জৰিয়তে অধিক সুৰক্ষিত"</string>
     <string name="passkey_creation_intro_body_password" msgid="8825872426579958200">"পাছকী ব্যৱহাৰ কৰিলে আপুনি জটিল পাছৱৰ্ড সৃষ্টি কৰিব অথবা মনত ৰাখিব নালাগে"</string>
     <string name="passkey_creation_intro_body_fingerprint" msgid="7331338631826254055">"পাছকীসমূহ হৈছে আপুনি আপোনাৰ ফিংগাৰপ্ৰিণ্ট, মুখাৱয়ব অথবা স্ক্ৰীন লক ব্যৱহাৰ কৰি সৃষ্টি কৰা এনক্ৰিপ্ট কৰা ডিজিটেল চাবি"</string>
@@ -18,7 +32,7 @@
     <string name="passwordless_technology_title" msgid="2497513482056606668">"পাছৱৰ্ডবিহীন প্ৰযুক্তি"</string>
     <string name="passwordless_technology_detail" msgid="6853928846532955882">"পাছকীসমূহে আপোনাক পাছৱৰ্ডৰ ওপৰত নিৰ্ভৰ নকৰাকৈ ছাইন ইন কৰাৰ অনুমতি দিয়ে। আপুনি আপোনাৰ পৰিচয় সত্যাপন কৰিবলৈ আৰু এটা পাছকী সৃষ্টি কৰিবলৈ কেৱল আপোনাৰ ফিংগাৰপ্ৰিণ্ট, মুখাৱয়ব চিনাক্তকৰণ, পিন অথবা ছোৱাইপ কৰাৰ আৰ্হি ব্যৱহাৰ কৰিলেই হ’ল।"</string>
     <string name="public_key_cryptography_title" msgid="6751970819265298039">"ৰাজহুৱা চাবি ক্ৰিপ্ট’গ্ৰাফী"</string>
-    <string name="public_key_cryptography_detail" msgid="6937631710280562213">"FIDO জোঁট (য’ত Google, Apple, Microsoft আৰু অধিক অন্তৰ্ভুক্ত) আৰু W3C মানকসমূহৰ ওপৰত ভিত্তি কৰি, পাছকীসমূহে ক্ৰিপ্ট’গ্ৰাফিক চাবিৰ যোৰা ব্যৱহাৰ কৰে। আমি পাছৱৰ্ডৰ বাবে ব্যৱহাৰ কৰা ব্যৱহাৰকাৰীৰ নাম আৰু বৰ্ণৰ ষ্ট্ৰিঙৰ বিপৰীতে, এটা এপ্‌ অথবা ৱেবছাইটৰ বাবে এটা ব্যক্তিগত-ৰাজহুৱা কীৰ যোৰা সৃষ্টি কৰা হয়। ব্যক্তিগত চাবিটো আপোনাৰ ডিভাইচ অথবা পাছৱৰ্ড পৰিচালকত সুৰক্ষিতভাৱে ষ্ট’ৰ কৰা হয় আৰু ই আপোনাৰ পৰিচয় নিশ্চিত কৰে। ৰাজহুৱা চাবিটো এপ্‌ অথবা ৱেবছাইটৰ ছাৰ্ভাৰৰ সৈতে শ্বেয়াৰ কৰা হয়। সংশ্লিষ্ট চাবিসমূহৰ জৰিয়তে আপুনি তাৎক্ষণিকভাৱে পঞ্জীয়ন আৰু ছাইন ইন কৰিব পাৰিব।"</string>
+    <string name="public_key_cryptography_detail" msgid="6937631710280562213">"FIDO জোঁট (য’ত Google, Apple, Microsoft আৰু অধিক অন্তৰ্ভুক্ত) আৰু W3C মানকসমূহৰ ওপৰত ভিত্তি কৰি, পাছকীসমূহে ক্ৰিপ্ট’গ্ৰাফিক চাবিৰ যোৰা ব্যৱহাৰ কৰে। আমি পাছৱৰ্ডৰ বাবে ব্যৱহাৰ কৰা ব্যৱহাৰকাৰীৰ নাম আৰু বৰ্ণৰ ষ্ট্ৰিঙৰ বিপৰীতে, এটা এপ্‌ অথবা ৱেবছাইটৰ বাবে এটা ব্যক্তিগত-ৰাজহুৱা চাবিৰ যোৰা সৃষ্টি কৰা হয়। ব্যক্তিগত চাবিটো আপোনাৰ ডিভাইচ অথবা পাছৱৰ্ড পৰিচালকত সুৰক্ষিতভাৱে ষ্ট’ৰ কৰা হয় আৰু ই আপোনাৰ পৰিচয় নিশ্চিত কৰে। ৰাজহুৱা চাবিটো এপ্‌ অথবা ৱেবছাইটৰ ছাৰ্ভাৰৰ সৈতে শ্বেয়াৰ কৰা হয়। সংশ্লিষ্ট চাবিসমূহৰ জৰিয়তে আপুনি তাৎক্ষণিকভাৱে পঞ্জীয়ন আৰু ছাইন ইন কৰিব পাৰিব।"</string>
     <string name="improved_account_security_title" msgid="1069841917893513424">"একাউণ্টৰ উন্নত সুৰক্ষা"</string>
     <string name="improved_account_security_detail" msgid="9123750251551844860">"প্ৰতিটো চাবি বিশেষভাৱে সেই এপ্‌ অথবা ৱেবছাইটৰ সৈতে লিংক কৰা হয় যাৰ বাবে সেইটো সৃষ্টি কৰা হৈছে, সেয়ে আপুনি কেতিয়াও ভুলতে কোনো প্ৰৱঞ্চনামূলক এপ্‌ অথবা ৱেবছাইটত ছাইন ইন কৰিব নোৱাৰে। ইয়াৰ উপৰিও, কেৱল ৰাজহুৱা চাবিসমূহ ৰখা ছাৰ্ভাৰৰ ক্ষেত্ৰত হেক কৰাটো বহুত কঠিন হৈ পৰে।"</string>
     <string name="seamless_transition_title" msgid="5335622196351371961">"বাধাহীন স্থানান্তৰণ"</string>
@@ -35,10 +49,13 @@
     <string name="sign_ins" msgid="4710739369149469208">"ছাইন-ইন"</string>
     <string name="sign_in_info" msgid="2627704710674232328">"ছাইন ইনৰ তথ্য"</string>
     <string name="save_credential_to_title" msgid="3172811692275634301">"<xliff:g id="CREDENTIALTYPES">%1$s</xliff:g> ইয়াত ছেভ কৰক"</string>
-    <string name="create_passkey_in_other_device_title" msgid="9195411122362461390">"অন্য এটা ডিভাইচত পাছকী সৃষ্টি কৰিবনে?"</string>
+    <string name="create_passkey_in_other_device_title" msgid="2360053098931886245">"অন্য কোনো ডিভাইচত পাছকী সৃষ্টি কৰিব নেকি?"</string>
+    <string name="save_password_on_other_device_title" msgid="5829084591948321207">"অন্য কোনো ডিভাইচত পাছৱৰ্ড ছেভ কৰিব নেকি?"</string>
+    <string name="save_sign_in_on_other_device_title" msgid="2827990118560134692">"অন্য কোনো ডিভাইচত ছাইন-ইন ছেভ কৰিব নেকি?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"আপোনাৰ আটাইবোৰ ছাইন ইনৰ বাবে <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ব্যৱহাৰ কৰিবনে?"</string>
-    <string name="use_provider_for_all_description" msgid="8466427781848268490">"আপোনাক সহজে ছাইন ইন কৰাত সহায় কৰিবলৈ এই পাছৱৰ্ড পৰিচালকটোৱে আপোনাৰ পাছৱৰ্ড আৰু পাছকী ষ্ট’ৰ কৰিব"</string>
+    <string name="use_provider_for_all_description" msgid="1998772715863958997">"আপোনাক সহজে ছাইন ইন কৰাত সহায় কৰিবলৈ <xliff:g id="USERNAME">%1$s</xliff:g>ৰ বাবে থকা এই পাছৱৰ্ড পৰিচালকে আপোনাৰ পাছৱৰ্ড আৰু পাছকী ষ্ট’ৰ কৰিব"</string>
     <string name="set_as_default" msgid="4415328591568654603">"ডিফ’ল্ট হিচাপে ছেট কৰক"</string>
+    <string name="settings" msgid="6536394145760913145">"ছেটিং"</string>
     <string name="use_once" msgid="9027366575315399714">"এবাৰ ব্যৱহাৰ কৰক"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> টা পাছৱৰ্ড • <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> টা পাছকী"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> টা পাছৱৰ্ড"</string>
@@ -49,21 +66,30 @@
     <string name="other_password_manager" msgid="565790221427004141">"অন্য পাছৱৰ্ড পৰিচালক"</string>
     <string name="close_sheet" msgid="1393792015338908262">"শ্বীট বন্ধ কৰক"</string>
     <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"পূৰ্বৱৰ্তী পৃষ্ঠালৈ ঘূৰি যাওক"</string>
-    <string name="accessibility_close_button" msgid="2953807735590034688">"স্ক্ৰীনখনৰ একেবাৰে তলত প্ৰদৰ্শিত ক্ৰিডেনশ্বিয়েল পৰিচালকৰ কাৰ্য সম্পৰ্কীয় পৰামৰ্শ বন্ধ কৰক"</string>
+    <string name="accessibility_close_button" msgid="1163435587545377687">"বন্ধ কৰক"</string>
+    <string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"অগ্ৰাহ্য কৰক"</string>
     <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"<xliff:g id="APP_NAME">%1$s</xliff:g>ৰ বাবে আপোনাৰ ছেভ হৈ থকা পাছকী ব্যৱহাৰ কৰিবনে?"</string>
-    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"<xliff:g id="APP_NAME">%1$s</xliff:g>ৰ বাবে আপোনাৰ ছেভ হৈ থকা ছাইন ইন তথ্য ব্যৱহাৰ কৰিবনে?"</string>
-    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"<xliff:g id="APP_NAME">%1$s</xliff:g>ৰ বাবে ছেভ হৈ থকা এটা ছাইন ইন বাছনি কৰক"</string>
+    <string name="get_dialog_title_use_password_for" msgid="625828023234318484">"<xliff:g id="APP_NAME">%1$s</xliff:g>ৰ বাবে আপোনাৰ ছেভ কৰি থোৱা পাছৱৰ্ড ব্যৱহাৰ কৰিবনে?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"<xliff:g id="APP_NAME">%1$s</xliff:g>ৰ বাবে ছাইন ইন কৰিবলৈ আপোনাৰ ক্ৰিডেনশ্বিয়েল ব্যৱহাৰ কৰিবনে?"</string>
+    <string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"<xliff:g id="APP_NAME">%1$s</xliff:g>ৰ বাবে ছাইন ইনৰ বিকল্পসমূহ আনলক কৰিবনে?"</string>
+    <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"<xliff:g id="APP_NAME">%1$s</xliff:g>ৰ বাবে ছেভ হৈ থকা এটা পাছকী বাছনি কৰক"</string>
+    <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"<xliff:g id="APP_NAME">%1$s</xliff:g>ৰ বাবে ছেভ হৈ থকা এটা পাছৱৰ্ড বাছনি কৰক"</string>
+    <string name="get_dialog_title_choose_saved_sign_in_for" msgid="2420298653461652728">"<xliff:g id="APP_NAME">%1$s</xliff:g>ৰ বাবে ছেভ হৈ থকা এটা ছাইন ইন বাছনি কৰক"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="3048870756117876514">"<xliff:g id="APP_NAME">%1$s</xliff:g>ৰ বাবে ছাইন ইন কৰিবলৈ এটা ক্ৰিডেনশ্বিয়েল বাছনি কৰক"</string>
+    <string name="get_dialog_title_choose_option_for" msgid="4976380044745029107">"<xliff:g id="APP_NAME">%1$s</xliff:g>ৰ বাবে এটা বিকল্প বাছনি কৰিবনে?"</string>
+    <string name="get_dialog_title_use_info_on" msgid="8863708099535435146">"<xliff:g id="APP_NAME">%1$s</xliff:g>ত এই তথ্য ব্যৱহাৰ কৰিবনে?"</string>
     <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"অন্য উপায়েৰে ছাইন ইন কৰক"</string>
     <string name="snackbar_action" msgid="37373514216505085">"বিকল্পসমূহ চাওক"</string>
     <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"অব্যাহত ৰাখক"</string>
     <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"ছাইন ইনৰ বিকল্প"</string>
+    <string name="button_label_view_more" msgid="3429098227286495651">"অধিক চাওক"</string>
     <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"<xliff:g id="USERNAME">%1$s</xliff:g>ৰ বাবে"</string>
     <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"লক হৈ থকা পাছৱৰ্ড পৰিচালক"</string>
-    <!-- no translation found for locked_credential_entry_label_subtext_tap_to_unlock (6390367581393605009) -->
-    <skip />
-    <!-- no translation found for locked_credential_entry_label_subtext_no_sign_in (8131725029983174901) -->
-    <skip />
+    <string name="locked_credential_entry_label_subtext_tap_to_unlock" msgid="6390367581393605009">"আনলক কৰিবলৈ টিপক"</string>
+    <string name="locked_credential_entry_label_subtext_no_sign_in" msgid="8131725029983174901">"ছাইন ইনৰ তথ্য নাই"</string>
+    <string name="no_sign_in_info_in" msgid="2641118151920288356">"<xliff:g id="SOURCE">%1$s</xliff:g>ত কোনো ছাইন ইনৰ তথ্য নাই"</string>
     <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"ছাইন ইন পৰিচালনা কৰক"</string>
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"অন্য এটা ডিভাইচৰ পৰা"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"অন্য এটা ডিভাইচ ব্যৱহাৰ কৰক"</string>
+    <string name="request_cancelled_by" msgid="3735222326886267820">"<xliff:g id="APP_NAME">%1$s</xliff:g>এ অনুৰোধটো বাতিল কৰিছে"</string>
 </resources>
diff --git a/packages/CredentialManager/res/values-az/strings.xml b/packages/CredentialManager/res/values-az/strings.xml
index dd1db6f..d0f8bb0 100644
--- a/packages/CredentialManager/res/values-az/strings.xml
+++ b/packages/CredentialManager/res/values-az/strings.xml
@@ -1,69 +1,95 @@
 <?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.
+   -->
+
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="4539824758261855508">"Giriş Məlumatları Meneceri"</string>
     <string name="string_cancel" msgid="6369133483981306063">"Ləğv edin"</string>
     <string name="string_continue" msgid="1346732695941131882">"Davam edin"</string>
-    <string name="string_more_options" msgid="7990658711962795124">"Digər seçimlər"</string>
+    <string name="string_more_options" msgid="2763852250269945472">"Başqa cür saxlayın"</string>
     <string name="string_learn_more" msgid="4541600451688392447">"Ətraflı məlumat"</string>
-    <!-- no translation found for content_description_show_password (3283502010388521607) -->
-    <skip />
-    <!-- no translation found for content_description_hide_password (6841375971631767996) -->
-    <skip />
-    <string name="passkey_creation_intro_title" msgid="4251037543787718844">"Giriş açarları ilə daha təhlükəsiz"</string>
-    <string name="passkey_creation_intro_body_password" msgid="8825872426579958200">"Giriş açarları ilə mürəkkəb parollar yaratmağa və ya yadda saxlamağa ehtiyac yoxdur"</string>
+    <string name="content_description_show_password" msgid="3283502010388521607">"Parolu göstərin"</string>
+    <string name="content_description_hide_password" msgid="6841375971631767996">"Parolu gizlədin"</string>
+    <string name="passkey_creation_intro_title" msgid="4251037543787718844">"Açarlar ilə daha təhlükəsiz"</string>
+    <string name="passkey_creation_intro_body_password" msgid="8825872426579958200">"Açarlar ilə mürəkkəb parollar yaratmağa və ya yadda saxlamağa ehtiyac yoxdur"</string>
     <string name="passkey_creation_intro_body_fingerprint" msgid="7331338631826254055">"Giriş açarları barmaq izi, üz və ya ekran kilidindən istifadə edərək yaratdığınız şifrələnmiş rəqəmsal açarlardır"</string>
     <string name="passkey_creation_intro_body_device" msgid="1203796455762131631">"Onlar parol menecerində saxlanılır ki, digər cihazlarda daxil ola biləsiniz"</string>
-    <string name="more_about_passkeys_title" msgid="7797903098728837795">"Giriş açarları haqqında ətraflı"</string>
+    <string name="more_about_passkeys_title" msgid="7797903098728837795">"Açarlar haqqında ətraflı"</string>
     <string name="passwordless_technology_title" msgid="2497513482056606668">"Parolsuz texnologiya"</string>
-    <string name="passwordless_technology_detail" msgid="6853928846532955882">"Giriş açarları parollara etibar etmədən daxil olmağa imkan verir. Kimliyinizi doğrulamaq və giriş açarı yaratmaq üçün sadəcə barmaq izi, üz tanıma, PIN və ya sürüşdürmə modelindən istifadə etməlisiniz."</string>
+    <string name="passwordless_technology_detail" msgid="6853928846532955882">"Giriş açarları parollara etibar etmədən daxil olmağa imkan verir. Kimliyinizi doğrulamaq və açar yaratmaq üçün sadəcə barmaq izi, üz tanıma, PIN və ya sürüşdürmə modelindən istifadə etməlisiniz."</string>
     <string name="public_key_cryptography_title" msgid="6751970819265298039">"İctimai açar kriptoqrafiyası"</string>
-    <string name="public_key_cryptography_detail" msgid="6937631710280562213">"FIDO Alliance (Google, Apple, Microsoft və s. daxildir) və W3C standartlarına əsaslanaraq giriş açarları kriptoqrafik açar cütlərindən istifadə edir. İstifadəçi adı və parollar üçün istifadə etdiyimiz simvol sətrindən fərqli olaraq, tətbiq və ya vebsayt üçün şəxsi-ictimai açar cütü yaradılır. Şəxsi açar cihazınızda və ya parol menecerinizdə təhlükəsiz şəkildə saxlanılır və kimliyinizi təsdiq edir. İctimai açar tətbiq və ya vebsayt serveri ilə paylaşılır. Müvafiq açarlarla dərhal qeydiyyatdan keçə və daxil ola bilərsiniz."</string>
+    <string name="public_key_cryptography_detail" msgid="6937631710280562213">"FIDO Alliance (Google, Apple, Microsoft və s. daxildir) və W3C standartlarına əsaslanaraq açarlar kriptoqrafik açar cütlərindən istifadə edir. İstifadəçi adı və parollar üçün istifadə etdiyimiz simvol sətrindən fərqli olaraq, tətbiq və ya vebsayt üçün şəxsi-ictimai açar cütü yaradılır. Şəxsi açar cihazınızda və ya parol menecerinizdə təhlükəsiz şəkildə saxlanılır və kimliyinizi təsdiq edir. İctimai açar tətbiq və ya vebsayt serveri ilə paylaşılır. Müvafiq açarlarla dərhal qeydiyyatdan keçə və daxil ola bilərsiniz."</string>
     <string name="improved_account_security_title" msgid="1069841917893513424">"Təkmilləşdirilmiş hesab təhlükəsizliyi"</string>
     <string name="improved_account_security_detail" msgid="9123750251551844860">"Hər bir açar eksklüziv olaraq onların yaradıldığı tətbiq və ya vebsaytla əlaqələndirilib, ona görə də heç vaxt səhvən saxta tətbiqə və ya vebsayta daxil ola bilməzsiniz. Üstəlik, yalnız ictimai açarları saxlayan serverlərlə hekinq daha çətindir."</string>
     <string name="seamless_transition_title" msgid="5335622196351371961">"Rahat keçid"</string>
-    <string name="seamless_transition_detail" msgid="4475509237171739843">"Parolsuz gələcəyə doğru irəlilədikcə parollar hələ də giriş açarları ilə yanaşı əlçatan olacaq."</string>
+    <string name="seamless_transition_detail" msgid="4475509237171739843">"Parolsuz gələcəyə doğru irəlilədikcə parollar hələ də açarlar ilə yanaşı əlçatan olacaq."</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"<xliff:g id="CREATETYPES">%1$s</xliff:g> elementinin saxlanacağı yeri seçin"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"Məlumatlarınızı yadda saxlamaq və növbəti dəfə daha sürətli daxil olmaq üçün parol meneceri seçin"</string>
     <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"<xliff:g id="APPNAME">%1$s</xliff:g> üçün giriş açarı yaradılsın?"</string>
     <string name="choose_create_option_password_title" msgid="7097275038523578687">"<xliff:g id="APPNAME">%1$s</xliff:g> üçün parol yadda saxlanılsın?"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"<xliff:g id="APPNAME">%1$s</xliff:g> üçün giriş məlumatları yadda saxlansın?"</string>
-    <string name="passkey" msgid="632353688396759522">"giriş açarı"</string>
+    <string name="passkey" msgid="632353688396759522">"açar"</string>
     <string name="password" msgid="6738570945182936667">"parol"</string>
-    <string name="passkeys" msgid="5733880786866559847">"giriş açarları"</string>
+    <string name="passkeys" msgid="5733880786866559847">"açarlar"</string>
     <string name="passwords" msgid="5419394230391253816">"parollar"</string>
     <string name="sign_ins" msgid="4710739369149469208">"girişlər"</string>
     <string name="sign_in_info" msgid="2627704710674232328">"Giriş məlumatları"</string>
-    <string name="save_credential_to_title" msgid="3172811692275634301">"<xliff:g id="CREDENTIALTYPES">%1$s</xliff:g> burada yadda saxlansın:"</string>
-    <string name="create_passkey_in_other_device_title" msgid="9195411122362461390">"Başqa cihazda giriş açarı yaradılsın?"</string>
+    <string name="save_credential_to_title" msgid="3172811692275634301">"<xliff:g id="CREDENTIALTYPES">%1$s</xliff:g> harada saxlanılsın?"</string>
+    <string name="create_passkey_in_other_device_title" msgid="2360053098931886245">"Giriş açarı başqa cihazda yaradılsın?"</string>
+    <string name="save_password_on_other_device_title" msgid="5829084591948321207">"Parol başqa cihazda yadda saxlansın?"</string>
+    <string name="save_sign_in_on_other_device_title" msgid="2827990118560134692">"Giriş başqa cihazda yadda saxlansın?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"Bütün girişlər üçün <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> istifadə edilsin?"</string>
-    <string name="use_provider_for_all_description" msgid="8466427781848268490">"Bu parol meneceri asanlıqla daxil olmanıza kömək etmək üçün parollarınızı və giriş açarlarınızı saxlayacaq"</string>
+    <string name="use_provider_for_all_description" msgid="1998772715863958997">"<xliff:g id="USERNAME">%1$s</xliff:g> üçün bu parol meneceri asanlıqla daxil olmağınız məqsədilə parol və açarları saxlayacaq"</string>
     <string name="set_as_default" msgid="4415328591568654603">"Defolt olaraq seçin"</string>
+    <string name="settings" msgid="6536394145760913145">"Ayarlar"</string>
     <string name="use_once" msgid="9027366575315399714">"Bir dəfə istifadə edin"</string>
-    <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> parol • <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> giriş açarı"</string>
+    <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> parol • <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> açar"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> parol"</string>
-    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> giriş açarı"</string>
+    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> açarları"</string>
     <string name="more_options_usage_credentials" msgid="1785697001787193984">"<xliff:g id="TOTALCREDENTIALSNUMBER">%1$s</xliff:g> giriş məlumatları"</string>
     <string name="passkey_before_subtitle" msgid="2448119456208647444">"Giriş açarı"</string>
     <string name="another_device" msgid="5147276802037801217">"Digər cihaz"</string>
     <string name="other_password_manager" msgid="565790221427004141">"Digər parol menecerləri"</string>
     <string name="close_sheet" msgid="1393792015338908262">"Səhifəni bağlayın"</string>
     <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Əvvəlki səhifəyə qayıdın"</string>
-    <string name="accessibility_close_button" msgid="2953807735590034688">"Ekranın aşağı hissəsində görünən Giriş Məlumatları Meneceri əməliyyat təklifini bağlayın"</string>
+    <string name="accessibility_close_button" msgid="1163435587545377687">"Bağlayın"</string>
+    <string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"İmtina edin"</string>
     <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"<xliff:g id="APP_NAME">%1$s</xliff:g> üçün yadda saxlanmış giriş açarı istifadə edilsin?"</string>
-    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"<xliff:g id="APP_NAME">%1$s</xliff:g> üçün yadda saxlanmış girişdən istifadə edilsin?"</string>
-    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"<xliff:g id="APP_NAME">%1$s</xliff:g> üçün yadda saxlanmış girişi seçin"</string>
+    <string name="get_dialog_title_use_password_for" msgid="625828023234318484">"<xliff:g id="APP_NAME">%1$s</xliff:g> üçün yadda saxlanmış parol istifadə edilsin?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"<xliff:g id="APP_NAME">%1$s</xliff:g> üçün giriş istifadə edilsin?"</string>
+    <string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"<xliff:g id="APP_NAME">%1$s</xliff:g> üçün giriş seçimləri kiliddən çıxarılsın?"</string>
+    <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"<xliff:g id="APP_NAME">%1$s</xliff:g> üçün yadda saxlanmış giriş açarı seçin"</string>
+    <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"<xliff:g id="APP_NAME">%1$s</xliff:g> üçün yadda saxlanmış parol seçin"</string>
+    <string name="get_dialog_title_choose_saved_sign_in_for" msgid="2420298653461652728">"<xliff:g id="APP_NAME">%1$s</xliff:g> üçün yadda saxlanmış giriş seçin"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="3048870756117876514">"<xliff:g id="APP_NAME">%1$s</xliff:g> üçün giriş seçin"</string>
+    <string name="get_dialog_title_choose_option_for" msgid="4976380044745029107">"<xliff:g id="APP_NAME">%1$s</xliff:g> üçün seçim edilsin?"</string>
+    <string name="get_dialog_title_use_info_on" msgid="8863708099535435146">"Məlumat <xliff:g id="APP_NAME">%1$s</xliff:g> tətbiqində istifadə edilsin?"</string>
     <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Başqa üsulla daxil olun"</string>
     <string name="snackbar_action" msgid="37373514216505085">"Seçimlərə baxın"</string>
     <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Davam edin"</string>
     <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Giriş seçimləri"</string>
+    <string name="button_label_view_more" msgid="3429098227286495651">"Davamına baxın"</string>
     <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"<xliff:g id="USERNAME">%1$s</xliff:g> üçün"</string>
     <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Kilidli parol menecerləri"</string>
-    <!-- no translation found for locked_credential_entry_label_subtext_tap_to_unlock (6390367581393605009) -->
-    <skip />
-    <!-- no translation found for locked_credential_entry_label_subtext_no_sign_in (8131725029983174901) -->
-    <skip />
+    <string name="locked_credential_entry_label_subtext_tap_to_unlock" msgid="6390367581393605009">"Kiliddən çıxarmaq üçün toxunun"</string>
+    <string name="locked_credential_entry_label_subtext_no_sign_in" msgid="8131725029983174901">"Giriş məlumatı yoxdur"</string>
+    <string name="no_sign_in_info_in" msgid="2641118151920288356">"<xliff:g id="SOURCE">%1$s</xliff:g> giriş məlumatı mövcud deyil"</string>
     <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Girişləri idarə edin"</string>
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Başqa cihazdan"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Başqa cihaz istifadə edin"</string>
+    <string name="request_cancelled_by" msgid="3735222326886267820">"<xliff:g id="APP_NAME">%1$s</xliff:g> sorğunu ləğv etdi"</string>
 </resources>
diff --git a/packages/CredentialManager/res/values-b+sr+Latn/strings.xml b/packages/CredentialManager/res/values-b+sr+Latn/strings.xml
index 10f80fe..89e48f9 100644
--- a/packages/CredentialManager/res/values-b+sr+Latn/strings.xml
+++ b/packages/CredentialManager/res/values-b+sr+Latn/strings.xml
@@ -1,15 +1,29 @@
 <?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.
+   -->
+
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="4539824758261855508">"Menadžer akreditiva"</string>
     <string name="string_cancel" msgid="6369133483981306063">"Otkaži"</string>
     <string name="string_continue" msgid="1346732695941131882">"Nastavi"</string>
-    <string name="string_more_options" msgid="7990658711962795124">"Još opcija"</string>
+    <string name="string_more_options" msgid="2763852250269945472">"Sačuvaj drugi način"</string>
     <string name="string_learn_more" msgid="4541600451688392447">"Saznajte više"</string>
-    <!-- no translation found for content_description_show_password (3283502010388521607) -->
-    <skip />
-    <!-- no translation found for content_description_hide_password (6841375971631767996) -->
-    <skip />
+    <string name="content_description_show_password" msgid="3283502010388521607">"Prikažite lozinku"</string>
+    <string name="content_description_hide_password" msgid="6841375971631767996">"Sakrijte lozinku"</string>
     <string name="passkey_creation_intro_title" msgid="4251037543787718844">"Bezbednije uz pristupne kodove"</string>
     <string name="passkey_creation_intro_body_password" msgid="8825872426579958200">"Uz pristupne kodove nema potrebe da pravite ili pamtite složene lozinke"</string>
     <string name="passkey_creation_intro_body_fingerprint" msgid="7331338631826254055">"Pristupni kodovi su šifrovani digitalni kodovi koje pravite pomoću otiska prsta, lica ili zaključavanja ekrana"</string>
@@ -23,7 +37,7 @@
     <string name="improved_account_security_detail" msgid="9123750251551844860">"Svaki ključ je isključivo povezan sa aplikacijom ili veb-sajtom za koje je napravljen, pa nikad ne možete greškom da se prijavite u aplikaciju ili na veb-sajt koji služe za prevaru. Osim toga, sa serverima koji čuvaju samo javne ključeve hakovanje je mnogo teže."</string>
     <string name="seamless_transition_title" msgid="5335622196351371961">"Besprekoran prelaz"</string>
     <string name="seamless_transition_detail" msgid="4475509237171739843">"Kako se krećemo ka budućnosti bez lozinki, lozinke će i dalje biti dostupne uz pristupne kodove."</string>
-    <string name="choose_provider_title" msgid="8870795677024868108">"Odaberite gde ćete sačuvati stavke <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <string name="choose_provider_title" msgid="8870795677024868108">"Odaberite gde ćete sačuvati: <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"Izaberite menadžera lozinki da biste sačuvali podatke i brže se prijavili sledeći put"</string>
     <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"Želite da napravite pristupni kôd za: <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_password_title" msgid="7097275038523578687">"Želite da sačuvate lozinku za: <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
@@ -34,11 +48,14 @@
     <string name="passwords" msgid="5419394230391253816">"lozinke"</string>
     <string name="sign_ins" msgid="4710739369149469208">"prijavljivanja"</string>
     <string name="sign_in_info" msgid="2627704710674232328">"podaci za prijavljivanje"</string>
-    <string name="save_credential_to_title" msgid="3172811692275634301">"Sačuvajte stavku<xliff:g id="CREDENTIALTYPES">%1$s</xliff:g> u"</string>
-    <string name="create_passkey_in_other_device_title" msgid="9195411122362461390">"Želite da napravite pristupni kôd na drugom uređaju?"</string>
+    <string name="save_credential_to_title" msgid="3172811692275634301">"Sačuvaj <xliff:g id="CREDENTIALTYPES">%1$s</xliff:g> u"</string>
+    <string name="create_passkey_in_other_device_title" msgid="2360053098931886245">"Želite da napravite pristupni kôd na drugom uređaju?"</string>
+    <string name="save_password_on_other_device_title" msgid="5829084591948321207">"Želite da sačuvate lozinku na drugom uređaju?"</string>
+    <string name="save_sign_in_on_other_device_title" msgid="2827990118560134692">"Želite da sačuvate akreditive za prijavu na drugom uređaju?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"Želite da za sva prijavljivanja koristite: <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
-    <string name="use_provider_for_all_description" msgid="8466427781848268490">"Ovaj menadžer lozinki će čuvati lozinke i pristupne kodove da biste se lako prijavljivali"</string>
+    <string name="use_provider_for_all_description" msgid="1998772715863958997">"Ovaj menadžer lozinki za <xliff:g id="USERNAME">%1$s</xliff:g> će čuvati lozinke i pristupne kodove da biste se lako prijavljivali"</string>
     <string name="set_as_default" msgid="4415328591568654603">"Podesi kao podrazumevano"</string>
+    <string name="settings" msgid="6536394145760913145">"Podešavanja"</string>
     <string name="use_once" msgid="9027366575315399714">"Koristi jednom"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"Lozinki: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> • Pristupnih kodova:<xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"Lozinki: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>"</string>
@@ -49,21 +66,30 @@
     <string name="other_password_manager" msgid="565790221427004141">"Drugi menadžeri lozinki"</string>
     <string name="close_sheet" msgid="1393792015338908262">"Zatvorite tabelu"</string>
     <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Vratite se na prethodnu stranicu"</string>
-    <string name="accessibility_close_button" msgid="2953807735590034688">"Zatvorite predlog za radnju Menadžera akreditiva koji se prikazuje u dnu ekrana"</string>
+    <string name="accessibility_close_button" msgid="1163435587545377687">"Zatvorite"</string>
+    <string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"Odbaci"</string>
     <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Želite da koristite sačuvani pristupni kôd za: <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
-    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Želite da koristite sačuvane podatke za prijavljivanje za: <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
-    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Odaberite sačuvano prijavljivanje za: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_use_password_for" msgid="625828023234318484">"Želite da koristite sačuvanu lozinku za: <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"Želite li da koristite svoje podatke za prijavljivanje za aplikaciju <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"Želite da otključate opcije prijavljivanja za: <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"Izaberite sačuvan pristupni kôd za: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"Izaberite sačuvanu lozinku za: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_saved_sign_in_for" msgid="2420298653461652728">"Izaberite sačuvane podatke za prijavljivanje za: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="3048870756117876514">"Odaberite podatke za prijavljivanje za aplikaciju <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_option_for" msgid="4976380044745029107">"Želite da odaberete opciju za aplikaciju <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_info_on" msgid="8863708099535435146">"Želite da koristite te podatke u aplikaciji <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Prijavite se na drugi način"</string>
     <string name="snackbar_action" msgid="37373514216505085">"Prikaži opcije"</string>
     <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Nastavi"</string>
     <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Opcije za prijavljivanje"</string>
+    <string name="button_label_view_more" msgid="3429098227286495651">"Prikaži još"</string>
     <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Za: <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Menadžeri zaključanih lozinki"</string>
-    <!-- no translation found for locked_credential_entry_label_subtext_tap_to_unlock (6390367581393605009) -->
-    <skip />
-    <!-- no translation found for locked_credential_entry_label_subtext_no_sign_in (8131725029983174901) -->
-    <skip />
+    <string name="locked_credential_entry_label_subtext_tap_to_unlock" msgid="6390367581393605009">"Dodirnite da biste otključali"</string>
+    <string name="locked_credential_entry_label_subtext_no_sign_in" msgid="8131725029983174901">"Nema podataka za prijavljivanje"</string>
+    <string name="no_sign_in_info_in" msgid="2641118151920288356">"Nema podataka za prijavljivanje u: <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Upravljajte prijavljivanjima"</string>
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Sa drugog uređaja"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Koristi drugi uređaj"</string>
+    <string name="request_cancelled_by" msgid="3735222326886267820">"Zahtve je otkazala aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
 </resources>
diff --git a/packages/CredentialManager/res/values-be/strings.xml b/packages/CredentialManager/res/values-be/strings.xml
index 39c8200..144bb86 100644
--- a/packages/CredentialManager/res/values-be/strings.xml
+++ b/packages/CredentialManager/res/values-be/strings.xml
@@ -1,15 +1,29 @@
 <?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.
+   -->
+
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="4539824758261855508">"Менеджар уліковых даных"</string>
     <string name="string_cancel" msgid="6369133483981306063">"Скасаваць"</string>
     <string name="string_continue" msgid="1346732695941131882">"Далей"</string>
-    <string name="string_more_options" msgid="7990658711962795124">"Дадатковыя параметры"</string>
+    <string name="string_more_options" msgid="2763852250269945472">"Захаваць іншы спосаб"</string>
     <string name="string_learn_more" msgid="4541600451688392447">"Даведацца больш"</string>
-    <!-- no translation found for content_description_show_password (3283502010388521607) -->
-    <skip />
-    <!-- no translation found for content_description_hide_password (6841375971631767996) -->
-    <skip />
+    <string name="content_description_show_password" msgid="3283502010388521607">"Паказаць пароль"</string>
+    <string name="content_description_hide_password" msgid="6841375971631767996">"Схаваць пароль"</string>
     <string name="passkey_creation_intro_title" msgid="4251037543787718844">"З ключамі доступу вам будзе бяспечней."</string>
     <string name="passkey_creation_intro_body_password" msgid="8825872426579958200">"Дзякуючы ключам доступу вам не трэба ствараць і запамінаць складаныя паролі."</string>
     <string name="passkey_creation_intro_body_fingerprint" msgid="7331338631826254055">"Ключы доступу – гэта зашыфраваныя лючбавыя ключы, створаныя вамі з дапамогай адбітка пальца, твару ці блакіроўкі экрана."</string>
@@ -35,10 +49,13 @@
     <string name="sign_ins" msgid="4710739369149469208">"спосабы ўваходу"</string>
     <string name="sign_in_info" msgid="2627704710674232328">"інфармацыя пра спосабы ўваходу"</string>
     <string name="save_credential_to_title" msgid="3172811692275634301">"Захаваць <xliff:g id="CREDENTIALTYPES">%1$s</xliff:g> сюды:"</string>
-    <string name="create_passkey_in_other_device_title" msgid="9195411122362461390">"Стварыць ключ доступу на іншай прыладзе?"</string>
+    <string name="create_passkey_in_other_device_title" msgid="2360053098931886245">"Стварыць ключ доступу на іншай прыладзе?"</string>
+    <string name="save_password_on_other_device_title" msgid="5829084591948321207">"Захаваць пароль на іншай прыладзе?"</string>
+    <string name="save_sign_in_on_other_device_title" msgid="2827990118560134692">"Захаваць спосаб уваходу на іншай прыладзе?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"Выкарыстоўваць папку \"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>\" для ўсіх спосабаў уваходу?"</string>
-    <string name="use_provider_for_all_description" msgid="8466427781848268490">"Каб вам было прасцей уваходзіць у сістэму, вашы паролі і ключы доступу будуць захоўвацца ў менеджары пароляў"</string>
+    <string name="use_provider_for_all_description" msgid="1998772715863958997">"Каб вам было прасцей уваходзіць у сістэму, вашы паролі і ключы доступу будуць захоўвацца ў менеджары пароляў для <xliff:g id="USERNAME">%1$s</xliff:g>."</string>
     <string name="set_as_default" msgid="4415328591568654603">"Выкарыстоўваць стандартна"</string>
+    <string name="settings" msgid="6536394145760913145">"Налады"</string>
     <string name="use_once" msgid="9027366575315399714">"Скарыстаць адзін раз"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"Пароляў: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> • Ключоў доступу: <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"Пароляў: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>"</string>
@@ -49,21 +66,30 @@
     <string name="other_password_manager" msgid="565790221427004141">"Іншыя спосабы ўваходу"</string>
     <string name="close_sheet" msgid="1393792015338908262">"Закрыць аркуш"</string>
     <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Вярнуцца да папярэдняй старонкі"</string>
-    <string name="accessibility_close_button" msgid="2953807735590034688">"Закрыць прапанову ад Менеджара ўліковых даных унізе экрана"</string>
+    <string name="accessibility_close_button" msgid="1163435587545377687">"Закрыць"</string>
+    <string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"Закрыць"</string>
     <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Скарыстаць захаваны ключ доступу для праграмы \"<xliff:g id="APP_NAME">%1$s</xliff:g>\"?"</string>
-    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Скарыстаць захаваныя спосабы ўваходу для праграмы \"<xliff:g id="APP_NAME">%1$s</xliff:g>\"?"</string>
-    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Выберыце захаваны спосаб уваходу для праграмы \"<xliff:g id="APP_NAME">%1$s</xliff:g>\""</string>
+    <string name="get_dialog_title_use_password_for" msgid="625828023234318484">"Выкарыстоўваць пароль, захаваны для праграмы \"<xliff:g id="APP_NAME">%1$s</xliff:g>\"?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"Скарыстаць ваш спосаб уваходу для праграмы <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"Разблакіраваць варыянты ўваходу для праграмы\"<xliff:g id="APP_NAME">%1$s</xliff:g>\"?"</string>
+    <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"Выберыце захаваны ключ доступу для праграмы \"<xliff:g id="APP_NAME">%1$s</xliff:g>\""</string>
+    <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"Выберыце захаваны пароль для праграмы \"<xliff:g id="APP_NAME">%1$s</xliff:g>\""</string>
+    <string name="get_dialog_title_choose_saved_sign_in_for" msgid="2420298653461652728">"Выберыце захаваны спосаб уваходу для праграмы \"<xliff:g id="APP_NAME">%1$s</xliff:g>\""</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="3048870756117876514">"Выберыце спосаб уваходу для праграмы <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_option_for" msgid="4976380044745029107">"Выберыце ўліковыя даныя для ўваходу ў праграму \"<xliff:g id="APP_NAME">%1$s</xliff:g>\""</string>
+    <string name="get_dialog_title_use_info_on" msgid="8863708099535435146">"Выкарыстоўваць гэтую інфармацыю на прыладзе <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Увайсці іншым спосабам"</string>
     <string name="snackbar_action" msgid="37373514216505085">"Праглядзець варыянты"</string>
     <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Далей"</string>
     <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Спосабы ўваходу"</string>
+    <string name="button_label_view_more" msgid="3429098227286495651">"Паказаць больш"</string>
     <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Для карыстальніка <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Заблакіраваныя спосабы ўваходу"</string>
-    <!-- no translation found for locked_credential_entry_label_subtext_tap_to_unlock (6390367581393605009) -->
-    <skip />
-    <!-- no translation found for locked_credential_entry_label_subtext_no_sign_in (8131725029983174901) -->
-    <skip />
+    <string name="locked_credential_entry_label_subtext_tap_to_unlock" msgid="6390367581393605009">"Націсніце, каб разблакіраваць"</string>
+    <string name="locked_credential_entry_label_subtext_no_sign_in" msgid="8131725029983174901">"Няма даных для ўваходу"</string>
+    <string name="no_sign_in_info_in" msgid="2641118151920288356">"Ва ўліковым запісе <xliff:g id="SOURCE">%1$s</xliff:g> адсутнічае інфармацыя для ўваходу"</string>
     <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Кіраваць спосабамі ўваходу"</string>
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"З іншай прылады"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Скарыстаць іншую прыладу"</string>
+    <string name="request_cancelled_by" msgid="3735222326886267820">"Запыт скасаваны праграмай \"<xliff:g id="APP_NAME">%1$s</xliff:g>\""</string>
 </resources>
diff --git a/packages/CredentialManager/res/values-bg/strings.xml b/packages/CredentialManager/res/values-bg/strings.xml
index c2923b8..ef4dd54 100644
--- a/packages/CredentialManager/res/values-bg/strings.xml
+++ b/packages/CredentialManager/res/values-bg/strings.xml
@@ -1,28 +1,42 @@
 <?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.
+   -->
+
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="4539824758261855508">"Мениджър на идентификационни данни"</string>
     <string name="string_cancel" msgid="6369133483981306063">"Отказ"</string>
     <string name="string_continue" msgid="1346732695941131882">"Напред"</string>
-    <string name="string_more_options" msgid="7990658711962795124">"Още опции"</string>
+    <string name="string_more_options" msgid="2763852250269945472">"Запазване другояче"</string>
     <string name="string_learn_more" msgid="4541600451688392447">"Научете повече"</string>
-    <!-- no translation found for content_description_show_password (3283502010388521607) -->
-    <skip />
-    <!-- no translation found for content_description_hide_password (6841375971631767996) -->
-    <skip />
-    <string name="passkey_creation_intro_title" msgid="4251037543787718844">"По-сигурно с помощта на кодове за достъп"</string>
-    <string name="passkey_creation_intro_body_password" msgid="8825872426579958200">"Когато използвате кодове за достъп, не е необходимо да създавате, нито да помните сложни пароли"</string>
-    <string name="passkey_creation_intro_body_fingerprint" msgid="7331338631826254055">"Кодовете за достъп са шифровани дигитални ключове, които създавате посредством отпечатъка, лицето си или опцията си за заключване на екрана"</string>
+    <string name="content_description_show_password" msgid="3283502010388521607">"Показване на паролата"</string>
+    <string name="content_description_hide_password" msgid="6841375971631767996">"Скриване на паролата"</string>
+    <string name="passkey_creation_intro_title" msgid="4251037543787718844">"По-сигурно с помощта на ключове за достъп"</string>
+    <string name="passkey_creation_intro_body_password" msgid="8825872426579958200">"Когато използвате ключове за достъп, не е необходимо да създавате, нито да помните сложни пароли"</string>
+    <string name="passkey_creation_intro_body_fingerprint" msgid="7331338631826254055">"Ключовете за достъп са шифровани дигитални ключове, които създавате посредством отпечатъка, лицето си или опцията си за заключване на екрана"</string>
     <string name="passkey_creation_intro_body_device" msgid="1203796455762131631">"Данните се запазват в мениджър на пароли, за да можете да влизате в профила си на други устройства"</string>
-    <string name="more_about_passkeys_title" msgid="7797903098728837795">"Още за кодовете за достъп"</string>
+    <string name="more_about_passkeys_title" msgid="7797903098728837795">"Още за ключовете за достъп"</string>
     <string name="passwordless_technology_title" msgid="2497513482056606668">"Заменяща паролите технология"</string>
-    <string name="passwordless_technology_detail" msgid="6853928846532955882">"Кодовете за достъп ви дават възможност да влизате в профила си без парола. Трябва само да използвате отпечатъка, лицето, ПИН кода или фигурата си, за да потвърдите самоличността си и да създадете код за достъп."</string>
+    <string name="passwordless_technology_detail" msgid="6853928846532955882">"Ключовете за достъп ви дават възможност да влизате в профила си без парола. Трябва само да използвате отпечатъка, лицето, ПИН кода или фигурата си, за да потвърдите самоличността си и да създадете код за достъп."</string>
     <string name="public_key_cryptography_title" msgid="6751970819265298039">"Криптография с публичен ключ"</string>
-    <string name="public_key_cryptography_detail" msgid="6937631710280562213">"Според съюза FIDO (Google, Apple, Microsoft и др.) и стандартите на W3C кодовете за достъп ползват двойки криптографски ключове. Вместо потребителско име и парола за дадено приложение или уебсайт се създава двойка ключове – частен и публичен. Първият се съхранява надеждно на устройството ви или в мениджъра на пароли и служи за потвърждаване на самоличността ви. Вторият се споделя със съответния сървър. С двойката ключове можете да се регистрирате и да влезете в профила си незабавно."</string>
+    <string name="public_key_cryptography_detail" msgid="6937631710280562213">"Според съюза FIDO (Google, Apple, Microsoft и др.) и стандартите на W3C ключовете за достъп ползват двойки криптографски ключове. Вместо потребителско име и парола за дадено приложение или уебсайт се създава двойка ключове – частен и публичен. Първият се съхранява надеждно на устройството ви или в мениджъра на пароли и служи за потвърждаване на самоличността ви. Вторият се споделя със съответния сървър. С двойката ключове можете да се регистрирате и да влезете в профила си незабавно."</string>
     <string name="improved_account_security_title" msgid="1069841917893513424">"Подобрена сигурност на профила"</string>
     <string name="improved_account_security_detail" msgid="9123750251551844860">"Всеки ключ е свързан само с приложението или уебсайта, за които е създаден. Затова не е възможно да влезете в измамно приложение или уебсайт по погрешка. Освен това сървърите съхраняват само публичните ключове, което значително затруднява опитите за хакерство."</string>
     <string name="seamless_transition_title" msgid="5335622196351371961">"Безпроблемен преход"</string>
-    <string name="seamless_transition_detail" msgid="4475509237171739843">"Паролите ще продължат да са налице заедно с кодовете за достъп по пътя ни към бъдеще без пароли."</string>
+    <string name="seamless_transition_detail" msgid="4475509237171739843">"Паролите ще продължат да са налице заедно с ключовете за достъп по пътя ни към бъдеще без пароли."</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"Изберете къде да запазите своите <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"Изберете мениджър на пароли, в който да се запазят данните ви, така че следващия път да влезете по-бързо в профила си"</string>
     <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"Да се създаде ли код за достъп за <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
@@ -30,40 +44,52 @@
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Да се запазят ли данните за вход за <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="passkey" msgid="632353688396759522">"код за достъп"</string>
     <string name="password" msgid="6738570945182936667">"парола"</string>
-    <string name="passkeys" msgid="5733880786866559847">"кодове за достъп"</string>
+    <string name="passkeys" msgid="5733880786866559847">"ключове за достъп"</string>
     <string name="passwords" msgid="5419394230391253816">"пароли"</string>
     <string name="sign_ins" msgid="4710739369149469208">"данни за вход"</string>
     <string name="sign_in_info" msgid="2627704710674232328">"данните за вход"</string>
     <string name="save_credential_to_title" msgid="3172811692275634301">"Запазване на <xliff:g id="CREDENTIALTYPES">%1$s</xliff:g> във:"</string>
-    <string name="create_passkey_in_other_device_title" msgid="9195411122362461390">"Искате ли да създадете код за достъп на друго устройство?"</string>
+    <string name="create_passkey_in_other_device_title" msgid="2360053098931886245">"Да се създаде ли ключ за достъп на друго устройство?"</string>
+    <string name="save_password_on_other_device_title" msgid="5829084591948321207">"Паролата да се запази ли на друго устройство?"</string>
+    <string name="save_sign_in_on_other_device_title" msgid="2827990118560134692">"Данните за вход да се запазят ли на друго устройство?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"Да се използва ли <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> за всичките ви данни за вход?"</string>
-    <string name="use_provider_for_all_description" msgid="8466427781848268490">"Този мениджър на пароли ще съхранява вашите пароли и кодове за достъп, за да влизате лесно в профила си"</string>
+    <string name="use_provider_for_all_description" msgid="1998772715863958997">"Мениджърът на пароли за <xliff:g id="USERNAME">%1$s</xliff:g> ще съхранява вашите пароли и ключове за достъп, за да влизате лесно в профила си"</string>
     <string name="set_as_default" msgid="4415328591568654603">"Задаване като основно"</string>
+    <string name="settings" msgid="6536394145760913145">"Настройки"</string>
     <string name="use_once" msgid="9027366575315399714">"Еднократно използване"</string>
-    <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> пароли • <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> кода за достъп"</string>
+    <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> пароли • <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> ключа за достъп"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> пароли"</string>
-    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> кода за достъп"</string>
+    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> ключа за достъп"</string>
     <string name="more_options_usage_credentials" msgid="1785697001787193984">"Идентификационни данни: <xliff:g id="TOTALCREDENTIALSNUMBER">%1$s</xliff:g>"</string>
     <string name="passkey_before_subtitle" msgid="2448119456208647444">"Код за достъп"</string>
     <string name="another_device" msgid="5147276802037801217">"Друго устройство"</string>
     <string name="other_password_manager" msgid="565790221427004141">"Други мениджъри на пароли"</string>
     <string name="close_sheet" msgid="1393792015338908262">"Затваряне на таблицата"</string>
     <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Назад към предишната страница"</string>
-    <string name="accessibility_close_button" msgid="2953807735590034688">"Затваряне на предложението за действие от мениджъра за идентификационни данни, което се показва в долната част на екрана"</string>
+    <string name="accessibility_close_button" msgid="1163435587545377687">"Затваряне"</string>
+    <string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"Отхвърляне"</string>
     <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Да се използва ли запазеният ви код за достъп за <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
-    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Да се използват ли запазените ви данни за вход за <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
-    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Изберете запазени данни за вход за <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_use_password_for" msgid="625828023234318484">"Искате ли да използвате запазената си парола за <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"Да се използват ли вашите данни за вход за <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"Искате ли да отключите опциите за влизане в профила за <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"Изберете запазен ключ за достъп за <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"Изберете запазена парола за <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_saved_sign_in_for" msgid="2420298653461652728">"Изберете запазени данни за вход за <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="3048870756117876514">"Избиране на данни за вход за <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_option_for" msgid="4976380044745029107">"Искате ли да изберете опция за <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_info_on" msgid="8863708099535435146">"Да се използва ли тази информация за <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Влизане в профила по друг начин"</string>
     <string name="snackbar_action" msgid="37373514216505085">"Преглед на опциите"</string>
     <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Напред"</string>
     <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Опции за влизане в профила"</string>
+    <string name="button_label_view_more" msgid="3429098227286495651">"Преглед на още"</string>
     <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"За <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Заключени мениджъри на пароли"</string>
-    <!-- no translation found for locked_credential_entry_label_subtext_tap_to_unlock (6390367581393605009) -->
-    <skip />
-    <!-- no translation found for locked_credential_entry_label_subtext_no_sign_in (8131725029983174901) -->
-    <skip />
+    <string name="locked_credential_entry_label_subtext_tap_to_unlock" msgid="6390367581393605009">"Докоснете, за да отключите"</string>
+    <string name="locked_credential_entry_label_subtext_no_sign_in" msgid="8131725029983174901">"Няма данни за вход"</string>
+    <string name="no_sign_in_info_in" msgid="2641118151920288356">"<xliff:g id="SOURCE">%1$s</xliff:g> не съдържа данни за вход"</string>
     <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Управление на данните за вход"</string>
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"От друго устройство"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Използване на друго устройство"</string>
+    <string name="request_cancelled_by" msgid="3735222326886267820">"Заявката е анулирана от <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
 </resources>
diff --git a/packages/CredentialManager/res/values-bn/strings.xml b/packages/CredentialManager/res/values-bn/strings.xml
index 6ede693..14f4a9b 100644
--- a/packages/CredentialManager/res/values-bn/strings.xml
+++ b/packages/CredentialManager/res/values-bn/strings.xml
@@ -1,15 +1,29 @@
 <?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.
+   -->
+
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="4539824758261855508">"CredentialManager"</string>
     <string name="string_cancel" msgid="6369133483981306063">"বাতিল করুন"</string>
     <string name="string_continue" msgid="1346732695941131882">"চালিয়ে যান"</string>
-    <string name="string_more_options" msgid="7990658711962795124">"আরও বিকল্প"</string>
+    <string name="string_more_options" msgid="2763852250269945472">"অন্য উপায়ে সেভ করুন"</string>
     <string name="string_learn_more" msgid="4541600451688392447">"আরও জানুন"</string>
-    <!-- no translation found for content_description_show_password (3283502010388521607) -->
-    <skip />
-    <!-- no translation found for content_description_hide_password (6841375971631767996) -->
-    <skip />
+    <string name="content_description_show_password" msgid="3283502010388521607">"পাসওয়ার্ড দেখুন"</string>
+    <string name="content_description_hide_password" msgid="6841375971631767996">"পাসওয়ার্ড লুকান"</string>
     <string name="passkey_creation_intro_title" msgid="4251037543787718844">"\'পাসকী\'-এর সাথে সুরক্ষিত"</string>
     <string name="passkey_creation_intro_body_password" msgid="8825872426579958200">"\'পাসকী\' ব্যবহার করলে জটিল পাসওয়ার্ড তৈরি করার বা মনে রাখার কোনও প্রয়োজন নেই"</string>
     <string name="passkey_creation_intro_body_fingerprint" msgid="7331338631826254055">"আপনার ফিঙ্গারপ্রিন্ট, ফেস মডেল বা \'স্ক্রিন লক\' ব্যবহার করে আপনি যে এনক্রিপটেড ডিজিটাল \'কী\' তৈরি করেন সেগুলিই হল \'পাসকী\'"</string>
@@ -35,10 +49,13 @@
     <string name="sign_ins" msgid="4710739369149469208">"সাইন-ইন"</string>
     <string name="sign_in_info" msgid="2627704710674232328">"সাইন-ইন সংক্রান্ত তথ্য"</string>
     <string name="save_credential_to_title" msgid="3172811692275634301">"<xliff:g id="CREDENTIALTYPES">%1$s</xliff:g> এখানে সেভ করুন"</string>
-    <string name="create_passkey_in_other_device_title" msgid="9195411122362461390">"অন্য ডিভাইসে পাসকী তৈরি করবেন?"</string>
+    <string name="create_passkey_in_other_device_title" msgid="2360053098931886245">"অন্য ডিভাইসে \'পাসকী\' তৈরি করবেন?"</string>
+    <string name="save_password_on_other_device_title" msgid="5829084591948321207">"অন্য ডিভাইসে পাসওয়ার্ড সেভ করবেন?"</string>
+    <string name="save_sign_in_on_other_device_title" msgid="2827990118560134692">"অন্য ডিভাইসে সাইন-ইন ক্রেডেনশিয়াল সেভ করবেন?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"আপনার সব সাইন-ইনের জন্য <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ব্যবহার করবেন?"</string>
-    <string name="use_provider_for_all_description" msgid="8466427781848268490">"এই Password Manager আপনার পাসওয়ার্ড ও পাসকী সেভ করবে যাতে সহজেই সাইন-ইন করতে পারেন"</string>
+    <string name="use_provider_for_all_description" msgid="1998772715863958997">"<xliff:g id="USERNAME">%1$s</xliff:g>-এর জন্য এই Password Manager আপনার পাসওয়ার্ড ও \'পাসকী\' সেভ করবে যাতে সহজেই সাইন-ইন করতে পারেন"</string>
     <string name="set_as_default" msgid="4415328591568654603">"ডিফল্ট হিসেবে সেট করুন"</string>
+    <string name="settings" msgid="6536394145760913145">"সেটিংস"</string>
     <string name="use_once" msgid="9027366575315399714">"একবার ব্যবহার করুন"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>টি পাসওয়ার্ড • <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>টি \'পাসকী\'"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>টি পাসওয়ার্ড"</string>
@@ -46,24 +63,33 @@
     <string name="more_options_usage_credentials" msgid="1785697001787193984">"<xliff:g id="TOTALCREDENTIALSNUMBER">%1$s</xliff:g>টি ক্রেডেনশিয়াল"</string>
     <string name="passkey_before_subtitle" msgid="2448119456208647444">"পাসকী"</string>
     <string name="another_device" msgid="5147276802037801217">"অন্য ডিভাইস"</string>
-    <string name="other_password_manager" msgid="565790221427004141">"অন্যান্য Password Manager"</string>
+    <string name="other_password_manager" msgid="565790221427004141">"অন্যান্য পাসওয়ার্ড ম্যানেজার"</string>
     <string name="close_sheet" msgid="1393792015338908262">"শিট বন্ধ করুন"</string>
     <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"আগের পৃষ্ঠায় ফিরে যান"</string>
-    <string name="accessibility_close_button" msgid="2953807735590034688">"স্ক্রিনের নিচে দেখানো Credential Manager অ্যাকশন সংক্রান্ত সাজেশন বন্ধ করুন"</string>
+    <string name="accessibility_close_button" msgid="1163435587545377687">"বন্ধ করুন"</string>
+    <string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"বাতিল করুন"</string>
     <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"<xliff:g id="APP_NAME">%1$s</xliff:g>-এর জন্য আপনার সেভ করা পাসকী ব্যবহার করবেন?"</string>
-    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"<xliff:g id="APP_NAME">%1$s</xliff:g>-এর জন্য আপনার সেভ করা সাইন-ইন সম্পর্কিত ক্রেডেনশিয়াল ব্যবহার করবেন?"</string>
-    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"<xliff:g id="APP_NAME">%1$s</xliff:g>-এর জন্য সাইন-ইন করা সম্পর্কিত ক্রেডেনশিয়াল বেছে নিন"</string>
+    <string name="get_dialog_title_use_password_for" msgid="625828023234318484">"আপনার সেভ করা পাসওয়ার্ড <xliff:g id="APP_NAME">%1$s</xliff:g>-এর জন্য ব্যবহার করবেন?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"<xliff:g id="APP_NAME">%1$s</xliff:g>-এর জন্য আপনার সাইন-ইন ক্রেডেনশিয়াল ব্যবহার করবেন?"</string>
+    <string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"<xliff:g id="APP_NAME">%1$s</xliff:g>-এর জন্য সাইন-ইন করার বিকল্প আনলক করতে চান?"</string>
+    <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"<xliff:g id="APP_NAME">%1$s</xliff:g>-এর জন্য সেভ করা পাসকী বেছে নিন"</string>
+    <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"<xliff:g id="APP_NAME">%1$s</xliff:g>-এর জন্য সেভ করা পাসকী বেছে নিন"</string>
+    <string name="get_dialog_title_choose_saved_sign_in_for" msgid="2420298653461652728">"<xliff:g id="APP_NAME">%1$s</xliff:g>-এর জন্য সেভ করা ক্রেডেনশিয়াল বেছে নিন"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="3048870756117876514">"<xliff:g id="APP_NAME">%1$s</xliff:g>-এর জন্য সাইন-ইন ক্রেডেনশিয়াল বেছে নিন"</string>
+    <string name="get_dialog_title_choose_option_for" msgid="4976380044745029107">"<xliff:g id="APP_NAME">%1$s</xliff:g>-এর জন্য বিকল্প বেছে নেবেন?"</string>
+    <string name="get_dialog_title_use_info_on" msgid="8863708099535435146">"<xliff:g id="APP_NAME">%1$s</xliff:g>-এ সাইন-ইন করতে এই তথ্য ব্যবহার করবেন?"</string>
     <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"অন্যভাবে সাইন-ইন করুন"</string>
     <string name="snackbar_action" msgid="37373514216505085">"বিকল্প দেখুন"</string>
     <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"চালিয়ে যান"</string>
     <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"সাইন-ইন করার বিকল্প"</string>
+    <string name="button_label_view_more" msgid="3429098227286495651">"আরও দেখুন"</string>
     <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"<xliff:g id="USERNAME">%1$s</xliff:g>-এর জন্য"</string>
     <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"লক করা Password Manager"</string>
-    <!-- no translation found for locked_credential_entry_label_subtext_tap_to_unlock (6390367581393605009) -->
-    <skip />
-    <!-- no translation found for locked_credential_entry_label_subtext_no_sign_in (8131725029983174901) -->
-    <skip />
+    <string name="locked_credential_entry_label_subtext_tap_to_unlock" msgid="6390367581393605009">"আনলক করতে ট্যাপ করুন"</string>
+    <string name="locked_credential_entry_label_subtext_no_sign_in" msgid="8131725029983174901">"সাইন-ইন সম্পর্কিত তথ্য নেই"</string>
+    <string name="no_sign_in_info_in" msgid="2641118151920288356">"<xliff:g id="SOURCE">%1$s</xliff:g>-এ সাইন-ইন সম্পর্কিত কোনও তথ্য নেই"</string>
     <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"সাইন-ইন করার ক্রেডেনশিয়াল ম্যানেজ করুন"</string>
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"অন্য ডিভাইস থেকে"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"আলাদা ডিভাইস ব্যবহার করুন"</string>
+    <string name="request_cancelled_by" msgid="3735222326886267820">"<xliff:g id="APP_NAME">%1$s</xliff:g> দ্বারা অনুরোধ বাতিল করা হয়েছে"</string>
 </resources>
diff --git a/packages/CredentialManager/res/values-bs/strings.xml b/packages/CredentialManager/res/values-bs/strings.xml
index ee42d75..f90db6f 100644
--- a/packages/CredentialManager/res/values-bs/strings.xml
+++ b/packages/CredentialManager/res/values-bs/strings.xml
@@ -1,15 +1,31 @@
 <?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.
+   -->
+
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="4539824758261855508">"Upravitelj akreditiva"</string>
     <string name="string_cancel" msgid="6369133483981306063">"Otkaži"</string>
     <string name="string_continue" msgid="1346732695941131882">"Nastavi"</string>
-    <string name="string_more_options" msgid="7990658711962795124">"Više opcija"</string>
+    <string name="string_more_options" msgid="2763852250269945472">"Sačuvaj na drugi način"</string>
     <string name="string_learn_more" msgid="4541600451688392447">"Saznajte više"</string>
-    <string name="content_description_show_password" msgid="3283502010388521607">"Prikaži zaporku"</string>
-    <string name="content_description_hide_password" msgid="6841375971631767996">"Sakrij zaporku"</string>
+    <string name="content_description_show_password" msgid="3283502010388521607">"Prikaži lozinku"</string>
+    <string name="content_description_hide_password" msgid="6841375971631767996">"Sakrij lozinku"</string>
     <string name="passkey_creation_intro_title" msgid="4251037543787718844">"Sigurniji ste uz pristupne ključeve"</string>
-    <string name="passkey_creation_intro_body_password" msgid="8825872426579958200">"Uz pristupne ključeve ne morate kreirati ili pamtiti složene lozinke"</string>
+    <string name="passkey_creation_intro_body_password" msgid="8825872426579958200">"Uz pristupne ključeve ne morate kreirati niti pamtiti složene lozinke"</string>
     <string name="passkey_creation_intro_body_fingerprint" msgid="7331338631826254055">"Pristupni ključevi su šifrirani digitalni ključevi koje kreirate pomoću otiska prsta, lica ili zaključavanja ekrana"</string>
     <string name="passkey_creation_intro_body_device" msgid="1203796455762131631">"Pristupni ključevi se pohranjuju u upravitelju lozinki da se možete prijaviti na drugim uređajima"</string>
     <string name="more_about_passkeys_title" msgid="7797903098728837795">"Više o pristupnim ključevima"</string>
@@ -21,7 +37,7 @@
     <string name="improved_account_security_detail" msgid="9123750251551844860">"Svaki ključ je isključivo povezan s aplikacijom ili web lokacijom za koju je kreiran, tako da se nikada ne možete greškom prijaviti u prevarantsku aplikaciju ili na prevarantsku web lokaciju. Osim toga, hakiranje je puno teže zahvaljujući serverima koji čuvaju samo javne ključeve."</string>
     <string name="seamless_transition_title" msgid="5335622196351371961">"Neometani prijelaz"</string>
     <string name="seamless_transition_detail" msgid="4475509237171739843">"Kako se krećemo prema budućnosti bez lozinki, lozinke će i dalje biti dostupne uz pristupne ključeve."</string>
-    <string name="choose_provider_title" msgid="8870795677024868108">"Odaberite gdje sačuvati stavku <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <string name="choose_provider_title" msgid="8870795677024868108">"Odaberite gdje će se pohranjivati <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"Odaberite upravitelja lozinki da sačuvate svoje informacije i brže se prijavite sljedeći put"</string>
     <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"Kreirati pristupni ključ za aplikaciju <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_password_title" msgid="7097275038523578687">"Sačuvati lozinku za aplikaciju <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
@@ -32,11 +48,14 @@
     <string name="passwords" msgid="5419394230391253816">"lozinke"</string>
     <string name="sign_ins" msgid="4710739369149469208">"prijave"</string>
     <string name="sign_in_info" msgid="2627704710674232328">"informacije o prijavi"</string>
-    <string name="save_credential_to_title" msgid="3172811692275634301">"Sačuvajte vrstu akreditiva \"<xliff:g id="CREDENTIALTYPES">%1$s</xliff:g>\" na"</string>
-    <string name="create_passkey_in_other_device_title" msgid="9195411122362461390">"Kreirati pristupni ključ na drugom uređaju?"</string>
+    <string name="save_credential_to_title" msgid="3172811692275634301">"Sačuvaj <xliff:g id="CREDENTIALTYPES">%1$s</xliff:g> na"</string>
+    <string name="create_passkey_in_other_device_title" msgid="2360053098931886245">"Kreirati pristupni ključ na drugom uređaju?"</string>
+    <string name="save_password_on_other_device_title" msgid="5829084591948321207">"Sačuvati lozinku na drugom uređaju?"</string>
+    <string name="save_sign_in_on_other_device_title" msgid="2827990118560134692">"Sačuvati akreditive za prijavu na drugom uređaju?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"Koristiti uslugu <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> za sve vaše prijave?"</string>
-    <string name="use_provider_for_all_description" msgid="8466427781848268490">"Ovaj upravitelj lozinki će pohraniti vaše lozinke i pristupne ključeve da vam olakša prijavu"</string>
+    <string name="use_provider_for_all_description" msgid="1998772715863958997">"Ovaj upravitelj lozinki za <xliff:g id="USERNAME">%1$s</xliff:g> će pohraniti vaše lozinke i pristupne ključeve da vam olakša prijavu"</string>
     <string name="set_as_default" msgid="4415328591568654603">"Postavi kao zadano"</string>
+    <string name="settings" msgid="6536394145760913145">"Postavke"</string>
     <string name="use_once" msgid="9027366575315399714">"Koristi jednom"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"Broj lozinki: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> • Broj pristupnih ključeva: <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"Broj lozinki: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>"</string>
@@ -47,21 +66,30 @@
     <string name="other_password_manager" msgid="565790221427004141">"Drugi upravitelji lozinki"</string>
     <string name="close_sheet" msgid="1393792015338908262">"Zatvaranje tabele"</string>
     <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Povratak na prethodnu stranicu"</string>
-    <string name="accessibility_close_button" msgid="2953807735590034688">"Zatvaranje prijedloga radnje Upravitelja akreditiva koji se prikazuje na dnu ekrana"</string>
+    <string name="accessibility_close_button" msgid="1163435587545377687">"Zatvaranje"</string>
+    <string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"Odbacivanje"</string>
     <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Koristiti sačuvani pristupni ključ za aplikaciju <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
-    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Koristiti sačuvanu prijavu za aplikaciju <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
-    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Odaberite sačuvanu prijavu za aplikaciju <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_use_password_for" msgid="625828023234318484">"Koristiti sačuvanu lozinku za aplikaciju <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"Koristiti vaše akreditive za prijavu u aplikaciju <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"Otključati opcije prijave za aplikaciju <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"Odaberite sačuvani pristupni ključ za aplikaciju <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"Odaberite sačuvanu lozinku za aplikaciju <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_saved_sign_in_for" msgid="2420298653461652728">"Odaberite sačuvanu prijavu za aplikaciju <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="3048870756117876514">"Odaberite akreditive za prijavu u aplikaciju <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_option_for" msgid="4976380044745029107">"Odabrati opciju za aplikaciju <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_info_on" msgid="8863708099535435146">"Koristiti ove informacije u aplikaciji <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Prijavite se na drugi način"</string>
     <string name="snackbar_action" msgid="37373514216505085">"Prikaži opcije"</string>
     <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Nastavi"</string>
     <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Opcije prijave"</string>
+    <string name="button_label_view_more" msgid="3429098227286495651">"Pregledajte više"</string>
     <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Za osobu <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Zaključani upravitelji lozinki"</string>
-    <!-- no translation found for locked_credential_entry_label_subtext_tap_to_unlock (6390367581393605009) -->
-    <skip />
-    <!-- no translation found for locked_credential_entry_label_subtext_no_sign_in (8131725029983174901) -->
-    <skip />
+    <string name="locked_credential_entry_label_subtext_tap_to_unlock" msgid="6390367581393605009">"Dodirnite da otključate"</string>
+    <string name="locked_credential_entry_label_subtext_no_sign_in" msgid="8131725029983174901">"Nema podataka za prijavu"</string>
+    <string name="no_sign_in_info_in" msgid="2641118151920288356">"Nema informacija za prijavu na <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Upravljajte prijavama"</string>
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"S drugog uređaja"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Upotrijebite drugi uređaj"</string>
+    <string name="request_cancelled_by" msgid="3735222326886267820">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> je otkazala zahtjev"</string>
 </resources>
diff --git a/packages/CredentialManager/res/values-ca/strings.xml b/packages/CredentialManager/res/values-ca/strings.xml
index 6b898e1..8e3fda8 100644
--- a/packages/CredentialManager/res/values-ca/strings.xml
+++ b/packages/CredentialManager/res/values-ca/strings.xml
@@ -1,24 +1,38 @@
 <?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.
+   -->
+
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="4539824758261855508">"Gestor de credencials"</string>
     <string name="string_cancel" msgid="6369133483981306063">"Cancel·la"</string>
     <string name="string_continue" msgid="1346732695941131882">"Continua"</string>
-    <string name="string_more_options" msgid="7990658711962795124">"Més opcions"</string>
+    <string name="string_more_options" msgid="2763852250269945472">"Desa diferent"</string>
     <string name="string_learn_more" msgid="4541600451688392447">"Més informació"</string>
-    <!-- no translation found for content_description_show_password (3283502010388521607) -->
-    <skip />
-    <!-- no translation found for content_description_hide_password (6841375971631767996) -->
-    <skip />
+    <string name="content_description_show_password" msgid="3283502010388521607">"Mostra la contrasenya"</string>
+    <string name="content_description_hide_password" msgid="6841375971631767996">"Amaga la contrasenya"</string>
     <string name="passkey_creation_intro_title" msgid="4251037543787718844">"Més seguretat amb les claus d\'accés"</string>
     <string name="passkey_creation_intro_body_password" msgid="8825872426579958200">"Amb les claus d\'accés, no cal que creïs ni recordis contrasenyes difícils"</string>
     <string name="passkey_creation_intro_body_fingerprint" msgid="7331338631826254055">"Les claus d\'accés són claus digitals encriptades que pots crear amb la teva cara, l\'empremta digital o el bloqueig de pantalla"</string>
-    <string name="passkey_creation_intro_body_device" msgid="1203796455762131631">"Es desen a un gestor de contrasenyes perquè puguis iniciar la sessió en altres dispositius"</string>
+    <string name="passkey_creation_intro_body_device" msgid="1203796455762131631">"Es desen en un gestor de contrasenyes perquè puguis iniciar la sessió en altres dispositius"</string>
     <string name="more_about_passkeys_title" msgid="7797903098728837795">"Més sobre les claus d\'accés"</string>
     <string name="passwordless_technology_title" msgid="2497513482056606668">"Tecnologia sense contrasenyes"</string>
     <string name="passwordless_technology_detail" msgid="6853928846532955882">"Les claus d\'accés et permeten iniciar la sessió sense dependre de cap contrasenya. Només necessites utilitzar l\'empremta digital, el reconeixement facial, el PIN o lliscar el patró per verificar la teva identitat i crear una clau d\'accés."</string>
     <string name="public_key_cryptography_title" msgid="6751970819265298039">"Criptografia de la clau pública"</string>
-    <string name="public_key_cryptography_detail" msgid="6937631710280562213">"D\'acord amb FIDO Alliance i els estàndards W3C, les claus d\'accés utilitzen parells de claus criptogràfiques. En lloc del nom d\'usuari i la cadena de caràcters que utilitzem per a les contrasenyes, es crea un parell de claus per a una app o web. La clau privada es desa de manera segura al dispositiu o al gestor de contrasenyes i confirma la teva identitat. La clau pública es comparteix amb l\'app o el servidor del lloc web. Amb les claus corresponents, pots registrar-te i iniciar sessió a l\'instant."</string>
+    <string name="public_key_cryptography_detail" msgid="6937631710280562213">"D\'acord amb FIDO Alliance i els estàndards W3C, les claus d\'accés utilitzen parells de claus criptogràfiques. En lloc del nom d\'usuari i la cadena de caràcters que utilitzem per a les contrasenyes, es crea un parell de claus per a una app o un lloc web. La clau privada es desa de manera segura al dispositiu o al gestor de contrasenyes i confirma la teva identitat. La clau pública es comparteix amb l\'app o el servidor del lloc web. Amb les claus corresponents, pots registrar-te i iniciar sessió a l\'instant."</string>
     <string name="improved_account_security_title" msgid="1069841917893513424">"Seguretat dels comptes millorada"</string>
     <string name="improved_account_security_detail" msgid="9123750251551844860">"Cada clau està exclusivament enllaçada a l\'aplicació o al lloc web per als quals s\'ha creat. D\'aquesta manera, mai iniciaràs la sessió en una aplicació o un lloc web fraudulents per error. A més, com que els servidors només conserven les claus públiques, el hacking és molt més difícil."</string>
     <string name="seamless_transition_title" msgid="5335622196351371961">"Transició fluida"</string>
@@ -34,11 +48,14 @@
     <string name="passwords" msgid="5419394230391253816">"contrasenyes"</string>
     <string name="sign_ins" msgid="4710739369149469208">"inicis de sessió"</string>
     <string name="sign_in_info" msgid="2627704710674232328">"informació d\'inici de sessió"</string>
-    <string name="save_credential_to_title" msgid="3172811692275634301">"Desa <xliff:g id="CREDENTIALTYPES">%1$s</xliff:g> a"</string>
-    <string name="create_passkey_in_other_device_title" msgid="9195411122362461390">"Vols crear una clau d\'accés en un altre dispositiu?"</string>
+    <string name="save_credential_to_title" msgid="3172811692275634301">"Desa la <xliff:g id="CREDENTIALTYPES">%1$s</xliff:g> a"</string>
+    <string name="create_passkey_in_other_device_title" msgid="2360053098931886245">"Vols crear una clau d\'accés en un altre dispositiu?"</string>
+    <string name="save_password_on_other_device_title" msgid="5829084591948321207">"Vols desar la contrasenya en un altre dispositiu?"</string>
+    <string name="save_sign_in_on_other_device_title" msgid="2827990118560134692">"Vols desar l\'inici de sessió en un altre dispositiu?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"Vols utilitzar <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> per a tots els teus inicis de sessió?"</string>
-    <string name="use_provider_for_all_description" msgid="8466427781848268490">"Aquest gestor de contrasenyes emmagatzemarà les teves contrasenyes i claus d\'accés per ajudar-te a iniciar la sessió fàcilment"</string>
+    <string name="use_provider_for_all_description" msgid="1998772715863958997">"Aquest gestor de contrasenyes per a <xliff:g id="USERNAME">%1$s</xliff:g> emmagatzemarà les teves contrasenyes i claus d\'accés per ajudar-te a iniciar la sessió fàcilment"</string>
     <string name="set_as_default" msgid="4415328591568654603">"Estableix com a predeterminada"</string>
+    <string name="settings" msgid="6536394145760913145">"Configuració"</string>
     <string name="use_once" msgid="9027366575315399714">"Utilitza un cop"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> contrasenyes • <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> claus d\'accés"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> contrasenyes"</string>
@@ -49,21 +66,30 @@
     <string name="other_password_manager" msgid="565790221427004141">"Altres gestors de contrasenyes"</string>
     <string name="close_sheet" msgid="1393792015338908262">"Tanca el full"</string>
     <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Torna a la pàgina anterior"</string>
-    <string name="accessibility_close_button" msgid="2953807735590034688">"Tanca el suggeriment d\'acció del Gestor de credencials que es mostra a la part inferior de la pantalla"</string>
+    <string name="accessibility_close_button" msgid="1163435587545377687">"Tanca"</string>
+    <string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"Ignora"</string>
     <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Vols utilitzar la clau d\'accés desada per a <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
-    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Vols utilitzar l\'inici de sessió desat per a <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
-    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Tria un inici de sessió desat per a <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_use_password_for" msgid="625828023234318484">"Vols utilitzar la contrasenya desada per a <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"Vols utilitzar el teu inici de sessió per a <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"Vols desbloquejar les opcions d\'inici de sessió per a <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"Tria una clau d\'accés desada per a <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"Tria una clau d\'accés desada per a <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_saved_sign_in_for" msgid="2420298653461652728">"Tria un inici de sessió desat per a <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="3048870756117876514">"Tria un inici de sessió per a <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_option_for" msgid="4976380044745029107">"Vols triar una opció per a <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_info_on" msgid="8863708099535435146">"Vols utilitzar aquesta informació a <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Inicia la sessió d\'una altra manera"</string>
     <string name="snackbar_action" msgid="37373514216505085">"Mostra les opcions"</string>
     <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Continua"</string>
     <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Opcions d\'inici de sessió"</string>
+    <string name="button_label_view_more" msgid="3429098227286495651">"Mostra\'n més"</string>
     <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Per a <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Gestors de contrasenyes bloquejats"</string>
-    <!-- no translation found for locked_credential_entry_label_subtext_tap_to_unlock (6390367581393605009) -->
-    <skip />
-    <!-- no translation found for locked_credential_entry_label_subtext_no_sign_in (8131725029983174901) -->
-    <skip />
+    <string name="locked_credential_entry_label_subtext_tap_to_unlock" msgid="6390367581393605009">"Toca per desbloquejar"</string>
+    <string name="locked_credential_entry_label_subtext_no_sign_in" msgid="8131725029983174901">"Sense informació d\'inici de sessió"</string>
+    <string name="no_sign_in_info_in" msgid="2641118151920288356">"Sense informació d\'inici de sessió per a <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Gestiona els inicis de sessió"</string>
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Des d\'un altre dispositiu"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Utilitza un dispositiu diferent"</string>
+    <string name="request_cancelled_by" msgid="3735222326886267820">"<xliff:g id="APP_NAME">%1$s</xliff:g> ha cancel·lat la sol·licitud"</string>
 </resources>
diff --git a/packages/CredentialManager/res/values-cs/strings.xml b/packages/CredentialManager/res/values-cs/strings.xml
index 00c037b..9fe5a49 100644
--- a/packages/CredentialManager/res/values-cs/strings.xml
+++ b/packages/CredentialManager/res/values-cs/strings.xml
@@ -1,10 +1,26 @@
 <?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="4539824758261855508">"Správce oprávnění"</string>
     <string name="string_cancel" msgid="6369133483981306063">"Zrušit"</string>
     <string name="string_continue" msgid="1346732695941131882">"Pokračovat"</string>
-    <string name="string_more_options" msgid="7990658711962795124">"Další možnosti"</string>
+    <string name="string_more_options" msgid="2763852250269945472">"Uložit jinak"</string>
     <string name="string_learn_more" msgid="4541600451688392447">"Další informace"</string>
     <string name="content_description_show_password" msgid="3283502010388521607">"Zobrazit heslo"</string>
     <string name="content_description_hide_password" msgid="6841375971631767996">"Skrýt heslo"</string>
@@ -16,7 +32,7 @@
     <string name="passwordless_technology_title" msgid="2497513482056606668">"Technologie bez hesel"</string>
     <string name="passwordless_technology_detail" msgid="6853928846532955882">"Přístupové klíče umožňují přihlašovat se bez hesel. Stačí pomocí otisku prstu, rozpoznání obličeje, kódu PIN nebo gesta ověřit svou totožnost a vytvořit přístupový klíč."</string>
     <string name="public_key_cryptography_title" msgid="6751970819265298039">"Kryptografie s veřejným klíčem"</string>
-    <string name="public_key_cryptography_detail" msgid="6937631710280562213">"Podle pokynů FIDO Alliance (která zahrnuje společnosti Google, Apple, Microsoft a další) a standardů W3C používají přístupové klíče páry kryptografických klíčů. Na rozdíl od uživatelského jména a řetězce znaků, které používáme pro hesla, se pro aplikaci nebo web vytváří pár klíčů (soukromého a veřejného ). Soukromý klíč je bezpečně uložen ve vašem zařízení nebo správci hesel a potvrzuje vaši identitu. Veřejný klíč je sdílen s aplikací nebo webovým serverem. Pomocí odpovídajících klíčů se můžete okamžitě zaregistrovat a přihlásit."</string>
+    <string name="public_key_cryptography_detail" msgid="6937631710280562213">"Podle pokynů FIDO Alliance (zahrnující mj. firmy Google, Apple a Microsoft) a standardů W3C používají přístupové klíče páry kryptografických klíčů. Na rozdíl od jména uživatele a řetězce znaků používaného pro heslo se pro aplikaci nebo web vytváří soukromý a veřejný klíč. Soukromý klíč se bezpečně uloží do zařízení nebo správce hesel a potvrzuje vaši identitu. Veřejný klíč se sdílí s aplikací nebo webovým serverem. Pomocí odpovídajících klíčů se můžete rychle zaregistrovat a přihlásit."</string>
     <string name="improved_account_security_title" msgid="1069841917893513424">"Vylepšené zabezpečení účtu"</string>
     <string name="improved_account_security_detail" msgid="9123750251551844860">"Každý klíč je propojen výhradně s aplikací nebo webem, pro které byl vytvořen, takže se nikdy nemůžete omylem přihlásit k podvodné aplikaci nebo webu. Protože na serverech jsou uloženy pouze veřejné klíče, je hackování navíc mnohem obtížnější."</string>
     <string name="seamless_transition_title" msgid="5335622196351371961">"Bezproblémový přechod"</string>
@@ -33,10 +49,13 @@
     <string name="sign_ins" msgid="4710739369149469208">"přihlášení"</string>
     <string name="sign_in_info" msgid="2627704710674232328">"přihlašovací údaje"</string>
     <string name="save_credential_to_title" msgid="3172811692275634301">"Uložit <xliff:g id="CREDENTIALTYPES">%1$s</xliff:g> do"</string>
-    <string name="create_passkey_in_other_device_title" msgid="9195411122362461390">"Vytvořit přístupový klíč v jiném zařízení?"</string>
+    <string name="create_passkey_in_other_device_title" msgid="2360053098931886245">"Vytvořit přístupový klíč na jiném zařízení?"</string>
+    <string name="save_password_on_other_device_title" msgid="5829084591948321207">"Uložit přístupový klíč na jiném zařízení?"</string>
+    <string name="save_sign_in_on_other_device_title" msgid="2827990118560134692">"Uložit přihlášení na jiném zařízení?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"Používat <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> pro všechna přihlášení?"</string>
-    <string name="use_provider_for_all_description" msgid="8466427781848268490">"Tento správce hesel bude ukládat vaše hesla a přístupové klíče, abyste se mohli snadno přihlásit"</string>
+    <string name="use_provider_for_all_description" msgid="1998772715863958997">"Správce hesel pro účet <xliff:g id="USERNAME">%1$s</xliff:g> bude ukládat vaše hesla a přístupové klíče, abyste se mohli snadno přihlásit"</string>
     <string name="set_as_default" msgid="4415328591568654603">"Nastavit jako výchozí"</string>
+    <string name="settings" msgid="6536394145760913145">"Nastavení"</string>
     <string name="use_once" msgid="9027366575315399714">"Použít jednou"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"Hesla: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> • Přístupové klíče: <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"Počet hesel: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>"</string>
@@ -47,21 +66,30 @@
     <string name="other_password_manager" msgid="565790221427004141">"Další správci hesel"</string>
     <string name="close_sheet" msgid="1393792015338908262">"Zavřít list"</string>
     <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Zpět na předchozí stránku"</string>
-    <string name="accessibility_close_button" msgid="2953807735590034688">"Zavřít návrh akce Správce oprávnění zobrazený ve spodní části obrazovky"</string>
+    <string name="accessibility_close_button" msgid="1163435587545377687">"Zavřít"</string>
+    <string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"Zavřít"</string>
     <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Použít uložený přístupový klíč pro aplikaci <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
-    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Použít uložené přihlášení pro aplikaci <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
-    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Vyberte uložené přihlášení pro <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_use_password_for" msgid="625828023234318484">"Použít pro aplikaci <xliff:g id="APP_NAME">%1$s</xliff:g> uložené heslo?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"Použít přihlášení pro <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"Odemknout možnosti přihlášení pro aplikaci <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"Vyberte uložený přístupový klíč pro aplikaci <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"Vyberte uložené heslo pro aplikaci <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_saved_sign_in_for" msgid="2420298653461652728">"Vyberte uložené přihlášení pro aplikaci <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="3048870756117876514">"Vyberte přihlášení pro <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_option_for" msgid="4976380044745029107">"Vybrat možnost pro aplikaci <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_info_on" msgid="8863708099535435146">"Použít tyto informace na <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Přihlásit se jinak"</string>
     <string name="snackbar_action" msgid="37373514216505085">"Zobrazit možnosti"</string>
     <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Pokračovat"</string>
     <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Možnosti přihlašování"</string>
+    <string name="button_label_view_more" msgid="3429098227286495651">"Zobrazit více"</string>
     <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Pro uživatele <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Uzamčení správci hesel"</string>
-    <!-- no translation found for locked_credential_entry_label_subtext_tap_to_unlock (6390367581393605009) -->
-    <skip />
-    <!-- no translation found for locked_credential_entry_label_subtext_no_sign_in (8131725029983174901) -->
-    <skip />
+    <string name="locked_credential_entry_label_subtext_tap_to_unlock" msgid="6390367581393605009">"Klepnutím odemknete"</string>
+    <string name="locked_credential_entry_label_subtext_no_sign_in" msgid="8131725029983174901">"Žádné informace o přihlášení"</string>
+    <string name="no_sign_in_info_in" msgid="2641118151920288356">"<xliff:g id="SOURCE">%1$s</xliff:g> neobsahuje žádné přihlašovací údaje"</string>
     <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Spravovat přihlášení"</string>
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Z jiného zařízení"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Použít jiné zařízení"</string>
+    <string name="request_cancelled_by" msgid="3735222326886267820">"Aplikace <xliff:g id="APP_NAME">%1$s</xliff:g> žádost zrušila"</string>
 </resources>
diff --git a/packages/CredentialManager/res/values-da/strings.xml b/packages/CredentialManager/res/values-da/strings.xml
index 7583b7c..c7ee038 100644
--- a/packages/CredentialManager/res/values-da/strings.xml
+++ b/packages/CredentialManager/res/values-da/strings.xml
@@ -1,15 +1,29 @@
 <?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.
+   -->
+
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="4539824758261855508">"Loginstyring"</string>
     <string name="string_cancel" msgid="6369133483981306063">"Annuller"</string>
     <string name="string_continue" msgid="1346732695941131882">"Fortsæt"</string>
-    <string name="string_more_options" msgid="7990658711962795124">"Flere valgmuligheder"</string>
+    <string name="string_more_options" msgid="2763852250269945472">"Gem på en anden måde"</string>
     <string name="string_learn_more" msgid="4541600451688392447">"Få flere oplysninger"</string>
-    <!-- no translation found for content_description_show_password (3283502010388521607) -->
-    <skip />
-    <!-- no translation found for content_description_hide_password (6841375971631767996) -->
-    <skip />
+    <string name="content_description_show_password" msgid="3283502010388521607">"Vis adgangskode"</string>
+    <string name="content_description_hide_password" msgid="6841375971631767996">"Skjul adgangskode"</string>
     <string name="passkey_creation_intro_title" msgid="4251037543787718844">"Øget beskyttelse med adgangsnøgler"</string>
     <string name="passkey_creation_intro_body_password" msgid="8825872426579958200">"Når du bruger adgangsnøgler, behøver du ikke at oprette eller huske avancerede adgangskoder"</string>
     <string name="passkey_creation_intro_body_fingerprint" msgid="7331338631826254055">"Adgangsnøgler er krypterede digitale nøgler, som du opretter ved hjælp af fingeraftryk, ansigtsgenkendelse eller skærmlås"</string>
@@ -34,11 +48,14 @@
     <string name="passwords" msgid="5419394230391253816">"adgangskoder"</string>
     <string name="sign_ins" msgid="4710739369149469208">"loginmetoder"</string>
     <string name="sign_in_info" msgid="2627704710674232328">"loginoplysninger"</string>
-    <string name="save_credential_to_title" msgid="3172811692275634301">"Gem <xliff:g id="CREDENTIALTYPES">%1$s</xliff:g> her:"</string>
-    <string name="create_passkey_in_other_device_title" msgid="9195411122362461390">"Vil du oprette en adgangsnøgle på en anden enhed?"</string>
+    <string name="save_credential_to_title" msgid="3172811692275634301">"Gem <xliff:g id="CREDENTIALTYPES">%1$s</xliff:g> i"</string>
+    <string name="create_passkey_in_other_device_title" msgid="2360053098931886245">"Vil du oprette en adgangsnøgle på en anden enhed?"</string>
+    <string name="save_password_on_other_device_title" msgid="5829084591948321207">"Vil du gemme adgangskoden på en anden enhed?"</string>
+    <string name="save_sign_in_on_other_device_title" msgid="2827990118560134692">"Vil du gemme loginmetoden på en anden enhed?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"Vil du bruge <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> til alle dine loginmetoder?"</string>
-    <string name="use_provider_for_all_description" msgid="8466427781848268490">"Denne adgangskodeadministrator gemmer dine adgangskoder og adgangsnøgler for at hjælpe dig med nemt at logge ind"</string>
+    <string name="use_provider_for_all_description" msgid="1998772715863958997">"Denne adgangskodeadministrator for <xliff:g id="USERNAME">%1$s</xliff:g> gemmer dine adgangskoder og adgangsnøgler for at hjælpe dig med nemt at logge ind"</string>
     <string name="set_as_default" msgid="4415328591568654603">"Angiv som standard"</string>
+    <string name="settings" msgid="6536394145760913145">"Indstillinger"</string>
     <string name="use_once" msgid="9027366575315399714">"Brug én gang"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> adgangskoder • <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> adgangsnøgler"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> adgangskoder"</string>
@@ -49,21 +66,30 @@
     <string name="other_password_manager" msgid="565790221427004141">"Andre adgangskodeadministratorer"</string>
     <string name="close_sheet" msgid="1393792015338908262">"Luk arket"</string>
     <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Gå tilbage til den forrige side"</string>
-    <string name="accessibility_close_button" msgid="2953807735590034688">"Luk handlingsforslaget for Loginstyring, som vises nederst på skærmen"</string>
+    <string name="accessibility_close_button" msgid="1163435587545377687">"Luk"</string>
+    <string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"Luk"</string>
     <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Vil du bruge din gemte adgangsnøgle til <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
-    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Vil du bruge din gemte loginmetode til <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
-    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Vælg en gemt loginmetode til <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_use_password_for" msgid="625828023234318484">"Vil du bruge din gemte adgangskode til <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"Vil du bruge dine loginoplysninger til <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"Vil du låse enheden op for at se loginmetoder for <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"Vælg en gemt adgangsnøgle til <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"Vælg en gemt adgangskode til <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_saved_sign_in_for" msgid="2420298653461652728">"Vælg en gemt loginmetode til <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="3048870756117876514">"Vælg loginoplysninger til <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_option_for" msgid="4976380044745029107">"Vil du vælge en mulighed for <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_info_on" msgid="8863708099535435146">"Vil du bruge disse oplysninger i <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Log ind på en anden måde"</string>
     <string name="snackbar_action" msgid="37373514216505085">"Se valgmuligheder"</string>
     <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Fortsæt"</string>
     <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Valgmuligheder for login"</string>
+    <string name="button_label_view_more" msgid="3429098227286495651">"Se mere"</string>
     <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"For <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Låste adgangskodeadministratorer"</string>
-    <!-- no translation found for locked_credential_entry_label_subtext_tap_to_unlock (6390367581393605009) -->
-    <skip />
-    <!-- no translation found for locked_credential_entry_label_subtext_no_sign_in (8131725029983174901) -->
-    <skip />
+    <string name="locked_credential_entry_label_subtext_tap_to_unlock" msgid="6390367581393605009">"Tryk for at låse op"</string>
+    <string name="locked_credential_entry_label_subtext_no_sign_in" msgid="8131725029983174901">"Ingen loginoplysninger"</string>
+    <string name="no_sign_in_info_in" msgid="2641118151920288356">"Der er ingen loginoplysninger i <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Administrer loginmetoder"</string>
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Fra en anden enhed"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Brug en anden enhed"</string>
+    <string name="request_cancelled_by" msgid="3735222326886267820">"Anmodningen blev annulleret af <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
 </resources>
diff --git a/packages/CredentialManager/res/values-de/strings.xml b/packages/CredentialManager/res/values-de/strings.xml
index 0946556..29d9a86b 100644
--- a/packages/CredentialManager/res/values-de/strings.xml
+++ b/packages/CredentialManager/res/values-de/strings.xml
@@ -1,24 +1,38 @@
 <?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.
+   -->
+
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="4539824758261855508">"Anmeldedaten-Manager"</string>
     <string name="string_cancel" msgid="6369133483981306063">"Abbrechen"</string>
     <string name="string_continue" msgid="1346732695941131882">"Weiter"</string>
-    <string name="string_more_options" msgid="7990658711962795124">"Weitere Optionen"</string>
+    <string name="string_more_options" msgid="2763852250269945472">"Anders speichern"</string>
     <string name="string_learn_more" msgid="4541600451688392447">"Weitere Informationen"</string>
-    <!-- no translation found for content_description_show_password (3283502010388521607) -->
-    <skip />
-    <!-- no translation found for content_description_hide_password (6841375971631767996) -->
-    <skip />
+    <string name="content_description_show_password" msgid="3283502010388521607">"Passwort einblenden"</string>
+    <string name="content_description_hide_password" msgid="6841375971631767996">"Passwort ausblenden"</string>
     <string name="passkey_creation_intro_title" msgid="4251037543787718844">"Mehr Sicherheit mit Passkeys"</string>
-    <string name="passkey_creation_intro_body_password" msgid="8825872426579958200">"Mit Passkeys musst du keine komplizierten Passwörter erstellen oder dir merken"</string>
-    <string name="passkey_creation_intro_body_fingerprint" msgid="7331338631826254055">"Passkeys sind verschlüsselte digitale Schlüssel, die du mithilfe deines Fingerabdrucks, Gesichts oder deiner Displaysperre erstellst"</string>
+    <string name="passkey_creation_intro_body_password" msgid="8825872426579958200">"Mit Passkeys musst du keine komplizierten Passwörter erstellen oder sie dir merken"</string>
+    <string name="passkey_creation_intro_body_fingerprint" msgid="7331338631826254055">"Passkeys sind verschlüsselte digitale Schlüssel, die du mithilfe deines Fingerabdrucks, der Gesichtserkennung oder deiner Displaysperre erstellst"</string>
     <string name="passkey_creation_intro_body_device" msgid="1203796455762131631">"Sie werden in einem Passwortmanager gespeichert, damit du dich auf anderen Geräten anmelden kannst"</string>
     <string name="more_about_passkeys_title" msgid="7797903098728837795">"Weitere Informationen zu Passkeys"</string>
     <string name="passwordless_technology_title" msgid="2497513482056606668">"Passwortlose Technologie"</string>
     <string name="passwordless_technology_detail" msgid="6853928846532955882">"Passkeys ermöglichen die Anmeldung, ohne dass Passwörter erforderlich sind. Du verwendest einfach die Fingerabdruck- oder Gesichtserkennung, eine PIN oder ein Muster, um deine Identität zu bestätigen und einen Passkey zu erstellen."</string>
     <string name="public_key_cryptography_title" msgid="6751970819265298039">"Asymmetrisches Kryptosystem"</string>
-    <string name="public_key_cryptography_detail" msgid="6937631710280562213">"Passkeys beruhen auf FIDO Alliance- (zu der auch Google und Apple gehören) und W3C-Standards. Anstelle von Nutzername und Passwort wird ein kryptografisches Paar aus privatem und öffentlichem Schlüssel für eine App oder Website erstellt. Der private Schlüssel wird sicher auf deinem Gerät oder im Passwortmanager gespeichert und bestätigt deine Identität. Der öffentliche Schlüssel wird mit dem App- oder Websiteserver geteilt. Stimmen beide überein, kannst du dich sofort registrieren und anmelden."</string>
+    <string name="public_key_cryptography_detail" msgid="6937631710280562213">"Passkeys beruhen auf FIDO Alliance- (zu der unter anderem auch Google, Apple und Microsoft gehören) und W3C-Standards. Anstelle von Nutzername und Passwort wird ein kryptografisches Paar aus privatem und öffentlichem Schlüssel für eine App oder Website erstellt. Der private Schlüssel wird sicher auf deinem Gerät oder im Passwortmanager gespeichert und bestätigt deine Identität. Der öffentliche Schlüssel wird mit dem App- oder Websiteserver geteilt. Stimmen beide überein, kannst du dich sofort registrieren und anmelden."</string>
     <string name="improved_account_security_title" msgid="1069841917893513424">"Verbesserte Kontosicherheit"</string>
     <string name="improved_account_security_detail" msgid="9123750251551844860">"Jeder Schlüssel ist ausschließlich mit der App oder Website verknüpft, für die er erstellt wurde. Du kannst dich also nicht aus Versehen bei einer betrügerischen App oder Website anmelden. Da auf Servern nur öffentliche Schlüssel verwaltet werden, wird das Hacking außerdem erheblich erschwert."</string>
     <string name="seamless_transition_title" msgid="5335622196351371961">"Nahtlose Umstellung"</string>
@@ -35,10 +49,13 @@
     <string name="sign_ins" msgid="4710739369149469208">"Anmeldungen"</string>
     <string name="sign_in_info" msgid="2627704710674232328">"Anmeldedaten"</string>
     <string name="save_credential_to_title" msgid="3172811692275634301">"<xliff:g id="CREDENTIALTYPES">%1$s</xliff:g> speichern unter"</string>
-    <string name="create_passkey_in_other_device_title" msgid="9195411122362461390">"Passkey auf einem anderen Gerät erstellen?"</string>
+    <string name="create_passkey_in_other_device_title" msgid="2360053098931886245">"Passkey auf einem anderen Gerät erstellen?"</string>
+    <string name="save_password_on_other_device_title" msgid="5829084591948321207">"Passwort auf einem anderen Gerät speichern?"</string>
+    <string name="save_sign_in_on_other_device_title" msgid="2827990118560134692">"Anmeldedaten auf einem anderen Gerät speichern?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> für alle Anmeldungen verwenden?"</string>
-    <string name="use_provider_for_all_description" msgid="8466427781848268490">"Mit diesem Passwortmanager werden deine Passwörter und Passkeys gespeichert, damit du dich problemlos anmelden kannst"</string>
+    <string name="use_provider_for_all_description" msgid="1998772715863958997">"Mit diesem Passwortmanager für <xliff:g id="USERNAME">%1$s</xliff:g> werden deine Passwörter und Passkeys gespeichert, damit du dich problemlos anmelden kannst"</string>
     <string name="set_as_default" msgid="4415328591568654603">"Als Standard festlegen"</string>
+    <string name="settings" msgid="6536394145760913145">"Einstellungen"</string>
     <string name="use_once" msgid="9027366575315399714">"Einmal verwenden"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> Passwörter • <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> Passkeys"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> Passwörter"</string>
@@ -49,21 +66,30 @@
     <string name="other_password_manager" msgid="565790221427004141">"Andere Passwortmanager"</string>
     <string name="close_sheet" msgid="1393792015338908262">"Tabellenblatt schließen"</string>
     <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Zurück zur vorherigen Seite"</string>
-    <string name="accessibility_close_button" msgid="2953807735590034688">"Den Vorschlag des Anmeldedaten-Managers unten auf dem Bildschirm schließen"</string>
+    <string name="accessibility_close_button" msgid="1163435587545377687">"Schließen"</string>
+    <string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"Schließen"</string>
     <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Gespeicherten Passkey für <xliff:g id="APP_NAME">%1$s</xliff:g> verwenden?"</string>
-    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Gespeicherte Anmeldedaten für <xliff:g id="APP_NAME">%1$s</xliff:g> verwenden?"</string>
-    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Gespeicherte Anmeldedaten für <xliff:g id="APP_NAME">%1$s</xliff:g> auswählen"</string>
+    <string name="get_dialog_title_use_password_for" msgid="625828023234318484">"Soll dein gespeichertes Passwort für <xliff:g id="APP_NAME">%1$s</xliff:g> verwendet werden?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"Anmeldedaten für <xliff:g id="APP_NAME">%1$s</xliff:g> verwenden?"</string>
+    <string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"Anmeldeoptionen für <xliff:g id="APP_NAME">%1$s</xliff:g> freischalten?"</string>
+    <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"Einen gespeicherten Passkey für <xliff:g id="APP_NAME">%1$s</xliff:g> auswählen"</string>
+    <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"Ein gespeichertes Passwort für <xliff:g id="APP_NAME">%1$s</xliff:g> auswählen"</string>
+    <string name="get_dialog_title_choose_saved_sign_in_for" msgid="2420298653461652728">"Gespeicherte Anmeldedaten für <xliff:g id="APP_NAME">%1$s</xliff:g> auswählen"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="3048870756117876514">"Anmeldedaten für <xliff:g id="APP_NAME">%1$s</xliff:g> auswählen"</string>
+    <string name="get_dialog_title_choose_option_for" msgid="4976380044745029107">"Option für <xliff:g id="APP_NAME">%1$s</xliff:g> auswählen?"</string>
+    <string name="get_dialog_title_use_info_on" msgid="8863708099535435146">"Diese Infos für <xliff:g id="APP_NAME">%1$s</xliff:g> verwenden?"</string>
     <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Andere Anmeldeoption auswählen"</string>
     <string name="snackbar_action" msgid="37373514216505085">"Optionen ansehen"</string>
     <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Weiter"</string>
     <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Anmeldeoptionen"</string>
+    <string name="button_label_view_more" msgid="3429098227286495651">"Mehr ansehen"</string>
     <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Für <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Gesperrte Passwortmanager"</string>
-    <!-- no translation found for locked_credential_entry_label_subtext_tap_to_unlock (6390367581393605009) -->
-    <skip />
-    <!-- no translation found for locked_credential_entry_label_subtext_no_sign_in (8131725029983174901) -->
-    <skip />
+    <string name="locked_credential_entry_label_subtext_tap_to_unlock" msgid="6390367581393605009">"Zum Entsperren tippen"</string>
+    <string name="locked_credential_entry_label_subtext_no_sign_in" msgid="8131725029983174901">"Keine Anmeldedaten"</string>
+    <string name="no_sign_in_info_in" msgid="2641118151920288356">"Keine Anmeldedaten in <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Anmeldedaten verwalten"</string>
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Von einem anderen Gerät"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Anderes Gerät verwenden"</string>
+    <string name="request_cancelled_by" msgid="3735222326886267820">"Anfrage abgebrochen von <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
 </resources>
diff --git a/packages/CredentialManager/res/values-el/strings.xml b/packages/CredentialManager/res/values-el/strings.xml
index 909ec97..4d97803 100644
--- a/packages/CredentialManager/res/values-el/strings.xml
+++ b/packages/CredentialManager/res/values-el/strings.xml
@@ -1,10 +1,26 @@
 <?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="4539824758261855508">"Credential Manager"</string>
     <string name="string_cancel" msgid="6369133483981306063">"Ακύρωση"</string>
     <string name="string_continue" msgid="1346732695941131882">"Συνέχεια"</string>
-    <string name="string_more_options" msgid="7990658711962795124">"Περισσότερες επιλογές"</string>
+    <string name="string_more_options" msgid="2763852250269945472">"Αποθ. με άλλο τρόπο"</string>
     <string name="string_learn_more" msgid="4541600451688392447">"Μάθετε περισσότερα"</string>
     <string name="content_description_show_password" msgid="3283502010388521607">"Εμφάνιση κωδικού πρόσβασης"</string>
     <string name="content_description_hide_password" msgid="6841375971631767996">"Απόκρυψη κωδικού πρόσβασης"</string>
@@ -33,10 +49,13 @@
     <string name="sign_ins" msgid="4710739369149469208">"στοιχεία σύνδεσης"</string>
     <string name="sign_in_info" msgid="2627704710674232328">"στοιχεία σύνδεσης"</string>
     <string name="save_credential_to_title" msgid="3172811692275634301">"Αποθήκευση <xliff:g id="CREDENTIALTYPES">%1$s</xliff:g> σε"</string>
-    <string name="create_passkey_in_other_device_title" msgid="9195411122362461390">"Δημιουργία κλειδιού πρόσβασης σε άλλη συσκευή;"</string>
+    <string name="create_passkey_in_other_device_title" msgid="2360053098931886245">"Θέλετε να δημιουργήσετε ένα κλειδί πρόσβασης σε κάποια άλλη συσκευή;"</string>
+    <string name="save_password_on_other_device_title" msgid="5829084591948321207">"Θέλετε να αποθηκεύσετε τον κωδικό πρόσβασης σε κάποια άλλη συσκευή;"</string>
+    <string name="save_sign_in_on_other_device_title" msgid="2827990118560134692">"Θέλετε να αποθηκεύσετε τα στοιχεία σύνδεσης σε κάποια άλλη συσκευή;"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"Να χρησιμοποιηθεί το <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> για όλες τις συνδέσεις σας;"</string>
-    <string name="use_provider_for_all_description" msgid="8466427781848268490">"Αυτός ο διαχειριστής κωδικών πρόσβασης θα αποθηκεύει τους κωδικούς πρόσβασης και τα κλειδιά πρόσβασης, για να συνδέεστε εύκολα."</string>
+    <string name="use_provider_for_all_description" msgid="1998772715863958997">"Αυτός ο διαχειριστής κωδικών πρόσβασης για τον χρήστη <xliff:g id="USERNAME">%1$s</xliff:g> θα αποθηκεύει τους κωδικούς πρόσβασης και τα κλειδιά πρόσβασης, για πιο εύκολη πρόσβαση"</string>
     <string name="set_as_default" msgid="4415328591568654603">"Ορισμός ως προεπιλογής"</string>
+    <string name="settings" msgid="6536394145760913145">"Ρυθμίσεις"</string>
     <string name="use_once" msgid="9027366575315399714">"Χρήση μία φορά"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> κωδικοί πρόσβασης • <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> κλειδιά πρόσβασης"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> κωδικοί πρόσβασης"</string>
@@ -47,21 +66,30 @@
     <string name="other_password_manager" msgid="565790221427004141">"Άλλοι διαχειριστές κωδικών πρόσβασης"</string>
     <string name="close_sheet" msgid="1393792015338908262">"Κλείσιμο φύλλου"</string>
     <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Επιστροφή στην προηγούμενη σελίδα"</string>
-    <string name="accessibility_close_button" msgid="2953807735590034688">"Κλείσιμο της πρότασης για ενέργεια από το Credential Manager που εμφανίζεται στο κάτω μέρος της οθόνης"</string>
+    <string name="accessibility_close_button" msgid="1163435587545377687">"Κλείσιμο"</string>
+    <string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"Παράβλεψη"</string>
     <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Να χρησιμοποιηθεί το αποθηκευμένο κλειδί πρόσβασης για την εφαρμογή <xliff:g id="APP_NAME">%1$s</xliff:g>;"</string>
-    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Να χρησιμοποιηθούν τα αποθηκευμένα στοιχεία σύνδεσης για την εφαρμογή <xliff:g id="APP_NAME">%1$s</xliff:g>;"</string>
-    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Επιλογή αποθηκευμένων στοιχείων σύνδεσης για την εφαρμογή <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_use_password_for" msgid="625828023234318484">"Χρήση του αποθηκευμένου κωδικού πρόσβασης για την εφαρμογή <xliff:g id="APP_NAME">%1$s</xliff:g>;"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"Χρήση της σύνδεσής σας για την εφαρμογή <xliff:g id="APP_NAME">%1$s</xliff:g>;"</string>
+    <string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"Ξεκλείδωμα των επιλογών σύνδεσης για την εφαρμογή <xliff:g id="APP_NAME">%1$s</xliff:g>;"</string>
+    <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"Επιλογή αποθηκευμένου κλειδιού πρόσβασης για την εφαρμογή <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"Επιλογή αποθηκευμένου κωδικού πρόσβασης για την εφαρμογή <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_saved_sign_in_for" msgid="2420298653461652728">"Επιλογή αποθηκευμένων στοιχείων σύνδεσης για την εφαρμογή <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="3048870756117876514">"Επιλέξτε μια σύνδεση για την εφαρμογή <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_option_for" msgid="4976380044745029107">"Επιλογή ενέργειας για την εφαρμογή <xliff:g id="APP_NAME">%1$s</xliff:g>;"</string>
+    <string name="get_dialog_title_use_info_on" msgid="8863708099535435146">"Να χρησιμοποιηθούν αυτές οι πληροφορίες στην εφαρμογή <xliff:g id="APP_NAME">%1$s</xliff:g>;"</string>
     <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Σύνδεση με άλλον τρόπο"</string>
     <string name="snackbar_action" msgid="37373514216505085">"Προβολή επιλογών"</string>
     <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Συνέχεια"</string>
     <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Επιλογές σύνδεσης"</string>
+    <string name="button_label_view_more" msgid="3429098227286495651">"Προβολή περισσότερων"</string>
     <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Για τον χρήστη <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Κλειδωμένοι διαχειριστές κωδικών πρόσβασης"</string>
-    <!-- no translation found for locked_credential_entry_label_subtext_tap_to_unlock (6390367581393605009) -->
-    <skip />
-    <!-- no translation found for locked_credential_entry_label_subtext_no_sign_in (8131725029983174901) -->
-    <skip />
+    <string name="locked_credential_entry_label_subtext_tap_to_unlock" msgid="6390367581393605009">"Πατήστε για ξεκλείδωμα"</string>
+    <string name="locked_credential_entry_label_subtext_no_sign_in" msgid="8131725029983174901">"Δεν υπάρχουν στοιχεία σύνδεσης"</string>
+    <string name="no_sign_in_info_in" msgid="2641118151920288356">"Δεν υπάρχουν στοιχεία σύνδεσης στο <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Διαχείριση στοιχείων σύνδεσης"</string>
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Από άλλη συσκευή"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Χρήση διαφορετικής συσκευής"</string>
+    <string name="request_cancelled_by" msgid="3735222326886267820">"Το αίτημα ακυρώθηκε από την εφαρμογή <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
 </resources>
diff --git a/packages/CredentialManager/res/values-en-rAU/strings.xml b/packages/CredentialManager/res/values-en-rAU/strings.xml
index 42f61b9..7b80db0 100644
--- a/packages/CredentialManager/res/values-en-rAU/strings.xml
+++ b/packages/CredentialManager/res/values-en-rAU/strings.xml
@@ -1,10 +1,26 @@
 <?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="4539824758261855508">"Credential manager"</string>
     <string name="string_cancel" msgid="6369133483981306063">"Cancel"</string>
     <string name="string_continue" msgid="1346732695941131882">"Continue"</string>
-    <string name="string_more_options" msgid="7990658711962795124">"More options"</string>
+    <string name="string_more_options" msgid="2763852250269945472">"Save another way"</string>
     <string name="string_learn_more" msgid="4541600451688392447">"Learn more"</string>
     <string name="content_description_show_password" msgid="3283502010388521607">"Show password"</string>
     <string name="content_description_hide_password" msgid="6841375971631767996">"Hide password"</string>
@@ -18,7 +34,7 @@
     <string name="public_key_cryptography_title" msgid="6751970819265298039">"Public key cryptography"</string>
     <string name="public_key_cryptography_detail" msgid="6937631710280562213">"Based on FIDO Alliance (which includes Google, Apple, Microsoft and more) and W3C standards, passkeys use cryptographic key pairs. Unlike the username and string of characters that we use for passwords, a private-public key pair is created for an app or website. The private key is safely stored on your device or password manager and it confirms your identity. The public key is shared with the app or website server. With corresponding keys, you can instantly register and sign in."</string>
     <string name="improved_account_security_title" msgid="1069841917893513424">"Improved account security"</string>
-    <string name="improved_account_security_detail" msgid="9123750251551844860">"Each key is exclusively linked with the app or website they were created for, so you can never sign in to a fraudulent app or website by mistake. Plus, with servers only keeping public keys, hacking is a lot harder."</string>
+    <string name="improved_account_security_detail" msgid="9123750251551844860">"Each key is exclusively linked with the app or website it was created for, so you can never sign in to a fraudulent app or website by mistake. Plus, with servers only keeping public keys, hacking is a lot harder."</string>
     <string name="seamless_transition_title" msgid="5335622196351371961">"Seamless transition"</string>
     <string name="seamless_transition_detail" msgid="4475509237171739843">"As we move towards a passwordless future, passwords will still be available alongside passkeys."</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"Choose where to save your <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
@@ -33,10 +49,13 @@
     <string name="sign_ins" msgid="4710739369149469208">"sign-ins"</string>
     <string name="sign_in_info" msgid="2627704710674232328">"sign-in info"</string>
     <string name="save_credential_to_title" msgid="3172811692275634301">"Save <xliff:g id="CREDENTIALTYPES">%1$s</xliff:g> to"</string>
-    <string name="create_passkey_in_other_device_title" msgid="9195411122362461390">"Create passkey on another device?"</string>
+    <string name="create_passkey_in_other_device_title" msgid="2360053098931886245">"Create passkey on another device?"</string>
+    <string name="save_password_on_other_device_title" msgid="5829084591948321207">"Save password on another device?"</string>
+    <string name="save_sign_in_on_other_device_title" msgid="2827990118560134692">"Save sign-in on another device?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"Use <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> for all your sign-ins?"</string>
-    <string name="use_provider_for_all_description" msgid="8466427781848268490">"This password manager will store your passwords and passkeys to help you easily sign in"</string>
+    <string name="use_provider_for_all_description" msgid="1998772715863958997">"This password manager for <xliff:g id="USERNAME">%1$s</xliff:g> will store your passwords and passkeys to help you easily sign in"</string>
     <string name="set_as_default" msgid="4415328591568654603">"Set as default"</string>
+    <string name="settings" msgid="6536394145760913145">"Settings"</string>
     <string name="use_once" msgid="9027366575315399714">"Use once"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> passwords • <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> passkeys"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> passwords"</string>
@@ -47,21 +66,30 @@
     <string name="other_password_manager" msgid="565790221427004141">"Other password managers"</string>
     <string name="close_sheet" msgid="1393792015338908262">"Close sheet"</string>
     <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Go back to the previous page"</string>
-    <string name="accessibility_close_button" msgid="2953807735590034688">"Close the Credential Manager action suggestion appearing at the bottom of the screen"</string>
+    <string name="accessibility_close_button" msgid="1163435587545377687">"Close"</string>
+    <string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"Dismiss"</string>
     <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Use your saved passkey for <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
-    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Use your saved sign-in for <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
-    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Choose a saved sign-in for <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_use_password_for" msgid="625828023234318484">"Use your saved password for <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"Use your sign-in for <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"Unlock sign-in options for <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"Choose a saved passkey for <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"Choose a saved password for <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_saved_sign_in_for" msgid="2420298653461652728">"Choose a saved sign-in for <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="3048870756117876514">"Choose a sign-in for <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_option_for" msgid="4976380044745029107">"Choose an option for <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_info_on" msgid="8863708099535435146">"Use this info for <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Sign in another way"</string>
     <string name="snackbar_action" msgid="37373514216505085">"View options"</string>
     <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Continue"</string>
     <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Sign-in options"</string>
+    <string name="button_label_view_more" msgid="3429098227286495651">"View more"</string>
     <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"For <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Locked password managers"</string>
-    <!-- no translation found for locked_credential_entry_label_subtext_tap_to_unlock (6390367581393605009) -->
-    <skip />
-    <!-- no translation found for locked_credential_entry_label_subtext_no_sign_in (8131725029983174901) -->
-    <skip />
+    <string name="locked_credential_entry_label_subtext_tap_to_unlock" msgid="6390367581393605009">"Tap to unlock"</string>
+    <string name="locked_credential_entry_label_subtext_no_sign_in" msgid="8131725029983174901">"No sign-in info"</string>
+    <string name="no_sign_in_info_in" msgid="2641118151920288356">"No sign-in info in <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Manage sign-ins"</string>
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"From another device"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Use a different device"</string>
+    <string name="request_cancelled_by" msgid="3735222326886267820">"Request cancelled by <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
 </resources>
diff --git a/packages/CredentialManager/res/values-en-rCA/strings.xml b/packages/CredentialManager/res/values-en-rCA/strings.xml
index 24d6bf2..b173616 100644
--- a/packages/CredentialManager/res/values-en-rCA/strings.xml
+++ b/packages/CredentialManager/res/values-en-rCA/strings.xml
@@ -1,10 +1,26 @@
 <?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="4539824758261855508">"Credential Manager"</string>
     <string name="string_cancel" msgid="6369133483981306063">"Cancel"</string>
     <string name="string_continue" msgid="1346732695941131882">"Continue"</string>
-    <string name="string_more_options" msgid="7990658711962795124">"More options"</string>
+    <string name="string_more_options" msgid="2763852250269945472">"Save another way"</string>
     <string name="string_learn_more" msgid="4541600451688392447">"Learn more"</string>
     <string name="content_description_show_password" msgid="3283502010388521607">"Show password"</string>
     <string name="content_description_hide_password" msgid="6841375971631767996">"Hide password"</string>
@@ -33,10 +49,13 @@
     <string name="sign_ins" msgid="4710739369149469208">"sign-ins"</string>
     <string name="sign_in_info" msgid="2627704710674232328">"sign-in info"</string>
     <string name="save_credential_to_title" msgid="3172811692275634301">"Save <xliff:g id="CREDENTIALTYPES">%1$s</xliff:g> to"</string>
-    <string name="create_passkey_in_other_device_title" msgid="9195411122362461390">"Create passkey in another device?"</string>
+    <string name="create_passkey_in_other_device_title" msgid="2360053098931886245">"Create passkey on another device?"</string>
+    <string name="save_password_on_other_device_title" msgid="5829084591948321207">"Save password on another device?"</string>
+    <string name="save_sign_in_on_other_device_title" msgid="2827990118560134692">"Save sign-in on another device?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"Use <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> for all your sign-ins?"</string>
-    <string name="use_provider_for_all_description" msgid="8466427781848268490">"This password manager will store your passwords and passkeys to help you easily sign in"</string>
+    <string name="use_provider_for_all_description" msgid="1998772715863958997">"This password manager for <xliff:g id="USERNAME">%1$s</xliff:g> will store your passwords and passkeys to help you easily sign in"</string>
     <string name="set_as_default" msgid="4415328591568654603">"Set as default"</string>
+    <string name="settings" msgid="6536394145760913145">"Settings"</string>
     <string name="use_once" msgid="9027366575315399714">"Use once"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> passwords • <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> passkeys"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> passwords"</string>
@@ -47,21 +66,30 @@
     <string name="other_password_manager" msgid="565790221427004141">"Other password managers"</string>
     <string name="close_sheet" msgid="1393792015338908262">"Close sheet"</string>
     <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Go back to the previous page"</string>
-    <string name="accessibility_close_button" msgid="2953807735590034688">"Close the Credential Manager action suggestion appearing at the bottom of the screen"</string>
+    <string name="accessibility_close_button" msgid="1163435587545377687">"Close"</string>
+    <string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"Dismiss"</string>
     <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Use your saved passkey for <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
-    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Use your saved sign-in for <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
-    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Choose a saved sign-in for <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_use_password_for" msgid="625828023234318484">"Use your saved password for <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"Use your sign-in for <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"Unlock sign-in options for <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"Choose a saved passkey for <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"Choose a saved password for <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_saved_sign_in_for" msgid="2420298653461652728">"Choose a saved sign-in for <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="3048870756117876514">"Choose a sign-in for <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_option_for" msgid="4976380044745029107">"Choose an option for <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_info_on" msgid="8863708099535435146">"Use this info on <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Sign in another way"</string>
     <string name="snackbar_action" msgid="37373514216505085">"View options"</string>
     <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Continue"</string>
     <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Sign-in options"</string>
+    <string name="button_label_view_more" msgid="3429098227286495651">"View more"</string>
     <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"For <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Locked password managers"</string>
-    <!-- no translation found for locked_credential_entry_label_subtext_tap_to_unlock (6390367581393605009) -->
-    <skip />
-    <!-- no translation found for locked_credential_entry_label_subtext_no_sign_in (8131725029983174901) -->
-    <skip />
+    <string name="locked_credential_entry_label_subtext_tap_to_unlock" msgid="6390367581393605009">"Tap to unlock"</string>
+    <string name="locked_credential_entry_label_subtext_no_sign_in" msgid="8131725029983174901">"No sign-in info"</string>
+    <string name="no_sign_in_info_in" msgid="2641118151920288356">"No sign-in info in <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Manage sign-ins"</string>
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"From another device"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Use a different device"</string>
+    <string name="request_cancelled_by" msgid="3735222326886267820">"Request cancelled by <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
 </resources>
diff --git a/packages/CredentialManager/res/values-en-rGB/strings.xml b/packages/CredentialManager/res/values-en-rGB/strings.xml
index 42f61b9..7b80db0 100644
--- a/packages/CredentialManager/res/values-en-rGB/strings.xml
+++ b/packages/CredentialManager/res/values-en-rGB/strings.xml
@@ -1,10 +1,26 @@
 <?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="4539824758261855508">"Credential manager"</string>
     <string name="string_cancel" msgid="6369133483981306063">"Cancel"</string>
     <string name="string_continue" msgid="1346732695941131882">"Continue"</string>
-    <string name="string_more_options" msgid="7990658711962795124">"More options"</string>
+    <string name="string_more_options" msgid="2763852250269945472">"Save another way"</string>
     <string name="string_learn_more" msgid="4541600451688392447">"Learn more"</string>
     <string name="content_description_show_password" msgid="3283502010388521607">"Show password"</string>
     <string name="content_description_hide_password" msgid="6841375971631767996">"Hide password"</string>
@@ -18,7 +34,7 @@
     <string name="public_key_cryptography_title" msgid="6751970819265298039">"Public key cryptography"</string>
     <string name="public_key_cryptography_detail" msgid="6937631710280562213">"Based on FIDO Alliance (which includes Google, Apple, Microsoft and more) and W3C standards, passkeys use cryptographic key pairs. Unlike the username and string of characters that we use for passwords, a private-public key pair is created for an app or website. The private key is safely stored on your device or password manager and it confirms your identity. The public key is shared with the app or website server. With corresponding keys, you can instantly register and sign in."</string>
     <string name="improved_account_security_title" msgid="1069841917893513424">"Improved account security"</string>
-    <string name="improved_account_security_detail" msgid="9123750251551844860">"Each key is exclusively linked with the app or website they were created for, so you can never sign in to a fraudulent app or website by mistake. Plus, with servers only keeping public keys, hacking is a lot harder."</string>
+    <string name="improved_account_security_detail" msgid="9123750251551844860">"Each key is exclusively linked with the app or website it was created for, so you can never sign in to a fraudulent app or website by mistake. Plus, with servers only keeping public keys, hacking is a lot harder."</string>
     <string name="seamless_transition_title" msgid="5335622196351371961">"Seamless transition"</string>
     <string name="seamless_transition_detail" msgid="4475509237171739843">"As we move towards a passwordless future, passwords will still be available alongside passkeys."</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"Choose where to save your <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
@@ -33,10 +49,13 @@
     <string name="sign_ins" msgid="4710739369149469208">"sign-ins"</string>
     <string name="sign_in_info" msgid="2627704710674232328">"sign-in info"</string>
     <string name="save_credential_to_title" msgid="3172811692275634301">"Save <xliff:g id="CREDENTIALTYPES">%1$s</xliff:g> to"</string>
-    <string name="create_passkey_in_other_device_title" msgid="9195411122362461390">"Create passkey on another device?"</string>
+    <string name="create_passkey_in_other_device_title" msgid="2360053098931886245">"Create passkey on another device?"</string>
+    <string name="save_password_on_other_device_title" msgid="5829084591948321207">"Save password on another device?"</string>
+    <string name="save_sign_in_on_other_device_title" msgid="2827990118560134692">"Save sign-in on another device?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"Use <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> for all your sign-ins?"</string>
-    <string name="use_provider_for_all_description" msgid="8466427781848268490">"This password manager will store your passwords and passkeys to help you easily sign in"</string>
+    <string name="use_provider_for_all_description" msgid="1998772715863958997">"This password manager for <xliff:g id="USERNAME">%1$s</xliff:g> will store your passwords and passkeys to help you easily sign in"</string>
     <string name="set_as_default" msgid="4415328591568654603">"Set as default"</string>
+    <string name="settings" msgid="6536394145760913145">"Settings"</string>
     <string name="use_once" msgid="9027366575315399714">"Use once"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> passwords • <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> passkeys"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> passwords"</string>
@@ -47,21 +66,30 @@
     <string name="other_password_manager" msgid="565790221427004141">"Other password managers"</string>
     <string name="close_sheet" msgid="1393792015338908262">"Close sheet"</string>
     <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Go back to the previous page"</string>
-    <string name="accessibility_close_button" msgid="2953807735590034688">"Close the Credential Manager action suggestion appearing at the bottom of the screen"</string>
+    <string name="accessibility_close_button" msgid="1163435587545377687">"Close"</string>
+    <string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"Dismiss"</string>
     <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Use your saved passkey for <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
-    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Use your saved sign-in for <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
-    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Choose a saved sign-in for <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_use_password_for" msgid="625828023234318484">"Use your saved password for <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"Use your sign-in for <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"Unlock sign-in options for <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"Choose a saved passkey for <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"Choose a saved password for <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_saved_sign_in_for" msgid="2420298653461652728">"Choose a saved sign-in for <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="3048870756117876514">"Choose a sign-in for <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_option_for" msgid="4976380044745029107">"Choose an option for <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_info_on" msgid="8863708099535435146">"Use this info for <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Sign in another way"</string>
     <string name="snackbar_action" msgid="37373514216505085">"View options"</string>
     <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Continue"</string>
     <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Sign-in options"</string>
+    <string name="button_label_view_more" msgid="3429098227286495651">"View more"</string>
     <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"For <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Locked password managers"</string>
-    <!-- no translation found for locked_credential_entry_label_subtext_tap_to_unlock (6390367581393605009) -->
-    <skip />
-    <!-- no translation found for locked_credential_entry_label_subtext_no_sign_in (8131725029983174901) -->
-    <skip />
+    <string name="locked_credential_entry_label_subtext_tap_to_unlock" msgid="6390367581393605009">"Tap to unlock"</string>
+    <string name="locked_credential_entry_label_subtext_no_sign_in" msgid="8131725029983174901">"No sign-in info"</string>
+    <string name="no_sign_in_info_in" msgid="2641118151920288356">"No sign-in info in <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Manage sign-ins"</string>
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"From another device"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Use a different device"</string>
+    <string name="request_cancelled_by" msgid="3735222326886267820">"Request cancelled by <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
 </resources>
diff --git a/packages/CredentialManager/res/values-en-rIN/strings.xml b/packages/CredentialManager/res/values-en-rIN/strings.xml
index 42f61b9..7b80db0 100644
--- a/packages/CredentialManager/res/values-en-rIN/strings.xml
+++ b/packages/CredentialManager/res/values-en-rIN/strings.xml
@@ -1,10 +1,26 @@
 <?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="4539824758261855508">"Credential manager"</string>
     <string name="string_cancel" msgid="6369133483981306063">"Cancel"</string>
     <string name="string_continue" msgid="1346732695941131882">"Continue"</string>
-    <string name="string_more_options" msgid="7990658711962795124">"More options"</string>
+    <string name="string_more_options" msgid="2763852250269945472">"Save another way"</string>
     <string name="string_learn_more" msgid="4541600451688392447">"Learn more"</string>
     <string name="content_description_show_password" msgid="3283502010388521607">"Show password"</string>
     <string name="content_description_hide_password" msgid="6841375971631767996">"Hide password"</string>
@@ -18,7 +34,7 @@
     <string name="public_key_cryptography_title" msgid="6751970819265298039">"Public key cryptography"</string>
     <string name="public_key_cryptography_detail" msgid="6937631710280562213">"Based on FIDO Alliance (which includes Google, Apple, Microsoft and more) and W3C standards, passkeys use cryptographic key pairs. Unlike the username and string of characters that we use for passwords, a private-public key pair is created for an app or website. The private key is safely stored on your device or password manager and it confirms your identity. The public key is shared with the app or website server. With corresponding keys, you can instantly register and sign in."</string>
     <string name="improved_account_security_title" msgid="1069841917893513424">"Improved account security"</string>
-    <string name="improved_account_security_detail" msgid="9123750251551844860">"Each key is exclusively linked with the app or website they were created for, so you can never sign in to a fraudulent app or website by mistake. Plus, with servers only keeping public keys, hacking is a lot harder."</string>
+    <string name="improved_account_security_detail" msgid="9123750251551844860">"Each key is exclusively linked with the app or website it was created for, so you can never sign in to a fraudulent app or website by mistake. Plus, with servers only keeping public keys, hacking is a lot harder."</string>
     <string name="seamless_transition_title" msgid="5335622196351371961">"Seamless transition"</string>
     <string name="seamless_transition_detail" msgid="4475509237171739843">"As we move towards a passwordless future, passwords will still be available alongside passkeys."</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"Choose where to save your <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
@@ -33,10 +49,13 @@
     <string name="sign_ins" msgid="4710739369149469208">"sign-ins"</string>
     <string name="sign_in_info" msgid="2627704710674232328">"sign-in info"</string>
     <string name="save_credential_to_title" msgid="3172811692275634301">"Save <xliff:g id="CREDENTIALTYPES">%1$s</xliff:g> to"</string>
-    <string name="create_passkey_in_other_device_title" msgid="9195411122362461390">"Create passkey on another device?"</string>
+    <string name="create_passkey_in_other_device_title" msgid="2360053098931886245">"Create passkey on another device?"</string>
+    <string name="save_password_on_other_device_title" msgid="5829084591948321207">"Save password on another device?"</string>
+    <string name="save_sign_in_on_other_device_title" msgid="2827990118560134692">"Save sign-in on another device?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"Use <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> for all your sign-ins?"</string>
-    <string name="use_provider_for_all_description" msgid="8466427781848268490">"This password manager will store your passwords and passkeys to help you easily sign in"</string>
+    <string name="use_provider_for_all_description" msgid="1998772715863958997">"This password manager for <xliff:g id="USERNAME">%1$s</xliff:g> will store your passwords and passkeys to help you easily sign in"</string>
     <string name="set_as_default" msgid="4415328591568654603">"Set as default"</string>
+    <string name="settings" msgid="6536394145760913145">"Settings"</string>
     <string name="use_once" msgid="9027366575315399714">"Use once"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> passwords • <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> passkeys"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> passwords"</string>
@@ -47,21 +66,30 @@
     <string name="other_password_manager" msgid="565790221427004141">"Other password managers"</string>
     <string name="close_sheet" msgid="1393792015338908262">"Close sheet"</string>
     <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Go back to the previous page"</string>
-    <string name="accessibility_close_button" msgid="2953807735590034688">"Close the Credential Manager action suggestion appearing at the bottom of the screen"</string>
+    <string name="accessibility_close_button" msgid="1163435587545377687">"Close"</string>
+    <string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"Dismiss"</string>
     <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Use your saved passkey for <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
-    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Use your saved sign-in for <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
-    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Choose a saved sign-in for <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_use_password_for" msgid="625828023234318484">"Use your saved password for <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"Use your sign-in for <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"Unlock sign-in options for <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"Choose a saved passkey for <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"Choose a saved password for <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_saved_sign_in_for" msgid="2420298653461652728">"Choose a saved sign-in for <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="3048870756117876514">"Choose a sign-in for <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_option_for" msgid="4976380044745029107">"Choose an option for <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_info_on" msgid="8863708099535435146">"Use this info for <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Sign in another way"</string>
     <string name="snackbar_action" msgid="37373514216505085">"View options"</string>
     <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Continue"</string>
     <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Sign-in options"</string>
+    <string name="button_label_view_more" msgid="3429098227286495651">"View more"</string>
     <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"For <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Locked password managers"</string>
-    <!-- no translation found for locked_credential_entry_label_subtext_tap_to_unlock (6390367581393605009) -->
-    <skip />
-    <!-- no translation found for locked_credential_entry_label_subtext_no_sign_in (8131725029983174901) -->
-    <skip />
+    <string name="locked_credential_entry_label_subtext_tap_to_unlock" msgid="6390367581393605009">"Tap to unlock"</string>
+    <string name="locked_credential_entry_label_subtext_no_sign_in" msgid="8131725029983174901">"No sign-in info"</string>
+    <string name="no_sign_in_info_in" msgid="2641118151920288356">"No sign-in info in <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Manage sign-ins"</string>
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"From another device"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Use a different device"</string>
+    <string name="request_cancelled_by" msgid="3735222326886267820">"Request cancelled by <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
 </resources>
diff --git a/packages/CredentialManager/res/values-en-rXC/strings.xml b/packages/CredentialManager/res/values-en-rXC/strings.xml
index f646b49..c3eeb04 100644
--- a/packages/CredentialManager/res/values-en-rXC/strings.xml
+++ b/packages/CredentialManager/res/values-en-rXC/strings.xml
@@ -1,10 +1,26 @@
 <?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="4539824758261855508">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‏‏‎‎‎‎‎‎‎‎‏‎‏‏‎‎‏‎‏‎‎‏‎‎‎‎‏‎‎‎‏‎‏‎‏‎‏‏‏‎‎‎‏‎‏‏‎‏‎‏‎‎‎‏‎‏‎‎‎Credential Manager‎‏‎‎‏‎"</string>
     <string name="string_cancel" msgid="6369133483981306063">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‎‎‎‏‏‎‎‎‏‏‏‎‏‏‎‏‎‏‎‎‏‎‏‎‎‎‏‎‎‏‎‏‏‏‏‏‎‎‎‎‎‎‎‏‏‏‏‎‎‎‏‏‎‎‏‏‏‏‎Cancel‎‏‎‎‏‎"</string>
     <string name="string_continue" msgid="1346732695941131882">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‎‏‎‏‎‏‏‎‎‎‎‏‎‎‎‏‏‏‎‎‏‎‎‎‎‎‎‎‏‏‎‏‏‎‎‏‏‏‏‏‎‎‏‏‎‏‎‏‏‏‎‎‏‏‎‏‎‏‎‎Continue‎‏‎‎‏‎"</string>
-    <string name="string_more_options" msgid="7990658711962795124">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‏‎‏‏‏‎‎‏‎‎‏‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‎‎‎‏‏‏‏‎‎‎‏‏‏‎‏‎‎‎More options‎‏‎‎‏‎"</string>
+    <string name="string_more_options" msgid="2763852250269945472">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‏‎‎‏‎‏‏‎‏‏‎‎‏‎‏‏‎‎‏‏‏‏‏‎‏‎‎‏‏‎‎‏‎‏‎‎‎‎‎‎‎‏‎‎‎‎‏‏‏‎‏‎‎‎‎‎‎‎‎Save another way‎‏‎‎‏‎"</string>
     <string name="string_learn_more" msgid="4541600451688392447">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‏‏‎‎‎‎‎‏‏‏‎‎‎‎‎‎‎‏‏‎‎‎‏‏‎‎‎‏‏‎‎‎‏‎‏‎‏‎‎‎‏‏‏‏‏‎‎‏‏‎‏‏‏‏‏‏‏‏‎Learn more‎‏‎‎‏‎"</string>
     <string name="content_description_show_password" msgid="3283502010388521607">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‏‎‏‏‎‎‏‎‎‎‏‎‏‎‏‎‏‏‏‏‎‏‎‏‏‏‎‏‎‎‎‎‏‎‎‎‏‏‏‏‏‎‏‎‎‏‎‎‎‏‎‏‎‎‎‎‏‏‏‎Show password‎‏‎‎‏‎"</string>
     <string name="content_description_hide_password" msgid="6841375971631767996">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‏‎‏‏‏‏‎‎‎‏‎‏‏‏‎‎‏‏‎‎‏‏‎‎‏‎‏‏‏‏‎‏‎‎‎‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‎‏‏‏‏‎‎‎Hide password‎‏‎‎‏‎"</string>
@@ -33,10 +49,13 @@
     <string name="sign_ins" msgid="4710739369149469208">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‎‏‎‏‎‏‏‏‏‏‏‏‏‎‏‎‎‎‎‏‏‏‏‏‏‏‎‏‏‎‎‏‏‏‏‎‎‏‏‎‏‎‎‎‎‏‎‎‏‎‎‎‎‏‏‎‎‎‎sign-ins‎‏‎‎‏‎"</string>
     <string name="sign_in_info" msgid="2627704710674232328">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‎‎‎‏‏‏‎‏‏‏‎‏‏‏‏‎‏‏‏‎‎‎‎‎‏‏‎‎‎‏‏‏‏‎‎‏‎‏‏‎‏‏‏‎‏‏‎‏‎‎‎‎‎‎‏‎‎‎‎sign-in info‎‏‎‎‏‎"</string>
     <string name="save_credential_to_title" msgid="3172811692275634301">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‏‎‎‎‎‎‎‏‎‎‎‎‎‎‏‎‏‏‏‎‏‏‎‏‏‎‎‏‎‎‎‎‎‎‏‎‏‎‎‏‎‏‏‏‎‎‏‎‎‎‎‎‏‏‏‏‏‎‏‎Save ‎‏‎‎‏‏‎<xliff:g id="CREDENTIALTYPES">%1$s</xliff:g>‎‏‎‎‏‏‏‎ to‎‏‎‎‏‎"</string>
-    <string name="create_passkey_in_other_device_title" msgid="9195411122362461390">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‎‏‏‏‎‎‏‎‏‎‏‎‎‏‏‎‏‏‎‎‏‎‎‏‎‎‏‎‎‎‎‎‎‏‎‏‏‏‎‎‏‎‏‏‎‎‏‏‎‎‏‏‏‎‎Create passkey in another device?‎‏‎‎‏‎"</string>
+    <string name="create_passkey_in_other_device_title" msgid="2360053098931886245">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‎‎‎‏‏‎‎‎‎‎‎‏‎‎‏‎‏‏‏‏‏‎‎‏‎‏‎‎‎‎‏‎‏‎‎‏‏‏‎‎‏‎‏‎‎‎‏‏‎‎‎‏‎‏‎‎‏‎‏‎Create passkey on another device?‎‏‎‎‏‎"</string>
+    <string name="save_password_on_other_device_title" msgid="5829084591948321207">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‎‎‏‏‏‎‎‏‎‏‎‎‎‏‎‎‎‏‏‎‎‎‏‏‎‏‎‏‏‏‎‏‎‎‏‏‎‎‏‎‎‎‎‏‎‏‎‎‎‏‏‎‏‏‎‏‏‏‎Save password on another device?‎‏‎‎‏‎"</string>
+    <string name="save_sign_in_on_other_device_title" msgid="2827990118560134692">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‏‏‎‎‏‏‏‏‏‏‎‎‎‎‏‎‏‎‎‎‎‎‎‏‏‏‏‎‎‎‏‏‎‏‎‎‎‏‎‏‏‏‎‏‏‏‎‏‏‎‎‎‏‎‎‏‎‎‎Save sign-in on another device?‎‏‎‎‏‎"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‏‎‎‏‎‎‏‏‎‏‎‎‎‎‎‏‎‏‏‎‎‏‏‏‎‎‎‎‎‎‎‏‎‎‏‎‏‏‏‎‏‏‏‎‎‏‎‏‏‏‏‎‎‏‎‏‎‏‎Use ‎‏‎‎‏‏‎<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎ for all your sign-ins?‎‏‎‎‏‎"</string>
-    <string name="use_provider_for_all_description" msgid="8466427781848268490">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‎‏‎‏‏‏‏‏‏‎‏‏‎‎‏‎‏‏‎‎‏‏‎‏‎‏‏‏‏‎‎‏‎‏‏‎‎‏‎‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‏‎‏‎‎This password manager will store your passwords and passkeys to help you easily sign in‎‏‎‎‏‎"</string>
+    <string name="use_provider_for_all_description" msgid="1998772715863958997">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‏‏‏‎‏‏‏‏‎‏‎‎‎‏‎‎‎‏‎‎‏‏‎‎‎‏‏‏‏‏‎‎‏‏‏‏‎‏‎‎‎‎‎‏‏‎‎‏‎‏‏‏‎‏‎‏‎‏‎This password manager for ‎‏‎‎‏‏‎<xliff:g id="USERNAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎ will store your passwords and passkeys to help you easily sign in‎‏‎‎‏‎"</string>
     <string name="set_as_default" msgid="4415328591568654603">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‎‏‎‏‎‎‎‏‏‎‎‏‏‎‎‏‎‏‏‏‏‏‎‏‎‏‏‏‏‎‏‎‏‏‎‎‎‏‎‎‏‏‎‏‎‏‎‎‎‏‎‎‎‎‏‎‏‏‎Set as default‎‏‎‎‏‎"</string>
+    <string name="settings" msgid="6536394145760913145">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‏‎‏‎‏‏‎‏‎‏‏‏‏‎‏‏‏‏‏‏‎‏‏‎‎‎‎‎‏‎‎‎‏‎‎‎‎‎‎‏‎‏‏‎‏‎‎‏‏‎‏‏‏‏‏‎‎‏‎Settings‎‏‎‎‏‎"</string>
     <string name="use_once" msgid="9027366575315399714">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‎‏‎‏‎‎‎‏‏‏‏‎‏‎‎‏‏‎‎‎‎‏‎‎‏‎‎‎‏‏‏‎‏‎‏‏‏‎‏‎‎‎‏‎‏‏‎‏‎‎‎‎‏‎‎‎‏‎‎Use once‎‏‎‎‏‎"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‎‎‎‎‏‎‏‎‎‎‎‏‎‏‎‎‏‎‎‎‏‏‏‏‏‎‏‎‏‏‏‏‏‎‏‏‏‎‎‎‏‎‏‎‎‎‏‎‎‎‏‎‎‎‎‏‏‏‎‎‏‎‎‏‏‎<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>‎‏‎‎‏‏‏‎ passwords • ‎‏‎‎‏‏‎<xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>‎‏‎‎‏‏‏‎ passkeys‎‏‎‎‏‎"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‏‎‏‎‏‎‎‏‏‎‎‎‏‏‎‎‏‎‎‏‎‏‏‎‏‎‎‎‏‎‎‏‎‏‎‏‎‏‎‏‎‏‎‏‏‏‏‎‏‎‎‏‏‎‎‏‎‏‎‎‏‎‎‏‏‎<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>‎‏‎‎‏‏‏‎ passwords‎‏‎‎‏‎"</string>
@@ -47,21 +66,30 @@
     <string name="other_password_manager" msgid="565790221427004141">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‏‏‏‏‏‏‎‏‏‎‏‎‎‎‎‏‎‏‏‏‎‎‏‏‎‏‎‏‎‏‏‎‏‏‎‏‏‎‎‎‏‎‏‏‏‏‎‏‎‎‏‎‏‏‏‎‏‏‎‏‎Other password managers‎‏‎‎‏‎"</string>
     <string name="close_sheet" msgid="1393792015338908262">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‎‏‏‎‏‎‏‎‏‏‏‏‎‏‏‏‏‏‎‎‏‏‏‎‏‎‎‎‎‎‎‏‏‎‏‎‎‎‎‏‏‎‏‎‎‏‎‏‎‏‎‎‏‏‎‎‏‏‎‎Close sheet‎‏‎‎‏‎"</string>
     <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‏‎‎‏‏‎‏‏‏‏‎‏‎‏‎‎‎‎‎‏‎‎‏‏‏‏‎‎‎‎‏‏‏‏‏‏‏‎‏‎‎‏‏‏‎‏‏‏‏‏‏‎‎‏‏‏‏‎‎‎Go back to the previous page‎‏‎‎‏‎"</string>
-    <string name="accessibility_close_button" msgid="2953807735590034688">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‎‎‏‏‏‏‏‏‏‎‎‎‎‎‏‎‎‎‎‏‏‏‏‏‎‎‎‏‎‎‏‏‏‎‏‎‏‎‎‏‎‏‎‎‎‏‏‏‎‏‎‎‎‎‎‎‎‎‎Close the Credential Manager action suggestion appearing at the bottom of the screen‎‏‎‎‏‎"</string>
+    <string name="accessibility_close_button" msgid="1163435587545377687">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‎‎‎‎‎‏‎‎‏‎‏‎‏‎‏‏‎‏‎‏‎‎‎‎‎‎‎‏‎‏‏‎‎‏‎‏‎‏‏‎‎‎‎‏‎‏‎‏‎‏‏‏‎‎‏‎‏‏‏‎Close‎‏‎‎‏‎"</string>
+    <string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‏‏‏‏‏‏‏‏‎‎‎‎‏‎‎‏‏‎‏‏‏‏‎‏‎‎‎‎‏‏‏‎‏‎‏‎‏‎‎‎‎‎‏‏‎‎‎‏‎‎‎‎‏‎‏‏‎‎‎‎Dismiss‎‏‎‎‏‎"</string>
     <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‏‎‏‎‎‎‏‏‎‎‏‏‏‎‎‎‏‎‏‎‏‏‏‎‏‎‎‎‎‎‏‏‎‎‏‏‎‎‏‏‏‏‏‎‎‏‏‏‏‎‎‎‎‎‎‏‏‏‎Use your saved passkey for ‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎?‎‏‎‎‏‎"</string>
-    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‎‏‎‏‎‏‎‎‎‏‎‏‎‏‎‏‏‏‎‎‎‎‎‏‏‏‏‏‎‎‏‏‏‎‎‎‎‎‏‎‏‏‏‏‎‏‏‎‎‎‏‏‏‏‎‏‎‎‎Use your saved sign-in for ‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎?‎‏‎‎‏‎"</string>
-    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‎‏‎‏‏‏‎‎‏‎‏‏‏‎‎‏‎‎‎‏‏‏‏‏‎‏‎‏‎‎‏‎‎‏‎‏‎‏‎‏‏‎‎‏‎‏‏‎‎‏‎‏‎‎‎‎‏‎‏‎Choose a saved sign-in for ‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
+    <string name="get_dialog_title_use_password_for" msgid="625828023234318484">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‎‎‏‎‏‎‏‏‏‏‎‏‏‎‎‎‏‏‎‏‎‎‎‏‎‏‎‏‏‏‎‏‏‏‎‏‎‏‏‏‎‏‏‏‎‏‎‎‎‎‏‎‎‏‎‏‎‎‎Use your saved password for ‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎?‎‏‎‎‏‎"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‏‎‏‏‏‏‎‏‏‎‏‏‎‏‎‎‏‎‎‎‏‎‏‎‎‏‎‎‎‎‎‏‎‏‎‏‎‏‎‏‏‎‎‏‎‎‎‎‎‏‏‎‎‎‏‎‎‏‎Use your sign-in for ‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎?‎‏‎‎‏‎"</string>
+    <string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‎‏‏‎‎‎‏‏‎‎‎‏‏‎‎‏‏‏‏‏‏‏‏‎‎‏‎‎‏‎‏‎‏‏‏‎‎‏‎‏‎‏‎‎‎‏‎‎‎‏‏‎‏‏‎‎‎‏‎Unlock sign-in options for ‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎?‎‏‎‎‏‎"</string>
+    <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‎‏‎‏‏‏‏‎‏‏‎‎‎‏‎‏‎‎‎‏‏‏‏‏‏‎‎‎‎‏‏‎‏‏‏‎‏‎‎‎‎‏‎‏‏‎‏‏‏‏‏‎‏‎‎Choose a saved passkey for ‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
+    <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‏‏‏‏‏‎‏‏‏‎‎‏‏‎‏‏‎‏‎‎‏‏‏‏‏‎‏‏‎‎‏‏‎‎‏‏‎‎‏‏‎‎‎‏‎‎‏‎‏‏‎‎‎‏‎‏‎‏‎Choose a saved password for ‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
+    <string name="get_dialog_title_choose_saved_sign_in_for" msgid="2420298653461652728">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‎‎‏‏‎‎‏‎‏‏‎‏‎‏‎‎‎‎‎‏‏‎‎‏‏‎‏‎‏‎‏‎‎‎‏‏‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‏‏‏‏‏‎‎‎‎Choose a saved sign-in for ‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="3048870756117876514">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‏‎‎‏‎‎‏‏‏‏‏‏‎‎‎‎‏‏‏‏‎‎‏‏‎‎‏‎‎‏‎‎‎‎‎‏‏‏‏‎‏‏‏‎‎‎‏‏‏‏‎‎‏‎‎‎‏‎‎Choose a sign-in for ‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
+    <string name="get_dialog_title_choose_option_for" msgid="4976380044745029107">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‏‎‏‎‎‎‎‏‏‏‏‏‎‏‎‎‏‏‏‎‏‎‎‏‎‎‎‏‏‏‎‎‎‏‎‎‏‎‏‏‏‏‎‏‎‏‏‎‏‎‏‏‏‏‏‎‎‏‏‎Choose an option for ‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎?‎‏‎‎‏‎"</string>
+    <string name="get_dialog_title_use_info_on" msgid="8863708099535435146">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‎‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‎‎‎‏‏‎‏‏‏‏‏‏‎‎‏‏‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‏‎‎‎‏‎‏‎‎Use this info on ‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎?‎‏‎‎‏‎"</string>
     <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‎‎‎‎‎‏‎‏‏‎‏‏‎‎‏‎‏‎‎‎‏‏‎‏‎‏‎‏‏‎‏‎‎‏‎‏‎‏‏‏‏‎‎‏‎‏‎‎‎‎‏‏‎‎‎‎‎‎‎Sign in another way‎‏‎‎‏‎"</string>
     <string name="snackbar_action" msgid="37373514216505085">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‎‎‏‎‎‎‎‏‎‎‏‏‎‎‎‏‏‏‎‎‎‎‎‎‏‏‎‏‎‏‏‏‏‎‏‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‏‏‏‎‏‎View options‎‏‎‎‏‎"</string>
     <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‎‏‎‏‏‏‎‏‎‏‏‎‎‎‎‎‏‎‎‏‎‎‏‎‎‏‏‎‏‏‏‎‎‎‏‎‏‏‏‎‏‏‏‏‏‏‏‎‏‏‎‏‎‏‏‏‏‎‎Continue‎‏‎‎‏‎"</string>
     <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‎‏‎‎‎‎‏‎‏‏‎‏‏‎‎‏‎‎‎‎‎‎‏‎‎‏‎‏‎‏‎‏‏‎‏‎‏‎‎‏‎‏‎‏‏‏‎‎‎‏‎‎‏‏‎‎‏‎‎Sign-in options‎‏‎‎‏‎"</string>
+    <string name="button_label_view_more" msgid="3429098227286495651">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‏‏‏‏‎‎‏‎‏‏‎‏‎‎‏‏‎‏‎‏‎‏‎‏‎‏‏‎‏‏‎‏‎‎‏‎‏‎‏‎‏‎‏‎‏‎‏‎‎‎‏‏‎‏‎‎‎‏‏‎View more‎‏‎‎‏‎"</string>
     <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‏‏‏‏‏‏‏‏‎‎‏‎‏‎‎‎‎‏‏‏‎‎‏‏‎‎‏‎‏‎‎‎‏‎‏‎‎‏‏‏‏‎‎‎‏‎‏‎‎‏‎‎‏‏‎‏‎‎‎‎For ‎‏‎‎‏‏‎<xliff:g id="USERNAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
     <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‎‏‏‏‎‏‎‏‏‎‎‎‎‎‎‏‏‏‏‏‎‎‎‏‎‏‎‏‏‎‏‏‏‎‏‏‏‏‏‎‎‎‏‎‏‏‏‏‎‎‎‎‏‏‎‎‏‎‎‎Locked password managers‎‏‎‎‏‎"</string>
-    <!-- no translation found for locked_credential_entry_label_subtext_tap_to_unlock (6390367581393605009) -->
-    <skip />
-    <!-- no translation found for locked_credential_entry_label_subtext_no_sign_in (8131725029983174901) -->
-    <skip />
+    <string name="locked_credential_entry_label_subtext_tap_to_unlock" msgid="6390367581393605009">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‎‎‏‎‏‎‏‏‏‏‎‎‏‎‎‏‎‏‎‏‏‏‎‏‎‏‎‎‏‎‏‎‎‏‎‎‏‎‎‎‏‏‎‏‎‏‎‎‎‏‏‎‎‏‎‎‎‏‎Tap to unlock‎‏‎‎‏‎"</string>
+    <string name="locked_credential_entry_label_subtext_no_sign_in" msgid="8131725029983174901">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‎‎‏‏‎‏‏‎‎‏‏‎‏‏‎‎‎‎‏‏‎‏‎‏‎‎‏‎‏‏‏‏‏‏‎‏‎‎‏‏‎‎‏‏‎‎‎‎‎‎‏‏‏‏‎‏‎‏‎No sign-in info‎‏‎‎‏‎"</string>
+    <string name="no_sign_in_info_in" msgid="2641118151920288356">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‎‎‏‎‏‎‎‏‏‏‎‎‏‎‎‎‏‎‏‏‏‏‎‏‏‏‎‏‎‎‎‏‏‏‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‏‎‎‏‎‎‎No sign-in info in ‎‏‎‎‏‏‎<xliff:g id="SOURCE">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
     <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‎‎‏‏‏‎‎‎‏‎‏‎‏‎‎‎‏‎‎‏‏‏‎‎‏‏‏‎‏‏‏‎‎‏‏‏‏‎‎‎‎‏‏‏‏‎‎‎‏‏‏‎‏‎‏‏‏‎‎Manage sign-ins‎‏‎‎‏‎"</string>
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‎‎‎‎‎‏‏‎‎‎‎‏‏‏‏‎‎‎‎‏‏‎‎‎‎‎‏‎‏‏‎‏‏‎‏‏‎‎‏‎‎‏‏‏‏‏‎‏‏‎‏‏‏‏‎‏‎‎‎‎From another device‎‏‎‎‏‎"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‎‏‏‏‎‏‎‎‎‏‏‏‎‏‏‏‎‎‎‏‏‏‏‎‏‎‎‏‏‏‏‎‎‎‏‎‏‏‏‎‏‏‎‏‎‏‎‏‎‎‏‎‏‏‏‏‎‏‎Use a different device‎‏‎‎‏‎"</string>
+    <string name="request_cancelled_by" msgid="3735222326886267820">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‏‏‏‏‎‏‎‏‏‎‎‎‏‎‏‏‎‎‏‏‏‎‏‎‏‎‏‎‎‎‏‏‏‏‎‎‏‎‏‎‎‏‏‏‏‏‏‎‏‏‏‎‏‎‏‏‎‎‎Request cancelled by ‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
 </resources>
diff --git a/packages/CredentialManager/res/values-es-rUS/strings.xml b/packages/CredentialManager/res/values-es-rUS/strings.xml
index ec00eb6..f6a5dcb 100644
--- a/packages/CredentialManager/res/values-es-rUS/strings.xml
+++ b/packages/CredentialManager/res/values-es-rUS/strings.xml
@@ -1,24 +1,38 @@
 <?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.
+   -->
+
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="4539824758261855508">"Credential Manager"</string>
     <string name="string_cancel" msgid="6369133483981306063">"Cancelar"</string>
     <string name="string_continue" msgid="1346732695941131882">"Continuar"</string>
-    <string name="string_more_options" msgid="7990658711962795124">"Más opciones"</string>
+    <string name="string_more_options" msgid="2763852250269945472">"Guardar otra forma"</string>
     <string name="string_learn_more" msgid="4541600451688392447">"Más información"</string>
-    <!-- no translation found for content_description_show_password (3283502010388521607) -->
-    <skip />
-    <!-- no translation found for content_description_hide_password (6841375971631767996) -->
-    <skip />
+    <string name="content_description_show_password" msgid="3283502010388521607">"Mostrar contraseña"</string>
+    <string name="content_description_hide_password" msgid="6841375971631767996">"Ocultar contraseña"</string>
     <string name="passkey_creation_intro_title" msgid="4251037543787718844">"Más seguridad con llaves de acceso"</string>
     <string name="passkey_creation_intro_body_password" msgid="8825872426579958200">"Con las llaves de acceso, no es necesario crear ni recordar contraseñas complejas"</string>
     <string name="passkey_creation_intro_body_fingerprint" msgid="7331338631826254055">"Las llaves de acceso son claves digitales encriptadas que puedes crear usando tu huella dactilar, rostro o bloqueo de pantalla"</string>
     <string name="passkey_creation_intro_body_device" msgid="1203796455762131631">"Se guardan en un administrador de contraseñas para que puedas acceder en otros dispositivos"</string>
     <string name="more_about_passkeys_title" msgid="7797903098728837795">"Más información sobre las llaves de acceso"</string>
     <string name="passwordless_technology_title" msgid="2497513482056606668">"Tecnología sin contraseñas"</string>
-    <string name="passwordless_technology_detail" msgid="6853928846532955882">"Las llaves de acceso permiten acceder sin depender de contraseñas. Solo tienes que usar tu huella dactilar, reconocimiento facial, PIN o patrón de deslizamiento para verificar tu identidad y crear una clave de acceso."</string>
+    <string name="passwordless_technology_detail" msgid="6853928846532955882">"Las llaves de acceso permiten acceder sin depender de contraseñas. Solo tienes que usar tu huella dactilar, reconocimiento facial, PIN o patrón de deslizamiento para verificar tu identidad y crear una llave de acceso."</string>
     <string name="public_key_cryptography_title" msgid="6751970819265298039">"Criptografía de claves públicas"</string>
-    <string name="public_key_cryptography_detail" msgid="6937631710280562213">"Basadas en la Alianza FIDO (Google, Apple, Microsoft, etc.) y en estándares del W3C, las llaves de acceso usan pares de claves criptográficas. A diferencia de la cadena de caracteres de las contraseñas, para una app o sitio web se crean claves privadas y públicas. La clave privada se guarda en tu dispositivo/administrador de contraseñas, y confirma tu identidad. La clave pública se comparte con el servidor de la app o sitio web. Las claves adecuadas te permiten registrarte y acceder al instante."</string>
+    <string name="public_key_cryptography_detail" msgid="6937631710280562213">"Basadas en la Alianza FIDO (que incluye a Google, Apple, Microsoft, etc.) y en estándares del W3C, las llaves de acceso usan pares de claves criptográficas. A diferencia del nombre de usuario y la cadena de caracteres de las contraseñas, para una app o sitio web se crean claves privadas y públicas. La clave privada se guarda en tu dispositivo/administrador de contraseñas, y confirma tu identidad. La clave pública se comparte con el servidor de la app o sitio web. Las claves adecuadas te permiten registrarte y acceder al instante."</string>
     <string name="improved_account_security_title" msgid="1069841917893513424">"Mayor seguridad para las cuentas"</string>
     <string name="improved_account_security_detail" msgid="9123750251551844860">"Cada llave está vinculada exclusivamente con el sitio web o la app para la que fue creada, por lo que nunca podrás acceder por error a una app o sitio web fraudulentos. Además, como los servidores solo guardan claves públicas, hackearlas es mucho más difícil."</string>
     <string name="seamless_transition_title" msgid="5335622196351371961">"Transición fluida"</string>
@@ -35,10 +49,13 @@
     <string name="sign_ins" msgid="4710739369149469208">"accesos"</string>
     <string name="sign_in_info" msgid="2627704710674232328">"información de acceso"</string>
     <string name="save_credential_to_title" msgid="3172811692275634301">"Guardar <xliff:g id="CREDENTIALTYPES">%1$s</xliff:g> en"</string>
-    <string name="create_passkey_in_other_device_title" msgid="9195411122362461390">"¿Quieres crear una llave de acceso en otro dispositivo?"</string>
+    <string name="create_passkey_in_other_device_title" msgid="2360053098931886245">"¿Quieres crear una llave de acceso en otro dispositivo?"</string>
+    <string name="save_password_on_other_device_title" msgid="5829084591948321207">"¿Quieres guardar la contraseña en otro dispositivo?"</string>
+    <string name="save_sign_in_on_other_device_title" msgid="2827990118560134692">"¿Quieres guardar las credenciales de acceso en otro dispositivo?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"¿Quieres usar <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> para todos tus accesos?"</string>
-    <string name="use_provider_for_all_description" msgid="8466427781848268490">"Este administrador de contraseñas almacenará tus contraseñas y llaves de acceso para ayudarte a acceder fácilmente"</string>
+    <string name="use_provider_for_all_description" msgid="1998772715863958997">"Este administrador de contraseñas para <xliff:g id="USERNAME">%1$s</xliff:g> almacenará tus contraseñas y llaves de acceso para ayudarte a acceder fácilmente"</string>
     <string name="set_as_default" msgid="4415328591568654603">"Establecer como predeterminado"</string>
+    <string name="settings" msgid="6536394145760913145">"Configuración"</string>
     <string name="use_once" msgid="9027366575315399714">"Usar una vez"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> contraseñas • <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> llaves de acceso"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> contraseñas"</string>
@@ -49,21 +66,30 @@
     <string name="other_password_manager" msgid="565790221427004141">"Otros administradores de contraseñas"</string>
     <string name="close_sheet" msgid="1393792015338908262">"Cerrar hoja"</string>
     <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Volver a la página anterior"</string>
-    <string name="accessibility_close_button" msgid="2953807735590034688">"Cierra la sugerencia de acción del Administrador de credenciales que aparece en la parte inferior de la pantalla"</string>
+    <string name="accessibility_close_button" msgid="1163435587545377687">"Cerrar"</string>
+    <string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"Descartar"</string>
     <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"¿Quieres usar tu llave de acceso guardada para <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
-    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"¿Quieres usar tu acceso guardado para <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
-    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Elige un acceso guardado para <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_use_password_for" msgid="625828023234318484">"¿Quieres usar la contraseña guardada para <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"¿Quieres usar tu acceso para <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"¿Quieres desbloquear las opciones de acceso para <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"Elige una llave de acceso guardada para <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"Elige una contraseña guardada para <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_saved_sign_in_for" msgid="2420298653461652728">"Elige un acceso guardado para <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="3048870756117876514">"Elige un acceso para <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_option_for" msgid="4976380044745029107">"¿Quieres una opción para <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_info_on" msgid="8863708099535435146">"¿Quieres usar esta información en <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Acceder de otra forma"</string>
     <string name="snackbar_action" msgid="37373514216505085">"Ver opciones"</string>
     <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Continuar"</string>
     <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Opciones de acceso"</string>
+    <string name="button_label_view_more" msgid="3429098227286495651">"Ver más"</string>
     <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Para <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Administradores de contraseñas bloqueados"</string>
-    <!-- no translation found for locked_credential_entry_label_subtext_tap_to_unlock (6390367581393605009) -->
-    <skip />
-    <!-- no translation found for locked_credential_entry_label_subtext_no_sign_in (8131725029983174901) -->
-    <skip />
+    <string name="locked_credential_entry_label_subtext_tap_to_unlock" msgid="6390367581393605009">"Presiona para desbloquear"</string>
+    <string name="locked_credential_entry_label_subtext_no_sign_in" msgid="8131725029983174901">"No hay información de acceso"</string>
+    <string name="no_sign_in_info_in" msgid="2641118151920288356">"No hay información de acceso en <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Administrar accesos"</string>
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Desde otro dispositivo"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Usar otra voz"</string>
+    <string name="request_cancelled_by" msgid="3735222326886267820">"<xliff:g id="APP_NAME">%1$s</xliff:g> canceló la solicitud"</string>
 </resources>
diff --git a/packages/CredentialManager/res/values-es/strings.xml b/packages/CredentialManager/res/values-es/strings.xml
index 94eaffd..fb0cbf9 100644
--- a/packages/CredentialManager/res/values-es/strings.xml
+++ b/packages/CredentialManager/res/values-es/strings.xml
@@ -1,20 +1,34 @@
 <?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.
+   -->
+
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="4539824758261855508">"Gestor de credenciales"</string>
     <string name="string_cancel" msgid="6369133483981306063">"Cancelar"</string>
     <string name="string_continue" msgid="1346732695941131882">"Continuar"</string>
-    <string name="string_more_options" msgid="7990658711962795124">"Más opciones"</string>
+    <string name="string_more_options" msgid="2763852250269945472">"Guardar de otra forma"</string>
     <string name="string_learn_more" msgid="4541600451688392447">"Más información"</string>
-    <!-- no translation found for content_description_show_password (3283502010388521607) -->
-    <skip />
-    <!-- no translation found for content_description_hide_password (6841375971631767996) -->
-    <skip />
+    <string name="content_description_show_password" msgid="3283502010388521607">"Mostrar contraseña"</string>
+    <string name="content_description_hide_password" msgid="6841375971631767996">"Ocultar contraseña"</string>
     <string name="passkey_creation_intro_title" msgid="4251037543787718844">"Más seguridad con las llaves de acceso"</string>
     <string name="passkey_creation_intro_body_password" msgid="8825872426579958200">"Con las llaves de acceso, no tienes que crear ni recordar contraseñas complicadas"</string>
     <string name="passkey_creation_intro_body_fingerprint" msgid="7331338631826254055">"Las llaves de acceso son claves digitales cifradas que puedes crear con tu huella digital, cara o bloqueo de pantalla"</string>
     <string name="passkey_creation_intro_body_device" msgid="1203796455762131631">"Se guardan en un gestor de contraseñas para que puedas iniciar sesión en otros dispositivos"</string>
-    <string name="more_about_passkeys_title" msgid="7797903098728837795">"Más información sobre llaves de acceso"</string>
+    <string name="more_about_passkeys_title" msgid="7797903098728837795">"Más información sobre las llaves de acceso"</string>
     <string name="passwordless_technology_title" msgid="2497513482056606668">"Tecnología sin contraseñas"</string>
     <string name="passwordless_technology_detail" msgid="6853928846532955882">"Las llaves de acceso te permiten iniciar sesión sin necesidad de contraseñas. Solo necesitas usar la huella digital, el reconocimiento facial, el PIN o patrón para verificar tu identidad y crear una llave de acceso."</string>
     <string name="public_key_cryptography_title" msgid="6751970819265298039">"Criptografía de claves públicas"</string>
@@ -35,35 +49,47 @@
     <string name="sign_ins" msgid="4710739369149469208">"inicios de sesión"</string>
     <string name="sign_in_info" msgid="2627704710674232328">"información de inicio de sesión"</string>
     <string name="save_credential_to_title" msgid="3172811692275634301">"Guardar <xliff:g id="CREDENTIALTYPES">%1$s</xliff:g> en"</string>
-    <string name="create_passkey_in_other_device_title" msgid="9195411122362461390">"¿Crear llave de acceso en otro dispositivo?"</string>
+    <string name="create_passkey_in_other_device_title" msgid="2360053098931886245">"¿Crear llave de acceso en otro dispositivo?"</string>
+    <string name="save_password_on_other_device_title" msgid="5829084591948321207">"¿Guardar contraseña en otro dispositivo?"</string>
+    <string name="save_sign_in_on_other_device_title" msgid="2827990118560134692">"¿Guardar inicio de sesión en otro dispositivo?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"¿Usar <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> para todos tus inicios de sesión?"</string>
-    <string name="use_provider_for_all_description" msgid="8466427781848268490">"Este gestor de contraseñas almacenará tus contraseñas y llaves de acceso para que puedas iniciar sesión fácilmente"</string>
+    <string name="use_provider_for_all_description" msgid="1998772715863958997">"Este gestor de contraseñas de <xliff:g id="USERNAME">%1$s</xliff:g> almacenará tus contraseñas y llaves de acceso para que puedas iniciar sesión fácilmente"</string>
     <string name="set_as_default" msgid="4415328591568654603">"Fijar como predeterminado"</string>
+    <string name="settings" msgid="6536394145760913145">"Ajustes"</string>
     <string name="use_once" msgid="9027366575315399714">"Usar una vez"</string>
-    <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> contraseñas • <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> llaves de acceso"</string>
+    <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"Contraseñas: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> • Llaves de acceso: <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> contraseñas"</string>
-    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> llaves de acceso"</string>
+    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"Llaves de acceso: <xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g>"</string>
     <string name="more_options_usage_credentials" msgid="1785697001787193984">"<xliff:g id="TOTALCREDENTIALSNUMBER">%1$s</xliff:g> credenciales"</string>
     <string name="passkey_before_subtitle" msgid="2448119456208647444">"Llave de acceso"</string>
     <string name="another_device" msgid="5147276802037801217">"Otro dispositivo"</string>
     <string name="other_password_manager" msgid="565790221427004141">"Otros gestores de contraseñas"</string>
     <string name="close_sheet" msgid="1393792015338908262">"Cerrar hoja"</string>
     <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Volver a la página anterior"</string>
-    <string name="accessibility_close_button" msgid="2953807735590034688">"Cierra la sugerencia de acción del Gestor de credenciales de la parte inferior de la pantalla"</string>
+    <string name="accessibility_close_button" msgid="1163435587545377687">"Cerrar"</string>
+    <string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"Cerrar"</string>
     <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"¿Usar la llave de acceso guardada para <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
-    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"¿Usar el inicio de sesión guardado para <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
-    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Elige un inicio de sesión guardado para <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_use_password_for" msgid="625828023234318484">"¿Usar la contraseña que tienes guardada para <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"¿Usar tu inicio de sesión en <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"¿Desbloquear las opciones de inicio de sesión de <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"Elige una llave de acceso guardada para <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"Elige una contraseña guardada para <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_saved_sign_in_for" msgid="2420298653461652728">"Elige un inicio de sesión guardado para <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="3048870756117876514">"Elige un inicio de sesión para <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_option_for" msgid="4976380044745029107">"¿Elegir una opción para <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_info_on" msgid="8863708099535435146">"¿Usar esta información en <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Iniciar sesión de otra manera"</string>
     <string name="snackbar_action" msgid="37373514216505085">"Ver opciones"</string>
     <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Continuar"</string>
     <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Opciones de inicio de sesión"</string>
+    <string name="button_label_view_more" msgid="3429098227286495651">"Ver más"</string>
     <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Para <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Gestores de contraseñas bloqueados"</string>
-    <!-- no translation found for locked_credential_entry_label_subtext_tap_to_unlock (6390367581393605009) -->
-    <skip />
-    <!-- no translation found for locked_credential_entry_label_subtext_no_sign_in (8131725029983174901) -->
-    <skip />
+    <string name="locked_credential_entry_label_subtext_tap_to_unlock" msgid="6390367581393605009">"Tocar para desbloquear"</string>
+    <string name="locked_credential_entry_label_subtext_no_sign_in" msgid="8131725029983174901">"No hay información de inicio de sesión"</string>
+    <string name="no_sign_in_info_in" msgid="2641118151920288356">"No hay información de inicio de sesión en <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Gestionar inicios de sesión"</string>
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"De otro dispositivo"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Usar otro dispositivo"</string>
+    <string name="request_cancelled_by" msgid="3735222326886267820">"<xliff:g id="APP_NAME">%1$s</xliff:g> ha cancelado la solicitud"</string>
 </resources>
diff --git a/packages/CredentialManager/res/values-et/strings.xml b/packages/CredentialManager/res/values-et/strings.xml
index eb50711..97dbe4d 100644
--- a/packages/CredentialManager/res/values-et/strings.xml
+++ b/packages/CredentialManager/res/values-et/strings.xml
@@ -1,15 +1,29 @@
 <?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.
+   -->
+
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="4539824758261855508">"Mandaatide haldur"</string>
     <string name="string_cancel" msgid="6369133483981306063">"Tühista"</string>
     <string name="string_continue" msgid="1346732695941131882">"Jätka"</string>
-    <string name="string_more_options" msgid="7990658711962795124">"Rohkem valikuid"</string>
+    <string name="string_more_options" msgid="2763852250269945472">"Salvesta muul viisil"</string>
     <string name="string_learn_more" msgid="4541600451688392447">"Lisateave"</string>
-    <!-- no translation found for content_description_show_password (3283502010388521607) -->
-    <skip />
-    <!-- no translation found for content_description_hide_password (6841375971631767996) -->
-    <skip />
+    <string name="content_description_show_password" msgid="3283502010388521607">"Kuva parool"</string>
+    <string name="content_description_hide_password" msgid="6841375971631767996">"Peida parool"</string>
     <string name="passkey_creation_intro_title" msgid="4251037543787718844">"Pääsuvõtmed suurendavad turvalisust"</string>
     <string name="passkey_creation_intro_body_password" msgid="8825872426579958200">"Pääsuvõtmetega ei pea te looma ega meelde jätma keerukaid paroole"</string>
     <string name="passkey_creation_intro_body_fingerprint" msgid="7331338631826254055">"Pääsuvõtmed on digitaalsed krüpteeritud võtmed, mille loote sõrmejälje, näo või ekraanilukuga"</string>
@@ -28,42 +42,54 @@
     <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"Kas luua rakenduse <xliff:g id="APPNAME">%1$s</xliff:g> jaoks pääsuvõti?"</string>
     <string name="choose_create_option_password_title" msgid="7097275038523578687">"Kas salvestada rakenduse <xliff:g id="APPNAME">%1$s</xliff:g> jaoks parool?"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Kas salvestada rakenduse <xliff:g id="APPNAME">%1$s</xliff:g> jaoks sisselogimisandmed?"</string>
-    <string name="passkey" msgid="632353688396759522">"pääsukood"</string>
+    <string name="passkey" msgid="632353688396759522">"pääsuvõti"</string>
     <string name="password" msgid="6738570945182936667">"parool"</string>
     <string name="passkeys" msgid="5733880786866559847">"pääsuvõtmed"</string>
     <string name="passwords" msgid="5419394230391253816">"paroolid"</string>
     <string name="sign_ins" msgid="4710739369149469208">"sisselogimisandmed"</string>
     <string name="sign_in_info" msgid="2627704710674232328">"sisselogimisteave"</string>
-    <string name="save_credential_to_title" msgid="3172811692275634301">"Salvestage <xliff:g id="CREDENTIALTYPES">%1$s</xliff:g>:"</string>
-    <string name="create_passkey_in_other_device_title" msgid="9195411122362461390">"Kas luua pääsuvõti muus seadmes?"</string>
+    <string name="save_credential_to_title" msgid="3172811692275634301">"Salvesta <xliff:g id="CREDENTIALTYPES">%1$s</xliff:g>:"</string>
+    <string name="create_passkey_in_other_device_title" msgid="2360053098931886245">"Kas luua pääsuvõti teises seadmes?"</string>
+    <string name="save_password_on_other_device_title" msgid="5829084591948321207">"Kas salvestada parool teises seadmes?"</string>
+    <string name="save_sign_in_on_other_device_title" msgid="2827990118560134692">"Kas salvestada sisselogimisandmed teises seadmes?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"Kas kasutada teenust <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> kõigi teie sisselogimisandmete puhul?"</string>
-    <string name="use_provider_for_all_description" msgid="8466427781848268490">"See paroolihaldur salvestab teie paroolid ja pääsuvõtmed, et aidata teil hõlpsalt sisse logida"</string>
+    <string name="use_provider_for_all_description" msgid="1998772715863958997">"Konto <xliff:g id="USERNAME">%1$s</xliff:g> paroolihaldur salvestab teie paroolid ja pääsuvõtmed, et aidata teil hõlpsalt sisse logida"</string>
     <string name="set_as_default" msgid="4415328591568654603">"Määra vaikeseadeks"</string>
+    <string name="settings" msgid="6536394145760913145">"Seaded"</string>
     <string name="use_once" msgid="9027366575315399714">"Kasuta ühe korra"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> parooli • <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> pääsuvõtit"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> parooli"</string>
     <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> pääsuvõtit"</string>
     <string name="more_options_usage_credentials" msgid="1785697001787193984">"<xliff:g id="TOTALCREDENTIALSNUMBER">%1$s</xliff:g> mandaati"</string>
-    <string name="passkey_before_subtitle" msgid="2448119456208647444">"Pääsukood"</string>
+    <string name="passkey_before_subtitle" msgid="2448119456208647444">"Pääsuvõti"</string>
     <string name="another_device" msgid="5147276802037801217">"Teine seade"</string>
     <string name="other_password_manager" msgid="565790221427004141">"Muud paroolihaldurid"</string>
     <string name="close_sheet" msgid="1393792015338908262">"Sule leht"</string>
     <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Minge tagasi eelmisele lehele"</string>
-    <string name="accessibility_close_button" msgid="2953807735590034688">"Ekraanikuva allosas kuvatud mandaatide halduri toimingusoovituse sulgemine"</string>
+    <string name="accessibility_close_button" msgid="1163435587545377687">"Sule"</string>
+    <string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"Loobu"</string>
     <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Kas kasutada rakenduse <xliff:g id="APP_NAME">%1$s</xliff:g> jaoks salvestatud pääsuvõtit?"</string>
-    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Kas kasutada rakenduse <xliff:g id="APP_NAME">%1$s</xliff:g> jaoks salvestatud sisselogimisandmeid?"</string>
-    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Valige rakenduse <xliff:g id="APP_NAME">%1$s</xliff:g> jaoks salvestatud sisselogimisandmed"</string>
+    <string name="get_dialog_title_use_password_for" msgid="625828023234318484">"Kas kasutada rakenduse <xliff:g id="APP_NAME">%1$s</xliff:g> puhul salvestatud parooli?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"Kas soovite rakendusse <xliff:g id="APP_NAME">%1$s</xliff:g> sisselogimiseks kasutada oma mandaati?"</string>
+    <string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"Kas avada rakenduse <xliff:g id="APP_NAME">%1$s</xliff:g> jaoks sisselogimisvalikud?"</string>
+    <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"Valige rakenduse <xliff:g id="APP_NAME">%1$s</xliff:g> jaoks salvestatud pääsuvõti"</string>
+    <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"Valige rakenduse <xliff:g id="APP_NAME">%1$s</xliff:g> jaoks salvestatud parool"</string>
+    <string name="get_dialog_title_choose_saved_sign_in_for" msgid="2420298653461652728">"Valige rakenduse <xliff:g id="APP_NAME">%1$s</xliff:g> jaoks salvestatud sisselogimisandmed"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="3048870756117876514">"Valige rakendusse <xliff:g id="APP_NAME">%1$s</xliff:g> sisselogimiseks mandaat"</string>
+    <string name="get_dialog_title_choose_option_for" msgid="4976380044745029107">"Kas teha valik rakendusele <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_info_on" msgid="8863708099535435146">"Kas soovite kasutada seda teavet rakenduses <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Logige sisse muul viisil"</string>
     <string name="snackbar_action" msgid="37373514216505085">"Kuva valikud"</string>
     <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Jätka"</string>
     <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Sisselogimise valikud"</string>
+    <string name="button_label_view_more" msgid="3429098227286495651">"Kuva rohkem"</string>
     <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Kasutajale <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Lukustatud paroolihaldurid"</string>
-    <!-- no translation found for locked_credential_entry_label_subtext_tap_to_unlock (6390367581393605009) -->
-    <skip />
-    <!-- no translation found for locked_credential_entry_label_subtext_no_sign_in (8131725029983174901) -->
-    <skip />
+    <string name="locked_credential_entry_label_subtext_tap_to_unlock" msgid="6390367581393605009">"Avamiseks puudutage"</string>
+    <string name="locked_credential_entry_label_subtext_no_sign_in" msgid="8131725029983174901">"Sisselogimisteave puudub"</string>
+    <string name="no_sign_in_info_in" msgid="2641118151920288356">"Kontol <xliff:g id="SOURCE">%1$s</xliff:g> puudub sisselogimisteave"</string>
     <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Sisselogimisandmete haldamine"</string>
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Muus seadmes"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Kasuta teist seadet"</string>
+    <string name="request_cancelled_by" msgid="3735222326886267820">"Rakendus <xliff:g id="APP_NAME">%1$s</xliff:g> tühistas taotluse"</string>
 </resources>
diff --git a/packages/CredentialManager/res/values-eu/strings.xml b/packages/CredentialManager/res/values-eu/strings.xml
index 985d897..8316283 100644
--- a/packages/CredentialManager/res/values-eu/strings.xml
+++ b/packages/CredentialManager/res/values-eu/strings.xml
@@ -1,24 +1,38 @@
 <?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.
+   -->
+
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="4539824758261855508">"Kredentzialen kudeatzailea"</string>
     <string name="string_cancel" msgid="6369133483981306063">"Utzi"</string>
     <string name="string_continue" msgid="1346732695941131882">"Egin aurrera"</string>
-    <string name="string_more_options" msgid="7990658711962795124">"Aukera gehiago"</string>
+    <string name="string_more_options" msgid="2763852250269945472">"Gorde beste modu batera"</string>
     <string name="string_learn_more" msgid="4541600451688392447">"Lortu informazio gehiago"</string>
-    <!-- no translation found for content_description_show_password (3283502010388521607) -->
-    <skip />
-    <!-- no translation found for content_description_hide_password (6841375971631767996) -->
-    <skip />
-    <string name="passkey_creation_intro_title" msgid="4251037543787718844">"Sarbide-gako seguruagoak"</string>
+    <string name="content_description_show_password" msgid="3283502010388521607">"Erakutsi pasahitza"</string>
+    <string name="content_description_hide_password" msgid="6841375971631767996">"Ezkutatu pasahitza"</string>
+    <string name="passkey_creation_intro_title" msgid="4251037543787718844">"Sarbide-gakoekin, seguruago"</string>
     <string name="passkey_creation_intro_body_password" msgid="8825872426579958200">"Sarbide-gakoei esker, ez duzu pasahitz konplexurik sortu edo gogoratu beharrik"</string>
-    <string name="passkey_creation_intro_body_fingerprint" msgid="7331338631826254055">"Hatz-marka, aurpegia edo pantailaren blokeoa erabilita sortzen dituzun giltza digital enkriptatuak dira sarbide-gakoak"</string>
+    <string name="passkey_creation_intro_body_fingerprint" msgid="7331338631826254055">"Hatz-marka, aurpegia edo pantailaren blokeoa erabilita sortzen dituzun gako digital enkriptatuak dira sarbide-gakoak"</string>
     <string name="passkey_creation_intro_body_device" msgid="1203796455762131631">"Pasahitz-kudeatzaile batean gordetzen dira, beste gailu batzuen bidez saioa hasi ahal izateko"</string>
     <string name="more_about_passkeys_title" msgid="7797903098728837795">"Sarbide-gakoei buruzko informazio gehiago"</string>
     <string name="passwordless_technology_title" msgid="2497513482056606668">"Pasahitzik gabeko teknologia"</string>
     <string name="passwordless_technology_detail" msgid="6853928846532955882">"Sarbide-gakoekin, pasahitzik erabili behar gabe has dezakezu saioa. Hatz-marka, aurpegi-hautematea, PINa edo desblokeatzeko eredua baino ez duzu erabili behar zure identitatea egiaztatu, eta sarbide-gako bat sortzeko."</string>
     <string name="public_key_cryptography_title" msgid="6751970819265298039">"Gako publikoen kriptografia"</string>
-    <string name="public_key_cryptography_detail" msgid="6937631710280562213">"FIDO itunean (Google, Apple, Microsoft eta beste enpresa batzuk daude sartuta) eta W3C-ren arauetan oinarritzen dira sarbide-gakoak, eta giltza pare kriptografikoak erabiltzen dituzte. Pasahitzetarako erabiltzen diren erabiltzaile-izenak eta karaktere-kateak ez bezala, giltza pare bat sortzen da (bata pribatua, eta bestea publikoa) aplikazio edo webgune bakoitzerako. Gako pribatua gailuan edo pasahitz-kudeatzailean gordetzen da modu seguruan, eta zure identitatea berresteko balio du. Gako publikoa, aldiz, aplikazioaren edo webgunearen zerbitzariarekin partekatzen da. Aplikazio edo webgune bakoitzari dagokion giltzarekin, berehala erregistra zaitezke, bai eta saioa hasi ere."</string>
+    <string name="public_key_cryptography_detail" msgid="6937631710280562213">"FIDO itunean (Google, Apple, Microsoft eta beste enpresa batzuk daude sartuta) eta W3C-ren arauetan oinarritzen dira sarbide-gakoak, eta gako pare kriptografikoak erabiltzen dituzte. Pasahitzetarako erabiltzen diren erabiltzaile-izenak eta karaktere-kateak ez bezala, gako pare bat sortzen da (bata pribatua, eta bestea publikoa) aplikazio edo webgune bakoitzerako. Gako pribatua gailuan edo pasahitz-kudeatzailean gordetzen da modu seguruan, eta zure identitatea berresteko balio du. Gako publikoa, aldiz, aplikazioaren edo webgunearen zerbitzariarekin partekatzen da. Aplikazio edo webgune bakoitzari dagokion gakoarekin, berehala erregistra zaitezke, bai eta saioa hasi ere."</string>
     <string name="improved_account_security_title" msgid="1069841917893513424">"Kontuaren segurtasun areagotua"</string>
     <string name="improved_account_security_detail" msgid="9123750251551844860">"Gako bakoitza harekin batera erabili behar den aplikazioarekin edo webgunearekin soilik lotzen da. Hala, ezingo duzu inoiz hasi saioa nahi gabe iruzurrezko aplikazio edo webgune batean. Gainera, zerbitzarietan gako publikoak soilik gordetzen direnez, askoz zailagoa da sarbide-gakoak hackeatzea."</string>
     <string name="seamless_transition_title" msgid="5335622196351371961">"Etenik gabeko trantsizioa"</string>
@@ -35,10 +49,13 @@
     <string name="sign_ins" msgid="4710739369149469208">"kredentzialak"</string>
     <string name="sign_in_info" msgid="2627704710674232328">"saioa hasteko informazioa"</string>
     <string name="save_credential_to_title" msgid="3172811692275634301">"Gorde <xliff:g id="CREDENTIALTYPES">%1$s</xliff:g> hemen:"</string>
-    <string name="create_passkey_in_other_device_title" msgid="9195411122362461390">"Beste gailu batean sortu nahi duzu sarbide-gakoa?"</string>
+    <string name="create_passkey_in_other_device_title" msgid="2360053098931886245">"Beste gailu batean sarbide-gako bat sortu nahi duzu?"</string>
+    <string name="save_password_on_other_device_title" msgid="5829084591948321207">"Beste gailu batean pasahitza gorde nahi duzu?"</string>
+    <string name="save_sign_in_on_other_device_title" msgid="2827990118560134692">"Beste gailu batean saioa hasteko modua gorde nahi duzu?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> erabili nahi duzu kredentzial guztietarako?"</string>
-    <string name="use_provider_for_all_description" msgid="8466427781848268490">"Pasahitz-kudeatzaile honek pasahitzak eta sarbide-gakoak gordeko ditu saioa erraz has dezazun"</string>
+    <string name="use_provider_for_all_description" msgid="1998772715863958997">"<xliff:g id="USERNAME">%1$s</xliff:g> erabiltzailearen pasahitz-kudeatzaile honek pasahitzak eta sarbide-gakoak gordeko ditu saioa erraz has dezazun"</string>
     <string name="set_as_default" msgid="4415328591568654603">"Ezarri lehenetsi gisa"</string>
+    <string name="settings" msgid="6536394145760913145">"Ezarpenak"</string>
     <string name="use_once" msgid="9027366575315399714">"Erabili behin"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> pasahitz • <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> sarbide-gako"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> pasahitz"</string>
@@ -49,21 +66,30 @@
     <string name="other_password_manager" msgid="565790221427004141">"Beste pasahitz-kudeatzaile batzuk"</string>
     <string name="close_sheet" msgid="1393792015338908262">"Itxi orria"</string>
     <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Itzuli aurreko orrira"</string>
-    <string name="accessibility_close_button" msgid="2953807735590034688">"Itxi pantailaren behealdean agertzen den kredentzialen kudeatzailearen ekintza iradokia"</string>
+    <string name="accessibility_close_button" msgid="1163435587545377687">"Itxi"</string>
+    <string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"Baztertu"</string>
     <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"<xliff:g id="APP_NAME">%1$s</xliff:g> aplikaziorako gorde duzun sarbide-gakoa erabili nahi duzu?"</string>
-    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"<xliff:g id="APP_NAME">%1$s</xliff:g> aplikaziorako gorde dituzun kredentzialak erabili nahi dituzu?"</string>
-    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Aukeratu <xliff:g id="APP_NAME">%1$s</xliff:g> aplikaziorako gorde dituzun kredentzialak"</string>
+    <string name="get_dialog_title_use_password_for" msgid="625828023234318484">"<xliff:g id="APP_NAME">%1$s</xliff:g> aplikaziorako gordetako pasahitza erabili nahi duzu?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"<xliff:g id="APP_NAME">%1$s</xliff:g> zerbitzuan saioa hasteko kredentzialak erabili nahi dituzu?"</string>
+    <string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"<xliff:g id="APP_NAME">%1$s</xliff:g> aplikazioan saioa hasteko aukerak desblokeatu nahi dituzu?"</string>
+    <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"Aukeratu <xliff:g id="APP_NAME">%1$s</xliff:g> aplikaziorako gordetako sarbide-gakoa"</string>
+    <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"Aukeratu <xliff:g id="APP_NAME">%1$s</xliff:g> aplikaziorako gordetako pasahitza"</string>
+    <string name="get_dialog_title_choose_saved_sign_in_for" msgid="2420298653461652728">"Aukeratu <xliff:g id="APP_NAME">%1$s</xliff:g> aplikaziorako gordetako kredentzialak"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="3048870756117876514">"Aukeratu <xliff:g id="APP_NAME">%1$s</xliff:g> zerbitzuan saioa hasteko kredentzialak"</string>
+    <string name="get_dialog_title_choose_option_for" msgid="4976380044745029107">"<xliff:g id="APP_NAME">%1$s</xliff:g> aplikaziorako aukera bat hautatu nahi duzu?"</string>
+    <string name="get_dialog_title_use_info_on" msgid="8863708099535435146">"<xliff:g id="APP_NAME">%1$s</xliff:g> aplikazioan erabili nahi duzu informazio hori?"</string>
     <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Hasi saioa beste modu batean"</string>
     <string name="snackbar_action" msgid="37373514216505085">"Ikusi aukerak"</string>
     <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Egin aurrera"</string>
     <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Saioa hasteko aukerak"</string>
+    <string name="button_label_view_more" msgid="3429098227286495651">"Ikusi gehiago"</string>
     <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"<xliff:g id="USERNAME">%1$s</xliff:g> erabiltzailearenak"</string>
     <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Blokeatutako pasahitz-kudeatzaileak"</string>
-    <!-- no translation found for locked_credential_entry_label_subtext_tap_to_unlock (6390367581393605009) -->
-    <skip />
-    <!-- no translation found for locked_credential_entry_label_subtext_no_sign_in (8131725029983174901) -->
-    <skip />
+    <string name="locked_credential_entry_label_subtext_tap_to_unlock" msgid="6390367581393605009">"Desblokeatzeko, sakatu hau"</string>
+    <string name="locked_credential_entry_label_subtext_no_sign_in" msgid="8131725029983174901">"Ez dago saioa hasteko informaziorik"</string>
+    <string name="no_sign_in_info_in" msgid="2641118151920288356">"Ez dago saioa hasteko informaziorik <xliff:g id="SOURCE">%1$s</xliff:g> kontuan"</string>
     <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Kudeatu kredentzialak"</string>
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Beste gailu batean gordetakoak"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Erabili beste gailu bat"</string>
+    <string name="request_cancelled_by" msgid="3735222326886267820">"Utzi du bertan behera eskaera <xliff:g id="APP_NAME">%1$s</xliff:g> aplikazioak"</string>
 </resources>
diff --git a/packages/CredentialManager/res/values-fa/strings.xml b/packages/CredentialManager/res/values-fa/strings.xml
index a24e6cb..8e4113d 100644
--- a/packages/CredentialManager/res/values-fa/strings.xml
+++ b/packages/CredentialManager/res/values-fa/strings.xml
@@ -1,30 +1,44 @@
 <?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.
+   -->
+
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="4539824758261855508">"مدیر اطلاعات اعتباری"</string>
     <string name="string_cancel" msgid="6369133483981306063">"لغو"</string>
     <string name="string_continue" msgid="1346732695941131882">"ادامه"</string>
-    <string name="string_more_options" msgid="7990658711962795124">"گزینه‌های بیشتر"</string>
+    <string name="string_more_options" msgid="2763852250269945472">"ذخیره به روشی دیگر"</string>
     <string name="string_learn_more" msgid="4541600451688392447">"بیشتر بدانید"</string>
-    <!-- no translation found for content_description_show_password (3283502010388521607) -->
-    <skip />
-    <!-- no translation found for content_description_hide_password (6841375971631767996) -->
-    <skip />
+    <string name="content_description_show_password" msgid="3283502010388521607">"نمایش گذرواژه"</string>
+    <string name="content_description_hide_password" msgid="6841375971631767996">"پنهان کردن گذرواژه"</string>
     <string name="passkey_creation_intro_title" msgid="4251037543787718844">"فضایی ایمن‌تر با گذرکلید"</string>
     <string name="passkey_creation_intro_body_password" msgid="8825872426579958200">"با گذرکلیدها، لازم نیست گذرواژه پیچیده‌ای بسازید یا آن را به‌خاطر بسپارید"</string>
     <string name="passkey_creation_intro_body_fingerprint" msgid="7331338631826254055">"گذرکلیدها کلیدهای دیجیتالی رمزگذاری‌شده‌ای هستند که بااستفاده از اثر انگشت، چهره، یا قفل صفحه ایجاد می‌کنید"</string>
     <string name="passkey_creation_intro_body_device" msgid="1203796455762131631">"گذرکلیدها در «مدیر گذرواژه» ذخیره می‌شود تا بتوانید در دستگاه‌های دیگر به سیستم وارد شوید"</string>
     <string name="more_about_passkeys_title" msgid="7797903098728837795">"اطلاعات بیشتر درباره گذرکلیدها"</string>
-    <string name="passwordless_technology_title" msgid="2497513482056606668">"فناوری بدون گذرواژه"</string>
-    <string name="passwordless_technology_detail" msgid="6853928846532955882">"گذرکلیدها به شما اجازه می‌دهند بدون اتکا به گذرواژه به سیستم وارد شوید. برای به‌تأیید رساندن هویت خود و ایجاد گذرکلید، کافی است از اثر انگشت، تشخیص چهره، پین، یا الگوی کشیدنی استفاده کنید."</string>
+    <string name="passwordless_technology_title" msgid="2497513482056606668">"فناوری بی‌گذرواژه"</string>
+    <string name="passwordless_technology_detail" msgid="6853928846532955882">"گذرکلیدها به شما اجازه می‌دهند بدون اتکا به گذرواژه به سیستم وارد شوید. برای به‌تأیید رساندن هویت و ایجاد گذرکلید، کافی است از اثر انگشت، تشخیص چهره، پین، یا الگوی کشیدنی استفاده کنید."</string>
     <string name="public_key_cryptography_title" msgid="6751970819265298039">"رمزنگاری کلید عمومی"</string>
     <string name="public_key_cryptography_detail" msgid="6937631710280562213">"‏براساس «اتحاد FIDO» (که شامل Google،‏ Apple،‏ Microsoft، و غیره می‌شود) و استانداردهای W3C، گذرکلیدها از جفت کلیدهای رمزنگاری‌شده استفاده می‌کنند. برخلاف نام کاربری و رشته نویسه‌هایی که برای گذرواژه‌ها استفاده می‌کنیم، یک جفت کلید خصوصی-عمومی برای برنامه یا وب‌سایت ایجاد می‌شود. کلید خصوصی به‌طور امن در دستگاه یا مدیر گذرواژه شما ذخیره می‌شود و هویت شما را تأیید می‌کند. کلید عمومی با سرور وب‌سایت یا برنامه هم‌رسانی می‌شود. با کلیدهای مربوطه می‌توانید بی‌درنگ ثبت‌نام کنید و به سیستم وارد شوید."</string>
     <string name="improved_account_security_title" msgid="1069841917893513424">"بهبود امنیت حساب"</string>
-    <string name="improved_account_security_detail" msgid="9123750251551844860">"هر کلید با برنامه یا وب‌سایتی که برای آن ایجاد شده است پیوند انحصاری دارد، بنابراین هرگز نمی‌توانید به‌اشتباه به سیستم برنامه یا وب‌سایتی جعلی وارد شوید. به‌علاوه، با سرورهایی که فقط کلیدهای عمومی را نگه می‌دارند رخنه‌گری بسیار سخت‌تر است."</string>
+    <string name="improved_account_security_detail" msgid="9123750251551844860">"هر کلید منحصراً با برنامه یا وب‌سایتی که برای آن ایجاد شده است پیوند داده می‌شود، بنابراین هرگز نمی‌توانید به اشتباه وارد برنامه یا وب‌سایت تقلبی شوید. به‌علاوه، باتوجه‌به اینکه سرورها فقط کلیدهای عمومی را نگه می‌دارند، رخنه‌گری (هک کردن) بسیار سخت‌تر است."</string>
     <string name="seamless_transition_title" msgid="5335622196351371961">"انتقال یک‌پارچه"</string>
-    <string name="seamless_transition_detail" msgid="4475509237171739843">"با اینکه به‌سوی آینده‌ای بدون گذرواژه حرکت می‌کنیم، گذرواژه‌ها همچنان در کنار گذرکلیدها دردسترس خواهند بود."</string>
+    <string name="seamless_transition_detail" msgid="4475509237171739843">"درحالی‌که به‌سوی آینده‌ای بی‌گذرواژه حرکت می‌کنیم، گذرواژه‌ها همچنان در کنار گذرکلیدها دردسترس خواهند بود"</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"جایی را برای ذخیره کردن <xliff:g id="CREATETYPES">%1$s</xliff:g> انتخاب کنید"</string>
-    <string name="choose_provider_body" msgid="4967074531845147434">"مدیر گذرواژه‌ای انتخاب کنید تا اطلاعاتتان ذخیره شود و دفعه بعدی سریع‌تر به سیستم وارد شوید"</string>
+    <string name="choose_provider_body" msgid="4967074531845147434">"مدیر گذرواژه‌ای انتخاب کنید تا اطلاعاتتان را ذخیره کنید و دفعه بعد سریع‌تر به سیستم وارد شوید"</string>
     <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"برای <xliff:g id="APPNAME">%1$s</xliff:g> گذرکلید ایجاد شود؟"</string>
     <string name="choose_create_option_password_title" msgid="7097275038523578687">"گذرواژه <xliff:g id="APPNAME">%1$s</xliff:g> ذخیره شود؟"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"اطلاعات ورود به سیستم <xliff:g id="APPNAME">%1$s</xliff:g> ذخیره شود؟"</string>
@@ -35,10 +49,13 @@
     <string name="sign_ins" msgid="4710739369149469208">"ورود به سیستم‌ها"</string>
     <string name="sign_in_info" msgid="2627704710674232328">"اطلاعات ورود به سیستم"</string>
     <string name="save_credential_to_title" msgid="3172811692275634301">"ذخیره <xliff:g id="CREDENTIALTYPES">%1$s</xliff:g> در"</string>
-    <string name="create_passkey_in_other_device_title" msgid="9195411122362461390">"گذرکلید در دستگاه دیگر ایجاد شود؟"</string>
+    <string name="create_passkey_in_other_device_title" msgid="2360053098931886245">"گذرکلید در دستگاه دیگری ایجاد شود؟"</string>
+    <string name="save_password_on_other_device_title" msgid="5829084591948321207">"گذرواژه در دستگاه دیگری ذخیره شود؟"</string>
+    <string name="save_sign_in_on_other_device_title" msgid="2827990118560134692">"اطلاعات ورود به سیستم در دستگاه دیگری ذخیره شود؟"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"از <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> برای همه ورود به سیستم‌ها استفاده شود؟"</string>
-    <string name="use_provider_for_all_description" msgid="8466427781848268490">"این مدیر گذرواژه گذرکلیدها و گذرواژه‌های شما را ذخیره خواهد کرد تا به‌راحتی بتوانید به سیستم وارد شوید"</string>
+    <string name="use_provider_for_all_description" msgid="1998772715863958997">"این مدیر گذرواژه برای <xliff:g id="USERNAME">%1$s</xliff:g> گذرکلیدها و گذرواژه‌های شما را ذخیره می‌کند تا به‌راحتی بتوانید به سیستم وارد شوید"</string>
     <string name="set_as_default" msgid="4415328591568654603">"تنظیم به‌عنوان پیش‌فرض"</string>
+    <string name="settings" msgid="6536394145760913145">"تنظیمات"</string>
     <string name="use_once" msgid="9027366575315399714">"یک‌بار استفاده"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> گذرواژه • <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> گذرکلید"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> گذرواژه"</string>
@@ -49,21 +66,30 @@
     <string name="other_password_manager" msgid="565790221427004141">"دیگر مدیران گذرواژه"</string>
     <string name="close_sheet" msgid="1393792015338908262">"بستن برگ"</string>
     <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"برگشتن به صفحه قبلی"</string>
-    <string name="accessibility_close_button" msgid="2953807735590034688">"بستن پیشنهاد کنش «مدیر اطلاعات اعتباری» که در پایین صفحه نشان داده می‌شود"</string>
-    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"گذرکلید ذخیره‌شده برای <xliff:g id="APP_NAME">%1$s</xliff:g> استفاده شود؟"</string>
-    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"ورود به سیستم ذخیره‌شده برای <xliff:g id="APP_NAME">%1$s</xliff:g> استفاده شود؟"</string>
-    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"انتخاب ورود به سیستم ذخیره‌شده برای <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="accessibility_close_button" msgid="1163435587545377687">"بستن"</string>
+    <string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"بستن"</string>
+    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"از گذرکلید ذخیره‌شده برای «<xliff:g id="APP_NAME">%1$s</xliff:g>» استفاده شود؟"</string>
+    <string name="get_dialog_title_use_password_for" msgid="625828023234318484">"از گذرواژه ذخیره‌شده‌تان برای <xliff:g id="APP_NAME">%1$s</xliff:g> استفاده شود؟"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"از روش ورود به سیستم شما برای <xliff:g id="APP_NAME">%1$s</xliff:g> استفاده شود؟"</string>
+    <string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"گزینه‌های ورود به سیستم برای <xliff:g id="APP_NAME">%1$s</xliff:g> باز شود؟"</string>
+    <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"گذرکلید ذخیره‌شده‌ای را برای <xliff:g id="APP_NAME">%1$s</xliff:g> انتخاب کنید"</string>
+    <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"گذرواژه ذخیره‌شده‌ای را برای <xliff:g id="APP_NAME">%1$s</xliff:g> انتخاب کنید"</string>
+    <string name="get_dialog_title_choose_saved_sign_in_for" msgid="2420298653461652728">"اطلاعات ورود به سیستم ذخیره‌شده‌ای را برای <xliff:g id="APP_NAME">%1$s</xliff:g> انتخاب کنید"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="3048870756117876514">"انتخاب روش ورود به سیستم برای <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_option_for" msgid="4976380044745029107">"گزینه‌ای را برای <xliff:g id="APP_NAME">%1$s</xliff:g> انتخاب کنید؟"</string>
+    <string name="get_dialog_title_use_info_on" msgid="8863708099535435146">"از این اطلاعات در <xliff:g id="APP_NAME">%1$s</xliff:g> استفاده شود؟"</string>
     <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"ورود به سیستم به روشی دیگر"</string>
     <string name="snackbar_action" msgid="37373514216505085">"مشاهده گزینه‌ها"</string>
     <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"ادامه"</string>
     <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"گزینه‌های ورود به سیستم"</string>
+    <string name="button_label_view_more" msgid="3429098227286495651">"مشاهده موارد بیشتر"</string>
     <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"برای <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"مدیران گذرواژه قفل‌شده"</string>
-    <!-- no translation found for locked_credential_entry_label_subtext_tap_to_unlock (6390367581393605009) -->
-    <skip />
-    <!-- no translation found for locked_credential_entry_label_subtext_no_sign_in (8131725029983174901) -->
-    <skip />
+    <string name="locked_credential_entry_label_subtext_tap_to_unlock" msgid="6390367581393605009">"برای باز کردن قفل ضربه بزنید"</string>
+    <string name="locked_credential_entry_label_subtext_no_sign_in" msgid="8131725029983174901">"اطلاعات ورود به سیستم موجود نیست"</string>
+    <string name="no_sign_in_info_in" msgid="2641118151920288356">"هیچ اطلاعات ورود به سیستمی در <xliff:g id="SOURCE">%1$s</xliff:g> وجود ندارد"</string>
     <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"مدیریت ورود به سیستم‌ها"</string>
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"از دستگاهی دیگر"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"استفاده از دستگاه دیگری"</string>
+    <string name="request_cancelled_by" msgid="3735222326886267820">"درخواست را <xliff:g id="APP_NAME">%1$s</xliff:g> لغو کرد"</string>
 </resources>
diff --git a/packages/CredentialManager/res/values-fi/strings.xml b/packages/CredentialManager/res/values-fi/strings.xml
index 73a964a..fff45c9 100644
--- a/packages/CredentialManager/res/values-fi/strings.xml
+++ b/packages/CredentialManager/res/values-fi/strings.xml
@@ -1,10 +1,26 @@
 <?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="4539824758261855508">"Kirjautumistietojen hallinta"</string>
     <string name="string_cancel" msgid="6369133483981306063">"Peru"</string>
     <string name="string_continue" msgid="1346732695941131882">"Jatka"</string>
-    <string name="string_more_options" msgid="7990658711962795124">"Lisäasetukset"</string>
+    <string name="string_more_options" msgid="2763852250269945472">"Tallenna eri tavalla"</string>
     <string name="string_learn_more" msgid="4541600451688392447">"Lue lisää"</string>
     <string name="content_description_show_password" msgid="3283502010388521607">"Näytä salasana"</string>
     <string name="content_description_hide_password" msgid="6841375971631767996">"Piilota salasana"</string>
@@ -33,10 +49,13 @@
     <string name="sign_ins" msgid="4710739369149469208">"sisäänkirjautumiset"</string>
     <string name="sign_in_info" msgid="2627704710674232328">"kirjautumistiedot"</string>
     <string name="save_credential_to_title" msgid="3172811692275634301">"Tallenna <xliff:g id="CREDENTIALTYPES">%1$s</xliff:g> tänne:"</string>
-    <string name="create_passkey_in_other_device_title" msgid="9195411122362461390">"Luodaanko avainkoodi toisella laitteella?"</string>
+    <string name="create_passkey_in_other_device_title" msgid="2360053098931886245">"Luodaanko avainkoodi toisella laitteella?"</string>
+    <string name="save_password_on_other_device_title" msgid="5829084591948321207">"Tallennetaanko salasana toisella laitteella?"</string>
+    <string name="save_sign_in_on_other_device_title" msgid="2827990118560134692">"Tallennetaanko sisäänkirjautuminen toisella laitteella?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"Otetaanko <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> käyttöön kaikissa sisäänkirjautumisissa?"</string>
-    <string name="use_provider_for_all_description" msgid="8466427781848268490">"Tämä salasanojen ylläpitotyökalu tallentaa salasanat ja avainkoodit, jotta voit kirjautua helposti sisään"</string>
+    <string name="use_provider_for_all_description" msgid="1998772715863958997">"Tämä salasanojen ylläpitotyökalu, jota <xliff:g id="USERNAME">%1$s</xliff:g> käyttää, tallentaa salasanat ja avainkoodit, jotta voit kirjautua helposti sisään"</string>
     <string name="set_as_default" msgid="4415328591568654603">"Aseta oletukseksi"</string>
+    <string name="settings" msgid="6536394145760913145">"Asetukset"</string>
     <string name="use_once" msgid="9027366575315399714">"Käytä kerran"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> salasanaa • <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> avainkoodia"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> salasanaa"</string>
@@ -47,21 +66,30 @@
     <string name="other_password_manager" msgid="565790221427004141">"Muut salasanojen ylläpitotyökalut"</string>
     <string name="close_sheet" msgid="1393792015338908262">"Sulje taulukko"</string>
     <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Takaisin edelliselle sivulle"</string>
-    <string name="accessibility_close_button" msgid="2953807735590034688">"Sulje näytön alaosassa näkyvä Kirjautumistietojen hallinta ‑toiminnon ehdotus"</string>
+    <string name="accessibility_close_button" msgid="1163435587545377687">"Sulje"</string>
+    <string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"Sulje"</string>
     <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Käytetäänkö tallennettua avainkoodiasi täällä: <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
-    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Käytetäänkö tallennettuja kirjautumistietoja täällä: <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
-    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Valitse tallennetut kirjautumistiedot (<xliff:g id="APP_NAME">%1$s</xliff:g>)"</string>
+    <string name="get_dialog_title_use_password_for" msgid="625828023234318484">"Käytetäänkö tallennettua salasanaasi sovelluksessa <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"Käytetäänkö kirjautumistapaa: <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"Avataanko kirjautumisvaihtoehdot (<xliff:g id="APP_NAME">%1$s</xliff:g>)?"</string>
+    <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"<xliff:g id="APP_NAME">%1$s</xliff:g>: valitse tallennettu avainkoodi"</string>
+    <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"<xliff:g id="APP_NAME">%1$s</xliff:g>: valitse tallennettu salasana"</string>
+    <string name="get_dialog_title_choose_saved_sign_in_for" msgid="2420298653461652728">"<xliff:g id="APP_NAME">%1$s</xliff:g>: valitse tallennetut kirjautumistiedot"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="3048870756117876514">"Valitse kirjautumistapa: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_option_for" msgid="4976380044745029107">"Valitaanko vaihtoehto, jota <xliff:g id="APP_NAME">%1$s</xliff:g> käyttää?"</string>
+    <string name="get_dialog_title_use_info_on" msgid="8863708099535435146">"Saako <xliff:g id="APP_NAME">%1$s</xliff:g> käyttää näitä tietoja?"</string>
     <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Kirjaudu sisään toisella tavalla"</string>
     <string name="snackbar_action" msgid="37373514216505085">"Katseluasetukset"</string>
     <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Jatka"</string>
     <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Kirjautumisvaihtoehdot"</string>
+    <string name="button_label_view_more" msgid="3429098227286495651">"Näytä lisää"</string>
     <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Käyttäjä: <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Lukitut salasanojen ylläpitotyökalut"</string>
-    <!-- no translation found for locked_credential_entry_label_subtext_tap_to_unlock (6390367581393605009) -->
-    <skip />
-    <!-- no translation found for locked_credential_entry_label_subtext_no_sign_in (8131725029983174901) -->
-    <skip />
+    <string name="locked_credential_entry_label_subtext_tap_to_unlock" msgid="6390367581393605009">"Avaa napauttamalla"</string>
+    <string name="locked_credential_entry_label_subtext_no_sign_in" msgid="8131725029983174901">"Ei kirjautumistietoja"</string>
+    <string name="no_sign_in_info_in" msgid="2641118151920288356">"Ei kirjautumistietoja (<xliff:g id="SOURCE">%1$s</xliff:g>)"</string>
     <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Muuta kirjautumistietoja"</string>
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Toiselta laitteelta"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Käytä toista laitetta"</string>
+    <string name="request_cancelled_by" msgid="3735222326886267820">"<xliff:g id="APP_NAME">%1$s</xliff:g> hylkäsi pyynnön"</string>
 </resources>
diff --git a/packages/CredentialManager/res/values-fr-rCA/strings.xml b/packages/CredentialManager/res/values-fr-rCA/strings.xml
index c79e500..155be6f 100644
--- a/packages/CredentialManager/res/values-fr-rCA/strings.xml
+++ b/packages/CredentialManager/res/values-fr-rCA/strings.xml
@@ -1,29 +1,43 @@
 <?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.
+   -->
+
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="4539824758261855508">"Gestionnaire d\'identifiants"</string>
     <string name="string_cancel" msgid="6369133483981306063">"Annuler"</string>
     <string name="string_continue" msgid="1346732695941131882">"Continuer"</string>
-    <string name="string_more_options" msgid="7990658711962795124">"Autres options"</string>
+    <string name="string_more_options" msgid="2763852250269945472">"Enregistrer autrement"</string>
     <string name="string_learn_more" msgid="4541600451688392447">"En savoir plus"</string>
-    <!-- no translation found for content_description_show_password (3283502010388521607) -->
-    <skip />
-    <!-- no translation found for content_description_hide_password (6841375971631767996) -->
-    <skip />
+    <string name="content_description_show_password" msgid="3283502010388521607">"Afficher le mot de passe"</string>
+    <string name="content_description_hide_password" msgid="6841375971631767996">"Masquer le mot de passe"</string>
     <string name="passkey_creation_intro_title" msgid="4251037543787718844">"Une sécurité accrue grâce aux clés d\'accès"</string>
     <string name="passkey_creation_intro_body_password" msgid="8825872426579958200">"Avec les clés d\'accès, nul besoin de créer ou de mémoriser des mots de passe complexes"</string>
     <string name="passkey_creation_intro_body_fingerprint" msgid="7331338631826254055">"Les clés d\'accès sont des clés numériques chiffrées que vous créez en utilisant votre empreinte digitale, votre visage ou le verrouillage de votre écran"</string>
-    <string name="passkey_creation_intro_body_device" msgid="1203796455762131631">"Ils sont enregistrés dans un gestionnaire de mots de passe pour vous permettre de vous connecter sur d\'autres appareils"</string>
+    <string name="passkey_creation_intro_body_device" msgid="1203796455762131631">"Elles sont enregistrées dans un gestionnaire de mots de passe pour vous permettre de vous connecter à d\'autres appareils"</string>
     <string name="more_about_passkeys_title" msgid="7797903098728837795">"En savoir plus sur les clés d\'accès"</string>
     <string name="passwordless_technology_title" msgid="2497513482056606668">"Technologie sans mot de passe"</string>
     <string name="passwordless_technology_detail" msgid="6853928846532955882">"Les clés d\'accès vous permettent de vous connecter sans utiliser de mots de passe. Il vous suffit d\'utiliser votre empreinte digitale, la reconnaissance faciale, un NIP ou un schéma de balayage pour vérifier votre identité et créer un mot de passe."</string>
-    <string name="public_key_cryptography_title" msgid="6751970819265298039">"Cryptographie des clés publiques"</string>
-    <string name="public_key_cryptography_detail" msgid="6937631710280562213">"Selon les normes de l\'Alliance FIDO (y compris Google, Apple, Microsoft et plus) et du W3C, les clés d\'accès utilisent des biclés cryptographiques. Contrairement au nom d\'utilisateur et à la chaîne de caractères que nous utilisons pour les mots de passe, une biclé privée-publique est créée pour une appli ou un site Web. La clé privée est stockée en toute sécurité sur votre appareil ou votre gestionnaire de mots de passe et confirme votre identité. La clé publique est partagée avec le serveur de l\'appli ou du site Web. Avec les clés correspondantes, vous pouvez vous inscrire et vous connecter instantanément."</string>
+    <string name="public_key_cryptography_title" msgid="6751970819265298039">"Cryptographie à clé publique"</string>
+    <string name="public_key_cryptography_detail" msgid="6937631710280562213">"Selon les normes de l\'Alliance FIDO (y compris Google, Apple, Microsoft et plus) et du W3C, les clés d\'accès utilisent des biclés cryptographiques. Contrairement au nom d\'utilisateur et à la chaîne de caractères que nous utilisons pour les mots de passe, une biclé privée-publique est créée pour une application ou un site Web. La clé privée est stockée en toute sécurité sur votre appareil ou votre gestionnaire de mots de passe et confirme votre identité. La clé publique est partagée avec le serveur de l\'application ou du site Web. Avec les clés correspondantes, vous pouvez vous inscrire et vous connecter instantanément."</string>
     <string name="improved_account_security_title" msgid="1069841917893513424">"Sécurité accrue du compte"</string>
-    <string name="improved_account_security_detail" msgid="9123750251551844860">"Chaque clé est exclusivement liée à l\'application ou au site Web pour lequel elle a été créée, de sorte que vous ne pourrez jamais vous connecter par erreur à une application ou un site Web frauduleux. En outre, comme les serveurs ne conservent que les clés publiques, le piratage informatique est beaucoup plus difficile."</string>
+    <string name="improved_account_security_detail" msgid="9123750251551844860">"Chaque clé est exclusivement liée à l\'application ou au site Web pour lequel elle a été créée, de sorte que vous ne pourrez jamais vous connecter par erreur à une application ou à un site Web frauduleux. En outre, comme les serveurs ne conservent que les clés publiques, le piratage informatique est beaucoup plus difficile."</string>
     <string name="seamless_transition_title" msgid="5335622196351371961">"Transition fluide"</string>
-    <string name="seamless_transition_detail" msgid="4475509237171739843">"À mesure que nous nous dirigeons vers un avenir sans mot de passe, les mots de passe seront toujours utilisés parallèlement aux clés d\'accès."</string>
-    <string name="choose_provider_title" msgid="8870795677024868108">"Choisir où sauvegarder vos <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <string name="seamless_transition_detail" msgid="4475509237171739843">"À mesure que nous nous dirigeons vers un avenir sans mot de passe, ils resteront toujours utilisés parallèlement aux clés d\'accès."</string>
+    <string name="choose_provider_title" msgid="8870795677024868108">"Choisir où enregistrer vos <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"Sélectionnez un gestionnaire de mots de passe pour enregistrer vos renseignements et vous connecter plus rapidement la prochaine fois"</string>
     <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"Créer une clé d\'accès pour <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_password_title" msgid="7097275038523578687">"Enregistrer le mot de passe pour <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
@@ -34,11 +48,14 @@
     <string name="passwords" msgid="5419394230391253816">"mots de passe"</string>
     <string name="sign_ins" msgid="4710739369149469208">"connexions"</string>
     <string name="sign_in_info" msgid="2627704710674232328">"renseignements de connexion"</string>
-    <string name="save_credential_to_title" msgid="3172811692275634301">"Enregistrer <xliff:g id="CREDENTIALTYPES">%1$s</xliff:g> dans"</string>
-    <string name="create_passkey_in_other_device_title" msgid="9195411122362461390">"Créer une clé d\'accès dans un autre appareil?"</string>
+    <string name="save_credential_to_title" msgid="3172811692275634301">"Enregistrer la <xliff:g id="CREDENTIALTYPES">%1$s</xliff:g> dans"</string>
+    <string name="create_passkey_in_other_device_title" msgid="2360053098931886245">"Créer une clé d\'accès sur un autre appareil?"</string>
+    <string name="save_password_on_other_device_title" msgid="5829084591948321207">"Enregistrer le mot de passe sur un autre appareil?"</string>
+    <string name="save_sign_in_on_other_device_title" msgid="2827990118560134692">"Enregistrer l\'authentifiant de connexion sur un autre appareil?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"Utiliser <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> pour toutes vos connexions?"</string>
-    <string name="use_provider_for_all_description" msgid="8466427781848268490">"Ce gestionnaire de mots de passe stockera vos mots de passe et vos clés d\'accès pour vous permettre de vous connecter facilement"</string>
+    <string name="use_provider_for_all_description" msgid="1998772715863958997">"Ce gestionnaire de mots de passe de <xliff:g id="USERNAME">%1$s</xliff:g> stockera vos mots de passe et vos clés d\'accès pour vous permettre de vous connecter facilement"</string>
     <string name="set_as_default" msgid="4415328591568654603">"Définir par défaut"</string>
+    <string name="settings" msgid="6536394145760913145">"Paramètres"</string>
     <string name="use_once" msgid="9027366575315399714">"Utiliser une fois"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> mots de passe • <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> clés d\'accès"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> mots de passe"</string>
@@ -49,21 +66,30 @@
     <string name="other_password_manager" msgid="565790221427004141">"Autres gestionnaires de mots de passe"</string>
     <string name="close_sheet" msgid="1393792015338908262">"Fermer la feuille"</string>
     <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Retourner à la page précédente"</string>
-    <string name="accessibility_close_button" msgid="2953807735590034688">"Fermez l\'action suggérée par le gestionnaire d\'authentifiants qui est affichée au bas de l\'écran"</string>
+    <string name="accessibility_close_button" msgid="1163435587545377687">"Fermer"</string>
+    <string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"Fermer"</string>
     <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Utiliser votre clé d\'accès enregistrée pour <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
-    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Utiliser votre connexion enregistrée pour <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
-    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Choisir une connexion enregistrée pour <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_use_password_for" msgid="625828023234318484">"Utiliser votre mot de passe enregistré pour <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"Utiliser votre identifiant de connexion pour <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"Déverrouiller les options de connexion pour <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"Choisissez une clé d\'accès enregistrée pour <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"Choisissez un mot de passe enregistré pour <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_saved_sign_in_for" msgid="2420298653461652728">"Choisissez une connexion enregistrée pour <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="3048870756117876514">"Choisissez un identifiant de connexion pour <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_option_for" msgid="4976380044745029107">"Choisir une option pour <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_info_on" msgid="8863708099535435146">"Utiliser ces renseignements dans <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Se connecter d\'une autre manière"</string>
     <string name="snackbar_action" msgid="37373514216505085">"Afficher les options"</string>
     <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Continuer"</string>
     <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Options de connexion"</string>
+    <string name="button_label_view_more" msgid="3429098227286495651">"Afficher plus"</string>
     <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Pour <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Gestionnaires de mots de passe verrouillés"</string>
-    <!-- no translation found for locked_credential_entry_label_subtext_tap_to_unlock (6390367581393605009) -->
-    <skip />
-    <!-- no translation found for locked_credential_entry_label_subtext_no_sign_in (8131725029983174901) -->
-    <skip />
+    <string name="locked_credential_entry_label_subtext_tap_to_unlock" msgid="6390367581393605009">"Touchez pour déverrouiller"</string>
+    <string name="locked_credential_entry_label_subtext_no_sign_in" msgid="8131725029983174901">"Aucun renseignement de connexion"</string>
+    <string name="no_sign_in_info_in" msgid="2641118151920288356">"Aucun renseignement de connexion dans <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Gérer les connexions"</string>
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"À partir d\'un autre appareil"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Utiliser un autre appareil"</string>
+    <string name="request_cancelled_by" msgid="3735222326886267820">"Demande annulée par <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
 </resources>
diff --git a/packages/CredentialManager/res/values-fr/strings.xml b/packages/CredentialManager/res/values-fr/strings.xml
index 27354d3..e042815 100644
--- a/packages/CredentialManager/res/values-fr/strings.xml
+++ b/packages/CredentialManager/res/values-fr/strings.xml
@@ -1,15 +1,29 @@
 <?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.
+   -->
+
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="4539824758261855508">"Gestionnaire d\'identifiants"</string>
     <string name="string_cancel" msgid="6369133483981306063">"Annuler"</string>
     <string name="string_continue" msgid="1346732695941131882">"Continuer"</string>
-    <string name="string_more_options" msgid="7990658711962795124">"Autres options"</string>
+    <string name="string_more_options" msgid="2763852250269945472">"Sauver autrement"</string>
     <string name="string_learn_more" msgid="4541600451688392447">"En savoir plus"</string>
-    <!-- no translation found for content_description_show_password (3283502010388521607) -->
-    <skip />
-    <!-- no translation found for content_description_hide_password (6841375971631767996) -->
-    <skip />
+    <string name="content_description_show_password" msgid="3283502010388521607">"Afficher le mot de passe"</string>
+    <string name="content_description_hide_password" msgid="6841375971631767996">"Masquer le mot de passe"</string>
     <string name="passkey_creation_intro_title" msgid="4251037543787718844">"Sécurité renforcée grâce aux clés d\'accès"</string>
     <string name="passkey_creation_intro_body_password" msgid="8825872426579958200">"Avec les clés d\'accès, plus besoin de créer ni de mémoriser des mots de passe complexes"</string>
     <string name="passkey_creation_intro_body_fingerprint" msgid="7331338631826254055">"Les clés d\'accès sont des clés numériques chiffrées que vous créez à l\'aide de votre empreinte digitale, de votre visage ou du verrouillage de l\'écran"</string>
@@ -18,7 +32,7 @@
     <string name="passwordless_technology_title" msgid="2497513482056606668">"Une technologie sans mot de passe"</string>
     <string name="passwordless_technology_detail" msgid="6853928846532955882">"Les clés d\'accès vous permettent de vous connecter sans dépendre d\'un mot de passe. Il vous suffit d\'utiliser votre empreinte digitale, la reconnaissance faciale, un code ou un schéma afin de vérifier votre identité et de créer une clé d\'accès."</string>
     <string name="public_key_cryptography_title" msgid="6751970819265298039">"Cryptographie à clé publique"</string>
-    <string name="public_key_cryptography_detail" msgid="6937631710280562213">"Les clés d\'accès, basées sur standards W3C et FIDO Alliance (Google, Apple, Microsoft, etc.), utilisent paires de clés cryptographiques. Une paire est créée pour appli ou site, contrairement au nom d\'utilisateur et au mot de passe. La clé privée, stockée en sécurité sur votre appareil ou sur un gestionnaire de mots de passe, confirme votre identité. La clé publique est partagée avec le serveur de l\'appli ou du site. Avec des clés correspondantes, l\'inscription et la connexion sont instantanées."</string>
+    <string name="public_key_cryptography_detail" msgid="6937631710280562213">"Les clés d\'accès, basées sur des standards W3C et FIDO Alliance (Google, Apple, Microsoft, etc.), utilisent des paires de clés cryptographiques. À la différence d\'un nom d\'utilisateur/mot de passe, une paire est créée pour chaque appli ou site. La clé privée, stockée en sécurité sur votre appareil ou dans un gestionnaire de mots de passe, confirme votre identité. La clé publique est partagée avec le serveur de l\'appli ou du site. Si les clés correspondent, l\'inscription et la connexion sont instantanées."</string>
     <string name="improved_account_security_title" msgid="1069841917893513424">"Des comptes plus sécurisés"</string>
     <string name="improved_account_security_detail" msgid="9123750251551844860">"Chaque clé est liée exclusivement à l\'appli ou au site Web pour lequel elle a été créée, pour que vous ne puissiez jamais vous connecter par erreur à une appli ou un site Web frauduleux. De plus, le piratage est bien plus difficile, car les serveurs ne conservent que les clés publiques."</string>
     <string name="seamless_transition_title" msgid="5335622196351371961">"Une transition fluide"</string>
@@ -35,10 +49,13 @@
     <string name="sign_ins" msgid="4710739369149469208">"connexions"</string>
     <string name="sign_in_info" msgid="2627704710674232328">"informations de connexion"</string>
     <string name="save_credential_to_title" msgid="3172811692275634301">"Enregistrer la <xliff:g id="CREDENTIALTYPES">%1$s</xliff:g> dans"</string>
-    <string name="create_passkey_in_other_device_title" msgid="9195411122362461390">"Créer une clé d\'accès sur un autre appareil ?"</string>
+    <string name="create_passkey_in_other_device_title" msgid="2360053098931886245">"Créer une clé d\'accès sur un autre appareil ?"</string>
+    <string name="save_password_on_other_device_title" msgid="5829084591948321207">"Enregistrer le mot de passe sur un autre appareil ?"</string>
+    <string name="save_sign_in_on_other_device_title" msgid="2827990118560134692">"Enregistrer les identifiants de connexion sur un autre appareil ?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"Utiliser <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> pour toutes vos connexions ?"</string>
-    <string name="use_provider_for_all_description" msgid="8466427781848268490">"Ce gestionnaire de mots de passe stockera vos mots de passe et clés d\'accès pour vous permettre de vous connecter facilement"</string>
+    <string name="use_provider_for_all_description" msgid="1998772715863958997">"Ce gestionnaire de mots de passe pour <xliff:g id="USERNAME">%1$s</xliff:g> stockera vos mots de passe et clés d\'accès pour vous permettre de vous connecter facilement"</string>
     <string name="set_as_default" msgid="4415328591568654603">"Définir par défaut"</string>
+    <string name="settings" msgid="6536394145760913145">"Paramètres"</string>
     <string name="use_once" msgid="9027366575315399714">"Utiliser une fois"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> mot(s) de passe • <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> clé(s) d\'accès"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> mots de passe"</string>
@@ -49,21 +66,30 @@
     <string name="other_password_manager" msgid="565790221427004141">"Autres gestionnaires de mots de passe"</string>
     <string name="close_sheet" msgid="1393792015338908262">"Fermer la feuille"</string>
     <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Revenir à la page précédente"</string>
-    <string name="accessibility_close_button" msgid="2953807735590034688">"Fermer la suggestion d\'action du gestionnaire d\'identifiants qui apparaît en bas de l\'écran"</string>
+    <string name="accessibility_close_button" msgid="1163435587545377687">"Fermer"</string>
+    <string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"Fermer"</string>
     <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Utiliser votre clé d\'accès enregistrée pour <xliff:g id="APP_NAME">%1$s</xliff:g> ?"</string>
-    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Utiliser vos informations de connexion enregistrées pour <xliff:g id="APP_NAME">%1$s</xliff:g> ?"</string>
-    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Choisir des informations de connexion enregistrées pour <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_use_password_for" msgid="625828023234318484">"Utiliser votre mot de passe enregistré pour <xliff:g id="APP_NAME">%1$s</xliff:g> ?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"Utiliser vos infos de connexion pour <xliff:g id="APP_NAME">%1$s</xliff:g> ?"</string>
+    <string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"Déverrouiller les options de connexion pour <xliff:g id="APP_NAME">%1$s</xliff:g> ?"</string>
+    <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"Choisir une clé d\'accès enregistrée pour <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"Choisir un mot de passe enregistré pour <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_saved_sign_in_for" msgid="2420298653461652728">"Choisir des informations de connexion enregistrées pour <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="3048870756117876514">"Choisir des infos de connexion pour <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_option_for" msgid="4976380044745029107">"Choisir une option pour <xliff:g id="APP_NAME">%1$s</xliff:g> ?"</string>
+    <string name="get_dialog_title_use_info_on" msgid="8863708099535435146">"Utiliser ces informations dans <xliff:g id="APP_NAME">%1$s</xliff:g> ?"</string>
     <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Se connecter d\'une autre manière"</string>
     <string name="snackbar_action" msgid="37373514216505085">"Voir les options"</string>
     <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Continuer"</string>
     <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Options de connexion"</string>
+    <string name="button_label_view_more" msgid="3429098227286495651">"Afficher plus"</string>
     <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Pour <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Gestionnaires de mots de passe verrouillés"</string>
-    <!-- no translation found for locked_credential_entry_label_subtext_tap_to_unlock (6390367581393605009) -->
-    <skip />
-    <!-- no translation found for locked_credential_entry_label_subtext_no_sign_in (8131725029983174901) -->
-    <skip />
+    <string name="locked_credential_entry_label_subtext_tap_to_unlock" msgid="6390367581393605009">"Appuyer pour déverrouiller"</string>
+    <string name="locked_credential_entry_label_subtext_no_sign_in" msgid="8131725029983174901">"Aucune information de connexion"</string>
+    <string name="no_sign_in_info_in" msgid="2641118151920288356">"Aucune info de connexion dans <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Gérer les connexions"</string>
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Depuis un autre appareil"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Utiliser un autre appareil"</string>
+    <string name="request_cancelled_by" msgid="3735222326886267820">"Requête annulée par <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
 </resources>
diff --git a/packages/CredentialManager/res/values-gl/strings.xml b/packages/CredentialManager/res/values-gl/strings.xml
index 260933c..79e0b5e 100644
--- a/packages/CredentialManager/res/values-gl/strings.xml
+++ b/packages/CredentialManager/res/values-gl/strings.xml
@@ -1,18 +1,32 @@
 <?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.
+   -->
+
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="4539824758261855508">"Xestor de credenciais"</string>
     <string name="string_cancel" msgid="6369133483981306063">"Cancelar"</string>
     <string name="string_continue" msgid="1346732695941131882">"Continuar"</string>
-    <string name="string_more_options" msgid="7990658711962795124">"Máis opcións"</string>
+    <string name="string_more_options" msgid="2763852250269945472">"Gardar doutro xeito"</string>
     <string name="string_learn_more" msgid="4541600451688392447">"Máis información"</string>
-    <!-- no translation found for content_description_show_password (3283502010388521607) -->
-    <skip />
-    <!-- no translation found for content_description_hide_password (6841375971631767996) -->
-    <skip />
+    <string name="content_description_show_password" msgid="3283502010388521607">"Mostrar contrasinal"</string>
+    <string name="content_description_hide_password" msgid="6841375971631767996">"Ocultar contrasinal"</string>
     <string name="passkey_creation_intro_title" msgid="4251037543787718844">"Máis protección coas claves de acceso"</string>
-    <string name="passkey_creation_intro_body_password" msgid="8825872426579958200">"Cunha clave de acceso, non é necesario que crees ou lembres contrasinais complexos"</string>
-    <string name="passkey_creation_intro_body_fingerprint" msgid="7331338631826254055">"As claves de acceso son claves dixitais encriptadas que creas usando a túa impresión dixital, a túa cara ou o teu bloqueo de pantalla"</string>
+    <string name="passkey_creation_intro_body_password" msgid="8825872426579958200">"Cunha clave de acceso non é necesario que crees ou lembres contrasinais complexos"</string>
+    <string name="passkey_creation_intro_body_fingerprint" msgid="7331338631826254055">"As claves de acceso son claves dixitais encriptadas que creas usando a impresión dixital, o recoñecemento facial ou un bloqueo de pantalla"</string>
     <string name="passkey_creation_intro_body_device" msgid="1203796455762131631">"As claves de acceso gárdanse nun xestor de contrasinais para que poidas iniciar sesión noutros dispositivos"</string>
     <string name="more_about_passkeys_title" msgid="7797903098728837795">"Máis información sobre as claves de acceso"</string>
     <string name="passwordless_technology_title" msgid="2497513482056606668">"Tecnoloxía sen contrasinais"</string>
@@ -20,7 +34,7 @@
     <string name="public_key_cryptography_title" msgid="6751970819265298039">"Criptografía das claves públicas"</string>
     <string name="public_key_cryptography_detail" msgid="6937631710280562213">"Segundo os estándares da Alianza FIDO (con Google, Apple, Microsoft…) e o W3C, as claves de acceso usan pares de claves criptográficas. A diferenza do nome de usuario e contrasinal, créase un par de claves público-privado para unha aplicación ou sitio web. A clave privada, que confirma a identidade, almacénase de xeito seguro no dispositivo ou xestor de contrasinais. A pública compártese co servidor da aplicación ou sitio web. Coas claves vinculadas, podes rexistrarte e conectarte ao momento."</string>
     <string name="improved_account_security_title" msgid="1069841917893513424">"Mellora na seguranza das contas"</string>
-    <string name="improved_account_security_detail" msgid="9123750251551844860">"Cada clave está vinculada de xeito exclusivo coa aplicación ou o sitio web para o que foi creada, de tal forma que nunca poidas iniciar sesión nunha aplicación ou un sitio web fraudulentos por erro. Ademais, como os servidores só gardan as claves públicas, resulta moito máis difícil piratear."</string>
+    <string name="improved_account_security_detail" msgid="9123750251551844860">"Cada clave está vinculada de xeito exclusivo coa aplicación ou o sitio web para o que foi creada, de tal forma que nunca poidas iniciar sesión nunha aplicación ou un sitio web fraudulentos por erro. Ademais, como os servidores só gardan as claves públicas, resultan moito máis difíciles de piratear."</string>
     <string name="seamless_transition_title" msgid="5335622196351371961">"Transición fluída"</string>
     <string name="seamless_transition_detail" msgid="4475509237171739843">"Durante este percorrido cara a un futuro sen contrasinais, estes seguirán estando dispoñibles a canda as claves de acceso."</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"Escolle onde queres gardar: <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
@@ -35,10 +49,13 @@
     <string name="sign_ins" msgid="4710739369149469208">"métodos de inicio de sesión"</string>
     <string name="sign_in_info" msgid="2627704710674232328">"información de inicio de sesión"</string>
     <string name="save_credential_to_title" msgid="3172811692275634301">"Gardar <xliff:g id="CREDENTIALTYPES">%1$s</xliff:g> en"</string>
-    <string name="create_passkey_in_other_device_title" msgid="9195411122362461390">"Queres crear unha clave de acceso noutro dispositivo?"</string>
+    <string name="create_passkey_in_other_device_title" msgid="2360053098931886245">"Queres crear unha clave de acceso noutro dispositivo?"</string>
+    <string name="save_password_on_other_device_title" msgid="5829084591948321207">"Queres gardar o contrasinal noutro dispositivo?"</string>
+    <string name="save_sign_in_on_other_device_title" msgid="2827990118560134692">"Queres gardar o inicio de sesión noutro dispositivo?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"Queres usar <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> cada vez que inicies sesión?"</string>
-    <string name="use_provider_for_all_description" msgid="8466427781848268490">"Este xestor de contrasinais almacenará os contrasinais e as claves de acceso para axudarche a iniciar sesión facilmente"</string>
+    <string name="use_provider_for_all_description" msgid="1998772715863958997">"Este xestor de contrasinais de <xliff:g id="USERNAME">%1$s</xliff:g> almacenará os teus contrasinais e claves de acceso para axudarche a iniciar sesión facilmente"</string>
     <string name="set_as_default" msgid="4415328591568654603">"Establecer como predeterminado"</string>
+    <string name="settings" msgid="6536394145760913145">"Configuración"</string>
     <string name="use_once" msgid="9027366575315399714">"Usar unha vez"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> contrasinais • <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> claves de acceso"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> contrasinais"</string>
@@ -49,21 +66,30 @@
     <string name="other_password_manager" msgid="565790221427004141">"Outros xestores de contrasinais"</string>
     <string name="close_sheet" msgid="1393792015338908262">"Pechar folla"</string>
     <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Volver á páxina anterior"</string>
-    <string name="accessibility_close_button" msgid="2953807735590034688">"Pechar suxestión de acción do Xestor de credenciais mostrada na parte inferior da pantalla"</string>
+    <string name="accessibility_close_button" msgid="1163435587545377687">"Pechar"</string>
+    <string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"Pechar"</string>
     <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Queres usar a clave de acceso gardada para <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
-    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Queres usar o método de inicio de sesión gardado para <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
-    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Escolle un método de inicio de sesión gardado para <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_use_password_for" msgid="625828023234318484">"Queres usar o contrasinal gardado para <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"Queres usar o teu inicio de sesión para <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"Queres desbloquear as opcións de inicio de sesión para <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"Escolle unha clave de acceso gardada para <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"Escolle un contrasinal gardado para <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_saved_sign_in_for" msgid="2420298653461652728">"Escolle un método de inicio de sesión gardado para <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="3048870756117876514">"Escolle un inicio de sesión para <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_option_for" msgid="4976380044745029107">"Queres escoller unha opción para <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_info_on" msgid="8863708099535435146">"Queres usar esta información en <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Iniciar sesión doutra forma"</string>
     <string name="snackbar_action" msgid="37373514216505085">"Ver opcións"</string>
     <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Continuar"</string>
     <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Opcións de inicio de sesión"</string>
+    <string name="button_label_view_more" msgid="3429098227286495651">"Ver máis"</string>
     <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Para <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Xestores de contrasinais bloqueados"</string>
-    <!-- no translation found for locked_credential_entry_label_subtext_tap_to_unlock (6390367581393605009) -->
-    <skip />
-    <!-- no translation found for locked_credential_entry_label_subtext_no_sign_in (8131725029983174901) -->
-    <skip />
+    <string name="locked_credential_entry_label_subtext_tap_to_unlock" msgid="6390367581393605009">"Toca para desbloquear"</string>
+    <string name="locked_credential_entry_label_subtext_no_sign_in" msgid="8131725029983174901">"Sen información de inicio de sesión"</string>
+    <string name="no_sign_in_info_in" msgid="2641118151920288356">"Non hai información de inicio de sesión para <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Xestionar os métodos de inicio de sesión"</string>
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Doutro dispositivo"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Usar outro dispositivo"</string>
+    <string name="request_cancelled_by" msgid="3735222326886267820">"<xliff:g id="APP_NAME">%1$s</xliff:g> cancelou a solicitude"</string>
 </resources>
diff --git a/packages/CredentialManager/res/values-gu/strings.xml b/packages/CredentialManager/res/values-gu/strings.xml
index bdf0257..c3c5b62 100644
--- a/packages/CredentialManager/res/values-gu/strings.xml
+++ b/packages/CredentialManager/res/values-gu/strings.xml
@@ -1,19 +1,33 @@
 <?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.
+   -->
+
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="4539824758261855508">"લૉગ ઇન વિગતોના મેનેજર"</string>
     <string name="string_cancel" msgid="6369133483981306063">"રદ કરો"</string>
     <string name="string_continue" msgid="1346732695941131882">"ચાલુ રાખો"</string>
-    <string name="string_more_options" msgid="7990658711962795124">"વધુ વિકલ્પો"</string>
+    <string name="string_more_options" msgid="2763852250269945472">"અન્ય રીતે સાચવો"</string>
     <string name="string_learn_more" msgid="4541600451688392447">"વધુ જાણો"</string>
-    <!-- no translation found for content_description_show_password (3283502010388521607) -->
-    <skip />
-    <!-- no translation found for content_description_hide_password (6841375971631767996) -->
-    <skip />
+    <string name="content_description_show_password" msgid="3283502010388521607">"પાસવર્ડ બતાવો"</string>
+    <string name="content_description_hide_password" msgid="6841375971631767996">"પાસવર્ડ છુપાવો"</string>
     <string name="passkey_creation_intro_title" msgid="4251037543787718844">"પાસકી સાથે વધુ સલામત"</string>
     <string name="passkey_creation_intro_body_password" msgid="8825872426579958200">"પાસકી હોવાથી, તમારે જટિલ પાસવર્ડ બનાવવાની કે યાદ રાખવાની જરૂર રહેતી નથી"</string>
     <string name="passkey_creation_intro_body_fingerprint" msgid="7331338631826254055">"પાસકી એ એન્ક્રિપ્ટેડ ડિજિટલ કી છે, જેને તમે તમારી ફિંગરપ્રિન્ટ, ચહેરા અથવા સ્ક્રીન લૉકનો ઉપયોગ કરીને બનાવો છો"</string>
-    <string name="passkey_creation_intro_body_device" msgid="1203796455762131631">"તેને પાસવર્ડ મેનેજરમાં સાચવવામાં આવે છે, જેથી તમે અન્ય ડિવાઇસમાં સાઇન ઇન ન કરી શકો"</string>
+    <string name="passkey_creation_intro_body_device" msgid="1203796455762131631">"તેને પાસવર્ડ મેનેજરમાં સાચવવામાં આવે છે, જેથી તમે અન્ય ડિવાઇસમાં સાઇન ઇન કરી શકો"</string>
     <string name="more_about_passkeys_title" msgid="7797903098728837795">"પાસકી વિશે વધુ"</string>
     <string name="passwordless_technology_title" msgid="2497513482056606668">"પાસવર્ડ રહિત ટેક્નોલોજી"</string>
     <string name="passwordless_technology_detail" msgid="6853928846532955882">"પાસકી તમને પાસવર્ડ પર આધાર રાખ્યા વિના સાઇન ઇન કરવાની મંજૂરી આપે છે. તમારી ઓળખની ચકાસણી કરીને તમારી પાસકી બનાવવા માટે, તમારે માત્ર તમારી ફિંગરપ્રિન્ટ, ચહેરાની ઓળખ, પિન અથવા સ્વાઇપ પૅટર્નનો ઉપયોગ કરવાની જરૂર છે."</string>
@@ -35,10 +49,13 @@
     <string name="sign_ins" msgid="4710739369149469208">"સાઇન-ઇન"</string>
     <string name="sign_in_info" msgid="2627704710674232328">"સાઇન-ઇન કરવાની માહિતી"</string>
     <string name="save_credential_to_title" msgid="3172811692275634301">"<xliff:g id="CREDENTIALTYPES">%1$s</xliff:g>ને આમાં સાચવો"</string>
-    <string name="create_passkey_in_other_device_title" msgid="9195411122362461390">"અન્ય ડિવાઇસ પર પાસકી બનાવીએ?"</string>
+    <string name="create_passkey_in_other_device_title" msgid="2360053098931886245">"બીજા કોઈ ડિવાઇસ પર પાસકી બનાવીએ?"</string>
+    <string name="save_password_on_other_device_title" msgid="5829084591948321207">"બીજા કોઈ ડિવાઇસ પર પાસવર્ડ સાચવીએ?"</string>
+    <string name="save_sign_in_on_other_device_title" msgid="2827990118560134692">"બીજા કોઈ ડિવાઇસ પર સાઇન-ઇન સાચવીએ?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"શું તમારા બધા સાઇન-ઇન માટે <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>નો ઉપયોગ કરીએ?"</string>
-    <string name="use_provider_for_all_description" msgid="8466427781848268490">"આ પાસવર્ડ મેનેજર તમને સરળતાથી સાઇન ઇન કરવામાં સહાય કરવા માટે, તમારા પાસવર્ડ અને પાસકી સ્ટોર કરશે"</string>
+    <string name="use_provider_for_all_description" msgid="1998772715863958997">"<xliff:g id="USERNAME">%1$s</xliff:g> માટેના આ પાસવર્ડ મેનેજર તમને સરળતાથી સાઇન ઇન કરવામાં સહાય કરવા માટે, તમારા પાસવર્ડ અને પાસકી સ્ટોર કરશે"</string>
     <string name="set_as_default" msgid="4415328591568654603">"ડિફૉલ્ટ તરીકે સેટ કરો"</string>
+    <string name="settings" msgid="6536394145760913145">"સેટિંગ"</string>
     <string name="use_once" msgid="9027366575315399714">"એકવાર ઉપયોગ કરો"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> પાસવર્ડ • <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> પાસકી"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> પાસવર્ડ"</string>
@@ -49,21 +66,30 @@
     <string name="other_password_manager" msgid="565790221427004141">"અન્ય પાસવર્ડ મેનેજર"</string>
     <string name="close_sheet" msgid="1393792015338908262">"શીટ બંધ કરો"</string>
     <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"પાછલા પેજ પર પરત જાઓ"</string>
-    <string name="accessibility_close_button" msgid="2953807735590034688">"સ્ક્રીન પર સૌથી નીચે દેખાતું, લૉગ ઇન વિગતના મેનેજરનું ક્રિયા માટેનું સૂચન બંધ કરો"</string>
+    <string name="accessibility_close_button" msgid="1163435587545377687">"બંધ કરો"</string>
+    <string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"છોડી દો"</string>
     <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"<xliff:g id="APP_NAME">%1$s</xliff:g> માટે શું તમારી સાચવેલી પાસકીનો ઉપયોગ કરીએ?"</string>
-    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"<xliff:g id="APP_NAME">%1$s</xliff:g> માટે શું તમારા સાચવેલા સાઇન-ઇનનો ઉપયોગ કરીએ?"</string>
-    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"<xliff:g id="APP_NAME">%1$s</xliff:g> માટે કોઈ સાચવેલું સાઇન-ઇન પસંદ કરો"</string>
+    <string name="get_dialog_title_use_password_for" msgid="625828023234318484">"શું <xliff:g id="APP_NAME">%1$s</xliff:g> માટે તમારા સાચવેલા પાસવર્ડનો ઉપયોગ કરીએ?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"શું <xliff:g id="APP_NAME">%1$s</xliff:g>માં સાઇન ઇન કરવા માટે તમારી આ લૉગ ઇન વિગતોનો ઉપયોગ કરીએ?"</string>
+    <string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"<xliff:g id="APP_NAME">%1$s</xliff:g> માટે સાઇન ઇન વિકલ્પો અનલૉક કરીએ?"</string>
+    <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"<xliff:g id="APP_NAME">%1$s</xliff:g> માટે કોઈ સાચવેલી પાસકી પસંદ કરો"</string>
+    <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"<xliff:g id="APP_NAME">%1$s</xliff:g> માટે કોઈ સાચવેલો પાસવર્ડ પસંદ કરો"</string>
+    <string name="get_dialog_title_choose_saved_sign_in_for" msgid="2420298653461652728">"<xliff:g id="APP_NAME">%1$s</xliff:g> માટે કોઈ સાચવેલું સાઇન-ઇન પસંદ કરો"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="3048870756117876514">"<xliff:g id="APP_NAME">%1$s</xliff:g>માં સાઇન ઇન કરવાની કોઈ રીત પસંદ કરો"</string>
+    <string name="get_dialog_title_choose_option_for" msgid="4976380044745029107">"<xliff:g id="APP_NAME">%1$s</xliff:g>નો વિકલ્પ પસંદ કરીએ?"</string>
+    <string name="get_dialog_title_use_info_on" msgid="8863708099535435146">"<xliff:g id="APP_NAME">%1$s</xliff:g> પર આ માહિતીનો ઉપયોગ કરીએ?"</string>
     <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"કોઈ અન્ય રીતે સાઇન ઇન કરો"</string>
     <string name="snackbar_action" msgid="37373514216505085">"વ્યૂના વિકલ્પો"</string>
     <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"ચાલુ રાખો"</string>
     <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"સાઇન-ઇનના વિકલ્પો"</string>
+    <string name="button_label_view_more" msgid="3429098227286495651">"વધુ જુઓ"</string>
     <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"<xliff:g id="USERNAME">%1$s</xliff:g> માટે"</string>
     <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"લૉક કરેલા પાસવર્ડ મેનેજર"</string>
-    <!-- no translation found for locked_credential_entry_label_subtext_tap_to_unlock (6390367581393605009) -->
-    <skip />
-    <!-- no translation found for locked_credential_entry_label_subtext_no_sign_in (8131725029983174901) -->
-    <skip />
+    <string name="locked_credential_entry_label_subtext_tap_to_unlock" msgid="6390367581393605009">"અનલૉક કરવા માટે ટૅપ કરો"</string>
+    <string name="locked_credential_entry_label_subtext_no_sign_in" msgid="8131725029983174901">"સાઇન-ઇનની કોઈ માહિતી નથી"</string>
+    <string name="no_sign_in_info_in" msgid="2641118151920288356">"<xliff:g id="SOURCE">%1$s</xliff:g>માં સાઇન-ઇનની કોઈ માહિતી નથી"</string>
     <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"સાઇન-ઇન મેનેજ કરો"</string>
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"કોઈ અન્ય ડિવાઇસમાંથી"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"કોઈ અન્ય ડિવાઇસનો ઉપયોગ કરો"</string>
+    <string name="request_cancelled_by" msgid="3735222326886267820">"<xliff:g id="APP_NAME">%1$s</xliff:g> દ્વારા વિનંતી રદ કરવામાં આવી"</string>
 </resources>
diff --git a/packages/CredentialManager/res/values-hi/strings.xml b/packages/CredentialManager/res/values-hi/strings.xml
index 6533362..d986a93 100644
--- a/packages/CredentialManager/res/values-hi/strings.xml
+++ b/packages/CredentialManager/res/values-hi/strings.xml
@@ -1,30 +1,44 @@
 <?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.
+   -->
+
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="4539824758261855508">"CredentialManager"</string>
     <string name="string_cancel" msgid="6369133483981306063">"रद्द करें"</string>
     <string name="string_continue" msgid="1346732695941131882">"जारी रखें"</string>
-    <string name="string_more_options" msgid="7990658711962795124">"ज़्यादा विकल्प"</string>
+    <string name="string_more_options" msgid="2763852250269945472">"दूसरा तरीका सेव करें"</string>
     <string name="string_learn_more" msgid="4541600451688392447">"ज़्यादा जानें"</string>
-    <!-- no translation found for content_description_show_password (3283502010388521607) -->
-    <skip />
-    <!-- no translation found for content_description_hide_password (6841375971631767996) -->
-    <skip />
-    <string name="passkey_creation_intro_title" msgid="4251037543787718844">"पासकी के साथ सुरक्षित रहें"</string>
-    <string name="passkey_creation_intro_body_password" msgid="8825872426579958200">"पासकी होने पर, आपको जटिल पासवर्ड बनाने या याद रखने की ज़रूरत नहीं पड़ती"</string>
-    <string name="passkey_creation_intro_body_fingerprint" msgid="7331338631826254055">"पासकी, एन्क्रिप्ट (सुरक्षित) की गई डिजिटल की होती हैं. इन्हें फ़िंगरप्रिंट, चेहरे या स्क्रीन लॉक का इस्तेमाल करके बनाया जाता है"</string>
+    <string name="content_description_show_password" msgid="3283502010388521607">"पासवर्ड दिखाएं"</string>
+    <string name="content_description_hide_password" msgid="6841375971631767996">"पासवर्ड छिपाएं"</string>
+    <string name="passkey_creation_intro_title" msgid="4251037543787718844">"पासकी से पाएं ज़्यादा सुरक्षा"</string>
+    <string name="passkey_creation_intro_body_password" msgid="8825872426579958200">"पासकी होने पर, आपको मुश्किल पासवर्ड बनाने या याद रखने की ज़रूरत नहीं पड़ती"</string>
+    <string name="passkey_creation_intro_body_fingerprint" msgid="7331338631826254055">"पासकी, एन्क्रिप्ट (सुरक्षित) की गई \'डिजिटल की\' होती हैं. इन्हें फ़िंगरप्रिंट, चेहरे या स्क्रीन लॉक का इस्तेमाल करके बनाया जाता है"</string>
     <string name="passkey_creation_intro_body_device" msgid="1203796455762131631">"पासकी को पासवर्ड मैनेजर में सेव किया जाता है, ताकि इनका इस्तेमाल करके आप अन्य डिवाइसों में साइन इन कर सकें"</string>
     <string name="more_about_passkeys_title" msgid="7797903098728837795">"पासकी के बारे में ज़्यादा जानकारी"</string>
     <string name="passwordless_technology_title" msgid="2497513482056606668">"बिना पासवर्ड वाली टेक्नोलॉजी"</string>
     <string name="passwordless_technology_detail" msgid="6853928846532955882">"पासकी की मदद से, पासवर्ड के बिना साइन इन किया जा सकता है. अपनी पहचान की पुष्टि करने और पासकी बनाने के लिए, फ़िंगरप्रिंट, चेहरे की पहचान करने की सुविधा, पिन या स्वाइप पैटर्न का इस्तेमाल करें."</string>
     <string name="public_key_cryptography_title" msgid="6751970819265298039">"सार्वजनिक कुंजी क्रिप्टोग्राफ़ी"</string>
-    <string name="public_key_cryptography_detail" msgid="6937631710280562213">"W3C के तय मानकों और FIDO एलायंस (Google, Apple, Microsoft वगैरह) के मुताबिक, पासकी में क्रिप्टोग्राफ़िक कुंजी के जोड़े इस्तेमाल किए जाते हैं. पासवर्ड में उपयोगकर्ता नाम और वर्णों की स्ट्रिंग इस्तेमाल की जाती है, जबकि ऐप्लिकेशन या वेबसाइट के लिए निजी-सार्वजनिक कुंजी का जोड़ा बनाया जाता है. निजी कुंजी, Password Manager या डिवाइस में सुरक्षित रहती है और आपकी पहचान की पुष्टि करती है. सार्वजनिक कुंजी, ऐप्लिकेशन या वेबसाइट सर्वर के साथ शेयर होती है. दोनों कुंजियों से, ऐप्लिकेशन या वेबसाइट पर तुरंत रजिस्टर और साइन इन किया जा सकता है."</string>
+    <string name="public_key_cryptography_detail" msgid="6937631710280562213">"W3C के तय मानकों और FIDO अलायंस (इसमें Google, Apple, Microsoft वगैरह शामिल हैं) के मुताबिक, पासकी में क्रिप्टोग्राफ़िक कुंजी के जोड़े इस्तेमाल किए जाते हैं. पासवर्ड में उपयोगकर्ता नाम और वर्णों की स्ट्रिंग इस्तेमाल की जाती है, जबकि ऐप्लिकेशन या वेबसाइट के लिए निजी-सार्वजनिक कुंजी का जोड़ा बनाया जाता है. निजी कुंजी, Password Manager या डिवाइस में सुरक्षित रहती है और आपकी पहचान की पुष्टि करती है. सार्वजनिक कुंजी, ऐप्लिकेशन या वेबसाइट सर्वर के साथ शेयर होती है. दोनों कुंजियों से, ऐप्लिकेशन या वेबसाइट पर तुरंत रजिस्टर और साइन इन किया जा सकता है."</string>
     <string name="improved_account_security_title" msgid="1069841917893513424">"खाते की बेहतर सुरक्षा"</string>
-    <string name="improved_account_security_detail" msgid="9123750251551844860">"हर कुंजी खास तौर पर उस ऐप्लिकेशन या वेबसाइट से लिंक होती है जिसके लिए उसे बनाया गया है. ऐसा इसलिए किया जाता है, ताकि कोई भी व्यक्ति धोखाधड़ी करने वाले ऐप्लिकेशन या वेबसाइट पर कभी भी गलती से साइन इन न करे. साथ ही, जिन सर्वर के पास सिर्फ़ सार्वजनिक कुंजी होती हैं उन्हें हैक करना काफ़ी मुश्किल होता है."</string>
-    <string name="seamless_transition_title" msgid="5335622196351371961">"अपने-आप होने वाला ट्रांज़िशन"</string>
-    <string name="seamless_transition_detail" msgid="4475509237171739843">"आने वाले समय में टेक्नोलॉजी का इस्तेमाल बिना पासवर्ड के किया जा सकेगा. हालांकि, पासकी के साथ पासवर्ड भी इस्तेमाल किए जा सकेंगे."</string>
+    <string name="improved_account_security_detail" msgid="9123750251551844860">"हर कुंजी खास तौर पर उस ऐप्लिकेशन या वेबसाइट से लिंक होती है जिसके लिए उसे बनाया गया है. ऐसा इसलिए किया जाता है, ताकि कोई भी व्यक्ति धोखाधड़ी करने वाले ऐप्लिकेशन या वेबसाइट पर कभी भी गलती से साइन इन न करे. साथ ही, सर्वर के पास सिर्फ़ सार्वजनिक कुंजी होती हैं, इसलिए पूरी कुंजी को हैक करना काफ़ी मुश्किल होता है."</string>
+    <string name="seamless_transition_title" msgid="5335622196351371961">"आसान ट्रांज़िशन"</string>
+    <string name="seamless_transition_detail" msgid="4475509237171739843">"आने वाले समय में बिना पासवर्ड वाली टेक्नोलॉजी यानी पासकी का इस्तेमाल बढ़ेगा, हालांकि इसके साथ-साथ पासवर्ड भी इस्तेमाल किए जा सकेंगे."</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"चुनें कि अपनी <xliff:g id="CREATETYPES">%1$s</xliff:g> कहां सेव करनी हैं"</string>
-    <string name="choose_provider_body" msgid="4967074531845147434">"अपनी जानकारी सेव करने के लिए, कोई पासवर्ड मैनेजर चुनें और अगली बार तेज़ी से साइन इन करें"</string>
+    <string name="choose_provider_body" msgid="4967074531845147434">"अपनी जानकारी सेव करने के लिए, पासवर्ड मैनेजर चुनें और अगली बार ज़्यादा तेज़ी से साइन इन करें"</string>
     <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"क्या आपको <xliff:g id="APPNAME">%1$s</xliff:g> के लिए पासकी बनानी है?"</string>
     <string name="choose_create_option_password_title" msgid="7097275038523578687">"क्या आपको <xliff:g id="APPNAME">%1$s</xliff:g> के लिए पासवर्ड सेव करना है?"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"क्या आपको <xliff:g id="APPNAME">%1$s</xliff:g> के लिए साइन-इन की जानकारी सेव करनी है?"</string>
@@ -35,10 +49,13 @@
     <string name="sign_ins" msgid="4710739369149469208">"साइन इन"</string>
     <string name="sign_in_info" msgid="2627704710674232328">"साइन-इन की जानकारी"</string>
     <string name="save_credential_to_title" msgid="3172811692275634301">"<xliff:g id="CREDENTIALTYPES">%1$s</xliff:g> को यहां सेव करें"</string>
-    <string name="create_passkey_in_other_device_title" msgid="9195411122362461390">"क्या किसी दूसरे डिवाइस में पासकी सेव करनी है?"</string>
+    <string name="create_passkey_in_other_device_title" msgid="2360053098931886245">"क्या आपको किसी दूसरे डिवाइस में पासकी बनानी है?"</string>
+    <string name="save_password_on_other_device_title" msgid="5829084591948321207">"क्या आपको किसी दूसरे डिवाइस में पासवर्ड सेव करना है?"</string>
+    <string name="save_sign_in_on_other_device_title" msgid="2827990118560134692">"क्या आपको किसी दूसरे डिवाइस में साइन इन क्रेडेंशियल सेव करने हैं?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"क्या आपको साइन इन से जुड़ी सारी जानकारी सेव करने के लिए, <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> का इस्तेमाल करना है?"</string>
-    <string name="use_provider_for_all_description" msgid="8466427781848268490">"यह पासवर्ड मैनेजर, आपके पासवर्ड और पासकी सेव करेगा, ताकि आपको साइन इन करने में आसानी हो"</string>
+    <string name="use_provider_for_all_description" msgid="1998772715863958997">"<xliff:g id="USERNAME">%1$s</xliff:g> के लिए यह पासवर्ड मैनेजर, आपके पासवर्ड और पासकी सेव करेगा, ताकि आपको साइन इन करने में आसानी हो"</string>
     <string name="set_as_default" msgid="4415328591568654603">"डिफ़ॉल्ट के तौर पर सेट करें"</string>
+    <string name="settings" msgid="6536394145760913145">"सेटिंग"</string>
     <string name="use_once" msgid="9027366575315399714">"इसका इस्तेमाल एक बार किया जा सकता है"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> पासवर्ड • <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> पासकी"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> पासवर्ड"</string>
@@ -49,21 +66,30 @@
     <string name="other_password_manager" msgid="565790221427004141">"दूसरे पासवर्ड मैनेजर"</string>
     <string name="close_sheet" msgid="1393792015338908262">"शीट बंद करें"</string>
     <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"पिछले पेज पर वापस जाएं"</string>
-    <string name="accessibility_close_button" msgid="2953807735590034688">"स्क्रीन के सबसे नीचे दिख रहे, क्रेडेंशियल मैनेजर की कार्रवाई के सुझाव को बंद करें"</string>
+    <string name="accessibility_close_button" msgid="1163435587545377687">"बंद करें"</string>
+    <string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"खारिज करें"</string>
     <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"क्या आपको <xliff:g id="APP_NAME">%1$s</xliff:g> पर साइन इन करने के लिए, सेव की गई पासकी का इस्तेमाल करना है?"</string>
-    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"क्या आपको <xliff:g id="APP_NAME">%1$s</xliff:g> पर साइन इन करने के लिए, सेव की गई जानकारी का इस्तेमाल करना है?"</string>
-    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"<xliff:g id="APP_NAME">%1$s</xliff:g> पर साइन इन करने के लिए, सेव की गई जानकारी में से चुनें"</string>
+    <string name="get_dialog_title_use_password_for" msgid="625828023234318484">"क्या आपको <xliff:g id="APP_NAME">%1$s</xliff:g> के लिए सेव किया हुआ पासवर्ड इस्तेमाल करना है?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"क्या आपको <xliff:g id="APP_NAME">%1$s</xliff:g> के लिए अपने साइन-इन क्रेडेंशियल का इस्तेमाल करना है?"</string>
+    <string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"क्या आपको <xliff:g id="APP_NAME">%1$s</xliff:g> में साइन इन करने के विकल्पों को अनलॉक करना है?"</string>
+    <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"<xliff:g id="APP_NAME">%1$s</xliff:g> के लिए सेव की गई पासकी चुनें"</string>
+    <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"<xliff:g id="APP_NAME">%1$s</xliff:g> के लिए सेव किया गया पासवर्ड चुनें"</string>
+    <string name="get_dialog_title_choose_saved_sign_in_for" msgid="2420298653461652728">"<xliff:g id="APP_NAME">%1$s</xliff:g> के लिए सेव किया गया साइन इन क्रेडेंशियल चुनें"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="3048870756117876514">"<xliff:g id="APP_NAME">%1$s</xliff:g> के लिए साइन-इन क्रेडेंशियल चुनें"</string>
+    <string name="get_dialog_title_choose_option_for" msgid="4976380044745029107">"<xliff:g id="APP_NAME">%1$s</xliff:g> में साइन इन करने के लिए सेव किए गए विकल्पों में से किसी को चुनना है?"</string>
+    <string name="get_dialog_title_use_info_on" msgid="8863708099535435146">"<xliff:g id="APP_NAME">%1$s</xliff:g> के लिए, क्या इस जानकारी का इस्तेमाल करना है?"</string>
     <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"किसी दूसरे तरीके से साइन इन करें"</string>
     <string name="snackbar_action" msgid="37373514216505085">"विकल्प देखें"</string>
     <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"जारी रखें"</string>
     <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"साइन इन करने के विकल्प"</string>
+    <string name="button_label_view_more" msgid="3429098227286495651">"ज़्यादा देखें"</string>
     <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"<xliff:g id="USERNAME">%1$s</xliff:g> के लिए"</string>
     <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"लॉक किए गए पासवर्ड मैनेजर"</string>
-    <!-- no translation found for locked_credential_entry_label_subtext_tap_to_unlock (6390367581393605009) -->
-    <skip />
-    <!-- no translation found for locked_credential_entry_label_subtext_no_sign_in (8131725029983174901) -->
-    <skip />
+    <string name="locked_credential_entry_label_subtext_tap_to_unlock" msgid="6390367581393605009">"अनलॉक करने के लिए टैप करें"</string>
+    <string name="locked_credential_entry_label_subtext_no_sign_in" msgid="8131725029983174901">"साइन-इन करने से जुड़ी कोई जानकारी उपलब्ध नहीं है"</string>
+    <string name="no_sign_in_info_in" msgid="2641118151920288356">"<xliff:g id="SOURCE">%1$s</xliff:g> में साइन-इन की जानकारी नहीं है"</string>
     <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"साइन इन करने की सुविधा को मैनेज करें"</string>
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"किसी दूसरे डिवाइस से"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"दूसरे डिवाइस का इस्तेमाल करें"</string>
+    <string name="request_cancelled_by" msgid="3735222326886267820">"<xliff:g id="APP_NAME">%1$s</xliff:g> की ओर से अनुरोध रद्द किया गया"</string>
 </resources>
diff --git a/packages/CredentialManager/res/values-hr/strings.xml b/packages/CredentialManager/res/values-hr/strings.xml
index 392a8ed..4425e24 100644
--- a/packages/CredentialManager/res/values-hr/strings.xml
+++ b/packages/CredentialManager/res/values-hr/strings.xml
@@ -1,10 +1,26 @@
 <?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="4539824758261855508">"Upravitelj vjerodajnicama"</string>
     <string name="string_cancel" msgid="6369133483981306063">"Odustani"</string>
     <string name="string_continue" msgid="1346732695941131882">"Nastavi"</string>
-    <string name="string_more_options" msgid="7990658711962795124">"Više opcija"</string>
+    <string name="string_more_options" msgid="2763852250269945472">"Spremi drugi način"</string>
     <string name="string_learn_more" msgid="4541600451688392447">"Saznajte više"</string>
     <string name="content_description_show_password" msgid="3283502010388521607">"Prikaži zaporku"</string>
     <string name="content_description_hide_password" msgid="6841375971631767996">"Sakrij zaporku"</string>
@@ -16,14 +32,14 @@
     <string name="passwordless_technology_title" msgid="2497513482056606668">"Tehnologija bez upotrebe zaporke"</string>
     <string name="passwordless_technology_detail" msgid="6853928846532955882">"Pristupni ključevi omogućuju prijavu bez upotrebe zaporki. Treba vam samo otisak prsta, prepoznavanje lica, PIN ili uzorak pokreta prstom da biste potvrdili svoj identitet i izradili pristupni ključ."</string>
     <string name="public_key_cryptography_title" msgid="6751970819265298039">"Kriptografija javnog ključa"</string>
-    <string name="public_key_cryptography_detail" msgid="6937631710280562213">"Na temelju saveza FIDO (koji uključuje Google, Apple, Microsoft i mnoge druge) i standarda W3C pristupni ključevi koriste kriptografske ključeve. Za razliku od korisničkog imena i niza znakova za zaporke, privatno-javni ključ izrađen je za aplikaciju ili web-lokaciju. Privatni ključ pohranjen je na vašem uređaju ili upravitelju zaporki i potvrđuje vaš identitet. Javni se ključ dijeli s poslužiteljem aplikacije ili web-lokacije. Uz odgovarajuće ključeve možete se odmah registrirati i prijaviti."</string>
+    <string name="public_key_cryptography_detail" msgid="6937631710280562213">"Prema standardima FIDO Alliancea (koja uključuje Google, Apple, Microsoft i mnoge druge) i W3C-a pristupni ključevi koriste kriptografske ključeve. Za razliku od korisničkog imena i niza znakova za zaporke, privatno-javni ključ izrađen je za aplikaciju ili web-lokaciju. Privatni ključ pohranjen je na vašem uređaju ili upravitelju zaporki i potvrđuje vaš identitet. Javni se ključ dijeli s poslužiteljem aplikacije ili web-lokacije. Uz odgovarajuće ključeve možete se odmah registrirati i prijaviti."</string>
     <string name="improved_account_security_title" msgid="1069841917893513424">"Poboljšana sigurnost računa"</string>
-    <string name="improved_account_security_detail" msgid="9123750251551844860">"Svaki ključ povezan isključivo s aplikacijom ili web-lokacijom za koju je izrađen, stoga se nikad ne možete pogreškom prijaviti u prijevarnu aplikaciju ili na web-lokaciju. Osim toga, kad je riječ o poslužiteljima na kojem se nalaze samo javni ključevi, hakiranje je mnogo teže."</string>
+    <string name="improved_account_security_detail" msgid="9123750251551844860">"Svaki je ključ povezan isključivo s aplikacijom ili web-lokacijom za koju je izrađen, stoga se nikad ne možete pogreškom prijaviti u prijevarnu aplikaciju ili na web-lokaciju. Osim toga, kad je riječ o poslužiteljima na kojem se nalaze samo javni ključevi, hakiranje je mnogo teže."</string>
     <string name="seamless_transition_title" msgid="5335622196351371961">"Besprijekorni prijelaz"</string>
     <string name="seamless_transition_detail" msgid="4475509237171739843">"Kako idemo u smjeru budućnosti bez zaporki, one će i dalje biti dostupne uz pristupne ključeve."</string>
-    <string name="choose_provider_title" msgid="8870795677024868108">"Odaberite mjesto za spremanje: <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <string name="choose_provider_title" msgid="8870795677024868108">"Odaberite gdje će se spremati <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"Odaberite upravitelja zaporki kako biste spremili svoje informacije i drugi se put brže prijavili"</string>
-    <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"Izraditi pristupni ključ za <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"Želite li izraditi pristupni ključ za <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_password_title" msgid="7097275038523578687">"Spremiti zaporku za <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Spremiti informacije o prijavi za <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="passkey" msgid="632353688396759522">"pristupni ključ"</string>
@@ -33,10 +49,13 @@
     <string name="sign_ins" msgid="4710739369149469208">"prijave"</string>
     <string name="sign_in_info" msgid="2627704710674232328">"informacije o prijavi"</string>
     <string name="save_credential_to_title" msgid="3172811692275634301">"Spremi <xliff:g id="CREDENTIALTYPES">%1$s</xliff:g> u"</string>
-    <string name="create_passkey_in_other_device_title" msgid="9195411122362461390">"Želite li izraditi pristupni ključ na drugom uređaju?"</string>
+    <string name="create_passkey_in_other_device_title" msgid="2360053098931886245">"Izraditi pristupni ključ na drugom uređaju?"</string>
+    <string name="save_password_on_other_device_title" msgid="5829084591948321207">"Spremiti zaporku na drugom uređaju?"</string>
+    <string name="save_sign_in_on_other_device_title" msgid="2827990118560134692">"Spremiti prijavu na drugom uređaju?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"Želite li upotrebljavati uslugu <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> za sve prijave?"</string>
-    <string name="use_provider_for_all_description" msgid="8466427781848268490">"Upravitelj zaporki pohranit će vaše zaporke i pristupne ključeve radi jednostavnije prijave"</string>
+    <string name="use_provider_for_all_description" msgid="1998772715863958997">"Upravitelj zaporki za korisničko ime <xliff:g id="USERNAME">%1$s</xliff:g> pohranit će vaše zaporke i pristupne ključeve radi jednostavnije prijave"</string>
     <string name="set_as_default" msgid="4415328591568654603">"Postavi kao zadano"</string>
+    <string name="settings" msgid="6536394145760913145">"Postavke"</string>
     <string name="use_once" msgid="9027366575315399714">"Upotrijebi jednom"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"Broj zaporki: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> • broj pristupnih ključeva: <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"Broj zaporki: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>"</string>
@@ -47,21 +66,30 @@
     <string name="other_password_manager" msgid="565790221427004141">"Drugi upravitelji zaporki"</string>
     <string name="close_sheet" msgid="1393792015338908262">"Zatvaranje lista"</string>
     <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Vratite se na prethodnu stranicu"</string>
-    <string name="accessibility_close_button" msgid="2953807735590034688">"Zatvorite prijedlog radnje upravitelja vjerodajnicama koji se pojavljuje na dnu zaslona"</string>
+    <string name="accessibility_close_button" msgid="1163435587545377687">"Zatvori"</string>
+    <string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"Odbaci"</string>
     <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Želite li upotrijebiti spremljeni pristupni ključ za aplikaciju <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
-    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Želite li upotrijebiti spremljene podatke za prijavu za aplikaciju <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
-    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Odaberite spremljene podatke za prijavu za aplikaciju <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_use_password_for" msgid="625828023234318484">"Želite li upotrijebiti spremljenu zaporku za aplikaciju <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"Želite li upotrijebiti prijavu za aplikaciju <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"Želite li otključati opcije za prijavu za aplikaciju <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"Odaberite spremljeni pristupni ključ za aplikaciju <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"Odaberite spremljenu zaporku za aplikaciju <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_saved_sign_in_for" msgid="2420298653461652728">"Odaberite spremljene podatke za prijavu za aplikaciju <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="3048870756117876514">"Odaberite prijavu za aplikaciju <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_option_for" msgid="4976380044745029107">"Želite li odabrati opciju za <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_info_on" msgid="8863708099535435146">"Želite li koristiti te podatke u aplikaciji <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Prijavite se na neki drugi način"</string>
     <string name="snackbar_action" msgid="37373514216505085">"Prikaži opcije"</string>
     <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Nastavi"</string>
     <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Opcije prijave"</string>
+    <string name="button_label_view_more" msgid="3429098227286495651">"Prikaži više"</string>
     <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Za korisnika <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Upravitelji zaključanih zaporki"</string>
-    <!-- no translation found for locked_credential_entry_label_subtext_tap_to_unlock (6390367581393605009) -->
-    <skip />
-    <!-- no translation found for locked_credential_entry_label_subtext_no_sign_in (8131725029983174901) -->
-    <skip />
+    <string name="locked_credential_entry_label_subtext_tap_to_unlock" msgid="6390367581393605009">"Dodirnite za otključavanje"</string>
+    <string name="locked_credential_entry_label_subtext_no_sign_in" msgid="8131725029983174901">"Nema podataka o prijavi"</string>
+    <string name="no_sign_in_info_in" msgid="2641118151920288356">"Nema podataka o prijavi u aplikaciji <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Upravljanje prijavama"</string>
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Na drugom uređaju"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Upotrijebite drugi uređaj"</string>
+    <string name="request_cancelled_by" msgid="3735222326886267820">"Zahtjev je otkazala aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
 </resources>
diff --git a/packages/CredentialManager/res/values-hu/strings.xml b/packages/CredentialManager/res/values-hu/strings.xml
index e7ecc79..118a77c 100644
--- a/packages/CredentialManager/res/values-hu/strings.xml
+++ b/packages/CredentialManager/res/values-hu/strings.xml
@@ -1,15 +1,29 @@
 <?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.
+   -->
+
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="4539824758261855508">"Tanúsítványkezelő"</string>
     <string name="string_cancel" msgid="6369133483981306063">"Mégse"</string>
     <string name="string_continue" msgid="1346732695941131882">"Folytatás"</string>
-    <string name="string_more_options" msgid="7990658711962795124">"További lehetőségek"</string>
+    <string name="string_more_options" msgid="2763852250269945472">"Mentés más módon"</string>
     <string name="string_learn_more" msgid="4541600451688392447">"További információ"</string>
-    <!-- no translation found for content_description_show_password (3283502010388521607) -->
-    <skip />
-    <!-- no translation found for content_description_hide_password (6841375971631767996) -->
-    <skip />
+    <string name="content_description_show_password" msgid="3283502010388521607">"Jelszó megjelenítése"</string>
+    <string name="content_description_hide_password" msgid="6841375971631767996">"Jelszó elrejtése"</string>
     <string name="passkey_creation_intro_title" msgid="4251037543787718844">"Fokozott biztonság – azonosítókulccsal"</string>
     <string name="passkey_creation_intro_body_password" msgid="8825872426579958200">"Azonosítókulcs birtokában nincs szükség összetett jelszavak létrehozására vagy megjegyzésére"</string>
     <string name="passkey_creation_intro_body_fingerprint" msgid="7331338631826254055">"Az azonosítókulcsok olyan digitális kulcsok, amelyeket ujjlenyomata, arca vagy képernyőzár használatával hoz létre"</string>
@@ -18,7 +32,7 @@
     <string name="passwordless_technology_title" msgid="2497513482056606668">"Jelszó nélküli technológia"</string>
     <string name="passwordless_technology_detail" msgid="6853928846532955882">"Az azonosítókulcsok lehetővé teszik a jelszó nélküli bejelentkezést. Csak ujjlenyomatát, arcfelismerést, PIN-kódot vagy csúsztatási mintát kell használnia személyazonosságának igazolásához és azonosítókulcs létrehozásához."</string>
     <string name="public_key_cryptography_title" msgid="6751970819265298039">"A nyilvános kulcs kriptográfiája"</string>
-    <string name="public_key_cryptography_detail" msgid="6937631710280562213">"Az azonosítókulcsok a FIDO Alliance (amely tagja a Google, az Apple és a Microsoft is) és a W3C szabványai alapján titkosítási kulcspárokat használnak. A felhasználónevekkel és a jelszavaknál megszokott karaktersorozattal ellentétben az adott apphoz vagy webhelyhez egy titkos és egy nyilvános kulcsból álló kulcspárt hoz létre a rendszer. A titkos kulcs tárolása biztonságosan történik az eszközén vagy a Jelszókezelőben, és ez a kulcs igazolja az Ön személyazonosságát. A nyilvános kulcsot osztja meg a rendszer az appal vagy a webhely szerverével. A kapcsolódó kulcsok révén Ön azonnal regisztrálhat és bejelentkezhet."</string>
+    <string name="public_key_cryptography_detail" msgid="6937631710280562213">"Az azonosítókulcsok a FIDO Alliance (melynek a Google, az Apple és a Microsoft is tagja) és a W3C szabványai alapján titkosítási kulcspárokat használnak. A felhasználónevekkel és a jelszavaknál megszokott karaktersorozattal ellentétben az adott apphoz vagy webhelyhez egy titkos és egy nyilvános kulcsból álló kulcspárt hoz létre a rendszer. A titkos kulcs tárolása biztonságosan történik az eszközén vagy a Jelszókezelőben, és ez a kulcs igazolja az Ön személyazonosságát. A nyilvános kulcsot osztja meg a rendszer az appal vagy a webhely szerverével. A kapcsolódó kulcsok révén Ön azonnal regisztrálhat és bejelentkezhet."</string>
     <string name="improved_account_security_title" msgid="1069841917893513424">"Nagyobb fiókbiztonság"</string>
     <string name="improved_account_security_detail" msgid="9123750251551844860">"Minden kulcs kizárólag ahhoz az alkalmazáshoz vagy weboldalhoz kapcsolódik, amelyhez létrehozták, így soha nem fordulhat elő, hogy Ön tévedésből csalárd alkalmazásba vagy webhelyre jelentkezik be. Ráadásul – mivel a szerverek csak nyilvános kulcsokat tárolnak – a hackelés jóval nehezebb."</string>
     <string name="seamless_transition_title" msgid="5335622196351371961">"Zökkenőmentes átmenet"</string>
@@ -35,10 +49,13 @@
     <string name="sign_ins" msgid="4710739369149469208">"bejelentkezési adatok"</string>
     <string name="sign_in_info" msgid="2627704710674232328">"bejelentkezési adatok"</string>
     <string name="save_credential_to_title" msgid="3172811692275634301">"<xliff:g id="CREDENTIALTYPES">%1$s</xliff:g> mentése ide:"</string>
-    <string name="create_passkey_in_other_device_title" msgid="9195411122362461390">"Egy másik eszközön szeretne azonosítókulcsot létrehozni?"</string>
+    <string name="create_passkey_in_other_device_title" msgid="2360053098931886245">"Létrehoz egy azonosítókulcsot másik eszközön?"</string>
+    <string name="save_password_on_other_device_title" msgid="5829084591948321207">"Elmenti a jelszót másik eszközön?"</string>
+    <string name="save_sign_in_on_other_device_title" msgid="2827990118560134692">"Elmenti a bejelentkezési adatokat másik eszközön?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"Szeretné a következőt használni az összes bejelentkezési adatához: <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
-    <string name="use_provider_for_all_description" msgid="8466427781848268490">"Ez a jelszókezelő fogja tárolni a jelszavait és azonosítókulcsait a bejelentkezés megkönnyítése érdekében."</string>
+    <string name="use_provider_for_all_description" msgid="1998772715863958997">"Ez a jelszókezelő fogja tárolni a(z) <xliff:g id="USERNAME">%1$s</xliff:g> fiókhoz tartozó jelszavait és azonosítókulcsait a bejelentkezés megkönnyítése érdekében."</string>
     <string name="set_as_default" msgid="4415328591568654603">"Beállítás alapértelmezettként"</string>
+    <string name="settings" msgid="6536394145760913145">"Beállítások"</string>
     <string name="use_once" msgid="9027366575315399714">"Egyszeri használat"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> jelszó, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> azonosítókulcs"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> jelszó"</string>
@@ -49,21 +66,30 @@
     <string name="other_password_manager" msgid="565790221427004141">"Egyéb jelszókezelők"</string>
     <string name="close_sheet" msgid="1393792015338908262">"Munkalap bezárása"</string>
     <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Vissza az előző oldalra"</string>
-    <string name="accessibility_close_button" msgid="2953807735590034688">"A Tanúsítványkezelő képernyő alján megjelenő műveletjavaslat bezárása"</string>
+    <string name="accessibility_close_button" msgid="1163435587545377687">"Bezárás"</string>
+    <string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"Elvetés"</string>
     <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Szeretné a(z) <xliff:g id="APP_NAME">%1$s</xliff:g> alkalmazáshoz mentett azonosítókulcsot használni?"</string>
-    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Szeretné a(z) <xliff:g id="APP_NAME">%1$s</xliff:g> alkalmazáshoz mentett bejelentkezési adatait használni?"</string>
-    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Mentett bejelentkezési adatok választása a következő számára: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_use_password_for" msgid="625828023234318484">"Szeretné az elmentett jelszavát használni a(z) <xliff:g id="APP_NAME">%1$s</xliff:g> alkalmazáshoz?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"Szeretné használni a következőhöz tartozó bejelentkezési adatait: <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"Feloldja a(z) <xliff:g id="APP_NAME">%1$s</xliff:g> bejelentkezési lehetőségeit?"</string>
+    <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"Mentett azonosítókulcs kiválasztása a(z) <xliff:g id="APP_NAME">%1$s</xliff:g> alkalmazáshoz"</string>
+    <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"Mentett jelszó kiválasztása a(z) <xliff:g id="APP_NAME">%1$s</xliff:g> alkalmazáshoz"</string>
+    <string name="get_dialog_title_choose_saved_sign_in_for" msgid="2420298653461652728">"Mentett bejelentkezési adatok kiválasztása a(z) <xliff:g id="APP_NAME">%1$s</xliff:g> alkalmazáshoz"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="3048870756117876514">"Válasszon bejelentkezési adatokat – <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_option_for" msgid="4976380044745029107">"Kiválaszt egy lehetőséget a következőbe való bejelentkezéshez: <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_info_on" msgid="8863708099535435146">"Használni szeretná ezt az információt a(z) <xliff:g id="APP_NAME">%1$s</xliff:g> alkalmazásban?"</string>
     <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Bejelentkezés más módon"</string>
     <string name="snackbar_action" msgid="37373514216505085">"Lehetőségek megtekintése"</string>
     <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Folytatás"</string>
     <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Bejelentkezési beállítások"</string>
+    <string name="button_label_view_more" msgid="3429098227286495651">"Továbbiak megjelenítése"</string>
     <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"<xliff:g id="USERNAME">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Zárolt jelszókezelők"</string>
-    <!-- no translation found for locked_credential_entry_label_subtext_tap_to_unlock (6390367581393605009) -->
-    <skip />
-    <!-- no translation found for locked_credential_entry_label_subtext_no_sign_in (8131725029983174901) -->
-    <skip />
+    <string name="locked_credential_entry_label_subtext_tap_to_unlock" msgid="6390367581393605009">"Koppintson a feloldáshoz"</string>
+    <string name="locked_credential_entry_label_subtext_no_sign_in" msgid="8131725029983174901">"Nincsenek bejelentkezési adatok"</string>
+    <string name="no_sign_in_info_in" msgid="2641118151920288356">"Nem találhatók bejelentkezési adatok itt: <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Bejelentkezési adatok kezelése"</string>
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Másik eszközről"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Másik eszköz használata"</string>
+    <string name="request_cancelled_by" msgid="3735222326886267820">"A kérelmet törölte a(z) <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
 </resources>
diff --git a/packages/CredentialManager/res/values-hy/strings.xml b/packages/CredentialManager/res/values-hy/strings.xml
index d47154b..a6bda50 100644
--- a/packages/CredentialManager/res/values-hy/strings.xml
+++ b/packages/CredentialManager/res/values-hy/strings.xml
@@ -1,10 +1,26 @@
 <?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="4539824758261855508">"Մուտքի տվյալների կառավարիչ"</string>
     <string name="string_cancel" msgid="6369133483981306063">"Չեղարկել"</string>
     <string name="string_continue" msgid="1346732695941131882">"Շարունակել"</string>
-    <string name="string_more_options" msgid="7990658711962795124">"Այլ տարբերակներ"</string>
+    <string name="string_more_options" msgid="2763852250269945472">"Պահել մեկ այլ եղանակ"</string>
     <string name="string_learn_more" msgid="4541600451688392447">"Իմանալ ավելին"</string>
     <string name="content_description_show_password" msgid="3283502010388521607">"Ցուցադրել գաղտնաբառը"</string>
     <string name="content_description_hide_password" msgid="6841375971631767996">"Թաքցնել գաղտնաբառը"</string>
@@ -12,7 +28,7 @@
     <string name="passkey_creation_intro_body_password" msgid="8825872426579958200">"Անցաբառերի շնորհիվ դուք բարդ գաղտնաբառեր ստեղծելու կամ հիշելու անհրաժեշտություն չեք ունենա"</string>
     <string name="passkey_creation_intro_body_fingerprint" msgid="7331338631826254055">"Անցաբառերը գաղտնագրված թվային բանալիներ են, որոնք ստեղծվում են մատնահետքի, դեմքով ապակողպման կամ էկրանի կողպման օգտագործմամբ"</string>
     <string name="passkey_creation_intro_body_device" msgid="1203796455762131631">"Դուք կարող եք մուտք գործել այլ սարքերում, քանի որ անցաբառերը պահվում են գաղտնաբառերի կառավարիչում"</string>
-    <string name="more_about_passkeys_title" msgid="7797903098728837795">"Մանրամասն անցաբառերի մասին"</string>
+    <string name="more_about_passkeys_title" msgid="7797903098728837795">"Ավելին՝ անցաբառերի մասին"</string>
     <string name="passwordless_technology_title" msgid="2497513482056606668">"Գաղտնաբառեր չպահանջող տեխնոլոգիա"</string>
     <string name="passwordless_technology_detail" msgid="6853928846532955882">"Անցաբառերը ձեզ թույլ են տալիս մուտք գործել առանց գաղտնաբառերի։ Ձեզ պարզապես հարկավոր է օգտագործել ձեր մատնահետքը, դիմաճանաչումը, PIN կոդը կամ նախշը՝ ձեր ինքնությունը հաստատելու և անցաբառ ստեղծելու համար։"</string>
     <string name="public_key_cryptography_title" msgid="6751970819265298039">"Բաց բանալու կրիպտոգրաֆիա"</string>
@@ -23,7 +39,7 @@
     <string name="seamless_transition_detail" msgid="4475509237171739843">"Թեև մենք առանց գաղտնաբառերի ապագայի ճանապարհին ենք, դրանք դեռ հասանելի կլինեն անցաբառերի հետ մեկտեղ։"</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"Նշեք, թե որտեղ եք ուզում պահել ձեր <xliff:g id="CREATETYPES">%1$s</xliff:g>ը"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"Ընտրեք գաղտնաբառերի կառավարիչ՝ ձեր տեղեկությունները պահելու և հաջորդ անգամ ավելի արագ մուտք գործելու համար"</string>
-    <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"Ստեղծե՞լ անցաբառ «<xliff:g id="APPNAME">%1$s</xliff:g>» հավելվածի համար"</string>
+    <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"Ստեղծե՞լ անցաբառ <xliff:g id="APPNAME">%1$s</xliff:g> հավելվածի համար"</string>
     <string name="choose_create_option_password_title" msgid="7097275038523578687">"Պահե՞լ «<xliff:g id="APPNAME">%1$s</xliff:g>» հավելվածի գաղտնաբառը"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Պահե՞լ «<xliff:g id="APPNAME">%1$s</xliff:g>» հավելվածի մուտքի տվյալները"</string>
     <string name="passkey" msgid="632353688396759522">"անցաբառ"</string>
@@ -33,10 +49,13 @@
     <string name="sign_ins" msgid="4710739369149469208">"մուտք"</string>
     <string name="sign_in_info" msgid="2627704710674232328">"մուտքի տվյալներ"</string>
     <string name="save_credential_to_title" msgid="3172811692275634301">"Պահել <xliff:g id="CREDENTIALTYPES">%1$s</xliff:g>ն այստեղ՝"</string>
-    <string name="create_passkey_in_other_device_title" msgid="9195411122362461390">"Ստեղծե՞լ անցաբառ այլ սարքում"</string>
+    <string name="create_passkey_in_other_device_title" msgid="2360053098931886245">"Ստեղծե՞լ անցաբառ այլ սարքում"</string>
+    <string name="save_password_on_other_device_title" msgid="5829084591948321207">"Պահե՞լ գաղտնաբառն այլ սարքում"</string>
+    <string name="save_sign_in_on_other_device_title" msgid="2827990118560134692">"Պահե՞լ մուտքի տվյալներն այլ սարքում"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"Միշտ մուտք գործե՞լ <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> հավելվածի միջոցով"</string>
-    <string name="use_provider_for_all_description" msgid="8466427781848268490">"Գաղտնաբառերի այս կառավարիչը կպահի ձեր գաղտնաբառերն ու անցաբառերը՝ օգնելու ձեզ հեշտությամբ մուտք գործել հաշիվ"</string>
+    <string name="use_provider_for_all_description" msgid="1998772715863958997">"Այս գաղտնաբառերի կառավարչում <xliff:g id="USERNAME">%1$s</xliff:g> օգտատերը կկարողանա պահել իր գաղտնաբառերն ու անցաբառերը, որպեսզի հետագայում ավելի արագ մուտք գործի հաշիվ"</string>
     <string name="set_as_default" msgid="4415328591568654603">"Նշել որպես կանխադրված"</string>
+    <string name="settings" msgid="6536394145760913145">"Կարգավորումներ"</string>
     <string name="use_once" msgid="9027366575315399714">"Օգտագործել մեկ անգամ"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> գաղտնաբառ • <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> անցաբառ"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> գաղտնաբառ"</string>
@@ -47,21 +66,30 @@
     <string name="other_password_manager" msgid="565790221427004141">"Գաղտնաբառերի այլ կառավարիչներ"</string>
     <string name="close_sheet" msgid="1393792015338908262">"Փակել թերթը"</string>
     <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Անցնել նախորդ էջ"</string>
-    <string name="accessibility_close_button" msgid="2953807735590034688">"Փակել Մուտքի տվյալների կառավարչի հուշումը, որը ցուցադրվում է էկրանի ներքևի հատվածում"</string>
+    <string name="accessibility_close_button" msgid="1163435587545377687">"Փակել"</string>
+    <string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"Փակել"</string>
     <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Օգտագործե՞լ պահված անցաբառը <xliff:g id="APP_NAME">%1$s</xliff:g> հավելվածի համար"</string>
-    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Օգտագործե՞լ մուտքի պահված տվյալները <xliff:g id="APP_NAME">%1$s</xliff:g> հավելվածի համար"</string>
-    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Ընտրեք մուտքի պահված տվյալներ <xliff:g id="APP_NAME">%1$s</xliff:g> հավելվածի համար"</string>
+    <string name="get_dialog_title_use_password_for" msgid="625828023234318484">"Օգտագործե՞լ <xliff:g id="APP_NAME">%1$s</xliff:g> հավելվածի համար պահված ձեր գաղտնաբառը"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"Օգտագործե՞լ այս տվյալները <xliff:g id="APP_NAME">%1$s</xliff:g> հավելված մուտք գործելու համար"</string>
+    <string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"Ապակողպե՞լ մուտքի տարբերակներ <xliff:g id="APP_NAME">%1$s</xliff:g> հավելվածի համար"</string>
+    <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"Ընտրեք պահված անցաբառ <xliff:g id="APP_NAME">%1$s</xliff:g> հավելվածի համար"</string>
+    <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"Ընտրեք պահված գաղտնաբառ <xliff:g id="APP_NAME">%1$s</xliff:g> հավելվածի համար"</string>
+    <string name="get_dialog_title_choose_saved_sign_in_for" msgid="2420298653461652728">"Ընտրեք մուտքի պահված տվյալներ <xliff:g id="APP_NAME">%1$s</xliff:g> հավելվածի համար"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="3048870756117876514">"Ընտրեք, թե ինչպես եք ուզում մուտք գործել <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_option_for" msgid="4976380044745029107">"Ընտրե՞լ տարբերակ <xliff:g id="APP_NAME">%1$s</xliff:g> հավելվածի համար"</string>
+    <string name="get_dialog_title_use_info_on" msgid="8863708099535435146">"Օգտագործե՞լ այս տեղեկությունները <xliff:g id="APP_NAME">%1$s</xliff:g> մտնելու համար"</string>
     <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Մուտք գործել այլ եղանակով"</string>
     <string name="snackbar_action" msgid="37373514216505085">"Դիտել տարբերակները"</string>
     <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Շարունակել"</string>
     <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Մուտքի տարբերակներ"</string>
+    <string name="button_label_view_more" msgid="3429098227286495651">"Դիտել ավելին"</string>
     <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"<xliff:g id="USERNAME">%1$s</xliff:g>-ի համար"</string>
     <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Գաղտնաբառերի կողպված կառավարիչներ"</string>
-    <!-- no translation found for locked_credential_entry_label_subtext_tap_to_unlock (6390367581393605009) -->
-    <skip />
-    <!-- no translation found for locked_credential_entry_label_subtext_no_sign_in (8131725029983174901) -->
-    <skip />
+    <string name="locked_credential_entry_label_subtext_tap_to_unlock" msgid="6390367581393605009">"Հպեք ապակողպելու համար"</string>
+    <string name="locked_credential_entry_label_subtext_no_sign_in" msgid="8131725029983174901">"Մուտքի տվյալներ չկան"</string>
+    <string name="no_sign_in_info_in" msgid="2641118151920288356">"Մուտքի տվյալներ չկան այստեղ՝ <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Մուտքի կառավարում"</string>
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Մեկ այլ սարքից"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Օգտագործել այլ սարք"</string>
+    <string name="request_cancelled_by" msgid="3735222326886267820">"Հարցումը չեղարկվել է <xliff:g id="APP_NAME">%1$s</xliff:g> հավելվածի կողմից"</string>
 </resources>
diff --git a/packages/CredentialManager/res/values-in/strings.xml b/packages/CredentialManager/res/values-in/strings.xml
index d62de2f..e47cf7b 100644
--- a/packages/CredentialManager/res/values-in/strings.xml
+++ b/packages/CredentialManager/res/values-in/strings.xml
@@ -1,30 +1,44 @@
 <?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.
+   -->
+
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="4539824758261855508">"Pengelola Kredensial"</string>
     <string name="string_cancel" msgid="6369133483981306063">"Batal"</string>
     <string name="string_continue" msgid="1346732695941131882">"Lanjutkan"</string>
-    <string name="string_more_options" msgid="7990658711962795124">"Opsi lainnya"</string>
+    <string name="string_more_options" msgid="2763852250269945472">"Simpan dengan cara lain"</string>
     <string name="string_learn_more" msgid="4541600451688392447">"Pelajari lebih lanjut"</string>
-    <!-- no translation found for content_description_show_password (3283502010388521607) -->
-    <skip />
-    <!-- no translation found for content_description_hide_password (6841375971631767996) -->
-    <skip />
+    <string name="content_description_show_password" msgid="3283502010388521607">"Tampilkan sandi"</string>
+    <string name="content_description_hide_password" msgid="6841375971631767996">"Sembunyikan sandi"</string>
     <string name="passkey_creation_intro_title" msgid="4251037543787718844">"Lebih aman dengan kunci sandi"</string>
     <string name="passkey_creation_intro_body_password" msgid="8825872426579958200">"Dengan kunci sandi, Anda tidak perlu membuat atau mengingat sandi yang rumit"</string>
     <string name="passkey_creation_intro_body_fingerprint" msgid="7331338631826254055">"Kunci sandi adalah kunci digital terenkripsi yang Anda buat menggunakan sidik jari, wajah, atau kunci layar"</string>
-    <string name="passkey_creation_intro_body_device" msgid="1203796455762131631">"Kunci sandi disimpan ke pengelola sandi, sehingga Anda dapat login di perangkat lainnya"</string>
+    <string name="passkey_creation_intro_body_device" msgid="1203796455762131631">"Kunci sandi disimpan ke pengelola sandi, sehingga Anda dapat login di perangkat lain"</string>
     <string name="more_about_passkeys_title" msgid="7797903098728837795">"Selengkapnya tentang kunci sandi"</string>
     <string name="passwordless_technology_title" msgid="2497513482056606668">"Teknologi tanpa sandi"</string>
     <string name="passwordless_technology_detail" msgid="6853928846532955882">"Kunci sandi memungkinkan Anda login tanpa mengandalkan sandi. Anda hanya perlu menggunakan sidik jari, pengenalan wajah, PIN, atau pola geser untuk memverifikasi identitas Anda dan membuat kunci sandi."</string>
     <string name="public_key_cryptography_title" msgid="6751970819265298039">"Kriptografi kunci publik"</string>
-    <string name="public_key_cryptography_detail" msgid="6937631710280562213">"Berdasarkan FIDO Alliance (yang mencakup Google, Apple, Microsoft, dan lainnya) dan standar W3C, kunci sandi menggunakan pasangan kunci kriptografis. Tidak seperti nama pengguna dan string karakter yang digunakan untuk sandi, pasangan kunci pribadi-publik dibuat untuk aplikasi atau situs. Kunci pribadi disimpan dengan aman di perangkat atau pengelola sandi dan mengonfirmasi identitas Anda. Kunci publik dibagikan ke server aplikasi atau situs. Dengan kunci yang sesuai, Anda dapat langsung mendaftar dan login."</string>
+    <string name="public_key_cryptography_detail" msgid="6937631710280562213">"Berdasarkan FIDO Alliance (yang mencakup Google, Apple, Microsoft, dan lainnya) dan standar W3C, kunci sandi menggunakan pasangan kunci kriptografis. Tidak seperti nama pengguna dan string karakter yang digunakan untuk sandi, pasangan kunci pribadi-publik dibuat untuk aplikasi atau situs. Kunci pribadi disimpan dengan aman di perangkat atau pengelola sandi, dan digunakan untuk mengonfirmasi identitas Anda. Kunci publik dibagikan ke server aplikasi atau situs. Dengan kunci yang sesuai, Anda dapat langsung mendaftar dan login."</string>
     <string name="improved_account_security_title" msgid="1069841917893513424">"Keamanan akun yang ditingkatkan"</string>
     <string name="improved_account_security_detail" msgid="9123750251551844860">"Setiap kunci ditautkan secara eksklusif dengan aplikasi atau situs tempatnya dibuat, sehingga Anda tidak akan login ke aplikasi atau situs yang menipu secara tidak sengaja. Selain itu, peretasan lebih sulit dilakukan karena server hanya menyimpan kunci publik."</string>
     <string name="seamless_transition_title" msgid="5335622196351371961">"Transisi yang lancar"</string>
-    <string name="seamless_transition_detail" msgid="4475509237171739843">"Seiring kita menuju masa depan tanpa sandi, sandi akan tetap tersedia bersama kunci sandi."</string>
+    <string name="seamless_transition_detail" msgid="4475509237171739843">"Sandi akan tetap tersedia bersama kunci sandi seiring perjalanan menuju era di mana sandi tidak diperlukan lagi."</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"Pilih tempat penyimpanan <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
-    <string name="choose_provider_body" msgid="4967074531845147434">"Pilih pengelola sandi untuk menyimpan info Anda dan login lebih cepat pada waktu berikutnya"</string>
+    <string name="choose_provider_body" msgid="4967074531845147434">"Pilih pengelola sandi untuk menyimpan info Anda dan login lebih cepat lain kali"</string>
     <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"Buat kunci sandi untuk <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_password_title" msgid="7097275038523578687">"Simpan sandi untuk <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Simpan info login untuk <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
@@ -35,10 +49,13 @@
     <string name="sign_ins" msgid="4710739369149469208">"login"</string>
     <string name="sign_in_info" msgid="2627704710674232328">"info login"</string>
     <string name="save_credential_to_title" msgid="3172811692275634301">"Simpan <xliff:g id="CREDENTIALTYPES">%1$s</xliff:g> ke"</string>
-    <string name="create_passkey_in_other_device_title" msgid="9195411122362461390">"Buat kunci sandi di perangkat lain?"</string>
+    <string name="create_passkey_in_other_device_title" msgid="2360053098931886245">"Buat kunci sandi di perangkat lain?"</string>
+    <string name="save_password_on_other_device_title" msgid="5829084591948321207">"Simpan sandi di perangkat lain?"</string>
+    <string name="save_sign_in_on_other_device_title" msgid="2827990118560134692">"Simpan info login di perangkat lain?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"Gunakan <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> untuk semua info login Anda?"</string>
-    <string name="use_provider_for_all_description" msgid="8466427781848268490">"Pengelola sandi ini akan menyimpan sandi dan kunci sandi untuk membantu Anda login dengan mudah"</string>
+    <string name="use_provider_for_all_description" msgid="1998772715863958997">"Pengelola sandi untuk <xliff:g id="USERNAME">%1$s</xliff:g> ini akan menyimpan sandi dan kunci sandi guna membantu Anda login dengan mudah"</string>
     <string name="set_as_default" msgid="4415328591568654603">"Setel sebagai default"</string>
+    <string name="settings" msgid="6536394145760913145">"Setelan"</string>
     <string name="use_once" msgid="9027366575315399714">"Gunakan sekali"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> sandi • <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> kunci sandi"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> sandi"</string>
@@ -49,21 +66,30 @@
     <string name="other_password_manager" msgid="565790221427004141">"Pengelola sandi lainnya"</string>
     <string name="close_sheet" msgid="1393792015338908262">"Tutup sheet"</string>
     <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Kembali ke halaman sebelumnya"</string>
-    <string name="accessibility_close_button" msgid="2953807735590034688">"Tutup saran tindakan Pengelola Kredensial yang muncul di bagian bawah layar"</string>
+    <string name="accessibility_close_button" msgid="1163435587545377687">"Tutup"</string>
+    <string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"Tutup"</string>
     <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Gunakan kunci sandi tersimpan untuk <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
-    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Gunakan info login tersimpan untuk <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
-    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Pilih info login tersimpan untuk <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_use_password_for" msgid="625828023234318484">"Gunakan sandi tersimpan untuk <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"Gunakan login Anda untuk <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"Autentikasi opsi login untuk <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"Pilih kunci sandi tersimpan untuk <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"Pilih sandi tersimpan untuk <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_saved_sign_in_for" msgid="2420298653461652728">"Pilih info login tersimpan untuk <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="3048870756117876514">"Pilih login untuk <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_option_for" msgid="4976380044745029107">"Pilih opsi untuk <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_info_on" msgid="8863708099535435146">"Gunakan info ini di <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Login dengan cara lain"</string>
     <string name="snackbar_action" msgid="37373514216505085">"Lihat opsi"</string>
     <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Lanjutkan"</string>
     <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Opsi login"</string>
+    <string name="button_label_view_more" msgid="3429098227286495651">"Tampilkan lainnya"</string>
     <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Untuk <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Pengelola sandi terkunci"</string>
-    <!-- no translation found for locked_credential_entry_label_subtext_tap_to_unlock (6390367581393605009) -->
-    <skip />
-    <!-- no translation found for locked_credential_entry_label_subtext_no_sign_in (8131725029983174901) -->
-    <skip />
+    <string name="locked_credential_entry_label_subtext_tap_to_unlock" msgid="6390367581393605009">"Ketuk untuk membuka kunci"</string>
+    <string name="locked_credential_entry_label_subtext_no_sign_in" msgid="8131725029983174901">"Tidak ada info login"</string>
+    <string name="no_sign_in_info_in" msgid="2641118151920288356">"Tidak ada info login di <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Kelola login"</string>
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Dari perangkat lain"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Gunakan perangkat lain"</string>
+    <string name="request_cancelled_by" msgid="3735222326886267820">"Permintaan dibatalkan oleh <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
 </resources>
diff --git a/packages/CredentialManager/res/values-is/strings.xml b/packages/CredentialManager/res/values-is/strings.xml
index 5339ece..76a869f 100644
--- a/packages/CredentialManager/res/values-is/strings.xml
+++ b/packages/CredentialManager/res/values-is/strings.xml
@@ -1,22 +1,36 @@
 <?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.
+   -->
+
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="4539824758261855508">"Skilríkjastjórnun"</string>
     <string name="string_cancel" msgid="6369133483981306063">"Hætta við"</string>
     <string name="string_continue" msgid="1346732695941131882">"Áfram"</string>
-    <string name="string_more_options" msgid="7990658711962795124">"Fleiri valkostir"</string>
+    <string name="string_more_options" msgid="2763852250269945472">"Vista á annan hátt"</string>
     <string name="string_learn_more" msgid="4541600451688392447">"Nánar"</string>
-    <!-- no translation found for content_description_show_password (3283502010388521607) -->
-    <skip />
-    <!-- no translation found for content_description_hide_password (6841375971631767996) -->
-    <skip />
+    <string name="content_description_show_password" msgid="3283502010388521607">"Sýna aðgangsorð"</string>
+    <string name="content_description_hide_password" msgid="6841375971631767996">"Fela aðgangsorð"</string>
     <string name="passkey_creation_intro_title" msgid="4251037543787718844">"Aukið öryggi með aðgangslyklum"</string>
     <string name="passkey_creation_intro_body_password" msgid="8825872426579958200">"Með aðgangslyklum þarftu hvorki að búa til né muna flókin aðgangsorð"</string>
     <string name="passkey_creation_intro_body_fingerprint" msgid="7331338631826254055">"Aðgangslyklar eru dulkóðaðir stafrænir lyklar sem þú býrð til með fingrafarinu þínu, andliti eða skjálás."</string>
     <string name="passkey_creation_intro_body_device" msgid="1203796455762131631">"Þeir eru vistaðir í aðgangsorðastjórnun svo þú getir skráð þig inn í öðrum tækjum"</string>
     <string name="more_about_passkeys_title" msgid="7797903098728837795">"Nánar um aðgangslykla"</string>
     <string name="passwordless_technology_title" msgid="2497513482056606668">"Aðgangsorðalaus tækni"</string>
-    <string name="passwordless_technology_detail" msgid="6853928846532955882">"Aðgangslyklar gera þér kleift að skrá þig inn án þess að þurfa aðgangsorð. Þú þarft aðeins og nota fingrafarið, andlitsgreiningu, PIN-númer eða strokmynstur til að staðfesta hver þú ert og búa til aðgangslykil."</string>
+    <string name="passwordless_technology_detail" msgid="6853928846532955882">"Aðgangslyklar gera þér kleift að skrá þig inn án þess að þurfa aðgangsorð. Þú þarft aðeins að nota fingrafarið, andlitsgreiningu, PIN-númer eða strokmynstur til að staðfesta hver þú ert og búa til aðgangslykil."</string>
     <string name="public_key_cryptography_title" msgid="6751970819265298039">"Dulritun opinberra lykla"</string>
     <string name="public_key_cryptography_detail" msgid="6937631710280562213">"Aðgangslyklar nota dulkóðuð lyklasett byggð á stöðlum FIDO Alliance (sem nær til Google, Apple, Microsoft og fleiri) og W3C. Ólíkt notandanafni og stafarunu aðgangsorðs er lyklasett opinbers lykils og einkalykils búið til fyrir forrit eða vefsvæði. Einkalykillinn er vistaður á öruggan hátt í tækinu eða í aðgangsorðastjórnun og hann staðfestir auðkenni þitt. Opinbera lyklinum er deilt með þjóni forritsins eða vefsvæðisins. Samsvarandi lyklar tryggja tafarlausa skráningu og innskráningu."</string>
     <string name="improved_account_security_title" msgid="1069841917893513424">"Bætt reikningsöryggi"</string>
@@ -30,15 +44,18 @@
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Viltu vista innskráningarupplýsingar fyrir <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="passkey" msgid="632353688396759522">"aðgangslykill"</string>
     <string name="password" msgid="6738570945182936667">"aðgangsorð"</string>
-    <string name="passkeys" msgid="5733880786866559847">"aðgangslyklar"</string>
+    <string name="passkeys" msgid="5733880786866559847">"aðgangslykla"</string>
     <string name="passwords" msgid="5419394230391253816">"aðgangsorð"</string>
     <string name="sign_ins" msgid="4710739369149469208">"innskráningar"</string>
     <string name="sign_in_info" msgid="2627704710674232328">"innskráningarupplýsingar"</string>
     <string name="save_credential_to_title" msgid="3172811692275634301">"Vista <xliff:g id="CREDENTIALTYPES">%1$s</xliff:g> í"</string>
-    <string name="create_passkey_in_other_device_title" msgid="9195411122362461390">"Viltu búa til aðgangslykil í öðru tæki?"</string>
+    <string name="create_passkey_in_other_device_title" msgid="2360053098931886245">"Búa til aðgangslykil í öðru tæki?"</string>
+    <string name="save_password_on_other_device_title" msgid="5829084591948321207">"Vista aðgangsorð í öðru tæki?"</string>
+    <string name="save_sign_in_on_other_device_title" msgid="2827990118560134692">"Vista innskráningu í öðru tæki?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"Nota <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> fyrir allar innskráningar?"</string>
-    <string name="use_provider_for_all_description" msgid="8466427781848268490">"Þessi aðgangsorðastjórnun vistar aðgangsorð og aðgangslykla til að auðvelda þér að skrá þig inn"</string>
+    <string name="use_provider_for_all_description" msgid="1998772715863958997">"Þessi aðgangsorðastjórnun fyrir <xliff:g id="USERNAME">%1$s</xliff:g> vistar aðgangsorð og aðgangslykla til að auðvelda þér að skrá þig inn"</string>
     <string name="set_as_default" msgid="4415328591568654603">"Stilla sem sjálfgefið"</string>
+    <string name="settings" msgid="6536394145760913145">"Stillingar"</string>
     <string name="use_once" msgid="9027366575315399714">"Nota einu sinni"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> aðgangsorð • <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> aðgangslyklar"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> aðgangsorð"</string>
@@ -49,21 +66,30 @@
     <string name="other_password_manager" msgid="565790221427004141">"Önnur aðgangsorðastjórnun"</string>
     <string name="close_sheet" msgid="1393792015338908262">"Loka blaði"</string>
     <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Fara aftur á fyrri síðu"</string>
-    <string name="accessibility_close_button" msgid="2953807735590034688">"Loka aðgerðartillögu frá skilríkjastjórnun sem birtist neðst á skjánum"</string>
-    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Notað vistaðan aðgangslykil fyrir <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
-    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Nota vistaða innskráningu fyrir <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
-    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Veldu vistaða innskráningu fyrir <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="accessibility_close_button" msgid="1163435587545377687">"Loka"</string>
+    <string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"Hunsa"</string>
+    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Nota vistaðan aðgangslykil fyrir <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_password_for" msgid="625828023234318484">"Nota vistaða aðgangsorðið þitt fyrir <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"Nota innskráningu fyrir <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"Opna fyrir innskráningarvalkosti fyrir <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"Veldu vistaðan aðgangslykil fyrir <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"Veldu vistað aðgangsorð fyrir <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_saved_sign_in_for" msgid="2420298653461652728">"Veldu vistaða innskráningu fyrir <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="3048870756117876514">"Veldu innskráningu fyrir <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_option_for" msgid="4976380044745029107">"Velja valkost fyrir <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_info_on" msgid="8863708099535435146">"Nota þessar upplýsingar í <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Skrá inn með öðrum hætti"</string>
     <string name="snackbar_action" msgid="37373514216505085">"Skoða valkosti"</string>
     <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Áfram"</string>
     <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Innskráningarkostir"</string>
+    <string name="button_label_view_more" msgid="3429098227286495651">"Nánar"</string>
     <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Fyrir: <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Læst aðgangsorðastjórnun"</string>
-    <!-- no translation found for locked_credential_entry_label_subtext_tap_to_unlock (6390367581393605009) -->
-    <skip />
-    <!-- no translation found for locked_credential_entry_label_subtext_no_sign_in (8131725029983174901) -->
-    <skip />
+    <string name="locked_credential_entry_label_subtext_tap_to_unlock" msgid="6390367581393605009">"Ýttu til að opna"</string>
+    <string name="locked_credential_entry_label_subtext_no_sign_in" msgid="8131725029983174901">"Engar innskráningarupplýsingar"</string>
+    <string name="no_sign_in_info_in" msgid="2641118151920288356">"Engar innskráningarupplýsingar á <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Stjórna innskráningu"</string>
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Úr öðru tæki"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Nota annað tæki"</string>
+    <string name="request_cancelled_by" msgid="3735222326886267820">"<xliff:g id="APP_NAME">%1$s</xliff:g> hætti við beiðnina"</string>
 </resources>
diff --git a/packages/CredentialManager/res/values-it/strings.xml b/packages/CredentialManager/res/values-it/strings.xml
index d9b67fe..87045cb 100644
--- a/packages/CredentialManager/res/values-it/strings.xml
+++ b/packages/CredentialManager/res/values-it/strings.xml
@@ -1,10 +1,26 @@
 <?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="4539824758261855508">"Gestore delle credenziali"</string>
     <string name="string_cancel" msgid="6369133483981306063">"Annulla"</string>
     <string name="string_continue" msgid="1346732695941131882">"Continua"</string>
-    <string name="string_more_options" msgid="7990658711962795124">"Altre opzioni"</string>
+    <string name="string_more_options" msgid="2763852250269945472">"Salva un altro modo"</string>
     <string name="string_learn_more" msgid="4541600451688392447">"Scopri di più"</string>
     <string name="content_description_show_password" msgid="3283502010388521607">"Mostra password"</string>
     <string name="content_description_hide_password" msgid="6841375971631767996">"Nascondi password"</string>
@@ -14,14 +30,14 @@
     <string name="passkey_creation_intro_body_device" msgid="1203796455762131631">"Vengono salvate in un gestore delle password, così potrai accedere su altri dispositivi"</string>
     <string name="more_about_passkeys_title" msgid="7797903098728837795">"Scopri di più sulle passkey"</string>
     <string name="passwordless_technology_title" msgid="2497513482056606668">"Tecnologia senza password"</string>
-    <string name="passwordless_technology_detail" msgid="6853928846532955882">"Le passkey ti consentono di accedere senza usare le password. Devi soltanto usare la tua impronta, il riconoscimento del volto, il tuo PIN o la tua sequenza per verificare la tua identità e creare una passkey."</string>
+    <string name="passwordless_technology_detail" msgid="6853928846532955882">"Le passkey ti consentono di accedere senza usare le password. Basta usare l\'impronta, il riconoscimento del volto, il PIN o la sequenza per verificare la tua identità e creare una passkey."</string>
     <string name="public_key_cryptography_title" msgid="6751970819265298039">"Crittografia a chiave pubblica"</string>
-    <string name="public_key_cryptography_detail" msgid="6937631710280562213">"Basate su standard FIDO Alliance (che include Google, Apple, Microsoft e non solo) e W3C, le passkey usano coppie di chiavi di crittografia. Diversamente dal nome utente e dalla stringa di caratteri usata per le password, per un\'app o un sito web viene creata una coppia di chiavi (privata e pubblica). La chiave privata viene memorizzata in sicurezza sul dispositivo o nel gestore delle password e conferma la tua identità. La chiave pubblica viene condivisa con il server dell\'app o del sito. Con chiavi corrispondenti puoi registrarti e accedere subito."</string>
-    <string name="improved_account_security_title" msgid="1069841917893513424">"Sicurezza degli account migliorata"</string>
+    <string name="public_key_cryptography_detail" msgid="6937631710280562213">"Basate su standard FIDO Alliance (che include Google, Apple, Microsoft e non solo) e W3C, le passkey usano coppie di chiavi di crittografia. Diversamente dal nome utente e dalla stringa di caratteri usata per le password, per un\'app o un sito web viene creata una coppia di chiavi (privata e pubblica). La chiave privata viene memorizzata in sicurezza sul dispositivo o nel gestore delle password e conferma la tua identità. La chiave pubblica viene condivisa con il server dell\'app o del sito web. Con chiavi corrispondenti puoi registrarti e accedere subito."</string>
+    <string name="improved_account_security_title" msgid="1069841917893513424">"Account ancora più sicuri"</string>
     <string name="improved_account_security_detail" msgid="9123750251551844860">"Ogni chiave è collegata in modo esclusivo all\'app o al sito web per cui è stata creata, quindi non puoi mai accedere a un\'app o un sito web fraudolenti per sbaglio. Inoltre, le compromissioni diventano molto più difficili perché i server conservano soltanto le chiavi pubbliche."</string>
-    <string name="seamless_transition_title" msgid="5335622196351371961">"Transizione senza interruzioni"</string>
-    <string name="seamless_transition_detail" msgid="4475509237171739843">"Mentre ci dirigiamo verso un futuro senza password, queste ultime saranno ancora disponibili insieme alle passkey."</string>
-    <string name="choose_provider_title" msgid="8870795677024868108">"Scegli dove salvare: <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <string name="seamless_transition_title" msgid="5335622196351371961">"Transizione graduale"</string>
+    <string name="seamless_transition_detail" msgid="4475509237171739843">"Il futuro sarà senza password, ma per ora saranno ancora disponibili insieme alle passkey."</string>
+    <string name="choose_provider_title" msgid="8870795677024868108">"Scegli dove salvare le <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"Seleziona un gestore delle password per salvare i tuoi dati e accedere più velocemente la prossima volta"</string>
     <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"Vuoi creare una passkey per <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_password_title" msgid="7097275038523578687">"Vuoi salvare la password di <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
@@ -33,10 +49,13 @@
     <string name="sign_ins" msgid="4710739369149469208">"accessi"</string>
     <string name="sign_in_info" msgid="2627704710674232328">"dati di accesso"</string>
     <string name="save_credential_to_title" msgid="3172811692275634301">"Salva <xliff:g id="CREDENTIALTYPES">%1$s</xliff:g> in"</string>
-    <string name="create_passkey_in_other_device_title" msgid="9195411122362461390">"Creare la passkey in un altro dispositivo?"</string>
+    <string name="create_passkey_in_other_device_title" msgid="2360053098931886245">"Vuoi creare la passkey su un altro dispositivo?"</string>
+    <string name="save_password_on_other_device_title" msgid="5829084591948321207">"Vuoi salvare la password su un altro dispositivo?"</string>
+    <string name="save_sign_in_on_other_device_title" msgid="2827990118560134692">"Vuoi salvare la credenziale di accesso su un altro dispositivo?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"Vuoi usare <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> per tutti gli accessi?"</string>
-    <string name="use_provider_for_all_description" msgid="8466427781848268490">"Questo gestore delle password archivierà le password e le passkey per aiutarti ad accedere facilmente"</string>
+    <string name="use_provider_for_all_description" msgid="1998772715863958997">"Questo gestore delle password di <xliff:g id="USERNAME">%1$s</xliff:g> archivierà le password e le passkey per aiutarti ad accedere facilmente"</string>
     <string name="set_as_default" msgid="4415328591568654603">"Imposta come valore predefinito"</string>
+    <string name="settings" msgid="6536394145760913145">"Impostazioni"</string>
     <string name="use_once" msgid="9027366575315399714">"Usa una volta"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> password • <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> passkey"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> password"</string>
@@ -47,21 +66,30 @@
     <string name="other_password_manager" msgid="565790221427004141">"Altri gestori delle password"</string>
     <string name="close_sheet" msgid="1393792015338908262">"Chiudi il foglio"</string>
     <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Torna alla pagina precedente"</string>
-    <string name="accessibility_close_button" msgid="2953807735590034688">"Chiudi l\'azione suggerita di Gestore delle credenziali visualizzata nella parte inferiore dello schermo"</string>
+    <string name="accessibility_close_button" msgid="1163435587545377687">"Chiudi"</string>
+    <string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"Chiudi"</string>
     <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Vuoi usare la passkey salvata per <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
-    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Vuoi usare l\'accesso salvato per <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
-    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Scegli un accesso salvato per <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_use_password_for" msgid="625828023234318484">"Vuoi usare la password salvata per <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"Vuoi usare il tuo accesso per <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"Vuoi sbloccare le opzioni di accesso per <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"Scegli una passkey salvata per <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"Scegli una password salvata per <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_saved_sign_in_for" msgid="2420298653461652728">"Scegli un accesso salvato per <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="3048870756117876514">"Scegli un accesso per <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_option_for" msgid="4976380044745029107">"Vuoi scegliere un\'opzione per <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_info_on" msgid="8863708099535435146">"Vuoi usare questi dati su <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Accedi in un altro modo"</string>
     <string name="snackbar_action" msgid="37373514216505085">"Visualizza opzioni"</string>
     <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Continua"</string>
     <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Opzioni di accesso"</string>
+    <string name="button_label_view_more" msgid="3429098227286495651">"Mostra altro"</string>
     <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Per <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Gestori delle password bloccati"</string>
-    <!-- no translation found for locked_credential_entry_label_subtext_tap_to_unlock (6390367581393605009) -->
-    <skip />
-    <!-- no translation found for locked_credential_entry_label_subtext_no_sign_in (8131725029983174901) -->
-    <skip />
+    <string name="locked_credential_entry_label_subtext_tap_to_unlock" msgid="6390367581393605009">"Tocca per sbloccare"</string>
+    <string name="locked_credential_entry_label_subtext_no_sign_in" msgid="8131725029983174901">"Nessuna informazione di accesso"</string>
+    <string name="no_sign_in_info_in" msgid="2641118151920288356">"Non sono presenti dati di accesso in <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Gestisci gli accessi"</string>
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Da un altro dispositivo"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Usa un dispositivo diverso"</string>
+    <string name="request_cancelled_by" msgid="3735222326886267820">"Richiesta annullata da <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
 </resources>
diff --git a/packages/CredentialManager/res/values-iw/strings.xml b/packages/CredentialManager/res/values-iw/strings.xml
index 900c037..0643568 100644
--- a/packages/CredentialManager/res/values-iw/strings.xml
+++ b/packages/CredentialManager/res/values-iw/strings.xml
@@ -1,10 +1,26 @@
 <?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="4539824758261855508">"מנהל פרטי הכניסה"</string>
     <string name="string_cancel" msgid="6369133483981306063">"ביטול"</string>
     <string name="string_continue" msgid="1346732695941131882">"המשך"</string>
-    <string name="string_more_options" msgid="7990658711962795124">"אפשרויות נוספות"</string>
+    <string name="string_more_options" msgid="2763852250269945472">"שמירה בדרך אחרת"</string>
     <string name="string_learn_more" msgid="4541600451688392447">"מידע נוסף"</string>
     <string name="content_description_show_password" msgid="3283502010388521607">"הצגת הסיסמה"</string>
     <string name="content_description_hide_password" msgid="6841375971631767996">"הסתרת הסיסמה"</string>
@@ -16,7 +32,7 @@
     <string name="passwordless_technology_title" msgid="2497513482056606668">"טכנולוגיה ללא סיסמאות"</string>
     <string name="passwordless_technology_detail" msgid="6853928846532955882">"מפתחות גישה מאפשרים לך להיכנס לחשבון בלי להסתמך על סיסמאות. עליך רק להשתמש בטביעת אצבע, בזיהוי הפנים, בקוד אימות או בקו ביטול נעילה כדי לאמת את זהותך וליצור מפתח גישה."</string>
     <string name="public_key_cryptography_title" msgid="6751970819265298039">"קריפטוגרפיה של מפתח ציבורי"</string>
-    <string name="public_key_cryptography_detail" msgid="6937631710280562213">"‏על סמך תקן FIDO‏ (ארגון שכולל חברות כמו Google,‏ Apple,‏ Microsoft ועוד) ותקני W3C, מפתחות הגישה מבוססים על זוגות של מפתחות קריפטוגרפיים. בניגוד למחרוזות תווים ולשם המשתמש שבהם אנחנו משתמשים בסיסמאות, זוג מפתחות – מפתח פרטי ומפתח ציבורי – נוצר עבור אפליקציה או אתר. המפתח הפרטי מאוחסן בבטחה במכשיר או במנהל הסיסמאות שלך, ומאמת את זהותך. המפתח הציבורי משותף עם השרת של האפליקציה או האתר. בעזרת המפתחות התואמים אפשר מיד להירשם ולהיכנס לחשבון."</string>
+    <string name="public_key_cryptography_detail" msgid="6937631710280562213">"‏על סמך תקן FIDO‏ (ארגון שכולל חברות כמו Google,‏ Apple,‏ Microsoft ועוד) ותקני W3C, מפתחות הגישה מבוססים על זוגות של מפתחות קריפטוגרפיים. בניגוד לשם המשתמש ולמחרוזת התווים ששמשמשת אצלנו בתור סיסמה, בתקן הזה נוצר זוג מפתחות – מפתח פרטי ומפתח ציבורי – עבור אפליקציה או אתר. המפתח הפרטי מאוחסן בבטחה במכשיר או במנהל הסיסמאות שלך, ומאמת את זהותך. המפתח הציבורי משותף עם השרת של האפליקציה או האתר. בעזרת המפתחות התואמים אפשר מיד להירשם ולהיכנס לחשבון."</string>
     <string name="improved_account_security_title" msgid="1069841917893513424">"אבטחה טובה יותר של החשבון"</string>
     <string name="improved_account_security_detail" msgid="9123750251551844860">"כל מפתח מקושר אך ורק לאפליקציה או לאתר שעבורם הוא נוצר, ולכן אף פעם אי אפשר להיכנס בטעות לחשבון באפליקציה או באתר שמטרתם להונות. בנוסף, כיוון שהשרתים שומרים רק מפתחות ציבוריים, קשה יותר לפרוץ לחשבון."</string>
     <string name="seamless_transition_title" msgid="5335622196351371961">"מעבר חלק"</string>
@@ -33,10 +49,13 @@
     <string name="sign_ins" msgid="4710739369149469208">"פרטי כניסה"</string>
     <string name="sign_in_info" msgid="2627704710674232328">"פרטי הכניסה"</string>
     <string name="save_credential_to_title" msgid="3172811692275634301">"שמירת <xliff:g id="CREDENTIALTYPES">%1$s</xliff:g> ב-"</string>
-    <string name="create_passkey_in_other_device_title" msgid="9195411122362461390">"ליצור מפתח גישה במכשיר אחר?"</string>
+    <string name="create_passkey_in_other_device_title" msgid="2360053098931886245">"ליצור מפתח גישה במכשיר אחר?"</string>
+    <string name="save_password_on_other_device_title" msgid="5829084591948321207">"לשמור את הסיסמה במכשיר אחר?"</string>
+    <string name="save_sign_in_on_other_device_title" msgid="2827990118560134692">"לשמור את פרטי הכניסה במכשיר אחר?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"להשתמש ב-<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> בכל הכניסות?"</string>
-    <string name="use_provider_for_all_description" msgid="8466427781848268490">"במנהל הסיסמאות הזה יאוחסנו הסיסמאות ומפתחות הגישה שלך, כדי לעזור לך להיכנס לחשבון בקלות"</string>
+    <string name="use_provider_for_all_description" msgid="1998772715863958997">"מנהל הסיסמאות הזה של <xliff:g id="USERNAME">%1$s</xliff:g> יאחסן את הסיסמאות ומפתחות הגישה שלך, כדי לעזור לך להיכנס לחשבון בקלות"</string>
     <string name="set_as_default" msgid="4415328591568654603">"הגדרה כברירת מחדל"</string>
+    <string name="settings" msgid="6536394145760913145">"הגדרות"</string>
     <string name="use_once" msgid="9027366575315399714">"שימוש פעם אחת"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> סיסמאות • <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> מפתחות גישה"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> סיסמאות"</string>
@@ -47,21 +66,30 @@
     <string name="other_password_manager" msgid="565790221427004141">"מנהלי סיסמאות אחרים"</string>
     <string name="close_sheet" msgid="1393792015338908262">"סגירת הגיליון"</string>
     <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"חזרה לדף הקודם"</string>
-    <string name="accessibility_close_button" msgid="2953807735590034688">"הצעה לפעולה \'סגירה של מנהל פרטי הכניסה\' שמופיעה בתחתית המסך"</string>
+    <string name="accessibility_close_button" msgid="1163435587545377687">"סגירה"</string>
+    <string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"סגירה"</string>
     <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"להשתמש במפתח גישה שנשמר עבור <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
-    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"להשתמש בפרטי הכניסה שנשמרו עבור <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
-    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"בחירת פרטי כניסה שמורים עבור <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_use_password_for" msgid="625828023234318484">"להשתמש בסיסמה השמורה של <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"להשתמש בחשבון שלך כדי להיכנס אל <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"לבטל את הנעילה של אפשרויות הכניסה אל <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"בחירת מפתח גישה שמור ל-<xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"בחירת סיסמה שמורה ל-<xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_saved_sign_in_for" msgid="2420298653461652728">"בחירת פרטי כניסה שמורים ל-<xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="3048870756117876514">"בחירת חשבון לכניסה אל <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_option_for" msgid="4976380044745029107">"רוצה לבחור אפשרות עבור <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_info_on" msgid="8863708099535435146">"להשתמש במידע הזה בשביל <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"כניסה בדרך אחרת"</string>
     <string name="snackbar_action" msgid="37373514216505085">"הצגת האפשרויות"</string>
     <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"המשך"</string>
     <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"אפשרויות כניסה לחשבון"</string>
+    <string name="button_label_view_more" msgid="3429098227286495651">"עוד מידע"</string>
     <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"עבור <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"מנהלי סיסמאות נעולים"</string>
-    <!-- no translation found for locked_credential_entry_label_subtext_tap_to_unlock (6390367581393605009) -->
-    <skip />
-    <!-- no translation found for locked_credential_entry_label_subtext_no_sign_in (8131725029983174901) -->
-    <skip />
+    <string name="locked_credential_entry_label_subtext_tap_to_unlock" msgid="6390367581393605009">"יש להקיש כדי לבטל את הנעילה"</string>
+    <string name="locked_credential_entry_label_subtext_no_sign_in" msgid="8131725029983174901">"אין פרטי כניסה"</string>
+    <string name="no_sign_in_info_in" msgid="2641118151920288356">"אין פרטי כניסה ב-<xliff:g id="SOURCE">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"ניהול כניסות"</string>
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"ממכשיר אחר"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"צריך להשתמש במכשיר אחר"</string>
+    <string name="request_cancelled_by" msgid="3735222326886267820">"האפליקציה <xliff:g id="APP_NAME">%1$s</xliff:g> ביטלה את הבקשה"</string>
 </resources>
diff --git a/packages/CredentialManager/res/values-ja/strings.xml b/packages/CredentialManager/res/values-ja/strings.xml
index 48d73e4..afbff90 100644
--- a/packages/CredentialManager/res/values-ja/strings.xml
+++ b/packages/CredentialManager/res/values-ja/strings.xml
@@ -1,15 +1,29 @@
 <?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.
+   -->
+
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="4539824758261855508">"認証情報マネージャー"</string>
     <string name="string_cancel" msgid="6369133483981306063">"キャンセル"</string>
     <string name="string_continue" msgid="1346732695941131882">"続行"</string>
-    <string name="string_more_options" msgid="7990658711962795124">"その他のオプション"</string>
+    <string name="string_more_options" msgid="2763852250269945472">"別の方法を保存"</string>
     <string name="string_learn_more" msgid="4541600451688392447">"詳細"</string>
-    <!-- no translation found for content_description_show_password (3283502010388521607) -->
-    <skip />
-    <!-- no translation found for content_description_hide_password (6841375971631767996) -->
-    <skip />
+    <string name="content_description_show_password" msgid="3283502010388521607">"パスワードを表示する"</string>
+    <string name="content_description_hide_password" msgid="6841375971631767996">"パスワードを表示しない"</string>
     <string name="passkey_creation_intro_title" msgid="4251037543787718844">"パスキーでより安全に"</string>
     <string name="passkey_creation_intro_body_password" msgid="8825872426579958200">"パスキーがあれば、複雑なパスワードを作成したり覚えたりする必要はありません"</string>
     <string name="passkey_creation_intro_body_fingerprint" msgid="7331338631826254055">"パスキーは、指紋認証、顔認証、または画面ロックを使って作成される暗号化されたデジタルキーです"</string>
@@ -23,7 +37,7 @@
     <string name="improved_account_security_detail" msgid="9123750251551844860">"作成された各鍵は、対象となるアプリまたはウェブサイトのみとリンクされるため、間違って不正なアプリやウェブサイトにログインすることはありません。さらに、公開鍵はサーバーのみに保存されるため、ハッキングのリスクも大幅に抑えられます。"</string>
     <string name="seamless_transition_title" msgid="5335622196351371961">"シームレスな移行"</string>
     <string name="seamless_transition_detail" msgid="4475509237171739843">"将来的にパスワードレスに移行するにあたり、パスワードもパスキーと並行して引き続きご利用いただけます。"</string>
-    <string name="choose_provider_title" msgid="8870795677024868108">"<xliff:g id="CREATETYPES">%1$s</xliff:g> の保存先の選択"</string>
+    <string name="choose_provider_title" msgid="8870795677024868108">"<xliff:g id="CREATETYPES">%1$s</xliff:g>の保存先を選択"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"パスワード マネージャーを選択して情報を保存しておくと、次回からすばやくログインできます"</string>
     <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"<xliff:g id="APPNAME">%1$s</xliff:g> のパスキーを作成しますか?"</string>
     <string name="choose_create_option_password_title" msgid="7097275038523578687">"<xliff:g id="APPNAME">%1$s</xliff:g> のパスワードを保存しますか?"</string>
@@ -35,10 +49,13 @@
     <string name="sign_ins" msgid="4710739369149469208">"ログイン"</string>
     <string name="sign_in_info" msgid="2627704710674232328">"ログイン情報"</string>
     <string name="save_credential_to_title" msgid="3172811692275634301">"<xliff:g id="CREDENTIALTYPES">%1$s</xliff:g>の保存先"</string>
-    <string name="create_passkey_in_other_device_title" msgid="9195411122362461390">"別のデバイスにパスキーを作成しますか?"</string>
+    <string name="create_passkey_in_other_device_title" msgid="2360053098931886245">"別のデバイスでパスキーを作成しますか?"</string>
+    <string name="save_password_on_other_device_title" msgid="5829084591948321207">"別のデバイスでパスワードを保存しますか?"</string>
+    <string name="save_sign_in_on_other_device_title" msgid="2827990118560134692">"別のデバイスでログインを保存しますか?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"ログインのたびに <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> を使用しますか?"</string>
-    <string name="use_provider_for_all_description" msgid="8466427781848268490">"このパスワード マネージャーに、パスワードやパスキーが保存され、簡単にログインできるようになります"</string>
+    <string name="use_provider_for_all_description" msgid="1998772715863958997">"<xliff:g id="USERNAME">%1$s</xliff:g> のパスワード マネージャーにパスワードやパスキーが保存され、簡単にログインできるようになります"</string>
     <string name="set_as_default" msgid="4415328591568654603">"デフォルトに設定"</string>
+    <string name="settings" msgid="6536394145760913145">"設定"</string>
     <string name="use_once" msgid="9027366575315399714">"1 回使用"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> 件のパスワード • <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> 件のパスキー"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> 件のパスワード"</string>
@@ -49,21 +66,30 @@
     <string name="other_password_manager" msgid="565790221427004141">"他のパスワード マネージャー"</string>
     <string name="close_sheet" msgid="1393792015338908262">"シートを閉じます"</string>
     <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"前のページに戻ります"</string>
-    <string name="accessibility_close_button" msgid="2953807735590034688">"画面下に表示されている認証情報マネージャーのおすすめの操作を閉じます"</string>
+    <string name="accessibility_close_button" msgid="1163435587545377687">"閉じる"</string>
+    <string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"閉じる"</string>
     <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"<xliff:g id="APP_NAME">%1$s</xliff:g> の保存したパスキーを使用しますか?"</string>
-    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"<xliff:g id="APP_NAME">%1$s</xliff:g> の保存したログイン情報を使用しますか?"</string>
-    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"<xliff:g id="APP_NAME">%1$s</xliff:g> の保存したログイン情報の選択"</string>
+    <string name="get_dialog_title_use_password_for" msgid="625828023234318484">"<xliff:g id="APP_NAME">%1$s</xliff:g> の保存したパスワードを使用しますか?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"この認証情報を使用して <xliff:g id="APP_NAME">%1$s</xliff:g> にログインしますか?"</string>
+    <string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"<xliff:g id="APP_NAME">%1$s</xliff:g> がログイン方法を使用できるようにしますか?"</string>
+    <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"<xliff:g id="APP_NAME">%1$s</xliff:g> に使用するパスキーを選択してください"</string>
+    <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"<xliff:g id="APP_NAME">%1$s</xliff:g> に使用するパスワードを選択してください"</string>
+    <string name="get_dialog_title_choose_saved_sign_in_for" msgid="2420298653461652728">"<xliff:g id="APP_NAME">%1$s</xliff:g> に使用するログイン情報を選択してください"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="3048870756117876514">"<xliff:g id="APP_NAME">%1$s</xliff:g> のログインに使用する認証情報の選択"</string>
+    <string name="get_dialog_title_choose_option_for" msgid="4976380044745029107">"<xliff:g id="APP_NAME">%1$s</xliff:g> のオプションを選択しますか?"</string>
+    <string name="get_dialog_title_use_info_on" msgid="8863708099535435146">"<xliff:g id="APP_NAME">%1$s</xliff:g> でこの情報を使用しますか?"</string>
     <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"別の方法でログイン"</string>
     <string name="snackbar_action" msgid="37373514216505085">"オプションを表示"</string>
     <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"続行"</string>
     <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"ログイン オプション"</string>
+    <string name="button_label_view_more" msgid="3429098227286495651">"さらに表示"</string>
     <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"<xliff:g id="USERNAME">%1$s</xliff:g> 用"</string>
     <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"パスワード マネージャー ロック中"</string>
-    <!-- no translation found for locked_credential_entry_label_subtext_tap_to_unlock (6390367581393605009) -->
-    <skip />
-    <!-- no translation found for locked_credential_entry_label_subtext_no_sign_in (8131725029983174901) -->
-    <skip />
+    <string name="locked_credential_entry_label_subtext_tap_to_unlock" msgid="6390367581393605009">"タップしてロック解除"</string>
+    <string name="locked_credential_entry_label_subtext_no_sign_in" msgid="8131725029983174901">"ログイン情報がありません"</string>
+    <string name="no_sign_in_info_in" msgid="2641118151920288356">"<xliff:g id="SOURCE">%1$s</xliff:g> のログイン情報はありません"</string>
     <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"ログインを管理"</string>
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"別のデバイスを使う"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"別のデバイスを使用"</string>
+    <string name="request_cancelled_by" msgid="3735222326886267820">"<xliff:g id="APP_NAME">%1$s</xliff:g> がリクエストをキャンセルしました"</string>
 </resources>
diff --git a/packages/CredentialManager/res/values-ka/strings.xml b/packages/CredentialManager/res/values-ka/strings.xml
index 15b7c98..b597a9a 100644
--- a/packages/CredentialManager/res/values-ka/strings.xml
+++ b/packages/CredentialManager/res/values-ka/strings.xml
@@ -1,10 +1,26 @@
 <?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="4539824758261855508">"ავტორიზაციის მონაცემების მმართველი"</string>
     <string name="string_cancel" msgid="6369133483981306063">"გაუქმება"</string>
     <string name="string_continue" msgid="1346732695941131882">"გაგრძელება"</string>
-    <string name="string_more_options" msgid="7990658711962795124">"სხვა ვარიანტები"</string>
+    <string name="string_more_options" msgid="2763852250269945472">"სხვაგვარად შენახვა"</string>
     <string name="string_learn_more" msgid="4541600451688392447">"შეიტყვეთ მეტი"</string>
     <string name="content_description_show_password" msgid="3283502010388521607">"პაროლის ჩვენება"</string>
     <string name="content_description_hide_password" msgid="6841375971631767996">"პაროლის დამალვა"</string>
@@ -16,7 +32,7 @@
     <string name="passwordless_technology_title" msgid="2497513482056606668">"უპაროლო ტექნოლოგია"</string>
     <string name="passwordless_technology_detail" msgid="6853928846532955882">"წვდომის გასაღებები საშუალებას გაძლევთ, სისტემაში შეხვიდეთ პაროლის გარეშე. უბრალოდ, ვინაობის დასადასტურებლად და წვდომის გასაღების შესაქმნელად უნდა გამოიყენოთ თითის ანაბეჭდი, სახით ამოცნობა, PIN-კოდი, ან განბლოკვის გრაფიკული გასაღები."</string>
     <string name="public_key_cryptography_title" msgid="6751970819265298039">"საჯარო გასაღების კრიპტოგრაფია"</string>
-    <string name="public_key_cryptography_detail" msgid="6937631710280562213">"FIDO-ს ალიანსისა (Google, Apple, Microsoft და ა.შ.) და W3C-ის სტანდარტების შესაბამისად, წვდომის გასაღებები იყენებს კრიპტოგრაფიულ გასაღებების წყვილს. მომხ. სახ. და სიმბ. სტრიქ. განსხვავებით, რომელთაც პაროლ. ვიყენებთ, რამდენიმე პირადი/საჯარო გას. იქმნება აპის ან ვებსაიტისთვის. პირადი გასაღები უსაფრთხოდ ინახება მოწყობილობაზე ან პაროლ. მმართველში და ის ადასტურებს თქვენს ვინაობას. საჯარო გას. ზიარდება აპისა და ვებ. სერვერის მეშვეობით. შესაბ. გასაღებებით შეგიძლიათ მაშინვე დარეგისტ. და სისტ. შეხვიდეთ."</string>
+    <string name="public_key_cryptography_detail" msgid="6937631710280562213">"FIDO ალიანსის (Google, Apple, Microsoft და სხვა) და W3C სტანდარტების შესაბამისად, წვდომის გასაღებები კრიპტოგრაფიულ გასაღებთა წყვილია. პაროლზე გამოყენებული მომხმარებლის სახელის/სიმბოლოთა სტრიქონების განსხვავებით, პირადი/საჯარო გასაღები იქმნება აპისა და ვებსაიტისთვის. პირადი გასაღები უსაფრთხოდ ინახება მოწყობილობაზე/პაროლთა მმართველში და ადასტურებს ვინაობას. საჯარო გასაღები კი ზიარდება აპთან/ვებსერვერთან. შესაბაბისი გასაღებებით შეგიძლიათ დაუყოვნებლივ დარეგისტრირდეთ და სისტემაში შეხვიდეთ."</string>
     <string name="improved_account_security_title" msgid="1069841917893513424">"ანგარიშის გაუმჯობესებული უსაფრთხოება"</string>
     <string name="improved_account_security_detail" msgid="9123750251551844860">"თითოეული გასაღები დაკავშირებულია მხოლოდ აპთან ან ვებსაიტთან, რომელთათვისაც ის შეიქმნა, ამიტომაც შემთხვევით ვერასდროს შეხვალთ თაღლითურ აპში თუ ვებსაიტზე. ამასთანავე, სერვერები ინახავს მხოლოდ საჯარო გასაღებებს, რაც ართულებს გატეხვის ალბათობას."</string>
     <string name="seamless_transition_title" msgid="5335622196351371961">"დაუბრკოლებელი გადასვლა"</string>
@@ -33,10 +49,13 @@
     <string name="sign_ins" msgid="4710739369149469208">"სისტემაში შესვლა"</string>
     <string name="sign_in_info" msgid="2627704710674232328">"შესვლის ინფორმაცია"</string>
     <string name="save_credential_to_title" msgid="3172811692275634301">"<xliff:g id="CREDENTIALTYPES">%1$s</xliff:g>-ის შენახვა"</string>
-    <string name="create_passkey_in_other_device_title" msgid="9195411122362461390">"გსურთ პაროლის შექმნა სხვა მოწყობილობაში?"</string>
+    <string name="create_passkey_in_other_device_title" msgid="2360053098931886245">"გსურთ სხვა მოწყობილობაზე წვდომის გასაღებების შექმნა?"</string>
+    <string name="save_password_on_other_device_title" msgid="5829084591948321207">"გსურთ სხვა მოწყობილობაზე პაროლის შენახვა?"</string>
+    <string name="save_sign_in_on_other_device_title" msgid="2827990118560134692">"გსურთ სხვა მოწყობილობაზე ავტორიზაციის მონაცემების შენახვა?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"გსურთ, გამოიყენოთ<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> სისტემაში ყველა შესვლისთვის?"</string>
-    <string name="use_provider_for_all_description" msgid="8466427781848268490">"მოცემული პაროლების მმართველი შეინახავს თქვენს პაროლებს და წვდომის გასაღებს, რომლებიც დაგეხმარებათ სისტემაში მარტივად შესვლაში."</string>
+    <string name="use_provider_for_all_description" msgid="1998772715863958997">"მოცემული პაროლების მმართველი <xliff:g id="USERNAME">%1$s</xliff:g>-ისთვის შეინახავს თქვენს პაროლებს და წვდომის გასაღებს, რომლებიც დაგეხმარებათ სისტემაში მარტივად შესვლაში"</string>
     <string name="set_as_default" msgid="4415328591568654603">"ნაგულისხმევად დაყენება"</string>
+    <string name="settings" msgid="6536394145760913145">"პარამეტრები"</string>
     <string name="use_once" msgid="9027366575315399714">"ერთხელ გამოყენება"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> პაროლები • <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> წვდომის გასაღებები"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> პაროლი"</string>
@@ -47,21 +66,30 @@
     <string name="other_password_manager" msgid="565790221427004141">"პაროლების სხვა მმართველები"</string>
     <string name="close_sheet" msgid="1393792015338908262">"ფურცლის დახურვა"</string>
     <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"წინა გვერდზე დაბრუნება"</string>
-    <string name="accessibility_close_button" msgid="2953807735590034688">"დახურეთ ავტორიზაციის მონაცემების მმართველის მოქმედებების შემოთავაზებები, რომლებიც ეკრანის ქვემოთ ჩნდება"</string>
+    <string name="accessibility_close_button" msgid="1163435587545377687">"დახურვა"</string>
+    <string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"დახურვა"</string>
     <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"გსურთ თქვენი დამახსოვრებული წვდომის გასაღების გამოყენება აპისთვის: <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
-    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"გსურთ თქვენი დამახსოვრებული სისტემაში შესვლის მონაცემების გამოყენება აპისთვის: <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
-    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"აირჩიეთ სისტემაში შესვლის ინფორმაცია აპისთვის: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_use_password_for" msgid="625828023234318484">"გამოიყენებთ შენახულ პაროლს <xliff:g id="APP_NAME">%1$s</xliff:g>-სთვის?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"გსურთ შესვლის <xliff:g id="APP_NAME">%1$s</xliff:g>-სთვის გამოყენება?"</string>
+    <string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"გსურთ შესვლის ვარიანტების განბლოკვა <xliff:g id="APP_NAME">%1$s</xliff:g>-სთვის?"</string>
+    <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"აირჩიეთ შენახული წვდომის გასაღები <xliff:g id="APP_NAME">%1$s</xliff:g>-სთვის"</string>
+    <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"აირჩიეთ შენახული პაროლი <xliff:g id="APP_NAME">%1$s</xliff:g>-სთვის"</string>
+    <string name="get_dialog_title_choose_saved_sign_in_for" msgid="2420298653461652728">"აირჩიეთ სისტემაში შესვლის ინფორმაცია <xliff:g id="APP_NAME">%1$s</xliff:g>-სთვის"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="3048870756117876514">"აირჩიეთ შესვლა <xliff:g id="APP_NAME">%1$s</xliff:g>-სთვის"</string>
+    <string name="get_dialog_title_choose_option_for" msgid="4976380044745029107">"გსურთ აირჩიოთ ვარიანტი <xliff:g id="APP_NAME">%1$s</xliff:g>-ისთვის?"</string>
+    <string name="get_dialog_title_use_info_on" msgid="8863708099535435146">"გსურთ ამ ინფორმაციის გამოყენება <xliff:g id="APP_NAME">%1$s</xliff:g>-ში?"</string>
     <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"სხვა ხერხით შესვლა"</string>
     <string name="snackbar_action" msgid="37373514216505085">"პარამეტრების ნახვა"</string>
     <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"გაგრძელება"</string>
     <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"სისტემაში შესვლის ვარიანტები"</string>
+    <string name="button_label_view_more" msgid="3429098227286495651">"მეტის ნახვა"</string>
     <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"<xliff:g id="USERNAME">%1$s</xliff:g>-ისთვის"</string>
     <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"ჩაკეტილი პაროლის მმართველები"</string>
-    <!-- no translation found for locked_credential_entry_label_subtext_tap_to_unlock (6390367581393605009) -->
-    <skip />
-    <!-- no translation found for locked_credential_entry_label_subtext_no_sign_in (8131725029983174901) -->
-    <skip />
+    <string name="locked_credential_entry_label_subtext_tap_to_unlock" msgid="6390367581393605009">"შეეხეთ განსაბლოკად"</string>
+    <string name="locked_credential_entry_label_subtext_no_sign_in" msgid="8131725029983174901">"შესვლის ინფორმაცია არ არის"</string>
+    <string name="no_sign_in_info_in" msgid="2641118151920288356">"<xliff:g id="SOURCE">%1$s</xliff:g>-ში არ არის შესვლის ინფორმაცია"</string>
     <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"სისტემაში შესვლის მონაცემების მართვა"</string>
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"სხვა მოწყობილობიდან"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"გამოიყენეთ სხვა მოწყობილობა"</string>
+    <string name="request_cancelled_by" msgid="3735222326886267820">"თხოვნა გაუქმებულია <xliff:g id="APP_NAME">%1$s</xliff:g>-ის მიერ"</string>
 </resources>
diff --git a/packages/CredentialManager/res/values-kk/strings.xml b/packages/CredentialManager/res/values-kk/strings.xml
index 3a481cb..cb68444 100644
--- a/packages/CredentialManager/res/values-kk/strings.xml
+++ b/packages/CredentialManager/res/values-kk/strings.xml
@@ -1,15 +1,29 @@
 <?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.
+   -->
+
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="4539824758261855508">"Тіркелу деректері менеджері"</string>
     <string name="string_cancel" msgid="6369133483981306063">"Бас тарту"</string>
     <string name="string_continue" msgid="1346732695941131882">"Жалғастыру"</string>
-    <string name="string_more_options" msgid="7990658711962795124">"Басқа опциялар"</string>
+    <string name="string_more_options" msgid="2763852250269945472">"Басқаша сақтау"</string>
     <string name="string_learn_more" msgid="4541600451688392447">"Толық ақпарат"</string>
-    <!-- no translation found for content_description_show_password (3283502010388521607) -->
-    <skip />
-    <!-- no translation found for content_description_hide_password (6841375971631767996) -->
-    <skip />
+    <string name="content_description_show_password" msgid="3283502010388521607">"Құпия сөзді көрсету"</string>
+    <string name="content_description_hide_password" msgid="6841375971631767996">"Құпия сөзді жасыру"</string>
     <string name="passkey_creation_intro_title" msgid="4251037543787718844">"Кіру кілттерімен қауіпсіздеу"</string>
     <string name="passkey_creation_intro_body_password" msgid="8825872426579958200">"Кіру кілттері бар кезде күрделі құпия сөздер жасаудың немесе оларды есте сақтаудың қажеті жоқ."</string>
     <string name="passkey_creation_intro_body_fingerprint" msgid="7331338631826254055">"Кіру кілттері — саусақ ізі, бет не экран құлпы арқылы жасалатын шифрланған цифрлық кілттер."</string>
@@ -18,7 +32,7 @@
     <string name="passwordless_technology_title" msgid="2497513482056606668">"Құпия сөзсіз технология"</string>
     <string name="passwordless_technology_detail" msgid="6853928846532955882">"Кіру кілттері сізге құпия сөзге сүйенбей-ақ кіруге мүмкіндік береді. Жеке басыңызды растап, кіру кілтін жасау үшін тек саусақ ізі, бет тану функциясы, PIN коды немесе сырғыту өрнегі қажет."</string>
     <string name="public_key_cryptography_title" msgid="6751970819265298039">"Ашық кілт криптографиясы"</string>
-    <string name="public_key_cryptography_detail" msgid="6937631710280562213">"FIDO Alliance (оған Google, Apple, Microsoft, т.б. кіреді) және W3C стандарттары бойынша кіру кілттерінде криптографиялық кілт жұптары қолданылады. Құпия сөз үшін пайдаланушы аты мен таңбалар қолданылады, ал қолданба немесе веб-сайт үшін жеке-ашық кілт жұбы жасалады. Жеке кілт құрылғыңызда немесе құпия сөз менеджерінде қорғалып, сақталады. Ол жеке басыңызды растау үшін қажет. Ашық кілт қолданба немесе веб-сайт серверіне жіберіледі. Кілттер сәйкес келсе, бірден тіркеліп, аккаунтқа кіре аласыз."</string>
+    <string name="public_key_cryptography_detail" msgid="6937631710280562213">"FIDO Alliance (оған Google, Apple, Microsoft, т.б. кіреді) және W3C стандарттары бойынша кіру кілттерінде криптографиялық кілт жұптары қолданылады. Құпия сөз үшін пайдаланушы аты мен таңбалар қолданылады, ал қолданба немесе веб-сайт үшін жеке-ашық кілт жұбы жасалады. Жеке кілт құрылғыңызда немесе құпия сөз менеджерінде қауіпсіз сақталады. Ол жеке басыңызды растау үшін қажет. Ашық кілт қолданба немесе веб-сайт серверіне жіберіледі. Кілттер сәйкес келсе, бірден тіркеліп, аккаунтқа кіре аласыз."</string>
     <string name="improved_account_security_title" msgid="1069841917893513424">"Аккаунттың қосымша қауіпсіздігі"</string>
     <string name="improved_account_security_detail" msgid="9123750251551844860">"Әрбір кілт өзі арнайы жасалған қолданбамен немесе веб-сайтпен ғана байланысты болады, сондықтан алаяқтар қолданбасына немесе веб-сайтына байқаусызда кіру мүмкін емес. Онымен қоса тек ашық кілттер сақталатын серверлер арқасында хакерлердің бұзып кіруі айтарлықтай қиындады."</string>
     <string name="seamless_transition_title" msgid="5335622196351371961">"Оңай ауысу"</string>
@@ -28,17 +42,20 @@
     <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"<xliff:g id="APPNAME">%1$s</xliff:g> үшін кіру кілтін жасау керек пе?"</string>
     <string name="choose_create_option_password_title" msgid="7097275038523578687">"<xliff:g id="APPNAME">%1$s</xliff:g> үшін құпия сөзді сақтау керек пе?"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"<xliff:g id="APPNAME">%1$s</xliff:g> үшін кіру мәліметін сақтау керек пе?"</string>
-    <string name="passkey" msgid="632353688396759522">"кіру кілті"</string>
+    <string name="passkey" msgid="632353688396759522">"Кіру кілті"</string>
     <string name="password" msgid="6738570945182936667">"құпия сөз"</string>
-    <string name="passkeys" msgid="5733880786866559847">"Кіру кілттері"</string>
+    <string name="passkeys" msgid="5733880786866559847">"кіру кілттері"</string>
     <string name="passwords" msgid="5419394230391253816">"Құпия сөздер"</string>
     <string name="sign_ins" msgid="4710739369149469208">"кіру әрекеттері"</string>
     <string name="sign_in_info" msgid="2627704710674232328">"кіру мәліметі"</string>
-    <string name="save_credential_to_title" msgid="3172811692275634301">"<xliff:g id="CREDENTIALTYPES">%1$s</xliff:g> тіркелу дерегін сақтау орны:"</string>
-    <string name="create_passkey_in_other_device_title" msgid="9195411122362461390">"Кіру кілтін басқа құрылғыда жасау керек пе?"</string>
+    <string name="save_credential_to_title" msgid="3172811692275634301">"<xliff:g id="CREDENTIALTYPES">%1$s</xliff:g> дерегін сақтау орны:"</string>
+    <string name="create_passkey_in_other_device_title" msgid="2360053098931886245">"Кіру кілті басқа құрылғыда жасалсын ба?"</string>
+    <string name="save_password_on_other_device_title" msgid="5829084591948321207">"Құпия сөз басқа құрылғыда сақталсын ба?"</string>
+    <string name="save_sign_in_on_other_device_title" msgid="2827990118560134692">"Кіру басқа құрылғыда сақталсын ба?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"Барлық кіру әрекеті үшін <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> пайдаланылсын ба?"</string>
-    <string name="use_provider_for_all_description" msgid="8466427781848268490">"Аккаунтқа кіру оңай болуы үшін, құпия сөз менеджері құпия сөздер мен кіру кілттерін сақтайды."</string>
+    <string name="use_provider_for_all_description" msgid="1998772715863958997">"<xliff:g id="USERNAME">%1$s</xliff:g> аккаунтына оңай кіру үшін құпия сөз менеджері құпия сөздер мен кіру кілттерін сақтайды."</string>
     <string name="set_as_default" msgid="4415328591568654603">"Әдепкі етіп орнату"</string>
+    <string name="settings" msgid="6536394145760913145">"Параметрлер"</string>
     <string name="use_once" msgid="9027366575315399714">"Бір рет пайдалану"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> құпия сөз • <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> кіру кілті"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> құпия сөз"</string>
@@ -49,21 +66,30 @@
     <string name="other_password_manager" msgid="565790221427004141">"Басқа құпия сөз менеджерлері"</string>
     <string name="close_sheet" msgid="1393792015338908262">"Парақты жабу"</string>
     <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Алдыңғы бетке оралу"</string>
-    <string name="accessibility_close_button" msgid="2953807735590034688">"Экранның төменгі жағында шығатын тіркелу деректері менеджерінің әрекет ұсынысын жабады."</string>
+    <string name="accessibility_close_button" msgid="1163435587545377687">"Жабу"</string>
+    <string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"Жабу"</string>
     <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"<xliff:g id="APP_NAME">%1$s</xliff:g> үшін сақталған кіру кілті пайдаланылсын ба?"</string>
-    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"<xliff:g id="APP_NAME">%1$s</xliff:g> үшін сақталған тіркелу деректері пайдаланылсын ба?"</string>
-    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"<xliff:g id="APP_NAME">%1$s</xliff:g> үшін сақталған тіркелу деректерін таңдаңыз"</string>
+    <string name="get_dialog_title_use_password_for" msgid="625828023234318484">"<xliff:g id="APP_NAME">%1$s</xliff:g> үшін сақталған құпия сөзіңізді пайдаланасыз ба?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"<xliff:g id="APP_NAME">%1$s</xliff:g> қолданбасына кіру деректерін пайдаланасыз ба?"</string>
+    <string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"<xliff:g id="APP_NAME">%1$s</xliff:g> үшін кіру опциялары ашылсын ба?"</string>
+    <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"<xliff:g id="APP_NAME">%1$s</xliff:g> үшін сақталған кіру кілтін таңдаңыз"</string>
+    <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"<xliff:g id="APP_NAME">%1$s</xliff:g> үшін сақталған құпия сөзді таңдаңыз"</string>
+    <string name="get_dialog_title_choose_saved_sign_in_for" msgid="2420298653461652728">"<xliff:g id="APP_NAME">%1$s</xliff:g> үшін сақталған тіркелу деректерін таңдаңыз"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="3048870756117876514">"<xliff:g id="APP_NAME">%1$s</xliff:g> қолданбасына кіру деректерін таңдаңыз"</string>
+    <string name="get_dialog_title_choose_option_for" msgid="4976380044745029107">"<xliff:g id="APP_NAME">%1$s</xliff:g> үшін опция таңдайсыз ба?"</string>
+    <string name="get_dialog_title_use_info_on" msgid="8863708099535435146">"Бұл ақпарат <xliff:g id="APP_NAME">%1$s</xliff:g> қолданбасында сақталсын ба?"</string>
     <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Басқаша кіру"</string>
     <string name="snackbar_action" msgid="37373514216505085">"Опцияларды көру"</string>
     <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Жалғастыру"</string>
     <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Кіру опциялары"</string>
+    <string name="button_label_view_more" msgid="3429098227286495651">"Тағы көру"</string>
     <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"<xliff:g id="USERNAME">%1$s</xliff:g> үшін"</string>
     <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Құлыпталған құпия сөз менеджерлері"</string>
-    <!-- no translation found for locked_credential_entry_label_subtext_tap_to_unlock (6390367581393605009) -->
-    <skip />
-    <!-- no translation found for locked_credential_entry_label_subtext_no_sign_in (8131725029983174901) -->
-    <skip />
+    <string name="locked_credential_entry_label_subtext_tap_to_unlock" msgid="6390367581393605009">"Құлыпты ашу үшін түртіңіз."</string>
+    <string name="locked_credential_entry_label_subtext_no_sign_in" msgid="8131725029983174901">"Кіру ақпараты жоқ."</string>
+    <string name="no_sign_in_info_in" msgid="2641118151920288356">"<xliff:g id="SOURCE">%1$s</xliff:g> аккаунтында кіру туралы ешқандай ақпарат жоқ."</string>
     <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Кіру әрекеттерін басқару"</string>
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Басқа құрылғыдан жасау"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Басқа құрылғыны пайдалану"</string>
+    <string name="request_cancelled_by" msgid="3735222326886267820">"<xliff:g id="APP_NAME">%1$s</xliff:g> қолданбасы сұрауды тоқтатты."</string>
 </resources>
diff --git a/packages/CredentialManager/res/values-km/strings.xml b/packages/CredentialManager/res/values-km/strings.xml
index f4ac3e2..d361ad9 100644
--- a/packages/CredentialManager/res/values-km/strings.xml
+++ b/packages/CredentialManager/res/values-km/strings.xml
@@ -1,18 +1,32 @@
 <?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.
+   -->
+
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="4539824758261855508">"កម្មវិធី​គ្រប់គ្រង​ព័ត៌មាន​ផ្ទៀងផ្ទាត់"</string>
     <string name="string_cancel" msgid="6369133483981306063">"បោះបង់"</string>
     <string name="string_continue" msgid="1346732695941131882">"បន្ត"</string>
-    <string name="string_more_options" msgid="7990658711962795124">"ជម្រើសច្រើនទៀត"</string>
+    <string name="string_more_options" msgid="2763852250269945472">"រក្សាទុកតាមវិធីផ្សេង"</string>
     <string name="string_learn_more" msgid="4541600451688392447">"ស្វែងយល់បន្ថែម"</string>
-    <!-- no translation found for content_description_show_password (3283502010388521607) -->
-    <skip />
-    <!-- no translation found for content_description_hide_password (6841375971631767996) -->
-    <skip />
+    <string name="content_description_show_password" msgid="3283502010388521607">"បង្ហាញពាក្យសម្ងាត់"</string>
+    <string name="content_description_hide_password" msgid="6841375971631767996">"លាក់​ពាក្យ​សម្ងាត់"</string>
     <string name="passkey_creation_intro_title" msgid="4251037543787718844">"កាន់តែមានសុវត្ថិភាពដោយប្រើកូដសម្ងាត់"</string>
     <string name="passkey_creation_intro_body_password" msgid="8825872426579958200">"តាមរយៈ​កូដសម្ងាត់ អ្នកមិនចាំបាច់​បង្កើត ឬចងចាំពាក្យសម្ងាត់ស្មុគស្មាញ​នោះទេ"</string>
-    <string name="passkey_creation_intro_body_fingerprint" msgid="7331338631826254055">"កូដសម្ងាត់​ត្រូវបានអ៊ីនគ្រីប​ឃីឌីជីថលដែលអ្នកបង្កើតដោយប្រើ​ស្នាមម្រាមដៃ មុខ ឬចាក់សោអេក្រង់​របស់អ្នក"</string>
+    <string name="passkey_creation_intro_body_fingerprint" msgid="7331338631826254055">"កូដសម្ងាត់​គឺជាសោឌីជីថលដែលត្រូវបានអ៊ីនគ្រីប ​ដែលអ្នកបង្កើតដោយប្រើ​ស្នាមម្រាមដៃ មុខ ឬមុខងារចាក់សោអេក្រង់​របស់អ្នក"</string>
     <string name="passkey_creation_intro_body_device" msgid="1203796455762131631">"កូដសម្ងាត់ត្រូវបានរក្សាទុក​ទៅក្នុង​កម្មវិធីគ្រប់គ្រងពាក្យសម្ងាត់ ដូច្នេះអ្នកអាច​ចូលនៅលើឧបករណ៍ផ្សេងទៀត"</string>
     <string name="more_about_passkeys_title" msgid="7797903098728837795">"ច្រើនទៀតអំពីកូដសម្ងាត់"</string>
     <string name="passwordless_technology_title" msgid="2497513482056606668">"បច្ចេកវិទ្យាគ្មានពាក្យសម្ងាត់"</string>
@@ -34,13 +48,16 @@
     <string name="passwords" msgid="5419394230391253816">"ពាក្យសម្ងាត់"</string>
     <string name="sign_ins" msgid="4710739369149469208">"ការចូល​គណនី"</string>
     <string name="sign_in_info" msgid="2627704710674232328">"ព័ត៌មានអំពី​ការចូលគណនី"</string>
-    <string name="save_credential_to_title" msgid="3172811692275634301">"រក្សាទុក <xliff:g id="CREDENTIALTYPES">%1$s</xliff:g> ទៅកាន់"</string>
-    <string name="create_passkey_in_other_device_title" msgid="9195411122362461390">"បង្កើតកូដសម្ងាត់​នៅក្នុងឧបករណ៍​ផ្សេងទៀតឬ?"</string>
+    <string name="save_credential_to_title" msgid="3172811692275634301">"រក្សាទុក​<xliff:g id="CREDENTIALTYPES">%1$s</xliff:g>​ទៅកាន់"</string>
+    <string name="create_passkey_in_other_device_title" msgid="2360053098931886245">"បង្កើត​កូដសម្ងាត់​នៅលើឧបករណ៍​ផ្សេងទៀតឬ?"</string>
+    <string name="save_password_on_other_device_title" msgid="5829084591948321207">"រក្សាទុក​ពាក្យសម្ងាត់​នៅលើឧបករណ៍​ផ្សេងទៀតឬ?"</string>
+    <string name="save_sign_in_on_other_device_title" msgid="2827990118560134692">"រក្សាទុក​ការចូលគណនី​នៅលើឧបករណ៍​ផ្សេងទៀតឬ?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"ប្រើ <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> សម្រាប់ការចូលគណនីទាំងអស់របស់អ្នកឬ?"</string>
-    <string name="use_provider_for_all_description" msgid="8466427781848268490">"កម្មវិធីគ្រប់គ្រងពាក្យសម្ងាត់នេះ​នឹងរក្សាទុកពាក្យសម្ងាត់ និងកូដសម្ងាត់​របស់អ្នក ដើម្បីជួយឱ្យអ្នក​ចូលគណនី​បានយ៉ាងងាយស្រួល"</string>
+    <string name="use_provider_for_all_description" msgid="1998772715863958997">"កម្មវិធីគ្រប់គ្រងពាក្យសម្ងាត់សម្រាប់ <xliff:g id="USERNAME">%1$s</xliff:g> ​នេះនឹងរក្សាទុកពាក្យសម្ងាត់ និងកូដសម្ងាត់​របស់អ្នក ដើម្បីជួយឱ្យអ្នក​ចូលគណនី​បានយ៉ាងងាយស្រួល"</string>
     <string name="set_as_default" msgid="4415328591568654603">"កំណត់ជាលំនាំដើម"</string>
+    <string name="settings" msgid="6536394145760913145">"ការកំណត់"</string>
     <string name="use_once" msgid="9027366575315399714">"ប្រើម្ដង"</string>
-    <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"ពាក្យសម្ងាត់ <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> • កូដសម្ងាត់<xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>"</string>
+    <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"ពាក្យសម្ងាត់ <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> • កូដសម្ងាត់ <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"ពាក្យសម្ងាត់ <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>"</string>
     <string name="more_options_usage_passkeys" msgid="5390320437243042237">"កូដសម្ងាត់ <xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g>"</string>
     <string name="more_options_usage_credentials" msgid="1785697001787193984">"ព័ត៌មានផ្ទៀងផ្ទាត់ <xliff:g id="TOTALCREDENTIALSNUMBER">%1$s</xliff:g>"</string>
@@ -49,21 +66,30 @@
     <string name="other_password_manager" msgid="565790221427004141">"កម្មវិធីគ្រប់គ្រងពាក្យសម្ងាត់ផ្សេងទៀត"</string>
     <string name="close_sheet" msgid="1393792015338908262">"បិទសន្លឹក"</string>
     <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"ត្រឡប់ទៅ​ទំព័រ​មុនវិញ"</string>
-    <string name="accessibility_close_button" msgid="2953807735590034688">"បិទការណែនាំសកម្មភាពរបស់កម្មវិធី​គ្រប់គ្រង​ព័ត៌មាន​ផ្ទៀងផ្ទាត់ដែលបង្ហាញនៅផ្នែកខាងក្រោម​នៃ​អេក្រង់"</string>
+    <string name="accessibility_close_button" msgid="1163435587545377687">"បិទ"</string>
+    <string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"ច្រានចោល"</string>
     <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"ប្រើកូដសម្ងាត់ដែលបានរក្សាទុករបស់អ្នកសម្រាប់ <xliff:g id="APP_NAME">%1$s</xliff:g> ឬ?"</string>
-    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"ប្រើការចូល​គណនីដែលបានរក្សាទុករបស់អ្នកសម្រាប់ <xliff:g id="APP_NAME">%1$s</xliff:g> ឬ?"</string>
-    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"ជ្រើសរើសការចូលគណនីដែលបានរក្សាទុកសម្រាប់ <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_use_password_for" msgid="625828023234318484">"ប្រើពាក្យសម្ងាត់​ដែលអ្នកបាន​រក្សាទុកសម្រាប់ <xliff:g id="APP_NAME">%1$s</xliff:g> ឬ?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"ប្រើការចូលគណនីរបស់អ្នកសម្រាប់ <xliff:g id="APP_NAME">%1$s</xliff:g> ឬ?"</string>
+    <string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"ដោះ​សោជម្រើសចូល​គណនីសម្រាប់ <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"ជ្រើសរើសកូដសម្ងាត់ដែលបានរក្សាទុកសម្រាប់ <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"ជ្រើសរើសពាក្យ​សម្ងាត់ដែលបានរក្សាទុកសម្រាប់ <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_saved_sign_in_for" msgid="2420298653461652728">"ជ្រើសរើសការចូលគណនីដែលបានរក្សាទុកសម្រាប់ <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="3048870756117876514">"ជ្រើសរើសការចូលគណនីសម្រាប់ <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_option_for" msgid="4976380044745029107">"ជ្រើសរើសជម្រើសសម្រាប់ <xliff:g id="APP_NAME">%1$s</xliff:g> ឬ?"</string>
+    <string name="get_dialog_title_use_info_on" msgid="8863708099535435146">"ប្រើព័ត៌មាននេះ​នៅលើ <xliff:g id="APP_NAME">%1$s</xliff:g> ឬ?"</string>
     <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"ចូលគណនីដោយប្រើវិធីផ្សេងទៀត"</string>
     <string name="snackbar_action" msgid="37373514216505085">"មើលជម្រើស"</string>
     <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"បន្ត"</string>
     <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"ជម្រើស​ចូលគណនី"</string>
+    <string name="button_label_view_more" msgid="3429098227286495651">"មើល​ច្រើនទៀត"</string>
     <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"សម្រាប់ <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"កម្មវិធីគ្រប់គ្រងពាក្យសម្ងាត់ដែលបានចាក់សោ"</string>
-    <!-- no translation found for locked_credential_entry_label_subtext_tap_to_unlock (6390367581393605009) -->
-    <skip />
-    <!-- no translation found for locked_credential_entry_label_subtext_no_sign_in (8131725029983174901) -->
-    <skip />
+    <string name="locked_credential_entry_label_subtext_tap_to_unlock" msgid="6390367581393605009">"ចុចដើម្បីដោះសោ"</string>
+    <string name="locked_credential_entry_label_subtext_no_sign_in" msgid="8131725029983174901">"គ្មានព័ត៌មានចូលគណនីទេ"</string>
+    <string name="no_sign_in_info_in" msgid="2641118151920288356">"មិនមានព័ត៌មានចូលគណនីនៅក្នុង <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"គ្រប់គ្រងការចូល​គណនី"</string>
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"ពីឧបករណ៍ផ្សេងទៀត"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"ប្រើឧបករណ៍ផ្សេង"</string>
+    <string name="request_cancelled_by" msgid="3735222326886267820">"បានបោះបង់សំណើដោយ <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
 </resources>
diff --git a/packages/CredentialManager/res/values-kn/strings.xml b/packages/CredentialManager/res/values-kn/strings.xml
index 2e039da..ae680a0 100644
--- a/packages/CredentialManager/res/values-kn/strings.xml
+++ b/packages/CredentialManager/res/values-kn/strings.xml
@@ -1,27 +1,43 @@
 <?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.
+   -->
+
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="4539824758261855508">"ರುಜುವಾತು ನಿರ್ವಾಹಕ"</string>
     <string name="string_cancel" msgid="6369133483981306063">"ರದ್ದುಗೊಳಿಸಿ"</string>
     <string name="string_continue" msgid="1346732695941131882">"ಮುಂದುವರಿಸಿ"</string>
-    <string name="string_more_options" msgid="7990658711962795124">"ಇನ್ನಷ್ಟು ಆಯ್ಕೆಗಳು"</string>
+    <string name="string_more_options" msgid="2763852250269945472">"ಬೇರೆ ವಿಧಾನದಲ್ಲಿ ಉಳಿಸಿ"</string>
     <string name="string_learn_more" msgid="4541600451688392447">"ಇನ್ನಷ್ಟು ತಿಳಿಯಿರಿ"</string>
     <string name="content_description_show_password" msgid="3283502010388521607">"ಪಾಸ್‌ವರ್ಡ್ ತೋರಿಸಿ"</string>
     <string name="content_description_hide_password" msgid="6841375971631767996">"ಪಾಸ್‌ವರ್ಡ್ ಅನ್ನು ಮರೆಮಾಡಿ"</string>
-    <string name="passkey_creation_intro_title" msgid="4251037543787718844">"ಪಾಸ್‌ಕೀಗಳೊಂದಿಗೆ ಸುರಕ್ಷಿತವಾಗಿರುತ್ತವೆ"</string>
+    <string name="passkey_creation_intro_title" msgid="4251037543787718844">"ಪಾಸ್‌ಕೀಗಳ ಸಹಾಯದಿಂದ ಸುರಕ್ಷಿತವಾಗಿರಿ"</string>
     <string name="passkey_creation_intro_body_password" msgid="8825872426579958200">"ಪಾಸ್‌ಕೀಗಳ ಮೂಲಕ, ನೀವು ಕ್ಲಿಷ್ಟ ಪಾಸ್‌ವರ್ಡ್‌ಗಳನ್ನು ರಚಿಸುವ ಅಥವಾ ನೆನಪಿಟ್ಟುಕೊಳ್ಳುವ ಅಗತ್ಯವಿಲ್ಲ"</string>
     <string name="passkey_creation_intro_body_fingerprint" msgid="7331338631826254055">"ಪಾಸ್‌ಕೀಗಳು ನಿಮ್ಮ ಫಿಂಗರ್‌ಪ್ರಿಂಟ್, ಫೇಸ್ ಅಥವಾ ಸ್ಕ್ರೀನ್ ಲಾಕ್ ಅನ್ನು ಬಳಸಿಕೊಂಡು ನೀವು ರಚಿಸುವ ಎನ್‌ಕ್ರಿಪ್ಟ್ ಮಾಡಿದ ಡಿಜಿಟಲ್ ಕೀಗಳಾಗಿವೆ"</string>
-    <string name="passkey_creation_intro_body_device" msgid="1203796455762131631">"ಅವುಗಳನ್ನು ಪಾಸ್‌ವರ್ಡ್ ನಿರ್ವಾಹಕದಲ್ಲಿ ಉಳಿಸಲಾಗಿದೆ, ಹಾಗಾಗಿ ನೀವು ಇತರ ಸಾಧನಗಳಲ್ಲಿ ಸೈನ್ ಇನ್ ಮಾಡಬಹುದು"</string>
+    <string name="passkey_creation_intro_body_device" msgid="1203796455762131631">"ಅವುಗಳನ್ನು ಪಾಸ್‌ವರ್ಡ್ ನಿರ್ವಾಹಕದಲ್ಲಿ ಉಳಿಸಲಾಗುತ್ತದೆ, ಹಾಗಾಗಿ ನೀವು ಇತರ ಸಾಧನಗಳಲ್ಲಿ ಸೈನ್ ಇನ್ ಮಾಡಬಹುದು"</string>
     <string name="more_about_passkeys_title" msgid="7797903098728837795">"ಪಾಸ್‌ಕೀಗಳ ಕುರಿತು ಇನ್ನಷ್ಟು"</string>
     <string name="passwordless_technology_title" msgid="2497513482056606668">"ಪಾಸ್‌ವರ್ಡ್ ರಹಿತ ತಂತ್ರಜ್ಞಾನ"</string>
-    <string name="passwordless_technology_detail" msgid="6853928846532955882">"ಪಾಸ್‌ಕೀಗಳು ಪಾಸ್‌ವರ್ಡ್‌ಗಳನ್ನು ಅವಲಂಬಿಸದೆ ಸೈನ್ ಇನ್ ಮಾಡಲು ಅನುಮತಿಸುತ್ತದೆ. ನಿಮ್ಮ ಗುರುತನ್ನು ಪರಿಶೀಲಿಸಲು ಮತ್ತು ಪಾಸ್‌ಕೀ ರಚಿಸಲು ನಿಮ್ಮ ಫಿಂಗರ್‌ಪ್ರಿಂಟ್, ಮುಖ ಗುರುತಿಸುವಿಕೆ, ಪಿನ್ ಅಥವಾ ಸ್ವೈಪ್ ಪ್ಯಾಟರ್ನ್ ಅನ್ನು ನೀವು ಬಳಸಬೇಕಾಗುತ್ತದೆ."</string>
+    <string name="passwordless_technology_detail" msgid="6853928846532955882">"ಪಾಸ್‌ಕೀಗಳು ಪಾಸ್‌ವರ್ಡ್‌ಗಳನ್ನು ಅವಲಂಬಿಸದೆ ಸೈನ್ ಇನ್ ಮಾಡಲು ಅನುಮತಿಸುತ್ತವೆ. ನಿಮ್ಮ ಗುರುತನ್ನು ದೃಢೀಕರಿಸಲು ಮತ್ತು ಪಾಸ್‌ಕೀ ರಚಿಸಲು ನಿಮ್ಮ ಫಿಂಗರ್‌ಪ್ರಿಂಟ್, ಮುಖ ಗುರುತಿಸುವಿಕೆ, ಪಿನ್ ಅಥವಾ ಸ್ವೈಪ್ ಪ್ಯಾಟರ್ನ್ ಅನ್ನು ನೀವು ಬಳಸಬೇಕಾಗುತ್ತದೆ."</string>
     <string name="public_key_cryptography_title" msgid="6751970819265298039">"ಸಾರ್ವಜನಿಕ ಕೀ ಕ್ರಿಪ್ಟೋಗ್ರಫಿ"</string>
-    <string name="public_key_cryptography_detail" msgid="6937631710280562213">"FIDO ಅಲೈಯನ್ಸ್ (ಇದು Google, Apple, Microsoft ಮತ್ತು ಹೆಚ್ಚಿನದನ್ನು ಒಳಗೊಂಡಿರುತ್ತದೆ) ಮತ್ತು W3C ಮಾನದಂಡಗಳನ್ನು ಆಧರಿಸಿ, ಪಾಸ್‌ಕೀಗಳು ಕ್ರಿಪ್ಟೋಗ್ರಾಫಿಕ್ ಕೀ ಜೋಡಿಗಳನ್ನು ಬಳಸುತ್ತವೆ. ಪಾಸ್‌ವರ್ಡ್‌ಗಳಿಗಾಗಿ ನಾವು ಬಳಸುವ ಬಳಕೆದಾರಹೆಸರು ಮತ್ತು ಅಕ್ಷರಗಳ ಸ್ಟ್ರಿಂಗ್‌ಗಿಂತ ಭಿನ್ನವಾಗಿ, ಆ್ಯಪ್ ಅಥವಾ ವೆಬ್‌ಸೈಟ್‌ಗಾಗಿ ಖಾಸಗಿ-ಸಾರ್ವಜನಿಕ ಕೀ ಜೋಡಿಯನ್ನು ರಚಿಸಲಾಗಿದೆ. ಖಾಸಗಿ ಕೀ ಅನ್ನು ನಿಮ್ಮ ಸಾಧನ ಅಥವಾ ಪಾಸ್‌ವರ್ಡ್ ನಿರ್ವಾಹಕದಲ್ಲಿ ಸುರಕ್ಷಿತವಾಗಿ ಸಂಗ್ರಹಿಸಲಾಗಿದೆ ಮತ್ತು ಅದು ನಿಮ್ಮ ಗುರುತನ್ನು ಖಚಿತಪಡಿಸುತ್ತದೆ. ಸಾರ್ವಜನಿಕ ಕೀ ಅನ್ನು ಆ್ಯಪ್ ಅಥವಾ ವೆಬ್‌ಸೈಟ್ ಸರ್ವರ್ ಜೊತೆಗೆ ಹಂಚಿಕೊಳ್ಳಲಾಗಿದೆ. ಅನುಗುಣವಾದ ಕೀ ಮೂಲಕ, ನೀವು ತಕ್ಷಣ ನೋಂದಾಯಿಸಬಹುದು ಮತ್ತು ಸೈನ್ ಇನ್ ಮಾಡಬಹುದು."</string>
+    <string name="public_key_cryptography_detail" msgid="6937631710280562213">"FIDO ಅಲೈಯನ್ಸ್ (ಇದು Google, Apple, Microsoft ಮತ್ತು ಹೆಚ್ಚಿನದನ್ನು ಒಳಗೊಂಡಿದೆ) ಮತ್ತು W3C ಮಾನದಂಡಗಳನ್ನು ಆಧರಿಸಿ, ಪಾಸ್‌ಕೀಗಳು ಕ್ರಿಪ್ಟೋಗ್ರಾಫಿಕ್ ಕೀ ಜೋಡಿಗಳನ್ನು ಬಳಸುತ್ತವೆ. ಪಾಸ್‌ವರ್ಡ್‌ಗಳಿಗಾಗಿ ನಾವು ಬಳಸುವ ಬಳಕೆದಾರರ ಹೆಸರು ಮತ್ತು ಅಕ್ಷರಗಳ ಸ್ಟ್ರಿಂಗ್‌ಗಿಂತ ಭಿನ್ನವಾಗಿ, ಆ್ಯಪ್ ಅಥವಾ ವೆಬ್‌ಸೈಟ್‌ಗಾಗಿ ಖಾಸಗಿ-ಸಾರ್ವಜನಿಕ ಕೀ ಜೋಡಿಯನ್ನು ರಚಿಸಲಾಗುತ್ತದೆ. ಖಾಸಗಿ ಕೀ ಅನ್ನು ನಿಮ್ಮ ಸಾಧನ ಅಥವಾ ಪಾಸ್‌ವರ್ಡ್ ನಿರ್ವಾಹಕದಲ್ಲಿ ಸುರಕ್ಷಿತವಾಗಿ ಸಂಗ್ರಹಿಸಲಾಗುತ್ತದೆ ಮತ್ತು ಅದು ನಿಮ್ಮ ಗುರುತನ್ನು ಖಚಿತಪಡಿಸುತ್ತದೆ. ಸಾರ್ವಜನಿಕ ಕೀ ಅನ್ನು ಆ್ಯಪ್ ಅಥವಾ ವೆಬ್‌ಸೈಟ್ ಸರ್ವರ್ ಜೊತೆಗೆ ಹಂಚಿಕೊಳ್ಳಲಾಗುತ್ತದೆ. ಅನುಗುಣವಾದ ಕೀಗಳೊಂದಿಗೆ, ನೀವು ತಕ್ಷಣ ನೋಂದಾಯಿಸಬಹುದು ಮತ್ತು ಸೈನ್ ಇನ್ ಮಾಡಬಹುದು."</string>
     <string name="improved_account_security_title" msgid="1069841917893513424">"ಸುಧಾರಿತ ಖಾತೆಯ ಭದ್ರತೆ"</string>
-    <string name="improved_account_security_detail" msgid="9123750251551844860">"ಪ್ರತಿಯೊಂದು ಕೀ ಅವುಗಳನ್ನು ರಚಿಸಲಾದ ಆ್ಯಪ್ ಅಥವಾ ವೆಬ್‌ಸೈಟ್‌ನ ಜೊತೆಗೆ ಪ್ರತ್ಯೇಕವಾಗಿ ಲಿಂಕ್ ಮಾಡಲಾಗಿದೆ, ಆದ್ದರಿಂದ ನೀವು ಎಂದಿಗೂ ತಪ್ಪಾಗಿ ವಂಚನೆಯ ಆ್ಯಪ್ ಅಥವಾ ವೆಬ್‌ಸೈಟ್‌ಗೆ ಸೈನ್ ಇನ್ ಮಾಡಲು ಸಾಧ್ಯವಿಲ್ಲ. ಜೊತೆಗೆ, ಸರ್ವರ್‌ಗಳು ಮಾತ್ರ ಸಾರ್ವಜನಿಕ ಕೀಗಳನ್ನು ಇಟ್ಟುಕೊಳ್ಳುವುದರಿಂದ, ಹ್ಯಾಕಿಂಗ್ ಮಾಡುವುದು ತುಂಬಾ ಕಷ್ಟಕರವಾಗಿದೆ."</string>
+    <string name="improved_account_security_detail" msgid="9123750251551844860">"ಪ್ರತಿಯೊಂದು ಕೀಯನ್ನು ಯಾವ ಆ್ಯಪ್ ಅಥವಾ ವೆಬ್‌ಸೈಟ್‌ಗಾಗಿ ರಚಿಸಲಾಗಿದೆಯೋ ಅದರೊಂದಿಗೆ ಮಾತ್ರ ಲಿಂಕ್ ಮಾಡಲಾಗುತ್ತದೆ. ಆದ್ದರಿಂದ ನೀವು ಎಂದಿಗೂ ತಪ್ಪಾಗಿ ವಂಚನೆಯ ಆ್ಯಪ್ ಅಥವಾ ವೆಬ್‌ಸೈಟ್‌ಗೆ ಸೈನ್ ಇನ್ ಮಾಡಲು ಸಾಧ್ಯವಿಲ್ಲ. ಜೊತೆಗೆ, ಸರ್ವರ್‌ಗಳು ಸಾರ್ವಜನಿಕ ಕೀಗಳನ್ನು ಮಾತ್ರ ಇಟ್ಟುಕೊಳ್ಳುವುದರಿಂದ, ಹ್ಯಾಕಿಂಗ್ ಮಾಡುವುದು ತುಂಬಾ ಕಷ್ಟಕರವಾಗಿದೆ."</string>
     <string name="seamless_transition_title" msgid="5335622196351371961">"ಅಡಚಣೆರಹಿತ ಪರಿವರ್ತನೆ"</string>
     <string name="seamless_transition_detail" msgid="4475509237171739843">"ನಾವು ಪಾಸ್‌ವರ್ಡ್ ರಹಿತ ತಂತ್ರಜ್ಞಾನದ ಕಡೆಗೆ ಸಾಗುತ್ತಿರುವಾಗ, ಪಾಸ್‌ಕೀಗಳ ಜೊತೆಗೆ ಪಾಸ್‌ವರ್ಡ್‌ಗಳು ಇನ್ನೂ ಲಭ್ಯವಿರುತ್ತವೆ."</string>
-    <string name="choose_provider_title" msgid="8870795677024868108">"ನಿಮ್ಮ <xliff:g id="CREATETYPES">%1$s</xliff:g> ಅನ್ನು ಎಲ್ಲಿ ಉಳಿಸಬೇಕು ಎಂದು ಆರಿಸಿ"</string>
+    <string name="choose_provider_title" msgid="8870795677024868108">"ನಿಮ್ಮ <xliff:g id="CREATETYPES">%1$s</xliff:g> ಎಲ್ಲಿ ಸೇವ್‌ ಆಗಬೇಕು ಎಂಬುದನ್ನು ಆರಿಸಿ"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"ನಿಮ್ಮ ಮಾಹಿತಿಯನ್ನು ಉಳಿಸಲು ಪಾಸ್‌ವರ್ಡ್ ನಿರ್ವಾಹಕವನ್ನು ಆಯ್ಕೆಮಾಡಿ ಹಾಗೂ ಮುಂದಿನ ಬಾರಿ ವೇಗವಾಗಿ ಸೈನ್ ಇನ್ ಮಾಡಿ"</string>
     <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"<xliff:g id="APPNAME">%1$s</xliff:g> ಗಾಗಿ ಪಾಸ್‌ಕೀ ಅನ್ನು ರಚಿಸುವುದೇ?"</string>
     <string name="choose_create_option_password_title" msgid="7097275038523578687">"<xliff:g id="APPNAME">%1$s</xliff:g> ಗಾಗಿ ಪಾಸ್‌ವರ್ಡ್‌ ಉಳಿಸುವುದೇ?"</string>
@@ -33,10 +49,13 @@
     <string name="sign_ins" msgid="4710739369149469208">"ಸೈನ್-ಇನ್‌ಗಳು"</string>
     <string name="sign_in_info" msgid="2627704710674232328">"ಸೈನ್-ಇನ್ ಮಾಹಿತಿ"</string>
     <string name="save_credential_to_title" msgid="3172811692275634301">"ಇಲ್ಲಿಗೆ <xliff:g id="CREDENTIALTYPES">%1$s</xliff:g> ಅನ್ನು ಉಳಿಸಿ"</string>
-    <string name="create_passkey_in_other_device_title" msgid="9195411122362461390">"ಮತ್ತೊಂದು ಸಾಧನದಲ್ಲಿ ಪಾಸ್‌ಕೀಯನ್ನು ರಚಿಸಬೇಕೇ?"</string>
+    <string name="create_passkey_in_other_device_title" msgid="2360053098931886245">"ಇನ್ನೊಂದು ಸಾಧನದಲ್ಲಿ ಪಾಸ್‌ಕೀ ಅನ್ನು ರಚಿಸಬೇಕೆ?"</string>
+    <string name="save_password_on_other_device_title" msgid="5829084591948321207">"ಇನ್ನೊಂದು ಸಾಧನದಲ್ಲಿ ಪಾಸ್‌ವರ್ಡ್ ಉಳಿಸಬೇಕೆ?"</string>
+    <string name="save_sign_in_on_other_device_title" msgid="2827990118560134692">"ಮತ್ತೊಂದು ಸಾಧನದಲ್ಲಿ ಸೈನ್-ಇನ್ ಅನ್ನು ಉಳಿಸಬೇಕೆ?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"ನಿಮ್ಮ ಎಲ್ಲಾ ಸೈನ್-ಇನ್‌ಗಳಿಗಾಗಿ <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ಅನ್ನು ಬಳಸುವುದೇ?"</string>
-    <string name="use_provider_for_all_description" msgid="8466427781848268490">"ಈ ಪಾಸ್‌ವರ್ಡ್ ನಿರ್ವಾಹಕವು ನಿಮಗೆ ಸುಲಭವಾಗಿ ಸೈನ್ ಇನ್ ಮಾಡುವುದಕ್ಕೆ ಸಹಾಯ ಮಾಡಲು ನಿಮ್ಮ ಪಾಸ್‌ವರ್ಡ್‌ಗಳು ಮತ್ತು ಪಾಸ್‌ಕೀಗಳನ್ನು ಸಂಗ್ರಹಿಸುತ್ತದೆ"</string>
+    <string name="use_provider_for_all_description" msgid="1998772715863958997">"<xliff:g id="USERNAME">%1$s</xliff:g> ಗಾಗಿ ಈ ಪಾಸ್‌ವರ್ಡ್ ನಿರ್ವಾಹಕವು ನಿಮಗೆ ಸುಲಭವಾಗಿ ಸೈನ್ ಇನ್ ಮಾಡುವುದಕ್ಕೆ ಸಹಾಯ ಮಾಡಲು ನಿಮ್ಮ ಪಾಸ್‌ವರ್ಡ್‌ಗಳು ಮತ್ತು ಪಾಸ್‌ಕೀಗಳನ್ನು ಸಂಗ್ರಹಿಸುತ್ತದೆ"</string>
     <string name="set_as_default" msgid="4415328591568654603">"ಡೀಫಾಲ್ಟ್ ಆಗಿ ಸೆಟ್ ಮಾಡಿ"</string>
+    <string name="settings" msgid="6536394145760913145">"ಸೆಟ್ಟಿಂಗ್‌ಗಳು"</string>
     <string name="use_once" msgid="9027366575315399714">"ಒಂದು ಬಾರಿ ಬಳಸಿ"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> ಪಾಸ್‌ವರ್ಡ್‌ಗಳು • <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> ಪಾಸ್‌ಕೀಗಳು"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> ಪಾಸ್‌ವರ್ಡ್‌ಗಳು"</string>
@@ -47,21 +66,30 @@
     <string name="other_password_manager" msgid="565790221427004141">"ಇತರ ಪಾಸ್‌ವರ್ಡ್ ನಿರ್ವಾಹಕರು"</string>
     <string name="close_sheet" msgid="1393792015338908262">"ಶೀಟ್ ಮುಚ್ಚಿರಿ"</string>
     <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"ಹಿಂದಿನ ಪುಟಕ್ಕೆ ಹಿಂದಿರುಗಿ"</string>
-    <string name="accessibility_close_button" msgid="2953807735590034688">"ಸ್ಕ್ರೀನ್‌ನ ಕೆಳಭಾಗದಲ್ಲಿ ಗೋಚರಿಸುವ ರುಜುವಾತು ನಿರ್ವಾಹಕ ಕ್ರಿಯೆಯ ಸಲಹೆಯನ್ನು ಮುಚ್ಚಿ"</string>
+    <string name="accessibility_close_button" msgid="1163435587545377687">"ಮುಚ್ಚಿರಿ"</string>
+    <string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"ವಜಾಗೊಳಿಸಿ"</string>
     <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"<xliff:g id="APP_NAME">%1$s</xliff:g> ಗಾಗಿ ಉಳಿಸಲಾದ ನಿಮ್ಮ ಪಾಸ್‌ಕೀ ಅನ್ನು ಬಳಸಬೇಕೆ?"</string>
-    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"<xliff:g id="APP_NAME">%1$s</xliff:g> ಗಾಗಿ ಉಳಿಸಲಾದ ನಿಮ್ಮ ಸೈನ್-ಇನ್ ಅನ್ನು ಬಳಸಬೇಕೆ?"</string>
-    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"<xliff:g id="APP_NAME">%1$s</xliff:g> ಗಾಗಿ ಉಳಿಸಲಾದ ಸೈನ್-ಇನ್ ಮಾಹಿತಿಯನ್ನು ಆಯ್ಕೆಮಾಡಿ"</string>
+    <string name="get_dialog_title_use_password_for" msgid="625828023234318484">"<xliff:g id="APP_NAME">%1$s</xliff:g> ಗಾಗಿ ನಿಮ್ಮ ಉಳಿಸಲಾದ ಪಾಸ್‌ವರ್ಡ್ ಅನ್ನು ಬಳಸಬೇಕೇ?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"<xliff:g id="APP_NAME">%1$s</xliff:g> ಗಾಗಿ ನಿಮ್ಮ ಸೈನ್ ಇನ್ ಅನ್ನು ಬಳಸಬೇಕೇ?"</string>
+    <string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"<xliff:g id="APP_NAME">%1$s</xliff:g> ಗಾಗಿ ಸೈನ್-ಇನ್ ಆಯ್ಕೆಗಳನ್ನು ಅನ್‌ಲಾಕ್ ಮಾಡಬೇಕೇ?"</string>
+    <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"<xliff:g id="APP_NAME">%1$s</xliff:g> ಗಾಗಿ ಉಳಿಸಲಾದ ಪಾಸ್‌ಕೀ ಅನ್ನು ಆರಿಸಿ"</string>
+    <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"<xliff:g id="APP_NAME">%1$s</xliff:g> ಗಾಗಿ ಉಳಿಸಲಾದ ಪಾಸ್‌ವರ್ಡ್ ಅನ್ನು ಆರಿಸಿ"</string>
+    <string name="get_dialog_title_choose_saved_sign_in_for" msgid="2420298653461652728">"<xliff:g id="APP_NAME">%1$s</xliff:g> ಗಾಗಿ ಉಳಿಸಲಾದ ಸೈನ್-ಇನ್ ಮಾಹಿತಿಯನ್ನು ಆಯ್ಕೆಮಾಡಿ"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="3048870756117876514">"<xliff:g id="APP_NAME">%1$s</xliff:g> ಗಾಗಿ ಸೈನ್ ಇನ್ ಅನ್ನು ಆರಿಸಿ"</string>
+    <string name="get_dialog_title_choose_option_for" msgid="4976380044745029107">"<xliff:g id="APP_NAME">%1$s</xliff:g> ಗಾಗಿ ಆಯ್ಕೆಯನ್ನು ಆರಿಸಬೇಕೆ?"</string>
+    <string name="get_dialog_title_use_info_on" msgid="8863708099535435146">"ಈ ಮಾಹಿತಿಯನ್ನು <xliff:g id="APP_NAME">%1$s</xliff:g> ನಲ್ಲಿ ಬಳಸಬೇಕೆ?"</string>
     <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"ಬೇರೆ ವಿಧಾನದಲ್ಲಿ ಸೈನ್ ಇನ್ ಮಾಡಿ"</string>
     <string name="snackbar_action" msgid="37373514216505085">"ಆಯ್ಕೆಗಳನ್ನು ವೀಕ್ಷಿಸಿ"</string>
     <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"ಮುಂದುವರಿಸಿ"</string>
     <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"ಸೈನ್ ಇನ್ ಆಯ್ಕೆಗಳು"</string>
+    <string name="button_label_view_more" msgid="3429098227286495651">"ಇನ್ನಷ್ಟು ವೀಕ್ಷಿಸಿ"</string>
     <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"<xliff:g id="USERNAME">%1$s</xliff:g> ಗಾಗಿ"</string>
     <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"ಪಾಸ್‌ವರ್ಡ್ ನಿರ್ವಾಹಕರನ್ನು ಲಾಕ್ ಮಾಡಲಾಗಿದೆ"</string>
-    <!-- no translation found for locked_credential_entry_label_subtext_tap_to_unlock (6390367581393605009) -->
-    <skip />
-    <!-- no translation found for locked_credential_entry_label_subtext_no_sign_in (8131725029983174901) -->
-    <skip />
+    <string name="locked_credential_entry_label_subtext_tap_to_unlock" msgid="6390367581393605009">"ಅನ್‌ಲಾಕ್ ಮಾಡಲು ಟ್ಯಾಪ್ ಮಾಡಿ"</string>
+    <string name="locked_credential_entry_label_subtext_no_sign_in" msgid="8131725029983174901">"ಯಾವುದೇ ಸೈನ್ ಇನ್ ಮಾಹಿತಿಯಿಲ್ಲ"</string>
+    <string name="no_sign_in_info_in" msgid="2641118151920288356">"<xliff:g id="SOURCE">%1$s</xliff:g> ನಲ್ಲಿ ಯಾವುದೇ ಸೈನ್-ಇನ್ ಮಾಹಿತಿ ಇಲ್ಲ"</string>
     <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"ಸೈನ್-ಇನ್‌ಗಳನ್ನು ನಿರ್ವಹಿಸಿ"</string>
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"ಮತ್ತೊಂದು ಸಾಧನದಿಂದ"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"ಬೇರೆ ಸಾಧನವನ್ನು ಬಳಸಿ"</string>
+    <string name="request_cancelled_by" msgid="3735222326886267820">"<xliff:g id="APP_NAME">%1$s</xliff:g> ನಿಂದ ವಿನಂತಿಯನ್ನು ರದ್ದುಗೊಳಿಸಲಾಗಿದೆ"</string>
 </resources>
diff --git a/packages/CredentialManager/res/values-ko/strings.xml b/packages/CredentialManager/res/values-ko/strings.xml
index 21fce5d..07a7fbc 100644
--- a/packages/CredentialManager/res/values-ko/strings.xml
+++ b/packages/CredentialManager/res/values-ko/strings.xml
@@ -1,21 +1,35 @@
 <?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.
+   -->
+
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="4539824758261855508">"인증서 관리자"</string>
     <string name="string_cancel" msgid="6369133483981306063">"취소"</string>
     <string name="string_continue" msgid="1346732695941131882">"계속"</string>
-    <string name="string_more_options" msgid="7990658711962795124">"옵션 더보기"</string>
+    <string name="string_more_options" msgid="2763852250269945472">"다른 방법 저장하기"</string>
     <string name="string_learn_more" msgid="4541600451688392447">"자세히 알아보기"</string>
-    <!-- no translation found for content_description_show_password (3283502010388521607) -->
-    <skip />
-    <!-- no translation found for content_description_hide_password (6841375971631767996) -->
-    <skip />
+    <string name="content_description_show_password" msgid="3283502010388521607">"비밀번호 표시"</string>
+    <string name="content_description_hide_password" msgid="6841375971631767996">"비밀번호 숨기기"</string>
     <string name="passkey_creation_intro_title" msgid="4251037543787718844">"패스키로 더 안전하게"</string>
     <string name="passkey_creation_intro_body_password" msgid="8825872426579958200">"패스키를 사용하면 복잡한 비밀번호를 만들거나 기억하지 않아도 됩니다."</string>
     <string name="passkey_creation_intro_body_fingerprint" msgid="7331338631826254055">"패스키는 지문, 얼굴 또는 화면 잠금으로 생성하는 암호화된 디지털 키입니다."</string>
     <string name="passkey_creation_intro_body_device" msgid="1203796455762131631">"비밀번호 관리자에 저장되므로 다른 기기에서 로그인할 수 있습니다."</string>
     <string name="more_about_passkeys_title" msgid="7797903098728837795">"패스키 자세히 알아보기"</string>
-    <string name="passwordless_technology_title" msgid="2497513482056606668">"비밀번호 없는 기술"</string>
+    <string name="passwordless_technology_title" msgid="2497513482056606668">"패스워드리스 기술"</string>
     <string name="passwordless_technology_detail" msgid="6853928846532955882">"패스키를 이용하면 비밀번호에 의존하지 않고 로그인할 수 있습니다. 지문, 얼굴 인식, PIN 또는 스와이프 패턴만으로 본인 인증을 하고 패스키를 만들 수 있습니다."</string>
     <string name="public_key_cryptography_title" msgid="6751970819265298039">"공개 키 암호화"</string>
     <string name="public_key_cryptography_detail" msgid="6937631710280562213">"FIDO 연합(Google, Apple, Microsoft 등 포함) 및 W3C 표준을 토대로 패스키는 암호화 키 쌍을 사용합니다. 사용자 이름과 비밀번호로 사용하는 문자열과는 달리 비공개-공개 키 쌍은 특정 앱 또는 웹사이트를 대상으로 생성됩니다. 비공개 키는 안전하게 기기 또는 비밀번호 관리자에 저장되며 사용자 본인 인증에 사용됩니다. 공개 키는 앱 또는 웹사이트 서버와 공유됩니다. 해당하는 키를 사용하면 즉시 등록하고 로그인할 수 있습니다."</string>
@@ -35,10 +49,13 @@
     <string name="sign_ins" msgid="4710739369149469208">"로그인 정보"</string>
     <string name="sign_in_info" msgid="2627704710674232328">"로그인 정보"</string>
     <string name="save_credential_to_title" msgid="3172811692275634301">"<xliff:g id="CREDENTIALTYPES">%1$s</xliff:g> 저장 위치"</string>
-    <string name="create_passkey_in_other_device_title" msgid="9195411122362461390">"다른 기기에서 패스키를 만드시겠습니까?"</string>
+    <string name="create_passkey_in_other_device_title" msgid="2360053098931886245">"다른 기기에서 패스키를 만드시겠습니까?"</string>
+    <string name="save_password_on_other_device_title" msgid="5829084591948321207">"다른 기기에서 비밀번호를 저장하시겠습니까?"</string>
+    <string name="save_sign_in_on_other_device_title" msgid="2827990118560134692">"다른 기기에서 로그인 정보를 저장하시겠습니까?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"모든 로그인에 <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>을(를) 사용하시겠습니까?"</string>
-    <string name="use_provider_for_all_description" msgid="8466427781848268490">"이 비밀번호 관리자는 비밀번호와 패스키를 저장하여 사용자가 간편하게 로그인할 수 있도록 돕습니다."</string>
+    <string name="use_provider_for_all_description" msgid="1998772715863958997">"<xliff:g id="USERNAME">%1$s</xliff:g>님을 위한 이 비밀번호 관리자는 비밀번호와 패스키를 저장하여 사용자가 간편하게 로그인할 수 있도록 돕습니다."</string>
     <string name="set_as_default" msgid="4415328591568654603">"기본값으로 설정"</string>
+    <string name="settings" msgid="6536394145760913145">"설정"</string>
     <string name="use_once" msgid="9027366575315399714">"한 번 사용"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"비밀번호 <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>개 • 패스키 <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>개"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"비밀번호 <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>개"</string>
@@ -49,21 +66,30 @@
     <string name="other_password_manager" msgid="565790221427004141">"기타 비밀번호 관리자"</string>
     <string name="close_sheet" msgid="1393792015338908262">"시트 닫기"</string>
     <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"이전 페이지로 돌아가기"</string>
-    <string name="accessibility_close_button" msgid="2953807735590034688">"화면 하단에 표시되는 인증 관리자 작업 제안 닫기"</string>
-    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"<xliff:g id="APP_NAME">%1$s</xliff:g> 앱용 저장된 패스키를 사용하시겠습니까?"</string>
-    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"<xliff:g id="APP_NAME">%1$s</xliff:g> 앱용 저장된 로그인 정보를 사용하시겠습니까?"</string>
-    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"<xliff:g id="APP_NAME">%1$s</xliff:g> 앱용 저장된 로그인 정보 선택"</string>
+    <string name="accessibility_close_button" msgid="1163435587545377687">"닫기"</string>
+    <string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"닫기"</string>
+    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"<xliff:g id="APP_NAME">%1$s</xliff:g> 앱용으로 저장된 패스키를 사용하시겠습니까?"</string>
+    <string name="get_dialog_title_use_password_for" msgid="625828023234318484">"저장된 비밀번호를 <xliff:g id="APP_NAME">%1$s</xliff:g>에 사용할까요?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"<xliff:g id="APP_NAME">%1$s</xliff:g>에 로그인을 사용하시겠습니까?"</string>
+    <string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"<xliff:g id="APP_NAME">%1$s</xliff:g>에 대해 로그인 옵션을 잠금 해제하시겠습니까?"</string>
+    <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"<xliff:g id="APP_NAME">%1$s</xliff:g>에 대해 저장된 패스키 선택"</string>
+    <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"<xliff:g id="APP_NAME">%1$s</xliff:g>에 대해 저장된 비밀번호 선택"</string>
+    <string name="get_dialog_title_choose_saved_sign_in_for" msgid="2420298653461652728">"<xliff:g id="APP_NAME">%1$s</xliff:g>에 대해 저장된 로그인 정보 선택"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="3048870756117876514">"<xliff:g id="APP_NAME">%1$s</xliff:g>에 사용할 로그인 선택"</string>
+    <string name="get_dialog_title_choose_option_for" msgid="4976380044745029107">"<xliff:g id="APP_NAME">%1$s</xliff:g> 앱의 옵션을 선택하시겠습니까?"</string>
+    <string name="get_dialog_title_use_info_on" msgid="8863708099535435146">"<xliff:g id="APP_NAME">%1$s</xliff:g>에서 이 정보를 사용하시나요?"</string>
     <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"다른 방법으로 로그인"</string>
     <string name="snackbar_action" msgid="37373514216505085">"옵션 보기"</string>
     <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"계속"</string>
     <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"로그인 옵션"</string>
+    <string name="button_label_view_more" msgid="3429098227286495651">"더보기"</string>
     <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"<xliff:g id="USERNAME">%1$s</xliff:g>님의 로그인 정보"</string>
     <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"잠긴 비밀번호 관리자"</string>
-    <!-- no translation found for locked_credential_entry_label_subtext_tap_to_unlock (6390367581393605009) -->
-    <skip />
-    <!-- no translation found for locked_credential_entry_label_subtext_no_sign_in (8131725029983174901) -->
-    <skip />
+    <string name="locked_credential_entry_label_subtext_tap_to_unlock" msgid="6390367581393605009">"탭하여 잠금 해제"</string>
+    <string name="locked_credential_entry_label_subtext_no_sign_in" msgid="8131725029983174901">"로그인 정보 없음"</string>
+    <string name="no_sign_in_info_in" msgid="2641118151920288356">"<xliff:g id="SOURCE">%1$s</xliff:g>의 로그인 정보 없음"</string>
     <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"로그인 관리"</string>
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"다른 기기에서"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"다른 기기 사용"</string>
+    <string name="request_cancelled_by" msgid="3735222326886267820">"<xliff:g id="APP_NAME">%1$s</xliff:g>에 의해 요청이 취소됨"</string>
 </resources>
diff --git a/packages/CredentialManager/res/values-ky/strings.xml b/packages/CredentialManager/res/values-ky/strings.xml
index 37e078a..b4c5670 100644
--- a/packages/CredentialManager/res/values-ky/strings.xml
+++ b/packages/CredentialManager/res/values-ky/strings.xml
@@ -1,67 +1,95 @@
 <?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.
+   -->
+
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="4539824758261855508">"Credential Manager"</string>
     <string name="string_cancel" msgid="6369133483981306063">"Жок"</string>
     <string name="string_continue" msgid="1346732695941131882">"Улантуу"</string>
-    <string name="string_more_options" msgid="7990658711962795124">"Башка варианттар"</string>
+    <string name="string_more_options" msgid="2763852250269945472">"Башка жолду сактоо"</string>
     <string name="string_learn_more" msgid="4541600451688392447">"Кеңири маалымат"</string>
     <string name="content_description_show_password" msgid="3283502010388521607">"Сырсөздү көрсөтүү"</string>
     <string name="content_description_hide_password" msgid="6841375971631767996">"Сырсөздү жашыруу"</string>
-    <string name="passkey_creation_intro_title" msgid="4251037543787718844">"Мүмкүндүк алуу ачкычтары менен коопсузураак болот"</string>
-    <string name="passkey_creation_intro_body_password" msgid="8825872426579958200">"Мүмкүндүк алуу ачкычтары менен татаал сырсөздөрдү түзүп же эстеп калуунун кереги жок"</string>
-    <string name="passkey_creation_intro_body_fingerprint" msgid="7331338631826254055">"Мүмкүндүк алуу ачкычтары – манжаңыздын изи, жүзүңүз же экранды кулпулоо функциясы аркылуу түзгөн шифрленген санариптик ачкычтар"</string>
+    <string name="passkey_creation_intro_title" msgid="4251037543787718844">"Киргизүүчү ачкычтар менен коопсузураак болот"</string>
+    <string name="passkey_creation_intro_body_password" msgid="8825872426579958200">"Киргизүүчү ачкычтар менен татаал сырсөздөрдү түзүп же эстеп калуунун кереги жок"</string>
+    <string name="passkey_creation_intro_body_fingerprint" msgid="7331338631826254055">"Киргизүүчү ачкычтар – манжаңыздын изи, жүзүңүз же экранды кулпулоо функциясы аркылуу түзгөн шифрленген санариптик ачкычтар"</string>
     <string name="passkey_creation_intro_body_device" msgid="1203796455762131631">"Алар сырсөздөрдү башкаргычка сакталып, аккаунтуңузга башка түзмөктөрдөн кире аласыз"</string>
-    <string name="more_about_passkeys_title" msgid="7797903098728837795">"Мүмкүндүк алуу ачкычтары тууралуу кеңири маалымат"</string>
+    <string name="more_about_passkeys_title" msgid="7797903098728837795">"Киргизүүчү ачкычтар тууралуу кеңири маалымат"</string>
     <string name="passwordless_technology_title" msgid="2497513482056606668">"Сырсөзсүз технология"</string>
-    <string name="passwordless_technology_detail" msgid="6853928846532955882">"Мүмкүндүк алуу ачкычтары аркылуу сырсөздөрсүз эле аккаунтуңузга кире аласыз. Ким экениңизди ырастоо жана мүмкүндүк алуу ачкычын түзүү үчүн жөн гана манжаңыздын изин, жүзүнөн таануу функциясын, PIN кодду же графикалык ачкычты колдонушуңуз керек болот."</string>
+    <string name="passwordless_technology_detail" msgid="6853928846532955882">"Киргизүүчү ачкычтар аркылуу сырсөздөрсүз эле аккаунтуңузга кире аласыз. Ким экениңизди ырастоо жана киргизүүчү ачкычты түзүү үчүн жөн гана манжаңыздын изин, жүзүнөн таануу функциясын, PIN кодду же графикалык ачкычты колдонушуңуз керек болот."</string>
     <string name="public_key_cryptography_title" msgid="6751970819265298039">"Жалпыга ачык ачкыч менен криптография"</string>
-    <string name="public_key_cryptography_detail" msgid="6937631710280562213">"FIDO Alliance (Google, Apple, Microsoft ж.б.) жана W3C стандартына ылайык, мүмкүндүк алуу ачкычтары криптографиялык жуп ачкычтарды колдонот. Колдонмо же вебсайтта колдонуучунун аты жана сырсөз үчүн колдонулган символ сабы эмес, купуя жана жалпыга ачык жуп ачкыч түзүлөт. Купуя ачкыч түзмөктө же сырсөздөрдү башкаргычта коопсуз сакталып, өздүгүңүздү ырастоо үчүн колдонулат. Жалпыга ачык ачкыч колдонмо же вебсайттын серверине жөнөтүлөт. Туура келген ачкычтар аркылуу тез катталып жана кире аласыз."</string>
+    <string name="public_key_cryptography_detail" msgid="6937631710280562213">"FIDO Alliance (Google, Apple, Microsoft ж.б.) жана W3C стандартына ылайык, киргизүүчү ачкычтар криптографиялык жуп ачкычтарды колдонот. Колдонмо же вебсайтта колдонуучунун аты жана сырсөз үчүн колдонулган символ сабы эмес, купуя жана жалпыга ачык жуп ачкыч түзүлөт. Купуя ачкыч түзмөктө же сырсөздөрдү башкаргычта коопсуз сакталып, өздүгүңүздү ырастоо үчүн колдонулат. Жалпыга ачык ачкыч колдонмо же вебсайттын серверине жөнөтүлөт. Туура келген ачкычтар аркылуу тез катталып жана кире аласыз."</string>
     <string name="improved_account_security_title" msgid="1069841917893513424">"Аккаунттун коопсуздугу жакшыртылды"</string>
     <string name="improved_account_security_detail" msgid="9123750251551844860">"Ар бир ачкыч өзү арналган колдонмо же вебсайт менен гана байланыштырылгандыктан, эч качан шылуундардын колдонмолоруна же вебсайттарына жаңылыштык менен кирип албайсыз. Мындан тышкары, серверлерде жалпыга ачык ачкычтар гана сакталгандыктан, хакерлик кылуу кыйла кыйын."</string>
     <string name="seamless_transition_title" msgid="5335622196351371961">"Тез которулуу"</string>
-    <string name="seamless_transition_detail" msgid="4475509237171739843">"Сырсөзсүз келечекти көздөй баратсак да, аларды мүмкүндүк алуу ачкычтары менен бирге колдоно берүүгө болот."</string>
+    <string name="seamless_transition_detail" msgid="4475509237171739843">"Сырсөзсүз келечекти көздөй баратсак да, аларды киргизүүчү ачкычтар менен бирге колдоно берүүгө болот."</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"<xliff:g id="CREATETYPES">%1$s</xliff:g> кайда сакталарын тандаңыз"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"Маалыматыңызды сактоо жана кийинки жолу тезирээк кирүү үчүн сырсөздөрдү башкаргычты тандаңыз"</string>
-    <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"<xliff:g id="APPNAME">%1$s</xliff:g> үчүн мүмкүндүк алуу ачкычын түзөсүзбү?"</string>
+    <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"<xliff:g id="APPNAME">%1$s</xliff:g> колдонмосуна киргизүүчү ачкыч түзөсүзбү?"</string>
     <string name="choose_create_option_password_title" msgid="7097275038523578687">"<xliff:g id="APPNAME">%1$s</xliff:g> үчүн сырсөз сакталсынбы?"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"<xliff:g id="APPNAME">%1$s</xliff:g> үчүн кирүү маалыматы сакталсынбы?"</string>
-    <string name="passkey" msgid="632353688396759522">"мүмкүндүк алуу ачкычы"</string>
+    <string name="passkey" msgid="632353688396759522">"киргизүүчү ачкыч"</string>
     <string name="password" msgid="6738570945182936667">"сырсөз"</string>
-    <string name="passkeys" msgid="5733880786866559847">"мүмкүндүк алуу ачкычтары"</string>
+    <string name="passkeys" msgid="5733880786866559847">"киргизүүчү ачкычтар"</string>
     <string name="passwords" msgid="5419394230391253816">"сырсөздөр"</string>
     <string name="sign_ins" msgid="4710739369149469208">"кирүүлөр"</string>
     <string name="sign_in_info" msgid="2627704710674232328">"кирүү маалыматы"</string>
     <string name="save_credential_to_title" msgid="3172811692275634301">"<xliff:g id="CREDENTIALTYPES">%1$s</xliff:g> төмөнкүгө сакталсын:"</string>
-    <string name="create_passkey_in_other_device_title" msgid="9195411122362461390">"Мүмкүндүк алуу ачкычы башка түзмөктө түзүлсүнбү?"</string>
+    <string name="create_passkey_in_other_device_title" msgid="2360053098931886245">"Башка түзмөктө киргизүүчү ачкычты түзөсүзбү?"</string>
+    <string name="save_password_on_other_device_title" msgid="5829084591948321207">"Сырсөздү башка түзмөктө сактайсызбы?"</string>
+    <string name="save_sign_in_on_other_device_title" msgid="2827990118560134692">"Кирүү маалыматын башка түзмөктө сактайсызбы?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> бардык аккаунттарга кирүү үчүн колдонулсунбу?"</string>
-    <string name="use_provider_for_all_description" msgid="8466427781848268490">"Сырсөздөрүңүздү жана ачкычтарыңызды Сырсөздөрдү башкаргычка сактап коюп, каалаган убакта колдоно берсеңиз болот"</string>
+    <string name="use_provider_for_all_description" msgid="1998772715863958997">"Сырсөздөрүңүздү жана киргизүүчү ачкычтарыңызды <xliff:g id="USERNAME">%1$s</xliff:g> аккаунтуңуздагы сырсөздөрдү башкаргычка сактап коюп, каалаган убакта колдоно берсеңиз болот"</string>
     <string name="set_as_default" msgid="4415328591568654603">"Демейки катары коюу"</string>
+    <string name="settings" msgid="6536394145760913145">"Параметрлер"</string>
     <string name="use_once" msgid="9027366575315399714">"Бир жолу колдонуу"</string>
-    <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> сырсөз • <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> мүмкүндүк алуу ачкычы"</string>
+    <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> сырсөз • <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> киргизүүчү ачкыч"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> сырсөз"</string>
-    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> мүмкүндүк алуу ачкычы"</string>
+    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> киргизүүчү ачкыч"</string>
     <string name="more_options_usage_credentials" msgid="1785697001787193984">"<xliff:g id="TOTALCREDENTIALSNUMBER">%1$s</xliff:g> эсептик дайындары"</string>
-    <string name="passkey_before_subtitle" msgid="2448119456208647444">"Мүмкүндүк алуу ачкычы"</string>
+    <string name="passkey_before_subtitle" msgid="2448119456208647444">"Киргизүүчү ачкыч"</string>
     <string name="another_device" msgid="5147276802037801217">"Башка түзмөк"</string>
     <string name="other_password_manager" msgid="565790221427004141">"Башка сырсөздөрдү башкаргычтар"</string>
     <string name="close_sheet" msgid="1393792015338908262">"Баракты жабуу"</string>
     <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Мурунку бетке кайтуу"</string>
-    <string name="accessibility_close_button" msgid="2953807735590034688">"Экрандын ылдый жагындагы Credential Manager кызматынын сунушун жабуу"</string>
-    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"<xliff:g id="APP_NAME">%1$s</xliff:g> үчүн сакталган мүмкүндүк алуу ачкычын колдоносузбу?"</string>
-    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"<xliff:g id="APP_NAME">%1$s</xliff:g> үчүн сакталган кирүү параметрин колдоносузбу?"</string>
-    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"<xliff:g id="APP_NAME">%1$s</xliff:g> үчүн кирүү маалыматын тандаңыз"</string>
+    <string name="accessibility_close_button" msgid="1163435587545377687">"Жабуу"</string>
+    <string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"Жабуу"</string>
+    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"<xliff:g id="APP_NAME">%1$s</xliff:g> колдонмосуна кирүү үчүн сакталган ачкычты колдоносузбу?"</string>
+    <string name="get_dialog_title_use_password_for" msgid="625828023234318484">"<xliff:g id="APP_NAME">%1$s</xliff:g> үчүн сакталган сырсөздү колдоносузбу?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"<xliff:g id="APP_NAME">%1$s</xliff:g> колдонмосуна кирүү жолун тандайсызбы?"</string>
+    <string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"<xliff:g id="APP_NAME">%1$s</xliff:g> үчүн кирүү параметрлеринин кулпусу ачылсынбы?"</string>
+    <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"<xliff:g id="APP_NAME">%1$s</xliff:g> үчүн сакталган киргизүүчү ачкычты тандаңыз"</string>
+    <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"<xliff:g id="APP_NAME">%1$s</xliff:g> үчүн сакталган сырсөздү тандаңыз"</string>
+    <string name="get_dialog_title_choose_saved_sign_in_for" msgid="2420298653461652728">"<xliff:g id="APP_NAME">%1$s</xliff:g> үчүн кирүү маалыматын тандаңыз"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="3048870756117876514">"<xliff:g id="APP_NAME">%1$s</xliff:g> колдонмосуна кирүү жолун тандаңыз"</string>
+    <string name="get_dialog_title_choose_option_for" msgid="4976380044745029107">"<xliff:g id="APP_NAME">%1$s</xliff:g> үчүн параметр тандайсызбы?"</string>
+    <string name="get_dialog_title_use_info_on" msgid="8863708099535435146">"Бул маалыматты <xliff:g id="APP_NAME">%1$s</xliff:g> колдонмосунда пайдаланасызбы?"</string>
     <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Башка жол менен кирүү"</string>
     <string name="snackbar_action" msgid="37373514216505085">"Параметрлерди көрүү"</string>
     <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Улантуу"</string>
     <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Аккаунтка кирүү параметрлери"</string>
+    <string name="button_label_view_more" msgid="3429098227286495651">"Дагы көрүү"</string>
     <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"<xliff:g id="USERNAME">%1$s</xliff:g> үчүн"</string>
     <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Кулпуланган сырсөздөрдү башкаргычтар"</string>
-    <!-- no translation found for locked_credential_entry_label_subtext_tap_to_unlock (6390367581393605009) -->
-    <skip />
-    <!-- no translation found for locked_credential_entry_label_subtext_no_sign_in (8131725029983174901) -->
-    <skip />
+    <string name="locked_credential_entry_label_subtext_tap_to_unlock" msgid="6390367581393605009">"Кулпусун ачуу үчүн таптаңыз"</string>
+    <string name="locked_credential_entry_label_subtext_no_sign_in" msgid="8131725029983174901">"Аккаунтка кирүү тууралуу маалымат жок"</string>
+    <string name="no_sign_in_info_in" msgid="2641118151920288356">"<xliff:g id="SOURCE">%1$s</xliff:g> аккаунтунда кирүү маалыматы жок"</string>
     <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Кирүү параметрлерин тескөө"</string>
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Башка түзмөктөн"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Башка түзмөктү колдонуу"</string>
+    <string name="request_cancelled_by" msgid="3735222326886267820">"Сурамды <xliff:g id="APP_NAME">%1$s</xliff:g> жокко чыгарды"</string>
 </resources>
diff --git a/packages/CredentialManager/res/values-lo/strings.xml b/packages/CredentialManager/res/values-lo/strings.xml
index 1213259..3b2e2aa 100644
--- a/packages/CredentialManager/res/values-lo/strings.xml
+++ b/packages/CredentialManager/res/values-lo/strings.xml
@@ -1,10 +1,26 @@
 <?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="4539824758261855508">"ຕົວຈັດການຂໍ້ມູນການເຂົ້າສູ່ລະບົບ"</string>
     <string name="string_cancel" msgid="6369133483981306063">"ຍົກເລີກ"</string>
     <string name="string_continue" msgid="1346732695941131882">"ສືບຕໍ່"</string>
-    <string name="string_more_options" msgid="7990658711962795124">"ຕົວເລືອກເພີ່ມເຕີມ"</string>
+    <string name="string_more_options" msgid="2763852250269945472">"ບັນທຶກດ້ວຍວິທີອື່ນ"</string>
     <string name="string_learn_more" msgid="4541600451688392447">"ສຶກສາເພີ່ມເຕີມ"</string>
     <string name="content_description_show_password" msgid="3283502010388521607">"ສະແດງລະຫັດຜ່ານ"</string>
     <string name="content_description_hide_password" msgid="6841375971631767996">"ເຊື່ອງລະຫັດຜ່ານ"</string>
@@ -20,7 +36,7 @@
     <string name="improved_account_security_title" msgid="1069841917893513424">"ປັບປຸງຄວາມປອດໄພບັນຊີ"</string>
     <string name="improved_account_security_detail" msgid="9123750251551844860">"ກະແຈແຕ່ລະອັນແມ່ນລິ້ງເຈາະຈົງກັບແອັບ ຫຼື ເວັບໄຊທີ່ພວກມັນຖືກສ້າງໃຫ້, ດັ່ງນັ້ນທ່ານຈະບໍ່ສາມາດເຂົ້າສູ່ລະບົບຫາແອັບ ຫຼື ເວັບໄຊສໍ້ໂກງຕ່າງໆໂດຍບໍ່ໄດ້ຕັ້ງໃຈໄດ້. ນອກຈາກນັ້ນ, ເຊີບເວີຍັງມີການເກັບກະແຈສາທາລະນະໄວ້ເທົ່ານັ້ນ, ການແຮັກຈຶ່ງເປັນເລື່ອງຍາກຂຶ້ນຫຼາຍ."</string>
     <string name="seamless_transition_title" msgid="5335622196351371961">"ການປ່ຽນຜ່ານທີ່ຕໍ່ເນື່ອງ"</string>
-    <string name="seamless_transition_detail" msgid="4475509237171739843">"ໃນຂະນະທີ່ພວກເຮົາກ້າວໄປສູ່ອະນາຄົດທີ່ບໍ່ຕ້ອງໃຊ້ລະຫັດຜ່ານ, ລະຫັດຜ່ານຈະຍັງຄົງໃຊ້ໄດ້ຄວບຄູ່ໄປກັບລະຫັດຜ່ານ."</string>
+    <string name="seamless_transition_detail" msgid="4475509237171739843">"ໃນຂະນະທີ່ພວກເຮົາກ້າວໄປສູ່ອະນາຄົດທີ່ບໍ່ຕ້ອງໃຊ້ລະຫັດຜ່ານ, ລະຫັດຜ່ານຈະຍັງຄົງໃຊ້ໄດ້ຄວບຄູ່ໄປກັບກະແຈຜ່ານ."</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"ເລືອກບ່ອນທີ່ຈະບັນທຶກ <xliff:g id="CREATETYPES">%1$s</xliff:g> ຂອງທ່ານ"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"ເລືອກຕົວຈັດການລະຫັດຜ່ານເພື່ອບັນທຶກຂໍ້ມູນຂອງທ່ານ ແລະ ເຂົ້າສູ່ລະບົບໄວຂຶ້ນໃນເທື່ອຕໍ່ໄປ"</string>
     <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"ສ້າງກະແຈຜ່ານສຳລັບ <xliff:g id="APPNAME">%1$s</xliff:g> ບໍ?"</string>
@@ -33,10 +49,13 @@
     <string name="sign_ins" msgid="4710739369149469208">"ການເຂົ້າສູ່ລະບົບ"</string>
     <string name="sign_in_info" msgid="2627704710674232328">"ຂໍ້ມູນການເຂົ້າສູ່ລະບົບ"</string>
     <string name="save_credential_to_title" msgid="3172811692275634301">"ບັນທຶກ <xliff:g id="CREDENTIALTYPES">%1$s</xliff:g> ໃສ່"</string>
-    <string name="create_passkey_in_other_device_title" msgid="9195411122362461390">"ສ້າງກະແຈຜ່ານໃນອຸປະກອນອື່ນບໍ?"</string>
+    <string name="create_passkey_in_other_device_title" msgid="2360053098931886245">"ສ້າງກະແຈຜ່ານຢູ່ອຸປະກອນອື່ນບໍ?"</string>
+    <string name="save_password_on_other_device_title" msgid="5829084591948321207">"ບັນທຶກລະຫັດຜ່ານຢູ່ອຸປະກອນອື່ນບໍ?"</string>
+    <string name="save_sign_in_on_other_device_title" msgid="2827990118560134692">"ບັນທຶກການເຂົ້າສູ່ລະບົບຢູ່ອຸປະກອນອື່ນບໍ?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"ໃຊ້ <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ສຳລັບການເຂົ້າສູ່ລະບົບທັງໝົດຂອງທ່ານບໍ?"</string>
-    <string name="use_provider_for_all_description" msgid="8466427781848268490">"ຕົວຈັດການລະຫັດຜ່ານນີ້ຈະຈັດເກັບລະຫັດຜ່ານ ແລະ ກະແຈຜ່ານຂອງທ່ານໄວ້ເພື່ອຊ່ວຍໃຫ້ທ່ານເຂົ້າສູ່ລະບົບໄດ້ໂດຍງ່າຍ"</string>
+    <string name="use_provider_for_all_description" msgid="1998772715863958997">"ຕົວຈັດການລະຫັດຜ່ານນີ້ສຳລັບ <xliff:g id="USERNAME">%1$s</xliff:g> ຈະຈັດເກັບລະຫັດຜ່ານ ແລະ ກະແຈຜ່ານຂອງທ່ານໄວ້ເພື່ອຊ່ວຍໃຫ້ທ່ານເຂົ້າສູ່ລະບົບໄດ້ຢ່າງງ່າຍດາຍ"</string>
     <string name="set_as_default" msgid="4415328591568654603">"ຕັ້ງເປັນຄ່າເລີ່ມຕົ້ນ"</string>
+    <string name="settings" msgid="6536394145760913145">"ການຕັ້ງຄ່າ"</string>
     <string name="use_once" msgid="9027366575315399714">"ໃຊ້ເທື່ອດຽວ"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> ລະຫັດຜ່ານ • <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> ກະແຈຜ່ານ"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> ລະຫັດຜ່ານ"</string>
@@ -47,21 +66,30 @@
     <string name="other_password_manager" msgid="565790221427004141">"ຕົວຈັດການລະຫັດຜ່ານອື່ນໆ"</string>
     <string name="close_sheet" msgid="1393792015338908262">"ປິດຊີດ"</string>
     <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"ກັບຄືນໄປຫາໜ້າກ່ອນໜ້ານີ້"</string>
-    <string name="accessibility_close_button" msgid="2953807735590034688">"ປິດການແນະນຳການດຳເນີນການຂອງຕົວຈັດການຂໍ້ມູນການເຂົ້າສູ່ລະບົບເຊິ່ງປາກົດຢູ່ລຸ່ມສຸດຂອງໜ້າຈໍ"</string>
+    <string name="accessibility_close_button" msgid="1163435587545377687">"ປິດ"</string>
+    <string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"ປິດໄວ້"</string>
     <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"ໃຊ້ກະແຈຜ່ານທີ່ບັນທຶກໄວ້ຂອງທ່ານສຳລັບ <xliff:g id="APP_NAME">%1$s</xliff:g> ບໍ?"</string>
-    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"ໃຊ້ການເຂົ້າສູ່ລະບົບທີ່ບັນທຶກໄວ້ຂອງທ່ານສຳລັບ <xliff:g id="APP_NAME">%1$s</xliff:g> ບໍ?"</string>
-    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"ເລືອກການເຂົ້າສູ່ລະບົບທີ່ບັນທຶກໄວ້ສຳລັບ <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_use_password_for" msgid="625828023234318484">"ໃຊ້ລະຫັດຜ່ານທີ່ບັນທຶກໄວ້ຂອງທ່ານສຳລັບ <xliff:g id="APP_NAME">%1$s</xliff:g> ບໍ?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"ເລືອກການເຂົ້າສູ່ລະບົບຂອງທ່ານສຳລັບ <xliff:g id="APP_NAME">%1$s</xliff:g> ບໍ?"</string>
+    <string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"ປົດລັອກຕົວເລືອກການເຂົ້າສູ່ລະບົບສຳລັບ <xliff:g id="APP_NAME">%1$s</xliff:g> ບໍ?"</string>
+    <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"ເລືອກກະແຈຜ່ານທີ່ບັນທຶກໄວ້ສຳລັບ <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"ເລືອກລະຫັດຜ່ານທີ່ບັນທຶກໄວ້ສຳລັບ <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_saved_sign_in_for" msgid="2420298653461652728">"ເລືອກການເຂົ້າສູ່ລະບົບທີ່ບັນທຶກໄວ້ສຳລັບ <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="3048870756117876514">"ເລືອກການເຂົ້າສູ່ລະບົບສຳລັບ <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_option_for" msgid="4976380044745029107">"ເລືອກຕົວເລືອກສຳລັບ <xliff:g id="APP_NAME">%1$s</xliff:g> ບໍ?"</string>
+    <string name="get_dialog_title_use_info_on" msgid="8863708099535435146">"ໃຊ້ຂໍ້ມູນນີ້ຢູ່ <xliff:g id="APP_NAME">%1$s</xliff:g> ບໍ?"</string>
     <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"ເຂົ້າສູ່ລະບົບດ້ວຍວິທີອື່ນ"</string>
     <string name="snackbar_action" msgid="37373514216505085">"ເບິ່ງຕົວເລືອກ"</string>
     <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"ສືບຕໍ່"</string>
     <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"ຕົວເລືອກການເຂົ້າສູ່ລະບົບ"</string>
+    <string name="button_label_view_more" msgid="3429098227286495651">"ເບິ່ງເພີ່ມເຕີມ"</string>
     <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"ສຳລັບ <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"ຕົວຈັດການລະຫັດຜ່ານທີ່ລັອກໄວ້"</string>
-    <!-- no translation found for locked_credential_entry_label_subtext_tap_to_unlock (6390367581393605009) -->
-    <skip />
-    <!-- no translation found for locked_credential_entry_label_subtext_no_sign_in (8131725029983174901) -->
-    <skip />
+    <string name="locked_credential_entry_label_subtext_tap_to_unlock" msgid="6390367581393605009">"ແຕະເພື່ອປົດລັອກ"</string>
+    <string name="locked_credential_entry_label_subtext_no_sign_in" msgid="8131725029983174901">"ບໍ່ມີຂໍ້ມູນການເຂົ້າສູ່ລະບົບ"</string>
+    <string name="no_sign_in_info_in" msgid="2641118151920288356">"ບໍ່ມີຂໍ້ມູນການເຂົ້າສູ່ລະບົບໃນ <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"ຈັດການການເຂົ້າສູ່ລະບົບ"</string>
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"ຈາກອຸປະກອນອື່ນ"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"ໃຊ້ອຸປະກອນອື່ນ"</string>
+    <string name="request_cancelled_by" msgid="3735222326886267820">"ການຮ້ອງຂໍຖືກຍົກເລີກໂດຍ <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
 </resources>
diff --git a/packages/CredentialManager/res/values-lt/strings.xml b/packages/CredentialManager/res/values-lt/strings.xml
index 011d745..d5f5f7f 100644
--- a/packages/CredentialManager/res/values-lt/strings.xml
+++ b/packages/CredentialManager/res/values-lt/strings.xml
@@ -1,26 +1,42 @@
 <?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.
+   -->
+
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="4539824758261855508">"Prisijungimo duomenų tvarkytuvė"</string>
     <string name="string_cancel" msgid="6369133483981306063">"Atšaukti"</string>
     <string name="string_continue" msgid="1346732695941131882">"Tęsti"</string>
-    <string name="string_more_options" msgid="7990658711962795124">"Daugiau parinkčių"</string>
+    <string name="string_more_options" msgid="2763852250269945472">"Išsaugoti kitu būdu"</string>
     <string name="string_learn_more" msgid="4541600451688392447">"Sužinokite daugiau"</string>
     <string name="content_description_show_password" msgid="3283502010388521607">"Rodyti slaptažodį"</string>
     <string name="content_description_hide_password" msgid="6841375971631767996">"Slėpti slaptažodį"</string>
-    <string name="passkey_creation_intro_title" msgid="4251037543787718844">"Saugiau naudojant slaptažodžius"</string>
-    <string name="passkey_creation_intro_body_password" msgid="8825872426579958200">"Naudojant „passkey“ nereikės kurti ir prisiminti sudėtingų slaptažodžių"</string>
-    <string name="passkey_creation_intro_body_fingerprint" msgid="7331338631826254055">"„Passkey“ šifruojami skaitiniais raktais, kuriuos sukuriate naudodami piršto atspaudą, veidą ar ekrano užraktą"</string>
+    <string name="passkey_creation_intro_title" msgid="4251037543787718844">"Saugiau naudojant prieigos raktus"</string>
+    <string name="passkey_creation_intro_body_password" msgid="8825872426579958200">"Naudojant prieigos raktus nereikės kurti ir prisiminti sudėtingų slaptažodžių"</string>
+    <string name="passkey_creation_intro_body_fingerprint" msgid="7331338631826254055">"Prieigos raktai šifruojami skaitiniais raktais, kuriuos sukuriate naudodami piršto atspaudą, veidą ar ekrano užraktą"</string>
     <string name="passkey_creation_intro_body_device" msgid="1203796455762131631">"Jie saugomi slaptažodžių tvarkyklėje, kad galėtumėte prisijungti kituose įrenginiuose"</string>
-    <string name="more_about_passkeys_title" msgid="7797903098728837795">"Daugiau apie slaptuosius raktus („passkey“)"</string>
+    <string name="more_about_passkeys_title" msgid="7797903098728837795">"Daugiau apie prieigos raktus („passkey“)"</string>
     <string name="passwordless_technology_title" msgid="2497513482056606668">"Technologijos be slaptažodžių"</string>
-    <string name="passwordless_technology_detail" msgid="6853928846532955882">"Naudodami slaptuosius raktus galite prisijungti be slaptažodžių. Tiesiog naudokite piršto atspaudą, atpažinimą pagal veidą, PIN kodą arba perbraukiamą atrakinimo piešinį, kad patvirtintumėte tapatybę ir sukurtumėte slaptąjį raktą."</string>
+    <string name="passwordless_technology_detail" msgid="6853928846532955882">"Naudodami prieigos raktus galite prisijungti be slaptažodžių. Tiesiog naudokite piršto atspaudą, atpažinimą pagal veidą, PIN kodą arba perbraukiamą atrakinimo piešinį, kad patvirtintumėte tapatybę ir sukurtumėte prieigos raktą."</string>
     <string name="public_key_cryptography_title" msgid="6751970819265298039">"Viešojo rakto kriptografija"</string>
-    <string name="public_key_cryptography_detail" msgid="6937631710280562213">"Pagal FIDO aljansą (kuriam priklauso „Google“, „Apple“, „Microsoft“ ir kt.) bei W3C standartus, slaptiesiems raktams naudojamos kriptografinių raktų poros. Priešingai nei naudotojo vardas ir eilutė simbolių, naudojamų slaptažodžiams, privataus ir viešojo raktų pora sukuriama programai ar svetainei. Tapatybę patvirtinantis privatusis raktas saugomas įrenginyje ar Slaptažodžių tvarkyklėje. Viešasis raktas bendrinamas su programos ar svetainės serveriu. Naudodami atitinkamus raktus galite akimirksniu užsiregistruoti ir prisijungti."</string>
+    <string name="public_key_cryptography_detail" msgid="6937631710280562213">"Pagal FIDO aljansą (kuriam priklauso „Google“, „Apple“, „Microsoft“ ir kt.) bei W3C standartus, prieigos raktams naudojamos kriptografinių raktų poros. Priešingai nei naudotojo vardas ir eilutė simbolių, naudojamų slaptažodžiams, privataus ir viešojo raktų pora sukuriama programai ar svetainei. Tapatybę patvirtinantis privatusis raktas saugomas įrenginyje ar Slaptažodžių tvarkyklėje. Viešasis raktas bendrinamas su programos ar svetainės serveriu. Naudodami atitinkamus raktus galite akimirksniu užsiregistruoti ir prisijungti."</string>
     <string name="improved_account_security_title" msgid="1069841917893513424">"Geresnė paskyros sauga"</string>
     <string name="improved_account_security_detail" msgid="9123750251551844860">"Kiekvienas raktas išskirtinai susietas su programa ar svetaine, kuriai buvo sukurtas, todėl niekada per klaidą neprisijungsite prie apgavikiškos programos ar svetainės. Be to, viešieji raktai laikomi tik serveriuose, todėl įsilaužti tampa gerokai sudėtingiau."</string>
     <string name="seamless_transition_title" msgid="5335622196351371961">"Sklandus perėjimas"</string>
-    <string name="seamless_transition_detail" msgid="4475509237171739843">"Kol stengiamės padaryti, kad ateityje nereikėtų naudoti slaptažodžių, jie vis dar bus pasiekiami kartu su slaptaisiais raktais."</string>
+    <string name="seamless_transition_detail" msgid="4475509237171739843">"Kol stengiamės padaryti, kad ateityje nereikėtų naudoti slaptažodžių, jie vis dar bus pasiekiami kartu su prieigos raktais."</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"Pasirinkite, kur išsaugoti „<xliff:g id="CREATETYPES">%1$s</xliff:g>“"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"Pasirinkite slaptažodžių tvarkyklę, kurią naudodami galėsite išsaugoti informaciją ir kitą kartą prisijungti greičiau"</string>
     <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"Sukurti „passkey“, skirtą „<xliff:g id="APPNAME">%1$s</xliff:g>“?"</string>
@@ -28,40 +44,52 @@
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Išsaugoti prisijungimo prie „<xliff:g id="APPNAME">%1$s</xliff:g>“ informaciją?"</string>
     <string name="passkey" msgid="632353688396759522">"„passkey“"</string>
     <string name="password" msgid="6738570945182936667">"slaptažodis"</string>
-    <string name="passkeys" msgid="5733880786866559847">"passkey"</string>
+    <string name="passkeys" msgid="5733880786866559847">"prieigos raktas"</string>
     <string name="passwords" msgid="5419394230391253816">"slaptažodžiai"</string>
     <string name="sign_ins" msgid="4710739369149469208">"prisijungimo informacija"</string>
     <string name="sign_in_info" msgid="2627704710674232328">"prisijungimo informaciją"</string>
     <string name="save_credential_to_title" msgid="3172811692275634301">"Išsaugoti <xliff:g id="CREDENTIALTYPES">%1$s</xliff:g>"</string>
-    <string name="create_passkey_in_other_device_title" msgid="9195411122362461390">"Kurti „passkey“ kitame įrenginyje?"</string>
+    <string name="create_passkey_in_other_device_title" msgid="2360053098931886245">"Sukurti „passkey“ kitame įrenginyje?"</string>
+    <string name="save_password_on_other_device_title" msgid="5829084591948321207">"Išsaugoti slaptažodį kitame įrenginyje?"</string>
+    <string name="save_sign_in_on_other_device_title" msgid="2827990118560134692">"Išsaugoti prisijungimo duomenis kitame įrenginyje?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"Naudoti <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> visada prisijungiant?"</string>
-    <string name="use_provider_for_all_description" msgid="8466427781848268490">"Šioje slaptažodžių tvarkyklėje bus saugomi jūsų slaptažodžiai ir „passkey“, kad galėtumėte lengvai prisijungti"</string>
+    <string name="use_provider_for_all_description" msgid="1998772715863958997">"Šioje <xliff:g id="USERNAME">%1$s</xliff:g> Slaptažodžių tvarkyklėje bus saugomi jūsų slaptažodžiai ir prieigos raktai, kad galėtumėte lengvai prisijungti"</string>
     <string name="set_as_default" msgid="4415328591568654603">"Nustatyti kaip numatytąjį"</string>
+    <string name="settings" msgid="6536394145760913145">"Nustatymai"</string>
     <string name="use_once" msgid="9027366575315399714">"Naudoti vieną kartą"</string>
-    <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"Slaptažodžių: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> • „Passkey“: <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>"</string>
+    <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"Slaptažodžių: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> • Prieigos raktų: <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"slaptažodžių: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>"</string>
-    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"„passkey“: <xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g>"</string>
+    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"Prieigos raktai: <xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g>"</string>
     <string name="more_options_usage_credentials" msgid="1785697001787193984">"Prisijungimo duomenų: <xliff:g id="TOTALCREDENTIALSNUMBER">%1$s</xliff:g>"</string>
     <string name="passkey_before_subtitle" msgid="2448119456208647444">"Slaptažodis"</string>
     <string name="another_device" msgid="5147276802037801217">"Kitas įrenginys"</string>
     <string name="other_password_manager" msgid="565790221427004141">"Kitos slaptažodžių tvarkyklės"</string>
     <string name="close_sheet" msgid="1393792015338908262">"Uždaryti lapą"</string>
     <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Grįžti į ankstesnį puslapį"</string>
-    <string name="accessibility_close_button" msgid="2953807735590034688">"Uždaryti prisijungimo duomenų tvarkytuvės veiksmo pasiūlymą, rodomą ekrano apačioje"</string>
+    <string name="accessibility_close_button" msgid="1163435587545377687">"Uždaryti"</string>
+    <string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"Atsisakyti"</string>
     <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Naudoti išsaugotą „passkey“ programai „<xliff:g id="APP_NAME">%1$s</xliff:g>“?"</string>
-    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Naudoti išsaugotą prisijungimo informaciją programai „<xliff:g id="APP_NAME">%1$s</xliff:g>“?"</string>
-    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Pasirinkite išsaugotą prisijungimo informaciją programai „<xliff:g id="APP_NAME">%1$s</xliff:g>“"</string>
+    <string name="get_dialog_title_use_password_for" msgid="625828023234318484">"Naudoti išsaugotą slaptažodį programai „<xliff:g id="APP_NAME">%1$s</xliff:g>“?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"Naudoti prisijungimo informaciją programai „<xliff:g id="APP_NAME">%1$s</xliff:g>“?"</string>
+    <string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"Atrakinti prisijungimo prie „<xliff:g id="APP_NAME">%1$s</xliff:g>“ parinktis?"</string>
+    <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"Išsaugoto prieigos rakto, skirto „<xliff:g id="APP_NAME">%1$s</xliff:g>“, pasirinkimas"</string>
+    <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"Išsaugoto slaptažodžio, skirto „<xliff:g id="APP_NAME">%1$s</xliff:g>“, pasirinkimas"</string>
+    <string name="get_dialog_title_choose_saved_sign_in_for" msgid="2420298653461652728">"Pasirinkite išsaugotą prisijungimo informaciją programai „<xliff:g id="APP_NAME">%1$s</xliff:g>“"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="3048870756117876514">"Pasirinkite prisijungimo informaciją programai „<xliff:g id="APP_NAME">%1$s</xliff:g>“"</string>
+    <string name="get_dialog_title_choose_option_for" msgid="4976380044745029107">"Pasirinkti parinktį programai „<xliff:g id="APP_NAME">%1$s</xliff:g>“?"</string>
+    <string name="get_dialog_title_use_info_on" msgid="8863708099535435146">"Naudoti šią informaciją programoje „<xliff:g id="APP_NAME">%1$s</xliff:g>“?"</string>
     <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Prisijungti kitu būdu"</string>
     <string name="snackbar_action" msgid="37373514216505085">"Peržiūrėti parinktis"</string>
     <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Tęsti"</string>
     <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Prisijungimo parinktys"</string>
+    <string name="button_label_view_more" msgid="3429098227286495651">"Peržiūrėti daugiau"</string>
     <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Skirta <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Užrakintos slaptažodžių tvarkyklės"</string>
-    <!-- no translation found for locked_credential_entry_label_subtext_tap_to_unlock (6390367581393605009) -->
-    <skip />
-    <!-- no translation found for locked_credential_entry_label_subtext_no_sign_in (8131725029983174901) -->
-    <skip />
+    <string name="locked_credential_entry_label_subtext_tap_to_unlock" msgid="6390367581393605009">"Palieskite, kad atrakintumėte"</string>
+    <string name="locked_credential_entry_label_subtext_no_sign_in" msgid="8131725029983174901">"Prisijungimo informacijos nėra"</string>
+    <string name="no_sign_in_info_in" msgid="2641118151920288356">"Nėra prisijungimo informacijos <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Tvarkyti prisijungimo informaciją"</string>
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Naudojant kitą įrenginį"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Naudoti kitą įrenginį"</string>
+    <string name="request_cancelled_by" msgid="3735222326886267820">"Užklausą atšaukė „<xliff:g id="APP_NAME">%1$s</xliff:g>“"</string>
 </resources>
diff --git a/packages/CredentialManager/res/values-lv/strings.xml b/packages/CredentialManager/res/values-lv/strings.xml
index 899dc3d9..27115ca 100644
--- a/packages/CredentialManager/res/values-lv/strings.xml
+++ b/packages/CredentialManager/res/values-lv/strings.xml
@@ -1,15 +1,29 @@
 <?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.
+   -->
+
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="4539824758261855508">"Akreditācijas datu pārvaldnieks"</string>
     <string name="string_cancel" msgid="6369133483981306063">"Atcelt"</string>
     <string name="string_continue" msgid="1346732695941131882">"Turpināt"</string>
-    <string name="string_more_options" msgid="7990658711962795124">"Citas opcijas"</string>
+    <string name="string_more_options" msgid="2763852250269945472">"Saglabāt citā veidā"</string>
     <string name="string_learn_more" msgid="4541600451688392447">"Uzzināt vairāk"</string>
-    <!-- no translation found for content_description_show_password (3283502010388521607) -->
-    <skip />
-    <!-- no translation found for content_description_hide_password (6841375971631767996) -->
-    <skip />
+    <string name="content_description_show_password" msgid="3283502010388521607">"Rādīt paroli"</string>
+    <string name="content_description_hide_password" msgid="6841375971631767996">"Slēpt paroli"</string>
     <string name="passkey_creation_intro_title" msgid="4251037543787718844">"Lielāka drošība ar piekļuves atslēgām"</string>
     <string name="passkey_creation_intro_body_password" msgid="8825872426579958200">"Izmantojot piekļuves atslēgas, nav jāveido vai jāatceras sarežģītas paroles."</string>
     <string name="passkey_creation_intro_body_fingerprint" msgid="7331338631826254055">"Piekļuves atslēgas ir šifrētas digitālas atslēgas, ko varat izveidot, izmantojot pirksta nospiedumu, seju vai ekrāna bloķēšanas informāciju."</string>
@@ -35,10 +49,13 @@
     <string name="sign_ins" msgid="4710739369149469208">"pierakstīšanās informācija"</string>
     <string name="sign_in_info" msgid="2627704710674232328">"pierakstīšanās informācija"</string>
     <string name="save_credential_to_title" msgid="3172811692275634301">"Kur jāsaglabā <xliff:g id="CREDENTIALTYPES">%1$s</xliff:g>"</string>
-    <string name="create_passkey_in_other_device_title" msgid="9195411122362461390">"Vai izveidot piekļuves atslēgu citā ierīcē?"</string>
+    <string name="create_passkey_in_other_device_title" msgid="2360053098931886245">"Vai izveidot piekļuves atslēgu citā ierīcē?"</string>
+    <string name="save_password_on_other_device_title" msgid="5829084591948321207">"Vai saglabāt paroli citā ierīcē?"</string>
+    <string name="save_sign_in_on_other_device_title" msgid="2827990118560134692">"Vai saglabāt pierakstīšanās datus citā ierīcē?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"Vai vienmēr izmantot <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>, lai pierakstītos?"</string>
-    <string name="use_provider_for_all_description" msgid="8466427781848268490">"Šis paroļu pārvaldnieks glabās jūsu paroles un piekļuves atslēgas, lai atvieglotu pierakstīšanos."</string>
+    <string name="use_provider_for_all_description" msgid="1998772715863958997">"Šis paroļu pārvaldnieks glabās konta <xliff:g id="USERNAME">%1$s</xliff:g> paroles un piekļuves atslēgas, lai atvieglotu pierakstīšanos."</string>
     <string name="set_as_default" msgid="4415328591568654603">"Iestatīt kā noklusējumu"</string>
+    <string name="settings" msgid="6536394145760913145">"Iestatījumi"</string>
     <string name="use_once" msgid="9027366575315399714">"Izmantot vienreiz"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"Paroļu skaits: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> • Piekļuves atslēgu skaits: <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> paroles"</string>
@@ -49,21 +66,30 @@
     <string name="other_password_manager" msgid="565790221427004141">"Citi paroļu pārvaldnieki"</string>
     <string name="close_sheet" msgid="1393792015338908262">"Aizvērt lapu"</string>
     <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Atgriezties iepriekšējā lapā"</string>
-    <string name="accessibility_close_button" msgid="2953807735590034688">"Aizvērt Akreditācijas datu pārvaldnieka darbības ieteikumu, kas tiek rādīts ekrāna apakšdaļā"</string>
+    <string name="accessibility_close_button" msgid="1163435587545377687">"Aizvērt"</string>
+    <string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"Nerādīt"</string>
     <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Vai izmantot saglabāto piekļuves atslēgu lietotnei <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
-    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Vai izmantot saglabāto pierakstīšanās informāciju lietotnei <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
-    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Saglabātas pierakstīšanās informācijas izvēle lietotnei <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_use_password_for" msgid="625828023234318484">"Vai izmantot jūsu saglabāto paroli lietotnei <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"Vai izmantot jūsu pierakstīšanās informāciju lietotnei <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"Vai vēlaties atbloķēt lietotnes <xliff:g id="APP_NAME">%1$s</xliff:g> pierakstīšanās opcijas?"</string>
+    <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"Saglabātas piekļuves atslēgas izvēle lietotnei <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"Saglabātas paroles izvēle lietotnei <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_saved_sign_in_for" msgid="2420298653461652728">"Saglabātas pierakstīšanās informācijas izvēle lietotnei <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="3048870756117876514">"Pierakstīšanās informācijas izvēle lietotnei <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_option_for" msgid="4976380044745029107">"Vai izvēlēties opciju lietotnei <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_info_on" msgid="8863708099535435146">"Vai izmantot šo informāciju lietotnē <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Pierakstīties citā veidā"</string>
     <string name="snackbar_action" msgid="37373514216505085">"Skatīt opcijas"</string>
     <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Turpināt"</string>
     <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Pierakstīšanās opcijas"</string>
+    <string name="button_label_view_more" msgid="3429098227286495651">"Skatīt vairāk"</string>
     <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Lietotājam <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Paroļu pārvaldnieki, kuros nepieciešams autentificēties"</string>
-    <!-- no translation found for locked_credential_entry_label_subtext_tap_to_unlock (6390367581393605009) -->
-    <skip />
-    <!-- no translation found for locked_credential_entry_label_subtext_no_sign_in (8131725029983174901) -->
-    <skip />
+    <string name="locked_credential_entry_label_subtext_tap_to_unlock" msgid="6390367581393605009">"Pieskarieties, lai atbloķētu"</string>
+    <string name="locked_credential_entry_label_subtext_no_sign_in" msgid="8131725029983174901">"Nav pierakstīšanās informācijas"</string>
+    <string name="no_sign_in_info_in" msgid="2641118151920288356">"Kontā <xliff:g id="SOURCE">%1$s</xliff:g> nav pierakstīšanās informācijas."</string>
     <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Pierakstīšanās informācijas pārvaldība"</string>
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"No citas ierīces"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Izmantot citu ierīci"</string>
+    <string name="request_cancelled_by" msgid="3735222326886267820">"<xliff:g id="APP_NAME">%1$s</xliff:g> atcēla pieprasījumu"</string>
 </resources>
diff --git a/packages/CredentialManager/res/values-mk/strings.xml b/packages/CredentialManager/res/values-mk/strings.xml
index ba7f029..0755c9c 100644
--- a/packages/CredentialManager/res/values-mk/strings.xml
+++ b/packages/CredentialManager/res/values-mk/strings.xml
@@ -1,30 +1,44 @@
 <?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.
+   -->
+
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="4539824758261855508">"Управник на акредитиви"</string>
     <string name="string_cancel" msgid="6369133483981306063">"Откажи"</string>
     <string name="string_continue" msgid="1346732695941131882">"Продолжи"</string>
-    <string name="string_more_options" msgid="7990658711962795124">"Повеќе опции"</string>
+    <string name="string_more_options" msgid="2763852250269945472">"Зачувајте поинаку"</string>
     <string name="string_learn_more" msgid="4541600451688392447">"Дознајте повеќе"</string>
-    <!-- no translation found for content_description_show_password (3283502010388521607) -->
-    <skip />
-    <!-- no translation found for content_description_hide_password (6841375971631767996) -->
-    <skip />
+    <string name="content_description_show_password" msgid="3283502010388521607">"Прикажи ја лозинката"</string>
+    <string name="content_description_hide_password" msgid="6841375971631767996">"Скриј ја лозинката"</string>
     <string name="passkey_creation_intro_title" msgid="4251037543787718844">"Побезбедно со криптографски клучеви"</string>
     <string name="passkey_creation_intro_body_password" msgid="8825872426579958200">"Со криптографските клучеви нема потреба да создавате или да помните сложени лозинки"</string>
     <string name="passkey_creation_intro_body_fingerprint" msgid="7331338631826254055">"Криптографските клучеви се шифрирани дигитални клучеви што ги создавате со вашиот отпечаток, лик или заклучување екран"</string>
     <string name="passkey_creation_intro_body_device" msgid="1203796455762131631">"Се зачувуваат во управник со лозинки за да може да се најавувате на други уреди"</string>
     <string name="more_about_passkeys_title" msgid="7797903098728837795">"Повеќе за криптографските клучеви"</string>
     <string name="passwordless_technology_title" msgid="2497513482056606668">"Технологија без лозинки"</string>
-    <string name="passwordless_technology_detail" msgid="6853928846532955882">"Криптографските клучеви дозволуваат да се најавувате без да се потпирате на лозинки. Треба само да користите отпечаток, препознавање лик, PIN или шема на повлекување за да го потврдите идентитетот и да создадете криптографски клуч."</string>
+    <string name="passwordless_technology_detail" msgid="6853928846532955882">"Криптографските клучеви дозволуваат да се најавувате без да зависите од лозинки. Треба само да користите отпечаток, препознавање лик, PIN или шема на повлекување за да го потврдите идентитетот и да создадете криптографски клуч."</string>
     <string name="public_key_cryptography_title" msgid="6751970819265298039">"Криптографија за јавни клучеви"</string>
-    <string name="public_key_cryptography_detail" msgid="6937631710280562213">"Според FIDO Alliance (во која членуваат Google, Apple, Microsoft и др.) и W3C-стандардите, криптографските клучеви користат криптографски парови на клучеви. За разлика од корисничките имиња и знаците што ги користиме за лозинки, се создава приватно-јавен пар клучеви за апликација или сајт. Приватниот клуч безбедно се чува на уредот или управникот со лозинки и го потврдува вашиот идентитет. Јавниот клуч се споделува со серверот на апликацијата или сајтот. Со соодветните клучеви, може инстантно да се регистрирате и најавувате."</string>
+    <string name="public_key_cryptography_detail" msgid="6937631710280562213">"Според FIDO Alliance (во која членуваат Google, Apple, Microsoft и др.) и W3C-стандардите, криптографските клучеви користат парови криптографски клучеви. За разлика од корисничките имиња и знаците што ги користиме за лозинки, се создава приватно-јавен пар клучеви за апликација или сајт. Приватниот клуч безбедно се чува на уредот или управникот со лозинки и го потврдува вашиот идентитет. Јавниот клуч се споделува со серверот на апликацијата или сајтот. Со соодветните клучеви, може инстантно да се регистрирате и најавувате."</string>
     <string name="improved_account_security_title" msgid="1069841917893513424">"Подобрена безбедност на сметката"</string>
     <string name="improved_account_security_detail" msgid="9123750251551844860">"Секој клуч е поврзан само со апликацијата или веб-сајтот за кој бил создаден за да не може никогаш по грешка да се најавите на измамничка апликација или веб-сајт. Плус, кога серверите ги чуваат само јавните клучеви, хакирањето е многу потешко."</string>
     <string name="seamless_transition_title" msgid="5335622196351371961">"Беспрекорна транзиција"</string>
     <string name="seamless_transition_detail" msgid="4475509237171739843">"Како што се движиме кон иднина без лозинки, лозинките сепак ќе бидат достапни покрај криптографските клучеви."</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"Изберете каде да ги зачувате вашите <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
-    <string name="choose_provider_body" msgid="4967074531845147434">"Изберете Password Manager за да ги зачувате вашите податоци и да се најавите побрзо следниот пат"</string>
+    <string name="choose_provider_body" msgid="4967074531845147434">"Изберете управник со лозинки за да ги зачувате вашите податоци и да се најавите побрзо следниот пат"</string>
     <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"Да се создаде криптографски клуч за <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_password_title" msgid="7097275038523578687">"Дали да се зачува лозинката за <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Да се зачуваат податоците за најавување за <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
@@ -35,35 +49,47 @@
     <string name="sign_ins" msgid="4710739369149469208">"најавувања"</string>
     <string name="sign_in_info" msgid="2627704710674232328">"податоци за најавување"</string>
     <string name="save_credential_to_title" msgid="3172811692275634301">"Зачувајте <xliff:g id="CREDENTIALTYPES">%1$s</xliff:g> во"</string>
-    <string name="create_passkey_in_other_device_title" msgid="9195411122362461390">"Да се создаде криптографски клуч во друг уред?"</string>
+    <string name="create_passkey_in_other_device_title" msgid="2360053098931886245">"Да се создаде криптографски клуч на друг уред?"</string>
+    <string name="save_password_on_other_device_title" msgid="5829084591948321207">"Да се зачува лозинката на друг уред?"</string>
+    <string name="save_sign_in_on_other_device_title" msgid="2827990118560134692">"Да се зачува најавувањето на друг уред?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"Да се користи <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> за сите ваши најавувања?"</string>
-    <string name="use_provider_for_all_description" msgid="8466427781848268490">"Овој Password Manager ќе ги складира вашите лозинки и криптографски клучеви за да ви помогне лесно да се најавите"</string>
+    <string name="use_provider_for_all_description" msgid="1998772715863958997">"Овој управник со лозинки за <xliff:g id="USERNAME">%1$s</xliff:g> ќе ги складира вашите лозинки и криптографски клучеви за да ви помогне лесно да се најавите"</string>
     <string name="set_as_default" msgid="4415328591568654603">"Постави како стандардна опција"</string>
+    <string name="settings" msgid="6536394145760913145">"Поставки"</string>
     <string name="use_once" msgid="9027366575315399714">"Употребете еднаш"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"Лозинки: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> • Криптографски клучеви: <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> лозинки"</string>
-    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> криптографски клучеви"</string>
+    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"Криптографски клучеви: <xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g>"</string>
     <string name="more_options_usage_credentials" msgid="1785697001787193984">"Акредитиви: <xliff:g id="TOTALCREDENTIALSNUMBER">%1$s</xliff:g>"</string>
     <string name="passkey_before_subtitle" msgid="2448119456208647444">"Криптографски клуч"</string>
     <string name="another_device" msgid="5147276802037801217">"Друг уред"</string>
     <string name="other_password_manager" msgid="565790221427004141">"Други управници со лозинки"</string>
     <string name="close_sheet" msgid="1393792015338908262">"Затворете го листот"</string>
     <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Врати се на претходната страница"</string>
-    <string name="accessibility_close_button" msgid="2953807735590034688">"Затворете го предложеното дејство за „Управникот со акредитиви“ што се појавува најдолу на екранот"</string>
+    <string name="accessibility_close_button" msgid="1163435587545377687">"Затвори"</string>
+    <string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"Отфрли"</string>
     <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Да се користи вашиот зачуван криптографски клуч за <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
-    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Да се користи вашето зачувано најавување за <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
-    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Изберете зачувано најавување за <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_use_password_for" msgid="625828023234318484">"Да се користат зачуваните лозинки за <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"Да се користи вашето најавување за <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"Да се отклучат опциите за најавување за <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"Изберете зачуван криптографски клуч за <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"Изберете зачувана лозинка за <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_saved_sign_in_for" msgid="2420298653461652728">"Изберете зачувано најавување за <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="3048870756117876514">"Изберете најавување за <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_option_for" msgid="4976380044745029107">"Избери опција за <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_info_on" msgid="8863708099535435146">"Да се користат овие информации на <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Најавете се на друг начин"</string>
     <string name="snackbar_action" msgid="37373514216505085">"Прикажи ги опциите"</string>
     <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Продолжи"</string>
     <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Опции за најавување"</string>
+    <string name="button_label_view_more" msgid="3429098227286495651">"Прегледајте повеќе"</string>
     <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"За <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Заклучени управници со лозинки"</string>
-    <!-- no translation found for locked_credential_entry_label_subtext_tap_to_unlock (6390367581393605009) -->
-    <skip />
-    <!-- no translation found for locked_credential_entry_label_subtext_no_sign_in (8131725029983174901) -->
-    <skip />
+    <string name="locked_credential_entry_label_subtext_tap_to_unlock" msgid="6390367581393605009">"Допрете за да отклучите"</string>
+    <string name="locked_credential_entry_label_subtext_no_sign_in" msgid="8131725029983174901">"Нема податоци за најавување"</string>
+    <string name="no_sign_in_info_in" msgid="2641118151920288356">"Нема податоци за најавување во <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Управувајте со најавувањата"</string>
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Од друг уред"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Употребете друг уред"</string>
+    <string name="request_cancelled_by" msgid="3735222326886267820">"Барањето е откажано од <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
 </resources>
diff --git a/packages/CredentialManager/res/values-ml/strings.xml b/packages/CredentialManager/res/values-ml/strings.xml
index 4c18ba0..07fea38 100644
--- a/packages/CredentialManager/res/values-ml/strings.xml
+++ b/packages/CredentialManager/res/values-ml/strings.xml
@@ -1,10 +1,26 @@
 <?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="4539824758261855508">"ക്രെഡൻഷ്യൽ മാനേജർ"</string>
     <string name="string_cancel" msgid="6369133483981306063">"റദ്ദാക്കുക"</string>
     <string name="string_continue" msgid="1346732695941131882">"തുടരുക"</string>
-    <string name="string_more_options" msgid="7990658711962795124">"കൂടുതൽ ഓപ്‌ഷനുകൾ"</string>
+    <string name="string_more_options" msgid="2763852250269945472">"മറ്റ് രീതിയിൽ സംരക്ഷിക്കൂ"</string>
     <string name="string_learn_more" msgid="4541600451688392447">"കൂടുതലറിയുക"</string>
     <string name="content_description_show_password" msgid="3283502010388521607">"പാസ്‌വേഡ് കാണിക്കുക"</string>
     <string name="content_description_hide_password" msgid="6841375971631767996">"പാസ്‌വേഡ് മറയ്ക്കുക"</string>
@@ -33,10 +49,13 @@
     <string name="sign_ins" msgid="4710739369149469208">"സൈൻ ഇന്നുകൾ"</string>
     <string name="sign_in_info" msgid="2627704710674232328">"സൈൻ ഇൻ വിവരങ്ങൾ"</string>
     <string name="save_credential_to_title" msgid="3172811692275634301">"<xliff:g id="CREDENTIALTYPES">%1$s</xliff:g> ഇനിപ്പറയുന്നതിലേക്ക് സംരക്ഷിക്കുക"</string>
-    <string name="create_passkey_in_other_device_title" msgid="9195411122362461390">"മറ്റൊരു ഉപകരണത്തിൽ പാസ്‌കീ സൃഷ്ടിക്കണോ?"</string>
+    <string name="create_passkey_in_other_device_title" msgid="2360053098931886245">"മറ്റൊരു ഉപകരണത്തിൽ പാസ്‌കീ സൃഷ്ടിക്കണോ?"</string>
+    <string name="save_password_on_other_device_title" msgid="5829084591948321207">"മറ്റൊരു ഉപകരണത്തിൽ പാസ്‌വേഡ് സംരക്ഷിക്കണോ?"</string>
+    <string name="save_sign_in_on_other_device_title" msgid="2827990118560134692">"മറ്റൊരു ഉപകരണത്തിൽ സൈൻ ഇൻ ക്രെഡൻഷ്യൽ സംരക്ഷിക്കണോ?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"നിങ്ങളുടെ എല്ലാ സൈൻ ഇന്നുകൾക്കും <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ഉപയോഗിക്കണോ?"</string>
-    <string name="use_provider_for_all_description" msgid="8466427781848268490">"എളുപ്പത്തിൽ സൈൻ ഇൻ ചെയ്യാൻ സഹായിക്കുന്നതിന് ഈ Password Manager നിങ്ങളുടെ പാസ്‌വേഡുകളും പാസ്‌കീകളും സംഭരിക്കും"</string>
+    <string name="use_provider_for_all_description" msgid="1998772715863958997">"എളുപ്പത്തിൽ സൈൻ ഇൻ ചെയ്യാൻ സഹായിക്കുന്നതിന്, <xliff:g id="USERNAME">%1$s</xliff:g> എന്ന വിലാസത്തിന്റെ ഈ Password Manager നിങ്ങളുടെ പാസ്‌വേഡുകളും പാസ്‌കീകളും സംഭരിക്കും"</string>
     <string name="set_as_default" msgid="4415328591568654603">"ഡിഫോൾട്ടായി സജ്ജീകരിക്കുക"</string>
+    <string name="settings" msgid="6536394145760913145">"ക്രമീകരണം"</string>
     <string name="use_once" msgid="9027366575315399714">"ഒരു തവണ ഉപയോഗിക്കുക"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> പാസ്‌വേഡുകൾ • <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> പാസ്‌കീകൾ"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> പാസ്‌വേഡുകൾ"</string>
@@ -47,21 +66,30 @@
     <string name="other_password_manager" msgid="565790221427004141">"മറ്റ് പാസ്‌വേഡ് മാനേജർമാർ"</string>
     <string name="close_sheet" msgid="1393792015338908262">"ഷീറ്റ് അടയ്ക്കുക"</string>
     <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"മുമ്പത്തെ പേജിലേക്ക് മടങ്ങുക"</string>
-    <string name="accessibility_close_button" msgid="2953807735590034688">"സ്ക്രീന്റെ ചുവടെ ദൃശ്യമാകുന്ന ക്രെഡൻഷ്യൽ മാനേജർ പ്രവർത്തന നിർദ്ദേശം അടയ്ക്കുക"</string>
+    <string name="accessibility_close_button" msgid="1163435587545377687">"അടയ്ക്കുക"</string>
+    <string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"ഡിസ്‌മിസ് ചെയ്യുക"</string>
     <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"<xliff:g id="APP_NAME">%1$s</xliff:g> എന്നതിനായി നിങ്ങൾ സംരക്ഷിച്ച പാസ്‌കീ ഉപയോഗിക്കണോ?"</string>
-    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"<xliff:g id="APP_NAME">%1$s</xliff:g> എന്നതിനായി നിങ്ങൾ സംരക്ഷിച്ച സൈൻ ഇൻ ഉപയോഗിക്കണോ?"</string>
-    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"<xliff:g id="APP_NAME">%1$s</xliff:g> എന്നതിനായി ഒരു സംരക്ഷിച്ച സൈൻ ഇൻ തിരഞ്ഞെടുക്കുക"</string>
+    <string name="get_dialog_title_use_password_for" msgid="625828023234318484">"<xliff:g id="APP_NAME">%1$s</xliff:g> എന്നതിനായി നിങ്ങളുടെ സംരക്ഷിച്ച പാസ്‌വേഡ് ഉപയോഗിക്കണോ?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"<xliff:g id="APP_NAME">%1$s</xliff:g> ആപ്പിനുള്ള നിങ്ങളുടെ സൈൻ ഇൻ ക്രെഡൻഷ്യലുകൾ ഉപയോഗിക്കണോ?"</string>
+    <string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"<xliff:g id="APP_NAME">%1$s</xliff:g> എന്നതിനായി സൈൻ ഇൻ ഓപ്‌ഷനുകൾ അൺലോക്ക് ചെയ്യണോ?"</string>
+    <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"<xliff:g id="APP_NAME">%1$s</xliff:g> എന്നതിനായി ഒരു സംരക്ഷിച്ച പാസ്‌കീ തിരഞ്ഞെടുക്കുക"</string>
+    <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"<xliff:g id="APP_NAME">%1$s</xliff:g> എന്നതിനായി ഒരു സംരക്ഷിച്ച പാസ്‌വേഡ് തിരഞ്ഞെടുക്കുക"</string>
+    <string name="get_dialog_title_choose_saved_sign_in_for" msgid="2420298653461652728">"<xliff:g id="APP_NAME">%1$s</xliff:g> എന്നതിനായി ഒരു സംരക്ഷിച്ച സൈൻ ഇൻ തിരഞ്ഞെടുക്കുക"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="3048870756117876514">"<xliff:g id="APP_NAME">%1$s</xliff:g> ആപ്പിനുള്ള സൈൻ ഇൻ ക്രെഡൻഷ്യലുകൾ തിരഞ്ഞെടുക്കുക"</string>
+    <string name="get_dialog_title_choose_option_for" msgid="4976380044745029107">"<xliff:g id="APP_NAME">%1$s</xliff:g> എന്ന ആപ്പിനായി ഒരു ഓപ്‌ഷൻ തിരഞ്ഞെടുക്കണോ?"</string>
+    <string name="get_dialog_title_use_info_on" msgid="8863708099535435146">"<xliff:g id="APP_NAME">%1$s</xliff:g> എന്നതിൽ ഈ വിവരങ്ങൾ ഉപയോഗിക്കണോ?"</string>
     <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"മറ്റൊരു രീതിയിൽ സൈൻ ഇൻ ചെയ്യുക"</string>
     <string name="snackbar_action" msgid="37373514216505085">"ഓപ്ഷനുകൾ കാണുക"</string>
     <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"തുടരുക"</string>
     <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"സൈൻ ഇൻ ഓപ്ഷനുകൾ"</string>
+    <string name="button_label_view_more" msgid="3429098227286495651">"കൂടുതൽ കാണുക"</string>
     <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"<xliff:g id="USERNAME">%1$s</xliff:g> എന്നയാൾക്ക്"</string>
     <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"ലോക്ക് ചെയ്‌ത പാസ്‌വേഡ് സൈൻ ഇൻ മാനേജർമാർ"</string>
-    <!-- no translation found for locked_credential_entry_label_subtext_tap_to_unlock (6390367581393605009) -->
-    <skip />
-    <!-- no translation found for locked_credential_entry_label_subtext_no_sign_in (8131725029983174901) -->
-    <skip />
+    <string name="locked_credential_entry_label_subtext_tap_to_unlock" msgid="6390367581393605009">"അൺലോക്ക് ചെയ്യാൻ ടാപ്പ് ചെയ്യുക"</string>
+    <string name="locked_credential_entry_label_subtext_no_sign_in" msgid="8131725029983174901">"സൈൻ ഇൻ ചെയ്യാനുള്ള വിവരങ്ങളൊന്നുമില്ല"</string>
+    <string name="no_sign_in_info_in" msgid="2641118151920288356">"<xliff:g id="SOURCE">%1$s</xliff:g> എന്നതിൽ സൈൻ ഇൻ വിവരങ്ങളില്ല"</string>
     <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"സൈൻ ഇന്നുകൾ മാനേജ് ചെയ്യുക"</string>
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"മറ്റൊരു ഉപകരണത്തിൽ നിന്ന്"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"മറ്റൊരു ഉപകരണം ഉപയോഗിക്കുക"</string>
+    <string name="request_cancelled_by" msgid="3735222326886267820">"അഭ്യർത്ഥന <xliff:g id="APP_NAME">%1$s</xliff:g> റദ്ദാക്കി"</string>
 </resources>
diff --git a/packages/CredentialManager/res/values-mn/strings.xml b/packages/CredentialManager/res/values-mn/strings.xml
index 8b91f3b..e37155a 100644
--- a/packages/CredentialManager/res/values-mn/strings.xml
+++ b/packages/CredentialManager/res/values-mn/strings.xml
@@ -1,28 +1,42 @@
 <?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.
+   -->
+
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="4539824758261855508">"Мандат үнэмлэхийн менежер"</string>
     <string name="string_cancel" msgid="6369133483981306063">"Цуцлах"</string>
     <string name="string_continue" msgid="1346732695941131882">"Үргэлжлүүлэх"</string>
-    <string name="string_more_options" msgid="7990658711962795124">"Бусад сонголт"</string>
+    <string name="string_more_options" msgid="2763852250269945472">"Өөр аргаар хадгалах"</string>
     <string name="string_learn_more" msgid="4541600451688392447">"Нэмэлт мэдээлэл авах"</string>
-    <!-- no translation found for content_description_show_password (3283502010388521607) -->
-    <skip />
-    <!-- no translation found for content_description_hide_password (6841375971631767996) -->
-    <skip />
-    <string name="passkey_creation_intro_title" msgid="4251037543787718844">"Passkey-тэй байхад илүү аюулгүй"</string>
-    <string name="passkey_creation_intro_body_password" msgid="8825872426579958200">"Passkey-н тусламжтай та нарийн төвөгтэй нууц үг үүсгэх эсвэл санах шаардлагагүй"</string>
-    <string name="passkey_creation_intro_body_fingerprint" msgid="7331338631826254055">"Passkey нь таны хурууны хээ, царай эсвэл дэлгэцийн түгжээгээ ашиглан үүсгэсэн шифрлэгдсэн дижитал түлхүүр юм"</string>
+    <string name="content_description_show_password" msgid="3283502010388521607">"Нууц үгийг харуулах"</string>
+    <string name="content_description_hide_password" msgid="6841375971631767996">"Нууц үгийг нуух"</string>
+    <string name="passkey_creation_intro_title" msgid="4251037543787718844">"Нэвтрэх түлхүүртэй байхад илүү аюулгүй"</string>
+    <string name="passkey_creation_intro_body_password" msgid="8825872426579958200">"Нэвтрэх түлхүүрийн тусламжтай та нарийн төвөгтэй нууц үг үүсгэх эсвэл санах шаардлагагүй"</string>
+    <string name="passkey_creation_intro_body_fingerprint" msgid="7331338631826254055">"Нэвтрэх түлхүүр нь таны хурууны хээ, царай эсвэл дэлгэцийн түгжээгээ ашиглан үүсгэсэн шифрлэгдсэн дижитал түлхүүр юм"</string>
     <string name="passkey_creation_intro_body_device" msgid="1203796455762131631">"Тэдгээрийг нууц үгний менежерт хадгалдаг бөгөөд ингэснээр та бусад төхөөрөмжид нэвтрэх боломжтой"</string>
-    <string name="more_about_passkeys_title" msgid="7797903098728837795">"Passkey-н талаарх дэлгэрэнгүй"</string>
+    <string name="more_about_passkeys_title" msgid="7797903098728837795">"Нэвтрэх түлхүүрийн талаарх дэлгэрэнгүй"</string>
     <string name="passwordless_technology_title" msgid="2497513482056606668">"Нууц үггүй технологи"</string>
-    <string name="passwordless_technology_detail" msgid="6853928846532955882">"Passkey нь танд нууц үгэнд найдалгүйгээр нэвтрэх боломжийг олгодог. Та таниулбараа баталгаажуулах болон passkey үүсгэхийн тулд ердөө хурууны хээ, царай танилт, ПИН эсвэл шудрах хээгээ ашиглах шаардлагатай."</string>
+    <string name="passwordless_technology_detail" msgid="6853928846532955882">"Нэвтрэх түлхүүр нь танд нууц үгэнд найдалгүйгээр нэвтрэх боломжийг олгодог. Та хувийн мэдээллээ баталгаажуулах болон нэвтрэх түлхүүр үүсгэхийн тулд ердөө хурууны хээ, царай танилт, ПИН эсвэл шудрах хээгээ ашиглах шаардлагатай."</string>
     <string name="public_key_cryptography_title" msgid="6751970819265298039">"Нийтийн түлхүүрийн криптограф"</string>
-    <string name="public_key_cryptography_detail" msgid="6937631710280562213">"FIDO Холбоодын (Google, Apple, Microsoft ба бусад багтдаг) W3C стандартад тулгуурлан passkey нь криптограф түлхүүрийн хослолыг ашигладаг. Хэрэглэгчийн нэр, бидний нууц үгэнд ашигладаг тэмдэгтийн мөрөөс ялгаатай хувийн-нийтийн түлхүүрийн хослолыг апп эсвэл вебсайтад үүсгэдэг. Хувийн түлхүүрийг таны төхөөрөмж эсвэл нууц үгний менежерт аюулгүй хадгалдаг бөгөөд үүнийг таны таниулбарыг баталгаажуулахад ашигладаг. Нийтийн түлхүүрийг апп эсвэл вебсайтын сервертэй хуваалцдаг. Харгалзах түлхүүрээр та даруй бүртгүүлэх, нэвтрэх боломжтой."</string>
+    <string name="public_key_cryptography_detail" msgid="6937631710280562213">"Нэвтрэх түлхүүр нь FIDO Холбоо (Google, Apple, Microsoft ба бусад багтдаг) болон W3C стандартад тулгуурлан криптограф түлхүүрийн хослолыг ашигладаг. Хэрэглэгчийн нэр, бидний нууц үгэнд ашигладаг тэмдэгтийн мөрөөс ялгаатай хувийн-нийтийн түлхүүрийн хослолыг апп эсвэл вебсайтад үүсгэдэг. Хувийн түлхүүрийг таны төхөөрөмж эсвэл нууц үгний менежерт аюулгүй хадгалдаг бөгөөд үүнийг таны хувийн мэдээллийг баталгаажуулахад ашигладаг. Нийтийн түлхүүрийг апп эсвэл вебсайтын сервертэй хуваалцдаг. Харгалзах түлхүүрээр та даруй бүртгүүлэх, нэвтрэх боломжтой."</string>
     <string name="improved_account_security_title" msgid="1069841917893513424">"Бүртгэлийн сайжруулсан аюулгүй байдал"</string>
     <string name="improved_account_security_detail" msgid="9123750251551844860">"Түлхүүр тус бүрийг тэдгээрийг зориулж үүсгэсэн апп эсвэл вебсайттай нь тусгайлан холбодог бөгөөд ингэснээр та залилан мэхэлсэн апп эсвэл вебсайтад санамсаргүй байдлаар хэзээ ч нэвтрэхгүй. Түүнчлэн зөвхөн нийтийн түлхүүрийг хадгалж буй серверүүдийг хакердахад илүү хэцүү байдаг."</string>
     <string name="seamless_transition_title" msgid="5335622196351371961">"Саадгүй шилжилт"</string>
-    <string name="seamless_transition_detail" msgid="4475509237171739843">"Бид нууц үггүй ирээдүй рүү урагшлахын хэрээр нууц үг нь passkey-н хамтаар боломжтой хэвээр байх болно."</string>
+    <string name="seamless_transition_detail" msgid="4475509237171739843">"Бид нууц үггүй ирээдүй рүү урагшлахын хэрээр нууц үг нь нэвтрэх түлхүүрийн хамтаар боломжтой хэвээр байх болно."</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"<xliff:g id="CREATETYPES">%1$s</xliff:g>-г хаана хадгалахаа сонгоно уу"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"Мэдээллээ хадгалж, дараагийн удаа илүү хурдан нэвтрэхийн тулд нууц үгний менежерийг сонгоно уу"</string>
     <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"<xliff:g id="APPNAME">%1$s</xliff:g>-д passkey үүсгэх үү?"</string>
@@ -30,40 +44,52 @@
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"<xliff:g id="APPNAME">%1$s</xliff:g>-н нэвтрэх мэдээллийг хадгалах уу?"</string>
     <string name="passkey" msgid="632353688396759522">"passkey"</string>
     <string name="password" msgid="6738570945182936667">"нууц үг"</string>
-    <string name="passkeys" msgid="5733880786866559847">"passkeys"</string>
+    <string name="passkeys" msgid="5733880786866559847">"нэвтрэх түлхүүрүүд"</string>
     <string name="passwords" msgid="5419394230391253816">"нууц үг"</string>
     <string name="sign_ins" msgid="4710739369149469208">"нэвтрэлт"</string>
     <string name="sign_in_info" msgid="2627704710674232328">"нэвтрэх мэдээлэл"</string>
     <string name="save_credential_to_title" msgid="3172811692275634301">"<xliff:g id="CREDENTIALTYPES">%1$s</xliff:g>-г дараахад хадгалах"</string>
-    <string name="create_passkey_in_other_device_title" msgid="9195411122362461390">"Өөр төхөөрөмжид passkey үүсгэх үү?"</string>
+    <string name="create_passkey_in_other_device_title" msgid="2360053098931886245">"Өөр төхөөрөмжид нэвтрэх түлхүүр үүсгэх үү?"</string>
+    <string name="save_password_on_other_device_title" msgid="5829084591948321207">"Өөр төхөөрөмжид нууц үг хадгалах уу?"</string>
+    <string name="save_sign_in_on_other_device_title" msgid="2827990118560134692">"Өөр төхөөрөмжид нэвтрэлт хадгалах уу?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>-г бүх нэвтрэлтдээ ашиглах уу?"</string>
-    <string name="use_provider_for_all_description" msgid="8466427781848268490">"Хялбархан нэвтрэхэд туслахын тулд энэ нууц үгний менежер таны нууц үг болон passkeys-г хадгална"</string>
+    <string name="use_provider_for_all_description" msgid="1998772715863958997">"Танд хялбархан нэвтрэхэд туслахын тулд <xliff:g id="USERNAME">%1$s</xliff:g>-н энэ нууц үгний менежер таны нууц үг болон нэвтрэх түлхүүрийг хадгална"</string>
     <string name="set_as_default" msgid="4415328591568654603">"Өгөгдмөлөөр тохируулах"</string>
+    <string name="settings" msgid="6536394145760913145">"Тохиргоо"</string>
     <string name="use_once" msgid="9027366575315399714">"Нэг удаа ашиглах"</string>
-    <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> нууц үг • <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> passkey"</string>
+    <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> нууц үг • <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> нэвтрэх түлхүүр"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> нууц үг"</string>
-    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> passkey"</string>
+    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> нэвтрэх түлхүүр"</string>
     <string name="more_options_usage_credentials" msgid="1785697001787193984">"<xliff:g id="TOTALCREDENTIALSNUMBER">%1$s</xliff:g> мандат үнэмлэх"</string>
     <string name="passkey_before_subtitle" msgid="2448119456208647444">"Passkey"</string>
     <string name="another_device" msgid="5147276802037801217">"Өөр төхөөрөмж"</string>
     <string name="other_password_manager" msgid="565790221427004141">"Нууц үгний бусад менежер"</string>
     <string name="close_sheet" msgid="1393792015338908262">"Хүснэгтийг хаах"</string>
     <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Өмнөх хуудас руу буцах"</string>
-    <string name="accessibility_close_button" msgid="2953807735590034688">"Дэлгэцийн доод талд гарч ирэх Мандат үнэмлэхийн менежерийн үйлдлийн зөвлөмжийг хаана"</string>
+    <string name="accessibility_close_button" msgid="1163435587545377687">"Хаах"</string>
+    <string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"Хаах"</string>
     <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"<xliff:g id="APP_NAME">%1$s</xliff:g>-д өөрийн хадгалсан passkey-г ашиглах уу?"</string>
-    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"<xliff:g id="APP_NAME">%1$s</xliff:g>-д хадгалсан нэвтрэх мэдээллээ ашиглах уу?"</string>
-    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"<xliff:g id="APP_NAME">%1$s</xliff:g>-д зориулж хадгалсан нэвтрэх мэдээллийг сонгоно уу"</string>
+    <string name="get_dialog_title_use_password_for" msgid="625828023234318484">"<xliff:g id="APP_NAME">%1$s</xliff:g>-д хадгалсан нууц үгээ ашиглах уу?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"<xliff:g id="APP_NAME">%1$s</xliff:g>-н нэвтрэлтээ ашиглах уу?"</string>
+    <string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"<xliff:g id="APP_NAME">%1$s</xliff:g>-д нэвтрэлтийн сонголтын түгжээг тайлах уу?"</string>
+    <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"<xliff:g id="APP_NAME">%1$s</xliff:g>-д хадгалсан нэвтрэх түлхүүр сонгоно уу"</string>
+    <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"<xliff:g id="APP_NAME">%1$s</xliff:g>-д хадгалсан нууц үг сонгоно уу"</string>
+    <string name="get_dialog_title_choose_saved_sign_in_for" msgid="2420298653461652728">"<xliff:g id="APP_NAME">%1$s</xliff:g>-д хадгалсан нэвтрэх мэдээллийг сонгоно уу"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="3048870756117876514">"<xliff:g id="APP_NAME">%1$s</xliff:g>-н нэвтрэлтийг сонгоно уу"</string>
+    <string name="get_dialog_title_choose_option_for" msgid="4976380044745029107">"<xliff:g id="APP_NAME">%1$s</xliff:g>-д сонголт хийх үү?"</string>
+    <string name="get_dialog_title_use_info_on" msgid="8863708099535435146">"Энэ мэдээллийг <xliff:g id="APP_NAME">%1$s</xliff:g>-д ашиглах уу?"</string>
     <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Өөр аргаар нэвтрэх"</string>
     <string name="snackbar_action" msgid="37373514216505085">"Сонголт харах"</string>
     <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Үргэлжлүүлэх"</string>
     <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Нэвтрэх сонголт"</string>
+    <string name="button_label_view_more" msgid="3429098227286495651">"Дэлгэрэнгүй үзэх"</string>
     <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"<xliff:g id="USERNAME">%1$s</xliff:g>-д"</string>
     <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Түгжээтэй нууц үгний менежерүүд"</string>
-    <!-- no translation found for locked_credential_entry_label_subtext_tap_to_unlock (6390367581393605009) -->
-    <skip />
-    <!-- no translation found for locked_credential_entry_label_subtext_no_sign_in (8131725029983174901) -->
-    <skip />
+    <string name="locked_credential_entry_label_subtext_tap_to_unlock" msgid="6390367581393605009">"Түгжээг тайлахын тулд товшино уу"</string>
+    <string name="locked_credential_entry_label_subtext_no_sign_in" msgid="8131725029983174901">"Ямар ч нэвтрэх мэдээлэл байхгүй"</string>
+    <string name="no_sign_in_info_in" msgid="2641118151920288356">"<xliff:g id="SOURCE">%1$s</xliff:g>-д ямар ч нэвтрэх мэдээлэл алга"</string>
     <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Нэвтрэлтийг удирдах"</string>
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Өөр төхөөрөмжөөс"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Өөр төхөөрөмж ашиглах"</string>
+    <string name="request_cancelled_by" msgid="3735222326886267820">"Хүсэлтийг <xliff:g id="APP_NAME">%1$s</xliff:g> цуцалсан"</string>
 </resources>
diff --git a/packages/CredentialManager/res/values-mr/strings.xml b/packages/CredentialManager/res/values-mr/strings.xml
index 0ac9ce6..ceba101 100644
--- a/packages/CredentialManager/res/values-mr/strings.xml
+++ b/packages/CredentialManager/res/values-mr/strings.xml
@@ -1,15 +1,29 @@
 <?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.
+   -->
+
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="4539824758261855508">"क्रेडेंशियल व्यवस्थापक"</string>
     <string name="string_cancel" msgid="6369133483981306063">"रद्द करा"</string>
     <string name="string_continue" msgid="1346732695941131882">"पुढे सुरू ठेवा"</string>
-    <string name="string_more_options" msgid="7990658711962795124">"आणखी पर्याय"</string>
+    <string name="string_more_options" msgid="2763852250269945472">"दुसऱ्या पद्धतीने सेव्ह करा"</string>
     <string name="string_learn_more" msgid="4541600451688392447">"अधिक जाणून घ्या"</string>
-    <!-- no translation found for content_description_show_password (3283502010388521607) -->
-    <skip />
-    <!-- no translation found for content_description_hide_password (6841375971631767996) -->
-    <skip />
+    <string name="content_description_show_password" msgid="3283502010388521607">"पासवर्ड दाखवा"</string>
+    <string name="content_description_hide_password" msgid="6841375971631767996">"पासवर्ड लपवा"</string>
     <string name="passkey_creation_intro_title" msgid="4251037543787718844">"पासकीसह आणखी सुरक्षित"</string>
     <string name="passkey_creation_intro_body_password" msgid="8825872426579958200">"पासकीसोबत, तुम्हाला क्लिष्ट पासवर्ड तयार करण्याची किंवा लक्षात ठेवण्याची आवश्यकता नाही"</string>
     <string name="passkey_creation_intro_body_fingerprint" msgid="7331338631826254055">"पासकी या तुम्ही तुमचे फिंगरप्रिंट, फेस किंवा स्क्रीन लॉक वापरून तयार करता अशा एंक्रिप्ट केलेल्या डिजिटल की आहेत"</string>
@@ -35,10 +49,13 @@
     <string name="sign_ins" msgid="4710739369149469208">"साइन-इन"</string>
     <string name="sign_in_info" msgid="2627704710674232328">"साइन-इनसंबंधित माहिती"</string>
     <string name="save_credential_to_title" msgid="3172811692275634301">"<xliff:g id="CREDENTIALTYPES">%1$s</xliff:g> येथे सेव्ह करा"</string>
-    <string name="create_passkey_in_other_device_title" msgid="9195411122362461390">"दुसऱ्या डिव्हाइसमध्ये पासकी तयार करायची का?"</string>
+    <string name="create_passkey_in_other_device_title" msgid="2360053098931886245">"दुसऱ्या डिव्हाइसवर पासकी तयार करायची आहे का?"</string>
+    <string name="save_password_on_other_device_title" msgid="5829084591948321207">"दुसऱ्या डिव्हाइसवर पासवर्ड सेव्ह करायचा आहे का?"</string>
+    <string name="save_sign_in_on_other_device_title" msgid="2827990118560134692">"दुसऱ्या डिव्हाइसवर साइन-इन सेव्ह करायचे आहे का?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"तुमच्या सर्व साइन-इन साठी <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>वापरायचे का?"</string>
-    <string name="use_provider_for_all_description" msgid="8466427781848268490">"तुम्हाला सहजरीत्या साइन इन करण्यात मदत करण्यासाठी हा पासवर्ड व्यवस्थापक तुमचे पासवर्ड आणि पासकी स्टोअर करेल"</string>
+    <string name="use_provider_for_all_description" msgid="1998772715863958997">"तुम्हाला सहजरीत्या साइन इन करण्यात मदत करण्यासाठी हा <xliff:g id="USERNAME">%1$s</xliff:g> चा पासवर्ड व्यवस्थापक तुमचे पासवर्ड आणि पासकी स्टोअर करेल"</string>
     <string name="set_as_default" msgid="4415328591568654603">"डिफॉल्ट म्हणून सेट करा"</string>
+    <string name="settings" msgid="6536394145760913145">"सेटिंग्ज"</string>
     <string name="use_once" msgid="9027366575315399714">"एकदा वापरा"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> पासवर्ड • <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> पासकी"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> पासवर्ड"</string>
@@ -49,21 +66,30 @@
     <string name="other_password_manager" msgid="565790221427004141">"इतर पासवर्ड व्यवस्थापक"</string>
     <string name="close_sheet" msgid="1393792015338908262">"शीट बंद करा"</string>
     <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"मागील पेजवर परत जा"</string>
-    <string name="accessibility_close_button" msgid="2953807735590034688">"स्क्रीनच्या तळाशी दिसणाऱ्या क्रेडेंशियल व्यवस्थापक कृती सूचना बंद करा"</string>
+    <string name="accessibility_close_button" msgid="1163435587545377687">"बंद करा"</string>
+    <string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"डिसमिस करा"</string>
     <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"<xliff:g id="APP_NAME">%1$s</xliff:g> साठी तुमची सेव्ह केलेली पासकी वापरायची का?"</string>
-    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"<xliff:g id="APP_NAME">%1$s</xliff:g> साठी तुमचे सेव्ह केलेले साइन-इन वापरायचे का?"</string>
-    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"<xliff:g id="APP_NAME">%1$s</xliff:g> साठी सेव्ह केलेले साइन-इन निवडा"</string>
+    <string name="get_dialog_title_use_password_for" msgid="625828023234318484">"तुमचा सेव्ह केलेला पासवर्ड <xliff:g id="APP_NAME">%1$s</xliff:g> साठी वापरायचा आहे का?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"<xliff:g id="APP_NAME">%1$s</xliff:g> साठी तुमचे साइन-इन वापरायचे आहे का?"</string>
+    <string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"<xliff:g id="APP_NAME">%1$s</xliff:g> साठी साइन-इन पर्याय अनलॉक करायचे आहेत का?"</string>
+    <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"<xliff:g id="APP_NAME">%1$s</xliff:g> साठी सेव्ह केलेली पासकी निवडा"</string>
+    <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"<xliff:g id="APP_NAME">%1$s</xliff:g> साठी सेव्ह केलेला पासवर्ड निवडा"</string>
+    <string name="get_dialog_title_choose_saved_sign_in_for" msgid="2420298653461652728">"<xliff:g id="APP_NAME">%1$s</xliff:g> साठी सेव्ह केलेले साइन-इन निवडा"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="3048870756117876514">"<xliff:g id="APP_NAME">%1$s</xliff:g> साठी साइन-इन निवडा"</string>
+    <string name="get_dialog_title_choose_option_for" msgid="4976380044745029107">"<xliff:g id="APP_NAME">%1$s</xliff:g> साठी पर्याय निवडा?"</string>
+    <string name="get_dialog_title_use_info_on" msgid="8863708099535435146">"ही माहिती <xliff:g id="APP_NAME">%1$s</xliff:g> वर वापरायची का?"</string>
     <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"दुसऱ्या मार्गाने साइन इन करा"</string>
     <string name="snackbar_action" msgid="37373514216505085">"पर्याय पहा"</string>
     <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"पुढे सुरू ठेवा"</string>
     <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"साइन इन पर्याय"</string>
+    <string name="button_label_view_more" msgid="3429098227286495651">"आणखी पहा"</string>
     <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"<xliff:g id="USERNAME">%1$s</xliff:g> साठी"</string>
     <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"लॉक केलेले पासवर्ड व्यवस्थापक"</string>
-    <!-- no translation found for locked_credential_entry_label_subtext_tap_to_unlock (6390367581393605009) -->
-    <skip />
-    <!-- no translation found for locked_credential_entry_label_subtext_no_sign_in (8131725029983174901) -->
-    <skip />
+    <string name="locked_credential_entry_label_subtext_tap_to_unlock" msgid="6390367581393605009">"अनलॉक करण्यासाठी टॅप करा"</string>
+    <string name="locked_credential_entry_label_subtext_no_sign_in" msgid="8131725029983174901">"साइन-इन माहिती नाही"</string>
+    <string name="no_sign_in_info_in" msgid="2641118151920288356">"<xliff:g id="SOURCE">%1$s</xliff:g> मध्ये कोणतीही साइन-इन माहिती नाही"</string>
     <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"साइन-इन व्यवस्थापित करा"</string>
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"दुसऱ्या डिव्हाइस वरून"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"वेगळे डिव्हाइस वापरा"</string>
+    <string name="request_cancelled_by" msgid="3735222326886267820">"<xliff:g id="APP_NAME">%1$s</xliff:g> ने विनंती रद्द केली आहे"</string>
 </resources>
diff --git a/packages/CredentialManager/res/values-ms/strings.xml b/packages/CredentialManager/res/values-ms/strings.xml
index d762bed..b933f3e 100644
--- a/packages/CredentialManager/res/values-ms/strings.xml
+++ b/packages/CredentialManager/res/values-ms/strings.xml
@@ -1,10 +1,26 @@
 <?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="4539824758261855508">"Pengurus Bukti Kelayakan"</string>
     <string name="string_cancel" msgid="6369133483981306063">"Batal"</string>
     <string name="string_continue" msgid="1346732695941131882">"Teruskan"</string>
-    <string name="string_more_options" msgid="7990658711962795124">"Lagi pilihan"</string>
+    <string name="string_more_options" msgid="2763852250269945472">"Simpan cara lain"</string>
     <string name="string_learn_more" msgid="4541600451688392447">"Ketahui lebih lanjut"</string>
     <string name="content_description_show_password" msgid="3283502010388521607">"Tunjukkan kata laluan"</string>
     <string name="content_description_hide_password" msgid="6841375971631767996">"Sembunyikan kata laluan"</string>
@@ -16,11 +32,11 @@
     <string name="passwordless_technology_title" msgid="2497513482056606668">"Teknologi tanpa kata laluan"</string>
     <string name="passwordless_technology_detail" msgid="6853928846532955882">"Kunci laluan membolehkan anda log masuk tanpa bergantung pada kata laluan. Anda hanya perlu menggunakan cap jari anda, pengecaman wajah, PIN atau corak leret untuk mengesahkan identiti anda dan mencipta kunci laluan."</string>
     <string name="public_key_cryptography_title" msgid="6751970819265298039">"Kriptografi kunci awam"</string>
-    <string name="public_key_cryptography_detail" msgid="6937631710280562213">"Berdasarkan standard Perikatan FIDO (termasuk Google, Apple, Microsoft dll) &amp; W3C, kunci laluan menggunakan pasangan kunci kriptografi. Tidak seperti nama pengguna &amp; rentetan aksara yang digunakan untuk kata laluan, pasangan kunci peribadi-umum dicipta untuk apl/laman web. Kunci persendirian akan disimpan dengan selamat pada peranti atau pengurus kata laluan dan ia mengesahkan identiti anda. Kunci awam dikongsi dengan pelayan apl/laman web. Dengan kunci sepadan, anda boleh mendaftar dan log masuk dengan segera."</string>
+    <string name="public_key_cryptography_detail" msgid="6937631710280562213">"Berdasarkan standard Perikatan FIDO (termasuk Google, Apple, Microsoft dll) &amp; W3C, kunci laluan menggunakan pasangan kunci kriptografi. Tidak seperti nama pengguna &amp; rentetan aksara yang digunakan untuk kata laluan, pasangan kunci peribadi-umum dicipta untuk apl/laman web. Kunci persendirian akan disimpan dengan selamat pada peranti atau pengurus kata laluan dan digunakan untuk mengesahkan identiti anda. Kunci awam dikongsi dengan pelayan apl/laman web. Dengan kunci sepadan, anda boleh mendaftar dan log masuk dengan segera."</string>
     <string name="improved_account_security_title" msgid="1069841917893513424">"Keselamatan akaun yang dipertingkatkan"</string>
     <string name="improved_account_security_detail" msgid="9123750251551844860">"Setiap kunci dipautkan secara eksklusif dengan apl atau laman web kunci dicipta, jadi anda tidak boleh log masuk ke apl atau laman web penipuan secara tidak sengaja. Selain itu, dengan pelayan yang hanya menyimpan kunci awam, penggodaman menjadi jauh lebih sukar."</string>
     <string name="seamless_transition_title" msgid="5335622196351371961">"Peralihan yang lancar"</string>
-    <string name="seamless_transition_detail" msgid="4475509237171739843">"Semasa kita bergerak menuju ke arah masa depan tanpa kata laluan, kata laluan masih akan tersedia bersama dengan kunci laluan."</string>
+    <string name="seamless_transition_detail" msgid="4475509237171739843">"Meskipun masa depan kita nanti tidak memerlukan kata laluan, kata laluan masih akan tersedia bersama dengan kunci laluan."</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"Pilih tempat untuk menyimpan <xliff:g id="CREATETYPES">%1$s</xliff:g> anda"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"Pilih Password Manager untuk menyimpan maklumat anda dan log masuk lebih pantas pada kali seterusnya"</string>
     <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"Cipta kunci laluan untuk <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
@@ -33,35 +49,47 @@
     <string name="sign_ins" msgid="4710739369149469208">"log masuk"</string>
     <string name="sign_in_info" msgid="2627704710674232328">"maklumat log masuk"</string>
     <string name="save_credential_to_title" msgid="3172811692275634301">"Simpan <xliff:g id="CREDENTIALTYPES">%1$s</xliff:g> pada"</string>
-    <string name="create_passkey_in_other_device_title" msgid="9195411122362461390">"Cipta kunci laluan dalam peranti lain?"</string>
+    <string name="create_passkey_in_other_device_title" msgid="2360053098931886245">"Buat kunci laluan pada peranti lain?"</string>
+    <string name="save_password_on_other_device_title" msgid="5829084591948321207">"Simpan kata laluan pada peranti lain?"</string>
+    <string name="save_sign_in_on_other_device_title" msgid="2827990118560134692">"Simpan log masuk pada peranti lain?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"Gunakan <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> untuk semua log masuk anda?"</string>
-    <string name="use_provider_for_all_description" msgid="8466427781848268490">"Password Manager ini akan menyimpan kata laluan dan kunci laluan anda untuk membantu anda log masuk dengan mudah"</string>
+    <string name="use_provider_for_all_description" msgid="1998772715863958997">"Password Manager untuk <xliff:g id="USERNAME">%1$s</xliff:g> akan menyimpan kata laluan dan kunci laluan anda untuk membantu anda log masuk dengan mudah"</string>
     <string name="set_as_default" msgid="4415328591568654603">"Tetapkan sebagai lalai"</string>
+    <string name="settings" msgid="6536394145760913145">"Tetapan"</string>
     <string name="use_once" msgid="9027366575315399714">"Gunakan sekali"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> kata laluan • <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> kunci laluan"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"Kata laluan <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>"</string>
-    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"Kunci laluan <xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g>"</string>
+    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> kunci laluan"</string>
     <string name="more_options_usage_credentials" msgid="1785697001787193984">"<xliff:g id="TOTALCREDENTIALSNUMBER">%1$s</xliff:g> bukti kelayakan"</string>
     <string name="passkey_before_subtitle" msgid="2448119456208647444">"Kunci laluan"</string>
     <string name="another_device" msgid="5147276802037801217">"Peranti lain"</string>
     <string name="other_password_manager" msgid="565790221427004141">"Password Manager lain"</string>
     <string name="close_sheet" msgid="1393792015338908262">"Tutup helaian"</string>
     <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Kembali ke halaman sebelumnya"</string>
-    <string name="accessibility_close_button" msgid="2953807735590034688">"Tutup cadangan tindakan Pengurus Bukti Kelayakan yang muncul di bahagian bawah skrin"</string>
+    <string name="accessibility_close_button" msgid="1163435587545377687">"Tutup"</string>
+    <string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"Ketepikan"</string>
     <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Gunakan kunci laluan anda yang telah disimpan untuk <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
-    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Gunakan maklumat log masuk anda yang telah disimpan untuk <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
-    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Pilih log masuk yang telah disimpan untuk <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_use_password_for" msgid="625828023234318484">"Gunakan kata laluan anda yang disimpan untuk <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"Gunakan log masuk anda untuk <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"Buka kunci pilihan log masuk untuk <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"Pilih kunci laluan yang disimpan untuk <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"Pilih kata laluan yang disimpan untuk <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_saved_sign_in_for" msgid="2420298653461652728">"Pilih log masuk yang disimpan untuk <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="3048870756117876514">"Pilih log masuk untuk <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_option_for" msgid="4976380044745029107">"Pilih satu pilihan untuk <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_info_on" msgid="8863708099535435146">"Gunakan maklumat ini pada <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Log masuk menggunakan cara lain"</string>
     <string name="snackbar_action" msgid="37373514216505085">"Lihat pilihan"</string>
     <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Teruskan"</string>
     <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Pilihan log masuk"</string>
+    <string name="button_label_view_more" msgid="3429098227286495651">"Lihat lagi"</string>
     <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Untuk <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Password Manager dikunci"</string>
-    <!-- no translation found for locked_credential_entry_label_subtext_tap_to_unlock (6390367581393605009) -->
-    <skip />
-    <!-- no translation found for locked_credential_entry_label_subtext_no_sign_in (8131725029983174901) -->
-    <skip />
+    <string name="locked_credential_entry_label_subtext_tap_to_unlock" msgid="6390367581393605009">"Ketik untuk membuka kunci"</string>
+    <string name="locked_credential_entry_label_subtext_no_sign_in" msgid="8131725029983174901">"Tiada maklumat log masuk"</string>
+    <string name="no_sign_in_info_in" msgid="2641118151920288356">"Tiada maklumat log masuk pada <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Urus log masuk"</string>
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Daripada peranti lain"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Gunakan peranti yang lain"</string>
+    <string name="request_cancelled_by" msgid="3735222326886267820">"Permintaan dibatalkan oleh <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
 </resources>
diff --git a/packages/CredentialManager/res/values-my/strings.xml b/packages/CredentialManager/res/values-my/strings.xml
index 0a03356..c359ce1 100644
--- a/packages/CredentialManager/res/values-my/strings.xml
+++ b/packages/CredentialManager/res/values-my/strings.xml
@@ -1,15 +1,31 @@
 <?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.
+   -->
+
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="4539824758261855508">"အထောက်အထားမန်နေဂျာ"</string>
     <string name="string_cancel" msgid="6369133483981306063">"မလုပ်တော့"</string>
     <string name="string_continue" msgid="1346732695941131882">"ရှေ့ဆက်ရန်"</string>
-    <string name="string_more_options" msgid="7990658711962795124">"နောက်ထပ်ရွေးစရာများ"</string>
+    <string name="string_more_options" msgid="2763852250269945472">"အခြားနည်း သိမ်းရန်"</string>
     <string name="string_learn_more" msgid="4541600451688392447">"ပိုမိုလေ့လာရန်"</string>
     <string name="content_description_show_password" msgid="3283502010388521607">"စကားဝှက်ကို ပြရန်"</string>
     <string name="content_description_hide_password" msgid="6841375971631767996">"စကားဝှက်ကို ဖျောက်ရန်"</string>
     <string name="passkey_creation_intro_title" msgid="4251037543787718844">"လျှို့ဝှက်ကီးများဖြင့် ပိုလုံခြုံသည်"</string>
-    <string name="passkey_creation_intro_body_password" msgid="8825872426579958200">"လျှို့ဝှက်ကီးများဖြင့် ရှုပ်ထွေးသောစကားဝှက်များကို ပြုလုပ်ရန် (သို့) မှတ်မိရန် မလိုပါ"</string>
+    <string name="passkey_creation_intro_body_password" msgid="8825872426579958200">"လျှို့ဝှက်ကီးများသုံးလျှင် ရှုပ်ထွေးသောစကားဝှက်များကို ပြုလုပ်ရန် (သို့) မှတ်မိရန် မလိုပါ"</string>
     <string name="passkey_creation_intro_body_fingerprint" msgid="7331338631826254055">"လျှို့ဝှက်ကီးများမှာ သင်၏လက်ဗွေ၊ မျက်နှာ (သို့) ဖန်သားပြင်လော့ခ်သုံး၍ ပြုလုပ်ထားသော အသွင်ဝှက်ထားသည့် ဒစ်ဂျစ်တယ်ကီးများ ဖြစ်သည်"</string>
     <string name="passkey_creation_intro_body_device" msgid="1203796455762131631">"၎င်းတို့ကို စကားဝှက်မန်နေဂျာတွင် သိမ်းသဖြင့် အခြားစက်များတွင် လက်မှတ်ထိုးဝင်နိုင်ပါသည်"</string>
     <string name="more_about_passkeys_title" msgid="7797903098728837795">"လျှို့ဝှက်ကီးများအကြောင်း ပိုပြရန်"</string>
@@ -33,10 +49,13 @@
     <string name="sign_ins" msgid="4710739369149469208">"လက်မှတ်ထိုးဝင်မှုများ"</string>
     <string name="sign_in_info" msgid="2627704710674232328">"လက်မှတ်ထိုးဝင်သည့် အချက်အလက်"</string>
     <string name="save_credential_to_title" msgid="3172811692275634301">"<xliff:g id="CREDENTIALTYPES">%1$s</xliff:g> သိမ်းမည့်နေရာ"</string>
-    <string name="create_passkey_in_other_device_title" msgid="9195411122362461390">"အခြားစက်ပစ္စည်းတွင် လျှို့ဝှက်ကီး ပြုလုပ်မလား။"</string>
+    <string name="create_passkey_in_other_device_title" msgid="2360053098931886245">"စက်နောက်တစ်ခုတွင် လျှို့ဝှက်ကီးလုပ်မလား။"</string>
+    <string name="save_password_on_other_device_title" msgid="5829084591948321207">"စက်နောက်တစ်ခုတွင် စကားဝှက်ကို သိမ်းမလား။"</string>
+    <string name="save_sign_in_on_other_device_title" msgid="2827990118560134692">"စက်နောက်တစ်ခုတွင် လက်မှတ်ထိုးဝင်ရန် အထောက်အထားကို သိမ်းမလား။"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"သင်၏လက်မှတ်ထိုးဝင်မှု အားလုံးအတွက် <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> သုံးမလား။"</string>
-    <string name="use_provider_for_all_description" msgid="8466427781848268490">"သင်အလွယ်တကူ လက်မှတ်ထိုးဝင်နိုင်ရန် ဤစကားဝှက်မန်နေဂျာက စကားဝှက်နှင့် လျှို့ဝှက်ကီးများကို သိမ်းပေးပါမည်"</string>
+    <string name="use_provider_for_all_description" msgid="1998772715863958997">"သင်အလွယ်တကူ လက်မှတ်ထိုးဝင်နိုင်ရန် <xliff:g id="USERNAME">%1$s</xliff:g> အတွက် ဤစကားဝှက်မန်နေဂျာက စကားဝှက်နှင့် လျှို့ဝှက်ကီးများကို သိမ်းမည်"</string>
     <string name="set_as_default" msgid="4415328591568654603">"မူရင်းအဖြစ် သတ်မှတ်ရန်"</string>
+    <string name="settings" msgid="6536394145760913145">"ဆက်တင်များ"</string>
     <string name="use_once" msgid="9027366575315399714">"တစ်ကြိမ်သုံးရန်"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"စကားဝှက် <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> ခု • လျှို့ဝှက်ကီး <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> ခု"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"စကားဝှက် <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> ခု"</string>
@@ -47,21 +66,30 @@
     <string name="other_password_manager" msgid="565790221427004141">"အခြားစကားဝှက်မန်နေဂျာများ"</string>
     <string name="close_sheet" msgid="1393792015338908262">"စာမျက်နှာ ပိတ်ရန်"</string>
     <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"ယခင်စာမျက်နှာကို ပြန်သွားပါ"</string>
-    <string name="accessibility_close_button" msgid="2953807735590034688">"ဖန်သားပြင်အောက်ခြေတွင် ပြထားသော ‘အထောက်အထားမန်နေဂျာ’ လုပ်ဆောင်မှု အကြံပြုချက်ကို ပိတ်နိုင်သည်"</string>
-    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"<xliff:g id="APP_NAME">%1$s</xliff:g> အတွက် သိမ်းထားသောလျှို့ဝှက်ကီး သုံးမလား။"</string>
-    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"<xliff:g id="APP_NAME">%1$s</xliff:g> အတွက် သိမ်းထားသောလက်မှတ်ထိုးဝင်မှု သုံးမလား။"</string>
-    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"<xliff:g id="APP_NAME">%1$s</xliff:g> အတွက် သိမ်းထားသော လက်မှတ်ထိုးဝင်မှုကို ရွေးပါ"</string>
+    <string name="accessibility_close_button" msgid="1163435587545377687">"ပိတ်ရန်"</string>
+    <string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"ပယ်ရန်"</string>
+    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"သိမ်းထားသောလျှို့ဝှက်ကီးကို <xliff:g id="APP_NAME">%1$s</xliff:g> အတွက် သုံးမလား။"</string>
+    <string name="get_dialog_title_use_password_for" msgid="625828023234318484">"<xliff:g id="APP_NAME">%1$s</xliff:g> အတွက် သိမ်းထားသောစကားဝှက် သုံးမလား။"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"<xliff:g id="APP_NAME">%1$s</xliff:g> အတွက် သင့်လက်မှတ်ထိုးဝင်မှုကို သုံးမလား။"</string>
+    <string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"<xliff:g id="APP_NAME">%1$s</xliff:g> အတွက် လက်မှတ်ထိုးဝင်မှု ရွေးစရာကို ဖွင့်မလား။"</string>
+    <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"<xliff:g id="APP_NAME">%1$s</xliff:g> အတွက် သိမ်းထားသော လျှို့ဝှက်ကီး ရွေးပါ"</string>
+    <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"<xliff:g id="APP_NAME">%1$s</xliff:g> အတွက် သိမ်းထားသော စကားဝှက် ရွေးပါ"</string>
+    <string name="get_dialog_title_choose_saved_sign_in_for" msgid="2420298653461652728">"<xliff:g id="APP_NAME">%1$s</xliff:g> အတွက် သိမ်းထားသော လက်မှတ်ထိုးဝင်မှုကို ရွေးပါ"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="3048870756117876514">"<xliff:g id="APP_NAME">%1$s</xliff:g> အတွက် လက်မှတ်ထိုးဝင်မှု ရွေးခြင်း"</string>
+    <string name="get_dialog_title_choose_option_for" msgid="4976380044745029107">"<xliff:g id="APP_NAME">%1$s</xliff:g> အတွက် တစ်ခုကိုရွေးမလား။"</string>
+    <string name="get_dialog_title_use_info_on" msgid="8863708099535435146">"<xliff:g id="APP_NAME">%1$s</xliff:g> တွင် ဤအချက်အလက်ကို သုံးမလား။"</string>
     <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"နောက်တစ်နည်းဖြင့် လက်မှတ်ထိုးဝင်ရန်"</string>
     <string name="snackbar_action" msgid="37373514216505085">"ရွေးစရာများကို ကြည့်ရန်"</string>
     <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"ရှေ့ဆက်ရန်"</string>
     <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"လက်မှတ်ထိုးဝင်ရန် နည်းလမ်းများ"</string>
+    <string name="button_label_view_more" msgid="3429098227286495651">"ပိုကြည့်ရန်"</string>
     <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"<xliff:g id="USERNAME">%1$s</xliff:g> အတွက်"</string>
     <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"လော့ခ်ချထားသည့် စကားဝှက်မန်နေဂျာများ"</string>
-    <!-- no translation found for locked_credential_entry_label_subtext_tap_to_unlock (6390367581393605009) -->
-    <skip />
-    <!-- no translation found for locked_credential_entry_label_subtext_no_sign_in (8131725029983174901) -->
-    <skip />
+    <string name="locked_credential_entry_label_subtext_tap_to_unlock" msgid="6390367581393605009">"ဖွင့်ရန် တို့ပါ"</string>
+    <string name="locked_credential_entry_label_subtext_no_sign_in" msgid="8131725029983174901">"လက်မှတ်ထိုးဝင်သည့် အချက်အလက် မရှိပါ"</string>
+    <string name="no_sign_in_info_in" msgid="2641118151920288356">"<xliff:g id="SOURCE">%1$s</xliff:g> တွင် လက်မှတ်ထိုးဝင်ရန်အချက်အလက် မရှိပါ"</string>
     <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"လက်မှတ်ထိုးဝင်မှုများ စီမံခြင်း"</string>
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"စက်နောက်တစ်ခုမှ"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"အခြားစက်သုံးရန်"</string>
+    <string name="request_cancelled_by" msgid="3735222326886267820">"တောင်းဆိုချက်ကို <xliff:g id="APP_NAME">%1$s</xliff:g> က ပယ်ဖျက်လိုက်သည်"</string>
 </resources>
diff --git a/packages/CredentialManager/res/values-nb/strings.xml b/packages/CredentialManager/res/values-nb/strings.xml
index 774b35b..33b0b3ae 100644
--- a/packages/CredentialManager/res/values-nb/strings.xml
+++ b/packages/CredentialManager/res/values-nb/strings.xml
@@ -1,28 +1,42 @@
 <?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.
+   -->
+
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="4539824758261855508">"Legitimasjonslagring"</string>
     <string name="string_cancel" msgid="6369133483981306063">"Avbryt"</string>
     <string name="string_continue" msgid="1346732695941131882">"Fortsett"</string>
-    <string name="string_more_options" msgid="7990658711962795124">"Flere alternativer"</string>
+    <string name="string_more_options" msgid="2763852250269945472">"Lagre annerledes"</string>
     <string name="string_learn_more" msgid="4541600451688392447">"Finn ut mer"</string>
-    <!-- no translation found for content_description_show_password (3283502010388521607) -->
-    <skip />
-    <!-- no translation found for content_description_hide_password (6841375971631767996) -->
-    <skip />
-    <string name="passkey_creation_intro_title" msgid="4251037543787718844">"Tryggere med tilgangsnøkler"</string>
-    <string name="passkey_creation_intro_body_password" msgid="8825872426579958200">"Med tilgangsnøkler trenger du ikke å lage eller huske kompliserte passord"</string>
-    <string name="passkey_creation_intro_body_fingerprint" msgid="7331338631826254055">"Tilgangsnøkler er krypterte digitale nøkler du oppretter med fingeravtrykket, ansiktet eller skjermlåsen"</string>
+    <string name="content_description_show_password" msgid="3283502010388521607">"Vis passordet"</string>
+    <string name="content_description_hide_password" msgid="6841375971631767996">"Skjul passordet"</string>
+    <string name="passkey_creation_intro_title" msgid="4251037543787718844">"Tryggere med passnøkler"</string>
+    <string name="passkey_creation_intro_body_password" msgid="8825872426579958200">"Med passnøkler trenger du ikke å lage eller huske kompliserte passord"</string>
+    <string name="passkey_creation_intro_body_fingerprint" msgid="7331338631826254055">"Passnøkler er krypterte digitale nøkler du oppretter med fingeravtrykket, ansiktet eller skjermlåsen"</string>
     <string name="passkey_creation_intro_body_device" msgid="1203796455762131631">"De lagres i et verktøy for passordlagring, slik at du kan logge på andre enheter"</string>
-    <string name="more_about_passkeys_title" msgid="7797903098728837795">"Mer om tilgangsnøkler"</string>
+    <string name="more_about_passkeys_title" msgid="7797903098728837795">"Mer om passnøkler"</string>
     <string name="passwordless_technology_title" msgid="2497513482056606668">"Passordfri teknologi"</string>
-    <string name="passwordless_technology_detail" msgid="6853928846532955882">"Med tilgangsnøkler kan du logge på uten å bruke passord. Du trenger bare å bruke et fingeravtrykk, ansiktsgjenkjenning, en PIN-kode eller et sveipemønster for å bekrefte hvem du er, og lage en tilgangsnøkkel."</string>
+    <string name="passwordless_technology_detail" msgid="6853928846532955882">"Med passnøkler kan du logge på uten å bruke passord. Du trenger bare å bruke et fingeravtrykk, ansiktsgjenkjenning, en PIN-kode eller et sveipemønster for å bekrefte hvem du er, og lage en passnøkkel."</string>
     <string name="public_key_cryptography_title" msgid="6751970819265298039">"Kryptografi for offentlige nøkler"</string>
-    <string name="public_key_cryptography_detail" msgid="6937631710280562213">"Tilgangsnøkler er basert på FIDO Alliance (med bl.a. Google, Apple og Microsoft) og W3C-standardene og bruker kryptografiske nøkkelpar. I stedet for brukernavn og strenger med tegn som brukes som passord, opprettes det et privat-offentlig nøkkelpar per app eller nettsted. Den private nøkkelen lagres trygt på enheten eller i passordlagringen og bekrefter hvem du er. Den offentlige nøkkelen deles med app- eller nettstedstjeneren. Med korresponderende nøkler kan du raskt registrere deg og logge på."</string>
+    <string name="public_key_cryptography_detail" msgid="6937631710280562213">"Passnøkler er basert på FIDO Alliance (med bl.a. Google, Apple og Microsoft) og W3C-standardene og bruker kryptografiske nøkkelpar. I stedet for brukernavn og strenger med tegn som brukes som passord, opprettes det et privat-offentlig nøkkelpar per app eller nettsted. Den private nøkkelen lagres trygt på enheten eller i passordlagringen og bekrefter hvem du er. Den offentlige nøkkelen deles med app- eller nettstedstjeneren. Med korresponderende nøkler kan du raskt registrere deg og logge på."</string>
     <string name="improved_account_security_title" msgid="1069841917893513424">"Forbedret kontosikkerhet"</string>
     <string name="improved_account_security_detail" msgid="9123750251551844860">"Hver nøkkel er eksklusivt tilknyttet appen eller nettstedet den er laget for. Dermed kan du aldri logge på falske apper eller nettsteder ved et uhell. Og siden tjenerne bare har offentlige nøkler, blir det mye vanskeligere å hacke deg."</string>
     <string name="seamless_transition_title" msgid="5335622196351371961">"Sømløs overgang"</string>
-    <string name="seamless_transition_detail" msgid="4475509237171739843">"Vi går mot en fremtid uten passord, men passord fortsetter å være tilgjengelige ved siden av tilgangsnøkler."</string>
+    <string name="seamless_transition_detail" msgid="4475509237171739843">"Vi går mot en fremtid uten passord, men passord fortsetter å være tilgjengelige ved siden av passnøkler."</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"Velg hvor du vil lagre <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"Velg et verktøy for passordlagring for å lagre informasjonen din og logge på raskere neste gang"</string>
     <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"Vil du opprette en tilgangsnøkkel for <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
@@ -30,40 +44,52 @@
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Vil du lagre påloggingsinformasjon for <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="passkey" msgid="632353688396759522">"tilgangsnøkkel"</string>
     <string name="password" msgid="6738570945182936667">"passord"</string>
-    <string name="passkeys" msgid="5733880786866559847">"tilgangsnøkler"</string>
+    <string name="passkeys" msgid="5733880786866559847">"passnøkler"</string>
     <string name="passwords" msgid="5419394230391253816">"passord"</string>
     <string name="sign_ins" msgid="4710739369149469208">"pålogginger"</string>
     <string name="sign_in_info" msgid="2627704710674232328">"påloggingsinformasjon"</string>
     <string name="save_credential_to_title" msgid="3172811692275634301">"Lagre <xliff:g id="CREDENTIALTYPES">%1$s</xliff:g> i"</string>
-    <string name="create_passkey_in_other_device_title" msgid="9195411122362461390">"Vil du opprette en tilgangsnøkkel på en annen enhet?"</string>
+    <string name="create_passkey_in_other_device_title" msgid="2360053098931886245">"Vil du opprette passnøkkelen på en annen enhet?"</string>
+    <string name="save_password_on_other_device_title" msgid="5829084591948321207">"Vil du lagre passordet på en annen enhet?"</string>
+    <string name="save_sign_in_on_other_device_title" msgid="2827990118560134692">"Vil du lagre pålogging på en annen enhet?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"Vil du bruke <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> for alle pålogginger?"</string>
-    <string name="use_provider_for_all_description" msgid="8466427781848268490">"Dette verktøyet for passordlagring lagrer passord og tilgangsnøkler, så det blir lett å logge på"</string>
+    <string name="use_provider_for_all_description" msgid="1998772715863958997">"Dette verktøyet for passordlagring for <xliff:g id="USERNAME">%1$s</xliff:g> lagrer passord og passnøkler, så det blir lett å logge på"</string>
     <string name="set_as_default" msgid="4415328591568654603">"Angi som standard"</string>
+    <string name="settings" msgid="6536394145760913145">"Innstillinger"</string>
     <string name="use_once" msgid="9027366575315399714">"Bruk én gang"</string>
-    <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> passord • <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> tilgangsnøkler"</string>
+    <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> passord • <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> passnøkler"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> passord"</string>
-    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> tilgangsnøkler"</string>
+    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> passnøkler"</string>
     <string name="more_options_usage_credentials" msgid="1785697001787193984">"<xliff:g id="TOTALCREDENTIALSNUMBER">%1$s</xliff:g>-legitimasjon"</string>
     <string name="passkey_before_subtitle" msgid="2448119456208647444">"Tilgangsnøkkel"</string>
     <string name="another_device" msgid="5147276802037801217">"En annen enhet"</string>
     <string name="other_password_manager" msgid="565790221427004141">"Andre løsninger for passordlagring"</string>
     <string name="close_sheet" msgid="1393792015338908262">"Lukk arket"</string>
     <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Gå tilbake til den forrige siden"</string>
-    <string name="accessibility_close_button" msgid="2953807735590034688">"Lukk handlingsforslaget fra legitimasjonslagring som vises nedest på skjermen"</string>
+    <string name="accessibility_close_button" msgid="1163435587545377687">"Lukk"</string>
+    <string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"Lukk"</string>
     <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Vil du bruke den lagrede tilgangsnøkkelen for <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
-    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Vil du bruke den lagrede påloggingen for <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
-    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Velg en lagret pålogging for <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_use_password_for" msgid="625828023234318484">"Vil du bruke det lagrede passordet ditt for <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"Vil du bruke påloggingen for <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"Vil du låse opp påloggingsalternativene for <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"Velg en lagret passnøkkel for <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"Velg et lagret passord for <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_saved_sign_in_for" msgid="2420298653461652728">"Velg en lagret pålogging for <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="3048870756117876514">"Velg pålogging for <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_option_for" msgid="4976380044745029107">"Vil du velge et alternativ for <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_info_on" msgid="8863708099535435146">"Vil du bruke denne informasjonen i <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Bruk en annen påloggingsmetode"</string>
     <string name="snackbar_action" msgid="37373514216505085">"Se alternativene"</string>
     <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Fortsett"</string>
     <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Påloggingsalternativer"</string>
+    <string name="button_label_view_more" msgid="3429098227286495651">"Se mer"</string>
     <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"For <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Låste løsninger for passordlagring"</string>
-    <!-- no translation found for locked_credential_entry_label_subtext_tap_to_unlock (6390367581393605009) -->
-    <skip />
-    <!-- no translation found for locked_credential_entry_label_subtext_no_sign_in (8131725029983174901) -->
-    <skip />
+    <string name="locked_credential_entry_label_subtext_tap_to_unlock" msgid="6390367581393605009">"Trykk for å låse opp"</string>
+    <string name="locked_credential_entry_label_subtext_no_sign_in" msgid="8131725029983174901">"Ingen påloggingsinformasjon"</string>
+    <string name="no_sign_in_info_in" msgid="2641118151920288356">"Ingen påloggingsinformasjon i <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Administrer pålogginger"</string>
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Fra en annen enhet"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Bruk en annen enhet"</string>
+    <string name="request_cancelled_by" msgid="3735222326886267820">"Forespørselen er kansellert av <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
 </resources>
diff --git a/packages/CredentialManager/res/values-ne/strings.xml b/packages/CredentialManager/res/values-ne/strings.xml
index ca501bd..042ed62 100644
--- a/packages/CredentialManager/res/values-ne/strings.xml
+++ b/packages/CredentialManager/res/values-ne/strings.xml
@@ -1,10 +1,26 @@
 <?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="4539824758261855508">"क्रिडेन्सियल म्यानेजर"</string>
     <string name="string_cancel" msgid="6369133483981306063">"रद्द गर्नुहोस्"</string>
     <string name="string_continue" msgid="1346732695941131882">"जारी राख्नुहोस्"</string>
-    <string name="string_more_options" msgid="7990658711962795124">"थप विकल्पहरू"</string>
+    <string name="string_more_options" msgid="2763852250269945472">"अर्को तरिकाले सेभ गर्नुहोस्"</string>
     <string name="string_learn_more" msgid="4541600451688392447">"थप जान्नुहोस्"</string>
     <string name="content_description_show_password" msgid="3283502010388521607">"पासवर्ड देखाइयोस्"</string>
     <string name="content_description_hide_password" msgid="6841375971631767996">"पासवर्ड लुकाइयोस्"</string>
@@ -18,11 +34,11 @@
     <string name="public_key_cryptography_title" msgid="6751970819265298039">"सार्वजनिक कीको क्रिप्टोग्राफी"</string>
     <string name="public_key_cryptography_detail" msgid="6937631710280562213">"FIDO Alliance (जसमा Google, Apple, Microsoft र अन्य कम्पनी सामेल छन्) र W3C ले तोकेका मापदण्डहरूका आधारमा पासकीमा क्रिप्टोग्राफिक जोडी की प्रयोग गरिएको छ। निजी-सार्वजनिक जोडी की कुनै एप वा वेबसाइटका लागि बनाइन्छ। यो जोडी की युजरनेम र हामीले पासवर्डमा प्रयोग गर्ने वर्णहरूको स्ट्रिङभन्दा फरक हुन्छ। निजी की तपाईंको डिभाइस वा पासवर्ड म्यानेजरमा सुरक्षित रूपमा राखिन्छ र यसले तपाईंको पहिचान पुष्टि गर्छ। सार्वजनिक की चाहिँ एप वा वेबसाइटको सर्भरसँग सेयर गरिन्छ। तपाईं यी की प्रयोग गरी तुरुन्तै दर्ता वा साइन इन गर्न सक्नुहुन्छ।"</string>
     <string name="improved_account_security_title" msgid="1069841917893513424">"खाताको सुदृढ सुरक्षा"</string>
-    <string name="improved_account_security_detail" msgid="9123750251551844860">"तपाईं कहिले पनि गल्तीले कुनै कपटपूर्ण एप वा वेबसाइटमा लग इन गर्न नसक्नुहोस् भन्नाका लागि हरेक की जुन एप वा वेबसाइटको लागि बनाइएको थियो त्यसलाई खास गरी सोही एप वा वेबसाइटसँग लिंक गरिन्छ। यसका साथै, सर्भरहरूले सार्वजनिक की मात्र राखिराख्ने भएकाले ह्याक गर्न झनै कठिन भएको छ।"</string>
+    <string name="improved_account_security_detail" msgid="9123750251551844860">"तपाईं कहिले पनि गल्तीले कुनै कपटपूर्ण एप वा वेबसाइटमा लग इन गर्न नसक्नुहोस् भन्नाका लागि हरेक की जुन एप वा वेबसाइटको लागि बनाइएको थियो त्यसलाई खास गरी सोही एप वा वेबसाइटसँग लिंक गरिन्छ। यसका साथै, सर्भरहरूले सार्वजनिक की मात्र राख्ने भएकाले ह्याक गर्न झनै कठिन हुन्छ।"</string>
     <string name="seamless_transition_title" msgid="5335622196351371961">"निर्बाध ट्रान्जिसन"</string>
     <string name="seamless_transition_detail" msgid="4475509237171739843">"हामी पासवर्डरहित भविष्यतर्फ बढ्दै गर्दा पासकीका साथसाथै पासवर्ड पनि उपलब्ध हुने छ।"</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"तपाईं आफ्ना <xliff:g id="CREATETYPES">%1$s</xliff:g> कहाँ सेभ गर्न चाहनुहुन्छ भन्ने कुरा छनौट गर्नुहोस्"</string>
-    <string name="choose_provider_body" msgid="4967074531845147434">"कुनै पासवर्ड म्यानेजरमा आफ्नो जानकारी सेभ गरी अर्को टपक अझ छिटो साइन एन गर्नुहोस्"</string>
+    <string name="choose_provider_body" msgid="4967074531845147434">"कुनै पासवर्ड म्यानेजरमा आफ्नो जानकारी सेभ गरी अर्को पटक अझ छिटो साइन इन गर्नुहोस्"</string>
     <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"<xliff:g id="APPNAME">%1$s</xliff:g> को पासकी बनाउने हो?"</string>
     <string name="choose_create_option_password_title" msgid="7097275038523578687">"<xliff:g id="APPNAME">%1$s</xliff:g> को पासवर्ड सेभ गर्ने हो?"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"<xliff:g id="APPNAME">%1$s</xliff:g> मा साइन गर्न प्रयोग गरिनु पर्ने जानकारी सेभ गर्ने हो?"</string>
@@ -33,10 +49,13 @@
     <string name="sign_ins" msgid="4710739369149469208">"साइन इनसम्बन्धी जानकारी"</string>
     <string name="sign_in_info" msgid="2627704710674232328">"साइन इन गर्न प्रयोग गरिने जानकारी"</string>
     <string name="save_credential_to_title" msgid="3172811692275634301">"<xliff:g id="CREDENTIALTYPES">%1$s</xliff:g> यहाँ सेभ गर्नुहोस्:"</string>
-    <string name="create_passkey_in_other_device_title" msgid="9195411122362461390">"अर्को डिभाइसमा पासकी बनाउने हो?"</string>
+    <string name="create_passkey_in_other_device_title" msgid="2360053098931886245">"अर्को डिभाइसमा पासकी बनाउने हो?"</string>
+    <string name="save_password_on_other_device_title" msgid="5829084591948321207">"अर्को डिभाइसमा पासवर्ड सेभ गर्ने हो?"</string>
+    <string name="save_sign_in_on_other_device_title" msgid="2827990118560134692">"साइन इन गर्न प्रयोग गरिने युजरनेम र पासवर्ड अर्को डिभाइसमा सेभ गर्ने हो?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"तपाईंले साइन इन गर्ने सबै डिभाइसहरूमा <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> प्रयोग गर्ने हो?"</string>
-    <string name="use_provider_for_all_description" msgid="8466427781848268490">"तपाईं सजिलैसँग साइन इन गर्न सक्नुहोस् भन्नाका लागि यो पासवर्ड म्यानेजरले तपाईंका पासवर्ड र पासकीहरू सेभ गर्छ"</string>
+    <string name="use_provider_for_all_description" msgid="1998772715863958997">"तपाईं सजिलैसँग साइन इन गर्न सक्नुहोस् भन्नाका लागि <xliff:g id="USERNAME">%1$s</xliff:g> को यो पासवर्ड म्यानेजरले तपाईंका पासवर्ड र पासकीहरू सेभ गर्छ"</string>
     <string name="set_as_default" msgid="4415328591568654603">"डिफल्ट जानकारीका रूपमा सेट गर्नुहोस्"</string>
+    <string name="settings" msgid="6536394145760913145">"सेटिङ"</string>
     <string name="use_once" msgid="9027366575315399714">"एक पटक प्रयोग गर्नुहोस्"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> वटा पासवर्ड • <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> वटा पासकी"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> वटा पासवर्ड"</string>
@@ -47,21 +66,30 @@
     <string name="other_password_manager" msgid="565790221427004141">"अन्य पासवर्ड म्यानेजरहरू"</string>
     <string name="close_sheet" msgid="1393792015338908262">"पाना बन्द गर्नुहोस्"</string>
     <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"अघिल्लो पेजमा फर्कनुहोस्"</string>
-    <string name="accessibility_close_button" msgid="2953807735590034688">"स्क्रिनको फेदमा देखिएको क्रिडेन्सियल म्यानेजरको कारबाहीसम्बन्धी सुझाव बन्द गर्नुहोस्"</string>
+    <string name="accessibility_close_button" msgid="1163435587545377687">"बन्द गर्नुहोस्"</string>
+    <string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"हटाउनुहोस्"</string>
     <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"आफूले सेभ गरेको पासकी प्रयोग गरी <xliff:g id="APP_NAME">%1$s</xliff:g> मा साइन इन गर्ने हो?"</string>
-    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"आफूले सेभ गरेको साइन इनसम्बन्धी जानकारी प्रयोग गरी <xliff:g id="APP_NAME">%1$s</xliff:g> मा साइन इन गर्ने हो?"</string>
-    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"<xliff:g id="APP_NAME">%1$s</xliff:g> मा साइन इन गर्नका लागि सेभ गरिएका साइन इनसम्बन्धी जानकारी छनौट गर्नुहोस्"</string>
+    <string name="get_dialog_title_use_password_for" msgid="625828023234318484">"<xliff:g id="APP_NAME">%1$s</xliff:g> मा साइन इन गर्न सेभ गरिएको पासवर्ड प्रयोग गर्ने हो?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"<xliff:g id="APP_NAME">%1$s</xliff:g> मा साइन इन गर्नका लागि तपाईंका क्रिडेन्सियलहरू प्रयोग गर्ने हो?"</string>
+    <string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"<xliff:g id="APP_NAME">%1$s</xliff:g> का साइन इनसम्बन्धी विकल्पहरू अनलक गर्ने हो?"</string>
+    <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"<xliff:g id="APP_NAME">%1$s</xliff:g> मा साइन इन गर्न सेभ गरिएको पासकी छनौट गर्नुहोस्"</string>
+    <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"<xliff:g id="APP_NAME">%1$s</xliff:g> मा साइन इन गर्न सेभ गरिएको पासवर्ड छनौट गर्नुहोस्"</string>
+    <string name="get_dialog_title_choose_saved_sign_in_for" msgid="2420298653461652728">"<xliff:g id="APP_NAME">%1$s</xliff:g> मा साइन इन गर्न सेभ गरिएका क्रिडेन्सियल छनौट गर्नुहोस्"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="3048870756117876514">"<xliff:g id="APP_NAME">%1$s</xliff:g> मा साइन इन गर्न प्रयोग गरिने क्रिडेन्सियलहरू छनौट गर्नुहोस्"</string>
+    <string name="get_dialog_title_choose_option_for" msgid="4976380044745029107">"<xliff:g id="APP_NAME">%1$s</xliff:g> मा साइन गर्न प्रयोग गरिने क्रिडेन्सियल छनौट गर्ने हो?"</string>
+    <string name="get_dialog_title_use_info_on" msgid="8863708099535435146">"<xliff:g id="APP_NAME">%1$s</xliff:g> मा साइन गर्न गर्नका निम्ति यो जानकारी प्रयोग गर्ने हो?"</string>
     <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"अर्कै विधि प्रयोग गरी साइन इन गर्नुहोस्"</string>
     <string name="snackbar_action" msgid="37373514216505085">"विकल्पहरू हेर्नुहोस्"</string>
     <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"जारी राख्नुहोस्"</string>
     <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"साइन‍ इनसम्बन्धी विकल्पहरू"</string>
+    <string name="button_label_view_more" msgid="3429098227286495651">"थप हेर्नुहोस्"</string>
     <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"<xliff:g id="USERNAME">%1$s</xliff:g> का लागि"</string>
     <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"लक गरिएका पासवर्ड म्यानेजरहरू"</string>
-    <!-- no translation found for locked_credential_entry_label_subtext_tap_to_unlock (6390367581393605009) -->
-    <skip />
-    <!-- no translation found for locked_credential_entry_label_subtext_no_sign_in (8131725029983174901) -->
-    <skip />
+    <string name="locked_credential_entry_label_subtext_tap_to_unlock" msgid="6390367581393605009">"अनलक गर्न ट्याप गर्नुहोस्"</string>
+    <string name="locked_credential_entry_label_subtext_no_sign_in" msgid="8131725029983174901">"साइन गर्न प्रयोग गरिनु पर्ने जानकारी उपलब्ध छैन"</string>
+    <string name="no_sign_in_info_in" msgid="2641118151920288356">"<xliff:g id="SOURCE">%1$s</xliff:g> मा साइन इन गर्नेसम्बन्धी कुनै पनि जानकारी छैन"</string>
     <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"साइन इनसम्बन्धी विकल्पहरू व्यवस्थापन गर्नुहोस्"</string>
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"अर्को डिभाइसका लागि"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"अर्कै डिभाइस प्रयोग गरी हेर्नुहोस्"</string>
+    <string name="request_cancelled_by" msgid="3735222326886267820">"<xliff:g id="APP_NAME">%1$s</xliff:g> ले अनुरोध रद्द गरेको छ"</string>
 </resources>
diff --git a/packages/CredentialManager/res/values-nl/strings.xml b/packages/CredentialManager/res/values-nl/strings.xml
index ccba755..68f95a7 100644
--- a/packages/CredentialManager/res/values-nl/strings.xml
+++ b/packages/CredentialManager/res/values-nl/strings.xml
@@ -1,10 +1,26 @@
 <?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="4539824758261855508">"Credential Manager"</string>
     <string name="string_cancel" msgid="6369133483981306063">"Annuleren"</string>
     <string name="string_continue" msgid="1346732695941131882">"Doorgaan"</string>
-    <string name="string_more_options" msgid="7990658711962795124">"Meer opties"</string>
+    <string name="string_more_options" msgid="2763852250269945472">"Op een andere manier opslaan"</string>
     <string name="string_learn_more" msgid="4541600451688392447">"Meer informatie"</string>
     <string name="content_description_show_password" msgid="3283502010388521607">"Wachtwoord tonen"</string>
     <string name="content_description_hide_password" msgid="6841375971631767996">"Wachtwoord verbergen"</string>
@@ -16,27 +32,30 @@
     <string name="passwordless_technology_title" msgid="2497513482056606668">"Wachtwoordloze technologie"</string>
     <string name="passwordless_technology_detail" msgid="6853928846532955882">"Met een toegangssleutel heb je geen wachtwoord meer nodig om in te loggen. Als je op deze manier wilt inloggen, moet je je identiteit bevestigen met je vingerafdruk, gezichtsherkenning, pincode of swipepatroon en een toegangssleutel maken."</string>
     <string name="public_key_cryptography_title" msgid="6751970819265298039">"Cryptografie met openbare sleutels"</string>
-    <string name="public_key_cryptography_detail" msgid="6937631710280562213">"Toegangssleutels maken gebruik van cryptografische sleutelparen in overeenstemming met de FIDO Alliance (waartoe onder andere Google, Apple en Microsoft behoren) en W3C-standaarden. Anders dan de combinatie van gebruikersnaam en de tekenreeks die het wachtwoord vormt, wordt bij toegangssleutels voor elke app of website een privé/openbaar sleutelpaar gemaakt. De privésleutel wordt beveiligd opgeslagen op je apparaat of in de wachtwoordmanager om je identiteit te bevestigen. De openbare sleutel wordt gedeeld met de server van de app of website. Als de sleutels overeenkomen, kun je je meteen registreren en inloggen."</string>
+    <string name="public_key_cryptography_detail" msgid="6937631710280562213">"Toegangssleutels maken gebruik van cryptografische sleutelparen in overeenstemming met de FIDO Alliance (waartoe onder andere Google, Apple en Microsoft behoren) en W3C-standaarden. Anders dan de combinatie van gebruikersnaam en de tekenreeks die het wachtwoord vormt, wordt bij toegangssleutels voor elke app of website een privé/openbaar sleutelpaar gemaakt. De privésleutel wordt beveiligd opgeslagen op je apparaat of in de wachtwoordmanager en bevestigt je identiteit. De openbare sleutel wordt gedeeld met de server van de app of website. Als de sleutels overeenkomen, kun je je meteen registreren en inloggen."</string>
     <string name="improved_account_security_title" msgid="1069841917893513424">"Verbeterde accountbeveiliging"</string>
     <string name="improved_account_security_detail" msgid="9123750251551844860">"Elke sleutel is exclusief gekoppeld aan de app of website waarvoor deze is gemaakt. Je kunt dus nooit per ongeluk inloggen bij een bedrieglijke app of website. Bovendien bewaren servers alleen openbare sleutels, wat hacken een stuk lastiger maakt."</string>
     <string name="seamless_transition_title" msgid="5335622196351371961">"Naadloze overgang"</string>
     <string name="seamless_transition_detail" msgid="4475509237171739843">"We zijn op weg naar een wachtwoordloze toekomst, maar naast toegangssleutels kun je nog steeds gebruikmaken van wachtwoorden."</string>
-    <string name="choose_provider_title" msgid="8870795677024868108">"Kies waar je je <xliff:g id="CREATETYPES">%1$s</xliff:g> wilt opslaan"</string>
+    <string name="choose_provider_title" msgid="8870795677024868108">"Kiezen waar je je <xliff:g id="CREATETYPES">%1$s</xliff:g> wilt opslaan"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"Selecteer een wachtwoordmanager om je informatie op te slaan en de volgende keer sneller in te loggen"</string>
     <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"Toegangssleutel maken voor <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_password_title" msgid="7097275038523578687">"Wachtwoord opslaan voor <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Inloggegevens opslaan voor <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
-    <string name="passkey" msgid="632353688396759522">"toegangssleutel"</string>
+    <string name="passkey" msgid="632353688396759522">"Toegangssleutel"</string>
     <string name="password" msgid="6738570945182936667">"wachtwoord"</string>
     <string name="passkeys" msgid="5733880786866559847">"toegangssleutels"</string>
     <string name="passwords" msgid="5419394230391253816">"wachtwoorden"</string>
     <string name="sign_ins" msgid="4710739369149469208">"inloggegevens"</string>
     <string name="sign_in_info" msgid="2627704710674232328">"inloggegevens"</string>
     <string name="save_credential_to_title" msgid="3172811692275634301">"<xliff:g id="CREDENTIALTYPES">%1$s</xliff:g> opslaan in"</string>
-    <string name="create_passkey_in_other_device_title" msgid="9195411122362461390">"Toegangssleutel maken op een ander apparaat?"</string>
+    <string name="create_passkey_in_other_device_title" msgid="2360053098931886245">"Toegangssleutel maken op een ander apparaat?"</string>
+    <string name="save_password_on_other_device_title" msgid="5829084591948321207">"Wachtwoord opslaan op een ander apparaat?"</string>
+    <string name="save_sign_in_on_other_device_title" msgid="2827990118560134692">"Login opslaan op een ander apparaat?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> elke keer gebruiken als je inlogt?"</string>
-    <string name="use_provider_for_all_description" msgid="8466427781848268490">"Deze wachtwoordmanager slaat je wachtwoorden en toegangssleutels op zodat je makkelijk kunt inloggen"</string>
+    <string name="use_provider_for_all_description" msgid="1998772715863958997">"Deze wachtwoordmanager voor <xliff:g id="USERNAME">%1$s</xliff:g> slaat je wachtwoorden en toegangssleutels op zodat je makkelijk kunt inloggen"</string>
     <string name="set_as_default" msgid="4415328591568654603">"Instellen als standaard"</string>
+    <string name="settings" msgid="6536394145760913145">"Instellingen"</string>
     <string name="use_once" msgid="9027366575315399714">"Eén keer gebruiken"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> wachtwoorden • <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> toegangssleutels"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> wachtwoorden"</string>
@@ -47,21 +66,30 @@
     <string name="other_password_manager" msgid="565790221427004141">"Andere wachtwoordmanagers"</string>
     <string name="close_sheet" msgid="1393792015338908262">"Blad sluiten"</string>
     <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Ga terug naar de vorige pagina"</string>
-    <string name="accessibility_close_button" msgid="2953807735590034688">"Sluit de aanbevolen actie voor de Credential Manager onderaan het scherm"</string>
-    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Je opgeslagen toegangssleutel voor <xliff:g id="APP_NAME">%1$s</xliff:g> gebruiken?"</string>
-    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Je opgeslagen inloggegevens voor <xliff:g id="APP_NAME">%1$s</xliff:g> gebruiken?"</string>
-    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Opgeslagen inloggegevens kiezen voor <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
-    <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Inloggen via een andere methode"</string>
+    <string name="accessibility_close_button" msgid="1163435587545377687">"Sluiten"</string>
+    <string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"Sluiten"</string>
+    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Je opgeslagen toegangssleutel gebruiken voor <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_password_for" msgid="625828023234318484">"Je opgeslagen wachtwoord voor <xliff:g id="APP_NAME">%1$s</xliff:g> gebruiken?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"Je login voor <xliff:g id="APP_NAME">%1$s</xliff:g> gebruiken?"</string>
+    <string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"Inlogopties voor <xliff:g id="APP_NAME">%1$s</xliff:g> ontgrendelen?"</string>
+    <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"Opgeslagen toegangssleutel kiezen voor <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"Opgeslagen wachtwoord kiezen voor <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_saved_sign_in_for" msgid="2420298653461652728">"Opgeslagen login kiezen voor <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="3048870756117876514">"Kies een login voor <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_option_for" msgid="4976380044745029107">"Een optie kiezen voor <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_info_on" msgid="8863708099535435146">"Deze informatie gebruiken in <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Op een andere manier inloggen"</string>
     <string name="snackbar_action" msgid="37373514216505085">"Opties bekijken"</string>
     <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Doorgaan"</string>
     <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Opties voor inloggen"</string>
+    <string name="button_label_view_more" msgid="3429098227286495651">"Meer bekijken"</string>
     <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Voor <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Vergrendelde wachtwoordmanagers"</string>
-    <!-- no translation found for locked_credential_entry_label_subtext_tap_to_unlock (6390367581393605009) -->
-    <skip />
-    <!-- no translation found for locked_credential_entry_label_subtext_no_sign_in (8131725029983174901) -->
-    <skip />
+    <string name="locked_credential_entry_label_subtext_tap_to_unlock" msgid="6390367581393605009">"Tik om te ontgrendelen"</string>
+    <string name="locked_credential_entry_label_subtext_no_sign_in" msgid="8131725029983174901">"Geen inloggegevens"</string>
+    <string name="no_sign_in_info_in" msgid="2641118151920288356">"Geen inloggevens in <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Inloggegevens beheren"</string>
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Via een ander apparaat"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Een ander apparaat gebruiken"</string>
+    <string name="request_cancelled_by" msgid="3735222326886267820">"Verzoek geannuleerd door <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
 </resources>
diff --git a/packages/CredentialManager/res/values-or/strings.xml b/packages/CredentialManager/res/values-or/strings.xml
index 73d5104..150ef0b 100644
--- a/packages/CredentialManager/res/values-or/strings.xml
+++ b/packages/CredentialManager/res/values-or/strings.xml
@@ -1,18 +1,32 @@
 <?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.
+   -->
+
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="4539824758261855508">"କ୍ରେଡେନସିଆଲ ମେନେଜର"</string>
     <string name="string_cancel" msgid="6369133483981306063">"ବାତିଲ କରନ୍ତୁ"</string>
     <string name="string_continue" msgid="1346732695941131882">"ଜାରି ରଖନ୍ତୁ"</string>
-    <string name="string_more_options" msgid="7990658711962795124">"ଅଧିକ ବିକଳ୍ପ"</string>
+    <string name="string_more_options" msgid="2763852250269945472">"ଅନ୍ୟ ଉପାୟରେ ସେଭ କର"</string>
     <string name="string_learn_more" msgid="4541600451688392447">"ଅଧିକ ଜାଣନ୍ତୁ"</string>
-    <!-- no translation found for content_description_show_password (3283502010388521607) -->
-    <skip />
-    <!-- no translation found for content_description_hide_password (6841375971631767996) -->
-    <skip />
+    <string name="content_description_show_password" msgid="3283502010388521607">"ପାସୱାର୍ଡ ଦେଖାନ୍ତୁ"</string>
+    <string name="content_description_hide_password" msgid="6841375971631767996">"ପାସୱାର୍ଡ ଲୁଚାନ୍ତୁ"</string>
     <string name="passkey_creation_intro_title" msgid="4251037543787718844">"ପାସକୀ ସହ ଅଧିକ ସୁରକ୍ଷିତ"</string>
     <string name="passkey_creation_intro_body_password" msgid="8825872426579958200">"ପାସକୀଗୁଡ଼ିକ ସହ ଆପଣଙ୍କୁ ଜଟିଳ ପାସୱାର୍ଡଗୁଡ଼ିକ ତିଆରି କରିବା କିମ୍ବା ମନେରଖିବାର ଆବଶ୍ୟକତା ନାହିଁ"</string>
-    <string name="passkey_creation_intro_body_fingerprint" msgid="7331338631826254055">"ପାସକୀଗୁଡ଼ିକ ହେଉଛି ଆପଣ ଆପଣଙ୍କ ଟିପଚିହ୍ନ, ଫେସ କିମ୍ବା ସ୍କ୍ରିନ ଲକ ବ୍ୟବହାର କରି ତିଆରି କରୁଥିବା ଏକକ୍ରିପ୍ଟ କରାଯାଇଥିବା ଡିଜିଟାଲ କୀ"</string>
+    <string name="passkey_creation_intro_body_fingerprint" msgid="7331338631826254055">"ପାସକୀ ହେଉଛି ଏନକ୍ରିପ୍ଟ ହୋଇଥିବା ଡିଜିଟାଲ କୀ\' ଯାହା ଆପଣ ଆପଣଙ୍କ ଟିପଚିହ୍ନ, ଫେସ କିମ୍ବା ସ୍କ୍ରିନ ଲକ ବ୍ୟବହାର କରି ତିଆରି କରନ୍ତି"</string>
     <string name="passkey_creation_intro_body_device" msgid="1203796455762131631">"ସେଗୁଡ଼ିକୁ ଏକ Password Managerରେ ସେଭ କରାଯାଏ, ଯାହା ଫଳରେ ଆପଣ ଅନ୍ୟ ଡିଭାଇସଗୁଡ଼ିକରେ ସାଇନ ଇନ କରିପାରିବେ"</string>
     <string name="more_about_passkeys_title" msgid="7797903098728837795">"ପାସକୀଗୁଡ଼ିକ ବିଷୟରେ ଅଧିକ"</string>
     <string name="passwordless_technology_title" msgid="2497513482056606668">"ପାସୱାର୍ଡ ବିହୀନ ଟେକ୍ନୋଲୋଜି"</string>
@@ -22,8 +36,8 @@
     <string name="improved_account_security_title" msgid="1069841917893513424">"ଉନ୍ନତ ଆକାଉଣ୍ଟ ସୁରକ୍ଷା"</string>
     <string name="improved_account_security_detail" msgid="9123750251551844860">"ପ୍ରତ୍ୟେକ କୀ\'କୁ ସେହି ଆପ କିମ୍ବା ୱେବସାଇଟ ସହ ଏକ୍ସକ୍ଲୁସିଭ ଭାବେ ଲିଙ୍କ କରାଯାଏ ଯେଉଁଥିପାଇଁ ଏହାକୁ ତିଆରି କରାଯାଇଛି, ଫଳରେ ଆପଣ ଭୁଲବଶତଃ କୌଣସି ପ୍ରତାରଣାମୂଳକ ଆପ କିମ୍ବା ୱେବସାଇଟରେ କେବେ ବି ସାଇନ ଇନ କରିପାରିବେ ନାହିଁ। ଏହା ସହ, କେବଳ ସର୍ଭରଗୁଡ଼ିକ ସାର୍ବଜନୀନ କୀ ରଖୁଥିବା ଯୋଗୁଁ ଏହାକୁ ହେକ କରିବା ବହୁତ କଷ୍ଟକର।"</string>
     <string name="seamless_transition_title" msgid="5335622196351371961">"ବାଧାରହିତ ଟ୍ରାଞ୍ଜିସନ"</string>
-    <string name="seamless_transition_detail" msgid="4475509237171739843">"ଆମେ ଏକ ପାସୱାର୍ଡ ବିହୀନ ଭବିଷ୍ୟତ ଆଡ଼କୁ ମୁଭ କରୁଥିବା ଯୋଗୁଁ ଏବେ ବି ପାସକୀଗୁଡ଼ିକ ସହିତ ପାସୱାର୍ଡ ଉପଲବ୍ଧ ହେବ।"</string>
-    <string name="choose_provider_title" msgid="8870795677024868108">"ଆପଣଙ୍କ <xliff:g id="CREATETYPES">%1$s</xliff:g>କୁ କେଉଁଠାରେ ସେଭ କରିବେ ତାହା ବାଛନ୍ତୁ"</string>
+    <string name="seamless_transition_detail" msgid="4475509237171739843">"ଆମେ ଏକ ପାସୱାର୍ଡବିହୀନ ଭବିଷ୍ୟତ ଆଡ଼କୁ ମୁଭ କରୁଥିବା ଯୋଗୁଁ ଏବେ ବି ପାସକୀଗୁଡ଼ିକ ସହିତ ପାସୱାର୍ଡ ଉପଲବ୍ଧ ହେବ।"</string>
+    <string name="choose_provider_title" msgid="8870795677024868108">"ଆପଣଙ୍କ <xliff:g id="CREATETYPES">%1$s</xliff:g> କେଉଁଠାରେ ସେଭ କରିବେ ତାହା ବାଛନ୍ତୁ"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"ଆପଣଙ୍କ ସୂଚନା ସେଭ କରି ପରବର୍ତ୍ତୀ ସମୟରେ ଶୀଘ୍ର ସାଇନ ଇନ କରିବା ପାଇଁ ଏକ Password Manager ଚୟନ କରନ୍ତୁ"</string>
     <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"<xliff:g id="APPNAME">%1$s</xliff:g> ପାଇଁ ପାସକୀ ତିଆରି କରିବେ?"</string>
     <string name="choose_create_option_password_title" msgid="7097275038523578687">"<xliff:g id="APPNAME">%1$s</xliff:g> ପାଇଁ ପାସୱାର୍ଡ ସେଭ କରିବେ?"</string>
@@ -35,10 +49,13 @@
     <string name="sign_ins" msgid="4710739369149469208">"ସାଇନ-ଇନ"</string>
     <string name="sign_in_info" msgid="2627704710674232328">"ସାଇନ-ଇନ ସୂଚନା"</string>
     <string name="save_credential_to_title" msgid="3172811692275634301">"<xliff:g id="CREDENTIALTYPES">%1$s</xliff:g>କୁ ଏଥିରେ ସେଭ କରନ୍ତୁ"</string>
-    <string name="create_passkey_in_other_device_title" msgid="9195411122362461390">"ଅନ୍ୟ ଏକ ଡିଭାଇସରେ ପାସକୀ ତିଆରି କରିବେ?"</string>
+    <string name="create_passkey_in_other_device_title" msgid="2360053098931886245">"ଅନ୍ୟ ଏକ ଡିଭାଇସରେ ପାସକୀ ତିଆରି କରିବେ?"</string>
+    <string name="save_password_on_other_device_title" msgid="5829084591948321207">"ଅନ୍ୟ ଏକ ଡିଭାଇସରେ ପାସୱାର୍ଡ ସେଭ କରିବେ?"</string>
+    <string name="save_sign_in_on_other_device_title" msgid="2827990118560134692">"ଅନ୍ୟ ଏକ ଡିଭାଇସରେ ସାଇନ-ଇନ ସେଭ କରିବେ?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"ଆପଣଙ୍କ ସମସ୍ତ ସାଇନ-ଇନ ପାଇଁ <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ବ୍ୟବହାର କରିବେ?"</string>
-    <string name="use_provider_for_all_description" msgid="8466427781848268490">"ଏହି Password Manager ସହଜରେ ସାଇନ ଇନ କରିବାରେ ଆପଣଙ୍କୁ ସାହାଯ୍ୟ କରିବା ପାଇଁ ଆପଣଙ୍କ ପାସୱାର୍ଡ ଏବଂ ପାସକୀଗୁଡ଼ିକୁ ଷ୍ଟୋର କରିବ"</string>
+    <string name="use_provider_for_all_description" msgid="1998772715863958997">"ସହଜରେ ସାଇନ ଇନ କରିବାରେ ଆପଣଙ୍କୁ ସାହାଯ୍ୟ କରିବାକୁ <xliff:g id="USERNAME">%1$s</xliff:g>ଙ୍କ ପାଇଁ ଏହି Password Manager ଆପଣଙ୍କ ପାସୱାର୍ଡ ଏବଂ ପାସକୀଗୁଡ଼ିକୁ ଷ୍ଟୋର କରିବ"</string>
     <string name="set_as_default" msgid="4415328591568654603">"ଡିଫଲ୍ଟ ଭାବେ ସେଟ କରନ୍ତୁ"</string>
+    <string name="settings" msgid="6536394145760913145">"ସେଟିଂସ"</string>
     <string name="use_once" msgid="9027366575315399714">"ଥରେ ବ୍ୟବହାର କରନ୍ତୁ"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>ଟି ପାସୱାର୍ଡ • <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>ଟି ପାସକୀ"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>ଟି ପାସୱାର୍ଡ"</string>
@@ -49,21 +66,30 @@
     <string name="other_password_manager" msgid="565790221427004141">"ଅନ୍ୟ Password Manager"</string>
     <string name="close_sheet" msgid="1393792015338908262">"ସିଟ ବନ୍ଦ କରନ୍ତୁ"</string>
     <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"ପୂର୍ବବର୍ତ୍ତୀ ପୃଷ୍ଠାକୁ ଫେରନ୍ତୁ"</string>
-    <string name="accessibility_close_button" msgid="2953807735590034688">"ସ୍କ୍ରିନର ନିମ୍ନରେ ଦେଖାଯାଉଥିବା କ୍ରେଡେନସିଆଲ ମେନେଜର ଆକ୍ସନ ବିଷୟରେ ପରାମର୍ଶକୁ ବନ୍ଦ କରନ୍ତୁ"</string>
+    <string name="accessibility_close_button" msgid="1163435587545377687">"ବନ୍ଦ କରନ୍ତୁ"</string>
+    <string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"ଖାରଜ କରନ୍ତୁ"</string>
     <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"<xliff:g id="APP_NAME">%1$s</xliff:g> ପାଇଁ ସେଭ କରାଯାଇଥିବା ଆପଣଙ୍କ ପାସକୀ ବ୍ୟବହାର କରିବେ?"</string>
-    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"<xliff:g id="APP_NAME">%1$s</xliff:g> ପାଇଁ ସେଭ କରାଯାଇଥିବା ଆପଣଙ୍କ ସାଇନ-ଇନ ବ୍ୟବହାର କରିବେ?"</string>
-    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"<xliff:g id="APP_NAME">%1$s</xliff:g> ପାଇଁ ସେଭ କରାଯାଇଥିବା ଏକ ସାଇନ-ଇନ ବାଛନ୍ତୁ"</string>
+    <string name="get_dialog_title_use_password_for" msgid="625828023234318484">"<xliff:g id="APP_NAME">%1$s</xliff:g> ପାଇଁ ଆପଣଙ୍କ ସେଭ କରାଯାଇଥିବା ପାସୱାର୍ଡକୁ ବ୍ୟବହାର କରିବେ?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"<xliff:g id="APP_NAME">%1$s</xliff:g> ପାଇଁ ଆପଣଙ୍କ ସାଇନ-ଇନ ବ୍ୟବହାର କରିବେ?"</string>
+    <string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"<xliff:g id="APP_NAME">%1$s</xliff:g> ପାଇଁ ସାଇନ-ଇନ ବିକଳ୍ପଗୁଡ଼ିକୁ ଅନଲକ କରିବେ?"</string>
+    <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"<xliff:g id="APP_NAME">%1$s</xliff:g> ପାଇଁ ସେଭ କରାଯାଇଥିବା ଏକ ପାସକୀ ବାଛନ୍ତୁ"</string>
+    <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"<xliff:g id="APP_NAME">%1$s</xliff:g> ପାଇଁ ସେଭ କରାଯାଇଥିବା ଏକ ପାସୱାର୍ଡ ବାଛନ୍ତୁ"</string>
+    <string name="get_dialog_title_choose_saved_sign_in_for" msgid="2420298653461652728">"<xliff:g id="APP_NAME">%1$s</xliff:g> ପାଇଁ ସେଭ କରାଯାଇଥିବା ଏକ ସାଇନ-ଇନ ବାଛନ୍ତୁ"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="3048870756117876514">"<xliff:g id="APP_NAME">%1$s</xliff:g> ପାଇଁ ଏକ ସାଇନ-ଇନ ବାଛନ୍ତୁ"</string>
+    <string name="get_dialog_title_choose_option_for" msgid="4976380044745029107">"<xliff:g id="APP_NAME">%1$s</xliff:g> ପାଇଁ ଏକ ବିକଳ୍ପ ବାଛିବେ?"</string>
+    <string name="get_dialog_title_use_info_on" msgid="8863708099535435146">"<xliff:g id="APP_NAME">%1$s</xliff:g>ରେ ଏହି ସୂଚନାକୁ ବ୍ୟବହାର କରିବେ?"</string>
     <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"ଅନ୍ୟ ଏକ ଉପାୟରେ ସାଇନ ଇନ କରନ୍ତୁ"</string>
     <string name="snackbar_action" msgid="37373514216505085">"ବିକଳ୍ପଗୁଡ଼ିକୁ ଦେଖନ୍ତୁ"</string>
     <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"ଜାରି ରଖନ୍ତୁ"</string>
     <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"ସାଇନ ଇନ ବିକଳ୍ପଗୁଡ଼ିକ"</string>
+    <string name="button_label_view_more" msgid="3429098227286495651">"ଅଧିକ ଦେଖନ୍ତୁ"</string>
     <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"<xliff:g id="USERNAME">%1$s</xliff:g>ରେ"</string>
     <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"ଲକ ଥିବା Password Manager"</string>
-    <!-- no translation found for locked_credential_entry_label_subtext_tap_to_unlock (6390367581393605009) -->
-    <skip />
-    <!-- no translation found for locked_credential_entry_label_subtext_no_sign_in (8131725029983174901) -->
-    <skip />
+    <string name="locked_credential_entry_label_subtext_tap_to_unlock" msgid="6390367581393605009">"ଅନଲକ କରିବାକୁ ଟାପ କରନ୍ତୁ"</string>
+    <string name="locked_credential_entry_label_subtext_no_sign_in" msgid="8131725029983174901">"କୌଣସି ସାଇନ-ଇନ ସୂଚନା ନାହିଁ"</string>
+    <string name="no_sign_in_info_in" msgid="2641118151920288356">"<xliff:g id="SOURCE">%1$s</xliff:g>ରେ କୌଣସି ସାଇନ-ଇନ ସୂଚନା ନାହିଁ"</string>
     <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"ସାଇନ-ଇନ ପରିଚାଳନା କରନ୍ତୁ"</string>
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"ଅନ୍ୟ ଏକ ଡିଭାଇସରୁ"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"ଏକ ଭିନ୍ନ ଡିଭାଇସ ବ୍ୟବହାର କରନ୍ତୁ"</string>
+    <string name="request_cancelled_by" msgid="3735222326886267820">"<xliff:g id="APP_NAME">%1$s</xliff:g> ଦ୍ୱାରା ଅନୁରୋଧ ବାତିଲ ହୋଇଛି"</string>
 </resources>
diff --git a/packages/CredentialManager/res/values-pa/strings.xml b/packages/CredentialManager/res/values-pa/strings.xml
index aa6d853..10ff1ad 100644
--- a/packages/CredentialManager/res/values-pa/strings.xml
+++ b/packages/CredentialManager/res/values-pa/strings.xml
@@ -1,15 +1,29 @@
 <?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.
+   -->
+
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="4539824758261855508">"ਕ੍ਰੀਡੈਂਸ਼ੀਅਲ ਪ੍ਰਬੰਧਕ"</string>
     <string name="string_cancel" msgid="6369133483981306063">"ਰੱਦ ਕਰੋ"</string>
     <string name="string_continue" msgid="1346732695941131882">"ਜਾਰੀ ਰੱਖੋ"</string>
-    <string name="string_more_options" msgid="7990658711962795124">"ਹੋਰ ਵਿਕਲਪ"</string>
+    <string name="string_more_options" msgid="2763852250269945472">"ਹੋਰ ਤਰੀਕੇ ਰੱਖਿਅਤ ਕਰੋ"</string>
     <string name="string_learn_more" msgid="4541600451688392447">"ਹੋਰ ਜਾਣੋ"</string>
-    <!-- no translation found for content_description_show_password (3283502010388521607) -->
-    <skip />
-    <!-- no translation found for content_description_hide_password (6841375971631767996) -->
-    <skip />
+    <string name="content_description_show_password" msgid="3283502010388521607">"ਪਾਸਵਰਡ ਦਿਖਾਓ"</string>
+    <string name="content_description_hide_password" msgid="6841375971631767996">"ਪਾਸਵਰਡ ਲੁਕਾਓ"</string>
     <string name="passkey_creation_intro_title" msgid="4251037543787718844">"ਪਾਸਕੀਆਂ ਨਾਲ ਸੁਰੱਖਿਅਤ"</string>
     <string name="passkey_creation_intro_body_password" msgid="8825872426579958200">"ਪਾਸਕੀਆਂ ਨਾਲ, ਤੁਹਾਨੂੰ ਜਟਿਲ ਪਾਸਵਰਡ ਬਣਾਉਣ ਜਾਂ ਯਾਦ ਰੱਖਣ ਦੀ ਲੋੜ ਨਹੀਂ ਹੁੰਦੀ"</string>
     <string name="passkey_creation_intro_body_fingerprint" msgid="7331338631826254055">"ਪਾਸਕੀਆਂ ਇਨਕ੍ਰਿਪਟਡ ਡਿਜੀਟਲ ਕੁੰਜੀਆਂ ਹੁੰਦੀਆਂ ਹਨ ਜੋ ਤੁਹਾਡੇ ਵੱਲੋਂ ਤੁਹਾਡੇ ਫਿੰਗਰਪ੍ਰਿੰਟ, ਚਿਹਰੇ ਜਾਂ ਸਕ੍ਰੀਨ ਲਾਕ ਦੀ ਵਰਤੋਂ ਕਰ ਕੇ ਬਣਾਈਆਂ ਜਾਂਦੀਆਂ ਹਨ"</string>
@@ -18,12 +32,12 @@
     <string name="passwordless_technology_title" msgid="2497513482056606668">"ਪਾਸਵਰਡ ਰਹਿਤ ਤਕਨਾਲੋਜੀ"</string>
     <string name="passwordless_technology_detail" msgid="6853928846532955882">"ਪਾਸਕੀਆਂ ਤੁਹਾਨੂੰ ਪਾਸਵਰਡਾਂ \'ਤੇ ਭਰੋਸਾ ਕੀਤੇ ਬਿਨਾਂ ਸਾਈਨ-ਇਨ ਕਰਨ ਦੀ ਆਗਿਆ ਦਿੰਦੀਆਂ ਹਨ। ਤੁਹਾਨੂੰ ਆਪਣੀ ਪਛਾਣ ਦੀ ਪੁਸ਼ਟੀ ਕਰਨ ਅਤੇ ਪਾਸਕੀ ਬਣਾਉਣ ਲਈ ਸਿਰਫ਼ ਆਪਣੇ ਫਿੰਗਰਪ੍ਰਿੰਟ, ਚਿਹਰਾ ਪਛਾਣ, ਪਿੰਨ ਜਾਂ ਸਵਾਈਪ ਪੈਟਰਨ ਦੀ ਵਰਤੋਂ ਕਰਨ ਦੀ ਲੋੜ ਹੈ।"</string>
     <string name="public_key_cryptography_title" msgid="6751970819265298039">"ਜਨਤਕ ਕੁੰਜੀ ਕ੍ਰਿਪਟੋਗ੍ਰਾਫ਼ੀ"</string>
-    <string name="public_key_cryptography_detail" msgid="6937631710280562213">"FIDO ਗਠਜੋੜ (ਜਿਸ ਵਿੱਚ Google, Apple, Microsoft ਅਤੇ ਹੋਰ ਸ਼ਾਮਲ ਹਨ) ਅਤੇ W3C ਮਿਆਰਾਂ ਦੇ ਆਧਾਰ \'ਤੇ ਪਾਸਕੀਆਂ ਕ੍ਰਿਪਟੋਗ੍ਰਾਫ਼ਿਕ ਕੁੰਜੀਆਂ ਦੇ ਜੋੜੇ ਵਰਤਦੀਆਂ ਹਨ। ਪਾਸਵਰਡ ਲਈ ਵਰਤੇ ਜਾਂਦੇ ਵਰਤੋਂਕਾਰ ਨਾਮ ਅਤੇ ਅੱਖਰ-ਚਿੰਨ੍ਹਾਂ ਦੀ ਸਤਰ ਤੋਂ ਅਲੱਗ, ਐਪ ਜਾਂ ਵੈੱਬਸਾਈਟ ਲਈ ਨਿੱਜੀ-ਜਨਤਕ ਕੁੰਜੀ ਜੋੜਾ ਬਣਾਇਆ ਜਾਂਦਾ ਹੈ। ਨਿੱਜੀ ਕੁੰਜੀ ਤੁਹਾਡੇ ਡੀਵਾਈਸ ਜਾਂ ਪਾਸਵਰਡ ਪ੍ਰਬੰਧਕ \'ਤੇ ਸੁਰੱਖਿਅਤ ਢੰਗ ਨਾਲ ਸਟੋਰ ਕੀਤੀ ਜਾਂਦੀ ਹੈ ਅਤੇ ਇਹ ਤੁਹਾਡੀ ਪਛਾਣ ਦੀ ਪੁਸ਼ਟੀ ਕਰਦੀ ਹੈ। ਜਨਤਕ ਕੁੰਜੀ ਐਪ ਜਾਂ ਵੈੱਬਸਾਈਟ ਸਰਵਰ ਨਾਲ ਸਾਂਝੀ ਕੀਤੀ ਜਾਂਦੀ ਹੈ। ਸੰਬੰਧਿਤ ਕੁੰਜੀਆਂ ਨਾਲ, ਤੁਸੀਂ ਤੁਰੰਤ ਰਜਿਸਟਰ ਅਤੇ ਸਾਈਨ-ਇਨ ਕਰ ਸਕਦੇ ਹੋ।"</string>
+    <string name="public_key_cryptography_detail" msgid="6937631710280562213">"FIDO ਗਠਜੋੜ (ਜਿਸ ਵਿੱਚ Google, Apple, Microsoft ਅਤੇ ਹੋਰ ਸ਼ਾਮਲ ਹਨ) ਅਤੇ W3C ਮਿਆਰਾਂ ਦੇ ਆਧਾਰ \'ਤੇ ਪਾਸਕੀਆਂ ਕ੍ਰਿਪਟੋਗ੍ਰਾਫ਼ਿਕ ਕੁੰਜੀਆਂ ਦੇ ਜੋੜੇ ਵਰਤਦੀਆਂ ਹਨ। ਪਾਸਵਰਡ ਲਈ ਵਰਤੀ ਜਾਂਦੀ ਅੱਖਰ-ਚਿੰਨ੍ਹਾਂ ਦੀ ਸਤਰ ਅਤੇ ਵਰਤੋਂਕਾਰ ਨਾਮ ਤੋਂ ਅਲੱਗ, ਐਪ ਜਾਂ ਵੈੱਬਸਾਈਟ ਲਈ ਨਿੱਜੀ-ਜਨਤਕ ਕੁੰਜੀ ਜੋੜਾ ਬਣਾਇਆ ਜਾਂਦਾ ਹੈ। ਨਿੱਜੀ ਕੁੰਜੀ ਤੁਹਾਡੇ ਡੀਵਾਈਸ ਜਾਂ ਪਾਸਵਰਡ ਪ੍ਰਬੰਧਕ \'ਤੇ ਸੁਰੱਖਿਅਤ ਢੰਗ ਨਾਲ ਸਟੋਰ ਕੀਤੀ ਜਾਂਦੀ ਹੈ ਅਤੇ ਤੁਹਾਡੀ ਪਛਾਣ ਦੀ ਪੁਸ਼ਟੀ ਕਰਦੀ ਹੈ। ਜਨਤਕ ਕੁੰਜੀ ਐਪ ਜਾਂ ਵੈੱਬਸਾਈਟ ਸਰਵਰ ਨਾਲ ਸਾਂਝੀ ਕੀਤੀ ਜਾਂਦੀ ਹੈ। ਸੰਬੰਧਿਤ ਕੁੰਜੀਆਂ ਨਾਲ, ਤੁਸੀਂ ਤੁਰੰਤ ਰਜਿਸਟਰ ਅਤੇ ਸਾਈਨ-ਇਨ ਕਰ ਸਕਦੇ ਹੋ।"</string>
     <string name="improved_account_security_title" msgid="1069841917893513424">"ਬਿਹਤਰ ਖਾਤਾ ਸੁਰੱਖਿਆ"</string>
     <string name="improved_account_security_detail" msgid="9123750251551844860">"ਹਰੇਕ ਕੁੰਜੀ ਖਾਸ ਤੌਰ \'ਤੇ ਉਸ ਐਪ ਜਾਂ ਵੈੱਬਸਾਈਟ ਨਾਲ ਲਿੰਕ ਹੁੰਦੀ ਹੈ ਜਿਸ ਲਈ ਉਹ ਬਣਾਈ ਗਈ ਸੀ, ਇਸ ਲਈ ਤੁਸੀਂ ਕਦੇ ਵੀ ਗਲਤੀ ਨਾਲ ਕਿਸੇ ਧੋਖਾਧੜੀ ਵਾਲੀ ਐਪ ਜਾਂ ਵੈੱਬਸਾਈਟ \'ਤੇ ਸਾਈਨ-ਇਨ ਨਹੀਂ ਕਰ ਸਕਦੇ। ਇਸ ਤੋਂ ਇਲਾਵਾ, ਸਿਰਫ਼ ਜਨਤਕ ਕੁੰਜੀਆਂ ਵਾਲੇ ਸਰਵਰਾਂ \'ਤੇ, ਹੈਕਿੰਗ ਕਰਨਾ ਬਹੁਤ ਔਖਾ ਹੁੰਦਾ ਹੈ।"</string>
     <string name="seamless_transition_title" msgid="5335622196351371961">"ਸਹਿਜ ਪਰਿਵਰਤਨ"</string>
-    <string name="seamless_transition_detail" msgid="4475509237171739843">"ਜਦੋਂ ਕਿ ਅਸੀਂ ਪਾਸਵਰਡ ਰਹਿਤ ਭਵਿੱਖ ਵੱਲ ਵਧ ਰਹੇ ਹਾਂ, ਪਰ ਪਾਸਕੀਆਂ ਦੇ ਨਾਲ ਪਾਸਵਰਡ ਹਾਲੇ ਵੀ ਉਪਲਬਧ ਹੋਣਗੇ।"</string>
-    <string name="choose_provider_title" msgid="8870795677024868108">"ਚੁਣੋ ਕਿ ਆਪਣੇ <xliff:g id="CREATETYPES">%1$s</xliff:g> ਨੂੰ ਕਿੱਥੇ ਰੱਖਿਅਤ ਕਰਨਾ ਹੈ"</string>
+    <string name="seamless_transition_detail" msgid="4475509237171739843">"ਹਾਲਾਂਕਿ, ਅਸੀਂ ਪਾਸਵਰਡ ਰਹਿਤ ਭਵਿੱਖ ਵੱਲ ਵਧ ਰਹੇ ਹਾਂ, ਪਰ ਪਾਸਕੀਆਂ ਦੇ ਨਾਲ ਪਾਸਵਰਡ ਹਾਲੇ ਵੀ ਉਪਲਬਧ ਹੋਣਗੇ।"</string>
+    <string name="choose_provider_title" msgid="8870795677024868108">"ਚੁਣੋ ਕਿ ਆਪਣੀਆਂ <xliff:g id="CREATETYPES">%1$s</xliff:g> ਨੂੰ ਕਿੱਥੇ ਰੱਖਿਅਤ ਕਰਨਾ ਹੈ"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"ਆਪਣੀ ਜਾਣਕਾਰੀ ਨੂੰ ਰੱਖਿਅਤ ਕਰਨ ਅਤੇ ਅਗਲੀ ਵਾਰ ਤੇਜ਼ੀ ਨਾਲ ਸਾਈਨ-ਇਨ ਕਰਨ ਲਈ ਪਾਸਵਰਡ ਪ੍ਰਬੰਧਕ ਚੁਣੋ"</string>
     <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"ਕੀ <xliff:g id="APPNAME">%1$s</xliff:g> ਲਈ ਪਾਸਕੀ ਬਣਾਉਣੀ ਹੈ?"</string>
     <string name="choose_create_option_password_title" msgid="7097275038523578687">"ਕੀ <xliff:g id="APPNAME">%1$s</xliff:g> ਲਈ ਪਾਸਵਰਡ ਰੱਖਿਅਤ ਕਰਨਾ ਹੈ?"</string>
@@ -35,10 +49,13 @@
     <string name="sign_ins" msgid="4710739369149469208">"ਸਾਈਨ-ਇਨਾਂ ਦੀ ਜਾਣਕਾਰੀ"</string>
     <string name="sign_in_info" msgid="2627704710674232328">"ਸਾਈਨ-ਇਨ ਜਾਣਕਾਰੀ"</string>
     <string name="save_credential_to_title" msgid="3172811692275634301">"<xliff:g id="CREDENTIALTYPES">%1$s</xliff:g> ਨੂੰ ਇੱਥੇ ਰੱਖਿਅਤ ਕਰੋ"</string>
-    <string name="create_passkey_in_other_device_title" msgid="9195411122362461390">"ਕੀ ਕਿਸੇ ਹੋਰ ਡੀਵਾਈਸ ਨਾਲ ਪਾਸਕੀ ਬਣਾਉਣੀ ਹੈ?"</string>
+    <string name="create_passkey_in_other_device_title" msgid="2360053098931886245">"ਕੀ ਕਿਸੇ ਹੋਰ ਡੀਵਾਈਸ \'ਤੇ ਪਾਸਕੀ ਬਣਾਉਣੀ ਹੈ?"</string>
+    <string name="save_password_on_other_device_title" msgid="5829084591948321207">"ਕੀ ਕਿਸੇ ਹੋਰ ਡੀਵਾਈਸ \'ਤੇ ਪਾਸਵਰਡ ਰੱਖਿਅਤ ਕਰਨਾ ਹੈ?"</string>
+    <string name="save_sign_in_on_other_device_title" msgid="2827990118560134692">"ਕੀ ਕਿਸੇ ਹੋਰ ਡੀਵਾਈਸ \'ਤੇ ਸਾਈਨ-ਇਨ ਕ੍ਰੀਡੈਂਸ਼ੀਅਲ ਰੱਖਿਅਤ ਕਰਨੇ ਹਨ?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"ਕੀ ਆਪਣੇ ਸਾਰੇ ਸਾਈਨ-ਇਨਾਂ ਲਈ<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ਦੀ ਵਰਤੋਂ ਕਰਨੀ ਹੈ?"</string>
-    <string name="use_provider_for_all_description" msgid="8466427781848268490">"ਇਹ ਪਾਸਵਰਡ ਪ੍ਰਬੰਧਕ ਆਸਾਨੀ ਨਾਲ ਸਾਈਨ-ਇਨ ਕਰਨ ਵਿੱਚ ਤੁਹਾਡੀ ਮਦਦ ਕਰਨ ਲਈ ਤੁਹਾਡੇ ਪਾਸਵਰਡਾਂ ਅਤੇ ਪਾਸਕੀਆਂ ਨੂੰ ਸਟੋਰ ਕਰੇਗਾ"</string>
+    <string name="use_provider_for_all_description" msgid="1998772715863958997">"<xliff:g id="USERNAME">%1$s</xliff:g> ਦਾ ਇਹ Password Manager ਆਸਾਨੀ ਨਾਲ ਸਾਈਨ-ਇਨ ਕਰਨ ਵਿੱਚ ਤੁਹਾਡੀ ਮਦਦ ਕਰਨ ਲਈ ਤੁਹਾਡੇ ਪਾਸਵਰਡਾਂ ਅਤੇ ਪਾਸਕੀਆਂ ਨੂੰ ਸਟੋਰ ਕਰੇਗਾ"</string>
     <string name="set_as_default" msgid="4415328591568654603">"ਪੂਰਵ-ਨਿਰਧਾਰਿਤ ਵਜੋਂ ਸੈੱਟ ਕਰੋ"</string>
+    <string name="settings" msgid="6536394145760913145">"ਸੈਟਿੰਗਾਂ"</string>
     <string name="use_once" msgid="9027366575315399714">"ਇੱਕ ਵਾਰ ਵਰਤੋ"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> ਪਾਸਵਰਡ • <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> ਪਾਸਕੀਆਂ"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> ਪਾਸਵਰਡ"</string>
@@ -49,21 +66,30 @@
     <string name="other_password_manager" msgid="565790221427004141">"ਹੋਰ ਪਾਸਵਰਡ ਪ੍ਰਬੰਧਕ"</string>
     <string name="close_sheet" msgid="1393792015338908262">"ਸ਼ੀਟ ਬੰਦ ਕਰੋ"</string>
     <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"ਪਿਛਲੇ ਪੰਨੇ \'ਤੇ ਵਾਪਸ ਜਾਓ"</string>
-    <string name="accessibility_close_button" msgid="2953807735590034688">"ਸਕ੍ਰੀਨ ਦੇ ਹੇਠਾਂ ਦਿਖਾਈ ਦੇ ਰਹੇ ਕ੍ਰੀਡੈਂਸ਼ੀਅਲ ਪ੍ਰਬੰਧਕ ਦੀ ਕਾਰਵਾਈ ਦੇ ਸੁਝਾਅ ਨੂੰ ਬੰਦ ਕਰੋ"</string>
+    <string name="accessibility_close_button" msgid="1163435587545377687">"ਬੰਦ ਕਰੋ"</string>
+    <string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"ਖਾਰਜ ਕਰੋ"</string>
     <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"ਕੀ <xliff:g id="APP_NAME">%1$s</xliff:g> ਲਈ ਆਪਣੀ ਰੱਖਿਅਤ ਕੀਤੀ ਪਾਸਕੀ ਦੀ ਵਰਤੋਂ ਕਰਨੀ ਹੈ?"</string>
-    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"ਕੀ <xliff:g id="APP_NAME">%1$s</xliff:g> ਲਈ ਆਪਣੀ ਰੱਖਿਅਤ ਕੀਤੀ ਸਾਈਨ-ਇਨ ਜਾਣਕਾਰੀ ਦੀ ਵਰਤੋਂ ਕਰਨੀ ਹੈ?"</string>
-    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"<xliff:g id="APP_NAME">%1$s</xliff:g> ਲਈ ਰੱਖਿਅਤ ਕੀਤੀ ਸਾਈਨ-ਇਨ ਜਾਣਕਾਰੀ ਚੁਣੋ"</string>
+    <string name="get_dialog_title_use_password_for" msgid="625828023234318484">"ਕੀ <xliff:g id="APP_NAME">%1$s</xliff:g> ਲਈ ਤੁਹਾਡਾ ਰੱਖਿਅਤ ਕੀਤਾ ਪਾਸਵਰਡ ਵਰਤਣਾ ਹੈ?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"ਕੀ <xliff:g id="APP_NAME">%1$s</xliff:g> ਲਈ ਆਪਣਾ ਸਾਈਨ-ਇਨ ਕਰਨ ਦਾ ਵਿਕਲਪ ਵਰਤਣਾ ਹੈ?"</string>
+    <string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"ਕੀ <xliff:g id="APP_NAME">%1$s</xliff:g> ਲਈ ਸਾਈਨ-ਇਨ ਵਿਕਲਪਾਂ ਨੂੰ ਅਣਲਾਕ ਕਰਨਾ ਹੈ?"</string>
+    <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"<xliff:g id="APP_NAME">%1$s</xliff:g> ਲਈ ਕੋਈ ਰੱਖਿਅਤ ਕੀਤੀ ਪਾਸਕੀ ਚੁਣੋ"</string>
+    <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"<xliff:g id="APP_NAME">%1$s</xliff:g> ਲਈ ਕੋਈ ਰੱਖਿਅਤ ਕੀਤਾ ਪਾਸਵਰਡ ਚੁਣੋ"</string>
+    <string name="get_dialog_title_choose_saved_sign_in_for" msgid="2420298653461652728">"<xliff:g id="APP_NAME">%1$s</xliff:g> ਲਈ ਕੋਈ ਰੱਖਿਅਤ ਕੀਤੀ ਸਾਈਨ-ਇਨ ਜਾਣਕਾਰੀ ਚੁਣੋ"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="3048870756117876514">"<xliff:g id="APP_NAME">%1$s</xliff:g> ਲਈ ਸਾਈਨ-ਇਨ ਕਰਨ ਦਾ ਵਿਕਲਪ ਚੁਣੋ"</string>
+    <string name="get_dialog_title_choose_option_for" msgid="4976380044745029107">"ਕੀ <xliff:g id="APP_NAME">%1$s</xliff:g> ਲਈ ਕਿਸੇ ਵਿਕਲਪ ਦੀ ਚੋਣ ਕਰਨੀ ਹੈ?"</string>
+    <string name="get_dialog_title_use_info_on" msgid="8863708099535435146">"ਕੀ <xliff:g id="APP_NAME">%1$s</xliff:g> \'ਤੇ ਇਸ ਜਾਣਕਾਰੀ ਦੀ ਵਰਤੋਂ ਕਰਨੀ ਹੈ?"</string>
     <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"ਕਿਸੇ ਹੋਰ ਤਰੀਕੇ ਨਾਲ ਸਾਈਨ-ਇਨ ਕਰੋ"</string>
     <string name="snackbar_action" msgid="37373514216505085">"ਵਿਕਲਪ ਦੇਖੋ"</string>
     <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"ਜਾਰੀ ਰੱਖੋ"</string>
     <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"ਸਾਈਨ-ਇਨ ਕਰਨ ਦੇ ਵਿਕਲਪ"</string>
+    <string name="button_label_view_more" msgid="3429098227286495651">"ਹੋਰ ਦੇਖੋ"</string>
     <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"<xliff:g id="USERNAME">%1$s</xliff:g> ਲਈ"</string>
     <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"ਲਾਕ ਕੀਤੇ ਪਾਸਵਰਡ ਪ੍ਰਬੰਧਕ"</string>
-    <!-- no translation found for locked_credential_entry_label_subtext_tap_to_unlock (6390367581393605009) -->
-    <skip />
-    <!-- no translation found for locked_credential_entry_label_subtext_no_sign_in (8131725029983174901) -->
-    <skip />
+    <string name="locked_credential_entry_label_subtext_tap_to_unlock" msgid="6390367581393605009">"ਅਣਲਾਕ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ"</string>
+    <string name="locked_credential_entry_label_subtext_no_sign_in" msgid="8131725029983174901">"ਸਾਈਨ-ਇਨ ਜਾਣਕਾਰੀ ਉਪਲਬਧ ਨਹੀਂ ਹੈ"</string>
+    <string name="no_sign_in_info_in" msgid="2641118151920288356">"<xliff:g id="SOURCE">%1$s</xliff:g> ਵਿੱਚ ਕੋਈ ਸਾਈਨ-ਇਨ ਜਾਣਕਾਰੀ ਨਹੀਂ"</string>
     <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"ਸਾਈਨ-ਇਨਾਂ ਦਾ ਪ੍ਰਬੰਧਨ ਕਰੋ"</string>
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"ਹੋਰ ਡੀਵਾਈਸ ਤੋਂ"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"ਵੱਖਰੇ ਡੀਵਾਈਸ ਦੀ ਵਰਤੋਂ ਕਰੋ"</string>
+    <string name="request_cancelled_by" msgid="3735222326886267820">"<xliff:g id="APP_NAME">%1$s</xliff:g> ਵੱਲੋਂ ਬੇਨਤੀ ਰੱਦ ਕੀਤੀ ਗਈ"</string>
 </resources>
diff --git a/packages/CredentialManager/res/values-pl/strings.xml b/packages/CredentialManager/res/values-pl/strings.xml
index 8aca7ce..be60af5 100644
--- a/packages/CredentialManager/res/values-pl/strings.xml
+++ b/packages/CredentialManager/res/values-pl/strings.xml
@@ -1,28 +1,42 @@
 <?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.
+   -->
+
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="4539824758261855508">"Menedżer danych logowania"</string>
     <string name="string_cancel" msgid="6369133483981306063">"Anuluj"</string>
     <string name="string_continue" msgid="1346732695941131882">"Dalej"</string>
-    <string name="string_more_options" msgid="7990658711962795124">"Więcej opcji"</string>
+    <string name="string_more_options" msgid="2763852250269945472">"Zapisz inny sposób"</string>
     <string name="string_learn_more" msgid="4541600451688392447">"Więcej informacji"</string>
-    <!-- no translation found for content_description_show_password (3283502010388521607) -->
-    <skip />
-    <!-- no translation found for content_description_hide_password (6841375971631767996) -->
-    <skip />
-    <string name="passkey_creation_intro_title" msgid="4251037543787718844">"Klucze zwiększają Twoje bezpieczeństwo"</string>
-    <string name="passkey_creation_intro_body_password" msgid="8825872426579958200">"Dzięki kluczom nie musisz tworzyć ani zapamiętywać skomplikowanych haseł"</string>
-    <string name="passkey_creation_intro_body_fingerprint" msgid="7331338631826254055">"Klucze są szyfrowanymi kluczami cyfrowymi, które tworzysz za pomocą funkcji rozpoznawania odcisku palca lub twarzy bądź blokady ekranu"</string>
+    <string name="content_description_show_password" msgid="3283502010388521607">"Pokaż hasło"</string>
+    <string name="content_description_hide_password" msgid="6841375971631767996">"Ukryj hasło"</string>
+    <string name="passkey_creation_intro_title" msgid="4251037543787718844">"Klucze dostępu zwiększają Twoje bezpieczeństwo"</string>
+    <string name="passkey_creation_intro_body_password" msgid="8825872426579958200">"Dzięki kluczom dostępu nie musisz tworzyć ani zapamiętywać skomplikowanych haseł"</string>
+    <string name="passkey_creation_intro_body_fingerprint" msgid="7331338631826254055">"Klucze dostępu są szyfrowanymi kluczami cyfrowymi, które tworzysz za pomocą blokady ekranu bądź funkcji rozpoznawania twarzy lub odcisku palca"</string>
     <string name="passkey_creation_intro_body_device" msgid="1203796455762131631">"Klucze są zapisane w menedżerze haseł, dzięki czemu możesz logować się na innych urządzeniach"</string>
-    <string name="more_about_passkeys_title" msgid="7797903098728837795">"Więcej informacji o kluczach"</string>
+    <string name="more_about_passkeys_title" msgid="7797903098728837795">"Więcej informacji o kluczach dostępu"</string>
     <string name="passwordless_technology_title" msgid="2497513482056606668">"Technologia niewymagająca haseł"</string>
-    <string name="passwordless_technology_detail" msgid="6853928846532955882">"Klucze umożliwiają logowanie się bez konieczności stosowania haseł. Wystarczy użyć odcisku palca, rozpoznawania twarzy, kodu PIN lub wzoru, aby potwierdzić tożsamość i utworzyć klucz."</string>
+    <string name="passwordless_technology_detail" msgid="6853928846532955882">"Klucze dostępu umożliwiają logowanie się bez konieczności stosowania haseł. Wystarczy użyć odcisku palca, rozpoznawania twarzy, kodu PIN lub wzoru, aby potwierdzić tożsamość i utworzyć klucz dostępu."</string>
     <string name="public_key_cryptography_title" msgid="6751970819265298039">"Kryptografia klucza publicznego"</string>
-    <string name="public_key_cryptography_detail" msgid="6937631710280562213">"Zgodnie z zasadami FIDO Alliance (stowarzyszenia zrzeszającego m.in. Google, Apple i Microsoft) oraz standardami W3C klucze opierają się kluczach kryptograficznych. W odróżnieniu od nazw użytkownika i ciągów znaków stanowiących hasła pary kluczy prywatnych i publicznych są tworzone dla konkretnych aplikacji i stron. Klucz prywatny jest bezpiecznie przechowywany na urządzeniu lub w menedżerze haseł i potwierdza Twoją tożsamość. Klucz publiczny jest udostępniany serwerowi aplikacji lub strony. Mając odpowiednie klucze, od razu się zarejestrujesz i zalogujesz."</string>
+    <string name="public_key_cryptography_detail" msgid="6937631710280562213">"Zgodnie z zasadami FIDO Alliance (stowarzyszenia zrzeszającego m.in. Google, Apple i Microsoft) oraz standardami W3C klucze dostępu opierają się na kluczach kryptograficznych. W odróżnieniu od nazw użytkownika i ciągów znaków stanowiących hasła pary kluczy prywatnych i publicznych są tworzone dla konkretnych aplikacji i stron. Klucz prywatny jest bezpiecznie przechowywany na urządzeniu lub w menedżerze haseł i potwierdza Twoją tożsamość. Klucz publiczny jest udostępniany serwerowi aplikacji lub strony. Mając odpowiednie klucze, od razu się zarejestrujesz i zalogujesz."</string>
     <string name="improved_account_security_title" msgid="1069841917893513424">"Zwiększone bezpieczeństwo konta"</string>
     <string name="improved_account_security_detail" msgid="9123750251551844860">"Każdy klucz jest połączony wyłącznie z aplikacją lub stroną, dla której został utworzony, więc nie zalogujesz się przypadkowo w fałszywej aplikacji ani na fałszywej stronie. Ponadto na serwerach są przechowywane wyłącznie klucze publiczne, co znacznie utrudnia hakowanie."</string>
     <string name="seamless_transition_title" msgid="5335622196351371961">"Płynne przechodzenie"</string>
-    <string name="seamless_transition_detail" msgid="4475509237171739843">"W czasie przechodzenia na technologie niewymagające haseł możliwość stosowania haseł – niezależnie od kluczy – wciąż będzie dostępna."</string>
+    <string name="seamless_transition_detail" msgid="4475509237171739843">"W czasie przechodzenia na technologie niewymagające haseł możliwość stosowania haseł – niezależnie od kluczy dostępu – wciąż będzie dostępna."</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"Wybierz, gdzie zapisywać <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"Wybierz menedżera haseł, aby zapisywać informacje i logować się szybciej"</string>
     <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"Utworzyć klucz dla aplikacji <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
@@ -30,40 +44,52 @@
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Zapisać dane logowania do aplikacji <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="passkey" msgid="632353688396759522">"klucz"</string>
     <string name="password" msgid="6738570945182936667">"hasło"</string>
-    <string name="passkeys" msgid="5733880786866559847">"klucze"</string>
+    <string name="passkeys" msgid="5733880786866559847">"klucze dostępu"</string>
     <string name="passwords" msgid="5419394230391253816">"hasła"</string>
     <string name="sign_ins" msgid="4710739369149469208">"dane logowania"</string>
-    <string name="sign_in_info" msgid="2627704710674232328">"informacje dotyczące logowania"</string>
-    <string name="save_credential_to_title" msgid="3172811692275634301">"Zapisać: <xliff:g id="CREDENTIALTYPES">%1$s</xliff:g> w:"</string>
-    <string name="create_passkey_in_other_device_title" msgid="9195411122362461390">"Utworzyć klucz na innym urządzeniu?"</string>
+    <string name="sign_in_info" msgid="2627704710674232328">"dane logowania"</string>
+    <string name="save_credential_to_title" msgid="3172811692275634301">"Zapisać <xliff:g id="CREDENTIALTYPES">%1$s</xliff:g> w:"</string>
+    <string name="create_passkey_in_other_device_title" msgid="2360053098931886245">"Utworzyć klucz dostępu na innym urządzeniu?"</string>
+    <string name="save_password_on_other_device_title" msgid="5829084591948321207">"Zapisać hasło na innym urządzeniu?"</string>
+    <string name="save_sign_in_on_other_device_title" msgid="2827990118560134692">"Zapisać sposób logowania się na innym urządzeniu?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"Używać usługi <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> w przypadku wszystkich danych logowania?"</string>
-    <string name="use_provider_for_all_description" msgid="8466427781848268490">"Menedżer haseł będzie zapisywał Twoje hasła i klucze, aby ułatwić Ci logowanie"</string>
+    <string name="use_provider_for_all_description" msgid="1998772715863958997">"Menedżer haseł na koncie <xliff:g id="USERNAME">%1$s</xliff:g> będzie zapisywał Twoje hasła i klucze dostępu, aby ułatwić Ci logowanie"</string>
     <string name="set_as_default" msgid="4415328591568654603">"Ustaw jako domyślną"</string>
+    <string name="settings" msgid="6536394145760913145">"Ustawienia"</string>
     <string name="use_once" msgid="9027366575315399714">"Użyj raz"</string>
-    <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"Hasła: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> • Klucze: <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>"</string>
+    <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"Hasła: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> • Klucze dostępu: <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"Hasła: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>"</string>
-    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"Klucze: <xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g>"</string>
+    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"Klucze dostępu: <xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g>"</string>
     <string name="more_options_usage_credentials" msgid="1785697001787193984">"Dane logowania: <xliff:g id="TOTALCREDENTIALSNUMBER">%1$s</xliff:g>"</string>
     <string name="passkey_before_subtitle" msgid="2448119456208647444">"Klucz"</string>
     <string name="another_device" msgid="5147276802037801217">"Inne urządzenie"</string>
     <string name="other_password_manager" msgid="565790221427004141">"Inne menedżery haseł"</string>
     <string name="close_sheet" msgid="1393792015338908262">"Zamknij arkusz"</string>
     <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Wróć do poprzedniej strony"</string>
-    <string name="accessibility_close_button" msgid="2953807735590034688">"Zamknij sugestię działania w Menedżerze danych logowania na dole ekranu"</string>
+    <string name="accessibility_close_button" msgid="1163435587545377687">"Zamknij"</string>
+    <string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"Zamknij"</string>
     <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Użyć zapisanego klucza dla aplikacji <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
-    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Użyć zapisanych danych logowania dla aplikacji <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
-    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Wybierz zapisane dane logowania dla aplikacji <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_use_password_for" msgid="625828023234318484">"Użyć zapisanego hasła do aplikacji <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"Użyć tych danych logowania do aplikacji <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"Odblokować opcje logowania do aplikacji <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"Wybierz zapisany klucz dostępu do aplikacji <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"Wybierz zapisane hasło do aplikacji <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_saved_sign_in_for" msgid="2420298653461652728">"Wybierz zapisane dane logowania do aplikacji <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="3048870756117876514">"Wybierz dane logowania do aplikacji <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_option_for" msgid="4976380044745029107">"Wybrać opcję dla aplikacji <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_info_on" msgid="8863708099535435146">"Użyć tych informacji w aplikacji <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Zaloguj się w inny sposób"</string>
     <string name="snackbar_action" msgid="37373514216505085">"Wyświetl opcje"</string>
     <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Dalej"</string>
     <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Opcje logowania"</string>
+    <string name="button_label_view_more" msgid="3429098227286495651">"Wyświetl więcej"</string>
     <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"<xliff:g id="USERNAME">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Zablokowane menedżery haseł"</string>
-    <!-- no translation found for locked_credential_entry_label_subtext_tap_to_unlock (6390367581393605009) -->
-    <skip />
-    <!-- no translation found for locked_credential_entry_label_subtext_no_sign_in (8131725029983174901) -->
-    <skip />
+    <string name="locked_credential_entry_label_subtext_tap_to_unlock" msgid="6390367581393605009">"Kliknij, aby odblokować"</string>
+    <string name="locked_credential_entry_label_subtext_no_sign_in" msgid="8131725029983174901">"Brak danych logowania"</string>
+    <string name="no_sign_in_info_in" msgid="2641118151920288356">"<xliff:g id="SOURCE">%1$s</xliff:g>: brak danych logowania"</string>
     <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Zarządzanie danymi logowania"</string>
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Na innym urządzeniu"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Użyj innego urządzenia"</string>
+    <string name="request_cancelled_by" msgid="3735222326886267820">"Żądanie anulowane przez aplikację <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
 </resources>
diff --git a/packages/CredentialManager/res/values-pt-rBR/strings.xml b/packages/CredentialManager/res/values-pt-rBR/strings.xml
index ed0f275..93459e6 100644
--- a/packages/CredentialManager/res/values-pt-rBR/strings.xml
+++ b/packages/CredentialManager/res/values-pt-rBR/strings.xml
@@ -1,16 +1,32 @@
 <?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.
+   -->
+
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="4539824758261855508">"Gerenciador de credenciais"</string>
     <string name="string_cancel" msgid="6369133483981306063">"Cancelar"</string>
     <string name="string_continue" msgid="1346732695941131882">"Continuar"</string>
-    <string name="string_more_options" msgid="7990658711962795124">"Mais opções"</string>
+    <string name="string_more_options" msgid="2763852250269945472">"Salvar de outra forma"</string>
     <string name="string_learn_more" msgid="4541600451688392447">"Saiba mais"</string>
     <string name="content_description_show_password" msgid="3283502010388521607">"Mostrar senha"</string>
     <string name="content_description_hide_password" msgid="6841375971631767996">"Ocultar senha"</string>
     <string name="passkey_creation_intro_title" msgid="4251037543787718844">"Mais segurança com as chaves de acesso"</string>
     <string name="passkey_creation_intro_body_password" msgid="8825872426579958200">"Com as chaves de acesso, você não precisa criar nem se lembrar de senhas complexas"</string>
-    <string name="passkey_creation_intro_body_fingerprint" msgid="7331338631826254055">"As chaves de acesso são chaves digitais criptografadas que você cria usando a impressão digital, o rosto ou o bloqueio de tela"</string>
+    <string name="passkey_creation_intro_body_fingerprint" msgid="7331338631826254055">"As chaves de acesso são chaves digitais criptografadas que você cria usando a impressão digital, o rosto ou o bloqueio de tela."</string>
     <string name="passkey_creation_intro_body_device" msgid="1203796455762131631">"Elas são salvas em um gerenciador de senhas para que você possa fazer login em outros dispositivos"</string>
     <string name="more_about_passkeys_title" msgid="7797903098728837795">"Saiba mais sobre chaves de acesso"</string>
     <string name="passwordless_technology_title" msgid="2497513482056606668">"Tecnologia sem senha"</string>
@@ -33,10 +49,13 @@
     <string name="sign_ins" msgid="4710739369149469208">"logins"</string>
     <string name="sign_in_info" msgid="2627704710674232328">"informações de login"</string>
     <string name="save_credential_to_title" msgid="3172811692275634301">"Salvar <xliff:g id="CREDENTIALTYPES">%1$s</xliff:g> em"</string>
-    <string name="create_passkey_in_other_device_title" msgid="9195411122362461390">"Criar chave de acesso em outro dispositivo?"</string>
+    <string name="create_passkey_in_other_device_title" msgid="2360053098931886245">"Criar chave de acesso em outro dispositivo?"</string>
+    <string name="save_password_on_other_device_title" msgid="5829084591948321207">"Salvar senha em outro dispositivo?"</string>
+    <string name="save_sign_in_on_other_device_title" msgid="2827990118560134692">"Salvar credenciais de login em outro dispositivo?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"Usar <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> para todos os seus logins?"</string>
-    <string name="use_provider_for_all_description" msgid="8466427781848268490">"Esse gerenciador de senhas vai armazenar suas senhas e chaves de acesso para facilitar o processo de login"</string>
+    <string name="use_provider_for_all_description" msgid="1998772715863958997">"Esse gerenciador vai armazenar as senhas e chaves de acesso para facilitar o processo de login de <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
     <string name="set_as_default" msgid="4415328591568654603">"Definir como padrão"</string>
+    <string name="settings" msgid="6536394145760913145">"Configurações"</string>
     <string name="use_once" msgid="9027366575315399714">"Usar uma vez"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> senhas • <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> chaves de acesso"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> senhas"</string>
@@ -47,21 +66,30 @@
     <string name="other_password_manager" msgid="565790221427004141">"Outros gerenciadores de senha"</string>
     <string name="close_sheet" msgid="1393792015338908262">"Fechar página"</string>
     <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Voltar à página anterior"</string>
-    <string name="accessibility_close_button" msgid="2953807735590034688">"Fechar a sugestão de ação do Gerenciador de Credenciais mostrada na parte de baixo da tela"</string>
-    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Usar sua chave de acesso salva para <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
-    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Usar suas informações de login salvas para <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
-    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Escolher um login salvo para <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="accessibility_close_button" msgid="1163435587545377687">"Fechar"</string>
+    <string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"Dispensar"</string>
+    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Usar sua chave de acesso salva para o app <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_password_for" msgid="625828023234318484">"Usar a senha salva para o app <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"Usar seu login para o app <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"Desbloquear opções de login para o app <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"Escolha uma chave de acesso salva para o app <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"Escolha uma senha salva para o app <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_saved_sign_in_for" msgid="2420298653461652728">"Escolha uma credencial de login salva para o app <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="3048870756117876514">"Escolha uma opção de login para o app <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_option_for" msgid="4976380044745029107">"Escolher uma opção para o app <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_info_on" msgid="8863708099535435146">"Usar estas informações no app <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Fazer login de outra forma"</string>
     <string name="snackbar_action" msgid="37373514216505085">"Conferir opções"</string>
     <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Continuar"</string>
     <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Opções de login"</string>
+    <string name="button_label_view_more" msgid="3429098227286495651">"Mostrar mais"</string>
     <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Para <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Gerenciadores de senha bloqueados"</string>
-    <!-- no translation found for locked_credential_entry_label_subtext_tap_to_unlock (6390367581393605009) -->
-    <skip />
-    <!-- no translation found for locked_credential_entry_label_subtext_no_sign_in (8131725029983174901) -->
-    <skip />
+    <string name="locked_credential_entry_label_subtext_tap_to_unlock" msgid="6390367581393605009">"Toque para desbloquear"</string>
+    <string name="locked_credential_entry_label_subtext_no_sign_in" msgid="8131725029983174901">"Nenhuma informação de login"</string>
+    <string name="no_sign_in_info_in" msgid="2641118151920288356">"Sem informações de login em <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Gerenciar logins"</string>
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"De outro dispositivo"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Usar um dispositivo diferente"</string>
+    <string name="request_cancelled_by" msgid="3735222326886267820">"Solicitação cancelada por <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
 </resources>
diff --git a/packages/CredentialManager/res/values-pt-rPT/strings.xml b/packages/CredentialManager/res/values-pt-rPT/strings.xml
index d9884aa..27b84aa 100644
--- a/packages/CredentialManager/res/values-pt-rPT/strings.xml
+++ b/packages/CredentialManager/res/values-pt-rPT/strings.xml
@@ -1,10 +1,26 @@
 <?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="4539824758261855508">"Credential Manager"</string>
     <string name="string_cancel" msgid="6369133483981306063">"Cancelar"</string>
     <string name="string_continue" msgid="1346732695941131882">"Continuar"</string>
-    <string name="string_more_options" msgid="7990658711962795124">"Mais opções"</string>
+    <string name="string_more_options" msgid="2763852250269945472">"Guardar outra forma"</string>
     <string name="string_learn_more" msgid="4541600451688392447">"Saber mais"</string>
     <string name="content_description_show_password" msgid="3283502010388521607">"Mostrar palavra-passe"</string>
     <string name="content_description_hide_password" msgid="6841375971631767996">"Ocultar palavra-passe"</string>
@@ -16,7 +32,7 @@
     <string name="passwordless_technology_title" msgid="2497513482056606668">"Tecnologia sem palavras-passe"</string>
     <string name="passwordless_technology_detail" msgid="6853928846532955882">"As chaves de acesso permitem-lhe iniciar sessão sem depender das palavras-passe. Basta usar a impressão digital, o reconhecimento facial, o PIN ou o padrão de deslize para validar a sua identidade e criar uma chave de acesso."</string>
     <string name="public_key_cryptography_title" msgid="6751970819265298039">"Criptografia de chaves públicas"</string>
-    <string name="public_key_cryptography_detail" msgid="6937631710280562213">"Baseadas na FIDO Alliance e no W3C, as palavras de acesso usam pares de chaves criptográficas. Ao contrário do nome de utilizador e da string de carateres das palavras-passe, cria-se um par de chaves públicas/privadas para a app ou Website. A chave privada é armazenada de forma segura no dispositivo ou gestor de palavras-passe e confirma a identidade. A chave pública é partilhada com o servidor do Website ou app. Com as chaves correspondentes, pode registar-se e iniciar sessão instantaneamente."</string>
+    <string name="public_key_cryptography_detail" msgid="6937631710280562213">"Baseadas na FIDO Alliance e no W3C, as chaves de acesso usam pares de chaves criptográficas. Ao contrário do nome de utilizador e da string de carateres das palavras-passe, cria-se um par de chaves públicas/privadas para a app ou Website. A chave privada é armazenada de forma segura no dispositivo ou gestor de palavras-passe e confirma a identidade. A chave pública é partilhada com o servidor do Website ou app. Com as chaves correspondentes, pode registar-se e iniciar sessão instantaneamente."</string>
     <string name="improved_account_security_title" msgid="1069841917893513424">"Segurança melhorada nas contas"</string>
     <string name="improved_account_security_detail" msgid="9123750251551844860">"Cada chave é exclusivamente associada à app ou ao Website para o qual foi criada, por isso, nunca pode iniciar sessão numa app ou num Website fraudulento acidentalmente. Além disso, os servidores só mantêm chaves públicas, o que dificulta a pirataria."</string>
     <string name="seamless_transition_title" msgid="5335622196351371961">"Transição sem complicações"</string>
@@ -32,11 +48,14 @@
     <string name="passwords" msgid="5419394230391253816">"palavras-passe"</string>
     <string name="sign_ins" msgid="4710739369149469208">"inícios de sessão"</string>
     <string name="sign_in_info" msgid="2627704710674232328">"informações de início de sessão"</string>
-    <string name="save_credential_to_title" msgid="3172811692275634301">"Guarde <xliff:g id="CREDENTIALTYPES">%1$s</xliff:g> em"</string>
-    <string name="create_passkey_in_other_device_title" msgid="9195411122362461390">"Criar chave de acesso noutro dispositivo?"</string>
+    <string name="save_credential_to_title" msgid="3172811692275634301">"Guardar <xliff:g id="CREDENTIALTYPES">%1$s</xliff:g> em:"</string>
+    <string name="create_passkey_in_other_device_title" msgid="2360053098931886245">"Criar uma chave de acesso noutro dispositivo?"</string>
+    <string name="save_password_on_other_device_title" msgid="5829084591948321207">"Guardar a palavra-passe noutro dispositivo?"</string>
+    <string name="save_sign_in_on_other_device_title" msgid="2827990118560134692">"Guardar o início de sessão noutro dispositivo?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"Usar <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> para todos os seus inícios de sessão?"</string>
-    <string name="use_provider_for_all_description" msgid="8466427781848268490">"Este gestor de palavras-passe armazena as suas palavras-passe e chaves de acesso para ajudar a iniciar sessão facilmente"</string>
+    <string name="use_provider_for_all_description" msgid="1998772715863958997">"Este gestor de palavras-passe de <xliff:g id="USERNAME">%1$s</xliff:g> armazena as suas palavras-passe e chaves de acesso para ajudar a iniciar sessão facilmente"</string>
     <string name="set_as_default" msgid="4415328591568654603">"Definir como predefinição"</string>
+    <string name="settings" msgid="6536394145760913145">"Definições"</string>
     <string name="use_once" msgid="9027366575315399714">"Usar uma vez"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> palavras-passe • <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> chaves de acesso"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> palavras-passe"</string>
@@ -47,21 +66,30 @@
     <string name="other_password_manager" msgid="565790221427004141">"Outros gestores de palavras-passe"</string>
     <string name="close_sheet" msgid="1393792015338908262">"Fechar folha"</string>
     <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Volte à página anterior"</string>
-    <string name="accessibility_close_button" msgid="2953807735590034688">"Feche a sugestão de ação do Gestor de credenciais apresentada na parte inferior do ecrã"</string>
+    <string name="accessibility_close_button" msgid="1163435587545377687">"Fechar"</string>
+    <string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"Ignorar"</string>
     <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Usar a sua chave de acesso guardada na app <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
-    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Usar o seu início de sessão guardado na app <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
-    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Escolha um início de sessão guardado para a app <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_use_password_for" msgid="625828023234318484">"Usar a sua palavra-passe guardada para a app <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"Usar o seu início de sessão para a app <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"Desbloquear as opções de início de sessão para a app <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"Escolha uma chave de acesso guardada para a app <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"Escolha uma palavra-passe guardada para a app <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_saved_sign_in_for" msgid="2420298653461652728">"Escolha um início de sessão guardado para a app <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="3048870756117876514">"Escolha um início de sessão para a app <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_option_for" msgid="4976380044745029107">"Escolher uma opção para a app <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_info_on" msgid="8863708099535435146">"Usar estas informações na app <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Iniciar sessão de outra forma"</string>
     <string name="snackbar_action" msgid="37373514216505085">"Ver opções"</string>
     <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Continuar"</string>
     <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Opções de início de sessão"</string>
+    <string name="button_label_view_more" msgid="3429098227286495651">"Ver mais"</string>
     <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Para <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Gestores de palavras-passe bloqueados"</string>
-    <!-- no translation found for locked_credential_entry_label_subtext_tap_to_unlock (6390367581393605009) -->
-    <skip />
-    <!-- no translation found for locked_credential_entry_label_subtext_no_sign_in (8131725029983174901) -->
-    <skip />
+    <string name="locked_credential_entry_label_subtext_tap_to_unlock" msgid="6390367581393605009">"Tocar para desbloquear"</string>
+    <string name="locked_credential_entry_label_subtext_no_sign_in" msgid="8131725029983174901">"Sem informações de início de sessão"</string>
+    <string name="no_sign_in_info_in" msgid="2641118151920288356">"Sem informações de início de sessão em <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Faça a gestão dos inícios de sessão"</string>
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"De outro dispositivo"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Use um dispositivo diferente"</string>
+    <string name="request_cancelled_by" msgid="3735222326886267820">"Pedido cancelado pela app <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
 </resources>
diff --git a/packages/CredentialManager/res/values-pt/strings.xml b/packages/CredentialManager/res/values-pt/strings.xml
index ed0f275..93459e6 100644
--- a/packages/CredentialManager/res/values-pt/strings.xml
+++ b/packages/CredentialManager/res/values-pt/strings.xml
@@ -1,16 +1,32 @@
 <?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.
+   -->
+
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="4539824758261855508">"Gerenciador de credenciais"</string>
     <string name="string_cancel" msgid="6369133483981306063">"Cancelar"</string>
     <string name="string_continue" msgid="1346732695941131882">"Continuar"</string>
-    <string name="string_more_options" msgid="7990658711962795124">"Mais opções"</string>
+    <string name="string_more_options" msgid="2763852250269945472">"Salvar de outra forma"</string>
     <string name="string_learn_more" msgid="4541600451688392447">"Saiba mais"</string>
     <string name="content_description_show_password" msgid="3283502010388521607">"Mostrar senha"</string>
     <string name="content_description_hide_password" msgid="6841375971631767996">"Ocultar senha"</string>
     <string name="passkey_creation_intro_title" msgid="4251037543787718844">"Mais segurança com as chaves de acesso"</string>
     <string name="passkey_creation_intro_body_password" msgid="8825872426579958200">"Com as chaves de acesso, você não precisa criar nem se lembrar de senhas complexas"</string>
-    <string name="passkey_creation_intro_body_fingerprint" msgid="7331338631826254055">"As chaves de acesso são chaves digitais criptografadas que você cria usando a impressão digital, o rosto ou o bloqueio de tela"</string>
+    <string name="passkey_creation_intro_body_fingerprint" msgid="7331338631826254055">"As chaves de acesso são chaves digitais criptografadas que você cria usando a impressão digital, o rosto ou o bloqueio de tela."</string>
     <string name="passkey_creation_intro_body_device" msgid="1203796455762131631">"Elas são salvas em um gerenciador de senhas para que você possa fazer login em outros dispositivos"</string>
     <string name="more_about_passkeys_title" msgid="7797903098728837795">"Saiba mais sobre chaves de acesso"</string>
     <string name="passwordless_technology_title" msgid="2497513482056606668">"Tecnologia sem senha"</string>
@@ -33,10 +49,13 @@
     <string name="sign_ins" msgid="4710739369149469208">"logins"</string>
     <string name="sign_in_info" msgid="2627704710674232328">"informações de login"</string>
     <string name="save_credential_to_title" msgid="3172811692275634301">"Salvar <xliff:g id="CREDENTIALTYPES">%1$s</xliff:g> em"</string>
-    <string name="create_passkey_in_other_device_title" msgid="9195411122362461390">"Criar chave de acesso em outro dispositivo?"</string>
+    <string name="create_passkey_in_other_device_title" msgid="2360053098931886245">"Criar chave de acesso em outro dispositivo?"</string>
+    <string name="save_password_on_other_device_title" msgid="5829084591948321207">"Salvar senha em outro dispositivo?"</string>
+    <string name="save_sign_in_on_other_device_title" msgid="2827990118560134692">"Salvar credenciais de login em outro dispositivo?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"Usar <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> para todos os seus logins?"</string>
-    <string name="use_provider_for_all_description" msgid="8466427781848268490">"Esse gerenciador de senhas vai armazenar suas senhas e chaves de acesso para facilitar o processo de login"</string>
+    <string name="use_provider_for_all_description" msgid="1998772715863958997">"Esse gerenciador vai armazenar as senhas e chaves de acesso para facilitar o processo de login de <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
     <string name="set_as_default" msgid="4415328591568654603">"Definir como padrão"</string>
+    <string name="settings" msgid="6536394145760913145">"Configurações"</string>
     <string name="use_once" msgid="9027366575315399714">"Usar uma vez"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> senhas • <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> chaves de acesso"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> senhas"</string>
@@ -47,21 +66,30 @@
     <string name="other_password_manager" msgid="565790221427004141">"Outros gerenciadores de senha"</string>
     <string name="close_sheet" msgid="1393792015338908262">"Fechar página"</string>
     <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Voltar à página anterior"</string>
-    <string name="accessibility_close_button" msgid="2953807735590034688">"Fechar a sugestão de ação do Gerenciador de Credenciais mostrada na parte de baixo da tela"</string>
-    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Usar sua chave de acesso salva para <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
-    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Usar suas informações de login salvas para <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
-    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Escolher um login salvo para <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="accessibility_close_button" msgid="1163435587545377687">"Fechar"</string>
+    <string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"Dispensar"</string>
+    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Usar sua chave de acesso salva para o app <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_password_for" msgid="625828023234318484">"Usar a senha salva para o app <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"Usar seu login para o app <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"Desbloquear opções de login para o app <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"Escolha uma chave de acesso salva para o app <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"Escolha uma senha salva para o app <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_saved_sign_in_for" msgid="2420298653461652728">"Escolha uma credencial de login salva para o app <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="3048870756117876514">"Escolha uma opção de login para o app <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_option_for" msgid="4976380044745029107">"Escolher uma opção para o app <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_info_on" msgid="8863708099535435146">"Usar estas informações no app <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Fazer login de outra forma"</string>
     <string name="snackbar_action" msgid="37373514216505085">"Conferir opções"</string>
     <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Continuar"</string>
     <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Opções de login"</string>
+    <string name="button_label_view_more" msgid="3429098227286495651">"Mostrar mais"</string>
     <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Para <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Gerenciadores de senha bloqueados"</string>
-    <!-- no translation found for locked_credential_entry_label_subtext_tap_to_unlock (6390367581393605009) -->
-    <skip />
-    <!-- no translation found for locked_credential_entry_label_subtext_no_sign_in (8131725029983174901) -->
-    <skip />
+    <string name="locked_credential_entry_label_subtext_tap_to_unlock" msgid="6390367581393605009">"Toque para desbloquear"</string>
+    <string name="locked_credential_entry_label_subtext_no_sign_in" msgid="8131725029983174901">"Nenhuma informação de login"</string>
+    <string name="no_sign_in_info_in" msgid="2641118151920288356">"Sem informações de login em <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Gerenciar logins"</string>
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"De outro dispositivo"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Usar um dispositivo diferente"</string>
+    <string name="request_cancelled_by" msgid="3735222326886267820">"Solicitação cancelada por <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
 </resources>
diff --git a/packages/CredentialManager/res/values-ro/strings.xml b/packages/CredentialManager/res/values-ro/strings.xml
index e2a243c..5292eca 100644
--- a/packages/CredentialManager/res/values-ro/strings.xml
+++ b/packages/CredentialManager/res/values-ro/strings.xml
@@ -1,44 +1,61 @@
 <?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="4539824758261855508">"Manager de date de conectare"</string>
     <string name="string_cancel" msgid="6369133483981306063">"Anulează"</string>
     <string name="string_continue" msgid="1346732695941131882">"Continuă"</string>
-    <string name="string_more_options" msgid="7990658711962795124">"Mai multe opțiuni"</string>
+    <string name="string_more_options" msgid="2763852250269945472">"Salvează altfel"</string>
     <string name="string_learn_more" msgid="4541600451688392447">"Află mai multe"</string>
-    <!-- no translation found for content_description_show_password (3283502010388521607) -->
-    <skip />
-    <!-- no translation found for content_description_hide_password (6841375971631767996) -->
-    <skip />
+    <string name="content_description_show_password" msgid="3283502010388521607">"Afișează parola"</string>
+    <string name="content_description_hide_password" msgid="6841375971631767996">"Ascunde parola"</string>
     <string name="passkey_creation_intro_title" msgid="4251037543787718844">"Mai în siguranță cu chei de acces"</string>
     <string name="passkey_creation_intro_body_password" msgid="8825872426579958200">"Dacă folosești chei de acces, nu este nevoie să creezi sau să reții parole complexe"</string>
     <string name="passkey_creation_intro_body_fingerprint" msgid="7331338631826254055">"Cheile de acces sunt chei digitale criptate pe care le creezi folosindu-ți amprenta, fața sau blocarea ecranului"</string>
     <string name="passkey_creation_intro_body_device" msgid="1203796455762131631">"Acestea sunt salvate într-un manager de parole, ca să te poți conecta pe alte dispozitive"</string>
     <string name="more_about_passkeys_title" msgid="7797903098728837795">"Mai multe despre cheile de acces"</string>
     <string name="passwordless_technology_title" msgid="2497513482056606668">"Tehnologie fără parole"</string>
-    <string name="passwordless_technology_detail" msgid="6853928846532955882">"Cu ajutorul cheilor de acces, poți să te conectezi fără să te bazezi pe parole. Pentru a-ți verifica identitatea și a crea o cheie de acces, nu trebuie decât să folosești amprenta, recunoașterea facială, PIN-ul sau modelul."</string>
+    <string name="passwordless_technology_detail" msgid="6853928846532955882">"Cu ajutorul cheilor de acces, poți să te conectezi fără să te bazezi pe parole. Pentru a-ți confirma identitatea și a crea o cheie de acces, nu trebuie decât să folosești amprenta, recunoașterea facială, PIN-ul sau modelul."</string>
     <string name="public_key_cryptography_title" msgid="6751970819265298039">"Criptografia cheilor publice"</string>
     <string name="public_key_cryptography_detail" msgid="6937631710280562213">"Conform standardelor FIDO Alliance (din care fac parte Google, Apple, Microsoft etc.) și W3C, cheile de acces folosesc perechi de chei criptografice. Spre deosebire de numele de utilizator și șirul de caractere pe care le folosim pentru parole, perechea de chei publică/privată este creată pentru o aplicație sau un site. Cheia privată este stocată în siguranță pe dispozitivul sau în managerul tău de parole și îți confirmă identitatea. Cheia publică este trimisă aplicației sau serverului site-ului. Cu ajutorul cheilor corespunzătoare, poți să te înregistrezi și să te conectezi instantaneu."</string>
     <string name="improved_account_security_title" msgid="1069841917893513424">"Securitate îmbunătățită a contului"</string>
     <string name="improved_account_security_detail" msgid="9123750251551844860">"Fiecare cheie este conectată în mod exclusiv cu aplicația sau site-ul pentru care a fost creată, prin urmare nu te poți conecta niciodată din greșeală la o aplicație sau un site fraudulos. În plus, întrucât pe servere sunt stocate doar chei publice, hackingul este mult mai dificil."</string>
     <string name="seamless_transition_title" msgid="5335622196351371961">"Tranziție fluidă"</string>
-    <string name="seamless_transition_detail" msgid="4475509237171739843">"Ne îndreptăm spre un viitor fără parole, în care parolele vor fi disponibile pe lângă cheile de acces."</string>
+    <string name="seamless_transition_detail" msgid="4475509237171739843">"Ne îndreptăm spre un viitor fără parole, însă acestea sunt încă disponibile, alături de cheile de acces."</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"Alege unde dorești să salvezi <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"Selectează un manager de parole pentru a salva informațiile și a te conecta mai rapid data viitoare"</string>
     <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"Creezi o cheie de acces pentru <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_password_title" msgid="7097275038523578687">"Salvezi parola pentru <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Salvezi informațiile de conectare pentru <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
-    <string name="passkey" msgid="632353688396759522">"cheie de acces"</string>
+    <string name="passkey" msgid="632353688396759522">"cheia de acces"</string>
     <string name="password" msgid="6738570945182936667">"parolă"</string>
     <string name="passkeys" msgid="5733880786866559847">"cheile de acces"</string>
     <string name="passwords" msgid="5419394230391253816">"parolele"</string>
     <string name="sign_ins" msgid="4710739369149469208">"date de conectare"</string>
     <string name="sign_in_info" msgid="2627704710674232328">"informațiile de conectare"</string>
     <string name="save_credential_to_title" msgid="3172811692275634301">"Salvează <xliff:g id="CREDENTIALTYPES">%1$s</xliff:g> în"</string>
-    <string name="create_passkey_in_other_device_title" msgid="9195411122362461390">"Creezi cheia de acces pe alt dispozitiv?"</string>
+    <string name="create_passkey_in_other_device_title" msgid="2360053098931886245">"Creezi o cheie de acces pe alt dispozitiv?"</string>
+    <string name="save_password_on_other_device_title" msgid="5829084591948321207">"Salvezi parola pe alt dispozitiv?"</string>
+    <string name="save_sign_in_on_other_device_title" msgid="2827990118560134692">"Salvezi datele de conectare pe alt dispozitiv?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"Folosești <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> pentru toate conectările?"</string>
-    <string name="use_provider_for_all_description" msgid="8466427781848268490">"Managerul de parole îți va stoca parolele și cheile de acces, pentru a te ajuta să te conectezi cu ușurință"</string>
+    <string name="use_provider_for_all_description" msgid="1998772715863958997">"Managerul de parole pentru <xliff:g id="USERNAME">%1$s</xliff:g> îți va stoca parolele și cheile de acces, pentru a te ajuta să te conectezi cu ușurință"</string>
     <string name="set_as_default" msgid="4415328591568654603">"Setează ca prestabilite"</string>
+    <string name="settings" msgid="6536394145760913145">"Setări"</string>
     <string name="use_once" msgid="9027366575315399714">"Folosește o dată"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> parole • <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> chei de acces"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> parole"</string>
@@ -49,21 +66,30 @@
     <string name="other_password_manager" msgid="565790221427004141">"Alți manageri de parole"</string>
     <string name="close_sheet" msgid="1393792015338908262">"Închide foaia"</string>
     <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Revino la pagina precedentă"</string>
-    <string name="accessibility_close_button" msgid="2953807735590034688">"Închide sugestia de acțiune de la Managerul de date de conectare, care apare în partea de jos a ecranului"</string>
+    <string name="accessibility_close_button" msgid="1163435587545377687">"Închide"</string>
+    <string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"Închide"</string>
     <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Folosești cheia de acces salvată pentru <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
-    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Folosești datele de conectare salvate pentru <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
-    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Alege o conectare salvată pentru <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_use_password_for" msgid="625828023234318484">"Folosești parola salvată pentru <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"Folosești datele de conectare pentru <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"Deblochezi opțiunile de conectare pentru <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"Alege o cheie de acces salvată pentru <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"Alege o parolă salvată pentru <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_saved_sign_in_for" msgid="2420298653461652728">"Alege o conectare salvată pentru <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="3048870756117876514">"Alege un set de date conectare pentru <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_option_for" msgid="4976380044745029107">"Alegi o opțiune pentru <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_info_on" msgid="8863708099535435146">"Folosești aceste informații în <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Conectează-te altfel"</string>
     <string name="snackbar_action" msgid="37373514216505085">"Afișează opțiunile"</string>
     <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Continuă"</string>
     <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Opțiuni de conectare"</string>
+    <string name="button_label_view_more" msgid="3429098227286495651">"Afișează mai multe"</string>
     <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Pentru <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Manageri de parole blocate"</string>
-    <!-- no translation found for locked_credential_entry_label_subtext_tap_to_unlock (6390367581393605009) -->
-    <skip />
-    <!-- no translation found for locked_credential_entry_label_subtext_no_sign_in (8131725029983174901) -->
-    <skip />
+    <string name="locked_credential_entry_label_subtext_tap_to_unlock" msgid="6390367581393605009">"Atinge pentru a debloca"</string>
+    <string name="locked_credential_entry_label_subtext_no_sign_in" msgid="8131725029983174901">"Fără informații de conectare"</string>
+    <string name="no_sign_in_info_in" msgid="2641118151920288356">"Nu există informații de conectare în contul <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Gestionează acreditările"</string>
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"De pe alt dispozitiv"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Folosește alt dispozitiv"</string>
+    <string name="request_cancelled_by" msgid="3735222326886267820">"Solicitare anulată de <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
 </resources>
diff --git a/packages/CredentialManager/res/values-ru/strings.xml b/packages/CredentialManager/res/values-ru/strings.xml
index 3542edc..99d2d7c 100644
--- a/packages/CredentialManager/res/values-ru/strings.xml
+++ b/packages/CredentialManager/res/values-ru/strings.xml
@@ -1,22 +1,38 @@
 <?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.
+   -->
+
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="4539824758261855508">"Менеджер учетных данных"</string>
     <string name="string_cancel" msgid="6369133483981306063">"Отмена"</string>
     <string name="string_continue" msgid="1346732695941131882">"Продолжить"</string>
-    <string name="string_more_options" msgid="7990658711962795124">"Ещё"</string>
+    <string name="string_more_options" msgid="2763852250269945472">"Сохранить"</string>
     <string name="string_learn_more" msgid="4541600451688392447">"Подробнее"</string>
     <string name="content_description_show_password" msgid="3283502010388521607">"Показать пароль"</string>
     <string name="content_description_hide_password" msgid="6841375971631767996">"Скрыть пароль"</string>
     <string name="passkey_creation_intro_title" msgid="4251037543787718844">"Ключи доступа безопаснее"</string>
     <string name="passkey_creation_intro_body_password" msgid="8825872426579958200">"Благодаря ключам доступа вам не придется создавать или запоминать сложные пароли."</string>
-    <string name="passkey_creation_intro_body_fingerprint" msgid="7331338631826254055">"Ключ доступа – это зашифрованное цифровое удостоверение, которое создается с использованием отпечатка пальца, функции фейсконтроля или блокировки экрана."</string>
+    <string name="passkey_creation_intro_body_fingerprint" msgid="7331338631826254055">"Ключ доступа – это зашифрованное цифровое удостоверение, которое создается на основе отпечатка пальца, снимка для фейсконтроля, PIN-кода или графического ключа."</string>
     <string name="passkey_creation_intro_body_device" msgid="1203796455762131631">"Данные хранятся в менеджере паролей, чтобы вы могли входить в аккаунт на других устройствах."</string>
     <string name="more_about_passkeys_title" msgid="7797903098728837795">"Подробнее о ключах доступа"</string>
     <string name="passwordless_technology_title" msgid="2497513482056606668">"Технология аутентификации без пароля"</string>
     <string name="passwordless_technology_detail" msgid="6853928846532955882">"Ключи доступа позволяют входить в аккаунт без ввода пароля. Чтобы подтвердить личность и создать ключ доступа, достаточно использовать отпечаток пальца, распознавание по лицу, PIN-код или графический ключ."</string>
     <string name="public_key_cryptography_title" msgid="6751970819265298039">"Шифрование с помощью открытого ключа"</string>
-    <string name="public_key_cryptography_detail" msgid="6937631710280562213">"Согласно стандартам W3C и ассоциации FIDO Alliance (Google, Apple, Microsoft и др.) для создания ключей доступа используются пары криптографических ключей. В приложении или на сайте создается не имя пользователя и пароль, а пара открытого и закрытого ключей. Закрытый ключ хранится на вашем устройстве или в менеджере паролей и нужен для подтверждения личности. Открытый ключ передается приложению или серверу сайта. Подходящие ключи помогают быстро войти в аккаунт или зарегистрироваться."</string>
+    <string name="public_key_cryptography_detail" msgid="6937631710280562213">"Согласно стандартам W3C и ассоциации FIDO Alliance (Google, Apple, Microsoft и др.) для создания ключей доступа используются пары ключей шифрования. В приложении или на сайте создается не имя пользователя и пароль, а пара ключей – открытый и закрытый. Закрытый хранится на вашем устройстве или в менеджере паролей и нужен для подтверждения личности, а открытый передается приложению или серверу сайта. Когда ключи соответствуют друг другу, вы можете быстро зарегистрироваться или войти в аккаунт."</string>
     <string name="improved_account_security_title" msgid="1069841917893513424">"Повышенная безопасность аккаунта"</string>
     <string name="improved_account_security_detail" msgid="9123750251551844860">"Каждый ключ связан только с тем приложением или сайтом, для которого был создан, поэтому вы не сможете по ошибке войти в приложение или на сайт мошенников. Кроме того, на серверах хранятся только открытые ключи, что служит дополнительной защитой от взлома."</string>
     <string name="seamless_transition_title" msgid="5335622196351371961">"Плавный переход"</string>
@@ -32,11 +48,14 @@
     <string name="passwords" msgid="5419394230391253816">"пароли"</string>
     <string name="sign_ins" msgid="4710739369149469208">"входы"</string>
     <string name="sign_in_info" msgid="2627704710674232328">"учетные данные"</string>
-    <string name="save_credential_to_title" msgid="3172811692275634301">"Сохранить <xliff:g id="CREDENTIALTYPES">%1$s</xliff:g> в"</string>
-    <string name="create_passkey_in_other_device_title" msgid="9195411122362461390">"Создать ключ доступа на другом устройстве?"</string>
+    <string name="save_credential_to_title" msgid="3172811692275634301">"Где сохранить <xliff:g id="CREDENTIALTYPES">%1$s</xliff:g>"</string>
+    <string name="create_passkey_in_other_device_title" msgid="2360053098931886245">"Создать ключ доступа на другом устройстве?"</string>
+    <string name="save_password_on_other_device_title" msgid="5829084591948321207">"Сохранить пароль на другом устройстве?"</string>
+    <string name="save_sign_in_on_other_device_title" msgid="2827990118560134692">"Сохранить учетные данные на другом устройстве?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"Всегда входить с помощью приложения \"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>\"?"</string>
-    <string name="use_provider_for_all_description" msgid="8466427781848268490">"В этом менеджере паролей можно сохранять учетные данные, например ключи доступа, чтобы потом использовать их для быстрого входа."</string>
+    <string name="use_provider_for_all_description" msgid="1998772715863958997">"В этом менеджере паролей пользователь <xliff:g id="USERNAME">%1$s</xliff:g> сможет сохранять пароли и ключи доступа для быстрого входа."</string>
     <string name="set_as_default" msgid="4415328591568654603">"Использовать по умолчанию"</string>
+    <string name="settings" msgid="6536394145760913145">"Настройки"</string>
     <string name="use_once" msgid="9027366575315399714">"Использовать один раз"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"Пароли (<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>) и ключи доступа (<xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>)"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"Пароли (<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>)"</string>
@@ -47,21 +66,30 @@
     <string name="other_password_manager" msgid="565790221427004141">"Другие менеджеры паролей"</string>
     <string name="close_sheet" msgid="1393792015338908262">"Закрыть лист"</string>
     <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Вернуться на предыдущую страницу"</string>
-    <string name="accessibility_close_button" msgid="2953807735590034688">"Закрыть подсказку Менеджера учетных данных в нижней части экрана"</string>
+    <string name="accessibility_close_button" msgid="1163435587545377687">"Закрыть"</string>
+    <string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"Закрыть"</string>
     <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Использовать сохраненный ключ доступа для приложения \"<xliff:g id="APP_NAME">%1$s</xliff:g>\"?"</string>
-    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Использовать сохраненные учетные данные для приложения \"<xliff:g id="APP_NAME">%1$s</xliff:g>\"?"</string>
-    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Выберите сохраненные данные для приложения \"<xliff:g id="APP_NAME">%1$s</xliff:g>\""</string>
+    <string name="get_dialog_title_use_password_for" msgid="625828023234318484">"Использовать сохраненный пароль для приложения \"<xliff:g id="APP_NAME">%1$s</xliff:g>\"?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"Войти в приложение \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" с этими данными?"</string>
+    <string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"Разблокировать варианты входа для приложения \"<xliff:g id="APP_NAME">%1$s</xliff:g>\"?"</string>
+    <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"Выберите сохраненный ключ доступа для приложения \"<xliff:g id="APP_NAME">%1$s</xliff:g>\""</string>
+    <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"Выберите сохраненный пароль для приложения \"<xliff:g id="APP_NAME">%1$s</xliff:g>\""</string>
+    <string name="get_dialog_title_choose_saved_sign_in_for" msgid="2420298653461652728">"Выберите сохраненные учетные данные для приложения \"<xliff:g id="APP_NAME">%1$s</xliff:g>\""</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="3048870756117876514">"Как вы хотите войти в приложение \"<xliff:g id="APP_NAME">%1$s</xliff:g>\"?"</string>
+    <string name="get_dialog_title_choose_option_for" msgid="4976380044745029107">"Выберите данные для входа в приложение \"<xliff:g id="APP_NAME">%1$s</xliff:g>\""</string>
+    <string name="get_dialog_title_use_info_on" msgid="8863708099535435146">"Использовать эту информацию для входа в приложение \"<xliff:g id="APP_NAME">%1$s</xliff:g>\"?"</string>
     <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Войти другим способом"</string>
     <string name="snackbar_action" msgid="37373514216505085">"Показать варианты"</string>
     <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Продолжить"</string>
     <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Варианты входа"</string>
+    <string name="button_label_view_more" msgid="3429098227286495651">"Ещё"</string>
     <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Для пользователя <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Заблокированные менеджеры паролей"</string>
-    <!-- no translation found for locked_credential_entry_label_subtext_tap_to_unlock (6390367581393605009) -->
-    <skip />
-    <!-- no translation found for locked_credential_entry_label_subtext_no_sign_in (8131725029983174901) -->
-    <skip />
+    <string name="locked_credential_entry_label_subtext_tap_to_unlock" msgid="6390367581393605009">"Нажмите для разблокировки"</string>
+    <string name="locked_credential_entry_label_subtext_no_sign_in" msgid="8131725029983174901">"Необходимо ввести учетные данные"</string>
+    <string name="no_sign_in_info_in" msgid="2641118151920288356">"В аккаунте <xliff:g id="SOURCE">%1$s</xliff:g> нет учетных данных"</string>
     <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Управление входом"</string>
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"С другого устройства"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Использовать другое устройство"</string>
+    <string name="request_cancelled_by" msgid="3735222326886267820">"Приложение \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" отменило запрос."</string>
 </resources>
diff --git a/packages/CredentialManager/res/values-si/strings.xml b/packages/CredentialManager/res/values-si/strings.xml
index 3064d26..79eaa13 100644
--- a/packages/CredentialManager/res/values-si/strings.xml
+++ b/packages/CredentialManager/res/values-si/strings.xml
@@ -1,15 +1,29 @@
 <?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.
+   -->
+
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="4539824758261855508">"අක්තපත්‍ර කළමනාකරු"</string>
     <string name="string_cancel" msgid="6369133483981306063">"අවලංගු කරන්න"</string>
     <string name="string_continue" msgid="1346732695941131882">"ඉදිරියට යන්න"</string>
-    <string name="string_more_options" msgid="7990658711962795124">"තවත් විකල්ප"</string>
+    <string name="string_more_options" msgid="2763852250269945472">"වෙන මාර්ගයක සුරකින්න"</string>
     <string name="string_learn_more" msgid="4541600451688392447">"තව දැන ගන්න"</string>
-    <!-- no translation found for content_description_show_password (3283502010388521607) -->
-    <skip />
-    <!-- no translation found for content_description_hide_password (6841375971631767996) -->
-    <skip />
+    <string name="content_description_show_password" msgid="3283502010388521607">"මුරපදය පෙන්වන්න"</string>
+    <string name="content_description_hide_password" msgid="6841375971631767996">"මුරපදය සඟවන්න"</string>
     <string name="passkey_creation_intro_title" msgid="4251037543787718844">"මුරයතුරු සමග සුරක්ෂිතයි"</string>
     <string name="passkey_creation_intro_body_password" msgid="8825872426579958200">"මුරයතුරු සමග, ඔබට සංකීර්ණ මුරපද තැනීමට හෝ මතක තබා ගැනීමට අවශ්‍ය නොවේ"</string>
     <string name="passkey_creation_intro_body_fingerprint" msgid="7331338631826254055">"මුරයතුරු යනු ඔබේ ඇඟිලි සලකුණ, මුහුණ, හෝ තිර අගුල භාවිතයෙන් ඔබ තනන සංකේතාත්මක ඩිජිටල් යතුරු වේ"</string>
@@ -35,10 +49,13 @@
     <string name="sign_ins" msgid="4710739369149469208">"පුරනය වීම්"</string>
     <string name="sign_in_info" msgid="2627704710674232328">"පුරනය වීමේ තතු"</string>
     <string name="save_credential_to_title" msgid="3172811692275634301">"වෙත <xliff:g id="CREDENTIALTYPES">%1$s</xliff:g> සුරකින්න"</string>
-    <string name="create_passkey_in_other_device_title" msgid="9195411122362461390">"වෙනත් උපාංගයක මුරයතුර තනන්න ද?"</string>
+    <string name="create_passkey_in_other_device_title" msgid="2360053098931886245">"වෙනත් උපාංගයක මුරයතුර තනන්න ද?"</string>
+    <string name="save_password_on_other_device_title" msgid="5829084591948321207">"වෙනත් උපාංගයක මුරපදය සුරකින්න ද?"</string>
+    <string name="save_sign_in_on_other_device_title" msgid="2827990118560134692">"වෙනත් උපාංගයක පුරනය වීම සුරකින්න ද?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"ඔබේ සියලු පුරනය වීම් සඳහා <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> භාවිතා කරන්න ද?"</string>
-    <string name="use_provider_for_all_description" msgid="8466427781848268490">"මෙම මුරපද කළමනාකරු ඔබට පහසුවෙන් පුරනය වීමට උදවු කිරීම සඳහා ඔබේ මුරපද සහ මුරයතුරු ගබඩා කරනු ඇත"</string>
+    <string name="use_provider_for_all_description" msgid="1998772715863958997">"<xliff:g id="USERNAME">%1$s</xliff:g> සඳහා මෙම මුරපද කළමනාකරු ඔබට පහසුවෙන් පුරනය වීමට උදවු කිරීම සඳහා ඔබේ මුරපද සහ මුරයතුරු ගබඩා කරනු ඇත"</string>
     <string name="set_as_default" msgid="4415328591568654603">"පෙරනිමි ලෙස සකසන්න"</string>
+    <string name="settings" msgid="6536394145760913145">"සැකසීම්"</string>
     <string name="use_once" msgid="9027366575315399714">"වරක් භාවිතා කරන්න"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"මුරපද <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>ක් • මුරයතුරු <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>ක්"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"මුරපද <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>ක්"</string>
@@ -49,21 +66,30 @@
     <string name="other_password_manager" msgid="565790221427004141">"වෙනත් මුරපද කළමනාකරුවන්"</string>
     <string name="close_sheet" msgid="1393792015338908262">"පත්‍රය වසන්න"</string>
     <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"පෙර පිටුවට ආපසු යන්න"</string>
-    <string name="accessibility_close_button" msgid="2953807735590034688">"තිරයේ පහළින් දිස්වන අක්තපත්‍ර කළමනාකරු ක්‍රියා යෝජනාව වසන්න"</string>
+    <string name="accessibility_close_button" msgid="1163435587545377687">"වසන්න"</string>
+    <string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"අස් කරන්න"</string>
     <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"<xliff:g id="APP_NAME">%1$s</xliff:g> සඳහා ඔබේ සුරැකි මුරයතුර භාවිතා කරන්න ද?"</string>
-    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"<xliff:g id="APP_NAME">%1$s</xliff:g> සඳහා ඔබේ සුරැකි පුරනය භාවිතා කරන්න ද?"</string>
-    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"<xliff:g id="APP_NAME">%1$s</xliff:g> සඳහා සුරැකි පුරනයක් තෝරා ගන්න"</string>
+    <string name="get_dialog_title_use_password_for" msgid="625828023234318484">"<xliff:g id="APP_NAME">%1$s</xliff:g> සඳහා ඔබේ සුරැකි මුරපදය භාවිත කරන්න ද?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"<xliff:g id="APP_NAME">%1$s</xliff:g> සඳහා ඔබේ පුරනය වීම භාවිතා කරන්න ද?"</string>
+    <string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"<xliff:g id="APP_NAME">%1$s</xliff:g> සඳහා පුරන විකල්ප අගුලු හරින්න ද?"</string>
+    <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"<xliff:g id="APP_NAME">%1$s</xliff:g> සඳහා සුරකින ලද මුරයතුරක් තෝරන්න"</string>
+    <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"<xliff:g id="APP_NAME">%1$s</xliff:g> සඳහා සුරකින ලද මුරපදයක් තෝරන්න"</string>
+    <string name="get_dialog_title_choose_saved_sign_in_for" msgid="2420298653461652728">"<xliff:g id="APP_NAME">%1$s</xliff:g> සඳහා සුරැකි පුරනයක් තෝරා ගන්න"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="3048870756117876514">"<xliff:g id="APP_NAME">%1$s</xliff:g> සඳහා පුරනය වීමක් තෝරා ගන්න"</string>
+    <string name="get_dialog_title_choose_option_for" msgid="4976380044745029107">"<xliff:g id="APP_NAME">%1$s</xliff:g> සඳහා විකල්පයක් තෝරන්නද?"</string>
+    <string name="get_dialog_title_use_info_on" msgid="8863708099535435146">"<xliff:g id="APP_NAME">%1$s</xliff:g> මත මෙම තතු භාවිතා කරන්න ද?"</string>
     <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"වෙනත් ආකාරයකින් පුරන්න"</string>
     <string name="snackbar_action" msgid="37373514216505085">"විකල්ප බලන්න"</string>
     <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"ඉදිරියට යන්න"</string>
     <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"පුරනය වීමේ විකල්ප"</string>
+    <string name="button_label_view_more" msgid="3429098227286495651">"වැඩියෙන් දකින්න"</string>
     <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"<xliff:g id="USERNAME">%1$s</xliff:g> සඳහා"</string>
     <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"අගුළු දැමූ මුරපද කළමනාකරුවන්"</string>
-    <!-- no translation found for locked_credential_entry_label_subtext_tap_to_unlock (6390367581393605009) -->
-    <skip />
-    <!-- no translation found for locked_credential_entry_label_subtext_no_sign_in (8131725029983174901) -->
-    <skip />
+    <string name="locked_credential_entry_label_subtext_tap_to_unlock" msgid="6390367581393605009">"අගුළු හැරීමට තට්ටු කරන්න"</string>
+    <string name="locked_credential_entry_label_subtext_no_sign_in" msgid="8131725029983174901">"පුරනය වීමේ තතු නැත"</string>
+    <string name="no_sign_in_info_in" msgid="2641118151920288356">"<xliff:g id="SOURCE">%1$s</xliff:g> තුළ පුරනය වීමේ තතු නැත"</string>
     <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"පුරනය වීම් කළමනාකරණය කරන්න"</string>
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"වෙනත් උපාංගයකින්"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"වෙනස් උපාංගයක් භාවිතා කරන්න"</string>
+    <string name="request_cancelled_by" msgid="3735222326886267820">"<xliff:g id="APP_NAME">%1$s</xliff:g> විසින් ඉල්ලීම අවලංගු කරන ලදී"</string>
 </resources>
diff --git a/packages/CredentialManager/res/values-sk/strings.xml b/packages/CredentialManager/res/values-sk/strings.xml
index b7c3d2e..8168b51 100644
--- a/packages/CredentialManager/res/values-sk/strings.xml
+++ b/packages/CredentialManager/res/values-sk/strings.xml
@@ -1,22 +1,38 @@
 <?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.
+   -->
+
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="4539824758261855508">"Správca prihlasovacích údajov"</string>
     <string name="string_cancel" msgid="6369133483981306063">"Zrušiť"</string>
     <string name="string_continue" msgid="1346732695941131882">"Pokračovať"</string>
-    <string name="string_more_options" msgid="7990658711962795124">"Ďalšie možnosti"</string>
+    <string name="string_more_options" msgid="2763852250269945472">"Uložiť inak"</string>
     <string name="string_learn_more" msgid="4541600451688392447">"Ďalšie informácie"</string>
     <string name="content_description_show_password" msgid="3283502010388521607">"Zobraziť heslo"</string>
     <string name="content_description_hide_password" msgid="6841375971631767996">"Skryť heslo"</string>
     <string name="passkey_creation_intro_title" msgid="4251037543787718844">"Bezpečnejšie s prístupovými kľúčmi"</string>
     <string name="passkey_creation_intro_body_password" msgid="8825872426579958200">"Ak máte prístupové kľúče, nemusíte vytvárať ani si pamätať zložité heslá"</string>
-    <string name="passkey_creation_intro_body_fingerprint" msgid="7331338631826254055">"Prístupové kľúče sú šifrované digitálne kľúče, ktoré môžete vytvoriť odtlačkom prsta, tvárou alebo zámkou obrazovky."</string>
+    <string name="passkey_creation_intro_body_fingerprint" msgid="7331338631826254055">"Prístupové kľúče sú šifrované digitálne kľúče, ktoré môžete vytvoriť odtlačkom prsta, tvárou alebo zámkou obrazovky"</string>
     <string name="passkey_creation_intro_body_device" msgid="1203796455762131631">"Ukladajú sa do správcu hesiel, aby ste sa mohli prihlasovať v iných zariadeniach"</string>
     <string name="more_about_passkeys_title" msgid="7797903098728837795">"Viac o prístupových kľúčoch"</string>
     <string name="passwordless_technology_title" msgid="2497513482056606668">"Technológia bez hesiel"</string>
-    <string name="passwordless_technology_detail" msgid="6853928846532955882">"Prístupový kľúče vám umožňujú prihlásiť sa bez využitia hesiel. Stačí overiť totožnosť odtlačkom prsta, rozpoznávaním tváre, kódom PIN alebo vzorom potiahnutia a vytvoriť prístupový kľúč."</string>
+    <string name="passwordless_technology_detail" msgid="6853928846532955882">"Prístupové kľúče vám umožňujú prihlásiť sa bez použitia hesiel. Stačí overiť totožnosť odtlačkom prsta, rozpoznávaním tváre, kódom PIN alebo vzorom potiahnutia a vytvoriť prístupový kľúč."</string>
     <string name="public_key_cryptography_title" msgid="6751970819265298039">"Kryptografia verejných kľúčov"</string>
-    <string name="public_key_cryptography_detail" msgid="6937631710280562213">"Prístup. kľúče firiem patriacich do združenia FIDO (zahŕňajúceho Google, Apple, Microsoft a ďalšie) využívajú páry kryptograf. kľúčov a štandardy W3C. Na rozdiel od použív. mena a reťazca znakov využívaných v prípade hesiel sa pár súkr. a verej. kľúča vytvára pre aplikáciu alebo web. Súkr. kľúč sa bezpečne ukladá v zar. či správcovi hesiel a slúži na overenie totožnosti. Verej. kľúč sa zdieľa so serverom danej aplik. alebo webu. Príslušnými kľúčami sa môžete okamžite registrovať a prihlasovať."</string>
+    <string name="public_key_cryptography_detail" msgid="6937631710280562213">"Prístupové kľúče založené na štandardoch W3C a aliancie FIDO (do ktorej patria Google, Apple, Microsoft a ďalší) používajú páry kryptografických kľúčov. Na rozdiel od používateľského mena a hesla sa pre každú aplikáciu alebo web vytvára jeden pár kľúčov (súkromný a verejný). Súkromný kľúč, bezpečne uložený v zariadení alebo v správcovi hesiel, potvrdzuje vašu identitu. Verejný kľúč sa zdieľa so serverom aplikácie alebo stránky. Vďaka zhodným kľúčom je registrácia a prihlásenie okamžité."</string>
     <string name="improved_account_security_title" msgid="1069841917893513424">"Lepšie zabezpečenie účtu"</string>
     <string name="improved_account_security_detail" msgid="9123750251551844860">"Každý kľúč je výhradne prepojený s aplikáciou alebo webom, pre ktorý bol vytvorený, takže sa nikdy nemôžete omylom prihlásiť do podvodnej aplikácie alebo na podvodnom webe. Servery navyše uchovávajú iba verejné kľúče, čím podstatne sťažujú hackovanie."</string>
     <string name="seamless_transition_title" msgid="5335622196351371961">"Plynulý prechod"</string>
@@ -33,10 +49,13 @@
     <string name="sign_ins" msgid="4710739369149469208">"prihlasovacie údaje"</string>
     <string name="sign_in_info" msgid="2627704710674232328">"prihlasovacie údaje"</string>
     <string name="save_credential_to_title" msgid="3172811692275634301">"Uložiť <xliff:g id="CREDENTIALTYPES">%1$s</xliff:g> do"</string>
-    <string name="create_passkey_in_other_device_title" msgid="9195411122362461390">"Chcete vytvoriť prístupový kľúč v inom zariadení?"</string>
+    <string name="create_passkey_in_other_device_title" msgid="2360053098931886245">"Chcete vytvoriť prístupový kľúč v inom zariadení?"</string>
+    <string name="save_password_on_other_device_title" msgid="5829084591948321207">"Chcete uložiť heslo v inom zariadení?"</string>
+    <string name="save_sign_in_on_other_device_title" msgid="2827990118560134692">"Chcete uložiť prihlasovacie údaje v inom zariadení?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"Chcete pre všetky svoje prihlasovacie údaje použiť <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
-    <string name="use_provider_for_all_description" msgid="8466427781848268490">"Tento správca hesiel uchová vaše heslá a prístupové kľúče, aby vám pomohol ľahšie sa prihlasovať"</string>
+    <string name="use_provider_for_all_description" msgid="1998772715863958997">"Tento správca hesiel poskytovateľa <xliff:g id="USERNAME">%1$s</xliff:g> uchová vaše heslá a prístupové kľúče, aby vám pomohol ľahšie sa prihlasovať"</string>
     <string name="set_as_default" msgid="4415328591568654603">"Nastaviť ako predvolené"</string>
+    <string name="settings" msgid="6536394145760913145">"Nastavenia"</string>
     <string name="use_once" msgid="9027366575315399714">"Použiť raz"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"Počet hesiel: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> • Počet prístupových kľúčov: <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"Počet hesiel: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>"</string>
@@ -47,21 +66,30 @@
     <string name="other_password_manager" msgid="565790221427004141">"Iní správcovia hesiel"</string>
     <string name="close_sheet" msgid="1393792015338908262">"Zavrieť hárok"</string>
     <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Prejsť späť na predchádzajúcu stránku"</string>
-    <string name="accessibility_close_button" msgid="2953807735590034688">"Zavrieť návrh akcie správcu prihlasovacích údajov dole na obrazovke"</string>
+    <string name="accessibility_close_button" msgid="1163435587545377687">"Zavrieť"</string>
+    <string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"Zavrieť"</string>
     <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Chcete pre aplikáciu <xliff:g id="APP_NAME">%1$s</xliff:g> použiť uložený prístupový kľúč?"</string>
-    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Chcete pre aplikáciu <xliff:g id="APP_NAME">%1$s</xliff:g> použiť uložené prihlasovacie údaje?"</string>
-    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Vyberte uložené prihlasovacie údaje pre aplikáciu <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
-    <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Prihláste sa inak"</string>
+    <string name="get_dialog_title_use_password_for" msgid="625828023234318484">"Chcete použiť uložené heslo aplikácie <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"Chcete použiť svoje prihlásenie pre aplikáciu <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"Chcete odomknúť možnosti prihlásenia pre aplikáciu <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"Vyberte uložený prístupový kľúč pre aplikáciu <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"Vyberte uložené heslo pre aplikáciu <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_saved_sign_in_for" msgid="2420298653461652728">"Vyberte uložené prihlasovacie údaje pre aplikáciu <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="3048870756117876514">"Vyberte prihlásenie pre aplikáciu <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_option_for" msgid="4976380044745029107">"Chcete pre aplikáciu <xliff:g id="APP_NAME">%1$s</xliff:g> vybrať možnosť?"</string>
+    <string name="get_dialog_title_use_info_on" msgid="8863708099535435146">"Chcete použiť tieto informácie v aplikácii <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Prihlásiť sa inak"</string>
     <string name="snackbar_action" msgid="37373514216505085">"Zobraziť možnosti"</string>
     <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Pokračovať"</string>
     <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Možnosti prihlásenia"</string>
+    <string name="button_label_view_more" msgid="3429098227286495651">"Zobraziť viac"</string>
     <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Pre používateľa <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Správcovia uzamknutých hesiel"</string>
-    <!-- no translation found for locked_credential_entry_label_subtext_tap_to_unlock (6390367581393605009) -->
-    <skip />
-    <!-- no translation found for locked_credential_entry_label_subtext_no_sign_in (8131725029983174901) -->
-    <skip />
+    <string name="locked_credential_entry_label_subtext_tap_to_unlock" msgid="6390367581393605009">"Odomknúť klepnutím"</string>
+    <string name="locked_credential_entry_label_subtext_no_sign_in" msgid="8131725029983174901">"Žiadne prihlasovacie údaje"</string>
+    <string name="no_sign_in_info_in" msgid="2641118151920288356">"Žiadne prihlasovacie údaje v zdroji <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Spravovať prihlasovacie údaje"</string>
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Z iného zariadenia"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Použiť iné zariadenie"</string>
+    <string name="request_cancelled_by" msgid="3735222326886267820">"Požiadavku zrušila aplikácia <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
 </resources>
diff --git a/packages/CredentialManager/res/values-sl/strings.xml b/packages/CredentialManager/res/values-sl/strings.xml
index 3d70707..16ba222 100644
--- a/packages/CredentialManager/res/values-sl/strings.xml
+++ b/packages/CredentialManager/res/values-sl/strings.xml
@@ -1,15 +1,29 @@
 <?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.
+   -->
+
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="4539824758261855508">"Upravitelj poverilnic"</string>
     <string name="string_cancel" msgid="6369133483981306063">"Prekliči"</string>
     <string name="string_continue" msgid="1346732695941131882">"Naprej"</string>
-    <string name="string_more_options" msgid="7990658711962795124">"Več možnosti"</string>
+    <string name="string_more_options" msgid="2763852250269945472">"Shrani na drug način"</string>
     <string name="string_learn_more" msgid="4541600451688392447">"Več o tem"</string>
-    <!-- no translation found for content_description_show_password (3283502010388521607) -->
-    <skip />
-    <!-- no translation found for content_description_hide_password (6841375971631767996) -->
-    <skip />
+    <string name="content_description_show_password" msgid="3283502010388521607">"Prikaz gesla"</string>
+    <string name="content_description_hide_password" msgid="6841375971631767996">"Skrivanje gesla"</string>
     <string name="passkey_creation_intro_title" msgid="4251037543787718844">"Večja varnost s ključi za dostop"</string>
     <string name="passkey_creation_intro_body_password" msgid="8825872426579958200">"Po zaslugi ključev za dostop vam ni treba ustvarjati ali si zapomniti zapletenih gesel."</string>
     <string name="passkey_creation_intro_body_fingerprint" msgid="7331338631826254055">"Ključi za dostop so šifrirani digitalni ključi, ki jih ustvarite s prstnim odtisom, obrazom ali načinom zaklepanja zaslona."</string>
@@ -30,15 +44,18 @@
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Želite shraniti podatke za prijavo za aplikacijo <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="passkey" msgid="632353688396759522">"ključ za dostop"</string>
     <string name="password" msgid="6738570945182936667">"geslo"</string>
-    <string name="passkeys" msgid="5733880786866559847">"ključev za dostop"</string>
+    <string name="passkeys" msgid="5733880786866559847">"ključi za dostop"</string>
     <string name="passwords" msgid="5419394230391253816">"gesel"</string>
     <string name="sign_ins" msgid="4710739369149469208">"prijave"</string>
     <string name="sign_in_info" msgid="2627704710674232328">"podatkov za prijavo"</string>
-    <string name="save_credential_to_title" msgid="3172811692275634301">"Mesto shranjevanja <xliff:g id="CREDENTIALTYPES">%1$s</xliff:g>"</string>
-    <string name="create_passkey_in_other_device_title" msgid="9195411122362461390">"Želite ustvariti ključ za dostop v drugi napravi?"</string>
+    <string name="save_credential_to_title" msgid="3172811692275634301">"Mesto shranjevanja: <xliff:g id="CREDENTIALTYPES">%1$s</xliff:g>"</string>
+    <string name="create_passkey_in_other_device_title" msgid="2360053098931886245">"Želite ustvariti ključ za dostop v drugi napravi?"</string>
+    <string name="save_password_on_other_device_title" msgid="5829084591948321207">"Želite shraniti geslo v drugi napravi?"</string>
+    <string name="save_sign_in_on_other_device_title" msgid="2827990118560134692">"Želite shraniti prijavo v drugi napravi?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"Želite za vse prijave uporabiti »<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>«?"</string>
-    <string name="use_provider_for_all_description" msgid="8466427781848268490">"V tem upravitelju gesel bodo shranjeni gesla in ključi za dostop, kar vam bo olajšalo prijavo."</string>
+    <string name="use_provider_for_all_description" msgid="1998772715863958997">"V tem upravitelju gesel za uporabnika <xliff:g id="USERNAME">%1$s</xliff:g> bodo shranjeni gesla in ključi za dostop, kar vam bo olajšalo prijavo."</string>
     <string name="set_as_default" msgid="4415328591568654603">"Nastavi kot privzeto"</string>
+    <string name="settings" msgid="6536394145760913145">"Nastavitve"</string>
     <string name="use_once" msgid="9027366575315399714">"Uporabi enkrat"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"Št. gesel: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> • Št. ključev za dostop: <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"Št. gesel: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>"</string>
@@ -49,21 +66,30 @@
     <string name="other_password_manager" msgid="565790221427004141">"Drugi upravitelji gesel"</string>
     <string name="close_sheet" msgid="1393792015338908262">"Zapri list"</string>
     <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Nazaj na prejšnjo stran"</string>
-    <string name="accessibility_close_button" msgid="2953807735590034688">"Zaprtje na dnu zaslona prikazanega predloga dejanja upravitelja poverilnic"</string>
+    <string name="accessibility_close_button" msgid="1163435587545377687">"Zapri"</string>
+    <string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"Opusti"</string>
     <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Želite uporabiti shranjeni ključ za dostop do aplikacije <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
-    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Želite uporabiti shranjene podatke za prijavo v aplikacijo <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
-    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Izberite shranjene podatke za prijavo v aplikacijo <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_use_password_for" msgid="625828023234318484">"Ali želite uporabiti shranjeno geslo za aplikacijo <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"Želite uporabiti svojo prijavo za aplikacijo <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"Želite odkleniti možnosti prijave za aplikacijo <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"Izberite shranjeni ključ za dostop za aplikacijo <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"Izberite shranjeno geslo za aplikacijo <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_saved_sign_in_for" msgid="2420298653461652728">"Izberite shranjene podatke za prijavo za aplikacijo <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="3048870756117876514">"Izberite prijavo za aplikacijo <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_option_for" msgid="4976380044745029107">"Izberite možnost za aplikacijo <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_use_info_on" msgid="8863708099535435146">"Želite te podatke uporabiti v aplikaciji <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Prijava na drug način"</string>
     <string name="snackbar_action" msgid="37373514216505085">"Prikaz možnosti"</string>
     <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Naprej"</string>
     <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Možnosti prijave"</string>
+    <string name="button_label_view_more" msgid="3429098227286495651">"Prikaži več"</string>
     <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Za uporabnika <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Zaklenjeni upravitelji gesel"</string>
-    <!-- no translation found for locked_credential_entry_label_subtext_tap_to_unlock (6390367581393605009) -->
-    <skip />
-    <!-- no translation found for locked_credential_entry_label_subtext_no_sign_in (8131725029983174901) -->
-    <skip />
+    <string name="locked_credential_entry_label_subtext_tap_to_unlock" msgid="6390367581393605009">"Dotaknite se, da odklenete"</string>
+    <string name="locked_credential_entry_label_subtext_no_sign_in" msgid="8131725029983174901">"Ni nobenih podatkov za prijavo"</string>
+    <string name="no_sign_in_info_in" msgid="2641118151920288356">"Ni nobenih podatkov za prijavo v »<xliff:g id="SOURCE">%1$s</xliff:g>«"</string>
     <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Upravljanje podatkov za prijavo"</string>
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Iz druge naprave"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Uporaba druge naprave"</string>
+    <string name="request_cancelled_by" msgid="3735222326886267820">"Zahtevo je preklicala aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
 </resources>
diff --git a/packages/CredentialManager/res/values-sq/strings.xml b/packages/CredentialManager/res/values-sq/strings.xml
index 4b7ff96..40f8dc4 100644
--- a/packages/CredentialManager/res/values-sq/strings.xml
+++ b/packages/CredentialManager/res/values-sq/strings.xml
@@ -1,17 +1,33 @@
 <?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.
+   -->
+
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="4539824758261855508">"Menaxheri i kredencialeve"</string>
     <string name="string_cancel" msgid="6369133483981306063">"Anulo"</string>
     <string name="string_continue" msgid="1346732695941131882">"Vazhdo"</string>
-    <string name="string_more_options" msgid="7990658711962795124">"Opsione të tjera"</string>
+    <string name="string_more_options" msgid="2763852250269945472">"Ruaje ndryshe"</string>
     <string name="string_learn_more" msgid="4541600451688392447">"Mëso më shumë"</string>
     <string name="content_description_show_password" msgid="3283502010388521607">"Shfaq fjalëkalimin"</string>
     <string name="content_description_hide_password" msgid="6841375971631767996">"Fshih fjalëkalimin"</string>
     <string name="passkey_creation_intro_title" msgid="4251037543787718844">"Më e sigurt me çelësat e kalimit"</string>
     <string name="passkey_creation_intro_body_password" msgid="8825872426579958200">"Me çelësat e kalimit, nuk ka nevojë të krijosh ose të mbash mend fjalëkalime të ndërlikuara"</string>
-    <string name="passkey_creation_intro_body_fingerprint" msgid="7331338631826254055">"Çelësat e kalimit kanë çelësa dixhitalë të enkriptuar që ti i krijon duke përdorur gjurmën e gishtit, fytyrën ose kyçjen e ekranit"</string>
-    <string name="passkey_creation_intro_body_device" msgid="1203796455762131631">"Ata ruhen te një menaxher fjalëkalimesh, në mënyrë që mund të identifikohesh në pajisje të tjera"</string>
+    <string name="passkey_creation_intro_body_fingerprint" msgid="7331338631826254055">"Çelësat e kalimit janë çelësa dixhitalë të enkriptuar që ti i krijon duke përdorur gjurmën e gishtit, fytyrën ose kyçjen e ekranit"</string>
+    <string name="passkey_creation_intro_body_device" msgid="1203796455762131631">"Ata ruhen te një menaxher fjalëkalimesh, në mënyrë që të mund të identifikohesh në pajisje të tjera"</string>
     <string name="more_about_passkeys_title" msgid="7797903098728837795">"Më shumë rreth çelësave të kalimit"</string>
     <string name="passwordless_technology_title" msgid="2497513482056606668">"Teknologji pa fjalëkalime"</string>
     <string name="passwordless_technology_detail" msgid="6853928846532955882">"Çelësat e kalimit të lejojnë të identifikohesh pa u mbështetur te fjalëkalimet. Të duhet vetëm të përdorësh gjurmën e gishtit, njohjen e fytyrës, PIN-in ose të rrëshqasësh motivin për të verifikuar identitetin dhe për të krijuar një çelës kalimi."</string>
@@ -21,22 +37,25 @@
     <string name="improved_account_security_detail" msgid="9123750251551844860">"Secili çelës është i lidhur ekskluzivisht me aplikacionin ose sajtin e uebit për të cilin është krijuar, kështu që nuk do të identifikohesh asnjëherë gabimisht në një aplikacion ose sajt uebi mashtrues. Gjithashtu, me serverët që mbajnë vetëm çelësa publikë, pirateria informatike është shumë më e vështirë."</string>
     <string name="seamless_transition_title" msgid="5335622196351371961">"Kalim i thjeshtuar"</string>
     <string name="seamless_transition_detail" msgid="4475509237171739843">"Teksa shkojmë drejt një të ardhmeje pa fjalëkalime, këto të fundit do të ofrohen ende së bashku me çelësat e kalimit."</string>
-    <string name="choose_provider_title" msgid="8870795677024868108">"Zgjidh se ku të ruash <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <string name="choose_provider_title" msgid="8870795677024868108">"Zgjidh se ku t\'i ruash <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"Zgjidh një menaxher fjalëkalimesh për të ruajtur informacionet e tua dhe për t\'u identifikuar më shpejt herën tjetër"</string>
     <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"Të krijohet çelësi i kalimit për <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_password_title" msgid="7097275038523578687">"Të ruhet fjalëkalimi për <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Të ruhen informacionet e identifikimit për <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
-    <string name="passkey" msgid="632353688396759522">"çelësi i kalimit"</string>
+    <string name="passkey" msgid="632353688396759522">"çelësin e kalimit"</string>
     <string name="password" msgid="6738570945182936667">"fjalëkalimi"</string>
-    <string name="passkeys" msgid="5733880786866559847">"çelësa kalimi"</string>
+    <string name="passkeys" msgid="5733880786866559847">"çelësat e kalimit"</string>
     <string name="passwords" msgid="5419394230391253816">"fjalëkalime"</string>
     <string name="sign_ins" msgid="4710739369149469208">"identifikimet"</string>
     <string name="sign_in_info" msgid="2627704710674232328">"informacionet e identifikimit"</string>
     <string name="save_credential_to_title" msgid="3172811692275634301">"Ruaj <xliff:g id="CREDENTIALTYPES">%1$s</xliff:g> te"</string>
-    <string name="create_passkey_in_other_device_title" msgid="9195411122362461390">"Të krijohet çelës kalimi në një pajisje tjetër?"</string>
+    <string name="create_passkey_in_other_device_title" msgid="2360053098931886245">"Të krijohet çelësi i kalimit në një pajisje tjetër?"</string>
+    <string name="save_password_on_other_device_title" msgid="5829084591948321207">"Të ruhet fjalëkalimi në një pajisje tjetër?"</string>
+    <string name="save_sign_in_on_other_device_title" msgid="2827990118560134692">"Të ruhet identifikimi në një pajisje tjetër?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"Të përdoret <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> për të gjitha identifikimet?"</string>
-    <string name="use_provider_for_all_description" msgid="8466427781848268490">"Ky menaxher i fjalëkalimeve do të ruajë fjalëkalimet dhe çelësat e kalimit për të të ndihmuar të identifikohesh me lehtësi"</string>
+    <string name="use_provider_for_all_description" msgid="1998772715863958997">"Ky menaxher i fjalëkalimeve për <xliff:g id="USERNAME">%1$s</xliff:g> do të ruajë fjalëkalimet dhe çelësat e kalimit për të të ndihmuar të identifikohesh me lehtësi"</string>
     <string name="set_as_default" msgid="4415328591568654603">"Cakto si parazgjedhje"</string>
+    <string name="settings" msgid="6536394145760913145">"Cilësimet"</string>
     <string name="use_once" msgid="9027366575315399714">"Përdor një herë"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> fjalëkalime • <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> çelësa kalimi"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> fjalëkalime"</string>
@@ -47,21 +66,30 @@
     <string name="other_password_manager" msgid="565790221427004141">"Menaxherët e tjerë të fjalëkalimeve"</string>
     <string name="close_sheet" msgid="1393792015338908262">"Mbyll fletën"</string>
     <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Kthehu te faqja e mëparshme"</string>
-    <string name="accessibility_close_button" msgid="2953807735590034688">"Mbyll sugjerimin për veprimin e \"Menaxherit të kredencialeve\" që shfaqet në fund të ekranit"</string>
+    <string name="accessibility_close_button" msgid="1163435587545377687">"Mbyll"</string>
+    <string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"Hiq"</string>
     <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Të përdoret fjalëkalimi yt i ruajtur për <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
-    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Të përdoret identifikimi yt i ruajtur për <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
-    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Zgjidh një identifikim të ruajtur për <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_use_password_for" msgid="625828023234318484">"Të përdoret fjalëkalimi i ruajtur për <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"Të përdoret identifikimi yt për <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"Të shkyçen opsionet e identifikimit për <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"Zgjidh një çelës kalimi të ruajtur për <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"Zgjidh një fjalëkalim të ruajtur për <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_saved_sign_in_for" msgid="2420298653461652728">"Zgjidh një identifikim të ruajtur për <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="3048870756117876514">"Zgjidh një identifikim për <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_option_for" msgid="4976380044745029107">"Të zgjidhet një opsion për <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_info_on" msgid="8863708099535435146">"Të përdoren këto informacione në <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Identifikohu me një mënyrë tjetër"</string>
     <string name="snackbar_action" msgid="37373514216505085">"Shiko opsionet"</string>
     <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Vazhdo"</string>
     <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Opsionet e identifikimit"</string>
+    <string name="button_label_view_more" msgid="3429098227286495651">"Shiko më shumë"</string>
     <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Për <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Menaxherët e fjalëkalimeve të kyçura"</string>
-    <!-- no translation found for locked_credential_entry_label_subtext_tap_to_unlock (6390367581393605009) -->
-    <skip />
-    <!-- no translation found for locked_credential_entry_label_subtext_no_sign_in (8131725029983174901) -->
-    <skip />
+    <string name="locked_credential_entry_label_subtext_tap_to_unlock" msgid="6390367581393605009">"Trokit për të shkyçur"</string>
+    <string name="locked_credential_entry_label_subtext_no_sign_in" msgid="8131725029983174901">"Nuk ka informacione për identifikimin"</string>
+    <string name="no_sign_in_info_in" msgid="2641118151920288356">"Nuk ka informacione regjistrimi në <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Identifikimet e menaxhimit"</string>
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Nga një pajisje tjetër"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Përdor një pajisje tjetër"</string>
+    <string name="request_cancelled_by" msgid="3735222326886267820">"Kërkesa u anulua nga <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
 </resources>
diff --git a/packages/CredentialManager/res/values-sr/strings.xml b/packages/CredentialManager/res/values-sr/strings.xml
index 6cd5d8d..f5a29a2 100644
--- a/packages/CredentialManager/res/values-sr/strings.xml
+++ b/packages/CredentialManager/res/values-sr/strings.xml
@@ -1,15 +1,29 @@
 <?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.
+   -->
+
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="4539824758261855508">"Менаџер акредитива"</string>
     <string name="string_cancel" msgid="6369133483981306063">"Откажи"</string>
     <string name="string_continue" msgid="1346732695941131882">"Настави"</string>
-    <string name="string_more_options" msgid="7990658711962795124">"Још опција"</string>
+    <string name="string_more_options" msgid="2763852250269945472">"Сачувај други начин"</string>
     <string name="string_learn_more" msgid="4541600451688392447">"Сазнајте више"</string>
-    <!-- no translation found for content_description_show_password (3283502010388521607) -->
-    <skip />
-    <!-- no translation found for content_description_hide_password (6841375971631767996) -->
-    <skip />
+    <string name="content_description_show_password" msgid="3283502010388521607">"Прикажите лозинку"</string>
+    <string name="content_description_hide_password" msgid="6841375971631767996">"Сакријте лозинку"</string>
     <string name="passkey_creation_intro_title" msgid="4251037543787718844">"Безбедније уз приступне кодове"</string>
     <string name="passkey_creation_intro_body_password" msgid="8825872426579958200">"Уз приступне кодове нема потребе да правите или памтите сложене лозинке"</string>
     <string name="passkey_creation_intro_body_fingerprint" msgid="7331338631826254055">"Приступни кодови су шифровани дигитални кодови које правите помоћу отиска прста, лица или закључавања екрана"</string>
@@ -23,7 +37,7 @@
     <string name="improved_account_security_detail" msgid="9123750251551844860">"Сваки кључ је искључиво повезан са апликацијом или веб-сајтом за које је направљен, па никад не можете грешком да се пријавите у апликацију или на веб-сајт који служе за превару. Осим тога, са серверима који чувају само јавне кључеве хаковање је много теже."</string>
     <string name="seamless_transition_title" msgid="5335622196351371961">"Беспрекоран прелаз"</string>
     <string name="seamless_transition_detail" msgid="4475509237171739843">"Како се крећемо ка будућности без лозинки, лозинке ће и даље бити доступне уз приступне кодове."</string>
-    <string name="choose_provider_title" msgid="8870795677024868108">"Одаберите где ћете сачувати ставке <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <string name="choose_provider_title" msgid="8870795677024868108">"Одаберите где ћете сачувати: <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"Изаберите менаџера лозинки да бисте сачували податке и брже се пријавили следећи пут"</string>
     <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"Желите да направите приступни кôд за: <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_password_title" msgid="7097275038523578687">"Желите да сачувате лозинку за: <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
@@ -34,11 +48,14 @@
     <string name="passwords" msgid="5419394230391253816">"лозинке"</string>
     <string name="sign_ins" msgid="4710739369149469208">"пријављивања"</string>
     <string name="sign_in_info" msgid="2627704710674232328">"подаци за пријављивање"</string>
-    <string name="save_credential_to_title" msgid="3172811692275634301">"Сачувајте ставку<xliff:g id="CREDENTIALTYPES">%1$s</xliff:g> у"</string>
-    <string name="create_passkey_in_other_device_title" msgid="9195411122362461390">"Желите да направите приступни кôд на другом уређају?"</string>
+    <string name="save_credential_to_title" msgid="3172811692275634301">"Сачувај <xliff:g id="CREDENTIALTYPES">%1$s</xliff:g> у"</string>
+    <string name="create_passkey_in_other_device_title" msgid="2360053098931886245">"Желите да направите приступни кôд на другом уређају?"</string>
+    <string name="save_password_on_other_device_title" msgid="5829084591948321207">"Желите да сачувате лозинку на другом уређају?"</string>
+    <string name="save_sign_in_on_other_device_title" msgid="2827990118560134692">"Желите да сачувате акредитиве за пријаву на другом уређају?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"Желите да за сва пријављивања користите: <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
-    <string name="use_provider_for_all_description" msgid="8466427781848268490">"Овај менаџер лозинки ће чувати лозинке и приступне кодове да бисте се лако пријављивали"</string>
+    <string name="use_provider_for_all_description" msgid="1998772715863958997">"Овај менаџер лозинки за <xliff:g id="USERNAME">%1$s</xliff:g> ће чувати лозинке и приступне кодове да бисте се лако пријављивали"</string>
     <string name="set_as_default" msgid="4415328591568654603">"Подеси као подразумевано"</string>
+    <string name="settings" msgid="6536394145760913145">"Подешавања"</string>
     <string name="use_once" msgid="9027366575315399714">"Користи једном"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"Лозинки: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> • Приступних кодова:<xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"Лозинки: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>"</string>
@@ -49,21 +66,30 @@
     <string name="other_password_manager" msgid="565790221427004141">"Други менаџери лозинки"</string>
     <string name="close_sheet" msgid="1393792015338908262">"Затворите табелу"</string>
     <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Вратите се на претходну страницу"</string>
-    <string name="accessibility_close_button" msgid="2953807735590034688">"Затворите предлог за радњу Менаџера акредитива који се приказује у дну екрана"</string>
+    <string name="accessibility_close_button" msgid="1163435587545377687">"Затворите"</string>
+    <string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"Одбаци"</string>
     <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Желите да користите сачувани приступни кôд за: <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
-    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Желите да користите сачуване податке за пријављивање за: <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
-    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Одаберите сачувано пријављивање за: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_use_password_for" msgid="625828023234318484">"Желите да користите сачувану лозинку за: <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"Желите ли да користите своје податке за пријављивање за апликацију <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"Желите да откључате опције пријављивања за: <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"Изаберите сачуван приступни кôд за: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"Изаберите сачувану лозинку за: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_saved_sign_in_for" msgid="2420298653461652728">"Изаберите сачуване податке за пријављивање за: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="3048870756117876514">"Одаберите податке за пријављивање за апликацију <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_option_for" msgid="4976380044745029107">"Желите да одаберете опцију за апликацију <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_info_on" msgid="8863708099535435146">"Желите да користите те податке у апликацији <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Пријавите се на други начин"</string>
     <string name="snackbar_action" msgid="37373514216505085">"Прикажи опције"</string>
     <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Настави"</string>
     <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Опције за пријављивање"</string>
+    <string name="button_label_view_more" msgid="3429098227286495651">"Прикажи још"</string>
     <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"За: <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Менаџери закључаних лозинки"</string>
-    <!-- no translation found for locked_credential_entry_label_subtext_tap_to_unlock (6390367581393605009) -->
-    <skip />
-    <!-- no translation found for locked_credential_entry_label_subtext_no_sign_in (8131725029983174901) -->
-    <skip />
+    <string name="locked_credential_entry_label_subtext_tap_to_unlock" msgid="6390367581393605009">"Додирните да бисте откључали"</string>
+    <string name="locked_credential_entry_label_subtext_no_sign_in" msgid="8131725029983174901">"Нема података за пријављивање"</string>
+    <string name="no_sign_in_info_in" msgid="2641118151920288356">"Нема података за пријављивање у: <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Управљајте пријављивањима"</string>
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Са другог уређаја"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Користи други уређај"</string>
+    <string name="request_cancelled_by" msgid="3735222326886267820">"Захтве је отказала апликација <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
 </resources>
diff --git a/packages/CredentialManager/res/values-sv/strings.xml b/packages/CredentialManager/res/values-sv/strings.xml
index e061c9b..65319b0 100644
--- a/packages/CredentialManager/res/values-sv/strings.xml
+++ b/packages/CredentialManager/res/values-sv/strings.xml
@@ -1,17 +1,31 @@
 <?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.
+   -->
+
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="4539824758261855508">"Credential Manager"</string>
     <string name="string_cancel" msgid="6369133483981306063">"Avbryt"</string>
     <string name="string_continue" msgid="1346732695941131882">"Fortsätt"</string>
-    <string name="string_more_options" msgid="7990658711962795124">"Fler alternativ"</string>
+    <string name="string_more_options" msgid="2763852250269945472">"Spara på ett annat sätt"</string>
     <string name="string_learn_more" msgid="4541600451688392447">"Läs mer"</string>
-    <!-- no translation found for content_description_show_password (3283502010388521607) -->
-    <skip />
-    <!-- no translation found for content_description_hide_password (6841375971631767996) -->
-    <skip />
+    <string name="content_description_show_password" msgid="3283502010388521607">"Visa lösenord"</string>
+    <string name="content_description_hide_password" msgid="6841375971631767996">"Dölj lösenord"</string>
     <string name="passkey_creation_intro_title" msgid="4251037543787718844">"Säkrare med nycklar"</string>
-    <string name="passkey_creation_intro_body_password" msgid="8825872426579958200">"Med nycklar behöver du inte skapa eller komma ihop invecklade lösenord"</string>
+    <string name="passkey_creation_intro_body_password" msgid="8825872426579958200">"Med nycklar behöver du inte skapa eller komma ihåg invecklade lösenord"</string>
     <string name="passkey_creation_intro_body_fingerprint" msgid="7331338631826254055">"Nycklar är krypterade digitala nycklar som du skapar med ditt fingeravtryck, ansikte eller skärmlås"</string>
     <string name="passkey_creation_intro_body_device" msgid="1203796455762131631">"De sparas i lösenordshanteraren så att du kan logga in på andra enheter"</string>
     <string name="more_about_passkeys_title" msgid="7797903098728837795">"Mer om nycklar"</string>
@@ -35,10 +49,13 @@
     <string name="sign_ins" msgid="4710739369149469208">"inloggningar"</string>
     <string name="sign_in_info" msgid="2627704710674232328">"inloggningsuppgifter"</string>
     <string name="save_credential_to_title" msgid="3172811692275634301">"Spara <xliff:g id="CREDENTIALTYPES">%1$s</xliff:g> i"</string>
-    <string name="create_passkey_in_other_device_title" msgid="9195411122362461390">"Vill du skapa en nyckel på en annan enhet?"</string>
+    <string name="create_passkey_in_other_device_title" msgid="2360053098931886245">"Vill du skapa en nyckel på en annan enhet?"</string>
+    <string name="save_password_on_other_device_title" msgid="5829084591948321207">"Vill du spara lösenordet på en annan enhet?"</string>
+    <string name="save_sign_in_on_other_device_title" msgid="2827990118560134692">"Vill du spara inloggningen på en annan enhet?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"Vill du använda <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> för alla dina inloggningar?"</string>
-    <string name="use_provider_for_all_description" msgid="8466427781848268490">"Den här lösenordshanteraren sparar dina lösenord och nycklar för att underlätta inloggning"</string>
+    <string name="use_provider_for_all_description" msgid="1998772715863958997">"Den här lösenordshanteraren för <xliff:g id="USERNAME">%1$s</xliff:g> sparar dina lösenord och nycklar för att underlätta inloggning"</string>
     <string name="set_as_default" msgid="4415328591568654603">"Ange som standard"</string>
+    <string name="settings" msgid="6536394145760913145">"Inställningar"</string>
     <string name="use_once" msgid="9027366575315399714">"Använd en gång"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> lösenord • <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> nycklar"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> lösenord"</string>
@@ -49,21 +66,30 @@
     <string name="other_password_manager" msgid="565790221427004141">"Andra lösenordshanterare"</string>
     <string name="close_sheet" msgid="1393792015338908262">"Stäng kalkylarket"</string>
     <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Gå tillbaka till föregående sida"</string>
-    <string name="accessibility_close_button" msgid="2953807735590034688">"Förslaget om att stänga Credential Manager visas längst ned på skärmen"</string>
+    <string name="accessibility_close_button" msgid="1163435587545377687">"Stäng"</string>
+    <string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"Stäng"</string>
     <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Vill du använda din sparade nyckel för <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
-    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Vill du använda dina sparade inloggningsuppgifter för <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
-    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Välj en sparad inloggning för <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_use_password_for" msgid="625828023234318484">"Vill du använda det sparade lösenordet för <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"Vill du använda din inloggning för <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"Vill du låsa upp inloggningsalternativ för <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"Välj en sparad nyckel för <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"Välj ett sparat lösenord för <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_saved_sign_in_for" msgid="2420298653461652728">"Välj en sparad inloggning för <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="3048870756117876514">"Välj en inloggning för <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_option_for" msgid="4976380044745029107">"Vill du välja ett alternativ för <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_info_on" msgid="8863708099535435146">"Vill du använda den här informationen på <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Logga in på ett annat sätt"</string>
     <string name="snackbar_action" msgid="37373514216505085">"Visa alternativ"</string>
     <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Fortsätt"</string>
     <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Inloggningsalternativ"</string>
+    <string name="button_label_view_more" msgid="3429098227286495651">"Visa fler"</string>
     <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"För <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Låsta lösenordshanterare"</string>
-    <!-- no translation found for locked_credential_entry_label_subtext_tap_to_unlock (6390367581393605009) -->
-    <skip />
-    <!-- no translation found for locked_credential_entry_label_subtext_no_sign_in (8131725029983174901) -->
-    <skip />
+    <string name="locked_credential_entry_label_subtext_tap_to_unlock" msgid="6390367581393605009">"Tryck för att låsa upp"</string>
+    <string name="locked_credential_entry_label_subtext_no_sign_in" msgid="8131725029983174901">"Ingen inloggningsinformation"</string>
+    <string name="no_sign_in_info_in" msgid="2641118151920288356">"Det finns inga inloggningsuppgifter i <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Hantera inloggningar"</string>
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Via en annan enhet"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Använd en annan enhet"</string>
+    <string name="request_cancelled_by" msgid="3735222326886267820">"Begäran avbruten av <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
 </resources>
diff --git a/packages/CredentialManager/res/values-sw/strings.xml b/packages/CredentialManager/res/values-sw/strings.xml
index 9f30dbb..ffb4fa0 100644
--- a/packages/CredentialManager/res/values-sw/strings.xml
+++ b/packages/CredentialManager/res/values-sw/strings.xml
@@ -1,24 +1,38 @@
 <?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.
+   -->
+
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="4539824758261855508">"Kidhibiti cha Vitambulisho"</string>
     <string name="string_cancel" msgid="6369133483981306063">"Ghairi"</string>
     <string name="string_continue" msgid="1346732695941131882">"Endelea"</string>
-    <string name="string_more_options" msgid="7990658711962795124">"Chaguo zaidi"</string>
+    <string name="string_more_options" msgid="2763852250269945472">"Hifadhi vingine"</string>
     <string name="string_learn_more" msgid="4541600451688392447">"Pata maelezo zaidi"</string>
-    <!-- no translation found for content_description_show_password (3283502010388521607) -->
-    <skip />
-    <!-- no translation found for content_description_hide_password (6841375971631767996) -->
-    <skip />
-    <string name="passkey_creation_intro_title" msgid="4251037543787718844">"Ni salama ukitumia funguo za siri"</string>
+    <string name="content_description_show_password" msgid="3283502010388521607">"Onyesha nenosiri"</string>
+    <string name="content_description_hide_password" msgid="6841375971631767996">"Ficha nenosiri"</string>
+    <string name="passkey_creation_intro_title" msgid="4251037543787718844">"Salama zaidi ukitumia funguo za siri"</string>
     <string name="passkey_creation_intro_body_password" msgid="8825872426579958200">"Kwa kutumia funguo za siri, huhitaji kuunda au kukumbuka manenosiri changamano"</string>
     <string name="passkey_creation_intro_body_fingerprint" msgid="7331338631826254055">"Funguo za siri ni funguo dijitali zilizosimbwa kwa njia fiche unazounda kwa kutumia alama yako ya kidole, uso au mbinu ya kufunga skrini"</string>
     <string name="passkey_creation_intro_body_device" msgid="1203796455762131631">"Vyote huhifadhiwa kwenye kidhibiti cha manenosiri, ili uweze kuingia katika akaunti kwenye vifaa vingine"</string>
     <string name="more_about_passkeys_title" msgid="7797903098728837795">"Maelezo zaidi kuhusu funguo za siri"</string>
     <string name="passwordless_technology_title" msgid="2497513482056606668">"Teknolojia isiyotumia manenosiri"</string>
-    <string name="passwordless_technology_detail" msgid="6853928846532955882">"Funguo za siri zinakuruhusu uingie katika akaunti bila kutegemea manenosiri. Unapaswa tu kutumia alama yako ya kidole, kipengele cha utambuzi wa uso, PIN au mchoro wa kutelezesha ili uthibitishe utambulisho wako na uunde ufunguo wa siri."</string>
-    <string name="public_key_cryptography_title" msgid="6751970819265298039">"Kriptographia ya ufunguo wa umma"</string>
-    <string name="public_key_cryptography_detail" msgid="6937631710280562213">"Kulingana na Ushirikiano wa FIDO (unaojumuisha Google, Apple, Microsoft na zaidi) na viwango vya W3C, funguo za siri hutumia jozi ya funguo za kriptografia. Tofauti na jina la mtumiaji na mfuatano wa herufi tunazotumia kwa ajili ya manenosiri, jozi ya funguo binafsi na za umma imeundwa kwa ajili ya programu au tovuti. Ufunguo binafsi unatunzwa kwa usalama kwenye kifaa chako au kidhibiti cha manenosiri na huthibitisha utambulisho wako. Ufunguo wa umma unashirikiwa na seva ya programu au tovuti. Kwa funguo zinazolingana, unaweza kujisajili na kuingia katika akaunti papo hapo."</string>
+    <string name="passwordless_technology_detail" msgid="6853928846532955882">"Funguo za siri zinakuruhusu uingie katika akaunti bila kutegemea manenosiri. Unahitaji tu kutumia alama yako ya kidole, kipengele cha utambuzi wa uso, PIN au mchoro wa kutelezesha ili kuthibitisha utambulisho wako na kuunda ufunguo wa siri."</string>
+    <string name="public_key_cryptography_title" msgid="6751970819265298039">"Kriptografia ya funguo za umma"</string>
+    <string name="public_key_cryptography_detail" msgid="6937631710280562213">"Kulingana na Muungano wa FIDO (unaojumuisha Google, Apple, Microsoft na zaidi) na viwango vya W3C, funguo za siri hutumia jozi ya funguo za kriptografia. Tofauti na jina la mtumiaji na mfuatano wa herufi tunazotumia kwa ajili ya manenosiri, jozi ya funguo binafsi na za umma imeundwa kwa ajili ya programu au tovuti. Ufunguo binafsi hutunzwa kwa usalama kwenye kifaa chako au kidhibiti cha manenosiri na huthibitisha utambulisho wako. Ufunguo wa umma hushirikiwa na seva ya programu au tovuti. Ukiwa na funguo zinazolingana, unaweza kujisajili na kuingia katika akaunti papo hapo."</string>
     <string name="improved_account_security_title" msgid="1069841917893513424">"Ulinzi wa akaunti ulioboreshwa"</string>
     <string name="improved_account_security_detail" msgid="9123750251551844860">"Kila ufunguo umeunganishwa kwa upekee na programu au tovuti husika, kwa hivyo kamwe huwezi kuingia katika akaunti ya programu au tovuti ya kilaghai kwa bahati mbaya. Pia, kwa kuwa seva huhifadhi tu funguo za umma, udukuzi si rahisi."</string>
     <string name="seamless_transition_title" msgid="5335622196351371961">"Mabadiliko rahisi"</string>
@@ -35,12 +49,15 @@
     <string name="sign_ins" msgid="4710739369149469208">"michakato ya kuingia katika akaunti"</string>
     <string name="sign_in_info" msgid="2627704710674232328">"maelezo ya kuingia katika akaunti"</string>
     <string name="save_credential_to_title" msgid="3172811692275634301">"Hifadhi <xliff:g id="CREDENTIALTYPES">%1$s</xliff:g> kwenye"</string>
-    <string name="create_passkey_in_other_device_title" msgid="9195411122362461390">"Ungependa kuunda ufunguo wa siri kwenye kifaa kingine?"</string>
+    <string name="create_passkey_in_other_device_title" msgid="2360053098931886245">"Ungependa kuunda ufunguo wa siri kwenye kifaa kingine?"</string>
+    <string name="save_password_on_other_device_title" msgid="5829084591948321207">"Ungependa kuhifadhi nenosiri kwenye kifaa kingine?"</string>
+    <string name="save_sign_in_on_other_device_title" msgid="2827990118560134692">"Ungependa kuhifadhi kitambulisho cha kuingia katika akaunti kwenye kifaa kingine?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"Ungependa kutumia <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> kwa ajili ya michakato yako yote ya kuingia katika akaunti?"</string>
-    <string name="use_provider_for_all_description" msgid="8466427781848268490">"Kidhibiti hiki cha manenosiri kitahifadhi manenosiri na funguo zako za siri ili kukusaidia uingie katika akaunti kwa urahisi"</string>
+    <string name="use_provider_for_all_description" msgid="1998772715863958997">"Kidhibiti hiki cha manenosiri cha <xliff:g id="USERNAME">%1$s</xliff:g> kitahifadhi manenosiri na funguo zako za siri ili kukusaidia uingie katika akaunti kwa urahisi"</string>
     <string name="set_as_default" msgid="4415328591568654603">"Weka iwe chaguomsingi"</string>
+    <string name="settings" msgid="6536394145760913145">"Mipangilio"</string>
     <string name="use_once" msgid="9027366575315399714">"Tumia mara moja"</string>
-    <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"Manenosiri <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> • funguo <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> za siri"</string>
+    <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"Manenosiri <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> • Funguo <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> za siri"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"Manenosiri <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>"</string>
     <string name="more_options_usage_passkeys" msgid="5390320437243042237">"Funguo <xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> za siri"</string>
     <string name="more_options_usage_credentials" msgid="1785697001787193984">"Kitambulisho cha <xliff:g id="TOTALCREDENTIALSNUMBER">%1$s</xliff:g>"</string>
@@ -49,21 +66,30 @@
     <string name="other_password_manager" msgid="565790221427004141">"Vidhibiti vinginevyo vya manenosiri"</string>
     <string name="close_sheet" msgid="1393792015338908262">"Funga laha"</string>
     <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Rudi kwenye ukurasa uliotangulia"</string>
-    <string name="accessibility_close_button" msgid="2953807735590034688">"Funga pendekezo la kitendo cha Kidhibiti cha Vitambulisho linaloonekana kwenye sehemu ya chini ya skrini"</string>
-    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Ungependa kutumia ufunguo wa siri uliohifadhiwa wa<xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
-    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Ungependa kutumia kitambulisho kilichohifadhiwa cha kuingia katika akaunti ya <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
-    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Chagua vitambulisho vilivyohifadhiwa kwa ajili ya kuingia katika akaunti ya <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="accessibility_close_button" msgid="1163435587545377687">"Funga"</string>
+    <string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"Ondoa"</string>
+    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Ungependa kutumia ufunguo wa siri uliohifadhiwa wa <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_password_for" msgid="625828023234318484">"Je, ungependa kutumia nenosiri lako lililohifadhiwa kuingia katika <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"Ungependa kutumia kitambulisho chako cha kuingia katika akaunti ya <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"Je, ungependa kuona chaguo za kuingia katika <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"Chagua ufunguo wa siri uliohifadhiwa ambao ungependa kutumia kuingia katika <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"Chagua nenosiri lililohifadhiwa ambalo ungependa kutumia kuingia katika <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_saved_sign_in_for" msgid="2420298653461652728">"Chagua vitambulisho vilivyohifadhiwa ambavyo ungependa kutumia kuingia katika <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="3048870756117876514">"Chagua kitambulisho cha kuingia katika akaunti ya <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_option_for" msgid="4976380044745029107">"Ungependa kuteua chaguo la <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_info_on" msgid="8863708099535435146">"Ungependa kutumia maelezo haya kwenye <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Ingia katika akaunti kwa kutumia njia nyingine"</string>
     <string name="snackbar_action" msgid="37373514216505085">"Angalia chaguo"</string>
     <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Endelea"</string>
     <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Chaguo za kuingia katika akaunti"</string>
+    <string name="button_label_view_more" msgid="3429098227286495651">"Angalia zaidi"</string>
     <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Kwa ajili ya <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Vidhibiti vya manenosiri vilivyofungwa"</string>
-    <!-- no translation found for locked_credential_entry_label_subtext_tap_to_unlock (6390367581393605009) -->
-    <skip />
-    <!-- no translation found for locked_credential_entry_label_subtext_no_sign_in (8131725029983174901) -->
-    <skip />
+    <string name="locked_credential_entry_label_subtext_tap_to_unlock" msgid="6390367581393605009">"Gusa ili ufungue"</string>
+    <string name="locked_credential_entry_label_subtext_no_sign_in" msgid="8131725029983174901">"Hakuna maelezo ya kuingia katika akaunti"</string>
+    <string name="no_sign_in_info_in" msgid="2641118151920288356">"Hakuna maelezo ya kuingia katika akaunti kwenye <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Dhibiti michakato ya kuingia katika akaunti"</string>
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Kutoka kwenye kifaa kingine"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Tumia kifaa tofauti"</string>
+    <string name="request_cancelled_by" msgid="3735222326886267820">"Ombi lilighairiwa na <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
 </resources>
diff --git a/packages/CredentialManager/res/values-ta/strings.xml b/packages/CredentialManager/res/values-ta/strings.xml
index 4041066..750b67d 100644
--- a/packages/CredentialManager/res/values-ta/strings.xml
+++ b/packages/CredentialManager/res/values-ta/strings.xml
@@ -1,15 +1,29 @@
 <?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.
+   -->
+
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="4539824758261855508">"அனுமதிச் சான்று நிர்வாகி"</string>
     <string name="string_cancel" msgid="6369133483981306063">"ரத்துசெய்"</string>
     <string name="string_continue" msgid="1346732695941131882">"தொடர்க"</string>
-    <string name="string_more_options" msgid="7990658711962795124">"கூடுதல் விருப்பங்கள்"</string>
+    <string name="string_more_options" msgid="2763852250269945472">"வேறு வழியில் சேமி"</string>
     <string name="string_learn_more" msgid="4541600451688392447">"மேலும் அறிக"</string>
-    <!-- no translation found for content_description_show_password (3283502010388521607) -->
-    <skip />
-    <!-- no translation found for content_description_hide_password (6841375971631767996) -->
-    <skip />
+    <string name="content_description_show_password" msgid="3283502010388521607">"கடவுச்சொல்லைக் காட்டும்"</string>
+    <string name="content_description_hide_password" msgid="6841375971631767996">"கடவுச்சொல்லை மறைக்கும்"</string>
     <string name="passkey_creation_intro_title" msgid="4251037543787718844">"கடவுச்சாவிகள் மூலம் பாதுகாப்பாக வைத்திருங்கள்"</string>
     <string name="passkey_creation_intro_body_password" msgid="8825872426579958200">"கடவுச்சாவிகளைப் பயன்படுத்துவதன் மூலம் கடினமான கடவுச்சொற்களை உருவாக்கவோ நினைவில்கொள்ளவோ தேவையில்லை"</string>
     <string name="passkey_creation_intro_body_fingerprint" msgid="7331338631826254055">"கடவுச்சாவிகள் என்பவை உங்கள் கைரேகை, முகம் அல்லது திரைப் பூட்டு மூலம் உருவாக்கப்படும் என்க்ரிப்ஷன் செய்யப்பட்ட டிஜிட்டல் சாவிகள் ஆகும்"</string>
@@ -28,42 +42,54 @@
     <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"<xliff:g id="APPNAME">%1$s</xliff:g> ஆப்ஸுக்கான கடவுச்சாவியை உருவாக்கவா?"</string>
     <string name="choose_create_option_password_title" msgid="7097275038523578687">"<xliff:g id="APPNAME">%1$s</xliff:g> ஆப்ஸுக்கான கடவுச்சொல்லைச் சேமிக்கவா?"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"<xliff:g id="APPNAME">%1$s</xliff:g> ஆப்ஸுக்கான உள்நுழைவு விவரங்களைச் சேமிக்கவா?"</string>
-    <string name="passkey" msgid="632353688396759522">"கடவுக்குறியீடு"</string>
+    <string name="passkey" msgid="632353688396759522">"கடவுச்சாவி"</string>
     <string name="password" msgid="6738570945182936667">"கடவுச்சொல்"</string>
     <string name="passkeys" msgid="5733880786866559847">"கடவுச்சாவிகள்"</string>
     <string name="passwords" msgid="5419394230391253816">"கடவுச்சொற்கள்"</string>
     <string name="sign_ins" msgid="4710739369149469208">"உள்நுழைவுகள்"</string>
     <string name="sign_in_info" msgid="2627704710674232328">"உள்நுழைவு விவரங்கள்"</string>
     <string name="save_credential_to_title" msgid="3172811692275634301">"<xliff:g id="CREDENTIALTYPES">%1$s</xliff:g> ஐ இதில் சேமியுங்கள்"</string>
-    <string name="create_passkey_in_other_device_title" msgid="9195411122362461390">"வேறொரு சாதனத்தில் கடவுச்சாவியை உருவாக்க வேண்டுமா?"</string>
+    <string name="create_passkey_in_other_device_title" msgid="2360053098931886245">"வேறொரு சாதனத்தில் கடவுச்சாவியை உருவாக்கவா?"</string>
+    <string name="save_password_on_other_device_title" msgid="5829084591948321207">"வேறொரு சாதனத்தில் கடவுச்சொல்லைச் சேமிக்கவா?"</string>
+    <string name="save_sign_in_on_other_device_title" msgid="2827990118560134692">"வேறொரு சாதனத்தில் உள்நுழைவைச் சேமிக்கவா?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"உங்கள் அனைத்து உள்நுழைவுகளுக்கும் <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>ஐப் பயன்படுத்தவா?"</string>
-    <string name="use_provider_for_all_description" msgid="8466427781848268490">"இந்தக் கடவுச்சொல் நிர்வாகி உங்கள் கடவுச்சொற்களையும் கடவுச்சாவிகளையும் சேமித்து நீங்கள் எளிதாக உள்நுழைய உதவும்"</string>
+    <string name="use_provider_for_all_description" msgid="1998772715863958997">"<xliff:g id="USERNAME">%1$s</xliff:g> என்ற மின்னஞ்சல் முகவரிக்கான இந்தக் கடவுச்சொல் நிர்வாகி உங்கள் கடவுச்சொற்களையும் கடவுச்சாவிகளையும் சேமித்து நீங்கள் எளிதாக உள்நுழைய உதவும்"</string>
     <string name="set_as_default" msgid="4415328591568654603">"இயல்பானதாக அமை"</string>
+    <string name="settings" msgid="6536394145760913145">"அமைப்புகள்"</string>
     <string name="use_once" msgid="9027366575315399714">"ஒருமுறை பயன்படுத்தவும்"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> கடவுச்சொற்கள் • <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> கடவுச்சாவிகள்"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> கடவுச்சொற்கள்"</string>
-    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> கடவுக்குறியீடுகள்"</string>
+    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> கடவுச்சாவிகள்"</string>
     <string name="more_options_usage_credentials" msgid="1785697001787193984">"<xliff:g id="TOTALCREDENTIALSNUMBER">%1$s</xliff:g> அனுமதிச் சான்றுகள்"</string>
-    <string name="passkey_before_subtitle" msgid="2448119456208647444">"கடவுக்குறியீடு"</string>
+    <string name="passkey_before_subtitle" msgid="2448119456208647444">"கடவுச்சாவி"</string>
     <string name="another_device" msgid="5147276802037801217">"மற்றொரு சாதனம்"</string>
     <string name="other_password_manager" msgid="565790221427004141">"பிற கடவுச்சொல் நிர்வாகிகள்"</string>
     <string name="close_sheet" msgid="1393792015338908262">"ஷீட்டை மூடும்"</string>
     <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"முந்தைய பக்கத்திற்குச் செல்லும்"</string>
-    <string name="accessibility_close_button" msgid="2953807735590034688">"அனுமதிச் சான்று நிர்வாகியின் நடவடிக்கை குறித்து திரையின் கீழ்ப்பகுதியில் காட்டப்படும் பரிந்துரையை மூடும்"</string>
+    <string name="accessibility_close_button" msgid="1163435587545377687">"மூடும்"</string>
+    <string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"நிராகரிக்கும்"</string>
     <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"<xliff:g id="APP_NAME">%1$s</xliff:g> ஆப்ஸுக்கு ஏற்கெனவே சேமிக்கப்பட்ட கடவுக்குறியீட்டைப் பயன்படுத்தவா?"</string>
-    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"<xliff:g id="APP_NAME">%1$s</xliff:g> ஆப்ஸுக்கு ஏற்கெனவே சேமிக்கப்பட்ட உள்நுழைவுத் தகவலைப் பயன்படுத்தவா?"</string>
-    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"<xliff:g id="APP_NAME">%1$s</xliff:g> ஆப்ஸுக்கு ஏற்கெனவே சேமிக்கப்பட்ட உள்நுழைவுத் தகவலைத் தேர்வுசெய்யவும்"</string>
+    <string name="get_dialog_title_use_password_for" msgid="625828023234318484">"<xliff:g id="APP_NAME">%1$s</xliff:g> ஆப்ஸுக்குச் சேமித்த கடவுச்சொல்லைப் பயன்படுத்தவா?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"<xliff:g id="APP_NAME">%1$s</xliff:g> ஆப்ஸுக்கு உங்கள் உள்நுழைவு விவரங்களைப் பயன்படுத்தவா?"</string>
+    <string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"<xliff:g id="APP_NAME">%1$s</xliff:g> ஆப்ஸுக்கான உள்நுழைவு விருப்பங்களை அன்லாக் செய்யவா?"</string>
+    <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"<xliff:g id="APP_NAME">%1$s</xliff:g> ஆப்ஸுக்கான சேமிக்கப்பட்ட கடவுச்சாவியைத் தேர்ந்தெடுங்கள்"</string>
+    <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"<xliff:g id="APP_NAME">%1$s</xliff:g> ஆப்ஸுக்கான சேமிக்கப்பட்ட கடவுச்சொல்லைத் தேர்ந்தெடுங்கள்"</string>
+    <string name="get_dialog_title_choose_saved_sign_in_for" msgid="2420298653461652728">"<xliff:g id="APP_NAME">%1$s</xliff:g> ஆப்ஸுக்கான சேமிக்கப்பட்ட உள்நுழைவுத் தகவல்களைத் தேர்ந்தெடுங்கள்"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="3048870756117876514">"<xliff:g id="APP_NAME">%1$s</xliff:g> ஆப்ஸுக்கான உள்நுழைவு விவரங்களைத் தேர்வுசெய்யுங்கள்"</string>
+    <string name="get_dialog_title_choose_option_for" msgid="4976380044745029107">"<xliff:g id="APP_NAME">%1$s</xliff:g> ஆப்ஸுக்கான விருப்பத்தைத் தேர்வுசெய்யவா?"</string>
+    <string name="get_dialog_title_use_info_on" msgid="8863708099535435146">"<xliff:g id="APP_NAME">%1$s</xliff:g> ஆப்ஸில் இந்தத் தகவல்களைப் பயன்படுத்தவா?"</string>
     <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"வேறு முறையில் உள்நுழைக"</string>
     <string name="snackbar_action" msgid="37373514216505085">"விருப்பங்களைக் காட்டு"</string>
     <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"தொடர்க"</string>
     <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"உள்நுழைவு விருப்பங்கள்"</string>
+    <string name="button_label_view_more" msgid="3429098227286495651">"மேலும் காட்டு"</string>
     <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"<xliff:g id="USERNAME">%1$s</xliff:g>க்கு"</string>
     <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"பூட்டப்பட்ட கடவுச்சொல் நிர்வாகிகள்"</string>
-    <!-- no translation found for locked_credential_entry_label_subtext_tap_to_unlock (6390367581393605009) -->
-    <skip />
-    <!-- no translation found for locked_credential_entry_label_subtext_no_sign_in (8131725029983174901) -->
-    <skip />
+    <string name="locked_credential_entry_label_subtext_tap_to_unlock" msgid="6390367581393605009">"அன்லாக் செய்ய தட்டவும்"</string>
+    <string name="locked_credential_entry_label_subtext_no_sign_in" msgid="8131725029983174901">"உள்நுழைவு விவரங்கள் இல்லை"</string>
+    <string name="no_sign_in_info_in" msgid="2641118151920288356">"<xliff:g id="SOURCE">%1$s</xliff:g> கணக்கில் உள்நுழைவு விவரங்கள் இல்லை"</string>
     <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"உள்நுழைவுகளை நிர்வகித்தல்"</string>
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"மற்றொரு சாதனத்திலிருந்து பயன்படுத்து"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"வேறு சாதனத்தைப் பயன்படுத்து"</string>
+    <string name="request_cancelled_by" msgid="3735222326886267820">"<xliff:g id="APP_NAME">%1$s</xliff:g> ஆப்ஸால் கோரிக்கை ரத்துசெய்யப்பட்டது"</string>
 </resources>
diff --git a/packages/CredentialManager/res/values-te/strings.xml b/packages/CredentialManager/res/values-te/strings.xml
index 6dd8204..064ee96 100644
--- a/packages/CredentialManager/res/values-te/strings.xml
+++ b/packages/CredentialManager/res/values-te/strings.xml
@@ -1,31 +1,45 @@
 <?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.
+   -->
+
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="4539824758261855508">"డాక్యుమెంట్ ప్రూఫ్ మేనేజర్"</string>
     <string name="string_cancel" msgid="6369133483981306063">"రద్దు చేయండి"</string>
     <string name="string_continue" msgid="1346732695941131882">"కొనసాగించండి"</string>
-    <string name="string_more_options" msgid="7990658711962795124">"మరిన్ని ఆప్షన్‌లు"</string>
+    <string name="string_more_options" msgid="2763852250269945472">"మరోలా సేవ్ చేయండి"</string>
     <string name="string_learn_more" msgid="4541600451688392447">"మరింత తెలుసుకోండి"</string>
-    <!-- no translation found for content_description_show_password (3283502010388521607) -->
-    <skip />
-    <!-- no translation found for content_description_hide_password (6841375971631767996) -->
-    <skip />
-    <string name="passkey_creation_intro_title" msgid="4251037543787718844">"పాస్-కీలతో సురక్షితంగా చెల్లించవచ్చు"</string>
+    <string name="content_description_show_password" msgid="3283502010388521607">"పాస్‌వర్డ్‌ను చూపండి"</string>
+    <string name="content_description_hide_password" msgid="6841375971631767996">"పాస్‌వర్డ్‌ను చూపవద్దు"</string>
+    <string name="passkey_creation_intro_title" msgid="4251037543787718844">"పాస్-కీలతో సురక్షితంగా పేమెంట్ చేయవచ్చు"</string>
     <string name="passkey_creation_intro_body_password" msgid="8825872426579958200">"పాస్-కీలతో, మీరు క్లిష్టమైన పాస్‌వర్డ్‌లను క్రియేట్ చేయనవసరం లేదు లేదా గుర్తుంచుకోనవసరం లేదు"</string>
     <string name="passkey_creation_intro_body_fingerprint" msgid="7331338631826254055">"పాస్-కీలు అనేవి మీ వేలిముద్రను, ముఖాన్ని లేదా స్క్రీన్ లాక్‌ను ఉపయోగించి మీరు క్రియేట్ చేసే ఎన్‌క్రిప్ట్ చేసిన డిజిటల్ కీలు"</string>
-    <string name="passkey_creation_intro_body_device" msgid="1203796455762131631">"అవి Password Managerకు సేవ్ చేయబడతాయి, తద్వారా మీరు ఇతర పరికరాలలో సైన్ ఇన్ చేయవచ్చు"</string>
+    <string name="passkey_creation_intro_body_device" msgid="1203796455762131631">"ఏదైనా ఒక పాస్‌వర్డ్ మేనేజర్‌లో అవి సేవ్ అవుతాయి, తద్వారా మీరు ఇతర పరికరాలలో సైన్ ఇన్ చేయవచ్చు"</string>
     <string name="more_about_passkeys_title" msgid="7797903098728837795">"పాస్-కీల గురించి మరిన్ని వివరాలు"</string>
     <string name="passwordless_technology_title" msgid="2497513482056606668">"పాస్‌వర్డ్ రహిత టెక్నాలజీ"</string>
     <string name="passwordless_technology_detail" msgid="6853928846532955882">"పాస్‌వర్డ్‌లపై ఆధారపడకుండా సైన్ ఇన్ చేయడానికి పాస్-కీలు మిమ్మల్ని అనుమతిస్తాయి. మీ గుర్తింపును వెరిఫై చేసి, పాస్-కీని క్రియేట్ చేయడానికి మీరు మీ వేలిముద్ర, ముఖ గుర్తింపు, PIN, లేదా స్వైప్ ఆకృతిని ఉపయోగించాలి."</string>
     <string name="public_key_cryptography_title" msgid="6751970819265298039">"పబ్లిక్ కీ క్రిప్టోగ్రఫీ"</string>
-    <string name="public_key_cryptography_detail" msgid="6937631710280562213">"FIDO Alliance (దీనిలో Google, Apple, Microsoft, మరిన్ని ఉన్నాయి), W3C ప్రమాణాల ప్రకారం, పాస్‌కీలు క్రిప్టోగ్రాఫిక్ కీల జతలను ఉపయోగిస్తాయి. మనం పాస్‌వర్డ్‌ల కోసం ఉపయోగించే యూజర్‌నేమ్, అక్షరాల స్ట్రింగ్ కాకుండా, యాప్ లేదా సైట్ కోసం ప్రైవేట్-పబ్లిక్ కీల జత సృష్టించబడుతుంది. ప్రైవేట్ కీ మీ డివైజ్/పాస్‌వర్డ్ మేనేజర్‌లో సురక్షితంగా స్టోర్ చేయబడుతుంది, ఇది మీ గుర్తింపును నిర్ధారిస్తుంది. పబ్లిక్ కీ, యాప్/వెబ్‌సైట్ సర్వర్‌తో షేర్ చేయబడుతుంది. సంబంధిత కీలతో, తక్షణమే రిజిస్టర్ చేసుకొని, సైన్ ఇన్ చేయవచ్చు."</string>
+    <string name="public_key_cryptography_detail" msgid="6937631710280562213">"FIDO Alliance (దీనిలో Google, Apple, Microsoft, మరిన్ని ఉన్నాయి), W3C ప్రమాణాల ప్రకారం, పాస్‌కీలు క్రిప్టోగ్రాఫిక్ కీల జతలను ఉపయోగిస్తాయి. మనం పాస్‌వర్డ్‌ల కోసం ఉపయోగించే యూజర్‌నేమ్, అక్షరాల స్ట్రింగ్ కాకుండా, యాప్/సైట్ కోసం ప్రైవేట్-పబ్లిక్ కీల జత క్రియేట్ చేయబడుతుంది. ప్రైవేట్ కీ మీ డివైజ్/పాస్‌వర్డ్ మేనేజర్‌లో సురక్షితంగా స్టోర్ చేయబడుతుంది, ఇది మీ గుర్తింపును నిర్ధారిస్తుంది. పబ్లిక్ కీ, యాప్/వెబ్‌సైట్ సర్వర్‌తో షేర్ చేయబడుతుంది. సంబంధిత కీలతో, తక్షణమే రిజిస్టర్ చేసుకొని, సైన్ ఇన్ చేయవచ్చు."</string>
     <string name="improved_account_security_title" msgid="1069841917893513424">"మెరుగైన ఖాతా సెక్యూరిటీ"</string>
     <string name="improved_account_security_detail" msgid="9123750251551844860">"ప్రతి కీ దానిని క్రియేట్ చేసిన యాప్ లేదా వెబ్‌సైట్‌తో ప్రత్యేకంగా లింక్ చేయబడి ఉంటుంది, కాబట్టి మీరు పొరపాటున కూడా మోసపూరిత యాప్ లేదా వెబ్‌సైట్‌కు సైన్ ఇన్ చేయలేరు. అంతే కాకుండా, సర్వర్‌లు పబ్లిక్ కీలను మాత్రమే స్టోర్ చేయడం వల్ల, హ్యాకింగ్ చేయడం చాలా కష్టం."</string>
     <string name="seamless_transition_title" msgid="5335622196351371961">"అవాంతరాలు లేని పరివర్తన"</string>
     <string name="seamless_transition_detail" msgid="4475509237171739843">"మనం భవిష్యత్తులో పాస్‌వర్డ్ రహిత టెక్నాలజీని ఉపయోగించినా, పాస్‌కీలతో పాటు పాస్‌వర్డ్‌లు కూడా అందుబాటులో ఉంటాయి."</string>
-    <string name="choose_provider_title" msgid="8870795677024868108">"మీ <xliff:g id="CREATETYPES">%1$s</xliff:g>ని ఎక్కడ సేవ్ చేయాలో ఎంచుకోండి"</string>
-    <string name="choose_provider_body" msgid="4967074531845147434">"మీ సమాచారాన్ని సేవ్ చేయడం కోసం ఒక Password Managerను ఎంచుకొని, తర్వాతసారి వేగంగా సైన్ ఇన్ చేయండి"</string>
-    <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"<xliff:g id="APPNAME">%1$s</xliff:g> కోసం పాస్‌కీని క్రియేట్ చేయాలా?"</string>
+    <string name="choose_provider_title" msgid="8870795677024868108">"మీ <xliff:g id="CREATETYPES">%1$s</xliff:g> ఎక్కడ సేవ్ చేయాలో ఎంచుకోండి"</string>
+    <string name="choose_provider_body" msgid="4967074531845147434">"తర్వాతిసారి మరింత వేగంగా సైన్ ఇన్ చేసేందుకు వీలుగా మీ సమాచారాన్ని సేవ్ చేయడం కోసం ఒక పాస్‌వర్డ్ మేనేజర్‌ను ఎంచుకోండి"</string>
+    <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"<xliff:g id="APPNAME">%1$s</xliff:g> కోసం పాస్‌-కీని క్రియేట్ చేయాలా?"</string>
     <string name="choose_create_option_password_title" msgid="7097275038523578687">"<xliff:g id="APPNAME">%1$s</xliff:g> కోసం పాస్‌వర్డ్‌ను సేవ్ చేయాలా?"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"<xliff:g id="APPNAME">%1$s</xliff:g> కోసం సైన్ ఇన్ సమాచారాన్ని సేవ్ చేయాలా?"</string>
     <string name="passkey" msgid="632353688396759522">"పాస్-కీ"</string>
@@ -35,10 +49,13 @@
     <string name="sign_ins" msgid="4710739369149469208">"సైన్‌ ఇన్‌లు"</string>
     <string name="sign_in_info" msgid="2627704710674232328">"సైన్ ఇన్ సమాచారం"</string>
     <string name="save_credential_to_title" msgid="3172811692275634301">"<xliff:g id="CREDENTIALTYPES">%1$s</xliff:g>‌లో సేవ్ చేయండి"</string>
-    <string name="create_passkey_in_other_device_title" msgid="9195411122362461390">"మరొక పరికరంలో పాస్‌కీని క్రియేట్ చేయాలా?"</string>
+    <string name="create_passkey_in_other_device_title" msgid="2360053098931886245">"మరొక పరికరంలో పాస్-కీని క్రియేట్ చేయాలా?"</string>
+    <string name="save_password_on_other_device_title" msgid="5829084591948321207">"మరొక పరికరంలో పాస్‌వర్డ్‌ను సేవ్ చేయాలా?"</string>
+    <string name="save_sign_in_on_other_device_title" msgid="2827990118560134692">"మరో పరికరంలో సైన్-ఇన్‌ని సేవ్ చేయాలా?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"మీ అన్ని సైన్-ఇన్ వివరాల కోసం <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>ను ఉపయోగించాలా?"</string>
-    <string name="use_provider_for_all_description" msgid="8466427781848268490">"మీరు సులభంగా సైన్ ఇన్ చేయడంలో సహాయపడటానికి ఈ పాస్‌వర్డ్ మేనేజర్ మీ పాస్‌వర్డ్‌లు, పాస్-కీలను స్టోర్ చేస్తుంది"</string>
+    <string name="use_provider_for_all_description" msgid="1998772715863958997">"<xliff:g id="USERNAME">%1$s</xliff:g> కోసం ఈ పాస్‌వర్డ్ మేనేజర్ మీకు సులభంగా సైన్ ఇన్ చేయడంలో సహాయపడటానికి మీ పాస్‌వర్డ్‌లు, పాస్-కీలను స్టోర్ చేస్తుంది"</string>
     <string name="set_as_default" msgid="4415328591568654603">"ఆటోమేటిక్ సెట్టింగ్‌గా సెట్ చేయండి"</string>
+    <string name="settings" msgid="6536394145760913145">"సెట్టింగ్‌లు"</string>
     <string name="use_once" msgid="9027366575315399714">"ఒకసారి ఉపయోగించండి"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> పాస్‌వర్డ్‌లు • <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> పాస్-కీలు"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> పాస్‌వర్డ్‌లు"</string>
@@ -49,21 +66,30 @@
     <string name="other_password_manager" msgid="565790221427004141">"ఇతర పాస్‌వర్డ్ మేనేజర్‌లు"</string>
     <string name="close_sheet" msgid="1393792015338908262">"షీట్‌ను మూసివేయండి"</string>
     <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"మునుపటి పేజీకి తిరిగి వెళ్లండి"</string>
-    <string name="accessibility_close_button" msgid="2953807735590034688">"స్క్రీన్ దిగువున కనిపించే ఆధారాల మేనేజర్ చర్య సూచనను మూసివేయండి"</string>
-    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"<xliff:g id="APP_NAME">%1$s</xliff:g> కోసం మీ సేవ్ చేసిన పాస్-కీ వివరాలను ఉపయోగించాలా?"</string>
-    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"<xliff:g id="APP_NAME">%1$s</xliff:g> కోసం మీరు సేవ్ చేసిన సైన్ ఇన్ వివరాలను ఉపయోగించాలా?"</string>
-    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"<xliff:g id="APP_NAME">%1$s</xliff:g> కోసం సేవ్ చేసిన సైన్ ఇన్ వివరాలను ఎంచుకోండి"</string>
+    <string name="accessibility_close_button" msgid="1163435587545377687">"మూసివేయండి"</string>
+    <string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"విస్మరించండి"</string>
+    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"<xliff:g id="APP_NAME">%1$s</xliff:g> కోసం మీ సేవ్ చేసిన పాస్-కీని ఉపయోగించాలా?"</string>
+    <string name="get_dialog_title_use_password_for" msgid="625828023234318484">"<xliff:g id="APP_NAME">%1$s</xliff:g> కోసం మీ సేవ్ చేసిన పాస్‌వర్డ్‌ను ఉపయోగించాలా?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"<xliff:g id="APP_NAME">%1$s</xliff:g> కోసం మీ సైన్ ఇన్ వివరాలను ఉపయోగించాలా?"</string>
+    <string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"<xliff:g id="APP_NAME">%1$s</xliff:g> కోసం సైన్ ఇన్ ఆప్షన్‌లను అన్‌లాక్ చేయాలా?"</string>
+    <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"<xliff:g id="APP_NAME">%1$s</xliff:g> కోసం సేవ్ చేసిన పాస్-కీని ఎంచుకోండి"</string>
+    <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"<xliff:g id="APP_NAME">%1$s</xliff:g> కోసం సేవ్ చేసిన పాస్‌వర్డ్‌ను ఎంచుకోండి"</string>
+    <string name="get_dialog_title_choose_saved_sign_in_for" msgid="2420298653461652728">"<xliff:g id="APP_NAME">%1$s</xliff:g> కోసం సేవ్ చేసిన సైన్ ఇన్ వివరాలను ఎంచుకోండి"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="3048870756117876514">"<xliff:g id="APP_NAME">%1$s</xliff:g> కోసం సైన్ ఇన్ వివరాలను ఎంచుకోండి"</string>
+    <string name="get_dialog_title_choose_option_for" msgid="4976380044745029107">"<xliff:g id="APP_NAME">%1$s</xliff:g> కోసం ఏదైనా ఆప్షన్‌ను ఎంచుకోవాలనుకుంటున్నారా?"</string>
+    <string name="get_dialog_title_use_info_on" msgid="8863708099535435146">"ఈ సమాచారాన్ని <xliff:g id="APP_NAME">%1$s</xliff:g>లో ఉపయోగించాలా?"</string>
     <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"మరొక పద్ధతిలో సైన్ ఇన్ చేయండి"</string>
     <string name="snackbar_action" msgid="37373514216505085">"ఆప్షన్‌లను చూడండి"</string>
     <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"కొనసాగించండి"</string>
     <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"సైన్ ఇన్ ఆప్షన్‌లు"</string>
+    <string name="button_label_view_more" msgid="3429098227286495651">"మరిన్ని చూడండి"</string>
     <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"<xliff:g id="USERNAME">%1$s</xliff:g> కోసం"</string>
     <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"లాక్ చేయబడిన పాస్‌వర్డ్ మేనేజర్‌లు"</string>
-    <!-- no translation found for locked_credential_entry_label_subtext_tap_to_unlock (6390367581393605009) -->
-    <skip />
-    <!-- no translation found for locked_credential_entry_label_subtext_no_sign_in (8131725029983174901) -->
-    <skip />
+    <string name="locked_credential_entry_label_subtext_tap_to_unlock" msgid="6390367581393605009">"అన్‌లాక్ చేయడానికి ట్యాప్ చేయండి"</string>
+    <string name="locked_credential_entry_label_subtext_no_sign_in" msgid="8131725029983174901">"సైన్ ఇన్ సమాచారం ఏదీ లేదు"</string>
+    <string name="no_sign_in_info_in" msgid="2641118151920288356">"<xliff:g id="SOURCE">%1$s</xliff:g>‌లో సైన్ ఇన్ సమాచారం లేదు"</string>
     <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"సైన్‌ ఇన్‌లను మేనేజ్ చేయండి"</string>
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"మరొక పరికరం నుండి"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"వేరే పరికరాన్ని ఉపయోగించండి"</string>
+    <string name="request_cancelled_by" msgid="3735222326886267820">"<xliff:g id="APP_NAME">%1$s</xliff:g>, రిక్వెస్ట్‌ను రద్దు చేసింది"</string>
 </resources>
diff --git a/packages/CredentialManager/res/values-th/strings.xml b/packages/CredentialManager/res/values-th/strings.xml
index 78f89fc..249bd88 100644
--- a/packages/CredentialManager/res/values-th/strings.xml
+++ b/packages/CredentialManager/res/values-th/strings.xml
@@ -1,10 +1,26 @@
 <?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="4539824758261855508">"เครื่องมือจัดการข้อมูลเข้าสู่ระบบ"</string>
     <string name="string_cancel" msgid="6369133483981306063">"ยกเลิก"</string>
     <string name="string_continue" msgid="1346732695941131882">"ต่อไป"</string>
-    <string name="string_more_options" msgid="7990658711962795124">"ตัวเลือกเพิ่มเติม"</string>
+    <string name="string_more_options" msgid="2763852250269945472">"บันทึกด้วยวิธีอื่น"</string>
     <string name="string_learn_more" msgid="4541600451688392447">"ดูข้อมูลเพิ่มเติม"</string>
     <string name="content_description_show_password" msgid="3283502010388521607">"แสดงรหัสผ่าน"</string>
     <string name="content_description_hide_password" msgid="6841375971631767996">"ซ่อนรหัสผ่าน"</string>
@@ -16,7 +32,7 @@
     <string name="passwordless_technology_title" msgid="2497513482056606668">"เทคโนโลยีที่ไม่ต้องใช้รหัสผ่าน"</string>
     <string name="passwordless_technology_detail" msgid="6853928846532955882">"พาสคีย์ช่วยให้คุณลงชื่อเข้าใช้ได้โดยไม่ต้องใช้รหัสผ่าน ใช้เพียงแค่ลายนิ้วมือ, การจดจำใบหน้า, PIN หรือรูปแบบการลากเส้นในการยืนยันตัวตนและสร้างพาสคีย์"</string>
     <string name="public_key_cryptography_title" msgid="6751970819265298039">"วิทยาการเข้ารหัสคีย์สาธารณะ"</string>
-    <string name="public_key_cryptography_detail" msgid="6937631710280562213">"พาสคีย์ใช้คู่คีย์การเข้ารหัสตามมาตรฐานของ FIDO Alliance (เช่น Google, Apple, Microsoft และอื่นๆ) และ W3C คู่คีย์สาธารณะและคีย์ส่วนตัวจะสร้างขึ้นสำหรับแอปหรือเว็บไซต์ที่ใช้งานคีย์ดังกล่าวต่างจากชื่อผู้ใช้และชุดอักขระที่ใช้เป็นรหัสผ่าน โดยระบบจะจัดเก็บคีย์ส่วนตัวไว้ในอุปกรณ์หรือเครื่องมือจัดการรหัสผ่านและใช้คีย์ดังกล่าวเพื่อยืนยันตัวตน ส่วนคีย์สาธารณะจะแชร์กับเซิร์ฟเวอร์ของแอปหรือเว็บไซต์ ลงทะเบียนและลงชื่อเข้าใช้ได้ทันทีด้วยคีย์ที่สอดคล้องกัน"</string>
+    <string name="public_key_cryptography_detail" msgid="6937631710280562213">"พาสคีย์ใช้คู่คีย์การเข้ารหัสตามมาตรฐานของ FIDO Alliance (เช่น Google, Apple, Microsoft และอื่นๆ) และ W3C คู่คีย์สาธารณะและคีย์ส่วนตัวจะสร้างขึ้นสำหรับแอปหรือเว็บไซต์ ซึ่งต่างจากชื่อผู้ใช้และชุดอักขระที่ใช้เป็นรหัสผ่าน โดยระบบจะจัดเก็บคีย์ส่วนตัวไว้อย่างปลอดภัยในอุปกรณ์หรือเครื่องมือจัดการรหัสผ่านและใช้คีย์ดังกล่าวเพื่อยืนยันตัวตน ส่วนคีย์สาธารณะจะแชร์กับเซิร์ฟเวอร์ของแอปหรือเว็บไซต์ คุณลงทะเบียนและลงชื่อเข้าใช้ได้ทันทีด้วยคีย์ที่สอดคล้องกัน"</string>
     <string name="improved_account_security_title" msgid="1069841917893513424">"ความปลอดภัยของบัญชีที่เพิ่มมากขึ้น"</string>
     <string name="improved_account_security_detail" msgid="9123750251551844860">"คีย์ที่สร้างขึ้นแต่ละคีย์จะลิงก์กับแอปหรือเว็บไซต์ที่ใช้งานคีย์ดังกล่าวเท่านั้น ดังนั้นจึงไม่มีการลงชื่อเข้าใช้แอปเว็บไซต์ที่เป็นการฉ้อโกงโดยไม่ตั้งใจเกิดขึ้น นอกจากนี้ เซิร์ฟเวอร์จะบันทึกเฉพาะคีย์สาธารณะ จึงทำให้แฮ็กได้ยากขึ้น"</string>
     <string name="seamless_transition_title" msgid="5335622196351371961">"การเปลี่ยนผ่านอย่างราบรื่น"</string>
@@ -33,10 +49,13 @@
     <string name="sign_ins" msgid="4710739369149469208">"การลงชื่อเข้าใช้"</string>
     <string name="sign_in_info" msgid="2627704710674232328">"ข้อมูลการลงชื่อเข้าใช้"</string>
     <string name="save_credential_to_title" msgid="3172811692275634301">"บันทึก<xliff:g id="CREDENTIALTYPES">%1$s</xliff:g>ไปยัง"</string>
-    <string name="create_passkey_in_other_device_title" msgid="9195411122362461390">"สร้างพาสคีย์ในอุปกรณ์อื่นไหม"</string>
+    <string name="create_passkey_in_other_device_title" msgid="2360053098931886245">"สร้างพาสคีย์ในอุปกรณ์อื่นใช่ไหม"</string>
+    <string name="save_password_on_other_device_title" msgid="5829084591948321207">"บันทึกรหัสผ่านในอุปกรณ์อื่นใช่ไหม"</string>
+    <string name="save_sign_in_on_other_device_title" msgid="2827990118560134692">"บันทึกการลงชื่อเข้าใช้ในอุปกรณ์อื่นใช่ไหม"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"ใช้ <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> สำหรับการลงชื่อเข้าใช้ทั้งหมดใช่ไหม"</string>
-    <string name="use_provider_for_all_description" msgid="8466427781848268490">"เครื่องมือจัดการรหัสผ่านนี้จะจัดเก็บรหัสผ่านและพาสคีย์ไว้เพื่อช่วยให้คุณลงชื่อเข้าใช้ได้โดยง่าย"</string>
+    <string name="use_provider_for_all_description" msgid="1998772715863958997">"เครื่องมือจัดการรหัสผ่านสำหรับ <xliff:g id="USERNAME">%1$s</xliff:g> นี้จะจัดเก็บรหัสผ่านและพาสคีย์ไว้เพื่อช่วยให้คุณลงชื่อเข้าใช้ได้โดยง่าย"</string>
     <string name="set_as_default" msgid="4415328591568654603">"ตั้งเป็นค่าเริ่มต้น"</string>
+    <string name="settings" msgid="6536394145760913145">"การตั้งค่า"</string>
     <string name="use_once" msgid="9027366575315399714">"ใช้ครั้งเดียว"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"รหัสผ่าน <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> รายการ • พาสคีย์ <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> รายการ"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"รหัสผ่าน <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> รายการ"</string>
@@ -47,21 +66,30 @@
     <string name="other_password_manager" msgid="565790221427004141">"เครื่องมือจัดการรหัสผ่านอื่นๆ"</string>
     <string name="close_sheet" msgid="1393792015338908262">"ปิดชีต"</string>
     <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"กลับไปยังหน้าก่อนหน้า"</string>
-    <string name="accessibility_close_button" msgid="2953807735590034688">"ปิดการแนะนำการดำเนินการของเครื่องมือจัดการข้อมูลเข้าสู่ระบบซึ่งปรากฏที่ด้านล่างของหน้าจอ"</string>
-    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"ใช้พาสคีย์ที่บันทึกไว้สำหรับ \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" ใช่ไหม"</string>
-    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"ใช้การลงชื่อเข้าใช้ที่บันทึกไว้สำหรับ \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" ใช่ไหม"</string>
-    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"เลือกการลงชื่อเข้าใช้ที่บันทึกไว้สำหรับ \"<xliff:g id="APP_NAME">%1$s</xliff:g>\""</string>
+    <string name="accessibility_close_button" msgid="1163435587545377687">"ปิด"</string>
+    <string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"ปิด"</string>
+    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"ใช้พาสคีย์ที่บันทึกไว้สำหรับ <xliff:g id="APP_NAME">%1$s</xliff:g> ใช่ไหม"</string>
+    <string name="get_dialog_title_use_password_for" msgid="625828023234318484">"ใช้รหัสผ่านที่บันทึกไว้สำหรับ <xliff:g id="APP_NAME">%1$s</xliff:g> ใช่ไหม"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"ใช้การลงชื่อเข้าใช้สำหรับ <xliff:g id="APP_NAME">%1$s</xliff:g> ใช่ไหม"</string>
+    <string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"ปลดล็อกตัวเลือกการลงชื่อเข้าใช้สำหรับ <xliff:g id="APP_NAME">%1$s</xliff:g> ใช่ไหม"</string>
+    <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"เลือกพาสคีย์ที่บันทึกไว้สำหรับ <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"เลือกรหัสผ่านที่บันทึกไว้สำหรับ <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_saved_sign_in_for" msgid="2420298653461652728">"เลือกการลงชื่อเข้าใช้ที่บันทึกไว้สำหรับ \"<xliff:g id="APP_NAME">%1$s</xliff:g>\""</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="3048870756117876514">"เลือกการลงชื่อเข้าใช้สำหรับ <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_option_for" msgid="4976380044745029107">"ต้องการเลือกตัวเลือกสำหรับ <xliff:g id="APP_NAME">%1$s</xliff:g> ไหม"</string>
+    <string name="get_dialog_title_use_info_on" msgid="8863708099535435146">"ใช้ข้อมูลนี้กับ <xliff:g id="APP_NAME">%1$s</xliff:g> ไหม"</string>
     <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"ลงชื่อเข้าใช้ด้วยวิธีอื่น"</string>
     <string name="snackbar_action" msgid="37373514216505085">"ดูตัวเลือก"</string>
     <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"ต่อไป"</string>
     <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"ตัวเลือกการลงชื่อเข้าใช้"</string>
+    <string name="button_label_view_more" msgid="3429098227286495651">"ดูเพิ่มเติม"</string>
     <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"สำหรับ <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"เครื่องมือจัดการรหัสผ่านที่ล็อกไว้"</string>
-    <!-- no translation found for locked_credential_entry_label_subtext_tap_to_unlock (6390367581393605009) -->
-    <skip />
-    <!-- no translation found for locked_credential_entry_label_subtext_no_sign_in (8131725029983174901) -->
-    <skip />
+    <string name="locked_credential_entry_label_subtext_tap_to_unlock" msgid="6390367581393605009">"แตะเพื่อปลดล็อก"</string>
+    <string name="locked_credential_entry_label_subtext_no_sign_in" msgid="8131725029983174901">"ไม่มีข้อมูลการลงชื่อเข้าใช้"</string>
+    <string name="no_sign_in_info_in" msgid="2641118151920288356">"ไม่มีข้อมูลการลงชื่อเข้าใช้ใน <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"จัดการการลงชื่อเข้าใช้"</string>
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"จากอุปกรณ์อื่น"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"ใช้อุปกรณ์อื่น"</string>
+    <string name="request_cancelled_by" msgid="3735222326886267820">"ยกเลิกคำขอแล้วโดย <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
 </resources>
diff --git a/packages/CredentialManager/res/values-tl/strings.xml b/packages/CredentialManager/res/values-tl/strings.xml
index 431ce07..e33f1bf 100644
--- a/packages/CredentialManager/res/values-tl/strings.xml
+++ b/packages/CredentialManager/res/values-tl/strings.xml
@@ -1,15 +1,29 @@
 <?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.
+   -->
+
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="4539824758261855508">"Manager ng Kredensyal"</string>
     <string name="string_cancel" msgid="6369133483981306063">"Kanselahin"</string>
     <string name="string_continue" msgid="1346732695941131882">"Magpatuloy"</string>
-    <string name="string_more_options" msgid="7990658711962795124">"Higit pang opsyon"</string>
+    <string name="string_more_options" msgid="2763852250269945472">"I-save sa ibang paraan"</string>
     <string name="string_learn_more" msgid="4541600451688392447">"Matuto pa"</string>
-    <!-- no translation found for content_description_show_password (3283502010388521607) -->
-    <skip />
-    <!-- no translation found for content_description_hide_password (6841375971631767996) -->
-    <skip />
+    <string name="content_description_show_password" msgid="3283502010388521607">"Ipakita ang password"</string>
+    <string name="content_description_hide_password" msgid="6841375971631767996">"Itago ang password"</string>
     <string name="passkey_creation_intro_title" msgid="4251037543787718844">"Mas ligtas gamit ang mga passkey"</string>
     <string name="passkey_creation_intro_body_password" msgid="8825872426579958200">"Gamit ang mga passkey, hindi mo na kailangang gumawa o tumanda ng mga komplikadong password"</string>
     <string name="passkey_creation_intro_body_fingerprint" msgid="7331338631826254055">"Ang mga passkey ay mga naka-encrypt na digital na susi na ginagawa mo gamit ang iyong fingerprint, mukha, o lock ng screen"</string>
@@ -18,13 +32,13 @@
     <string name="passwordless_technology_title" msgid="2497513482056606668">"Teknolohiyang hindi gumagamit ng password"</string>
     <string name="passwordless_technology_detail" msgid="6853928846532955882">"Sa pamamagitan ng mga passkey, makakapag-sign in ka nang hindi umaasa sa mga password. Kailangan mo lang gamitin ang iyong fingerprint, pagkilala ng mukha, PIN, o swipe pattern para i-verify ang pagkakakilanlan mo at gumawa ng passkey."</string>
     <string name="public_key_cryptography_title" msgid="6751970819265298039">"Cryptography ng pampublikong key"</string>
-    <string name="public_key_cryptography_detail" msgid="6937631710280562213">"Batay sa FIDO Alliance (na kinabibilangan ng Google, Apple, Microsoft, atbp) at W3C standards, gumagamit ang passkeys ng cryptographic key pairs. Hindi tulad ng username at string ng characters na ginagamit sa password, para sa app o website ginagawa ang private-public key pair. Ligtas na naka-store sa device o password manager ang private key at kinukumpirma nito ang identity. Naka-share sa app o website server ang public key. Gamit ang key, instant kang makakapag-register at makakapag-sign in."</string>
+    <string name="public_key_cryptography_detail" msgid="6937631710280562213">"Batay sa FIDO Alliance (na kinabibilangan ng Google, Apple, Microsoft, atbp) at W3C standards, gumagamit ang mga passkey ng cryptographic key pairs. Hindi tulad ng username at string ng characters na ginagamit sa password, para sa app o website ginagawa ang private-public key pair. Ligtas na naka-store sa device o password manager ang private key at kinukumpirma nito ang identity. Naka-share sa app o website server ang public key. Gamit ang key, instant kang makakapag-register at makakapag-sign in."</string>
     <string name="improved_account_security_title" msgid="1069841917893513424">"Pinahusay na seguridad sa account"</string>
     <string name="improved_account_security_detail" msgid="9123750251551844860">"Eksklusibong naka-link sa app o website kung para saan ginawa ang bawat key, kaya hindi ka makakapag-sign in sa isang mapanlokong app o website nang hindi sinasadya. Bukod pa rito, dahil mga pampublikong key lang ang itinatabi ng mga server, lubos na mas mahirap ang pag-hack."</string>
     <string name="seamless_transition_title" msgid="5335622196351371961">"Madaling transition"</string>
     <string name="seamless_transition_detail" msgid="4475509237171739843">"Habang lumalayo tayo sa mga password, magiging available pa rin ang mga password kasama ng mga passkey."</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"Piliin kung saan mo ise-save ang iyong <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
-    <string name="choose_provider_body" msgid="4967074531845147434">"Pumili ng password manger para ma-save ang iyong impormasyon at makapag-sign in nang mas mabilis sa susunod na pagkakataon"</string>
+    <string name="choose_provider_body" msgid="4967074531845147434">"Pumili ng password manager para ma-save ang iyong impormasyon at makapag-sign in nang mas mabilis sa susunod na pagkakataon"</string>
     <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"Gumawa ng passkey para sa <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_password_title" msgid="7097275038523578687">"I-save ang password para sa <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"I-save ang impormasyon sa pag-sign in para sa <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
@@ -35,10 +49,13 @@
     <string name="sign_ins" msgid="4710739369149469208">"mga sign-in"</string>
     <string name="sign_in_info" msgid="2627704710674232328">"impormasyon sa pag-sign in"</string>
     <string name="save_credential_to_title" msgid="3172811692275634301">"I-save ang <xliff:g id="CREDENTIALTYPES">%1$s</xliff:g> sa"</string>
-    <string name="create_passkey_in_other_device_title" msgid="9195411122362461390">"Gumawa ng passkey sa ibang device?"</string>
+    <string name="create_passkey_in_other_device_title" msgid="2360053098931886245">"Gumawa ng passkey sa ibang device?"</string>
+    <string name="save_password_on_other_device_title" msgid="5829084591948321207">"I-save ang password sa ibang device?"</string>
+    <string name="save_sign_in_on_other_device_title" msgid="2827990118560134692">"I-save ang pag-sign in sa ibang device?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"Gamitin ang <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> para sa lahat ng iyong pag-sign in?"</string>
-    <string name="use_provider_for_all_description" msgid="8466427781848268490">"Iso-store ng password manager na ito ang iyong mga password at passkey para madali kang makapag-sign in"</string>
+    <string name="use_provider_for_all_description" msgid="1998772715863958997">"Iso-store ng password manager na ito para sa <xliff:g id="USERNAME">%1$s</xliff:g> ang iyong mga password at passkey para madali kang makapag-sign in"</string>
     <string name="set_as_default" msgid="4415328591568654603">"Itakda bilang default"</string>
+    <string name="settings" msgid="6536394145760913145">"Mga Setting"</string>
     <string name="use_once" msgid="9027366575315399714">"Gamitin nang isang beses"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> (na) password • <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> (na) passkey"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> (na) password"</string>
@@ -49,21 +66,30 @@
     <string name="other_password_manager" msgid="565790221427004141">"Iba pang password manager"</string>
     <string name="close_sheet" msgid="1393792015338908262">"Isara ang sheet"</string>
     <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Bumalik sa nakaraang page"</string>
-    <string name="accessibility_close_button" msgid="2953807735590034688">"Isara ang suhestyon sa pagkilos ng Manager ng Kredensyal na lumalabas sa ibaba ng screen"</string>
+    <string name="accessibility_close_button" msgid="1163435587545377687">"Isara"</string>
+    <string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"I-dismiss"</string>
     <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Gamitin ang iyong naka-save na passkey para sa <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
-    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Gamitin ang iyong naka-save na sign-in para sa <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
-    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Pumili ng naka-save na sign-in para sa <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_use_password_for" msgid="625828023234318484">"Gamitin ang iyong naka-save na password para sa <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"Gamitin ang iyong sign-in para sa <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"I-unlock ang mga opsyon sa pag-sign in para sa <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"Pumili ng naka-save na passkey para sa <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"Pumili ng naka-save na password para sa <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_saved_sign_in_for" msgid="2420298653461652728">"Pumili ng naka-save na sign-in para sa <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="3048870756117876514">"Pumili ng sign-in para sa <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_option_for" msgid="4976380044745029107">"Pumili ng opsyon para sa <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_info_on" msgid="8863708099535435146">"Gamitin ang impormasyong ito sa <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Mag-sign in sa ibang paraan"</string>
     <string name="snackbar_action" msgid="37373514216505085">"Mga opsyon sa view"</string>
     <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Magpatuloy"</string>
     <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Mga opsyon sa pag-sign in"</string>
+    <string name="button_label_view_more" msgid="3429098227286495651">"Tumingin pa"</string>
     <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Para kay <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Mga naka-lock na password manager"</string>
-    <!-- no translation found for locked_credential_entry_label_subtext_tap_to_unlock (6390367581393605009) -->
-    <skip />
-    <!-- no translation found for locked_credential_entry_label_subtext_no_sign_in (8131725029983174901) -->
-    <skip />
+    <string name="locked_credential_entry_label_subtext_tap_to_unlock" msgid="6390367581393605009">"I-tap para i-unlock"</string>
+    <string name="locked_credential_entry_label_subtext_no_sign_in" msgid="8131725029983174901">"Walang impormasyon sa pag-sign in"</string>
+    <string name="no_sign_in_info_in" msgid="2641118151920288356">"Walang impormasyon sa pag-sign in sa <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Pamahalaan ang mga sign-in"</string>
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Mula sa ibang device"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Gumamit ng ibang device"</string>
+    <string name="request_cancelled_by" msgid="3735222326886267820">"Kinansela ng <xliff:g id="APP_NAME">%1$s</xliff:g> ang kahilingan"</string>
 </resources>
diff --git a/packages/CredentialManager/res/values-tr/strings.xml b/packages/CredentialManager/res/values-tr/strings.xml
index 0cf5cfd..30d1773 100644
--- a/packages/CredentialManager/res/values-tr/strings.xml
+++ b/packages/CredentialManager/res/values-tr/strings.xml
@@ -1,69 +1,95 @@
 <?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.
+   -->
+
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="4539824758261855508">"Kimlik Bilgisi Yöneticisi"</string>
     <string name="string_cancel" msgid="6369133483981306063">"İptal"</string>
     <string name="string_continue" msgid="1346732695941131882">"Devam"</string>
-    <string name="string_more_options" msgid="7990658711962795124">"Diğer seçenekler"</string>
+    <string name="string_more_options" msgid="2763852250269945472">"Başka şekilde kaydet"</string>
     <string name="string_learn_more" msgid="4541600451688392447">"Daha fazla bilgi"</string>
-    <!-- no translation found for content_description_show_password (3283502010388521607) -->
-    <skip />
-    <!-- no translation found for content_description_hide_password (6841375971631767996) -->
-    <skip />
-    <string name="passkey_creation_intro_title" msgid="4251037543787718844">"Şifre anahtarlarıyla daha yüksek güvenlik"</string>
-    <string name="passkey_creation_intro_body_password" msgid="8825872426579958200">"Şifre anahtarı kullandığınızda karmaşık şifreler oluşturmanız veya bunları hatırlamanız gerekmez"</string>
-    <string name="passkey_creation_intro_body_fingerprint" msgid="7331338631826254055">"Şifre anahtarları; parmak iziniz, yüzünüz veya ekran kilidinizi kullanarak oluşturduğunuz şifrelenmiş dijital anahtarlardır"</string>
+    <string name="content_description_show_password" msgid="3283502010388521607">"Şifreyi göster"</string>
+    <string name="content_description_hide_password" msgid="6841375971631767996">"Şifreyi gizle"</string>
+    <string name="passkey_creation_intro_title" msgid="4251037543787718844">"Geçiş anahtarlarıyla daha yüksek güvenlik"</string>
+    <string name="passkey_creation_intro_body_password" msgid="8825872426579958200">"Geçiş anahtarı kullandığınızda karmaşık şifreler oluşturmanız veya bunları hatırlamanız gerekmez"</string>
+    <string name="passkey_creation_intro_body_fingerprint" msgid="7331338631826254055">"Geçiş anahtarları; parmak iziniz, yüzünüz veya ekran kilidinizi kullanarak oluşturduğunuz şifrelenmiş dijital anahtarlardır"</string>
     <string name="passkey_creation_intro_body_device" msgid="1203796455762131631">"Diğer cihazlarda oturum açabilmeniz için şifre anahtarları bir şifre yöneticisine kaydedilir"</string>
-    <string name="more_about_passkeys_title" msgid="7797903098728837795">"Şifre anahtarları hakkında daha fazla bilgi"</string>
+    <string name="more_about_passkeys_title" msgid="7797903098728837795">"Geçiş anahtarları hakkında daha fazla bilgi"</string>
     <string name="passwordless_technology_title" msgid="2497513482056606668">"Şifresiz teknoloji"</string>
-    <string name="passwordless_technology_detail" msgid="6853928846532955882">"Şifre anahtarları, şifre kullanmadan oturum açmanıza olanak tanır. Kimliğinizi doğrulayıp şifre anahtarı oluşturmak için parmak iziniz, yüz tanıma özelliği, PIN veya kaydırma deseni kullanmanız yeterlidir."</string>
+    <string name="passwordless_technology_detail" msgid="6853928846532955882">"Geçiş anahtarları, şifre kullanmadan oturum açmanıza olanak tanır. Kimliğinizi doğrulayıp geçiş anahtarı oluşturmak için parmak iziniz, yüz tanıma özelliği, PIN veya kaydırma deseni kullanmanız yeterlidir."</string>
     <string name="public_key_cryptography_title" msgid="6751970819265298039">"Ortak anahtar kriptografisi"</string>
-    <string name="public_key_cryptography_detail" msgid="6937631710280562213">"Şifre anahtarları, FIDO Alliance (Google, Apple, Microsoft ve daha pek çok şirket yer alır) ve W3C standartları uyarınca şifreleme anahtarı çiftleri kullanır. Şifrelerde kullandığımız kullanıcı adı ve karakter dizisinden farklı olarak bir uygulama veya web sitesi için özel-ortak anahtar çifti oluşturulur. Özel anahtar, cihazınızda ya da şifre yöneticinizde güvenle saklanır ve kimliğinizi doğrular. Ortak anahtar, uygulama veya web sitesi sunucusuyla paylaşılır. İlgili anahtarları kullanarak anında kaydolabilir ve oturum açabilirsiniz."</string>
+    <string name="public_key_cryptography_detail" msgid="6937631710280562213">"Geçiş anahtarları, FIDO Alliance (Google, Apple, Microsoft ve daha pek çok şirket yer alır) ve W3C standartları uyarınca şifreleme anahtarı çiftleri kullanır. Şifrelerde kullandığımız kullanıcı adı ve karakter dizisinden farklı olarak bir uygulama veya web sitesi için özel-ortak anahtar çifti oluşturulur. Özel anahtar, cihazınızda ya da şifre yöneticinizde güvenle saklanır ve kimliğinizi doğrular. Ortak anahtar, uygulama veya web sitesi sunucusuyla paylaşılır. İlgili anahtarları kullanarak anında kaydolabilir ve oturum açabilirsiniz."</string>
     <string name="improved_account_security_title" msgid="1069841917893513424">"Daha iyi hesap güvenliği"</string>
     <string name="improved_account_security_detail" msgid="9123750251551844860">"Her anahtar, oluşturulduğu uygulama veya web sitesiyle özel olarak bağlantılı olduğu için sahte bir uygulamaya veya web sitesine hiçbir zaman yanlışlıkla giriş yapamazsınız. Ayrıca, sunucularda yalnızca ortak anahtarlar saklandığı için saldırıya uğramak daha zordur."</string>
     <string name="seamless_transition_title" msgid="5335622196351371961">"Sorunsuz geçiş"</string>
-    <string name="seamless_transition_detail" msgid="4475509237171739843">"Şifresiz bir geleceğe doğru ilerlerken şifreler, şifre anahtarlarıyla birlikte kullanılmaya devam edecektir."</string>
-    <string name="choose_provider_title" msgid="8870795677024868108">"<xliff:g id="CREATETYPES">%1$s</xliff:g> bilgilerinizin kaydedileceği yeri seçin"</string>
+    <string name="seamless_transition_detail" msgid="4475509237171739843">"Şifresiz bir geleceğe doğru ilerlerken şifreler, geçiş anahtarlarıyla birlikte kullanılmaya devam edecektir."</string>
+    <string name="choose_provider_title" msgid="8870795677024868108">"<xliff:g id="CREATETYPES">%1$s</xliff:g> kaydedileceği yeri seçin"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"Bilgilerinizi kaydedip bir dahaki sefere daha hızlı oturum açmak için bir şifre yöneticisi seçin"</string>
     <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"<xliff:g id="APPNAME">%1$s</xliff:g> için şifre anahtarı oluşturulsun mu?"</string>
     <string name="choose_create_option_password_title" msgid="7097275038523578687">"<xliff:g id="APPNAME">%1$s</xliff:g> için şifre kaydedilsin mi?"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"<xliff:g id="APPNAME">%1$s</xliff:g> için oturum açma bilgileri kaydedilsin mi?"</string>
-    <string name="passkey" msgid="632353688396759522">"şifre anahtarı"</string>
-    <string name="password" msgid="6738570945182936667">"şifre"</string>
-    <string name="passkeys" msgid="5733880786866559847">"şifre anahtarları"</string>
+    <string name="passkey" msgid="632353688396759522">"Şifre anahtarı"</string>
+    <string name="password" msgid="6738570945182936667">"Şifre"</string>
+    <string name="passkeys" msgid="5733880786866559847">"Geçiş anahtarlarınızın"</string>
     <string name="passwords" msgid="5419394230391253816">"şifreler"</string>
     <string name="sign_ins" msgid="4710739369149469208">"oturum aç"</string>
-    <string name="sign_in_info" msgid="2627704710674232328">"oturum açma bilgileri"</string>
-    <string name="save_credential_to_title" msgid="3172811692275634301">"Şuraya kaydet: <xliff:g id="CREDENTIALTYPES">%1$s</xliff:g>"</string>
-    <string name="create_passkey_in_other_device_title" msgid="9195411122362461390">"Başka bir cihazda şifre anahtarı oluşturulsun mu?"</string>
+    <string name="sign_in_info" msgid="2627704710674232328">"Oturum açma bilgileri"</string>
+    <string name="save_credential_to_title" msgid="3172811692275634301">"<xliff:g id="CREDENTIALTYPES">%1$s</xliff:g> nereye kaydedilsin?"</string>
+    <string name="create_passkey_in_other_device_title" msgid="2360053098931886245">"Başka bir cihazda geçiş anahtarı oluşturulsun mu?"</string>
+    <string name="save_password_on_other_device_title" msgid="5829084591948321207">"Şifre başka bir cihaza kaydedilsin mi?"</string>
+    <string name="save_sign_in_on_other_device_title" msgid="2827990118560134692">"Oturum açma bilgileri başka bir cihaza kaydedilsin mi?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"Tüm oturum açma işlemlerinizde <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> kullanılsın mı?"</string>
-    <string name="use_provider_for_all_description" msgid="8466427781848268490">"Bu şifre yöneticisi, şifrelerinizi ve şifre anahtarlarınızı saklayarak kolayca oturum açmanıza yardımcı olur"</string>
+    <string name="use_provider_for_all_description" msgid="1998772715863958997">"<xliff:g id="USERNAME">%1$s</xliff:g> için bu şifre yöneticisi, şifrelerinizi ve geçiş anahtarlarınızı saklayarak kolayca oturum açmanıza yardımcı olur"</string>
     <string name="set_as_default" msgid="4415328591568654603">"Varsayılan olarak ayarla"</string>
+    <string name="settings" msgid="6536394145760913145">"Ayarlar"</string>
     <string name="use_once" msgid="9027366575315399714">"Bir kez kullanın"</string>
-    <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> şifre • <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> şifre anahtarı"</string>
+    <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> şifre • <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> geçiş anahtarı"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> şifre"</string>
-    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> şifre anahtarı"</string>
+    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> geçiş anahtarı"</string>
     <string name="more_options_usage_credentials" msgid="1785697001787193984">"<xliff:g id="TOTALCREDENTIALSNUMBER">%1$s</xliff:g> kimlik bilgileri"</string>
     <string name="passkey_before_subtitle" msgid="2448119456208647444">"Şifre anahtarı"</string>
     <string name="another_device" msgid="5147276802037801217">"Başka bir cihaz"</string>
     <string name="other_password_manager" msgid="565790221427004141">"Diğer şifre yöneticileri"</string>
     <string name="close_sheet" msgid="1393792015338908262">"Sayfayı kapat"</string>
     <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Önceki sayfaya geri dön"</string>
-    <string name="accessibility_close_button" msgid="2953807735590034688">"Ekranın altında görünen Kimlik Bilgisi Yöneticisi işlem önerisini kapatın"</string>
+    <string name="accessibility_close_button" msgid="1163435587545377687">"Kapat"</string>
+    <string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"Kapat"</string>
     <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"<xliff:g id="APP_NAME">%1$s</xliff:g> için kayıtlı şifre anahtarınız kullanılsın mı?"</string>
-    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"<xliff:g id="APP_NAME">%1$s</xliff:g> için kayıtlı oturum açma bilgileriniz kullanılsın mı?"</string>
-    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"<xliff:g id="APP_NAME">%1$s</xliff:g> için kayıtlı oturum açma bilgilerini kullanın"</string>
+    <string name="get_dialog_title_use_password_for" msgid="625828023234318484">"<xliff:g id="APP_NAME">%1$s</xliff:g> için kayıtlı şifreniz kullanılsın mı?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"<xliff:g id="APP_NAME">%1$s</xliff:g> için oturum açma bilgileriniz kullanılsın mı?"</string>
+    <string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"<xliff:g id="APP_NAME">%1$s</xliff:g> için oturum açma seçeneklerine izin verilsin mi?"</string>
+    <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"<xliff:g id="APP_NAME">%1$s</xliff:g> için kayıtlı bir geçiş anahtarı kullanın"</string>
+    <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"<xliff:g id="APP_NAME">%1$s</xliff:g> için kayıtlı bir şifre kullanın"</string>
+    <string name="get_dialog_title_choose_saved_sign_in_for" msgid="2420298653461652728">"<xliff:g id="APP_NAME">%1$s</xliff:g> için kayıtlı oturum açma bilgilerini kullanın"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="3048870756117876514">"<xliff:g id="APP_NAME">%1$s</xliff:g> için oturum açma bilgilerini seçin"</string>
+    <string name="get_dialog_title_choose_option_for" msgid="4976380044745029107">"<xliff:g id="APP_NAME">%1$s</xliff:g> için bir seçim yapmak ister misiniz?"</string>
+    <string name="get_dialog_title_use_info_on" msgid="8863708099535435146">"Bu bilgiler <xliff:g id="APP_NAME">%1$s</xliff:g> uygulamasında kullanılsın mı?"</string>
     <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Başka bir yöntemle oturum aç"</string>
     <string name="snackbar_action" msgid="37373514216505085">"Seçenekleri göster"</string>
     <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Devam"</string>
     <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Oturum açma seçenekleri"</string>
+    <string name="button_label_view_more" msgid="3429098227286495651">"Daha fazla"</string>
     <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"<xliff:g id="USERNAME">%1$s</xliff:g> için"</string>
     <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Kilitli şifre yöneticileri"</string>
-    <!-- no translation found for locked_credential_entry_label_subtext_tap_to_unlock (6390367581393605009) -->
-    <skip />
-    <!-- no translation found for locked_credential_entry_label_subtext_no_sign_in (8131725029983174901) -->
-    <skip />
+    <string name="locked_credential_entry_label_subtext_tap_to_unlock" msgid="6390367581393605009">"Kilidi açmak için dokunun"</string>
+    <string name="locked_credential_entry_label_subtext_no_sign_in" msgid="8131725029983174901">"Oturum açma bilgisi yok"</string>
+    <string name="no_sign_in_info_in" msgid="2641118151920288356">"<xliff:g id="SOURCE">%1$s</xliff:g> hesabında oturum açma bilgisi yok"</string>
     <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Oturum açma bilgilerini yönetin"</string>
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Başka bir cihazdan"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Farklı bir cihaz kullan"</string>
+    <string name="request_cancelled_by" msgid="3735222326886267820">"İstek, <xliff:g id="APP_NAME">%1$s</xliff:g> tarafından iptal edildi"</string>
 </resources>
diff --git a/packages/CredentialManager/res/values-uk/strings.xml b/packages/CredentialManager/res/values-uk/strings.xml
index 30c085e..78a5a5b 100644
--- a/packages/CredentialManager/res/values-uk/strings.xml
+++ b/packages/CredentialManager/res/values-uk/strings.xml
@@ -1,15 +1,29 @@
 <?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.
+   -->
+
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="4539824758261855508">"Диспетчер облікових даних"</string>
     <string name="string_cancel" msgid="6369133483981306063">"Скасувати"</string>
     <string name="string_continue" msgid="1346732695941131882">"Продовжити"</string>
-    <string name="string_more_options" msgid="7990658711962795124">"Інші опції"</string>
+    <string name="string_more_options" msgid="2763852250269945472">"Зберегти інший спосіб"</string>
     <string name="string_learn_more" msgid="4541600451688392447">"Докладніше"</string>
-    <!-- no translation found for content_description_show_password (3283502010388521607) -->
-    <skip />
-    <!-- no translation found for content_description_hide_password (6841375971631767996) -->
-    <skip />
+    <string name="content_description_show_password" msgid="3283502010388521607">"Показати пароль"</string>
+    <string name="content_description_hide_password" msgid="6841375971631767996">"Сховати пароль"</string>
     <string name="passkey_creation_intro_title" msgid="4251037543787718844">"Ключі доступу покращують безпеку"</string>
     <string name="passkey_creation_intro_body_password" msgid="8825872426579958200">"Маючи ключі доступу, не потрібно створювати чи запам’ятовувати складні паролі"</string>
     <string name="passkey_creation_intro_body_fingerprint" msgid="7331338631826254055">"Ключі доступу – це зашифровані цифрові ключі, які ви створюєте за допомогою відбитка пальця, фейс-контролю чи засобу розблокування екрана"</string>
@@ -22,7 +36,7 @@
     <string name="improved_account_security_title" msgid="1069841917893513424">"Підвищена безпека облікового запису"</string>
     <string name="improved_account_security_detail" msgid="9123750251551844860">"Кожен ключ зв’язано виключно з додатком або веб-сайтом, для якого його створено, тому ви ніколи не зможете помилково ввійти в шахрайський додаток чи на шахрайський веб-сайт. Крім того, коли на серверах зберігаються лише відкриті ключі, зламати захист набагато складніше."</string>
     <string name="seamless_transition_title" msgid="5335622196351371961">"Плавний перехід"</string>
-    <string name="seamless_transition_detail" msgid="4475509237171739843">"На шляху до безпарольного майбутнього паролі й надалі будуть використовуватися паралельно з ключами."</string>
+    <string name="seamless_transition_detail" msgid="4475509237171739843">"На шляху до безпарольного майбутнього паролі й надалі будуть використовуватися паралельно з ключами доступу."</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"Виберіть, де зберігати <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"Виберіть менеджер паролів, щоб зберігати свої дані й надалі входити в облікові записи швидше"</string>
     <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"Створити ключ доступу для додатка <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
@@ -35,10 +49,13 @@
     <string name="sign_ins" msgid="4710739369149469208">"дані для входу"</string>
     <string name="sign_in_info" msgid="2627704710674232328">"дані для входу"</string>
     <string name="save_credential_to_title" msgid="3172811692275634301">"Зберегти <xliff:g id="CREDENTIALTYPES">%1$s</xliff:g> в"</string>
-    <string name="create_passkey_in_other_device_title" msgid="9195411122362461390">"Створити ключ доступу на іншому пристрої?"</string>
+    <string name="create_passkey_in_other_device_title" msgid="2360053098931886245">"Створити ключ доступу на іншому пристрої?"</string>
+    <string name="save_password_on_other_device_title" msgid="5829084591948321207">"Зберегти пароль на іншому пристрої?"</string>
+    <string name="save_sign_in_on_other_device_title" msgid="2827990118560134692">"Зберегти дані для входу на іншому пристрої?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"Використовувати сервіс <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> в усіх випадках входу?"</string>
-    <string name="use_provider_for_all_description" msgid="8466427781848268490">"Цей менеджер паролів зберігатиме ваші паролі та ключі доступу, щоб ви могли легко входити в облікові записи"</string>
+    <string name="use_provider_for_all_description" msgid="1998772715863958997">"Цей менеджер паролів для користувача <xliff:g id="USERNAME">%1$s</xliff:g> зберігатиме ваші паролі й ключі доступу, щоб ви могли легко входити в облікові записи"</string>
     <string name="set_as_default" msgid="4415328591568654603">"Вибрати за умовчанням"</string>
+    <string name="settings" msgid="6536394145760913145">"Налаштування"</string>
     <string name="use_once" msgid="9027366575315399714">"Скористатися раз"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"Кількість паролів: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> • Кількість ключів доступу: <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"Кількість паролів: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>"</string>
@@ -49,21 +66,30 @@
     <string name="other_password_manager" msgid="565790221427004141">"Інші менеджери паролів"</string>
     <string name="close_sheet" msgid="1393792015338908262">"Закрити аркуш"</string>
     <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Повернутися на попередню сторінку"</string>
-    <string name="accessibility_close_button" msgid="2953807735590034688">"Закрити пропозицію Диспетчера облікових даних, що відображається внизу екрана"</string>
+    <string name="accessibility_close_button" msgid="1163435587545377687">"Закрити"</string>
+    <string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"Закрити"</string>
     <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Використати збережений ключ доступу для додатка <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
-    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Використати збережені дані для входу для додатка <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
-    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Виберіть збережені дані для входу в додаток <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_use_password_for" msgid="625828023234318484">"Використати ваш збережений пароль для додатка <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"Використовувати ваші дані для входу в додаток <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"Розблокувати опції входу для додатка <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"Виберіть збережений ключ доступу для додатка <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"Виберіть збережений пароль для додатка <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_saved_sign_in_for" msgid="2420298653461652728">"Виберіть збережені дані для входу в додаток <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="3048870756117876514">"Виберіть дані для входу в додаток <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_option_for" msgid="4976380044745029107">"Вибрати варіант для додатка <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_info_on" msgid="8863708099535435146">"Використовувати ці дані в додатку <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Увійти іншим способом"</string>
     <string name="snackbar_action" msgid="37373514216505085">"Переглянути варіанти"</string>
     <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Продовжити"</string>
     <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Опції входу"</string>
+    <string name="button_label_view_more" msgid="3429098227286495651">"Переглянути більше"</string>
     <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Для користувача <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Заблоковані менеджери паролів"</string>
-    <!-- no translation found for locked_credential_entry_label_subtext_tap_to_unlock (6390367581393605009) -->
-    <skip />
-    <!-- no translation found for locked_credential_entry_label_subtext_no_sign_in (8131725029983174901) -->
-    <skip />
+    <string name="locked_credential_entry_label_subtext_tap_to_unlock" msgid="6390367581393605009">"Торкніться, щоб розблокувати"</string>
+    <string name="locked_credential_entry_label_subtext_no_sign_in" msgid="8131725029983174901">"Немає даних для входу"</string>
+    <string name="no_sign_in_info_in" msgid="2641118151920288356">"Немає даних для входу в обліковий запис <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Керування даними для входу"</string>
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"З іншого пристрою"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Використовувати інший пристрій"</string>
+    <string name="request_cancelled_by" msgid="3735222326886267820">"Додаток <xliff:g id="APP_NAME">%1$s</xliff:g> скасував запит"</string>
 </resources>
diff --git a/packages/CredentialManager/res/values-ur/strings.xml b/packages/CredentialManager/res/values-ur/strings.xml
index e1e530b..11cba46 100644
--- a/packages/CredentialManager/res/values-ur/strings.xml
+++ b/packages/CredentialManager/res/values-ur/strings.xml
@@ -1,15 +1,29 @@
 <?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.
+   -->
+
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="4539824758261855508">"سند سے متعلق مینیجر"</string>
     <string name="string_cancel" msgid="6369133483981306063">"منسوخ کریں"</string>
     <string name="string_continue" msgid="1346732695941131882">"جاری رکھیں"</string>
-    <string name="string_more_options" msgid="7990658711962795124">"مزید اختیارات"</string>
+    <string name="string_more_options" msgid="2763852250269945472">"دوسرے طریقے سے محفوظ کریں"</string>
     <string name="string_learn_more" msgid="4541600451688392447">"مزید جانیں"</string>
-    <!-- no translation found for content_description_show_password (3283502010388521607) -->
-    <skip />
-    <!-- no translation found for content_description_hide_password (6841375971631767996) -->
-    <skip />
+    <string name="content_description_show_password" msgid="3283502010388521607">"پاس ورڈ دکھائیں"</string>
+    <string name="content_description_hide_password" msgid="6841375971631767996">"پاس ورڈ چھپائیں"</string>
     <string name="passkey_creation_intro_title" msgid="4251037543787718844">"پاس کیز کے ساتھ زیادہ محفوظ"</string>
     <string name="passkey_creation_intro_body_password" msgid="8825872426579958200">"پاس کیز کے ساتھ آپ کو پیچیدہ پاس ورڈز تخلیق کرنے یا انہیں یاد رکھنے کی ضرورت نہیں ہے"</string>
     <string name="passkey_creation_intro_body_fingerprint" msgid="7331338631826254055">"پاس کیز مرموز کردہ ڈیجیٹل کلیدیں ہیں جنہیں آپ اپنا فنگر پرنٹ، چہرہ یا اسکرین لاک استعمال کرتے ہوئے تخلیق کرتے ہیں"</string>
@@ -35,10 +49,13 @@
     <string name="sign_ins" msgid="4710739369149469208">"سائن انز"</string>
     <string name="sign_in_info" msgid="2627704710674232328">"سائن ان کی معلومات"</string>
     <string name="save_credential_to_title" msgid="3172811692275634301">"<xliff:g id="CREDENTIALTYPES">%1$s</xliff:g> کو اس میں محفوظ کریں"</string>
-    <string name="create_passkey_in_other_device_title" msgid="9195411122362461390">"کسی دوسرے آلے میں پاس کی تخلیق کریں؟"</string>
+    <string name="create_passkey_in_other_device_title" msgid="2360053098931886245">"کسی دوسرے آلے پر پاسکی بنائیں؟"</string>
+    <string name="save_password_on_other_device_title" msgid="5829084591948321207">"کسی دوسرے آلے پر پاس ورڈ محفوظ کریں؟"</string>
+    <string name="save_sign_in_on_other_device_title" msgid="2827990118560134692">"کسی دوسرے آلے پر سائن ان کو محفوظ کریں؟"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"اپنے سبھی سائن انز کے لیے <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> کا استعمال کریں؟"</string>
-    <string name="use_provider_for_all_description" msgid="8466427781848268490">"آسانی سے سائن ان کرنے میں آپ کی مدد کرنے کے لیے یہ پاس ورڈ مینیجر آپ کے پاس ورڈز اور پاس کیز کو اسٹور کرے گا"</string>
+    <string name="use_provider_for_all_description" msgid="1998772715863958997">"آسانی سے سائن ان کرنے میں آپ کی مدد کرنے کے لیے یہ پاس ورڈ مینیجر <xliff:g id="USERNAME">%1$s</xliff:g> کے لیے آپ کے پاس ورڈز اور پاس کیز کو اسٹور کرے گا"</string>
     <string name="set_as_default" msgid="4415328591568654603">"بطور ڈیفالٹ سیٹ کریں"</string>
+    <string name="settings" msgid="6536394145760913145">"ترتیبات"</string>
     <string name="use_once" msgid="9027366575315399714">"ایک بار استعمال کریں"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> پاس ورڈز • <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> پاس کیز"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> پاس ورڈز"</string>
@@ -49,21 +66,30 @@
     <string name="other_password_manager" msgid="565790221427004141">"دیگر پاس ورڈ مینیجرز"</string>
     <string name="close_sheet" msgid="1393792015338908262">"شیٹ بند کریں"</string>
     <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"گزشتہ صفحے پر واپس جائیں"</string>
-    <string name="accessibility_close_button" msgid="2953807735590034688">"اسکرین کے نیچے ظاہر ہونے والے سند مینیجر کی کاروائی کی تجویز بند کریں"</string>
+    <string name="accessibility_close_button" msgid="1163435587545377687">"بند کریں"</string>
+    <string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"برخاست کریں"</string>
     <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"<xliff:g id="APP_NAME">%1$s</xliff:g> کے لیے اپنی محفوظ کردہ پاس کی استعمال کریں؟"</string>
-    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"<xliff:g id="APP_NAME">%1$s</xliff:g> کے لیے اپنے محفوظ کردہ سائن ان کو استعمال کریں؟"</string>
-    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"<xliff:g id="APP_NAME">%1$s</xliff:g> کے لیے محفوظ کردہ سائن انز منتخب کریں"</string>
+    <string name="get_dialog_title_use_password_for" msgid="625828023234318484">"<xliff:g id="APP_NAME">%1$s</xliff:g> کے لیے آپ کا محفوظ کردہ پاس ورڈ استعمال کریں؟"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"<xliff:g id="APP_NAME">%1$s</xliff:g> کے لیے اپنے سائن ان کی اسناد کا استعمال کرنا؟"</string>
+    <string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"<xliff:g id="APP_NAME">%1$s</xliff:g> کے لیے سائن ان کے اختیارات کو غیر مقفل کریں؟"</string>
+    <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"<xliff:g id="APP_NAME">%1$s</xliff:g> کے لیے ایک محفوظ کردہ پاس کی منتخب کریں"</string>
+    <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"<xliff:g id="APP_NAME">%1$s</xliff:g> کے لیے ایک محفوظ کردہ پاس ورڈ منتخب کریں"</string>
+    <string name="get_dialog_title_choose_saved_sign_in_for" msgid="2420298653461652728">"<xliff:g id="APP_NAME">%1$s</xliff:g> کے لیے محفوظ کردہ سائن ان منتخب کریں"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="3048870756117876514">"<xliff:g id="APP_NAME">%1$s</xliff:g> کے لیے سائن ان کی اسناد کا انتخاب کرنا"</string>
+    <string name="get_dialog_title_choose_option_for" msgid="4976380044745029107">"<xliff:g id="APP_NAME">%1$s</xliff:g> کے لیے ایک اختیار منتخب کریں؟"</string>
+    <string name="get_dialog_title_use_info_on" msgid="8863708099535435146">"<xliff:g id="APP_NAME">%1$s</xliff:g> پر اس معلومات کا استعمال کریں؟"</string>
     <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"دوسرے طریقے سے سائن ان کریں"</string>
     <string name="snackbar_action" msgid="37373514216505085">"اختیارات دیکھیں"</string>
     <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"جاری رکھیں"</string>
     <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"سائن ان کے اختیارات"</string>
+    <string name="button_label_view_more" msgid="3429098227286495651">"مزید دیکھیں"</string>
     <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"<xliff:g id="USERNAME">%1$s</xliff:g> کے لیے"</string>
     <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"مقفل کردہ پاس ورڈ مینیجرز"</string>
-    <!-- no translation found for locked_credential_entry_label_subtext_tap_to_unlock (6390367581393605009) -->
-    <skip />
-    <!-- no translation found for locked_credential_entry_label_subtext_no_sign_in (8131725029983174901) -->
-    <skip />
+    <string name="locked_credential_entry_label_subtext_tap_to_unlock" msgid="6390367581393605009">"غیر مقفل کرنے کیلئے تھپتھپائیں"</string>
+    <string name="locked_credential_entry_label_subtext_no_sign_in" msgid="8131725029983174901">"سائن ان کی کوئی معلومات نہیں"</string>
+    <string name="no_sign_in_info_in" msgid="2641118151920288356">"<xliff:g id="SOURCE">%1$s</xliff:g> میں سائن ان کی کوئی معلومات نہیں ہے"</string>
     <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"سائن انز کا نظم کریں"</string>
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"دوسرے آلے سے"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"ایک مختلف آلہ استعمال کریں"</string>
+    <string name="request_cancelled_by" msgid="3735222326886267820">"<xliff:g id="APP_NAME">%1$s</xliff:g> نے درخواست منسوخ کر دی"</string>
 </resources>
diff --git a/packages/CredentialManager/res/values-uz/strings.xml b/packages/CredentialManager/res/values-uz/strings.xml
index 96bcbc8..a0785b6 100644
--- a/packages/CredentialManager/res/values-uz/strings.xml
+++ b/packages/CredentialManager/res/values-uz/strings.xml
@@ -1,44 +1,63 @@
 <?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.
+   -->
+
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="4539824758261855508">"Hisob maʼlumotlari menejeri"</string>
     <string name="string_cancel" msgid="6369133483981306063">"Bekor qilish"</string>
     <string name="string_continue" msgid="1346732695941131882">"Davom etish"</string>
-    <string name="string_more_options" msgid="7990658711962795124">"Boshqa parametrlar"</string>
+    <string name="string_more_options" msgid="2763852250269945472">"Boshqa usulda saqlang"</string>
     <string name="string_learn_more" msgid="4541600451688392447">"Batafsil"</string>
     <string name="content_description_show_password" msgid="3283502010388521607">"Parolni koʻrsatish"</string>
     <string name="content_description_hide_password" msgid="6841375971631767996">"Parolni berkitish"</string>
     <string name="passkey_creation_intro_title" msgid="4251037543787718844">"Kalitlar orqali qulay"</string>
-    <string name="passkey_creation_intro_body_password" msgid="8825872426579958200">"Kodlar yordami tufayli murakkab parollarni yaratish va eslab qolish shart emas"</string>
-    <string name="passkey_creation_intro_body_fingerprint" msgid="7331338631826254055">"Kodlar – bu barmoq izi, yuz yoki ekran qulfi yordamida yaratilgan shifrlangan raqamli identifikator."</string>
+    <string name="passkey_creation_intro_body_password" msgid="8825872426579958200">"Kalitlar yordami tufayli murakkab parollarni yaratish va eslab qolish shart emas"</string>
+    <string name="passkey_creation_intro_body_fingerprint" msgid="7331338631826254055">"Kalitlar – bu barmoq izi, yuz yoki ekran qulfi yordamida yaratilgan shifrlangan raqamli identifikator."</string>
     <string name="passkey_creation_intro_body_device" msgid="1203796455762131631">"Ular parollar menejerida saqlanadi va ulardan boshqa qurilmalarda hisobga kirib foydalanish mumkin"</string>
-    <string name="more_about_passkeys_title" msgid="7797903098728837795">"Kodlar haqida batafsil"</string>
+    <string name="more_about_passkeys_title" msgid="7797903098728837795">"Kalitlar haqida batafsil"</string>
     <string name="passwordless_technology_title" msgid="2497513482056606668">"Parolsiz texnologiya"</string>
-    <string name="passwordless_technology_detail" msgid="6853928846532955882">"Kodlar parollarga tayanmasdan tizimga kirish imkonini beradi. Shaxsingizni tasdiqlash va kod yaratish uchun barmoq izi, yuzni tanish, PIN kod yoki grafik kalitni surishdan foydalanishingiz kifoya."</string>
+    <string name="passwordless_technology_detail" msgid="6853928846532955882">"Kalitlar tizimga parol ishlatmasdan kirish imkonini beradi. Shaxsingizni tasdiqlash va kod yaratish uchun barmoq izi, yuzni tanish, PIN kod yoki grafik kalitni surishdan foydalanishingiz kifoya."</string>
     <string name="public_key_cryptography_title" msgid="6751970819265298039">"Ochiq kalit kriptografiyasi"</string>
     <string name="public_key_cryptography_detail" msgid="6937631710280562213">"FIDO Alliance (Google, Apple, Microsoft va boshqalar) va W3C standartlari asosida kodlar kriptografik kalitlar juftligidan foydalanadi. Parollarda ishlatiladigan foydalanuvchi nomi va belgilardan farqli ravishda, ilova yoki veb-sayt uchun maxfiy ochiq kalitlar juftligi yaratiladi. Maxfiy kalit qurilmangizda yoki parollar menejerida xavfsiz saqlanadi va u shaxsingizni tasdiqlaydi. Ochiq kalit ilova yoki veb-sayt serveriga ulashiladi. Mos kalitlar bilan darhol registratsiya va tizimga kirish mumkin."</string>
     <string name="improved_account_security_title" msgid="1069841917893513424">"Hisob xavfsizligi yaxshilandi"</string>
     <string name="improved_account_security_detail" msgid="9123750251551844860">"Har bir kalit faqat ular uchun yaratilgan ilova yoki veb-sayt bilan ulangan, shuning uchun siz hech qachon xatolik bilan soxta ilova yoki veb-saytga kira olmaysiz. Shuningdek, serverlar bilan faqat ochiq kalitlarni saqlagan holda, buzib kirish ancha qiyinroq boʻladi."</string>
     <string name="seamless_transition_title" msgid="5335622196351371961">"Uzluksiz oʻtish"</string>
-    <string name="seamless_transition_detail" msgid="4475509237171739843">"Parolsiz kelajak sari borayotganimizda, parollar kodlar bilan birga ishlatilishda davom etadi."</string>
-    <string name="choose_provider_title" msgid="8870795677024868108">"<xliff:g id="CREATETYPES">%1$s</xliff:g> qayerga saqlanishini tanlang"</string>
+    <string name="seamless_transition_detail" msgid="4475509237171739843">"Parolsiz kelajak sari harakatlanar ekanmiz, parollar kalitlar bilan birga ishlatilishda davom etadi."</string>
+    <string name="choose_provider_title" msgid="8870795677024868108">"Bu <xliff:g id="CREATETYPES">%1$s</xliff:g> qayerga saqlanishini tanlang"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"Maʼlumotlaringizni saqlash va keyingi safar tez kirish uchun parollar menejerini tanlang"</string>
     <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"<xliff:g id="APPNAME">%1$s</xliff:g> uchun kod yaratilsinmi?"</string>
     <string name="choose_create_option_password_title" msgid="7097275038523578687">"<xliff:g id="APPNAME">%1$s</xliff:g> uchun parol saqlansinmi?"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"<xliff:g id="APPNAME">%1$s</xliff:g> uchun kirish maʼlumoti saqlansinmi?"</string>
     <string name="passkey" msgid="632353688396759522">"kalit"</string>
     <string name="password" msgid="6738570945182936667">"parol"</string>
-    <string name="passkeys" msgid="5733880786866559847">"kodlar"</string>
+    <string name="passkeys" msgid="5733880786866559847">"kalitlar"</string>
     <string name="passwords" msgid="5419394230391253816">"parollar"</string>
     <string name="sign_ins" msgid="4710739369149469208">"kirishlar"</string>
     <string name="sign_in_info" msgid="2627704710674232328">"kirish maʼlumoti"</string>
-    <string name="save_credential_to_title" msgid="3172811692275634301">"<xliff:g id="CREDENTIALTYPES">%1$s</xliff:g>ni saqlash"</string>
-    <string name="create_passkey_in_other_device_title" msgid="9195411122362461390">"Boshqa qurilmada kod yaratilsinmi?"</string>
+    <string name="save_credential_to_title" msgid="3172811692275634301">"Bu <xliff:g id="CREDENTIALTYPES">%1$s</xliff:g>ni saqlash"</string>
+    <string name="create_passkey_in_other_device_title" msgid="2360053098931886245">"Kalit boshqa qurilmada yaratilsinmi?"</string>
+    <string name="save_password_on_other_device_title" msgid="5829084591948321207">"Parol boshqa qurilmaga saqlansinmi?"</string>
+    <string name="save_sign_in_on_other_device_title" msgid="2827990118560134692">"Kirish maʼlumotlari boshqa qurilmaga saqlansinmi?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"Hamma kirishlarda <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ishlatilsinmi?"</string>
-    <string name="use_provider_for_all_description" msgid="8466427781848268490">"Bu parollar menejerida hisobga oson kirishga yordam beruvchi parol va kalitlar saqlanadi"</string>
+    <string name="use_provider_for_all_description" msgid="1998772715863958997">"<xliff:g id="USERNAME">%1$s</xliff:g> uchun bu parollar menejerida hisobga oson kirishga yordam beruvchi parol va kalitlar saqlanadi"</string>
     <string name="set_as_default" msgid="4415328591568654603">"Birlamchi deb belgilash"</string>
+    <string name="settings" msgid="6536394145760913145">"Sozlamalar"</string>
     <string name="use_once" msgid="9027366575315399714">"Bir marta ishlatish"</string>
-    <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> ta parol • <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> ta kod"</string>
+    <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> ta parol • <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> ta kalit"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> ta parol"</string>
     <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> ta kalit"</string>
     <string name="more_options_usage_credentials" msgid="1785697001787193984">"<xliff:g id="TOTALCREDENTIALSNUMBER">%1$s</xliff:g> hisobi maʼlumotlari"</string>
@@ -47,21 +66,30 @@
     <string name="other_password_manager" msgid="565790221427004141">"Boshqa parol menejerlari"</string>
     <string name="close_sheet" msgid="1393792015338908262">"Varaqni yopish"</string>
     <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Avvalgi sahifaga qaytish"</string>
-    <string name="accessibility_close_button" msgid="2953807735590034688">"Ekranning pastki qismida chiqadigan Hisob maʼlumotlari menejeri amali taklifini yoping"</string>
+    <string name="accessibility_close_button" msgid="1163435587545377687">"Yopish"</string>
+    <string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"Yopish"</string>
     <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"<xliff:g id="APP_NAME">%1$s</xliff:g> uchun saqlangan kalit ishlatilsinmi?"</string>
-    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"<xliff:g id="APP_NAME">%1$s</xliff:g> uchun saqlangan maʼlumotlar ishlatilsinmi?"</string>
-    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"<xliff:g id="APP_NAME">%1$s</xliff:g> hisob maʼlumotlarini tanlang"</string>
+    <string name="get_dialog_title_use_password_for" msgid="625828023234318484">"<xliff:g id="APP_NAME">%1$s</xliff:g> uchun saqlangan parol ishlatilsinmi?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"<xliff:g id="APP_NAME">%1$s</xliff:g> ilovasiga bu maʼlumotlar bilan kirilsinmi?"</string>
+    <string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"<xliff:g id="APP_NAME">%1$s</xliff:g> uchun kirish usullari ochilsinmi?"</string>
+    <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"<xliff:g id="APP_NAME">%1$s</xliff:g> uchun saqlangan kalitni tanlang"</string>
+    <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"<xliff:g id="APP_NAME">%1$s</xliff:g> uchun saqlangan parolni tanlang"</string>
+    <string name="get_dialog_title_choose_saved_sign_in_for" msgid="2420298653461652728">"<xliff:g id="APP_NAME">%1$s</xliff:g> uchun hisob maʼlumotlarini tanlang"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="3048870756117876514">"<xliff:g id="APP_NAME">%1$s</xliff:g> ilovasiga qanday kirishni tanlang"</string>
+    <string name="get_dialog_title_choose_option_for" msgid="4976380044745029107">"<xliff:g id="APP_NAME">%1$s</xliff:g> ilovasiga kirish uchun maʼlumotlar tanlansinmi?"</string>
+    <string name="get_dialog_title_use_info_on" msgid="8863708099535435146">"Bu axborotdan <xliff:g id="APP_NAME">%1$s</xliff:g> ilovasiga kirish uchun foydalanilsinmi?"</string>
     <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Boshqa usul orqali kirish"</string>
     <string name="snackbar_action" msgid="37373514216505085">"Variantlarni ochish"</string>
     <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Davom etish"</string>
     <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Kirish parametrlari"</string>
+    <string name="button_label_view_more" msgid="3429098227286495651">"Yana"</string>
     <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"<xliff:g id="USERNAME">%1$s</xliff:g> uchun"</string>
     <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Qulfli parol menejerlari"</string>
-    <!-- no translation found for locked_credential_entry_label_subtext_tap_to_unlock (6390367581393605009) -->
-    <skip />
-    <!-- no translation found for locked_credential_entry_label_subtext_no_sign_in (8131725029983174901) -->
-    <skip />
+    <string name="locked_credential_entry_label_subtext_tap_to_unlock" msgid="6390367581393605009">"Qulfni ochish uchun bosing"</string>
+    <string name="locked_credential_entry_label_subtext_no_sign_in" msgid="8131725029983174901">"Kirishga oid axborot topilmadi"</string>
+    <string name="no_sign_in_info_in" msgid="2641118151920288356">"<xliff:g id="SOURCE">%1$s</xliff:g> kirish axboroti mavjud emas"</string>
     <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Hisob maʼlumotlarini boshqarish"</string>
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Boshqa qurilmada"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Boshqa qurilmadan foydalanish"</string>
+    <string name="request_cancelled_by" msgid="3735222326886267820">"Soʻrovni <xliff:g id="APP_NAME">%1$s</xliff:g> bekor qilgan"</string>
 </resources>
diff --git a/packages/CredentialManager/res/values-vi/strings.xml b/packages/CredentialManager/res/values-vi/strings.xml
index 61b425e..0e17025 100644
--- a/packages/CredentialManager/res/values-vi/strings.xml
+++ b/packages/CredentialManager/res/values-vi/strings.xml
@@ -1,69 +1,95 @@
 <?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.
+   -->
+
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="4539824758261855508">"Trình quản lý thông tin xác thực"</string>
     <string name="string_cancel" msgid="6369133483981306063">"Huỷ"</string>
     <string name="string_continue" msgid="1346732695941131882">"Tiếp tục"</string>
-    <string name="string_more_options" msgid="7990658711962795124">"Tuỳ chọn khác"</string>
+    <string name="string_more_options" msgid="2763852250269945472">"Lưu theo cách khác"</string>
     <string name="string_learn_more" msgid="4541600451688392447">"Tìm hiểu thêm"</string>
-    <!-- no translation found for content_description_show_password (3283502010388521607) -->
-    <skip />
-    <!-- no translation found for content_description_hide_password (6841375971631767996) -->
-    <skip />
-    <string name="passkey_creation_intro_title" msgid="4251037543787718844">"An toàn hơn nhờ mã xác thực"</string>
-    <string name="passkey_creation_intro_body_password" msgid="8825872426579958200">"Mã xác thực giúp bạn tránh được việc phải tạo và ghi nhớ mật khẩu phức tạp"</string>
-    <string name="passkey_creation_intro_body_fingerprint" msgid="7331338631826254055">"Mã xác thực là các khoá kỹ thuật số được mã hoá mà bạn tạo bằng cách dùng vân tay, khuôn mặt hoặc phương thức khoá màn hình của mình"</string>
+    <string name="content_description_show_password" msgid="3283502010388521607">"Hiện mật khẩu"</string>
+    <string name="content_description_hide_password" msgid="6841375971631767996">"Ẩn mật khẩu"</string>
+    <string name="passkey_creation_intro_title" msgid="4251037543787718844">"An toàn hơn nhờ khoá truy cập"</string>
+    <string name="passkey_creation_intro_body_password" msgid="8825872426579958200">"Khoá truy cập giúp bạn tránh được việc phải tạo và ghi nhớ mật khẩu phức tạp"</string>
+    <string name="passkey_creation_intro_body_fingerprint" msgid="7331338631826254055">"Khoá truy cập là các khoá kỹ thuật số được mã hoá mà bạn tạo bằng cách dùng vân tay, khuôn mặt hoặc phương thức khoá màn hình của mình"</string>
     <string name="passkey_creation_intro_body_device" msgid="1203796455762131631">"Thông tin này được lưu vào trình quản lý mật khẩu nên bạn có thể đăng nhập trên các thiết bị khác"</string>
-    <string name="more_about_passkeys_title" msgid="7797903098728837795">"Xem thêm thông tin về mã xác thực"</string>
+    <string name="more_about_passkeys_title" msgid="7797903098728837795">"Xem thêm thông tin về khoá truy cập"</string>
     <string name="passwordless_technology_title" msgid="2497513482056606668">"Công nghệ không dùng mật khẩu"</string>
-    <string name="passwordless_technology_detail" msgid="6853928846532955882">"Mã xác thực cho phép bạn đăng nhập mà không cần dựa vào mật khẩu. Bạn chỉ cần dùng vân tay, tính năng nhận dạng khuôn mặt, mã PIN hoặc hình mở khoá để xác minh danh tính và tạo mã xác thực."</string>
-    <string name="public_key_cryptography_title" msgid="6751970819265298039">"Mật mã của khoá công khai"</string>
-    <string name="public_key_cryptography_detail" msgid="6937631710280562213">"Dựa trên Liên minh FIDO (bao gồm Google, Apple, Microsoft, v.v.) và tiêu chuẩn W3C, mã xác thực sử dụng cặp khoá mã hoá. Khác với tên người dùng và chuỗi ký tự chúng tôi dùng cho mật khẩu, một cặp khoá riêng tư – công khai được tạo cho một ứng dụng hoặc trang web. Khoá riêng tư được lưu trữ an toàn trên thiết bị hoặc trình quản lý mật khẩu và xác nhận danh tính của bạn. Khoá công khai được chia sẻ với máy chủ ứng dụng hoặc trang web. Với khoá tương ứng, bạn có thể đăng ký và đăng nhập tức thì."</string>
+    <string name="passwordless_technology_detail" msgid="6853928846532955882">"Khoá truy cập cho phép bạn đăng nhập mà không cần dựa vào mật khẩu. Bạn chỉ cần dùng vân tay, tính năng nhận dạng khuôn mặt, mã PIN hoặc hình mở khoá để xác minh danh tính và tạo khoá truy cập."</string>
+    <string name="public_key_cryptography_title" msgid="6751970819265298039">"Mã hoá khoá công khai"</string>
+    <string name="public_key_cryptography_detail" msgid="6937631710280562213">"Dựa trên Liên minh FIDO (bao gồm Google, Apple, Microsoft, v.v.) và tiêu chuẩn W3C, khoá truy cập sử dụng cặp khoá mã hoá. Khác với tên người dùng và chuỗi ký tự chúng tôi dùng cho mật khẩu, một cặp khoá riêng tư – công khai được tạo cho một ứng dụng hoặc trang web. Khoá riêng tư được lưu trữ an toàn trên thiết bị hoặc trình quản lý mật khẩu và xác nhận danh tính của bạn. Khoá công khai được chia sẻ với máy chủ ứng dụng hoặc trang web. Với khoá tương ứng, bạn có thể đăng ký và đăng nhập tức thì."</string>
     <string name="improved_account_security_title" msgid="1069841917893513424">"Cải thiện tính bảo mật của tài khoản"</string>
     <string name="improved_account_security_detail" msgid="9123750251551844860">"Mỗi khoá được liên kết riêng với ứng dụng hoặc trang web mà khoá đó được tạo. Vì vậy, bạn sẽ không bao giờ đăng nhập nhầm vào một ứng dụng hoặc trang web lừa đảo. Ngoài ra, với các máy chủ chỉ lưu giữ khoá công khai, việc xâm nhập càng khó hơn nhiều."</string>
     <string name="seamless_transition_title" msgid="5335622196351371961">"Chuyển đổi liền mạch"</string>
-    <string name="seamless_transition_detail" msgid="4475509237171739843">"Trong quá trình chúng tôi hướng đến tương lai không dùng mật khẩu, bạn vẫn sẽ dùng được mật khẩu cùng với mã xác thực."</string>
+    <string name="seamless_transition_detail" msgid="4475509237171739843">"Trong quá trình chúng tôi hướng đến tương lai không dùng mật khẩu, bạn vẫn sẽ dùng được mật khẩu cùng với khoá truy cập."</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"Chọn vị trí lưu <xliff:g id="CREATETYPES">%1$s</xliff:g> của bạn"</string>
-    <string name="choose_provider_body" msgid="4967074531845147434">"Hãy chọn một trình quản lý mật khẩu để lưu thông tin của bạn và đăng nhập nhanh hơn trong lần tới"</string>
-    <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"Tạo mã xác thực cho <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+    <string name="choose_provider_body" msgid="4967074531845147434">"Hãy chọn một trình quản lý mật khẩu để lưu thông tin của bạn và đăng nhập nhanh hơn vào lần tới"</string>
+    <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"Tạo khoá đăng nhập cho <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_password_title" msgid="7097275038523578687">"Lưu mật khẩu cho <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Lưu thông tin đăng nhập cho <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
-    <string name="passkey" msgid="632353688396759522">"mã xác thực"</string>
+    <string name="passkey" msgid="632353688396759522">"khoá đăng nhập"</string>
     <string name="password" msgid="6738570945182936667">"mật khẩu"</string>
-    <string name="passkeys" msgid="5733880786866559847">"mã xác thực"</string>
+    <string name="passkeys" msgid="5733880786866559847">"khoá truy cập"</string>
     <string name="passwords" msgid="5419394230391253816">"mật khẩu"</string>
     <string name="sign_ins" msgid="4710739369149469208">"thông tin đăng nhập"</string>
     <string name="sign_in_info" msgid="2627704710674232328">"thông tin đăng nhập"</string>
     <string name="save_credential_to_title" msgid="3172811692275634301">"Lưu <xliff:g id="CREDENTIALTYPES">%1$s</xliff:g> vào"</string>
-    <string name="create_passkey_in_other_device_title" msgid="9195411122362461390">"Tạo mã xác thực trên thiết bị khác?"</string>
+    <string name="create_passkey_in_other_device_title" msgid="2360053098931886245">"Tạo khoá truy cập trên một thiết bị khác?"</string>
+    <string name="save_password_on_other_device_title" msgid="5829084591948321207">"Lưu mật khẩu trên một thiết bị khác?"</string>
+    <string name="save_sign_in_on_other_device_title" msgid="2827990118560134692">"Lưu thông tin đăng nhập trên một thiết bị khác?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"Dùng <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> cho mọi thông tin đăng nhập của bạn?"</string>
-    <string name="use_provider_for_all_description" msgid="8466427781848268490">"Trình quản lý mật khẩu này sẽ lưu trữ mật khẩu và mã xác thực của bạn để bạn dễ dàng đăng nhập"</string>
+    <string name="use_provider_for_all_description" msgid="1998772715863958997">"Trình quản lý mật khẩu này cho <xliff:g id="USERNAME">%1$s</xliff:g> sẽ lưu trữ mật khẩu và khoá truy cập để bạn dễ dàng đăng nhập"</string>
     <string name="set_as_default" msgid="4415328591568654603">"Đặt làm mặc định"</string>
+    <string name="settings" msgid="6536394145760913145">"Cài đặt"</string>
     <string name="use_once" msgid="9027366575315399714">"Dùng một lần"</string>
-    <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> mật khẩu • <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> mã xác thực"</string>
+    <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> mật khẩu • <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> khoá truy cập"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> mật khẩu"</string>
-    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> mã xác thực"</string>
+    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> khoá truy cập"</string>
     <string name="more_options_usage_credentials" msgid="1785697001787193984">"<xliff:g id="TOTALCREDENTIALSNUMBER">%1$s</xliff:g> thông tin xác thực"</string>
-    <string name="passkey_before_subtitle" msgid="2448119456208647444">"Mã xác thực"</string>
+    <string name="passkey_before_subtitle" msgid="2448119456208647444">"Khoá truy cập"</string>
     <string name="another_device" msgid="5147276802037801217">"Thiết bị khác"</string>
     <string name="other_password_manager" msgid="565790221427004141">"Trình quản lý mật khẩu khác"</string>
     <string name="close_sheet" msgid="1393792015338908262">"Đóng trang tính"</string>
     <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Quay lại trang trước"</string>
-    <string name="accessibility_close_button" msgid="2953807735590034688">"Đóng đề xuất hành động của Trình quản lý thông tin xác thực xuất hiện ở cuối màn hình"</string>
-    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Dùng mã xác thực bạn đã lưu cho <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
-    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Dùng thông tin đăng nhập bạn đã lưu cho <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
-    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Chọn thông tin đăng nhập đã lưu cho <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="accessibility_close_button" msgid="1163435587545377687">"Đóng"</string>
+    <string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"Đóng"</string>
+    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Dùng khoá đăng nhập bạn đã lưu cho <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_password_for" msgid="625828023234318484">"Sử dụng mật khẩu bạn đã lưu cho <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"Dùng thông tin đăng nhập của bạn cho <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"Mở khoá các tuỳ chọn đăng nhập cho <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"Chọn khoá truy cập đã lưu cho <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"Chọn mật khẩu đã lưu cho <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_saved_sign_in_for" msgid="2420298653461652728">"Chọn thông tin đăng nhập đã lưu cho <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="3048870756117876514">"Chọn thông tin đăng nhập cho <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_option_for" msgid="4976380044745029107">"Chọn một lựa chọn cho <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_info_on" msgid="8863708099535435146">"Sử dụng thông tin này trên <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Đăng nhập bằng cách khác"</string>
     <string name="snackbar_action" msgid="37373514216505085">"Xem các lựa chọn"</string>
     <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Tiếp tục"</string>
     <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Tuỳ chọn đăng nhập"</string>
+    <string name="button_label_view_more" msgid="3429098227286495651">"Xem thêm"</string>
     <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Cho <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Trình quản lý mật khẩu đã khoá"</string>
-    <!-- no translation found for locked_credential_entry_label_subtext_tap_to_unlock (6390367581393605009) -->
-    <skip />
-    <!-- no translation found for locked_credential_entry_label_subtext_no_sign_in (8131725029983174901) -->
-    <skip />
+    <string name="locked_credential_entry_label_subtext_tap_to_unlock" msgid="6390367581393605009">"Nhấn để mở khoá"</string>
+    <string name="locked_credential_entry_label_subtext_no_sign_in" msgid="8131725029983174901">"Không có thông tin đăng nhập"</string>
+    <string name="no_sign_in_info_in" msgid="2641118151920288356">"Không có thông tin đăng nhập trong <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Quản lý thông tin đăng nhập"</string>
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Từ một thiết bị khác"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Dùng thiết bị khác"</string>
+    <string name="request_cancelled_by" msgid="3735222326886267820">"<xliff:g id="APP_NAME">%1$s</xliff:g> đã huỷ yêu cầu"</string>
 </resources>
diff --git a/packages/CredentialManager/res/values-zh-rCN/strings.xml b/packages/CredentialManager/res/values-zh-rCN/strings.xml
index d03be77..42eaf00 100644
--- a/packages/CredentialManager/res/values-zh-rCN/strings.xml
+++ b/packages/CredentialManager/res/values-zh-rCN/strings.xml
@@ -1,26 +1,42 @@
 <?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.
+   -->
+
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="4539824758261855508">"Credential Manager"</string>
     <string name="string_cancel" msgid="6369133483981306063">"取消"</string>
     <string name="string_continue" msgid="1346732695941131882">"继续"</string>
-    <string name="string_more_options" msgid="7990658711962795124">"更多选项"</string>
+    <string name="string_more_options" msgid="2763852250269945472">"换一种方式保存"</string>
     <string name="string_learn_more" msgid="4541600451688392447">"了解详情"</string>
     <string name="content_description_show_password" msgid="3283502010388521607">"显示密码"</string>
     <string name="content_description_hide_password" msgid="6841375971631767996">"隐藏密码"</string>
     <string name="passkey_creation_intro_title" msgid="4251037543787718844">"通行密钥可提高安全性"</string>
     <string name="passkey_creation_intro_body_password" msgid="8825872426579958200">"借助通行密钥,您无需创建或记住复杂的密码"</string>
-    <string name="passkey_creation_intro_body_fingerprint" msgid="7331338631826254055">"通行密钥是指您使用您的指纹、面孔或屏锁方式创建的加密数字钥匙"</string>
+    <string name="passkey_creation_intro_body_fingerprint" msgid="7331338631826254055">"通行密钥是指您使用自己的指纹、面孔或屏锁创建的加密数字钥匙"</string>
     <string name="passkey_creation_intro_body_device" msgid="1203796455762131631">"系统会将通行密钥保存到密码管理工具中,以便您在其他设备上登录"</string>
     <string name="more_about_passkeys_title" msgid="7797903098728837795">"详细了解通行密钥"</string>
     <string name="passwordless_technology_title" msgid="2497513482056606668">"无密码技术"</string>
     <string name="passwordless_technology_detail" msgid="6853928846532955882">"借助通行密钥,您不必依赖密码就能登录。您只需使用指纹、人脸识别功能、PIN 码或滑动图案便可验证您的身份并创建通行密钥。"</string>
     <string name="public_key_cryptography_title" msgid="6751970819265298039">"公钥加密"</string>
-    <string name="public_key_cryptography_detail" msgid="6937631710280562213">"根据 FIDO 联盟(成员包括 Google、Apple、Microsoft 等)和 W3C 的标准,通行密钥使用加密密钥对。不同于用户名及可在密码中使用的一系列字符,系统会为应用或网站创建一个私钥-公钥对。私钥会安全地存储在您的设备上或密码管理工具中,用于证实您的身份。公钥会被共享给应用或网站服务器。您只要使用相应密钥,就能瞬间注册并登录。"</string>
+    <string name="public_key_cryptography_detail" msgid="6937631710280562213">"根据 FIDO 联盟(成员包括 Google、Apple、Microsoft 等)和 W3C 的标准,通行密钥使用加密密钥对。不同于“用户名+密码字符串”的传统登录凭据,采用通行密钥时,系统会为应用或网站创建一个私钥-公钥对。私钥会安全地存储在您的设备上或密码管理工具中,用于证实您的身份。公钥会被共享给应用或网站服务器。您只要使用相应密钥,就能瞬间注册并登录。"</string>
     <string name="improved_account_security_title" msgid="1069841917893513424">"提升了帐号安全性"</string>
     <string name="improved_account_security_detail" msgid="9123750251551844860">"每个密钥都是专为特定应用或网站创建的,且仅与各自对应的网站或应用关联,因此您绝不会错误地登录任何欺诈性应用或网站。另外,由于服务器只保留公钥,黑客入侵的难度会大大增加。"</string>
     <string name="seamless_transition_title" msgid="5335622196351371961">"无缝转换"</string>
-    <string name="seamless_transition_detail" msgid="4475509237171739843">"在我们向无密码未来迈进的过程中,密码仍会与通行密钥并存供用。"</string>
+    <string name="seamless_transition_detail" msgid="4475509237171739843">"在我们向无密码未来迈进的过程中,密码仍会与通行密钥并行使用。"</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"选择保存<xliff:g id="CREATETYPES">%1$s</xliff:g>的位置"</string>
     <string name="choose_provider_body" msgid="4967074531845147434">"请选择一款密码管理工具来保存您的信息,以便下次更快地登录"</string>
     <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"要为“<xliff:g id="APPNAME">%1$s</xliff:g>”创建通行密钥吗?"</string>
@@ -33,10 +49,13 @@
     <string name="sign_ins" msgid="4710739369149469208">"登录"</string>
     <string name="sign_in_info" msgid="2627704710674232328">"登录信息"</string>
     <string name="save_credential_to_title" msgid="3172811692275634301">"将<xliff:g id="CREDENTIALTYPES">%1$s</xliff:g>保存到"</string>
-    <string name="create_passkey_in_other_device_title" msgid="9195411122362461390">"在其他设备上创建通行密钥?"</string>
+    <string name="create_passkey_in_other_device_title" msgid="2360053098931886245">"在其他设备上创建通行密钥?"</string>
+    <string name="save_password_on_other_device_title" msgid="5829084591948321207">"在其他设备上保存密码?"</string>
+    <string name="save_sign_in_on_other_device_title" msgid="2827990118560134692">"在其他设备上保存登录凭据?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"将“<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>”用于您的所有登录信息?"</string>
-    <string name="use_provider_for_all_description" msgid="8466427781848268490">"此密码管理工具将会存储您的密码和通行密钥,以帮助您轻松登录"</string>
+    <string name="use_provider_for_all_description" msgid="1998772715863958997">"此 <xliff:g id="USERNAME">%1$s</xliff:g> 密码管理工具将会存储您的密码和通行密钥,帮助您轻松登录"</string>
     <string name="set_as_default" msgid="4415328591568654603">"设为默认项"</string>
+    <string name="settings" msgid="6536394145760913145">"设置"</string>
     <string name="use_once" msgid="9027366575315399714">"使用一次"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> 个密码 • <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> 个通行密钥"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> 个密码"</string>
@@ -47,21 +66,30 @@
     <string name="other_password_manager" msgid="565790221427004141">"其他密码管理工具"</string>
     <string name="close_sheet" msgid="1393792015338908262">"关闭工作表"</string>
     <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"返回上一页"</string>
-    <string name="accessibility_close_button" msgid="2953807735590034688">"关闭屏幕底部显示的 Credential Manager 操作建议"</string>
-    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"将您已保存的通行密钥用于<xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
-    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"将您已保存的登录信息用于<xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
-    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"为<xliff:g id="APP_NAME">%1$s</xliff:g>选择已保存的登录信息"</string>
-    <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"以另一种方式登录"</string>
+    <string name="accessibility_close_button" msgid="1163435587545377687">"关闭"</string>
+    <string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"忽略"</string>
+    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"要使用您为“<xliff:g id="APP_NAME">%1$s</xliff:g>”保存的通行密钥吗?"</string>
+    <string name="get_dialog_title_use_password_for" msgid="625828023234318484">"要使用已保存的密码登录“<xliff:g id="APP_NAME">%1$s</xliff:g>”吗?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"使用您的<xliff:g id="APP_NAME">%1$s</xliff:g>登录凭据?"</string>
+    <string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"要解锁“<xliff:g id="APP_NAME">%1$s</xliff:g>”的登录选项吗?"</string>
+    <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"选择一个已保存的通行密钥来登录“<xliff:g id="APP_NAME">%1$s</xliff:g>”"</string>
+    <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"选择一个已保存的密码来登录“<xliff:g id="APP_NAME">%1$s</xliff:g>”"</string>
+    <string name="get_dialog_title_choose_saved_sign_in_for" msgid="2420298653461652728">"选择一种已保存的登录方式来登录“<xliff:g id="APP_NAME">%1$s</xliff:g>”"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="3048870756117876514">"选择一种登录<xliff:g id="APP_NAME">%1$s</xliff:g>的方式"</string>
+    <string name="get_dialog_title_choose_option_for" msgid="4976380044745029107">"要为“<xliff:g id="APP_NAME">%1$s</xliff:g>”选择一个选项吗?"</string>
+    <string name="get_dialog_title_use_info_on" msgid="8863708099535435146">"要将此信息用于“<xliff:g id="APP_NAME">%1$s</xliff:g>”吗?"</string>
+    <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"使用其他登录方式"</string>
     <string name="snackbar_action" msgid="37373514216505085">"查看选项"</string>
     <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"继续"</string>
     <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"登录选项"</string>
+    <string name="button_label_view_more" msgid="3429098227286495651">"查看更多"</string>
     <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"用户:<xliff:g id="USERNAME">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"已锁定的密码管理工具"</string>
-    <!-- no translation found for locked_credential_entry_label_subtext_tap_to_unlock (6390367581393605009) -->
-    <skip />
-    <!-- no translation found for locked_credential_entry_label_subtext_no_sign_in (8131725029983174901) -->
-    <skip />
+    <string name="locked_credential_entry_label_subtext_tap_to_unlock" msgid="6390367581393605009">"点按即可解锁"</string>
+    <string name="locked_credential_entry_label_subtext_no_sign_in" msgid="8131725029983174901">"无登录信息"</string>
+    <string name="no_sign_in_info_in" msgid="2641118151920288356">"<xliff:g id="SOURCE">%1$s</xliff:g> 中没有任何登录信息"</string>
     <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"管理登录信息"</string>
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"通过另一台设备"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"使用其他设备"</string>
+    <string name="request_cancelled_by" msgid="3735222326886267820">"<xliff:g id="APP_NAME">%1$s</xliff:g>已取消请求"</string>
 </resources>
diff --git a/packages/CredentialManager/res/values-zh-rHK/strings.xml b/packages/CredentialManager/res/values-zh-rHK/strings.xml
index 8ddd4be..f786254 100644
--- a/packages/CredentialManager/res/values-zh-rHK/strings.xml
+++ b/packages/CredentialManager/res/values-zh-rHK/strings.xml
@@ -1,26 +1,40 @@
 <?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.
+   -->
+
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="4539824758261855508">"憑證管理工具"</string>
     <string name="string_cancel" msgid="6369133483981306063">"取消"</string>
     <string name="string_continue" msgid="1346732695941131882">"繼續"</string>
-    <string name="string_more_options" msgid="7990658711962795124">"更多選項"</string>
+    <string name="string_more_options" msgid="2763852250269945472">"儲存其他方式"</string>
     <string name="string_learn_more" msgid="4541600451688392447">"瞭解詳情"</string>
-    <!-- no translation found for content_description_show_password (3283502010388521607) -->
-    <skip />
-    <!-- no translation found for content_description_hide_password (6841375971631767996) -->
-    <skip />
+    <string name="content_description_show_password" msgid="3283502010388521607">"顯示密碼"</string>
+    <string name="content_description_hide_password" msgid="6841375971631767996">"隱藏密碼"</string>
     <string name="passkey_creation_intro_title" msgid="4251037543787718844">"使用密鑰確保帳戶安全"</string>
-    <string name="passkey_creation_intro_body_password" msgid="8825872426579958200">"有了密鑰,您便無需建立或記住複雜的密碼"</string>
-    <string name="passkey_creation_intro_body_fingerprint" msgid="7331338631826254055">"密鑰是您使用指紋、面孔或螢幕鎖定時建立的加密數碼鑰匙"</string>
-    <string name="passkey_creation_intro_body_device" msgid="1203796455762131631">"密鑰已儲存至密碼管理工具,方便您在其他裝置上登入"</string>
+    <string name="passkey_creation_intro_body_password" msgid="8825872426579958200">"有了密鑰,你便無需建立或記住複雜的密碼"</string>
+    <string name="passkey_creation_intro_body_fingerprint" msgid="7331338631826254055">"密鑰是你使用指紋、面孔或螢幕鎖定時建立的加密數碼鑰匙"</string>
+    <string name="passkey_creation_intro_body_device" msgid="1203796455762131631">"密鑰已儲存至密碼管理工具,方便你在其他裝置上登入"</string>
     <string name="more_about_passkeys_title" msgid="7797903098728837795">"進一步瞭解密鑰"</string>
     <string name="passwordless_technology_title" msgid="2497513482056606668">"無密碼技術"</string>
     <string name="passwordless_technology_detail" msgid="6853928846532955882">"只要有密鑰,就無需使用密碼登入。使用指紋、面孔識別、PIN 或滑動畫出圖案,便可驗證身分並建立密鑰。"</string>
     <string name="public_key_cryptography_title" msgid="6751970819265298039">"公開金鑰加密技術"</string>
-    <string name="public_key_cryptography_detail" msgid="6937631710280562213">"密鑰根據 FIDO 聯盟 (包括 Google、Apple、Microsoft 等) 及 W3C 標準,使用加密配對金鑰技術。私密 - 公開金鑰組專為應用程式或網站建立,與建立密碼時使用的使用者名稱和作為密碼的字元字串不同。私密金鑰會安全地儲存在裝置或密碼管理工具上,用來確認您的身分。公開金鑰會與應用程式或網站伺服器共用。只要有對應的金鑰,就能立即註冊和登入。"</string>
+    <string name="public_key_cryptography_detail" msgid="6937631710280562213">"密鑰根據 FIDO 聯盟 (包括 Google、Apple、Microsoft 等) 及 W3C 標準,使用加密配對金鑰技術。私密 - 公開金鑰組專為應用程式或網站建立,與建立密碼時使用的使用者名稱和作為密碼的字元字串不同。私密金鑰會安全地儲存在裝置或密碼管理工具上,用來確認你的身分。公開金鑰會與應用程式或網站伺服器共用。只要有對應的金鑰,就能立即註冊和登入。"</string>
     <string name="improved_account_security_title" msgid="1069841917893513424">"提升帳戶安全性"</string>
-    <string name="improved_account_security_detail" msgid="9123750251551844860">"系統會為應用程式或網站建立專用的對應金鑰,因此您不會錯誤登入欺詐的應用程式或網站。此外,伺服器上只會保留公開金鑰,因此可大幅降低駭客入侵的風險。"</string>
+    <string name="improved_account_security_detail" msgid="9123750251551844860">"系統會為應用程式或網站建立專用的對應金鑰,因此你不會錯誤登入欺詐的應用程式或網站。此外,伺服器上只會保留公開金鑰,因此可大幅降低駭客入侵的風險。"</string>
     <string name="seamless_transition_title" msgid="5335622196351371961">"流暢轉換"</string>
     <string name="seamless_transition_detail" msgid="4475509237171739843">"我們將會改用無密碼技術,而密碼仍可與密鑰並行使用。"</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"選擇儲存<xliff:g id="CREATETYPES">%1$s</xliff:g>的位置"</string>
@@ -35,10 +49,13 @@
     <string name="sign_ins" msgid="4710739369149469208">"登入資料"</string>
     <string name="sign_in_info" msgid="2627704710674232328">"登入資料"</string>
     <string name="save_credential_to_title" msgid="3172811692275634301">"將<xliff:g id="CREDENTIALTYPES">%1$s</xliff:g>儲存至"</string>
-    <string name="create_passkey_in_other_device_title" msgid="9195411122362461390">"要在其他裝置上建立密鑰嗎?"</string>
+    <string name="create_passkey_in_other_device_title" msgid="2360053098931886245">"要在其他裝置上建立密鑰嗎?"</string>
+    <string name="save_password_on_other_device_title" msgid="5829084591948321207">"要在其他裝置上儲存密碼嗎?"</string>
+    <string name="save_sign_in_on_other_device_title" msgid="2827990118560134692">"要在其他裝置上儲存登入資料嗎?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"要將「<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>」用於所有的登入資料嗎?"</string>
-    <string name="use_provider_for_all_description" msgid="8466427781848268490">"此密碼管理工具將儲存您的密碼和密鑰,協助您輕鬆登入"</string>
+    <string name="use_provider_for_all_description" msgid="1998772715863958997">"此密碼管理工具將儲存「<xliff:g id="USERNAME">%1$s</xliff:g>」的密碼和密鑰,協助你輕鬆登入"</string>
     <string name="set_as_default" msgid="4415328591568654603">"設定為預設"</string>
+    <string name="settings" msgid="6536394145760913145">"設定"</string>
     <string name="use_once" msgid="9027366575315399714">"單次使用"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> 個密碼 • <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> 個密鑰"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> 個密碼"</string>
@@ -49,21 +66,30 @@
     <string name="other_password_manager" msgid="565790221427004141">"其他密碼管理工具"</string>
     <string name="close_sheet" msgid="1393792015338908262">"閂工作表"</string>
     <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"返回上一頁"</string>
-    <string name="accessibility_close_button" msgid="2953807735590034688">"閂顯示喺螢幕底部嘅憑證管理工具操作建議"</string>
+    <string name="accessibility_close_button" msgid="1163435587545377687">"關閉"</string>
+    <string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"關閉"</string>
     <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"要使用已儲存的「<xliff:g id="APP_NAME">%1$s</xliff:g>」密鑰嗎?"</string>
-    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"要使用已儲存的「<xliff:g id="APP_NAME">%1$s</xliff:g>」登入資料嗎?"</string>
-    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"選擇已儲存的「<xliff:g id="APP_NAME">%1$s</xliff:g>」登入資料"</string>
+    <string name="get_dialog_title_use_password_for" msgid="625828023234318484">"要使用已儲存的「<xliff:g id="APP_NAME">%1$s</xliff:g>」密碼嗎?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"要以此登入方式使用「<xliff:g id="APP_NAME">%1$s</xliff:g>」嗎?"</string>
+    <string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"要解鎖「<xliff:g id="APP_NAME">%1$s</xliff:g>」的登入選項嗎?"</string>
+    <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"選擇已儲存的「<xliff:g id="APP_NAME">%1$s</xliff:g>」密鑰"</string>
+    <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"選擇已儲存的「<xliff:g id="APP_NAME">%1$s</xliff:g>」密碼"</string>
+    <string name="get_dialog_title_choose_saved_sign_in_for" msgid="2420298653461652728">"選擇已儲存的「<xliff:g id="APP_NAME">%1$s</xliff:g>」登入資料"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="3048870756117876514">"選擇用於「<xliff:g id="APP_NAME">%1$s</xliff:g>」的登入方式"</string>
+    <string name="get_dialog_title_choose_option_for" msgid="4976380044745029107">"要選擇適用於「<xliff:g id="APP_NAME">%1$s</xliff:g>」的項目嗎?"</string>
+    <string name="get_dialog_title_use_info_on" msgid="8863708099535435146">"要在「<xliff:g id="APP_NAME">%1$s</xliff:g>」上使用這些資料嗎?"</string>
     <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"使用其他方式登入"</string>
     <string name="snackbar_action" msgid="37373514216505085">"查看選項"</string>
     <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"繼續"</string>
     <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"登入選項"</string>
+    <string name="button_label_view_more" msgid="3429098227286495651">"查看更多"</string>
     <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"<xliff:g id="USERNAME">%1$s</xliff:g> 專用"</string>
     <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"已鎖定的密碼管理工具"</string>
-    <!-- no translation found for locked_credential_entry_label_subtext_tap_to_unlock (6390367581393605009) -->
-    <skip />
-    <!-- no translation found for locked_credential_entry_label_subtext_no_sign_in (8131725029983174901) -->
-    <skip />
+    <string name="locked_credential_entry_label_subtext_tap_to_unlock" msgid="6390367581393605009">"輕按即可解鎖"</string>
+    <string name="locked_credential_entry_label_subtext_no_sign_in" msgid="8131725029983174901">"沒有登入資料"</string>
+    <string name="no_sign_in_info_in" msgid="2641118151920288356">"<xliff:g id="SOURCE">%1$s</xliff:g> 中沒有登入資料"</string>
     <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"管理登入資料"</string>
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"透過其他裝置"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"使用其他裝置"</string>
+    <string name="request_cancelled_by" msgid="3735222326886267820">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」已取消要求"</string>
 </resources>
diff --git a/packages/CredentialManager/res/values-zh-rTW/strings.xml b/packages/CredentialManager/res/values-zh-rTW/strings.xml
index 7e42d56..f14a5ce 100644
--- a/packages/CredentialManager/res/values-zh-rTW/strings.xml
+++ b/packages/CredentialManager/res/values-zh-rTW/strings.xml
@@ -1,15 +1,29 @@
 <?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.
+   -->
+
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="4539824758261855508">"憑證管理工具"</string>
     <string name="string_cancel" msgid="6369133483981306063">"取消"</string>
     <string name="string_continue" msgid="1346732695941131882">"繼續"</string>
-    <string name="string_more_options" msgid="7990658711962795124">"更多選項"</string>
+    <string name="string_more_options" msgid="2763852250269945472">"儲存其他方式"</string>
     <string name="string_learn_more" msgid="4541600451688392447">"瞭解詳情"</string>
-    <!-- no translation found for content_description_show_password (3283502010388521607) -->
-    <skip />
-    <!-- no translation found for content_description_hide_password (6841375971631767996) -->
-    <skip />
+    <string name="content_description_show_password" msgid="3283502010388521607">"顯示密碼"</string>
+    <string name="content_description_hide_password" msgid="6841375971631767996">"隱藏密碼"</string>
     <string name="passkey_creation_intro_title" msgid="4251037543787718844">"使用密碼金鑰確保帳戶安全"</string>
     <string name="passkey_creation_intro_body_password" msgid="8825872426579958200">"有了密碼金鑰,就不必建立或記住複雜的密碼"</string>
     <string name="passkey_creation_intro_body_fingerprint" msgid="7331338631826254055">"密碼金鑰是你利用指紋、臉孔或螢幕鎖定功能建立的加密數位金鑰"</string>
@@ -18,9 +32,9 @@
     <string name="passwordless_technology_title" msgid="2497513482056606668">"無密碼技術"</string>
     <string name="passwordless_technology_detail" msgid="6853928846532955882">"只要有密碼金鑰,就不必使用密碼登入。使用指紋、臉部辨識、PIN 碼或滑動畫出解鎖圖案,就能驗證身分並建立密碼金鑰。"</string>
     <string name="public_key_cryptography_title" msgid="6751970819265298039">"公開金鑰密碼編譯"</string>
-    <string name="public_key_cryptography_detail" msgid="6937631710280562213">"密碼金鑰根據 FIDO 聯盟 (包括 Google、Apple、Microsoft 等) 及 W3C 標準,使用加密編譯金鑰技術。私密 - 公開金鑰組專為應用程式或網站建立,有別於建立密碼時使用的使用者名稱和做為密碼的字元字串。私密金鑰會安全地儲存在裝置或 Google 密碼管理工具中,並用來確認你的身分。公開金鑰會提供給應用程式或網站伺服器。只要有相對應的金鑰,就能立即註冊和登入。"</string>
+    <string name="public_key_cryptography_detail" msgid="6937631710280562213">"密碼金鑰根據 FIDO 聯盟 (包括 Google、Apple、Microsoft 等) 及 W3C 標準,使用加密編譯金鑰組。有別於傳統密碼,系統會針對應用程式或網站建立專屬的私密 - 公開金鑰組,因此你不再需要輸入使用者名稱和一串密碼字元。私密金鑰會安全地儲存在裝置或密碼管理工具中,並用來確認你的身分。公開金鑰會提供給應用程式或網站伺服器。只要有相對應的金鑰,就能立即註冊和登入。"</string>
     <string name="improved_account_security_title" msgid="1069841917893513424">"提升帳戶安全性"</string>
-    <string name="improved_account_security_detail" msgid="9123750251551844860">"系統會為應用程式或網站建立專屬的對應金鑰,因此你不會意外登入詐欺性的應用程式或網站。此外,伺服器上只存放公開金鑰,因此可大幅降低駭客入侵的風險。"</string>
+    <string name="improved_account_security_detail" msgid="9123750251551844860">"系統會為應用程式或網站建立專屬的對應金鑰,因此你不會意外登入詐騙應用程式或網站。此外,伺服器上只存放公開金鑰,可大幅降低駭客入侵的風險。"</string>
     <string name="seamless_transition_title" msgid="5335622196351371961">"流暢轉換"</string>
     <string name="seamless_transition_detail" msgid="4475509237171739843">"我們日後將改採無密碼技術,密碼仍可與密碼金鑰並行使用。"</string>
     <string name="choose_provider_title" msgid="8870795677024868108">"選擇要將<xliff:g id="CREATETYPES">%1$s</xliff:g>存在哪裡"</string>
@@ -35,10 +49,13 @@
     <string name="sign_ins" msgid="4710739369149469208">"登入資訊"</string>
     <string name="sign_in_info" msgid="2627704710674232328">"登入資訊"</string>
     <string name="save_credential_to_title" msgid="3172811692275634301">"選擇儲存<xliff:g id="CREDENTIALTYPES">%1$s</xliff:g>的位置"</string>
-    <string name="create_passkey_in_other_device_title" msgid="9195411122362461390">"要在其他裝置上建立密碼金鑰嗎?"</string>
+    <string name="create_passkey_in_other_device_title" msgid="2360053098931886245">"要在另一部裝置上建立密碼金鑰嗎?"</string>
+    <string name="save_password_on_other_device_title" msgid="5829084591948321207">"要將密碼儲存在另一部裝置上嗎?"</string>
+    <string name="save_sign_in_on_other_device_title" msgid="2827990118560134692">"要將登入憑證儲存在另一部裝置上嗎?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"要將「<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>」用於所有的登入資訊嗎?"</string>
-    <string name="use_provider_for_all_description" msgid="8466427781848268490">"這個密碼管理工具會儲存密碼和密碼金鑰,協助你輕鬆登入"</string>
+    <string name="use_provider_for_all_description" msgid="1998772715863958997">"<xliff:g id="USERNAME">%1$s</xliff:g> 的密碼管理工具會儲存密碼和密碼金鑰,協助你輕鬆登入"</string>
     <string name="set_as_default" msgid="4415328591568654603">"設為預設"</string>
+    <string name="settings" msgid="6536394145760913145">"設定"</string>
     <string name="use_once" msgid="9027366575315399714">"單次使用"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> 個密碼 • <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> 個密碼金鑰"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> 個密碼"</string>
@@ -49,21 +66,30 @@
     <string name="other_password_manager" msgid="565790221427004141">"其他密碼管理工具"</string>
     <string name="close_sheet" msgid="1393792015338908262">"關閉功能表"</string>
     <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"返回上一頁"</string>
-    <string name="accessibility_close_button" msgid="2953807735590034688">"關閉顯示在畫面底部的憑證管理工具操作建議"</string>
+    <string name="accessibility_close_button" msgid="1163435587545377687">"關閉"</string>
+    <string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"關閉"</string>
     <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"要使用已儲存的「<xliff:g id="APP_NAME">%1$s</xliff:g>」密碼金鑰嗎?"</string>
-    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"要使用已儲存的「<xliff:g id="APP_NAME">%1$s</xliff:g>」登入資訊嗎?"</string>
-    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"選擇已儲存的「<xliff:g id="APP_NAME">%1$s</xliff:g>」登入資訊"</string>
+    <string name="get_dialog_title_use_password_for" msgid="625828023234318484">"要使用已儲存的「<xliff:g id="APP_NAME">%1$s</xliff:g>」密碼嗎?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"要使用你的憑證登入「<xliff:g id="APP_NAME">%1$s</xliff:g>」嗎?"</string>
+    <string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"要解鎖「<xliff:g id="APP_NAME">%1$s</xliff:g>」的登入選項嗎?"</string>
+    <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"選擇已儲存的「<xliff:g id="APP_NAME">%1$s</xliff:g>」密碼金鑰"</string>
+    <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"選擇已儲存的「<xliff:g id="APP_NAME">%1$s</xliff:g>」密碼"</string>
+    <string name="get_dialog_title_choose_saved_sign_in_for" msgid="2420298653461652728">"選擇已儲存的「<xliff:g id="APP_NAME">%1$s</xliff:g>」登入資訊"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="3048870756117876514">"選擇用於登入「<xliff:g id="APP_NAME">%1$s</xliff:g>」的憑證"</string>
+    <string name="get_dialog_title_choose_option_for" msgid="4976380044745029107">"要選擇適用於「<xliff:g id="APP_NAME">%1$s</xliff:g>」的項目嗎?"</string>
+    <string name="get_dialog_title_use_info_on" msgid="8863708099535435146">"要在「<xliff:g id="APP_NAME">%1$s</xliff:g>」上使用這項資訊嗎?"</string>
     <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"使用其他方式登入"</string>
     <string name="snackbar_action" msgid="37373514216505085">"查看選項"</string>
     <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"繼續"</string>
     <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"登入選項"</string>
+    <string name="button_label_view_more" msgid="3429098227286495651">"顯示更多"</string>
     <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"<xliff:g id="USERNAME">%1$s</xliff:g> 專用"</string>
     <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"已鎖定的密碼管理工具"</string>
-    <!-- no translation found for locked_credential_entry_label_subtext_tap_to_unlock (6390367581393605009) -->
-    <skip />
-    <!-- no translation found for locked_credential_entry_label_subtext_no_sign_in (8131725029983174901) -->
-    <skip />
+    <string name="locked_credential_entry_label_subtext_tap_to_unlock" msgid="6390367581393605009">"輕觸即可解鎖"</string>
+    <string name="locked_credential_entry_label_subtext_no_sign_in" msgid="8131725029983174901">"沒有登入資訊"</string>
+    <string name="no_sign_in_info_in" msgid="2641118151920288356">"<xliff:g id="SOURCE">%1$s</xliff:g> 中沒有登入資訊"</string>
     <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"管理登入資訊"</string>
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"透過其他裝置"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"使用其他裝置"</string>
+    <string name="request_cancelled_by" msgid="3735222326886267820">"要求已由 <xliff:g id="APP_NAME">%1$s</xliff:g> 取消"</string>
 </resources>
diff --git a/packages/CredentialManager/res/values-zu/strings.xml b/packages/CredentialManager/res/values-zu/strings.xml
index 7a40771..91f93e2 100644
--- a/packages/CredentialManager/res/values-zu/strings.xml
+++ b/packages/CredentialManager/res/values-zu/strings.xml
@@ -1,10 +1,26 @@
 <?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="4539824758261855508">"Umphathi Wezimfanelo"</string>
     <string name="string_cancel" msgid="6369133483981306063">"Khansela"</string>
     <string name="string_continue" msgid="1346732695941131882">"Qhubeka"</string>
-    <string name="string_more_options" msgid="7990658711962795124">"Okunye okungakukhethwa kukho"</string>
+    <string name="string_more_options" msgid="2763852250269945472">"Londoloza ngenye indlela"</string>
     <string name="string_learn_more" msgid="4541600451688392447">"Funda kabanzi"</string>
     <string name="content_description_show_password" msgid="3283502010388521607">"Bonisa iphasiwedi"</string>
     <string name="content_description_hide_password" msgid="6841375971631767996">"Fihla iphasiwedi"</string>
@@ -33,10 +49,13 @@
     <string name="sign_ins" msgid="4710739369149469208">"ukungena ngemvume"</string>
     <string name="sign_in_info" msgid="2627704710674232328">"ulwazi lokungena ngemvume"</string>
     <string name="save_credential_to_title" msgid="3172811692275634301">"Londoloza i-<xliff:g id="CREDENTIALTYPES">%1$s</xliff:g> ku-"</string>
-    <string name="create_passkey_in_other_device_title" msgid="9195411122362461390">"Sungula ukhiye wokudlula kwenye idivayisi?"</string>
+    <string name="create_passkey_in_other_device_title" msgid="2360053098931886245">"Sungula ukhiye wokudlula kwenye idivayisi?"</string>
+    <string name="save_password_on_other_device_title" msgid="5829084591948321207">"Londoloza iphasiwedi kwenye idivayisi?"</string>
+    <string name="save_sign_in_on_other_device_title" msgid="2827990118560134692">"Londoloza ukungena ngemvume kwenye idivayisi?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"Sebenzisa i-<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> kukho konke ukungena kwakho ngemvume?"</string>
-    <string name="use_provider_for_all_description" msgid="8466427781848268490">"Lesi siphathi sephasiwedi sizogcina amaphasiwedi akho nezikhiye zokungena ukuze zikusize ungene ngemvume kalula."</string>
+    <string name="use_provider_for_all_description" msgid="1998772715863958997">"Lesi siphathi sephasiwedi sika-<xliff:g id="USERNAME">%1$s</xliff:g> sizogcina amaphasiwedi akho nezikhiye zokungena ukuze zikusize ungene ngemvume kalula."</string>
     <string name="set_as_default" msgid="4415328591568654603">"Setha njengokuzenzakalelayo"</string>
+    <string name="settings" msgid="6536394145760913145">"Amasethingi"</string>
     <string name="use_once" msgid="9027366575315399714">"Sebenzisa kanye"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"Amaphasiwedi angu-<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> • okhiye bokudlula abangu-<xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"Amaphasiwedi angu-<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>"</string>
@@ -47,21 +66,30 @@
     <string name="other_password_manager" msgid="565790221427004141">"Abanye abaphathi bephasiwedi"</string>
     <string name="close_sheet" msgid="1393792015338908262">"Vala ishidi"</string>
     <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Buyela emuva ekhasini langaphambilini"</string>
-    <string name="accessibility_close_button" msgid="2953807735590034688">"Vala isiphakamiso sesenzo Somphathi Wezimfanelo esivela phansi esikrinini"</string>
+    <string name="accessibility_close_button" msgid="1163435587545377687">"Vala"</string>
+    <string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"Chitha"</string>
     <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Sebenzisa ukhiye wakho wokungena olondoloziwe <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
-    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Sebenzisa ukungena kwakho ngemvume okulondoloziwe <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
-    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Khetha ukungena ngemvume okulondoloziwe kwakho <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_use_password_for" msgid="625828023234318484">"Sebenzisa iphasiwedi yakho elondoloziwe ye-<xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"Sebenzisa ukungena kwakho ngemvume ku-<xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"Vula ukungena ngemvume okukhethwa kukho kwe-<xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"Khetha ukhiye wokudlula olondoloziwe we-<xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"Khetha iphasiwedi elondoloziwe ye-<xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_saved_sign_in_for" msgid="2420298653461652728">"Khetha ukungena ngemvume okulondoloziwe kwe-<xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="3048870756117876514">"Khetha ukungenangemvume ku-<xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_title_choose_option_for" msgid="4976380044745029107">"Khetha ongakhetha kukho kwe-<xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+    <string name="get_dialog_title_use_info_on" msgid="8863708099535435146">"Sebenzisa lolu lwazi ku-<xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Ngena ngemvume ngenye indlela"</string>
     <string name="snackbar_action" msgid="37373514216505085">"Buka okungakhethwa kukho"</string>
     <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Qhubeka"</string>
     <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Okungakhethwa kukho kokungena ngemvume"</string>
+    <string name="button_label_view_more" msgid="3429098227286495651">"Buka okwengeziwe"</string>
     <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Okuka-<xliff:g id="USERNAME">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Abaphathi bephasiwedi abakhiyiwe"</string>
-    <!-- no translation found for locked_credential_entry_label_subtext_tap_to_unlock (6390367581393605009) -->
-    <skip />
-    <!-- no translation found for locked_credential_entry_label_subtext_no_sign_in (8131725029983174901) -->
-    <skip />
+    <string name="locked_credential_entry_label_subtext_tap_to_unlock" msgid="6390367581393605009">"Thepha ukuze uvule"</string>
+    <string name="locked_credential_entry_label_subtext_no_sign_in" msgid="8131725029983174901">"Alukho ulwazi lokungena ngemvume"</string>
+    <string name="no_sign_in_info_in" msgid="2641118151920288356">"Alukho ulwazi lokungena ngemvume lwe-<xliff:g id="SOURCE">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Phatha ukungena ngemvume"</string>
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Kusukela kwenye idivayisi"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Sebenzisa idivayisi ehlukile"</string>
+    <string name="request_cancelled_by" msgid="3735222326886267820">"Isicelo sikhanselwe yi-<xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
 </resources>
diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
index b265a42..25ac3c9 100644
--- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
+++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
@@ -16,11 +16,10 @@
 
 package com.android.dynsystem;
 
-import static android.os.AsyncTask.Status.FINISHED;
-import static android.os.AsyncTask.Status.PENDING;
 import static android.os.AsyncTask.Status.RUNNING;
 import static android.os.image.DynamicSystemClient.ACTION_HIDE_NOTIFICATION;
 import static android.os.image.DynamicSystemClient.ACTION_NOTIFY_IF_IN_USE;
+import static android.os.image.DynamicSystemClient.ACTION_NOTIFY_KEYGUARD_DISMISSED;
 import static android.os.image.DynamicSystemClient.ACTION_START_INSTALL;
 import static android.os.image.DynamicSystemClient.CAUSE_ERROR_EXCEPTION;
 import static android.os.image.DynamicSystemClient.CAUSE_ERROR_INVALID_URL;
@@ -234,6 +233,8 @@
             executeNotifyIfInUseCommand();
         } else if (ACTION_HIDE_NOTIFICATION.equals(action)) {
             executeHideNotificationCommand();
+        } else if (ACTION_NOTIFY_KEYGUARD_DISMISSED.equals(action)) {
+            executeNotifyKeyguardDismissed();
         }
 
         return Service.START_NOT_STICKY;
@@ -418,12 +419,23 @@
         mDynSystem.remove();
     }
 
+    private boolean isDsuSlotLocked() {
+        // Slot names ending with ".lock" are a customized installation.
+        // We expect the client app to provide custom UI to enter/exit DSU mode.
+        // We will ignore the ACTION_REBOOT_TO_NORMAL command and will not show
+        // notifications in this case.
+        return mDynSystem.getActiveDsuSlot().endsWith(".lock");
+    }
+
     private void executeRebootToNormalCommand() {
         if (!isInDynamicSystem()) {
             Log.e(TAG, "It's already running in normal system.");
             return;
         }
-
+        if (isDsuSlotLocked()) {
+            Log.e(TAG, "Ignore the reboot intent for a locked DSU slot");
+            return;
+        }
         if (!mDynSystem.setEnable(/* enable = */ false, /* oneShot = */ false)) {
             Log.e(TAG, "Failed to disable DynamicSystem.");
 
@@ -445,13 +457,13 @@
     private void executeNotifyIfInUseCommand() {
         switch (getStatus()) {
             case STATUS_IN_USE:
-                if (!mHideNotification) {
+                if (!mHideNotification && !isDsuSlotLocked()) {
                     startForeground(NOTIFICATION_ID,
                             buildNotification(STATUS_IN_USE, CAUSE_NOT_SPECIFIED));
                 }
                 break;
             case STATUS_READY:
-                if (!mHideNotification) {
+                if (!mHideNotification && !isDsuSlotLocked()) {
                     startForeground(NOTIFICATION_ID,
                             buildNotification(STATUS_READY, CAUSE_NOT_SPECIFIED));
                 }
@@ -474,6 +486,10 @@
         }
     }
 
+    private void executeNotifyKeyguardDismissed() {
+        postStatus(STATUS_NOT_STARTED, CAUSE_INSTALL_CANCELLED, null);
+    }
+
     private void resetTaskAndStop() {
         resetTaskAndStop(/* removeNotification= */ false);
     }
diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/VerificationActivity.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/VerificationActivity.java
index b522729..7401691 100644
--- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/VerificationActivity.java
+++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/VerificationActivity.java
@@ -16,6 +16,7 @@
 
 package com.android.dynsystem;
 
+import static android.os.image.DynamicSystemClient.ACTION_NOTIFY_KEYGUARD_DISMISSED;
 import static android.os.image.DynamicSystemClient.KEY_KEYGUARD_USE_DEFAULT_STRINGS;
 
 import android.app.Activity;
@@ -83,11 +84,19 @@
     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
         if (requestCode == REQUEST_CODE && resultCode == RESULT_OK) {
             startInstallationService();
+        } else {
+            notifyKeyguardDismissed();
         }
 
         finish();
     }
 
+    private void notifyKeyguardDismissed() {
+        Intent intent = new Intent(this, DynamicSystemInstallationService.class);
+        intent.setAction(ACTION_NOTIFY_KEYGUARD_DISMISSED);
+        startServiceAsUser(intent, UserHandle.SYSTEM);
+    }
+
     private void startInstallationService() {
         // retrieve data from calling intent
         Intent callingIntent = getIntent();
diff --git a/packages/EasterEgg/res/values/q_puzzles.xml b/packages/EasterEgg/res/values/q_puzzles.xml
index 7c2eff1..bff99d5 100644
--- a/packages/EasterEgg/res/values/q_puzzles.xml
+++ b/packages/EasterEgg/res/values/q_puzzles.xml
@@ -44,7 +44,6 @@
 
         <item>android:drawable/ic_audio_alarm</item>
         <item>android:drawable/ic_audio_alarm_mute</item>
-        <item>android:drawable/ic_bluetooth_share_icon</item>
         <item>android:drawable/ic_bt_headphones_a2dp</item>
         <item>android:drawable/ic_bt_headset_hfp</item>
         <item>android:drawable/ic_bt_hearing_aid</item>
@@ -206,7 +205,7 @@
         <item>com.android.systemui:drawable/ic_volume_ringer_vibrate</item>
         <item>com.android.systemui:drawable/ic_volume_voice</item>
         <item>com.android.systemui:drawable/stat_sys_camera</item>
-        <item>com.android.systemui:drawable/stat_sys_managed_profile_status</item>
+        <item>android:drawable/stat_sys_managed_profile_status</item>
         <item>com.android.systemui:drawable/stat_sys_mic_none</item>
         <item>com.android.systemui:drawable/stat_sys_vpn_ic</item>
 
diff --git a/packages/ExternalStorageProvider/res/values-bs/strings.xml b/packages/ExternalStorageProvider/res/values-bs/strings.xml
index 2c42df7..9abfcec 100644
--- a/packages/ExternalStorageProvider/res/values-bs/strings.xml
+++ b/packages/ExternalStorageProvider/res/values-bs/strings.xml
@@ -18,6 +18,6 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="748293919008814871">"Aplikacija za vanjsku pohranu"</string>
     <string name="storage_description" msgid="9176081505553938524">"Lokalna pohrana"</string>
-    <string name="root_internal_storage" msgid="4980477711224234931">"Interna pohrana"</string>
+    <string name="root_internal_storage" msgid="4980477711224234931">"Unutrašnja pohrana"</string>
     <string name="root_documents" msgid="5695037589229175941">"Dokumenti"</string>
 </resources>
diff --git a/packages/ExternalStorageProvider/res/values-te/strings.xml b/packages/ExternalStorageProvider/res/values-te/strings.xml
index 773ae0e..33a57b0 100644
--- a/packages/ExternalStorageProvider/res/values-te/strings.xml
+++ b/packages/ExternalStorageProvider/res/values-te/strings.xml
@@ -16,8 +16,8 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_label" msgid="748293919008814871">"బాహ్య నిల్వ"</string>
-    <string name="storage_description" msgid="9176081505553938524">"స్థానిక నిల్వ"</string>
-    <string name="root_internal_storage" msgid="4980477711224234931">"అంతర్గత నిల్వ"</string>
-    <string name="root_documents" msgid="5695037589229175941">"పత్రాలు"</string>
+    <string name="app_label" msgid="748293919008814871">"బాహ్య స్టోరేజ్‌"</string>
+    <string name="storage_description" msgid="9176081505553938524">"స్థానిక స్టోరేజ్‌"</string>
+    <string name="root_internal_storage" msgid="4980477711224234931">"అంతర్గత స్టోరేజ్‌"</string>
+    <string name="root_documents" msgid="5695037589229175941">"డాక్యుమెంట్లు"</string>
 </resources>
diff --git a/packages/PackageInstaller/Android.bp b/packages/PackageInstaller/Android.bp
index 6ecd328..58224b8 100644
--- a/packages/PackageInstaller/Android.bp
+++ b/packages/PackageInstaller/Android.bp
@@ -39,13 +39,18 @@
 
     certificate: "platform",
     privileged: true,
-    platform_apis: true,
+    platform_apis: false,
+    sdk_version: "system_current",
     rename_resources_package: false,
     static_libs: [
         "xz-java",
         "androidx.leanback_leanback",
         "androidx.annotation_annotation",
     ],
+
+    lint: {
+        error_checks: ["Recycle"],
+    },
 }
 
 android_app {
@@ -56,7 +61,8 @@
 
     certificate: "platform",
     privileged: true,
-    platform_apis: true,
+    platform_apis: false,
+    sdk_version: "system_current",
     rename_resources_package: false,
     overrides: ["PackageInstaller"],
 
@@ -65,6 +71,10 @@
         "androidx.leanback_leanback",
     ],
     aaptflags: ["--product tablet"],
+
+    lint: {
+        error_checks: ["Recycle"],
+    },
 }
 
 android_app {
@@ -75,7 +85,8 @@
 
     certificate: "platform",
     privileged: true,
-    platform_apis: true,
+    platform_apis: false,
+    sdk_version: "system_current",
     rename_resources_package: false,
     overrides: ["PackageInstaller"],
 
@@ -85,4 +96,8 @@
         "androidx.annotation_annotation",
     ],
     aaptflags: ["--product tv"],
+
+    lint: {
+        error_checks: ["Recycle"],
+    },
 }
diff --git a/packages/PackageInstaller/AndroidManifest.xml b/packages/PackageInstaller/AndroidManifest.xml
index 6ccebfd..1edb751 100644
--- a/packages/PackageInstaller/AndroidManifest.xml
+++ b/packages/PackageInstaller/AndroidManifest.xml
@@ -10,6 +10,7 @@
     <uses-permission android:name="android.permission.DELETE_PACKAGES" />
     <uses-permission android:name="android.permission.READ_INSTALL_SESSIONS" />
     <uses-permission android:name="android.permission.READ_INSTALLED_SESSION_PATHS" />
+    <uses-permission android:name="android.permission.WAKE_LOCK" />
     <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
     <uses-permission android:name="android.permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS" />
     <uses-permission android:name="android.permission.USE_RESERVED_DISK" />
diff --git a/packages/PackageInstaller/res/layout-television/alert_dialog_button_bar_leanback.xml b/packages/PackageInstaller/res/layout-television/alert_dialog_button_bar_leanback.xml
index 3ced1db..4ff09ad 100644
--- a/packages/PackageInstaller/res/layout-television/alert_dialog_button_bar_leanback.xml
+++ b/packages/PackageInstaller/res/layout-television/alert_dialog_button_bar_leanback.xml
@@ -21,13 +21,13 @@
             android:layout_height="wrap_content"
             android:scrollbarAlwaysDrawVerticalTrack="true"
             android:scrollIndicators="top|bottom"
-            android:fillViewport="true"
-            style="?android:attr/buttonBarStyle">
+            android:fillViewport="true">
     <com.android.packageinstaller.ButtonBarLayout
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:layoutDirection="locale"
         android:orientation="horizontal"
+        style="?android:attr/buttonBarStyle"
         android:gravity="start">
 
         <Button
diff --git a/packages/PackageInstaller/res/values-af/strings.xml b/packages/PackageInstaller/res/values-af/strings.xml
index 3545179..8a5738e 100644
--- a/packages/PackageInstaller/res/values-af/strings.xml
+++ b/packages/PackageInstaller/res/values-af/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"Program geïnstalleer."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Wil jy hierdie program installeer?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Wil jy hierdie program opdateer?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Dateer hierdie app vanaf <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> op?\n\nHierdie app kry gewoonlik opdaterings vanaf <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. As jy vanaf ’n ander bron opdateer, kan jy in die toekoms dalk opdaterings vanaf enige bron op jou foon kry. Appfunksie kan verander."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="7994800761970572198">"&lt;p&gt;Dateer hierdie app op vanaf &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Hierdie app ontvang gewoonlik opdaterings vanaf &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. As jy vanaf ’n ander bron opdateer, kan jy in die toekoms dalk opdaterings vanaf enige bron op jou tablet kry. Appfunksies kan verander.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="2435174886412089791">"&lt;p&gt;Dateer hierdie app op vanaf &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Hierdie app ontvang gewoonlik opdaterings vanaf &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. As jy vanaf ’n ander bron opdateer, kan jy in die toekoms dalk opdaterings vanaf enige bron op jou TV kry. Appfunksies kan verander.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="7155138616126795839">"&lt;p&gt;Dateer hierdie app op vanaf &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Hierdie app ontvang gewoonlik opdaterings vanaf &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. As jy vanaf ’n ander bron opdateer, kan jy in die toekoms dalk opdaterings vanaf enige bron op jou foon kry. Appfunksies kan verander.&lt;/p&gt;"</string>
     <string name="install_failed" msgid="5777824004474125469">"Program nie geïnstalleer nie."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"Die installering van die pakket is geblokkeer."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"Program is nie geïnstalleer nie omdat pakket met \'n bestaande pakket bots."</string>
diff --git a/packages/PackageInstaller/res/values-am/strings.xml b/packages/PackageInstaller/res/values-am/strings.xml
index 3dab467c..7b664bc 100644
--- a/packages/PackageInstaller/res/values-am/strings.xml
+++ b/packages/PackageInstaller/res/values-am/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"መተግበሪያ ተጭኗል።"</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"ይህን መተግበሪያ መጫን ይፈልጋሉ?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"ይህን መተግበሪያ ማዘመን ይፈልጋሉ?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"ይህ መተግበሪያ ከ<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> ይዘምን?\n\nይህ መተግበሪያ በመደበኛነት ዝማኔዎችን ከ<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> ይቀበላል። ከተለየ ምንጭ በማዘመን በስልክዎ ላይ ካለ ማንኛውም ምንጭ የወደፊት ዝማኔዎችን ሊቀበሉ ይችላሉ። የመተግበሪያ ተግባራዊነት ሊለወጥ ይችላል።"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="7994800761970572198">"&lt;p&gt;ይህን መተግበሪያ &lt;b&gt;ከ<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;ያዘምኑ?&lt;/p&gt;&lt;p&gt;ይህ መተግበሪያ በተለምዶ ዝማኔዎችን የሚቀበለው &lt;b&gt;ከ<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&amp;gt ነው። ከተለየ ምንጭ በማዘመን በጡባዊዎ ላይ ካለ ማንኛውም ምንጭ የወደፊት ዝማኔዎችን ሊቀበሉ ይችላሉ። የመተግበሪያ ተግባራዊነት ሊቀየር ይችላል።&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="2435174886412089791">"&lt;p&gt;ይህን መተግበሪያ &lt;b&gt;ከ<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;ያዘምኑ?&lt;/p&gt;&lt;p&gt;ይህ መተግበሪያ በተለምዶ ዝማኔዎችን የሚቀበለው &lt;b&gt;ከ<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&amp;gt ነው። ከተለየ ምንጭ በማዘመን በቲቪዎ ላይ ካለ ማንኛውም ምንጭ የወደፊት ዝማኔዎችን ሊቀበሉ ይችላሉ። የመተግበሪያ ተግባራዊነት ሊቀየር ይችላል።&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="7155138616126795839">"&lt;p&gt;ይህን መተግበሪያ &lt;b&gt;ከ<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;ያዘምኑ?&lt;/p&gt;&lt;p&gt;ይህ መተግበሪያ በተለምዶ ዝማኔዎችን የሚቀበለው &lt;b&gt;ከ<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&amp;gt ነው። ከተለየ ምንጭ በማዘመን በስልክዎ ላይ ካለ ማንኛውም ምንጭ የወደፊት ዝማኔዎችን ሊቀበሉ ይችላሉ። የመተግበሪያ ተግባራዊነት ሊቀየር ይችላል።&lt;/p&gt;"</string>
     <string name="install_failed" msgid="5777824004474125469">"መተግበሪያ አልተጫነም።"</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"ጥቅሉ እንዳይጫን ታግዷል።"</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"እንደ ጥቅል ያልተጫነ መተግበሪያ ከነባር ጥቅል ጋር ይጋጫል።"</string>
diff --git a/packages/PackageInstaller/res/values-ar/strings.xml b/packages/PackageInstaller/res/values-ar/strings.xml
index 170c1fc..9a4d7ea 100644
--- a/packages/PackageInstaller/res/values-ar/strings.xml
+++ b/packages/PackageInstaller/res/values-ar/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"تم تثبيت التطبيق."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"هل تريد تثبيت هذا التطبيق؟"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"هل تريد تحديث هذا التطبيق؟"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"هل تريد تحديث هذا التطبيق من خلال \"<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>\"؟\n\nيتلقّى هذا التطبيق التحديثات عادةً من \"<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>\". من خلال إجراء التحديث من مصدر مختلف، قد تتلقّى تحديثات في المستقبل من أي مصدر على هاتفك. قد تتغير وظائف التطبيق."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="7994800761970572198">"‏&lt;p&gt;هل تريد تحديث هذا التطبيق من &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;؟&lt;/p&gt;&lt;p&gt;يتلقّى هذا التطبيق التحديثات عادةً من &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. من خلال تحديثه من مصدر مختلف، قد يتلقّى تحديثات في المستقبل من أي مصدر على جهازك اللوحي. قد تتغير وظائف التطبيق.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="2435174886412089791">"‏&lt;p&gt;هل تريد تحديث هذا التطبيق من &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;؟&lt;/p&gt;&lt;p&gt;يتلقّى هذا التطبيق التحديثات عادةً من &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. من خلال تحديثه من مصدر مختلف، قد يتلقّى تحديثات في المستقبل من أي مصدر على التلفزيون. قد تتغير وظائف التطبيق.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="7155138616126795839">"‏&lt;p&gt;هل تريد تحديث هذا التطبيق من &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;؟&lt;/p&gt;&lt;p&gt;يتلقّى هذا التطبيق التحديثات عادةً من &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. من خلال تحديثه مصدر مختلف، قد تتلقّى تحديثات في المستقبل من أي مصدر على هاتفك. قد تتغير وظائف التطبيق.&lt;/p&gt;"</string>
     <string name="install_failed" msgid="5777824004474125469">"التطبيق ليس مثبتًا."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"تم حظر تثبيت الحزمة."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"لم يتم تثبيت التطبيق لأن حزمة التثبيت تتعارض مع حزمة حالية."</string>
diff --git a/packages/PackageInstaller/res/values-as/strings.xml b/packages/PackageInstaller/res/values-as/strings.xml
index 37f6c13..65f6641 100644
--- a/packages/PackageInstaller/res/values-as/strings.xml
+++ b/packages/PackageInstaller/res/values-as/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"এপ্ ইনষ্টল কৰা হ’ল।"</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"আপুনি এই এপ্‌টো ইনষ্টল কৰিবলৈ বিচাৰেনে?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"আপুনি এই এপ্‌টো আপডে’ট কৰিবলৈ বিচাৰেনে?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"এই এপ্‌টো <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>ৰ পৰা আপডে’ট কৰিবনে?\n\nএই এপ্‌টোৱে সাধাৰণতে <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>ৰ পৰা আপডে’ট লাভ কৰে। অন্য এটা উৎসৰ পৰা আপডে’ট কৰি আপুনি যিকোনো উৎসৰ পৰা আপোনাৰ ফ’নত অনাগত আপডে’টসমূহ পাব পাৰে। এপৰ কাৰ্যক্ষমতা সলনি হ’ব পাৰে।"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="7994800761970572198">"&lt;p&gt;&lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;ৰ পৰা এই এপ্‌টো আপডে’ট কৰিবনে?&lt;/p&gt;&lt;p&gt;এই এপ্‌টোৱে সাধাৰণতে &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;ৰ পৰা আপডে’ট লাভ কৰে। অন্য এটা উৎসৰ পৰা আপডে’ট কৰি আপুনি যিকোনো উৎসৰ পৰা আপোনাৰ টেবলেটত অনাগত আপডে’টসমূহ পাব পাৰে। এপৰ কাৰ্যক্ষমতা সলনি হ’ব পাৰে।&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="2435174886412089791">"&lt;p&gt;&lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;ৰ পৰা এই এপ্‌টো আপডে’ট কৰিবনে?&lt;/p&gt;&lt;p&gt;এই এপ্‌টোৱে সাধাৰণতে &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;ৰ পৰা আপডে’ট লাভ কৰে। অন্য এটা উৎসৰ পৰা আপডে’ট কৰি আপুনি যিকোনো উৎসৰ পৰা আপোনাৰ টিভিত অনাগত আপডে’টসমূহ পাব পাৰে। এপৰ কাৰ্যক্ষমতা সলনি হ’ব পাৰে।&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="7155138616126795839">"&lt;p&gt;&lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;ৰ পৰা এই এপ্‌টো আপডে’ট কৰিবনে?&lt;/p&gt;&lt;p&gt;এই এপ্‌টোৱে সাধাৰণতে &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;ৰ পৰা আপডে’ট লাভ কৰে। অন্য এটা উৎসৰ পৰা আপডে’ট কৰি আপুনি যিকোনো উৎসৰ পৰা আপোনাৰ ফ’নত অনাগত আপডে’টসমূহ পাব পাৰে। এপৰ কাৰ্যক্ষমতা সলনি হ’ব পাৰে।&lt;/p&gt;"</string>
     <string name="install_failed" msgid="5777824004474125469">"এপ্ ইনষ্টল কৰা হোৱা নাই।"</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"পেকেজটোৰ ইনষ্টল অৱৰোধ কৰা হৈছে।"</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"এপ্‌টো ইনষ্টল কৰিব পৰা নগ\'ল কাৰণ ইয়াৰ সৈতে আগৰে পৰা থকা এটা পেকেজৰ সংঘাত হৈছে।"</string>
diff --git a/packages/PackageInstaller/res/values-az/strings.xml b/packages/PackageInstaller/res/values-az/strings.xml
index ae7b2fc..cf3ea84 100644
--- a/packages/PackageInstaller/res/values-az/strings.xml
+++ b/packages/PackageInstaller/res/values-az/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"Tətbiq quraşdırılıb."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Bu tətbiqi quraşdırmaq istəyirsiniz?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Bu tətbiqi güncəlləmək istəyirsiniz?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Bu tətbiq <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> mənbəyindən güncəllənsin?\n\nBu tətbiq adətən <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> mənbəyindən güncəllənmələr qəbul edir. Fərqli mənbədən güncəllədikdə telefonda istənilən mənbədən gələcəkdə güncəllənmələr qəbul edə bilərsiniz. Tətbiq funksionallığı dəyişə bilər."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="7994800761970572198">"&lt;p&gt;Bu tətbiq &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt; mənbəyindən güncəllənsin?&lt;/p&gt;&lt;p&gt;Bu tətbiq adətən &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt; mənbəyindən güncəllənir. Fərqli mənbədən güncəllədikdə gələcəkdə planşetdə istənilən mənbədən güncəlləyə bilərsiniz. Tətbiq funksionallığı dəyişə bilər.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="2435174886412089791">"&lt;p&gt;Bu tətbiq &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt; mənbəyindən güncəllənsin?&lt;/p&gt;&lt;p&gt;Bu tətbiq adətən &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt; mənbəyindən güncəllənir. Fərqli mənbədən güncəllədikdə gələcəkdə TV-də istənilən mənbədən güncəlləyə bilərsiniz. Tətbiq funksionallığı dəyişə bilər.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="7155138616126795839">"&lt;p&gt;Bu tətbiq &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt; mənbəyindən güncəllənsin?&lt;/p&gt;&lt;p&gt;Bu tətbiq adətən &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt; mənbəyindən güncəllənir. Fərqli mənbədən güncəllədikdə gələcəkdə telefonda istənilən mənbədən güncəlləyə bilərsiniz. Tətbiq funksionallığı dəyişə bilər.&lt;/p&gt;"</string>
     <string name="install_failed" msgid="5777824004474125469">"Tətbiq quraşdırılmayıb."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"Paketin quraşdırılması blok edildi."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"Bu paketin mövcud paket ilə ziddiyətinə görə tətbiq quraşdırılmadı."</string>
diff --git a/packages/PackageInstaller/res/values-b+sr+Latn/strings.xml b/packages/PackageInstaller/res/values-b+sr+Latn/strings.xml
index 2b0fa82..77fe3ba 100644
--- a/packages/PackageInstaller/res/values-b+sr+Latn/strings.xml
+++ b/packages/PackageInstaller/res/values-b+sr+Latn/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"Aplikacija je instalirana."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Želite da instalirate ovu aplikaciju?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Želite da ažurirate ovu aplikaciju?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Želite da ažurirate ovu aplikaciju iz izvora <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nOva aplikacija se obično ažurira iz izvora <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Ako ažurirate iz drugog izvora, možete da primate buduća ažuriranja iz bilo kog izvora na telefonu. Funkcije aplikacije mogu da se promene."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="7994800761970572198">"&lt;p&gt;Ažurirajte ovu aplikaciju kod: &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Ova aplikacija obično dobija ažuriranja od: &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Ako ažurirate iz drugog izvora, možete da primate buduća ažuriranja iz bilo kog izvora na tabletu. Funkcije aplikacije će se možda promeniti.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="2435174886412089791">"&lt;p&gt;Ažurirajte ovu aplikaciju kod: &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Ova aplikacija obično dobija ažuriranja od: &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Ako ažurirate iz drugog izvora, možete da primate buduća ažuriranja iz bilo kog izvora na TV-u. Funkcije aplikacije će se možda promeniti.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="7155138616126795839">"&lt;p&gt;Ažurirajte ovu aplikaciju kod: &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Ova aplikacija obično dobija ažuriranja od: &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Ako ažurirate iz drugog izvora, možete da primate buduća ažuriranja iz bilo kog izvora na telefonu. Funkcije aplikacije će se možda promeniti.&lt;/p&gt;"</string>
     <string name="install_failed" msgid="5777824004474125469">"Aplikacija nije instalirana."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"Instaliranje paketa je blokirano."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"Aplikacija nije instalirana jer je paket neusaglašen sa postojećim paketom."</string>
diff --git a/packages/PackageInstaller/res/values-be/strings.xml b/packages/PackageInstaller/res/values-be/strings.xml
index d18e009..b10a9e0 100644
--- a/packages/PackageInstaller/res/values-be/strings.xml
+++ b/packages/PackageInstaller/res/values-be/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"Праграма ўсталявана."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Усталяваць гэту праграму?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Абнавіць гэту праграму?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Абнавіць праграму ад <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nЗвычайна гэтая праграма атрымлівае абнаўленні ад <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Пры абнаўленні з іншай крыніцы вы, магчыма, будзеце атрымліваць будучыя абнаўленні з любой крыніцы на тэлефоне. Функцыі праграмы могуць змяніцца."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="7994800761970572198">"&lt;p&gt;Абнавіць праграму з &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Звычайна гэта праграма атрымлівае абнаўленні з &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Калі абнавіць праграму з іншай крыніцы, то ў будучыні вы, магчыма, будзеце атрымліваць абнаўленні з любых крыніц на планшэце. Функцыі праграмы могуць змяніцца.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="2435174886412089791">"&lt;p&gt;Абнавіць праграму з &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Звычайна гэта праграма атрымлівае абнаўленні з &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Калі абнавіць праграму з іншай крыніцы, то ў будучыні вы, магчыма, будзеце атрымліваць абнаўленні з любых крыніц на тэлевізары. Функцыі праграмы могуць змяніцца.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="7155138616126795839">"&lt;p&gt;Абнавіць праграму з &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Звычайна гэта праграма атрымлівае абнаўленні з &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Пры абнаўленні з іншай крыніцы вы, магчыма, будзеце атрымліваць будучыя абнаўленні з любой крыніцы на тэлефоне. Функцыі праграмы могуць змяніцца.&lt;/p&gt;"</string>
     <string name="install_failed" msgid="5777824004474125469">"Праграма не ўсталявана."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"Усталяванне пакета заблакіравана."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"Праграма не ўсталявана, таму што пакет канфліктуе з існуючым пакетам."</string>
diff --git a/packages/PackageInstaller/res/values-bg/strings.xml b/packages/PackageInstaller/res/values-bg/strings.xml
index 6dc927f..8d4739c 100644
--- a/packages/PackageInstaller/res/values-bg/strings.xml
+++ b/packages/PackageInstaller/res/values-bg/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"Приложението бе инсталирано."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Искате ли да инсталирате това приложение?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Искате ли да актуализирате това приложение?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Да се актуализира ли това приложение от <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nТо обикновено получава актуализации от <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Ако инсталирате актуализация от друг източник, може да получавате бъдещи актуализации от който и да е източник на телефона си. Функционалността на приложението може да се промени."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="7994800761970572198">"&lt;p&gt;Това приложение да се актуализира ли от &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Обикновено то получава актуализации от &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Ако инсталирате актуализация от друг източник, може да получавате бъдещи актуализации от който и да е източник на таблета си. Функционалността на приложението може да се промени.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="2435174886412089791">"&lt;p&gt;Това приложение да се актуализира ли от &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Обикновено то получава актуализации от &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Ако инсталирате актуализация от друг източник, може да получавате бъдещи актуализации от който и да е източник на телевизора си. Функционалността на приложението може да се промени.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="7155138616126795839">"&lt;p&gt;Това приложение да се актуализира ли от &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Обикновено то получава актуализации от &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Ако инсталирате актуализация от друг източник, може да получавате бъдещи актуализации от който и да е източник на телефона си. Функционалността на приложението може да се промени.&lt;/p&gt;"</string>
     <string name="install_failed" msgid="5777824004474125469">"Приложението не бе инсталирано."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"Инсталирането на пакета бе блокирано."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"Приложението не бе инсталирано, тъй като пакетът е в конфликт със съществуващ пакет."</string>
diff --git a/packages/PackageInstaller/res/values-bn/strings.xml b/packages/PackageInstaller/res/values-bn/strings.xml
index 5b5c6dc..d3c8ad7 100644
--- a/packages/PackageInstaller/res/values-bn/strings.xml
+++ b/packages/PackageInstaller/res/values-bn/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"অ্যাপটি ইনস্টল করা হয়ে গেছে।"</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"আপনি কি এই অ্যাপটি ইনস্টল করতে চান?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"আপনি কি এই অ্যাপটি আপডেট করতে চান?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> থেকে এই অ্যাপ আপডেট করবেন?\n\nএই অ্যাপ সাধারণত <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> থেকে আপডেট পায়। অন্য কোনও সোর্স থেকে আপডেট করলে, আপনার ফোনে ভবিষ্যতে যেকোনও সোর্স থেকে আপডেট পেতে পারেন। অ্যাপের কার্যকারিতা পরিবর্তন হতে পারে।"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="7994800761970572198">"<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;p&gt; থেকে এই অ্যাপ আপডেট করুন &lt;b&gt;&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;এই অ্যাপটি সাধারণত &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> থেকে আপডেট পায়&lt;/b&gt;. অন্য কোনও সোর্স থেকে আপডেট করলে, আপনার ট্যাবলেটে ভবিষ্যতে যেকোনও সোর্স থেকে আপডেট পেতে পারেন। অ্যাপের কার্যকারিতা হয়ত পরিবর্তন হবে।&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="2435174886412089791">"<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;p&gt; থেকে এই অ্যাপ আপডেট করুন &lt;b&gt;&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;এই অ্যাপটি সাধারণত &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> থেকে আপডেট পায়&lt;/b&gt;. অন্য কোনও সোর্স থেকে আপডেট করলে, আপনার টিভিতে ভবিষ্যতে যেকোনও সোর্স থেকে আপডেট পেতে পারেন। অ্যাপের কার্যকারিতা হয়ত পরিবর্তন হবে।&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="7155138616126795839">"<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;p&gt; থেকে এই অ্যাপ আপডেট করুন &lt;b&gt;&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;এই অ্যাপটি সাধারণত &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> থেকে আপডেট পায়&lt;/b&gt;. অন্য কোনও সোর্স থেকে আপডেট করলে, আপনার ফোনে ভবিষ্যতে যেকোনও সোর্স থেকে আপডেট পেতে পারেন। অ্যাপের কার্যকারিতা হয়ত পরিবর্তন হবে।&lt;/p&gt;"</string>
     <string name="install_failed" msgid="5777824004474125469">"অ্যাপটি ইনস্টল করা হয়নি।"</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"ইনস্টল হওয়া থেকে প্যাকেজটিকে ব্লক করা হয়েছে।"</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"আগে থেকেই থাকা একটি প্যাকেজের সাথে প্যাকেজটির সমস্যা সৃষ্টি হওয়ায় অ্যাপটি ইনস্টল করা যায়নি।"</string>
diff --git a/packages/PackageInstaller/res/values-bs/strings.xml b/packages/PackageInstaller/res/values-bs/strings.xml
index e728937..f93263f0 100644
--- a/packages/PackageInstaller/res/values-bs/strings.xml
+++ b/packages/PackageInstaller/res/values-bs/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"Aplikacija je instalirana."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Želite li instalirati ovu aplikaciju?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Želite li ažurirati ovu aplikaciju?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Ažurirati aplikaciju iz izvora <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nOva aplikacija obično prima ažuriranja iz izvora <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Ako je ažurirate iz drugog izvora, možda ćete primati buduća ažuriranja iz bilo kojeg izvora na telefonu. Funkcionalnost aplikacije se može promijeniti."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="7994800761970572198">"&lt;p&gt;Ažurirajte aplikaciju iz izvora &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Aplikacija obično prima ažuriranja iz izvora &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Ako je ažurirate iz drugog izvora, možda ćete primati buduća ažuriranja iz bilo kojeg izvora na tabletu. Funkcionalnost aplikacije se može promijeniti.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="2435174886412089791">"&lt;p&gt;Ažurirajte aplikaciju iz izvora &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Aplikacija obično prima ažuriranja iz izvora &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Ako je ažurirate iz drugog izvora, možda ćete primati buduća ažuriranja iz bilo kojeg izvora na TV-u. Funkcionalnost aplikacije se može promijeniti.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="7155138616126795839">"&lt;p&gt;Ažurirajte aplikaciju iz izvora &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Aplikacija obično prima ažuriranja iz izvora &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Ako je ažurirate iz drugog izvora, možda ćete primati buduća ažuriranja iz bilo kojeg izvora na telefonu. Funkcionalnost aplikacije se može promijeniti.&lt;/p&gt;"</string>
     <string name="install_failed" msgid="5777824004474125469">"Aplikacija nije instalirana."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"Instaliranje ovog paketa je blokirano."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"Aplikacija nije instalirana jer paket nije usaglašen s postojećim paketom."</string>
diff --git a/packages/PackageInstaller/res/values-ca/strings.xml b/packages/PackageInstaller/res/values-ca/strings.xml
index 8b0d1a8..896b075 100644
--- a/packages/PackageInstaller/res/values-ca/strings.xml
+++ b/packages/PackageInstaller/res/values-ca/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"S\'ha instal·lat l\'aplicació."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Vols instal·lar aquesta aplicació?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Vols actualitzar aquesta aplicació?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Vols actualitzar l\'aplicació des de <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nAquesta aplicació sol rebre actualitzacions a través de <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Si l\'actualitzes des d\'una font diferent, pot ser que en el futur rebis actualitzacions des de qualsevol font del teu telèfon. És possible que la funcionalitat de l\'aplicació canviï."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="7994800761970572198">"&lt;p&gt;Vols actualitzar l\'aplicació de &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Aquesta aplicació sol rebre actualitzacions de &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Si l\'actualitzes des d\'una font diferent, pot ser que en el futur rebis actualitzacions des de qualsevol font de la teva tauleta. És possible que la funcionalitat de l\'aplicació canviï.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="2435174886412089791">"&lt;p&gt;Vols actualitzar l\'aplicació de &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Aquesta aplicació sol rebre actualitzacions de &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Si l\'actualitzes des d\'una font diferent, pot ser que en el futur rebis actualitzacions des de qualsevol font del teu televisor. És possible que la funcionalitat de l\'aplicació canviï.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="7155138616126795839">"&lt;p&gt;Vols actualitzar l\'aplicació de &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Aquesta aplicació sol rebre actualitzacions de &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Si l\'actualitzes des d\'una font diferent, pot ser que en el futur rebis actualitzacions des de qualsevol font del teu telèfon. És possible que la funcionalitat de l\'aplicació canviï.&lt;/p&gt;"</string>
     <string name="install_failed" msgid="5777824004474125469">"No s\'ha instal·lat l\'aplicació."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"El paquet s\'ha bloquejat perquè no es pugui instal·lar."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"L\'aplicació no s\'ha instal·lat perquè el paquet entra en conflicte amb un d\'existent."</string>
diff --git a/packages/PackageInstaller/res/values-cs/strings.xml b/packages/PackageInstaller/res/values-cs/strings.xml
index c96d27e..0874214 100644
--- a/packages/PackageInstaller/res/values-cs/strings.xml
+++ b/packages/PackageInstaller/res/values-cs/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"Aplikace je nainstalována."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Chcete tuto aplikaci nainstalovat?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Chcete tuto aplikaci aktualizovat?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Aktualizovat tuto aplikaci ze zdroje <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nTato aplikace obvykle dostává aktualizace ze zdroje <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Pokud ji aktualizujete z jiného zdroje, budete v budoucnu do telefonu moci dostávat aktualizace z libovolného zdroje. Funkčnost aplikace se může změnit."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="7994800761970572198">"&lt;p&gt;Aktualizovat tuto aplikaci ze zdroje &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Tato aplikace obvykle dostává aktualizace ze zdroje &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Pokud ji aktualizujete z jiného zdroje, budete v budoucnu do tabletu moci dostávat aktualizace z libovolného zdroje. Funkčnost aplikace se může změnit.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="2435174886412089791">"&lt;p&gt;Aktualizovat tuto aplikaci ze zdroje &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Tato aplikace obvykle dostává aktualizace ze zdroje &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Pokud ji aktualizujete z jiného zdroje, budete v budoucnu do televize moci dostávat aktualizace z libovolného zdroje. Funkčnost aplikace se může změnit.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="7155138616126795839">"&lt;p&gt;Aktualizovat tuto aplikaci ze zdroje &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Tato aplikace obvykle dostává aktualizace ze zdroje &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Pokud ji aktualizujete z jiného zdroje, budete v budoucnu do telefonu moci dostávat aktualizace z libovolného zdroje. Funkčnost aplikace se může změnit.&lt;/p&gt;"</string>
     <string name="install_failed" msgid="5777824004474125469">"Aplikaci nelze nainstalovat."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"Instalace balíčku byla zablokována."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"Aplikaci nelze nainstalovat, protože balíček je v konfliktu se stávajícím balíčkem."</string>
diff --git a/packages/PackageInstaller/res/values-da/strings.xml b/packages/PackageInstaller/res/values-da/strings.xml
index d8759d4..85032c9 100644
--- a/packages/PackageInstaller/res/values-da/strings.xml
+++ b/packages/PackageInstaller/res/values-da/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"Appen er installeret."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Vil du installere denne app?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Vil du opdatere denne app?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Vil du opdatere denne app fra <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nDenne app plejer at modtage opdateringer fra <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Hvis du opdaterer fra en anden kilde, vil du kunne modtage opdateringer fra en hvilken som helst kilde på din telefon fremover. Appfunktionaliteten kan ændre sig."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="7994800761970572198">"&lt;p&gt;Vil du opdatere denne app via &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Denne app modtager normalt opdateriner fra &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Hvis du opdaterer fra en anden kilde, vil du kunne modtage opdateringer fra en hvilken som helst kilde på din tablet fremover. Dette kan påvirke appfunktionaliteten.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="2435174886412089791">"&lt;p&gt;Vil du opdatere denne app via &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Denne app modtager normalt opdateriner fra &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Hvis du opdaterer fra en anden kilde, vil du kunne modtage opdateringer fra en hvilken som helst kilde på dit fjernsyn fremover. Dette kan påvirke appfunktionaliteten.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="7155138616126795839">"&lt;p&gt;Vil du opdatere denne app via &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Denne app modtager normalt opdateriner fra &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Hvis du opdaterer fra en anden kilde, vil du kunne modtage opdateringer fra en hvilken som helst kilde på din telefon fremover. Dette kan påvirke appfunktionaliteten.&lt;/p&gt;"</string>
     <string name="install_failed" msgid="5777824004474125469">"Appen blev ikke installeret."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"Pakken blev forhindret i at blive installeret."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"Appen blev ikke installeret, da pakken er i strid med en eksisterende pakke."</string>
diff --git a/packages/PackageInstaller/res/values-de/strings.xml b/packages/PackageInstaller/res/values-de/strings.xml
index bb55708..b638040b 100644
--- a/packages/PackageInstaller/res/values-de/strings.xml
+++ b/packages/PackageInstaller/res/values-de/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"App wurde installiert."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Möchtest du diese App installieren?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Möchtest du diese App aktualisieren?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Diese App mit einem Update von <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> aktualisieren?\n\nSie erhält normalerweise Updates von <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Wenn du ein Update von einer anderen Quelle verwendest, erhältst du möglicherweise zukünftige Updates von beliebigen Quellen auf deinem Smartphone. Die Funktionalität der App kann sich ändern."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="7994800761970572198">"&lt;p&gt;Soll diese App von &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt; aktualisiert werden?&lt;/p&gt;&lt;p&gt;Normalerweise erhält diese App Updates von &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Wenn du ein Update von einer anderen Quelle verwendest, erhältst du künftige Updates auf deinem Tablet möglicherweise aus unklaren Quellen. Der Funktionsumfang der App kann sich dadurch ändern.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="2435174886412089791">"&lt;p&gt;Soll diese App von &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt; aktualisiert werden?&lt;/p&gt;&lt;p&gt;Normalerweise erhält diese App Updates von &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Wenn du ein Update von einer anderen Quelle verwendest, erhältst du künftige Updates auf deinem Fernseher möglicherweise aus unklaren Quellen. Der Funktionsumfang der App kann sich dadurch ändern.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="7155138616126795839">"&lt;p&gt;Soll diese App von &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt; aktualisiert werden?&lt;/p&gt;&lt;p&gt;Normalerweise erhält diese App Updates von &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Wenn du ein Update von einer anderen Quelle verwendest, erhältst du künftige Updates auf deinem Smartphone möglicherweise aus unklaren Quellen. Der Funktionsumfang der App kann sich dadurch ändern.&lt;/p&gt;"</string>
     <string name="install_failed" msgid="5777824004474125469">"App wurde nicht installiert."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"Die Installation des Pakets wurde blockiert."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"Die App wurde nicht installiert, da das Paket in Konflikt mit einem bestehenden Paket steht."</string>
diff --git a/packages/PackageInstaller/res/values-el/strings.xml b/packages/PackageInstaller/res/values-el/strings.xml
index 9721a19..43c9b1e 100644
--- a/packages/PackageInstaller/res/values-el/strings.xml
+++ b/packages/PackageInstaller/res/values-el/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"Η εφαρμογή εγκαταστάθηκε."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Θέλετε να εγκαταστήσετε αυτήν την εφαρμογή;"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Θέλετε να ενημερώσετε αυτήν την εφαρμογή;"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Να ενημερωθεί αυτή η εφαρμογή από <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>;\n\nΗ συγκεκριμένη εφαρμογή λαμβάνει συνήθως ενημερώσεις από <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Αν κάνετε την ενημέρωση από διαφορετική πηγή, μπορεί να λαμβάνετε μελλοντικές ενημερώσεις από οποιαδήποτε πηγή στο τηλέφωνό σας. Η λειτουργικότητα της εφαρμογής μπορεί να αλλάξει."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="7994800761970572198">"&lt;p&gt;Ενημερώστε αυτήν την εφαρμογή από &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Η συγκεκριμένη εφαρμογή λαμβάνει συνήθως ενημερώσεις από &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Αν κάνετε την ενημέρωση από διαφορετική πηγή, μπορεί να λαμβάνετε μελλοντικές ενημερώσεις από οποιαδήποτε πηγή στο tablet σας. Η λειτουργικότητα της εφαρμογής μπορεί να αλλάξει.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="2435174886412089791">"&lt;p&gt;Ενημερώστε αυτήν την εφαρμογή από &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Η συγκεκριμένη εφαρμογή λαμβάνει συνήθως ενημερώσεις από &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Αν κάνετε την ενημέρωση από διαφορετική πηγή, μπορεί να λαμβάνετε μελλοντικές ενημερώσεις από οποιαδήποτε πηγή στην τηλεόρασή σας. Η λειτουργικότητα της εφαρμογής μπορεί να αλλάξει.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="7155138616126795839">"&lt;p&gt;Ενημερώστε αυτήν την εφαρμογή από &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Η συγκεκριμένη εφαρμογή λαμβάνει συνήθως ενημερώσεις από &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Αν κάνετε την ενημέρωση από διαφορετική πηγή, μπορεί να λαμβάνετε μελλοντικές ενημερώσεις από οποιαδήποτε πηγή στο τηλέφωνό σας. Η λειτουργικότητα της εφαρμογής μπορεί να αλλάξει.&lt;/p&gt;"</string>
     <string name="install_failed" msgid="5777824004474125469">"Η εφαρμογή δεν εγκαταστάθηκε."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"Η εγκατάσταση του πακέτου αποκλείστηκε."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"Η εφαρμογή δεν εγκαταστάθηκε, επειδή το πακέτο είναι σε διένεξη με κάποιο υπάρχον πακέτο."</string>
diff --git a/packages/PackageInstaller/res/values-en-rAU/strings.xml b/packages/PackageInstaller/res/values-en-rAU/strings.xml
index 543dbf6..08b8bf1a 100644
--- a/packages/PackageInstaller/res/values-en-rAU/strings.xml
+++ b/packages/PackageInstaller/res/values-en-rAU/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"App installed."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Do you want to install this app?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Do you want to update this app?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Update this app from <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nThis app normally receives updates from <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. By updating from a different source, you may receive future updates from any source on your phone. App functionality may change."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="7994800761970572198">"&lt;p&gt;Update this app from &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;This app normally receives updates from &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. By updating from a different source, you may receive future updates from any source on your tablet. App functionality may change.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="2435174886412089791">"&lt;p&gt;Update this app from &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;This app normally receives updates from &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. By updating from a different source, you may receive future updates from any source on your TV. App functionality may change.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="7155138616126795839">"&lt;p&gt;Update this app from &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;This app normally receives updates from &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. By updating from a different source, you may receive future updates from any source on your phone. App functionality may change.&lt;/p&gt;"</string>
     <string name="install_failed" msgid="5777824004474125469">"App not installed."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"The package was blocked from being installed."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"App not installed as package conflicts with an existing package."</string>
diff --git a/packages/PackageInstaller/res/values-en-rCA/strings.xml b/packages/PackageInstaller/res/values-en-rCA/strings.xml
index f2457f2..da4a8a0 100644
--- a/packages/PackageInstaller/res/values-en-rCA/strings.xml
+++ b/packages/PackageInstaller/res/values-en-rCA/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"App installed."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Do you want to install this app?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Do you want to update this app?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Update this app from <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nThis app normally receives updates from <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. By updating from a different source, you may receive future updates from any source on your phone. App functionality may change."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="7994800761970572198">"&lt;p&gt;Update this app from &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;This app normally receives updates from &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. By updating from a different source, you may receive future updates from any source on your tablet. App functionality may change.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="2435174886412089791">"&lt;p&gt;Update this app from &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;This app normally receives updates from &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. By updating from a different source, you may receive future updates from any source on your TV. App functionality may change.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="7155138616126795839">"&lt;p&gt;Update this app from &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;This app normally receives updates from &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. By updating from a different source, you may receive future updates from any source on your phone. App functionality may change.&lt;/p&gt;"</string>
     <string name="install_failed" msgid="5777824004474125469">"App not installed."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"The package was blocked from being installed."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"App not installed as package conflicts with an existing package."</string>
diff --git a/packages/PackageInstaller/res/values-en-rGB/strings.xml b/packages/PackageInstaller/res/values-en-rGB/strings.xml
index 543dbf6..08b8bf1a 100644
--- a/packages/PackageInstaller/res/values-en-rGB/strings.xml
+++ b/packages/PackageInstaller/res/values-en-rGB/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"App installed."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Do you want to install this app?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Do you want to update this app?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Update this app from <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nThis app normally receives updates from <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. By updating from a different source, you may receive future updates from any source on your phone. App functionality may change."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="7994800761970572198">"&lt;p&gt;Update this app from &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;This app normally receives updates from &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. By updating from a different source, you may receive future updates from any source on your tablet. App functionality may change.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="2435174886412089791">"&lt;p&gt;Update this app from &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;This app normally receives updates from &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. By updating from a different source, you may receive future updates from any source on your TV. App functionality may change.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="7155138616126795839">"&lt;p&gt;Update this app from &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;This app normally receives updates from &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. By updating from a different source, you may receive future updates from any source on your phone. App functionality may change.&lt;/p&gt;"</string>
     <string name="install_failed" msgid="5777824004474125469">"App not installed."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"The package was blocked from being installed."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"App not installed as package conflicts with an existing package."</string>
diff --git a/packages/PackageInstaller/res/values-en-rIN/strings.xml b/packages/PackageInstaller/res/values-en-rIN/strings.xml
index 543dbf6..08b8bf1a 100644
--- a/packages/PackageInstaller/res/values-en-rIN/strings.xml
+++ b/packages/PackageInstaller/res/values-en-rIN/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"App installed."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Do you want to install this app?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Do you want to update this app?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Update this app from <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nThis app normally receives updates from <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. By updating from a different source, you may receive future updates from any source on your phone. App functionality may change."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="7994800761970572198">"&lt;p&gt;Update this app from &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;This app normally receives updates from &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. By updating from a different source, you may receive future updates from any source on your tablet. App functionality may change.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="2435174886412089791">"&lt;p&gt;Update this app from &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;This app normally receives updates from &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. By updating from a different source, you may receive future updates from any source on your TV. App functionality may change.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="7155138616126795839">"&lt;p&gt;Update this app from &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;This app normally receives updates from &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. By updating from a different source, you may receive future updates from any source on your phone. App functionality may change.&lt;/p&gt;"</string>
     <string name="install_failed" msgid="5777824004474125469">"App not installed."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"The package was blocked from being installed."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"App not installed as package conflicts with an existing package."</string>
diff --git a/packages/PackageInstaller/res/values-en-rXC/strings.xml b/packages/PackageInstaller/res/values-en-rXC/strings.xml
index a674c20..0749a27 100644
--- a/packages/PackageInstaller/res/values-en-rXC/strings.xml
+++ b/packages/PackageInstaller/res/values-en-rXC/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‏‏‎‎‎‏‎‏‏‏‎‏‏‎‎‎‏‏‎‏‏‏‎‏‎‏‏‏‏‎‎‏‏‏‏‎‏‏‎‎‎‎‎‏‏‎‎‎‏‏‎‏‎‏‏‎‎‎‎App installed.‎‏‎‎‏‎"</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‏‎‎‏‎‏‏‎‏‏‎‎‎‎‏‏‎‏‎‎‏‎‏‎‎‏‏‏‏‎‎‏‎‎‎‏‎‏‎‎‎‎‏‏‏‏‏‎‏‎‎‎‏‎‏‏‏‏‎Do you want to install this app?‎‏‎‎‏‎"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‏‏‎‎‏‏‏‏‎‎‏‏‎‏‎‎‏‎‎‏‎‏‎‏‎‏‏‎‎‎‎‎‎‏‎‏‎‏‏‎‏‎‎‎‎‎‎‎‎‎‏‎‏‏‎‏‎‎‎‎Do you want to update this app?‎‏‎‎‏‎"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‎‎‎‎‎‎‏‏‏‎‎‎‏‎‏‏‏‎‎‏‏‎‎‎‏‎‏‏‏‏‏‎‏‎‎‎‏‎‎‏‎‎‏‏‎‎‎‎‎‏‎‎‎‏‏‎‏‎‎Update this app from ‎‏‎‎‏‏‎<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>‎‏‎‎‏‏‏‎?‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎This app normally receives updates from ‎‏‎‎‏‏‎<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>‎‏‎‎‏‏‏‎. By updating from a different source, you may receive future updates from any source on your phone. App functionality may change.‎‏‎‎‏‎"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="7994800761970572198">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‏‎‏‏‏‏‎‎‏‏‎‎‏‏‏‏‎‎‏‏‏‎‏‏‏‏‎‏‎‎‏‎‏‏‎‏‎‎‎‏‏‏‎‎‏‏‏‎‏‏‏‎‏‎‎‏‏‎‎&lt;p&gt;Update this app from &lt;b&gt;‎‏‎‎‏‏‎<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>‎‏‎‎‏‏‏‎&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;This app normally receives updates from &lt;b&gt;‎‏‎‎‏‏‎<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>‎‏‎‎‏‏‏‎&lt;/b&gt;. By updating from a different source, you may receive future updates from any source on your tablet. App functionality may change.&lt;/p&gt;‎‏‎‎‏‎"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="2435174886412089791">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‎‎‏‏‏‎‎‏‎‏‏‎‏‏‏‏‎‏‎‏‎‏‎‏‎‎‎‎‎‏‏‎‏‏‎‏‎‎‎‏‎‏‏‎‏‏‏‏‎‎‏‏‎‏‏‏‏‏‏‎&lt;p&gt;Update this app from &lt;b&gt;‎‏‎‎‏‏‎<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>‎‏‎‎‏‏‏‎&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;This app normally receives updates from &lt;b&gt;‎‏‎‎‏‏‎<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>‎‏‎‎‏‏‏‎&lt;/b&gt;. By updating from a different source, you may receive future updates from any source on your TV. App functionality may change.&lt;/p&gt;‎‏‎‎‏‎"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="7155138616126795839">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‎‏‏‎‏‎‎‏‏‎‎‎‎‏‎‏‎‎‎‏‎‏‎‏‎‎‏‎‏‏‎‏‏‎‎‏‏‏‎‏‎‏‎‏‏‏‏‎‎‎‎‎‎‏‏‏‏‏‏‎&lt;p&gt;Update this app from &lt;b&gt;‎‏‎‎‏‏‎<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>‎‏‎‎‏‏‏‎&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;This app normally receives updates from &lt;b&gt;‎‏‎‎‏‏‎<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>‎‏‎‎‏‏‏‎&lt;/b&gt;. By updating from a different source, you may receive future updates from any source on your phone. App functionality may change.&lt;/p&gt;‎‏‎‎‏‎"</string>
     <string name="install_failed" msgid="5777824004474125469">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‎‎‎‎‏‎‏‏‏‎‏‏‏‏‎‏‎‎‎‏‎‏‎‎‎‏‏‏‏‎‎‎‎‏‎‏‏‎‎‏‏‏‏‎‏‏‏‏‎‎‏‎‎‏‏‏‎‏‎App not installed.‎‏‎‎‏‎"</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‏‎‎‎‏‎‎‎‎‏‏‎‏‏‎‏‎‏‏‎‎‎‎‏‎‎‏‏‎‏‏‏‏‏‎‏‎‏‏‏‏‎‎‎‏‏‎‏‏‎‎‏‎‏‏‏‏‎‎The package was blocked from being installed.‎‏‎‎‏‎"</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‎‎‎‏‏‏‏‎‏‎‎‏‎‎‏‎‎‎‏‎‎‎‏‎‎‎‏‎‎‎‎‏‎‎‏‏‏‏‎‎‏‏‎‎‏‎‏‎‎‏‎‏‎‏‎‎‏‎‎App not installed as package conflicts with an existing package.‎‏‎‎‏‎"</string>
diff --git a/packages/PackageInstaller/res/values-es-rUS/strings.xml b/packages/PackageInstaller/res/values-es-rUS/strings.xml
index eb3abe9..3330e5f 100644
--- a/packages/PackageInstaller/res/values-es-rUS/strings.xml
+++ b/packages/PackageInstaller/res/values-es-rUS/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"Se instaló la app."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"¿Deseas instalar esta app?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"¿Deseas actualizar esta app?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"¿Quieres actualizar esta app a través de <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nEn general, esta suele recibir actualizaciones de <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Si actualizas a través de otra fuente, es posible que recibas las próximas actualizaciones de cualquier fuente en el teléfono. Por ende, podría verse afectada la funcionalidad de la app."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="7994800761970572198">"&lt;p&gt;Actualiza esta app desde &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Esta app normalmente recibe actualizaciones desde &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Si actualizas a través de otra fuente, es posible que recibas las próximas actualizaciones de cualquier fuente en la tablet. Las funciones de la app puede cambiar.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="2435174886412089791">"&lt;p&gt;Actualiza esta app desde &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Esta app normalmente recibe actualizaciones desde &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Si actualizas a través de otra fuente, es posible que recibas las próximas actualizaciones de cualquier fuente en la TV. Las funciones de la app puede cambiar.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="7155138616126795839">"&lt;p&gt;Actualiza esta app desde &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Esta app normalmente recibe actualizaciones desde &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Si actualizas a través de otra fuente, es posible que recibas las próximas actualizaciones de cualquier fuente en el teléfono. Las funciones de la app puede cambiar.&lt;/p&gt;"</string>
     <string name="install_failed" msgid="5777824004474125469">"No se instaló la app."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"Se bloqueó el paquete para impedir la instalación."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"No se instaló la app debido a un conflicto con un paquete."</string>
diff --git a/packages/PackageInstaller/res/values-es/strings.xml b/packages/PackageInstaller/res/values-es/strings.xml
index 9005718..c351f15 100644
--- a/packages/PackageInstaller/res/values-es/strings.xml
+++ b/packages/PackageInstaller/res/values-es/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"Aplicación instalada."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"¿Quieres instalar esta aplicación?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"¿Quieres actualizar esta aplicación?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"¿Actualizar esta aplicación a través de <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nEsta aplicación normalmente recibe actualizaciones a través de <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Si la actualizas usando otra fuente, puede que recibas futuras actualizaciones a través de cualquier fuente en tu teléfono. La funcionalidad de la aplicación puede cambiar."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="7994800761970572198">"&lt;p&gt;¿Actualizar esta aplicación con &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Esta aplicación suele recibir actualizaciones de &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Si actualizas a través de otra fuente, puede que recibas futuras actualizaciones de cualquier fuente de tu tablet. La funcionalidad de la aplicación puede cambiar.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="2435174886412089791">"&lt;p&gt;¿Actualizar esta aplicación con &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Esta aplicación suele recibir actualizaciones de &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Si actualizas a través de otra fuente, puede que recibas futuras actualizaciones de cualquier fuente de tu TV. La funcionalidad de la aplicación puede cambiar.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="7155138616126795839">"&lt;p&gt;¿Actualizar esta aplicación con &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Esta aplicación suele recibir actualizaciones de &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Si actualizas a través de otra fuente, puede que recibas futuras actualizaciones de cualquier fuente de tu teléfono. La funcionalidad de la aplicación puede cambiar.&lt;/p&gt;"</string>
     <string name="install_failed" msgid="5777824004474125469">"No se ha instalado la aplicación."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"Se ha bloqueado la instalación del paquete."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"La aplicación no se ha instalado debido a un conflicto con un paquete."</string>
diff --git a/packages/PackageInstaller/res/values-et/strings.xml b/packages/PackageInstaller/res/values-et/strings.xml
index cf488a9..5ecfbf4 100644
--- a/packages/PackageInstaller/res/values-et/strings.xml
+++ b/packages/PackageInstaller/res/values-et/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"Rakendus on installitud."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Kas soovite selle rakenduse installida?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Kas soovite seda rakendust värskendada?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Kas värskendada seda rakendust allikast <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nSee rakendus saab tavaliselt värskendusi allikast <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Muust allikast värskendamise korral võite edaspidi saada telefonis värskendusi mis tahes allikast. Rakenduse funktsioonid võivad muutuda."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="7994800761970572198">"&lt;p&gt;Update this app from &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;See rakendus saab tavaliselt värskendusi omanikult &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Muust allikast värskendamise korral võite edaspidi saada tahvelarvutis värskendusi mis tahes allikast. Rakenduse funktsioonid võivad muutuda.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="2435174886412089791">"&lt;p&gt;Update this app from &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;See rakendus saab tavaliselt värskendusi omanikult &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Muust allikast värskendamise korral võite edaspidi saada teleris värskendusi mis tahes allikast. Rakenduse funktsioonid võivad muutuda.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="7155138616126795839">"&lt;p&gt;Update this app from &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;See rakendus saab tavaliselt värskendusi omanikult &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Muust allikast värskendamise korral võite edaspidi saada telefonis värskendusi mis tahes allikast. Rakenduse funktsioonid võivad muutuda.&lt;/p&gt;"</string>
     <string name="install_failed" msgid="5777824004474125469">"Rakendus pole installitud."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"Paketi installimine blokeeriti."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"Rakendust ei installitud, kuna pakett on olemasoleva paketiga vastuolus."</string>
diff --git a/packages/PackageInstaller/res/values-eu/strings.xml b/packages/PackageInstaller/res/values-eu/strings.xml
index 9dadbed..ec720ac 100644
--- a/packages/PackageInstaller/res/values-eu/strings.xml
+++ b/packages/PackageInstaller/res/values-eu/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"Instalatu da aplikazioa."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Aplikazioa instalatu nahi duzu?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Aplikazioa eguneratu nahi duzu?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Aplikazioa <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> zerbitzutik eguneratu nahi duzu?\n\nAplikazioak <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> zerbitzutik jaso ohi ditu eguneratzeak. Beste iturburu batetik eguneratuz gero, baliteke aurrerantzeko eguneratzeak telefonoko edozein iturburutatik jasotzea. Baliteke aplikazioaren funtzioak aldatzea."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="7994800761970572198">"&lt;p&gt;Aplikazioa &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt; iturburutik eguneratu nahi duzu?&lt;/p&gt;&lt;p&gt;Normalean, &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt; iturburuaren eguneratzeak instalatzen dira aplikazioan. Aplikazioa beste iturburu batetik eguneratuz gero, baliteke aurrerantzeko eguneratzeak tabletako edozein iturburutatik jasotzea. Baliteke aplikazioaren funtzioak aldatzea.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="2435174886412089791">"&lt;p&gt;Aplikazioa &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt; iturburutik eguneratu nahi duzu?&lt;/p&gt;&lt;p&gt;Normalean, &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt; iturburuaren eguneratzeak instalatzen dira aplikazioan. Aplikazioa beste iturburu batetik eguneratuz gero, baliteke aurrerantzeko eguneratzeak telebistako edozein iturburutatik jasotzea. Baliteke aplikazioaren funtzioak aldatzea.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="7155138616126795839">"&lt;p&gt;Aplikazioa &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt; iturburutik eguneratu nahi duzu?&lt;/p&gt;&lt;p&gt;Normalean, &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt; iturburuaren eguneratzeak instalatzen dira aplikazioan. Aplikazioa beste iturburu batetik eguneratuz gero, baliteke aurrerantzeko eguneratzeak telefonoko edozein iturburutatik jasotzea. Baliteke aplikazioaren funtzioak aldatzea.&lt;/p&gt;"</string>
     <string name="install_failed" msgid="5777824004474125469">"Ez da instalatu aplikazioa."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"Paketea instalatzeko aukera blokeatu egin da."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"Ez da instalatu aplikazioa, gatazka bat sortu delako lehendik dagoen pakete batekin."</string>
diff --git a/packages/PackageInstaller/res/values-fa/strings.xml b/packages/PackageInstaller/res/values-fa/strings.xml
index 73b070d..7a8b579 100644
--- a/packages/PackageInstaller/res/values-fa/strings.xml
+++ b/packages/PackageInstaller/res/values-fa/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"برنامه نصب شد."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"می‌خواهید این برنامه را نصب کنید؟"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"می‌خواهید این برنامه را به‌روزرسانی کنید؟"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"این برنامه ازطریق <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> به‌روز شود؟\n\nاین برنامه معمولاً به‌روزرسانی‌ها را از <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> دریافت می‌کند. با به‌روزرسانی از منبعی متفاوت، ممکن است به‌روزرسانی‌های بعدی را از هر منبعی در تلفنتان دریافت کنید. قابلیت‌های برنامه ممکن است تغییر کند."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="7994800761970572198">"‏&lt;p&gt;این برنامه ازطریق &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt; به‌روز شود؟&lt;/p&gt;&lt;p&gt;این برنامه معمولاً به‌روزرسانی‌ها را از &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt; دریافت می‌کند. با به‌روزرسانی از منبعی متفاوت، ممکن است به‌روزرسانی‌های آتی را از هر منبعی در رایانه لوحی‌تان دریافت کنید. ممکن است قابلیت‌های برنامه تغییر کند.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="2435174886412089791">"‏&lt;p&gt;این برنامه ازطریق &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt; به‌روز شود؟&lt;/p&gt;&lt;p&gt;این برنامه معمولاً به‌روزرسانی‌ها را از &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt; دریافت می‌کند. با به‌روزرسانی از منبعی متفاوت، ممکن است به‌روزرسانی‌های آتی را از هر منبعی در تلویزیون دریافت کنید. ممکن است قابلیت‌های برنامه تغییر کند.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="7155138616126795839">"‏&lt;p&gt;این برنامه ازطریق &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt; به‌روز شود؟&lt;/p&gt;&lt;p&gt;این برنامه معمولاً به‌روزرسانی‌ها را از &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt; دریافت می‌کند. با به‌روزرسانی از منبعی متفاوت، ممکن است به‌روزرسانی‌های بعدی را از هر منبعی در تلفنتان دریافت کنید. ممکن است قابلیت‌های برنامه تغییر کند.&lt;/p&gt;"</string>
     <string name="install_failed" msgid="5777824004474125469">"برنامه نصب نشد."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"از نصب شدن بسته جلوگیری شد."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"برنامه نصب نشد چون بسته با بسته موجود تداخل دارد."</string>
diff --git a/packages/PackageInstaller/res/values-fi/strings.xml b/packages/PackageInstaller/res/values-fi/strings.xml
index ee8910b..a9b6984 100644
--- a/packages/PackageInstaller/res/values-fi/strings.xml
+++ b/packages/PackageInstaller/res/values-fi/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"Sovellus on asennettu."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Haluatko asentaa tämän sovelluksen?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Haluatko päivittää tämän sovelluksen?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Voiko <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> päivittää sovelluksen?\n\nTämän sovelluksen päivitykset tarjoaa yleensä <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Kun päivität uudesta lähteestä, tulevat päivitykset voivat tulla mistä tahansa puhelimella olevasta lähteestä. Sovelluksen toiminnot voivat muuttua."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="7994800761970572198">"&lt;p&gt;Päivitä sovellus täältä: &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Sovellus saa päivitykset yleensä täältä: &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Kun päivität uudesta lähteestä, tulevat päivitykset voivat tulla mistä tahansa tabletilla olevasta lähteestä. Sovelluksen toiminnot voivat muuttua.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="2435174886412089791">"&lt;p&gt;Päivitä sovellus täältä: &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Sovellus saa päivitykset yleensä täältä: &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Kun päivität uudesta lähteestä, tulevat päivitykset voivat tulla mistä tahansa televisiolla olevasta lähteestä. Sovelluksen toiminnot voivat muuttua.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="7155138616126795839">"&lt;p&gt;Päivitä sovellus täältä: &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Sovellus saa päivitykset yleensä täältä: &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Kun päivität uudesta lähteestä, tulevat päivitykset voivat tulla mistä tahansa puhelimella olevasta lähteestä. Sovelluksen toiminnot voivat muuttua.&lt;/p&gt;"</string>
     <string name="install_failed" msgid="5777824004474125469">"Sovellusta ei asennettu."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"Paketin asennus estettiin."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"Sovellusta ei asennettu, koska paketti on ristiriidassa nykyisen paketin kanssa."</string>
diff --git a/packages/PackageInstaller/res/values-fr-rCA/strings.xml b/packages/PackageInstaller/res/values-fr-rCA/strings.xml
index b971c35..0ef53b9 100644
--- a/packages/PackageInstaller/res/values-fr-rCA/strings.xml
+++ b/packages/PackageInstaller/res/values-fr-rCA/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"Application installée."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Voulez-vous installer cette application?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Voulez-vous mettre à jour cette application?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Mettre à jour cette application à partir de <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nCette application reçoit normalement des mises à jour de <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. En effectuant une mise à jour à partir d\'une source différente, vous pourriez recevoir des mises à jour futures à partir de n\'importe quelle source sur votre téléphone. Le fonctionnement de l\'application peut en être modifié."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="7994800761970572198">"&lt;p&gt;Mettre à jour cette application à partir de &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Cette application reçoit normalement des mises à jour de &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. En effectuant une mise à jour à partir d\'une source différente, vous pourriez recevoir des mises à jour futures à partir de n\'importe quelle source sur votre tablette. Le fonctionnement de l\'application peut en être modifié.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="2435174886412089791">"&lt;p&gt;Mettre à jour cette application à partir de &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Cette application reçoit normalement des mises à jour de &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. En effectuant une mise à jour à partir d\'une source différente, vous pourriez recevoir des mises à jour futures à partir de n\'importe quelle source sur votre téléviseur. Le fonctionnement de l\'application peut en être modifié.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="7155138616126795839">"&lt;p&gt;Mettre à jour cette application à partir de &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Cette application reçoit normalement des mises à jour de &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. En effectuant une mise à jour à partir d\'une source différente, vous pourriez recevoir des mises à jour futures à partir de n\'importe quelle source sur votre téléphone. Le fonctionnement de l\'application peut en être modifié.&lt;/p&gt;"</string>
     <string name="install_failed" msgid="5777824004474125469">"Application non installée."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"L\'installation du paquet a été bloquée."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"L\'application n\'a pas été installée, car le paquet entre en conflit avec un paquet existant."</string>
diff --git a/packages/PackageInstaller/res/values-fr/strings.xml b/packages/PackageInstaller/res/values-fr/strings.xml
index 08d37d1..5065a22 100644
--- a/packages/PackageInstaller/res/values-fr/strings.xml
+++ b/packages/PackageInstaller/res/values-fr/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"Application installée."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Voulez-vous installer cette appli ?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Voulez-vous mettre à jour cette appli ?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Mettre à jour cette appli à partir de <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> ?\n\nCette appli reçoit normalement des mises à jour de <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Si vous effectuez la mise à jour à partir d\'une autre source, vous recevrez peut-être les prochaines mises à jour depuis n\'importe quelle source sur votre téléphone. Le fonctionnement de l\'application est susceptible de changer."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="7994800761970572198">"&lt;p&gt;Mettez à jour cette appli depuis &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Elle reçoit normalement des mises à jour depuis &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Si vous effectuez la mise à jour à partir d\'une autre source, vous recevrez peut-être les prochaines mises à jour depuis n\'importe quelle source sur votre tablette. Le fonctionnement de l\'application peut changer.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="2435174886412089791">"&lt;p&gt;Mettez à jour cette appli depuis &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Elle reçoit normalement des mises à jour depuis &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Si vous effectuez la mise à jour à partir d\'une autre source, vous recevrez peut-être les prochaines mises à jour depuis n\'importe quelle source sur votre téléviseur. Le fonctionnement de l\'application peut changer.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="7155138616126795839">"&lt;p&gt;Mettez à jour cette appli depuis &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Elle reçoit normalement des mises à jour depuis &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Si vous effectuez la mise à jour à partir d\'une autre source, vous recevrez peut-être les prochaines mises à jour depuis n\'importe quelle source sur votre téléphone. Le fonctionnement de l\'application peut changer.&lt;/p&gt;"</string>
     <string name="install_failed" msgid="5777824004474125469">"Application non installée."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"L\'installation du package a été bloquée."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"L\'application n\'a pas été installée, car le package est en conflit avec un package déjà présent."</string>
diff --git a/packages/PackageInstaller/res/values-gl/strings.xml b/packages/PackageInstaller/res/values-gl/strings.xml
index d6cbf60..694218d 100644
--- a/packages/PackageInstaller/res/values-gl/strings.xml
+++ b/packages/PackageInstaller/res/values-gl/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"Instalouse a aplicación."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Queres instalar esta aplicación?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Queres actualizar esta aplicación?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Queres actualizar esta aplicación desde <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nAs actualizacións desta aplicación adoitan provir de <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Se actualizas a aplicación desde unha fonte diferente, pode que, a partir de agora, recibas actualizacións de calquera fonte no teléfono. Poderían cambiar as funcións da aplicación."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="7994800761970572198">"&lt;p&gt;Actualiza esta aplicación desde &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Esta aplicación adoita recibir actualizacións de &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Se actualizas a aplicación desde unha fonte diferente, pode que, a partir de agora, recibas actualizacións de calquera fonte na tableta. As funcións da aplicación poderían variar.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="2435174886412089791">"&lt;p&gt;Actualiza esta aplicación desde &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Esta aplicación adoita recibir actualizacións de &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Se actualizas a aplicación desde unha fonte diferente, pode que, a partir de agora, recibas actualizacións de calquera fonte na televisión. As funcións da aplicación poderían variar.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="7155138616126795839">"&lt;p&gt;Actualiza esta aplicación desde &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Esta aplicación adoita recibir actualizacións de &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Se actualizas a aplicación desde unha fonte diferente, pode que, a partir de agora, recibas actualizacións de calquera fonte no teléfono. As funcións da aplicación poderían variar.&lt;/p&gt;"</string>
     <string name="install_failed" msgid="5777824004474125469">"Non se instalou a aplicación"</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"Bloqueouse a instalación do paquete."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"A aplicación non se instalou porque o paquete presenta un conflito cun paquete que xa hai."</string>
diff --git a/packages/PackageInstaller/res/values-gu/strings.xml b/packages/PackageInstaller/res/values-gu/strings.xml
index dcaa48f..8f5976c 100644
--- a/packages/PackageInstaller/res/values-gu/strings.xml
+++ b/packages/PackageInstaller/res/values-gu/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"ઍપ્લિકેશન ઇન્સ્ટૉલ કરી."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"શું તમે આ ઍપ ઇન્સ્ટૉલ કરવા માગો છો?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"શું તમે આ ઍપ અપડેટ કરવા માગો છો?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"આ ઍપને <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>થી અપડેટ કરવી છે?\n\nઆ ઍપ સામાન્ય રીતે <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>થી અપડેટ મેળવે છે. અલગ સૉર્સથી અપડેટ કરીને, તમે તમારા ફોન પર કોઈપણ સૉર્સથી ભાવિ અપડેટ મેળવી શકો છો. ઍપની કાર્યક્ષમતા બદલાઈ શકે છે."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="7994800761970572198">"&lt;p&gt;શું આ ઍપને &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;થી અપડેટ કરવી છે?&lt;/p&gt;&lt;p&gt;આ ઍપ સામાન્ય રીતે &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;થી અપડેટ મેળવે છે. અલગ સૉર્સથી અપડેટ કરીને, તમે તમારા ટૅબ્લેટ પર કોઈપણ સૉર્સથી ભાવિ અપડેટ મેળવી શકો છો. ઍપની કાર્યક્ષમતામાં ફેરફાર થઈ શકે.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="2435174886412089791">"&lt;p&gt;શું આ ઍપને &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;થી અપડેટ કરવી છે?&lt;/p&gt;&lt;p&gt;આ ઍપ સામાન્ય રીતે &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;થી અપડેટ મેળવે છે. અલગ સૉર્સથી અપડેટ કરીને, તમે તમારા ટીવી પર કોઈપણ સૉર્સથી ભાવિ અપડેટ મેળવી શકો છો. ઍપની કાર્યક્ષમતામાં ફેરફાર થઈ શકે.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="7155138616126795839">"&lt;p&gt;શું આ ઍપને &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;થી અપડેટ કરવી છે?&lt;/p&gt;&lt;p&gt;આ ઍપ સામાન્ય રીતે &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;થી અપડેટ મેળવે છે. અલગ સૉર્સથી અપડેટ કરીને, તમે તમારા ફોન પર કોઈપણ સૉર્સથી ભાવિ અપડેટ મેળવી શકો છો. ઍપની કાર્યક્ષમતામાં ફેરફાર થઈ શકે.&lt;/p&gt;"</string>
     <string name="install_failed" msgid="5777824004474125469">"ઍપ્લિકેશન ઇન્સ્ટૉલ કરી નથી."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"પૅકેજને ઇન્સ્ટૉલ થવાથી બ્લૉક કરવામાં આવ્યું હતું."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"પૅકેજનો અસ્તિત્વમાંના પૅકેજ સાથે વિરોધાભાસ હોવાને કારણે ઍપ્લિકેશન ઇન્સ્ટૉલ થઈ નથી."</string>
diff --git a/packages/PackageInstaller/res/values-hi/strings.xml b/packages/PackageInstaller/res/values-hi/strings.xml
index df3353b..ddb16dd 100644
--- a/packages/PackageInstaller/res/values-hi/strings.xml
+++ b/packages/PackageInstaller/res/values-hi/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"ऐप्लिकेशन इंस्‍टॉल हो गया."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"क्या आपको यह ऐप्लिकेशन इंस्टॉल करना है?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"क्या आप इस ऐप्लिकेशन को अपडेट करना चाहते हैं?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"क्या इस ऐप्लिकेशन को <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> से अपडेट करना है?\n\nआम तौर पर, इस ऐप्लिकेशन को <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> से अपडेट मिलते हैं. किसी दूसरे सोर्स से अपडेट करने पर, आपको नए अपडेट फ़ोन पर मौजूद किसी भी सोर्स से मिल सकते हैं. इसके साथ ही, ऐप्लिकेशन की मुख्य सुविधाओं और उनके काम करने के तरीके में बदलाव आ सकता है."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="7994800761970572198">"&lt;p&gt;क्या इस ऐप्लिकेशन को &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt; से अपडेट करना है?&lt;/p&gt;&lt;p&gt;आम तौर पर, इस ऐप्लिकेशन को &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt; से अपडेट मिलते हैं. किसी दूसरे सोर्स से अपडेट करने पर, आपको आगे से अपने टैबलेट पर किसी भी सोर्स से अपडेट मिल सकते हैं. ऐप्लिकेशन की मुख्य सुविधाएं और उनके काम करने का तरीका बदल सकता है.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="2435174886412089791">"&lt;p&gt;क्या इस ऐप्लिकेशन को &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt; से अपडेट करना है?&lt;/p&gt;&lt;p&gt;आम तौर पर, इस ऐप्लिकेशन को &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt; से अपडेट मिलते हैं. किसी दूसरे सोर्स से अपडेट करने पर, आपको आगे से अपने टीवी पर किसी भी सोर्स से अपडेट मिल सकते हैं. ऐप्लिकेशन की मुख्य सुविधाएं और उनके काम करने का तरीका बदल सकता है.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="7155138616126795839">"&lt;p&gt;क्या इस ऐप्लिकेशन को &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt; से अपडेट करना है?&lt;/p&gt;&lt;p&gt;आम तौर पर, इस ऐप्लिकेशन को &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt; से अपडेट मिलते हैं. किसी दूसरे सोर्स से अपडेट करने पर, आपको आगे से अपने फ़ोन पर किसी भी सोर्स से अपडेट मिल सकते हैं. ऐप्लिकेशन की मुख्य सुविधाएं और उनके काम करने का तरीका बदल सकता है.&lt;/p&gt;"</string>
     <string name="install_failed" msgid="5777824004474125469">"ऐप्लिकेशन इंस्‍टॉल नहीं हुआ."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"पैकेज को इंस्टॉल होने से ब्लॉक किया हुआ है."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"ऐप्लिकेशन इंस्टॉल नहीं हुआ क्योंकि पैकेज का किसी मौजूदा पैकेज से विरोध है."</string>
diff --git a/packages/PackageInstaller/res/values-hr/strings.xml b/packages/PackageInstaller/res/values-hr/strings.xml
index 74c1fa1..c758f4f 100644
--- a/packages/PackageInstaller/res/values-hr/strings.xml
+++ b/packages/PackageInstaller/res/values-hr/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"Aplikacija je instalirana."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Želite li instalirati ovu aplikaciju?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Želite li ažurirati ovu aplikaciju?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Želite li ovu aplikaciju ažurirati s izvora <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nOva aplikacija obično prima ažuriranja s izvora <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Ako je ažurirate s nekog drugog izvora, buduća ažuriranja možete primati s bilo kojeg izvora na svojem telefonu. Funkcije aplikacije mogu se promijeniti."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="7994800761970572198">"&lt;p&gt;Želite li ovu aplikaciju ažurirati sa &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Ova aplikacija uglavnom prima ažuriranja sa &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Ako je ažurirate s nekog drugog izvora, buduća ažuriranja možete primati s bilo kojeg izvora na svojem tabletu. Funkcije aplikacije mogu se promijeniti.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="2435174886412089791">"&lt;p&gt;Želite li ovu aplikaciju ažurirati sa &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Ova aplikacija uglavnom prima ažuriranja sa &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Ako je ažurirate s nekog drugog izvora, buduća ažuriranja možete primati s bilo kojeg izvora na svojem TV-u. Funkcije aplikacije mogu se promijeniti.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="7155138616126795839">"&lt;p&gt;Želite li ovu aplikaciju ažurirati sa &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Ova aplikacija uglavnom prima ažuriranja sa &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Ako je ažurirate s nekog drugog izvora, buduća ažuriranja možete primati s bilo kojeg izvora na svojem telefonu. Funkcije aplikacije mogu se promijeniti.&lt;/p&gt;"</string>
     <string name="install_failed" msgid="5777824004474125469">"Aplikacija nije instalirana."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"Instaliranje paketa blokirano je."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"Aplikacija koja nije instalirana kao paket u sukobu je s postojećim paketom."</string>
diff --git a/packages/PackageInstaller/res/values-hu/strings.xml b/packages/PackageInstaller/res/values-hu/strings.xml
index 823c20c..4fd866d 100644
--- a/packages/PackageInstaller/res/values-hu/strings.xml
+++ b/packages/PackageInstaller/res/values-hu/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"Alkalmazás telepítve."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Telepíti ezt az alkalmazást?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Frissíti ezt az alkalmazást?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Frissíti az appot ebből a forrásból: <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nEz az app általában a következő forrásból kap frissítéseket: <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Ha másik forrásból frissíti, a későbbiekben bármelyik forrásból kaphat frissítéseket a telefonján. Emiatt megváltozhat az app működése."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="7994800761970572198">"&lt;p&gt;Frissítse ezt az alkalmazást innen: &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Ez az alkalmazás általában a következő forrásból kap frissítéseket: &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Ha másik forrásból frissíti, a későbbiekben bármelyik forrásból kaphat frissítéseket a táblagépén. Emiatt megváltozhat az alkalmazás működése.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="2435174886412089791">"&lt;p&gt;Frissítse ezt az alkalmazást innen: &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Ez az alkalmazás általában a következő forrásból kap frissítéseket: &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Ha másik forrásból frissíti, a későbbiekben bármelyik forrásból kaphat frissítéseket a tévéjén. Emiatt megváltozhat az alkalmazás működése.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="7155138616126795839">"&lt;p&gt;Frissítse ezt az alkalmazást innen: &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Ez az alkalmazás általában a következő forrásból kap frissítéseket: &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Ha másik forrásból frissíti, a későbbiekben bármelyik forrásból kaphat frissítéseket a telefonján. Emiatt megváltozhat az alkalmazás működése.&lt;/p&gt;"</string>
     <string name="install_failed" msgid="5777824004474125469">"Az alkalmazás nincs telepítve."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"A csomag telepítését letiltotta a rendszer."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"A nem csomagként telepített alkalmazás ütközik egy már létező csomaggal."</string>
diff --git a/packages/PackageInstaller/res/values-hy/strings.xml b/packages/PackageInstaller/res/values-hy/strings.xml
index 4f56568..e5e7cec 100644
--- a/packages/PackageInstaller/res/values-hy/strings.xml
+++ b/packages/PackageInstaller/res/values-hy/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"Հավելվածը տեղադրված է:"</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Տեղադրե՞լ այս հավելվածը:"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Թարմացնե՞լ այս հավելվածը։"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Թարմացնե՞լ այս հավելվածը <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>-ից։\n\nՍովորաբար այս հավելվածի թարմացումները ստացվում են <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>-ից։ Եթե թարմացնեք այլ աղբյուրից, հետագայում կարող եք ձեր հեռախոսում թարմացումներ ստանալ ցանկացած աղբյուրից։ Հավելվածի գործառույթները կարող են փոփոխվել։"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="7994800761970572198">"&lt;p&gt;Թարմացրեք այս հավելվածը &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>-ից&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Հավելվածը սովորաբար թարմացումները ստանում է &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>-ից&lt;/b&gt;։ Եթե թարմացնեք այլ աղբյուրից, հետագայում կարող եք ձեր պլանշետում թարմացումներ ստանալ ցանկացած աղբյուրից։ Հավելվածի գործառույթները կարող են փոխվել։&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="2435174886412089791">"&lt;p&gt;Թարմացրեք այս հավելվածը &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>-ից&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Հավելվածը սովորաբար թարմացումները ստանում է &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>-ից&lt;/b&gt;։ Եթե թարմացնեք այլ աղբյուրից, հետագայում կարող եք ձեր հեռուստացույցում թարմացումներ ստանալ ցանկացած աղբյուրից։ Հավելվածի գործառույթները կարող են փոխվել։&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="7155138616126795839">"&lt;p&gt;Թարմացրեք այս հավելվածը &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>-ից&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Հավելվածը սովորաբար թարմացումները ստանում է &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>-ից&lt;/b&gt;։ Եթե թարմացնեք այլ աղբյուրից, հետագայում կարող եք ձեր հեռախոսում թարմացումներ ստանալ ցանկացած աղբյուրից։ Հավելվածի գործառույթները կարող են փոխվել։&lt;/p&gt;"</string>
     <string name="install_failed" msgid="5777824004474125469">"Հավելվածը տեղադրված չէ:"</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"Փաթեթի տեղադրումն արգելափակվել է:"</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"Հավելվածը չի տեղադրվել, քանի որ տեղադրման փաթեթն ունի հակասություն առկա փաթեթի հետ:"</string>
diff --git a/packages/PackageInstaller/res/values-in/strings.xml b/packages/PackageInstaller/res/values-in/strings.xml
index 0758a1d..a52b63c 100644
--- a/packages/PackageInstaller/res/values-in/strings.xml
+++ b/packages/PackageInstaller/res/values-in/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"Aplikasi terinstal."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Ingin menginstal aplikasi ini?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Ingin mengupdate aplikasi ini?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Update aplikasi ini dari <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nAplikasi ini biasanya menerima update dari <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Dengan mengupdate dari sumber yang berbeda, Anda mungkin menerima update berikutnya dari sumber mana pun di ponsel. Fungsi aplikasi mungkin berubah."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="7994800761970572198">"&lt;p&gt;Update aplikasi ini dari &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Aplikasi ini biasanya menerima update dari &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Dengan mengupdate dari sumber yang berbeda, Anda mungkin menerima update berikutnya dari sumber mana pun di tablet. Fungsi aplikasi mungkin berubah.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="2435174886412089791">"&lt;p&gt;Update aplikasi ini dari &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Aplikasi ini biasanya menerima update dari &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Dengan mengupdate dari sumber yang berbeda, Anda mungkin menerima update berikutnya dari sumber mana pun di TV. Fungsi aplikasi mungkin berubah.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="7155138616126795839">"&lt;p&gt;Update aplikasi ini dari &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Aplikasi ini biasanya menerima update dari &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Dengan mengupdate dari sumber yang berbeda, Anda mungkin menerima update berikutnya dari sumber mana pun di ponsel. Fungsi aplikasi mungkin berubah.&lt;/p&gt;"</string>
     <string name="install_failed" msgid="5777824004474125469">"Aplikasi tidak terinstal."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"Paket diblokir sehingga tidak dapat diinstal."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"Aplikasi tidak diinstal karena paket ini bentrok dengan paket yang sudah ada."</string>
diff --git a/packages/PackageInstaller/res/values-is/strings.xml b/packages/PackageInstaller/res/values-is/strings.xml
index 6cbb2ee..b125da1 100644
--- a/packages/PackageInstaller/res/values-is/strings.xml
+++ b/packages/PackageInstaller/res/values-is/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"Forritið er uppsett."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Viltu setja upp þetta forrit?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Viltu uppfæra þetta forrit?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Uppfæra þetta forrit frá <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nÞetta forrit fær venjulega uppfærslur frá <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Með því að uppfæra frá öðrum uppruna gætirðu fengið framtíðaruppfærslur frá hvaða uppruna sem er í símanum. Forritseiginleikar kunna að breytast."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="7994800761970572198">"&lt;p&gt;Uppfæra þetta forrit frá &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Þetta forrit fær yfirleitt uppfærslur frá &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Með því að uppfæra frá öðrum uppruna gætirðu fengið framtíðaruppfærslur frá hvaða uppruna sem er í spjaldtölvunni. Virkni forrits kann að breytast.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="2435174886412089791">"&lt;p&gt;Uppfæra þetta forrit frá &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Þetta forrit fær yfirleitt uppfærslur frá &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Með því að uppfæra frá öðrum uppruna gætirðu fengið framtíðaruppfærslur frá hvaða uppruna sem er í sjónvarpinu. Virkni forrits kann að breytast.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="7155138616126795839">"&lt;p&gt;Uppfæra þetta forrit frá &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Þetta forrit fær yfirleitt uppfærslur frá &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Með því að uppfæra frá öðrum uppruna gætirðu fengið framtíðaruppfærslur frá hvaða uppruna sem er í símanum. Virkni forrits kann að breytast.&lt;/p&gt;"</string>
     <string name="install_failed" msgid="5777824004474125469">"Forritið er ekki uppsett."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"Lokað var á uppsetningu pakkans."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"Forritið var ekki sett upp vegna árekstra á milli pakkans og annars pakka."</string>
diff --git a/packages/PackageInstaller/res/values-it/strings.xml b/packages/PackageInstaller/res/values-it/strings.xml
index 323db1d..03e3925 100644
--- a/packages/PackageInstaller/res/values-it/strings.xml
+++ b/packages/PackageInstaller/res/values-it/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"App installata."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Vuoi installare questa app?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Vuoi aggiornare questa app?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Vuoi aggiornare questa app tramite <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nGeneralmente l\'app viene aggiornata tramite <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Se la aggiorni da un\'origine diversa, in futuro potresti ricevere aggiornamenti da qualsiasi origine sul telefono. La funzionalità dell\'app potrebbe cambiare."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="7994800761970572198">"&lt;p&gt;Vuoi aggiornare questa app da &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Generalmente l\'app riceve gli aggiornamenti da &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Se la aggiorni da un\'origine diversa, in futuro potresti ricevere aggiornamenti da qualsiasi origine sul tablet. La funzionalità dell\'app potrebbe cambiare.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="2435174886412089791">"&lt;p&gt;Vuoi aggiornare questa app da &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Generalmente l\'app riceve gli aggiornamenti da &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Se la aggiorni da un\'origine diversa, in futuro potresti ricevere aggiornamenti da qualsiasi origine sulla TV. La funzionalità dell\'app potrebbe cambiare.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="7155138616126795839">"&lt;p&gt;Vuoi aggiornare questa app da &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Generalmente l\'app riceve gli aggiornamenti da &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Se la aggiorni da un\'origine diversa, in futuro potresti ricevere aggiornamenti da qualsiasi origine sullo smartphone. La funzionalità dell\'app potrebbe cambiare.&lt;/p&gt;"</string>
     <string name="install_failed" msgid="5777824004474125469">"App non installata."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"È stata bloccata l\'installazione del pacchetto."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"App non installata poiché il pacchetto è in conflitto con un pacchetto esistente."</string>
diff --git a/packages/PackageInstaller/res/values-iw/strings.xml b/packages/PackageInstaller/res/values-iw/strings.xml
index 24beba6..6684ec0 100644
--- a/packages/PackageInstaller/res/values-iw/strings.xml
+++ b/packages/PackageInstaller/res/values-iw/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"האפליקציה הותקנה."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"האם ברצונך להתקין אפליקציה זו?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"האם ברצונך לעדכן אפליקציה זו?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"לקבל את העדכון לאפליקציה הזו מ-<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nהאפליקציה הזו בדרך כלל מקבלת עדכונים מ: <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. אם בחרת לעדכן ממקור אחר, יכול להיות שבעתיד יתקבלו עדכונים ממקורות אחרים בטלפון. תכונות האפליקציה יכולות להשתנות."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="7994800761970572198">"‏&lt;p&gt;לקבל את העדכון לאפליקציה הזו מ-&lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;האפליקציה הזו בדרך כלל מקבלת עדכונים מ-&lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. אם בחרת לעדכן ממקור אחר, יכול להיות שבעתיד יתקבלו עדכונים ממקורות אחרים בטאבלט. תכונות האפליקציה עשויות להשתנות.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="2435174886412089791">"‏&lt;p&gt;לקבל את העדכון לאפליקציה הזו מ-&lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;האפליקציה הזו בדרך כלל מקבלת עדכונים מ-&lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. אם בחרת לעדכן ממקור אחר, יכול להיות שבעתיד יתקבלו עדכונים ממקורות אחרים בטלוויזיה. תכונות האפליקציה עשויות להשתנות.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="7155138616126795839">"‏&lt;p&gt;לקבל את העדכון לאפליקציה הזו מ-&lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;האפליקציה הזו בדרך כלל מקבלת עדכונים מ-&lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. אם בחרת לעדכן ממקור אחר, יכול להיות שבעתיד יתקבלו עדכונים ממקורות אחרים בטלפון. תכונות האפליקציה עשויות להשתנות.&lt;/p&gt;"</string>
     <string name="install_failed" msgid="5777824004474125469">"האפליקציה לא הותקנה."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"החבילה נחסמה להתקנה."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"האפליקציה לא הותקנה כי החבילה מתנגשת עם חבילה קיימת."</string>
diff --git a/packages/PackageInstaller/res/values-ja/strings.xml b/packages/PackageInstaller/res/values-ja/strings.xml
index 95b789e..600b6b7 100644
--- a/packages/PackageInstaller/res/values-ja/strings.xml
+++ b/packages/PackageInstaller/res/values-ja/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"アプリをインストールしました。"</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"このアプリをインストールしますか?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"このアプリを更新しますか?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"このアプリを <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> から更新しますか?\n\nこのアプリは通常、<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> からアップデートを受信しています。別の提供元から更新することにより、お使いのスマートフォンで今後のアップデートを任意の提供元から受け取ることになります。アプリの機能は変更される場合があります。"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="7994800761970572198">"&lt;p&gt;このアプリを &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt; から更新しますか?&lt;/p&gt;&lt;p&gt;このアプリは通常、&lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt; からアップデートを受信しています。別の提供元から更新することにより、お使いのタブレットで今後のアップデートを任意の提供元から受け取ることになります。アプリの機能は変更される場合があります。&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="2435174886412089791">"&lt;p&gt;このアプリを &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt; から更新しますか?&lt;/p&gt;&lt;p&gt;このアプリは通常、&lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt; からアップデートを受信しています。別の提供元から更新することにより、お使いのテレビで今後のアップデートを任意の提供元から受け取ることになります。アプリの機能は変更される場合があります。&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="7155138616126795839">"&lt;p&gt;このアプリを &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt; から更新しますか?&lt;/p&gt;&lt;p&gt;このアプリは通常、&lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt; からアップデートを受信しています。別の提供元から更新することにより、お使いのスマートフォンで今後のアップデートを任意の提供元から受け取ることになります。アプリの機能は変更される場合があります。&lt;/p&gt;"</string>
     <string name="install_failed" msgid="5777824004474125469">"アプリはインストールされていません。"</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"パッケージのインストールはブロックされています。"</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"パッケージが既存のパッケージと競合するため、アプリをインストールできませんでした。"</string>
diff --git a/packages/PackageInstaller/res/values-ka/strings.xml b/packages/PackageInstaller/res/values-ka/strings.xml
index 0699f0b..c8bf93b 100644
--- a/packages/PackageInstaller/res/values-ka/strings.xml
+++ b/packages/PackageInstaller/res/values-ka/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"აპი დაინსტალირებულია."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"გნებავთ ამ აპის დაყენება?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"გსურთ ამ აპის განახლება?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"გსურთ განაახლოთ ეს აპი <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>-ისგან?\n\nეს აპი, როგორც წესი, განახლებებს იღებს <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>-ისგან. აპის სხვა წყაროდან განახლებით შემდგომში განახლებების მიღებას შეძლებთ ნებისმიერი წყაროდან თქვენს ტელეფონზე. აპის ფუნქციები, შესაძლოა, შეიცვალოს."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="7994800761970572198">"&lt;p&gt;გსურთ განაახლოთ ეს აპი &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;-ისგან?&lt;/p&gt;&lt;p&gt;ეს აპი ჩვეულებრივ განახლებებს იღებს &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;-ისგან. სხვა წყაროდან განახლებით, შეგიძლიათ მიიღოთ მომავალი განახლებები ტაბლეტზე არსებული ნებისმიერი წყაროდან. აპის ფუნქციები შეიძლება შეიცვალოს.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="2435174886412089791">"&lt;p&gt;გსურთ განაახლოთ ეს აპი &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;-ისგან?&lt;/p&gt;&lt;p&gt;ეს აპი ჩვეულებრივ განახლებებს იღებს &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;-ისგან. სხვა წყაროდან განახლებით, შეგიძლიათ მიიღოთ მომავალი განახლებები ტელევიზორზე არსებული ნებისმიერი წყაროდან. აპის ფუნქციები შეიძლება შეიცვალოს.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="7155138616126795839">"&lt;p&gt;გსურთ განაახლოთ ეს აპი &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;-ისგან?&lt;/p&gt;&lt;p&gt;ეს აპი ჩვეულებრივ განახლებებს იღებს &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;-ისგან. აპის სხვა წყაროდან განახლებით შემდგომში განახლებების მიღებას შეძლებთ ნებისმიერი წყაროდან თქვენს ტელეფონზე. აპის ფუნქციები შეიძლება შეიცვალოს.&lt;/p&gt;"</string>
     <string name="install_failed" msgid="5777824004474125469">"აპი დაუინსტალირებელია."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"ამ პაკეტის ინსტალაცია დაბლოკილია."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"აპი ვერ დაინსტალირდა, რადგან პაკეტი კონფლიქტშია არსებულ პაკეტთან."</string>
diff --git a/packages/PackageInstaller/res/values-kk/strings.xml b/packages/PackageInstaller/res/values-kk/strings.xml
index 129267f..1ccac10 100644
--- a/packages/PackageInstaller/res/values-kk/strings.xml
+++ b/packages/PackageInstaller/res/values-kk/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"Қолданба орнатылды."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Бұл қолданбаны орнатқыңыз келе ме?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Бұл қолданбаны жаңартқыңыз келе ме?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Бұл қолданба <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> арқылы жаңартылсын ба?\n\nБұл қолданба әдетте <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> көмегімен жаңартылады. Басқа дереккөзден жаңартсаңыз, алдағы жаңартулар телефоныңыздағы кез келген дереккөзден келуі мүмкін. Қолданба функциялары өзгеруі мүмкін."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="7994800761970572198">"&lt;p&gt;Қолданба &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt; дереккөзінен жаңартылсын ба?&lt;/p&gt;&lt;p&gt;Бұл қолданба жаңартуды&lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt; дереккөзінен қалыпты түрде алады. Басқа дереккөзден жаңартсаңыз, планшетіңіздегі кез келген дереккөзден алдағы жаңартулар берілуі мүмкін. Қолданба функциялары өзгеруі мүмкін.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="2435174886412089791">"&lt;p&gt;Қолданба &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt; дереккөзінен жаңартылсын ба?&lt;/p&gt;&lt;p&gt;Бұл қолданба жаңартуды&lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt; дереккөзінен қалыпты түрде алады. Басқа дереккөзден жаңартсаңыз, теледидарыңыздағы кез келген дереккөзден алдағы жаңартулар берілуі мүмкін. Қолданба функциялары өзгеруі мүмкін.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="7155138616126795839">"&lt;p&gt;Қолданба &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt; дереккөзінен жаңартылсын ба?&lt;/p&gt;&lt;p&gt;Бұл қолданба жаңартуды&lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt; дереккөзінен қалыпты түрде алады. Басқа дереккөзден жаңартсаңыз, телефоныңыздағы кез келген дереккөзден алдағы жаңартулар берілуі мүмкін. Қолданба функциялары өзгеруі мүмкін.&lt;/p&gt;"</string>
     <string name="install_failed" msgid="5777824004474125469">"Қолданба орнатылмады."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"Пакетті орнатуға тыйым салынды."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"Жаңа пакет пен бұрыннан бар пакеттің арасында қайшылық туындағандықтан, қолданба орнатылмады."</string>
diff --git a/packages/PackageInstaller/res/values-km/strings.xml b/packages/PackageInstaller/res/values-km/strings.xml
index 04dc574..3e3ef99 100644
--- a/packages/PackageInstaller/res/values-km/strings.xml
+++ b/packages/PackageInstaller/res/values-km/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"បាន​ដំឡើង​កម្មវិធី។"</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"តើ​អ្នក​ចង់​ដំឡើង​កម្មវិធី​នេះ​ដែរទេ?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"តើអ្នកចង់ដំឡើងកំណែ​កម្មវិធីនេះដែរទេ?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"ដំឡើងកំណែកម្មវិធីនេះពី <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> ឬ?\n\nកម្មវិធីនេះជាធម្មតាទទួលបានកំណែថ្មីពី <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>។ តាមរយៈការដំឡើងកំណែពីប្រភពផ្សេង អ្នកអាចនឹងទទួលបានកំណែថ្មីនាពេលអនាគតពីប្រភពណាក៏បាននៅលើទូរសព្ទរបស់អ្នក។ មុខងារ​កម្មវិធីអាចមានការផ្លាស់ប្ដូរ។"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="7994800761970572198">"&lt;p&gt;ដំឡើងកំណែកម្មវិធីនេះពី &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt; ឬ?&lt;/p&gt;&lt;p&gt;ជាធម្មតា កម្មវិធីនេះទទួលបានកំណែថ្មីពី &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;។ តាមរយៈការដំឡើងកំណែពីប្រភពផ្សេង អ្នកអាចទទួលបានកំណែថ្មីៗនាពេលអនាគតពីប្រភពណាក៏បាននៅលើថេប្លេតរបស់អ្នក។ មុខងារ​កម្មវិធីអាចផ្លាស់ប្ដូរ។&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="2435174886412089791">"&lt;p&gt;ដំឡើងកំណែកម្មវិធីនេះពី &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt; ឬ?&lt;/p&gt;&lt;p&gt;ជាធម្មតា កម្មវិធីនេះទទួលបានកំណែថ្មីពី &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;។ តាមរយៈការដំឡើងកំណែពីប្រភពផ្សេង អ្នកអាចទទួលបានកំណែថ្មីៗនាពេលអនាគតពីប្រភពណាក៏បាននៅលើទូរទស្សន៍របស់អ្នក។ មុខងារ​កម្មវិធីអាចផ្លាស់ប្ដូរ។&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="7155138616126795839">"&lt;p&gt;ដំឡើងកំណែកម្មវិធីនេះពី &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt; ឬ?&lt;/p&gt;&lt;p&gt;ជាធម្មតា កម្មវិធីនេះទទួលបានកំណែថ្មីពី &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;។ តាមរយៈការដំឡើងកំណែពីប្រភពផ្សេង អ្នកអាចនឹងទទួលបានកំណែថ្មីនាពេលអនាគតពីប្រភពណាក៏បាននៅលើទូរសព្ទរបស់អ្នក។ មុខងារ​កម្មវិធីអាចផ្លាស់ប្ដូរ។&lt;/p&gt;"</string>
     <string name="install_failed" msgid="5777824004474125469">"មិន​បាន​ដំឡើង​កម្មវិធីទេ។"</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"កញ្ចប់ត្រូវបានទប់ស្កាត់​មិន​ឱ្យ​ដំឡើង។"</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"កម្មវិធីមិនបានដំឡើងទេ ដោយសារកញ្ចប់កម្មវិធីមិនត្រូវគ្នាជាមួយកញ្ចប់ដែលមានស្រាប់។"</string>
diff --git a/packages/PackageInstaller/res/values-kn/strings.xml b/packages/PackageInstaller/res/values-kn/strings.xml
index c9455ae..9cfe389 100644
--- a/packages/PackageInstaller/res/values-kn/strings.xml
+++ b/packages/PackageInstaller/res/values-kn/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"ಆ್ಯಪ್‌ ಅನ್ನು ಇನ್‌ಸ್ಟಾಲ್‌ ಮಾಡಲಾಗಿದೆ."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"ನೀವು ಈ ಆ್ಯಪ್‌ ಅನ್ನು ಇನ್‌ಸ್ಟಾಲ್‌ ಮಾಡಲು ಬಯಸುವಿರಾ?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"ನೀವು ಈ ಆ್ಯಪ್‌ ಅನ್ನು ಅಪ್‌ಡೇಟ್‌ ಮಾಡಲು ಬಯಸುವಿರಾ?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> ನಿಂದ ಈ ಆ್ಯಪ್ ಅನ್ನು ಅಪ್‌ಡೇಟ್‌ ಮಾಡಬೇಕೇ?\n\nಈ ಆ್ಯಪ್ ಸಾಮಾನ್ಯವಾಗಿ <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> ನಿಂದ ಅಪ್‌ಡೇಟ್‌ಗಳನ್ನು ಸ್ವೀಕರಿಸುತ್ತದೆ. ಬೇರೆ ಮೂಲವೊಂದರಿಂದ ಅಪ್‌ಡೇಟ್‌ ಮಾಡುವ ಮೂಲಕ, ನಿಮ್ಮ ಫೋನ್‌ನಲ್ಲಿರುವ ಯಾವುದೇ ಮೂಲದಿಂದ ಭವಿಷ್ಯದ ಅಪ್‌ಡೇಟ್‌ಗಳನ್ನು ನೀವು ಸ್ವೀಕರಿಸಬಹುದು. ಆ್ಯಪ್‌ನ ಕಾರ್ಯಚಟುವಟಿಕೆಯು ಬದಲಾಗಬಹುದು."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="7994800761970572198">"&lt;p&gt;ಈ ಆ್ಯಪ್ ಅನ್ನು &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt; ನಿಂದ ಅಪ್‌ಡೇಟ್ ಮಾಡಬೇಕೇ?&lt;/p&gt;&lt;p&gt;ಈ ಆ್ಯಪ್ ಸಾಮಾನ್ಯವಾಗಿ &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt; ನಿಂದ ಅಪ್‌ಡೇಟ್‌ಗಳನ್ನು ಸ್ವೀಕರಿಸುತ್ತದೆ. ಬೇರೆ ಮೂಲವೊಂದರಿಂದ ಅಪ್‌ಡೇಟ್‌ ಮಾಡುವ ಮೂಲಕ, ನಿಮ್ಮ ಟ್ಯಾಬ್ಲೆಟ್‌ನಲ್ಲಿರುವ ಯಾವುದೇ ಮೂಲದಿಂದ ನೀವು ಭವಿಷ್ಯದ ಅಪ್‌ಡೇಟ್‌ಗಳನ್ನು ಸ್ವೀಕರಿಸಬಹುದು. ಆ್ಯಪ್‌ನ ಕಾರ್ಯಚಟುವಟಿಕೆಯು ಬದಲಾಗಬಹುದು.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="2435174886412089791">"&lt;p&gt;ಈ ಆ್ಯಪ್ ಅನ್ನು &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt; ನಿಂದ ಅಪ್‌ಡೇಟ್ ಮಾಡಬೇಕೇ?&lt;/p&gt;&lt;p&gt;ಈ ಆ್ಯಪ್ ಸಾಮಾನ್ಯವಾಗಿ &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt; ನಿಂದ ಅಪ್‌ಡೇಟ್‌ಗಳನ್ನು ಸ್ವೀಕರಿಸುತ್ತದೆ. ಬೇರೆ ಮೂಲವೊಂದರಿಂದ ಅಪ್‌ಡೇಟ್‌ ಮಾಡುವ ಮೂಲಕ, ನಿಮ್ಮ TV ಯಲ್ಲಿರುವ ಯಾವುದೇ ಮೂಲದಿಂದ ನೀವು ಭವಿಷ್ಯದ ಅಪ್‌ಡೇಟ್‌ಗಳನ್ನು ಸ್ವೀಕರಿಸಬಹುದು. ಆ್ಯಪ್‌ನ ಕಾರ್ಯಚಟುವಟಿಕೆಯು ಬದಲಾಗಬಹುದು.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="7155138616126795839">"&lt;p&gt;ಈ ಆ್ಯಪ್ ಅನ್ನು &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt; ನಿಂದ ಅಪ್‌ಡೇಟ್ ಮಾಡಬೇಕೇ?&lt;/p&gt;&lt;p&gt;ಈ ಆ್ಯಪ್ ಸಾಮಾನ್ಯವಾಗಿ &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt; ನಿಂದ ಅಪ್‌ಡೇಟ್‌ಗಳನ್ನು ಸ್ವೀಕರಿಸುತ್ತದೆ. ಬೇರೆ ಮೂಲವೊಂದರಿಂದ ಅಪ್‌ಡೇಟ್‌ ಮಾಡುವ ಮೂಲಕ, ನಿಮ್ಮ ಫೋನ್‌ನಲ್ಲಿರುವ ಯಾವುದೇ ಮೂಲದಿಂದ ನೀವು ಭವಿಷ್ಯದ ಅಪ್‌ಡೇಟ್‌ಗಳನ್ನು ಸ್ವೀಕರಿಸಬಹುದು. ಆ್ಯಪ್‌ನ ಕಾರ್ಯಚಟುವಟಿಕೆಯು ಬದಲಾಗಬಹುದು.&lt;/p&gt;"</string>
     <string name="install_failed" msgid="5777824004474125469">"ಆ್ಯಪ್‌ ಇನ್‌ಸ್ಟಾಲ್‌ ಮಾಡಲಾಗಿಲ್ಲ."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"ಇನ್‌ಸ್ಟಾಲ್‌ ಮಾಡುವ ಪ್ಯಾಕೇಜ್‌ ಅನ್ನು ನಿರ್ಬಂಧಿಸಲಾಗಿದೆ."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"ಪ್ಯಾಕೇಜ್‌ನಂತೆ ಇನ್‌ಸ್ಟಾಲ್‌ ಮಾಡಲಾಗಿರುವ ಆ್ಯಪ್‌ ಅಸ್ತಿತ್ವದಲ್ಲಿರುವ ಪ್ಯಾಕೇಜ್ ಜೊತೆಗೆ ಸಂಘರ್ಷವಾಗುತ್ತದೆ."</string>
diff --git a/packages/PackageInstaller/res/values-ko/strings.xml b/packages/PackageInstaller/res/values-ko/strings.xml
index ef39eef..271ec90 100644
--- a/packages/PackageInstaller/res/values-ko/strings.xml
+++ b/packages/PackageInstaller/res/values-ko/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"앱이 설치되었습니다."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"이 앱을 설치하시겠습니까?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"이 앱을 업데이트하시겠습니까?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>에서 이 앱에 대한 업데이트를 받으시겠습니까?\n\n평소에는 <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>에서 앱을 업데이트했습니다. 다른 출처에서 앱을 업데이트하면 향후 휴대전화에 있는 어떤 출처에서든지 업데이트를 받을 수 있습니다. 앱 기능도 변경될 수 있습니다."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="7994800761970572198">"&lt;p&gt;&lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>에서&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;이 앱을 업데이트하세요. 이 앱은 보통&lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;에서 업데이트를 받습니다. 다른 출처에서 업데이트를 받으면 향후 태블릿에 있는 어떤 출처에서든지 업데이트를 받을 수 있습니다. 앱 기능이 변경될 수 있습니다.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="2435174886412089791">"&lt;p&gt;&lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>에서&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;이 앱을 업데이트하세요. 이 앱은 보통&lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;에서 업데이트를 받습니다. 다른 출처에서 앱을 업데이트하면 향후 TV에 있는 어떤 출처에서든지 업데이트를 받을 수 있습니다. 앱 기능이 변경될 수 있습니다.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="7155138616126795839">"&lt;p&gt;&lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>에서&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;이 앱을 업데이트하세요. 이 앱은 보통&lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;에서 업데이트를 받습니다. 다른 출처에서 업데이트를 받으면 향후 휴대전화에 있는 어떤 출처든지 업데이트를 받을 수 있습니다. 앱 기능이 변경될 수 있습니다.&lt;/p&gt;"</string>
     <string name="install_failed" msgid="5777824004474125469">"앱이 설치되지 않았습니다."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"패키지 설치가 차단되었습니다."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"패키지가 기존 패키지와 충돌하여 앱이 설치되지 않았습니다."</string>
diff --git a/packages/PackageInstaller/res/values-ky/strings.xml b/packages/PackageInstaller/res/values-ky/strings.xml
index 8b166c8..5e9948d 100644
--- a/packages/PackageInstaller/res/values-ky/strings.xml
+++ b/packages/PackageInstaller/res/values-ky/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"Колдонмо орнотулду."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Бул колдонмону орнотоюн деп жатасызбы?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Бул колдонмону жаңыртайын деп жатасызбы?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Колдонмону <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> аркылуу жаңыртасызбы?\n\nАдатта бул колдонмонун жаңыртууларын <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> жөнөтөт. Эгер колдонмону башка булактан жаңыртсаңыз, эртеңки күнү телефонуңуз ар кайсы булактан жаңырып, колдонмонун функциялары өзгөрүшү мүмкүн."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="7994800761970572198">"&lt;p&gt;Колдонмо &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt; аркылуу жаңыртылсынбы?&lt;/p&gt;&lt;p&gt;Адатта бул колдонмо &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt; жөнөткөн жаңыртууларды алат. Башка булактан жаңыртcаңыз, кийинки жаңыртуулар планшетиңиздеги ар кандай булактардан алынышы мүмкүн. Колдонмонун функциялары өзгөрүшү мүмкүн.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="2435174886412089791">"&lt;p&gt;Колдонмо &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt; аркылуу жаңыртылсынбы?&lt;/p&gt;&lt;p&gt;Адатта бул колдонмо &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt; жөнөткөн жаңыртууларды алат. Башка булактан жаңыртcаңыз, кийинки жаңыртуулар сыналгыңыздагы ар кандай булактардан алынышы мүмкүн. Колдонмонун функциялары өзгөрүшү мүмкүн.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="7155138616126795839">"&lt;p&gt;Колдонмо &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt; аркылуу жаңыртылсынбы?&lt;/p&gt;&lt;p&gt;Адатта бул колдонмо &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt; жөнөткөн жаңыртууларды алат. Башка булактан жаңыртcаңыз, кийинки жаңыртуулар телефонуңуздагы ар кандай булактардан алынышы мүмкүн. Колдонмонун функциялары өзгөрүшү мүмкүн.&lt;/p&gt;"</string>
     <string name="install_failed" msgid="5777824004474125469">"Колдонмо орнотулган жок."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"Топтомду орнотууга болбойт."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"Башка топтом менен дал келбегендиктен колдонмо орнотулган жок."</string>
diff --git a/packages/PackageInstaller/res/values-lo/strings.xml b/packages/PackageInstaller/res/values-lo/strings.xml
index f3912cc..d12bb4b 100644
--- a/packages/PackageInstaller/res/values-lo/strings.xml
+++ b/packages/PackageInstaller/res/values-lo/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"ຕິດຕັ້ງແອັບແລ້ວ."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"ທ່ານຕ້ອງການຕິດຕັ້ງແອັບນີ້ບໍ່?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"ທ່ານຕ້ອງການອັບເດດແອັບນີ້ບໍ່?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"ອັບເດດແອັບນີ້ຈາກ <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> ບໍ?\n\nໂດຍທົ່ວໄປແລ້ວແອັບນີ້ຈະໄດ້ຮັບການອັບເດດຈາກ <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. ການອັບເດດຈາກແຫຼ່ງທີ່ມາອື່ນອາດເຮັດໃຫ້ໂທລະສັບຂອງທ່ານໄດ້ຮັບການອັບເດດຈາກແຫຼ່ງທີ່ມານັ້ນໃນອະນາຄົດ. ຟັງຊັນການເຮັດວຽກຂອງແອັບອາດມີການປ່ຽນແປງ."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="7994800761970572198">"&lt;p&gt;ອັບເດດແອັບນີ້ຈາກ &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;ໂດຍປົກກະຕິແລ້ວແອັບນີ້ຈະໄດ້ຮັບການອັບເດດຈາກ &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. ການອັບເດດຈາກແຫຼ່ງທີ່ມາອື່ນອາດເຮັດໃຫ້ແທັບເລັດຂອງທ່ານໄດ້ຮັບການອັບເດດຈາກແຫຼ່ງທີ່ມານັ້ນໃນອະນາຄົດ. ເຊິ່ງອາດເຮັດໃຫ້ຟັງຊັນການນຳໃຊ້ແອັບປ່ຽນແປງ.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="2435174886412089791">"&lt;p&gt;ອັບເດດແອັບນີ້ຈາກ &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;ໂດຍປົກກະຕິແລ້ວແອັບນີ້ຈະໄດ້ຮັບການອັບເດດຈາກ &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. ການອັບເດດຈາກແຫຼ່ງທີ່ມາອື່ນອາດເຮັດໃຫ້ໂທລະທັດຂອງທ່ານໄດ້ຮັບການອັບເດດຈາກແຫຼ່ງທີ່ມານັ້ນໃນອະນາຄົດ. ເຊິ່ງອາດເຮັດໃຫ້ຟັງຊັນການນຳໃຊ້ແອັບປ່ຽນແປງ.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="7155138616126795839">"&lt;p&gt;ອັບເດດແອັບນີ້ຈາກ &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;ໂດຍປົກກະຕິແລ້ວແອັບນີ້ຈະໄດ້ຮັບການອັບເດດຈາກ &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. ການອັບເດດຈາກແຫຼ່ງທີ່ມາອື່ນອາດເຮັດໃຫ້ໂທລະສັບຂອງທ່ານໄດ້ຮັບການອັບເດດຈາກແຫຼ່ງທີ່ມານັ້ນໃນອະນາຄົດ. ເຊິ່ງອາດເຮັດໃຫ້ຟັງຊັນການນຳໃຊ້ແອັບປ່ຽນແປງ.&lt;/p&gt;"</string>
     <string name="install_failed" msgid="5777824004474125469">"ບໍ່ໄດ້ຕິດຕັ້ງແອັບເທື່ອ."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"ແພັກ​ເກດ​ຖືກບ​ລັອກ​ບໍ່​ໃຫ້​ໄດ້​ຮັບ​ການ​ຕິດ​ຕັ້ງ."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"ບໍ່ໄດ້ຕິດຕັ້ງແອັບເນື່ອງຈາກແພັກເກດຂັດແຍ່ງກັບແພັກເກດທີ່ມີຢູ່ກ່ອນແລ້ວ."</string>
diff --git a/packages/PackageInstaller/res/values-lt/strings.xml b/packages/PackageInstaller/res/values-lt/strings.xml
index a7ec560e..91ae9d1 100644
--- a/packages/PackageInstaller/res/values-lt/strings.xml
+++ b/packages/PackageInstaller/res/values-lt/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"Programa įdiegta."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Ar norite įdiegti šią programą?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Ar norite atnaujinti šią programą?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Atnaujinti šią programą iš <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nŠi programa įprastai gauna naujinius iš <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Atnaujinę iš kito šaltinio, būsimus naujinius galite gauti iš bet kurio šaltinio telefone. Gali būti pakeistos programos funkcijos."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="7994800761970572198">"&lt;p&gt;Šią programą atnaujinti iš &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Ši programa paprastai naujinius gauna iš &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Atnaujinę iš kito šaltinio, būsimus naujinius galite gauti iš bet kurio šaltinio planšetiniame kompiuteryje. Programos funkcijos gali pasikeisti.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="2435174886412089791">"&lt;p&gt;Šią programą atnaujinti iš &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Ši programa paprastai naujinius gauna iš &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Atnaujinę iš kito šaltinio, būsimus naujinius galite gauti iš bet kurio šaltinio televizoriuje. Programos funkcijos gali pasikeisti.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="7155138616126795839">"&lt;p&gt;Šią programą atnaujinti iš &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Ši programa paprastai naujinius gauna iš &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Atnaujinę iš kito šaltinio, būsimus naujinius galite gauti iš bet kurio šaltinio telefone. Programos funkcijos gali pasikeisti.&lt;/p&gt;"</string>
     <string name="install_failed" msgid="5777824004474125469">"Programa neįdiegta."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"Paketas užblokuotas ir negali būti įdiegtas."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"Programa neįdiegta, nes paketas nesuderinamas su esamu paketu."</string>
diff --git a/packages/PackageInstaller/res/values-lv/strings.xml b/packages/PackageInstaller/res/values-lv/strings.xml
index 17dd542..c6f9eb4 100644
--- a/packages/PackageInstaller/res/values-lv/strings.xml
+++ b/packages/PackageInstaller/res/values-lv/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"Lietotne ir instalēta."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Vai vēlaties instalēt šo lietotni?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Vai vēlaties atjaunināt šo lietotni?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Vai atjaunināt šo lietotni, izmantojot “<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>”?\n\nŠī lietotne parasti saņem atjauninājumus no “<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>”. Veicot atjaunināšanu no cita avota, iespējams, turpmāk tālrunī saņemsiet atjauninājumus no jebkāda avota. Lietotnes funkcionalitāte var mainīties."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="7994800761970572198">"&lt;p&gt;Vai atjaunināt šo lietotni, izmantojot &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Šī lietotne parasti saņem atjauninājumus no &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Veicot atjaunināšanu no cita avota, iespējams, turpmāk planšetdatorā saņemsiet atjauninājumus no jebkāda avota. Lietotnes funkcionalitāte var mainīties.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="2435174886412089791">"&lt;p&gt;Vai atjaunināt šo lietotni, izmantojot &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Šī lietotne parasti saņem atjauninājumus no &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Veicot atjaunināšanu no cita avota, iespējams, turpmāk televizorā saņemsiet atjauninājumus no jebkāda avota. Lietotnes funkcionalitāte var mainīties.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="7155138616126795839">"&lt;p&gt;Vai atjaunināt šo lietotni, izmantojot &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Šī lietotne parasti saņem atjauninājumus no &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Veicot atjaunināšanu no cita avota, iespējams, turpmāk tālrunī saņemsiet atjauninājumus no jebkāda avota. Lietotnes funkcionalitāte var mainīties.&lt;/p&gt;"</string>
     <string name="install_failed" msgid="5777824004474125469">"Lietotne nav instalēta."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"Pakotnes instalēšana tika bloķēta."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"Lietotne netika instalēta, jo pastāv pakotnes konflikts ar esošu pakotni."</string>
diff --git a/packages/PackageInstaller/res/values-mk/strings.xml b/packages/PackageInstaller/res/values-mk/strings.xml
index 5aaea17..bc5cb90 100644
--- a/packages/PackageInstaller/res/values-mk/strings.xml
+++ b/packages/PackageInstaller/res/values-mk/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"Апликацијата е инсталирана."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Дали сакате да ја инсталирате апликацијава?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Дали сакате да ја ажурирате апликацијава?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Да се ажурира апликацијава од <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nАпликацијава вообичаено добива ажурирања од<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Со ажурирање од различен извор, може да добивате идни ажурирања од кој било извор на вашиот телефон. Функционалноста на апликацијата може да се промени."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="7994800761970572198">"&lt;p&gt;Ажурирајте ја апликацијава од &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Апликацијава вообичаено добива ажурирања од &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Со ажурирање од различен извор, може да добивате идни ажурирања од кој било извор на вашиот таблет. Може да дојде до промени во функционалноста на апликацијата.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="2435174886412089791">"&lt;p&gt;Ажурирајте ја апликацијава од &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Апликацијава вообичаено добива ажурирања од &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Со ажурирање од различен извор, може да добивате идни ажурирања од кој било извор на вашиот телевизор. Може да дојде до промени во функционалноста на апликацијата.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="7155138616126795839">"&lt;p&gt;Ажурирајте ја апликацијава од &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Апликацијава вообичаено добива ажурирања од &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Со ажурирање од различен извор, може да добивате идни ажурирања од кој било извор на вашиот телефон. Може да дојде до промени во функционалноста на апликацијата.&lt;/p&gt;"</string>
     <string name="install_failed" msgid="5777824004474125469">"Апликацијата не е инсталирана."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"Инсталирањето на пакетот е блокирано."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"Апликација што не е инсталирана како пакет е во конфликт со постоечки пакет."</string>
diff --git a/packages/PackageInstaller/res/values-ml/strings.xml b/packages/PackageInstaller/res/values-ml/strings.xml
index 0535843..a64f87f 100644
--- a/packages/PackageInstaller/res/values-ml/strings.xml
+++ b/packages/PackageInstaller/res/values-ml/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"ആപ്പ് ഇൻസ്‌റ്റാൾ ചെയ്‌തു."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"ഈ ആപ്പ് ഇൻസ്‌റ്റാൾ ചെയ്യണോ?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"ഈ ആപ്പ് അപ്‌ഡേറ്റ് ചെയ്യണോ?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> എന്നതിൽ നിന്ന് ഈ ആപ്പ് അപ്‌ഡേറ്റ് ചെയ്യണോ?\n\nഈ ആപ്പിന് സാധാരണയായി <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> എന്നതിൽ നിന്ന് അപ്‌ഡേറ്റുകൾ ലഭിക്കാറുണ്ട്. മറ്റൊരു ഉറവിടത്തിൽ നിന്ന് അപ്‌ഡേറ്റ് ചെയ്യുന്നത് വഴി, നിങ്ങളുടെ ഫോണിലെ ഏത് ഉറവിടത്തിൽ നിന്നും ഭാവിയിൽ അപ്‌ഡേറ്റുകൾ ലഭിക്കാൻ ഇടയുണ്ട്. ആപ്പ് ഫംഗ്ഷണാലിറ്റിയിൽ വ്യത്യാസം വന്നേക്കാം."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="7994800761970572198">"&lt;p&gt; &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt; എന്നതിൽ നിന്ന് ഈ ആപ്പ് അപ്ഡേറ്റ് ചെയ്യണോ?&lt;/p&gt;&lt;p&gt;ഈ ആപ്പിന് സാധാരണയായി &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt; എന്നതിൽ നിന്ന് അപ്‌ഡേറ്റുകൾ ലഭിക്കാറുണ്ട്. മറ്റൊരു ഉറവിടത്തിൽ നിന്ന് അപ്‌ഡേറ്റ് ചെയ്യുന്നതിലൂടെ, നിങ്ങളുടെ ടാബ്‌ലെറ്റിലെ ഏത് ഉറവിടത്തിൽ നിന്നും ഭാവിയിൽ അപ്‌ഡേറ്റുകൾ ലഭിച്ചേക്കാം. ആപ്പ് ഫംഗ്ഷണാലിറ്റി മാറിയേക്കാം.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="2435174886412089791">"&lt;p&gt; &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt; എന്നതിൽ നിന്ന് ഈ ആപ്പ് അപ്ഡേറ്റ് ചെയ്യണോ?&lt;/p&gt;&lt;p&gt;ഈ ആപ്പിന് സാധാരണയായി &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt; എന്നതിൽ നിന്ന് അപ്‌ഡേറ്റുകൾ ലഭിക്കാറുണ്ട്. മറ്റൊരു ഉറവിടത്തിൽ നിന്ന് അപ്‌ഡേറ്റ് ചെയ്യുന്നതിലൂടെ, നിങ്ങളുടെ ടിവിയിലെ ഏത് ഉറവിടത്തിൽ നിന്നും ഭാവിയിൽ അപ്‌ഡേറ്റുകൾ ലഭിച്ചേക്കാം. ആപ്പ് ഫംഗ്ഷണാലിറ്റി മാറിയേക്കാം.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="7155138616126795839">"&lt;p&gt; &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt; എന്നതിൽ നിന്ന് ഈ ആപ്പ് അപ്ഡേറ്റ് ചെയ്യണോ?&lt;/p&gt;&lt;p&gt;ഈ ആപ്പിന് സാധാരണയായി &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt; എന്നതിൽ നിന്ന് അപ്‌ഡേറ്റുകൾ ലഭിക്കാറുണ്ട്. മറ്റൊരു ഉറവിടത്തിൽ നിന്ന് അപ്‌ഡേറ്റ് ചെയ്യുന്നത് വഴി, നിങ്ങളുടെ ഫോണിലെ ഏത് ഉറവിടത്തിൽ നിന്നും ഭാവിയിൽ അപ്‌ഡേറ്റുകൾ ലഭിക്കാൻ ഇടയുണ്ട്. ആപ്പ് ഫംഗ്ഷണാലിറ്റി മാറിയേക്കാം.&lt;/p&gt;"</string>
     <string name="install_failed" msgid="5777824004474125469">"ആപ്പ് ഇൻസ്‌റ്റാൾ ചെയ്‌തിട്ടില്ല."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"പാക്കേജ് ഇൻസ്‌റ്റാൾ ചെയ്യുന്നത് ബ്ലോക്ക് ചെയ്‌തു."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"പാക്കേജിന് നിലവിലുള്ള പാക്കേജുമായി പൊരുത്തക്കേടുള്ളതിനാൽ, ആപ്പ് ഇൻസ്‌റ്റാൾ ചെയ്‌തില്ല."</string>
diff --git a/packages/PackageInstaller/res/values-mn/strings.xml b/packages/PackageInstaller/res/values-mn/strings.xml
index 84a3909..a9365b6 100644
--- a/packages/PackageInstaller/res/values-mn/strings.xml
+++ b/packages/PackageInstaller/res/values-mn/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"Аппыг суулгасан."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Та энэ аппыг суулгахыг хүсэж байна уу?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Та энэ аппыг шинэчлэхийг хүсэж байна уу?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Аппыг <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>-с шинэчлэх үү?\n\nЭнэ апп ихэвчлэн <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>-с шинэчлэлт хүлээн авдаг. Өөр эх сурвалжаас шинэчилснээр та ирээдүйн шинэчлэлтийг утсан дээрх аливаа эх сурвалжаас хүлээн авч магадгүй. Аппын ажиллагаа өөрчлөгдөж магадгүй."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="7994800761970572198">"&lt;p&gt;Энэ аппыг &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>-с шинэчлэх үү&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Энэ апп ихэвчлэн &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>-с шинэчлэлт хүлээн авдаг&lt;/b&gt;. Өөр эх сурвалжаас шинэчилснээр та ирээдүйн шинэчлэлтийг таблет дээрх аливаа эх сурвалжаас хүлээн авч магадгүй. Аппын ажиллагаа өөрчлөгдөж магадгүй.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="2435174886412089791">"&lt;p&gt;Энэ аппыг &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>-с шинэчлэх үү&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Энэ апп ихэвчлэн &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>-с шинэчлэлт хүлээн авдаг&lt;/b&gt;. Өөр эх сурвалжаас шинэчилснээр та ирээдүйн шинэчлэлтийг ТВ дээрх аливаа эх сурвалжаас хүлээн авч магадгүй. Аппын ажиллагаа өөрчлөгдөж магадгүй.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="7155138616126795839">"&lt;p&gt;Энэ аппыг &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>-с шинэчлэх үү&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Энэ апп ихэвчлэн &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>-с шинэчлэлт хүлээн авдаг&lt;/b&gt;. Өөр эх сурвалжаас шинэчилснээр та ирээдүйн шинэчлэлтийг утсан дээрх аливаа эх сурвалжаас хүлээн авч магадгүй. Аппын ажиллагаа өөрчлөгдөж магадгүй.&lt;/p&gt;"</string>
     <string name="install_failed" msgid="5777824004474125469">"Аппыг суулгаагүй."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"Багц суулгахыг блоклосон байна."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"Багц одоо байгаа багцтай тохирохгүй байгаа тул аппыг суулгаж чадсангүй."</string>
diff --git a/packages/PackageInstaller/res/values-mr/strings.xml b/packages/PackageInstaller/res/values-mr/strings.xml
index 367dede..d6f04b4 100644
--- a/packages/PackageInstaller/res/values-mr/strings.xml
+++ b/packages/PackageInstaller/res/values-mr/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"अ‍ॅप इंस्टॉल झाले."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"तुम्हाला हे ॲप इंस्टॉल करायचे आहे का?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"तुम्हाला हे ॲप अपडेट करायचे आहे का?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> वरून हे अ‍ॅप अपडेट करायचे आहे का?\n\nया अ‍ॅपला सामान्यतः <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> कडून अपडेट मिळतात. वेगवेगळ्या स्रोताकडून अपडेट करून, तुम्हाला तुमच्या फोनवरील कोणत्याही स्रोताकडून भविष्यातील अपडेट मिळू शकतात. अ‍ॅपची कार्यक्षमता बदलू शकते."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="7994800761970572198">"&lt;p&gt;हे अ‍ॅप&lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;कडून अपडेट करायचे आहे का?&lt;/p&gt;&lt;p&gt;हे अ‍ॅप सामान्यपणे&lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt; कडून अपडेट मिळवते. वेगवेगळ्या स्रोताकडून अपडेट करून, तुम्हाला तुमच्या टॅबलेटवरील कोणत्याही स्रोताकडून भविष्यातील अपडेट मिळू शकतात. अ‍ॅपची कार्यक्षमता बदलू शकते.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="2435174886412089791">"&lt;p&gt;हे अ‍ॅप&lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;कडून अपडेट करायचे आहे का?&lt;/p&gt;&lt;p&gt;हे अ‍ॅप सामान्यपणे&lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt; कडून अपडेट मिळवते. वेगवेगळ्या स्रोताकडून अपडेट करून, तुम्हाला तुमच्या टीव्हीवरील कोणत्याही स्रोताकडून भविष्यातील अपडेट मिळू शकतात. अ‍ॅपची कार्यक्षमता बदलू शकते.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="7155138616126795839">"&lt;p&gt;हे अ‍ॅप&lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;कडून अपडेट करायचे आहे का?&lt;/p&gt;&lt;p&gt;हे अ‍ॅप सामान्यपणे&lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt; कडून अपडेट मिळवते. वेगवेगळ्या स्रोताकडून अपडेट करून, तुम्हाला तुमच्या फोनवरील कोणत्याही स्रोताकडून भविष्यातील अपडेट मिळू शकतात. अ‍ॅपची कार्यक्षमता बदलू शकते.&lt;/p&gt;"</string>
     <string name="install_failed" msgid="5777824004474125469">"अ‍ॅप इंस्टॉल झाले नाही."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"पॅकेज इंस्टॉल होण्यापासून ब्लॉक केले होते."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"पॅकेजचा विद्यमान पॅकेजशी विरोध असल्याने अ‍ॅप इंस्टॉल झाले नाही."</string>
diff --git a/packages/PackageInstaller/res/values-ms/strings.xml b/packages/PackageInstaller/res/values-ms/strings.xml
index 6ab6622..80f4be3 100644
--- a/packages/PackageInstaller/res/values-ms/strings.xml
+++ b/packages/PackageInstaller/res/values-ms/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"Aplikasi dipasang."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Adakah anda ingin memasang aplikasi ini?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Adakah anda mahu mengemas kini apl ini?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Kemas kinikan apl ini daripada <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nApl ini biasanya menerima kemaskinian daripada <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Dengan membuat kemaskinian daripada sumber yang berbeza, anda mungkin menerima kemaskinian masa hadapan daripada sebarang sumber pada telefon anda. Fungsi apl mungkin berubah."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="7994800761970572198">"&lt;p&gt;Kemas kinikan apl ini daripada &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Apl ini biasanya menerima kemaskinian daripada &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Dengan membuat kemaskinian daripada sumber yang berbeza, anda mungkin menerima kemaskinian masa hadapan daripada sebarang sumber pada tablet anda. Fungsi apl mungkin berubah.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="2435174886412089791">"&lt;p&gt;Kemas kinikan apl ini daripada &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Apl ini biasanya menerima kemaskinian daripada &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Dengan membuat kemaskinian daripada sumber yang berbeza, anda mungkin menerima kemaskinian masa hadapan daripada sebarang sumber pada TV anda. Fungsi apl mungkin berubah.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="7155138616126795839">"&lt;p&gt;Kemas kinikan apl ini daripada &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Apl ini biasanya menerima kemaskinian daripada &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Dengan membuat kemaskinian daripada sumber yang berbeza, anda mungkin menerima kemaskinian masa hadapan daripada sebarang sumber pada telefon anda. Fungsi apl mungkin berubah.&lt;/p&gt;"</string>
     <string name="install_failed" msgid="5777824004474125469">"Aplikasi tidak dipasang."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"Pakej ini telah disekat daripada dipasang."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"Apl tidak dipasang kerana pakej bercanggah dengan pakej yang sedia ada."</string>
diff --git a/packages/PackageInstaller/res/values-my/strings.xml b/packages/PackageInstaller/res/values-my/strings.xml
index cb05b06..77a45899 100644
--- a/packages/PackageInstaller/res/values-my/strings.xml
+++ b/packages/PackageInstaller/res/values-my/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"အက်ပ်ထည့်သွင်းပြီးပါပြီ။"</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"ဤအက်ပ်ကို ထည့်သွင်းလိုသလား။"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"ဤအက်ပ်ကို အပ်ဒိတ်လုပ်လိုသလား။"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"ဤအက်ပ်ကို <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> မှ အပ်ဒိတ်လုပ်မလား။\n\nဤအက်ပ်သည် ပုံမှန်အားဖြင့် <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> မှ အပ်ဒိတ်များ ရရှိသည်။ မတူညီသောရင်းမြစ်မှ အပ်ဒိတ်လုပ်ခြင်းဖြင့် ဖုန်းပေါ်တွင် နောင်လာမည့်အပ်ဒိတ်များကို မည်သည့်ရင်းမြစ်မှမဆို လက်ခံရယူနိုင်သည်။ အက်ပ်လုပ်ဆောင်ချက် ပြောင်းလဲနိုင်သည်။"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="7994800761970572198">"&lt;p&gt;ဤအက်ပ်ကို &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt; မှ အပ်ဒိတ်လုပ်မလား။&lt;/p&gt;&lt;p&gt;ဤအက်ပ်သည် ပုံမှန်အားဖြင့် &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt; မှ အပ်ဒိတ်များ ရရှိသည်။ မတူညီသောရင်းမြစ်မှ အပ်ဒိတ်လုပ်ခြင်းဖြင့် တက်ဘလက်ပေါ်တွင် နောင်လာမည့်အပ်ဒိတ်များကို မည်သည့်ရင်းမြစ်မဆိုမှ လက်ခံရယူနိုင်သည်။ အက်ပ်လုပ်ဆောင်ချက် ပြောင်းနိုင်သည်။&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="2435174886412089791">"&lt;p&gt;ဤအက်ပ်ကို &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt; မှ အပ်ဒိတ်လုပ်မလား။&lt;/p&gt;&lt;p&gt;ဤအက်ပ်သည် ပုံမှန်အားဖြင့် &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt; မှ အပ်ဒိတ်များ ရရှိသည်။ မတူညီသောရင်းမြစ်မှ အပ်ဒိတ်လုပ်ခြင်းဖြင့် TV ပေါ်တွင် နောင်လာမည့်အပ်ဒိတ်များကို မည်သည့်ရင်းမြစ်မဆိုမှ လက်ခံရယူနိုင်သည်။ အက်ပ်လုပ်ဆောင်ချက် ပြောင်းနိုင်သည်။&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="7155138616126795839">"&lt;p&gt;ဤအက်ပ်ကို &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt; မှ အပ်ဒိတ်လုပ်မလား။&lt;/p&gt;&lt;p&gt;ဤအက်ပ်သည် ပုံမှန်အားဖြင့် &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt; မှ အပ်ဒိတ်များ ရရှိသည်။ မတူညီသောရင်းမြစ်မှ အပ်ဒိတ်လုပ်ခြင်းဖြင့် ဖုန်းပေါ်တွင် နောင်လာမည့်အပ်ဒိတ်များကို မည်သည့်ရင်းမြစ်မဆိုမှ လက်ခံရယူနိုင်သည်။ အက်ပ်လုပ်ဆောင်ချက် ပြောင်းနိုင်သည်။&lt;/p&gt;"</string>
     <string name="install_failed" msgid="5777824004474125469">"အက်ပ်မထည့်သွင်းရသေးပါ"</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"ပက်ကေ့ဂျ်ထည့်သွင်းခြင်းကို ပိတ်ထားသည်။"</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"ပက်ကေ့ဂျ်အဖြစ် ထည့်သွင်းမထားသော အက်ပ်သည် လက်ရှိပက်ကေ့ဂျ်နှင့် တိုက်နေသည်။"</string>
diff --git a/packages/PackageInstaller/res/values-nb/strings.xml b/packages/PackageInstaller/res/values-nb/strings.xml
index 7532d7b..89d9762 100644
--- a/packages/PackageInstaller/res/values-nb/strings.xml
+++ b/packages/PackageInstaller/res/values-nb/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"Appen er installert."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Vil du installere denne appen?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Vil du oppdatere denne appen?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Vil du oppdatere denne appen fra <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nDenne appen mottar vanligvis oppdateringer fra <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Hvis du oppdaterer fra en annen kilde, kan du få fremtidige oppdateringer fra en hvilken som helst kilde på telefonen. Appfunksjonaliteten kan endres."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="7994800761970572198">"&lt;p&gt;Vil du oppdatere denne appen fra &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Denne appen mottar vanligvis oppdateringer fra &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Hvis du oppdaterer fra en annen kilde, kan du få fremtidige oppdateringer fra en hvilken som helst kilde på nettbrettet. Appfunksjonaliteten kan endres.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="2435174886412089791">"&lt;p&gt;Vil du oppdatere denne appen fra &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Denne appen mottar vanligvis oppdateringer fra &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Hvis du oppdaterer fra en annen kilde, kan du få fremtidige oppdateringer fra en hvilken som helst kilde på TV-en. Appfunksjonaliteten kan endres.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="7155138616126795839">"&lt;p&gt;Vil du oppdatere denne appen fra &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Denne appen mottar vanligvis oppdateringer fra &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Hvis du oppdaterer fra en annen kilde, kan du få fremtidige oppdateringer fra en hvilken som helst kilde på telefonen. Appfunksjonaliteten kan endres.&lt;/p&gt;"</string>
     <string name="install_failed" msgid="5777824004474125469">"Appen ble ikke installert."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"Pakken er blokkert fra å bli installert."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"Appen ble ikke installert fordi pakken er i konflikt med en eksisterende pakke."</string>
diff --git a/packages/PackageInstaller/res/values-ne/strings.xml b/packages/PackageInstaller/res/values-ne/strings.xml
index 0d7068e..23a689f 100644
--- a/packages/PackageInstaller/res/values-ne/strings.xml
+++ b/packages/PackageInstaller/res/values-ne/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"एप इन्स्टल गरियो।"</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"तपाईं यो एप इन्स्टल गर्न चाहनुहुन्छ?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"तपाईं यो एप अपडेट गर्न चाहनुहुन्छ?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"यो एप <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> बाट अपडेट गर्ने हो?\n\nयो एपले सामान्यतया <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> बाट अपडेट प्राप्त गर्छ। तपाईंले कुनै फरक स्रोतबाट अपडेट गर्नुभयो भने तपाईं भविष्यमा आफ्नो फोनमा भएको जुनसुकै स्रोतबाट अपडेटहरू प्राप्त गर्न सक्नुहुन्छ। यसो गर्दा एपका मुख्य सुविधाहरूले काम गर्ने तरिका परिवर्तन हुन सक्छ।"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="7994800761970572198">"&lt;p&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> &lt;b&gt;बाट यो एप अपडेट गर्ने हो&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;यो एपले सामान्यतया &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> बाट अपडेटहरू प्राप्त गर्छ&lt;/b&gt;। तपाईंले कुनै फरक स्रोतबाट अपडेट गर्नुभयो भने तपाईं भविष्यमा आफ्नो ट्याब्लेटमा भएको जुनसुकै स्रोतबाट अपडेटहरू प्राप्त गर्न सक्नुहुन्छ। एपका मुख्य सुविधाहरू परिवर्तन हुन सक्छन्।&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="2435174886412089791">"&lt;p&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> &lt;b&gt;बाट यो एप अपडेट गर्ने हो&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;यो एपले सामान्यतया &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> बाट अपडेटहरू प्राप्त गर्छ&lt;/b&gt;। तपाईंले कुनै फरक स्रोतबाट अपडेट गर्नुभयो भने तपाईं भविष्यमा आफ्नो टिभीमा भएको जुनसुकै स्रोतबाट अपडेटहरू प्राप्त गर्न सक्नुहुन्छ। एपका मुख्य सुविधाहरू परिवर्तन हुन सक्छन्।&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="7155138616126795839">"&lt;p&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> &lt;b&gt;बाट यो एप अपडेट गर्ने हो&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;यो एपले सामान्यतया &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> बाट अपडेटहरू प्राप्त गर्छ&lt;/b&gt;। तपाईंले कुनै फरक स्रोतबाट अपडेट गर्नुभयो भने तपाईं भविष्यमा आफ्नो फोनमा भएको जुनसुकै स्रोतबाट अपडेटहरू प्राप्त गर्न सक्नुहुन्छ। एपका मुख्य सुविधाहरू परिवर्तन हुन सक्छन्।&lt;/p&gt;"</string>
     <string name="install_failed" msgid="5777824004474125469">"एप स्थापना गरिएन।"</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"यो प्याकेज स्थापना गर्ने क्रममा अवरोध गरियो।"</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"प्याकेजका रूपमा स्थापना नगरिएको एप विद्यमान प्याकेजसँग मेल खाँदैन।"</string>
diff --git a/packages/PackageInstaller/res/values-nl/strings.xml b/packages/PackageInstaller/res/values-nl/strings.xml
index fab6d51..731dc48 100644
--- a/packages/PackageInstaller/res/values-nl/strings.xml
+++ b/packages/PackageInstaller/res/values-nl/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"App geïnstalleerd."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Wil je deze app installeren?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Wil je deze app updaten?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Deze app updaten via <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nDeze app krijgt gewoonlijk updates via <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Als je updatet via een andere bron, kun je toekomstige updates via elke bron op je telefoon krijgen. De app-functionaliteit kan veranderen."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="7994800761970572198">"&lt;p&gt;Deze app updaten via &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Deze app krijgt gewoonlijk updates via &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Als je updatet via een andere bron, kun je toekomstige updates via elke bron op je tablet krijgen. De app-functionaliteit kan veranderen.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="2435174886412089791">"&lt;p&gt;Deze app updaten via &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Deze app krijgt gewoonlijk updates via &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Als je updatet via een andere bron, kun je toekomstige updates via elke bron op je tv krijgen. De app-functionaliteit kan veranderen.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="7155138616126795839">"&lt;p&gt;Deze app updaten via &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Deze app krijgt gewoonlijk updates via &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Als je updatet via een andere bron, kun je toekomstige updates via elke bron op je telefoon krijgen. De app-functionaliteit kan veranderen.&lt;/p&gt;"</string>
     <string name="install_failed" msgid="5777824004474125469">"App niet geïnstalleerd."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"De installatie van het pakket is geblokkeerd."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"App die niet is geïnstalleerd als pakket conflicteert met een bestaand pakket."</string>
diff --git a/packages/PackageInstaller/res/values-or/strings.xml b/packages/PackageInstaller/res/values-or/strings.xml
index b6caebd..331852e 100644
--- a/packages/PackageInstaller/res/values-or/strings.xml
+++ b/packages/PackageInstaller/res/values-or/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"ଆପ ଇନଷ୍ଟଲ ହୋଇଗଲା।"</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"ଆପଣ ଏହି ଆପକୁ ଇନଷ୍ଟଲ୍ କରିବା ପାଇଁ ଚାହୁଁଛନ୍ତି କି?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"ଆପଣ ଏହି ଆପକୁ ଅପଡେଟ୍ କରିବା ପାଇଁ ଚାହୁଁଛନ୍ତି କି?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>ରୁ ଏହି ଆପକୁ ଅପଡେଟ କରିବେ?\n\nଏହି ଆପ ସାଧାରଣତଃ <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>ରୁ ଅପଡେଟଗୁଡ଼ିକ ପାଏ। ଏକ ଭିନ୍ନ ସୋର୍ସରୁ ଅପଡେଟ କରି ଆପଣଙ୍କ ଫୋନରେ ଯେ କୌଣସି ସୋର୍ସରୁ ଭବିଷ୍ୟତର ଅପଡେଟଗୁଡ଼ିକ ଆପଣ ପାଇପାରନ୍ତି। ଆପ କାର୍ଯ୍ୟକ୍ଷମତା ପରିବର୍ତ୍ତନ ହୋଇପାରେ।"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="7994800761970572198">"&lt;p&gt;&lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;ରୁ ଏହି ଆପକୁ ଅପଡେଟ କରିବେ?&lt;/p&gt;&lt;p&gt;ଏହି ଆପ ସାଧାରଣତଃ &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;ରୁ ଅପଡେଟ ପାଇଥାଏ। ଏକ ଭିନ୍ନ ସୋର୍ସରୁ ଅପଡେଟ କରି ଆପଣ ଆପଣଙ୍କ ଟାବଲେଟରେ ଯେ କୌଣସି ସୋର୍ସରୁ ଭବିଷ୍ୟତର ଅପଡେଟଗୁଡ଼ିକ ପାଇପାରନ୍ତି। ଆପ କାର୍ଯ୍ୟକ୍ଷମତା ପରିବର୍ତ୍ତନ ହୋଇପାରେ।&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="2435174886412089791">"&lt;p&gt;&lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;ରୁ ଏହି ଆପକୁ ଅପଡେଟ କରିବେ?&lt;/p&gt;&lt;p&gt;ଏହି ଆପ ସାଧାରଣତଃ &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;ରୁ ଅପଡେଟ ପାଇଥାଏ। ଏକ ଭିନ୍ନ ସୋର୍ସରୁ ଅପଡେଟ କରି ଆପଣ ଆପଣଙ୍କ ଟିଭିରେ ଯେ କୌଣସି ସୋର୍ସରୁ ଭବିଷ୍ୟତର ଅପଡେଟଗୁଡ଼ିକ ପାଇପାରନ୍ତି। ଆପ କାର୍ଯ୍ୟକ୍ଷମତା ପରିବର୍ତ୍ତନ ହୋଇପାରେ।&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="7155138616126795839">"&lt;p&gt;&lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;ରୁ ଏହି ଆପକୁ ଅପଡେଟ କରିବେ?&lt;/p&gt;&lt;p&gt;ଏହି ଆପ ସାଧାରଣତଃ &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;ରୁ ଅପଡେଟ ପାଇଥାଏ। ଏକ ଭିନ୍ନ ସୋର୍ସରୁ ଅପଡେଟ କରି ଆପଣ ଆପଣଙ୍କ ଫୋନରେ ଯେ କୌଣସି ସୋର୍ସରୁ ଭବିଷ୍ୟତର ଅପଡେଟଗୁଡ଼ିକ ପାଇପାରନ୍ତି। ଆପ କାର୍ଯ୍ୟକ୍ଷମତା ପରିବର୍ତ୍ତନ ହୋଇପାରେ।&lt;/p&gt;"</string>
     <string name="install_failed" msgid="5777824004474125469">"ଆପ୍‍ ଇନଷ୍ଟଲ୍‌ ହୋଇନାହିଁ।"</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"ଏହି ପ୍ୟାକେଜ୍‌କୁ ଇନଷ୍ଟଲ୍‍ କରାଯିବାରୁ ଅବରୋଧ କରାଯାଇଥିଲା।"</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"ପୂର୍ବରୁ ଥିବା ପ୍ୟାକେଜ୍‍ ସହ ଏହି ପ୍ୟାକେଜ୍‌ର ସମସ୍ୟା ଉପୁଯିବାରୁ ଆପ୍‍ ଇନଷ୍ଟଲ୍‍ ହୋଇପାରିଲା ନାହିଁ।"</string>
diff --git a/packages/PackageInstaller/res/values-pa/strings.xml b/packages/PackageInstaller/res/values-pa/strings.xml
index 1ef4921..5bc4624 100644
--- a/packages/PackageInstaller/res/values-pa/strings.xml
+++ b/packages/PackageInstaller/res/values-pa/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"ਐਪ ਸਥਾਪਤ ਕੀਤੀ ਗਈ।"</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"ਕੀ ਤੁਸੀਂ ਇਸ ਐਪ ਨੂੰ ਸਥਾਪਤ ਕਰਨਾ ਚਾਹੁੰਦੇ ਹੋ?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"ਕੀ ਤੁਸੀਂ ਇਸ ਐਪ ਨੂੰ ਅੱਪਡੇਟ ਕਰਨਾ ਚਾਹੁੰਦੇ ਹੋ?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"ਕੀ ਇਸ ਐਪ ਨੂੰ <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> ਤੋਂ ਅੱਪਡੇਟ ਕਰਨਾ ਹੈ?\n\nਇਸ ਐਪ ਨੂੰ ਆਮ ਤੌਰ \'ਤੇ <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> ਤੋਂ ਅੱਪਡੇਟਾਂ ਪ੍ਰਾਪਤ ਹੁੰਦੀਆਂ ਹਨ। ਕਿਸੇ ਵੱਖਰੇ ਸਰੋਤ ਤੋਂ ਅੱਪਡੇਟ ਕਰ ਕੇ, ਤੁਸੀਂ ਆਪਣੇ ਫ਼ੋਨ \'ਤੇ ਕਿਸੇ ਵੀ ਸਰੋਤ ਤੋਂ ਭਵਿੱਖੀ ਅੱਪਡੇਟਾਂ ਪ੍ਰਾਪਤ ਕਰ ਸਕਦੇ ਹੋ। ਐਪ ਪ੍ਰਕਾਰਜਾਤਮਕਤਾ ਬਦਲ ਸਕਦੀ ਹੈ।"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="7994800761970572198">"&lt;p&gt;ਕੀ ਇਸ ਐਪ ਨੂੰ &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt; ਤੋਂ ਅੱਪਡੇਟ ਕਰਨਾ ਹੈ?&lt;/p&gt;&lt;p&gt;ਇਹ ਐਪ ਆਮ ਤੌਰ \'ਤੇ &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt; ਤੋਂ ਅੱਪਡੇਟਾਂ ਪ੍ਰਾਪਤ ਕਰਦੀ ਹੈ। ਕਿਸੇ ਵੱਖਰੇ ਸਰੋਤ ਤੋਂ ਅੱਪਡੇਟ ਕਰ ਕੇ, ਤੁਸੀਂ ਆਪਣੇ ਟੈਬਲੈੱਟ \'ਤੇ ਕਿਸੇ ਵੀ ਸਰੋਤ ਤੋਂ ਭਵਿੱਖੀ ਅੱਪਡੇਟਾਂ ਪ੍ਰਾਪਤ ਕਰ ਸਕਦੇ ਹੋ। ਐਪ ਪ੍ਰਕਾਰਜਾਤਮਕਤਾ ਬਦਲ ਸਕਦੀ ਹੈ।&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="2435174886412089791">"&lt;p&gt;ਕੀ ਇਸ ਐਪ ਨੂੰ &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt; ਤੋਂ ਅੱਪਡੇਟ ਕਰਨਾ ਹੈ?&lt;/p&gt;&lt;p&gt;ਇਹ ਐਪ ਆਮ ਤੌਰ \'ਤੇ &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt; ਤੋਂ ਅੱਪਡੇਟਾਂ ਪ੍ਰਾਪਤ ਕਰਦੀ ਹੈ। ਕਿਸੇ ਵੱਖਰੇ ਸਰੋਤ ਤੋਂ ਅੱਪਡੇਟ ਕਰ ਕੇ, ਤੁਸੀਂ ਆਪਣੇ ਟੀਵੀ \'ਤੇ ਕਿਸੇ ਵੀ ਸਰੋਤ ਤੋਂ ਭਵਿੱਖੀ ਅੱਪਡੇਟਾਂ ਪ੍ਰਾਪਤ ਕਰ ਸਕਦੇ ਹੋ। ਐਪ ਪ੍ਰਕਾਰਜਾਤਮਕਤਾ ਬਦਲ ਸਕਦੀ ਹੈ।&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="7155138616126795839">"&lt;p&gt;ਕੀ ਇਸ ਐਪ ਨੂੰ &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt; ਤੋਂ ਅੱਪਡੇਟ ਕਰਨਾ ਹੈ?&lt;/p&gt;&lt;p&gt;ਇਹ ਐਪ ਆਮ ਤੌਰ \'ਤੇ &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt; ਤੋਂ ਅੱਪਡੇਟਾਂ ਪ੍ਰਾਪਤ ਕਰਦੀ ਹੈ। ਕਿਸੇ ਵੱਖਰੇ ਸਰੋਤ ਤੋਂ ਅੱਪਡੇਟ ਕਰ ਕੇ, ਤੁਸੀਂ ਆਪਣੇ ਫ਼ੋਨ \'ਤੇ ਕਿਸੇ ਵੀ ਸਰੋਤ ਤੋਂ ਭਵਿੱਖੀ ਅੱਪਡੇਟਾਂ ਪ੍ਰਾਪਤ ਕਰ ਸਕਦੇ ਹੋ। ਐਪ ਪ੍ਰਕਾਰਜਾਤਮਕਤਾ ਬਦਲ ਸਕਦੀ ਹੈ।&lt;/p&gt;"</string>
     <string name="install_failed" msgid="5777824004474125469">"ਐਪ ਸਥਾਪਤ ਨਹੀਂ ਕੀਤੀ ਗਈ।"</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"ਪੈਕੇਜ ਨੂੰ ਸਥਾਪਤ ਹੋਣ ਤੋਂ ਬਲਾਕ ਕੀਤਾ ਗਿਆ ਸੀ।"</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"ਪੈਕੇਜ ਦੇ ਇੱਕ ਮੌਜੂਦਾ ਪੈਕੇਜ ਨਾਲ ਵਿਵਾਦ ਹੋਣ ਕਰਕੇ ਐਪ ਸਥਾਪਤ ਨਹੀਂ ਕੀਤੀ ਗਈ।"</string>
diff --git a/packages/PackageInstaller/res/values-pl/strings.xml b/packages/PackageInstaller/res/values-pl/strings.xml
index e44a391..4aa96f7 100644
--- a/packages/PackageInstaller/res/values-pl/strings.xml
+++ b/packages/PackageInstaller/res/values-pl/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"Aplikacja została zainstalowana."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Zainstalować tę aplikację?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Zaktualizować tę aplikację?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Zastosować do aplikacji aktualizację pochodzącą z tego źródła (<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>)?\n\nAktualizacje dla tej aplikacji zwykle dostarcza <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Jeśli zastosujesz aplikację pochodzącą z innego źródła, możesz w przyszłości otrzymywać na telefonie aktualizacje z dowolnych źródeł. Funkcje aplikacji mogą się zmienić."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="7994800761970572198">"&lt;p&gt;Zaktualizować tę aplikację, korzystając z: &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Aktualizacje tej aplikacji pochodzą zwykle z: &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Jeśli zastosujesz aktualizację pochodzącą z innego źródła, możesz w przyszłości otrzymywać na tablecie aktualizacje z dowolnych źródeł. Funkcje aplikacji mogą ulec zmianie.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="2435174886412089791">"&lt;p&gt;Zaktualizować tę aplikację, korzystając z: &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Aktualizacje tej aplikacji pochodzą zwykle z: &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Jeśli zastosujesz aktualizację pochodzącą z innego źródła, możesz w przyszłości otrzymywać na telewizorze aktualizacje z dowolnych źródeł. Funkcje aplikacji mogą ulec zmianie.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="7155138616126795839">"&lt;p&gt;Zaktualizować tę aplikację, korzystając z: &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Aktualizacje tej aplikacji pochodzą zwykle z: &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Jeśli zastosujesz aplikację pochodzącą z innego źródła, możesz w przyszłości otrzymywać na telefonie aktualizacje z dowolnych źródeł. Funkcje aplikacji mogą ulec zmianie.&lt;/p&gt;"</string>
     <string name="install_failed" msgid="5777824004474125469">"Aplikacja nie została zainstalowana."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"Instalacja pakietu została zablokowana."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"Aplikacja nie została zainstalowana, bo powoduje konflikt z istniejącym pakietem."</string>
diff --git a/packages/PackageInstaller/res/values-pt-rBR/strings.xml b/packages/PackageInstaller/res/values-pt-rBR/strings.xml
index 923c3bd..d8713db 100644
--- a/packages/PackageInstaller/res/values-pt-rBR/strings.xml
+++ b/packages/PackageInstaller/res/values-pt-rBR/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"App instalado."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Quer instalar esse app?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Quer atualizar esse app?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Atualizar este app com <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nEste app normalmente recebe atualizações de <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Se você usa uma fonte diferente, o smartphone vai aceitar outras fontes para fazer as próximas atualizações. A funcionalidade do app pode mudar."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="7994800761970572198">"&lt;p&gt;Atualizar este app de &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Este app costuma receber atualizações de &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Ao usar uma fonte diferente, as próximas atualizações poderão ser feitas com qualquer fonte no tablet. A funcionalidade do app pode mudar.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="2435174886412089791">"&lt;p&gt;Atualizar este app de &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Este app costuma receber atualizações de &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Ao usar uma fonte diferente, as próximas atualizações poderão ser feitas com qualquer fonte na TV. A funcionalidade do app pode mudar.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="7155138616126795839">"&lt;p&gt;Atualizar este app de &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Este app costuma receber atualizações de &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Ao atualizar usando uma origem diferente, as próximas atualizações poderão ser feitas com qualquer origem no seu smartphone. A funcionalidade do app pode mudar.&lt;/p&gt;"</string>
     <string name="install_failed" msgid="5777824004474125469">"O app não foi instalado."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"A instalação do pacote foi bloqueada."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"Como o pacote tem um conflito com um pacote já existente, o app não foi instalado."</string>
diff --git a/packages/PackageInstaller/res/values-pt-rPT/strings.xml b/packages/PackageInstaller/res/values-pt-rPT/strings.xml
index de85f66..cfa2ca6 100644
--- a/packages/PackageInstaller/res/values-pt-rPT/strings.xml
+++ b/packages/PackageInstaller/res/values-pt-rPT/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"App instalada."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Instalar esta app?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Pretende atualizar esta app?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Atualizar esta app a partir de <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nNormalmente, esta app recebe atualizações de <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Se atualizar a partir de uma fonte diferente, poderá receber futuras atualizações de qualquer fonte no seu telemóvel. A funcionalidade da app pode sofrer alterações."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="7994800761970572198">"&lt;p&gt;Atualize esta app a partir do proprietário &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Normalmente, esta app recebe atualizações a partir do proprietário &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Se atualizar a partir de uma origem diferente, pode receber futuras atualizações de qualquer origem no seu tablet. A funcionalidade da app pode mudar.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="2435174886412089791">"&lt;p&gt;Atualize esta app a partir do proprietário &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Normalmente, esta app recebe atualizações a partir do proprietário &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Se atualizar a partir de uma origem diferente, pode receber futuras atualizações de qualquer origem na sua TV. A funcionalidade da app pode mudar.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="7155138616126795839">"&lt;p&gt;Atualize esta app a partir do proprietário &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Normalmente, esta app recebe atualizações a partir do proprietário &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Se atualizar a partir de uma origem diferente, pode receber futuras atualizações de qualquer origem no seu telemóvel. A funcionalidade da app pode mudar.&lt;/p&gt;"</string>
     <string name="install_failed" msgid="5777824004474125469">"Aplicação não instalada."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"Foi bloqueada a instalação do pacote."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"A app não foi instalada porque o pacote entra em conflito com um pacote existente."</string>
diff --git a/packages/PackageInstaller/res/values-pt/strings.xml b/packages/PackageInstaller/res/values-pt/strings.xml
index 923c3bd..d8713db 100644
--- a/packages/PackageInstaller/res/values-pt/strings.xml
+++ b/packages/PackageInstaller/res/values-pt/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"App instalado."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Quer instalar esse app?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Quer atualizar esse app?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Atualizar este app com <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nEste app normalmente recebe atualizações de <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Se você usa uma fonte diferente, o smartphone vai aceitar outras fontes para fazer as próximas atualizações. A funcionalidade do app pode mudar."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="7994800761970572198">"&lt;p&gt;Atualizar este app de &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Este app costuma receber atualizações de &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Ao usar uma fonte diferente, as próximas atualizações poderão ser feitas com qualquer fonte no tablet. A funcionalidade do app pode mudar.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="2435174886412089791">"&lt;p&gt;Atualizar este app de &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Este app costuma receber atualizações de &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Ao usar uma fonte diferente, as próximas atualizações poderão ser feitas com qualquer fonte na TV. A funcionalidade do app pode mudar.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="7155138616126795839">"&lt;p&gt;Atualizar este app de &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Este app costuma receber atualizações de &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Ao atualizar usando uma origem diferente, as próximas atualizações poderão ser feitas com qualquer origem no seu smartphone. A funcionalidade do app pode mudar.&lt;/p&gt;"</string>
     <string name="install_failed" msgid="5777824004474125469">"O app não foi instalado."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"A instalação do pacote foi bloqueada."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"Como o pacote tem um conflito com um pacote já existente, o app não foi instalado."</string>
diff --git a/packages/PackageInstaller/res/values-ro/strings.xml b/packages/PackageInstaller/res/values-ro/strings.xml
index de4dd55..fab0c33 100644
--- a/packages/PackageInstaller/res/values-ro/strings.xml
+++ b/packages/PackageInstaller/res/values-ro/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"Aplicație instalată."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Vrei să instalezi această aplicație?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Vrei să actualizezi această aplicație?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Actualizezi aplicația de la <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nDe obicei, aplicația primește actualizări de la <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Dacă actualizezi din altă sursă, este posibil să primești actualizări viitoare din orice sursă pe telefon. Funcționalitatea aplicației se poate modifica."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="7994800761970572198">"&lt;p&gt;Actualizează aplicația de la &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;De obicei, aplicația primește actualizări de la &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Dacă actualizezi din altă sursă, este posibil să primești actualizări viitoare din orice sursă pe tabletă. Funcționalitatea aplicației se poate modifica.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="2435174886412089791">"&lt;p&gt;Actualizează aplicația de la &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;De obicei, aplicația primește actualizări de la &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Dacă actualizezi din altă sursă, este posibil să primești actualizări viitoare din orice sursă pe televizor. Funcționalitatea aplicației se poate modifica.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="7155138616126795839">"&lt;p&gt;Actualizează aplicația de la &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;De obicei, aplicația primește actualizări de la &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Dacă actualizezi din altă sursă, este posibil să primești actualizări viitoare din orice sursă pe telefon. Funcționalitatea aplicației se poate modifica.&lt;/p&gt;"</string>
     <string name="install_failed" msgid="5777824004474125469">"Aplicația nu a fost instalată."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"Instalarea pachetului a fost blocată."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"Aplicația nu a fost instalată deoarece pachetul intră în conflict cu un pachet existent."</string>
diff --git a/packages/PackageInstaller/res/values-ru/strings.xml b/packages/PackageInstaller/res/values-ru/strings.xml
index d1f56fd..094a19a 100644
--- a/packages/PackageInstaller/res/values-ru/strings.xml
+++ b/packages/PackageInstaller/res/values-ru/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"Приложение установлено."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Установить приложение?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Обновить приложение?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Обновить приложение отсюда: <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nСтандартный источник обновлений этого приложения – <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Если обновить приложение из другого источника, для последующих обновлений будут использоваться любые источники на телефоне. Функции приложения могут измениться."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="7994800761970572198">"&lt;p&gt;Обновить приложение из &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Обычно это приложение получает обновления из &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Если обновить приложение из другого источника, в будущем для обновления могут использоваться любые источники на планшете. Функции приложения могут измениться.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="2435174886412089791">"&lt;p&gt;Обновить приложение из &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Обычно это приложение получает обновления из &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Если обновить приложение из другого источника, в будущем для обновления могут использоваться любые источники на телевизоре. Функции приложения могут измениться.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="7155138616126795839">"&lt;p&gt;Обновить приложение из &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Обычно это приложение получает обновления из &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Если обновить приложение из другого источника, в будущем для обновления могут использоваться любые источники на телефоне. Функции приложения могут измениться.&lt;/p&gt;"</string>
     <string name="install_failed" msgid="5777824004474125469">"Приложение не установлено."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"Установка пакета заблокирована."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"Приложение не установлено, так как оно конфликтует с другим пакетом."</string>
diff --git a/packages/PackageInstaller/res/values-si/strings.xml b/packages/PackageInstaller/res/values-si/strings.xml
index c300b68..afc2195 100644
--- a/packages/PackageInstaller/res/values-si/strings.xml
+++ b/packages/PackageInstaller/res/values-si/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"යෙදුම ස්ථාපනය කර ඇත."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"මෙම යෙදුම ස්ථාපනය කිරීමට ඔබට අවශ්‍යද?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"ඔබට මෙම යෙදුම යාවත්කාලීන කිරීමට අවශ්‍යද?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> වෙතින් මෙම යෙදුම යාවත්කාලීන කරන්න ද?\n\nමෙම යෙදුමට සාමාන්‍යයෙන් <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> සිට යාවත්කාලීන ලැබේ. වෙනස් මූලාශ්‍රයකින් යාවත්කාලීන කිරීමෙන්, ඔබට ඔබේ දුරකථනයෙහි ඕනෑම මූලාශ්‍රයකින් අනාගත යාවත්කාලීන ලැබීමට ඉඩ ඇත. යෙදුම් ක්‍රියාකාරිත්වය වෙනස් වීමට ඉඩ ඇත."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="7994800761970572198">"&lt;p&gt;මෙම යෙදුම &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;වෙතින් යාවත්කාලීන කරන්න ද?&lt;/p&gt;&lt;p&gt;මෙම යෙදුම සාමාන්‍යයෙන් &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt; වෙතින් යාවත්කාලීන ලබා ගනී. වෙනස් මූලාශ්‍රයකින් යාවත්කාලීන කිරීමෙන්, ඔබට ඔබේ ටැබ්ලටයෙහි ඕනෑම මූලාශ්‍රයකින් අනාගත යාවත්කාලීන ලැබීමට ඉඩ ඇත. යෙදුම් ක්‍රියාකාරිත්වය වෙනස් විය හැක.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="2435174886412089791">"&lt;p&gt;මෙම යෙදුම &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;වෙතින් යාවත්කාලීන කරන්න ද?&lt;/p&gt;&lt;p&gt;මෙම යෙදුම සාමාන්‍යයෙන් &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt; වෙතින් යාවත්කාලීන ලබා ගනී. වෙනස් මූලාශ්‍රයකින් යාවත්කාලීන කිරීමෙන්, ඔබට ඔබේ TV මත ඕනෑම මූලාශ්‍රයකින් අනාගත යාවත්කාලීන ලැබීමට ඉඩ ඇත. යෙදුම් ක්‍රියාකාරිත්වය වෙනස් විය හැක.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="7155138616126795839">"&lt;p&gt;මෙම යෙදුම &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;වෙතින් යාවත්කාලීන කරන්න ද?&lt;/p&gt;&lt;p&gt;මෙම යෙදුම සාමාන්‍යයෙන් &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt; වෙතින් යාවත්කාලීන ලබා ගනී. වෙනස් මූලාශ්‍රයකින් යාවත්කාලීන කිරීමෙන්, ඔබට ඔබේ දුරකථනයෙහි ඕනෑම මූලාශ්‍රයකින් අනාගත යාවත්කාලීන ලැබීමට ඉඩ ඇත. යෙදුම් ක්‍රියාකාරිත්වය වෙනස් විය හැක.&lt;/p&gt;"</string>
     <string name="install_failed" msgid="5777824004474125469">"යෙදුම ස්ථාපනය කර නැත."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"මෙම පැකේජය ස්ථාපනය කිරීම අවහිර කරන ලදි."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"පැකේජය දැනට පවතින පැකේජයක් සමග ගැටෙන නිසා යෙදුම ස්ථාපනය නොකරන ලදී."</string>
diff --git a/packages/PackageInstaller/res/values-sk/strings.xml b/packages/PackageInstaller/res/values-sk/strings.xml
index 58a3a35..7a1e70c 100644
--- a/packages/PackageInstaller/res/values-sk/strings.xml
+++ b/packages/PackageInstaller/res/values-sk/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"Aplikácia bola nainštalovaná."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Chcete túto aplikáciu nainštalovať?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Chcete túto aplikáciu aktualizovať?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Chcete aktualizovať túto aplikáciu zo zdroja <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nTúto aplikáciu obvykle aktualizuje <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Ak vykonáte aktualizáciu z iného zdroja, aplikácia sa v budúcnosti môže aktualizovať z ľubovoľného zdroja v telefóne. Funkcie aplikácie sa môžu zmeniť."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="7994800761970572198">"&lt;p&gt;Aktualizujte túto aplikáciu zo zdroja &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Obvykle dostáva aktualizácie zo zdroja &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Ak aktualizujete z iného zdroja, môžete v budúcnosti dostávať aktualizácie z ľubovoľného zdroja v tablete. Funkcie aplikácie sa môžu zmeniť.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="2435174886412089791">"&lt;p&gt;Aktualizujte túto aplikáciu zo zdroja &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Obvykle dostáva aktualizácie zo zdroja &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Ak aktualizujete z iného zdroja, môžete v budúcnosti dostávať aktualizácie z ľubovoľného zdroja v televízore. Funkcie aplikácie sa môžu zmeniť.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="7155138616126795839">"&lt;p&gt;Aktualizujte túto aplikáciu zo zdroja &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Obvykle dostáva aktualizácie zo zdroja &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Ak aktualizujete z iného zdroja, môžete v budúcnosti dostávať aktualizácie z ľubovoľného zdroja v telefóne. Funkcie aplikácie sa môžu zmeniť.&lt;/p&gt;"</string>
     <string name="install_failed" msgid="5777824004474125469">"Aplikácia nebola nainštalovaná."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"Inštalácia balíka bola zablokovaná."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"Aplikácia sa nenainštalovala, pretože balík je v konflikte s existujúcim balíkom."</string>
diff --git a/packages/PackageInstaller/res/values-sl/strings.xml b/packages/PackageInstaller/res/values-sl/strings.xml
index 00c3d15..91bcbda 100644
--- a/packages/PackageInstaller/res/values-sl/strings.xml
+++ b/packages/PackageInstaller/res/values-sl/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"Aplikacija je nameščena."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Ali želite namestiti to aplikacijo?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Ali želite posodobiti to aplikacijo?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Želite to aplikacijo posodobiti iz vira <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nTa aplikacija običajno prejema posodobitve iz vira <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Če jo posodobite iz drugega vira, boste prihodnje posodobitve morda prejemali iz katerega koli vira v telefonu. Funkcija aplikacije se lahko spremeni."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="7994800761970572198">"&lt;p&gt;Želite to aplikacijo posodobiti iz vira &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Ta aplikacija običajno prejema posodobitve iz vira &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Če jo posodobite iz drugega vira, boste prihodnje posodobitve morda prejemali iz katerega koli vira v tabličnem računalniku. Funkcija aplikacije se lahko spremeni.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="2435174886412089791">"&lt;p&gt;Želite to aplikacijo posodobiti iz vira &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Ta aplikacija običajno prejema posodobitve iz vira &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Če jo posodobite iz drugega vira, boste prihodnje posodobitve morda prejemali iz katerega koli vira v televizorju. Funkcija aplikacije se lahko spremeni.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="7155138616126795839">"&lt;p&gt;Želite to aplikacijo posodobiti iz vira &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Ta aplikacija običajno prejema posodobitve iz vira &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Če jo posodobite iz drugega vira, boste prihodnje posodobitve morda prejemali iz katerega koli vira v telefonu. Funkcija aplikacije se lahko spremeni.&lt;/p&gt;"</string>
     <string name="install_failed" msgid="5777824004474125469">"Aplikacija ni nameščena."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"Namestitev paketa je bila blokirana."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"Aplikacija ni bila nameščena, ker je paket v navzkrižju z obstoječim paketom."</string>
diff --git a/packages/PackageInstaller/res/values-sq/strings.xml b/packages/PackageInstaller/res/values-sq/strings.xml
index 9904bc0..566054ed 100644
--- a/packages/PackageInstaller/res/values-sq/strings.xml
+++ b/packages/PackageInstaller/res/values-sq/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"Aplikacioni u instalua."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Dëshiron ta instalosh këtë aplikacion?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Dëshiron ta përditësosh këtë aplikacion?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Të përditësohet ky aplikacion nga <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nKy aplikacion zakonisht merr përditësime nga <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Duke përditësuar nga një burim tjetër, mund të marrësh përditësime të ardhshme nga çdo burim në telefonin tënd. Funksionaliteti i aplikacionit mund të ndryshojë."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="7994800761970572198">"&lt;p&gt;Përditëso këtë aplikacion nga &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Ky aplikacion zakonisht merr përditësime nga &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Duke përditësuar nga një burim tjetër, mund të marrësh përditësime të ardhshme nga çdo burim në tabletin tënd. Funksionaliteti i aplikacionit mund të ndryshojë.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="2435174886412089791">"&lt;p&gt;Përditëso këtë aplikacion nga &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Ky aplikacion zakonisht merr përditësime nga &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Duke përditësuar nga një burim tjetër, mund të marrësh përditësime të ardhshme nga çdo burim në televizorin tënd. Funksionaliteti i aplikacionit mund të ndryshojë.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="7155138616126795839">"&lt;p&gt;Përditëso këtë aplikacion nga &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Ky aplikacion zakonisht merr përditësime nga &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Duke përditësuar nga një burim tjetër, mund të marrësh përditësime të ardhshme nga çdo burim në telefonin tënd. Funksionaliteti i aplikacionit mund të ndryshojë.&lt;/p&gt;"</string>
     <string name="install_failed" msgid="5777824004474125469">"Aplikacioni nuk u instalua."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"Instalimi paketës u bllokua."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"Aplikacioni nuk u instalua pasi paketa është në konflikt me një paketë ekzistuese."</string>
diff --git a/packages/PackageInstaller/res/values-sr/strings.xml b/packages/PackageInstaller/res/values-sr/strings.xml
index 5a0f52d..9e17716 100644
--- a/packages/PackageInstaller/res/values-sr/strings.xml
+++ b/packages/PackageInstaller/res/values-sr/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"Апликација је инсталирана."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Желите да инсталирате ову апликацију?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Желите да ажурирате ову апликацију?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Желите да ажурирате ову апликацију из извора <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nОва апликација се обично ажурира из извора <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Ако ажурирате из другог извора, можете да примате будућа ажурирања из било ког извора на телефону. Функције апликације могу да се промене."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="7994800761970572198">"&lt;p&gt;Ажурирајте ову апликацију код: &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Ова апликација обично добија ажурирања од: &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Ако ажурирате из другог извора, можете да примате будућа ажурирања из било ког извора на таблету. Функције апликације ће се можда променити.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="2435174886412089791">"&lt;p&gt;Ажурирајте ову апликацију код: &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Ова апликација обично добија ажурирања од: &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Ако ажурирате из другог извора, можете да примате будућа ажурирања из било ког извора на ТВ-у. Функције апликације ће се можда променити.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="7155138616126795839">"&lt;p&gt;Ажурирајте ову апликацију код: &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Ова апликација обично добија ажурирања од: &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Ако ажурирате из другог извора, можете да примате будућа ажурирања из било ког извора на телефону. Функције апликације ће се можда променити.&lt;/p&gt;"</string>
     <string name="install_failed" msgid="5777824004474125469">"Апликација није инсталирана."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"Инсталирање пакета је блокирано."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"Апликација није инсталирана јер је пакет неусаглашен са постојећим пакетом."</string>
diff --git a/packages/PackageInstaller/res/values-sv/strings.xml b/packages/PackageInstaller/res/values-sv/strings.xml
index ec6af2e..79e5738 100644
--- a/packages/PackageInstaller/res/values-sv/strings.xml
+++ b/packages/PackageInstaller/res/values-sv/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"Appen har installerats."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Vill du installera den här appen?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Vill du uppdatera den här appen?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Vill du uppdatera den här appen från <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nAppen tar vanligtvis emot uppdateringar från <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Genom att uppdatera från en annan källa kan du komma att ta emot framtida uppdateringar från olika källor på telefonen. Appfunktioner kan förändras."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="7994800761970572198">"&lt;p&gt;Uppdatera appen från &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Appen får vanligtvis uppdateringar från &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Om du uppdaterar från en annan källa kanske du får framtida uppdateringar från olika källor på surfplattan. Appfunktionerna kanske ändras.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="2435174886412089791">"&lt;p&gt;Uppdatera appen från &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Appen får vanligtvis uppdateringar från &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Om du uppdaterar från en annan källa kanske du får framtida uppdateringar från olika källor på tv:n. Appfunktionerna kanske ändras.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="7155138616126795839">"&lt;p&gt;Uppdatera appen från &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Appen får vanligtvis uppdateringar från &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Om du uppdaterar från en annan källa kanske du får framtida uppdateringar från olika källor på telefonen. Appfunktionerna kanske ändras.&lt;/p&gt;"</string>
     <string name="install_failed" msgid="5777824004474125469">"Appen har inte installerats."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"Paketet har blockerats för installation."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"Appen har inte installerats på grund av en konflikt mellan detta paket och ett befintligt paket."</string>
diff --git a/packages/PackageInstaller/res/values-sw/strings.xml b/packages/PackageInstaller/res/values-sw/strings.xml
index 0eb224a..f69d612 100644
--- a/packages/PackageInstaller/res/values-sw/strings.xml
+++ b/packages/PackageInstaller/res/values-sw/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"Imesakinisha programu."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Ungependa kusakinisha programu hii?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Ungependa kusasisha programu hii?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Ungependa kusasisha programu hii kutoka kwenye <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nProgramu hii kwa kawaida hupokea masasisho kutoka <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Kwa kusasisha kutoka chanzo tofauti, huenda ukapokea masasisho ya siku zijazo kutoka chanzo chochote kwenye simu yako. Utendaji wa programu unaweza kubadilika."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="7994800761970572198">"&lt;p&gt;Sasisha programu hii kutoka kwenye &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Programu hii hupokea masasisho kutoka kwenye &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Kwa kusasisha kutoka kwenye chanzo tofauti, unaweza kupokea masasisho ya baadaye kutoka kwenye chanzo chochote katika kishikwambi chako. Huenda utendaji wa programu ukabadilika.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="2435174886412089791">"&lt;p&gt;Sasisha programu hii kutoka kwenye &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Programu hii hupokea masasisho kutoka kwenye &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Kwa kusasisha kutoka kwenye chanzo tofauti, unaweza kupokea masasisho ya baadaye kutoka kwenye chanzo chochote katika TV yako. Huenda utendaji wa programu ukabadilika.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="7155138616126795839">"&lt;p&gt;Sasisha programu hii kutoka kwenye &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Programu hii hupokea masasisho kutoka kwenye &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Kwa kusasisha kutoka kwenye chanzo tofauti, unaweza kupokea masasisho ya baadaye kutoka kwenye chanzo chochote katika simu yako. Huenda utendaji wa programu ukabadilika.&lt;/p&gt;"</string>
     <string name="install_failed" msgid="5777824004474125469">"Imeshindwa kusakinisha programu."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"Kifurushi kimezuiwa kisisakinishwe."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"Programu haikusakinishwa kwa sababu kifurushi kinakinzana na kifurushi kingine kilichopo."</string>
diff --git a/packages/PackageInstaller/res/values-ta/strings.xml b/packages/PackageInstaller/res/values-ta/strings.xml
index c60910c..f2d5463 100644
--- a/packages/PackageInstaller/res/values-ta/strings.xml
+++ b/packages/PackageInstaller/res/values-ta/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"ஆப்ஸ் நிறுவப்பட்டது."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"இந்த ஆப்ஸை நிறுவ வேண்டுமா?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"இந்த ஆப்ஸைப் புதுப்பிக்க வேண்டுமா?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> இலிருந்து இந்த ஆப்ஸைப் புதுப்பிக்க வேண்டுமா?\n\nபொதுவாக இந்த ஆப்ஸ்<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> இலிருந்து புதுப்பிப்புகளைப் பெறும். வேறொன்றின் மூலம் புதுப்பித்தால் எதிர்காலத்தில் மொபைலில் வேறு இடத்திலிருந்து புதுப்பிப்புகளை நீங்கள் பெறக்கூடும். ஆப்ஸ் செயல்பாடுகள் மாறுபடக்கூடும்."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="7994800761970572198">"&lt;p&gt;&lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt; இல் இருந்து இந்த ஆப்ஸைப் புதுப்பிக்க வேண்டுமா?&lt;/p&gt;&lt;p&gt;பொதுவாக இந்த ஆப்ஸ் &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt; இல் இருந்து புதுப்பிப்புகளைப் பெறும். வேறு உரிமையாளர் மூலம் புதுப்பித்தால் எதிர்காலத்தில் டேப்லெட்டில் இடம்பெற்றுள்ள எந்த உரிமையாளரிடம் இருந்தும் புதுப்பிப்புகளைப் பெறக்கூடும். ஆப்ஸ் செயல்பாடுகள் மாறுபடக்கூடும்.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="2435174886412089791">"&lt;p&gt;&lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt; இல் இருந்து இந்த ஆப்ஸைப் புதுப்பிக்க வேண்டுமா?&lt;/p&gt;&lt;p&gt;பொதுவாக இந்த ஆப்ஸ் &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt; இல் இருந்து புதுப்பிப்புகளைப் பெறும். வேறு உரிமையாளர் மூலம் புதுப்பித்தால் எதிர்காலத்தில் டிவியில் இடம்பெற்றுள்ள எந்த உரிமையாளரிடம் இருந்தும் புதுப்பிப்புகளைப் பெறக்கூடும். ஆப்ஸ் செயல்பாடுகள் மாறுபடக்கூடும்.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="7155138616126795839">"&lt;p&gt;&lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt; இல் இருந்து இந்த ஆப்ஸைப் புதுப்பிக்க வேண்டுமா?&lt;/p&gt;&lt;p&gt;பொதுவாக இந்த ஆப்ஸ் &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt; இல் இருந்து புதுப்பிப்புகளைப் பெறும். வேறு உரிமையாளர் மூலம் புதுப்பித்தால் எதிர்காலத்தில் மொபைலில் இடம்பெற்றுள்ள எந்த உரிமையாளரிடம் இருந்தும் புதுப்பிப்புகளைப் பெறக்கூடும். ஆப்ஸ் செயல்பாடுகள் மாறுபடக்கூடும்.&lt;/p&gt;"</string>
     <string name="install_failed" msgid="5777824004474125469">"ஆப்ஸ் நிறுவப்படவில்லை."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"இந்தத் தொகுப்பு நிறுவப்படுவதிலிருந்து தடுக்கப்பட்டது."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"இந்தத் தொகுப்பு ஏற்கனவே உள்ள தொகுப்புடன் முரண்படுவதால் ஆப்ஸ் நிறுவப்படவில்லை."</string>
diff --git a/packages/PackageInstaller/res/values-te/strings.xml b/packages/PackageInstaller/res/values-te/strings.xml
index 25673f3..1013e3d 100644
--- a/packages/PackageInstaller/res/values-te/strings.xml
+++ b/packages/PackageInstaller/res/values-te/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"యాప్ ఇన్‌స్టాల్ చేయబడింది."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"మీరు ఈ యాప్‌ను ఇన్‌స్టాల్ చేయాలనుకుంటున్నారా?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"మీరు ఈ యాప్‌ను అప్‌డేట్ చేయాలనుకుంటున్నారా?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> ద్వారా ఈ యాప్‌ను అప్‌డేట్ చేయాలా?\n\nఈ యాప్ సాధారణంగా <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> నుండి అప్‌డేట్‌లను అందుకుంటుంది. వేరే సోర్స్ ద్వారా అప్‌డేట్ చేయడం వల్ల, భవిష్యత్తులో మీ ఫోన్‌లోని ఏ సోర్స్ ద్వారా అయినా అప్‌డేట్‌లను పొందవచ్చు. యాప్ ఫంక్షనాలిటీ మారవచ్చు."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="7994800761970572198">"&lt;p&gt;ఈ యాప్‌ను &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt; నుండి అప్‌డేట్ చేయాలా?&lt;/p&gt;&lt;p&gt;ఈ యాప్ సాధారణంగా &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt; నుండి అప్‌డేట్‌లను స్వీకరిస్తుంది. విభిన్నమైన సోర్స్ నుండి అప్‌డేట్ చేయడం ద్వారా, మీరు టాబ్లెట్‌లోని ఏదైనా సోర్స్ నుండి భవిష్యత్తు అప్‌డేట్‌లను పొందవచ్చు. యాప్ ఫంక్షనాలిటీ మారవచ్చు.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="2435174886412089791">"&lt;p&gt;ఈ యాప్‌ను &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt; నుండి అప్‌డేట్ చేయాలా?&lt;/p&gt;&lt;p&gt;ఈ యాప్ సాధారణంగా &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt; నుండి అప్‌డేట్‌లను స్వీకరిస్తుంది. విభిన్నమైన సోర్స్ నుండి అప్‌డేట్ చేయడం ద్వారా, మీరు TVలోని ఏదైనా సోర్స్ నుండి భవిష్యత్తు అప్‌డేట్‌లను పొందవచ్చు. యాప్ ఫంక్షనాలిటీ మారవచ్చు.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="7155138616126795839">"&lt;p&gt;ఈ యాప్‌ను &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt; నుండి అప్‌డేట్ చేయాలా?&lt;/p&gt;&lt;p&gt;ఈ యాప్ సాధారణంగా &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt; నుండి అప్‌డేట్‌లను స్వీకరిస్తుంది. విభిన్న సోర్స్ నుండి అప్‌డేట్ చేయడం ద్వారా, మీరు ఫోన్‌లోని ఏదైనా సోర్స్ నుండి భవిష్యత్తు అప్‌డేట్‌లను పొందవచ్చు. యాప్ ఫంక్షనాలిటీ మారవచ్చు.&lt;/p&gt;"</string>
     <string name="install_failed" msgid="5777824004474125469">"యాప్ ఇన్‌స్టాల్ చేయబడలేదు."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"ప్యాకేజీ ఇన్‌స్టాల్ కాకుండా బ్లాక్ చేయబడింది."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"ప్యాకేజీ, అలాగే ఇప్పటికే ఉన్న ప్యాకేజీ మధ్య వైరుధ్యం ఉన్నందున యాప్ ఇన్‌స్టాల్ చేయబడలేదు."</string>
diff --git a/packages/PackageInstaller/res/values-th/strings.xml b/packages/PackageInstaller/res/values-th/strings.xml
index c8f3275..35a7611 100644
--- a/packages/PackageInstaller/res/values-th/strings.xml
+++ b/packages/PackageInstaller/res/values-th/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"ติดตั้งแอปแล้ว"</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"คุณต้องการติดตั้งแอปนี้ไหม"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"คุณต้องการอัปเดตแอปนี้ไหม"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"อัปเดตแอปนี้จาก <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> ไหม\n\nโดยปกติแล้ว แอปนี้จะได้รับการอัปเดตจาก <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> การอัปเดตจากแหล่งที่มาอื่นอาจทำให้โทรศัพท์ของคุณได้รับการอัปเดตจากแหล่งที่มาใดก็ได้ในอนาคต ฟังก์ชันการทำงานของแอปอาจมีการเปลี่ยนแปลง"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="7994800761970572198">"&lt;p&gt;อัปเดตแอปนี้จาก &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt; ไหม&lt;/p&gt;&lt;p&gt;โดยปกติแล้ว แอปนี้จะได้รับการอัปเดตจาก &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt; การอัปเดตจากแหล่งที่มาอื่นอาจทำให้แท็บเล็ตของคุณได้รับการอัปเดตจากแหล่งที่มานั้นในอนาคต ซึ่งอาจทำให้ฟังก์ชันการทำงานของแอปเปลี่ยนแปลงไป&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="2435174886412089791">"&lt;p&gt;อัปเดตแอปนี้จาก &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt; ไหม&lt;/p&gt;&lt;p&gt;โดยปกติแล้ว แอปนี้จะได้รับการอัปเดตจาก &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt; การอัปเดตจากแหล่งที่มาอื่นอาจทำให้ทีวีของคุณได้รับการอัปเดตจากแหล่งที่มานั้นในอนาคต ซึ่งอาจทำให้ฟังก์ชันการทำงานของแอปเปลี่ยนแปลงไป&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="7155138616126795839">"&lt;p&gt;อัปเดตแอปนี้จาก &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt; ไหม&lt;/p&gt;&lt;p&gt;โดยปกติแล้ว แอปนี้จะได้รับการอัปเดตจาก &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt; การอัปเดตจากแหล่งที่มาอื่นอาจทำให้โทรศัพท์ของคุณได้รับการอัปเดตจากแหล่งที่มานั้นในอนาคต ซึ่งอาจทำให้ฟังก์ชันการทำงานของแอปเปลี่ยนแปลงไป&lt;/p&gt;"</string>
     <string name="install_failed" msgid="5777824004474125469">"ไม่ได้ติดตั้งแอป"</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"มีการบล็อกแพ็กเกจไม่ให้ติดตั้ง"</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"ไม่ได้ติดตั้งแอปเพราะแพ็กเกจขัดแย้งกับแพ็กเกจที่มีอยู่"</string>
diff --git a/packages/PackageInstaller/res/values-tl/strings.xml b/packages/PackageInstaller/res/values-tl/strings.xml
index 4d516b5..78a1b2b 100644
--- a/packages/PackageInstaller/res/values-tl/strings.xml
+++ b/packages/PackageInstaller/res/values-tl/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"Na-install na ang app."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Gusto mo bang i-install ang app na ito?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Gusto mo bang i-update ang app na ito?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"I-update itong app na mula sa <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nKaraniwang nakakatanggap ang app na ito ng mga update mula sa <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Sa pag-update mula sa ibang pinagmulan, puwede kang makatanggap ng mga update mula sa anumang pinagmulan sa iyong telepono sa hinaharap. Posibleng magbago ang functionality ng app."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="7994800761970572198">"&lt;p&gt;I-update ang app na ito mula sa &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Karaniwang nakakatanggap ng mga update ang app na ito mula sa &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Sa pamamagitan ng pag-update mula sa ibang source, puwede kang makatanggap ng mga update mula sa anumang source sa iyong tablet sa hinaharap. Posibleng magbago ang functionality ng app.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="2435174886412089791">"&lt;p&gt;I-update ang app na ito mula sa &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Karaniwang nakakatanggap ng mga update ang app na ito mula sa &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Sa pamamagitan ng pag-update mula sa ibang source, puwede kang makatanggap ng mga update mula sa anumang source sa iyong TV sa hinaharap. Posibleng magbago ang functionality ng app.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="7155138616126795839">"&lt;p&gt;I-update ang app na ito mula sa &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Karaniwang nakakatanggap ng mga update ang app na ito mula sa &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Sa pamamagitan ng pag-update mula sa ibang source, puwede kang makatanggap ng mga update mula sa anumang source sa iyong telepono sa hinaharap. Posibleng magbago ang functionality ng app.&lt;/p&gt;"</string>
     <string name="install_failed" msgid="5777824004474125469">"Hindi na-install ang app."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"Na-block ang pag-install sa package."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"Hindi na-install ang app dahil nagkakaproblema ang package sa isang dati nang package."</string>
diff --git a/packages/PackageInstaller/res/values-tr/strings.xml b/packages/PackageInstaller/res/values-tr/strings.xml
index 050d398..a34765c 100644
--- a/packages/PackageInstaller/res/values-tr/strings.xml
+++ b/packages/PackageInstaller/res/values-tr/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"Uygulama yüklendi."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Bu uygulamayı yüklemek istiyor musunuz?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Bu uygulamayı güncellemek istiyor musunuz?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Bu uygulama <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> kaynağından güncellensin mi?\n\nBu uygulama genellikle <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> kaynağından güncelleme alır. Farklı bir kaynaktan güncellerseniz ileride telefonunuzda herhangi bir kaynaktan güncelleme alabilirsiniz. Uygulama işlevselliği değişebilir."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="7994800761970572198">"&lt;p&gt;Bu uygulama &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt; kaynağından güncellensin mi?&lt;/p&gt;&lt;p&gt;Bu uygulama genellikle &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt; kaynağından güncelleme alır. Farklı bir kaynaktan güncellerseniz ileride tabletinizde herhangi bir kaynaktan güncelleme alabilirsiniz. Uygulama işlevselliği değişebilir.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="2435174886412089791">"&lt;p&gt;Bu uygulama &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt; kaynağından güncellensin mi?&lt;/p&gt;&lt;p&gt;Bu uygulama genellikle &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt; kaynağından güncelleme alır. Farklı bir kaynaktan güncellerseniz ileride televizyonunuzda herhangi bir kaynaktan güncelleme alabilirsiniz. Uygulama işlevselliği değişebilir.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="7155138616126795839">"&lt;p&gt;Bu uygulama &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt; kaynağından güncellensin mi?&lt;/p&gt;&lt;p&gt;Bu uygulama genellikle &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt; kaynağından güncelleme alır. Farklı bir kaynaktan güncellerseniz ileride telefonunuzda herhangi bir kaynaktan güncelleme alabilirsiniz. Uygulama işlevselliği değişebilir.&lt;/p&gt;"</string>
     <string name="install_failed" msgid="5777824004474125469">"Uygulama yüklenmedi."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"Paketin yüklemesi engellendi."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"Paket, mevcut bir paketle çakıştığından uygulama yüklenemedi."</string>
diff --git a/packages/PackageInstaller/res/values-uk/strings.xml b/packages/PackageInstaller/res/values-uk/strings.xml
index ec10962..76f3372 100644
--- a/packages/PackageInstaller/res/values-uk/strings.xml
+++ b/packages/PackageInstaller/res/values-uk/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"Програму встановлено."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Установити цей додаток?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Оновити цей додаток?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Оновити цей додаток через <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nЗазвичай цей додаток отримує оновлення в інший спосіб (<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>). Якщо встановити оновлення з іншого джерела, надалі на ваш телефон зможуть надходити оновлення з будь-яких джерел. Це може змінити функції додатка."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="7994800761970572198">"&lt;p&gt;Оновити цей додаток з &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Зазвичай цей додаток отримує оновлення від &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Якщо встановити оновлення з іншого джерела, надалі на ваш планшет зможуть надходити оновлення з будь-яких джерел. Це може змінити функції додатка.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="2435174886412089791">"&lt;p&gt;Оновити цей додаток з &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Зазвичай цей додаток отримує оновлення від &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Якщо встановити оновлення з іншого джерела, надалі на ваш телевізор зможуть надходити оновлення з будь-яких джерел. Це може змінити функції додатка.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="7155138616126795839">"&lt;p&gt;Оновити цей додаток з &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Зазвичай цей додаток отримує оновлення від &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Якщо встановити оновлення з іншого джерела, надалі на ваш телефон зможуть надходити оновлення з будь-яких джерел. Це може змінити функції додатка.&lt;/p&gt;"</string>
     <string name="install_failed" msgid="5777824004474125469">"Програму не встановлено."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"Встановлення пакета заблоковано."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"Додаток не встановлено, оскільки пакет конфліктує з наявним пакетом."</string>
diff --git a/packages/PackageInstaller/res/values-ur/strings.xml b/packages/PackageInstaller/res/values-ur/strings.xml
index b3b4c0d..e621d71 100644
--- a/packages/PackageInstaller/res/values-ur/strings.xml
+++ b/packages/PackageInstaller/res/values-ur/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"ایپ انسٹال ہو گئی۔"</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"کیا آپ یہ ایپ انسٹال کرنا چاہتے ہیں؟"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"کیا آپ یہ ایپ اپ ڈیٹ کرنا چاہتے ہیں؟"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"اس ایپ کو <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> سے اپ ڈیٹ کریں؟\n\n اس ایپ کو عام طور پر <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> سے اپ ڈیٹس موصول ہوتی ہیں۔ کسی مختلف ذریعے سے اپ ڈیٹ کر کے، آپ اپنے فون پر کسی بھی ذریعے سے مستقبل کی اپ ڈیٹس حاصل کر سکتے ہیں۔ ایپ کی فعالیت تبدیل ہو سکتی ہے۔"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="7994800761970572198">"‏‎&lt;p&gt;‎اس ایپ کو ‎&lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;‎ سے اپ ڈیٹ کریں یہ ایپ عام طور پر ‎&lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;‎ سے اپ ڈیٹس موصول کرتی ہے۔ کسی مختلف وسیلے سے اپ ڈیٹ کر کے، آپ اپنے ٹیبلیٹ پر کسی بھی وسیلے سے مستقبل کی اپ ڈیٹس حاصل کر سکتے ہیں۔ ایپ کی فعالیت تبدیل ہو سکتی ہے۔‎&lt;/p&gt;‎"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="2435174886412089791">"‏‎&lt;p&gt;‎اس ایپ کو ‎&lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;‎ سے اپ ڈیٹ کریں یہ ایپ عام طور پر ‎&lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;‎ سے اپ ڈیٹس موصول کرتی ہے۔ کسی مختلف وسیلے سے اپ ڈیٹ کر کے، آپ اپنے TV پر کسی بھی وسیلے سے مستقبل کی اپ ڈیٹس حاصل کر سکتے ہیں۔ ایپ کی فعالیت تبدیل ہو سکتی ہے۔‎&lt;/p&gt;‎"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="7155138616126795839">"‏‎&lt;p&gt;‎اس ایپ کو ‎&lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;‎ سے اپ ڈیٹ کریں یہ ایپ عام طور پر ‎&lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;‎ سے اپ ڈیٹس موصول کرتی ہے۔ کسی مختلف ذریعے سے اپ ڈیٹ کر کے، آپ اپنے فون پر کسی بھی ذریعے سے مستقبل کی اپ ڈیٹس حاصل کر سکتے ہیں۔ ایپ کی فعالیت تبدیل ہو سکتی ہے۔‎&lt;/p&gt;‎"</string>
     <string name="install_failed" msgid="5777824004474125469">"ایپ انسٹال نہیں ہوئی۔"</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"پیکج کو انسٹال ہونے سے مسدود کر دیا گیا تھا۔"</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"ایپ انسٹال نہیں ہوئی کیونکہ پیکج ایک موجودہ پیکیج سے متصادم ہے۔"</string>
diff --git a/packages/PackageInstaller/res/values-uz/strings.xml b/packages/PackageInstaller/res/values-uz/strings.xml
index 2993663c..7597fb2 100644
--- a/packages/PackageInstaller/res/values-uz/strings.xml
+++ b/packages/PackageInstaller/res/values-uz/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"Ilova o‘rnatildi."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Bu ilovani oʻrnatmoqchimisiz?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Bu ilova yangilansinmi?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Bu ilova <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> orqali yangilansinmi?\n\nBu ilova odatda <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> orqali yangilanishlar oladi. Boshqa manbadan yangilash orqali siz kelajakdagi yangilanishlarni telefoningizda istalgan manbadan olishingiz mumkin. Ilova funksiyalari oʻzgarishi mumkin."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="7994800761970572198">"&lt;p&gt;Bu ilovani bundan yangilash: &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Bu ilova odatda yangilanishlarni bundan oladi: &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Boshqa manbadan yangilash orqali kelgusi yangilanishlarni planshetda istalgan manbadan olishingiz mumkin. Ilova funksiyalari oʻzgarishi mumkin.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="2435174886412089791">"&lt;p&gt;Bu ilovani bundan yangilash: &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Bu ilova odatda yangilanishlarni bundan oladi: &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Boshqa manbadan yangilash orqali kelgusi yangilanishlarni televizorda istalgan manbadan olishingiz mumkin. Ilova funksiyalari oʻzgarishi mumkin.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="7155138616126795839">"&lt;p&gt;Bu ilovani bundan yangilash: &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Bu ilova odatda yangilanishlarni bundan oladi: &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Boshqa manbadan yangilash orqali kelgusi yangilanishlarni telefonda istalgan manbadan olishingiz mumkin. Ilova funksiyalari oʻzgarishi mumkin.&lt;/p&gt;"</string>
     <string name="install_failed" msgid="5777824004474125469">"Ilova o‘rnatilmadi."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"Paket o‘rnatilishga qarshi bloklangan."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"Paket mavjud paket bilan zid kelganligi uchun ilovani o‘rnatib bo‘lmadi."</string>
diff --git a/packages/PackageInstaller/res/values-vi/strings.xml b/packages/PackageInstaller/res/values-vi/strings.xml
index f6ffa3a..53b2a78 100644
--- a/packages/PackageInstaller/res/values-vi/strings.xml
+++ b/packages/PackageInstaller/res/values-vi/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"Ứng dụng đã được cài đặt."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Bạn có muốn cài đặt ứng dụng này không?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Bạn có muốn cập nhật ứng dụng này không?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Cập nhật ứng dụng này của <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nỨng dụng này thường nhận thông tin cập nhật từ <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Khi cập nhật từ một nguồn khác, trong tương lai, bạn có thể nhận thông tin cập nhật từ nguồn bất kỳ trên điện thoại của bạn. Chức năng ứng dụng có thể thay đổi."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="7994800761970572198">"&lt;p&gt;Cập nhật ứng dụng này từ &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Ứng dụng này thường nhận các bản cập nhật từ &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Khi cập nhật từ một nguồn khác, bạn có thể nhận các bản cập nhật trong tương lai từ nguồn bất kỳ trên máy tính bảng của mình. Chức năng của ứng dụng có thể thay đổi.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="2435174886412089791">"&lt;p&gt;Cập nhật ứng dụng này từ &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Ứng dụng này thường nhận các bản cập nhật từ &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Khi cập nhật từ một nguồn khác, bạn có thể nhận các bản cập nhật trong tương lai từ nguồn bất kỳ trên TV của mình. Chức năng của ứng dụng có thể thay đổi.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="7155138616126795839">"&lt;p&gt;Cập nhật ứng dụng này từ &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Ứng dụng này thường nhận các bản cập nhật từ &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Khi cập nhật từ một nguồn khác, bạn có thể nhận các bản cập nhật trong tương lai từ nguồn bất kỳ trên điện thoại của mình. Chức năng của ứng dụng có thể thay đổi.&lt;/p&gt;"</string>
     <string name="install_failed" msgid="5777824004474125469">"Ứng dụng chưa được cài đặt."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"Đã chặn cài đặt gói."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"Chưa cài đặt được ứng dụng do gói xung đột với một gói hiện có."</string>
diff --git a/packages/PackageInstaller/res/values-zh-rCN/strings.xml b/packages/PackageInstaller/res/values-zh-rCN/strings.xml
index 5c2d11f..eb6d0f2 100644
--- a/packages/PackageInstaller/res/values-zh-rCN/strings.xml
+++ b/packages/PackageInstaller/res/values-zh-rCN/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"已安装应用。"</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"要安装此应用吗?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"要更新此应用吗?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"要通过<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>更新此应用吗?\n\n此应用通常通过<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>接收更新。如果通过其他来源更新,手机未来可能会收到任何来源的更新。应用功能可能会变化。"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="7994800761970572198">"&lt;p&gt;要通过&lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;更新此应用?&lt;/p&gt;&lt;p&gt;此应用通常从&lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;接收更新。如果通过其他来源更新,平板电脑未来可能会收到任何来源的更新。应用功能可能会改变。&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="2435174886412089791">"&lt;p&gt;要通过&lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;更新此应用?&lt;/p&gt;&lt;p&gt;此应用通常从&lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;接收更新。如果通过其他来源更新,电视未来可能会收到任何来源的更新。应用功能可能会改变。&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="7155138616126795839">"&lt;p&gt;要通过&lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;更新此应用?&lt;/p&gt;&lt;p&gt;此应用通常从&lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;接收更新。如果通过其他来源更新,手机未来可能会收到任何来源的更新。应用功能可能会改变。&lt;/p&gt;"</string>
     <string name="install_failed" msgid="5777824004474125469">"未安装应用。"</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"系统已禁止安装该软件包。"</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"应用未安装:软件包与现有软件包存在冲突。"</string>
diff --git a/packages/PackageInstaller/res/values-zh-rHK/strings.xml b/packages/PackageInstaller/res/values-zh-rHK/strings.xml
index b36770d..883a6b1 100644
--- a/packages/PackageInstaller/res/values-zh-rHK/strings.xml
+++ b/packages/PackageInstaller/res/values-zh-rHK/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"已安裝應用程式。"</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"要安裝此應用程式嗎?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"要更新此應用程式嗎?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"要從「<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>」更新此應用程式嗎?\n\n在正常情況下,系統會透過「<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>」更新此應用程式。如果透過其他來源更新,手機未來可能會收到任何來源的更新。應用程式功能可能會有變動。"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="7994800761970572198">"要從「<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>」更新此應用程式嗎?在正常情況下,系統會透過「<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>」更新此應用程式。如果透過其他來源更新,平板電腦未來可能會收到任何來源的更新。應用程式功能可能會有變動。"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="2435174886412089791">"要從「<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>」更新此應用程式嗎?在正常情況下,系統會透過「<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>」更新此應用程式。如果透過其他來源更新,電視未來可能會收到任何來源的更新。應用程式功能可能會有變動。"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="7155138616126795839">"要從「<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>」更新此應用程式嗎?在正常情況下,系統會透過「<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>」更新此應用程式。如果透過其他來源更新,手機未來可能會收到任何來源的更新。應用程式功能可能會有變動。"</string>
     <string name="install_failed" msgid="5777824004474125469">"未安裝應用程式。"</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"套件已遭封鎖,無法安裝。"</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"套件與現有的套件發生衝突,無法安裝應用程式。"</string>
diff --git a/packages/PackageInstaller/res/values-zh-rTW/strings.xml b/packages/PackageInstaller/res/values-zh-rTW/strings.xml
index 2a87eb8..0d9e266 100644
--- a/packages/PackageInstaller/res/values-zh-rTW/strings.xml
+++ b/packages/PackageInstaller/res/values-zh-rTW/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"已安裝應用程式。"</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"要安裝這個應用程式嗎?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"要更新這個應用程式嗎?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"要透過「<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>」更新這個應用程式嗎?\n\n在正常情況下,系統會透過「<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>」更新這個應用程式。如果透過其他來源更新,手機未來可能會收到任何來源的更新。應用程式功能可能會有變動。"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="7994800761970572198">"&lt;p&gt;要透過「<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>」&lt;b&gt;&lt;/b&gt;更新這個應用程式嗎?&lt;/p&gt;&lt;p&gt;這個應用程式一般是接收「<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>」&lt;b&gt;&lt;/b&gt;的更新。如果透過其他來源更新,平板電腦未來可能會收到任何來源的更新。應用程式功能可能會變動。&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="2435174886412089791">"&lt;p&gt;要透過「<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>」&lt;b&gt;&lt;/b&gt;更新這個應用程式嗎?&lt;/p&gt;&lt;p&gt;這個應用程式一般是接收「<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>」&lt;b&gt;&lt;/b&gt;的更新。如果透過其他來源更新,TV 裝置未來可能會收到任何來源的更新。應用程式功能可能會變動。&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="7155138616126795839">"&lt;p&gt;要透過「<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>」&lt;b&gt;&lt;/b&gt;更新這個應用程式嗎?&lt;/p&gt;&lt;p&gt;這個應用程式一般是接收「<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>」&lt;b&gt;&lt;/b&gt;的更新。如果透過其他來源更新,手機未來可能會收到任何來源的更新。應用程式功能可能會變動。&lt;/p&gt;"</string>
     <string name="install_failed" msgid="5777824004474125469">"未安裝應用程式。"</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"系統已封鎖這個套件,因此無法安裝。"</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"應用程式套件與現有套件衝突,因此未能完成安裝。"</string>
diff --git a/packages/PackageInstaller/res/values-zu/strings.xml b/packages/PackageInstaller/res/values-zu/strings.xml
index ca9c63b..98c75e8 100644
--- a/packages/PackageInstaller/res/values-zu/strings.xml
+++ b/packages/PackageInstaller/res/values-zu/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"Uhlelo lokusebenza olufakiwe."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Ingabe ufuna ukufaka le app?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Ingabe ufuna ukubuyekeza le app?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Buyekeza le app kusuka ku-<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nNgokuvamile le app ithola izibuyekezo kusuka ku-<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Ngokubuyekeza kusuka kumthombo ohlukile, ungase uthole izibuyekezo zesikhathi esizayo kusuka kunoma yimuphi umthombo efonini yakho. Okwenziwa yi-app kungase kushintshe."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="7994800761970572198">"&lt;p&gt;Buyekeza le-app ukusuka ku-&lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Le app ivame ukuthola izibuyekezo ukusuka ku-&lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Ngokubuyekeza ukusuka kumthombo ohlukile, ungase uthole izibuyekezo zesikhathi esizayo ukusuka kunoma yimuphi umthombo kuthebulethi yakho. Ukusebenza ku-app kungashintsha.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="2435174886412089791">"&lt;p&gt;Buyekeza le-app ukusuka ku-&lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Le app ivame ukuthola izibuyekezo ukusuka ku-&lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Ngokubuyekeza ukusuka kumthombo ohlukile, ungase uthole izibuyekezo zesikhathi esizayo ukusuka kunoma yimuphi umthombo ku-TV yakho. Ukusebenza ku-app kungashintsha.&lt;/p&gt;"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="7155138616126795839">"&lt;p&gt;Buyekeza le-app ukusuka ku-&lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Le app ivame ukuthola izibuyekezo ukusuka ku-&lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. Ngokubuyekeza ukusuka kumthombo ohlukile, ungase uthole izibuyekezo zesikhathi esizayo ukusuka kunoma yimuphi umthombo efonini yakho. Ukusebenza ku-app kungashintsha.&lt;/p&gt;"</string>
     <string name="install_failed" msgid="5777824004474125469">"Uhlelo lokusebenza alufakiwe."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"Iphakheji livinjiwe kusukela ekufakweni."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"Uhlelo lokusebenza alufakiwe njengoba ukuphakheja kushayisana nephakheji elikhona."</string>
diff --git a/packages/PackageInstaller/res/values/strings.xml b/packages/PackageInstaller/res/values/strings.xml
index a2118fa..4eaa39b 100644
--- a/packages/PackageInstaller/res/values/strings.xml
+++ b/packages/PackageInstaller/res/values/strings.xml
@@ -38,7 +38,11 @@
     <!-- Message for updating an existing app [CHAR LIMIT=NONE] -->
     <string name="install_confirm_question_update">Do you want to update this app?</string>
     <!-- Message for updating an existing app with update owner reminder [CHAR LIMIT=NONE] -->
-    <string name="install_confirm_question_update_owner_reminder">Update this app from <xliff:g id="new_update_owner">%1$s</xliff:g>?\n\nThis app normally receives updates from <xliff:g id="existing_update_owner">%2$s</xliff:g>. By updating from a different source, you may receive future updates from any source on your phone. App functionality may change.</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet">&lt;p>Update this app from &lt;b><xliff:g id="new_update_owner">%1$s</xliff:g>&lt;/b>\?&lt;/p>&lt;p>This app normally receives updates from &lt;b><xliff:g id="existing_update_owner">%2$s</xliff:g>&lt;/b>. By updating from a different source, you may receive future updates from any source on your tablet. App functionality may change.&lt;/p></string>
+    <!-- Message for updating an existing app with update owner reminder [CHAR LIMIT=NONE] -->
+    <string name="install_confirm_question_update_owner_reminder" product="tv">&lt;p>Update this app from &lt;b><xliff:g id="new_update_owner">%1$s</xliff:g>&lt;/b>\?&lt;/p>&lt;p>This app normally receives updates from &lt;b><xliff:g id="existing_update_owner">%2$s</xliff:g>&lt;/b>. By updating from a different source, you may receive future updates from any source on your TV. App functionality may change.&lt;/p></string>
+    <!-- Message for updating an existing app with update owner reminder [CHAR LIMIT=NONE] -->
+    <string name="install_confirm_question_update_owner_reminder" product="default">&lt;p>Update this app from &lt;b><xliff:g id="new_update_owner">%1$s</xliff:g>&lt;/b>\?&lt;/p>&lt;p>This app normally receives updates from &lt;b><xliff:g id="existing_update_owner">%2$s</xliff:g>&lt;/b>. By updating from a different source, you may receive future updates from any source on your phone. App functionality may change.&lt;/p></string>
     <!-- [CHAR LIMIT=100] -->
     <string name="install_failed">App not installed.</string>
     <!-- Reason displayed when installation fails because the package was blocked
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/DeleteStagedFileOnResult.java b/packages/PackageInstaller/src/com/android/packageinstaller/DeleteStagedFileOnResult.java
index 7b17cbd..19d74b3 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/DeleteStagedFileOnResult.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/DeleteStagedFileOnResult.java
@@ -16,6 +16,8 @@
 
 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;
@@ -45,6 +47,9 @@
     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 3505cfb..74f04e0 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallFailed.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallFailed.java
@@ -33,8 +33,6 @@
 
 import androidx.annotation.Nullable;
 
-import java.io.File;
-
 /**
  * Installation failed: Return status code to the caller or display failure UI to user
  */
@@ -101,14 +99,8 @@
             // Set header icon and title
             PackageUtil.AppSnippet as;
             PackageManager pm = getPackageManager();
-
-            if ("package".equals(packageURI.getScheme())) {
-                as = new PackageUtil.AppSnippet(pm.getApplicationLabel(appInfo),
-                        pm.getApplicationIcon(appInfo));
-            } else {
-                final File sourceFile = new File(packageURI.getPath());
-                as = PackageUtil.getAppSnippet(this, appInfo, sourceFile);
-            }
+            as = intent.getParcelableExtra(PackageInstallerActivity.EXTRA_APP_SNIPPET,
+                    PackageUtil.AppSnippet.class);
 
             // Store label for dialog
             mLabel = as.label;
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java
index 7bea339..1088ace 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java
@@ -16,6 +16,7 @@
 
 package com.android.packageinstaller;
 
+import static com.android.packageinstaller.PackageInstallerActivity.EXTRA_APP_SNIPPET;
 import static com.android.packageinstaller.PackageInstallerActivity.EXTRA_STAGED_SESSION_ID;
 
 import android.app.PendingIntent;
@@ -86,7 +87,8 @@
             // ContentResolver.SCHEME_FILE
             // STAGED_SESSION_ID extra contains an ID of a previously staged install session.
             final File sourceFile = new File(mPackageURI.getPath());
-            PackageUtil.AppSnippet as = PackageUtil.getAppSnippet(this, appInfo, sourceFile);
+            PackageUtil.AppSnippet as = getIntent()
+                    .getParcelableExtra(EXTRA_APP_SNIPPET, PackageUtil.AppSnippet.class);
 
             mAlert.setIcon(as.icon);
             mAlert.setTitle(as.label);
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java
index 229b7a7..f2f019d9 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java
@@ -51,7 +51,6 @@
 public class InstallStart extends Activity {
     private static final String TAG = InstallStart.class.getSimpleName();
 
-    private static final String DOWNLOADS_AUTHORITY = "downloads";
 
     private static final int DLG_INSTALL_APPS_RESTRICTED_FOR_USER = 1;
     private static final int DLG_UNKNOWN_SOURCES_RESTRICTED_FOR_USER = 2;
@@ -103,6 +102,8 @@
 
         boolean isDocumentsManager = checkPermission(Manifest.permission.MANAGE_DOCUMENTS,
                 -1, callingUid) == PackageManager.PERMISSION_GRANTED;
+        boolean isSystemDownloadsProvider = PackageUtil.getSystemDownloadsProviderInfo(
+                                                mPackageManager, callingUid) != null;
         boolean isTrustedSource = false;
         if (sourceInfo != null && sourceInfo.isPrivilegedApp()) {
             isTrustedSource = intent.getBooleanExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, false) || (
@@ -111,7 +112,7 @@
                             == PackageManager.PERMISSION_GRANTED);
         }
 
-        if (!isTrustedSource && !isSystemDownloadsProvider(callingUid) && !isDocumentsManager
+        if (!isTrustedSource && !isSystemDownloadsProvider && !isDocumentsManager
                 && originatingUid != Process.INVALID_UID) {
             final int targetSdkVersion = getMaxTargetSdkVersionForUid(this, originatingUid);
             if (targetSdkVersion < 0) {
@@ -241,17 +242,6 @@
         return null;
     }
 
-    private boolean isSystemDownloadsProvider(int uid) {
-        final ProviderInfo downloadProviderPackage = getPackageManager().resolveContentProvider(
-                DOWNLOADS_AUTHORITY, 0);
-        if (downloadProviderPackage == null) {
-            // There seems to be no currently enabled downloads provider on the system.
-            return false;
-        }
-        final ApplicationInfo appInfo = downloadProviderPackage.applicationInfo;
-        return ((appInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0
-                && uid == appInfo.uid);
-    }
 
     @NonNull
     private boolean canPackageQuery(int callingUid, Uri packageUri) {
@@ -266,7 +256,7 @@
         if (callingPackages == null) {
             return false;
         }
-        for (String callingPackage: callingPackages) {
+        for (String callingPackage : callingPackages) {
             try {
                 if (mPackageManager.canPackageQuery(callingPackage, targetPackage)) {
                     return true;
@@ -279,9 +269,16 @@
     }
 
     private boolean isCallerSessionOwner(int originatingUid, int sessionId) {
+        if (originatingUid == Process.ROOT_UID) {
+            return true;
+        }
         PackageInstaller packageInstaller = getPackageManager().getPackageInstaller();
-        int installerUid = packageInstaller.getSessionInfo(sessionId).getInstallerUid();
-        return (originatingUid == Process.ROOT_UID) || (originatingUid == installerUid);
+        PackageInstaller.SessionInfo sessionInfo = packageInstaller.getSessionInfo(sessionId);
+        if (sessionInfo == null) {
+            return false;
+        }
+        int installerUid = sessionInfo.getInstallerUid();
+        return originatingUid == installerUid;
     }
 
     private void checkDevicePolicyRestriction() {
@@ -346,7 +343,6 @@
      * Create a new dialog.
      *
      * @param id The id of the dialog (determines dialog type)
-     *
      * @return The dialog
      */
     private DialogFragment createDialog(int id) {
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallSuccess.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallSuccess.java
index 73c03a5..fbc9525 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallSuccess.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallSuccess.java
@@ -17,13 +17,11 @@
 package com.android.packageinstaller;
 
 import android.app.Activity;
-import android.content.ActivityNotFoundException;
 import android.content.DialogInterface;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
-import android.net.Uri;
 import android.os.Bundle;
 import android.util.Log;
 import android.view.View;
@@ -31,7 +29,6 @@
 
 import androidx.annotation.Nullable;
 
-import java.io.File;
 import java.util.List;
 
 /**
@@ -66,18 +63,8 @@
             ApplicationInfo appInfo =
                     intent.getParcelableExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO);
             mAppPackageName = appInfo.packageName;
-            Uri packageURI = intent.getData();
-
-            // Set header icon and title
-            PackageManager pm = getPackageManager();
-
-            if ("package".equals(packageURI.getScheme())) {
-                mAppSnippet = new PackageUtil.AppSnippet(pm.getApplicationLabel(appInfo),
-                        pm.getApplicationIcon(appInfo));
-            } else {
-                File sourceFile = new File(packageURI.getPath());
-                mAppSnippet = PackageUtil.getAppSnippet(this, appInfo, sourceFile);
-            }
+            mAppSnippet = intent.getParcelableExtra(PackageInstallerActivity.EXTRA_APP_SNIPPET,
+                    PackageUtil.AppSnippet.class);
 
             mLaunchIntent = getPackageManager().getLaunchIntentForPackage(mAppPackageName);
 
@@ -123,11 +110,7 @@
         Button launchButton = mAlert.getButton(DialogInterface.BUTTON_POSITIVE);
         if (enabled) {
             launchButton.setOnClickListener(view -> {
-                try {
-                    startActivity(mLaunchIntent);
-                } catch (ActivityNotFoundException | SecurityException e) {
-                    Log.e(LOG_TAG, "Could not start activity", e);
-                }
+                setResult(Activity.RESULT_OK, mLaunchIntent);
                 finish();
             });
         } else {
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
index d154156..b66679a 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
@@ -16,8 +16,9 @@
 */
 package com.android.packageinstaller;
 
+import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
 import static android.content.Intent.FLAG_ACTIVITY_NO_HISTORY;
-import static android.content.Intent.FLAG_ACTIVITY_REORDER_TO_FRONT;
+import static android.content.Intent.FLAG_ACTIVITY_SINGLE_TOP;
 import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
 
 import android.Manifest;
@@ -48,6 +49,8 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
+import android.text.Html;
+import android.text.Spanned;
 import android.text.TextUtils;
 import android.util.Log;
 import android.view.View;
@@ -81,6 +84,7 @@
     static final String EXTRA_CALLING_ATTRIBUTION_TAG = "EXTRA_CALLING_ATTRIBUTION_TAG";
     static final String EXTRA_ORIGINAL_SOURCE_INFO = "EXTRA_ORIGINAL_SOURCE_INFO";
     static final String EXTRA_STAGED_SESSION_ID = "EXTRA_STAGED_SESSION_ID";
+    static final String EXTRA_APP_SNIPPET = "EXTRA_APP_SNIPPET";
     private static final String ALLOW_UNKNOWN_SOURCES_KEY =
             PackageInstallerActivity.class.getName() + "ALLOW_UNKNOWN_SOURCES_KEY";
 
@@ -143,9 +147,12 @@
             final CharSequence requestedUpdateOwnerLabel = getApplicationLabel(mCallingPackage);
             if (!TextUtils.isEmpty(existingUpdateOwnerLabel)
                     && mPendingUserActionReason == PackageInstaller.REASON_REMIND_OWNERSHIP) {
-                viewToEnable.setText(
+                String updateOwnerString =
                         getString(R.string.install_confirm_question_update_owner_reminder,
-                                requestedUpdateOwnerLabel, existingUpdateOwnerLabel));
+                                requestedUpdateOwnerLabel, existingUpdateOwnerLabel);
+                Spanned styledUpdateOwnerString =
+                        Html.fromHtml(updateOwnerString, Html.FROM_HTML_MODE_LEGACY);
+                viewToEnable.setText(styledUpdateOwnerString);
                 mOk.setText(R.string.update_anyway);
             } else {
                 mOk.setText(R.string.update);
@@ -259,6 +266,15 @@
     }
 
     private String getPackageNameForUid(int sourceUid) {
+        // 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
+        ApplicationInfo systemDownloadProviderInfo = PackageUtil.getSystemDownloadsProviderInfo(
+                                                        mPm, sourceUid);
+        if (systemDownloadProviderInfo != null) {
+            return systemDownloadProviderInfo.packageName;
+        }
         String[] packagesForUid = mPm.getPackagesForUid(sourceUid);
         if (packagesForUid == null) {
             return null;
@@ -693,6 +709,9 @@
         if (stagedSessionId > 0) {
             newIntent.putExtra(EXTRA_STAGED_SESSION_ID, stagedSessionId);
         }
+        if (mAppSnippet != null) {
+            newIntent.putExtra(EXTRA_APP_SNIPPET, mAppSnippet);
+        }
         newIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
         if (mLocalLOGV) Log.i(TAG, "downloaded app uri=" + mPackageURI);
         startActivity(newIntent);
@@ -789,7 +808,8 @@
             }
             new Handler(Looper.getMainLooper()).postDelayed(() -> {
                 if (!isDestroyed()) {
-                    startActivity(getIntent().addFlags(FLAG_ACTIVITY_REORDER_TO_FRONT));
+                    startActivity(getIntent().addFlags(
+                            FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_SINGLE_TOP));
                 }
             }, 500);
 
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java
index ff0e5fb..5880a29 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java
@@ -26,9 +26,15 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.ProviderInfo;
 import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
 import android.os.UserHandle;
 import android.util.Log;
 import android.view.View;
@@ -48,7 +54,7 @@
  * used in the package installer application.
  */
 public class PackageUtil {
-    private static final String LOG_TAG = PackageUtil.class.getSimpleName();
+    private static final String LOG_TAG = "PackageInstaller";
 
     public static final String PREFIX="com.android.packageinstaller.";
     public static final String INTENT_ATTR_INSTALL_STATUS = PREFIX+"installStatus";
@@ -56,6 +62,7 @@
     public static final String INTENT_ATTR_PERMISSIONS_LIST=PREFIX+"PermissionsList";
     //intent attribute strings related to uninstall
     public static final String INTENT_ATTR_PACKAGE_NAME=PREFIX+"PackageName";
+    private static final String DOWNLOADS_AUTHORITY = "downloads";
 
     /**
      * Utility method to get package information for a given {@link File}
@@ -115,7 +122,7 @@
                 icon);
     }
 
-    static final class AppSnippet {
+    static final class AppSnippet implements Parcelable {
         @NonNull public CharSequence label;
         @Nullable public Drawable icon;
         public AppSnippet(@NonNull CharSequence label, @Nullable Drawable icon) {
@@ -123,10 +130,52 @@
             this.icon = icon;
         }
 
+        private AppSnippet(Parcel in) {
+            label = in.readString();
+            Bitmap bmp = in.readParcelable(getClass().getClassLoader(), Bitmap.class);
+            icon = new BitmapDrawable(Resources.getSystem(), bmp);
+        }
+
         @Override
         public String toString() {
             return "AppSnippet[" + label + (icon != null ? "(has" : "(no ") + " icon)]";
         }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(@NonNull Parcel dest, int flags) {
+            dest.writeString(label.toString());
+            Bitmap bmp = getBitmapFromDrawable(icon);
+            dest.writeParcelable(bmp, 0);
+        }
+
+        private Bitmap getBitmapFromDrawable(Drawable drawable) {
+            // Create an empty bitmap with the dimensions of our drawable
+            final Bitmap bmp = Bitmap.createBitmap(drawable.getIntrinsicWidth(),
+                    drawable.getIntrinsicHeight(),
+                    Bitmap.Config.ARGB_8888);
+            // Associate it with a canvas. This canvas will draw the icon on the bitmap
+            final Canvas canvas = new Canvas(bmp);
+            // Draw the drawable in the canvas. The canvas will ultimately paint the drawable in the
+            // bitmap held within
+            drawable.draw(canvas);
+
+            return bmp;
+        }
+
+        public static final Parcelable.Creator<AppSnippet> CREATOR = new Parcelable.Creator<>() {
+            public AppSnippet createFromParcel(Parcel in) {
+                return new AppSnippet(in);
+            }
+
+            public AppSnippet[] newArray(int size) {
+                return new AppSnippet[size];
+            }
+        };
     }
 
     /**
@@ -245,4 +294,26 @@
             getActivity().finish();
         }
     }
+
+    /**
+     * Determines if the UID belongs to the system downloads provider and returns the
+     * {@link ApplicationInfo} of the provider
+     *
+     * @param uid UID of the caller
+     * @return {@link ApplicationInfo} of the provider if a downloads provider exists,
+     *          it is a system app, and its UID matches with the passed UID, null otherwise.
+     */
+    public static ApplicationInfo getSystemDownloadsProviderInfo(PackageManager pm, int uid) {
+        final ProviderInfo providerInfo = pm.resolveContentProvider(
+                DOWNLOADS_AUTHORITY, 0);
+        if (providerInfo == null) {
+            // There seems to be no currently enabled downloads provider on the system.
+            return null;
+        }
+        ApplicationInfo appInfo = providerInfo.applicationInfo;
+        if ((appInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0 && uid == appInfo.uid) {
+            return appInfo;
+        }
+        return null;
+    }
 }
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/UninstallUninstalling.java b/packages/PackageInstaller/src/com/android/packageinstaller/UninstallUninstalling.java
index e6710ff..4e28d77 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/UninstallUninstalling.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/UninstallUninstalling.java
@@ -190,10 +190,10 @@
 
             dialogBuilder.setCancelable(false);
             if (isCloneUser) {
-                dialogBuilder.setMessage(getActivity().getString(R.string.uninstalling_cloned_app,
+                dialogBuilder.setTitle(getActivity().getString(R.string.uninstalling_cloned_app,
                         ((UninstallUninstalling) getActivity()).mLabel));
             } else {
-                dialogBuilder.setMessage(getActivity().getString(R.string.uninstalling_app,
+                dialogBuilder.setTitle(getActivity().getString(R.string.uninstalling_app,
                         ((UninstallUninstalling) getActivity()).mLabel));
             }
 
diff --git a/packages/PrintSpooler/res/values/themes.xml b/packages/PrintSpooler/res/values/themes.xml
index 844e9c9..4dcad10 100644
--- a/packages/PrintSpooler/res/values/themes.xml
+++ b/packages/PrintSpooler/res/values/themes.xml
@@ -23,12 +23,14 @@
     <style name="Theme.SelectPrinterActivity"
            parent="android:style/Theme.DeviceDefault.Light">
         <item name="android:textAppearanceListItemSecondary">@style/ListItemSecondary</item>
+        <item name="android:windowLightStatusBar">true</item>
     </style>
 
     <style name="Theme.PrintActivity" parent="@android:style/Theme.DeviceDefault.Light">
         <item name="android:windowIsTranslucent">true</item>
         <item name="android:windowActionBar">false</item>
         <item name="android:windowNoTitle">true</item>
+        <item name="android:windowLightStatusBar">true</item>
     </style>
 
 </resources>
diff --git a/packages/SettingsLib/AppPreference/res/values-af/strings.xml b/packages/SettingsLib/AppPreference/res/values-af/strings.xml
new file mode 100644
index 0000000..442059c
--- /dev/null
+++ b/packages/SettingsLib/AppPreference/res/values-af/strings.xml
@@ -0,0 +1,21 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="install_type_instant" msgid="7217305006127216917">"Kitsprogram"</string>
+</resources>
diff --git a/packages/SettingsLib/AppPreference/res/values-am/strings.xml b/packages/SettingsLib/AppPreference/res/values-am/strings.xml
new file mode 100644
index 0000000..f5786b3
--- /dev/null
+++ b/packages/SettingsLib/AppPreference/res/values-am/strings.xml
@@ -0,0 +1,21 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="install_type_instant" msgid="7217305006127216917">"በቅጽበት መተግበሪያ"</string>
+</resources>
diff --git a/packages/SettingsLib/AppPreference/res/values-ar/strings.xml b/packages/SettingsLib/AppPreference/res/values-ar/strings.xml
new file mode 100644
index 0000000..024c0a6
--- /dev/null
+++ b/packages/SettingsLib/AppPreference/res/values-ar/strings.xml
@@ -0,0 +1,21 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="install_type_instant" msgid="7217305006127216917">"تطبيق فوري"</string>
+</resources>
diff --git a/packages/SettingsLib/AppPreference/res/values-as/strings.xml b/packages/SettingsLib/AppPreference/res/values-as/strings.xml
new file mode 100644
index 0000000..a7a666e
--- /dev/null
+++ b/packages/SettingsLib/AppPreference/res/values-as/strings.xml
@@ -0,0 +1,21 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="install_type_instant" msgid="7217305006127216917">"তাৎক্ষণিক এপ্‌"</string>
+</resources>
diff --git a/packages/SettingsLib/AppPreference/res/values-az/strings.xml b/packages/SettingsLib/AppPreference/res/values-az/strings.xml
new file mode 100644
index 0000000..8af282b
--- /dev/null
+++ b/packages/SettingsLib/AppPreference/res/values-az/strings.xml
@@ -0,0 +1,21 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="install_type_instant" msgid="7217305006127216917">"Ani tətbiq"</string>
+</resources>
diff --git a/packages/SettingsLib/AppPreference/res/values-b+sr+Latn/strings.xml b/packages/SettingsLib/AppPreference/res/values-b+sr+Latn/strings.xml
new file mode 100644
index 0000000..009cf22
--- /dev/null
+++ b/packages/SettingsLib/AppPreference/res/values-b+sr+Latn/strings.xml
@@ -0,0 +1,21 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="install_type_instant" msgid="7217305006127216917">"Instant aplikacija"</string>
+</resources>
diff --git a/packages/SettingsLib/AppPreference/res/values-be/strings.xml b/packages/SettingsLib/AppPreference/res/values-be/strings.xml
new file mode 100644
index 0000000..39babed
--- /dev/null
+++ b/packages/SettingsLib/AppPreference/res/values-be/strings.xml
@@ -0,0 +1,21 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="install_type_instant" msgid="7217305006127216917">"Імгненная праграма"</string>
+</resources>
diff --git a/packages/SettingsLib/AppPreference/res/values-bg/strings.xml b/packages/SettingsLib/AppPreference/res/values-bg/strings.xml
new file mode 100644
index 0000000..6df6483
--- /dev/null
+++ b/packages/SettingsLib/AppPreference/res/values-bg/strings.xml
@@ -0,0 +1,21 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="install_type_instant" msgid="7217305006127216917">"Мигновено приложение"</string>
+</resources>
diff --git a/packages/SettingsLib/AppPreference/res/values-bn/strings.xml b/packages/SettingsLib/AppPreference/res/values-bn/strings.xml
new file mode 100644
index 0000000..be1785e
--- /dev/null
+++ b/packages/SettingsLib/AppPreference/res/values-bn/strings.xml
@@ -0,0 +1,21 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="install_type_instant" msgid="7217305006127216917">"ইনস্ট্যান্ট অ্যাপ"</string>
+</resources>
diff --git a/packages/SettingsLib/AppPreference/res/values-bs/strings.xml b/packages/SettingsLib/AppPreference/res/values-bs/strings.xml
new file mode 100644
index 0000000..009cf22
--- /dev/null
+++ b/packages/SettingsLib/AppPreference/res/values-bs/strings.xml
@@ -0,0 +1,21 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="install_type_instant" msgid="7217305006127216917">"Instant aplikacija"</string>
+</resources>
diff --git a/packages/SettingsLib/AppPreference/res/values-ca/strings.xml b/packages/SettingsLib/AppPreference/res/values-ca/strings.xml
new file mode 100644
index 0000000..68b17cd
--- /dev/null
+++ b/packages/SettingsLib/AppPreference/res/values-ca/strings.xml
@@ -0,0 +1,21 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="install_type_instant" msgid="7217305006127216917">"Aplicació instantània"</string>
+</resources>
diff --git a/packages/SettingsLib/AppPreference/res/values-cs/strings.xml b/packages/SettingsLib/AppPreference/res/values-cs/strings.xml
new file mode 100644
index 0000000..a423b22
--- /dev/null
+++ b/packages/SettingsLib/AppPreference/res/values-cs/strings.xml
@@ -0,0 +1,21 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="install_type_instant" msgid="7217305006127216917">"Okamžitá aplikace"</string>
+</resources>
diff --git a/packages/SettingsLib/AppPreference/res/values-da/strings.xml b/packages/SettingsLib/AppPreference/res/values-da/strings.xml
new file mode 100644
index 0000000..c648449
--- /dev/null
+++ b/packages/SettingsLib/AppPreference/res/values-da/strings.xml
@@ -0,0 +1,21 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="install_type_instant" msgid="7217305006127216917">"Instant-app"</string>
+</resources>
diff --git a/packages/SettingsLib/AppPreference/res/values-de/strings.xml b/packages/SettingsLib/AppPreference/res/values-de/strings.xml
new file mode 100644
index 0000000..d48a9fa
--- /dev/null
+++ b/packages/SettingsLib/AppPreference/res/values-de/strings.xml
@@ -0,0 +1,21 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="install_type_instant" msgid="7217305006127216917">"Instant App"</string>
+</resources>
diff --git a/packages/SettingsLib/AppPreference/res/values-el/strings.xml b/packages/SettingsLib/AppPreference/res/values-el/strings.xml
new file mode 100644
index 0000000..ad834b1
--- /dev/null
+++ b/packages/SettingsLib/AppPreference/res/values-el/strings.xml
@@ -0,0 +1,21 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="install_type_instant" msgid="7217305006127216917">"Instant Εφαρμογή"</string>
+</resources>
diff --git a/packages/SettingsLib/AppPreference/res/values-en-rAU/strings.xml b/packages/SettingsLib/AppPreference/res/values-en-rAU/strings.xml
new file mode 100644
index 0000000..595fea3
--- /dev/null
+++ b/packages/SettingsLib/AppPreference/res/values-en-rAU/strings.xml
@@ -0,0 +1,21 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="install_type_instant" msgid="7217305006127216917">"Instant app"</string>
+</resources>
diff --git a/packages/SettingsLib/AppPreference/res/values-en-rCA/strings.xml b/packages/SettingsLib/AppPreference/res/values-en-rCA/strings.xml
new file mode 100644
index 0000000..595fea3
--- /dev/null
+++ b/packages/SettingsLib/AppPreference/res/values-en-rCA/strings.xml
@@ -0,0 +1,21 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="install_type_instant" msgid="7217305006127216917">"Instant app"</string>
+</resources>
diff --git a/packages/SettingsLib/AppPreference/res/values-en-rGB/strings.xml b/packages/SettingsLib/AppPreference/res/values-en-rGB/strings.xml
new file mode 100644
index 0000000..595fea3
--- /dev/null
+++ b/packages/SettingsLib/AppPreference/res/values-en-rGB/strings.xml
@@ -0,0 +1,21 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="install_type_instant" msgid="7217305006127216917">"Instant app"</string>
+</resources>
diff --git a/packages/SettingsLib/AppPreference/res/values-en-rIN/strings.xml b/packages/SettingsLib/AppPreference/res/values-en-rIN/strings.xml
new file mode 100644
index 0000000..595fea3
--- /dev/null
+++ b/packages/SettingsLib/AppPreference/res/values-en-rIN/strings.xml
@@ -0,0 +1,21 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="install_type_instant" msgid="7217305006127216917">"Instant app"</string>
+</resources>
diff --git a/packages/SettingsLib/AppPreference/res/values-en-rXC/strings.xml b/packages/SettingsLib/AppPreference/res/values-en-rXC/strings.xml
new file mode 100644
index 0000000..ba37e36
--- /dev/null
+++ b/packages/SettingsLib/AppPreference/res/values-en-rXC/strings.xml
@@ -0,0 +1,21 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="install_type_instant" msgid="7217305006127216917">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‎‎‎‎‏‎‏‎‎‏‎‎‎‎‎‏‎‎‏‎‏‎‏‎‏‎‎‎‎‎‎‏‎‏‏‏‎‏‏‏‎‎‎‏‏‏‏‎‎‏‎‎‎‏‎‏‎‏‎Instant app‎‏‎‎‏‎"</string>
+</resources>
diff --git a/packages/SettingsLib/AppPreference/res/values-es-rUS/strings.xml b/packages/SettingsLib/AppPreference/res/values-es-rUS/strings.xml
new file mode 100644
index 0000000..0d95fd9
--- /dev/null
+++ b/packages/SettingsLib/AppPreference/res/values-es-rUS/strings.xml
@@ -0,0 +1,21 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="install_type_instant" msgid="7217305006127216917">"App instantánea"</string>
+</resources>
diff --git a/packages/SettingsLib/AppPreference/res/values-es/strings.xml b/packages/SettingsLib/AppPreference/res/values-es/strings.xml
new file mode 100644
index 0000000..97fc538
--- /dev/null
+++ b/packages/SettingsLib/AppPreference/res/values-es/strings.xml
@@ -0,0 +1,21 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="install_type_instant" msgid="7217305006127216917">"Aplicación instantánea"</string>
+</resources>
diff --git a/packages/SettingsLib/AppPreference/res/values-et/strings.xml b/packages/SettingsLib/AppPreference/res/values-et/strings.xml
new file mode 100644
index 0000000..73fd742
--- /dev/null
+++ b/packages/SettingsLib/AppPreference/res/values-et/strings.xml
@@ -0,0 +1,21 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="install_type_instant" msgid="7217305006127216917">"Installimata avatav rakendus"</string>
+</resources>
diff --git a/packages/SettingsLib/AppPreference/res/values-eu/strings.xml b/packages/SettingsLib/AppPreference/res/values-eu/strings.xml
new file mode 100644
index 0000000..e35e113
--- /dev/null
+++ b/packages/SettingsLib/AppPreference/res/values-eu/strings.xml
@@ -0,0 +1,21 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="install_type_instant" msgid="7217305006127216917">"Zuzeneko aplikazioa"</string>
+</resources>
diff --git a/packages/SettingsLib/AppPreference/res/values-fa/strings.xml b/packages/SettingsLib/AppPreference/res/values-fa/strings.xml
new file mode 100644
index 0000000..d525e85
--- /dev/null
+++ b/packages/SettingsLib/AppPreference/res/values-fa/strings.xml
@@ -0,0 +1,21 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="install_type_instant" msgid="7217305006127216917">"برنامه فوری"</string>
+</resources>
diff --git a/packages/SettingsLib/AppPreference/res/values-fi/strings.xml b/packages/SettingsLib/AppPreference/res/values-fi/strings.xml
new file mode 100644
index 0000000..b3d564d
--- /dev/null
+++ b/packages/SettingsLib/AppPreference/res/values-fi/strings.xml
@@ -0,0 +1,21 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="install_type_instant" msgid="7217305006127216917">"Pikasovellus"</string>
+</resources>
diff --git a/packages/SettingsLib/AppPreference/res/values-fr-rCA/strings.xml b/packages/SettingsLib/AppPreference/res/values-fr-rCA/strings.xml
new file mode 100644
index 0000000..7be1e97
--- /dev/null
+++ b/packages/SettingsLib/AppPreference/res/values-fr-rCA/strings.xml
@@ -0,0 +1,21 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="install_type_instant" msgid="7217305006127216917">"Application instantanée"</string>
+</resources>
diff --git a/packages/SettingsLib/AppPreference/res/values-fr/strings.xml b/packages/SettingsLib/AppPreference/res/values-fr/strings.xml
new file mode 100644
index 0000000..4771382
--- /dev/null
+++ b/packages/SettingsLib/AppPreference/res/values-fr/strings.xml
@@ -0,0 +1,21 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="install_type_instant" msgid="7217305006127216917">"Appli instantanée"</string>
+</resources>
diff --git a/packages/SettingsLib/AppPreference/res/values-gl/strings.xml b/packages/SettingsLib/AppPreference/res/values-gl/strings.xml
new file mode 100644
index 0000000..97fc538
--- /dev/null
+++ b/packages/SettingsLib/AppPreference/res/values-gl/strings.xml
@@ -0,0 +1,21 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="install_type_instant" msgid="7217305006127216917">"Aplicación instantánea"</string>
+</resources>
diff --git a/packages/SettingsLib/AppPreference/res/values-gu/strings.xml b/packages/SettingsLib/AppPreference/res/values-gu/strings.xml
new file mode 100644
index 0000000..b58791c
--- /dev/null
+++ b/packages/SettingsLib/AppPreference/res/values-gu/strings.xml
@@ -0,0 +1,21 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="install_type_instant" msgid="7217305006127216917">"ઝટપટ ઍપ્લિકેશન"</string>
+</resources>
diff --git a/packages/SettingsLib/AppPreference/res/values-hi/strings.xml b/packages/SettingsLib/AppPreference/res/values-hi/strings.xml
new file mode 100644
index 0000000..8e890108
--- /dev/null
+++ b/packages/SettingsLib/AppPreference/res/values-hi/strings.xml
@@ -0,0 +1,21 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="install_type_instant" msgid="7217305006127216917">"इंस्टैंट ऐप्लिकेशन"</string>
+</resources>
diff --git a/packages/SettingsLib/AppPreference/res/values-hr/strings.xml b/packages/SettingsLib/AppPreference/res/values-hr/strings.xml
new file mode 100644
index 0000000..009cf22
--- /dev/null
+++ b/packages/SettingsLib/AppPreference/res/values-hr/strings.xml
@@ -0,0 +1,21 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="install_type_instant" msgid="7217305006127216917">"Instant aplikacija"</string>
+</resources>
diff --git a/packages/SettingsLib/AppPreference/res/values-hu/strings.xml b/packages/SettingsLib/AppPreference/res/values-hu/strings.xml
new file mode 100644
index 0000000..0aa7154
--- /dev/null
+++ b/packages/SettingsLib/AppPreference/res/values-hu/strings.xml
@@ -0,0 +1,21 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="install_type_instant" msgid="7217305006127216917">"Azonnali alkalmazás"</string>
+</resources>
diff --git a/packages/SettingsLib/AppPreference/res/values-hy/strings.xml b/packages/SettingsLib/AppPreference/res/values-hy/strings.xml
new file mode 100644
index 0000000..4ed6de5
--- /dev/null
+++ b/packages/SettingsLib/AppPreference/res/values-hy/strings.xml
@@ -0,0 +1,21 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="install_type_instant" msgid="7217305006127216917">"Ակնթարթային հավելված"</string>
+</resources>
diff --git a/packages/SettingsLib/AppPreference/res/values-in/strings.xml b/packages/SettingsLib/AppPreference/res/values-in/strings.xml
new file mode 100644
index 0000000..ccb16e7
--- /dev/null
+++ b/packages/SettingsLib/AppPreference/res/values-in/strings.xml
@@ -0,0 +1,21 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="install_type_instant" msgid="7217305006127216917">"Aplikasi instan"</string>
+</resources>
diff --git a/packages/SettingsLib/AppPreference/res/values-is/strings.xml b/packages/SettingsLib/AppPreference/res/values-is/strings.xml
new file mode 100644
index 0000000..0bdbbbc
--- /dev/null
+++ b/packages/SettingsLib/AppPreference/res/values-is/strings.xml
@@ -0,0 +1,21 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="install_type_instant" msgid="7217305006127216917">"Skyndiforrit"</string>
+</resources>
diff --git a/packages/SettingsLib/AppPreference/res/values-it/strings.xml b/packages/SettingsLib/AppPreference/res/values-it/strings.xml
new file mode 100644
index 0000000..5d200c4
--- /dev/null
+++ b/packages/SettingsLib/AppPreference/res/values-it/strings.xml
@@ -0,0 +1,21 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="install_type_instant" msgid="7217305006127216917">"App istantanea"</string>
+</resources>
diff --git a/packages/SettingsLib/AppPreference/res/values-iw/strings.xml b/packages/SettingsLib/AppPreference/res/values-iw/strings.xml
new file mode 100644
index 0000000..9048f51
--- /dev/null
+++ b/packages/SettingsLib/AppPreference/res/values-iw/strings.xml
@@ -0,0 +1,21 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="install_type_instant" msgid="7217305006127216917">"אפליקציה ללא התקנה"</string>
+</resources>
diff --git a/packages/SettingsLib/AppPreference/res/values-ja/strings.xml b/packages/SettingsLib/AppPreference/res/values-ja/strings.xml
new file mode 100644
index 0000000..d48a9fa
--- /dev/null
+++ b/packages/SettingsLib/AppPreference/res/values-ja/strings.xml
@@ -0,0 +1,21 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="install_type_instant" msgid="7217305006127216917">"Instant App"</string>
+</resources>
diff --git a/packages/SettingsLib/AppPreference/res/values-ka/strings.xml b/packages/SettingsLib/AppPreference/res/values-ka/strings.xml
new file mode 100644
index 0000000..bf94b4b
--- /dev/null
+++ b/packages/SettingsLib/AppPreference/res/values-ka/strings.xml
@@ -0,0 +1,21 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="install_type_instant" msgid="7217305006127216917">"მყისიერი აპი"</string>
+</resources>
diff --git a/packages/SettingsLib/AppPreference/res/values-kk/strings.xml b/packages/SettingsLib/AppPreference/res/values-kk/strings.xml
new file mode 100644
index 0000000..78ffbfe
--- /dev/null
+++ b/packages/SettingsLib/AppPreference/res/values-kk/strings.xml
@@ -0,0 +1,21 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="install_type_instant" msgid="7217305006127216917">"Лездік қолданба"</string>
+</resources>
diff --git a/packages/SettingsLib/AppPreference/res/values-km/strings.xml b/packages/SettingsLib/AppPreference/res/values-km/strings.xml
new file mode 100644
index 0000000..b60696d
--- /dev/null
+++ b/packages/SettingsLib/AppPreference/res/values-km/strings.xml
@@ -0,0 +1,21 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="install_type_instant" msgid="7217305006127216917">"កម្មវិធីប្រើភ្លាមៗ"</string>
+</resources>
diff --git a/packages/SettingsLib/AppPreference/res/values-kn/strings.xml b/packages/SettingsLib/AppPreference/res/values-kn/strings.xml
new file mode 100644
index 0000000..f1224e4
--- /dev/null
+++ b/packages/SettingsLib/AppPreference/res/values-kn/strings.xml
@@ -0,0 +1,21 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="install_type_instant" msgid="7217305006127216917">"ತತ್‌ಕ್ಷಣ ಆ್ಯಪ್"</string>
+</resources>
diff --git a/packages/SettingsLib/AppPreference/res/values-ko/strings.xml b/packages/SettingsLib/AppPreference/res/values-ko/strings.xml
new file mode 100644
index 0000000..0b592d7
--- /dev/null
+++ b/packages/SettingsLib/AppPreference/res/values-ko/strings.xml
@@ -0,0 +1,21 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="install_type_instant" msgid="7217305006127216917">"인스턴트 앱"</string>
+</resources>
diff --git a/packages/SettingsLib/AppPreference/res/values-ky/strings.xml b/packages/SettingsLib/AppPreference/res/values-ky/strings.xml
new file mode 100644
index 0000000..9a5bf8f
--- /dev/null
+++ b/packages/SettingsLib/AppPreference/res/values-ky/strings.xml
@@ -0,0 +1,21 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="install_type_instant" msgid="7217305006127216917">"Ыкчам ачылуучу колдонмо"</string>
+</resources>
diff --git a/packages/SettingsLib/AppPreference/res/values-lo/strings.xml b/packages/SettingsLib/AppPreference/res/values-lo/strings.xml
new file mode 100644
index 0000000..8d4c2fa
--- /dev/null
+++ b/packages/SettingsLib/AppPreference/res/values-lo/strings.xml
@@ -0,0 +1,21 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="install_type_instant" msgid="7217305006127216917">"ອິນສະແຕນແອັບ"</string>
+</resources>
diff --git a/packages/SettingsLib/AppPreference/res/values-lt/strings.xml b/packages/SettingsLib/AppPreference/res/values-lt/strings.xml
new file mode 100644
index 0000000..b7702ab
--- /dev/null
+++ b/packages/SettingsLib/AppPreference/res/values-lt/strings.xml
@@ -0,0 +1,21 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="install_type_instant" msgid="7217305006127216917">"Akimirksniu įkeliama programėlė"</string>
+</resources>
diff --git a/packages/SettingsLib/AppPreference/res/values-lv/strings.xml b/packages/SettingsLib/AppPreference/res/values-lv/strings.xml
new file mode 100644
index 0000000..5716188
--- /dev/null
+++ b/packages/SettingsLib/AppPreference/res/values-lv/strings.xml
@@ -0,0 +1,21 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="install_type_instant" msgid="7217305006127216917">"Tūlītējā lietotne"</string>
+</resources>
diff --git a/packages/SettingsLib/AppPreference/res/values-mk/strings.xml b/packages/SettingsLib/AppPreference/res/values-mk/strings.xml
new file mode 100644
index 0000000..9dacef2
--- /dev/null
+++ b/packages/SettingsLib/AppPreference/res/values-mk/strings.xml
@@ -0,0 +1,21 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="install_type_instant" msgid="7217305006127216917">"Инстант апликација"</string>
+</resources>
diff --git a/packages/SettingsLib/AppPreference/res/values-ml/strings.xml b/packages/SettingsLib/AppPreference/res/values-ml/strings.xml
new file mode 100644
index 0000000..e3b258e
--- /dev/null
+++ b/packages/SettingsLib/AppPreference/res/values-ml/strings.xml
@@ -0,0 +1,21 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="install_type_instant" msgid="7217305006127216917">"ഇൻസ്‌റ്റന്റ് ആപ്പ്"</string>
+</resources>
diff --git a/packages/SettingsLib/AppPreference/res/values-mn/strings.xml b/packages/SettingsLib/AppPreference/res/values-mn/strings.xml
new file mode 100644
index 0000000..b545ed6
--- /dev/null
+++ b/packages/SettingsLib/AppPreference/res/values-mn/strings.xml
@@ -0,0 +1,21 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="install_type_instant" msgid="7217305006127216917">"Шуурхай апп"</string>
+</resources>
diff --git a/packages/SettingsLib/AppPreference/res/values-mr/strings.xml b/packages/SettingsLib/AppPreference/res/values-mr/strings.xml
new file mode 100644
index 0000000..027b050
--- /dev/null
+++ b/packages/SettingsLib/AppPreference/res/values-mr/strings.xml
@@ -0,0 +1,21 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="install_type_instant" msgid="7217305006127216917">"इंस्टंट अ‍ॅप"</string>
+</resources>
diff --git a/packages/SettingsLib/AppPreference/res/values-ms/strings.xml b/packages/SettingsLib/AppPreference/res/values-ms/strings.xml
new file mode 100644
index 0000000..65742a0
--- /dev/null
+++ b/packages/SettingsLib/AppPreference/res/values-ms/strings.xml
@@ -0,0 +1,21 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="install_type_instant" msgid="7217305006127216917">"Apl segera"</string>
+</resources>
diff --git a/packages/SettingsLib/AppPreference/res/values-my/strings.xml b/packages/SettingsLib/AppPreference/res/values-my/strings.xml
new file mode 100644
index 0000000..2933fd7
--- /dev/null
+++ b/packages/SettingsLib/AppPreference/res/values-my/strings.xml
@@ -0,0 +1,21 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="install_type_instant" msgid="7217305006127216917">"အသင့်သုံးအက်ပ်"</string>
+</resources>
diff --git a/packages/SettingsLib/AppPreference/res/values-nb/strings.xml b/packages/SettingsLib/AppPreference/res/values-nb/strings.xml
new file mode 100644
index 0000000..c648449
--- /dev/null
+++ b/packages/SettingsLib/AppPreference/res/values-nb/strings.xml
@@ -0,0 +1,21 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="install_type_instant" msgid="7217305006127216917">"Instant-app"</string>
+</resources>
diff --git a/packages/SettingsLib/AppPreference/res/values-ne/strings.xml b/packages/SettingsLib/AppPreference/res/values-ne/strings.xml
new file mode 100644
index 0000000..9152882
--- /dev/null
+++ b/packages/SettingsLib/AppPreference/res/values-ne/strings.xml
@@ -0,0 +1,21 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="install_type_instant" msgid="7217305006127216917">"इन्स्टेन्ट एप"</string>
+</resources>
diff --git a/packages/SettingsLib/AppPreference/res/values-nl/strings.xml b/packages/SettingsLib/AppPreference/res/values-nl/strings.xml
new file mode 100644
index 0000000..c648449
--- /dev/null
+++ b/packages/SettingsLib/AppPreference/res/values-nl/strings.xml
@@ -0,0 +1,21 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="install_type_instant" msgid="7217305006127216917">"Instant-app"</string>
+</resources>
diff --git a/packages/SettingsLib/AppPreference/res/values-or/strings.xml b/packages/SettingsLib/AppPreference/res/values-or/strings.xml
new file mode 100644
index 0000000..a64fa89
--- /dev/null
+++ b/packages/SettingsLib/AppPreference/res/values-or/strings.xml
@@ -0,0 +1,21 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="install_type_instant" msgid="7217305006127216917">"ଇନଷ୍ଟାଣ୍ଟ ଆପ"</string>
+</resources>
diff --git a/packages/SettingsLib/AppPreference/res/values-pa/strings.xml b/packages/SettingsLib/AppPreference/res/values-pa/strings.xml
new file mode 100644
index 0000000..9d5b655
--- /dev/null
+++ b/packages/SettingsLib/AppPreference/res/values-pa/strings.xml
@@ -0,0 +1,21 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="install_type_instant" msgid="7217305006127216917">"ਤਤਕਾਲ ਐਪ"</string>
+</resources>
diff --git a/packages/SettingsLib/AppPreference/res/values-pl/strings.xml b/packages/SettingsLib/AppPreference/res/values-pl/strings.xml
new file mode 100644
index 0000000..a4b4046
--- /dev/null
+++ b/packages/SettingsLib/AppPreference/res/values-pl/strings.xml
@@ -0,0 +1,21 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="install_type_instant" msgid="7217305006127216917">"Aplikacja błyskawiczna"</string>
+</resources>
diff --git a/packages/SettingsLib/AppPreference/res/values-pt-rBR/strings.xml b/packages/SettingsLib/AppPreference/res/values-pt-rBR/strings.xml
new file mode 100644
index 0000000..6b0e049
--- /dev/null
+++ b/packages/SettingsLib/AppPreference/res/values-pt-rBR/strings.xml
@@ -0,0 +1,21 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="install_type_instant" msgid="7217305006127216917">"App instantâneo"</string>
+</resources>
diff --git a/packages/SettingsLib/AppPreference/res/values-pt-rPT/strings.xml b/packages/SettingsLib/AppPreference/res/values-pt-rPT/strings.xml
new file mode 100644
index 0000000..8fb9473
--- /dev/null
+++ b/packages/SettingsLib/AppPreference/res/values-pt-rPT/strings.xml
@@ -0,0 +1,21 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="install_type_instant" msgid="7217305006127216917">"App instantânea"</string>
+</resources>
diff --git a/packages/SettingsLib/AppPreference/res/values-pt/strings.xml b/packages/SettingsLib/AppPreference/res/values-pt/strings.xml
new file mode 100644
index 0000000..6b0e049
--- /dev/null
+++ b/packages/SettingsLib/AppPreference/res/values-pt/strings.xml
@@ -0,0 +1,21 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="install_type_instant" msgid="7217305006127216917">"App instantâneo"</string>
+</resources>
diff --git a/packages/SettingsLib/AppPreference/res/values-ro/strings.xml b/packages/SettingsLib/AppPreference/res/values-ro/strings.xml
new file mode 100644
index 0000000..820b45c5
--- /dev/null
+++ b/packages/SettingsLib/AppPreference/res/values-ro/strings.xml
@@ -0,0 +1,21 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="install_type_instant" msgid="7217305006127216917">"Aplicație instantanee"</string>
+</resources>
diff --git a/packages/SettingsLib/AppPreference/res/values-ru/strings.xml b/packages/SettingsLib/AppPreference/res/values-ru/strings.xml
new file mode 100644
index 0000000..64fbfa2
--- /dev/null
+++ b/packages/SettingsLib/AppPreference/res/values-ru/strings.xml
@@ -0,0 +1,21 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="install_type_instant" msgid="7217305006127216917">"Приложение с мгновенным запуском"</string>
+</resources>
diff --git a/packages/SettingsLib/AppPreference/res/values-si/strings.xml b/packages/SettingsLib/AppPreference/res/values-si/strings.xml
new file mode 100644
index 0000000..3307b4e
--- /dev/null
+++ b/packages/SettingsLib/AppPreference/res/values-si/strings.xml
@@ -0,0 +1,21 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="install_type_instant" msgid="7217305006127216917">"ක්ෂණික යෙදුම"</string>
+</resources>
diff --git a/packages/SettingsLib/AppPreference/res/values-sk/strings.xml b/packages/SettingsLib/AppPreference/res/values-sk/strings.xml
new file mode 100644
index 0000000..fc00b9f
--- /dev/null
+++ b/packages/SettingsLib/AppPreference/res/values-sk/strings.xml
@@ -0,0 +1,21 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="install_type_instant" msgid="7217305006127216917">"Okamžitá aplikácia"</string>
+</resources>
diff --git a/packages/SettingsLib/AppPreference/res/values-sl/strings.xml b/packages/SettingsLib/AppPreference/res/values-sl/strings.xml
new file mode 100644
index 0000000..4c4fddd
--- /dev/null
+++ b/packages/SettingsLib/AppPreference/res/values-sl/strings.xml
@@ -0,0 +1,21 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="install_type_instant" msgid="7217305006127216917">"Nenamestljiva aplikacija"</string>
+</resources>
diff --git a/packages/SettingsLib/AppPreference/res/values-sq/strings.xml b/packages/SettingsLib/AppPreference/res/values-sq/strings.xml
new file mode 100644
index 0000000..d6e9dd1
--- /dev/null
+++ b/packages/SettingsLib/AppPreference/res/values-sq/strings.xml
@@ -0,0 +1,21 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="install_type_instant" msgid="7217305006127216917">"Aplikacioni i çastit"</string>
+</resources>
diff --git a/packages/SettingsLib/AppPreference/res/values-sr/strings.xml b/packages/SettingsLib/AppPreference/res/values-sr/strings.xml
new file mode 100644
index 0000000..9dacef2
--- /dev/null
+++ b/packages/SettingsLib/AppPreference/res/values-sr/strings.xml
@@ -0,0 +1,21 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="install_type_instant" msgid="7217305006127216917">"Инстант апликација"</string>
+</resources>
diff --git a/packages/SettingsLib/AppPreference/res/values-sv/strings.xml b/packages/SettingsLib/AppPreference/res/values-sv/strings.xml
new file mode 100644
index 0000000..5ef5d7f
--- /dev/null
+++ b/packages/SettingsLib/AppPreference/res/values-sv/strings.xml
@@ -0,0 +1,21 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="install_type_instant" msgid="7217305006127216917">"Snabbapp"</string>
+</resources>
diff --git a/packages/SettingsLib/AppPreference/res/values-sw/strings.xml b/packages/SettingsLib/AppPreference/res/values-sw/strings.xml
new file mode 100644
index 0000000..2f045b0
--- /dev/null
+++ b/packages/SettingsLib/AppPreference/res/values-sw/strings.xml
@@ -0,0 +1,21 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="install_type_instant" msgid="7217305006127216917">"Programu inayofunguka papo hapo"</string>
+</resources>
diff --git a/packages/SettingsLib/AppPreference/res/values-ta/strings.xml b/packages/SettingsLib/AppPreference/res/values-ta/strings.xml
new file mode 100644
index 0000000..4760a07
--- /dev/null
+++ b/packages/SettingsLib/AppPreference/res/values-ta/strings.xml
@@ -0,0 +1,21 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="install_type_instant" msgid="7217305006127216917">"இன்ஸ்டண்ட் ஆப்ஸ்"</string>
+</resources>
diff --git a/packages/SettingsLib/AppPreference/res/values-te/strings.xml b/packages/SettingsLib/AppPreference/res/values-te/strings.xml
new file mode 100644
index 0000000..2f93c2a
--- /dev/null
+++ b/packages/SettingsLib/AppPreference/res/values-te/strings.xml
@@ -0,0 +1,21 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="install_type_instant" msgid="7217305006127216917">"ఇన్‌స్టంట్ యాప్"</string>
+</resources>
diff --git a/packages/SettingsLib/AppPreference/res/values-th/strings.xml b/packages/SettingsLib/AppPreference/res/values-th/strings.xml
new file mode 100644
index 0000000..d48a9fa
--- /dev/null
+++ b/packages/SettingsLib/AppPreference/res/values-th/strings.xml
@@ -0,0 +1,21 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="install_type_instant" msgid="7217305006127216917">"Instant App"</string>
+</resources>
diff --git a/packages/SettingsLib/AppPreference/res/values-tl/strings.xml b/packages/SettingsLib/AppPreference/res/values-tl/strings.xml
new file mode 100644
index 0000000..595fea3
--- /dev/null
+++ b/packages/SettingsLib/AppPreference/res/values-tl/strings.xml
@@ -0,0 +1,21 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="install_type_instant" msgid="7217305006127216917">"Instant app"</string>
+</resources>
diff --git a/packages/SettingsLib/AppPreference/res/values-tr/strings.xml b/packages/SettingsLib/AppPreference/res/values-tr/strings.xml
new file mode 100644
index 0000000..d90ce9c
--- /dev/null
+++ b/packages/SettingsLib/AppPreference/res/values-tr/strings.xml
@@ -0,0 +1,21 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="install_type_instant" msgid="7217305006127216917">"Hazır uygulama"</string>
+</resources>
diff --git a/packages/SettingsLib/AppPreference/res/values-uk/strings.xml b/packages/SettingsLib/AppPreference/res/values-uk/strings.xml
new file mode 100644
index 0000000..eff0e78
--- /dev/null
+++ b/packages/SettingsLib/AppPreference/res/values-uk/strings.xml
@@ -0,0 +1,21 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="install_type_instant" msgid="7217305006127216917">"Додаток із миттєвим запуском"</string>
+</resources>
diff --git a/packages/SettingsLib/AppPreference/res/values-ur/strings.xml b/packages/SettingsLib/AppPreference/res/values-ur/strings.xml
new file mode 100644
index 0000000..f62fe62
--- /dev/null
+++ b/packages/SettingsLib/AppPreference/res/values-ur/strings.xml
@@ -0,0 +1,21 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="install_type_instant" msgid="7217305006127216917">"فوری ایپ"</string>
+</resources>
diff --git a/packages/SettingsLib/AppPreference/res/values-uz/strings.xml b/packages/SettingsLib/AppPreference/res/values-uz/strings.xml
new file mode 100644
index 0000000..b9ac330
--- /dev/null
+++ b/packages/SettingsLib/AppPreference/res/values-uz/strings.xml
@@ -0,0 +1,21 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="install_type_instant" msgid="7217305006127216917">"Darhol ochiladigan ilova"</string>
+</resources>
diff --git a/packages/SettingsLib/AppPreference/res/values-vi/strings.xml b/packages/SettingsLib/AppPreference/res/values-vi/strings.xml
new file mode 100644
index 0000000..d23dad7
--- /dev/null
+++ b/packages/SettingsLib/AppPreference/res/values-vi/strings.xml
@@ -0,0 +1,21 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="install_type_instant" msgid="7217305006127216917">"Ứng dụng tức thì"</string>
+</resources>
diff --git a/packages/SettingsLib/AppPreference/res/values-zh-rCN/strings.xml b/packages/SettingsLib/AppPreference/res/values-zh-rCN/strings.xml
new file mode 100644
index 0000000..0a00c52
--- /dev/null
+++ b/packages/SettingsLib/AppPreference/res/values-zh-rCN/strings.xml
@@ -0,0 +1,21 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="install_type_instant" msgid="7217305006127216917">"免安装应用"</string>
+</resources>
diff --git a/packages/SettingsLib/AppPreference/res/values-zh-rHK/strings.xml b/packages/SettingsLib/AppPreference/res/values-zh-rHK/strings.xml
new file mode 100644
index 0000000..da93bfc
--- /dev/null
+++ b/packages/SettingsLib/AppPreference/res/values-zh-rHK/strings.xml
@@ -0,0 +1,21 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="install_type_instant" msgid="7217305006127216917">"免安裝應用程式"</string>
+</resources>
diff --git a/packages/SettingsLib/AppPreference/res/values-zh-rTW/strings.xml b/packages/SettingsLib/AppPreference/res/values-zh-rTW/strings.xml
new file mode 100644
index 0000000..da93bfc
--- /dev/null
+++ b/packages/SettingsLib/AppPreference/res/values-zh-rTW/strings.xml
@@ -0,0 +1,21 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="install_type_instant" msgid="7217305006127216917">"免安裝應用程式"</string>
+</resources>
diff --git a/packages/SettingsLib/AppPreference/res/values-zu/strings.xml b/packages/SettingsLib/AppPreference/res/values-zu/strings.xml
new file mode 100644
index 0000000..d73467c
--- /dev/null
+++ b/packages/SettingsLib/AppPreference/res/values-zu/strings.xml
@@ -0,0 +1,21 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="install_type_instant" msgid="7217305006127216917">"Ama-app asheshayo"</string>
+</resources>
diff --git a/packages/SettingsLib/BannerMessagePreference/res/values-te/strings.xml b/packages/SettingsLib/BannerMessagePreference/res/values-te/strings.xml
index 22a6f59..102fdc8 100644
--- a/packages/SettingsLib/BannerMessagePreference/res/values-te/strings.xml
+++ b/packages/SettingsLib/BannerMessagePreference/res/values-te/strings.xml
@@ -17,5 +17,5 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="accessibility_banner_message_dismiss" msgid="5272928723898304168">"విస్మరించు"</string>
+    <string name="accessibility_banner_message_dismiss" msgid="5272928723898304168">"విస్మరించండి"</string>
 </resources>
diff --git a/packages/SettingsLib/FooterPreference/res/values-kk/strings.xml b/packages/SettingsLib/FooterPreference/res/values-kk/strings.xml
index db11a76..dc3af28 100644
--- a/packages/SettingsLib/FooterPreference/res/values-kk/strings.xml
+++ b/packages/SettingsLib/FooterPreference/res/values-kk/strings.xml
@@ -17,5 +17,5 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="settingslib_learn_more_text" msgid="7385478101223578464">"Толығырақ"</string>
+    <string name="settingslib_learn_more_text" msgid="7385478101223578464">"Толық ақпарат"</string>
 </resources>
diff --git a/packages/SettingsLib/ProfileSelector/res/values-af/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-af/strings.xml
new file mode 100644
index 0000000..8055736
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/res/values-af/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_category_personal" msgid="1142302328104700620">"Persoonlik"</string>
+    <string name="settingslib_category_work" msgid="4867750733682444676">"Werk"</string>
+</resources>
diff --git a/packages/SettingsLib/ProfileSelector/res/values-am/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-am/strings.xml
new file mode 100644
index 0000000..4de6c61
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/res/values-am/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_category_personal" msgid="1142302328104700620">"የግል"</string>
+    <string name="settingslib_category_work" msgid="4867750733682444676">"ስራ"</string>
+</resources>
diff --git a/packages/SettingsLib/ProfileSelector/res/values-ar/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-ar/strings.xml
new file mode 100644
index 0000000..cae1f00
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/res/values-ar/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_category_personal" msgid="1142302328104700620">"شخصي"</string>
+    <string name="settingslib_category_work" msgid="4867750733682444676">"للعمل"</string>
+</resources>
diff --git a/packages/SettingsLib/ProfileSelector/res/values-as/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-as/strings.xml
new file mode 100644
index 0000000..9ac55bbe
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/res/values-as/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_category_personal" msgid="1142302328104700620">"ব্যক্তিগত"</string>
+    <string name="settingslib_category_work" msgid="4867750733682444676">"কৰ্মস্থান"</string>
+</resources>
diff --git a/packages/SettingsLib/ProfileSelector/res/values-az/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-az/strings.xml
new file mode 100644
index 0000000..5e9d3fb
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/res/values-az/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_category_personal" msgid="1142302328104700620">"Şəxsi"</string>
+    <string name="settingslib_category_work" msgid="4867750733682444676">"İş"</string>
+</resources>
diff --git a/packages/SettingsLib/ProfileSelector/res/values-b+sr+Latn/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-b+sr+Latn/strings.xml
new file mode 100644
index 0000000..7f9cf21
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/res/values-b+sr+Latn/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_category_personal" msgid="1142302328104700620">"Lično"</string>
+    <string name="settingslib_category_work" msgid="4867750733682444676">"Posao"</string>
+</resources>
diff --git a/packages/SettingsLib/ProfileSelector/res/values-be/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-be/strings.xml
new file mode 100644
index 0000000..b7774de
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/res/values-be/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_category_personal" msgid="1142302328104700620">"Асабістыя"</string>
+    <string name="settingslib_category_work" msgid="4867750733682444676">"Працоўныя"</string>
+</resources>
diff --git a/packages/SettingsLib/ProfileSelector/res/values-bg/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-bg/strings.xml
new file mode 100644
index 0000000..f1ca6b2
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/res/values-bg/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_category_personal" msgid="1142302328104700620">"Лични"</string>
+    <string name="settingslib_category_work" msgid="4867750733682444676">"Служебни"</string>
+</resources>
diff --git a/packages/SettingsLib/ProfileSelector/res/values-bn/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-bn/strings.xml
new file mode 100644
index 0000000..385a901
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/res/values-bn/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_category_personal" msgid="1142302328104700620">"ব্যক্তিগত"</string>
+    <string name="settingslib_category_work" msgid="4867750733682444676">"অফিস"</string>
+</resources>
diff --git a/packages/SettingsLib/ProfileSelector/res/values-bs/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-bs/strings.xml
new file mode 100644
index 0000000..19390c2
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/res/values-bs/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_category_personal" msgid="1142302328104700620">"Lično"</string>
+    <string name="settingslib_category_work" msgid="4867750733682444676">"Poslovno"</string>
+</resources>
diff --git a/packages/SettingsLib/ProfileSelector/res/values-ca/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-ca/strings.xml
new file mode 100644
index 0000000..0190b91
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/res/values-ca/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_category_personal" msgid="1142302328104700620">"Personal"</string>
+    <string name="settingslib_category_work" msgid="4867750733682444676">"Feina"</string>
+</resources>
diff --git a/packages/SettingsLib/ProfileSelector/res/values-cs/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-cs/strings.xml
new file mode 100644
index 0000000..d5f920a
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/res/values-cs/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_category_personal" msgid="1142302328104700620">"Osobní"</string>
+    <string name="settingslib_category_work" msgid="4867750733682444676">"Prácovní"</string>
+</resources>
diff --git a/packages/SettingsLib/ProfileSelector/res/values-da/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-da/strings.xml
new file mode 100644
index 0000000..9d41b9c
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/res/values-da/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_category_personal" msgid="1142302328104700620">"Personlig"</string>
+    <string name="settingslib_category_work" msgid="4867750733682444676">"Arbejde"</string>
+</resources>
diff --git a/packages/SettingsLib/ProfileSelector/res/values-de/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-de/strings.xml
new file mode 100644
index 0000000..d61ff96
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/res/values-de/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_category_personal" msgid="1142302328104700620">"Privat"</string>
+    <string name="settingslib_category_work" msgid="4867750733682444676">"Dienstlich"</string>
+</resources>
diff --git a/packages/SettingsLib/ProfileSelector/res/values-el/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-el/strings.xml
new file mode 100644
index 0000000..0832eab
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/res/values-el/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_category_personal" msgid="1142302328104700620">"Προσωπικά"</string>
+    <string name="settingslib_category_work" msgid="4867750733682444676">"Εργασία"</string>
+</resources>
diff --git a/packages/SettingsLib/ProfileSelector/res/values-en-rAU/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-en-rAU/strings.xml
new file mode 100644
index 0000000..478e603
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/res/values-en-rAU/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_category_personal" msgid="1142302328104700620">"Personal"</string>
+    <string name="settingslib_category_work" msgid="4867750733682444676">"Work"</string>
+</resources>
diff --git a/packages/SettingsLib/ProfileSelector/res/values-en-rCA/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-en-rCA/strings.xml
new file mode 100644
index 0000000..478e603
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/res/values-en-rCA/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_category_personal" msgid="1142302328104700620">"Personal"</string>
+    <string name="settingslib_category_work" msgid="4867750733682444676">"Work"</string>
+</resources>
diff --git a/packages/SettingsLib/ProfileSelector/res/values-en-rGB/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-en-rGB/strings.xml
new file mode 100644
index 0000000..478e603
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/res/values-en-rGB/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_category_personal" msgid="1142302328104700620">"Personal"</string>
+    <string name="settingslib_category_work" msgid="4867750733682444676">"Work"</string>
+</resources>
diff --git a/packages/SettingsLib/ProfileSelector/res/values-en-rIN/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-en-rIN/strings.xml
new file mode 100644
index 0000000..478e603
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/res/values-en-rIN/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_category_personal" msgid="1142302328104700620">"Personal"</string>
+    <string name="settingslib_category_work" msgid="4867750733682444676">"Work"</string>
+</resources>
diff --git a/packages/SettingsLib/ProfileSelector/res/values-en-rXC/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-en-rXC/strings.xml
new file mode 100644
index 0000000..89b7183
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/res/values-en-rXC/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_category_personal" msgid="1142302328104700620">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‎‏‏‏‏‏‏‎‏‏‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‏‎‏‎‎‏‎‎‏‏‎‎‏‏‎‎‏‎‎‎‎‏‎‎‏‎‏‎‏‏‎‎‏‏‎‎‎Personal‎‏‎‎‏‎"</string>
+    <string name="settingslib_category_work" msgid="4867750733682444676">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‎‎‎‏‏‏‎‎‎‏‏‎‏‏‎‏‏‏‎‎‏‏‎‎‎‎‎‎‎‎‏‏‏‏‎‎‎‎‏‎‎‏‎‏‎‎‎‎‎‏‎‎‏‏‎‎‎‎‏‎‎‎Work‎‏‎‎‏‎"</string>
+</resources>
diff --git a/packages/SettingsLib/ProfileSelector/res/values-es-rUS/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-es-rUS/strings.xml
new file mode 100644
index 0000000..b73026a
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/res/values-es-rUS/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_category_personal" msgid="1142302328104700620">"Personal"</string>
+    <string name="settingslib_category_work" msgid="4867750733682444676">"Trabajo"</string>
+</resources>
diff --git a/packages/SettingsLib/ProfileSelector/res/values-es/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-es/strings.xml
new file mode 100644
index 0000000..b73026a
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/res/values-es/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_category_personal" msgid="1142302328104700620">"Personal"</string>
+    <string name="settingslib_category_work" msgid="4867750733682444676">"Trabajo"</string>
+</resources>
diff --git a/packages/SettingsLib/ProfileSelector/res/values-et/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-et/strings.xml
new file mode 100644
index 0000000..e8fc44b
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/res/values-et/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_category_personal" msgid="1142302328104700620">"Isiklik"</string>
+    <string name="settingslib_category_work" msgid="4867750733682444676">"Töö"</string>
+</resources>
diff --git a/packages/SettingsLib/ProfileSelector/res/values-eu/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-eu/strings.xml
new file mode 100644
index 0000000..c22f4da
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/res/values-eu/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_category_personal" msgid="1142302328104700620">"Pertsonala"</string>
+    <string name="settingslib_category_work" msgid="4867750733682444676">"Lanekoa"</string>
+</resources>
diff --git a/packages/SettingsLib/ProfileSelector/res/values-fa/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-fa/strings.xml
new file mode 100644
index 0000000..6eaf057
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/res/values-fa/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_category_personal" msgid="1142302328104700620">"شخصی"</string>
+    <string name="settingslib_category_work" msgid="4867750733682444676">"محل کار"</string>
+</resources>
diff --git a/packages/SettingsLib/ProfileSelector/res/values-fi/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-fi/strings.xml
new file mode 100644
index 0000000..8d0b9bf
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/res/values-fi/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_category_personal" msgid="1142302328104700620">"Henkilökohtainen"</string>
+    <string name="settingslib_category_work" msgid="4867750733682444676">"Työ"</string>
+</resources>
diff --git a/packages/SettingsLib/ProfileSelector/res/values-fr-rCA/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-fr-rCA/strings.xml
new file mode 100644
index 0000000..43e4a59
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/res/values-fr-rCA/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_category_personal" msgid="1142302328104700620">"Personnel"</string>
+    <string name="settingslib_category_work" msgid="4867750733682444676">"Professionnel"</string>
+</resources>
diff --git a/packages/SettingsLib/ProfileSelector/res/values-fr/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-fr/strings.xml
new file mode 100644
index 0000000..43e4a59
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/res/values-fr/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_category_personal" msgid="1142302328104700620">"Personnel"</string>
+    <string name="settingslib_category_work" msgid="4867750733682444676">"Professionnel"</string>
+</resources>
diff --git a/packages/SettingsLib/ProfileSelector/res/values-gl/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-gl/strings.xml
new file mode 100644
index 0000000..364f15c
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/res/values-gl/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_category_personal" msgid="1142302328104700620">"Persoal"</string>
+    <string name="settingslib_category_work" msgid="4867750733682444676">"Traballo"</string>
+</resources>
diff --git a/packages/SettingsLib/ProfileSelector/res/values-gu/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-gu/strings.xml
new file mode 100644
index 0000000..4aba6db
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/res/values-gu/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_category_personal" msgid="1142302328104700620">"વ્યક્તિગત"</string>
+    <string name="settingslib_category_work" msgid="4867750733682444676">"ઑફિસ"</string>
+</resources>
diff --git a/packages/SettingsLib/ProfileSelector/res/values-hi/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-hi/strings.xml
new file mode 100644
index 0000000..dc7818f
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/res/values-hi/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_category_personal" msgid="1142302328104700620">"निजी ऐप्लिकेशन"</string>
+    <string name="settingslib_category_work" msgid="4867750733682444676">"वर्क"</string>
+</resources>
diff --git a/packages/SettingsLib/ProfileSelector/res/values-hr/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-hr/strings.xml
new file mode 100644
index 0000000..cb434a8
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/res/values-hr/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_category_personal" msgid="1142302328104700620">"Osobno"</string>
+    <string name="settingslib_category_work" msgid="4867750733682444676">"Posao"</string>
+</resources>
diff --git a/packages/SettingsLib/ProfileSelector/res/values-hu/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-hu/strings.xml
new file mode 100644
index 0000000..0127717
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/res/values-hu/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_category_personal" msgid="1142302328104700620">"Személyes"</string>
+    <string name="settingslib_category_work" msgid="4867750733682444676">"Munkahelyi"</string>
+</resources>
diff --git a/packages/SettingsLib/ProfileSelector/res/values-hy/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-hy/strings.xml
new file mode 100644
index 0000000..353a6d3
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/res/values-hy/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_category_personal" msgid="1142302328104700620">"Անձնական"</string>
+    <string name="settingslib_category_work" msgid="4867750733682444676">"Աշխատանքային"</string>
+</resources>
diff --git a/packages/SettingsLib/ProfileSelector/res/values-in/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-in/strings.xml
new file mode 100644
index 0000000..173d56f
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/res/values-in/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_category_personal" msgid="1142302328104700620">"Pribadi"</string>
+    <string name="settingslib_category_work" msgid="4867750733682444676">"Kerja"</string>
+</resources>
diff --git a/packages/SettingsLib/ProfileSelector/res/values-is/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-is/strings.xml
new file mode 100644
index 0000000..cc3bdd5
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/res/values-is/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_category_personal" msgid="1142302328104700620">"Persónulegt"</string>
+    <string name="settingslib_category_work" msgid="4867750733682444676">"Vinna"</string>
+</resources>
diff --git a/packages/SettingsLib/ProfileSelector/res/values-it/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-it/strings.xml
new file mode 100644
index 0000000..1d1e70e
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/res/values-it/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_category_personal" msgid="1142302328104700620">"Personale"</string>
+    <string name="settingslib_category_work" msgid="4867750733682444676">"Lavoro"</string>
+</resources>
diff --git a/packages/SettingsLib/ProfileSelector/res/values-iw/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-iw/strings.xml
new file mode 100644
index 0000000..4245145
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/res/values-iw/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_category_personal" msgid="1142302328104700620">"פרופיל אישי"</string>
+    <string name="settingslib_category_work" msgid="4867750733682444676">"פרופיל עבודה"</string>
+</resources>
diff --git a/packages/SettingsLib/ProfileSelector/res/values-ja/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-ja/strings.xml
new file mode 100644
index 0000000..f52d803
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/res/values-ja/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_category_personal" msgid="1142302328104700620">"個人用"</string>
+    <string name="settingslib_category_work" msgid="4867750733682444676">"仕事用"</string>
+</resources>
diff --git a/packages/SettingsLib/ProfileSelector/res/values-ka/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-ka/strings.xml
new file mode 100644
index 0000000..e93adff
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/res/values-ka/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_category_personal" msgid="1142302328104700620">"პირადი"</string>
+    <string name="settingslib_category_work" msgid="4867750733682444676">"სამსახური"</string>
+</resources>
diff --git a/packages/SettingsLib/ProfileSelector/res/values-kk/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-kk/strings.xml
new file mode 100644
index 0000000..94eab4d
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/res/values-kk/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_category_personal" msgid="1142302328104700620">"Жеке"</string>
+    <string name="settingslib_category_work" msgid="4867750733682444676">"Жұмыс істейтін"</string>
+</resources>
diff --git a/packages/SettingsLib/ProfileSelector/res/values-km/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-km/strings.xml
new file mode 100644
index 0000000..40fe4ff
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/res/values-km/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_category_personal" msgid="1142302328104700620">"ផ្ទាល់ខ្លួន"</string>
+    <string name="settingslib_category_work" msgid="4867750733682444676">"ការងារ"</string>
+</resources>
diff --git a/packages/SettingsLib/ProfileSelector/res/values-kn/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-kn/strings.xml
new file mode 100644
index 0000000..41b70e8
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/res/values-kn/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_category_personal" msgid="1142302328104700620">"ವೈಯಕ್ತಿಕ"</string>
+    <string name="settingslib_category_work" msgid="4867750733682444676">"ಕೆಲಸ"</string>
+</resources>
diff --git a/packages/SettingsLib/ProfileSelector/res/values-ko/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-ko/strings.xml
new file mode 100644
index 0000000..3577efc
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/res/values-ko/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_category_personal" msgid="1142302328104700620">"개인"</string>
+    <string name="settingslib_category_work" msgid="4867750733682444676">"업무"</string>
+</resources>
diff --git a/packages/SettingsLib/ProfileSelector/res/values-ky/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-ky/strings.xml
new file mode 100644
index 0000000..a65965c
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/res/values-ky/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_category_personal" msgid="1142302328104700620">"Жеке"</string>
+    <string name="settingslib_category_work" msgid="4867750733682444676">"Жумуш"</string>
+</resources>
diff --git a/packages/SettingsLib/ProfileSelector/res/values-lo/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-lo/strings.xml
new file mode 100644
index 0000000..1e47347
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/res/values-lo/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_category_personal" msgid="1142302328104700620">"ສ່ວນຕົວ"</string>
+    <string name="settingslib_category_work" msgid="4867750733682444676">"ບ່ອນເຮັດວຽກ"</string>
+</resources>
diff --git a/packages/SettingsLib/ProfileSelector/res/values-lt/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-lt/strings.xml
new file mode 100644
index 0000000..3ea9004
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/res/values-lt/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_category_personal" msgid="1142302328104700620">"Asmeninė"</string>
+    <string name="settingslib_category_work" msgid="4867750733682444676">"Darbo"</string>
+</resources>
diff --git a/packages/SettingsLib/ProfileSelector/res/values-lv/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-lv/strings.xml
new file mode 100644
index 0000000..528deeb
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/res/values-lv/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_category_personal" msgid="1142302328104700620">"Personīgais"</string>
+    <string name="settingslib_category_work" msgid="4867750733682444676">"Darba"</string>
+</resources>
diff --git a/packages/SettingsLib/ProfileSelector/res/values-mk/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-mk/strings.xml
new file mode 100644
index 0000000..773b3f1
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/res/values-mk/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_category_personal" msgid="1142302328104700620">"Лични"</string>
+    <string name="settingslib_category_work" msgid="4867750733682444676">"Работа"</string>
+</resources>
diff --git a/packages/SettingsLib/ProfileSelector/res/values-ml/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-ml/strings.xml
new file mode 100644
index 0000000..6ae94cc
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/res/values-ml/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_category_personal" msgid="1142302328104700620">"വ്യക്തിപരം"</string>
+    <string name="settingslib_category_work" msgid="4867750733682444676">"ഔദ്യോഗികം"</string>
+</resources>
diff --git a/packages/SettingsLib/ProfileSelector/res/values-mn/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-mn/strings.xml
new file mode 100644
index 0000000..e2361b7
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/res/values-mn/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_category_personal" msgid="1142302328104700620">"Хувийн"</string>
+    <string name="settingslib_category_work" msgid="4867750733682444676">"Ажил"</string>
+</resources>
diff --git a/packages/SettingsLib/ProfileSelector/res/values-mr/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-mr/strings.xml
new file mode 100644
index 0000000..0a48d0f
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/res/values-mr/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_category_personal" msgid="1142302328104700620">"वैयक्तिक"</string>
+    <string name="settingslib_category_work" msgid="4867750733682444676">"ऑफिस"</string>
+</resources>
diff --git a/packages/SettingsLib/ProfileSelector/res/values-ms/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-ms/strings.xml
new file mode 100644
index 0000000..607a290
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/res/values-ms/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_category_personal" msgid="1142302328104700620">"Peribadi"</string>
+    <string name="settingslib_category_work" msgid="4867750733682444676">"Kerja"</string>
+</resources>
diff --git a/packages/SettingsLib/ProfileSelector/res/values-my/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-my/strings.xml
new file mode 100644
index 0000000..a6f8d23
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/res/values-my/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_category_personal" msgid="1142302328104700620">"ကိုယ်ရေးကိုယ်တာ"</string>
+    <string name="settingslib_category_work" msgid="4867750733682444676">"အလုပ်"</string>
+</resources>
diff --git a/packages/SettingsLib/ProfileSelector/res/values-nb/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-nb/strings.xml
new file mode 100644
index 0000000..0a6ff7d
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/res/values-nb/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_category_personal" msgid="1142302328104700620">"Personlig"</string>
+    <string name="settingslib_category_work" msgid="4867750733682444676">"Jobb"</string>
+</resources>
diff --git a/packages/SettingsLib/ProfileSelector/res/values-ne/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-ne/strings.xml
new file mode 100644
index 0000000..7c0d9e6
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/res/values-ne/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_category_personal" msgid="1142302328104700620">"व्यक्तिगत"</string>
+    <string name="settingslib_category_work" msgid="4867750733682444676">"कामसम्बन्धी"</string>
+</resources>
diff --git a/packages/SettingsLib/ProfileSelector/res/values-nl/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-nl/strings.xml
new file mode 100644
index 0000000..932057f
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/res/values-nl/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_category_personal" msgid="1142302328104700620">"Persoonlijk"</string>
+    <string name="settingslib_category_work" msgid="4867750733682444676">"Werk"</string>
+</resources>
diff --git a/packages/SettingsLib/ProfileSelector/res/values-or/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-or/strings.xml
new file mode 100644
index 0000000..eea4177
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/res/values-or/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_category_personal" msgid="1142302328104700620">"ବ୍ୟକ୍ତିଗତ"</string>
+    <string name="settingslib_category_work" msgid="4867750733682444676">"ୱାର୍କ"</string>
+</resources>
diff --git a/packages/SettingsLib/ProfileSelector/res/values-pa/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-pa/strings.xml
new file mode 100644
index 0000000..48d915e
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/res/values-pa/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_category_personal" msgid="1142302328104700620">"ਨਿੱਜੀ"</string>
+    <string name="settingslib_category_work" msgid="4867750733682444676">"ਕਾਰਜ"</string>
+</resources>
diff --git a/packages/SettingsLib/ProfileSelector/res/values-pl/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-pl/strings.xml
new file mode 100644
index 0000000..8300df8
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/res/values-pl/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_category_personal" msgid="1142302328104700620">"Osobiste"</string>
+    <string name="settingslib_category_work" msgid="4867750733682444676">"Służbowe"</string>
+</resources>
diff --git a/packages/SettingsLib/ProfileSelector/res/values-pt-rBR/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-pt-rBR/strings.xml
new file mode 100644
index 0000000..6e98dc9
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/res/values-pt-rBR/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_category_personal" msgid="1142302328104700620">"Pessoal"</string>
+    <string name="settingslib_category_work" msgid="4867750733682444676">"Trabalho"</string>
+</resources>
diff --git a/packages/SettingsLib/ProfileSelector/res/values-pt-rPT/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-pt-rPT/strings.xml
new file mode 100644
index 0000000..a89bb04
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/res/values-pt-rPT/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_category_personal" msgid="1142302328104700620">"Pessoal"</string>
+    <string name="settingslib_category_work" msgid="4867750733682444676">"Profissional"</string>
+</resources>
diff --git a/packages/SettingsLib/ProfileSelector/res/values-pt/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-pt/strings.xml
new file mode 100644
index 0000000..6e98dc9
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/res/values-pt/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_category_personal" msgid="1142302328104700620">"Pessoal"</string>
+    <string name="settingslib_category_work" msgid="4867750733682444676">"Trabalho"</string>
+</resources>
diff --git a/packages/SettingsLib/ProfileSelector/res/values-ro/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-ro/strings.xml
new file mode 100644
index 0000000..317b4e3
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/res/values-ro/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_category_personal" msgid="1142302328104700620">"Personal"</string>
+    <string name="settingslib_category_work" msgid="4867750733682444676">"Serviciu"</string>
+</resources>
diff --git a/packages/SettingsLib/ProfileSelector/res/values-ru/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-ru/strings.xml
new file mode 100644
index 0000000..165fda1
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/res/values-ru/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_category_personal" msgid="1142302328104700620">"Личный профиль"</string>
+    <string name="settingslib_category_work" msgid="4867750733682444676">"Рабочий профиль"</string>
+</resources>
diff --git a/packages/SettingsLib/ProfileSelector/res/values-si/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-si/strings.xml
new file mode 100644
index 0000000..746e6e7
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/res/values-si/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_category_personal" msgid="1142302328104700620">"පුද්ගලික"</string>
+    <string name="settingslib_category_work" msgid="4867750733682444676">"කාර්ය"</string>
+</resources>
diff --git a/packages/SettingsLib/ProfileSelector/res/values-sk/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-sk/strings.xml
new file mode 100644
index 0000000..5e882b5
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/res/values-sk/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_category_personal" msgid="1142302328104700620">"Osobné"</string>
+    <string name="settingslib_category_work" msgid="4867750733682444676">"Pracovné"</string>
+</resources>
diff --git a/packages/SettingsLib/ProfileSelector/res/values-sl/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-sl/strings.xml
new file mode 100644
index 0000000..d239f44
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/res/values-sl/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_category_personal" msgid="1142302328104700620">"Osebno"</string>
+    <string name="settingslib_category_work" msgid="4867750733682444676">"Delo"</string>
+</resources>
diff --git a/packages/SettingsLib/ProfileSelector/res/values-sq/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-sq/strings.xml
new file mode 100644
index 0000000..84ce281
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/res/values-sq/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_category_personal" msgid="1142302328104700620">"Personale"</string>
+    <string name="settingslib_category_work" msgid="4867750733682444676">"Puna"</string>
+</resources>
diff --git a/packages/SettingsLib/ProfileSelector/res/values-sr/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-sr/strings.xml
new file mode 100644
index 0000000..70b4793
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/res/values-sr/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_category_personal" msgid="1142302328104700620">"Лично"</string>
+    <string name="settingslib_category_work" msgid="4867750733682444676">"Посао"</string>
+</resources>
diff --git a/packages/SettingsLib/ProfileSelector/res/values-sv/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-sv/strings.xml
new file mode 100644
index 0000000..8d1c657
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/res/values-sv/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_category_personal" msgid="1142302328104700620">"Privat"</string>
+    <string name="settingslib_category_work" msgid="4867750733682444676">"Arbete"</string>
+</resources>
diff --git a/packages/SettingsLib/ProfileSelector/res/values-sw/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-sw/strings.xml
new file mode 100644
index 0000000..63d150c
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/res/values-sw/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_category_personal" msgid="1142302328104700620">"Binafsi"</string>
+    <string name="settingslib_category_work" msgid="4867750733682444676">"Kazini"</string>
+</resources>
diff --git a/packages/SettingsLib/ProfileSelector/res/values-ta/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-ta/strings.xml
new file mode 100644
index 0000000..ab360a9
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/res/values-ta/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_category_personal" msgid="1142302328104700620">"தனிப்பட்டவை"</string>
+    <string name="settingslib_category_work" msgid="4867750733682444676">"பணி"</string>
+</resources>
diff --git a/packages/SettingsLib/ProfileSelector/res/values-te/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-te/strings.xml
new file mode 100644
index 0000000..9a44dcf
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/res/values-te/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_category_personal" msgid="1142302328104700620">"వ్యక్తిగతం"</string>
+    <string name="settingslib_category_work" msgid="4867750733682444676">"వర్క్"</string>
+</resources>
diff --git a/packages/SettingsLib/ProfileSelector/res/values-th/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-th/strings.xml
new file mode 100644
index 0000000..f35e8fc
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/res/values-th/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_category_personal" msgid="1142302328104700620">"ส่วนตัว"</string>
+    <string name="settingslib_category_work" msgid="4867750733682444676">"งาน"</string>
+</resources>
diff --git a/packages/SettingsLib/ProfileSelector/res/values-tl/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-tl/strings.xml
new file mode 100644
index 0000000..92b6f16
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/res/values-tl/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_category_personal" msgid="1142302328104700620">"Personal"</string>
+    <string name="settingslib_category_work" msgid="4867750733682444676">"Trabaho"</string>
+</resources>
diff --git a/packages/SettingsLib/ProfileSelector/res/values-tr/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-tr/strings.xml
new file mode 100644
index 0000000..680ebfe
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/res/values-tr/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_category_personal" msgid="1142302328104700620">"Kişisel"</string>
+    <string name="settingslib_category_work" msgid="4867750733682444676">"İş"</string>
+</resources>
diff --git a/packages/SettingsLib/ProfileSelector/res/values-uk/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-uk/strings.xml
new file mode 100644
index 0000000..953e72c
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/res/values-uk/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_category_personal" msgid="1142302328104700620">"Особисті"</string>
+    <string name="settingslib_category_work" msgid="4867750733682444676">"Робочі"</string>
+</resources>
diff --git a/packages/SettingsLib/ProfileSelector/res/values-ur/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-ur/strings.xml
new file mode 100644
index 0000000..336a7e0
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/res/values-ur/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_category_personal" msgid="1142302328104700620">"ذاتی"</string>
+    <string name="settingslib_category_work" msgid="4867750733682444676">"کام"</string>
+</resources>
diff --git a/packages/SettingsLib/ProfileSelector/res/values-uz/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-uz/strings.xml
new file mode 100644
index 0000000..5c1e4c0
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/res/values-uz/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_category_personal" msgid="1142302328104700620">"Shaxsiy"</string>
+    <string name="settingslib_category_work" msgid="4867750733682444676">"Ish"</string>
+</resources>
diff --git a/packages/SettingsLib/ProfileSelector/res/values-vi/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-vi/strings.xml
new file mode 100644
index 0000000..7e04838
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/res/values-vi/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_category_personal" msgid="1142302328104700620">"Cá nhân"</string>
+    <string name="settingslib_category_work" msgid="4867750733682444676">"Công việc"</string>
+</resources>
diff --git a/packages/SettingsLib/ProfileSelector/res/values-zh-rCN/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-zh-rCN/strings.xml
new file mode 100644
index 0000000..d5a1c03
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/res/values-zh-rCN/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_category_personal" msgid="1142302328104700620">"个人"</string>
+    <string name="settingslib_category_work" msgid="4867750733682444676">"工作"</string>
+</resources>
diff --git a/packages/SettingsLib/ProfileSelector/res/values-zh-rHK/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-zh-rHK/strings.xml
new file mode 100644
index 0000000..ec247c9
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/res/values-zh-rHK/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_category_personal" msgid="1142302328104700620">"個人"</string>
+    <string name="settingslib_category_work" msgid="4867750733682444676">"工作"</string>
+</resources>
diff --git a/packages/SettingsLib/ProfileSelector/res/values-zh-rTW/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-zh-rTW/strings.xml
new file mode 100644
index 0000000..ec247c9
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/res/values-zh-rTW/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_category_personal" msgid="1142302328104700620">"個人"</string>
+    <string name="settingslib_category_work" msgid="4867750733682444676">"工作"</string>
+</resources>
diff --git a/packages/SettingsLib/ProfileSelector/res/values-zu/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-zu/strings.xml
new file mode 100644
index 0000000..c53aba8
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/res/values-zu/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_category_personal" msgid="1142302328104700620">"Okomuntu siqu"</string>
+    <string name="settingslib_category_work" msgid="4867750733682444676">"Umsebenzi"</string>
+</resources>
diff --git a/packages/SettingsLib/SelectorWithWidgetPreference/res/values-or/strings.xml b/packages/SettingsLib/SelectorWithWidgetPreference/res/values-or/strings.xml
index 8b211e7..567c4bc 100644
--- a/packages/SettingsLib/SelectorWithWidgetPreference/res/values-or/strings.xml
+++ b/packages/SettingsLib/SelectorWithWidgetPreference/res/values-or/strings.xml
@@ -17,5 +17,5 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="settings_label" msgid="5948970810295631236">"ସେଟିଂସ୍"</string>
+    <string name="settings_label" msgid="5948970810295631236">"ସେଟିଂସ"</string>
 </resources>
diff --git a/packages/SettingsLib/Spa/OWNERS b/packages/SettingsLib/Spa/OWNERS
index 2887872..464328e 100644
--- a/packages/SettingsLib/Spa/OWNERS
+++ b/packages/SettingsLib/Spa/OWNERS
@@ -4,3 +4,6 @@
 hanxu@google.com
 kellyz@google.com
 pierreqian@google.com
+lijun@google.com
+songchenxi@google.com
+cyl@google.com
diff --git a/packages/SettingsLib/Spa/build.gradle b/packages/SettingsLib/Spa/build.gradle
deleted file mode 100644
index e68ef85..0000000
--- a/packages/SettingsLib/Spa/build.gradle
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * 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.
- */
-
-import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
-
-buildscript {
-    ext {
-        BUILD_TOOLS_VERSION = "30.0.3"
-        MIN_SDK = 21
-        TARGET_SDK = 33
-        jetpack_compose_version = '1.4.0-beta01'
-        jetpack_compose_compiler_version = '1.4.4'
-    }
-}
-plugins {
-    id 'com.android.application' version '8.0.0' apply false
-    id 'com.android.library' version '8.0.0' apply false
-    id 'org.jetbrains.kotlin.android' version '1.8.10' apply false
-}
-subprojects {
-    tasks.withType(KotlinCompile).configureEach {
-        kotlinOptions {
-            jvmTarget = "17"
-            freeCompilerArgs = ["-Xjvm-default=all"]
-        }
-    }
-}
diff --git a/packages/SettingsLib/Spa/build.gradle.kts b/packages/SettingsLib/Spa/build.gradle.kts
new file mode 100644
index 0000000..e76139f
--- /dev/null
+++ b/packages/SettingsLib/Spa/build.gradle.kts
@@ -0,0 +1,66 @@
+/*
+ * 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.
+ */
+
+import com.android.build.gradle.BaseExtension
+import com.android.build.gradle.api.AndroidBasePlugin
+import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+
+plugins {
+    alias(libs.plugins.android.application) apply false
+    alias(libs.plugins.android.library) apply false
+    alias(libs.plugins.kotlin.android) apply false
+}
+
+allprojects {
+    extra["jetpackComposeVersion"] = "1.6.0-alpha01"
+}
+
+subprojects {
+    plugins.withType<AndroidBasePlugin> {
+        configure<BaseExtension> {
+            compileSdkVersion(34)
+
+            defaultConfig {
+                minSdk = 21
+                targetSdk = 34
+            }
+
+            compileOptions {
+                sourceCompatibility = JavaVersion.VERSION_17
+                targetCompatibility = JavaVersion.VERSION_17
+            }
+        }
+    }
+
+    afterEvaluate {
+        plugins.withType<AndroidBasePlugin> {
+            configure<BaseExtension> {
+                if (buildFeatures.compose == true) {
+                    composeOptions {
+                        kotlinCompilerExtensionVersion = "1.4.4"
+                    }
+                }
+            }
+        }
+    }
+
+    tasks.withType<KotlinCompile> {
+        kotlinOptions {
+            jvmTarget = "17"
+            freeCompilerArgs = listOf("-Xjvm-default=all")
+        }
+    }
+}
diff --git a/packages/SettingsLib/Spa/gallery/Android.bp b/packages/SettingsLib/Spa/gallery/Android.bp
index bc083c9..e59e982 100644
--- a/packages/SettingsLib/Spa/gallery/Android.bp
+++ b/packages/SettingsLib/Spa/gallery/Android.bp
@@ -28,6 +28,6 @@
         "androidx.compose.runtime_runtime",
     ],
     kotlincflags: ["-Xjvm-default=all"],
-    platform_apis: true,
+    sdk_version: "current",
     min_sdk_version: "31",
 }
diff --git a/packages/SettingsLib/Spa/gallery/build.gradle b/packages/SettingsLib/Spa/gallery/build.gradle
deleted file mode 100644
index 212aa7b..0000000
--- a/packages/SettingsLib/Spa/gallery/build.gradle
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * 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.
- */
-
-plugins {
-    id 'com.android.application'
-    id 'kotlin-android'
-}
-
-android {
-    namespace 'com.android.settingslib.spa.gallery'
-    compileSdk TARGET_SDK
-    buildToolsVersion = BUILD_TOOLS_VERSION
-
-    defaultConfig {
-        applicationId "com.android.settingslib.spa.gallery"
-        minSdk MIN_SDK
-        targetSdk TARGET_SDK
-        versionCode 1
-        versionName "1.0"
-    }
-
-    sourceSets {
-        main {
-            kotlin {
-                srcDir "src"
-            }
-            res.srcDirs = ["res"]
-            manifest.srcFile "AndroidManifest.xml"
-        }
-    }
-    compileOptions {
-        sourceCompatibility JavaVersion.VERSION_17
-        targetCompatibility JavaVersion.VERSION_17
-    }
-    buildFeatures {
-        compose true
-    }
-    composeOptions {
-        kotlinCompilerExtensionVersion jetpack_compose_compiler_version
-    }
-}
-
-dependencies {
-    implementation project(":spa")
-}
diff --git a/packages/SettingsLib/Spa/gallery/build.gradle.kts b/packages/SettingsLib/Spa/gallery/build.gradle.kts
new file mode 100644
index 0000000..a1151a5
--- /dev/null
+++ b/packages/SettingsLib/Spa/gallery/build.gradle.kts
@@ -0,0 +1,45 @@
+/*
+ * 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.
+ */
+
+plugins {
+    alias(libs.plugins.android.application)
+    alias(libs.plugins.kotlin.android)
+}
+
+android {
+    namespace = "com.android.settingslib.spa.gallery"
+
+    defaultConfig {
+        applicationId = "com.android.settingslib.spa.gallery"
+        versionCode = 1
+        versionName = "1.0"
+    }
+
+    sourceSets {
+        sourceSets.getByName("main") {
+            kotlin.setSrcDirs(listOf("src"))
+            res.setSrcDirs(listOf("res"))
+            manifest.srcFile("AndroidManifest.xml")
+        }
+    }
+    buildFeatures {
+        compose = true
+    }
+}
+
+dependencies {
+    implementation(project(":spa"))
+}
diff --git a/packages/SettingsLib/Spa/gradle/libs.versions.toml b/packages/SettingsLib/Spa/gradle/libs.versions.toml
new file mode 100644
index 0000000..9a16df8
--- /dev/null
+++ b/packages/SettingsLib/Spa/gradle/libs.versions.toml
@@ -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.
+#
+
+[versions]
+agp = "8.0.2"
+dexmaker-mockito = "2.28.3"
+kotlin = "1.8.10"
+truth = "1.1"
+
+[libraries]
+dexmaker-mockito = { module = "com.linkedin.dexmaker:dexmaker-mockito", version.ref = "dexmaker-mockito" }
+truth = { module = "com.google.truth:truth", version.ref = "truth" }
+
+[plugins]
+android-application = { id = "com.android.application", version.ref = "agp" }
+android-library = { id = "com.android.library", version.ref = "agp" }
+kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
diff --git a/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties b/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties
index ed85e33..33f49e3 100644
--- a/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties
+++ b/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties
@@ -14,9 +14,8 @@
 # limitations under the License.
 #
 
-#Thu Jul 14 10:36:06 CST 2022
 distributionBase=GRADLE_USER_HOME
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.1-bin.zip
 distributionPath=wrapper/dists
 zipStorePath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
diff --git a/packages/SettingsLib/Spa/settings.gradle b/packages/SettingsLib/Spa/settings.gradle
deleted file mode 100644
index 1c5a1ce..0000000
--- a/packages/SettingsLib/Spa/settings.gradle
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * 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.
- */
-
-pluginManagement {
-    repositories {
-        gradlePluginPortal()
-        google()
-        mavenCentral()
-    }
-}
-dependencyResolutionManagement {
-    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
-    repositories {
-        google()
-        mavenCentral()
-        maven { url "https://jitpack.io"}
-    }
-}
-rootProject.name = "SpaLib"
-include ':spa'
-include ':gallery'
-include ':testutils'
diff --git a/packages/SettingsLib/Spa/settings.gradle.kts b/packages/SettingsLib/Spa/settings.gradle.kts
new file mode 100644
index 0000000..9909781
--- /dev/null
+++ b/packages/SettingsLib/Spa/settings.gradle.kts
@@ -0,0 +1,42 @@
+/*
+ * 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.
+ */
+
+pluginManagement {
+    repositories {
+        google()
+        mavenCentral()
+        gradlePluginPortal()
+    }
+}
+dependencyResolutionManagement {
+    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
+    rulesMode.set(RulesMode.FAIL_ON_PROJECT_RULES)
+
+    repositories {
+        google()
+        mavenCentral()
+        maven {
+            url = uri("https://jitpack.io")
+            content {
+                includeGroup("com.github.PhilJay")
+            }
+        }
+    }
+}
+rootProject.name = "SpaLib"
+include(":spa")
+include(":gallery")
+include(":testutils")
diff --git a/packages/SettingsLib/Spa/spa/Android.bp b/packages/SettingsLib/Spa/spa/Android.bp
index 139f3e1..79f8c46 100644
--- a/packages/SettingsLib/Spa/spa/Android.bp
+++ b/packages/SettingsLib/Spa/spa/Android.bp
@@ -43,6 +43,7 @@
     kotlincflags: [
         "-Xjvm-default=all",
     ],
+    sdk_version: "current",
     min_sdk_version: "31",
 }
 
diff --git a/packages/SettingsLib/Spa/spa/build.gradle b/packages/SettingsLib/Spa/spa/build.gradle
deleted file mode 100644
index a591366..0000000
--- a/packages/SettingsLib/Spa/spa/build.gradle
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * 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.
- */
-
-plugins {
-    id 'com.android.library'
-    id 'kotlin-android'
-}
-
-android {
-    namespace 'com.android.settingslib.spa'
-    compileSdk TARGET_SDK
-    buildToolsVersion = BUILD_TOOLS_VERSION
-
-    defaultConfig {
-        minSdk MIN_SDK
-        targetSdk TARGET_SDK
-
-        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
-    }
-
-    sourceSets {
-        main {
-            kotlin {
-                srcDir "src"
-            }
-            res.srcDirs = ["res"]
-            manifest.srcFile "AndroidManifest.xml"
-        }
-        androidTest {
-            kotlin {
-                srcDir "../tests/src"
-            }
-            res.srcDirs = ["../tests/res"]
-            manifest.srcFile "../tests/AndroidManifest.xml"
-        }
-    }
-    compileOptions {
-        sourceCompatibility JavaVersion.VERSION_17
-        targetCompatibility JavaVersion.VERSION_17
-    }
-    buildFeatures {
-        compose true
-    }
-    composeOptions {
-        kotlinCompilerExtensionVersion jetpack_compose_compiler_version
-    }
-    buildTypes {
-        debug {
-            testCoverageEnabled = true
-        }
-    }
-}
-
-dependencies {
-    api "androidx.appcompat:appcompat:1.7.0-alpha02"
-    api "androidx.slice:slice-builders:1.1.0-alpha02"
-    api "androidx.slice:slice-core:1.1.0-alpha02"
-    api "androidx.slice:slice-view:1.1.0-alpha02"
-    api "androidx.compose.material3:material3:1.1.0-alpha06"
-    api "androidx.compose.material:material-icons-extended:$jetpack_compose_version"
-    api "androidx.compose.runtime:runtime-livedata:$jetpack_compose_version"
-    api "androidx.compose.ui:ui-tooling-preview:$jetpack_compose_version"
-    api "androidx.lifecycle:lifecycle-livedata-ktx"
-    api "androidx.lifecycle:lifecycle-runtime-compose"
-    api "androidx.navigation:navigation-compose:2.6.0-alpha08"
-    api "com.github.PhilJay:MPAndroidChart:v3.1.0-alpha"
-    api "com.google.android.material:material:1.7.0-alpha03"
-    debugApi "androidx.compose.ui:ui-tooling:$jetpack_compose_version"
-    implementation "com.airbnb.android:lottie-compose:5.2.0"
-
-    androidTestImplementation project(":testutils")
-    androidTestImplementation 'androidx.lifecycle:lifecycle-runtime-testing'
-    androidTestImplementation "com.linkedin.dexmaker:dexmaker-mockito:2.28.3"
-}
-
-task coverageReport(type: JacocoReport, dependsOn: "connectedDebugAndroidTest") {
-    group = "Reporting"
-    description = "Generate Jacoco coverage reports after running tests."
-
-    sourceDirectories.from = files("src")
-    classDirectories.from = fileTree(
-            dir: "$buildDir/tmp/kotlin-classes/debug",
-            excludes: [
-                    "com/android/settingslib/spa/debug/**",
-
-                    // Excludes files forked from AndroidX.
-                    "com/android/settingslib/spa/widget/scaffold/CustomizedAppBar*",
-                    "com/android/settingslib/spa/widget/scaffold/TopAppBarColors*",
-
-                    // Excludes files forked from Accompanist.
-                    "com/android/settingslib/spa/framework/compose/DrawablePainter*",
-
-                    // Excludes inline functions, which is not covered in Jacoco reports.
-                    "com/android/settingslib/spa/framework/util/Collections*",
-                    "com/android/settingslib/spa/framework/util/Flows*",
-
-                    // Excludes debug functions
-                    "com/android/settingslib/spa/framework/compose/TimeMeasurer*",
-
-                    // Excludes slice demo presenter & provider
-                    "com/android/settingslib/spa/slice/presenter/Demo*",
-                    "com/android/settingslib/spa/slice/provider/Demo*",
-            ],
-    )
-    executionData.from = fileTree(dir: "$buildDir/outputs/code_coverage/debugAndroidTest/connected")
-}
diff --git a/packages/SettingsLib/Spa/spa/build.gradle.kts b/packages/SettingsLib/Spa/spa/build.gradle.kts
new file mode 100644
index 0000000..188e7f6
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/build.gradle.kts
@@ -0,0 +1,110 @@
+/*
+ * 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.
+ */
+
+plugins {
+    alias(libs.plugins.android.library)
+    alias(libs.plugins.kotlin.android)
+    jacoco
+}
+
+val jetpackComposeVersion: String? by extra
+
+android {
+    namespace = "com.android.settingslib.spa"
+
+    defaultConfig {
+        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+    }
+
+    sourceSets {
+        sourceSets.getByName("main") {
+            kotlin.setSrcDirs(listOf("src"))
+            res.setSrcDirs(listOf("res"))
+            manifest.srcFile("AndroidManifest.xml")
+        }
+        sourceSets.getByName("androidTest") {
+            kotlin.setSrcDirs(listOf("../tests/src"))
+            res.setSrcDirs(listOf("../tests/res"))
+            manifest.srcFile("../tests/AndroidManifest.xml")
+        }
+    }
+    buildFeatures {
+        compose = true
+    }
+    buildTypes {
+        getByName("debug") {
+            enableAndroidTestCoverage = true
+        }
+    }
+}
+
+dependencies {
+    api("androidx.appcompat:appcompat:1.7.0-alpha02")
+    api("androidx.slice:slice-builders:1.1.0-alpha02")
+    api("androidx.slice:slice-core:1.1.0-alpha02")
+    api("androidx.slice:slice-view:1.1.0-alpha02")
+    api("androidx.compose.material3:material3:1.2.0-alpha03")
+    api("androidx.compose.material:material-icons-extended:$jetpackComposeVersion")
+    api("androidx.compose.runtime:runtime-livedata:$jetpackComposeVersion")
+    api("androidx.compose.ui:ui-tooling-preview:$jetpackComposeVersion")
+    api("androidx.lifecycle:lifecycle-livedata-ktx")
+    api("androidx.lifecycle:lifecycle-runtime-compose")
+    api("androidx.navigation:navigation-compose:2.7.0-beta01")
+    api("com.github.PhilJay:MPAndroidChart:v3.1.0-alpha")
+    api("com.google.android.material:material:1.7.0-alpha03")
+    debugApi("androidx.compose.ui:ui-tooling:$jetpackComposeVersion")
+    implementation("com.airbnb.android:lottie-compose:5.2.0")
+
+    androidTestImplementation(project(":testutils"))
+    androidTestImplementation(libs.dexmaker.mockito)
+}
+
+tasks.register<JacocoReport>("coverageReport") {
+    group = "Reporting"
+    description = "Generate Jacoco coverage reports after running tests."
+    dependsOn("connectedDebugAndroidTest")
+    sourceDirectories.setFrom(files("src"))
+    classDirectories.setFrom(
+        fileTree(layout.buildDirectory.dir("tmp/kotlin-classes/debug")) {
+            setExcludes(
+                listOf(
+                    "com/android/settingslib/spa/debug/**",
+
+                    // Excludes files forked from AndroidX.
+                    "com/android/settingslib/spa/widget/scaffold/CustomizedAppBar*",
+                    "com/android/settingslib/spa/widget/scaffold/TopAppBarColors*",
+
+                    // Excludes files forked from Accompanist.
+                    "com/android/settingslib/spa/framework/compose/DrawablePainter*",
+
+                    // Excludes inline functions, which is not covered in Jacoco reports.
+                    "com/android/settingslib/spa/framework/util/Collections*",
+                    "com/android/settingslib/spa/framework/util/Flows*",
+
+                    // Excludes debug functions
+                    "com/android/settingslib/spa/framework/compose/TimeMeasurer*",
+
+                    // Excludes slice demo presenter & provider
+                    "com/android/settingslib/spa/slice/presenter/Demo*",
+                    "com/android/settingslib/spa/slice/provider/Demo*",
+                )
+            )
+        }
+    )
+    executionData.setFrom(
+        fileTree(layout.buildDirectory.dir("outputs/code_coverage/debugAndroidTest/connected"))
+    )
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
index 0f5862a..08e3a27 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
@@ -14,8 +14,6 @@
  * limitations under the License.
  */
 
-@file:OptIn(ExperimentalAnimationApi::class)
-
 package com.android.settingslib.spa.framework
 
 import android.content.Intent
@@ -24,20 +22,17 @@
 import androidx.activity.ComponentActivity
 import androidx.activity.compose.setContent
 import androidx.annotation.VisibleForTesting
-import androidx.compose.animation.AnimatedContentScope
-import androidx.compose.animation.ExperimentalAnimationApi
-import androidx.compose.animation.core.tween
-import androidx.compose.animation.fadeIn
-import androidx.compose.animation.fadeOut
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.saveable.rememberSaveable
-import androidx.compose.ui.unit.IntOffset
 import androidx.core.view.WindowCompat
 import androidx.navigation.NavGraph.Companion.findStartDestination
+import androidx.navigation.compose.NavHost
+import androidx.navigation.compose.composable
+import androidx.navigation.compose.rememberNavController
 import com.android.settingslib.spa.R
 import com.android.settingslib.spa.framework.common.LogCategory
 import com.android.settingslib.spa.framework.common.NullPageProvider
@@ -46,12 +41,10 @@
 import com.android.settingslib.spa.framework.common.SettingsPageProviderRepository
 import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
 import com.android.settingslib.spa.framework.common.createSettingsPage
-import com.android.settingslib.spa.framework.compose.AnimatedNavHost
 import com.android.settingslib.spa.framework.compose.LocalNavController
 import com.android.settingslib.spa.framework.compose.NavControllerWrapperImpl
-import com.android.settingslib.spa.framework.compose.composable
+import com.android.settingslib.spa.framework.compose.animatedComposable
 import com.android.settingslib.spa.framework.compose.localNavController
-import com.android.settingslib.spa.framework.compose.rememberAnimatedNavController
 import com.android.settingslib.spa.framework.theme.SettingsTheme
 import com.android.settingslib.spa.framework.util.PageLogger
 import com.android.settingslib.spa.framework.util.getDestination
@@ -108,7 +101,7 @@
     isPageEnabled: (SettingsPage) -> Boolean,
     initialIntent: Intent?,
 ) {
-    val navController = rememberAnimatedNavController()
+    val navController = rememberNavController()
     CompositionLocalProvider(navController.localNavController()) {
         val controller = LocalNavController.current as NavControllerWrapperImpl
         controller.NavContent(sppRepository.getAllProviders()) { page ->
@@ -133,37 +126,15 @@
     allProvider: Collection<SettingsPageProvider>,
     content: @Composable (SettingsPage) -> Unit,
 ) {
-    AnimatedNavHost(
+    NavHost(
         navController = navController,
         startDestination = NullPageProvider.name,
     ) {
-        val slideEffect = tween<IntOffset>(durationMillis = 300)
-        val fadeEffect = tween<Float>(durationMillis = 300)
         composable(NullPageProvider.name) {}
         for (spp in allProvider) {
-            composable(
+            animatedComposable(
                 route = spp.name + spp.parameter.navRoute(),
                 arguments = spp.parameter,
-                enterTransition = {
-                    slideIntoContainer(
-                        AnimatedContentScope.SlideDirection.Start, animationSpec = slideEffect
-                    ) + fadeIn(animationSpec = fadeEffect)
-                },
-                exitTransition = {
-                    slideOutOfContainer(
-                        AnimatedContentScope.SlideDirection.Start, animationSpec = slideEffect
-                    ) + fadeOut(animationSpec = fadeEffect)
-                },
-                popEnterTransition = {
-                    slideIntoContainer(
-                        AnimatedContentScope.SlideDirection.End, animationSpec = slideEffect
-                    ) + fadeIn(animationSpec = fadeEffect)
-                },
-                popExitTransition = {
-                    slideOutOfContainer(
-                        AnimatedContentScope.SlideDirection.End, animationSpec = slideEffect
-                    ) + fadeOut(animationSpec = fadeEffect)
-                },
             ) { navBackStackEntry ->
                 val page = remember { spp.createSettingsPage(navBackStackEntry.arguments) }
                 content(page)
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/AnimatedComposeNavigator.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/AnimatedComposeNavigator.kt
deleted file mode 100644
index 930a83f..0000000
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/AnimatedComposeNavigator.kt
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.animation.AnimatedVisibilityScope
-import androidx.compose.animation.ExperimentalAnimationApi
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.mutableStateOf
-import androidx.navigation.NavBackStackEntry
-import androidx.navigation.NavDestination
-import androidx.navigation.NavOptions
-import androidx.navigation.Navigator
-
-/**
- * Navigator that navigates through [Composable]s. Every destination using this Navigator must
- * set a valid [Composable] by setting it directly on an instantiated [Destination] or calling
- * [composable].
- */
-@ExperimentalAnimationApi
-@Navigator.Name("animatedComposable")
-public class AnimatedComposeNavigator : Navigator<AnimatedComposeNavigator.Destination>() {
-    internal val transitionsInProgress get() = state.transitionsInProgress
-
-    internal val backStack get() = state.backStack
-
-    internal val isPop = mutableStateOf(false)
-
-    override fun navigate(
-        entries: List<NavBackStackEntry>,
-        navOptions: NavOptions?,
-        navigatorExtras: Extras?
-    ) {
-        entries.forEach { entry ->
-            state.pushWithTransition(entry)
-        }
-        isPop.value = false
-    }
-
-    override fun createDestination(): Destination {
-        return Destination(this, content = { })
-    }
-
-    override fun popBackStack(popUpTo: NavBackStackEntry, savedState: Boolean) {
-        state.popWithTransition(popUpTo, savedState)
-        isPop.value = true
-    }
-
-    internal fun markTransitionComplete(entry: NavBackStackEntry) {
-        state.markTransitionComplete(entry)
-    }
-
-    /**
-     * NavDestination specific to [AnimatedComposeNavigator]
-     */
-    @ExperimentalAnimationApi
-    @NavDestination.ClassType(Composable::class)
-    public class Destination(
-        navigator: AnimatedComposeNavigator,
-        internal val content: @Composable AnimatedVisibilityScope.(NavBackStackEntry) -> Unit
-    ) : NavDestination(navigator)
-
-    internal companion object {
-        internal const val NAME = "animatedComposable"
-    }
-}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/AnimatedNavGraphBuilder.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/AnimatedNavGraphBuilder.kt
new file mode 100644
index 0000000..192b125
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/AnimatedNavGraphBuilder.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.settingslib.spa.framework.compose
+
+import androidx.compose.animation.AnimatedContentScope
+import androidx.compose.animation.AnimatedContentTransitionScope
+import androidx.compose.animation.core.FastOutLinearInEasing
+import androidx.compose.animation.core.LinearOutSlowInEasing
+import androidx.compose.animation.core.tween
+import androidx.compose.animation.fadeIn
+import androidx.compose.animation.fadeOut
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.unit.IntOffset
+import androidx.navigation.NamedNavArgument
+import androidx.navigation.NavBackStackEntry
+import androidx.navigation.NavDeepLink
+import androidx.navigation.NavGraphBuilder
+import androidx.navigation.compose.composable
+
+/**
+ * Add the [Composable] to the [NavGraphBuilder] with animation
+ *
+ * @param route route for the destination
+ * @param arguments list of arguments to associate with destination
+ * @param deepLinks list of deep links to associate with the destinations
+ * @param content composable for the destination
+ */
+internal fun NavGraphBuilder.animatedComposable(
+    route: String,
+    arguments: List<NamedNavArgument> = emptyList(),
+    deepLinks: List<NavDeepLink> = emptyList(),
+    content: @Composable AnimatedContentScope.(NavBackStackEntry) -> Unit,
+) = composable(
+    route = route,
+    arguments = arguments,
+    deepLinks = deepLinks,
+    enterTransition = {
+        slideIntoContainer(
+            towards = AnimatedContentTransitionScope.SlideDirection.Start,
+            animationSpec = slideInEffect,
+            initialOffset = offsetFunc,
+        ) + fadeIn(animationSpec = fadeInEffect)
+    },
+    exitTransition = {
+        slideOutOfContainer(
+            towards = AnimatedContentTransitionScope.SlideDirection.Start,
+            animationSpec = slideOutEffect,
+            targetOffset = offsetFunc,
+        ) + fadeOut(animationSpec = fadeOutEffect)
+    },
+    popEnterTransition = {
+        slideIntoContainer(
+            towards = AnimatedContentTransitionScope.SlideDirection.End,
+            animationSpec = slideInEffect,
+            initialOffset = offsetFunc,
+        ) + fadeIn(animationSpec = fadeInEffect)
+    },
+    popExitTransition = {
+        slideOutOfContainer(
+            towards = AnimatedContentTransitionScope.SlideDirection.End,
+            animationSpec = slideOutEffect,
+            targetOffset = offsetFunc,
+        ) + fadeOut(animationSpec = fadeOutEffect)
+    },
+    content = content,
+)
+
+private const val FADE_OUT_MILLIS = 75
+private const val FADE_IN_MILLIS = 300
+
+private val slideInEffect = tween<IntOffset>(
+    durationMillis = FADE_IN_MILLIS,
+    delayMillis = FADE_OUT_MILLIS,
+    easing = LinearOutSlowInEasing,
+)
+private val slideOutEffect = tween<IntOffset>(durationMillis = FADE_IN_MILLIS)
+private val fadeOutEffect = tween<Float>(
+    durationMillis = FADE_OUT_MILLIS,
+    easing = FastOutLinearInEasing,
+)
+private val fadeInEffect = tween<Float>(
+    durationMillis = FADE_IN_MILLIS,
+    delayMillis = FADE_OUT_MILLIS,
+    easing = LinearOutSlowInEasing,
+)
+private val offsetFunc: (offsetForFullSlide: Int) -> Int = { it.div(5) }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/AnimatedNavHost.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/AnimatedNavHost.kt
deleted file mode 100644
index 57bb838..0000000
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/AnimatedNavHost.kt
+++ /dev/null
@@ -1,263 +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.settingslib.spa.framework.compose
-
-import androidx.activity.compose.LocalOnBackPressedDispatcherOwner
-import androidx.compose.animation.AnimatedContent
-import androidx.compose.animation.AnimatedContentScope
-import androidx.compose.animation.ContentTransform
-import androidx.compose.animation.EnterTransition
-import androidx.compose.animation.ExitTransition
-import androidx.compose.animation.ExperimentalAnimationApi
-import androidx.compose.animation.core.tween
-import androidx.compose.animation.core.updateTransition
-import androidx.compose.animation.fadeIn
-import androidx.compose.animation.fadeOut
-import androidx.compose.animation.with
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.collectAsState
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.saveable.rememberSaveableStateHolder
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.platform.LocalLifecycleOwner
-import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner
-import androidx.navigation.NavBackStackEntry
-import androidx.navigation.NavDestination
-import androidx.navigation.NavDestination.Companion.hierarchy
-import androidx.navigation.NavGraph
-import androidx.navigation.NavGraphBuilder
-import androidx.navigation.NavHostController
-import androidx.navigation.Navigator
-import androidx.navigation.compose.DialogHost
-import androidx.navigation.compose.DialogNavigator
-import androidx.navigation.compose.LocalOwnersProvider
-import androidx.navigation.createGraph
-import androidx.navigation.get
-import kotlinx.coroutines.flow.map
-
-/**
- * Provides in place in the Compose hierarchy for self contained navigation to occur.
- *
- * Once this is called, any Composable within the given [NavGraphBuilder] can be navigated to from
- * the provided [navController].
- *
- * The builder passed into this method is [remember]ed. This means that for this NavHost, the
- * contents of the builder cannot be changed.
- *
- * @param navController the navController for this host
- * @param startDestination the route for the start destination
- * @param modifier The modifier to be applied to the layout.
- * @param route the route for the graph
- * @param enterTransition callback to define enter transitions for destination in this host
- * @param exitTransition callback to define exit transitions for destination in this host
- * @param popEnterTransition callback to define popEnter transitions for destination in this host
- * @param popExitTransition callback to define popExit transitions for destination in this host
- * @param builder the builder used to construct the graph
- */
-@Composable
-@ExperimentalAnimationApi
-public fun AnimatedNavHost(
-    navController: NavHostController,
-    startDestination: String,
-    modifier: Modifier = Modifier,
-    contentAlignment: Alignment = Alignment.Center,
-    route: String? = null,
-    enterTransition: (AnimatedContentScope<NavBackStackEntry>.() -> EnterTransition) =
-        { fadeIn(animationSpec = tween(700)) },
-    exitTransition: (AnimatedContentScope<NavBackStackEntry>.() -> ExitTransition) =
-        { fadeOut(animationSpec = tween(700)) },
-    popEnterTransition: (AnimatedContentScope<NavBackStackEntry>.() -> EnterTransition) =
-        enterTransition,
-    popExitTransition: (AnimatedContentScope<NavBackStackEntry>.() -> ExitTransition) =
-        exitTransition,
-    builder: NavGraphBuilder.() -> Unit
-) {
-    AnimatedNavHost(
-        navController,
-        remember(route, startDestination, builder) {
-            navController.createGraph(startDestination, route, builder)
-        },
-        modifier,
-        contentAlignment,
-        enterTransition,
-        exitTransition,
-        popEnterTransition,
-        popExitTransition
-    )
-}
-
-/**
- * Provides in place in the Compose hierarchy for self contained navigation to occur.
- *
- * Once this is called, any Composable within the given [NavGraphBuilder] can be navigated to from
- * the provided [navController].
- *
- * @param navController the navController for this host
- * @param graph the graph for this host
- * @param modifier The modifier to be applied to the layout.
- * @param enterTransition callback to define enter transitions for destination in this host
- * @param exitTransition callback to define exit transitions for destination in this host
- * @param popEnterTransition callback to define popEnter transitions for destination in this host
- * @param popExitTransition callback to define popExit transitions for destination in this host
- */
-@ExperimentalAnimationApi
-@Composable
-public fun AnimatedNavHost(
-    navController: NavHostController,
-    graph: NavGraph,
-    modifier: Modifier = Modifier,
-    contentAlignment: Alignment = Alignment.Center,
-    enterTransition: (AnimatedContentScope<NavBackStackEntry>.() -> EnterTransition) =
-        { fadeIn(animationSpec = tween(700)) },
-    exitTransition: (AnimatedContentScope<NavBackStackEntry>.() -> ExitTransition) =
-        { fadeOut(animationSpec = tween(700)) },
-    popEnterTransition: (AnimatedContentScope<NavBackStackEntry>.() -> EnterTransition) =
-        enterTransition,
-    popExitTransition: (AnimatedContentScope<NavBackStackEntry>.() -> ExitTransition) =
-        exitTransition,
-) {
-
-    val lifecycleOwner = LocalLifecycleOwner.current
-    val viewModelStoreOwner = checkNotNull(LocalViewModelStoreOwner.current) {
-        "NavHost requires a ViewModelStoreOwner to be provided via LocalViewModelStoreOwner"
-    }
-    val onBackPressedDispatcherOwner = LocalOnBackPressedDispatcherOwner.current
-    val onBackPressedDispatcher = onBackPressedDispatcherOwner?.onBackPressedDispatcher
-
-    // on successful recompose we setup the navController with proper inputs
-    // after the first time, this will only happen again if one of the inputs changes
-    navController.setLifecycleOwner(lifecycleOwner)
-    navController.setViewModelStore(viewModelStoreOwner.viewModelStore)
-    if (onBackPressedDispatcher != null) {
-        navController.setOnBackPressedDispatcher(onBackPressedDispatcher)
-    }
-
-    navController.graph = graph
-
-    val saveableStateHolder = rememberSaveableStateHolder()
-
-    // Find the ComposeNavigator, returning early if it isn't found
-    // (such as is the case when using TestNavHostController)
-    val composeNavigator = navController.navigatorProvider.get<Navigator<out NavDestination>>(
-        AnimatedComposeNavigator.NAME
-    ) as? AnimatedComposeNavigator ?: return
-    val visibleEntries by remember(navController.visibleEntries) {
-        navController.visibleEntries.map {
-            it.filter { entry ->
-                entry.destination.navigatorName == AnimatedComposeNavigator.NAME
-            }
-        }
-    }.collectAsState(emptyList())
-
-    val backStackEntry = visibleEntries.lastOrNull()
-
-    if (backStackEntry != null) {
-        val finalEnter: AnimatedContentScope<NavBackStackEntry>.() -> EnterTransition = {
-            val targetDestination = targetState.destination as AnimatedComposeNavigator.Destination
-
-            if (composeNavigator.isPop.value) {
-                targetDestination.hierarchy.firstNotNullOfOrNull { destination ->
-                    popEnterTransitions[destination.route]?.invoke(this)
-                } ?: popEnterTransition.invoke(this)
-            } else {
-                targetDestination.hierarchy.firstNotNullOfOrNull { destination ->
-                    enterTransitions[destination.route]?.invoke(this)
-                } ?: enterTransition.invoke(this)
-            }
-        }
-
-        val finalExit: AnimatedContentScope<NavBackStackEntry>.() -> ExitTransition = {
-            val initialDestination =
-                initialState.destination as AnimatedComposeNavigator.Destination
-
-            if (composeNavigator.isPop.value) {
-                initialDestination.hierarchy.firstNotNullOfOrNull { destination ->
-                    popExitTransitions[destination.route]?.invoke(this)
-                } ?: popExitTransition.invoke(this)
-            } else {
-                initialDestination.hierarchy.firstNotNullOfOrNull { destination ->
-                    exitTransitions[destination.route]?.invoke(this)
-                } ?: exitTransition.invoke(this)
-            }
-        }
-
-        val transition = updateTransition(backStackEntry, label = "entry")
-        transition.AnimatedContent(
-            modifier,
-            transitionSpec = {
-                val zIndex = composeNavigator.backStack.value.size.toFloat()
-                // If the initialState of the AnimatedContent is not in visibleEntries, we are in
-                // a case where visible has cleared the old state for some reason, so instead of
-                // attempting to animate away from the initialState, we skip the animation.
-                if (initialState in visibleEntries) {
-                    ContentTransform(finalEnter(this), finalExit(this), zIndex)
-                } else {
-                    EnterTransition.None with ExitTransition.None
-                }
-            },
-            contentAlignment,
-            contentKey = { it.id }
-        ) {
-            // In some specific cases, such as clearing your back stack by changing your
-            // start destination, AnimatedContent can contain an entry that is no longer
-            // part of visible entries since it was cleared from the back stack and is not
-            // animating. In these cases the currentEntry will be null, and in those cases,
-            // AnimatedContent will just skip attempting to transition the old entry.
-            // See https://issuetracker.google.com/238686802
-            val currentEntry = visibleEntries.lastOrNull { entry ->
-                it == entry
-            }
-            // while in the scope of the composable, we provide the navBackStackEntry as the
-            // ViewModelStoreOwner and LifecycleOwner
-            currentEntry?.LocalOwnersProvider(saveableStateHolder) {
-                (currentEntry.destination as AnimatedComposeNavigator.Destination)
-                    .content(this, currentEntry)
-            }
-        }
-        if (transition.currentState == transition.targetState) {
-            visibleEntries.forEach { entry ->
-                composeNavigator.markTransitionComplete(entry)
-            }
-        }
-    }
-
-    val dialogNavigator = navController.navigatorProvider.get<Navigator<out NavDestination>>(
-        "dialog"
-    ) as? DialogNavigator ?: return
-
-    // Show any dialog destinations
-    DialogHost(dialogNavigator)
-}
-
-@ExperimentalAnimationApi
-internal val enterTransitions =
-    mutableMapOf<String?,
-        (AnimatedContentScope<NavBackStackEntry>.() -> EnterTransition?)?>()
-
-@ExperimentalAnimationApi
-internal val exitTransitions =
-    mutableMapOf<String?, (AnimatedContentScope<NavBackStackEntry>.() -> ExitTransition?)?>()
-
-@ExperimentalAnimationApi
-internal val popEnterTransitions =
-    mutableMapOf<String?, (AnimatedContentScope<NavBackStackEntry>.() -> EnterTransition?)?>()
-
-@ExperimentalAnimationApi
-internal val popExitTransitions =
-    mutableMapOf<String?, (AnimatedContentScope<NavBackStackEntry>.() -> ExitTransition?)?>()
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/NavGraphBuilder.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/NavGraphBuilder.kt
deleted file mode 100644
index 9e58603..0000000
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/NavGraphBuilder.kt
+++ /dev/null
@@ -1,118 +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.settingslib.spa.framework.compose
-
-import androidx.compose.animation.AnimatedContentScope
-import androidx.compose.animation.AnimatedVisibilityScope
-import androidx.compose.animation.EnterTransition
-import androidx.compose.animation.ExitTransition
-import androidx.compose.animation.ExperimentalAnimationApi
-import androidx.compose.runtime.Composable
-import androidx.navigation.NamedNavArgument
-import androidx.navigation.NavBackStackEntry
-import androidx.navigation.NavDeepLink
-import androidx.navigation.NavGraph
-import androidx.navigation.NavGraphBuilder
-import androidx.navigation.compose.navigation
-import androidx.navigation.get
-
-/**
- * Add the [Composable] to the [NavGraphBuilder]
- *
- * @param route route for the destination
- * @param arguments list of arguments to associate with destination
- * @param deepLinks list of deep links to associate with the destinations
- * @param enterTransition callback to determine the destination's enter transition
- * @param exitTransition callback to determine the destination's exit transition
- * @param popEnterTransition callback to determine the destination's popEnter transition
- * @param popExitTransition callback to determine the destination's popExit transition
- * @param content composable for the destination
- */
-@ExperimentalAnimationApi
-public fun NavGraphBuilder.composable(
-    route: String,
-    arguments: List<NamedNavArgument> = emptyList(),
-    deepLinks: List<NavDeepLink> = emptyList(),
-    enterTransition: (AnimatedContentScope<NavBackStackEntry>.() -> EnterTransition?)? = null,
-    exitTransition: (AnimatedContentScope<NavBackStackEntry>.() -> ExitTransition?)? = null,
-    popEnterTransition: (
-    AnimatedContentScope<NavBackStackEntry>.() -> EnterTransition?
-    )? = enterTransition,
-    popExitTransition: (
-    AnimatedContentScope<NavBackStackEntry>.() -> ExitTransition?
-    )? = exitTransition,
-    content: @Composable AnimatedVisibilityScope.(NavBackStackEntry) -> Unit
-) {
-    addDestination(
-        AnimatedComposeNavigator.Destination(
-            provider[AnimatedComposeNavigator::class],
-            content
-        ).apply {
-            this.route = route
-            arguments.forEach { (argumentName, argument) ->
-                addArgument(argumentName, argument)
-            }
-            deepLinks.forEach { deepLink ->
-                addDeepLink(deepLink)
-            }
-            enterTransition?.let { enterTransitions[route] = enterTransition }
-            exitTransition?.let { exitTransitions[route] = exitTransition }
-            popEnterTransition?.let { popEnterTransitions[route] = popEnterTransition }
-            popExitTransition?.let { popExitTransitions[route] = popExitTransition }
-        }
-    )
-}
-
-/**
- * Construct a nested [NavGraph]
- *
- * @param startDestination the starting destination's route for this NavGraph
- * @param route the destination's unique route
- * @param arguments list of arguments to associate with destination
- * @param deepLinks list of deep links to associate with the destinations
- * @param enterTransition callback to define enter transitions for destination in this NavGraph
- * @param exitTransition callback to define exit transitions for destination in this NavGraph
- * @param popEnterTransition callback to define pop enter transitions for destination in this
- * NavGraph
- * @param popExitTransition callback to define pop exit transitions for destination in this NavGraph
- * @param builder the builder used to construct the graph
- *
- * @return the newly constructed nested NavGraph
- */
-@ExperimentalAnimationApi
-public fun NavGraphBuilder.navigation(
-    startDestination: String,
-    route: String,
-    arguments: List<NamedNavArgument> = emptyList(),
-    deepLinks: List<NavDeepLink> = emptyList(),
-    enterTransition: (AnimatedContentScope<NavBackStackEntry>.() -> EnterTransition?)? = null,
-    exitTransition: (AnimatedContentScope<NavBackStackEntry>.() -> ExitTransition?)? = null,
-    popEnterTransition: (
-    AnimatedContentScope<NavBackStackEntry>.() -> EnterTransition?
-    )? = enterTransition,
-    popExitTransition: (
-    AnimatedContentScope<NavBackStackEntry>.() -> ExitTransition?
-    )? = exitTransition,
-    builder: NavGraphBuilder.() -> Unit
-) {
-    navigation(startDestination, route, arguments, deepLinks, builder).apply {
-        enterTransition?.let { enterTransitions[route] = enterTransition }
-        exitTransition?.let { exitTransitions[route] = exitTransition }
-        popEnterTransition?.let { popEnterTransitions[route] = popEnterTransition }
-        popExitTransition?.let { popExitTransitions[route] = popExitTransition }
-    }
-}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/NavHostController.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/NavHostController.kt
deleted file mode 100644
index a8ac86c..0000000
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/NavHostController.kt
+++ /dev/null
@@ -1,41 +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.settingslib.spa.framework.compose
-
-import androidx.compose.animation.ExperimentalAnimationApi
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.remember
-import androidx.navigation.NavDestination
-import androidx.navigation.NavHostController
-import androidx.navigation.Navigator
-import androidx.navigation.compose.rememberNavController
-
-/**
- * Creates a NavHostController that handles the adding of the [ComposeNavigator], [DialogNavigator]
- * and [AnimatedComposeNavigator]. Additional [androidx.navigation.Navigator] instances should be
- * added in a [androidx.compose.runtime.SideEffect] block.
- *
- * @see AnimatedNavHost
- */
-@ExperimentalAnimationApi
-@Composable
-fun rememberAnimatedNavController(
-    vararg navigators: Navigator<out NavDestination>
-): NavHostController {
-    val animatedNavigator = remember { AnimatedComposeNavigator() }
-    return rememberNavController(animatedNavigator, *navigators)
-}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt
index e07a629..90a723f 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt
@@ -50,7 +50,7 @@
 import androidx.compose.runtime.SideEffect
 import androidx.compose.runtime.Stable
 import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.mutableFloatStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.rememberUpdatedState
 import androidx.compose.ui.Alignment
@@ -286,21 +286,22 @@
         )
     }
     val pinnedHeightPx: Float
-    val density = LocalDensity.current
-    val maxHeightPx = density.run {
-        remember { mutableStateOf((MaxHeightWithoutTitle + DefaultTitleHeight).toPx()) }
-    }
     val titleBottomPaddingPx: Int
+    val defaultMaxHeightPx: Float
+    val density = LocalDensity.current
     density.run {
         pinnedHeightPx = pinnedHeight.toPx()
         titleBottomPaddingPx = titleBottomPadding.roundToPx()
+        defaultMaxHeightPx = (MaxHeightWithoutTitle + DefaultTitleHeight).toPx()
     }
 
+    val maxHeightPx = remember(density) { mutableFloatStateOf(defaultMaxHeightPx) }
+
     // Sets the app bar's height offset limit to hide just the bottom title area and keep top title
     // visible when collapsed.
     SideEffect {
-        if (scrollBehavior?.state?.heightOffsetLimit != pinnedHeightPx - maxHeightPx.value) {
-            scrollBehavior?.state?.heightOffsetLimit = pinnedHeightPx - maxHeightPx.value
+        if (scrollBehavior?.state?.heightOffsetLimit != pinnedHeightPx - maxHeightPx.floatValue) {
+            scrollBehavior?.state?.heightOffsetLimit = pinnedHeightPx - maxHeightPx.floatValue
         }
     }
 
@@ -370,16 +371,20 @@
             )
             TopAppBarLayout(
                 modifier = Modifier.clipToBounds(),
-                heightPx = maxHeightPx.value - pinnedHeightPx +
+                heightPx = maxHeightPx.floatValue - pinnedHeightPx +
                     (scrollBehavior?.state?.heightOffset ?: 0f),
                 navigationIconContentColor = colors.navigationIconContentColor,
                 titleContentColor = colors.titleContentColor,
                 actionIconContentColor = colors.actionIconContentColor,
                 title = {
                     Box(modifier = Modifier.onGloballyPositioned { coordinates ->
-                        density.run {
-                            maxHeightPx.value =
-                                MaxHeightWithoutTitle.toPx() + coordinates.size.height.toFloat()
+                        val measuredMaxHeightPx = density.run {
+                            MaxHeightWithoutTitle.toPx() + coordinates.size.height.toFloat()
+                        }
+                        // Allow larger max height for multi-line title, but do not reduce
+                        // max height to prevent flaky.
+                        if (measuredMaxHeightPx > defaultMaxHeightPx) {
+                            maxHeightPx.floatValue = measuredMaxHeightPx
                         }
                     }) { title() }
                 },
@@ -506,7 +511,7 @@
                 0
             }
 
-        val layoutHeight = heightPx.roundToInt()
+        val layoutHeight = if (heightPx.isNaN()) 0 else heightPx.roundToInt()
 
         layout(constraints.maxWidth, layoutHeight) {
             // Navigation icon
@@ -612,9 +617,9 @@
 // Medium or Large app bar.
 private val TopTitleAlphaEasing = CubicBezierEasing(.8f, 0f, .8f, .15f)
 
-private val MaxHeightWithoutTitle = 124.dp
-private val DefaultTitleHeight = 52.dp
-private val ContainerHeight = 56.dp
+internal val MaxHeightWithoutTitle = 124.dp
+internal val DefaultTitleHeight = 52.dp
+internal val ContainerHeight = 56.dp
 private val LargeTitleBottomPadding = 28.dp
 private val TopAppBarHorizontalPadding = 4.dp
 
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SearchScaffold.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SearchScaffold.kt
index 67c4cdc..67f4418 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SearchScaffold.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SearchScaffold.kt
@@ -163,7 +163,6 @@
     BackHandler { onClose() }
 }
 
-@OptIn(ExperimentalMaterial3Api::class)
 @Composable
 private fun SearchBox(query: TextFieldValue, onQueryChange: (TextFieldValue) -> Unit) {
     val focusRequester = remember { FocusRequester() }
@@ -186,8 +185,9 @@
         keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search),
         keyboardActions = KeyboardActions(onSearch = { hideKeyboardAction() }),
         singleLine = true,
-        colors = TextFieldDefaults.textFieldColors(
-            containerColor = Color.Transparent,
+        colors = TextFieldDefaults.colors(
+            focusedContainerColor = Color.Transparent,
+            unfocusedContainerColor = Color.Transparent,
             focusedIndicatorColor = Color.Transparent,
             unfocusedIndicatorColor = Color.Transparent,
         ),
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsPager.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsPager.kt
index 79635a0..aa148b0 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsPager.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsPager.kt
@@ -20,20 +20,12 @@
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.pager.HorizontalPager
-import androidx.compose.foundation.pager.PagerState
 import androidx.compose.foundation.pager.rememberPagerState
 import androidx.compose.material3.TabRow
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.SideEffect
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.rememberCoroutineScope
-import androidx.compose.runtime.saveable.rememberSaveable
-import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.platform.LocalConfiguration
 import com.android.settingslib.spa.framework.theme.SettingsDimension
 import kotlin.math.absoluteValue
 import kotlinx.coroutines.launch
@@ -49,7 +41,7 @@
 
     Column {
         val coroutineScope = rememberCoroutineScope()
-        val pagerState = rememberPageStateFixed()
+        val pagerState = rememberPagerState { titles.size }
 
         TabRow(
             selectedTabIndex = pagerState.currentPage,
@@ -72,46 +64,8 @@
             }
         }
 
-        HorizontalPager(pageCount = titles.size, state = pagerState) { page ->
+        HorizontalPager(state = pagerState) { page ->
             content(page)
         }
     }
 }
-
-/**
- * Gets the state of [PagerState].
- *
- * This is a work around.
- *
- * TODO: Remove this and replace with rememberPageState() after the Compose Foundation 1.5.0-alpha04
- *       updated in the platform.
- */
-@Composable
-@OptIn(ExperimentalFoundationApi::class)
-private fun rememberPageStateFixed(): PagerState {
-    var currentPage by rememberSaveable { mutableStateOf(0) }
-    var targetPage by rememberSaveable { mutableStateOf(-1) }
-    val pagerState = rememberPagerState()
-    val configuration = LocalConfiguration.current
-    var lastScreenWidthDp by rememberSaveable { mutableStateOf(-1) }
-    val screenWidthDp = configuration.screenWidthDp
-    LaunchedEffect(screenWidthDp) {
-        // Reset pager state to fix an issue after configuration change.
-        // When we declare android:configChanges in the manifest, the pager state is in a weird
-        // state after configuration change.
-        targetPage = currentPage
-        lastScreenWidthDp = screenWidthDp
-    }
-    LaunchedEffect(targetPage) {
-        if (targetPage != -1) {
-            pagerState.scrollToPage(targetPage)
-            targetPage = -1
-        }
-    }
-    SideEffect {
-        if (lastScreenWidthDp == screenWidthDp) {
-            currentPage = pagerState.currentPage
-        }
-    }
-    return pagerState
-}
diff --git a/packages/SettingsLib/Spa/tests/Android.bp b/packages/SettingsLib/Spa/tests/Android.bp
index b4c67cc..0d9ba54 100644
--- a/packages/SettingsLib/Spa/tests/Android.bp
+++ b/packages/SettingsLib/Spa/tests/Android.bp
@@ -31,11 +31,11 @@
         "SpaLib",
         "SpaLibTestUtils",
         "androidx.compose.runtime_runtime",
-        "androidx.lifecycle_lifecycle-runtime-testing",
         "androidx.test.ext.junit",
         "androidx.test.runner",
         "mockito-target-minus-junit4",
     ],
     kotlincflags: ["-Xjvm-default=all"],
+    sdk_version: "current",
     min_sdk_version: "31",
 }
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBarTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBarTest.kt
new file mode 100644
index 0000000..a6a5ed22
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBarTest.kt
@@ -0,0 +1,457 @@
+/*
+ * 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.settingslib.spa.widget.scaffold
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.LazyListState
+import androidx.compose.foundation.lazy.LazyRow
+import androidx.compose.foundation.lazy.rememberLazyListState
+import androidx.compose.material3.CenterAlignedTopAppBar
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.LocalContentColor
+import androidx.compose.material3.LocalTextStyle
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Text
+import androidx.compose.material3.TopAppBarDefaults
+import androidx.compose.material3.TopAppBarScrollBehavior
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.painter.ColorPainter
+import androidx.compose.ui.input.nestedscroll.nestedScroll
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.test.assertHeightIsEqualTo
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertLeftPositionInRootIsEqualTo
+import androidx.compose.ui.test.assertTopPositionInRootIsEqualTo
+import androidx.compose.ui.test.assertWidthIsEqualTo
+import androidx.compose.ui.test.getUnclippedBoundsInRoot
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performTouchInput
+import androidx.compose.ui.test.swipeLeft
+import androidx.compose.ui.test.swipeRight
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.height
+import androidx.compose.ui.unit.width
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.testutils.rootWidth
+import com.android.settingslib.spa.testutils.setContentForSizeAssertions
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalMaterial3Api::class)
+@RunWith(AndroidJUnit4::class)
+class CustomizedAppBarTest {
+
+    @get:Rule
+    val rule = createComposeRule()
+
+    @Test
+    fun smallTopAppBar_expandsToScreen() {
+        rule
+            .setContentForSizeAssertions {
+                CustomizedTopAppBar(title = { Text("Title") })
+            }
+            .assertHeightIsEqualTo(ContainerHeight)
+            .assertWidthIsEqualTo(rule.rootWidth())
+    }
+
+    @Test
+    fun smallTopAppBar_withTitle() {
+        val title = "Title"
+        rule.setContent {
+            Box(Modifier.testTag(TopAppBarTestTag)) {
+                CustomizedTopAppBar(title = { Text(title) })
+            }
+        }
+        rule.onNodeWithText(title).assertIsDisplayed()
+    }
+
+    @Test
+    fun smallTopAppBar_default_positioning() {
+        rule.setContent {
+            Box(Modifier.testTag(TopAppBarTestTag)) {
+                CustomizedTopAppBar(
+                    navigationIcon = {
+                        FakeIcon(Modifier.testTag(NavigationIconTestTag))
+                    },
+                    title = {
+                        Text("Title", Modifier.testTag(TitleTestTag))
+                    },
+                    actions = {
+                        FakeIcon(Modifier.testTag(ActionsTestTag))
+                    }
+                )
+            }
+        }
+        assertSmallDefaultPositioning()
+    }
+
+    @Test
+    fun smallTopAppBar_noNavigationIcon_positioning() {
+        rule.setContent {
+            Box(Modifier.testTag(TopAppBarTestTag)) {
+                CustomizedTopAppBar(
+                    title = {
+                        Text("Title", Modifier.testTag(TitleTestTag))
+                    },
+                    actions = {
+                        FakeIcon(Modifier.testTag(ActionsTestTag))
+                    }
+                )
+            }
+        }
+        assertSmallPositioningWithoutNavigation()
+    }
+
+    @Test
+    fun smallTopAppBar_titleDefaultStyle() {
+        var textStyle: TextStyle? = null
+        var expectedTextStyle: TextStyle? = null
+        rule.setContent {
+            CustomizedTopAppBar(
+                title = {
+                    Text("Title")
+                    textStyle = LocalTextStyle.current
+                    expectedTextStyle = MaterialTheme.typography.titleMedium
+                },
+            )
+        }
+        assertThat(textStyle).isNotNull()
+        assertThat(textStyle).isEqualTo(expectedTextStyle)
+    }
+
+    @Test
+    fun smallTopAppBar_contentColor() {
+        var titleColor: Color = Color.Unspecified
+        var navigationIconColor: Color = Color.Unspecified
+        var actionsColor: Color = Color.Unspecified
+        var expectedTitleColor: Color = Color.Unspecified
+        var expectedNavigationIconColor: Color = Color.Unspecified
+        var expectedActionsColor: Color = Color.Unspecified
+
+        rule.setContent {
+            CustomizedTopAppBar(
+                navigationIcon = {
+                    FakeIcon(Modifier.testTag(NavigationIconTestTag))
+                    navigationIconColor = LocalContentColor.current
+                    expectedNavigationIconColor =
+                        TopAppBarDefaults.topAppBarColors().navigationIconContentColor
+                    // fraction = 0f to indicate no scroll.
+                },
+                title = {
+                    Text("Title", Modifier.testTag(TitleTestTag))
+                    titleColor = LocalContentColor.current
+                    expectedTitleColor = TopAppBarDefaults.topAppBarColors().titleContentColor
+                },
+                actions = {
+                    FakeIcon(Modifier.testTag(ActionsTestTag))
+                    actionsColor = LocalContentColor.current
+                    expectedActionsColor =
+                        TopAppBarDefaults.topAppBarColors().actionIconContentColor
+                }
+            )
+        }
+        assertThat(navigationIconColor).isNotNull()
+        assertThat(titleColor).isNotNull()
+        assertThat(actionsColor).isNotNull()
+        assertThat(navigationIconColor).isEqualTo(expectedNavigationIconColor)
+        assertThat(titleColor).isEqualTo(expectedTitleColor)
+        assertThat(actionsColor).isEqualTo(expectedActionsColor)
+    }
+
+    @Test
+    fun largeTopAppBar_scrolled_positioning() {
+        val content = @Composable { scrollBehavior: TopAppBarScrollBehavior? ->
+            Box(Modifier.testTag(TopAppBarTestTag)) {
+                CustomizedLargeTopAppBar(
+                    navigationIcon = {
+                        FakeIcon(Modifier.testTag(NavigationIconTestTag))
+                    },
+                    title = "Title",
+                    actions = {
+                        FakeIcon(Modifier.testTag(ActionsTestTag))
+                    },
+                    scrollBehavior = scrollBehavior,
+                )
+            }
+        }
+        assertLargeScrolledHeight(
+            MaxHeightWithoutTitle + DefaultTitleHeight,
+            MaxHeightWithoutTitle + DefaultTitleHeight,
+            content,
+        )
+    }
+
+    @OptIn(ExperimentalMaterial3Api::class)
+    @Test
+    fun topAppBar_enterAlways_allowHorizontalScroll() {
+        lateinit var state: LazyListState
+        rule.setContent {
+            state = rememberLazyListState()
+            MultiPageContent(TopAppBarDefaults.enterAlwaysScrollBehavior(), state)
+        }
+
+        rule.onNodeWithTag(LazyListTag).performTouchInput { swipeLeft() }
+        rule.runOnIdle {
+            assertThat(state.firstVisibleItemIndex).isEqualTo(1)
+        }
+
+        rule.onNodeWithTag(LazyListTag).performTouchInput { swipeRight() }
+        rule.runOnIdle {
+            assertThat(state.firstVisibleItemIndex).isEqualTo(0)
+        }
+    }
+
+    @OptIn(ExperimentalMaterial3Api::class)
+    @Test
+    fun topAppBar_exitUntilCollapsed_allowHorizontalScroll() {
+        lateinit var state: LazyListState
+        rule.setContent {
+            state = rememberLazyListState()
+            MultiPageContent(TopAppBarDefaults.exitUntilCollapsedScrollBehavior(), state)
+        }
+
+        rule.onNodeWithTag(LazyListTag).performTouchInput { swipeLeft() }
+        rule.runOnIdle {
+            assertThat(state.firstVisibleItemIndex).isEqualTo(1)
+        }
+
+        rule.onNodeWithTag(LazyListTag).performTouchInput { swipeRight() }
+        rule.runOnIdle {
+            assertThat(state.firstVisibleItemIndex).isEqualTo(0)
+        }
+    }
+
+    @OptIn(ExperimentalMaterial3Api::class)
+    @Test
+    fun topAppBar_pinned_allowHorizontalScroll() {
+        lateinit var state: LazyListState
+        rule.setContent {
+            state = rememberLazyListState()
+            MultiPageContent(
+                TopAppBarDefaults.pinnedScrollBehavior(),
+                state
+            )
+        }
+
+        rule.onNodeWithTag(LazyListTag).performTouchInput { swipeLeft() }
+        rule.runOnIdle {
+            assertThat(state.firstVisibleItemIndex).isEqualTo(1)
+        }
+
+        rule.onNodeWithTag(LazyListTag).performTouchInput { swipeRight() }
+        rule.runOnIdle {
+            assertThat(state.firstVisibleItemIndex).isEqualTo(0)
+        }
+    }
+
+    @OptIn(ExperimentalMaterial3Api::class)
+    @Composable
+    private fun MultiPageContent(scrollBehavior: TopAppBarScrollBehavior, state: LazyListState) {
+        Scaffold(
+            modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
+            topBar = {
+                CustomizedTopAppBar(
+                    title = { Text(text = "Title") },
+                )
+            }
+        ) { contentPadding ->
+            LazyRow(
+                Modifier
+                    .fillMaxSize()
+                    .testTag(LazyListTag), state
+            ) {
+                items(2) { page ->
+                    LazyColumn(
+                        modifier = Modifier.fillParentMaxSize(),
+                        contentPadding = contentPadding
+                    ) {
+                        items(50) {
+                            Text(
+                                modifier = Modifier.fillParentMaxWidth(),
+                                text = "Item #$page x $it"
+                            )
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Checks the app bar's components positioning when it's a [CustomizedTopAppBar], a
+     * [CenterAlignedTopAppBar], or a larger app bar that is scrolled up and collapsed into a small
+     * configuration and there is no navigation icon.
+     */
+    private fun assertSmallPositioningWithoutNavigation(isCenteredTitle: Boolean = false) {
+        val appBarBounds = rule.onNodeWithTag(TopAppBarTestTag).getUnclippedBoundsInRoot()
+        val titleBounds = rule.onNodeWithTag(TitleTestTag).getUnclippedBoundsInRoot()
+
+        val titleNode = rule.onNodeWithTag(TitleTestTag)
+        // Title should be vertically centered
+        titleNode.assertTopPositionInRootIsEqualTo((appBarBounds.height - titleBounds.height) / 2)
+        if (isCenteredTitle) {
+            // Title should be horizontally centered
+            titleNode.assertLeftPositionInRootIsEqualTo(
+                (appBarBounds.width - titleBounds.width) / 2
+            )
+        } else {
+            // Title should now be placed 16.dp from the start, as there is no navigation icon
+            // 4.dp padding for the whole app bar + 12.dp inset
+            titleNode.assertLeftPositionInRootIsEqualTo(4.dp + 12.dp)
+        }
+
+        rule.onNodeWithTag(ActionsTestTag)
+            // Action should still be placed at the end
+            .assertLeftPositionInRootIsEqualTo(expectedActionPosition(appBarBounds.width))
+    }
+
+    /**
+     * Checks the app bar's components positioning when it's a [CustomizedTopAppBar] or a
+     * [CenterAlignedTopAppBar].
+     */
+    private fun assertSmallDefaultPositioning(isCenteredTitle: Boolean = false) {
+        val appBarBounds = rule.onNodeWithTag(TopAppBarTestTag).getUnclippedBoundsInRoot()
+        val titleBounds = rule.onNodeWithTag(TitleTestTag).getUnclippedBoundsInRoot()
+        val appBarBottomEdgeY = appBarBounds.top + appBarBounds.height
+
+        rule.onNodeWithTag(NavigationIconTestTag)
+            // Navigation icon should be 4.dp from the start
+            .assertLeftPositionInRootIsEqualTo(AppBarStartAndEndPadding)
+            // Navigation icon should be centered within the height of the app bar.
+            .assertTopPositionInRootIsEqualTo(
+                appBarBottomEdgeY - AppBarTopAndBottomPadding - FakeIconSize
+            )
+
+        val titleNode = rule.onNodeWithTag(TitleTestTag)
+        // Title should be vertically centered
+        titleNode.assertTopPositionInRootIsEqualTo((appBarBounds.height - titleBounds.height) / 2)
+        if (isCenteredTitle) {
+            // Title should be horizontally centered
+            titleNode.assertLeftPositionInRootIsEqualTo(
+                (appBarBounds.width - titleBounds.width) / 2
+            )
+        } else {
+            // Title should be 56.dp from the start
+            // 4.dp padding for the whole app bar + 48.dp icon size + 4.dp title padding.
+            titleNode.assertLeftPositionInRootIsEqualTo(4.dp + FakeIconSize + 4.dp)
+        }
+
+        rule.onNodeWithTag(ActionsTestTag)
+            // Action should be placed at the end
+            .assertLeftPositionInRootIsEqualTo(expectedActionPosition(appBarBounds.width))
+            // Action should be 8.dp from the top
+            .assertTopPositionInRootIsEqualTo(
+                appBarBottomEdgeY - AppBarTopAndBottomPadding - FakeIconSize
+            )
+    }
+
+    /**
+     * Checks that changing values at a [CustomizedLargeTopAppBar] scroll behavior
+     * affects the height of the app bar.
+     *
+     * This check partially and fully collapses the app bar to test its height.
+     *
+     * @param appBarMaxHeight the max height of the app bar [content]
+     * @param appBarMinHeight the min height of the app bar [content]
+     * @param content a Composable that adds a CustomizedLargeTopAppBar
+     */
+    @OptIn(ExperimentalMaterial3Api::class)
+    private fun assertLargeScrolledHeight(
+        appBarMaxHeight: Dp,
+        appBarMinHeight: Dp,
+        content: @Composable (TopAppBarScrollBehavior?) -> Unit
+    ) {
+        val fullyCollapsedOffsetDp = appBarMaxHeight - appBarMinHeight
+        val partiallyCollapsedOffsetDp = fullyCollapsedOffsetDp / 3
+        var partiallyCollapsedHeightOffsetPx = 0f
+        var fullyCollapsedHeightOffsetPx = 0f
+        lateinit var scrollBehavior: TopAppBarScrollBehavior
+        rule.setContent {
+            scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
+            with(LocalDensity.current) {
+                partiallyCollapsedHeightOffsetPx = partiallyCollapsedOffsetDp.toPx()
+                fullyCollapsedHeightOffsetPx = fullyCollapsedOffsetDp.toPx()
+            }
+
+            content(scrollBehavior)
+        }
+
+        // Simulate a partially collapsed app bar.
+        rule.runOnIdle {
+            scrollBehavior.state.heightOffset = -partiallyCollapsedHeightOffsetPx
+            scrollBehavior.state.contentOffset = -partiallyCollapsedHeightOffsetPx
+        }
+        rule.waitForIdle()
+        rule.onNodeWithTag(TopAppBarTestTag)
+            .assertHeightIsEqualTo(
+                appBarMaxHeight - partiallyCollapsedOffsetDp
+            )
+
+        // Simulate a fully collapsed app bar.
+        rule.runOnIdle {
+            scrollBehavior.state.heightOffset = -fullyCollapsedHeightOffsetPx
+            // Simulate additional content scroll beyond the max offset scroll.
+            scrollBehavior.state.contentOffset =
+                -fullyCollapsedHeightOffsetPx - partiallyCollapsedHeightOffsetPx
+        }
+        rule.waitForIdle()
+        // Check that the app bar collapsed to its min height.
+        rule.onNodeWithTag(TopAppBarTestTag).assertHeightIsEqualTo(appBarMinHeight)
+    }
+
+    /**
+     * An [IconButton] with an [Icon] inside for testing positions.
+     *
+     * An [IconButton] is defaulted to be 48X48dp, while its child [Icon] is defaulted to 24x24dp.
+     */
+    private val FakeIcon = @Composable { modifier: Modifier ->
+        IconButton(
+            onClick = { /* doSomething() */ },
+            modifier = modifier.semantics(mergeDescendants = true) {}
+        ) {
+            Icon(ColorPainter(Color.Red), null)
+        }
+    }
+
+    private fun expectedActionPosition(appBarWidth: Dp): Dp =
+        appBarWidth - AppBarStartAndEndPadding - FakeIconSize
+
+    private val FakeIconSize = 48.dp
+    private val AppBarStartAndEndPadding = 4.dp
+    private val AppBarTopAndBottomPadding = (ContainerHeight - FakeIconSize) / 2
+
+    private val LazyListTag = "lazyList"
+    private val TopAppBarTestTag = "bar"
+    private val NavigationIconTestTag = "navigationIcon"
+    private val TitleTestTag = "title"
+    private val ActionsTestTag = "actions"
+}
diff --git a/packages/SettingsLib/Spa/testutils/Android.bp b/packages/SettingsLib/Spa/testutils/Android.bp
index 2c1e1c2..e4d56cc 100644
--- a/packages/SettingsLib/Spa/testutils/Android.bp
+++ b/packages/SettingsLib/Spa/testutils/Android.bp
@@ -29,6 +29,7 @@
         "androidx.compose.runtime_runtime",
         "androidx.compose.ui_ui-test-junit4",
         "androidx.compose.ui_ui-test-manifest",
+        "androidx.lifecycle_lifecycle-runtime-testing",
         "mockito",
         "truth-prebuilt",
     ],
diff --git a/packages/SettingsLib/Spa/testutils/build.gradle b/packages/SettingsLib/Spa/testutils/build.gradle
deleted file mode 100644
index 23a9add..0000000
--- a/packages/SettingsLib/Spa/testutils/build.gradle
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * 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.
- */
-
-plugins {
-    id 'com.android.library'
-    id 'kotlin-android'
-}
-
-android {
-    namespace 'com.android.settingslib.spa.testutils'
-    compileSdk TARGET_SDK
-    buildToolsVersion = BUILD_TOOLS_VERSION
-
-    defaultConfig {
-        minSdk MIN_SDK
-        targetSdk TARGET_SDK
-    }
-
-    sourceSets {
-        main {
-            kotlin {
-                srcDir "src"
-            }
-            manifest.srcFile "AndroidManifest.xml"
-        }
-    }
-    compileOptions {
-        sourceCompatibility JavaVersion.VERSION_17
-        targetCompatibility JavaVersion.VERSION_17
-    }
-    buildFeatures {
-        compose true
-    }
-    composeOptions {
-        kotlinCompilerExtensionVersion jetpack_compose_compiler_version
-    }
-}
-
-dependencies {
-    api project(":spa")
-
-    api "androidx.arch.core:core-testing:2.2.0-alpha01"
-    api "androidx.compose.ui:ui-test-junit4:$jetpack_compose_version"
-    api "com.google.truth:truth:1.1.3"
-    api "org.mockito:mockito-core:2.21.0"
-    debugApi "androidx.compose.ui:ui-test-manifest:$jetpack_compose_version"
-}
diff --git a/packages/SettingsLib/Spa/testutils/build.gradle.kts b/packages/SettingsLib/Spa/testutils/build.gradle.kts
new file mode 100644
index 0000000..f5a22c9
--- /dev/null
+++ b/packages/SettingsLib/Spa/testutils/build.gradle.kts
@@ -0,0 +1,47 @@
+/*
+ * 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.
+ */
+
+plugins {
+    alias(libs.plugins.android.library)
+    alias(libs.plugins.kotlin.android)
+}
+
+val jetpackComposeVersion: String? by extra
+
+android {
+    namespace = "com.android.settingslib.spa.testutils"
+
+    sourceSets {
+        sourceSets.getByName("main") {
+            kotlin.setSrcDirs(listOf("src"))
+            manifest.srcFile("AndroidManifest.xml")
+        }
+    }
+    buildFeatures {
+        compose = true
+    }
+}
+
+dependencies {
+    api(project(":spa"))
+
+    api("androidx.arch.core:core-testing:2.2.0-alpha01")
+    api("androidx.compose.ui:ui-test-junit4:$jetpackComposeVersion")
+    api("androidx.lifecycle:lifecycle-runtime-testing")
+    api(libs.truth)
+    api("org.mockito:mockito-core:2.21.0")
+    debugApi("androidx.compose.ui:ui-test-manifest:$jetpackComposeVersion")
+}
diff --git a/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/ComposeContentTestRuleExt.kt b/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/ComposeContentTestRuleExt.kt
index a5d1f40..0436fc2 100644
--- a/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/ComposeContentTestRuleExt.kt
+++ b/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/ComposeContentTestRuleExt.kt
@@ -16,13 +16,28 @@
 
 package com.android.settingslib.spa.testutils
 
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.sizeIn
+import androidx.compose.material3.Surface
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.test.ComposeTimeoutException
 import androidx.compose.ui.test.SemanticsMatcher
 import androidx.compose.ui.test.SemanticsNodeInteraction
+import androidx.compose.ui.test.getUnclippedBoundsInRoot
 import androidx.compose.ui.test.hasAnyAncestor
 import androidx.compose.ui.test.hasText
 import androidx.compose.ui.test.isDialog
 import androidx.compose.ui.test.junit4.ComposeContentTestRule
+import androidx.compose.ui.test.junit4.ComposeTestRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.onRoot
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.height
+import androidx.compose.ui.unit.width
+import com.android.settingslib.spa.framework.theme.SettingsTheme
 
 /** Blocks until the found a semantics node that match the given condition. */
 fun ComposeContentTestRule.waitUntilExists(matcher: SemanticsMatcher) = waitUntil {
@@ -39,3 +54,39 @@
 /** Finds a text node that within dialog. */
 fun ComposeContentTestRule.onDialogText(text: String): SemanticsNodeInteraction =
     onNode(hasAnyAncestor(isDialog()) and hasText(text))
+
+fun ComposeTestRule.rootWidth(): Dp = onRoot().getUnclippedBoundsInRoot().width
+
+fun ComposeTestRule.rootHeight(): Dp = onRoot().getUnclippedBoundsInRoot().height
+
+/**
+ * Constant to emulate very big but finite constraints
+ */
+private val sizeAssertionMaxSize = 5000.dp
+
+private const val SIZE_ASSERTION_TAG = "containerForSizeAssertion"
+
+fun ComposeContentTestRule.setContentForSizeAssertions(
+    parentMaxWidth: Dp = sizeAssertionMaxSize,
+    parentMaxHeight: Dp = sizeAssertionMaxSize,
+    // TODO : figure out better way to make it flexible
+    content: @Composable () -> Unit
+): SemanticsNodeInteraction {
+    setContent {
+        SettingsTheme {
+            Surface {
+                Box {
+                    Box(
+                        Modifier
+                            .sizeIn(maxWidth = parentMaxWidth, maxHeight = parentMaxHeight)
+                            .testTag(SIZE_ASSERTION_TAG)
+                    ) {
+                        content()
+                    }
+                }
+            }
+        }
+    }
+
+    return onNodeWithTag(SIZE_ASSERTION_TAG)
+}
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-el/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-el/strings.xml
index ac4106a..8ac56d4 100644
--- a/packages/SettingsLib/SpaPrivileged/res/values-el/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/res/values-el/strings.xml
@@ -23,5 +23,5 @@
     <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Επιτρέπεται"</string>
     <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Δεν επιτρέπεται"</string>
     <string name="version_text" msgid="4001669804596458577">"έκδοση <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
-    <string name="cloned_app_info_label" msgid="1765651167024478391">"Κλώνος <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>"</string>
+    <string name="cloned_app_info_label" msgid="1765651167024478391">"Διπλότυπο <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>"</string>
 </resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-et/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-et/strings.xml
index a216abc..4bfc25f 100644
--- a/packages/SettingsLib/SpaPrivileged/res/values-et/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/res/values-et/strings.xml
@@ -23,5 +23,5 @@
     <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Lubatud"</string>
     <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Pole lubatud"</string>
     <string name="version_text" msgid="4001669804596458577">"versioon <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
-    <string name="cloned_app_info_label" msgid="1765651167024478391">"Üksuse <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> kloon"</string>
+    <string name="cloned_app_info_label" msgid="1765651167024478391">"Rakenduse <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> kloon"</string>
 </resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-fa/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-fa/strings.xml
index 8654c64..eaf5bcd 100644
--- a/packages/SettingsLib/SpaPrivileged/res/values-fa/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/res/values-fa/strings.xml
@@ -21,7 +21,7 @@
     <string name="menu_show_system" msgid="906304605807554788">"نمایش سیستم"</string>
     <string name="menu_hide_system" msgid="374571689914923020">"پنهان کردن سیستم"</string>
     <string name="app_permission_summary_allowed" msgid="6115213465364138103">"مجاز"</string>
-    <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"غیرمجاز"</string>
+    <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"مجاز نبودن"</string>
     <string name="version_text" msgid="4001669804596458577">"نسخه <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
-    <string name="cloned_app_info_label" msgid="1765651167024478391">"همتای <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>"</string>
+    <string name="cloned_app_info_label" msgid="1765651167024478391">"همسانه <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>"</string>
 </resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-fi/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-fi/strings.xml
index 8f42d50..b7895e2 100644
--- a/packages/SettingsLib/SpaPrivileged/res/values-fi/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/res/values-fi/strings.xml
@@ -23,5 +23,5 @@
     <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Sallittu"</string>
     <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Ei sallittu"</string>
     <string name="version_text" msgid="4001669804596458577">"versio <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
-    <string name="cloned_app_info_label" msgid="1765651167024478391">"<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> klooni"</string>
+    <string name="cloned_app_info_label" msgid="1765651167024478391">"Klooni: <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>"</string>
 </resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-ko/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-ko/strings.xml
index ef4ee0d..3e1b837 100644
--- a/packages/SettingsLib/SpaPrivileged/res/values-ko/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/res/values-ko/strings.xml
@@ -23,5 +23,5 @@
     <string name="app_permission_summary_allowed" msgid="6115213465364138103">"허용됨"</string>
     <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"허용되지 않음"</string>
     <string name="version_text" msgid="4001669804596458577">"버전 <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
-    <string name="cloned_app_info_label" msgid="1765651167024478391">"<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> 클론"</string>
+    <string name="cloned_app_info_label" msgid="1765651167024478391">"<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> 복제"</string>
 </resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-mk/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-mk/strings.xml
index 56ed2d9..fc5b94b 100644
--- a/packages/SettingsLib/SpaPrivileged/res/values-mk/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/res/values-mk/strings.xml
@@ -19,7 +19,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="no_applications" msgid="5800789569715871963">"Нема апликации."</string>
     <string name="menu_show_system" msgid="906304605807554788">"Прикажи го системот"</string>
-    <string name="menu_hide_system" msgid="374571689914923020">"Сокриј го системот"</string>
+    <string name="menu_hide_system" msgid="374571689914923020">"Скриј го системот"</string>
     <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Со дозволен пристап"</string>
     <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Без дозволен пристап"</string>
     <string name="version_text" msgid="4001669804596458577">"верзија <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-mn/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-mn/strings.xml
index 2074222..9150424 100644
--- a/packages/SettingsLib/SpaPrivileged/res/values-mn/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/res/values-mn/strings.xml
@@ -23,5 +23,5 @@
     <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Зөвшөөрсөн"</string>
     <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Зөвшөөрөөгүй"</string>
     <string name="version_text" msgid="4001669804596458577">"хувилбар <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
-    <string name="cloned_app_info_label" msgid="1765651167024478391">"<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> клон"</string>
+    <string name="cloned_app_info_label" msgid="1765651167024478391">"<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>-н хувилал"</string>
 </resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-pl/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-pl/strings.xml
index c947a66..b0fd2bf 100644
--- a/packages/SettingsLib/SpaPrivileged/res/values-pl/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/res/values-pl/strings.xml
@@ -23,5 +23,5 @@
     <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Dozwolone"</string>
     <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Niedozwolone"</string>
     <string name="version_text" msgid="4001669804596458577">"wersja <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
-    <string name="cloned_app_info_label" msgid="1765651167024478391">"Klonuj: <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>"</string>
+    <string name="cloned_app_info_label" msgid="1765651167024478391">"Klon aplikacji <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>"</string>
 </resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-ru/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-ru/strings.xml
index 3507bc7..960d94f 100644
--- a/packages/SettingsLib/SpaPrivileged/res/values-ru/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/res/values-ru/strings.xml
@@ -23,5 +23,5 @@
     <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Разрешено"</string>
     <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Запрещено"</string>
     <string name="version_text" msgid="4001669804596458577">"версия <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
-    <string name="cloned_app_info_label" msgid="1765651167024478391">"Клон приложения \"<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>\""</string>
+    <string name="cloned_app_info_label" msgid="1765651167024478391">"Клон приложения <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>"</string>
 </resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-sk/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-sk/strings.xml
index 9888125..6cca71e 100644
--- a/packages/SettingsLib/SpaPrivileged/res/values-sk/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/res/values-sk/strings.xml
@@ -23,5 +23,5 @@
     <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Povolené"</string>
     <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Nepovolené"</string>
     <string name="version_text" msgid="4001669804596458577">"verzia <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
-    <string name="cloned_app_info_label" msgid="1765651167024478391">"Klon <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>"</string>
+    <string name="cloned_app_info_label" msgid="1765651167024478391">"Klon aplikácie <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>"</string>
 </resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-sl/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-sl/strings.xml
index 74b3ffd..2451d61 100644
--- a/packages/SettingsLib/SpaPrivileged/res/values-sl/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/res/values-sl/strings.xml
@@ -23,5 +23,5 @@
     <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Dovoljeno"</string>
     <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Ni dovoljeno"</string>
     <string name="version_text" msgid="4001669804596458577">"različica <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
-    <string name="cloned_app_info_label" msgid="1765651167024478391">"Klonirani paket <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>"</string>
+    <string name="cloned_app_info_label" msgid="1765651167024478391">"Klonirana aplikacija <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>"</string>
 </resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-zh-rTW/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-zh-rTW/strings.xml
index 03efe37..1fb9492 100644
--- a/packages/SettingsLib/SpaPrivileged/res/values-zh-rTW/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/res/values-zh-rTW/strings.xml
@@ -18,7 +18,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="no_applications" msgid="5800789569715871963">"沒有應用程式。"</string>
-    <string name="menu_show_system" msgid="906304605807554788">"顯示系統程序"</string>
+    <string name="menu_show_system" msgid="906304605807554788">"顯示系統設定"</string>
     <string name="menu_hide_system" msgid="374571689914923020">"隱藏系統程序"</string>
     <string name="app_permission_summary_allowed" msgid="6115213465364138103">"允許"</string>
     <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"不允許"</string>
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt
index 2b38b4c..8e0cf89 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt
@@ -34,11 +34,11 @@
 /**
  * The repository to load the App List data.
  */
-internal interface AppListRepository {
+interface AppListRepository {
     /** Loads the list of [ApplicationInfo]. */
     suspend fun loadApps(
         userId: Int,
-        showInstantApps: Boolean = false,
+        loadInstantApps: Boolean = false,
         matchAnyUserForAdmin: Boolean = false,
     ): List<ApplicationInfo>
 
@@ -50,6 +50,9 @@
 
     /** Gets the system app package names. */
     fun getSystemPackageNamesBlocking(userId: Int): Set<String>
+
+    /** Loads the list of [ApplicationInfo], and filter base on `isSystemApp`. */
+    suspend fun loadAndFilterApps(userId: Int, isSystemApp: Boolean): List<ApplicationInfo>
 }
 
 /**
@@ -62,13 +65,13 @@
         AppListRepositoryImpl(context).getSystemPackageNamesBlocking(userId)
 }
 
-internal class AppListRepositoryImpl(private val context: Context) : AppListRepository {
+class AppListRepositoryImpl(private val context: Context) : AppListRepository {
     private val packageManager = context.packageManager
     private val userManager = context.userManager
 
     override suspend fun loadApps(
         userId: Int,
-        showInstantApps: Boolean,
+        loadInstantApps: Boolean,
         matchAnyUserForAdmin: Boolean,
     ): List<ApplicationInfo> = coroutineScope {
         val hiddenSystemModulesDeferred = async {
@@ -86,7 +89,7 @@
         val hiddenSystemModules = hiddenSystemModulesDeferred.await()
         val hideWhenDisabledPackages = hideWhenDisabledPackagesDeferred.await()
         installedApplicationsAsUser.filter { app ->
-            app.isInAppList(showInstantApps, hiddenSystemModules, hideWhenDisabledPackages)
+            app.isInAppList(loadInstantApps, hiddenSystemModules, hideWhenDisabledPackages)
         }
     }
 
@@ -136,17 +139,17 @@
     ): Flow<(app: ApplicationInfo) -> Boolean> =
         userIdFlow.combine(showSystemFlow, ::showSystemPredicate)
 
-    override fun getSystemPackageNamesBlocking(userId: Int) =
-        runBlocking { getSystemPackageNames(userId) }
+    override fun getSystemPackageNamesBlocking(userId: Int) = runBlocking {
+        loadAndFilterApps(userId = userId, isSystemApp = true).map { it.packageName }.toSet()
+    }
 
-    private suspend fun getSystemPackageNames(userId: Int): Set<String> =
-        coroutineScope {
-            val loadAppsDeferred = async { loadApps(userId) }
-            val homeOrLauncherPackages = loadHomeOrLauncherPackages(userId)
-            val showSystemPredicate =
-                { app: ApplicationInfo -> isSystemApp(app, homeOrLauncherPackages) }
-            loadAppsDeferred.await().filter(showSystemPredicate).map { it.packageName }.toSet()
+    override suspend fun loadAndFilterApps(userId: Int, isSystemApp: Boolean) = coroutineScope {
+        val loadAppsDeferred = async { loadApps(userId) }
+        val homeOrLauncherPackages = loadHomeOrLauncherPackages(userId)
+        loadAppsDeferred.await().filter { app ->
+            isSystemApp(app, homeOrLauncherPackages) == isSystemApp
         }
+    }
 
     private suspend fun showSystemPredicate(
         userId: Int,
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 a0ff216..ddb92b1 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
@@ -27,14 +27,17 @@
 import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.size
-import androidx.compose.material3.Divider
+import androidx.compose.material3.HorizontalDivider
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
 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.SettingsBody
@@ -80,11 +83,22 @@
     }
 
     @Composable
-    fun FooterAppVersion() {
+    fun FooterAppVersion(showPackageName: Boolean = rememberIsDevelopmentSettingsEnabled()) {
         if (packageInfo.versionName == null) return
-        Divider()
-        Box(modifier = Modifier.padding(SettingsDimension.itemPadding)) {
+        HorizontalDivider()
+        Column(modifier = Modifier.padding(SettingsDimension.itemPadding)) {
             SettingsBody(stringResource(R.string.version_text, packageInfo.versionNameBidiWrapped))
+            if (showPackageName) {
+                SettingsBody(packageInfo.packageName)
+            }
+        }
+    }
+
+    @Composable
+    private fun rememberIsDevelopmentSettingsEnabled(): Boolean {
+        val context = LocalContext.current
+        return remember {
+            DevelopmentSettingsEnabler.isDevelopmentSettingsEnabled(context)
         }
     }
 
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt
index 030b70a..07e4235 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt
@@ -42,7 +42,6 @@
     showInstantApps: Boolean = false,
     noMoreOptions: Boolean = false,
     matchAnyUserForAdmin: Boolean = false,
-    primaryUserOnly: Boolean = false,
     noItemMessage: String? = null,
     moreOptions: @Composable MoreOptionsScope.() -> Unit = {},
     header: @Composable () -> Unit = {},
@@ -60,7 +59,7 @@
             }
         },
     ) { bottomPadding, searchQuery ->
-        UserProfilePager(primaryUserOnly) { userGroup ->
+        UserProfilePager { userGroup ->
             val appListInput = AppListInput(
                 config = AppListConfig(
                     userIds = userGroup.userInfos.map { it.id },
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListSwitchItem.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListSwitchItem.kt
index e846687..155905b 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListSwitchItem.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListSwitchItem.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -19,26 +19,26 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.State
 import com.android.settingslib.spa.framework.theme.SettingsDimension
+import com.android.settingslib.spa.widget.preference.SwitchPreference
 import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
-import com.android.settingslib.spa.widget.preference.TwoTargetSwitchPreference
 import com.android.settingslib.spaprivileged.model.app.AppRecord
 
 @Composable
 fun <T : AppRecord> AppListItemModel<T>.AppListSwitchItem(
-    onClick: () -> Unit,
     checked: State<Boolean?>,
     changeable: State<Boolean>,
     onCheckedChange: ((newChecked: Boolean) -> Unit)?,
 ) {
-    TwoTargetSwitchPreference(
+    SwitchPreference(
         model = object : SwitchPreferenceModel {
             override val title = label
             override val summary = this@AppListSwitchItem.summary
+            override val icon = @Composable {
+                AppIcon(record.app, SettingsDimension.appIconItemSize)
+            }
             override val checked = checked
             override val changeable = changeable
             override val onCheckedChange = onCheckedChange
         },
-        icon = { AppIcon(record.app, SettingsDimension.appIconItemSize) },
-        onClick = onClick,
     )
 }
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListTwoTargetSwitchItem.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListTwoTargetSwitchItem.kt
new file mode 100644
index 0000000..99d3840
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListTwoTargetSwitchItem.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spaprivileged.template.app
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.State
+import com.android.settingslib.spa.framework.theme.SettingsDimension
+import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
+import com.android.settingslib.spa.widget.preference.TwoTargetSwitchPreference
+import com.android.settingslib.spaprivileged.model.app.AppRecord
+
+@Composable
+fun <T : AppRecord> AppListItemModel<T>.AppListTwoTargetSwitchItem(
+    onClick: () -> Unit,
+    checked: State<Boolean?>,
+    changeable: State<Boolean>,
+    onCheckedChange: ((newChecked: Boolean) -> Unit)?,
+) {
+    TwoTargetSwitchPreference(
+        model = object : SwitchPreferenceModel {
+            override val title = label
+            override val summary = this@AppListTwoTargetSwitchItem.summary
+            override val checked = checked
+            override val changeable = changeable
+            override val onCheckedChange = onCheckedChange
+        },
+        icon = { AppIcon(record.app, SettingsDimension.appIconItemSize) },
+        onClick = onClick,
+    )
+}
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 b5a4929..6a76c93 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
@@ -38,14 +38,9 @@
 )
 
 @Composable
-fun UserProfilePager(
-    primaryUserOnly: Boolean = false,
-    content: @Composable (userGroup: UserGroup) -> Unit,
-) {
+fun UserProfilePager(content: @Composable (userGroup: UserGroup) -> Unit) {
     val context = LocalContext.current
-    val userGroups = remember {
-        context.userManager.getUserGroups(primaryUserOnly)
-    }
+    val userGroups = remember { context.userManager.getUserGroups() }
     val titles = remember {
         val enterpriseRepository = EnterpriseRepository(context)
         userGroups.map { userGroup ->
@@ -60,10 +55,9 @@
     }
 }
 
-private fun UserManager.getUserGroups(primaryUserOnly: Boolean): List<UserGroup> {
+private fun UserManager.getUserGroups(): List<UserGroup> {
     val userGroupList = mutableListOf<UserGroup>()
     val profileToShowInSettingsList = getProfiles(UserHandle.myUserId())
-        .filter { userInfo -> !primaryUserOnly || userInfo.isPrimary }
         .map { userInfo -> userInfo to getUserProperties(userInfo.userHandle).showInSettings }
 
     profileToShowInSettingsList.filter { it.second == UserProperties.SHOW_IN_SETTINGS_WITH_PARENT }
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt
index 302f780..375ed60 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt
@@ -108,7 +108,7 @@
 
         val appList = repository.loadApps(
             userId = ADMIN_USER_ID,
-            showInstantApps = false,
+            loadInstantApps = false,
         )
 
         assertThat(appList).containsExactly(NORMAL_APP)
@@ -120,7 +120,7 @@
 
         val appList = repository.loadApps(
             userId = ADMIN_USER_ID,
-            showInstantApps = true,
+            loadInstantApps = true,
         )
 
         assertThat(appList).containsExactly(NORMAL_APP, INSTANT_APP)
@@ -325,6 +325,21 @@
         assertThat(systemPackageNames).containsExactly(SYSTEM_APP.packageName)
     }
 
+    @Test
+    fun loadAndFilterApps_loadNonSystemApp_returnExpectedValues() = runTest {
+        mockInstalledApplications(
+            apps = listOf(
+                NORMAL_APP, INSTANT_APP, SYSTEM_APP, UPDATED_SYSTEM_APP, HOME_APP, IN_LAUNCHER_APP
+            ),
+            userId = ADMIN_USER_ID,
+        )
+
+        val appList = repository.loadAndFilterApps(userId = ADMIN_USER_ID, isSystemApp = false)
+
+        assertThat(appList)
+            .containsExactly(NORMAL_APP, UPDATED_SYSTEM_APP, HOME_APP, IN_LAUNCHER_APP)
+    }
+
     private suspend fun getShowSystemPredicate(showSystem: Boolean) =
         repository.showSystemPredicate(
             userIdFlow = flowOf(ADMIN_USER_ID),
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt
index 6889e5d..9b22497 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt
@@ -87,7 +87,7 @@
     private object FakeAppListRepository : AppListRepository {
         override suspend fun loadApps(
             userId: Int,
-            showInstantApps: Boolean,
+            loadInstantApps: Boolean,
             matchAnyUserForAdmin: Boolean,
         ) = listOf(APP)
 
@@ -97,6 +97,9 @@
         ): Flow<(app: ApplicationInfo) -> Boolean> = flowOf { true }
 
         override fun getSystemPackageNamesBlocking(userId: Int): Set<String> = emptySet()
+
+        override suspend fun loadAndFilterApps(userId: Int, isSystemApp: Boolean) =
+            emptyList<ApplicationInfo>()
     }
 
     private object FakeAppRepository : AppRepository {
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 bb56c10..6831e56 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
@@ -93,6 +93,7 @@
         val packageInfo = PackageInfo().apply {
             applicationInfo = APP
             versionName = VERSION_NAME
+            packageName = PACKAGE_NAME
         }
         val appInfoProvider = AppInfoProvider(packageInfo)
 
@@ -105,9 +106,45 @@
         composeTestRule.onNodeWithText("version $VERSION_NAME").assertIsDisplayed()
     }
 
+    @Test
+    fun footerAppVersion_developmentEnabled_packageNameIsDisplayed() {
+        val packageInfo = PackageInfo().apply {
+            applicationInfo = APP
+            versionName = VERSION_NAME
+            packageName = PACKAGE_NAME
+        }
+        val appInfoProvider = AppInfoProvider(packageInfo)
+
+        composeTestRule.setContent {
+            CompositionLocalProvider(LocalContext provides context) {
+                appInfoProvider.FooterAppVersion(true)
+            }
+        }
+        composeTestRule.onNodeWithText(PACKAGE_NAME).assertIsDisplayed()
+    }
+
+
+    @Test
+    fun footerAppVersion_developmentDisabled_packageNameDoesNotExist() {
+        val packageInfo = PackageInfo().apply {
+            applicationInfo = APP
+            versionName = VERSION_NAME
+            packageName = PACKAGE_NAME
+        }
+        val appInfoProvider = AppInfoProvider(packageInfo)
+
+        composeTestRule.setContent {
+            CompositionLocalProvider(LocalContext provides context) {
+                appInfoProvider.FooterAppVersion(false)
+            }
+        }
+        composeTestRule.onNodeWithText(PACKAGE_NAME).assertDoesNotExist()
+    }
+
     private companion object {
         const val LABEL = "Label"
         const val VERSION_NAME = "VersionName"
+        const val PACKAGE_NAME = "package.name"
         val APP = object : ApplicationInfo() {
             override fun loadLabel(pm: PackageManager) = LABEL
         }
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListSwitchItemTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListSwitchItemTest.kt
index abdcd3b..2fd1b10 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListSwitchItemTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListSwitchItemTest.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -43,7 +43,6 @@
     fun appLabel_displayed() {
         composeTestRule.setContent {
             ITEM_MODEL.AppListSwitchItem(
-                onClick = {},
                 checked = stateOf(null),
                 changeable = stateOf(false),
                 onCheckedChange = {},
@@ -57,7 +56,6 @@
     fun summary_displayed() {
         composeTestRule.setContent {
             ITEM_MODEL.AppListSwitchItem(
-                onClick = {},
                 checked = stateOf(null),
                 changeable = stateOf(false),
                 onCheckedChange = {},
@@ -68,27 +66,9 @@
     }
 
     @Test
-    fun title_onClick() {
-        var titleClicked = false
-        composeTestRule.setContent {
-            ITEM_MODEL.AppListSwitchItem(
-                onClick = { titleClicked = true },
-                checked = stateOf(false),
-                changeable = stateOf(false),
-                onCheckedChange = {},
-            )
-        }
-
-        composeTestRule.onNodeWithText(LABEL).performClick()
-
-        assertThat(titleClicked).isTrue()
-    }
-
-    @Test
     fun switch_checkIsNull() {
         composeTestRule.setContent {
             ITEM_MODEL.AppListSwitchItem(
-                onClick = {},
                 checked = stateOf(null),
                 changeable = stateOf(false),
                 onCheckedChange = {},
@@ -102,7 +82,6 @@
     fun switch_checked() {
         composeTestRule.setContent {
             ITEM_MODEL.AppListSwitchItem(
-                onClick = {},
                 checked = stateOf(true),
                 changeable = stateOf(false),
                 onCheckedChange = {},
@@ -116,7 +95,6 @@
     fun switch_notChecked() {
         composeTestRule.setContent {
             ITEM_MODEL.AppListSwitchItem(
-                onClick = {},
                 checked = stateOf(false),
                 changeable = stateOf(false),
                 onCheckedChange = {},
@@ -130,7 +108,6 @@
     fun switch_changeable() {
         composeTestRule.setContent {
             ITEM_MODEL.AppListSwitchItem(
-                onClick = {},
                 checked = stateOf(false),
                 changeable = stateOf(true),
                 onCheckedChange = {},
@@ -144,7 +121,6 @@
     fun switch_notChangeable() {
         composeTestRule.setContent {
             ITEM_MODEL.AppListSwitchItem(
-                onClick = {},
                 checked = stateOf(false),
                 changeable = stateOf(false),
                 onCheckedChange = {},
@@ -159,7 +135,6 @@
         var switchClicked = false
         composeTestRule.setContent {
             ITEM_MODEL.AppListSwitchItem(
-                onClick = {},
                 checked = stateOf(false),
                 changeable = stateOf(true),
                 onCheckedChange = { switchClicked = true },
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTwoTargetSwitchItemTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTwoTargetSwitchItemTest.kt
new file mode 100644
index 0000000..6e7fc8e
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTwoTargetSwitchItemTest.kt
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spaprivileged.template.app
+
+import android.content.pm.ApplicationInfo
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsEnabled
+import androidx.compose.ui.test.assertIsNotEnabled
+import androidx.compose.ui.test.assertIsOff
+import androidx.compose.ui.test.assertIsOn
+import androidx.compose.ui.test.isToggleable
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performClick
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.framework.compose.stateOf
+import com.android.settingslib.spaprivileged.model.app.AppRecord
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class AppListTwoTargetSwitchItemTest {
+    @get:Rule
+    val composeTestRule = createComposeRule()
+
+    @Test
+    fun appLabel_displayed() {
+        composeTestRule.setContent {
+            ITEM_MODEL.AppListTwoTargetSwitchItem(
+                onClick = {},
+                checked = stateOf(null),
+                changeable = stateOf(false),
+                onCheckedChange = {},
+            )
+        }
+
+        composeTestRule.onNodeWithText(LABEL).assertIsDisplayed()
+    }
+
+    @Test
+    fun summary_displayed() {
+        composeTestRule.setContent {
+            ITEM_MODEL.AppListTwoTargetSwitchItem(
+                onClick = {},
+                checked = stateOf(null),
+                changeable = stateOf(false),
+                onCheckedChange = {},
+            )
+        }
+
+        composeTestRule.onNodeWithText(SUMMARY).assertIsDisplayed()
+    }
+
+    @Test
+    fun title_onClick() {
+        var titleClicked = false
+        composeTestRule.setContent {
+            ITEM_MODEL.AppListTwoTargetSwitchItem(
+                onClick = { titleClicked = true },
+                checked = stateOf(false),
+                changeable = stateOf(false),
+                onCheckedChange = {},
+            )
+        }
+
+        composeTestRule.onNodeWithText(LABEL).performClick()
+
+        assertThat(titleClicked).isTrue()
+    }
+
+    @Test
+    fun switch_checkIsNull() {
+        composeTestRule.setContent {
+            ITEM_MODEL.AppListTwoTargetSwitchItem(
+                onClick = {},
+                checked = stateOf(null),
+                changeable = stateOf(false),
+                onCheckedChange = {},
+            )
+        }
+
+        composeTestRule.onNode(isToggleable()).assertDoesNotExist()
+    }
+
+    @Test
+    fun switch_checked() {
+        composeTestRule.setContent {
+            ITEM_MODEL.AppListTwoTargetSwitchItem(
+                onClick = {},
+                checked = stateOf(true),
+                changeable = stateOf(false),
+                onCheckedChange = {},
+            )
+        }
+
+        composeTestRule.onNode(isToggleable()).assertIsOn()
+    }
+
+    @Test
+    fun switch_notChecked() {
+        composeTestRule.setContent {
+            ITEM_MODEL.AppListTwoTargetSwitchItem(
+                onClick = {},
+                checked = stateOf(false),
+                changeable = stateOf(false),
+                onCheckedChange = {},
+            )
+        }
+
+        composeTestRule.onNode(isToggleable()).assertIsOff()
+    }
+
+    @Test
+    fun switch_changeable() {
+        composeTestRule.setContent {
+            ITEM_MODEL.AppListTwoTargetSwitchItem(
+                onClick = {},
+                checked = stateOf(false),
+                changeable = stateOf(true),
+                onCheckedChange = {},
+            )
+        }
+
+        composeTestRule.onNode(isToggleable()).assertIsEnabled()
+    }
+
+    @Test
+    fun switch_notChangeable() {
+        composeTestRule.setContent {
+            ITEM_MODEL.AppListTwoTargetSwitchItem(
+                onClick = {},
+                checked = stateOf(false),
+                changeable = stateOf(false),
+                onCheckedChange = {},
+            )
+        }
+
+        composeTestRule.onNode(isToggleable()).assertIsNotEnabled()
+    }
+
+    @Test
+    fun switch_onClick() {
+        var switchClicked = false
+        composeTestRule.setContent {
+            ITEM_MODEL.AppListTwoTargetSwitchItem(
+                onClick = {},
+                checked = stateOf(false),
+                changeable = stateOf(true),
+                onCheckedChange = { switchClicked = true },
+            )
+        }
+
+        composeTestRule.onNode(isToggleable()).performClick()
+
+        assertThat(switchClicked).isTrue()
+    }
+
+    private companion object {
+        const val PACKAGE_NAME = "package.name"
+        const val LABEL = "Label"
+        const val SUMMARY = "Summary"
+        val APP = ApplicationInfo().apply {
+            packageName = PACKAGE_NAME
+        }
+        val ITEM_MODEL = AppListItemModel(
+            record = object : AppRecord {
+                override val app = APP
+            },
+            label = LABEL,
+            summary = stateOf(SUMMARY),
+        )
+    }
+}
diff --git a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/DynamicSummary.java b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/DynamicSummary.java
index c9d9b57..5b7899b 100644
--- a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/DynamicSummary.java
+++ b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/DynamicSummary.java
@@ -16,7 +16,7 @@
 
 package com.android.settingslib.drawer;
 
-/** Interface for {@link SwitchController} whose instances support dynamic summary */
+/** Interface for {@link EntryController} whose instances support dynamic summary */
 public interface DynamicSummary {
     /** @return the dynamic summary text */
     String getDynamicSummary();
diff --git a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/DynamicTitle.java b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/DynamicTitle.java
index af711dd..cb15773 100644
--- a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/DynamicTitle.java
+++ b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/DynamicTitle.java
@@ -16,7 +16,7 @@
 
 package com.android.settingslib.drawer;
 
-/** Interface for {@link SwitchController} whose instances support dynamic title */
+/** Interface for {@link EntryController} whose instances support dynamic title */
 public interface DynamicTitle {
     /** @return the dynamic title text */
     String getDynamicTitle();
diff --git a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/EntriesProvider.java b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/EntriesProvider.java
new file mode 100644
index 0000000..1c14c0a
--- /dev/null
+++ b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/EntriesProvider.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.drawer;
+
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_KEYHINT;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SUMMARY;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_TITLE;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.pm.ProviderInfo;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * An abstract class for injecting entries to Settings.
+ */
+public abstract class EntriesProvider extends ContentProvider {
+    private static final String TAG = "EntriesProvider";
+
+    public static final String METHOD_GET_ENTRY_DATA = "getEntryData";
+    public static final String METHOD_GET_PROVIDER_ICON = "getProviderIcon";
+    public static final String METHOD_GET_DYNAMIC_TITLE = "getDynamicTitle";
+    public static final String METHOD_GET_DYNAMIC_SUMMARY = "getDynamicSummary";
+    public static final String METHOD_IS_CHECKED = "isChecked";
+    public static final String METHOD_ON_CHECKED_CHANGED = "onCheckedChanged";
+
+    /**
+     * @deprecated use {@link #METHOD_GET_ENTRY_DATA} instead.
+     */
+    @Deprecated
+    public static final String METHOD_GET_SWITCH_DATA = "getSwitchData";
+
+    public static final String EXTRA_ENTRY_DATA = "entry_data";
+    public static final String EXTRA_SWITCH_CHECKED_STATE = "checked_state";
+    public static final String EXTRA_SWITCH_SET_CHECKED_ERROR = "set_checked_error";
+    public static final String EXTRA_SWITCH_SET_CHECKED_ERROR_MESSAGE = "set_checked_error_message";
+
+    /**
+     * @deprecated use {@link #EXTRA_ENTRY_DATA} instead.
+     */
+    @Deprecated
+    public static final String EXTRA_SWITCH_DATA = "switch_data";
+
+    private String mAuthority;
+    private final Map<String, EntryController> mControllerMap = new LinkedHashMap<>();
+    private final List<Bundle> mEntryDataList = new ArrayList<>();
+
+    /**
+     * Get a list of {@link EntryController} for this provider.
+     */
+    protected abstract List<? extends EntryController> createEntryControllers();
+
+    protected EntryController getController(String key) {
+        return mControllerMap.get(key);
+    }
+
+    @Override
+    public void attachInfo(Context context, ProviderInfo info) {
+        mAuthority = info.authority;
+        Log.i(TAG, mAuthority);
+        super.attachInfo(context, info);
+    }
+
+    @Override
+    public boolean onCreate() {
+        final List<? extends EntryController> controllers = createEntryControllers();
+        if (controllers == null || controllers.isEmpty()) {
+            throw new IllegalArgumentException();
+        }
+
+        for (EntryController controller : controllers) {
+            final String key = controller.getKey();
+            if (TextUtils.isEmpty(key)) {
+                throw new NullPointerException("Entry key cannot be null: "
+                        + controller.getClass().getSimpleName());
+            } else if (mControllerMap.containsKey(key)) {
+                throw new IllegalArgumentException("Entry key " + key + " is duplicated by: "
+                        + controller.getClass().getSimpleName());
+            }
+
+            controller.setAuthority(mAuthority);
+            mControllerMap.put(key, controller);
+            if (!(controller instanceof PrimarySwitchController)) {
+                mEntryDataList.add(controller.getBundle());
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public Bundle call(String method, String uriString, Bundle extras) {
+        final Bundle bundle = new Bundle();
+        final String key = extras != null
+                ? extras.getString(META_DATA_PREFERENCE_KEYHINT)
+                : null;
+        if (TextUtils.isEmpty(key)) {
+            switch (method) {
+                case METHOD_GET_ENTRY_DATA:
+                    bundle.putParcelableList(EXTRA_ENTRY_DATA, mEntryDataList);
+                    return bundle;
+                case METHOD_GET_SWITCH_DATA:
+                    bundle.putParcelableList(EXTRA_SWITCH_DATA, mEntryDataList);
+                    return bundle;
+                default:
+                    return null;
+            }
+        }
+
+        final EntryController controller = mControllerMap.get(key);
+        if (controller == null) {
+            return null;
+        }
+
+        switch (method) {
+            case METHOD_GET_ENTRY_DATA:
+            case METHOD_GET_SWITCH_DATA:
+                if (!(controller instanceof PrimarySwitchController)) {
+                    return controller.getBundle();
+                }
+                break;
+            case METHOD_GET_PROVIDER_ICON:
+                if (controller instanceof ProviderIcon) {
+                    return ((ProviderIcon) controller).getProviderIcon();
+                }
+                break;
+            case METHOD_GET_DYNAMIC_TITLE:
+                if (controller instanceof DynamicTitle) {
+                    bundle.putString(META_DATA_PREFERENCE_TITLE,
+                            ((DynamicTitle) controller).getDynamicTitle());
+                    return bundle;
+                }
+                break;
+            case METHOD_GET_DYNAMIC_SUMMARY:
+                if (controller instanceof DynamicSummary) {
+                    bundle.putString(META_DATA_PREFERENCE_SUMMARY,
+                            ((DynamicSummary) controller).getDynamicSummary());
+                    return bundle;
+                }
+                break;
+            case METHOD_IS_CHECKED:
+                if (controller instanceof ProviderSwitch) {
+                    bundle.putBoolean(EXTRA_SWITCH_CHECKED_STATE,
+                            ((ProviderSwitch) controller).isSwitchChecked());
+                    return bundle;
+                }
+                break;
+            case METHOD_ON_CHECKED_CHANGED:
+                if (controller instanceof ProviderSwitch) {
+                    return onSwitchCheckedChanged(extras.getBoolean(EXTRA_SWITCH_CHECKED_STATE),
+                            (ProviderSwitch) controller);
+                }
+                break;
+        }
+        return null;
+    }
+
+    private Bundle onSwitchCheckedChanged(boolean checked, ProviderSwitch controller) {
+        final boolean success = controller.onSwitchCheckedChanged(checked);
+        final Bundle bundle = new Bundle();
+        bundle.putBoolean(EXTRA_SWITCH_SET_CHECKED_ERROR, !success);
+        if (success) {
+            if (controller instanceof DynamicSummary) {
+                ((EntryController) controller).notifySummaryChanged(getContext());
+            }
+        } else {
+            bundle.putString(EXTRA_SWITCH_SET_CHECKED_ERROR_MESSAGE,
+                    controller.getSwitchErrorMessage(checked));
+        }
+        return bundle;
+    }
+
+    @Override
+    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+            String sortOrder) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String getType(Uri uri) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Uri insert(Uri uri, ContentValues values) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public int delete(Uri uri, String selection, String[] selectionArgs) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+        throw new UnsupportedOperationException();
+    }
+}
+
diff --git a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/EntryController.java b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/EntryController.java
new file mode 100644
index 0000000..5d6e6a3
--- /dev/null
+++ b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/EntryController.java
@@ -0,0 +1,246 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.drawer;
+
+import static com.android.settingslib.drawer.SwitchesProvider.METHOD_GET_DYNAMIC_SUMMARY;
+import static com.android.settingslib.drawer.SwitchesProvider.METHOD_GET_DYNAMIC_TITLE;
+import static com.android.settingslib.drawer.TileUtils.EXTRA_CATEGORY_KEY;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_KEY_ORDER;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON_BACKGROUND_ARGB;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON_BACKGROUND_HINT;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON_TINTABLE;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON_URI;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_KEYHINT;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_PENDING_INTENT;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SUMMARY;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SUMMARY_URI;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SWITCH_URI;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_TITLE;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_TITLE_URI;
+
+import android.app.PendingIntent;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.net.Uri;
+import android.os.Bundle;
+
+import androidx.annotation.DrawableRes;
+import androidx.annotation.NonNull;
+import androidx.annotation.StringRes;
+
+/**
+ * A controller that manages events for switch.
+ */
+public abstract class EntryController {
+
+    private String mAuthority;
+
+    /**
+     * Returns the key for this switch.
+     */
+    public abstract String getKey();
+
+    /**
+     * Returns the {@link MetaData} for this switch.
+     */
+    protected abstract MetaData getMetaData();
+
+    /**
+     * Notify registered observers that title was updated and attempt to sync changes.
+     */
+    public void notifyTitleChanged(Context context) {
+        if (this instanceof DynamicTitle) {
+            notifyChanged(context, METHOD_GET_DYNAMIC_TITLE);
+        }
+    }
+
+    /**
+     * Notify registered observers that summary was updated and attempt to sync changes.
+     */
+    public void notifySummaryChanged(Context context) {
+        if (this instanceof DynamicSummary) {
+            notifyChanged(context, METHOD_GET_DYNAMIC_SUMMARY);
+        }
+    }
+
+    void setAuthority(String authority) {
+        mAuthority = authority;
+    }
+
+    Bundle getBundle() {
+        final MetaData metaData = getMetaData();
+        if (metaData == null) {
+            throw new NullPointerException("Should not return null in getMetaData()");
+        }
+
+        final Bundle bundle = metaData.build();
+        final String uriString = new Uri.Builder()
+                .scheme(ContentResolver.SCHEME_CONTENT)
+                .authority(mAuthority)
+                .build()
+                .toString();
+        bundle.putString(META_DATA_PREFERENCE_KEYHINT, getKey());
+        if (this instanceof ProviderIcon) {
+            bundle.putString(META_DATA_PREFERENCE_ICON_URI, uriString);
+        }
+        if (this instanceof DynamicTitle) {
+            bundle.putString(META_DATA_PREFERENCE_TITLE_URI, uriString);
+        }
+        if (this instanceof DynamicSummary) {
+            bundle.putString(META_DATA_PREFERENCE_SUMMARY_URI, uriString);
+        }
+        if (this instanceof ProviderSwitch) {
+            bundle.putString(META_DATA_PREFERENCE_SWITCH_URI, uriString);
+        }
+        return bundle;
+    }
+
+    private void notifyChanged(Context context, String method) {
+        final Uri uri = TileUtils.buildUri(mAuthority, method, getKey());
+        context.getContentResolver().notifyChange(uri, null);
+    }
+
+    /**
+     * Collects all meta data of the item.
+     */
+    protected static class MetaData {
+        private String mCategory;
+        private int mOrder;
+        @DrawableRes
+        private int mIcon;
+        private int mIconBackgroundHint;
+        private int mIconBackgroundArgb;
+        private Boolean mIconTintable;
+        @StringRes
+        private int mTitleId;
+        private String mTitle;
+        @StringRes
+        private int mSummaryId;
+        private String mSummary;
+        private PendingIntent mPendingIntent;
+
+        /**
+         * @param category the category of the switch. This value must be from {@link CategoryKey}.
+         */
+        public MetaData(@NonNull String category) {
+            mCategory = category;
+        }
+
+        /**
+         * Set the order of the item that should be displayed on screen. Bigger value items displays
+         * closer on top.
+         */
+        public MetaData setOrder(int order) {
+            mOrder = order;
+            return this;
+        }
+
+        /** Set the icon that should be displayed for the item. */
+        public MetaData setIcon(@DrawableRes int icon) {
+            mIcon = icon;
+            return this;
+        }
+
+        /** Set the icon background color. The value may or may not be used by Settings app. */
+        public MetaData setIconBackgoundHint(int hint) {
+            mIconBackgroundHint = hint;
+            return this;
+        }
+
+        /** Set the icon background color as raw ARGB. */
+        public MetaData setIconBackgoundArgb(int argb) {
+            mIconBackgroundArgb = argb;
+            return this;
+        }
+
+        /** Specify whether the icon is tintable. */
+        public MetaData setIconTintable(boolean tintable) {
+            mIconTintable = tintable;
+            return this;
+        }
+
+        /** Set the title that should be displayed for the item. */
+        public MetaData setTitle(@StringRes int id) {
+            mTitleId = id;
+            return this;
+        }
+
+        /** Set the title that should be displayed for the item. */
+        public MetaData setTitle(String title) {
+            mTitle = title;
+            return this;
+        }
+
+        /** Set the summary text that should be displayed for the item. */
+        public MetaData setSummary(@StringRes int id) {
+            mSummaryId = id;
+            return this;
+        }
+
+        /** Set the summary text that should be displayed for the item. */
+        public MetaData setSummary(String summary) {
+            mSummary = summary;
+            return this;
+        }
+
+        public MetaData setPendingIntent(PendingIntent pendingIntent) {
+            mPendingIntent = pendingIntent;
+            return this;
+        }
+
+        protected Bundle build() {
+            final Bundle bundle = new Bundle();
+            bundle.putString(EXTRA_CATEGORY_KEY, mCategory);
+
+            if (mOrder != 0) {
+                bundle.putInt(META_DATA_KEY_ORDER, mOrder);
+            }
+
+            if (mIcon != 0) {
+                bundle.putInt(META_DATA_PREFERENCE_ICON, mIcon);
+            }
+            if (mIconBackgroundHint != 0) {
+                bundle.putInt(META_DATA_PREFERENCE_ICON_BACKGROUND_HINT, mIconBackgroundHint);
+            }
+            if (mIconBackgroundArgb != 0) {
+                bundle.putInt(META_DATA_PREFERENCE_ICON_BACKGROUND_ARGB, mIconBackgroundArgb);
+            }
+            if (mIconTintable != null) {
+                bundle.putBoolean(META_DATA_PREFERENCE_ICON_TINTABLE, mIconTintable);
+            }
+
+            if (mTitleId != 0) {
+                bundle.putInt(META_DATA_PREFERENCE_TITLE, mTitleId);
+            } else if (mTitle != null) {
+                bundle.putString(META_DATA_PREFERENCE_TITLE, mTitle);
+            }
+
+            if (mSummaryId != 0) {
+                bundle.putInt(META_DATA_PREFERENCE_SUMMARY, mSummaryId);
+            } else if (mSummary != null) {
+                bundle.putString(META_DATA_PREFERENCE_SUMMARY, mSummary);
+            }
+
+            if (mPendingIntent != null) {
+                bundle.putParcelable(META_DATA_PREFERENCE_PENDING_INTENT, mPendingIntent);
+            }
+
+            return bundle;
+        }
+    }
+}
diff --git a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/ProviderIcon.java b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/ProviderIcon.java
index 2945d5c..3aa6fcb 100644
--- a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/ProviderIcon.java
+++ b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/ProviderIcon.java
@@ -19,7 +19,7 @@
 import android.os.Bundle;
 
 /**
- *  Interface for {@link SwitchController} whose instances support icon provided from the content
+ *  Interface for {@link EntryController} whose instances support icon provided from the content
  *  provider
  */
 public interface ProviderIcon {
diff --git a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/ProviderSwitch.java b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/ProviderSwitch.java
new file mode 100644
index 0000000..47eb31c
--- /dev/null
+++ b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/ProviderSwitch.java
@@ -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.settingslib.drawer;
+
+/**
+ *  Interface for {@link EntryController} whose instances support switch widget provided from the
+ *  content provider
+ */
+public interface ProviderSwitch {
+    /**
+     * Returns the checked state of this switch.
+     */
+    boolean isSwitchChecked();
+
+    /**
+     * Called when the checked state of this switch is changed.
+     *
+     * @return true if the checked state was successfully changed, otherwise false
+     */
+    boolean onSwitchCheckedChanged(boolean checked);
+
+    /**
+     * Returns the error message which will be toasted when {@link #onSwitchCheckedChanged} returns
+     * false.
+     */
+    String getSwitchErrorMessage(boolean attemptedChecked);
+}
diff --git a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/ProviderTile.java b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/ProviderTile.java
index 54da585..b775e93 100644
--- a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/ProviderTile.java
+++ b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/ProviderTile.java
@@ -75,7 +75,7 @@
             if (infoList != null && !infoList.isEmpty()) {
                 final ProviderInfo providerInfo = infoList.get(0).providerInfo;
                 mComponentInfo = providerInfo;
-                setMetaData(TileUtils.getSwitchDataFromProvider(context, providerInfo.authority,
+                setMetaData(TileUtils.getEntryDataFromProvider(context, providerInfo.authority,
                         mKey));
             } else {
                 Log.e(TAG, "Cannot find package info for "
diff --git a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/SwitchController.java b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/SwitchController.java
index 23669b2..a1a4e58 100644
--- a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/SwitchController.java
+++ b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/SwitchController.java
@@ -16,38 +16,16 @@
 
 package com.android.settingslib.drawer;
 
-import static com.android.settingslib.drawer.SwitchesProvider.METHOD_GET_DYNAMIC_SUMMARY;
-import static com.android.settingslib.drawer.SwitchesProvider.METHOD_GET_DYNAMIC_TITLE;
-import static com.android.settingslib.drawer.SwitchesProvider.METHOD_IS_CHECKED;
-import static com.android.settingslib.drawer.TileUtils.EXTRA_CATEGORY_KEY;
-import static com.android.settingslib.drawer.TileUtils.META_DATA_KEY_ORDER;
-import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON;
-import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON_BACKGROUND_ARGB;
-import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON_BACKGROUND_HINT;
-import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON_TINTABLE;
-import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON_URI;
-import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_KEYHINT;
-import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SUMMARY;
-import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SUMMARY_URI;
-import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SWITCH_URI;
-import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_TITLE;
-import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_TITLE_URI;
-
-import android.content.ContentResolver;
-import android.content.Context;
-import android.net.Uri;
-import android.os.Bundle;
-
-import androidx.annotation.DrawableRes;
 import androidx.annotation.NonNull;
-import androidx.annotation.StringRes;
 
 /**
  * A controller that manages events for switch.
+ *
+ * @deprecated Use {@link EntriesProvider} with {@link ProviderSwitch} instead.
  */
-public abstract class SwitchController {
+@Deprecated
+public abstract class SwitchController extends EntryController implements ProviderSwitch {
 
-    private String mAuthority;
 
     /**
      * Returns the key for this switch.
@@ -55,11 +33,6 @@
     public abstract String getSwitchKey();
 
     /**
-     * Returns the {@link MetaData} for this switch.
-     */
-    protected abstract MetaData getMetaData();
-
-    /**
      * Returns the checked state of this switch.
      */
     protected abstract boolean isChecked();
@@ -76,181 +49,41 @@
      */
     protected abstract String getErrorMessage(boolean attemptedChecked);
 
-    /**
-     * Notify registered observers that title was updated and attempt to sync changes.
-     */
-    public void notifyTitleChanged(Context context) {
-        if (this instanceof DynamicTitle) {
-            notifyChanged(context, METHOD_GET_DYNAMIC_TITLE);
-        }
+    @Override
+    public String getKey() {
+        return getSwitchKey();
+    }
+
+    @Override
+    public boolean isSwitchChecked() {
+        return isChecked();
+    }
+
+    @Override
+    public boolean onSwitchCheckedChanged(boolean checked) {
+        return onCheckedChanged(checked);
+    }
+
+    @Override
+    public String getSwitchErrorMessage(boolean attemptedChecked) {
+        return getErrorMessage(attemptedChecked);
     }
 
     /**
-     * Notify registered observers that summary was updated and attempt to sync changes.
+     * Same as {@link EntryController.MetaData}, for backwards compatibility purpose.
+     *
+     * @deprecated Use {@link EntryController.MetaData} instead.
      */
-    public void notifySummaryChanged(Context context) {
-        if (this instanceof DynamicSummary) {
-            notifyChanged(context, METHOD_GET_DYNAMIC_SUMMARY);
-        }
-    }
-
-    /**
-     * Notify registered observers that checked state was updated and attempt to sync changes.
-     */
-    public void notifyCheckedChanged(Context context) {
-        notifyChanged(context, METHOD_IS_CHECKED);
-    }
-
-    void setAuthority(String authority) {
-        mAuthority = authority;
-    }
-
-    Bundle getBundle() {
-        final MetaData metaData = getMetaData();
-        if (metaData == null) {
-            throw new NullPointerException("Should not return null in getMetaData()");
-        }
-
-        final Bundle bundle = metaData.build();
-        final String uriString = new Uri.Builder()
-                .scheme(ContentResolver.SCHEME_CONTENT)
-                .authority(mAuthority)
-                .build()
-                .toString();
-        bundle.putString(META_DATA_PREFERENCE_KEYHINT, getSwitchKey());
-        bundle.putString(META_DATA_PREFERENCE_SWITCH_URI, uriString);
-        if (this instanceof ProviderIcon) {
-            bundle.putString(META_DATA_PREFERENCE_ICON_URI, uriString);
-        }
-        if (this instanceof DynamicTitle) {
-            bundle.putString(META_DATA_PREFERENCE_TITLE_URI, uriString);
-        }
-        if (this instanceof DynamicSummary) {
-            bundle.putString(META_DATA_PREFERENCE_SUMMARY_URI, uriString);
-        }
-        return bundle;
-    }
-
-    private void notifyChanged(Context context, String method) {
-        final Uri uri = TileUtils.buildUri(mAuthority, method, getSwitchKey());
-        context.getContentResolver().notifyChange(uri, null);
-    }
-
-    /**
-     * Collects all meta data of the item.
-     */
-    protected static class MetaData {
-        private String mCategory;
-        private int mOrder;
-        @DrawableRes
-        private int mIcon;
-        private int mIconBackgroundHint;
-        private int mIconBackgroundArgb;
-        private Boolean mIconTintable;
-        @StringRes
-        private int mTitleId;
-        private String mTitle;
-        @StringRes
-        private int mSummaryId;
-        private String mSummary;
-
+    @Deprecated
+    protected static class MetaData extends EntryController.MetaData {
         /**
          * @param category the category of the switch. This value must be from {@link CategoryKey}.
+         *
+         * @deprecated Use {@link EntryController.MetaData} instead.
          */
+        @Deprecated
         public MetaData(@NonNull String category) {
-            mCategory = category;
-        }
-
-        /**
-         * Set the order of the item that should be displayed on screen. Bigger value items displays
-         * closer on top.
-         */
-        public MetaData setOrder(int order) {
-            mOrder = order;
-            return this;
-        }
-
-        /** Set the icon that should be displayed for the item. */
-        public MetaData setIcon(@DrawableRes int icon) {
-            mIcon = icon;
-            return this;
-        }
-
-        /** Set the icon background color. The value may or may not be used by Settings app. */
-        public MetaData setIconBackgoundHint(int hint) {
-            mIconBackgroundHint = hint;
-            return this;
-        }
-
-        /** Set the icon background color as raw ARGB. */
-        public MetaData setIconBackgoundArgb(int argb) {
-            mIconBackgroundArgb = argb;
-            return this;
-        }
-
-        /** Specify whether the icon is tintable. */
-        public MetaData setIconTintable(boolean tintable) {
-            mIconTintable = tintable;
-            return this;
-        }
-
-        /** Set the title that should be displayed for the item. */
-        public MetaData setTitle(@StringRes int id) {
-            mTitleId = id;
-            return this;
-        }
-
-        /** Set the title that should be displayed for the item. */
-        public MetaData setTitle(String title) {
-            mTitle = title;
-            return this;
-        }
-
-        /** Set the summary text that should be displayed for the item. */
-        public MetaData setSummary(@StringRes int id) {
-            mSummaryId = id;
-            return this;
-        }
-
-        /** Set the summary text that should be displayed for the item. */
-        public MetaData setSummary(String summary) {
-            mSummary = summary;
-            return this;
-        }
-
-        private Bundle build() {
-            final Bundle bundle = new Bundle();
-            bundle.putString(EXTRA_CATEGORY_KEY, mCategory);
-
-            if (mOrder != 0) {
-                bundle.putInt(META_DATA_KEY_ORDER, mOrder);
-            }
-
-            if (mIcon != 0) {
-                bundle.putInt(META_DATA_PREFERENCE_ICON, mIcon);
-            }
-            if (mIconBackgroundHint != 0) {
-                bundle.putInt(META_DATA_PREFERENCE_ICON_BACKGROUND_HINT, mIconBackgroundHint);
-            }
-            if (mIconBackgroundArgb != 0) {
-                bundle.putInt(META_DATA_PREFERENCE_ICON_BACKGROUND_ARGB, mIconBackgroundArgb);
-            }
-            if (mIconTintable != null) {
-                bundle.putBoolean(META_DATA_PREFERENCE_ICON_TINTABLE, mIconTintable);
-            }
-
-            if (mTitleId != 0) {
-                bundle.putInt(META_DATA_PREFERENCE_TITLE, mTitleId);
-            } else if (mTitle != null) {
-                bundle.putString(META_DATA_PREFERENCE_TITLE, mTitle);
-            }
-
-            if (mSummaryId != 0) {
-                bundle.putInt(META_DATA_PREFERENCE_SUMMARY, mSummaryId);
-            } else if (mSummary != null) {
-                bundle.putString(META_DATA_PREFERENCE_SUMMARY, mSummary);
-            }
-            return bundle;
+            super(category);
         }
     }
 }
diff --git a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/SwitchesProvider.java b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/SwitchesProvider.java
index f2b3e30..ad00ced 100644
--- a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/SwitchesProvider.java
+++ b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/SwitchesProvider.java
@@ -16,46 +16,15 @@
 
 package com.android.settingslib.drawer;
 
-import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_KEYHINT;
-import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SUMMARY;
-import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_TITLE;
-
-import android.content.ContentProvider;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.pm.ProviderInfo;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.Bundle;
-import android.text.TextUtils;
-import android.util.Log;
-
-import java.util.ArrayList;
-import java.util.LinkedHashMap;
 import java.util.List;
-import java.util.Map;
 
 /**
  * An abstract class for injecting switches to Settings.
+ *
+ * @deprecated Use {@link EntriesProvider} instead.
  */
-public abstract class SwitchesProvider extends ContentProvider {
-    private static final String TAG = "SwitchesProvider";
-
-    public static final String METHOD_GET_SWITCH_DATA = "getSwitchData";
-    public static final String METHOD_GET_PROVIDER_ICON = "getProviderIcon";
-    public static final String METHOD_GET_DYNAMIC_TITLE = "getDynamicTitle";
-    public static final String METHOD_GET_DYNAMIC_SUMMARY = "getDynamicSummary";
-    public static final String METHOD_IS_CHECKED = "isChecked";
-    public static final String METHOD_ON_CHECKED_CHANGED = "onCheckedChanged";
-
-    public static final String EXTRA_SWITCH_DATA = "switch_data";
-    public static final String EXTRA_SWITCH_CHECKED_STATE = "checked_state";
-    public static final String EXTRA_SWITCH_SET_CHECKED_ERROR = "set_checked_error";
-    public static final String EXTRA_SWITCH_SET_CHECKED_ERROR_MESSAGE = "set_checked_error_message";
-
-    private String mAuthority;
-    private final Map<String, SwitchController> mControllerMap = new LinkedHashMap<>();
-    private final List<Bundle> mSwitchDataList = new ArrayList<>();
+@Deprecated
+public abstract class SwitchesProvider extends EntriesProvider {
 
     /**
      * Get a list of {@link SwitchController} for this provider.
@@ -63,129 +32,7 @@
     protected abstract List<SwitchController> createSwitchControllers();
 
     @Override
-    public void attachInfo(Context context, ProviderInfo info) {
-        mAuthority = info.authority;
-        Log.i(TAG, mAuthority);
-        super.attachInfo(context, info);
-    }
-
-    @Override
-    public boolean onCreate() {
-        final List<SwitchController> controllers = createSwitchControllers();
-        if (controllers == null || controllers.isEmpty()) {
-            throw new IllegalArgumentException();
-        }
-
-        controllers.forEach(controller -> {
-            final String key = controller.getSwitchKey();
-            if (TextUtils.isEmpty(key)) {
-                throw new NullPointerException("Switch key cannot be null: "
-                        + controller.getClass().getSimpleName());
-            } else if (mControllerMap.containsKey(key)) {
-                throw new IllegalArgumentException("Switch key " + key + " is duplicated by: "
-                        + controller.getClass().getSimpleName());
-            }
-
-            controller.setAuthority(mAuthority);
-            mControllerMap.put(key, controller);
-            if (!(controller instanceof PrimarySwitchController)) {
-                mSwitchDataList.add(controller.getBundle());
-            }
-        });
-        return true;
-    }
-
-    @Override
-    public Bundle call(String method, String uriString, Bundle extras) {
-        final Bundle bundle = new Bundle();
-        final String key = extras != null
-                ? extras.getString(META_DATA_PREFERENCE_KEYHINT)
-                : null;
-        if (TextUtils.isEmpty(key)) {
-            if (METHOD_GET_SWITCH_DATA.equals(method)) {
-                bundle.putParcelableList(EXTRA_SWITCH_DATA, mSwitchDataList);
-                return bundle;
-            }
-            return null;
-        }
-
-        final SwitchController controller = mControllerMap.get(key);
-        if (controller == null) {
-            return null;
-        }
-
-        switch (method) {
-            case METHOD_GET_SWITCH_DATA:
-                if (!(controller instanceof PrimarySwitchController)) {
-                    return controller.getBundle();
-                }
-                break;
-            case METHOD_GET_PROVIDER_ICON:
-                if (controller instanceof ProviderIcon) {
-                    return ((ProviderIcon) controller).getProviderIcon();
-                }
-                break;
-            case METHOD_GET_DYNAMIC_TITLE:
-                if (controller instanceof DynamicTitle) {
-                    bundle.putString(META_DATA_PREFERENCE_TITLE,
-                            ((DynamicTitle) controller).getDynamicTitle());
-                    return bundle;
-                }
-                break;
-            case METHOD_GET_DYNAMIC_SUMMARY:
-                if (controller instanceof DynamicSummary) {
-                    bundle.putString(META_DATA_PREFERENCE_SUMMARY,
-                            ((DynamicSummary) controller).getDynamicSummary());
-                    return bundle;
-                }
-                break;
-            case METHOD_IS_CHECKED:
-                bundle.putBoolean(EXTRA_SWITCH_CHECKED_STATE, controller.isChecked());
-                return bundle;
-            case METHOD_ON_CHECKED_CHANGED:
-                return onCheckedChanged(extras.getBoolean(EXTRA_SWITCH_CHECKED_STATE), controller);
-        }
-        return null;
-    }
-
-    private Bundle onCheckedChanged(boolean checked, SwitchController controller) {
-        final boolean success = controller.onCheckedChanged(checked);
-        final Bundle bundle = new Bundle();
-        bundle.putBoolean(EXTRA_SWITCH_SET_CHECKED_ERROR, !success);
-        if (success) {
-            if (controller instanceof DynamicSummary) {
-                controller.notifySummaryChanged(getContext());
-            }
-        } else {
-            bundle.putString(EXTRA_SWITCH_SET_CHECKED_ERROR_MESSAGE,
-                    controller.getErrorMessage(checked));
-        }
-        return bundle;
-    }
-
-    @Override
-    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
-            String sortOrder) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public String getType(Uri uri) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public Uri insert(Uri uri, ContentValues values) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public int delete(Uri uri, String selection, String[] selectionArgs) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
-        throw new UnsupportedOperationException();
+    protected List<? extends EntryController> createEntryControllers() {
+        return createSwitchControllers();
     }
 }
diff --git a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java
index a0c8ac4..1a938d6 100644
--- a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java
+++ b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java
@@ -19,6 +19,7 @@
 import static com.android.settingslib.drawer.TileUtils.META_DATA_KEY_ORDER;
 import static com.android.settingslib.drawer.TileUtils.META_DATA_KEY_PROFILE;
 import static com.android.settingslib.drawer.TileUtils.META_DATA_NEW_TASK;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_GROUP_KEY;
 import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON;
 import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_KEYHINT;
 import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SUMMARY;
@@ -29,6 +30,7 @@
 import static com.android.settingslib.drawer.TileUtils.PROFILE_ALL;
 import static com.android.settingslib.drawer.TileUtils.PROFILE_PRIMARY;
 
+import android.app.PendingIntent;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ComponentInfo;
@@ -47,6 +49,7 @@
 
 import java.util.ArrayList;
 import java.util.Comparator;
+import java.util.HashMap;
 
 /**
  * Description of a single dashboard tile that the user can select.
@@ -60,6 +63,8 @@
      */
     public ArrayList<UserHandle> userHandle = new ArrayList<>();
 
+    public HashMap<UserHandle, PendingIntent> pendingIntentMap = new HashMap<>();
+
     @VisibleForTesting
     long mLastUpdateTime;
     private final String mComponentPackage;
@@ -174,7 +179,8 @@
      * Check whether tile has order.
      */
     public boolean hasOrder() {
-        return mMetaData.containsKey(META_DATA_KEY_ORDER)
+        return mMetaData != null
+                && mMetaData.containsKey(META_DATA_KEY_ORDER)
                 && mMetaData.get(META_DATA_KEY_ORDER) instanceof Integer;
     }
 
@@ -186,13 +192,20 @@
     }
 
     /**
+     * Check whether tile has a pending intent.
+     */
+    public boolean hasPendingIntent() {
+        return !pendingIntentMap.isEmpty();
+    }
+
+    /**
      * Title of the tile that is shown to the user.
      */
     public CharSequence getTitle(Context context) {
         CharSequence title = null;
         ensureMetadataNotStale(context);
         final PackageManager packageManager = context.getPackageManager();
-        if (mMetaData.containsKey(META_DATA_PREFERENCE_TITLE)) {
+        if (mMetaData != null && mMetaData.containsKey(META_DATA_PREFERENCE_TITLE)) {
             if (mMetaData.containsKey(META_DATA_PREFERENCE_TITLE_URI)) {
                 // If has as uri to provide dynamic title, skip loading here. UI will later load
                 // at tile binding time.
@@ -272,10 +285,10 @@
      * Optional key to use for this tile.
      */
     public String getKey(Context context) {
+        ensureMetadataNotStale(context);
         if (!hasKey()) {
             return null;
         }
-        ensureMetadataNotStale(context);
         if (mMetaData.get(META_DATA_PREFERENCE_KEYHINT) instanceof Integer) {
             return context.getResources().getString(mMetaData.getInt(META_DATA_PREFERENCE_KEYHINT));
         } else {
@@ -395,6 +408,76 @@
         return TextUtils.equals(profile, PROFILE_PRIMARY);
     }
 
+    /**
+     * Returns whether the tile belongs to another group / category.
+     */
+    public boolean hasGroupKey() {
+        return mMetaData != null
+                && !TextUtils.isEmpty(mMetaData.getString(META_DATA_PREFERENCE_GROUP_KEY));
+    }
+
+    /**
+     * Returns the group / category key this tile belongs to.
+     */
+    public String getGroupKey() {
+        return (mMetaData == null) ? null : mMetaData.getString(META_DATA_PREFERENCE_GROUP_KEY);
+    }
+
+    /**
+     * The type of the tile.
+     */
+    public enum Type {
+        /**
+         * A preference that can be tapped on to open a new page.
+         */
+        ACTION,
+
+        /**
+         * A preference that can be tapped on to open an external app.
+         */
+        EXTERNAL_ACTION,
+
+        /**
+         * A preference that shows an on / off switch that can be toggled by the user.
+         */
+        SWITCH,
+
+        /**
+         * A preference with both an on / off switch, and a tappable area that can perform an
+         * action.
+         */
+        SWITCH_WITH_ACTION,
+
+        /**
+         * A preference category with a title that can be used to group multiple preferences
+         * together.
+         */
+        GROUP;
+    }
+
+    /**
+     * Returns the type of the tile.
+     *
+     * @see Type
+     */
+    public Type getType() {
+        boolean hasExternalAction = hasPendingIntent();
+        boolean hasAction = hasExternalAction || this instanceof ActivityTile;
+        boolean hasSwitch = hasSwitch();
+
+        if (hasSwitch && hasAction) {
+            return Type.SWITCH_WITH_ACTION;
+        } else if (hasSwitch) {
+            return Type.SWITCH;
+        } else if (hasExternalAction) {
+            return Type.EXTERNAL_ACTION;
+        } else if (hasAction) {
+            return Type.ACTION;
+        } else {
+            return Type.GROUP;
+        }
+    }
+
     public static final Comparator<Tile> TILE_COMPARATOR =
             (lhs, rhs) -> rhs.getOrder() - lhs.getOrder();
 }
diff --git a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java
index acc0087..e46db75 100644
--- a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java
+++ b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java
@@ -113,6 +113,12 @@
     public static final String META_DATA_PREFERENCE_KEYHINT = "com.android.settings.keyhint";
 
     /**
+     * Name of the meta-data item that can be set in the AndroidManifest.xml or in the content
+     * provider to specify the key of a group / category where this preference belongs to.
+     */
+    public static final String META_DATA_PREFERENCE_GROUP_KEY = "com.android.settings.group_key";
+
+    /**
      * Order of the item that should be displayed on screen. Bigger value items displays closer on
      * top.
      */
@@ -202,6 +208,13 @@
             "com.android.settings.switch_uri";
 
     /**
+     * Name of the meta-data item that can be set from the content provider providing the intent
+     * that will be executed when the user taps on the preference.
+     */
+    public static final String META_DATA_PREFERENCE_PENDING_INTENT =
+            "com.android.settings.pending_intent";
+
+    /**
      * Value for {@link #META_DATA_KEY_PROFILE}. When the device has a managed profile,
      * the app will always be run in the primary profile.
      *
@@ -331,12 +344,12 @@
                 continue;
             }
             final ProviderInfo providerInfo = resolved.providerInfo;
-            final List<Bundle> switchData = getSwitchDataFromProvider(context,
+            final List<Bundle> entryData = getEntryDataFromProvider(context,
                     providerInfo.authority);
-            if (switchData == null || switchData.isEmpty()) {
+            if (entryData == null || entryData.isEmpty()) {
                 continue;
             }
-            for (Bundle metaData : switchData) {
+            for (Bundle metaData : entryData) {
                 loadTile(user, addedCache, defaultCategory, outTiles, intent, metaData,
                         providerInfo);
             }
@@ -386,27 +399,43 @@
         if (!tile.userHandle.contains(user)) {
             tile.userHandle.add(user);
         }
+        if (metaData.containsKey(META_DATA_PREFERENCE_PENDING_INTENT)) {
+            tile.pendingIntentMap.put(
+                    user, metaData.getParcelable(META_DATA_PREFERENCE_PENDING_INTENT));
+        }
         if (!outTiles.contains(tile)) {
             outTiles.add(tile);
         }
     }
 
-    /** Returns the switch data of the key specified from the provider */
+    /** Returns the entry data of the key specified from the provider */
     // TODO(b/144732809): rearrange methods by access level modifiers
-    static Bundle getSwitchDataFromProvider(Context context, String authority, String key) {
+    static Bundle getEntryDataFromProvider(Context context, String authority, String key) {
         final Map<String, IContentProvider> providerMap = new ArrayMap<>();
-        final Uri uri = buildUri(authority, SwitchesProvider.METHOD_GET_SWITCH_DATA, key);
-        return getBundleFromUri(context, uri, providerMap, null /* bundle */);
+        final Uri uri = buildUri(authority, EntriesProvider.METHOD_GET_ENTRY_DATA, key);
+        Bundle result = getBundleFromUri(context, uri, providerMap, null /* bundle */);
+        if (result == null) {
+            Uri fallbackUri = buildUri(authority, EntriesProvider.METHOD_GET_SWITCH_DATA, key);
+            result = getBundleFromUri(context, fallbackUri, providerMap, null /* bundle */);
+        }
+        return result;
     }
 
-    /** Returns all switch data from the provider */
-    private static List<Bundle> getSwitchDataFromProvider(Context context, String authority) {
+    /** Returns all entry data from the provider */
+    private static List<Bundle> getEntryDataFromProvider(Context context, String authority) {
         final Map<String, IContentProvider> providerMap = new ArrayMap<>();
-        final Uri uri = buildUri(authority, SwitchesProvider.METHOD_GET_SWITCH_DATA);
+        final Uri uri = buildUri(authority, EntriesProvider.METHOD_GET_ENTRY_DATA);
         final Bundle result = getBundleFromUri(context, uri, providerMap, null /* bundle */);
-        return result != null
-                ? result.getParcelableArrayList(SwitchesProvider.EXTRA_SWITCH_DATA)
-                : null;
+        if (result != null) {
+            return result.getParcelableArrayList(EntriesProvider.EXTRA_ENTRY_DATA);
+        } else {
+            Uri fallbackUri = buildUri(authority, EntriesProvider.METHOD_GET_SWITCH_DATA);
+            Bundle fallbackResult =
+                    getBundleFromUri(context, fallbackUri, providerMap, null /* bundle */);
+            return fallbackResult != null
+                    ? fallbackResult.getParcelableArrayList(EntriesProvider.EXTRA_SWITCH_DATA)
+                    : null;
+        }
     }
 
     /**
diff --git a/packages/SettingsLib/Utils/src/com/android/settingslib/utils/BuildCompatUtils.java b/packages/SettingsLib/Utils/src/com/android/settingslib/utils/BuildCompatUtils.java
index ddbc907..bf3651b 100644
--- a/packages/SettingsLib/Utils/src/com/android/settingslib/utils/BuildCompatUtils.java
+++ b/packages/SettingsLib/Utils/src/com/android/settingslib/utils/BuildCompatUtils.java
@@ -17,7 +17,6 @@
 package com.android.settingslib.utils;
 
 import android.os.Build;
-import android.os.Build.VERSION;
 
 import androidx.annotation.ChecksSdkIntAtLeast;
 
diff --git a/packages/SettingsLib/res/drawable/dialog_btn_filled.xml b/packages/SettingsLib/res/drawable/dialog_btn_filled.xml
index 14cb1de9..0614f9d 100644
--- a/packages/SettingsLib/res/drawable/dialog_btn_filled.xml
+++ b/packages/SettingsLib/res/drawable/dialog_btn_filled.xml
@@ -22,12 +22,12 @@
         <item android:id="@android:id/mask">
             <shape android:shape="rectangle">
                 <solid android:color="@android:color/white"/>
-                <corners android:radius="?android:attr/buttonCornerRadius"/>
+                <corners android:radius="@dimen/button_corner_radius"/>
             </shape>
         </item>
         <item>
             <shape android:shape="rectangle">
-                <corners android:radius="?android:attr/buttonCornerRadius"/>
+                <corners android:radius="@dimen/button_corner_radius"/>
                 <solid android:color="?androidprv:attr/colorAccentPrimary"/>
                 <padding android:left="@dimen/dialog_button_horizontal_padding"
                          android:top="@dimen/dialog_button_vertical_padding"
diff --git a/packages/SettingsLib/res/drawable/dialog_btn_outline.xml b/packages/SettingsLib/res/drawable/dialog_btn_outline.xml
index 1e77759..a920b50 100644
--- a/packages/SettingsLib/res/drawable/dialog_btn_outline.xml
+++ b/packages/SettingsLib/res/drawable/dialog_btn_outline.xml
@@ -22,16 +22,15 @@
         <item android:id="@android:id/mask">
             <shape android:shape="rectangle">
                 <solid android:color="@android:color/white"/>
-                <corners android:radius="?android:attr/buttonCornerRadius"/>
+                <corners android:radius="@dimen/button_corner_radius"/>
             </shape>
         </item>
         <item>
             <shape android:shape="rectangle">
-                <corners android:radius="?android:attr/buttonCornerRadius"/>
+                <corners android:radius="@dimen/button_corner_radius"/>
                 <solid android:color="@android:color/transparent"/>
                 <stroke android:color="?androidprv:attr/colorAccentPrimaryVariant"
-                        android:width="1dp"
-                />
+                        android:width="1dp"/>
                 <padding android:left="@dimen/dialog_button_horizontal_padding"
                          android:top="@dimen/dialog_button_vertical_padding"
                          android:right="@dimen/dialog_button_horizontal_padding"
diff --git a/packages/SettingsLib/res/layout/dialog_with_icon.xml b/packages/SettingsLib/res/layout/dialog_with_icon.xml
index 54f8096..3586dcb 100644
--- a/packages/SettingsLib/res/layout/dialog_with_icon.xml
+++ b/packages/SettingsLib/res/layout/dialog_with_icon.xml
@@ -33,15 +33,14 @@
             android:importantForAccessibility="no"/>
         <TextView
             android:id="@+id/dialog_with_icon_title"
-            android:layout_width="wrap_content"
+            android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:gravity="center"
             style="@style/DialogWithIconTitle"/>
         <TextView
             android:id="@+id/dialog_with_icon_message"
-            android:layout_width="wrap_content"
+            android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:padding="10dp"
             android:gravity="center"
             style="@style/TextAppearanceSmall"/>
 
@@ -64,7 +63,6 @@
                 android:id="@+id/button_cancel"
                 style="@style/DialogButtonNegative"
                 android:layout_width="wrap_content"
-                android:buttonCornerRadius="28dp"
                 android:layout_height="wrap_content"
                 android:visibility="gone"/>
 
@@ -79,7 +77,6 @@
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 style="@style/DialogButtonNegative"
-                android:buttonCornerRadius="40dp"
                 android:visibility="gone"/>
 
             <Space
@@ -93,8 +90,7 @@
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 style="@style/DialogButtonPositive"
-                android:visibility="gone"
-            />
+                android:visibility="gone"/>
         </LinearLayout>
     </LinearLayout>
 </ScrollView>
diff --git a/packages/SettingsLib/res/layout/edit_user_info_dialog_content.xml b/packages/SettingsLib/res/layout/edit_user_info_dialog_content.xml
index 4ffaf1b..2ded3c6 100644
--- a/packages/SettingsLib/res/layout/edit_user_info_dialog_content.xml
+++ b/packages/SettingsLib/res/layout/edit_user_info_dialog_content.xml
@@ -14,62 +14,48 @@
      limitations under the License.
 -->
 
-<ScrollView
+<LinearLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/user_info_editor"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:id="@+id/user_info_scroll"
-    android:padding="16dp">
-
-    <LinearLayout
-        android:layout_width="match_parent"
+    android:baselineAligned="false"
+    android:orientation="vertical">
+    <FrameLayout
+        android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:baselineAligned="false"
-        android:orientation="vertical">
-        <TextView
-            android:id="@+id/user_info_title"
-            android:gravity="center"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            style="@style/EditUserDialogTitle"
-            android:text="@string/user_info_settings_title"
-            android:textDirection="locale"/>
-        <FrameLayout
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_gravity="center">
-            <ImageView
-                android:id="@+id/user_photo"
-                android:layout_width="@dimen/user_photo_size_in_user_info_dialog"
-                android:layout_height="@dimen/user_photo_size_in_user_info_dialog"
-                android:contentDescription="@string/user_image_photo_selector"
-                android:scaleType="fitCenter"/>
-            <ImageView
-                android:id="@+id/add_a_photo_icon"
-                android:layout_width="@dimen/add_a_photo_icon_size_in_user_info_dialog"
-                android:layout_height="@dimen/add_a_photo_icon_size_in_user_info_dialog"
-                android:src="@drawable/add_a_photo_circled"
-                android:layout_gravity="bottom|right"/>
-        </FrameLayout>
+        android:layout_gravity="center">
+        <ImageView
+            android:id="@+id/user_photo"
+            android:layout_width="@dimen/user_photo_size_in_user_info_dialog"
+            android:layout_height="@dimen/user_photo_size_in_user_info_dialog"
+            android:contentDescription="@string/user_image_photo_selector"
+            android:scaleType="fitCenter"/>
+        <ImageView
+            android:id="@+id/add_a_photo_icon"
+            android:layout_width="@dimen/add_a_photo_icon_size_in_user_info_dialog"
+            android:layout_height="@dimen/add_a_photo_icon_size_in_user_info_dialog"
+            android:src="@drawable/add_a_photo_circled"
+            android:layout_gravity="bottom|right"/>
+    </FrameLayout>
 
-        <EditText
-            android:id="@+id/user_name"
-            android:layout_width="match_parent"
-            android:layout_height="@dimen/user_name_height_in_user_info_dialog"
-            android:layout_gravity="center"
-            android:minWidth="200dp"
-            android:layout_marginStart="6dp"
-            android:minHeight="@dimen/min_tap_target_size"
-            android:ellipsize="end"
-            android:singleLine="true"
-            android:textAppearance="?android:attr/textAppearanceMedium"
-            android:textAlignment="viewStart"
-            android:inputType="text|textCapWords"
-            android:selectAllOnFocus="true"
-            android:hint="@string/user_nickname"
-            android:maxLength="100"/>
+    <EditText
+        android:id="@+id/user_name"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/user_name_height_in_user_info_dialog"
+        android:layout_gravity="center"
+        android:minWidth="200dp"
+        android:layout_marginStart="6dp"
+        android:minHeight="@dimen/min_tap_target_size"
+        android:ellipsize="end"
+        android:singleLine="true"
+        android:textAppearance="?android:attr/textAppearanceMedium"
+        android:textAlignment="viewStart"
+        android:inputType="text|textCapWords"
+        android:selectAllOnFocus="true"
+        android:hint="@string/user_nickname"
+        android:maxLength="100"/>
 
-    </LinearLayout>
+</LinearLayout>
 
-</ScrollView>
 
diff --git a/packages/SettingsLib/res/values-af/strings.xml b/packages/SettingsLib/res/values-af/strings.xml
index 98ad6e3..a005693 100644
--- a/packages/SettingsLib/res/values-af/strings.xml
+++ b/packages/SettingsLib/res/values-af/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"SIM-toegang"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD-oudio: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD oudio"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Gehoortoestelle"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Gehoortoestelle"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE-oudio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Gekoppel aan gehoortoestelle"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Gekoppel aan gehoortoestelle"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Gekoppel aan LE-oudio"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Gekoppel aan media-oudio"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Gekoppel aan foonoudio"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Gebruik vir foonoudio"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Gebruik vir lêeroordrag"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Gebruik vir invoer"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Gebruik vir gehoortoestelle"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Gebruik vir gehoortoestelle"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Gebruik vir LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Bind saam"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"BIND SAAM"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Skermlaag wys huidige raakdata"</string>
     <string name="show_touches" msgid="8437666942161289025">"Wys tikke"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Wys visuele terugvoer vir tikke"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Wys sleuteldrukke"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Wys visuele terugvoer vir fisieke sleuteldrukke"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Wys oppervlakopdaterings"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Flits totale vensteroppervlakke wanneer dit opdateer"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Wys aansigopdaterings"</string>
diff --git a/packages/SettingsLib/res/values-am/strings.xml b/packages/SettingsLib/res/values-am/strings.xml
index b8b1b69..28ca2bb 100644
--- a/packages/SettingsLib/res/values-am/strings.xml
+++ b/packages/SettingsLib/res/values-am/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"የሲም መዳረሻ"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"ኤችዲ ኦዲዮ፦ <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"ኤችዲ ኦዲዮ"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"አጋዥ መስሚያዎች"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"መስሚያ አጋዥ መሣሪያዎች"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE ኦዲዮ"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"ከአጋዥ መስሚያዎች ጋር ተገናኝቷል"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"ከመስሚያ አጋዥ መሣሪያዎች ጋር ተገናኝቷል"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"ከLE ኦዲዮ ጋር ተገናኝቷል"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"ወደ ማህደረ  መረጃ  አውዲዮ ተያይዟል"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"ወደ ስልክ አውዲዮ ተያይዟል"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"ለስልክ ድምፅ ተጠቀም"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"ለፋይል ዝውውር ተጠቀም"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"ለውፅአት ተጠቀም"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"ለአጋዥ መስሚያዎች ይጠቀሙ"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"ለመስሚያ አጋዥ መሣሪያዎች ይጠቀሙ"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"ለLE_AUDIO ይጠቀሙ"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"አጣምር"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"አጣምር"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"የማያ ተደራቢ የአሁኑን የCPU አጠቃቀም  እያሳየ ነው።"</string>
     <string name="show_touches" msgid="8437666942161289025">"ነካ ማድረጎችን አሳይ"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"ለነካ ማድረጎች ምስላዊ ግብረመልስን አሳይ"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"የቁልፍ ጭነቶችን አሳይ"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"ለአካላዊ የቁልፍ ጭነቶች የሚታይ ግብረመልስን አሳይ"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"የወለል ዝማኔዎችን አሳይ"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"የመስኮት ወለሎች ሲዘምኑ መላ መስኮቱን አብለጭልጭ"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"የእይታ ዝማኔዎችን አሳይ"</string>
diff --git a/packages/SettingsLib/res/values-ar/strings.xml b/packages/SettingsLib/res/values-ar/strings.xml
index 18dc07f..aff5c81 100644
--- a/packages/SettingsLib/res/values-ar/strings.xml
+++ b/packages/SettingsLib/res/values-ar/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"‏الوصول إلى شريحة SIM"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"صوت عالي الدقة: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"صوت عالي الدقة"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"سماعات الأذن الطبية"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"سماعات الأذن الطبية"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"تمّ التوصيل بسماعات الأذن الطبية"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"تم توصيل سماعات الأذن الطبية"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"‏متصل بـ LE Audio"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"متصل بالإعدادات الصوتية للوسائط"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"متصل بالإعدادات الصوتية للهاتف"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"الاستخدام لإعدادات الهاتف الصوتية"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"استخدامه لنقل الملفات"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"استخدام للإدخال"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"استخدام سماعات الأذن الطبية"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"استخدام سماعات الأذن الطبية"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"‏الاستخدام لـ LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"إقران"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"إقران"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"عرض بيانات اللمس الحالية فوق المحتوى على الشاشة"</string>
     <string name="show_touches" msgid="8437666942161289025">"عرض النقرات"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"عرض التعليقات المرئية للنقرات"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"عرض الضغطات على المفاتيح"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"عرض الملاحظات المرئية للضغطات الفعلية على المفاتيح"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"عرض تحديثات السطح"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"وميض أسطح النوافذ بالكامل عندما يتم تحديثها"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"إظهار تحديثات العرض"</string>
diff --git a/packages/SettingsLib/res/values-as/strings.xml b/packages/SettingsLib/res/values-as/strings.xml
index 725a057..c1128ae 100644
--- a/packages/SettingsLib/res/values-as/strings.xml
+++ b/packages/SettingsLib/res/values-as/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"ছিমৰ এক্সেছ"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"এইচ্ছডি অডি\'অ: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"এইচ্ছডি অডিঅ’"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"শ্ৰৱণ যন্ত্ৰ"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"শ্ৰৱণ যন্ত্ৰ"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE অডিঅ’"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"শ্ৰৱণ যন্ত্ৰলৈ সংযোগ কৰা হৈছে"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"শ্ৰৱণ যন্ত্ৰৰ সৈতে সংযোগ কৰা হৈছে"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"LE অডিঅ’ৰ সৈতে সংযোগ কৰক"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"মিডিয়া অডিঅ’লৈ সংযোগ হৈছে"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"ফ’ন অডিঅ\'ৰ লগত সংযোগ কৰা হ’ল"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"ফ\'ন অডিঅ\'ৰ বাবে ব্যৱহাৰ কৰক"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"ফাইল স্থানান্তৰ কৰিবলৈ ব্যৱহাৰ কৰক"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"ইনপুটৰ বাবে ব্যৱহাৰ কৰক"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"শ্ৰৱণ যন্ত্ৰৰ বাবে ব্যৱহাৰ কৰক"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"শ্ৰৱণ যন্ত্ৰৰ বাবে ব্যৱহাৰ কৰক"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"LE_AUDIOৰ বাবে ব্যৱহাৰ কৰক"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"পেয়াৰ কৰক"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"পেয়াৰ কৰক"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"চলিত স্পৰ্শ-বিষয়ক তথ্যসহ স্ক্ৰীন অভাৰলে’"</string>
     <string name="show_touches" msgid="8437666942161289025">"টেপসমূহ দেখুৱাওক"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"টিপিলে দৃশ্যায়িত ফীডবেক দিয়ক"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"কী টিপা দেখুৱাওক"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"ভৌতিক কী টিপাৰ ভিজুৱেল প্ৰতিক্ৰিয়া দেখুৱাওক"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"পৃষ্ঠভাগৰ আপডে’ট দেখুৱাওক"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"আপডে’ট হওতে গোটেই ৱিণ্ড পৃষ্ঠসমূহ ফ্লাশ্ব কৰক"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"ভিউৰ আপডে’ট দেখুৱাওক"</string>
diff --git a/packages/SettingsLib/res/values-az/strings.xml b/packages/SettingsLib/res/values-az/strings.xml
index ec1b8cf..57b4694 100644
--- a/packages/SettingsLib/res/values-az/strings.xml
+++ b/packages/SettingsLib/res/values-az/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"SIM-karta giriş"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD audio: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD audio"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Eşitmə cihazları"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Eşitmə aparatları"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Eşitmə Aparatlarına qoşuldu"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Eşitmə aparatlarına qoşuldu"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"LE audiosuna qoşulub"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Media audioya birləşdirilib"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Telefon audiosuna qoşulu"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Telefon audiosu istifadə edin"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Fayl transferi üçün istifadə edin"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Daxiletmə üçün istifadə edin"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Eşitmə Aparatları üçün istifadə edin"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Eşitmə aparatları üçün istifadə edin"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"LE_AUDIO üçün istifadə edin"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Qoşulsun"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"QOŞULSUN"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Toxunuş və jest datası göstərilsin"</string>
     <string name="show_touches" msgid="8437666942161289025">"Vizual reaksiya"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Toxunuşa vizual reaksiya verilsin"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Düyməyə basma göstərilsin"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Fiziki düymə basılmaları üçün vizual rəy göstərin"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Səth yenilənməsi göstərilsin"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Pəncərə səthi təzələnəndə işıqlansın"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Baxış yenilənməsi göstərilsin"</string>
diff --git a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
index 91ca8a6..e9e0e90 100644
--- a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
+++ b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"Pristup SIM kartici"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD zvuk: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD zvuk"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Slušni aparati"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Slušni aparati"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Povezano sa slušnim aparatima"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Povezano sa slušnim aparatima"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Povezano sa LE audio"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Povezano sa zvukom medija"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Povezano sa zvukom telefona"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Korišćenje za audio telefona"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Korišćenje za prenos datoteka"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Koristi za ulaz"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Koristi za slušne aparate"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Koristi za slušne aparate"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Koristite za LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Upari"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"UPARI"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Preklopni element sa trenutnim podacima o dodiru"</string>
     <string name="show_touches" msgid="8437666942161289025">"Prikazuj dodire"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Prikazuje vizuelne povratne informacije za dodire"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Prikazuj pritiske tastera"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Prikazuje povratne informacije za pritiske tastera"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Prikaži ažuriranja površine"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Osvetljava sve površine prozora kada se ažuriraju"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Prikaži ažuriranja prikaza"</string>
diff --git a/packages/SettingsLib/res/values-be/strings.xml b/packages/SettingsLib/res/values-be/strings.xml
index 71aac42..0f6112c 100644
--- a/packages/SettingsLib/res/values-be/strings.xml
+++ b/packages/SettingsLib/res/values-be/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"Доступ да SIM-карты"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"Аўдыя ў HD: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"Аўдыя ў HD"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Слыхавыя апараты"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Слыхавыя апараты"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Падключана да слыхавых апаратаў"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Слыхавыя апараты падключаны"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Падключана да LE audio"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Падключана да аўдыё медыа"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Падключана да аўдыё тэлефона"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Выкарыстоўваць для аўдыё тэлефона"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Выкарыстоўваць для перадачы файлаў"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Выкарыстоўваць для ўводу"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Выкарыстоўваць для слыхавых апаратаў"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Выкарыстоўваць для слыхавых апаратаў"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Выкарыстоўваць для LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Спалучыць"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"СПАЛУЧЫЦЬ"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Паказваць на экране націсканні і жэсты"</string>
     <string name="show_touches" msgid="8437666942161289025">"Паказваць дакрананні"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Паказваць візуалізацыю дакрананняў"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Паказваць націсканні"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Паказваць візуальны водгук пры націсканні клавіш"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Абнаўленне паверхні"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Падсвяціць паверхню акна пры абнаўленні"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Паказаць абнаўленні"</string>
diff --git a/packages/SettingsLib/res/values-bg/strings.xml b/packages/SettingsLib/res/values-bg/strings.xml
index 2779689..abd90d0 100644
--- a/packages/SettingsLib/res/values-bg/strings.xml
+++ b/packages/SettingsLib/res/values-bg/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"Достъп до SIM картата"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"Висококачествено аудио: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"Висококачествено аудио"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Слухови апарати"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Слухови апарати"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Установена е връзка със слухов апарат"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Установена е връзка със слухов апарат"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Свързано с LE Audio"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Установена е връзка с медийно аудио"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Връзка със звука на телефона"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Използване на телефон за аудио"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Използване на за пренос на файлове"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Да се използва за въвеждане"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Използване за слухови апарати"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Използване за слухови апарати"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Използване за LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Сдвояване"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"СДВОЯВАНЕ"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Насл. на екран показва текущи данни при докосване"</string>
     <string name="show_touches" msgid="8437666942161289025">"Показване на докосванията"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Показване на визуална обр. връзка за докосванията"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Видими натиск. на клавиши"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Визуална обратна връзка при натискане на клавиши"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Актуализации на повърхн."</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Примигв. на целите повърхности на прозорците при актуализирането им"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Актуализации на изгледите"</string>
diff --git a/packages/SettingsLib/res/values-bn/strings.xml b/packages/SettingsLib/res/values-bn/strings.xml
index 68283ca..b037b13 100644
--- a/packages/SettingsLib/res/values-bn/strings.xml
+++ b/packages/SettingsLib/res/values-bn/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"সিম অ্যাক্সেস"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD অডিও: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD অডিও"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"হিয়ারিং এড"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"হিয়ারিং এড"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE অডিও"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"হিয়ারিং এডের সাথে কানেক্ট করা হয়েছে"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"হিয়ারিং এডের সাথে কানেক্ট করা হয়েছে"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"LE অডিও কানেক্ট করা হয়েছে"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"মিডিয়া অডিওতে কানেক্ট রয়েছে"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"ফোন অডিওতে কানেক্ট"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"ফোন অডিওয়ের জন্য ব্যবহার করুন"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"ফাইল স্থানান্তরের জন্য ব্যবহার করুন"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"ইনপুটের জন্য ব্যবহার করুন"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"হিয়ারিং এডের জন্য ব্যবহার করুন"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"হিয়ারিং এডের জন্য ব্যবহার করুন"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"LE_AUDIO-এর জন্য ব্যবহার করুন"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"যুক্ত করুন"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"যুক্ত করুন"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"স্ক্রিন ওভারলে বর্তমান স্পর্শ ডেটা দেখাচ্ছে"</string>
     <string name="show_touches" msgid="8437666942161289025">"আলতো চাপ দেখান"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"আলতো চাপ দিলে ভিজ্যুয়াল প্রতিক্রিয়া দেখান"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"প্রেস করা কী দেখুন"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"প্রেস করা ফিজিকাল কীয়ের জন্য ভিস্যুয়াল মতামত দেখুন"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"সারফেস আপডেটগুলি দেখান"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"সম্পূর্ণ উইন্ডোর সারফেস আপডেট হয়ে গেলে সেটিকে ফ্ল্যাশ করুন"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"ভিউয়ের আপডেট দেখুন"</string>
diff --git a/packages/SettingsLib/res/values-bs/strings.xml b/packages/SettingsLib/res/values-bs/strings.xml
index 8e589f6..1a37a3f 100644
--- a/packages/SettingsLib/res/values-bs/strings.xml
+++ b/packages/SettingsLib/res/values-bs/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"Pristup SIM-u"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD audio: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD audio"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Slušni aparati"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Slušni aparati"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Povezan na slušne aparate"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Povezano je sa slušnim aparatima"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Povezano s LE zvukom"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Povezano sa zvukom medija"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Povezano na zvuk telefona"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Koristi za zvuk telefona"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Koristi za prijenos fajlova"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Koristi kao ulaz"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Korištenje za slušne aparate"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Korištenje za slušne aparate"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Koristi za: LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Upari"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"UPARI"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Preklapanje ekrana s trenutnim podacima o dodiru"</string>
     <string name="show_touches" msgid="8437666942161289025">"Prikaži dodire"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Prikaz vizuelnih povratnih informacija za dodire"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Prikaži pritiskanja tipki"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Prikaži vizuelne povr. inform. za pritiske tipki"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Prikaži ažuriranja za površinu"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Osvjetljava sve površine prozora kada se ažuriraju"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Prikaži ažuriranja prikaza"</string>
diff --git a/packages/SettingsLib/res/values-ca/strings.xml b/packages/SettingsLib/res/values-ca/strings.xml
index f22b489b8..233b3e1 100644
--- a/packages/SettingsLib/res/values-ca/strings.xml
+++ b/packages/SettingsLib/res/values-ca/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"Accés a la SIM"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"Àudio HD: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"Àudio HD"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Audiòfons"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Audiòfons"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"S\'ha connectat als audiòfons"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"S\'ha connectat als audiòfons"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Connectat a LE Audio"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Connectat a l\'àudio del mitjà"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Connectat a àudio del telèfon"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Utilitza-ho per a l\'àudio del telèfon"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Utilitza per a la transferència de fitxers"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Utilitza per a entrada"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Utilitza per als audiòfons"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Utilitza per als audiòfons"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Utilitza per a LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Vincula"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"VINCULA"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Superposa les dades dels tocs a la pantalla"</string>
     <string name="show_touches" msgid="8437666942161289025">"Mostra els tocs"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Mostra la ubicació visual dels tocs"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Mostra les tecles premudes"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Mostra avisos visuals en prémer tecles físiques"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Canvis de superfície"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Il·lumina superfícies de finestres en actualitzar-se"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Actualitzacions de visualitzacions"</string>
diff --git a/packages/SettingsLib/res/values-cs/strings.xml b/packages/SettingsLib/res/values-cs/strings.xml
index 363c22e..41d3d52 100644
--- a/packages/SettingsLib/res/values-cs/strings.xml
+++ b/packages/SettingsLib/res/values-cs/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"Přístup k SIM kartě"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD zvuk: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD zvuk"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Naslouchátka"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Naslouchátka"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Připojeno k naslouchátkům"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Připojeno k naslouchátkům"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Připojeno k LE Audio"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Připojeno ke zvukovému médiu"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Připojeno k náhlavní soupravě"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Umožňuje připojení náhlavní soupravy"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Použít pro přenos souborů"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Použít pro vstup"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Použít pro naslouchátka"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Použít pro naslouchátka"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Používat pro LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Spárovat"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"SPÁROVAT"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Překryvná vrstva zobrazuje aktuální data o dotycích"</string>
     <string name="show_touches" msgid="8437666942161289025">"Zobrazovat klepnutí"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Zobrazovat vizuální zpětnou vazbu pro klepnutí"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Zobrazit stisknutí klávesy"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Zobrazit vizuální odezvu při stisknutí fyzické klávesy"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Zobrazit obnovení obsahu"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Rozbliká obsah okna při aktualizaci"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Ukazovat aktualizace zobrazení"</string>
diff --git a/packages/SettingsLib/res/values-da/strings.xml b/packages/SettingsLib/res/values-da/strings.xml
index 1df4dee..30022d3 100644
--- a/packages/SettingsLib/res/values-da/strings.xml
+++ b/packages/SettingsLib/res/values-da/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"Adgang til SIM-kort"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD-lyd: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD-lyd"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Høreapparater"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Høreapparater"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE-lyd"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Forbundet til høreapparater"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Forbundet til høreapparater"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Forbundet med LE-lyd"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Forbundet til medielyd"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Forbundet til telefonlyd"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Brug til telefonlyd"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Brug til filoverførsel"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Brug til input"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Brug til høreapparater"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Brug til høreapparater"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Brug med LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Par"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"ACCEPTÉR PARRING"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Skærmoverlejringen viser de aktuelle berøringsdata"</string>
     <string name="show_touches" msgid="8437666942161289025">"Vis tryk"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Vis visuel feedback ved tryk"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Vis tastetryk"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Vis visuel feedback ved fysiske tastetryk"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Vis overfladeopdateringer"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Fremhæv hele vinduesoverflader, når de opdateres"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Se visningsopdateringer"</string>
diff --git a/packages/SettingsLib/res/values-de/strings.xml b/packages/SettingsLib/res/values-de/strings.xml
index bf5a8b7..e9d885a 100644
--- a/packages/SettingsLib/res/values-de/strings.xml
+++ b/packages/SettingsLib/res/values-de/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"Zugriff auf SIM"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD-Audio: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD-Audio"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Hörgeräte"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Hörgerät"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Mit Hörgeräten verbunden"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Mit Hörgerät verbunden"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Mit LE Audio verbunden"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Verbunden mit Medien-Audio"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Verbunden mit Telefon-Audio"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Für Telefon-Audio verwenden"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Für Dateiübertragung verwenden"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Für Eingabe verwenden"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Für Hörgeräte verwenden"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Für Hörgerät verwenden"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Für LE_AUDIO verwenden"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Koppeln"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"KOPPELN"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Overlay mit aktuellen Daten zu Tippaktionen anzeigen"</string>
     <string name="show_touches" msgid="8437666942161289025">"Fingertippen visualisieren"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Bei Fingertippen visuelles Feedback anzeigen"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Tastatureingaben anzeigen"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Visuelles Feedback für Tastatureingaben anzeigen"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Oberflächenaktualisierungen hervorheben"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Gesamte Fensteroberflächen blinken bei Aktualisierung"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Aktualisierungen von Ansichten hervorheben"</string>
diff --git a/packages/SettingsLib/res/values-el/strings.xml b/packages/SettingsLib/res/values-el/strings.xml
index 8f75180..19413c1 100644
--- a/packages/SettingsLib/res/values-el/strings.xml
+++ b/packages/SettingsLib/res/values-el/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"Πρόσβαση SIM"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"Ήχος HD: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"Ήχος HD"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Βοηθήματα ακοής"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Βοηθήματα ακοής"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Έγινε σύνδεση σε βοηθήματα ακοής"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Σε σύνδεση με βοηθήματα ακοής"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Συνδέθηκε σε LE Audio"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Συνδέθηκε σε ήχο πολυμέσων"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Συνδεδεμένο στον ήχο τηλεφώνου"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Χρήση για ήχο τηλεφώνου"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Χρήση για τη μεταφορά αρχείων"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Χρήση για είσοδο"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Χρήση για βοηθήματα ακοής"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Χρήση για βοηθήματα ακοής"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Χρήση για LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Σύζευξη"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"ΣΥΖΕΥΞΗ"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Επικάλ.οθόνης για προβολή τρεχόντων δεδ/νων αφής"</string>
     <string name="show_touches" msgid="8437666942161289025">"Εμφάνιση πατημάτων"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Εμφάνιση οπτικών σχολίων για πατήματα"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Εμφάν. πατημάτων πλήκτρων"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Εμφ. οπτικής ανάδρασης για πατήμ. φυσικών πλήκτρων"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Εμφάνιση ενημερώσεων επιφάνειας"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Προβολή Flash ολόκλ. των επιφ παραθ. όταν ενημερ."</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Εμφάνιση ενημερ. προβολής"</string>
diff --git a/packages/SettingsLib/res/values-en-rAU/strings.xml b/packages/SettingsLib/res/values-en-rAU/strings.xml
index 3a50fa3..58ea681 100644
--- a/packages/SettingsLib/res/values-en-rAU/strings.xml
+++ b/packages/SettingsLib/res/values-en-rAU/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"SIM access"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD audio: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD audio"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Hearing Aids"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Hearing aids"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Connected to Hearing Aids"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Connected to hearing aids"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Connected to LE audio"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Connected to media audio"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Connected to phone audio"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Use for phone audio"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Use for file transfer"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Use for input"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Use for Hearing Aids"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Use for hearing aids"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Use for LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Pair"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"PAIR"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Screen overlay showing current touch data"</string>
     <string name="show_touches" msgid="8437666942161289025">"Show taps"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Show visual feedback for taps"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Show key presses"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Show visual feedback for physical key presses"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Show surface updates"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Flash entire window surfaces when they update"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Show view updates"</string>
diff --git a/packages/SettingsLib/res/values-en-rCA/strings.xml b/packages/SettingsLib/res/values-en-rCA/strings.xml
index 9ee30e1..aab6224 100644
--- a/packages/SettingsLib/res/values-en-rCA/strings.xml
+++ b/packages/SettingsLib/res/values-en-rCA/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"SIM Access"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD audio: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD audio"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Hearing Aids"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Hearing aids"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Connected to Hearing Aids"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Connected to hearing aids"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Connected to LE audio"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Connected to media audio"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Connected to phone audio"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Use for phone audio"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Use for file transfer"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Use for input"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Use for Hearing Aids"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Use for hearing aids"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Use for LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Pair"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"PAIR"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Screen overlay showing current touch data"</string>
     <string name="show_touches" msgid="8437666942161289025">"Show taps"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Show visual feedback for taps"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Show key presses"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Show visual feedback for physical key presses"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Show surface updates"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Flash entire window surfaces when they update"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Show view updates"</string>
diff --git a/packages/SettingsLib/res/values-en-rGB/strings.xml b/packages/SettingsLib/res/values-en-rGB/strings.xml
index 3a50fa3..58ea681 100644
--- a/packages/SettingsLib/res/values-en-rGB/strings.xml
+++ b/packages/SettingsLib/res/values-en-rGB/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"SIM access"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD audio: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD audio"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Hearing Aids"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Hearing aids"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Connected to Hearing Aids"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Connected to hearing aids"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Connected to LE audio"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Connected to media audio"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Connected to phone audio"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Use for phone audio"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Use for file transfer"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Use for input"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Use for Hearing Aids"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Use for hearing aids"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Use for LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Pair"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"PAIR"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Screen overlay showing current touch data"</string>
     <string name="show_touches" msgid="8437666942161289025">"Show taps"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Show visual feedback for taps"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Show key presses"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Show visual feedback for physical key presses"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Show surface updates"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Flash entire window surfaces when they update"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Show view updates"</string>
diff --git a/packages/SettingsLib/res/values-en-rIN/strings.xml b/packages/SettingsLib/res/values-en-rIN/strings.xml
index 3a50fa3..58ea681 100644
--- a/packages/SettingsLib/res/values-en-rIN/strings.xml
+++ b/packages/SettingsLib/res/values-en-rIN/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"SIM access"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD audio: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD audio"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Hearing Aids"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Hearing aids"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Connected to Hearing Aids"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Connected to hearing aids"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Connected to LE audio"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Connected to media audio"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Connected to phone audio"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Use for phone audio"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Use for file transfer"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Use for input"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Use for Hearing Aids"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Use for hearing aids"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Use for LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Pair"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"PAIR"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Screen overlay showing current touch data"</string>
     <string name="show_touches" msgid="8437666942161289025">"Show taps"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Show visual feedback for taps"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Show key presses"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Show visual feedback for physical key presses"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Show surface updates"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Flash entire window surfaces when they update"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Show view updates"</string>
diff --git a/packages/SettingsLib/res/values-en-rXC/strings.xml b/packages/SettingsLib/res/values-en-rXC/strings.xml
index 9ad8a39..d69cda5 100644
--- a/packages/SettingsLib/res/values-en-rXC/strings.xml
+++ b/packages/SettingsLib/res/values-en-rXC/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‏‎‎‏‏‎‎‏‏‏‏‏‎‎‏‎‏‎‏‏‏‎‏‏‏‏‎‏‏‎‏‏‎‎‏‏‏‏‎‎‏‎‎‎‏‎‎‏‎‏‎‏‏‏‎‏‏‎‎‏‎‎SIM Access‎‏‎‎‏‎"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‎‎‎‎‏‏‏‎‎‎‏‎‏‏‏‏‎‎‎‎‎‎‏‏‎‏‏‎‎‏‏‏‎‏‏‎‏‏‎‎‏‏‎‏‏‎‏‏‏‎‎‏‏‏‏‎‎‎‏‏‏‎HD audio: ‎‏‎‎‏‏‎<xliff:g id="CODEC_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‎‎‎‏‎‎‏‏‎‎‎‏‎‎‏‎‏‎‏‏‏‏‎‏‎‎‎‎‏‏‎‏‎‏‏‎‎‏‎‎‏‎‎‏‎‏‎‎‏‏‎‏‏‎‎‎‎‎‏‏‎‎HD audio‎‏‎‎‏‎"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‎‎‎‏‏‎‎‏‏‏‎‏‎‎‏‏‎‏‏‎‏‎‎‎‏‏‏‎‏‎‎‎‎‏‏‎‎‏‎‎‏‏‎‏‎‏‎‎‏‏‎‏‎‎‏‎‎‏‎‎Hearing Aids‎‏‎‎‏‎"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‎‎‏‎‎‎‎‏‏‎‎‎‏‎‎‎‎‎‎‎‏‏‎‏‏‏‏‎‏‎‏‏‏‏‏‎‎‎‏‎‎‎‏‏‎‏‏‎‎‏‏‏‎‏‎‎‎‎‏‎‎‎Hearing aids‎‏‎‎‏‎"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‏‏‎‏‏‏‏‏‏‏‎‎‏‎‎‏‎‎‏‎‎‎‏‎‎‎‏‎‎‎‏‏‏‎‎‏‏‏‏‎‏‏‎‏‎‎‏‎‏‎‏‏‏‏‏‎‏‎‎‏‏‏‎LE Audio‎‏‎‎‏‎"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‏‎‎‎‏‏‎‏‎‏‏‎‏‎‎‏‏‏‏‏‏‏‎‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‏‏‎‎‎‏‏‎‏‏‎‎‏‏‏‏‏‏‏‏‎‎Connected to Hearing Aids‎‏‎‎‏‎"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‎‏‏‏‏‏‏‏‎‎‏‏‏‏‎‏‎‎‏‏‎‏‏‎‎‏‏‎‏‏‎‏‎‏‏‏‎‎‏‎‎‎‏‏‎‎‎‏‎‎‎‏‏‏‏‏‎‏‏‎‎‎Connected to hearing aids‎‏‎‎‏‎"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‏‏‏‏‏‏‏‏‏‏‎‏‏‎‏‎‏‏‏‏‏‏‏‎‎‏‎‏‎‎‎‎‏‏‏‏‎‎‏‎‎‎‏‏‎‎‎‏‏‏‏‎‎‏‏‎‎‏‎‏‎‎Connected to LE audio‎‏‎‎‏‎"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‎‎‏‏‏‎‎‎‎‎‎‏‎‎‏‏‎‎‏‏‎‏‎‎‏‏‏‏‏‎‏‎‎‎‏‎‏‏‎‏‏‎‏‏‎‏‏‎‏‏‏‎‏‏‎‏‏‏‎‎‏‎Connected to media audio‎‏‎‎‏‎"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‎‎‎‎‏‏‎‎‏‏‎‎‏‎‎‎‎‏‏‎‏‏‏‏‎‏‎‎‎‎‏‎‎‏‎‏‎‏‏‎‎‏‏‏‎‎‎‏‎‎‏‎‎‎‎‏‏‏‎‎‎‎Connected to phone audio‎‏‎‎‏‎"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‎‏‎‏‏‎‎‏‏‏‎‏‎‎‎‎‎‏‎‏‎‏‎‎‎‎‎‏‏‏‎‎‎‏‎‎‏‎‎‎‏‏‏‎‎‎‎‏‎‏‏‎‏‏‎‏‎‏‎‏‎‎Use for phone audio‎‏‎‎‏‎"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‎‏‏‏‏‎‎‏‏‎‏‎‎‏‎‏‎‎‏‎‎‏‎‏‏‎‏‎‎‏‏‎‎‎‏‎‎‎‎‎‏‎‏‎‎‏‏‎‏‏‎‏‏‏‎‎‏‎‎‎‎‏‎Use for file transfer‎‏‎‎‏‎"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‏‏‎‏‏‏‎‎‎‎‏‏‏‎‎‏‏‏‎‎‏‏‎‏‏‎‏‏‎‏‏‎‎‏‏‏‎‏‎‎‏‎‏‏‎‏‎‎‎‏‏‎‏‎‏‎‎‏‎‎‎‎Use for input‎‏‎‎‏‎"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‎‏‎‏‎‏‎‏‏‎‏‏‎‎‎‏‏‎‏‏‎‏‏‎‏‏‎‏‏‏‎‎‎‎‎‎‎‏‎‎‎‎‎‎‎‎‎‏‎‎‏‏‎‏‎‏‎‎‎‏‏‎Use for Hearing Aids‎‏‎‎‏‎"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‎‏‏‏‎‏‏‎‏‎‎‏‏‎‎‎‎‏‏‏‏‎‏‎‎‏‎‎‏‏‏‎‏‏‎‏‏‏‎‎‏‎‎‏‎‏‎‏‏‎‎‏‎‎‏‎‏‎‏‎‎‎Use for hearing aids‎‏‎‎‏‎"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‎‎‏‏‎‏‎‎‎‏‏‏‎‏‎‎‏‎‎‏‎‎‎‎‏‎‏‎‎‎‏‎‎‏‏‎‏‎‏‎‎‎‎‏‎‎‎‎‎‎‏‏‎‎‏‎‏‏‏‎‎‎Use for LE_AUDIO‎‏‎‎‏‎"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‏‏‏‏‎‎‏‎‎‎‎‎‏‎‎‎‎‏‏‎‎‏‏‎‏‎‏‏‎‎‏‏‏‏‏‏‎‏‏‎‏‎‏‏‏‎‎‏‎‏‎‏‏‏‎‎‎‏‎‏‎‎‎Pair‎‏‎‎‏‎"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‎‎‏‎‏‏‏‏‏‎‎‏‎‎‏‏‏‏‎‏‎‏‏‏‎‏‎‏‎‏‏‏‏‏‏‎‎‏‏‎‏‏‎‏‏‏‎‏‏‎‏‏‏‏‏‏‎‏‏‎‎‎PAIR‎‏‎‎‏‎"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‎‏‏‎‏‎‏‎‎‏‎‎‎‎‏‎‏‏‏‏‏‏‎‏‎‏‏‎‎‏‏‏‎‎‏‎‎‏‎‎‎‏‏‏‎‏‎‏‎‏‎‎‎‎‏‎‎‎‎‎‎‎Screen overlay showing current touch data‎‏‎‎‏‎"</string>
     <string name="show_touches" msgid="8437666942161289025">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‏‎‏‎‏‎‎‎‏‏‎‎‎‏‎‎‏‏‏‎‏‎‏‏‎‎‎‎‏‎‎‎‏‎‏‎‎‏‎‎‏‎‏‎‎‎‏‏‏‏‏‏‏‎‏‎‎‎‎‎‏‎Show taps‎‏‎‎‏‎"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‏‎‎‏‏‎‎‏‏‏‏‏‏‏‎‏‎‏‏‏‎‎‎‎‏‏‏‏‎‏‎‎‏‏‏‏‎‎‏‎‎‎‎‏‎‎‏‏‏‎‎‎‎‎‎‏‏‎‎‎‏‎Show visual feedback for taps‎‏‎‎‏‎"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‏‏‎‎‎‎‏‎‎‎‎‏‏‏‏‎‎‎‎‏‏‎‎‏‏‎‎‏‏‎‎‎‎‏‎‎‎‏‏‎‏‎‎‎‎‎‏‏‏‎‏‏‎‎‎‏‏‎‏‏‎‎Show key presses‎‏‎‎‏‎"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‎‏‎‏‎‎‎‎‏‎‎‎‏‎‎‎‏‏‎‎‎‎‎‎‎‏‏‎‎‏‎‎‏‏‎‎‏‎‎‏‏‏‏‏‎‎‎‎‎‏‎‏‏‏‏‏‏‎‎‎‎‎Show visual feedback for physical key presses‎‏‎‎‏‎"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‏‏‏‏‎‎‏‏‎‏‏‎‎‏‎‏‎‏‎‎‏‎‎‎‎‎‎‏‏‏‏‎‏‎‎‏‏‎‏‏‎‏‎‏‏‎‏‏‎‎‎‎‏‎‎‎‎‎‎‏‏‎‎Show surface updates‎‏‎‎‏‎"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‏‏‏‏‎‏‏‎‎‎‎‏‎‎‎‏‏‎‎‎‏‎‎‏‎‎‎‏‎‎‎‏‎‏‎‏‏‎‏‏‏‏‎‏‏‏‏‎‏‎‏‏‎‏‏‏‏‎‏‏‏‎‎Flash entire window surfaces when they update‎‏‎‎‏‎"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‏‏‏‏‎‎‎‎‎‎‏‏‎‏‎‎‎‎‏‏‎‏‎‎‎‏‏‏‏‎‎‎‏‏‏‏‎‎‎‎‎‏‏‎‏‎‎‎‏‎‎‏‏‎‏‎‏‏‎‎‏‎‎Show view updates‎‏‎‎‏‎"</string>
diff --git a/packages/SettingsLib/res/values-es-rUS/strings.xml b/packages/SettingsLib/res/values-es-rUS/strings.xml
index 7ac4ab0..39fd341 100644
--- a/packages/SettingsLib/res/values-es-rUS/strings.xml
+++ b/packages/SettingsLib/res/values-es-rUS/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"Acceso a SIM"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"Audio en HD: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"Audio en HD"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Audífonos"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Audífonos"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"audio de bajo consumo"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Conectado a audífonos"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Conectado a audífonos"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Conectado a audio de bajo consumo"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Conectado al audio multimedia"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Conectado al audio del dispositivo"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Utilizar para el audio del dispositivo"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Utilizar para la transferencia de archivos"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Utilizar para entrada"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Usar para audífonos"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Usar para audífonos"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Usar para LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Vincular"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"SINCRONIZAR"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Muestra los datos táctiles en la pantalla"</string>
     <string name="show_touches" msgid="8437666942161289025">"Mostrar presiones"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Muestra la ubicación de las presiones en la pantalla"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Ver pulsaciones de teclas"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Ver coment. visual para pulsac. de teclas físicas"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Ver actualiz. de superficie"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Destello en superficie por actualización"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Mostrar cambios de vista"</string>
diff --git a/packages/SettingsLib/res/values-es/strings.xml b/packages/SettingsLib/res/values-es/strings.xml
index 6ffed25..df256e5 100644
--- a/packages/SettingsLib/res/values-es/strings.xml
+++ b/packages/SettingsLib/res/values-es/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"Acceso a tarjeta SIM"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"Audio HD: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"Audio HD"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Audífonos"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Audífonos"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Conectado a audífonos"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Conectado a audífonos"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Conectado a LE Audio"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Conectado al audio del medio"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Conectado al audio del teléfono"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Utilizar para audio del teléfono"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Uso de la transferencia de archivos"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Usar para entrada"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Usar con audífonos"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Usar con audífonos"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Usar en LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Emparejar"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"EMPAREJAR"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Superpone los datos de las pulsaciones en la pantalla"</string>
     <string name="show_touches" msgid="8437666942161289025">"Mostrar toques"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Muestra la ubicación de los toques en la pantalla"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Ver pulsación de teclas"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Ver respuestas visuales al pulsar teclas físicas"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Mostrar cambios de superficies"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Hace parpadear todas las superficies de la ventana cuando se actualizan"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Ver actualizaciones de vista"</string>
diff --git a/packages/SettingsLib/res/values-et/strings.xml b/packages/SettingsLib/res/values-et/strings.xml
index 434e7d7..be847b3 100644
--- a/packages/SettingsLib/res/values-et/strings.xml
+++ b/packages/SettingsLib/res/values-et/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"Juurdepääs SIM-ile"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD-heli: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD-heli"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Kuuldeaparaadid"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Kuuldeaparaadid"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Kuuldeaparaatidega ühendatud"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Kuuldeaparaatidega ühendatud"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Ühendatud üksusega LE Audio"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Ühendatud meediumiheliga"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Ühendatud telefoniheliga"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Kasuta telefoniheli jaoks"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Kasutage failide edastamiseks"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Kasutage sisendi jaoks"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Kasuta kuulmisaparaatide puhul"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Kasutatakse kuuldeaparaatide puhul"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Üksuse LE_AUDIO kasutamine"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Seo"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"SEO"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Praegusi puuteandmeid kuvav ekraani ülekate"</string>
     <string name="show_touches" msgid="8437666942161289025">"Kuva puudutused"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Kuvab puudutuste visuaalse tagasiside"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Kuva klahvivajutused"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Kuva visuaalset tagasisidet füüsiliste klahvivajutuste kohta"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Näita pinna värskendusi"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Akna pinna värskendamiseks kirjuta kogu akna pind üle"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Kuva ekraanikuva värskendusi"</string>
diff --git a/packages/SettingsLib/res/values-eu/strings.xml b/packages/SettingsLib/res/values-eu/strings.xml
index 591d3cf..93abed2 100644
--- a/packages/SettingsLib/res/values-eu/strings.xml
+++ b/packages/SettingsLib/res/values-eu/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"SIMerako sarbidea"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"Kalitate handiko audioa: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"Kalitate handiko audioa"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Audifonoak"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Audifonoak"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"Kontsumo txikiko Bluetooth bidezko audioa"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Audifonoetara konektatuta"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Audifonoetara konektatuta"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"LE audio-ra konektatuta"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Euskarriaren audiora konektatuta"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Telefonoaren audiora konektatuta"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Erabili telefonoaren audiorako"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Erabili fitxategi-transferentziarako"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Erabili idazketarako"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Erabili audifonoak"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Erabili audifonoekin"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Erabili LE_AUDIO-rako"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Parekatu"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"PAREKATU"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Ukipen-datuak erakusteko pantaila-gainjartzea"</string>
     <string name="show_touches" msgid="8437666942161289025">"Erakutsi sakatutakoa"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Erakutsi sakatutako elementuak"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Erakutsi tekla-sakatzeak"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Erakutsi sakatutako tekla fisikoak"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Erakutsi azaleko aldaketak"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Distirarazi leiho osoen azalak haiek eguneratzean"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Erakutsi ikuspegi-aldaketak"</string>
diff --git a/packages/SettingsLib/res/values-fa/strings.xml b/packages/SettingsLib/res/values-fa/strings.xml
index 15e0968..9f33fa4 100644
--- a/packages/SettingsLib/res/values-fa/strings.xml
+++ b/packages/SettingsLib/res/values-fa/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"دسترسی سیم‌کارت"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"‏صدای HD: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"‏صدای HD"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"سمعک"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"سمعک"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"صدای کم‌مصرف"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"به سمعک متصل شد"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"متصل به سمعک"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"به «صدای کم‌مصرف» وصل است"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"به رسانه صوتی متصل شد"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"به تلفن صوتی متصل شد"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"استفاده برای تلفن صوتی"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"استفاده برای انتقال فایل"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"استفاده برای چاپ"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"استفاده کردن برای سمعک"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"استفاده برای سمعک"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"‏استفاده برای LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"مرتبط‌سازی"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"مرتبط‌سازی"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"هم‌پوشانی صفحه‌نمایش با نمایش داده لمسی فعلی"</string>
     <string name="show_touches" msgid="8437666942161289025">"نمایش ضربه‌ها"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"نمایش بازخورد تصویری برای ضربه‌ها"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"نمایش فشار کلیدها"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"نمایش بازخورد بصری برای فشار دادن کلیدهای فیزیکی"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"نمایش به‌روزرسانی سطح"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"هنگام به‌روزرسانی سطوح پنجره همه فلش شوند"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"نمایش به‌روزرسانی‌های نما"</string>
diff --git a/packages/SettingsLib/res/values-fi/strings.xml b/packages/SettingsLib/res/values-fi/strings.xml
index b3a7560..6ac22b2 100644
--- a/packages/SettingsLib/res/values-fi/strings.xml
+++ b/packages/SettingsLib/res/values-fi/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"SIM-kortin käyttö"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD-ääni: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD-ääni"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Kuulolaitteet"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Kuulolaitteet"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Yhdistetty kuulolaitteisiin"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Yhdistetty kuulolaitteisiin"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"LE Audio yhdistetty"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Yhdistetty median ääneen"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Yhdistetty puhelimen ääneen"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Käytä puhelimen äänille"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Käytä tiedostojen siirtoon"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Käytä syöttöön"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Käytä kuulolaitteilla"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Käytä kuulolaitteilla"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Käyttö: LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Muodosta laitepari"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"MUODOSTA LAITEPARI"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Näytön peittokuva näyttää nykyiset kosketustiedot"</string>
     <string name="show_touches" msgid="8437666942161289025">"Näytä kosketus"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Anna visuaalista palautetta kosketuksesta"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Näytä näppäinpainallukset"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Näytä visuaalista palautetta näppäinpainalluksista"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Näytä pintapäivitykset"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Väläytä koko ikkunoiden pinnat päivitettäessä"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Näytä näyttöpäivitykset"</string>
diff --git a/packages/SettingsLib/res/values-fr-rCA/strings.xml b/packages/SettingsLib/res/values-fr-rCA/strings.xml
index 8d3eae4..532374da 100644
--- a/packages/SettingsLib/res/values-fr-rCA/strings.xml
+++ b/packages/SettingsLib/res/values-fr-rCA/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"Accès à la carte SIM"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"Audio HD : <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"Audio HD"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Prothèses auditives"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Prothèses auditives"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Connecté aux prothèses auditives"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Connecté aux prothèses auditives"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Connecté par LE Audio"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Connecté aux paramètres audio du média"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Connecté à l\'audio du téléphone"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Utiliser pour les paramètres audio du téléphone"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Utiliser pour le transfert de fichiers"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Utiliser comme entrée"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Utiliser avec les prothèses auditives"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Utiliser avec les prothèses auditives"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Utiliser pour LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Associer"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"ASSOCIER"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Superposition écran indiquant données actuelles"</string>
     <string name="show_touches" msgid="8437666942161289025">"Afficher éléments sélect."</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Afficher repère visuel pour éléments sélectionnés"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Afficher press. de touches"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Afficher retour visuel pour press. de touches phys."</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Afficher mises à jour surface"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Faire clignoter les surfaces à chaque mise à jour"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Afficher m. à j. affichage"</string>
diff --git a/packages/SettingsLib/res/values-fr/strings.xml b/packages/SettingsLib/res/values-fr/strings.xml
index a4e43c7..683cfa5 100644
--- a/packages/SettingsLib/res/values-fr/strings.xml
+++ b/packages/SettingsLib/res/values-fr/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"Accès à la SIM"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"Audio HD : <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"Audio HD"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Appareils auditifs"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Appareils auditifs"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Connexion établie avec les appareils auditifs"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Connexion établie avec les appareils auditifs"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Connecté à LE Audio"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Connecté aux paramètres audio du média"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Connecté aux paramètres audio du téléphone"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Utiliser pour les paramètres audio du téléphone"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Utiliser pour le transfert de fichiers"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Utiliser comme entrée"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Utiliser pour les appareils auditifs"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Utiliser pour les appareils auditifs"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Utiliser pour LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Associer"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"ASSOCIER"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Superposition à l\'écran indiquant l\'emplacement actuel du curseur"</string>
     <string name="show_touches" msgid="8437666942161289025">"Indicateurs visuels"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Afficher un indicateur visuel là où l\'utilisateur appuie sur l\'écran"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Afficher appuis touche"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Afficher retour visuel pour appuis de touches"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Mises à jour de la surface"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Faire clignoter les endroits où des mises à jour sont effectuées"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Mises à jour de fenêtres"</string>
diff --git a/packages/SettingsLib/res/values-gl/strings.xml b/packages/SettingsLib/res/values-gl/strings.xml
index dbc0ba61..0661d7a 100644
--- a/packages/SettingsLib/res/values-gl/strings.xml
+++ b/packages/SettingsLib/res/values-gl/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"Acceso á SIM"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"Audio en HD: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"Audio en HD"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Audiófonos"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Audiófonos"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"Audio de baixo consumo"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Conectado a audiófonos"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Conectado a audiófonos"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Estableceuse conexión co audio de baixo consumo"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Conectado ao audio multimedia"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Conectado ao audio do teléfono"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Utilízase para o audio do teléfono"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Utilízase para a transferencia de ficheiros"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Utilízase para a entrada"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Utilizar para audiófonos"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Úsase para audiófonos"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Usa esta opción para LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Vincular"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"VINCULAR"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Superpón os datos dos toques na pantalla"</string>
     <string name="show_touches" msgid="8437666942161289025">"Mostrar toques"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Mostra a localización dos toques na pantalla"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Mostrar teclas premidas"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Mostra información das teclas físicas premidas"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Mostrar cambios de superficie"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Ilumina as superficies de ventás ao actualizarse"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Mostrar actualizacións"</string>
diff --git a/packages/SettingsLib/res/values-gu/strings.xml b/packages/SettingsLib/res/values-gu/strings.xml
index ee44c0a..b92d2f2 100644
--- a/packages/SettingsLib/res/values-gu/strings.xml
+++ b/packages/SettingsLib/res/values-gu/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"સિમ ઍક્સેસ"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD ઑડિયો: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD ઑડિયો"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"શ્રવણ યંત્રો"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"સાંભળવામાં મદદ આપતા યંત્રો"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE ઑડિયો"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"શ્રવણ યંત્રો સાથે કનેક્ટ કરેલું છે"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"સાંભળવામાં મદદ આપતા યંત્રો સાથે કનેક્ટ કરેલું છે"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"LE ઑડિયોથી કનેક્ટેડ"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"મીડિયા ઑડિઓ સાથે કનેક્ટ કર્યુ"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"ફોન ઑડિઓ સાથે કનેક્ટ થયાં"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"ફોન ઑડિઓ માટે ઉપયોગ કરો"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"ફાઇલ સ્થાનાંતર માટે ઉપયોગ કરો"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"ઇનપુટ માટે ઉપયોગ કરો"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"શ્રવણ યંત્રો માટે ઉપયોગ કરો"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"સાંભળવામાં મદદ આપતા યંત્રો માટે ઉપયોગ કરો"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"LE_AUDIO માટે ઉપયોગ કરો"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"જોડી કરો"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"જોડી કરો"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"વર્તમાન ટચ ડેટા દર્શાવતું સ્ક્રીન ઓવરલે"</string>
     <string name="show_touches" msgid="8437666942161289025">"ટૅપ બતાવો"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"ટૅપ માટે વિઝ્યુઅલ પ્રતિસાદ બતાવો"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"કી દબાવવાની ઘટના બતાવો"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"વાસ્તિવક રીતે કી દબાવવાના વિઝ્યુઅલ પ્રતિસાદ બતાવો"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"સપાટી અપડેટ બતાવો"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"તે અપડેટ થાય ત્યારે સમગ્ર વિન્ડો સપાટી ફ્લેશ કરો"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"વ્યૂના અપડેટ બતાવો"</string>
diff --git a/packages/SettingsLib/res/values-hi/strings.xml b/packages/SettingsLib/res/values-hi/strings.xml
index b97837b..bd508d9 100644
--- a/packages/SettingsLib/res/values-hi/strings.xml
+++ b/packages/SettingsLib/res/values-hi/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"सिम ऐक्सेस"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"एचडी ऑडियो: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"एचडी ऑडियो"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"कान की मशीन"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"कान की मशीनें"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"सुनने में मदद करने वाले डिवाइस से कनेक्ट है"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"कान की मशीनों के साथ कनेक्ट किया गया"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"LE Audio से कनेक्ट किया गया"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"मीडिया ऑडियो से कनेक्‍ट किया गया"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"फ़ोन ऑडियो से कनेक्‍ट किया गया"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"फ़ोन ऑडियो के लिए उपयोग करें"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"फ़ाइल स्‍थानांतरण के लिए उपयोग करें"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"इनपुट के लिए उपयोग करें"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"सुनने में मदद करने वाले डिवाइस के लिए इस्तेमाल करें"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"कान की मशीनों के लिए इस्तेमाल करें"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"LE_AUDIO के लिए इस्तेमाल करें"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"जोड़ें"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"जोड़ें"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"मौजूदा टच डेटा दिखाने वाला स्‍क्रीन ओवरले"</string>
     <string name="show_touches" msgid="8437666942161289025">"टैप दिखाएं"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"टैप के लिए विज़ुअल फ़ीडबैक दिखाएं"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"दबाए गए बटन दिखाएं"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"दबाए गए बटन के लिए विज़ुअल फ़ीडबैक दिखाएं"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"सर्फ़ेस अपडेट दिखाएं"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"अपडेट होने पर पूरे विंडो सर्फ़ेस फ़्लैश करें"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"जीपीयू व्यू के अपडेट दिखाएं"</string>
diff --git a/packages/SettingsLib/res/values-hr/strings.xml b/packages/SettingsLib/res/values-hr/strings.xml
index 6acfb95..7a8507d 100644
--- a/packages/SettingsLib/res/values-hr/strings.xml
+++ b/packages/SettingsLib/res/values-hr/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"Pristup SIM-u"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD audio: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD audio"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Slušni aparati"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Slušna pomagala"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Povezano sa Slušnim aparatima"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Povezano sa slušnim pomagalima"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Povezano s profilom LE_AUDIO"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Povezano s medijskim zvukom"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Povezano sa telefonskim zvukom"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Koristi za telefonski zvuk"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Koristi za prijenos datoteke"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Upotrijebi za ulaz"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Upotrijebi za Slušne aparate"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Upotrijebi za slušna pomagala"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Upotrebljavajte za LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Upari"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"UPARI"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Na zaslonu se prikazuju podaci o dodirima"</string>
     <string name="show_touches" msgid="8437666942161289025">"Prikaži dodire"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Prikazuju se vizualne povratne informacije za dodire"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Prikaži pritiske na tipke"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Prikaži vizualne povratne informacije za pritiske na tipke"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Prikaži ažuriranja površine"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Sve površine prozora bljeskaju pri ažuriranju"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Prikaži ažuriranja prikaza"</string>
diff --git a/packages/SettingsLib/res/values-hu/strings.xml b/packages/SettingsLib/res/values-hu/strings.xml
index 14ecad0..a67748f 100644
--- a/packages/SettingsLib/res/values-hu/strings.xml
+++ b/packages/SettingsLib/res/values-hu/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"SIM-elérés"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD audio: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD audio"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Hallókészülékek"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Hallókészülék"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"Alacsony energiaszintű hangátvitel"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Hallókészülékhez csatlakoztatva"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Hallókészülékhez csatlakoztatva"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Csatlakoztatva az alacsony energiaszintű hangátvitelhez"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Csatlakoztatva az eszköz hangjához"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Csatlakoztatva a telefon hangjához"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Felhasználás a telefon hangjához"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Felhasználás fájlátvitelre"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Használat beviteli eszközként"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Hallókészülékkel való használat"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Hallókészülékkel való használat"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Használat ehhez: LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Párosítás"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"PÁROSÍTÁS"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"A fedvény mutatja az aktuális érintési adatokat"</string>
     <string name="show_touches" msgid="8437666942161289025">"Koppintások megjelenítése"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Koppintások vizuális visszajelzésének megjelenítése"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Gomblenyomások mutatása"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Fizikai gomblenyomások vizuális visszajelzéseinek mutatása"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Felületfrissítések megj."</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"A teljes ablakfelület villogjon frissítéskor."</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Frissítések megjelenítése"</string>
diff --git a/packages/SettingsLib/res/values-hy/strings.xml b/packages/SettingsLib/res/values-hy/strings.xml
index 5083165..b72b094 100644
--- a/packages/SettingsLib/res/values-hy/strings.xml
+++ b/packages/SettingsLib/res/values-hy/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"SIM քարտի հասանելիություն"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD աուդիո՝ <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD աուդիո"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Լսողական ապարատ"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Լսողական սարք"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Լսողական ապարատը միացված է"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Միացված է լսողական սարքին"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Միացած է LE audio-ին"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Միացված է մեդիա աուդիոյին"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Միացված է հեռախոսի ձայնային տվյալներին"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Օգտագործել հեռախոսի աուդիոյի համար"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Օգտագործել ֆայլի փոխանցման համար"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Օգտագործել ներմուծման համար"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Օգտագործել լսողական ապարատի համար"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Օգտագործել լսողական սարքի համար"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Օգտագործել LE_AUDIO-ի համար"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Զուգակցել"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"Զուգակցել"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Էկրանի վերադրումը ցույց է տալիս ընթացիկ հպման տվյալները"</string>
     <string name="show_touches" msgid="8437666942161289025">"Ցույց տալ հպումները"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Ցույց տալ հպումների տեսանելի արձագանքը"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Ցույց տալ սեղմումները"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Տեսողական արձագանք ստեղների սեղմումների համար"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Ցույց տալ մակերեսի թարմացումները"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Թարմացվելիս ընդգծել սարքաշարի ծածկույթները կանաչ գույնով"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Ցուցադրել թարմացումները"</string>
diff --git a/packages/SettingsLib/res/values-in/strings.xml b/packages/SettingsLib/res/values-in/strings.xml
index c5e6912..07dd53a 100644
--- a/packages/SettingsLib/res/values-in/strings.xml
+++ b/packages/SettingsLib/res/values-in/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"Akses SIM"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"Audio HD: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"Audio HD"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Alat Bantu Dengar"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Alat bantu dengar"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Terhubung ke Alat Bantu Dengar"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Terhubung ke alat bantu dengar"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Terhubung ke LE Audio"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Terhubung ke media audio"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Terhubung ke audio ponsel"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Gunakan untuk audio ponsel"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Gunakan untuk transfer file"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Gunakan untuk masukan"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Gunakan untuk Alat Bantu Dengar"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Gunakan untuk alat bantu dengar"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Digunakan untuk LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Sambungkan"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"SAMBUNGKAN"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Overlay layar menampilkan data sentuhan saat ini"</string>
     <string name="show_touches" msgid="8437666942161289025">"Tampilkan ketukan"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Tampilkan efek visual untuk ketukan"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Tampilkan penekanan tombol"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Tampilkan respons visual untuk penekanan tombol fisik"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Tampilkan pembaruan permukaan"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Buat seluruh permukaan jendela berkedip saat diperbarui"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Tampilkan pembaruan tampilan"</string>
diff --git a/packages/SettingsLib/res/values-is/strings.xml b/packages/SettingsLib/res/values-is/strings.xml
index 5d18930..bb7bf70 100644
--- a/packages/SettingsLib/res/values-is/strings.xml
+++ b/packages/SettingsLib/res/values-is/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"Aðgangur að SIM-korti"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD-hljóð: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD-hljóð"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Heyrnartæki"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Heyrnartæki"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE-hljóð"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Tengt við heyrnartæki"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Tengt við heyrnartæki"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Tengt við LE-hljóð"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Tengt við hljóðspilun efnis"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Tengt við hljóð símans"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Nota fyrir hljóð símans"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Nota við skráaflutning"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Nota fyrir inntak"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Nota fyrir heyrnartæki"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Nota fyrir heyrnartæki"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Nota fyrir LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Para"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"PARA"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Skjáyfirlögn sem sýnir rauntímagögn um snertingar"</string>
     <string name="show_touches" msgid="8437666942161289025">"Sýna snertingar"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Sýna snertingar myndrænt"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Sýna „Ýtt á lykil“"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Sýna myndsvörun fyrir „Ýtt á raunverulegan lykil“"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Sýna yfirborðsuppfærslur"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Láta allt yfirborð glugga blikka við uppfærslu"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Sýna uppfærslur yfirlits"</string>
diff --git a/packages/SettingsLib/res/values-it/strings.xml b/packages/SettingsLib/res/values-it/strings.xml
index 85d4985..5926a6b 100644
--- a/packages/SettingsLib/res/values-it/strings.xml
+++ b/packages/SettingsLib/res/values-it/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"Accesso alla SIM"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"Audio HD: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"Audio HD"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Apparecchi acustici"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Apparecchi acustici"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Connessione con gli apparecchi acustici stabilita"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Connessione con gli apparecchi acustici stabilita"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Connesso a LE Audio"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Collegato ad audio media"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Collegato ad audio telefono"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Usa per audio telefono"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Usa per trasferimento file"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Utilizza per l\'input"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Utilizza per gli apparecchi acustici"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Utilizza per gli apparecchi acustici"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Usa per LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Accoppia"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"ACCOPPIA"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Overlay schermo che mostra i dati touch correnti"</string>
     <string name="show_touches" msgid="8437666942161289025">"Mostra tocchi"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Mostra feedback visivi per i tocchi"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Mostra pressioni tasti"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Mostra feedback visivo per pressioni tasti fisici"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Aggiornamenti superficie"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Fai lampeggiare le superfici delle finestre quando si aggiornano"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Aggiornam. visualizzazione"</string>
diff --git a/packages/SettingsLib/res/values-iw/strings.xml b/packages/SettingsLib/res/values-iw/strings.xml
index af56031..51bda70 100644
--- a/packages/SettingsLib/res/values-iw/strings.xml
+++ b/packages/SettingsLib/res/values-iw/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"‏גישה ל-SIM"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"‏אודיו באיכות HD: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"‏אודיו באיכות HD"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"מכשירי שמיעה"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"מכשירי שמיעה"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"מחובר אל מכשירי שמיעה"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"מחובר למכשירי השמיעה"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"‏מחובר אל LE audio"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"מחובר לאודיו של מדיה"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"מחובר לאודיו של הטלפון"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"השתמש עבור האודיו של הטלפון"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"לצורך העברת קבצים"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"שימוש כקלט"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"שימוש בשביל מכשירי שמיעה"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"שימוש למכשירי שמיעה"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"‏לשימוש עבור LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"התאמה"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"התאמה"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"שכבת-על של המסך המציגה את נתוני המגע הנוכחיים"</string>
     <string name="show_touches" msgid="8437666942161289025">"הצגת הקשות"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"הצגת משוב ויזואלי להקשות"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"הצגת לחיצות המקשים"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"הצגת משוב חזותי עבור לחיצות פיזיות על מקשים"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"הצגת עדכונים על פני השטח"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"הבהוב של כל שטחי החלון כשהם מתעדכנים"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"הצגת עדכונים של התצוגה"</string>
diff --git a/packages/SettingsLib/res/values-ja/strings.xml b/packages/SettingsLib/res/values-ja/strings.xml
index 0032351..d15b4ef 100644
--- a/packages/SettingsLib/res/values-ja/strings.xml
+++ b/packages/SettingsLib/res/values-ja/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"SIMアクセス"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD オーディオ: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD オーディオ"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"補聴器"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"補聴器"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"補聴器に接続"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"補聴器に接続"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"LE Audio に接続"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"メディアの音声に接続"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"携帯電話の音声に接続"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"携帯電話の音声に使用"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"ファイル転送に使用"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"入力に使用"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"補聴器に使用"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"補聴器に使用"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"LE_AUDIO の使用"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"ペア設定する"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"ペア設定する"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"現在のタップデータをオーバーレイ表示する"</string>
     <string name="show_touches" msgid="8437666942161289025">"タップを表示"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"タップを視覚表示する"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"キーの押下を表示"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"物理キーの押下に関する視覚的フィードバックを表示"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"表示面の更新を通知"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"更新時にウィンドウの表示面全体を点滅させる"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"画面の更新を表示"</string>
diff --git a/packages/SettingsLib/res/values-ka/strings.xml b/packages/SettingsLib/res/values-ka/strings.xml
index 147512b..f63d4c2 100644
--- a/packages/SettingsLib/res/values-ka/strings.xml
+++ b/packages/SettingsLib/res/values-ka/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"SIM წვდომა"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD აუდიო: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD აუდიო"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"სმენის მოწყობილობები"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"სმენის მოწყობილობები"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE-აუდიო"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"დაკავშირებულია სმენის მოწყობილობებთან"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"დაკავშირებულია სმენის მოწყობილობებთან"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"დაკავშირებულია LE აუდიოსთან"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"დაკავშირებულია აუდიო მულტიმედიურ სისტემასთან"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"დაკავშირებულია ტელეფონის აუდიო მოწყობილობასთან"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"გამოიყენეთ ტელეფონის აუდიომოწყობილობაში"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"ფაილების ტრანსფერისათვის გამოყენება"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"შეტანისთვის გამოყენება"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"გამოყენება სმენის მოწყობილობებისთვის"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"გამოყენება სმენის მოწყობილობებისთვის"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"გამოიყენება შემდეგისთვის: LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"დაწყვილება"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"დაწყვილება"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"ეკრანის გადაფარვა შეხების მონაცემების ჩვენებით"</string>
     <string name="show_touches" msgid="8437666942161289025">"შეხებების ჩვენება"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"შეხებებისთვის ვიზუალური უკუკავშირის ჩვენება"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"კლავიშების დაჭერის ჩვენება"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"ვიზუალური გამოხმაურების ჩვენება კლავიშის დაჭერაზე"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"ზედაპირის განახლებების ჩვენება"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"ფანჯრის მთელი ზედაპირის აციმციმება მისი განახლებისას"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"განახლებების ჩვენება"</string>
diff --git a/packages/SettingsLib/res/values-kk/strings.xml b/packages/SettingsLib/res/values-kk/strings.xml
index 7df9fb3..883dea6 100644
--- a/packages/SettingsLib/res/values-kk/strings.xml
+++ b/packages/SettingsLib/res/values-kk/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"SIM картасына кіру"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD форматты аудио: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD форматты аудио"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Есту аппараттары"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Есту аппараттары"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Есту аппараттарына жалғанған"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Есту аппараттарына жалғанған"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"LE Audio-ға жалғанды."</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Медиа аудиосына жалғанған"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Телефон аудиосына қосылған"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Телефон аудиосы үшін қолдану"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Файлды жіберу үшін қолдану"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Кіріс үшін қолдану"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Есту аппараттары үшін пайдалану"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Есту аппараттары үшін пайдалану"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"LE_AUDIO үшін пайдалану"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Жұптау"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"ЖҰПТАУ"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Экран бетіне түртілген элемент дерегі көрсетіледі"</string>
     <string name="show_touches" msgid="8437666942161289025">"Түрту қимылын көрсету"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Түрту қимылын экраннан көрсету"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Түйменің басылуын көрсету"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Түйменің басылуын экраннан көрсету"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Бедердің жаңарғанын көрсету"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Бедері жаңарғанда, терезені түгелдей жыпылықтату"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Көріністің жаңарғанын көрсету"</string>
diff --git a/packages/SettingsLib/res/values-km/strings.xml b/packages/SettingsLib/res/values-km/strings.xml
index 81d2669..f7a8bfd 100644
--- a/packages/SettingsLib/res/values-km/strings.xml
+++ b/packages/SettingsLib/res/values-km/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"ការចូលដំណើរការស៊ីម"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"សំឡេងកម្រិត HD៖ <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"សំឡេងកម្រិត HD"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"ឧបករណ៍​ជំនួយការ​ស្ដាប់"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"ឧបករណ៍ជំនួយការស្ដាប់"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"បាន​ភ្ជាប់ទៅ​ឧបករណ៍​ជំនួយការ​ស្ដាប់"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"បានភ្ជាប់ទៅ​ឧបករណ៍​ជំនួយ​ការស្ដាប់"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"បានភ្ជាប់​ទៅ LE audio"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"បា​ន​ភ្ជាប់​ទៅ​អូឌីយ៉ូ​មេឌៀ"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"តភ្ជាប់​ទៅ​អូឌីយ៉ូ​ទូរស័ព្ទ"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"ប្រើ​សម្រាប់​​អូឌីយ៉ូ​ទូរស័ព្ទ"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"ប្រើ​សម្រាប់​ផ្ទេរ​ឯកសារ"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"ប្រើ​សម្រាប់​បញ្ចូល"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"ប្រើ​សម្រាប់​ឧបករណ៍​ជំនួយការ​ស្តាប់"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"ប្រើ​សម្រាប់​ឧបករណ៍​ជំនួយការ​ស្ដាប់"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"ប្រើ​សម្រាប់ LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"ផ្គូផ្គង"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"ផ្គូផ្គង"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"អេក្រង់​ត្រួត​គ្នា​បង្ហាញ​ទិន្នន័យ​ប៉ះ​បច្ចុប្បន្ន"</string>
     <string name="show_touches" msgid="8437666942161289025">"បង្ហាញការចុច"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"បង្ហាញដានចុច នៅពេលចុច"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"បង្ហាញការចុចគ្រាប់ចុច"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"បង្ហាញព័ត៌មានឆ្លើយតបជារូបភាពសម្រាប់ការចុចគ្រាប់ចុចរូបវន្ត"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"បង្ហាញ​បច្ចុប្បន្នភាព​ផ្ទៃ"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"ផ្ទៃ​វីនដូទាំង​មូល​បញ្ចេញពន្លឺ​នៅពេល​ធ្វើ​បច្ចុប្បន្នភាព"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"បង្ហាញ​បច្ចុប្បន្នភាពទិដ្ឋភាព"</string>
diff --git a/packages/SettingsLib/res/values-kn/strings.xml b/packages/SettingsLib/res/values-kn/strings.xml
index 134f591..e29e22b 100644
--- a/packages/SettingsLib/res/values-kn/strings.xml
+++ b/packages/SettingsLib/res/values-kn/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"ಸಿಮ್ ಆ್ಯಕ್ಸೆಸ್"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD ಆಡಿಯೋ: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD ಆಡಿಯೋ"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"ಶ್ರವಣ ಸಾಧನಗಳು"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"ಶ್ರವಣ ಸಾಧನಗಳು"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE ಆಡಿಯೋ"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"ಶ್ರವಣ ಸಾಧನಗಳಿಗೆ ಸಂಪರ್ಕಿಸಲಾಗಿದೆ"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"ಶ್ರವಣ ಸಾಧನಗಳಿಗೆ ಕನೆಕ್ಟ್ ಮಾಡಲಾಗಿದೆ"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"LE ಆಡಿಯೋಗೆ ಕನೆಕ್ಟ್ ಮಾಡಲಾಗಿದೆ"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"ಮಾಧ್ಯಮ ಆಡಿಯೋಗೆ ಸಂಪರ್ಕಗೊಂಡಿದೆ"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"ಫೋನ್ ಆಡಿಯೋಗೆ ಸಂಪರ್ಕಗೊಂಡಿದೆ"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"ಫೋನ್‌ ಆಡಿಯೋಗಾಗಿ ಬಳಕೆ"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"ಫೈಲ್‌ ವರ್ಗಾವಣೆಗಾಗಿ ಬಳಸು"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"ಇನ್‌ಪುಟ್‌ಗಾಗಿ ಬಳಸು"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"ಶ್ರವಣ ಸಾಧನಗಳಿಗಾಗಿ ಬಳಸಿ"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"ಶ್ರವಣ ಸಾಧನಗಳಿಗಾಗಿ ಬಳಸಿ"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"LE_AUDIO ಗೆ ಬಳಸಿ"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"ಜೋಡಿಸಿ"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"ಜೋಡಿ ಮಾಡು"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"ಪ್ರಸ್ತುತ ಸ್ಪರ್ಶ ಡೇಟಾ ತೋರಿಸುವ ಪರದೆಯ ಓವರ್‌ಲೇ"</string>
     <string name="show_touches" msgid="8437666942161289025">"ಟ್ಯಾಪ್‌ಗಳನ್ನು ತೋರಿಸಿ"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"ಟ್ಯಾಪ್‌ಗಳಿಗೆ ದೃಶ್ಯ ಪ್ರತಿಕ್ರಿಯೆ ತೋರಿಸು"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"ಕೀ ಪ್ರೆಸ್‌ಗಳನ್ನು ತೋರಿಸಿ"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"ಭೌತಿಕ ಕೀ ಪ್ರೆಸ್‌ಗಳ ವಿಷುವಲ್ ಪ್ರತಿಕ್ರಿಯೆಗಾಗಿ ನೋಡಿ"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"ಸರ್ಫೇಸ್‌‌ ಅಪ್‌ಡೇಟ್ ತೋರಿಸಿ‌"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"ಅಪ್‌ಡೇಟ್‌ ಆಗುವಾಗ ವಿಂಡೋದ ಸರ್ಫೇಸ್‌ ಫ್ಲ್ಯಾಶ್ ಆಗುತ್ತದೆ"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"\'ಅಪ್‌ಡೇಟ್‌ಗಳನ್ನು ವೀಕ್ಷಿಸಿ\' ತೋರಿಸಿ"</string>
diff --git a/packages/SettingsLib/res/values-ko/strings.xml b/packages/SettingsLib/res/values-ko/strings.xml
index ffa05cc..653ac23 100644
--- a/packages/SettingsLib/res/values-ko/strings.xml
+++ b/packages/SettingsLib/res/values-ko/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"SIM 액세스"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD 오디오: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD 오디오"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"보청기"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"보청기"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE 오디오"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"보청기에 연결됨"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"보청기에 연결됨"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"LE 오디오에 연결됨"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"미디어 오디오에 연결됨"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"휴대전화 오디오에 연결됨"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"휴대전화 오디오에 사용"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"파일 전송에 사용"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"입력에 사용"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"보청기로 사용"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"보청기로 사용"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"LE_AUDIO에 사용"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"페어링"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"페어링"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"현재 터치 데이터 오버레이 표시"</string>
     <string name="show_touches" msgid="8437666942161289025">"탭한 항목 표시"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"탭한 항목에 대해 시각적인 피드백 표시"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"키 누름 표시"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"물리적 키 누름에 관한 시각적 피드백을 표시합니다."</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"표면 업데이트 표시"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"전체 창 화면이 업데이트되었을 때 플래시 처리"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"보기 업데이트 표시"</string>
diff --git a/packages/SettingsLib/res/values-ky/strings.xml b/packages/SettingsLib/res/values-ky/strings.xml
index 7e7d382..c2aa652 100644
--- a/packages/SettingsLib/res/values-ky/strings.xml
+++ b/packages/SettingsLib/res/values-ky/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"SIM картаны пайдалануу мүмкүнчүлүгү"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD форматындагы аудио: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD форматындагы аудио"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Угуу аппараттары"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Угуу аппараттары"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Угуу аппараттарына туташып турат"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Угуу аппараттарына туташып турат"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"LE аудио менен туташты"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Медиа аудиого туташты"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Телефон аудиосуна туташты"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Телефон аудиосу үчүн колдонулсун"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Файл өткөрүү үчүн колдонулсун"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Киргизүү үчүн колдонулсун"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Угуу аппараттары үчүн колдонуу"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Угуу аппараттары үчүн колдонуу"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"LE_AUDIO үчүн колдонуу"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Байланыштыруу"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"ЖУПТАШТЫРУУ"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Басылган жерлер жана жаңсоолор экранда көрүнүп турат"</string>
     <string name="show_touches" msgid="8437666942161289025">"Басылган жерлерди көрсөтүү"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Экранда басылган жерлер көрүнүп турат"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Баскычтардын басылганын көрсөтүү"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Баскычтар басылганда визуалдык сигнал көрүнөт"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Экран жаңыруусун көрсөтүү"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Экран жаңырганда анын үстү жарык болот"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Жаңыртууларды көрсөтүү"</string>
diff --git a/packages/SettingsLib/res/values-lo/strings.xml b/packages/SettingsLib/res/values-lo/strings.xml
index 28fd87e..0a429fd 100644
--- a/packages/SettingsLib/res/values-lo/strings.xml
+++ b/packages/SettingsLib/res/values-lo/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"ການ​ເຂົ້າ​ເຖິງ SIM"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"ສຽງ HD: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"ສຽງ HD"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"ອຸປະກອນຊ່ວຍຟັງ"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"ເຄື່ອງຊ່ວຍຟັງ"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"ສຽງ LE"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"ເຊື່ອມຕໍ່ຫາອຸປະກອນຊ່ວຍຟັງແລ້ວ"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"ເຊື່ອມຕໍ່ກັບເຄື່ອງຊ່ວຍຟັງແລ້ວ"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"ເຊື່ອມຕໍ່ຫາສຽງ LE ແລ້ວ"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"ເຊື່ອມຕໍ່ກັບສື່ດ້ານສຽງແລ້ວ"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"ເຊື່ອມຕໍ່ກັບສຽງໂທລະສັບແລ້ວ"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"ໃຊ້ສຳລັບລະບົບສຽງຂອງໂທລະສັບ"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"ໃຊ້ເພື່ອໂອນຍ້າຍໄຟລ໌"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"ໃຊ້ສຳລັບການປ້ອນຂໍ້ມູນ"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"ໃຊ້ສຳລັບອຸປະກອນຊ່ວຍຟັງ"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"ໃຊ້ສຳລັບເຄື່ອງຊ່ວຍຟັງ"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"ໃຊ້ສຳລັບ LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"ຈັບຄູ່"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"ຈັບຄູ່"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"ການວາງຊ້ອນໜ້າຈໍກຳລັງສະແດງຂໍ້ມູນການສຳຜັດໃນປັດຈຸບັນ"</string>
     <string name="show_touches" msgid="8437666942161289025">"ສະແດງການແຕະ"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"ສະແດງຄໍາຕິຊົມທາງຮູບພາບສຳລັບການແຕະ"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"ສະແດງການກົດປຸ່ມ"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"ສະແດງຄຳຕິຊົມພາບສຳລັບການກົດປຸ່ມຈິງ"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"ສະແດງການອັບເດດພື້ນຜິວ"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"ກະພິບໜ້າຈໍທັງໜ້າເມື່ອມີການອັບເດດ"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"ສະແດງອັບເດດມຸມມອງ"</string>
diff --git a/packages/SettingsLib/res/values-lt/strings.xml b/packages/SettingsLib/res/values-lt/strings.xml
index 8246c6b..78246dc 100644
--- a/packages/SettingsLib/res/values-lt/strings.xml
+++ b/packages/SettingsLib/res/values-lt/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"SIM prieiga"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD garsas: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD garsas"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Klausos aparatai"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Klausos aparatai"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Prisijungta prie klausos aparatų"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Prisijungta prie klausos aparatų"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Prisijungta prie „LE Audio“"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Prijungta prie medijos garso įrašo"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Prijungta prie telefono garso"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Naudoti telefono garso įrašui"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Naudoti failų perkėlimui"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Naudoti įvedant"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Naudoti su klausos aparatais"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Naudoti su klausos aparatais"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Naudoti LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Susieti"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"SUSIETI"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Ekrano perdanga rodo dabartinius lietimo duomenis"</string>
     <string name="show_touches" msgid="8437666942161289025">"Rodyti palietimus"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Rodyti vaizdinius palietimų atsiliepimus"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Rodyt klavišų paspaudimus"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Rodyti fizinių klavišų paspaudimų vaizdinį atsiliepimą"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Rodyti paviršiaus naujin."</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Naujinant mirginti visus langų paviršius"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Rodyti rodinių naujinius"</string>
diff --git a/packages/SettingsLib/res/values-lv/strings.xml b/packages/SettingsLib/res/values-lv/strings.xml
index 807df3a..e3fbcdf 100644
--- a/packages/SettingsLib/res/values-lv/strings.xml
+++ b/packages/SettingsLib/res/values-lv/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"Piekļuve SIM kartei"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD audio: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD audio"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Dzirdes aparāti"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Dzirdes aparāti"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Izveidots savienojums ar dzirdes aparātiem"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Izveidots savienojums ar dzirdes aparātiem"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Izveidots savienojums ar LE Audio"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Savienots ar multivides audio"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Savienots ar tālruņa audio"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Izmantot tālruņa skaņai"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Izmantot faila pārsūtīšanai"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Izmantot ievadei"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Izmantot dzirdes aparātiem"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Izmantot dzirdes aparātiem"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Izmantot LE_AUDIO profilam"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Izveidot pāri"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"SAVIENOT PĀRĪ"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Ekrāna pārklājums ar aktuāliem pieskāriena datiem"</string>
     <string name="show_touches" msgid="8437666942161289025">"Rādīt pieskārienus"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Rādīt vizuālo reakciju pēc pieskārieniem"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Rādīt taustiņu nospiešanu"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Vizuāla reakcija uz fizisku taustiņu nospiešanu"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Rādīt virsmas atjauninājumus WL: 294"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Atjaunināt visa loga virsmas, kad tās tiek atjauninātas WL: 294"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Rādīt skat. atjaunin."</string>
diff --git a/packages/SettingsLib/res/values-mk/strings.xml b/packages/SettingsLib/res/values-mk/strings.xml
index 583b069..6ba8d5c 100644
--- a/packages/SettingsLib/res/values-mk/strings.xml
+++ b/packages/SettingsLib/res/values-mk/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"Пристап до SIM"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD-аудио: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD-аудио"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Слушни помагала"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Слушни помагала"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE-аудио"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Поврзано со слушни помагала"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Поврзано со слушни помагала"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Поврзано на LE-аудио"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Поврзан со аудио на медиуми"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Поврзан со аудио на телефон"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Користи за аудио на телефон"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Користи за пренос на датотеки"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Користи за внес"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Користи за слушни помагала"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Користам слушни помагала"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Користи за LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Спари"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"СПАРИ"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Прекривката на екран ги покажува тековните податоци на допир"</string>
     <string name="show_touches" msgid="8437666942161289025">"Прикажувај допири"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Прикажувај визуелни повратни информации за допири"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Прикаж. притисн. копчиња"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Визуелно го прикажува притискањето на копчињата"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Прикажи ажурир. површина"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Осветли површ. на прозорци при нивно ажурирање"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Покажувај ажурирања на приказот"</string>
diff --git a/packages/SettingsLib/res/values-ml/strings.xml b/packages/SettingsLib/res/values-ml/strings.xml
index 610f365..0f4d30d 100644
--- a/packages/SettingsLib/res/values-ml/strings.xml
+++ b/packages/SettingsLib/res/values-ml/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"സിം ആക്സസ്"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD ഓഡിയോ: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD ഓഡിയോ"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"ശ്രവണ സഹായികൾ"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"ശ്രവണ സഹായികൾ"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE ഓഡിയോ"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"ശ്രവണ സഹായികളിലേക്ക് കണക്‌റ്റ് ചെയ്‌തു"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"ശ്രവണ സഹായികളിലേക്ക് കണക്‌റ്റ് ചെയ്‌തു"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"LE ഓഡിയോയിലേക്ക് കണക്‌റ്റ് ചെയ്‌തു"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"മീഡിയ ഓഡിയോയിലേക്ക് കണ‌ക്റ്റുചെയ്‌തു"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"ഫോൺ ഓഡിയോയിൽ കണ‌ക്റ്റുചെ‌യ്‌തു"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"ഫോൺ ഓഡിയോയ്ക്കായി ഉപയോഗിക്കുക"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"ഫയൽ കൈമാറ്റത്തിനായി ഉപയോഗിക്കുന്നു"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"ഇൻപുട്ടിനായി ഉപയോഗിക്കുക"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"ശ്രവണ സഹായികൾക്കായി ഉപയോഗിക്കുക"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"ശ്രവണ സഹായികൾക്കായി ഉപയോഗിക്കുക"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"LE_AUDIO ഉപയോഗിക്കുക"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"ജോടിയാക്കുക"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"ജോടിയാക്കുക"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"സ്‌ക്രീൻ ഓവർലേ നിലവിലെ ടച്ച് ഡാറ്റ ദൃശ്യമാക്കുന്നു"</string>
     <string name="show_touches" msgid="8437666942161289025">"ടാപ്പുകൾ കാണിക്കുക"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"ടാപ്പുകൾക്ക് ദൃശ്യ ഫീഡ്ബാക്ക് കാണിക്കുക"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"കീ അമർത്തലുകൾ കാണിക്കുക"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"ഫിസിക്കൽ കീ അമർത്തൽ വിഷ്വൽ ഫീഡ്‌ബാക്ക് കാണിക്കൂ"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"സർഫേസ് അപ്‌ഡേറ്റ് കാണിക്കൂ"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"അപ്‍ഡേറ്റ് പൂർത്തിയാകുമ്പോൾ മുഴുവൻ വിൻഡോ സർഫേസുകളും ഫ്ലാഷ് ചെയ്യുക"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"\'അപ്‌ഡേറ്റുകൾ കാണുക\' കാണിക്കുക"</string>
diff --git a/packages/SettingsLib/res/values-mn/strings.xml b/packages/SettingsLib/res/values-mn/strings.xml
index 040f2c8..29fb6d4 100644
--- a/packages/SettingsLib/res/values-mn/strings.xml
+++ b/packages/SettingsLib/res/values-mn/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"SIM Хандалт"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD аудио: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD аудио"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Сонсголын төхөөрөмж"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Сонсголын төхөөрөмж"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Аудио"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Сонсголын төхөөрөмжтэй холбосон"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Сонсголын төхөөрөмжтэй холбосон"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"LE аудионд холбогдсон"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Медиа аудиод холбогдсон"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Утасны аудид холбогдсон"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Утасны аудиод ашиглах"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Файл дамжуулахад ашиглах"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Оруулахад ашиглах"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Сонсголын төхөөрөмжид ашиглах"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Сонсголын төхөөрөмж болгон ашиглах"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"LE_АУДИОНД ашиглах"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Хослуулах"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"ХОСЛУУЛАХ"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Дэлгэцийн давхаргаар одоогийн хүрэлтийн өгөгдлийг харуулж байна"</string>
     <string name="show_touches" msgid="8437666942161289025">"Товшилтыг харуулах"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Товшилтын визуал хариу үйлдлийг харуулах"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Түлхүүрийн даралт харуул"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Биет түлхүүрийн даралтын визуал санал хүсэлт харуул"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Гадаргын шинэчлэлтүүдийг харуулах"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Шинэчлэгдэх үед цонхны гадаргыг бүхэлд нь анивчуулах"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Шинэчлэлт харахыг харуулах"</string>
diff --git a/packages/SettingsLib/res/values-mr/strings.xml b/packages/SettingsLib/res/values-mr/strings.xml
index 932ef6c..e9e0ee4 100644
--- a/packages/SettingsLib/res/values-mr/strings.xml
+++ b/packages/SettingsLib/res/values-mr/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"सिम अ‍ॅक्सेस"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD ऑडिओ: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD ऑडिओ"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"श्रवणयंत्रे"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"श्रवणयंत्रे"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE ऑडिओ"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"श्रवण यंत्रांशी कनेक्ट केले आहे"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"श्रवणयंत्रांशी कनेक्ट केले आहे"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"LE ऑडिओशी कनेक्ट केले आहे"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"मीडिया ऑडिओवर कनेक्ट केले"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"फोन ऑडिओ वर कनेक्ट केले"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"फोन ऑडिओसाठी वापरा"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"फाइल स्थानांतरणासाठी वापरा"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"इनपुट साठी वापरा"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"श्रवण यंत्रांसाठी वापरा"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"श्रवणयंत्रांसाठी वापरा"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"LE_AUDIO साठी वापरले आहे"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"पेअर करा"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"पेअर करा"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"वर्तमान स्पर्श डेटा दर्शविणारे स्क्रीन ओव्हरले"</string>
     <string name="show_touches" msgid="8437666942161289025">"टॅप दाखवा"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"टॅपसाठी व्हिज्युअल फीडबॅक दाखवा"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"की प्रेस दाखवा"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"प्रत्यक्ष की प्रेससाठी व्हिज्युअल फीडबॅक दाखवा"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"सर्फेस अपडेट दाखवा"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"संपूर्ण विंडो सर्फेस अपडेट होतात तेव्हा ते फ्‍लॅश करा"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"व्‍ह्यू अपडेट दाखवा"</string>
diff --git a/packages/SettingsLib/res/values-ms/strings.xml b/packages/SettingsLib/res/values-ms/strings.xml
index 460eb56..0a41c0f 100644
--- a/packages/SettingsLib/res/values-ms/strings.xml
+++ b/packages/SettingsLib/res/values-ms/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"Akses SIM"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"Audio HD: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"Audio HD"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Alat Bantu Dengar"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Alat bantu pendengaran"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Disambungkan pada Alat Bantu Dengar"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Disambungkan kepada alat bantu pendengaran"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Disambungkan kepada LE Audio"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Disambungkan ke audio media"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Disambungkan ke audio telefon"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Gunakan untuk audio telefon"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Gunakan untuk pemindahan fail"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Gunakan untuk input"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Gunakan untuk Alat Bantu Dengar"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Gunakan untuk alat bantu pendengaran"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Gunakan untuk LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Gandingkan"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"JADIKAN PASANGAN"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Tindihan skrin menunjukkan data sentuh semasa"</string>
     <string name="show_touches" msgid="8437666942161289025">"Tunjukkan ketikan"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Tunjukkan maklum balas visual untuk ketikan"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Tunjukkan tekanan kekunci"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Tunjukkan maklum balas visual untuk tekanan kekunci fizikal"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Tunjuk kemaskinian permukaan"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Denyar permukaan tetingkap apabila dikemas kini"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Tunjuk kemaskinian paparan"</string>
diff --git a/packages/SettingsLib/res/values-my/strings.xml b/packages/SettingsLib/res/values-my/strings.xml
index 8458a62..7346d91 100644
--- a/packages/SettingsLib/res/values-my/strings.xml
+++ b/packages/SettingsLib/res/values-my/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"SIM အသုံးပြုခြင်း"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD အသံ- <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD အသံ"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"နားကြားကိရိယာ"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"နားကြားကိရိယာ"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"နားကြားကိရိယာနှင့် ချိတ်ဆက်ပြီးပါပြီ"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"နားကြားကိရိယာနှင့် ချိတ်ဆက်ပြီးပါပြီ"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"LE အသံနှင့် ချိတ်ဆက်ထားသည်"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"မီဒီယာအသံအား ချိတ်ဆက်ရန်"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"ဖုန်းအသံအား ချိတ်ဆက်ရန်"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"ဖုန်းအသံအားအသုံးပြုရန်"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"ဖိုင်လွဲပြောင်းရန်အတွက်အသုံးပြုရန်"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"ထည့်သွင်းရန်အသုံးပြုသည်"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"နားကြားကိရိယာအတွက် အသုံးပြုသည်"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"နားကြားကိရိယာအတွက် အသုံးပြုသည်"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"LE_AUDIO အတွက် သုံးသည်"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"တွဲချိတ်ရန်"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"တွဲချိတ်ရန်"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"လက်ရှိထိတွေ့မှုဒေတာကို ဖန်သားပေါ်တွင်ထပ်၍ ပြသသည်"</string>
     <string name="show_touches" msgid="8437666942161289025">"တို့ခြင်းများကို ပြပါ"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"တို့ခြင်းများအတွက် အမြင်ဖြင့် တုံ့ပြန်မှုပြသည်"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"ကီးနှိပ်မှုများ ပြပါ"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"ပကတိကီးနှိပ်မှုများအတွက် ရုပ်မြင်အကြံပြုချက် ပြပါ"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"မျက်နှာပြင်အပ်ဒိတ်ပြရန်"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"အပ်ဒိတ်လုပ်စဉ် ဝင်းဒိုးမျက်နှာပြင်တွင် အချက်ပြရန်"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"မြင်ကွင်းအပ်ဒိတ်များ ပြခြင်း"</string>
diff --git a/packages/SettingsLib/res/values-nb/strings.xml b/packages/SettingsLib/res/values-nb/strings.xml
index a082be6..dc68bf3 100644
--- a/packages/SettingsLib/res/values-nb/strings.xml
+++ b/packages/SettingsLib/res/values-nb/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"Tilgang til SIM-kortet"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD-lyd: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD-lyd"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Høreapparater"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Høreapparater"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE-lyd"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Koblet til høreapparater"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Koblet til høreapparater"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Koblet til LE-lyd"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Koblet til medielyd"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Koblet til telefonlyd"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Bruk for telefonlyd"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Bruk til filoverføring"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Bruk for inndata"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Bruk for høreapparater"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Bruk for høreapparater"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Bruk for LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Koble til"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"KOBLE TIL"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Skjermoverlegg viser aktuelle berøringsdata"</string>
     <string name="show_touches" msgid="8437666942161289025">"Vis trykk"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Vis visuell tilbakemelding for trykk"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Vis tastetrykk"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Vis visuell tilbakemelding for fysiske tastetrykk"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Vis overflateoppdateringer"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Fremhev hele vindusoverflater når de oppdateres"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Vis visningsoppdateringer"</string>
diff --git a/packages/SettingsLib/res/values-ne/strings.xml b/packages/SettingsLib/res/values-ne/strings.xml
index 349b9d3..5d61132 100644
--- a/packages/SettingsLib/res/values-ne/strings.xml
+++ b/packages/SettingsLib/res/values-ne/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"SIM एक्सेस"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD अडियो: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD अडियो"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"श्रवण यन्त्रहरू"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"श्रवण यन्त्रहरू"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE अडियो"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"श्रवण यन्त्रहरूमा जडान गरियो"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"श्रवण यन्त्रहरूमा जडान गरियो"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"LE अडियोमा कनेक्ट गरिएको छ"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"मिडिया अडियोसँग जडित"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"फोन अडियोमा जडान गरियो"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"फोन अडियोको लागि प्रयोग गर्नुहोस्"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"फाइल ट्रान्सफरका लागि प्रयोग गर्नुहोस्"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"इनपुटको लागि प्रयोग गर्नुहोस्"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"श्रवण यन्त्रहरूका लागि प्रयोग गर्नुहोस्"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"श्रवण यन्त्रहरूका लागि प्रयोग गर्नुहोस्"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"LE_AUDIO मा प्रयोग गर्नुहोस्"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"कनेक्ट गर्नुहोस्"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"जोडी"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"स्क्रिन ओभरलेले हालको टच डेटा देखाउँदै छ"</string>
     <string name="show_touches" msgid="8437666942161289025">"ट्याप देखाइयोस्"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"ट्यापका लागि भिजुअल प्रतिक्रिया देखाइयोस्"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"थिचिएका कीहरू देखाइयून्"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"थिचिएका भौतिक कीसम्बन्धी भिजुअल प्रतिक्रिया देखाइयोस्"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"सर्फेस अपडेट देखाइयोस्"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"अपडेट हुँदा विन्डोका पूरै सतहमा देखाइयोस्"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"GPU भ्युको अपडेट देखाइयोस्"</string>
diff --git a/packages/SettingsLib/res/values-nl/strings.xml b/packages/SettingsLib/res/values-nl/strings.xml
index 1b456b9..51fc99a2 100644
--- a/packages/SettingsLib/res/values-nl/strings.xml
+++ b/packages/SettingsLib/res/values-nl/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"Sim-toegang"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD-audio: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD-audio"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Hoortoestellen"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Hoortoestellen"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Verbonden met hoortoestellen"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Verbonden met hoortoestellen"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Verbonden met LE Audio"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Verbonden met audio van medium"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Verbonden met audio van telefoon"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Gebruiken voor audio van telefoon"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Gebruiken voor bestandsoverdracht"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Gebruiken voor invoer"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Gebruiken voor hoortoestellen"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Gebruiken voor hoortoestellen"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Gebruiken voor LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Koppelen"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"KOPPELEN"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Schermoverlay met huidige aanraakgegevens"</string>
     <string name="show_touches" msgid="8437666942161289025">"Tikken tonen"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Toon visuele feedback voor tikken"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Toetsaanslagen tonen"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Visuele feedback tonen voor fysieke toetsaanslagen"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Oppervlakupdates tonen"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Flash volledige vensteroppervlakken bij updates"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Weergave-updates tonen"</string>
diff --git a/packages/SettingsLib/res/values-or/strings.xml b/packages/SettingsLib/res/values-or/strings.xml
index 164f24d..bf7470c 100644
--- a/packages/SettingsLib/res/values-or/strings.xml
+++ b/packages/SettingsLib/res/values-or/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"SIM ଆକ୍ସେସ"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD ଅଡିଓ: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD ଅଡିଓ"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"ଶ୍ରବଣ ଯନ୍ତ୍ର"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"ଶ୍ରବଣ ଯନ୍ତ୍ର"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE ଅଡିଓ"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"ଶ୍ରବଣ ଯନ୍ତ୍ରକୁ ସଂଯୋଗ ହୋଇଛି"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"ଶ୍ରବଣ ଯନ୍ତ୍ର ସହ କନେକ୍ଟ କରାଯାଇଛି"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"LE ଅଡିଓ ସହ କନେକ୍ଟ କରାଯାଇଛି"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"ମିଡିଆ ଅଡିଓ ସହ ସଂଯୁକ୍ତ"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"ଫୋନ୍‌ ଅଡିଓ ସହିତ ସଂଯୁକ୍ତ"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"ଫୋନ୍‌ ଅଡିଓ ପାଇଁ ବ୍ୟବହାର କର"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"ଫାଇଲ୍‌ ଟ୍ରାନ୍ସଫର୍‌ ପାଇଁ ବ୍ୟବହାର କରନ୍ତୁ"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"ଇନ୍‌ପୁଟ୍‌ ପାଇଁ ବ୍ୟବହାର କରନ୍ତୁ"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"ଶ୍ରବଣ ଯନ୍ତ୍ର ପାଇଁ ବ୍ୟବହାର କରନ୍ତୁ"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"ଶ୍ରବଣ ଯନ୍ତ୍ର ପାଇଁ ବ୍ୟବହାର କରନ୍ତୁ"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"LE_AUDIO ପାଇଁ ବ୍ୟବହାର କରନ୍ତୁ"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"ପେୟାର୍‌"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"ପେୟାର୍‌"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"ଏବେର ଟଚ୍‌ ଡାଟା ଦେଖାଉଥିବା ସ୍କ୍ରୀନ୍‌ ଓଭର୍‌ଲେ"</string>
     <string name="show_touches" msgid="8437666942161289025">"ଟାପ୍‌ ଦେଖାନ୍ତୁ"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"ଟାପ୍ ପାଇଁ ଭିଜୁଆଲ୍ ମତାମତ ଦେଖାନ୍ତୁ"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"ଦବାଯାଇଥିବା କୀ ଦେଖାନ୍ତୁ"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"ଦବାଯାଇଥିବା ଫିଜିକାଲ କୀ ପାଇଁ ଭିଜୁଆଲ ମତାମତ ଦେଖାନ୍ତୁ"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"ସର୍ଫେସ୍‌ ଅପଡେଟ୍‌ ଦେଖାନ୍ତୁ"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"ସମଗ୍ର ୱିଣ୍ଡୋ ପୃଷ୍ଠ ଅପଡେଟ୍‌ ହେବା ବେଳେ ସେଗୁଡ଼ିକ ଫ୍ଲାସ୍‌ କରନ୍ତୁ"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"ଭ୍ୟୁ ଅପଡେଟ୍‌ଗୁଡ଼ିକୁ ଦେଖାନ୍ତୁ"</string>
diff --git a/packages/SettingsLib/res/values-pa/strings.xml b/packages/SettingsLib/res/values-pa/strings.xml
index 3e20201..3c4c4bc 100644
--- a/packages/SettingsLib/res/values-pa/strings.xml
+++ b/packages/SettingsLib/res/values-pa/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"ਸਿਮ ਪਹੁੰਚ"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD ਆਡੀਓ: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD ਆਡੀਓ"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"ਸੁਣਨ ਦੇ ਸਾਧਨ"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"ਸੁਣਨ ਦੇ ਸਾਧਨ"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE ਆਡੀਓ"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"ਸੁਣਨ ਦੇ ਸਾਧਨਾਂ ਨਾਲ ਕਨੈਕਟ ਕੀਤਾ ਗਿਆ"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"ਸੁਣਨ ਦੇ ਸਾਧਨਾਂ ਨਾਲ ਕਨੈਕਟ ਕੀਤਾ ਗਿਆ"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"LE ਆਡੀਓ ਨਾਲ ਕਨੈਕਟ ਕੀਤਾ ਗਿਆ"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"ਮੀਡੀਆ  ਆਡੀਓ  ਨਾਲ ਕਨੈਕਟ ਕੀਤਾ"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"ਫ਼ੋਨ ਔਡੀਓ ਨਾਲ ਕਨੈਕਟ ਕੀਤਾ"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"ਫ਼ੋਨ ਔਡੀਓ ਲਈ ਵਰਤੋ"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"ਫਾਈਲ ਟ੍ਰਾਂਸਫਰ ਲਈ ਵਰਤੋ"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"ਇਨਪੁਟ ਲਈ ਵਰਤੋ"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"ਸੁਣਨ ਦੇ ਸਾਧਨਾਂ ਲਈ ਵਰਤੋ"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"ਸੁਣਨ ਦੇ ਸਾਧਨਾਂ ਲਈ ਵਰਤੋ"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"LE_AUDIO ਲਈ ਵਰਤੋ"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"ਜੋੜਾਬੱਧ ਕਰੋ"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"ਜੋੜਾਬੱਧ ਕਰੋ"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"ਸਕ੍ਰੀਨ ਓਵਰਲੇ ਮੌਜੂਦਾ ਸਪਰਸ਼ ਡਾਟਾ ਦਿਖਾ ਰਿਹਾ ਹੈ"</string>
     <string name="show_touches" msgid="8437666942161289025">"ਟੈਪਾਂ ਦਿਖਾਓ"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"ਟੈਪਾਂ ਲਈ ਦ੍ਰਿਸ਼ਟੀਗਤ ਪ੍ਰਤੀਕਰਮ ਦਿਖਾਓ"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"ਕੁੰਜੀ ਦਬਾਉਣ ਸੰਬੰਧੀ ਜਾਣਕਾਰੀ ਦਿਖਾਓ"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"ਭੌਤਿਕ ਕੁੰਜੀ ਦਬਾਉਣ ਸੰਬੰਧੀ ਦ੍ਰਿਸ਼ਟੀਗਤ ਵਿਚਾਰ ਦਿਖਾਓ"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"ਸਰਫ਼ੇਸ ਅੱਪਡੇਟ ਦਿਖਾਓ"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"ਅੱਪਡੇਟ ਹੋਣ \'ਤੇ, ਸਮੁੱਚੀਆਂ ਵਿੰਡੋ ਸਰਫ਼ੇਸਾਂ ਫਲੈਸ਼ ਕਰੋ"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"\'ਅੱਪਡੇਟ ਦੇਖੋ\' ਨੂੰ ਦਿਖਾਓ"</string>
diff --git a/packages/SettingsLib/res/values-pl/strings.xml b/packages/SettingsLib/res/values-pl/strings.xml
index 58a89d9..62f7e81 100644
--- a/packages/SettingsLib/res/values-pl/strings.xml
+++ b/packages/SettingsLib/res/values-pl/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"Dostęp do karty SIM"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"Dźwięk HD: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"Dźwięk HD"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Aparaty słuchowe"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Aparaty słuchowe"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Połączono z aparatami słuchowymi"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Połączono z aparatami słuchowymi"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Połączono z LE Audio"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Połączono z funkcją audio multimediów"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Połączono z funkcją audio telefonu"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Użyj dla funkcji audio telefonu"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Użyj do transferu plików"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Użyj do wprowadzania"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Użyj z aparatami słuchowymi"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Używaj w przypadku aparatów słuchowych"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Używaj dla LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Sparuj"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"SPARUJ"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Nakładka pokazująca dane o dotknięciach ekranu"</string>
     <string name="show_touches" msgid="8437666942161289025">"Pokazuj dotknięcia"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Pokazuj potwierdzenie wizualne po dotknięciu"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Wyświetl naciśnięcia klawiszy"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Wyświetl opinie wizualne dla naciśnięć fizycznego klucza"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Pokazuj zmiany powierzchni"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Podświetlaj całe aktualizowane powierzchnie okien"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Pokazuj aktualizacje widoku"</string>
diff --git a/packages/SettingsLib/res/values-pt-rBR/strings.xml b/packages/SettingsLib/res/values-pt-rBR/strings.xml
index 678c076..6880511 100644
--- a/packages/SettingsLib/res/values-pt-rBR/strings.xml
+++ b/packages/SettingsLib/res/values-pt-rBR/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"Acesso ao chip"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"Áudio HD: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"Áudio HD"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Aparelhos auditivos"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Aparelhos auditivos"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Conectado a aparelhos auditivos"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Conectado a aparelhos auditivos"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Conectado ao perfil Áudio de baixa energia"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Conectado ao áudio da mídia"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Conectado ao áudio do smartphone"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Usar para áudio do smartphone"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Usado para transferência de arquivo"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Usar para entrada"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Usar para aparelhos auditivos"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Usar para aparelhos auditivos"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Usar para LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Parear"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"PAREAR"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Exibir dados de toque"</string>
     <string name="show_touches" msgid="8437666942161289025">"Mostrar toques"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Mostrar feedback visual para toques"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Mostrar teclas pressionadas"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Mostrar feedback visual de teclas pressionadas"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Mostrar superfície atualizada"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Piscar superfícies de toda a janela ao atualizar"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Ver atualizações de exibição"</string>
diff --git a/packages/SettingsLib/res/values-pt-rPT/strings.xml b/packages/SettingsLib/res/values-pt-rPT/strings.xml
index 847bfb5..49e3f91 100644
--- a/packages/SettingsLib/res/values-pt-rPT/strings.xml
+++ b/packages/SettingsLib/res/values-pt-rPT/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"Acesso ao SIM"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"Áudio HD: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"Áudio HD"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Aparelhos auditivos"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Aparelhos auditivos"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Ligado a aparelhos auditivos"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Ligado a aparelhos auditivos"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Ligado a LE Audio"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Ligado ao áudio de multimédia"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Ligado ao áudio do telefone"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Usar para áudio do telefone"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Usar para transferência de ficheiros"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Usar para entrada"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Usar para aparelhos auditivos"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Usar para aparelhos auditivos"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Usar para LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Sincr."</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"SINCRONIZAR"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Apresentar dados atuais de toque"</string>
     <string name="show_touches" msgid="8437666942161289025">"Mostrar toques"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Mostrar feedback visual para toques"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Mostrar toques em teclas"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Mostrar feedback visual (toques em teclas físicas)"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Mostrar superfície atualizada"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Destacar a superfície da janela ao atualizar"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Ver atualizações de vistas"</string>
diff --git a/packages/SettingsLib/res/values-pt/strings.xml b/packages/SettingsLib/res/values-pt/strings.xml
index 678c076..6880511 100644
--- a/packages/SettingsLib/res/values-pt/strings.xml
+++ b/packages/SettingsLib/res/values-pt/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"Acesso ao chip"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"Áudio HD: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"Áudio HD"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Aparelhos auditivos"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Aparelhos auditivos"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Conectado a aparelhos auditivos"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Conectado a aparelhos auditivos"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Conectado ao perfil Áudio de baixa energia"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Conectado ao áudio da mídia"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Conectado ao áudio do smartphone"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Usar para áudio do smartphone"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Usado para transferência de arquivo"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Usar para entrada"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Usar para aparelhos auditivos"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Usar para aparelhos auditivos"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Usar para LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Parear"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"PAREAR"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Exibir dados de toque"</string>
     <string name="show_touches" msgid="8437666942161289025">"Mostrar toques"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Mostrar feedback visual para toques"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Mostrar teclas pressionadas"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Mostrar feedback visual de teclas pressionadas"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Mostrar superfície atualizada"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Piscar superfícies de toda a janela ao atualizar"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Ver atualizações de exibição"</string>
diff --git a/packages/SettingsLib/res/values-ro/strings.xml b/packages/SettingsLib/res/values-ro/strings.xml
index 7fa228b..1dce0e1 100644
--- a/packages/SettingsLib/res/values-ro/strings.xml
+++ b/packages/SettingsLib/res/values-ro/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"Acces la SIM"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"Audio HD: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"Audio HD"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Aparate auditive"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Aparate auditive"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Conectat la aparatul auditiv"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Conectat la aparatul auditiv"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Conectat la LE audio"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Conectat la profilul pentru conținut media audio"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Conectat la componenta audio a telefonului"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Folosește pentru componenta audio a telefonului"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Folosește pentru transferul de fișiere"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Folosește pentru introducere date"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Folosește pentru aparatele auditive"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Folosește pentru aparatele auditive"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Folosește pentru LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Asociază"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"CONECTEAZĂ"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Suprapunere care indică date curente pt. atingeri"</string>
     <string name="show_touches" msgid="8437666942161289025">"Afișează atingerile"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Afișează feedbackul vizual pentru atingeri"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Arată apăsările pe taste"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Arată feedback vizual pentru apăsările pe taste"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Actualizări suprafețe"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Iluminarea întregii fereastre la actualizare"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Afiș. actualizări ecran"</string>
diff --git a/packages/SettingsLib/res/values-ru/strings.xml b/packages/SettingsLib/res/values-ru/strings.xml
index a53b67a..79db17c 100644
--- a/packages/SettingsLib/res/values-ru/strings.xml
+++ b/packages/SettingsLib/res/values-ru/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"Доступ к SIM-карте"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD Audio: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD Audio"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Слуховые аппараты"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Слуховые аппараты"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Слуховой аппарат подключен"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Слуховой аппарат подключен"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Подключено к LE Audio"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Подключено к мультимедийному аудиоустройству"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Подключено к аудиоустройству телефона"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Использовать для аудиоустройства телефона"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Используется для передачи файлов"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Использовать для ввода"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Использовать для слухового аппарата"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Использовать для слухового аппарата"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Использовать для LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Добавить"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"ДОБАВИТЬ"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Показывать данные касаний и жестов"</string>
     <string name="show_touches" msgid="8437666942161289025">"Показывать нажатия"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Визуальный отклик при нажатии"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Показывать нажатия клавиш"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Показывать визуальный отклик при нажатии клавиш"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Показ. обнов. поверхности"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Подсвечивать поверхности окон при обновлении"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Показывать обнов. экрана"</string>
diff --git a/packages/SettingsLib/res/values-si/strings.xml b/packages/SettingsLib/res/values-si/strings.xml
index ab5d3ce..c43f5dd 100644
--- a/packages/SettingsLib/res/values-si/strings.xml
+++ b/packages/SettingsLib/res/values-si/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"SIM ප්‍රවේශය"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD ශ්‍රව්‍යය: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD ශ්‍රව්‍යය"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"ශ්‍රවණාධාරක"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"ශ්‍රවණාධාරක"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE ශ්‍රව්‍ය"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"ශ්‍රවණාධාරක වෙත සම්බන්ධ කළා"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"ශ්‍රවණාධාරක වෙත සම්බන්ධ කළා"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"LE ශ්‍රව්‍ය වෙත සම්බන්ධ විය"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"මාධ්‍ය ශ්‍රව්‍යට සම්බන්ධ විය"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"දුරකතනයේ ශ්‍රව්‍යට සම්බන්ධ විය"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"දුරකථන ශ්‍රව්‍ය සඳහා භාවිතා කෙරේ"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"ගොනු හුවමාරුව සඳහා භාවිතා කරන්න"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"ආදානය සඳහා භාවිතා කරන්න"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"ශ්‍රවණාධාර සඳහා භාවිත කරන්න"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"ශ්‍රවණාධාර සඳහා භාවිත කරන්න"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"LE_AUDIO සඳහා භාවිත කරන්න"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"යුගල කරන්න"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"යුගල කරන්න"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"තිර උඩැතිරිය වර්තමාන ස්පර්ශ දත්ත පෙන්වයි"</string>
     <string name="show_touches" msgid="8437666942161289025">"තට්ටු කිරීම් පෙන්වන්න"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"තට්ටු කිරීම් සඳහා දෘශ්‍ය ප්‍රතිපෝෂණ පෙන්වන්න"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"යතුරු එබීම් පෙන්වන්න"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"භෞතික යතුරු එබීම සඳහා දෘශ්‍ය ප්‍රතිපෝෂණය පෙන්වන්න"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"පෘෂ්ඨ යාවත්කාලීන පෙන්වන්න"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"යාවත්කාලින වනවිට මුළු කවුළු තලයම දැල්වෙන්න"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"නැරඹීම් යාවත්කාලීන පෙන්වන්න"</string>
diff --git a/packages/SettingsLib/res/values-sk/strings.xml b/packages/SettingsLib/res/values-sk/strings.xml
index daf9492..ec383f9 100644
--- a/packages/SettingsLib/res/values-sk/strings.xml
+++ b/packages/SettingsLib/res/values-sk/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"Prístup k SIM karte"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD zvuk: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD zvuk"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Načúvadlá"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Načúvadlá"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Pripojené k načúvadlám"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Pripojené k načúvadlám"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Pripojené k systému LE audio"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Pripojené ku zvukovému médiu"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Pripojené ku zvuku telefónu"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Použiť pre zvuk telefónu"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Použiť na prenos súborov"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Použiť pre vstup"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Použiť pre načúvadlá"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Používať pre načúvadlá"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Používať s profilom LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Párovať"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"PÁROVAŤ"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Zobraziť prekryvnú vrstvu s aktuálnymi údajmi o klepnutiach"</string>
     <string name="show_touches" msgid="8437666942161289025">"Zobrazovať klepnutia"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Vizuálne znázorňovať klepnutia"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Zobrazovať stlač. kláves."</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Zobr. vizuálnu spätnú väzbu fyz. stlač. klávesov"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Ukazovať obnovenia obsahu"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Rozblikať obsah okna pri aktualizácii"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Ukazovať obnovenia zobrazenia"</string>
diff --git a/packages/SettingsLib/res/values-sl/strings.xml b/packages/SettingsLib/res/values-sl/strings.xml
index 7164da6..4d0e7a8 100644
--- a/packages/SettingsLib/res/values-sl/strings.xml
+++ b/packages/SettingsLib/res/values-sl/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"Dostop do kartice SIM"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"Zvok visoke kakovosti: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"Zvok visoke kakovosti"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Slušni pripomočki"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Slušni aparati"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE zvok"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Povezava s slušnimi pripomočki je vzpostavljena"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Povezava s slušnimi aparati je vzpostavljena."</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Povezano s profilom LE Audio"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Povezan s profilom za predstavnostni zvok"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Povezava s profilom za zvok telefona vzpostavljena"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Uporabi za zvok telefona"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Uporabi za prenos datotek"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Uporabi za vnos"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Uporabi za slušne pripomočke"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Uporabi za slušne aparate"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Uporaba za profil LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Seznani"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"SEZNANI"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Prekrivanje zaslona prikazuje trenutni dotik."</string>
     <string name="show_touches" msgid="8437666942161289025">"Prikaži dotike"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Prikaži vizualne povratne informacije za dotike."</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Pokaži pritiske tipk"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Pokaži vizualne povratne informacije za pritiske fizičnih tipk"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Pokaži posodob. površine"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Ob posodobitvi osveži celotne površine oken."</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Prikaži posodob. pogleda"</string>
diff --git a/packages/SettingsLib/res/values-sq/strings.xml b/packages/SettingsLib/res/values-sq/strings.xml
index 7fd6ec9..2fdac37 100644
--- a/packages/SettingsLib/res/values-sq/strings.xml
+++ b/packages/SettingsLib/res/values-sq/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"Qasje në kartën SIM"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"Audio HD: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"Audio HD"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Aparatet e dëgjimit"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Aparatet e dëgjimit"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Lidhur me aparatet e dëgjimit"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Lidhur me aparatet e dëgjimit"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"U lidh me audion LE"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"U lidh me audion e medias"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"U lidh me audion e telefonit"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Përdor për audion e telefonit"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Përdor për transferimin e skedarëve"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Përdore për hyrjen"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Përdore për aparatet e dëgjimit"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Përdore për aparatet e dëgjimit"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Përdor për LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Çifto"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"ÇIFTO"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Mbivendosja e ekranit tregon të dhënat e prekjes"</string>
     <string name="show_touches" msgid="8437666942161289025">"Shfaq trokitjet"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Shfaq reagimet vizuale për trokitjet"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Shfaq shtypjet e tasteve"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Shfaq reagime vizuale për shtypjen e tasteve fizike"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Shfaq përditësimet e sipërfaqes"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Ndriço të gjitha sipërfaqet e dritares kur ato të përditësohen"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Shfaq përditësimet e pamjes"</string>
diff --git a/packages/SettingsLib/res/values-sr/strings.xml b/packages/SettingsLib/res/values-sr/strings.xml
index 86675cf..dc698ce 100644
--- a/packages/SettingsLib/res/values-sr/strings.xml
+++ b/packages/SettingsLib/res/values-sr/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"Приступ SIM картици"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD звук: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD звук"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Слушни апарати"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Слушни апарати"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Повезано са слушним апаратима"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Повезано са слушним апаратима"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Повезано са LE audio"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Повезано са звуком медија"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Повезано са звуком телефона"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Коришћење за аудио телефона"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Коришћење за пренос датотека"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Користи за улаз"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Користи за слушне апарате"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Користи за слушне апарате"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Користите за LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Упари"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"УПАРИ"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Преклопни елемент са тренутним подацима о додиру"</string>
     <string name="show_touches" msgid="8437666942161289025">"Приказуј додире"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Приказује визуелне повратне информације за додире"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Приказуј притиске тастера"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Приказује повратне информације за притиске тастера"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Прикажи ажурирања површине"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Осветљава све површине прозора када се ажурирају"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Прикажи ажурирања приказа"</string>
diff --git a/packages/SettingsLib/res/values-sv/strings.xml b/packages/SettingsLib/res/values-sv/strings.xml
index 0d71afd..64412dd5 100644
--- a/packages/SettingsLib/res/values-sv/strings.xml
+++ b/packages/SettingsLib/res/values-sv/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"SIM-åtkomst"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD-ljud: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD-ljud"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Hörapparater"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Hörapparater"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Ansluten till hörapparater"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Ansluten till hörapparater"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Ansluten till LE audio"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Ansluten till medialjud"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Ansluten till telefonens ljud"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Använd för telefonens ljud"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Använd för filöverföring"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Använd för inmatning"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Använd med hörapparater"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Använd med hörapparater"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Använd för LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Parkoppla"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"PARKOPPLA"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Överlägg på skärmen med aktuella skärmtryck"</string>
     <string name="show_touches" msgid="8437666942161289025">"Visa tryck"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Visa visuell feedback för tryck"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Visa tangenttryckningar"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Visa visuell feedback för tangenttryckningar"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Visa ytuppdateringar"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Hela fönstret blinkar vid uppdatering"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Visa visningsuppdatering"</string>
diff --git a/packages/SettingsLib/res/values-sw/strings.xml b/packages/SettingsLib/res/values-sw/strings.xml
index 303131e..4893fe83 100644
--- a/packages/SettingsLib/res/values-sw/strings.xml
+++ b/packages/SettingsLib/res/values-sw/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"Ufikiaji wa SIM"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"Sauti ya HD: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"Sauti ya HD"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Vifaa vya Kusaidia Kusikia"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Visaidizi vya kusikia"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Imeunganishwa kwenye Vifaa vya Kusaidia Kusikia"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Imeunganishwa kwenye visaidizi vya kusikia"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Imeunganishwa kwenye LE audio"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Imeunganishwa kwenye sikika ya njia ya mawasiliano"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Imeunganishwa kwenye sauti ya simu"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Tumia kwa sauti ya simu"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Tumia kwa hali faili"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Tumia kwa kuingiza"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Tumia kwenye Vifaa vya Kusaidia Kusikia"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Tumia kwa visaidizi vya kusikia"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Tumia kwa ajili ya LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Oanisha"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"OANISHA"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Kuegeshwa kwa skrini ikionyesha data ya mguso ya sasa"</string>
     <string name="show_touches" msgid="8437666942161289025">"Onyesha unapogusa"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Onyesha ishara za kuthibitisha unapogusa"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Onyesha mibofyo ya vitufe"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Onyesha muhtasari wa mibofyo halisi ya vitufe"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Onyesha masasisho ya sehemu"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Angaza dirisha lote zitakaposasisha"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Onyesha taarifa za kuonekana"</string>
diff --git a/packages/SettingsLib/res/values-ta/strings.xml b/packages/SettingsLib/res/values-ta/strings.xml
index 83aa2af..b72c533 100644
--- a/packages/SettingsLib/res/values-ta/strings.xml
+++ b/packages/SettingsLib/res/values-ta/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"சிம் அணுகல்"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD ஆடியோ: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD ஆடியோ"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"செவித்துணை கருவிகள்"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"செவித்துணைக் கருவிகள்"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE ஆடியோ"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"செவித்துணை கருவிகளுடன் இணைக்கப்பட்டது"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"செவித்துணைக் கருவிகளுடன் இணைக்கப்பட்டது"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"LE ஆடியோவுடன் இணைக்கப்பட்டுள்ளது"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"மீடியா ஆடியோவுடன் இணைக்கப்பட்டது"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"மொபைல் ஆடியோவுடன் இணைக்கப்பட்டது"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"மொபைல் ஆடியோவைப் பயன்படுத்து"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"ஃபைல் பரிமாற்றத்திற்காகப் பயன்படுத்து"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"உள்ளீட்டுக்குப் பயன்படுத்து"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"செவித்துணை கருவிகளுக்குப் பயன்படுத்தவும்"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"செவித்துணைக் கருவிகளுக்காகப் பயன்படுத்தப்படும்"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"LE_AUDIOவிற்குப் பயன்படுத்தும்"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"இணை"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"இணை"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"திரையின் மேல் அடுக்கானது தற்போது தொடப்பட்டிருக்கும் தரவைக் காண்பிக்கிறது"</string>
     <string name="show_touches" msgid="8437666942161289025">"தட்டல்களைக் காட்டு"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"தட்டல்களின் போது காட்சி அறிகுறிகளைக் காட்டும்"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"பட்டன் அழுத்தத்தை காட்டவா"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"பட்டன் அழுத்தங்களைக் காட்சி மூலம் உறுதிப்படுத்தும்"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"மேலோட்ட புதுப்பிப்புகளைக் காட்டு"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"சாளரத்தின் பரப்புநிலைகள் புதுப்பிக்கப்படும்போது, அவற்றை முழுவதுமாகக் காட்டும்"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"வியூ அப்டேட்ஸைக் காட்டு"</string>
diff --git a/packages/SettingsLib/res/values-te/strings.xml b/packages/SettingsLib/res/values-te/strings.xml
index a216c81..ae1b251 100644
--- a/packages/SettingsLib/res/values-te/strings.xml
+++ b/packages/SettingsLib/res/values-te/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"SIM యాక్సెస్"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD ఆడియో: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD ఆడియో"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"వినికిడి మద్దతు ఉపకరణాలు"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"వినికిడి పరికరాలు"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE ఆడియో"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"వినికిడి మద్దతు ఉపకరణాలకు కనెక్ట్ చేయబడింది"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"వినికిడి పరికరాలకు కనెక్ట్ చేయబడింది"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"LE ఆడియోకు కనెక్ట్ చేయబడింది"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"మీడియా ఆడియోకు కనెక్ట్ చేయబడింది"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"ఫోన్ ఆడియోకు కనెక్ట్ చేయబడింది"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"ఫోన్ ఆడియో కోసం ఉపయోగించండి"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"ఫైల్ బదిలీ కోసం ఉపయోగించండి"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"ఇన్‌పుట్ కోసం ఉపయోగించండి"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"వినికిడి మద్దతు ఉపకరణాలకు ఉపయోగించండి"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"వినికిడి పరికరాల కోసం ఉపయోగించండి"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"LE_AUDIO కోసం ఉపయోగించండి"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"జత చేయి"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"జత చేయి"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"ప్రస్తుత టచ్ డేటాను చూపుతోన్న స్క్రీన్"</string>
     <string name="show_touches" msgid="8437666942161289025">"నొక్కినవి చూపు"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"నొక్కినప్పుడు దృశ్యపరమైన ప్రతిస్పందన చూపు"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"కీ ప్రెస్‌లను చూడండి"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"భౌతిక కీ ప్రెస్‌ల విజువల్ ఫీడ్‌బ్యాక్‌ను చూడండి"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"సర్ఫేస్‌ అప్‌డేట్లను చూపు"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"విండో సర్‌ఫేస్‌లన్నీ అప్‌డేట్‌ అయితే ఫ్లాష్ చేయి"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"వీక్షణ అప్‌డేట్‌లను చూపు"</string>
diff --git a/packages/SettingsLib/res/values-th/strings.xml b/packages/SettingsLib/res/values-th/strings.xml
index 32629c3..14ef3a3 100644
--- a/packages/SettingsLib/res/values-th/strings.xml
+++ b/packages/SettingsLib/res/values-th/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"การเข้าถึงซิม"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"เสียง HD: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"เสียง HD"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"เครื่องช่วยฟัง"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"เครื่องช่วยฟัง"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"เชื่อมต่อกับเครื่องช่วยฟังแล้ว"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"เชื่อมต่อกับเครื่องช่วยฟังแล้ว"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"เชื่อมต่อกับ LE Audio แล้ว"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"เชื่อมต่อกับระบบเสียงของสื่อแล้ว"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"เชื่อมต่อกับระบบเสียงของโทรศัพท์แล้ว"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"ใช้สำหรับระบบเสียงของโทรศัพท์"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"ใช้สำหรับการโอนไฟล์"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"ใช้สำหรับการป้อนข้อมูล"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"ใช้สำหรับเครื่องช่วยฟัง"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"ใช้สำหรับเครื่องช่วยฟัง"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"ใช้สำหรับ LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"จับคู่"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"จับคู่อุปกรณ์"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"การวางซ้อนหน้าจอที่แสดงข้อมูลการแตะในปัจจุบัน"</string>
     <string name="show_touches" msgid="8437666942161289025">"แสดงการแตะ"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"แสดงผลตอบสนองแบบภาพเมื่อแตะ"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"แสดงการกดแป้น"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"แสดงฟีดแบ็กที่เป็นภาพสำหรับการกดแป้นพิมพ์"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"แสดงการอัปเดตพื้นผิว"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"กะพริบหน้าต่างทั้งหมดเมื่อมีการอัปเดต"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"แสดงการอัปเดตมุมมอง"</string>
diff --git a/packages/SettingsLib/res/values-tl/strings.xml b/packages/SettingsLib/res/values-tl/strings.xml
index e60faf6..ab4acb6 100644
--- a/packages/SettingsLib/res/values-tl/strings.xml
+++ b/packages/SettingsLib/res/values-tl/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"Access sa SIM"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD audio: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD audio"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Mga Hearing Aid"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Mga hearing aid"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Nakakonekta sa Mga Hearing Aid"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Nakakonekta sa mga hearing aid"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Nakakonekta sa LE audio"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Konektado sa media audio"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Nakakonekta sa audio ng telepono"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Ginagamit para sa audio ng telepono"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Ginagamit para sa paglilipat ng file"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Gamitin para sa input"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Gamitin para sa Mga Hearing Aid"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Gamitin para sa mga hearing aid"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Gamitin para sa LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Ipares"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"IPARES"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Overlay ng screen na nagpapakita ng touch data"</string>
     <string name="show_touches" msgid="8437666942161289025">"Ipakita ang mga pag-tap"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Ipakita ang visual na feedback para sa mga pag-tap"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Ipakita ang mga key press"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Ipakita: visual feedback ng mga physical key press"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Ipakita update sa surface"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"I-flash ang buong window surface kapag nag-update"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Ipakita update ng view"</string>
diff --git a/packages/SettingsLib/res/values-tr/strings.xml b/packages/SettingsLib/res/values-tr/strings.xml
index 3ea12ad..abbef53 100644
--- a/packages/SettingsLib/res/values-tr/strings.xml
+++ b/packages/SettingsLib/res/values-tr/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"SIM Erişimi"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD ses: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD ses"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"İşitme Cihazları"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"İşitme cihazları"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"İşitme Cihazlarına Bağlandı"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"İşitme cihazlarına bağlandı"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"LE Audio\'ya bağlandı"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Medya sesine bağlanıldı"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Telefon sesine bağlandı"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Telefon sesi için kullan"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Dosya aktarımı için kullan"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Giriş için kullan"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"İşitme Cihazları için kullan"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"İşitme cihazları için kullan"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"LE_AUDIO kullan"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Eşle"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"EŞLE"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Mevcut dokunmatik verilerini gösteren yer paylaşımı"</string>
     <string name="show_touches" msgid="8437666942161289025">"Dokunmayı göster"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Dokunmalarda görsel geri bildirim göster"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Basılan tuşları göster"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Fiziksel tuşlara basınca görsel geri bildirim ver"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Yüzey güncellemelerini göster"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Güncellenirken tüm pencere yüzeylerini yakıp söndür"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Görünüm güncellemelerini göster"</string>
diff --git a/packages/SettingsLib/res/values-uk/strings.xml b/packages/SettingsLib/res/values-uk/strings.xml
index 95ece26..bf297e6 100644
--- a/packages/SettingsLib/res/values-uk/strings.xml
+++ b/packages/SettingsLib/res/values-uk/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"Доступ до SIM-карти"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD-аудіо: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD-аудіо"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Слухові апарати"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Слухові апарати"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Підключено до слухових апаратів"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Підключено до слухових апаратів"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Підключено до LE Audio"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Підключено до аудіоджерела"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Підключено до звуку телеф."</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Викор. для звуку тел."</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Викор. для перед. файлів"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Викор. для введ."</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Використовувати для слухових апаратів"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Використовувати для слухових апаратів"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Використовувати для LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Підключити"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"ПІДКЛЮЧИТИСЯ"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Показувати на екрані жести й натискання"</string>
     <string name="show_touches" msgid="8437666942161289025">"Показувати дотики"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Показувати візуальну реакцію на торкання"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Показувати натиск. клавіш"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Показувати візуальний відгук на натискання клавіш"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Показ. оновлення поверхні"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Підсвічувати вікна повністю під час оновлення"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Показувати оновлення областей"</string>
diff --git a/packages/SettingsLib/res/values-ur/strings.xml b/packages/SettingsLib/res/values-ur/strings.xml
index 70aa9a3..db2bb26 100644
--- a/packages/SettingsLib/res/values-ur/strings.xml
+++ b/packages/SettingsLib/res/values-ur/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"‏SIM رسائی"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"‏HD آڈیو: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"‏HD آڈیو"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"سماعتی آلات"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"سماعتی آلات"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"‏LE آڈیو"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"سماعتی آلات سے منسلک ہے"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"سماعتی آلات سے منسلک ہے"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"‏LE آڈیو سے منسلک ہے"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"میڈیا آڈیو سے مربوط"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"فون آڈیو سے مربوط"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"فون آڈیو کیلئے استعمال کریں"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"فائل منتقل کرنے کیلئے استعمال کریں"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"ان پٹ کیلئے استعمال"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"سماعتی آلات کے لیے استعمال کریں"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"سماعتی آلات کیلئے استعمال کریں"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"‏LE_AUDIO کے لیے استعمال کریں"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"جوڑا بنائیں"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"جوڑا بنائیں"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"موجودہ ٹچ ڈیٹا دکھانے والا اسکرین اوور لے"</string>
     <string name="show_touches" msgid="8437666942161289025">"تھپتھپاہٹیں دکھائیں"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"تھپتھپاہٹوں کیلئے بصری تاثرات دکھائیں"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"\'کلید کو دبانا\' دکھائیں"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"\'طبعی کلید کو دبانا\' کیلئے ویژوئل تاثرات دکھائیں"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"سطح کے اپ ڈیٹس دکھائیں"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"اپ ڈیٹ ہونے پر ونڈو کی پوری سطحیں جھلملائیں"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"منظر کے اپ ڈیٹس دکھائیں"</string>
diff --git a/packages/SettingsLib/res/values-uz/strings.xml b/packages/SettingsLib/res/values-uz/strings.xml
index 5f42969..96e5d6a 100644
--- a/packages/SettingsLib/res/values-uz/strings.xml
+++ b/packages/SettingsLib/res/values-uz/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"SIM kartaga kirish"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD audio: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD audio"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Eshitish apparatlari"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Eshitish moslamalari"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Eshitish apparatlariga ulangan"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Eshitish moslamalariga ulangan"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"LE audioga ulandi"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Audio qurilmasiga ulangan"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Telefon karnayiga ulanildi"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Dok’dan karnay sifatida foydalanish"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Fayl almashinish uchun foydalanish"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Kiritish qurilmasi sifatida foydalanish"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Eshitish apparatlari uchun foydalanish"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Eshitish moslamalari uchun foydalanish"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"LE_AUDIO uchun foydalanish"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"OK"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"ULANISH"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Ekranda bosilgan va tegilgan joylarni vizuallashtirish"</string>
     <string name="show_touches" msgid="8437666942161289025">"Bosishlarni ko‘rsatish"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Ekranda bosilgan joylardagi nuqtalarni ko‘rsatish"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Tugma bosishlarini chiqarish"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Jismoniy tugmani bosishlar uchun vizual fikr-mulohazani chiqarish"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Yuza yangilanishlarini ko‘rsatish"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Yangilangandan so‘ng to‘liq oyna sirtlarini miltillatish"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Yangilash oynasini ochish"</string>
diff --git a/packages/SettingsLib/res/values-vi/strings.xml b/packages/SettingsLib/res/values-vi/strings.xml
index d75f1b2..7035077 100644
--- a/packages/SettingsLib/res/values-vi/strings.xml
+++ b/packages/SettingsLib/res/values-vi/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"Truy cập SIM"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"Âm thanh HD: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"Âm thanh HD"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Thiết bị trợ thính"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Thiết bị trợ thính"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"Âm thanh năng lượng thấp"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Đã kết nối với Thiết bị trợ thính"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Đã kết nối với thiết bị trợ thính"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Đã kết nối với âm thanh LE"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Đã kết nối với âm thanh nội dung nghe nhìn"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Đã kết nối với âm thanh điện thoại"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Sử dụng cho âm thanh điện thoại"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Sử dụng để chuyển tệp"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Sử dụng để nhập"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Dùng cho Thiết bị trợ thính"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Dùng cho thiết bị trợ thính"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Dùng cho LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Ghép nối"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"GHÉP NỐI"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Lớp phủ màn hình hiển thị dữ liệu chạm hiện tại"</string>
     <string name="show_touches" msgid="8437666942161289025">"Hiện số lần nhấn"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Hiển thị phản hồi trực quan cho các lần nhấn"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Hiện thao tác nhấn phím"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Hiện phản hồi trực quan cho thao tác nhấn phím vật lý"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Hiện bản cập nhật giao diện"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Chuyển nhanh toàn bộ các giao diện cửa sổ khi các giao diện này cập nhật"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Hiện bản cập nhật chế độ xem"</string>
diff --git a/packages/SettingsLib/res/values-zh-rCN/strings.xml b/packages/SettingsLib/res/values-zh-rCN/strings.xml
index 6787f32..fabc004 100644
--- a/packages/SettingsLib/res/values-zh-rCN/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rCN/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"SIM 卡访问权限"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD 音频:<xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD 音频"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"助听器"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"助听器"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE 音频"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"已连接到助听器"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"已连接到助听器"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"已连接到 LE 音频"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"已连接到媒体音频"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"已连接到手机音频"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"用于手机音频"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"用于文件传输"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"用于输入"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"用于助听器"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"用于助听器"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"用于 LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"配对"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"配对"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"屏幕叠加层显示当前触摸数据"</string>
     <string name="show_touches" msgid="8437666942161289025">"显示点按操作反馈"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"显示点按操作的视觉反馈"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"显示按键操作"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"显示实体按键操作的视觉反馈"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"显示面 (surface) 更新"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"窗口中的面 (surface) 更新时全部闪烁"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"显示视图更新"</string>
diff --git a/packages/SettingsLib/res/values-zh-rHK/strings.xml b/packages/SettingsLib/res/values-zh-rHK/strings.xml
index e6da4bc..9ec57a3 100644
--- a/packages/SettingsLib/res/values-zh-rHK/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rHK/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"SIM 卡存取"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"高清音訊:<xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"高清音訊"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"助聽器"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"助聽器"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"已連接助聽器"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"已連接助聽器"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"已連接 LE Audio"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"已連接媒體音頻裝置"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"已連接手機耳機"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"用於手機音效"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"用於傳輸檔案"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"用於輸入"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"用於助聽器"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"用於助聽器"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"用於 LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"配對"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"配對"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"在螢幕上重疊顯示目前的觸控資料"</string>
     <string name="show_touches" msgid="8437666942161289025">"顯示輕按回應"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"顯示輕按位置的視覺回應"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"顯示按鍵操作"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"顯示實際按鍵操作的視覺回應"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"顯示表層更新"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"更新表層時閃動整個視窗表層"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"顯示畫面更新"</string>
diff --git a/packages/SettingsLib/res/values-zh-rTW/strings.xml b/packages/SettingsLib/res/values-zh-rTW/strings.xml
index e33934c..59e66aa 100644
--- a/packages/SettingsLib/res/values-zh-rTW/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rTW/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"SIM 卡存取權"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD 高解析音訊:<xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD 高解析音訊"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"助聽器"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"助聽器"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"已連接到助聽器"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"已連上助聽器"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"已連上 LE audio"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"連接至媒體音訊"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"連接至電話音訊"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"用於電話音訊"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"用於傳輸檔案"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"用於輸入"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"用於助聽器"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"用於助聽器"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"用於 LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"配對"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"配對"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"在螢幕圖層上顯示目前的觸控資料"</string>
     <string name="show_touches" msgid="8437666942161289025">"顯示觸控回應"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"顯示觸控位置的視覺回應"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"顯示按鍵操作"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"顯示實際按鍵操作的視覺回饋"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"顯示表層更新"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"更新表層時閃爍顯示整個視窗表層"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"顯示畫面更新"</string>
diff --git a/packages/SettingsLib/res/values-zu/strings.xml b/packages/SettingsLib/res/values-zu/strings.xml
index 5c7a7b5..d292bd5 100644
--- a/packages/SettingsLib/res/values-zu/strings.xml
+++ b/packages/SettingsLib/res/values-zu/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"Ukufinyelela kwe-SIM"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"Umsindo we-HD: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"Umsindo we-HD"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Izinsiza zokuzwa"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Imishini yendlebe"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"Umsindo we-LE"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Kuxhumeke kwizinsiza zokuzwa"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Kuxhume emishinini yendlebe"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Kuxhunywe kumsindo we-LE"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Ixhume emsindweni wemidiya"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Ixhunywe kumsindo wefoni"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Sebenziselwa umsindo wefoni"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Sebenziselwa ukudlulisa ifayela"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Isetshenziselwa okufakwayo"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Sebenzisa izinsiza zokuzwa"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Sebenzisa imishini yendlebe"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Sebenzisela i-LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Bhangqa"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"BHANGQA"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Imbondela yesikrini ibonisa idatha yokuthinta yamanje"</string>
     <string name="show_touches" msgid="8437666942161289025">"Bonisa amathebhu"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Bonisa izmpendulo ebukekayo ngamathebhu"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Bonisa ukucindezela ukhiye"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Bonisa impendulo ebonakalayo yokucindezela ukhiye obonakalayo"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Buka izibuyekezo ezibonakalayo"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Khanyisa ukubonakala kwalo lonke iwindi uma libuyekezwa"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Bonisa izibuyekezo zokubuka"</string>
diff --git a/packages/SettingsLib/res/values/dimens.xml b/packages/SettingsLib/res/values/dimens.xml
index 91549d7..07854bd 100644
--- a/packages/SettingsLib/res/values/dimens.xml
+++ b/packages/SettingsLib/res/values/dimens.xml
@@ -124,4 +124,5 @@
     <dimen name="dialog_bottom_padding">18dp</dimen>
     <dimen name="dialog_side_padding">24dp</dimen>
     <dimen name="dialog_button_bar_top_padding">32dp</dimen>
+    <dimen name="button_corner_radius">28dp</dimen>
 </resources>
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index b44c0e0..60bc226 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -241,12 +241,12 @@
     <!-- Bluetooth settings. Similar to bluetooth_profile_a2dp_high_quality, but used when the device supports high quality audio but we don't know which codec that will be used. -->
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec">HD audio</string>
 
-    <!-- Bluetooth settings.  The user-visible string that is used whenever referring to the Hearing Aid profile. -->
-    <string name="bluetooth_profile_hearing_aid">Hearing Aids</string>
+    <!-- Bluetooth settings.  The user-visible string that is used whenever referring to the Hearing aid profile. -->
+    <string name="bluetooth_profile_hearing_aid">Hearing aids</string>
     <!-- Bluetooth settings.  The user-visible string that is used whenever referring to the LE audio profile. -->
     <string name="bluetooth_profile_le_audio">LE Audio</string>
-    <!-- Bluetooth settings.  Connection options screen.  The summary for the Hearing Aid checkbox preference when Hearing Aid is connected. -->
-    <string name="bluetooth_hearing_aid_profile_summary_connected">Connected to Hearing Aids</string>
+    <!-- Bluetooth settings.  Connection options screen.  The summary for the Hearing aid checkbox preference when hearing aid is connected. -->
+    <string name="bluetooth_hearing_aid_profile_summary_connected">Connected to hearing aids</string>
     <!-- Bluetooth settings.  Connection options screen.  The summary for the LE audio checkbox preference when LE audio is connected. -->
     <string name="bluetooth_le_audio_profile_summary_connected">Connected to LE audio</string>
 
@@ -287,8 +287,8 @@
          for the HID checkbox preference that describes how checking it
          will set the HID profile as preferred. -->
     <string name="bluetooth_hid_profile_summary_use_for">Use for input</string>
-    <!-- Bluetooth settings.  Connection options screen.  The summary for the Hearing Aid checkbox preference that describes how checking it will set the Hearing Aid profile as preferred. -->
-    <string name="bluetooth_hearing_aid_profile_summary_use_for">Use for Hearing Aids</string>
+    <!-- Bluetooth settings.  Connection options screen.  The summary for the Hearing aid checkbox preference that describes how checking it will set the hearing aid related profile as preferred. -->
+    <string name="bluetooth_hearing_aid_profile_summary_use_for">Use for hearing aids</string>
     <!-- Bluetooth settings.  Connection options screen.  The summary for the LE_AUDIO checkbox preference that describes how checking it will set the LE_AUDIO profile as preferred. -->
     <string name="bluetooth_le_audio_profile_summary_use_for">Use for LE_AUDIO</string>
 
@@ -827,6 +827,11 @@
     <!-- UI debug setting: show touches location summary [CHAR LIMIT=50] -->
     <string name="show_touches_summary">Show visual feedback for taps</string>
 
+    <!-- UI debug setting: show key presses? [CHAR LIMIT=50] -->
+    <string name="show_key_presses">Show key presses</string>
+    <!-- UI debug setting: show physical key presses summary [CHAR LIMIT=150] -->
+    <string name="show_key_presses_summary">Show visual feedback for physical key presses</string>
+
     <!-- UI debug setting: show where surface updates happen? [CHAR LIMIT=25] -->
     <string name="show_screen_updates">Show surface updates</string>
     <!-- UI debug setting: show surface updates summary [CHAR LIMIT=50] -->
diff --git a/packages/SettingsLib/res/values/styles.xml b/packages/SettingsLib/res/values/styles.xml
index 4ac4aae..1249c6b 100644
--- a/packages/SettingsLib/res/values/styles.xml
+++ b/packages/SettingsLib/res/values/styles.xml
@@ -93,7 +93,6 @@
     </style>
 
     <style name="DialogButtonPositive">
-        <item name="android:buttonCornerRadius">0dp</item>
         <item name="android:background">@drawable/dialog_btn_filled</item>
         <item name="android:textColor">?androidprv:attr/textColorOnAccent</item>
         <item name="android:textSize">14sp</item>
@@ -104,7 +103,6 @@
     </style>
 
     <style name="DialogButtonNegative">
-        <item name="android:buttonCornerRadius">28dp</item>
         <item name="android:background">@drawable/dialog_btn_outline</item>
         <item name="android:textColor">?android:attr/textColorPrimary</item>
         <item name="android:textSize">14sp</item>
diff --git a/packages/SettingsLib/search/Android.bp b/packages/SettingsLib/search/Android.bp
index cfff519..918d696 100644
--- a/packages/SettingsLib/search/Android.bp
+++ b/packages/SettingsLib/search/Android.bp
@@ -7,8 +7,18 @@
     default_applicable_licenses: ["frameworks_base_license"],
 }
 
+java_library {
+    name: "SettingsLib-search-interface",
+    visibility: ["//visibility:private"],
+    srcs: ["interface-src/**/*.java"],
+    host_supported: true,
+}
+
 android_library {
     name: "SettingsLib-search",
+    static_libs: [
+        "SettingsLib-search-interface",
+    ],
     srcs: ["src/**/*.java"],
 
     sdk_version: "system_current",
@@ -19,12 +29,10 @@
     name: "SettingsLib-annotation-processor",
     processor_class: "com.android.settingslib.search.IndexableProcessor",
     static_libs: [
+        "SettingsLib-search-interface",
         "javapoet",
     ],
-    srcs: [
-        "processor-src/**/*.java",
-        "src/com/android/settingslib/search/SearchIndexable.java",
-    ],
+    srcs: ["processor-src/**/*.java"],
     java_resource_dirs: ["resources"],
 }
 
diff --git a/packages/SettingsLib/search/common.mk b/packages/SettingsLib/search/common.mk
deleted file mode 100644
index 05226db..0000000
--- a/packages/SettingsLib/search/common.mk
+++ /dev/null
@@ -1,10 +0,0 @@
-# Include this file to generate SearchIndexableResourcesImpl
-
-LOCAL_ANNOTATION_PROCESSORS += \
-    SettingsLib-annotation-processor
-
-LOCAL_ANNOTATION_PROCESSOR_CLASSES += \
-    com.android.settingslib.search.IndexableProcessor
-
-LOCAL_STATIC_JAVA_LIBRARIES += \
-    SettingsLib-search
diff --git a/packages/SettingsLib/search/interface-src/com/android/settingslib/search/SearchIndexable.java b/packages/SettingsLib/search/interface-src/com/android/settingslib/search/SearchIndexable.java
new file mode 100644
index 0000000..174f337
--- /dev/null
+++ b/packages/SettingsLib/search/interface-src/com/android/settingslib/search/SearchIndexable.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.search;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Denotes that the class should participate in search indexing.
+ */
+@Retention(RetentionPolicy.SOURCE)
+public @interface SearchIndexable {
+    /**
+     * Bitfield for the form factors this class should be considered indexable for.
+     * Default is {@link #ALL}.
+     * <p>
+     * TODO: actually use this value somehow
+     */
+    int forTarget() default ALL;
+
+    /**
+     * Indicates that the class should be considered indexable for Mobile.
+     */
+    int MOBILE = 1 << 0;
+
+    /**
+     * Indicates that the class should be considered indexable for TV.
+     */
+    int TV = 1 << 1;
+
+    /**
+     * Indicates that the class should be considered indexable for Wear.
+     */
+    int WEAR = 1 << 2;
+
+    /**
+     * Indicates that the class should be considered indexable for Auto.
+     */
+    int AUTO = 1 << 3;
+
+    /**
+     * Indicates that the class should be considered indexable for ARC++.
+     */
+    int ARC = 1 << 4;
+
+    /**
+     * Indicates that the class should be considered indexable for all targets.
+     */
+    int ALL = MOBILE | TV | WEAR | AUTO | ARC;
+
+}
diff --git a/packages/SettingsLib/search/processor-src/com/android/settingslib/search/IndexableProcessor.java b/packages/SettingsLib/search/processor-src/com/android/settingslib/search/IndexableProcessor.java
index e92157e..fa43915 100644
--- a/packages/SettingsLib/search/processor-src/com/android/settingslib/search/IndexableProcessor.java
+++ b/packages/SettingsLib/search/processor-src/com/android/settingslib/search/IndexableProcessor.java
@@ -34,6 +34,7 @@
 import javax.annotation.processing.ProcessingEnvironment;
 import javax.annotation.processing.RoundEnvironment;
 import javax.annotation.processing.SupportedAnnotationTypes;
+import javax.annotation.processing.SupportedOptions;
 import javax.annotation.processing.SupportedSourceVersion;
 import javax.lang.model.SourceVersion;
 import javax.lang.model.element.Element;
@@ -48,10 +49,11 @@
  * subclasses.
  */
 @SupportedSourceVersion(SourceVersion.RELEASE_17)
+@SupportedOptions(IndexableProcessor.PACKAGE_KEY)
 @SupportedAnnotationTypes({"com.android.settingslib.search.SearchIndexable"})
 public class IndexableProcessor extends AbstractProcessor {
 
-    private static final String PACKAGE = "com.android.settingslib.search";
+    private static final String SETTINGSLIB_SEARCH_PACKAGE = "com.android.settingslib.search";
     private static final String CLASS_BASE = "SearchIndexableResourcesBase";
     private static final String CLASS_MOBILE = "SearchIndexableResourcesMobile";
     private static final String CLASS_TV = "SearchIndexableResourcesTv";
@@ -59,6 +61,9 @@
     private static final String CLASS_AUTO = "SearchIndexableResourcesAuto";
     private static final String CLASS_ARC = "SearchIndexableResourcesArc";
 
+    static final String PACKAGE_KEY = "com.android.settingslib.search.processor.package";
+
+    private String mPackage;
     private Filer mFiler;
     private Messager mMessager;
     private boolean mRanOnce;
@@ -72,7 +77,8 @@
         }
         mRanOnce = true;
 
-        final ClassName searchIndexableData = ClassName.get(PACKAGE, "SearchIndexableData");
+        final ClassName searchIndexableData =
+                ClassName.get(SETTINGSLIB_SEARCH_PACKAGE, "SearchIndexableData");
 
         final FieldSpec providers = FieldSpec.builder(
                 ParameterizedTypeName.get(
@@ -130,7 +136,7 @@
                         builder = arcConstructorBuilder;
                     }
                     builder.addCode(
-                            "$N(new SearchIndexableData($L.class, $L"
+                            "$N(new com.android.settingslib.search.SearchIndexableData($L.class, $L"
                                     + ".SEARCH_INDEX_DATA_PROVIDER));\n",
                             addIndex, className, className);
                 } else {
@@ -150,50 +156,51 @@
 
         final TypeSpec baseClass = TypeSpec.classBuilder(CLASS_BASE)
                 .addModifiers(Modifier.PUBLIC)
-                .addSuperinterface(ClassName.get(PACKAGE, "SearchIndexableResources"))
+                .addSuperinterface(
+                        ClassName.get(SETTINGSLIB_SEARCH_PACKAGE, "SearchIndexableResources"))
                 .addField(providers)
                 .addMethod(baseConstructorBuilder.build())
                 .addMethod(addIndex)
                 .addMethod(getProviderValues)
                 .build();
-        final JavaFile searchIndexableResourcesBase = JavaFile.builder(PACKAGE, baseClass).build();
+        final JavaFile searchIndexableResourcesBase = JavaFile.builder(mPackage, baseClass).build();
 
-        final JavaFile searchIndexableResourcesMobile = JavaFile.builder(PACKAGE,
+        final JavaFile searchIndexableResourcesMobile = JavaFile.builder(mPackage,
                 TypeSpec.classBuilder(CLASS_MOBILE)
                         .addModifiers(Modifier.PUBLIC)
-                        .superclass(ClassName.get(PACKAGE, baseClass.name))
+                        .superclass(ClassName.get(mPackage, baseClass.name))
                         .addMethod(mobileConstructorBuilder.build())
                         .build())
                 .build();
 
-        final JavaFile searchIndexableResourcesTv = JavaFile.builder(PACKAGE,
+        final JavaFile searchIndexableResourcesTv = JavaFile.builder(mPackage,
                 TypeSpec.classBuilder(CLASS_TV)
                         .addModifiers(Modifier.PUBLIC)
-                        .superclass(ClassName.get(PACKAGE, baseClass.name))
+                        .superclass(ClassName.get(mPackage, baseClass.name))
                         .addMethod(tvConstructorBuilder.build())
                         .build())
                 .build();
 
-        final JavaFile searchIndexableResourcesWear = JavaFile.builder(PACKAGE,
+        final JavaFile searchIndexableResourcesWear = JavaFile.builder(mPackage,
                 TypeSpec.classBuilder(CLASS_WEAR)
                         .addModifiers(Modifier.PUBLIC)
-                        .superclass(ClassName.get(PACKAGE, baseClass.name))
+                        .superclass(ClassName.get(mPackage, baseClass.name))
                         .addMethod(wearConstructorBuilder.build())
                         .build())
                 .build();
 
-        final JavaFile searchIndexableResourcesAuto = JavaFile.builder(PACKAGE,
+        final JavaFile searchIndexableResourcesAuto = JavaFile.builder(mPackage,
                 TypeSpec.classBuilder(CLASS_AUTO)
                         .addModifiers(Modifier.PUBLIC)
-                        .superclass(ClassName.get(PACKAGE, baseClass.name))
+                        .superclass(ClassName.get(mPackage, baseClass.name))
                         .addMethod(autoConstructorBuilder.build())
                         .build())
                 .build();
 
-        final JavaFile searchIndexableResourcesArc = JavaFile.builder(PACKAGE,
+        final JavaFile searchIndexableResourcesArc = JavaFile.builder(mPackage,
                 TypeSpec.classBuilder(CLASS_ARC)
                         .addModifiers(Modifier.PUBLIC)
-                        .superclass(ClassName.get(PACKAGE, baseClass.name))
+                        .superclass(ClassName.get(mPackage, baseClass.name))
                         .addMethod(arcConstructorBuilder.build())
                         .build())
                 .build();
@@ -214,6 +221,8 @@
     @Override
     public synchronized void init(ProcessingEnvironment processingEnvironment) {
         super.init(processingEnvironment);
+        mPackage = processingEnvironment.getOptions()
+                .getOrDefault(PACKAGE_KEY, SETTINGSLIB_SEARCH_PACKAGE);
         mFiler = processingEnvironment.getFiler();
         mMessager = processingEnvironment.getMessager();
     }
diff --git a/packages/SettingsLib/search/src/com/android/settingslib/search/SearchIndexable.java b/packages/SettingsLib/search/src/com/android/settingslib/search/SearchIndexable.java
deleted file mode 100644
index 638fa3e9..0000000
--- a/packages/SettingsLib/search/src/com/android/settingslib/search/SearchIndexable.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settingslib.search;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-/**
- * Denotes that the class should participate in search indexing.
- */
-@Retention(RetentionPolicy.SOURCE)
-public @interface SearchIndexable {
-    /**
-     * Bitfield for the form factors this class should be considered indexable for.
-     * Default is {@link #ALL}.
-     *
-     * TODO: actually use this value somehow
-     */
-    int forTarget() default ALL;
-
-    /**
-     * Indicates that the class should be considered indexable for Mobile.
-     */
-    int MOBILE = 1<<0;
-
-    /**
-     * Indicates that the class should be considered indexable for TV.
-     */
-    int TV = 1<<1;
-
-    /**
-     * Indicates that the class should be considered indexable for Wear.
-     */
-    int WEAR = 1<<2;
-
-    /**
-     * Indicates that the class should be considered indexable for Auto.
-     */
-    int AUTO = 1<<3;
-
-    /**
-     * Indicates that the class should be considered indexable for ARC++.
-     */
-    int ARC = 1<<4;
-
-    /**
-     * Indicates that the class should be considered indexable for all targets.
-     */
-    int ALL = MOBILE | TV | WEAR | AUTO | ARC;
-
-}
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java
index 00ccea1..ff960f3 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java
@@ -409,7 +409,7 @@
      */
     public static EnforcedAdmin checkIfUsbDataSignalingIsDisabled(Context context, int userId) {
         DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class);
-        if (dpm == null || dpm.isUsbDataSignalingEnabledForUser(userId)) {
+        if (dpm == null || dpm.isUsbDataSignalingEnabled()) {
             return null;
         } else {
             EnforcedAdmin admin = getProfileOrDeviceOwner(context, getUserHandleOf(userId));
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/InterestingConfigChanges.java b/packages/SettingsLib/src/com/android/settingslib/applications/InterestingConfigChanges.java
index 64a0781..5d520ce 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/InterestingConfigChanges.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/InterestingConfigChanges.java
@@ -28,11 +28,11 @@
 public class InterestingConfigChanges {
     private final Configuration mLastConfiguration = new Configuration();
     private final int mFlags;
-    private int mLastDensity;
 
     public InterestingConfigChanges() {
         this(ActivityInfo.CONFIG_LOCALE | ActivityInfo.CONFIG_LAYOUT_DIRECTION
-                | ActivityInfo.CONFIG_UI_MODE | ActivityInfo.CONFIG_ASSETS_PATHS);
+                | ActivityInfo.CONFIG_UI_MODE | ActivityInfo.CONFIG_ASSETS_PATHS
+                | ActivityInfo.CONFIG_DENSITY);
     }
 
     public InterestingConfigChanges(int flags) {
@@ -50,11 +50,6 @@
     public boolean applyNewConfig(Resources res) {
         int configChanges = mLastConfiguration.updateFrom(
                 Configuration.generateDelta(mLastConfiguration, res.getConfiguration()));
-        boolean densityChanged = mLastDensity != res.getDisplayMetrics().densityDpi;
-        if (densityChanged || (configChanges & (mFlags)) != 0) {
-            mLastDensity = res.getDisplayMetrics().densityDpi;
-            return true;
-        }
-        return false;
+        return (configChanges & (mFlags)) != 0;
     }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index 2e6bb53..2118d2c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -356,11 +356,7 @@
         connectDevice();
     }
 
-    public HearingAidInfo getHearingAidInfo() {
-        return mHearingAidInfo;
-    }
-
-    public void setHearingAidInfo(HearingAidInfo hearingAidInfo) {
+    void setHearingAidInfo(HearingAidInfo hearingAidInfo) {
         mHearingAidInfo = hearingAidInfo;
     }
 
@@ -583,7 +579,7 @@
      */
     public void setName(String name) {
         // Prevent getName() to be set to null if setName(null) is called
-        if (name == null || TextUtils.equals(name, getName())) {
+        if (TextUtils.isEmpty(name) || TextUtils.equals(name, getName())) {
             return;
         }
         mDevice.setAlias(name);
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
index 0c1b793..441d3a5 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
@@ -20,6 +20,7 @@
 import android.bluetooth.BluetoothCsipSetCoordinator;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothProfile;
+import android.bluetooth.le.ScanFilter;
 import android.content.Context;
 import android.util.Log;
 
@@ -114,10 +115,21 @@
     /**
      * Create and return a new {@link CachedBluetoothDevice}. This assumes
      * that {@link #findDevice} has already been called and returned null.
-     * @param device the address of the new Bluetooth device
+     * @param device the new Bluetooth device
      * @return the newly created CachedBluetoothDevice object
      */
     public CachedBluetoothDevice addDevice(BluetoothDevice device) {
+        return addDevice(device, /*leScanFilters=*/null);
+    }
+
+    /**
+     * Create and return a new {@link CachedBluetoothDevice}. This assumes
+     * that {@link #findDevice} has already been called and returned null.
+     * @param device the new Bluetooth device
+     * @param leScanFilters the BLE scan filters which the device matched
+     * @return the newly created CachedBluetoothDevice object
+     */
+    public CachedBluetoothDevice addDevice(BluetoothDevice device, List<ScanFilter> leScanFilters) {
         CachedBluetoothDevice newDevice;
         final LocalBluetoothProfileManager profileManager = mBtManager.getProfileManager();
         synchronized (this) {
@@ -125,7 +137,7 @@
             if (newDevice == null) {
                 newDevice = new CachedBluetoothDevice(mContext, profileManager, device);
                 mCsipDeviceManager.initCsipDeviceIfNeeded(newDevice);
-                mHearingAidDeviceManager.initHearingAidDeviceIfNeeded(newDevice);
+                mHearingAidDeviceManager.initHearingAidDeviceIfNeeded(newDevice, leScanFilters);
                 if (!mCsipDeviceManager.setMemberDeviceIfNeeded(newDevice)
                         && !mHearingAidDeviceManager.setSubDeviceIfNeeded(newDevice)) {
                     mCachedDevices.add(newDevice);
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java
index e5e5782..efba953 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java
@@ -18,10 +18,13 @@
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothHearingAid;
 import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothUuid;
+import android.bluetooth.le.ScanFilter;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.media.AudioDeviceAttributes;
 import android.media.audiopolicy.AudioProductStrategy;
+import android.os.ParcelUuid;
 import android.provider.Settings;
 import android.util.Log;
 
@@ -59,7 +62,8 @@
         mRoutingHelper = routingHelper;
     }
 
-    void initHearingAidDeviceIfNeeded(CachedBluetoothDevice newDevice) {
+    void initHearingAidDeviceIfNeeded(CachedBluetoothDevice newDevice,
+            List<ScanFilter> leScanFilters) {
         long hiSyncId = getHiSyncId(newDevice.getDevice());
         if (isValidHiSyncId(hiSyncId)) {
             // Once hiSyncId is valid, assign hearing aid info
@@ -68,6 +72,21 @@
                     .setAshaDeviceMode(getDeviceMode(newDevice.getDevice()))
                     .setHiSyncId(hiSyncId);
             newDevice.setHearingAidInfo(infoBuilder.build());
+        } else if (leScanFilters != null && !newDevice.isHearingAidDevice()) {
+            // If the device is added with hearing aid scan filter during pairing, set an empty
+            // hearing aid info to indicate it's a hearing aid device. The info will be updated
+            // when corresponding profiles connected.
+            for (ScanFilter leScanFilter: leScanFilters) {
+                final ParcelUuid serviceUuid = leScanFilter.getServiceUuid();
+                final ParcelUuid serviceDataUuid = leScanFilter.getServiceDataUuid();
+                if (BluetoothUuid.HEARING_AID.equals(serviceUuid)
+                        || BluetoothUuid.HAS.equals(serviceUuid)
+                        || BluetoothUuid.HEARING_AID.equals(serviceDataUuid)
+                        || BluetoothUuid.HAS.equals(serviceDataUuid)) {
+                    newDevice.setHearingAidInfo(new HearingAidInfo.Builder().build());
+                    break;
+                }
+            }
         }
     }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
index 79fb566..a05a6e9 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
@@ -237,7 +237,8 @@
                     "startBroadcast: language = " + language + " ,programInfo = " + programInfo);
         }
         buildContentMetadata(language, programInfo);
-        mService.startBroadcast(mBluetoothLeAudioContentMetadata, mBroadcastCode);
+        mService.startBroadcast(mBluetoothLeAudioContentMetadata,
+                (mBroadcastCode != null && mBroadcastCode.length > 0) ? mBroadcastCode : null);
     }
 
     public String getProgramInfo() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java
index b0392be..bb103b8 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java
@@ -221,6 +221,27 @@
     }
 
     /**
+     * Stops an ongoing search for nearby Broadcast Sources.
+     *
+     * On success, {@link BluetoothLeBroadcastAssistant.Callback#onSearchStopped(int)} will be
+     * called with reason code {@link BluetoothStatusCodes#REASON_LOCAL_APP_REQUEST}.
+     * On failure, {@link BluetoothLeBroadcastAssistant.Callback#onSearchStopFailed(int)} will be
+     * called with reason code
+     *
+     * @throws IllegalStateException if callback was not registered
+     */
+    public void stopSearchingForSources() {
+        if (DEBUG) {
+            Log.d(TAG, "stopSearchingForSources()");
+        }
+        if (mService == null) {
+            Log.d(TAG, "The BluetoothLeBroadcastAssistant is null");
+            return;
+        }
+        mService.stopSearchingForSources();
+    }
+
+    /**
      * Get information about all Broadcast Sources that a Broadcast Sink knows about.
      *
      * @param sink Broadcast Sink from which to get all Broadcast Sources
diff --git a/packages/SettingsLib/src/com/android/settingslib/connectivity/OWNERS b/packages/SettingsLib/src/com/android/settingslib/connectivity/OWNERS
index c88ed8e..9f15c1b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/connectivity/OWNERS
+++ b/packages/SettingsLib/src/com/android/settingslib/connectivity/OWNERS
@@ -2,7 +2,6 @@
 andychou@google.com
 arcwang@google.com
 changbetty@google.com
-goldmanj@google.com
 qal@google.com
 wengsu@google.com
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/AbstractPreferenceController.java b/packages/SettingsLib/src/com/android/settingslib/core/AbstractPreferenceController.java
index 9567a3b..ea65ade 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/AbstractPreferenceController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/core/AbstractPreferenceController.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.core;
 
 import android.app.admin.DevicePolicyManager;
@@ -6,8 +22,11 @@
 import android.text.TextUtils;
 import android.util.Log;
 
+import androidx.annotation.EmptySuper;
+import androidx.annotation.NonNull;
 import androidx.annotation.RequiresApi;
 import androidx.core.os.BuildCompat;
+import androidx.lifecycle.LifecycleOwner;
 import androidx.preference.Preference;
 import androidx.preference.PreferenceGroup;
 import androidx.preference.PreferenceScreen;
@@ -52,6 +71,13 @@
     }
 
     /**
+     * Called on view created.
+     */
+    @EmptySuper
+    public void onViewCreated(@NonNull LifecycleOwner viewLifecycleOwner) {
+    }
+
+    /**
      * Updates the current status of preference (summary, switch state, etc)
      */
     public void updateState(Preference preference) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/MetricsFeatureProvider.java b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/MetricsFeatureProvider.java
index 09abc39..9ee8a32 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/MetricsFeatureProvider.java
+++ b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/MetricsFeatureProvider.java
@@ -209,8 +209,7 @@
         }
         final ComponentName cn = intent.getComponent();
         final String key = cn != null ? cn.flattenToString() : intent.getAction();
-        return logSettingsTileClick(key + (isWorkProfile ? "/work" : "/personal"),
-                sourceMetricsCategory);
+        return logSettingsTileClickWithProfile(key, sourceMetricsCategory, isWorkProfile);
     }
 
     /**
@@ -226,4 +225,20 @@
         clicked(sourceMetricsCategory, logKey);
         return true;
     }
+
+    /**
+     * Logs an event when the setting key is clicked with a specific profile from Profile select
+     * dialog.
+     *
+     * @return true if the key is loggable, otherwise false
+     */
+    public boolean logSettingsTileClickWithProfile(String logKey, int sourceMetricsCategory,
+            boolean isWorkProfile) {
+        if (TextUtils.isEmpty(logKey)) {
+            // Not loggable
+            return false;
+        }
+        clicked(sourceMetricsCategory, logKey + (isWorkProfile ? "/work" : "/personal"));
+        return true;
+    }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXml.java b/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXml.java
index fb3f382..e91d697 100644
--- a/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXml.java
+++ b/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXml.java
@@ -324,6 +324,7 @@
 
         if (!TextUtils.isEmpty(noticeHeader)) {
             writer.println(noticeHeader);
+            writer.println("<br/>");
         }
 
         int count = 0;
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java
index 963bd9d..ed518f7 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java
@@ -23,7 +23,6 @@
 import android.graphics.drawable.Drawable;
 import android.media.AudioManager;
 import android.media.MediaRoute2Info;
-import android.media.MediaRouter2Manager;
 import android.media.RouteListingPreference;
 
 import com.android.settingslib.R;
@@ -40,15 +39,21 @@
     private CachedBluetoothDevice mCachedDevice;
     private final AudioManager mAudioManager;
 
-    BluetoothMediaDevice(Context context, CachedBluetoothDevice device,
-            MediaRouter2Manager routerManager, MediaRoute2Info info, String packageName) {
-        this(context, device, routerManager, info, packageName, null);
+    BluetoothMediaDevice(
+            Context context,
+            CachedBluetoothDevice device,
+            MediaRoute2Info info,
+            String packageName) {
+        this(context, device, info, packageName, null);
     }
 
-    BluetoothMediaDevice(Context context, CachedBluetoothDevice device,
-            MediaRouter2Manager routerManager, MediaRoute2Info info, String packageName,
+    BluetoothMediaDevice(
+            Context context,
+            CachedBluetoothDevice device,
+            MediaRoute2Info info,
+            String packageName,
             RouteListingPreference.Item item) {
-        super(context, routerManager, info, packageName, item);
+        super(context, info, packageName, item);
         mCachedDevice = device;
         mAudioManager = context.getSystemService(AudioManager.class);
         initDeviceRecord();
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/ComplexMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/ComplexMediaDevice.java
index c38dfe3..4e0ebd1 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/ComplexMediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/ComplexMediaDevice.java
@@ -19,7 +19,6 @@
 import android.content.Context;
 import android.graphics.drawable.Drawable;
 import android.media.MediaRoute2Info;
-import android.media.MediaRouter2Manager;
 import android.media.RouteListingPreference;
 
 import com.android.settingslib.R;
@@ -32,10 +31,12 @@
 
     private final String mSummary = "";
 
-    ComplexMediaDevice(Context context, MediaRouter2Manager routerManager,
-            MediaRoute2Info info, String packageName,
+    ComplexMediaDevice(
+            Context context,
+            MediaRoute2Info info,
+            String packageName,
             RouteListingPreference.Item item) {
-        super(context, routerManager, info, packageName, item);
+        super(context, info, packageName, item);
     }
 
     // MediaRoute2Info.getName was made public on API 34, but exists since API 30.
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java
index b10d794..012cbc0 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java
@@ -29,7 +29,6 @@
 import android.content.Context;
 import android.graphics.drawable.Drawable;
 import android.media.MediaRoute2Info;
-import android.media.MediaRouter2Manager;
 import android.media.RouteListingPreference;
 
 import androidx.annotation.VisibleForTesting;
@@ -43,15 +42,17 @@
 
     private static final String TAG = "InfoMediaDevice";
 
-    InfoMediaDevice(Context context, MediaRouter2Manager routerManager, MediaRoute2Info info,
-            String packageName, RouteListingPreference.Item item) {
-        super(context, routerManager, info, packageName, item);
+    InfoMediaDevice(
+            Context context,
+            MediaRoute2Info info,
+            String packageName,
+            RouteListingPreference.Item item) {
+        super(context, info, packageName, item);
         initDeviceRecord();
     }
 
-    InfoMediaDevice(Context context, MediaRouter2Manager routerManager, MediaRoute2Info info,
-            String packageName) {
-        this(context, routerManager, info, packageName, null);
+    InfoMediaDevice(Context context, MediaRoute2Info info, String packageName) {
+        this(context, info, packageName, null);
     }
 
     @Override
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
index 1728e40..bff51e3 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
@@ -41,6 +41,7 @@
 
 import static com.android.settingslib.media.LocalMediaManager.MediaDeviceState.STATE_SELECTED;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.TargetApi;
 import android.app.Notification;
@@ -49,7 +50,6 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.media.MediaRoute2Info;
-import android.media.MediaRouter2Manager;
 import android.media.RouteListingPreference;
 import android.media.RoutingSessionInfo;
 import android.os.Build;
@@ -57,7 +57,6 @@
 import android.util.Log;
 
 import androidx.annotation.DoNotInline;
-import androidx.annotation.NonNull;
 import androidx.annotation.RequiresApi;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -67,34 +66,25 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashSet;
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
+import java.util.function.Function;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
-/**
- * InfoMediaManager provide interface to get InfoMediaDevice list.
- */
+/** InfoMediaManager provide interface to get InfoMediaDevice list. */
 @RequiresApi(Build.VERSION_CODES.R)
-public class InfoMediaManager extends MediaManager {
+public abstract class InfoMediaManager extends MediaManager {
 
     private static final String TAG = "InfoMediaManager";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-    @VisibleForTesting
-    final RouterManagerCallback mMediaRouterCallback = new RouterManagerCallback();
-    @VisibleForTesting
-    final Executor mExecutor = Executors.newSingleThreadExecutor();
-    @VisibleForTesting
-    MediaRouter2Manager mRouterManager;
-    @VisibleForTesting
-    String mPackageName;
-    boolean mIsScanning = false;
 
+    protected String mPackageName;
     private MediaDevice mCurrentConnectedDevice;
-    private LocalBluetoothManager mBluetoothManager;
+    private final LocalBluetoothManager mBluetoothManager;
     private final Map<String, RouteListingPreference.Item> mPreferenceItemMap =
             new ConcurrentHashMap<>();
 
@@ -102,7 +92,6 @@
             LocalBluetoothManager localBluetoothManager) {
         super(context, notification);
 
-        mRouterManager = MediaRouter2Manager.getInstance(context);
         mBluetoothManager = localBluetoothManager;
         if (!TextUtils.isEmpty(packageName)) {
             mPackageName = packageName;
@@ -111,31 +100,99 @@
 
     @Override
     public void startScan() {
-        if (!mIsScanning) {
-            mMediaDevices.clear();
-            mRouterManager.registerCallback(mExecutor, mMediaRouterCallback);
-            mRouterManager.registerScanRequest();
-            mIsScanning = true;
-            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE
-                    && !TextUtils.isEmpty(mPackageName)) {
-                RouteListingPreference routeListingPreference =
-                        mRouterManager.getRouteListingPreference(mPackageName);
-                Api34Impl.onRouteListingPreferenceUpdated(routeListingPreference,
-                        mPreferenceItemMap);
-            }
-            refreshDevices();
+        mMediaDevices.clear();
+        startScanOnRouter();
+        updateRouteListingPreference();
+        refreshDevices();
+    }
+
+    private void updateRouteListingPreference() {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE
+                && !TextUtils.isEmpty(mPackageName)) {
+            RouteListingPreference routeListingPreference =
+                    getRouteListingPreference();
+            Api34Impl.onRouteListingPreferenceUpdated(routeListingPreference,
+                    mPreferenceItemMap);
         }
     }
 
     @Override
-    public void stopScan() {
-        if (mIsScanning) {
-            mRouterManager.unregisterCallback(mMediaRouterCallback);
-            mRouterManager.unregisterScanRequest();
-            mIsScanning = false;
+    public abstract void stopScan();
+
+    protected abstract void startScanOnRouter();
+
+    /**
+     * Transfer MediaDevice for media without package name.
+     */
+    protected abstract boolean connectDeviceWithoutPackageName(@NonNull MediaDevice device);
+
+    protected abstract void transferToRoute(@NonNull MediaRoute2Info route);
+
+    protected abstract void selectRoute(
+            @NonNull MediaRoute2Info route, @NonNull RoutingSessionInfo info);
+
+    protected abstract void deselectRoute(
+            @NonNull MediaRoute2Info route, @NonNull RoutingSessionInfo info);
+
+    protected abstract void releaseSession(@NonNull RoutingSessionInfo sessionInfo);
+
+    @NonNull
+    protected abstract List<MediaRoute2Info> getSelectableRoutes(@NonNull RoutingSessionInfo info);
+
+    @NonNull
+    protected abstract List<MediaRoute2Info> getDeselectableRoutes(
+            @NonNull RoutingSessionInfo info);
+
+    @NonNull
+    protected abstract List<MediaRoute2Info> getSelectedRoutes(@NonNull RoutingSessionInfo info);
+
+    protected abstract void setSessionVolume(@NonNull RoutingSessionInfo info, int volume);
+
+    protected abstract void setRouteVolume(@NonNull MediaRoute2Info route, int volume);
+
+    @Nullable
+    protected abstract RouteListingPreference getRouteListingPreference();
+
+    /**
+     * Returns the list of currently active {@link RoutingSessionInfo routing sessions} known to the
+     * system.
+     */
+    @NonNull
+    protected abstract List<RoutingSessionInfo> getActiveRoutingSessions();
+
+    @NonNull
+    protected abstract List<RoutingSessionInfo> getRoutingSessionsForPackage();
+
+    @NonNull
+    protected abstract List<MediaRoute2Info> getAllRoutes();
+
+    @NonNull
+    protected abstract List<MediaRoute2Info> getAvailableRoutesFromRouter();
+
+    @NonNull
+    protected abstract List<MediaRoute2Info> getTransferableRoutes(@NonNull String packageName);
+
+    protected final void rebuildDeviceList() {
+        mMediaDevices.clear();
+        mCurrentConnectedDevice = null;
+        if (TextUtils.isEmpty(mPackageName)) {
+            buildAllRoutes();
+        } else {
+            buildAvailableRoutes();
         }
     }
 
+    protected final void notifyCurrentConnectedDeviceChanged() {
+        final String id = mCurrentConnectedDevice != null ? mCurrentConnectedDevice.getId() : null;
+        dispatchConnectedDeviceChanged(id);
+    }
+
+    @RequiresApi(34)
+    protected final void notifyRouteListingPreferenceUpdated(
+            RouteListingPreference routeListingPreference) {
+        Api34Impl.onRouteListingPreferenceUpdated(routeListingPreference, mPreferenceItemMap);
+    }
+
     /**
      * Get current device that played media.
      * @return MediaDevice
@@ -144,17 +201,18 @@
         return mCurrentConnectedDevice;
     }
 
-    /**
-     * Transfer MediaDevice for media without package name.
-     */
-    boolean connectDeviceWithoutPackageName(MediaDevice device) {
-        boolean isConnected = false;
-        final RoutingSessionInfo info = mRouterManager.getSystemRoutingSession(null);
-        if (info != null) {
-            mRouterManager.transfer(info, device.mRouteInfo);
-            isConnected = true;
+    /* package */ void connectToDevice(MediaDevice device) {
+        if (device.mRouteInfo == null) {
+            Log.w(TAG, "Unable to connect. RouteInfo is empty");
+            return;
         }
-        return isConnected;
+
+        if (TextUtils.isEmpty(mPackageName)) {
+            connectDeviceWithoutPackageName(device);
+        } else {
+            device.setConnectedRecord();
+            transferToRoute(device.mRouteInfo);
+        }
     }
 
     /**
@@ -170,34 +228,27 @@
         }
 
         final RoutingSessionInfo info = getRoutingSessionInfo();
-        if (info != null && info.getSelectableRoutes().contains(device.mRouteInfo.getId())) {
-            mRouterManager.selectRoute(info, device.mRouteInfo);
-            return true;
+        if (info == null || !info.getSelectableRoutes().contains(device.mRouteInfo.getId())) {
+            Log.w(TAG, "addDeviceToPlayMedia() Ignoring selecting a non-selectable device : "
+                    + device.getName());
+            return false;
         }
 
-        Log.w(TAG, "addDeviceToPlayMedia() Ignoring selecting a non-selectable device : "
-                + device.getName());
-
-        return false;
+        selectRoute(device.mRouteInfo, info);
+        return true;
     }
 
     private RoutingSessionInfo getRoutingSessionInfo() {
-        return getRoutingSessionInfo(mPackageName);
-    }
+        final List<RoutingSessionInfo> sessionInfos = getRoutingSessionsForPackage();
 
-    private RoutingSessionInfo getRoutingSessionInfo(String packageName) {
-        final List<RoutingSessionInfo> sessionInfos =
-                mRouterManager.getRoutingSessions(packageName);
-
-        if (sessionInfos == null || sessionInfos.isEmpty()) {
+        if (sessionInfos.isEmpty()) {
             return null;
         }
         return sessionInfos.get(sessionInfos.size() - 1);
     }
 
     boolean isRoutingSessionAvailableForVolumeControl() {
-        List<RoutingSessionInfo> sessions =
-                mRouterManager.getRoutingSessions(mPackageName);
+        List<RoutingSessionInfo> sessions = getRoutingSessionsForPackage();
 
         for (RoutingSessionInfo session : sessions) {
             if (!session.isSystemSession()
@@ -212,15 +263,17 @@
 
     boolean preferRouteListingOrdering() {
         return Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE
-                && Api34Impl.preferRouteListingOrdering(mRouterManager, mPackageName);
+                && !TextUtils.isEmpty(mPackageName)
+                && Api34Impl.preferRouteListingOrdering(getRouteListingPreference());
     }
 
     @Nullable
     ComponentName getLinkedItemComponentName() {
-        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE && TextUtils.isEmpty(
+                mPackageName)) {
             return null;
         }
-        return Api34Impl.getLinkedItemComponentName(mRouterManager, mPackageName);
+        return Api34Impl.getLinkedItemComponentName(getRouteListingPreference());
     }
 
     /**
@@ -236,15 +289,14 @@
         }
 
         final RoutingSessionInfo info = getRoutingSessionInfo();
-        if (info != null && info.getSelectedRoutes().contains(device.mRouteInfo.getId())) {
-            mRouterManager.deselectRoute(info, device.mRouteInfo);
-            return true;
+        if (info == null || !info.getSelectedRoutes().contains(device.mRouteInfo.getId())) {
+            Log.w(TAG, "removeDeviceFromMedia() Ignoring deselecting a non-deselectable device : "
+                    + device.getName());
+            return false;
         }
 
-        Log.w(TAG, "removeDeviceFromMedia() Ignoring deselecting a non-deselectable device : "
-                + device.getName());
-
-        return false;
+        deselectRoute(device.mRouteInfo, info);
+        return true;
     }
 
     /**
@@ -257,105 +309,112 @@
         }
 
         final RoutingSessionInfo sessionInfo = getRoutingSessionInfo();
-
-        if (sessionInfo != null) {
-            mRouterManager.releaseSession(sessionInfo);
-            return true;
+        if (sessionInfo == null) {
+            Log.w(TAG, "releaseSession() Ignoring release session : " + mPackageName);
+            return false;
         }
 
-        Log.w(TAG, "releaseSession() Ignoring release session : " + mPackageName);
-
-        return false;
+        releaseSession(sessionInfo);
+        return true;
     }
 
     /**
-     * Get the MediaDevice list that can be added to current media.
-     *
-     * @return list of MediaDevice
+     * Returns the list of {@link MediaDevice media devices} that can be added to the current {@link
+     * RoutingSessionInfo routing session}.
      */
-    List<MediaDevice> getSelectableMediaDevice() {
-        final List<MediaDevice> deviceList = new ArrayList<>();
+    @NonNull
+    List<MediaDevice> getSelectableMediaDevices() {
         if (TextUtils.isEmpty(mPackageName)) {
-            Log.w(TAG, "getSelectableMediaDevice() package name is null or empty!");
-            return deviceList;
+            Log.w(TAG, "getSelectableMediaDevices() package name is null or empty!");
+            return Collections.emptyList();
         }
 
         final RoutingSessionInfo info = getRoutingSessionInfo();
-        if (info != null) {
-            for (MediaRoute2Info route : mRouterManager.getSelectableRoutes(info)) {
-                deviceList.add(new InfoMediaDevice(mContext, mRouterManager,
-                        route, mPackageName, mPreferenceItemMap.get(route.getId())));
-            }
-            return deviceList;
+        if (info == null) {
+            Log.w(TAG, "getSelectableMediaDevices() cannot find selectable MediaDevice from : "
+                    + mPackageName);
+            return Collections.emptyList();
         }
 
-        Log.w(TAG, "getSelectableMediaDevice() cannot found selectable MediaDevice from : "
-                + mPackageName);
-
+        final List<MediaDevice> deviceList = new ArrayList<>();
+        for (MediaRoute2Info route : getSelectableRoutes(info)) {
+            deviceList.add(
+                    new InfoMediaDevice(
+                            mContext, route, mPackageName, mPreferenceItemMap.get(route.getId())));
+        }
         return deviceList;
     }
 
     /**
-     * Get the MediaDevice list that can be removed from current media session.
-     *
-     * @return list of MediaDevice
+     * Returns the list of {@link MediaDevice media devices} that can be deselected from the current
+     * {@link RoutingSessionInfo routing session}.
      */
-    List<MediaDevice> getDeselectableMediaDevice() {
-        final List<MediaDevice> deviceList = new ArrayList<>();
+    @NonNull
+    List<MediaDevice> getDeselectableMediaDevices() {
         if (TextUtils.isEmpty(mPackageName)) {
-            Log.d(TAG, "getDeselectableMediaDevice() package name is null or empty!");
-            return deviceList;
+            Log.d(TAG, "getDeselectableMediaDevices() package name is null or empty!");
+            return Collections.emptyList();
         }
 
         final RoutingSessionInfo info = getRoutingSessionInfo();
-        if (info != null) {
-            for (MediaRoute2Info route : mRouterManager.getDeselectableRoutes(info)) {
-                deviceList.add(new InfoMediaDevice(mContext, mRouterManager,
-                        route, mPackageName, mPreferenceItemMap.get(route.getId())));
-                Log.d(TAG, route.getName() + " is deselectable for " + mPackageName);
-            }
-            return deviceList;
+        if (info == null) {
+            Log.d(TAG, "getDeselectableMediaDevices() cannot find deselectable MediaDevice from : "
+                    + mPackageName);
+            return Collections.emptyList();
         }
-        Log.d(TAG, "getDeselectableMediaDevice() cannot found deselectable MediaDevice from : "
-                + mPackageName);
 
+        final List<MediaDevice> deviceList = new ArrayList<>();
+        for (MediaRoute2Info route : getDeselectableRoutes(info)) {
+            deviceList.add(
+                    new InfoMediaDevice(
+                            mContext, route, mPackageName, mPreferenceItemMap.get(route.getId())));
+            Log.d(TAG, route.getName() + " is deselectable for " + mPackageName);
+        }
         return deviceList;
     }
 
     /**
-     * Get the MediaDevice list that has been selected to current media.
-     *
-     * @return list of MediaDevice
+     * Returns the list of {@link MediaDevice media devices} that are selected in the current {@link
+     * RoutingSessionInfo routing session}.
      */
-    List<MediaDevice> getSelectedMediaDevice() {
-        final List<MediaDevice> deviceList = new ArrayList<>();
+    @NonNull
+    List<MediaDevice> getSelectedMediaDevices() {
         if (TextUtils.isEmpty(mPackageName)) {
-            Log.w(TAG, "getSelectedMediaDevice() package name is null or empty!");
-            return deviceList;
+            Log.w(TAG, "getSelectedMediaDevices() package name is null or empty!");
+            return Collections.emptyList();
         }
 
         final RoutingSessionInfo info = getRoutingSessionInfo();
-        if (info != null) {
-            for (MediaRoute2Info route : mRouterManager.getSelectedRoutes(info)) {
-                deviceList.add(new InfoMediaDevice(mContext, mRouterManager,
-                        route, mPackageName, mPreferenceItemMap.get(route.getId())));
-            }
-            return deviceList;
+        if (info == null) {
+            Log.w(TAG, "getSelectedMediaDevices() cannot find selectable MediaDevice from : "
+                    + mPackageName);
+            return Collections.emptyList();
         }
 
-        Log.w(TAG, "getSelectedMediaDevice() cannot found selectable MediaDevice from : "
-                + mPackageName);
-
+        final List<MediaDevice> deviceList = new ArrayList<>();
+        for (MediaRoute2Info route : getSelectedRoutes(info)) {
+            deviceList.add(
+                    new InfoMediaDevice(
+                            mContext, route, mPackageName, mPreferenceItemMap.get(route.getId())));
+        }
         return deviceList;
     }
 
+    /* package */ void adjustDeviceVolume(MediaDevice device, int volume) {
+        if (device.mRouteInfo == null) {
+            Log.w(TAG, "Unable to set volume. RouteInfo is empty");
+            return;
+        }
+        setRouteVolume(device.mRouteInfo, volume);
+    }
+
     void adjustSessionVolume(RoutingSessionInfo info, int volume) {
         if (info == null) {
             Log.w(TAG, "Unable to adjust session volume. RoutingSessionInfo is empty");
             return;
         }
 
-        mRouterManager.setSessionVolume(info, volume);
+        setSessionVolume(info, volume);
     }
 
     /**
@@ -370,15 +429,14 @@
         }
 
         final RoutingSessionInfo info = getRoutingSessionInfo();
-        if (info != null) {
-            Log.d(TAG, "adjustSessionVolume() adjust volume : " + volume + ", with : "
+        if (info == null) {
+            Log.w(TAG, "adjustSessionVolume() can't found corresponding RoutingSession with : "
                     + mPackageName);
-            mRouterManager.setSessionVolume(info, volume);
             return;
         }
 
-        Log.w(TAG, "adjustSessionVolume() can't found corresponding RoutingSession with : "
-                + mPackageName);
+        Log.d(TAG, "adjustSessionVolume() adjust volume: " + volume + ", with : " + mPackageName);
+        setSessionVolume(info, volume);
     }
 
     /**
@@ -393,13 +451,13 @@
         }
 
         final RoutingSessionInfo info = getRoutingSessionInfo();
-        if (info != null) {
-            return info.getVolumeMax();
+        if (info == null) {
+            Log.w(TAG, "getSessionVolumeMax() can't find corresponding RoutingSession with : "
+                    + mPackageName);
+            return -1;
         }
 
-        Log.w(TAG, "getSessionVolumeMax() can't found corresponding RoutingSession with : "
-                + mPackageName);
-        return -1;
+        return info.getVolumeMax();
     }
 
     /**
@@ -414,13 +472,13 @@
         }
 
         final RoutingSessionInfo info = getRoutingSessionInfo();
-        if (info != null) {
-            return info.getVolume();
+        if (info == null) {
+            Log.w(TAG, "getSessionVolume() can't find corresponding RoutingSession with : "
+                    + mPackageName);
+            return -1;
         }
 
-        Log.w(TAG, "getSessionVolume() can't found corresponding RoutingSession with : "
-                + mPackageName);
-        return -1;
+        return info.getVolume();
     }
 
     CharSequence getSessionName() {
@@ -430,22 +488,12 @@
         }
 
         final RoutingSessionInfo info = getRoutingSessionInfo();
-        if (info != null) {
-            return info.getName();
+        if (info == null) {
+            Log.w(TAG, "Unable to get session name for package: " + mPackageName);
+            return null;
         }
 
-        Log.w(TAG, "Unable to get session name for package: " + mPackageName);
-        return null;
-    }
-
-    boolean shouldDisableMediaOutput(String packageName) {
-        if (TextUtils.isEmpty(packageName)) {
-            Log.w(TAG, "shouldDisableMediaOutput() package name is null or empty!");
-            return true;
-        }
-
-        // Disable when there is no transferable route
-        return mRouterManager.getTransferableRoutes(packageName).isEmpty();
+        return info.getName();
     }
 
     @TargetApi(Build.VERSION_CODES.R)
@@ -454,21 +502,15 @@
                 || sessionInfo.getVolumeHandling() != MediaRoute2Info.PLAYBACK_VOLUME_FIXED;
     }
 
-    private synchronized void refreshDevices() {
-        mMediaDevices.clear();
-        mCurrentConnectedDevice = null;
-        if (TextUtils.isEmpty(mPackageName)) {
-            buildAllRoutes();
-        } else {
-            buildAvailableRoutes();
-        }
+    protected final synchronized void refreshDevices() {
+        rebuildDeviceList();
         dispatchDeviceListAdded();
     }
 
     // MediaRoute2Info.getType was made public on API 34, but exists since API 30.
     @SuppressWarnings("NewApi")
     private void buildAllRoutes() {
-        for (MediaRoute2Info route : mRouterManager.getAllRoutes()) {
+        for (MediaRoute2Info route : getAllRoutes()) {
             if (DEBUG) {
                 Log.d(TAG, "buildAllRoutes() route : " + route.getName() + ", volume : "
                         + route.getVolume() + ", type : " + route.getType());
@@ -479,17 +521,10 @@
         }
     }
 
-    List<RoutingSessionInfo> getActiveMediaSession() {
-        List<RoutingSessionInfo> infos = new ArrayList<>();
-        infos.add(mRouterManager.getSystemRoutingSession(null));
-        infos.addAll(mRouterManager.getRemoteSessions());
-        return infos;
-    }
-
     // MediaRoute2Info.getType was made public on API 34, but exists since API 30.
     @SuppressWarnings("NewApi")
     private synchronized void buildAvailableRoutes() {
-        for (MediaRoute2Info route : getAvailableRoutes(mPackageName)) {
+        for (MediaRoute2Info route : getAvailableRoutes()) {
             if (DEBUG) {
                 Log.d(TAG, "buildAvailableRoutes() route : " + route.getName() + ", volume : "
                         + route.getVolume() + ", type : " + route.getType());
@@ -497,18 +532,17 @@
             addMediaDevice(route);
         }
     }
-
-    private synchronized List<MediaRoute2Info> getAvailableRoutes(String packageName) {
+    private synchronized List<MediaRoute2Info> getAvailableRoutes() {
         List<MediaRoute2Info> infos = new ArrayList<>();
-        RoutingSessionInfo routingSessionInfo = getRoutingSessionInfo(packageName);
+        RoutingSessionInfo routingSessionInfo = getRoutingSessionInfo();
         List<MediaRoute2Info> selectedRouteInfos = new ArrayList<>();
         if (routingSessionInfo != null) {
-            selectedRouteInfos = mRouterManager.getSelectedRoutes(routingSessionInfo);
+            selectedRouteInfos = getSelectedRoutes(routingSessionInfo);
             infos.addAll(selectedRouteInfos);
-            infos.addAll(mRouterManager.getSelectableRoutes(routingSessionInfo));
+            infos.addAll(getSelectableRoutes(routingSessionInfo));
         }
         final List<MediaRoute2Info> transferableRoutes =
-                mRouterManager.getTransferableRoutes(packageName);
+                getTransferableRoutes(mPackageName);
         for (MediaRoute2Info transferableRoute : transferableRoutes) {
             boolean alreadyAdded = false;
             for (MediaRoute2Info mediaRoute2Info : infos) {
@@ -523,14 +557,13 @@
         }
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE
                 && !TextUtils.isEmpty(mPackageName)) {
-            RouteListingPreference routeListingPreference =
-                    mRouterManager.getRouteListingPreference(mPackageName);
+            RouteListingPreference routeListingPreference = getRouteListingPreference();
             if (routeListingPreference != null) {
                 final List<RouteListingPreference.Item> preferenceRouteListing =
                         Api34Impl.composePreferenceRouteListing(
                                 routeListingPreference);
                 infos = Api34Impl.arrangeRouteListByPreference(selectedRouteInfos,
-                                mRouterManager.getAvailableRoutes(packageName),
+                        getAvailableRoutesFromRouter(),
                                 preferenceRouteListing);
             }
             return Api34Impl.filterDuplicatedIds(infos);
@@ -557,8 +590,12 @@
             case TYPE_REMOTE_CAR:
             case TYPE_REMOTE_SMARTWATCH:
             case TYPE_REMOTE_SMARTPHONE:
-                mediaDevice = new InfoMediaDevice(mContext, mRouterManager, route,
-                        mPackageName, mPreferenceItemMap.get(route.getId()));
+                mediaDevice =
+                        new InfoMediaDevice(
+                                mContext,
+                                route,
+                                mPackageName,
+                                mPreferenceItemMap.get(route.getId()));
                 break;
             case TYPE_BUILTIN_SPEAKER:
             case TYPE_USB_DEVICE:
@@ -568,10 +605,12 @@
             case TYPE_HDMI:
             case TYPE_WIRED_HEADSET:
             case TYPE_WIRED_HEADPHONES:
-                mediaDevice = mPreferenceItemMap.containsKey(route.getId()) ? new PhoneMediaDevice(
-                        mContext, mRouterManager, route, mPackageName,
-                        mPreferenceItemMap.get(route.getId())) : new PhoneMediaDevice(mContext,
-                        mRouterManager, route, mPackageName);
+                mediaDevice =
+                        new PhoneMediaDevice(
+                                mContext,
+                                route,
+                                mPackageName,
+                                mPreferenceItemMap.getOrDefault(route.getId(), null));
                 break;
             case TYPE_HEARING_AID:
             case TYPE_BLUETOOTH_A2DP:
@@ -581,16 +620,22 @@
                 final CachedBluetoothDevice cachedDevice =
                         mBluetoothManager.getCachedDeviceManager().findDevice(device);
                 if (cachedDevice != null) {
-                    mediaDevice = mPreferenceItemMap.containsKey(route.getId())
-                            ? new BluetoothMediaDevice(mContext, cachedDevice, mRouterManager,
-                            route, mPackageName, mPreferenceItemMap.get(route.getId()))
-                            : new BluetoothMediaDevice(mContext, cachedDevice, mRouterManager,
-                                    route, mPackageName);
+                    mediaDevice =
+                            new BluetoothMediaDevice(
+                                    mContext,
+                                    cachedDevice,
+                                    route,
+                                    mPackageName,
+                                    mPreferenceItemMap.getOrDefault(route.getId(), null));
                 }
                 break;
             case TYPE_REMOTE_AUDIO_VIDEO_RECEIVER:
-                mediaDevice = new ComplexMediaDevice(mContext, mRouterManager, route,
-                        mPackageName, mPreferenceItemMap.get(route.getId()));
+                mediaDevice =
+                        new ComplexMediaDevice(
+                                mContext,
+                                route,
+                                mPackageName,
+                                mPreferenceItemMap.get(route.getId()));
             default:
                 Log.w(TAG, "addMediaDevice() unknown device type : " + deviceType);
                 break;
@@ -608,76 +653,8 @@
         }
     }
 
-    class RouterManagerCallback implements MediaRouter2Manager.Callback {
-
-        @Override
-        public void onRoutesUpdated() {
-            refreshDevices();
-        }
-
-        @Override
-        public void onPreferredFeaturesChanged(String packageName, List<String> preferredFeatures) {
-            if (TextUtils.equals(mPackageName, packageName)) {
-                refreshDevices();
-            }
-        }
-
-        @Override
-        public void onTransferred(RoutingSessionInfo oldSession, RoutingSessionInfo newSession) {
-            if (DEBUG) {
-                Log.d(TAG, "onTransferred() oldSession : " + oldSession.getName()
-                        + ", newSession : " + newSession.getName());
-            }
-            mMediaDevices.clear();
-            mCurrentConnectedDevice = null;
-            if (TextUtils.isEmpty(mPackageName)) {
-                buildAllRoutes();
-            } else {
-                buildAvailableRoutes();
-            }
-
-            final String id = mCurrentConnectedDevice != null
-                    ? mCurrentConnectedDevice.getId()
-                    : null;
-            dispatchConnectedDeviceChanged(id);
-        }
-
-        /**
-         * Ignore callback here since we'll also receive {@link #onRequestFailed} with reason code.
-         */
-        @Override
-        public void onTransferFailed(RoutingSessionInfo session, MediaRoute2Info route) {
-        }
-
-        @Override
-        public void onRequestFailed(int reason) {
-            dispatchOnRequestFailed(reason);
-        }
-
-        @Override
-        public void onSessionUpdated(RoutingSessionInfo sessionInfo) {
-            refreshDevices();
-        }
-
-        @Override
-        public void onSessionReleased(@NonNull RoutingSessionInfo session) {
-            refreshDevices();
-        }
-
-        @Override
-        public void onRouteListingPreferenceUpdated(
-                String packageName,
-                RouteListingPreference routeListingPreference) {
-            if (TextUtils.equals(mPackageName, packageName)) {
-                Api34Impl.onRouteListingPreferenceUpdated(
-                        routeListingPreference, mPreferenceItemMap);
-                refreshDevices();
-            }
-        }
-    }
-
     @RequiresApi(34)
-    private static class Api34Impl {
+    static class Api34Impl {
         @DoNotInline
         static List<RouteListingPreference.Item> composePreferenceRouteListing(
                 RouteListingPreference routeListingPreference) {
@@ -709,35 +686,66 @@
             return filteredInfos;
         }
 
+        /**
+         * Returns an ordered list of available devices based on the provided {@code
+         * routeListingPreferenceItems}.
+         *
+         * <p>The result has the following order:
+         *
+         * <ol>
+         *   <li>Selected routes.
+         *   <li>Not-selected system routes.
+         *   <li>Not-selected, non-system, available routes sorted by route listing preference.
+         * </ol>
+         *
+         * @param selectedRoutes List of currently selected routes.
+         * @param availableRoutes List of available routes that match the app's requested route
+         *     features.
+         * @param routeListingPreferenceItems Ordered list of {@link RouteListingPreference.Item} to
+         *     sort routes with.
+         */
         @DoNotInline
         static List<MediaRoute2Info> arrangeRouteListByPreference(
-                List<MediaRoute2Info> selectedRouteInfos, List<MediaRoute2Info> infolist,
-                List<RouteListingPreference.Item> preferenceRouteListing) {
-            final List<MediaRoute2Info> sortedInfoList = new ArrayList<>(selectedRouteInfos);
-            infolist.removeAll(selectedRouteInfos);
-            sortedInfoList.addAll(infolist.stream().filter(
-                    MediaRoute2Info::isSystemRoute).collect(Collectors.toList()));
-            for (RouteListingPreference.Item item : preferenceRouteListing) {
-                for (MediaRoute2Info info : infolist) {
-                    if (item.getRouteId().equals(info.getId())
-                            && !selectedRouteInfos.contains(info)
-                            && !info.isSystemRoute()) {
-                        sortedInfoList.add(info);
-                        break;
-                    }
+                List<MediaRoute2Info> selectedRoutes,
+                List<MediaRoute2Info> availableRoutes,
+                List<RouteListingPreference.Item> routeListingPreferenceItems) {
+            Set<String> sortedRouteIds = new LinkedHashSet<>();
+
+            // Add selected routes first.
+            for (MediaRoute2Info selectedRoute : selectedRoutes) {
+                sortedRouteIds.add(selectedRoute.getId());
+            }
+
+            // Add not-yet-added system routes.
+            for (MediaRoute2Info availableRoute : availableRoutes) {
+                if (availableRoute.isSystemRoute()) {
+                    sortedRouteIds.add(availableRoute.getId());
                 }
             }
-            return sortedInfoList;
+
+            // Create a mapping from id to route to avoid a quadratic search.
+            Map<String, MediaRoute2Info> idToRouteMap =
+                    Stream.concat(selectedRoutes.stream(), availableRoutes.stream())
+                            .collect(
+                                    Collectors.toMap(
+                                            MediaRoute2Info::getId,
+                                            Function.identity(),
+                                            (route1, route2) -> route1));
+
+            // Add not-selected routes that match RLP items. All system routes have already been
+            // added at this point.
+            for (RouteListingPreference.Item item : routeListingPreferenceItems) {
+                MediaRoute2Info route = idToRouteMap.get(item.getRouteId());
+                if (route != null) {
+                    sortedRouteIds.add(route.getId());
+                }
+            }
+
+            return sortedRouteIds.stream().map(idToRouteMap::get).collect(Collectors.toList());
         }
 
         @DoNotInline
-        static boolean preferRouteListingOrdering(MediaRouter2Manager mediaRouter2Manager,
-                String packageName) {
-            if (TextUtils.isEmpty(packageName)) {
-                return false;
-            }
-            RouteListingPreference routeListingPreference =
-                    mediaRouter2Manager.getRouteListingPreference(packageName);
+        static boolean preferRouteListingOrdering(RouteListingPreference routeListingPreference) {
             return routeListingPreference != null
                     && !routeListingPreference.getUseSystemOrdering();
         }
@@ -745,12 +753,7 @@
         @DoNotInline
         @Nullable
         static ComponentName getLinkedItemComponentName(
-                MediaRouter2Manager mediaRouter2Manager, String packageName) {
-            if (TextUtils.isEmpty(packageName)) {
-                return null;
-            }
-            RouteListingPreference routeListingPreference =
-                    mediaRouter2Manager.getRouteListingPreference(packageName);
+                RouteListingPreference routeListingPreference) {
             return routeListingPreference == null ? null
                     : routeListingPreference.getLinkedItemComponentName();
         }
@@ -761,9 +764,8 @@
                 Map<String, RouteListingPreference.Item> preferenceItemMap) {
             preferenceItemMap.clear();
             if (routeListingPreference != null) {
-                routeListingPreference.getItems().forEach((item) -> {
-                    preferenceItemMap.put(item.getRouteId(), item);
-                });
+                routeListingPreference.getItems().forEach((item) ->
+                        preferenceItemMap.put(item.getRouteId(), item));
             }
         }
     }
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
index c45e8e4..fd5ca84 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
@@ -137,7 +137,8 @@
         }
 
         mInfoMediaManager =
-                new InfoMediaManager(context, packageName, notification, mLocalBluetoothManager);
+                new ManagerInfoMediaManager(
+                        context, packageName, notification, mLocalBluetoothManager);
     }
 
     /**
@@ -187,11 +188,7 @@
         }
 
         device.setState(MediaDeviceState.STATE_CONNECTING);
-        if (TextUtils.isEmpty(mPackageName)) {
-            mInfoMediaManager.connectDeviceWithoutPackageName(device);
-        } else {
-            device.connect();
-        }
+        mInfoMediaManager.connectToDevice(device);
         return true;
     }
 
@@ -346,7 +343,7 @@
      * @return list of MediaDevice
      */
     public List<MediaDevice> getSelectableMediaDevice() {
-        return mInfoMediaManager.getSelectableMediaDevice();
+        return mInfoMediaManager.getSelectableMediaDevices();
     }
 
     /**
@@ -355,7 +352,7 @@
      * @return list of MediaDevice
      */
     public List<MediaDevice> getDeselectableMediaDevice() {
-        return mInfoMediaManager.getDeselectableMediaDevice();
+        return mInfoMediaManager.getDeselectableMediaDevices();
     }
 
     /**
@@ -371,7 +368,17 @@
      * @return list of MediaDevice
      */
     public List<MediaDevice> getSelectedMediaDevice() {
-        return mInfoMediaManager.getSelectedMediaDevice();
+        return mInfoMediaManager.getSelectedMediaDevices();
+    }
+
+    /**
+     * Requests a volume change for a specific media device.
+     *
+     * This operation is different from {@link #adjustSessionVolume(String, int)}, which changes the
+     * volume of the overall session.
+     */
+    public void adjustDeviceVolume(MediaDevice device, int volume) {
+        mInfoMediaManager.adjustDeviceVolume(device, volume);
     }
 
     /**
@@ -433,7 +440,7 @@
      * @return current active session list{@link android.media.RoutingSessionInfo}
      */
     public List<RoutingSessionInfo> getActiveMediaSession() {
-        return mInfoMediaManager.getActiveMediaSession();
+        return mInfoMediaManager.getActiveRoutingSessions();
     }
 
     /**
@@ -446,13 +453,6 @@
     }
 
     /**
-     * Returns {@code true} if needed to disable media output, otherwise returns {@code false}.
-     */
-    public boolean shouldDisableMediaOutput(String packageName) {
-        return mInfoMediaManager.shouldDisableMediaOutput(packageName);
-    }
-
-    /**
      * Returns {@code true} if needed to enable volume seekbar, otherwise returns {@code false}.
      */
     public boolean shouldEnableVolumeSeekBar(RoutingSessionInfo sessionInfo) {
@@ -558,9 +558,7 @@
                 final CachedBluetoothDevice cachedDevice =
                         cachedDeviceManager.findDevice(device);
                 if (isBondedMediaDevice(cachedDevice) && isMutingExpectedDevice(cachedDevice)) {
-                    return new BluetoothMediaDevice(mContext,
-                            cachedDevice,
-                            null, null, mPackageName);
+                    return new BluetoothMediaDevice(mContext, cachedDevice, null, mPackageName);
                 }
             }
             return null;
@@ -606,9 +604,8 @@
             unRegisterDeviceAttributeChangeCallback();
             mDisconnectedMediaDevices.clear();
             for (CachedBluetoothDevice cachedDevice : cachedBluetoothDeviceList) {
-                final MediaDevice mediaDevice = new BluetoothMediaDevice(mContext,
-                        cachedDevice,
-                        null, null, mPackageName);
+                final MediaDevice mediaDevice =
+                        new BluetoothMediaDevice(mContext, cachedDevice, null, mPackageName);
                 if (!mMediaDevices.contains(mediaDevice)) {
                     cachedDevice.registerCallback(mDeviceAttributeChangeCallback);
                     mDisconnectedMediaDevices.add(mediaDevice);
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java
new file mode 100644
index 0000000..b7ac1dce
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java
@@ -0,0 +1,240 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.media;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.Notification;
+import android.content.Context;
+import android.media.MediaRoute2Info;
+import android.media.MediaRouter2Manager;
+import android.media.RouteListingPreference;
+import android.media.RoutingSessionInfo;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+
+/**
+ * Template implementation of {@link InfoMediaManager} using {@link MediaRouter2Manager}.
+ */
+public class ManagerInfoMediaManager extends InfoMediaManager {
+
+    private static final String TAG = "ManagerInfoMediaManager";
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+    @VisibleForTesting
+    /* package */ final RouterManagerCallback mMediaRouterCallback = new RouterManagerCallback();
+    @VisibleForTesting
+    /* package */ MediaRouter2Manager mRouterManager;
+    boolean mIsScanning = false;
+
+    private final Executor mExecutor = Executors.newSingleThreadExecutor();
+
+    public ManagerInfoMediaManager(
+            Context context,
+            String packageName,
+            Notification notification,
+            LocalBluetoothManager localBluetoothManager) {
+        super(context, packageName, notification, localBluetoothManager);
+
+        mRouterManager = MediaRouter2Manager.getInstance(context);
+    }
+
+    @Override
+    protected void startScanOnRouter() {
+        if (!mIsScanning) {
+            mRouterManager.registerCallback(mExecutor, mMediaRouterCallback);
+            mRouterManager.registerScanRequest();
+            mIsScanning = true;
+        }
+    }
+
+    @Override
+    public void stopScan() {
+        if (mIsScanning) {
+            mRouterManager.unregisterCallback(mMediaRouterCallback);
+            mRouterManager.unregisterScanRequest();
+            mIsScanning = false;
+        }
+    }
+
+    @Override
+    protected void transferToRoute(@NonNull MediaRoute2Info route) {
+        mRouterManager.transfer(mPackageName, route);
+    }
+
+    @Override
+    protected boolean connectDeviceWithoutPackageName(@NonNull MediaDevice device) {
+        final RoutingSessionInfo info = mRouterManager.getSystemRoutingSession(null);
+        if (info != null) {
+            mRouterManager.transfer(info, device.mRouteInfo);
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    protected void selectRoute(@NonNull MediaRoute2Info route, @NonNull RoutingSessionInfo info) {
+        mRouterManager.selectRoute(info, route);
+    }
+
+    @Override
+    protected void deselectRoute(@NonNull MediaRoute2Info route, @NonNull RoutingSessionInfo info) {
+        mRouterManager.deselectRoute(info, route);
+    }
+
+    @Override
+    protected void releaseSession(@NonNull RoutingSessionInfo sessionInfo) {
+        mRouterManager.releaseSession(sessionInfo);
+    }
+
+    @Override
+    @NonNull
+    protected List<MediaRoute2Info> getSelectableRoutes(@NonNull RoutingSessionInfo info) {
+        return mRouterManager.getSelectableRoutes(info);
+    }
+
+    @Override
+    @NonNull
+    protected List<MediaRoute2Info> getDeselectableRoutes(@NonNull RoutingSessionInfo info) {
+        return mRouterManager.getDeselectableRoutes(info);
+    }
+
+    @Override
+    @NonNull
+    protected List<MediaRoute2Info> getSelectedRoutes(@NonNull RoutingSessionInfo info) {
+        return mRouterManager.getSelectedRoutes(info);
+    }
+
+    @Override
+    protected void setSessionVolume(@NonNull RoutingSessionInfo info, int volume) {
+        mRouterManager.setSessionVolume(info, volume);
+    }
+
+    @Override
+    protected void setRouteVolume(@NonNull MediaRoute2Info route, int volume) {
+        mRouterManager.setRouteVolume(route, volume);
+    }
+
+    @Override
+    @Nullable
+    protected RouteListingPreference getRouteListingPreference() {
+        return mRouterManager.getRouteListingPreference(mPackageName);
+    }
+
+    @Override
+    @NonNull
+    protected List<RoutingSessionInfo> getRoutingSessionsForPackage() {
+        return mRouterManager.getRoutingSessions(mPackageName);
+    }
+
+    @Override
+    @NonNull
+    protected List<RoutingSessionInfo> getActiveRoutingSessions() {
+        List<RoutingSessionInfo> infos = new ArrayList<>();
+        infos.add(mRouterManager.getSystemRoutingSession(null));
+        infos.addAll(mRouterManager.getRemoteSessions());
+        return infos;
+    }
+
+    @Override
+    @NonNull
+    protected List<MediaRoute2Info> getAllRoutes() {
+        return mRouterManager.getAllRoutes();
+    }
+
+    @Override
+    @NonNull
+    protected List<MediaRoute2Info> getAvailableRoutesFromRouter() {
+        return mRouterManager.getAvailableRoutes(mPackageName);
+    }
+
+    @Override
+    @NonNull
+    protected List<MediaRoute2Info> getTransferableRoutes(@NonNull String packageName) {
+        return mRouterManager.getTransferableRoutes(packageName);
+    }
+
+    @VisibleForTesting
+    /* package */ final class RouterManagerCallback implements MediaRouter2Manager.Callback {
+
+        @Override
+        public void onRoutesUpdated() {
+            refreshDevices();
+        }
+
+        @Override
+        public void onPreferredFeaturesChanged(String packageName, List<String> preferredFeatures) {
+            if (TextUtils.equals(mPackageName, packageName)) {
+                refreshDevices();
+            }
+        }
+
+        @Override
+        public void onTransferred(RoutingSessionInfo oldSession, RoutingSessionInfo newSession) {
+            if (DEBUG) {
+                Log.d(
+                        TAG,
+                        "onTransferred() oldSession : "
+                                + oldSession.getName()
+                                + ", newSession : "
+                                + newSession.getName());
+            }
+            rebuildDeviceList();
+            notifyCurrentConnectedDeviceChanged();
+        }
+
+        /**
+         * Ignore callback here since we'll also receive{@link
+         * MediaRouter2Manager.Callback#onRequestFailed onRequestFailed} with reason code.
+         */
+        @Override
+        public void onTransferFailed(RoutingSessionInfo session, MediaRoute2Info route) {}
+
+        @Override
+        public void onRequestFailed(int reason) {
+            dispatchOnRequestFailed(reason);
+        }
+
+        @Override
+        public void onSessionUpdated(RoutingSessionInfo sessionInfo) {
+            refreshDevices();
+        }
+
+        @Override
+        public void onSessionReleased(@NonNull RoutingSessionInfo session) {
+            refreshDevices();
+        }
+
+        @Override
+        public void onRouteListingPreferenceUpdated(
+                String packageName, RouteListingPreference routeListingPreference) {
+            if (!TextUtils.equals(mPackageName, packageName)) {
+                return;
+            }
+            notifyRouteListingPreferenceUpdated(routeListingPreference);
+            refreshDevices();
+        }
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
index a9d15f3..9234d37 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
@@ -50,7 +50,6 @@
 import android.content.Context;
 import android.graphics.drawable.Drawable;
 import android.media.MediaRoute2Info;
-import android.media.MediaRouter2Manager;
 import android.media.NearbyDevice;
 import android.media.RouteListingPreference;
 import android.os.Build;
@@ -116,15 +115,16 @@
 
     protected final Context mContext;
     protected final MediaRoute2Info mRouteInfo;
-    protected final MediaRouter2Manager mRouterManager;
     protected final RouteListingPreference.Item mItem;
     protected final String mPackageName;
 
-    MediaDevice(Context context, MediaRouter2Manager routerManager, MediaRoute2Info info,
-            String packageName, RouteListingPreference.Item item) {
+    MediaDevice(
+            Context context,
+            MediaRoute2Info info,
+            String packageName,
+            RouteListingPreference.Item item) {
         mContext = context;
         mRouteInfo = info;
-        mRouterManager = routerManager;
         mPackageName = packageName;
         mItem = item;
         setType(info);
@@ -306,20 +306,6 @@
     public abstract boolean isConnected();
 
     /**
-     * Request to set volume.
-     *
-     * @param volume is the new value.
-     */
-
-    public void requestSetVolume(int volume) {
-        if (mRouteInfo == null) {
-            Log.w(TAG, "Unable to set volume. RouteInfo is empty");
-            return;
-        }
-        mRouterManager.setRouteVolume(mRouteInfo, volume);
-    }
-
-    /**
      * Get max volume from MediaDevice.
      *
      * @return max volume.
@@ -393,21 +379,6 @@
     }
 
     /**
-     * Transfer MediaDevice for media
-     *
-     * @return result of transfer media
-     */
-    public boolean connect() {
-        if (mRouteInfo == null) {
-            Log.w(TAG, "Unable to connect. RouteInfo is empty");
-            return false;
-        }
-        setConnectedRecord();
-        mRouterManager.transfer(mPackageName, mRouteInfo);
-        return true;
-    }
-
-    /**
      * Stop transfer MediaDevice
      */
     public void disconnect() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
index accd88c..41afc7b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
@@ -29,7 +29,6 @@
 import android.content.Context;
 import android.graphics.drawable.Drawable;
 import android.media.MediaRoute2Info;
-import android.media.MediaRouter2Manager;
 import android.media.RouteListingPreference;
 
 import androidx.annotation.VisibleForTesting;
@@ -52,14 +51,16 @@
 
     private final DeviceIconUtil mDeviceIconUtil;
 
-    PhoneMediaDevice(Context context, MediaRouter2Manager routerManager, MediaRoute2Info info,
-            String packageName) {
-        this(context, routerManager, info, packageName, null);
+    PhoneMediaDevice(Context context, MediaRoute2Info info, String packageName) {
+        this(context, info, packageName, null);
     }
 
-    PhoneMediaDevice(Context context, MediaRouter2Manager routerManager, MediaRoute2Info info,
-            String packageName, RouteListingPreference.Item item) {
-        super(context, routerManager, info, packageName, item);
+    PhoneMediaDevice(
+            Context context,
+            MediaRoute2Info info,
+            String packageName,
+            RouteListingPreference.Item item) {
+        super(context, info, packageName, item);
         mDeviceIconUtil = new DeviceIconUtil();
         initDeviceRecord();
     }
diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/SubscriptionInfoEntity.java b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/SubscriptionInfoEntity.java
index c40388f..361a2461 100644
--- a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/SubscriptionInfoEntity.java
+++ b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/SubscriptionInfoEntity.java
@@ -16,27 +16,23 @@
 
 package com.android.settingslib.mobile.dataservice;
 
-import static androidx.room.ForeignKey.CASCADE;
-
 import android.text.TextUtils;
 
-import java.util.Objects;
-
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.room.ColumnInfo;
 import androidx.room.Entity;
-import androidx.room.ForeignKey;
-import androidx.room.Index;
 import androidx.room.PrimaryKey;
 
+import java.util.Objects;
+
 @Entity(tableName = DataServiceUtils.SubscriptionInfoData.TABLE_NAME)
 public class SubscriptionInfoEntity {
     public SubscriptionInfoEntity(@NonNull String subId, int simSlotIndex, int carrierId,
             String displayName, String carrierName, int dataRoaming, String mcc, String mnc,
             String countryIso, boolean isEmbedded, int cardId, int portIndex,
             boolean isOpportunistic, @Nullable String groupUUID, int subscriptionType,
-            String uniqueName, boolean isSubscriptionVisible, String formattedPhoneNumber,
+            String uniqueName, boolean isSubscriptionVisible, @Nullable String formattedPhoneNumber,
             boolean isFirstRemovableSubscription, boolean isDefaultSubscriptionSelection,
             boolean isValidSubscription, boolean isUsableSubscription,
             boolean isActiveSubscriptionId, boolean isAvailableSubscription,
@@ -123,6 +119,7 @@
     public boolean isSubscriptionVisible;
 
     @ColumnInfo(name = DataServiceUtils.SubscriptionInfoData.COLUMN_FORMATTED_PHONE_NUMBER)
+    @Nullable
     public String formattedPhoneNumber;
 
     @ColumnInfo(name = DataServiceUtils.SubscriptionInfoData.COLUMN_IS_FIRST_REMOVABLE_SUBSCRIPTION)
@@ -165,33 +162,32 @@
 
     @Override
     public int hashCode() {
-        int result = 17;
-        result = 31 * result + subId.hashCode();
-        result = 31 * result + simSlotIndex;
-        result = 31 * result + carrierId;
-        result = 31 * result + displayName.hashCode();
-        result = 31 * result + carrierName.hashCode();
-        result = 31 * result + dataRoaming;
-        result = 31 * result + mcc.hashCode();
-        result = 31 * result + mnc.hashCode();
-        result = 31 * result + countryIso.hashCode();
-        result = 31 * result + Boolean.hashCode(isEmbedded);
-        result = 31 * result + cardId;
-        result = 31 * result + portIndex;
-        result = 31 * result + Boolean.hashCode(isOpportunistic);
-        result = 31 * result + groupUUID.hashCode();
-        result = 31 * result + subscriptionType;
-        result = 31 * result + uniqueName.hashCode();
-        result = 31 * result + Boolean.hashCode(isSubscriptionVisible);
-        result = 31 * result + formattedPhoneNumber.hashCode();
-        result = 31 * result + Boolean.hashCode(isFirstRemovableSubscription);
-        result = 31 * result + Boolean.hashCode(isDefaultSubscriptionSelection);
-        result = 31 * result + Boolean.hashCode(isValidSubscription);
-        result = 31 * result + Boolean.hashCode(isUsableSubscription);
-        result = 31 * result + Boolean.hashCode(isActiveSubscriptionId);
-        result = 31 * result + Boolean.hashCode(isAvailableSubscription);
-        result = 31 * result + Boolean.hashCode(isActiveDataSubscriptionId);
-        return result;
+        return Objects.hash(
+                subId,
+                simSlotIndex,
+                carrierId,
+                displayName,
+                carrierName,
+                dataRoaming,
+                mcc,
+                mnc,
+                countryIso,
+                isEmbedded,
+                cardId,
+                portIndex,
+                isOpportunistic,
+                groupUUID,
+                subscriptionType,
+                uniqueName,
+                isSubscriptionVisible,
+                formattedPhoneNumber,
+                isFirstRemovableSubscription,
+                isDefaultSubscriptionSelection,
+                isValidSubscription,
+                isUsableSubscription,
+                isActiveSubscriptionId,
+                isAvailableSubscription,
+                isActiveDataSubscriptionId);
     }
 
     @Override
diff --git a/packages/SettingsLib/src/com/android/settingslib/net/ChartDataLoader.java b/packages/SettingsLib/src/com/android/settingslib/net/ChartDataLoader.java
deleted file mode 100644
index 573922f..0000000
--- a/packages/SettingsLib/src/com/android/settingslib/net/ChartDataLoader.java
+++ /dev/null
@@ -1,154 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.net;
-
-import static android.net.NetworkStats.SET_DEFAULT;
-import static android.net.NetworkStats.SET_FOREGROUND;
-import static android.net.NetworkStats.TAG_NONE;
-
-import android.annotation.NonNull;
-import android.app.usage.NetworkStats;
-import android.app.usage.NetworkStatsManager;
-import android.content.AsyncTaskLoader;
-import android.content.Context;
-import android.net.NetworkTemplate;
-import android.os.Bundle;
-import android.os.RemoteException;
-
-import com.android.settingslib.AppItem;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Framework loader is deprecated, use the compat version instead.
- *
- * @deprecated
- */
-@Deprecated
-public class ChartDataLoader extends AsyncTaskLoader<ChartData> {
-    private static final String KEY_TEMPLATE = "template";
-    private static final String KEY_APP = "app";
-
-    private final NetworkStatsManager mNetworkStatsManager;
-    private final Bundle mArgs;
-
-    public static Bundle buildArgs(NetworkTemplate template, AppItem app) {
-        final Bundle args = new Bundle();
-        args.putParcelable(KEY_TEMPLATE, template);
-        args.putParcelable(KEY_APP, app);
-        return args;
-    }
-
-    public ChartDataLoader(Context context, NetworkStatsManager statsManager, Bundle args) {
-        super(context);
-        mNetworkStatsManager = statsManager;
-        mArgs = args;
-    }
-
-    @Override
-    protected void onStartLoading() {
-        super.onStartLoading();
-        forceLoad();
-    }
-
-    @Override
-    public ChartData loadInBackground() {
-        final NetworkTemplate template = mArgs.getParcelable(KEY_TEMPLATE);
-        final AppItem app = mArgs.getParcelable(KEY_APP);
-
-        try {
-            return loadInBackground(template, app);
-        } catch (RemoteException e) {
-            // since we can't do much without history, and we don't want to
-            // leave with half-baked UI, we bail hard.
-            throw new RuntimeException("problem reading network stats", e);
-        }
-    }
-
-    @NonNull
-    private List<NetworkStats.Bucket> convertToBuckets(@NonNull NetworkStats stats) {
-        final List<NetworkStats.Bucket> ret = new ArrayList<>();
-        while (stats.hasNextBucket()) {
-            final NetworkStats.Bucket bucket = new NetworkStats.Bucket();
-            stats.getNextBucket(bucket);
-            ret.add(bucket);
-        }
-        return ret;
-    }
-
-    private ChartData loadInBackground(NetworkTemplate template, AppItem app)
-            throws RemoteException {
-        final ChartData data = new ChartData();
-        data.network = convertToBuckets(mNetworkStatsManager.queryDetailsForDevice(
-                template, Long.MIN_VALUE, Long.MAX_VALUE));
-
-        if (app != null) {
-            // load stats for current uid and template
-            final int size = app.uids.size();
-            for (int i = 0; i < size; i++) {
-                final int uid = app.uids.keyAt(i);
-                data.detailDefault = collectHistoryForUid(
-                        template, uid, SET_DEFAULT, data.detailDefault);
-                data.detailForeground = collectHistoryForUid(
-                        template, uid, SET_FOREGROUND, data.detailForeground);
-            }
-
-            if (size > 0) {
-                data.detail = new ArrayList<>();
-                data.detail.addAll(data.detailDefault);
-                data.detail.addAll(data.detailForeground);
-            } else {
-                data.detailDefault = new ArrayList<>();
-                data.detailForeground = new ArrayList<>();
-                data.detail = new ArrayList<>();
-            }
-        }
-
-        return data;
-    }
-
-    @Override
-    protected void onStopLoading() {
-        super.onStopLoading();
-        cancelLoad();
-    }
-
-    @Override
-    protected void onReset() {
-        super.onReset();
-        cancelLoad();
-    }
-
-    /**
-     * Collect {@link List<NetworkStats.Bucket>} for the requested UID, combining with
-     * an existing {@link List<NetworkStats.Bucket>} if provided.
-     */
-    private List<NetworkStats.Bucket> collectHistoryForUid(
-            NetworkTemplate template, int uid, int set, List<NetworkStats.Bucket> existing) {
-        final List<NetworkStats.Bucket> history = convertToBuckets(
-                mNetworkStatsManager.queryDetailsForUidTagState(template,
-                        Long.MIN_VALUE, Long.MAX_VALUE, uid, TAG_NONE, set));
-
-        if (existing != null) {
-            existing.addAll(history);
-            return existing;
-        } else {
-            return history;
-        }
-    }
-}
diff --git a/packages/SettingsLib/src/com/android/settingslib/net/DataUsageController.java b/packages/SettingsLib/src/com/android/settingslib/net/DataUsageController.java
index 30c6645..d088c3b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/net/DataUsageController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/net/DataUsageController.java
@@ -23,7 +23,6 @@
 import android.app.usage.NetworkStats.Bucket;
 import android.app.usage.NetworkStatsManager;
 import android.content.Context;
-import android.net.ConnectivityManager;
 import android.net.NetworkPolicy;
 import android.net.NetworkPolicyManager;
 import android.net.NetworkTemplate;
@@ -95,17 +94,6 @@
         return null;
     }
 
-    public DataUsageInfo getDataUsageInfo() {
-        NetworkTemplate template = DataUsageUtils.getMobileTemplate(mContext, mSubscriptionId);
-
-        return getDataUsageInfo(template);
-    }
-
-    public DataUsageInfo getWifiDataUsageInfo() {
-        NetworkTemplate template = new NetworkTemplate.Builder(NetworkTemplate.MATCH_WIFI).build();
-        return getDataUsageInfo(template);
-    }
-
     public DataUsageInfo getDataUsageInfo(NetworkTemplate template) {
         final NetworkPolicy policy = findNetworkPolicy(template);
         final long now = System.currentTimeMillis();
@@ -137,7 +125,7 @@
         } else {
             usage.warningLevel = getDefaultWarningLevel();
         }
-        if (usage != null && mNetworkController != null) {
+        if (mNetworkController != null) {
             usage.carrier = mNetworkController.getMobileDataNetworkName();
         }
         return usage;
@@ -170,9 +158,7 @@
         if (mPolicyManager == null || template == null) return null;
         final NetworkPolicy[] policies = mPolicyManager.getNetworkPolicies();
         if (policies == null) return null;
-        final int N = policies.length;
-        for (int i = 0; i < N; i++) {
-            final NetworkPolicy policy = policies[i];
+        for (final NetworkPolicy policy : policies) {
             if (policy != null && template.equals(policy.template)) {
                 return policy;
             }
@@ -180,17 +166,6 @@
         return null;
     }
 
-    private static String statsBucketToString(Bucket bucket) {
-        return bucket == null ? null : new StringBuilder("Entry[")
-            .append("bucketDuration=").append(bucket.getEndTimeStamp() - bucket.getStartTimeStamp())
-            .append(",bucketStart=").append(bucket.getStartTimeStamp())
-            .append(",rxBytes=").append(bucket.getRxBytes())
-            .append(",rxPackets=").append(bucket.getRxPackets())
-            .append(",txBytes=").append(bucket.getTxBytes())
-            .append(",txPackets=").append(bucket.getTxPackets())
-            .append(']').toString();
-    }
-
     @VisibleForTesting
     public TelephonyManager getTelephonyManager() {
         int subscriptionId = mSubscriptionId;
@@ -208,8 +183,8 @@
             }
         }
 
-        return mContext.getSystemService(
-                TelephonyManager.class).createForSubscriptionId(subscriptionId);
+        return mContext.getSystemService(TelephonyManager.class)
+                .createForSubscriptionId(subscriptionId);
     }
 
     public void setMobileDataEnabled(boolean enabled) {
@@ -230,28 +205,6 @@
         return getTelephonyManager().isDataEnabled();
     }
 
-    static int getNetworkType(NetworkTemplate networkTemplate) {
-        if (networkTemplate == null) {
-            return ConnectivityManager.TYPE_NONE;
-        }
-        final int matchRule = networkTemplate.getMatchRule();
-        switch (matchRule) {
-            case NetworkTemplate.MATCH_MOBILE:
-                return ConnectivityManager.TYPE_MOBILE;
-            case NetworkTemplate.MATCH_WIFI:
-                return  ConnectivityManager.TYPE_WIFI;
-            case NetworkTemplate.MATCH_ETHERNET:
-                return  ConnectivityManager.TYPE_ETHERNET;
-            default:
-                return ConnectivityManager.TYPE_MOBILE;
-        }
-    }
-
-    private String getActiveSubscriberId() {
-        final String actualSubscriberId = getTelephonyManager().getSubscriberId();
-        return actualSubscriberId;
-    }
-
     private String formatDateRange(long start, long end) {
         final int flags = FORMAT_SHOW_DATE | FORMAT_ABBREV_MONTH;
         synchronized (PERIOD_BUILDER) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/net/DataUsageUtils.java b/packages/SettingsLib/src/com/android/settingslib/net/DataUsageUtils.java
deleted file mode 100644
index 386a47a..0000000
--- a/packages/SettingsLib/src/com/android/settingslib/net/DataUsageUtils.java
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.net;
-
-import android.content.Context;
-import android.net.NetworkStats;
-import android.net.NetworkTemplate;
-import android.telephony.SubscriptionInfo;
-import android.telephony.SubscriptionManager;
-import android.telephony.TelephonyManager;
-import android.util.Log;
-
-import com.android.internal.util.ArrayUtils;
-
-import java.util.List;
-import java.util.Set;
-
-/**
- * Utils class for data usage
- */
-public class DataUsageUtils {
-    private static final String TAG = "DataUsageUtils";
-
-    /**
-     * Return mobile NetworkTemplate based on {@code subId}
-     */
-    public static NetworkTemplate getMobileTemplate(Context context, int subId) {
-        final TelephonyManager telephonyManager = context.getSystemService(TelephonyManager.class);
-        final int mobileDefaultSubId = telephonyManager.getSubscriptionId();
-
-        final SubscriptionManager subscriptionManager =
-                context.getSystemService(SubscriptionManager.class);
-        final List<SubscriptionInfo> subInfoList =
-                subscriptionManager.getAvailableSubscriptionInfoList();
-        if (subInfoList == null) {
-            Log.i(TAG, "Subscription is not inited: " + subId);
-            return getMobileTemplateForSubId(telephonyManager, mobileDefaultSubId);
-        }
-
-        for (SubscriptionInfo subInfo : subInfoList) {
-            if ((subInfo != null) && (subInfo.getSubscriptionId() == subId)) {
-                return getNormalizedMobileTemplate(telephonyManager, subId);
-            }
-        }
-        Log.i(TAG, "Subscription is not active: " + subId);
-        return getMobileTemplateForSubId(telephonyManager, mobileDefaultSubId);
-    }
-
-    private static NetworkTemplate getNormalizedMobileTemplate(
-            TelephonyManager telephonyManager, int subId) {
-        final NetworkTemplate mobileTemplate = getMobileTemplateForSubId(telephonyManager, subId);
-        final Set<String> mergedSubscriberIds = Set.of(telephonyManager
-                .createForSubscriptionId(subId).getMergedImsisFromGroup());
-        if (ArrayUtils.isEmpty(mergedSubscriberIds)) {
-            Log.i(TAG, "mergedSubscriberIds is null.");
-            return mobileTemplate;
-        }
-
-        return normalizeMobileTemplate(mobileTemplate, mergedSubscriberIds);
-    }
-
-    private static NetworkTemplate normalizeMobileTemplate(
-            NetworkTemplate template, Set<String> mergedSet) {
-        if (template.getSubscriberIds().isEmpty()) return template;
-        // The input template should have at most 1 subscriberId.
-        final String subscriberId = template.getSubscriberIds().iterator().next();
-
-        if (mergedSet.contains(subscriberId)) {
-            // Requested template subscriber is part of the merge group; return
-            // a template that matches all merged subscribers.
-            return new NetworkTemplate.Builder(template.getMatchRule())
-                    .setSubscriberIds(mergedSet)
-                    .setWifiNetworkKeys(template.getWifiNetworkKeys())
-                    .setMeteredness(NetworkStats.METERED_YES).build();
-        }
-
-        return template;
-    }
-
-    private static NetworkTemplate getMobileTemplateForSubId(
-            TelephonyManager telephonyManager, int subId) {
-        // Create template that matches any mobile network when the subscriberId is null.
-        String subscriberId = telephonyManager.getSubscriberId(subId);
-        return subscriberId != null
-                ? new NetworkTemplate.Builder(NetworkTemplate.MATCH_CARRIER)
-                .setSubscriberIds(Set.of(subscriberId))
-                .setMeteredness(NetworkStats.METERED_YES)
-                .build()
-                : new NetworkTemplate.Builder(NetworkTemplate.MATCH_MOBILE)
-                        .setMeteredness(NetworkStats.METERED_YES)
-                        .build();
-    }
-}
diff --git a/packages/SettingsLib/src/com/android/settingslib/qrcode/OWNERS b/packages/SettingsLib/src/com/android/settingslib/qrcode/OWNERS
index 61c73fb..372eb81 100644
--- a/packages/SettingsLib/src/com/android/settingslib/qrcode/OWNERS
+++ b/packages/SettingsLib/src/com/android/settingslib/qrcode/OWNERS
@@ -1,7 +1,6 @@
 # Default reviewers for this and subdirectories.
 bonianchen@google.com
 changbetty@google.com
-goldmanj@google.com
 wengsu@google.com
 zoeychen@google.com
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/qrcode/QrCodeGenerator.java b/packages/SettingsLib/src/com/android/settingslib/qrcode/QrCodeGenerator.java
index bc5824a..5c48c54 100644
--- a/packages/SettingsLib/src/com/android/settingslib/qrcode/QrCodeGenerator.java
+++ b/packages/SettingsLib/src/com/android/settingslib/qrcode/QrCodeGenerator.java
@@ -40,6 +40,19 @@
      */
     public static Bitmap encodeQrCode(String contents, int size)
             throws WriterException, IllegalArgumentException {
+        return encodeQrCode(contents, size, /*invert=*/false);
+    }
+
+    /**
+     * Generates a barcode image with {@code contents}.
+     *
+     * @param contents The contents to encode in the barcode
+     * @param size     The preferred image size in pixels
+     * @param invert   Whether to invert the black/white pixels (e.g. for dark mode)
+     * @return Barcode bitmap
+     */
+    public static Bitmap encodeQrCode(String contents, int size, boolean invert)
+            throws WriterException, IllegalArgumentException {
         final Map<EncodeHintType, Object> hints = new HashMap<>();
         if (!isIso88591(contents)) {
             hints.put(EncodeHintType.CHARACTER_SET, StandardCharsets.UTF_8.name());
@@ -48,9 +61,11 @@
         final BitMatrix qrBits = new MultiFormatWriter().encode(contents, BarcodeFormat.QR_CODE,
                 size, size, hints);
         final Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.RGB_565);
+        int setColor = invert ? Color.WHITE : Color.BLACK;
+        int unsetColor = invert ? Color.BLACK : Color.WHITE;
         for (int x = 0; x < size; x++) {
             for (int y = 0; y < size; y++) {
-                bitmap.setPixel(x, y, qrBits.get(x, y) ? Color.BLACK : Color.WHITE);
+                bitmap.setPixel(x, y, qrBits.get(x, y) ? setColor : unsetColor);
             }
         }
         return bitmap;
diff --git a/packages/SettingsLib/src/com/android/settingslib/users/CreateUserDialogController.java b/packages/SettingsLib/src/com/android/settingslib/users/CreateUserDialogController.java
index e61c8f5..997d1f4 100644
--- a/packages/SettingsLib/src/com/android/settingslib/users/CreateUserDialogController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/users/CreateUserDialogController.java
@@ -59,6 +59,7 @@
     private static final String KEY_IS_ADMIN = "admin_status";
     private static final String KEY_ADD_USER_LONG_MESSAGE_DISPLAYED =
             "key_add_user_long_message_displayed";
+    public static final int MESSAGE_PADDING = 10;
 
     @Retention(RetentionPolicy.SOURCE)
     @IntDef({EXIT_DIALOG, INITIAL_DIALOG, GRANT_ADMIN_DIALOG,
@@ -191,6 +192,7 @@
             cancelCallback.run();
             clear();
         });
+        mCustomDialogHelper.setMessagePadding(MESSAGE_PADDING);
         mUserCreationDialog.setCanceledOnTouchOutside(true);
         return mUserCreationDialog;
     }
@@ -212,7 +214,6 @@
             }
             updateLayout();
         });
-        return;
     }
 
     private void updateLayout() {
@@ -234,7 +235,6 @@
                 }
                 Drawable icon = mActivity.getDrawable(R.drawable.ic_person_add);
                 mCustomDialogHelper.setVisibility(mCustomDialogHelper.ICON, true)
-                        .setVisibility(mCustomDialogHelper.TITLE, true)
                         .setVisibility(mCustomDialogHelper.MESSAGE, true)
                         .setIcon(icon)
                         .setButtonEnabled(true)
@@ -248,7 +248,6 @@
                 mGrantAdminView.setVisibility(View.VISIBLE);
                 mCustomDialogHelper
                         .setVisibility(mCustomDialogHelper.ICON, true)
-                        .setVisibility(mCustomDialogHelper.TITLE, true)
                         .setVisibility(mCustomDialogHelper.MESSAGE, true)
                         .setIcon(mActivity.getDrawable(R.drawable.ic_admin_panel_settings))
                         .setTitle(R.string.user_grant_admin_title)
@@ -262,8 +261,8 @@
             case EDIT_NAME_DIALOG:
                 mCustomDialogHelper
                         .setVisibility(mCustomDialogHelper.ICON, false)
-                        .setVisibility(mCustomDialogHelper.TITLE, false)
                         .setVisibility(mCustomDialogHelper.MESSAGE, false)
+                        .setTitle(R.string.user_info_settings_title)
                         .setNegativeButtonText(R.string.back)
                         .setPositiveButtonText(R.string.done);
                 mEditUserInfoView.setVisibility(View.VISIBLE);
diff --git a/packages/SettingsLib/src/com/android/settingslib/users/EditUserInfoController.java b/packages/SettingsLib/src/com/android/settingslib/users/EditUserInfoController.java
index e55d7ea..cd5f597 100644
--- a/packages/SettingsLib/src/com/android/settingslib/users/EditUserInfoController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/users/EditUserInfoController.java
@@ -17,7 +17,6 @@
 package com.android.settingslib.users;
 
 import android.app.Activity;
-import android.app.AlertDialog;
 import android.app.Dialog;
 import android.content.Context;
 import android.content.Intent;
@@ -31,7 +30,6 @@
 import android.view.WindowManager;
 import android.widget.EditText;
 import android.widget.ImageView;
-import android.widget.ScrollView;
 
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
@@ -41,6 +39,7 @@
 import com.android.settingslib.RestrictedLockUtils;
 import com.android.settingslib.RestrictedLockUtilsInternal;
 import com.android.settingslib.drawable.CircleFramedDrawable;
+import com.android.settingslib.utils.CustomDialogHelper;
 
 import java.io.File;
 import java.util.function.BiConsumer;
@@ -128,7 +127,7 @@
      *                        codes to take photo/choose photo/crop photo.
      */
     public Dialog createDialog(Activity activity, ActivityStarter activityStarter,
-            @Nullable Drawable oldUserIcon, String defaultUserName, String title,
+            @Nullable Drawable oldUserIcon, String defaultUserName,
             BiConsumer<String, Drawable> successCallback, Runnable cancelCallback) {
         LayoutInflater inflater = LayoutInflater.from(activity);
         View content = inflater.inflate(R.layout.edit_user_info_dialog_content, null);
@@ -160,10 +159,8 @@
                         userPhotoView);
             }
         }
-        ScrollView scrollView = content.findViewById(R.id.user_info_scroll);
-        scrollView.setClipToOutline(true);
         mEditUserInfoDialog = buildDialog(activity, content, userNameView, oldUserIcon,
-                defaultUserName, title, successCallback, cancelCallback);
+                defaultUserName, successCallback, cancelCallback);
 
         // Make sure the IME is up.
         mEditUserInfoDialog.getWindow()
@@ -181,12 +178,13 @@
     }
 
     private Dialog buildDialog(Activity activity, View content, EditText userNameView,
-            @Nullable Drawable oldUserIcon, String defaultUserName, String title,
+            @Nullable Drawable oldUserIcon, String defaultUserName,
             BiConsumer<String, Drawable> successCallback, Runnable cancelCallback) {
-        return new AlertDialog.Builder(activity)
-                .setView(content)
-                .setCancelable(true)
-                .setPositiveButton(android.R.string.ok, (dialog, which) -> {
+        CustomDialogHelper dialogHelper = new CustomDialogHelper(activity);
+        dialogHelper
+                .setTitle(R.string.user_info_settings_title)
+                .addCustomView(content)
+                .setPositiveButton(android.R.string.ok, view -> {
                     Drawable newUserIcon = mEditUserPhotoController != null
                             ? mEditUserPhotoController.getNewUserPhotoDrawable()
                             : null;
@@ -201,20 +199,23 @@
                     if (successCallback != null) {
                         successCallback.accept(userName, userIcon);
                     }
+                    dialogHelper.getDialog().dismiss();
                 })
-                .setNegativeButton(android.R.string.cancel, (dialog, which) -> {
+                .setBackButton(android.R.string.cancel, view -> {
                     clear();
                     if (cancelCallback != null) {
                         cancelCallback.run();
                     }
-                })
-                .setOnCancelListener(dialog -> {
-                    clear();
-                    if (cancelCallback != null) {
-                        cancelCallback.run();
-                    }
-                })
-                .create();
+                    dialogHelper.getDialog().dismiss();
+                });
+        dialogHelper.getDialog().setOnCancelListener(dialog -> {
+            clear();
+            if (cancelCallback != null) {
+                cancelCallback.run();
+            }
+            dialogHelper.getDialog().dismiss();
+        });
+        return dialogHelper.getDialog();
     }
 
     @VisibleForTesting
diff --git a/packages/SettingsLib/src/com/android/settingslib/utils/CustomDialogHelper.java b/packages/SettingsLib/src/com/android/settingslib/utils/CustomDialogHelper.java
index de48814..5201b3d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/utils/CustomDialogHelper.java
+++ b/packages/SettingsLib/src/com/android/settingslib/utils/CustomDialogHelper.java
@@ -193,6 +193,14 @@
     }
 
     /**
+     * Sets message padding of the dialog.
+     */
+    public CustomDialogHelper setMessagePadding(int dp) {
+        mDialogMessage.setPadding(dp, dp, dp, dp);
+        return this;
+    }
+
+    /**
      * Sets icon of the dialog.
      */
     public CustomDialogHelper setIcon(Drawable icon) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/widget/UpdatableListPreferenceDialogFragment.java b/packages/SettingsLib/src/com/android/settingslib/widget/UpdatableListPreferenceDialogFragment.java
deleted file mode 100644
index bd86d67..0000000
--- a/packages/SettingsLib/src/com/android/settingslib/widget/UpdatableListPreferenceDialogFragment.java
+++ /dev/null
@@ -1,174 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.widget;
-
-import android.content.res.TypedArray;
-import android.os.Bundle;
-import android.widget.ArrayAdapter;
-
-import androidx.annotation.VisibleForTesting;
-import androidx.appcompat.app.AlertDialog.Builder;
-import androidx.preference.ListPreference;
-import androidx.preference.PreferenceDialogFragmentCompat;
-
-import com.android.settingslib.core.instrumentation.Instrumentable;
-
-import java.util.ArrayList;
-
-/**
- * {@link PreferenceDialogFragmentCompat} that updates the available options
- * when {@code onListPreferenceUpdated} is called."
- */
-public class UpdatableListPreferenceDialogFragment extends PreferenceDialogFragmentCompat implements
-        Instrumentable {
-
-    private static final String SAVE_STATE_INDEX = "UpdatableListPreferenceDialogFragment.index";
-    private static final String SAVE_STATE_ENTRIES =
-            "UpdatableListPreferenceDialogFragment.entries";
-    private static final String SAVE_STATE_ENTRY_VALUES =
-            "UpdatableListPreferenceDialogFragment.entryValues";
-    private static final String METRICS_CATEGORY_KEY = "metrics_category_key";
-    private ArrayAdapter mAdapter;
-    private int mClickedDialogEntryIndex;
-    private ArrayList<CharSequence> mEntries;
-    private CharSequence[] mEntryValues;
-    private int mMetricsCategory = METRICS_CATEGORY_UNKNOWN;
-
-    /**
-     * Creates a new instance of {@link UpdatableListPreferenceDialogFragment}.
-     */
-    public static UpdatableListPreferenceDialogFragment newInstance(
-            String key, int metricsCategory) {
-        UpdatableListPreferenceDialogFragment fragment =
-                new UpdatableListPreferenceDialogFragment();
-        Bundle args = new Bundle(1);
-        args.putString(ARG_KEY, key);
-        args.putInt(METRICS_CATEGORY_KEY, metricsCategory);
-        fragment.setArguments(args);
-        return fragment;
-    }
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        final Bundle bundle = getArguments();
-        mMetricsCategory =
-                bundle.getInt(METRICS_CATEGORY_KEY, METRICS_CATEGORY_UNKNOWN);
-        if (savedInstanceState == null) {
-            mEntries = new ArrayList<>();
-            setPreferenceData(getListPreference());
-        } else {
-            mClickedDialogEntryIndex = savedInstanceState.getInt(SAVE_STATE_INDEX, 0);
-            mEntries = savedInstanceState.getCharSequenceArrayList(SAVE_STATE_ENTRIES);
-            mEntryValues =
-                    savedInstanceState.getCharSequenceArray(SAVE_STATE_ENTRY_VALUES);
-        }
-    }
-
-    @Override
-    public void onSaveInstanceState(Bundle outState) {
-        super.onSaveInstanceState(outState);
-        outState.putInt(SAVE_STATE_INDEX, mClickedDialogEntryIndex);
-        outState.putCharSequenceArrayList(SAVE_STATE_ENTRIES, mEntries);
-        outState.putCharSequenceArray(SAVE_STATE_ENTRY_VALUES, mEntryValues);
-    }
-
-    @Override
-    public void onDialogClosed(boolean positiveResult) {
-        if (positiveResult && mClickedDialogEntryIndex >= 0) {
-            final ListPreference preference = getListPreference();
-            final String value = mEntryValues[mClickedDialogEntryIndex].toString();
-            if (preference.callChangeListener(value)) {
-                preference.setValue(value);
-            }
-        }
-    }
-
-    @VisibleForTesting
-    void setAdapter(ArrayAdapter adapter) {
-        mAdapter = adapter;
-    }
-
-    @VisibleForTesting
-    void setEntries(ArrayList<CharSequence> entries) {
-        mEntries = entries;
-    }
-
-    @VisibleForTesting
-    ArrayAdapter getAdapter() {
-        return mAdapter;
-    }
-
-    @VisibleForTesting
-    void setMetricsCategory(Bundle bundle) {
-        mMetricsCategory =
-                bundle.getInt(METRICS_CATEGORY_KEY, METRICS_CATEGORY_UNKNOWN);
-    }
-
-    @Override
-    protected void onPrepareDialogBuilder(Builder builder) {
-        super.onPrepareDialogBuilder(builder);
-        final TypedArray a = getContext().obtainStyledAttributes(
-                null,
-                com.android.internal.R.styleable.AlertDialog,
-                com.android.internal.R.attr.alertDialogStyle, 0);
-
-        mAdapter = new ArrayAdapter<>(
-                getContext(),
-                a.getResourceId(
-                        com.android.internal.R.styleable.AlertDialog_singleChoiceItemLayout,
-                        com.android.internal.R.layout.select_dialog_singlechoice),
-                mEntries);
-
-        builder.setSingleChoiceItems(mAdapter, mClickedDialogEntryIndex,
-                (dialog, which) -> {
-                    mClickedDialogEntryIndex = which;
-                    onClick(dialog, -1);
-                    dialog.dismiss();
-                });
-        builder.setPositiveButton(null, null);
-        a.recycle();
-    }
-
-    @Override
-    public int getMetricsCategory() {
-        return mMetricsCategory;
-    }
-
-    @VisibleForTesting
-    ListPreference getListPreference() {
-        return (ListPreference) getPreference();
-    }
-
-    private void setPreferenceData(ListPreference preference) {
-        mEntries.clear();
-        mClickedDialogEntryIndex = preference.findIndexOfValue(preference.getValue());
-        for (CharSequence entry : preference.getEntries()) {
-            mEntries.add(entry);
-        }
-        mEntryValues = preference.getEntryValues();
-    }
-
-    /**
-     * Update new data set for list preference.
-     */
-    public void onListPreferenceUpdated(ListPreference preference) {
-        if (mAdapter != null) {
-            setPreferenceData(preference);
-            mAdapter.notifyDataSetChanged();
-        }
-    }
-}
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java
index fd986e5..21eb35a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java
@@ -87,7 +87,13 @@
             // Fallback for platforms that do not need friction icon resources.
             frictionSld = null;
         }
-        return frictionSld != null ? (StateListDrawable) frictionSld.getDrawable(0) : null;
+        if (frictionSld != null) {
+            StateListDrawable val = (StateListDrawable) frictionSld.getDrawable(0);
+            frictionSld.recycle();
+            return val;
+        } else {
+            return null;
+        }
     }
 
     // Used for fake pref.
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/OWNERS b/packages/SettingsLib/src/com/android/settingslib/wifi/OWNERS
index f3b600c..b9449ac 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/OWNERS
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/OWNERS
@@ -3,7 +3,6 @@
 arcwang@google.com
 asapperstein@google.com
 changbetty@google.com
-goldmanj@google.com
 qal@google.com
 wengsu@google.com
 
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
index 6444f3b..4b61ff1 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
@@ -1015,6 +1015,13 @@
     }
 
     @Test
+    public void setName_setDeviceNameIsEmpty() {
+        mCachedDevice.setName("");
+
+        verify(mDevice, never()).setAlias(any());
+    }
+
+    @Test
     public void getProfileConnectionState_nullProfile_returnDisconnected() {
         assertThat(mCachedDevice.getProfileConnectionState(null)).isEqualTo(
                 BluetoothProfile.STATE_DISCONNECTED);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java
index 0d5de88..ea10944 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java
@@ -33,6 +33,8 @@
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothHearingAid;
 import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothUuid;
+import android.bluetooth.le.ScanFilter;
 import android.content.Context;
 import android.media.AudioAttributes;
 import android.media.AudioDeviceAttributes;
@@ -147,7 +149,7 @@
                 HearingAidProfile.DeviceSide.SIDE_RIGHT);
 
         assertThat(mCachedDevice1.getHiSyncId()).isNotEqualTo(HISYNCID1);
-        mHearingAidDeviceManager.initHearingAidDeviceIfNeeded(mCachedDevice1);
+        mHearingAidDeviceManager.initHearingAidDeviceIfNeeded(mCachedDevice1, null);
 
         assertThat(mCachedDevice1.getHiSyncId()).isEqualTo(HISYNCID1);
         assertThat(mCachedDevice1.getDeviceMode()).isEqualTo(
@@ -164,12 +166,43 @@
         when(mHearingAidProfile.getHiSyncId(mDevice1)).thenReturn(
                 BluetoothHearingAid.HI_SYNC_ID_INVALID);
 
-        mHearingAidDeviceManager.initHearingAidDeviceIfNeeded(mCachedDevice1);
+        mHearingAidDeviceManager.initHearingAidDeviceIfNeeded(mCachedDevice1, null);
 
         verify(mCachedDevice1, never()).setHearingAidInfo(any(HearingAidInfo.class));
     }
 
     /**
+     * Test initHearingAidDeviceIfNeeded, an invalid HiSyncId and hearing aid scan filter, set an
+     * empty hearing aid info on the device.
+     */
+    @Test
+    public void initHearingAidDeviceIfNeeded_hearingAidScanFilter_setHearingAidInfo() {
+        when(mHearingAidProfile.getHiSyncId(mDevice1)).thenReturn(
+                BluetoothHearingAid.HI_SYNC_ID_INVALID);
+        final ScanFilter scanFilter = new ScanFilter.Builder()
+                .setServiceData(BluetoothUuid.HEARING_AID, new byte[]{0}).build();
+
+        mHearingAidDeviceManager.initHearingAidDeviceIfNeeded(mCachedDevice1, List.of(scanFilter));
+
+        assertThat(mCachedDevice1.isHearingAidDevice()).isTrue();
+    }
+
+    /**
+     * Test initHearingAidDeviceIfNeeded, an invalid HiSyncId and random scan filter, not to set
+     * hearing aid info on the device.
+     */
+    @Test
+    public void initHearingAidDeviceIfNeeded_randomScanFilter_setHearingAidInfo() {
+        when(mHearingAidProfile.getHiSyncId(mDevice1)).thenReturn(
+                BluetoothHearingAid.HI_SYNC_ID_INVALID);
+        final ScanFilter scanFilter = new ScanFilter.Builder().build();
+
+        mHearingAidDeviceManager.initHearingAidDeviceIfNeeded(mCachedDevice1, List.of(scanFilter));
+
+        assertThat(mCachedDevice1.isHearingAidDevice()).isFalse();
+    }
+
+    /**
      * Test setSubDeviceIfNeeded, a device with same HiSyncId will be set as sub device
      */
     @Test
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/MetricsFeatureProviderTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/MetricsFeatureProviderTest.java
index 3352d86..dd8d54a 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/MetricsFeatureProviderTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/MetricsFeatureProviderTest.java
@@ -203,4 +203,24 @@
         assertThat(loggable).isFalse();
         verifyNoMoreInteractions(mLogWriter);
     }
+
+    @Test
+    public void logSettingsTileClickWithProfile_isPersonalProfile_shouldTagPersonal() {
+        final String key = "abc";
+        final boolean loggable = mProvider.logSettingsTileClickWithProfile(key,
+                MetricsEvent.SETTINGS_GESTURES, false);
+
+        assertThat(loggable).isTrue();
+        verify(mLogWriter).clicked(MetricsEvent.SETTINGS_GESTURES, "abc/personal");
+    }
+
+    @Test
+    public void logSettingsTileClickWithProfile_isWorkProfile_shouldTagWork() {
+        final String key = "abc";
+        final boolean loggable = mProvider.logSettingsTileClickWithProfile(key,
+                MetricsEvent.SETTINGS_GESTURES, true);
+
+        assertThat(loggable).isTrue();
+        verify(mLogWriter).clicked(MetricsEvent.SETTINGS_GESTURES, "abc/work");
+    }
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/EnableAdbPreferenceControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/EnableAdbPreferenceControllerTest.java
index 5f53a92c..35223c2 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/EnableAdbPreferenceControllerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/EnableAdbPreferenceControllerTest.java
@@ -140,8 +140,8 @@
     public void updateState_settingsOn_shouldCheck() {
         when(mUserManager.isAdminUser()).thenReturn(true);
         when(mDevicePolicyManager.getProfileOwner()).thenReturn(TEST_COMPONENT_NAME);
-        when(mDevicePolicyManager.isUsbDataSignalingEnabledForUser(
-                UserHandle.myUserId())).thenReturn(true);
+        when(mDevicePolicyManager.isUsbDataSignalingEnabled(
+                )).thenReturn(true);
         Settings.Global.putInt(mContext.getContentResolver(),
                 Settings.Global.ADB_ENABLED, 1);
         mPreference.setChecked(false);
@@ -156,8 +156,8 @@
     public void updateState_settingsOff_shouldUncheck() {
         when(mUserManager.isAdminUser()).thenReturn(true);
         when(mDevicePolicyManager.getProfileOwner()).thenReturn(TEST_COMPONENT_NAME);
-        when(mDevicePolicyManager.isUsbDataSignalingEnabledForUser(
-                UserHandle.myUserId())).thenReturn(true);
+        when(mDevicePolicyManager.isUsbDataSignalingEnabled(
+                )).thenReturn(true);
         Settings.Global.putInt(mContext.getContentResolver(),
                 Settings.Global.ADB_ENABLED, 0);
         mPreference.setChecked(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 aa6b0bf..4d2b1ae 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
@@ -17,19 +17,23 @@
 
 import static com.android.settingslib.drawer.TileUtils.META_DATA_KEY_ORDER;
 import static com.android.settingslib.drawer.TileUtils.META_DATA_KEY_PROFILE;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_GROUP_KEY;
 import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON;
 import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON_URI;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SWITCH_URI;
 import static com.android.settingslib.drawer.TileUtils.PROFILE_ALL;
 import static com.android.settingslib.drawer.TileUtils.PROFILE_PRIMARY;
 
 import static com.google.common.truth.Truth.assertThat;
 
+import android.app.PendingIntent;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.ResolveInfo;
 import android.os.Bundle;
+import android.os.UserHandle;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -191,4 +195,65 @@
 
         assertThat(tile.getTitle(RuntimeEnvironment.application)).isNull();
     }
+
+    @Test
+    public void hasPendingIntent_empty_returnsFalse() {
+        final Tile tile = new ActivityTile(mActivityInfo, "category");
+
+        assertThat(tile.hasPendingIntent()).isFalse();
+    }
+
+    @Test
+    public void hasPendingIntent_notEmpty_returnsTrue() {
+        final Tile tile = new ActivityTile(mActivityInfo, "category");
+        tile.pendingIntentMap.put(
+                UserHandle.CURRENT, PendingIntent.getActivity(mContext, 0, new Intent(), 0));
+
+        assertThat(tile.hasPendingIntent()).isTrue();
+    }
+
+    @Test
+    public void hasGroupKey_empty_returnsFalse() {
+        final Tile tile = new ActivityTile(mActivityInfo, "category");
+
+        assertThat(tile.hasGroupKey()).isFalse();
+    }
+
+    @Test
+    public void hasGroupKey_notEmpty_returnsTrue() {
+        mActivityInfo.metaData.putString(META_DATA_PREFERENCE_GROUP_KEY, "test_key");
+        final Tile tile = new ActivityTile(mActivityInfo, "category");
+
+        assertThat(tile.hasGroupKey()).isTrue();
+    }
+
+    @Test
+    public void getGroupKey_empty_returnsNull() {
+        final Tile tile = new ActivityTile(mActivityInfo, "category");
+
+        assertThat(tile.getGroupKey()).isNull();
+    }
+
+    @Test
+    public void getGroupKey_notEmpty_returnsValue() {
+        mActivityInfo.metaData.putString(META_DATA_PREFERENCE_GROUP_KEY, "test_key");
+        final Tile tile = new ActivityTile(mActivityInfo, "category");
+
+        assertThat(tile.getGroupKey()).isEqualTo("test_key");
+    }
+
+    @Test
+    public void getType_withoutSwitch_returnsAction() {
+        final Tile tile = new ActivityTile(mActivityInfo, "category");
+
+        assertThat(tile.getType()).isEqualTo(Tile.Type.ACTION);
+    }
+
+    @Test
+    public void getType_withSwitch_returnsSwitchWithAction() {
+        mActivityInfo.metaData.putString(META_DATA_PREFERENCE_SWITCH_URI, "test://testabc/");
+        final Tile tile = new ActivityTile(mActivityInfo, "category");
+
+        assertThat(tile.getType()).isEqualTo(Tile.Type.SWITCH_WITH_ACTION);
+    }
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/EntriesProviderTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/EntriesProviderTest.java
new file mode 100644
index 0000000..a248330
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/EntriesProviderTest.java
@@ -0,0 +1,472 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.drawer;
+
+import static com.android.settingslib.drawer.EntriesProvider.EXTRA_ENTRY_DATA;
+import static com.android.settingslib.drawer.EntriesProvider.EXTRA_SWITCH_CHECKED_STATE;
+import static com.android.settingslib.drawer.EntriesProvider.EXTRA_SWITCH_DATA;
+import static com.android.settingslib.drawer.EntriesProvider.EXTRA_SWITCH_SET_CHECKED_ERROR;
+import static com.android.settingslib.drawer.EntriesProvider.EXTRA_SWITCH_SET_CHECKED_ERROR_MESSAGE;
+import static com.android.settingslib.drawer.EntriesProvider.METHOD_GET_DYNAMIC_SUMMARY;
+import static com.android.settingslib.drawer.EntriesProvider.METHOD_GET_DYNAMIC_TITLE;
+import static com.android.settingslib.drawer.EntriesProvider.METHOD_GET_ENTRY_DATA;
+import static com.android.settingslib.drawer.EntriesProvider.METHOD_GET_PROVIDER_ICON;
+import static com.android.settingslib.drawer.EntriesProvider.METHOD_GET_SWITCH_DATA;
+import static com.android.settingslib.drawer.EntriesProvider.METHOD_IS_CHECKED;
+import static com.android.settingslib.drawer.EntriesProvider.METHOD_ON_CHECKED_CHANGED;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_KEYHINT;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_PENDING_INTENT;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SUMMARY;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_TITLE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ProviderInfo;
+import android.os.Bundle;
+
+import com.android.settingslib.drawer.EntryController.MetaData;
+import com.android.settingslib.drawer.PrimarySwitchControllerTest.TestPrimarySwitchController;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(RobolectricTestRunner.class)
+public class EntriesProviderTest {
+
+    @Rule
+    public final ExpectedException thrown = ExpectedException.none();
+
+    private Context mContext;
+    private ProviderInfo mProviderInfo;
+
+    private TestEntriesProvider mEntriesProvider;
+
+    @Before
+    public void setUp() {
+        mContext = RuntimeEnvironment.application;
+        mEntriesProvider = new TestEntriesProvider();
+        mProviderInfo = new ProviderInfo();
+        mProviderInfo.authority = "auth";
+    }
+
+    @Test
+    public void attachInfo_noController_shouldThrowIllegalArgumentException() {
+        thrown.expect(IllegalArgumentException.class);
+
+        mEntriesProvider.attachInfo(mContext, mProviderInfo);
+    }
+
+    @Test
+    public void attachInfo_NoKeyInController_shouldThrowNullPointerException() {
+        thrown.expect(NullPointerException.class);
+        final TestEntryController controller = new TestEntryController();
+        mEntriesProvider.addController(controller);
+
+        mEntriesProvider.attachInfo(mContext, mProviderInfo);
+    }
+
+    @Test
+    public void attachInfo_NoMetaDataInController_shouldThrowNullPointerException() {
+        thrown.expect(NullPointerException.class);
+        final TestEntryController controller = new TestEntryController();
+        controller.setKey("123");
+        mEntriesProvider.addController(controller);
+
+        mEntriesProvider.attachInfo(mContext, mProviderInfo);
+    }
+
+    @Test
+    public void attachInfo_duplicateKey_shouldThrowIllegalArgumentException() {
+        thrown.expect(IllegalArgumentException.class);
+        final TestEntryController controller1 = new TestEntryController();
+        final TestEntryController controller2 = new TestEntryController();
+        controller1.setKey("123");
+        controller2.setKey("123");
+        controller1.setMetaData(new MetaData("category"));
+        controller2.setMetaData(new MetaData("category"));
+        mEntriesProvider.addController(controller1);
+        mEntriesProvider.addController(controller2);
+
+        mEntriesProvider.attachInfo(mContext, mProviderInfo);
+    }
+
+    @Test
+    public void attachInfo_hasDifferentControllers_shouldNotThrowException() {
+        final TestEntryController controller1 = new TestEntryController();
+        final TestEntryController controller2 = new TestEntryController();
+        controller1.setKey("123");
+        controller2.setKey("456");
+        controller1.setMetaData(new MetaData("category"));
+        controller2.setMetaData(new MetaData("category"));
+        mEntriesProvider.addController(controller1);
+        mEntriesProvider.addController(controller2);
+
+        mEntriesProvider.attachInfo(mContext, mProviderInfo);
+    }
+
+    @Test
+    public void getEntryData_shouldNotReturnPrimarySwitchData() {
+        final EntryController controller = new TestPrimarySwitchController("123");
+        mEntriesProvider.addController(controller);
+        mEntriesProvider.attachInfo(mContext, mProviderInfo);
+
+        final Bundle switchData = mEntriesProvider.call(METHOD_GET_ENTRY_DATA, "uri",
+                null /* extras*/);
+
+        final ArrayList<Bundle> dataList = switchData.getParcelableArrayList(EXTRA_ENTRY_DATA);
+        assertThat(dataList).isEmpty();
+    }
+
+    @Test
+    public void getEntryData_shouldReturnDataList() {
+        final TestEntryController controller = new TestEntryController();
+        final PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+        controller.setKey("123");
+        controller.setMetaData(new MetaData("category").setPendingIntent(pendingIntent));
+        mEntriesProvider.addController(controller);
+        mEntriesProvider.attachInfo(mContext, mProviderInfo);
+
+        final Bundle entryData = mEntriesProvider.call(METHOD_GET_ENTRY_DATA, "uri",
+                null /* extras*/);
+
+        final ArrayList<Bundle> dataList = entryData.getParcelableArrayList(EXTRA_ENTRY_DATA);
+        assertThat(dataList).hasSize(1);
+        assertThat(dataList.get(0).getString(META_DATA_PREFERENCE_KEYHINT)).isEqualTo("123");
+        assertThat(dataList.get(0).getParcelable(META_DATA_PREFERENCE_PENDING_INTENT,
+                PendingIntent.class))
+                .isEqualTo(pendingIntent);
+    }
+
+    @Test
+    public void getSwitchData_shouldReturnDataList() {
+        final TestEntryController controller = new TestEntryController();
+        final PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+        controller.setKey("123");
+        controller.setMetaData(new MetaData("category").setPendingIntent(pendingIntent));
+        mEntriesProvider.addController(controller);
+        mEntriesProvider.attachInfo(mContext, mProviderInfo);
+
+        final Bundle entryData = mEntriesProvider.call(METHOD_GET_SWITCH_DATA, "uri",
+                null /* extras*/);
+
+        final ArrayList<Bundle> dataList = entryData.getParcelableArrayList(EXTRA_SWITCH_DATA);
+        assertThat(dataList).hasSize(1);
+        assertThat(dataList.get(0).getString(META_DATA_PREFERENCE_KEYHINT)).isEqualTo("123");
+        assertThat(dataList.get(0).getParcelable(META_DATA_PREFERENCE_PENDING_INTENT,
+                PendingIntent.class))
+                .isEqualTo(pendingIntent);
+    }
+
+    @Test
+    public void getEntryDataByKey_shouldReturnData() {
+        final Bundle extras = new Bundle();
+        extras.putString(META_DATA_PREFERENCE_KEYHINT, "123");
+        final TestEntryController controller = new TestEntryController();
+        controller.setKey("123");
+        controller.setMetaData(new MetaData("category"));
+        mEntriesProvider.addController(controller);
+        mEntriesProvider.attachInfo(mContext, mProviderInfo);
+
+        final Bundle entryData = mEntriesProvider.call(METHOD_GET_ENTRY_DATA, "uri", extras);
+
+        assertThat(entryData.getString(META_DATA_PREFERENCE_KEYHINT)).isEqualTo("123");
+    }
+
+    @Test
+    public void getSwitchDataByKey_shouldReturnData() {
+        final Bundle extras = new Bundle();
+        extras.putString(META_DATA_PREFERENCE_KEYHINT, "123");
+        final TestEntryController controller = new TestEntryController();
+        controller.setKey("123");
+        controller.setMetaData(new MetaData("category"));
+        mEntriesProvider.addController(controller);
+        mEntriesProvider.attachInfo(mContext, mProviderInfo);
+
+        final Bundle entryData = mEntriesProvider.call(METHOD_GET_SWITCH_DATA, "uri", extras);
+
+        assertThat(entryData.getString(META_DATA_PREFERENCE_KEYHINT)).isEqualTo("123");
+    }
+
+    @Test
+    public void isSwitchChecked_shouldReturnCheckedState() {
+        final Bundle extras = new Bundle();
+        extras.putString(META_DATA_PREFERENCE_KEYHINT, "123");
+        final TestSwitchController controller = new TestSwitchController();
+        controller.setKey("123");
+        controller.setMetaData(new MetaData("category"));
+        mEntriesProvider.addController(controller);
+        mEntriesProvider.attachInfo(mContext, mProviderInfo);
+
+        controller.setSwitchChecked(true);
+        Bundle result = mEntriesProvider.call(METHOD_IS_CHECKED, "uri", extras);
+
+        assertThat(result.getBoolean(EXTRA_SWITCH_CHECKED_STATE)).isTrue();
+
+        controller.setSwitchChecked(false);
+        result = mEntriesProvider.call(METHOD_IS_CHECKED, "uri", extras);
+
+        assertThat(result.getBoolean(EXTRA_SWITCH_CHECKED_STATE)).isFalse();
+    }
+
+    @Test
+    public void getProviderIcon_noImplementInterface_shouldReturnNull() {
+        final Bundle extras = new Bundle();
+        extras.putString(META_DATA_PREFERENCE_KEYHINT, "123");
+        final TestEntryController controller = new TestEntryController();
+        controller.setKey("123");
+        controller.setMetaData(new MetaData("category"));
+        mEntriesProvider.addController(controller);
+        mEntriesProvider.attachInfo(mContext, mProviderInfo);
+
+        final Bundle iconBundle = mEntriesProvider.call(METHOD_GET_PROVIDER_ICON, "uri", extras);
+
+        assertThat(iconBundle).isNull();
+    }
+
+    @Test
+    public void getProviderIcon_implementInterface_shouldReturnIcon() {
+        final Bundle extras = new Bundle();
+        extras.putString(META_DATA_PREFERENCE_KEYHINT, "123");
+        final TestEntryController controller = new TestDynamicController();
+        controller.setKey("123");
+        controller.setMetaData(new MetaData("category"));
+        mEntriesProvider.addController(controller);
+        mEntriesProvider.attachInfo(mContext, mProviderInfo);
+
+        final Bundle iconBundle = mEntriesProvider.call(METHOD_GET_PROVIDER_ICON, "uri", extras);
+
+        assertThat(iconBundle).isEqualTo(TestDynamicController.ICON_BUNDLE);
+    }
+
+    @Test
+    public void getDynamicTitle_noImplementInterface_shouldReturnNull() {
+        final Bundle extras = new Bundle();
+        extras.putString(META_DATA_PREFERENCE_KEYHINT, "123");
+        final TestEntryController controller = new TestEntryController();
+        controller.setKey("123");
+        controller.setMetaData(new MetaData("category"));
+        mEntriesProvider.addController(controller);
+        mEntriesProvider.attachInfo(mContext, mProviderInfo);
+
+        final Bundle result = mEntriesProvider.call(METHOD_GET_DYNAMIC_TITLE, "uri", extras);
+
+        assertThat(result).isNull();
+    }
+
+    @Test
+    public void getDynamicTitle_implementInterface_shouldReturnTitle() {
+        final Bundle extras = new Bundle();
+        extras.putString(META_DATA_PREFERENCE_KEYHINT, "123");
+        final TestEntryController controller = new TestDynamicController();
+        controller.setKey("123");
+        controller.setMetaData(new MetaData("category"));
+        mEntriesProvider.addController(controller);
+        mEntriesProvider.attachInfo(mContext, mProviderInfo);
+
+        final Bundle result = mEntriesProvider.call(METHOD_GET_DYNAMIC_TITLE, "uri", extras);
+
+        assertThat(result.getString(META_DATA_PREFERENCE_TITLE))
+                .isEqualTo(TestDynamicController.TITLE);
+    }
+
+    @Test
+    public void getDynamicSummary_noImplementInterface_shouldReturnNull() {
+        final Bundle extras = new Bundle();
+        extras.putString(META_DATA_PREFERENCE_KEYHINT, "123");
+        final TestEntryController controller = new TestEntryController();
+        controller.setKey("123");
+        controller.setMetaData(new MetaData("category"));
+        mEntriesProvider.addController(controller);
+        mEntriesProvider.attachInfo(mContext, mProviderInfo);
+
+        final Bundle result = mEntriesProvider.call(METHOD_GET_DYNAMIC_SUMMARY, "uri", extras);
+
+        assertThat(result).isNull();
+    }
+
+    @Test
+    public void getDynamicSummary_implementInterface_shouldReturnSummary() {
+        final Bundle extras = new Bundle();
+        extras.putString(META_DATA_PREFERENCE_KEYHINT, "123");
+        final TestEntryController controller = new TestDynamicController();
+        controller.setKey("123");
+        controller.setMetaData(new MetaData("category"));
+        mEntriesProvider.addController(controller);
+        mEntriesProvider.attachInfo(mContext, mProviderInfo);
+
+        final Bundle result = mEntriesProvider.call(METHOD_GET_DYNAMIC_SUMMARY, "uri", extras);
+
+        assertThat(result.getString(META_DATA_PREFERENCE_SUMMARY))
+                .isEqualTo(TestDynamicController.SUMMARY);
+    }
+
+    @Test
+    public void onSwitchCheckedChangedSuccess_shouldReturnNoError() {
+        final Bundle extras = new Bundle();
+        extras.putString(META_DATA_PREFERENCE_KEYHINT, "123");
+        final TestSwitchController controller = new TestSwitchController();
+        controller.setKey("123");
+        controller.setMetaData(new MetaData("category"));
+        mEntriesProvider.addController(controller);
+        mEntriesProvider.attachInfo(mContext, mProviderInfo);
+
+        final Bundle result = mEntriesProvider.call(METHOD_ON_CHECKED_CHANGED, "uri", extras);
+
+        assertThat(result.getBoolean(EXTRA_SWITCH_SET_CHECKED_ERROR)).isFalse();
+    }
+
+    @Test
+    public void onSwitchCheckedChangedFailed_shouldReturnErrorMessage() {
+        final Bundle extras = new Bundle();
+        extras.putString(META_DATA_PREFERENCE_KEYHINT, "123");
+        final TestSwitchController controller = new TestSwitchController();
+        controller.setKey("123");
+        controller.setMetaData(new MetaData("category"));
+        controller.setSwitchErrorMessage("error");
+        mEntriesProvider.addController(controller);
+        mEntriesProvider.attachInfo(mContext, mProviderInfo);
+
+        final Bundle result = mEntriesProvider.call(METHOD_ON_CHECKED_CHANGED, "uri", extras);
+
+        assertThat(result.getBoolean(EXTRA_SWITCH_SET_CHECKED_ERROR)).isTrue();
+        assertThat(result.getString(EXTRA_SWITCH_SET_CHECKED_ERROR_MESSAGE)).isEqualTo("error");
+    }
+
+    private static class TestEntriesProvider extends EntriesProvider {
+
+        private List<EntryController> mControllers;
+
+        @Override
+        protected List<EntryController> createEntryControllers() {
+            return mControllers;
+        }
+
+        void addController(EntryController controller) {
+            if (mControllers == null) {
+                mControllers = new ArrayList<>();
+            }
+            mControllers.add(controller);
+        }
+    }
+
+    private static class TestEntryController extends EntryController {
+
+        private String mKey;
+        private MetaData mMetaData;
+
+        @Override
+        public String getKey() {
+            return mKey;
+        }
+
+        @Override
+        protected MetaData getMetaData() {
+            return mMetaData;
+        }
+
+        void setKey(String key) {
+            mKey = key;
+        }
+
+        void setMetaData(MetaData metaData) {
+            mMetaData = metaData;
+        }
+    }
+
+    private static class TestSwitchController extends EntryController implements ProviderSwitch {
+
+        private String mKey;
+        private MetaData mMetaData;
+        private boolean mChecked;
+        private String mErrorMsg;
+
+        @Override
+        public String getKey() {
+            return mKey;
+        }
+
+        @Override
+        protected MetaData getMetaData() {
+            return mMetaData;
+        }
+
+        @Override
+        public boolean isSwitchChecked() {
+            return mChecked;
+        }
+
+        @Override
+        public boolean onSwitchCheckedChanged(boolean checked) {
+            return mErrorMsg == null ? true : false;
+        }
+
+        @Override
+        public String getSwitchErrorMessage(boolean attemptedChecked) {
+            return mErrorMsg;
+        }
+
+        void setKey(String key) {
+            mKey = key;
+        }
+
+        void setMetaData(MetaData metaData) {
+            mMetaData = metaData;
+        }
+
+        void setSwitchChecked(boolean checked) {
+            mChecked = checked;
+        }
+
+        void setSwitchErrorMessage(String errorMsg) {
+            mErrorMsg = errorMsg;
+        }
+    }
+
+    private static class TestDynamicController extends TestEntryController
+            implements ProviderIcon, DynamicTitle, DynamicSummary {
+
+        static final String TITLE = "title";
+        static final String SUMMARY = "summary";
+        static final Bundle ICON_BUNDLE = new Bundle();
+
+        @Override
+        public Bundle getProviderIcon() {
+            return ICON_BUNDLE;
+        }
+
+        @Override
+        public String getDynamicTitle() {
+            return TITLE;
+        }
+
+        @Override
+        public String getDynamicSummary() {
+            return SUMMARY;
+        }
+    }
+}
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 abfb407..80f9efb 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
@@ -17,20 +17,24 @@
 
 import static com.android.settingslib.drawer.TileUtils.META_DATA_KEY_ORDER;
 import static com.android.settingslib.drawer.TileUtils.META_DATA_KEY_PROFILE;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_GROUP_KEY;
 import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON;
 import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_KEYHINT;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SWITCH_URI;
 import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_TITLE;
 import static com.android.settingslib.drawer.TileUtils.PROFILE_ALL;
 import static com.android.settingslib.drawer.TileUtils.PROFILE_PRIMARY;
 
 import static com.google.common.truth.Truth.assertThat;
 
+import android.app.PendingIntent;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.ProviderInfo;
 import android.content.pm.ResolveInfo;
 import android.os.Bundle;
+import android.os.UserHandle;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -173,13 +177,93 @@
         assertThat(tile.mLastUpdateTime).isNotEqualTo(staleTimeStamp);
     }
 
+    @Test
+    public void hasPendingIntent_empty_returnsFalse() {
+        final Tile tile = new ProviderTile(mProviderInfo, "category", mMetaData);
+
+        assertThat(tile.hasPendingIntent()).isFalse();
+    }
+
+    @Test
+    public void hasPendingIntent_notEmpty_returnsTrue() {
+        final Tile tile = new ProviderTile(mProviderInfo, "category", mMetaData);
+        tile.pendingIntentMap.put(
+                UserHandle.CURRENT, PendingIntent.getActivity(mContext, 0, new Intent(), 0));
+
+        assertThat(tile.hasPendingIntent()).isTrue();
+    }
+
+    @Test
+    public void hasGroupKey_empty_returnsFalse() {
+        final Tile tile = new ProviderTile(mProviderInfo, "category", mMetaData);
+
+        assertThat(tile.hasGroupKey()).isFalse();
+    }
+
+    @Test
+    public void hasGroupKey_notEmpty_returnsTrue() {
+        mMetaData.putString(META_DATA_PREFERENCE_GROUP_KEY, "test_key");
+        final Tile tile = new ProviderTile(mProviderInfo, "category", mMetaData);
+
+        assertThat(tile.hasGroupKey()).isTrue();
+    }
+
+    @Test
+    public void getGroupKey_empty_returnsNull() {
+        final Tile tile = new ProviderTile(mProviderInfo, "category", mMetaData);
+
+        assertThat(tile.getGroupKey()).isNull();
+    }
+
+    @Test
+    public void getGroupKey_notEmpty_returnsValue() {
+        mMetaData.putString(META_DATA_PREFERENCE_GROUP_KEY, "test_key");
+        final Tile tile = new ProviderTile(mProviderInfo, "category", mMetaData);
+
+        assertThat(tile.getGroupKey()).isEqualTo("test_key");
+    }
+
+    @Test
+    public void getType_withSwitch_returnsSwitch() {
+        mMetaData.putString(META_DATA_PREFERENCE_SWITCH_URI, "test://testabc/");
+        final Tile tile = new ProviderTile(mProviderInfo, "category", mMetaData);
+
+        assertThat(tile.getType()).isEqualTo(Tile.Type.SWITCH);
+    }
+
+    @Test
+    public void getType_withSwitchAndPendingIntent_returnsSwitchWithAction() {
+        mMetaData.putString(META_DATA_PREFERENCE_SWITCH_URI, "test://testabc/");
+        final Tile tile = new ProviderTile(mProviderInfo, "category", mMetaData);
+        tile.pendingIntentMap.put(
+                UserHandle.CURRENT, PendingIntent.getActivity(mContext, 0, new Intent(), 0));
+
+        assertThat(tile.getType()).isEqualTo(Tile.Type.SWITCH_WITH_ACTION);
+    }
+
+    @Test
+    public void getType_withPendingIntent_returnsExternalAction() {
+        final Tile tile = new ProviderTile(mProviderInfo, "category", mMetaData);
+        tile.pendingIntentMap.put(
+                UserHandle.CURRENT, PendingIntent.getActivity(mContext, 0, new Intent(), 0));
+
+        assertThat(tile.getType()).isEqualTo(Tile.Type.EXTERNAL_ACTION);
+    }
+
+    @Test
+    public void getType_withoutSwitchAndPendingIntent_returnsGroup() {
+        final Tile tile = new ProviderTile(mProviderInfo, "category", mMetaData);
+
+        assertThat(tile.getType()).isEqualTo(Tile.Type.GROUP);
+    }
+
     @Implements(TileUtils.class)
     private static class ShadowTileUtils {
 
         private static Bundle sMetaData;
 
         @Implementation
-        protected static Bundle getSwitchDataFromProvider(Context context, String authority,
+        protected static Bundle getEntryDataFromProvider(Context context, String authority,
                 String key) {
             return sMetaData;
         }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java
index 906e06e..2086466 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java
@@ -21,6 +21,7 @@
 import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON;
 import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON_URI;
 import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_KEYHINT;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_PENDING_INTENT;
 import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SUMMARY;
 import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SUMMARY_URI;
 import static com.android.settingslib.drawer.TileUtils.PROFILE_ALL;
@@ -40,6 +41,7 @@
 import static org.robolectric.RuntimeEnvironment.application;
 
 import android.app.ActivityManager;
+import android.app.PendingIntent;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
@@ -350,6 +352,53 @@
         assertThat(outTiles).isEmpty();
     }
 
+    @Test
+    public void loadTilesForAction_multipleUserProfiles_updatesUserHandle() {
+        Map<Pair<String, String>, Tile> addedCache = new ArrayMap<>();
+        List<Tile> outTiles = new ArrayList<>();
+        List<ResolveInfo> info = new ArrayList<>();
+        ResolveInfo resolveInfo = newInfo(true, null /* category */, null, URI_GET_ICON,
+                URI_GET_SUMMARY, null, 123, PROFILE_ALL);
+        info.add(resolveInfo);
+
+        when(mPackageManager.queryIntentActivitiesAsUser(any(Intent.class), anyInt(), anyInt()))
+                .thenReturn(info);
+
+        TileUtils.loadTilesForAction(mContext, UserHandle.CURRENT, IA_SETTINGS_ACTION,
+                addedCache, null /* defaultCategory */, outTiles, false /* requiresSettings */);
+        TileUtils.loadTilesForAction(mContext, new UserHandle(10), IA_SETTINGS_ACTION,
+                addedCache, null /* defaultCategory */, outTiles, false /* requiresSettings */);
+
+        assertThat(outTiles).hasSize(1);
+        assertThat(outTiles.get(0).userHandle)
+                .containsExactly(UserHandle.CURRENT, new UserHandle(10));
+    }
+
+    @Test
+    public void loadTilesForAction_withPendingIntent_updatesPendingIntentMap() {
+        Map<Pair<String, String>, Tile> addedCache = new ArrayMap<>();
+        List<Tile> outTiles = new ArrayList<>();
+        List<ResolveInfo> info = new ArrayList<>();
+        ResolveInfo resolveInfo = newInfo(true, null /* category */, null, URI_GET_ICON,
+                URI_GET_SUMMARY, null, 123, PROFILE_ALL);
+        PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+        resolveInfo.activityInfo.metaData
+                .putParcelable(META_DATA_PREFERENCE_PENDING_INTENT, pendingIntent);
+        info.add(resolveInfo);
+
+        when(mPackageManager.queryIntentActivitiesAsUser(any(Intent.class), anyInt(), anyInt()))
+                .thenReturn(info);
+
+        TileUtils.loadTilesForAction(mContext, UserHandle.CURRENT, IA_SETTINGS_ACTION,
+                addedCache, null /* defaultCategory */, outTiles, false /* requiresSettings */);
+        TileUtils.loadTilesForAction(mContext, new UserHandle(10), IA_SETTINGS_ACTION,
+                addedCache, null /* defaultCategory */, outTiles, false /* requiresSettings */);
+
+        assertThat(outTiles).hasSize(1);
+        assertThat(outTiles.get(0).pendingIntentMap).containsExactly(
+                UserHandle.CURRENT, pendingIntent, new UserHandle(10), pendingIntent);
+    }
+
     public static ResolveInfo newInfo(boolean systemApp, String category) {
         return newInfo(systemApp, category, null);
     }
@@ -424,7 +473,7 @@
         private static Bundle sMetaData;
 
         @Implementation
-        protected static List<Bundle> getSwitchDataFromProvider(Context context, String authority) {
+        protected static List<Bundle> getEntryDataFromProvider(Context context, String authority) {
             return Arrays.asList(sMetaData);
         }
 
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaDeviceTest.java
index 19a3db2..a072c17 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaDeviceTest.java
@@ -65,8 +65,7 @@
         MockitoAnnotations.initMocks(this);
         mContext = RuntimeEnvironment.application;
 
-        mInfoMediaDevice = new InfoMediaDevice(mContext, mRouterManager, mRouteInfo,
-                TEST_PACKAGE_NAME);
+        mInfoMediaDevice = new InfoMediaDevice(mContext, mRouteInfo, TEST_PACKAGE_NAME);
     }
 
     @Test
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
index 7b8815e..866ef9d 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
@@ -96,7 +96,7 @@
     @Mock
     private ComponentName mComponentName;
 
-    private InfoMediaManager mInfoMediaManager;
+    private ManagerInfoMediaManager mInfoMediaManager;
     private Context mContext;
     private ShadowRouter2Manager mShadowRouter2Manager;
 
@@ -108,7 +108,8 @@
         doReturn(mMediaSessionManager).when(mContext).getSystemService(
                 Context.MEDIA_SESSION_SERVICE);
         mInfoMediaManager =
-                new InfoMediaManager(mContext, TEST_PACKAGE_NAME, null, mLocalBluetoothManager);
+                new ManagerInfoMediaManager(
+                        mContext, TEST_PACKAGE_NAME, null, mLocalBluetoothManager);
         mShadowRouter2Manager = ShadowRouter2Manager.getShadow();
         mInfoMediaManager.mRouterManager = MediaRouter2Manager.getInstance(mContext);
     }
@@ -426,7 +427,7 @@
         final MediaRoute2Info availableInfo4 = mock(MediaRoute2Info.class);
         when(availableInfo4.getId()).thenReturn(TEST_ID_1);
         when(availableInfo4.isSystemRoute()).thenReturn(true);
-        when(availableInfo4.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME);
+        when(availableInfo4.getClientPackageName()).thenReturn(packageName);
         availableRoutes.add(availableInfo4);
 
         when(mRouterManager.getAvailableRoutes(packageName)).thenReturn(availableRoutes);
@@ -546,8 +547,7 @@
     @Test
     public void connectDeviceWithoutPackageName_noSession_returnFalse() {
         final MediaRoute2Info info = mock(MediaRoute2Info.class);
-        final MediaDevice device = new InfoMediaDevice(mContext, mInfoMediaManager.mRouterManager,
-                info, TEST_PACKAGE_NAME);
+        final MediaDevice device = new InfoMediaDevice(mContext, info, TEST_PACKAGE_NAME);
 
         final List<RoutingSessionInfo> infos = new ArrayList<>();
 
@@ -623,9 +623,7 @@
         routingSessionInfos.add(info);
 
         final MediaRoute2Info route2Info = mock(MediaRoute2Info.class);
-        final MediaDevice device =
-                new InfoMediaDevice(mContext, mInfoMediaManager.mRouterManager, route2Info,
-                        TEST_PACKAGE_NAME);
+        final MediaDevice device = new InfoMediaDevice(mContext, route2Info, TEST_PACKAGE_NAME);
 
         final List<String> list = new ArrayList<>();
         list.add(TEST_ID);
@@ -646,9 +644,7 @@
         routingSessionInfos.add(info);
 
         final MediaRoute2Info route2Info = mock(MediaRoute2Info.class);
-        final MediaDevice device =
-                new InfoMediaDevice(mContext, mInfoMediaManager.mRouterManager, route2Info,
-                        TEST_PACKAGE_NAME);
+        final MediaDevice device = new InfoMediaDevice(mContext, route2Info, TEST_PACKAGE_NAME);
 
         final List<String> list = new ArrayList<>();
         list.add("fake_id");
@@ -678,9 +674,7 @@
         routingSessionInfos.add(info);
 
         final MediaRoute2Info route2Info = mock(MediaRoute2Info.class);
-        final MediaDevice device =
-                new InfoMediaDevice(mContext, mInfoMediaManager.mRouterManager, route2Info,
-                        TEST_PACKAGE_NAME);
+        final MediaDevice device = new InfoMediaDevice(mContext, route2Info, TEST_PACKAGE_NAME);
 
         final List<String> list = new ArrayList<>();
         list.add(TEST_ID);
@@ -701,9 +695,7 @@
         routingSessionInfos.add(info);
 
         final MediaRoute2Info route2Info = mock(MediaRoute2Info.class);
-        final MediaDevice device =
-                new InfoMediaDevice(mContext, mInfoMediaManager.mRouterManager, route2Info,
-                        TEST_PACKAGE_NAME);
+        final MediaDevice device = new InfoMediaDevice(mContext, route2Info, TEST_PACKAGE_NAME);
 
         final List<String> list = new ArrayList<>();
         list.add("fake_id");
@@ -722,7 +714,7 @@
     public void getSelectableMediaDevice_packageNameIsNull_returnFalse() {
         mInfoMediaManager.mPackageName = null;
 
-        assertThat(mInfoMediaManager.getSelectableMediaDevice()).isEmpty();
+        assertThat(mInfoMediaManager.getSelectableMediaDevices()).isEmpty();
     }
 
     @Test
@@ -734,14 +726,14 @@
         mShadowRouter2Manager.setRoutingSessions(routingSessionInfos);
         when(info.getClientPackageName()).thenReturn("com.fake.packagename");
 
-        assertThat(mInfoMediaManager.getSelectableMediaDevice()).isEmpty();
+        assertThat(mInfoMediaManager.getSelectableMediaDevices()).isEmpty();
     }
 
     @Test
     public void getDeselectableMediaDevice_packageNameIsNull_returnFalse() {
         mInfoMediaManager.mPackageName = null;
 
-        assertThat(mInfoMediaManager.getDeselectableMediaDevice()).isEmpty();
+        assertThat(mInfoMediaManager.getDeselectableMediaDevices()).isEmpty();
     }
 
     @Test
@@ -757,7 +749,7 @@
         when(mediaRoute2Info.getName()).thenReturn(TEST_NAME);
         when(mediaRoute2Info.getId()).thenReturn(TEST_ID);
 
-        final List<MediaDevice> mediaDevices = mInfoMediaManager.getDeselectableMediaDevice();
+        final List<MediaDevice> mediaDevices = mInfoMediaManager.getDeselectableMediaDevices();
 
         assertThat(mediaDevices.size()).isEqualTo(1);
         assertThat(mediaDevices.get(0).getName()).isEqualTo(TEST_NAME);
@@ -851,7 +843,7 @@
         mShadowRouter2Manager.setSystemRoutingSession(sysSessionInfo);
         mShadowRouter2Manager.setRemoteSessions(infos);
 
-        assertThat(mInfoMediaManager.getActiveMediaSession())
+        assertThat(mInfoMediaManager.getActiveRoutingSessions())
                 .containsExactlyElementsIn(activeSessionInfos);
     }
 
@@ -1091,51 +1083,4 @@
         assertThat(device.getState()).isEqualTo(STATE_SELECTED);
         assertThat(mInfoMediaManager.getCurrentConnectedDevice()).isEqualTo(device);
     }
-
-    @Test
-    public void shouldDisableMediaOutput_infosIsEmpty_returnsTrue() {
-        mShadowRouter2Manager.setTransferableRoutes(new ArrayList<>());
-
-        assertThat(mInfoMediaManager.shouldDisableMediaOutput("test")).isTrue();
-    }
-
-    @Test
-    public void shouldDisableMediaOutput_infosSizeEqual1_returnsFalse() {
-        final MediaRoute2Info info = mock(MediaRoute2Info.class);
-        final List<MediaRoute2Info> infos = new ArrayList<>();
-        infos.add(info);
-        mShadowRouter2Manager.setTransferableRoutes(infos);
-
-        when(info.getType()).thenReturn(TYPE_REMOTE_SPEAKER);
-
-        assertThat(mInfoMediaManager.shouldDisableMediaOutput("test")).isFalse();
-    }
-
-    @Test
-    public void shouldDisableMediaOutput_infosSizeEqual1AndNotCastDevice_returnsFalse() {
-        final MediaRoute2Info info = mock(MediaRoute2Info.class);
-        final List<MediaRoute2Info> infos = new ArrayList<>();
-        infos.add(info);
-        mShadowRouter2Manager.setTransferableRoutes(infos);
-
-        when(info.getType()).thenReturn(TYPE_BUILTIN_SPEAKER);
-
-        assertThat(mInfoMediaManager.shouldDisableMediaOutput("test")).isFalse();
-    }
-
-
-    @Test
-    public void shouldDisableMediaOutput_infosSizeOverThan1_returnsFalse() {
-        final MediaRoute2Info info = mock(MediaRoute2Info.class);
-        final MediaRoute2Info info2 = mock(MediaRoute2Info.class);
-        final List<MediaRoute2Info> infos = new ArrayList<>();
-        infos.add(info);
-        infos.add(info2);
-        mShadowRouter2Manager.setTransferableRoutes(infos);
-
-        when(info.getType()).thenReturn(TYPE_REMOTE_SPEAKER);
-        when(info2.getType()).thenReturn(TYPE_REMOTE_SPEAKER);
-
-        assertThat(mInfoMediaManager.shouldDisableMediaOutput("test")).isFalse();
-    }
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
index d87fc54..d6c33ff 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
@@ -116,10 +116,8 @@
         when(mLocalProfileManager.getA2dpProfile()).thenReturn(mA2dpProfile);
         when(mLocalProfileManager.getHearingAidProfile()).thenReturn(mHapProfile);
 
-        mInfoMediaDevice1 = spy(new InfoMediaDevice(mContext, mMediaRouter2Manager, mRouteInfo1,
-                TEST_PACKAGE_NAME));
-        mInfoMediaDevice2 = new InfoMediaDevice(mContext, mMediaRouter2Manager, mRouteInfo2,
-                TEST_PACKAGE_NAME);
+        mInfoMediaDevice1 = spy(new InfoMediaDevice(mContext, mRouteInfo1, TEST_PACKAGE_NAME));
+        mInfoMediaDevice2 = new InfoMediaDevice(mContext, mRouteInfo2, TEST_PACKAGE_NAME);
         mLocalMediaManager = new LocalMediaManager(mContext, mLocalBluetoothManager,
                 mInfoMediaManager, "com.test.packagename");
         mLocalMediaManager.mAudioManager = mAudioManager;
@@ -150,7 +148,7 @@
         assertThat(mLocalMediaManager.connectDevice(device)).isTrue();
 
         verify(currentDevice).disconnect();
-        verify(device).connect();
+        verify(mInfoMediaManager).connectToDevice(device);
     }
 
     @Test
@@ -435,7 +433,7 @@
         final RoutingSessionInfo info = mock(RoutingSessionInfo.class);
         when(info.getId()).thenReturn(TEST_SESSION_ID);
         routingSessionInfos.add(info);
-        when(mInfoMediaManager.getActiveMediaSession()).thenReturn(routingSessionInfos);
+        when(mInfoMediaManager.getActiveRoutingSessions()).thenReturn(routingSessionInfos);
 
         assertThat(mLocalMediaManager.getActiveMediaSession().get(0).getId())
                 .matches(TEST_SESSION_ID);
@@ -508,7 +506,7 @@
         devices.add(currentDevice);
         mLocalMediaManager.mMediaDeviceCallback.onDeviceListAdded(devices);
 
-        verify(mInfoMediaDevice1).connect();
+        verify(mInfoMediaManager).connectToDevice(mInfoMediaDevice1);
     }
 
     @Test
@@ -546,7 +544,7 @@
         final RoutingSessionInfo info = mock(RoutingSessionInfo.class);
         when(info.getId()).thenReturn(TEST_SESSION_ID);
         routingSessionInfos.add(info);
-        when(mInfoMediaManager.getActiveMediaSession()).thenReturn(routingSessionInfos);
+        when(mInfoMediaManager.getActiveRoutingSessions()).thenReturn(routingSessionInfos);
 
         mLocalMediaManager.adjustSessionVolume(TEST_SESSION_ID, 10);
 
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java
index f22e090..18055d9 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java
@@ -26,14 +26,12 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothDevice;
 import android.content.Context;
 import android.media.MediaRoute2Info;
-import android.media.MediaRouter2Manager;
 import android.media.NearbyDevice;
 import android.media.RouteListingPreference;
 import android.os.Parcel;
@@ -111,9 +109,6 @@
     private A2dpProfile mA2dpProfile;
     @Mock
     private BluetoothDevice mDevice;
-    @Mock
-    private MediaRouter2Manager mMediaRouter2Manager;
-
     private RouteListingPreference.Item mItem;
 
     private BluetoothMediaDevice mBluetoothMediaDevice1;
@@ -175,23 +170,18 @@
         when(mA2dpProfile.getActiveDevice()).thenReturn(mDevice);
 
         mBluetoothMediaDevice1 =
-                new BluetoothMediaDevice(mContext, mCachedDevice1, mMediaRouter2Manager,
-                        mBluetoothRouteInfo1, TEST_PACKAGE_NAME);
+                new BluetoothMediaDevice(
+                        mContext, mCachedDevice1, mBluetoothRouteInfo1, TEST_PACKAGE_NAME);
         mBluetoothMediaDevice2 =
-                new BluetoothMediaDevice(mContext, mCachedDevice2, mMediaRouter2Manager,
-                        mBluetoothRouteInfo2, TEST_PACKAGE_NAME);
+                new BluetoothMediaDevice(
+                        mContext, mCachedDevice2, mBluetoothRouteInfo2, TEST_PACKAGE_NAME);
         mBluetoothMediaDevice3 =
-                new BluetoothMediaDevice(mContext, mCachedDevice3, mMediaRouter2Manager,
-                        mBluetoothRouteInfo3, TEST_PACKAGE_NAME);
-        mInfoMediaDevice1 = new InfoMediaDevice(mContext, mMediaRouter2Manager, mRouteInfo1,
-                TEST_PACKAGE_NAME);
-        mInfoMediaDevice2 = new InfoMediaDevice(mContext, mMediaRouter2Manager, mRouteInfo2,
-                TEST_PACKAGE_NAME);
-        mInfoMediaDevice3 = new InfoMediaDevice(mContext, mMediaRouter2Manager, mRouteInfo3,
-                TEST_PACKAGE_NAME);
-        mPhoneMediaDevice =
-                new PhoneMediaDevice(mContext, mMediaRouter2Manager, mPhoneRouteInfo,
-                        TEST_PACKAGE_NAME);
+                new BluetoothMediaDevice(
+                        mContext, mCachedDevice3, mBluetoothRouteInfo3, TEST_PACKAGE_NAME);
+        mInfoMediaDevice1 = new InfoMediaDevice(mContext, mRouteInfo1, TEST_PACKAGE_NAME);
+        mInfoMediaDevice2 = new InfoMediaDevice(mContext, mRouteInfo2, TEST_PACKAGE_NAME);
+        mInfoMediaDevice3 = new InfoMediaDevice(mContext, mRouteInfo3, TEST_PACKAGE_NAME);
+        mPhoneMediaDevice = new PhoneMediaDevice(mContext, mPhoneRouteInfo, TEST_PACKAGE_NAME);
     }
 
     @Test
@@ -257,7 +247,7 @@
     public void compareTo_lastSelected_others_lastSelectedFirst() {
         mMediaDevices.add(mBluetoothMediaDevice1);
         mMediaDevices.add(mBluetoothMediaDevice2);
-        mBluetoothMediaDevice2.connect();
+        mBluetoothMediaDevice2.setConnectedRecord();
 
         assertThat(mMediaDevices.get(0)).isEqualTo(mBluetoothMediaDevice1);
         Collections.sort(mMediaDevices, COMPARATOR);
@@ -268,9 +258,9 @@
     public void compareTo_connectionRecord_sortByRecord() {
         mMediaDevices.add(mBluetoothMediaDevice1);
         mMediaDevices.add(mBluetoothMediaDevice2);
-        mBluetoothMediaDevice1.connect();
-        mBluetoothMediaDevice2.connect();
-        mBluetoothMediaDevice2.connect();
+        mBluetoothMediaDevice1.setConnectedRecord();
+        mBluetoothMediaDevice2.setConnectedRecord();
+        mBluetoothMediaDevice2.setConnectedRecord();
         // Reset last selected record
         ConnectionRecordManager.getInstance().setConnectionRecord(mContext, null, 0);
 
@@ -287,9 +277,9 @@
         mMediaDevices.add(mBluetoothMediaDevice2);
         when(mCachedDevice2.isConnected()).thenReturn(false);
 
-        mBluetoothMediaDevice1.connect();
-        mBluetoothMediaDevice2.connect();
-        mBluetoothMediaDevice2.connect();
+        mBluetoothMediaDevice1.setConnectedRecord();
+        mBluetoothMediaDevice2.setConnectedRecord();
+        mBluetoothMediaDevice2.setConnectedRecord();
         // Reset last selected record
         ConnectionRecordManager.getInstance().setConnectionRecord(mContext, null, 0);
 
@@ -325,8 +315,8 @@
         final MediaRoute2Info phoneRouteInfo = mock(MediaRoute2Info.class);
         when(phoneRouteInfo.getType()).thenReturn(TYPE_WIRED_HEADPHONES);
 
-        final PhoneMediaDevice phoneMediaDevice = new PhoneMediaDevice(mContext,
-                mMediaRouter2Manager, phoneRouteInfo, TEST_PACKAGE_NAME);
+        final PhoneMediaDevice phoneMediaDevice =
+                new PhoneMediaDevice(mContext, phoneRouteInfo, TEST_PACKAGE_NAME);
 
         mMediaDevices.add(mBluetoothMediaDevice1);
         mMediaDevices.add(phoneMediaDevice);
@@ -341,8 +331,8 @@
         final MediaRoute2Info phoneRouteInfo = mock(MediaRoute2Info.class);
         when(phoneRouteInfo.getType()).thenReturn(TYPE_WIRED_HEADPHONES);
 
-        final PhoneMediaDevice phoneMediaDevice = new PhoneMediaDevice(mContext,
-                mMediaRouter2Manager, phoneRouteInfo, TEST_PACKAGE_NAME);
+        final PhoneMediaDevice phoneMediaDevice =
+                new PhoneMediaDevice(mContext, phoneRouteInfo, TEST_PACKAGE_NAME);
 
         mMediaDevices.add(mInfoMediaDevice1);
         mMediaDevices.add(phoneMediaDevice);
@@ -403,13 +393,13 @@
         mMediaDevices.add(mInfoMediaDevice2);
         mMediaDevices.add(mInfoMediaDevice3);
         mMediaDevices.add(mPhoneMediaDevice);
-        mBluetoothMediaDevice3.connect();
-        mBluetoothMediaDevice2.connect();
-        mBluetoothMediaDevice2.connect();
-        mInfoMediaDevice3.connect();
-        mInfoMediaDevice2.connect();
-        mInfoMediaDevice2.connect();
-        mInfoMediaDevice1.connect();
+        mBluetoothMediaDevice3.setConnectedRecord();
+        mBluetoothMediaDevice2.setConnectedRecord();
+        mBluetoothMediaDevice2.setConnectedRecord();
+        mInfoMediaDevice3.setConnectedRecord();
+        mInfoMediaDevice2.setConnectedRecord();
+        mInfoMediaDevice2.setConnectedRecord();
+        mInfoMediaDevice1.setConnectedRecord();
 
         Collections.sort(mMediaDevices, COMPARATOR);
         assertThat(mMediaDevices.get(0)).isEqualTo(mPhoneMediaDevice);
@@ -443,15 +433,15 @@
         mMediaDevices.add(mInfoMediaDevice2);
         mMediaDevices.add(mInfoMediaDevice3);
         mMediaDevices.add(mPhoneMediaDevice);
-        mBluetoothMediaDevice3.connect();
-        mBluetoothMediaDevice2.connect();
-        mBluetoothMediaDevice2.connect();
-        mBluetoothMediaDevice2.connect();
-        mBluetoothMediaDevice2.connect();
-        mInfoMediaDevice3.connect();
-        mInfoMediaDevice2.connect();
-        mInfoMediaDevice2.connect();
-        mInfoMediaDevice1.connect();
+        mBluetoothMediaDevice3.setConnectedRecord();
+        mBluetoothMediaDevice2.setConnectedRecord();
+        mBluetoothMediaDevice2.setConnectedRecord();
+        mBluetoothMediaDevice2.setConnectedRecord();
+        mBluetoothMediaDevice2.setConnectedRecord();
+        mInfoMediaDevice3.setConnectedRecord();
+        mInfoMediaDevice2.setConnectedRecord();
+        mInfoMediaDevice2.setConnectedRecord();
+        mInfoMediaDevice1.setConnectedRecord();
 
         Collections.sort(mMediaDevices, COMPARATOR);
         assertThat(mMediaDevices.get(0)).isEqualTo(mPhoneMediaDevice);
@@ -464,13 +454,6 @@
     }
 
     @Test
-    public void connect_shouldSelectRoute() {
-        mInfoMediaDevice1.connect();
-
-        verify(mMediaRouter2Manager).transfer(TEST_PACKAGE_NAME, mRouteInfo1);
-    }
-
-    @Test
     public void getClientPackageName_returnPackageName() {
         when(mRouteInfo1.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME);
 
@@ -498,8 +481,9 @@
 
     @Test
     public void getFeatures_noRouteInfo_returnEmptyList() {
-        mBluetoothMediaDevice1 = new BluetoothMediaDevice(mContext, mCachedDevice1,
-                mMediaRouter2Manager, null /* MediaRoute2Info */, TEST_PACKAGE_NAME);
+        mBluetoothMediaDevice1 =
+                new BluetoothMediaDevice(
+                        mContext, mCachedDevice1, null /* MediaRoute2Info */, TEST_PACKAGE_NAME);
 
         assertThat(mBluetoothMediaDevice1.getFeatures().size()).isEqualTo(0);
     }
@@ -509,11 +493,15 @@
         mItem = new RouteListingPreference.Item.Builder(DEVICE_ADDRESS_1)
                 .setSelectionBehavior(SELECTION_BEHAVIOR_GO_TO_APP)
                 .build();
-        mBluetoothMediaDevice1 = new BluetoothMediaDevice(mContext, mCachedDevice1,
-                mMediaRouter2Manager, null /* MediaRoute2Info */, TEST_PACKAGE_NAME, mItem);
+        mBluetoothMediaDevice1 =
+                new BluetoothMediaDevice(
+                        mContext,
+                        mCachedDevice1,
+                        null /* MediaRoute2Info */,
+                        TEST_PACKAGE_NAME,
+                        mItem);
         mPhoneMediaDevice =
-                new PhoneMediaDevice(mContext, mMediaRouter2Manager, mPhoneRouteInfo,
-                        TEST_PACKAGE_NAME, mItem);
+                new PhoneMediaDevice(mContext, mPhoneRouteInfo, TEST_PACKAGE_NAME, mItem);
 
         assertThat(mBluetoothMediaDevice1.getSelectionBehavior()).isEqualTo(
                 SELECTION_BEHAVIOR_TRANSFER);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java
index 00d1f76..1746bef 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java
@@ -56,8 +56,7 @@
         MockitoAnnotations.initMocks(this);
         mContext = RuntimeEnvironment.application;
 
-        mPhoneMediaDevice =
-                new PhoneMediaDevice(mContext, null, mInfo, null);
+        mPhoneMediaDevice = new PhoneMediaDevice(mContext, mInfo, null);
     }
 
     @Test
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/DataUsageUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/DataUsageUtilsTest.java
deleted file mode 100644
index 508dffc..0000000
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/DataUsageUtilsTest.java
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.net;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.Mockito.anyInt;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-import android.net.NetworkTemplate;
-import android.os.ParcelUuid;
-import android.os.RemoteException;
-import android.telephony.SubscriptionInfo;
-import android.telephony.SubscriptionManager;
-import android.telephony.TelephonyManager;
-
-import org.junit.Before;
-import org.junit.Ignore;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
-
-import java.util.List;
-
-@RunWith(RobolectricTestRunner.class)
-public class DataUsageUtilsTest {
-
-    private static final int SUB_ID = 1;
-    private static final int SUB_ID_2 = 2;
-    private static final String SUBSCRIBER_ID = "Test Subscriber";
-    private static final String SUBSCRIBER_ID_2 = "Test Subscriber 2";
-
-    @Mock
-    private TelephonyManager mTelephonyManager;
-    @Mock
-    private SubscriptionManager mSubscriptionManager;
-    @Mock
-    private SubscriptionInfo mInfo1;
-    @Mock
-    private SubscriptionInfo mInfo2;
-    @Mock
-    private ParcelUuid mParcelUuid;
-    private Context mContext;
-    private List<SubscriptionInfo> mInfos;
-
-    @Before
-    public void setUp() throws RemoteException {
-        MockitoAnnotations.initMocks(this);
-
-        mContext = spy(RuntimeEnvironment.application);
-        when(mContext.getSystemService(TelephonyManager.class)).thenReturn(mTelephonyManager);
-        when(mContext.getSystemService(SubscriptionManager.class)).thenReturn(mSubscriptionManager);
-        when(mTelephonyManager.getSubscriberId(SUB_ID)).thenReturn(SUBSCRIBER_ID);
-        when(mTelephonyManager.getSubscriberId(SUB_ID_2)).thenReturn(SUBSCRIBER_ID_2);
-        when(mTelephonyManager.createForSubscriptionId(anyInt())).thenReturn(mTelephonyManager);
-        when(mSubscriptionManager.isActiveSubscriptionId(anyInt())).thenReturn(true);
-    }
-
-    @Test
-    @Ignore
-    public void getMobileTemplate_infoNull_returnMobileAll() {
-        when(mSubscriptionManager.isActiveSubscriptionId(SUB_ID)).thenReturn(false);
-
-        final NetworkTemplate networkTemplate = DataUsageUtils.getMobileTemplate(mContext, SUB_ID);
-        assertThat(networkTemplate.getSubscriberIds().contains(SUBSCRIBER_ID)).isTrue();
-        assertThat(networkTemplate.getSubscriberIds().contains(SUBSCRIBER_ID_2)).isFalse();
-    }
-
-    @Test
-    @Ignore
-    public void getMobileTemplate_groupUuidNull_returnMobileAll() {
-        when(mSubscriptionManager.getActiveSubscriptionInfo(SUB_ID)).thenReturn(mInfo1);
-        when(mInfo1.getGroupUuid()).thenReturn(null);
-        when(mTelephonyManager.getMergedImsisFromGroup())
-                .thenReturn(new String[] {SUBSCRIBER_ID});
-
-        final NetworkTemplate networkTemplate = DataUsageUtils.getMobileTemplate(mContext, SUB_ID);
-        assertThat(networkTemplate.getSubscriberIds().contains(SUBSCRIBER_ID)).isTrue();
-        assertThat(networkTemplate.getSubscriberIds().contains(SUBSCRIBER_ID_2)).isFalse();
-    }
-
-    @Test
-    @Ignore
-    public void getMobileTemplate_groupUuidExist_returnMobileMerged() {
-        when(mSubscriptionManager.getActiveSubscriptionInfo(SUB_ID)).thenReturn(mInfo1);
-        when(mInfo1.getGroupUuid()).thenReturn(mParcelUuid);
-        when(mTelephonyManager.getMergedImsisFromGroup())
-                .thenReturn(new String[] {SUBSCRIBER_ID, SUBSCRIBER_ID_2});
-
-        final NetworkTemplate networkTemplate = DataUsageUtils.getMobileTemplate(mContext, SUB_ID);
-        assertThat(networkTemplate.getSubscriberIds().contains(SUBSCRIBER_ID)).isTrue();
-        assertThat(networkTemplate.getSubscriberIds().contains(SUBSCRIBER_ID_2)).isTrue();
-    }
-}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/CreateUserDialogControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/CreateUserDialogControllerTest.java
index e989ed2..b538077 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/CreateUserDialogControllerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/CreateUserDialogControllerTest.java
@@ -111,13 +111,13 @@
                 mActivityStarter, true, null,
                 cancelCallback);
         dialog.show();
-        assertThat(dialog.findViewById(R.id.user_info_scroll).getVisibility()).isEqualTo(View.GONE);
+        assertThat(dialog.findViewById(R.id.user_info_editor).getVisibility()).isEqualTo(View.GONE);
         Button next = dialog.findViewById(R.id.button_ok);
         next.performClick();
         ((RadioButton) dialog.findViewById(R.id.grant_admin_yes)).setChecked(true);
-        assertThat(dialog.findViewById(R.id.user_info_scroll).getVisibility()).isEqualTo(View.GONE);
+        assertThat(dialog.findViewById(R.id.user_info_editor).getVisibility()).isEqualTo(View.GONE);
         next.performClick();
-        assertThat(dialog.findViewById(R.id.user_info_scroll).getVisibility())
+        assertThat(dialog.findViewById(R.id.user_info_editor).getVisibility())
                 .isEqualTo(View.VISIBLE);
         dialog.dismiss();
     }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/EditUserInfoControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/EditUserInfoControllerTest.java
index f760032..f595cd3 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/EditUserInfoControllerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/EditUserInfoControllerTest.java
@@ -27,7 +27,6 @@
 
 import android.app.Activity;
 import android.app.AlertDialog;
-import android.app.Dialog;
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.drawable.Drawable;
@@ -108,7 +107,7 @@
     @Test
     public void photoControllerOnActivityResult_whenWaiting_isCalled() {
         mController.createDialog(mActivity, mActivityStarter, mCurrentIcon, "test user",
-                "title", null, null);
+                null, null);
         mController.startingActivityForResult();
         Intent resultData = new Intent();
         mController.onActivityResult(0, 0, resultData);
@@ -126,9 +125,7 @@
                 () -> String.valueOf('A')).limit(200).collect(Collectors.joining());
 
         final AlertDialog dialog = (AlertDialog) mController.createDialog(mActivity,
-                mActivityStarter, mCurrentIcon,
-                "test user", "title", null,
-                null);
+                mActivityStarter, mCurrentIcon, "test user", null, null);
         dialog.show();
         final EditText userNameEditText = dialog.findViewById(R.id.user_name);
         userNameEditText.setText(longName);
@@ -143,7 +140,7 @@
 
         AlertDialog dialog = (AlertDialog) mController.createDialog(
                 mActivity, mActivityStarter, mCurrentIcon, "test",
-                "title", successCallback, cancelCallback);
+                successCallback, cancelCallback);
         dialog.show();
         dialog.cancel();
 
@@ -159,9 +156,9 @@
 
         AlertDialog dialog = (AlertDialog) mController.createDialog(
                 mActivity, mActivityStarter, mCurrentIcon, "test",
-                "title", successCallback, cancelCallback);
+                successCallback, cancelCallback);
         dialog.show();
-        dialog.getButton(Dialog.BUTTON_NEGATIVE).performClick();
+        dialog.findViewById(R.id.button_back).performClick();
 
         verifyNoInteractions(successCallback);
         verify(cancelCallback, times(1))
@@ -176,11 +173,11 @@
         Drawable oldUserIcon = mCurrentIcon;
         AlertDialog dialog = (AlertDialog) mController.createDialog(
                 mActivity, mActivityStarter, oldUserIcon, "test",
-                "title", successCallback, cancelCallback);
+                successCallback, cancelCallback);
         // No change to the photo.
         when(mController.getPhotoController().getNewUserPhotoDrawable()).thenReturn(null);
         dialog.show();
-        dialog.getButton(Dialog.BUTTON_POSITIVE).performClick();
+        dialog.findViewById(R.id.button_ok).performClick();
 
         verify(successCallback, times(1))
                 .accept("test", oldUserIcon);
@@ -194,11 +191,11 @@
 
         AlertDialog dialog = (AlertDialog) mController.createDialog(
                 mActivity, mActivityStarter, null, "test",
-                "title", successCallback, cancelCallback);
+                successCallback, cancelCallback);
         // No change to the photo.
         when(mController.getPhotoController().getNewUserPhotoDrawable()).thenReturn(null);
         dialog.show();
-        dialog.getButton(Dialog.BUTTON_POSITIVE).performClick();
+        dialog.findViewById(R.id.button_ok).performClick();
 
         verify(successCallback, times(1))
                 .accept("test", null);
@@ -212,14 +209,14 @@
 
         AlertDialog dialog = (AlertDialog) mController.createDialog(
                 mActivity, mActivityStarter, mCurrentIcon, "test",
-                "title", successCallback, cancelCallback);
+                successCallback, cancelCallback);
         // No change to the photo.
         when(mController.getPhotoController().getNewUserPhotoDrawable()).thenReturn(null);
         dialog.show();
         String expectedNewName = "new test user";
         EditText editText = (EditText) dialog.findViewById(R.id.user_name);
         editText.setText(expectedNewName);
-        dialog.getButton(Dialog.BUTTON_POSITIVE).performClick();
+        dialog.findViewById(R.id.button_ok).performClick();
 
         verify(successCallback, times(1))
                 .accept(expectedNewName, mCurrentIcon);
@@ -233,12 +230,12 @@
 
         AlertDialog dialog = (AlertDialog) mController.createDialog(
                 mActivity, mActivityStarter, mCurrentIcon, "test",
-                "title", successCallback, cancelCallback);
+                successCallback, cancelCallback);
         // A different drawable.
         Drawable newPhoto = mock(Drawable.class);
         when(mController.getPhotoController().getNewUserPhotoDrawable()).thenReturn(newPhoto);
         dialog.show();
-        dialog.getButton(Dialog.BUTTON_POSITIVE).performClick();
+        dialog.findViewById(R.id.button_ok).performClick();
 
         verify(successCallback, times(1))
                 .accept("test", newPhoto);
@@ -252,12 +249,12 @@
 
         AlertDialog dialog = (AlertDialog) mController.createDialog(
                 mActivity, mActivityStarter, null, "test",
-                "title", successCallback, cancelCallback);
+                 successCallback, cancelCallback);
         // A different drawable.
         Drawable newPhoto = mock(Drawable.class);
         when(mController.getPhotoController().getNewUserPhotoDrawable()).thenReturn(newPhoto);
         dialog.show();
-        dialog.getButton(Dialog.BUTTON_POSITIVE).performClick();
+        dialog.findViewById(R.id.button_ok).performClick();
 
         verify(successCallback, times(1))
                 .accept("test", newPhoto);
@@ -269,7 +266,7 @@
         mPhotoRestrictedByBase = true;
 
         mController.createDialog(mActivity, mActivityStarter, mCurrentIcon,
-                "test", "title", null, null);
+                "test", null, null);
 
         assertThat(mController.mPhotoController).isNull();
     }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/UpdatableListPreferenceDialogFragmentTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/UpdatableListPreferenceDialogFragmentTest.java
deleted file mode 100644
index ca0aa0d..0000000
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/UpdatableListPreferenceDialogFragmentTest.java
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.widget;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-
-import android.content.Context;
-import android.widget.ArrayAdapter;
-
-import androidx.preference.ListPreference;
-
-import com.android.internal.logging.nano.MetricsProto;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
-
-import java.util.ArrayList;
-
-@RunWith(RobolectricTestRunner.class)
-public class UpdatableListPreferenceDialogFragmentTest {
-
-    private static final String KEY = "Test_Key";
-    @Mock
-    private UpdatableListPreferenceDialogFragment mUpdatableListPrefDlgFragment;
-    private Context mContext;
-    private ArrayAdapter mAdapter;
-    private ArrayList<CharSequence> mEntries;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-        mContext = RuntimeEnvironment.application;
-
-        mUpdatableListPrefDlgFragment = spy(UpdatableListPreferenceDialogFragment
-                .newInstance(KEY, MetricsProto.MetricsEvent.DIALOG_SWITCH_A2DP_DEVICES));
-        mEntries = new ArrayList<>();
-        mUpdatableListPrefDlgFragment.setEntries(mEntries);
-        mUpdatableListPrefDlgFragment
-                .setMetricsCategory(mUpdatableListPrefDlgFragment.getArguments());
-        initAdapter();
-    }
-
-    private void initAdapter() {
-        mAdapter = spy(new ArrayAdapter<>(
-                mContext,
-                com.android.internal.R.layout.select_dialog_singlechoice,
-                mEntries));
-        mUpdatableListPrefDlgFragment.setAdapter(mAdapter);
-    }
-
-    @Test
-    public void getMetricsCategory() {
-        assertThat(mUpdatableListPrefDlgFragment.getMetricsCategory())
-                .isEqualTo(MetricsProto.MetricsEvent.DIALOG_SWITCH_A2DP_DEVICES);
-    }
-
-    @Test
-    public void onListPreferenceUpdated_verifyAdapterCanBeUpdate() {
-        assertThat(mUpdatableListPrefDlgFragment.getAdapter().getCount()).isEqualTo(0);
-
-        ListPreference listPreference = new ListPreference(mContext);
-        final CharSequence[] charSequences = {"Test_DEVICE_1", "Test_DEVICE_2"};
-        listPreference.setEntries(charSequences);
-        mUpdatableListPrefDlgFragment.onListPreferenceUpdated(listPreference);
-
-        assertThat(mUpdatableListPrefDlgFragment.getAdapter().getCount()).isEqualTo(2);
-    }
-
-    @Test
-    public void onDialogClosed_emptyPreference() {
-        mUpdatableListPrefDlgFragment.onDialogClosed(false);
-
-        verify(mUpdatableListPrefDlgFragment, never()).getListPreference();
-    }
-}
diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml
index a93cd62..fe51ed5 100644
--- a/packages/SettingsProvider/res/values/defaults.xml
+++ b/packages/SettingsProvider/res/values/defaults.xml
@@ -286,7 +286,7 @@
     <bool name="def_wearable_sideButtonPresent">true</bool>
 
     <!-- Android wear version. This value is a string due to no long type in resources -->
-    <string name="def_wearable_androidWearVersion" translatable="false">2</string>
+    <string name="def_wearable_androidWearVersion" translatable="false">4</string>
 
     <!-- This value is the decimal representation of the capabilities bitmask as defined below:
         0000001 - WIFI
@@ -321,4 +321,7 @@
 
     <!-- Whether vibrate icon is shown in the status bar by default. -->
     <integer name="def_statusBarVibrateIconEnabled">0</integer>
+
+    <!-- Whether predictive back animation is enabled by default. -->
+    <bool name="def_enable_back_animation">false</bool>
 </resources>
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 3efb41d..423c8a3 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -83,6 +83,8 @@
         Settings.Secure.DOUBLE_TAP_TO_WAKE,
         Settings.Secure.WAKE_GESTURE_ENABLED,
         Settings.Secure.LONG_PRESS_TIMEOUT,
+        Settings.Secure.KEY_REPEAT_TIMEOUT_MS,
+        Settings.Secure.KEY_REPEAT_DELAY_MS,
         Settings.Secure.CAMERA_GESTURE_DISABLED,
         Settings.Secure.ACCESSIBILITY_AUTOCLICK_ENABLED,
         Settings.Secure.ACCESSIBILITY_AUTOCLICK_DELAY,
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
index 6a5535d..6b0a906 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
@@ -61,6 +61,7 @@
         Settings.System.TTY_MODE,
         Settings.System.MASTER_MONO,
         Settings.System.MASTER_BALANCE,
+        Settings.System.STAY_AWAKE_ON_FOLD,
         Settings.System.SOUND_EFFECTS_ENABLED,
         Settings.System.HAPTIC_FEEDBACK_ENABLED,
         Settings.System.POWER_SOUNDS_ENABLED,       // moved to global
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
index a1c0172..e31a672 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
@@ -176,10 +176,21 @@
         VALIDATORS.put(Global.REQUIRE_PASSWORD_TO_DECRYPT, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Global.DEVICE_DEMO_MODE, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Global.AWARE_ALLOWED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Global.POWER_BUTTON_SHORT_PRESS, new InclusiveIntegerRangeValidator(0, 7));
+        VALIDATORS.put(Global.POWER_BUTTON_DOUBLE_PRESS, new InclusiveIntegerRangeValidator(0, 3));
+        VALIDATORS.put(Global.POWER_BUTTON_TRIPLE_PRESS, new InclusiveIntegerRangeValidator(0, 3));
         VALIDATORS.put(Global.POWER_BUTTON_LONG_PRESS, new InclusiveIntegerRangeValidator(0, 5));
         VALIDATORS.put(
                 Global.POWER_BUTTON_VERY_LONG_PRESS, new InclusiveIntegerRangeValidator(0, 1));
         VALIDATORS.put(Global.KEY_CHORD_POWER_VOLUME_UP, new InclusiveIntegerRangeValidator(0, 2));
+        VALIDATORS.put(
+                Global.STEM_PRIMARY_BUTTON_SHORT_PRESS, new InclusiveIntegerRangeValidator(0, 1));
+        VALIDATORS.put(
+                Global.STEM_PRIMARY_BUTTON_DOUBLE_PRESS, new InclusiveIntegerRangeValidator(0, 1));
+        VALIDATORS.put(
+                Global.STEM_PRIMARY_BUTTON_TRIPLE_PRESS, new InclusiveIntegerRangeValidator(0, 1));
+        VALIDATORS.put(
+                Global.STEM_PRIMARY_BUTTON_LONG_PRESS, new InclusiveIntegerRangeValidator(0, 1));
         VALIDATORS.put(Global.CUSTOM_BUGREPORT_HANDLER_APP, ANY_STRING_VALIDATOR);
         VALIDATORS.put(Global.CUSTOM_BUGREPORT_HANDLER_USER, ANY_INTEGER_VALIDATOR);
         VALIDATORS.put(Global.DEVELOPMENT_SETTINGS_ENABLED, BOOLEAN_VALIDATOR);
@@ -373,7 +384,7 @@
                 Global.Wearable.WEAR_ACTIVITY_AUTO_RESUME_TIMEOUT_SET_BY_USER,
                 BOOLEAN_VALIDATOR);
         VALIDATORS.put(Global.Wearable.BURN_IN_PROTECTION_ENABLED, BOOLEAN_VALIDATOR);
-        VALIDATORS.put(Global.Wearable.COMBINED_LOCATION_ENABLED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Global.Wearable.COMBINED_LOCATION_ENABLE, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Global.Wearable.WRIST_ORIENTATION_MODE,
                        new DiscreteValueValidator(new String[] {"0", "1", "2", "3"}));
         VALIDATORS.put(Global.USER_PREFERRED_REFRESH_RATE, NON_NEGATIVE_FLOAT_VALIDATOR);
@@ -405,7 +416,7 @@
         VALIDATORS.put(Global.Wearable.BEDTIME_MODE, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Global.Wearable.BEDTIME_HARD_MODE, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Global.Wearable.DYNAMIC_COLOR_THEME_ENABLED, BOOLEAN_VALIDATOR);
-	VALIDATORS.put(Global.Wearable.SCREENSHOT_ENABLED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Global.Wearable.SCREENSHOT_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Global.Wearable.UPGRADE_DATA_MIGRATION_STATUS,
                        new DiscreteValueValidator(
                         new String[] {
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index f6c2f69..f1efe9e 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -125,6 +125,8 @@
         VALIDATORS.put(Secure.DOUBLE_TAP_TO_WAKE, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.WAKE_GESTURE_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.LONG_PRESS_TIMEOUT, NON_NEGATIVE_INTEGER_VALIDATOR);
+        VALIDATORS.put(Secure.KEY_REPEAT_TIMEOUT_MS, NON_NEGATIVE_INTEGER_VALIDATOR);
+        VALIDATORS.put(Secure.KEY_REPEAT_DELAY_MS, NON_NEGATIVE_INTEGER_VALIDATOR);
         VALIDATORS.put(Secure.CAMERA_GESTURE_DISABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.ACCESSIBILITY_AUTOCLICK_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.ACCESSIBILITY_AUTOCLICK_DELAY, NON_NEGATIVE_INTEGER_VALIDATOR);
@@ -381,5 +383,7 @@
         VALIDATORS.put(Secure.HEARING_AID_SYSTEM_SOUNDS_ROUTING,
                 new DiscreteValueValidator(new String[] {"0", "1", "2"}));
         VALIDATORS.put(Secure.ACCESSIBILITY_FONT_SCALING_HAS_BEEN_CHANGED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_EDGE_HAPTIC_ENABLED,
+                BOOLEAN_VALIDATOR);
     }
 }
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
index 753c860..0dd8569 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
@@ -218,8 +218,10 @@
         VALIDATORS.put(System.WIFI_STATIC_DNS1, LENIENT_IP_ADDRESS_VALIDATOR);
         VALIDATORS.put(System.WIFI_STATIC_DNS2, LENIENT_IP_ADDRESS_VALIDATOR);
         VALIDATORS.put(System.SHOW_BATTERY_PERCENT, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(System.STAY_AWAKE_ON_FOLD, BOOLEAN_VALIDATOR);
         VALIDATORS.put(System.NOTIFICATION_LIGHT_PULSE, BOOLEAN_VALIDATOR);
         VALIDATORS.put(System.WEAR_ACCESSIBILITY_GESTURE_ENABLED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(System.WEAR_ACCESSIBILITY_GESTURE_ENABLED_DURING_OOBE, BOOLEAN_VALIDATOR);
         VALIDATORS.put(System.CLOCKWORK_BLUETOOTH_SETTINGS_PREF, BOOLEAN_VALIDATOR);
         VALIDATORS.put(System.UNREAD_NOTIFICATION_DOT_INDICATOR, BOOLEAN_VALIDATOR);
         VALIDATORS.put(System.AUTO_LAUNCH_MEDIA_CONTROLS, BOOLEAN_VALIDATOR);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java b/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
index 720d6dd..00f2d0e 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
@@ -44,6 +44,8 @@
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.PrintWriter;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
@@ -73,6 +75,15 @@
         }
     }
 
+    @Override
+    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+      final IContentProvider iprovider = mProvider.getIContentProvider();
+      pw.println("device config properties:");
+      for (String line : MyShellCommand.listAll(iprovider)) {
+        pw.println(line);
+      }
+    }
+
     private void callUpdableDeviceConfigShellCommandHandler(FileDescriptor in, FileDescriptor out,
             FileDescriptor err, String[] args, ResultReceiver resultReceiver) {
         int result = -1;
@@ -99,6 +110,7 @@
             PUT,
             DELETE,
             LIST,
+            LIST_NAMESPACES,
             RESET,
             SET_SYNC_DISABLED_FOR_TESTS,
             GET_SYNC_DISABLED_FOR_TESTS,
@@ -108,6 +120,31 @@
             mProvider = provider;
         }
 
+        public static List<String> listAll(IContentProvider provider) {
+            final ArrayList<String> lines = new ArrayList<>();
+
+            try {
+                Bundle args = new Bundle();
+                args.putInt(Settings.CALL_METHOD_USER_KEY,
+                        ActivityManager.getService().getCurrentUser().id);
+                Bundle b = provider.call(new AttributionSource(Process.myUid(),
+                                resolveCallingPackage(), null), Settings.AUTHORITY,
+                        Settings.CALL_METHOD_LIST_CONFIG, null, args);
+                if (b != null) {
+                    Map<String, String> flagsToValues =
+                            (HashMap) b.getSerializable(Settings.NameValueTable.VALUE);
+                    for (String key : flagsToValues.keySet()) {
+                        lines.add(key + "=" + flagsToValues.get(key));
+                    }
+                }
+
+                Collections.sort(lines);
+            } catch (RemoteException e) {
+                throw new RuntimeException("Failed in IPC", e);
+            }
+            return lines;
+        }
+
         @SuppressLint("AndroidFrameworkRequiresPermission")
         @Override
         public int onCommand(String cmd) {
@@ -131,6 +168,11 @@
                 if (peekNextArg() == null) {
                     isValid = true;
                 }
+            } else if ("list_namespaces".equalsIgnoreCase(cmd)) {
+                verb = CommandVerb.LIST_NAMESPACES;
+                if (peekNextArg() == null) {
+                    isValid = true;
+                }
             } else if ("reset".equalsIgnoreCase(cmd)) {
                 verb = CommandVerb.RESET;
             } else if ("set_sync_disabled_for_tests".equalsIgnoreCase(cmd)) {
@@ -156,6 +198,7 @@
             String key = null;
             String value = null;
             String arg;
+            boolean publicOnly = false;
             while ((arg = getNextArg()) != null) {
                 if (verb == CommandVerb.RESET) {
                     if (resetMode == -1) {
@@ -198,6 +241,11 @@
                             isValid = true;
                         }
                     }
+                } else if (verb == CommandVerb.LIST_NAMESPACES) {
+                    if (arg.equals("--public")) {
+                        isValid = true;
+                        publicOnly = true;
+                    }
                 } else if (namespace == null) {
                     // GET, PUT, DELETE, LIST 1st arg
                     namespace = arg;
@@ -275,6 +323,30 @@
                         }
                     }
                     break;
+                case LIST_NAMESPACES:
+                    List<String> namespaces;
+                    if (publicOnly) {
+                        namespaces = DeviceConfig.getPublicNamespaces();
+                    } else {
+                        Field[] fields = DeviceConfig.class.getDeclaredFields();
+                        namespaces = new ArrayList<>(fields.length);
+                        // TODO(b/265948913): once moved to mainline, it should call a hidden method
+                        // directly
+                        for (Field field : fields) {
+                            int modifiers = field.getModifiers();
+                            try {
+                                if (Modifier.isStatic(modifiers) && Modifier.isFinal(modifiers)
+                                        && field.getType().equals(String.class)
+                                        && field.getName().startsWith("NAMESPACE_")) {
+                                    namespaces.add((String) field.get(null));
+                                }
+                            } catch (IllegalAccessException ignored) { }
+                        }
+                    }
+                    for (int i = 0; i < namespaces.size(); i++) {
+                        pout.println(namespaces.get(i));
+                    }
+                    break;
                 case RESET:
                     DeviceConfig.resetToDefaults(resetMode, namespace);
                     break;
@@ -310,6 +382,8 @@
             pw.println("      {default} to set as the default value.");
             pw.println("  delete NAMESPACE KEY");
             pw.println("      Delete the entry for KEY for the given NAMESPACE.");
+            pw.println("  list_namespaces [--public]");
+            pw.println("      Prints the name of all (or just the public) namespaces.");
             pw.println("  list [NAMESPACE]");
             pw.println("      Print all keys and values defined, optionally for the given "
                     + "NAMESPACE.");
@@ -351,31 +425,6 @@
             return success;
         }
 
-        private List<String> listAll(IContentProvider provider) {
-            final ArrayList<String> lines = new ArrayList<>();
-
-            try {
-                Bundle args = new Bundle();
-                args.putInt(Settings.CALL_METHOD_USER_KEY,
-                        ActivityManager.getService().getCurrentUser().id);
-                Bundle b = provider.call(new AttributionSource(Process.myUid(),
-                                resolveCallingPackage(), null), Settings.AUTHORITY,
-                        Settings.CALL_METHOD_LIST_CONFIG, null, args);
-                if (b != null) {
-                    Map<String, String> flagsToValues =
-                            (HashMap) b.getSerializable(Settings.NameValueTable.VALUE);
-                    for (String key : flagsToValues.keySet()) {
-                        lines.add(key + "=" + flagsToValues.get(key));
-                    }
-                }
-
-                Collections.sort(lines);
-            } catch (RemoteException e) {
-                throw new RuntimeException("Failed in IPC", e);
-            }
-            return lines;
-        }
-
         private static String resolveCallingPackage() {
             switch (Binder.getCallingUid()) {
                 case Process.ROOT_UID: {
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
index 11154d1..99a00e4 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
@@ -246,9 +246,13 @@
         stateChecksums[STATE_LOCK_SETTINGS] =
                 writeIfChanged(stateChecksums[STATE_LOCK_SETTINGS], KEY_LOCK_SETTINGS,
                         lockSettingsData, data);
-        stateChecksums[STATE_SOFTAP_CONFIG] =
-                writeIfChanged(stateChecksums[STATE_SOFTAP_CONFIG], KEY_SOFTAP_CONFIG,
-                        softApConfigData, data);
+        if (isWatch()) {
+            stateChecksums[STATE_SOFTAP_CONFIG] = 0;
+        } else {
+            stateChecksums[STATE_SOFTAP_CONFIG] =
+                    writeIfChanged(stateChecksums[STATE_SOFTAP_CONFIG], KEY_SOFTAP_CONFIG,
+                            softApConfigData, data);
+        }
         stateChecksums[STATE_NETWORK_POLICIES] =
                 writeIfChanged(stateChecksums[STATE_NETWORK_POLICIES], KEY_NETWORK_POLICIES,
                         netPoliciesData, data);
@@ -265,6 +269,10 @@
         writeNewChecksums(stateChecksums, newState);
     }
 
+    private boolean isWatch() {
+        return getBaseContext().getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH);
+    }
+
     @Override
     public void onRestore(BackupDataInput data, int appVersionCode,
             ParcelFileDescriptor newState) {
@@ -366,19 +374,25 @@
                 case KEY_SOFTAP_CONFIG :
                     byte[] softapData = new byte[size];
                     data.readEntityData(softapData, 0, size);
-                    restoreSoftApConfiguration(softapData);
+                    if (!isWatch()) {
+                        restoreSoftApConfiguration(softapData);
+                    }
                     break;
 
                 case KEY_NETWORK_POLICIES:
                     byte[] netPoliciesData = new byte[size];
                     data.readEntityData(netPoliciesData, 0, size);
-                    restoreNetworkPolicies(netPoliciesData);
+                    if (!isWatch()) {
+                        restoreNetworkPolicies(netPoliciesData);
+                    }
                     break;
 
                 case KEY_WIFI_NEW_CONFIG:
                     byte[] restoredWifiNewConfigData = new byte[size];
                     data.readEntityData(restoredWifiNewConfigData, 0, size);
-                    restoreNewWifiConfigData(restoredWifiNewConfigData);
+                    if (!isWatch()) {
+                        restoreNewWifiConfigData(restoredWifiNewConfigData);
+                    }
                     break;
 
                 case KEY_DEVICE_SPECIFIC_CONFIG:
@@ -407,7 +421,7 @@
         }
 
         // Do this at the end so that we also pull in the ipconfig data.
-        if (restoredWifiSupplicantData != null) {
+        if (restoredWifiSupplicantData != null && !isWatch()) {
             restoreSupplicantWifiConfigData(
                     restoredWifiSupplicantData, restoredWifiIpConfigData);
         }
@@ -491,7 +505,9 @@
                 if (DEBUG_BACKUP) Log.d(TAG, ipconfig_size + " bytes of ip config data");
                 byte[] ipconfig_buffer = new byte[ipconfig_size];
                 in.readFully(ipconfig_buffer, 0, nBytes);
-                restoreSupplicantWifiConfigData(supplicant_buffer, ipconfig_buffer);
+                if (!isWatch()) {
+                    restoreSupplicantWifiConfigData(supplicant_buffer, ipconfig_buffer);
+                }
             }
 
             if (version >= FULL_BACKUP_ADDED_LOCK_SETTINGS) {
@@ -510,7 +526,9 @@
                 if (nBytes > buffer.length) buffer = new byte[nBytes];
                 if (nBytes > 0) {
                     in.readFully(buffer, 0, nBytes);
-                    restoreSoftApConfiguration(buffer);
+                    if (!isWatch()) {
+                        restoreSoftApConfiguration(buffer);
+                    }
                 }
             }
             // network policies
@@ -520,7 +538,9 @@
                 if (nBytes > buffer.length) buffer = new byte[nBytes];
                 if (nBytes > 0) {
                     in.readFully(buffer, 0, nBytes);
-                    restoreNetworkPolicies(buffer);
+                    if (!isWatch()) {
+                        restoreNetworkPolicies(buffer);
+                    }
                 }
             }
             // Restore full wifi config data
@@ -529,7 +549,9 @@
                 if (DEBUG_BACKUP) Log.d(TAG, nBytes + " bytes of full wifi config data");
                 if (nBytes > buffer.length) buffer = new byte[nBytes];
                 in.readFully(buffer, 0, nBytes);
-                restoreNewWifiConfigData(buffer);
+                if (!isWatch()) {
+                    restoreNewWifiConfigData(buffer);
+                }
             }
 
             if (DEBUG_BACKUP) Log.d(TAG, "Full restore complete.");
@@ -961,7 +983,6 @@
                         lockPatternUtils.setOwnerInfo(value, userId);
                         break;
                     case KEY_LOCK_SETTINGS_VISIBLE_PATTERN_ENABLED:
-                        lockPatternUtils.reportPatternWasChosen(userId);
                         lockPatternUtils.setVisiblePatternEnabled("1".equals(value), userId);
                         break;
                     case KEY_LOCK_SETTINGS_POWER_BUTTON_INSTANTLY_LOCKS:
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index a83bfda..1192e00 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -3059,7 +3059,7 @@
                 Settings.System.APPLY_RAMPING_RINGER,
                 SystemSettingsProto.APPLY_RAMPING_RINGER);
 
-        // Please insert new settings using the same order as in SecureSettingsProto.
+        // Please insert new settings using the same order as in SystemSettingsProto.
 
         // The rest of the settings were moved to Settings.Secure, and are thus excluded here since
         // they're deprecated from Settings.System.
@@ -3097,8 +3097,8 @@
         // The rest of the settings were moved to Settings.Secure, and are thus excluded here since
         // they're deprecated from Settings.System.
 
-        // Please insert new settings using the same order as in SecureSettingsProto.
+        // Please insert new settings using the same order as in SystemSettingsProto.
         p.end(token);
-        // Please insert new settings using the same order as in SecureSettingsProto.
+        // Please insert new settings using the same order as in SystemSettingsProto.
     }
 }
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 23b6308..d1d745f 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -246,6 +246,7 @@
     public static final String RESULT_ROWS_DELETED = "result_rows_deleted";
     public static final String RESULT_SETTINGS_LIST = "result_settings_list";
 
+    public static final String SETTINGS_PROVIDER_JOBS_NS = "SettingsProviderJobsNamespace";
     // Used for scheduling jobs to make a copy for the settings files
     public static final int WRITE_FALLBACK_SETTINGS_FILES_JOB_ID = 1;
     public static final long ONE_DAY_INTERVAL_MILLIS = 24 * 60 * 60 * 1000L;
@@ -520,6 +521,13 @@
                 break;
             }
 
+            case Settings.CALL_METHOD_RESET_SYSTEM: {
+                final int mode = getResetModeEnforcingPermission(args);
+                String tag = getSettingTag(args);
+                resetSystemSetting(requestingUserId, mode, tag);
+                break;
+            }
+
             case Settings.CALL_METHOD_DELETE_CONFIG: {
                 int rows  = deleteConfigSetting(name) ? 1 : 0;
                 Bundle result = new Bundle();
@@ -1261,11 +1269,13 @@
             Setting settingLocked = mSettingsRegistry.getSettingLocked(
                     SETTINGS_TYPE_GLOBAL, UserHandle.USER_SYSTEM,
                     Global.DEVICE_CONFIG_SYNC_DISABLED);
-            if (settingLocked == null) {
-                return SYNC_DISABLED_MODE_NONE;
+            String settingValue = settingLocked == null ? null : settingLocked.getValue();
+            if (settingValue == null) {
+                // Disable sync by default in test harness mode.
+                return ActivityManager.isRunningInUserTestHarness()
+                        ? SYNC_DISABLED_MODE_PERSISTENT : SYNC_DISABLED_MODE_NONE;
             }
-            String settingValue = settingLocked.getValue();
-            boolean isSyncDisabledPersistent = settingValue != null && !"0".equals(settingValue);
+            boolean isSyncDisabledPersistent = !"0".equals(settingValue);
             return isSyncDisabledPersistent
                     ? SYNC_DISABLED_MODE_PERSISTENT : SYNC_DISABLED_MODE_NONE;
         } finally {
@@ -1873,8 +1883,8 @@
                     + requestingUserId + ")");
         }
 
-        return mutateSystemSetting(name, value, requestingUserId, MUTATION_OPERATION_INSERT,
-                overrideableByRestore);
+        return mutateSystemSetting(name, value, /* tag= */ null, requestingUserId,
+                MUTATION_OPERATION_INSERT, /* mode= */ 0, overrideableByRestore);
     }
 
     private boolean deleteSystemSetting(String name, int requestingUserId) {
@@ -1894,15 +1904,25 @@
         return mutateSystemSetting(name, value, requestingUserId, MUTATION_OPERATION_UPDATE);
     }
 
+    private void resetSystemSetting(int requestingUserId, int mode, String tag) {
+        if (DEBUG) {
+            Slog.v(LOG_TAG, "resetSystemSetting(" + requestingUserId + ", "
+                    + mode + ", " + tag + ")");
+        }
+
+        mutateSystemSetting(null, null, tag, requestingUserId, MUTATION_OPERATION_RESET, mode,
+                false);
+    }
+
     private boolean mutateSystemSetting(String name, String value, int runAsUserId, int operation) {
         // overrideableByRestore = false as by default settings values shouldn't be overrideable by
         // restore.
-        return mutateSystemSetting(name, value, runAsUserId, operation,
-                /* overrideableByRestore */ false);
+        return mutateSystemSetting(name, value, /* tag= */ null, runAsUserId, operation,
+                /* mode= */ 0, /* overrideableByRestore */ false);
     }
 
-    private boolean mutateSystemSetting(String name, String value, int runAsUserId, int operation,
-            boolean overrideableByRestore) {
+    private boolean mutateSystemSetting(String name, String value, String tag, int runAsUserId,
+            int operation, int mode, boolean overrideableByRestore) {
         final String callingPackage = getCallingPackage();
         if (!hasWriteSecureSettingsPermission()) {
             // If the caller doesn't hold WRITE_SECURE_SETTINGS, we verify whether this
@@ -1974,6 +1994,12 @@
                             owningUserId, name, value, null, false, callingPackage,
                             false, null);
                 }
+
+                case MUTATION_OPERATION_RESET: {
+                    mSettingsRegistry.resetSettingsLocked(SETTINGS_TYPE_SYSTEM,
+                            UserHandle.USER_SYSTEM, callingPackage, mode, tag);
+                    return true;
+                }
             }
             Slog.e(LOG_TAG, "Unknown operation code: " + operation);
             return false;
@@ -2760,12 +2786,13 @@
      */
     public void scheduleWriteFallbackFilesJob() {
         final Context context = getContext();
-        final JobScheduler jobScheduler =
+        JobScheduler jobScheduler =
                 (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
         if (jobScheduler == null) {
             // Might happen: SettingsProvider is created before JobSchedulerService in system server
             return;
         }
+        jobScheduler = jobScheduler.forNamespace(SETTINGS_PROVIDER_JOBS_NS);
         // Check if the job is already scheduled. If so, skip scheduling another one
         if (jobScheduler.getPendingJob(WRITE_FALLBACK_SETTINGS_FILES_JOB_ID) != null) {
             return;
@@ -3748,7 +3775,7 @@
         }
 
         private final class UpgradeController {
-            private static final int SETTINGS_VERSION = 220;
+            private static final int SETTINGS_VERSION = 221;
 
             private final int mUserId;
 
@@ -5118,14 +5145,15 @@
                             Secure.ACCESSIBILITY_MAGNIFICATION_CAPABILITY);
                     final boolean supportMagnificationArea = getContext().getResources().getBoolean(
                             com.android.internal.R.bool.config_magnification_area);
-                    final int capability = supportMagnificationArea
-                            ? R.integer.def_accessibility_magnification_capabilities
-                            : Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
                     final String supportShowPrompt = supportMagnificationArea ? "1" : "0";
                     if (magnificationCapabilities.isNull()) {
+                        final int capability = supportMagnificationArea
+                                ? getContext().getResources().getInteger(
+                                        R.integer.def_accessibility_magnification_capabilities)
+                                : Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
                         secureSettings.insertSettingLocked(
                                 Secure.ACCESSIBILITY_MAGNIFICATION_CAPABILITY,
-                                String.valueOf(getContext().getResources().getInteger(capability)),
+                                String.valueOf(capability),
                                 null, true, SettingsState.SYSTEM_PACKAGE_NAME);
 
                         if (isMagnificationSettingsOn(secureSettings)) {
@@ -5682,6 +5710,7 @@
                                         R.string.airplane_mode_toggleable_radios),
                                 null, true, SettingsState.SYSTEM_PACKAGE_NAME);
                     }
+
                     currentVersion = 216;
                 }
 
@@ -5840,6 +5869,21 @@
                     currentVersion = 220;
                 }
 
+                if (currentVersion == 220) {
+                    final SettingsState globalSettings = getGlobalSettingsLocked();
+                    final Setting enableBackAnimation =
+                            globalSettings.getSettingLocked(Global.ENABLE_BACK_ANIMATION);
+                    if (enableBackAnimation.isNull()) {
+                        final boolean defEnableBackAnimation =
+                                getContext()
+                                        .getResources()
+                                        .getBoolean(R.bool.def_enable_back_animation);
+                        initGlobalSettingsDefaultValLocked(
+                                Settings.Global.ENABLE_BACK_ANIMATION, defEnableBackAnimation);
+                    }
+                    currentVersion = 221;
+                }
+
                 // vXXX: Add new settings above this point.
 
                 if (currentVersion != newVersion) {
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index e3153e0..73ead3c 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -94,6 +94,7 @@
 
     static final int SETTINGS_VERSION_NEW_ENCODING = 121;
 
+    public static final int MAX_LENGTH_PER_STRING = 32768;
     private static final long WRITE_SETTINGS_DELAY_MILLIS = 200;
     private static final long MAX_WRITE_SETTINGS_DELAY_MILLIS = 2000;
 
@@ -430,6 +431,19 @@
             return false;
         }
 
+        final boolean isNameTooLong = name.length() > SettingsState.MAX_LENGTH_PER_STRING;
+        final boolean isValueTooLong =
+                value != null && value.length() > SettingsState.MAX_LENGTH_PER_STRING;
+        if (isNameTooLong || isValueTooLong) {
+            // only print the first few bytes of the name in case it is long
+            final String errorMessage = "The " + (isNameTooLong ? "name" : "value")
+                    + " of your setting ["
+                    + (name.length() > 20 ? (name.substring(0, 20) + "...") : name)
+                    + "] is too long. The max length allowed for the string is "
+                    + MAX_LENGTH_PER_STRING + ".";
+            throw new IllegalArgumentException(errorMessage);
+        }
+
         Setting oldState = mSettings.get(name);
         String oldValue = (oldState != null) ? oldState.value : null;
         String oldDefaultValue = (oldState != null) ? oldState.defaultValue : null;
@@ -860,7 +874,6 @@
 
                 final int settingCount = settings.size();
                 for (int i = 0; i < settingCount; i++) {
-
                     Setting setting = settings.valueAt(i);
                     if (setting.isTransient()) {
                         if (DEBUG_PERSISTENCE) {
@@ -869,14 +882,21 @@
                         continue;
                     }
 
-                    if (writeSingleSetting(mVersion, serializer, setting.getId(), setting.getName(),
-                            setting.getValue(), setting.getDefaultValue(), setting.getPackageName(),
-                            setting.getTag(), setting.isDefaultFromSystem(),
-                            setting.isValuePreservedInRestore())) {
-                        if (DEBUG_PERSISTENCE) {
-                            Slog.i(LOG_TAG, "[PERSISTED]" + setting.getName() + "="
-                                    + setting.getValue());
+                    try {
+                        if (writeSingleSetting(mVersion, serializer, setting.getId(),
+                                setting.getName(),
+                                setting.getValue(), setting.getDefaultValue(),
+                                setting.getPackageName(),
+                                setting.getTag(), setting.isDefaultFromSystem(),
+                                setting.isValuePreservedInRestore())) {
+                            if (DEBUG_PERSISTENCE) {
+                                Slog.i(LOG_TAG, "[PERSISTED]" + setting.getName() + "="
+                                        + setting.getValue());
+                            }
                         }
+                    } catch (IOException ex) {
+                        Slog.e(LOG_TAG, "[SKIPPED PERSISTING]" + setting.getName()
+                                + " due to error writing to disk", ex);
                     }
                 }
                 serializer.endTag(null, TAG_SETTINGS);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/WriteFallbackSettingsFilesJobService.java b/packages/SettingsProvider/src/com/android/providers/settings/WriteFallbackSettingsFilesJobService.java
index 66aa7ba..91e8bf8 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/WriteFallbackSettingsFilesJobService.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/WriteFallbackSettingsFilesJobService.java
@@ -16,6 +16,7 @@
 
 package com.android.providers.settings;
 
+import static com.android.providers.settings.SettingsProvider.SETTINGS_PROVIDER_JOBS_NS;
 import static com.android.providers.settings.SettingsProvider.TABLE_CONFIG;
 import static com.android.providers.settings.SettingsProvider.TABLE_GLOBAL;
 import static com.android.providers.settings.SettingsProvider.TABLE_SECURE;
@@ -35,7 +36,8 @@
 public class WriteFallbackSettingsFilesJobService extends JobService {
     @Override
     public boolean onStartJob(final JobParameters params) {
-        if (params.getJobId() != WRITE_FALLBACK_SETTINGS_FILES_JOB_ID) {
+        if (!SETTINGS_PROVIDER_JOBS_NS.equals(params.getJobNamespace())
+                || params.getJobId() != WRITE_FALLBACK_SETTINGS_FILES_JOB_ID) {
             return false;
         }
         final List<String> settingsFiles = new ArrayList<>();
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 873b434..016dc21 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -101,6 +101,7 @@
                     Settings.System.MIN_REFRESH_RATE, // depends on hardware capabilities
                     Settings.System.PEAK_REFRESH_RATE, // depends on hardware capabilities
                     Settings.System.SCREEN_BRIGHTNESS_FLOAT,
+                    Settings.System.WEAR_ACCESSIBILITY_GESTURE_ENABLED_DURING_OOBE,
                     Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ,
                     Settings.System.MULTI_AUDIO_FOCUS_ENABLED // form-factor/OEM specific
                     );
@@ -345,6 +346,8 @@
                     Settings.Global.MIN_DURATION_BETWEEN_RECOVERY_STEPS_IN_MS,
                     Settings.Global.MOBILE_DATA, // Candidate for backup?
                     Settings.Global.MOBILE_DATA_ALWAYS_ON,
+                    Settings.Global.DSRM_DURATION_MILLIS,
+                    Settings.Global.DSRM_ENABLED_ACTIONS,
                     Settings.Global.MODE_RINGER,
                     Settings.Global.MULTI_SIM_DATA_CALL_SUBSCRIPTION,
                     Settings.Global.MULTI_SIM_SMS_PROMPT,
@@ -590,11 +593,19 @@
                     Settings.Global.APPOP_HISTORY_BASE_INTERVAL_MILLIS,
                     Settings.Global.AUTO_REVOKE_PARAMETERS,
                     Settings.Global.ENABLE_RADIO_BUG_DETECTION,
+                    Settings.Global.REPAIR_MODE_ACTIVE,
                     Settings.Global.RADIO_BUG_WAKELOCK_TIMEOUT_COUNT_THRESHOLD,
                     Settings.Global.RADIO_BUG_SYSTEM_ERROR_COUNT_THRESHOLD,
                     Settings.Global.ENABLED_SUBSCRIPTION_FOR_SLOT,
                     Settings.Global.MODEM_STACK_ENABLED_FOR_SLOT,
+                    Settings.Global.POWER_BUTTON_SHORT_PRESS,
+                    Settings.Global.POWER_BUTTON_DOUBLE_PRESS,
+                    Settings.Global.POWER_BUTTON_TRIPLE_PRESS,
                     Settings.Global.POWER_BUTTON_VERY_LONG_PRESS,
+                    Settings.Global.STEM_PRIMARY_BUTTON_SHORT_PRESS,
+                    Settings.Global.STEM_PRIMARY_BUTTON_DOUBLE_PRESS,
+                    Settings.Global.STEM_PRIMARY_BUTTON_TRIPLE_PRESS,
+                    Settings.Global.STEM_PRIMARY_BUTTON_LONG_PRESS,
                     Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS, // Temporary for R beta
                     Settings.Global.INTEGRITY_CHECK_INCLUDES_RULE_PROVIDER,
                     Settings.Global.CACHED_APPS_FREEZER_ENABLED,
@@ -605,7 +616,7 @@
                     Settings.Global.MANAGED_PROVISIONING_DEFER_PROVISIONING_TO_ROLE_HOLDER,
                     Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
                     Settings.Global.ENABLE_BACK_ANIMATION, // Temporary for T, dev option only
-                    Settings.Global.Wearable.COMBINED_LOCATION_ENABLED,
+                    Settings.Global.Wearable.COMBINED_LOCATION_ENABLE,
                     Settings.Global.Wearable.HAS_PAY_TOKENS,
                     Settings.Global.Wearable.GMS_CHECKIN_TIMEOUT_MIN,
                     Settings.Global.Wearable.HOTWORD_DETECTION_ENABLED,
@@ -724,6 +735,7 @@
                  Settings.Secure.CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS,
                  Settings.Secure.CONTENT_CAPTURE_ENABLED,
                  Settings.Secure.DEFAULT_INPUT_METHOD,
+                 Settings.Secure.DEFAULT_NOTE_TASK_PROFILE,
                  Settings.Secure.DEVICE_PAIRED,
                  Settings.Secure.DIALER_DEFAULT_APPLICATION,
                  Settings.Secure.DISABLED_PRINT_SERVICES,
@@ -843,7 +855,8 @@
                  Settings.Secure.ACCESSIBILITY_FLOATING_MENU_MIGRATION_TOOLTIP_PROMPT,
                  Settings.Secure.UI_TRANSLATION_ENABLED,
                  Settings.Secure.CREDENTIAL_SERVICE,
-                 Settings.Secure.CREDENTIAL_SERVICE_PRIMARY);
+                 Settings.Secure.CREDENTIAL_SERVICE_PRIMARY,
+                 Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_EDGE_HAPTIC_ENABLED);
 
     @Test
     public void systemSettingsBackedUpOrDenied() {
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/InstallNonMarketAppsDeprecationTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/InstallNonMarketAppsDeprecationTest.java
index ff11f70..2b33057 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/InstallNonMarketAppsDeprecationTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/InstallNonMarketAppsDeprecationTest.java
@@ -19,6 +19,7 @@
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
 
 import android.content.Context;
 import android.content.pm.UserInfo;
@@ -116,6 +117,8 @@
 
     @Test
     public void testValueForNewUser() throws Exception {
+        assumeTrue(mUm.supportsMultipleUsers());
+
         UserInfo newUser = mUm.createUser("TEST_USER", 0);
         mUsersAddedByTest.add(newUser.id);
         String value = getSecureSettingForUserViaShell(newUser.id);
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
index 55160fb..26cac37 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
@@ -31,21 +31,21 @@
 public class SettingsStateTest extends AndroidTestCase {
     public static final String CRAZY_STRING =
             "\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007\u0008\u0009\n\u000b\u000c\r" +
-            "\u000e\u000f\u0010\u0011\u0012\u0013\u0014\u0015\u0016\u0017\u0018\u0019\u001a" +
-            "\u001b\u001c\u001d\u001e\u001f\u0020" +
-            "fake_setting_value_1" +
-            "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +
-            "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +
-            "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +
-            "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +
-            "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +
-            "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +
-            "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +
-            "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +
-            "\u1000 \u2000 \u5000 \u8000 \uc000 \ue000" +
-            "\ud800\udc00\udbff\udfff" + // surrogate pairs
-            "\uD800ab\uDC00 " + // broken surrogate pairs
-            "日本語";
+                    "\u000e\u000f\u0010\u0011\u0012\u0013\u0014\u0015\u0016\u0017\u0018\u0019\u001a" +
+                    "\u001b\u001c\u001d\u001e\u001f\u0020" +
+                    "fake_setting_value_1" +
+                    "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +
+                    "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +
+                    "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +
+                    "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +
+                    "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +
+                    "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +
+                    "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +
+                    "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +
+                    "\u1000 \u2000 \u5000 \u8000 \uc000 \ue000" +
+                    "\ud800\udc00\udbff\udfff" + // surrogate pairs
+                    "\uD800ab\uDC00 " + // broken surrogate pairs
+                    "日本語";
 
     private static final String TEST_PACKAGE = "package";
     private static final String SYSTEM_PACKAGE = "android";
@@ -170,11 +170,11 @@
         final PrintStream os = new PrintStream(new FileOutputStream(file));
         os.print(
                 "<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>" +
-                "<settings version=\"120\">" +
-                "  <setting id=\"0\" name=\"k0\" value=\"null\" package=\"null\" />" +
-                "  <setting id=\"1\" name=\"k1\" value=\"\" package=\"\" />" +
-                "  <setting id=\"2\" name=\"k2\" value=\"v2\" package=\"p2\" />" +
-                "</settings>");
+                        "<settings version=\"120\">" +
+                        "  <setting id=\"0\" name=\"k0\" value=\"null\" package=\"null\" />" +
+                        "  <setting id=\"1\" name=\"k1\" value=\"\" package=\"\" />" +
+                        "  <setting id=\"2\" name=\"k2\" value=\"v2\" package=\"p2\" />" +
+                        "</settings>");
         os.close();
 
         final SettingsState ss = new SettingsState(getContext(), lock, file, 1,
@@ -408,4 +408,50 @@
         }
         assertEquals(expectedMemUsage, settingsState.getMemoryUsage(TEST_PACKAGE));
     }
+
+    public void testLargeSettingKey() {
+        SettingsState settingsState = new SettingsState(getContext(), mLock, mSettingsFile, 1,
+                SettingsState.MAX_BYTES_PER_APP_PACKAGE_LIMITED, Looper.getMainLooper());
+        final String largeKey = Strings.repeat("A", SettingsState.MAX_LENGTH_PER_STRING + 1);
+        final String testValue = "testValue";
+        synchronized (mLock) {
+            // Test system package
+            try {
+                settingsState.insertSettingLocked(largeKey, testValue, null, true, SYSTEM_PACKAGE);
+                fail("Should throw because it exceeded max string length");
+            } catch (IllegalArgumentException ex) {
+                assertTrue(ex.getMessage().contains("The max length allowed for the string is "));
+            }
+            // Test non system package
+            try {
+                settingsState.insertSettingLocked(largeKey, testValue, null, true, TEST_PACKAGE);
+                fail("Should throw because it exceeded max string length");
+            } catch (IllegalArgumentException ex) {
+                assertTrue(ex.getMessage().contains("The max length allowed for the string is "));
+            }
+        }
+    }
+
+    public void testLargeSettingValue() {
+        SettingsState settingsState = new SettingsState(getContext(), mLock, mSettingsFile, 1,
+                SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper());
+        final String testKey = "testKey";
+        final String largeValue = Strings.repeat("A", SettingsState.MAX_LENGTH_PER_STRING + 1);
+        synchronized (mLock) {
+            // Test system package
+            try {
+                settingsState.insertSettingLocked(testKey, largeValue, null, true, SYSTEM_PACKAGE);
+                fail("Should throw because it exceeded max string length");
+            } catch (IllegalArgumentException ex) {
+                assertTrue(ex.getMessage().contains("The max length allowed for the string is "));
+            }
+            // Test non system package
+            try {
+                settingsState.insertSettingLocked(testKey, largeValue, null, true, TEST_PACKAGE);
+                fail("Should throw because it exceeded max string length");
+            } catch (IllegalArgumentException ex) {
+                assertTrue(ex.getMessage().contains("The max length allowed for the string is "));
+            }
+        }
+    }
 }
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 56e0643..368115b 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -17,12 +17,12 @@
  */
 -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-   package="com.android.shell"
-   coreApp="true"
-   android:sharedUserId="android.uid.shell"
-   >
+        package="com.android.shell"
+        coreApp="true"
+        android:sharedUserId="android.uid.shell"
+        >
 
-    <!-- Standard permissions granted to the shell. -->
+        <!-- Standard permissions granted to the shell. -->
     <uses-permission android:name="android.permission.MANAGE_HEALTH_PERMISSIONS" />
     <uses-permission android:name="android.permission.MANAGE_HEALTH_DATA" />
     <uses-permission android:name="android.permission.health.READ_EXERCISE_ROUTE" />
@@ -320,6 +320,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.OBSERVE_APP_USAGE" />
     <uses-permission android:name="android.permission.READ_CLIPBOARD_IN_BACKGROUND" />
@@ -833,8 +834,13 @@
     <uses-permission android:name="android.permission.WRITE_APN_SETTINGS" />
 
     <uses-permission android:name="android.permission.LOG_FOREGROUND_RESOURCE_USE" />
+
+    <!-- Permission required for CTS test - CtsPackageInstallTestCases -->
+    <uses-permission android:name="android.permission.READ_INSTALLED_SESSION_PATHS" />
+
     <!-- Permission required for GTS test - GtsAttestationVerificationDeviceSideTestCases -->
     <uses-permission android:name="android.permission.USE_ATTESTATION_VERIFICATION_SERVICE" />
+
     <!-- Permission required for GTS test - GtsCredentialsTestCases -->
     <uses-permission android:name="android.permission.LAUNCH_CREDENTIAL_SELECTOR" />
     <!-- Permission required for CTS test IntentRedirectionTest -->
diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java
index e069a9a..42952de 100644
--- a/packages/Shell/src/com/android/shell/BugreportProgressService.java
+++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java
@@ -1212,13 +1212,31 @@
 
     private void maybeShowWarningMessageAndCloseNotification(int id) {
         if (!hasUserDecidedNotToGetWarningMessage()) {
-            Intent warningIntent = buildWarningIntent(mContext, /* sendIntent */ null);
+            Intent warningIntent;
+            if (mIsWatch) {
+                warningIntent = buildWearWarningIntent();
+            } else {
+                warningIntent = buildWarningIntent(mContext, /* sendIntent */ null);
+            }
             warningIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
             mContext.startActivity(warningIntent);
         }
         NotificationManager.from(mContext).cancel(id);
     }
 
+    /**
+     * Build intent to show warning dialog on Wear after bugreport is done
+     */
+    private Intent buildWearWarningIntent() {
+        Intent intent = new Intent();
+        intent.setClassName(mContext, getPackageName() + ".WearBugreportWarningActivity");
+        if (mContext.getPackageManager().resolveActivity(intent, /* flags */ 0) == null) {
+            Log.e(TAG, "Cannot find wear bugreport warning activity");
+            return buildWarningIntent(mContext, /* sendIntent */ null);
+        }
+        return intent;
+    }
+
     private void shareBugreport(int id, BugreportInfo sharedInfo) {
         shareBugreport(id, sharedInfo, !hasUserDecidedNotToGetWarningMessage());
     }
diff --git a/packages/Shell/tests/Android.bp b/packages/Shell/tests/Android.bp
index 70e8c10..0dc3314 100644
--- a/packages/Shell/tests/Android.bp
+++ b/packages/Shell/tests/Android.bp
@@ -18,7 +18,7 @@
     static_libs: [
         "androidx.test.rules",
         "mockito-target-minus-junit4",
-        "ub-uiautomator",
+        "androidx.test.uiautomator_uiautomator",
         "junit",
     ],
     platform_apis: true,
diff --git a/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java b/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
index a719d77..4579168 100644
--- a/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
+++ b/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
@@ -59,10 +59,6 @@
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.service.notification.StatusBarNotification;
-import android.support.test.uiautomator.UiDevice;
-import android.support.test.uiautomator.UiObject;
-import android.support.test.uiautomator.UiObject2;
-import android.support.test.uiautomator.UiObjectNotFoundException;
 import android.text.TextUtils;
 import android.text.format.DateUtils;
 import android.util.Log;
@@ -71,6 +67,10 @@
 import androidx.test.filters.LargeTest;
 import androidx.test.rule.ServiceTestRule;
 import androidx.test.runner.AndroidJUnit4;
+import androidx.test.uiautomator.UiDevice;
+import androidx.test.uiautomator.UiObject;
+import androidx.test.uiautomator.UiObject2;
+import androidx.test.uiautomator.UiObjectNotFoundException;
 
 import com.android.shell.ActionSendMultipleConsumerActivity.CustomActionSendMultipleListener;
 
diff --git a/packages/Shell/tests/src/com/android/shell/UiBot.java b/packages/Shell/tests/src/com/android/shell/UiBot.java
index 53b124f..ce9f70d 100644
--- a/packages/Shell/tests/src/com/android/shell/UiBot.java
+++ b/packages/Shell/tests/src/com/android/shell/UiBot.java
@@ -19,16 +19,17 @@
 import android.app.Instrumentation;
 import android.app.StatusBarManager;
 import android.os.SystemClock;
-import android.support.test.uiautomator.By;
-import android.support.test.uiautomator.UiDevice;
-import android.support.test.uiautomator.UiObject;
-import android.support.test.uiautomator.UiObject2;
-import android.support.test.uiautomator.UiObjectNotFoundException;
-import android.support.test.uiautomator.UiSelector;
-import android.support.test.uiautomator.Until;
 import android.text.format.DateUtils;
 import android.util.Log;
 
+import androidx.test.uiautomator.By;
+import androidx.test.uiautomator.UiDevice;
+import androidx.test.uiautomator.UiObject;
+import androidx.test.uiautomator.UiObject2;
+import androidx.test.uiautomator.UiObjectNotFoundException;
+import androidx.test.uiautomator.UiSelector;
+import androidx.test.uiautomator.Until;
+
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertNotNull;
 import static junit.framework.Assert.assertTrue;
diff --git a/packages/SoundPicker/Android.bp b/packages/SoundPicker/Android.bp
index 2c89d6d..235e672 100644
--- a/packages/SoundPicker/Android.bp
+++ b/packages/SoundPicker/Android.bp
@@ -7,22 +7,40 @@
     default_applicable_licenses: ["frameworks_base_license"],
 }
 
-android_app {
-    name: "SoundPicker",
-    defaults: ["platform_app_defaults"],
-    manifest: "AndroidManifest.xml",
-
-    static_libs: [
-        "androidx.appcompat_appcompat",
+android_library {
+    name: "SoundPickerLib",
+    srcs: [
+        "src/**/*.java",
     ],
     resource_dirs: [
         "res",
     ],
-    srcs: [
-        "src/**/*.java",
+    static_libs: [
+        "androidx.appcompat_appcompat",
+        "hilt_android",
+        "guava",
+        "androidx.recyclerview_recyclerview",
+        "androidx-constraintlayout_constraintlayout",
+        "androidx.viewpager2_viewpager2",
+        "com.google.android.material_material",
     ],
+}
 
+android_app {
+    name: "SoundPicker",
+    defaults: ["platform_app_defaults"],
+    manifest: "AndroidManifest.xml",
+    static_libs: ["SoundPickerLib"],
     platform_apis: true,
     certificate: "media",
     privileged: true,
+
+    optimize: {
+        enabled: true,
+        optimize: true,
+        shrink: true,
+        shrink_resources: true,
+        obfuscate: false,
+        proguard_compatibility: false,
+    },
 }
diff --git a/packages/SoundPicker/AndroidManifest.xml b/packages/SoundPicker/AndroidManifest.xml
index 6cb885f..934b003 100644
--- a/packages/SoundPicker/AndroidManifest.xml
+++ b/packages/SoundPicker/AndroidManifest.xml
@@ -9,9 +9,13 @@
     <uses-permission android:name="android.permission.RECEIVE_DEVICE_CUSTOMIZATION_READY" />
     <uses-permission android:name="android.permission.WRITE_SETTINGS" />
 
+    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
+
     <application
+            android:name=".RingtonePickerApplication"
             android:allowBackup="false"
             android:label="@string/app_label"
+            android:theme="@style/Theme.AppCompat"
             android:supportsRtl="true">
         <receiver android:name="RingtoneReceiver"
                 android:exported="true">
@@ -23,13 +27,16 @@
         <service android:name="RingtoneOverlayService" />
 
         <activity android:name="RingtonePickerActivity"
-                android:theme="@style/PickerDialogTheme"
+                android:theme="@style/Theme.AppCompat.Dialog"
                 android:enabled="@*android:bool/config_defaultRingtonePickerEnabled"
                 android:excludeFromRecents="true"
                 android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.RINGTONE_PICKER" />
                 <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.RINGTONE_PICKER_SOUND" />
+                <category android:name="android.intent.category.RINGTONE_PICKER_VIBRATION" />
+                <category android:name="android.intent.category.RINGTONE_PICKER_RINGTONE" />
             </intent-filter>
         </activity>
     </application>
diff --git a/packages/SoundPicker/OWNERS b/packages/SoundPicker/OWNERS
new file mode 100644
index 0000000..5bf46e0
--- /dev/null
+++ b/packages/SoundPicker/OWNERS
@@ -0,0 +1,2 @@
+# Haptics team works on the SoundPicker
+include platform/frameworks/base:/services/core/java/com/android/server/vibrator/OWNERS
diff --git a/packages/SoundPicker/res/layout/activity_ringtone_picker.xml b/packages/SoundPicker/res/layout/activity_ringtone_picker.xml
new file mode 100644
index 0000000..6fc6080
--- /dev/null
+++ b/packages/SoundPicker/res/layout/activity_ringtone_picker.xml
@@ -0,0 +1,21 @@
+<?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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:orientation="vertical"
+              android:layout_width="match_parent"
+              android:layout_height="match_parent"/>
\ No newline at end of file
diff --git a/packages/SoundPicker/res/layout/add_new_sound_item.xml b/packages/SoundPicker/res/layout/add_new_sound_item.xml
index 14421c9..024b97e 100644
--- a/packages/SoundPicker/res/layout/add_new_sound_item.xml
+++ b/packages/SoundPicker/res/layout/add_new_sound_item.xml
@@ -16,12 +16,14 @@
 -->
 
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="fill_parent"
-    android:layout_height="wrap_content"
-    android:gravity="center_vertical"
-    android:background="?android:attr/selectableItemBackground">
+              android:layout_width="fill_parent"
+              android:layout_height="wrap_content"
+              android:gravity="center_vertical"
+              android:background="?android:attr/selectableItemBackground"
+              android:focusable="true"
+              android:clickable="true">
 
-<ImageView
+    <ImageView
         android:layout_width="24dp"
         android:layout_height="24dp"
         android:layout_alignParentRight="true"
@@ -29,9 +31,9 @@
         android:scaleType="centerCrop"
         android:layout_marginRight="24dp"
         android:layout_marginLeft="24dp"
-        android:src="@drawable/ic_add" />
+        android:src="@drawable/ic_add"/>
 
-<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+    <TextView
         android:id="@+id/add_new_sound_text"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
@@ -43,5 +45,5 @@
         android:gravity="center_vertical"
         android:paddingEnd="?android:attr/dialogPreferredPadding"
         android:drawablePadding="20dp"
-        android:ellipsize="marquee" />
+        android:ellipsize="marquee"/>
 </LinearLayout>
\ No newline at end of file
diff --git a/packages/SoundPicker/res/layout/fragment_ringtone_picker.xml b/packages/SoundPicker/res/layout/fragment_ringtone_picker.xml
new file mode 100644
index 0000000..787f92e
--- /dev/null
+++ b/packages/SoundPicker/res/layout/fragment_ringtone_picker.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2023 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<androidx.recyclerview.widget.RecyclerView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/recycler_view"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+/>
\ No newline at end of file
diff --git a/packages/SoundPicker/res/layout/fragment_tabbed_dialog.xml b/packages/SoundPicker/res/layout/fragment_tabbed_dialog.xml
new file mode 100644
index 0000000..7efd911
--- /dev/null
+++ b/packages/SoundPicker/res/layout/fragment_tabbed_dialog.xml
@@ -0,0 +1,31 @@
+<?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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:orientation="vertical"
+              android:layout_width="match_parent"
+              android:layout_height="match_parent">
+    <com.google.android.material.tabs.TabLayout
+            android:id="@+id/tabLayout"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"/>
+    <androidx.viewpager2.widget.ViewPager2
+            android:id="@+id/masterViewPager"
+            android:paddingTop="12dp"
+            android:paddingBottom="12dp"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"/>
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SoundPicker/res/layout/radio_with_work_badge.xml b/packages/SoundPicker/res/layout/radio_with_work_badge.xml
index c8ca231..36ac93e 100644
--- a/packages/SoundPicker/res/layout/radio_with_work_badge.xml
+++ b/packages/SoundPicker/res/layout/radio_with_work_badge.xml
@@ -14,12 +14,14 @@
      limitations under the License.
 -->
 
-<com.android.soundpicker.CheckedListItem xmlns:android="http://schemas.android.com/apk/res/android"
+<com.android.soundpicker.CheckedListItem
+    xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="fill_parent"
     android:layout_height="wrap_content"
     android:gravity="center_vertical"
     android:background="?android:attr/selectableItemBackground"
-    >
+    android:focusable="true"
+    android:clickable="true">
 
     <CheckedTextView
         android:id="@+id/checked_text_view"
@@ -35,7 +37,7 @@
         android:drawablePadding="20dp"
         android:ellipsize="marquee"
         android:layout_toLeftOf="@+id/work_icon"
-        android:maxLines="3" />
+        android:maxLines="3"/>
 
     <ImageView
         android:id="@id/work_icon"
@@ -44,5 +46,5 @@
         android:layout_alignParentRight="true"
         android:layout_centerVertical="true"
         android:scaleType="centerCrop"
-        android:layout_marginRight="20dp" />
+        android:layout_marginRight="20dp"/>
 </com.android.soundpicker.CheckedListItem>
diff --git a/packages/SoundPicker/res/values-af/strings.xml b/packages/SoundPicker/res/values-af/strings.xml
index fd857b1..7396b76 100644
--- a/packages/SoundPicker/res/values-af/strings.xml
+++ b/packages/SoundPicker/res/values-af/strings.xml
@@ -26,4 +26,7 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Kan nie gepasmaakte luitoon byvoeg nie"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Kan nie gepasmaakte luitoon uitvee nie"</string>
     <string name="app_label" msgid="3091611356093417332">"Klanke"</string>
+    <string name="empty_list" msgid="2871978423955821191">"Die lys is leeg"</string>
+    <string name="sound_page_title" msgid="2143312098775103522">"Klank"</string>
+    <string name="vibration_page_title" msgid="6519501440349124677">"Vibrasie"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-am/strings.xml b/packages/SoundPicker/res/values-am/strings.xml
index 85206c0..bd1c24b 100644
--- a/packages/SoundPicker/res/values-am/strings.xml
+++ b/packages/SoundPicker/res/values-am/strings.xml
@@ -26,4 +26,7 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"ብጁ የጥሪ ቅላጼን ማከል አልተቻለም"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"ብጁ የጥሪ ቅላጼን መሰረዝ አልተቻለም"</string>
     <string name="app_label" msgid="3091611356093417332">"ድምፆች"</string>
+    <string name="empty_list" msgid="2871978423955821191">"ዝርዝሩ ባዶ ነው"</string>
+    <string name="sound_page_title" msgid="2143312098775103522">"ድምፅ"</string>
+    <string name="vibration_page_title" msgid="6519501440349124677">"ንዝረት"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-ar/strings.xml b/packages/SoundPicker/res/values-ar/strings.xml
index f8844e9..805c7cf 100644
--- a/packages/SoundPicker/res/values-ar/strings.xml
+++ b/packages/SoundPicker/res/values-ar/strings.xml
@@ -26,4 +26,7 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"يتعذر إضافة نغمة رنين مخصصة"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"يتعذر حذف نغمة الرنين المخصصة"</string>
     <string name="app_label" msgid="3091611356093417332">"الأصوات"</string>
+    <string name="empty_list" msgid="2871978423955821191">"القائمة فارغة."</string>
+    <string name="sound_page_title" msgid="2143312098775103522">"الصوت"</string>
+    <string name="vibration_page_title" msgid="6519501440349124677">"الاهتزاز"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-as/strings.xml b/packages/SoundPicker/res/values-as/strings.xml
index 5d6bc5d..0a1cd1b 100644
--- a/packages/SoundPicker/res/values-as/strings.xml
+++ b/packages/SoundPicker/res/values-as/strings.xml
@@ -26,4 +26,7 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"নিজৰ উপযোগিতা অনুযায়ী তৈয়াৰ কৰা ৰিংট\'ন যোগ কৰিব পৰা নগ\'ল"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"নিজৰ উপযোগিতা অনুযায়ী তৈয়াৰ কৰা ৰিংট\'ন মচিব পৰা নগ\'ল"</string>
     <string name="app_label" msgid="3091611356093417332">"ধ্বনিসমূহ"</string>
+    <string name="empty_list" msgid="2871978423955821191">"সূচীখন খালী আছে"</string>
+    <string name="sound_page_title" msgid="2143312098775103522">"ধ্বনি"</string>
+    <string name="vibration_page_title" msgid="6519501440349124677">"কম্পন"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-az/strings.xml b/packages/SoundPicker/res/values-az/strings.xml
index e32c3eb..a308329 100644
--- a/packages/SoundPicker/res/values-az/strings.xml
+++ b/packages/SoundPicker/res/values-az/strings.xml
@@ -26,4 +26,7 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Fərdi zəng səsi əlavə etmək mümkün deyil"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Fərdi zəng səsini silmək mümkün deyil"</string>
     <string name="app_label" msgid="3091611356093417332">"Səslər"</string>
+    <string name="empty_list" msgid="2871978423955821191">"Siyahı boşdur"</string>
+    <string name="sound_page_title" msgid="2143312098775103522">"Səs"</string>
+    <string name="vibration_page_title" msgid="6519501440349124677">"Vibrasiya"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-b+sr+Latn/strings.xml b/packages/SoundPicker/res/values-b+sr+Latn/strings.xml
index 947c85c..2a7a196 100644
--- a/packages/SoundPicker/res/values-b+sr+Latn/strings.xml
+++ b/packages/SoundPicker/res/values-b+sr+Latn/strings.xml
@@ -26,4 +26,7 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Dodavanje prilagođene melodije zvona nije uspelo"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Brisanje prilagođene melodije zvona nije uspelo"</string>
     <string name="app_label" msgid="3091611356093417332">"Zvukovi"</string>
+    <string name="empty_list" msgid="2871978423955821191">"Lista je prazna"</string>
+    <string name="sound_page_title" msgid="2143312098775103522">"Zvuk"</string>
+    <string name="vibration_page_title" msgid="6519501440349124677">"Vibriranje"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-be/strings.xml b/packages/SoundPicker/res/values-be/strings.xml
index 6f7fc68..431a301 100644
--- a/packages/SoundPicker/res/values-be/strings.xml
+++ b/packages/SoundPicker/res/values-be/strings.xml
@@ -26,4 +26,7 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Немагчыма дадаць карыстальніцкі рынгтон"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Немагчыма выдаліць карыстальніцкі рынгтон"</string>
     <string name="app_label" msgid="3091611356093417332">"Гукі"</string>
+    <string name="empty_list" msgid="2871978423955821191">"Спіс пусты"</string>
+    <string name="sound_page_title" msgid="2143312098775103522">"Гук"</string>
+    <string name="vibration_page_title" msgid="6519501440349124677">"Вібрацыя"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-bg/strings.xml b/packages/SoundPicker/res/values-bg/strings.xml
index 4277d28..7447af6 100644
--- a/packages/SoundPicker/res/values-bg/strings.xml
+++ b/packages/SoundPicker/res/values-bg/strings.xml
@@ -26,4 +26,7 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Персонализираната мелодия не може да се добави"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Персонализираната мелодия не може да се изтрие"</string>
     <string name="app_label" msgid="3091611356093417332">"Sounds"</string>
+    <string name="empty_list" msgid="2871978423955821191">"Списъкът е празен"</string>
+    <string name="sound_page_title" msgid="2143312098775103522">"Звук"</string>
+    <string name="vibration_page_title" msgid="6519501440349124677">"Вибриране"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-bn/strings.xml b/packages/SoundPicker/res/values-bn/strings.xml
index 276594a..c31b36a 100644
--- a/packages/SoundPicker/res/values-bn/strings.xml
+++ b/packages/SoundPicker/res/values-bn/strings.xml
@@ -26,4 +26,7 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"কাস্টম রিংটোন যোগ করা গেল না"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"কাস্টম রিংটোন মোছা গেল না"</string>
     <string name="app_label" msgid="3091611356093417332">"Sounds"</string>
+    <string name="empty_list" msgid="2871978423955821191">"তালিকায় কিছু নেই"</string>
+    <string name="sound_page_title" msgid="2143312098775103522">"সাউন্ড"</string>
+    <string name="vibration_page_title" msgid="6519501440349124677">"ভাইব্রেশন"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-bs/strings.xml b/packages/SoundPicker/res/values-bs/strings.xml
index 0c8d33f..e65b90d 100644
--- a/packages/SoundPicker/res/values-bs/strings.xml
+++ b/packages/SoundPicker/res/values-bs/strings.xml
@@ -26,4 +26,7 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Nije moguće dodati prilagođenu melodiju zvona"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Nije moguće izbrisati prilagođenu melodiju zvona"</string>
     <string name="app_label" msgid="3091611356093417332">"Zvukovi"</string>
+    <string name="empty_list" msgid="2871978423955821191">"Lista je prazna"</string>
+    <string name="sound_page_title" msgid="2143312098775103522">"Zvuk"</string>
+    <string name="vibration_page_title" msgid="6519501440349124677">"Vibracija"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-ca/strings.xml b/packages/SoundPicker/res/values-ca/strings.xml
index ed96f70..33839bc 100644
--- a/packages/SoundPicker/res/values-ca/strings.xml
+++ b/packages/SoundPicker/res/values-ca/strings.xml
@@ -26,4 +26,7 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"No es pot afegir el so de trucada personalitzat"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"No es pot suprimir el so de trucada personalitzat"</string>
     <string name="app_label" msgid="3091611356093417332">"Sons"</string>
+    <string name="empty_list" msgid="2871978423955821191">"La llista és buida"</string>
+    <string name="sound_page_title" msgid="2143312098775103522">"So"</string>
+    <string name="vibration_page_title" msgid="6519501440349124677">"Vibració"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-cs/strings.xml b/packages/SoundPicker/res/values-cs/strings.xml
index dc67c96..612a6b2 100644
--- a/packages/SoundPicker/res/values-cs/strings.xml
+++ b/packages/SoundPicker/res/values-cs/strings.xml
@@ -26,4 +26,7 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Vlastní vyzvánění se nepodařilo přidat"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Vlastní vyzvánění se nepodařilo smazat"</string>
     <string name="app_label" msgid="3091611356093417332">"Zvuky"</string>
+    <string name="empty_list" msgid="2871978423955821191">"Seznam je prázdný"</string>
+    <string name="sound_page_title" msgid="2143312098775103522">"Zvuk"</string>
+    <string name="vibration_page_title" msgid="6519501440349124677">"Vibrace"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-da/strings.xml b/packages/SoundPicker/res/values-da/strings.xml
index b4437dc..56c4d23 100644
--- a/packages/SoundPicker/res/values-da/strings.xml
+++ b/packages/SoundPicker/res/values-da/strings.xml
@@ -26,4 +26,7 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Den tilpassede ringetone kunne ikke tilføjes"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Den tilpassede ringetone kunne ikke slettes"</string>
     <string name="app_label" msgid="3091611356093417332">"Lyde"</string>
+    <string name="empty_list" msgid="2871978423955821191">"Listen er tom"</string>
+    <string name="sound_page_title" msgid="2143312098775103522">"Lyd"</string>
+    <string name="vibration_page_title" msgid="6519501440349124677">"Vibration"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-de/strings.xml b/packages/SoundPicker/res/values-de/strings.xml
index 8be3aaa..b004e2a 100644
--- a/packages/SoundPicker/res/values-de/strings.xml
+++ b/packages/SoundPicker/res/values-de/strings.xml
@@ -26,4 +26,7 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Benutzerdefinierter Klingelton konnte nicht hinzugefügt werden"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Benutzerdefinierter Klingelton konnte nicht gelöscht werden"</string>
     <string name="app_label" msgid="3091611356093417332">"Töne"</string>
+    <string name="empty_list" msgid="2871978423955821191">"Die Liste ist leer"</string>
+    <string name="sound_page_title" msgid="2143312098775103522">"Ton"</string>
+    <string name="vibration_page_title" msgid="6519501440349124677">"Vibration"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-el/strings.xml b/packages/SoundPicker/res/values-el/strings.xml
index 41e9b0c..bbcb1f5 100644
--- a/packages/SoundPicker/res/values-el/strings.xml
+++ b/packages/SoundPicker/res/values-el/strings.xml
@@ -26,4 +26,7 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Δεν είναι δυνατή η προσθήκη προσαρμοσμένου ήχου κλήσης"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Δεν είναι δυνατή η διαγραφή προσαρμοσμένου ήχου κλήσης"</string>
     <string name="app_label" msgid="3091611356093417332">"Ήχοι"</string>
+    <string name="empty_list" msgid="2871978423955821191">"Η λίστα είναι κενή"</string>
+    <string name="sound_page_title" msgid="2143312098775103522">"Ήχος"</string>
+    <string name="vibration_page_title" msgid="6519501440349124677">"Δόνηση"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-en-rAU/strings.xml b/packages/SoundPicker/res/values-en-rAU/strings.xml
index 4c237b9..5030314 100644
--- a/packages/SoundPicker/res/values-en-rAU/strings.xml
+++ b/packages/SoundPicker/res/values-en-rAU/strings.xml
@@ -26,4 +26,7 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Unable to add customised ringtone"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Unable to delete customised ringtone"</string>
     <string name="app_label" msgid="3091611356093417332">"Sounds"</string>
+    <string name="empty_list" msgid="2871978423955821191">"The list is empty"</string>
+    <string name="sound_page_title" msgid="2143312098775103522">"Sound"</string>
+    <string name="vibration_page_title" msgid="6519501440349124677">"Vibration"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-en-rCA/strings.xml b/packages/SoundPicker/res/values-en-rCA/strings.xml
index b0708356..d887082 100644
--- a/packages/SoundPicker/res/values-en-rCA/strings.xml
+++ b/packages/SoundPicker/res/values-en-rCA/strings.xml
@@ -26,4 +26,7 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Unable to add custom ringtone"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Unable to delete custom ringtone"</string>
     <string name="app_label" msgid="3091611356093417332">"Sounds"</string>
+    <string name="empty_list" msgid="2871978423955821191">"The list is empty"</string>
+    <string name="sound_page_title" msgid="2143312098775103522">"Sound"</string>
+    <string name="vibration_page_title" msgid="6519501440349124677">"Vibration"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-en-rGB/strings.xml b/packages/SoundPicker/res/values-en-rGB/strings.xml
index 4c237b9..5030314 100644
--- a/packages/SoundPicker/res/values-en-rGB/strings.xml
+++ b/packages/SoundPicker/res/values-en-rGB/strings.xml
@@ -26,4 +26,7 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Unable to add customised ringtone"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Unable to delete customised ringtone"</string>
     <string name="app_label" msgid="3091611356093417332">"Sounds"</string>
+    <string name="empty_list" msgid="2871978423955821191">"The list is empty"</string>
+    <string name="sound_page_title" msgid="2143312098775103522">"Sound"</string>
+    <string name="vibration_page_title" msgid="6519501440349124677">"Vibration"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-en-rIN/strings.xml b/packages/SoundPicker/res/values-en-rIN/strings.xml
index 4c237b9..5030314 100644
--- a/packages/SoundPicker/res/values-en-rIN/strings.xml
+++ b/packages/SoundPicker/res/values-en-rIN/strings.xml
@@ -26,4 +26,7 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Unable to add customised ringtone"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Unable to delete customised ringtone"</string>
     <string name="app_label" msgid="3091611356093417332">"Sounds"</string>
+    <string name="empty_list" msgid="2871978423955821191">"The list is empty"</string>
+    <string name="sound_page_title" msgid="2143312098775103522">"Sound"</string>
+    <string name="vibration_page_title" msgid="6519501440349124677">"Vibration"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-en-rXC/strings.xml b/packages/SoundPicker/res/values-en-rXC/strings.xml
index 8397e0b..d26c89b 100644
--- a/packages/SoundPicker/res/values-en-rXC/strings.xml
+++ b/packages/SoundPicker/res/values-en-rXC/strings.xml
@@ -26,4 +26,7 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‏‏‏‎‎‏‏‎‏‏‏‏‏‎‎‏‏‏‎‎‏‏‎‏‎‏‏‎‏‎‏‎‎‏‏‏‏‎‏‎‎‎‎‏‎‎‏‎‎‏‏‎‏‏‏‏‏‎‎Unable to add custom ringtone‎‏‎‎‏‎"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‏‎‎‏‎‎‎‎‏‏‎‎‎‏‏‎‏‎‎‎‎‏‏‏‏‎‎‎‏‏‏‎‏‎‏‏‎‎‏‏‏‏‏‎‎‎‎‏‎‎‏‏‏‎‏‎‎‎‎Unable to delete custom ringtone‎‏‎‎‏‎"</string>
     <string name="app_label" msgid="3091611356093417332">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‏‎‏‏‏‎‎‏‏‏‏‎‎‏‏‏‎‎‎‎‏‎‎‏‎‏‏‏‏‏‎‏‏‏‏‎‎‎‎‎‏‏‎‎‎‎‎‏‏‏‎‏‏‏‎‏‎‎‎Sounds‎‏‎‎‏‎"</string>
+    <string name="empty_list" msgid="2871978423955821191">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‏‏‏‏‎‏‏‎‏‏‎‏‎‏‎‎‎‏‎‎‏‎‏‎‎‏‎‎‎‎‎‎‏‎‎‏‏‏‏‎‎‏‎‎‎‏‎‎‏‎‏‎‎‎‎‏‏‏‎The list is empty‎‏‎‎‏‎"</string>
+    <string name="sound_page_title" msgid="2143312098775103522">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‎‏‏‎‏‏‏‏‏‎‏‎‎‏‎‎‏‎‏‏‏‏‏‏‏‏‏‎‎‎‎‎‏‎‎‏‎‎‏‎‎‏‎‏‏‏‏‎‎‎‎‎‏‎‎‎‏‎‎Sound‎‏‎‎‏‎"</string>
+    <string name="vibration_page_title" msgid="6519501440349124677">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‏‎‎‏‏‏‏‎‎‏‏‏‏‎‏‏‎‎‎‎‎‎‎‏‎‎‏‎‏‎‏‎‎‎‏‎‏‎‏‎‎‎‎‎‏‏‏‎‎‎‎‏‎‎‎‏‎‏‎Vibration‎‏‎‎‏‎"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-es-rUS/strings.xml b/packages/SoundPicker/res/values-es-rUS/strings.xml
index 5bf73b2..211bfed 100644
--- a/packages/SoundPicker/res/values-es-rUS/strings.xml
+++ b/packages/SoundPicker/res/values-es-rUS/strings.xml
@@ -26,4 +26,7 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"No se puede agregar el tono personalizado"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"No se puede borrar el tono personalizado"</string>
     <string name="app_label" msgid="3091611356093417332">"Sonidos"</string>
+    <string name="empty_list" msgid="2871978423955821191">"La lista está vacía"</string>
+    <string name="sound_page_title" msgid="2143312098775103522">"Sonido"</string>
+    <string name="vibration_page_title" msgid="6519501440349124677">"Vibración"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-es/strings.xml b/packages/SoundPicker/res/values-es/strings.xml
index a77f656..3fdad74 100644
--- a/packages/SoundPicker/res/values-es/strings.xml
+++ b/packages/SoundPicker/res/values-es/strings.xml
@@ -26,4 +26,7 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"No se ha podido añadir un tono de llamada personalizado"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"No se ha podido eliminar un tono de llamada personalizado"</string>
     <string name="app_label" msgid="3091611356093417332">"Sonidos"</string>
+    <string name="empty_list" msgid="2871978423955821191">"La lista está vacía"</string>
+    <string name="sound_page_title" msgid="2143312098775103522">"Sonido"</string>
+    <string name="vibration_page_title" msgid="6519501440349124677">"Vibración"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-et/strings.xml b/packages/SoundPicker/res/values-et/strings.xml
index fa680ac..3d9ee94 100644
--- a/packages/SoundPicker/res/values-et/strings.xml
+++ b/packages/SoundPicker/res/values-et/strings.xml
@@ -26,4 +26,7 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Kohandatud helinat ei õnnestu lisada"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Kohandatud helinat ei õnnestu kustutada"</string>
     <string name="app_label" msgid="3091611356093417332">"Helid"</string>
+    <string name="empty_list" msgid="2871978423955821191">"See loend on tühi"</string>
+    <string name="sound_page_title" msgid="2143312098775103522">"Heli"</string>
+    <string name="vibration_page_title" msgid="6519501440349124677">"Vibreerimine"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-eu/strings.xml b/packages/SoundPicker/res/values-eu/strings.xml
index e8e07fe..1f929bf 100644
--- a/packages/SoundPicker/res/values-eu/strings.xml
+++ b/packages/SoundPicker/res/values-eu/strings.xml
@@ -26,4 +26,7 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Ezin da gehitu tonu pertsonalizatua"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Ezin da ezabatu tonu pertsonalizatua"</string>
     <string name="app_label" msgid="3091611356093417332">"Soinuak"</string>
+    <string name="empty_list" msgid="2871978423955821191">"Zerrenda hutsik dago"</string>
+    <string name="sound_page_title" msgid="2143312098775103522">"Soinua"</string>
+    <string name="vibration_page_title" msgid="6519501440349124677">"Dardara"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-fa/strings.xml b/packages/SoundPicker/res/values-fa/strings.xml
index 769d5d5..3b75ac9 100644
--- a/packages/SoundPicker/res/values-fa/strings.xml
+++ b/packages/SoundPicker/res/values-fa/strings.xml
@@ -26,4 +26,7 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"افزودن آهنگ زنگ سفارشی ممکن نیست"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"حذف آهنگ زنگ سفارشی ممکن نیست"</string>
     <string name="app_label" msgid="3091611356093417332">"صداها"</string>
+    <string name="empty_list" msgid="2871978423955821191">"فهرست خالی است"</string>
+    <string name="sound_page_title" msgid="2143312098775103522">"صدا"</string>
+    <string name="vibration_page_title" msgid="6519501440349124677">"لرزش"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-fi/strings.xml b/packages/SoundPicker/res/values-fi/strings.xml
index fcda098..15bf658 100644
--- a/packages/SoundPicker/res/values-fi/strings.xml
+++ b/packages/SoundPicker/res/values-fi/strings.xml
@@ -26,4 +26,7 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Muokatun soittoäänen lisääminen epäonnistui."</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Muokatun soittoäänen poistaminen epäonnistui."</string>
     <string name="app_label" msgid="3091611356093417332">"Äänet"</string>
+    <string name="empty_list" msgid="2871978423955821191">"Lista on tyhjä"</string>
+    <string name="sound_page_title" msgid="2143312098775103522">"Ääni"</string>
+    <string name="vibration_page_title" msgid="6519501440349124677">"Värinä"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-fr-rCA/strings.xml b/packages/SoundPicker/res/values-fr-rCA/strings.xml
index 4d4545f..1cc170f 100644
--- a/packages/SoundPicker/res/values-fr-rCA/strings.xml
+++ b/packages/SoundPicker/res/values-fr-rCA/strings.xml
@@ -26,4 +26,7 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Impossible d\'ajouter une sonnerie personnalisée"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Impossible de supprimer la sonnerie personnalisée"</string>
     <string name="app_label" msgid="3091611356093417332">"Sons"</string>
+    <string name="empty_list" msgid="2871978423955821191">"La liste est vide"</string>
+    <string name="sound_page_title" msgid="2143312098775103522">"Son"</string>
+    <string name="vibration_page_title" msgid="6519501440349124677">"Vibration"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-fr/strings.xml b/packages/SoundPicker/res/values-fr/strings.xml
index 9452e70..ade2c16 100644
--- a/packages/SoundPicker/res/values-fr/strings.xml
+++ b/packages/SoundPicker/res/values-fr/strings.xml
@@ -26,4 +26,7 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Impossible d\'ajouter une sonnerie personnalisée"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Impossible de supprimer la sonnerie personnalisée"</string>
     <string name="app_label" msgid="3091611356093417332">"Sounds"</string>
+    <string name="empty_list" msgid="2871978423955821191">"La liste est vide"</string>
+    <string name="sound_page_title" msgid="2143312098775103522">"Son"</string>
+    <string name="vibration_page_title" msgid="6519501440349124677">"Vibration"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-gl/strings.xml b/packages/SoundPicker/res/values-gl/strings.xml
index 59a9d06..83f2b2e 100644
--- a/packages/SoundPicker/res/values-gl/strings.xml
+++ b/packages/SoundPicker/res/values-gl/strings.xml
@@ -26,4 +26,7 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Non se pode engadir un ton de chamada personalizado"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Non se pode eliminar un ton de chamada personalizado"</string>
     <string name="app_label" msgid="3091611356093417332">"Sons"</string>
+    <string name="empty_list" msgid="2871978423955821191">"A lista está baleira"</string>
+    <string name="sound_page_title" msgid="2143312098775103522">"Son"</string>
+    <string name="vibration_page_title" msgid="6519501440349124677">"Vibración"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-gu/strings.xml b/packages/SoundPicker/res/values-gu/strings.xml
index 209769f..8207512 100644
--- a/packages/SoundPicker/res/values-gu/strings.xml
+++ b/packages/SoundPicker/res/values-gu/strings.xml
@@ -26,4 +26,7 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"કસ્ટમ રિંગટોન ઉમેરવામાં અસમર્થ"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"કસ્ટમ રિંગટોન કાઢી નાખવામાં અસમર્થ"</string>
     <string name="app_label" msgid="3091611356093417332">"Sounds"</string>
+    <string name="empty_list" msgid="2871978423955821191">"સૂચિ ખાલી છે"</string>
+    <string name="sound_page_title" msgid="2143312098775103522">"સાઉન્ડ"</string>
+    <string name="vibration_page_title" msgid="6519501440349124677">"વાઇબ્રેશન"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-hi/strings.xml b/packages/SoundPicker/res/values-hi/strings.xml
index ab3b7f8..304201f 100644
--- a/packages/SoundPicker/res/values-hi/strings.xml
+++ b/packages/SoundPicker/res/values-hi/strings.xml
@@ -26,4 +26,7 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"आपके मुताबिक रिंगटोन नहीं जोड़ी जा सकी"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"आपके मुताबिक रिंगटोन नहीं हटाई जा सकी"</string>
     <string name="app_label" msgid="3091611356093417332">"Sounds"</string>
+    <string name="empty_list" msgid="2871978423955821191">"यह सूची खाली है"</string>
+    <string name="sound_page_title" msgid="2143312098775103522">"साउंड"</string>
+    <string name="vibration_page_title" msgid="6519501440349124677">"वाइब्रेशन"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-hr/strings.xml b/packages/SoundPicker/res/values-hr/strings.xml
index 3adc500..642c7d5 100644
--- a/packages/SoundPicker/res/values-hr/strings.xml
+++ b/packages/SoundPicker/res/values-hr/strings.xml
@@ -26,4 +26,7 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Dodavanje prilagođene melodije zvona nije moguće"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Brisanje prilagođene melodije zvona nije moguće"</string>
     <string name="app_label" msgid="3091611356093417332">"Zvukovi"</string>
+    <string name="empty_list" msgid="2871978423955821191">"Popis je prazan"</string>
+    <string name="sound_page_title" msgid="2143312098775103522">"Zvuk"</string>
+    <string name="vibration_page_title" msgid="6519501440349124677">"Vibracija"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-hu/strings.xml b/packages/SoundPicker/res/values-hu/strings.xml
index 32d4ba9..401da84 100644
--- a/packages/SoundPicker/res/values-hu/strings.xml
+++ b/packages/SoundPicker/res/values-hu/strings.xml
@@ -26,4 +26,7 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Nem sikerült hozzáadni az egyéni csengőhangot"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Nem sikerült törölni az egyéni csengőhangot"</string>
     <string name="app_label" msgid="3091611356093417332">"Hangok"</string>
+    <string name="empty_list" msgid="2871978423955821191">"A lista üres"</string>
+    <string name="sound_page_title" msgid="2143312098775103522">"Hang"</string>
+    <string name="vibration_page_title" msgid="6519501440349124677">"Rezgés"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-hy/strings.xml b/packages/SoundPicker/res/values-hy/strings.xml
index da8934f..d57345c 100644
--- a/packages/SoundPicker/res/values-hy/strings.xml
+++ b/packages/SoundPicker/res/values-hy/strings.xml
@@ -26,4 +26,7 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Հնարավոր չէ հատուկ զանգերանգ ավելացնել"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Հնարավոր չէ ջնջել հատուկ զանգերանգը"</string>
     <string name="app_label" msgid="3091611356093417332">"Ձայներ"</string>
+    <string name="empty_list" msgid="2871978423955821191">"Ցանկը դատարկ է"</string>
+    <string name="sound_page_title" msgid="2143312098775103522">"Ձայն"</string>
+    <string name="vibration_page_title" msgid="6519501440349124677">"Թրթռոց"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-in/strings.xml b/packages/SoundPicker/res/values-in/strings.xml
index 86dce64..518a09d 100644
--- a/packages/SoundPicker/res/values-in/strings.xml
+++ b/packages/SoundPicker/res/values-in/strings.xml
@@ -26,4 +26,7 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Tidak dapat menambahkan nada dering khusus"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Tidak dapat menghapus nada dering khusus"</string>
     <string name="app_label" msgid="3091611356093417332">"Sounds"</string>
+    <string name="empty_list" msgid="2871978423955821191">"Daftar ini kosong"</string>
+    <string name="sound_page_title" msgid="2143312098775103522">"Suara"</string>
+    <string name="vibration_page_title" msgid="6519501440349124677">"Getaran"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-is/strings.xml b/packages/SoundPicker/res/values-is/strings.xml
index d0fce78..7f05c79 100644
--- a/packages/SoundPicker/res/values-is/strings.xml
+++ b/packages/SoundPicker/res/values-is/strings.xml
@@ -26,4 +26,7 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Get ekki bætt sérsniðnum hringitóni við"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Get ekki eytt sérsniðnum hringitóni"</string>
     <string name="app_label" msgid="3091611356093417332">"Hljóð"</string>
+    <string name="empty_list" msgid="2871978423955821191">"Listinn er auður"</string>
+    <string name="sound_page_title" msgid="2143312098775103522">"Hljóð"</string>
+    <string name="vibration_page_title" msgid="6519501440349124677">"Titringur"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-it/strings.xml b/packages/SoundPicker/res/values-it/strings.xml
index 632cb41..e1d8e19 100644
--- a/packages/SoundPicker/res/values-it/strings.xml
+++ b/packages/SoundPicker/res/values-it/strings.xml
@@ -26,4 +26,7 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Impossibile aggiungere suoneria personalizzata"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Impossibile eliminare suoneria personalizzata"</string>
     <string name="app_label" msgid="3091611356093417332">"Sounds"</string>
+    <string name="empty_list" msgid="2871978423955821191">"L\'elenco è vuoto"</string>
+    <string name="sound_page_title" msgid="2143312098775103522">"Suoneria"</string>
+    <string name="vibration_page_title" msgid="6519501440349124677">"Vibrazione"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-iw/strings.xml b/packages/SoundPicker/res/values-iw/strings.xml
index 387b140..238370c9 100644
--- a/packages/SoundPicker/res/values-iw/strings.xml
+++ b/packages/SoundPicker/res/values-iw/strings.xml
@@ -26,4 +26,7 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"לא ניתן להוסיף רינגטון מותאם אישית"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"לא ניתן למחוק רינגטון מותאם אישית"</string>
     <string name="app_label" msgid="3091611356093417332">"צלילים"</string>
+    <string name="empty_list" msgid="2871978423955821191">"הרשימה ריקה"</string>
+    <string name="sound_page_title" msgid="2143312098775103522">"צליל"</string>
+    <string name="vibration_page_title" msgid="6519501440349124677">"רטט"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-ja/strings.xml b/packages/SoundPicker/res/values-ja/strings.xml
index 7c2aec6..408e870 100644
--- a/packages/SoundPicker/res/values-ja/strings.xml
+++ b/packages/SoundPicker/res/values-ja/strings.xml
@@ -26,4 +26,7 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"カスタム着信音を追加できません"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"カスタム着信音を削除できません"</string>
     <string name="app_label" msgid="3091611356093417332">"サウンド"</string>
+    <string name="empty_list" msgid="2871978423955821191">"このリストは空です"</string>
+    <string name="sound_page_title" msgid="2143312098775103522">"音"</string>
+    <string name="vibration_page_title" msgid="6519501440349124677">"バイブレーション"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-ka/strings.xml b/packages/SoundPicker/res/values-ka/strings.xml
index 1cfe240..83dfc3c 100644
--- a/packages/SoundPicker/res/values-ka/strings.xml
+++ b/packages/SoundPicker/res/values-ka/strings.xml
@@ -26,4 +26,7 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"მორგებული ზარის დამატება შეუძლებელია"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"მორგებული ზარის წაშლა შეუძლებელია"</string>
     <string name="app_label" msgid="3091611356093417332">"ხმები"</string>
+    <string name="empty_list" msgid="2871978423955821191">"სია ცარიელია"</string>
+    <string name="sound_page_title" msgid="2143312098775103522">"ხმა"</string>
+    <string name="vibration_page_title" msgid="6519501440349124677">"ვიბრაცია"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-kk/strings.xml b/packages/SoundPicker/res/values-kk/strings.xml
index 8c4c169..1815a95 100644
--- a/packages/SoundPicker/res/values-kk/strings.xml
+++ b/packages/SoundPicker/res/values-kk/strings.xml
@@ -26,4 +26,7 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Арнаулы рингтонды енгізу мүмкін емес"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Арнаулы рингтонды жою мүмкін емес"</string>
     <string name="app_label" msgid="3091611356093417332">"Дыбыстар"</string>
+    <string name="empty_list" msgid="2871978423955821191">"Тізім бос."</string>
+    <string name="sound_page_title" msgid="2143312098775103522">"Дыбыс"</string>
+    <string name="vibration_page_title" msgid="6519501440349124677">"Діріл"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-km/strings.xml b/packages/SoundPicker/res/values-km/strings.xml
index a334429..6ae048f 100644
--- a/packages/SoundPicker/res/values-km/strings.xml
+++ b/packages/SoundPicker/res/values-km/strings.xml
@@ -26,4 +26,7 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"មិន​អាច​បន្ថែម​សំឡេង​រោទ៍​ផ្ទាល់ខ្លួន​បាន"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"មិន​អាច​លុប​សំឡេង​រោទ៍​ផ្ទាល់ខ្លួន​បាន​ទេ"</string>
     <string name="app_label" msgid="3091611356093417332">"សំឡេង"</string>
+    <string name="empty_list" msgid="2871978423955821191">"បញ្ជីគឺទទេ"</string>
+    <string name="sound_page_title" msgid="2143312098775103522">"សំឡេង"</string>
+    <string name="vibration_page_title" msgid="6519501440349124677">"ការញ័រ"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-kn/strings.xml b/packages/SoundPicker/res/values-kn/strings.xml
index da90ccb..c528866 100644
--- a/packages/SoundPicker/res/values-kn/strings.xml
+++ b/packages/SoundPicker/res/values-kn/strings.xml
@@ -26,4 +26,7 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"ಕಸ್ಟಮ್ ರಿಂಗ್‌ಟೋನ್ ಸೇರಿಸಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"ಕಸ್ಟಮ್ ರಿಂಗ್‌ಟೋನ್ ಅಳಿಸಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ"</string>
     <string name="app_label" msgid="3091611356093417332">"ಧ್ವನಿಗಳು"</string>
+    <string name="empty_list" msgid="2871978423955821191">"ಪಟ್ಟಿ ಖಾಲಿಯಾಗಿದೆ"</string>
+    <string name="sound_page_title" msgid="2143312098775103522">"ಧ್ವನಿ"</string>
+    <string name="vibration_page_title" msgid="6519501440349124677">"ವೈಬ್ರೇಷನ್"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-ko/strings.xml b/packages/SoundPicker/res/values-ko/strings.xml
index 70554d6..bcab6b2 100644
--- a/packages/SoundPicker/res/values-ko/strings.xml
+++ b/packages/SoundPicker/res/values-ko/strings.xml
@@ -26,4 +26,7 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"맞춤 벨소리를 추가할 수 없습니다."</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"맞춤 벨소리를 삭제할 수 없습니다."</string>
     <string name="app_label" msgid="3091611356093417332">"소리"</string>
+    <string name="empty_list" msgid="2871978423955821191">"목록이 비어 있음"</string>
+    <string name="sound_page_title" msgid="2143312098775103522">"소리"</string>
+    <string name="vibration_page_title" msgid="6519501440349124677">"진동"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-ky/strings.xml b/packages/SoundPicker/res/values-ky/strings.xml
index 3c95228..babd8c0 100644
--- a/packages/SoundPicker/res/values-ky/strings.xml
+++ b/packages/SoundPicker/res/values-ky/strings.xml
@@ -26,4 +26,7 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Жеке рингтон кошулбай жатат"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Жеке рингтон жок кылынбай жатат"</string>
     <string name="app_label" msgid="3091611356093417332">"Үндөр"</string>
+    <string name="empty_list" msgid="2871978423955821191">"Тизме бош"</string>
+    <string name="sound_page_title" msgid="2143312098775103522">"Үн"</string>
+    <string name="vibration_page_title" msgid="6519501440349124677">"Дирилдөө"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-lo/strings.xml b/packages/SoundPicker/res/values-lo/strings.xml
index 8bcae0d..5e496e8 100644
--- a/packages/SoundPicker/res/values-lo/strings.xml
+++ b/packages/SoundPicker/res/values-lo/strings.xml
@@ -26,4 +26,7 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Unable to add custom ringtone"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Unable to delete custom ringtone"</string>
     <string name="app_label" msgid="3091611356093417332">"ສຽງ"</string>
+    <string name="empty_list" msgid="2871978423955821191">"ລາຍຊື່ຫວ່າງເປົ່າ"</string>
+    <string name="sound_page_title" msgid="2143312098775103522">"ສຽງ"</string>
+    <string name="vibration_page_title" msgid="6519501440349124677">"ການສັ່ນເຕືອນ"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-lt/strings.xml b/packages/SoundPicker/res/values-lt/strings.xml
index c7ea369..c68cc3b 100644
--- a/packages/SoundPicker/res/values-lt/strings.xml
+++ b/packages/SoundPicker/res/values-lt/strings.xml
@@ -26,4 +26,7 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Nepavyksta pridėti tinkinto skambėjimo tono"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Nepavyksta ištrinti tinkinto skambėjimo tono"</string>
     <string name="app_label" msgid="3091611356093417332">"Garsai"</string>
+    <string name="empty_list" msgid="2871978423955821191">"Sąrašas yra tuščias"</string>
+    <string name="sound_page_title" msgid="2143312098775103522">"Garsas"</string>
+    <string name="vibration_page_title" msgid="6519501440349124677">"Vibravimas"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-lv/strings.xml b/packages/SoundPicker/res/values-lv/strings.xml
index 2a26289..fab7f8c 100644
--- a/packages/SoundPicker/res/values-lv/strings.xml
+++ b/packages/SoundPicker/res/values-lv/strings.xml
@@ -26,4 +26,7 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Nevar pievienot pielāgotu zvana signālu"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Nevar izdzēst pielāgotu zvana signālu"</string>
     <string name="app_label" msgid="3091611356093417332">"Skaņas"</string>
+    <string name="empty_list" msgid="2871978423955821191">"Saraksts ir tukšs"</string>
+    <string name="sound_page_title" msgid="2143312098775103522">"Skaņa"</string>
+    <string name="vibration_page_title" msgid="6519501440349124677">"Vibrācija"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-mk/strings.xml b/packages/SoundPicker/res/values-mk/strings.xml
index 545d5ed..4f2322a 100644
--- a/packages/SoundPicker/res/values-mk/strings.xml
+++ b/packages/SoundPicker/res/values-mk/strings.xml
@@ -26,4 +26,7 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Не може да се додаде приспособена мелодија"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Не може да се избрише приспособена мелодија"</string>
     <string name="app_label" msgid="3091611356093417332">"Звуци"</string>
+    <string name="empty_list" msgid="2871978423955821191">"Списокот е празен"</string>
+    <string name="sound_page_title" msgid="2143312098775103522">"Звук"</string>
+    <string name="vibration_page_title" msgid="6519501440349124677">"Вибрации"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-ml/strings.xml b/packages/SoundPicker/res/values-ml/strings.xml
index 21da8e8..16cf725 100644
--- a/packages/SoundPicker/res/values-ml/strings.xml
+++ b/packages/SoundPicker/res/values-ml/strings.xml
@@ -26,4 +26,7 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"ഇഷ്ടാനുസൃത റിംഗ്‌ടോൺ ചേർക്കാനാവില്ല"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"ഇഷ്ടാനുസൃത റിംഗ്‌ടോൺ ഇല്ലാതാക്കാനാവില്ല"</string>
     <string name="app_label" msgid="3091611356093417332">"ശബ്‌ദങ്ങൾ"</string>
+    <string name="empty_list" msgid="2871978423955821191">"ലിസ്‌റ്റിൽ ഒന്നുമില്ല"</string>
+    <string name="sound_page_title" msgid="2143312098775103522">"ശബ്‌ദം"</string>
+    <string name="vibration_page_title" msgid="6519501440349124677">"വൈബ്രേഷൻ"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-mn/strings.xml b/packages/SoundPicker/res/values-mn/strings.xml
index 15f7d12..6215a41 100644
--- a/packages/SoundPicker/res/values-mn/strings.xml
+++ b/packages/SoundPicker/res/values-mn/strings.xml
@@ -26,4 +26,7 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Захиалгат хонхны ая нэмэх боломжгүй"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Захиалгат хонхны ая устгах боломжгүй"</string>
     <string name="app_label" msgid="3091611356093417332">"Дуу чимээ"</string>
+    <string name="empty_list" msgid="2871978423955821191">"Жагсаалт хоосон байна"</string>
+    <string name="sound_page_title" msgid="2143312098775103522">"Дуу"</string>
+    <string name="vibration_page_title" msgid="6519501440349124677">"Чичиргээ"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-mr/strings.xml b/packages/SoundPicker/res/values-mr/strings.xml
index 3ddb991..fe2fe12 100644
--- a/packages/SoundPicker/res/values-mr/strings.xml
+++ b/packages/SoundPicker/res/values-mr/strings.xml
@@ -26,4 +26,7 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"कस्टम रिंगटोन जोडण्यात अक्षम"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"कस्टम रिंगटोन हटविण्यात अक्षम"</string>
     <string name="app_label" msgid="3091611356093417332">"आवाज"</string>
+    <string name="empty_list" msgid="2871978423955821191">"ही सूची रिकामी आहे"</string>
+    <string name="sound_page_title" msgid="2143312098775103522">"आवाज"</string>
+    <string name="vibration_page_title" msgid="6519501440349124677">"व्हायब्रेशन"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-ms/strings.xml b/packages/SoundPicker/res/values-ms/strings.xml
index 9d87d72..5788f18 100644
--- a/packages/SoundPicker/res/values-ms/strings.xml
+++ b/packages/SoundPicker/res/values-ms/strings.xml
@@ -26,4 +26,7 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Tidak dapat menambah nada dering tersuai"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Tidak dapat memadamkan nada dering tersuai"</string>
     <string name="app_label" msgid="3091611356093417332">"Bunyi"</string>
+    <string name="empty_list" msgid="2871978423955821191">"Senarai ini kosong"</string>
+    <string name="sound_page_title" msgid="2143312098775103522">"Bunyi"</string>
+    <string name="vibration_page_title" msgid="6519501440349124677">"Getaran"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-my/strings.xml b/packages/SoundPicker/res/values-my/strings.xml
index 62163e9..0a1a4cf 100644
--- a/packages/SoundPicker/res/values-my/strings.xml
+++ b/packages/SoundPicker/res/values-my/strings.xml
@@ -26,4 +26,7 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"စိတ်ကြိုက်ဖုန်းမြည်သံကို ထည့်သွင်း၍မရပါ"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"စိတ်ကြိုက်ဖုန်းမြည်သံကို ဖျက်၍မရပါ"</string>
     <string name="app_label" msgid="3091611356093417332">"အသံများ"</string>
+    <string name="empty_list" msgid="2871978423955821191">"ဤစာရင်းတွင် မည်သည့်အရာမျှမရှိပါ"</string>
+    <string name="sound_page_title" msgid="2143312098775103522">"အသံ"</string>
+    <string name="vibration_page_title" msgid="6519501440349124677">"တုန်ခါမှု"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-nb/strings.xml b/packages/SoundPicker/res/values-nb/strings.xml
index e4e259a..c315801 100644
--- a/packages/SoundPicker/res/values-nb/strings.xml
+++ b/packages/SoundPicker/res/values-nb/strings.xml
@@ -26,4 +26,7 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Kan ikke legge til egendefinert ringelyd"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Kan ikke slette egendefinert ringelyd"</string>
     <string name="app_label" msgid="3091611356093417332">"Lyder"</string>
+    <string name="empty_list" msgid="2871978423955821191">"Listen er tom"</string>
+    <string name="sound_page_title" msgid="2143312098775103522">"Lyd"</string>
+    <string name="vibration_page_title" msgid="6519501440349124677">"Vibrering"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-ne/strings.xml b/packages/SoundPicker/res/values-ne/strings.xml
index 0a2bceb..b95b70c 100644
--- a/packages/SoundPicker/res/values-ne/strings.xml
+++ b/packages/SoundPicker/res/values-ne/strings.xml
@@ -26,4 +26,7 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"आफू अनुकूल रिङटोन थप्न सकिएन"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"आफू अनुकूल रिङटोनलाई मेट्न सकिएन"</string>
     <string name="app_label" msgid="3091611356093417332">"ध्वनिहरू"</string>
+    <string name="empty_list" msgid="2871978423955821191">"यो सूची खाली छ"</string>
+    <string name="sound_page_title" msgid="2143312098775103522">"ध्वनि"</string>
+    <string name="vibration_page_title" msgid="6519501440349124677">"भाइब्रेसन"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-nl/strings.xml b/packages/SoundPicker/res/values-nl/strings.xml
index 5b6fb70..192431b 100644
--- a/packages/SoundPicker/res/values-nl/strings.xml
+++ b/packages/SoundPicker/res/values-nl/strings.xml
@@ -26,4 +26,7 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Toevoegen van aangepaste ringtone is mislukt"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Verwijderen van aangepaste ringtone is mislukt"</string>
     <string name="app_label" msgid="3091611356093417332">"Geluiden"</string>
+    <string name="empty_list" msgid="2871978423955821191">"De lijst is leeg"</string>
+    <string name="sound_page_title" msgid="2143312098775103522">"Geluid"</string>
+    <string name="vibration_page_title" msgid="6519501440349124677">"Trillen"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-or/strings.xml b/packages/SoundPicker/res/values-or/strings.xml
index 45ce594..5d82039 100644
--- a/packages/SoundPicker/res/values-or/strings.xml
+++ b/packages/SoundPicker/res/values-or/strings.xml
@@ -26,4 +26,7 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"କଷ୍ଟମ୍‍ ରିଙ୍ଗଟୋନ୍‍ ଯୋଡ଼ିପାରିବ ନାହିଁ"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"କଷ୍ଟମ୍‍ ରିଙ୍ଗଟୋନ୍‍ ଡିଲିଟ୍‍ କରିପାରିବ ନାହିଁ"</string>
     <string name="app_label" msgid="3091611356093417332">"ସାଉଣ୍ଡ"</string>
+    <string name="empty_list" msgid="2871978423955821191">"ତାଲିକା ଖାଲି ଅଛି"</string>
+    <string name="sound_page_title" msgid="2143312098775103522">"ସାଉଣ୍ଡ"</string>
+    <string name="vibration_page_title" msgid="6519501440349124677">"ଭାଇବ୍ରେସନ"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-pa/strings.xml b/packages/SoundPicker/res/values-pa/strings.xml
index 1e62f64..63ecb9d 100644
--- a/packages/SoundPicker/res/values-pa/strings.xml
+++ b/packages/SoundPicker/res/values-pa/strings.xml
@@ -26,4 +26,7 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"ਵਿਉਂਤੀ ਰਿੰਗਟੋਨ ਨੂੰ ਸ਼ਾਮਲ ਕਰਨ ਦੇ ਅਯੋਗ"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"ਵਿਉਂਤੀ ਰਿੰਗਟੋਨ ਨੂੰ ਮਿਟਾਉਣ ਦੇ ਅਯੋਗ"</string>
     <string name="app_label" msgid="3091611356093417332">"Sounds"</string>
+    <string name="empty_list" msgid="2871978423955821191">"ਇਹ ਸੂਚੀ ਖਾਲੀ ਹੈ"</string>
+    <string name="sound_page_title" msgid="2143312098775103522">"ਅਵਾਜ਼"</string>
+    <string name="vibration_page_title" msgid="6519501440349124677">"ਥਰਥਰਾਹਟ"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-pl/strings.xml b/packages/SoundPicker/res/values-pl/strings.xml
index 1b3b5c4..310f40f 100644
--- a/packages/SoundPicker/res/values-pl/strings.xml
+++ b/packages/SoundPicker/res/values-pl/strings.xml
@@ -26,4 +26,7 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Nie można dodać dzwonka niestandardowego"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Nie można usunąć dzwonka niestandardowego"</string>
     <string name="app_label" msgid="3091611356093417332">"Dźwięki"</string>
+    <string name="empty_list" msgid="2871978423955821191">"Lista jest pusta"</string>
+    <string name="sound_page_title" msgid="2143312098775103522">"Dźwięk"</string>
+    <string name="vibration_page_title" msgid="6519501440349124677">"Wibracje"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-pt-rBR/strings.xml b/packages/SoundPicker/res/values-pt-rBR/strings.xml
index 7b545e1..9f58ca0 100644
--- a/packages/SoundPicker/res/values-pt-rBR/strings.xml
+++ b/packages/SoundPicker/res/values-pt-rBR/strings.xml
@@ -26,4 +26,7 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Não foi possível adicionar o toque personalizado"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Não foi possível excluir o toque personalizado"</string>
     <string name="app_label" msgid="3091611356093417332">"Sons"</string>
+    <string name="empty_list" msgid="2871978423955821191">"A lista está vazia"</string>
+    <string name="sound_page_title" msgid="2143312098775103522">"Som"</string>
+    <string name="vibration_page_title" msgid="6519501440349124677">"Vibração"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-pt-rPT/strings.xml b/packages/SoundPicker/res/values-pt-rPT/strings.xml
index 5d742f1..fd4e69f 100644
--- a/packages/SoundPicker/res/values-pt-rPT/strings.xml
+++ b/packages/SoundPicker/res/values-pt-rPT/strings.xml
@@ -26,4 +26,7 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Não foi possível adicionar o toque personalizado"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Não foi possível eliminar o toque personalizado"</string>
     <string name="app_label" msgid="3091611356093417332">"Sons"</string>
+    <string name="empty_list" msgid="2871978423955821191">"A lista está vazia"</string>
+    <string name="sound_page_title" msgid="2143312098775103522">"Som"</string>
+    <string name="vibration_page_title" msgid="6519501440349124677">"Vibração"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-pt/strings.xml b/packages/SoundPicker/res/values-pt/strings.xml
index 7b545e1..9f58ca0 100644
--- a/packages/SoundPicker/res/values-pt/strings.xml
+++ b/packages/SoundPicker/res/values-pt/strings.xml
@@ -26,4 +26,7 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Não foi possível adicionar o toque personalizado"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Não foi possível excluir o toque personalizado"</string>
     <string name="app_label" msgid="3091611356093417332">"Sons"</string>
+    <string name="empty_list" msgid="2871978423955821191">"A lista está vazia"</string>
+    <string name="sound_page_title" msgid="2143312098775103522">"Som"</string>
+    <string name="vibration_page_title" msgid="6519501440349124677">"Vibração"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-ro/strings.xml b/packages/SoundPicker/res/values-ro/strings.xml
index 58b5aeb..08d937c 100644
--- a/packages/SoundPicker/res/values-ro/strings.xml
+++ b/packages/SoundPicker/res/values-ro/strings.xml
@@ -26,4 +26,7 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Nu se poate adăuga tonul de sonerie personalizat"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Nu se poate șterge tonul de sonerie personalizat"</string>
     <string name="app_label" msgid="3091611356093417332">"Sunete"</string>
+    <string name="empty_list" msgid="2871978423955821191">"Lista este goală"</string>
+    <string name="sound_page_title" msgid="2143312098775103522">"Sunet"</string>
+    <string name="vibration_page_title" msgid="6519501440349124677">"Vibrație"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-ru/strings.xml b/packages/SoundPicker/res/values-ru/strings.xml
index 0d48ac1..be5495a 100644
--- a/packages/SoundPicker/res/values-ru/strings.xml
+++ b/packages/SoundPicker/res/values-ru/strings.xml
@@ -26,4 +26,7 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Не удалось добавить рингтон"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Не удалось удалить рингтон"</string>
     <string name="app_label" msgid="3091611356093417332">"Звуки"</string>
+    <string name="empty_list" msgid="2871978423955821191">"Список пуст"</string>
+    <string name="sound_page_title" msgid="2143312098775103522">"Звук"</string>
+    <string name="vibration_page_title" msgid="6519501440349124677">"Вибрация"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-si/strings.xml b/packages/SoundPicker/res/values-si/strings.xml
index 1872b6b..6ba86cb 100644
--- a/packages/SoundPicker/res/values-si/strings.xml
+++ b/packages/SoundPicker/res/values-si/strings.xml
@@ -26,4 +26,7 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"අභිරුචි නාද රිද්මය එක් කළ නොහැකිය"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"අභිරුචි නාද රිද්මය මැකිය නොහැකිය"</string>
     <string name="app_label" msgid="3091611356093417332">"ශබ්ද"</string>
+    <string name="empty_list" msgid="2871978423955821191">"ලැයිස්තුව හිස් ය"</string>
+    <string name="sound_page_title" msgid="2143312098775103522">"ශබ්දය"</string>
+    <string name="vibration_page_title" msgid="6519501440349124677">"කම්පනය"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-sk/strings.xml b/packages/SoundPicker/res/values-sk/strings.xml
index 8ff6d12..8f54350 100644
--- a/packages/SoundPicker/res/values-sk/strings.xml
+++ b/packages/SoundPicker/res/values-sk/strings.xml
@@ -26,4 +26,7 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Nepodarilo sa pridať vlastný tón zvonenia"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Nepodarilo sa odstrániť vlastný tón zvonenia"</string>
     <string name="app_label" msgid="3091611356093417332">"Zvuky"</string>
+    <string name="empty_list" msgid="2871978423955821191">"Zoznam je prázdny"</string>
+    <string name="sound_page_title" msgid="2143312098775103522">"Zvuk"</string>
+    <string name="vibration_page_title" msgid="6519501440349124677">"Vibrácie"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-sl/strings.xml b/packages/SoundPicker/res/values-sl/strings.xml
index 77a2a2c..1a818b2 100644
--- a/packages/SoundPicker/res/values-sl/strings.xml
+++ b/packages/SoundPicker/res/values-sl/strings.xml
@@ -26,4 +26,7 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Tona zvonjenja po meri ni mogoče dodati"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Tona zvonjenja po meri ni mogoče izbrisati"</string>
     <string name="app_label" msgid="3091611356093417332">"Zvoki"</string>
+    <string name="empty_list" msgid="2871978423955821191">"Seznam je prazen"</string>
+    <string name="sound_page_title" msgid="2143312098775103522">"Zvok"</string>
+    <string name="vibration_page_title" msgid="6519501440349124677">"Vibriranje"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-sq/strings.xml b/packages/SoundPicker/res/values-sq/strings.xml
index e35dd71..4c497bb 100644
--- a/packages/SoundPicker/res/values-sq/strings.xml
+++ b/packages/SoundPicker/res/values-sq/strings.xml
@@ -26,4 +26,7 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Nuk mund të shtojë ton zileje të personalizuar"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Nuk mund të fshijë ton zileje të personalizuar"</string>
     <string name="app_label" msgid="3091611356093417332">"Tingujt"</string>
+    <string name="empty_list" msgid="2871978423955821191">"Lista është bosh"</string>
+    <string name="sound_page_title" msgid="2143312098775103522">"Me tingull"</string>
+    <string name="vibration_page_title" msgid="6519501440349124677">"Me dridhje"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-sr/strings.xml b/packages/SoundPicker/res/values-sr/strings.xml
index bc573f5..2ec1f17 100644
--- a/packages/SoundPicker/res/values-sr/strings.xml
+++ b/packages/SoundPicker/res/values-sr/strings.xml
@@ -26,4 +26,7 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Додавање прилагођене мелодије звона није успело"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Брисање прилагођене мелодије звона није успело"</string>
     <string name="app_label" msgid="3091611356093417332">"Звукови"</string>
+    <string name="empty_list" msgid="2871978423955821191">"Листа је празна"</string>
+    <string name="sound_page_title" msgid="2143312098775103522">"Звук"</string>
+    <string name="vibration_page_title" msgid="6519501440349124677">"Вибрирање"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-sv/strings.xml b/packages/SoundPicker/res/values-sv/strings.xml
index c1dd1c2..cb1cd3a 100644
--- a/packages/SoundPicker/res/values-sv/strings.xml
+++ b/packages/SoundPicker/res/values-sv/strings.xml
@@ -26,4 +26,7 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Det gick inte att lägga till en egen ringsignal"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Det gick inte att radera den egna ringsignalen"</string>
     <string name="app_label" msgid="3091611356093417332">"Ljud"</string>
+    <string name="empty_list" msgid="2871978423955821191">"Listan är tom"</string>
+    <string name="sound_page_title" msgid="2143312098775103522">"Ljud"</string>
+    <string name="vibration_page_title" msgid="6519501440349124677">"Vibration"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-sw/strings.xml b/packages/SoundPicker/res/values-sw/strings.xml
index b023450..8fd6d58 100644
--- a/packages/SoundPicker/res/values-sw/strings.xml
+++ b/packages/SoundPicker/res/values-sw/strings.xml
@@ -26,4 +26,7 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Imeshindwa kuongeza mlio maalum wa simu"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Imeshindwa kufuta mlio maalum wa simu"</string>
     <string name="app_label" msgid="3091611356093417332">"Sauti"</string>
+    <string name="empty_list" msgid="2871978423955821191">"Orodha hii haina chochote"</string>
+    <string name="sound_page_title" msgid="2143312098775103522">"Sauti"</string>
+    <string name="vibration_page_title" msgid="6519501440349124677">"Mtetemo"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-ta/strings.xml b/packages/SoundPicker/res/values-ta/strings.xml
index 38e45b7..692e58a 100644
--- a/packages/SoundPicker/res/values-ta/strings.xml
+++ b/packages/SoundPicker/res/values-ta/strings.xml
@@ -26,4 +26,7 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"பிரத்தியேக ரிங்டோனைச் சேர்க்க முடியவில்லை"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"பிரத்தியேக ரிங்டோனை நீக்க முடியவில்லை"</string>
     <string name="app_label" msgid="3091611356093417332">"ஒலிகள்"</string>
+    <string name="empty_list" msgid="2871978423955821191">"பட்டியல் காலியாக உள்ளது"</string>
+    <string name="sound_page_title" msgid="2143312098775103522">"ஒலி"</string>
+    <string name="vibration_page_title" msgid="6519501440349124677">"அதிர்வு"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-te/strings.xml b/packages/SoundPicker/res/values-te/strings.xml
index 2d03ac0..ce13e53 100644
--- a/packages/SoundPicker/res/values-te/strings.xml
+++ b/packages/SoundPicker/res/values-te/strings.xml
@@ -26,4 +26,7 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"అనుకూల రింగ్‌టోన్‌ను జోడించలేకపోయింది"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"అనుకూల రింగ్‌టోన్‌ను తొలగించలేకపోయింది"</string>
     <string name="app_label" msgid="3091611356093417332">"ధ్వనులు"</string>
+    <string name="empty_list" msgid="2871978423955821191">"మీ లిస్ట్ ఖాళీగా ఉంది"</string>
+    <string name="sound_page_title" msgid="2143312098775103522">"సౌండ్"</string>
+    <string name="vibration_page_title" msgid="6519501440349124677">"వైబ్రేషన్"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-th/strings.xml b/packages/SoundPicker/res/values-th/strings.xml
index cc2e43f..d5dc9b7 100644
--- a/packages/SoundPicker/res/values-th/strings.xml
+++ b/packages/SoundPicker/res/values-th/strings.xml
@@ -26,4 +26,7 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"ไม่สามารถเพิ่มเสียงเรียกเข้าที่กำหนดเอง"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"ไม่สามารถลบเสียงเรียกเข้าที่กำหนดเอง"</string>
     <string name="app_label" msgid="3091611356093417332">"เสียง"</string>
+    <string name="empty_list" msgid="2871978423955821191">"รายการว่างเปล่า"</string>
+    <string name="sound_page_title" msgid="2143312098775103522">"เสียง"</string>
+    <string name="vibration_page_title" msgid="6519501440349124677">"การสั่น"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-tl/strings.xml b/packages/SoundPicker/res/values-tl/strings.xml
index c0c1712..4e3bf7e 100644
--- a/packages/SoundPicker/res/values-tl/strings.xml
+++ b/packages/SoundPicker/res/values-tl/strings.xml
@@ -26,4 +26,7 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Hindi maidagdag ang custom na ringtone"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Hindi ma-delete ang custom na ringtone"</string>
     <string name="app_label" msgid="3091611356093417332">"Mga Tunog"</string>
+    <string name="empty_list" msgid="2871978423955821191">"Walang laman ang listahan"</string>
+    <string name="sound_page_title" msgid="2143312098775103522">"Tunog"</string>
+    <string name="vibration_page_title" msgid="6519501440349124677">"Pag-vibrate"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-tr/strings.xml b/packages/SoundPicker/res/values-tr/strings.xml
index 955c23f..51ac541 100644
--- a/packages/SoundPicker/res/values-tr/strings.xml
+++ b/packages/SoundPicker/res/values-tr/strings.xml
@@ -26,4 +26,7 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Özel zil sesi eklenemiyor"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Özel zil sesi silinemiyor"</string>
     <string name="app_label" msgid="3091611356093417332">"Sesler"</string>
+    <string name="empty_list" msgid="2871978423955821191">"Liste boş"</string>
+    <string name="sound_page_title" msgid="2143312098775103522">"Ses"</string>
+    <string name="vibration_page_title" msgid="6519501440349124677">"Titreşim"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-uk/strings.xml b/packages/SoundPicker/res/values-uk/strings.xml
index 42dbfb0..a905b95 100644
--- a/packages/SoundPicker/res/values-uk/strings.xml
+++ b/packages/SoundPicker/res/values-uk/strings.xml
@@ -26,4 +26,7 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Не вдалося додати користувацький сигнал дзвінка"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Не вдалося видалити користувацький сигнал дзвінка"</string>
     <string name="app_label" msgid="3091611356093417332">"Звуки"</string>
+    <string name="empty_list" msgid="2871978423955821191">"Список пустий"</string>
+    <string name="sound_page_title" msgid="2143312098775103522">"Звук"</string>
+    <string name="vibration_page_title" msgid="6519501440349124677">"Вібрація"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-ur/strings.xml b/packages/SoundPicker/res/values-ur/strings.xml
index 58141d6..9cae118 100644
--- a/packages/SoundPicker/res/values-ur/strings.xml
+++ b/packages/SoundPicker/res/values-ur/strings.xml
@@ -26,4 +26,7 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"حسب ضرورت رنگ ٹون شامل کرنے سے قاصر ہے"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"حسب ضرورت رنگ ٹون حذف کرنے سے قاصر ہے"</string>
     <string name="app_label" msgid="3091611356093417332">"آوازیں"</string>
+    <string name="empty_list" msgid="2871978423955821191">"فہرست خالی ہے"</string>
+    <string name="sound_page_title" msgid="2143312098775103522">"آواز"</string>
+    <string name="vibration_page_title" msgid="6519501440349124677">"وائبریشن"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-uz/strings.xml b/packages/SoundPicker/res/values-uz/strings.xml
index c39db5f..e6231f2 100644
--- a/packages/SoundPicker/res/values-uz/strings.xml
+++ b/packages/SoundPicker/res/values-uz/strings.xml
@@ -26,4 +26,7 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Maxsus rington qo‘shib bo‘lmadi"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Maxsus ringtonni o‘chirib bo‘lmadi"</string>
     <string name="app_label" msgid="3091611356093417332">"Tovushlar"</string>
+    <string name="empty_list" msgid="2871978423955821191">"Roʻyxat boʻsh"</string>
+    <string name="sound_page_title" msgid="2143312098775103522">"Tovush"</string>
+    <string name="vibration_page_title" msgid="6519501440349124677">"Tebranish"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-vi/strings.xml b/packages/SoundPicker/res/values-vi/strings.xml
index bed0e96..070ac16 100644
--- a/packages/SoundPicker/res/values-vi/strings.xml
+++ b/packages/SoundPicker/res/values-vi/strings.xml
@@ -26,4 +26,7 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Không thể thêm nhạc chuông tùy chỉnh"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Không thể xóa nhạc chuông tùy chỉnh"</string>
     <string name="app_label" msgid="3091611356093417332">"Âm thanh"</string>
+    <string name="empty_list" msgid="2871978423955821191">"Danh sách này đang trống"</string>
+    <string name="sound_page_title" msgid="2143312098775103522">"Âm thanh"</string>
+    <string name="vibration_page_title" msgid="6519501440349124677">"Rung"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-zh-rCN/strings.xml b/packages/SoundPicker/res/values-zh-rCN/strings.xml
index 864aaae..0420fc9 100644
--- a/packages/SoundPicker/res/values-zh-rCN/strings.xml
+++ b/packages/SoundPicker/res/values-zh-rCN/strings.xml
@@ -26,4 +26,7 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"无法添加自定义铃声"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"无法删除自定义铃声"</string>
     <string name="app_label" msgid="3091611356093417332">"声音"</string>
+    <string name="empty_list" msgid="2871978423955821191">"列表为空"</string>
+    <string name="sound_page_title" msgid="2143312098775103522">"声音"</string>
+    <string name="vibration_page_title" msgid="6519501440349124677">"振动"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-zh-rHK/strings.xml b/packages/SoundPicker/res/values-zh-rHK/strings.xml
index 4cde32d..60d9a52 100644
--- a/packages/SoundPicker/res/values-zh-rHK/strings.xml
+++ b/packages/SoundPicker/res/values-zh-rHK/strings.xml
@@ -26,4 +26,7 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"無法加入自訂鈴聲"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"無法刪除自訂鈴聲"</string>
     <string name="app_label" msgid="3091611356093417332">"音效"</string>
+    <string name="empty_list" msgid="2871978423955821191">"清單中沒有內容"</string>
+    <string name="sound_page_title" msgid="2143312098775103522">"音效"</string>
+    <string name="vibration_page_title" msgid="6519501440349124677">"震動"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-zh-rTW/strings.xml b/packages/SoundPicker/res/values-zh-rTW/strings.xml
index df8a66a..c173c0a 100644
--- a/packages/SoundPicker/res/values-zh-rTW/strings.xml
+++ b/packages/SoundPicker/res/values-zh-rTW/strings.xml
@@ -26,4 +26,7 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"無法新增自訂鈴聲"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"無法刪除自訂鈴聲"</string>
     <string name="app_label" msgid="3091611356093417332">"音效"</string>
+    <string name="empty_list" msgid="2871978423955821191">"清單中沒有任何項目"</string>
+    <string name="sound_page_title" msgid="2143312098775103522">"音效"</string>
+    <string name="vibration_page_title" msgid="6519501440349124677">"震動"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values-zu/strings.xml b/packages/SoundPicker/res/values-zu/strings.xml
index 29a8ffe..978e925 100644
--- a/packages/SoundPicker/res/values-zu/strings.xml
+++ b/packages/SoundPicker/res/values-zu/strings.xml
@@ -26,4 +26,7 @@
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Ayikwazi ukwengeza ithoni yokukhala yangokwezifiso"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Ayikwazi ukususa ithoni yokukhala yangokwezifiso"</string>
     <string name="app_label" msgid="3091611356093417332">"Imisindo"</string>
+    <string name="empty_list" msgid="2871978423955821191">"Uhlu alunalutho"</string>
+    <string name="sound_page_title" msgid="2143312098775103522">"Umsindo"</string>
+    <string name="vibration_page_title" msgid="6519501440349124677">"Ukudlidliza"</string>
 </resources>
diff --git a/packages/SoundPicker/res/values/strings.xml b/packages/SoundPicker/res/values/strings.xml
index 04a2c2b..ab7b95a 100644
--- a/packages/SoundPicker/res/values/strings.xml
+++ b/packages/SoundPicker/res/values/strings.xml
@@ -40,4 +40,8 @@
 
     <!-- Text for the name of the app. [CHAR LIMIT=12] -->
     <string name="app_label">Sounds</string>
+
+    <string name="empty_list">The list is empty</string>
+    <string name="sound_page_title">Sound</string>
+    <string name="vibration_page_title">Vibration</string>
 </resources>
diff --git a/packages/SoundPicker/src/com/android/soundpicker/BasePickerFragment.java b/packages/SoundPicker/src/com/android/soundpicker/BasePickerFragment.java
new file mode 100644
index 0000000..4fc2a86
--- /dev/null
+++ b/packages/SoundPicker/src/com/android/soundpicker/BasePickerFragment.java
@@ -0,0 +1,312 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.soundpicker;
+
+import android.app.Activity;
+import android.content.ContentProvider;
+import android.content.Intent;
+import android.graphics.drawable.Drawable;
+import android.media.RingtoneManager;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.MediaStore;
+import android.util.Log;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.fragment.app.Fragment;
+import androidx.lifecycle.ViewModelProvider;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import dagger.hilt.android.AndroidEntryPoint;
+
+import java.util.Objects;
+
+/**
+ * Base class for generic picker fragments.
+ *
+ * <p>This fragment displays a recycler view that is populated by a {@link RingtoneListViewAdapter}
+ * with data provided by a {@link RingtoneListHandler}. Each item can be selected on click,
+ * which also triggers a ringtone preview performed by the shared {@link RingtonePickerViewModel}.
+ * The ringtone preview uses the selection state of all picker fragments (e.g. sound selected by
+ * one fragment and vibration selected by another).
+ */
+@AndroidEntryPoint(Fragment.class)
+public abstract class BasePickerFragment extends Hilt_BasePickerFragment implements
+        RingtoneListViewAdapter.Callbacks {
+
+    private static final String TAG = "BasePickerFragment";
+    private static final String COLUMN_LABEL = MediaStore.Audio.Media.TITLE;
+    private boolean mIsManagedProfile;
+    private Drawable mWorkIconDrawable;
+
+    protected RingtoneListViewAdapter mRingtoneListViewAdapter;
+    protected RecyclerView mRecyclerView;
+    protected RingtonePickerViewModel.Config mPickerConfig;
+    protected RingtonePickerViewModel mRingtonePickerViewModel;
+    protected RingtoneListHandler.Config mRingtoneListConfig;
+    protected RingtoneListHandler mRingtoneListHandler;
+
+    public BasePickerFragment() {
+        super(R.layout.fragment_ringtone_picker);
+    }
+
+    @Override
+    public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
+        super.onViewCreated(view, savedInstanceState);
+        mRingtonePickerViewModel = new ViewModelProvider(requireActivity()).get(
+                RingtonePickerViewModel.class);
+        mRingtoneListHandler = getRingtoneListHandler();
+        mRecyclerView = view.requireViewById(R.id.recycler_view);
+
+        mPickerConfig = mRingtonePickerViewModel.getPickerConfig();
+        mRingtoneListConfig = mRingtoneListHandler.getRingtoneListConfig();
+
+        mIsManagedProfile = UserManager.get(requireActivity()).isManagedProfile(
+                mPickerConfig.userId);
+
+        mRingtoneListViewAdapter = createRingtoneListViewAdapter();
+        mRecyclerView.setHasFixedSize(true);
+        mRecyclerView.setAdapter(mRingtoneListViewAdapter);
+        mRecyclerView.setLayoutManager(new LinearLayoutManager(requireActivity()));
+        setSelectedItem(mRingtoneListHandler.getSelectedItemPosition());
+        prepareRecyclerView(mRecyclerView);
+    }
+
+    @Override
+    public boolean isWorkRingtone(int position) {
+        if (!mIsManagedProfile) {
+            return false;
+        }
+
+        /*
+         * Display the work icon if the ringtone belongs to a work profile. We
+         * can tell that a ringtone belongs to a work profile if the picker user
+         * is a managed profile, the ringtone Uri is in external storage, and
+         * either the uri has no user id or has the id of the picker user
+         */
+        Uri currentUri = mRingtoneListHandler.getRingtoneUri(position);
+        int uriUserId = ContentProvider.getUserIdFromUri(currentUri,
+                mPickerConfig.userId);
+        Uri uriWithoutUserId = ContentProvider.getUriWithoutUserId(currentUri);
+
+        return uriUserId == mPickerConfig.userId
+                && uriWithoutUserId.toString().startsWith(
+                MediaStore.Audio.Media.EXTERNAL_CONTENT_URI.toString());
+    }
+
+    @Override
+    public Drawable getWorkIconDrawable() {
+        if (mWorkIconDrawable == null) {
+            mWorkIconDrawable = requireActivity().getPackageManager()
+                    .getUserBadgeForDensityNoBackground(
+                            UserHandle.of(mPickerConfig.userId), /* density= */ -1);
+        }
+
+        return mWorkIconDrawable;
+    }
+
+    @Override
+    public void onRingtoneSelected(int position) {
+        setSelectedItem(position);
+
+        // In the buttonless (watch-only) version, preemptively set our result since
+        // we won't have another chance to do so before the activity closes.
+        if (!mPickerConfig.showOkCancelButtons) {
+            setSuccessResultWithSelectedRingtone();
+        }
+
+        // Play clip
+        mRingtonePickerViewModel.playRingtone();
+    }
+
+    @Override
+    public void onAddRingtoneSelected() {
+        addRingtoneAsync();
+    }
+
+    /**
+     * Sets up the list by adding fixed items to the top and bottom, if required. And sets the
+     * selected item in the list.
+     * @param recyclerView The recyclerview that contains the list of displayed items.
+     */
+    protected void prepareRecyclerView(@NonNull RecyclerView recyclerView) {
+        // Reset the static item count, as this method can be called multiple times
+        mRingtoneListHandler.resetFixedItems();
+
+        if (mRingtoneListConfig.hasDefaultItem) {
+            int defaultItemPos = addDefaultRingtoneItem();
+
+            if (getSelectedItem() < 0
+                    && RingtoneManager.isDefault(mRingtoneListConfig.initialSelectedUri)) {
+                setSelectedItem(defaultItemPos);
+            }
+        }
+
+        if (mRingtoneListConfig.hasSilentItem) {
+            int silentItemPos = addSilentItem();
+
+            // The 'Silent' item should use a null Uri
+            if (getSelectedItem() < 0
+                    && mRingtoneListConfig.initialSelectedUri == null) {
+                setSelectedItem(silentItemPos);
+            }
+        }
+
+        if (getSelectedItem() < 0) {
+            setSelectedItem(mRingtoneListHandler.getRingtonePosition(
+                    mRingtoneListConfig.initialSelectedUri));
+        }
+
+        // In the buttonless (watch-only) version, preemptively set our result since we won't
+        // have another chance to do so before the activity closes.
+        if (!mPickerConfig.showOkCancelButtons) {
+            setSuccessResultWithSelectedRingtone();
+        }
+
+        addNewRingtoneItem();
+
+        // Enable context menu in ringtone items
+        registerForContextMenu(recyclerView);
+    }
+
+    /**
+     * Returns the fragment's sound/vibration list handler.
+     * @return The ringtone list handler.
+     */
+    protected abstract RingtoneListHandler getRingtoneListHandler();
+
+    /**
+     * Starts the process to add a new ringtone to the list of ringtones asynchronously.
+     * Currently, only works for adding sound files.
+     */
+    protected abstract void addRingtoneAsync();
+
+    /**
+     * Adds an item to the end of the list that can be used to add new ringtones to the list.
+     * Currently, only works for adding sound files.
+     */
+    protected abstract void addNewRingtoneItem();
+
+    protected int getSelectedItem() {
+        return mRingtoneListHandler.getSelectedItemPosition();
+    }
+
+    /**
+     * Returns the selected URI to the caller activity.
+     */
+    protected void setSuccessResultWithSelectedRingtone() {
+        requireActivity().setResult(Activity.RESULT_OK,
+                new Intent().putExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI,
+                        mRingtonePickerViewModel.getSelectedRingtoneUri()));
+    }
+
+    /**
+     * Creates a ringtone recyclerview adapter using the ringtone manager cursor.
+     * @return The created RingtoneListViewAdapter.
+     */
+    protected RingtoneListViewAdapter createRingtoneListViewAdapter() {
+        LocalizedCursor cursor = new LocalizedCursor(
+                mRingtoneListHandler.getRingtoneCursor(), getResources(), COLUMN_LABEL);
+        return new RingtoneListViewAdapter(cursor, /* RingtoneListViewAdapterCallbacks= */ this);
+    }
+
+    /**
+     * Sets the selected item in the list and scroll to the position in the recyclerview.
+     * @param pos the position of the selected item in the list.
+     */
+    protected void setSelectedItem(int pos) {
+        Objects.requireNonNull(mRingtoneListViewAdapter);
+        mRingtoneListHandler.setSelectedItemPosition(pos);
+        mRingtoneListViewAdapter.setSelectedItem(pos);
+        mRingtoneListHandler.setSelectedItemId(mRingtoneListViewAdapter.getItemId(pos));
+        mRecyclerView.scrollToPosition(pos);
+    }
+
+    /**
+     * Adds a fixed item to the fixed items list . A fixed item is one that is not from
+     * the RingtoneManager.
+     *
+     * @param textResId The resource ID of the text for the item.
+     * @return The index of the inserted fixed item in the adapter.
+     */
+    protected int addFixedItem(int textResId) {
+        return mRingtoneListViewAdapter.addTitleForFixedItem(textResId);
+    }
+
+    /**
+     * Re-query RingtoneManager for the most recent set of installed ringtones. May move the
+     * selected item position to match the new position of the chosen ringtone.
+     * <p>
+     * This should only need to happen after adding or removing a ringtone.
+     */
+    protected void requeryForAdapter() {
+        mRingtonePickerViewModel.reinit();
+        // Refresh and set a new cursor, and closing the old one.
+        mRingtoneListViewAdapter = createRingtoneListViewAdapter();
+        mRecyclerView.setAdapter(mRingtoneListViewAdapter);
+        prepareRecyclerView(mRecyclerView);
+
+        // Update selected item location.
+        for (int i = 0; i < mRingtoneListViewAdapter.getItemCount(); i++) {
+            if (mRingtoneListViewAdapter.getItemId(i)
+                    == mRingtoneListHandler.getSelectedItemId()) {
+                setSelectedItem(i);
+                return;
+            }
+        }
+
+        // If selected item is still unknown, then set it to the default item, if available.
+        // If it's not available, then attempt to set it to the silent item in the list.
+        int selectedPosition = mRingtoneListHandler.getDefaultItemPosition();
+
+        if (selectedPosition < 0) {
+            selectedPosition = mRingtoneListHandler.getSilentItemPosition();
+        }
+
+        setSelectedItem(selectedPosition);
+    }
+
+    private int addDefaultRingtoneItem() {
+        int defaultItemPosInAdapter = addFixedItem(
+                RingtonePickerViewModel.getDefaultRingtoneItemTextByType(
+                        mPickerConfig.ringtoneType));
+        int defaultItemPosInListHandler = mRingtoneListHandler.addDefaultItem();
+
+        if (defaultItemPosInAdapter != defaultItemPosInListHandler) {
+            Log.wtf(TAG, "Default item position in adapter and list handler must match.");
+            return RingtoneListHandler.ITEM_POSITION_UNKNOWN;
+        }
+
+        return defaultItemPosInListHandler;
+    }
+
+    private int addSilentItem() {
+        int silentItemPosInAdapter = addFixedItem(com.android.internal.R.string.ringtone_silent);
+        int silentItemPosInListHandler = mRingtoneListHandler.addSilentItem();
+
+        if (silentItemPosInAdapter != silentItemPosInListHandler) {
+            Log.wtf(TAG, "Silent item position in adapter and list handler must match.");
+            return RingtoneListHandler.ITEM_POSITION_UNKNOWN;
+        }
+
+        return silentItemPosInListHandler;
+    }
+}
diff --git a/packages/SoundPicker/src/com/android/soundpicker/ListeningExecutorServiceFactory.java b/packages/SoundPicker/src/com/android/soundpicker/ListeningExecutorServiceFactory.java
new file mode 100644
index 0000000..afdbf05
--- /dev/null
+++ b/packages/SoundPicker/src/com/android/soundpicker/ListeningExecutorServiceFactory.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.soundpicker;
+
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.common.util.concurrent.MoreExecutors;
+
+import java.util.concurrent.Executors;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * A factory class used to create {@link ListeningExecutorService}.
+ */
+@Singleton
+public class ListeningExecutorServiceFactory {
+
+    @Inject
+    ListeningExecutorServiceFactory() {
+    }
+
+    /**
+     * Returns a single thread {@link ListeningExecutorService}.
+     *
+     */
+    public ListeningExecutorService createSingleThreadExecutor() {
+        return MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor());
+    }
+}
diff --git a/packages/SoundPicker/src/com/android/soundpicker/LocalizedCursor.java b/packages/SoundPicker/src/com/android/soundpicker/LocalizedCursor.java
new file mode 100644
index 0000000..83d04a3
--- /dev/null
+++ b/packages/SoundPicker/src/com/android/soundpicker/LocalizedCursor.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 com.android.soundpicker;
+
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.database.CursorWrapper;
+import android.util.Log;
+import android.util.TypedValue;
+
+import androidx.annotation.Nullable;
+
+import java.util.Locale;
+import java.util.regex.Pattern;
+
+/**
+ * A cursor wrapper class mainly used to guarantee getting a ringtone title
+ */
+final class LocalizedCursor extends CursorWrapper {
+
+    private static final String TAG = "LocalizedCursor";
+    private static final String SOUND_NAME_RES_PREFIX = "sound_name_";
+
+    private final int mTitleIndex;
+    private final Resources mResources;
+    private final Pattern mSanitizePattern;
+    private final String mNamePrefix;
+
+    LocalizedCursor(Cursor cursor, Resources resources, String columnLabel) {
+        super(cursor);
+        mTitleIndex = mCursor.getColumnIndex(columnLabel);
+        mResources = resources;
+        mSanitizePattern = Pattern.compile("[^a-zA-Z0-9]");
+        if (mTitleIndex == -1) {
+            Log.e(TAG, "No index for column " + columnLabel);
+            mNamePrefix = null;
+        } else {
+            mNamePrefix = buildNamePrefix(mResources);
+        }
+    }
+
+    /**
+     * Builds the prefix for the name of the resource to look up.
+     * The format is: "ResourcePackageName::ResourceTypeName/" (the type name is expected to be
+     * "string" but let's not hardcode it).
+     * Here we use an existing resource "notification_sound_default" which is always expected to be
+     * found.
+     *
+     * @param resources Application's resources
+     * @return the built name prefix, or null if failed to build.
+     */
+    @Nullable
+    private static String buildNamePrefix(Resources resources) {
+        try {
+            return String.format("%s:%s/%s",
+                    resources.getResourcePackageName(R.string.notification_sound_default),
+                    resources.getResourceTypeName(R.string.notification_sound_default),
+                    SOUND_NAME_RES_PREFIX);
+        } catch (Resources.NotFoundException e) {
+            Log.e(TAG, "Failed to build the prefix for the name of the resource.", e);
+        }
+
+        return null;
+    }
+
+    /**
+     * Process resource name to generate a valid resource name.
+     *
+     * @return a non-null String
+     */
+    private String sanitize(String input) {
+        if (input == null) {
+            return "";
+        }
+        return mSanitizePattern.matcher(input).replaceAll("_").toLowerCase(Locale.ROOT);
+    }
+
+    @Override
+    public String getString(int columnIndex) {
+        final String defaultName = mCursor.getString(columnIndex);
+        if ((columnIndex != mTitleIndex) || (mNamePrefix == null)) {
+            return defaultName;
+        }
+        TypedValue value = new TypedValue();
+        try {
+            // the name currently in the database is used to derive a name to match
+            // against resource names in this package
+            mResources.getValue(mNamePrefix + sanitize(defaultName), value,
+                    /* resolveRefs= */ false);
+        } catch (Resources.NotFoundException e) {
+            Log.d(TAG, "Failed to get localized string. Using default string instead.", e);
+            return defaultName;
+        }
+        if ((value != null) && (value.type == TypedValue.TYPE_STRING)) {
+            Log.d(TAG, String.format("Replacing name %s with %s",
+                    defaultName, value.string.toString()));
+            return value.string.toString();
+        } else {
+            Log.e(TAG, "Invalid value when looking up localized name, using " + defaultName);
+            return defaultName;
+        }
+    }
+}
diff --git a/packages/SoundPicker/src/com/android/soundpicker/RingtoneFactory.java b/packages/SoundPicker/src/com/android/soundpicker/RingtoneFactory.java
new file mode 100644
index 0000000..cb41eab
--- /dev/null
+++ b/packages/SoundPicker/src/com/android/soundpicker/RingtoneFactory.java
@@ -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.soundpicker;
+
+import android.content.Context;
+import android.media.AudioAttributes;
+import android.media.Ringtone;
+import android.net.Uri;
+
+import dagger.hilt.android.qualifiers.ApplicationContext;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * A factory class used to create {@link Ringtone}.
+ */
+@Singleton
+public class RingtoneFactory {
+
+    private final Context mApplicationContext;
+
+    @Inject
+    RingtoneFactory(@ApplicationContext Context applicationContext) {
+        mApplicationContext = applicationContext;
+    }
+
+    /**
+     * Returns a {@link Ringtone} built from the provided URI and audio attributes flags.
+     *
+     * @param uri The URI used to build the {@link Ringtone}.
+     * @param audioAttributesFlags A combination of audio attribute flags that affect the volume
+     *                             and settings when playing the ringtone.
+     * @return the built {@link Ringtone}.
+     */
+    public Ringtone create(Uri uri, int audioAttributesFlags) {
+        AudioAttributes audioAttributes = new AudioAttributes.Builder()
+                .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
+                .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
+                .setFlags(audioAttributesFlags)
+                .build();
+        // TODO: We are currently only using MEDIA_SOUND for enabledMedia. This will change once we
+        //  start playing sound and/or vibration.
+        return new Ringtone.Builder(mApplicationContext, Ringtone.MEDIA_SOUND, audioAttributes)
+                .setUri(uri)
+                .build();
+    }
+}
diff --git a/packages/SoundPicker/src/com/android/soundpicker/RingtoneListHandler.java b/packages/SoundPicker/src/com/android/soundpicker/RingtoneListHandler.java
new file mode 100644
index 0000000..bb38e0e
--- /dev/null
+++ b/packages/SoundPicker/src/com/android/soundpicker/RingtoneListHandler.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.soundpicker;
+
+import static java.util.Objects.requireNonNull;
+
+import android.annotation.Nullable;
+import android.database.Cursor;
+import android.media.RingtoneManager;
+import android.net.Uri;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import javax.inject.Inject;
+
+/**
+ * Handles ringtone list state and actions. This includes keeping track of the selected item,
+ * ringtone manager cursor and added items to the list.
+ */
+public class RingtoneListHandler {
+
+    // TODO: We're using an empty URI instead of null, because null URIs still produce a sound,
+    //  while empty ones don't (Potentially this might be due to empty URIs being perceived as
+    //  malformed ones). We will switch to using the official silent URIs (SOUND_OFF, VIBRATION_OFF)
+    //  once they become available.
+    static final Uri SILENT_URI = Uri.EMPTY;
+    static final int ITEM_POSITION_UNKNOWN = -1;
+
+    private static final String TAG = "RingtoneListHandler";
+
+    /** The position in the list of the 'Silent' item. */
+    private int mSilentItemPosition = ITEM_POSITION_UNKNOWN;
+    /** The position in the list of the 'Default' item. */
+    private int mDefaultItemPosition = ITEM_POSITION_UNKNOWN;
+    /** The number of fixed items in the list. */
+    private int mFixedItemCount;
+    /**
+     * Stable ID for the ringtone that is currently selected (may be -1 if no ringtone is selected).
+     */
+    private long mSelectedItemId = -1;
+    private int mSelectedItemPosition = ITEM_POSITION_UNKNOWN;
+
+    private RingtoneManager mRingtoneManager;
+    private Config mRingtoneListConfig;
+    private Cursor mRingtoneCursor;
+
+    /**
+     * Holds immutable info on the ringtone list that is displayed.
+     */
+    static final class Config {
+        /**
+         * Whether this list has the 'Default' item.
+         */
+        public final boolean hasDefaultItem;
+        /**
+         * The Uri to play when the 'Default' item is clicked.
+         */
+        public final Uri uriForDefaultItem;
+        /**
+         * Whether this list has the 'Silent' item.
+         */
+        public final boolean hasSilentItem;
+        /**
+         * The initially selected uri in the list.
+         */
+        public final Uri initialSelectedUri;
+
+        Config(boolean hasDefaultItem, Uri uriForDefaultItem, boolean hasSilentItem,
+                Uri initialSelectedUri) {
+            this.hasDefaultItem = hasDefaultItem;
+            this.uriForDefaultItem = uriForDefaultItem;
+            this.hasSilentItem = hasSilentItem;
+            this.initialSelectedUri = initialSelectedUri;
+        }
+    }
+
+    @Inject
+    RingtoneListHandler() {
+    }
+
+    void init(@NonNull Config ringtoneListConfig,
+            @NonNull RingtoneManager ringtoneManager, @NonNull Cursor ringtoneCursor) {
+        mRingtoneManager = requireNonNull(ringtoneManager);
+        mRingtoneListConfig = requireNonNull(ringtoneListConfig);
+        mRingtoneCursor = requireNonNull(ringtoneCursor);
+    }
+
+    Config getRingtoneListConfig() {
+        return mRingtoneListConfig;
+    }
+
+    Cursor getRingtoneCursor() {
+        requireInitCalled();
+        return mRingtoneCursor;
+    }
+
+    Uri getRingtoneUri(int position) {
+        if (position < 0) {
+            Log.w(TAG, "Selected item position is unknown.");
+            // When the selected item is ITEM_POSITION_UNKNOWN, it is not the case we expected.
+            // We return SILENT_URI for this case.
+            return SILENT_URI;
+        } else if (position == mDefaultItemPosition) {
+            // Use the default Uri that they originally gave us.
+            return mRingtoneListConfig.uriForDefaultItem;
+        } else if (position == mSilentItemPosition) {
+            // Use SILENT_URI for the 'Silent' item.
+            return SILENT_URI;
+        } else {
+            requireInitCalled();
+            return mRingtoneManager.getRingtoneUri(mapListPositionToRingtonePosition(position));
+        }
+    }
+
+    int getRingtonePosition(Uri uri) {
+        requireInitCalled();
+        return mapRingtonePositionToListPosition(mRingtoneManager.getRingtonePosition(uri));
+    }
+
+    void resetFixedItems() {
+        mFixedItemCount = 0;
+        mDefaultItemPosition = ITEM_POSITION_UNKNOWN;
+        mSilentItemPosition = ITEM_POSITION_UNKNOWN;
+    }
+
+    int addDefaultItem() {
+        if (mDefaultItemPosition < 0) {
+            mDefaultItemPosition = addFixedItem();
+        }
+        return mDefaultItemPosition;
+    }
+
+    int getDefaultItemPosition() {
+        return mDefaultItemPosition;
+    }
+
+    int addSilentItem() {
+        if (mSilentItemPosition < 0) {
+            mSilentItemPosition = addFixedItem();
+        }
+        return mSilentItemPosition;
+    }
+
+    public int getSilentItemPosition() {
+        return mSilentItemPosition;
+    }
+
+    int getSelectedItemPosition() {
+        return mSelectedItemPosition;
+    }
+
+    void setSelectedItemPosition(int selectedItemPosition) {
+        mSelectedItemPosition = selectedItemPosition;
+    }
+
+    void setSelectedItemId(long selectedItemId) {
+        mSelectedItemId = selectedItemId;
+    }
+
+    long getSelectedItemId() {
+        return mSelectedItemId;
+    }
+
+    @Nullable
+    Uri getSelectedRingtoneUri() {
+        return getRingtoneUri(mSelectedItemPosition);
+    }
+
+    /**
+     * Maps the item position in the list, to its equivalent position in the RingtoneManager.
+     *
+     * @param itemPosition the position of item in the list.
+     * @return position of the item in the RingtoneManager.
+     */
+    private int mapListPositionToRingtonePosition(int itemPosition) {
+        // If the manager position is less than add items, then return that.
+        if (itemPosition < mFixedItemCount) return itemPosition;
+
+        return itemPosition - mFixedItemCount;
+    }
+
+    /**
+     * Maps the item position in the RingtoneManager, to its equivalent position in the list.
+     *
+     * @param itemPosition the position of the item in the RingtoneManager.
+     * @return position of the item in the list.
+     */
+    private int mapRingtonePositionToListPosition(int itemPosition) {
+        // If the manager position is less than add items, then return that.
+        if (itemPosition < 0) return itemPosition;
+
+        return itemPosition + mFixedItemCount;
+    }
+
+    /**
+     * Increments the number of added fixed items and returns the index of the newest added item.
+     * @return index of the newest added fixed item.
+     */
+    private int addFixedItem() {
+        return mFixedItemCount++;
+    }
+
+    private void requireInitCalled() {
+        requireNonNull(mRingtoneManager);
+        requireNonNull(mRingtoneCursor);
+    }
+}
diff --git a/packages/SoundPicker/src/com/android/soundpicker/RingtoneListViewAdapter.java b/packages/SoundPicker/src/com/android/soundpicker/RingtoneListViewAdapter.java
new file mode 100644
index 0000000..4ca8943
--- /dev/null
+++ b/packages/SoundPicker/src/com/android/soundpicker/RingtoneListViewAdapter.java
@@ -0,0 +1,264 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.soundpicker;
+
+import static com.android.internal.widget.RecyclerView.NO_ID;
+
+import android.database.Cursor;
+import android.graphics.drawable.Drawable;
+import android.media.RingtoneManager;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.CheckedTextView;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import androidx.annotation.StringRes;
+import androidx.recyclerview.widget.RecyclerView;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * The adapter presents a list of ringtones which may include fixed item in the list and an action
+ * button at the end.
+ *
+ * The adapter handles three different types of items:
+ * <ul>
+ * <li>FIXED: Fixed items are items added to the top of the list. These items can not be modified
+ * and their position will never change.
+ * <li>DYNAMIC: Dynamic items are items from the ringtone manager. These items can be modified
+ * and their position can change.
+ * <li>FOOTER: A footer item is an added button to the end of the list. This item can be clicked
+ * but not selected and its position will never change.
+ * </ul>
+ */
+final class RingtoneListViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
+
+    private static final int VIEW_TYPE_FIXED_ITEM = 0;
+    private static final int VIEW_TYPE_DYNAMIC_ITEM = 1;
+    private static final int VIEW_TYPE_ADD_RINGTONE_ITEM = 2;
+    private final Cursor mCursor;
+    private final List<Integer> mFixedItemTitles;
+    private final Callbacks mCallbacks;
+    private final int mRowIDColumn;
+    private int mSelectedItem = -1;
+    @StringRes private Integer mAddRingtoneItemTitle;
+
+    /** Provides callbacks for the adapter. */
+    interface Callbacks {
+        void onRingtoneSelected(int position);
+        void onAddRingtoneSelected();
+        boolean isWorkRingtone(int position);
+        Drawable getWorkIconDrawable();
+    }
+
+    RingtoneListViewAdapter(Cursor cursor,
+            Callbacks callbacks) {
+        mCursor = cursor;
+        mCallbacks = callbacks;
+        mFixedItemTitles = new ArrayList<>();
+        mRowIDColumn = mCursor != null ? mCursor.getColumnIndex("_id") : -1;
+        setHasStableIds(true);
+    }
+
+    void setSelectedItem(int position) {
+        notifyItemChanged(mSelectedItem);
+        mSelectedItem = position;
+        notifyItemChanged(mSelectedItem);
+    }
+
+    /**
+     * Adds title to the fixed items list and returns the index of the newest added item.
+     * @param textResId the title to add to the fixed items list.
+     * @return The index of the newest added item in the fixed items list.
+     */
+    int addTitleForFixedItem(@StringRes int textResId) {
+        mFixedItemTitles.add(textResId);
+        notifyItemInserted(mFixedItemTitles.size() - 1);
+        return mFixedItemTitles.size() - 1;
+    }
+
+    void addTitleForAddRingtoneItem(@StringRes int textResId) {
+        mAddRingtoneItemTitle = textResId;
+        notifyItemInserted(getItemCount() - 1);
+    }
+
+    @NotNull
+    @Override
+    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+        LayoutInflater inflater = LayoutInflater.from(parent.getContext());
+
+        if (viewType == VIEW_TYPE_FIXED_ITEM) {
+            View fixedItemView = inflater.inflate(
+                    com.android.internal.R.layout.select_dialog_singlechoice_material, parent,
+                    false);
+
+            return new FixedItemViewHolder(fixedItemView, mCallbacks);
+        }
+
+        if (viewType == VIEW_TYPE_ADD_RINGTONE_ITEM) {
+            View addRingtoneItemView = inflater.inflate(R.layout.add_new_sound_item, parent, false);
+
+            return new AddRingtoneItemViewHolder(addRingtoneItemView,
+                    mCallbacks);
+        }
+
+        View view = inflater.inflate(R.layout.radio_with_work_badge, parent, false);
+
+        return new DynamicItemViewHolder(view, mCallbacks);
+    }
+
+    @Override
+    public void onBindViewHolder(@NotNull RecyclerView.ViewHolder holder, int position) {
+        if (holder instanceof FixedItemViewHolder) {
+            FixedItemViewHolder viewHolder = (FixedItemViewHolder) holder;
+
+            viewHolder.onBind(mFixedItemTitles.get(position),
+                    /* isChecked= */ position == mSelectedItem);
+            return;
+        }
+        if (holder instanceof AddRingtoneItemViewHolder) {
+            AddRingtoneItemViewHolder viewHolder = (AddRingtoneItemViewHolder) holder;
+
+            viewHolder.onBind(mAddRingtoneItemTitle);
+            return;
+        }
+
+        if (!(holder instanceof DynamicItemViewHolder)) {
+            throw new IllegalArgumentException("holder type is not supported");
+        }
+
+        DynamicItemViewHolder viewHolder = (DynamicItemViewHolder) holder;
+        int pos = position - mFixedItemTitles.size();
+        if (!mCursor.moveToPosition(pos)) {
+            throw new IllegalStateException("Could not move cursor to position: " + pos);
+        }
+
+        Drawable workIcon = (mCallbacks != null)
+                && mCallbacks.isWorkRingtone(position)
+                ? mCallbacks.getWorkIconDrawable() : null;
+
+        viewHolder.onBind(mCursor.getString(RingtoneManager.TITLE_COLUMN_INDEX),
+                /* isChecked= */ position == mSelectedItem, workIcon);
+    }
+
+    @Override
+    public int getItemViewType(int position) {
+        if (!mFixedItemTitles.isEmpty() && position < mFixedItemTitles.size()) {
+            return VIEW_TYPE_FIXED_ITEM;
+        }
+        if (mAddRingtoneItemTitle != null && position == getItemCount() - 1) {
+            return VIEW_TYPE_ADD_RINGTONE_ITEM;
+        }
+
+        return VIEW_TYPE_DYNAMIC_ITEM;
+    }
+
+    @Override
+    public int getItemCount() {
+        int itemCount = mFixedItemTitles.size() + mCursor.getCount();
+
+        if (mAddRingtoneItemTitle != null) {
+            itemCount++;
+        }
+
+        return itemCount;
+    }
+
+    @Override
+    public long getItemId(int position) {
+        int itemViewType = getItemViewType(position);
+        if (itemViewType == VIEW_TYPE_FIXED_ITEM) {
+            // Since the item is a fixed item, then we can use the position as a stable ID
+            // since the order of the fixed items should never change.
+            return position;
+        }
+        if (itemViewType == VIEW_TYPE_DYNAMIC_ITEM && mCursor != null
+                && mCursor.moveToPosition(position - mFixedItemTitles.size())
+                && mRowIDColumn != -1) {
+            return mCursor.getLong(mRowIDColumn) + mFixedItemTitles.size();
+        }
+
+        // The position is either invalid or the item is the add ringtone item view, so no stable
+        // ID is returned. Add ringtone item view cannot be selected and only include an action
+        // buttons.
+        return NO_ID;
+    }
+
+    private static class DynamicItemViewHolder extends RecyclerView.ViewHolder {
+        private final CheckedTextView mTitleTextView;
+        private final ImageView mWorkIcon;
+
+        DynamicItemViewHolder(View itemView, Callbacks listener) {
+            super(itemView);
+
+            mTitleTextView = itemView.requireViewById(R.id.checked_text_view);
+            mWorkIcon = itemView.requireViewById(R.id.work_icon);
+            itemView.setOnClickListener(v -> listener.onRingtoneSelected(this.getLayoutPosition()));
+        }
+
+        void onBind(String title, boolean isChecked, Drawable workIcon) {
+            mTitleTextView.setText(title);
+            mTitleTextView.setChecked(isChecked);
+
+            if (workIcon == null) {
+                mWorkIcon.setVisibility(View.GONE);
+            } else {
+                mWorkIcon.setImageDrawable(workIcon);
+                mWorkIcon.setVisibility(View.VISIBLE);
+            }
+        }
+    }
+
+    private static class FixedItemViewHolder extends RecyclerView.ViewHolder {
+        private final CheckedTextView mTitleTextView;
+
+        FixedItemViewHolder(View itemView, Callbacks listener) {
+            super(itemView);
+
+            mTitleTextView = (CheckedTextView) itemView;
+            itemView.setOnClickListener(v -> listener.onRingtoneSelected(this.getLayoutPosition()));
+        }
+
+        void onBind(@StringRes int title, boolean isChecked) {
+            Objects.requireNonNull(mTitleTextView);
+
+            mTitleTextView.setText(title);
+            mTitleTextView.setChecked(isChecked);
+        }
+    }
+
+    private static class AddRingtoneItemViewHolder extends RecyclerView.ViewHolder {
+        private final TextView mTitleTextView;
+
+        AddRingtoneItemViewHolder(View itemView, Callbacks listener) {
+            super(itemView);
+
+            mTitleTextView = itemView.requireViewById(R.id.add_new_sound_text);
+            itemView.setOnClickListener(v -> listener.onAddRingtoneSelected());
+        }
+
+        void onBind(@StringRes int title) {
+            mTitleTextView.setText(title);
+        }
+    }
+}
diff --git a/packages/SoundPicker/src/com/android/soundpicker/RingtoneManagerFactory.java b/packages/SoundPicker/src/com/android/soundpicker/RingtoneManagerFactory.java
new file mode 100644
index 0000000..f08eb24
--- /dev/null
+++ b/packages/SoundPicker/src/com/android/soundpicker/RingtoneManagerFactory.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.soundpicker;
+
+import android.content.Context;
+import android.media.RingtoneManager;
+
+import dagger.hilt.android.qualifiers.ApplicationContext;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * A factory class used to create {@link RingtoneManager}.
+ */
+@Singleton
+public class RingtoneManagerFactory {
+
+    private final Context mApplicationContext;
+
+    @Inject
+    RingtoneManagerFactory(@ApplicationContext Context applicationContext) {
+        mApplicationContext = applicationContext;
+    }
+
+    /**
+     * Creates a new {@link RingtoneManager} and returns it.
+     *
+     * @return a {@link RingtoneManager}
+     */
+    public RingtoneManager create() {
+        return new RingtoneManager(mApplicationContext, /* includeParentRingtones */ true);
+    }
+}
+
diff --git a/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerActivity.java b/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerActivity.java
index 56b940c..90a14f9 100644
--- a/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerActivity.java
+++ b/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerActivity.java
@@ -16,43 +16,19 @@
 
 package com.android.soundpicker;
 
-import android.content.ContentProvider;
-import android.content.Context;
-import android.content.DialogInterface;
 import android.content.Intent;
-import android.content.res.Resources;
-import android.content.res.Resources.NotFoundException;
-import android.database.Cursor;
-import android.database.CursorWrapper;
-import android.media.AudioAttributes;
-import android.media.Ringtone;
 import android.media.RingtoneManager;
 import android.net.Uri;
-import android.os.AsyncTask;
 import android.os.Bundle;
-import android.os.Environment;
-import android.os.Handler;
 import android.os.UserHandle;
-import android.os.UserManager;
-import android.provider.MediaStore;
-import android.provider.Settings;
 import android.util.Log;
-import android.util.TypedValue;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.AdapterView;
-import android.widget.CursorAdapter;
-import android.widget.ImageView;
-import android.widget.ListView;
-import android.widget.TextView;
-import android.widget.Toast;
 
-import com.android.internal.app.AlertActivity;
-import com.android.internal.app.AlertController;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentTransaction;
+import androidx.lifecycle.ViewModelProvider;
 
-import java.io.IOException;
-import java.util.regex.Pattern;
+import dagger.hilt.android.AndroidEntryPoint;
 
 /**
  * The {@link RingtonePickerActivity} allows the user to choose one from all of the
@@ -60,727 +36,183 @@
  *
  * @see RingtoneManager#ACTION_RINGTONE_PICKER
  */
-public final class RingtonePickerActivity extends AlertActivity implements
-        AdapterView.OnItemSelectedListener, Runnable, DialogInterface.OnClickListener,
-        AlertController.AlertParams.OnPrepareListViewListener {
-
-    private static final int POS_UNKNOWN = -1;
+@AndroidEntryPoint(AppCompatActivity.class)
+public final class RingtonePickerActivity extends Hilt_RingtonePickerActivity {
 
     private static final String TAG = "RingtonePickerActivity";
+    // TODO: Use the extra keys from RingtoneManager once they're added.
+    private static final String EXTRA_RINGTONE_PICKER_CATEGORY = "EXTRA_RINGTONE_PICKER_CATEGORY";
+    private static final String EXTRA_VIBRATION_SHOW_DEFAULT = "EXTRA_VIBRATION_SHOW_DEFAULT";
+    private static final String EXTRA_VIBRATION_DEFAULT_URI = "EXTRA_VIBRATION_DEFAULT_URI";
+    private static final String EXTRA_VIBRATION_SHOW_SILENT = "EXTRA_VIBRATION_SHOW_SILENT";
+    private static final String EXTRA_VIBRATION_EXISTING_URI = "EXTRA_VIBRATION_EXISTING_URI";
+    private static final boolean RINGTONE_PICKER_CATEGORY_FEATURE_ENABLED = false;
 
-    private static final int DELAY_MS_SELECTION_PLAYED = 300;
-
-    private static final String COLUMN_LABEL = MediaStore.Audio.Media.TITLE;
-
-    private static final String SAVE_CLICKED_POS = "clicked_pos";
-
-    private static final String SOUND_NAME_RES_PREFIX = "sound_name_";
-
-    private static final int ADD_FILE_REQUEST_CODE = 300;
-
-    private RingtoneManager mRingtoneManager;
-    private int mType;
-
-    private Cursor mCursor;
-    private Handler mHandler;
-    private BadgedRingtoneAdapter mAdapter;
-
-    /** The position in the list of the 'Silent' item. */
-    private int mSilentPos = POS_UNKNOWN;
-
-    /** The position in the list of the 'Default' item. */
-    private int mDefaultRingtonePos = POS_UNKNOWN;
-
-    /** The position in the list of the ringtone to sample. */
-    private int mSampleRingtonePos = POS_UNKNOWN;
-
-    /** Whether this list has the 'Silent' item. */
-    private boolean mHasSilentItem;
-
-    /** The Uri to place a checkmark next to. */
-    private Uri mExistingUri;
-
-    /** The number of static items in the list. */
-    private int mStaticItemCount;
-
-    /** Whether this list has the 'Default' item. */
-    private boolean mHasDefaultItem;
-
-    /** The Uri to play when the 'Default' item is clicked. */
-    private Uri mUriForDefaultItem;
-
-    /** Id of the user to which the ringtone picker should list the ringtones */
-    private int mPickerUserId;
-
-    /** Context of the user specified by mPickerUserId */
-    private Context mTargetContext;
-
-    /**
-     * A Ringtone for the default ringtone. In most cases, the RingtoneManager
-     * will stop the previous ringtone. However, the RingtoneManager doesn't
-     * manage the default ringtone for us, so we should stop this one manually.
-     */
-    private Ringtone mDefaultRingtone;
-
-    /**
-     * The ringtone that's currently playing, unless the currently playing one is the default
-     * ringtone.
-     */
-    private Ringtone mCurrentRingtone;
-
-    /**
-     * Stable ID for the ringtone that is currently checked (may be -1 if no ringtone is checked).
-     */
-    private long mCheckedItemId = -1;
-
+    private RingtonePickerViewModel mRingtonePickerViewModel;
     private int mAttributesFlags;
 
-    private boolean mShowOkCancelButtons;
-
-    /**
-     * Keep the currently playing ringtone around when changing orientation, so that it
-     * can be stopped later, after the activity is recreated.
-     */
-    private static Ringtone sPlayingRingtone;
-
-    private DialogInterface.OnClickListener mRingtoneClickListener =
-            new DialogInterface.OnClickListener() {
-
-        /*
-         * On item clicked
-         */
-        public void onClick(DialogInterface dialog, int which) {
-            if (which == mCursor.getCount() + mStaticItemCount) {
-                // The "Add new ringtone" item was clicked. Start a file picker intent to select
-                // only audio files (MIME type "audio/*")
-                final Intent chooseFile = getMediaFilePickerIntent();
-                startActivityForResult(chooseFile, ADD_FILE_REQUEST_CODE);
-                return;
-            }
-
-            // Save the position of most recently clicked item
-            setCheckedItem(which);
-
-            // In the buttonless (watch-only) version, preemptively set our result since we won't
-            // have another chance to do so before the activity closes.
-            if (!mShowOkCancelButtons) {
-                setSuccessResultWithRingtone(getCurrentlySelectedRingtoneUri());
-            }
-
-            // Play clip
-            playRingtone(which, 0);
-        }
-
-    };
-
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_ringtone_picker);
 
-        mHandler = new Handler();
+        mRingtonePickerViewModel = new ViewModelProvider(this).get(RingtonePickerViewModel.class);
 
         Intent intent = getIntent();
-        mPickerUserId = UserHandle.myUserId();
-        mTargetContext = this;
+        /**
+         * Id of the user to which the ringtone picker should list the ringtones
+         */
+        int pickerUserId = UserHandle.myUserId();
 
         // Get the types of ringtones to show
-        mType = intent.getIntExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, -1);
-        initRingtoneManager();
+        int ringtoneType = intent.getIntExtra(RingtoneManager.EXTRA_RINGTONE_TYPE,
+                RingtonePickerViewModel.RINGTONE_TYPE_UNKNOWN);
 
-        /*
-         * Get whether to show the 'Default' item, and the URI to play when the
-         * default is clicked
-         */
-        mHasDefaultItem = intent.getBooleanExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true);
-        mUriForDefaultItem = intent.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_DEFAULT_URI);
-        if (mUriForDefaultItem == null) {
-            if (mType == RingtoneManager.TYPE_NOTIFICATION) {
-                mUriForDefaultItem = Settings.System.DEFAULT_NOTIFICATION_URI;
-            } else if (mType == RingtoneManager.TYPE_ALARM) {
-                mUriForDefaultItem = Settings.System.DEFAULT_ALARM_ALERT_URI;
-            } else if (mType == RingtoneManager.TYPE_RINGTONE) {
-                mUriForDefaultItem = Settings.System.DEFAULT_RINGTONE_URI;
-            } else {
-                // or leave it null for silence.
-                mUriForDefaultItem = Settings.System.DEFAULT_RINGTONE_URI;
-            }
-        }
-
-        // Get whether to show the 'Silent' item
-        mHasSilentItem = intent.getBooleanExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, true);
         // AudioAttributes flags
         mAttributesFlags |= intent.getIntExtra(
                 RingtoneManager.EXTRA_RINGTONE_AUDIO_ATTRIBUTES_FLAGS,
                 0 /*defaultValue == no flags*/);
 
-        mShowOkCancelButtons = getResources().getBoolean(R.bool.config_showOkCancelButtons);
+        boolean showOkCancelButtons = getResources().getBoolean(R.bool.config_showOkCancelButtons);
+
+        String title = intent.getStringExtra(RingtoneManager.EXTRA_RINGTONE_TITLE);
+        if (title == null) {
+            title = getString(RingtonePickerViewModel.getTitleByType(ringtoneType));
+        }
+        String ringtonePickerCategory = intent.getStringExtra(EXTRA_RINGTONE_PICKER_CATEGORY);
+        RingtonePickerViewModel.PickerType pickerType = mapCategoryToPickerType(
+                ringtonePickerCategory);
+
+        RingtoneListHandler.Config soundListConfig = getSoundListConfig(pickerType, intent,
+                ringtoneType);
+        RingtoneListHandler.Config vibrationListConfig = getVibrationListConfig(pickerType, intent);
+
+        RingtonePickerViewModel.Config pickerConfig =
+                new RingtonePickerViewModel.Config(title, pickerUserId, ringtoneType,
+                        showOkCancelButtons, mAttributesFlags, pickerType);
+
+        mRingtonePickerViewModel.init(pickerConfig, soundListConfig, vibrationListConfig);
+
+        if (savedInstanceState == null) {
+            TabbedDialogFragment dialogFragment = new TabbedDialogFragment();
+
+            FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
+            Fragment prev = getSupportFragmentManager().findFragmentByTag(TabbedDialogFragment.TAG);
+            if (prev != null) {
+                ft.remove(prev);
+            }
+            ft.addToBackStack(null);
+            dialogFragment.show(ft, TabbedDialogFragment.TAG);
+        }
 
         // The volume keys will control the stream that we are choosing a ringtone for
-        setVolumeControlStream(mRingtoneManager.inferStreamType());
+        setVolumeControlStream(mRingtonePickerViewModel.getRingtoneStreamType());
+    }
 
-        // Get the URI whose list item should have a checkmark
-        mExistingUri = intent
+    private RingtoneListHandler.Config getSoundListConfig(
+            RingtonePickerViewModel.PickerType pickerType, Intent intent, int ringtoneType) {
+        if (pickerType != RingtonePickerViewModel.PickerType.SOUND_PICKER
+                && pickerType != RingtonePickerViewModel.PickerType.RINGTONE_PICKER) {
+            // This ringtone picker does not require a sound picker.
+            return null;
+        }
+
+        // Get whether to show the 'Default' sound item, and the URI to play when it's clicked
+        boolean hasDefaultSoundItem =
+                intent.getBooleanExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true);
+
+        // The Uri to play when the 'Default' sound item is clicked.
+        Uri uriForDefaultSoundItem =
+                intent.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_DEFAULT_URI);
+        if (uriForDefaultSoundItem == null) {
+            uriForDefaultSoundItem = RingtonePickerViewModel.getDefaultItemUriByType(ringtoneType);
+        }
+
+        // Get whether this list has the 'Silent' sound item.
+        boolean hasSilentSoundItem =
+                intent.getBooleanExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, true);
+
+        // AudioAttributes flags
+        mAttributesFlags |= intent.getIntExtra(
+                RingtoneManager.EXTRA_RINGTONE_AUDIO_ATTRIBUTES_FLAGS,
+                0 /*defaultValue == no flags*/);
+
+        // Get the sound URI whose list item should have a checkmark
+        Uri existingSoundUri = intent
                 .getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI);
 
-        // Create the list of ringtones and hold on to it so we can update later.
-        mAdapter = new BadgedRingtoneAdapter(this, mCursor,
-                /* isManagedProfile = */ UserManager.get(this).isManagedProfile(mPickerUserId));
-        if (savedInstanceState != null) {
-            setCheckedItem(savedInstanceState.getInt(SAVE_CLICKED_POS, POS_UNKNOWN));
-        }
-
-        final AlertController.AlertParams p = mAlertParams;
-        p.mAdapter = mAdapter;
-        p.mOnClickListener = mRingtoneClickListener;
-        p.mLabelColumn = COLUMN_LABEL;
-        p.mIsSingleChoice = true;
-        p.mOnItemSelectedListener = this;
-        if (mShowOkCancelButtons) {
-            p.mPositiveButtonText = getString(com.android.internal.R.string.ok);
-            p.mPositiveButtonListener = this;
-            p.mNegativeButtonText = getString(com.android.internal.R.string.cancel);
-            p.mPositiveButtonListener = this;
-        }
-        p.mOnPrepareListViewListener = this;
-
-        p.mTitle = intent.getCharSequenceExtra(RingtoneManager.EXTRA_RINGTONE_TITLE);
-        if (p.mTitle == null) {
-          if (mType == RingtoneManager.TYPE_ALARM) {
-              p.mTitle = getString(com.android.internal.R.string.ringtone_picker_title_alarm);
-          } else if (mType == RingtoneManager.TYPE_NOTIFICATION) {
-              p.mTitle =
-                  getString(com.android.internal.R.string.ringtone_picker_title_notification);
-          } else {
-              p.mTitle = getString(com.android.internal.R.string.ringtone_picker_title);
-          }
-        }
-
-        setupAlert();
-
-        ListView listView = mAlert.getListView();
-        if (listView != null) {
-            // List view needs to gain focus in order for RSB to work.
-            if (!listView.requestFocus()) {
-                Log.e(TAG, "Unable to gain focus! RSB may not work properly.");
-            }
-        }
-    }
-    @Override
-    public void onSaveInstanceState(Bundle outState) {
-        super.onSaveInstanceState(outState);
-        outState.putInt(SAVE_CLICKED_POS, getCheckedItem());
+        return new RingtoneListHandler.Config(hasDefaultSoundItem,
+                uriForDefaultSoundItem, hasSilentSoundItem, existingSoundUri);
     }
 
-    @Override
-    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
-        super.onActivityResult(requestCode, resultCode, data);
-
-        if (requestCode == ADD_FILE_REQUEST_CODE && resultCode == RESULT_OK) {
-            // Add the custom ringtone in a separate thread
-            final AsyncTask<Uri, Void, Uri> installTask = new AsyncTask<Uri, Void, Uri>() {
-                @Override
-                protected Uri doInBackground(Uri... params) {
-                    try {
-                        return mRingtoneManager.addCustomExternalRingtone(params[0], mType);
-                    } catch (IOException | IllegalArgumentException e) {
-                        Log.e(TAG, "Unable to add new ringtone", e);
-                    }
-                    return null;
-                }
-
-                @Override
-                protected void onPostExecute(Uri ringtoneUri) {
-                    if (ringtoneUri != null) {
-                        requeryForAdapter();
-                    } else {
-                        // Ringtone was not added, display error Toast
-                        Toast.makeText(RingtonePickerActivity.this, R.string.unable_to_add_ringtone,
-                                Toast.LENGTH_SHORT).show();
-                    }
-                }
-            };
-            installTask.execute(data.getData());
+    private RingtoneListHandler.Config getVibrationListConfig(
+            RingtonePickerViewModel.PickerType pickerType, Intent intent) {
+        if (pickerType != RingtonePickerViewModel.PickerType.VIBRATION_PICKER
+                && pickerType != RingtonePickerViewModel.PickerType.RINGTONE_PICKER) {
+            // This ringtone picker does not require a vibration picker.
+            return null;
         }
-    }
 
-    // Disabled because context menus aren't Material Design :(
-    /*
-    @Override
-    public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo) {
-        int position = ((AdapterContextMenuInfo) menuInfo).position;
+        // Get whether to show the 'Default' vibration item, and the URI to play when it's clicked
+        boolean hasDefaultVibrationItem =
+                intent.getBooleanExtra(EXTRA_VIBRATION_SHOW_DEFAULT, false);
 
-        Ringtone ringtone = getRingtone(getRingtoneManagerPosition(position));
-        if (ringtone != null && mRingtoneManager.isCustomRingtone(ringtone.getUri())) {
-            // It's a custom ringtone so we display the context menu
-            menu.setHeaderTitle(ringtone.getTitle(this));
-            menu.add(Menu.NONE, Menu.FIRST, Menu.NONE, R.string.delete_ringtone_text);
-        }
-    }
+        // The Uri to play when the 'Default' vibration item is clicked.
+        Uri uriForDefaultVibrationItem = intent.getParcelableExtra(EXTRA_VIBRATION_DEFAULT_URI);
 
-    @Override
-    public boolean onContextItemSelected(MenuItem item) {
-        switch (item.getItemId()) {
-            case Menu.FIRST: {
-                int deletedRingtonePos = ((AdapterContextMenuInfo) item.getMenuInfo()).position;
-                Uri deletedRingtoneUri = getRingtone(
-                        getRingtoneManagerPosition(deletedRingtonePos)).getUri();
-                if(mRingtoneManager.deleteExternalRingtone(deletedRingtoneUri)) {
-                    requeryForAdapter();
-                } else {
-                    Toast.makeText(this, R.string.unable_to_delete_ringtone, Toast.LENGTH_SHORT)
-                            .show();
-                }
-                return true;
-            }
-            default: {
-                return false;
-            }
-        }
+        // Get whether this list has the 'Silent' vibration item.
+        boolean hasSilentVibrationItem =
+                intent.getBooleanExtra(EXTRA_VIBRATION_SHOW_SILENT, true);
+
+        // Get the vibration URI whose list item should have a checkmark
+        Uri existingVibrationUri = intent.getParcelableExtra(EXTRA_VIBRATION_EXISTING_URI);
+
+        return new RingtoneListHandler.Config(
+                hasDefaultVibrationItem, uriForDefaultVibrationItem, hasSilentVibrationItem,
+                existingVibrationUri);
     }
-    */
 
     @Override
     public void onDestroy() {
-        if (mHandler != null) {
-            mHandler.removeCallbacksAndMessages(null);
-        }
-        if (mCursor != null) {
-            mCursor.close();
-            mCursor = null;
-        }
+        mRingtonePickerViewModel.cancelPendingAsyncTasks();
         super.onDestroy();
     }
 
-    public void onPrepareListView(ListView listView) {
-        // Reset the static item count, as this method can be called multiple times
-        mStaticItemCount = 0;
-
-        if (mHasDefaultItem) {
-            mDefaultRingtonePos = addDefaultRingtoneItem(listView);
-
-            if (getCheckedItem() == POS_UNKNOWN && RingtoneManager.isDefault(mExistingUri)) {
-                setCheckedItem(mDefaultRingtonePos);
-            }
-        }
-
-        if (mHasSilentItem) {
-            mSilentPos = addSilentItem(listView);
-
-            // The 'Silent' item should use a null Uri
-            if (getCheckedItem() == POS_UNKNOWN && mExistingUri == null) {
-                setCheckedItem(mSilentPos);
-            }
-        }
-
-        if (getCheckedItem() == POS_UNKNOWN) {
-            setCheckedItem(getListPosition(mRingtoneManager.getRingtonePosition(mExistingUri)));
-        }
-
-        // In the buttonless (watch-only) version, preemptively set our result since we won't
-        // have another chance to do so before the activity closes.
-        if (!mShowOkCancelButtons) {
-            setSuccessResultWithRingtone(getCurrentlySelectedRingtoneUri());
-        }
-        // If external storage is available, add a button to install sounds from storage.
-        if (resolvesMediaFilePicker()
-                && Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
-            addNewSoundItem(listView);
-        }
-
-        // Enable context menu in ringtone items
-        registerForContextMenu(listView);
-    }
-
-    /**
-     * Re-query RingtoneManager for the most recent set of installed ringtones. May move the
-     * selected item position to match the new position of the chosen sound.
-     *
-     * This should only need to happen after adding or removing a ringtone.
-     */
-    private void requeryForAdapter() {
-        // Refresh and set a new cursor, closing the old one.
-        initRingtoneManager();
-        mAdapter.changeCursor(mCursor);
-
-        // Update checked item location.
-        int checkedPosition = POS_UNKNOWN;
-        for (int i = 0; i < mAdapter.getCount(); i++) {
-            if (mAdapter.getItemId(i) == mCheckedItemId) {
-                checkedPosition = getListPosition(i);
-                break;
-            }
-        }
-        if (mHasSilentItem && checkedPosition == POS_UNKNOWN) {
-            checkedPosition = mSilentPos;
-        }
-        setCheckedItem(checkedPosition);
-        setupAlert();
-    }
-
-    /**
-     * Adds a static item to the top of the list. A static item is one that is not from the
-     * RingtoneManager.
-     *
-     * @param listView The ListView to add to.
-     * @param textResId The resource ID of the text for the item.
-     * @return The position of the inserted item.
-     */
-    private int addStaticItem(ListView listView, int textResId) {
-        TextView textView = (TextView) getLayoutInflater().inflate(
-                com.android.internal.R.layout.select_dialog_singlechoice_material, listView, false);
-        textView.setText(textResId);
-        listView.addHeaderView(textView);
-        mStaticItemCount++;
-        return listView.getHeaderViewsCount() - 1;
-    }
-
-    private int addDefaultRingtoneItem(ListView listView) {
-        if (mType == RingtoneManager.TYPE_NOTIFICATION) {
-            return addStaticItem(listView, R.string.notification_sound_default);
-        } else if (mType == RingtoneManager.TYPE_ALARM) {
-            return addStaticItem(listView, R.string.alarm_sound_default);
-        }
-
-        return addStaticItem(listView, R.string.ringtone_default);
-    }
-
-    private int addSilentItem(ListView listView) {
-        return addStaticItem(listView, com.android.internal.R.string.ringtone_silent);
-    }
-
-    private void addNewSoundItem(ListView listView) {
-        View view = getLayoutInflater().inflate(R.layout.add_new_sound_item, listView,
-                false /* attachToRoot */);
-        TextView text = (TextView)view.findViewById(R.id.add_new_sound_text);
-
-        if (mType == RingtoneManager.TYPE_ALARM) {
-            text.setText(R.string.add_alarm_text);
-        } else if (mType == RingtoneManager.TYPE_NOTIFICATION) {
-            text.setText(R.string.add_notification_text);
-        } else {
-            text.setText(R.string.add_ringtone_text);
-        }
-        listView.addFooterView(view);
-    }
-
-    private void initRingtoneManager() {
-        // Reinstantiate the RingtoneManager. Cursor.requery() was deprecated and calling it
-        // causes unexpected behavior.
-        mRingtoneManager = new RingtoneManager(mTargetContext, /* includeParentRingtones */ true);
-        if (mType != -1) {
-            mRingtoneManager.setType(mType);
-        }
-        mCursor = new LocalizedCursor(mRingtoneManager.getCursor(), getResources(), COLUMN_LABEL);
-    }
-
-    private Ringtone getRingtone(int ringtoneManagerPosition) {
-        if (ringtoneManagerPosition < 0) {
-            return null;
-        }
-        return mRingtoneManager.getRingtone(ringtoneManagerPosition);
-    }
-
-    private int getCheckedItem() {
-        return mAlertParams.mCheckedItem;
-    }
-
-    private void setCheckedItem(int pos) {
-        mAlertParams.mCheckedItem = pos;
-        mCheckedItemId = mAdapter.getItemId(getRingtoneManagerPosition(pos));
-    }
-
-    /*
-     * On click of Ok/Cancel buttons
-     */
-    public void onClick(DialogInterface dialog, int which) {
-        boolean positiveResult = which == DialogInterface.BUTTON_POSITIVE;
-
-        // Stop playing the previous ringtone
-        mRingtoneManager.stopPreviousRingtone();
-
-        if (positiveResult) {
-            setSuccessResultWithRingtone(getCurrentlySelectedRingtoneUri());
-        } else {
-            setResult(RESULT_CANCELED);
-        }
-
-        finish();
-    }
-
-    /*
-     * On item selected via keys
-     */
-    public void onItemSelected(AdapterView parent, View view, int position, long id) {
-        // footer view
-        if (position >= mCursor.getCount() + mStaticItemCount) {
-            return;
-        }
-
-        playRingtone(position, DELAY_MS_SELECTION_PLAYED);
-
-        // In the buttonless (watch-only) version, preemptively set our result since we won't
-        // have another chance to do so before the activity closes.
-        if (!mShowOkCancelButtons) {
-            setSuccessResultWithRingtone(getCurrentlySelectedRingtoneUri());
-        }
-    }
-
-    public void onNothingSelected(AdapterView parent) {
-    }
-
-    private void playRingtone(int position, int delayMs) {
-        mHandler.removeCallbacks(this);
-        mSampleRingtonePos = position;
-        mHandler.postDelayed(this, delayMs);
-    }
-
-    public void run() {
-        stopAnyPlayingRingtone();
-        if (mSampleRingtonePos == mSilentPos) {
-            return;
-        }
-
-        Ringtone ringtone;
-        if (mSampleRingtonePos == mDefaultRingtonePos) {
-            if (mDefaultRingtone == null) {
-                mDefaultRingtone = RingtoneManager.getRingtone(this, mUriForDefaultItem);
-            }
-           /*
-            * Stream type of mDefaultRingtone is not set explicitly here.
-            * It should be set in accordance with mRingtoneManager of this Activity.
-            */
-            if (mDefaultRingtone != null) {
-                mDefaultRingtone.setStreamType(mRingtoneManager.inferStreamType());
-            }
-            ringtone = mDefaultRingtone;
-            mCurrentRingtone = null;
-        } else {
-            ringtone = mRingtoneManager.getRingtone(getRingtoneManagerPosition(mSampleRingtonePos));
-            mCurrentRingtone = ringtone;
-        }
-
-        if (ringtone != null) {
-            if (mAttributesFlags != 0) {
-                ringtone.setAudioAttributes(
-                        new AudioAttributes.Builder(ringtone.getAudioAttributes())
-                                .setFlags(mAttributesFlags)
-                                .build());
-            }
-            ringtone.play();
-        }
-    }
-
     @Override
     protected void onStop() {
         super.onStop();
-
-        if (!isChangingConfigurations()) {
-            stopAnyPlayingRingtone();
-        } else {
-            saveAnyPlayingRingtone();
-        }
+        mRingtonePickerViewModel.onStop(isChangingConfigurations());
     }
 
     @Override
     protected void onPause() {
         super.onPause();
-        if (!isChangingConfigurations()) {
-            stopAnyPlayingRingtone();
-        }
+        mRingtonePickerViewModel.onPause(isChangingConfigurations());
     }
 
-    private void setSuccessResultWithRingtone(Uri ringtoneUri) {
-      setResult(RESULT_OK,
-          new Intent().putExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI, ringtoneUri));
-    }
-
-    private Uri getCurrentlySelectedRingtoneUri() {
-        if (getCheckedItem() == POS_UNKNOWN) {
-            // When the getCheckItem is POS_UNKNOWN, it is not the case we expected.
-            // We return null for this case.
-            return null;
-        } else if (getCheckedItem() == mDefaultRingtonePos) {
-            // Use the default Uri that they originally gave us.
-            return mUriForDefaultItem;
-        } else if (getCheckedItem() == mSilentPos) {
-            // Use a null Uri for the 'Silent' item.
-            return null;
-        } else {
-            return mRingtoneManager.getRingtoneUri(getRingtoneManagerPosition(getCheckedItem()));
-        }
-    }
-
-    private void saveAnyPlayingRingtone() {
-        if (mDefaultRingtone != null && mDefaultRingtone.isPlaying()) {
-            sPlayingRingtone = mDefaultRingtone;
-        } else if (mCurrentRingtone != null && mCurrentRingtone.isPlaying()) {
-            sPlayingRingtone = mCurrentRingtone;
-        }
-    }
-
-    private void stopAnyPlayingRingtone() {
-        if (sPlayingRingtone != null && sPlayingRingtone.isPlaying()) {
-            sPlayingRingtone.stop();
-        }
-        sPlayingRingtone = null;
-
-        if (mDefaultRingtone != null && mDefaultRingtone.isPlaying()) {
-            mDefaultRingtone.stop();
+    /**
+     * Maps the ringtone picker category to the appropriate PickerType.
+     * If the category is null or the feature is still not released, then it defaults to sound
+     * picker.
+     *
+     * @param category the ringtone picker category.
+     * @return the corresponding picker type.
+     */
+    private static RingtonePickerViewModel.PickerType mapCategoryToPickerType(String category) {
+        if (category == null || !RINGTONE_PICKER_CATEGORY_FEATURE_ENABLED) {
+            return RingtonePickerViewModel.PickerType.SOUND_PICKER;
         }
 
-        if (mRingtoneManager != null) {
-            mRingtoneManager.stopPreviousRingtone();
-        }
-    }
-
-    private int getRingtoneManagerPosition(int listPos) {
-        return listPos - mStaticItemCount;
-    }
-
-    private int getListPosition(int ringtoneManagerPos) {
-
-        // If the manager position is -1 (for not found), return that
-        if (ringtoneManagerPos < 0) return ringtoneManagerPos;
-
-        return ringtoneManagerPos + mStaticItemCount;
-    }
-
-    private Intent getMediaFilePickerIntent() {
-        final Intent chooseFile = new Intent(Intent.ACTION_GET_CONTENT);
-        chooseFile.setType("audio/*");
-        chooseFile.putExtra(Intent.EXTRA_MIME_TYPES,
-                new String[] { "audio/*", "application/ogg" });
-        return chooseFile;
-    }
-
-    private boolean resolvesMediaFilePicker() {
-        return getMediaFilePickerIntent().resolveActivity(getPackageManager()) != null;
-    }
-
-    private static class LocalizedCursor extends CursorWrapper {
-
-        final int mTitleIndex;
-        final Resources mResources;
-        String mNamePrefix;
-        final Pattern mSanitizePattern;
-
-        LocalizedCursor(Cursor cursor, Resources resources, String columnLabel) {
-            super(cursor);
-            mTitleIndex = mCursor.getColumnIndex(columnLabel);
-            mResources = resources;
-            mSanitizePattern = Pattern.compile("[^a-zA-Z0-9]");
-            if (mTitleIndex == -1) {
-                Log.e(TAG, "No index for column " + columnLabel);
-                mNamePrefix = null;
-            } else {
-                try {
-                    // Build the prefix for the name of the resource to look up
-                    // format is: "ResourcePackageName::ResourceTypeName/"
-                    // (the type name is expected to be "string" but let's not hardcode it).
-                    // Here we use an existing resource "notification_sound_default" which is
-                    // always expected to be found.
-                    mNamePrefix = String.format("%s:%s/%s",
-                            mResources.getResourcePackageName(R.string.notification_sound_default),
-                            mResources.getResourceTypeName(R.string.notification_sound_default),
-                            SOUND_NAME_RES_PREFIX);
-                } catch (NotFoundException e) {
-                    mNamePrefix = null;
-                }
-            }
-        }
-
-        /**
-         * Process resource name to generate a valid resource name.
-         * @param input
-         * @return a non-null String
-         */
-        private String sanitize(String input) {
-            if (input == null) {
-                return "";
-            }
-            return mSanitizePattern.matcher(input).replaceAll("_").toLowerCase();
-        }
-
-        @Override
-        public String getString(int columnIndex) {
-            final String defaultName = mCursor.getString(columnIndex);
-            if ((columnIndex != mTitleIndex) || (mNamePrefix == null)) {
-                return defaultName;
-            }
-            TypedValue value = new TypedValue();
-            try {
-                // the name currently in the database is used to derive a name to match
-                // against resource names in this package
-                mResources.getValue(mNamePrefix + sanitize(defaultName), value, false);
-            } catch (NotFoundException e) {
-                // no localized string, use the default string
-                return defaultName;
-            }
-            if ((value != null) && (value.type == TypedValue.TYPE_STRING)) {
-                Log.d(TAG, String.format("Replacing name %s with %s",
-                        defaultName, value.string.toString()));
-                return value.string.toString();
-            } else {
-                Log.e(TAG, "Invalid value when looking up localized name, using " + defaultName);
-                return defaultName;
-            }
-        }
-    }
-
-    private class BadgedRingtoneAdapter extends CursorAdapter {
-        private final boolean mIsManagedProfile;
-
-        public BadgedRingtoneAdapter(Context context, Cursor cursor, boolean isManagedProfile) {
-            super(context, cursor);
-            mIsManagedProfile = isManagedProfile;
-        }
-
-        @Override
-        public long getItemId(int position) {
-            if (position < 0) {
-                return position;
-            }
-            return super.getItemId(position);
-        }
-
-        @Override
-        public View newView(Context context, Cursor cursor, ViewGroup parent) {
-            LayoutInflater inflater = LayoutInflater.from(context);
-            return inflater.inflate(R.layout.radio_with_work_badge, parent, false);
-        }
-
-        @Override
-        public void bindView(View view, Context context, Cursor cursor) {
-            // Set text as the title of the ringtone
-            ((TextView) view.findViewById(R.id.checked_text_view))
-                    .setText(cursor.getString(RingtoneManager.TITLE_COLUMN_INDEX));
-
-            boolean isWorkRingtone = false;
-            if (mIsManagedProfile) {
-                /*
-                 * Display the work icon if the ringtone belongs to a work profile. We can tell that
-                 * a ringtone belongs to a work profile if the picker user is a managed profile, the
-                 * ringtone Uri is in external storage, and either the uri has no user id or has the
-                 * id of the picker user
-                 */
-                Uri currentUri = mRingtoneManager.getRingtoneUri(cursor.getPosition());
-                int uriUserId = ContentProvider.getUserIdFromUri(currentUri, mPickerUserId);
-                Uri uriWithoutUserId = ContentProvider.getUriWithoutUserId(currentUri);
-
-                if (uriUserId == mPickerUserId && uriWithoutUserId.toString()
-                        .startsWith(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI.toString())) {
-                    isWorkRingtone = true;
-                }
-            }
-
-            ImageView workIcon = (ImageView) view.findViewById(R.id.work_icon);
-            if(isWorkRingtone) {
-                workIcon.setImageDrawable(getPackageManager().getUserBadgeForDensityNoBackground(
-                        UserHandle.of(mPickerUserId), -1 /* density */));
-                workIcon.setVisibility(View.VISIBLE);
-            } else {
-                workIcon.setVisibility(View.GONE);
-            }
+        switch (category) {
+            case "android.intent.category.RINGTONE_PICKER_RINGTONE":
+                return RingtonePickerViewModel.PickerType.RINGTONE_PICKER;
+            case "android.intent.category.RINGTONE_PICKER_SOUND":
+                return RingtonePickerViewModel.PickerType.SOUND_PICKER;
+            case "android.intent.category.RINGTONE_PICKER_VIBRATION":
+                return RingtonePickerViewModel.PickerType.VIBRATION_PICKER;
+            default:
+                Log.w(TAG, "Unrecognized category: " + category + ". Defaulting to sound picker.");
+                return RingtonePickerViewModel.PickerType.SOUND_PICKER;
         }
     }
 }
diff --git a/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerApplication.java b/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerApplication.java
new file mode 100644
index 0000000..48fd4fe
--- /dev/null
+++ b/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerApplication.java
@@ -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.soundpicker;
+
+import android.app.Application;
+
+import dagger.hilt.android.HiltAndroidApp;
+
+/**
+ * The main application class for the project.
+ */
+@HiltAndroidApp(Application.class)
+public class RingtonePickerApplication extends Hilt_RingtonePickerApplication {
+}
diff --git a/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerViewModel.java b/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerViewModel.java
new file mode 100644
index 0000000..2c09711
--- /dev/null
+++ b/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerViewModel.java
@@ -0,0 +1,340 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.soundpicker;
+
+import static java.util.Objects.requireNonNull;
+
+import android.annotation.Nullable;
+import android.annotation.StringRes;
+import android.media.Ringtone;
+import android.media.RingtoneManager;
+import android.net.Uri;
+import android.provider.Settings;
+
+import androidx.annotation.NonNull;
+import androidx.lifecycle.ViewModel;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import com.google.common.util.concurrent.FutureCallback;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningExecutorService;
+
+import dagger.hilt.android.lifecycle.HiltViewModel;
+
+import java.io.IOException;
+import java.util.concurrent.Executor;
+
+import javax.inject.Inject;
+
+/**
+ * A view model which holds immutable info about the picker state and means to retrieve and play
+ * currently selected ringtones.
+ */
+@HiltViewModel
+public final class RingtonePickerViewModel extends ViewModel {
+
+    static final int RINGTONE_TYPE_UNKNOWN = -1;
+
+    /**
+     * Keep the currently playing ringtone around when changing orientation, so that it
+     * can be stopped later, after the activity is recreated.
+     */
+    @VisibleForTesting
+    static Ringtone sPlayingRingtone;
+
+    private static final String TAG = "RingtonePickerViewModel";
+
+    private final RingtoneManagerFactory mRingtoneManagerFactory;
+    private final RingtoneFactory mRingtoneFactory;
+    private final RingtoneListHandler mSoundListHandler;
+    private final RingtoneListHandler mVibrationListHandler;
+    private final ListeningExecutorService mListeningExecutorService;
+
+    private RingtoneManager mRingtoneManager;
+
+    /**
+     * The ringtone that's currently playing.
+     */
+    private Ringtone mCurrentRingtone;
+
+    private Config mPickerConfig;
+
+    private ListenableFuture<Uri> mAddCustomRingtoneFuture;
+
+    public enum PickerType {
+        RINGTONE_PICKER,
+        SOUND_PICKER,
+        VIBRATION_PICKER
+    }
+
+    /**
+     * Holds immutable info on the picker that should be displayed.
+     */
+    static final class Config {
+        public final String title;
+        /**
+         * Id of the user to which the ringtone picker should list the ringtones.
+         */
+        public final int userId;
+        /**
+         * Ringtone type.
+         */
+        public final int ringtoneType;
+        /**
+         * AudioAttributes flags.
+         */
+        public final int audioAttributesFlags;
+        /**
+         * In the buttonless (watch-only) version we don't show the OK/Cancel buttons.
+         */
+        public final boolean showOkCancelButtons;
+
+        public final PickerType mPickerType;
+
+        Config(String title, int userId, int ringtoneType, boolean showOkCancelButtons,
+                int audioAttributesFlags, PickerType pickerType) {
+            this.title = title;
+            this.userId = userId;
+            this.ringtoneType = ringtoneType;
+            this.showOkCancelButtons = showOkCancelButtons;
+            this.audioAttributesFlags = audioAttributesFlags;
+            this.mPickerType = pickerType;
+        }
+    }
+
+    @Inject
+    RingtonePickerViewModel(RingtoneManagerFactory ringtoneManagerFactory,
+            RingtoneFactory ringtoneFactory,
+            ListeningExecutorServiceFactory listeningExecutorServiceFactory,
+            RingtoneListHandler soundListHandler,
+            RingtoneListHandler vibrationListHandler) {
+        mRingtoneManagerFactory = ringtoneManagerFactory;
+        mRingtoneFactory = ringtoneFactory;
+        mListeningExecutorService = listeningExecutorServiceFactory.createSingleThreadExecutor();
+        mSoundListHandler = soundListHandler;
+        mVibrationListHandler = vibrationListHandler;
+    }
+
+    @StringRes
+    static int getTitleByType(int ringtoneType) {
+        switch (ringtoneType) {
+            case RingtoneManager.TYPE_ALARM:
+                return com.android.internal.R.string.ringtone_picker_title_alarm;
+            case RingtoneManager.TYPE_NOTIFICATION:
+                return com.android.internal.R.string.ringtone_picker_title_notification;
+            default:
+                return com.android.internal.R.string.ringtone_picker_title;
+        }
+    }
+
+    static Uri getDefaultItemUriByType(int ringtoneType) {
+        switch (ringtoneType) {
+            case RingtoneManager.TYPE_ALARM:
+                return Settings.System.DEFAULT_ALARM_ALERT_URI;
+            case RingtoneManager.TYPE_NOTIFICATION:
+                return Settings.System.DEFAULT_NOTIFICATION_URI;
+            default:
+                return Settings.System.DEFAULT_RINGTONE_URI;
+        }
+    }
+
+    @StringRes
+    static int getAddNewItemTextByType(int ringtoneType) {
+        switch (ringtoneType) {
+            case RingtoneManager.TYPE_ALARM:
+                return R.string.add_alarm_text;
+            case RingtoneManager.TYPE_NOTIFICATION:
+                return R.string.add_notification_text;
+            default:
+                return R.string.add_ringtone_text;
+        }
+    }
+
+    @StringRes
+    static int getDefaultRingtoneItemTextByType(int ringtoneType) {
+        switch (ringtoneType) {
+            case RingtoneManager.TYPE_ALARM:
+                return R.string.alarm_sound_default;
+            case RingtoneManager.TYPE_NOTIFICATION:
+                return R.string.notification_sound_default;
+            default:
+                return R.string.ringtone_default;
+        }
+    }
+
+    void init(@NonNull Config pickerConfig,
+            RingtoneListHandler.Config soundListConfig,
+            RingtoneListHandler.Config vibrationListConfig) {
+        mRingtoneManager = mRingtoneManagerFactory.create();
+        mPickerConfig = pickerConfig;
+        if (mPickerConfig.ringtoneType != RINGTONE_TYPE_UNKNOWN) {
+            mRingtoneManager.setType(mPickerConfig.ringtoneType);
+        }
+        if (soundListConfig != null) {
+            mSoundListHandler.init(soundListConfig, mRingtoneManager,
+                    mRingtoneManager.getCursor());
+        }
+        if (vibrationListConfig != null) {
+            // TODO: Switch to the vibration cursor, once the API is made available.
+            mVibrationListHandler.init(vibrationListConfig, mRingtoneManager,
+                    mRingtoneManager.getCursor());
+        }
+    }
+
+    /**
+     * Re-initializes the view model which is required after updating any of the picker lists.
+     * This could happen when adding a custom ringtone.
+     */
+    void reinit() {
+        init(mPickerConfig, mSoundListHandler.getRingtoneListConfig(),
+                mVibrationListHandler.getRingtoneListConfig());
+    }
+
+    @NonNull
+    Config getPickerConfig() {
+        requireInitCalled();
+        return mPickerConfig;
+    }
+
+    @NonNull
+    RingtoneListHandler getSoundListHandler() {
+        return mSoundListHandler;
+    }
+
+    @NonNull
+    RingtoneListHandler getVibrationListHandler() {
+        return mVibrationListHandler;
+    }
+
+    /**
+     * Combined the currently selected sound and vibration URIs and returns a unified URI. If the
+     * picker does not show either sound or vibration, that portion of the URI will be null.
+     *
+     * Currently only the sound URI is returned, since we don't have the API to retrieve vibrations
+     * yet.
+     * @return Combined sound and vibration URI.
+     */
+    Uri getSelectedRingtoneUri() {
+        // TODO: Combine sound and vibration URIs before returning.
+        return mSoundListHandler.getSelectedRingtoneUri();
+    }
+
+    int getRingtoneStreamType() {
+        requireInitCalled();
+        return mRingtoneManager.inferStreamType();
+    }
+
+    void onPause(boolean isChangingConfigurations) {
+        if (!isChangingConfigurations) {
+            stopAnyPlayingRingtone();
+        }
+    }
+
+    void onStop(boolean isChangingConfigurations) {
+        if (isChangingConfigurations) {
+            saveAnyPlayingRingtone();
+        } else {
+            stopAnyPlayingRingtone();
+        }
+    }
+
+    /**
+     * Plays a ringtone which is created using the currently selected sound and vibration URIs. If
+     * this is a sound or vibration only picker, then the other portion of the URI will be empty
+     * and should not affect the played ringtone.
+     *
+     * Currently, we only use the sound URI to create the ringtone, since we still don't have the
+     * API to retrieve the available vibrations list.
+     */
+    void playRingtone() {
+        requireInitCalled();
+        stopAnyPlayingRingtone();
+
+        mCurrentRingtone = mRingtoneFactory.create(getSelectedRingtoneUri(),
+                mPickerConfig.audioAttributesFlags);
+
+        if (mCurrentRingtone != null) {
+            mCurrentRingtone.play();
+        }
+    }
+
+    /**
+     * Cancels all pending async tasks.
+     */
+    void cancelPendingAsyncTasks() {
+        if (mAddCustomRingtoneFuture != null && !mAddCustomRingtoneFuture.isDone()) {
+            mAddCustomRingtoneFuture.cancel(/* mayInterruptIfRunning= */ true);
+        }
+    }
+
+    /**
+     * Adds an audio file to the list of ringtones asynchronously.
+     * Any previous async tasks are canceled before start the new one.
+     *
+     * @param uri      Uri of the file to be added as ringtone. Must be a media file.
+     * @param type     The type of the ringtone to be added.
+     * @param callback The callback to invoke when the task is completed.
+     * @param executor The executor to run the callback on when the task completes.
+     */
+    void addSoundRingtoneAsync(Uri uri, int type, FutureCallback<Uri> callback, Executor executor) {
+        // Cancel any currently running add ringtone tasks before starting a new one
+        cancelPendingAsyncTasks();
+        mAddCustomRingtoneFuture = mListeningExecutorService.submit(
+                () -> addRingtone(uri, type));
+        Futures.addCallback(mAddCustomRingtoneFuture, callback, executor);
+    }
+
+    /**
+     * Adds an audio file to the list of ringtones.
+     *
+     * @param uri  Uri of the file to be added as ringtone. Must be a media file.
+     * @param type The type of the ringtone to be added.
+     * @return The Uri of the installed ringtone, which may be the {@code uri} if it
+     * is already in ringtone storage. Or null if it failed to add the audio file.
+     */
+    @Nullable
+    private Uri addRingtone(Uri uri, int type) throws IOException {
+        requireInitCalled();
+        return mRingtoneManager.addCustomExternalRingtone(uri, type);
+    }
+
+    private void saveAnyPlayingRingtone() {
+        if (mCurrentRingtone != null && mCurrentRingtone.isPlaying()) {
+            sPlayingRingtone = mCurrentRingtone;
+        }
+        mCurrentRingtone = null;
+    }
+
+    private void stopAnyPlayingRingtone() {
+        if (sPlayingRingtone != null && sPlayingRingtone.isPlaying()) {
+            sPlayingRingtone.stop();
+        }
+        sPlayingRingtone = null;
+
+        if (mCurrentRingtone != null && mCurrentRingtone.isPlaying()) {
+            mCurrentRingtone.stop();
+        }
+        mCurrentRingtone = null;
+    }
+
+    private void requireInitCalled() {
+        requireNonNull(mRingtoneManager);
+        requireNonNull(mPickerConfig);
+    }
+}
diff --git a/packages/SoundPicker/src/com/android/soundpicker/SoundPickerFragment.java b/packages/SoundPicker/src/com/android/soundpicker/SoundPickerFragment.java
new file mode 100644
index 0000000..a37191f
--- /dev/null
+++ b/packages/SoundPicker/src/com/android/soundpicker/SoundPickerFragment.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.soundpicker;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Environment;
+import android.util.Log;
+import android.view.View;
+import android.widget.Toast;
+
+import androidx.activity.result.ActivityResult;
+import androidx.activity.result.ActivityResultCallback;
+import androidx.activity.result.ActivityResultLauncher;
+import androidx.activity.result.contract.ActivityResultContracts;
+import androidx.core.content.ContextCompat;
+import androidx.lifecycle.ViewModelProvider;
+
+import com.google.common.util.concurrent.FutureCallback;
+
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * A fragment that displays a picker used to select sound or silent. It also includes the
+ * ability to add custom sounds.
+ */
+public class SoundPickerFragment extends BasePickerFragment {
+
+    private static final String TAG = "SoundPickerFragment";
+
+    private final FutureCallback<Uri> mAddCustomRingtoneCallback = new FutureCallback<>() {
+        @Override
+        public void onSuccess(Uri ringtoneUri) {
+            requeryForAdapter();
+        }
+
+        @Override
+        public void onFailure(Throwable throwable) {
+            Log.e(TAG, "Failed to add custom ringtone.", throwable);
+            // Ringtone was not added, display error Toast
+            Toast.makeText(requireActivity().getApplicationContext(),
+                    R.string.unable_to_add_ringtone, Toast.LENGTH_SHORT).show();
+        }
+    };
+
+    ActivityResultLauncher<Intent> mActivityResultLauncher = registerForActivityResult(
+            new ActivityResultContracts.StartActivityForResult(),
+            new ActivityResultCallback<ActivityResult>() {
+                @Override
+                public void onActivityResult(ActivityResult result) {
+                    if (result.getResultCode() == Activity.RESULT_OK) {
+                        // There are no request codes
+                        Intent data = result.getData();
+                        mRingtonePickerViewModel.addSoundRingtoneAsync(data.getData(),
+                                mPickerConfig.ringtoneType,
+                                mAddCustomRingtoneCallback,
+                                // Causes the callback to be executed on the main thread.
+                                ContextCompat.getMainExecutor(
+                                        requireActivity().getApplicationContext()));
+                    }
+                }
+            });
+
+    @Override
+    public void onViewCreated(@NotNull View view, Bundle savedInstanceState) {
+        mRingtonePickerViewModel = new ViewModelProvider(requireActivity()).get(
+                RingtonePickerViewModel.class);
+        super.onViewCreated(view, savedInstanceState);
+    }
+
+    @Override
+    protected RingtoneListHandler getRingtoneListHandler() {
+        return mRingtonePickerViewModel.getSoundListHandler();
+    }
+
+    @Override
+    protected void addRingtoneAsync() {
+        // The "Add new ringtone" item was clicked. Start a file picker intent to
+        // select only audio files (MIME type "audio/*")
+        final Intent chooseFile = getMediaFilePickerIntent();
+        mActivityResultLauncher.launch(chooseFile);
+    }
+
+    @Override
+    protected void addNewRingtoneItem() {
+        // If external storage is available, add a button to install sounds from storage.
+        if (resolvesMediaFilePicker()
+                && Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
+            mRingtoneListViewAdapter.addTitleForAddRingtoneItem(
+                    RingtonePickerViewModel.getAddNewItemTextByType(mPickerConfig.ringtoneType));
+        }
+    }
+
+    private boolean resolvesMediaFilePicker() {
+        return getMediaFilePickerIntent().resolveActivity(requireActivity().getPackageManager())
+                != null;
+    }
+
+    private Intent getMediaFilePickerIntent() {
+        final Intent chooseFile = new Intent(Intent.ACTION_GET_CONTENT);
+        chooseFile.setType("audio/*");
+        chooseFile.putExtra(Intent.EXTRA_MIME_TYPES,
+                new String[]{"audio/*", "application/ogg"});
+        return chooseFile;
+    }
+}
diff --git a/packages/SoundPicker/src/com/android/soundpicker/TabbedDialogFragment.java b/packages/SoundPicker/src/com/android/soundpicker/TabbedDialogFragment.java
new file mode 100644
index 0000000..50ea9d7
--- /dev/null
+++ b/packages/SoundPicker/src/com/android/soundpicker/TabbedDialogFragment.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.soundpicker;
+
+import static android.app.Activity.RESULT_CANCELED;
+
+import android.app.Activity;
+import android.app.Dialog;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.media.RingtoneManager;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AlertDialog;
+import androidx.fragment.app.DialogFragment;
+import androidx.lifecycle.ViewModelProvider;
+import androidx.viewpager2.widget.ViewPager2;
+
+import com.google.android.material.tabs.TabLayout;
+import com.google.android.material.tabs.TabLayoutMediator;
+
+import dagger.hilt.android.AndroidEntryPoint;
+
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * A dialog fragment with a sound and/or vibration tab based on the picker type.
+ * <ul>
+ * <li> Ringtone Pickers will display both sound and vibration tabs.
+ * <li> Sound Pickers will only display the sound tab.
+ * <li> Vibration Pickers will only display the vibration tab.
+ * </ul>
+ */
+@AndroidEntryPoint(DialogFragment.class)
+public class TabbedDialogFragment extends Hilt_TabbedDialogFragment {
+
+    static final String TAG = "TabbedDialogFragment";
+
+    private RingtonePickerViewModel mRingtonePickerViewModel;
+
+    private final ViewPager2.OnPageChangeCallback mOnPageChangeCallback =
+            new ViewPager2.OnPageChangeCallback() {
+                @Override
+                public void onPageScrollStateChanged(int state) {
+                    super.onPageScrollStateChanged(state);
+                    if (state == ViewPager2.SCROLL_STATE_IDLE) {
+                        mRingtonePickerViewModel.onStop(/* isChangingConfigurations= */ false);
+                    }
+                }
+            };
+
+    @Override
+    public void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        mRingtonePickerViewModel = new ViewModelProvider(requireActivity()).get(
+                RingtonePickerViewModel.class);
+    }
+
+    @NonNull
+    @Override
+    public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
+        AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(requireActivity(),
+                android.R.style.ThemeOverlay_Material_Dialog)
+                .setTitle(mRingtonePickerViewModel.getPickerConfig().title);
+        // Do not show OK/Cancel buttons in the buttonless (watch-only) version.
+        if (mRingtonePickerViewModel.getPickerConfig().showOkCancelButtons) {
+            dialogBuilder
+                    .setPositiveButton(getString(com.android.internal.R.string.ok),
+                            (dialog, whichButton) -> {
+                                setSuccessResultWithSelectedRingtone();
+                                requireActivity().finish();
+                            })
+                    .setNegativeButton(getString(com.android.internal.R.string.cancel),
+                            (dialog, whichButton) -> {
+                                requireActivity().setResult(RESULT_CANCELED);
+                                requireActivity().finish();
+                            });
+        }
+
+        View view = buildTabbedView(requireActivity().getLayoutInflater());
+        dialogBuilder.setView(view);
+
+        return dialogBuilder.create();
+    }
+
+    @Override
+    public void onCancel(@NonNull @NotNull DialogInterface dialog) {
+        super.onCancel(dialog);
+        if (!requireActivity().isChangingConfigurations()) {
+            requireActivity().finish();
+        }
+    }
+
+    @Override
+    public void onDismiss(@NonNull @NotNull DialogInterface dialog) {
+        super.onDismiss(dialog);
+        if (!requireActivity().isChangingConfigurations()) {
+            requireActivity().finish();
+        }
+    }
+
+    private void setSuccessResultWithSelectedRingtone() {
+        requireActivity().setResult(Activity.RESULT_OK,
+                new Intent().putExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI,
+                        mRingtonePickerViewModel.getSelectedRingtoneUri()));
+    }
+
+    /**
+     * Inflates the tabbed layout view and adds the required fragments. If there's only one
+     * fragment to display, then the tab area is hidden.
+     * @param inflater The LayoutInflater that is used to inflate the tabbed view.
+     * @return The tabbed view.
+     */
+    private View buildTabbedView(@NonNull LayoutInflater inflater) {
+        View view = inflater.inflate(R.layout.fragment_tabbed_dialog, null, false);
+        TabLayout tabLayout = view.requireViewById(R.id.tabLayout);
+        ViewPager2 viewPager = view.requireViewById(R.id.masterViewPager);
+
+        ViewPagerAdapter adapter = new ViewPagerAdapter(requireActivity());
+        addFragments(adapter);
+
+        if (adapter.getItemCount() == 1) {
+            // Hide the tab area since there's only one fragment to display.
+            tabLayout.setVisibility(View.GONE);
+        }
+
+        viewPager.setAdapter(adapter);
+        viewPager.registerOnPageChangeCallback(mOnPageChangeCallback);
+        new TabLayoutMediator(tabLayout, viewPager,
+                (tab, position) -> tab.setText(adapter.getTitle(position))).attach();
+
+        return view;
+    }
+
+    /**
+     * Adds the appropriate fragments to the adapter based on the PickerType.
+     *
+     * @param adapter The adapter to add the fragments to.
+     */
+    private void addFragments(ViewPagerAdapter adapter) {
+        switch (mRingtonePickerViewModel.getPickerConfig().mPickerType) {
+            case RINGTONE_PICKER:
+                adapter.addFragment(getString(R.string.sound_page_title),
+                        new SoundPickerFragment());
+                adapter.addFragment(getString(R.string.vibration_page_title),
+                        new VibrationPickerFragment());
+                break;
+            case SOUND_PICKER:
+                adapter.addFragment(getString(R.string.sound_page_title),
+                        new SoundPickerFragment());
+                break;
+            case VIBRATION_PICKER:
+                adapter.addFragment(getString(R.string.vibration_page_title),
+                        new VibrationPickerFragment());
+                break;
+            default:
+                adapter.addFragment(getString(R.string.sound_page_title),
+                        new SoundPickerFragment());
+                break;
+        }
+    }
+}
diff --git a/packages/SoundPicker/src/com/android/soundpicker/VibrationPickerFragment.java b/packages/SoundPicker/src/com/android/soundpicker/VibrationPickerFragment.java
new file mode 100644
index 0000000..7412c19
--- /dev/null
+++ b/packages/SoundPicker/src/com/android/soundpicker/VibrationPickerFragment.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.soundpicker;
+
+import android.os.Bundle;
+import android.view.View;
+
+import androidx.lifecycle.ViewModelProvider;
+
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * A fragment that displays a picker used to select vibration or silent (no vibration).
+ */
+public class VibrationPickerFragment extends BasePickerFragment {
+
+    @Override
+    public void onViewCreated(@NotNull View view, Bundle savedInstanceState) {
+        mRingtonePickerViewModel = new ViewModelProvider(requireActivity()).get(
+                RingtonePickerViewModel.class);
+        super.onViewCreated(view, savedInstanceState);
+    }
+
+    @Override
+    protected RingtoneListHandler getRingtoneListHandler() {
+        return mRingtonePickerViewModel.getVibrationListHandler();
+    }
+
+    @Override
+    protected void addRingtoneAsync() {
+        // no-op
+    }
+
+    @Override
+    protected void addNewRingtoneItem() {
+        // no-op
+    }
+}
diff --git a/packages/SoundPicker/src/com/android/soundpicker/ViewPagerAdapter.java b/packages/SoundPicker/src/com/android/soundpicker/ViewPagerAdapter.java
new file mode 100644
index 0000000..179068e
--- /dev/null
+++ b/packages/SoundPicker/src/com/android/soundpicker/ViewPagerAdapter.java
@@ -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.soundpicker;
+
+import androidx.annotation.NonNull;
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentActivity;
+import androidx.viewpager2.adapter.FragmentStateAdapter;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * An adapter used to populate pages inside a ViewPager.
+ */
+public class ViewPagerAdapter extends FragmentStateAdapter {
+
+    private final List<Fragment> mFragments = new ArrayList<>();
+    private final List<String> mTitles = new ArrayList<>();
+
+    public ViewPagerAdapter(@NonNull FragmentActivity fragmentActivity) {
+        super(fragmentActivity);
+    }
+
+    /**
+     * Adds a fragment and page title to the adapter.
+     * @param title the title of the page in the ViewPager.
+     * @param fragment the fragment that will be inflated on this page.
+     */
+    public void addFragment(String title, Fragment fragment) {
+        mTitles.add(title);
+        mFragments.add(fragment);
+    }
+
+    /**
+     * Returns the title of the requested page.
+     * @param position the position of the page in the Viewpager.
+     * @return The title of the requested page.
+     */
+    public String getTitle(int position) {
+        return mTitles.get(position);
+    }
+
+    @NonNull
+    @Override
+    public Fragment createFragment(int position) {
+        return Objects.requireNonNull(mFragments.get(position),
+                "Could not find a fragment using position: " + position);
+    }
+
+    @Override
+    public int getItemCount() {
+        return mFragments.size();
+    }
+}
diff --git a/packages/SoundPicker/tests/Android.bp b/packages/SoundPicker/tests/Android.bp
new file mode 100644
index 0000000..c38426f
--- /dev/null
+++ b/packages/SoundPicker/tests/Android.bp
@@ -0,0 +1,38 @@
+// 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 {
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test {
+    name: "SoundPickerTests",
+    certificate: "platform",
+    libs: [
+        "android.test.runner",
+        "android.test.base",
+    ],
+    static_libs: [
+        "androidx.test.core",
+        "androidx.test.rules",
+        "androidx.test.ext.junit",
+        "androidx.test.ext.truth",
+        "mockito-target-minus-junit4",
+        "guava-android-testlib",
+        "SoundPickerLib",
+    ],
+    srcs: [
+        "src/**/*.java",
+    ],
+}
diff --git a/packages/SoundPicker/tests/AndroidManifest.xml b/packages/SoundPicker/tests/AndroidManifest.xml
new file mode 100644
index 0000000..295aeb1
--- /dev/null
+++ b/packages/SoundPicker/tests/AndroidManifest.xml
@@ -0,0 +1,11 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.soundpicker.tests">
+
+    <application android:debuggable="true">
+        <uses-library android:name="android.test.runner" />
+    </application>
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="com.android.soundpicker.tests"
+        android:label="Sound picker tests">
+    </instrumentation>
+</manifest>
diff --git a/packages/SoundPicker/tests/src/com/android/soundpicker/RingtoneListHandlerTest.java b/packages/SoundPicker/tests/src/com/android/soundpicker/RingtoneListHandlerTest.java
new file mode 100644
index 0000000..80e71e200
--- /dev/null
+++ b/packages/SoundPicker/tests/src/com/android/soundpicker/RingtoneListHandlerTest.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
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.soundpicker;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.when;
+
+import android.database.Cursor;
+import android.media.RingtoneManager;
+import android.net.Uri;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+public class RingtoneListHandlerTest {
+
+    private static final Uri DEFAULT_URI = Uri.parse("media://custom/ringtone/default_uri");
+    private static final Uri RINGTONE_URI = Uri.parse("media://custom/ringtone/uri");
+    private static final int SILENT_RINGTONE_POSITION = 0;
+    private static final int DEFAULT_RINGTONE_POSITION = 1;
+    private static final int RINGTONE_POSITION = 2;
+
+    @Mock
+    private RingtoneManager mMockRingtoneManager;
+    @Mock
+    private Cursor mMockCursor;
+
+    private RingtoneListHandler mRingtoneListHandler;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        RingtoneListHandler.Config mRingtoneListConfig = createRingtoneListConfig();
+
+        mRingtoneListHandler = new RingtoneListHandler();
+
+        // Add silent and default options to the list.
+        mRingtoneListHandler.addSilentItem();
+        mRingtoneListHandler.addDefaultItem();
+
+        mRingtoneListHandler.init(mRingtoneListConfig, mMockRingtoneManager, mMockCursor);
+    }
+
+    @Test
+    public void testGetRingtoneCursor_returnsTheCorrectRingtoneCursor() {
+        assertThat(mRingtoneListHandler.getRingtoneCursor()).isEqualTo(mMockCursor);
+    }
+
+    @Test
+    public void testGetRingtoneUri_returnsTheCorrectRingtoneUri() {
+        Uri expectedUri = RINGTONE_URI;
+        when(mMockRingtoneManager.getRingtoneUri(eq(0))).thenReturn(expectedUri);
+
+        // Request 3rd item from list.
+        Uri actualUri = mRingtoneListHandler.getRingtoneUri(RINGTONE_POSITION);
+        assertThat(actualUri).isEqualTo(expectedUri);
+    }
+
+    @Test
+    public void testGetRingtoneUri_withSelectedItemUnknown_returnsTheCorrectRingtoneUri() {
+        Uri uri = mRingtoneListHandler.getRingtoneUri(RingtoneListHandler.ITEM_POSITION_UNKNOWN);
+        assertThat(uri).isEqualTo(RingtoneListHandler.SILENT_URI);
+    }
+
+    @Test
+    public void testGetRingtoneUri_withSelectedItemDefaultPosition_returnsTheCorrectRingtoneUri() {
+        Uri actualUri = mRingtoneListHandler.getRingtoneUri(DEFAULT_RINGTONE_POSITION);
+        assertThat(actualUri).isEqualTo(DEFAULT_URI);
+    }
+
+    @Test
+    public void testGetRingtoneUri_withSelectedItemSilentPosition_returnsTheCorrectRingtoneUri() {
+        Uri uri = mRingtoneListHandler.getRingtoneUri(SILENT_RINGTONE_POSITION);
+        assertThat(uri).isEqualTo(RingtoneListHandler.SILENT_URI);
+    }
+
+    @Test
+    public void testGetCurrentlySelectedRingtoneUri_returnsTheCorrectRingtoneUri() {
+        mRingtoneListHandler.setSelectedItemPosition(RingtoneListHandler.ITEM_POSITION_UNKNOWN);
+        Uri actualUri = mRingtoneListHandler.getSelectedRingtoneUri();
+        assertThat(actualUri).isEqualTo(RingtoneListHandler.SILENT_URI);
+
+        mRingtoneListHandler.setSelectedItemPosition(DEFAULT_RINGTONE_POSITION);
+        actualUri = mRingtoneListHandler.getSelectedRingtoneUri();
+        assertThat(actualUri).isEqualTo(DEFAULT_URI);
+
+        mRingtoneListHandler.setSelectedItemPosition(SILENT_RINGTONE_POSITION);
+        actualUri = mRingtoneListHandler.getSelectedRingtoneUri();
+        assertThat(actualUri).isEqualTo(RingtoneListHandler.SILENT_URI);
+
+        when(mMockRingtoneManager.getRingtoneUri(eq(0))).thenReturn(RINGTONE_URI);
+        mRingtoneListHandler.setSelectedItemPosition(RINGTONE_POSITION);
+        actualUri = mRingtoneListHandler.getSelectedRingtoneUri();
+        assertThat(actualUri).isEqualTo(RINGTONE_URI);
+    }
+
+    @Test
+    public void testGetRingtonePosition_returnsTheCorrectRingtonePosition() {
+        when(mMockRingtoneManager.getRingtonePosition(RINGTONE_URI)).thenReturn(0);
+
+        int actualPosition = mRingtoneListHandler.getRingtonePosition(RINGTONE_URI);
+
+        assertThat(actualPosition).isEqualTo(RINGTONE_POSITION);
+
+    }
+
+    @Test
+    public void testFixedItems_onlyAddsItemsOnceAndInOrder() {
+        // Clear fixed items before testing the add methods.
+        mRingtoneListHandler.resetFixedItems();
+
+        assertThat(mRingtoneListHandler.getSilentItemPosition()).isEqualTo(
+                RingtoneListHandler.ITEM_POSITION_UNKNOWN);
+        assertThat(mRingtoneListHandler.getDefaultItemPosition()).isEqualTo(
+                RingtoneListHandler.ITEM_POSITION_UNKNOWN);
+
+        mRingtoneListHandler.addSilentItem();
+        mRingtoneListHandler.addDefaultItem();
+        mRingtoneListHandler.addSilentItem();
+        mRingtoneListHandler.addDefaultItem();
+
+        assertThat(mRingtoneListHandler.getSilentItemPosition()).isEqualTo(
+                SILENT_RINGTONE_POSITION);
+        assertThat(mRingtoneListHandler.getDefaultItemPosition()).isEqualTo(
+                DEFAULT_RINGTONE_POSITION);
+    }
+
+    @Test
+    public void testResetFixedItems_resetsSilentAndDefaultItemPositions() {
+        assertThat(mRingtoneListHandler.getSilentItemPosition()).isEqualTo(
+                SILENT_RINGTONE_POSITION);
+        assertThat(mRingtoneListHandler.getDefaultItemPosition()).isEqualTo(
+                DEFAULT_RINGTONE_POSITION);
+
+        mRingtoneListHandler.resetFixedItems();
+
+        assertThat(mRingtoneListHandler.getSilentItemPosition()).isEqualTo(
+                RingtoneListHandler.ITEM_POSITION_UNKNOWN);
+        assertThat(mRingtoneListHandler.getDefaultItemPosition()).isEqualTo(
+                RingtoneListHandler.ITEM_POSITION_UNKNOWN);
+    }
+
+    private RingtoneListHandler.Config createRingtoneListConfig() {
+        return new RingtoneListHandler.Config(/* hasDefaultItem= */ true,
+                /* uriForDefaultItem= */ DEFAULT_URI, /* hasSilentItem= */ true,
+                /* existingUri= */ DEFAULT_URI);
+    }
+}
diff --git a/packages/SoundPicker/tests/src/com/android/soundpicker/RingtonePickerViewModelTest.java b/packages/SoundPicker/tests/src/com/android/soundpicker/RingtonePickerViewModelTest.java
new file mode 100644
index 0000000..cde6c76
--- /dev/null
+++ b/packages/SoundPicker/tests/src/com/android/soundpicker/RingtonePickerViewModelTest.java
@@ -0,0 +1,534 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.soundpicker;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertNull;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.database.Cursor;
+import android.media.AudioAttributes;
+import android.media.AudioManager;
+import android.media.Ringtone;
+import android.media.RingtoneManager;
+import android.net.Uri;
+import android.provider.Settings;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.google.common.util.concurrent.FutureCallback;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.common.util.concurrent.testing.TestingExecutors;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.IOException;
+import java.util.concurrent.ExecutorService;
+
+@RunWith(AndroidJUnit4.class)
+public class RingtonePickerViewModelTest {
+
+    private static final Uri DEFAULT_URI = Uri.parse("media://custom/ringtone/default_uri");
+    private static final Uri RINGTONE_URI = Uri.parse("media://custom/ringtone/uri");
+    private static final int RINGTONE_TYPE_UNKNOWN = -1;
+    private static final int DEFAULT_RINGTONE_POSITION = 1;
+
+    @Mock
+    private RingtoneManagerFactory mMockRingtoneManagerFactory;
+    @Mock
+    private RingtoneFactory mMockRingtoneFactory;
+    @Mock
+    private RingtoneManager mMockRingtoneManager;
+    @Mock
+    private ListeningExecutorServiceFactory mMockListeningExecutorServiceFactory;
+    @Mock
+    private Cursor mMockCursor;
+
+    private RingtoneListHandler mSoundListHandler;
+    private RingtoneListHandler mVibrationListHandler;
+    private ExecutorService mMainThreadExecutor;
+    private ListeningExecutorService mBackgroundThreadExecutor;
+    private Ringtone mMockDefaultRingtone;
+    private Ringtone mMockRingtone;
+    private RingtonePickerViewModel mViewModel;
+    private RingtoneListHandler.Config mSoundListConfig;
+    private RingtoneListHandler.Config mVibrationListConfig;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mSoundListHandler = new RingtoneListHandler();
+        mVibrationListHandler = new RingtoneListHandler();
+        mSoundListConfig = createRingtoneListConfig();
+        mVibrationListConfig = createRingtoneListConfig();
+        mMockDefaultRingtone = createMockRingtone();
+        mMockRingtone = createMockRingtone();
+        when(mMockRingtoneManagerFactory.create()).thenReturn(mMockRingtoneManager);
+        when(mMockRingtoneFactory.create(DEFAULT_URI,
+                AudioAttributes.FLAG_AUDIBILITY_ENFORCED)).thenReturn(mMockDefaultRingtone);
+        when(mMockRingtoneManager.getRingtoneUri(anyInt())).thenReturn(RINGTONE_URI);
+        when(mMockRingtoneManager.getCursor()).thenReturn(mMockCursor);
+        mMainThreadExecutor = TestingExecutors.sameThreadScheduledExecutor();
+        mBackgroundThreadExecutor = TestingExecutors.sameThreadScheduledExecutor();
+        when(mMockListeningExecutorServiceFactory.createSingleThreadExecutor()).thenReturn(
+                mBackgroundThreadExecutor);
+
+        mViewModel = new RingtonePickerViewModel(mMockRingtoneManagerFactory, mMockRingtoneFactory,
+                mMockListeningExecutorServiceFactory, mSoundListHandler,
+                mVibrationListHandler);
+
+        // Add silent and default options to the sound list.
+        mSoundListHandler.addSilentItem();
+        mSoundListHandler.addDefaultItem();
+
+        // Add silent and default options to the vibration list.
+        mVibrationListHandler.addSilentItem();
+        mVibrationListHandler.addDefaultItem();
+
+        mSoundListHandler.setSelectedItemPosition(DEFAULT_RINGTONE_POSITION);
+        mVibrationListHandler.setSelectedItemPosition(DEFAULT_RINGTONE_POSITION);
+    }
+
+    @After
+    public void teardown() {
+        if (mMainThreadExecutor != null && !mMainThreadExecutor.isShutdown()) {
+            mMainThreadExecutor.shutdown();
+        }
+        if (mBackgroundThreadExecutor != null && !mBackgroundThreadExecutor.isShutdown()) {
+            mBackgroundThreadExecutor.shutdown();
+        }
+    }
+
+    @Test
+    public void testInitRingtoneManager_whenTypeIsUnknown_createManagerButDoNotSetType() {
+        mViewModel.init(createPickerConfig(RINGTONE_TYPE_UNKNOWN), mSoundListConfig,
+                mVibrationListConfig);
+
+        verify(mMockRingtoneManagerFactory).create();
+        verify(mMockRingtoneManager, never()).setType(anyInt());
+        assertNotNull(mViewModel.getSoundListHandler().getRingtoneListConfig());
+        assertNotNull(mViewModel.getVibrationListHandler().getRingtoneListConfig());
+    }
+
+    @Test
+    public void testInitRingtoneManager_whenTypeIsNotUnknown_createManagerAndSetType() {
+        mViewModel.init(createPickerConfig(RingtoneManager.TYPE_NOTIFICATION), mSoundListConfig,
+                mVibrationListConfig);
+
+        verify(mMockRingtoneManagerFactory).create();
+        verify(mMockRingtoneManager).setType(RingtoneManager.TYPE_NOTIFICATION);
+        assertNotNull(mViewModel.getSoundListHandler().getRingtoneListConfig());
+        assertNotNull(mViewModel.getVibrationListHandler().getRingtoneListConfig());
+    }
+
+    @Test
+    public void testInitRingtoneManager_bothListConfigsAreNull_onlyRecreateRingtoneManager() {
+        mViewModel.init(
+                createPickerConfig(RingtoneManager.TYPE_NOTIFICATION),
+                /* soundListConfig= */ null, /* vibrationListConfig= */ null);
+
+        verify(mMockRingtoneManagerFactory).create();
+        verify(mMockRingtoneManager).setType(RingtoneManager.TYPE_NOTIFICATION);
+        assertNull(mViewModel.getSoundListHandler().getRingtoneListConfig());
+        assertNull(mViewModel.getVibrationListHandler().getRingtoneListConfig());
+    }
+
+    @Test
+    public void testReinitialize_bothListConfigsInitialized_recreateManagerAndReinitHandlers() {
+        mViewModel.init(createPickerConfig(RingtoneManager.TYPE_NOTIFICATION), mSoundListConfig,
+                mVibrationListConfig);
+        mViewModel.reinit();
+
+        verify(mMockRingtoneManagerFactory, times(2)).create();
+        verify(mMockRingtoneManager, times(2)).setType(RingtoneManager.TYPE_NOTIFICATION);
+        assertNotNull(mViewModel.getSoundListHandler().getRingtoneListConfig());
+        assertNotNull(mViewModel.getVibrationListHandler().getRingtoneListConfig());
+    }
+
+    @Test
+    public void testReinitialize_bothListConfigsAlreadyNull_onlyRecreateRingtoneManager() {
+        mViewModel.init(
+                createPickerConfig(RingtoneManager.TYPE_NOTIFICATION),
+                /* soundListConfig= */ null, /* vibrationListConfig= */ null);
+        mViewModel.reinit();
+
+        verify(mMockRingtoneManagerFactory, times(2)).create();
+        verify(mMockRingtoneManager, times(2)).setType(RingtoneManager.TYPE_NOTIFICATION);
+        assertNull(mViewModel.getSoundListHandler().getRingtoneListConfig());
+        assertNull(mViewModel.getVibrationListHandler().getRingtoneListConfig());
+    }
+
+    @Test
+    public void testGetStreamType_returnsTheCorrectStreamType() {
+        when(mMockRingtoneManager.inferStreamType()).thenReturn(AudioManager.STREAM_ALARM);
+        mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE), mSoundListConfig,
+                mVibrationListConfig);
+        assertEquals(mViewModel.getRingtoneStreamType(), AudioManager.STREAM_ALARM);
+    }
+
+    @Test
+    public void testOnPause_withChangingConfigurationTrue_doNotStopPlayingRingtone() {
+        mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE), mSoundListConfig,
+                mVibrationListConfig);
+        mViewModel.playRingtone();
+        verifyRingtonePlayCalledAndMockPlayingState(mMockDefaultRingtone);
+        mViewModel.onPause(/* isChangingConfigurations= */ true);
+        verify(mMockDefaultRingtone, never()).stop();
+    }
+
+    @Test
+    public void testOnPause_withChangingConfigurationFalse_stopPlayingRingtone() {
+        mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE), mSoundListConfig,
+                mVibrationListConfig);
+        mViewModel.playRingtone();
+        verifyRingtonePlayCalledAndMockPlayingState(mMockDefaultRingtone);
+        mViewModel.onPause(/* isChangingConfigurations= */ false);
+        verify(mMockDefaultRingtone).stop();
+    }
+
+    @Test
+    public void testOnViewModelRecreated_previousRingtoneCanStillBeStopped() {
+        mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE), mSoundListConfig,
+                mVibrationListConfig);
+        Ringtone mockRingtone1 = createMockRingtone();
+        Ringtone mockRingtone2 = createMockRingtone();
+
+        when(mMockRingtoneFactory.create(any(), anyInt())).thenReturn(mockRingtone1, mockRingtone2);
+        mViewModel.playRingtone();
+        verifyRingtonePlayCalledAndMockPlayingState(mockRingtone1);
+        // Fake a scenario where the activity is destroyed and recreated due to a config change.
+        // This will result in a new view model getting created.
+        mViewModel.onStop(/* isChangingConfigurations= */ true);
+        verify(mockRingtone1, never()).stop();
+        mViewModel = new RingtonePickerViewModel(mMockRingtoneManagerFactory, mMockRingtoneFactory,
+                mMockListeningExecutorServiceFactory, mSoundListHandler,
+                mVibrationListHandler);
+        mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE), mSoundListConfig,
+                mVibrationListConfig);
+        mViewModel.playRingtone();
+        verifyRingtonePlayCalledAndMockPlayingState(mockRingtone2);
+        verify(mockRingtone1).stop();
+        verify(mockRingtone2, never()).stop();
+    }
+
+    @Test
+    public void testOnStop_withChangingConfigurationTrueAndDefaultRingtonePlaying_saveRingtone() {
+        mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE), mSoundListConfig,
+                mVibrationListConfig);
+        mViewModel.playRingtone();
+        verifyRingtonePlayCalledAndMockPlayingState(mMockDefaultRingtone);
+        mViewModel.onStop(/* isChangingConfigurations= */ true);
+        assertEquals(RingtonePickerViewModel.sPlayingRingtone, mMockDefaultRingtone);
+    }
+
+    @Test
+    public void testOnStop_withChangingConfigurationTrueAndCurrentRingtonePlaying_saveRingtone() {
+        mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE), mSoundListConfig,
+                mVibrationListConfig);
+        mViewModel.playRingtone();
+        verifyRingtonePlayCalledAndMockPlayingState(mMockDefaultRingtone);
+        mViewModel.onStop(/* isChangingConfigurations= */ true);
+        assertEquals(RingtonePickerViewModel.sPlayingRingtone, mMockDefaultRingtone);
+    }
+
+    @Test
+    public void testOnStop_withChangingConfigurationTrueAndNoPlayingRingtone_saveNothing() {
+        mViewModel.onStop(/* isChangingConfigurations= */ true);
+        assertNull(RingtonePickerViewModel.sPlayingRingtone);
+    }
+
+    @Test
+    public void testOnStop_withChangingConfigurationFalse_stopPlayingRingtone() {
+        mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE), mSoundListConfig,
+                mVibrationListConfig);
+
+        mViewModel.playRingtone();
+        verifyRingtonePlayCalledAndMockPlayingState(mMockDefaultRingtone);
+        mViewModel.onStop(/* isChangingConfigurations= */ false);
+        verify(mMockDefaultRingtone).stop();
+    }
+
+    @Test
+    public void testGetCurrentlySelectedRingtoneUri_returnsTheCorrectRingtoneUri() {
+        mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE), mSoundListConfig,
+                mVibrationListConfig);
+
+        assertEquals(DEFAULT_URI, mViewModel.getSelectedRingtoneUri());
+    }
+
+    @Test
+    public void testPlayRingtone_playTheCorrectRingtone() {
+        mSoundListHandler.setSelectedItemPosition(DEFAULT_RINGTONE_POSITION);
+        mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE), mSoundListConfig,
+                mVibrationListConfig);
+
+        mViewModel.playRingtone();
+        verifyRingtonePlayCalledAndMockPlayingState(mMockDefaultRingtone);
+    }
+
+    @Test
+    public void testPlayRingtone_stopsPreviouslyRunningRingtone() {
+        // Start playing the first ringtone
+        mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE), mSoundListConfig,
+                mVibrationListConfig);
+        mViewModel.playRingtone();
+        verifyRingtonePlayCalledAndMockPlayingState(mMockDefaultRingtone);
+        // Start playing the second ringtone
+        when(mMockRingtoneFactory.create(DEFAULT_URI,
+                AudioAttributes.FLAG_AUDIBILITY_ENFORCED)).thenReturn(mMockRingtone);
+        mViewModel.playRingtone();
+        verifyRingtonePlayCalledAndMockPlayingState(mMockRingtone);
+
+        verify(mMockDefaultRingtone).stop();
+    }
+
+    @Test
+    public void testDefaultItemUri_withNotificationIntent_returnDefaultNotificationUri() {
+        Uri uri = RingtonePickerViewModel.getDefaultItemUriByType(
+                RingtoneManager.TYPE_NOTIFICATION);
+        assertEquals(Settings.System.DEFAULT_NOTIFICATION_URI, uri);
+    }
+
+    @Test
+    public void testDefaultItemUri_withAlarmIntent_returnDefaultAlarmUri() {
+        Uri uri = RingtonePickerViewModel.getDefaultItemUriByType(RingtoneManager.TYPE_ALARM);
+        assertEquals(Settings.System.DEFAULT_ALARM_ALERT_URI, uri);
+    }
+
+    @Test
+    public void testDefaultItemUri_withRingtoneIntent_returnDefaultRingtoneUri() {
+        Uri uri = RingtonePickerViewModel.getDefaultItemUriByType(RingtoneManager.TYPE_RINGTONE);
+        assertEquals(Settings.System.DEFAULT_RINGTONE_URI, uri);
+    }
+
+    @Test
+    public void testDefaultItemUri_withInvalidRingtoneType_returnDefaultRingtoneUri() {
+        Uri uri = RingtonePickerViewModel.getDefaultItemUriByType(-1);
+        assertEquals(Settings.System.DEFAULT_RINGTONE_URI, uri);
+    }
+
+    @Test
+    public void testTitle_withNotificationRingtoneType_returnRingtoneNotificationTitle() {
+        int title = RingtonePickerViewModel.getTitleByType(RingtoneManager.TYPE_NOTIFICATION);
+        assertEquals(com.android.internal.R.string.ringtone_picker_title_notification, title);
+    }
+
+    @Test
+    public void testTitle_withAlarmRingtoneType_returnRingtoneAlarmTitle() {
+        int title = RingtonePickerViewModel.getTitleByType(RingtoneManager.TYPE_ALARM);
+        assertEquals(com.android.internal.R.string.ringtone_picker_title_alarm, title);
+    }
+
+    @Test
+    public void testTitle_withInvalidRingtoneType_returnDefaultRingtoneTitle() {
+        int title = RingtonePickerViewModel.getTitleByType(/*ringtoneType= */ -1);
+        assertEquals(com.android.internal.R.string.ringtone_picker_title, title);
+    }
+
+    @Test
+    public void testAddNewItemText_withAlarmType_returnAlarmAddItemText() {
+        int addNewItemTextResId = RingtonePickerViewModel.getAddNewItemTextByType(
+                RingtoneManager.TYPE_ALARM);
+        assertEquals(R.string.add_alarm_text, addNewItemTextResId);
+    }
+
+    @Test
+    public void testAddNewItemText_withNotificationType_returnNotificationAddItemText() {
+        int addNewItemTextResId = RingtonePickerViewModel.getAddNewItemTextByType(
+                RingtoneManager.TYPE_NOTIFICATION);
+        assertEquals(R.string.add_notification_text, addNewItemTextResId);
+    }
+
+    @Test
+    public void testAddNewItemText_withRingtoneType_returnRingtoneAddItemText() {
+        int addNewItemTextResId = RingtonePickerViewModel.getAddNewItemTextByType(
+                RingtoneManager.TYPE_RINGTONE);
+        assertEquals(R.string.add_ringtone_text, addNewItemTextResId);
+    }
+
+    @Test
+    public void testAddNewItemText_withInvalidType_returnRingtoneAddItemText() {
+        int addNewItemTextResId = RingtonePickerViewModel.getAddNewItemTextByType(-1);
+        assertEquals(R.string.add_ringtone_text, addNewItemTextResId);
+    }
+
+    @Test
+    public void testDefaultItemText_withNotificationType_returnNotificationDefaultItemText() {
+        int defaultRingtoneItemText = RingtonePickerViewModel.getDefaultRingtoneItemTextByType(
+                RingtoneManager.TYPE_NOTIFICATION);
+        assertEquals(R.string.notification_sound_default, defaultRingtoneItemText);
+    }
+
+    @Test
+    public void testDefaultItemText_withAlarmType_returnAlarmDefaultItemText() {
+        int defaultRingtoneItemText = RingtonePickerViewModel.getDefaultRingtoneItemTextByType(
+                RingtoneManager.TYPE_NOTIFICATION);
+        assertEquals(R.string.notification_sound_default, defaultRingtoneItemText);
+    }
+
+    @Test
+    public void testDefaultItemText_withRingtoneType_returnRingtoneDefaultItemText() {
+        int defaultRingtoneItemText = RingtonePickerViewModel.getDefaultRingtoneItemTextByType(
+                RingtoneManager.TYPE_RINGTONE);
+        assertEquals(R.string.ringtone_default, defaultRingtoneItemText);
+    }
+
+    @Test
+    public void testDefaultItemText_withInvalidType_returnRingtoneDefaultItemText() {
+        int defaultRingtoneItemText = RingtonePickerViewModel.getDefaultRingtoneItemTextByType(-1);
+        assertEquals(R.string.ringtone_default, defaultRingtoneItemText);
+    }
+
+    @Test
+    public void testCancelPendingAsyncTasks_correctlyCancelsPendingTasks()
+            throws IOException {
+        FutureCallback<Uri> mockCallback = mock(FutureCallback.class);
+
+        when(mMockListeningExecutorServiceFactory.createSingleThreadExecutor()).thenReturn(
+                TestingExecutors.noOpScheduledExecutor());
+
+        mViewModel = new RingtonePickerViewModel(mMockRingtoneManagerFactory, mMockRingtoneFactory,
+                mMockListeningExecutorServiceFactory, mSoundListHandler,
+                mVibrationListHandler);
+        mViewModel.addSoundRingtoneAsync(DEFAULT_URI, RingtoneManager.TYPE_NOTIFICATION,
+                mockCallback, mMainThreadExecutor);
+        verify(mockCallback, never()).onFailure(any());
+        // Calling cancelPendingAsyncTasks should cancel the pending task. Cancelling an async
+        // task invokes the onFailure method in the callable.
+        mViewModel.cancelPendingAsyncTasks();
+        verify(mockCallback).onFailure(any());
+        verify(mockCallback, never()).onSuccess(any());
+
+    }
+
+    @Test
+    public void testAddRingtoneAsync_cancelPreviousTaskBeforeStartingNewOne()
+            throws IOException {
+        FutureCallback<Uri> mockCallback1 = mock(FutureCallback.class);
+        FutureCallback<Uri> mockCallback2 = mock(FutureCallback.class);
+
+        when(mMockListeningExecutorServiceFactory.createSingleThreadExecutor()).thenReturn(
+                TestingExecutors.noOpScheduledExecutor());
+
+        mViewModel = new RingtonePickerViewModel(mMockRingtoneManagerFactory, mMockRingtoneFactory,
+                mMockListeningExecutorServiceFactory, mSoundListHandler,
+                mVibrationListHandler);
+        mViewModel.addSoundRingtoneAsync(DEFAULT_URI, RingtoneManager.TYPE_NOTIFICATION,
+                mockCallback1, mMainThreadExecutor);
+        verify(mockCallback1, never()).onFailure(any());
+        // We call addRingtoneAsync again to cancel the previous task and start a new one.
+        // Cancelling an async task invokes the onFailure method in the callable.
+        mViewModel.addSoundRingtoneAsync(DEFAULT_URI, RingtoneManager.TYPE_NOTIFICATION,
+                mockCallback2, mMainThreadExecutor);
+        verify(mockCallback1).onFailure(any());
+        verify(mockCallback1, never()).onSuccess(any());
+        verifyNoMoreInteractions(mockCallback2);
+    }
+
+    @Test
+    public void testAddRingtoneAsync_whenAddRingtoneIsSuccessful_successCallbackIsInvoked()
+            throws IOException {
+        Uri expectedUri = DEFAULT_URI;
+        FutureCallback<Uri> mockCallback = mock(FutureCallback.class);
+
+        when(mMockRingtoneManager.addCustomExternalRingtone(DEFAULT_URI,
+                RingtoneManager.TYPE_NOTIFICATION)).thenReturn(expectedUri);
+
+        mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE), mSoundListConfig,
+                mVibrationListConfig);
+
+        mViewModel.addSoundRingtoneAsync(DEFAULT_URI, RingtoneManager.TYPE_NOTIFICATION,
+                mockCallback, mMainThreadExecutor);
+
+        verify(mockCallback).onSuccess(expectedUri);
+        verify(mockCallback, never()).onFailure(any());
+    }
+
+    @Test
+    public void testAddRingtoneAsync_whenAddRingtoneFailed_failureCallbackIsInvoked()
+            throws IOException {
+        FutureCallback<Uri> mockCallback = mock(FutureCallback.class);
+
+        when(mMockRingtoneManager.addCustomExternalRingtone(any(), anyInt())).thenThrow(
+                IOException.class);
+
+        mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE), mSoundListConfig,
+                mVibrationListConfig);
+
+        mViewModel.addSoundRingtoneAsync(DEFAULT_URI, RingtoneManager.TYPE_NOTIFICATION,
+                mockCallback, mMainThreadExecutor);
+
+        verify(mockCallback).onFailure(any(IOException.class));
+        verify(mockCallback, never()).onSuccess(any());
+    }
+
+    private Ringtone createMockRingtone() {
+        Ringtone mockRingtone = mock(Ringtone.class);
+        when(mockRingtone.getAudioAttributes()).thenReturn(
+                audioAttributes(AudioAttributes.USAGE_NOTIFICATION_RINGTONE, 0));
+
+        return mockRingtone;
+    }
+
+    private void verifyRingtonePlayCalledAndMockPlayingState(Ringtone ringtone) {
+        verify(ringtone).play();
+        when(ringtone.isPlaying()).thenReturn(true);
+    }
+
+    private static AudioAttributes audioAttributes(int audioUsage, int flags) {
+        return new AudioAttributes.Builder()
+                .setUsage(audioUsage)
+                .setFlags(flags)
+                .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
+                .build();
+    }
+
+    private RingtonePickerViewModel.Config createPickerConfig(int ringtoneType,
+            int audioAttributes) {
+        return new RingtonePickerViewModel.Config("Phone ringtone", /* userId= */ 1,
+                ringtoneType, /* showOkCancelButtons= */ true,
+                audioAttributes, RingtonePickerViewModel.PickerType.RINGTONE_PICKER);
+    }
+
+    private RingtonePickerViewModel.Config createPickerConfig(int ringtoneType) {
+        return new RingtonePickerViewModel.Config("Phone ringtone", /* userId= */ 1,
+                ringtoneType, /* showOkCancelButtons= */ true,
+                AudioAttributes.FLAG_AUDIBILITY_ENFORCED,
+                RingtonePickerViewModel.PickerType.RINGTONE_PICKER);
+    }
+
+    private RingtoneListHandler.Config createRingtoneListConfig() {
+        return new RingtoneListHandler.Config(/* hasDefaultItem= */ true,
+                /* uriForDefaultItem= */ DEFAULT_URI, /* hasSilentItem= */ true,
+                /* existingUri= */ Uri.parse(""));
+    }
+}
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 7df3dfb..7be6043 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -65,6 +65,7 @@
                 "androidx.compose.runtime_runtime",
                 "androidx.compose.material3_material3",
                 "androidx.activity_activity-compose",
+                "androidx.compose.animation_animation-graphics",
             ],
 
             // By default, Compose is disabled and we compile the ComposeFacade
@@ -122,6 +123,7 @@
     ],
     static_libs: [
         "SystemUISharedLib",
+        "SystemUICustomizationLib",
         "SettingsLib",
         "androidx.leanback_leanback",
         "androidx.slice_slice-core",
@@ -250,17 +252,17 @@
         "tests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt",
         "tests/src/com/android/systemui/keyguard/data/repository/TrustRepositoryTest.kt",
         // domain
-        "tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt",
+        "tests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt",
+        "tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerCallbackInteractorTest.kt",
+        "tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt",
         "tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt",
         "tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt",
         "tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt",
         "tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt",
         "tests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt",
-        "tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerCallbackInteractorTest.kt",
-        "tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt",
         // ui
+        "tests/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModelTest.kt",
         "tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt",
-        "tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModelTest.kt",
         "tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt",
         "tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt",
         "tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt",
@@ -271,7 +273,6 @@
         "tests/src/com/android/systemui/dock/DockManagerFake.java",
         "tests/src/com/android/systemui/dump/LogBufferHelper.kt",
         "tests/src/com/android/systemui/statusbar/phone/FakeKeyguardStateController.java",
-        "tests/src/com/android/systemui/keyguard/domain/quickaffordance/FakeKeyguardQuickAffordanceRegistry.kt",
 
         /* Biometric converted tests */
         "tests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt",
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 5c03698..4fd4723 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -176,6 +176,7 @@
     <uses-permission android:name="android.permission.FOREGROUND_SERVICE_SYSTEM_EXEMPTED" />
     <uses-permission android:name="android.permission.RECORD_AUDIO" />
     <uses-permission android:name="android.permission.CAPTURE_AUDIO_OUTPUT"/>
+    <uses-permission android:name="android.permission.USE_EXACT_ALARM"/>
 
     <!-- Assist -->
     <uses-permission android:name="android.permission.ACCESS_VOICE_INTERACTION_SERVICE" />
@@ -369,7 +370,7 @@
         android:defaultToDeviceProtectedStorage="true"
         android:directBootAware="true"
         tools:replace="android:appComponentFactory"
-        android:appComponentFactory=".SystemUIAppComponentFactory">
+        android:appComponentFactory=".PhoneSystemUIAppComponentFactory">
         <!-- Keep theme in sync with SystemUIApplication.onCreate().
              Setting the theme on the application does not affect views inflated by services.
              The application theme is set again from onCreate to take effect for those views. -->
@@ -451,12 +452,14 @@
             android:noHistory="true" />
 
         <service android:name=".screenshot.appclips.AppClipsScreenshotHelperService"
-            android:permission="com.android.systemui.permission.SELF"
-            android:exported="false" />
+            android:exported="false"
+            android:singleUser="true"
+            android:permission="com.android.systemui.permission.SELF" />
 
         <service android:name=".screenshot.appclips.AppClipsService"
-            android:permission="android.permission.LAUNCH_CAPTURE_CONTENT_ACTIVITY_FOR_NOTE"
-            android:exported="true" />
+            android:exported="true"
+            android:singleUser="true"
+            android:permission="android.permission.LAUNCH_CAPTURE_CONTENT_ACTIVITY_FOR_NOTE" />
 
         <service android:name=".screenrecord.RecordingService"
                  android:foregroundServiceType="systemExempted"/>
@@ -814,7 +817,8 @@
         <service
             android:name=".dreams.DreamOverlayService"
             android:enabled="false"
-            android:exported="true" />
+            android:exported="true"
+            android:singleUser="true" />
 
         <activity android:name=".keyguard.WorkLockActivity"
                   android:label="@string/accessibility_desc_work_lock"
@@ -861,7 +865,7 @@
         <activity
             android:name=".settings.brightness.BrightnessDialog"
             android:label="@string/quick_settings_brightness_dialog_title"
-            android:theme="@style/Theme.SystemUI.QuickSettings.BrightnessDialog"
+            android:theme="@style/BrightnessDialog"
             android:finishOnCloseSystemDialogs="true"
             android:launchMode="singleInstance"
             android:excludeFromRecents="true"
@@ -992,6 +996,11 @@
 
         <service android:name=".notetask.NoteTaskControllerUpdateService" />
 
+        <service android:name=".notetask.NoteTaskBubblesController$NoteTaskBubblesService"
+            android:exported="false"
+            android:singleUser="true"
+            android:permission="com.android.systemui.permission.SELF" />
+
         <activity
             android:name=".notetask.shortcut.LaunchNoteTaskActivity"
             android:exported="true"
@@ -1004,20 +1013,10 @@
             </intent-filter>
         </activity>
 
-        <!-- LaunchNoteTaskManagedProfileProxyActivity MUST NOT be exported because it allows caller
-             to specify an Android user when launching the default notes app. -->
-        <activity
-            android:name=".notetask.shortcut.LaunchNoteTaskManagedProfileProxyActivity"
-            android:exported="false"
-            android:enabled="true"
-            android:excludeFromRecents="true"
-            android:theme="@android:style/Theme.NoDisplay" />
-
         <activity
             android:name=".notetask.LaunchNotesRoleSettingsTrampolineActivity"
             android:exported="true"
             android:excludeFromRecents="true"
-            android:resizeableActivity="false"
             android:theme="@android:style/Theme.NoDisplay" >
             <intent-filter>
                 <action android:name="com.android.systemui.action.MANAGE_NOTES_ROLE_FROM_QUICK_AFFORDANCE" />
@@ -1069,15 +1068,6 @@
                   android:exported="true">
         </provider>
 
-        <!-- Provides list and realistic previews of clock faces for the picker app. -->
-        <provider
-            android:name="com.android.keyguard.clock.ClockOptionsProvider"
-            android:authorities="com.android.keyguard.clock"
-            android:enabled="false"
-            android:exported="false"
-            android:grantUriPermissions="true">
-        </provider>
-
         <receiver
             android:name=".statusbar.KeyboardShortcutsReceiver"
             android:exported="true">
diff --git a/packages/SystemUI/OWNERS b/packages/SystemUI/OWNERS
index 1ce3472..bba926d 100644
--- a/packages/SystemUI/OWNERS
+++ b/packages/SystemUI/OWNERS
@@ -23,6 +23,7 @@
 brycelee@google.com
 brzezinski@google.com
 caitlinshk@google.com
+cameronyee@google.com
 chandruis@google.com
 chrisgollner@google.com
 cinek@google.com
@@ -44,6 +45,7 @@
 jjaggi@google.com
 jonmiranda@google.com
 joshtrask@google.com
+juansmartinez@google.com
 juliacr@google.com
 juliatuttle@google.com
 justinkoh@google.com
@@ -74,7 +76,6 @@
 pixel@google.com
 pomini@google.com
 rahulbanerjee@google.com
-rasheedlewis@google.com
 roosa@google.com
 saff@google.com
 santie@google.com
diff --git a/packages/SystemUI/TEST_MAPPING b/packages/SystemUI/TEST_MAPPING
index 01e6bf0..7a5a382 100644
--- a/packages/SystemUI/TEST_MAPPING
+++ b/packages/SystemUI/TEST_MAPPING
@@ -66,7 +66,7 @@
     },
     {
       // Permission indicators
-      "name": "CtsPermission3TestCases",
+      "name": "CtsPermissionUiTestCases",
       "options": [
         {
           "exclude-annotation": "org.junit.Ignore"
@@ -75,7 +75,7 @@
           "exclude-annotation": "androidx.test.filters.FlakyTest"
         },
         {
-          "include-filter": "android.permission3.cts.CameraMicIndicatorsPermissionTest"
+          "include-filter": "android.permissionui.cts.CameraMicIndicatorsPermissionTest"
         }
       ]
     },
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/tests/TEST_MAPPING b/packages/SystemUI/accessibility/accessibilitymenu/TEST_MAPPING
similarity index 100%
rename from packages/SystemUI/accessibility/accessibilitymenu/tests/TEST_MAPPING
rename to packages/SystemUI/accessibility/accessibilitymenu/TEST_MAPPING
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-af/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-af/strings.xml
new file mode 100644
index 0000000..cbb5ad7
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-af/strings.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="accessibility_menu_service_name" msgid="730136711554740131">"Toeganklikheid-kieslys"</string>
+    <string name="accessibility_menu_intro" msgid="3164193281544042394">"Die Toeganklikheidkieslys bied ’n groot kieslys op die skerm om jou toestel te beheer. Jy kan jou toestel sluit, volume en helderheid beheer, skermskote neem, en meer."</string>
+    <string name="assistant_label" msgid="6796392082252272356">"Assistent"</string>
+    <string name="assistant_utterance" msgid="65509599221141377">"Assistent"</string>
+    <string name="a11y_settings_label" msgid="3977714687248445050">"Toeganklikheid-instellings"</string>
+    <string name="power_label" msgid="7699720321491287839">"Krag"</string>
+    <string name="power_utterance" msgid="7444296686402104807">"Kragopsies"</string>
+    <string name="recent_apps_label" msgid="6583276995616385847">"Onlangse apps"</string>
+    <string name="lockscreen_label" msgid="648347953557887087">"Sluitskerm"</string>
+    <string name="quick_settings_label" msgid="2999117381487601865">"Kitsinstellings"</string>
+    <string name="notifications_label" msgid="6829741046963013567">"Kennisgewings"</string>
+    <string name="screenshot_label" msgid="863978141223970162">"Skermkiekie"</string>
+    <string name="screenshot_utterance" msgid="1430760563401895074">"Neem skermkiekie"</string>
+    <string name="volume_up_label" msgid="8592766918780362870">"Volume harder"</string>
+    <string name="volume_down_label" msgid="8574981863656447346">"Volume sagter"</string>
+    <string name="brightness_up_label" msgid="8010753822854544846">"Verhoog helderheid"</string>
+    <string name="brightness_down_label" msgid="7115662941913272072">"Verlaag helderheid"</string>
+    <string name="previous_button_content_description" msgid="840869171117765966">"Gaan na vorige skerm toe"</string>
+    <string name="next_button_content_description" msgid="6810058269847364406">"Gaan na volgende skerm toe"</string>
+    <string name="accessibility_menu_description" msgid="4458354794093858297">"Die Toeganklikheidkieslys bied ’n groot kieslys op die skerm om jou toestel te beheer. Jy kan jou toestel sluit, volume en helderheid beheer, skermskote neem, en meer."</string>
+    <string name="accessibility_menu_summary" msgid="340071398148208130">"Beheer toestel deur groot kieslys"</string>
+    <string name="accessibility_menu_settings_name" msgid="1716888058785672611">"Toeganklikheidkieslys-instellings"</string>
+    <string name="accessibility_menu_large_buttons_title" msgid="8978499601044961736">"Groot knoppies"</string>
+    <string name="accessibility_menu_large_buttons_summary" msgid="236873938502785311">"Maak Toeganklikheidkieslys-knoppies groter"</string>
+    <string name="pref_help_title" msgid="6871558837025010641">"Hulp"</string>
+    <string name="brightness_percentage_label" msgid="7391554573977867369">"Helderheid <xliff:g id="PERCENTAGE">%1$s</xliff:g>%%"</string>
+    <string name="music_volume_percentage_label" msgid="398635599662604706">"Musiekvolume <xliff:g id="PERCENTAGE">%1$s</xliff:g>%%"</string>
+</resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-am/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-am/strings.xml
new file mode 100644
index 0000000..0aeb410
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-am/strings.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="accessibility_menu_service_name" msgid="730136711554740131">"የተደራሽነት ምናሌ"</string>
+    <string name="accessibility_menu_intro" msgid="3164193281544042394">"የተደራሽነት ምናሌ መሣሪያዎን ለመቆጣጠር ትልቅ የማያ ገፅ ላይ ምናሌን ያቀርባል። የእርስዎን መሣሪያ መቆለፍ፣ ድምፅን እና ብሩህነትን መቆጣጠር፣ ቅጽበታዊ ገፅ ዕይታዎችን ማንሳት እና ተጨማሪ ነገሮችን ማድረግ ይችላሉ።"</string>
+    <string name="assistant_label" msgid="6796392082252272356">"ረዳት"</string>
+    <string name="assistant_utterance" msgid="65509599221141377">"ረዳት"</string>
+    <string name="a11y_settings_label" msgid="3977714687248445050">"የተደራሽነት ቅንብሮች"</string>
+    <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="quick_settings_label" msgid="2999117381487601865">"ፈጣን ቅንብሮች"</string>
+    <string name="notifications_label" msgid="6829741046963013567">"ማሳወቂያዎች"</string>
+    <string name="screenshot_label" msgid="863978141223970162">"ቅጽበታዊ ገፅ እይታ"</string>
+    <string name="screenshot_utterance" msgid="1430760563401895074">"ቅጽበታዊ ገፅ እይታን ያነሳል"</string>
+    <string name="volume_up_label" msgid="8592766918780362870">"ድምፅ ጨምር"</string>
+    <string name="volume_down_label" msgid="8574981863656447346">"ድምፅ ቀንስ"</string>
+    <string name="brightness_up_label" msgid="8010753822854544846">"ብሩህነት ጨምር"</string>
+    <string name="brightness_down_label" msgid="7115662941913272072">"ብሩህነት ቀንስ"</string>
+    <string name="previous_button_content_description" msgid="840869171117765966">"ወደ ቀዳሚው ማያ ገፅ ይሂዱ"</string>
+    <string name="next_button_content_description" msgid="6810058269847364406">"ወደ ቀጣዩ ማያ ገፅ ይሂዱ"</string>
+    <string name="accessibility_menu_description" msgid="4458354794093858297">"የተደራሽነት ምናሌ መሣሪያዎን ለመቆጣጠር ትልቅ የማያ ገፅ ላይ ምናሌን ያቀርባል። የእርስዎን መሣሪያ መቆለፍ፣ ድምፅን እና ብሩህነትን መቆጣጠር፣ ቅጽበታዊ ገፅ ዕይታዎችን ማንሳት እና ተጨማሪ ነገሮችን ማድረግ ይችላሉ።"</string>
+    <string name="accessibility_menu_summary" msgid="340071398148208130">"መሣሪያውን በትልቅ ምናሌ በኩል ይቆጣጠሩ"</string>
+    <string name="accessibility_menu_settings_name" msgid="1716888058785672611">"የተደራሽነት ምናሌ ቅንብሮች"</string>
+    <string name="accessibility_menu_large_buttons_title" msgid="8978499601044961736">"ትልቅ አዝራሮች"</string>
+    <string name="accessibility_menu_large_buttons_summary" msgid="236873938502785311">"የተደራሽነት ምናሌ አዝራሮች መጠን ይጨምሩ"</string>
+    <string name="pref_help_title" msgid="6871558837025010641">"እገዛ"</string>
+    <string name="brightness_percentage_label" msgid="7391554573977867369">"የብርሃን መጠን <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+    <string name="music_volume_percentage_label" msgid="398635599662604706">"የሙዚቃ ድምፅ መጠን<xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+</resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-ar/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-ar/strings.xml
new file mode 100644
index 0000000..29a09d5
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-ar/strings.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="accessibility_menu_service_name" msgid="730136711554740131">"قائمة تسهيل الاستخدام"</string>
+    <string name="accessibility_menu_intro" msgid="3164193281544042394">"\"قائمة تسهيل الاستخدام\" هي قائمة كبيرة تظهر على الشاشة وتتيح لك التحكّم في جهازك. يمكنك من خلال هذه القائمة قفل جهازك والتحكّم في مستوى الصوت والسطوع وتسجيل لقطات الشاشة وغير ذلك."</string>
+    <string name="assistant_label" msgid="6796392082252272356">"مساعِد"</string>
+    <string name="assistant_utterance" msgid="65509599221141377">"‏مساعد Google"</string>
+    <string name="a11y_settings_label" msgid="3977714687248445050">"إعدادات تسهيل الاستخدام"</string>
+    <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="quick_settings_label" msgid="2999117381487601865">"الإعدادات السريعة"</string>
+    <string name="notifications_label" msgid="6829741046963013567">"الإشعارات"</string>
+    <string name="screenshot_label" msgid="863978141223970162">"لقطة شاشة"</string>
+    <string name="screenshot_utterance" msgid="1430760563401895074">"أخذ لقطة شاشة"</string>
+    <string name="volume_up_label" msgid="8592766918780362870">"رفع الصوت"</string>
+    <string name="volume_down_label" msgid="8574981863656447346">"خفض الصوت"</string>
+    <string name="brightness_up_label" msgid="8010753822854544846">"رفع مستوى السطوع"</string>
+    <string name="brightness_down_label" msgid="7115662941913272072">"خفض مستوى السطوع"</string>
+    <string name="previous_button_content_description" msgid="840869171117765966">"الانتقال إلى الشاشة السابقة"</string>
+    <string name="next_button_content_description" msgid="6810058269847364406">"الانتقال إلى الشاشة التالية"</string>
+    <string name="accessibility_menu_description" msgid="4458354794093858297">"توفّر \"قائمة تسهيل الاستخدام\" قائمةً كبيرةً تُعرض على الشاشة وتتيح لك التحكّم في جهازك. يمكنك من خلال هذه القائمة قفل جهازك أو التحكّم في مستوى الصوت والسطوع وتسجيل لقطات الشاشة وغير ذلك."</string>
+    <string name="accessibility_menu_summary" msgid="340071398148208130">"التحكُّم في جهازك من خلال قائمة كبيرة الحجم"</string>
+    <string name="accessibility_menu_settings_name" msgid="1716888058785672611">"إعدادات \"قائمة تسهيل الاستخدام\""</string>
+    <string name="accessibility_menu_large_buttons_title" msgid="8978499601044961736">"الأزرار الكبيرة"</string>
+    <string name="accessibility_menu_large_buttons_summary" msgid="236873938502785311">"زيادة حجم أزرار \"قائمة تسهيل الاستخدام\""</string>
+    <string name="pref_help_title" msgid="6871558837025010641">"مساعدة"</string>
+    <string name="brightness_percentage_label" msgid="7391554573977867369">"السطوع %%<xliff:g id="PERCENTAGE">%1$s</xliff:g>"</string>
+    <string name="music_volume_percentage_label" msgid="398635599662604706">"مستوى صوت الموسيقى %%<xliff:g id="PERCENTAGE">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-as/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-as/strings.xml
new file mode 100644
index 0000000..1ed78d6
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-as/strings.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="accessibility_menu_service_name" msgid="730136711554740131">"সাধ্য সুবিধাসমূহৰ মেনু"</string>
+    <string name="accessibility_menu_intro" msgid="3164193281544042394">"সাধ্য সুবিধাৰ মেনুখনে আপোনাৰ ডিভাইচটো নিয়ন্ত্ৰণ কৰিবলৈ স্ক্ৰীনত এখন ডাঙৰ মেনু দেখুৱায়। আপুনি নিজৰ ডিভাইচটো লক কৰিব পাৰে, ভলিউম আৰু উজ্জ্বলতা নিয়ন্ত্ৰণ কৰিব পাৰে, স্ক্ৰীনশ্বট ল’ব পাৰে আৰু বহুতো কাম কৰিব পাৰে।"</string>
+    <string name="assistant_label" msgid="6796392082252272356">"Assistant"</string>
+    <string name="assistant_utterance" msgid="65509599221141377">"Assistant"</string>
+    <string name="a11y_settings_label" msgid="3977714687248445050">"সাধ্য সুবিধাৰ ছেটিং"</string>
+    <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="quick_settings_label" msgid="2999117381487601865">"ক্ষিপ্ৰ ছেটিং"</string>
+    <string name="notifications_label" msgid="6829741046963013567">"জাননীসমূহ"</string>
+    <string name="screenshot_label" msgid="863978141223970162">"স্ক্ৰীনশ্বট"</string>
+    <string name="screenshot_utterance" msgid="1430760563401895074">"স্ক্ৰীনশ্বট লওক"</string>
+    <string name="volume_up_label" msgid="8592766918780362870">"ভলিউম বঢ়াওক"</string>
+    <string name="volume_down_label" msgid="8574981863656447346">"ভলিউম কমাওক"</string>
+    <string name="brightness_up_label" msgid="8010753822854544846">"উজ্জ্বলতা বঢ়াওক"</string>
+    <string name="brightness_down_label" msgid="7115662941913272072">"উজ্জ্বলতা কমাওক"</string>
+    <string name="previous_button_content_description" msgid="840869171117765966">"পূৰ্বৱৰ্তী স্ক্ৰীনখনলৈ যাওক"</string>
+    <string name="next_button_content_description" msgid="6810058269847364406">"পৰৱৰ্তী স্ক্ৰীনখনলৈ যাওক"</string>
+    <string name="accessibility_menu_description" msgid="4458354794093858297">"সাধ্য সুবিধা মেনুৱে আপোনাৰ ডিভাইচটো নিয়ন্ত্ৰণ কৰিবলৈ স্ক্ৰীনত এখন বৃহৎ মেনু দেখুৱায়। আপুনি নিজৰ ডিভাইচটো লক কৰিব পাৰে, ভলিউম আৰু উজ্জ্বলতা নিয়ন্ত্ৰণ কৰিব পাৰে, স্ক্ৰীনশ্বট ল’ব পাৰে আৰু লগতে বহুতো কাম কৰিব পাৰে।"</string>
+    <string name="accessibility_menu_summary" msgid="340071398148208130">"ডাঙৰ মেনুৰ জৰিয়তে ডিভাইচ নিয়ন্ত্ৰণ কৰক"</string>
+    <string name="accessibility_menu_settings_name" msgid="1716888058785672611">"সাধ্য সুবিধা মেনুৰ ছেটিং"</string>
+    <string name="accessibility_menu_large_buttons_title" msgid="8978499601044961736">"ডাঙৰ বুটাম"</string>
+    <string name="accessibility_menu_large_buttons_summary" msgid="236873938502785311">"সাধ্য সুবিধাৰ মেনু বুটামবিলাকৰ আকাৰ বঢ়াওক"</string>
+    <string name="pref_help_title" msgid="6871558837025010641">"সহায়"</string>
+    <string name="brightness_percentage_label" msgid="7391554573977867369">"উজ্জ্বলতা <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+    <string name="music_volume_percentage_label" msgid="398635599662604706">"সংগীতৰ ভলিউম <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+</resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-az/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-az/strings.xml
new file mode 100644
index 0000000..49c26bf
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-az/strings.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="accessibility_menu_service_name" msgid="730136711554740131">"Əlçatımlılıq Menyusu"</string>
+    <string name="accessibility_menu_intro" msgid="3164193281544042394">"Əlçatımlılıq Menyusu cihazınızı idarə etmək üçün böyük geniş ekran menyusu təqdim edir. Cihazı kilidləyə, səs səviyyəsinə və parlaqlığa nəzarət edə, skrinşotlar çəkə və s. edə bilərsiniz."</string>
+    <string name="assistant_label" msgid="6796392082252272356">"Assistent"</string>
+    <string name="assistant_utterance" msgid="65509599221141377">"Assistent"</string>
+    <string name="a11y_settings_label" msgid="3977714687248445050">"Əlçatımlılıq Ayarları"</string>
+    <string name="power_label" msgid="7699720321491287839">"Yandırıb-söndürmə düyməsi"</string>
+    <string name="power_utterance" msgid="7444296686402104807">"Qidalanma düyməsi seçimləri"</string>
+    <string name="recent_apps_label" msgid="6583276995616385847">"Son tətbiqlər"</string>
+    <string name="lockscreen_label" msgid="648347953557887087">"Ekran kilidi"</string>
+    <string name="quick_settings_label" msgid="2999117381487601865">"Sürətli Ayarlar"</string>
+    <string name="notifications_label" msgid="6829741046963013567">"Bildirişlər"</string>
+    <string name="screenshot_label" msgid="863978141223970162">"Skrinşot"</string>
+    <string name="screenshot_utterance" msgid="1430760563401895074">"Skrinşot çəkin"</string>
+    <string name="volume_up_label" msgid="8592766918780362870">"Səsi artırın"</string>
+    <string name="volume_down_label" msgid="8574981863656447346">"Səsi azaldın"</string>
+    <string name="brightness_up_label" msgid="8010753822854544846">"Parlaqlığı artırın"</string>
+    <string name="brightness_down_label" msgid="7115662941913272072">"Parlaqlığı azaldın"</string>
+    <string name="previous_button_content_description" msgid="840869171117765966">"Əvvəlki ekrana keçin"</string>
+    <string name="next_button_content_description" msgid="6810058269847364406">"Növbəti ekrana keçin"</string>
+    <string name="accessibility_menu_description" msgid="4458354794093858297">"Əlçatımlılıq Menyusu cihazınızı idarə etmək üçün böyük geniş ekran menyusu təqdim edir. Cihazı kilidləyə, səs səviyyəsinə və parlaqlığa nəzarət edə, skrinşotlar çəkə və s. edə bilərsiniz."</string>
+    <string name="accessibility_menu_summary" msgid="340071398148208130">"Cihazı böyük menyu ilə idarə edin"</string>
+    <string name="accessibility_menu_settings_name" msgid="1716888058785672611">"Əlçatımlılıq Menyusu Ayarları"</string>
+    <string name="accessibility_menu_large_buttons_title" msgid="8978499601044961736">"Böyük düymələr"</string>
+    <string name="accessibility_menu_large_buttons_summary" msgid="236873938502785311">"Əlçatımlıq Menyusu Düymələrinin ölçüsünü artırın"</string>
+    <string name="pref_help_title" msgid="6871558837025010641">"Yardım"</string>
+    <string name="brightness_percentage_label" msgid="7391554573977867369">"Parlaqlıq <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+    <string name="music_volume_percentage_label" msgid="398635599662604706">"Musiqinin səs həcmi <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+</resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-b+sr+Latn/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-b+sr+Latn/strings.xml
new file mode 100644
index 0000000..d051bb7
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-b+sr+Latn/strings.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="accessibility_menu_service_name" msgid="730136711554740131">"Meni Pristupačnost"</string>
+    <string name="accessibility_menu_intro" msgid="3164193281544042394">"Meni Pristupačnost pruža veliki meni na ekranu za kontrolu uređaja. Možete da zaključate uređaj, kontrolišete jačinu zvuka i osvetljenost, pravite snimke ekrana i drugo."</string>
+    <string name="assistant_label" msgid="6796392082252272356">"Pomoćnik"</string>
+    <string name="assistant_utterance" msgid="65509599221141377">"Pomoćnik"</string>
+    <string name="a11y_settings_label" msgid="3977714687248445050">"Podešavanja pristupačnosti"</string>
+    <string name="power_label" msgid="7699720321491287839">"Napajanje"</string>
+    <string name="power_utterance" msgid="7444296686402104807">"Opcije napajanja"</string>
+    <string name="recent_apps_label" msgid="6583276995616385847">"Nedavne aplikacije"</string>
+    <string name="lockscreen_label" msgid="648347953557887087">"Zaključan ekran"</string>
+    <string name="quick_settings_label" msgid="2999117381487601865">"Brza podešavanja"</string>
+    <string name="notifications_label" msgid="6829741046963013567">"Obaveštenja"</string>
+    <string name="screenshot_label" msgid="863978141223970162">"Snimak ekrana"</string>
+    <string name="screenshot_utterance" msgid="1430760563401895074">"Snimi ekran"</string>
+    <string name="volume_up_label" msgid="8592766918780362870">"Pojačaj zvuk"</string>
+    <string name="volume_down_label" msgid="8574981863656447346">"Utišaj zvuk"</string>
+    <string name="brightness_up_label" msgid="8010753822854544846">"Povećajte osvetljenost"</string>
+    <string name="brightness_down_label" msgid="7115662941913272072">"Smanjite osvetljenost"</string>
+    <string name="previous_button_content_description" msgid="840869171117765966">"Idi na prethodni ekran"</string>
+    <string name="next_button_content_description" msgid="6810058269847364406">"Idi na sledeći ekran"</string>
+    <string name="accessibility_menu_description" msgid="4458354794093858297">"Meni Pristupačnost pruža veliki meni na ekranu za kontrolu uređaja. Možete da zaključate uređaj, kontrolišete jačinu zvuka i osvetljenost, pravite snimke ekrana i drugo."</string>
+    <string name="accessibility_menu_summary" msgid="340071398148208130">"Kontrolišite uređaj pomoću velikog menija"</string>
+    <string name="accessibility_menu_settings_name" msgid="1716888058785672611">"Podešavanja menija Pristupačnost"</string>
+    <string name="accessibility_menu_large_buttons_title" msgid="8978499601044961736">"Velika dugmad"</string>
+    <string name="accessibility_menu_large_buttons_summary" msgid="236873938502785311">"Povećajte veličinu dugmadi u meniju za pristupačnost"</string>
+    <string name="pref_help_title" msgid="6871558837025010641">"Pomoć"</string>
+    <string name="brightness_percentage_label" msgid="7391554573977867369">"Osvetljenost: <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+    <string name="music_volume_percentage_label" msgid="398635599662604706">"Jačina muzike: <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+</resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-be/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-be/strings.xml
new file mode 100644
index 0000000..db1e8b5
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-be/strings.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="accessibility_menu_service_name" msgid="730136711554740131">"Меню спецыяльных магчымасцей"</string>
+    <string name="accessibility_menu_intro" msgid="3164193281544042394">"Меню спецыяльных магчымасцей – гэта вялікае экраннае меню для кіравання прыладай. Вы можаце блакіраваць прыладу, рэгуляваць гучнасць і яркасць, рабіць здымкі экрана і выконваць іншыя дзеянні."</string>
+    <string name="assistant_label" msgid="6796392082252272356">"Памочнік"</string>
+    <string name="assistant_utterance" msgid="65509599221141377">"Памочнік"</string>
+    <string name="a11y_settings_label" msgid="3977714687248445050">"Налады спецыяльных магчымасцей"</string>
+    <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="quick_settings_label" msgid="2999117381487601865">"Хуткія налады"</string>
+    <string name="notifications_label" msgid="6829741046963013567">"Апавяшчэнні"</string>
+    <string name="screenshot_label" msgid="863978141223970162">"Здымак экрана"</string>
+    <string name="screenshot_utterance" msgid="1430760563401895074">"Зрабіць здымак экрана"</string>
+    <string name="volume_up_label" msgid="8592766918780362870">"Павялічыць гучнасць"</string>
+    <string name="volume_down_label" msgid="8574981863656447346">"Паменшыць гучнасць"</string>
+    <string name="brightness_up_label" msgid="8010753822854544846">"Павялічыць яркасць"</string>
+    <string name="brightness_down_label" msgid="7115662941913272072">"Паменшыць яркасць"</string>
+    <string name="previous_button_content_description" msgid="840869171117765966">"Вярнуцца на папярэдні экран"</string>
+    <string name="next_button_content_description" msgid="6810058269847364406">"Перайсці на наступны экран"</string>
+    <string name="accessibility_menu_description" msgid="4458354794093858297">"Меню спецыяльных магчымасцей – гэта вялікае экраннае меню для кіравання прыладай. Вы можаце блакіраваць прыладу, рэгуляваць гучнасць і яркасць, рабіць здымкі экрана і выконваць іншыя дзеянні."</string>
+    <string name="accessibility_menu_summary" msgid="340071398148208130">"Кіраваць прыладай праз вялікае меню"</string>
+    <string name="accessibility_menu_settings_name" msgid="1716888058785672611">"Налады меню спец. магчым."</string>
+    <string name="accessibility_menu_large_buttons_title" msgid="8978499601044961736">"Вялікія кнопкі"</string>
+    <string name="accessibility_menu_large_buttons_summary" msgid="236873938502785311">"Павялічыць памер кнопак меню спецыяльных магчымасцей"</string>
+    <string name="pref_help_title" msgid="6871558837025010641">"Даведка"</string>
+    <string name="brightness_percentage_label" msgid="7391554573977867369">"Яркасць: <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+    <string name="music_volume_percentage_label" msgid="398635599662604706">"Гучнасць музыкі: <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+</resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-bg/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-bg/strings.xml
new file mode 100644
index 0000000..8faa670
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-bg/strings.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="accessibility_menu_service_name" msgid="730136711554740131">"Меню за достъпност"</string>
+    <string name="accessibility_menu_intro" msgid="3164193281544042394">"Менюто за достъпност предоставя голямо екранно меню за управление на устройството ви. Можете да заключвате устройството си, да управлявате яркостта и силата на звука, да правите екранни снимки и др."</string>
+    <string name="assistant_label" msgid="6796392082252272356">"Асистент"</string>
+    <string name="assistant_utterance" msgid="65509599221141377">"Асистент"</string>
+    <string name="a11y_settings_label" msgid="3977714687248445050">"Настройки за достъпност"</string>
+    <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="quick_settings_label" msgid="2999117381487601865">"Бързи настройки"</string>
+    <string name="notifications_label" msgid="6829741046963013567">"Известия"</string>
+    <string name="screenshot_label" msgid="863978141223970162">"Екранна снимка"</string>
+    <string name="screenshot_utterance" msgid="1430760563401895074">"Създава екранни снимки"</string>
+    <string name="volume_up_label" msgid="8592766918780362870">"Увеличаване на силата на звука"</string>
+    <string name="volume_down_label" msgid="8574981863656447346">"Намаляване на силата на звука"</string>
+    <string name="brightness_up_label" msgid="8010753822854544846">"Увеличаване на яркостта"</string>
+    <string name="brightness_down_label" msgid="7115662941913272072">"Намаляване на яркостта"</string>
+    <string name="previous_button_content_description" msgid="840869171117765966">"Към предишния екран"</string>
+    <string name="next_button_content_description" msgid="6810058269847364406">"Към следващия екран"</string>
+    <string name="accessibility_menu_description" msgid="4458354794093858297">"Менюто за достъпност предоставя голямо екранно меню за управление на устройството ви. Можете да заключвате устройството си, да управлявате яркостта и силата на звука, да правите екранни снимки и др."</string>
+    <string name="accessibility_menu_summary" msgid="340071398148208130">"Управление на устройството чрез голямо меню"</string>
+    <string name="accessibility_menu_settings_name" msgid="1716888058785672611">"Настройки за менюто за достъпност"</string>
+    <string name="accessibility_menu_large_buttons_title" msgid="8978499601044961736">"Големи бутони"</string>
+    <string name="accessibility_menu_large_buttons_summary" msgid="236873938502785311">"Увеличаване на размера на бутоните в менюто за достъпност"</string>
+    <string name="pref_help_title" msgid="6871558837025010641">"Помощ"</string>
+    <string name="brightness_percentage_label" msgid="7391554573977867369">"Яркост: <xliff:g id="PERCENTAGE">%1$s</xliff:g>%%"</string>
+    <string name="music_volume_percentage_label" msgid="398635599662604706">"Сила на звука за музиката: <xliff:g id="PERCENTAGE">%1$s</xliff:g>%%"</string>
+</resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-bn/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-bn/strings.xml
new file mode 100644
index 0000000..9a0ebef
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-bn/strings.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="accessibility_menu_service_name" msgid="730136711554740131">"অ্যাক্সেসিবিলিটি মেনু"</string>
+    <string name="accessibility_menu_intro" msgid="3164193281544042394">"আপনার ডিভাইস নিয়ন্ত্রণ করতে, \'অ্যাক্সেসিবিলিটি মেনু\' একটি বড় অন-স্ক্রিন মেনু দেখায়। আপনি ফোন লক, ভলিউম ও উজ্জ্বলতা নিয়ন্ত্রণ, স্ক্রিনশট নেওয়া এবং আরও অনেক কিছু করতে পারবেন।"</string>
+    <string name="assistant_label" msgid="6796392082252272356">"Assistant"</string>
+    <string name="assistant_utterance" msgid="65509599221141377">"Assistant"</string>
+    <string name="a11y_settings_label" msgid="3977714687248445050">"অ্যাক্সেসিবিলিটি সেটিংস"</string>
+    <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="quick_settings_label" msgid="2999117381487601865">"দ্রুত সেটিংস"</string>
+    <string name="notifications_label" msgid="6829741046963013567">"বিজ্ঞপ্তি"</string>
+    <string name="screenshot_label" msgid="863978141223970162">"স্ক্রিনশট"</string>
+    <string name="screenshot_utterance" msgid="1430760563401895074">"স্ক্রিনশট নিন"</string>
+    <string name="volume_up_label" msgid="8592766918780362870">"ভলিউম বাড়ান"</string>
+    <string name="volume_down_label" msgid="8574981863656447346">"ভলিউম কমান"</string>
+    <string name="brightness_up_label" msgid="8010753822854544846">"উজ্জ্বলতা বাড়ান"</string>
+    <string name="brightness_down_label" msgid="7115662941913272072">"উজ্জ্বলতা কমান"</string>
+    <string name="previous_button_content_description" msgid="840869171117765966">"আগের স্ক্রিনে যান"</string>
+    <string name="next_button_content_description" msgid="6810058269847364406">"পরের স্ক্রিনে যান"</string>
+    <string name="accessibility_menu_description" msgid="4458354794093858297">"আপনার ডিভাইস নিয়ন্ত্রণ করতে, \'অ্যাক্সেসিবিলিটি মেনু\' একটি বড় অন-স্ক্রিন মেনু দেখায়। আপনি ফোন লক, ভলিউম ও উজ্জ্বলতা নিয়ন্ত্রণ, স্ক্রিনশট নেওয়া এবং আরও অনেক কিছু করতে পারবেন।"</string>
+    <string name="accessibility_menu_summary" msgid="340071398148208130">"বড় করে দেখানো মেনুর মাধ্যমে ডিভাইস নিয়ন্ত্রণ করুন"</string>
+    <string name="accessibility_menu_settings_name" msgid="1716888058785672611">"অ্যাক্সেসিবিলিটি মেনুর সেটিংস"</string>
+    <string name="accessibility_menu_large_buttons_title" msgid="8978499601044961736">"বোতাম বড় করা"</string>
+    <string name="accessibility_menu_large_buttons_summary" msgid="236873938502785311">"অ্যাক্সেসিবিলিটি মেনু বোতামের সাইজ বাড়ান"</string>
+    <string name="pref_help_title" msgid="6871558837025010641">"সহায়তা"</string>
+    <string name="brightness_percentage_label" msgid="7391554573977867369">"উজ্জ্বলতা <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+    <string name="music_volume_percentage_label" msgid="398635599662604706">"মিউজিকের ভলিউম <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+</resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-bs/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-bs/strings.xml
new file mode 100644
index 0000000..749a6f2
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-bs/strings.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="accessibility_menu_service_name" msgid="730136711554740131">"Meni za pristupačnost"</string>
+    <string name="accessibility_menu_intro" msgid="3164193281544042394">"Meni za pristupačnost pruža veliki meni na ekranu za upravljanje uređajem. Možete zaključati uređaj, kontrolirati jačinu zvuka i osvjetljenje, praviti snimke ekrana i drugo."</string>
+    <string name="assistant_label" msgid="6796392082252272356">"Asistent"</string>
+    <string name="assistant_utterance" msgid="65509599221141377">"Asistent"</string>
+    <string name="a11y_settings_label" msgid="3977714687248445050">"Postavke pristupačnosti"</string>
+    <string name="power_label" msgid="7699720321491287839">"Napajanje"</string>
+    <string name="power_utterance" msgid="7444296686402104807">"Opcije napajanja"</string>
+    <string name="recent_apps_label" msgid="6583276995616385847">"Nedavne aplikacije"</string>
+    <string name="lockscreen_label" msgid="648347953557887087">"Zaključavanje ekrana"</string>
+    <string name="quick_settings_label" msgid="2999117381487601865">"Brze postavke"</string>
+    <string name="notifications_label" msgid="6829741046963013567">"Obavještenja"</string>
+    <string name="screenshot_label" msgid="863978141223970162">"Snimak ekrana"</string>
+    <string name="screenshot_utterance" msgid="1430760563401895074">"Napravi snimak ekrana"</string>
+    <string name="volume_up_label" msgid="8592766918780362870">"Pojačaj zvuk"</string>
+    <string name="volume_down_label" msgid="8574981863656447346">"Utišaj zvuk"</string>
+    <string name="brightness_up_label" msgid="8010753822854544846">"Posvijetli"</string>
+    <string name="brightness_down_label" msgid="7115662941913272072">"Zatamni"</string>
+    <string name="previous_button_content_description" msgid="840869171117765966">"Idi na prethodni ekran"</string>
+    <string name="next_button_content_description" msgid="6810058269847364406">"Idi na sljedeći ekran"</string>
+    <string name="accessibility_menu_description" msgid="4458354794093858297">"Meni za pristupačnost pruža veliki meni na ekranu za upravljanje uređajem. Možete zaključati uređaj, kontrolirati jačinu zvuka i osvjetljenje, praviti snimke ekrana i drugo."</string>
+    <string name="accessibility_menu_summary" msgid="340071398148208130">"Upravljajte uređajem putem velikog menija"</string>
+    <string name="accessibility_menu_settings_name" msgid="1716888058785672611">"Postavke Menija za pristupačnost"</string>
+    <string name="accessibility_menu_large_buttons_title" msgid="8978499601044961736">"Velika dugmad"</string>
+    <string name="accessibility_menu_large_buttons_summary" msgid="236873938502785311">"Povećajte dugmad menija za pristupačnost"</string>
+    <string name="pref_help_title" msgid="6871558837025010641">"Pomoć"</string>
+    <string name="brightness_percentage_label" msgid="7391554573977867369">"Osvjetljenje: <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+    <string name="music_volume_percentage_label" msgid="398635599662604706">"Jačina muzike: <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+</resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-ca/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-ca/strings.xml
new file mode 100644
index 0000000..2dc2c7c
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-ca/strings.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="accessibility_menu_service_name" msgid="730136711554740131">"Menú d\'accessibilitat"</string>
+    <string name="accessibility_menu_intro" msgid="3164193281544042394">"El menú d\'accessibilitat t\'ofereix un menú gran en pantalla per controlar el dispositiu. Pots bloquejar el dispositiu, controlar-ne el volum i la brillantor, fer captures de pantalla i molt més."</string>
+    <string name="assistant_label" msgid="6796392082252272356">"Assistent"</string>
+    <string name="assistant_utterance" msgid="65509599221141377">"Assistent"</string>
+    <string name="a11y_settings_label" msgid="3977714687248445050">"Configuració d\'accessibilitat"</string>
+    <string name="power_label" msgid="7699720321491287839">"Botó d\'engegada"</string>
+    <string name="power_utterance" msgid="7444296686402104807">"Opcions d\'engegada"</string>
+    <string name="recent_apps_label" msgid="6583276995616385847">"Aplicacions recents"</string>
+    <string name="lockscreen_label" msgid="648347953557887087">"Pantalla de bloqueig"</string>
+    <string name="quick_settings_label" msgid="2999117381487601865">"Configuració ràpida"</string>
+    <string name="notifications_label" msgid="6829741046963013567">"Notificacions"</string>
+    <string name="screenshot_label" msgid="863978141223970162">"Captura de pantalla"</string>
+    <string name="screenshot_utterance" msgid="1430760563401895074">"Fes una captura de pantalla"</string>
+    <string name="volume_up_label" msgid="8592766918780362870">"Apuja el volum"</string>
+    <string name="volume_down_label" msgid="8574981863656447346">"Abaixa el volum"</string>
+    <string name="brightness_up_label" msgid="8010753822854544846">"Augmenta la brillantor"</string>
+    <string name="brightness_down_label" msgid="7115662941913272072">"Disminueix la brillantor"</string>
+    <string name="previous_button_content_description" msgid="840869171117765966">"Ves a la pantalla anterior"</string>
+    <string name="next_button_content_description" msgid="6810058269847364406">"Ves a la pantalla següent"</string>
+    <string name="accessibility_menu_description" msgid="4458354794093858297">"El menú d\'accessibilitat t\'ofereix un menú gran en pantalla per controlar el dispositiu. Pots bloquejar el dispositiu, controlar-ne el volum i la brillantor, fer captures de pantalla i molt més."</string>
+    <string name="accessibility_menu_summary" msgid="340071398148208130">"Controla el dispositiu amb un menú gran"</string>
+    <string name="accessibility_menu_settings_name" msgid="1716888058785672611">"Config. del menú d\'accessibilitat"</string>
+    <string name="accessibility_menu_large_buttons_title" msgid="8978499601044961736">"Botons grans"</string>
+    <string name="accessibility_menu_large_buttons_summary" msgid="236873938502785311">"Augmenta la mida dels botons del menú d\'accessibilitat"</string>
+    <string name="pref_help_title" msgid="6871558837025010641">"Ajuda"</string>
+    <string name="brightness_percentage_label" msgid="7391554573977867369">"Brillantor: <xliff:g id="PERCENTAGE">%1$s</xliff:g>%%"</string>
+    <string name="music_volume_percentage_label" msgid="398635599662604706">"Volum de la música: <xliff:g id="PERCENTAGE">%1$s</xliff:g>%%"</string>
+</resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-cs/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-cs/strings.xml
new file mode 100644
index 0000000..e5dd693
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-cs/strings.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="accessibility_menu_service_name" msgid="730136711554740131">"Nabídka usnadnění přístupu"</string>
+    <string name="accessibility_menu_intro" msgid="3164193281544042394">"Nabídka usnadnění přístupu zobrazuje na obrazovce velkou nabídku k ovládání zařízení. Můžete zamknout zařízení, upravit hlasitost a jas, pořídit snímek obrazovky apod."</string>
+    <string name="assistant_label" msgid="6796392082252272356">"Asistent"</string>
+    <string name="assistant_utterance" msgid="65509599221141377">"Asistent"</string>
+    <string name="a11y_settings_label" msgid="3977714687248445050">"Nastavení usnadnění přístupu"</string>
+    <string name="power_label" msgid="7699720321491287839">"Vypínač"</string>
+    <string name="power_utterance" msgid="7444296686402104807">"Možnosti napájení"</string>
+    <string name="recent_apps_label" msgid="6583276995616385847">"Poslední aplikace"</string>
+    <string name="lockscreen_label" msgid="648347953557887087">"Obrazovka uzamčení"</string>
+    <string name="quick_settings_label" msgid="2999117381487601865">"Rychlé nastavení"</string>
+    <string name="notifications_label" msgid="6829741046963013567">"Oznámení"</string>
+    <string name="screenshot_label" msgid="863978141223970162">"Snímek obrazovky"</string>
+    <string name="screenshot_utterance" msgid="1430760563401895074">"Pořídit snímek obrazovky"</string>
+    <string name="volume_up_label" msgid="8592766918780362870">"Zvýšit hlasitost"</string>
+    <string name="volume_down_label" msgid="8574981863656447346">"Snížit hlasitost"</string>
+    <string name="brightness_up_label" msgid="8010753822854544846">"Zvýšit jas"</string>
+    <string name="brightness_down_label" msgid="7115662941913272072">"Snížit jas"</string>
+    <string name="previous_button_content_description" msgid="840869171117765966">"Zpět na předchozí obrazovku"</string>
+    <string name="next_button_content_description" msgid="6810058269847364406">"Přejít na další obrazovku"</string>
+    <string name="accessibility_menu_description" msgid="4458354794093858297">"Nabídka usnadnění přístupu zobrazuje na obrazovce velkou nabídku k ovládání zařízení. Můžete zamknout zařízení, upravit hlasitost a jas, pořídit snímek obrazovky apod."</string>
+    <string name="accessibility_menu_summary" msgid="340071398148208130">"Ovládat zařízení pomocí velké nabídky"</string>
+    <string name="accessibility_menu_settings_name" msgid="1716888058785672611">"Nastavení nabídky usnadnění přístupu"</string>
+    <string name="accessibility_menu_large_buttons_title" msgid="8978499601044961736">"Velká tlačítka"</string>
+    <string name="accessibility_menu_large_buttons_summary" msgid="236873938502785311">"Zvětšit tlačítka v nabídce přístupnosti"</string>
+    <string name="pref_help_title" msgid="6871558837025010641">"Nápověda"</string>
+    <string name="brightness_percentage_label" msgid="7391554573977867369">"Jas <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+    <string name="music_volume_percentage_label" msgid="398635599662604706">"Hlasitost hudby <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+</resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-da/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-da/strings.xml
new file mode 100644
index 0000000..d801298
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-da/strings.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="accessibility_menu_service_name" msgid="730136711554740131">"Menuen Hjælpefunktioner"</string>
+    <string name="accessibility_menu_intro" msgid="3164193281544042394">"Menuen Hjælpefunktioner giver dig en stor menu på skærmen, som du kan bruge til at styre din enhed. Du kan låse din enhed, justere lyd- og lysstyrken, tage screenshots og meget mere."</string>
+    <string name="assistant_label" msgid="6796392082252272356">"Assistent"</string>
+    <string name="assistant_utterance" msgid="65509599221141377">"Assistent"</string>
+    <string name="a11y_settings_label" msgid="3977714687248445050">"Indstillinger for hjælpefunktioner"</string>
+    <string name="power_label" msgid="7699720321491287839">"Afbryderknap"</string>
+    <string name="power_utterance" msgid="7444296686402104807">"Indstillinger for afbryderknappen"</string>
+    <string name="recent_apps_label" msgid="6583276995616385847">"Seneste apps"</string>
+    <string name="lockscreen_label" msgid="648347953557887087">"Lås skærm"</string>
+    <string name="quick_settings_label" msgid="2999117381487601865">"Kvikmenu"</string>
+    <string name="notifications_label" msgid="6829741046963013567">"Notifikationer"</string>
+    <string name="screenshot_label" msgid="863978141223970162">"Screenshot"</string>
+    <string name="screenshot_utterance" msgid="1430760563401895074">"Tag et screenshot"</string>
+    <string name="volume_up_label" msgid="8592766918780362870">"Lydstyrke op"</string>
+    <string name="volume_down_label" msgid="8574981863656447346">"Lydstyrke ned"</string>
+    <string name="brightness_up_label" msgid="8010753822854544846">"Lysstyrke op"</string>
+    <string name="brightness_down_label" msgid="7115662941913272072">"Lysstyrke ned"</string>
+    <string name="previous_button_content_description" msgid="840869171117765966">"Gå til forrige skærm"</string>
+    <string name="next_button_content_description" msgid="6810058269847364406">"Gå til næste skærm"</string>
+    <string name="accessibility_menu_description" msgid="4458354794093858297">"Menuen Hjælpefunktioner giver dig en stor menu på skærmen, som du kan bruge til at styre din enhed. Du kan låse din enhed, justere lyd- og lysstyrken, tage screenshots og meget mere."</string>
+    <string name="accessibility_menu_summary" msgid="340071398148208130">"Styr enheden via den store menu"</string>
+    <string name="accessibility_menu_settings_name" msgid="1716888058785672611">"Indst. for menuen Hjælpefunktioner"</string>
+    <string name="accessibility_menu_large_buttons_title" msgid="8978499601044961736">"Store knapper"</string>
+    <string name="accessibility_menu_large_buttons_summary" msgid="236873938502785311">"Forstør knapperne i menuen Hjælpefunktioner"</string>
+    <string name="pref_help_title" msgid="6871558837025010641">"Hjælp"</string>
+    <string name="brightness_percentage_label" msgid="7391554573977867369">"Lysstyrke <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+    <string name="music_volume_percentage_label" msgid="398635599662604706">"Lydstyrke for musik <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+</resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-de/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-de/strings.xml
new file mode 100644
index 0000000..9d9b05a
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-de/strings.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="accessibility_menu_service_name" msgid="730136711554740131">"Menü für Bedienungshilfen"</string>
+    <string name="accessibility_menu_intro" msgid="3164193281544042394">"Über das Menü für Bedienungshilfen lässt sich ein großes Menü zur Bedienung deines Geräts auf dem Bildschirm öffnen. Du kannst beispielsweise das Gerät sperren, die Lautstärke und Helligkeit anpassen und Screenshots machen."</string>
+    <string name="assistant_label" msgid="6796392082252272356">"Assistant"</string>
+    <string name="assistant_utterance" msgid="65509599221141377">"Assistant"</string>
+    <string name="a11y_settings_label" msgid="3977714687248445050">"Einstellungen für Bedienungshilfen"</string>
+    <string name="power_label" msgid="7699720321491287839">"Ein/Aus"</string>
+    <string name="power_utterance" msgid="7444296686402104807">"Optionen für Ein-/Aus-Taste"</string>
+    <string name="recent_apps_label" msgid="6583276995616385847">"Kürzlich geöffnete Apps"</string>
+    <string name="lockscreen_label" msgid="648347953557887087">"Sperrbildschirm"</string>
+    <string name="quick_settings_label" msgid="2999117381487601865">"Schnell­­­ein­­stel­lungen"</string>
+    <string name="notifications_label" msgid="6829741046963013567">"Benach­­rich­ti­gungen"</string>
+    <string name="screenshot_label" msgid="863978141223970162">"Screenshot"</string>
+    <string name="screenshot_utterance" msgid="1430760563401895074">"Screenshot erstellen"</string>
+    <string name="volume_up_label" msgid="8592766918780362870">"Lautstärke erhöhen"</string>
+    <string name="volume_down_label" msgid="8574981863656447346">"Lautstärke verringern"</string>
+    <string name="brightness_up_label" msgid="8010753822854544846">"Helligkeit erhöhen"</string>
+    <string name="brightness_down_label" msgid="7115662941913272072">"Helligkeit verringern"</string>
+    <string name="previous_button_content_description" msgid="840869171117765966">"Zum vorherigen Bildschirm"</string>
+    <string name="next_button_content_description" msgid="6810058269847364406">"Zum nächsten Bildschirm"</string>
+    <string name="accessibility_menu_description" msgid="4458354794093858297">"Über das Menü für Bedienungshilfen lässt sich ein großes Menü zur Bedienung deines Geräts auf dem Bildschirm öffnen. Du kannst beispielsweise das Gerät sperren, die Lautstärke und Helligkeit anpassen und Screenshots machen."</string>
+    <string name="accessibility_menu_summary" msgid="340071398148208130">"Gerät mit großem Menü steuern"</string>
+    <string name="accessibility_menu_settings_name" msgid="1716888058785672611">"Einstellungen für das Menü „Bedienungshilfen“"</string>
+    <string name="accessibility_menu_large_buttons_title" msgid="8978499601044961736">"Große Schaltflächen"</string>
+    <string name="accessibility_menu_large_buttons_summary" msgid="236873938502785311">"Schaltflächen für das Menü „Bedienungshilfen“ vergrößern"</string>
+    <string name="pref_help_title" msgid="6871558837025010641">"Hilfe"</string>
+    <string name="brightness_percentage_label" msgid="7391554573977867369">"Helligkeit: <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+    <string name="music_volume_percentage_label" msgid="398635599662604706">"Musiklautstärke: <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+</resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-el/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-el/strings.xml
new file mode 100644
index 0000000..c51c9af
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-el/strings.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="accessibility_menu_service_name" msgid="730136711554740131">"Μενού προσβασιμότητας"</string>
+    <string name="accessibility_menu_intro" msgid="3164193281544042394">"Το μενού προσβασιμότητας παρέχει ένα μεγάλο μενού στην οθόνη για να ελέγχετε τη συσκευή σας. Μπορείτε να κλειδώνετε τη συσκευή, να ελέγχετε την ένταση ήχου και τη φωτεινότητα, να λαμβάνετε στιγμιότυπα οθόνης και άλλα."</string>
+    <string name="assistant_label" msgid="6796392082252272356">"Βοηθός"</string>
+    <string name="assistant_utterance" msgid="65509599221141377">"Βοηθός"</string>
+    <string name="a11y_settings_label" msgid="3977714687248445050">"Ρυθμίσεις προσβασιμότητας"</string>
+    <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="quick_settings_label" msgid="2999117381487601865">"Γρήγορες ρυθμίσεις"</string>
+    <string name="notifications_label" msgid="6829741046963013567">"Ειδοποιήσεις"</string>
+    <string name="screenshot_label" msgid="863978141223970162">"Στιγμιότυπο οθόνης"</string>
+    <string name="screenshot_utterance" msgid="1430760563401895074">"Λήψη στιγμιότυπου οθόνης"</string>
+    <string name="volume_up_label" msgid="8592766918780362870">"Αύξηση έντασης ήχου"</string>
+    <string name="volume_down_label" msgid="8574981863656447346">"Μείωση έντασης ήχου"</string>
+    <string name="brightness_up_label" msgid="8010753822854544846">"Αύξηση φωτεινότητας"</string>
+    <string name="brightness_down_label" msgid="7115662941913272072">"Μείωση φωτεινότητας"</string>
+    <string name="previous_button_content_description" msgid="840869171117765966">"Μετάβαση στην προηγούμενη οθόνη"</string>
+    <string name="next_button_content_description" msgid="6810058269847364406">"Μετάβαση στην επόμενη οθόνη"</string>
+    <string name="accessibility_menu_description" msgid="4458354794093858297">"Το μενού προσβασιμότητας παρέχει ένα μεγάλο μενού στην οθόνη για να ελέγχετε τη συσκευή σας. Μπορείτε να κλειδώνετε τη συσκευή, να ελέγχετε την ένταση ήχου και τη φωτεινότητα, να λαμβάνετε στιγμιότυπα οθόνης και άλλα."</string>
+    <string name="accessibility_menu_summary" msgid="340071398148208130">"Έλεγχος συσκευής μέσω μεγάλου μενού"</string>
+    <string name="accessibility_menu_settings_name" msgid="1716888058785672611">"Ρυθμίσεις μενού προσβασιμότητας"</string>
+    <string name="accessibility_menu_large_buttons_title" msgid="8978499601044961736">"Μεγάλα κουμπιά"</string>
+    <string name="accessibility_menu_large_buttons_summary" msgid="236873938502785311">"Αύξηση του μεγέθους των κουμπιών στο μενού προσβασιμότητας"</string>
+    <string name="pref_help_title" msgid="6871558837025010641">"Βοήθεια"</string>
+    <string name="brightness_percentage_label" msgid="7391554573977867369">"Φωτεινότητα <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+    <string name="music_volume_percentage_label" msgid="398635599662604706">"Ένταση μουσικής <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+</resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-en-rAU/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-en-rAU/strings.xml
new file mode 100644
index 0000000..0993a9b
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-en-rAU/strings.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="accessibility_menu_service_name" msgid="730136711554740131">"Accessibility Menu"</string>
+    <string name="accessibility_menu_intro" msgid="3164193281544042394">"The Accessibility Menu provides a large on-screen menu to control your device. You can lock your device, control volume and brightness, take screenshots and more."</string>
+    <string name="assistant_label" msgid="6796392082252272356">"Assistant"</string>
+    <string name="assistant_utterance" msgid="65509599221141377">"Assistant"</string>
+    <string name="a11y_settings_label" msgid="3977714687248445050">"Accessibility settings"</string>
+    <string name="power_label" msgid="7699720321491287839">"Power"</string>
+    <string name="power_utterance" msgid="7444296686402104807">"Power options"</string>
+    <string name="recent_apps_label" msgid="6583276995616385847">"Recent apps"</string>
+    <string name="lockscreen_label" msgid="648347953557887087">"Lock screen"</string>
+    <string name="quick_settings_label" msgid="2999117381487601865">"Quick Settings"</string>
+    <string name="notifications_label" msgid="6829741046963013567">"Notifications"</string>
+    <string name="screenshot_label" msgid="863978141223970162">"Screenshot"</string>
+    <string name="screenshot_utterance" msgid="1430760563401895074">"Take screenshot"</string>
+    <string name="volume_up_label" msgid="8592766918780362870">"Volume up"</string>
+    <string name="volume_down_label" msgid="8574981863656447346">"Volume down"</string>
+    <string name="brightness_up_label" msgid="8010753822854544846">"Brightness up"</string>
+    <string name="brightness_down_label" msgid="7115662941913272072">"Brightness down"</string>
+    <string name="previous_button_content_description" msgid="840869171117765966">"Go to previous screen"</string>
+    <string name="next_button_content_description" msgid="6810058269847364406">"Go to next screen"</string>
+    <string name="accessibility_menu_description" msgid="4458354794093858297">"The Accessibility Menu provides a large on-screen menu to control your device. You can lock your device, control volume and brightness, take screenshots and more."</string>
+    <string name="accessibility_menu_summary" msgid="340071398148208130">"Control device via large menu"</string>
+    <string name="accessibility_menu_settings_name" msgid="1716888058785672611">"Accessibility Menu settings"</string>
+    <string name="accessibility_menu_large_buttons_title" msgid="8978499601044961736">"Large buttons"</string>
+    <string name="accessibility_menu_large_buttons_summary" msgid="236873938502785311">"Increase size of Accessibility Menu buttons"</string>
+    <string name="pref_help_title" msgid="6871558837025010641">"Help"</string>
+    <string name="brightness_percentage_label" msgid="7391554573977867369">"Brightness <xliff:g id="PERCENTAGE">%1$s</xliff:g>%%"</string>
+    <string name="music_volume_percentage_label" msgid="398635599662604706">"Music volume <xliff:g id="PERCENTAGE">%1$s</xliff:g>%%"</string>
+</resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-en-rCA/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-en-rCA/strings.xml
new file mode 100644
index 0000000..5fc3afd
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-en-rCA/strings.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="accessibility_menu_service_name" msgid="730136711554740131">"Accessibility Menu"</string>
+    <string name="accessibility_menu_intro" msgid="3164193281544042394">"The Accessibility Menu provides a large on-screen menu to control your device. You can lock your device, control volume and brightness, take screenshots, and more."</string>
+    <string name="assistant_label" msgid="6796392082252272356">"Assistant"</string>
+    <string name="assistant_utterance" msgid="65509599221141377">"Assistant"</string>
+    <string name="a11y_settings_label" msgid="3977714687248445050">"Accessibility Settings"</string>
+    <string name="power_label" msgid="7699720321491287839">"Power"</string>
+    <string name="power_utterance" msgid="7444296686402104807">"Power options"</string>
+    <string name="recent_apps_label" msgid="6583276995616385847">"Recent apps"</string>
+    <string name="lockscreen_label" msgid="648347953557887087">"Lock screen"</string>
+    <string name="quick_settings_label" msgid="2999117381487601865">"Quick Settings"</string>
+    <string name="notifications_label" msgid="6829741046963013567">"Notifications"</string>
+    <string name="screenshot_label" msgid="863978141223970162">"Screenshot"</string>
+    <string name="screenshot_utterance" msgid="1430760563401895074">"Take screenshot"</string>
+    <string name="volume_up_label" msgid="8592766918780362870">"Volume up"</string>
+    <string name="volume_down_label" msgid="8574981863656447346">"Volume down"</string>
+    <string name="brightness_up_label" msgid="8010753822854544846">"Brightness up"</string>
+    <string name="brightness_down_label" msgid="7115662941913272072">"Brightness down"</string>
+    <string name="previous_button_content_description" msgid="840869171117765966">"Go to previous screen"</string>
+    <string name="next_button_content_description" msgid="6810058269847364406">"Go to next screen"</string>
+    <string name="accessibility_menu_description" msgid="4458354794093858297">"The Accessibility Menu provides a large on-screen menu to control your device. You can lock your device, control volume and brightness, take screenshots, and more."</string>
+    <string name="accessibility_menu_summary" msgid="340071398148208130">"Control device via large menu"</string>
+    <string name="accessibility_menu_settings_name" msgid="1716888058785672611">"Accessibility Menu Settings"</string>
+    <string name="accessibility_menu_large_buttons_title" msgid="8978499601044961736">"Large buttons"</string>
+    <string name="accessibility_menu_large_buttons_summary" msgid="236873938502785311">"Increase size of Accessibility Menu Buttons"</string>
+    <string name="pref_help_title" msgid="6871558837025010641">"Help"</string>
+    <string name="brightness_percentage_label" msgid="7391554573977867369">"Brightness <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+    <string name="music_volume_percentage_label" msgid="398635599662604706">"Music volume <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+</resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-en-rGB/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-en-rGB/strings.xml
new file mode 100644
index 0000000..0993a9b
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-en-rGB/strings.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="accessibility_menu_service_name" msgid="730136711554740131">"Accessibility Menu"</string>
+    <string name="accessibility_menu_intro" msgid="3164193281544042394">"The Accessibility Menu provides a large on-screen menu to control your device. You can lock your device, control volume and brightness, take screenshots and more."</string>
+    <string name="assistant_label" msgid="6796392082252272356">"Assistant"</string>
+    <string name="assistant_utterance" msgid="65509599221141377">"Assistant"</string>
+    <string name="a11y_settings_label" msgid="3977714687248445050">"Accessibility settings"</string>
+    <string name="power_label" msgid="7699720321491287839">"Power"</string>
+    <string name="power_utterance" msgid="7444296686402104807">"Power options"</string>
+    <string name="recent_apps_label" msgid="6583276995616385847">"Recent apps"</string>
+    <string name="lockscreen_label" msgid="648347953557887087">"Lock screen"</string>
+    <string name="quick_settings_label" msgid="2999117381487601865">"Quick Settings"</string>
+    <string name="notifications_label" msgid="6829741046963013567">"Notifications"</string>
+    <string name="screenshot_label" msgid="863978141223970162">"Screenshot"</string>
+    <string name="screenshot_utterance" msgid="1430760563401895074">"Take screenshot"</string>
+    <string name="volume_up_label" msgid="8592766918780362870">"Volume up"</string>
+    <string name="volume_down_label" msgid="8574981863656447346">"Volume down"</string>
+    <string name="brightness_up_label" msgid="8010753822854544846">"Brightness up"</string>
+    <string name="brightness_down_label" msgid="7115662941913272072">"Brightness down"</string>
+    <string name="previous_button_content_description" msgid="840869171117765966">"Go to previous screen"</string>
+    <string name="next_button_content_description" msgid="6810058269847364406">"Go to next screen"</string>
+    <string name="accessibility_menu_description" msgid="4458354794093858297">"The Accessibility Menu provides a large on-screen menu to control your device. You can lock your device, control volume and brightness, take screenshots and more."</string>
+    <string name="accessibility_menu_summary" msgid="340071398148208130">"Control device via large menu"</string>
+    <string name="accessibility_menu_settings_name" msgid="1716888058785672611">"Accessibility Menu settings"</string>
+    <string name="accessibility_menu_large_buttons_title" msgid="8978499601044961736">"Large buttons"</string>
+    <string name="accessibility_menu_large_buttons_summary" msgid="236873938502785311">"Increase size of Accessibility Menu buttons"</string>
+    <string name="pref_help_title" msgid="6871558837025010641">"Help"</string>
+    <string name="brightness_percentage_label" msgid="7391554573977867369">"Brightness <xliff:g id="PERCENTAGE">%1$s</xliff:g>%%"</string>
+    <string name="music_volume_percentage_label" msgid="398635599662604706">"Music volume <xliff:g id="PERCENTAGE">%1$s</xliff:g>%%"</string>
+</resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-en-rIN/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-en-rIN/strings.xml
new file mode 100644
index 0000000..0993a9b
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-en-rIN/strings.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="accessibility_menu_service_name" msgid="730136711554740131">"Accessibility Menu"</string>
+    <string name="accessibility_menu_intro" msgid="3164193281544042394">"The Accessibility Menu provides a large on-screen menu to control your device. You can lock your device, control volume and brightness, take screenshots and more."</string>
+    <string name="assistant_label" msgid="6796392082252272356">"Assistant"</string>
+    <string name="assistant_utterance" msgid="65509599221141377">"Assistant"</string>
+    <string name="a11y_settings_label" msgid="3977714687248445050">"Accessibility settings"</string>
+    <string name="power_label" msgid="7699720321491287839">"Power"</string>
+    <string name="power_utterance" msgid="7444296686402104807">"Power options"</string>
+    <string name="recent_apps_label" msgid="6583276995616385847">"Recent apps"</string>
+    <string name="lockscreen_label" msgid="648347953557887087">"Lock screen"</string>
+    <string name="quick_settings_label" msgid="2999117381487601865">"Quick Settings"</string>
+    <string name="notifications_label" msgid="6829741046963013567">"Notifications"</string>
+    <string name="screenshot_label" msgid="863978141223970162">"Screenshot"</string>
+    <string name="screenshot_utterance" msgid="1430760563401895074">"Take screenshot"</string>
+    <string name="volume_up_label" msgid="8592766918780362870">"Volume up"</string>
+    <string name="volume_down_label" msgid="8574981863656447346">"Volume down"</string>
+    <string name="brightness_up_label" msgid="8010753822854544846">"Brightness up"</string>
+    <string name="brightness_down_label" msgid="7115662941913272072">"Brightness down"</string>
+    <string name="previous_button_content_description" msgid="840869171117765966">"Go to previous screen"</string>
+    <string name="next_button_content_description" msgid="6810058269847364406">"Go to next screen"</string>
+    <string name="accessibility_menu_description" msgid="4458354794093858297">"The Accessibility Menu provides a large on-screen menu to control your device. You can lock your device, control volume and brightness, take screenshots and more."</string>
+    <string name="accessibility_menu_summary" msgid="340071398148208130">"Control device via large menu"</string>
+    <string name="accessibility_menu_settings_name" msgid="1716888058785672611">"Accessibility Menu settings"</string>
+    <string name="accessibility_menu_large_buttons_title" msgid="8978499601044961736">"Large buttons"</string>
+    <string name="accessibility_menu_large_buttons_summary" msgid="236873938502785311">"Increase size of Accessibility Menu buttons"</string>
+    <string name="pref_help_title" msgid="6871558837025010641">"Help"</string>
+    <string name="brightness_percentage_label" msgid="7391554573977867369">"Brightness <xliff:g id="PERCENTAGE">%1$s</xliff:g>%%"</string>
+    <string name="music_volume_percentage_label" msgid="398635599662604706">"Music volume <xliff:g id="PERCENTAGE">%1$s</xliff:g>%%"</string>
+</resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-en-rXC/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-en-rXC/strings.xml
new file mode 100644
index 0000000..fec60d5
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-en-rXC/strings.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="accessibility_menu_service_name" msgid="730136711554740131">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‏‎‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‏‏‏‏‎‎‎‏‎‎‎‎‎‎‏‏‎‏‏‎‎‎‏‎‎‏‎‏‏‏‏‏‎‏‎‎‎‏‏‎Accessibility Menu‎‏‎‎‏‎"</string>
+    <string name="accessibility_menu_intro" msgid="3164193281544042394">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‏‏‏‏‏‎‏‎‎‏‎‏‏‏‏‎‎‏‎‎‎‎‎‏‏‎‎‏‏‎‎‎‏‏‎‎‎‏‏‏‎‎‎‎‏‏‎‏‏‏‏‎‎‏‏‎‏‎‎The Accessibility Menu provides a large on-screen menu to control your device. You can lock your device, control volume and brightness, take screenshots, and more.‎‏‎‎‏‎"</string>
+    <string name="assistant_label" msgid="6796392082252272356">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‏‎‎‏‎‏‎‎‎‏‏‎‏‎‎‎‏‎‏‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‏‏‏‎‏‏‎‎‏‎‎‏‎‎‏‎‏‏‏‎‎‏‎‎‎Assistant‎‏‎‎‏‎"</string>
+    <string name="assistant_utterance" msgid="65509599221141377">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‎‎‏‏‏‎‏‎‎‎‏‎‏‏‏‏‎‎‏‎‏‎‎‎‏‎‎‎‏‎‎‏‏‎‏‏‏‏‎‏‎‎‎‏‎‎‏‏‏‏‏‎‎‎‎‎‎‏‎Assistant‎‏‎‎‏‎"</string>
+    <string name="a11y_settings_label" msgid="3977714687248445050">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‏‏‎‎‏‏‎‎‏‏‏‎‏‎‏‏‏‎‎‏‏‎‏‏‏‎‏‏‏‎‎‏‏‏‎‏‎‎‏‏‎‎‎‎‏‎‏‏‏‎‎‏‏‏‏‎‏‎‎Accessibility Settings‎‏‎‎‏‎"</string>
+    <string name="power_label" msgid="7699720321491287839">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‏‎‏‏‎‏‏‎‏‎‏‏‏‎‎‏‏‎‏‏‎‏‎‏‏‎‎‏‏‎‏‎‎‎‎‏‎‏‏‎‎‎‎‎‏‏‏‏‏‏‎‎‎‏‏‏‏‏‎Power‎‏‎‎‏‎"</string>
+    <string name="power_utterance" msgid="7444296686402104807">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‏‏‎‏‎‎‏‏‏‏‎‏‏‏‎‏‎‎‎‏‏‎‎‏‎‏‎‎‎‎‏‎‏‏‏‎‎‏‏‏‎‏‎‏‏‎‏‏‎‏‏‏‏‎‎‏‏‏‎Power options‎‏‎‎‏‎"</string>
+    <string name="recent_apps_label" msgid="6583276995616385847">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‏‏‎‏‎‏‏‏‎‎‎‏‏‏‏‏‏‏‏‎‎‎‏‏‎‎‎‎‏‏‏‏‎‏‏‎‏‏‎‏‏‏‎‎‏‎‏‎‏‏‎‎‏‏‎‏‏‏‎Recent apps‎‏‎‎‏‎"</string>
+    <string name="lockscreen_label" msgid="648347953557887087">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‎‏‎‏‎‎‎‎‎‏‏‏‎‏‎‏‎‏‏‎‏‎‏‎‏‎‏‏‎‏‎‏‏‎‎‎‎‏‏‎‏‏‏‏‎Lock screen‎‏‎‎‏‎"</string>
+    <string name="quick_settings_label" msgid="2999117381487601865">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‎‏‏‎‎‏‏‏‏‏‎‎‎‎‎‎‎‏‎‏‎‏‏‏‏‎‎‏‎‏‎‎‏‏‎‏‎‎‎‏‎‎‏‎‏‎‏‎‎‎‏‏‎‎‏‎‎‏‎Quick Settings‎‏‎‎‏‎"</string>
+    <string name="notifications_label" msgid="6829741046963013567">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‏‎‏‏‎‎‏‎‎‎‎‎‎‏‏‏‎‏‎‏‎‎‏‎‏‏‏‏‏‏‎‏‎‎‎‎‏‎‎‎‏‎‏‎‎‎‎‏‏‏‏‎‏‏‏‏‏‏‎Notifications‎‏‎‎‏‎"</string>
+    <string name="screenshot_label" msgid="863978141223970162">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‏‏‏‏‏‏‏‏‎‏‎‏‏‏‎‏‏‏‏‎‎‎‏‎‏‎‏‎‏‏‏‎‎‏‎‏‎‏‏‎‏‏‎‏‏‎‎‏‎‏‎‏‏‏‎‎‏‎‎Screenshot‎‏‎‎‏‎"</string>
+    <string name="screenshot_utterance" msgid="1430760563401895074">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‎‏‏‏‏‎‏‏‎‏‏‎‎‎‏‎‏‎‏‎‎‏‎‎‏‏‎‏‏‏‎‎‏‎‎‎‎‏‏‎‏‎‏‎‎‏‎‏‏‎‎‏‎‏‎‎‎‏‎‎Take screenshot‎‏‎‎‏‎"</string>
+    <string name="volume_up_label" msgid="8592766918780362870">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‏‏‎‎‏‏‏‏‏‏‏‎‏‎‎‎‏‏‏‏‏‏‏‏‎‎‎‏‏‏‎‏‏‏‏‎‏‎‎‎‎‏‏‎‏‎‎‎‎‎‎‏‏‏‎‏‏‎‎Volume up‎‏‎‎‏‎"</string>
+    <string name="volume_down_label" msgid="8574981863656447346">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‏‏‎‎‎‎‎‎‎‎‎‏‏‏‎‏‎‎‏‎‎‏‎‎‏‎‏‎‏‎‎‎‏‎‏‎‏‎‏‎‎‎‏‏‎‏‏‏‎‏‎‏‏‏‎‎‏‎‎Volume down‎‏‎‎‏‎"</string>
+    <string name="brightness_up_label" msgid="8010753822854544846">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‏‏‎‎‏‎‏‎‏‏‏‏‏‎‏‎‏‎‎‎‏‎‏‎‎‎‏‎‏‏‏‎‏‎‎‏‎‏‏‎‎‎‏‏‏‏‏‏‎‏‏‏‎‎‏‏‏‎‎Brightness up‎‏‎‎‏‎"</string>
+    <string name="brightness_down_label" msgid="7115662941913272072">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‎‏‎‏‎‏‏‏‏‏‏‏‏‏‎‏‎‎‏‏‎‏‏‏‏‏‎‏‏‎‎‏‎‏‎‎‎‏‎‎‏‏‎‏‏‎‎‏‏‏‏‎‎‎‎‏‎‎‎‎Brightness down‎‏‎‎‏‎"</string>
+    <string name="previous_button_content_description" msgid="840869171117765966">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‏‏‏‎‏‎‏‎‏‏‎‏‎‏‏‏‏‎‎‎‎‎‏‏‏‎‎‏‎‏‎‏‏‏‎‎‎‎‏‏‎‏‎‎‏‏‎‏‎‏‎‏‎‎‏‏‏‎‎Go to previous screen‎‏‎‎‏‎"</string>
+    <string name="next_button_content_description" msgid="6810058269847364406">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‏‎‏‎‎‎‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‏‎‏‎‎‏‎‎‏‎‎‏‏‏‏‏‎‏‏‎‏‎‏‎‎‎‏‏‎‎‏‏‎‏‏‎‎Go to next screen‎‏‎‎‏‎"</string>
+    <string name="accessibility_menu_description" msgid="4458354794093858297">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‎‏‏‏‎‏‏‏‏‏‎‏‎‎‎‎‏‎‎‎‎‏‎‎‎‎‎‏‎‏‏‎‎‎‏‎‎‏‏‏‏‏‏‏‎‎‏‏‎‏‏‏‏‏‏‎‎‏‎The Accessibility Menu provides a large on-screen menu to control your device. You can lock your device, control volume and brightness, take screenshots, and more.‎‏‎‎‏‎"</string>
+    <string name="accessibility_menu_summary" msgid="340071398148208130">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‏‏‎‎‏‎‏‏‏‎‎‎‎‎‏‎‏‏‎‏‎‎‏‎‎‎‏‎‏‎‎‎‎‏‎‎‏‏‏‎‎‏‎‎‏‏‏‏‎‏‏‎‎‎‎‎‎‎‏‎‎Control device via large menu‎‏‎‎‏‎"</string>
+    <string name="accessibility_menu_settings_name" msgid="1716888058785672611">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‏‏‏‏‎‏‎‎‏‏‏‎‎‏‏‏‎‎‏‎‎‏‎‏‏‏‏‏‎‎‏‏‏‏‎‎‎‎‎‏‏‏‏‎‏‎‏‏‎‏‏‎‏‎‎‎‏‏‎Accessibility Menu Settings‎‏‎‎‏‎"</string>
+    <string name="accessibility_menu_large_buttons_title" msgid="8978499601044961736">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‎‎‏‎‎‏‏‎‏‎‎‎‎‎‏‎‎‏‏‏‎‏‎‎‎‏‎‎‏‎‏‎‎‎‎‏‎‎‎‏‎‏‎‏‏‏‏‎‎‏‏‏‎‎‏‎‎‎‎Large buttons‎‏‎‎‏‎"</string>
+    <string name="accessibility_menu_large_buttons_summary" msgid="236873938502785311">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‎‏‏‎‏‎‎‏‎‎‏‏‎‎‎‏‎‏‏‏‎‎‏‎‏‏‏‏‎‎‏‎‎‎‏‎‎‎‎‎‎‎‏‏‎‎‏‏‎‎‏‎‎‎‏‏‏‏‏‎Increase size of Accessibility Menu Buttons‎‏‎‎‏‎"</string>
+    <string name="pref_help_title" msgid="6871558837025010641">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‏‏‎‏‎‏‏‏‎‎‏‎‏‎‏‏‏‎‎‏‎‏‏‎‏‎‏‏‏‎‏‏‏‎‎‎‎‏‎‎‏‎‎‏‏‏‎‎‏‏‏‏‎‏‎‎‎‏‎Help‎‏‎‎‏‎"</string>
+    <string name="brightness_percentage_label" msgid="7391554573977867369">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‏‎‏‎‎‏‎‏‎‎‎‎‎‏‎‎‏‏‏‎‏‏‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‏‎‏‎‏‎‏‎‎‎‏‏‎‏‎‎‏‎Brightness ‎‏‎‎‏‏‎<xliff:g id="PERCENTAGE">%1$s</xliff:g>‎‏‎‎‏‏‏‎ %%‎‏‎‎‏‎"</string>
+    <string name="music_volume_percentage_label" msgid="398635599662604706">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‏‏‎‏‏‎‎‎‏‎‎‎‎‎‏‏‏‏‎‎‏‏‏‏‎‏‏‏‎‏‎‎‎‎‎‎‏‎‏‏‎‏‏‎‏‏‏‎‎‎‎‏‏‎‏‎‎‎‏‎‎Music volume ‎‏‎‎‏‏‎<xliff:g id="PERCENTAGE">%1$s</xliff:g>‎‏‎‎‏‏‏‎ %%‎‏‎‎‏‎"</string>
+</resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-es-rUS/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-es-rUS/strings.xml
new file mode 100644
index 0000000..4249958
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-es-rUS/strings.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="accessibility_menu_service_name" msgid="730136711554740131">"Menú de accesibilidad"</string>
+    <string name="accessibility_menu_intro" msgid="3164193281544042394">"El Menú de accesibilidad es un menú de gran tamaño que se muestra en pantalla y te permite controlar tu dispositivo para bloquearlo, controlar su volumen y brillo, realizar capturas de pantalla y mucho más."</string>
+    <string name="assistant_label" msgid="6796392082252272356">"Asistente"</string>
+    <string name="assistant_utterance" msgid="65509599221141377">"Asistente"</string>
+    <string name="a11y_settings_label" msgid="3977714687248445050">"Configuración de accesibilidad"</string>
+    <string name="power_label" msgid="7699720321491287839">"Encendido"</string>
+    <string name="power_utterance" msgid="7444296686402104807">"Opciones de encendido"</string>
+    <string name="recent_apps_label" msgid="6583276995616385847">"Apps recientes"</string>
+    <string name="lockscreen_label" msgid="648347953557887087">"Bloquear pantalla"</string>
+    <string name="quick_settings_label" msgid="2999117381487601865">"Configuración rápida"</string>
+    <string name="notifications_label" msgid="6829741046963013567">"Notificaciones"</string>
+    <string name="screenshot_label" msgid="863978141223970162">"Captura de pantalla"</string>
+    <string name="screenshot_utterance" msgid="1430760563401895074">"Tomar captura de pantalla"</string>
+    <string name="volume_up_label" msgid="8592766918780362870">"Subir volumen"</string>
+    <string name="volume_down_label" msgid="8574981863656447346">"Bajar volumen"</string>
+    <string name="brightness_up_label" msgid="8010753822854544846">"Aumentar brillo"</string>
+    <string name="brightness_down_label" msgid="7115662941913272072">"Disminuir brillo"</string>
+    <string name="previous_button_content_description" msgid="840869171117765966">"Ir a la pantalla anterior"</string>
+    <string name="next_button_content_description" msgid="6810058269847364406">"Ir a la siguiente pantalla"</string>
+    <string name="accessibility_menu_description" msgid="4458354794093858297">"El menú de Accesibilidad es un menú de gran tamaño que se muestra en la pantalla y te permite controlar tu dispositivo. Puedes bloquearlo, controlar el volumen y el brillo, realizar capturas de pantalla y mucho más."</string>
+    <string name="accessibility_menu_summary" msgid="340071398148208130">"Controla el dispositivo con un menú de gran tamaño"</string>
+    <string name="accessibility_menu_settings_name" msgid="1716888058785672611">"Configuración del Menú de accesibilidad"</string>
+    <string name="accessibility_menu_large_buttons_title" msgid="8978499601044961736">"Botones grandes"</string>
+    <string name="accessibility_menu_large_buttons_summary" msgid="236873938502785311">"Aumenta el tamaño de los botones del Menú de accesibilidad"</string>
+    <string name="pref_help_title" msgid="6871558837025010641">"Ayuda"</string>
+    <string name="brightness_percentage_label" msgid="7391554573977867369">"Brillo: <xliff:g id="PERCENTAGE">%1$s</xliff:g>%%"</string>
+    <string name="music_volume_percentage_label" msgid="398635599662604706">"Volumen de la música: <xliff:g id="PERCENTAGE">%1$s</xliff:g>%%"</string>
+</resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-es/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-es/strings.xml
new file mode 100644
index 0000000..877a43c
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-es/strings.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="accessibility_menu_service_name" msgid="730136711554740131">"Menú Accesibilidad"</string>
+    <string name="accessibility_menu_intro" msgid="3164193281544042394">"El menú de accesibilidad es un menú de gran tamaño que se muestra en pantalla para controlar tu dispositivo. Puedes bloquear el dispositivo, controlar el volumen y el brillo, hacer capturas de pantalla y más."</string>
+    <string name="assistant_label" msgid="6796392082252272356">"Asistente"</string>
+    <string name="assistant_utterance" msgid="65509599221141377">"Asistente"</string>
+    <string name="a11y_settings_label" msgid="3977714687248445050">"Ajustes de accesibilidad"</string>
+    <string name="power_label" msgid="7699720321491287839">"Encendido"</string>
+    <string name="power_utterance" msgid="7444296686402104807">"Opciones de encendido"</string>
+    <string name="recent_apps_label" msgid="6583276995616385847">"Aplicaciones recientes"</string>
+    <string name="lockscreen_label" msgid="648347953557887087">"Bloquear pantalla"</string>
+    <string name="quick_settings_label" msgid="2999117381487601865">"Ajustes rápidos"</string>
+    <string name="notifications_label" msgid="6829741046963013567">"Notificaciones"</string>
+    <string name="screenshot_label" msgid="863978141223970162">"Captura de pantalla"</string>
+    <string name="screenshot_utterance" msgid="1430760563401895074">"Hacer captura"</string>
+    <string name="volume_up_label" msgid="8592766918780362870">"Subir volumen"</string>
+    <string name="volume_down_label" msgid="8574981863656447346">"Bajar volumen"</string>
+    <string name="brightness_up_label" msgid="8010753822854544846">"Aumentar el brillo"</string>
+    <string name="brightness_down_label" msgid="7115662941913272072">"Reducir el brillo"</string>
+    <string name="previous_button_content_description" msgid="840869171117765966">"Ir a la pantalla anterior"</string>
+    <string name="next_button_content_description" msgid="6810058269847364406">"Ir a la siguiente pantalla"</string>
+    <string name="accessibility_menu_description" msgid="4458354794093858297">"El menú de accesibilidad es un menú de gran tamaño que se muestra en pantalla para controlar tu dispositivo. Puedes bloquear el dispositivo, controlar el volumen y el brillo, hacer capturas de pantalla y más."</string>
+    <string name="accessibility_menu_summary" msgid="340071398148208130">"Controla el dispositivo con un menú de gran tamaño"</string>
+    <string name="accessibility_menu_settings_name" msgid="1716888058785672611">"Ajustes del menú de accesibilidad"</string>
+    <string name="accessibility_menu_large_buttons_title" msgid="8978499601044961736">"Botones grandes"</string>
+    <string name="accessibility_menu_large_buttons_summary" msgid="236873938502785311">"Aumenta el tamaño de los botones del menú de accesibilidad"</string>
+    <string name="pref_help_title" msgid="6871558837025010641">"Ayuda"</string>
+    <string name="brightness_percentage_label" msgid="7391554573977867369">"Brillo: <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+    <string name="music_volume_percentage_label" msgid="398635599662604706">"Volumen de la música: <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+</resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-et/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-et/strings.xml
new file mode 100644
index 0000000..8ba0206
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-et/strings.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="accessibility_menu_service_name" msgid="730136711554740131">"Juurdepääsetavuse menüü"</string>
+    <string name="accessibility_menu_intro" msgid="3164193281544042394">"Juurdepääsetavuse menüü on suur ekraanil kuvatav menüü, mille abil oma seadet hallata. Saate oma seadme lukustada, hallata helitugevust ja eredust, jäädvustada ekraanipilte ning teha muudki."</string>
+    <string name="assistant_label" msgid="6796392082252272356">"Assistent"</string>
+    <string name="assistant_utterance" msgid="65509599221141377">"Assistent"</string>
+    <string name="a11y_settings_label" msgid="3977714687248445050">"Juurde­pääsetavuse seaded"</string>
+    <string name="power_label" msgid="7699720321491287839">"Toitenupp"</string>
+    <string name="power_utterance" msgid="7444296686402104807">"Toitevalikud"</string>
+    <string name="recent_apps_label" msgid="6583276995616385847">"Hiljutised rakendused"</string>
+    <string name="lockscreen_label" msgid="648347953557887087">"Lukustuskuva"</string>
+    <string name="quick_settings_label" msgid="2999117381487601865">"Kiirseaded"</string>
+    <string name="notifications_label" msgid="6829741046963013567">"Märguanded"</string>
+    <string name="screenshot_label" msgid="863978141223970162">"Ekraanipilt"</string>
+    <string name="screenshot_utterance" msgid="1430760563401895074">"Jäädvusta ekraanipilt"</string>
+    <string name="volume_up_label" msgid="8592766918780362870">"Suurenda helitugevust"</string>
+    <string name="volume_down_label" msgid="8574981863656447346">"Vähenda helitugevust"</string>
+    <string name="brightness_up_label" msgid="8010753822854544846">"Suurenda eredust"</string>
+    <string name="brightness_down_label" msgid="7115662941913272072">"Vähenda eredust"</string>
+    <string name="previous_button_content_description" msgid="840869171117765966">"Eelmise ekraanikuva avamine"</string>
+    <string name="next_button_content_description" msgid="6810058269847364406">"Järgmise ekraanikuva avamine"</string>
+    <string name="accessibility_menu_description" msgid="4458354794093858297">"Juurdepääsetavuse menüü on suur ekraanil kuvatav menüü, mille abil oma seadet hallata. Saate oma seadme lukustada, hallata helitugevust ja eredust, jäädvustada ekraanipilte ning teha muudki."</string>
+    <string name="accessibility_menu_summary" msgid="340071398148208130">"Seadme juhtimine suure menüü kaudu"</string>
+    <string name="accessibility_menu_settings_name" msgid="1716888058785672611">"Juurdepääsetavuse menüü seaded"</string>
+    <string name="accessibility_menu_large_buttons_title" msgid="8978499601044961736">"Suured nupud"</string>
+    <string name="accessibility_menu_large_buttons_summary" msgid="236873938502785311">"Juurdepääsetavuse menüü nuppude suurendamine"</string>
+    <string name="pref_help_title" msgid="6871558837025010641">"Abi"</string>
+    <string name="brightness_percentage_label" msgid="7391554573977867369">"Eredus on <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+    <string name="music_volume_percentage_label" msgid="398635599662604706">"Muusika helitugevus on <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+</resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-eu/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-eu/strings.xml
new file mode 100644
index 0000000..f6dcdd3
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-eu/strings.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="accessibility_menu_service_name" msgid="730136711554740131">"Erabilerraztasun-menua"</string>
+    <string name="accessibility_menu_intro" msgid="3164193281544042394">"Erabilerraztasun-menuari esker, tamaina handiko menu bat izango duzu pantailan; menu horren bidez, gailua kontrolatzeko aukera izango duzu. Besteak beste, hauek egin ahalko dituzu: gailua blokeatu; bolumena eta distira kontrolatu, eta pantaila-argazkiak egin."</string>
+    <string name="assistant_label" msgid="6796392082252272356">"Laguntzailea"</string>
+    <string name="assistant_utterance" msgid="65509599221141377">"Laguntzailea"</string>
+    <string name="a11y_settings_label" msgid="3977714687248445050">"Erabilerraztasun-&amp;#173;ezarpenak"</string>
+    <string name="power_label" msgid="7699720321491287839">"Bateria"</string>
+    <string name="power_utterance" msgid="7444296686402104807">"Bateria kontrolatzeko aukerak"</string>
+    <string name="recent_apps_label" msgid="6583276995616385847">"Azken aplikazioak"</string>
+    <string name="lockscreen_label" msgid="648347953557887087">"Pantaila blokeatua"</string>
+    <string name="quick_settings_label" msgid="2999117381487601865">"Ezarpen bizkorrak"</string>
+    <string name="notifications_label" msgid="6829741046963013567">"Jakinarazpenak"</string>
+    <string name="screenshot_label" msgid="863978141223970162">"Pantaila-argazkia"</string>
+    <string name="screenshot_utterance" msgid="1430760563401895074">"Atera pantaila-argazki bat"</string>
+    <string name="volume_up_label" msgid="8592766918780362870">"Igo bolumena"</string>
+    <string name="volume_down_label" msgid="8574981863656447346">"Jaitsi bolumena"</string>
+    <string name="brightness_up_label" msgid="8010753822854544846">"Handitu distira"</string>
+    <string name="brightness_down_label" msgid="7115662941913272072">"Txikitu distira"</string>
+    <string name="previous_button_content_description" msgid="840869171117765966">"Joan aurreko pantailara"</string>
+    <string name="next_button_content_description" msgid="6810058269847364406">"Joan hurrengo pantailara"</string>
+    <string name="accessibility_menu_description" msgid="4458354794093858297">"Erabilerraztasun-menuari esker, tamaina handiko menu bat izango duzu pantailan; menu horren bidez, gailua kontrolatzeko aukera izango duzu. Besteak beste, hauek egin ahalko dituzu: gailua blokeatu; bolumena eta distira kontrolatu, eta pantaila-argazkiak egin."</string>
+    <string name="accessibility_menu_summary" msgid="340071398148208130">"Kontrolatu gailua menu handiaren bidez"</string>
+    <string name="accessibility_menu_settings_name" msgid="1716888058785672611">"Erabilerraztasun-menuaren ezarpenak"</string>
+    <string name="accessibility_menu_large_buttons_title" msgid="8978499601044961736">"Botoi handiak"</string>
+    <string name="accessibility_menu_large_buttons_summary" msgid="236873938502785311">"Handitu erabilerraztasun-menuko botoien tamaina"</string>
+    <string name="pref_help_title" msgid="6871558837025010641">"Laguntza"</string>
+    <string name="brightness_percentage_label" msgid="7391554573977867369">"Distira: %% <xliff:g id="PERCENTAGE">%1$s</xliff:g>"</string>
+    <string name="music_volume_percentage_label" msgid="398635599662604706">"Musikaren bolumena: %% <xliff:g id="PERCENTAGE">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-fa/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-fa/strings.xml
new file mode 100644
index 0000000..49d8f69
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-fa/strings.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="accessibility_menu_service_name" msgid="730136711554740131">"منوی دسترس‌پذیری"</string>
+    <string name="accessibility_menu_intro" msgid="3164193281544042394">"«منوی دسترس‌پذیری» منوی بزرگی را روی صفحه برای کنترل دستگاه ارائه می‌دهد. می‌توانید دستگاه را قفل کنید، میزان صدا و روشنایی را کنترل کنید، نماگرفت ثبت کنید، و کارهای بیشتری انجام دهید."</string>
+    <string name="assistant_label" msgid="6796392082252272356">"دستیار"</string>
+    <string name="assistant_utterance" msgid="65509599221141377">"دستیار"</string>
+    <string name="a11y_settings_label" msgid="3977714687248445050">"تنظیمات دسترس‌پذیری"</string>
+    <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="quick_settings_label" msgid="2999117381487601865">"تنظیمات سریع"</string>
+    <string name="notifications_label" msgid="6829741046963013567">"اعلان‌ها"</string>
+    <string name="screenshot_label" msgid="863978141223970162">"نماگرفت"</string>
+    <string name="screenshot_utterance" msgid="1430760563401895074">"گرفتن نماگرفت"</string>
+    <string name="volume_up_label" msgid="8592766918780362870">"افزایش صدا"</string>
+    <string name="volume_down_label" msgid="8574981863656447346">"کاهش صدا"</string>
+    <string name="brightness_up_label" msgid="8010753822854544846">"افزایش روشنایی"</string>
+    <string name="brightness_down_label" msgid="7115662941913272072">"کاهش روشنایی"</string>
+    <string name="previous_button_content_description" msgid="840869171117765966">"رفتن به صفحه قبل"</string>
+    <string name="next_button_content_description" msgid="6810058269847364406">"رفتن به صفحه بعد"</string>
+    <string name="accessibility_menu_description" msgid="4458354794093858297">"«منوی دسترس‌پذیری» منوی بزرگی را روی صفحه برای کنترل دستگاه ارائه می‌دهد. می‌توانید دستگاه را قفل کنید، میزان صدا و روشنایی را کنترل کنید، از صفحه نمایش عکس بگیرید، و کارهای بیشتری انجام دهید."</string>
+    <string name="accessibility_menu_summary" msgid="340071398148208130">"کنترل دستگاه ازطریق منوی بزرگ"</string>
+    <string name="accessibility_menu_settings_name" msgid="1716888058785672611">"تنظیمات منوی دسترس‌پذیری"</string>
+    <string name="accessibility_menu_large_buttons_title" msgid="8978499601044961736">"دکمه‌های بزرگ"</string>
+    <string name="accessibility_menu_large_buttons_summary" msgid="236873938502785311">"افزایش اندازه «دکمه‌های منوی دسترس‌پذیری»"</string>
+    <string name="pref_help_title" msgid="6871558837025010641">"راهنما"</string>
+    <string name="brightness_percentage_label" msgid="7391554573977867369">"روشنایی <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+    <string name="music_volume_percentage_label" msgid="398635599662604706">"بلندی صدای موسیقی <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+</resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-fi/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-fi/strings.xml
new file mode 100644
index 0000000..5e31739
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-fi/strings.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="accessibility_menu_service_name" msgid="730136711554740131">"Saavutettavuusvalikko"</string>
+    <string name="accessibility_menu_intro" msgid="3164193281544042394">"Saavutettavuusvalikko on suuri näyttövalikko, josta voit ohjata laitettasi. Voit esimerkiksi lukita laitteen, säätää äänenvoimakkuutta ja kirkkautta sekä ottaa kuvakaappauksia."</string>
+    <string name="assistant_label" msgid="6796392082252272356">"Assistant"</string>
+    <string name="assistant_utterance" msgid="65509599221141377">"Assistant"</string>
+    <string name="a11y_settings_label" msgid="3977714687248445050">"Saavutettavuusasetukset"</string>
+    <string name="power_label" msgid="7699720321491287839">"Virta"</string>
+    <string name="power_utterance" msgid="7444296686402104807">"Virta-asetukset"</string>
+    <string name="recent_apps_label" msgid="6583276995616385847">"Viimeaikaiset sovellukset"</string>
+    <string name="lockscreen_label" msgid="648347953557887087">"Lukitusnäyttö"</string>
+    <string name="quick_settings_label" msgid="2999117381487601865">"Pika-asetukset"</string>
+    <string name="notifications_label" msgid="6829741046963013567">"Ilmoitukset"</string>
+    <string name="screenshot_label" msgid="863978141223970162">"Kuvakaappaus"</string>
+    <string name="screenshot_utterance" msgid="1430760563401895074">"Ota kuvakaappaus"</string>
+    <string name="volume_up_label" msgid="8592766918780362870">"Lisää äänenvoimakkuutta"</string>
+    <string name="volume_down_label" msgid="8574981863656447346">"Vähennä äänenvoimakkuutta"</string>
+    <string name="brightness_up_label" msgid="8010753822854544846">"Lisää kirkkautta"</string>
+    <string name="brightness_down_label" msgid="7115662941913272072">"Vähennä kirkkautta"</string>
+    <string name="previous_button_content_description" msgid="840869171117765966">"Siirry edelliselle näytöllä"</string>
+    <string name="next_button_content_description" msgid="6810058269847364406">"Siirry seuraavalle näytölle"</string>
+    <string name="accessibility_menu_description" msgid="4458354794093858297">"Saavutettavuusvalikko on suuri näyttövalikko, josta voit ohjata laitettasi. Voit esimerkiksi lukita laitteen, säätää äänenvoimakkuutta ja kirkkautta sekä ottaa kuvakaappauksia."</string>
+    <string name="accessibility_menu_summary" msgid="340071398148208130">"Ohjaa laitetta suurella valikolla"</string>
+    <string name="accessibility_menu_settings_name" msgid="1716888058785672611">"Saavutettavuusvalikon asetukset"</string>
+    <string name="accessibility_menu_large_buttons_title" msgid="8978499601044961736">"Suuret painikkeet"</string>
+    <string name="accessibility_menu_large_buttons_summary" msgid="236873938502785311">"Suurenna saavutettavuusvalikon painikkeita"</string>
+    <string name="pref_help_title" msgid="6871558837025010641">"Ohje"</string>
+    <string name="brightness_percentage_label" msgid="7391554573977867369">"Kirkkaus <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+    <string name="music_volume_percentage_label" msgid="398635599662604706">"Musiikin äänenvoimakkuus <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+</resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-fr-rCA/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-fr-rCA/strings.xml
new file mode 100644
index 0000000..87a9503
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-fr-rCA/strings.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="accessibility_menu_service_name" msgid="730136711554740131">"Menu Accessibilité"</string>
+    <string name="accessibility_menu_intro" msgid="3164193281544042394">"Le menu Accessibilité propose un grand espace à l\'écran à l\'aide duquel vous pouvez contrôler votre appareil. Utilisez-le pour verrouiller votre appareil, régler le volume et la luminosité, prendre des captures d\'écran et plus."</string>
+    <string name="assistant_label" msgid="6796392082252272356">"Assistant"</string>
+    <string name="assistant_utterance" msgid="65509599221141377">"Assistant"</string>
+    <string name="a11y_settings_label" msgid="3977714687248445050">"Paramètres d\'accessibilité"</string>
+    <string name="power_label" msgid="7699720321491287839">"Alimentation"</string>
+    <string name="power_utterance" msgid="7444296686402104807">"Options d\'alimentation"</string>
+    <string name="recent_apps_label" msgid="6583276995616385847">"Applis récentes"</string>
+    <string name="lockscreen_label" msgid="648347953557887087">"Écran de verrouillage"</string>
+    <string name="quick_settings_label" msgid="2999117381487601865">"Paramètres rapides"</string>
+    <string name="notifications_label" msgid="6829741046963013567">"Notifications"</string>
+    <string name="screenshot_label" msgid="863978141223970162">"Capture d\'écran"</string>
+    <string name="screenshot_utterance" msgid="1430760563401895074">"Prendre une capture d\'écran"</string>
+    <string name="volume_up_label" msgid="8592766918780362870">"Augmenter volume"</string>
+    <string name="volume_down_label" msgid="8574981863656447346">"Baisser volume"</string>
+    <string name="brightness_up_label" msgid="8010753822854544846">"Augmenter luminosité"</string>
+    <string name="brightness_down_label" msgid="7115662941913272072">"Baisser luminosité"</string>
+    <string name="previous_button_content_description" msgid="840869171117765966">"Aller à l\'écran précédent"</string>
+    <string name="next_button_content_description" msgid="6810058269847364406">"Aller à l\'écran suivant"</string>
+    <string name="accessibility_menu_description" msgid="4458354794093858297">"Le menu Accessibilité propose un grand espace à l\'écran à l\'aide duquel vous pouvez contrôler votre appareil. Utilisez-le pour verrouiller votre appareil, régler le volume et la luminosité, prendre des captures d\'écran et plus."</string>
+    <string name="accessibility_menu_summary" msgid="340071398148208130">"contrôler l\'appareil à l\'aide d\'un menu de grande taille"</string>
+    <string name="accessibility_menu_settings_name" msgid="1716888058785672611">"Paramètres du menu Accessibilité"</string>
+    <string name="accessibility_menu_large_buttons_title" msgid="8978499601044961736">"Boutons de grande taille"</string>
+    <string name="accessibility_menu_large_buttons_summary" msgid="236873938502785311">"Augmenter la taille des boutons du menu Accessibilité"</string>
+    <string name="pref_help_title" msgid="6871558837025010641">"Aide"</string>
+    <string name="brightness_percentage_label" msgid="7391554573977867369">"Luminosité : <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+    <string name="music_volume_percentage_label" msgid="398635599662604706">"Volume de la musique : <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+</resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-fr/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-fr/strings.xml
new file mode 100644
index 0000000..0445e8d
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-fr/strings.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="accessibility_menu_service_name" msgid="730136711554740131">"Menu Accessibilité"</string>
+    <string name="accessibility_menu_intro" msgid="3164193281544042394">"Le menu Accessibilité s\'affiche en grand sur votre écran pour vous permettre de contrôler votre appareil. Vous pouvez verrouiller votre appareil, ajuster le volume et la luminosité, réaliser des captures d\'écran, et plus encore."</string>
+    <string name="assistant_label" msgid="6796392082252272356">"Assistant"</string>
+    <string name="assistant_utterance" msgid="65509599221141377">"Assistant"</string>
+    <string name="a11y_settings_label" msgid="3977714687248445050">"Accessibilité"</string>
+    <string name="power_label" msgid="7699720321491287839">"Marche/Arrêt"</string>
+    <string name="power_utterance" msgid="7444296686402104807">"Options du bouton Marche/Arrêt"</string>
+    <string name="recent_apps_label" msgid="6583276995616385847">"Applis récentes"</string>
+    <string name="lockscreen_label" msgid="648347953557887087">"Écran de verrouillage"</string>
+    <string name="quick_settings_label" msgid="2999117381487601865">"Réglages rapides"</string>
+    <string name="notifications_label" msgid="6829741046963013567">"Notifications"</string>
+    <string name="screenshot_label" msgid="863978141223970162">"Capture d\'écran"</string>
+    <string name="screenshot_utterance" msgid="1430760563401895074">"Prendre une capture d\'écran"</string>
+    <string name="volume_up_label" msgid="8592766918780362870">"Augmenter volume"</string>
+    <string name="volume_down_label" msgid="8574981863656447346">"Baisser volume"</string>
+    <string name="brightness_up_label" msgid="8010753822854544846">"Augmenter luminosité"</string>
+    <string name="brightness_down_label" msgid="7115662941913272072">"Baisser luminosité"</string>
+    <string name="previous_button_content_description" msgid="840869171117765966">"Revenir à l\'écran précédent"</string>
+    <string name="next_button_content_description" msgid="6810058269847364406">"Accéder à l\'écran suivant"</string>
+    <string name="accessibility_menu_description" msgid="4458354794093858297">"Le menu Accessibilité s\'affiche en grand sur votre écran pour vous permettre de contrôler votre appareil. Vous pouvez verrouiller votre appareil, ajuster le volume et la luminosité, réaliser des captures d\'écran, et plus encore."</string>
+    <string name="accessibility_menu_summary" msgid="340071398148208130">"Contrôler l\'appareil via un grand menu"</string>
+    <string name="accessibility_menu_settings_name" msgid="1716888058785672611">"Paramètres du menu d\'accessibilité"</string>
+    <string name="accessibility_menu_large_buttons_title" msgid="8978499601044961736">"Boutons de grande taille"</string>
+    <string name="accessibility_menu_large_buttons_summary" msgid="236873938502785311">"Augmenter la taille des boutons du menu d\'accessibilité"</string>
+    <string name="pref_help_title" msgid="6871558837025010641">"Aide"</string>
+    <string name="brightness_percentage_label" msgid="7391554573977867369">"Luminosité : <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+    <string name="music_volume_percentage_label" msgid="398635599662604706">"Volume de la musique : <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+</resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-gl/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-gl/strings.xml
new file mode 100644
index 0000000..5547d63
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-gl/strings.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="accessibility_menu_service_name" msgid="730136711554740131">"Menú de accesibilidade"</string>
+    <string name="accessibility_menu_intro" msgid="3164193281544042394">"O menú de accesibilidade é un panel grande que aparece na pantalla co que podes controlar o dispositivo. Permíteche realizar varias accións, como bloquear o dispositivo, controlar o volume, axustar o brillo e facer capturas de pantalla."</string>
+    <string name="assistant_label" msgid="6796392082252272356">"Asistente"</string>
+    <string name="assistant_utterance" msgid="65509599221141377">"Asistente"</string>
+    <string name="a11y_settings_label" msgid="3977714687248445050">"Accesibilidade (configuración)"</string>
+    <string name="power_label" msgid="7699720321491287839">"Acender"</string>
+    <string name="power_utterance" msgid="7444296686402104807">"Opcións de acendido"</string>
+    <string name="recent_apps_label" msgid="6583276995616385847">"Aplicacións recentes"</string>
+    <string name="lockscreen_label" msgid="648347953557887087">"Pantalla de bloqueo"</string>
+    <string name="quick_settings_label" msgid="2999117381487601865">"Configuración rápida"</string>
+    <string name="notifications_label" msgid="6829741046963013567">"Notificacións"</string>
+    <string name="screenshot_label" msgid="863978141223970162">"Captura de pantalla"</string>
+    <string name="screenshot_utterance" msgid="1430760563401895074">"Fai unha captura de pantalla"</string>
+    <string name="volume_up_label" msgid="8592766918780362870">"Subir volume"</string>
+    <string name="volume_down_label" msgid="8574981863656447346">"Baixar volume"</string>
+    <string name="brightness_up_label" msgid="8010753822854544846">"Aumentar brillo"</string>
+    <string name="brightness_down_label" msgid="7115662941913272072">"Reducir brillo"</string>
+    <string name="previous_button_content_description" msgid="840869171117765966">"Ir á pantalla anterior"</string>
+    <string name="next_button_content_description" msgid="6810058269847364406">"Ir á seguinte pantalla"</string>
+    <string name="accessibility_menu_description" msgid="4458354794093858297">"O menú de accesibilidade é un panel grande que aparece na pantalla e permite controlar o dispositivo. Permíteche realizar varias accións, como bloquear o dispositivo, controlar o volume, axustar o brillo e facer capturas de pantalla."</string>
+    <string name="accessibility_menu_summary" msgid="340071398148208130">"Controla o dispositivo a través dun menú grande"</string>
+    <string name="accessibility_menu_settings_name" msgid="1716888058785672611">"Axustes do menú de accesibilidade"</string>
+    <string name="accessibility_menu_large_buttons_title" msgid="8978499601044961736">"Botóns grandes"</string>
+    <string name="accessibility_menu_large_buttons_summary" msgid="236873938502785311">"Aumenta o tamaño dos botóns do menú de accesibilidade"</string>
+    <string name="pref_help_title" msgid="6871558837025010641">"Axuda"</string>
+    <string name="brightness_percentage_label" msgid="7391554573977867369">"Brillo: <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+    <string name="music_volume_percentage_label" msgid="398635599662604706">"Volume da música: <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+</resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-gu/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-gu/strings.xml
new file mode 100644
index 0000000..5e0bec5
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-gu/strings.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="accessibility_menu_service_name" msgid="730136711554740131">"ઍક્સેસિબિલિટી મેનૂ"</string>
+    <string name="accessibility_menu_intro" msgid="3164193281544042394">"ઍક્સેસિબિલિટી મેનૂ તમારા ડિવાઇસને નિયંત્રિત કરવા માટે મોટું ઑન-સ્ક્રીન મેનૂ પૂરું પાડે છે. તમે તમારા ડિવાઇસને લૉક કરી શકો છો, વૉલ્યૂમ અને બ્રાઇટનેસ નિયંત્રિત કરી શકો છો, સ્ક્રીનશૉટ લઈ શકો છો અને બીજું ઘણું બધું કરી શકો છો."</string>
+    <string name="assistant_label" msgid="6796392082252272356">"Assistant"</string>
+    <string name="assistant_utterance" msgid="65509599221141377">"Assistant"</string>
+    <string name="a11y_settings_label" msgid="3977714687248445050">"ઍક્સેસિબિલિટી સેટિંગ"</string>
+    <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="quick_settings_label" msgid="2999117381487601865">"ઝડપી સેટિંગ"</string>
+    <string name="notifications_label" msgid="6829741046963013567">"નોટિફિકેશન"</string>
+    <string name="screenshot_label" msgid="863978141223970162">"સ્ક્રીનશૉટ"</string>
+    <string name="screenshot_utterance" msgid="1430760563401895074">"સ્ક્રીનશૉટ લો"</string>
+    <string name="volume_up_label" msgid="8592766918780362870">"વૉલ્યૂમ વધારો"</string>
+    <string name="volume_down_label" msgid="8574981863656447346">"વૉલ્યૂમ ઘટાડો"</string>
+    <string name="brightness_up_label" msgid="8010753822854544846">"બ્રાઇટનેસ વધારો"</string>
+    <string name="brightness_down_label" msgid="7115662941913272072">"બ્રાઇટનેસ ઘટાડો"</string>
+    <string name="previous_button_content_description" msgid="840869171117765966">"પાછલી સ્ક્રીન પર જાઓ"</string>
+    <string name="next_button_content_description" msgid="6810058269847364406">"આગલી સ્ક્રીન પર જાઓ"</string>
+    <string name="accessibility_menu_description" msgid="4458354794093858297">"ઍક્સેસિબિલિટી મેનૂ તમારા ડિવાઇસને નિયંત્રિત કરવા માટે મોટું ઑન-સ્ક્રીન મેનૂ પૂરું પાડે છે. તમે તમારા ડિવાઇસને લૉક કરી શકો છો, વૉલ્યૂમ અને બ્રાઇટનેસ નિયંત્રિત કરી શકો છો, સ્ક્રીનશૉટ લઈ શકો છો અને બીજું ઘણું બધું કરી શકો છો."</string>
+    <string name="accessibility_menu_summary" msgid="340071398148208130">"મોટા મેનૂ મારફતે ડિવાઇસને નિયંત્રિત કરો"</string>
+    <string name="accessibility_menu_settings_name" msgid="1716888058785672611">"ઍક્સેસિબિલિટી મેનૂ સેટિંગ"</string>
+    <string name="accessibility_menu_large_buttons_title" msgid="8978499601044961736">"મોટા બટન"</string>
+    <string name="accessibility_menu_large_buttons_summary" msgid="236873938502785311">"ઍક્સેસિબિલિટી મેનૂ બટનનું કદ વધારો"</string>
+    <string name="pref_help_title" msgid="6871558837025010641">"સહાય"</string>
+    <string name="brightness_percentage_label" msgid="7391554573977867369">"બ્રાઇટનેસ <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+    <string name="music_volume_percentage_label" msgid="398635599662604706">"મ્યુઝિકનું વૉલ્યૂમ <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+</resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-hi/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-hi/strings.xml
new file mode 100644
index 0000000..1cb9b5e
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-hi/strings.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="accessibility_menu_service_name" msgid="730136711554740131">"सुलभता मेन्यू"</string>
+    <string name="accessibility_menu_intro" msgid="3164193281544042394">"सुलभता मेन्यू, स्क्रीन पर दिखने वाला एक बड़ा मेन्यू होता है. इसकी मदद से, अपने डिवाइस को कंट्रोल किया जा सकता है. इस मेन्यू में जाकर, अपना डिवाइस लॉक करने, स्क्रीनशॉट लेने, स्क्रीन की रोशनी और आवाज़ कंट्रोल करने जैसे कई दूसरे काम किए जा सकते हैं."</string>
+    <string name="assistant_label" msgid="6796392082252272356">"Assistant"</string>
+    <string name="assistant_utterance" msgid="65509599221141377">"Assistant"</string>
+    <string name="a11y_settings_label" msgid="3977714687248445050">"सुलभता सेटिंग"</string>
+    <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="quick_settings_label" msgid="2999117381487601865">"क्विक सेटिंग"</string>
+    <string name="notifications_label" msgid="6829741046963013567">"सूचनाएं"</string>
+    <string name="screenshot_label" msgid="863978141223970162">"स्क्रीनशॉट"</string>
+    <string name="screenshot_utterance" msgid="1430760563401895074">"स्क्रीनशॉट लें"</string>
+    <string name="volume_up_label" msgid="8592766918780362870">"आवाज़ बढ़ाएं"</string>
+    <string name="volume_down_label" msgid="8574981863656447346">"आवाज़ कम करें"</string>
+    <string name="brightness_up_label" msgid="8010753822854544846">"स्क्रीन की रोशनी बढ़ाएं"</string>
+    <string name="brightness_down_label" msgid="7115662941913272072">"स्क्रीन की रोशनी कम करें"</string>
+    <string name="previous_button_content_description" msgid="840869171117765966">"पिछली स्क्रीन पर जाएं"</string>
+    <string name="next_button_content_description" msgid="6810058269847364406">"अगली स्क्रीन पर जाएं"</string>
+    <string name="accessibility_menu_description" msgid="4458354794093858297">"सुलभता मेन्यू, स्क्रीन पर दिखने वाला एक बड़ा मेन्यू होता है. इसकी मदद से, अपने डिवाइस को कंट्रोल किया जा सकता है. इस मेन्यू में जाकर, अपना डिवाइस लॉक करने, स्क्रीनशॉट लेने, स्क्रीन की रोशनी और आवाज़ कंट्रोल करने जैसे कई दूसरे काम किए जा सकते हैं."</string>
+    <string name="accessibility_menu_summary" msgid="340071398148208130">"बड़े मेन्यू की मदद से डिवाइस को कंट्रोल करें"</string>
+    <string name="accessibility_menu_settings_name" msgid="1716888058785672611">"सुलभता मेन्यू सेटिंग"</string>
+    <string name="accessibility_menu_large_buttons_title" msgid="8978499601044961736">"बड़े बटन"</string>
+    <string name="accessibility_menu_large_buttons_summary" msgid="236873938502785311">"सुलभता मेन्यू के बटनाें का साइज़ बढ़ाएं"</string>
+    <string name="pref_help_title" msgid="6871558837025010641">"सहायता"</string>
+    <string name="brightness_percentage_label" msgid="7391554573977867369">"स्क्रीन की रोशनी <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+    <string name="music_volume_percentage_label" msgid="398635599662604706">"संगीत की आवाज़ <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+</resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-hr/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-hr/strings.xml
new file mode 100644
index 0000000..eff6cb4
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-hr/strings.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="accessibility_menu_service_name" msgid="730136711554740131">"Izbornik pristupačnosti"</string>
+    <string name="accessibility_menu_intro" msgid="3164193281544042394">"Izbornik pristupačnosti veliki je zaslonski izbornik koji vam omogućuje upravljanje uređajem. Putem ovog izbornika možete zaključati uređaj, upravljati glasnoćom i svjetlinom, izrađivati snimke zaslona i drugo."</string>
+    <string name="assistant_label" msgid="6796392082252272356">"Asistent"</string>
+    <string name="assistant_utterance" msgid="65509599221141377">"Asistent"</string>
+    <string name="a11y_settings_label" msgid="3977714687248445050">"Postavke pristupačnosti"</string>
+    <string name="power_label" msgid="7699720321491287839">"Napajanje"</string>
+    <string name="power_utterance" msgid="7444296686402104807">"Opcije napajanja"</string>
+    <string name="recent_apps_label" msgid="6583276995616385847">"Nedavne aplikacije"</string>
+    <string name="lockscreen_label" msgid="648347953557887087">"Zaključani zaslon"</string>
+    <string name="quick_settings_label" msgid="2999117381487601865">"Brze postavke"</string>
+    <string name="notifications_label" msgid="6829741046963013567">"Obavijesti"</string>
+    <string name="screenshot_label" msgid="863978141223970162">"Snimka zaslona"</string>
+    <string name="screenshot_utterance" msgid="1430760563401895074">"Snimi zaslon"</string>
+    <string name="volume_up_label" msgid="8592766918780362870">"Pojačaj"</string>
+    <string name="volume_down_label" msgid="8574981863656447346">"Stišaj"</string>
+    <string name="brightness_up_label" msgid="8010753822854544846">"Pojačaj svjetlinu"</string>
+    <string name="brightness_down_label" msgid="7115662941913272072">"Smanji svjetlinu"</string>
+    <string name="previous_button_content_description" msgid="840869171117765966">"Idi na prethodni zaslon"</string>
+    <string name="next_button_content_description" msgid="6810058269847364406">"Idi na sljedeći zaslon"</string>
+    <string name="accessibility_menu_description" msgid="4458354794093858297">"Izbornik pristupačnosti pruža velik izbornik na zaslonu u svrhu upravljanja uređajem. Možete zaključati uređaj, upravljati glasnoćom i svjetlinom, izrađivati snimke zaslona i drugo."</string>
+    <string name="accessibility_menu_summary" msgid="340071398148208130">"Upravljanje uređajem pomoću velikog izbornika"</string>
+    <string name="accessibility_menu_settings_name" msgid="1716888058785672611">"Postavke izbornika pristupačnosti"</string>
+    <string name="accessibility_menu_large_buttons_title" msgid="8978499601044961736">"Veliki gumbi"</string>
+    <string name="accessibility_menu_large_buttons_summary" msgid="236873938502785311">"Povećanje veličine gumba izbornika Pristupačnosti"</string>
+    <string name="pref_help_title" msgid="6871558837025010641">"Pomoć"</string>
+    <string name="brightness_percentage_label" msgid="7391554573977867369">"Svjetlina <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+    <string name="music_volume_percentage_label" msgid="398635599662604706">"Glasnoća glazbe <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+</resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-hu/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-hu/strings.xml
new file mode 100644
index 0000000..978deaa
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-hu/strings.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="accessibility_menu_service_name" msgid="730136711554740131">"Kisegítő lehetőségek menüje"</string>
+    <string name="accessibility_menu_intro" msgid="3164193281544042394">"A Kisegítő lehetőségek menüje az eszköz vezérlésére szolgáló nagyméretű, képernyőn megjelenő menü. Lezárhatja vele az eszközt, szabályozhatja a hang- és a fényerőt, képernyőképeket készíthet, és egyebekre is használhatja."</string>
+    <string name="assistant_label" msgid="6796392082252272356">"Segéd"</string>
+    <string name="assistant_utterance" msgid="65509599221141377">"Segéd"</string>
+    <string name="a11y_settings_label" msgid="3977714687248445050">"Kisegítő lehetőségek beállításai"</string>
+    <string name="power_label" msgid="7699720321491287839">"Bekapcsológomb"</string>
+    <string name="power_utterance" msgid="7444296686402104807">"Bekapcsológomb beállításai"</string>
+    <string name="recent_apps_label" msgid="6583276995616385847">"Legutóbbi alkalmazások"</string>
+    <string name="lockscreen_label" msgid="648347953557887087">"Képernyő lezárása"</string>
+    <string name="quick_settings_label" msgid="2999117381487601865">"Gyorsbeállítások"</string>
+    <string name="notifications_label" msgid="6829741046963013567">"Értesítések"</string>
+    <string name="screenshot_label" msgid="863978141223970162">"Képernyőkép"</string>
+    <string name="screenshot_utterance" msgid="1430760563401895074">"Képernyőkép készítése"</string>
+    <string name="volume_up_label" msgid="8592766918780362870">"Hangerő növelése"</string>
+    <string name="volume_down_label" msgid="8574981863656447346">"Hangerő csökkentése"</string>
+    <string name="brightness_up_label" msgid="8010753822854544846">"Fényerő növelése"</string>
+    <string name="brightness_down_label" msgid="7115662941913272072">"Fényerő csökkentése"</string>
+    <string name="previous_button_content_description" msgid="840869171117765966">"Ugrás az előző képernyőre"</string>
+    <string name="next_button_content_description" msgid="6810058269847364406">"Ugrás a következő képernyőre"</string>
+    <string name="accessibility_menu_description" msgid="4458354794093858297">"A Kisegítő lehetőségek menüje az eszköz vezérlésére szolgáló nagyméretű, képernyőn megjelenő menü. Lezárhatja az eszközt, szabályozhatja a hang- és a fényerőt, képernyőképeket készíthet, és egyebekre is használhatja a funkciót."</string>
+    <string name="accessibility_menu_summary" msgid="340071398148208130">"Nagyméretű menün keresztül vezérelheti eszközét"</string>
+    <string name="accessibility_menu_settings_name" msgid="1716888058785672611">"A kisegítő lehetőségek menü beállításai"</string>
+    <string name="accessibility_menu_large_buttons_title" msgid="8978499601044961736">"Nagy gombok"</string>
+    <string name="accessibility_menu_large_buttons_summary" msgid="236873938502785311">"A Kisegítő lehetőségek menüben található gombok méretének növelése"</string>
+    <string name="pref_help_title" msgid="6871558837025010641">"Súgó"</string>
+    <string name="brightness_percentage_label" msgid="7391554573977867369">"Fényerő: <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+    <string name="music_volume_percentage_label" msgid="398635599662604706">"Zene hangereje: <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+</resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-hy/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-hy/strings.xml
new file mode 100644
index 0000000..e06787f
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-hy/strings.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="accessibility_menu_service_name" msgid="730136711554740131">"Հատուկ գործառույթների ընտրացանկ"</string>
+    <string name="accessibility_menu_intro" msgid="3164193281544042394">"Հատուկ գործառույթների մեծ ընտրացանկը նախատեսված է ձեր սարքը կառավարելու համար։ Դուք կարող եք կողպել ձեր հեռախոսը, կարգավորել պայծառությունը և ձայնի ուժգնությունը, սքրինշոթներ անել և այլն։"</string>
+    <string name="assistant_label" msgid="6796392082252272356">"Օգնական"</string>
+    <string name="assistant_utterance" msgid="65509599221141377">"Օգնական"</string>
+    <string name="a11y_settings_label" msgid="3977714687248445050">"Հատուկ գործառույթների կարգավորումներ"</string>
+    <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="quick_settings_label" msgid="2999117381487601865">"Արագ\\nկարգավորումներ"</string>
+    <string name="notifications_label" msgid="6829741046963013567">"Ծանուցումներ"</string>
+    <string name="screenshot_label" msgid="863978141223970162">"Սքրինշոթ"</string>
+    <string name="screenshot_utterance" msgid="1430760563401895074">"Ստանալ սքրինշոթը"</string>
+    <string name="volume_up_label" msgid="8592766918780362870">"Բարձրացնել ձայնը"</string>
+    <string name="volume_down_label" msgid="8574981863656447346">"Իջեցնել ձայնը"</string>
+    <string name="brightness_up_label" msgid="8010753822854544846">"Ավելացնել պայծառությունը"</string>
+    <string name="brightness_down_label" msgid="7115662941913272072">"Նվազեցնել պայծառությունը"</string>
+    <string name="previous_button_content_description" msgid="840869171117765966">"Անցնել նախորդ էկրան"</string>
+    <string name="next_button_content_description" msgid="6810058269847364406">"Անցնել հաջորդ էկրան"</string>
+    <string name="accessibility_menu_description" msgid="4458354794093858297">"Հատուկ գործառույթների մեծ ընտրացանկը նախատեսված է ձեր սարքը կառավարելու համար: Դուք կարող եք կողպել ձեր հեռախոսը, կարգավորել պայծառությունը և ձայնի ուժգնությունը, սքրինշոթներ անել և այլն։"</string>
+    <string name="accessibility_menu_summary" msgid="340071398148208130">"Կառավարել սարքը մեծ ընտրացանկի միջոցով"</string>
+    <string name="accessibility_menu_settings_name" msgid="1716888058785672611">"Հատուկ գործառույթների ընտրացանկի կարգավորումներ"</string>
+    <string name="accessibility_menu_large_buttons_title" msgid="8978499601044961736">"Մեծ կոճակներ"</string>
+    <string name="accessibility_menu_large_buttons_summary" msgid="236873938502785311">"Մեծացնել «Հատուկ գործառույթների» ընտրացանկի կոճակները"</string>
+    <string name="pref_help_title" msgid="6871558837025010641">"Օգնություն"</string>
+    <string name="brightness_percentage_label" msgid="7391554573977867369">"Պայծառությունը՝ <xliff:g id="PERCENTAGE">%1$s</xliff:g>%%"</string>
+    <string name="music_volume_percentage_label" msgid="398635599662604706">"Երաժշտության ձայնի ուժգնությունը՝ <xliff:g id="PERCENTAGE">%1$s</xliff:g>%%"</string>
+</resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-in/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-in/strings.xml
new file mode 100644
index 0000000..5bb5e40
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-in/strings.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="accessibility_menu_service_name" msgid="730136711554740131">"Menu Aksesibilitas"</string>
+    <string name="accessibility_menu_intro" msgid="3164193281544042394">"Menu Aksesibilitas menyediakan menu di layar dengan ukuran besar untuk mengontrol perangkat Anda. Anda dapat mengunci perangkat, mengontrol volume dan kecerahan, mengambil screenshot, dan banyak lagi."</string>
+    <string name="assistant_label" msgid="6796392082252272356">"Asisten"</string>
+    <string name="assistant_utterance" msgid="65509599221141377">"Asisten"</string>
+    <string name="a11y_settings_label" msgid="3977714687248445050">"Setelan Aksesibilitas"</string>
+    <string name="power_label" msgid="7699720321491287839">"Power"</string>
+    <string name="power_utterance" msgid="7444296686402104807">"Opsi power"</string>
+    <string name="recent_apps_label" msgid="6583276995616385847">"Aplikasi terbaru"</string>
+    <string name="lockscreen_label" msgid="648347953557887087">"Layar kunci"</string>
+    <string name="quick_settings_label" msgid="2999117381487601865">"Setelan Cepat"</string>
+    <string name="notifications_label" msgid="6829741046963013567">"Notifikasi"</string>
+    <string name="screenshot_label" msgid="863978141223970162">"Screenshot"</string>
+    <string name="screenshot_utterance" msgid="1430760563401895074">"Ambil screenshot"</string>
+    <string name="volume_up_label" msgid="8592766918780362870">"Naikkan volume"</string>
+    <string name="volume_down_label" msgid="8574981863656447346">"Turunkan volume"</string>
+    <string name="brightness_up_label" msgid="8010753822854544846">"Naikkan kecerahan"</string>
+    <string name="brightness_down_label" msgid="7115662941913272072">"Turunkan kecerahan"</string>
+    <string name="previous_button_content_description" msgid="840869171117765966">"Buka layar sebelumnya"</string>
+    <string name="next_button_content_description" msgid="6810058269847364406">"Buka layar berikutnya"</string>
+    <string name="accessibility_menu_description" msgid="4458354794093858297">"Menu Aksesibilitas menyediakan menu di layar dengan ukuran besar untuk mengontrol perangkat Anda. Anda dapat mengunci perangkat, mengontrol volume dan kecerahan, mengambil screenshot, dan banyak lagi."</string>
+    <string name="accessibility_menu_summary" msgid="340071398148208130">"Kontrol perangkat melalui menu berukuran besar"</string>
+    <string name="accessibility_menu_settings_name" msgid="1716888058785672611">"Setelan Menu Aksesibilitas"</string>
+    <string name="accessibility_menu_large_buttons_title" msgid="8978499601044961736">"Tombol besar"</string>
+    <string name="accessibility_menu_large_buttons_summary" msgid="236873938502785311">"Perbesar ukuran Tombol Menu Aksesibilitas"</string>
+    <string name="pref_help_title" msgid="6871558837025010641">"Bantuan"</string>
+    <string name="brightness_percentage_label" msgid="7391554573977867369">"Kecerahan <xliff:g id="PERCENTAGE">%1$s</xliff:g>%%"</string>
+    <string name="music_volume_percentage_label" msgid="398635599662604706">"Volume musik <xliff:g id="PERCENTAGE">%1$s</xliff:g>%%"</string>
+</resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-is/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-is/strings.xml
new file mode 100644
index 0000000..71047ed
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-is/strings.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="accessibility_menu_service_name" msgid="730136711554740131">"Aðgengisvalmynd"</string>
+    <string name="accessibility_menu_intro" msgid="3164193281544042394">"Aðgengisvalmyndin er stór valmynd sem birtist á skjánum sem má nota til að stjórna tækinu. Þú getur læst tækinu, stjórnað hljóðstyrk og birtustigi, tekið skjámyndir og fleira."</string>
+    <string name="assistant_label" msgid="6796392082252272356">"Hjálpari"</string>
+    <string name="assistant_utterance" msgid="65509599221141377">"Hjálpari"</string>
+    <string name="a11y_settings_label" msgid="3977714687248445050">"Aðgengisstillingar"</string>
+    <string name="power_label" msgid="7699720321491287839">"Orka"</string>
+    <string name="power_utterance" msgid="7444296686402104807">"Orkuvalkostir"</string>
+    <string name="recent_apps_label" msgid="6583276995616385847">"Nýleg forrit"</string>
+    <string name="lockscreen_label" msgid="648347953557887087">"Lásskjár"</string>
+    <string name="quick_settings_label" msgid="2999117381487601865">"Flýtistillingar"</string>
+    <string name="notifications_label" msgid="6829741046963013567">"Tilkynningar"</string>
+    <string name="screenshot_label" msgid="863978141223970162">"Skjámynd"</string>
+    <string name="screenshot_utterance" msgid="1430760563401895074">"Taka skjámynd"</string>
+    <string name="volume_up_label" msgid="8592766918780362870">"Hækka hljóð"</string>
+    <string name="volume_down_label" msgid="8574981863656447346">"Lækka hljóð"</string>
+    <string name="brightness_up_label" msgid="8010753822854544846">"Auka birtustig"</string>
+    <string name="brightness_down_label" msgid="7115662941913272072">"Minnka birtustig"</string>
+    <string name="previous_button_content_description" msgid="840869171117765966">"Fara á fyrri skjá"</string>
+    <string name="next_button_content_description" msgid="6810058269847364406">"Fara á næsta skjá"</string>
+    <string name="accessibility_menu_description" msgid="4458354794093858297">"Aðgengisvalmyndin er stór valmynd sem birtist á skjánum sem má nota til að stjórna tækinu. Þú getur læst tækinu, stjórnað hljóðstyrk og birtustigi, tekið skjámyndir og fleira."</string>
+    <string name="accessibility_menu_summary" msgid="340071398148208130">"Stjórna tæki í gegnum stóra valmynd"</string>
+    <string name="accessibility_menu_settings_name" msgid="1716888058785672611">"Aðgengisvalmyndarstillingar"</string>
+    <string name="accessibility_menu_large_buttons_title" msgid="8978499601044961736">"Stórir hnappar"</string>
+    <string name="accessibility_menu_large_buttons_summary" msgid="236873938502785311">"Stækka hnappa aðgengisvalmyndar"</string>
+    <string name="pref_help_title" msgid="6871558837025010641">"Hjálp"</string>
+    <string name="brightness_percentage_label" msgid="7391554573977867369">"Birtustig <xliff:g id="PERCENTAGE">%1$s</xliff:g>%%"</string>
+    <string name="music_volume_percentage_label" msgid="398635599662604706">"Hljóðstyrkur tónlistar <xliff:g id="PERCENTAGE">%1$s</xliff:g>%%"</string>
+</resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-it/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-it/strings.xml
new file mode 100644
index 0000000..147a1d3
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-it/strings.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="accessibility_menu_service_name" msgid="730136711554740131">"Menu Accessibilità"</string>
+    <string name="accessibility_menu_intro" msgid="3164193281544042394">"Il menu Accessibilità mostra sullo schermo un menu di grandi dimensioni per permetterti di controllare il dispositivo. Puoi bloccare il dispositivo, regolare il volume e la luminosità, acquisire screenshot e altro ancora."</string>
+    <string name="assistant_label" msgid="6796392082252272356">"Assistente"</string>
+    <string name="assistant_utterance" msgid="65509599221141377">"Assistente"</string>
+    <string name="a11y_settings_label" msgid="3977714687248445050">"Impostazioni di accessibilità"</string>
+    <string name="power_label" msgid="7699720321491287839">"Accensione"</string>
+    <string name="power_utterance" msgid="7444296686402104807">"Opzioni di accensione"</string>
+    <string name="recent_apps_label" msgid="6583276995616385847">"App recenti"</string>
+    <string name="lockscreen_label" msgid="648347953557887087">"Schermata di blocco"</string>
+    <string name="quick_settings_label" msgid="2999117381487601865">"Impostazioni rapide"</string>
+    <string name="notifications_label" msgid="6829741046963013567">"Notifiche"</string>
+    <string name="screenshot_label" msgid="863978141223970162">"Screenshot"</string>
+    <string name="screenshot_utterance" msgid="1430760563401895074">"Acquisisci screenshot"</string>
+    <string name="volume_up_label" msgid="8592766918780362870">"Alza il volume"</string>
+    <string name="volume_down_label" msgid="8574981863656447346">"Abbassa il volume"</string>
+    <string name="brightness_up_label" msgid="8010753822854544846">"Aumenta luminosità"</string>
+    <string name="brightness_down_label" msgid="7115662941913272072">"Diminuisci luminosità"</string>
+    <string name="previous_button_content_description" msgid="840869171117765966">"Vai alla schermata precedente"</string>
+    <string name="next_button_content_description" msgid="6810058269847364406">"Vai alla schermata successiva"</string>
+    <string name="accessibility_menu_description" msgid="4458354794093858297">"Il menu Accessibilità mostra sullo schermo un menu di grandi dimensioni per permetterti di controllare il dispositivo. Puoi bloccare il dispositivo, regolare il volume e la luminosità, acquisire screenshot e altro ancora."</string>
+    <string name="accessibility_menu_summary" msgid="340071398148208130">"Controlla il dispositivo tramite un menu di grandi dimensioni"</string>
+    <string name="accessibility_menu_settings_name" msgid="1716888058785672611">"Impostazioni del menu Accessibilità"</string>
+    <string name="accessibility_menu_large_buttons_title" msgid="8978499601044961736">"Pulsanti grandi"</string>
+    <string name="accessibility_menu_large_buttons_summary" msgid="236873938502785311">"Aumenta le dimensioni dei pulsanti del menu Accessibilità"</string>
+    <string name="pref_help_title" msgid="6871558837025010641">"Guida"</string>
+    <string name="brightness_percentage_label" msgid="7391554573977867369">"Luminosità: <xliff:g id="PERCENTAGE">%1$s</xliff:g>%%"</string>
+    <string name="music_volume_percentage_label" msgid="398635599662604706">"Volume musica: <xliff:g id="PERCENTAGE">%1$s</xliff:g>%%"</string>
+</resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-iw/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-iw/strings.xml
new file mode 100644
index 0000000..7072b34
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-iw/strings.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="accessibility_menu_service_name" msgid="730136711554740131">"תפריט נגישות"</string>
+    <string name="accessibility_menu_intro" msgid="3164193281544042394">"תפריט הנגישות הוא תפריט גדול שמופיע במסך ומאפשר לשלוט במכשיר. אפשר לנעול את המכשיר, לשלוט בעוצמת הקול ובבהירות, לצלם צילומי מסך ועוד."</string>
+    <string name="assistant_label" msgid="6796392082252272356">"Assistant"</string>
+    <string name="assistant_utterance" msgid="65509599221141377">"Assistant"</string>
+    <string name="a11y_settings_label" msgid="3977714687248445050">"הגדרות נגישות"</string>
+    <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="quick_settings_label" msgid="2999117381487601865">"הגדרות מהירות"</string>
+    <string name="notifications_label" msgid="6829741046963013567">"התראות"</string>
+    <string name="screenshot_label" msgid="863978141223970162">"צילום מסך"</string>
+    <string name="screenshot_utterance" msgid="1430760563401895074">"שמירת צילום של המסך"</string>
+    <string name="volume_up_label" msgid="8592766918780362870">"הגברת עוצמת הקול"</string>
+    <string name="volume_down_label" msgid="8574981863656447346">"החלשת עוצמת הקול"</string>
+    <string name="brightness_up_label" msgid="8010753822854544846">"הגברת הבהירות"</string>
+    <string name="brightness_down_label" msgid="7115662941913272072">"הפחתת הבהירות"</string>
+    <string name="previous_button_content_description" msgid="840869171117765966">"מעבר למסך הקודם"</string>
+    <string name="next_button_content_description" msgid="6810058269847364406">"מעבר למסך הבא"</string>
+    <string name="accessibility_menu_description" msgid="4458354794093858297">"תפריט הנגישות הוא תפריט גדול שמופיע במסך ומאפשר לשלוט במכשיר. אפשר לנעול את המכשיר, לשלוט בעוצמת הקול ובבהירות, לצלם צילומי מסך ועוד."</string>
+    <string name="accessibility_menu_summary" msgid="340071398148208130">"שליטה במכשיר באמצעות התפריט הגדול"</string>
+    <string name="accessibility_menu_settings_name" msgid="1716888058785672611">"הגדרות של תפריט נגישות"</string>
+    <string name="accessibility_menu_large_buttons_title" msgid="8978499601044961736">"לחצנים גדולים"</string>
+    <string name="accessibility_menu_large_buttons_summary" msgid="236873938502785311">"הגדלת הלחצנים של תפריט הנגישות"</string>
+    <string name="pref_help_title" msgid="6871558837025010641">"עזרה"</string>
+    <string name="brightness_percentage_label" msgid="7391554573977867369">"בהירות  %% <xliff:g id="PERCENTAGE">%1$s</xliff:g>"</string>
+    <string name="music_volume_percentage_label" msgid="398635599662604706">"עוצמת הקול של המוזיקה %% <xliff:g id="PERCENTAGE">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-ja/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-ja/strings.xml
new file mode 100644
index 0000000..cc6638b
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-ja/strings.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="accessibility_menu_service_name" msgid="730136711554740131">"ユーザー補助機能メニュー"</string>
+    <string name="accessibility_menu_intro" msgid="3164193281544042394">"ユーザー補助メニューは、デバイスを操作するために画面上に大きく表示されるメニューです。デバイスのロック、音量や明るさの調節、スクリーンショットの撮影などを行えます。"</string>
+    <string name="assistant_label" msgid="6796392082252272356">"アシスタント"</string>
+    <string name="assistant_utterance" msgid="65509599221141377">"アシスタント"</string>
+    <string name="a11y_settings_label" msgid="3977714687248445050">"ユーザー補助機能の設定"</string>
+    <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="quick_settings_label" msgid="2999117381487601865">"クイック設定"</string>
+    <string name="notifications_label" msgid="6829741046963013567">"通知"</string>
+    <string name="screenshot_label" msgid="863978141223970162">"スクリーンショット"</string>
+    <string name="screenshot_utterance" msgid="1430760563401895074">"スクリーンショットを撮る"</string>
+    <string name="volume_up_label" msgid="8592766918780362870">"音量を上げる"</string>
+    <string name="volume_down_label" msgid="8574981863656447346">"音量を下げる"</string>
+    <string name="brightness_up_label" msgid="8010753822854544846">"明るさを上げる"</string>
+    <string name="brightness_down_label" msgid="7115662941913272072">"明るさを下げる"</string>
+    <string name="previous_button_content_description" msgid="840869171117765966">"前の画面に移動"</string>
+    <string name="next_button_content_description" msgid="6810058269847364406">"次の画面に移動"</string>
+    <string name="accessibility_menu_description" msgid="4458354794093858297">"ユーザー補助メニューは、デバイスを操作するために画面上に大きく表示されるメニューです。デバイスのロック、音量や明るさの調節、スクリーンショットの撮影などを行えます。"</string>
+    <string name="accessibility_menu_summary" msgid="340071398148208130">"大きく表示されるメニューでデバイスを操作します"</string>
+    <string name="accessibility_menu_settings_name" msgid="1716888058785672611">"ユーザー補助機能メニューの設定"</string>
+    <string name="accessibility_menu_large_buttons_title" msgid="8978499601044961736">"大きいボタン"</string>
+    <string name="accessibility_menu_large_buttons_summary" msgid="236873938502785311">"ユーザー補助機能メニューのボタンを大きくする"</string>
+    <string name="pref_help_title" msgid="6871558837025010641">"ヘルプ"</string>
+    <string name="brightness_percentage_label" msgid="7391554573977867369">"明るさ <xliff:g id="PERCENTAGE">%1$s</xliff:g>%%"</string>
+    <string name="music_volume_percentage_label" msgid="398635599662604706">"音楽の音量 <xliff:g id="PERCENTAGE">%1$s</xliff:g>%%"</string>
+</resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-ka/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-ka/strings.xml
new file mode 100644
index 0000000..62ae27b
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-ka/strings.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="accessibility_menu_service_name" msgid="730136711554740131">"მარტივი წვდომის მენიუ"</string>
+    <string name="accessibility_menu_intro" msgid="3164193281544042394">"მარტივი წვდომის მენიუ გთავაზობთ ვრცელ, ეკრანულ მენიუს მოწყობილობის სამართავად. თქვენ შეგიძლიათ, ჩაკეტოთ მოწყობილობა, მართოთ ხმის სიმძლავრე და სიკაშკაშე, გადაიღოთ ეკრანის ანაბეჭდები და ა.შ."</string>
+    <string name="assistant_label" msgid="6796392082252272356">"ასისტენტი"</string>
+    <string name="assistant_utterance" msgid="65509599221141377">"ასისტენტი"</string>
+    <string name="a11y_settings_label" msgid="3977714687248445050">"მარტივი წვდომის პარამეტრები"</string>
+    <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="quick_settings_label" msgid="2999117381487601865">"სწრაფი პარამეტრები"</string>
+    <string name="notifications_label" msgid="6829741046963013567">"შეტყობინებები"</string>
+    <string name="screenshot_label" msgid="863978141223970162">"ეკრანის ანაბეჭდი"</string>
+    <string name="screenshot_utterance" msgid="1430760563401895074">"ეკრანის ანაბეჭდის გადაღება"</string>
+    <string name="volume_up_label" msgid="8592766918780362870">"ხმის აწევა"</string>
+    <string name="volume_down_label" msgid="8574981863656447346">"ხმის დაწევა"</string>
+    <string name="brightness_up_label" msgid="8010753822854544846">"სიკაშკაშის მომატება"</string>
+    <string name="brightness_down_label" msgid="7115662941913272072">"სიკაშკაშის დაკლება"</string>
+    <string name="previous_button_content_description" msgid="840869171117765966">"წინა ეკრანზე გადასვლა"</string>
+    <string name="next_button_content_description" msgid="6810058269847364406">"შემდეგ ეკრანზე გადასვლა"</string>
+    <string name="accessibility_menu_description" msgid="4458354794093858297">"მარტივი წვდომის მენიუ გთავაზობთ ვრცელ, ეკრანულ მენიუს მოწყობილობის სამართავად. თქვენ შეგიძლიათ, ჩაკეტოთ მოწყობილობა, მართოთ ხმის სიმძლავრე და სიკაშკაშე, გადაიღოთ ეკრანის ანაბეჭდები და ა.შ."</string>
+    <string name="accessibility_menu_summary" msgid="340071398148208130">"მართეთ მოწყობილობა დიდი მენიუს მეშვეობით"</string>
+    <string name="accessibility_menu_settings_name" msgid="1716888058785672611">"მარტივი წვდომის მენიუს პარამეტრები"</string>
+    <string name="accessibility_menu_large_buttons_title" msgid="8978499601044961736">"დიდი ღილაკები"</string>
+    <string name="accessibility_menu_large_buttons_summary" msgid="236873938502785311">"მარტივი წვდომის მენიუს ღილაკების ზომის გაზრდა"</string>
+    <string name="pref_help_title" msgid="6871558837025010641">"დახმარება"</string>
+    <string name="brightness_percentage_label" msgid="7391554573977867369">"სიკაშკაშე: <xliff:g id="PERCENTAGE">%1$s</xliff:g>%%"</string>
+    <string name="music_volume_percentage_label" msgid="398635599662604706">"მუსიკის ხმა: <xliff:g id="PERCENTAGE">%1$s</xliff:g>%%"</string>
+</resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-kk/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-kk/strings.xml
new file mode 100644
index 0000000..48c2634
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-kk/strings.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="accessibility_menu_service_name" msgid="730136711554740131">"Арнайы мүмкіндіктер мәзірі"</string>
+    <string name="accessibility_menu_intro" msgid="3164193281544042394">"Арнайы мүмкіндіктер мәзірі – экранда үлкейтіліп берілген мәзір. Ол арқылы құрылғыны құлыптайсыз, дыбыс деңгейі мен түс ашықтығын басқарасыз, скриншот түсіресіз және т.б. әрекеттерді орындай аласыз."</string>
+    <string name="assistant_label" msgid="6796392082252272356">"Assistant"</string>
+    <string name="assistant_utterance" msgid="65509599221141377">"Assistant"</string>
+    <string name="a11y_settings_label" msgid="3977714687248445050">"Арнайы мүмкіндіктер параметрлері"</string>
+    <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="quick_settings_label" msgid="2999117381487601865">"Жылдам параметрлер"</string>
+    <string name="notifications_label" msgid="6829741046963013567">"Хабарланды­рулар"</string>
+    <string name="screenshot_label" msgid="863978141223970162">"Скриншот"</string>
+    <string name="screenshot_utterance" msgid="1430760563401895074">"Скриншот жасау"</string>
+    <string name="volume_up_label" msgid="8592766918780362870">"Дыбысын арттыру"</string>
+    <string name="volume_down_label" msgid="8574981863656447346">"Дыбысын азайту"</string>
+    <string name="brightness_up_label" msgid="8010753822854544846">"Жарықтығын арттыру"</string>
+    <string name="brightness_down_label" msgid="7115662941913272072">"Жарықтығын азайту"</string>
+    <string name="previous_button_content_description" msgid="840869171117765966">"Алдыңғы экранға өту"</string>
+    <string name="next_button_content_description" msgid="6810058269847364406">"Келесі экранға өту"</string>
+    <string name="accessibility_menu_description" msgid="4458354794093858297">"Арнайы мүмкіндіктер мәзірінде құрылғыны басқаруға арналған үлкейтілген экран мәзірі бар. Ол арқылы құрылғыны құлыптай, дыбыс деңгейі мен түс ашықтығын басқара, скриншот түсіре және т. б. әрекеттерді орындай аласыз."</string>
+    <string name="accessibility_menu_summary" msgid="340071398148208130">"Құрылғыны үлкейтілген экран мәзірі арқылы басқару"</string>
+    <string name="accessibility_menu_settings_name" msgid="1716888058785672611">"Арнайы мүмкіндіктер мәзірі параметрлері"</string>
+    <string name="accessibility_menu_large_buttons_title" msgid="8978499601044961736">"Үлкен түймелер"</string>
+    <string name="accessibility_menu_large_buttons_summary" msgid="236873938502785311">"Арнайы мүмкіндіктер мәзірінің түймелерін үлкейту"</string>
+    <string name="pref_help_title" msgid="6871558837025010641">"Анықтама"</string>
+    <string name="brightness_percentage_label" msgid="7391554573977867369">"Жарықтығы: <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+    <string name="music_volume_percentage_label" msgid="398635599662604706">"Музыканың дыбыс деңгейі: <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+</resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-km/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-km/strings.xml
new file mode 100644
index 0000000..e091dd9
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-km/strings.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="accessibility_menu_service_name" msgid="730136711554740131">"ម៉ឺនុយ​ភាពងាយស្រួល"</string>
+    <string name="accessibility_menu_intro" msgid="3164193281544042394">"ម៉ឺនុយភាពងាយស្រួលផ្ដល់ម៉ឺនុយធំនៅលើអេក្រង់ ដើម្បីគ្រប់គ្រងឧបករណ៍របស់អ្នក។ អ្នកអាច​ចាក់សោឧបករណ៍​របស់អ្នក គ្រប់គ្រងកម្រិតសំឡេងនិងពន្លឺ ថតរូបអេក្រង់ និង​អ្វីៗច្រើនទៀត។"</string>
+    <string name="assistant_label" msgid="6796392082252272356">"ជំនួយការ"</string>
+    <string name="assistant_utterance" msgid="65509599221141377">"Google Assistant"</string>
+    <string name="a11y_settings_label" msgid="3977714687248445050">"ការកំណត់​ភាព​ងាយស្រួល"</string>
+    <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="quick_settings_label" msgid="2999117381487601865">"ការកំណត់រហ័ស"</string>
+    <string name="notifications_label" msgid="6829741046963013567">"ការ​ជូនដំណឹង"</string>
+    <string name="screenshot_label" msgid="863978141223970162">"រូបថតអេក្រង់"</string>
+    <string name="screenshot_utterance" msgid="1430760563401895074">"ថត​រូបថត​អេក្រង់"</string>
+    <string name="volume_up_label" msgid="8592766918780362870">"ដំឡើង​កម្រិត​សំឡេង"</string>
+    <string name="volume_down_label" msgid="8574981863656447346">"បន្ថយកម្រិតសំឡេង"</string>
+    <string name="brightness_up_label" msgid="8010753822854544846">"បង្កើន​​ពន្លឺ"</string>
+    <string name="brightness_down_label" msgid="7115662941913272072">"បន្ថយ​ពន្លឺ"</string>
+    <string name="previous_button_content_description" msgid="840869171117765966">"ចូល​ទៅ​កាន់​អេក្រង់​មុន"</string>
+    <string name="next_button_content_description" msgid="6810058269847364406">"ចូល​ទៅកាន់​អេក្រង់​បន្ទាប់​"</string>
+    <string name="accessibility_menu_description" msgid="4458354794093858297">"ម៉ឺនុយភាពងាយស្រួលផ្ដល់ម៉ឺនុយធំនៅលើអេក្រង់ ដើម្បីគ្រប់គ្រងឧបករណ៍របស់អ្នក។ អ្នកអាច​ចាក់សោឧបករណ៍​របស់អ្នក គ្រប់គ្រងកម្រិតសំឡេងនិងពន្លឺ ថតរូបអេក្រង់ និង​អ្វីៗច្រើនទៀត។"</string>
+    <string name="accessibility_menu_summary" msgid="340071398148208130">"គ្រប់គ្រងឧបករណ៍តាមរយៈម៉ឺនុយធំ"</string>
+    <string name="accessibility_menu_settings_name" msgid="1716888058785672611">"ការកំណត់​ម៉ឺនុយ​ភាពងាយស្រួល"</string>
+    <string name="accessibility_menu_large_buttons_title" msgid="8978499601044961736">"ប៊ូតុង​ធំ"</string>
+    <string name="accessibility_menu_large_buttons_summary" msgid="236873938502785311">"បង្កើន​ទំហំ​ប៊ូតុង​ម៉ឺនុយភាពងាយស្រួល"</string>
+    <string name="pref_help_title" msgid="6871558837025010641">"ជំនួយ"</string>
+    <string name="brightness_percentage_label" msgid="7391554573977867369">"ពន្លឺ <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+    <string name="music_volume_percentage_label" msgid="398635599662604706">"កម្រិត​សំឡេង​តន្ត្រី <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+</resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-kn/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-kn/strings.xml
new file mode 100644
index 0000000..5d1f722
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-kn/strings.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="accessibility_menu_service_name" msgid="730136711554740131">"ಆ್ಯಕ್ಸೆಸಿಬಿಲಿಟಿ ಮೆನು"</string>
+    <string name="accessibility_menu_intro" msgid="3164193281544042394">"ನಿಮ್ಮ ಸಾಧನವನ್ನು ನಿಯಂತ್ರಿಸುವುದಕ್ಕಾಗಿ ಆ್ಯಕ್ಸೆಸಿಬಿಲಿಟಿ ಮೆನು ದೊಡ್ಡ ಸ್ಕ್ರೀನ್ ಮೆನುವನ್ನು ಒದಗಿಸುತ್ತದೆ. ನೀವು ನಿಮ್ಮ ಸಾಧನವನ್ನು ಲಾಕ್ ಮಾಡಬಹುದು, ವಾಲ್ಯೂಮ್ ಮತ್ತು ಪ್ರಖರತೆಯನ್ನು ನಿಯಂತ್ರಿಸಬಹುದು, ಸ್ಕ್ರೀನ್‌ಶಾಟ್‌ಗಳನ್ನು ತೆಗೆದುಕೊಳ್ಳಬಹುದು ಮತ್ತು ಇನ್ನೂ ಹೆಚ್ಚಿನವನ್ನು ಮಾಡಬಹುದು."</string>
+    <string name="assistant_label" msgid="6796392082252272356">"Assistant"</string>
+    <string name="assistant_utterance" msgid="65509599221141377">"Assistant"</string>
+    <string name="a11y_settings_label" msgid="3977714687248445050">"ಆ್ಯಕ್ಸೆಸಿಬಿಲಿಟಿ ಸೆಟ್ಟಿಂಗ್‌ಗಳು"</string>
+    <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="quick_settings_label" msgid="2999117381487601865">"ತ್ವರಿತ ಸೆಟ್ಟಿಂಗ್‍ಗಳು"</string>
+    <string name="notifications_label" msgid="6829741046963013567">"ಅಧಿಸೂಚನೆಗಳು"</string>
+    <string name="screenshot_label" msgid="863978141223970162">"ಸ್ಕ್ರೀನ್‌ಶಾಟ್"</string>
+    <string name="screenshot_utterance" msgid="1430760563401895074">"ಸ್ಕ್ರೀನ್‌ಶಾಟ್ ತೆಗೆದುಕೊಳ್ಳಿ"</string>
+    <string name="volume_up_label" msgid="8592766918780362870">"ವಾಲ್ಯೂಮ್ ಜಾಸ್ತಿ"</string>
+    <string name="volume_down_label" msgid="8574981863656447346">"ವಾಲ್ಯೂಮ್ ಕಡಿಮೆ"</string>
+    <string name="brightness_up_label" msgid="8010753822854544846">"ಪ್ರಖರತೆ ಹೆಚ್ಚು ಮಾಡಿ"</string>
+    <string name="brightness_down_label" msgid="7115662941913272072">"ಪ್ರಖರತೆ ಕಡಿಮೆ ಮಾಡಿ"</string>
+    <string name="previous_button_content_description" msgid="840869171117765966">"ಹಿಂದಿನ ಸ್ಕ್ರೀನ್‌ಗೆ ಹೋಗಿ"</string>
+    <string name="next_button_content_description" msgid="6810058269847364406">"ಮುಂದಿನ ಸ್ಕ್ರೀನ್‌ಗೆ ಹೋಗಿ"</string>
+    <string name="accessibility_menu_description" msgid="4458354794093858297">"ನಿಮ್ಮ ಫೋನ್ ಅನ್ನು ನಿಯಂತ್ರಿಸಲು ಆ್ಯಕ್ಸೆಸಿಬಿಲಿಟಿ ಮೆನು ದೊಡ್ಡ ಸ್ಕ್ರೀನ್ ಮೆನುವನ್ನು ಒದಗಿಸುತ್ತದೆ. ನೀವು ನಿಮ್ಮ ಸಾಧನವನ್ನು ಲಾಕ್ ಮಾಡಬಹುದು, ವಾಲ್ಯೂಮ್ ಮತ್ತು ಪ್ರಖರತೆಯನ್ನು ನಿಯಂತ್ರಿಸಬಹುದು, ಸ್ಕ್ರೀನ್‌ಶಾಟ್‌ಗಳನ್ನು ತೆಗೆದುಕೊಳ್ಳಬಹುದು ಮತ್ತು ಇನ್ನೂ ಹೆಚ್ಚಿನದನ್ನು ಮಾಡಬಹುದು."</string>
+    <string name="accessibility_menu_summary" msgid="340071398148208130">"ದೊಡ್ಡ ಮೆನುವಿನ ಮೂಲಕ ಸಾಧನವನ್ನು ನಿಯಂತ್ರಿಸಿ"</string>
+    <string name="accessibility_menu_settings_name" msgid="1716888058785672611">"ಆ್ಯಕ್ಸೆಸಿಬಿಲಿಟಿ ಮೆನು ಸೆಟ್ಟಿಂಗ್‌ಗಳು"</string>
+    <string name="accessibility_menu_large_buttons_title" msgid="8978499601044961736">"ದೊಡ್ಡ ಬಟನ್‌ಗಳು"</string>
+    <string name="accessibility_menu_large_buttons_summary" msgid="236873938502785311">"ಆ್ಯಕ್ಸೆಸಿಬಿಲಿಟಿ ಮೆನು ಬಟನ್‌ಗಳ ಗಾತ್ರವನ್ನು ಹೆಚ್ಚಿಸಿ"</string>
+    <string name="pref_help_title" msgid="6871558837025010641">"ಸಹಾಯ"</string>
+    <string name="brightness_percentage_label" msgid="7391554573977867369">"ಪ್ರಖರತೆ <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+    <string name="music_volume_percentage_label" msgid="398635599662604706">"ಸಂಗೀತ ವಾಲ್ಯೂಮ್‌ <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+</resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-ko/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-ko/strings.xml
new file mode 100644
index 0000000..92d7049
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-ko/strings.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="accessibility_menu_service_name" msgid="730136711554740131">"접근성 메뉴"</string>
+    <string name="accessibility_menu_intro" msgid="3164193281544042394">"접근성 메뉴를 사용하면 화면에 크게 표시되는 메뉴로 기기를 제어할 수 있습니다. 기기 잠금, 볼륨 및 밝기 조절, 스크린샷 찍기 등의 작업이 지원됩니다."</string>
+    <string name="assistant_label" msgid="6796392082252272356">"어시스턴트"</string>
+    <string name="assistant_utterance" msgid="65509599221141377">"어시스턴트"</string>
+    <string name="a11y_settings_label" msgid="3977714687248445050">"접근성 설정"</string>
+    <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="quick_settings_label" msgid="2999117381487601865">"빠른 설정"</string>
+    <string name="notifications_label" msgid="6829741046963013567">"알림"</string>
+    <string name="screenshot_label" msgid="863978141223970162">"스크린샷"</string>
+    <string name="screenshot_utterance" msgid="1430760563401895074">"스크린샷 촬영"</string>
+    <string name="volume_up_label" msgid="8592766918780362870">"볼륨 크게"</string>
+    <string name="volume_down_label" msgid="8574981863656447346">"볼륨 작게"</string>
+    <string name="brightness_up_label" msgid="8010753822854544846">"밝기 높이기"</string>
+    <string name="brightness_down_label" msgid="7115662941913272072">"밝기 낮추기"</string>
+    <string name="previous_button_content_description" msgid="840869171117765966">"이전 화면으로 이동"</string>
+    <string name="next_button_content_description" msgid="6810058269847364406">"다음 화면으로 이동"</string>
+    <string name="accessibility_menu_description" msgid="4458354794093858297">"접근성 메뉴를 사용하면 화면에 크게 표시되는 메뉴로 기기를 제어할 수 있습니다. 기기 잠금, 볼륨 및 밝기 조절, 스크린샷 찍기 등의 작업이 지원됩니다."</string>
+    <string name="accessibility_menu_summary" msgid="340071398148208130">"큰 메뉴로 기기 제어"</string>
+    <string name="accessibility_menu_settings_name" msgid="1716888058785672611">"접근성 메뉴 설정"</string>
+    <string name="accessibility_menu_large_buttons_title" msgid="8978499601044961736">"큰 버튼"</string>
+    <string name="accessibility_menu_large_buttons_summary" msgid="236873938502785311">"접근성 메뉴 버튼 크기 늘리기"</string>
+    <string name="pref_help_title" msgid="6871558837025010641">"도움말"</string>
+    <string name="brightness_percentage_label" msgid="7391554573977867369">"밝기 <xliff:g id="PERCENTAGE">%1$s</xliff:g>%%"</string>
+    <string name="music_volume_percentage_label" msgid="398635599662604706">"음악 볼륨 <xliff:g id="PERCENTAGE">%1$s</xliff:g>%%"</string>
+</resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-ky/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-ky/strings.xml
new file mode 100644
index 0000000..fa8b587
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-ky/strings.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="accessibility_menu_service_name" msgid="730136711554740131">"Атайын мүмкүнчүлүктөр менюсу"</string>
+    <string name="accessibility_menu_intro" msgid="3164193281544042394">"Атайын мүмкүнчүлүктөр менюсу аркылуу түзмөгүңүздү кулпулап, үнүн катуулатып/акырындатып, экрандын жарык деңгээлин тууралап, скриншот тартып жана башка нерселерди жасай аласыз."</string>
+    <string name="assistant_label" msgid="6796392082252272356">"Жардамчы"</string>
+    <string name="assistant_utterance" msgid="65509599221141377">"Жардамчы"</string>
+    <string name="a11y_settings_label" msgid="3977714687248445050">"Атайын мүмкүнчүлүктөрдүн параметрлери"</string>
+    <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="quick_settings_label" msgid="2999117381487601865">"Ыкчам параметрлер"</string>
+    <string name="notifications_label" msgid="6829741046963013567">"Билдирмелер"</string>
+    <string name="screenshot_label" msgid="863978141223970162">"Скриншот"</string>
+    <string name="screenshot_utterance" msgid="1430760563401895074">"Скриншот тартып алуу"</string>
+    <string name="volume_up_label" msgid="8592766918780362870">"Катуулатуу"</string>
+    <string name="volume_down_label" msgid="8574981863656447346">"Акырындатуу"</string>
+    <string name="brightness_up_label" msgid="8010753822854544846">"Жарыгыраак"</string>
+    <string name="brightness_down_label" msgid="7115662941913272072">"Күңүртүрөөк"</string>
+    <string name="previous_button_content_description" msgid="840869171117765966">"Мурунку экранга өтүү"</string>
+    <string name="next_button_content_description" msgid="6810058269847364406">"Кийинки экранга өтүү"</string>
+    <string name="accessibility_menu_description" msgid="4458354794093858297">"Атайын мүмкүнчүлүктөр менюсу — бул түзмөгүңүздү көзөмөлдөөнү жеңилдетүүгө ылайыкташтырылган экрандагы чоң меню. Түзмөгүңүздү кулпулап, үнүнүн катуулугун жана экрандын жарыктыгын көзөмөлдөп, скриншотторду тартып жана башка аракеттерди аткара аласыз."</string>
+    <string name="accessibility_menu_summary" msgid="340071398148208130">"Түзмөктү чоң менюдан башкаруу"</string>
+    <string name="accessibility_menu_settings_name" msgid="1716888058785672611">"Атайын мүмкүнчүлүктөр менюсунун параметрлери"</string>
+    <string name="accessibility_menu_large_buttons_title" msgid="8978499601044961736">"Чоң баскычтар"</string>
+    <string name="accessibility_menu_large_buttons_summary" msgid="236873938502785311">"Атайын мүмкүнчүлүктөр менюсундагы баскычтардын өлчөмүн чоңойтот"</string>
+    <string name="pref_help_title" msgid="6871558837025010641">"Жардам"</string>
+    <string name="brightness_percentage_label" msgid="7391554573977867369">"Жарыктыгы <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+    <string name="music_volume_percentage_label" msgid="398635599662604706">"Музыканын үнүнүн катуулугу <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+</resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-lo/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-lo/strings.xml
new file mode 100644
index 0000000..4032565
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-lo/strings.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="accessibility_menu_service_name" msgid="730136711554740131">"​ເມ​ນູ​ການ​ຊ່ວຍ​ເຂົ້າ​ເຖິງ"</string>
+    <string name="accessibility_menu_intro" msgid="3164193281544042394">"ເມນູການຊ່ວຍເຂົ້າເຖິງຈະສະໜອງເມນູຢູ່ໜ້າຈໍຂະໜາດໃຫຍ່ເພື່ອຄວບຄຸມອຸປະກອນຂອງທ່ານ. ທ່ານສາມາດລັອກອຸປະກອນຂອງທ່ານ, ຄວບຄຸມລະດັບສຽງ ແລະ ຄວາມສະຫວ່າງ, ຖ່າຍຮູບໜ້າຈໍ ແລະ ອື່ນໆໄດ້."</string>
+    <string name="assistant_label" msgid="6796392082252272356">"ຜູ້ຊ່ວຍ"</string>
+    <string name="assistant_utterance" msgid="65509599221141377">"ຜູ້ຊ່ວຍ"</string>
+    <string name="a11y_settings_label" msgid="3977714687248445050">"ການຕັ້ງຄ່າການຊ່ວຍເຂົ້າເຖິງ"</string>
+    <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="quick_settings_label" msgid="2999117381487601865">"ການ​ຕັ້ງ​ຄ່າ​ດ່ວນ"</string>
+    <string name="notifications_label" msgid="6829741046963013567">"ການແຈ້ງເຕືອນ"</string>
+    <string name="screenshot_label" msgid="863978141223970162">"ຮູບໜ້າຈໍ"</string>
+    <string name="screenshot_utterance" msgid="1430760563401895074">"ຖ່າຍຮູບໜ້າຈໍ"</string>
+    <string name="volume_up_label" msgid="8592766918780362870">"ເພີ່ມສຽງຂຶ້ນ"</string>
+    <string name="volume_down_label" msgid="8574981863656447346">"ຄ່ອຍສຽງລົງ"</string>
+    <string name="brightness_up_label" msgid="8010753822854544846">"ເພີ່ມຄວາມສະຫວ່າງຂຶ້ນ"</string>
+    <string name="brightness_down_label" msgid="7115662941913272072">"ຫຼຸດຄວາມສະຫວ່າງລົງ"</string>
+    <string name="previous_button_content_description" msgid="840869171117765966">"ໄປທີ່ໜ້າຈໍກ່ອນໜ້າ"</string>
+    <string name="next_button_content_description" msgid="6810058269847364406">"ໄປທີ່ໜ້າຈໍຖັດໄປ"</string>
+    <string name="accessibility_menu_description" msgid="4458354794093858297">"ເມນູການຊ່ວຍເຂົ້າເຖິງຈະສະໜອງເມນູຢູ່ໜ້າຈໍຂະໜາດໃຫຍ່ເພື່ອຄວບຄຸມອຸປະກອນຂອງທ່ານ. ທ່ານສາມາດລັອກອຸປະກອນຂອງທ່ານ, ຄວບຄຸມລະດັບສຽງ ແລະ ຄວາມສະຫວ່າງ, ຖ່າຍຮູບໜ້າຈໍ ແລະ ອື່ນໆໄດ້."</string>
+    <string name="accessibility_menu_summary" msgid="340071398148208130">"ຄວບຄຸມອຸປະກອນຜ່ານເມນູໃຫຍ່"</string>
+    <string name="accessibility_menu_settings_name" msgid="1716888058785672611">"ການຕັ້ງຄ່າເມນູການຊ່ວຍເຂົ້າເຖິງ"</string>
+    <string name="accessibility_menu_large_buttons_title" msgid="8978499601044961736">"ປຸ່ມໃຫຍ່"</string>
+    <string name="accessibility_menu_large_buttons_summary" msgid="236873938502785311">"ເພີ່ມຂະໜາດຂອງປຸ່ມເມນູການຊ່ວຍເຂົ້າເຖິງ"</string>
+    <string name="pref_help_title" msgid="6871558837025010641">"ຊ່ວຍເຫຼືອ"</string>
+    <string name="brightness_percentage_label" msgid="7391554573977867369">"ຄວາມສະຫວ່າງ <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+    <string name="music_volume_percentage_label" msgid="398635599662604706">"ລະດັບສຽງເພງ <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+</resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-lt/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-lt/strings.xml
new file mode 100644
index 0000000..11d1ffa
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-lt/strings.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="accessibility_menu_service_name" msgid="730136711554740131">"Pritaikymo neįgaliesiems meniu"</string>
+    <string name="accessibility_menu_intro" msgid="3164193281544042394">"Naudodami pritaikomumo meniu galite atidaryti didelį ekrane pateikiamą meniu, skirtą įrenginiui valdyti. Galite užrakinti įrenginį, valdyti garsumą ir šviesumą, užfiksuoti ekrano kopijas ir atlikti kitus veiksmus."</string>
+    <string name="assistant_label" msgid="6796392082252272356">"Padėjėjas"</string>
+    <string name="assistant_utterance" msgid="65509599221141377">"Padėjėjas"</string>
+    <string name="a11y_settings_label" msgid="3977714687248445050">"Pritaikymo neįgaliesiems nustatymai"</string>
+    <string name="power_label" msgid="7699720321491287839">"Maitinimas"</string>
+    <string name="power_utterance" msgid="7444296686402104807">"Maitinimo parinktys"</string>
+    <string name="recent_apps_label" msgid="6583276995616385847">"Naujausios programos"</string>
+    <string name="lockscreen_label" msgid="648347953557887087">"Užrakinimo ekranas"</string>
+    <string name="quick_settings_label" msgid="2999117381487601865">"Spartieji nustatymai"</string>
+    <string name="notifications_label" msgid="6829741046963013567">"Pranešimai"</string>
+    <string name="screenshot_label" msgid="863978141223970162">"Ekrano kopija"</string>
+    <string name="screenshot_utterance" msgid="1430760563401895074">"Padaryti ekrano kopiją"</string>
+    <string name="volume_up_label" msgid="8592766918780362870">"Garsumo didinimas"</string>
+    <string name="volume_down_label" msgid="8574981863656447346">"Garsumo mažinimas"</string>
+    <string name="brightness_up_label" msgid="8010753822854544846">"Šviesumo didinimas"</string>
+    <string name="brightness_down_label" msgid="7115662941913272072">"Šviesumo mažinimas"</string>
+    <string name="previous_button_content_description" msgid="840869171117765966">"Eiti į ankstesnį ekraną"</string>
+    <string name="next_button_content_description" msgid="6810058269847364406">"Eiti į kitą ekraną"</string>
+    <string name="accessibility_menu_description" msgid="4458354794093858297">"Naudodami pritaikomumo meniu galite atidaryti didelį ekrane pateikiamą meniu, skirtą įrenginiui valdyti. Galite užrakinti įrenginį, valdyti garsumą ir šviesumą, užfiksuoti ekrano kopijas ir atlikti kitus veiksmus."</string>
+    <string name="accessibility_menu_summary" msgid="340071398148208130">"Valdykite įrenginį naudodami didelį meniu"</string>
+    <string name="accessibility_menu_settings_name" msgid="1716888058785672611">"Pritaikymo neįgaliesiems meniu nustatymai"</string>
+    <string name="accessibility_menu_large_buttons_title" msgid="8978499601044961736">"Dideli mygtukai"</string>
+    <string name="accessibility_menu_large_buttons_summary" msgid="236873938502785311">"Padidinti pritaikymo neįgaliesiems meniu mygtukų dydį"</string>
+    <string name="pref_help_title" msgid="6871558837025010641">"Pagalba"</string>
+    <string name="brightness_percentage_label" msgid="7391554573977867369">"Šviesumas: <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+    <string name="music_volume_percentage_label" msgid="398635599662604706">"Muzikos garsumas: <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+</resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-lv/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-lv/strings.xml
new file mode 100644
index 0000000..4a0c9e6
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-lv/strings.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="accessibility_menu_service_name" msgid="730136711554740131">"Pieejamības izvēlne"</string>
+    <string name="accessibility_menu_intro" msgid="3164193281544042394">"Pieejamības izvēlne ir liela ekrāna izvēlne, ar ko varat kontrolēt ierīci. Varat bloķēt ierīci, kontrolēt skaļumu un spilgtumu, veidot ekrānuzņēmumus un paveikt daudz ko citu."</string>
+    <string name="assistant_label" msgid="6796392082252272356">"Asistents"</string>
+    <string name="assistant_utterance" msgid="65509599221141377">"Asistents"</string>
+    <string name="a11y_settings_label" msgid="3977714687248445050">"Pieejamības iestatījumi"</string>
+    <string name="power_label" msgid="7699720321491287839">"Barošana"</string>
+    <string name="power_utterance" msgid="7444296686402104807">"Barošanas opcijas"</string>
+    <string name="recent_apps_label" msgid="6583276995616385847">"Pēdējās izmantotās lietotnes"</string>
+    <string name="lockscreen_label" msgid="648347953557887087">"Bloķēšanas ekrāns"</string>
+    <string name="quick_settings_label" msgid="2999117381487601865">"Ātrie iestatījumi"</string>
+    <string name="notifications_label" msgid="6829741046963013567">"Paziņojumi"</string>
+    <string name="screenshot_label" msgid="863978141223970162">"Ekrānuzņēmums"</string>
+    <string name="screenshot_utterance" msgid="1430760563401895074">"Izveidot ekrānuzņēmumu"</string>
+    <string name="volume_up_label" msgid="8592766918780362870">"Palielināt skaļumu"</string>
+    <string name="volume_down_label" msgid="8574981863656447346">"Samazināt skaļumu"</string>
+    <string name="brightness_up_label" msgid="8010753822854544846">"Palielināt spilgtumu"</string>
+    <string name="brightness_down_label" msgid="7115662941913272072">"Samazināt spilgtumu"</string>
+    <string name="previous_button_content_description" msgid="840869171117765966">"Pāriet uz iepriekšējo ekrānu"</string>
+    <string name="next_button_content_description" msgid="6810058269847364406">"Pāriet uz nākamo ekrānu"</string>
+    <string name="accessibility_menu_description" msgid="4458354794093858297">"Pieejamības izvēlne nodrošina lielu ekrāna izvēlni, ko varat izmantot ierīces kontrolēšanai. Varat bloķēt ierīci, kontrolēt skaļumu un spilgtumu, veidot ekrānuzņēmumus un paveikt daudz ko citu."</string>
+    <string name="accessibility_menu_summary" msgid="340071398148208130">"Kontrolējiet ierīci, izmantojot lielu izvēlni"</string>
+    <string name="accessibility_menu_settings_name" msgid="1716888058785672611">"Pieejamības izvēlnes iestatījumi"</string>
+    <string name="accessibility_menu_large_buttons_title" msgid="8978499601044961736">"Lielas pogas"</string>
+    <string name="accessibility_menu_large_buttons_summary" msgid="236873938502785311">"Palielināt pieejamības izvēlnes pogu lielumu"</string>
+    <string name="pref_help_title" msgid="6871558837025010641">"Palīdzība"</string>
+    <string name="brightness_percentage_label" msgid="7391554573977867369">"Spilgtums: <xliff:g id="PERCENTAGE">%1$s</xliff:g>%%"</string>
+    <string name="music_volume_percentage_label" msgid="398635599662604706">"Mūzikas skaļums: <xliff:g id="PERCENTAGE">%1$s</xliff:g>%%"</string>
+</resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-mk/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-mk/strings.xml
new file mode 100644
index 0000000..d026759
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-mk/strings.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="accessibility_menu_service_name" msgid="730136711554740131">"Мени за пристапност"</string>
+    <string name="accessibility_menu_intro" msgid="3164193281544042394">"„Менито за пристапност“ ви овозможува да го контролирате уредот преку големо мени на екранот. Може да го заклучите уредот, да ги контролирате јачината на звукот и осветленоста, да правите слики од екранот и друго."</string>
+    <string name="assistant_label" msgid="6796392082252272356">"Помошник"</string>
+    <string name="assistant_utterance" msgid="65509599221141377">"Помошник"</string>
+    <string name="a11y_settings_label" msgid="3977714687248445050">"Пристапност"</string>
+    <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="quick_settings_label" msgid="2999117381487601865">"Брзи поставки"</string>
+    <string name="notifications_label" msgid="6829741046963013567">"Известувања"</string>
+    <string name="screenshot_label" msgid="863978141223970162">"Слика од екранот"</string>
+    <string name="screenshot_utterance" msgid="1430760563401895074">"Направи слика од екранот"</string>
+    <string name="volume_up_label" msgid="8592766918780362870">"Зголеми звук"</string>
+    <string name="volume_down_label" msgid="8574981863656447346">"Намали звук"</string>
+    <string name="brightness_up_label" msgid="8010753822854544846">"Осветли"</string>
+    <string name="brightness_down_label" msgid="7115662941913272072">"Затемни"</string>
+    <string name="previous_button_content_description" msgid="840869171117765966">"Оди на претходниот екран"</string>
+    <string name="next_button_content_description" msgid="6810058269847364406">"Оди на следниот екран"</string>
+    <string name="accessibility_menu_description" msgid="4458354794093858297">"„Менито за пристапност“ ви овозможува да го контролирате уредот преку големо мени на екранот. Може да го заклучите уредот, да ги контролирате јачината на звукот и осветленоста, да правите слики од екранот и друго."</string>
+    <string name="accessibility_menu_summary" msgid="340071398148208130">"Контролирајте го уредот преку големо мени"</string>
+    <string name="accessibility_menu_settings_name" msgid="1716888058785672611">"Поставки за мени за прист."</string>
+    <string name="accessibility_menu_large_buttons_title" msgid="8978499601044961736">"Големи копчиња"</string>
+    <string name="accessibility_menu_large_buttons_summary" msgid="236873938502785311">"Зголеми ги копчињата на менито за пристапност"</string>
+    <string name="pref_help_title" msgid="6871558837025010641">"Помош"</string>
+    <string name="brightness_percentage_label" msgid="7391554573977867369">"Осветленост <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+    <string name="music_volume_percentage_label" msgid="398635599662604706">"Јачина на звук за музика <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+</resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-ml/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-ml/strings.xml
new file mode 100644
index 0000000..00e0a0f
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-ml/strings.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="accessibility_menu_service_name" msgid="730136711554740131">"ഉപയോഗസഹായി മെനു"</string>
+    <string name="accessibility_menu_intro" msgid="3164193281544042394">"നിങ്ങളുടെ ഉപകരണം നിയന്ത്രിക്കുന്നതിന്, ഉപയോഗസഹായി മെനു വലിയൊരു ഓൺ-സ്ക്രീൻ മെനു നൽകുന്നു. ഉപകരണം ലോക്ക് ചെയ്യാനും ശബ്‌ദവും തെളിച്ചവും നിയന്ത്രിക്കാനും സ്‌ക്രീൻ ഷോട്ടുകൾ എടുക്കാനും മറ്റും നിങ്ങൾക്ക് കഴിയും."</string>
+    <string name="assistant_label" msgid="6796392082252272356">"അസിസ്റ്റന്റ്"</string>
+    <string name="assistant_utterance" msgid="65509599221141377">"Assistant"</string>
+    <string name="a11y_settings_label" msgid="3977714687248445050">"പ്രവേശനക്ഷമത ക്രമീകരണം"</string>
+    <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="quick_settings_label" msgid="2999117381487601865">"ദ്രുത ക്രമീകരണം"</string>
+    <string name="notifications_label" msgid="6829741046963013567">"അറിയിപ്പുകൾ"</string>
+    <string name="screenshot_label" msgid="863978141223970162">"സ്‌ക്രീൻഷോട്ട്"</string>
+    <string name="screenshot_utterance" msgid="1430760563401895074">"സ്ക്രീന്‍ഷോട്ട് എടുക്കുക"</string>
+    <string name="volume_up_label" msgid="8592766918780362870">"ശബ്‌ദം കൂട്ടുക"</string>
+    <string name="volume_down_label" msgid="8574981863656447346">"ശബ്‌ദം കുറയ്ക്കുക"</string>
+    <string name="brightness_up_label" msgid="8010753822854544846">"തെളിച്ചം കൂട്ടുക"</string>
+    <string name="brightness_down_label" msgid="7115662941913272072">"തെളിച്ചം കുറയ്‌ക്കുക"</string>
+    <string name="previous_button_content_description" msgid="840869171117765966">"മുമ്പത്തെ സ്‌ക്രീനിലേക്ക് പോവുക"</string>
+    <string name="next_button_content_description" msgid="6810058269847364406">"അടുത്ത സ്‌ക്രീനിലേക്ക് പോവുക"</string>
+    <string name="accessibility_menu_description" msgid="4458354794093858297">"നിങ്ങളുടെ ഉപകരണം നിയന്ത്രിക്കുന്നതിന്, ഉപയോഗസഹായി മെനു വലിയൊരു ഓൺ-സ്ക്രീൻ മെനു നൽകുന്നു. ഉപകരണം ലോക്ക് ചെയ്യാനും ശബ്‌ദവും തെളിച്ചവും നിയന്ത്രിക്കാനും സ്‌ക്രീൻ ഷോട്ടുകൾ എടുക്കാനും മറ്റും നിങ്ങൾക്ക് കഴിയും."</string>
+    <string name="accessibility_menu_summary" msgid="340071398148208130">"വലിയ മെനുവിലൂടെ ഉപകരണം നിയന്ത്രിക്കുക"</string>
+    <string name="accessibility_menu_settings_name" msgid="1716888058785672611">"ഉപയോഗസഹായി മെനു ക്രമീകരണം"</string>
+    <string name="accessibility_menu_large_buttons_title" msgid="8978499601044961736">"വലിയ ബട്ടണുകൾ"</string>
+    <string name="accessibility_menu_large_buttons_summary" msgid="236873938502785311">"ഉപയോഗസഹായി മെനു ബട്ടണുകളുടെ വലുപ്പം കൂട്ടുക"</string>
+    <string name="pref_help_title" msgid="6871558837025010641">"സഹായം"</string>
+    <string name="brightness_percentage_label" msgid="7391554573977867369">"തെളിച്ചം, <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+    <string name="music_volume_percentage_label" msgid="398635599662604706">"സംഗീത ശബ്‌ദം, <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+</resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-mn/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-mn/strings.xml
new file mode 100644
index 0000000..6f8d221
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-mn/strings.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="accessibility_menu_service_name" msgid="730136711554740131">"Хандалтын цэс"</string>
+    <string name="accessibility_menu_intro" msgid="3164193281544042394">"Хандалтын цэс нь танд төхөөрөмжөө том дэлгэцийн цэсээр хянах боломжийг олгоно. Та төхөөрөмжөө түгжих, дууны түвшин болон гэрэлтүүлгийг хянах, дэлгэцийн агшин авах болон бусад үйлдлийг хийж болно."</string>
+    <string name="assistant_label" msgid="6796392082252272356">"Туслах"</string>
+    <string name="assistant_utterance" msgid="65509599221141377">"Туслах"</string>
+    <string name="a11y_settings_label" msgid="3977714687248445050">"Хандалтын тохиргоо"</string>
+    <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="quick_settings_label" msgid="2999117381487601865">"Шуурхай тохиргоо"</string>
+    <string name="notifications_label" msgid="6829741046963013567">"Мэдэгдэл"</string>
+    <string name="screenshot_label" msgid="863978141223970162">"Дэлгэцийн агшин"</string>
+    <string name="screenshot_utterance" msgid="1430760563401895074">"Дэлгэцний зургийг дарах"</string>
+    <string name="volume_up_label" msgid="8592766918780362870">"Дууны түвшнийг нэмэх"</string>
+    <string name="volume_down_label" msgid="8574981863656447346">"Дууны түвшнийг багасгах"</string>
+    <string name="brightness_up_label" msgid="8010753822854544846">"Гэрэлтүүлгийг нэмэх"</string>
+    <string name="brightness_down_label" msgid="7115662941913272072">"Гэрэлтүүлгийг бууруулах"</string>
+    <string name="previous_button_content_description" msgid="840869171117765966">"Өмнөх дэлгэц рүү очих"</string>
+    <string name="next_button_content_description" msgid="6810058269847364406">"Дараагийн дэлгэц рүү очих"</string>
+    <string name="accessibility_menu_description" msgid="4458354794093858297">"Хандалтын цэс нь танд төхөөрөмжөө том дэлгэцийн цэсээр хянах боломжийг олгоно. Та төхөөрөмжөө түгжих, дууны түвшин болон гэрэлтүүлгийг хянах, дэлгэцийн агшин авах болон бусад үйлдлийг хийж болно."</string>
+    <string name="accessibility_menu_summary" msgid="340071398148208130">"Төхөөрөмжийг том цэсээр хянана уу"</string>
+    <string name="accessibility_menu_settings_name" msgid="1716888058785672611">"Хандалтын цэсийн тохиргоо"</string>
+    <string name="accessibility_menu_large_buttons_title" msgid="8978499601044961736">"Том товчлуур"</string>
+    <string name="accessibility_menu_large_buttons_summary" msgid="236873938502785311">"Хандалтын цэсний товчлуурын хэмжээг томруулах"</string>
+    <string name="pref_help_title" msgid="6871558837025010641">"Тусламж"</string>
+    <string name="brightness_percentage_label" msgid="7391554573977867369">"Гэрэлтүүлэг <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+    <string name="music_volume_percentage_label" msgid="398635599662604706">"Хөгжмийн дууны түвшин <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+</resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-mr/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-mr/strings.xml
new file mode 100644
index 0000000..8220749
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-mr/strings.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="accessibility_menu_service_name" msgid="730136711554740131">"अ‍ॅक्सेसिबिलिटी मेनू"</string>
+    <string name="accessibility_menu_intro" msgid="3164193281544042394">"तुमचे डिव्हाइस नियंत्रित करण्यासाठी अ‍ॅक्सेसिबिलिटी मेनू मोठा स्क्रीनवरील मेनू पुरवतो. तुम्ही तुमचे डिव्हाइस लॉक करणे, व्हॉल्यूम आणि ब्राइटनेस नियंत्रित करणे, स्क्रीनशॉट घेणे आणि आणखी बरेच काही करू शकता."</string>
+    <string name="assistant_label" msgid="6796392082252272356">"असिस्टंट"</string>
+    <string name="assistant_utterance" msgid="65509599221141377">"Assistant"</string>
+    <string name="a11y_settings_label" msgid="3977714687248445050">"अ‍ॅक्सेसिबिलिटी सेटिंग्ज"</string>
+    <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="quick_settings_label" msgid="2999117381487601865">"क्विक सेटिंग्ज"</string>
+    <string name="notifications_label" msgid="6829741046963013567">"सूचना"</string>
+    <string name="screenshot_label" msgid="863978141223970162">"स्क्रीनशॉट"</string>
+    <string name="screenshot_utterance" msgid="1430760563401895074">"स्क्रीनशॉट घ्या"</string>
+    <string name="volume_up_label" msgid="8592766918780362870">"व्‍हॉल्‍यूम वाढवा"</string>
+    <string name="volume_down_label" msgid="8574981863656447346">"व्‍हॉल्‍यूम कमी करा"</string>
+    <string name="brightness_up_label" msgid="8010753822854544846">"ब्राइटनेस वाढवा"</string>
+    <string name="brightness_down_label" msgid="7115662941913272072">"ब्राइटनेस कमी करा"</string>
+    <string name="previous_button_content_description" msgid="840869171117765966">"मागील स्क्रीनवर जा"</string>
+    <string name="next_button_content_description" msgid="6810058269847364406">"पुढील स्क्रीनवर जा"</string>
+    <string name="accessibility_menu_description" msgid="4458354794093858297">"तुमचे डिव्हाइस नियंत्रित करण्यासाठी अ‍ॅक्सेसिबिलिटी मेनू मोठा स्क्रीनवरील मेनू पुरवतो. तुम्ही तुमचे डिव्हाइस लॉक करणे, व्हॉल्यूम आणि ब्राइटनेस नियंत्रित करणे, स्क्रीनशॉट घेणे आणि आणखी बरेच काही करू शकता."</string>
+    <string name="accessibility_menu_summary" msgid="340071398148208130">"मोठ्या मेनूद्वारे डिव्हाइस नियंत्रित करा"</string>
+    <string name="accessibility_menu_settings_name" msgid="1716888058785672611">"अ‍ॅक्सेसिबिलिटी मेनू सेटिंग्ज"</string>
+    <string name="accessibility_menu_large_buttons_title" msgid="8978499601044961736">"मोठी बटणे"</string>
+    <string name="accessibility_menu_large_buttons_summary" msgid="236873938502785311">"अ‍ॅक्सेसिबिलिटी मेनू बटणांचा आकार वाढवा"</string>
+    <string name="pref_help_title" msgid="6871558837025010641">"मदत"</string>
+    <string name="brightness_percentage_label" msgid="7391554573977867369">"ब्राइटनेस <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+    <string name="music_volume_percentage_label" msgid="398635599662604706">"संगीताचा व्हॉल्यूम <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+</resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-ms/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-ms/strings.xml
new file mode 100644
index 0000000..9c1ea75
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-ms/strings.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="accessibility_menu_service_name" msgid="730136711554740131">"Menu Kebolehaksesan"</string>
+    <string name="accessibility_menu_intro" msgid="3164193281544042394">"Menu Kebolehaksesan menyediakan menu pada skrin yang besar untuk mengawal peranti anda. Anda boleh mengunci peranti anda, mengawal kelantangan dan kecerahan, mengambil tangkapan skrin dan banyak lagi."</string>
+    <string name="assistant_label" msgid="6796392082252272356">"Assistant"</string>
+    <string name="assistant_utterance" msgid="65509599221141377">"Assistant"</string>
+    <string name="a11y_settings_label" msgid="3977714687248445050">"Tetapan Kebolehaksesan"</string>
+    <string name="power_label" msgid="7699720321491287839">"Kuasa"</string>
+    <string name="power_utterance" msgid="7444296686402104807">"Pilihan kuasa"</string>
+    <string name="recent_apps_label" msgid="6583276995616385847">"Apl terbaharu"</string>
+    <string name="lockscreen_label" msgid="648347953557887087">"Kunci skrin"</string>
+    <string name="quick_settings_label" msgid="2999117381487601865">"Tetapan Pantas"</string>
+    <string name="notifications_label" msgid="6829741046963013567">"Pemberitahuan"</string>
+    <string name="screenshot_label" msgid="863978141223970162">"Tangkapan skrin"</string>
+    <string name="screenshot_utterance" msgid="1430760563401895074">"Ambil tangkapan skrin"</string>
+    <string name="volume_up_label" msgid="8592766918780362870">"Tambah kelantangan"</string>
+    <string name="volume_down_label" msgid="8574981863656447346">"Kurangkan kelantangan"</string>
+    <string name="brightness_up_label" msgid="8010753822854544846">"Tambahkan kecerahan"</string>
+    <string name="brightness_down_label" msgid="7115662941913272072">"Kurangkan kecerahan"</string>
+    <string name="previous_button_content_description" msgid="840869171117765966">"Pergi ke skrin sebelumnya"</string>
+    <string name="next_button_content_description" msgid="6810058269847364406">"Pergi ke skrin seterusnya"</string>
+    <string name="accessibility_menu_description" msgid="4458354794093858297">"Menu Kebolehaksesan menyediakan menu pada skrin yang besar untuk mengawal peranti anda. Anda boleh mengunci peranti anda, mengawal kelantangan dan kecerahan, mengambil tangkapan skrin dan pelbagai lagi."</string>
+    <string name="accessibility_menu_summary" msgid="340071398148208130">"Kawal peranti melalui menu besar"</string>
+    <string name="accessibility_menu_settings_name" msgid="1716888058785672611">"Tetapan Menu Kebolehaksesan"</string>
+    <string name="accessibility_menu_large_buttons_title" msgid="8978499601044961736">"Butang besar"</string>
+    <string name="accessibility_menu_large_buttons_summary" msgid="236873938502785311">"Besarkan saiz Butang Menu Kebolehaksesan"</string>
+    <string name="pref_help_title" msgid="6871558837025010641">"Bantuan"</string>
+    <string name="brightness_percentage_label" msgid="7391554573977867369">"Kecerahan <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+    <string name="music_volume_percentage_label" msgid="398635599662604706">"Kelantangan muzik <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+</resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-my/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-my/strings.xml
new file mode 100644
index 0000000..1097f87
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-my/strings.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="accessibility_menu_service_name" msgid="730136711554740131">"အများသုံးနိုင်မှု မီနူး"</string>
+    <string name="accessibility_menu_intro" msgid="3164193281544042394">"‘အများသုံးနိုင်မှု မီနူး’ တွင် သင့်စက်ပစ္စည်းကို စီမံရန် ကြီးမားသည့်ဖန်သားပြင်မီနူး ပါဝင်သည်။ စက်ပစ္စည်းလော့ခ်ချခြင်း၊ အသံအတိုးအကျယ်နှင့် အလင်းအမှောင် ထိန်းချုပ်ခြင်း၊ ဖန်သားပြင်ဓာတ်ပုံရိုက်ခြင်း စသည်တို့ ပြုလုပ်နိုင်သည်။"</string>
+    <string name="assistant_label" msgid="6796392082252272356">"Assistant"</string>
+    <string name="assistant_utterance" msgid="65509599221141377">"Assistant"</string>
+    <string name="a11y_settings_label" msgid="3977714687248445050">"အများသုံးနိုင်မှု ဆက်တင်များ"</string>
+    <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="quick_settings_label" msgid="2999117381487601865">"အမြန် ဆက်တင်များ"</string>
+    <string name="notifications_label" msgid="6829741046963013567">"အကြောင်းကြားချက်များ"</string>
+    <string name="screenshot_label" msgid="863978141223970162">"ဖန်သားပြင်ဓာတ်ပုံ"</string>
+    <string name="screenshot_utterance" msgid="1430760563401895074">"ဖန်သားပြင်ဓာတ်ပုံ ရိုက်ရန်"</string>
+    <string name="volume_up_label" msgid="8592766918780362870">"အသံချဲ့ခလုတ်"</string>
+    <string name="volume_down_label" msgid="8574981863656447346">"အသံတိုးခလုတ်"</string>
+    <string name="brightness_up_label" msgid="8010753822854544846">"တောက်ပမှု တိုးမြှင့်ရန်"</string>
+    <string name="brightness_down_label" msgid="7115662941913272072">"တောက်ပမှု လျှော့ချရန်"</string>
+    <string name="previous_button_content_description" msgid="840869171117765966">"ယခင် မျက်နှာပြင်သို့ သွားရန်"</string>
+    <string name="next_button_content_description" msgid="6810058269847364406">"နောက်မျက်နှာပြင်သို့ ဆက်သွားရန်"</string>
+    <string name="accessibility_menu_description" msgid="4458354794093858297">"‘အများသုံးနိုင်မှု မီနူး’ တွင် သင့်စက်ပစ္စည်းကို စီမံရန် ကြီးမားသည့်ဖန်သားပြင်မီနူး ပါဝင်သည်။ စက်ပစ္စည်းလော့ခ်ချခြင်း၊ အသံအတိုးအကျယ်နှင့် အလင်းအမှောင် ထိန်းချုပ်ခြင်း၊ ဖန်သားပြင်ဓာတ်ပုံရိုက်ခြင်း စသည်တို့ ပြုလုပ်နိုင်သည်။"</string>
+    <string name="accessibility_menu_summary" msgid="340071398148208130">"ကြီးမားသည့်မီနူးဖြင့် စက်ပစ္စည်းကို စီမံနိုင်သည်"</string>
+    <string name="accessibility_menu_settings_name" msgid="1716888058785672611">"အများသုံးနိုင်မှု မီနူးဆက်တင်များ"</string>
+    <string name="accessibility_menu_large_buttons_title" msgid="8978499601044961736">"ခလုတ်အကြီးများ"</string>
+    <string name="accessibility_menu_large_buttons_summary" msgid="236873938502785311">"\'အများသုံးနိုင်မှု မီနူး ခလုတ်များ\' ၏ အရွယ်အစားတိုးရန်"</string>
+    <string name="pref_help_title" msgid="6871558837025010641">"အကူအညီ"</string>
+    <string name="brightness_percentage_label" msgid="7391554573977867369">"တောက်ပမှု <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+    <string name="music_volume_percentage_label" msgid="398635599662604706">"ဂီတသံ အတိုးအကျယ် <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+</resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-nb/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-nb/strings.xml
new file mode 100644
index 0000000..a3e7ab0
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-nb/strings.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="accessibility_menu_service_name" msgid="730136711554740131">"Tilgjengelighetsmeny"</string>
+    <string name="accessibility_menu_intro" msgid="3164193281544042394">"Med tilgjengelighetsmenyen får du en stor meny på skjermen for å kontrollere enheten. Du kan låse enheten, kontrollere volum og lysstyrke, ta skjermdumper med mer."</string>
+    <string name="assistant_label" msgid="6796392082252272356">"Assistent"</string>
+    <string name="assistant_utterance" msgid="65509599221141377">"Assistent"</string>
+    <string name="a11y_settings_label" msgid="3977714687248445050">"Tilgjengelighetsinnstillinger"</string>
+    <string name="power_label" msgid="7699720321491287839">"Av/på"</string>
+    <string name="power_utterance" msgid="7444296686402104807">"Av/på-alternativer"</string>
+    <string name="recent_apps_label" msgid="6583276995616385847">"Nylige apper"</string>
+    <string name="lockscreen_label" msgid="648347953557887087">"Låseskjerm"</string>
+    <string name="quick_settings_label" msgid="2999117381487601865">"Hurtiginnstillinger"</string>
+    <string name="notifications_label" msgid="6829741046963013567">"Varsler"</string>
+    <string name="screenshot_label" msgid="863978141223970162">"Skjermdump"</string>
+    <string name="screenshot_utterance" msgid="1430760563401895074">"Ta skjermdump"</string>
+    <string name="volume_up_label" msgid="8592766918780362870">"Volum opp"</string>
+    <string name="volume_down_label" msgid="8574981863656447346">"Volum ned"</string>
+    <string name="brightness_up_label" msgid="8010753822854544846">"Lysstyrke opp"</string>
+    <string name="brightness_down_label" msgid="7115662941913272072">"Lysstyrke ned"</string>
+    <string name="previous_button_content_description" msgid="840869171117765966">"Gå til forrige skjerm"</string>
+    <string name="next_button_content_description" msgid="6810058269847364406">"Gå til neste skjerm"</string>
+    <string name="accessibility_menu_description" msgid="4458354794093858297">"Med tilgjengelighetsmenyen får du en stor meny på skjermen for å kontrollere enheten. Du kan låse enheten, kontrollere volum og lysstyrke, ta skjermdumper med mer."</string>
+    <string name="accessibility_menu_summary" msgid="340071398148208130">"Kontroller enheten med en stor meny"</string>
+    <string name="accessibility_menu_settings_name" msgid="1716888058785672611">"Innstillinger for Tilgjengelighetsmeny"</string>
+    <string name="accessibility_menu_large_buttons_title" msgid="8978499601044961736">"Store knapper"</string>
+    <string name="accessibility_menu_large_buttons_summary" msgid="236873938502785311">"Få større knapper i Tilgjengelighetsmeny"</string>
+    <string name="pref_help_title" msgid="6871558837025010641">"Hjelp"</string>
+    <string name="brightness_percentage_label" msgid="7391554573977867369">"Lysstyrke <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+    <string name="music_volume_percentage_label" msgid="398635599662604706">"Musikkvolum <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+</resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-ne/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-ne/strings.xml
new file mode 100644
index 0000000..10e36b8
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-ne/strings.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="accessibility_menu_service_name" msgid="730136711554740131">"एक्सेसिबिलिटी मेनु"</string>
+    <string name="accessibility_menu_intro" msgid="3164193281544042394">"तपाईं आफ्नो डिभाइस नियन्त्रण गर्न एक्सेसिबिलिटी मेनुमा गई ठुलो अन स्क्रिन मेनु खोल्न सक्नुहुन्छ। तपाईं आफ्नो डिभाइस लक गर्न, भोल्युम र चमक नियन्त्रण गर्न, स्क्रिनसटहरू लिन र थप कार्यहरू गर्न सक्नुहुन्छ।"</string>
+    <string name="assistant_label" msgid="6796392082252272356">"सहायक"</string>
+    <string name="assistant_utterance" msgid="65509599221141377">"सहायक"</string>
+    <string name="a11y_settings_label" msgid="3977714687248445050">"एक्सेसिबिलिटी सेटिङ"</string>
+    <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="quick_settings_label" msgid="2999117381487601865">"द्रुत सेटिङहरू"</string>
+    <string name="notifications_label" msgid="6829741046963013567">"सूचनाहरू"</string>
+    <string name="screenshot_label" msgid="863978141223970162">"स्क्रिनसट"</string>
+    <string name="screenshot_utterance" msgid="1430760563401895074">"स्क्रिनसट लिनुहोस्"</string>
+    <string name="volume_up_label" msgid="8592766918780362870">"भोल्युम बढाउनुहोस्"</string>
+    <string name="volume_down_label" msgid="8574981863656447346">"भोल्युम कम गर्नुहोस्"</string>
+    <string name="brightness_up_label" msgid="8010753822854544846">"उज्यालो बढाउनुहोस्"</string>
+    <string name="brightness_down_label" msgid="7115662941913272072">"उज्यालो कम गर्नुहोस्"</string>
+    <string name="previous_button_content_description" msgid="840869171117765966">"अघिल्लो स्क्रिनमा जानुहोस्"</string>
+    <string name="next_button_content_description" msgid="6810058269847364406">"अर्को स्क्रिनमा जानुहोस्"</string>
+    <string name="accessibility_menu_description" msgid="4458354794093858297">"तपाईं आफ्नो डिभाइस नियन्त्रण गर्न एक्सेसिबिलिटी मेनुमा गई ठुलो अन स्क्रिन मेनु खोल्न सक्नुहुन्छ। तपाईं आफ्नो डिभाइस लक गर्न, भोल्युम र चमक नियन्त्रण गर्न, स्क्रिनसटहरू लिन र थप कार्यहरू गर्न सक्नुहुन्छ।"</string>
+    <string name="accessibility_menu_summary" msgid="340071398148208130">"ठुलो मेनुको सहायताले डिभाइस नियन्त्रण गर्नुहोस्"</string>
+    <string name="accessibility_menu_settings_name" msgid="1716888058785672611">"एक्सेसिबिलिटी मेनुसम्बन्धी सेटिङ"</string>
+    <string name="accessibility_menu_large_buttons_title" msgid="8978499601044961736">"ठूला बटनहरू"</string>
+    <string name="accessibility_menu_large_buttons_summary" msgid="236873938502785311">"एक्सेसिबिलिटी मेनुका बटनहरूको आकार बढाउनुहोस्"</string>
+    <string name="pref_help_title" msgid="6871558837025010641">"मद्दत"</string>
+    <string name="brightness_percentage_label" msgid="7391554573977867369">"चमक <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+    <string name="music_volume_percentage_label" msgid="398635599662604706">"सङ्गीतको भोल्युम <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+</resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-nl/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-nl/strings.xml
new file mode 100644
index 0000000..a8d6a0b
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-nl/strings.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="accessibility_menu_service_name" msgid="730136711554740131">"Toegankelijkheids­menu"</string>
+    <string name="accessibility_menu_intro" msgid="3164193281544042394">"Het toegankelijkheidsmenu is een groot menu op het scherm waarmee je je apparaat kunt bedienen. Je kunt onder meer je apparaat vergrendelen, het volume en de helderheid beheren en screenshots maken."</string>
+    <string name="assistant_label" msgid="6796392082252272356">"Assistent"</string>
+    <string name="assistant_utterance" msgid="65509599221141377">"Assistent"</string>
+    <string name="a11y_settings_label" msgid="3977714687248445050">"Instellingen voor toegankelijkheid"</string>
+    <string name="power_label" msgid="7699720321491287839">"Voeding"</string>
+    <string name="power_utterance" msgid="7444296686402104807">"Voedingsopties"</string>
+    <string name="recent_apps_label" msgid="6583276995616385847">"Recente apps"</string>
+    <string name="lockscreen_label" msgid="648347953557887087">"Scherm vergrendelen"</string>
+    <string name="quick_settings_label" msgid="2999117381487601865">"Snelle instellingen"</string>
+    <string name="notifications_label" msgid="6829741046963013567">"Meldingen"</string>
+    <string name="screenshot_label" msgid="863978141223970162">"Screenshot"</string>
+    <string name="screenshot_utterance" msgid="1430760563401895074">"Screenshot maken"</string>
+    <string name="volume_up_label" msgid="8592766918780362870">"Volume omhoog"</string>
+    <string name="volume_down_label" msgid="8574981863656447346">"Volume omlaag"</string>
+    <string name="brightness_up_label" msgid="8010753822854544846">"Helderheid verhogen"</string>
+    <string name="brightness_down_label" msgid="7115662941913272072">"Helderheid verlagen"</string>
+    <string name="previous_button_content_description" msgid="840869171117765966">"Ga naar vorig scherm"</string>
+    <string name="next_button_content_description" msgid="6810058269847364406">"Ga naar volgend scherm"</string>
+    <string name="accessibility_menu_description" msgid="4458354794093858297">"Het toegankelijkheidsmenu is een groot menu op het scherm waarmee je je apparaat kunt bedienen. Je kunt onder meer je apparaat vergrendelen, het volume en de helderheid aanpassen en screenshots maken."</string>
+    <string name="accessibility_menu_summary" msgid="340071398148208130">"Bedien apparaat via groot menu"</string>
+    <string name="accessibility_menu_settings_name" msgid="1716888058785672611">"Instellingen toegankelijkheidsmenu"</string>
+    <string name="accessibility_menu_large_buttons_title" msgid="8978499601044961736">"Grote knoppen"</string>
+    <string name="accessibility_menu_large_buttons_summary" msgid="236873938502785311">"Vergroot knoppen in het toegankelijkheidsmenu"</string>
+    <string name="pref_help_title" msgid="6871558837025010641">"Hulp"</string>
+    <string name="brightness_percentage_label" msgid="7391554573977867369">"Helderheid <xliff:g id="PERCENTAGE">%1$s</xliff:g>%%"</string>
+    <string name="music_volume_percentage_label" msgid="398635599662604706">"Muziekvolume <xliff:g id="PERCENTAGE">%1$s</xliff:g>%%"</string>
+</resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-or/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-or/strings.xml
new file mode 100644
index 0000000..3a40b9f
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-or/strings.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="accessibility_menu_service_name" msgid="730136711554740131">"ଆକ୍ସେସିବିଲିଟୀ ମେନୁ"</string>
+    <string name="accessibility_menu_intro" msgid="3164193281544042394">"ଆପଣଙ୍କ ଡିଭାଇସକୁ ନିୟନ୍ତ୍ରଣ କରିବା ପାଇଁ ଆକ୍ସେସିବିଲିଟୀ ମେନୁ ଏକ ବଡ଼ ଅନ-ସ୍କ୍ରିନ ମେନୁ ପ୍ରଦାନ କରେ। ଆପଣ ଆପଣଙ୍କ ଡିଭାଇସକୁ ଲକ କରିପାରିବେ, ଭଲ୍ୟୁମ ଓ ଉଜ୍ଜ୍ୱଳତାକୁ ନିୟନ୍ତ୍ରଣ କରିପାରିବେ, ସ୍କ୍ରିନସଟ ନେଇପାରିବେ ଏବଂ ଆହୁରି ଅନେକ କିଛି କରିପାରିବେ।"</string>
+    <string name="assistant_label" msgid="6796392082252272356">"Assistant"</string>
+    <string name="assistant_utterance" msgid="65509599221141377">"Assistant"</string>
+    <string name="a11y_settings_label" msgid="3977714687248445050">"ଆକ୍ସେସିବିଲିଟୀ ସେଟିଂସ"</string>
+    <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="quick_settings_label" msgid="2999117381487601865">"କ୍ୱିକ ସେଟିଂସ"</string>
+    <string name="notifications_label" msgid="6829741046963013567">"ବିଜ୍ଞପ୍ତି"</string>
+    <string name="screenshot_label" msgid="863978141223970162">"ସ୍କ୍ରିନସଟ"</string>
+    <string name="screenshot_utterance" msgid="1430760563401895074">"ସ୍କ୍ରୀନଶଟ୍‌ ନିଅନ୍ତୁ"</string>
+    <string name="volume_up_label" msgid="8592766918780362870">"ଭଲ୍ୟୁମ୍ ବଢ଼ାନ୍ତୁ"</string>
+    <string name="volume_down_label" msgid="8574981863656447346">"ଭଲ୍ୟୁମ୍ କମାନ୍ତୁ"</string>
+    <string name="brightness_up_label" msgid="8010753822854544846">"ଉଜ୍ଜ୍ୱଳତା ବଢ଼ାନ୍ତୁ"</string>
+    <string name="brightness_down_label" msgid="7115662941913272072">"ଉଜ୍ଜ୍ୱଳତା କମ କରନ୍ତୁ"</string>
+    <string name="previous_button_content_description" msgid="840869171117765966">"ପୂର୍ବବର୍ତ୍ତୀ ସ୍କ୍ରିନ୍‍କୁ ଯାଆନ୍ତୁ"</string>
+    <string name="next_button_content_description" msgid="6810058269847364406">"ପରବର୍ତ୍ତୀ ସ୍କ୍ରିନ୍‍କୁ ଯାଆନ୍ତୁ"</string>
+    <string name="accessibility_menu_description" msgid="4458354794093858297">"ଆପଣଙ୍କ ଡିଭାଇସକୁ ନିୟନ୍ତ୍ରଣ କରିବା ପାଇଁ ଆକ୍ସେସିବିଲିଟୀ ମେନୁ ଏକ ବଡ଼ ଅନ-ସ୍କ୍ରିନ ମେନୁ ପ୍ରଦାନ କରେ। ଆପଣ ଆପଣଙ୍କ ଡିଭାଇସକୁ ଲକ କରିପାରିବେ, ଭଲ୍ୟୁମ ଓ ଉଜ୍ଜ୍ୱଳତାକୁ ନିୟନ୍ତ୍ରଣ କରିପାରିବେ, ସ୍କ୍ରିନସଟ ନେଇପାରିବେ ଏବଂ ଆହୁରି ଅନେକ କିଛି କରିପାରିବେ।"</string>
+    <string name="accessibility_menu_summary" msgid="340071398148208130">"ବଡ଼ ମେନୁ ମାଧ୍ୟମରେ ଡିଭାଇସକୁ ନିୟନ୍ତ୍ରଣ କରନ୍ତୁ"</string>
+    <string name="accessibility_menu_settings_name" msgid="1716888058785672611">"ଆକ୍ସେସିବିଲିଟୀ ମେନୁ ସେଟିଂସ"</string>
+    <string name="accessibility_menu_large_buttons_title" msgid="8978499601044961736">"ବଡ଼ ବଟନ"</string>
+    <string name="accessibility_menu_large_buttons_summary" msgid="236873938502785311">"ଆକ୍ସେସିବିଲିଟୀ ମେନୁ ବଟନର ଆକାର ବଢ଼ାନ୍ତୁ"</string>
+    <string name="pref_help_title" msgid="6871558837025010641">"ସାହାଯ୍ୟ"</string>
+    <string name="brightness_percentage_label" msgid="7391554573977867369">"ଉଜ୍ଜ୍ୱଳତା <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+    <string name="music_volume_percentage_label" msgid="398635599662604706">"ମ୍ୟୁଜିକର ଭଲ୍ୟୁମ <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+</resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-pa/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-pa/strings.xml
new file mode 100644
index 0000000..bfe0980
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-pa/strings.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="accessibility_menu_service_name" msgid="730136711554740131">"ਪਹੁੰਚਯੋਗਤਾ ਮੀਨੂ"</string>
+    <string name="accessibility_menu_intro" msgid="3164193281544042394">"ਤੁਹਾਡੇ ਡੀਵਾਈਸ ਨੂੰ ਕੰਟਰੋਲ ਕਰਨ ਲਈ ਪਹੁੰਚਯੋਗਤਾ ਮੀਨੂ ਇੱਕ ਵੱਡਾ ਆਨ-ਸਕ੍ਰੀਨ ਮੀਨੂ ਮੁਹੱਈਆ ਕਰਦਾ ਹੈ। ਤੁਸੀਂ ਆਪਣੇ ਡੀਵਾਈਸ ਨੂੰ ਲਾਕ ਕਰ ਸਕਦੇ ਹੋ, ਅਵਾਜ਼ ਅਤੇ ਚਮਕ ਨੂੰ ਕੰਟਰੋਲ ਕਰ ਸਕਦੇ ਹੋ, ਸਕ੍ਰੀਨਸ਼ਾਟ ਲੈ ਸਕਦੇ ਹੋ ਅਤੇ ਹੋਰ ਵੀ ਬਹੁਤ ਕੁਝ।"</string>
+    <string name="assistant_label" msgid="6796392082252272356">"Assistant"</string>
+    <string name="assistant_utterance" msgid="65509599221141377">"Assistant"</string>
+    <string name="a11y_settings_label" msgid="3977714687248445050">"ਪਹੁੰਚਯੋਗਤਾ ਸੈਟਿੰਗਾਂ"</string>
+    <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="quick_settings_label" msgid="2999117381487601865">"ਤਤਕਾਲ ਸੈਟਿੰਗਾਂ"</string>
+    <string name="notifications_label" msgid="6829741046963013567">"ਸੂਚਨਾਵਾਂ"</string>
+    <string name="screenshot_label" msgid="863978141223970162">"ਸਕ੍ਰੀਨਸ਼ਾਟ"</string>
+    <string name="screenshot_utterance" msgid="1430760563401895074">"ਸਕ੍ਰੀਨਸ਼ਾਟ ਲਓ"</string>
+    <string name="volume_up_label" msgid="8592766918780362870">"ਅਵਾਜ਼ ਵਧਾਓ"</string>
+    <string name="volume_down_label" msgid="8574981863656447346">"ਅਵਾਜ਼ ਘਟਾਓ"</string>
+    <string name="brightness_up_label" msgid="8010753822854544846">"ਚਮਕ ਵਧਾਓ"</string>
+    <string name="brightness_down_label" msgid="7115662941913272072">"ਚਮਕ ਘਟਾਓ"</string>
+    <string name="previous_button_content_description" msgid="840869171117765966">"ਪਿਛਲੀ ਸਕ੍ਰੀਨ \'ਤੇ ਜਾਓ"</string>
+    <string name="next_button_content_description" msgid="6810058269847364406">"ਅਗਲੀ ਸਕ੍ਰੀਨ \'ਤੇ ਜਾਓ"</string>
+    <string name="accessibility_menu_description" msgid="4458354794093858297">"ਤੁਹਾਡੇ ਡੀਵਾਈਸ ਨੂੰ ਕੰਟਰੋਲ ਕਰਨ ਲਈ ਪਹੁੰਚਯੋਗਤਾ ਮੀਨੂ ਇੱਕ ਵੱਡਾ ਸਕ੍ਰੀਨ-ਉੱਪਰਲਾ ਮੀਨੂ ਮੁਹੱਈਆ ਕਰਦਾ ਹੈ। ਤੁਸੀਂ ਆਪਣੇ ਡੀਵਾਈਸ ਨੂੰ ਲਾਕ ਕਰ ਸਕਦੇ ਹੋ, ਅਵਾਜ਼ ਅਤੇ ਚਮਕ ਨੂੰ ਕੰਟਰੋਲ ਕਰ ਸਕਦੇ ਹੋ, ਸਕ੍ਰੀਨਸ਼ਾਟ ਲੈ ਸਕਦੇ ਹੋ ਅਤੇ ਹੋਰ ਵੀ ਬਹੁਤ ਕੁਝ।"</string>
+    <string name="accessibility_menu_summary" msgid="340071398148208130">"ਵੱਡੇ ਮੀਨੂ ਰਾਹੀਂ ਡੀਵਾਈਸ ਨੂੰ ਕੰਟਰੋਲ ਕਰੋ"</string>
+    <string name="accessibility_menu_settings_name" msgid="1716888058785672611">"ਪਹੁੰਚਯੋਗਤਾ ਮੀਨੂ ਸੈਟਿੰਗਾਂ"</string>
+    <string name="accessibility_menu_large_buttons_title" msgid="8978499601044961736">"ਵੱਡੇ ਬਟਨ"</string>
+    <string name="accessibility_menu_large_buttons_summary" msgid="236873938502785311">"\'ਪਹੁੰਚਯੋਗਤਾ ਮੀਨੂ\' ਬਟਨਾਂ ਦਾ ਆਕਾਰ ਵਧਾਓ"</string>
+    <string name="pref_help_title" msgid="6871558837025010641">"ਮਦਦ"</string>
+    <string name="brightness_percentage_label" msgid="7391554573977867369">"ਚਮਕ <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+    <string name="music_volume_percentage_label" msgid="398635599662604706">"ਸੰਗੀਤ ਦੀ ਅਵਾਜ਼ <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+</resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-pl/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-pl/strings.xml
new file mode 100644
index 0000000..69a0834
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-pl/strings.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="accessibility_menu_service_name" msgid="730136711554740131">"Menu ułatwień dostępu"</string>
+    <string name="accessibility_menu_intro" msgid="3164193281544042394">"Menu ułatwień dostępu to duże menu ekranowe, które umożliwia obsługę urządzenia. Możesz zablokować urządzenie, zwiększyć lub zmniejszyć głośność oraz jasność, zrobić zrzut ekranu i wykonać inne działania."</string>
+    <string name="assistant_label" msgid="6796392082252272356">"Asystent"</string>
+    <string name="assistant_utterance" msgid="65509599221141377">"Asystent"</string>
+    <string name="a11y_settings_label" msgid="3977714687248445050">"Ustawienia ułatwień dostępu"</string>
+    <string name="power_label" msgid="7699720321491287839">"Zasilanie"</string>
+    <string name="power_utterance" msgid="7444296686402104807">"Opcje przycisku zasilania"</string>
+    <string name="recent_apps_label" msgid="6583276995616385847">"Ostatnie aplikacje"</string>
+    <string name="lockscreen_label" msgid="648347953557887087">"Ekran blokady"</string>
+    <string name="quick_settings_label" msgid="2999117381487601865">"Szybkie ustawienia"</string>
+    <string name="notifications_label" msgid="6829741046963013567">"Powiadomienia"</string>
+    <string name="screenshot_label" msgid="863978141223970162">"Zrzut ekranu"</string>
+    <string name="screenshot_utterance" msgid="1430760563401895074">"Zapisz zrzut ekranu"</string>
+    <string name="volume_up_label" msgid="8592766918780362870">"Zwiększ głośność"</string>
+    <string name="volume_down_label" msgid="8574981863656447346">"Zmniejsz głośność"</string>
+    <string name="brightness_up_label" msgid="8010753822854544846">"Zwiększ jasność"</string>
+    <string name="brightness_down_label" msgid="7115662941913272072">"Zmniejsz jasność"</string>
+    <string name="previous_button_content_description" msgid="840869171117765966">"Przejdź do poprzedniego ekranu"</string>
+    <string name="next_button_content_description" msgid="6810058269847364406">"Przejdź do następnego ekranu"</string>
+    <string name="accessibility_menu_description" msgid="4458354794093858297">"Menu ułatwień dostępu to duże menu ekranowe, które umożliwia obsługę urządzenia. Możesz zablokować urządzenie, zwiększyć lub zmniejszyć głośność oraz jasność, zrobić zrzut ekranu i wykonać inne działania."</string>
+    <string name="accessibility_menu_summary" msgid="340071398148208130">"Obsługuj urządzenie za pomocą dużego menu"</string>
+    <string name="accessibility_menu_settings_name" msgid="1716888058785672611">"Ustawienia menu ułatwień dostępu"</string>
+    <string name="accessibility_menu_large_buttons_title" msgid="8978499601044961736">"Duże przyciski"</string>
+    <string name="accessibility_menu_large_buttons_summary" msgid="236873938502785311">"Zwiększ rozmiar przycisków w menu Ułatwienia dostępu"</string>
+    <string name="pref_help_title" msgid="6871558837025010641">"Pomoc"</string>
+    <string name="brightness_percentage_label" msgid="7391554573977867369">"Jasność: <xliff:g id="PERCENTAGE">%1$s</xliff:g>%%"</string>
+    <string name="music_volume_percentage_label" msgid="398635599662604706">"Głośność muzyki: <xliff:g id="PERCENTAGE">%1$s</xliff:g>%%"</string>
+</resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-pt-rBR/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-pt-rBR/strings.xml
new file mode 100644
index 0000000..160d310
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-pt-rBR/strings.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="accessibility_menu_service_name" msgid="730136711554740131">"Menu de acessibilidade"</string>
+    <string name="accessibility_menu_intro" msgid="3164193281544042394">"\"Acessibilidade\" é um menu grande mostrado na tela para controlar seu dispositivo. Você pode bloquear o dispositivo, controlar o volume e o brilho, fazer capturas de tela e muito mais."</string>
+    <string name="assistant_label" msgid="6796392082252272356">"Assistente"</string>
+    <string name="assistant_utterance" msgid="65509599221141377">"Google Assistente"</string>
+    <string name="a11y_settings_label" msgid="3977714687248445050">"Configurações de acessibilidade"</string>
+    <string name="power_label" msgid="7699720321491287839">"Liga/desliga"</string>
+    <string name="power_utterance" msgid="7444296686402104807">"Opções do botão liga/desliga"</string>
+    <string name="recent_apps_label" msgid="6583276995616385847">"Apps recentes"</string>
+    <string name="lockscreen_label" msgid="648347953557887087">"Tela de bloqueio"</string>
+    <string name="quick_settings_label" msgid="2999117381487601865">"Config. rápida"</string>
+    <string name="notifications_label" msgid="6829741046963013567">"Notificações"</string>
+    <string name="screenshot_label" msgid="863978141223970162">"Captura de tela"</string>
+    <string name="screenshot_utterance" msgid="1430760563401895074">"Fazer uma captura de tela"</string>
+    <string name="volume_up_label" msgid="8592766918780362870">"Aumentar volume"</string>
+    <string name="volume_down_label" msgid="8574981863656447346">"Diminuir volume"</string>
+    <string name="brightness_up_label" msgid="8010753822854544846">"Aumentar o brilho"</string>
+    <string name="brightness_down_label" msgid="7115662941913272072">"Diminuir o brilho"</string>
+    <string name="previous_button_content_description" msgid="840869171117765966">"Ir para tela anterior"</string>
+    <string name="next_button_content_description" msgid="6810058269847364406">"Ir para a próxima tela"</string>
+    <string name="accessibility_menu_description" msgid="4458354794093858297">"\"Acessibilidade\" é um grande menu mostrado na tela para controlar seu dispositivo. Você pode bloquear o dispositivo, controlar o volume e o brilho, fazer capturas de tela e muito mais."</string>
+    <string name="accessibility_menu_summary" msgid="340071398148208130">"Controlar o dispositivo com o menu grande"</string>
+    <string name="accessibility_menu_settings_name" msgid="1716888058785672611">"Config. do menu de acessibilidade"</string>
+    <string name="accessibility_menu_large_buttons_title" msgid="8978499601044961736">"Botões grandes"</string>
+    <string name="accessibility_menu_large_buttons_summary" msgid="236873938502785311">"Aumentar o tamanho dos botões do menu de acessibilidade"</string>
+    <string name="pref_help_title" msgid="6871558837025010641">"Ajuda"</string>
+    <string name="brightness_percentage_label" msgid="7391554573977867369">"Brilho: <xliff:g id="PERCENTAGE">%1$s</xliff:g>%%"</string>
+    <string name="music_volume_percentage_label" msgid="398635599662604706">"Volume da música: <xliff:g id="PERCENTAGE">%1$s</xliff:g>%%"</string>
+</resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-pt-rPT/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-pt-rPT/strings.xml
new file mode 100644
index 0000000..44aff75
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-pt-rPT/strings.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="accessibility_menu_service_name" msgid="730136711554740131">"Menu Acessibilidade"</string>
+    <string name="accessibility_menu_intro" msgid="3164193281544042394">"O menu Acessibilidade disponibiliza um menu grande no ecrã para controlar o dispositivo. Pode bloquear o dispositivo, controlar o volume e o brilho, fazer capturas de ecrã e muito mais."</string>
+    <string name="assistant_label" msgid="6796392082252272356">"Assistente"</string>
+    <string name="assistant_utterance" msgid="65509599221141377">"Assistente"</string>
+    <string name="a11y_settings_label" msgid="3977714687248445050">"Definições de acessibilidade"</string>
+    <string name="power_label" msgid="7699720321491287839">"Ligar/desligar"</string>
+    <string name="power_utterance" msgid="7444296686402104807">"Opções para ligar/desligar"</string>
+    <string name="recent_apps_label" msgid="6583276995616385847">"Apps recentes"</string>
+    <string name="lockscreen_label" msgid="648347953557887087">"Ecrã de bloqueio"</string>
+    <string name="quick_settings_label" msgid="2999117381487601865">"Definições rápidas"</string>
+    <string name="notifications_label" msgid="6829741046963013567">"Notificações"</string>
+    <string name="screenshot_label" msgid="863978141223970162">"Captura de ecrã"</string>
+    <string name="screenshot_utterance" msgid="1430760563401895074">"Fazer captura de ecrã"</string>
+    <string name="volume_up_label" msgid="8592766918780362870">"Aumentar volume"</string>
+    <string name="volume_down_label" msgid="8574981863656447346">"Diminuir volume"</string>
+    <string name="brightness_up_label" msgid="8010753822854544846">"Aumentar brilho"</string>
+    <string name="brightness_down_label" msgid="7115662941913272072">"Diminuir brilho"</string>
+    <string name="previous_button_content_description" msgid="840869171117765966">"Ir para o ecrã anterior"</string>
+    <string name="next_button_content_description" msgid="6810058269847364406">"Ir para o ecrã seguinte"</string>
+    <string name="accessibility_menu_description" msgid="4458354794093858297">"O menu Acessibilidade disponibiliza um menu grande no ecrã para controlar o dispositivo. Pode bloquear o dispositivo, controlar o volume e o brilho, fazer capturas de ecrã e muito mais."</string>
+    <string name="accessibility_menu_summary" msgid="340071398148208130">"Controle o dispositivo através do menu grande"</string>
+    <string name="accessibility_menu_settings_name" msgid="1716888058785672611">"Definições do menu Acessibilidade"</string>
+    <string name="accessibility_menu_large_buttons_title" msgid="8978499601044961736">"Botões grandes"</string>
+    <string name="accessibility_menu_large_buttons_summary" msgid="236873938502785311">"Aumentar o tamanho dos botões do menu Acessibilidade"</string>
+    <string name="pref_help_title" msgid="6871558837025010641">"Ajuda"</string>
+    <string name="brightness_percentage_label" msgid="7391554573977867369">"Brilho: <xliff:g id="PERCENTAGE">%1$s</xliff:g>%%"</string>
+    <string name="music_volume_percentage_label" msgid="398635599662604706">"Volume da música: <xliff:g id="PERCENTAGE">%1$s</xliff:g>%%"</string>
+</resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-pt/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-pt/strings.xml
new file mode 100644
index 0000000..160d310
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-pt/strings.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="accessibility_menu_service_name" msgid="730136711554740131">"Menu de acessibilidade"</string>
+    <string name="accessibility_menu_intro" msgid="3164193281544042394">"\"Acessibilidade\" é um menu grande mostrado na tela para controlar seu dispositivo. Você pode bloquear o dispositivo, controlar o volume e o brilho, fazer capturas de tela e muito mais."</string>
+    <string name="assistant_label" msgid="6796392082252272356">"Assistente"</string>
+    <string name="assistant_utterance" msgid="65509599221141377">"Google Assistente"</string>
+    <string name="a11y_settings_label" msgid="3977714687248445050">"Configurações de acessibilidade"</string>
+    <string name="power_label" msgid="7699720321491287839">"Liga/desliga"</string>
+    <string name="power_utterance" msgid="7444296686402104807">"Opções do botão liga/desliga"</string>
+    <string name="recent_apps_label" msgid="6583276995616385847">"Apps recentes"</string>
+    <string name="lockscreen_label" msgid="648347953557887087">"Tela de bloqueio"</string>
+    <string name="quick_settings_label" msgid="2999117381487601865">"Config. rápida"</string>
+    <string name="notifications_label" msgid="6829741046963013567">"Notificações"</string>
+    <string name="screenshot_label" msgid="863978141223970162">"Captura de tela"</string>
+    <string name="screenshot_utterance" msgid="1430760563401895074">"Fazer uma captura de tela"</string>
+    <string name="volume_up_label" msgid="8592766918780362870">"Aumentar volume"</string>
+    <string name="volume_down_label" msgid="8574981863656447346">"Diminuir volume"</string>
+    <string name="brightness_up_label" msgid="8010753822854544846">"Aumentar o brilho"</string>
+    <string name="brightness_down_label" msgid="7115662941913272072">"Diminuir o brilho"</string>
+    <string name="previous_button_content_description" msgid="840869171117765966">"Ir para tela anterior"</string>
+    <string name="next_button_content_description" msgid="6810058269847364406">"Ir para a próxima tela"</string>
+    <string name="accessibility_menu_description" msgid="4458354794093858297">"\"Acessibilidade\" é um grande menu mostrado na tela para controlar seu dispositivo. Você pode bloquear o dispositivo, controlar o volume e o brilho, fazer capturas de tela e muito mais."</string>
+    <string name="accessibility_menu_summary" msgid="340071398148208130">"Controlar o dispositivo com o menu grande"</string>
+    <string name="accessibility_menu_settings_name" msgid="1716888058785672611">"Config. do menu de acessibilidade"</string>
+    <string name="accessibility_menu_large_buttons_title" msgid="8978499601044961736">"Botões grandes"</string>
+    <string name="accessibility_menu_large_buttons_summary" msgid="236873938502785311">"Aumentar o tamanho dos botões do menu de acessibilidade"</string>
+    <string name="pref_help_title" msgid="6871558837025010641">"Ajuda"</string>
+    <string name="brightness_percentage_label" msgid="7391554573977867369">"Brilho: <xliff:g id="PERCENTAGE">%1$s</xliff:g>%%"</string>
+    <string name="music_volume_percentage_label" msgid="398635599662604706">"Volume da música: <xliff:g id="PERCENTAGE">%1$s</xliff:g>%%"</string>
+</resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-ro/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-ro/strings.xml
new file mode 100644
index 0000000..6edbb77
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-ro/strings.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="accessibility_menu_service_name" msgid="730136711554740131">"Meniul Accesibilitate"</string>
+    <string name="accessibility_menu_intro" msgid="3164193281544042394">"Meniul Accesibilitate este un meniu mare afișat pe ecran, cu ajutorul căruia îți controlezi dispozitivul. Poți să blochezi dispozitivul, să ajustezi volumul și luminozitatea, să faci capturi de ecran și multe altele."</string>
+    <string name="assistant_label" msgid="6796392082252272356">"Asistent"</string>
+    <string name="assistant_utterance" msgid="65509599221141377">"Asistent"</string>
+    <string name="a11y_settings_label" msgid="3977714687248445050">"Setări de accesibilitate"</string>
+    <string name="power_label" msgid="7699720321491287839">"Alimentare"</string>
+    <string name="power_utterance" msgid="7444296686402104807">"Opțiuni pentru alimentare"</string>
+    <string name="recent_apps_label" msgid="6583276995616385847">"Aplicații recente"</string>
+    <string name="lockscreen_label" msgid="648347953557887087">"Ecran de blocare"</string>
+    <string name="quick_settings_label" msgid="2999117381487601865">"Setări rapide"</string>
+    <string name="notifications_label" msgid="6829741046963013567">"Notificări"</string>
+    <string name="screenshot_label" msgid="863978141223970162">"Captură de ecran"</string>
+    <string name="screenshot_utterance" msgid="1430760563401895074">"Realizează o captură de ecran"</string>
+    <string name="volume_up_label" msgid="8592766918780362870">"Mărește volumul"</string>
+    <string name="volume_down_label" msgid="8574981863656447346">"Redu volumul"</string>
+    <string name="brightness_up_label" msgid="8010753822854544846">"Mărește luminozitatea"</string>
+    <string name="brightness_down_label" msgid="7115662941913272072">"Redu luminozitatea"</string>
+    <string name="previous_button_content_description" msgid="840869171117765966">"Accesează ecranul precedent"</string>
+    <string name="next_button_content_description" msgid="6810058269847364406">"Accesează ecranul următor"</string>
+    <string name="accessibility_menu_description" msgid="4458354794093858297">"Meniul Accesibilitate este un meniu mare afișat pe ecran, cu ajutorul căruia îți controlezi dispozitivul. Poți să blochezi dispozitivul, să ajustezi volumul și luminozitatea, să faci capturi de ecran și multe altele."</string>
+    <string name="accessibility_menu_summary" msgid="340071398148208130">"Controlează dispozitivul cu ajutorul unui meniu mare"</string>
+    <string name="accessibility_menu_settings_name" msgid="1716888058785672611">"Setări pentru meniul Accesibilitate"</string>
+    <string name="accessibility_menu_large_buttons_title" msgid="8978499601044961736">"Butoane mari"</string>
+    <string name="accessibility_menu_large_buttons_summary" msgid="236873938502785311">"Mărește butoanele meniului de accesibilitate"</string>
+    <string name="pref_help_title" msgid="6871558837025010641">"Ajutor"</string>
+    <string name="brightness_percentage_label" msgid="7391554573977867369">"Luminozitate: <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+    <string name="music_volume_percentage_label" msgid="398635599662604706">"Volumul pentru muzică: <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+</resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-ru/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-ru/strings.xml
new file mode 100644
index 0000000..7614fb5
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-ru/strings.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="accessibility_menu_service_name" msgid="730136711554740131">"Меню спец. возможностей"</string>
+    <string name="accessibility_menu_intro" msgid="3164193281544042394">"С помощью большого экранного меню специальных возможностей можно блокировать устройство, регулировать громкость звука и яркость экрана, делать скриншоты и выполнять другие действия."</string>
+    <string name="assistant_label" msgid="6796392082252272356">"Ассистент"</string>
+    <string name="assistant_utterance" msgid="65509599221141377">"Ассистент"</string>
+    <string name="a11y_settings_label" msgid="3977714687248445050">"Настройки спец. возможностей"</string>
+    <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="quick_settings_label" msgid="2999117381487601865">"Быстрые настройки"</string>
+    <string name="notifications_label" msgid="6829741046963013567">"Уведомления"</string>
+    <string name="screenshot_label" msgid="863978141223970162">"Скриншот"</string>
+    <string name="screenshot_utterance" msgid="1430760563401895074">"Сделать скриншот"</string>
+    <string name="volume_up_label" msgid="8592766918780362870">"Увеличить громкость"</string>
+    <string name="volume_down_label" msgid="8574981863656447346">"Уменьшить громкость"</string>
+    <string name="brightness_up_label" msgid="8010753822854544846">"Увеличить яркость"</string>
+    <string name="brightness_down_label" msgid="7115662941913272072">"Уменьшить яркость"</string>
+    <string name="previous_button_content_description" msgid="840869171117765966">"Вернуться на предыдущий экран"</string>
+    <string name="next_button_content_description" msgid="6810058269847364406">"Перейти на следующий экран"</string>
+    <string name="accessibility_menu_description" msgid="4458354794093858297">"С помощью большого экранного меню специальных возможностей можно блокировать устройство, регулировать громкость звука и яркость экрана, делать скриншоты и выполнять другие действия."</string>
+    <string name="accessibility_menu_summary" msgid="340071398148208130">"Управление устройством с помощью большого меню"</string>
+    <string name="accessibility_menu_settings_name" msgid="1716888058785672611">"Настройки меню спец. возможностей"</string>
+    <string name="accessibility_menu_large_buttons_title" msgid="8978499601044961736">"Увеличить кнопки"</string>
+    <string name="accessibility_menu_large_buttons_summary" msgid="236873938502785311">"Увеличить размер кнопок в меню специальных возможностей"</string>
+    <string name="pref_help_title" msgid="6871558837025010641">"Справка"</string>
+    <string name="brightness_percentage_label" msgid="7391554573977867369">"Яркость <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+    <string name="music_volume_percentage_label" msgid="398635599662604706">"Громкость музыки <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+</resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-si/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-si/strings.xml
new file mode 100644
index 0000000..10573df
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-si/strings.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="accessibility_menu_service_name" msgid="730136711554740131">"ප්‍රවේශ්‍යතා මෙනුව"</string>
+    <string name="accessibility_menu_intro" msgid="3164193281544042394">"ප්‍රවේශ්‍යතා මෙනුව ඔබගේ උපාංගය පාලනය කිරීම සඳහා විශාල තිරය මත මෙනුවක් සපයයි. ඔබට ඔබගේ උපාංගය අගුලු හැරීමට, හඬ පරිමාව සහ දීප්තිය පාලනය කිරීමට, තිර රූ ගැනීමට සහ තවත් දේ කිරීමට හැකිය."</string>
+    <string name="assistant_label" msgid="6796392082252272356">"සහායක"</string>
+    <string name="assistant_utterance" msgid="65509599221141377">"සහායක"</string>
+    <string name="a11y_settings_label" msgid="3977714687248445050">"ප්‍රවේශ්‍යතා සැකසීම්"</string>
+    <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="quick_settings_label" msgid="2999117381487601865">"ක්ෂණික සැකසීම්"</string>
+    <string name="notifications_label" msgid="6829741046963013567">"දැනුම්දීම්"</string>
+    <string name="screenshot_label" msgid="863978141223970162">"තිර රුව"</string>
+    <string name="screenshot_utterance" msgid="1430760563401895074">"තිර රුව ගන්න"</string>
+    <string name="volume_up_label" msgid="8592766918780362870">"හඬ පරිමාව වැඩි කිරීම"</string>
+    <string name="volume_down_label" msgid="8574981863656447346">"හඬ පරිමාව අඩු කිරීම"</string>
+    <string name="brightness_up_label" msgid="8010753822854544846">"දීප්තිය වැඩි කිරීම"</string>
+    <string name="brightness_down_label" msgid="7115662941913272072">"දීප්තිය අඩු කිරීම"</string>
+    <string name="previous_button_content_description" msgid="840869171117765966">"පෙර තිරයට යන්න"</string>
+    <string name="next_button_content_description" msgid="6810058269847364406">"මීළඟ තිරයට යන්න"</string>
+    <string name="accessibility_menu_description" msgid="4458354794093858297">"ප්‍රවේශ්‍යතා මෙනුව ඔබගේ උපාංගය පාලනය කිරීම සඳහා විශාල තිරය මත මෙනුවක් සපයයි. ඔබට ඔබගේ උපාංගය අගුලු හැරීමට, හඬ පරිමාව සහ දීප්තිය පාලනය කිරීමට, තිර රූ ගැනීමට සහ තවත් දේ කිරීමට හැකිය."</string>
+    <string name="accessibility_menu_summary" msgid="340071398148208130">"විශාල මෙනුව හරහා උපාංගය පාලනය කරන්න"</string>
+    <string name="accessibility_menu_settings_name" msgid="1716888058785672611">"ප්‍රවේශ්‍යතා මෙනු සැකසීම්"</string>
+    <string name="accessibility_menu_large_buttons_title" msgid="8978499601044961736">"විශාල බොත්තම්"</string>
+    <string name="accessibility_menu_large_buttons_summary" msgid="236873938502785311">"ප්‍රවේශ්‍යතා මෙනු බොත්තම්වල ප්‍රමාණය වැඩි කරන්න"</string>
+    <string name="pref_help_title" msgid="6871558837025010641">"උදවු"</string>
+    <string name="brightness_percentage_label" msgid="7391554573977867369">"දීප්තිය <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+    <string name="music_volume_percentage_label" msgid="398635599662604706">"සංගීත හඬ පරිමාව <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+</resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-sk/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-sk/strings.xml
new file mode 100644
index 0000000..c29002b
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-sk/strings.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="accessibility_menu_service_name" msgid="730136711554740131">"Ponuka dostupnosti"</string>
+    <string name="accessibility_menu_intro" msgid="3164193281544042394">"Ponukou dostupnosti sa rozumie veľká ponuka na obrazovke, pomocou ktorej môžete ovládať zariadenie. Môžete ho uzamknúť, ovládať hlasitosť a jas, vytvárať snímky obrazovky a mnoho ďalšieho."</string>
+    <string name="assistant_label" msgid="6796392082252272356">"Asistent"</string>
+    <string name="assistant_utterance" msgid="65509599221141377">"Asistent"</string>
+    <string name="a11y_settings_label" msgid="3977714687248445050">"Nastavenia dostupnosti"</string>
+    <string name="power_label" msgid="7699720321491287839">"Vypínač"</string>
+    <string name="power_utterance" msgid="7444296686402104807">"Možnosti vypínača"</string>
+    <string name="recent_apps_label" msgid="6583276995616385847">"Nedávne aplikácie"</string>
+    <string name="lockscreen_label" msgid="648347953557887087">"Zamknúť obrazovku"</string>
+    <string name="quick_settings_label" msgid="2999117381487601865">"Rýchle nastavenia"</string>
+    <string name="notifications_label" msgid="6829741046963013567">"Upozornenia"</string>
+    <string name="screenshot_label" msgid="863978141223970162">"Snímka obrazovky"</string>
+    <string name="screenshot_utterance" msgid="1430760563401895074">"Vytvoriť snímku obrazovky"</string>
+    <string name="volume_up_label" msgid="8592766918780362870">"Zvýšiť hlasitosť"</string>
+    <string name="volume_down_label" msgid="8574981863656447346">"Znížiť hlasitosť"</string>
+    <string name="brightness_up_label" msgid="8010753822854544846">"Zvýšiť jas"</string>
+    <string name="brightness_down_label" msgid="7115662941913272072">"Znížiť jas"</string>
+    <string name="previous_button_content_description" msgid="840869171117765966">"Prejsť na predchádzajúcu obrazovku"</string>
+    <string name="next_button_content_description" msgid="6810058269847364406">"Prejsť na ďalšiu obrazovku"</string>
+    <string name="accessibility_menu_description" msgid="4458354794093858297">"Ponuka dostupnosti spustí na obrazovke telefónu veľkú ponuku, pomocou ktorej môžete ovládať svoje zariadenie. Môžete ho uzamknúť, ovládať hlasitosť a jas, vytvárať snímky obrazovky a mnoho ďalšieho."</string>
+    <string name="accessibility_menu_summary" msgid="340071398148208130">"Ovládať zariadenie pomocou veľkej ponuky"</string>
+    <string name="accessibility_menu_settings_name" msgid="1716888058785672611">"Nastavenia ponuky dostupnosti"</string>
+    <string name="accessibility_menu_large_buttons_title" msgid="8978499601044961736">"Veľké tlačidlá"</string>
+    <string name="accessibility_menu_large_buttons_summary" msgid="236873938502785311">"Zväčšiť tlačidlá ponuky dostupnosti"</string>
+    <string name="pref_help_title" msgid="6871558837025010641">"Pomocník"</string>
+    <string name="brightness_percentage_label" msgid="7391554573977867369">"Jas: <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+    <string name="music_volume_percentage_label" msgid="398635599662604706">"Hlasitosť hudby: <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+</resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-sl/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-sl/strings.xml
new file mode 100644
index 0000000..c4f1b0f
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-sl/strings.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="accessibility_menu_service_name" msgid="730136711554740131">"Meni za dostopnost"</string>
+    <string name="accessibility_menu_intro" msgid="3164193281544042394">"Meni za dostopnost je velik zaslonski meni za upravljanje naprave. V njem lahko zaklenete napravo, nastavljate glasnost in svetlost, zajamete posnetke zaslona in drugo."</string>
+    <string name="assistant_label" msgid="6796392082252272356">"Pomočnik"</string>
+    <string name="assistant_utterance" msgid="65509599221141377">"Pomočnik"</string>
+    <string name="a11y_settings_label" msgid="3977714687248445050">"Nastavitve dostopnosti"</string>
+    <string name="power_label" msgid="7699720321491287839">"Vklop"</string>
+    <string name="power_utterance" msgid="7444296686402104807">"Možnosti gumba za vklop"</string>
+    <string name="recent_apps_label" msgid="6583276995616385847">"Nedavne aplikacije"</string>
+    <string name="lockscreen_label" msgid="648347953557887087">"Zaklepanje zaslona"</string>
+    <string name="quick_settings_label" msgid="2999117381487601865">"Hitre nastavitve"</string>
+    <string name="notifications_label" msgid="6829741046963013567">"Obvestila"</string>
+    <string name="screenshot_label" msgid="863978141223970162">"Posnetek zaslona"</string>
+    <string name="screenshot_utterance" msgid="1430760563401895074">"Ustvarjanje posnetka zaslona"</string>
+    <string name="volume_up_label" msgid="8592766918780362870">"Povečanje glasnosti"</string>
+    <string name="volume_down_label" msgid="8574981863656447346">"Zmanjšanje glasnosti"</string>
+    <string name="brightness_up_label" msgid="8010753822854544846">"Povečanje svetlosti"</string>
+    <string name="brightness_down_label" msgid="7115662941913272072">"Zmanjšanje svetlosti"</string>
+    <string name="previous_button_content_description" msgid="840869171117765966">"Na prejšnji zaslon"</string>
+    <string name="next_button_content_description" msgid="6810058269847364406">"Na naslednji zaslon"</string>
+    <string name="accessibility_menu_description" msgid="4458354794093858297">"Meni za dostopnost je velik zaslonski meni za upravljanje naprave. V njem lahko zaklenete napravo, nastavljate glasnost in svetlost, zajamete posnetke zaslona in drugo."</string>
+    <string name="accessibility_menu_summary" msgid="340071398148208130">"Upravljanje naprave prek velikega menija"</string>
+    <string name="accessibility_menu_settings_name" msgid="1716888058785672611">"Nastavitve menija za dostopnost"</string>
+    <string name="accessibility_menu_large_buttons_title" msgid="8978499601044961736">"Veliki gumbi"</string>
+    <string name="accessibility_menu_large_buttons_summary" msgid="236873938502785311">"Povečanje velikosti gumbov menija za dostopnost"</string>
+    <string name="pref_help_title" msgid="6871558837025010641">"Pomoč"</string>
+    <string name="brightness_percentage_label" msgid="7391554573977867369">"Svetlost <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+    <string name="music_volume_percentage_label" msgid="398635599662604706">"Glasnost glasbe <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+</resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-sq/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-sq/strings.xml
new file mode 100644
index 0000000..d8141da
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-sq/strings.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="accessibility_menu_service_name" msgid="730136711554740131">"Menyja e qasshmërisë"</string>
+    <string name="accessibility_menu_intro" msgid="3164193281544042394">"\"Menyja e qasshmërisë\" ofron një meny të madhe në ekran për të kontrolluar pajisjen tënde. Mund të kyçësh pajisjen, të kontrollosh volumin dhe ndriçimin, të nxjerrësh pamje ekrani dhe të tjera."</string>
+    <string name="assistant_label" msgid="6796392082252272356">"Asistenti"</string>
+    <string name="assistant_utterance" msgid="65509599221141377">"Asistenti"</string>
+    <string name="a11y_settings_label" msgid="3977714687248445050">"Cilësimet e qasshmërisë"</string>
+    <string name="power_label" msgid="7699720321491287839">"Energjia"</string>
+    <string name="power_utterance" msgid="7444296686402104807">"Opsionet e energjisë"</string>
+    <string name="recent_apps_label" msgid="6583276995616385847">"Aplikacionet e fundit"</string>
+    <string name="lockscreen_label" msgid="648347953557887087">"Ekrani i kyçjes"</string>
+    <string name="quick_settings_label" msgid="2999117381487601865">"Cilësimet e shpejta"</string>
+    <string name="notifications_label" msgid="6829741046963013567">"Njoftimet"</string>
+    <string name="screenshot_label" msgid="863978141223970162">"Pamja e ekranit"</string>
+    <string name="screenshot_utterance" msgid="1430760563401895074">"Të nxjerrë një pamje të ekranit"</string>
+    <string name="volume_up_label" msgid="8592766918780362870">"Rrit volumin"</string>
+    <string name="volume_down_label" msgid="8574981863656447346">"Ul volumin"</string>
+    <string name="brightness_up_label" msgid="8010753822854544846">"Rrit ndriçimin"</string>
+    <string name="brightness_down_label" msgid="7115662941913272072">"Ul ndriçimin"</string>
+    <string name="previous_button_content_description" msgid="840869171117765966">"Shko tek ekrani i mëparshëm"</string>
+    <string name="next_button_content_description" msgid="6810058269847364406">"Shko tek ekrani tjetër"</string>
+    <string name="accessibility_menu_description" msgid="4458354794093858297">"\"Menyja e qasshmërisë\" ofron një meny të madhe në ekran për të kontrolluar pajisjen tënde. Mund të kyçësh pajisjen, të kontrollosh volumin dhe ndriçimin, të nxjerrësh pamje ekrani dhe të tjera."</string>
+    <string name="accessibility_menu_summary" msgid="340071398148208130">"Kontrollo pajisjen nëpërmjet menysë së madhe"</string>
+    <string name="accessibility_menu_settings_name" msgid="1716888058785672611">"Cilësimet e menysë së qasshmërisë"</string>
+    <string name="accessibility_menu_large_buttons_title" msgid="8978499601044961736">"Butona të mëdhenj"</string>
+    <string name="accessibility_menu_large_buttons_summary" msgid="236873938502785311">"Rrit madhësinë e butonave të \"Menysë së qasshmërisë\""</string>
+    <string name="pref_help_title" msgid="6871558837025010641">"Ndihma"</string>
+    <string name="brightness_percentage_label" msgid="7391554573977867369">"Ndriçimi <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+    <string name="music_volume_percentage_label" msgid="398635599662604706">"Volumi i muzikës <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+</resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-sr/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-sr/strings.xml
new file mode 100644
index 0000000..e27447f
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-sr/strings.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="accessibility_menu_service_name" msgid="730136711554740131">"Мени Приступачност"</string>
+    <string name="accessibility_menu_intro" msgid="3164193281544042394">"Мени Приступачност пружа велики мени на екрану за контролу уређаја. Можете да закључате уређај, контролишете јачину звука и осветљеност, правите снимке екрана и друго."</string>
+    <string name="assistant_label" msgid="6796392082252272356">"Помоћник"</string>
+    <string name="assistant_utterance" msgid="65509599221141377">"Помоћник"</string>
+    <string name="a11y_settings_label" msgid="3977714687248445050">"Подешавања приступачности"</string>
+    <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="quick_settings_label" msgid="2999117381487601865">"Брза подешавања"</string>
+    <string name="notifications_label" msgid="6829741046963013567">"Обавештења"</string>
+    <string name="screenshot_label" msgid="863978141223970162">"Снимак екрана"</string>
+    <string name="screenshot_utterance" msgid="1430760563401895074">"Сними екран"</string>
+    <string name="volume_up_label" msgid="8592766918780362870">"Појачај звук"</string>
+    <string name="volume_down_label" msgid="8574981863656447346">"Утишај звук"</string>
+    <string name="brightness_up_label" msgid="8010753822854544846">"Повећајте осветљеност"</string>
+    <string name="brightness_down_label" msgid="7115662941913272072">"Смањите осветљеност"</string>
+    <string name="previous_button_content_description" msgid="840869171117765966">"Иди на претходни екран"</string>
+    <string name="next_button_content_description" msgid="6810058269847364406">"Иди на следећи екран"</string>
+    <string name="accessibility_menu_description" msgid="4458354794093858297">"Мени Приступачност пружа велики мени на екрану за контролу уређаја. Можете да закључате уређај, контролишете јачину звука и осветљеност, правите снимке екрана и друго."</string>
+    <string name="accessibility_menu_summary" msgid="340071398148208130">"Контролишите уређај помоћу великог менија"</string>
+    <string name="accessibility_menu_settings_name" msgid="1716888058785672611">"Подешавања менија Приступачност"</string>
+    <string name="accessibility_menu_large_buttons_title" msgid="8978499601044961736">"Велика дугмад"</string>
+    <string name="accessibility_menu_large_buttons_summary" msgid="236873938502785311">"Повећајте величину дугмади у менију за приступачност"</string>
+    <string name="pref_help_title" msgid="6871558837025010641">"Помоћ"</string>
+    <string name="brightness_percentage_label" msgid="7391554573977867369">"Осветљеност: <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+    <string name="music_volume_percentage_label" msgid="398635599662604706">"Јачина музике: <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+</resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-sv/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-sv/strings.xml
new file mode 100644
index 0000000..ef69667
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-sv/strings.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="accessibility_menu_service_name" msgid="730136711554740131">"Tillgänglighetsmenyn"</string>
+    <string name="accessibility_menu_intro" msgid="3164193281544042394">"Tillgänglighetsmenyn är en stor meny på skärmen som du kan styra enheten med. Du kan låsa enheten, ställa in volym och ljusstyrka, ta skärmbilder och annat."</string>
+    <string name="assistant_label" msgid="6796392082252272356">"Assistent"</string>
+    <string name="assistant_utterance" msgid="65509599221141377">"Assistent"</string>
+    <string name="a11y_settings_label" msgid="3977714687248445050">"Tillgänglighets­inställningar"</string>
+    <string name="power_label" msgid="7699720321491287839">"Styrka"</string>
+    <string name="power_utterance" msgid="7444296686402104807">"Strömalternativ"</string>
+    <string name="recent_apps_label" msgid="6583276995616385847">"Senaste apparna"</string>
+    <string name="lockscreen_label" msgid="648347953557887087">"Låsskärm"</string>
+    <string name="quick_settings_label" msgid="2999117381487601865">"Snabbinställningar"</string>
+    <string name="notifications_label" msgid="6829741046963013567">"Aviseringar"</string>
+    <string name="screenshot_label" msgid="863978141223970162">"Skärmbild"</string>
+    <string name="screenshot_utterance" msgid="1430760563401895074">"Ta skärmbild"</string>
+    <string name="volume_up_label" msgid="8592766918780362870">"Höj volymen"</string>
+    <string name="volume_down_label" msgid="8574981863656447346">"Sänk volymen"</string>
+    <string name="brightness_up_label" msgid="8010753822854544846">"Öka ljusstyrkan"</string>
+    <string name="brightness_down_label" msgid="7115662941913272072">"Minska ljusstyrkan"</string>
+    <string name="previous_button_content_description" msgid="840869171117765966">"Tillbaka till föregående skärm"</string>
+    <string name="next_button_content_description" msgid="6810058269847364406">"Fortsätt till nästa skärm"</string>
+    <string name="accessibility_menu_description" msgid="4458354794093858297">"Tillgänglighetsmenyn är en stor meny på skärmen som du kan styra enheten med. Du kan låsa enheten, ställa in volym och ljusstyrka, ta skärmbilder och annat."</string>
+    <string name="accessibility_menu_summary" msgid="340071398148208130">"Styr enheten via en stor meny"</string>
+    <string name="accessibility_menu_settings_name" msgid="1716888058785672611">"Inställningar för tillgänglighetsmenyn"</string>
+    <string name="accessibility_menu_large_buttons_title" msgid="8978499601044961736">"Stora knappar"</string>
+    <string name="accessibility_menu_large_buttons_summary" msgid="236873938502785311">"Öka knapparnas storlek i tillgänglighetsmenyn"</string>
+    <string name="pref_help_title" msgid="6871558837025010641">"Hjälp"</string>
+    <string name="brightness_percentage_label" msgid="7391554573977867369">"Ljusstyrka <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+    <string name="music_volume_percentage_label" msgid="398635599662604706">"Musikvolym <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+</resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-sw/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-sw/strings.xml
new file mode 100644
index 0000000..b8eef85
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-sw/strings.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="accessibility_menu_service_name" msgid="730136711554740131">"Menyu ya Ufikivu"</string>
+    <string name="accessibility_menu_intro" msgid="3164193281544042394">"Menyu ya Ufikivu huonyesha menyu pana iliyo kwenye skrini ili udhibiti kifaa chako. Unaweza kufunga kifaa chako, kudhibiti sauti na ung\'avu, kupiga picha ya skrini na zaidi."</string>
+    <string name="assistant_label" msgid="6796392082252272356">"Mratibu"</string>
+    <string name="assistant_utterance" msgid="65509599221141377">"Programu ya Mratibu"</string>
+    <string name="a11y_settings_label" msgid="3977714687248445050">"Mipangilio ya Ufikivu"</string>
+    <string name="power_label" msgid="7699720321491287839">"Nishati"</string>
+    <string name="power_utterance" msgid="7444296686402104807">"Chaguo za kuwasha"</string>
+    <string name="recent_apps_label" msgid="6583276995616385847">"Programu za hivi karibuni"</string>
+    <string name="lockscreen_label" msgid="648347953557887087">"Skrini iliyofungwa"</string>
+    <string name="quick_settings_label" msgid="2999117381487601865">"Mipangilio ya Haraka"</string>
+    <string name="notifications_label" msgid="6829741046963013567">"Arifa"</string>
+    <string name="screenshot_label" msgid="863978141223970162">"Picha ya skrini"</string>
+    <string name="screenshot_utterance" msgid="1430760563401895074">"Piga picha ya skrini"</string>
+    <string name="volume_up_label" msgid="8592766918780362870">"Ongeza sauti"</string>
+    <string name="volume_down_label" msgid="8574981863656447346">"Punguza sauti"</string>
+    <string name="brightness_up_label" msgid="8010753822854544846">"Ongeza ung\'aavu"</string>
+    <string name="brightness_down_label" msgid="7115662941913272072">"Punguza ung\'aavu"</string>
+    <string name="previous_button_content_description" msgid="840869171117765966">"Nenda kwenye skrini iliyotangulia"</string>
+    <string name="next_button_content_description" msgid="6810058269847364406">"Nenda kwenye skrini inayofuata"</string>
+    <string name="accessibility_menu_description" msgid="4458354794093858297">"Menyu ya Ufikivu huonyesha menyu pana iliyo kwenye skrini ili udhibiti kifaa chako. Unaweza kufunga kifaa chako, kudhibiti sauti na ung\'avu, kupiga picha ya skrini na zaidi."</string>
+    <string name="accessibility_menu_summary" msgid="340071398148208130">"Dhibiti kifaa ukitumia menyu pana"</string>
+    <string name="accessibility_menu_settings_name" msgid="1716888058785672611">"Mipangilio ya Zana za Ufikivu"</string>
+    <string name="accessibility_menu_large_buttons_title" msgid="8978499601044961736">"Vitufe vikubwa"</string>
+    <string name="accessibility_menu_large_buttons_summary" msgid="236873938502785311">"Ongeza ukubwa wa Vitufe vya Menyu ya Ufikivu"</string>
+    <string name="pref_help_title" msgid="6871558837025010641">"Usaidizi"</string>
+    <string name="brightness_percentage_label" msgid="7391554573977867369">"Ung\'avu <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+    <string name="music_volume_percentage_label" msgid="398635599662604706">"Sauti ya muziki <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+</resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-ta/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-ta/strings.xml
new file mode 100644
index 0000000..57bbc52
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-ta/strings.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="accessibility_menu_service_name" msgid="730136711554740131">"அணுகல்தன்மை மெனு"</string>
+    <string name="accessibility_menu_intro" msgid="3164193281544042394">"அணுகல்தன்மை மெனுவானது உங்கள் சாதனத்தைக் கட்டுப்படுத்துவதற்கு, திரையில் தோன்றும் பெரிய மெனுவை வழங்குகிறது. சாதனத்தைப் பூட்டுதல், ஒலியளவையும் ஒளிர்வையும் மாற்றுதல், ஸ்கிரீன்ஷாட்களை எடுத்தல் போன்ற பலவற்றைச் செய்யலாம்."</string>
+    <string name="assistant_label" msgid="6796392082252272356">"Assistant"</string>
+    <string name="assistant_utterance" msgid="65509599221141377">"அசிஸ்டண்ட்"</string>
+    <string name="a11y_settings_label" msgid="3977714687248445050">"அணுகல்தன்மை அமைப்புகள்"</string>
+    <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="quick_settings_label" msgid="2999117381487601865">"விரைவு அமைப்புகள்"</string>
+    <string name="notifications_label" msgid="6829741046963013567">"அறிவிப்புகள்"</string>
+    <string name="screenshot_label" msgid="863978141223970162">"ஸ்கிரீன்ஷாட்"</string>
+    <string name="screenshot_utterance" msgid="1430760563401895074">"ஸ்கிரீன் ஷாட்டை எடுக்கும் பட்டன்"</string>
+    <string name="volume_up_label" msgid="8592766918780362870">"ஒலியளவை அதிகரிக்கும்"</string>
+    <string name="volume_down_label" msgid="8574981863656447346">"ஒலியளவைக் குறைக்கும்"</string>
+    <string name="brightness_up_label" msgid="8010753822854544846">"ஒளிர்வை அதிகரிக்கும்"</string>
+    <string name="brightness_down_label" msgid="7115662941913272072">"ஒளிர்வைக் குறைக்கும்"</string>
+    <string name="previous_button_content_description" msgid="840869171117765966">"முந்தைய திரைக்குச் செல்வதற்கான பட்டன்"</string>
+    <string name="next_button_content_description" msgid="6810058269847364406">"அடுத்த திரைக்குச் செல்வதற்கான பட்டன்"</string>
+    <string name="accessibility_menu_description" msgid="4458354794093858297">"அணுகல்தன்மை மெனுவானது உங்கள் சாதனத்தைக் கட்டுப்படுத்துவதற்கு, திரையில் தோன்றும் பெரிய மெனுவை வழங்குகிறது. சாதனத்தைப் பூட்டுதல், ஒலியளவையும் ஒளிர்வையும் மாற்றுதல், ஸ்கிரீன்ஷாட்களை எடுத்தல் போன்ற பலவற்றைச் செய்யலாம்."</string>
+    <string name="accessibility_menu_summary" msgid="340071398148208130">"பெரிய மெனுவின் மூலம் சாதனத்தைக் கட்டுப்படுத்தலாம்"</string>
+    <string name="accessibility_menu_settings_name" msgid="1716888058785672611">"அணுகல்தன்மை மெனு அமைப்புகள்"</string>
+    <string name="accessibility_menu_large_buttons_title" msgid="8978499601044961736">"பெரிய பட்டன்கள்"</string>
+    <string name="accessibility_menu_large_buttons_summary" msgid="236873938502785311">"’அணுகலம்சங்கள் மெனு பட்டன்களின்’ அளவைப் பெரிதாக்கும்"</string>
+    <string name="pref_help_title" msgid="6871558837025010641">"உதவி"</string>
+    <string name="brightness_percentage_label" msgid="7391554573977867369">"ஒளிர்வு: <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+    <string name="music_volume_percentage_label" msgid="398635599662604706">"இசை ஒலியளவு: <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+</resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-te/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-te/strings.xml
new file mode 100644
index 0000000..827e6de
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-te/strings.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="accessibility_menu_service_name" msgid="730136711554740131">"యాక్సెసిబిలిటీ మెనూ"</string>
+    <string name="accessibility_menu_intro" msgid="3164193281544042394">"మీ పరికరాన్ని కంట్రోల్ చేయడానికి యాక్సెసిబిలిటీ మెనూ, స్క్రీన్‌పై పెద్ద మెనూను అందిస్తుంది. మీరు మీ పరికరాన్ని లాక్ చేయవచ్చు, వాల్యూమ్‌ను, బ్రైట్‌నెస్‌ను కంట్రోల్ చేయవచ్చు, స్క్రీన్‌షాట్‌లు తీసుకోవచ్చు, మరిన్ని చేయవచ్చు."</string>
+    <string name="assistant_label" msgid="6796392082252272356">"Assistant"</string>
+    <string name="assistant_utterance" msgid="65509599221141377">"Assistant"</string>
+    <string name="a11y_settings_label" msgid="3977714687248445050">"యాక్సెసిబిలిటీ సెట్టింగ్‌లు"</string>
+    <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="quick_settings_label" msgid="2999117381487601865">"క్విక్ సెట్టింగ్‌లు"</string>
+    <string name="notifications_label" msgid="6829741046963013567">"నోటిఫికేషన్‌లు"</string>
+    <string name="screenshot_label" msgid="863978141223970162">"స్క్రీన్‌షాట్"</string>
+    <string name="screenshot_utterance" msgid="1430760563401895074">"స్క్రీన్‌షాట్‌ని తీయండి"</string>
+    <string name="volume_up_label" msgid="8592766918780362870">"వాల్యూమ్ పెంచండి"</string>
+    <string name="volume_down_label" msgid="8574981863656447346">"వాల్యూమ్ తగ్గించండి"</string>
+    <string name="brightness_up_label" msgid="8010753822854544846">"బ్రైట్‌నెస్‌ను పెంచండి"</string>
+    <string name="brightness_down_label" msgid="7115662941913272072">"బ్రైట్‌నెస్‌ను తగ్గించండి"</string>
+    <string name="previous_button_content_description" msgid="840869171117765966">"మునుపటి స్క్రీన్‌కు వెళ్లండి"</string>
+    <string name="next_button_content_description" msgid="6810058269847364406">"తదుపరి స్క్రీన్‌కు వెళ్లండి"</string>
+    <string name="accessibility_menu_description" msgid="4458354794093858297">"మీ పరికరాన్ని కంట్రోల్ చేయడానికి యాక్సెసిబిలిటీ మెనూ, స్క్రీన్‌పై పెద్ద మెనూను అందిస్తుంది. మీరు మీ పరికరాన్ని లాక్ చేయవచ్చు, వాల్యూమ్‌ను, బ్రైట్‌నెస్‌ను కంట్రోల్ చేయవచ్చు, స్క్రీన్‌షాట్‌లు తీసుకోవచ్చు, మరిన్ని చేయవచ్చు."</string>
+    <string name="accessibility_menu_summary" msgid="340071398148208130">"పెద్ద మెనూ ద్వారా పరికరాన్ని కంట్రోల్ చేయండి"</string>
+    <string name="accessibility_menu_settings_name" msgid="1716888058785672611">"యాక్సెసిబిలిటీ మెనూ సెట్టింగ్‌లు"</string>
+    <string name="accessibility_menu_large_buttons_title" msgid="8978499601044961736">"పెద్ద బటన్‌లు"</string>
+    <string name="accessibility_menu_large_buttons_summary" msgid="236873938502785311">"యాక్సెసిబిలిటీ మెనూ బటన్‌ల సైజ్‌ పెంచుతుంది"</string>
+    <string name="pref_help_title" msgid="6871558837025010641">"సహాయం"</string>
+    <string name="brightness_percentage_label" msgid="7391554573977867369">"బ్రైట్‌నెస్ <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+    <string name="music_volume_percentage_label" msgid="398635599662604706">"మ్యూజిక్ వాల్యూమ్ <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+</resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-th/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-th/strings.xml
new file mode 100644
index 0000000..29581a9
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-th/strings.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="accessibility_menu_service_name" msgid="730136711554740131">"เมนูการช่วยเหลือพิเศษ"</string>
+    <string name="accessibility_menu_intro" msgid="3164193281544042394">"เมนูการช่วยเหลือพิเศษเป็นเมนูขนาดใหญ่บนหน้าจอที่มีไว้ควบคุมอุปกรณ์ คุณจะล็อกอุปกรณ์ ควบคุมระดับเสียงและความสว่าง ถ่ายภาพหน้าจอ และอื่นๆ ได้"</string>
+    <string name="assistant_label" msgid="6796392082252272356">"Assistant"</string>
+    <string name="assistant_utterance" msgid="65509599221141377">"Assistant"</string>
+    <string name="a11y_settings_label" msgid="3977714687248445050">"การตั้งค่าการช่วยเหลือพิเศษ"</string>
+    <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="quick_settings_label" msgid="2999117381487601865">"การตั้งค่าด่วน"</string>
+    <string name="notifications_label" msgid="6829741046963013567">"การแจ้งเตือน"</string>
+    <string name="screenshot_label" msgid="863978141223970162">"ภาพหน้าจอ"</string>
+    <string name="screenshot_utterance" msgid="1430760563401895074">"จับภาพหน้าจอ"</string>
+    <string name="volume_up_label" msgid="8592766918780362870">"เพิ่มระดับเสียง"</string>
+    <string name="volume_down_label" msgid="8574981863656447346">"ลดระดับเสียง"</string>
+    <string name="brightness_up_label" msgid="8010753822854544846">"เพิ่มความสว่าง"</string>
+    <string name="brightness_down_label" msgid="7115662941913272072">"ลดความสว่าง"</string>
+    <string name="previous_button_content_description" msgid="840869171117765966">"ไปที่หน้าจอก่อนหน้า"</string>
+    <string name="next_button_content_description" msgid="6810058269847364406">"ไปที่หน้าจอถัดไป"</string>
+    <string name="accessibility_menu_description" msgid="4458354794093858297">"เมนูการช่วยเหลือพิเศษเป็นเมนูขนาดใหญ่บนหน้าจอที่มีไว้ควบคุมอุปกรณ์ คุณจะล็อกอุปกรณ์ ควบคุมระดับเสียงและความสว่าง ถ่ายภาพหน้าจอ และอื่นๆ ได้"</string>
+    <string name="accessibility_menu_summary" msgid="340071398148208130">"ควบคุมอุปกรณ์ผ่านเมนูขนาดใหญ่"</string>
+    <string name="accessibility_menu_settings_name" msgid="1716888058785672611">"การตั้งค่าเมนูการช่วยเหลือพิเศษ"</string>
+    <string name="accessibility_menu_large_buttons_title" msgid="8978499601044961736">"ปุ่มใหญ่"</string>
+    <string name="accessibility_menu_large_buttons_summary" msgid="236873938502785311">"เพิ่มขนาดของปุ่มเมนูการช่วยเหลือพิเศษ"</string>
+    <string name="pref_help_title" msgid="6871558837025010641">"ความช่วยเหลือ"</string>
+    <string name="brightness_percentage_label" msgid="7391554573977867369">"ความสว่าง <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+    <string name="music_volume_percentage_label" msgid="398635599662604706">"ระดับเสียงเพลง <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+</resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-tl/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-tl/strings.xml
new file mode 100644
index 0000000..4a5833f
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-tl/strings.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="accessibility_menu_service_name" msgid="730136711554740131">"Menu ng Accessibility"</string>
+    <string name="accessibility_menu_intro" msgid="3164193281544042394">"Nagbibigay ang Menu ng Accessibility ng malaking menu sa screen para sa pagkontrol sa iyong device. Magagawa mong i-lock ang iyong device, kontrolin ang volume at liwanag, kumuha ng mga screenshot, at higit pa."</string>
+    <string name="assistant_label" msgid="6796392082252272356">"Assistant"</string>
+    <string name="assistant_utterance" msgid="65509599221141377">"Assistant"</string>
+    <string name="a11y_settings_label" msgid="3977714687248445050">"Mga Setting ng Accessibility"</string>
+    <string name="power_label" msgid="7699720321491287839">"Power"</string>
+    <string name="power_utterance" msgid="7444296686402104807">"Mga opsyon sa power"</string>
+    <string name="recent_apps_label" msgid="6583276995616385847">"Mga kamakailang app"</string>
+    <string name="lockscreen_label" msgid="648347953557887087">"Lock screen"</string>
+    <string name="quick_settings_label" msgid="2999117381487601865">"Mga Mabilisang Setting"</string>
+    <string name="notifications_label" msgid="6829741046963013567">"Mga Notification"</string>
+    <string name="screenshot_label" msgid="863978141223970162">"Screenshot"</string>
+    <string name="screenshot_utterance" msgid="1430760563401895074">"Kumuha ng screenshot"</string>
+    <string name="volume_up_label" msgid="8592766918780362870">"Lakasan ang volume"</string>
+    <string name="volume_down_label" msgid="8574981863656447346">"Hinaan ang volume"</string>
+    <string name="brightness_up_label" msgid="8010753822854544846">"Taasan ang liwanag"</string>
+    <string name="brightness_down_label" msgid="7115662941913272072">"Babaan ang liwanag"</string>
+    <string name="previous_button_content_description" msgid="840869171117765966">"Pumunta sa nakaraang screen"</string>
+    <string name="next_button_content_description" msgid="6810058269847364406">"Pumunta sa susunod na screen"</string>
+    <string name="accessibility_menu_description" msgid="4458354794093858297">"Nagbibigay ang Menu ng Accessibility ng malaking menu sa screen para kontrolin ang iyong device. Magagawa mong i-lock ang iyong device, kontrolin ang volume at liwanag, kumuha ng mga screenshot, at higit pa."</string>
+    <string name="accessibility_menu_summary" msgid="340071398148208130">"Kontrolin ang device sa pamamagitan ng malaking menu"</string>
+    <string name="accessibility_menu_settings_name" msgid="1716888058785672611">"Mga Setting ng Menu ng Accessibility"</string>
+    <string name="accessibility_menu_large_buttons_title" msgid="8978499601044961736">"Malalaking button"</string>
+    <string name="accessibility_menu_large_buttons_summary" msgid="236873938502785311">"Palakihin ang Mga Button ng Menu ng Accessibility"</string>
+    <string name="pref_help_title" msgid="6871558837025010641">"Tulong"</string>
+    <string name="brightness_percentage_label" msgid="7391554573977867369">"Liwanag <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+    <string name="music_volume_percentage_label" msgid="398635599662604706">"Volume ng musika <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+</resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-tr/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-tr/strings.xml
new file mode 100644
index 0000000..38cc395
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-tr/strings.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="accessibility_menu_service_name" msgid="730136711554740131">"Erişilebilirlik menüsü"</string>
+    <string name="accessibility_menu_intro" msgid="3164193281544042394">"Erişilebilirlik menüsü, cihazınızı kontrol etmeniz için geniş bir ekran menüsü sağlar. Cihazınızı kilitleyebilir, ses düzeyini ve parlaklığı kontrol edebilir, ekran görüntüsü alabilir ve daha fazlasını yapabilirsiniz."</string>
+    <string name="assistant_label" msgid="6796392082252272356">"Asistan"</string>
+    <string name="assistant_utterance" msgid="65509599221141377">"Asistan"</string>
+    <string name="a11y_settings_label" msgid="3977714687248445050">"Erişebilirlik Ayarları"</string>
+    <string name="power_label" msgid="7699720321491287839">"Güç"</string>
+    <string name="power_utterance" msgid="7444296686402104807">"Güç seçenekleri"</string>
+    <string name="recent_apps_label" msgid="6583276995616385847">"Son uygulamalar"</string>
+    <string name="lockscreen_label" msgid="648347953557887087">"Kilit ekranı"</string>
+    <string name="quick_settings_label" msgid="2999117381487601865">"Hızlı Ayarlar"</string>
+    <string name="notifications_label" msgid="6829741046963013567">"Bildirimler"</string>
+    <string name="screenshot_label" msgid="863978141223970162">"Ekran görüntüsü"</string>
+    <string name="screenshot_utterance" msgid="1430760563401895074">"Ekran görüntüsü al"</string>
+    <string name="volume_up_label" msgid="8592766918780362870">"Sesi aç"</string>
+    <string name="volume_down_label" msgid="8574981863656447346">"Sesi kıs"</string>
+    <string name="brightness_up_label" msgid="8010753822854544846">"Parlaklığı artır"</string>
+    <string name="brightness_down_label" msgid="7115662941913272072">"Parlaklığı azalt"</string>
+    <string name="previous_button_content_description" msgid="840869171117765966">"Önceki ekrana git"</string>
+    <string name="next_button_content_description" msgid="6810058269847364406">"Sonraki ekrana git"</string>
+    <string name="accessibility_menu_description" msgid="4458354794093858297">"Erişilebilirlik menüsü, cihazınızı kontrol etmeniz için geniş bir ekran menüsü sağlar. Cihazınızı kilitleyebilir, ses düzeyini ve parlaklığı kontrol edebilir, ekran görüntüsü alabilir ve daha fazlasını yapabilirsiniz."</string>
+    <string name="accessibility_menu_summary" msgid="340071398148208130">"Cihazı geniş menüyle kontrol edin"</string>
+    <string name="accessibility_menu_settings_name" msgid="1716888058785672611">"Erişilebilirlik Menüsü Ayarları"</string>
+    <string name="accessibility_menu_large_buttons_title" msgid="8978499601044961736">"Büyük düğmeler"</string>
+    <string name="accessibility_menu_large_buttons_summary" msgid="236873938502785311">"Erişilebilirlik menüsündeki düğmelerin boyutunu artır"</string>
+    <string name="pref_help_title" msgid="6871558837025010641">"Yardım"</string>
+    <string name="brightness_percentage_label" msgid="7391554573977867369">"Parlaklık %%<xliff:g id="PERCENTAGE">%1$s</xliff:g>"</string>
+    <string name="music_volume_percentage_label" msgid="398635599662604706">"Müzik ses düzeyi %%<xliff:g id="PERCENTAGE">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-uk/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-uk/strings.xml
new file mode 100644
index 0000000..ee02b4a
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-uk/strings.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="accessibility_menu_service_name" msgid="730136711554740131">"Меню функцій доступності"</string>
+    <string name="accessibility_menu_intro" msgid="3164193281544042394">"За допомогою великого екранного меню функцій доступності можна блокувати пристрій, змінювати гучність і яскравість, робити знімки екрана й багато іншого."</string>
+    <string name="assistant_label" msgid="6796392082252272356">"Асистент"</string>
+    <string name="assistant_utterance" msgid="65509599221141377">"Асистент"</string>
+    <string name="a11y_settings_label" msgid="3977714687248445050">"Налаштування функцій доступності"</string>
+    <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="quick_settings_label" msgid="2999117381487601865">"Швидкі налаштування"</string>
+    <string name="notifications_label" msgid="6829741046963013567">"Сповіщення"</string>
+    <string name="screenshot_label" msgid="863978141223970162">"Знімок екрана"</string>
+    <string name="screenshot_utterance" msgid="1430760563401895074">"Зробити знімок екрана"</string>
+    <string name="volume_up_label" msgid="8592766918780362870">"Збільшити гучність"</string>
+    <string name="volume_down_label" msgid="8574981863656447346">"Зменшити гучність"</string>
+    <string name="brightness_up_label" msgid="8010753822854544846">"Збільшити яскравість"</string>
+    <string name="brightness_down_label" msgid="7115662941913272072">"Зменшити яскравість"</string>
+    <string name="previous_button_content_description" msgid="840869171117765966">"Перейти на попередній екран"</string>
+    <string name="next_button_content_description" msgid="6810058269847364406">"Перейти на наступний екран"</string>
+    <string name="accessibility_menu_description" msgid="4458354794093858297">"За допомогою великого екранного меню функцій доступності можна блокувати пристрій, змінювати гучність і яскравість, робити знімки екрана й багато іншого."</string>
+    <string name="accessibility_menu_summary" msgid="340071398148208130">"Керування пристроєм за допомогою великого меню"</string>
+    <string name="accessibility_menu_settings_name" msgid="1716888058785672611">"Налаштування меню функцій доступності"</string>
+    <string name="accessibility_menu_large_buttons_title" msgid="8978499601044961736">"Великі кнопки"</string>
+    <string name="accessibility_menu_large_buttons_summary" msgid="236873938502785311">"Збільшити розмір кнопок у меню функцій доступності"</string>
+    <string name="pref_help_title" msgid="6871558837025010641">"Довідка"</string>
+    <string name="brightness_percentage_label" msgid="7391554573977867369">"Яскравість: <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+    <string name="music_volume_percentage_label" msgid="398635599662604706">"Гучність музики: <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+</resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-ur/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-ur/strings.xml
new file mode 100644
index 0000000..620163e
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-ur/strings.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="accessibility_menu_service_name" msgid="730136711554740131">"ایکسیسبیلٹی مینیو"</string>
+    <string name="accessibility_menu_intro" msgid="3164193281544042394">"اپنے آلے کو کنٹرول کرنے کے لیے ایکسیسبیلٹی مینیو ایک بڑا آن اسکرین مینیو فراہم کرتا ہے۔ آپ اپنا آلہ مقفل، والیوم اور چمک کو کنٹرول، اسکرین شاٹ لینے کے ساتھ اور مزید بہت کچھ کر سکتے ہیں۔"</string>
+    <string name="assistant_label" msgid="6796392082252272356">"اسسٹنٹ"</string>
+    <string name="assistant_utterance" msgid="65509599221141377">"اسسٹنٹ"</string>
+    <string name="a11y_settings_label" msgid="3977714687248445050">"ایکسیسبیلٹی ترتیبات"</string>
+    <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="quick_settings_label" msgid="2999117381487601865">"فوری ترتیبات"</string>
+    <string name="notifications_label" msgid="6829741046963013567">"اطلاعات"</string>
+    <string name="screenshot_label" msgid="863978141223970162">"اسکرین شاٹ"</string>
+    <string name="screenshot_utterance" msgid="1430760563401895074">"اسکرین شاٹ لیں"</string>
+    <string name="volume_up_label" msgid="8592766918780362870">"والیوم بڑھانے کا بٹن"</string>
+    <string name="volume_down_label" msgid="8574981863656447346">"والیوم کم کرنے کا بٹن"</string>
+    <string name="brightness_up_label" msgid="8010753822854544846">"چمک بڑھانے کا بٹن"</string>
+    <string name="brightness_down_label" msgid="7115662941913272072">"چمک کم کرنے کا بٹن"</string>
+    <string name="previous_button_content_description" msgid="840869171117765966">"پچھلی اسکرین پر جائیں"</string>
+    <string name="next_button_content_description" msgid="6810058269847364406">"اگلی اسکرین پر جائیں"</string>
+    <string name="accessibility_menu_description" msgid="4458354794093858297">"اپنے آلے کو کنٹرول کرنے کے لیے ایکسیسبیلٹی مینیو ایک بڑا آن اسکرین مینیو فراہم کرتا ہے۔ آپ اپنا آلہ مقفل، والیوم اور چمک کو کنٹرول، اسکرین شاٹ لینے کے ساتھ اور مزید بہت کچھ کر سکتے ہیں۔"</string>
+    <string name="accessibility_menu_summary" msgid="340071398148208130">"بڑے مینیو کے ذریعے آلہ کنٹرول کریں"</string>
+    <string name="accessibility_menu_settings_name" msgid="1716888058785672611">"ایکسیسبیلٹی مینیو کی ترتیبات"</string>
+    <string name="accessibility_menu_large_buttons_title" msgid="8978499601044961736">"بڑے بٹنز"</string>
+    <string name="accessibility_menu_large_buttons_summary" msgid="236873938502785311">"ایکسیسبیلٹی مینیو بٹنز کا سائز بڑھائیں"</string>
+    <string name="pref_help_title" msgid="6871558837025010641">"مدد"</string>
+    <string name="brightness_percentage_label" msgid="7391554573977867369">"چمک %% <xliff:g id="PERCENTAGE">%1$s</xliff:g>"</string>
+    <string name="music_volume_percentage_label" msgid="398635599662604706">"موسیقی کا والیوم %% <xliff:g id="PERCENTAGE">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-uz/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-uz/strings.xml
new file mode 100644
index 0000000..b10c953
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-uz/strings.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="accessibility_menu_service_name" msgid="730136711554740131">"Qulayliklar menyusi"</string>
+    <string name="accessibility_menu_intro" msgid="3164193281544042394">"Qulayliklar menyusi telefoningizni boshqarish uchun katta menyuni taqdim etadi. Bu menyu orqali telefonni qulflash, ovoz balandligi va yorqinlikni boshqarish, skrinshotlar olish kabi amallarni bajarish mumkin."</string>
+    <string name="assistant_label" msgid="6796392082252272356">"Assistent"</string>
+    <string name="assistant_utterance" msgid="65509599221141377">"Assistent"</string>
+    <string name="a11y_settings_label" msgid="3977714687248445050">"Qulayliklar sozlamalari"</string>
+    <string name="power_label" msgid="7699720321491287839">"Quvvat"</string>
+    <string name="power_utterance" msgid="7444296686402104807">"Quvvat parametrlari"</string>
+    <string name="recent_apps_label" msgid="6583276995616385847">"Oxirgi ilovalar"</string>
+    <string name="lockscreen_label" msgid="648347953557887087">"Ekran qulfi"</string>
+    <string name="quick_settings_label" msgid="2999117381487601865">"Tezkor sozlamalar"</string>
+    <string name="notifications_label" msgid="6829741046963013567">"Bildirishnomalar"</string>
+    <string name="screenshot_label" msgid="863978141223970162">"Skrinshot"</string>
+    <string name="screenshot_utterance" msgid="1430760563401895074">"Skrinshot olish"</string>
+    <string name="volume_up_label" msgid="8592766918780362870">"Tovushni balandlatish"</string>
+    <string name="volume_down_label" msgid="8574981863656447346">"Tovushni pasaytirish"</string>
+    <string name="brightness_up_label" msgid="8010753822854544846">"Yorqinlikni oshirish"</string>
+    <string name="brightness_down_label" msgid="7115662941913272072">"Yorqinlikni pasaytirish"</string>
+    <string name="previous_button_content_description" msgid="840869171117765966">"Avvalgi ekranni ochish"</string>
+    <string name="next_button_content_description" msgid="6810058269847364406">"Keyingi ekranni ochish"</string>
+    <string name="accessibility_menu_description" msgid="4458354794093858297">"Qulayliklar menyusi telefoningizni boshqarish uchun katta menyuni taqdim etadi. Bu menyu orqali telefonni qulflash, ovoz balandligi va yorqinlikni boshqarish, skrinshotlar olish kabi amallarni bajarish mumkin."</string>
+    <string name="accessibility_menu_summary" msgid="340071398148208130">"Qurilmani yirik menyu orqali boshqarish"</string>
+    <string name="accessibility_menu_settings_name" msgid="1716888058785672611">"Qulayliklar menyusi sozlamalari"</string>
+    <string name="accessibility_menu_large_buttons_title" msgid="8978499601044961736">"Yirik tugmalar"</string>
+    <string name="accessibility_menu_large_buttons_summary" msgid="236873938502785311">"Qulayliklar menyusi tugmalarini kattalashtirish"</string>
+    <string name="pref_help_title" msgid="6871558837025010641">"Yordam"</string>
+    <string name="brightness_percentage_label" msgid="7391554573977867369">"Yorqinlik: <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+    <string name="music_volume_percentage_label" msgid="398635599662604706">"Musiqa tovushi balandligi: <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+</resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-vi/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-vi/strings.xml
new file mode 100644
index 0000000..f0112262
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-vi/strings.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="accessibility_menu_service_name" msgid="730136711554740131">"Trình đơn hỗ trợ tiếp cận"</string>
+    <string name="accessibility_menu_intro" msgid="3164193281544042394">"Trình đơn hỗ trợ tiếp cận cung cấp một trình đơn lớn trên màn hình dùng để điều khiển thiết bị. Bạn có thể khóa thiết bị, điều chỉnh âm lượng và độ sáng, chụp ảnh màn hình và thực hiện nhiều chức năng khác."</string>
+    <string name="assistant_label" msgid="6796392082252272356">"Trợ lý"</string>
+    <string name="assistant_utterance" msgid="65509599221141377">"Trợ lý"</string>
+    <string name="a11y_settings_label" msgid="3977714687248445050">"Cài đặt hỗ trợ tiếp cận"</string>
+    <string name="power_label" msgid="7699720321491287839">"Nguồn"</string>
+    <string name="power_utterance" msgid="7444296686402104807">"Tùy chọn nút Nguồn"</string>
+    <string name="recent_apps_label" msgid="6583276995616385847">"Ứng dụng gần đây"</string>
+    <string name="lockscreen_label" msgid="648347953557887087">"Màn hình khóa"</string>
+    <string name="quick_settings_label" msgid="2999117381487601865">"Cài đặt nhanh"</string>
+    <string name="notifications_label" msgid="6829741046963013567">"Thông báo"</string>
+    <string name="screenshot_label" msgid="863978141223970162">"Ảnh chụp màn hình"</string>
+    <string name="screenshot_utterance" msgid="1430760563401895074">"Chụp ảnh màn hình"</string>
+    <string name="volume_up_label" msgid="8592766918780362870">"Tăng âm lượng"</string>
+    <string name="volume_down_label" msgid="8574981863656447346">"Giảm âm lượng"</string>
+    <string name="brightness_up_label" msgid="8010753822854544846">"Tăng độ sáng"</string>
+    <string name="brightness_down_label" msgid="7115662941913272072">"Giảm độ sáng"</string>
+    <string name="previous_button_content_description" msgid="840869171117765966">"Chuyển đến màn hình trước"</string>
+    <string name="next_button_content_description" msgid="6810058269847364406">"Chuyển đến màn hình tiếp theo"</string>
+    <string name="accessibility_menu_description" msgid="4458354794093858297">"Trình đơn hỗ trợ tiếp cận cung cấp một trình đơn lớn trên màn hình dùng để điều khiển thiết bị. Bạn có thể khóa thiết bị, điều chỉnh âm lượng và độ sáng, chụp ảnh màn hình và thực hiện nhiều chức năng khác."</string>
+    <string name="accessibility_menu_summary" msgid="340071398148208130">"Điều khiển thiết bị qua trình đơn lớn"</string>
+    <string name="accessibility_menu_settings_name" msgid="1716888058785672611">"Cài đặt Trình đơn hỗ trợ tiếp cận"</string>
+    <string name="accessibility_menu_large_buttons_title" msgid="8978499601044961736">"Nút lớn"</string>
+    <string name="accessibility_menu_large_buttons_summary" msgid="236873938502785311">"Tăng kích thước của các nút trong Trình đơn hỗ trợ tiếp cận"</string>
+    <string name="pref_help_title" msgid="6871558837025010641">"Trợ giúp"</string>
+    <string name="brightness_percentage_label" msgid="7391554573977867369">"Độ sáng <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+    <string name="music_volume_percentage_label" msgid="398635599662604706">"Âm lượng nhạc <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+</resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-zh-rCN/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-zh-rCN/strings.xml
new file mode 100644
index 0000000..c896c77
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-zh-rCN/strings.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="accessibility_menu_service_name" msgid="730136711554740131">"无障碍功能菜单"</string>
+    <string name="accessibility_menu_intro" msgid="3164193281544042394">"无障碍功能菜单是一个放大的菜单,方便您通过屏幕控制设备,完成锁屏、控制音量和亮度、截图等操作。"</string>
+    <string name="assistant_label" msgid="6796392082252272356">"Google 助理"</string>
+    <string name="assistant_utterance" msgid="65509599221141377">"Google 助理"</string>
+    <string name="a11y_settings_label" msgid="3977714687248445050">"无障碍设置"</string>
+    <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="quick_settings_label" msgid="2999117381487601865">"快捷设置"</string>
+    <string name="notifications_label" msgid="6829741046963013567">"通知"</string>
+    <string name="screenshot_label" msgid="863978141223970162">"屏幕截图"</string>
+    <string name="screenshot_utterance" msgid="1430760563401895074">"抓取屏幕截图"</string>
+    <string name="volume_up_label" msgid="8592766918780362870">"调高音量"</string>
+    <string name="volume_down_label" msgid="8574981863656447346">"调低音量"</string>
+    <string name="brightness_up_label" msgid="8010753822854544846">"调高亮度"</string>
+    <string name="brightness_down_label" msgid="7115662941913272072">"调低亮度"</string>
+    <string name="previous_button_content_description" msgid="840869171117765966">"转到上一个屏幕"</string>
+    <string name="next_button_content_description" msgid="6810058269847364406">"转到下一个屏幕"</string>
+    <string name="accessibility_menu_description" msgid="4458354794093858297">"无障碍功能菜单是一个放大的菜单,方便您通过屏幕控制设备,完成锁屏、控制音量和亮度、截图等操作。"</string>
+    <string name="accessibility_menu_summary" msgid="340071398148208130">"使用大菜单控制设备"</string>
+    <string name="accessibility_menu_settings_name" msgid="1716888058785672611">"无障碍功能菜单设置"</string>
+    <string name="accessibility_menu_large_buttons_title" msgid="8978499601044961736">"大按钮"</string>
+    <string name="accessibility_menu_large_buttons_summary" msgid="236873938502785311">"放大无障碍功能菜单按钮"</string>
+    <string name="pref_help_title" msgid="6871558837025010641">"帮助"</string>
+    <string name="brightness_percentage_label" msgid="7391554573977867369">"亮度:<xliff:g id="PERCENTAGE">%1$s</xliff:g>%%"</string>
+    <string name="music_volume_percentage_label" msgid="398635599662604706">"音乐音量:<xliff:g id="PERCENTAGE">%1$s</xliff:g>%%"</string>
+</resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-zh-rHK/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-zh-rHK/strings.xml
new file mode 100644
index 0000000..fed2e9c
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-zh-rHK/strings.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="accessibility_menu_service_name" msgid="730136711554740131">"無障礙功能選單"</string>
+    <string name="accessibility_menu_intro" msgid="3164193281544042394">"「無障礙功能選單」是螢幕上的大型選單,用來控制裝置,方便你鎖定裝置、控制音量和亮度、擷取螢幕畫面及執行其他功能。"</string>
+    <string name="assistant_label" msgid="6796392082252272356">"Google 助理"</string>
+    <string name="assistant_utterance" msgid="65509599221141377">"Google 助理"</string>
+    <string name="a11y_settings_label" msgid="3977714687248445050">"無障礙功能設定"</string>
+    <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="quick_settings_label" msgid="2999117381487601865">"快速設定"</string>
+    <string name="notifications_label" msgid="6829741046963013567">"通知"</string>
+    <string name="screenshot_label" msgid="863978141223970162">"螢幕擷取畫面"</string>
+    <string name="screenshot_utterance" msgid="1430760563401895074">"擷取螢幕擷圖"</string>
+    <string name="volume_up_label" msgid="8592766918780362870">"調高音量"</string>
+    <string name="volume_down_label" msgid="8574981863656447346">"調低音量"</string>
+    <string name="brightness_up_label" msgid="8010753822854544846">"調光亮度"</string>
+    <string name="brightness_down_label" msgid="7115662941913272072">"調暗亮度"</string>
+    <string name="previous_button_content_description" msgid="840869171117765966">"前往上一個畫面"</string>
+    <string name="next_button_content_description" msgid="6810058269847364406">"前往下一個畫面"</string>
+    <string name="accessibility_menu_description" msgid="4458354794093858297">"「無障礙功能選單」是螢幕上的大型選單,用來控制裝置,方便你鎖定裝置、控制音量和亮度、擷取螢幕截圖及執行其他功能。"</string>
+    <string name="accessibility_menu_summary" msgid="340071398148208130">"透過大型選單控制裝置"</string>
+    <string name="accessibility_menu_settings_name" msgid="1716888058785672611">"無障礙功能選單設定"</string>
+    <string name="accessibility_menu_large_buttons_title" msgid="8978499601044961736">"大按鈕"</string>
+    <string name="accessibility_menu_large_buttons_summary" msgid="236873938502785311">"放大「無障礙功能選單按鈕」"</string>
+    <string name="pref_help_title" msgid="6871558837025010641">"說明"</string>
+    <string name="brightness_percentage_label" msgid="7391554573977867369">"光暗度:<xliff:g id="PERCENTAGE">%1$s</xliff:g>%%"</string>
+    <string name="music_volume_percentage_label" msgid="398635599662604706">"音樂音量:<xliff:g id="PERCENTAGE">%1$s</xliff:g>%%"</string>
+</resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-zh-rTW/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-zh-rTW/strings.xml
new file mode 100644
index 0000000..ac6b5c3
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-zh-rTW/strings.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="accessibility_menu_service_name" msgid="730136711554740131">"無障礙工具選單"</string>
+    <string name="accessibility_menu_intro" msgid="3164193281544042394">"無障礙工具選單是螢幕上的大型選單,可用來操控裝置,方便你鎖定裝置、控制音量和亮度、擷取螢幕畫面,以及執行其他功能。"</string>
+    <string name="assistant_label" msgid="6796392082252272356">"Google 助理"</string>
+    <string name="assistant_utterance" msgid="65509599221141377">"Google 助理"</string>
+    <string name="a11y_settings_label" msgid="3977714687248445050">"無障礙設定"</string>
+    <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="quick_settings_label" msgid="2999117381487601865">"快速設定"</string>
+    <string name="notifications_label" msgid="6829741046963013567">"通知"</string>
+    <string name="screenshot_label" msgid="863978141223970162">"螢幕截圖"</string>
+    <string name="screenshot_utterance" msgid="1430760563401895074">"擷取螢幕畫面"</string>
+    <string name="volume_up_label" msgid="8592766918780362870">"調高音量"</string>
+    <string name="volume_down_label" msgid="8574981863656447346">"調低音量"</string>
+    <string name="brightness_up_label" msgid="8010753822854544846">"調高亮度"</string>
+    <string name="brightness_down_label" msgid="7115662941913272072">"調低亮度"</string>
+    <string name="previous_button_content_description" msgid="840869171117765966">"返回上一個畫面"</string>
+    <string name="next_button_content_description" msgid="6810058269847364406">"前往下一個畫面"</string>
+    <string name="accessibility_menu_description" msgid="4458354794093858297">"無障礙選單是一種顯示在螢幕上的大型選單,可用來操控裝置的各項功能,方便你鎖定裝置、調整音量和亮度、擷取螢幕畫面,以及執行其他功能。"</string>
+    <string name="accessibility_menu_summary" msgid="340071398148208130">"透過大型選單操控裝置"</string>
+    <string name="accessibility_menu_settings_name" msgid="1716888058785672611">"無障礙選單設定"</string>
+    <string name="accessibility_menu_large_buttons_title" msgid="8978499601044961736">"大型按鈕"</string>
+    <string name="accessibility_menu_large_buttons_summary" msgid="236873938502785311">"放大無障礙選單按鈕"</string>
+    <string name="pref_help_title" msgid="6871558837025010641">"說明"</string>
+    <string name="brightness_percentage_label" msgid="7391554573977867369">"亮度 <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+    <string name="music_volume_percentage_label" msgid="398635599662604706">"音樂音量 <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+</resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-zu/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-zu/strings.xml
new file mode 100644
index 0000000..2b92ab1
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-zu/strings.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="accessibility_menu_service_name" msgid="730136711554740131">"Imenyu yokufinyeleleka"</string>
+    <string name="accessibility_menu_intro" msgid="3164193281544042394">"Imenyu yokufinyelela inikezela ngemenyu enkulu esesikrinini ukuze ulawule idivayisi yakho. Ungakhiya idivayisi yakho, ulawule ivolumu nokukhanya, uthathe izithombe-skrini, nokuningi."</string>
+    <string name="assistant_label" msgid="6796392082252272356">"Umsizi"</string>
+    <string name="assistant_utterance" msgid="65509599221141377">"I-Assistant"</string>
+    <string name="a11y_settings_label" msgid="3977714687248445050">"Izilungiselelo zokufinyelela"</string>
+    <string name="power_label" msgid="7699720321491287839">"Amandla"</string>
+    <string name="power_utterance" msgid="7444296686402104807">"Izinketho zamandla"</string>
+    <string name="recent_apps_label" msgid="6583276995616385847">"Ama-app akamuva"</string>
+    <string name="lockscreen_label" msgid="648347953557887087">"Khiya isikrini"</string>
+    <string name="quick_settings_label" msgid="2999117381487601865">"Izilungiselelo ezisheshayo"</string>
+    <string name="notifications_label" msgid="6829741046963013567">"Izaziso"</string>
+    <string name="screenshot_label" msgid="863978141223970162">"Isithombe-skrini"</string>
+    <string name="screenshot_utterance" msgid="1430760563401895074">"Thatha isithombe-skrini"</string>
+    <string name="volume_up_label" msgid="8592766918780362870">"Ivolumu phezulu"</string>
+    <string name="volume_down_label" msgid="8574981863656447346">"Ivolumu iphansi"</string>
+    <string name="brightness_up_label" msgid="8010753822854544846">"Ukukhanya kunyukile"</string>
+    <string name="brightness_down_label" msgid="7115662941913272072">"Ukukhanya kwehlile"</string>
+    <string name="previous_button_content_description" msgid="840869171117765966">"Hamba kusikrini sangaphambilini"</string>
+    <string name="next_button_content_description" msgid="6810058269847364406">"Iya kusikrini esilandelayo"</string>
+    <string name="accessibility_menu_description" msgid="4458354794093858297">"Imenyu yokufinyelela inikezela ngemenyu enkulu esesikrinini ukuze ulawule idivayisi yakho. Ungakhiya idivayisi yakho, ulawule ivolumu nokukhanya, uthathe izithombe-skrini, nokuningi."</string>
+    <string name="accessibility_menu_summary" msgid="340071398148208130">"Lawula idivayisi ngemenyu enkulu"</string>
+    <string name="accessibility_menu_settings_name" msgid="1716888058785672611">"Amasethingi Emenyu Yokufinyeleleka"</string>
+    <string name="accessibility_menu_large_buttons_title" msgid="8978499601044961736">"Izinkinobho ezinkulu"</string>
+    <string name="accessibility_menu_large_buttons_summary" msgid="236873938502785311">"Khulisa usayizi wezinkinobho zemenyu yokufinyelela"</string>
+    <string name="pref_help_title" msgid="6871558837025010641">"Usizo"</string>
+    <string name="brightness_percentage_label" msgid="7391554573977867369">"Ukukhanya <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+    <string name="music_volume_percentage_label" msgid="398635599662604706">"Ivolumu yomculo <xliff:g id="PERCENTAGE">%1$s</xliff:g> %%"</string>
+</resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuAdapter.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuAdapter.java
index 6ae65cb..c64ec6f 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuAdapter.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuAdapter.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.accessibility.accessibilitymenu.view;
 
+import android.content.Context;
 import android.graphics.Rect;
 import android.view.LayoutInflater;
 import android.view.TouchDelegate;
@@ -47,10 +48,11 @@
     private final ShortcutDrawableUtils mShortcutDrawableUtils;
 
     public A11yMenuAdapter(
-            AccessibilityMenuService service, List<A11yMenuShortcut> shortcutDataList) {
+            AccessibilityMenuService service,
+            Context displayContext, List<A11yMenuShortcut> shortcutDataList) {
         this.mService = service;
         this.mShortcutDataList = shortcutDataList;
-        mInflater = LayoutInflater.from(service);
+        mInflater = LayoutInflater.from(displayContext);
 
         mShortcutDrawableUtils = new ShortcutDrawableUtils(service);
 
@@ -75,7 +77,9 @@
 
     @Override
     public View getView(int position, View convertView, ViewGroup parent) {
-        convertView = mInflater.inflate(R.layout.grid_item, null);
+        if (convertView == null) {
+            convertView = mInflater.inflate(R.layout.grid_item, parent, false);
+        }
 
         A11yMenuShortcut shortcutItem = (A11yMenuShortcut) getItem(position);
         // Sets shortcut icon and label resource.
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuOverlayLayout.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuOverlayLayout.java
index 5b7bbe8..edd6a48 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuOverlayLayout.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuOverlayLayout.java
@@ -17,11 +17,13 @@
 package com.android.systemui.accessibility.accessibilitymenu.view;
 
 import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY;
 
 import static java.lang.Math.max;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
+import android.content.Context;
 import android.content.res.Configuration;
 import android.graphics.Insets;
 import android.graphics.PixelFormat;
@@ -135,15 +137,13 @@
             initLayoutParams();
         }
 
-        final Display display = mService.getSystemService(
-                DisplayManager.class).getDisplay(DEFAULT_DISPLAY);
-
-        mLayout = new FrameLayout(
-                mService.createDisplayContext(display).createWindowContext(
-                        WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY, null));
+        final Display display = mDisplayManager.getDisplay(DEFAULT_DISPLAY);
+        final Context context = mService.createDisplayContext(display).createWindowContext(
+                TYPE_ACCESSIBILITY_OVERLAY, null);
+        mLayout = new FrameLayout(context);
         updateLayoutPosition();
-        inflateLayoutAndSetOnTouchListener(mLayout);
-        mA11yMenuViewPager = new A11yMenuViewPager(mService);
+        inflateLayoutAndSetOnTouchListener(mLayout, context);
+        mA11yMenuViewPager = new A11yMenuViewPager(mService, context);
         mA11yMenuViewPager.configureViewPagerAndFooter(mLayout, createShortcutList(), pageIndex);
         mWindowManager.addView(mLayout, mLayoutParameter);
         mLayout.setVisibility(lastVisibilityState);
@@ -169,8 +169,8 @@
         mLayoutParameter.setTitle(mService.getString(R.string.accessibility_menu_service_name));
     }
 
-    private void inflateLayoutAndSetOnTouchListener(ViewGroup view) {
-        LayoutInflater inflater = LayoutInflater.from(mService);
+    private void inflateLayoutAndSetOnTouchListener(ViewGroup view, Context displayContext) {
+        LayoutInflater inflater = LayoutInflater.from(displayContext);
         inflater.inflate(R.layout.paged_menu, view);
         view.setOnTouchListener(mService);
     }
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuViewPager.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuViewPager.java
index 0a349e5..b969017 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuViewPager.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuViewPager.java
@@ -147,8 +147,12 @@
     /** The container layout for a11y menu. */
     private ViewGroup mA11yMenuLayout;
 
-    public A11yMenuViewPager(AccessibilityMenuService service) {
+    /** Display context for inflating views. */
+    private Context mDisplayContext;
+
+    public A11yMenuViewPager(AccessibilityMenuService service, Context displayContext) {
         this.mService = service;
+        this.mDisplayContext = displayContext;
     }
 
     /**
@@ -213,10 +217,11 @@
     }
 
     private void addGridPage(List<A11yMenuShortcut> shortcutDataListInPage) {
-        LayoutInflater inflater = LayoutInflater.from(mService);
+        LayoutInflater inflater = LayoutInflater.from(mDisplayContext);
         View view = inflater.inflate(R.layout.grid_view, null);
         GridView gridView = view.findViewById(R.id.gridview);
-        A11yMenuAdapter adapter = new A11yMenuAdapter(mService, shortcutDataListInPage);
+        A11yMenuAdapter adapter = new A11yMenuAdapter(
+                mService, mDisplayContext, shortcutDataListInPage);
         gridView.setNumColumns(GridViewParams.getGridColumnCount(mService));
         gridView.setAdapter(adapter);
         mGridPageList.add(gridView);
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
index 48dd08f..dbfa192 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
@@ -36,6 +36,7 @@
 import com.android.app.animation.Interpolators
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.internal.jank.InteractionJankMonitor.CujType
+import com.android.systemui.animation.view.LaunchableFrameLayout
 import com.android.systemui.util.registerAnimationOnBackInvoked
 import kotlin.math.roundToInt
 
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableFrameLayout.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableFrameLayout.kt
deleted file mode 100644
index 2eb503b..0000000
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableFrameLayout.kt
+++ /dev/null
@@ -1,53 +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.systemui.animation
-
-import android.content.Context
-import android.util.AttributeSet
-import android.widget.FrameLayout
-
-/** A [FrameLayout] that also implements [LaunchableView]. */
-open class LaunchableFrameLayout : FrameLayout, LaunchableView {
-    private val delegate =
-        LaunchableViewDelegate(
-            this,
-            superSetVisibility = { super.setVisibility(it) },
-        )
-
-    constructor(context: Context) : super(context)
-    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
-    constructor(
-        context: Context,
-        attrs: AttributeSet?,
-        defStyleAttr: Int
-    ) : super(context, attrs, defStyleAttr)
-
-    constructor(
-        context: Context,
-        attrs: AttributeSet?,
-        defStyleAttr: Int,
-        defStyleRes: Int
-    ) : super(context, attrs, defStyleAttr, defStyleRes)
-
-    override fun setShouldBlockVisibilityChanges(block: Boolean) {
-        delegate.setShouldBlockVisibilityChanges(block)
-    }
-
-    override fun setVisibility(visibility: Int) {
-        delegate.setVisibility(visibility)
-    }
-}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
index 8e79e3c..38b99cc 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
@@ -70,6 +70,9 @@
          * If a new layout change happens while an animation is already in progress, the animation
          * is updated to continue from the current values to the new end state.
          *
+         * A set of [excludedViews] can be passed. If any dependent view from [rootView] matches an
+         * entry in this set, changes to that view will not be animated.
+         *
          * The animator continues to respond to layout changes until [stopAnimating] is called.
          *
          * Successive calls to this method override the previous settings ([interpolator] and
@@ -82,9 +85,16 @@
         fun animate(
             rootView: View,
             interpolator: Interpolator = DEFAULT_INTERPOLATOR,
-            duration: Long = DEFAULT_DURATION
+            duration: Long = DEFAULT_DURATION,
+            excludedViews: Set<View> = emptySet()
         ): Boolean {
-            return animate(rootView, interpolator, duration, ephemeral = false)
+            return animate(
+                rootView,
+                interpolator,
+                duration,
+                ephemeral = false,
+                excludedViews = excludedViews
+            )
         }
 
         /**
@@ -95,16 +105,24 @@
         fun animateNextUpdate(
             rootView: View,
             interpolator: Interpolator = DEFAULT_INTERPOLATOR,
-            duration: Long = DEFAULT_DURATION
+            duration: Long = DEFAULT_DURATION,
+            excludedViews: Set<View> = emptySet()
         ): Boolean {
-            return animate(rootView, interpolator, duration, ephemeral = true)
+            return animate(
+                rootView,
+                interpolator,
+                duration,
+                ephemeral = true,
+                excludedViews = excludedViews
+            )
         }
 
         private fun animate(
             rootView: View,
             interpolator: Interpolator,
             duration: Long,
-            ephemeral: Boolean
+            ephemeral: Boolean,
+            excludedViews: Set<View> = emptySet()
         ): Boolean {
             if (
                 !occupiesSpace(
@@ -119,7 +137,7 @@
             }
 
             val listener = createUpdateListener(interpolator, duration, ephemeral)
-            addListener(rootView, listener, recursive = true)
+            addListener(rootView, listener, recursive = true, excludedViews = excludedViews)
             return true
         }
 
@@ -921,8 +939,11 @@
         private fun addListener(
             view: View,
             listener: View.OnLayoutChangeListener,
-            recursive: Boolean = false
+            recursive: Boolean = false,
+            excludedViews: Set<View> = emptySet()
         ) {
+            if (excludedViews.contains(view)) return
+
             // Make sure that only one listener is active at a time.
             val previousListener = view.getTag(R.id.tag_layout_listener)
             if (previousListener != null && previousListener is View.OnLayoutChangeListener) {
@@ -933,7 +954,12 @@
             view.setTag(R.id.tag_layout_listener, listener)
             if (view is ViewGroup && recursive) {
                 for (i in 0 until view.childCount) {
-                    addListener(view.getChildAt(i), listener, recursive = true)
+                    addListener(
+                        view.getChildAt(i),
+                        listener,
+                        recursive = true,
+                        excludedViews = excludedViews
+                    )
                 }
             }
         }
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/view/LaunchableFrameLayout.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/view/LaunchableFrameLayout.kt
new file mode 100644
index 0000000..7538f18
--- /dev/null
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/view/LaunchableFrameLayout.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.animation.view
+
+import android.content.Context
+import android.util.AttributeSet
+import android.widget.FrameLayout
+import com.android.systemui.animation.LaunchableView
+import com.android.systemui.animation.LaunchableViewDelegate
+
+/** A [FrameLayout] that also implements [LaunchableView]. */
+open class LaunchableFrameLayout : FrameLayout, LaunchableView {
+    private val delegate =
+        LaunchableViewDelegate(
+            this,
+            superSetVisibility = { super.setVisibility(it) },
+        )
+
+    constructor(context: Context) : super(context)
+    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
+    constructor(
+        context: Context,
+        attrs: AttributeSet?,
+        defStyleAttr: Int
+    ) : super(context, attrs, defStyleAttr)
+
+    constructor(
+        context: Context,
+        attrs: AttributeSet?,
+        defStyleAttr: Int,
+        defStyleRes: Int
+    ) : super(context, attrs, defStyleAttr, defStyleRes)
+
+    override fun setShouldBlockVisibilityChanges(block: Boolean) {
+        delegate.setShouldBlockVisibilityChanges(block)
+    }
+
+    override fun setVisibility(visibility: Int) {
+        delegate.setVisibility(visibility)
+    }
+}
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/Easings.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/Easings.kt
index 8fe1f48..4fe9f89 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/Easings.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/Easings.kt
@@ -27,37 +27,52 @@
 object Easings {
 
     /** The standard interpolator that should be used on every normal animation */
-    val StandardEasing = fromInterpolator(InterpolatorsAndroidX.STANDARD)
+    val Standard = fromInterpolator(InterpolatorsAndroidX.STANDARD)
 
     /**
      * The standard accelerating interpolator that should be used on every regular movement of
      * content that is disappearing e.g. when moving off screen.
      */
-    val StandardAccelerateEasing = fromInterpolator(InterpolatorsAndroidX.STANDARD_ACCELERATE)
+    val StandardAccelerate = fromInterpolator(InterpolatorsAndroidX.STANDARD_ACCELERATE)
 
     /**
      * The standard decelerating interpolator that should be used on every regular movement of
      * content that is appearing e.g. when coming from off screen.
      */
-    val StandardDecelerateEasing = fromInterpolator(InterpolatorsAndroidX.STANDARD_DECELERATE)
+    val StandardDecelerate = fromInterpolator(InterpolatorsAndroidX.STANDARD_DECELERATE)
 
     /** The default emphasized interpolator. Used for hero / emphasized movement of content. */
-    val EmphasizedEasing = fromInterpolator(InterpolatorsAndroidX.EMPHASIZED)
+    val Emphasized = fromInterpolator(InterpolatorsAndroidX.EMPHASIZED)
 
     /**
      * The accelerated emphasized interpolator. Used for hero / emphasized movement of content that
      * is disappearing e.g. when moving off screen.
      */
-    val EmphasizedAccelerateEasing = fromInterpolator(InterpolatorsAndroidX.EMPHASIZED_ACCELERATE)
+    val EmphasizedAccelerate = fromInterpolator(InterpolatorsAndroidX.EMPHASIZED_ACCELERATE)
 
     /**
      * The decelerating emphasized interpolator. Used for hero / emphasized movement of content that
      * is appearing e.g. when coming from off screen
      */
-    val EmphasizedDecelerateEasing = fromInterpolator(InterpolatorsAndroidX.EMPHASIZED_DECELERATE)
+    val EmphasizedDecelerate = fromInterpolator(InterpolatorsAndroidX.EMPHASIZED_DECELERATE)
 
     /** The linear interpolator. */
-    val LinearEasing = fromInterpolator(InterpolatorsAndroidX.LINEAR)
+    val Linear = fromInterpolator(InterpolatorsAndroidX.LINEAR)
+
+    /** The default legacy interpolator as defined in Material 1. Also known as FAST_OUT_SLOW_IN. */
+    val Legacy = fromInterpolator(InterpolatorsAndroidX.LEGACY)
+
+    /**
+     * The default legacy accelerating interpolator as defined in Material 1. Also known as
+     * FAST_OUT_LINEAR_IN.
+     */
+    val LegacyAccelerate = fromInterpolator(InterpolatorsAndroidX.LEGACY_ACCELERATE)
+
+    /**
+     * T The default legacy decelerating interpolator as defined in Material 1. Also known as
+     * LINEAR_OUT_SLOW_IN.
+     */
+    val LegacyDecelerate = fromInterpolator(InterpolatorsAndroidX.LEGACY_DECELERATE)
 
     private fun fromInterpolator(source: Interpolator) = Easing { x -> source.getInterpolation(x) }
 }
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt
index d4a81f9..ac1ef15 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt
@@ -70,8 +70,10 @@
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.dp
-import androidx.lifecycle.ViewTreeLifecycleOwner
-import androidx.lifecycle.ViewTreeViewModelStoreOwner
+import androidx.lifecycle.findViewTreeLifecycleOwner
+import androidx.lifecycle.findViewTreeViewModelStoreOwner
+import androidx.lifecycle.setViewTreeLifecycleOwner
+import androidx.lifecycle.setViewTreeViewModelStoreOwner
 import com.android.systemui.animation.Expandable
 import com.android.systemui.animation.LaunchAnimator
 import kotlin.math.max
@@ -368,13 +370,10 @@
                     context,
                     overlay,
                 )
-            ViewTreeLifecycleOwner.set(
-                overlayViewGroup,
-                ViewTreeLifecycleOwner.get(composeViewRoot),
-            )
-            ViewTreeViewModelStoreOwner.set(
-                overlayViewGroup,
-                ViewTreeViewModelStoreOwner.get(composeViewRoot),
+
+            overlayViewGroup.setViewTreeLifecycleOwner(composeViewRoot.findViewTreeLifecycleOwner())
+            overlayViewGroup.setViewTreeViewModelStoreOwner(
+                composeViewRoot.findViewTreeViewModelStoreOwner()
             )
             ViewTreeSavedStateRegistryOwner.set(
                 overlayViewGroup,
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 cc33745..82fe3f2 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
@@ -24,6 +24,9 @@
 import com.android.systemui.multishade.ui.viewmodel.MultiShadeViewModel
 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
+import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
 import com.android.systemui.util.time.SystemClock
 
 /** The Compose facade, when Compose is *not* available. */
@@ -58,6 +61,14 @@
         throwComposeUnavailableError()
     }
 
+    override fun createSceneContainerView(
+        context: Context,
+        viewModel: SceneContainerViewModel,
+        sceneByKey: Map<SceneKey, Scene>,
+    ): View {
+        throwComposeUnavailableError()
+    }
+
     private fun throwComposeUnavailableError(): Nothing {
         error(
             "Compose is not available. Make sure to check isComposeAvailable() before calling any" +
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 0e79b18..7926f92 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
@@ -29,6 +29,11 @@
 import com.android.systemui.people.ui.viewmodel.PeopleViewModel
 import com.android.systemui.qs.footer.ui.compose.FooterActions
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
+import com.android.systemui.scene.shared.model.Scene
+import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.ui.composable.ComposableScene
+import com.android.systemui.scene.ui.composable.SceneContainer
+import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
 import com.android.systemui.util.time.SystemClock
 
 /** The Compose facade, when Compose is available. */
@@ -71,4 +76,22 @@
             }
         }
     }
+
+    override fun createSceneContainerView(
+        context: Context,
+        viewModel: SceneContainerViewModel,
+        sceneByKey: Map<SceneKey, Scene>,
+    ): View {
+        return ComposeView(context).apply {
+            setContent {
+                PlatformTheme {
+                    SceneContainer(
+                        viewModel = viewModel,
+                        sceneByKey =
+                            sceneByKey.mapValues { (_, scene) -> scene as ComposableScene },
+                    )
+                }
+            }
+        }
+    }
 }
diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeInitializerImpl.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeInitializerImpl.kt
index fbd7f83..1674591 100644
--- a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeInitializerImpl.kt
+++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeInitializerImpl.kt
@@ -17,9 +17,9 @@
 package com.android.systemui.compose
 
 import android.view.View
+import androidx.lifecycle.findViewTreeLifecycleOwner
+import androidx.lifecycle.setViewTreeLifecycleOwner
 import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.ViewTreeLifecycleOwner
-import androidx.savedstate.SavedStateRegistry
 import androidx.savedstate.SavedStateRegistryController
 import androidx.savedstate.SavedStateRegistryOwner
 import com.android.compose.animation.ViewTreeSavedStateRegistryOwner
@@ -27,7 +27,7 @@
 
 internal object ComposeInitializerImpl : ComposeInitializer {
     override fun onAttachedToWindow(root: View) {
-        if (ViewTreeLifecycleOwner.get(root) != null) {
+        if (root.findViewTreeLifecycleOwner() != null) {
             error("root $root already has a LifecycleOwner")
         }
 
@@ -54,7 +54,8 @@
 
                 override val savedStateRegistry = savedStateRegistryController.savedStateRegistry
 
-                override fun getLifecycle(): Lifecycle = lifecycleOwner.lifecycle
+                override val lifecycle: Lifecycle
+                    get() = lifecycleOwner.lifecycle
             }
 
         // We must call [ViewLifecycleOwner.onCreate] after creating the [SavedStateRegistryOwner]
@@ -64,13 +65,13 @@
 
         // Set the owners on the root. They will be reused by any ComposeView inside the root
         // hierarchy.
-        ViewTreeLifecycleOwner.set(root, lifecycleOwner)
+        root.setViewTreeLifecycleOwner(lifecycleOwner)
         ViewTreeSavedStateRegistryOwner.set(root, savedStateRegistryOwner)
     }
 
     override fun onDetachedFromWindow(root: View) {
-        (ViewTreeLifecycleOwner.get(root) as ViewLifecycleOwner).onDestroy()
-        ViewTreeLifecycleOwner.set(root, null)
+        (root.findViewTreeLifecycleOwner() as ViewLifecycleOwner).onDestroy()
+        root.setViewTreeLifecycleOwner(null)
         ViewTreeSavedStateRegistryOwner.set(root, null)
     }
 }
diff --git a/packages/SystemUI/compose/features/Android.bp b/packages/SystemUI/compose/features/Android.bp
index 8f8bb1b..c6438c9 100644
--- a/packages/SystemUI/compose/features/Android.bp
+++ b/packages/SystemUI/compose/features/Android.bp
@@ -34,6 +34,7 @@
         "PlatformComposeCore",
 
         "androidx.compose.runtime_runtime",
+        "androidx.compose.animation_animation-graphics",
         "androidx.compose.material3_material3",
         "androidx.activity_activity-compose",
     ],
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 240bace..d83596e 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
@@ -99,7 +99,10 @@
         horizontalAlignment = Alignment.CenterHorizontally,
         verticalArrangement = Arrangement.spacedBy(60.dp),
         modifier =
-            modifier.background(MaterialTheme.colorScheme.surface).fillMaxSize().padding(32.dp)
+            modifier
+                .fillMaxSize()
+                .background(MaterialTheme.colorScheme.surface)
+                .padding(start = 32.dp, top = 92.dp, end = 32.dp, bottom = 32.dp)
     ) {
         Crossfade(
             targetState = message,
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 8844114..b3d2e35 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
@@ -131,7 +131,7 @@
                         animationSpec =
                             tween(
                                 durationMillis = SELECTED_DOT_REACTION_ANIMATION_DURATION_MS,
-                                easing = Easings.StandardAccelerateEasing,
+                                easing = Easings.StandardAccelerate,
                             ),
                     )
                 } else {
@@ -140,7 +140,7 @@
                         animationSpec =
                             tween(
                                 durationMillis = SELECTED_DOT_RETRACT_ANIMATION_DURATION_MS,
-                                easing = Easings.StandardDecelerateEasing,
+                                easing = Easings.StandardDecelerate,
                             ),
                     )
                 }
@@ -333,7 +333,7 @@
                                         FAILURE_ANIMATION_DOT_SHRINK_ANIMATION_DURATION_MS,
                                     delayMillis =
                                         rowIndex * FAILURE_ANIMATION_DOT_SHRINK_STAGGER_DELAY_MS,
-                                    easing = Easings.LinearEasing,
+                                    easing = Easings.Linear,
                                 ),
                         )
 
@@ -343,7 +343,7 @@
                                 tween(
                                     durationMillis =
                                         FAILURE_ANIMATION_DOT_REVERT_ANIMATION_DURATION,
-                                    easing = Easings.StandardEasing,
+                                    easing = Easings.Standard,
                                 ),
                         )
                     }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt
index 968e5ab..85178bc 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt
@@ -14,28 +14,30 @@
  * limitations under the License.
  */
 
-@file:OptIn(ExperimentalAnimationApi::class)
+@file:OptIn(ExperimentalAnimationApi::class, ExperimentalAnimationGraphicsApi::class)
 
 package com.android.systemui.bouncer.ui.composable
 
 import android.view.HapticFeedbackConstants
-import androidx.compose.animation.AnimatedVisibility
 import androidx.compose.animation.ExperimentalAnimationApi
 import androidx.compose.animation.animateColorAsState
-import androidx.compose.animation.animateContentSize
+import androidx.compose.animation.core.Animatable
 import androidx.compose.animation.core.AnimationSpec
-import androidx.compose.animation.core.LinearEasing
+import androidx.compose.animation.core.AnimationVector1D
+import androidx.compose.animation.core.MutableTransitionState
+import androidx.compose.animation.core.Transition
+import androidx.compose.animation.core.animateDp
 import androidx.compose.animation.core.animateDpAsState
+import androidx.compose.animation.core.animateFloatAsState
+import androidx.compose.animation.core.snap
 import androidx.compose.animation.core.tween
-import androidx.compose.animation.fadeIn
-import androidx.compose.animation.fadeOut
-import androidx.compose.animation.scaleIn
-import androidx.compose.animation.scaleOut
-import androidx.compose.animation.slideInHorizontally
-import androidx.compose.animation.slideOutHorizontally
-import androidx.compose.foundation.background
+import androidx.compose.animation.core.updateTransition
+import androidx.compose.animation.graphics.ExperimentalAnimationGraphicsApi
+import androidx.compose.animation.graphics.res.animatedVectorResource
+import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter
+import androidx.compose.animation.graphics.vector.AnimatedImageVector
+import androidx.compose.foundation.Image
 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
@@ -43,38 +45,49 @@
 import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.heightIn
 import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.foundation.layout.wrapContentSize
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
+import androidx.compose.runtime.key
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.runtime.setValue
+import androidx.compose.runtime.snapshotFlow
+import androidx.compose.runtime.snapshots.SnapshotStateList
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.drawBehind
 import androidx.compose.ui.geometry.CornerRadius
 import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.ColorFilter
+import androidx.compose.ui.graphics.graphicsLayer
 import androidx.compose.ui.input.pointer.pointerInput
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.layout.Layout
 import androidx.compose.ui.platform.LocalView
+import androidx.compose.ui.unit.Constraints
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
 import com.android.compose.animation.Easings
 import com.android.compose.grid.VerticalGrid
+import com.android.internal.R.id.image
 import com.android.systemui.R
+import com.android.systemui.bouncer.ui.viewmodel.ActionButtonAppearance
+import com.android.systemui.bouncer.ui.viewmodel.EnteredKey
 import com.android.systemui.bouncer.ui.viewmodel.PinBouncerViewModel
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.common.ui.compose.Icon
 import com.android.systemui.compose.modifiers.thenIf
-import kotlin.math.max
 import kotlin.time.Duration.Companion.milliseconds
 import kotlin.time.DurationUnit
 import kotlinx.coroutines.async
+import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.launch
 
@@ -86,139 +99,305 @@
     // Report that the UI is shown to let the view-model run some logic.
     LaunchedEffect(Unit) { viewModel.onShown() }
 
-    // The length of the PIN input received so far, so we know how many dots to render.
-    val pinLength: Pair<Int, Int> by viewModel.pinLengths.collectAsState()
-    val isInputEnabled: Boolean by viewModel.isInputEnabled.collectAsState()
-    val animateFailure: Boolean by viewModel.animateFailure.collectAsState()
-
-    // Show the failure animation if the user entered the wrong input.
-    LaunchedEffect(animateFailure) {
-        if (animateFailure) {
-            showFailureAnimation()
-            viewModel.onFailureAnimationShown()
-        }
-    }
-
     Column(
         horizontalAlignment = Alignment.CenterHorizontally,
         modifier = modifier,
     ) {
-        Row(
-            horizontalArrangement = Arrangement.spacedBy(12.dp),
-            modifier = Modifier.heightIn(min = 16.dp).animateContentSize(),
-        ) {
-            // TODO(b/281871687): add support for dot shapes.
-            val (previousPinLength, currentPinLength) = pinLength
-            val dotCount = max(previousPinLength, currentPinLength) + 1
-            repeat(dotCount) { index ->
-                AnimatedVisibility(
-                    visible = index < currentPinLength,
-                    enter = fadeIn() + scaleIn() + slideInHorizontally(),
-                    exit = fadeOut() + scaleOut() + slideOutHorizontally(),
-                ) {
-                    Box(
-                        modifier =
-                            Modifier.size(16.dp)
-                                .background(
-                                    MaterialTheme.colorScheme.onSurfaceVariant,
-                                    CircleShape,
-                                )
-                    )
-                }
-            }
-        }
-
+        PinInputDisplay(viewModel)
         Spacer(Modifier.height(100.dp))
+        PinPad(viewModel)
+    }
+}
 
-        VerticalGrid(
-            columns = 3,
-            verticalSpacing = 12.dp,
-            horizontalSpacing = 20.dp,
-        ) {
-            repeat(9) { index ->
-                val digit = index + 1
-                PinButton(
-                    onClicked = { viewModel.onPinButtonClicked(digit) },
-                    isEnabled = isInputEnabled,
-                ) { contentColor ->
-                    PinDigit(digit, contentColor)
+@Composable
+private fun PinInputDisplay(viewModel: PinBouncerViewModel) {
+    val currentPinEntries: List<EnteredKey> by viewModel.pinEntries.collectAsState()
+
+    // visiblePinEntries keeps pins removed from currentPinEntries in the composition until their
+    // disappear-animation completed. The list is sorted by the natural ordering of EnteredKey,
+    // which is guaranteed to produce the original edit order, since the model only modifies entries
+    // at the end.
+    val visiblePinEntries = remember { SnapshotStateList<EnteredKey>() }
+    currentPinEntries.forEach {
+        val index = visiblePinEntries.binarySearch(it)
+        if (index < 0) {
+            val insertionPoint = -(index + 1)
+            visiblePinEntries.add(insertionPoint, it)
+        }
+    }
+
+    Row(
+        modifier =
+            Modifier.heightIn(min = entryShapeSize)
+                // Pins overflowing horizontally should still be shown as scrolling.
+                .wrapContentSize(unbounded = true),
+    ) {
+        visiblePinEntries.forEachIndexed { index, entry ->
+            key(entry) {
+                val visibility = remember {
+                    MutableTransitionState<EntryVisibility>(EntryVisibility.Hidden)
                 }
-            }
+                visibility.targetState =
+                    when {
+                        currentPinEntries.isEmpty() && visiblePinEntries.size > 1 ->
+                            EntryVisibility.BulkHidden(index, visiblePinEntries.size)
+                        currentPinEntries.contains(entry) -> EntryVisibility.Shown
+                        else -> EntryVisibility.Hidden
+                    }
 
-            PinButton(
-                onClicked = { viewModel.onBackspaceButtonClicked() },
-                onLongPressed = { viewModel.onBackspaceButtonLongPressed() },
-                isEnabled = isInputEnabled,
-                isIconButton = true,
-            ) { contentColor ->
-                PinIcon(
-                    Icon.Resource(
-                        res = R.drawable.ic_backspace_24dp,
-                        contentDescription =
-                            ContentDescription.Resource(R.string.keyboardview_keycode_delete),
-                    ),
-                    contentColor,
-                )
-            }
+                val shape = viewModel.pinShapes.getShape(entry.sequenceNumber)
+                PinInputEntry(shape, updateTransition(visibility, label = "Pin Entry $entry"))
 
-            PinButton(
-                onClicked = { viewModel.onPinButtonClicked(0) },
-                isEnabled = isInputEnabled,
-            ) { contentColor ->
-                PinDigit(0, contentColor)
-            }
-
-            PinButton(
-                onClicked = { viewModel.onAuthenticateButtonClicked() },
-                isEnabled = isInputEnabled,
-                isIconButton = true,
-            ) { contentColor ->
-                PinIcon(
-                    Icon.Resource(
-                        res = R.drawable.ic_keyboard_tab_36dp,
-                        contentDescription =
-                            ContentDescription.Resource(R.string.keyboardview_keycode_enter),
-                    ),
-                    contentColor,
-                )
+                LaunchedEffect(entry) {
+                    // Remove entry from visiblePinEntries once the hide transition completed.
+                    snapshotFlow {
+                            visibility.currentState == visibility.targetState &&
+                                visibility.targetState != EntryVisibility.Shown
+                        }
+                        .collect { isRemoved ->
+                            if (isRemoved) {
+                                visiblePinEntries.remove(entry)
+                            }
+                        }
+                }
             }
         }
     }
 }
 
+private sealed class EntryVisibility {
+    object Shown : EntryVisibility()
+
+    object Hidden : EntryVisibility()
+
+    /**
+     * Same as [Hidden], but applies when multiple entries are hidden simultaneously, without
+     * collapsing during the hide.
+     */
+    data class BulkHidden(val staggerIndex: Int, val totalEntryCount: Int) : EntryVisibility()
+}
+
 @Composable
-private fun PinDigit(
+private fun PinInputEntry(shapeResourceId: Int, transition: Transition<EntryVisibility>) {
+    // spec: http://shortn/_DEhE3Xl2bi
+    val dismissStaggerDelayMs = 33
+    val dismissDurationMs = 450
+    val expansionDurationMs = 250
+    val shapeCollapseDurationMs = 200
+
+    val animatedEntryWidth by
+        transition.animateDp(
+            transitionSpec = {
+                when (val target = targetState) {
+                    is EntryVisibility.BulkHidden ->
+                        // only collapse horizontal space once all entries are removed
+                        snap(dismissDurationMs + dismissStaggerDelayMs * target.totalEntryCount)
+                    else -> tween(expansionDurationMs, easing = Easings.Standard)
+                }
+            },
+            label = "entry space"
+        ) { state ->
+            if (state == EntryVisibility.Shown) entryShapeSize else 0.dp
+        }
+
+    val animatedShapeSize by
+        transition.animateDp(
+            transitionSpec = {
+                when {
+                    EntryVisibility.Hidden isTransitioningTo EntryVisibility.Shown -> {
+                        // The AVD contains the entry transition.
+                        snap()
+                    }
+                    targetState is EntryVisibility.BulkHidden -> {
+                        val target = targetState as EntryVisibility.BulkHidden
+                        tween(
+                            dismissDurationMs,
+                            delayMillis = target.staggerIndex * dismissStaggerDelayMs,
+                            easing = Easings.Legacy,
+                        )
+                    }
+                    else -> tween(shapeCollapseDurationMs, easing = Easings.StandardDecelerate)
+                }
+            },
+            label = "shape size"
+        ) { state ->
+            if (state == EntryVisibility.Shown) entryShapeSize else 0.dp
+        }
+
+    val dotColor = MaterialTheme.colorScheme.onSurfaceVariant
+    Layout(
+        content = {
+            val image = AnimatedImageVector.animatedVectorResource(shapeResourceId)
+            var atEnd by remember { mutableStateOf(false) }
+            Image(
+                painter = rememberAnimatedVectorPainter(image, atEnd),
+                contentDescription = null,
+                contentScale = ContentScale.Crop,
+                colorFilter = ColorFilter.tint(dotColor),
+            )
+            LaunchedEffect(Unit) { atEnd = true }
+        }
+    ) { measurables, _ ->
+        val shapeSizePx = animatedShapeSize.roundToPx()
+        val placeable = measurables.single().measure(Constraints.fixed(shapeSizePx, shapeSizePx))
+
+        layout(animatedEntryWidth.roundToPx(), entryShapeSize.roundToPx()) {
+            placeable.place(
+                ((animatedEntryWidth - animatedShapeSize) / 2f).roundToPx(),
+                ((entryShapeSize - animatedShapeSize) / 2f).roundToPx()
+            )
+        }
+    }
+}
+
+@Composable
+private fun PinPad(viewModel: PinBouncerViewModel) {
+    val isInputEnabled: Boolean by viewModel.isInputEnabled.collectAsState()
+    val backspaceButtonAppearance by viewModel.backspaceButtonAppearance.collectAsState()
+    val confirmButtonAppearance by viewModel.confirmButtonAppearance.collectAsState()
+    val animateFailure: Boolean by viewModel.animateFailure.collectAsState()
+
+    val buttonScaleAnimatables = remember { List(12) { Animatable(1f) } }
+    LaunchedEffect(animateFailure) {
+        // Show the failure animation if the user entered the wrong input.
+        if (animateFailure) {
+            showFailureAnimation(buttonScaleAnimatables)
+            viewModel.onFailureAnimationShown()
+        }
+    }
+
+    VerticalGrid(
+        columns = 3,
+        verticalSpacing = 12.dp,
+        horizontalSpacing = 20.dp,
+    ) {
+        repeat(9) { index ->
+            DigitButton(
+                index + 1,
+                isInputEnabled,
+                viewModel::onPinButtonClicked,
+                buttonScaleAnimatables[index]::value,
+            )
+        }
+
+        ActionButton(
+            icon =
+                Icon.Resource(
+                    res = R.drawable.ic_backspace_24dp,
+                    contentDescription =
+                        ContentDescription.Resource(R.string.keyboardview_keycode_delete),
+                ),
+            isInputEnabled = isInputEnabled,
+            onClicked = viewModel::onBackspaceButtonClicked,
+            onLongPressed = viewModel::onBackspaceButtonLongPressed,
+            appearance = backspaceButtonAppearance,
+            scaling = buttonScaleAnimatables[9]::value,
+        )
+
+        DigitButton(
+            0,
+            isInputEnabled,
+            viewModel::onPinButtonClicked,
+            buttonScaleAnimatables[10]::value,
+        )
+
+        ActionButton(
+            icon =
+                Icon.Resource(
+                    res = R.drawable.ic_keyboard_tab_36dp,
+                    contentDescription =
+                        ContentDescription.Resource(R.string.keyboardview_keycode_enter),
+                ),
+            isInputEnabled = isInputEnabled,
+            onClicked = viewModel::onAuthenticateButtonClicked,
+            appearance = confirmButtonAppearance,
+            scaling = buttonScaleAnimatables[11]::value,
+        )
+    }
+}
+
+@Composable
+private fun DigitButton(
     digit: Int,
-    contentColor: Color,
+    isInputEnabled: Boolean,
+    onClicked: (Int) -> Unit,
+    scaling: () -> Float,
 ) {
-    // TODO(b/281878426): once "color: () -> Color" (added to BasicText in aosp/2568972) makes it
-    //  into Text, use that here, to animate more efficiently.
-    Text(
-        text = digit.toString(),
-        style = MaterialTheme.typography.headlineLarge,
-        color = contentColor,
-    )
+    PinPadButton(
+        onClicked = { onClicked(digit) },
+        isEnabled = isInputEnabled,
+        backgroundColor = MaterialTheme.colorScheme.surfaceVariant,
+        foregroundColor = MaterialTheme.colorScheme.onSurfaceVariant,
+        modifier =
+            Modifier.graphicsLayer {
+                val scale = scaling()
+                scaleX = scale
+                scaleY = scale
+            }
+    ) { contentColor ->
+        // TODO(b/281878426): once "color: () -> Color" (added to BasicText in aosp/2568972) makes
+        // it into Text, use that here, to animate more efficiently.
+        Text(
+            text = digit.toString(),
+            style = MaterialTheme.typography.headlineLarge,
+            color = contentColor(),
+        )
+    }
 }
 
 @Composable
-private fun PinIcon(
+private fun ActionButton(
     icon: Icon,
-    contentColor: Color,
+    isInputEnabled: Boolean,
+    onClicked: () -> Unit,
+    onLongPressed: (() -> Unit)? = null,
+    appearance: ActionButtonAppearance,
+    scaling: () -> Float,
 ) {
-    Icon(
-        icon = icon,
-        tint = contentColor,
-    )
+    val isHidden = appearance == ActionButtonAppearance.Hidden
+    val hiddenAlpha by animateFloatAsState(if (isHidden) 0f else 1f, label = "Action button alpha")
+
+    val foregroundColor =
+        when (appearance) {
+            ActionButtonAppearance.Shown -> MaterialTheme.colorScheme.onSecondaryContainer
+            else -> MaterialTheme.colorScheme.onSurface
+        }
+    val backgroundColor =
+        when (appearance) {
+            ActionButtonAppearance.Shown -> MaterialTheme.colorScheme.secondaryContainer
+            else -> MaterialTheme.colorScheme.surface
+        }
+
+    PinPadButton(
+        onClicked = onClicked,
+        onLongPressed = onLongPressed,
+        isEnabled = isInputEnabled && !isHidden,
+        backgroundColor = backgroundColor,
+        foregroundColor = foregroundColor,
+        modifier =
+            Modifier.graphicsLayer {
+                alpha = hiddenAlpha
+                val scale = scaling()
+                scaleX = scale
+                scaleY = scale
+            }
+    ) { contentColor ->
+        Icon(
+            icon = icon,
+            tint = contentColor(),
+        )
+    }
 }
 
 @Composable
-private fun PinButton(
+private fun PinPadButton(
     onClicked: () -> Unit,
     isEnabled: Boolean,
+    backgroundColor: Color,
+    foregroundColor: Color,
     modifier: Modifier = Modifier,
     onLongPressed: (() -> Unit)? = null,
-    isIconButton: Boolean = false,
-    content: @Composable (contentColor: Color) -> Unit,
+    content: @Composable (contentColor: () -> Color) -> Unit,
 ) {
     var isPressed: Boolean by remember { mutableStateOf(false) }
 
@@ -252,18 +431,16 @@
         animateColorAsState(
             when {
                 isPressed -> MaterialTheme.colorScheme.primary
-                isIconButton -> MaterialTheme.colorScheme.secondaryContainer
-                else -> MaterialTheme.colorScheme.surfaceVariant
+                else -> backgroundColor
             },
             label = "Pin button container color",
             animationSpec = colorAnimationSpec
         )
-    val contentColor: Color by
+    val contentColor =
         animateColorAsState(
             when {
                 isPressed -> MaterialTheme.colorScheme.onPrimary
-                isIconButton -> MaterialTheme.colorScheme.onSecondaryContainer
-                else -> MaterialTheme.colorScheme.onSurfaceVariant
+                else -> foregroundColor
             },
             label = "Pin button container color",
             animationSpec = colorAnimationSpec
@@ -302,19 +479,50 @@
                     }
                 },
     ) {
-        content(contentColor)
+        content(contentColor::value)
     }
 }
 
-private fun showFailureAnimation() {
-    // TODO(b/282730134): implement.
+private suspend fun showFailureAnimation(
+    buttonScaleAnimatables: List<Animatable<Float, AnimationVector1D>>
+) {
+    coroutineScope {
+        buttonScaleAnimatables.forEachIndexed { index, animatable ->
+            launch {
+                animatable.animateTo(
+                    targetValue = pinButtonErrorShrinkFactor,
+                    animationSpec =
+                        tween(
+                            durationMillis = pinButtonErrorShrinkMs,
+                            delayMillis = index * pinButtonErrorStaggerDelayMs,
+                            easing = Easings.Linear,
+                        ),
+                )
+
+                animatable.animateTo(
+                    targetValue = 1f,
+                    animationSpec =
+                        tween(
+                            durationMillis = pinButtonErrorRevertMs,
+                            easing = Easings.Legacy,
+                        ),
+                )
+            }
+        }
+    }
 }
 
+private val entryShapeSize = 30.dp
+
 private val pinButtonSize = 84.dp
+private val pinButtonErrorShrinkFactor = 67.dp / pinButtonSize
+private const val pinButtonErrorShrinkMs = 50
+private const val pinButtonErrorStaggerDelayMs = 33
+private const val pinButtonErrorRevertMs = 617
 
 // Pin button motion spec: http://shortn/_9TTIG6SoEa
 private val pinButtonPressedDuration = 100.milliseconds
-private val pinButtonPressedEasing = LinearEasing
+private val pinButtonPressedEasing = Easings.Linear
 private val pinButtonHoldTime = 33.milliseconds
 private val pinButtonReleasedDuration = 420.milliseconds
-private val pinButtonReleasedEasing = Easings.StandardEasing
+private val pinButtonReleasedEasing = Easings.Standard
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 ca91b8a..38b751c 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,15 +17,19 @@
 
 package com.android.systemui.notifications.ui.composable
 
+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.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.ui.Alignment
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
 import androidx.compose.ui.unit.dp
 
 @Composable
@@ -34,10 +38,26 @@
 ) {
     // TODO(b/272779828): implement.
     Column(
-        modifier = modifier.fillMaxWidth().defaultMinSize(minHeight = 300.dp).padding(4.dp),
+        modifier =
+            modifier
+                .fillMaxWidth()
+                .defaultMinSize(minHeight = 300.dp)
+                .clip(RoundedCornerShape(32.dp))
+                .background(MaterialTheme.colorScheme.surface)
+                .padding(16.dp),
     ) {
-        Text("Notifications", modifier = Modifier.align(Alignment.CenterHorizontally))
+        Text(
+            text = "Notifications",
+            modifier = Modifier.align(Alignment.CenterHorizontally),
+            style = MaterialTheme.typography.titleLarge,
+            color = MaterialTheme.colorScheme.onSurface,
+        )
         Spacer(modifier = Modifier.weight(1f))
-        Text("Shelf", modifier = Modifier.align(Alignment.CenterHorizontally))
+        Text(
+            text = "Shelf",
+            modifier = Modifier.align(Alignment.CenterHorizontally),
+            style = MaterialTheme.typography.titleSmall,
+            color = MaterialTheme.colorScheme.onSurface,
+        )
     }
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt
index 75bf281..13acde2 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt
@@ -121,8 +121,8 @@
         }
     }
 
-    val backgroundColor = colorAttr(R.attr.underSurfaceColor)
-    val contentColor = LocalAndroidColorScheme.current.deprecated.textColorPrimary
+    val backgroundColor = colorAttr(R.attr.underSurface)
+    val contentColor = LocalAndroidColorScheme.current.onSurface
     val backgroundTopRadius = dimensionResource(R.dimen.qs_corner_radius)
     val backgroundModifier =
         remember(
@@ -268,7 +268,7 @@
     val interactionSource = remember { MutableInteractionSource() }
 
     Expandable(
-        color = colorAttr(R.attr.offStateColor),
+        color = colorAttr(R.attr.shadeInactive),
         shape = CircleShape,
         onClick = onClick,
         interactionSource = interactionSource,
@@ -287,7 +287,7 @@
                     number.toString(),
                     modifier = Modifier.align(Alignment.Center),
                     style = MaterialTheme.typography.bodyLarge,
-                    color = LocalAndroidColorScheme.current.deprecated.textColorPrimary,
+                    color = colorAttr(R.attr.onShadeInactiveVariant),
                     // TODO(b/242040009): This should only use a standard text style instead and
                     // should not override the text size.
                     fontSize = 18.sp,
@@ -305,7 +305,7 @@
 @Composable
 private fun NewChangesDot(modifier: Modifier = Modifier) {
     val contentDescription = stringResource(R.string.fgs_dot_content_description)
-    val color = LocalAndroidColorScheme.current.deprecated.colorAccentTertiary
+    val color = LocalAndroidColorScheme.current.tertiary
 
     Canvas(modifier.size(12.dp).semantics { this.contentDescription = contentDescription }) {
         drawCircle(color)
@@ -323,10 +323,9 @@
 ) {
     Expandable(
         shape = CircleShape,
-        color = colorAttr(R.attr.underSurfaceColor),
-        contentColor = LocalAndroidColorScheme.current.deprecated.textColorSecondary,
-        borderStroke =
-            BorderStroke(1.dp, LocalAndroidColorScheme.current.deprecated.colorBackground),
+        color = colorAttr(R.attr.underSurface),
+        contentColor = LocalAndroidColorScheme.current.onSurfaceVariant,
+        borderStroke = BorderStroke(1.dp, colorAttr(R.attr.onShadeActive)),
         modifier = modifier.padding(horizontal = 4.dp),
         onClick = onClick,
     ) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/QuickSettings.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/QuickSettings.kt
index 665d6dd..1bb341c 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/QuickSettings.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/QuickSettings.kt
@@ -17,15 +17,19 @@
 
 package com.android.systemui.qs.footer.ui.compose
 
+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.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.ui.Alignment
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
 import androidx.compose.ui.unit.dp
 
 @Composable
@@ -34,10 +38,26 @@
 ) {
     // TODO(b/272780058): implement.
     Column(
-        modifier = modifier.fillMaxWidth().defaultMinSize(minHeight = 300.dp).padding(4.dp),
+        modifier =
+            modifier
+                .fillMaxWidth()
+                .defaultMinSize(minHeight = 300.dp)
+                .clip(RoundedCornerShape(32.dp))
+                .background(MaterialTheme.colorScheme.primary)
+                .padding(16.dp),
     ) {
-        Text("Quick settings", modifier = Modifier.align(Alignment.CenterHorizontally))
+        Text(
+            text = "Quick settings",
+            modifier = Modifier.align(Alignment.CenterHorizontally),
+            style = MaterialTheme.typography.titleLarge,
+            color = MaterialTheme.colorScheme.onPrimary,
+        )
         Spacer(modifier = Modifier.weight(1f))
-        Text("QS footer actions", modifier = Modifier.align(Alignment.CenterHorizontally))
+        Text(
+            text = "QS footer actions",
+            modifier = Modifier.align(Alignment.CenterHorizontally),
+            style = MaterialTheme.typography.titleSmall,
+            color = MaterialTheme.colorScheme.onPrimary,
+        )
     }
 }
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 20e1751..27358f5 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
@@ -16,18 +16,19 @@
 
 package com.android.systemui.shade.ui.composable
 
+import androidx.compose.foundation.clickable
 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.material3.Button
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Text
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.unit.dp
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.notifications.ui.composable.Notifications
+import com.android.systemui.qs.footer.ui.compose.QuickSettings
 import com.android.systemui.scene.shared.model.Direction
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
@@ -62,12 +63,7 @@
     override fun Content(
         containerName: String,
         modifier: Modifier,
-    ) {
-        ShadeScene(
-            viewModel = viewModel,
-            modifier = modifier,
-        )
-    }
+    ) = ShadeScene(viewModel, modifier)
 
     private fun destinationScenes(
         up: SceneKey,
@@ -84,23 +80,15 @@
     viewModel: ShadeSceneViewModel,
     modifier: Modifier = Modifier,
 ) {
-    // TODO(b/280887022): implement the real UI.
-
-    Box(modifier = modifier) {
-        Column(
-            horizontalAlignment = Alignment.CenterHorizontally,
-            modifier = Modifier.align(Alignment.Center)
-        ) {
-            Text("Shade", style = MaterialTheme.typography.headlineMedium)
-            Row(
-                horizontalArrangement = Arrangement.spacedBy(8.dp),
-            ) {
-                Button(
-                    onClick = { viewModel.onContentClicked() },
-                ) {
-                    Text("Open some content")
-                }
-            }
-        }
+    Column(
+        horizontalAlignment = Alignment.CenterHorizontally,
+        verticalArrangement = Arrangement.spacedBy(16.dp),
+        modifier =
+            Modifier.fillMaxSize()
+                .clickable(onClick = { viewModel.onContentClicked() })
+                .padding(horizontal = 16.dp, vertical = 48.dp)
+    ) {
+        QuickSettings(modifier = modifier.height(160.dp))
+        Notifications(modifier = modifier.weight(1f))
     }
 }
diff --git a/packages/SystemUI/compose/features/tests/AndroidManifest.xml b/packages/SystemUI/compose/features/tests/AndroidManifest.xml
index 2f41ea9..8fe9656 100644
--- a/packages/SystemUI/compose/features/tests/AndroidManifest.xml
+++ b/packages/SystemUI/compose/features/tests/AndroidManifest.xml
@@ -40,11 +40,6 @@
             android:enabled="false"
             tools:replace="android:authorities"
             tools:node="remove" />
-        <provider android:name="com.android.keyguard.clock.ClockOptionsProvider"
-            android:authorities="com.android.systemui.test.keyguard.clock.disabled"
-            android:enabled="false"
-            tools:replace="android:authorities"
-            tools:node="remove" />
         <provider android:name="com.android.systemui.people.PeopleProvider"
             android:authorities="com.android.systemui.test.people.disabled"
             android:enabled="false"
diff --git a/packages/SystemUI/compose/testing/Android.bp b/packages/SystemUI/compose/testing/Android.bp
deleted file mode 100644
index 555f42e..0000000
--- a/packages/SystemUI/compose/testing/Android.bp
+++ /dev/null
@@ -1,43 +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 {
-    // See: http://go/android-license-faq
-    // A large-scale-change added 'default_applicable_licenses' to import
-    // all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license"
-    // to get the below license kinds:
-    //   SPDX-license-identifier-Apache-2.0
-    default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"],
-}
-
-android_library {
-    name: "SystemUIComposeTesting",
-    manifest: "AndroidManifest.xml",
-
-    srcs: [
-        "src/**/*.kt",
-    ],
-
-    static_libs: [
-        "PlatformComposeCore",
-        "SystemUIScreenshotLib",
-
-        "androidx.compose.runtime_runtime",
-        "androidx.compose.material3_material3",
-        "androidx.compose.ui_ui-test-junit4",
-        "androidx.compose.ui_ui-test-manifest",
-    ],
-
-    kotlincflags: ["-Xjvm-default=all"],
-}
diff --git a/packages/SystemUI/compose/testing/AndroidManifest.xml b/packages/SystemUI/compose/testing/AndroidManifest.xml
deleted file mode 100644
index b1f7c3b..0000000
--- a/packages/SystemUI/compose/testing/AndroidManifest.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.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:tools="http://schemas.android.com/tools"
-    package="com.android.systemui.testing.compose">
-    <application
-        android:appComponentFactory="androidx.core.app.AppComponentFactory"
-        tools:replace="android:appComponentFactory">
-    </application>
-</manifest>
diff --git a/packages/SystemUI/compose/testing/src/com/android/systemui/testing/compose/ComposeScreenshotTestRule.kt b/packages/SystemUI/compose/testing/src/com/android/systemui/testing/compose/ComposeScreenshotTestRule.kt
deleted file mode 100644
index cb9e53c..0000000
--- a/packages/SystemUI/compose/testing/src/com/android/systemui/testing/compose/ComposeScreenshotTestRule.kt
+++ /dev/null
@@ -1,95 +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.systemui.testing.compose
-
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Surface
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.platform.ViewRootForTest
-import androidx.compose.ui.test.junit4.createAndroidComposeRule
-import androidx.compose.ui.test.onRoot
-import com.android.compose.theme.PlatformTheme
-import com.android.systemui.testing.screenshot.ScreenshotActivity
-import com.android.systemui.testing.screenshot.SystemUIGoldenImagePathManager
-import com.android.systemui.testing.screenshot.UnitTestBitmapMatcher
-import com.android.systemui.testing.screenshot.drawIntoBitmap
-import org.junit.rules.RuleChain
-import org.junit.rules.TestRule
-import org.junit.runner.Description
-import org.junit.runners.model.Statement
-import platform.test.screenshot.DeviceEmulationRule
-import platform.test.screenshot.DeviceEmulationSpec
-import platform.test.screenshot.MaterialYouColorsRule
-import platform.test.screenshot.ScreenshotTestRule
-import platform.test.screenshot.getEmulatedDevicePathConfig
-
-/** A rule for Compose screenshot diff tests. */
-class ComposeScreenshotTestRule(
-    emulationSpec: DeviceEmulationSpec,
-    assetPathRelativeToBuildRoot: String
-) : TestRule {
-    private val colorsRule = MaterialYouColorsRule()
-    private val deviceEmulationRule = DeviceEmulationRule(emulationSpec)
-    private val screenshotRule =
-        ScreenshotTestRule(
-            SystemUIGoldenImagePathManager(
-                getEmulatedDevicePathConfig(emulationSpec),
-                assetPathRelativeToBuildRoot
-            )
-        )
-    private val composeRule = createAndroidComposeRule<ScreenshotActivity>()
-    private val delegateRule =
-        RuleChain.outerRule(colorsRule)
-            .around(deviceEmulationRule)
-            .around(screenshotRule)
-            .around(composeRule)
-    private val matcher = UnitTestBitmapMatcher
-
-    override fun apply(base: Statement, description: Description): Statement {
-        return delegateRule.apply(base, description)
-    }
-
-    /**
-     * Compare [content] with the golden image identified by [goldenIdentifier] in the context of
-     * [testSpec].
-     */
-    fun screenshotTest(
-        goldenIdentifier: String,
-        content: @Composable () -> Unit,
-    ) {
-        // Make sure that the activity draws full screen and fits the whole display instead of the
-        // system bars.
-        val activity = composeRule.activity
-        activity.mainExecutor.execute { activity.window.setDecorFitsSystemWindows(false) }
-
-        // Set the content using the AndroidComposeRule to make sure that the Activity is set up
-        // correctly.
-        composeRule.setContent {
-            PlatformTheme {
-                Surface(
-                    color = MaterialTheme.colorScheme.background,
-                ) {
-                    content()
-                }
-            }
-        }
-        composeRule.waitForIdle()
-
-        val view = (composeRule.onRoot().fetchSemanticsNode().root as ViewRootForTest).view
-        screenshotRule.assertBitmapAgainstGolden(view.drawIntoBitmap(), goldenIdentifier, matcher)
-    }
-}
diff --git a/packages/SystemUI/customization/res/values-h800dp/dimens.xml b/packages/SystemUI/customization/res/values-h800dp/dimens.xml
index 60afc8a..cb49945 100644
--- a/packages/SystemUI/customization/res/values-h800dp/dimens.xml
+++ b/packages/SystemUI/customization/res/values-h800dp/dimens.xml
@@ -17,4 +17,7 @@
 <resources>
     <!-- Large clock maximum font size (dp is intentional, to prevent any further scaling) -->
     <dimen name="large_clock_text_size">200dp</dimen>
+
+    <!-- With the large clock, move up slightly from the center -->
+    <dimen name="keyguard_large_clock_top_margin">-112dp</dimen>
 </resources>
diff --git a/packages/SystemUI/customization/res/values/dimens.xml b/packages/SystemUI/customization/res/values/dimens.xml
index ba8f284..8eb8132 100644
--- a/packages/SystemUI/customization/res/values/dimens.xml
+++ b/packages/SystemUI/customization/res/values/dimens.xml
@@ -24,4 +24,12 @@
     <item name="keyguard_clock_line_spacing_scale" type="dimen" format="float">.7</item>
     <!-- Burmese line spacing multiplier between hours and minutes of the keyguard clock -->
     <item name="keyguard_clock_line_spacing_scale_burmese" type="dimen" format="float">1</item>
+
+    <!-- With the large clock, move up slightly from the center -->
+    <dimen name="keyguard_large_clock_top_margin">-60dp</dimen>
+
+    <!-- additional offset for clock switch area items -->
+    <dimen name="small_clock_height">114dp</dimen>
+    <dimen name="small_clock_padding_top">28dp</dimen>
+    <dimen name="clock_padding_start">28dp</dimen>
 </resources>
\ No newline at end of file
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
index 648ef03..b9d6643 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
@@ -33,8 +33,8 @@
 import com.android.systemui.animation.GlyphCallback
 import com.android.systemui.animation.TextAnimator
 import com.android.systemui.customization.R
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel.DEBUG
+import com.android.systemui.log.core.Logger
+import com.android.systemui.log.core.MessageBuffer
 import java.io.PrintWriter
 import java.util.Calendar
 import java.util.Locale
@@ -51,7 +51,12 @@
     defStyleAttr: Int = 0,
     defStyleRes: Int = 0
 ) : TextView(context, attrs, defStyleAttr, defStyleRes) {
-    var logBuffer: LogBuffer? = null
+    var messageBuffer: MessageBuffer? = null
+        set(value) {
+            logger = if (value != null) Logger(value, TAG) else null
+        }
+
+    private var logger: Logger? = null
 
     private val time = Calendar.getInstance()
 
@@ -129,7 +134,7 @@
 
     override fun onAttachedToWindow() {
         super.onAttachedToWindow()
-        logBuffer?.log(TAG, DEBUG, "onAttachedToWindow")
+        logger?.d("onAttachedToWindow")
         refreshFormat()
     }
 
@@ -145,39 +150,32 @@
         time.timeInMillis = timeOverrideInMillis ?: System.currentTimeMillis()
         contentDescription = DateFormat.format(descFormat, time)
         val formattedText = DateFormat.format(format, time)
-        logBuffer?.log(TAG, DEBUG,
-                { str1 = formattedText?.toString() },
-                { "refreshTime: new formattedText=$str1" }
-        )
+        logger?.d({ "refreshTime: new formattedText=$str1" }) { str1 = formattedText?.toString() }
         // Setting text actually triggers a layout pass (because the text view is set to
         // wrap_content width and TextView always relayouts for this). Avoid needless
         // relayout if the text didn't actually change.
         if (!TextUtils.equals(text, formattedText)) {
             text = formattedText
-            logBuffer?.log(TAG, DEBUG,
-                    { str1 = formattedText?.toString() },
-                    { "refreshTime: done setting new time text to: $str1" }
-            )
+            logger?.d({ "refreshTime: done setting new time text to: $str1" }) {
+                str1 = formattedText?.toString()
+            }
             // Because the TextLayout may mutate under the hood as a result of the new text, we
             // notify the TextAnimator that it may have changed and request a measure/layout. A
             // crash will occur on the next invocation of setTextStyle if the layout is mutated
             // without being notified TextInterpolator being notified.
             if (layout != null) {
                 textAnimator?.updateLayout(layout)
-                logBuffer?.log(TAG, DEBUG, "refreshTime: done updating textAnimator layout")
+                logger?.d("refreshTime: done updating textAnimator layout")
             }
             requestLayout()
-            logBuffer?.log(TAG, DEBUG, "refreshTime: after requestLayout")
+            logger?.d("refreshTime: after requestLayout")
         }
     }
 
     fun onTimeZoneChanged(timeZone: TimeZone?) {
         time.timeZone = timeZone
         refreshFormat()
-        logBuffer?.log(TAG, DEBUG,
-                { str1 = timeZone?.toString() },
-                { "onTimeZoneChanged newTimeZone=$str1" }
-        )
+        logger?.d({ "onTimeZoneChanged newTimeZone=$str1" }) { str1 = timeZone?.toString() }
     }
 
     @SuppressLint("DrawAllocation")
@@ -191,7 +189,7 @@
         } else {
             animator.updateLayout(layout)
         }
-        logBuffer?.log(TAG, DEBUG, "onMeasure")
+        logger?.d("onMeasure")
     }
 
     override fun onDraw(canvas: Canvas) {
@@ -203,12 +201,12 @@
         } else {
             super.onDraw(canvas)
         }
-        logBuffer?.log(TAG, DEBUG, "onDraw")
+        logger?.d("onDraw")
     }
 
     override fun invalidate() {
         super.invalidate()
-        logBuffer?.log(TAG, DEBUG, "invalidate")
+        logger?.d("invalidate")
     }
 
     override fun onTextChanged(
@@ -218,10 +216,7 @@
             lengthAfter: Int
     ) {
         super.onTextChanged(text, start, lengthBefore, lengthAfter)
-        logBuffer?.log(TAG, DEBUG,
-                { str1 = text.toString() },
-                { "onTextChanged text=$str1" }
-        )
+        logger?.d({ "onTextChanged text=$str1" }) { str1 = text.toString() }
     }
 
     fun setLineSpacingScale(scale: Float) {
@@ -235,7 +230,7 @@
     }
 
     fun animateColorChange() {
-        logBuffer?.log(TAG, DEBUG, "animateColorChange")
+        logger?.d("animateColorChange")
         setTextStyle(
             weight = lockScreenWeight,
             textSize = -1f,
@@ -257,7 +252,7 @@
     }
 
     fun animateAppearOnLockscreen() {
-        logBuffer?.log(TAG, DEBUG, "animateAppearOnLockscreen")
+        logger?.d("animateAppearOnLockscreen")
         setTextStyle(
             weight = dozingWeight,
             textSize = -1f,
@@ -283,7 +278,7 @@
         if (isAnimationEnabled && textAnimator == null) {
             return
         }
-        logBuffer?.log(TAG, DEBUG, "animateFoldAppear")
+        logger?.d("animateFoldAppear")
         setTextStyle(
             weight = lockScreenWeightInternal,
             textSize = -1f,
@@ -310,7 +305,7 @@
             // Skip charge animation if dozing animation is already playing.
             return
         }
-        logBuffer?.log(TAG, DEBUG, "animateCharge")
+        logger?.d("animateCharge")
         val startAnimPhase2 = Runnable {
             setTextStyle(
                 weight = if (isDozing()) dozingWeight else lockScreenWeight,
@@ -334,7 +329,7 @@
     }
 
     fun animateDoze(isDozing: Boolean, animate: Boolean) {
-        logBuffer?.log(TAG, DEBUG, "animateDoze")
+        logger?.d("animateDoze")
         setTextStyle(
             weight = if (isDozing) dozingWeight else lockScreenWeight,
             textSize = -1f,
@@ -453,10 +448,7 @@
             isSingleLineInternal && !use24HourFormat -> Patterns.sClockView12
             else -> DOUBLE_LINE_FORMAT_12_HOUR
         }
-        logBuffer?.log(TAG, DEBUG,
-                { str1 = format?.toString() },
-                { "refreshFormat format=$str1" }
-        )
+        logger?.d({ "refreshFormat format=$str1" }) { str1 = format?.toString() }
 
         descFormat = if (use24HourFormat) Patterns.sClockView24 else Patterns.sClockView12
         refreshTime()
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
index 702cc05..d65edae 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
@@ -23,12 +23,13 @@
 import android.provider.Settings
 import android.util.Log
 import androidx.annotation.OpenForTesting
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
-import com.android.systemui.log.LogMessage
 import com.android.systemui.log.LogMessageImpl
-import com.android.systemui.log.MessageInitializer
-import com.android.systemui.log.MessagePrinter
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.core.LogMessage
+import com.android.systemui.log.core.Logger
+import com.android.systemui.log.core.MessageBuffer
+import com.android.systemui.log.core.MessageInitializer
+import com.android.systemui.log.core.MessagePrinter
 import com.android.systemui.plugins.ClockController
 import com.android.systemui.plugins.ClockId
 import com.android.systemui.plugins.ClockMetadata
@@ -56,6 +57,7 @@
         "com.android.systemui.falcon.four" to listOf(ClockMetadata("DIGITAL_CLOCK_GROWTH")),
         "com.android.systemui.falcon.five" to listOf(ClockMetadata("DIGITAL_CLOCK_HANDWRITTEN")),
         "com.android.systemui.falcon.six" to listOf(ClockMetadata("DIGITAL_CLOCK_INFLATE")),
+        "com.android.systemui.falcon.seven" to listOf(ClockMetadata("DIGITAL_CLOCK_METRO")),
         "com.android.systemui.falcon.eight" to listOf(ClockMetadata("DIGITAL_CLOCK_NUMBEROVERLAP")),
         "com.android.systemui.falcon.nine" to listOf(ClockMetadata("DIGITAL_CLOCK_WEATHER")),
     )
@@ -74,7 +76,7 @@
 
 private val TMP_MESSAGE: LogMessage by lazy { LogMessageImpl.Factory.create() }
 
-private inline fun LogBuffer?.tryLog(
+private inline fun Logger?.tryLog(
     tag: String,
     level: LogLevel,
     messageInitializer: MessageInitializer,
@@ -83,7 +85,7 @@
 ) {
     if (this != null) {
         // Wrap messagePrinter to convert it from crossinline to noinline
-        this.log(tag, level, messageInitializer, messagePrinter, ex)
+        this.log(level, messagePrinter, ex, messageInitializer)
     } else {
         messageInitializer(TMP_MESSAGE)
         val msg = messagePrinter(TMP_MESSAGE)
@@ -109,9 +111,10 @@
     val handleAllUsers: Boolean,
     defaultClockProvider: ClockProvider,
     val fallbackClockId: ClockId = DEFAULT_CLOCK_ID,
-    val logBuffer: LogBuffer? = null,
+    messageBuffer: MessageBuffer? = null,
     val keepAllLoaded: Boolean,
     subTag: String,
+    var isTransitClockEnabled: Boolean = false,
 ) {
     private val TAG = "${ClockRegistry::class.simpleName} ($subTag)"
     interface ClockChangeListener {
@@ -122,6 +125,7 @@
         fun onAvailableClocksChanged() {}
     }
 
+    private val logger: Logger? = if (messageBuffer != null) Logger(messageBuffer, TAG) else null
     private val availableClocks = ConcurrentHashMap<ClockId, ClockInfo>()
     private val clockChangeListeners = mutableListOf<ClockChangeListener>()
     private val settingObserver =
@@ -148,7 +152,7 @@
 
                 val knownClocks = KNOWN_PLUGINS.get(manager.getPackage())
                 if (knownClocks == null) {
-                    logBuffer.tryLog(
+                    logger.tryLog(
                         TAG,
                         LogLevel.WARNING,
                         { str1 = manager.getPackage() },
@@ -157,7 +161,7 @@
                     return true
                 }
 
-                logBuffer.tryLog(
+                logger.tryLog(
                     TAG,
                     LogLevel.INFO,
                     { str1 = manager.getPackage() },
@@ -174,7 +178,7 @@
                         }
 
                     if (manager != info.manager) {
-                        logBuffer.tryLog(
+                        logger.tryLog(
                             TAG,
                             LogLevel.ERROR,
                             { str1 = id },
@@ -203,6 +207,10 @@
                 var isClockListChanged = false
                 for (clock in plugin.getClocks()) {
                     val id = clock.clockId
+                    if (!isTransitClockEnabled && id == "DIGITAL_CLOCK_METRO") {
+                        continue
+                    }
+
                     val info =
                         availableClocks.concurrentGetOrPut(id, ClockInfo(clock, plugin, manager)) {
                             isClockListChanged = true
@@ -210,7 +218,7 @@
                         }
 
                     if (manager != info.manager) {
-                        logBuffer.tryLog(
+                        logger.tryLog(
                             TAG,
                             LogLevel.ERROR,
                             { str1 = id },
@@ -238,7 +246,7 @@
                     val id = clock.clockId
                     val info = availableClocks[id]
                     if (info?.manager != manager) {
-                        logBuffer.tryLog(
+                        logger.tryLog(
                             TAG,
                             LogLevel.ERROR,
                             { str1 = id },
@@ -313,7 +321,7 @@
 
                 ClockSettings.deserialize(json)
             } catch (ex: Exception) {
-                logBuffer.tryLog(TAG, LogLevel.ERROR, {}, { "Failed to parse clock settings" }, ex)
+                logger.tryLog(TAG, LogLevel.ERROR, {}, { "Failed to parse clock settings" }, ex)
                 null
             }
         settings = result
@@ -342,7 +350,7 @@
                 )
             }
         } catch (ex: Exception) {
-            logBuffer.tryLog(TAG, LogLevel.ERROR, {}, { "Failed to set clock settings" }, ex)
+            logger.tryLog(TAG, LogLevel.ERROR, {}, { "Failed to set clock settings" }, ex)
         }
         settings = value
     }
@@ -502,9 +510,9 @@
     }
 
     private fun onConnected(clockId: ClockId) {
-        logBuffer.tryLog(TAG, LogLevel.DEBUG, { str1 = clockId }, { "Connected $str1" })
+        logger.tryLog(TAG, LogLevel.DEBUG, { str1 = clockId }, { "Connected $str1" })
         if (currentClockId == clockId) {
-            logBuffer.tryLog(
+            logger.tryLog(
                 TAG,
                 LogLevel.INFO,
                 { str1 = clockId },
@@ -514,10 +522,10 @@
     }
 
     private fun onLoaded(clockId: ClockId) {
-        logBuffer.tryLog(TAG, LogLevel.DEBUG, { str1 = clockId }, { "Loaded $str1" })
+        logger.tryLog(TAG, LogLevel.DEBUG, { str1 = clockId }, { "Loaded $str1" })
 
         if (currentClockId == clockId) {
-            logBuffer.tryLog(
+            logger.tryLog(
                 TAG,
                 LogLevel.INFO,
                 { str1 = clockId },
@@ -528,10 +536,10 @@
     }
 
     private fun onUnloaded(clockId: ClockId) {
-        logBuffer.tryLog(TAG, LogLevel.DEBUG, { str1 = clockId }, { "Unloaded $str1" })
+        logger.tryLog(TAG, LogLevel.DEBUG, { str1 = clockId }, { "Unloaded $str1" })
 
         if (currentClockId == clockId) {
-            logBuffer.tryLog(
+            logger.tryLog(
                 TAG,
                 LogLevel.WARNING,
                 { str1 = clockId },
@@ -542,10 +550,10 @@
     }
 
     private fun onDisconnected(clockId: ClockId) {
-        logBuffer.tryLog(TAG, LogLevel.DEBUG, { str1 = clockId }, { "Disconnected $str1" })
+        logger.tryLog(TAG, LogLevel.DEBUG, { str1 = clockId }, { "Disconnected $str1" })
 
         if (currentClockId == clockId) {
-            logBuffer.tryLog(
+            logger.tryLog(
                 TAG,
                 LogLevel.WARNING,
                 { str1 = clockId },
@@ -591,22 +599,17 @@
         if (isEnabled && clockId.isNotEmpty()) {
             val clock = createClock(clockId)
             if (clock != null) {
-                logBuffer.tryLog(
-                    TAG,
-                    LogLevel.INFO,
-                    { str1 = clockId },
-                    { "Rendering clock $str1" }
-                )
+                logger.tryLog(TAG, LogLevel.INFO, { str1 = clockId }, { "Rendering clock $str1" })
                 return clock
             } else if (availableClocks.containsKey(clockId)) {
-                logBuffer.tryLog(
+                logger.tryLog(
                     TAG,
                     LogLevel.WARNING,
                     { str1 = clockId },
                     { "Clock $str1 not loaded; using default" }
                 )
             } else {
-                logBuffer.tryLog(
+                logger.tryLog(
                     TAG,
                     LogLevel.ERROR,
                     { str1 = clockId },
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
index e557c8e..e539c95 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
@@ -24,7 +24,7 @@
 import android.widget.FrameLayout
 import androidx.annotation.VisibleForTesting
 import com.android.systemui.customization.R
-import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.MessageBuffer
 import com.android.systemui.plugins.ClockAnimations
 import com.android.systemui.plugins.ClockConfig
 import com.android.systemui.plugins.ClockController
@@ -108,10 +108,10 @@
 
         override val config = ClockFaceConfig()
 
-        override var logBuffer: LogBuffer?
-            get() = view.logBuffer
+        override var messageBuffer: MessageBuffer?
+            get() = view.messageBuffer
             set(value) {
-                view.logBuffer = value
+                view.messageBuffer = value
             }
 
         override var animations: DefaultClockAnimations = DefaultClockAnimations(view, 0f, 0f)
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt
index b6d5ef3..95a9ce9 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt
@@ -184,6 +184,9 @@
         /** Flag denoting AI Wallpapers are enabled in wallpaper picker. */
         const val FLAG_NAME_WALLPAPER_PICKER_UI_FOR_AIWP = "wallpaper_picker_ui_for_aiwp"
 
+        /** Flag denoting transit clock are enabled in wallpaper picker. */
+        const val FLAG_NAME_TRANSIT_CLOCK = "lockscreen_custom_transit_clock"
+
         object Columns {
             /** String. Unique ID for the flag. */
             const val NAME = "name"
diff --git a/packages/SystemUI/docs/clock-plugins.md b/packages/SystemUI/docs/clock-plugins.md
index 2226d79..9cb115a 100644
--- a/packages/SystemUI/docs/clock-plugins.md
+++ b/packages/SystemUI/docs/clock-plugins.md
@@ -1,7 +1,7 @@
 # Clock Plugins
 
-The clock appearing on the lock screen and always on display (AOD) can be
-customized via the ClockProviderPlugin plugin interface.
+The clock appearing on the lock screen and always on display (AOD) can be customized via the
+ClockProviderPlugin plugin interface. The ClockPlugin interface has been removed.
 
 ## Lock screen integration
 The lockscreen code has two main components, a [clock customization library](../customization), and
diff --git a/packages/SystemUI/ktfmt_includes.txt b/packages/SystemUI/ktfmt_includes.txt
index 18753fd..006fc09 100644
--- a/packages/SystemUI/ktfmt_includes.txt
+++ b/packages/SystemUI/ktfmt_includes.txt
@@ -422,9 +422,6 @@
 -packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallFlags.kt
 -packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallLogger.kt
 -packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherContainer.kt
--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/model/ConnectivitySlots.kt
--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt
 -packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryStateNotifier.kt
 -packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsController.kt
 -packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImpl.kt
@@ -696,7 +693,6 @@
 -packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallChronometerTest.kt
 -packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
 -packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallLoggerTest.kt
--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
 -packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryStateNotifierTest.kt
 -packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ClockTest.kt
 -packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImplTest.kt
diff --git a/packages/SystemUI/log/src/com/android/systemui/log/ConstantStringsLoggerImpl.kt b/packages/SystemUI/log/src/com/android/systemui/log/ConstantStringsLoggerImpl.kt
index 6fc52536..a4f4e13 100644
--- a/packages/SystemUI/log/src/com/android/systemui/log/ConstantStringsLoggerImpl.kt
+++ b/packages/SystemUI/log/src/com/android/systemui/log/ConstantStringsLoggerImpl.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.log
 
+import com.android.systemui.log.core.LogLevel
 import com.google.errorprone.annotations.CompileTimeConstant
 
 class ConstantStringsLoggerImpl(val buffer: LogBuffer, val tag: String) : ConstantStringsLogger {
diff --git a/packages/SystemUI/log/src/com/android/systemui/log/LogBuffer.kt b/packages/SystemUI/log/src/com/android/systemui/log/LogBuffer.kt
index 2007e76..e0051f5 100644
--- a/packages/SystemUI/log/src/com/android/systemui/log/LogBuffer.kt
+++ b/packages/SystemUI/log/src/com/android/systemui/log/LogBuffer.kt
@@ -19,6 +19,11 @@
 import android.os.Trace
 import android.util.Log
 import com.android.systemui.common.buffer.RingBuffer
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.core.LogMessage
+import com.android.systemui.log.core.MessageBuffer
+import com.android.systemui.log.core.MessageInitializer
+import com.android.systemui.log.core.MessagePrinter
 import com.google.errorprone.annotations.CompileTimeConstant
 import java.io.PrintWriter
 import java.util.concurrent.ArrayBlockingQueue
@@ -73,7 +78,7 @@
     private val maxSize: Int,
     private val logcatEchoTracker: LogcatEchoTracker,
     private val systrace: Boolean = true,
-) {
+) : MessageBuffer {
     private val buffer = RingBuffer(maxSize) { LogMessageImpl.create() }
 
     private val echoMessageQueue: BlockingQueue<LogMessage>? =
@@ -174,11 +179,11 @@
      * store any relevant data on the message and then call [commit].
      */
     @Synchronized
-    fun obtain(
+    override fun obtain(
         tag: String,
         level: LogLevel,
         messagePrinter: MessagePrinter,
-        exception: Throwable? = null,
+        exception: Throwable?,
     ): LogMessage {
         if (!mutable) {
             return FROZEN_MESSAGE
@@ -195,7 +200,7 @@
      * have finished filling in its data fields. The message will be echoed to logcat if necessary.
      */
     @Synchronized
-    fun commit(message: LogMessage) {
+    override fun commit(message: LogMessage) {
         if (!mutable) {
             return
         }
@@ -292,11 +297,5 @@
     }
 }
 
-/**
- * A function that will be called immediately to store relevant data on the log message. The value
- * of `this` will be the LogMessage to be initialized.
- */
-typealias MessageInitializer = LogMessage.() -> Unit
-
 private const val TAG = "LogBuffer"
 private val FROZEN_MESSAGE = LogMessageImpl.create()
diff --git a/packages/SystemUI/log/src/com/android/systemui/log/LogLevel.kt b/packages/SystemUI/log/src/com/android/systemui/log/LogLevel.kt
deleted file mode 100644
index 7d9647a..0000000
--- a/packages/SystemUI/log/src/com/android/systemui/log/LogLevel.kt
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * 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.
- */
-
-package com.android.systemui.log
-
-import android.util.Log
-
-/** Enum version of @Log.Level */
-enum class LogLevel(@Log.Level val nativeLevel: Int, val shortString: String) {
-    VERBOSE(Log.VERBOSE, "V"),
-    DEBUG(Log.DEBUG, "D"),
-    INFO(Log.INFO, "I"),
-    WARNING(Log.WARN, "W"),
-    ERROR(Log.ERROR, "E"),
-    WTF(Log.ASSERT, "WTF")
-}
diff --git a/packages/SystemUI/log/src/com/android/systemui/log/LogMessage.kt b/packages/SystemUI/log/src/com/android/systemui/log/LogMessage.kt
deleted file mode 100644
index 8c3988b..0000000
--- a/packages/SystemUI/log/src/com/android/systemui/log/LogMessage.kt
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * 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.
- */
-
-package com.android.systemui.log
-
-import android.icu.text.SimpleDateFormat
-import java.io.PrintWriter
-import java.util.Locale
-
-/**
- * Generic data class for storing messages logged to a [LogBuffer]
- *
- * Each LogMessage has a few standard fields ([level], [tag], and [timestamp]). The rest are generic
- * data slots that may or may not be used, depending on the nature of the specific message being
- * logged.
- *
- * When a message is logged, the code doing the logging stores data in one or more of the generic
- * fields ([str1], [int1], etc). When it comes time to dump the message to logcat/bugreport/etc, the
- * [messagePrinter] function reads the data stored in the generic fields and converts that to a
- * human- readable string. Thus, for every log type there must be a specialized initializer function
- * that stores data specific to that log type and a specialized printer function that prints that
- * data.
- *
- * See [LogBuffer.log] for more information.
- */
-interface LogMessage {
-    val level: LogLevel
-    val tag: String
-    val timestamp: Long
-    val messagePrinter: MessagePrinter
-    val exception: Throwable?
-
-    var str1: String?
-    var str2: String?
-    var str3: String?
-    var int1: Int
-    var int2: Int
-    var long1: Long
-    var long2: Long
-    var double1: Double
-    var bool1: Boolean
-    var bool2: Boolean
-    var bool3: Boolean
-    var bool4: Boolean
-
-    /** Function that dumps the [LogMessage] to the provided [writer]. */
-    fun dump(writer: PrintWriter) {
-        val formattedTimestamp = DATE_FORMAT.format(timestamp)
-        val shortLevel = level.shortString
-        val messageToPrint = messagePrinter(this)
-        printLikeLogcat(writer, formattedTimestamp, shortLevel, tag, messageToPrint)
-        exception?.printStackTrace(writer)
-    }
-}
-
-/**
- * A function that will be called if and when the message needs to be dumped to logcat or a bug
- * report. It should read the data stored by the initializer and convert it to a human-readable
- * string. The value of `this` will be the LogMessage to be printed. **IMPORTANT:** The printer
- * should ONLY ever reference fields on the LogMessage and NEVER any variables in its enclosing
- * scope. Otherwise, the runtime will need to allocate a new instance of the printer for each call,
- * thwarting our attempts at avoiding any sort of allocation.
- */
-typealias MessagePrinter = LogMessage.() -> String
-
-private fun printLikeLogcat(
-    pw: PrintWriter,
-    formattedTimestamp: String,
-    shortLogLevel: String,
-    tag: String,
-    message: String
-) {
-    pw.print(formattedTimestamp)
-    pw.print(" ")
-    pw.print(shortLogLevel)
-    pw.print(" ")
-    pw.print(tag)
-    pw.print(": ")
-    pw.println(message)
-}
-
-private val DATE_FORMAT = SimpleDateFormat("MM-dd HH:mm:ss.SSS", Locale.US)
diff --git a/packages/SystemUI/log/src/com/android/systemui/log/LogMessageImpl.kt b/packages/SystemUI/log/src/com/android/systemui/log/LogMessageImpl.kt
index 5e10f78..33cc199 100644
--- a/packages/SystemUI/log/src/com/android/systemui/log/LogMessageImpl.kt
+++ b/packages/SystemUI/log/src/com/android/systemui/log/LogMessageImpl.kt
@@ -16,6 +16,10 @@
 
 package com.android.systemui.log
 
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.core.LogMessage
+import com.android.systemui.log.core.MessagePrinter
+
 /** Recyclable implementation of [LogMessage]. */
 data class LogMessageImpl(
     override var level: LogLevel,
diff --git a/packages/SystemUI/log/src/com/android/systemui/log/LogcatEchoTracker.kt b/packages/SystemUI/log/src/com/android/systemui/log/LogcatEchoTracker.kt
index 55f3a73..ae717df 100644
--- a/packages/SystemUI/log/src/com/android/systemui/log/LogcatEchoTracker.kt
+++ b/packages/SystemUI/log/src/com/android/systemui/log/LogcatEchoTracker.kt
@@ -16,6 +16,8 @@
 
 package com.android.systemui.log
 
+import com.android.systemui.log.core.LogLevel
+
 /** Keeps track of which [LogBuffer] messages should also appear in logcat. */
 interface LogcatEchoTracker {
     /** Whether [bufferName] should echo messages of [level] or higher to logcat. */
diff --git a/packages/SystemUI/log/src/com/android/systemui/log/LogcatEchoTrackerDebug.kt b/packages/SystemUI/log/src/com/android/systemui/log/LogcatEchoTrackerDebug.kt
index d0ad28f..9ff48ca 100644
--- a/packages/SystemUI/log/src/com/android/systemui/log/LogcatEchoTrackerDebug.kt
+++ b/packages/SystemUI/log/src/com/android/systemui/log/LogcatEchoTrackerDebug.kt
@@ -23,6 +23,7 @@
 import android.os.Looper
 import android.os.Trace
 import android.provider.Settings
+import com.android.systemui.log.core.LogLevel
 
 /**
  * Version of [LogcatEchoTracker] for debuggable builds
diff --git a/packages/SystemUI/log/src/com/android/systemui/log/LogcatEchoTrackerProd.kt b/packages/SystemUI/log/src/com/android/systemui/log/LogcatEchoTrackerProd.kt
index 56966773..044d97f 100644
--- a/packages/SystemUI/log/src/com/android/systemui/log/LogcatEchoTrackerProd.kt
+++ b/packages/SystemUI/log/src/com/android/systemui/log/LogcatEchoTrackerProd.kt
@@ -16,6 +16,8 @@
 
 package com.android.systemui.log
 
+import com.android.systemui.log.core.LogLevel
+
 /** Production version of [LogcatEchoTracker] that isn't configurable. */
 class LogcatEchoTrackerProd : LogcatEchoTracker {
     override val logInBackgroundThread = false
diff --git a/packages/SystemUI/log/src/com/android/systemui/log/core/LogLevel.kt b/packages/SystemUI/log/src/com/android/systemui/log/core/LogLevel.kt
new file mode 100644
index 0000000..d30d8e9
--- /dev/null
+++ b/packages/SystemUI/log/src/com/android/systemui/log/core/LogLevel.kt
@@ -0,0 +1,29 @@
+/*
+ * 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.
+ */
+
+package com.android.systemui.log.core
+
+import android.util.Log
+
+/** Enum version of @Log.Level */
+enum class LogLevel(@Log.Level val nativeLevel: Int, val shortString: String) {
+    VERBOSE(Log.VERBOSE, "V"),
+    DEBUG(Log.DEBUG, "D"),
+    INFO(Log.INFO, "I"),
+    WARNING(Log.WARN, "W"),
+    ERROR(Log.ERROR, "E"),
+    WTF(Log.ASSERT, "WTF")
+}
diff --git a/packages/SystemUI/log/src/com/android/systemui/log/core/LogMessage.kt b/packages/SystemUI/log/src/com/android/systemui/log/core/LogMessage.kt
new file mode 100644
index 0000000..3bd6473
--- /dev/null
+++ b/packages/SystemUI/log/src/com/android/systemui/log/core/LogMessage.kt
@@ -0,0 +1,101 @@
+/*
+ * 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.
+ */
+
+package com.android.systemui.log.core
+
+import android.icu.text.SimpleDateFormat
+import java.io.PrintWriter
+import java.util.Locale
+
+/**
+ * Generic data class for storing messages logged to a [LogBuffer]
+ *
+ * Each LogMessage has a few standard fields ([level], [tag], and [timestamp]). The rest are generic
+ * data slots that may or may not be used, depending on the nature of the specific message being
+ * logged.
+ *
+ * When a message is logged, the code doing the logging stores data in one or more of the generic
+ * fields ([str1], [int1], etc). When it comes time to dump the message to logcat/bugreport/etc, the
+ * [messagePrinter] function reads the data stored in the generic fields and converts that to a
+ * human- readable string. Thus, for every log type there must be a specialized initializer function
+ * that stores data specific to that log type and a specialized printer function that prints that
+ * data.
+ *
+ * See [LogBuffer.log] for more information.
+ */
+interface LogMessage {
+    val level: LogLevel
+    val tag: String
+    val timestamp: Long
+    val messagePrinter: MessagePrinter
+    val exception: Throwable?
+
+    var str1: String?
+    var str2: String?
+    var str3: String?
+    var int1: Int
+    var int2: Int
+    var long1: Long
+    var long2: Long
+    var double1: Double
+    var bool1: Boolean
+    var bool2: Boolean
+    var bool3: Boolean
+    var bool4: Boolean
+
+    /** Function that dumps the [LogMessage] to the provided [writer]. */
+    fun dump(writer: PrintWriter) {
+        val formattedTimestamp = DATE_FORMAT.format(timestamp)
+        val shortLevel = level.shortString
+        val messageToPrint = messagePrinter(this)
+        printLikeLogcat(writer, formattedTimestamp, shortLevel, tag, messageToPrint)
+        exception?.printStackTrace(writer)
+    }
+}
+
+/**
+ * A function that will be called immediately to store relevant data on the log message. The value
+ * of `this` will be the LogMessage to be initialized.
+ */
+typealias MessageInitializer = LogMessage.() -> Unit
+
+/**
+ * A function that will be called if and when the message needs to be dumped to logcat or a bug
+ * report. It should read the data stored by the initializer and convert it to a human-readable
+ * string. The value of `this` will be the LogMessage to be printed. **IMPORTANT:** The printer
+ * should ONLY ever reference fields on the LogMessage and NEVER any variables in its enclosing
+ * scope. Otherwise, the runtime will need to allocate a new instance of the printer for each call,
+ * thwarting our attempts at avoiding any sort of allocation.
+ */
+typealias MessagePrinter = LogMessage.() -> String
+
+private fun printLikeLogcat(
+    pw: PrintWriter,
+    formattedTimestamp: String,
+    shortLogLevel: String,
+    tag: String,
+    message: String
+) {
+    pw.print(formattedTimestamp)
+    pw.print(" ")
+    pw.print(shortLogLevel)
+    pw.print(" ")
+    pw.print(tag)
+    pw.print(": ")
+    pw.println(message)
+}
+
+private val DATE_FORMAT = SimpleDateFormat("MM-dd HH:mm:ss.SSS", Locale.US)
diff --git a/packages/SystemUI/log/src/com/android/systemui/log/core/Logger.kt b/packages/SystemUI/log/src/com/android/systemui/log/core/Logger.kt
new file mode 100644
index 0000000..5729ab2
--- /dev/null
+++ b/packages/SystemUI/log/src/com/android/systemui/log/core/Logger.kt
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.log.core
+
+import com.google.errorprone.annotations.CompileTimeConstant
+
+/** Logs messages to the [MessageBuffer] with [tag]. */
+open class Logger(val buffer: MessageBuffer, val tag: String) {
+    /**
+     * Logs a message to the buffer.
+     *
+     * The actual string of the log message is not constructed until it is needed. To accomplish
+     * this, logging a message is a two-step process. First, a fresh instance of [LogMessage] is
+     * obtained and is passed to the [messageInitializer]. The initializer stores any relevant data
+     * on the message's fields. The message is then inserted into the buffer where it waits until it
+     * is either pushed out by newer messages or it needs to printed. If and when this latter moment
+     * occurs, the [messagePrinter] function is called on the message. It reads whatever data the
+     * initializer stored and converts it to a human-readable log message.
+     *
+     * @param level Which level to log the message at, both to the buffer and to logcat if it's
+     *   echoed. In general, a module should split most of its logs into either INFO or DEBUG level.
+     *   INFO level should be reserved for information that other parts of the system might care
+     *   about, leaving the specifics of code's day-to-day operations to DEBUG.
+     * @param messagePrinter A function that will be called if and when the message needs to be
+     *   dumped to logcat or a bug report. It should read the data stored by the initializer and
+     *   convert it to a human-readable string. The value of `this` will be the [LogMessage] to be
+     *   printed. **IMPORTANT:** The printer should ONLY ever reference fields on the [LogMessage]
+     *   and NEVER any variables in its enclosing scope. Otherwise, the runtime will need to
+     *   allocate a new instance of the printer for each call, thwarting our attempts at avoiding
+     *   any sort of allocation.
+     * @param exception Provide any exception that need to be logged. This is saved as
+     *   [LogMessage.exception]
+     * @param messageInitializer A function that will be called immediately to store relevant data
+     *   on the log message. The value of `this` will be the [LogMessage] to be initialized.
+     */
+    @JvmOverloads
+    inline fun log(
+        level: LogLevel,
+        noinline messagePrinter: MessagePrinter,
+        exception: Throwable? = null,
+        messageInitializer: MessageInitializer,
+    ) {
+        val message = buffer.obtain(tag, level, messagePrinter, exception)
+        messageInitializer(message)
+        buffer.commit(message)
+    }
+
+    /**
+     * Logs a compile-time string constant [message] to the log buffer. Use sparingly.
+     *
+     * This is for simpler use-cases where [message] is a compile time string constant. For
+     * use-cases where the log message is built during runtime, use the [log] overloaded method that
+     * takes in an initializer and a message printer.
+     *
+     * Buffers are limited by the number of entries, so logging more frequently will limit the time
+     * window that the [MessageBuffer] covers in a bug report. Richer logs, on the other hand, make
+     * a bug report more actionable, so using the [log] with a [MessagePrinter] to add more details
+     * to every log may do more to improve overall logging than adding more logs with this method.
+     */
+    @JvmOverloads
+    fun log(
+        level: LogLevel,
+        @CompileTimeConstant message: String,
+        exception: Throwable? = null,
+    ) = log(level, { str1!! }, exception) { str1 = message }
+
+    /**
+     * Logs a message to the buffer at [LogLevel.VERBOSE].
+     *
+     * @see log
+     */
+    @JvmOverloads
+    inline fun v(
+        noinline messagePrinter: MessagePrinter,
+        exception: Throwable? = null,
+        messageInitializer: MessageInitializer,
+    ) = log(LogLevel.VERBOSE, messagePrinter, exception, messageInitializer)
+
+    /**
+     * Logs a compile-time string constant [message] to the log buffer at [LogLevel.VERBOSE]. Use
+     * sparingly.
+     *
+     * @see log
+     */
+    @JvmOverloads
+    fun v(
+        @CompileTimeConstant message: String,
+        exception: Throwable? = null,
+    ) = log(LogLevel.VERBOSE, message, exception)
+
+    /**
+     * Logs a message to the buffer at [LogLevel.DEBUG].
+     *
+     * @see log
+     */
+    @JvmOverloads
+    inline fun d(
+        noinline messagePrinter: MessagePrinter,
+        exception: Throwable? = null,
+        messageInitializer: MessageInitializer,
+    ) = log(LogLevel.DEBUG, messagePrinter, exception, messageInitializer)
+
+    /**
+     * Logs a compile-time string constant [message] to the log buffer at [LogLevel.DEBUG]. Use
+     * sparingly.
+     *
+     * @see log
+     */
+    @JvmOverloads
+    fun d(
+        @CompileTimeConstant message: String,
+        exception: Throwable? = null,
+    ) = log(LogLevel.DEBUG, message, exception)
+
+    /**
+     * Logs a message to the buffer at [LogLevel.INFO].
+     *
+     * @see log
+     */
+    @JvmOverloads
+    inline fun i(
+        noinline messagePrinter: MessagePrinter,
+        exception: Throwable? = null,
+        messageInitializer: MessageInitializer,
+    ) = log(LogLevel.INFO, messagePrinter, exception, messageInitializer)
+
+    /**
+     * Logs a compile-time string constant [message] to the log buffer at [LogLevel.INFO]. Use
+     * sparingly.
+     *
+     * @see log
+     */
+    @JvmOverloads
+    fun i(
+        @CompileTimeConstant message: String,
+        exception: Throwable? = null,
+    ) = log(LogLevel.INFO, message, exception)
+
+    /**
+     * Logs a message to the buffer at [LogLevel.WARNING].
+     *
+     * @see log
+     */
+    @JvmOverloads
+    inline fun w(
+        noinline messagePrinter: MessagePrinter,
+        exception: Throwable? = null,
+        messageInitializer: MessageInitializer,
+    ) = log(LogLevel.WARNING, messagePrinter, exception, messageInitializer)
+
+    /**
+     * Logs a compile-time string constant [message] to the log buffer at [LogLevel.WARNING]. Use
+     * sparingly.
+     *
+     * @see log
+     */
+    @JvmOverloads
+    fun w(
+        @CompileTimeConstant message: String,
+        exception: Throwable? = null,
+    ) = log(LogLevel.WARNING, message, exception)
+
+    /**
+     * Logs a message to the buffer at [LogLevel.ERROR].
+     *
+     * @see log
+     */
+    @JvmOverloads
+    inline fun e(
+        noinline messagePrinter: MessagePrinter,
+        exception: Throwable? = null,
+        messageInitializer: MessageInitializer,
+    ) = log(LogLevel.ERROR, messagePrinter, exception, messageInitializer)
+
+    /**
+     * Logs a compile-time string constant [message] to the log buffer at [LogLevel.ERROR]. Use
+     * sparingly.
+     *
+     * @see log
+     */
+    @JvmOverloads
+    fun e(
+        @CompileTimeConstant message: String,
+        exception: Throwable? = null,
+    ) = log(LogLevel.ERROR, message, exception)
+
+    /**
+     * Logs a message to the buffer at [LogLevel.WTF].
+     *
+     * @see log
+     */
+    @JvmOverloads
+    inline fun wtf(
+        noinline messagePrinter: MessagePrinter,
+        exception: Throwable? = null,
+        messageInitializer: MessageInitializer,
+    ) = log(LogLevel.WTF, messagePrinter, exception, messageInitializer)
+
+    /**
+     * Logs a compile-time string constant [message] to the log buffer at [LogLevel.WTF]. Use
+     * sparingly.
+     *
+     * @see log
+     */
+    @JvmOverloads
+    fun wtf(
+        @CompileTimeConstant message: String,
+        exception: Throwable? = null,
+    ) = log(LogLevel.WTF, message, exception)
+}
diff --git a/packages/SystemUI/log/src/com/android/systemui/log/core/MessageBuffer.kt b/packages/SystemUI/log/src/com/android/systemui/log/core/MessageBuffer.kt
new file mode 100644
index 0000000..bb91633
--- /dev/null
+++ b/packages/SystemUI/log/src/com/android/systemui/log/core/MessageBuffer.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.log.core
+
+/**
+ * [MessageBuffer] is an interface that represents a buffer of log messages, and provides methods to
+ * [obtain] a log message and [commit] it to the buffer.
+ */
+interface MessageBuffer {
+    /**
+     * Obtains the next [LogMessage] from the buffer.
+     *
+     * After calling [obtain], the caller must store any relevant data on the message and then call
+     * [commit].
+     */
+    fun obtain(
+        tag: String,
+        level: LogLevel,
+        messagePrinter: MessagePrinter,
+        exception: Throwable? = null,
+    ): LogMessage
+
+    /**
+     * After acquiring a log message via [obtain], call this method to signal to the buffer that
+     * data fields have been filled.
+     */
+    fun commit(message: LogMessage)
+}
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 2baeaf6..9cc87fd 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java
@@ -133,6 +133,9 @@
             boolean willAnimateOnKeyguard,
             @Nullable String customMessage);
 
+    /** Whether we should animate an activity launch. */
+    boolean shouldAnimateLaunch(boolean isActivityIntent);
+
     interface Callback {
         void onActivityStarted(int resultCode);
     }
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockPlugin.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockPlugin.java
deleted file mode 100644
index bef61b8..0000000
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockPlugin.java
+++ /dev/null
@@ -1,145 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
- * except in compliance with the License. You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- */
-package com.android.systemui.plugins;
-
-import android.graphics.Bitmap;
-import android.graphics.Paint.Style;
-import android.view.View;
-
-import com.android.systemui.plugins.annotations.ProvidesInterface;
-
-import java.util.TimeZone;
-
-/**
- * Plugin used to replace main clock in keyguard.
- * @deprecated Migrating to ClockProviderPlugin
- */
-@Deprecated
-@ProvidesInterface(action = ClockPlugin.ACTION, version = ClockPlugin.VERSION)
-public interface ClockPlugin extends Plugin {
-
-    String ACTION = "com.android.systemui.action.PLUGIN_CLOCK";
-    int VERSION = 5;
-
-    /**
-     * Get the name of the clock face.
-     *
-     * This name should not be translated.
-     */
-    String getName();
-
-    /**
-     * Get the title of the clock face to be shown in the picker app.
-     */
-    String getTitle();
-
-    /**
-     * Get thumbnail of clock face to be shown in the picker app.
-     */
-    Bitmap getThumbnail();
-
-    /**
-     * Get preview images of clock face to be shown in the picker app.
-     *
-     * Preview image should be realistic and show what the clock face will look like on AOD and lock
-     * screen.
-     *
-     * @param width width of the preview image, should be the same as device width in pixels.
-     * @param height height of the preview image, should be the same as device height in pixels.
-     */
-    Bitmap getPreview(int width, int height);
-
-    /**
-     * Get clock view.
-     * @return clock view from plugin.
-     */
-    View getView();
-
-    /**
-     * Get clock view for a large clock that appears behind NSSL.
-     */
-    default View getBigClockView() {
-        return null;
-    }
-
-    /**
-     * Returns the preferred Y position of the clock.
-     *
-     * @param totalHeight Height of the parent container.
-     * @return preferred Y position.
-     */
-    int getPreferredY(int totalHeight);
-
-    /**
-     * Allows the plugin to clean up resources when no longer needed.
-     *
-     * Called when the view previously created by {@link ClockPlugin#getView()} has been detached
-     * from the view hierarchy.
-     */
-    void onDestroyView();
-
-    /**
-     * Set clock paint style.
-     * @param style The new style to set in the paint.
-     */
-    void setStyle(Style style);
-
-    /**
-     * Set clock text color.
-     * @param color A color value.
-     */
-    void setTextColor(int color);
-
-    /**
-     * Sets the color palette for the clock face.
-     * @param supportsDarkText Whether dark text can be displayed.
-     * @param colors Colors that should be used on the clock face, ordered from darker to lighter.
-     */
-    default void setColorPalette(boolean supportsDarkText, int[] colors) {}
-
-    /**
-     * Set the amount (ratio) that the device has transitioned to doze.
-     * @param darkAmount Amount of transition to doze: 1f for doze and 0f for awake.
-     */
-    default void setDarkAmount(float darkAmount) {}
-
-    /**
-     * Notifies that time tick alarm from doze service fired.
-     *
-     * Implement this method instead of registering a broadcast listener for TIME_TICK.
-     */
-    default void onTimeTick() {}
-
-    /**
-     * Notifies that the time zone has changed.
-     *
-     * Implement this method instead of registering a broadcast listener for TIME_ZONE_CHANGED.
-     */
-    default void onTimeZoneChanged(TimeZone timeZone) {}
-
-    /**
-     * Notifies that the time format has changed.
-     *
-     * @param timeFormat "12" for 12-hour format, "24" for 24-hour format
-     */
-    default void onTimeFormatChanged(String timeFormat) {}
-
-    /**
-     * Indicates whether the keyguard status area (date) should be shown below
-     * the clock.
-     */
-    default boolean shouldShowStatusArea() {
-        return true;
-    }
-}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
index 537b7a4..d962732 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
@@ -18,7 +18,7 @@
 import android.graphics.drawable.Drawable
 import android.view.View
 import com.android.internal.annotations.Keep
-import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.MessageBuffer
 import com.android.systemui.plugins.annotations.ProvidesInterface
 import java.io.PrintWriter
 import java.util.Locale
@@ -95,7 +95,7 @@
     val animations: ClockAnimations
 
     /** Some clocks may log debug information */
-    var logBuffer: LogBuffer?
+    var messageBuffer: MessageBuffer?
 }
 
 /** Events that should call when various rendering parameters change */
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/WeatherData.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/WeatherData.kt
index a4b1cee..f83fa33 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/WeatherData.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/WeatherData.kt
@@ -2,14 +2,18 @@
 
 import android.os.Bundle
 import android.util.Log
+import android.view.View
 import androidx.annotation.VisibleForTesting
 
+typealias WeatherTouchAction = (View) -> Unit
+
 class WeatherData
 constructor(
     val description: String,
     val state: WeatherStateIcon,
     val useCelsius: Boolean,
     val temperature: Int,
+    val touchAction: WeatherTouchAction? = null,
 ) {
     companion object {
         const val DEBUG = true
@@ -20,7 +24,7 @@
         @VisibleForTesting const val TEMPERATURE_KEY = "temperature"
         private const val INVALID_WEATHER_ICON_STATE = -1
 
-        fun fromBundle(extras: Bundle): WeatherData? {
+        fun fromBundle(extras: Bundle, touchAction: WeatherTouchAction? = null): WeatherData? {
             val description = extras.getString(DESCRIPTION_KEY)
             val state =
                 WeatherStateIcon.fromInt(extras.getInt(STATE_KEY, INVALID_WEATHER_ICON_STATE))
@@ -41,7 +45,8 @@
                         description = description,
                         state = state,
                         useCelsius = extras.getBoolean(USE_CELSIUS_KEY),
-                        temperature = temperature
+                        temperature = temperature,
+                        touchAction = touchAction
                     )
                 if (DEBUG) {
                     Log.i(TAG, "Weather data parsed $result from $extras")
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
index c0b69c1..25f77ea 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
@@ -172,7 +172,6 @@
         public String expandedAccessibilityClassName;
         public SlashState slash;
         public boolean handlesLongClick = true;
-        public boolean showRippleEffect = true;
         @Nullable
         public Drawable sideViewCustomDrawable;
         public String spec;
@@ -217,7 +216,6 @@
                     || !Objects.equals(other.dualTarget, dualTarget)
                     || !Objects.equals(other.slash, slash)
                     || !Objects.equals(other.handlesLongClick, handlesLongClick)
-                    || !Objects.equals(other.showRippleEffect, showRippleEffect)
                     || !Objects.equals(other.sideViewCustomDrawable, sideViewCustomDrawable);
             other.spec = spec;
             other.icon = icon;
@@ -234,7 +232,6 @@
             other.isTransient = isTransient;
             other.slash = slash != null ? slash.copy() : null;
             other.handlesLongClick = handlesLongClick;
-            other.showRippleEffect = showRippleEffect;
             other.sideViewCustomDrawable = sideViewCustomDrawable;
             return changed;
         }
diff --git a/packages/SystemUI/plugin_core/Android.bp b/packages/SystemUI/plugin_core/Android.bp
index 34d31d9..4e39f1a 100644
--- a/packages/SystemUI/plugin_core/Android.bp
+++ b/packages/SystemUI/plugin_core/Android.bp
@@ -25,6 +25,9 @@
     sdk_version: "current",
     name: "PluginCoreLib",
     srcs: ["src/**/*.java"],
+    optimize: {
+        proguard_flags_files: ["proguard.flags"],
+    },
 
     // Enforce that the library is built against java 8 so that there are
     // no compatibility issues with launcher
diff --git a/packages/SystemUI/plugin_core/proguard.flags b/packages/SystemUI/plugin_core/proguard.flags
new file mode 100644
index 0000000..6240898
--- /dev/null
+++ b/packages/SystemUI/plugin_core/proguard.flags
@@ -0,0 +1,11 @@
+# R8's full mode is a bit more aggressive in stripping annotations, but the
+# SystemUI plugin architecture requires these annotations at runtime. The
+# following rules are the minimal set necessary to ensure compatibility.
+# For more details, see:
+# https://r8.googlesource.com/r8/+/refs/heads/master/compatibility-faq.md#r8-full-mode
+-keepattributes RuntimeVisible*Annotation*,AnnotationDefault
+
+-keep interface com.android.systemui.plugins.annotations.** {
+    *;
+}
+-keep,allowshrinking,allowoptimization,allowobfuscation,allowaccessmodification @com.android.systemui.plugins.annotations.** class *
diff --git a/packages/SystemUI/proguard.flags b/packages/SystemUI/proguard.flags
index a8ed843..b534fcec 100644
--- a/packages/SystemUI/proguard.flags
+++ b/packages/SystemUI/proguard.flags
@@ -1,13 +1,7 @@
 -include proguard_common.flags
 
--keep class com.android.systemui.statusbar.tv.TvStatusBar
 -keep class com.android.systemui.SystemUIInitializerImpl {
     *;
 }
 
--keep class com.android.systemui.tv.TvSystemUIInitializer {
-    *;
-}
-
--keep,allowoptimization,allowaccessmodification class com.android.systemui.dagger.DaggerReferenceGlobalRootComponent** { !synthetic *; }
--keep,allowoptimization,allowaccessmodification class com.android.systemui.tv.DaggerTvGlobalRootComponent** { !synthetic *; }
\ No newline at end of file
+-keep,allowoptimization,allowaccessmodification class com.android.systemui.dagger.DaggerReferenceGlobalRootComponent** { !synthetic *; }
\ No newline at end of file
diff --git a/packages/SystemUI/proguard_common.flags b/packages/SystemUI/proguard_common.flags
index 1f47e72..3194815 100644
--- a/packages/SystemUI/proguard_common.flags
+++ b/packages/SystemUI/proguard_common.flags
@@ -62,18 +62,14 @@
 -keep class ** extends androidx.preference.PreferenceFragment
 -keep class com.android.systemui.tuner.*
 
-# The plugins, log & common subpackages act as shared libraries that might be referenced in
+# The plugins and core log subpackages act as shared libraries that might be referenced in
 # dynamically-loaded plugin APKs.
--keep class com.android.systemui.plugins.** { *; }
--keep class com.android.systemui.log.ConstantStringsLoggerImpl { *; }
--keep class com.android.systemui.log.ConstantStringsLogger { *; }
--keep class com.android.systemui.log.LogBuffer { *; }
--keep class com.android.systemui.log.LogcatEchoTrackerDebug { *; }
--keep class com.android.systemui.log.LogcatEchoTracker { *; }
--keep class com.android.systemui.log.LogcatEchoTrackerProd { *; }
--keep class com.android.systemui.log.LogLevel { *; }
--keep class com.android.systemui.log.LogMessageImpl { *; }
--keep class com.android.systemui.log.LogMessage { *; }
+-keep class com.android.systemui.plugins.** {
+    *;
+}
+-keep class com.android.systemui.log.core.** {
+    *;
+}
 -keep class com.android.systemui.fragments.FragmentService$FragmentCreator {
     *;
 }
diff --git a/packages/SystemUI/res-keyguard/color/shade_disabled.xml b/packages/SystemUI/res-keyguard/color/shade_disabled.xml
new file mode 100644
index 0000000..241f203
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/color/shade_disabled.xml
@@ -0,0 +1,19 @@
+<?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.
+  -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:color="@android:color/system_neutral1_500" android:lStar="4" />
+</selector>
\ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/drawable/ic_lock_locked.xml b/packages/SystemUI/res-keyguard/drawable/ic_lock_locked.xml
new file mode 100644
index 0000000..e572985
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/ic_lock_locked.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:pathData="M17,8H18C19.1,8 20,8.9 20,10V20C20,21.1 19.1,22 18,22H6C4.9,22 4,21.1 4,20V10C4,8.9 4.9,8 6,8H7V6C7,3.24 9.24,1 12,1C14.76,1 17,3.24 17,6V8ZM12,3C10.34,3 9,4.34 9,6V8H15V6C15,4.34 13.66,3 12,3ZM6,20V10H18V20H6ZM14,15C14,16.1 13.1,17 12,17C10.9,17 10,16.1 10,15C10,13.9 10.9,13 12,13C13.1,13 14,13.9 14,15Z"
+      android:fillColor="#5F6368"
+      android:fillType="evenOdd"/>
+</vector>
diff --git a/packages/SystemUI/res-keyguard/layout/footer_actions_number_button.xml b/packages/SystemUI/res-keyguard/layout/footer_actions_number_button.xml
index a7ffe9c..c09607d 100644
--- a/packages/SystemUI/res-keyguard/layout/footer_actions_number_button.xml
+++ b/packages/SystemUI/res-keyguard/layout/footer_actions_number_button.xml
@@ -26,7 +26,7 @@
         android:layout_height="wrap_content"
         android:textAppearance="@style/TextAppearance.QS.SecurityFooter"
         android:layout_gravity="center"
-        android:textColor="?android:attr/textColorPrimary"
+        android:textColor="?attr/onShadeInactiveVariant"
         android:textSize="18sp"/>
     <ImageView
         android:id="@+id/new_dot"
diff --git a/packages/SystemUI/res-keyguard/layout/footer_actions_text_button.xml b/packages/SystemUI/res-keyguard/layout/footer_actions_text_button.xml
index 6fe7d39..1c31f1d 100644
--- a/packages/SystemUI/res-keyguard/layout/footer_actions_text_button.xml
+++ b/packages/SystemUI/res-keyguard/layout/footer_actions_text_button.xml
@@ -33,7 +33,7 @@
         android:layout_marginEnd="12dp"
         android:contentDescription="@null"
         android:src="@drawable/ic_info_outline"
-        android:tint="?android:attr/textColorSecondary" />
+        android:tint="?attr/onSurfaceVariant" />
 
     <TextView
         android:id="@+id/text"
@@ -43,7 +43,7 @@
         android:maxLines="1"
         android:ellipsize="end"
         android:textAppearance="@style/TextAppearance.QS.SecurityFooter"
-        android:textColor="?android:attr/textColorSecondary"/>
+        android:textColor="?attr/onSurfaceVariant"/>
 
     <ImageView
         android:id="@+id/new_dot"
@@ -62,5 +62,5 @@
         android:contentDescription="@null"
         android:src="@*android:drawable/ic_chevron_end"
         android:autoMirrored="true"
-        android:tint="?android:attr/textColorSecondary" />
+        android:tint="?attr/onSurfaceVariant" />
 </com.android.systemui.animation.view.LaunchableLinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_message_area.xml b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_message_area.xml
index 57b3acd..66c54f2 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_message_area.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_message_area.xml
@@ -21,6 +21,7 @@
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:layout_marginTop="@dimen/keyguard_lock_padding"
+        android:importantForAccessibility="no"
         android:ellipsize="marquee"
         android:focusable="true"
         android:gravity="center"
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml
index 3412a30..2fc1d2e 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml
@@ -29,7 +29,7 @@
     >
     <include layout="@layout/keyguard_bouncer_message_area"/>
 
-    <com.android.systemui.keyguard.bouncer.ui.BouncerMessageView
+    <com.android.systemui.bouncer.ui.BouncerMessageView
         android:id="@+id/bouncer_message_view"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml
index 78a7c17..d9011c2 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml
@@ -33,7 +33,7 @@
     android:clipToPadding="false">
     <include layout="@layout/keyguard_bouncer_message_area"/>
 
-    <com.android.systemui.keyguard.bouncer.ui.BouncerMessageView
+    <com.android.systemui.bouncer.ui.BouncerMessageView
         android:id="@+id/bouncer_message_view"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml
index 01c5443..c7d2d81 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml
@@ -29,7 +29,7 @@
     androidprv:layout_maxWidth="@dimen/keyguard_security_width">
 <include layout="@layout/keyguard_bouncer_message_area"/>
 
-<com.android.systemui.keyguard.bouncer.ui.BouncerMessageView
+<com.android.systemui.bouncer.ui.BouncerMessageView
     android:id="@+id/bouncer_message_view"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
diff --git a/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_inner.xml b/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_inner.xml
index c85449d0..8f1323d 100644
--- a/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_inner.xml
+++ b/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_inner.xml
@@ -30,12 +30,13 @@
 
         <FrameLayout
             android:id="@+id/inout_container"
-            android:layout_height="17dp"
+            android:layout_height="@dimen/status_bar_mobile_inout_container_size"
             android:layout_width="wrap_content"
             android:layout_gravity="center_vertical">
             <ImageView
                 android:id="@+id/mobile_in"
-                android:layout_height="wrap_content"
+                android:layout_height="@dimen/status_bar_mobile_signal_size"
+                android:adjustViewBounds="true"
                 android:layout_width="wrap_content"
                 android:src="@drawable/ic_activity_down"
                 android:visibility="gone"
@@ -43,7 +44,8 @@
                 />
             <ImageView
                 android:id="@+id/mobile_out"
-                android:layout_height="wrap_content"
+                android:layout_height="@dimen/status_bar_mobile_signal_size"
+                android:adjustViewBounds="true"
                 android:layout_width="wrap_content"
                 android:src="@drawable/ic_activity_up"
                 android:paddingEnd="2dp"
@@ -52,11 +54,12 @@
         </FrameLayout>
         <ImageView
             android:id="@+id/mobile_type"
-            android:layout_height="wrap_content"
+            android:layout_height="@dimen/status_bar_mobile_signal_size"
             android:layout_width="wrap_content"
             android:layout_gravity="center_vertical"
-            android:paddingStart="2.5dp"
-            android:paddingEnd="1dp"
+            android:adjustViewBounds="true"
+            android:paddingStart="2.5sp"
+            android:paddingEnd="1sp"
             android:visibility="gone" />
         <Space
             android:id="@+id/mobile_roaming_space"
@@ -70,14 +73,14 @@
             android:layout_gravity="center_vertical">
             <com.android.systemui.statusbar.AnimatedImageView
                 android:id="@+id/mobile_signal"
-                android:layout_height="wrap_content"
-                android:layout_width="wrap_content"
+                android:layout_height="@dimen/status_bar_mobile_signal_size"
+                android:layout_width="@dimen/status_bar_mobile_signal_size"
                 systemui:hasOverlappingRendering="false"
                 />
             <ImageView
                 android:id="@+id/mobile_roaming"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
+                android:layout_width="@dimen/status_bar_mobile_signal_size"
+                android:layout_height="@dimen/status_bar_mobile_signal_size"
                 android:layout_gravity="top|start"
                 android:src="@drawable/stat_sys_roaming"
                 android:contentDescription="@string/data_connection_roaming"
diff --git a/packages/SystemUI/res-keyguard/layout/udfps_keyguard_view.xml b/packages/SystemUI/res-keyguard/layout/udfps_keyguard_view.xml
new file mode 100644
index 0000000..360ef26
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/layout/udfps_keyguard_view.xml
@@ -0,0 +1,25 @@
+<?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.UdfpsKeyguardView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/udfps_animation_view"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <!-- Add fingerprint views here. See udfps_keyguard_view_internal.xml. -->
+
+</com.android.systemui.biometrics.UdfpsKeyguardView>
diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml
index 39dd90e..8c81733 100644
--- a/packages/SystemUI/res-keyguard/values/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values/dimens.xml
@@ -95,9 +95,6 @@
     <dimen name="num_pad_key_margin_end">12dp</dimen>
 
     <!-- additional offset for clock switch area items -->
-    <dimen name="small_clock_height">114dp</dimen>
-    <dimen name="small_clock_padding_top">28dp</dimen>
-    <dimen name="clock_padding_start">28dp</dimen>
     <dimen name="below_clock_padding_start">32dp</dimen>
     <dimen name="below_clock_padding_end">16dp</dimen>
     <dimen name="below_clock_padding_start_icons">28dp</dimen>
diff --git a/packages/SystemUI/res/color/brightness_slider_overlay_color.xml b/packages/SystemUI/res/color/brightness_slider_overlay_color.xml
new file mode 100644
index 0000000..a8abd79
--- /dev/null
+++ b/packages/SystemUI/res/color/brightness_slider_overlay_color.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.
+  -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_pressed="true" android:color="?attr/onShadeActive" android:alpha="0.12" />
+    <item android:state_hovered="true" android:color="?attr/onShadeActive" android:alpha="0.09" />
+    <item android:color="@color/transparent" />
+</selector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/brightness_mirror_background.xml b/packages/SystemUI/res/drawable/brightness_mirror_background.xml
index 2095103..b5c181b 100644
--- a/packages/SystemUI/res/drawable/brightness_mirror_background.xml
+++ b/packages/SystemUI/res/drawable/brightness_mirror_background.xml
@@ -15,6 +15,6 @@
   ~ limitations under the License
   -->
 <shape xmlns:android="http://schemas.android.com/apk/res/android">
-    <solid android:color="?attr/underSurfaceColor" />
+    <solid android:color="?attr/underSurface" />
     <corners android:radius="@dimen/rounded_slider_background_rounded_corner" />
 </shape>
diff --git a/packages/SystemUI/res/drawable/brightness_progress_drawable.xml b/packages/SystemUI/res/drawable/brightness_progress_drawable.xml
index 569ee76..95c7778c 100644
--- a/packages/SystemUI/res/drawable/brightness_progress_drawable.xml
+++ b/packages/SystemUI/res/drawable/brightness_progress_drawable.xml
@@ -24,7 +24,7 @@
             <shape>
                 <size android:height="@dimen/rounded_slider_track_width" />
                 <corners android:radius="@dimen/rounded_slider_track_corner_radius" />
-                <solid android:color="?attr/offStateColor" />
+                <solid android:color="?attr/shadeInactive" />
             </shape>
         </inset>
     </item>
diff --git a/packages/SystemUI/res/drawable/brightness_progress_full_drawable.xml b/packages/SystemUI/res/drawable/brightness_progress_full_drawable.xml
index 4d9188c..a9e7adf 100644
--- a/packages/SystemUI/res/drawable/brightness_progress_full_drawable.xml
+++ b/packages/SystemUI/res/drawable/brightness_progress_full_drawable.xml
@@ -22,10 +22,17 @@
         android:height="@dimen/rounded_slider_height">
         <shape>
             <size android:height="@dimen/rounded_slider_height" />
-            <solid android:color="?priv-android:attr/colorAccentPrimary" />
+            <solid android:color="?attr/shadeActive" />
             <corners android:radius="@dimen/rounded_slider_corner_radius"/>
         </shape>
     </item>
+    <item>
+        <shape>
+            <corners android:radius="@dimen/rounded_slider_corner_radius" />
+            <size android:height="@dimen/rounded_slider_height" />
+            <solid android:color="@color/brightness_slider_overlay_color" />
+        </shape>
+    </item>
     <item
         android:id="@+id/slider_icon"
         android:gravity="center_vertical|right"
@@ -34,7 +41,7 @@
         android:right="@dimen/rounded_slider_icon_inset">
         <com.android.systemui.util.AlphaTintDrawableWrapper
             android:drawable="@drawable/ic_brightness"
-            android:tint="?android:attr/textColorPrimaryInverse"
+            android:tint="?attr/onShadeActive"
         />
     </item>
 </layer-list>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/fgs_dot.xml b/packages/SystemUI/res/drawable/fgs_dot.xml
index 3669e1d..0881d7c 100644
--- a/packages/SystemUI/res/drawable/fgs_dot.xml
+++ b/packages/SystemUI/res/drawable/fgs_dot.xml
@@ -19,5 +19,5 @@
     android:shape="oval"
     android:width="12dp"
     android:height="12dp">
-    <solid android:color="?androidprv:attr/colorAccentTertiary" />
+    <solid android:color="?attr/tertiary" />
 </shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_expand_more_48dp.xml b/packages/SystemUI/res/drawable/ic_expand_more_48dp.xml
new file mode 100644
index 0000000..c61a300
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_expand_more_48dp.xml
@@ -0,0 +1,24 @@
+<!--
+Copyright (C) 2023 The Android Open Source Project
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT 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="48.0dp"
+        android:height="48.0dp"
+        android:viewportWidth="48.0"
+        android:viewportHeight="48.0">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M33.17,17.17L24.0,26.34l-9.17,-9.17L12.0,20.0l12.0,12.0 12.0,-12.0z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/immersive_cling_bg_circ.xml b/packages/SystemUI/res/drawable/immersive_cling_bg_circ.xml
new file mode 100644
index 0000000..4029702
--- /dev/null
+++ b/packages/SystemUI/res/drawable/immersive_cling_bg_circ.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="oval" >
+
+    <solid android:color="@android:color/white" />
+
+    <size
+        android:height="56dp"
+        android:width="56dp" />
+
+</shape>
diff --git a/packages/SystemUI/res/drawable/immersive_cling_light_bg_circ.xml b/packages/SystemUI/res/drawable/immersive_cling_light_bg_circ.xml
new file mode 100644
index 0000000..e3c7d0c
--- /dev/null
+++ b/packages/SystemUI/res/drawable/immersive_cling_light_bg_circ.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="oval" >
+
+    <solid android:color="#80ffffff" />
+
+    <size
+        android:height="76dp"
+        android:width="76dp" />
+
+</shape>
diff --git a/packages/SystemUI/res/drawable/list_item_background.xml b/packages/SystemUI/res/drawable/list_item_background.xml
new file mode 100644
index 0000000..2dbab9c
--- /dev/null
+++ b/packages/SystemUI/res/drawable/list_item_background.xml
@@ -0,0 +1,21 @@
+<?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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_focused="true" android:drawable="@*android:drawable/list_choice_background_material" />
+    <item android:drawable="@android:color/transparent" />
+</selector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/pin_dot_avd.xml b/packages/SystemUI/res/drawable/pin_dot_avd.xml
index 1c16251..710ba83 100644
--- a/packages/SystemUI/res/drawable/pin_dot_avd.xml
+++ b/packages/SystemUI/res/drawable/pin_dot_avd.xml
@@ -1 +1,40 @@
-<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"><aapt:attr name="android:drawable"><vector android:height="30dp" android:width="30dp" android:viewportHeight="30" android:viewportWidth="30"><group android:name="_R_G"><group android:name="_R_G_L_0_G" android:translateX="28.237000000000002" android:translateY="23.112000000000002"><path android:name="_R_G_L_0_G_D_0_P_0" android:strokeColor="#ffffff" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M-13.24 -12.11 C-11.03,-12.11 -9.24,-10.32 -9.24,-8.11 C-9.24,-5.9 -11.03,-4.11 -13.24,-4.11 C-15.44,-4.11 -17.24,-5.9 -17.24,-8.11 C-17.24,-10.32 -15.44,-12.11 -13.24,-12.11c "/></group></group><group android:name="time_group"/></vector></aapt:attr><target android:name="time_group"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="translateX" android:duration="500" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target></animated-vector>
\ No newline at end of file
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:aapt="http://schemas.android.com/aapt">
+    <aapt:attr name="android:drawable">
+        <vector
+            android:height="30dp"
+            android:width="30dp"
+            android:viewportHeight="30"
+            android:viewportWidth="30">
+            <group android:name="_R_G">
+                <group
+                    android:name="_R_G_L_0_G"
+                    android:translateX="28.237000000000002"
+                    android:translateY="23.112000000000002">
+                    <path
+                        android:name="_R_G_L_0_G_D_0_P_0"
+                        android:strokeColor="#ffffff"
+                        android:strokeLineCap="round"
+                        android:strokeLineJoin="round"
+                        android:strokeWidth="2"
+                        android:strokeAlpha="1"
+                        android:pathData=" M-13.24 -12.11 C-11.03,-12.11 -9.24,-10.32 -9.24,-8.11 C-9.24,-5.9 -11.03,-4.11 -13.24,-4.11 C-15.44,-4.11 -17.24,-5.9 -17.24,-8.11 C-17.24,-10.32 -15.44,-12.11 -13.24,-12.11c " />
+                </group>
+            </group>
+            <group android:name="time_group" />
+        </vector>
+    </aapt:attr>
+    <target android:name="time_group">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:propertyName="translateX"
+                    android:duration="500"
+                    android:startOffset="0"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType" />
+            </set>
+        </aapt:attr>
+    </target>
+</animated-vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/pin_dot_delete_avd.xml b/packages/SystemUI/res/drawable/pin_dot_delete_avd.xml
index 0f8703f..72a03bf 100644
--- a/packages/SystemUI/res/drawable/pin_dot_delete_avd.xml
+++ b/packages/SystemUI/res/drawable/pin_dot_delete_avd.xml
@@ -1 +1,130 @@
-<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"><aapt:attr name="android:drawable"><vector android:height="30dp" android:width="30dp" android:viewportHeight="30" android:viewportWidth="30"><group android:name="_R_G"><group android:name="_R_G_L_0_G" android:translateX="28.54" android:translateY="23.54"><path android:name="_R_G_L_0_G_D_0_P_0" android:fillColor="#ffffff" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-13.54 -16.54 C-9.12,-16.54 -5.54,-12.96 -5.54,-8.54 C-5.54,-4.12 -9.12,-0.54 -13.54,-0.54 C-17.96,-0.54 -21.54,-4.12 -21.54,-8.54 C-21.54,-12.96 -17.96,-16.54 -13.54,-16.54c "/><path android:name="_R_G_L_0_G_D_1_P_0" android:strokeColor="#ffffff" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="0" android:strokeAlpha="1" android:pathData=" M-13.54 -16.54 C-9.12,-16.54 -5.54,-12.96 -5.54,-8.54 C-5.54,-4.12 -9.12,-0.54 -13.54,-0.54 C-17.96,-0.54 -21.54,-4.12 -21.54,-8.54 C-21.54,-12.96 -17.96,-16.54 -13.54,-16.54c "/></group></group><group android:name="time_group"/></vector></aapt:attr><target android:name="_R_G_L_0_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="fillAlpha" android:duration="150" android:startOffset="0" android:valueFrom="1" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="fillAlpha" android:duration="200" android:startOffset="150" android:valueFrom="1" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="pathData" android:duration="150" android:startOffset="0" android:valueFrom="M-13.54 -16.54 C-9.12,-16.54 -5.54,-12.96 -5.54,-8.54 C-5.54,-4.12 -9.12,-0.54 -13.54,-0.54 C-17.96,-0.54 -21.54,-4.12 -21.54,-8.54 C-21.54,-12.96 -17.96,-16.54 -13.54,-16.54c " android:valueTo="M-13.54 -11.54 C-11.88,-11.54 -10.54,-10.2 -10.54,-8.54 C-10.54,-6.88 -11.88,-5.54 -13.54,-5.54 C-15.2,-5.54 -16.54,-6.88 -16.54,-8.54 C-16.54,-10.2 -15.2,-11.54 -13.54,-11.54c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="200" android:startOffset="150" android:valueFrom="M-13.54 -11.54 C-11.88,-11.54 -10.54,-10.2 -10.54,-8.54 C-10.54,-6.88 -11.88,-5.54 -13.54,-5.54 C-15.2,-5.54 -16.54,-6.88 -16.54,-8.54 C-16.54,-10.2 -15.2,-11.54 -13.54,-11.54c " android:valueTo="M-13.54 -12.54 C-11.33,-12.54 -9.54,-10.75 -9.54,-8.54 C-9.54,-6.33 -11.33,-4.54 -13.54,-4.54 C-15.75,-4.54 -17.54,-6.33 -17.54,-8.54 C-17.54,-10.75 -15.75,-12.54 -13.54,-12.54c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_1_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="strokeWidth" android:duration="150" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="strokeWidth" android:duration="50" android:startOffset="150" android:valueFrom="0" android:valueTo="2" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_1_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="pathData" android:duration="150" android:startOffset="0" android:valueFrom="M-13.54 -16.54 C-9.12,-16.54 -5.54,-12.96 -5.54,-8.54 C-5.54,-4.12 -9.12,-0.54 -13.54,-0.54 C-17.96,-0.54 -21.54,-4.12 -21.54,-8.54 C-21.54,-12.96 -17.96,-16.54 -13.54,-16.54c " android:valueTo="M-13.54 -11.54 C-11.88,-11.54 -10.54,-10.2 -10.54,-8.54 C-10.54,-6.88 -11.88,-5.54 -13.54,-5.54 C-15.2,-5.54 -16.54,-6.88 -16.54,-8.54 C-16.54,-10.2 -15.2,-11.54 -13.54,-11.54c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="200" android:startOffset="150" android:valueFrom="M-13.54 -11.54 C-11.88,-11.54 -10.54,-10.2 -10.54,-8.54 C-10.54,-6.88 -11.88,-5.54 -13.54,-5.54 C-15.2,-5.54 -16.54,-6.88 -16.54,-8.54 C-16.54,-10.2 -15.2,-11.54 -13.54,-11.54c " android:valueTo="M-13.54 -12.54 C-11.33,-12.54 -9.54,-10.75 -9.54,-8.54 C-9.54,-6.33 -11.33,-4.54 -13.54,-4.54 C-15.75,-4.54 -17.54,-6.33 -17.54,-8.54 C-17.54,-10.75 -15.75,-12.54 -13.54,-12.54c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="time_group"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="translateX" android:duration="500" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target></animated-vector>
\ No newline at end of file
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:aapt="http://schemas.android.com/aapt">
+    <target android:name="_R_G_L_1_G">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="150"
+                    android:propertyName="scaleX"
+                    android:startOffset="0"
+                    android:valueFrom="1"
+                    android:valueTo="0.43"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="150"
+                    android:propertyName="scaleY"
+                    android:startOffset="0"
+                    android:valueFrom="1"
+                    android:valueTo="0.43"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_1_G_D_0_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:propertyName="fillAlpha"
+                    android:duration="200"
+                    android:startOffset="150"
+                    android:valueFrom="1"
+                    android:valueTo="0"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_0_G">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="200"
+                    android:propertyName="scaleX"
+                    android:startOffset="150"
+                    android:valueFrom="0.65"
+                    android:valueTo="1"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="200"
+                    android:propertyName="scaleY"
+                    android:startOffset="150"
+                    android:valueFrom="0.65"
+                    android:valueTo="1"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="time_group">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="500"
+                    android:propertyName="translateX"
+                    android:startOffset="0"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType" />
+            </set>
+        </aapt:attr>
+    </target>
+    <aapt:attr name="android:drawable">
+        <vector
+            android:width="30dp"
+            android:height="30dp"
+            android:viewportHeight="30"
+            android:viewportWidth="30">
+            <group android:name="_R_G">
+                <group
+                    android:name="_R_G_L_1_G"
+                    android:pivotX="-13.54"
+                    android:pivotY="-8.54"
+                    android:scaleX="1"
+                    android:scaleY="1"
+                    android:translateX="28.54"
+                    android:translateY="23.54">
+                    <path
+                        android:name="_R_G_L_1_G_D_0_P_0"
+                        android:fillAlpha="1"
+                        android:fillColor="#ffffff"
+                        android:fillType="nonZero"
+                        android:pathData=" M-13.54 -16.54 C-9.12,-16.54 -5.54,-12.96 -5.54,-8.54 C-5.54,-4.12 -9.12,-0.54 -13.54,-0.54 C-17.96,-0.54 -21.54,-4.12 -21.54,-8.54 C-21.54,-12.96 -17.96,-16.54 -13.54,-16.54c " />
+                </group>
+                <group
+                    android:name="_R_G_L_0_G"
+                    android:pivotX="317.509"
+                    android:pivotY="-826.986"
+                    android:scaleX="0.65"
+                    android:scaleY="0.65"
+                    android:translateX="-302.509"
+                    android:translateY="841.986">
+                    <path
+                        android:name="_R_G_L_0_G_D_0_P_0"
+                        android:fillAlpha="1"
+                        android:fillColor="#ffffff"
+                        android:fillType="nonZero"
+                        android:pathData=" M317.51 -821.99 C314.75,-821.99 312.51,-824.23 312.51,-826.99 C312.51,-829.74 314.75,-831.99 317.51,-831.99 C320.27,-831.99 322.51,-829.74 322.51,-826.99 C322.51,-824.23 320.27,-821.99 317.51,-821.99z  M317.51 -829.99 C315.86,-829.99 314.51,-828.64 314.51,-826.99 C314.51,-825.33 315.86,-823.99 317.51,-823.99 C319.16,-823.99 320.51,-825.33 320.51,-826.99 C320.51,-828.64 319.16,-829.99 317.51,-829.99z " />
+                </group>
+            </group>
+            <group android:name="time_group" />
+        </vector>
+    </aapt:attr>
+</animated-vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/pin_dot_shape_1_avd.xml b/packages/SystemUI/res/drawable/pin_dot_shape_1_avd.xml
index f1fb2aa..02abb99 100644
--- a/packages/SystemUI/res/drawable/pin_dot_shape_1_avd.xml
+++ b/packages/SystemUI/res/drawable/pin_dot_shape_1_avd.xml
@@ -1 +1,141 @@
-<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"><aapt:attr name="android:drawable"><vector android:height="30dp" android:width="30dp" android:viewportHeight="30" android:viewportWidth="30"><group android:name="_R_G"><group android:name="_R_G_L_1_G" android:translateX="15.441" android:translateY="15.691" android:scaleY="0"><path android:name="_R_G_L_1_G_D_0_P_0" android:fillColor="#ffffff" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-0.44 -12.06 C13.5,-14.63 13.5,-14.63 10.9,-0.69 C13.5,13.25 13.5,13.25 -0.44,10.53 C-14.38,13.25 -14.38,13.25 -11.86,-0.69 C-14.38,-14.63 -14.38,-14.63 -0.44,-12.06c "/></group><group android:name="_R_G_L_0_G" android:translateX="15" android:translateY="15" android:scaleX="0" android:scaleY="0"><path android:name="_R_G_L_0_G_D_0_P_0" android:fillColor="#ffffff" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-13.65 -3.95 C-16.31,-10.09 -10.09,-16.31 -3.95,-13.65 C-3.95,-13.65 -2.94,-13.21 -2.94,-13.21 C-1.06,-12.39 1.06,-12.39 2.94,-13.21 C2.94,-13.21 3.95,-13.65 3.95,-13.65 C10.09,-16.31 16.31,-10.09 13.65,-3.95 C13.65,-3.95 13.21,-2.94 13.21,-2.94 C12.39,-1.06 12.39,1.06 13.21,2.94 C13.21,2.94 13.65,3.95 13.65,3.95 C16.31,10.09 10.09,16.31 3.95,13.65 C3.95,13.65 2.94,13.21 2.94,13.21 C1.06,12.39 -1.06,12.39 -2.94,13.21 C-2.94,13.21 -3.95,13.65 -3.95,13.65 C-10.09,16.31 -16.31,10.09 -13.65,3.95 C-13.65,3.95 -13.21,2.94 -13.21,2.94 C-12.39,1.06 -12.39,-1.06 -13.21,-2.94 C-13.21,-2.94 -13.65,-3.95 -13.65,-3.95c "/></group></group><group android:name="time_group"/></vector></aapt:attr><target android:name="_R_G_L_1_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="pathData" android:duration="67" android:startOffset="0" android:valueFrom="M-0.44 -12.06 C13.5,-14.63 13.5,-14.63 10.9,-0.69 C13.5,13.25 13.5,13.25 -0.44,10.53 C-14.38,13.25 -14.38,13.25 -11.86,-0.69 C-14.38,-14.63 -14.38,-14.63 -0.44,-12.06c " android:valueTo="M-0.44 -12.06 C13.5,-14.63 13.5,-14.63 10.9,-0.69 C13.5,13.25 13.5,13.25 -0.44,10.53 C-14.38,13.25 -14.38,13.25 -11.86,-0.69 C-14.38,-14.63 -14.38,-14.63 -0.44,-12.06c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.8,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="283" android:startOffset="67" android:valueFrom="M-0.44 -12.06 C13.5,-14.63 13.5,-14.63 10.9,-0.69 C13.5,13.25 13.5,13.25 -0.44,10.53 C-14.38,13.25 -14.38,13.25 -11.86,-0.69 C-14.38,-14.63 -14.38,-14.63 -0.44,-12.06c " android:valueTo="M-0.44 -8.69 C3.98,-8.69 7.56,-5.11 7.56,-0.69 C7.56,3.73 3.98,7.31 -0.44,7.31 C-4.86,7.31 -8.44,3.73 -8.44,-0.69 C-8.44,-5.11 -4.86,-8.69 -0.44,-8.69c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.8,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_1_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="67" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleX" android:duration="67" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="67" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleX" android:duration="283" android:startOffset="67" android:valueFrom="1" android:valueTo="0.3" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.6,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="283" android:startOffset="67" android:valueFrom="1" android:valueTo="0.3" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.6,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="367" android:valueFrom="1" android:valueTo="0" android:valueType="floatType"/></set></aapt:attr></target><target android:name="time_group"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="translateX" android:duration="500" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target></animated-vector>
\ No newline at end of file
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:aapt="http://schemas.android.com/aapt">
+    <target android:name="_R_G_L_1_G_D_0_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="67"
+                    android:propertyName="pathData"
+                    android:startOffset="0"
+                    android:valueFrom="M-0.44 -12.06 C13.5,-14.63 13.5,-14.63 10.9,-0.69 C13.5,13.25 13.5,13.25 -0.44,10.53 C-14.38,13.25 -14.38,13.25 -11.86,-0.69 C-14.38,-14.63 -14.38,-14.63 -0.44,-12.06c "
+                    android:valueTo="M-0.44 -12.06 C13.5,-14.63 13.5,-14.63 10.9,-0.69 C13.5,13.25 13.5,13.25 -0.44,10.53 C-14.38,13.25 -14.38,13.25 -11.86,-0.69 C-14.38,-14.63 -14.38,-14.63 -0.44,-12.06c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.8,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="283"
+                    android:propertyName="pathData"
+                    android:startOffset="67"
+                    android:valueFrom="M-0.44 -12.06 C13.5,-14.63 13.5,-14.63 10.9,-0.69 C13.5,13.25 13.5,13.25 -0.44,10.53 C-14.38,13.25 -14.38,13.25 -11.86,-0.69 C-14.38,-14.63 -14.38,-14.63 -0.44,-12.06c "
+                    android:valueTo="M-0.44 -8.69 C3.98,-8.69 7.56,-5.11 7.56,-0.69 C7.56,3.73 3.98,7.31 -0.44,7.31 C-4.86,7.31 -8.44,3.73 -8.44,-0.69 C-8.44,-5.11 -4.86,-8.69 -0.44,-8.69c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.8,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_1_G">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="0"
+                    android:propertyName="scaleY"
+                    android:startOffset="67"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType" />
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_0_G">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="67"
+                    android:propertyName="scaleX"
+                    android:startOffset="0"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="67"
+                    android:propertyName="scaleY"
+                    android:startOffset="0"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="283"
+                    android:propertyName="scaleX"
+                    android:startOffset="67"
+                    android:valueFrom="1"
+                    android:valueTo="0.3"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.6,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="283"
+                    android:propertyName="scaleY"
+                    android:startOffset="67"
+                    android:valueFrom="1"
+                    android:valueTo="0.3"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.6,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="time_group">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="500"
+                    android:propertyName="translateX"
+                    android:startOffset="0"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType" />
+            </set>
+        </aapt:attr>
+    </target>
+    <aapt:attr name="android:drawable">
+        <vector
+            android:width="30dp"
+            android:height="30dp"
+            android:viewportHeight="30"
+            android:viewportWidth="30">
+            <group android:name="_R_G">
+                <group
+                    android:name="_R_G_L_1_G"
+                    android:scaleY="0"
+                    android:translateX="15.441"
+                    android:translateY="15.691">
+                    <path
+                        android:name="_R_G_L_1_G_D_0_P_0"
+                        android:fillAlpha="1"
+                        android:fillColor="#ffffff"
+                        android:fillType="nonZero"
+                        android:pathData=" M-0.44 -12.06 C13.5,-14.63 13.5,-14.63 10.9,-0.69 C13.5,13.25 13.5,13.25 -0.44,10.53 C-14.38,13.25 -14.38,13.25 -11.86,-0.69 C-14.38,-14.63 -14.38,-14.63 -0.44,-12.06c " />
+                </group>
+                <group
+                    android:name="_R_G_L_0_G"
+                    android:translateX="15"
+                    android:translateY="15">
+                    <path
+                        android:name="_R_G_L_0_G_D_0_P_0"
+                        android:fillAlpha="1"
+                        android:fillColor="#ffffff"
+                        android:fillType="nonZero"
+                        android:pathData=" M-13.65 -3.95 C-16.31,-10.09 -10.09,-16.31 -3.95,-13.65 C-3.95,-13.65 -2.94,-13.21 -2.94,-13.21 C-1.06,-12.39 1.06,-12.39 2.94,-13.21 C2.94,-13.21 3.95,-13.65 3.95,-13.65 C10.09,-16.31 16.31,-10.09 13.65,-3.95 C13.65,-3.95 13.21,-2.94 13.21,-2.94 C12.39,-1.06 12.39,1.06 13.21,2.94 C13.21,2.94 13.65,3.95 13.65,3.95 C16.31,10.09 10.09,16.31 3.95,13.65 C3.95,13.65 2.94,13.21 2.94,13.21 C1.06,12.39 -1.06,12.39 -2.94,13.21 C-2.94,13.21 -3.95,13.65 -3.95,13.65 C-10.09,16.31 -16.31,10.09 -13.65,3.95 C-13.65,3.95 -13.21,2.94 -13.21,2.94 C-12.39,1.06 -12.39,-1.06 -13.21,-2.94 C-13.21,-2.94 -13.65,-3.95 -13.65,-3.95c " />
+                </group>
+            </group>
+            <group android:name="time_group" />
+        </vector>
+    </aapt:attr>
+</animated-vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/pin_dot_shape_2_avd.xml b/packages/SystemUI/res/drawable/pin_dot_shape_2_avd.xml
index 3717db8..1607327 100644
--- a/packages/SystemUI/res/drawable/pin_dot_shape_2_avd.xml
+++ b/packages/SystemUI/res/drawable/pin_dot_shape_2_avd.xml
@@ -1 +1,148 @@
-<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"><aapt:attr name="android:drawable"><vector android:height="30dp" android:width="30dp" android:viewportHeight="30" android:viewportWidth="30"><group android:name="_R_G"><group android:name="_R_G_L_1_G" android:translateX="15.397" android:translateY="15.691" android:scaleY="0"><path android:name="_R_G_L_1_G_D_0_P_0" android:fillColor="#ffffff" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-0.56 -14.03 C3.65,-13.99 14.58,7.64 11.51,10.42 C8.45,13.2 5.92,9.56 -0.46,9.61 C-6.85,9.65 -9.27,12.76 -12.33,10.46 C-15.39,8.15 -4.77,-14.07 -0.56,-14.03c "/></group><group android:name="_R_G_L_0_G" android:translateX="15" android:translateY="13.205" android:pivotY="1.795" android:scaleX="0" android:scaleY="0"><path android:name="_R_G_L_0_G_D_0_P_0" android:fillColor="#ffffff" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M12.78 7.57 C12.78,7.57 4.72,-8.55 4.72,-8.55 C2.77,-12.44 -2.77,-12.44 -4.72,-8.55 C-4.72,-8.55 -12.78,7.57 -12.78,7.57 C-15,12.01 -10.42,16.78 -5.89,14.74 C-5.89,14.74 -2.17,13.07 -2.17,13.07 C-0.79,12.45 0.79,12.45 2.17,13.07 C2.17,13.07 5.9,14.74 5.9,14.74 C10.42,16.78 15,12.01 12.78,7.57c "/></group></group><group android:name="time_group"/></vector></aapt:attr><target android:name="_R_G_L_1_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="pathData" android:duration="67" android:startOffset="0" android:valueFrom="M-0.56 -14.03 C3.65,-13.99 14.58,7.64 11.51,10.42 C8.45,13.2 5.92,9.56 -0.46,9.61 C-6.85,9.65 -9.27,12.76 -12.33,10.46 C-15.39,8.15 -4.77,-14.07 -0.56,-14.03c " android:valueTo="M-0.56 -14.03 C3.65,-13.99 14.58,7.64 11.51,10.42 C8.45,13.2 5.92,9.56 -0.46,9.61 C-6.85,9.65 -9.27,12.76 -12.33,10.46 C-15.39,8.15 -4.77,-14.07 -0.56,-14.03c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.8,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="283" android:startOffset="67" android:valueFrom="M-0.56 -14.03 C3.65,-13.99 14.58,7.64 11.51,10.42 C8.45,13.2 5.92,9.56 -0.46,9.61 C-6.85,9.65 -9.27,12.76 -12.33,10.46 C-15.39,8.15 -4.77,-14.07 -0.56,-14.03c " android:valueTo="M-0.44 -8.69 C3.98,-8.69 7.56,-5.11 7.56,-0.69 C7.56,3.73 3.98,7.31 -0.44,7.31 C-4.86,7.31 -8.44,3.73 -8.44,-0.69 C-8.44,-5.11 -4.86,-8.69 -0.44,-8.69c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.8,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_1_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="67" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="pathData" android:duration="67" android:startOffset="0" android:valueFrom="M12.78 7.57 C12.78,7.57 4.72,-8.55 4.72,-8.55 C2.77,-12.44 -2.77,-12.44 -4.72,-8.55 C-4.72,-8.55 -12.78,7.57 -12.78,7.57 C-15,12.01 -10.42,16.78 -5.89,14.74 C-5.89,14.74 -2.17,13.07 -2.17,13.07 C-0.79,12.45 0.79,12.45 2.17,13.07 C2.17,13.07 5.9,14.74 5.9,14.74 C10.42,16.78 15,12.01 12.78,7.57c " android:valueTo="M12.78 7.57 C12.78,7.57 4.72,-8.55 4.72,-8.55 C2.77,-12.44 -2.77,-12.44 -4.72,-8.55 C-4.72,-8.55 -12.78,7.57 -12.78,7.57 C-15,12.01 -10.42,16.78 -5.89,14.74 C-5.89,14.74 -2.17,13.07 -2.17,13.07 C-0.79,12.45 0.79,12.45 2.17,13.07 C2.17,13.07 5.9,14.74 5.9,14.74 C10.42,16.78 15,12.01 12.78,7.57c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.833,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="283" android:startOffset="67" android:valueFrom="M12.78 7.57 C12.78,7.57 4.72,-8.55 4.72,-8.55 C2.77,-12.44 -2.77,-12.44 -4.72,-8.55 C-4.72,-8.55 -12.78,7.57 -12.78,7.57 C-15,12.01 -10.42,16.78 -5.89,14.74 C-5.89,14.74 -2.17,13.07 -2.17,13.07 C-0.79,12.45 0.79,12.45 2.17,13.07 C2.17,13.07 5.9,14.74 5.9,14.74 C10.42,16.78 15,12.01 12.78,7.57c " android:valueTo="M6.12 0.21 C6.12,0.21 2.27,-4.35 2.27,-4.35 C1.34,-6.2 -1.3,-6.2 -2.23,-4.35 C-2.23,-4.35 -6.06,0.21 -6.06,0.21 C-7.12,2.33 -5.46,5.79 -4.03,6.54 C-2.28,7.45 -1.01,7.48 -1.01,7.48 C-1.01,7.48 1.05,7.48 1.05,7.48 C1.05,7.48 3.28,7.36 4.23,6.66 C4.92,6.15 7.18,2.33 6.12,0.21c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.833,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleX" android:duration="67" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="67" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="367" android:valueFrom="1" android:valueTo="0" android:valueType="floatType"/></set></aapt:attr></target><target android:name="time_group"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="translateX" android:duration="500" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target></animated-vector>
\ No newline at end of file
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:aapt="http://schemas.android.com/aapt">
+    <aapt:attr name="android:drawable">
+        <vector
+            android:height="30dp"
+            android:width="30dp"
+            android:viewportHeight="30"
+            android:viewportWidth="30">
+            <group android:name="_R_G">
+                <group
+                    android:name="_R_G_L_1_G"
+                    android:translateX="15.397"
+                    android:translateY="15.691"
+                    android:scaleY="0">
+                    <path
+                        android:name="_R_G_L_1_G_D_0_P_0"
+                        android:fillColor="#ffffff"
+                        android:fillAlpha="1"
+                        android:fillType="nonZero"
+                        android:pathData=" M-0.56 -14.03 C3.65,-13.99 14.58,7.64 11.51,10.42 C8.45,13.2 5.92,9.56 -0.46,9.61 C-6.85,9.65 -9.27,12.76 -12.33,10.46 C-15.39,8.15 -4.77,-14.07 -0.56,-14.03c " />
+                </group>
+                <group
+                    android:name="_R_G_L_0_G"
+                    android:translateX="15"
+                    android:translateY="13.205"
+                    android:pivotY="1.795">
+                    <path
+                        android:name="_R_G_L_0_G_D_0_P_0"
+                        android:fillColor="#ffffff"
+                        android:fillAlpha="1"
+                        android:fillType="nonZero"
+                        android:pathData=" M12.78 7.57 C12.78,7.57 4.72,-8.55 4.72,-8.55 C2.77,-12.44 -2.77,-12.44 -4.72,-8.55 C-4.72,-8.55 -12.78,7.57 -12.78,7.57 C-15,12.01 -10.42,16.78 -5.89,14.74 C-5.89,14.74 -2.17,13.07 -2.17,13.07 C-0.79,12.45 0.79,12.45 2.17,13.07 C2.17,13.07 5.9,14.74 5.9,14.74 C10.42,16.78 15,12.01 12.78,7.57c " />
+                </group>
+            </group>
+            <group android:name="time_group" />
+        </vector>
+    </aapt:attr>
+    <target android:name="_R_G_L_1_G_D_0_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:propertyName="pathData"
+                    android:duration="67"
+                    android:startOffset="0"
+                    android:valueFrom="M-0.56 -14.03 C3.65,-13.99 14.58,7.64 11.51,10.42 C8.45,13.2 5.92,9.56 -0.46,9.61 C-6.85,9.65 -9.27,12.76 -12.33,10.46 C-15.39,8.15 -4.77,-14.07 -0.56,-14.03c "
+                    android:valueTo="M-0.56 -14.03 C3.65,-13.99 14.58,7.64 11.51,10.42 C8.45,13.2 5.92,9.56 -0.46,9.61 C-6.85,9.65 -9.27,12.76 -12.33,10.46 C-15.39,8.15 -4.77,-14.07 -0.56,-14.03c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.8,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:propertyName="pathData"
+                    android:duration="283"
+                    android:startOffset="67"
+                    android:valueFrom="M-0.56 -14.03 C3.65,-13.99 14.58,7.64 11.51,10.42 C8.45,13.2 5.92,9.56 -0.46,9.61 C-6.85,9.65 -9.27,12.76 -12.33,10.46 C-15.39,8.15 -4.77,-14.07 -0.56,-14.03c "
+                    android:valueTo="M-0.44 -8.69 C3.98,-8.69 7.56,-5.11 7.56,-0.69 C7.56,3.73 3.98,7.31 -0.44,7.31 C-4.86,7.31 -8.44,3.73 -8.44,-0.69 C-8.44,-5.11 -4.86,-8.69 -0.44,-8.69c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.8,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_1_G">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:propertyName="scaleY"
+                    android:duration="0"
+                    android:startOffset="67"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType" />
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_0_G_D_0_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:propertyName="pathData"
+                    android:duration="67"
+                    android:startOffset="0"
+                    android:valueFrom="M12.78 7.57 C12.78,7.57 4.72,-8.55 4.72,-8.55 C2.77,-12.44 -2.77,-12.44 -4.72,-8.55 C-4.72,-8.55 -12.78,7.57 -12.78,7.57 C-15,12.01 -10.42,16.78 -5.89,14.74 C-5.89,14.74 -2.17,13.07 -2.17,13.07 C-0.79,12.45 0.79,12.45 2.17,13.07 C2.17,13.07 5.9,14.74 5.9,14.74 C10.42,16.78 15,12.01 12.78,7.57c "
+                    android:valueTo="M12.78 7.57 C12.78,7.57 4.72,-8.55 4.72,-8.55 C2.77,-12.44 -2.77,-12.44 -4.72,-8.55 C-4.72,-8.55 -12.78,7.57 -12.78,7.57 C-15,12.01 -10.42,16.78 -5.89,14.74 C-5.89,14.74 -2.17,13.07 -2.17,13.07 C-0.79,12.45 0.79,12.45 2.17,13.07 C2.17,13.07 5.9,14.74 5.9,14.74 C10.42,16.78 15,12.01 12.78,7.57c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.833,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:propertyName="pathData"
+                    android:duration="283"
+                    android:startOffset="67"
+                    android:valueFrom="M12.78 7.57 C12.78,7.57 4.72,-8.55 4.72,-8.55 C2.77,-12.44 -2.77,-12.44 -4.72,-8.55 C-4.72,-8.55 -12.78,7.57 -12.78,7.57 C-15,12.01 -10.42,16.78 -5.89,14.74 C-5.89,14.74 -2.17,13.07 -2.17,13.07 C-0.79,12.45 0.79,12.45 2.17,13.07 C2.17,13.07 5.9,14.74 5.9,14.74 C10.42,16.78 15,12.01 12.78,7.57c "
+                    android:valueTo="M6.12 0.21 C6.12,0.21 2.27,-4.35 2.27,-4.35 C1.34,-6.2 -1.3,-6.2 -2.23,-4.35 C-2.23,-4.35 -6.06,0.21 -6.06,0.21 C-7.12,2.33 -5.46,5.79 -4.03,6.54 C-2.28,7.45 -1.01,7.48 -1.01,7.48 C-1.01,7.48 1.05,7.48 1.05,7.48 C1.05,7.48 3.28,7.36 4.23,6.66 C4.92,6.15 7.18,2.33 6.12,0.21c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.833,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_0_G">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:propertyName="scaleX"
+                    android:duration="67"
+                    android:startOffset="0"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:propertyName="scaleY"
+                    android:duration="67"
+                    android:startOffset="0"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="time_group">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:propertyName="translateX"
+                    android:duration="500"
+                    android:startOffset="0"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType" />
+            </set>
+        </aapt:attr>
+    </target>
+</animated-vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/pin_dot_shape_3_avd.xml b/packages/SystemUI/res/drawable/pin_dot_shape_3_avd.xml
index 95b8044..78e2249 100644
--- a/packages/SystemUI/res/drawable/pin_dot_shape_3_avd.xml
+++ b/packages/SystemUI/res/drawable/pin_dot_shape_3_avd.xml
@@ -1 +1,141 @@
-<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"><aapt:attr name="android:drawable"><vector android:height="30dp" android:width="30dp" android:viewportHeight="30" android:viewportWidth="30"><group android:name="_R_G"><group android:name="_R_G_L_1_G" android:translateX="15.441" android:translateY="15.691" android:scaleY="0"><path android:name="_R_G_L_1_G_D_0_P_0" android:fillColor="#ffffff" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-10.2 -11.16 C-1.58,-18.94 4.25,-12.72 8.06,-8.64 C11.88,-4.57 17.93,1.89 9.39,9.74 C0.85,17.6 -5.06,11.3 -8.87,7.22 C-12.69,3.14 -18.81,-3.39 -10.2,-11.16c "/></group><group android:name="_R_G_L_0_G" android:translateX="15" android:translateY="15" android:scaleX="0" android:scaleY="0"><path android:name="_R_G_L_0_G_D_0_P_0" android:fillColor="#ffffff" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M10.71 10.71 C5.92,15.5 -1.85,15.5 -6.64,10.71 C-6.64,10.71 -10.71,6.64 -10.71,6.64 C-15.5,1.85 -15.5,-5.92 -10.71,-10.71 C-5.92,-15.5 1.85,-15.5 6.64,-10.71 C6.64,-10.71 10.71,-6.64 10.71,-6.64 C15.5,-1.85 15.5,5.92 10.71,10.71c "/></group></group><group android:name="time_group"/></vector></aapt:attr><target android:name="_R_G_L_1_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="pathData" android:duration="67" android:startOffset="0" android:valueFrom="M-10.2 -11.16 C-1.58,-18.94 4.25,-12.72 8.06,-8.64 C11.88,-4.57 17.93,1.89 9.39,9.74 C0.85,17.6 -5.06,11.3 -8.87,7.22 C-12.69,3.14 -18.81,-3.39 -10.2,-11.16c " android:valueTo="M-10.2 -11.16 C-1.58,-18.94 4.25,-12.72 8.06,-8.64 C11.88,-4.57 17.93,1.89 9.39,9.74 C0.85,17.6 -5.06,11.3 -8.87,7.22 C-12.69,3.14 -18.81,-3.39 -10.2,-11.16c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.8,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="283" android:startOffset="67" android:valueFrom="M-10.2 -11.16 C-1.58,-18.94 4.25,-12.72 8.06,-8.64 C11.88,-4.57 17.93,1.89 9.39,9.74 C0.85,17.6 -5.06,11.3 -8.87,7.22 C-12.69,3.14 -18.81,-3.39 -10.2,-11.16c " android:valueTo="M-0.44 -8.69 C3.98,-8.69 7.56,-5.11 7.56,-0.69 C7.56,3.73 3.98,7.31 -0.44,7.31 C-4.86,7.31 -8.44,3.73 -8.44,-0.69 C-8.44,-5.11 -4.86,-8.69 -0.44,-8.69c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.8,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_1_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="67" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleX" android:duration="67" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="67" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleX" android:duration="283" android:startOffset="67" android:valueFrom="1" android:valueTo="0.4" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.6,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="283" android:startOffset="67" android:valueFrom="1" android:valueTo="0.4" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.6,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="367" android:valueFrom="1" android:valueTo="0" android:valueType="floatType"/></set></aapt:attr></target><target android:name="time_group"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="translateX" android:duration="500" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target></animated-vector>
\ No newline at end of file
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:aapt="http://schemas.android.com/aapt">
+    <aapt:attr name="android:drawable">
+        <vector
+            android:height="30dp"
+            android:width="30dp"
+            android:viewportHeight="30"
+            android:viewportWidth="30">
+            <group android:name="_R_G">
+                <group
+                    android:name="_R_G_L_1_G"
+                    android:translateX="15.441"
+                    android:translateY="15.691"
+                    android:scaleY="0">
+                    <path
+                        android:name="_R_G_L_1_G_D_0_P_0"
+                        android:fillColor="#ffffff"
+                        android:fillAlpha="1"
+                        android:fillType="nonZero"
+                        android:pathData=" M-10.2 -11.16 C-1.58,-18.94 4.25,-12.72 8.06,-8.64 C11.88,-4.57 17.93,1.89 9.39,9.74 C0.85,17.6 -5.06,11.3 -8.87,7.22 C-12.69,3.14 -18.81,-3.39 -10.2,-11.16c " />
+                </group>
+                <group
+                    android:name="_R_G_L_0_G"
+                    android:translateX="15"
+                    android:translateY="15">
+                    <path
+                        android:name="_R_G_L_0_G_D_0_P_0"
+                        android:fillColor="#ffffff"
+                        android:fillAlpha="1"
+                        android:fillType="nonZero"
+                        android:pathData=" M10.71 10.71 C5.92,15.5 -1.85,15.5 -6.64,10.71 C-6.64,10.71 -10.71,6.64 -10.71,6.64 C-15.5,1.85 -15.5,-5.92 -10.71,-10.71 C-5.92,-15.5 1.85,-15.5 6.64,-10.71 C6.64,-10.71 10.71,-6.64 10.71,-6.64 C15.5,-1.85 15.5,5.92 10.71,10.71c " />
+                </group>
+            </group>
+            <group android:name="time_group" />
+        </vector>
+    </aapt:attr>
+    <target android:name="_R_G_L_1_G_D_0_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:propertyName="pathData"
+                    android:duration="67"
+                    android:startOffset="0"
+                    android:valueFrom="M-10.2 -11.16 C-1.58,-18.94 4.25,-12.72 8.06,-8.64 C11.88,-4.57 17.93,1.89 9.39,9.74 C0.85,17.6 -5.06,11.3 -8.87,7.22 C-12.69,3.14 -18.81,-3.39 -10.2,-11.16c "
+                    android:valueTo="M-10.2 -11.16 C-1.58,-18.94 4.25,-12.72 8.06,-8.64 C11.88,-4.57 17.93,1.89 9.39,9.74 C0.85,17.6 -5.06,11.3 -8.87,7.22 C-12.69,3.14 -18.81,-3.39 -10.2,-11.16c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.8,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:propertyName="pathData"
+                    android:duration="283"
+                    android:startOffset="67"
+                    android:valueFrom="M-10.2 -11.16 C-1.58,-18.94 4.25,-12.72 8.06,-8.64 C11.88,-4.57 17.93,1.89 9.39,9.74 C0.85,17.6 -5.06,11.3 -8.87,7.22 C-12.69,3.14 -18.81,-3.39 -10.2,-11.16c "
+                    android:valueTo="M-0.44 -8.69 C3.98,-8.69 7.56,-5.11 7.56,-0.69 C7.56,3.73 3.98,7.31 -0.44,7.31 C-4.86,7.31 -8.44,3.73 -8.44,-0.69 C-8.44,-5.11 -4.86,-8.69 -0.44,-8.69c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.8,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_1_G">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:propertyName="scaleY"
+                    android:duration="0"
+                    android:startOffset="67"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType" />
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_0_G">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:propertyName="scaleX"
+                    android:duration="67"
+                    android:startOffset="0"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:propertyName="scaleY"
+                    android:duration="67"
+                    android:startOffset="0"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:propertyName="scaleX"
+                    android:duration="283"
+                    android:startOffset="67"
+                    android:valueFrom="1"
+                    android:valueTo="0.4"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.6,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:propertyName="scaleY"
+                    android:duration="283"
+                    android:startOffset="67"
+                    android:valueFrom="1"
+                    android:valueTo="0.4"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.6,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="time_group">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:propertyName="translateX"
+                    android:duration="500"
+                    android:startOffset="0"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType" />
+            </set>
+        </aapt:attr>
+    </target>
+</animated-vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/pin_dot_shape_4_avd.xml b/packages/SystemUI/res/drawable/pin_dot_shape_4_avd.xml
index 8ea8f85..35c7210 100644
--- a/packages/SystemUI/res/drawable/pin_dot_shape_4_avd.xml
+++ b/packages/SystemUI/res/drawable/pin_dot_shape_4_avd.xml
@@ -1 +1,119 @@
-<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"><aapt:attr name="android:drawable"><vector android:height="30dp" android:width="30dp" android:viewportHeight="30" android:viewportWidth="30"><group android:name="_R_G"><group android:name="_R_G_L_1_G" android:translateX="15.441" android:translateY="15.691" android:scaleY="0"><path android:name="_R_G_L_1_G_D_0_P_0" android:fillColor="#ffffff" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-0.44 -8.69 C3.97,-8.69 7.56,-5.11 7.56,-0.69 C7.56,3.72 3.97,7.31 -0.44,7.31 C-4.86,7.31 -8.44,3.72 -8.44,-0.69 C-8.44,-5.11 -4.86,-8.69 -0.44,-8.69c "/></group><group android:name="_R_G_L_0_G" android:translateX="15" android:translateY="15" android:scaleX="0" android:scaleY="0"><path android:name="_R_G_L_0_G_D_0_P_0" android:fillColor="#ffffff" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M8 0 C8,-1.35 7.97,-1.94 7.41,-3.02 C6.85,-4.09 6.61,-4.79 5.66,-5.66 C4.7,-6.52 4.2,-6.97 3.15,-7.36 C2.09,-7.74 1.39,-8 0,-8 C-1.39,-8 -2.18,-7.78 -3.12,-7.37 C-4.07,-6.96 -4.67,-6.63 -5.66,-5.66 C-6.64,-4.68 -6.98,-4.1 -7.37,-3.13 C-7.78,-2.08 -8,-1.39 -8,0 C-8,1.4 -7.86,1.98 -7.47,2.87 C-7.08,3.76 -6.68,4.66 -5.66,5.66 C-4.63,6.65 -4,6.96 -3.12,7.37 C-2.25,7.78 -1.32,8 0,8 C1.32,8 1.86,7.88 2.9,7.46 C3.95,7.03 4.85,6.63 5.66,5.66 C6.46,4.69 6.78,4.45 7.29,3.29 C7.81,2.14 8,1.35 8,0c "/></group></group><group android:name="time_group"/></vector></aapt:attr><target android:name="_R_G_L_1_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="67" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="pathData" android:duration="67" android:startOffset="0" android:valueFrom="M8 0 C8,-1.35 7.97,-1.94 7.41,-3.02 C6.85,-4.09 6.61,-4.79 5.66,-5.66 C4.7,-6.52 4.2,-6.97 3.15,-7.36 C2.09,-7.74 1.39,-8 0,-8 C-1.39,-8 -2.18,-7.78 -3.12,-7.37 C-4.07,-6.96 -4.67,-6.63 -5.66,-5.66 C-6.64,-4.68 -6.98,-4.1 -7.37,-3.13 C-7.78,-2.08 -8,-1.39 -8,0 C-8,1.4 -7.86,1.98 -7.47,2.87 C-7.08,3.76 -6.68,4.66 -5.66,5.66 C-4.63,6.65 -4,6.96 -3.12,7.37 C-2.25,7.78 -1.32,8 0,8 C1.32,8 1.86,7.88 2.9,7.46 C3.95,7.03 4.85,6.63 5.66,5.66 C6.46,4.69 6.78,4.45 7.29,3.29 C7.81,2.14 8,1.35 8,0c " android:valueTo="M15 0 C15,-2.52 11.99,-2.3 10.94,-4.32 C9.89,-6.33 12.4,-8.98 10.61,-10.61 C8.82,-12.23 6.44,-10.15 4.46,-10.86 C2.48,-11.58 2.61,-15 0,-15 C-2.61,-15 -2.68,-11.61 -4.46,-10.84 C-6.23,-10.08 -8.76,-12.44 -10.61,-10.61 C-12.45,-8.78 -10.31,-6.69 -10.87,-4.64 C-11.43,-2.61 -15,-2.61 -15,0 C-15,2.62 -11.67,2.75 -10.94,4.42 C-10.21,6.08 -12.53,8.74 -10.61,10.61 C-8.68,12.47 -6.18,10.19 -4.54,10.96 C-2.89,11.72 -2.48,15 0,15 C2.48,15 2.49,11.78 4.45,10.99 C6.4,10.19 9.09,12.43 10.61,10.61 C12.12,8.79 10,6.52 10.97,4.36 C11.94,2.2 15,2.52 15,0c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.833,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="283" android:startOffset="67" android:valueFrom="M15 0 C15,-2.52 11.99,-2.3 10.94,-4.32 C9.89,-6.33 12.4,-8.98 10.61,-10.61 C8.82,-12.23 6.44,-10.15 4.46,-10.86 C2.48,-11.58 2.61,-15 0,-15 C-2.61,-15 -2.68,-11.61 -4.46,-10.84 C-6.23,-10.08 -8.76,-12.44 -10.61,-10.61 C-12.45,-8.78 -10.31,-6.69 -10.87,-4.64 C-11.43,-2.61 -15,-2.61 -15,0 C-15,2.62 -11.67,2.75 -10.94,4.42 C-10.21,6.08 -12.53,8.74 -10.61,10.61 C-8.68,12.47 -6.18,10.19 -4.54,10.96 C-2.89,11.72 -2.48,15 0,15 C2.48,15 2.49,11.78 4.45,10.99 C6.4,10.19 9.09,12.43 10.61,10.61 C12.12,8.79 10,6.52 10.97,4.36 C11.94,2.2 15,2.52 15,0c " android:valueTo="M7.73 0 C7.73,-1.3 7.71,-1.88 7.16,-2.92 C6.62,-3.95 6.39,-4.63 5.47,-5.47 C4.55,-6.31 4.06,-6.74 3.04,-7.11 C2.02,-7.48 1.35,-7.73 0,-7.73 C-1.34,-7.73 -2.1,-7.52 -3.02,-7.12 C-3.93,-6.73 -4.52,-6.41 -5.47,-5.47 C-6.42,-4.53 -6.75,-3.96 -7.12,-3.02 C-7.52,-2.01 -7.73,-1.35 -7.73,0 C-7.73,1.35 -7.6,1.91 -7.22,2.77 C-6.85,3.63 -6.46,4.51 -5.47,5.47 C-4.48,6.43 -3.87,6.73 -3.02,7.12 C-2.17,7.52 -1.28,7.73 0,7.73 C1.28,7.73 1.8,7.62 2.81,7.21 C3.81,6.8 4.69,6.41 5.47,5.47 C6.25,4.53 6.55,4.3 7.05,3.19 C7.55,2.07 7.73,1.3 7.73,0c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.7,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleX" android:duration="67" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="67" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="367" android:valueFrom="1" android:valueTo="0" android:valueType="floatType"/></set></aapt:attr></target><target android:name="time_group"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="translateX" android:duration="500" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target></animated-vector>
\ No newline at end of file
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:aapt="http://schemas.android.com/aapt">
+    <target android:name="_R_G_L_1_G">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="0"
+                    android:propertyName="scaleY"
+                    android:startOffset="67"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType" />
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_0_G_D_0_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="67"
+                    android:propertyName="pathData"
+                    android:startOffset="0"
+                    android:valueFrom="M8 0 C8,-1.35 7.97,-1.94 7.41,-3.02 C6.85,-4.09 6.61,-4.79 5.66,-5.66 C4.7,-6.52 4.2,-6.97 3.15,-7.36 C2.09,-7.74 1.39,-8 0,-8 C-1.39,-8 -2.18,-7.78 -3.12,-7.37 C-4.07,-6.96 -4.67,-6.63 -5.66,-5.66 C-6.64,-4.68 -6.98,-4.1 -7.37,-3.13 C-7.78,-2.08 -8,-1.39 -8,0 C-8,1.4 -7.86,1.98 -7.47,2.87 C-7.08,3.76 -6.68,4.66 -5.66,5.66 C-4.63,6.65 -4,6.96 -3.12,7.37 C-2.25,7.78 -1.32,8 0,8 C1.32,8 1.86,7.88 2.9,7.46 C3.95,7.03 4.85,6.63 5.66,5.66 C6.46,4.69 6.78,4.45 7.29,3.29 C7.81,2.14 8,1.35 8,0c "
+                    android:valueTo="M15 0 C15,-2.52 11.99,-2.3 10.94,-4.32 C9.89,-6.33 12.4,-8.98 10.61,-10.61 C8.82,-12.23 6.44,-10.15 4.46,-10.86 C2.48,-11.58 2.61,-15 0,-15 C-2.61,-15 -2.68,-11.61 -4.46,-10.84 C-6.23,-10.08 -8.76,-12.44 -10.61,-10.61 C-12.45,-8.78 -10.31,-6.69 -10.87,-4.64 C-11.43,-2.61 -15,-2.61 -15,0 C-15,2.62 -11.67,2.75 -10.94,4.42 C-10.21,6.08 -12.53,8.74 -10.61,10.61 C-8.68,12.47 -6.18,10.19 -4.54,10.96 C-2.89,11.72 -2.48,15 0,15 C2.48,15 2.49,11.78 4.45,10.99 C6.4,10.19 9.09,12.43 10.61,10.61 C12.12,8.79 10,6.52 10.97,4.36 C11.94,2.2 15,2.52 15,0c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.833,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="283"
+                    android:propertyName="pathData"
+                    android:startOffset="67"
+                    android:valueFrom="M15 0 C15,-2.52 11.99,-2.3 10.94,-4.32 C9.89,-6.33 12.4,-8.98 10.61,-10.61 C8.82,-12.23 6.44,-10.15 4.46,-10.86 C2.48,-11.58 2.61,-15 0,-15 C-2.61,-15 -2.68,-11.61 -4.46,-10.84 C-6.23,-10.08 -8.76,-12.44 -10.61,-10.61 C-12.45,-8.78 -10.31,-6.69 -10.87,-4.64 C-11.43,-2.61 -15,-2.61 -15,0 C-15,2.62 -11.67,2.75 -10.94,4.42 C-10.21,6.08 -12.53,8.74 -10.61,10.61 C-8.68,12.47 -6.18,10.19 -4.54,10.96 C-2.89,11.72 -2.48,15 0,15 C2.48,15 2.49,11.78 4.45,10.99 C6.4,10.19 9.09,12.43 10.61,10.61 C12.12,8.79 10,6.52 10.97,4.36 C11.94,2.2 15,2.52 15,0c "
+                    android:valueTo="M7.73 0 C7.73,-1.3 7.71,-1.88 7.16,-2.92 C6.62,-3.95 6.39,-4.63 5.47,-5.47 C4.55,-6.31 4.06,-6.74 3.04,-7.11 C2.02,-7.48 1.35,-7.73 0,-7.73 C-1.34,-7.73 -2.1,-7.52 -3.02,-7.12 C-3.93,-6.73 -4.52,-6.41 -5.47,-5.47 C-6.42,-4.53 -6.75,-3.96 -7.12,-3.02 C-7.52,-2.01 -7.73,-1.35 -7.73,0 C-7.73,1.35 -7.6,1.91 -7.22,2.77 C-6.85,3.63 -6.46,4.51 -5.47,5.47 C-4.48,6.43 -3.87,6.73 -3.02,7.12 C-2.17,7.52 -1.28,7.73 0,7.73 C1.28,7.73 1.8,7.62 2.81,7.21 C3.81,6.8 4.69,6.41 5.47,5.47 C6.25,4.53 6.55,4.3 7.05,3.19 C7.55,2.07 7.73,1.3 7.73,0c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.7,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_0_G">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="67"
+                    android:propertyName="scaleX"
+                    android:startOffset="0"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="67"
+                    android:propertyName="scaleY"
+                    android:startOffset="0"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="time_group">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="500"
+                    android:propertyName="translateX"
+                    android:startOffset="0"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType" />
+            </set>
+        </aapt:attr>
+    </target>
+    <aapt:attr name="android:drawable">
+        <vector
+            android:width="30dp"
+            android:height="30dp"
+            android:viewportHeight="30"
+            android:viewportWidth="30">
+            <group android:name="_R_G">
+                <group
+                    android:name="_R_G_L_1_G"
+                    android:scaleY="0"
+                    android:translateX="15.441"
+                    android:translateY="15.691">
+                    <path
+                        android:name="_R_G_L_1_G_D_0_P_0"
+                        android:fillAlpha="1"
+                        android:fillColor="#ffffff"
+                        android:fillType="nonZero"
+                        android:pathData=" M-0.44 -8.69 C3.97,-8.69 7.56,-5.11 7.56,-0.69 C7.56,3.72 3.97,7.31 -0.44,7.31 C-4.86,7.31 -8.44,3.72 -8.44,-0.69 C-8.44,-5.11 -4.86,-8.69 -0.44,-8.69c " />
+                </group>
+                <group
+                    android:name="_R_G_L_0_G"
+                    android:translateX="15"
+                    android:translateY="15">
+                    <path
+                        android:name="_R_G_L_0_G_D_0_P_0"
+                        android:fillAlpha="1"
+                        android:fillColor="#ffffff"
+                        android:fillType="nonZero"
+                        android:pathData=" M8 0 C8,-1.35 7.97,-1.94 7.41,-3.02 C6.85,-4.09 6.61,-4.79 5.66,-5.66 C4.7,-6.52 4.2,-6.97 3.15,-7.36 C2.09,-7.74 1.39,-8 0,-8 C-1.39,-8 -2.18,-7.78 -3.12,-7.37 C-4.07,-6.96 -4.67,-6.63 -5.66,-5.66 C-6.64,-4.68 -6.98,-4.1 -7.37,-3.13 C-7.78,-2.08 -8,-1.39 -8,0 C-8,1.4 -7.86,1.98 -7.47,2.87 C-7.08,3.76 -6.68,4.66 -5.66,5.66 C-4.63,6.65 -4,6.96 -3.12,7.37 C-2.25,7.78 -1.32,8 0,8 C1.32,8 1.86,7.88 2.9,7.46 C3.95,7.03 4.85,6.63 5.66,5.66 C6.46,4.69 6.78,4.45 7.29,3.29 C7.81,2.14 8,1.35 8,0c " />
+                </group>
+            </group>
+            <group android:name="time_group" />
+        </vector>
+    </aapt:attr>
+</animated-vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/pin_dot_shape_5_avd.xml b/packages/SystemUI/res/drawable/pin_dot_shape_5_avd.xml
index 3779c80..9458b2e 100644
--- a/packages/SystemUI/res/drawable/pin_dot_shape_5_avd.xml
+++ b/packages/SystemUI/res/drawable/pin_dot_shape_5_avd.xml
@@ -1 +1,148 @@
-<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"><aapt:attr name="android:drawable"><vector android:height="30dp" android:width="30dp" android:viewportHeight="30" android:viewportWidth="30"><group android:name="_R_G"><group android:name="_R_G_L_1_G" android:translateX="15.397" android:translateY="15.691" android:scaleY="0"><path android:name="_R_G_L_1_G_D_0_P_0" android:fillColor="#ffffff" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-0.48 -12.86 C2.96,-12.87 14.59,5.93 11.98,9.12 C9.42,12.26 5.76,11.36 -0.48,11.41 C-6.72,11.45 -10.24,11.91 -12.78,9.16 C-15.4,6.32 -3.91,-12.85 -0.48,-12.86c "/></group><group android:name="_R_G_L_0_G" android:translateX="15" android:translateY="17.64" android:pivotY="-2.64" android:scaleX="0" android:scaleY="0"><path android:name="_R_G_L_0_G_D_0_P_0" android:fillColor="#ffffff" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-4.68 -13.34 C-2.59,-17.01 2.64,-17 4.72,-13.33 C4.72,-13.33 13.65,2.45 13.65,2.45 C15.72,6.11 13.11,10.66 8.94,10.65 C8.94,10.65 -8.98,10.62 -8.98,10.62 C-13.15,10.61 -15.75,6.05 -13.67,2.4 C-13.67,2.4 -4.68,-13.34 -4.68,-13.34c "/></group></group><group android:name="time_group"/></vector></aapt:attr><target android:name="_R_G_L_1_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="pathData" android:duration="67" android:startOffset="0" android:valueFrom="M-0.48 -12.86 C2.96,-12.87 14.59,5.93 11.98,9.12 C9.42,12.26 5.76,11.36 -0.48,11.41 C-6.72,11.45 -10.24,11.91 -12.78,9.16 C-15.4,6.32 -3.91,-12.85 -0.48,-12.86c " android:valueTo="M-0.48 -12.86 C2.96,-12.87 14.59,5.93 11.98,9.12 C9.42,12.26 5.76,11.36 -0.48,11.41 C-6.72,11.45 -10.24,11.91 -12.78,9.16 C-15.4,6.32 -3.91,-12.85 -0.48,-12.86c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.7,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="283" android:startOffset="67" android:valueFrom="M-0.48 -12.86 C2.96,-12.87 14.59,5.93 11.98,9.12 C9.42,12.26 5.76,11.36 -0.48,11.41 C-6.72,11.45 -10.24,11.91 -12.78,9.16 C-15.4,6.32 -3.91,-12.85 -0.48,-12.86c " android:valueTo="M-0.44 -8.69 C3.98,-8.69 7.56,-5.11 7.56,-0.69 C7.56,3.73 3.98,7.31 -0.44,7.31 C-4.86,7.31 -8.44,3.73 -8.44,-0.69 C-8.44,-5.11 -4.86,-8.69 -0.44,-8.69c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.7,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_1_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="67" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="pathData" android:duration="67" android:startOffset="0" android:valueFrom="M-4.68 -13.34 C-2.59,-17.01 2.64,-17 4.72,-13.33 C4.72,-13.33 13.65,2.45 13.65,2.45 C15.72,6.11 13.11,10.66 8.94,10.65 C8.94,10.65 -8.98,10.62 -8.98,10.62 C-13.15,10.61 -15.75,6.05 -13.67,2.4 C-13.67,2.4 -4.68,-13.34 -4.68,-13.34c " android:valueTo="M-4.68 -13.34 C-2.59,-17.01 2.64,-17 4.72,-13.33 C4.72,-13.33 13.65,2.45 13.65,2.45 C15.72,6.11 13.11,10.66 8.94,10.65 C8.94,10.65 -8.98,10.62 -8.98,10.62 C-13.15,10.61 -15.75,6.05 -13.67,2.4 C-13.67,2.4 -4.68,-13.34 -4.68,-13.34c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.7,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="283" android:startOffset="67" android:valueFrom="M-4.68 -13.34 C-2.59,-17.01 2.64,-17 4.72,-13.33 C4.72,-13.33 13.65,2.45 13.65,2.45 C15.72,6.11 13.11,10.66 8.94,10.65 C8.94,10.65 -8.98,10.62 -8.98,10.62 C-13.15,10.61 -15.75,6.05 -13.67,2.4 C-13.67,2.4 -4.68,-13.34 -4.68,-13.34c " android:valueTo="M-2.99 -8.52 C-1.33,-9.83 1.52,-9.34 3.25,-8.28 C4.98,-7.22 6.77,-5.88 6.7,-2.4 C6.64,1.08 4.48,2.26 3.2,3.05 C1.92,3.83 -1.1,3.72 -3.03,2.76 C-4.97,1.79 -6.38,0.3 -6.37,-2.59 C-6.36,-5.49 -4.65,-7.2 -2.99,-8.52c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.7,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleX" android:duration="67" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="67" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="367" android:valueFrom="1" android:valueTo="0" android:valueType="floatType"/></set></aapt:attr></target><target android:name="time_group"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="translateX" android:duration="500" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target></animated-vector>
\ No newline at end of file
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:aapt="http://schemas.android.com/aapt">
+    <aapt:attr name="android:drawable">
+        <vector
+            android:height="30dp"
+            android:width="30dp"
+            android:viewportHeight="30"
+            android:viewportWidth="30">
+            <group android:name="_R_G">
+                <group
+                    android:name="_R_G_L_1_G"
+                    android:translateX="15.397"
+                    android:translateY="15.691"
+                    android:scaleY="0">
+                    <path
+                        android:name="_R_G_L_1_G_D_0_P_0"
+                        android:fillColor="#ffffff"
+                        android:fillAlpha="1"
+                        android:fillType="nonZero"
+                        android:pathData=" M-0.48 -12.86 C2.96,-12.87 14.59,5.93 11.98,9.12 C9.42,12.26 5.76,11.36 -0.48,11.41 C-6.72,11.45 -10.24,11.91 -12.78,9.16 C-15.4,6.32 -3.91,-12.85 -0.48,-12.86c " />
+                </group>
+                <group
+                    android:name="_R_G_L_0_G"
+                    android:translateX="15"
+                    android:translateY="17.64"
+                    android:pivotY="-2.64">
+                    <path
+                        android:name="_R_G_L_0_G_D_0_P_0"
+                        android:fillColor="#ffffff"
+                        android:fillAlpha="1"
+                        android:fillType="nonZero"
+                        android:pathData=" M-4.68 -13.34 C-2.59,-17.01 2.64,-17 4.72,-13.33 C4.72,-13.33 13.65,2.45 13.65,2.45 C15.72,6.11 13.11,10.66 8.94,10.65 C8.94,10.65 -8.98,10.62 -8.98,10.62 C-13.15,10.61 -15.75,6.05 -13.67,2.4 C-13.67,2.4 -4.68,-13.34 -4.68,-13.34c " />
+                </group>
+            </group>
+            <group android:name="time_group" />
+        </vector>
+    </aapt:attr>
+    <target android:name="_R_G_L_1_G_D_0_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:propertyName="pathData"
+                    android:duration="67"
+                    android:startOffset="0"
+                    android:valueFrom="M-0.48 -12.86 C2.96,-12.87 14.59,5.93 11.98,9.12 C9.42,12.26 5.76,11.36 -0.48,11.41 C-6.72,11.45 -10.24,11.91 -12.78,9.16 C-15.4,6.32 -3.91,-12.85 -0.48,-12.86c "
+                    android:valueTo="M-0.48 -12.86 C2.96,-12.87 14.59,5.93 11.98,9.12 C9.42,12.26 5.76,11.36 -0.48,11.41 C-6.72,11.45 -10.24,11.91 -12.78,9.16 C-15.4,6.32 -3.91,-12.85 -0.48,-12.86c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.7,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:propertyName="pathData"
+                    android:duration="283"
+                    android:startOffset="67"
+                    android:valueFrom="M-0.48 -12.86 C2.96,-12.87 14.59,5.93 11.98,9.12 C9.42,12.26 5.76,11.36 -0.48,11.41 C-6.72,11.45 -10.24,11.91 -12.78,9.16 C-15.4,6.32 -3.91,-12.85 -0.48,-12.86c "
+                    android:valueTo="M-0.44 -8.69 C3.98,-8.69 7.56,-5.11 7.56,-0.69 C7.56,3.73 3.98,7.31 -0.44,7.31 C-4.86,7.31 -8.44,3.73 -8.44,-0.69 C-8.44,-5.11 -4.86,-8.69 -0.44,-8.69c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.7,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_1_G">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:propertyName="scaleY"
+                    android:duration="0"
+                    android:startOffset="67"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType" />
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_0_G_D_0_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:propertyName="pathData"
+                    android:duration="67"
+                    android:startOffset="0"
+                    android:valueFrom="M-4.68 -13.34 C-2.59,-17.01 2.64,-17 4.72,-13.33 C4.72,-13.33 13.65,2.45 13.65,2.45 C15.72,6.11 13.11,10.66 8.94,10.65 C8.94,10.65 -8.98,10.62 -8.98,10.62 C-13.15,10.61 -15.75,6.05 -13.67,2.4 C-13.67,2.4 -4.68,-13.34 -4.68,-13.34c "
+                    android:valueTo="M-4.68 -13.34 C-2.59,-17.01 2.64,-17 4.72,-13.33 C4.72,-13.33 13.65,2.45 13.65,2.45 C15.72,6.11 13.11,10.66 8.94,10.65 C8.94,10.65 -8.98,10.62 -8.98,10.62 C-13.15,10.61 -15.75,6.05 -13.67,2.4 C-13.67,2.4 -4.68,-13.34 -4.68,-13.34c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.7,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:propertyName="pathData"
+                    android:duration="283"
+                    android:startOffset="67"
+                    android:valueFrom="M-4.68 -13.34 C-2.59,-17.01 2.64,-17 4.72,-13.33 C4.72,-13.33 13.65,2.45 13.65,2.45 C15.72,6.11 13.11,10.66 8.94,10.65 C8.94,10.65 -8.98,10.62 -8.98,10.62 C-13.15,10.61 -15.75,6.05 -13.67,2.4 C-13.67,2.4 -4.68,-13.34 -4.68,-13.34c "
+                    android:valueTo="M-2.99 -8.52 C-1.33,-9.83 1.52,-9.34 3.25,-8.28 C4.98,-7.22 6.77,-5.88 6.7,-2.4 C6.64,1.08 4.48,2.26 3.2,3.05 C1.92,3.83 -1.1,3.72 -3.03,2.76 C-4.97,1.79 -6.38,0.3 -6.37,-2.59 C-6.36,-5.49 -4.65,-7.2 -2.99,-8.52c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.7,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_0_G">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:propertyName="scaleX"
+                    android:duration="67"
+                    android:startOffset="0"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:propertyName="scaleY"
+                    android:duration="67"
+                    android:startOffset="0"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="time_group">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:propertyName="translateX"
+                    android:duration="500"
+                    android:startOffset="0"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType" />
+            </set>
+        </aapt:attr>
+    </target>
+</animated-vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/pin_dot_shape_6_avd.xml b/packages/SystemUI/res/drawable/pin_dot_shape_6_avd.xml
index ddda94a..06e27df 100644
--- a/packages/SystemUI/res/drawable/pin_dot_shape_6_avd.xml
+++ b/packages/SystemUI/res/drawable/pin_dot_shape_6_avd.xml
@@ -1 +1,142 @@
-<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"><aapt:attr name="android:drawable"><vector android:height="30dp" android:width="30dp" android:viewportHeight="30" android:viewportWidth="30"><group android:name="_R_G"><group android:name="_R_G_L_1_G" android:translateX="15.397" android:translateY="15.691" android:scaleY="0"><path android:name="_R_G_L_1_G_D_0_P_0" android:fillColor="#ffffff" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-0.46 -13.65 C3.05,-13.65 12.63,-6.57 12.9,-3.63 C12.98,-2.7 12.65,12.85 -0.46,12.85 C-13.57,12.85 -13.77,-2.84 -13.76,-3.63 C-13.72,-6.87 -3.96,-13.65 -0.46,-13.65c "/></group><group android:name="_R_G_L_0_G" android:translateX="15" android:translateY="15.859" android:pivotY="-0.859" android:scaleX="0" android:scaleY="0"><path android:name="_R_G_L_0_G_D_0_P_0" android:fillColor="#ffffff" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-2.82 -14.07 C-1.14,-15.29 1.14,-15.29 2.82,-14.07 C2.82,-14.07 7.73,-10.53 7.73,-10.53 C7.73,-10.53 12.58,-7 12.58,-7 C14.29,-5.76 15,-3.55 14.35,-1.54 C14.35,-1.54 12.51,4.14 12.51,4.14 C12.51,4.14 10.64,9.85 10.64,9.85 C9.99,11.84 8.14,13.19 6.05,13.19 C6.05,13.19 0,13.2 0,13.2 C0,13.2 -6.05,13.19 -6.05,13.19 C-8.14,13.19 -9.98,11.84 -10.64,9.85 C-10.64,9.85 -12.51,4.14 -12.51,4.14 C-12.51,4.14 -14.35,-1.54 -14.35,-1.54 C-15,-3.55 -14.29,-5.76 -12.58,-7 C-12.58,-7 -7.73,-10.53 -7.73,-10.53 C-7.73,-10.53 -2.82,-14.07 -2.82,-14.07c "/></group></group><group android:name="time_group"/></vector></aapt:attr><target android:name="_R_G_L_1_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="pathData" android:duration="67" android:startOffset="0" android:valueFrom="M-0.46 -13.65 C3.05,-13.65 12.63,-6.57 12.9,-3.63 C12.98,-2.7 12.65,12.85 -0.46,12.85 C-13.57,12.85 -13.77,-2.84 -13.76,-3.63 C-13.72,-6.87 -3.96,-13.65 -0.46,-13.65c " android:valueTo="M-0.46 -13.65 C3.05,-13.65 12.63,-6.57 12.9,-3.63 C12.98,-2.7 12.65,12.85 -0.46,12.85 C-13.57,12.85 -13.77,-2.84 -13.76,-3.63 C-13.72,-6.87 -3.96,-13.65 -0.46,-13.65c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.8,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="283" android:startOffset="67" android:valueFrom="M-0.46 -13.65 C3.05,-13.65 12.63,-6.57 12.9,-3.63 C12.98,-2.7 12.65,12.85 -0.46,12.85 C-13.57,12.85 -13.77,-2.84 -13.76,-3.63 C-13.72,-6.87 -3.96,-13.65 -0.46,-13.65c " android:valueTo="M-0.44 -8.69 C3.98,-8.69 7.56,-5.11 7.56,-0.69 C7.56,3.73 3.98,7.31 -0.44,7.31 C-4.86,7.31 -8.44,3.73 -8.44,-0.69 C-8.44,-5.11 -4.86,-8.69 -0.44,-8.69c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.8,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_1_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="67" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleX" android:duration="67" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="67" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleX" android:duration="283" android:startOffset="67" android:valueFrom="1" android:valueTo="0.35000000000000003" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.6,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="283" android:startOffset="67" android:valueFrom="1" android:valueTo="0.35000000000000003" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.6,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="367" android:valueFrom="1" android:valueTo="0" android:valueType="floatType"/></set></aapt:attr></target><target android:name="time_group"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="translateX" android:duration="500" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target></animated-vector>
\ No newline at end of file
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:aapt="http://schemas.android.com/aapt">
+    <aapt:attr name="android:drawable">
+        <vector
+            android:height="30dp"
+            android:width="30dp"
+            android:viewportHeight="30"
+            android:viewportWidth="30">
+            <group android:name="_R_G">
+                <group
+                    android:name="_R_G_L_1_G"
+                    android:translateX="15.397"
+                    android:translateY="15.691"
+                    android:scaleY="0">
+                    <path
+                        android:name="_R_G_L_1_G_D_0_P_0"
+                        android:fillColor="#ffffff"
+                        android:fillAlpha="1"
+                        android:fillType="nonZero"
+                        android:pathData=" M-0.46 -13.65 C3.05,-13.65 12.63,-6.57 12.9,-3.63 C12.98,-2.7 12.65,12.85 -0.46,12.85 C-13.57,12.85 -13.77,-2.84 -13.76,-3.63 C-13.72,-6.87 -3.96,-13.65 -0.46,-13.65c " />
+                </group>
+                <group
+                    android:name="_R_G_L_0_G"
+                    android:translateX="15"
+                    android:translateY="15.859"
+                    android:pivotY="-0.859">
+                    <path
+                        android:name="_R_G_L_0_G_D_0_P_0"
+                        android:fillColor="#ffffff"
+                        android:fillAlpha="1"
+                        android:fillType="nonZero"
+                        android:pathData=" M-2.82 -14.07 C-1.14,-15.29 1.14,-15.29 2.82,-14.07 C2.82,-14.07 7.73,-10.53 7.73,-10.53 C7.73,-10.53 12.58,-7 12.58,-7 C14.29,-5.76 15,-3.55 14.35,-1.54 C14.35,-1.54 12.51,4.14 12.51,4.14 C12.51,4.14 10.64,9.85 10.64,9.85 C9.99,11.84 8.14,13.19 6.05,13.19 C6.05,13.19 0,13.2 0,13.2 C0,13.2 -6.05,13.19 -6.05,13.19 C-8.14,13.19 -9.98,11.84 -10.64,9.85 C-10.64,9.85 -12.51,4.14 -12.51,4.14 C-12.51,4.14 -14.35,-1.54 -14.35,-1.54 C-15,-3.55 -14.29,-5.76 -12.58,-7 C-12.58,-7 -7.73,-10.53 -7.73,-10.53 C-7.73,-10.53 -2.82,-14.07 -2.82,-14.07c " />
+                </group>
+            </group>
+            <group android:name="time_group" />
+        </vector>
+    </aapt:attr>
+    <target android:name="_R_G_L_1_G_D_0_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:propertyName="pathData"
+                    android:duration="67"
+                    android:startOffset="0"
+                    android:valueFrom="M-0.46 -13.65 C3.05,-13.65 12.63,-6.57 12.9,-3.63 C12.98,-2.7 12.65,12.85 -0.46,12.85 C-13.57,12.85 -13.77,-2.84 -13.76,-3.63 C-13.72,-6.87 -3.96,-13.65 -0.46,-13.65c "
+                    android:valueTo="M-0.46 -13.65 C3.05,-13.65 12.63,-6.57 12.9,-3.63 C12.98,-2.7 12.65,12.85 -0.46,12.85 C-13.57,12.85 -13.77,-2.84 -13.76,-3.63 C-13.72,-6.87 -3.96,-13.65 -0.46,-13.65c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.8,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:propertyName="pathData"
+                    android:duration="283"
+                    android:startOffset="67"
+                    android:valueFrom="M-0.46 -13.65 C3.05,-13.65 12.63,-6.57 12.9,-3.63 C12.98,-2.7 12.65,12.85 -0.46,12.85 C-13.57,12.85 -13.77,-2.84 -13.76,-3.63 C-13.72,-6.87 -3.96,-13.65 -0.46,-13.65c "
+                    android:valueTo="M-0.44 -8.69 C3.98,-8.69 7.56,-5.11 7.56,-0.69 C7.56,3.73 3.98,7.31 -0.44,7.31 C-4.86,7.31 -8.44,3.73 -8.44,-0.69 C-8.44,-5.11 -4.86,-8.69 -0.44,-8.69c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.8,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_1_G">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:propertyName="scaleY"
+                    android:duration="0"
+                    android:startOffset="67"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType" />
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_0_G">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:propertyName="scaleX"
+                    android:duration="67"
+                    android:startOffset="0"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:propertyName="scaleY"
+                    android:duration="67"
+                    android:startOffset="0"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:propertyName="scaleX"
+                    android:duration="283"
+                    android:startOffset="67"
+                    android:valueFrom="1"
+                    android:valueTo="0.35000000000000003"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.6,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:propertyName="scaleY"
+                    android:duration="283"
+                    android:startOffset="67"
+                    android:valueFrom="1"
+                    android:valueTo="0.35000000000000003"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.6,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="time_group">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:propertyName="translateX"
+                    android:duration="500"
+                    android:startOffset="0"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType" />
+            </set>
+        </aapt:attr>
+    </target>
+</animated-vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/qs_customizer_background_primary.xml b/packages/SystemUI/res/drawable/qs_customizer_background_primary.xml
index ea0aafd..e138d09 100644
--- a/packages/SystemUI/res/drawable/qs_customizer_background_primary.xml
+++ b/packages/SystemUI/res/drawable/qs_customizer_background_primary.xml
@@ -15,7 +15,7 @@
 -->
 <inset xmlns:android="http://schemas.android.com/apk/res/android">
     <shape>
-        <solid android:color="?attr/underSurfaceColor"/>
+        <solid android:color="?attr/underSurface"/>
         <corners android:radius="?android:attr/dialogCornerRadius" />
     </shape>
 </inset>
diff --git a/packages/SystemUI/res/drawable/qs_customizer_toolbar.xml b/packages/SystemUI/res/drawable/qs_customizer_toolbar.xml
index ef950fe..f1a24aa 100644
--- a/packages/SystemUI/res/drawable/qs_customizer_toolbar.xml
+++ b/packages/SystemUI/res/drawable/qs_customizer_toolbar.xml
@@ -15,6 +15,6 @@
 -->
 <inset xmlns:android="http://schemas.android.com/apk/res/android">
     <shape>
-        <solid android:color="?attr/underSurfaceColor"/>
+        <solid android:color="?attr/underSurface"/>
     </shape>
 </inset>
diff --git a/packages/SystemUI/res/drawable/qs_dialog_btn_filled.xml b/packages/SystemUI/res/drawable/qs_dialog_btn_filled.xml
index 14cb1de9..c4e45bf 100644
--- a/packages/SystemUI/res/drawable/qs_dialog_btn_filled.xml
+++ b/packages/SystemUI/res/drawable/qs_dialog_btn_filled.xml
@@ -28,7 +28,7 @@
         <item>
             <shape android:shape="rectangle">
                 <corners android:radius="?android:attr/buttonCornerRadius"/>
-                <solid android:color="?androidprv:attr/colorAccentPrimary"/>
+                <solid android:color="?androidprv:attr/materialColorPrimary"/>
                 <padding android:left="@dimen/dialog_button_horizontal_padding"
                          android:top="@dimen/dialog_button_vertical_padding"
                          android:right="@dimen/dialog_button_horizontal_padding"
diff --git a/packages/SystemUI/res/drawable/qs_dialog_btn_filled_large.xml b/packages/SystemUI/res/drawable/qs_dialog_btn_filled_large.xml
index 0544b871..1590daa 100644
--- a/packages/SystemUI/res/drawable/qs_dialog_btn_filled_large.xml
+++ b/packages/SystemUI/res/drawable/qs_dialog_btn_filled_large.xml
@@ -26,7 +26,7 @@
     <item>
         <shape android:shape="rectangle">
             <corners android:radius="18dp"/>
-            <solid android:color="?androidprv:attr/colorAccentPrimary"/>
+            <solid android:color="?androidprv:attr/materialColorPrimaryFixed"/>
         </shape>
     </item>
 </ripple>
diff --git a/packages/SystemUI/res/drawable/qs_dialog_btn_outline.xml b/packages/SystemUI/res/drawable/qs_dialog_btn_outline.xml
index a47299d..b0dc652 100644
--- a/packages/SystemUI/res/drawable/qs_dialog_btn_outline.xml
+++ b/packages/SystemUI/res/drawable/qs_dialog_btn_outline.xml
@@ -29,7 +29,7 @@
             <shape android:shape="rectangle">
                 <corners android:radius="?android:attr/buttonCornerRadius"/>
                 <solid android:color="@android:color/transparent"/>
-                <stroke android:color="?androidprv:attr/colorAccentPrimaryVariant"
+                <stroke android:color="?androidprv:attr/materialColorPrimary"
                         android:width="1dp"
                 />
                 <padding android:left="@dimen/dialog_button_horizontal_padding"
diff --git a/packages/SystemUI/res/drawable/qs_footer_action_circle.xml b/packages/SystemUI/res/drawable/qs_footer_action_circle.xml
index c8c36b0..4a5d4af 100644
--- a/packages/SystemUI/res/drawable/qs_footer_action_circle.xml
+++ b/packages/SystemUI/res/drawable/qs_footer_action_circle.xml
@@ -28,7 +28,7 @@
         </item>
         <item>
             <shape android:shape="rectangle">
-                <solid android:color="?attr/offStateColor"/>
+                <solid android:color="?attr/shadeInactive"/>
                 <corners android:radius="@dimen/qs_footer_action_corner_radius"/>
             </shape>
         </item>
diff --git a/packages/SystemUI/res/drawable/qs_footer_action_circle_color.xml b/packages/SystemUI/res/drawable/qs_footer_action_circle_color.xml
index 6a365000..a8c0349 100644
--- a/packages/SystemUI/res/drawable/qs_footer_action_circle_color.xml
+++ b/packages/SystemUI/res/drawable/qs_footer_action_circle_color.xml
@@ -28,7 +28,7 @@
         </item>
         <item>
             <shape android:shape="rectangle">
-                <solid android:color="?android:attr/colorAccent"/>
+                <solid android:color="?attr/shadeActive"/>
                 <corners android:radius="@dimen/qs_footer_action_corner_radius"/>
             </shape>
         </item>
diff --git a/packages/SystemUI/res/drawable/qs_footer_actions_background.xml b/packages/SystemUI/res/drawable/qs_footer_actions_background.xml
index c9517cd9..a7e8762 100644
--- a/packages/SystemUI/res/drawable/qs_footer_actions_background.xml
+++ b/packages/SystemUI/res/drawable/qs_footer_actions_background.xml
@@ -15,7 +15,7 @@
 -->
 <inset xmlns:android="http://schemas.android.com/apk/res/android">
     <shape>
-        <solid android:color="?attr/underSurfaceColor"/>
+        <solid android:color="?attr/underSurface"/>
         <corners android:topLeftRadius="@dimen/qs_corner_radius"
                  android:topRightRadius="@dimen/qs_corner_radius"/>
     </shape>
diff --git a/packages/SystemUI/res/drawable/qs_security_footer_background.xml b/packages/SystemUI/res/drawable/qs_security_footer_background.xml
index 381af50..0b0055b 100644
--- a/packages/SystemUI/res/drawable/qs_security_footer_background.xml
+++ b/packages/SystemUI/res/drawable/qs_security_footer_background.xml
@@ -29,7 +29,7 @@
         <item>
             <shape android:shape="rectangle">
                 <stroke android:width="1dp"
-                        android:color="?android:attr/colorBackground"/>
+                        android:color="?attr/shadeInactive"/>
                 <corners android:radius="@dimen/qs_security_footer_corner_radius"/>
             </shape>
         </item>
diff --git a/packages/SystemUI/res/drawable/stat_sys_connected_display.xml b/packages/SystemUI/res/drawable/stat_sys_connected_display.xml
new file mode 100644
index 0000000..3f3d6f5
--- /dev/null
+++ b/packages/SystemUI/res/drawable/stat_sys_connected_display.xml
@@ -0,0 +1,25 @@
+<!--
+Copyright (C) 2023 The Android Open Source Project
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT 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:fillColor="@android:color/white"
+    android:viewportWidth="960"
+    android:viewportHeight="960">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M320,840L320,760L400,760L400,680L160,680Q127,680 103.5,656.5Q80,633 80,600L80,200Q80,167 103.5,143.5Q127,120 160,120L800,120Q833,120 856.5,143.5Q880,167 880,200L880,600Q880,633 856.5,656.5Q833,680 800,680L560,680L560,760L640,760L640,840L320,840ZM160,600L800,600Q800,600 800,600Q800,600 800,600L800,200Q800,200 800,200Q800,200 800,200L160,200Q160,200 160,200Q160,200 160,200L160,600Q160,600 160,600Q160,600 160,600ZM160,600Q160,600 160,600Q160,600 160,600L160,200Q160,200 160,200Q160,200 160,200L160,200Q160,200 160,200Q160,200 160,200L160,600Q160,600 160,600Q160,600 160,600L160,600Z" />
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/alert_dialog_title_systemui.xml b/packages/SystemUI/res/layout/alert_dialog_title_systemui.xml
index 88f13b4..ca7df86 100644
--- a/packages/SystemUI/res/layout/alert_dialog_title_systemui.xml
+++ b/packages/SystemUI/res/layout/alert_dialog_title_systemui.xml
@@ -42,7 +42,7 @@
             android:layout_marginBottom="16dp"
             android:scaleType="fitCenter"
             android:src="@null"
-            android:tint="?androidprv:attr/colorAccentPrimaryVariant"
+            android:tint="?androidprv:attr/materialColorPrimary"
             />
 
         <TextView
diff --git a/packages/SystemUI/res/layout/battery_percentage_view.xml b/packages/SystemUI/res/layout/battery_percentage_view.xml
index b9b1bb1..82facd0 100644
--- a/packages/SystemUI/res/layout/battery_percentage_view.xml
+++ b/packages/SystemUI/res/layout/battery_percentage_view.xml
@@ -20,7 +20,7 @@
         xmlns:android="http://schemas.android.com/apk/res/android"
         android:id="@+id/battery_percentage_view"
         android:layout_width="wrap_content"
-        android:layout_height="match_parent"
+        android:layout_height="wrap_content"
         android:singleLine="true"
         android:textAppearance="@style/TextAppearance.StatusBar.Clock"
         android:textColor="?android:attr/textColorPrimary"
diff --git a/packages/SystemUI/res/layout/biometric_prompt_layout.xml b/packages/SystemUI/res/layout/biometric_prompt_layout.xml
index 595cea8..ecb0bfa 100644
--- a/packages/SystemUI/res/layout/biometric_prompt_layout.xml
+++ b/packages/SystemUI/res/layout/biometric_prompt_layout.xml
@@ -39,6 +39,7 @@
         android:singleLine="true"
         android:marqueeRepeatLimit="1"
         android:ellipsize="marquee"
+        android:importantForAccessibility="no"
         style="@style/TextAppearance.AuthCredential.Subtitle"/>
 
     <TextView
diff --git a/packages/SystemUI/res/layout/combined_qs_header.xml b/packages/SystemUI/res/layout/combined_qs_header.xml
index 386c9d6..f3a6bbe 100644
--- a/packages/SystemUI/res/layout/combined_qs_header.xml
+++ b/packages/SystemUI/res/layout/combined_qs_header.xml
@@ -74,7 +74,7 @@
         android:layout_height="wrap_content"
         android:id="@+id/barrier"
         app:barrierDirection="start"
-        app:constraint_referenced_ids="statusIcons,privacy_container" />
+        app:constraint_referenced_ids="shade_header_system_icons,privacy_container" />
 
     <com.android.systemui.statusbar.policy.Clock
         android:id="@+id/clock"
@@ -108,46 +108,41 @@
     <include
         android:id="@+id/carrier_group"
         layout="@layout/shade_carrier_group"
-        app:layout_constraintHeight_min="@dimen/large_screen_shade_header_min_height"
-        android:minHeight="@dimen/large_screen_shade_header_min_height"
-        app:layout_constraintWidth_min="48dp"
         android:layout_width="0dp"
         android:layout_height="0dp"
-        app:layout_constrainedWidth="true"
         android:layout_gravity="end|center_vertical"
         android:layout_marginStart="8dp"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toStartOf="@id/shade_header_system_icons"
+        app:layout_constraintHorizontal_bias="1"
         app:layout_constraintStart_toEndOf="@id/date"
-        app:layout_constraintEnd_toStartOf="@id/statusIcons"
-        app:layout_constraintTop_toTopOf="@id/clock"
-        app:layout_constraintBottom_toBottomOf="parent"
-        app:layout_constraintHorizontal_bias="1"
-    />
+        app:layout_constraintTop_toTopOf="@id/clock" />
 
-    <com.android.systemui.statusbar.phone.StatusIconContainer
-        android:id="@+id/statusIcons"
-        app:layout_constraintHeight_min="@dimen/large_screen_shade_header_min_height"
-        android:paddingEnd="@dimen/signal_cluster_battery_padding"
+    <LinearLayout
+        android:id="@+id/shade_header_system_icons"
         android:layout_width="wrap_content"
-        android:layout_height="@dimen/large_screen_shade_header_min_height"
-        app:layout_constraintStart_toEndOf="@id/carrier_group"
-        app:layout_constraintEnd_toStartOf="@id/batteryRemainingIcon"
-        app:layout_constraintTop_toTopOf="@id/clock"
+        android:layout_height="@dimen/shade_header_system_icons_height"
+        android:clickable="true"
+        android:orientation="horizontal"
+        android:gravity="center_vertical"
+        android:paddingStart="@dimen/shade_header_system_icons_padding_start"
+        android:paddingEnd="@dimen/shade_header_system_icons_padding_end"
         app:layout_constraintBottom_toBottomOf="parent"
-        app:layout_constraintHorizontal_bias="1"
-    />
+        app:layout_constraintEnd_toEndOf="@id/privacy_container"
+        app:layout_constraintTop_toTopOf="@id/clock">
 
-    <com.android.systemui.battery.BatteryMeterView
-        android:id="@+id/batteryRemainingIcon"
-        android:layout_width="wrap_content"
-        android:layout_height="@dimen/large_screen_shade_header_min_height"
-        app:layout_constraintHeight_min="@dimen/large_screen_shade_header_min_height"
-        app:layout_constrainedWidth="true"
-        app:textAppearance="@style/TextAppearance.QS.Status"
-        app:layout_constraintStart_toEndOf="@id/statusIcons"
-        app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintTop_toTopOf="@id/clock"
-        app:layout_constraintBottom_toBottomOf="parent"
-    />
+        <com.android.systemui.statusbar.phone.StatusIconContainer
+            android:id="@+id/statusIcons"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:paddingEnd="@dimen/signal_cluster_battery_padding" />
+
+        <com.android.systemui.battery.BatteryMeterView
+            android:id="@+id/batteryRemainingIcon"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            app:textAppearance="@style/TextAppearance.QS.Status" />
+    </LinearLayout>
 
     <FrameLayout
         android:id="@+id/privacy_container"
diff --git a/packages/SystemUI/res/layout/connected_display_chip.xml b/packages/SystemUI/res/layout/connected_display_chip.xml
new file mode 100644
index 0000000..d9df91e
--- /dev/null
+++ b/packages/SystemUI/res/layout/connected_display_chip.xml
@@ -0,0 +1,48 @@
+<?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.
+-->
+
+
+<merge xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/connected_display_chip"
+    android:layout_width="wrap_content"
+    android:layout_height="match_parent"
+    android:layout_gravity="center_vertical|end"
+    tools:parentTag="com.android.systemui.statusbar.ConnectedDisplayChip">
+
+
+    <FrameLayout
+        android:id="@+id/icons_rounded_container"
+        android:layout_width="wrap_content"
+        android:layout_height="@dimen/ongoing_appops_chip_height"
+        android:layout_gravity="center"
+        android:background="@drawable/statusbar_chip_bg"
+        android:clipToOutline="true"
+        android:clipToPadding="false"
+        android:gravity="center"
+        android:maxWidth="@dimen/ongoing_appops_chip_max_width"
+        android:minWidth="@dimen/ongoing_appops_chip_min_width">
+
+        <ImageView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:layout_marginHorizontal="10dp"
+            android:scaleType="centerInside"
+            android:src="@drawable/stat_sys_connected_display"
+            android:tint="@android:color/black" />
+    </FrameLayout>
+</merge>
\ No newline at end of file
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 50dcaf3..bb32022 100644
--- a/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml
+++ b/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml
@@ -57,7 +57,6 @@
             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:layout_marginTop="@dimen/dream_overlay_status_bar_marginTop"
             android:src="@drawable/ic_alarm"
             android:tint="@android:color/white"
             android:visibility="gone"
@@ -68,7 +67,6 @@
             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:layout_marginTop="@dimen/dream_overlay_status_bar_marginTop"
             android:src="@drawable/ic_qs_dnd_on"
             android:tint="@android:color/white"
             android:visibility="gone"
@@ -79,7 +77,6 @@
             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:layout_marginTop="@dimen/dream_overlay_status_bar_marginTop"
             android:src="@drawable/ic_signal_wifi_off"
             android:visibility="gone"
             android:contentDescription="@string/dream_overlay_status_bar_wifi_off" />
diff --git a/packages/SystemUI/res/layout/heads_up_status_bar_layout.xml b/packages/SystemUI/res/layout/heads_up_status_bar_layout.xml
index bacb5c1..49744e7 100644
--- a/packages/SystemUI/res/layout/heads_up_status_bar_layout.xml
+++ b/packages/SystemUI/res/layout/heads_up_status_bar_layout.xml
@@ -27,8 +27,8 @@
          view. -->
     <Space
         android:id="@+id/icon_placeholder"
-        android:layout_width="@dimen/status_bar_icon_drawing_size"
-        android:layout_height="@dimen/status_bar_icon_drawing_size"
+        android:layout_width="@dimen/status_bar_icon_size_sp"
+        android:layout_height="@dimen/status_bar_icon_size_sp"
         android:layout_gravity="center_vertical"
     />
     <TextView
diff --git a/packages/SystemUI/res/layout/immersive_mode_cling.xml b/packages/SystemUI/res/layout/immersive_mode_cling.xml
new file mode 100644
index 0000000..bfb8184
--- /dev/null
+++ b/packages/SystemUI/res/layout/immersive_mode_cling.xml
@@ -0,0 +1,91 @@
+<?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.
+-->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:gravity="center_vertical"
+        android:paddingBottom="24dp">
+
+    <FrameLayout
+            android:id="@+id/immersive_cling_chevron"
+            android:layout_width="76dp"
+            android:layout_height="76dp"
+            android:layout_marginTop="-24dp"
+            android:layout_centerHorizontal="true">
+
+        <ImageView
+                android:id="@+id/immersive_cling_back_bg_light"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:scaleType="center"
+                android:src="@drawable/immersive_cling_light_bg_circ" />
+
+        <ImageView
+                android:id="@+id/immersive_cling_back_bg"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:scaleType="center"
+                android:src="@drawable/immersive_cling_bg_circ" />
+
+        <ImageView
+                android:id="@+id/immersive_cling_ic_expand_more"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:paddingTop="8dp"
+                android:scaleType="center"
+                android:src="@drawable/ic_expand_more_48dp"/>
+    </FrameLayout>
+
+    <TextView
+            android:id="@+id/immersive_cling_title"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_below="@id/immersive_cling_chevron"
+            android:paddingEnd="48dp"
+            android:paddingStart="48dp"
+            android:paddingTop="40dp"
+            android:text="@string/immersive_cling_title"
+            android:textColor="@android:color/white"
+            android:textSize="24sp" />
+
+    <TextView
+            android:id="@+id/immersive_cling_description"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_below="@id/immersive_cling_title"
+            android:paddingEnd="48dp"
+            android:paddingStart="48dp"
+            android:paddingTop="12.6dp"
+            android:text="@string/immersive_cling_description"
+            android:textColor="@android:color/white"
+            android:textSize="16sp" />
+
+    <Button
+            android:id="@+id/ok"
+            style="@android:style/Widget.Material.Button.Borderless"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_alignParentEnd="true"
+            android:layout_below="@+id/immersive_cling_description"
+            android:layout_marginEnd="40dp"
+            android:layout_marginTop="18dp"
+            android:paddingEnd="8dp"
+            android:paddingStart="8dp"
+            android:text="@string/immersive_cling_positive"
+            android:textColor="@android:color/white"
+            android:textSize="14sp" />
+
+</RelativeLayout>
diff --git a/packages/SystemUI/res/layout/keyboard_shortcut_app_item.xml b/packages/SystemUI/res/layout/keyboard_shortcut_app_item.xml
index 3786812..fcf9638 100644
--- a/packages/SystemUI/res/layout/keyboard_shortcut_app_item.xml
+++ b/packages/SystemUI/res/layout/keyboard_shortcut_app_item.xml
@@ -17,6 +17,8 @@
 <com.android.systemui.statusbar.KeyboardShortcutAppItemLayout
         xmlns:android="http://schemas.android.com/apk/res/android"
         android:orientation="horizontal"
+        android:background="@drawable/list_item_background"
+        android:focusable="true"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:minHeight="48dp"
@@ -55,6 +57,5 @@
             android:layout_alignParentEnd="true"
             android:textSize="14sp"
             android:scrollHorizontally="false"
-            android:layout_centerVertical="true"
-            android:focusable="true"/>
+            android:layout_centerVertical="true"/>
 </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 8414223..0759990 100644
--- a/packages/SystemUI/res/layout/keyboard_shortcuts_category_title.xml
+++ b/packages/SystemUI/res/layout/keyboard_shortcuts_category_title.xml
@@ -16,10 +16,11 @@
   ~ limitations under the License
   -->
 <TextView xmlns:android="http://schemas.android.com/apk/res/android"
-          android:layout_width="wrap_content"
+          android:layout_width="match_parent"
           android:layout_height="match_parent"
           android:textSize="14sp"
           android:fontFamily="sans-serif-medium"
+          android:importantForAccessibility="yes"
           android:paddingStart="24dp"
           android:paddingTop="20dp"
           android:paddingEnd="24dp"
diff --git a/packages/SystemUI/res/layout/keyguard_bottom_area.xml b/packages/SystemUI/res/layout/keyguard_bottom_area.xml
index a3a7135..66c57fc 100644
--- a/packages/SystemUI/res/layout/keyguard_bottom_area.xml
+++ b/packages/SystemUI/res/layout/keyguard_bottom_area.xml
@@ -23,7 +23,7 @@
     android:outlineProvider="none" >
 
     <LinearLayout
-        android:id="@+id/keyguard_indication_area"
+        android:id="@id/keyguard_indication_area"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:layout_marginBottom="@dimen/keyguard_indication_margin_bottom"
@@ -31,7 +31,7 @@
         android:orientation="vertical">
 
         <com.android.systemui.statusbar.phone.KeyguardIndicationTextView
-            android:id="@+id/keyguard_indication_text"
+            android:id="@id/keyguard_indication_text"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:gravity="center"
@@ -41,13 +41,12 @@
             android:accessibilityLiveRegion="polite"/>
 
         <com.android.systemui.statusbar.phone.KeyguardIndicationTextView
-            android:id="@+id/keyguard_indication_text_bottom"
+            android:id="@id/keyguard_indication_text_bottom"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:gravity="center"
-            android:minHeight="48dp"
+            android:minHeight="@dimen/keyguard_indication_text_min_height"
             android:layout_gravity="center_horizontal"
-            android:layout_centerHorizontal="true"
             android:paddingStart="@dimen/keyguard_indication_text_padding"
             android:paddingEnd="@dimen/keyguard_indication_text_padding"
             android:textAppearance="@style/TextAppearance.Keyguard.BottomArea"
diff --git a/packages/SystemUI/res/layout/keyguard_qs_user_switch.xml b/packages/SystemUI/res/layout/keyguard_qs_user_switch.xml
index 07c428b..6194311 100644
--- a/packages/SystemUI/res/layout/keyguard_qs_user_switch.xml
+++ b/packages/SystemUI/res/layout/keyguard_qs_user_switch.xml
@@ -24,7 +24,7 @@
     android:layout_gravity="end">
     <!-- We add a background behind the UserAvatarView with the same color and with a circular shape
          so that this view can be expanded into a Dialog or an Activity. -->
-    <com.android.systemui.animation.LaunchableFrameLayout
+    <com.android.systemui.animation.view.LaunchableFrameLayout
         android:id="@+id/kg_multi_user_avatar_with_background"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
@@ -42,5 +42,5 @@
             systemui:framePadding="0dp"
             systemui:frameWidth="0dp">
         </com.android.systemui.statusbar.phone.UserAvatarView>
-    </com.android.systemui.animation.LaunchableFrameLayout>
+    </com.android.systemui.animation.view.LaunchableFrameLayout>
 </FrameLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/keyguard_status_bar.xml b/packages/SystemUI/res/layout/keyguard_status_bar.xml
index 64c4eff..fc0bf24 100644
--- a/packages/SystemUI/res/layout/keyguard_status_bar.xml
+++ b/packages/SystemUI/res/layout/keyguard_status_bar.xml
@@ -47,7 +47,10 @@
             android:layout_height="match_parent"
             android:layout_marginEnd="@dimen/status_bar_padding_end"
             android:gravity="center_vertical|end">
-            <include layout="@layout/system_icons" />
+            <include layout="@layout/system_icons"
+                android:layout_gravity="center_vertical"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content" />
         </FrameLayout>
 
         <ImageView android:id="@+id/multi_user_avatar"
diff --git a/packages/SystemUI/res/layout/media_smartspace_recommendations.xml b/packages/SystemUI/res/layout/media_smartspace_recommendations.xml
deleted file mode 100644
index 9304ff7..0000000
--- a/packages/SystemUI/res/layout/media_smartspace_recommendations.xml
+++ /dev/null
@@ -1,136 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2019 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License
-  -->
-
-<!-- Layout for media recommendations inside QSPanel carousel -->
-<!-- See media_recommendation_expanded.xml and media_recommendation_collapsed.xml for the
-     constraints. -->
-<com.android.systemui.util.animation.TransitionLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
-    android:id="@+id/media_recommendations"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:clipChildren="false"
-    android:clipToPadding="false"
-    android:forceHasOverlappingRendering="false"
-    android:background="@drawable/qs_media_background"
-    android:theme="@style/MediaPlayer">
-
-    <!-- This view just ensures the full media player is a certain height. -->
-    <View
-        android:id="@+id/sizing_view"
-        android:layout_width="match_parent"
-        android:layout_height="@dimen/qs_media_session_height_expanded" />
-
-    <com.android.internal.widget.CachingIconView
-        android:id="@+id/recommendation_card_icon"
-        android:layout_width="@dimen/qs_media_app_icon_size"
-        android:layout_height="@dimen/qs_media_app_icon_size"
-        android:minWidth="@dimen/qs_media_app_icon_size"
-        android:minHeight="@dimen/qs_media_app_icon_size"
-        android:layout_marginStart="@dimen/qs_media_padding"
-        android:layout_marginTop="@dimen/qs_media_rec_icon_top_margin"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toTopOf="parent" />
-
-    <FrameLayout
-        android:id="@+id/media_cover1_container"
-        style="@style/MediaPlayer.Recommendation.AlbumContainer"
-        >
-        <ImageView
-            android:id="@+id/media_cover1"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:minWidth="@dimen/qs_media_rec_album_size"
-            android:minHeight="@dimen/qs_media_rec_album_size"
-            app:layout_constraintTop_toTopOf="parent"
-            app:layout_constraintBottom_toBottomOf="parent"
-            android:adjustViewBounds="true"
-            android:background="@drawable/bg_smartspace_media_item"
-            style="@style/MediaPlayer.Recommendation.Album"
-            android:clipToOutline="true"
-            android:scaleType="centerCrop"/>
-    </FrameLayout>
-
-    <TextView
-        android:id="@+id/media_title1"
-        style="@style/MediaPlayer.Recommendation.Text.Title"
-        />
-
-    <TextView
-        android:id="@+id/media_subtitle1"
-        style="@style/MediaPlayer.Recommendation.Text.Subtitle"
-        />
-
-    <FrameLayout
-        android:id="@+id/media_cover2_container"
-        style="@style/MediaPlayer.Recommendation.AlbumContainer"
-        >
-        <ImageView
-            android:id="@+id/media_cover2"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:minWidth="@dimen/qs_media_rec_album_size"
-            android:minHeight="@dimen/qs_media_rec_album_size"
-            android:adjustViewBounds="true"
-            android:background="@drawable/bg_smartspace_media_item"
-            style="@style/MediaPlayer.Recommendation.Album"
-            android:clipToOutline="true"
-            android:scaleType="centerCrop"/>
-    </FrameLayout>
-
-    <TextView
-        android:id="@+id/media_title2"
-        style="@style/MediaPlayer.Recommendation.Text.Title"
-        />
-
-    <TextView
-        android:id="@+id/media_subtitle2"
-        style="@style/MediaPlayer.Recommendation.Text.Subtitle"
-        />
-
-    <FrameLayout
-        android:id="@+id/media_cover3_container"
-        style="@style/MediaPlayer.Recommendation.AlbumContainer"
-        >
-        <ImageView
-            android:id="@+id/media_cover3"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:minWidth="@dimen/qs_media_rec_album_size"
-            android:minHeight="@dimen/qs_media_rec_album_size"
-            android:adjustViewBounds="true"
-            android:background="@drawable/bg_smartspace_media_item"
-            style="@style/MediaPlayer.Recommendation.Album"
-            android:clipToOutline="true"
-            android:scaleType="centerCrop"/>
-    </FrameLayout>
-
-    <TextView
-        android:id="@+id/media_title3"
-        style="@style/MediaPlayer.Recommendation.Text.Title"
-        />
-
-    <TextView
-        android:id="@+id/media_subtitle3"
-        style="@style/MediaPlayer.Recommendation.Text.Subtitle"
-        />
-
-    <include
-        layout="@layout/media_long_press_menu" />
-
-</com.android.systemui.util.animation.TransitionLayout>
diff --git a/packages/SystemUI/res/layout/qs_footer_impl.xml b/packages/SystemUI/res/layout/qs_footer_impl.xml
index 745cfc6..b8f4c0f 100644
--- a/packages/SystemUI/res/layout/qs_footer_impl.xml
+++ b/packages/SystemUI/res/layout/qs_footer_impl.xml
@@ -53,6 +53,7 @@
                 android:layout_width="wrap_content"
                 android:layout_height="match_parent"
                 android:layout_gravity="center_vertical"
+                android:tint="?attr/shadeActive"
                 android:visibility="gone" />
 
             <FrameLayout
@@ -70,7 +71,7 @@
                     android:focusable="true"
                     android:padding="@dimen/qs_footer_icon_padding"
                     android:src="@*android:drawable/ic_mode_edit"
-                    android:tint="?android:attr/textColorPrimary" />
+                    android:tint="?attr/onSurfaceVariant" />
             </FrameLayout>
 
         </LinearLayout>
diff --git a/packages/SystemUI/res/layout/qs_tile_label.xml b/packages/SystemUI/res/layout/qs_tile_label.xml
index c124aea..974cad3 100644
--- a/packages/SystemUI/res/layout/qs_tile_label.xml
+++ b/packages/SystemUI/res/layout/qs_tile_label.xml
@@ -54,6 +54,6 @@
         android:focusable="false"
         android:importantForAccessibility="no"
         android:textAppearance="@style/TextAppearance.QS.TileLabel.Secondary"
-        android:textColor="?android:attr/textColorSecondary"/>
+        android:textColor="?attr/onShadeInactive"/>
 
 </com.android.systemui.qs.tileimpl.IgnorableChildLinearLayout>
diff --git a/packages/SystemUI/res/layout/quick_settings_brightness_dialog.xml b/packages/SystemUI/res/layout/quick_settings_brightness_dialog.xml
index e95c6a7..91550b3 100644
--- a/packages/SystemUI/res/layout/quick_settings_brightness_dialog.xml
+++ b/packages/SystemUI/res/layout/quick_settings_brightness_dialog.xml
@@ -34,5 +34,6 @@
             android:paddingEnd="0dp"
             android:progressDrawable="@drawable/brightness_progress_drawable"
             android:splitTrack="false"
+            android:clickable="true"
         />
     </com.android.systemui.settings.brightness.BrightnessSliderView>
diff --git a/packages/SystemUI/res/layout/scene_window_root.xml b/packages/SystemUI/res/layout/scene_window_root.xml
new file mode 100644
index 0000000..0dcd15b
--- /dev/null
+++ b/packages/SystemUI/res/layout/scene_window_root.xml
@@ -0,0 +1,32 @@
+<?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.
+*/
+-->
+
+<!-- The root view of the scene window. -->
+<com.android.systemui.scene.ui.view.SceneWindowRootView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:sysui="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/scene_window_root"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:fitsSystemWindows="true">
+
+    <include layout="@layout/super_notification_shade"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"/>
+</com.android.systemui.scene.ui.view.SceneWindowRootView>
diff --git a/packages/SystemUI/res/layout/screen_record_dialog.xml b/packages/SystemUI/res/layout/screen_record_dialog.xml
index bbf3adf..f6ce70d 100644
--- a/packages/SystemUI/res/layout/screen_record_dialog.xml
+++ b/packages/SystemUI/res/layout/screen_record_dialog.xml
@@ -15,6 +15,7 @@
   limitations under the License.
   -->
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:orientation="vertical">
@@ -47,7 +48,7 @@
                 <TextView
                     android:layout_width="wrap_content"
                     android:layout_height="wrap_content"
-                    android:textAppearance="?android:attr/textAppearanceLarge"
+                    android:textAppearance="@style/TextAppearance.Dialog.Title"
                     android:fontFamily="@*android:string/config_headlineFontFamily"
                     android:text="@string/screenrecord_permission_dialog_title"
                     android:layout_marginTop="22dp"
@@ -56,8 +57,7 @@
                     android:layout_width="wrap_content"
                     android:layout_height="wrap_content"
                     android:text="@string/screenrecord_permission_dialog_warning_entire_screen"
-                    android:textAppearance="?android:attr/textAppearanceSmall"
-                    android:textColor="?android:textColorSecondary"
+                    android:textAppearance="@style/TextAppearance.Dialog.Body.Message"
                     android:gravity="center"
                     android:layout_marginBottom="20dp"/>
 
@@ -81,6 +81,7 @@
                         android:minHeight="48dp"
                         android:layout_weight="1"
                         android:popupBackground="@drawable/screenrecord_spinner_background"
+                        android:textColor="?androidprv:attr/materialColorOnSurface"
                         android:dropDownWidth="274dp"
                         android:prompt="@string/screenrecord_audio_label"/>
                     <Switch
@@ -117,7 +118,7 @@
                         android:text="@string/screenrecord_taps_label"
                         android:textAppearance="?android:attr/textAppearanceMedium"
                         android:fontFamily="@*android:string/config_headlineFontFamily"
-                        android:textColor="?android:attr/textColorPrimary"
+                        android:textColor="?androidprv:attr/materialColorOnSurface"
                         android:importantForAccessibility="no"/>
                     <Switch
                         android:layout_width="wrap_content"
diff --git a/packages/SystemUI/res/layout/screen_record_options.xml b/packages/SystemUI/res/layout/screen_record_options.xml
index 6cc72dd..d9f4b79 100644
--- a/packages/SystemUI/res/layout/screen_record_options.xml
+++ b/packages/SystemUI/res/layout/screen_record_options.xml
@@ -55,14 +55,13 @@
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:orientation="horizontal"
+        android:gravity="center_vertical"
         android:layout_marginTop="@dimen/screenrecord_option_padding">
         <ImageView
             android:layout_width="@dimen/screenrecord_option_icon_size"
             android:layout_height="@dimen/screenrecord_option_icon_size"
-            android:layout_weight="0"
             android:src="@drawable/ic_touch"
             android:tint="?android:attr/textColorSecondary"
-            android:layout_gravity="center_vertical"
             android:layout_marginRight="@dimen/screenrecord_option_padding"
             android:importantForAccessibility="no"/>
         <TextView
@@ -70,7 +69,6 @@
             android:layout_height="wrap_content"
             android:minHeight="48dp"
             android:layout_weight="1"
-            android:gravity="center_vertical"
             android:text="@string/screenrecord_taps_label"
             android:textAppearance="?android:attr/textAppearanceMedium"
             android:fontFamily="@*android:string/config_bodyFontFamily"
@@ -80,7 +78,6 @@
             android:layout_width="wrap_content"
             android:minWidth="48dp"
             android:layout_height="48dp"
-            android:layout_weight="0"
             android:id="@+id/screenrecord_taps_switch"
             style="@style/ScreenRecord.Switch"
             android:importantForAccessibility="yes"/>
diff --git a/packages/SystemUI/res/layout/screen_share_dialog.xml b/packages/SystemUI/res/layout/screen_share_dialog.xml
index ab522a3..9af46c5 100644
--- a/packages/SystemUI/res/layout/screen_share_dialog.xml
+++ b/packages/SystemUI/res/layout/screen_share_dialog.xml
@@ -36,14 +36,13 @@
             android:layout_width="@dimen/screenrecord_logo_size"
             android:layout_height="@dimen/screenrecord_logo_size"
             android:src="@drawable/ic_media_projection_permission"
-            android:tint="?androidprv:attr/colorAccentPrimaryVariant"
+            android:tint="?androidprv:attr/materialColorPrimary"
             android:importantForAccessibility="no"/>
         <TextView
             android:id="@+id/screen_share_dialog_title"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:textAppearance="?android:attr/textAppearanceLarge"
-            android:fontFamily="@*android:string/config_headlineFontFamily"
+            android:textAppearance="@style/TextAppearance.Dialog.Title"
             android:layout_marginTop="@dimen/screenrecord_title_margin_top"
             android:gravity="center"/>
         <Spinner
@@ -64,8 +63,7 @@
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:text="@string/screenrecord_permission_dialog_warning_entire_screen"
-            android:textAppearance="?android:attr/textAppearanceSmall"
-            android:textColor="?android:textColorSecondary"
+            style="@style/TextAppearance.Dialog.Body.Message"
             android:gravity="start"
             android:lineHeight="@dimen/screenrecord_warning_line_height"/>
 
diff --git a/packages/SystemUI/res/layout/status_bar.xml b/packages/SystemUI/res/layout/status_bar.xml
index 331307a0..909f19f 100644
--- a/packages/SystemUI/res/layout/status_bar.xml
+++ b/packages/SystemUI/res/layout/status_bar.xml
@@ -32,7 +32,7 @@
 
     <ImageView
         android:id="@+id/notification_lights_out"
-        android:layout_width="@dimen/status_bar_icon_size"
+        android:layout_width="@dimen/status_bar_icon_size_sp"
         android:layout_height="match_parent"
         android:paddingStart="@dimen/status_bar_padding_start"
         android:paddingBottom="2dip"
@@ -80,7 +80,7 @@
                     android:layout_width="match_parent"
                     android:clipChildren="false">
                     <ViewStub
-                        android:id="@+id/operator_name"
+                        android:id="@+id/operator_name_stub"
                         android:layout_width="wrap_content"
                         android:layout_height="match_parent"
                         android:layout="@layout/operator_name" />
diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml
index d710676..e214666 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded.xml
@@ -132,21 +132,6 @@
         android:id="@+id/lock_icon_view"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content">
-        <!-- Background protection -->
-        <ImageView
-            android:id="@+id/lock_icon_bg"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:src="@drawable/fingerprint_bg"
-            android:visibility="invisible"/>
-
-        <ImageView
-            android:id="@+id/lock_icon"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:layout_gravity="center"
-            android:scaleType="centerCrop"/>
-
     </com.android.keyguard.LockIconView>
 
     <include
diff --git a/packages/SystemUI/res/layout/status_bar_notification_footer.xml b/packages/SystemUI/res/layout/status_bar_notification_footer.xml
index db94c92..909048e 100644
--- a/packages/SystemUI/res/layout/status_bar_notification_footer.xml
+++ b/packages/SystemUI/res/layout/status_bar_notification_footer.xml
@@ -17,10 +17,9 @@
 <!-- Extends Framelayout -->
 <com.android.systemui.statusbar.notification.row.FooterView
         xmlns:android="http://schemas.android.com/apk/res/android"
+        xmlns:app="http://schemas.android.com/apk/res-auto"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:paddingStart="16dp"
-        android:paddingEnd="16dp"
         android:visibility="gone">
     <com.android.systemui.statusbar.AlphaOptimizedFrameLayout
         android:id="@+id/content"
@@ -37,31 +36,45 @@
             android:visibility="gone"
             android:textAppearance="?android:attr/textAppearanceButton"
             android:text="@string/unlock_to_see_notif_text"/>
-        <com.android.systemui.statusbar.notification.row.FooterViewButton
-            style="@style/TextAppearance.NotificationSectionHeaderButton"
-            android:id="@+id/manage_text"
-            android:layout_width="wrap_content"
-            android:layout_height="48dp"
-            android:layout_marginTop="12dp"
-            android:layout_gravity="start"
-            android:background="@drawable/notif_footer_btn_background"
-            android:focusable="true"
-            android:textColor="@color/notif_pill_text"
-            android:contentDescription="@string/manage_notifications_history_text"
-            android:text="@string/manage_notifications_history_text"
-        />
-        <com.android.systemui.statusbar.notification.row.FooterViewButton
-            style="@style/TextAppearance.NotificationSectionHeaderButton"
-            android:id="@+id/dismiss_text"
-            android:layout_width="wrap_content"
-            android:layout_height="48dp"
-            android:layout_marginTop="12dp"
-            android:layout_gravity="end"
-            android:background="@drawable/notif_footer_btn_background"
-            android:focusable="true"
-            android:textColor="@color/notif_pill_text"
-            android:contentDescription="@string/accessibility_clear_all"
-            android:text="@string/clear_all_notifications_text"
-        />
+        <androidx.constraintlayout.widget.ConstraintLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            >
+            <com.android.systemui.statusbar.notification.row.FooterViewButton
+                style="@style/TextAppearance.NotificationSectionHeaderButton"
+                android:id="@+id/manage_text"
+                android:layout_width="wrap_content"
+                android:layout_height="48dp"
+                android:layout_marginTop="12dp"
+                android:layout_marginStart="16dp"
+                app:layout_constraintVertical_bias="0.0"
+                app:layout_constraintHorizontal_chainStyle="spread_inside"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toTopOf="parent"
+                app:layout_constraintEnd_toStartOf="@id/dismiss_text"
+                android:background="@drawable/notif_footer_btn_background"
+                android:focusable="true"
+                android:textColor="@color/notif_pill_text"
+                android:contentDescription="@string/manage_notifications_history_text"
+                android:text="@string/manage_notifications_history_text"
+                />
+            <com.android.systemui.statusbar.notification.row.FooterViewButton
+                style="@style/TextAppearance.NotificationSectionHeaderButton"
+                android:id="@+id/dismiss_text"
+                android:layout_width="wrap_content"
+                android:layout_height="48dp"
+                android:layout_marginTop="12dp"
+                android:layout_marginEnd="16dp"
+                app:layout_constraintVertical_bias="1.0"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintTop_toTopOf="parent"
+                app:layout_constraintStart_toEndOf="@id/manage_text"
+                android:background="@drawable/notif_footer_btn_background"
+                android:focusable="true"
+                android:textColor="@color/notif_pill_text"
+                android:contentDescription="@string/accessibility_clear_all"
+                android:text="@string/clear_all_notifications_text"
+                />
+        </androidx.constraintlayout.widget.ConstraintLayout>
     </com.android.systemui.statusbar.AlphaOptimizedFrameLayout>
 </com.android.systemui.statusbar.notification.row.FooterView>
diff --git a/packages/SystemUI/res/layout/status_bar_wifi_group.xml b/packages/SystemUI/res/layout/status_bar_wifi_group.xml
deleted file mode 100644
index 6cb6993..0000000
--- a/packages/SystemUI/res/layout/status_bar_wifi_group.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-**
-** Copyright 2018, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-<com.android.systemui.statusbar.StatusBarWifiView
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/wifi_combo"
-    android:layout_width="wrap_content"
-    android:layout_height="match_parent"
-    android:gravity="center_vertical" >
-
-    <include layout="@layout/status_bar_wifi_group_inner" />
-
-</com.android.systemui.statusbar.StatusBarWifiView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/status_bar_wifi_group_inner.xml b/packages/SystemUI/res/layout/status_bar_wifi_group_inner.xml
index 0ea0653..4c5cd7d 100644
--- a/packages/SystemUI/res/layout/status_bar_wifi_group_inner.xml
+++ b/packages/SystemUI/res/layout/status_bar_wifi_group_inner.xml
@@ -24,16 +24,17 @@
         android:layout_width="wrap_content"
         android:layout_height="match_parent"
         android:gravity="center_vertical"
-        android:layout_marginStart="2.5dp"
+        android:layout_marginStart="2.5sp"
     >
         <FrameLayout
                 android:id="@+id/inout_container"
-                android:layout_height="17dp"
+                android:layout_height="@dimen/status_bar_wifi_inout_container_size"
                 android:layout_width="wrap_content"
                 android:gravity="center_vertical" >
             <ImageView
                 android:id="@+id/wifi_in"
-                android:layout_height="wrap_content"
+                android:layout_height="@dimen/status_bar_wifi_signal_size"
+                android:adjustViewBounds="true"
                 android:layout_width="wrap_content"
                 android:src="@drawable/ic_activity_down"
                 android:visibility="gone"
@@ -41,7 +42,8 @@
             />
             <ImageView
                 android:id="@+id/wifi_out"
-                android:layout_height="wrap_content"
+                android:layout_height="@dimen/status_bar_wifi_signal_size"
+                android:adjustViewBounds="true"
                 android:layout_width="wrap_content"
                 android:src="@drawable/ic_activity_up"
                 android:paddingEnd="2dp"
@@ -75,7 +77,7 @@
         <View
             android:id="@+id/wifi_airplane_spacer"
             android:layout_width="@dimen/status_bar_airplane_spacer_width"
-            android:layout_height="4dp"
+            android:layout_height="wrap_content"
             android:visibility="gone"
         />
     </com.android.keyguard.AlphaOptimizedLinearLayout>
diff --git a/packages/SystemUI/res/layout/super_notification_shade.xml b/packages/SystemUI/res/layout/super_notification_shade.xml
index d9fe949..a336252 100644
--- a/packages/SystemUI/res/layout/super_notification_shade.xml
+++ b/packages/SystemUI/res/layout/super_notification_shade.xml
@@ -21,6 +21,7 @@
 <com.android.systemui.shade.NotificationShadeWindowView
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:sysui="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/legacy_window_root"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:fitsSystemWindows="true">
@@ -64,11 +65,24 @@
         android:layout_width="match_parent"
         android:layout_height="match_parent" />
 
+    <!-- Root for all keyguard content. It was previously located within the shade. -->
+    <com.android.systemui.keyguard.ui.view.KeyguardRootView
+        android:id="@id/keyguard_root_view"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+
     <include layout="@layout/status_bar_expanded"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:visibility="invisible" />
 
+    <!-- Shared container for the notification stack. Can be positioned by either
+         the keyguard_root_view or notification_panel -->
+    <com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
+        android:id="@+id/shared_notification_container"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+
     <include layout="@layout/brightness_mirror_container" />
 
     <com.android.systemui.scrim.ScrimView
diff --git a/packages/SystemUI/res/layout/system_icons.xml b/packages/SystemUI/res/layout/system_icons.xml
index 816dfd3..6a14c21 100644
--- a/packages/SystemUI/res/layout/system_icons.xml
+++ b/packages/SystemUI/res/layout/system_icons.xml
@@ -18,20 +18,23 @@
               xmlns:systemui="http://schemas.android.com/apk/res-auto"
     android:id="@+id/system_icons"
     android:layout_width="wrap_content"
-    android:layout_height="match_parent"
-    android:layout_gravity="center_vertical|end"
+    android:layout_height="wrap_content"
+    android:paddingBottom="@dimen/status_bar_icons_padding_bottom"
+    android:paddingTop="@dimen/status_bar_icons_padding_top"
+    android:paddingStart="@dimen/status_bar_icons_padding_start"
+    android:paddingEnd="@dimen/status_bar_icons_padding_end"
     android:gravity="center_vertical">
 
     <com.android.systemui.statusbar.phone.StatusIconContainer android:id="@+id/statusIcons"
         android:layout_width="0dp"
         android:layout_weight="1"
-        android:layout_height="match_parent"
+        android:layout_height="wrap_content"
         android:paddingEnd="@dimen/signal_cluster_battery_padding"
         android:gravity="center_vertical"
         android:orientation="horizontal"/>
 
     <com.android.systemui.battery.BatteryMeterView android:id="@+id/battery"
-        android:layout_height="match_parent"
+        android:layout_height="wrap_content"
         android:layout_width="wrap_content"
         android:clipToPadding="false"
         android:clipChildren="false"
diff --git a/packages/SystemUI/res/layout/udfps_keyguard_view_legacy.xml b/packages/SystemUI/res/layout/udfps_keyguard_view_legacy.xml
index 00af7f4..530d752 100644
--- a/packages/SystemUI/res/layout/udfps_keyguard_view_legacy.xml
+++ b/packages/SystemUI/res/layout/udfps_keyguard_view_legacy.xml
@@ -16,8 +16,7 @@
   -->
 <com.android.systemui.biometrics.UdfpsKeyguardViewLegacy
     xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
-    android:id="@+id/udfps_animation_view"
+    android:id="@+id/udfps_animation_view_legacy"
     android:layout_width="match_parent"
     android:layout_height="match_parent">
 
diff --git a/packages/SystemUI/res/layout/window_magnification_settings_view.xml b/packages/SystemUI/res/layout/window_magnification_settings_view.xml
index a8febe7..efdb0a3 100644
--- a/packages/SystemUI/res/layout/window_magnification_settings_view.xml
+++ b/packages/SystemUI/res/layout/window_magnification_settings_view.xml
@@ -147,10 +147,12 @@
         android:id="@+id/magnifier_zoom_slider"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        app:max="6"
         app:progress="0"
         app:iconStartContentDescription="@string/accessibility_control_zoom_out"
-        app:iconEndContentDescription="@string/accessibility_control_zoom_in"/>
+        app:iconEndContentDescription="@string/accessibility_control_zoom_in"
+        app:tickMark="@android:color/transparent"
+        app:seekBarChangeMagnitude="10"
+    />
 
     <Button
         android:id="@+id/magnifier_done_button"
diff --git a/packages/SystemUI/res/layout/zen_mode_condition.xml b/packages/SystemUI/res/layout/zen_mode_condition.xml
index ab52465..3baae33 100644
--- a/packages/SystemUI/res/layout/zen_mode_condition.xml
+++ b/packages/SystemUI/res/layout/zen_mode_condition.xml
@@ -15,6 +15,7 @@
      limitations under the License.
 -->
 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:theme="@style/Theme.SystemUI.QuickSettings"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:clipChildren="false"
diff --git a/packages/SystemUI/res/values-af/strings.xml b/packages/SystemUI/res/values-af/strings.xml
index 08326ce..e0c25e3 100644
--- a/packages/SystemUI/res/values-af/strings.xml
+++ b/packages/SystemUI/res/values-af/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Tik om te bekyk"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Kon nie skermopname stoor nie"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Kon nie skermopname begin nie"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Bekyk tans volskerm"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Swiep van bo af as jy wil uitgaan."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"Het dit"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Terug"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Tuis"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Kieslys"</string>
@@ -243,7 +246,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Oudio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Kopstuk"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Invoer"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Gehoortoestelle"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Gehoortoestelle"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Skakel tans aan …"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Outo-draai"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Outodraai skerm"</string>
@@ -412,6 +415,14 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Wanneer jy deel, opneem of uitsaai, het Android toegang tot enigiets wat op jou skerm sigbaar is of op jou toestel gespeel word. Wees dus versigtig met dinge soos wagwoorde, betalingbesonderhede, boodskappe, foto’s, en oudio en video."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Wanneer jy ’n app deel, opneem of uitsaai, het Android toegang tot enigiets wat in daardie app gewys of gespeel word. Wees dus versigtig met dinge soos wagwoorde, betalingbesonderhede, boodskappe, foto’s, en oudio en video."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Begin"</string>
+    <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+    <skip />
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Deur jou IT-admin geblokkeer"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Skermskote is deur toestelbeleid gedeaktiveer"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Vee alles uit"</string>
@@ -853,6 +864,7 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Vergroot die hele skerm"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Vergroot \'n deel van die skerm"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Maak vergrotinginstellings oop"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Maak vergrotinginstellings toe"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Sleep hoek om grootte te verander"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Laat diagonale rollees toe"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Verander grootte"</string>
@@ -1039,6 +1051,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Kon nie jou batterymeter lees nie"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Tik vir meer inligting"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Geen wekker nie"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"voer skermslot in"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Vingerafdruksensor"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"staaf"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"gaan by toestel in"</string>
@@ -1101,7 +1114,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Assistent luister"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# kennisgewing}other{# kennisgewings}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Neem notas"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Maak notas"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Maak notas, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Uitsaai"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Hou op om <xliff:g id="APP_NAME">%1$s</xliff:g> uit te saai?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"As jy <xliff:g id="SWITCHAPP">%1$s</xliff:g> uitsaai of die uitvoer verander, sal jou huidige uitsending stop"</string>
diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml
index 9e02309..b8ac5fc 100644
--- a/packages/SystemUI/res/values-am/strings.xml
+++ b/packages/SystemUI/res/values-am/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"ለመመልከት መታ ያድርጉ"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"የማያ ገጽ ቀረጻን ማስቀመጥ ላይ ስህተት"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"የማያ ገፅ ቀረጻን መጀመር ላይ ስህተት"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"ሙሉ ገፅ በማሳየት ላይ"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"ለመውጣት፣ ከላይ ወደታች ጠረግ ያድርጉ።"</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"ገባኝ"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"ተመለስ"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"መነሻ"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"ምናሌ"</string>
@@ -243,7 +246,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"ኦዲዮ"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"ማዳመጫ"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"ግቤት"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"አጋዥ መስሚያዎች"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"መስሚያ አጋዥ መሣሪያዎች"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"በማብራት ላይ..."</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"በራስ ሰር አሽከርክር"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"ማያ ገጽን በራስ-አሽከርክር"</string>
@@ -412,6 +415,14 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"እርስዎ ሲያጋሩ፣ ሲቀርጹ ወይም cast ሲያደርጉ Android በማያ ገጽዎ ላይ ለሚታይ ወይም በመሣሪያዎ ላይ ለሚጫወት ማንኛውም ነገር መዳረሻ አለው። ስለዚህ እንደ የይለፍ ቃላት፣ የክፍያ ዝርዝሮች፣ መልዕክቶች፣ ፎቶዎች እና ኦዲዮ እና ቪድዮ ላሉ ነገሮች ጥንቃቄ ያድርጉ።"</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"እርስዎ ሲያጋሩ፣ ሲቀርጹ ወይም cast ሲያደርጉ Android በማያ ገጽዎ ላይ ለሚታይ ወይም በመሣሪያዎ ላይ ለሚጫወት ማንኛውም ነገር መዳረሻ አለው። ስለዚህ እንደ የይለፍ ቃላት፣ የክፍያ ዝርዝሮች፣ መልዕክቶች፣ ፎቶዎች እና ኦዲዮ እና ቪድዮ ላሉ ነገሮች ጥንቃቄ ያድርጉ።"</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"ጀምር"</string>
+    <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+    <skip />
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"በእርስዎ የአይቲ አስተዳዳሪ ታግዷል"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"የማያ ገፅ ቀረጻ በመሣሪያ መመሪያ ተሰናክሏል"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"ሁሉንም አጽዳ"</string>
@@ -853,6 +864,7 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"ሙሉ ገፅ እይታን ያጉሉ"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"የማያ ገጹን ክፍል አጉላ"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"የማጉያ ቅንብሮችን ክፈት"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"የማጉላት ቅንብሮችን ዝጋ"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"መጠን ለመቀየር ጠርዙን ይዘው ይጎትቱ"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"ሰያፍ ሽብለላን ፍቀድ"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"መጠን ቀይር"</string>
@@ -1039,6 +1051,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"የባትሪ መለኪያዎን የማንበብ ችግር"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"ለበለጠ መረጃ መታ ያድርጉ"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"ምንም ማንቂያ አልተቀናበረም"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"ማያ ገጽ መቆለፊያ ያስገቡ"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"የጣት አሻራ ዳሳሽ"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"ያረጋግጡ"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"መሣሪያን ያስገቡ"</string>
@@ -1101,7 +1114,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"ረዳት በማዳመጥ ላይ ነው"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# ማሳወቂያ}one{# ማሳወቂያዎች}other{# ማሳወቂያዎች}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>፣ <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Notetaking"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"የማስታወሻ አያያዝ"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"የማስታወሻ አያያዝ፣ <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"በማሰራጨት ላይ"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"<xliff:g id="APP_NAME">%1$s</xliff:g>ን ማሰራጨት ይቁም?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"<xliff:g id="SWITCHAPP">%1$s</xliff:g>ን ካሰራጩ ወይም ውፅዓትን ከቀየሩ የአሁኑ ስርጭትዎ ይቆማል"</string>
diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml
index 9d3c85b..e465773 100644
--- a/packages/SystemUI/res/values-ar/strings.xml
+++ b/packages/SystemUI/res/values-ar/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"انقر لعرض التسجيل."</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"حدث خطأ أثناء حفظ تسجيل محتوى الشاشة."</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"حدث خطأ في بدء تسجيل الشاشة"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"جارٍ العرض بملء الشاشة"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"للخروج، مرر بسرعة من أعلى إلى أسفل."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"حسنًا"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"رجوع"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"الرئيسية"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"القائمة"</string>
@@ -243,7 +246,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"صوت"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"سماعة الرأس"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"الإدخال"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"سماعات الأذن الطبية"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"سماعات الأذن الطبية"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"جارٍ التفعيل…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"التدوير التلقائي"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"التدوير التلقائي للشاشة"</string>
@@ -412,6 +415,14 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"‏أثناء المشاركة أو التسجيل أو البثّ، يمكن لنظام Android الوصول إلى كل المحتوى المعروض على شاشتك أو الذي يتم تشغيله على جهازك، لذا يُرجى توخي الحذر بشأن المعلومات، مثل كلمات المرور وتفاصيل الدفع والرسائل والصور وملفات الصوت والفيديو."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"‏أثناء مشاركة محتوى تطبيق أو تسجيله أو بثّه، يمكن لنظام Android الوصول إلى كل المحتوى المعروض أو الذي يتم تشغيله في ذلك التطبيق، لذا يُرجى توخي الحذر بشأن المعلومات مثل كلمات المرور وتفاصيل الدفع والرسائل والصور وملفات الصوت والفيديو."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"بدء"</string>
+    <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+    <skip />
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"حظر مشرف تكنولوجيا المعلومات هذه الميزة"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"ميزة \"تصوير الشاشة\" غير مفعَّلة بسبب سياسة الجهاز."</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"محو الكل"</string>
@@ -853,6 +864,7 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"تكبير الشاشة كلها"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"تكبير جزء من الشاشة"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"فتح إعدادات التكبير"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"إغلاق إعدادات التكبير"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"اسحب الزاوية لتغيير الحجم."</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"السماح بالتمرير القطري"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"تغيير الحجم"</string>
@@ -1039,6 +1051,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"حدثت مشكلة أثناء قراءة مقياس مستوى شحن البطارية."</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"انقر للحصول على مزيد من المعلومات."</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"لم يتم ضبط منبّه."</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"إدخال الرمز لفتح القفل"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"مستشعر بصمات الإصبع"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"المصادقة"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"الدخول إلى الجهاز"</string>
@@ -1101,7 +1114,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"‏يستمع \"مساعد Google\" إليك الآن."</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{إشعار واحد}zero{# إشعار}two{إشعاران}few{# إشعارات}many{# إشعارًا}other{# إشعار}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>، <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"تدوين الملاحظات"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"تدوين الملاحظات"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"تدوين الملاحظات في \"<xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>\""</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"البث"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"هل تريد إيقاف بث تطبيق <xliff:g id="APP_NAME">%1$s</xliff:g>؟"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"إذا أجريت بث تطبيق <xliff:g id="SWITCHAPP">%1$s</xliff:g> أو غيَّرت جهاز الإخراج، سيتوقَف البث الحالي."</string>
diff --git a/packages/SystemUI/res/values-as/strings.xml b/packages/SystemUI/res/values-as/strings.xml
index 9abb834..372cab8 100644
--- a/packages/SystemUI/res/values-as/strings.xml
+++ b/packages/SystemUI/res/values-as/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"চাবলৈ টিপক"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"ৰেকৰ্ড কৰা স্ক্ৰীন ছেভ কৰোঁতে আসোঁৱাহ হৈছে"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"স্ক্রীন ৰেকৰ্ড কৰা আৰম্ভ কৰোঁতে আসোঁৱাহ হৈছে"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"পূৰ্ণ স্ক্ৰীনত চাই আছে"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"বাহিৰ হ’বলৈ ওপৰৰ পৰা তললৈ ছোৱাইপ কৰক।"</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"বুজি পালোঁ"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"উভতি যাওক"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"গৃহ পৃষ্ঠাৰ বুটাম"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"মেনু"</string>
@@ -243,7 +246,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"অডিঅ’"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"হেডছেট"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"ইনপুট"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"শ্ৰৱণ যন্ত্ৰ"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"শ্ৰৱণ যন্ত্ৰ"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"অন কৰি থকা হৈছে…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"স্বয়ং-ঘূৰ্ণন"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"স্বয়ং-ঘূৰ্ণন স্ক্ৰীন"</string>
@@ -412,6 +415,14 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"আপুনি শ্বেয়াৰ কৰা, ৰেকৰ্ড কৰা অথবা কাষ্ট কৰাৰ সময়ত, আপোনাৰ স্ক্ৰীনখনত দৃশ্যমান হোৱা যিকোনো বস্তু অথবা আপোনাৰ ডিভাইচত প্লে’ কৰা যিকোনো সমললৈ Androidৰ এক্সেছ থাকে। গতিকে, পাছৱৰ্ড, পৰিশোধৰ সবিশেষ, বাৰ্তা, ফট’ আৰু অডিঅ’ আৰু ভিডিঅ’ৰ ক্ষেত্ৰত সাৱধান হওক।"</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"আপুনি শ্বেয়াৰ কৰা, ৰেকৰ্ড কৰা অথবা কাষ্ট কৰাৰ সময়ত, সেইটো এপত দৃশ্যমান যিকোনো বস্তু অথবা আপোনাৰ ডিভাইচত প্লে’ কৰা যিকোনো সমললৈ Androidৰ এক্সেছ থাকে। গতিকে, পাছৱৰ্ড, পৰিশোধৰ সবিশেষ, বাৰ্তা, ফট’ আৰু অডিঅ’ আৰু ভিডিঅ’ৰ ক্ষেত্ৰত সাৱধান হওক।"</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"আৰম্ভ কৰক"</string>
+    <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+    <skip />
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"আপোনাৰ আইটি প্ৰশাসকে অৱৰোধ কৰিছে"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"ডিভাইচ সম্পৰ্কীয় নীতিয়ে স্ক্ৰীন কেপশ্বাৰ কৰাটো অক্ষম কৰিছে"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"আটাইবোৰ মচক"</string>
@@ -853,6 +864,7 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"পূৰ্ণ স্ক্ৰীন বিবৰ্ধন কৰক"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"স্ক্ৰীনৰ কিছু অংশ বিবৰ্ধন কৰক"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"বিবৰ্ধন কৰাৰ ছেটিং খোলক"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"বিবৰ্ধনৰ ছেটিং বন্ধ কৰক"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"আকাৰ সলনি কৰিবলৈ চুককেইটা টানি আনি এৰক"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"কোণীয়াকৈ স্ক্ৰ’ল কৰাৰ অনুমতি দিয়ক"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"আকাৰ সলনি কৰক"</string>
@@ -1039,6 +1051,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"আপোনাৰ বেটাৰী মিটাৰ পঢ়োঁতে সমস্যা হৈছে"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"অধিক তথ্যৰ বাবে টিপক"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"কোনো এলাৰ্ম ছেট কৰা হোৱা নাই"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"স্ক্ৰীন লকটো দিয়ক"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"ফিংগাৰপ্ৰিণ্ট ছেন্সৰ"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"বিশ্বাসযোগ্যতা প্ৰমাণ কৰক"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"ডিভাইচ আনলক কৰক"</string>
@@ -1101,7 +1114,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Assistantএ শুনি আছে"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# টা জাননী}one{# টা জাননী}other{# টা জাননী}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"টোকাগ্ৰহণ"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"টোকা গ্ৰহণ কৰা"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"টোকা গ্ৰহণ কৰা, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"সম্প্ৰচাৰণ"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"<xliff:g id="APP_NAME">%1$s</xliff:g>ৰ সম্প্ৰচাৰ কৰা বন্ধ কৰিবনে?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"যদি আপুনি <xliff:g id="SWITCHAPP">%1$s</xliff:g>ৰ সম্প্ৰচাৰ কৰে অথবা আউটপুট সলনি কৰে, তেন্তে, আপোনাৰ বৰ্তমানৰ সম্প্ৰচাৰ বন্ধ হৈ যাব"</string>
diff --git a/packages/SystemUI/res/values-az/strings.xml b/packages/SystemUI/res/values-az/strings.xml
index ad0bd43..ba87178 100644
--- a/packages/SystemUI/res/values-az/strings.xml
+++ b/packages/SystemUI/res/values-az/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Baxmaq üçün toxunun"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Ekran çəkimini yadda saxlayarkən xəta oldu"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Ekranın yazılması ilə bağlı xəta"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Tam ekrana baxış"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Çıxmaq üçün yuxarıdan aşağı sürüşdürün."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"Anladım"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Geri"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Ana səhifə"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Menyu"</string>
@@ -243,7 +246,7 @@
     <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>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Giriş"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Eşitmə cihazları"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Eşitmə aparatları"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Aktiv edilir..."</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Avtodönüş"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Ekranın avtomatik dönməsi"</string>
@@ -412,6 +415,14 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Paylaşım, qeydəalma və ya yayım zamanı Android-in ekranda görünən, yaxud cihazda oxudulan məlumatlara girişi olur. Parol, ödəniş detalları, mesaj, foto, habelə audio və video kimi məlumatlarla bağlı diqqətli olun."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Tətbiq paylaşdıqda, qeydə aldıqda və ya yayımladıqda Android-in həmin tətbiqdə göstərilən, yaxud oxudulan məlumatlara girişi olur. Parol, ödəniş detalları, mesaj, foto, habelə audio və video kimi məlumatlarla bağlı diqqətli olun."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Başlayın"</string>
+    <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+    <skip />
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"İT admininiz tərəfindən bloklanıb"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Ekran çəkimi cihaz siyasəti ilə deaktiv edilib"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Hamısını silin"</string>
@@ -853,6 +864,7 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Tam ekranı böyüdün"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Ekran hissəsinin böyüdülməsi"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Böyütmə ayarlarını açın"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Böyütmə ayarlarını bağlayın"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Ölçüsünü dəyişmək üçün küncündən sürüşdürün"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Diaqonal sürüşdürməyə icazə verin"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Ölçüsünü dəyişin"</string>
@@ -1039,6 +1051,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Batareya ölçüsünü oxuyarkən problem yarandı"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Ətraflı məlumat üçün toxunun"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Siqnal ayarlanmayıb"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"ekran kilidi daxil edin"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Barmaq izi sensoru"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"doğrulayın"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"cihaz daxil edin"</string>
@@ -1101,7 +1114,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Assistent dinləyir"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# bildiriş}other{# bildiriş}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Qeyd tutma"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Qeydgötürmə"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Qeydgötürmə, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Yayım"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"<xliff:g id="APP_NAME">%1$s</xliff:g> tətbiqinin yayımlanması dayandırılsın?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> tətbiqini yayımlasanız və ya nəticəni dəyişsəniz, cari yayımınız dayandırılacaq"</string>
diff --git a/packages/SystemUI/res/values-b+sr+Latn/strings.xml b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
index 48360ec..258ae1d 100644
--- a/packages/SystemUI/res/values-b+sr+Latn/strings.xml
+++ b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Dodirnite da biste pregledali"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Greška pri čuvanju snimka ekrana"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Greška pri pokretanju snimanja ekrana"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Prikazuje se ceo ekran"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Da biste izašli, prevucite nadole odozgo."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"Važi"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Nazad"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Početna"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Meni"</string>
@@ -243,7 +246,7 @@
     <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>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Unos"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Slušni aparati"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Slušni aparati"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Uključuje se..."</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Automatska rotacija"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Automatsko rotiranje ekrana"</string>
@@ -412,6 +415,14 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Kada delite, snimate ili prebacujete, Android ima pristup kompletnom sadržaju koji je vidljiv na ekranu ili se pušta na uređaju. Zato budite pažljivi sa lozinkama, informacijama o plaćanju, porukama, slikama i audio i video snimcima."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Kada delite, snimate ili prebacujete aplikaciju, Android ima pristup kompletnom sadržaju koji je vidljiv ili se pušta u toj aplikaciji. Zato budite pažljivi sa lozinkama, informacijama o plaćanju, porukama, slikama i audio i video snimcima."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Pokreni"</string>
+    <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+    <skip />
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blokira IT administrator"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Snimanje ekrana je onemogućeno smernicama za uređaj"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Obriši sve"</string>
@@ -853,6 +864,7 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Uvećajte ceo ekran"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Uvećajte deo ekrana"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Otvori podešavanja uvećanja"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Zatvori podešavanja uvećanja"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Prevucite ugao da biste promenili veličinu"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Dozvoli dijagonalno skrolovanje"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Promeni veličinu"</string>
@@ -1039,6 +1051,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Problem sa očitavanjem merača baterije"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Dodirnite za više informacija"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Nije podešen"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"unesite zaključavanje ekrana"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Senzor za otisak prsta"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"potvrdite identitet"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"unesite uređaj"</string>
@@ -1101,7 +1114,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Pomoćnik sluša"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# obaveštenje}one{# obaveštenje}few{# obaveštenja}other{# obaveštenja}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Pravljenje beležaka"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Pravljenje beležaka"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Pravljenje beležaka, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Emitovanje"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Želite da zaustavite emitovanje aplikacije <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Ako emitujete aplikaciju <xliff:g id="SWITCHAPP">%1$s</xliff:g> ili promenite izlaz, aktuelno emitovanje će se zaustaviti"</string>
diff --git a/packages/SystemUI/res/values-be/strings.xml b/packages/SystemUI/res/values-be/strings.xml
index 455059f..5f9d63c 100644
--- a/packages/SystemUI/res/values-be/strings.xml
+++ b/packages/SystemUI/res/values-be/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Націсніце для прагляду"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Памылка захавання запісу экрана"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Памылка пачатку запісу экрана"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Прагляд у поўнаэкранным рэжыме"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Для выхаду правядзіце зверху ўніз."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"Зразумела"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Назад"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"На Галоўную старонку"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Меню"</string>
@@ -243,7 +246,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Гук"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Гарнітура"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Увод"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Слыхавыя апараты"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Слыхавыя апараты"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Уключэнне…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Аўтапаварот"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Аўтаматычны паварот экрана"</string>
@@ -412,6 +415,14 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Калі адбываецца абагульванне, запіс ці трансляцыя, Android мае доступ да ўсяго змесціва, якое паказваецца на экране ці прайграецца на прыладзе. Таму прадухіліце паказ пароляў, плацежных рэквізітаў, паведамленняў, фота, відэа і аўдыя."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Калі адбываецца абагульванне, запіс ці трансляцыя змесціва праграмы, Android мае доступ да ўсяго змесціва, якое паказваецца ці прайграецца ў праграме. Таму прадухіліце паказ пароляў, плацежных рэквізітаў, паведамленняў, фота, відэа і аўдыя."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Пачаць"</string>
+    <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+    <skip />
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Заблакіравана вашым ІТ-адміністратарам"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Здыманне экрана адключана згодна з палітыкай прылады"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Ачысціць усё"</string>
@@ -853,6 +864,7 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Павялічыць увесь экран"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Павялічыць частку экрана"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Адкрыць налады павелічэння"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Закрыць налады павелічэння"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Каб змяніць памер, перацягніце вугал"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Дазволіць прагортванне па дыяганалі"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Змяніць памер"</string>
@@ -1039,6 +1051,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Праблема з чытаннем індыкатара зараду акумулятара"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Націсніце, каб убачыць больш"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Няма будзільнікаў"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"прыступіць да разблакіроўкі экрана"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Сканер адбіткаў пальцаў"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"правесці аўтэнтыфікацыю"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"адкрыць галоўны экран прылады"</string>
@@ -1101,7 +1114,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Памочнік слухае"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# апавяшчэнне}one{# апавяшчэнне}few{# апавяшчэнні}many{# апавяшчэнняў}other{# апавяшчэння}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Стварэнне нататак"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Стварэнне нататак"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Стварэнне нататак, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Перадача даных"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Спыніць трансляцыю праграмы \"<xliff:g id="APP_NAME">%1$s</xliff:g>\"?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Пры пераключэнні на праграму \"<xliff:g id="SWITCHAPP">%1$s</xliff:g>\" ці змяненні вываду бягучая трансляцыя спыняецца"</string>
diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml
index eb97358..f2a8dbf 100644
--- a/packages/SystemUI/res/values-bg/strings.xml
+++ b/packages/SystemUI/res/values-bg/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Докоснете за преглед"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Грешка при запазването на записа на екрана"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"При стартирането на записа на екрана възникна грешка"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Изглед на цял екран"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"За изход плъзнете пръст надолу от горната част."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"Разбрах"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Назад"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Начало"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Меню"</string>
@@ -243,7 +246,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Аудио"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Слушалки"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Вход"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Слухови апарати"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Слухови апарати"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Включва се..."</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Авт. ориентация"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Автоматично завъртане на екрана"</string>
@@ -412,6 +415,14 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Когато споделяте, записвате или предавате, Android има достъп до всичко, което се вижда на екрана ви или се възпроизвежда на устройството ви. Затова бъдете внимателни с неща като пароли, подробности за начини на плащане, съобщения, снимки, аудио и видео."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Когато споделяте, записвате или предавате дадено приложение, Android има достъп до всичко, което се показва или възпроизвежда в него. Затова бъдете внимателни с неща като пароли, подробности за начини на плащане, съобщения, снимки, аудио и видео."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Стартиране"</string>
+    <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+    <skip />
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Блокирано от системния ви администратор"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Заснемането на екрана е деактивирано от правило за устройството"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Изчистване на всички"</string>
@@ -853,6 +864,7 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Увеличаване на целия екран"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Увеличаване на част от екрана"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Отваряне на настройките за увеличението"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Затваряне на настройките за увеличение"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Плъзнете ъгъла за преоразмеряване"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Разрешаване на диагонално превъртане"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Преоразмеряване"</string>
@@ -1039,6 +1051,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Възникна проблем при четенето на данните за нивото на батерията"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Докоснете за още информация"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Няма зададен будилник"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"въведете опция за заключване на екрана"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Сензор за отпечатъци"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"удостоверяване"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"вход в устройството"</string>
@@ -1101,7 +1114,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Асистент слуша"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# известие}other{# известия}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Водене на бележки"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Водене на бележки"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Водене на бележки, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Излъчване"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Да се спре ли предаването на <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Ако предавате <xliff:g id="SWITCHAPP">%1$s</xliff:g> или промените изхода, текущото ви предаване ще бъде прекратено"</string>
diff --git a/packages/SystemUI/res/values-bn/strings.xml b/packages/SystemUI/res/values-bn/strings.xml
index d2b7047..836b0ed 100644
--- a/packages/SystemUI/res/values-bn/strings.xml
+++ b/packages/SystemUI/res/values-bn/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"দেখতে ট্যাপ করুন"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"স্ক্রিন রেকর্ডিং সেভ করার সময় কোনও সমস্যা হয়েছে"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"স্ক্রিন রেকর্ডিং শুরু করার সময় সমস্যা হয়েছে"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"ফুল-স্ক্রিনে দেখা হচ্ছে"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"বেরিয়ে যেতে উপর থেকে নিচের দিকে সোয়াইপ করুন।"</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"বুঝেছি"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"ফিরুন"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"হোম"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"মেনু"</string>
@@ -243,7 +246,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"অডিও"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"হেডসেট"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"ইনপুট"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"হিয়ারিং এড"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"হিয়ারিং এড"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"চালু করা হচ্ছে…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"নিজে থেকে ঘুরবে"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"অটো-রোটেট স্ক্রিন"</string>
@@ -412,6 +415,14 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"আপনি শেয়ার, রেকর্ড বা কাস্ট করার সময়, স্ক্রিনে দৃশ্যমান বা ডিভাইসে চালানো সব কিছুই Android অ্যাক্সেস করতে পারবে। তাই পাসওয়ার্ড, পেমেন্টের বিবরণ, মেসেজ, ফটো এবং অডিও ও ভিডিওর মতো বিষয়ে সতর্ক থাকুন।"</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"আপনি কোনও অ্যাপ শেয়ার, রেকর্ড বা কাস্ট করার সময়, সেই অ্যাপে দেখা যায় বা চালানো হয় এমন সব কিছু Android অ্যাক্সেস করতে পারবে। তাই পাসওয়ার্ড, পেমেন্টের বিবরণ, মেসেজ, ফটো এবং অডিও ও ভিডিওর মতো বিষয়ে সতর্ক থাকুন।"</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"শুরু করুন"</string>
+    <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+    <skip />
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"আপনার আইটি অ্যাডমিন ব্লক করেছেন"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"ডিভাইস নীতির কারণে স্ক্রিন ক্যাপচার করার প্রসেস বন্ধ করা আছে"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"সব মুছে দিন"</string>
@@ -853,6 +864,7 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"সম্পূর্ণ স্ক্রিন বড় করে দেখা"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"স্ক্রিনের কিছুটা অংশ বড় করুন"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"বড় করে দেখার সেটিংস খুলুন"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"\'বড় করে দেখা\' সেটিংস বন্ধ করুন"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"ছোট বড় করার জন্য কোণ টেনে আনুন"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"কোনাকুনি স্ক্রল করার অনুমতি দেওয়া"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"ছোট বড় করা"</string>
@@ -1039,6 +1051,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"ব্যাটারির মিটারের রিডিং নেওয়ার সময় সমস্যা হয়েছে"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"আরও তথ্যের জন্য ট্যাপ করুন"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"কোনও অ্যালার্ম সেট করা নেই"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"স্ক্রিন লক খুলুন"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"ফিঙ্গারপ্রিন্ট সেন্সর"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"যাচাই করিয়ে নিন"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"ডিভাইস আনলক করুন"</string>
@@ -1101,7 +1114,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Assistant শুনছে"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{#টি বিজ্ঞপ্তি}one{#টি বিজ্ঞপ্তি}other{#টি বিজ্ঞপ্তি}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Notetaking"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"নোট নেওয়া"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"নোট নেওয়া, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"ব্রডকাস্ট করা হচ্ছে"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"<xliff:g id="APP_NAME">%1$s</xliff:g> সম্প্রচার বন্ধ করবেন?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"আপনি <xliff:g id="SWITCHAPP">%1$s</xliff:g> সম্প্রচার করলে বা আউটপুট পরিবর্তন করলে, আপনার বর্তমান সম্প্রচার বন্ধ হয়ে যাবে"</string>
diff --git a/packages/SystemUI/res/values-bs/strings.xml b/packages/SystemUI/res/values-bs/strings.xml
index 4792c36..db902ef 100644
--- a/packages/SystemUI/res/values-bs/strings.xml
+++ b/packages/SystemUI/res/values-bs/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Dodirnite da vidite"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Greška prilikom pohranjivanja snimka ekrana"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Greška pri pokretanju snimanja ekrana"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Prikazuje se cijeli ekran"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Da izađete, prevucite odozgo nadolje."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"Razumijem"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Nazad"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Dugme za početnu stranicu"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Dugme Meni"</string>
@@ -243,7 +246,7 @@
     <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>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Ulaz"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Slušni aparat"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Slušni aparati"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Uključivanje…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Automatsko rotiranje"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Automatsko rotiranje ekrana"</string>
@@ -412,6 +415,14 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Kada dijelite, snimate ili emitirate, Android ima pristup svemu što je vidljivo na ekranu ili što se reproducira na uređaju. Stoga budite oprezni s informacijama kao što su lozinke, podaci o plaćanju, poruke, fotografije, zvukovi i videozapisi."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Kada dijelite, snimate ili emitirate aplikaciju, Android ima pristup svemu što se prikazuje ili reproducira u toj aplikaciji. Stoga budite oprezni s informacijama kao što su lozinke, podaci o plaćanju, poruke, fotografije, zvukovi i videozapisi."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Pokreni"</string>
+    <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+    <skip />
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blokirao je vaš IT administrator"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Snimanje ekrana je onemogućeno pravilima uređaja"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Očisti sve"</string>
@@ -853,6 +864,7 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Uvećavanje prikaza preko cijelog ekrana"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Uvećavanje dijela ekrana"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Otvori postavke uvećavanja"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Zatvori postavke uvećavanja"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Prevucite ugao da promijenite veličinu"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Dozvoli dijagonalno klizanje"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Promijeni veličinu"</string>
@@ -1039,6 +1051,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Došlo je do problema prilikom očitavanja mjerača stanja baterije"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Dodirnite za više informacija"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Nema nijednog alarma"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"unos zaključavanja ekrana"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Senzor za otisak prsta"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"autentificiranje"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"pristup uređaju"</string>
@@ -1101,7 +1114,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Asistent sluša"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# obavještenje}one{# obavještenje}few{# obavještenja}other{# obavještenja}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Pisanje bilješki"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Pisanje bilješki"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Pisanje bilješki, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Emitiranje"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Zaustaviti emitiranje aplikacije <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Ako emitirate aplikaciju <xliff:g id="SWITCHAPP">%1$s</xliff:g> ili promijenite izlaz, trenutno emitiranje će se zaustaviti"</string>
diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml
index b66d235..7eefa4f 100644
--- a/packages/SystemUI/res/values-ca/strings.xml
+++ b/packages/SystemUI/res/values-ca/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Toca per veure-la"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"S\'ha produït un error en desar la gravació de la pantalla"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"S\'ha produït un error en iniciar la gravació de pantalla"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Mode de pantalla completa"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Per sortir, llisca cap avall des de la part superior."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"Entesos"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Enrere"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Inici"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Menú"</string>
@@ -243,7 +246,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Àudio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Auriculars"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Entrada"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Audiòfons"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Audiòfons"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"S\'està activant…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Gira automàticament"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Gira la pantalla automàticament"</string>
@@ -412,6 +415,14 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Quan comparteixes, graves o emets contingut, Android té accés a qualsevol cosa que es vegi a la pantalla o que es reprodueixi al dispositiu. Per aquest motiu, ves amb compte amb les contrasenyes, les dades de pagament, els missatges, les fotos i l\'àudio i el vídeo."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Quan comparteixes, graves o emets contingut, Android té accés a qualsevol cosa que es vegi a la pantalla o que es reprodueixi a l\'aplicació. Per aquest motiu, ves amb compte amb les contrasenyes, les dades de pagament, els missatges, les fotos i l\'àudio i el vídeo."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Inicia"</string>
+    <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+    <skip />
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Bloquejat per l\'administrador de TI"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Les captures de pantalla estan desactivades per la política de dispositius"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Esborra-ho tot"</string>
@@ -853,6 +864,7 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Amplia la pantalla completa"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Amplia una part de la pantalla"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Obre la configuració de l\'ampliació"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Tanca la configuració de l\'ampliació"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Arrossega el cantó per canviar la mida"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Permet el desplaçament en diagonal"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Canvia la mida"</string>
@@ -1039,6 +1051,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Hi ha hagut un problema en llegir el mesurador de la bateria"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Toca per obtenir més informació"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Cap alarma definida"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"utilitza el bloqueig de pantalla"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Sensor d\'empremtes digitals"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"autenticar"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"accedir al dispositiu"</string>
@@ -1101,7 +1114,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"L\'assistent t\'escolta"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# notificació}many{# notificacions}other{# notificacions}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Presa de notes"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Presa de notes"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Presa de notes, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"S\'està emetent"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Vols deixar d\'emetre <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Si emets <xliff:g id="SWITCHAPP">%1$s</xliff:g> o canvies la sortida, l\'emissió actual s\'aturarà"</string>
diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml
index 1ea4c8c..4a070fb 100644
--- a/packages/SystemUI/res/values-cs/strings.xml
+++ b/packages/SystemUI/res/values-cs/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Klepnutím nahrávku zobrazíte"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Při ukládání záznamu obrazovky došlo k chybě"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Při spouštění nahrávání obrazovky došlo k chybě"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Zobrazení celé obrazovky"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Režim ukončíte přejetím prstem shora dolů."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"Rozumím"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Zpět"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Domů"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string>
@@ -243,7 +246,7 @@
     <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>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Vstup"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Naslouchátka"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Naslouchátka"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Zapínání…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Automatické otáčení"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Automatické otáčení obrazovky"</string>
@@ -412,6 +415,14 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Během sdílení, nahrávání nebo odesílání má Android přístup k veškerému obsahu, který je viditelný na obrazovce nebo se přehrává v zařízení. Buďte proto opatrní s věcmi, jako jsou hesla, platební údaje, zprávy, fotografie, zvuk a video."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Během sdílení, nahrávání nebo odesílání aplikace má Android přístup k veškerému obsahu, který je v dané aplikaci zobrazen nebo přehráván. Buďte proto opatrní s věcmi, jako jsou hesla, platební údaje, zprávy, fotografie, zvuk a video."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Začít"</string>
+    <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+    <skip />
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blokováno administrátorem IT"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Záznam obrazovky je zakázán zásadami zařízení"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Smazat vše"</string>
@@ -853,6 +864,7 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Zvětšit celou obrazovku"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Zvětšit část obrazovky"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Otevřít nastavení zvětšení"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Zavřít nastavení zvětšení"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Velikost změníte přetažením rohu"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Povolit diagonální posouvání"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Změnit velikost"</string>
@@ -1039,6 +1051,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Problém s načtením měřiče baterie"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Klepnutím zobrazíte další informace"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Budík nenastaven"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"zadejte zámek obrazovky"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Snímač otisků prstů"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"ověříte"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"zadáte zařízení"</string>
@@ -1101,7 +1114,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Asistent poslouchá"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# oznámení}few{# oznámení}many{# oznámení}other{# oznámení}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g> <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Poznámky"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Psaní poznámek"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Psaní poznámek, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Vysílání"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Zastavit vysílání v aplikaci <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Pokud budete vysílat v aplikaci <xliff:g id="SWITCHAPP">%1$s</xliff:g> nebo změníte výstup, aktuální vysílání se zastaví"</string>
diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml
index 6f112fc..2893b1e 100644
--- a/packages/SystemUI/res/values-da/strings.xml
+++ b/packages/SystemUI/res/values-da/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Tryk for at se"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Skærmoptagelsen kunne ikke gemmes"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Skærmoptagelsen kunne ikke startes"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Visning i fuld skærm"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Stryg ned fra toppen for at afslutte."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"OK, det er forstået"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Tilbage"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Hjem"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string>
@@ -243,7 +246,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Lyd"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Headset"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Input"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Høreapparater"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Høreapparater"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Aktiverer…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Roter automatisk"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Roter skærmen automatisk"</string>
@@ -412,6 +415,14 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Når du deler, optager eller caster, har Android adgang til alt, der er synligt på din skærm eller afspilles på din enhed. Vær derfor forsigtig med ting såsom adgangskoder, betalingsoplysninger, beskeder, billeder, lyd og video."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Når du deler, optager eller caster en app, har Android adgang til alt, der vises eller afspilles i den pågældende app. Vær derfor forsigtig med ting såsom adgangskoder, betalingsoplysninger, beskeder, billeder, lyd og video."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Start"</string>
+    <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+    <skip />
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blokeret af din it-administrator"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Screenshots er deaktiveret af enhedspolitikken"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Ryd alle"</string>
@@ -853,6 +864,7 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Forstør hele skærmen"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Forstør en del af skærmen"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Åbn indstillinger for forstørrelse"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Luk indstillinger for forstørrelse"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Træk i hjørnet for at justere størrelsen"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Tillad diagonal rulning"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Juster"</string>
@@ -1039,6 +1051,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Der er problemer med at læse dit batteriniveau"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Tryk for at få flere oplysninger"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Ingen alarm er indstillet"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"angiv skærmlås"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Fingeraftrykssensor"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"godkende"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"få adgang til enheden"</string>
@@ -1101,7 +1114,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Google Assistent lytter med"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# notifikation}one{# notifikation}other{# notifikationer}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Notetagning"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Notetagning"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Notetagning, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Udsender"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Stop udsendelsen <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Hvis du udsender <xliff:g id="SWITCHAPP">%1$s</xliff:g> eller skifter output, stopper din aktuelle udsendelse"</string>
diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml
index 1f26dae..b527541 100644
--- a/packages/SystemUI/res/values-de/strings.xml
+++ b/packages/SystemUI/res/values-de/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Zum Ansehen tippen"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Fehler beim Speichern der Bildschirmaufzeichnung"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Fehler beim Start der Bildschirmaufzeichnung"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Vollbildmodus wird aktiviert"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Zum Beenden von oben nach unten wischen"</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"Ok"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Zurück"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Startbildschirm"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Menü"</string>
@@ -243,7 +246,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Headset"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Eingabe"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Hörgeräte"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Hörgerät"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Wird aktiviert…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Autom. drehen"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Bildschirm automatisch drehen"</string>
@@ -412,6 +415,14 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Beim Teilen, Aufnehmen oder Streamen hat Android Zugriff auf alle Inhalte, die auf deinem Bildschirm sichtbar sind oder von deinem Gerät wiedergegeben werden. Sei also vorsichtig mit Informationen wie Passwörtern, Zahlungsdetails, Nachrichten, Fotos sowie Audio- und Videoinhalten."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Beim Teilen, Aufnehmen oder Streamen einer App hat Android Zugriff auf alle Inhalte, die in dieser App sichtbar sind oder von ihr wiedergegeben werden. Sei also vorsichtig mit Informationen wie Passwörtern, Zahlungsdetails, Nachrichten, Fotos sowie Audio- und Videoinhalten."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Starten"</string>
+    <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+    <skip />
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Vom IT-Administrator blockiert"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Bildschirmaufnahme ist durch die Geräterichtlinien deaktiviert"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Alle löschen"</string>
@@ -853,6 +864,7 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Ganzen Bildschirm vergrößern"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Teil des Bildschirms vergrößern"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Vergrößerungseinstellungen öffnen"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Vergrößerungseinstellungen schließen"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Zum Anpassen der Größe Ecke ziehen"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Diagonales Scrollen erlauben"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Größe ändern"</string>
@@ -1039,6 +1051,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Problem beim Lesen des Akkustands"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Für weitere Informationen tippen"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Kein Wecker gestellt"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"Displaysperre-Anmeldedaten eingeben"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Fingerabdrucksensor"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"Authentifizieren"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"Eingeben des Geräts"</string>
@@ -1101,7 +1114,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Assistant hört zu"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# Benachrichtigung}other{# Benachrichtigungen}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Notizen machen"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Notizen"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Notizen, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Übertragung läuft"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"<xliff:g id="APP_NAME">%1$s</xliff:g> nicht mehr streamen?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Wenn du <xliff:g id="SWITCHAPP">%1$s</xliff:g> streamst oder die Ausgabe änderst, wird dein aktueller Stream beendet"</string>
diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml
index f80fb0d..e23cbb8 100644
--- a/packages/SystemUI/res/values-el/strings.xml
+++ b/packages/SystemUI/res/values-el/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Πατήστε για προβολή"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Σφάλμα κατά την αποθήκευση της εγγραφής οθόνης"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Σφάλμα κατά την έναρξη της εγγραφής οθόνης"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Προβολή σε πλήρη οθόνη"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Για έξοδο, σύρετε προς τα κάτω από το επάνω μέρος."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"Το κατάλαβα"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Πίσω"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Αρχική οθόνη"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Μενού"</string>
@@ -243,7 +246,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Ήχος"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Ακουστικά"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Είσοδος"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Βοηθήματα ακρόασης"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Βοηθήματα ακοής"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Ενεργοποίηση…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Αυτόματη περιστροφή"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Αυτόματη περιστροφή οθόνης"</string>
@@ -412,6 +415,10 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Όταν κάνετε κοινή χρήση, εγγραφή ή μετάδοση, το Android έχει πρόσβαση σε οτιδήποτε είναι ορατό στην οθόνη σας ή αναπαράγεται στη συσκευή σας. Επομένως, να είστε προσεκτικοί με τους κωδικούς πρόσβασης, τα στοιχεία πληρωμής, τα μηνύματα, τις φωτογραφίες, τον ήχο και το βίντεο."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Όταν κάνετε κοινή χρήση, εγγραφή ή μετάδοση μιας εφαρμογής, το Android έχει πρόσβαση σε οτιδήποτε είναι ορατό ή αναπαράγεται στη συγκεκριμένη εφαρμογή. Επομένως, να είστε προσεκτικοί με τους κωδικούς πρόσβασης, τα στοιχεία πληρωμής, τα μηνύματα, τις φωτογραφίες, τον ήχο και το βίντεο."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Έναρξη"</string>
+    <string name="media_projection_task_switcher_text" msgid="590885489897412359">"Η κοινοποίηση τίθεται σε παύση κατά την εναλλαγή μεταξύ εφαρμογών"</string>
+    <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"Εναλλακτικά, κοινοποιήστε την εφαρμογή"</string>
+    <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"Επιστροφή"</string>
+    <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"Εναλλαγή μεταξύ εφαρμογών"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Αποκλείστηκε από τον διαχειριστή IT"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Η καταγραφή οθόνης έχει απενεργοποιηθεί από την πολιτική χρήσης συσκευής."</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Διαγραφή όλων"</string>
@@ -853,6 +860,7 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Μεγέθυνση πλήρους οθόνης"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Μεγέθυνση μέρους της οθόνης"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Άνοιγμα ρυθμίσεων μεγιστοποίησης"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Κλείσιμο ρυθμίσεων μεγιστοποίησης"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Σύρετε τη γωνία για αλλαγή μεγέθους"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Να επιτρέπεται η διαγώνια κύλιση"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Αλλαγή μεγέθους"</string>
@@ -1039,6 +1047,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Υπάρχει κάποιο πρόβλημα με την ανάγνωση του μετρητή μπαταρίας"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Πατήστε για περισσότερες πληροφορίες."</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Δεν ορίστηκε ξυπνητ."</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"εισαγωγή κλειδώματος οθόνης"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Αισθητήρας δακτυλικών αποτυπωμάτων"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"έλεγχος ταυτότητας"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"εισαγωγή συσκευής"</string>
@@ -1101,7 +1110,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Ο Βοηθός ακούει"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# ειδοποίηση}other{# ειδοποιήσεις}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Δημιουργία σημειώσεων"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Δημιουργία σημειώσεων"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Δημιουργία σημειώσεων, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Μετάδοση"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Διακοπή μετάδοσης με την εφαρμογή <xliff:g id="APP_NAME">%1$s</xliff:g>;"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Εάν κάνετε μετάδοση με την εφαρμογή <xliff:g id="SWITCHAPP">%1$s</xliff:g> ή αλλάξετε την έξοδο, η τρέχουσα μετάδοση θα σταματήσει"</string>
diff --git a/packages/SystemUI/res/values-en-rAU/strings.xml b/packages/SystemUI/res/values-en-rAU/strings.xml
index b265bdc..8098739 100644
--- a/packages/SystemUI/res/values-en-rAU/strings.xml
+++ b/packages/SystemUI/res/values-en-rAU/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Tap to view"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Error saving screen recording"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Error starting screen recording"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Viewing full screen"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"To exit, swipe down from the top."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"Got it"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Back"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Home"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string>
@@ -243,7 +246,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Headset"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Input"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Hearing aids"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Hearing aids"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Turning on…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Auto-rotate"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Auto-rotate screen"</string>
@@ -412,6 +415,14 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"When you’re sharing, recording or casting, Android has access to anything visible on your screen or played on your device. So be careful with things like passwords, payment details, messages, photos, audio and video."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"When you’re sharing, recording or casting an app, Android has access to anything shown or played on that app. So be careful with things like passwords, payment details, messages, photos, audio and video."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Start"</string>
+    <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+    <skip />
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blocked by your IT admin"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Screen capturing is disabled by device policy"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Clear all"</string>
@@ -853,6 +864,7 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Magnify full screen"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Magnify part of screen"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Open magnification settings"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Close magnification settings"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Drag corner to resize"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Allow diagonal scrolling"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Resize"</string>
@@ -1039,6 +1051,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Problem reading your battery meter"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Tap for more information"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"No alarm set"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"enter screen lock"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Fingerprint sensor"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"Authenticate"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"enter device"</string>
@@ -1101,7 +1114,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Assistant is listening"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# notification}other{# notifications}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Notetaking"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Note-taking"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Note-taking, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Broadcasting"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Stop broadcasting <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"If you broadcast <xliff:g id="SWITCHAPP">%1$s</xliff:g> or change the output, your current broadcast will stop"</string>
diff --git a/packages/SystemUI/res/values-en-rCA/strings.xml b/packages/SystemUI/res/values-en-rCA/strings.xml
index f10386b..d208382 100644
--- a/packages/SystemUI/res/values-en-rCA/strings.xml
+++ b/packages/SystemUI/res/values-en-rCA/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Tap to view"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Error saving screen recording"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Error starting screen recording"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Viewing full screen"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"To exit, swipe down from the top."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"Got it"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Back"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Home"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string>
@@ -243,7 +246,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Headset"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Input"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Hearing Aids"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Hearing aids"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Turning on…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Auto-rotate"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Auto-rotate screen"</string>
@@ -412,6 +415,10 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"When you’re sharing, recording, or casting, Android has access to anything visible on your screen or played on your device. So be careful with things like passwords, payment details, messages, photos, and audio and video."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"When you’re sharing, recording, or casting an app, Android has access to anything shown or played on that app. So be careful with things like passwords, payment details, messages, photos, and audio and video."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Start"</string>
+    <string name="media_projection_task_switcher_text" msgid="590885489897412359">"Sharing pauses when you switch apps"</string>
+    <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"Share this app instead"</string>
+    <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"Switch back"</string>
+    <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"App switch"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blocked by your IT admin"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Screen capturing is disabled by device policy"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Clear all"</string>
@@ -853,6 +860,7 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Magnify full screen"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Magnify part of screen"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Open magnification settings"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Close magnification settings"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Drag corner to resize"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Allow diagonal scrolling"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Resize"</string>
@@ -1039,6 +1047,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Problem reading your battery meter"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Tap for more information"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"No alarm set"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"enter screen lock"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Fingerprint sensor"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"authenticate"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"enter device"</string>
@@ -1101,7 +1110,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Assistant is listening"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# notification}other{# notifications}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Notetaking"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Note-taking"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Note-taking, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Broadcasting"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Stop broadcasting <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"If you broadcast <xliff:g id="SWITCHAPP">%1$s</xliff:g> or change the output, your current broadcast will stop"</string>
diff --git a/packages/SystemUI/res/values-en-rGB/strings.xml b/packages/SystemUI/res/values-en-rGB/strings.xml
index b265bdc..8098739 100644
--- a/packages/SystemUI/res/values-en-rGB/strings.xml
+++ b/packages/SystemUI/res/values-en-rGB/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Tap to view"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Error saving screen recording"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Error starting screen recording"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Viewing full screen"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"To exit, swipe down from the top."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"Got it"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Back"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Home"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string>
@@ -243,7 +246,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Headset"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Input"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Hearing aids"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Hearing aids"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Turning on…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Auto-rotate"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Auto-rotate screen"</string>
@@ -412,6 +415,14 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"When you’re sharing, recording or casting, Android has access to anything visible on your screen or played on your device. So be careful with things like passwords, payment details, messages, photos, audio and video."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"When you’re sharing, recording or casting an app, Android has access to anything shown or played on that app. So be careful with things like passwords, payment details, messages, photos, audio and video."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Start"</string>
+    <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+    <skip />
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blocked by your IT admin"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Screen capturing is disabled by device policy"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Clear all"</string>
@@ -853,6 +864,7 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Magnify full screen"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Magnify part of screen"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Open magnification settings"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Close magnification settings"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Drag corner to resize"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Allow diagonal scrolling"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Resize"</string>
@@ -1039,6 +1051,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Problem reading your battery meter"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Tap for more information"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"No alarm set"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"enter screen lock"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Fingerprint sensor"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"Authenticate"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"enter device"</string>
@@ -1101,7 +1114,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Assistant is listening"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# notification}other{# notifications}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Notetaking"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Note-taking"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Note-taking, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Broadcasting"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Stop broadcasting <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"If you broadcast <xliff:g id="SWITCHAPP">%1$s</xliff:g> or change the output, your current broadcast will stop"</string>
diff --git a/packages/SystemUI/res/values-en-rIN/strings.xml b/packages/SystemUI/res/values-en-rIN/strings.xml
index b265bdc..8098739 100644
--- a/packages/SystemUI/res/values-en-rIN/strings.xml
+++ b/packages/SystemUI/res/values-en-rIN/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Tap to view"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Error saving screen recording"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Error starting screen recording"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Viewing full screen"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"To exit, swipe down from the top."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"Got it"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Back"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Home"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string>
@@ -243,7 +246,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Headset"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Input"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Hearing aids"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Hearing aids"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Turning on…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Auto-rotate"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Auto-rotate screen"</string>
@@ -412,6 +415,14 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"When you’re sharing, recording or casting, Android has access to anything visible on your screen or played on your device. So be careful with things like passwords, payment details, messages, photos, audio and video."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"When you’re sharing, recording or casting an app, Android has access to anything shown or played on that app. So be careful with things like passwords, payment details, messages, photos, audio and video."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Start"</string>
+    <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+    <skip />
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blocked by your IT admin"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Screen capturing is disabled by device policy"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Clear all"</string>
@@ -853,6 +864,7 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Magnify full screen"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Magnify part of screen"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Open magnification settings"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Close magnification settings"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Drag corner to resize"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Allow diagonal scrolling"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Resize"</string>
@@ -1039,6 +1051,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Problem reading your battery meter"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Tap for more information"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"No alarm set"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"enter screen lock"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Fingerprint sensor"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"Authenticate"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"enter device"</string>
@@ -1101,7 +1114,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Assistant is listening"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# notification}other{# notifications}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Notetaking"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Note-taking"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Note-taking, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Broadcasting"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Stop broadcasting <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"If you broadcast <xliff:g id="SWITCHAPP">%1$s</xliff:g> or change the output, your current broadcast will stop"</string>
diff --git a/packages/SystemUI/res/values-en-rXC/strings.xml b/packages/SystemUI/res/values-en-rXC/strings.xml
index 530474b..8a321e2 100644
--- a/packages/SystemUI/res/values-en-rXC/strings.xml
+++ b/packages/SystemUI/res/values-en-rXC/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‎‏‏‏‎‎‎‎‏‎‎‎‎‎‎‏‎‏‎‎‎‏‎‏‏‏‏‎‎‏‎‎‎‎‎‏‎‏‎‏‎‏‏‏‎‎‎‏‏‏‏‏‏‎‎‎‏‏‎Tap to view‎‏‎‎‏‎"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‎‏‎‏‎‏‏‏‎‎‎‏‎‎‏‏‏‏‏‏‎‎‎‏‏‏‎‏‎‏‏‎‏‎‎‎‏‏‎‏‏‎‎‎‎‎‎‏‎‎‎‎‎‏‏‏‏‏‎Error saving screen recording‎‏‎‎‏‎"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‏‎‏‎‎‎‏‎‏‎‎‏‎‏‎‎‎‏‎‎‏‏‏‏‎‏‎‏‏‏‏‏‏‎‎‏‏‎‏‎‏‎‏‎‏‎‎‏‏‏‎‏‎‎‎‎‎‎‎Error starting screen recording‎‏‎‎‏‎"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‎‎‎‎‏‎‏‏‏‏‏‎‎‎‎‏‎‏‎‎‎‎‎‏‏‏‎‏‏‏‎‎‎‏‎‎‏‏‎‏‎‎‏‎‏‏‎‎‏‏‏‎‎‏‎‏‎‏‎Viewing full screen‎‏‎‎‏‎"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‏‏‏‏‏‏‎‎‏‏‎‏‎‏‎‎‎‎‏‏‏‏‎‎‏‎‏‎‏‎‏‎‎‎‏‎‎‏‏‏‏‏‏‎‏‎‎‏‎‎‏‏‏‎‏‏‏‏‎To exit, swipe down from the top.‎‏‎‎‏‎"</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‏‎‏‎‏‏‎‎‏‎‏‎‎‏‎‎‎‏‏‎‏‏‎‎‏‎‏‎‎‎‏‎‏‎‏‏‏‏‎‏‎‎‏‏‎‏‏‎‎‏‏‎‎‎‏‎‎‎‎Got it‎‏‎‎‏‎"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‏‎‏‎‎‏‏‏‏‏‏‎‎‏‎‏‏‏‎‏‎‏‏‎‏‎‏‎‎‏‏‎‎‏‎‏‎‏‏‎‎‏‏‎‏‏‎‏‎‏‏‎‏‎‎‎‏‏‎Back‎‏‎‎‏‎"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‏‏‎‏‎‏‏‏‎‎‏‏‎‏‎‏‎‏‎‏‎‏‏‎‏‏‏‏‎‏‏‏‎‏‏‎‎‏‎‎‎‎‏‎‏‏‏‏‎‏‏‎‎‎‏‎‎‏‎Home‎‏‎‎‏‎"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‎‏‎‏‏‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‏‎‏‎‎‎‏‏‏‎‎‎‎‏‏‎‎‎‏‏‏‏‏‎‏‎‏‎‎‏‏‎‎‏‎‎‎‎‎Menu‎‏‎‎‏‎"</string>
@@ -243,7 +246,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‏‎‏‏‎‏‎‏‎‎‎‏‎‎‏‏‎‏‎‎‎‏‎‏‎‏‏‎‎‎‎‏‏‏‏‏‎‎‏‏‏‎‏‎‎‎‏‏‎‏‎‏‎‏‏‎‎‏‎Audio‎‏‎‎‏‎"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‎‎‎‎‏‎‏‏‏‎‏‎‏‎‎‎‎‏‎‎‏‎‎‏‎‎‏‏‏‏‏‏‎‏‎‏‎‏‎‎‏‎‏‏‏‎‎‏‏‎‏‏‎‎‏‎‏‎‎‎Headset‎‏‎‎‏‎"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‎‏‏‏‏‏‎‎‏‏‎‏‎‏‏‏‎‎‏‎‎‏‏‎‏‎‎‏‎‏‎‎‏‎‏‎‏‏‎‏‎‎‎‏‏‏‏‎‎‎‏‏‏‎‎‏‎‎‎Input‎‏‎‎‏‎"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‎‏‏‎‏‎‏‏‏‎‎‎‎‎‎‎‎‎‏‎‎‎‎‏‎‎‏‏‎‎‏‏‎‎‏‎‎‏‏‏‏‏‏‎‎‏‎‎‏‎‎‎‏‎‎‏‎‏‎Hearing Aids‎‏‎‎‏‎"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‏‎‏‎‎‎‏‎‎‎‎‎‏‏‎‎‏‏‎‏‏‏‏‏‎‏‏‎‎‎‎‎‏‎‏‏‎‏‏‏‏‏‏‏‎‏‏‏‎‏‎‎‎‎‏‎‏‏‏‎Hearing aids‎‏‎‎‏‎"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‎‏‏‏‏‎‎‎‏‎‏‏‎‎‎‏‏‎‏‎‏‏‎‏‏‎‏‏‏‎‎‏‏‎‏‎‎‎‎‏‎‏‏‏‎‏‏‏‏‏‏‏‎‏‎‎‏‎‎Turning on…‎‏‎‎‏‎"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‎‎‎‏‏‎‎‎‎‎‎‎‎‏‎‎‎‎‏‎‏‎‎‎‎‎‏‎‎‎‎‏‎‎‏‎‎‎‎‏‎‎‏‎‏‏‏‎‏‏‏‏‎‎‎‎‎‎‎‎Auto-rotate‎‏‎‎‏‎"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‏‎‏‎‎‏‏‏‎‏‎‎‏‏‎‏‎‎‎‎‏‏‏‎‎‏‎‏‎‎‎‎‏‏‎‎‏‏‎‏‏‎‏‏‎‎‏‎‎‎‏‎‎‎‎‎‏‎‎Auto-rotate screen‎‏‎‎‏‎"</string>
@@ -412,6 +415,10 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‏‏‎‎‎‎‏‏‎‎‏‎‏‏‏‏‏‎‏‎‏‎‎‎‎‏‎‎‎‎‏‎‎‎‏‎‏‎‏‎‎‎‏‎‏‏‏‎‎‎‏‎‏‎‎‎‎‏‎When you’re sharing, recording, or casting, Android has access to anything visible on your screen or played on your device. So be careful with things like passwords, payment details, messages, photos, and audio and video.‎‏‎‎‏‎"</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‏‏‏‏‏‏‏‎‎‏‎‎‎‏‎‎‎‎‎‏‏‎‏‏‎‎‎‎‏‏‎‎‎‏‎‏‎‏‏‏‏‎‎‏‏‎‏‎‏‏‏‎‎‎‎‎‏‏‏‎When you’re sharing, recording, or casting an app, Android has access to anything shown or played on that app. So be careful with things like passwords, payment details, messages, photos, and audio and video.‎‏‎‎‏‎"</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‏‏‏‏‏‎‏‎‎‎‏‏‎‎‏‏‎‏‎‎‎‏‏‎‏‏‎‏‏‏‏‏‎‎‏‎‎‏‎‎‏‎‏‎‏‏‎‏‎‏‎‏‎‏‏‏‎‎‎Start‎‏‎‎‏‎"</string>
+    <string name="media_projection_task_switcher_text" msgid="590885489897412359">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‎‎‎‎‏‏‎‎‏‏‎‎‏‏‏‏‏‏‎‎‏‏‏‎‎‎‏‏‏‏‎‎‎‎‎‏‎‎‎‏‎‎‏‎‎‏‎‏‏‏‎‎‎‎‎‏‏‏‎Sharing pauses when you switch apps‎‏‎‎‏‎"</string>
+    <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‎‎‎‎‏‏‏‏‏‎‏‏‎‎‏‎‏‎‎‎‏‎‎‏‏‎‏‏‎‎‎‎‏‎‎‎‎‏‏‏‎‎‏‎‎‏‏‏‏‏‎‏‏‏‎‎‎‏‏‎Share this app instead‎‏‎‎‏‎"</string>
+    <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‎‏‏‏‏‎‎‎‏‏‎‎‏‏‏‎‏‏‎‎‏‎‎‏‏‏‎‎‏‏‏‎‏‎‎‎‏‎‏‏‎‎‏‏‏‏‎‎‎‎‏‎‏‎‎‎‏‎‎Switch back‎‏‎‎‏‎"</string>
+    <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‎‏‏‎‏‎‎‏‏‏‏‎‎‎‏‎‏‎‏‏‎‎‏‏‎‎‏‎‏‎‏‏‎‏‎‏‎‎‎‎‎‏‎‏‎‏‎‏‎‎‏‏‏‎‏‏‎‏‎App switch‎‏‎‎‏‎"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‎‏‎‏‎‏‎‏‎‎‎‎‎‏‎‎‎‎‎‎‎‏‎‎‎‏‎‏‏‎‎‏‏‎‏‎‎‎‏‎‏‏‏‎‏‏‏‎‏‏‎‏‏‎‎‎‏‏‎Blocked by your IT admin‎‏‎‎‏‎"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‏‏‎‏‏‏‏‏‎‏‎‎‎‎‏‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‎‎‎‏‏‎‏‏‏‎‎‏‎‎‎‎‏‎‎‏‎‏‏‏‏‎Screen capturing is disabled by device policy‎‏‎‎‏‎"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‏‏‎‎‏‏‎‏‎‏‎‏‎‏‏‏‎‏‎‎‎‏‎‎‎‎‎‏‎‏‎‏‏‏‏‎‏‏‎‎‏‎‎‏‏‎‎‏‎‎‎‏‏‏‏‏‎‎‏‎‎Clear all‎‏‎‎‏‎"</string>
@@ -853,6 +860,7 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‎‎‏‎‎‏‎‎‏‏‎‏‏‏‎‏‏‎‎‎‏‎‏‎‎‏‏‏‏‏‏‎‏‎‏‎‏‏‏‎‏‎‎‏‎‏‎‏‏‏‎‏‎‎‎‎‎‏‎Magnify full screen‎‏‎‎‏‎"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‏‏‎‏‎‎‏‏‏‏‎‎‎‎‏‎‎‏‎‎‏‎‎‎‎‎‏‎‏‎‏‎‏‏‎‏‏‏‏‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‎‏‎‏‎Magnify part of screen‎‏‎‎‏‎"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‎‏‎‏‎‏‏‏‏‏‏‏‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‏‏‎‎‎‏‎‎‏‎‏‏‏‎‎‎‏‏‏‎‎‎‏‎‎‎‏‏‎Open magnification settings‎‏‎‎‏‎"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‎‎‎‏‏‎‏‏‎‏‎‏‏‎‎‏‎‎‎‏‏‏‏‎‎‎‏‎‏‏‏‏‏‎‏‏‏‏‏‎‎‏‎‎‏‏‎‎‎‎‏‏‎‎‎‏‎‏‎Close magnification settings‎‏‎‎‏‎"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‎‎‏‎‏‎‏‏‎‎‎‎‎‎‎‏‏‏‏‏‏‎‏‎‏‏‎‏‎‎‏‏‏‏‎‏‎‎‎‎‏‏‏‏‎‎‏‏‏‎‎‎‏‎‏‎‎‏‎‎Drag corner to resize‎‏‎‎‏‎"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‏‎‏‎‎‏‏‎‏‏‎‏‏‏‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‏‏‏‏‎‎‏‎‏‏‎‎‎‎‎‏‎‏‏‎‎‏‏‏‎‎Allow diagonal scrolling‎‏‎‎‏‎"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‏‏‏‏‎‎‏‎‎‏‎‎‏‏‎‎‏‏‏‏‎‎‎‏‏‏‎‎‏‎‏‎‏‎‎‏‎‏‏‏‎‏‎‏‏‎‏‏‎‏‎‏‏‎‏‏‏‏‏‎Resize‎‏‎‎‏‎"</string>
@@ -1039,6 +1047,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‎‏‎‏‏‏‏‎‎‎‏‎‏‎‏‎‏‏‎‎‎‏‏‏‎‎‏‏‏‏‎‎‏‏‏‏‏‏‏‏‎‏‏‎‏‏‏‏‎‎‎‎‎‎‏‏‏‎‎Problem reading your battery meter‎‏‎‎‏‎"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‎‏‏‎‏‏‎‎‎‎‏‎‏‏‏‏‏‏‎‎‎‏‏‏‏‎‏‏‎‎‏‎‎‏‏‏‏‎‏‎‎‎‎‎‎‎‎‎‏‎‎‎‏‎‎‎‏‏‎Tap for more information‎‏‎‎‏‎"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‏‎‏‏‏‏‏‎‏‏‎‎‎‏‎‎‏‎‏‎‏‏‏‎‎‎‎‏‎‏‎‏‏‏‏‏‏‏‏‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‎‎‏‏‎No alarm set‎‏‎‎‏‎"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‎‏‏‏‎‏‎‏‏‎‎‎‎‏‎‏‎‎‏‏‎‏‏‎‎‏‎‏‎‏‎‎‎‎‎‎‏‎‎‏‎‏‎‎‏‏‏‏‏‎‏‎‎‎‎‏‏‎‎enter screen lock‎‏‎‎‏‎"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‎‎‏‏‏‏‎‎‎‎‎‎‎‏‏‎‏‏‏‎‏‏‎‎‏‏‎‏‎‎‎‎‎‏‏‎‎‎‏‎‏‏‏‏‏‎‏‎‏‏‏‎‏‏‏‏‎‎‎Fingerprint sensor‎‏‎‎‏‎"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‏‏‎‎‎‏‎‏‏‎‎‏‎‏‎‎‎‎‎‎‏‏‎‎‎‎‎‎‏‏‎‎‎‎‏‏‎‏‏‎‏‏‏‏‎‎‏‏‏‎‏‏‎‏‏‎‎‏‎authenticate‎‏‎‎‏‎"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‎‎‎‏‎‏‎‏‎‎‏‎‎‎‎‏‎‏‎‏‏‏‏‏‏‎‏‏‏‎‎‏‏‏‎‏‏‏‎‏‏‏‎‎‏‏‏‎‏‎‎‎‎‎‎‎‏‎‎enter device‎‏‎‎‏‎"</string>
@@ -1101,7 +1110,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‎‏‎‏‏‎‎‏‏‎‎‏‏‎‎‏‎‏‏‎‏‏‏‏‎‏‎‏‎‎‏‎‏‏‎‎‎‎‎‎‎‎‏‏‎‏‎‏‎‎‏‎‎‏‎‎‏‎‎Assistant is listening‎‏‎‎‏‎"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‎‎‎‏‎‎‏‎‏‎‎‏‏‎‎‎‏‏‏‎‏‎‏‎‎‎‎‏‏‏‎‏‎‎‏‏‏‎‎‎‎‏‎‎‎‏‎‏‏‏‏‏‏‎‏‏‏‏‎# notification‎‏‎‎‏‎}other{‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‎‎‎‏‎‎‏‎‏‎‎‏‏‎‎‎‏‏‏‎‏‎‏‎‎‎‎‏‏‏‎‏‎‎‏‏‏‎‎‎‎‏‎‎‎‏‎‏‏‏‏‏‏‎‏‏‏‏‎# notifications‎‏‎‎‏‎}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‏‏‎‏‏‏‎‎‎‏‎‎‏‏‏‎‎‏‏‎‏‏‎‏‏‎‎‏‎‏‎‏‏‎‏‏‎‏‏‏‏‎‏‏‎‎‏‎‏‎‎‎‎‏‎‎‎‎‎‎‏‎‎‏‏‎<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>‎‏‎‎‏‏‏‎, ‎‏‎‎‏‏‎<xliff:g id="TEMPERATURE">%2$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‎‎‎‏‏‏‏‏‏‏‎‏‎‏‏‏‏‏‏‎‎‏‎‎‏‎‏‎‏‎‏‏‎‏‏‎‎‏‎‏‏‏‎‏‎‏‏‏‎‏‎‎‏‏‎‎‎‎‎‎Notetaking‎‏‎‎‏‎"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‎‏‏‎‎‏‏‎‎‎‏‏‎‎‏‏‎‏‎‏‎‏‎‎‎‏‎‎‏‏‎‏‏‏‎‏‎‎‎‎‎‏‎‎‎‎‏‎‎‏‎‎‏‎‎‏‏‎‎‎Note-taking‎‏‎‎‏‎"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‏‏‎‏‎‎‎‏‎‎‎‎‎‏‎‏‎‎‎‎‏‏‎‏‏‏‏‏‏‏‏‎‏‏‎‏‎‏‏‎‎‏‏‎‎‎‏‎‎‎‎‏‏‏‎‎‎‏‎Note-taking, ‎‏‎‎‏‏‎<xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‏‎‏‎‏‎‎‎‎‎‎‎‎‎‏‏‏‎‎‎‏‎‏‎‎‎‎‏‎‎‎‏‎‏‏‏‎‏‏‎‏‎‎‏‎‏‏‏‏‏‏‏‎‎‎‎‏‎‎Broadcasting‎‏‎‎‏‎"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‎‎‎‎‏‏‏‎‏‏‏‎‏‎‎‎‏‏‏‎‏‎‏‎‏‏‎‏‏‏‎‎‎‏‏‏‏‎‎‏‏‎Stop broadcasting ‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎?‎‏‎‎‏‎"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‎‏‎‏‏‏‏‏‎‏‏‏‎‎‏‎‏‎‎‏‎‎‏‏‎‎‎‏‎‎‎‏‏‏‏‏‎‏‎‎‎‏‏‎‏‎‏‏‎‎‏‎‎‎‎‎‎‏‎If you broadcast ‎‏‎‎‏‏‎<xliff:g id="SWITCHAPP">%1$s</xliff:g>‎‏‎‎‏‏‏‎ or change the output, your current broadcast will stop‎‏‎‎‏‎"</string>
diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml
index 98f8c1d..a6edfd5 100644
--- a/packages/SystemUI/res/values-es-rUS/strings.xml
+++ b/packages/SystemUI/res/values-es-rUS/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Presiona para ver"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Se produjo un error al guardar la grabación de pantalla"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Error al iniciar la grabación de pantalla"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Visualización en pantalla completa"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Para salir, desliza el dedo hacia abajo desde la parte superior."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"Entendido"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Atrás"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Página principal"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Menú"</string>
@@ -243,7 +246,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Auriculares"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Entrada"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Audífonos"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Audífonos"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Activando…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Giro automático"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Girar la pantalla automáticamente"</string>
@@ -412,6 +415,14 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Cuando compartas, grabes o transmitas contenido, Android podrá acceder a todo lo que sea visible en la pantalla o que reproduzcas en el dispositivo. Por lo tanto, debes tener cuidado con contraseñas, detalles de pagos, mensajes, fotos, audios y videos."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Cuando compartas, grabes o transmitas una app, Android podrá acceder a todo el contenido que se muestre o que reproduzcas en ella. Por lo tanto, debes tener cuidado con contraseñas, detalles de pagos, mensajes, fotos, audios y videos."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Iniciar"</string>
+    <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+    <skip />
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Bloqueada por tu administrador de TI"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"La captura de pantalla está inhabilitada debido a la política del dispositivo"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Cerrar todo"</string>
@@ -853,6 +864,7 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Ampliar pantalla completa"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Ampliar parte de la pantalla"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Abrir la configuración de ampliación"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Cerrar configuración de ampliación"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Arrastra la esquina para cambiar el tamaño"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Desplazamiento en diagonal"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Cambiar tamaño"</string>
@@ -1039,6 +1051,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Problema al leer el medidor de batería"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Presiona para obtener más información"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"No establecida"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"ingresa el bloqueo pantalla"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Sensor de huellas dactilares"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"autenticar"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"ingresar al dispositivo"</string>
@@ -1101,7 +1114,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Asistente está escuchando"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# notificación}many{# notificaciones}other{# notificaciones}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Tomar notas"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Tomar notas"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Tomar notas, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Transmitiendo"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"¿Quieres dejar de transmitir <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Si transmites <xliff:g id="SWITCHAPP">%1$s</xliff:g> o cambias la salida, tu transmisión actual se detendrá"</string>
diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml
index 729d634..2fb76cd 100644
--- a/packages/SystemUI/res/values-es/strings.xml
+++ b/packages/SystemUI/res/values-es/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Toca para verla"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"No se ha podido guardar la grabación de pantalla"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"No se ha podido empezar a grabar la pantalla"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Modo de pantalla completa"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Para salir, desliza el dedo de arriba abajo."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"Entendido"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Atrás"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Inicio"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Menú"</string>
@@ -243,7 +246,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Auriculares"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Entrada"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Audífonos"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Audífonos"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Activando…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Giro automático"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Girar pantalla automáticamente"</string>
@@ -412,6 +415,10 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Cuando compartes, grabas o envías contenido, Android puede acceder a todo lo que se muestre en la pantalla o se reproduzca en tu dispositivo. Debes tener cuidado con elementos como contraseñas, detalles de pagos, mensajes, fotos, audio y vídeo."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Cuando compartes, grabas o envías una aplicación, Android puede acceder a todo lo que se muestre o se reproduzca en ella. Debes tener cuidado con elementos como contraseñas, detalles de pagos, mensajes, fotos, audio y vídeo."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Empezar"</string>
+    <string name="media_projection_task_switcher_text" msgid="590885489897412359">"El uso compartido se detiene al cambiar de aplicación"</string>
+    <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"Compartir esta aplicación"</string>
+    <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"Volver"</string>
+    <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"Cambio de aplicación"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Bloqueado por tu administrador de TI"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Las capturas de pantalla están inhabilitadas debido a la política de dispositivos"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Borrar todo"</string>
@@ -853,6 +860,7 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Ampliar pantalla completa"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Ampliar parte de la pantalla"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Abrir ajustes de ampliación"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Cerrar ajustes de ampliación"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Arrastra la esquina para cambiar el tamaño"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Permitir ir en diagonal"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Cambiar tamaño"</string>
@@ -1039,6 +1047,7 @@
     <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="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>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"acceder al dispositivo"</string>
@@ -1101,7 +1110,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"El Asistente está escuchando"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# notificación}many{# notificaciones}other{# notificaciones}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Tomar notas"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Toma de notas"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Toma de notas, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Emitiendo"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"¿Dejar de emitir <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Si emites <xliff:g id="SWITCHAPP">%1$s</xliff:g> o cambias la salida, tu emisión actual se detendrá"</string>
diff --git a/packages/SystemUI/res/values-et/strings.xml b/packages/SystemUI/res/values-et/strings.xml
index edb3830..b300e78 100644
--- a/packages/SystemUI/res/values-et/strings.xml
+++ b/packages/SystemUI/res/values-et/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Puudutage kuvamiseks"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Viga ekraanisalvestise salvestamisel"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Viga ekraanikuva salvestamise alustamisel"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Kuvamine täisekraanil"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Väljumiseks pühkige ülevalt alla."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"Selge"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Tagasi"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Kodu"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Menüü"</string>
@@ -243,7 +246,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Heli"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Peakomplekt"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Sisend"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Kuuldeaparaadid"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Kuuldeaparaadid"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Sisselülitamine …"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Autom. pööramine"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Kuva automaatne pööramine"</string>
@@ -412,6 +415,14 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Kui jagate, salvestate või kannate üle, on Androidil juurdepääs kõigele, mis on teie ekraanikuval nähtaval või mida teie seadmes esitatakse. Seega olge ettevaatlik selliste andmetega nagu paroolid, makseteave, sõnumid, fotod ning heli ja video."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Kui jagate, salvestate või kannate rakendust üle, on Androidil juurdepääs kõigele, mida selles rakenduses kuvatakse või esitatakse. Seega olge paroolide, makseteabe, sõnumite, fotode, heli ja videoga ettevaatlik."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Alusta"</string>
+    <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+    <skip />
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blokeeris teie IT-administraator"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Ekraanikuva jäädvustamine on seadmereeglitega keelatud"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Tühjenda kõik"</string>
@@ -853,6 +864,7 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Täisekraani suurendamine"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Ekraanikuva osa suurendamine"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Ava suurendamisseaded"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Sule suurendamisseaded"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Suuruse muutmiseks lohistage nurka"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Luba diagonaalne kerimine"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Muuda suurust"</string>
@@ -1039,6 +1051,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Probleem akumõõdiku lugemisel"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Puudutage lisateabe saamiseks"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Äratust pole"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"sisesta ekraanilukk"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Sõrmejäljeandur"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"autentimiseks"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"seadmesse sisenemiseks"</string>
@@ -1101,7 +1114,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Assistent kuulab"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# märguanne}other{# märguannet}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Märkmete tegemine"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Märkmete tegemine"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Märkmete tegemine <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Edastamine"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Kas peatada rakenduse <xliff:g id="APP_NAME">%1$s</xliff:g> ülekandmine?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Kui kannate rakendust <xliff:g id="SWITCHAPP">%1$s</xliff:g> üle või muudate väljundit, peatatakse teie praegune ülekanne"</string>
diff --git a/packages/SystemUI/res/values-eu/strings.xml b/packages/SystemUI/res/values-eu/strings.xml
index 545d2a2..8fd391c 100644
--- a/packages/SystemUI/res/values-eu/strings.xml
+++ b/packages/SystemUI/res/values-eu/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Sakatu ikusteko"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Errore bat gertatu da pantaila-grabaketa gordetzean"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Errore bat gertatu da pantaila grabatzen hastean"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Pantaila osoko ikuspegia"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Irteteko, pasatu hatza goitik behera."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"Ados"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Atzera"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Hasiera"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Menua"</string>
@@ -243,7 +246,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audioa"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Entzungailua"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Sarrera"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Audifonoak"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Audifonoak"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Aktibatzen…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Biratze automatikoa"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Biratu pantaila automatikoki"</string>
@@ -412,6 +415,14 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Edukia partekatzen, grabatzen edo igortzen ari zarenean, pantailan ikusgai dagoen edo gailuan erreproduzitzen ari den guztia atzi dezake Android-ek. Beraz, kontuz ibili pasahitzekin, ordainketen xehetasunekin, mezuekin, argazkiekin, audioekin eta bideoekin, besteak beste."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Aplikazio bat partekatzen, grabatzen edo igortzen ari zarenean, aplikazio horretan ikusgai dagoen edo bertan erreproduzitzen ari den guztia atzi dezake Android-ek. Beraz, kontuz ibili pasahitzekin, ordainketen xehetasunekin, mezuekin, argazkiekin, audioekin eta bideoekin, besteak beste."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Hasi"</string>
+    <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+    <skip />
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"IKT saileko administratzaileak blokeatu du"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Pantaila-kapturak egiteko aukera desgaituta dago, gailu-gidalerroei jarraikiz"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Garbitu guztiak"</string>
@@ -853,6 +864,7 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Handitu pantaila osoa"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Handitu pantailaren zati bat"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Ireki luparen ezarpenak"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Itxi luparen ezarpenak"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Arrastatu izkina bat tamaina aldatzeko"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Eman diagonalki gora eta behera egiteko aukera erabiltzeko baimena"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Aldatu tamaina"</string>
@@ -1039,6 +1051,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Arazo bat izan da bateria-neurgailua irakurtzean"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Informazio gehiago lortzeko, sakatu hau"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Ez da ezarri alarmarik"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"erabili pantailaren blokeoa"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Hatz-marken sentsorea"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"autentifikatu"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"sartu gailuan"</string>
@@ -1101,7 +1114,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Laguntzailea entzuten ari da"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# jakinarazpen}other{# jakinarazpen}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Oharrak idaztea"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Oharrak idaztea"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Oharrak idaztea, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Igortzen"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"<xliff:g id="APP_NAME">%1$s</xliff:g> aplikazioaren audioa igortzeari utzi nahi diozu?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> aplikazioaren audioa igortzen baduzu, edo audio-irteera aldatzen baduzu, une hartako igorpena eten egingo da"</string>
diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml
index 394bc39..4d3b3f0 100644
--- a/packages/SystemUI/res/values-fa/strings.xml
+++ b/packages/SystemUI/res/values-fa/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"برای مشاهده ضربه بزنید"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"خطا در ذخیره‌سازی ضبط صفحه‌نمایش"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"خطا هنگام شروع ضبط صفحه‌نمایش"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"درحال مشاهده در حالت تمام‌صفحه"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"برای خروج، از بالای صفحه تند به‌پایین بکشید."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"متوجه‌ام"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"برگشت"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"صفحهٔ اصلی"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"منو"</string>
@@ -243,7 +246,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"صوت"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"هدست"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"ورودی"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"سمعک"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"سمعک"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"روشن کردن…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"چرخش خودکار"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"چرخش خودکار صفحه‌نمایش"</string>
@@ -412,6 +415,14 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"‏وقتی درحال هم‌رسانی، ضبط، یا پخش محتوا هستید، Android به همه محتوایی که در صفحه‌تان نمایان است یا در دستگاهتان پخش می‌شود دسترسی دارد. درنتیجه مراقب چیزهایی مثل گذرواژه‌ها، جزئیات پرداخت، پیام‌ها، عکس‌ها، و صدا و تصویر باشید."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"‏وقتی درحال هم‌رسانی، ضبط، یا پخش محتوای برنامه‌ای هستید، Android به همه محتوایی که در آن برنامه نمایان است یا پخش می‌شود دسترسی دارد. درنتیجه مراقب چیزهایی مثل گذرواژه‌ها، جزئیات پرداخت، پیام‌ها، عکس‌ها، و صدا و تصویر باشید."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"شروع"</string>
+    <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+    <skip />
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"سرپرست فناوری اطلاعات آن را مسدود کرده است"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"«ضبط صفحه‌نمایش» به‌دلیل خط‌مشی دستگاه غیرفعال است"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"پاک کردن همه موارد"</string>
@@ -853,6 +864,7 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"درشت‌نمایی تمام‌صفحه"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"درشت‌نمایی بخشی از صفحه"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"باز کردن تنظیمات درشت‌نمایی"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"بستن تنظیمات درشت‌نمایی"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"برای تغییر اندازه، گوشه را بکشید"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"اجازه دادن برای پیمایش قطری"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"تغییر اندازه"</string>
@@ -1039,6 +1051,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"مشکلی در خواندن میزان باتری وجود دارد"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"برای اطلاعات بیشتر ضربه بزنید"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"هشداری تنظیم نشده"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"وارد کردن قفل صفحه"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"حسگر اثرانگشت"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"اصالت‌سنجی کردن"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"وارد شدن به دستگاه"</string>
@@ -1101,7 +1114,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"«دستیار» درحال گوش کردن است"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# اعلان}one{# اعلان}other{# اعلان}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>، <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"یادداشت‌برداری"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"یادداشت‌برداری"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"یادداشت‌برداری، <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"همه‌فرستی"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"همه‌فرستی <xliff:g id="APP_NAME">%1$s</xliff:g> متوقف شود؟"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"اگر <xliff:g id="SWITCHAPP">%1$s</xliff:g> را همه‌فرستی کنید یا خروجی را تغییر دهید، همه‌فرستی کنونی متوقف خواهد شد"</string>
diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml
index c005ec4..f3b95de 100644
--- a/packages/SystemUI/res/values-fi/strings.xml
+++ b/packages/SystemUI/res/values-fi/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Napauta näyttääksesi"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Virhe näyttötallenteen tallentamisessa"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Virhe näytön tallennuksen aloituksessa"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Koko näytön tilassa"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Sulje palkki pyyhkäisemällä alas ruudun ylälaidasta."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"Selvä"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Takaisin"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Aloitus"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Valikko"</string>
@@ -243,7 +246,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Ääni"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Headset"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Syöttölaite"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Kuulolaitteet"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Kuulolaitteet"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Otetaan käyttöön…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Automaattinen kääntö"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Käännä näyttöä automaattisesti."</string>
@@ -412,6 +415,14 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Kun jaat, tallennat tai striimaat, Android saa pääsyn kaikkeen näytölläsi näkyvään tai laitteellasi toistettuun sisältöön. Ole siis varovainen, kun lisäät salasanoja, maksutietoja, viestejä, kuvia, audiota tai videoita."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Kun jaat, tallennat tai striimaat, Android saa pääsyn kaikkeen sovelluksella näkyvään tai toistettuun sisältöön. Ole siis varovainen, kun lisäät salasanoja, maksutietoja, viestejä, kuvia, audiota tai videoita."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Aloita"</string>
+    <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+    <skip />
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"IT-järjestelmänvalvojasi estämä"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Kuvakaappaus on poistettu käytöstä laitekäytännön perusteella"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Tyhjennä kaikki"</string>
@@ -853,6 +864,7 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Koko näytön suurennus"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Suurenna osa näytöstä"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Avaa suurennusasetukset"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Sulje suurennusasetukset"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Muuta kokoa vetämällä kulmaa"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Salli diagonaalinen vierittäminen"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Muuta kokoa"</string>
@@ -1039,6 +1051,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Ongelma akkumittarin lukemisessa"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Saat lisätietoja napauttamalla"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Ei herätyksiä"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"käytä näytön lukitustapaa"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Sormenjälkitunnistin"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"todentaaksesi"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"avataksesi laitteen"</string>
@@ -1101,7 +1114,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Assistant kuuntelee"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# ilmoitus}other{# ilmoitusta}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Muistiinpanojen tekeminen"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Muistiinpanojen tekeminen"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Muistiinpanot, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Lähettää"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Lopetetaanko <xliff:g id="APP_NAME">%1$s</xliff:g>-sovelluksen lähettäminen?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Jos lähetät <xliff:g id="SWITCHAPP">%1$s</xliff:g>-sovellusta tai muutat ulostuloa, nykyinen lähetyksesi loppuu"</string>
diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml
index 5a629bc..8a74f8a 100644
--- a/packages/SystemUI/res/values-fr-rCA/strings.xml
+++ b/packages/SystemUI/res/values-fr-rCA/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Touchez pour afficher"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Erreur d\'enregistrement de l\'écran"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Une erreur s\'est produite lors du démarrage de l\'enregistrement d\'écran"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Affichage plein écran"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Pour quitter, balayez vers le bas à partir du haut."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"OK"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Précédent"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Domicile"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string>
@@ -243,7 +246,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Écouteurs"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Entrée"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Prothèses auditives"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Prothèses auditives"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Activation en cours…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Rotation automatique"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Rotation automatique de l\'écran"</string>
@@ -412,6 +415,14 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Lorsque vous partagez, enregistrez ou diffusez, Android a accès à tout ce qui est visible sur votre écran ou lu sur votre appareil. Par conséquent, soyez prudent avec les mots de passe, les détails du paiement, les messages, les photos et les contenus audio et vidéo."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Lorsque vous partagez, enregistrez ou diffusez une application, Android a accès à tout ce qui est visible sur votre écran ou lu sur cette application. Par conséquent, soyez prudent avec les mots de passe, les détails du paiement, les messages, les photos et les contenus audio et vidéo."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Commencer"</string>
+    <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+    <skip />
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Bloquée par votre administrateur informatique"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"La fonctionnalité de capture d\'écran est désactivée par l\'application Device Policy"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Tout effacer"</string>
@@ -853,6 +864,7 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Agrandir la totalité de l\'écran"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Agrandir une partie de l\'écran"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Ouvrir les paramètres d\'agrandissement"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Fermer les paramètres d\'agrandissement"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Faire glisser le coin pour redimensionner"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Autoriser défilement diagonal"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Redimensionner"</string>
@@ -1039,6 +1051,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Un problème est survenu lors de la lecture du niveau de charge de la pile"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Touchez pour en savoir plus"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Aucune alarme définie"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"entrer verrouillage de l\'écran"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Capteur d\'empreintes digitales"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"authentifier"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"accéder à l\'appareil"</string>
@@ -1101,7 +1114,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"L\'Assistant est à l\'écoute"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# notification}one{# notification}many{# de notifications}other{# notifications}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Prise de note"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Prise de note"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Prise de note, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Diffusion en cours…"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Arrêter la diffusion de <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Si vous diffusez <xliff:g id="SWITCHAPP">%1$s</xliff:g> ou changez la sortie, votre diffusion actuelle s\'arrêtera"</string>
diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml
index dc3bd0d..5fe8f55 100644
--- a/packages/SystemUI/res/values-fr/strings.xml
+++ b/packages/SystemUI/res/values-fr/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Appuyez pour afficher"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Erreur lors de l\'enregistrement de l\'écran"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Erreur lors du démarrage de l\'enregistrement de l\'écran"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Affichage en plein écran"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Pour quitter, balayez l\'écran du haut vers le bas."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"OK"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Retour"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Accueil"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string>
@@ -243,7 +246,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Casque"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Entrée"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Appareils auditifs"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Appareils auditifs"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Activation…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Rotation automatique"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Rotation automatique de l\'écran"</string>
@@ -412,6 +415,14 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Lorsque vous partagez, enregistrez ou castez, Android a accès à tout ce qui est visible sur votre écran ou lu sur votre appareil. Faites donc attention aux éléments tels que les mots de passe, détails de mode de paiement, messages, photos et contenus audio et vidéo."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Lorsque vous partagez, enregistrez ou castez une appli, Android a accès à tout ce qui est visible sur votre écran ou lu sur votre appareil. Faites donc attention aux éléments tels que les mots de passe, détails de mode de paiement, messages et contenus audio et vidéo."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Commencer"</string>
+    <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+    <skip />
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Bloqué par votre administrateur informatique"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"La capture d\'écran est désactivée conformément aux règles relatives à l\'appareil"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Tout effacer"</string>
@@ -853,6 +864,7 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Agrandir tout l\'écran"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Agrandir une partie de l\'écran"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Ouvrir les paramètres d\'agrandissement"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Fermer les paramètres d\'agrandissement"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Faire glisser le coin pour redimensionner"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Autoriser le défilement diagonal"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Redimensionner"</string>
@@ -1039,6 +1051,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Un problème est survenu au niveau de la lecture de votre outil de mesure de batterie"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Appuyer pour en savoir plus"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Pas d\'alarme définie"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"accéder au verrouillage de l\'écran"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Lecteur d\'empreinte digitale"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"s\'authentifier"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"accéder à l\'appareil"</string>
@@ -1101,7 +1114,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"L\'Assistant écoute"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# notification}one{# notification}many{# notifications}other{# notifications}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Prise de notes"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Prise de notes"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Prise de notes, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Diffusion…"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Arrêter la diffusion de <xliff:g id="APP_NAME">%1$s</xliff:g> ?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Si vous diffusez <xliff:g id="SWITCHAPP">%1$s</xliff:g> ou que vous modifiez le résultat, votre annonce actuelle s\'arrêtera"</string>
diff --git a/packages/SystemUI/res/values-gl/strings.xml b/packages/SystemUI/res/values-gl/strings.xml
index 453849a..804aeca 100644
--- a/packages/SystemUI/res/values-gl/strings.xml
+++ b/packages/SystemUI/res/values-gl/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Toca para ver o contido"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Produciuse un erro ao gardar a gravación da pantalla"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Produciuse un erro ao iniciar a gravación da pantalla"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Vendo pantalla completa"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Para saír, pasa o dedo cara abaixo desde a parte superior."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"Entendido"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Volver"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Inicio"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Menú"</string>
@@ -243,7 +246,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Auriculares"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Entrada"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Audiófonos"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Audiófonos"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Activando…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Xirar automaticamente"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Xirar pantalla automaticamente"</string>
@@ -412,6 +415,14 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Cando compartes, gravas ou emites contido, Android ten acceso a todo o que se vexa na pantalla ou se reproduza no teu dispositivo. Polo tanto, debes ter coidado con determinada información, como os contrasinais, os detalles de pago, as mensaxes e as fotos, así como o contido de audio e de vídeo."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Cando compartes, gravas ou emites unha aplicación, Android ten acceso a todo o que se vexa ou se reproduza nela. Polo tanto, debes ter coidado con determinada información, como os contrasinais, os detalles de pago, as mensaxes e as fotos, así como o contido de audio e de vídeo."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Iniciar"</string>
+    <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+    <skip />
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"O teu administrador de TI bloqueou esta aplicación"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"A política do dispositivo desactivou a opción de capturar a pantalla"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Eliminar todo"</string>
@@ -853,6 +864,7 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Ampliar pantalla completa"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Amplía parte da pantalla"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Abrir configuración da ampliación"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Pechar configuración de ampliación"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Arrastrar a esquina para cambiar o tamaño"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Permitir desprazamento diagonal"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Cambiar tamaño"</string>
@@ -1039,6 +1051,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Produciuse un problema ao ler o medidor da batería"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Toca para obter máis información"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Sen alarmas postas"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"introducir o bloqueo de pantalla"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Sensor de impresión dixital"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"autenticar"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"poñer o dispositivo"</string>
@@ -1101,7 +1114,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"O Asistente está escoitando"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# notificación}other{# notificacións}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Toma de notas"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Toma de notas"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Toma de notas (<xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>)"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Difusión"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Queres deixar de emitir contido a través de <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Se emites contido a través de <xliff:g id="SWITCHAPP">%1$s</xliff:g> ou cambias de saída, a emisión en curso deterase"</string>
diff --git a/packages/SystemUI/res/values-gu/strings.xml b/packages/SystemUI/res/values-gu/strings.xml
index ea4f871..cfd5a36 100644
--- a/packages/SystemUI/res/values-gu/strings.xml
+++ b/packages/SystemUI/res/values-gu/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"જોવા માટે ટૅપ કરો"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"સ્ક્રીન રેકોર્ડિંગ સાચવવામાં ભૂલ આવી"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"સ્ક્રીનને રેકૉર્ડ કરવાનું શરૂ કરવામાં ભૂલ"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"પૂર્ણ સ્ક્રીન પર જુઓ"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"બહાર નીકળવા માટે, ટોચ પરથી નીચે સ્વાઇપ કરો."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"સમજાઈ ગયું"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"પાછળ"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"હોમ"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"મેનુ"</string>
@@ -243,7 +246,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"ઑડિયો"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"હૅડસેટ"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"ઇનપુટ"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"શ્રવણ યંત્રો"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"સાંભળવામાં મદદ આપતા યંત્રો"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"ચાલુ કરી રહ્યાં છીએ…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"ઑટો રોટેટ"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"ઑટો રોટેટ સ્ક્રીન"</string>
@@ -412,6 +415,14 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"જ્યારે તમે શેર, રેકોર્ડ અથવા કાસ્ટ કરી રહ્યાં હો, ત્યારે તમારી સ્ક્રીન પર દેખાતી હોય કે તમારા ડિવાઇસ પર ચલાવવામાં આવતી હોય તેવી બધી વસ્તુનો ઍક્સેસ Android પાસે હોય છે. તેથી પાસવર્ડ, ચુકવણીની વિગતો, મેસેજ, ફોટા અને ડિવાઇસ પર વાગી રહેલા ઑડિયો તથા વીડિયો જેવી બાબતોને લઈને સાવચેત રહો."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"જ્યારે તમે કોઈ ઍપ શેર, રેકોર્ડ અથવા કાસ્ટ કરી રહ્યાં હો, ત્યારે તે ઍપ પર બતાવવામાં કે ચલાવવામાં આવતી હોય તેવી બધી વસ્તુનો ઍક્સેસ Android પાસે હોય છે. તેથી પાસવર્ડ, ચુકવણીની વિગતો, મેસેજ, ફોટા અને ડિવાઇસ પર વાગી રહેલા ઑડિયો તથા વીડિયો જેવી બાબતોને લઈને સાવચેત રહો."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"શરૂ કરો"</string>
+    <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+    <skip />
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"તમારા IT ઍડમિન દ્વારા બ્લૉક કરાયેલી"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"ડિવાઇસ પૉલિસી અનુસાર સ્ક્રીન કૅપ્ચર કરવાની સુવિધા બંધ કરવામાં આવી છે"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"બધુ સાફ કરો"</string>
@@ -853,6 +864,7 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"પૂર્ણ સ્ક્રીનને મોટી કરો"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"સ્ક્રીનનો કોઈ ભાગ મોટો કરો"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"મોટા કરવાના સેટિંગ ખોલો"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"મોટા કરવાના સેટિંગ બંધ કરો"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"કદ બદલવા માટે ખૂણો ખેંચો"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"ડાયગોનલ સ્ક્રોલિંગને મંજૂરી આપો"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"કદ બદલો"</string>
@@ -1039,6 +1051,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"તમારું બૅટરી મીટર વાંચવામાં સમસ્યા આવી"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"વધુ માહિતી માટે ટૅપ કરો"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"કોઈ અલાર્મ સેટ નથી"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"સ્ક્રીન લૉક દાખલ કરો"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"ફિંગરપ્રિન્ટ સેન્સર"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"ખાતરી કરો"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"ડિવાઇસ અનલૉક કરો"</string>
@@ -1101,7 +1114,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Assistant સાંભળી રહ્યું છે"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# નોટિફિકેશન}one{# નોટિફિકેશન}other{# નોટિફિકેશન}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"નોંધ લેવી"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"નોંધ લેવી"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"નોંધ લેવી, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"બ્રૉડકાસ્ટ કરી રહ્યાં છે"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"<xliff:g id="APP_NAME">%1$s</xliff:g> બ્રોડકાસ્ટ કરવાનું રોકીએ?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"જો તમે <xliff:g id="SWITCHAPP">%1$s</xliff:g> બ્રોડકાસ્ટ કરો અથવા આઉટપુટ બદલો, તો તમારું હાલનું બ્રોડકાસ્ટ બંધ થઈ જશે"</string>
diff --git a/packages/SystemUI/res/values-h800dp/dimens.xml b/packages/SystemUI/res/values-h800dp/dimens.xml
index 3a71994..829ef98 100644
--- a/packages/SystemUI/res/values-h800dp/dimens.xml
+++ b/packages/SystemUI/res/values-h800dp/dimens.xml
@@ -15,9 +15,6 @@
   -->
 
 <resources>
-    <!-- With the large clock, move up slightly from the center -->
-    <dimen name="keyguard_large_clock_top_margin">-112dp</dimen>
-
     <!-- Margin above the ambient indication container -->
     <dimen name="ambient_indication_container_margin_top">20dp</dimen>
 </resources>
diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml
index 2865c53..194321a 100644
--- a/packages/SystemUI/res/values-hi/strings.xml
+++ b/packages/SystemUI/res/values-hi/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"देखने के लिए टैप करें"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"स्क्रीन रिकॉर्डिंग सेव करते समय गड़बड़ी हुई"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"स्क्रीन को रिकॉर्ड करने में गड़बड़ी आ रही है"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"फ़ुल स्क्रीन मोड पर देखा जा रहा है"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"बाहर निकलने के लिए, सबसे ऊपर से नीचे की ओर स्वाइप करें."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"ठीक है"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"वापस जाएं"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"होम"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"मेन्यू"</string>
@@ -243,7 +246,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"ऑडियो"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"हेडसेट"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"इनपुट"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"कान की मशीन"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"कान की मशीनें"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"ब्लूटूथ चालू हो रहा है…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"ऑटो-रोटेट"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"स्क्रीन का अपने-आप दिशा बदलना (ऑटो-रोटेट)"</string>
@@ -412,6 +415,14 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"शेयर, रिकॉर्ड या कास्ट करते समय, Android के पास स्क्रीन पर दिख रहे कॉन्टेंट या डिवाइस पर चल रहे हर मीडिया का ऐक्सेस होता है. इसलिए, पासवर्ड, पेमेंट के तरीके की जानकारी, मैसेज, फ़ोटो, और डिवाइस पर चल रहे ऑडियो और वीडियो को लेकर सावधानी बरतें."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"किसी ऐप्लिकेशन को शेयर, रिकॉर्ड या कास्ट करते समय, Android के पास उस ऐप्लिकेशन पर दिख रहे कॉन्टेंट या उस पर चल रहे हर मीडिया का ऐक्सेस होता है. इसलिए, पासवर्ड, पेमेंट के तरीके की जानकारी, मैसेज, फ़ोटो, और डिवाइस पर चल रहे ऑडियो और वीडियो को लेकर सावधानी बरतें."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"शुरू करें"</string>
+    <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+    <skip />
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"आपके आईटी एडमिन ने स्क्रीन कैप्चर करने की सुविधा पर रोक लगाई है"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"डिवाइस से जुड़ी नीति के तहत स्क्रीन कैप्चर करने की सुविधा बंद है"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"सभी को हटाएं"</string>
@@ -853,6 +864,7 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"फ़ुल स्क्रीन को ज़ूम करें"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"स्क्रीन के किसी हिस्से को ज़ूम करें"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"ज़ूम करने की सुविधा वाली सेटिंग खोलें"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"ज़ूम करने की सुविधा वाली सेटिंग को बंद करें"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"साइज़ बदलने के लिए, कोने को खींचें और छोड़ें"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"तिरछी दिशा में स्क्रोल करने की अनुमति दें"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"साइज़ बदलें"</string>
@@ -1039,6 +1051,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"आपके डिवाइस के बैटरी मीटर की रीडिंग लेने में समस्या आ रही है"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"ज़्यादा जानकारी के लिए टैप करें"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"कोई अलार्म सेट नहीं है"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"स्क्रीन लॉक डालें"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"फ़िंगरप्रिंट सेंसर"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"पुष्टि करें"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"डिवाइस की होम स्क्रीन पर जाएं"</string>
@@ -1101,7 +1114,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Assistant सुन रही है"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# सूचना}one{# सूचना}other{# सूचनाएं}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"नोट बनाएं"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"नोट बनाएं"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"नोट लेने के लिए, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"ब्रॉडकास्ट ऐप्लिकेशन"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"<xliff:g id="APP_NAME">%1$s</xliff:g> पर ब्रॉडकास्ट करना रोकें?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> पर ब्रॉडकास्ट शुरू करने पर या आउटपुट बदलने पर, आपका मौजूदा ब्रॉडकास्ट बंद हो जाएगा"</string>
diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml
index c827355..7fbc100 100644
--- a/packages/SystemUI/res/values-hr/strings.xml
+++ b/packages/SystemUI/res/values-hr/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Dodirnite za prikaz"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Pogreška prilikom spremanja snimke zaslona"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Pogreška prilikom pokretanja snimanja zaslona"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Gledanje preko cijelog zaslona"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Za izlaz prijeđite prstom od vrha prema dolje."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"Shvaćam"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Natrag"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Početna"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Izbornik"</string>
@@ -243,7 +246,7 @@
     <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>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Unos"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Slušni aparati"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Slušna pomagala"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Uključivanje…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Autom. zakretanje"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Automatsko zakretanje zaslona"</string>
@@ -412,6 +415,14 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Kad dijelite, snimate ili emitirate, Android ima pristup svemu što je vidljivo na vašem zaslonu ili se reproducira na vašem uređaju. Stoga pazite na stvari kao što su zaporke, podaci o plaćanju, poruke, fotografije te audio i videozapisi."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Kad dijelite, snimate ili emitirate aplikaciju, Android ima pristup svemu što se prikazuje ili reproducira u toj aplikaciji. Stoga pazite na stvari kao što su zaporke, podaci o plaćanju, poruke, fotografije te audio i videozapisi."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Pokreni"</string>
+    <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+    <skip />
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blokirao vaš IT administrator"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Snimanje zaslona onemogućeno je u skladu s pravilima za uređaje"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Izbriši sve"</string>
@@ -853,6 +864,7 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Povećajte cijeli zaslon"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Povećaj dio zaslona"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Otvori postavke povećavanja"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Zatvori postavke povećavanja"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Povucite kut da biste promijenili veličinu"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Dopusti dijagonalno pomicanje"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Promijeni veličinu"</string>
@@ -1039,6 +1051,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Problem s očitavanjem mjerača baterije"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Dodirnite za više informacija"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Nema nijednog alarma"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"unesite zaključavanje zaslona"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Senzor otiska prsta"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"autentificirali"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"pristupili uređaju"</string>
@@ -1101,7 +1114,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Asistent sluša"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# obavijest}one{# obavijest}few{# obavijesti}other{# obavijesti}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Pisanje bilježaka"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Pisanje bilježaka"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Pisanje bilježaka, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Emitiranje"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Zaustaviti emitiranje aplikacije <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Ako emitirate aplikaciju <xliff:g id="SWITCHAPP">%1$s</xliff:g> ili promijenite izlaz, vaše će se trenutačno emitiranje zaustaviti"</string>
diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml
index 065a1db..1c46c44 100644
--- a/packages/SystemUI/res/values-hu/strings.xml
+++ b/packages/SystemUI/res/values-hu/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Koppintson a megtekintéshez"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Hiba történt a képernyőrögzítés mentése során"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Hiba a képernyőrögzítés indításakor"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Megtekintése teljes képernyőn"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Kilépéshez csúsztassa ujját fentről lefelé."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"Értem"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Vissza"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Főoldal"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Menü"</string>
@@ -243,7 +246,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Hang"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Headset"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Bevitel"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Hallókészülékek"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Hallókészülék"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Bekapcsolás…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Automatikus elforgatás"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Automatikus képernyőforgatás"</string>
@@ -412,6 +415,14 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Amikor Ön megosztást, rögzítést vagy átküldést végez, az Android a képernyőn látható vagy az eszközön lejátszott minden tartalomhoz hozzáfér. Ezért legyen elővigyázatos a jelszavakkal, a fizetési adatokkal, az üzenetekkel, a fotókkal, valamint a hang- és videófelvételekkel."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Amikor Ön megoszt, rögzít vagy átküld egy alkalmazást, az Android az adott alkalmazásban látható vagy lejátszott minden tartalomhoz hozzáfér. Ezért legyen elővigyázatos a jelszavakkal, a fizetési adatokkal, az üzenetekkel, a fotókkal, valamint a hang- és videófelvételekkel."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Indítás"</string>
+    <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+    <skip />
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Rendszergazda által letiltva"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"A képernyőfelvételt eszközszabályzat tiltja"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Az összes törlése"</string>
@@ -853,6 +864,7 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"A teljes képernyő felnagyítása"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Képernyő bizonyos részének nagyítása"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Nagyítási beállítások megnyitása"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Nagyítási beállítások bezárása"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Az átméretezéshez húzza a kívánt sarkot"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Átlós görgetés engedélyezése"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Átméretezés"</string>
@@ -1039,6 +1051,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Probléma merült fel az akkumulátor-töltésmérő olvasásakor"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Koppintással további információkat érhet el."</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Nincs ébresztés"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"képernyőzár megadása"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Ujjlenyomat-érzékelő"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"a hitelesítéshez"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"eszköz megadásához"</string>
@@ -1101,7 +1114,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"A Segéd figyel"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# értesítés}other{# értesítés}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Jegyzetelés"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Jegyzetelés"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Jegyzetelés, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Sugárzás"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Leállítja a(z) <xliff:g id="APP_NAME">%1$s</xliff:g> közvetítését?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"A(z) <xliff:g id="SWITCHAPP">%1$s</xliff:g> közvetítése vagy a kimenet módosítása esetén a jelenlegi közvetítés leáll"</string>
diff --git a/packages/SystemUI/res/values-hy/strings.xml b/packages/SystemUI/res/values-hy/strings.xml
index 0f72f07..afd8b66 100644
--- a/packages/SystemUI/res/values-hy/strings.xml
+++ b/packages/SystemUI/res/values-hy/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Հպեք՝ դիտելու համար"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Չհաջողվեց պահել էկրանի տեսագրությունը"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Չհաջողվեց սկսել տեսագրումը"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Լիաէկրան դիտում"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Դուրս գալու համար վերևից սահահարվածեք դեպի ներքև:"</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"Պարզ է"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Հետ"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Տուն"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Ցանկ"</string>
@@ -243,7 +246,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Աուդիո"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Ականջակալ"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Մուտքագրում"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Լսողական ապարատ"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Լսողական սարք"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Միացում…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Ինքնապտտում"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Ավտոմատ պտտել էկրանը"</string>
@@ -412,6 +415,14 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Երբ դուք ցուցադրում, տեսագրում կամ հեռարձակում եք էկրանը, Android-ին հասանելի է լինում այն ամենը, ինչ տեսանելի է ձեր էկրանին և նվագարկվում է ձեր սարքում։ Ուստի ուշադիր եղեք այնպիսի բաների հետ, ինչպիսիք են գաղտնաբառերը, վճարային տվյալները, հաղորդագրությունները, լուսանկարները, աուդիո և վիդեո բովանդակությունը։"</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Երբ դուք ցուցադրում, տեսագրում կամ հեռարձակում եք որևէ հավելվածի էկրանը, Android-ին հասանելի է լինում այն ամենը, ինչ ցուցադրվում է կամ նվագարկվում այդ հավելվածում։ Ուստի ուշադիր եղեք այնպիսի բաների հետ, ինչպիսիք են գաղտնաբառերը, վճարային տվյալները, հաղորդագրությունները, լուսանկարները, աուդիո և վիդեո բովանդակությունը։"</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Սկսել"</string>
+    <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+    <skip />
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Արգելափակվել է ձեր ՏՏ ադմինիստրատորի կողմից"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Էկրանի տեսագրումն անջատված է սարքի կանոնների համաձայն"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Մաքրել բոլորը"</string>
@@ -853,6 +864,7 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Խոշորացնել ամբողջ էկրանը"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Խոշորացնել էկրանի որոշակի հատվածը"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Բացել խոշորացման կարգավորումները"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Փակել խոշորացման կարգավորումները"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Քաշեք անկյունը՝ չափը փոխելու համար"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Թույլատրել անկյունագծով ոլորումը"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Փոխել չափը"</string>
@@ -1039,6 +1051,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Մարտկոցի ցուցիչի ցուցմունքը կարդալու հետ կապված խնդիր կա"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Հպեք՝ ավելին իմանալու համար"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Զարթուցիչ դրված չէ"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"ապակողպել էկրանը"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Մատնահետքի սկաներ"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"նույնականացնել"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"նշել սարքը"</string>
@@ -1101,7 +1114,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Օգնականը լսում է"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# ծանուցում}one{# ծանուցում}other{# ծանուցում}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Նշումների ստեղծում"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Նշումների ստեղծում"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Նշումների ստեղծում, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Հեռարձակում"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Կանգնեցնել <xliff:g id="APP_NAME">%1$s</xliff:g> հավելվածի հեռարձակումը"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Եթե հեռարձակեք <xliff:g id="SWITCHAPP">%1$s</xliff:g> հավելվածը կամ փոխեք աուդիո ելքը, ձեր ընթացիկ հեռարձակումը կկանգնեցվի։"</string>
diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml
index 6efb071..f5564ba 100644
--- a/packages/SystemUI/res/values-in/strings.xml
+++ b/packages/SystemUI/res/values-in/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Ketuk untuk melihat"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Terjadi error saat menyimpan rekaman layar"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Terjadi error saat memulai perekaman layar"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Melihat layar penuh"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Untuk keluar, geser layar ke bawah dari atas."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"Mengerti"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Kembali"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Utama"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string>
@@ -243,7 +246,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Headset"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Input"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Alat Bantu Dengar"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Alat bantu dengar"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Mengaktifkan…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Putar Otomatis"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Putar layar otomatis"</string>
@@ -412,6 +415,14 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Jika Anda membagikan, merekam, atau mentransmisikan, Android akan memiliki akses ke semua hal yang ditampilkan di layar atau yang diputar di perangkat Anda. Jadi, berhati-hatilah saat memasukkan sandi, detail pembayaran, pesan, foto, audio, dan video."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Jika Anda membagikan, merekam, atau mentransmisikan suatu aplikasi, Android akan memiliki akses ke semua hal yang ditampilkan atau yang diputar di aplikasi tersebut. Jadi, berhati-hatilah saat memasukkan sandi, detail pembayaran, pesan, foto, audio, dan video."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Mulai"</string>
+    <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+    <skip />
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Diblokir oleh admin IT Anda"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Pengambilan screenshot dinonaktifkan oleh kebijakan perangkat"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Hapus semua"</string>
@@ -853,6 +864,7 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Memperbesar tampilan layar penuh"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Perbesar sebagian layar"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Buka setelan pembesaran"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Tutup setelan pembesaran"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Tarik pojok persegi untuk mengubah ukuran"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Izinkan scrolling diagonal"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Ubah ukuran"</string>
@@ -1039,6 +1051,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Terjadi error saat membaca indikator baterai"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Ketuk untuk informasi selengkapnya"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Alarm tidak disetel"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"masukkan kunci layar"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Sensor sidik jari"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"autentikasi"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"masukkan perangkat"</string>
@@ -1101,7 +1114,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Assistant sedang mendengarkan"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# notifikasi}other{# notifikasi}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Pembuatan catatan"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Pembuatan catatan"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Pembuatan catatan, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Menyiarkan"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Hentikan siaran <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Jika Anda menyiarkan <xliff:g id="SWITCHAPP">%1$s</xliff:g> atau mengubah output, siaran saat ini akan dihentikan"</string>
diff --git a/packages/SystemUI/res/values-is/strings.xml b/packages/SystemUI/res/values-is/strings.xml
index 7de8cd1..647d1c4 100644
--- a/packages/SystemUI/res/values-is/strings.xml
+++ b/packages/SystemUI/res/values-is/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Ýttu til að skoða"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Villa við að vista skjáupptöku"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Villa við að hefja upptöku skjás"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Notar allan skjáinn"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Strjúktu niður frá efri brún til að hætta."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"Ég skil"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Til baka"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Heim"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Valmynd"</string>
@@ -243,7 +246,7 @@
     <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>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Inntak"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Heyrnartæki"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Heyrnartæki"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Kveikir…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Sjálfvirkur snúningur"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Snúa skjá sjálfkrafa"</string>
@@ -412,6 +415,14 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Þegar þú deilir, tekur upp eða varpar hefur Android aðgang að öllu sem sést á skjánum eða spilast í tækinu. Passaðu því upp á aðgangsorð, greiðsluupplýsingar, skilaboð, myndir, hljóð og myndskeið."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Þegar þú deilir, tekur upp eða varpar forriti hefur Android aðgang að öllu sem sést eða spilast í viðkomandi forriti. Passaðu því upp á aðgangsorð, greiðsluupplýsingar, skilaboð, myndir, hljóð og myndskeið."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Byrja"</string>
+    <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+    <skip />
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Útilokað af kerfisstjóra"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Slökkt er á skjáupptöku í tækjareglum"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Hreinsa allt"</string>
@@ -853,6 +864,7 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Stækka allan skjáinn"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Stækka hluta skjásins"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Opna stillingar stækkunar"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Loka stillingum stækkunar"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Dragðu horn til að breyta stærð"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Leyfa skáflettingu"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Breyta stærð"</string>
@@ -1039,6 +1051,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Vandamál við að lesa stöðu rafhlöðu"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Ýttu til að fá frekari upplýsingar"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Enginn vekjari"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"sláðu inn skjálás"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Fingrafaralesari"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"auðkenna"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"opna tæki"</string>
@@ -1101,7 +1114,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Hjálparinn er að hlusta"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# tilkynning}one{# tilkynning}other{# tilkynningar}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Glósugerð"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Glósugerð"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Glósugerð, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Útsending í gangi"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Hætta að senda út <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Ef þú sendir út <xliff:g id="SWITCHAPP">%1$s</xliff:g> eða skiptir um úttak lýkur yfirstandandi útsendingu"</string>
diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml
index 6accfd1..3b60f42 100644
--- a/packages/SystemUI/res/values-it/strings.xml
+++ b/packages/SystemUI/res/values-it/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Tocca per visualizzare"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Errore durante il salvataggio della registrazione dello schermo"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Errore durante l\'avvio della registrazione dello schermo"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Visualizzazione a schermo intero"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Per uscire, scorri dall\'alto verso il basso."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"OK"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Indietro"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Home"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string>
@@ -243,7 +246,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Auricolare"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Ingresso"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Apparecchi acustici"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Apparecchi acustici"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Attivazione…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Rotazione automatica"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Rotazione automatica dello schermo"</string>
@@ -412,6 +415,14 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Quando condividi, registri o trasmetti, Android ha accesso a qualsiasi elemento visibile sul tuo schermo o in riproduzione sul tuo dispositivo. Presta quindi attenzione a password, dettagli sui pagamenti, messaggi, foto, audio e video."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Quando condividi, registri o trasmetti un\'app, Android ha accesso a qualsiasi elemento visualizzato o riprodotto sull\'app. Presta quindi attenzione a password, dettagli sui pagamenti, messaggi, foto, audio e video."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Inizia"</string>
+    <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+    <skip />
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Bloccata dall\'amministratore IT"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"L\'acquisizione schermo è disattivata dai criteri relativi ai dispositivi"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Cancella tutto"</string>
@@ -853,6 +864,7 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Ingrandisci l\'intero schermo"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Ingrandisci parte dello schermo"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Apri le impostazioni di ingrandimento"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Chiudi impostazioni di ingrandimento"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Trascina l\'angolo per ridimensionare"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Scorrimento diagonale"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Ridimensiona"</string>
@@ -1039,6 +1051,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Problema durante la lettura dell\'indicatore di livello della batteria"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Tocca per ulteriori informazioni"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Nessuna"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"inserisci blocco schermo"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Sensore di impronte digitali"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"effettuare l\'autenticazione"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"accedere al dispositivo"</string>
@@ -1101,7 +1114,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"L\'assistente è in ascolto"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# notifica}many{# notifiche}other{# notifiche}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Aggiunta di note"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Aggiunta di note"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Aggiunta di note, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Trasmissione in corso…"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Vuoi interrompere la trasmissione dell\'app <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Se trasmetti l\'app <xliff:g id="SWITCHAPP">%1$s</xliff:g> o cambi l\'uscita, la trasmissione attuale viene interrotta"</string>
diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml
index 4226d92..121e5be 100644
--- a/packages/SystemUI/res/values-iw/strings.xml
+++ b/packages/SystemUI/res/values-iw/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"יש להקיש כדי להציג"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"שגיאה בשמירה של הקלטת המסך"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"שגיאה בהפעלה של הקלטת המסך"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"צפייה במסך מלא"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"כדי לצאת, פשוט מחליקים אצבע מלמעלה למטה."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"הבנתי"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"חזרה"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"בית"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"תפריט"</string>
@@ -243,7 +246,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"אודיו"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"אוזניות"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"קלט"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"מכשירי שמיעה"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"מכשירי שמיעה"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"ההפעלה מתבצעת…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"סיבוב אוטומטי"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"סיבוב אוטומטי של המסך"</string>
@@ -412,6 +415,14 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"‏בזמן שיתוף, הקלטה או העברה (cast) תהיה ל-Android גישה לכל הפרטים שגלויים במסך שלך או מופעלים מהמכשיר שלך. מומלץ להיזהר עם סיסמאות, פרטי תשלום, הודעות, תמונות, אודיו וסרטונים."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"‏בזמן שיתוף, הקלטה או העברה (cast) של אפליקציה, תהיה ל-Android גישה לכל מה שגלוי באפליקציה או מופעל מהאפליקציה. מומלץ להיזהר עם סיסמאות, פרטי תשלום, הודעות, תמונות, אודיו וסרטונים."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"התחלה"</string>
+    <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+    <skip />
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"‏האפשרות נחסמה על ידי אדמין ב-IT"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"צילום המסך מושבת בגלל מדיניות המכשיר"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"ניקוי הכול"</string>
@@ -853,6 +864,7 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"הגדלה של המסך המלא"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"הגדלת חלק מהמסך"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"פתיחת הגדרות ההגדלה"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"סגירת הגדרות ההגדלה"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"צריך לגרור את הפינה כדי לשנות את הגודל"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"הפעלת גלילה באלכסון"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"שינוי גודל"</string>
@@ -1039,6 +1051,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"בעיה בקריאת מדדי הסוללה"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"יש להקיש כדי להציג מידע נוסף"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"לא הוגדרה"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"הזנת קוד נעילת המסך"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"חיישן טביעות אצבע"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"אימות"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"הזנת מכשיר"</string>
@@ -1101,7 +1114,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"‏Assistant מאזינה"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{התראה אחת}one{# התראות}two{# התראות}other{# התראות}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"כתיבת הערות"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"כתיבת הערות"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"כתיבת הערות, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"שידור"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"האם להפסיק לשדר את התוכן מאפליקציית <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"אם משדרים את התוכן מאפליקציית <xliff:g id="SWITCHAPP">%1$s</xliff:g> או משנים את הפלט, השידור הנוכחי יפסיק לפעול"</string>
diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml
index 533fc4d..383d9f4 100644
--- a/packages/SystemUI/res/values-ja/strings.xml
+++ b/packages/SystemUI/res/values-ja/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"タップすると表示されます"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"画面の録画の保存中にエラーが発生しました"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"画面の録画中にエラーが発生しました"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"全画面表示"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"終了するには、上から下にスワイプします。"</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"OK"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"戻る"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"ホーム"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"メニュー"</string>
@@ -243,7 +246,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"オーディオ"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"ヘッドセット"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"入力"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"補聴器"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"補聴器"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"ON にしています…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"自動回転"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"画面を自動回転します"</string>
@@ -412,6 +415,14 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"共有、録画、キャスト中は、画面に表示される内容やデバイスで再生される内容に Android がアクセスできるため、パスワード、お支払いの詳細、メッセージ、写真、音声、動画などの情報にご注意ください。"</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"アプリの共有、録画、キャスト中は、そのアプリで表示または再生される内容に Android がアクセスできるため、パスワード、お支払いの詳細、メッセージ、写真、音声、動画などの情報にご注意ください。"</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"開始"</string>
+    <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+    <skip />
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"IT 管理者によりブロックされました"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"デバイス ポリシーに基づき、画面のキャプチャが無効になりました"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"すべて消去"</string>
@@ -853,6 +864,7 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"画面全体を拡大します"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"画面の一部を拡大します"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"画面の拡大設定を開く"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"拡大の設定を閉じる"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"サイズを変更するには角をドラッグ"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"斜めスクロールを許可"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"サイズ変更"</string>
@@ -1039,6 +1051,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"バッテリー残量の読み込み中に問題が発生しました"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"タップすると詳細が表示されます"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"アラーム未設定"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"画面ロックを設定"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"指紋認証センサー"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"認証"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"デバイスを入力"</string>
@@ -1101,7 +1114,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"アシスタントが音声認識中です"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# 件の通知}other{# 件の通知}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>、<xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"メモ"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"メモ"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"メモ、<xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"ブロードキャスト"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"<xliff:g id="APP_NAME">%1$s</xliff:g> のブロードキャストを停止しますか?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> をブロードキャストしたり、出力を変更したりすると、現在のブロードキャストが停止します。"</string>
diff --git a/packages/SystemUI/res/values-ka/strings.xml b/packages/SystemUI/res/values-ka/strings.xml
index 7caf170..943b2885 100644
--- a/packages/SystemUI/res/values-ka/strings.xml
+++ b/packages/SystemUI/res/values-ka/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"შეეხეთ სანახავად"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"ეკრანის ჩანაწერის შენახვისას შეცდომა მოხდა"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"ეკრანის ჩაწერის დაწყებისას წარმოიქმნა შეცდომა"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"მიმდინარეობს სრულ ეკრანზე ნახვა"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"გასვლისთვის გადაფურცლეთ ზემოდან ქვემოთ."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"გასაგებია"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"უკან"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"საწყისი"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"მენიუ"</string>
@@ -243,7 +246,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"აუდიო"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"ყურსაცვამი"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"შეყვანა"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"სმენის მოწყობილობები"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"სმენის მოწყობილობები"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"ირთვება…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"ავტოროტაცია"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"ეკრანის ავტომატური შეტრიალება"</string>
@@ -412,6 +415,14 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"გაზიარებისას, ჩაწერისას ან ტრანსლირებისას, Android-ს წვდომა აქვს ყველაფერზე, რაც ჩანს თქვენს ეკრანზე ან უკრავს თქვენს მოწყობილობაზე. ამიტომ იყავით ფრთხილად ისეთ ინფორმაციასთან, როგორიცაა პაროლები, გადახდის დეტალები, შეტყობინებები, ფოტოები, აუდიო და ვიდეო."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"აპის გაზიარებისას, ჩაწერისას ან ტრანსლირებისას, Android-ს წვდომა აქვს ყველაფერზე, რაც ჩანს ან იკვრება აპში. ამიტომ იყავით ფრთხილად ისეთ ინფორმაციასთან, როგორიცაა პაროლები, გადახდის დეტალები, შეტყობინებები, ფოტოები, აუდიო და ვიდეო."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"დაწყება"</string>
+    <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+    <skip />
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"დაბლოკილია თქვენი IT-ადმინისტრატორის მიერ"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"ეკრანის აღბეჭდვა გამორთულია მოწყობილობის წესების თანახმად"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"ყველას გასუფთავება"</string>
@@ -853,6 +864,7 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"გაადიდეთ სრულ ეკრანზე"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"ეკრანის ნაწილის გადიდება"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"გახსენით გადიდების პარამეტრები"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"გადიდების პარამეტრების დახურვა"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"ჩავლებით გადაიტანეთ კუთხე ზომის შესაცვლელად"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"დიაგონალური გადაადგილების დაშვება"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"ზომის შეცვლა"</string>
@@ -1039,6 +1051,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"თქვენი ბატარეის მზომის წაკითხვასთან დაკავშირებით პრობლემა დაფიქსირდა"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"შეეხეთ მეტი ინფორმაციისთვის"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"მაღვიძარა არ არის"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"ეკრანის დაბლოკვის შეყვანა"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"თითის ანაბეჭდის სენსორი"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"ავტორიზაცია"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"მოწყობილობის შეყვანა"</string>
@@ -1101,7 +1114,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"ასისტენტი უსმენს"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# შეტყობინება}other{# შეტყობინება}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"შენიშვნების ჩაწერა"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"ჩანიშვნა"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"ჩანიშვნა, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"იწყებთ მაუწყებლობას"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"გსურთ <xliff:g id="APP_NAME">%1$s</xliff:g>-ის ტრანსლაციის შეჩერება?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"<xliff:g id="SWITCHAPP">%1$s</xliff:g>-ის ტრანსლაციის შემთხვევაში ან აუდიოს გამოსასვლელის შეცვლისას, მიმდინარე ტრანსლაცია შეჩერდება"</string>
diff --git a/packages/SystemUI/res/values-kk/strings.xml b/packages/SystemUI/res/values-kk/strings.xml
index 8f60d73..a80dcfb 100644
--- a/packages/SystemUI/res/values-kk/strings.xml
+++ b/packages/SystemUI/res/values-kk/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Көру үшін түртіңіз."</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Экран жазбасын сақтау кезінде қате шықты."</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Экрандағы бейнені жазу кезінде қате шықты."</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Толық экранда көру"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Шығу үшін жоғарыдан төмен қарай сырғытыңыз."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"Түсінікті"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Артқа"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Үй"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Mәзір"</string>
@@ -243,7 +246,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Aудио"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Гарнитура"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Кіріс"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Есту аппараттары"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Есту аппараттары"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Қосылуда…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Автоматты түрде бұру"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Автоматты айналатын экран"</string>
@@ -412,6 +415,14 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Бөлісу, жазу не трансляциялау кезінде Android жүйесі экраныңызда көрінетін не құрылғыңызда ойнатылатын барлық нәрсені пайдалана алады. Сондықтан құпия сөздерді, төлем туралы мәліметті, хабарларды немесе басқа құпия ақпаратты енгізген кезде сақ болыңыз."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Қолданба экранын бөлісу, жазу не трансляциялау кезінде Android жүйесі онда көрінетін не ойнатылатын барлық нәрсені пайдалана алады. Сондықтан құпия сөздерді, төлем туралы мәліметті, хабарларды немесе басқа құпия ақпаратты енгізген кезде сақ болыңыз."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Бастау"</string>
+    <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+    <skip />
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Әкімшіңіз бөгеген"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Құрылғы саясатына байланысты экранды түсіру өшірілді."</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Барлығын тазарту"</string>
@@ -853,6 +864,7 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Толық экранды ұлғайту"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Экранның бөлігін ұлғайту"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Ұлғайту параметрлерін ашу"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Ұлғайту параметрлерін жабу"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Өлшемін өзгерту үшін бұрышынан сүйреңіз."</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Диагональ бойынша айналдыруға рұқсат беру"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Өлшемін өзгерту"</string>
@@ -1039,6 +1051,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Батарея зарядының дерегі алынбай жатыр"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Толығырақ ақпарат алу үшін түртіңіз."</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Оятқыш орнатылмаған."</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"экран құлпын енгізу"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Саусақ ізін оқу сканері"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"аутентификациялау"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"құрылғыны енгізу"</string>
@@ -1101,7 +1114,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Assistant тыңдап тұр."</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# хабарландыру}other{# хабарландыру}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Ескертпе жазу"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Ескертпе жазу"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Ескертпе жазу, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Таратуда"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"<xliff:g id="APP_NAME">%1$s</xliff:g> қолданбасын таратуды тоқтатасыз ба?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> қолданбасын таратсаңыз немесе аудио шығысын өзгертсеңіз, қазіргі тарату сеансы тоқтайды."</string>
diff --git a/packages/SystemUI/res/values-km/strings.xml b/packages/SystemUI/res/values-km/strings.xml
index 097fc17..702b9cb 100644
--- a/packages/SystemUI/res/values-km/strings.xml
+++ b/packages/SystemUI/res/values-km/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"ចុចដើម្បីមើល"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"មានបញ្ហាក្នុងការរក្សាទុក​ការថតវីដេអូអេក្រង់"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"មានបញ្ហា​ក្នុងការ​ចាប់ផ្ដើម​ថត​អេក្រង់"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"កំពុងមើលពេញអេក្រង់"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"ដើម្បីចាកចេញ សូមអូសពីលើចុះក្រោម។"</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"យល់ហើយ"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"ថយក្រោយ"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"គេហ​ទំព័រ"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"ម៉ឺនុយ"</string>
@@ -243,7 +246,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"សំឡេង"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"កាស"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"បញ្ចូល"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"ឧបករណ៍​ជំនួយការ​ស្ដាប់"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"ឧបករណ៍ជំនួយការស្ដាប់"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"កំពុង​បើក..."</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"បង្វិល​ស្វ័យ​ប្រវត្តិ"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"បង្វិលអេក្រង់ស្វ័យប្រវត្តិ"</string>
@@ -412,6 +415,14 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"នៅពេលអ្នកកំពុងចែករំលែក ថត ឬភ្ជាប់, Android មានសិទ្ធិចូលប្រើអ្វីៗដែលអាចមើលឃើញនៅលើអេក្រង់របស់អ្នក ឬចាក់នៅលើឧបករណ៍របស់អ្នក។ ដូច្នេះ សូមប្រុងប្រយ័ត្នចំពោះអ្វីៗដូចជា ពាក្យសម្ងាត់ ព័ត៌មានលម្អិតអំពីការទូទាត់ប្រាក់ សារ រូបថត ព្រមទាំងសំឡេង និងវីដេអូ។"</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"នៅពេលអ្នកកំពុងចែករំលែក ថត ឬភ្ជាប់កម្មវិធី, Android មានសិទ្ធិចូលប្រើអ្វីៗដែលបង្ហាញ ឬចាក់នៅលើកម្មវិធីនោះ។ ដូច្នេះ សូមប្រុងប្រយ័ត្នចំពោះអ្វីៗដូចជា ពាក្យសម្ងាត់ ព័ត៌មានលម្អិតអំពីការទូទាត់ប្រាក់ សារ រូបថត ព្រមទាំងសំឡេង និងវីដេអូ។"</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"ចាប់ផ្ដើម"</string>
+    <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+    <skip />
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"បានទប់ស្កាត់ដោយអ្នកគ្រប់គ្រង​ផ្នែកព័ត៌មានវិទ្យា​របស់អ្នក"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"ការថតអេក្រង់ត្រូវបានបិទ​ដោយគោលការណ៍ឧបករណ៍"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"សម្អាត​ទាំងអស់"</string>
@@ -853,6 +864,7 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"ពង្រីក​ពេញអេក្រង់"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"ពង្រីក​ផ្នែកនៃ​អេក្រង់"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"បើកការកំណត់​ការពង្រីក"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"បិទការកំណត់ការពង្រីក"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"អូសជ្រុងដើម្បីប្ដូរទំហំ"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"អនុញ្ញាត​ការរំកិលបញ្ឆិត"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"ប្ដូរ​ទំហំ"</string>
@@ -1039,6 +1051,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"មានបញ្ហាក្នុង​ការអាន​ឧបករណ៍រង្វាស់កម្រិតថ្មរបស់អ្នក"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"ចុចដើម្បីទទួលបាន​ព័ត៌មានបន្ថែម"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"មិនបាន​កំណត់​ម៉ោងរោទ៍​ទេ"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"បញ្ចូលព័ត៌មានផ្ទៀងផ្ទាត់សម្រាប់ការចាក់សោអេក្រង់"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"ឧបករណ៍​ចាប់ស្នាមម្រាមដៃ"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"ផ្ទៀងផ្ទាត់"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"បញ្ចូល​ឧបករណ៍"</string>
@@ -1101,7 +1114,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Google Assistant កំពុងស្តាប់"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{ការ​ជូន​ដំណឹង #}other{ការ​ជូនដំណឹង #}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"ការកត់ត្រា​"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"ការកត់ត្រា"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"ការកត់ត្រា, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"ការផ្សាយ"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"បញ្ឈប់ការផ្សាយ <xliff:g id="APP_NAME">%1$s</xliff:g> ឬ?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"ប្រសិនបើអ្នក​ផ្សាយ <xliff:g id="SWITCHAPP">%1$s</xliff:g> ឬប្ដូរឧបករណ៍បញ្ចេញសំឡេង ការផ្សាយបច្ចុប្បន្នរបស់អ្នកនឹង​បញ្ឈប់"</string>
diff --git a/packages/SystemUI/res/values-kn/strings.xml b/packages/SystemUI/res/values-kn/strings.xml
index a7df978..544af6a 100644
--- a/packages/SystemUI/res/values-kn/strings.xml
+++ b/packages/SystemUI/res/values-kn/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"ವೀಕ್ಷಿಸಲು ಟ್ಯಾಪ್ ಮಾಡಿ"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"ಸ್ಕ್ರೀನ್ ರೆಕಾರ್ಡಿಂಗ್ ಸೇವ್‌ ಮಾಡುವಾಗ ದೋಷ ಎದುರಾಗಿದೆ"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"ಸ್ಕ್ರೀನ್ ರೆಕಾರ್ಡಿಂಗ್ ಪ್ರಾರಂಭಿಸುವಾಗ ದೋಷ ಕಂಡುಬಂದಿದೆ"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"ಪೂರ್ಣ ಸ್ಕ್ರೀನ್‌ನಲ್ಲಿ ವೀಕ್ಷಿಸಲಾಗುತ್ತಿದೆ"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"ನಿರ್ಗಮಿಸಲು, ಮೇಲಿನಿಂದ ಕೆಳಕ್ಕೆ ಸ್ವೈಪ್ ಮಾಡಿ."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"ಅರ್ಥವಾಯಿತು"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"ಹಿಂದೆ"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"ಮುಖಪುಟ"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"ಮೆನು"</string>
@@ -243,7 +246,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"ಆಡಿಯೋ"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"ಹೆಡ್‌ಸೆಟ್"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"ಇನ್‌ಪುಟ್"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"ಶ್ರವಣ ಸಾಧನಗಳು"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"ಶ್ರವಣ ಸಾಧನಗಳು"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"ಆನ್ ಮಾಡಲಾಗುತ್ತಿದೆ..."</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"ಸ್ವಯಂ-ತಿರುಗುವಿಕೆ"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"ಪರದೆಯನ್ನು ಸ್ವಯಂ-ತಿರುಗಿಸಿ"</string>
@@ -412,6 +415,14 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"ನೀವು ಹಂಚಿಕೊಳ್ಳುತ್ತಿರುವಾಗ, ರೆಕಾರ್ಡಿಂಗ್ ಮಾಡುತ್ತಿರುವಾಗ ಅಥವಾ ಕ್ಯಾಸ್ಟ್ ಮಾಡುತ್ತಿರುವಾಗ, ನಿಮ್ಮ ಸ್ಕ್ರೀನ್ ಮೇಲೆ ಕಾಣಿಸುವ ಅಥವಾ ನಿಮ್ಮ ಸಾಧನದಲ್ಲಿ ಪ್ಲೇ ಮಾಡುವ ಯಾವುದೇ ವಿಷಯಕ್ಕೆ Android ಆ್ಯಕ್ಸೆಸ್ ಅನ್ನು ಹೊಂದಿರುತ್ತದೆ. ಆದ್ದರಿಂದ ಪಾಸ್‌ವರ್ಡ್‌ಗಳು, ಪಾವತಿ ವಿವರಗಳು, ಸಂದೇಶಗಳು, ಫೋಟೋಗಳು ಹಾಗೂ ಆಡಿಯೊ ಮತ್ತು ವೀಡಿಯೊದಂತಹ ವಿಷಯಗಳ ಕುರಿತು ಜಾಗರೂಕರಾಗಿರಿ."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"ನೀವು ಆ್ಯಪ್ ಅನ್ನು ಹಂಚಿಕೊಳ್ಳುತ್ತಿರುವಾಗ, ರೆಕಾರ್ಡಿಂಗ್ ಮಾಡುತ್ತಿರುವಾಗ ಅಥವಾ ಕ್ಯಾಸ್ಟ್ ಮಾಡುತ್ತಿರುವಾಗ, ಆ ಆ್ಯಪ್‌ನಲ್ಲಿ ತೋರಿಸುವ ಅಥವಾ ಪ್ಲೇ ಮಾಡುವ ಯಾವುದೇ ವಿಷಯಕ್ಕೆ Android ಆ್ಯಕ್ಸೆಸ್ ಅನ್ನು ಹೊಂದಿರುತ್ತದೆ. ಆದ್ದರಿಂದ ಪಾಸ್‌ವರ್ಡ್‌ಗಳು, ಪಾವತಿ ವಿವರಗಳು, ಸಂದೇಶಗಳು, ಫೋಟೋಗಳು ಹಾಗೂ ಆಡಿಯೊ ಮತ್ತು ವೀಡಿಯೊದಂತಹ ವಿಷಯಗಳ ಕುರಿತು ಜಾಗರೂಕರಾಗಿರಿ."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"ಪ್ರಾರಂಭಿಸಿ"</string>
+    <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+    <skip />
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"ನಿಮ್ಮ IT ನಿರ್ವಾಹಕರು ನಿರ್ಬಂಧಿಸಿದ್ದಾರೆ"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"ಸಾಧನ ನೀತಿಯಿಂದ ಸ್ಕ್ರೀನ್ ಕ್ಯಾಪ್ಚರಿಂಗ್ ಅನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"ಎಲ್ಲವನ್ನೂ ತೆರವುಗೊಳಿಸಿ"</string>
@@ -853,6 +864,7 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"ಪೂರ್ಣ ಸ್ಕ್ರೀನ್‌ ಅನ್ನು ಹಿಗ್ಗಿಸಿ"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"ಸ್ಕ್ರೀನ್‌ನ ಅರ್ಧಭಾಗವನ್ನು ಝೂಮ್ ಮಾಡಿ"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"ಹಿಗ್ಗಿಸುವಿಕೆ ಸೆಟ್ಟಿಂಗ್‌ಗಳನ್ನು ತೆರೆಯಿರಿ"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"ಮ್ಯಾಗ್ನಿಫಿಕೇಶನ್ ಸೆಟ್ಟಿಂಗ್‌ಗಳನ್ನು ಮುಚ್ಚಿರಿ"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"ಮರುಗಾತ್ರಗೊಳಿಸಲು ಮೂಲೆಯನ್ನು ಡ್ರ್ಯಾಗ್ ಮಾಡಿ"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"ಡಯಾಗನಲ್ ಸ್ಕ್ರೋಲಿಂಗ್ ಅನ್ನು ಅನುಮತಿಸಿ"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"ಮರುಗಾತ್ರಗೊಳಿಸಿ"</string>
@@ -1039,6 +1051,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"ನಿಮ್ಮ ಬ್ಯಾಟರಿ ಮೀಟರ್ ಓದುವಾಗ ಸಮಸ್ಯೆ ಎದುರಾಗಿದೆ"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"ಇನ್ನಷ್ಟು ಮಾಹಿತಿಗಾಗಿ ಟ್ಯಾಪ್ ಮಾಡಿ"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"ಅಲಾರಾಂ ಸೆಟ್ ಆಗಿಲ್ಲ"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"ಸ್ಕ್ರೀನ್ ಲಾಕ್ ನಮೂದಿಸಿ"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"ಫಿಂಗರ್‌ಪ್ರಿಂಟ್ ಸೆನ್ಸರ್"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"ದೃಢೀಕರಿಸಿ"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"ಸಾಧನವನ್ನು ಪ್ರವೇಶಿಸಿ"</string>
@@ -1101,7 +1114,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"ಅಸಿಸ್ಟೆಂಟ್ ಆಲಿಸುತ್ತಿದೆ"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# ನೋಟಿಫಿಕೇಶನ್}one{# ನೋಟಿಫಿಕೇಶನ್‌ಗಳು}other{# ನೋಟಿಫಿಕೇಶನ್‌ಗಳು}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"ಟಿಪ್ಪಣಿಗಳನ್ನು ಬರೆದುಕೊಳ್ಳುವುದು"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"ಟಿಪ್ಪಣಿ ಮಾಡಿಕೊಳ್ಳುವಿಕೆ"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"ಟಿಪ್ಪಣಿ ಮಾಡಿಕೊಳ್ಳುವಿಕೆ, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"ಪ್ರಸಾರ ಮಾಡಲಾಗುತ್ತಿದೆ"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"<xliff:g id="APP_NAME">%1$s</xliff:g> ನ ಪ್ರಸಾರವನ್ನು ನಿಲ್ಲಿಸಬೇಕೆ?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"ನೀವು <xliff:g id="SWITCHAPP">%1$s</xliff:g> ಅನ್ನು ಪ್ರಸಾರ ಮಾಡಿದರೆ ಅಥವಾ ಔಟ್‌ಪುಟ್ ಅನ್ನು ಬದಲಾಯಿಸಿದರೆ, ನಿಮ್ಮ ಪ್ರಸ್ತುತ ಪ್ರಸಾರವು ಸ್ಥಗಿತಗೊಳ್ಳುತ್ತದೆ"</string>
diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml
index db9ffcf..bd58c0f 100644
--- a/packages/SystemUI/res/values-ko/strings.xml
+++ b/packages/SystemUI/res/values-ko/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"탭하여 보기"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"화면 녹화 저장 중에 오류가 발생했습니다."</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"화면 녹화 시작 중 오류 발생"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"전체 화면 모드"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"종료하려면 위에서 아래로 스와이프합니다."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"확인"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"뒤로"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"홈"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"메뉴"</string>
@@ -243,7 +246,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"오디오"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"헤드셋"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"입력"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"보청기"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"보청기"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"켜는 중..."</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"자동 회전"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"화면 자동 회전"</string>
@@ -412,6 +415,14 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"공유, 녹화 또는 전송 중에 Android가 화면에 표시되거나 기기에서 재생되는 모든 항목에 액세스할 수 있습니다. 따라서 비밀번호, 결제 세부정보, 메시지, 사진, 오디오 및 동영상 등이 노출되지 않도록 주의하세요."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"앱을 공유, 녹화 또는 전송할 때는 Android가 해당 앱에 표시되거나 재생되는 모든 항목에 액세스할 수 있으므로 비밀번호, 결제 세부정보, 메시지, 사진, 오디오 및 동영상 등이 노출되지 않도록 주의하세요."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"시작"</string>
+    <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+    <skip />
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"IT 관리자에 의해 차단됨"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"기기 정책에 의해 화면 캡처가 사용 중지되었습니다."</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"모두 지우기"</string>
@@ -853,6 +864,7 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"전체 화면 확대"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"화면 일부 확대"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"확대 설정 열기"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"확대 설정 닫기"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"모서리를 드래그하여 크기 조절"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"대각선 스크롤 허용"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"크기 조절"</string>
@@ -1039,6 +1051,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"배터리 수준을 읽는 중에 문제가 발생함"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"탭하여 자세한 정보를 확인하세요."</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"설정된 알람 없음"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"화면 잠금 입력"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"지문 센서"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"인증"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"기기 입력"</string>
@@ -1101,7 +1114,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"어시스턴트가 대기 중입니다."</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{알림 #개}other{알림 #개}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"메모"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"메모"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"메모, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"방송 중"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"<xliff:g id="APP_NAME">%1$s</xliff:g> 방송을 중지하시겠습니까?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> 앱을 방송하거나 출력을 변경하면 기존 방송이 중단됩니다"</string>
diff --git a/packages/SystemUI/res/values-ky/strings.xml b/packages/SystemUI/res/values-ky/strings.xml
index c045902..054aa2e 100644
--- a/packages/SystemUI/res/values-ky/strings.xml
+++ b/packages/SystemUI/res/values-ky/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Көрүү үчүн таптаңыз"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Экран тартылган жок"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Экранды жаздырууну баштоодо ката кетти"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Толук экран режимин көрүү"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Чыгуу үчүн экранды ылдый сүрүп коюңуз."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"Түшүндүм"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Артка"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Үйгө"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Меню"</string>
@@ -243,7 +246,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Аудио"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Гарнитура"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Киргизүү"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Угуу аппараттары"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Угуу аппараттары"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Күйгүзүлүүдө…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Авто буруу"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Экранды авто буруу"</string>
@@ -412,6 +415,14 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Бөлүшүп, жаздырып же тышкы экранга чыгарып жатканда Android экраныңыздагы бардык маалыматты же түзмөктө ойнотулуп жаткан нерселерди көрө алат. Андыктан сырсөздөрдү, төлөмдүн чоо-жайын, билдирүүлөрдү, сүрөттөрдү, аудио жана видеону көрсөтүп албаңыз."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Колдонмону бөлүшүп, жаздырып же тышкы экранга чыгарганда Android ал колдонмодо көрсөтүлүп жана ойнотулуп жаткан нерселерди көрө алат. Андыктан сырсөздөрдү, төлөмдүн чоо-жайын, билдирүүлөрдү, сүрөттөрдү, аудио жана видеону көрсөтүп албаңыз."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Баштоо"</string>
+    <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+    <skip />
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"IT администраторуңуз бөгөттөп койгон"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Түзмөк саясаты экрандагыны тартып алууну өчүрүп койгон"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Баарын тазалап салуу"</string>
@@ -853,6 +864,7 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Толук экранда ачуу"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Экрандын бир бөлүгүн чоңойтуу"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Чоңойтуу параметрлерин ачуу"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Чоңойтуу параметрлерин жабуу"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Өлчөмүн өзгөртүү үчүн бурчун сүйрөңүз"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Диагональ боюнча сыдырууга уруксат берүү"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Өлчөмүн өзгөртүү"</string>
@@ -1039,6 +1051,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Батареяңыздын кубаты аныкталбай жатат"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Кеңири маалымат алуу үчүн таптап коюңуз"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Ойготкуч коюлган жок"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"экран кулпусун киргизүү"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Манжа изинин сенсору"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"аныктыгын текшерүү"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"түзмөккө кирүү"</string>
@@ -1101,7 +1114,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Жардамчы угуп жатат"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# билдирме}other{# билдирме}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Эскертме жазуу"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Эскертме жазуу"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Эскертме жазуу (<xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>)"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Кеңири таратуу"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"<xliff:g id="APP_NAME">%1$s</xliff:g> колдонмосунда кабарлоо токтотулсунбу?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Эгер <xliff:g id="SWITCHAPP">%1$s</xliff:g> колдонмосунда кабарласаңыз же аудионун чыгуусун өзгөртсөңүз, учурдагы кабарлоо токтотулат"</string>
diff --git a/packages/SystemUI/res/values-land/dimens.xml b/packages/SystemUI/res/values-land/dimens.xml
index da4547b..1681f7a 100644
--- a/packages/SystemUI/res/values-land/dimens.xml
+++ b/packages/SystemUI/res/values-land/dimens.xml
@@ -35,6 +35,9 @@
     <dimen name="volume_tool_tip_top_margin">12dp</dimen>
     <dimen name="volume_row_slider_height">128dp</dimen>
 
+    <!-- width of ImmersiveModeConfirmation (-1 for match_parent) -->
+    <dimen name="immersive_mode_cling_width">380dp</dimen>
+
     <dimen name="controls_activity_view_top_offset">25dp</dimen>
 
     <dimen name="biometric_dialog_button_negative_max_width">140dp</dimen>
diff --git a/packages/SystemUI/res/values-lo/strings.xml b/packages/SystemUI/res/values-lo/strings.xml
index e2d90d9..66bd9e3 100644
--- a/packages/SystemUI/res/values-lo/strings.xml
+++ b/packages/SystemUI/res/values-lo/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"ແຕະເພື່ອເບິ່ງ"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"ເກີດຂໍ້ຜິດພາດໃນການບັນທຶກໜ້າຈໍ"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"ເກີດຄວາມຜິດພາດໃນການບັນທຶກໜ້າຈໍ"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"ກຳລັງ​ເບິ່ງ​ເຕັມ​​ຈໍ"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"ເພື່ອອອກ, ໃຫ້ປັດລົງຈາກເທິງສຸດ."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"ເຂົ້າໃຈແລ້ວ"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"ກັບຄືນ"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"ໜ້າທຳອິດ"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"ເມນູ"</string>
@@ -243,7 +246,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"ສຽງ"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"ຊຸດຫູຟັງ"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"ການປ້ອນຂໍ້ມູນ"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"ເຄື່ອງຊ່ວຍຟັງ"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"ເຄື່ອງຊ່ວຍຟັງ"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"ກຳລັງເປີດ..."</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"ໝຸນ​ອັດ​ຕະ​ໂນ​ມັດ"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"ໝຸນໜ້າຈໍອັດຕະໂນມັດ"</string>
@@ -412,6 +415,14 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"ເມື່ອທ່ານກຳລັງແບ່ງປັນ, ບັນທຶກ ຫຼື ສົ່ງສັນຍານ, Android ຈະມີສິດເຂົ້າເຖິງທຸກສິ່ງທີ່ປາກົດຢູ່ໜ້າຈໍຂອງທ່ານ ຫຼື ຫຼິ້ນຢູ່ອຸປະກອນຂອງທ່ານ. ດັ່ງນັ້ນ, ໃຫ້ລະມັດລະວັງສິ່ງຕ່າງໆ ເຊັ່ນ: ລະຫັດຜ່ານ, ລາຍລະອຽດການຈ່າຍເງິນ, ຂໍ້ຄວາມ, ຮູບພາບ ພ້ອມທັງສຽງ ແລະ ວິດີໂອ."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"ເມື່ອທ່ານກຳລັງແບ່ງປັນ, ບັນທຶກ ຫຼື ສົ່ງສັນຍານແອັບ, Android ຈະມີສິດເຂົ້າເຖິງທຸກສິ່ງທີ່ສະແດງ ຫຼື ຫຼິ້ນຢູ່ແອັບດັ່ງກ່າວ. ດັ່ງນັ້ນ, ໃຫ້ລະມັດລະວັງສິ່ງຕ່າງໆ ເຊັ່ນ: ລະຫັດຜ່ານ, ລາຍລະອຽດການຈ່າຍເງິນ, ຂໍ້ຄວາມ, ຮູບພາບ ພ້ອມທັງສຽງ ແລະ ວິດີໂອ."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"ເລີ່ມ"</string>
+    <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+    <skip />
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"ຖືກບລັອກໄວ້ໂດຍຜູ້ເບິ່ງແຍງລະບົບໄອທີຂອງທ່ານ"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"ການຖ່າຍຮູບໜ້າຈໍຖືກປິດການນຳໃຊ້ໄວ້ໂດຍນະໂຍບາຍອຸປະກອນ"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"ລຶບລ້າງທັງໝົດ"</string>
@@ -853,6 +864,7 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"ຂະຫຍາຍເຕັມຈໍ"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"ຂະຫຍາຍບາງສ່ວນຂອງໜ້າຈໍ"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"ເປີດການຕັ້ງຄ່າການຂະຫຍາຍ"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"ປິດການຕັ້ງຄ່າການຂະຫຍາຍ"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"ລາກຢູ່ມຸມເພື່ອປັບຂະໜາດ"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"ອະນຸຍາດໃຫ້ເລື່ອນທາງຂວາງ"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"ປ່ຽນຂະໜາດ"</string>
@@ -1039,6 +1051,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"ເກີດບັນຫາໃນການອ່ານຕົວວັດແທກແບັດເຕີຣີຂອງທ່ານ"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"ແຕະເພື່ອເບິ່ງຂໍ້ມູນເພີ່ມເຕີມ"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"ບໍ່ໄດ້ຕັ້ງໂມງປຸກ"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"ໃສ່ຂໍ້ມູນການລັອກໜ້າຈໍ"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"ເຊັນ​ເຊີລາຍນິ້ວ​ມື"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"ພິສູດຢືນຢັນ"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"ເຂົ້າອຸປະກອນ"</string>
@@ -1101,7 +1114,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"ຜູ້ຊ່ວຍກຳລັງຟັງ"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# ການແຈ້ງເຕືອນ}other{# ການແຈ້ງເຕືອນ}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"ການຈົດບັນທຶກ"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"ການຈົດບັນທຶກ"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"ການຈົດບັນທຶກ <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"ກຳລັງອອກອາກາດ"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"ຢຸດການອອກອາກາດ <xliff:g id="APP_NAME">%1$s</xliff:g> ບໍ?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"ຫາກທ່ານອອກອາກາດ <xliff:g id="SWITCHAPP">%1$s</xliff:g> ຫຼື ປ່ຽນເອົ້າພຸດ, ການອອກອາກາດປັດຈຸບັນຂອງທ່ານຈະຢຸດ"</string>
diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml
index a338e09..c93506f 100644
--- a/packages/SystemUI/res/values-lt/strings.xml
+++ b/packages/SystemUI/res/values-lt/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Palieskite, kad peržiūrėtumėte"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Išsaugant ekrano įrašą įvyko klaida"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Pradedant ekrano vaizdo įrašymą iškilo problema"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Peržiūrima viso ekrano režimu"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Jei norite išeiti, perbraukite žemyn iš viršaus."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"Supratau"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Atgal"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Pagrindinis"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Meniu"</string>
@@ -243,7 +246,7 @@
     <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>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Įvestis"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Klausos aparatai"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Klausos aparatai"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Įjungiama…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Automatinis pasukimas"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Automatiškai sukti ekraną"</string>
@@ -412,6 +415,14 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Kai bendrinate, įrašote ar perduodate turinį, „Android“ gali pasiekti viską, kas rodoma ekrane ar leidžiama įrenginyje. Todėl būkite atsargūs naudodami slaptažodžius, išsamią mokėjimo metodo informaciją, pranešimus, nuotraukas ir garso bei vaizdo įrašus."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Kai bendrinate, įrašote ar perduodate programą, „Android“ gali pasiekti viską, kas rodoma ar leidžiama programoje. Todėl būkite atsargūs naudodami slaptažodžius, išsamią mokėjimo metodo informaciją, pranešimus, nuotraukas ir garso bei vaizdo įrašus."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Pradėti"</string>
+    <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+    <skip />
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Užblokavo jūsų IT administratorius"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Ekrano fiksavimo funkcija išjungta vadovaujantis įrenginio politika"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Viską išvalyti"</string>
@@ -853,6 +864,7 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Viso ekrano didinimas"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Didinti ekrano dalį"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Atidaryti didinimo nustatymus"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Uždaryti didinimo nustatymus"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Norėdami keisti dydį, vilkite kampą"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Slinkimo įstrižai leidimas"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Pakeisti dydį"</string>
@@ -1039,6 +1051,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Nuskaitant akumuliatoriaus skaitiklį iškilo problema"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Palieskite, kad sužinotumėte daugiau informacijos"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Nenustatyta signalų"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"įvesti ekrano užraktą"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Kontrolinio kodo jutiklis"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"nustatytumėte tapatybę"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"pasiektumėte įrenginį"</string>
@@ -1101,7 +1114,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Padėjėjas klausosi"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# pranešimas}one{# pranešimas}few{# pranešimai}many{# pranešimo}other{# pranešimų}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Užrašų kūrimas"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Užrašų kūrimas"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Užrašų kūrimas, „<xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>“"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Transliavimas"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Sustabdyti „<xliff:g id="APP_NAME">%1$s</xliff:g>“ transliaciją?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Jei transliuosite „<xliff:g id="SWITCHAPP">%1$s</xliff:g>“ arba pakeisite išvestį, dabartinė transliacija bus sustabdyta"</string>
diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml
index 053e984..dcd54c4 100644
--- a/packages/SystemUI/res/values-lv/strings.xml
+++ b/packages/SystemUI/res/values-lv/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Pieskarieties, lai skatītu"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Saglabājot ekrāna ierakstu, radās kļūda."</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Sākot ierakstīt ekrāna saturu, radās kļūda."</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Skatīšanās pilnekrāna režīmā"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Lai izietu, no augšdaļas velciet lejup."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"Labi"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Atpakaļ"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Sākums"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Izvēlne"</string>
@@ -243,7 +246,7 @@
     <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>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Ievade"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Dzirdes aparāti"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Dzirdes aparāti"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Notiek ieslēgšana…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Automātiska pagriešana"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Automātiska ekrāna pagriešana"</string>
@@ -412,6 +415,14 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Kopīgošanas, ierakstīšanas vai apraides laikā Android var piekļūt visam, kas tiek rādīts jūsu ekrānā vai atskaņots jūsu ierīcē. Tāpēc piesardzīgi apejieties ar parolēm, maksājumu informāciju, ziņojumiem, fotoattēliem un audio un video saturu."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Lietotnes kopīgošanas, ierakstīšanas vai apraides laikā Android var piekļūt visam, kas tiek rādīts vai atskaņots attiecīgajā lietotnē. Tāpēc piesardzīgi apejieties ar parolēm, maksājumu informāciju, ziņojumiem, fotoattēliem un audio un video saturu."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Sākt"</string>
+    <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+    <skip />
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Bloķējis jūsu IT administrators"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Ierīces politika ir atspējojusi ekrānuzņēmumu izveidi"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Dzēst visu"</string>
@@ -853,6 +864,7 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Palielināt visu ekrānu"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Palielināt ekrāna daļu"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Atvērt palielinājuma iestatījumus"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Aizvērt palielinājuma iestatījumus"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Velciet stūri, lai mainītu izmērus"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Atļaut ritināšanu pa diagonāli"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Mainīt lielumu"</string>
@@ -1039,6 +1051,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Nevar iegūt informāciju par akumulatora uzlādes līmeni."</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Pieskarieties, lai iegūtu plašāku informāciju."</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Nav iestatīts signāls"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"ievadīt ekrāna bloķēšanas informāciju"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Pirksta nospieduma sensors"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"veiktu autentificēšanu"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"izmantotu ierīci"</string>
@@ -1101,7 +1114,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Asistents klausās"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# paziņojums}zero{# paziņojumu}one{# paziņojums}other{# paziņojumi}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Piezīmju pierakstīšana"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Piezīmju pierakstīšana"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Piezīmju pierakstīšana, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Notiek apraidīšana"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Vai apturēt lietotnes <xliff:g id="APP_NAME">%1$s</xliff:g> apraidīšanu?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Ja sāksiet lietotnes <xliff:g id="SWITCHAPP">%1$s</xliff:g> apraidīšanu vai mainīsiet izvadi, pašreizējā apraide tiks apturēta"</string>
diff --git a/packages/SystemUI/res/values-mk/strings.xml b/packages/SystemUI/res/values-mk/strings.xml
index 1632600..e83bf81 100644
--- a/packages/SystemUI/res/values-mk/strings.xml
+++ b/packages/SystemUI/res/values-mk/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Допрете за прегледување"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Грешка при зачувувањето на снимката од екранот"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Грешка при почетокот на снимањето на екранот"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Се прикажува на цел екран"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"За да излезете, повлечете одозгора надолу."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"Сфатив"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Назад"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Почетна страница"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Мени"</string>
@@ -243,7 +246,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Аудио"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Слушалки"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Влез"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Слушни помагала"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Слушни помагала"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Се вклучува…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Автоматско ротирање"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Автоматско ротирање на екранот"</string>
@@ -412,6 +415,14 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Кога споделувате, снимате или емитувате, Android има пристап до сѐ што е видливо на вашиот екран или пуштено на вашиот уред. Затоа, бидете внимателни со работи како лозинки, детали за плаќање, пораки, фотографии и аудио и видео."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Кога споделувате, снимате или емитувате апликација, Android има пристап до сѐ што се прикажува или пушта на таа апликација. Затоа, бидете внимателни со работи како лозинки, детали за плаќање, пораки, фотографии и аудио и видео."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Започни"</string>
+    <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+    <skip />
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Блокирано од IT-администраторот"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Снимањето на екранот е оневозможено со правила на уредот"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Избриши сѐ"</string>
@@ -853,6 +864,7 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Зголемете го целиот екран"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Зголемувајте дел од екранот"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Отвори поставки за зголемување"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Затворете ги поставките за зголемување"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Повлечете на аголот за да ја промените големината"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Дозволете дијагонално лизгање"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Промени големина"</string>
@@ -1039,6 +1051,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Проблем при читањето на мерачот на батеријата"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Допрете за повеќе информации"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Не е поставен аларм"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"внесете PIN/шема/лозинка"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Сензор за отпечатоци"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"автентицирате"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"внесете уред"</string>
@@ -1101,7 +1114,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"„Помошникот“ слуша"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# известување}one{# известување}other{# известувања}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Фаќање белешки"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Фаќање белешки"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Фаќање белешки, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Емитување"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Да се прекине емитувањето на <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Ако емитувате на <xliff:g id="SWITCHAPP">%1$s</xliff:g> или го промените излезот, тековното емитување ќе запре"</string>
diff --git a/packages/SystemUI/res/values-ml/strings.xml b/packages/SystemUI/res/values-ml/strings.xml
index b753e98..c8fbde8 100644
--- a/packages/SystemUI/res/values-ml/strings.xml
+++ b/packages/SystemUI/res/values-ml/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"കാണാൻ ടാപ്പ് ചെയ്യുക"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"സ്ക്രീൻ റെക്കോർഡിംഗ് സംരക്ഷിക്കുന്നതിൽ പിശക്"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"സ്ക്രീൻ റെക്കോർഡിംഗ് ആരംഭിക്കുന്നതിൽ പിശക്"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"പൂർണ്ണ സ്‌ക്രീനിൽ കാണുന്നു"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"പുറത്തുകടക്കാൻ, മുകളിൽ നിന്ന് താഴോട്ട് സ്വൈപ്പ് ചെയ്യുക."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"മനസ്സിലായി"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"മടങ്ങുക"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"ഹോം"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"മെനു"</string>
@@ -243,7 +246,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"ഓഡിയോ"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"ഹെഡ്‌സെറ്റ്"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"ഇൻപുട്ട്"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"ശ്രവണ സഹായികൾ"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"ശ്രവണ സഹായികൾ"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"ഓണാക്കുന്നു…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"സ്‌ക്രീൻ സ്വയമേവ തിരിയൽ"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"സ്‌ക്രീൻ സ്വയമേവ തിരിക്കുക"</string>
@@ -412,6 +415,14 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"പങ്കിടുമ്പോൾ, റെക്കോർഡ് ചെയ്യുമ്പോൾ അല്ലെങ്കിൽ കാസ്റ്റ് ചെയ്യുമ്പോൾ, Android-ന് നിങ്ങളുടെ സ്ക്രീനിൽ ദൃശ്യമാകുന്നതോ ഉപകരണത്തിൽ പ്ലേ ചെയ്യുന്നതോ ആയ ഏത് കാര്യത്തിലേക്കും ആക്സസ് ഉണ്ട്. അതിനാൽ പാസ്‍വേഡുകൾ, പേയ്‌മെന്റ് വിശദാംശങ്ങൾ, സന്ദേശങ്ങൾ, ഫോട്ടോകൾ, ഓഡിയോ, വീഡിയോ എന്നിവ പോലുള്ള കാര്യങ്ങൾ നൽകുമ്പോൾ സൂക്ഷിക്കുക."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"ഒരു ആപ്പ് പങ്കിടുമ്പോൾ, റെക്കോർഡ് ചെയ്യുമ്പോൾ അല്ലെങ്കിൽ കാസ്റ്റ് ചെയ്യുമ്പോൾ, Android-ന് ആ ആപ്പിൽ കാണിക്കുന്ന അല്ലെങ്കിൽ പ്ലേ ചെയ്യുന്ന എല്ലാത്തിലേക്കും ആക്സസ് ഉണ്ട്. അതിനാൽ പാസ്‍വേഡുകൾ, പേയ്‌മെന്റ് വിശദാംശങ്ങൾ, സന്ദേശങ്ങൾ, ഫോട്ടോകൾ, ഓഡിയോ, വീഡിയോ എന്നിവ പോലുള്ള കാര്യങ്ങൾ നൽകുമ്പോൾ സൂക്ഷിക്കുക."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"ആരംഭിക്കുക"</string>
+    <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+    <skip />
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"നിങ്ങളുടെ ഐടി അഡ്‌മിൻ ബ്ലോക്ക് ചെയ്‌തു"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"ഉപകരണ നയം, സ്ക്രീൻ ക്യാപ്‌ചർ ചെയ്യൽ പ്രവർത്തനരഹിതമാക്കി"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"എല്ലാം മായ്‌ക്കുക"</string>
@@ -853,6 +864,7 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"സ്ക്രീൻ പൂർണ്ണമായും മാഗ്നിഫൈ ചെയ്യുക"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"സ്‌ക്രീനിന്റെ ഭാഗം മാഗ്നിഫൈ ചെയ്യുക"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"മാഗ്നിഫിക്കേഷൻ ക്രമീകരണം തുറക്കുക"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"മാഗ്നിഫിക്കേഷൻ ക്രമീകരണം അടയ്‌ക്കുക"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"വലുപ്പം മാറ്റാൻ മൂല വലിച്ചിടുക"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"ഡയഗണൽ സ്‌ക്രോളിംഗ് അനുവദിക്കുക"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"വലുപ്പം മാറ്റുക"</string>
@@ -1039,6 +1051,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"നിങ്ങളുടെ ബാറ്ററി മീറ്റർ വായിക്കുന്നതിൽ പ്രശ്‌നമുണ്ട്"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"കൂടുതൽ വിവരങ്ങൾക്ക് ടാപ്പ് ചെയ്യുക"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"അലാറം സജ്ജീകരിച്ചിട്ടില്ല"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"സ്‌ക്രീൻ ലോക്ക് നൽകുക"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"ഫിംഗർപ്രിന്റ് സെൻസർ"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"പരിശോധിച്ചുറപ്പിക്കുക"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"ഉപകരണം നൽകുക"</string>
@@ -1101,7 +1114,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Assistant കേൾക്കുന്നു"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# അറിയിപ്പ്}other{# അറിയിപ്പുകൾ}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"കുറിപ്പ് രേഖപ്പെടുത്തൽ"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"കുറിപ്പ് രേഖപ്പെടുത്തൽ"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"കുറിപ്പ് രേഖപ്പെടുത്തൽ, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"പ്രക്ഷേപണം ചെയ്യുന്നു"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"<xliff:g id="APP_NAME">%1$s</xliff:g> ബ്രോഡ്‌കാസ്റ്റ് ചെയ്യുന്നത് അവസാനിപ്പിക്കണോ?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"നിങ്ങൾ <xliff:g id="SWITCHAPP">%1$s</xliff:g> ബ്രോഡ്‌കാസ്റ്റ് ചെയ്യുകയോ ഔട്ട്പുട്ട് മാറ്റുകയോ ചെയ്താൽ നിങ്ങളുടെ നിലവിലുള്ള ബ്രോഡ്‌കാസ്റ്റ് അവസാനിക്കും"</string>
diff --git a/packages/SystemUI/res/values-mn/strings.xml b/packages/SystemUI/res/values-mn/strings.xml
index 927dbdf..15745ff 100644
--- a/packages/SystemUI/res/values-mn/strings.xml
+++ b/packages/SystemUI/res/values-mn/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Харахын тулд товшино уу"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Дэлгэцийн бичлэгийг хадгалахад алдаа гарлаа"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Дэлгэцийн бичлэгийг эхлүүлэхэд алдаа гарлаа"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Бүтэн дэлгэцээр үзэж байна"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Гарахаар бол дээрээс нь доош нь чирнэ үү."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"Ойлголоо"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Буцах"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Гэрийн"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Цэс"</string>
@@ -243,7 +246,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Аудио"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Чихэвч"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Оролт"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Сонсголын төхөөрөмж"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Сонсголын төхөөрөмж"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Асааж байна…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Автоматаар эргэх"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Дэлгэцийг автоматаар эргүүлэх"</string>
@@ -412,6 +415,14 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Таныг хуваалцаж, бичлэг хийж эсвэл дамжуулж байх үед Android таны дэлгэцэд харуулсан эсвэл төхөөрөмжид тань тоглуулсан аливаа зүйлд хандах эрхтэй. Тиймээс нууц үг, төлбөрийн дэлгэрэнгүй, мессеж, зураг, аудио болон видео зэрэг зүйлд болгоомжтой хандаарай."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Таныг хуваалцаж, бичлэг хийж эсвэл дамжуулж байх үед Android тухайн аппад харуулсан эсвэл тоглуулсан аливаа зүйлд хандах эрхтэй. Тиймээс нууц үг, төлбөрийн дэлгэрэнгүй, мессеж, зураг, аудио болон видео зэрэг бусад зүйлд болгоомжтой хандаарай."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Эхлүүлэх"</string>
+    <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+    <skip />
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Таны IT админ блоклосон"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Төхөөрөмжийн бодлогоор дэлгэцийн зураг авахыг идэвхгүй болгосон"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Бүгдийг арилгах"</string>
@@ -853,6 +864,7 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Бүтэн дэлгэцийг томруулах"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Дэлгэцийн нэг хэсгийг томруулах"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Томруулах тохиргоог нээх"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Томруулах тохиргоог хаах"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Хэмжээг өөрчлөхийн тулд булангаас чирнэ үү"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Хөндлөн гүйлгэхийг зөвшөөрөх"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Хэмжээг өөрчлөх"</string>
@@ -1039,6 +1051,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Таны батарей хэмжигчийг уншихад асуудал гарлаа"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Нэмэлт мэдээлэл авахын тулд товшино уу"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Сэрүүлэг тавиагүй"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"дэлгэцийн түгжээ оруулах"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Хурууны хээ мэдрэгч"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"баталгаажуулах"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"төхөөрөмж оруулах"</string>
@@ -1101,7 +1114,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Туслах сонсож байна"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# мэдэгдэл}other{# мэдэгдэл}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Тэмдэглэл хөтлөх"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Тэмдэглэл хөтлөх"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Тэмдэглэл хөтлөх, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Нэвтрүүлэлт"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"<xliff:g id="APP_NAME">%1$s</xliff:g>-г нэвтрүүлэхээ зогсоох уу?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Хэрэв та <xliff:g id="SWITCHAPP">%1$s</xliff:g>-г нэвтрүүлсэн эсвэл гаралтыг өөрчилсөн бол таны одоогийн нэвтрүүлэлтийг зогсооно"</string>
diff --git a/packages/SystemUI/res/values-mr/strings.xml b/packages/SystemUI/res/values-mr/strings.xml
index e54bcfc..a52d217 100644
--- a/packages/SystemUI/res/values-mr/strings.xml
+++ b/packages/SystemUI/res/values-mr/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"पाहण्यासाठी टॅप करा"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"स्क्रीन रेकॉर्डिंग सेव्ह करताना एरर आली"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"स्क्रीन रेकॉर्डिंग सुरू करताना एरर आली"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"पूर्ण स्क्रीनवर पाहत आहात"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"बाहेर पडण्यासाठी, वरून खाली स्वाइप करा."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"समजले"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"मागे"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"होम"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"मेनू"</string>
@@ -243,7 +246,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"ऑडिओ"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"हेडसेट"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"इनपुट"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"श्रवणयंत्रे"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"श्रवणयंत्रे"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"सुरू करत आहे…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"ऑटो-रोटेट"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"ऑटो-रोटेट स्क्रीन"</string>
@@ -412,6 +415,14 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"तुम्ही शेअर, रेकॉर्ड किंवा कास्ट करत असताना, Android ला तुमच्या स्क्रीनवर दाखवलेल्या किंवा डिव्हाइसवर प्ले केलेल्या कोणत्याही गोष्टीचा अ‍ॅक्सेस असतो. त्यामुळे पासवर्ड, पेमेंट तपशील, मेसेज, फोटो आणि ऑडिओ व व्हिडिओ यांसारख्या गोष्टींबाबत सावधगिरी बाळगा."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"तुम्ही एखादे अ‍ॅप शेअर, रेकॉर्ड किंवा कास्ट करत असताना, Android ला त्या अ‍ॅपवर दाखवलेल्या किंवा प्ले केलेल्या कोणत्याही गोष्टीचा अ‍ॅक्सेस असतो. त्यामुळे पासवर्ड, पेमेंट तपशील, मेसेज, फोटो आणि ऑडिओ व व्हिडिओ यांसारख्या गोष्टींबाबत सावधगिरी बाळगा."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"सुरुवात करा"</string>
+    <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+    <skip />
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"तुमच्या आयटी ॲडमिनने ब्लॉक केले आहे"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"डिव्हाइस धोरणाने स्‍क्रीन कॅप्‍चर करणे बंद केले आहे"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"सर्व साफ करा"</string>
@@ -853,6 +864,7 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"फुल स्क्रीन मॅग्निफाय करा"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"स्क्रीनचा काही भाग मॅग्निफाय करा"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"मॅग्निफिकेशन सेटिंग्ज उघडा"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"मॅग्निफिकेशन सेटिंग्ज बंद करा"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"आकार बदलण्यासाठी कोपरा ड्रॅग करा"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"तिरपे स्क्रोल करण्याची अनुमती द्या"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"आकार बदला"</string>
@@ -1039,6 +1051,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"तुमचे बॅटरी मीटर वाचताना समस्या आली"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"अधिक माहितीसाठी टॅप करा"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"अलार्म सेट केला नाही"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"स्क्रीन लॉक एंटर करा"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"फिंगरप्रिंट सेन्सर"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"ऑथेंटिकेट करा"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"डिव्हाइस एंटर करा"</string>
@@ -1101,7 +1114,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Assistant ऐकत आहे"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# सूचना}other{# सूचना}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"नोटटेकिंग"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"नोंद घेणे"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"नोंद घेणे, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"ब्रॉडकास्ट करत आहे"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"<xliff:g id="APP_NAME">%1$s</xliff:g> चे प्रसारण थांबवायचे आहे का?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"तुम्ही <xliff:g id="SWITCHAPP">%1$s</xliff:g> चे प्रसारण केल्यास किंवा आउटपुट बदलल्यास, तुमचे सध्याचे प्रसारण बंद होईल"</string>
diff --git a/packages/SystemUI/res/values-ms/strings.xml b/packages/SystemUI/res/values-ms/strings.xml
index a73a743..3da42a5 100644
--- a/packages/SystemUI/res/values-ms/strings.xml
+++ b/packages/SystemUI/res/values-ms/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Ketik untuk lihat"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Ralat semasa menyimpan rakaman skrin"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Ralat semasa memulakan rakaman skrin"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Melihat skrin penuh"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Untuk keluar, leret ke bawah dari bahagian atas."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"OK"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Kembali"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Rumah"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string>
@@ -243,7 +246,7 @@
     <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>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Input"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Alat Bantu Dengar"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Alat bantu pendengaran"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Menghidupkan…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Autoputar"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Autoputar skrin"</string>
@@ -412,6 +415,14 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Apabila anda membuat perkongsian, rakaman atau penghantaran, Android boleh mengakses apa-apa sahaja yang boleh dilihat pada skrin anda atau dimainkan pada peranti anda. Oleh hal yang demikian, berhati-hati dengan perkara seperti kata laluan, butiran pembayaran, mesej, foto dan audio serta video."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Apabila anda berkongsi, merakam atau menghantar apl, Android boleh mengakses apa-apa sahaja yang ditunjukan atau dimainkan pada apl tersebut. Oleh hal yang demikian, berhati-hati dengan perkara seperti kata laluan, butiran pembayaran, mesej, foto dan audio serta video."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Mula"</string>
+    <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+    <skip />
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Disekat oleh pentadbir IT anda"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Tangkapan skrin dilumpuhkan oleh dasar peranti"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Kosongkan semua"</string>
@@ -853,6 +864,7 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Besarkan skrin penuh"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Besarkan sebahagian skrin"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Buka tetapan pembesaran"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Tutup tetapan pembesaran"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Seret sudut untuk mengubah saiz"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Benarkan penatalan pepenjuru"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Ubah saiz"</string>
@@ -1039,6 +1051,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Masalah membaca meter bateri anda"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Ketik untuk mendapatkan maklumat lanjut"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Tiada penggera"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"masukkan kunci skrin"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Penderia cap jari"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"sahkan"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"akses peranti"</string>
@@ -1101,7 +1114,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Pembantu sedang mendengar"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# pemberitahuan}other{# pemberitahuan}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Pengambilan nota"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Pengambilan nota"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Pengambilan nota, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Menyiarkan"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Hentikan siaran <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Jika anda siarkan <xliff:g id="SWITCHAPP">%1$s</xliff:g> atau tukarkan output, siaran semasa anda akan berhenti"</string>
diff --git a/packages/SystemUI/res/values-my/strings.xml b/packages/SystemUI/res/values-my/strings.xml
index 21945fe..c258862 100644
--- a/packages/SystemUI/res/values-my/strings.xml
+++ b/packages/SystemUI/res/values-my/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"ကြည့်ရှုရန် တို့ပါ"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"ဖန်သားပြင်ရိုက်ကူးမှုကို သိမ်းရာတွင် အမှားရှိသည်"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"ဖန်သားပြင် ရိုက်ကူးမှု စတင်ရာတွင် အမှားအယွင်းရှိနေသည်"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"ဖန်သားပြင်အပြည့် ကြည့်နေသည်"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"ထွက်ရန် အပေါ်မှ အောက်သို့ ပွတ်ဆွဲပါ။"</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"နားလည်ပြီ"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"နောက်သို့"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"ပင်မစာမျက်နှာ"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"မီနူး"</string>
@@ -243,7 +246,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"အသံ"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"မိုက်ခွက်ပါနားကြပ်"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"အဝင်"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"နားကြားကိရိယာ"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"နားကြားကိရိယာ"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"ဖွင့်နေသည်…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"အော်တို-လည်"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"မျက်နှာပြင်အား အလိုအလျောက်လှည့်ခြင်း"</string>
@@ -412,6 +415,14 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"မျှဝေ၊ ရုပ်သံဖမ်း (သို့) ကာစ်လုပ်သည့်အခါ Android သည် သင့်ဖန်သားပြင်တွင် မြင်နိုင်သည့် (သို့) သင့်စက်တွင် ဖွင့်ထားသည့် အရာအားလုံးကို တွေ့နိုင်သည်။ စကားဝှက်၊ ငွေပေးချေမှု အချက်အလက်၊ မက်ဆေ့ဂျ်၊ ဓာတ်ပုံ၊ အသံနှင့် ဗီဒီယိုကဲ့သို့ အရာများကို ဂရုစိုက်ပါ။"</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"အက်ပ်တစ်ခုဖြင့် မျှဝေ၊ ရုပ်သံဖမ်း (သို့) ကာစ်လုပ်သည့်အခါ Android သည် ယင်းအက်ပ်တွင် ပြထားသည့် (သို့) ဖွင့်ထားသည့် အရာအားလုံးကို တွေ့နိုင်သည်။ ထို့ကြောင့် စကားဝှက်၊ ငွေပေးချေမှု အချက်အလက်၊ မက်ဆေ့ဂျ်၊ ဓာတ်ပုံ၊ အသံနှင့် ဗီဒီယိုကဲ့သို့အရာများကို ဂရုစိုက်ပါ။"</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"စတင်ရန်"</string>
+    <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+    <skip />
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"သင်၏ IT စီမံခန့်ခွဲသူက ပိတ်ထားသည်"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"ကိရိယာ မူဝါဒက ဖန်သားပြင်ပုံဖမ်းခြင်းကို ပိတ်ထားသည်"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"အားလုံးရှင်းရန်"</string>
@@ -853,6 +864,7 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"ဖန်သားပြင်အပြည့် ချဲ့သည်"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"ဖန်သားပြင် တစ်စိတ်တစ်ပိုင်းကို ချဲ့ပါ"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"ချဲ့ခြင်း ဆက်တင်များ ဖွင့်ရန်"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"ချဲ့ခြင်း ဆက်တင်များ ပိတ်ရန်"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"အရွယ်အစားပြန်ပြုပြင်ရန် ထောင့်စွန်းကို ဖိဆွဲပါ"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"ထောင့်ဖြတ် လှိမ့်ခွင့်ပြုရန်"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"အရွယ်အစားပြန်ပြုပြင်ရန်"</string>
@@ -1039,6 +1051,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"သင်၏ ဘက်ထရီမီတာကို ဖတ်ရာတွင် ပြဿနာရှိနေသည်"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"နောက်ထပ်အချက်အလက်များအတွက် တို့ပါ"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"နှိုးစက်ပေးမထားပါ"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"ဖန်သားပြင်လော့ခ် ထည့်ရန်"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"လက်ဗွေ အာရုံခံကိရိယာ"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"အထောက်အထားစိစစ်ရန်"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"စက်ပစ္စည်းသို့ ဝင်ရန်"</string>
@@ -1101,7 +1114,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Assistant နားထောင်နေသည်"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{အကြောင်းကြားချက် # ခု}other{အကြောင်းကြားချက် # ခု}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>၊ <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"မှတ်စုလိုက်ခြင်း"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"မှတ်စုလိုက်ခြင်း"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"မှတ်စုလိုက်ခြင်း၊ <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"ထုတ်လွှင့်ခြင်း"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"<xliff:g id="APP_NAME">%1$s</xliff:g> ထုတ်လွှင့်ခြင်းကို ရပ်မလား။"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> ကို ထုတ်လွှင့်သောအခါ (သို့) အထွက်ကို ပြောင်းသောအခါ သင့်လက်ရှိထုတ်လွှင့်ခြင်း ရပ်သွားမည်"</string>
diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml
index 682b4a0..9277d52 100644
--- a/packages/SystemUI/res/values-nb/strings.xml
+++ b/packages/SystemUI/res/values-nb/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Trykk for å se"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Feil ved lagring av skjermopptaket"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Feil ved start av skjermopptaket"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Visning i fullskjerm"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Sveip ned fra toppen for å avslutte."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"Skjønner"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Tilbake"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Startside"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Meny"</string>
@@ -243,7 +246,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Lyd"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Hodetelefoner"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Innenhet"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Høreapparater"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Høreapparater"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Slår på …"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Rotér automatisk"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Rotér skjermen automatisk"</string>
@@ -412,6 +415,14 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Når du deler, tar opp eller caster noe, har Android tilgang til alt som vises på skjermen eller spilles av på enheten. Derfor bør du være forsiktig med for eksempel passord, betalingsopplysninger, meldinger, bilder, lyd og video."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Når du deler, tar opp eller caster en app, har Android tilgang til alt som vises eller spilles av i den aktuelle appen. Derfor bør du være forsiktig med for eksempel passord, betalingsopplysninger, meldinger, bilder, lyd og video."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Begynn"</string>
+    <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+    <skip />
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blokkert av IT-administratoren"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Skjermdumper er deaktivert av enhetsinnstillingene"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Fjern alt"</string>
@@ -853,6 +864,7 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Forstørr hele skjermen"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Forstørr en del av skjermen"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Åpne innstillinger for forstørring"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Lukk forstørringsinnstillingene"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Dra hjørnet for å endre størrelse"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Tillat diagonal rulling"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Endre størrelse"</string>
@@ -1039,6 +1051,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Kunne ikke lese batterimåleren"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Trykk for å få mer informasjon"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Ingen alarm angitt"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"legg inn skjermlåsen"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Fingeravtrykkssensor"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"autentiser"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"åpne enheten"</string>
@@ -1101,7 +1114,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Assistenten lytter"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# varsel}other{# varsler}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Notatskriving"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Notatskriving"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Notatskriving, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Kringkaster"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Vil du stoppe kringkastingen av <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Hvis du kringkaster <xliff:g id="SWITCHAPP">%1$s</xliff:g> eller endrer utgangen, stopper den nåværende kringkastingen din"</string>
diff --git a/packages/SystemUI/res/values-ne/strings.xml b/packages/SystemUI/res/values-ne/strings.xml
index d9ee7a3..9f9bf85 100644
--- a/packages/SystemUI/res/values-ne/strings.xml
+++ b/packages/SystemUI/res/values-ne/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"हेर्नका लागि ट्याप गर्नुहोस्"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"स्क्रिन रेकर्डिङ सेभ गर्ने क्रममा त्रुटि भयो"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"स्क्रिन रेकर्ड गर्न थाल्ने क्रममा त्रुटि भयो"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"पूरा पर्दा हेर्दै"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"बाहिर निस्कन, माथिबाट तल स्वाइप गर्नुहोस्।"</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"बुझेँ"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"पछाडि"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"गृह"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"मेनु"</string>
@@ -243,7 +246,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"अडियो"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"हेडसेट"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"इनपुट"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"श्रवण यन्त्रहरू"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"श्रवण यन्त्रहरू"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"सक्रिय गर्दै…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"अटो रोटेट"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"स्क्रिन स्वतःघुम्ने"</string>
@@ -412,6 +415,14 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"तपाईंले सेयर गर्दा, रेकर्ड गर्दा वा कास्ट गर्दा Android ले तपाईंको स्क्रिनमा देखिने वा डिभाइसमा प्ले गरिने सबै कुरा हेर्न तथा प्रयोग गर्न सक्छ। त्यसैले पासवर्ड, भुक्तानीसम्बन्धी विवरण, म्यासेज, फोटो र अडियो तथा भिडियो जस्ता कुरा हेर्दा वा प्ले गर्दा सावधानी अपनाउनुहोला।"</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"तपाईंले कुनै एप सेयर गर्दा, रेकर्ड गर्दा वा कास्ट गर्दा Android ले उक्त एपमा देखाइने वा प्ले गरिने सबै कुरा हेर्न तथा प्रयोग गर्न सक्छ। त्यसैले पासवर्ड, भुक्तानीसम्बन्धी विवरण, म्यासेज, फोटो र अडियो तथा भिडियो जस्ता कुरा हेर्दा वा प्ले गर्दा सावधानी अपनाउनुहोला।"</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"सुरु गर्नुहोस्"</string>
+    <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+    <skip />
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"तपाईंका IT एड्मिनले ब्लक गर्नुभएको छ"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"डिभाइसको नीतिका कारण स्क्रिन क्याप्चर गर्ने सुविधा अफ गरिएको छ"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"सबै हटाउनुहोस्"</string>
@@ -853,6 +864,7 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"पूरै स्क्रिन जुम इन गर्नुहोस्"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"स्क्रिनको केही भाग म्याग्निफाइ गर्नुहोस्"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"जुम इनसम्बन्धी सेटिङ खोल्नुहोस्"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"जुम इन गर्ने सुविधाको सेटिङ बन्द गर्नुहोस्"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"आकार बदल्न कुनाबाट ड्र्याग गर्नुहोस्"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"डायगोनल तरिकाले स्क्रोल गर्ने अनुमति दिनुहोस्"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"आकार बदल्नुहोस्"</string>
@@ -1039,6 +1051,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"डिभाइसको ब्याट्रीको मिटर रिडिङ क्रममा समस्या भयो"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"थप जानकारी प्राप्त गर्न ट्याप गर्नुहोस्"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"अलार्म राखिएको छैन"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"स्क्रिन लक हाल्नुहोस्"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"फिंगरप्रिन्ट सेन्सर"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"प्रमाणित गर्नुहोस्"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"डिभाइस हाल्नुहोस्"</string>
@@ -1101,7 +1114,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"सहायकले सुनिरहेको छ"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# वटा सूचना}other{# वटा सूचनाहरू}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"टिपोट गर्ने कार्य"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"नोट लेख्ने कार्य"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"नोट लेख्ने कार्य, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"प्रसारण गरिँदै छ"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"<xliff:g id="APP_NAME">%1$s</xliff:g> ब्रोडकास्ट गर्न छाड्ने हो?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"तपाईंले <xliff:g id="SWITCHAPP">%1$s</xliff:g> ब्रोडकास्ट गर्नुभयो वा आउटपुट परिवर्तन गर्नुभयो भने तपाईंको हालको ब्रोडकास्ट रोकिने छ"</string>
diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml
index 70c746c..5c8ba84 100644
--- a/packages/SystemUI/res/values-nl/strings.xml
+++ b/packages/SystemUI/res/values-nl/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Tik om te bekijken"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Fout bij opslaan van schermopname"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Fout bij starten van schermopname"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Volledig scherm wordt getoond"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Swipe omlaag vanaf de bovenkant van het scherm om af te sluiten."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"OK"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Terug"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Startscherm"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string>
@@ -243,7 +246,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Headset"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Invoer"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Hoortoestellen"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Hoortoestellen"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Aanzetten…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Automatisch draaien"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Scherm automatisch draaien"</string>
@@ -412,6 +415,14 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Als je deelt, opneemt of cast, heeft Android toegang tot alles dat zichtbaar is op je scherm of wordt afgespeeld op je apparaat. Wees daarom voorzichtig met bijvoorbeeld wachtwoorden, betalingsgegevens, berichten, foto\'s, en audio en video."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Als je deelt, opneemt of cast, heeft Android toegang tot alles dat wordt getoond of afgespeeld in die app. Wees daarom voorzichtig met bijvoorbeeld wachtwoorden, betalingsgegevens, berichten, foto\'s, en audio en video."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Starten"</string>
+    <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+    <skip />
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Geblokkeerd door je IT-beheerder"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Schermopname staat uit vanwege apparaatbeleid"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Alles wissen"</string>
@@ -853,6 +864,7 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Volledig scherm vergroten"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Deel van het scherm vergroten"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Instellingen voor vergroting openen"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Instellingen voor vergroting sluiten"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Sleep een hoek om het formaat te wijzigen"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Diagonaal scrollen toestaan"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Formaat aanpassen"</string>
@@ -1039,6 +1051,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Probleem bij het lezen van je batterijmeter"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Tik hier voor meer informatie"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Geen wekker gezet"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"schermvergrendeling invoeren"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Vingerafdruksensor"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"verifiëren"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"apparaat opgeven"</string>
@@ -1101,7 +1114,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"De Assistent luistert"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# melding}other{# meldingen}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Aantekeningen maken"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Aantekeningen maken"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Aantekeningen maken, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Uitzending"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Uitzending van <xliff:g id="APP_NAME">%1$s</xliff:g> stopzetten?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Als je <xliff:g id="SWITCHAPP">%1$s</xliff:g> uitzendt of de uitvoer wijzigt, wordt je huidige uitzending gestopt"</string>
diff --git a/packages/SystemUI/res/values-or/strings.xml b/packages/SystemUI/res/values-or/strings.xml
index 192aee7..e41e7c2 100644
--- a/packages/SystemUI/res/values-or/strings.xml
+++ b/packages/SystemUI/res/values-or/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"ଦେଖିବାକୁ ଟାପ୍ କରନ୍ତୁ"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"ସ୍କ୍ରିନ ରେକର୍ଡିଂ ସେଭ କରିବାରେ ତ୍ରୁଟି"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"ସ୍କ୍ରିନ୍ ରେକର୍ଡିଂ ଆରମ୍ଭ କରିବାରେ ତ୍ରୁଟି"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"ପୂର୍ଣ୍ଣ ସ୍କ୍ରିନରେ ଦେଖିବା"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"ବାହାରି ଯିବା ପାଇଁ, ଶୀର୍ଷରୁ ତଳକୁ ସ୍ୱାଇପ କରନ୍ତୁ।"</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"ବୁଝିଗଲି"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"ଫେରନ୍ତୁ"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"ହୋମ"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"ମେନୁ"</string>
@@ -243,7 +246,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"ଅଡିଓ"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"ହେଡସେଟ୍‍"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"ଇନପୁଟ୍"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"ଶ୍ରବଣ ଯନ୍ତ୍ର"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"ଶ୍ରବଣ ଯନ୍ତ୍ର"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"ଅନ୍ ହେଉଛି…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"ଅଟୋ-ରୋଟେଟ୍"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"ଅଟୋ-ରୋଟେଟ ସ୍କ୍ରିନ"</string>
@@ -412,6 +415,14 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"ଆପଣ ସେୟାର, ରେକର୍ଡ କିମ୍ବା କାଷ୍ଟ କରିବା ସମୟରେ, ଆପଣଙ୍କ ସ୍କ୍ରିନରେ ଦେଖାଯାଉଥିବା କିମ୍ବା ଆପଣଙ୍କ ଡିଭାଇସରେ ପ୍ଲେ ହେଉଥିବା ସବୁକିଛିକୁ Androidର ଆକ୍ସେସ ଅଛି। ତେଣୁ ପାସୱାର୍ଡ, ପେମେଣ୍ଟ ବିବରଣୀ, ମେସେଜ, ଫଟୋ ଏବଂ ଅଡିଓ ଓ ଭିଡିଓ ପରି ବିଷୟଗୁଡ଼ିକ ପ୍ରତି ସତର୍କ ରୁହନ୍ତୁ।"</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"ଆପଣ ଏକ ଆପ ସେୟାର, ରେକର୍ଡ କିମ୍ବା କାଷ୍ଟ କରିବା ସମୟରେ, ସେହି ଆପରେ ଦେଖାଯାଉଥିବା କିମ୍ବା ପ୍ଲେ ହେଉଥିବା ସବୁକିଛିକୁ Androidର ଆକ୍ସେସ ଅଛି। ତେଣୁ ପାସୱାର୍ଡ, ପେମେଣ୍ଟ ବିବରଣୀ, ମେସେଜ, ଫଟୋ ଏବଂ ଅଡିଓ ଓ ଭିଡିଓ ପରି ବିଷୟଗୁଡ଼ିକ ପ୍ରତି ସତର୍କ ରୁହନ୍ତୁ।"</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"ଆରମ୍ଭ କରନ୍ତୁ"</string>
+    <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+    <skip />
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"ଆପଣଙ୍କ IT ଆଡମିନଙ୍କ ଦ୍ୱାରା ବ୍ଲକ କରାଯାଇଛି"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"ଡିଭାଇସ ନୀତି ଦ୍ୱାରା ସ୍କ୍ରିନ କେପଚରିଂକୁ ଅକ୍ଷମ କରାଯାଇଛି"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"ସବୁ ଖାଲି କରନ୍ତୁ"</string>
@@ -853,6 +864,7 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"ସମ୍ପୂର୍ଣ୍ଣ ସ୍କ୍ରିନକୁ ମ୍ୟାଗ୍ନିଫାଏ କରନ୍ତୁ"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"ସ୍କ୍ରିନର ଅଂଶ ମାଗ୍ନିଫାଏ କରନ୍ତୁ"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"ମାଗ୍ନିଫିକେସନ ସେଟିଂସ ଖୋଲନ୍ତୁ"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"ମେଗ୍ନିଫିକେସନ ସେଟିଂସକୁ ବନ୍ଦ କରନ୍ତୁ"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"ରିସାଇଜ କରିବା ପାଇଁ କୋଣକୁ ଡ୍ରାଗ କରନ୍ତୁ"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"ଡାଏଗୋନାଲ ସ୍କ୍ରୋଲିଂକୁ ଅନୁମତି ଦିଅନ୍ତୁ"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"ରିସାଇଜ କରନ୍ତୁ"</string>
@@ -1039,6 +1051,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"ଆପଣଙ୍କ ବ୍ୟାଟେରୀ ମିଟର୍ ପଢ଼ିବାରେ ସମସ୍ୟା ହେଉଛି"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"ଅଧିକ ସୂଚନା ପାଇଁ ଟାପ୍ କରନ୍ତୁ"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"ଆଲାରାମ ସେଟ ହୋଇନାହିଁ"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"ସ୍କ୍ରିନ ଲକ ଲେଖନ୍ତୁ"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"ଟିପଚିହ୍ନ ସେନ୍ସର୍"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"ପ୍ରମାଣୀକରଣ କରନ୍ତୁ"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"ଡିଭାଇସ୍ ବିଷୟରେ ସୂଚନା ଲେଖନ୍ତୁ"</string>
@@ -1101,7 +1114,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Assistant ଶୁଣୁଛି"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{#ଟି ବିଜ୍ଞପ୍ତି}other{#ଟି ବିଜ୍ଞପ୍ତି}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"ନୋଟଟେକିଂ"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"ନୋଟ-ଟେକିଂ"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"ନୋଟ-ଟେକିଂ, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"ବ୍ରଡକାଷ୍ଟ କରୁଛି"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"<xliff:g id="APP_NAME">%1$s</xliff:g> ବ୍ରଡକାଷ୍ଟ କରିବା ବନ୍ଦ କରିବେ?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"ଯଦି ଆପଣ <xliff:g id="SWITCHAPP">%1$s</xliff:g> ବ୍ରଡକାଷ୍ଟ କରନ୍ତି କିମ୍ବା ଆଉଟପୁଟ ବଦଳାନ୍ତି, ତେବେ ଆପଣଙ୍କ ବର୍ତ୍ତମାନର ବ୍ରଡକାଷ୍ଟ ବନ୍ଦ ହୋଇଯିବ"</string>
diff --git a/packages/SystemUI/res/values-pa/strings.xml b/packages/SystemUI/res/values-pa/strings.xml
index 4f1960a..cc63775 100644
--- a/packages/SystemUI/res/values-pa/strings.xml
+++ b/packages/SystemUI/res/values-pa/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"ਦੇਖਣ ਲਈ ਟੈਪ ਕਰੋ"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"ਸਕ੍ਰੀਨ ਰਿਕਾਰਡਿੰਗ ਨੂੰ ਰੱਖਿਅਤ ਕਰਨ ਵੇਲੇ ਗੜਬੜ ਹੋ ਗਈ"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"ਸਕ੍ਰੀਨ ਰਿਕਾਰਡਿੰਗ ਨੂੰ ਸ਼ੁਰੂ ਕਰਨ ਵੇਲੇ ਗੜਬੜ ਹੋਈ"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"ਪੂਰੀ ਸਕ੍ਰੀਨ \'ਤੇ ਦੇਖੋ"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"ਬਾਹਰ ਜਾਣ ਲਈ, ਉਪਰੋਂ ਹੇਠਾਂ ਸਵਾਈਪ ਕਰੋ।"</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"ਸਮਝ ਲਿਆ"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"ਪਿੱਛੇ"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"ਘਰ"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"ਮੀਨੂ"</string>
@@ -243,7 +246,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"ਆਡੀਓ"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"ਹੈੱਡਸੈੱਟ"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"ਇਨਪੁੱਟ"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"ਸੁਣਨ ਦੇ ਸਾਧਨ"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"ਸੁਣਨ ਦੇ ਸਾਧਨ"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"ਚਾਲੂ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"ਸਵੈ-ਘੁਮਾਓ"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"ਸਕ੍ਰੀਨ ਨੂੰ ਆਪਣੇ ਆਪ ਘੁੰਮਾਓ"</string>
@@ -412,6 +415,14 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"ਤੁਹਾਡੇ ਵੱਲੋਂ ਸਾਂਝਾ ਕਰਨ, ਰਿਕਾਰਡ ਕਰਨ, ਜਾਂ ਕਾਸਟ ਕਰਨ \'ਤੇ, Android ਕੋਲ ਤੁਹਾਡੀ ਸਕ੍ਰੀਨ \'ਤੇ ਦਿਸਦੀ ਜਾਂ ਤੁਹਾਡੇ ਡੀਵਾਈਸ \'ਤੇ ਚਲਾਈ ਗਈ ਹਰੇਕ ਚੀਜ਼ ਤੱਕ ਪਹੁੰਚ ਹੁੰਦੀ ਹੈ। ਇਸ ਲਈ ਪਾਸਵਰਡਾਂ, ਭੁਗਤਾਨ ਵੇਰਵਿਆਂ, ਸੁਨੇਹਿਆਂ, ਫ਼ੋਟੋਆਂ ਅਤੇ ਆਡੀਓ ਅਤੇ ਵੀਡੀਓ ਵਰਗੀਆਂ ਚੀਜ਼ਾਂ ਵਾਸਤੇ ਸਾਵਧਾਨ ਰਹੋ।"</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"ਤੁਹਾਡੇ ਵੱਲੋਂ ਸਾਂਝਾ ਕਰਨ, ਰਿਕਾਰਡ ਕਰਨ, ਜਾਂ ਕਾਸਟ ਕਰਨ \'ਤੇ, Android ਕੋਲ ਉਸ ਐਪ \'ਤੇ ਦਿਖਾਈ ਗਈ ਜਾਂ ਚਲਾਈ ਗਈ ਹਰੇਕ ਚੀਜ਼ ਤੱਕ ਪਹੁੰਚ ਹੁੰਦੀ ਹੈ। ਇਸ ਲਈ ਪਾਸਵਰਡਾਂ, ਭੁਗਤਾਨ ਵੇਰਵਿਆਂ, ਸੁਨੇਹਿਆਂ, ਫ਼ੋਟੋਆਂ ਅਤੇ ਆਡੀਓ ਅਤੇ ਵੀਡੀਓ ਵਰਗੀਆਂ ਚੀਜ਼ਾਂ ਸੰਬੰਧੀ ਸਾਵਧਾਨ ਰਹੋ।"</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"ਸ਼ੁਰੂ ਕਰੋ"</string>
+    <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+    <skip />
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"ਤੁਹਾਡੇ ਆਈ.ਟੀ. ਪ੍ਰਸ਼ਾਸਕ ਵੱਲੋਂ ਬਲਾਕ ਕੀਤਾ ਗਿਆ"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"ਡੀਵਾਈਸ ਨੀਤੀ ਦੇ ਕਾਰਨ ਸਕ੍ਰੀਨ ਕੈਪਚਰ ਕਰਨਾ ਬੰਦ ਹੈ"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"ਸਭ ਕਲੀਅਰ ਕਰੋ"</string>
@@ -853,6 +864,7 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"ਪੂਰੀ ਸਕ੍ਰੀਨ ਨੂੰ ਵੱਡਦਰਸ਼ੀ ਕਰੋ"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"ਸਕ੍ਰੀਨ ਦੇ ਹਿੱਸੇ ਨੂੰ ਵੱਡਾ ਕਰੋ"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"ਵੱਡਦਰਸ਼ੀਕਰਨ ਸੈਟਿੰਗਾਂ ਖੋਲ੍ਹੋ"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"ਵੱਡਦਰਸ਼ੀਕਰਨ ਸੈਟਿੰਗਾਂ ਬੰਦ ਕਰੋ"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"ਆਕਾਰ ਬਦਲਣ ਲਈ ਕੋਨਾ ਘਸੀਟੋ"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"ਟੇਡੀ ਦਿਸ਼ਾ ਵਿੱਚ ਸਕ੍ਰੋਲ ਕਰਨ ਦਿਓ"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"ਆਕਾਰ ਬਦਲੋ"</string>
@@ -1039,6 +1051,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"ਤੁਹਾਡੇ ਬੈਟਰੀ ਮੀਟਰ ਨੂੰ ਪੜ੍ਹਨ ਵਿੱਚ ਸਮੱਸਿਆ ਹੋ ਰਹੀ ਹੈ"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"ਹੋਰ ਜਾਣਕਾਰੀ ਲਈ ਟੈਪ ਕਰੋ"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"ਕੋਈ ਅਲਾਰਮ ਸੈੱਟ ਨਹੀਂ"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"ਸਕ੍ਰੀਨ ਲਾਕ ਦਾਖਲ ਕਰੋ"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"ਫਿੰਗਰਪ੍ਰਿੰਟ ਸੈਂਸਰ"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"ਪ੍ਰਮਾਣਿਤ ਕਰੋ"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"ਡੀਵਾਈਸ ਵਿੱਚ ਦਾਖਲ ਹੋਵੋ"</string>
@@ -1101,7 +1114,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Assistant ਸੁਣ ਰਹੀ ਹੈ"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# ਸੂਚਨਾ}one{# ਸੂਚਨਾ}other{# ਸੂਚਨਾਵਾਂ}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"ਨੋਟ ਬਣਾਉਣਾ"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"ਨੋਟ ਬਣਾਉਣਾ"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"ਨੋਟ ਬਣਾਉਣਾ, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"ਪ੍ਰਸਾਰਨ"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"ਕੀ <xliff:g id="APP_NAME">%1$s</xliff:g> ਦੇ ਪ੍ਰਸਾਰਨ ਨੂੰ ਰੋਕਣਾ ਹੈ?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"ਜੇ ਤੁਸੀਂ <xliff:g id="SWITCHAPP">%1$s</xliff:g> ਦਾ ਪ੍ਰਸਾਰਨ ਕਰਦੇ ਹੋ ਜਾਂ ਆਊਟਪੁੱਟ ਬਦਲਦੇ ਹੋ, ਤਾਂ ਤੁਹਾਡਾ ਮੌਜੂਦਾ ਪ੍ਰਸਾਰਨ ਰੁਕ ਜਾਵੇਗਾ"</string>
diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml
index a98616c..21018f9 100644
--- a/packages/SystemUI/res/values-pl/strings.xml
+++ b/packages/SystemUI/res/values-pl/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Kliknij, aby wyświetlić"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Podczas zapisywania nagrania ekranu wystąpił błąd"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Błąd podczas rozpoczynania rejestracji zawartości ekranu"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Włączony pełny ekran"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Aby wyjść, przesuń palcem z góry na dół."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"OK"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Wróć"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Ekran główny"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string>
@@ -243,7 +246,7 @@
     <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>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Wejście"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Aparaty słuchowe"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Aparaty słuchowe"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Włączam…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Autoobracanie"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Autoobracanie ekranu"</string>
@@ -412,6 +415,14 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Podczas udostępniania, nagrywania lub przesyłania treści Android ma dostęp do wszystkiego, co jest widoczne na ekranie lub odtwarzane na urządzeniu. Dlatego zachowaj ostrożność w zakresie haseł, danych do płatności, wiadomości, zdjęć, audio i filmów."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Podczas udostępniania, nagrywania lub przesyłania treści Android ma dostęp do wszystkiego, co jest w niej wyświetlane lub odtwarzane. Dlatego zachowaj ostrożność w zakresie haseł, danych do płatności, wiadomości, zdjęć, audio i filmów."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Rozpocznij"</string>
+    <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+    <skip />
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Zablokowane przez administratora IT"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Zrzuty ekranu są wyłączone zgodnie z zasadami dotyczącymi urządzeń"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Usuń wszystkie"</string>
@@ -853,6 +864,7 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Powiększanie pełnego ekranu"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Powiększ część ekranu"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Otwórz ustawienia powiększenia"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Zamknij ustawienia powiększenia"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Przeciągnij róg, aby zmienić rozmiar"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Zezwalaj na przewijanie poprzeczne"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Zmień rozmiar"</string>
@@ -1039,6 +1051,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Problem z odczytaniem pomiaru wykorzystania baterii"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Kliknij, aby uzyskać więcej informacji"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Nie ustawiono alarmu"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"Wprowadź blokadę ekranu"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Czytnik linii papilarnych"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"uwierzytelnij"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"otwórz urządzenie"</string>
@@ -1101,7 +1114,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Asystent słucha"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# powiadomienie}few{# powiadomienia}many{# powiadomień}other{# powiadomienia}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Notatki"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Notatki"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Notatki, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Transmisja"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Zatrzymaj transmisję aplikacji <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Jeśli transmitujesz aplikację <xliff:g id="SWITCHAPP">%1$s</xliff:g> lub zmieniasz dane wyjściowe, Twoja obecna transmisja zostanie zakończona"</string>
diff --git a/packages/SystemUI/res/values-pt-rBR/strings.xml b/packages/SystemUI/res/values-pt-rBR/strings.xml
index 1db32e8..82015ea 100644
--- a/packages/SystemUI/res/values-pt-rBR/strings.xml
+++ b/packages/SystemUI/res/values-pt-rBR/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Toque para ver"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Erro ao salvar a gravação da tela"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Erro ao iniciar a gravação de tela"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Visualização em tela cheia"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Para sair, deslize de cima para baixo."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"Entendi"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Voltar"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Página inicial"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string>
@@ -243,7 +246,7 @@
     <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>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Entrada"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Aparelhos auditivos"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Aparelhos auditivos"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Ativando…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Giro automático"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Giro automático da tela"</string>
@@ -412,6 +415,14 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Quando você compartilha, grava ou transmite a tela, o Android tem acesso a todas as informações visíveis nela ou reproduzidas no dispositivo. Portanto, tenha cuidado com senhas, detalhes de pagamento, mensagens, fotos, áudios e vídeos."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Quando você compartilha, grava ou transmite um app, o Android tem acesso a todas as informações visíveis ou reproduzidas nele. Tenha cuidado com senhas, detalhes de pagamento, mensagens, fotos, áudios e vídeos."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Início"</string>
+    <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+    <skip />
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Ação bloqueada pelo administrador de TI"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"A captura de tela foi desativada pela política do dispositivo"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Remover tudo"</string>
@@ -853,6 +864,7 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Ampliar toda a tela"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Ampliar parte da tela"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Abrir as configurações de ampliação"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Fechar configurações de ampliação"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Arraste o canto para redimensionar"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Permitir rolagem diagonal"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Redimensionar"</string>
@@ -1039,6 +1051,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Problema para ler seu medidor de bateria"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Toque para mais informações"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Nenhum alarme definido"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"inserir bloqueio de tela"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Sensor de impressão digital"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"autenticar"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"acessar o dispositivo"</string>
@@ -1101,7 +1114,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"O Google Assistente está ouvindo"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# notificação}one{# notificação}many{# notificações}other{# notificações}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Anotações"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Anotações"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Anotações, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Transmitindo"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Interromper a transmissão do app <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Se você transmitir o app <xliff:g id="SWITCHAPP">%1$s</xliff:g> ou mudar a saída, a transmissão atual será interrompida"</string>
diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml
index 802abf7..45853b9 100644
--- a/packages/SystemUI/res/values-pt-rPT/strings.xml
+++ b/packages/SystemUI/res/values-pt-rPT/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Toque para ver"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Erro ao guardar a gravação de ecrã"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Ocorreu um erro ao iniciar a gravação do ecrã."</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Visualização de ecrã inteiro"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Para sair, deslize rapidamente para baixo a partir da parte superior."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"OK"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Anterior"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Página inicial"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string>
@@ -243,7 +246,7 @@
     <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>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Entrada"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Aparelhos auditivos"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Aparelhos auditivos"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"A ativar..."</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Rotação auto."</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Rodar o ecrã automaticamente"</string>
@@ -412,6 +415,10 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Quando está a partilhar, gravar ou transmitir conteúdo, o Android tem acesso a tudo o que está visível no seu ecrã ou é reproduzido no seu dispositivo. Por isso, tenha cuidado com, por exemplo, palavras-passe, detalhes de pagamento, mensagens, fotos, áudio e vídeo."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Quando está a partilhar, gravar ou transmitir uma app, o Android tem acesso a tudo o que é apresentado ou reproduzido nessa app. Por isso, tenha cuidado com, por exemplo, palavras-passe, detalhes de pagamento, mensagens, fotos, áudio e vídeo."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Iniciar"</string>
+    <string name="media_projection_task_switcher_text" msgid="590885489897412359">"A partilha é pausada quando muda de app"</string>
+    <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"Partilhar antes esta app"</string>
+    <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"Voltar"</string>
+    <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"Mudança de app"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Bloqueado pelo administrador de TI"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"A captura de ecrã está desativada pela política do dispositivo"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Limpar tudo"</string>
@@ -853,6 +860,7 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Ampliar o ecrã inteiro"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Ampliar parte do ecrã"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Abrir definições de ampliação"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Fechar definições de ampliação"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Arrastar o canto para redimensionar"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Permitir deslocamento da página na diagonal"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Redimensionar"</string>
@@ -1039,6 +1047,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Ocorreu um problema ao ler o medidor da bateria"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Toque para obter mais informações"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Nenhum alarme defin."</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"introduzir bloqueio de ecrã"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Sensor de impressões digitais"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"autenticar"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"entrar no dispositivo"</string>
@@ -1101,7 +1110,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"O Assistente está a ouvir"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# notificação}many{# notificações}other{# notificações}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Tomar notas"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Tomar notas"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Tomar notas, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"A transmitir"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Interromper a transmissão da app <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Se transmitir a app <xliff:g id="SWITCHAPP">%1$s</xliff:g> ou alterar a saída, a sua transmissão atual é interrompida"</string>
diff --git a/packages/SystemUI/res/values-pt/strings.xml b/packages/SystemUI/res/values-pt/strings.xml
index 1db32e8..82015ea 100644
--- a/packages/SystemUI/res/values-pt/strings.xml
+++ b/packages/SystemUI/res/values-pt/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Toque para ver"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Erro ao salvar a gravação da tela"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Erro ao iniciar a gravação de tela"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Visualização em tela cheia"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Para sair, deslize de cima para baixo."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"Entendi"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Voltar"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Página inicial"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string>
@@ -243,7 +246,7 @@
     <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>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Entrada"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Aparelhos auditivos"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Aparelhos auditivos"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Ativando…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Giro automático"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Giro automático da tela"</string>
@@ -412,6 +415,14 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Quando você compartilha, grava ou transmite a tela, o Android tem acesso a todas as informações visíveis nela ou reproduzidas no dispositivo. Portanto, tenha cuidado com senhas, detalhes de pagamento, mensagens, fotos, áudios e vídeos."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Quando você compartilha, grava ou transmite um app, o Android tem acesso a todas as informações visíveis ou reproduzidas nele. Tenha cuidado com senhas, detalhes de pagamento, mensagens, fotos, áudios e vídeos."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Início"</string>
+    <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+    <skip />
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Ação bloqueada pelo administrador de TI"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"A captura de tela foi desativada pela política do dispositivo"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Remover tudo"</string>
@@ -853,6 +864,7 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Ampliar toda a tela"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Ampliar parte da tela"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Abrir as configurações de ampliação"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Fechar configurações de ampliação"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Arraste o canto para redimensionar"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Permitir rolagem diagonal"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Redimensionar"</string>
@@ -1039,6 +1051,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Problema para ler seu medidor de bateria"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Toque para mais informações"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Nenhum alarme definido"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"inserir bloqueio de tela"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Sensor de impressão digital"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"autenticar"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"acessar o dispositivo"</string>
@@ -1101,7 +1114,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"O Google Assistente está ouvindo"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# notificação}one{# notificação}many{# notificações}other{# notificações}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Anotações"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Anotações"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Anotações, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Transmitindo"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Interromper a transmissão do app <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Se você transmitir o app <xliff:g id="SWITCHAPP">%1$s</xliff:g> ou mudar a saída, a transmissão atual será interrompida"</string>
diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml
index 95ac223..bc6a5fd 100644
--- a/packages/SystemUI/res/values-ro/strings.xml
+++ b/packages/SystemUI/res/values-ro/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Atinge pentru a afișa"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Eroare la salvarea înregistrării ecranului"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Eroare la începerea înregistrării ecranului"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Vizualizare pe ecran complet"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Pentru a ieși, glisează de sus în jos."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"OK"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Înapoi"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Ecranul de pornire"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Meniu"</string>
@@ -243,7 +246,7 @@
     <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>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Intrare"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Aparate auditive"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Aparate auditive"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Se activează..."</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Rotire automată"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Rotirea automată a ecranului"</string>
@@ -412,6 +415,14 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Când permiți accesul, înregistrezi sau proiectezi, Android are acces la orice este vizibil pe ecran sau se redă pe dispozitiv. Prin urmare, ai grijă cu parolele, detaliile de plată, mesajele, fotografiile și conținutul audio și video."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Când permiți accesul, înregistrezi sau proiectezi o aplicație, Android are acces la orice se afișează pe ecran sau se redă în aplicație. Prin urmare, ai grijă cu informații cum ar fi parolele, detaliile de plată, mesajele, fotografiile și conținutul audio și video."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Începe"</string>
+    <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+    <skip />
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blocată de administratorul IT"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Capturile de ecran sunt dezactivate de politica privind dispozitivele"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Șterge toate notificările"</string>
@@ -853,6 +864,7 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Mărește tot ecranul"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Mărește o parte a ecranului"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Deschide setările pentru mărire"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Închide setările de mărire"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Trage de colț pentru a redimensiona"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Permite derularea pe diagonală"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Redimensionează"</string>
@@ -1039,6 +1051,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Problemă la citirea măsurării bateriei"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Atinge pentru mai multe informații"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Nicio alarmă setată"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"intră în blocarea ecranului"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Senzor de amprentă"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"autentifică-te"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"Accesează dispozitivul"</string>
@@ -1101,7 +1114,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Asistentul ascultă"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# notificare}few{# notificări}other{# de notificări}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Notetaking"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Luare de notițe"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Luare de notițe, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Se difuzează"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Oprești transmisia <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Dacă transmiți <xliff:g id="SWITCHAPP">%1$s</xliff:g> sau schimbi ieșirea, transmisia actuală se va opri"</string>
diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml
index 182fe52..80b0d0f 100644
--- a/packages/SystemUI/res/values-ru/strings.xml
+++ b/packages/SystemUI/res/values-ru/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Нажмите, чтобы посмотреть."</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Не удалось сохранить запись видео с экрана."</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Не удалось начать запись видео с экрана."</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Полноэкранный режим"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Чтобы выйти, проведите по экрану сверху вниз."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"ОК"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Назад"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Главный экран"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Меню"</string>
@@ -243,7 +246,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Аудиоустройство"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Гарнитура"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Устройство ввода"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Слуховые аппараты"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Слуховые аппараты"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Включение…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Автоповорот"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Автоповорот экрана"</string>
@@ -412,6 +415,14 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Когда вы демонстрируете, транслируете экран или записываете видео с него, система Android получает доступ ко всему, что видно или воспроизводится на устройстве. Поэтому будьте осторожны с паролями, сведениями о способах оплаты, сообщениями, фотографиями, аудио- и видеозаписями."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Когда вы демонстрируете, записываете или транслируете экран приложения, система Android получает доступ ко всему, что видно или воспроизводится в нем. Поэтому будьте осторожны с паролями, сведениями о способах оплаты, сообщениями, фотографиями, аудио- и видеозаписями."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Начать"</string>
+    <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+    <skip />
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Заблокировано вашим администратором"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Запись экрана отключена в соответствии с правилами для устройства."</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Очистить все"</string>
@@ -853,6 +864,7 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Увеличение всего экрана"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Увеличить часть экрана"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Открыть настройки увеличения"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Закрыть настройки увеличения"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Потяните за угол, чтобы изменить размер"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Разрешить прокручивать по диагонали"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Изменить размер"</string>
@@ -1039,6 +1051,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Не удалось узнать уровень заряда батареи"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Нажмите, чтобы узнать больше."</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Нет будильников"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"разблокировать экран"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Сканер отпечатков пальцев"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"выполнить аутентификацию"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"указать устройство"</string>
@@ -1101,7 +1114,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Ассистент вас слушает"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# уведомление}one{# уведомление}few{# уведомления}many{# уведомлений}other{# уведомления}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Создание заметок"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Создание заметок"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"<xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>: создание заметок"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Трансляция"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Остановить трансляцию \"<xliff:g id="APP_NAME">%1$s</xliff:g>\"?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Если вы начнете транслировать \"<xliff:g id="SWITCHAPP">%1$s</xliff:g>\" или смените целевое устройство, текущая трансляция прервется."</string>
diff --git a/packages/SystemUI/res/values-si/strings.xml b/packages/SystemUI/res/values-si/strings.xml
index a83ac86..8989b7e 100644
--- a/packages/SystemUI/res/values-si/strings.xml
+++ b/packages/SystemUI/res/values-si/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"බැලීමට තට්ටු කරන්න"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"තිර පටිගත කිරීම සුරැකීමේ දෝෂයකි"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"තිර පටිගත කිරීම ආරම්භ කිරීමේ දෝෂයකි"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"මුළු තිරය බලමින්"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"ඉවත් වීමට, ඉහළ සිට පහළට ස්වයිප් කරන්න"</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"වැටහුණි"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"ආපසු"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"මුල් පිටුව"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"මෙනුව"</string>
@@ -243,7 +246,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"ශ්‍රව්‍ය"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"හෙඩ්සෙටය"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"ආදානය"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"ශ්‍රවණාධාරක"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"ශ්‍රවණාධාරක"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"ක්‍රියාත්මක කරමින්…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"ස්වයංක්‍රීය කරකැවීම"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"ස්වයංක්‍රීයව-භ්‍රමණය වන තිරය"</string>
@@ -412,6 +415,14 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"ඔබ බෙදා ගන්නා විට, පටිගත කරන විට, හෝ විකාශය කරන විට, Android හට ඔබේ තිරයේ පෙනෙන හෝ ඔබේ උපාංගයේ වාදනය වන ඕනෑම දෙයකට ප්‍රවේශය ඇත. ඒ නිසා මුරපද, ගෙවීම් විස්තර, පණිවුඩ, ඡායාරූප, සහ ශ්‍රව්‍ය සහ දෘශ්‍ය වැනි දේවල් පිළිබඳ ප්‍රවේශම් වන්න."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"ඔබ යෙදුමක් බෙදා ගන්නා විට, පටිගත කරන විට හෝ විකාශය කරන විට, Android හට එම යෙදුමේ පෙන්වන හෝ වාදනය කරන ඕනෑම දෙයකට ප්‍රවේශය ඇත. ඒ නිසා මුරපද, ගෙවීම් විස්තර, පණිවුඩ, ඡායාරූප, සහ ශ්‍රව්‍ය සහ දෘශ්‍ය වැනි දේවල් පිළිබඳ ප්‍රවේශම් වන්න."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"අරඹන්න"</string>
+    <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+    <skip />
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"ඔබේ IT පරිපාලක විසින් අවහිර කර ඇත"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"උපාංග ප්‍රතිපත්තිය මගින් තිර ග්‍රහණය කිරීම අබල කර ඇත"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"සියල්ල හිස් කරන්න"</string>
@@ -853,6 +864,7 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"පූර්ණ තිරය විශාලනය කරන්න"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"තිරයේ කොටසක් විශාලනය කරන්න"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"විශාලන සැකසීම් විවෘත කරන්න"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"විශාලන සැකසීම් වසන්න"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"ප්‍රමාණය වෙනස් කිරීමට කොන අදින්න"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"විකර්ණ අනුචලනයට ඉඩ දෙන්න"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"ප්‍රතිප්‍රමාණය කරන්න"</string>
@@ -1039,6 +1051,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"ඔබගේ බැටරි මනුව කියවීමේ දෝෂයකි"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"තවත් තොරතුරු සඳහා තට්ටු කරන්න"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"එලාම සකසා නැත"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"තිර අගුල ඇතුළු කරන්න"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"ඇඟිලි සලකුණු සංවේදකය"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"සත්‍යාපනය කරන්න"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"උපාංගය ඇතුළු කරන්න"</string>
@@ -1101,7 +1114,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"සහයක සවන් දෙයි"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{දැනුම්දීම් #ක්}one{දැනුම්දීම් #ක්}other{දැනුම්දීම් #ක්}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"සටහන් කර ගැනීම"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"සටහන් කර ගැනීම"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"සටහන් කර ගැනීම, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"විකාශනය කරමින්"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"<xliff:g id="APP_NAME">%1$s</xliff:g> විකාශනය කිරීම නවත්වන්නද?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"ඔබ <xliff:g id="SWITCHAPP">%1$s</xliff:g> විකාශනය කළහොත් හෝ ප්‍රතිදානය වෙනස් කළහොත්, ඔබගේ වත්මන් විකාශනය නවතිනු ඇත."</string>
diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml
index 322386a..e743d56 100644
--- a/packages/SystemUI/res/values-sk/strings.xml
+++ b/packages/SystemUI/res/values-sk/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Zobrazte klepnutím"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Pri ukladaní nahrávky obrazovky sa vyskytla chyba"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Pri spustení nahrávania obrazovky sa vyskytla chyba"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Zobrazenie na celú obrazovku"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Ukončíte potiahnutím zhora nadol."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"Dobre"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Späť"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Plocha"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string>
@@ -243,7 +246,7 @@
     <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>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Vstup"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Načúvadlá"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Načúvadlá"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Zapína sa…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Automatické otáčanie"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Automatické otáčanie obrazovky"</string>
@@ -412,6 +415,14 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Počas zdieľania, nahrávania alebo prenosu bude mať Android prístup k všetkému, čo sa zobrazuje na obrazovke alebo prehráva v zariadení. Preto zvýšte pozornosť v prípade položiek, ako sú heslá, platobné údaje, správy, fotky a zvuk či video."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Počas zdieľania, nahrávania alebo prenosu v aplikácii bude mať Android prístup k všetkému zobrazovanému alebo prehrávaného obsahu v danej aplikácii. Preto zvýšte pozornosť v prípade položiek, ako sú heslá, platobné údaje, správy, fotky a zvuk či video."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Začať"</string>
+    <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+    <skip />
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blokované vaším správcom IT"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Snímanie obrazovky je zakázané pravidlami pre zariadenie"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Vymazať všetko"</string>
@@ -853,6 +864,7 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Zväčšenie celej obrazovky"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Zväčšiť časť obrazovky"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Otvoriť nastavenia zväčšenia"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Zavrieť nastavenia zväčšenia"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Veľkosť zmeníte presunutím rohu"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Povoliť diagonálne posúvanie"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Zmeniť veľkosť"</string>
@@ -1039,6 +1051,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Pri čítaní meradla batérie sa vyskytol problém"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Klepnutím si zobrazíte ďalšie informácie"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Žiadny budík"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"zadať zámku obrazovky"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Senzor odtlačkov prstov"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"overte"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"vstúpte do zariadenia"</string>
@@ -1101,7 +1114,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Asistent počúva"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# upozornenie}few{# upozornenia}many{# notifications}other{# upozornení}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Zapisovanie poznámok"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Zapisovanie poznámok"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Zapisovanie poznámok, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Vysiela"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Chcete zastaviť vysielanie aplikácie <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Ak vysielate aplikáciu <xliff:g id="SWITCHAPP">%1$s</xliff:g> alebo zmeníte výstup, aktuálne vysielanie bude zastavené"</string>
diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml
index a1e62d2..90d62cb 100644
--- a/packages/SystemUI/res/values-sl/strings.xml
+++ b/packages/SystemUI/res/values-sl/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Dotaknite se za ogled."</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Napaka pri shranjevanju posnetka zaslona"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Napaka pri začenjanju snemanja zaslona"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Vklopljen je celozaslonski način."</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Zaprete ga tako, da z vrha s prstom povlečete navzdol."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"Razumem"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Nazaj"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Začetni zaslon"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Meni"</string>
@@ -243,7 +246,7 @@
     <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>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Vhodna naprava"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Slušni pripomočki"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Slušni aparati"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Vklapljanje …"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Samodejno sukanje"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Samodejno sukanje zaslona"</string>
@@ -412,6 +415,14 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Pri deljenju, snemanju ali predvajanju ima Android dostop do vsega, kar je prikazano na zaslonu ali se predvaja v napravi. Zato bodite previdni z gesli, podatki za plačilo, sporočili, fotografijami ter z zvokom in videom."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Pri deljenju, snemanju ali predvajanju aplikacije ima Android dostop do vsega, kar je prikazano ali predvajano v tej aplikaciji, zato bodite previdni z gesli, podatki za plačilo, sporočili, fotografijami ter z zvokom in videom."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Začni"</string>
+    <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+    <skip />
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blokiral skrbnik za IT"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Zajemanje zaslonske slike je onemogočil pravilnik za naprave."</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Izbriši vse"</string>
@@ -853,6 +864,7 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Povečanje celotnega zaslona"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Povečava dela zaslona"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Odpri nastavitve povečave"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Zapri nastavitve povečave"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Povlecite vogal, da spremenite velikost."</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Dovoli diagonalno pomikanje"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Spremeni velikost"</string>
@@ -1039,6 +1051,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Težava z branjem indikatorja stanja napolnjenosti baterije"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Dotaknite se za več informacij"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Ni nastavljenih alarmov"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"odklenite zaslon"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Tipalo prstnih odtisov"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"preverjanje pristnosti"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"vstop v napravo"</string>
@@ -1101,7 +1114,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Pomočnik posluša."</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# obvestilo}one{# obvestilo}two{# obvestili}few{# obvestila}other{# obvestil}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Ustvarjanje zapiskov"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Ustvarjanje zapiskov"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Ustvarjanje zapiskov, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Oddajanje"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Želite ustaviti oddajanje aplikacije <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Če oddajate aplikacijo <xliff:g id="SWITCHAPP">%1$s</xliff:g> ali spremenite izhod, bo trenutno oddajanje ustavljeno."</string>
diff --git a/packages/SystemUI/res/values-sq/strings.xml b/packages/SystemUI/res/values-sq/strings.xml
index 35e82a6..3677bdee 100644
--- a/packages/SystemUI/res/values-sq/strings.xml
+++ b/packages/SystemUI/res/values-sq/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Trokit për të parë"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Gabim gjatë ruajtjes së regjistrimit të ekranit"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Gabim gjatë nisjes së regjistrimit të ekranit"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Po shikon ekranin e plotë"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Për të dalë, rrëshqit nga lart poshtë."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"E kuptova"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Prapa"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Faqja bazë"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Menyja"</string>
@@ -243,7 +246,7 @@
     <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>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Hyrja"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Aparatet e dëgjimit"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Aparatet e dëgjimit"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Po aktivizohet…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Rrotullim automatik"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Rrotullimi automatik i ekranit"</string>
@@ -412,6 +415,14 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Kur ti ndan, regjistron ose transmeton, Android ka qasje te çdo gjë e dukshme në ekranin tënd ose që po luhet në pajisjen tënde. Prandaj, ki kujdes me gjërat si fjalëkalimet, detajet e pagesave, mesazhet, fotografitë, si dhe audion dhe videon."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Kur ti ndan, regjistron ose transmeton një aplikacion, Android ka qasje te çdo gjë e dukshme në ekranin tënd ose që po luhet në atë aplikacion. Prandaj, ki kujdes me gjërat si fjalëkalimet, detajet e pagesave, mesazhet, fotografitë, si dhe audion dhe videon."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Nis"</string>
+    <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+    <skip />
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"U bllokua nga administratori yt i teknologjisë së informacionit"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Regjistrimi i ekranit është çaktivizuar nga politika e pajisjes."</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Pastroji të gjitha"</string>
@@ -853,6 +864,7 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Zmadho ekranin e plotë"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Zmadho një pjesë të ekranit"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Hap cilësimet e zmadhimit"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Mbyll cilësimet e zmadhimit"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Zvarrit këndin për të ndryshuar përmasat"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Lejo lëvizjen diagonale"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Ndrysho përmasat"</string>
@@ -1039,6 +1051,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Problem me leximin e matësit të baterisë"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Trokit për më shumë informacione"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Nuk është caktuar asnjë alarm"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"hyr te kyçja e ekranit"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Sensori i gjurmës së gishtit"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"për ta vërtetuar"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"për të hyrë në pajisje"</string>
@@ -1101,7 +1114,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"\"Asistenti\" po dëgjon"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# njoftim}other{# njoftime}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Mbajtja e shënimeve"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Mbajtja e shënimeve"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Mbajtja e shënimeve, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Po transmeton"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Të ndalohet transmetimi i <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Nëse transmeton <xliff:g id="SWITCHAPP">%1$s</xliff:g> ose ndryshon daljen, transmetimi yt aktual do të ndalojë"</string>
diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml
index ecabc1b..19d3065 100644
--- a/packages/SystemUI/res/values-sr/strings.xml
+++ b/packages/SystemUI/res/values-sr/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Додирните да бисте прегледали"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Грешка при чувању снимка екрана"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Грешка при покретању снимања екрана"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Приказује се цео екран"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Да бисте изашли, превуците надоле одозго."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"Важи"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Назад"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Почетна"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Мени"</string>
@@ -243,7 +246,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Аудио"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Слушалице"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Унос"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Слушни апарати"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Слушни апарати"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Укључује се..."</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Аутоматска ротација"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Аутоматско ротирање екрана"</string>
@@ -412,6 +415,14 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Када делите, снимате или пребацујете, Android има приступ комплетном садржају који је видљив на екрану или се пушта на уређају. Зато будите пажљиви са лозинкама, информацијама о плаћању, порукама, сликама и аудио и видео снимцима."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Када делите, снимате или пребацујете апликацију, Android има приступ комплетном садржају који је видљив или се пушта у тој апликацији. Зато будите пажљиви са лозинкама, информацијама о плаћању, порукама, сликама и аудио и видео снимцима."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Покрени"</string>
+    <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+    <skip />
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Блокира ИТ администратор"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Снимање екрана је онемогућено смерницама за уређај"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Обриши све"</string>
@@ -853,6 +864,7 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Увећајте цео екран"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Увећајте део екрана"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Отвори подешавања увећања"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Затвори подешавања увећања"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Превуците угао да бисте променили величину"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Дозволи дијагонално скроловање"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Промени величину"</string>
@@ -1039,6 +1051,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Проблем са очитавањем мерача батерије"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Додирните за више информација"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Није подешен"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"унесите закључавање екрана"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Сензор за отисак прста"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"потврдите идентитет"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"унесите уређај"</string>
@@ -1101,7 +1114,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Помоћник слуша"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# обавештење}one{# обавештење}few{# обавештења}other{# обавештења}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Прављење бележака"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Прављење бележака"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Прављење бележака, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Емитовање"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Желите да зауставите емитовање апликације <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Ако емитујете апликацију <xliff:g id="SWITCHAPP">%1$s</xliff:g> или промените излаз, актуелно емитовање ће се зауставити"</string>
diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml
index 3d07826..ae199e9 100644
--- a/packages/SystemUI/res/values-sv/strings.xml
+++ b/packages/SystemUI/res/values-sv/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Tryck för att visa"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Det gick inte att spara skärminspelningen"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Det gick inte att starta skärminspelningen"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Visar på fullskärm"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Svep nedåt från skärmens överkant för att avsluta."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"OK"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Tillbaka"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Startsida"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Meny"</string>
@@ -243,7 +246,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Ljud"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Headset"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Ingång"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Hörapparater"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Hörapparater"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Aktiverar …"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Rotera automatiskt"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Rotera skärmen automatiskt"</string>
@@ -412,6 +415,14 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"När du delar, spelar in eller castar har Android åtkomst till allt som visas på skärmen eller spelas upp på enheten. Var försiktig med sådant som lösenord, betalningsuppgifter, meddelanden, foton och ljud och video."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"När du delar, spelar in eller castar en app har Android åtkomst till allt som visas eller spelas upp i appen. Var försiktig med sådant som lösenord, betalningsuppgifter, meddelanden, foton och ljud och video."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Börja"</string>
+    <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+    <skip />
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blockeras av IT-administratören"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Skärminspelning är inaktiverat av enhetspolicyn"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Rensa alla"</string>
@@ -853,6 +864,7 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Förstora hela skärmen"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Förstora en del av skärmen"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Öppna inställningarna för förstoring"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Stäng inställningarna för förstoring"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Dra i hörnet för att ändra storlek"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Tillåt diagonal scrollning"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Ändra storlek"</string>
@@ -1039,6 +1051,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Batteriindikatorn visas inte"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Tryck för mer information"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Inget inställt alarm"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"ange skärmlåset"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Fingeravtryckssensor"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"autentisera"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"ange enhet"</string>
@@ -1101,7 +1114,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Assistenten lyssnar"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# avisering}other{# aviseringar}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Anteckningar"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Anteckna"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Anteckna med <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Sänder"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Vill du sluta sända från <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Om en utsändning från <xliff:g id="SWITCHAPP">%1$s</xliff:g> pågår eller om du byter ljudutgång avbryts den nuvarande utsändningen"</string>
diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml
index 055540a..abed50d 100644
--- a/packages/SystemUI/res/values-sw/strings.xml
+++ b/packages/SystemUI/res/values-sw/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Gusa ili uangalie"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Hitilafu imetokea wakati wa kuhifadhi rekodi ya skrini"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Hitilafu imetokea wakati wa kuanza kurekodi skrini"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Unatazama kwenye skrini nzima"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Ili uondoke, telezesha kidole kutoka juu hadi chini."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"Nimeelewa"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Nyuma"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Nyumbani"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Menyu"</string>
@@ -243,7 +246,7 @@
     <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>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Vifaa vya kuingiza sauti"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Visaidizi vya Kusikia"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Visaidizi vya kusikia"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Inawasha..."</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Zungusha kiotomatiki"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Skrini ijizungushe kiotomatiki"</string>
@@ -412,6 +415,14 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Unaposhiriki, kurekodi au kutuma, Android inaweza kufikia kitu chochote kitakachoonekana kwenye skrini yako au kuchezwa kwenye kifaa chako. Kwa hivyo kuwa mwangalifu na vitu kama vile manenosiri, maelezo ya malipo, ujumbe, picha na sauti na video."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Unaposhiriki, kurekodi au kutuma programu, Android inaweza kufikia kitu chochote kitakachoonekana au kuchezwa kwenye programu hiyo. Kwa hivyo kuwa mwangalifu na vitu kama vile manenosiri, maelezo ya malipo, ujumbe, picha na sauti na video."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Anza"</string>
+    <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+    <skip />
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Umezuiwa na msimamizi wako wa TEHAMA"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Mchakato wa kurekodi skrini umezimwa na sera ya kifaa"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Futa zote"</string>
@@ -853,6 +864,7 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Kuza skrini nzima"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Kuza sehemu ya skrini"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Fungua mipangilio ya ukuzaji"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Funga mipangilio ya ukuzaji"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Buruta kona ili ubadilishe ukubwa"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Ruhusu usogezaji wa kimshazari"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Badilisha ukubwa"</string>
@@ -1039,6 +1051,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Tatizo la kusoma mita ya betri yako"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Gusa ili upate maelezo zaidi"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Hujaweka kengele"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"weka kifunga skrini"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Kitambua alama ya kidole"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"thibitisha"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"weka kifaa"</string>
@@ -1101,7 +1114,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Programu ya Mratibu inasikiliza"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{Arifa #}other{Arifa #}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Kuandika vidokezo"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Kuandika madokezo"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Kuandika madokezo, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Inaarifu"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Ungependa kusimamisha utangazaji kwenye <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Ikiwa unatangaza kwenye <xliff:g id="SWITCHAPP">%1$s</xliff:g> au unabadilisha maudhui, tangazo lako la sasa litasimamishwa"</string>
diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml
index 59becc6..2b1d9d6 100644
--- a/packages/SystemUI/res/values-sw600dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp/dimens.xml
@@ -20,6 +20,13 @@
          On large screens should be the same as the regular status bar. -->
     <dimen name="status_bar_header_height_keyguard">@dimen/status_bar_height</dimen>
 
+    <!-- padding for container with status icons and battery -->
+    <dimen name="status_bar_icons_padding_end">12dp</dimen>
+    <!-- start padding is smaller to account for status icon margins coming from drawable itself -->
+    <dimen name="status_bar_icons_padding_start">11dp</dimen>
+
+    <dimen name="status_bar_padding_end">0dp</dimen>
+
     <!-- Size of user icon + frame in the qs user picker (incl. frame) -->
     <dimen name="qs_framed_avatar_size">60dp</dimen>
     <!-- Size of user icon + frame in the keyguard user picker (incl. frame) -->
@@ -46,6 +53,9 @@
 
     <dimen name="navigation_key_padding">25dp</dimen>
 
+    <!-- width of ImmersiveModeConfirmation (-1 for match_parent) -->
+    <dimen name="immersive_mode_cling_width">380dp</dimen>
+
     <!-- Keyboard shortcuts helper -->
     <dimen name="ksh_layout_width">488dp</dimen>
 
@@ -67,6 +77,9 @@
     <dimen name="large_dialog_width">472dp</dimen>
 
     <dimen name="large_screen_shade_header_height">42dp</dimen>
+    <!-- start padding is smaller to account for status icon margins coming from drawable itself -->
+    <dimen name="shade_header_system_icons_padding_start">11dp</dimen>
+    <dimen name="shade_header_system_icons_padding_end">12dp</dimen>
 
     <!-- Lockscreen shade transition values -->
     <dimen name="lockscreen_shade_transition_by_tap_distance">200dp</dimen>
diff --git a/packages/SystemUI/res/values-sw720dp/dimens.xml b/packages/SystemUI/res/values-sw720dp/dimens.xml
index 2086459..de913ac 100644
--- a/packages/SystemUI/res/values-sw720dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw720dp/dimens.xml
@@ -16,14 +16,20 @@
 */
 -->
 <resources>
+    <!-- it's a bit smaller on 720dp to account for status_bar_icon_horizontal_margin -->
+    <dimen name="status_bar_icons_padding_start">10dp</dimen>
+
     <!-- gap on either side of status bar notification icons -->
-    <dimen name="status_bar_icon_padding">1dp</dimen>
+    <dimen name="status_bar_icon_horizontal_margin">1sp</dimen>
 
     <dimen name="controls_header_horizontal_padding">28dp</dimen>
     <dimen name="controls_content_margin_horizontal">40dp</dimen>
 
     <dimen name="large_screen_shade_header_height">56dp</dimen>
 
+    <!-- it's a bit smaller on 720dp to account for status_bar_icon_horizontal_margin -->
+    <dimen name="shade_header_system_icons_padding_start">10dp</dimen>
+
     <!-- Biometric Auth pattern view size, better to align keyguard_security_width -->
     <dimen name="biometric_auth_pattern_view_size">348dp</dimen>
 </resources>
diff --git a/packages/SystemUI/res/values-ta/strings.xml b/packages/SystemUI/res/values-ta/strings.xml
index 72b3598..8d930a5 100644
--- a/packages/SystemUI/res/values-ta/strings.xml
+++ b/packages/SystemUI/res/values-ta/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"பார்க்கத் தட்டவும்"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"ஸ்கிரீன் ரெக்கார்டிங்கைச் சேமிப்பதில் பிழை ஏற்பட்டது"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"ஸ்கிரீன் ரெக்கார்டிங்கைத் தொடங்குவதில் பிழை"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"முழுத் திரையில் காட்டுகிறது"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"வெளியேற, மேலிருந்து கீழே ஸ்வைப் செய்யவும்"</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"புரிந்தது"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"பின்செல்"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"முகப்பு"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"மெனு"</string>
@@ -243,7 +246,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"ஆடியோ"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"ஹெட்செட்"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"உள்ளீடு"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"செவித்துணை கருவிகள்"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"செவித்துணைக் கருவி"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"ஆன் செய்கிறது…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"தானாகச் சுழற்று"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"திரையைத் தானாகச் சுழற்று"</string>
@@ -412,6 +415,14 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"நீங்கள் பகிரும்போதோ ரெக்கார்டு செய்யும்போதோ அலைபரப்பும்போதோ உங்கள் திரையில் காட்டப்படுகின்ற அல்லது சாதனத்தில் பிளே செய்யப்படுகின்ற அனைத்தையும் Android அணுக முடியும். எனவே கடவுச்சொற்கள், பேமெண்ட் விவரங்கள், மெசேஜ்கள், படங்கள், ஆடியோ, வீடியோ போன்றவை குறித்துக் கவனத்துடன் இருங்கள்."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"நீங்கள் ஓர் ஆப்ஸைப் பகிரும்போதோ ரெக்கார்டு செய்யும்போதோ அலைபரப்பும்போதோ அந்த ஆப்ஸில் காட்டப்படுகின்ற அல்லது பிளே செய்யப்படுகின்ற அனைத்தையும் Android அணுக முடியும். எனவே கடவுச்சொற்கள், பேமெண்ட் விவரங்கள், மெசேஜ்கள், படங்கள், ஆடியோ, வீடியோ போன்றவை குறித்துக் கவனத்துடன் இருங்கள்."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"தொடங்கு"</string>
+    <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+    <skip />
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"உங்கள் IT நிர்வாகி தடுத்துள்ளார்"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"\'திரையைப் படமெடுத்தல்\' சாதனக் கொள்கையின்படி முடக்கப்பட்டுள்ளது"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"எல்லாவற்றையும் அழி"</string>
@@ -853,6 +864,7 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"முழுத்திரையைப் பெரிதாக்கும்"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"திரையின் ஒரு பகுதியைப் பெரிதாக்கும்"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"பெரிதாக்கல் அமைப்புகளைத் திற"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"பெரிதாக்கல் அமைப்புகளை மூடுக"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"அளவை மாற்ற மூலையை இழுக்கவும்"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"குறுக்கே ஸ்க்ரோல் செய்வதை அனுமதி"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"அளவை மாற்று"</string>
@@ -1039,6 +1051,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"பேட்டரி அளவை அறிவதில் சிக்கல்"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"மேலும் தகவல்களுக்கு தட்டவும்"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"அலாரம் எதுவுமில்லை"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"திரைப் பூட்டை உள்ளிடலாம்"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"கைரேகை சென்சார்"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"அங்கீகரி"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"சாதனத்தைத் திற"</string>
@@ -1101,7 +1114,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Assistant கேட்டுக்கொண்டிருக்கிறது"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# அறிவிப்பு}other{# அறிவிப்புகள்}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"குறிப்பெடுத்தல்"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"குறிப்பெடுத்தல்"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"குறிப்பெடுத்தல், <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"ஒலிபரப்புதல்"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"<xliff:g id="APP_NAME">%1$s</xliff:g> ஆப்ஸ் ஒலிபரப்பப்படுவதை நிறுத்தவா?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"நீங்கள் <xliff:g id="SWITCHAPP">%1$s</xliff:g> ஆப்ஸை ஒலிபரப்பினாலோ அவுட்புட்டை மாற்றினாலோ உங்களின் தற்போதைய ஒலிபரப்பு நிறுத்தப்படும்"</string>
diff --git a/packages/SystemUI/res/values-te/strings.xml b/packages/SystemUI/res/values-te/strings.xml
index 8324993..f90a435 100644
--- a/packages/SystemUI/res/values-te/strings.xml
+++ b/packages/SystemUI/res/values-te/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"చూడటానికి ట్యాప్ చేయండి"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"స్క్రీన్ రికార్డింగ్‌ను సేవ్ చేయడంలో ఎర్రర్ ఏర్పడింది"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"స్క్రీన్ రికార్డింగ్ ప్రారంభించడంలో ఎర్రర్ ఏర్పడింది"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"ఫుల్ స్క్రీన్‌లో చూస్తున్నారు"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"నిష్క్రమించడానికి, పై నుండి కిందికి స్వైప్ చేయండి."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"సరే"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"వెనుకకు"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"హోమ్"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"మెనూ"</string>
@@ -243,7 +246,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"ఆడియో"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"హెడ్‌సెట్"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"ఇన్‌పుట్"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"వినికిడి పరికరాలు"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"వినికిడి పరికరాలు"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"ఆన్ చేస్తోంది…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"ఆటో-రొటేట్‌"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"స్క్రీన్ ఆటో-రొటేట్‌"</string>
@@ -412,6 +415,14 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"మీరు షేర్ చేస్తున్నప్పుడు, రికార్డ్ చేస్తున్నప్పుడు, లేదా ప్రసారం చేస్తున్నప్పుడు, మీ స్క్రీన్‌పై కనిపించే దేనికైనా లేదా మీ పరికరంలో ప్లే అయిన దేనికైనా Androidకు యాక్సెస్ ఉంటుంది. కాబట్టి పాస్‌వర్డ్‌లు, పేమెంట్ వివరాలు, మెసేజ్‌లు, ఫోటోలు, ఆడియో, ఇంకా వీడియో వంటి విషయాల్లో జాగ్రత్త వహించండి."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"మీరు ఏదైనా యాప్‌ను షేర్ చేస్తున్నప్పుడు, రికార్డ్ చేస్తున్నప్పుడు, లేదా ప్రసారం చేస్తున్నప్పుడు, ఆ యాప్‌లో చూపబడిన దేనికైనా లేదా ప్లే అయిన దేనికైనా Androidకు యాక్సెస్ ఉంటుంది. కాబట్టి పాస్‌వర్డ్‌లు, పేమెంట్ వివరాలు, మెసేజ్‌లు, ఫోటోలు, ఆడియో, ఇంకా వీడియో వంటి విషయాల్లో జాగ్రత్త వహించండి."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"ప్రారంభించండి"</string>
+    <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+    <skip />
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"మీ IT అడ్మిన్ ద్వారా బ్లాక్ చేయబడింది"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"పరికర పాలసీ ద్వారా స్క్రీన్ క్యాప్చర్ చేయడం డిజేబుల్ చేయబడింది"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"అన్నీ క్లియర్ చేయండి"</string>
@@ -853,6 +864,7 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"ఫుల్ స్క్రీన్‌ను మ్యాగ్నిఫై చేయండి"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"స్క్రీన్‌లో భాగాన్ని మ్యాగ్నిఫై చేయండి"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"మ్యాగ్నిఫికేషన్ సెట్టింగ్‌లను తెరవండి"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"మాగ్నిఫికేషన్ సెట్టింగ్‌లను మూసివేయండి"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"సైజ్ మార్చడానికి మూలను లాగండి"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"డయాగనల్ స్క్రోలింగ్‌ను అనుమతించండి"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"సైజ్ మార్చండి"</string>
@@ -1039,6 +1051,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"మీ బ్యాటరీ మీటర్‌ను చదవడంలో సమస్య"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"మరింత సమాచారం కోసం ట్యాప్ చేయండి"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"అలారం సెట్ చేయలేదు"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"స్క్రీన్ లాక్‌ను ఎంటర్ చేయండి"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"వేలిముద్ర సెన్సార్"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"ప్రామాణీకరించండి"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"పరికరాన్ని ఎంటర్ చేయండి"</string>
@@ -1101,7 +1114,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Assistant వింటోంది"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# నోటిఫికేషన్}other{# నోటిఫికేషన్‌లు}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"నోట్‌టేకింగ్"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"నోట్-టేకింగ్"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"నోట్-టేకింగ్, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"ప్రసారం చేస్తోంది"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"<xliff:g id="APP_NAME">%1$s</xliff:g> ప్రసారం చేయడాన్ని ఆపివేయాలా?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"మీరు <xliff:g id="SWITCHAPP">%1$s</xliff:g> ప్రసారం చేస్తే లేదా అవుట్‌పుట్‌ను మార్చినట్లయితే, మీ ప్రస్తుత ప్రసారం ఆగిపోతుంది"</string>
diff --git a/packages/SystemUI/res/values-television/config.xml b/packages/SystemUI/res/values-television/config.xml
index 0ca154e..2ace86f 100644
--- a/packages/SystemUI/res/values-television/config.xml
+++ b/packages/SystemUI/res/values-television/config.xml
@@ -20,11 +20,6 @@
 <!-- These resources are around just to allow their values to be customized
      for different hardware and product builds. -->
 <resources>
-    <!-- SystemUIFactory component -->
-    <string name="config_systemUIFactoryComponent" translatable="false">
-        com.android.systemui.tv.TvSystemUIInitializer
-    </string>
-
     <!-- Svelte specific logic, see RecentsConfiguration.SVELTE_* constants. -->
     <integer name="recents_svelte_level">3</integer>
 
diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml
index 698d52e..8043792 100644
--- a/packages/SystemUI/res/values-th/strings.xml
+++ b/packages/SystemUI/res/values-th/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"แตะเพื่อดู"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"เกิดข้อผิดพลาดในการบันทึกหน้าจอ"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"เกิดข้อผิดพลาดขณะเริ่มบันทึกหน้าจอ"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"กำลังดูแบบเต็มหน้าจอ"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"หากต้องการออก ให้เลื่อนลงจากด้านบน"</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"รับทราบ"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"กลับ"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"หน้าแรก"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"เมนู"</string>
@@ -243,7 +246,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"เสียง"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"ชุดหูฟัง"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"อินพุต"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"เครื่องช่วยฟัง"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"เครื่องช่วยฟัง"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"กำลังเปิด..."</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"หมุนอัตโนมัติ"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"หมุนหน้าจออัตโนมัติ"</string>
@@ -412,6 +415,14 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"เมื่อกำลังแชร์ บันทึก หรือแคสต์ Android จะมีสิทธิ์เข้าถึงทุกสิ่งที่ปรากฏบนหน้าจอหรือเล่นอยู่ในอุปกรณ์ ดังนั้นโปรดระวังสิ่งต่างๆ อย่างเช่นรหัสผ่าน รายละเอียดการชำระเงิน ข้อความ รูปภาพ รวมถึงเสียงและวิดีโอ"</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"เมื่อกำลังแชร์ บันทึก หรือแคสต์แอป Android จะมีสิทธิ์เข้าถึงทุกสิ่งที่แสดงหรือเล่นอยู่ในแอปดังกล่าว ดังนั้นโปรดระวังสิ่งต่างๆ อย่างเช่นรหัสผ่าน รายละเอียดการชำระเงิน ข้อความ รูปภาพ รวมถึงเสียงและวิดีโอ"</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"เริ่ม"</string>
+    <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+    <skip />
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"ผู้ดูแลระบบไอทีบล็อกไว้"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"การจับภาพหน้าจอปิดใช้โดยนโยบายด้านอุปกรณ์"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"ล้างทั้งหมด"</string>
@@ -853,6 +864,7 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"ขยายเป็นเต็มหน้าจอ"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"ขยายบางส่วนของหน้าจอ"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"เปิดการตั้งค่าการขยาย"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"ปิดการตั้งค่าการขยาย"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"ลากที่มุมเพื่อปรับขนาด"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"อนุญาตการเลื่อนแบบทแยงมุม"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"ปรับขนาด"</string>
@@ -1039,6 +1051,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"พบปัญหาในการอ่านเครื่องวัดแบตเตอรี่"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"แตะดูข้อมูลเพิ่มเติม"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"ไม่มีการตั้งปลุก"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"ป้อนข้อมูลการล็อกหน้าจอ"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"เซ็นเซอร์ลายนิ้วมือ"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"ตรวจสอบสิทธิ์"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"เข้าถึงอุปกรณ์"</string>
@@ -1101,7 +1114,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Assistant กำลังฟังอยู่"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{การแจ้งเตือน # รายการ}other{การแจ้งเตือน # รายการ}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"การจดบันทึก"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"การจดบันทึก"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"การจดบันทึก <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"กำลังออกอากาศ"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"หยุดการออกอากาศ <xliff:g id="APP_NAME">%1$s</xliff:g> ไหม"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"หากคุณออกอากาศ <xliff:g id="SWITCHAPP">%1$s</xliff:g> หรือเปลี่ยนแปลงเอาต์พุต การออกอากาศในปัจจุบันจะหยุดลง"</string>
diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml
index 412bbb4..835c864 100644
--- a/packages/SystemUI/res/values-tl/strings.xml
+++ b/packages/SystemUI/res/values-tl/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"I-tap para tingnan"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Nagka-error sa pag-save ng recording ng screen"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Nagkaroon ng error sa pagsisimula ng pag-record ng screen"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Panonood sa full screen"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Upang lumabas, mag-swipe mula sa itaas pababa."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"Nakuha ko"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Bumalik"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Home"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string>
@@ -243,7 +246,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Headset"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Input"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Mga Hearing Aid"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Mga hearing aid"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Ino-on…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"I-auto rotate"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Awtomatikong i-rotate ang screen"</string>
@@ -412,6 +415,14 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Kapag nagbabahagi, nagre-record, o nagka-cast ka, may access ang Android sa kahit anong nakikita sa iyong screen o pine-play sa device mo. Kaya mag-ingat sa mga bagay-bagay tulad ng mga password, detalye ng pagbabayad, mensahe, larawan, at audio at video."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Kapag nagbabahagi, nagre-record, o nagka-cast ka ng app, may access ang Android sa kahit anong ipinapakita o pine-play sa app na iyon. Kaya mag-ingat sa mga bagay-bagay tulad ng mga password, detalye ng pagbabayad, mensahe, larawan, at audio at video."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Simulan"</string>
+    <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+    <skip />
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Na-block ng iyong IT admin"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Naka-disable ang pag-screen capture ayon sa patakaran ng device"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"I-clear lahat"</string>
@@ -853,6 +864,7 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"I-magnify ang buong screen"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"I-magnify ang isang bahagi ng screen"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Buksan ang mga setting ng pag-magnify"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Isara ang mga setting ng pag-magnify"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"I-drag ang sulok para i-resize"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Payagan ang diagonal na pag-scroll"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"I-resize"</string>
@@ -1039,6 +1051,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Nagkaproblema sa pagbabasa ng iyong battery meter"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"I-tap para sa higit pang impormasyon"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Walang alarm"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"ilagay ang lock ng screen"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Sensor para sa fingerprint"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"i-authenticate"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"ilagay ang device"</string>
@@ -1101,7 +1114,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Nakikinig ang Assistant"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# notification}one{# notification}other{# na notification}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Pagtatala"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Pagtatala"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Pagtatala, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Nagbo-broadcast"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Ihinto ang pag-broadcast ng <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Kung magbo-broadcast ka ng <xliff:g id="SWITCHAPP">%1$s</xliff:g> o babaguhin mo ang output, hihinto ang iyong kasalukuyang broadcast"</string>
diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml
index 9a00c07..3f05efa 100644
--- a/packages/SystemUI/res/values-tr/strings.xml
+++ b/packages/SystemUI/res/values-tr/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Görüntülemek için dokunun"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Ekran kaydı saklanırken hata oluştu"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Ekran kaydı başlatılırken hata oluştu"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Tam ekran olarak görüntüleme"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Çıkmak için yukarıdan aşağıya doğru hızlıca kaydırın."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"Anladım"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Geri"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Ana sayfa"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Menü"</string>
@@ -243,7 +246,7 @@
     <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>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Giriş"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"İşitme Cihazları"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"İşitme cihazları"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Açılıyor…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Otomatik döndür"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Ekranı otomatik döndür"</string>
@@ -412,6 +415,14 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Paylaşma, kaydetme veya yayınlama özelliğini kullandığınızda Android, ekranınızda gösterilen veya cihazınızda oynatılan her şeye erişebilir. Bu nedenle şifre, ödeme ayrıntıları, mesaj, fotoğraf, ses ve video gibi öğeler konusunda dikkatli olun."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Bir uygulamayı paylaştığınızda, kaydettiğinizde veya yayınladığınızda Android, söz konusu uygulamada gösterilen veya oynatılan her şeye erişebilir. Bu nedenle şifre, ödeme ayrıntıları, mesaj, fotoğraf, ses ve video gibi öğeler konusunda dikkatli olun."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Başlat"</string>
+    <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+    <skip />
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"BT yöneticiniz tarafından engellendi"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Ekran görüntüsü alma, cihaz politikası tarafından devre dışı bırakıldı"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Tümünü temizle"</string>
@@ -853,6 +864,7 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Tam ekran büyütme"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Ekranın bir parçasını büyütün"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Büyütme ayarlarını aç"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Büyütme ayarlarını kapat"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Yeniden boyutlandırmak için köşeyi sürükleyin"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Çapraz kaydırmaya izin ver"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Yeniden boyutlandır"</string>
@@ -1039,6 +1051,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Pil ölçeriniz okunurken sorun oluştu"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Daha fazla bilgi için dokunun"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Alarm yok"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"ekran kilidini gir"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Parmak izi sensörü"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"kimlik doğrulaması yapın"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"cihaz girin"</string>
@@ -1101,7 +1114,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Asistan dinliyor"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# bildirim}other{# bildirim}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Not alma"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Not alma"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Not alma, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Yayınlama"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"<xliff:g id="APP_NAME">%1$s</xliff:g> uygulamasında anons durdurulsun mu?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> uygulamasında anons yapar veya çıkışı değiştirirseniz mevcut anonsunuz duraklatılır"</string>
diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml
index 470d1b1..d4b9c34 100644
--- a/packages/SystemUI/res/values-uk/strings.xml
+++ b/packages/SystemUI/res/values-uk/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Натисніть, щоб переглянути"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Не вдалося зберегти запис відео з екрана"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Не вдалося почати запис екрана"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Перегляд на весь екран"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Щоб вийти, проведіть пальцем зверху вниз."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"OK"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Назад"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Головна"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Меню"</string>
@@ -243,7 +246,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Аудіопристрій"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Гарнітура"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Джерело сигналу"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Слухові апарати"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Слухові апарати"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Увімкнення…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Автообертання"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Автоматично обертати екран"</string>
@@ -412,6 +415,14 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Коли ви показуєте, записуєте або транслюєте екран, ОС Android отримує доступ до всього, що відображається на ньому чи відтворюється на пристрої. Тому будьте уважні з паролями, повідомленнями, фотографіями, аудіо, відео, платіжною інформацією тощо."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Коли ви показуєте, записуєте або транслюєте додаток, ОС Android отримує доступ до всього, що відображається або відтворюється в ньому. Тому будьте уважні з паролями, повідомленнями, фотографіями, аудіо, відео, платіжною інформацією тощо."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Почати"</string>
+    <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+    <skip />
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Заблоковано адміністратором"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Запис екрана вимкнено згідно з правилами для пристрою"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Очистити все"</string>
@@ -853,6 +864,7 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Збільшення всього екрана"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Збільшити частину екрана"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Відкрити налаштування збільшення"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Закрити налаштування збільшення"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Потягніть кут, щоб змінити розмір"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Дозволити прокручування по діагоналі"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Змінити розмір"</string>
@@ -1039,6 +1051,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Не вдалось отримати дані про рівень заряду акумулятора"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Натисніть, щоб дізнатися більше"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Немає будильників"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"показати способи розблокування екрана"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Сканер відбитків пальців"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"пройти автентифікацію"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"відкрити пристрій"</string>
@@ -1101,7 +1114,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Асистент слухає"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# сповіщення}one{# сповіщення}few{# сповіщення}many{# сповіщень}other{# сповіщення}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Створення нотаток"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Створення нотаток"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Створення нотаток, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Трансляція"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Зупинити трансляцію з додатка <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Якщо ви зміните додаток (<xliff:g id="SWITCHAPP">%1$s</xliff:g>) або аудіовихід, поточну трансляцію буде припинено"</string>
diff --git a/packages/SystemUI/res/values-ur/strings.xml b/packages/SystemUI/res/values-ur/strings.xml
index 066fb9c..acf1357 100644
--- a/packages/SystemUI/res/values-ur/strings.xml
+++ b/packages/SystemUI/res/values-ur/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"دیکھنے کے لیے تھپتھپائیں"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"اسکرین ریکارڈنگ محفوظ کرنے میں خرابی"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"اسکرین ریکارڈنگ شروع کرنے میں خرابی"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"فُل اسکرین میں دیکھ رہے ہیں"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"باہر نکلنے کیلئے اوپر سے نیچے کی طرف سوائپ کریں۔"</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"سمجھ آ گئی"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"واپس جائیں"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"ہوم"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"مینیو"</string>
@@ -243,7 +246,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"آڈیو"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"ہیڈ سیٹ"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"ان پٹ"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"سماعتی آلات"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"سماعتی آلات"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"آن ہو رہا ہے…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"خود کار طور پر گھمائیں"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"اسکرین کو خود کار طور پر گھمائیں"</string>
@@ -412,6 +415,14 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"‏جب آپ اشتراک، ریکارڈنگ یا کاسٹ کر رہے ہوتے ہیں تو Android کو آپ کی اسکرین پر دکھائی دینے والی یا آپ کے آلے پر چلائی گئی ہر چیز تک رسائی حاصل ہوتی ہے۔ لہذا، پاس ورڈز، ادائیگی کی تفصیلات، پیغامات، تصاویر، ساتھ ہی آڈیو اور ویڈیو جیسی چیزوں کے سلسلے میں محتاط رہیں۔"</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"‏جب آپ اشتراک، ریکارڈنگ یا کسی ایپ کو کاسٹ کر رہے ہوتے ہیں تو Android کو اس ایپ پر دکھائی گئی یا چلائی گئی ہر چیز تک رسائی حاصل ہوتی ہے۔ لہذا، پاس ورڈز، ادائیگی کی تفصیلات، پیغامات، تصاویر، ساتھ ہی آڈیو اور ویڈیو جیسی چیزوں کے سلسلے میں محتاط رہیں۔"</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"شروع کریں"</string>
+    <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+    <skip />
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"‏آپ کے IT منتظم نے مسدود کر دیا"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"اسکرین کو کیپچر کرنا آلہ کی پالیسی کے ذریعے غیر فعال ہے"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"سبھی کو صاف کریں"</string>
@@ -853,6 +864,7 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"فُل اسکرین کو بڑا کریں"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"اسکرین کا حصہ بڑا کریں"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"میگنیفکیشن کی ترتیبات کھولیں"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"میگنیفکیشن کی ترتیبات بند کریں"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"سائز تبدیل کرنے کے لیے کونے کو گھسیٹیں"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"وتری سکرولنگ کی اجازت دیں"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"سائز تبدیل کریں"</string>
@@ -1039,6 +1051,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"آپ کے بیٹری میٹر کو پڑھنے میں دشواری"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"مزید معلومات کے لیے تھپتھپائیں"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"کوئی الارم سیٹ نہیں ہے"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"اسکرین لاک درج کریں"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"فنگر پرنٹ سینسر"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"تصدیق کریں"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"آلہ درج کریں"</string>
@@ -1101,7 +1114,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"اسسٹنٹ سن رہی ہے"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# اطلاع}other{# اطلاعات}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>، <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"نوٹ لینا"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"نوٹ لکھنا"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"نوٹ لکھنا، <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"نشریات"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"<xliff:g id="APP_NAME">%1$s</xliff:g> براڈکاسٹنگ روکیں؟"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"اگر آپ <xliff:g id="SWITCHAPP">%1$s</xliff:g> براڈکاسٹ کرتے ہیں یا آؤٹ پٹ کو تبدیل کرتے ہیں تو آپ کا موجودہ براڈکاسٹ رک جائے گا"</string>
diff --git a/packages/SystemUI/res/values-uz/strings.xml b/packages/SystemUI/res/values-uz/strings.xml
index d440541..2af9e92 100644
--- a/packages/SystemUI/res/values-uz/strings.xml
+++ b/packages/SystemUI/res/values-uz/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Koʻrish uchun bosing"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Ekran yozuvi saqlanmadi"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Ekranni yozib olish boshlanmadi"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Butun ekranli rejim"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Chiqish uchun tepadan pastga torting."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"OK"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Orqaga"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Uyga"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Menyu"</string>
@@ -243,7 +246,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Garnitura"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Kirish"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Eshitish apparatlari"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Eshitish moslamalari"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Yoqilmoqda…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Avto-burilish"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Ekranning avtomatik burilishi"</string>
@@ -412,6 +415,14 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Ulashish, yozib olish va translatsiya qilish vaqtida Android ekranda chiqadigan yoki qurilmada ijro qilinadigan kontentni koʻra oladi. Shu sababli parollar, toʻlov tafsilotlari, xabarlar, suratlar, audio va video chiqmasligi uchun ehtiyot boʻling."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Ilovani ulashish, yozib olish yoki translatsiya qilayotganingizda Android ekranda chiqadigan yoki qurilmada ijro qilinadigan kontentni koʻra oladi. Shu sababli parollar, toʻlov tafsilotlari, xabarlar, suratlar, audio va video chiqmasligi uchun ehtiyot boʻling."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Boshlash"</string>
+    <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+    <skip />
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"AT administratoringiz tomonidan bloklangan"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Ekranni tasvirga olish qurilmadan foydalanish tartibi tomonidan faolsizlantirilgan"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Hammasini tozalash"</string>
@@ -853,6 +864,7 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Ekranni toʻliq kattalashtirish"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Ekran qismini kattalashtirish"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Kattalashtirish sozlamalarini ochish"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Kattalashtirish sozlamalarini yopish"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Oʻlchamini oʻzgartirish uchun burchakni torting"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Diagonal aylantirishga ruxsat berish"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Oʻlchamini oʻzgartirish"</string>
@@ -1039,6 +1051,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Batareya quvvati aniqlanmadi"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Batafsil axborot olish uchun bosing"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Signal sozlanmagan"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"ekran qulfini kiriting"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Barmoq izi skaneri"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"autentifikatsiya"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"qurilmani ochish"</string>
@@ -1101,7 +1114,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Assistent tinglamoqda"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# ta bildirishnoma}other{# ta bildirishnoma}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Eslatma yozish"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Qayd olish"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"<xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>: qayd olish"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Signal uzatish"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"<xliff:g id="APP_NAME">%1$s</xliff:g> ilovasiga translatsiya toʻxtatilsinmi?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Agar <xliff:g id="SWITCHAPP">%1$s</xliff:g> ilovasiga translatsiya qilsangiz yoki ovoz chiqishini oʻzgartirsangiz, joriy translatsiya toʻxtab qoladi"</string>
diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml
index 9938783..791337d 100644
--- a/packages/SystemUI/res/values-vi/strings.xml
+++ b/packages/SystemUI/res/values-vi/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Nhấn để xem"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Có lỗi xảy ra khi lưu video ghi màn hình"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Lỗi khi bắt đầu ghi màn hình"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Xem toàn màn hình"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Để thoát, hãy vuốt từ trên cùng xuống dưới."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"OK"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Quay lại"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Trang chủ"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string>
@@ -243,7 +246,7 @@
     <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>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Thiết bị đầu vào"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Thiết bị trợ thính"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Thiết bị trợ thính"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Đang bật…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Tự động xoay"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Tự động xoay màn hình"</string>
@@ -412,6 +415,14 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Khi bạn chia sẻ, ghi hoặc truyền, Android sẽ có quyền truy cập vào mọi nội dung xuất hiện trên màn hình hoặc phát trên thiết bị của bạn. Vì vậy, hãy thận trọng để không làm lộ thông tin như mật khẩu, thông tin thanh toán, tin nhắn, ảnh, âm thanh và video."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Khi bạn chia sẻ, ghi hoặc truyền ứng dụng, Android sẽ có quyền truy cập vào mọi nội dung xuất hiện hoặc phát trên ứng dụng đó. Vì vậy, hãy thận trọng để không làm lộ các thông tin như mật khẩu, thông tin thanh toán, tin nhắn, ảnh, âm thanh và video."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Bắt đầu"</string>
+    <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+    <skip />
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Bị quản trị viên CNTT chặn"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Tính năng chụp ảnh màn hình đã bị tắt theo chính sách thiết bị"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Xóa tất cả"</string>
@@ -853,6 +864,7 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Phóng to toàn màn hình"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Phóng to một phần màn hình"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Mở chế độ cài đặt phóng to"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Đóng bảng cài đặt tính năng phóng to"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Kéo góc để thay đổi kích thước"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Cho phép cuộn chéo"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Đổi kích thước"</string>
@@ -1039,6 +1051,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Đã xảy ra vấn đề khi đọc dung lượng pin của bạn"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Nhấn để biết thêm thông tin"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Chưa đặt chuông báo"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"nhập phương thức mở khoá màn hình"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Cảm biến vân tay"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"xác thực"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"truy cập thiết bị"</string>
@@ -1101,7 +1114,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Trợ lý đang nghe bạn nói"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# thông báo}other{# thông báo}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Ghi chú"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Ghi chú"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Ghi chú, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Phát sóng"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Dừng phát <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Nếu bạn phát <xliff:g id="SWITCHAPP">%1$s</xliff:g> hoặc thay đổi đầu ra, phiên truyền phát hiện tại sẽ dừng"</string>
diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml
index f6c189c..bc289ee 100644
--- a/packages/SystemUI/res/values-zh-rCN/strings.xml
+++ b/packages/SystemUI/res/values-zh-rCN/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"点按即可查看"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"保存屏幕录制内容时出错"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"启动屏幕录制时出错"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"目前处于全屏模式"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"要退出,请从顶部向下滑动。"</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"知道了"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"返回"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"主屏幕"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"菜单"</string>
@@ -243,7 +246,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"音频"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"耳机"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"输入"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"助听器"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"助听器"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"正在开启…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"自动屏幕旋转"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"自动旋转屏幕"</string>
@@ -412,6 +415,14 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"在分享内容时,Android 可以访问屏幕上显示或设备中播放的所有内容。因此,请务必小心操作,谨防密码、付款信息、消息、照片、音频和视频等内容遭到泄露。"</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"在分享、录制或投放内容时,Android 可以访问通过此应用显示或播放的所有内容。因此,请务必小心操作,谨防密码、付款信息、消息、照片、音频和视频等内容遭到泄露。"</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"开始"</string>
+    <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+    <skip />
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"已被 IT 管理员禁止"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"设备政策已停用屏幕截图功能"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"全部清除"</string>
@@ -853,6 +864,7 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"放大整个屏幕"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"放大部分屏幕"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"打开放大功能设置"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"关闭放大设置"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"拖动一角即可调整大小"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"允许沿对角线滚动"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"调整大小"</string>
@@ -1039,6 +1051,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"读取电池计量器时出现问题"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"点按即可了解详情"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"未设置闹钟"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"输入屏幕解锁信息"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"指纹传感器"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"身份验证"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"进入设备"</string>
@@ -1101,7 +1114,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Google 助理正在聆听"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# 条通知}other{# 条通知}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>,<xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"记录"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"记事"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"记事,<xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"正在广播"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"要停止广播“<xliff:g id="APP_NAME">%1$s</xliff:g>”的内容吗?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"如果广播“<xliff:g id="SWITCHAPP">%1$s</xliff:g>”的内容或更改输出来源,当前的广播就会停止"</string>
diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml
index 8fb4e81..8db6dbd 100644
--- a/packages/SystemUI/res/values-zh-rHK/strings.xml
+++ b/packages/SystemUI/res/values-zh-rHK/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"輕按即可查看"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"儲存螢幕錄影時發生錯誤"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"開始錄影畫面時發生錯誤"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"開啟全螢幕"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"由頂部向下滑動即可退出。"</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"知道了"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"返回"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"首頁"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"選單"</string>
@@ -243,7 +246,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"音訊"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"耳機"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"輸入"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"助聽器"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"助聽器"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"正在開啟…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"自動旋轉"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"自動旋轉螢幕"</string>
@@ -412,6 +415,14 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"當你分享、錄影或投放時,Android 可存取顯示在螢幕畫面上或在裝置上播放的所有內容。因此,請謹慎處理密碼、付款資料、訊息、相片、音訊和影片等。"</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"當你分享、錄影或投放應用程式時,Android 可存取顯示在該應用程式中顯示或播放的所有內容。因此,請謹慎處理密碼、付款資料、訊息、相片、音訊和影片等。"</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"開始"</string>
+    <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+    <skip />
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"已被你的 IT 管理員封鎖"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"螢幕截圖功能因裝置政策而停用"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"全部清除"</string>
@@ -853,6 +864,7 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"放大成個畫面"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"放大部分螢幕畫面"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"開啟放大設定"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"關閉放大設定"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"拖曳角落即可調整大小"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"允許斜角捲動"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"調整大小"</string>
@@ -1039,6 +1051,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"讀取電池計量器時發生問題"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"輕按即可瞭解詳情"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"未設定鬧鐘"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"輸入螢幕鎖定憑證"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"指紋感應器"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"驗證"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"進入裝置"</string>
@@ -1101,7 +1114,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"「Google 助理」正在聆聽"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# 則通知}other{# 則通知}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>,<xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"做筆記"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"做筆記"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"做筆記,<xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"廣播"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"要停止廣播「<xliff:g id="APP_NAME">%1$s</xliff:g>」的內容嗎?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"如要廣播「<xliff:g id="SWITCHAPP">%1$s</xliff:g>」的內容或變更輸出來源,系統就會停止廣播目前的內容"</string>
diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml
index c09bb42..9ba2aa9 100644
--- a/packages/SystemUI/res/values-zh-rTW/strings.xml
+++ b/packages/SystemUI/res/values-zh-rTW/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"輕觸即可查看"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"儲存螢幕錄影內容時發生錯誤"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"開始錄製螢幕畫面時發生錯誤"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"以全螢幕檢視"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"如要退出,請從畫面頂端向下滑動。"</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"知道了"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"返回"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"主畫面"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"選單"</string>
@@ -243,7 +246,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"音訊"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"耳機"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"輸入"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"助聽器"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"助聽器"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"開啟中…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"自動旋轉"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"自動旋轉螢幕"</string>
@@ -412,6 +415,14 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"當你分享、錄製或投放內容時,Android 將可存取畫面上顯示的任何資訊或裝置播放的任何內容。因此,請謹慎處理密碼、付款資料、訊息、相片和影音內容等資訊。"</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"當你分享、錄製或投放內容時,Android 可存取應用程式中顯示的任何資訊或播放的任何內容。因此,請謹慎處理密碼、付款資料、訊息、相片和影音內容等資訊。"</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"開始"</string>
+    <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+    <skip />
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"IT 管理員已封鎖這項操作"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"根據裝置政策規定,螢幕畫面擷取功能已停用"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"全部清除"</string>
@@ -853,6 +864,7 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"放大整個螢幕畫面"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"放大局部螢幕畫面"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"開啟放大功能設定"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"關閉放大設定"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"拖曳角落即可調整大小"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"允許沿對角線捲動"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"調整大小"</string>
@@ -1039,6 +1051,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"讀取電池計量器時發生問題"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"輕觸即可瞭解詳情"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"未設定鬧鐘"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"輸入螢幕鎖定憑證"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"指紋感應器"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"驗證"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"進入裝置"</string>
@@ -1101,7 +1114,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Google 助理正在聆聽"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# 則通知}other{# 則通知}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>,<xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"做筆記"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"做筆記"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"做筆記,<xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"廣播"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"要停止播送「<xliff:g id="APP_NAME">%1$s</xliff:g>」的內容嗎?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"如果播送「<xliff:g id="SWITCHAPP">%1$s</xliff:g>」的內容或變更輸出來源,系統就會停止播送目前的內容"</string>
diff --git a/packages/SystemUI/res/values-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml
index 35c8a2b..0e6bde2 100644
--- a/packages/SystemUI/res/values-zu/strings.xml
+++ b/packages/SystemUI/res/values-zu/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Thepha ukuze ubuke"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Iphutha lokulondoloza okokuqopha iskrini"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Iphutha lokuqala ukurekhoda isikrini"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Ukubuka isikrini esigcwele"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Ukuze uphume, swayiphela phansi kusuka phezulu."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"Ngiyezwa"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Emuva"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Ekhaya"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Imenyu"</string>
@@ -243,7 +246,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Umsindo"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Ihedisethi"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Okokufaka"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Izinsiza zokuzwa"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Imishini yendlebe"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Iyavula..."</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Ukuphenduka okuzenzakalelayo"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Phendula iskrini ngokuzenzakalela"</string>
@@ -412,6 +415,14 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Uma wabelana, ukurekhoda, noma ukusakaza, i-Android inokufinyelela kunoma yini ebonakala esikrinini sakho noma okudlalwayo kudivayisi yakho. Ngakho-ke qaphela ngezinto ezifana namaphasiwedi, imininingwane yokukhokha, imilayezo, izithombe, nomsindo nevidiyo."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Uma wabelana, ukurekhoda, noma ukusakaza ku-app, i-Android inokufinyelela kunoma yini eboniswayo noma edlalwa kuleyo app. Ngakho-ke qaphela ngezinto ezfana namaphasiwedi, imininingwane yokukhokha, imilayezo, izithombe, nomsindo nevidiyo."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Qala"</string>
+    <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+    <skip />
+    <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+    <skip />
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Kuvinjelwe ngumlawuli wakho we-IT"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Ukuthwebula isikrini kukhutshazwe yinqubomgomo yedivayisi"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Sula konke"</string>
@@ -853,6 +864,7 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Khulisa isikrini esigcwele"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Khulisa ingxenye eyesikrini"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Vula amasethingi okukhuliswa"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Vala amasethingi okukhuliswa"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Hudula ikhona ukuze usayize kabusha"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Vumela ukuskrola oku-diagonal"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Shintsha usayizi"</string>
@@ -1039,6 +1051,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Kube khona inkinga ngokufunda imitha yakho yebhethri"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Thepha ukuze uthole olunye ulwazi"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Akukho alamu esethiwe"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"faka ukukhiya isikrini"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Inzwa yesigxivizo somunwe"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"gunyaza"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"faka idivayisi"</string>
@@ -1101,7 +1114,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"I-Assistant ilalele"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{Isaziso esingu-#}one{Izaziso ezingu-#}other{Izaziso ezingu-#}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Ukuthatha amanothi"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Ukuthatha amanothi"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Ukuthatha amanothi, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Ukusakaza"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Misa ukusakaza i-<xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Uma usakaza i-<xliff:g id="SWITCHAPP">%1$s</xliff:g> noma ushintsha okuphumayo, ukusakaza kwakho kwamanje kuzoma"</string>
diff --git a/packages/SystemUI/res/values/attrs.xml b/packages/SystemUI/res/values/attrs.xml
index bd86e51..d693631 100644
--- a/packages/SystemUI/res/values/attrs.xml
+++ b/packages/SystemUI/res/values/attrs.xml
@@ -118,8 +118,25 @@
     <attr name="wallpaperTextColorSecondary" format="reference|color" />
     <attr name="wallpaperTextColorAccent" format="reference|color" />
     <attr name="backgroundProtectedStyle" format="reference" />
-    <attr name="offStateColor" format="reference|color" />
-    <attr name="underSurfaceColor" format="reference|color" />
+
+    <!-- color attribute tokens for QS -->
+    <attr name="isQsTheme" format="boolean" />
+    <attr name="underSurface" format="reference|color"/>
+    <attr name="shadeActive" format="reference|color" />
+    <attr name="onShadeActive" format="reference|color" />
+    <attr name="onShadeActiveVariant" format="reference|color" />
+    <attr name="shadeInactive" format="reference|color" />
+    <attr name="onShadeInactive" format="reference|color" />
+    <attr name="onShadeInactiveVariant" format="reference|color" />
+    <attr name="shadeDisabled" format="reference|color" />
+    <attr name="surfaceBright" format="reference|color" />
+    <attr name="scHigh" format="reference|color" />
+    <attr name="tertiary" format="reference|color" />
+    <attr name="onSurface" format="reference|color" />
+    <attr name="onSurfaceVariant" format="reference|color" />
+    <attr name="outline" format="reference|color" />
+    <attr name="primary" format="reference|color" />
+
 
     <declare-styleable name="SmartReplyView">
         <attr name="spacing" format="dimension" />
@@ -216,6 +233,8 @@
         <attr name="progress" format="integer" />
         <attr name="iconStartContentDescription" format="reference" />
         <attr name="iconEndContentDescription" format="reference" />
+        <attr name="tickMark" format="reference" />
+        <attr name="seekBarChangeMagnitude" format="integer" />
     </declare-styleable>
 </resources>
 
diff --git a/packages/SystemUI/res/values/bools.xml b/packages/SystemUI/res/values/bools.xml
index 04fc4b8..405a59f 100644
--- a/packages/SystemUI/res/values/bools.xml
+++ b/packages/SystemUI/res/values/bools.xml
@@ -30,4 +30,25 @@
 
     <!-- Whether to enable transparent background for notification scrims -->
     <bool name="notification_scrim_transparent">false</bool>
+
+    <!-- Control whether to force apps to give up control over the display of system bars at all
+     times regardless of System Ui Flags.
+     In the Automotive case, this is helpful if there's a requirement for an UI element to be on
+     screen at all times. Setting this to true also gives System UI the ability to override the
+     visibility controls for the system through the usage of the
+     "SYSTEM_BAR_VISIBILITY_OVERRIDE" setting.
+     Ex: Only setting the config to true will force show system bars for the entire system.
+     Ex: Setting the config to true and the "SYSTEM_BAR_VISIBILITY_OVERRIDE" setting to
+     "immersive.status=apps" will force show navigation bar for all apps and force hide status
+     bar for all apps. -->
+    <bool name="config_remoteInsetsControllerControlsSystemBars">false</bool>
+
+    <!-- Control whether the system bars can be requested when using a remote insets control target.
+         This allows for specifying whether or not system bars can be shown by the user (via swipe
+         or other means) when they are hidden by the logic defined by the remote insets controller.
+         This is useful for cases where the system provides alternative affordances for showing and
+         hiding the bars or for cases in which it's desired the bars not be shown for any reason.
+         This configuration will only apply when config_remoteInsetsControllerControlsSystemBars.
+         is set to true. -->
+    <bool name="config_remoteInsetsControllerSystemBarsCanBeShownByUserAction">false</bool>
 </resources>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 166bd2a..9c864ab 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -302,9 +302,6 @@
     <!-- Determines whether the shell features all run on another thread. -->
     <bool name="config_enableShellMainThread">true</bool>
 
-    <!-- SystemUIFactory component -->
-    <string name="config_systemUIFactoryComponent" translatable="false">com.android.systemui.SystemUIInitializerImpl</string>
-
     <!-- QS tile shape store width. negative implies fill configuration instead of stroke-->
     <dimen name="config_qsTileStrokeWidthActive">-1dp</dimen>
     <dimen name="config_qsTileStrokeWidthInactive">-1dp</dimen>
@@ -701,6 +698,10 @@
     -->
     <integer name="config_face_auth_supported_posture">0</integer>
 
+    <!-- Components to allow running fingerprint listening if their activity is occluding the lock screen. -->
+    <string-array name="config_fingerprint_listen_on_occluding_activity_packages" translatable="false">
+    </string-array>
+
     <!-- Whether the communal service should be enabled -->
     <bool name="config_communalServiceEnabled">false</bool>
 
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 4f768cc..3366f4f 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -121,24 +121,26 @@
     <dimen name="navigation_edge_cancelled_arrow_height">0dp</dimen>
     <dimen name="navigation_edge_cancelled_edge_corners">6dp</dimen>
 
-    <!-- Height of notification icons in the status bar -->
+    <!-- New sp height of notification icons in the status bar -->
+    <dimen name="status_bar_icon_size_sp">@*android:dimen/status_bar_icon_size_sp</dimen>
+    <!-- Original dp height of notification icons in the status bar -->
     <dimen name="status_bar_icon_size">@*android:dimen/status_bar_icon_size</dimen>
 
     <!-- Default horizontal drawable padding for status bar icons. -->
-    <dimen name="status_bar_horizontal_padding">2.5dp</dimen>
+    <dimen name="status_bar_horizontal_padding">2.5sp</dimen>
 
     <!-- Height of the battery icon in the status bar. -->
-    <dimen name="status_bar_battery_icon_height">13.0dp</dimen>
+    <dimen name="status_bar_battery_icon_height">13.0sp</dimen>
 
     <!-- Width of the battery icon in the status bar. The battery drawable assumes a 12x20 canvas,
-    so the width of the icon should be 13.0dp * (12.0 / 20.0) -->
-    <dimen name="status_bar_battery_icon_width">7.8dp</dimen>
+    so the width of the icon should be 13.0sp * (12.0 / 20.0) -->
+    <dimen name="status_bar_battery_icon_width">7.8sp</dimen>
 
-    <!-- The battery icon is 13dp tall, but the other system icons are 15dp tall (see
+    <!-- The battery icon is 13sp tall, but the other system icons are 15sp tall (see
          @*android:dimen/status_bar_system_icon_size) with some top and bottom padding embedded in
          the drawables themselves. So, the battery icon may need an extra 1dp of spacing so that its
          bottom still aligns with the bottom of all the other system icons. See b/258672854. -->
-    <dimen name="status_bar_battery_extra_vertical_spacing">1dp</dimen>
+    <dimen name="status_bar_battery_extra_vertical_spacing">1sp</dimen>
 
     <!-- The font size for the clock in the status bar. -->
     <dimen name="status_bar_clock_size">14sp</dimen>
@@ -153,19 +155,26 @@
     <dimen name="status_bar_left_clock_starting_padding">0dp</dimen>
 
     <!-- End padding for left-aligned status bar clock -->
-    <dimen name="status_bar_left_clock_end_padding">2dp</dimen>
+    <dimen name="status_bar_left_clock_end_padding">2sp</dimen>
 
     <!-- Spacing after the wifi signals that is present if there are any icons following it. -->
-    <dimen name="status_bar_wifi_signal_spacer_width">2.5dp</dimen>
+    <dimen name="status_bar_wifi_signal_spacer_width">2.5sp</dimen>
 
+    <!-- Size of the view displaying the wifi inout icon in the status bar. -->
+    <dimen name="status_bar_wifi_inout_container_size">17sp</dimen>
     <!-- Size of the view displaying the wifi signal icon in the status bar. -->
-    <dimen name="status_bar_wifi_signal_size">@*android:dimen/status_bar_system_icon_size</dimen>
+    <dimen name="status_bar_wifi_signal_size">13sp</dimen>
+
+    <!-- Size of the view displaying the mobile inout icon in the status bar. -->
+    <dimen name="status_bar_mobile_inout_container_size">17sp</dimen>
+    <!-- Size of the view displaying the mobile signal icon in the status bar. -->
+    <dimen name="status_bar_mobile_signal_size">13sp</dimen>
 
     <!-- Spacing before the airplane mode icon if there are any icons preceding it. -->
-    <dimen name="status_bar_airplane_spacer_width">4dp</dimen>
+    <dimen name="status_bar_airplane_spacer_width">4sp</dimen>
 
     <!-- Spacing between system icons. -->
-    <dimen name="status_bar_system_icon_spacing">0dp</dimen>
+    <dimen name="status_bar_system_icon_spacing">0sp</dimen>
 
     <!-- The amount to scale each of the status bar icons by. A value of 1 means no scaling. -->
     <item name="status_bar_icon_scale_factor" format="float" type="dimen">1.0</item>
@@ -235,6 +244,9 @@
          and the notification won't use this much, but is measured with wrap_content -->
     <dimen name="notification_messaging_actions_min_height">196dp</dimen>
 
+    <!-- width of ImmersiveModeConfirmation (-1 for match_parent) -->
+    <dimen name="immersive_mode_cling_width">-1px</dimen>
+
     <!-- a threshold in dp per second that is considered fast scrolling -->
     <dimen name="scroll_fast_threshold">1500dp</dimen>
 
@@ -275,6 +287,9 @@
     <!-- The padding at start and end of indication text shown on AOD -->
     <dimen name="keyguard_indication_text_padding">16dp</dimen>
 
+    <!-- The min height on the indication text shown on AOD -->
+    <dimen name="keyguard_indication_text_min_height">48dp</dimen>
+
     <!-- Shadows under the clock, date and other keyguard text fields -->
     <dimen name="keyguard_shadow_radius">5</dimen>
 
@@ -323,8 +338,16 @@
     <!-- opacity at which Notification icons will be drawn in the status bar -->
     <item type="dimen" name="status_bar_icon_drawing_alpha">90%</item>
 
+    <!-- paddings for container with status icons and battery -->
+    <!-- padding start is a bit smaller than end to account for status icon margin-->
+    <dimen name="status_bar_icons_padding_start">11dp</dimen>
+
+    <dimen name="status_bar_icons_padding_end">0dp</dimen>
+    <dimen name="status_bar_icons_padding_bottom">8dp</dimen>
+    <dimen name="status_bar_icons_padding_top">8dp</dimen>
+
     <!-- gap on either side of status bar notification icons -->
-    <dimen name="status_bar_icon_padding">0dp</dimen>
+    <dimen name="status_bar_icon_horizontal_margin">0sp</dimen>
 
     <!-- the padding on the start of the statusbar -->
     <dimen name="status_bar_padding_start">8dp</dimen>
@@ -336,10 +359,10 @@
     <dimen name="status_bar_padding_top">0dp</dimen>
 
     <!-- the radius of the overflow dot in the status bar -->
-    <dimen name="overflow_dot_radius">2dp</dimen>
+    <dimen name="overflow_dot_radius">2sp</dimen>
 
     <!-- the padding between dots in the icon overflow -->
-    <dimen name="overflow_icon_dot_padding">3dp</dimen>
+    <dimen name="overflow_icon_dot_padding">3sp</dimen>
 
     <!-- Dimensions related to screenshots -->
 
@@ -459,6 +482,10 @@
     <dimen name="large_screen_shade_header_height">48dp</dimen>
     <dimen name="large_screen_shade_header_min_height">@dimen/qs_header_row_min_height</dimen>
     <dimen name="large_screen_shade_header_left_padding">@dimen/qs_horizontal_margin</dimen>
+    <dimen name="shade_header_system_icons_height">@dimen/large_screen_shade_header_min_height</dimen>
+    <dimen name="shade_header_system_icons_height_large_screen">32dp</dimen>
+    <dimen name="shade_header_system_icons_padding_start">0dp</dimen>
+    <dimen name="shade_header_system_icons_padding_end">0dp</dimen>
 
     <!-- The top margin of the panel that holds the list of notifications.
          On phones it's always 0dp but it's overridden in Car UI
@@ -636,9 +663,6 @@
 
     <dimen name="restricted_padlock_pading">4dp</dimen>
 
-    <!-- How far the expanded QS panel peeks from the header in collapsed state. -->
-    <dimen name="qs_peek_height">0dp</dimen>
-
     <!-- Padding between subtitles and the following text in the QSFooter dialog -->
     <dimen name="qs_footer_dialog_subtitle_padding">20dp</dimen>
 
@@ -720,8 +744,6 @@
     <dimen name="keyguard_clock_switch_y_shift">14dp</dimen>
     <!-- When large clock is showing, offset the smartspace by this amount -->
     <dimen name="keyguard_smartspace_top_offset">12dp</dimen>
-    <!-- With the large clock, move up slightly from the center -->
-    <dimen name="keyguard_large_clock_top_margin">-60dp</dimen>
 
     <dimen name="notification_scrim_corner_radius">32dp</dimen>
 
@@ -827,7 +849,7 @@
 
     <!-- Padding between the mobile signal indicator and the start icon when the roaming icon
          is displayed in the upper left corner. -->
-    <dimen name="roaming_icon_start_padding">2dp</dimen>
+    <dimen name="roaming_icon_start_padding">2sp</dimen>
 
     <!-- Extra padding between the mobile data type icon and the strength indicator when the data
          type icon is wide for the tile in quick settings. -->
@@ -1053,7 +1075,7 @@
     <!-- Margin between icons of Ongoing App Ops chip -->
     <dimen name="ongoing_appops_chip_icon_margin">4dp</dimen>
     <!-- Icon size of Ongoing App Ops chip -->
-    <dimen name="ongoing_appops_chip_icon_size">16dp</dimen>
+    <dimen name="ongoing_appops_chip_icon_size">16sp</dimen>
     <!-- Radius of Ongoing App Ops chip corners -->
     <dimen name="ongoing_appops_chip_bg_corner_radius">28dp</dimen>
     <!--  One or two privacy items  -->
@@ -1557,7 +1579,8 @@
 
     <!-- Status bar user chip -->
     <dimen name="status_bar_user_chip_avatar_size">16dp</dimen>
-    <dimen name="status_bar_user_chip_end_margin">12dp</dimen>
+    <!-- below also works as break between user chip and hover state of status icons -->
+    <dimen name="status_bar_user_chip_end_margin">4dp</dimen>
     <dimen name="status_bar_user_chip_text_size">12sp</dimen>
 
     <!-- System UI Dialog -->
@@ -1781,7 +1804,6 @@
     <dimen name="dream_overlay_status_bar_ambient_text_shadow_dy">0.5dp</dimen>
     <dimen name="dream_overlay_status_bar_ambient_text_shadow_radius">2dp</dimen>
     <dimen name="dream_overlay_icon_inset_dimen">0dp</dimen>
-    <dimen name="dream_overlay_status_bar_marginTop">22dp</dimen>
 
     <!-- Default device corner radius, used for assist UI -->
     <dimen name="config_rounded_mask_size">0px</dimen>
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index e5c9461..3a2177a 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -206,5 +206,12 @@
 
     <!-- keyboard backlight indicator-->
     <item type="id" name="backlight_icon" />
-</resources>
 
+    <item type="id" name="keyguard_root_view" />
+    <item type="id" name="keyguard_indication_area" />
+    <item type="id" name="keyguard_indication_text" />
+    <item type="id" name="keyguard_indication_text_bottom" />
+    <item type="id" name="nssl_guideline" />
+    <item type="id" name="lock_icon" />
+    <item type="id" name="lock_icon_bg" />
+</resources>
diff --git a/packages/SystemUI/res/values/integers.xml b/packages/SystemUI/res/values/integers.xml
index 675ae32..c925b26 100644
--- a/packages/SystemUI/res/values/integers.xml
+++ b/packages/SystemUI/res/values/integers.xml
@@ -21,6 +21,8 @@
 
     <integer name="magnification_default_scale">2</integer>
 
+    <integer name="dock_enter_exit_duration">250</integer>
+
     <!-- The position of the volume dialog on the screen.
          See com.android.systemui.volume.VolumeDialogImpl.
          Value 21 corresponds to RIGHT|CENTER_VERTICAL. -->
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 070748c..f8c13b0 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -300,6 +300,13 @@
     <!-- A toast message shown when the screen recording cannot be started due to a generic error [CHAR LIMIT=NONE] -->
     <string name="screenrecord_start_error">Error starting screen recording</string>
 
+    <!-- Cling help message title when hiding the navigation bar entering immersive mode [CHAR LIMIT=none] -->
+    <string name="immersive_cling_title">Viewing full screen</string>
+    <!-- Cling help message description when hiding the navigation bar entering immersive mode [CHAR LIMIT=none] -->
+    <string name="immersive_cling_description">To exit, swipe down from the top.</string>
+    <!-- Cling help message confirmation button when hiding the navigation bar entering immersive mode [CHAR LIMIT=30] -->
+    <string name="immersive_cling_positive">Got it</string>
+
     <!-- Content description of the back button for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
     <string name="accessibility_back">Back</string>
     <!-- Content description of the home button for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
@@ -613,8 +620,8 @@
     <string name="quick_settings_bluetooth_secondary_label_headset">Headset</string>
     <!-- QuickSettings: Bluetooth secondary label for an input/IO device being connected [CHAR LIMIT=20]-->
     <string name="quick_settings_bluetooth_secondary_label_input">Input</string>
-    <!-- QuickSettings: Bluetooth secondary label for a Hearing Aids device being connected [CHAR LIMIT=20]-->
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids">Hearing Aids</string>
+    <!-- QuickSettings: Bluetooth secondary label for a Hearing aids device being connected [CHAR LIMIT=20]-->
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids">Hearing aids</string>
     <!-- QuickSettings: Bluetooth secondary label shown when bluetooth is being enabled [CHAR LIMIT=NONE] -->
     <string name="quick_settings_bluetooth_secondary_label_transient">Turning on&#8230;</string>
     <!-- QuickSettings: Brightness [CHAR LIMIT=NONE] -->
@@ -1107,6 +1114,16 @@
     <!-- System sharing media projection permission button to continue. [CHAR LIMIT=60] -->
     <string name="media_projection_entry_generic_permission_dialog_continue">Start</string>
 
+    <!-- Task switcher notification -->
+    <!-- Task switcher notification text. [CHAR LIMIT=100] -->
+    <string name="media_projection_task_switcher_text">Sharing pauses when you switch apps</string>
+    <!-- The action for switching to the foreground task. [CHAR LIMIT=40] -->
+    <string name="media_projection_task_switcher_action_switch">Share this app instead</string>
+    <!-- The action for switching back to the projected task. [CHAR LIMIT=40] -->
+    <string name="media_projection_task_switcher_action_back">Switch back</string>
+    <!-- Task switcher notification channel name. [CHAR LIMIT=40] -->
+    <string name="media_projection_task_switcher_notification_channel">App switch</string>
+
     <!-- Title for the dialog that is shown when screen capturing is disabled by enterprise policy. [CHAR LIMIT=100] -->
     <string name="screen_capturing_disabled_by_policy_dialog_title">Blocked by your IT admin</string>
 
@@ -2378,6 +2395,8 @@
     <string name="magnification_mode_switch_state_window">Magnify part of screen</string>
     <!-- Click action label for magnification settings panel. [CHAR LIMIT=NONE] -->
     <string name="magnification_open_settings_click_label">Open magnification settings</string>
+    <!-- Click action label for magnification settings panel. [CHAR LIMIT=NONE] -->
+    <string name="magnification_close_settings_click_label">Close magnification settings</string>
     <!-- Label of the corner of a rectangle that you can tap and drag to resize the magnification area. [CHAR LIMIT=NONE] -->
     <string name="magnification_drag_corner_to_resize">Drag corner to resize</string>
 
@@ -2803,6 +2822,8 @@
     <!-- Secondary label for alarm tile when there is no next alarm information [CHAR LIMIT=20] -->
     <string name="qs_alarm_tile_no_alarm">No alarm set</string>
 
+    <!-- Accessibility label for a11y action to show the bouncer (pin/pattern/password) screen lock [CHAR LIMIT=NONE] -->
+    <string name="accessibility_bouncer">enter screen lock</string>
     <!-- Accessibility label for fingerprint sensor [CHAR LIMIT=NONE] -->
     <string name="accessibility_fingerprint_label">Fingerprint sensor</string>
     <!-- Accessibility action for tapping on an affordance that will bring up the user's
@@ -2959,9 +2980,11 @@
         <xliff:g id="weather_condition" example="Partly cloudy">%1$s</xliff:g>, <xliff:g id="temperature" example="7°C">%2$s</xliff:g>
     </string>
 
-    <!-- TODO(b/259369672): Replace with final resource. -->
     <!-- [CHAR LIMIT=30] Label used to open Note Task -->
-    <string name="note_task_button_label">Notetaking</string>
+    <string name="note_task_button_label">Note-taking</string>
+
+    <!-- [CHAR LIMIT=25] Long label used by Note Task Shortcut -->
+    <string name="note_task_shortcut_long_label">Note-taking, <xliff:g id="note_taking_app" example="Note-taking App">%1$s</xliff:g></string>
 
     <!-- [CHAR LIMIT=NONE] Le audio broadcast dialog, media app is broadcasting -->
     <string name="broadcasting_description_is_broadcasting">Broadcasting</string>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index cb5342a..6b85621 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -83,7 +83,7 @@
 
     <style name="TextAppearance.QS">
         <item name="android:textStyle">normal</item>
-        <item name="android:textColor">?android:attr/textColorPrimary</item>
+        <item name="android:textColor">?attr/onShadeInactive</item>
         <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
     </style>
 
@@ -93,7 +93,7 @@
 
     <style name="TextAppearance.QS.DetailItemSecondary">
         <item name="android:textSize">@dimen/qs_tile_text_size</item>
-        <item name="android:textColor">?android:attr/colorAccent</item>
+        <item name="android:textColor">?attr/shadeActive</item>
     </style>
 
     <style name="TextAppearance.QS.Introduction">
@@ -117,11 +117,11 @@
 
     <style name="TextAppearance.QS.DataUsage.Usage">
         <item name="android:textSize">@dimen/qs_data_usage_usage_text_size</item>
-        <item name="android:textColor">?android:attr/colorAccent</item>
+        <item name="android:textColor">?attr/shadeActive</item>
     </style>
 
     <style name="TextAppearance.QS.DataUsage.Secondary">
-        <item name="android:textColor">?android:attr/textColorSecondary</item>
+        <item name="android:textColor">?attr/onShadeInactiveVariant</item>
     </style>
 
     <style name="TextAppearance.QS.TileLabel">
@@ -137,31 +137,31 @@
 
     <style name="TextAppearance.QS.UserSwitcher">
         <item name="android:textSize">@dimen/qs_tile_text_size</item>
-        <item name="android:textColor">?android:attr/textColorSecondary</item>
+        <item name="android:textColor">?androidprv:attr/materialColorOnSurfaceVariant</item>
     </style>
 
     <!-- This is hard coded to be sans-serif-condensed to match the icons -->
 
     <style name="TextAppearance.QS.Status">
         <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
-        <item name="android:textColor">?android:attr/textColorPrimary</item>
+        <item name="android:textColor">?attr/onSurface</item>
         <item name="android:textSize">14sp</item>
         <item name="android:letterSpacing">0.01</item>
     </style>
 
     <style name="TextAppearance.QS.SecurityFooter" parent="@style/TextAppearance.QS.Status">
         <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
-        <item name="android:textColor">?android:attr/textColorSecondary</item>
+        <item name="android:textColor">?attr/onSurface</item>
     </style>
 
     <style name="TextAppearance.QS.Status.Carriers" />
 
     <style name="TextAppearance.QS.Status.Carriers.NoCarrierText">
-        <item name="android:textColor">?android:attr/textColorSecondary</item>
+        <item name="android:textColor">?attr/onSurfaceVariant</item>
     </style>
 
     <style name="TextAppearance.QS.Status.Build">
-        <item name="android:textColor">?android:attr/textColorSecondary</item>
+        <item name="android:textColor">?attr/onSurfaceVariant</item>
     </style>
 
     <style name="TextAppearance.DeviceManagementDialog.Title" parent="@android:style/TextAppearance.DeviceDefault.DialogWindowTitle"/>
@@ -278,10 +278,10 @@
 
     <style name="DeviceManagementDialogTitle">
         <item name="android:gravity">center</item>
-        <item name="android:textAppearance">@style/TextAppearance.DeviceManagementDialog.Title</item>
+        <item name="android:textAppearance">@style/TextAppearance.Dialog.Title</item>
     </style>
 
-    <style name="TextAppearance.DeviceManagementDialog.Content" parent="@*android:style/TextAppearance.DeviceDefault.Subhead"/>
+    <style name="TextAppearance.DeviceManagementDialog.Content" parent="@style/TextAppearance.Dialog.Body.Message"/>
 
     <style name="BaseBrightnessDialogContainer" parent="@style/Theme.SystemUI">
         <item name="android:layout_width">match_parent</item>
@@ -371,18 +371,35 @@
     </style>
 
     <style name="Theme.SystemUI.QuickSettings" parent="@*android:style/Theme.DeviceDefault">
+        <item name="isQsTheme">true</item>
         <item name="lightIconTheme">@style/QSIconTheme</item>
         <item name="darkIconTheme">@style/QSIconTheme</item>
         <item name="android:colorError">@*android:color/error_color_material_dark</item>
         <item name="android:windowIsFloating">true</item>
         <item name="android:homeAsUpIndicator">@drawable/ic_arrow_back</item>
-        <item name="offStateColor">@color/material_dynamic_neutral20</item>
-        <item name="underSurfaceColor">@color/material_dynamic_neutral0</item>
-        <item name="android:colorBackground">@color/material_dynamic_neutral10</item>
+
+        <item name="surfaceBright">?androidprv:attr/materialColorSurfaceBright</item>
+        <item name="android:colorBackground">?attr/surfaceBright</item>
+        <item name="scHigh">?androidprv:attr/materialColorSurfaceContainerHigh</item>
+        <item name="primary">?androidprv:attr/materialColorPrimary</item>
+        <item name="tertiary">?androidprv:attr/materialColorTertiary</item>
+        <item name="onSurface">?androidprv:attr/materialColorOnSurface</item>
+        <item name="onSurfaceVariant">?androidprv:attr/materialColorOnSurfaceVariant</item>
+        <item name="outline">?androidprv:attr/materialColorOutline</item>
+
+        <item name="shadeActive">@color/material_dynamic_primary90</item>
+        <item name="onShadeActive">@color/material_dynamic_primary10</item>
+        <item name="onShadeActiveVariant">@color/material_dynamic_primary30</item>
+        <item name="shadeInactive">@color/material_dynamic_neutral20</item>
+        <item name="onShadeInactive">@color/material_dynamic_neutral90</item>
+        <item name="onShadeInactiveVariant">@color/material_dynamic_neutral_variant80</item>
+        <item name="shadeDisabled">@color/shade_disabled</item>
+        <item name="underSurface">@color/material_dynamic_neutral0</item>
         <item name="android:itemTextAppearance">@style/Control.MenuItem</item>
     </style>
 
-    <style name="Theme.SystemUI.QuickSettings.BrightnessDialog" parent="@android:style/Theme.DeviceDefault.Dialog">
+    <!-- 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>
     </style>
 
@@ -390,8 +407,15 @@
         <item name="android:windowBackground">@android:color/transparent</item>
     </style>
 
-    <style name="Theme.SystemUI.QuickSettings.Dialog" parent="@android:style/Theme.DeviceDefault.Dialog">
+    <style name="Theme.SystemUI.QuickSettings.Dialog" parent="@style/Theme.SystemUI.Dialog.QuickSettings">
+    </style>
+
+    <!-- Parent style overrides style in the dot inheritance -->
+    <style name="Theme.SystemUI.Dialog.QuickSettings" parent="@style/Theme.SystemUI.QuickSettings">
         <item name="android:dialogCornerRadius">@dimen/notification_corner_radius</item>
+        <item name="android:buttonBarPositiveButtonStyle">@style/Widget.Dialog.Button.QuickSettings</item>
+        <item name="android:buttonBarNegativeButtonStyle">@style/Widget.Dialog.Button.QuickSettings</item>
+        <item name="android:buttonBarNeutralButtonStyle">@style/Widget.Dialog.Button.QuickSettings</item>
     </style>
 
     <!-- Overridden by values-television/styles.xml with tv-specific settings -->
@@ -406,7 +430,7 @@
         <item name="android:buttonBarPositiveButtonStyle">@style/Widget.Dialog.Button</item>
         <item name="android:buttonBarNegativeButtonStyle">@style/Widget.Dialog.Button.BorderButton</item>
         <item name="android:buttonBarNeutralButtonStyle">@style/Widget.Dialog.Button.BorderButton</item>
-        <item name="android:colorBackground">?androidprv:attr/colorSurface</item>
+        <item name="android:colorBackground">?androidprv:attr/materialColorSurfaceBright</item>
         <item name="android:alertDialogStyle">@style/ScrollableAlertDialogStyle</item>
         <item name="android:buttonBarStyle">@style/ButtonBarStyle</item>
         <item name="android:buttonBarButtonStyle">@style/Widget.Dialog.Button.Large</item>
@@ -605,11 +629,11 @@
         <item name="android:letterSpacing">0.01</item>
         <item name="android:lineHeight">20sp</item>
         <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
-        <item name="android:textColor">?android:attr/textColorSecondary</item>
+        <item name="android:textColor">?attr/onSurfaceVariant</item>
     </style>
 
     <style name="QSCustomizeToolbar" parent="@*android:style/Widget.DeviceDefault.Toolbar">
-        <item name="android:textColor">?android:attr/textColorPrimary</item>
+        <item name="android:textColor">?attr/onSurface</item>
         <item name="android:elevation">10dp</item>
     </style>
 
@@ -1055,7 +1079,7 @@
     </style>
 
     <style name="TextAppearance.Dialog.Title" parent="@android:style/TextAppearance.DeviceDefault.Large">
-        <item name="android:textColor">?android:attr/textColorPrimary</item>
+        <item name="android:textColor">?androidprv:attr/materialColorOnSurface</item>
         <item name="android:textSize">@dimen/dialog_title_text_size</item>
         <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
         <item name="android:lineHeight">32sp</item>
@@ -1064,7 +1088,7 @@
     </style>
 
     <style name="TextAppearance.Dialog.Body" parent="@android:style/TextAppearance.DeviceDefault.Medium">
-        <item name="android:textColor">?android:attr/textColorSecondary</item>
+        <item name="android:textColor">?androidprv:attr/materialColorOnSurfaceVariant</item>
         <item name="android:textSize">14sp</item>
         <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
         <item name="android:lineHeight">20sp</item>
@@ -1092,7 +1116,7 @@
     <style name="Widget.Dialog.Button">
         <item name="android:buttonCornerRadius">28dp</item>
         <item name="android:background">@drawable/qs_dialog_btn_filled</item>
-        <item name="android:textColor">?androidprv:attr/textColorOnAccent</item>
+        <item name="android:textColor">?androidprv:attr/materialColorOnPrimary</item>
         <item name="android:textSize">14sp</item>
         <item name="android:lineHeight">20sp</item>
         <item name="android:fontFamily">@*android:string/config_bodyFontFamilyMedium</item>
@@ -1102,12 +1126,18 @@
 
     <style name="Widget.Dialog.Button.BorderButton">
         <item name="android:background">@drawable/qs_dialog_btn_outline</item>
-        <item name="android:textColor">?android:attr/textColorPrimary</item>
+        <item name="android:textColor">?androidprv:attr/materialColorOnSurface</item>
     </style>
 
     <style name="Widget.Dialog.Button.Large">
         <item name="android:background">@drawable/qs_dialog_btn_filled_large</item>
         <item name="android:minHeight">56dp</item>
+        <item name="android:textColor">?androidprv:attr/materialColorOnPrimaryFixed</item>
+    </style>
+
+    <style name="Widget.Dialog.Button.QuickSettings">
+        <item name="android:textColor">?attr/primary</item>
+        <item name="android:background">?android:attr/selectableItemBackground</item>
     </style>
 
     <style name="MainSwitch.Settingslib" parent="@android:style/Theme.DeviceDefault">
diff --git a/packages/SystemUI/res/xml/combined_qs_header_scene.xml b/packages/SystemUI/res/xml/combined_qs_header_scene.xml
index 8512f6f..c167256 100644
--- a/packages/SystemUI/res/xml/combined_qs_header_scene.xml
+++ b/packages/SystemUI/res/xml/combined_qs_header_scene.xml
@@ -53,56 +53,28 @@
                 android:alpha="0"
                 />
             <KeyPosition
+                app:motionTarget="@id/shade_header_system_icons"
                 app:keyPositionType="deltaRelative"
                 app:percentX="0"
                 app:percentY="@dimen/percent_displacement_at_fade_out"
                 app:framePosition="@integer/fade_out_complete_frame"
                 app:sizePercent="0"
-                app:curveFit="linear"
-                app:motionTarget="@id/statusIcons" />
+                app:curveFit="linear" />
             <KeyPosition
+                app:motionTarget="@id/shade_header_system_icons"
                 app:keyPositionType="deltaRelative"
                 app:percentX="1"
                 app:percentY="0.5"
                 app:framePosition="50"
                 app:sizePercent="1"
-                app:curveFit="linear"
-                app:motionTarget="@id/statusIcons" />
+                app:curveFit="linear" />
             <KeyAttribute
-                app:motionTarget="@id/statusIcons"
+                app:motionTarget="@id/shade_header_system_icons"
                 app:framePosition="@integer/fade_out_complete_frame"
                 android:alpha="0"
                 />
             <KeyAttribute
-                app:motionTarget="@id/statusIcons"
-                app:framePosition="@integer/fade_in_start_frame"
-                android:alpha="0"
-                />
-            <KeyPosition
-                app:keyPositionType="deltaRelative"
-                app:percentX="0"
-                app:percentY="@dimen/percent_displacement_at_fade_out"
-                app:framePosition="@integer/fade_out_complete_frame"
-                app:percentWidth="1"
-                app:percentHeight="1"
-                app:curveFit="linear"
-                app:motionTarget="@id/batteryRemainingIcon" />
-            <KeyPosition
-                app:keyPositionType="deltaRelative"
-                app:percentX="1"
-                app:percentY="0.5"
-                app:framePosition="50"
-                app:percentWidth="1"
-                app:percentHeight="1"
-                app:curveFit="linear"
-                app:motionTarget="@id/batteryRemainingIcon" />
-            <KeyAttribute
-                app:motionTarget="@id/batteryRemainingIcon"
-                app:framePosition="@integer/fade_out_complete_frame"
-                android:alpha="0"
-                />
-            <KeyAttribute
-                app:motionTarget="@id/batteryRemainingIcon"
+                app:motionTarget="@id/shade_header_system_icons"
                 app:framePosition="@integer/fade_in_start_frame"
                 android:alpha="0"
                 />
diff --git a/packages/SystemUI/res/xml/large_screen_shade_header.xml b/packages/SystemUI/res/xml/large_screen_shade_header.xml
index 3881e8c..cb2c3a1 100644
--- a/packages/SystemUI/res/xml/large_screen_shade_header.xml
+++ b/packages/SystemUI/res/xml/large_screen_shade_header.xml
@@ -45,7 +45,7 @@
             android:layout_height="0dp"
             android:layout_gravity="end|center_vertical"
             app:layout_constraintBottom_toBottomOf="parent"
-            app:layout_constraintEnd_toStartOf="@id/statusIcons"
+            app:layout_constraintEnd_toStartOf="@id/shade_header_system_icons"
             app:layout_constraintStart_toEndOf="@id/date"
             app:layout_constraintTop_toTopOf="parent"
             app:layout_constraintWidth_default="wrap"
@@ -53,25 +53,14 @@
         <PropertySet android:alpha="1" />
     </Constraint>
 
-    <Constraint android:id="@+id/statusIcons">
+    <Constraint android:id="@+id/shade_header_system_icons">
         <Layout
             android:layout_width="wrap_content"
-            android:layout_height="@dimen/large_screen_shade_header_min_height"
-            app:layout_constraintBottom_toBottomOf="parent"
-            app:layout_constraintEnd_toStartOf="@id/batteryRemainingIcon"
-            app:layout_constraintTop_toTopOf="parent"
-            app:layout_constraintStart_toEndOf="@id/carrier_group"/>
-        <PropertySet android:alpha="1" />
-    </Constraint>
-
-    <Constraint android:id="@+id/batteryRemainingIcon">
-        <Layout
-            android:layout_width="wrap_content"
-            android:layout_height="0dp"
-            app:layout_constraintHeight_min="@dimen/large_screen_shade_header_min_height"
+            android:layout_height="@dimen/shade_header_system_icons_height_large_screen"
             app:layout_constraintBottom_toBottomOf="parent"
             app:layout_constraintEnd_toStartOf="@id/privacy_container"
-            app:layout_constraintTop_toTopOf="parent" />
+            app:layout_constraintTop_toTopOf="parent"
+            app:layout_constraintEnd_toEndOf="@id/carrier_group"/>
         <PropertySet android:alpha="1" />
     </Constraint>
 
diff --git a/packages/SystemUI/res/xml/media_recommendation_collapsed.xml b/packages/SystemUI/res/xml/media_recommendation_collapsed.xml
deleted file mode 100644
index b7d4b3a..0000000
--- a/packages/SystemUI/res/xml/media_recommendation_collapsed.xml
+++ /dev/null
@@ -1,101 +0,0 @@
-<?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
-  -->
-<ConstraintSet
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto" >
-
-    <Constraint
-        android:id="@+id/sizing_view"
-        android:layout_width="match_parent"
-        android:layout_height="@dimen/qs_media_session_height_collapsed"
-        />
-
-    <!-- Only the constraintBottom and marginBottom are different. The rest of the constraints are
-         the same as the constraints in media_recommendations_expanded.xml. But, due to how
-         ConstraintSets work, all the constraints need to be in the same place. So, the shared
-         constraints can't be put in the shared layout file media_smartspace_recommendations.xml and
-         the constraints are instead duplicated between here and media_recommendations_expanded.xml.
-         Ditto for the other cover containers. -->
-    <Constraint
-        android:id="@+id/media_cover1_container"
-        app:layout_constraintBottom_toBottomOf="parent"
-        android:layout_marginBottom="@dimen/qs_media_padding"
-        style="@style/MediaPlayer.Recommendation.AlbumContainer"
-        app:layout_constraintTop_toTopOf="parent"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintEnd_toStartOf="@id/media_cover2_container"
-        android:layout_marginEnd="@dimen/qs_media_rec_album_side_margin"
-        app:layout_constraintHorizontal_chainStyle="packed"
-        app:layout_constraintHorizontal_bias="1.0"
-        app:layout_constraintVertical_bias="0.5"
-        />
-
-    <Constraint
-        android:id="@+id/media_title1"
-        android:visibility="gone"
-        />
-
-    <Constraint
-        android:id="@+id/media_subtitle1"
-        android:visibility="gone"
-        />
-
-    <Constraint
-        android:id="@+id/media_cover2_container"
-        app:layout_constraintBottom_toBottomOf="parent"
-        android:layout_marginBottom="@dimen/qs_media_padding"
-        style="@style/MediaPlayer.Recommendation.AlbumContainer"
-        app:layout_constraintTop_toTopOf="parent"
-        app:layout_constraintStart_toEndOf="@id/media_cover1_container"
-        app:layout_constraintEnd_toStartOf="@id/media_cover3_container"
-        android:layout_marginEnd="@dimen/qs_media_rec_album_side_margin"
-        app:layout_constraintVertical_bias="0.5"
-        />
-
-    <Constraint
-        android:id="@+id/media_title2"
-        android:visibility="gone"
-        />
-
-    <Constraint
-        android:id="@+id/media_subtitle2"
-        android:visibility="gone"
-        />
-
-    <Constraint
-        android:id="@+id/media_cover3_container"
-        app:layout_constraintBottom_toBottomOf="parent"
-        android:layout_marginBottom="@dimen/qs_media_padding"
-        style="@style/MediaPlayer.Recommendation.AlbumContainer"
-        app:layout_constraintTop_toTopOf="parent"
-        app:layout_constraintStart_toEndOf="@id/media_cover2_container"
-        app:layout_constraintEnd_toEndOf="parent"
-        android:layout_marginEnd="@dimen/qs_media_padding"
-        app:layout_constraintVertical_bias="0.5"
-        />
-
-    <Constraint
-        android:id="@+id/media_title3"
-        android:visibility="gone"
-        />
-
-    <Constraint
-        android:id="@+id/media_subtitle3"
-        android:visibility="gone"
-        />
-
-</ConstraintSet>
diff --git a/packages/SystemUI/res/xml/media_recommendation_expanded.xml b/packages/SystemUI/res/xml/media_recommendation_expanded.xml
deleted file mode 100644
index ce25a7d..0000000
--- a/packages/SystemUI/res/xml/media_recommendation_expanded.xml
+++ /dev/null
@@ -1,123 +0,0 @@
-<?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
-  -->
-<ConstraintSet
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
-    >
-
-    <Constraint
-        android:id="@+id/sizing_view"
-        android:layout_width="match_parent"
-        android:layout_height="@dimen/qs_media_session_height_expanded"
-        />
-
-    <Constraint
-        android:id="@+id/media_cover1_container"
-        style="@style/MediaPlayer.Recommendation.AlbumContainer"
-        app:layout_constraintTop_toTopOf="parent"
-        app:layout_constraintBottom_toTopOf="@+id/media_title1"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintEnd_toStartOf="@id/media_cover2_container"
-        android:layout_marginEnd="@dimen/qs_media_rec_album_side_margin"
-        app:layout_constraintHorizontal_chainStyle="packed"
-        app:layout_constraintVertical_chainStyle="packed"
-        app:layout_constraintHorizontal_bias="1.0"
-        app:layout_constraintVertical_bias="0.4"
-        />
-
-    <Constraint
-        android:id="@+id/media_title1"
-        style="@style/MediaPlayer.Recommendation.Text.Title"
-        app:layout_constraintStart_toStartOf="@+id/media_cover1_container"
-        app:layout_constraintEnd_toEndOf="@+id/media_cover1_container"
-        app:layout_constraintTop_toBottomOf="@+id/media_cover1_container"
-        app:layout_constraintBottom_toTopOf="@+id/media_subtitle1"
-        />
-
-    <Constraint
-        android:id="@+id/media_subtitle1"
-        style="@style/MediaPlayer.Recommendation.Text.Subtitle"
-        app:layout_constraintStart_toStartOf="@+id/media_cover1_container"
-        app:layout_constraintEnd_toEndOf="@+id/media_cover1_container"
-        app:layout_constraintTop_toBottomOf="@+id/media_title1"
-        app:layout_constraintBottom_toBottomOf="parent"
-        android:layout_marginBottom="@dimen/qs_media_padding"
-        />
-
-    <Constraint
-        android:id="@+id/media_cover2_container"
-        style="@style/MediaPlayer.Recommendation.AlbumContainer"
-        app:layout_constraintTop_toTopOf="parent"
-        app:layout_constraintBottom_toTopOf="@id/media_title2"
-        app:layout_constraintStart_toEndOf="@id/media_cover1_container"
-        app:layout_constraintEnd_toStartOf="@id/media_cover3_container"
-        android:layout_marginEnd="@dimen/qs_media_rec_album_side_margin"
-        app:layout_constraintVertical_chainStyle="packed"
-        app:layout_constraintVertical_bias="0.4"
-        />
-
-    <Constraint
-        android:id="@+id/media_title2"
-        style="@style/MediaPlayer.Recommendation.Text.Title"
-        app:layout_constraintStart_toStartOf="@+id/media_cover2_container"
-        app:layout_constraintEnd_toEndOf="@+id/media_cover2_container"
-        app:layout_constraintTop_toBottomOf="@+id/media_cover2_container"
-        app:layout_constraintBottom_toTopOf="@+id/media_subtitle2"
-        />
-
-    <Constraint
-        android:id="@+id/media_subtitle2"
-        style="@style/MediaPlayer.Recommendation.Text.Subtitle"
-        app:layout_constraintStart_toStartOf="@+id/media_cover2_container"
-        app:layout_constraintEnd_toEndOf="@+id/media_cover2_container"
-        app:layout_constraintTop_toBottomOf="@+id/media_title2"
-        app:layout_constraintBottom_toBottomOf="parent"
-        android:layout_marginBottom="@dimen/qs_media_padding"
-        />
-
-    <Constraint
-        android:id="@+id/media_cover3_container"
-        style="@style/MediaPlayer.Recommendation.AlbumContainer"
-        app:layout_constraintTop_toTopOf="parent"
-        app:layout_constraintBottom_toTopOf="@id/media_title3"
-        app:layout_constraintStart_toEndOf="@id/media_cover2_container"
-        app:layout_constraintEnd_toEndOf="parent"
-        android:layout_marginEnd="@dimen/qs_media_padding"
-        app:layout_constraintVertical_chainStyle="packed"
-        app:layout_constraintVertical_bias="0.4"
-        />
-
-    <Constraint
-        android:id="@+id/media_title3"
-        style="@style/MediaPlayer.Recommendation.Text.Title"
-        app:layout_constraintStart_toStartOf="@+id/media_cover3_container"
-        app:layout_constraintEnd_toEndOf="@+id/media_cover3_container"
-        app:layout_constraintTop_toBottomOf="@+id/media_cover3_container"
-        app:layout_constraintBottom_toTopOf="@+id/media_subtitle3"
-        />
-
-    <Constraint
-        android:id="@+id/media_subtitle3"
-        style="@style/MediaPlayer.Recommendation.Text.Subtitle"
-        app:layout_constraintStart_toStartOf="@+id/media_cover3_container"
-        app:layout_constraintEnd_toEndOf="@+id/media_cover3_container"
-        app:layout_constraintTop_toBottomOf="@+id/media_title3"
-        app:layout_constraintBottom_toBottomOf="parent"
-        android:layout_marginBottom="@dimen/qs_media_padding"
-        />
-
-</ConstraintSet>
diff --git a/packages/SystemUI/res/xml/media_recommendations_view_collapsed.xml b/packages/SystemUI/res/xml/media_recommendations_collapsed.xml
similarity index 100%
rename from packages/SystemUI/res/xml/media_recommendations_view_collapsed.xml
rename to packages/SystemUI/res/xml/media_recommendations_collapsed.xml
diff --git a/packages/SystemUI/res/xml/media_recommendations_view_expanded.xml b/packages/SystemUI/res/xml/media_recommendations_expanded.xml
similarity index 100%
rename from packages/SystemUI/res/xml/media_recommendations_view_expanded.xml
rename to packages/SystemUI/res/xml/media_recommendations_expanded.xml
diff --git a/packages/SystemUI/res/xml/qqs_header.xml b/packages/SystemUI/res/xml/qqs_header.xml
index 1950965..50a388d 100644
--- a/packages/SystemUI/res/xml/qqs_header.xml
+++ b/packages/SystemUI/res/xml/qqs_header.xml
@@ -54,27 +54,12 @@
     </Constraint>
 
     <Constraint
-        android:id="@+id/statusIcons">
+        android:id="@+id/shade_header_system_icons">
         <Layout
             android:layout_width="wrap_content"
             android:layout_height="@dimen/new_qs_header_non_clickable_element_height"
             app:layout_constraintHeight_min="@dimen/new_qs_header_non_clickable_element_height"
             app:layout_constraintStart_toEndOf="@id/date"
-            app:layout_constraintEnd_toStartOf="@id/batteryRemainingIcon"
-            app:layout_constraintTop_toTopOf="parent"
-            app:layout_constraintBottom_toBottomOf="@id/qqs_header_bottom_guideline"
-            app:layout_constraintHorizontal_bias="1"
-            app:layout_constraintHorizontal_chainStyle="packed"
-            />
-    </Constraint>
-
-    <Constraint
-        android:id="@+id/batteryRemainingIcon">
-        <Layout
-            android:layout_width="wrap_content"
-            android:layout_height="@dimen/new_qs_header_non_clickable_element_height"
-            app:layout_constraintHeight_min="@dimen/new_qs_header_non_clickable_element_height"
-            app:layout_constraintStart_toEndOf="@id/statusIcons"
             app:layout_constraintEnd_toEndOf="@id/end_guide"
             app:layout_constraintTop_toTopOf="parent"
             app:layout_constraintBottom_toBottomOf="@id/qqs_header_bottom_guideline"
diff --git a/packages/SystemUI/res/xml/qs_header.xml b/packages/SystemUI/res/xml/qs_header.xml
index 8039c68..7b4282f 100644
--- a/packages/SystemUI/res/xml/qs_header.xml
+++ b/packages/SystemUI/res/xml/qs_header.xml
@@ -59,7 +59,7 @@
         />
     </Constraint>
 
-    <!-- LargeScreenShadeHeaderController helps with managing clock width to layout this view -->
+    <!-- ShadeHeaderController helps with managing clock width to layout this view -->
     <Constraint
         android:id="@+id/carrier_group">
         <Layout
@@ -78,25 +78,11 @@
     </Constraint>
 
     <Constraint
-        android:id="@+id/statusIcons">
+        android:id="@+id/shade_header_system_icons">
         <Layout
             android:layout_width="0dp"
             android:layout_height="@dimen/new_qs_header_non_clickable_element_height"
             app:layout_constraintWidth_default="wrap"
-            app:layout_constraintStart_toEndOf="@id/date"
-            app:layout_constraintEnd_toStartOf="@id/batteryRemainingIcon"
-            app:layout_constraintTop_toTopOf="@id/date"
-            app:layout_constraintBottom_toBottomOf="@id/date"
-            />
-    </Constraint>
-
-    <Constraint
-        android:id="@+id/batteryRemainingIcon">
-        <Layout
-            android:layout_width="0dp"
-            android:layout_height="@dimen/new_qs_header_non_clickable_element_height"
-            app:layout_constraintWidth_default="wrap"
-            app:layout_constraintHeight_min="@dimen/new_qs_header_non_clickable_element_height"
             app:layout_constraintEnd_toEndOf="parent"
             app:layout_constraintTop_toTopOf="@id/date"
             app:layout_constraintBottom_toBottomOf="@id/date"
diff --git a/packages/SystemUI/screenshot/Android.bp b/packages/SystemUI/screenshot/Android.bp
deleted file mode 100644
index f449398..0000000
--- a/packages/SystemUI/screenshot/Android.bp
+++ /dev/null
@@ -1,45 +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 {
-    // See: http://go/android-license-faq
-    // A large-scale-change added 'default_applicable_licenses' to import
-    // all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license"
-    // to get the below license kinds:
-    //   SPDX-license-identifier-Apache-2.0
-    default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"],
-}
-
-android_library {
-    name: "SystemUIScreenshotLib",
-    manifest: "AndroidManifest.xml",
-
-    srcs: [
-        "src/**/*.kt",
-    ],
-
-    resource_dirs: [
-        "res",
-    ],
-
-    static_libs: [
-        "SystemUI-core",
-        "androidx.test.espresso.core",
-        "androidx.appcompat_appcompat",
-        "platform-screenshot-diff-core",
-        "guava",
-    ],
-
-    kotlincflags: ["-Xjvm-default=all"],
-}
diff --git a/packages/SystemUI/screenshot/AndroidManifest.xml b/packages/SystemUI/screenshot/AndroidManifest.xml
deleted file mode 100644
index ba3dc8c..0000000
--- a/packages/SystemUI/screenshot/AndroidManifest.xml
+++ /dev/null
@@ -1,27 +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.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.systemui.testing.screenshot">
-    <application>
-        <activity
-            android:name="com.android.systemui.testing.screenshot.ScreenshotActivity"
-            android:configChanges="orientation|screenSize|screenLayout|smallestScreenSize"
-            android:exported="true"
-            android:theme="@style/Theme.SystemUI.Screenshot" />
-    </application>
-</manifest>
diff --git a/packages/SystemUI/screenshot/res/values/themes.xml b/packages/SystemUI/screenshot/res/values/themes.xml
deleted file mode 100644
index a7f8a26..0000000
--- a/packages/SystemUI/screenshot/res/values/themes.xml
+++ /dev/null
@@ -1,31 +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.
--->
-<resources>
-    <style name="Theme.SystemUI.Screenshot" parent="Theme.SystemUI">
-        <item name="android:windowActionBar">false</item>
-        <item name="android:windowNoTitle">true</item>
-
-        <!-- We make the status and navigation bars transparent so that the screenshotted content is
-             not clipped by the status bar height when drawn into the Bitmap (which is what happens
-             given that we draw the view into the Bitmap using hardware acceleration). -->
-        <item name="android:statusBarColor">@android:color/transparent</item>
-        <item name="android:navigationBarColor">@android:color/transparent</item>
-
-        <!-- Make sure that device specific cutouts don't impact the outcome of screenshot tests -->
-        <item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
-    </style>
-</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/Bitmap.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/Bitmap.kt
deleted file mode 100644
index a4a70a4..0000000
--- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/Bitmap.kt
+++ /dev/null
@@ -1,66 +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.systemui.testing.screenshot
-
-import android.graphics.Bitmap
-import android.graphics.Canvas
-import android.os.Build
-import android.view.View
-import platform.test.screenshot.matchers.MSSIMMatcher
-import platform.test.screenshot.matchers.PixelPerfectMatcher
-
-/** Draw this [View] into a [Bitmap]. */
-// TODO(b/195673633): Remove this once Compose screenshot tests use hardware rendering for their
-// tests.
-fun View.drawIntoBitmap(): Bitmap {
-    val bitmap =
-        Bitmap.createBitmap(
-            measuredWidth,
-            measuredHeight,
-            Bitmap.Config.ARGB_8888,
-        )
-    val canvas = Canvas(bitmap)
-    draw(canvas)
-    return bitmap
-}
-
-/**
- * The [BitmapMatcher][platform.test.screenshot.matchers.BitmapMatcher] that should be used for
- * screenshot *unit* tests.
- */
-val UnitTestBitmapMatcher =
-    if (Build.CPU_ABI == "x86_64") {
-        // Different CPU architectures can sometimes end up rendering differently, so we can't do
-        // pixel-perfect matching on different architectures using the same golden. Given that our
-        // presubmits are run on cf_x86_64_phone, our goldens should be perfectly matched on the
-        // x86_64 architecture and use the Structural Similarity Index on others.
-        // TODO(b/237511747): Run our screenshot presubmit tests on arm64 instead so that we can
-        // do pixel perfect matching both at presubmit time and at development time with actual
-        // devices.
-        PixelPerfectMatcher()
-    } else {
-        MSSIMMatcher()
-    }
-
-/**
- * The [BitmapMatcher][platform.test.screenshot.matchers.BitmapMatcher] that should be used for
- * screenshot *unit* tests.
- *
- * We use the Structural Similarity Index for integration tests because they usually contain
- * additional information and noise that shouldn't break the test.
- */
-val IntegrationTestBitmapMatcher = MSSIMMatcher()
diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ExternalViewScreenshotTestRule.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ExternalViewScreenshotTestRule.kt
deleted file mode 100644
index e032bb9..0000000
--- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ExternalViewScreenshotTestRule.kt
+++ /dev/null
@@ -1,107 +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.systemui.testing.screenshot
-
-import android.app.Activity
-import android.graphics.Color
-import android.view.View
-import android.view.Window
-import android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
-import androidx.core.view.WindowInsetsCompat
-import androidx.core.view.WindowInsetsControllerCompat
-import androidx.core.view.WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
-import androidx.test.platform.app.InstrumentationRegistry
-import org.junit.rules.RuleChain
-import org.junit.rules.TestRule
-import org.junit.runner.Description
-import org.junit.runners.model.Statement
-import platform.test.screenshot.*
-
-/**
- * A rule that allows to run a screenshot diff test on a view that is hosted in another activity.
- */
-class ExternalViewScreenshotTestRule(
-    emulationSpec: DeviceEmulationSpec,
-    assetPathRelativeToBuildRoot: String
-) : TestRule {
-
-    private val colorsRule = MaterialYouColorsRule()
-    private val deviceEmulationRule = DeviceEmulationRule(emulationSpec)
-    private val screenshotRule =
-        ScreenshotTestRule(
-            SystemUIGoldenImagePathManager(
-                getEmulatedDevicePathConfig(emulationSpec),
-                assetPathRelativeToBuildRoot
-            )
-        )
-    private val delegateRule =
-        RuleChain.outerRule(colorsRule).around(deviceEmulationRule).around(screenshotRule)
-    private val matcher = UnitTestBitmapMatcher
-
-    override fun apply(base: Statement, description: Description): Statement {
-        return delegateRule.apply(base, description)
-    }
-
-    /**
-     * Compare the content of the [view] with the golden image identified by [goldenIdentifier] in
-     * the context of [emulationSpec]. Window must be specified to capture views that render
-     * hardware buffers.
-     */
-    fun screenshotTest(goldenIdentifier: String, view: View, window: Window? = null) {
-        view.removeElevationRecursively()
-
-        ScreenshotRuleAsserter.Builder(screenshotRule)
-            .setScreenshotProvider { view.toBitmap(window) }
-            .withMatcher(matcher)
-            .build()
-            .assertGoldenImage(goldenIdentifier)
-    }
-
-    /**
-     * Compare the content of the [activity] with the golden image identified by [goldenIdentifier]
-     * in the context of [emulationSpec].
-     */
-    fun activityScreenshotTest(
-        goldenIdentifier: String,
-        activity: Activity,
-    ) {
-        val rootView = activity.window.decorView
-
-        // Hide system bars, remove insets, focus and make sure device-specific cutouts
-        // don't affect screenshots
-        InstrumentationRegistry.getInstrumentation().runOnMainSync {
-            val window = activity.window
-            window.setDecorFitsSystemWindows(false)
-            WindowInsetsControllerCompat(window, rootView).apply {
-                hide(WindowInsetsCompat.Type.systemBars())
-                systemBarsBehavior = BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
-            }
-
-            window.statusBarColor = Color.TRANSPARENT
-            window.navigationBarColor = Color.TRANSPARENT
-            window.attributes =
-                window.attributes.apply {
-                    layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
-                }
-
-            rootView.removeInsetsRecursively()
-            activity.currentFocus?.clearFocus()
-        }
-
-        screenshotTest(goldenIdentifier, rootView, activity.window)
-    }
-}
diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ScreenshotActivity.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ScreenshotActivity.kt
deleted file mode 100644
index 2a55a80..0000000
--- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ScreenshotActivity.kt
+++ /dev/null
@@ -1,22 +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.systemui.testing.screenshot
-
-import androidx.activity.ComponentActivity
-
-/** The Activity that is launched and whose content is set for screenshot tests. */
-class ScreenshotActivity : ComponentActivity()
diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/SystemUIGoldenImagePathManager.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/SystemUIGoldenImagePathManager.kt
deleted file mode 100644
index 72d8c5a..0000000
--- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/SystemUIGoldenImagePathManager.kt
+++ /dev/null
@@ -1,44 +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.systemui.testing.screenshot
-
-import androidx.test.platform.app.InstrumentationRegistry
-import platform.test.screenshot.GoldenImagePathManager
-import platform.test.screenshot.PathConfig
-
-/** A [GoldenImagePathManager] that should be used for all SystemUI screenshot tests. */
-class SystemUIGoldenImagePathManager(
-    pathConfig: PathConfig,
-    assetsPathRelativeToBuildRoot: String
-) :
-    GoldenImagePathManager(
-        appContext = InstrumentationRegistry.getInstrumentation().context,
-        assetsPathRelativeToBuildRoot = assetsPathRelativeToBuildRoot,
-        deviceLocalPath =
-            InstrumentationRegistry.getInstrumentation()
-                .targetContext
-                .filesDir
-                .absolutePath
-                .toString() + "/sysui_screenshots",
-        pathConfig = pathConfig,
-    ) {
-    override fun toString(): String {
-        // This string is appended to all actual/expected screenshots on the device, so make sure
-        // it is a static value.
-        return "SystemUIGoldenImagePathManager"
-    }
-}
diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/TestAppComponentFactory.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/TestAppComponentFactory.kt
deleted file mode 100644
index 98e9aaf..0000000
--- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/TestAppComponentFactory.kt
+++ /dev/null
@@ -1,60 +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.systemui.testing.screenshot
-
-import android.app.Activity
-import android.content.Intent
-import androidx.core.app.AppComponentFactory
-
-class TestAppComponentFactory : AppComponentFactory() {
-
-    init {
-        instance = this
-    }
-
-    private val overrides: MutableMap<String, () -> Activity> = hashMapOf()
-
-    fun clearOverrides() {
-        overrides.clear()
-    }
-
-    fun <T : Activity> registerActivityOverride(activity: Class<T>, provider: () -> T) {
-        overrides[activity.name] = provider
-    }
-
-    override fun instantiateActivityCompat(
-        cl: ClassLoader,
-        className: String,
-        intent: Intent?
-    ): Activity {
-        return overrides
-            .getOrDefault(className) { super.instantiateActivityCompat(cl, className, intent) }
-            .invoke()
-    }
-
-    companion object {
-
-        private var instance: TestAppComponentFactory? = null
-
-        fun getInstance(): TestAppComponentFactory =
-            instance
-                ?: error(
-                    "TestAppComponentFactory is not initialized, " +
-                        "did you specify it in the manifest?"
-                )
-    }
-}
diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/View.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/View.kt
deleted file mode 100644
index b84d26a..0000000
--- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/View.kt
+++ /dev/null
@@ -1,42 +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.systemui.testing.screenshot
-
-import android.view.View
-import android.view.ViewGroup
-import com.android.systemui.util.children
-import android.view.WindowInsets
-
-/**
- * Elevation/shadows is not deterministic when doing hardware rendering, this exentsion allows to
- * disable it for any view in the hierarchy.
- */
-fun View.removeElevationRecursively() {
-    this.elevation = 0f
-    (this as? ViewGroup)?.children?.forEach(View::removeElevationRecursively)
-}
-
-/**
- * Different devices could have different insets (e.g. different height of the navigation bar or
- * taskbar). This method dispatches empty insets to the whole view hierarchy and removes
- * the original listener, so the views won't receive real insets.
- */
-fun View.removeInsetsRecursively() {
-    this.dispatchApplyWindowInsets(WindowInsets.CONSUMED)
-    this.setOnApplyWindowInsetsListener(null)
-    (this as? ViewGroup)?.children?.forEach(View::removeInsetsRecursively)
-}
diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewCapture.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewCapture.kt
deleted file mode 100644
index dedf0a7..0000000
--- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewCapture.kt
+++ /dev/null
@@ -1,233 +0,0 @@
-package com.android.systemui.testing.screenshot
-
-import android.annotation.WorkerThread
-import android.app.Activity
-import android.content.Context
-import android.content.ContextWrapper
-import android.graphics.Bitmap
-import android.graphics.Canvas
-import android.graphics.HardwareRenderer
-import android.graphics.Rect
-import android.os.Build
-import android.os.Handler
-import android.os.Looper
-import android.util.Log
-import android.view.PixelCopy
-import android.view.SurfaceView
-import android.view.View
-import android.view.ViewTreeObserver
-import android.view.Window
-import androidx.annotation.RequiresApi
-import androidx.concurrent.futures.ResolvableFuture
-import androidx.test.annotation.ExperimentalTestApi
-import androidx.test.core.internal.os.HandlerExecutor
-import androidx.test.espresso.Espresso
-import androidx.test.platform.graphics.HardwareRendererCompat
-import com.google.common.util.concurrent.FutureCallback
-import com.google.common.util.concurrent.Futures
-import com.google.common.util.concurrent.ListenableFuture
-import kotlin.coroutines.suspendCoroutine
-import kotlinx.coroutines.runBlocking
-
-/*
- * This file was forked from androidx/test/core/view/ViewCapture.kt to add [Window] parameter to
- * [View.captureToBitmap].
- * TODO(b/195673633): Remove this fork and use the AndroidX version instead.
- */
-
-/**
- * Asynchronously captures an image of the underlying view into a [Bitmap].
- *
- * For devices below [Build.VERSION_CODES#O] (or if the view's window cannot be determined), the
- * image is obtained using [View#draw]. Otherwise, [PixelCopy] is used.
- *
- * This method will also enable [HardwareRendererCompat#setDrawingEnabled(boolean)] if required.
- *
- * This API is primarily intended for use in lower layer libraries or frameworks. For test authors,
- * its recommended to use espresso or compose's captureToImage.
- *
- * This API is currently experimental and subject to change or removal.
- */
-@ExperimentalTestApi
-@RequiresApi(Build.VERSION_CODES.JELLY_BEAN)
-fun View.captureToBitmap(window: Window? = null): ListenableFuture<Bitmap> {
-    val bitmapFuture: ResolvableFuture<Bitmap> = ResolvableFuture.create()
-    val mainExecutor = HandlerExecutor(Handler(Looper.getMainLooper()))
-    val isRobolectric = if (Build.FINGERPRINT.contains("robolectric")) true else false
-
-    // disable drawing again if necessary once work is complete
-    if (!HardwareRendererCompat.isDrawingEnabled()) {
-        HardwareRendererCompat.setDrawingEnabled(true)
-        bitmapFuture.addListener({ HardwareRendererCompat.setDrawingEnabled(false) }, mainExecutor)
-    }
-
-    mainExecutor.execute {
-        if (isRobolectric) {
-            generateBitmap(bitmapFuture)
-        } else {
-            val forceRedrawFuture = forceRedraw()
-            forceRedrawFuture.addListener({ generateBitmap(bitmapFuture, window) }, mainExecutor)
-        }
-    }
-
-    return bitmapFuture
-}
-
-/**
- * Synchronously captures an image of the view into a [Bitmap]. Synchronous equivalent of
- * [captureToBitmap].
- */
-@WorkerThread
-@ExperimentalTestApi
-@RequiresApi(Build.VERSION_CODES.JELLY_BEAN)
-fun View.toBitmap(window: Window? = null): Bitmap {
-    if (Looper.getMainLooper() == Looper.myLooper()) {
-        error("toBitmap() can't be called from the main thread")
-    }
-
-    if (!HardwareRenderer.isDrawingEnabled()) {
-        error("Hardware rendering is not enabled")
-    }
-
-    // Make sure we are idle.
-    Espresso.onIdle()
-
-    val mainExecutor = context.mainExecutor
-    return runBlocking {
-        suspendCoroutine { continuation ->
-            Futures.addCallback(
-                captureToBitmap(window),
-                object : FutureCallback<Bitmap> {
-                    override fun onSuccess(result: Bitmap?) {
-                        continuation.resumeWith(Result.success(result!!))
-                    }
-
-                    override fun onFailure(t: Throwable) {
-                        continuation.resumeWith(Result.failure(t))
-                    }
-                },
-                // We know that we are not on the main thread, so we can block the current
-                // thread and wait for the result in the main thread.
-                mainExecutor,
-            )
-        }
-    }
-}
-
-/**
- * Trigger a redraw of the given view.
- *
- * Should only be called on UI thread.
- *
- * @return a [ListenableFuture] that will be complete once ui drawing is complete
- */
-// NoClassDefFoundError occurs on API 15
-@RequiresApi(Build.VERSION_CODES.JELLY_BEAN)
-// @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-@ExperimentalTestApi
-fun View.forceRedraw(): ListenableFuture<Void> {
-    val future: ResolvableFuture<Void> = ResolvableFuture.create()
-
-    if (Build.VERSION.SDK_INT >= 29 && isHardwareAccelerated) {
-        viewTreeObserver.registerFrameCommitCallback() { future.set(null) }
-    } else {
-        viewTreeObserver.addOnDrawListener(
-            object : ViewTreeObserver.OnDrawListener {
-                var handled = false
-                override fun onDraw() {
-                    if (!handled) {
-                        handled = true
-                        future.set(null)
-                        // cannot remove on draw listener inside of onDraw
-                        Handler(Looper.getMainLooper()).post {
-                            viewTreeObserver.removeOnDrawListener(this)
-                        }
-                    }
-                }
-            }
-        )
-    }
-    invalidate()
-    return future
-}
-
-private fun View.generateBitmap(
-    bitmapFuture: ResolvableFuture<Bitmap>,
-    window: Window? = null,
-) {
-    if (bitmapFuture.isCancelled) {
-        return
-    }
-    val destBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
-    when {
-        Build.VERSION.SDK_INT < 26 -> generateBitmapFromDraw(destBitmap, bitmapFuture)
-        this is SurfaceView -> generateBitmapFromSurfaceViewPixelCopy(destBitmap, bitmapFuture)
-        else -> {
-            val window = window ?: getActivity()?.window
-            if (window != null) {
-                generateBitmapFromPixelCopy(window, destBitmap, bitmapFuture)
-            } else {
-                Log.i(
-                    "View.captureToImage",
-                    "Could not find window for view. Falling back to View#draw instead of PixelCopy"
-                )
-                generateBitmapFromDraw(destBitmap, bitmapFuture)
-            }
-        }
-    }
-}
-
-@SuppressWarnings("NewApi")
-private fun SurfaceView.generateBitmapFromSurfaceViewPixelCopy(
-    destBitmap: Bitmap,
-    bitmapFuture: ResolvableFuture<Bitmap>
-) {
-    val onCopyFinished =
-        PixelCopy.OnPixelCopyFinishedListener { result ->
-            if (result == PixelCopy.SUCCESS) {
-                bitmapFuture.set(destBitmap)
-            } else {
-                bitmapFuture.setException(
-                    RuntimeException(String.format("PixelCopy failed: %d", result))
-                )
-            }
-        }
-    PixelCopy.request(this, null, destBitmap, onCopyFinished, handler)
-}
-
-internal fun View.generateBitmapFromDraw(
-    destBitmap: Bitmap,
-    bitmapFuture: ResolvableFuture<Bitmap>
-) {
-    destBitmap.density = resources.displayMetrics.densityDpi
-    computeScroll()
-    val canvas = Canvas(destBitmap)
-    canvas.translate((-scrollX).toFloat(), (-scrollY).toFloat())
-    draw(canvas)
-    bitmapFuture.set(destBitmap)
-}
-
-private fun View.getActivity(): Activity? {
-    fun Context.getActivity(): Activity? {
-        return when (this) {
-            is Activity -> this
-            is ContextWrapper -> this.baseContext.getActivity()
-            else -> null
-        }
-    }
-    return context.getActivity()
-}
-
-private fun View.generateBitmapFromPixelCopy(
-    window: Window,
-    destBitmap: Bitmap,
-    bitmapFuture: ResolvableFuture<Bitmap>
-) {
-    val locationInWindow = intArrayOf(0, 0)
-    getLocationInWindow(locationInWindow)
-    val x = locationInWindow[0]
-    val y = locationInWindow[1]
-    val boundsInWindow = Rect(x, y, x + width, y + height)
-
-    return window.generateBitmapFromPixelCopy(boundsInWindow, destBitmap, bitmapFuture)
-}
diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt
deleted file mode 100644
index 070a451..0000000
--- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt
+++ /dev/null
@@ -1,188 +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.systemui.testing.screenshot
-
-import android.app.Activity
-import android.app.Dialog
-import android.graphics.Bitmap
-import android.os.Build
-import android.view.View
-import android.view.ViewGroup
-import android.view.ViewGroup.LayoutParams
-import android.view.ViewGroup.LayoutParams.MATCH_PARENT
-import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
-import androidx.activity.ComponentActivity
-import androidx.test.ext.junit.rules.ActivityScenarioRule
-import java.util.concurrent.TimeUnit
-import org.junit.Assert.assertEquals
-import org.junit.rules.RuleChain
-import org.junit.rules.TestRule
-import org.junit.runner.Description
-import org.junit.runners.model.Statement
-import platform.test.screenshot.DeviceEmulationRule
-import platform.test.screenshot.DeviceEmulationSpec
-import platform.test.screenshot.MaterialYouColorsRule
-import platform.test.screenshot.ScreenshotTestRule
-import platform.test.screenshot.getEmulatedDevicePathConfig
-import platform.test.screenshot.matchers.BitmapMatcher
-
-/** A rule for View screenshot diff unit tests. */
-open class ViewScreenshotTestRule(
-    emulationSpec: DeviceEmulationSpec,
-    private val matcher: BitmapMatcher = UnitTestBitmapMatcher,
-    assetsPathRelativeToBuildRoot: String
-) : TestRule {
-    private val colorsRule = MaterialYouColorsRule()
-    private val deviceEmulationRule = DeviceEmulationRule(emulationSpec)
-    protected val screenshotRule =
-        ScreenshotTestRule(
-            SystemUIGoldenImagePathManager(
-                getEmulatedDevicePathConfig(emulationSpec),
-                assetsPathRelativeToBuildRoot
-            )
-        )
-    private val activityRule = ActivityScenarioRule(ScreenshotActivity::class.java)
-    private val roboRule =
-        RuleChain.outerRule(deviceEmulationRule).around(screenshotRule).around(activityRule)
-    private val delegateRule = RuleChain.outerRule(colorsRule).around(roboRule)
-    private val isRobolectric = if (Build.FINGERPRINT.contains("robolectric")) true else false
-
-    override fun apply(base: Statement, description: Description): Statement {
-        if (isRobolectric) {
-            // In robolectric mode, we enable NATIVE graphics and unpack font and icu files.
-            // We need to use reflection, as this library is only needed and therefore
-            //  only available in deviceless mode.
-            val nativeLoaderClassName = "org.robolectric.nativeruntime.DefaultNativeRuntimeLoader"
-            val defaultNativeRuntimeLoader = Class.forName(nativeLoaderClassName)
-            System.setProperty("robolectric.graphicsMode", "NATIVE")
-            defaultNativeRuntimeLoader.getMethod("injectAndLoad").invoke(null)
-        }
-        val ruleToApply = if (isRobolectric) roboRule else delegateRule
-        return ruleToApply.apply(base, description)
-    }
-
-    protected fun takeScreenshot(
-        mode: Mode = Mode.WrapContent,
-        viewProvider: (ComponentActivity) -> View,
-        beforeScreenshot: (ComponentActivity) -> Unit = {}
-    ): Bitmap {
-        activityRule.scenario.onActivity { activity ->
-            // Make sure that the activity draws full screen and fits the whole display instead of
-            // the system bars.
-            val window = activity.window
-            window.setDecorFitsSystemWindows(false)
-
-            // Set the content.
-            activity.setContentView(viewProvider(activity), mode.layoutParams)
-
-            // Elevation/shadows is not deterministic when doing hardware rendering, so we disable
-            // it for any view in the hierarchy.
-            window.decorView.removeElevationRecursively()
-
-            activity.currentFocus?.clearFocus()
-        }
-
-        // We call onActivity again because it will make sure that our Activity is done measuring,
-        // laying out and drawing its content (that we set in the previous onActivity lambda).
-        var contentView: View? = null
-        activityRule.scenario.onActivity { activity ->
-            // Check that the content is what we expected.
-            val content = activity.requireViewById<ViewGroup>(android.R.id.content)
-            assertEquals(1, content.childCount)
-            contentView = content.getChildAt(0)
-            beforeScreenshot(activity)
-        }
-
-        return if (isRobolectric) {
-            contentView?.captureToBitmap()?.get(10, TimeUnit.SECONDS)
-                ?: error("timeout while trying to capture view to bitmap")
-        } else {
-            contentView?.toBitmap() ?: error("contentView is null")
-        }
-    }
-
-    /**
-     * Compare the content of the view provided by [viewProvider] with the golden image identified
-     * by [goldenIdentifier] in the context of [emulationSpec].
-     */
-    fun screenshotTest(
-        goldenIdentifier: String,
-        mode: Mode = Mode.WrapContent,
-        beforeScreenshot: (ComponentActivity) -> Unit = {},
-        viewProvider: (ComponentActivity) -> View
-    ) {
-        val bitmap = takeScreenshot(mode, viewProvider, beforeScreenshot)
-        screenshotRule.assertBitmapAgainstGolden(
-            bitmap,
-            goldenIdentifier,
-            matcher,
-        )
-    }
-
-    /**
-     * Compare the content of the dialog provided by [dialogProvider] with the golden image
-     * identified by [goldenIdentifier] in the context of [emulationSpec].
-     */
-    fun dialogScreenshotTest(
-        goldenIdentifier: String,
-        dialogProvider: (Activity) -> Dialog,
-    ) {
-        var dialog: Dialog? = null
-        activityRule.scenario.onActivity { activity ->
-            dialog =
-                dialogProvider(activity).apply {
-                    // Make sure that the dialog draws full screen and fits the whole display
-                    // instead of the system bars.
-                    window.setDecorFitsSystemWindows(false)
-
-                    // Disable enter/exit animations.
-                    create()
-                    window.setWindowAnimations(0)
-
-                    // Elevation/shadows is not deterministic when doing hardware rendering, so we
-                    // disable it for any view in the hierarchy.
-                    window.decorView.removeElevationRecursively()
-
-                    // Show the dialog.
-                    show()
-                }
-        }
-
-        try {
-            val bitmap = dialog?.toBitmap() ?: error("dialog is null")
-            screenshotRule.assertBitmapAgainstGolden(
-                bitmap,
-                goldenIdentifier,
-                matcher,
-            )
-        } finally {
-            dialog?.dismiss()
-        }
-    }
-
-    private fun Dialog.toBitmap(): Bitmap {
-        val window = window
-        return window.decorView.toBitmap(window)
-    }
-
-    enum class Mode(val layoutParams: LayoutParams) {
-        WrapContent(LayoutParams(WRAP_CONTENT, WRAP_CONTENT)),
-        MatchSize(LayoutParams(MATCH_PARENT, MATCH_PARENT)),
-        MatchWidth(LayoutParams(MATCH_PARENT, WRAP_CONTENT)),
-        MatchHeight(LayoutParams(WRAP_CONTENT, MATCH_PARENT)),
-    }
-}
diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/WindowCapture.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/WindowCapture.kt
deleted file mode 100644
index d34f46b..0000000
--- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/WindowCapture.kt
+++ /dev/null
@@ -1,37 +0,0 @@
-package com.android.systemui.testing.screenshot
-
-import android.graphics.Bitmap
-import android.graphics.Rect
-import android.os.Handler
-import android.os.Looper
-import android.view.PixelCopy
-import android.view.Window
-import androidx.concurrent.futures.ResolvableFuture
-
-/*
- * This file was forked from androidx/test/core/view/WindowCapture.kt.
- * TODO(b/195673633): Remove this fork and use the AndroidX version instead.
- */
-fun Window.generateBitmapFromPixelCopy(
-    boundsInWindow: Rect? = null,
-    destBitmap: Bitmap,
-    bitmapFuture: ResolvableFuture<Bitmap>
-) {
-    val onCopyFinished =
-        PixelCopy.OnPixelCopyFinishedListener { result ->
-            if (result == PixelCopy.SUCCESS) {
-                bitmapFuture.set(destBitmap)
-            } else {
-                bitmapFuture.setException(
-                    RuntimeException(String.format("PixelCopy failed: %d", result))
-                )
-            }
-        }
-    PixelCopy.request(
-        this,
-        boundsInWindow,
-        destBitmap,
-        onCopyFinished,
-        Handler(Looper.getMainLooper())
-    )
-}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/condition/Monitor.java b/packages/SystemUI/shared/src/com/android/systemui/shared/condition/Monitor.java
index 1f61c64..97d6099 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/condition/Monitor.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/condition/Monitor.java
@@ -215,7 +215,7 @@
             mSubscriptions.put(token, state);
 
             // Add and associate conditions.
-            normalizedCondition.getConditions().stream().forEach(condition -> {
+            normalizedCondition.getConditions().forEach(condition -> {
                 if (!mConditions.containsKey(condition)) {
                     mConditions.put(condition, new ArraySet<>());
                     condition.addCallback(mConditionCallback);
@@ -321,7 +321,6 @@
             private final Callback mCallback;
             private final Subscription mNestedSubscription;
             private final ArraySet<Condition> mConditions;
-            private final ArraySet<Condition> mPreconditions;
 
             /**
              * Default constructor specifying the {@link Callback} for the {@link Subscription}.
@@ -337,8 +336,7 @@
             private Builder(Subscription nestedSubscription, Callback callback) {
                 mNestedSubscription = nestedSubscription;
                 mCallback = callback;
-                mConditions = new ArraySet();
-                mPreconditions = new ArraySet();
+                mConditions = new ArraySet<>();
             }
 
             /**
@@ -352,29 +350,6 @@
             }
 
             /**
-             * Adds a set of {@link Condition} to be a precondition for {@link Subscription}.
-             *
-             * @return The updated {@link Builder}.
-             */
-            public Builder addPreconditions(Set<Condition> condition) {
-                if (condition == null) {
-                    return this;
-                }
-                mPreconditions.addAll(condition);
-                return this;
-            }
-
-            /**
-             * Adds a {@link Condition} to be a precondition for {@link Subscription}.
-             *
-             * @return The updated {@link Builder}.
-             */
-            public Builder addPrecondition(Condition condition) {
-                mPreconditions.add(condition);
-                return this;
-            }
-
-            /**
              * Adds a set of {@link Condition} to be associated with the {@link Subscription}.
              *
              * @return The updated {@link Builder}.
@@ -394,11 +369,7 @@
              * @return The resulting {@link Subscription}.
              */
             public Subscription build() {
-                final Subscription subscription =
-                        new Subscription(mConditions, mCallback, mNestedSubscription);
-                return !mPreconditions.isEmpty()
-                        ? new Subscription(mPreconditions, null, subscription)
-                        : subscription;
+                return new Subscription(mConditions, mCallback, mNestedSubscription);
             }
         }
     }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
index 117cf78a..4b31498 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
@@ -48,7 +48,7 @@
      *
      * Normal gesture: DOWN, MOVE/POINTER_DOWN/POINTER_UP)*, UP or CANCLE
      */
-    oneway void onStatusBarMotionEvent(in MotionEvent event) = 9;
+    oneway void onStatusBarTouchEvent(in MotionEvent event) = 9;
 
     /**
      * Proxies the assistant gesture's progress started from navigation bar.
@@ -125,5 +125,15 @@
      */
     oneway void takeScreenshot(in ScreenshotRequest request) = 51;
 
-    // Next id = 52
+    /**
+     * Dispatches trackpad status bar motion event to the notification shade. Currently these events
+     * are from the input monitor in {@link TouchInteractionService}. This is different from
+     * {@link #onStatusBarTouchEvent} above in that, this directly dispatches motion events to the
+     * notification shade, while {@link #onStatusBarTouchEvent} relies on setting the launcher
+     * window slippery to allow the frameworks to route those events after passing the initial
+     * threshold.
+     */
+    oneway void onStatusBarTrackpadEvent(in MotionEvent event) = 52;
+
+    // Next id = 53
 }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSampler.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSampler.kt
index 9a00447..f3296f0 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSampler.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSampler.kt
@@ -32,7 +32,7 @@
 @JvmOverloads
 constructor(
     val sampledView: View,
-    mainExecutor: Executor?,
+    val mainExecutor: Executor?,
     val bgExecutor: Executor?,
     val regionSamplingEnabled: Boolean,
     val isLockscreen: Boolean = false,
@@ -166,7 +166,7 @@
                         if (isLockscreen) WallpaperManager.FLAG_LOCK
                         else WallpaperManager.FLAG_SYSTEM
                     )
-                onColorsChanged(sampledRegionWithOffset, initialSampling)
+                mainExecutor?.execute { onColorsChanged(sampledRegionWithOffset, initialSampling) }
             }
         )
     }
@@ -202,8 +202,6 @@
 
     fun calculateScreenLocation(sampledView: View): RectF? {
 
-        if (!sampledView.isLaidOut) return null
-
         val screenLocation = tmpScreenLocation
         /**
          * The method getLocationOnScreen is used to obtain the view coordinates relative to its
@@ -219,6 +217,10 @@
         samplingBounds.right = left + sampledView.width
         samplingBounds.bottom = top + sampledView.height
 
+        // ensure never go out of bounds
+        if (samplingBounds.right > displaySize.x) samplingBounds.right = displaySize.x
+        if (samplingBounds.bottom > displaySize.y) samplingBounds.bottom = displaySize.y
+
         return RectF(samplingBounds)
     }
 
@@ -263,6 +265,8 @@
                 (colors?.colorHints?.and(WallpaperColors.HINT_SUPPORTS_DARK_TEXT)) !=
                     WallpaperColors.HINT_SUPPORTS_DARK_TEXT
             )
+        if (DEBUG)
+            Log.d(TAG, "onColorsChanged() | region darkness = $regionDarkness for region $area")
         updateForegroundColor()
     }
 
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java
index 4269530..c844db7 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java
@@ -61,6 +61,8 @@
             InteractionJankMonitor.CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS;
     public static final int CUJ_OPEN_SEARCH_RESULT =
             InteractionJankMonitor.CUJ_LAUNCHER_OPEN_SEARCH_RESULT;
+    public static final int CUJ_SHADE_EXPAND_FROM_STATUS_BAR =
+            InteractionJankMonitor.CUJ_SHADE_EXPAND_FROM_STATUS_BAR;
 
     @IntDef({
             CUJ_APP_LAUNCH_FROM_RECENTS,
@@ -76,6 +78,7 @@
             CUJ_CLOSE_ALL_APPS_SWIPE,
             CUJ_CLOSE_ALL_APPS_TO_HOME,
             CUJ_OPEN_SEARCH_RESULT,
+            CUJ_SHADE_EXPAND_FROM_STATUS_BAR,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface CujType {
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
index 3c447a8..4b14d3cf 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
@@ -42,8 +42,6 @@
             "com.google.android.apps.nexuslauncher.NexusLauncherActivity";
 
     public static final String KEY_EXTRA_SYSUI_PROXY = "extra_sysui_proxy";
-    public static final String KEY_EXTRA_WINDOW_CORNER_RADIUS = "extra_window_corner_radius";
-    public static final String KEY_EXTRA_SUPPORTS_WINDOW_CORNERS = "extra_supports_window_corners";
     public static final String KEY_EXTRA_UNFOLD_ANIMATION_FORWARDER = "extra_unfold_animation";
     // See ISysuiUnlockAnimationController.aidl
     public static final String KEY_EXTRA_UNLOCK_ANIMATION_CONTROLLER = "unlock_animation";
@@ -324,7 +322,7 @@
      * Returns whether the specified sysui state is such that the back gesture should be
      * disabled.
      */
-    public static boolean isBackGestureDisabled(int sysuiStateFlags) {
+    public static boolean isBackGestureDisabled(int sysuiStateFlags, boolean forTrackpad) {
         // Always allow when the bouncer/global actions/voice session is showing (even on top of
         // the keyguard)
         if ((sysuiStateFlags & SYSUI_STATE_BOUNCER_SHOWING) != 0
@@ -335,16 +333,23 @@
         if ((sysuiStateFlags & SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY) != 0) {
             sysuiStateFlags &= ~SYSUI_STATE_NAV_BAR_HIDDEN;
         }
+
+        return (sysuiStateFlags & getBackGestureDisabledMask(forTrackpad)) != 0;
+    }
+
+    private static int getBackGestureDisabledMask(boolean forTrackpad) {
         // Disable when in immersive, or the notifications are interactive
-        int disableFlags = SYSUI_STATE_NAV_BAR_HIDDEN | SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING;
+        int disableFlags = SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING;
+        if (!forTrackpad) {
+            disableFlags |= SYSUI_STATE_NAV_BAR_HIDDEN;
+        }
 
         // EdgeBackGestureHandler ignores Back gesture when SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED.
         // To allow Shade to respond to Back, we're bypassing this check (behind a flag).
         if (!ALLOW_BACK_GESTURE_IN_SHADE) {
             disableFlags |= SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
         }
-
-        return (sysuiStateFlags & disableFlags) != 0;
+        return disableFlags;
     }
 
     /**
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
deleted file mode 100644
index 74c325d..0000000
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
+++ /dev/null
@@ -1,541 +0,0 @@
-/*
- * 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.
- */
-
-package com.android.systemui.shared.system;
-
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
-import static android.view.WindowManager.TRANSIT_CHANGE;
-import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_LOCKED;
-import static android.view.WindowManager.TRANSIT_SLEEP;
-
-import android.annotation.SuppressLint;
-import android.app.ActivityManager;
-import android.app.ActivityTaskManager;
-import android.app.IApplicationThread;
-import android.graphics.Rect;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.ArrayMap;
-import android.util.Log;
-import android.view.IRecentsAnimationController;
-import android.view.RemoteAnimationTarget;
-import android.view.SurfaceControl;
-import android.window.IRemoteTransition;
-import android.window.IRemoteTransitionFinishedCallback;
-import android.window.PictureInPictureSurfaceTransaction;
-import android.window.RemoteTransition;
-import android.window.TaskSnapshot;
-import android.window.TransitionInfo;
-import android.window.WindowContainerToken;
-import android.window.WindowContainerTransaction;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.wm.shell.util.TransitionUtil;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-
-/**
- * Helper class to build {@link RemoteTransition} objects
- */
-public class RemoteTransitionCompat {
-    private static final String TAG = "RemoteTransitionCompat";
-
-    /** Constructor specifically for recents animation */
-    public static RemoteTransition newRemoteTransition(RecentsAnimationListener recents,
-            IApplicationThread appThread) {
-        IRemoteTransition remote = new IRemoteTransition.Stub() {
-            final RecentsControllerWrap mRecentsSession = new RecentsControllerWrap();
-            IBinder mToken = null;
-
-            @Override
-            public void startAnimation(IBinder transition, TransitionInfo info,
-                    SurfaceControl.Transaction t,
-                    IRemoteTransitionFinishedCallback finishedCallback) {
-                // TODO(b/177438007): Move this set-up logic into launcher's animation impl.
-                mToken = transition;
-                mRecentsSession.start(recents, mToken, info, t, finishedCallback);
-            }
-
-            @Override
-            public void mergeAnimation(IBinder transition, TransitionInfo info,
-                    SurfaceControl.Transaction t, IBinder mergeTarget,
-                    IRemoteTransitionFinishedCallback finishedCallback) {
-                if (mergeTarget.equals(mToken) && mRecentsSession.merge(info, t)) {
-                    try {
-                        finishedCallback.onTransitionFinished(null /* wct */, null /* sct */);
-                    } catch (RemoteException e) {
-                        Log.e(TAG, "Error merging transition.", e);
-                    }
-                    // commit taskAppeared after merge transition finished.
-                    mRecentsSession.commitTasksAppearedIfNeeded();
-                } else {
-                    t.close();
-                    info.releaseAllSurfaces();
-                }
-            }
-        };
-        return new RemoteTransition(remote, appThread, "Recents");
-    }
-
-    /**
-     * Wrapper to hook up parts of recents animation to shell transition.
-     * TODO(b/177438007): Remove this once Launcher handles shell transitions directly.
-     */
-    @VisibleForTesting
-    static class RecentsControllerWrap extends IRecentsAnimationController.Default {
-        private RecentsAnimationListener mListener = null;
-        private IRemoteTransitionFinishedCallback mFinishCB = null;
-
-        /**
-         * List of tasks that we are switching away from via this transition. Upon finish, these
-         * pausing tasks will become invisible.
-         * These need to be ordered since the order must be restored if there is no task-switch.
-         */
-        private ArrayList<TaskState> mPausingTasks = null;
-
-        /**
-         * List of tasks that we are switching to. Upon finish, these will remain visible and
-         * on top.
-         */
-        private ArrayList<TaskState> mOpeningTasks = null;
-
-        private WindowContainerToken mPipTask = null;
-        private WindowContainerToken mRecentsTask = null;
-        private int mRecentsTaskId = 0;
-        private TransitionInfo mInfo = null;
-        private boolean mOpeningSeparateHome = false;
-        private ArrayMap<SurfaceControl, SurfaceControl> mLeashMap = null;
-        private PictureInPictureSurfaceTransaction mPipTransaction = null;
-        private IBinder mTransition = null;
-        private boolean mKeyguardLocked = false;
-        private RemoteAnimationTarget[] mAppearedTargets;
-        private boolean mWillFinishToHome = false;
-
-        /** The animation is idle, waiting for the user to choose a task to switch to. */
-        private static final int STATE_NORMAL = 0;
-
-        /** The user chose a new task to switch to and the animation is animating to it. */
-        private static final int STATE_NEW_TASK = 1;
-
-        /** The latest state that the recents animation is operating in. */
-        private int mState = STATE_NORMAL;
-
-        void start(RecentsAnimationListener listener,
-                IBinder transition, TransitionInfo info, SurfaceControl.Transaction t,
-                IRemoteTransitionFinishedCallback finishedCallback) {
-            if (mInfo != null) {
-                throw new IllegalStateException("Trying to run a new recents animation while"
-                        + " recents is already active.");
-            }
-            mListener = listener;
-            mInfo = info;
-            mFinishCB = finishedCallback;
-            mPausingTasks = new ArrayList<>();
-            mOpeningTasks = new ArrayList<>();
-            mPipTask = null;
-            mRecentsTask = null;
-            mRecentsTaskId = -1;
-            mLeashMap = new ArrayMap<>();
-            mTransition = transition;
-            mKeyguardLocked = (info.getFlags() & TRANSIT_FLAG_KEYGUARD_LOCKED) != 0;
-            mState = STATE_NORMAL;
-
-            final ArrayList<RemoteAnimationTarget> apps = new ArrayList<>();
-            final ArrayList<RemoteAnimationTarget> wallpapers = new ArrayList<>();
-            TransitionUtil.LeafTaskFilter leafTaskFilter = new TransitionUtil.LeafTaskFilter();
-            // About layering: we divide up the "layer space" into 3 regions (each the size of
-            // the change count). This lets us categorize things into above/below/between
-            // while maintaining their relative ordering.
-            for (int i = 0; i < info.getChanges().size(); ++i) {
-                final TransitionInfo.Change change = info.getChanges().get(i);
-                final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
-                if (TransitionUtil.isWallpaper(change)) {
-                    final RemoteAnimationTarget target = TransitionUtil.newTarget(change,
-                            // wallpapers go into the "below" layer space
-                            info.getChanges().size() - i, info, t, mLeashMap);
-                    wallpapers.add(target);
-                    // Make all the wallpapers opaque since we want them visible from the start
-                    t.setAlpha(target.leash, 1);
-                } else if (leafTaskFilter.test(change)) {
-                    // start by putting everything into the "below" layer space.
-                    final RemoteAnimationTarget target = TransitionUtil.newTarget(change,
-                            info.getChanges().size() - i, info, t, mLeashMap);
-                    apps.add(target);
-                    if (TransitionUtil.isClosingType(change.getMode())) {
-                        // raise closing (pausing) task to "above" layer so it isn't covered
-                        t.setLayer(target.leash, info.getChanges().size() * 3 - i);
-                        mPausingTasks.add(new TaskState(change, target.leash));
-                        if (taskInfo.pictureInPictureParams != null
-                                && taskInfo.pictureInPictureParams.isAutoEnterEnabled()) {
-                            mPipTask = taskInfo.token;
-                        }
-                    } else if (taskInfo != null
-                            && taskInfo.topActivityType == ACTIVITY_TYPE_RECENTS) {
-                        // There's a 3p launcher, so make sure recents goes above that.
-                        t.setLayer(target.leash, info.getChanges().size() * 3 - i);
-                        mRecentsTask = taskInfo.token;
-                        mRecentsTaskId = taskInfo.taskId;
-                    } else if (taskInfo != null && taskInfo.topActivityType == ACTIVITY_TYPE_HOME) {
-                        mRecentsTask = taskInfo.token;
-                        mRecentsTaskId = taskInfo.taskId;
-                    } else if (TransitionUtil.isOpeningType(change.getMode())) {
-                        mOpeningTasks.add(new TaskState(change, target.leash));
-                    }
-                }
-            }
-            t.apply();
-            mListener.onAnimationStart(new RecentsAnimationControllerCompat(this),
-                    apps.toArray(new RemoteAnimationTarget[apps.size()]),
-                    wallpapers.toArray(new RemoteAnimationTarget[wallpapers.size()]),
-                    new Rect(0, 0, 0, 0), new Rect());
-        }
-
-        @SuppressLint("NewApi")
-        boolean merge(TransitionInfo info, SurfaceControl.Transaction t) {
-            if (info.getType() == TRANSIT_SLEEP) {
-                // A sleep event means we need to stop animations immediately, so cancel here.
-                mListener.onAnimationCanceled(new HashMap<>());
-                finish(mWillFinishToHome, false /* userLeaveHint */);
-                return false;
-            }
-            ArrayList<TransitionInfo.Change> openingTasks = null;
-            ArrayList<TransitionInfo.Change> closingTasks = null;
-            mAppearedTargets = null;
-            mOpeningSeparateHome = false;
-            TransitionInfo.Change recentsOpening = null;
-            boolean foundRecentsClosing = false;
-            boolean hasChangingApp = false;
-            final TransitionUtil.LeafTaskFilter leafTaskFilter =
-                    new TransitionUtil.LeafTaskFilter();
-            for (int i = 0; i < info.getChanges().size(); ++i) {
-                final TransitionInfo.Change change = info.getChanges().get(i);
-                final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
-                final boolean isLeafTask = leafTaskFilter.test(change);
-                if (TransitionUtil.isOpeningType(change.getMode())) {
-                    if (mRecentsTask.equals(change.getContainer())) {
-                        recentsOpening = change;
-                    } else if (isLeafTask) {
-                        if (taskInfo.topActivityType == ACTIVITY_TYPE_HOME) {
-                            // This is usually a 3p launcher
-                            mOpeningSeparateHome = true;
-                        }
-                        if (openingTasks == null) {
-                            openingTasks = new ArrayList<>();
-                        }
-                        openingTasks.add(change);
-                    }
-                } else if (TransitionUtil.isClosingType(change.getMode())) {
-                    if (mRecentsTask.equals(change.getContainer())) {
-                        foundRecentsClosing = true;
-                    } else if (isLeafTask) {
-                        if (closingTasks == null) {
-                            closingTasks = new ArrayList<>();
-                        }
-                        closingTasks.add(change);
-                    }
-                } else if (change.getMode() == TRANSIT_CHANGE) {
-                    // Finish recents animation if the display is changed, so the default
-                    // transition handler can play the animation such as rotation effect.
-                    if (change.hasFlags(TransitionInfo.FLAG_IS_DISPLAY)) {
-                        mListener.onSwitchToScreenshot(() -> finish(false /* toHome */,
-                                false /* userLeaveHint */));
-                        return false;
-                    }
-                    hasChangingApp = true;
-                }
-            }
-            if (hasChangingApp && foundRecentsClosing) {
-                // This happens when a visible app is expanding (usually PiP). In this case,
-                // that transition probably has a special-purpose animation, so finish recents
-                // now and let it do its animation (since recents is going to be occluded).
-                if (!mListener.onSwitchToScreenshot(
-                        () -> finish(true /* toHome */, false /* userLeaveHint */))) {
-                    Log.w(TAG, "Recents callback doesn't support support switching to screenshot"
-                            + ", there might be a flicker.");
-                    finish(true /* toHome */, false /* userLeaveHint */);
-                }
-                return false;
-            }
-            if (recentsOpening != null) {
-                // the recents task re-appeared. This happens if the user gestures before the
-                // task-switch (NEW_TASK) animation finishes.
-                if (mState == STATE_NORMAL) {
-                    Log.e(TAG, "Returning to recents while recents is already idle.");
-                }
-                if (closingTasks == null || closingTasks.size() == 0) {
-                    Log.e(TAG, "Returning to recents without closing any opening tasks.");
-                }
-                // Setup may hide it initially since it doesn't know that overview was still active.
-                t.show(recentsOpening.getLeash());
-                t.setAlpha(recentsOpening.getLeash(), 1.f);
-                mState = STATE_NORMAL;
-            }
-            boolean didMergeThings = false;
-            if (closingTasks != null) {
-                // Cancelling a task-switch. Move the tasks back to mPausing from mOpening
-                for (int i = 0; i < closingTasks.size(); ++i) {
-                    final TransitionInfo.Change change = closingTasks.get(i);
-                    int openingIdx = TaskState.indexOf(mOpeningTasks, change);
-                    if (openingIdx < 0) {
-                        Log.e(TAG, "Back to existing recents animation from an unrecognized "
-                                + "task: " + change.getTaskInfo().taskId);
-                        continue;
-                    }
-                    mPausingTasks.add(mOpeningTasks.remove(openingIdx));
-                    didMergeThings = true;
-                }
-            }
-            if (openingTasks != null && openingTasks.size() > 0) {
-                // Switching to some new tasks, add to mOpening and remove from mPausing. Also,
-                // enter NEW_TASK state since this will start the switch-to animation.
-                final int layer = mInfo.getChanges().size() * 3;
-                final RemoteAnimationTarget[] targets =
-                        new RemoteAnimationTarget[openingTasks.size()];
-                for (int i = 0; i < openingTasks.size(); ++i) {
-                    final TransitionInfo.Change change = openingTasks.get(i);
-                    int pausingIdx = TaskState.indexOf(mPausingTasks, change);
-                    if (pausingIdx >= 0) {
-                        // Something is showing/opening a previously-pausing app.
-                        targets[i] = TransitionUtil.newTarget(change, layer,
-                                mPausingTasks.get(pausingIdx).mLeash);
-                        mOpeningTasks.add(mPausingTasks.remove(pausingIdx));
-                        // Setup hides opening tasks initially, so make it visible again (since we
-                        // are already showing it).
-                        t.show(change.getLeash());
-                        t.setAlpha(change.getLeash(), 1.f);
-                    } else {
-                        // We are receiving new opening tasks, so convert to onTasksAppeared.
-                        targets[i] = TransitionUtil.newTarget(change, layer, info, t, mLeashMap);
-                        // reparent into the original `mInfo` since that's where we are animating.
-                        final int rootIdx = TransitionUtil.rootIndexFor(change, mInfo);
-                        t.reparent(targets[i].leash, mInfo.getRoot(rootIdx).getLeash());
-                        t.setLayer(targets[i].leash, layer);
-                        mOpeningTasks.add(new TaskState(change, targets[i].leash));
-                    }
-                }
-                didMergeThings = true;
-                mState = STATE_NEW_TASK;
-                mAppearedTargets = targets;
-            }
-            if (!didMergeThings) {
-                // Didn't recognize anything in incoming transition so don't merge it.
-                Log.w(TAG, "Don't know how to merge this transition.");
-                return false;
-            }
-            t.apply();
-            // not using the incoming anim-only surfaces
-            info.releaseAnimSurfaces();
-            return true;
-        }
-
-        private void commitTasksAppearedIfNeeded() {
-            if (mAppearedTargets != null) {
-                mListener.onTasksAppeared(mAppearedTargets);
-                mAppearedTargets = null;
-            }
-        }
-
-        @Override public TaskSnapshot screenshotTask(int taskId) {
-            try {
-                return ActivityTaskManager.getService().takeTaskSnapshot(taskId,
-                        true /* updateCache */);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Failed to screenshot task", e);
-            }
-            return null;
-        }
-
-        @Override public void setInputConsumerEnabled(boolean enabled) {
-            if (!enabled) return;
-            // transient launches don't receive focus automatically. Since we are taking over
-            // the gesture now, take focus explicitly.
-            // This also moves recents back to top if the user gestured before a switch
-            // animation finished.
-            try {
-                ActivityTaskManager.getService().setFocusedTask(mRecentsTaskId);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Failed to set focused task", e);
-            }
-        }
-
-        @Override public void setAnimationTargetsBehindSystemBars(boolean behindSystemBars) {
-        }
-
-        @Override public void setFinishTaskTransaction(int taskId,
-                PictureInPictureSurfaceTransaction finishTransaction, SurfaceControl overlay) {
-            mPipTransaction = finishTransaction;
-        }
-
-        @Override
-        @SuppressLint("NewApi")
-        public void finish(boolean toHome, boolean sendUserLeaveHint) {
-            if (mFinishCB == null) {
-                Log.e(TAG, "Duplicate call to finish", new RuntimeException());
-                return;
-            }
-            final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
-            final WindowContainerTransaction wct = new WindowContainerTransaction();
-
-            if (mKeyguardLocked && mRecentsTask != null) {
-                if (toHome) wct.reorder(mRecentsTask, true /* toTop */);
-                else wct.restoreTransientOrder(mRecentsTask);
-            }
-            if (!toHome && !mWillFinishToHome && mPausingTasks != null && mState == STATE_NORMAL) {
-                // The gesture is returning to the pausing-task(s) rather than continuing with
-                // recents, so end the transition by moving the app back to the top (and also
-                // re-showing it's task).
-                for (int i = mPausingTasks.size() - 1; i >= 0; --i) {
-                    // reverse order so that index 0 ends up on top
-                    wct.reorder(mPausingTasks.get(i).mToken, true /* onTop */);
-                    t.show(mPausingTasks.get(i).mTaskSurface);
-                }
-                if (!mKeyguardLocked && mRecentsTask != null) {
-                    wct.restoreTransientOrder(mRecentsTask);
-                }
-            } else if (toHome && mOpeningSeparateHome && mPausingTasks != null) {
-                // Special situation where 3p launcher was changed during recents (this happens
-                // during tapltests...). Here we get both "return to home" AND "home opening".
-                // This is basically going home, but we have to restore the recents and home order.
-                for (int i = 0; i < mOpeningTasks.size(); ++i) {
-                    final TaskState state = mOpeningTasks.get(i);
-                    if (state.mTaskInfo.topActivityType == ACTIVITY_TYPE_HOME) {
-                        // Make sure it is on top.
-                        wct.reorder(state.mToken, true /* onTop */);
-                    }
-                    t.show(state.mTaskSurface);
-                }
-                for (int i = mPausingTasks.size() - 1; i >= 0; --i) {
-                    t.hide(mPausingTasks.get(i).mTaskSurface);
-                }
-                if (!mKeyguardLocked && mRecentsTask != null) {
-                    wct.restoreTransientOrder(mRecentsTask);
-                }
-            } else {
-                // The general case: committing to recents, going home, or switching tasks.
-                for (int i = 0; i < mOpeningTasks.size(); ++i) {
-                    t.show(mOpeningTasks.get(i).mTaskSurface);
-                }
-                for (int i = 0; i < mPausingTasks.size(); ++i) {
-                    if (!sendUserLeaveHint) {
-                        // This means recents is not *actually* finishing, so of course we gotta
-                        // do special stuff in WMCore to accommodate.
-                        wct.setDoNotPip(mPausingTasks.get(i).mToken);
-                    }
-                    // Since we will reparent out of the leashes, pre-emptively hide the child
-                    // surface to match the leash. Otherwise, there will be a flicker before the
-                    // visibility gets committed in Core when using split-screen (in splitscreen,
-                    // the leaf-tasks are not "independent" so aren't hidden by normal setup).
-                    t.hide(mPausingTasks.get(i).mTaskSurface);
-                }
-                if (mPipTask != null && mPipTransaction != null && sendUserLeaveHint) {
-                    t.show(mInfo.getChange(mPipTask).getLeash());
-                    PictureInPictureSurfaceTransaction.apply(mPipTransaction,
-                            mInfo.getChange(mPipTask).getLeash(), t);
-                    mPipTask = null;
-                    mPipTransaction = null;
-                }
-            }
-            try {
-                mFinishCB.onTransitionFinished(wct.isEmpty() ? null : wct, t);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Failed to call animation finish callback", e);
-                t.apply();
-            }
-            // Only release the non-local created surface references. The animator is responsible
-            // for releasing the leashes created by local.
-            mInfo.releaseAllSurfaces();
-            // Reset all members.
-            mListener = null;
-            mFinishCB = null;
-            mPausingTasks = null;
-            mOpeningTasks = null;
-            mAppearedTargets = null;
-            mInfo = null;
-            mOpeningSeparateHome = false;
-            mLeashMap = null;
-            mTransition = null;
-            mState = STATE_NORMAL;
-        }
-
-        @Override public void setDeferCancelUntilNextTransition(boolean defer, boolean screenshot) {
-        }
-
-        @Override public void cleanupScreenshot() {
-        }
-
-        @Override public void setWillFinishToHome(boolean willFinishToHome) {
-            mWillFinishToHome = willFinishToHome;
-        }
-
-        /**
-         * @see IRecentsAnimationController#removeTask
-         */
-        @Override public boolean removeTask(int taskId) {
-            return false;
-        }
-
-        /**
-         * @see IRecentsAnimationController#detachNavigationBarFromApp
-         */
-        @Override public void detachNavigationBarFromApp(boolean moveHomeToTop) {
-            try {
-                ActivityTaskManager.getService().detachNavigationBarFromApp(mTransition);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Failed to detach the navigation bar from app", e);
-            }
-        }
-
-        /**
-         * @see IRecentsAnimationController#animateNavigationBarToApp(long)
-         */
-        @Override public void animateNavigationBarToApp(long duration) {
-        }
-    }
-
-    /** Utility class to track the state of a task as-seen by recents. */
-    private static class TaskState {
-        WindowContainerToken mToken;
-        ActivityManager.RunningTaskInfo mTaskInfo;
-
-        /** The surface/leash of the task provided by Core. */
-        SurfaceControl mTaskSurface;
-
-        /** The (local) animation-leash created for this task. */
-        SurfaceControl mLeash;
-
-        TaskState(TransitionInfo.Change change, SurfaceControl leash) {
-            mToken = change.getContainer();
-            mTaskInfo = change.getTaskInfo();
-            mTaskSurface = change.getLeash();
-            mLeash = leash;
-        }
-
-        static int indexOf(ArrayList<TaskState> list, TransitionInfo.Change change) {
-            for (int i = list.size() - 1; i >= 0; --i) {
-                if (list.get(i).mToken.equals(change.getContainer())) {
-                    return i;
-                }
-            }
-            return -1;
-        }
-
-        public String toString() {
-            return "" + mToken + " : " + mLeash;
-        }
-    }
-}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/tracing/FrameProtoTracer.java b/packages/SystemUI/shared/src/com/android/systemui/shared/tracing/FrameProtoTracer.java
deleted file mode 100644
index 98212e1..0000000
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/tracing/FrameProtoTracer.java
+++ /dev/null
@@ -1,192 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.shared.tracing;
-
-import android.os.Trace;
-import android.util.Log;
-import android.view.Choreographer;
-
-import com.android.internal.util.TraceBuffer;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.util.ArrayDeque;
-import java.util.ArrayList;
-import java.util.Queue;
-import java.util.function.Consumer;
-
-/**
- * A proto tracer implementation that can be updated directly (upon state change), or on the next
- * scheduled frame.
- *
- * @param <P> The class type of the proto provider
- * @param <S> The proto class type of the encapsulating proto
- * @param <T> The proto class type of the individual proto entries in the buffer
- * @param <R> The proto class type of the entry root proto in the buffer
- */
-public class FrameProtoTracer<P, S extends P, T extends P, R>
-        implements Choreographer.FrameCallback {
-
-    private static final String TAG = "FrameProtoTracer";
-    private static final int BUFFER_CAPACITY = 1024 * 1024;
-
-    private final Object mLock = new Object();
-    private final TraceBuffer<P, S, T> mBuffer;
-    private final File mTraceFile;
-    private final ProtoTraceParams<P, S, T, R> mParams;
-    private Choreographer mChoreographer;
-    private final Queue<T> mPool = new ArrayDeque<>();
-    private final ArrayList<ProtoTraceable<R>> mTraceables = new ArrayList<>();
-    private final ArrayList<ProtoTraceable<R>> mTmpTraceables = new ArrayList<>();
-
-    private volatile boolean mEnabled;
-    private boolean mFrameScheduled;
-
-    private final TraceBuffer.ProtoProvider<P, S, T> mProvider =
-            new TraceBuffer.ProtoProvider<P, S, T>() {
-        @Override
-        public int getItemSize(P proto) {
-            return mParams.getProtoSize(proto);
-        }
-
-        @Override
-        public byte[] getBytes(P proto) {
-            return mParams.getProtoBytes(proto);
-        }
-
-        @Override
-        public void write(S encapsulatingProto, Queue<T> buffer, OutputStream os)
-                throws IOException {
-            os.write(mParams.serializeEncapsulatingProto(encapsulatingProto, buffer));
-        }
-    };
-
-    public interface ProtoTraceParams<P, S, T, R> {
-        File getTraceFile();
-        S getEncapsulatingTraceProto();
-        T updateBufferProto(T reuseObj, ArrayList<ProtoTraceable<R>> traceables);
-        byte[] serializeEncapsulatingProto(S encapsulatingProto, Queue<T> buffer);
-        byte[] getProtoBytes(P proto);
-        int getProtoSize(P proto);
-    }
-
-    public FrameProtoTracer(ProtoTraceParams<P, S, T, R> params) {
-        mParams = params;
-        mBuffer = new TraceBuffer<>(BUFFER_CAPACITY, mProvider, new Consumer<T>() {
-            @Override
-            public void accept(T t) {
-                onProtoDequeued(t);
-            }
-        });
-        mTraceFile = params.getTraceFile();
-    }
-
-    public void start() {
-        synchronized (mLock) {
-            if (mEnabled) {
-                return;
-            }
-            mBuffer.resetBuffer();
-            mEnabled = true;
-        }
-        logState();
-    }
-
-    public void stop() {
-        synchronized (mLock) {
-            if (!mEnabled) {
-                return;
-            }
-            mEnabled = false;
-        }
-        writeToFile();
-    }
-
-    public boolean isEnabled() {
-        return mEnabled;
-    }
-
-    public void add(ProtoTraceable<R> traceable) {
-        synchronized (mLock) {
-            mTraceables.add(traceable);
-        }
-    }
-
-    public void remove(ProtoTraceable<R> traceable) {
-        synchronized (mLock) {
-            mTraceables.remove(traceable);
-        }
-    }
-
-    public void scheduleFrameUpdate() {
-        if (!mEnabled || mFrameScheduled) {
-            return;
-        }
-
-        // Schedule an update on the next frame
-        if (mChoreographer == null) {
-            mChoreographer = Choreographer.getMainThreadInstance();
-        }
-        mChoreographer.postFrameCallback(this);
-        mFrameScheduled = true;
-    }
-
-    public void update() {
-        if (!mEnabled) {
-            return;
-        }
-
-        logState();
-    }
-
-    public float getBufferUsagePct() {
-        return (float) mBuffer.getBufferSize() / BUFFER_CAPACITY;
-    }
-
-    @Override
-    public void doFrame(long frameTimeNanos) {
-        logState();
-    }
-
-    private void onProtoDequeued(T proto) {
-        mPool.add(proto);
-    }
-
-    private void logState() {
-        synchronized (mLock) {
-            mTmpTraceables.addAll(mTraceables);
-        }
-
-        mBuffer.add(mParams.updateBufferProto(mPool.poll(), mTmpTraceables));
-        mTmpTraceables.clear();
-        mFrameScheduled = false;
-    }
-
-    private void writeToFile() {
-        try {
-            Trace.beginSection("ProtoTracer.writeToFile");
-            mBuffer.writeTraceToFile(mTraceFile, mParams.getEncapsulatingTraceProto());
-        } catch (IOException e) {
-            Log.e(TAG, "Unable to write buffer to file", e);
-        } finally {
-            Trace.endSection();
-        }
-    }
-}
-
-
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/tracing/ProtoTraceable.java b/packages/SystemUI/shared/src/com/android/systemui/shared/tracing/ProtoTraceable.java
deleted file mode 100644
index e05b0b0..0000000
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/tracing/ProtoTraceable.java
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
- * except in compliance with the License. You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- */
-
-package com.android.systemui.shared.tracing;
-
-/**
- * @see FrameProtoTracer
- */
-public interface ProtoTraceable<T> {
-
-    /**
-     * NOTE: Implementations should update all fields in this proto.
-     */
-    void writeToProto(T proto);
-}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/util/TraceUtils.kt b/packages/SystemUI/shared/src/com/android/systemui/util/TraceUtils.kt
index 64234c2..96a974d 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/util/TraceUtils.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/util/TraceUtils.kt
@@ -18,22 +18,23 @@
 
 import android.os.Trace
 import android.os.TraceNameSupplier
+import java.util.concurrent.atomic.AtomicInteger
 
 /**
- * Run a block within a [Trace] section.
- * Calls [Trace.beginSection] before and [Trace.endSection] after the passed block.
+ * Run a block within a [Trace] section. Calls [Trace.beginSection] before and [Trace.endSection]
+ * after the passed block.
  */
 inline fun <T> traceSection(tag: String, block: () -> T): T =
-        if (Trace.isTagEnabled(Trace.TRACE_TAG_APP)) {
-            Trace.traceBegin(Trace.TRACE_TAG_APP, tag)
-            try {
-                block()
-            } finally {
-                Trace.traceEnd(Trace.TRACE_TAG_APP)
-            }
-        } else {
+    if (Trace.isTagEnabled(Trace.TRACE_TAG_APP)) {
+        Trace.traceBegin(Trace.TRACE_TAG_APP, tag)
+        try {
             block()
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_APP)
         }
+    } else {
+        block()
+    }
 
 class TraceUtils {
     companion object {
@@ -43,6 +44,7 @@
 
         /**
          * Helper function for creating a Runnable object that implements TraceNameSupplier.
+         *
          * This is useful for posting Runnables to Handlers with meaningful names.
          */
         inline fun namedRunnable(tag: String, crossinline block: () -> Unit): Runnable {
@@ -51,5 +53,37 @@
                 override fun run() = block()
             }
         }
+
+        /**
+         * Cookie used for async traces. Shouldn't be public, but to use it inside inline methods
+         * there is no other way around.
+         */
+        val lastCookie = AtomicInteger(0)
+
+        /**
+         * Creates an async slice in a track called "AsyncTraces".
+         *
+         * This can be used to trace coroutine code. Note that all usages of this method will appear
+         * under a single track.
+         */
+        inline fun <T> traceAsync(method: String, block: () -> T): T =
+            traceAsync("AsyncTraces", method, block)
+
+        /**
+         * Creates an async slice in a track with [trackName] while [block] runs.
+         *
+         * This can be used to trace coroutine code. [method] will be the name of the slice,
+         * [trackName] of the track. The track is one of the rows visible in a perfetto trace inside
+         * SystemUI process.
+         */
+        inline fun <T> traceAsync(trackName: String, method: String, block: () -> T): T {
+            val cookie = lastCookie.incrementAndGet()
+            Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_APP, trackName, method, cookie)
+            try {
+                return block()
+            } finally {
+                Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_APP, trackName, cookie)
+            }
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/BouncerKeyguardMessageArea.kt b/packages/SystemUI/src/com/android/keyguard/BouncerKeyguardMessageArea.kt
index 0842953..78a5c98 100644
--- a/packages/SystemUI/src/com/android/keyguard/BouncerKeyguardMessageArea.kt
+++ b/packages/SystemUI/src/com/android/keyguard/BouncerKeyguardMessageArea.kt
@@ -26,7 +26,7 @@
 import android.view.View
 import com.android.app.animation.Interpolators
 import com.android.settingslib.Utils
-import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.ColorId.TITLE
+import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.ColorId.TITLE
 
 /** Displays security messages for the keyguard bouncer. */
 open class BouncerKeyguardMessageArea(context: Context?, attrs: AttributeSet?) :
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index 8ea4c31a..4a6e53d 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -15,18 +15,19 @@
  */
 package com.android.keyguard
 
-import android.app.WallpaperManager
 import android.content.BroadcastReceiver
 import android.content.Context
 import android.content.Intent
 import android.content.IntentFilter
 import android.content.res.Resources
 import android.text.format.DateFormat
-import android.util.TypedValue
 import android.util.Log
+import android.util.TypedValue
 import android.view.View
 import android.view.View.OnAttachStateChangeListener
 import android.view.ViewTreeObserver
+import android.view.ViewTreeObserver.OnGlobalLayoutListener
+import android.widget.FrameLayout
 import androidx.annotation.VisibleForTesting
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
@@ -42,7 +43,7 @@
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel.DEBUG
+import com.android.systemui.log.core.LogLevel.DEBUG
 import com.android.systemui.log.dagger.KeyguardLargeClockLog
 import com.android.systemui.log.dagger.KeyguardSmallClockLog
 import com.android.systemui.plugins.ClockController
@@ -88,17 +89,48 @@
 ) {
     var clock: ClockController? = null
         set(value) {
+            smallClockOnAttachStateChangeListener?.let {
+                field?.smallClock?.view?.removeOnAttachStateChangeListener(it)
+                smallClockFrame?.viewTreeObserver
+                        ?.removeOnGlobalLayoutListener(onGlobalLayoutListener)
+            }
+            largeClockOnAttachStateChangeListener?.let {
+                field?.largeClock?.view?.removeOnAttachStateChangeListener(it)
+            }
+
             field = value
             if (value != null) {
                 smallLogBuffer?.log(TAG, DEBUG, {}, { "New Clock" })
-                value.smallClock.logBuffer = smallLogBuffer
+                value.smallClock.messageBuffer = smallLogBuffer
                 largeLogBuffer?.log(TAG, DEBUG, {}, { "New Clock" })
-                value.largeClock.logBuffer = largeLogBuffer
+                value.largeClock.messageBuffer = largeLogBuffer
 
                 value.initialize(resources, dozeAmount, 0f)
 
                 if (!regionSamplingEnabled) {
                     updateColors()
+                } else {
+                    clock?.let {
+                        smallRegionSampler = createRegionSampler(
+                                it.smallClock.view,
+                                mainExecutor,
+                                bgExecutor,
+                                regionSamplingEnabled,
+                                isLockscreen = true,
+                                ::updateColors
+                        )?.apply { startRegionSampler() }
+
+                        largeRegionSampler = createRegionSampler(
+                                it.largeClock.view,
+                                mainExecutor,
+                                bgExecutor,
+                                regionSamplingEnabled,
+                                isLockscreen = true,
+                                ::updateColors
+                        )?.apply { startRegionSampler() }
+
+                        updateColors()
+                    }
                 }
                 updateFontSizes()
                 updateTimeListeners()
@@ -108,27 +140,62 @@
                     }
                     value.events.onWeatherDataChanged(it)
                 }
-                value.smallClock.view.addOnAttachStateChangeListener(
+
+                smallClockOnAttachStateChangeListener =
+                    object : OnAttachStateChangeListener {
+                        var pastVisibility: Int? = null
+                        override fun onViewAttachedToWindow(view: View?) {
+                            value.events.onTimeFormatChanged(DateFormat.is24HourFormat(context))
+                            if (view != null) {
+                                smallClockFrame = view.parent as FrameLayout
+                                smallClockFrame?.let {frame ->
+                                    pastVisibility = frame.visibility
+                                    onGlobalLayoutListener = OnGlobalLayoutListener {
+                                        val currentVisibility = frame.visibility
+                                        if (pastVisibility != currentVisibility) {
+                                            pastVisibility = currentVisibility
+                                            // when small clock  visible,
+                                            // recalculate bounds and sample
+                                            if (currentVisibility == View.VISIBLE) {
+                                                smallRegionSampler?.stopRegionSampler()
+                                                smallRegionSampler?.startRegionSampler()
+                                            }
+                                        }
+                                    }
+                                    frame.viewTreeObserver
+                                            .addOnGlobalLayoutListener(onGlobalLayoutListener)
+                                }
+                            }
+                        }
+
+                        override fun onViewDetachedFromWindow(p0: View?) {
+                            smallClockFrame?.viewTreeObserver
+                                    ?.removeOnGlobalLayoutListener(onGlobalLayoutListener)
+                        }
+                }
+                value.smallClock.view
+                        .addOnAttachStateChangeListener(smallClockOnAttachStateChangeListener)
+
+                largeClockOnAttachStateChangeListener =
                     object : OnAttachStateChangeListener {
                         override fun onViewAttachedToWindow(p0: View?) {
                             value.events.onTimeFormatChanged(DateFormat.is24HourFormat(context))
                         }
-
                         override fun onViewDetachedFromWindow(p0: View?) {
                         }
-                })
-                value.largeClock.view.addOnAttachStateChangeListener(
-                    object : OnAttachStateChangeListener {
-                        override fun onViewAttachedToWindow(p0: View?) {
-                            value.events.onTimeFormatChanged(DateFormat.is24HourFormat(context))
-                        }
-
-                        override fun onViewDetachedFromWindow(p0: View?) {
-                        }
-                })
+                }
+                value.largeClock.view
+                        .addOnAttachStateChangeListener(largeClockOnAttachStateChangeListener)
             }
         }
 
+    @VisibleForTesting
+    var smallClockOnAttachStateChangeListener: OnAttachStateChangeListener? = null
+    @VisibleForTesting
+    var largeClockOnAttachStateChangeListener: OnAttachStateChangeListener? = null
+    private var smallClockFrame: FrameLayout? = null
+    private var onGlobalLayoutListener: OnGlobalLayoutListener? = null
+
     private var isDozing = false
         private set
 
@@ -141,21 +208,19 @@
 
 
     private fun updateColors() {
-        val wallpaperManager = WallpaperManager.getInstance(context)
         if (regionSamplingEnabled) {
-            regionSampler?.let { regionSampler ->
-                clock?.let { clock ->
-                    if (regionSampler.sampledView == clock.smallClock.view) {
-                        smallClockIsDark = regionSampler.currentRegionDarkness().isDark
-                        clock.smallClock.events.onRegionDarknessChanged(smallClockIsDark)
-                        return@updateColors
-                    } else if (regionSampler.sampledView == clock.largeClock.view) {
-                        largeClockIsDark = regionSampler.currentRegionDarkness().isDark
-                        clock.largeClock.events.onRegionDarknessChanged(largeClockIsDark)
-                        return@updateColors
-                    }
+            clock?.let { clock ->
+                smallRegionSampler?.let {
+                    smallClockIsDark = it.currentRegionDarkness().isDark
+                    clock.smallClock.events.onRegionDarknessChanged(smallClockIsDark)
+                }
+
+                largeRegionSampler?.let {
+                    largeClockIsDark = it.currentRegionDarkness().isDark
+                    clock.largeClock.events.onRegionDarknessChanged(largeClockIsDark)
                 }
             }
+            return
         }
 
         val isLightTheme = TypedValue()
@@ -168,23 +233,6 @@
             largeClock.events.onRegionDarknessChanged(largeClockIsDark)
         }
     }
-
-    private fun updateRegionSampler(sampledRegion: View) {
-        regionSampler?.stopRegionSampler()
-        regionSampler =
-            createRegionSampler(
-                    sampledRegion,
-                    mainExecutor,
-                    bgExecutor,
-                    regionSamplingEnabled,
-                    isLockscreen = true,
-                    ::updateColors
-                )
-                ?.apply { startRegionSampler() }
-
-        updateColors()
-    }
-
     protected open fun createRegionSampler(
         sampledView: View,
         mainExecutor: Executor?,
@@ -202,7 +250,10 @@
         ) { updateColors() }
     }
 
-    var regionSampler: RegionSampler? = null
+    var smallRegionSampler: RegionSampler? = null
+        private set
+    var largeRegionSampler: RegionSampler? = null
+        private set
     var smallTimeListener: TimeListener? = null
     var largeTimeListener: TimeListener? = null
     val shouldTimeListenerRun: Boolean
@@ -284,7 +335,6 @@
             return
         }
         isRegistered = true
-
         broadcastDispatcher.registerReceiver(
             localeBroadcastReceiver,
             IntentFilter(Intent.ACTION_LOCALE_CHANGED)
@@ -319,9 +369,16 @@
         configurationController.removeCallback(configListener)
         batteryController.removeCallback(batteryCallback)
         keyguardUpdateMonitor.removeCallback(keyguardUpdateMonitorCallback)
-        regionSampler?.stopRegionSampler()
+        smallRegionSampler?.stopRegionSampler()
+        largeRegionSampler?.stopRegionSampler()
         smallTimeListener?.stop()
         largeTimeListener?.stop()
+        clock?.smallClock?.view
+                ?.removeOnAttachStateChangeListener(smallClockOnAttachStateChangeListener)
+        smallClockFrame?.viewTreeObserver
+                ?.removeOnGlobalLayoutListener(onGlobalLayoutListener)
+        clock?.largeClock?.view
+                ?.removeOnAttachStateChangeListener(largeClockOnAttachStateChangeListener)
     }
 
     private fun updateTimeListeners() {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
index d7e8616..bb11217 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
@@ -143,6 +143,7 @@
         long elapsedRealtime = SystemClock.elapsedRealtime();
         long secondsInFuture = (long) Math.ceil(
                 (elapsedRealtimeDeadline - elapsedRealtime) / 1000.0);
+        getKeyguardSecurityCallback().onAttemptLockoutStart(secondsInFuture);
         mCountdownTimer = new CountDownTimer(secondsInFuture * 1000, 1000) {
 
             @Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index 376e27c..4793b4f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -29,7 +29,7 @@
 import com.android.keyguard.dagger.KeyguardStatusViewScope;
 import com.android.systemui.R;
 import com.android.systemui.log.LogBuffer;
-import com.android.systemui.log.LogLevel;
+import com.android.systemui.log.core.LogLevel;
 import com.android.systemui.plugins.ClockController;
 import com.android.systemui.shared.clocks.DefaultClockController;
 
@@ -303,9 +303,10 @@
         if (!animate) {
             out.setAlpha(0f);
             out.setTranslationY(clockOutYTranslation);
+            out.setVisibility(INVISIBLE);
             in.setAlpha(1f);
             in.setTranslationY(clockInYTranslation);
-            in.setVisibility(View.VISIBLE);
+            in.setVisibility(VISIBLE);
             mStatusArea.setScaleX(statusAreaClockScale);
             mStatusArea.setScaleY(statusAreaClockScale);
             mStatusArea.setTranslateXFromClockDesign(statusAreaClockTranslateX);
@@ -323,7 +324,10 @@
                 ObjectAnimator.ofFloat(out, TRANSLATION_Y, clockOutYTranslation));
         mClockOutAnim.addListener(new AnimatorListenerAdapter() {
             public void onAnimationEnd(Animator animation) {
-                mClockOutAnim = null;
+                if (mClockOutAnim == animation) {
+                    out.setVisibility(INVISIBLE);
+                    mClockOutAnim = null;
+                }
             }
         });
 
@@ -338,7 +342,9 @@
         mClockInAnim.setStartDelay(CLOCK_IN_START_DELAY_MILLIS);
         mClockInAnim.addListener(new AnimatorListenerAdapter() {
             public void onAnimationEnd(Animator animation) {
-                mClockInAnim = null;
+                if (mClockInAnim == animation) {
+                    mClockInAnim = null;
+                }
             }
         });
 
@@ -359,7 +365,9 @@
                         statusAreaClockTranslateY));
         mStatusAreaAnim.addListener(new AnimatorListenerAdapter() {
             public void onAnimationEnd(Animator animation) {
-                mStatusAreaAnim = null;
+                if (mStatusAreaAnim == animation) {
+                    mStatusAreaAnim = null;
+                }
             }
         });
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index a026130..d615e18 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -21,6 +21,8 @@
 
 import static com.android.keyguard.KeyguardClockSwitch.LARGE;
 import static com.android.keyguard.KeyguardClockSwitch.SMALL;
+import static com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED;
+import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
 
 import android.annotation.Nullable;
 import android.database.ContentObserver;
@@ -33,14 +35,17 @@
 import android.widget.LinearLayout;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
 
 import com.android.systemui.Dumpable;
 import com.android.systemui.R;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
 import com.android.systemui.log.LogBuffer;
-import com.android.systemui.log.LogLevel;
+import com.android.systemui.log.core.LogLevel;
 import com.android.systemui.log.dagger.KeyguardClockLog;
 import com.android.systemui.plugins.ClockController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -58,6 +63,7 @@
 
 import java.io.PrintWriter;
 import java.util.Locale;
+import java.util.function.Consumer;
 
 import javax.inject.Inject;
 
@@ -99,8 +105,20 @@
     private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
 
     private boolean mOnlyClock = false;
+    private boolean mIsActiveDreamLockscreenHosted = false;
+    private FeatureFlags mFeatureFlags;
+    private KeyguardInteractor mKeyguardInteractor;
     private final DelayableExecutor mUiExecutor;
     private boolean mCanShowDoubleLineClock = true;
+    @VisibleForTesting
+    final Consumer<Boolean> mIsActiveDreamLockscreenHostedCallback =
+            (Boolean isLockscreenHosted) -> {
+                if (mIsActiveDreamLockscreenHosted == isLockscreenHosted) {
+                    return;
+                }
+                mIsActiveDreamLockscreenHosted = isLockscreenHosted;
+                updateKeyguardStatusAreaVisibility();
+            };
     private final ContentObserver mDoubleLineClockObserver = new ContentObserver(null) {
         @Override
         public void onChange(boolean change) {
@@ -137,7 +155,9 @@
             @Main DelayableExecutor uiExecutor,
             DumpManager dumpManager,
             ClockEventController clockEventController,
-            @KeyguardClockLog LogBuffer logBuffer) {
+            @KeyguardClockLog LogBuffer logBuffer,
+            KeyguardInteractor keyguardInteractor,
+            FeatureFlags featureFlags) {
         super(keyguardClockSwitch);
         mStatusBarStateController = statusBarStateController;
         mClockRegistry = clockRegistry;
@@ -151,6 +171,8 @@
         mClockEventController = clockEventController;
         mLogBuffer = logBuffer;
         mView.setLogBuffer(mLogBuffer);
+        mFeatureFlags = featureFlags;
+        mKeyguardInteractor = keyguardInteractor;
 
         mClockChangedListener = new ClockRegistry.ClockChangeListener() {
             @Override
@@ -189,8 +211,14 @@
         mSmallClockFrame = mView.findViewById(R.id.lockscreen_clock_view);
         mLargeClockFrame = mView.findViewById(R.id.lockscreen_clock_view_large);
 
-        mDumpManager.unregisterDumpable(getClass().toString()); // unregister previous clocks
-        mDumpManager.registerDumpable(getClass().toString(), this);
+        mDumpManager.unregisterDumpable(getClass().getSimpleName()); // unregister previous clocks
+        mDumpManager.registerDumpable(getClass().getSimpleName(), this);
+
+        if (mFeatureFlags.isEnabled(LOCKSCREEN_WALLPAPER_DREAM_ENABLED)) {
+            mStatusArea = mView.findViewById(R.id.keyguard_status_area);
+            collectFlow(mStatusArea, mKeyguardInteractor.isActiveDreamLockscreenHosted(),
+                    mIsActiveDreamLockscreenHostedCallback);
+        }
     }
 
     @Override
@@ -444,10 +472,6 @@
             int clockHeight = clock.getLargeClock().getView().getHeight();
             return frameHeight / 2 + clockHeight / 2 + mKeyguardLargeClockTopMargin / -2;
         } else {
-            // This is only called if we've never shown the large clock as the frame is inflated
-            // with 'gone', but then the visibility is never set when it is animated away by
-            // KeyguardClockSwitch, instead it is removed from the view hierarchy.
-            // TODO(b/261755021): Cleanup Large Frame Visibility
             int clockHeight = clock.getSmallClock().getView().getHeight();
             return clockHeight + statusBarHeaderHeight + mKeyguardSmallClockTopMargin;
         }
@@ -465,15 +489,11 @@
         if (mLargeClockFrame.getVisibility() == View.VISIBLE) {
             return clock.getLargeClock().getView().getHeight();
         } else {
-            // Is not called except in certain edge cases, see comment in getClockBottom
-            // TODO(b/261755021): Cleanup Large Frame Visibility
             return clock.getSmallClock().getView().getHeight();
         }
     }
 
     boolean isClockTopAligned() {
-        // Returns false except certain edge cases, see comment in getClockBottom
-        // TODO(b/261755021): Cleanup Large Frame Visibility
         return mLargeClockFrame.getVisibility() != View.VISIBLE;
     }
 
@@ -532,6 +552,15 @@
         }
     }
 
+    private void updateKeyguardStatusAreaVisibility() {
+        if (mStatusArea != null) {
+            mUiExecutor.execute(() -> {
+                mStatusArea.setVisibility(
+                        mIsActiveDreamLockscreenHosted ? View.INVISIBLE : View.VISIBLE);
+            });
+        }
+    }
+
     /**
      * Sets the clipChildren property on relevant views, to allow the smartspace to draw out of
      * bounds during the unlock transition.
@@ -552,9 +581,13 @@
         if (clock != null) {
             clock.dump(pw);
         }
-        final RegionSampler regionSampler = mClockEventController.getRegionSampler();
-        if (regionSampler != null) {
-            regionSampler.dump(pw);
+        final RegionSampler smallRegionSampler = mClockEventController.getSmallRegionSampler();
+        if (smallRegionSampler != null) {
+            smallRegionSampler.dump(pw);
+        }
+        final RegionSampler largeRegionSampler = mClockEventController.getLargeRegionSampler();
+        if (largeRegionSampler != null) {
+            largeRegionSampler.dump(pw);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardFingerprintListenModel.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardFingerprintListenModel.kt
index f2685c5..f23ae67 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardFingerprintListenModel.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardFingerprintListenModel.kt
@@ -27,6 +27,7 @@
     override var userId: Int = 0,
     override var listening: Boolean = false,
     // keepSorted
+    var allowOnCurrentOccludingActivity: Boolean = false,
     var alternateBouncerShowing: Boolean = false,
     var biometricEnabledForUser: Boolean = false,
     var bouncerIsOrWillShow: Boolean = false,
@@ -58,6 +59,7 @@
             userId.toString(),
             listening.toString(),
             // keep sorted
+            allowOnCurrentOccludingActivity.toString(),
             alternateBouncerShowing.toString(),
             biometricEnabledForUser.toString(),
             bouncerIsOrWillShow.toString(),
@@ -98,6 +100,7 @@
                 userId = model.userId
                 listening = model.listening
                 // keep sorted
+                allowOnCurrentOccludingActivity = model.allowOnCurrentOccludingActivity
                 alternateBouncerShowing = model.alternateBouncerShowing
                 biometricEnabledForUser = model.biometricEnabledForUser
                 bouncerIsOrWillShow = model.bouncerIsOrWillShow
@@ -144,6 +147,7 @@
                 "userId",
                 "listening",
                 // keep sorted
+                "allowOnCurrentOccludingActivity",
                 "alternateBouncerShowing",
                 "biometricAllowedForUser",
                 "bouncerIsOrWillShow",
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
index 3c05299..20e4656 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
@@ -17,6 +17,7 @@
 package com.android.keyguard;
 
 import android.annotation.CallSuper;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.res.ColorStateList;
 import android.content.res.Resources;
@@ -33,6 +34,10 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
+import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor;
+import com.android.systemui.bouncer.ui.BouncerMessageView;
+import com.android.systemui.bouncer.ui.binder.BouncerMessageViewBinder;
+import com.android.systemui.log.BouncerLogger;
 import com.android.systemui.statusbar.policy.DevicePostureController;
 import com.android.systemui.util.ViewController;
 import com.android.systemui.util.concurrency.DelayableExecutor;
@@ -168,6 +173,24 @@
     /** Determines the message to show in the bouncer when it first appears. */
     protected abstract int getInitialMessageResId();
 
+    /**
+     * Binds the {@link KeyguardInputView#getBouncerMessageView()} view with the provided context.
+     */
+    public void bindMessageView(
+            @NonNull BouncerMessageInteractor bouncerMessageInteractor,
+            KeyguardMessageAreaController.Factory messageAreaControllerFactory,
+            BouncerLogger bouncerLogger,
+            FeatureFlags featureFlags) {
+        BouncerMessageView bouncerMessageView = (BouncerMessageView) mView.getBouncerMessageView();
+        if (bouncerMessageView != null) {
+            BouncerMessageViewBinder.bind(bouncerMessageView,
+                    bouncerMessageInteractor,
+                    messageAreaControllerFactory,
+                    bouncerLogger,
+                    featureFlags);
+        }
+    }
+
     /** Factory for a {@link KeyguardInputViewController}. */
     public static class Factory {
         private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
index 2377057..d9b7bde 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
@@ -69,7 +69,7 @@
                 (long) (125 * KeyguardPatternView.DISAPPEAR_MULTIPLIER_LOCKED),
                 0.6f /* translationScale */,
                 0.45f /* delayScale */, AnimationUtils.loadInterpolator(
-                        mContext, android.R.interpolator.fast_out_linear_in));
+                       mContext, android.R.interpolator.fast_out_linear_in));
         mDisappearYTranslation = getResources().getDimensionPixelSize(
                 R.dimen.disappear_y_translation);
         mYTrans = getResources().getDimensionPixelSize(R.dimen.pin_view_trans_y_entry);
@@ -82,8 +82,10 @@
     }
 
     void onDevicePostureChanged(@DevicePostureInt int posture) {
-        mLastDevicePosture = posture;
-        updateMargins();
+        if (mLastDevicePosture != posture) {
+            mLastDevicePosture = posture;
+            updateMargins();
+        }
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java
index 99b5d52..2bdf46e 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java
@@ -16,8 +16,10 @@
 package com.android.keyguard;
 
 import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_HALF_OPENED;
+import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_UNKNOWN;
 
 import android.content.Context;
+import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.os.SystemClock;
 import android.text.TextUtils;
@@ -74,6 +76,7 @@
     BouncerKeyguardMessageArea mSecurityMessageDisplay;
     private View mEcaView;
     private ConstraintLayout mContainer;
+    @DevicePostureInt private int mLastDevicePosture = DEVICE_POSTURE_UNKNOWN;
 
     public KeyguardPatternView(Context context) {
         this(context, null);
@@ -95,14 +98,27 @@
                 mContext, android.R.interpolator.fast_out_linear_in));
     }
 
+    @Override
+    protected void onConfigurationChanged(Configuration newConfig) {
+        updateMargins();
+    }
+
     void onDevicePostureChanged(@DevicePostureInt int posture) {
+        if (mLastDevicePosture != posture) {
+            mLastDevicePosture = posture;
+            updateMargins();
+        }
+    }
+
+    private void updateMargins() {
         // Update the guideline based on the device posture...
         float halfOpenPercentage =
                 mContext.getResources().getFloat(R.dimen.half_opened_bouncer_height_ratio);
 
         ConstraintSet cs = new ConstraintSet();
         cs.clone(mContainer);
-        cs.setGuidelinePercent(R.id.pattern_top_guideline, posture == DEVICE_POSTURE_HALF_OPENED
+        cs.setGuidelinePercent(R.id.pattern_top_guideline,
+                mLastDevicePosture == DEVICE_POSTURE_HALF_OPENED
                 ? halfOpenPercentage : 0.0f);
         cs.applyTo(mContainer);
     }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
index 3d255a5..49f788c 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
@@ -236,6 +236,7 @@
                 getKeyguardSecurityCallback().onCancelClicked();
             });
         }
+        mView.onDevicePostureChanged(mPostureController.getDevicePosture());
         mPostureController.addCallback(mPostureCallback);
     }
 
@@ -368,6 +369,7 @@
         final long elapsedRealtime = SystemClock.elapsedRealtime();
         final long secondsInFuture = (long) Math.ceil(
                 (elapsedRealtimeDeadline - elapsedRealtime) / 1000.0);
+        getKeyguardSecurityCallback().onAttemptLockoutStart(secondsInFuture);
         mCountdownTimer = new CountDownTimer(secondsInFuture * 1000, 1000) {
 
             @Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
index 3e16d55..b3e08c0 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
@@ -77,6 +77,7 @@
             });
         }
         mPasswordEntry.setUserActivityListener(this::onUserInput);
+        mView.onDevicePostureChanged(mPostureController.getDevicePosture());
         mPostureController.addCallback(mPostureCallback);
     }
 
@@ -164,15 +165,18 @@
      * Responsible for identifying if PIN hinting is to be enabled or not
      */
     private boolean isPinHinting() {
-        return mLockPatternUtils.getPinLength(KeyguardUpdateMonitor.getCurrentUser())
-                == DEFAULT_PIN_LENGTH;
+        return mPinLength == DEFAULT_PIN_LENGTH;
     }
 
     /**
-     * Responsible for identifying if auto confirm is enabled or not in Settings
+     * Responsible for identifying if auto confirm is enabled or not in Settings and
+     * a valid PIN_LENGTH is stored on the device (though the latter check is only to make it more
+     * robust since we only allow enabling PIN confirmation if the user has a valid PIN length
+     * saved on device)
      */
     private boolean isAutoPinConfirmEnabledInSettings() {
         //Checks if user has enabled the auto confirm in Settings
-        return mLockPatternUtils.isAutoPinConfirmEnabled(KeyguardUpdateMonitor.getCurrentUser());
+        return mLockPatternUtils.isAutoPinConfirmEnabled(KeyguardUpdateMonitor.getCurrentUser())
+                && mPinLength != LockPatternUtils.PIN_LENGTH_UNAVAILABLE;
     }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityCallback.java
index bf9c3bb..2878df2 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityCallback.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityCallback.java
@@ -87,6 +87,11 @@
     default void onUserInput() {
     }
 
+    /**
+     * Invoked when the auth input is disabled for specified number of seconds.
+     * @param seconds Number of seconds for which the auth input is disabled.
+     */
+    default void onAttemptLockoutStart(long seconds) {}
 
     /**
      * Dismisses keyguard and go to unlocked state.
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index 841b5b3..42a4e72 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -281,6 +281,8 @@
 
     public interface SwipeListener {
         void onSwipeUp();
+        /** */
+        void onSwipeDown();
     }
 
     @VisibleForTesting
@@ -543,6 +545,11 @@
                 if (mSwipeListener != null) {
                     mSwipeListener.onSwipeUp();
                 }
+            } else if (getTranslationY() > TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+                    MIN_DRAG_SIZE, getResources().getDisplayMetrics())) {
+                if (mSwipeListener != null) {
+                    mSwipeListener.onSwipeDown();
+                }
             }
         }
         return true;
@@ -786,8 +793,6 @@
 
     void reloadColors() {
         mViewMode.reloadColors();
-        setBackgroundColor(Utils.getColorAttrDefaultColor(getContext(),
-                com.android.internal.R.attr.materialColorSurface));
     }
 
     /** Handles density or font scale changes. */
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 74b29a7..f952337 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -70,6 +70,7 @@
 import com.android.systemui.R;
 import com.android.systemui.biometrics.SideFpsController;
 import com.android.systemui.biometrics.SideFpsUiRequestSource;
+import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor;
 import com.android.systemui.classifier.FalsingA11yDelegate;
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.flags.FeatureFlags;
@@ -78,17 +79,25 @@
 import com.android.systemui.log.SessionTracker;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.scene.domain.interactor.SceneInteractor;
+import com.android.systemui.scene.shared.model.SceneContainerNames;
+import com.android.systemui.scene.shared.model.SceneKey;
 import com.android.systemui.shared.system.SysUiStatsLog;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.UserSwitcherController;
+import com.android.systemui.user.domain.interactor.UserInteractor;
 import com.android.systemui.util.ViewController;
+import com.android.systemui.util.kotlin.JavaAdapter;
 import com.android.systemui.util.settings.GlobalSettings;
 
 import java.io.File;
 import java.util.Optional;
 
 import javax.inject.Inject;
+import javax.inject.Provider;
+
+import kotlinx.coroutines.Job;
 
 /** Controller for {@link KeyguardSecurityContainer} */
 @KeyguardBouncerScope
@@ -116,6 +125,7 @@
     private final Optional<SideFpsController> mSideFpsController;
     private final FalsingA11yDelegate mFalsingA11yDelegate;
     private final KeyguardFaceAuthInteractor mKeyguardFaceAuthInteractor;
+    private final BouncerMessageInteractor mBouncerMessageInteractor;
     private int mTranslationY;
     // Whether the volume keys should be handled by keyguard. If true, then
     // they will be handled here for specific media types such as music, otherwise
@@ -178,6 +188,7 @@
 
         @Override
         public void onUserInput() {
+            mBouncerMessageInteractor.onPrimaryBouncerUserInput();
             mKeyguardFaceAuthInteractor.onPrimaryBouncerUserInput();
             mUpdateMonitor.cancelFaceAuth();
         }
@@ -207,7 +218,15 @@
         }
 
         @Override
+        public void onAttemptLockoutStart(long seconds) {
+            mBouncerMessageInteractor.onPrimaryAuthLockedOut(seconds);
+        }
+
+        @Override
         public void reportUnlockAttempt(int userId, boolean success, int timeoutMs) {
+            if (timeoutMs == 0 && !success) {
+                mBouncerMessageInteractor.onPrimaryAuthIncorrectAttempt();
+            }
             int bouncerSide = SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED__SIDE__DEFAULT;
             if (mView.isSidedSecurityMode()) {
                 bouncerSide = mView.isSecurityLeftAligned()
@@ -308,6 +327,11 @@
                         "swipeUpOnBouncer");
             }
         }
+
+        @Override
+        public void onSwipeDown() {
+            mViewMediatorCallback.onBouncerSwipeDown();
+        }
     };
     private final ConfigurationController.ConfigurationListener mConfigurationListener =
             new ConfigurationController.ConfigurationListener() {
@@ -362,6 +386,10 @@
                     showPrimarySecurityScreen(false);
                 }
             };
+    private final UserInteractor mUserInteractor;
+    private final Provider<SceneInteractor> mSceneInteractor;
+    private final Provider<JavaAdapter> mJavaAdapter;
+    @Nullable private Job mSceneTransitionCollectionJob;
 
     @Inject
     public KeyguardSecurityContainerController(KeyguardSecurityContainer view,
@@ -385,7 +413,11 @@
             TelephonyManager telephonyManager,
             ViewMediatorCallback viewMediatorCallback,
             AudioManager audioManager,
-            KeyguardFaceAuthInteractor keyguardFaceAuthInteractor
+            KeyguardFaceAuthInteractor keyguardFaceAuthInteractor,
+            BouncerMessageInteractor bouncerMessageInteractor,
+            Provider<JavaAdapter> javaAdapter,
+            UserInteractor userInteractor,
+            Provider<SceneInteractor> sceneInteractor
     ) {
         super(view);
         mLockPatternUtils = lockPatternUtils;
@@ -411,6 +443,10 @@
         mViewMediatorCallback = viewMediatorCallback;
         mAudioManager = audioManager;
         mKeyguardFaceAuthInteractor = keyguardFaceAuthInteractor;
+        mBouncerMessageInteractor = bouncerMessageInteractor;
+        mUserInteractor = userInteractor;
+        mSceneInteractor = sceneInteractor;
+        mJavaAdapter = javaAdapter;
     }
 
     @Override
@@ -431,7 +467,26 @@
         // Update ViewMediator with the current input method requirements
         mViewMediatorCallback.setNeedsInput(needsInput());
         mView.setOnKeyListener(mOnKeyListener);
+
         showPrimarySecurityScreen(false);
+
+        if (mFeatureFlags.isEnabled(Flags.SCENE_CONTAINER)) {
+            // When the scene framework transitions from bouncer to gone, we dismiss the keyguard.
+            mSceneTransitionCollectionJob = mJavaAdapter.get().alwaysCollectFlow(
+                mSceneInteractor.get().sceneTransitions(SceneContainerNames.SYSTEM_UI_DEFAULT),
+                sceneTransitionModel -> {
+                    if (sceneTransitionModel != null
+                            && sceneTransitionModel.getFrom() == SceneKey.Bouncer.INSTANCE
+                            && sceneTransitionModel.getTo() == SceneKey.Gone.INSTANCE) {
+                        final int selectedUserId = mUserInteractor.getSelectedUserId();
+                        showNextSecurityScreenOrFinish(
+                                /* authenticated= */ true,
+                                selectedUserId,
+                                /* bypassSecondaryLockScreen= */ true,
+                                mSecurityModel.getSecurityMode(selectedUserId));
+                    }
+                });
+        }
     }
 
     @Override
@@ -440,6 +495,11 @@
         mConfigurationController.removeCallback(mConfigurationListener);
         mView.removeMotionEventListener(mGlobalTouchListener);
         mUserSwitcherController.removeUserSwitchCallback(mUserSwitchCallback);
+
+        if (mSceneTransitionCollectionJob != null) {
+            mSceneTransitionCollectionJob.cancel(null);
+            mSceneTransitionCollectionJob = null;
+        }
     }
 
     /** */
@@ -525,7 +585,6 @@
     public void setOnDismissAction(ActivityStarter.OnDismissAction action, Runnable cancelAction) {
         if (mCancelAction != null) {
             mCancelAction.run();
-            mCancelAction = null;
         }
         mDismissAction = action;
         mCancelAction = cancelAction;
@@ -763,9 +822,10 @@
                 case SimPuk:
                     // Shortcut for SIM PIN/PUK to go to directly to user's security screen or home
                     SecurityMode securityMode = mSecurityModel.getSecurityMode(targetUserId);
-                    if (securityMode == SecurityMode.None || mLockPatternUtils.isLockScreenDisabled(
-                            KeyguardUpdateMonitor.getCurrentUser())) {
-                        finish = true;
+                    boolean isLockscreenDisabled = mLockPatternUtils.isLockScreenDisabled(
+                            KeyguardUpdateMonitor.getCurrentUser());
+                    if (securityMode == SecurityMode.None || isLockscreenDisabled) {
+                        finish = isLockscreenDisabled;
                         eventSubtype = BOUNCER_DISMISS_SIM;
                         uiEvent = BouncerUiEvent.BOUNCER_DISMISS_SIM;
                     } else {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimInputView.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardSimInputView.kt
index 1461dbe..7c8d91f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimInputView.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimInputView.kt
@@ -22,7 +22,7 @@
 import androidx.core.graphics.drawable.DrawableCompat
 import com.android.settingslib.Utils
 import com.android.systemui.R
-import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.ColorId.EMERGENCY_BUTTON
+import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.ColorId.EMERGENCY_BUTTON
 
 abstract class KeyguardSimInputView(context: Context, attrs: AttributeSet) :
     KeyguardPinBasedInputView(context, attrs) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
index 61280af..886a1b5 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
@@ -53,7 +53,7 @@
 
     private ProgressDialog mSimUnlockProgressDialog;
     private CheckSimPin mCheckSimPinThread;
-    private int mRemainingAttempts;
+    private int mRemainingAttempts = -1;
     // Below flag is set to true during power-up or when a new SIM card inserted on device.
     // When this is true and when SIM card is PIN locked state, on PIN lock screen, message would
     // be displayed to inform user about the number of remaining PIN attempts left.
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index de24024..35198de 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -18,10 +18,14 @@
 
 import static android.app.StatusBarManager.SESSION_KEYGUARD;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.content.Intent.ACTION_USER_REMOVED;
 import static android.content.Intent.ACTION_USER_STOPPED;
 import static android.content.Intent.ACTION_USER_UNLOCKED;
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
 import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_LOCKOUT_NONE;
 import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_LOCKOUT_PERMANENT;
 import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_LOCKOUT_TIMED;
@@ -76,9 +80,9 @@
 import android.annotation.AnyThread;
 import android.annotation.MainThread;
 import android.annotation.SuppressLint;
-import android.app.ActivityTaskManager;
 import android.app.ActivityTaskManager.RootTaskInfo;
 import android.app.AlarmManager;
+import android.app.IActivityTaskManager;
 import android.app.admin.DevicePolicyManager;
 import android.app.trust.TrustManager;
 import android.content.BroadcastReceiver;
@@ -155,16 +159,18 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.dump.DumpsysTableLogger;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.keyguard.domain.interactor.FaceAuthenticationListener;
 import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
 import com.android.systemui.keyguard.shared.constants.TrustAgentUiEvent;
-import com.android.systemui.keyguard.shared.model.AcquiredAuthenticationStatus;
-import com.android.systemui.keyguard.shared.model.AuthenticationStatus;
-import com.android.systemui.keyguard.shared.model.DetectionStatus;
-import com.android.systemui.keyguard.shared.model.ErrorAuthenticationStatus;
-import com.android.systemui.keyguard.shared.model.FailedAuthenticationStatus;
-import com.android.systemui.keyguard.shared.model.HelpAuthenticationStatus;
-import com.android.systemui.keyguard.shared.model.SuccessAuthenticationStatus;
+import com.android.systemui.keyguard.shared.model.AcquiredFaceAuthenticationStatus;
+import com.android.systemui.keyguard.shared.model.ErrorFaceAuthenticationStatus;
+import com.android.systemui.keyguard.shared.model.FaceAuthenticationStatus;
+import com.android.systemui.keyguard.shared.model.FaceDetectionStatus;
+import com.android.systemui.keyguard.shared.model.FailedFaceAuthenticationStatus;
+import com.android.systemui.keyguard.shared.model.HelpFaceAuthenticationStatus;
+import com.android.systemui.keyguard.shared.model.SuccessFaceAuthenticationStatus;
 import com.android.systemui.keyguard.shared.model.SysUiFaceAuthenticateOptions;
 import com.android.systemui.log.SessionTracker;
 import com.android.systemui.plugins.WeatherData;
@@ -247,6 +253,7 @@
     private static final int MSG_REQUIRE_NFC_UNLOCK = 345;
     private static final int MSG_KEYGUARD_DISMISS_ANIMATION_FINISHED = 346;
     private static final int MSG_SERVICE_PROVIDERS_UPDATED = 347;
+    private static final int MSG_BIOMETRIC_ENROLLMENT_STATE_CHANGED = 348;
 
     /** Biometric authentication state: Not listening. */
     private static final int BIOMETRIC_STATE_STOPPED = 0;
@@ -308,6 +315,7 @@
     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;
     private final StatusBarStateController.StateListener mStatusBarStateControllerListener =
@@ -349,6 +357,7 @@
     private boolean mSecureCameraLaunched;
     @VisibleForTesting
     protected boolean mTelephonyCapable;
+    private boolean mAllowFingerprintOnCurrentOccludingActivity;
 
     // Device provisioning state
     private boolean mDeviceProvisioned;
@@ -385,12 +394,15 @@
     private final ActiveUnlockConfig mActiveUnlockConfig;
     private final IDreamManager mDreamManager;
     private final TelephonyManager mTelephonyManager;
+    private final FeatureFlags mFeatureFlags;
     @Nullable
     private final FingerprintManager mFpm;
     @Nullable
     private final FaceManager mFaceManager;
     @Nullable
     private KeyguardFaceAuthInteractor mFaceAuthInteractor;
+    private final TaskStackChangeListeners mTaskStackChangeListeners;
+    private final IActivityTaskManager mActivityTaskManager;
     private final LockPatternUtils mLockPatternUtils;
     @VisibleForTesting
     @DevicePostureInt
@@ -1459,35 +1471,31 @@
     private FaceAuthenticationListener mFaceAuthenticationListener =
             new FaceAuthenticationListener() {
                 @Override
-                public void onAuthenticationStatusChanged(@NonNull AuthenticationStatus status) {
-                    if (status instanceof AcquiredAuthenticationStatus) {
+                public void onAuthenticationStatusChanged(
+                        @NonNull FaceAuthenticationStatus status
+                ) {
+                    if (status instanceof AcquiredFaceAuthenticationStatus) {
                         handleFaceAcquired(
-                                ((AcquiredAuthenticationStatus) status).getAcquiredInfo());
-                    } else if (status instanceof ErrorAuthenticationStatus) {
-                        ErrorAuthenticationStatus error = (ErrorAuthenticationStatus) status;
+                                ((AcquiredFaceAuthenticationStatus) status).getAcquiredInfo());
+                    } else if (status instanceof ErrorFaceAuthenticationStatus) {
+                        ErrorFaceAuthenticationStatus error =
+                                (ErrorFaceAuthenticationStatus) status;
                         handleFaceError(error.getMsgId(), error.getMsg());
-                    } else if (status instanceof FailedAuthenticationStatus) {
-                        if (isFaceLockedOut()) {
-                            // TODO b/270090188: remove this hack when biometrics fixes this issue.
-                            // FailedAuthenticationStatus is emitted after ErrorAuthenticationStatus
-                            // for lockout error is received
-                            mLogger.d("onAuthenticationFailed called after"
-                                    + " face has been locked out");
-                            return;
-                        }
+                    } else if (status instanceof FailedFaceAuthenticationStatus) {
                         handleFaceAuthFailed();
-                    } else if (status instanceof HelpAuthenticationStatus) {
-                        HelpAuthenticationStatus helpMsg = (HelpAuthenticationStatus) status;
+                    } else if (status instanceof HelpFaceAuthenticationStatus) {
+                        HelpFaceAuthenticationStatus helpMsg =
+                                (HelpFaceAuthenticationStatus) status;
                         handleFaceHelp(helpMsg.getMsgId(), helpMsg.getMsg());
-                    } else if (status instanceof SuccessAuthenticationStatus) {
+                    } else if (status instanceof SuccessFaceAuthenticationStatus) {
                         FaceManager.AuthenticationResult result =
-                                ((SuccessAuthenticationStatus) status).getSuccessResult();
+                                ((SuccessFaceAuthenticationStatus) status).getSuccessResult();
                         handleFaceAuthenticated(result.getUserId(), result.isStrongBiometric());
                     }
                 }
 
                 @Override
-                public void onDetectionStatusChanged(@NonNull DetectionStatus status) {
+                public void onDetectionStatusChanged(@NonNull FaceDetectionStatus status) {
                     handleFaceAuthenticated(status.getUserId(), status.isStrongBiometric());
                 }
             };
@@ -1985,13 +1993,6 @@
 
                 @Override
                 public void onAuthenticationFailed() {
-                    if (isFaceLockedOut()) {
-                        // TODO b/270090188: remove this hack when biometrics fixes this issue.
-                        // onAuthenticationFailed is called after onAuthenticationError
-                        // for lockout error is received
-                        mLogger.d("onAuthenticationFailed called after face has been locked out");
-                        return;
-                    }
                     handleFaceAuthFailed();
                 }
 
@@ -2338,7 +2339,10 @@
             @Nullable BiometricManager biometricManager,
             FaceWakeUpTriggersConfig faceWakeUpTriggersConfig,
             DevicePostureController devicePostureController,
-            Optional<FingerprintInteractiveToAuthProvider> interactiveToAuthProvider) {
+            Optional<FingerprintInteractiveToAuthProvider> interactiveToAuthProvider,
+            FeatureFlags featureFlags,
+            TaskStackChangeListeners taskStackChangeListeners,
+            IActivityTaskManager activityTaskManagerService) {
         mContext = context;
         mSubscriptionManager = subscriptionManager;
         mUserTracker = userTracker;
@@ -2355,7 +2359,7 @@
         mLockPatternUtils = lockPatternUtils;
         mAuthController = authController;
         mSecureSettings = secureSettings;
-        dumpManager.registerDumpable(getClass().getName(), this);
+        dumpManager.registerDumpable(this);
         mSensorPrivacyManager = sensorPrivacyManager;
         mActiveUnlockConfig = activeUnlockConfiguration;
         mLogger = logger;
@@ -2370,6 +2374,7 @@
         mPackageManager = packageManager;
         mFpm = fingerprintManager;
         mFaceManager = faceManager;
+        mFeatureFlags = featureFlags;
         mActiveUnlockConfig.setKeyguardUpdateMonitor(this);
         mFaceAcquiredInfoIgnoreList = Arrays.stream(
                 mContext.getResources().getIntArray(
@@ -2379,6 +2384,12 @@
         mConfigFaceAuthSupportedPosture = mContext.getResources().getInteger(
                 R.integer.config_face_auth_supported_posture);
         mFaceWakeUpTriggersConfig = faceWakeUpTriggersConfig;
+        mAllowFingerprintOnOccludingActivitiesFromPackage = Arrays.stream(
+                mContext.getResources().getStringArray(
+                        R.array.config_fingerprint_listen_on_occluding_activity_packages))
+                .collect(Collectors.toSet());
+        mTaskStackChangeListeners = taskStackChangeListeners;
+        mActivityTaskManager = activityTaskManagerService;
 
         mHandler = new Handler(mainLooper) {
             @Override
@@ -2485,6 +2496,9 @@
                     case MSG_KEYGUARD_DISMISS_ANIMATION_FINISHED:
                         handleKeyguardDismissAnimationFinished();
                         break;
+                    case MSG_BIOMETRIC_ENROLLMENT_STATE_CHANGED:
+                        notifyAboutEnrollmentChange(msg.arg1);
+                        break;
                     default:
                         super.handleMessage(msg);
                         break;
@@ -2585,6 +2599,8 @@
 
             @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));
             }
@@ -2594,7 +2610,7 @@
         }
         updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE, FACE_AUTH_UPDATED_ON_KEYGUARD_INIT);
 
-        TaskStackChangeListeners.getInstance().registerTaskStackListener(mTaskStackListener);
+        mTaskStackChangeListeners.registerTaskStackListener(mTaskStackListener);
         mIsSystemUser = mUserManager.isSystemUser();
         int user = mUserTracker.getUserId();
         mUserIsUnlocked.put(user, mUserManager.isUserUnlocked(user));
@@ -3061,7 +3077,12 @@
                         || shouldListenForFingerprintAssistant
                         || (mKeyguardOccluded && mIsDreaming)
                         || (mKeyguardOccluded && userDoesNotHaveTrust && mKeyguardShowing
-                            && (mOccludingAppRequestingFp || isUdfps || mAlternateBouncerShowing));
+                        && (mOccludingAppRequestingFp
+                        || isUdfps
+                        || mAlternateBouncerShowing
+                        || mAllowFingerprintOnCurrentOccludingActivity
+                )
+            );
 
         // Only listen if this KeyguardUpdateMonitor belongs to the system user. There is an
         // instance of KeyguardUpdateMonitor for each user but KeyguardUpdateMonitor is user-aware.
@@ -3102,6 +3123,7 @@
                     System.currentTimeMillis(),
                     user,
                     shouldListen,
+                    mAllowFingerprintOnCurrentOccludingActivity,
                     mAlternateBouncerShowing,
                     biometricEnabledForUser,
                     mPrimaryBouncerIsOrWillBeShowing,
@@ -3218,6 +3240,13 @@
                 || (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);
         if (model instanceof KeyguardFingerprintListenModel) {
@@ -3421,6 +3450,25 @@
         return mIsFaceEnrolled;
     }
 
+    private void notifyAboutEnrollmentChange(@BiometricAuthenticator.Modality int modality) {
+        BiometricSourceType biometricSourceType;
+        if (modality == TYPE_FINGERPRINT) {
+            biometricSourceType = FINGERPRINT;
+        } else if (modality == TYPE_FACE) {
+            biometricSourceType = FACE;
+        } else {
+            return;
+        }
+        mLogger.notifyAboutEnrollmentsChanged(biometricSourceType);
+        Assert.isMainThread();
+        for (int i = 0; i < mCallbacks.size(); i++) {
+            KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+            if (cb != null) {
+                cb.onBiometricEnrollmentStateChanged(biometricSourceType);
+            }
+        }
+    }
+
     private void stopListeningForFingerprint() {
         mLogger.v("stopListeningForFingerprint()");
         if (mFingerprintRunningState == BIOMETRIC_STATE_RUNNING) {
@@ -4122,19 +4170,35 @@
         return mSimDatas.get(subId).slotId;
     }
 
-    private final TaskStackChangeListener
-            mTaskStackListener = new TaskStackChangeListener() {
+    private final TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() {
         @Override
         public void onTaskStackChangedBackground() {
             try {
-                RootTaskInfo info = ActivityTaskManager.getService().getRootTaskInfo(
+                if (mFeatureFlags.isEnabled(Flags.FP_LISTEN_OCCLUDING_APPS)) {
+                    RootTaskInfo standardTask = mActivityTaskManager.getRootTaskInfo(
+                            WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
+                    final boolean previousState = mAllowFingerprintOnCurrentOccludingActivity;
+                    mAllowFingerprintOnCurrentOccludingActivity =
+                            standardTask.topActivity != null
+                                    && !TextUtils.isEmpty(standardTask.topActivity.getPackageName())
+                                    && mAllowFingerprintOnOccludingActivitiesFromPackage.contains(
+                                            standardTask.topActivity.getPackageName())
+                                    && standardTask.visible;
+                    if (mAllowFingerprintOnCurrentOccludingActivity != previousState) {
+                        mLogger.allowFingerprintOnCurrentOccludingActivityChanged(
+                                mAllowFingerprintOnCurrentOccludingActivity);
+                        updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
+                    }
+                }
+
+                RootTaskInfo assistantTask = mActivityTaskManager.getRootTaskInfo(
                         WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_ASSISTANT);
-                if (info == null) {
+                if (assistantTask == null) {
                     return;
                 }
-                mLogger.logTaskStackChangedForAssistant(info.visible);
+                mLogger.logTaskStackChangedForAssistant(assistantTask.visible);
                 mHandler.sendMessage(mHandler.obtainMessage(MSG_ASSISTANT_STACK_CHANGED,
-                        info.visible));
+                        assistantTask.visible));
             } catch (RemoteException e) {
                 mLogger.logException(e, "unable to check task stack ");
             }
@@ -4326,7 +4390,7 @@
 
         mUserTracker.removeCallback(mUserChangedCallback);
 
-        TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mTaskStackListener);
+        mTaskStackChangeListeners.unregisterTaskStackListener(mTaskStackListener);
 
         mBroadcastDispatcher.unregisterReceiver(mBroadcastReceiver);
         mBroadcastDispatcher.unregisterReceiver(mBroadcastAllReceiver);
@@ -4385,6 +4449,8 @@
             pw.println("    enabledByUser=" + mBiometricEnabledForUser.get(userId));
             pw.println("    mKeyguardOccluded=" + mKeyguardOccluded);
             pw.println("    mIsDreaming=" + mIsDreaming);
+            pw.println("    mFingerprintListenOnOccludingActivitiesFromPackage="
+                    + mAllowFingerprintOnOccludingActivitiesFromPackage);
             if (isUdfpsSupported()) {
                 pw.println("        udfpsEnrolled=" + isUdfpsEnrolled());
                 pw.println("        shouldListenForUdfps=" + shouldListenForFingerprint(true));
@@ -4481,13 +4547,18 @@
      * Cancels all operations in the scheduler if it is hung for 10 seconds.
      */
     public void startBiometricWatchdog() {
-        if (mFaceManager != null && !isFaceAuthInteractorEnabled()) {
-            mLogger.scheduleWatchdog("face");
-            mFaceManager.scheduleWatchdog();
-        }
-        if (mFpm != null) {
-            mLogger.scheduleWatchdog("fingerprint");
-            mFpm.scheduleWatchdog();
-        }
+        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();
+            }
+            Trace.endSection();
+        });
     }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
index 7394005..7b59632 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
@@ -327,4 +327,9 @@
      * Called when the enabled trust agents associated with the specified user.
      */
     public void onEnabledTrustAgentsChanged(int userId) { }
+
+    /**
+     * On biometric enrollment state changed
+     */
+    public void onBiometricEnrollmentStateChanged(BiometricSourceType biometricSourceType) { }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java
index 281067d..bc12aee 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java
@@ -119,6 +119,14 @@
     boolean isUnlockWithWallpaper();
 
     /**
+     * @return Whether the bouncer over dream is showing. Note that the bouncer over dream is
+     * handled independently of the rest of the notification panel. As a result, setting this state
+     * via {@link CentralSurfaces#setBouncerShowing(boolean)} leads to unintended side effects from
+     * states modified behind the dream.
+     */
+    boolean isBouncerShowingOverDream();
+
+    /**
      * @return Whether subtle animation should be used for unlocking the device.
      */
     boolean shouldSubtleWindowAnimationsForUnlock();
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
index 61af722..71f78c3 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
@@ -23,7 +23,7 @@
 
 import com.android.app.animation.Interpolators;
 import com.android.systemui.log.LogBuffer;
-import com.android.systemui.log.LogLevel;
+import com.android.systemui.log.core.LogLevel;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.notification.AnimatableProperty;
 import com.android.systemui.statusbar.notification.PropertyAnimator;
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconView.java b/packages/SystemUI/src/com/android/keyguard/LockIconView.java
index abad0be..76b073e 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconView.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconView.java
@@ -16,6 +16,8 @@
 
 package com.android.keyguard;
 
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+
 import android.content.Context;
 import android.content.res.ColorStateList;
 import android.graphics.Color;
@@ -23,13 +25,13 @@
 import android.graphics.RectF;
 import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
+import android.view.Gravity;
 import android.view.View;
 import android.widget.FrameLayout;
 import android.widget.ImageView;
 
 import androidx.annotation.IntDef;
 import androidx.annotation.NonNull;
-import androidx.annotation.VisibleForTesting;
 
 import com.android.internal.graphics.ColorUtils;
 import com.android.settingslib.Utils;
@@ -68,13 +70,9 @@
     public LockIconView(Context context, AttributeSet attrs) {
         super(context, attrs);
         mSensorRect = new RectF();
-    }
 
-    @Override
-    public void onFinishInflate() {
-        super.onFinishInflate();
-        mLockIcon = findViewById(R.id.lock_icon);
-        mBgView = findViewById(R.id.lock_icon_bg);
+        addBgImageView(context, attrs);
+        addLockIconImageView(context, attrs);
     }
 
     void setDozeAmount(float dozeAmount) {
@@ -127,7 +125,6 @@
     /**
      * Set the location of the lock icon.
      */
-    @VisibleForTesting
     public void setCenterLocation(@NonNull Point center, float radius, int drawablePadding) {
         mLockIconCenter = center;
         mRadius = radius;
@@ -184,6 +181,30 @@
         mLockIcon.setImageState(getLockIconState(mIconType, mAod), true);
     }
 
+    private void addLockIconImageView(Context context, AttributeSet attrs) {
+        mLockIcon = new ImageView(context, attrs);
+        mLockIcon.setId(R.id.lock_icon);
+        mLockIcon.setScaleType(ImageView.ScaleType.CENTER_CROP);
+        addView(mLockIcon);
+        LayoutParams lp = (LayoutParams) mLockIcon.getLayoutParams();
+        lp.height = MATCH_PARENT;
+        lp.width = MATCH_PARENT;
+        lp.gravity = Gravity.CENTER;
+        mLockIcon.setLayoutParams(lp);
+    }
+
+    private void addBgImageView(Context context, AttributeSet attrs) {
+        mBgView = new ImageView(context, attrs);
+        mBgView.setId(R.id.lock_icon_bg);
+        mBgView.setImageDrawable(context.getDrawable(R.drawable.fingerprint_bg));
+        mBgView.setVisibility(View.INVISIBLE);
+        addView(mBgView);
+        LayoutParams lp = (LayoutParams) mBgView.getLayoutParams();
+        lp.height = MATCH_PARENT;
+        lp.width = MATCH_PARENT;
+        mBgView.setLayoutParams(lp);
+    }
+
     private static int[] getLockIconState(@IconType int icon, boolean aod) {
         if (icon == ICON_NONE) {
             return new int[0];
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
index 9003c43..5459718 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
@@ -24,6 +24,7 @@
 import static com.android.keyguard.LockIconView.ICON_UNLOCK;
 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.util.kotlin.JavaAdapterKt.collectFlow;
 
 import android.content.res.Configuration;
@@ -56,13 +57,14 @@
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.biometrics.AuthRippleController;
 import com.android.systemui.biometrics.UdfpsController;
+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.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
-import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor;
 import com.android.systemui.keyguard.shared.model.TransitionStep;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -123,6 +125,7 @@
     private int mActivePointerId = -1;
 
     private boolean mIsDozing;
+    private boolean mIsActiveDreamLockscreenHosted;
     private boolean mIsBouncerShowing;
     private boolean mRunningFPS;
     private boolean mCanDismissLockScreen;
@@ -164,6 +167,13 @@
         updateVisibility();
     };
 
+    @VisibleForTesting
+    final Consumer<Boolean> mIsActiveDreamLockscreenHostedCallback =
+            (Boolean isLockscreenHosted) -> {
+                mIsActiveDreamLockscreenHosted = isLockscreenHosted;
+                updateVisibility();
+            };
+
     @Inject
     public LockIconViewController(
             @Nullable LockIconView view,
@@ -223,6 +233,11 @@
                     mDozeTransitionCallback);
             collectFlow(mView, mKeyguardInteractor.isDozing(), mIsDozingCallback);
         }
+
+        if (mFeatureFlags.isEnabled(LOCKSCREEN_WALLPAPER_DREAM_ENABLED)) {
+            collectFlow(mView, mKeyguardInteractor.isActiveDreamLockscreenHosted(),
+                    mIsActiveDreamLockscreenHostedCallback);
+        }
     }
 
     @Override
@@ -288,6 +303,11 @@
             return;
         }
 
+        if (mIsKeyguardShowing && mIsActiveDreamLockscreenHosted) {
+            mView.setVisibility(View.INVISIBLE);
+            return;
+        }
+
         boolean wasShowingFpIcon = mUdfpsEnrolled && !mShowUnlockIcon && !mShowLockIcon
                 && !mShowAodUnlockedIcon && !mShowAodLockIcon;
         mShowLockIcon = !mCanDismissLockScreen && isLockScreen()
@@ -397,15 +417,17 @@
     private void updateLockIconLocation() {
         final float scaleFactor = mAuthController.getScaleFactor();
         final int scaledPadding = (int) (mDefaultPaddingPx * scaleFactor);
-        if (mUdfpsSupported) {
-            mView.setCenterLocation(mAuthController.getUdfpsLocation(),
-                    mAuthController.getUdfpsRadius(), scaledPadding);
-        } else {
-            mView.setCenterLocation(
-                    new Point((int) mWidthPixels / 2,
-                            (int) (mHeightPixels
-                                    - ((mBottomPaddingPx + sLockIconRadiusPx) * scaleFactor))),
+        if (!mFeatureFlags.isEnabled(Flags.MIGRATE_LOCK_ICON)) {
+            if (mUdfpsSupported) {
+                mView.setCenterLocation(mAuthController.getUdfpsLocation(),
+                        mAuthController.getUdfpsRadius(), scaledPadding);
+            } else {
+                mView.setCenterLocation(
+                        new Point((int) mWidthPixels / 2,
+                                (int) (mHeightPixels
+                                        - ((mBottomPaddingPx + sLockIconRadiusPx) * scaleFactor))),
                         sLockIconRadiusPx * scaleFactor, scaledPadding);
+            }
         }
     }
 
@@ -433,6 +455,7 @@
         pw.println(" mInterpolatedDarkAmount: " + mInterpolatedDarkAmount);
         pw.println(" mSensorTouchLocation: " + mSensorTouchLocation);
         pw.println(" mDefaultPaddingPx: " + mDefaultPaddingPx);
+        pw.println(" mIsActiveDreamLockscreenHosted: " + mIsActiveDreamLockscreenHosted);
 
         if (mView != null) {
             mView.dump(pw, args);
diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java b/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java
index 7d76f12..a04a48d 100644
--- a/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java
+++ b/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java
@@ -16,11 +16,11 @@
 package com.android.keyguard;
 
 import static com.android.settingslib.Utils.getColorAttrDefaultColor;
-import static com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.ColorId.NUM_PAD_BACKGROUND;
-import static com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.ColorId.NUM_PAD_BACKGROUND_PRESSED;
-import static com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.ColorId.NUM_PAD_BUTTON;
-import static com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.ColorId.NUM_PAD_KEY;
-import static com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.ColorId.NUM_PAD_PRESSED;
+import static com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.ColorId.NUM_PAD_BACKGROUND;
+import static com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.ColorId.NUM_PAD_BACKGROUND_PRESSED;
+import static com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.ColorId.NUM_PAD_BUTTON;
+import static com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.ColorId.NUM_PAD_KEY;
+import static com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.ColorId.NUM_PAD_PRESSED;
 import static com.android.systemui.util.ColorUtilKt.getPrivateAttrColorIfUnset;
 
 import android.animation.AnimatorSet;
diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadButton.java b/packages/SystemUI/src/com/android/keyguard/NumPadButton.java
index ebd234f..3f1741a6 100644
--- a/packages/SystemUI/src/com/android/keyguard/NumPadButton.java
+++ b/packages/SystemUI/src/com/android/keyguard/NumPadButton.java
@@ -15,8 +15,8 @@
  */
 package com.android.keyguard;
 
-import static com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.ColorId.NUM_PAD_BUTTON;
-import static com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.ColorId.NUM_PAD_KEY;
+import static com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.ColorId.NUM_PAD_BUTTON;
+import static com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.ColorId.NUM_PAD_KEY;
 
 import android.content.Context;
 import android.content.res.ColorStateList;
diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
index e22fc30..edc298c 100644
--- a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
+++ b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
@@ -15,7 +15,7 @@
  */
 package com.android.keyguard;
 
-import static com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.ColorId.NUM_PAD_KEY;
+import static com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.ColorId.NUM_PAD_KEY;
 
 import android.content.Context;
 import android.content.res.Configuration;
diff --git a/packages/SystemUI/src/com/android/keyguard/PinShapeHintingView.java b/packages/SystemUI/src/com/android/keyguard/PinShapeHintingView.java
index 32f06dc..7c129b4 100644
--- a/packages/SystemUI/src/com/android/keyguard/PinShapeHintingView.java
+++ b/packages/SystemUI/src/com/android/keyguard/PinShapeHintingView.java
@@ -16,7 +16,7 @@
 
 package com.android.keyguard;
 
-import static com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.ColorId.PIN_SHAPES;
+import static com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.ColorId.PIN_SHAPES;
 
 import android.content.Context;
 import android.graphics.drawable.AnimatedVectorDrawable;
diff --git a/packages/SystemUI/src/com/android/keyguard/PinShapeNonHintingView.java b/packages/SystemUI/src/com/android/keyguard/PinShapeNonHintingView.java
index 500910c..56c0953 100644
--- a/packages/SystemUI/src/com/android/keyguard/PinShapeNonHintingView.java
+++ b/packages/SystemUI/src/com/android/keyguard/PinShapeNonHintingView.java
@@ -16,7 +16,7 @@
 
 package com.android.keyguard;
 
-import static com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.ColorId.PIN_SHAPES;
+import static com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.ColorId.PIN_SHAPES;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
diff --git a/packages/SystemUI/src/com/android/keyguard/ViewMediatorCallback.java b/packages/SystemUI/src/com/android/keyguard/ViewMediatorCallback.java
index 50f8f7e..14ec27a 100644
--- a/packages/SystemUI/src/com/android/keyguard/ViewMediatorCallback.java
+++ b/packages/SystemUI/src/com/android/keyguard/ViewMediatorCallback.java
@@ -104,4 +104,9 @@
      * Call when cancel button is pressed in bouncer.
      */
     void onCancelClicked();
+
+    /**
+     * Determines if bouncer has swiped down.
+     */
+    void onBouncerSwipeDown();
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/AnalogClockController.java b/packages/SystemUI/src/com/android/keyguard/clock/AnalogClockController.java
deleted file mode 100644
index 7517dee..0000000
--- a/packages/SystemUI/src/com/android/keyguard/clock/AnalogClockController.java
+++ /dev/null
@@ -1,210 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.clock;
-
-import android.app.WallpaperManager;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.Color;
-import android.graphics.Paint.Style;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.TextClock;
-
-import com.android.internal.colorextraction.ColorExtractor;
-import com.android.systemui.R;
-import com.android.systemui.colorextraction.SysuiColorExtractor;
-import com.android.systemui.plugins.ClockPlugin;
-
-import java.util.TimeZone;
-
-/**
- * Controller for Stretch clock that can appear on lock screen and AOD.
- */
-public class AnalogClockController implements ClockPlugin {
-
-    /**
-     * Resources used to get title and thumbnail.
-     */
-    private final Resources mResources;
-
-    /**
-     * LayoutInflater used to inflate custom clock views.
-     */
-    private final LayoutInflater mLayoutInflater;
-
-    /**
-     * Extracts accent color from wallpaper.
-     */
-    private final SysuiColorExtractor mColorExtractor;
-
-    /**
-     * Computes preferred position of clock.
-     */
-    private final SmallClockPosition mClockPosition;
-
-    /**
-     * Renders preview from clock view.
-     */
-    private final ViewPreviewer mRenderer = new ViewPreviewer();
-
-    /**
-     * Custom clock shown on AOD screen and behind stack scroller on lock.
-     */
-    private ClockLayout mBigClockView;
-    private ImageClock mAnalogClock;
-
-    /**
-     * Small clock shown on lock screen above stack scroller.
-     */
-    private View mView;
-    private TextClock mLockClock;
-
-    /**
-     * Helper to extract colors from wallpaper palette for clock face.
-     */
-    private final ClockPalette mPalette = new ClockPalette();
-
-    /**
-     * Create a BubbleClockController instance.
-     *
-     * @param res Resources contains title and thumbnail.
-     * @param inflater Inflater used to inflate custom clock views.
-     * @param colorExtractor Extracts accent color from wallpaper.
-     */
-    public AnalogClockController(Resources res, LayoutInflater inflater,
-            SysuiColorExtractor colorExtractor) {
-        mResources = res;
-        mLayoutInflater = inflater;
-        mColorExtractor = colorExtractor;
-        mClockPosition = new SmallClockPosition(inflater.getContext());
-    }
-
-    private void createViews() {
-        mBigClockView = (ClockLayout) mLayoutInflater.inflate(R.layout.analog_clock, null);
-        mAnalogClock = mBigClockView.findViewById(R.id.analog_clock);
-
-        mView = mLayoutInflater.inflate(R.layout.digital_clock, null);
-        mLockClock = mView.findViewById(R.id.lock_screen_clock);
-    }
-
-    @Override
-    public void onDestroyView() {
-        mBigClockView = null;
-        mAnalogClock = null;
-        mView = null;
-        mLockClock = null;
-    }
-
-    @Override
-    public String getName() {
-        return "analog";
-    }
-
-    @Override
-    public String getTitle() {
-        return mResources.getString(R.string.clock_title_analog);
-    }
-
-    @Override
-    public Bitmap getThumbnail() {
-        return BitmapFactory.decodeResource(mResources, R.drawable.analog_thumbnail);
-    }
-
-    @Override
-    public Bitmap getPreview(int width, int height) {
-
-        // Use the big clock view for the preview
-        View view = getBigClockView();
-
-        // Initialize state of plugin before generating preview.
-        setDarkAmount(1f);
-        setTextColor(Color.WHITE);
-        ColorExtractor.GradientColors colors = mColorExtractor.getColors(
-                WallpaperManager.FLAG_LOCK);
-        setColorPalette(colors.supportsDarkText(), colors.getColorPalette());
-        onTimeTick();
-
-        return mRenderer.createPreview(view, width, height);
-    }
-
-    @Override
-    public View getView() {
-        if (mView == null) {
-            createViews();
-        }
-        return mView;
-    }
-
-    @Override
-    public View getBigClockView() {
-        if (mBigClockView == null) {
-            createViews();
-        }
-        return mBigClockView;
-    }
-
-    @Override
-    public int getPreferredY(int totalHeight) {
-        return mClockPosition.getPreferredY();
-    }
-
-    @Override
-    public void setStyle(Style style) {}
-
-    @Override
-    public void setTextColor(int color) {
-        updateColor();
-    }
-
-    @Override
-    public void setColorPalette(boolean supportsDarkText, int[] colorPalette) {
-        mPalette.setColorPalette(supportsDarkText, colorPalette);
-        updateColor();
-    }
-
-    private void updateColor() {
-        final int primary = mPalette.getPrimaryColor();
-        final int secondary = mPalette.getSecondaryColor();
-        mLockClock.setTextColor(secondary);
-        mAnalogClock.setClockColors(primary, secondary);
-    }
-
-    @Override
-    public void onTimeTick() {
-        mAnalogClock.onTimeChanged();
-        mBigClockView.onTimeChanged();
-        mLockClock.refreshTime();
-    }
-
-    @Override
-    public void setDarkAmount(float darkAmount) {
-        mPalette.setDarkAmount(darkAmount);
-        mClockPosition.setDarkAmount(darkAmount);
-        mBigClockView.setDarkAmount(darkAmount);
-    }
-
-    @Override
-    public void onTimeZoneChanged(TimeZone timeZone) {
-        mAnalogClock.onTimeZoneChanged(timeZone);
-    }
-
-    @Override
-    public boolean shouldShowStatusArea() {
-        return true;
-    }
-}
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/BubbleClockController.java b/packages/SystemUI/src/com/android/keyguard/clock/BubbleClockController.java
deleted file mode 100644
index 1add1a3..0000000
--- a/packages/SystemUI/src/com/android/keyguard/clock/BubbleClockController.java
+++ /dev/null
@@ -1,210 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.clock;
-
-import android.app.WallpaperManager;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.Color;
-import android.graphics.Paint.Style;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.TextClock;
-
-import com.android.internal.colorextraction.ColorExtractor;
-import com.android.systemui.R;
-import com.android.systemui.colorextraction.SysuiColorExtractor;
-import com.android.systemui.plugins.ClockPlugin;
-
-import java.util.TimeZone;
-
-/**
- * Controller for Bubble clock that can appear on lock screen and AOD.
- */
-public class BubbleClockController implements ClockPlugin {
-
-    /**
-     * Resources used to get title and thumbnail.
-     */
-    private final Resources mResources;
-
-    /**
-     * LayoutInflater used to inflate custom clock views.
-     */
-    private final LayoutInflater mLayoutInflater;
-
-    /**
-     * Extracts accent color from wallpaper.
-     */
-    private final SysuiColorExtractor mColorExtractor;
-
-    /**
-     * Computes preferred position of clock.
-     */
-    private final SmallClockPosition mClockPosition;
-
-    /**
-     * Renders preview from clock view.
-     */
-    private final ViewPreviewer mRenderer = new ViewPreviewer();
-
-    /**
-     * Custom clock shown on AOD screen and behind stack scroller on lock.
-     */
-    private ClockLayout mView;
-    private ImageClock mAnalogClock;
-
-    /**
-     * Small clock shown on lock screen above stack scroller.
-     */
-    private View mLockClockContainer;
-    private TextClock mLockClock;
-
-    /**
-     * Helper to extract colors from wallpaper palette for clock face.
-     */
-    private final ClockPalette mPalette = new ClockPalette();
-
-    /**
-     * Create a BubbleClockController instance.
-     *
-     * @param res Resources contains title and thumbnail.
-     * @param inflater Inflater used to inflate custom clock views.
-     * @param colorExtractor Extracts accent color from wallpaper.
-     */
-    public BubbleClockController(Resources res, LayoutInflater inflater,
-            SysuiColorExtractor colorExtractor) {
-        mResources = res;
-        mLayoutInflater = inflater;
-        mColorExtractor = colorExtractor;
-        mClockPosition = new SmallClockPosition(inflater.getContext());
-    }
-
-    private void createViews() {
-        mView = (ClockLayout) mLayoutInflater.inflate(R.layout.bubble_clock, null);
-        mAnalogClock = (ImageClock) mView.findViewById(R.id.analog_clock);
-
-        mLockClockContainer = mLayoutInflater.inflate(R.layout.digital_clock, null);
-        mLockClock = (TextClock) mLockClockContainer.findViewById(R.id.lock_screen_clock);
-    }
-
-    @Override
-    public void onDestroyView() {
-        mView = null;
-        mAnalogClock = null;
-        mLockClockContainer = null;
-        mLockClock = null;
-    }
-
-    @Override
-    public String getName() {
-        return "bubble";
-    }
-
-    @Override
-    public String getTitle() {
-        return mResources.getString(R.string.clock_title_bubble);
-    }
-
-    @Override
-    public Bitmap getThumbnail() {
-        return BitmapFactory.decodeResource(mResources, R.drawable.bubble_thumbnail);
-    }
-
-    @Override
-    public Bitmap getPreview(int width, int height) {
-
-        // Use the big clock view for the preview
-        View view = getBigClockView();
-
-        // Initialize state of plugin before generating preview.
-        setDarkAmount(1f);
-        setTextColor(Color.WHITE);
-        ColorExtractor.GradientColors colors = mColorExtractor.getColors(
-                WallpaperManager.FLAG_LOCK);
-        setColorPalette(colors.supportsDarkText(), colors.getColorPalette());
-        onTimeTick();
-
-        return mRenderer.createPreview(view, width, height);
-    }
-
-    @Override
-    public View getView() {
-        if (mLockClockContainer == null) {
-            createViews();
-        }
-        return mLockClockContainer;
-    }
-
-    @Override
-    public View getBigClockView() {
-        if (mView == null) {
-            createViews();
-        }
-        return mView;
-    }
-
-    @Override
-    public int getPreferredY(int totalHeight) {
-        return mClockPosition.getPreferredY();
-    }
-
-    @Override
-    public void setStyle(Style style) {}
-
-    @Override
-    public void setTextColor(int color) {
-        updateColor();
-    }
-
-    @Override
-    public void setColorPalette(boolean supportsDarkText, int[] colorPalette) {
-        mPalette.setColorPalette(supportsDarkText, colorPalette);
-        updateColor();
-    }
-
-    private void updateColor() {
-        final int primary = mPalette.getPrimaryColor();
-        final int secondary = mPalette.getSecondaryColor();
-        mLockClock.setTextColor(secondary);
-        mAnalogClock.setClockColors(primary, secondary);
-    }
-
-    @Override
-    public void setDarkAmount(float darkAmount) {
-        mPalette.setDarkAmount(darkAmount);
-        mClockPosition.setDarkAmount(darkAmount);
-        mView.setDarkAmount(darkAmount);
-    }
-
-    @Override
-    public void onTimeTick() {
-        mAnalogClock.onTimeChanged();
-        mView.onTimeChanged();
-        mLockClock.refreshTime();
-    }
-
-    @Override
-    public void onTimeZoneChanged(TimeZone timeZone) {
-        mAnalogClock.onTimeZoneChanged(timeZone);
-    }
-
-    @Override
-    public boolean shouldShowStatusArea() {
-        return true;
-    }
-}
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/ClockInfo.java b/packages/SystemUI/src/com/android/keyguard/clock/ClockInfo.java
deleted file mode 100644
index 0210e08..0000000
--- a/packages/SystemUI/src/com/android/keyguard/clock/ClockInfo.java
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.clock;
-
-import android.graphics.Bitmap;
-
-import java.util.function.Supplier;
-
-/**
- * Metadata about an available clock face.
- */
-final class ClockInfo {
-
-    private final String mName;
-    private final Supplier<String> mTitle;
-    private final String mId;
-    private final Supplier<Bitmap> mThumbnail;
-    private final Supplier<Bitmap> mPreview;
-
-    private ClockInfo(String name, Supplier<String> title, String id,
-            Supplier<Bitmap> thumbnail, Supplier<Bitmap> preview) {
-        mName = name;
-        mTitle = title;
-        mId = id;
-        mThumbnail = thumbnail;
-        mPreview = preview;
-    }
-
-    /**
-     * Gets the non-internationalized name for the clock face.
-     */
-    String getName() {
-        return mName;
-    }
-
-    /**
-     * Gets the name (title) of the clock face to be shown in the picker app.
-     */
-    String getTitle() {
-        return mTitle.get();
-    }
-
-    /**
-     * Gets the ID of the clock face, used by the picker to set the current selection.
-     */
-    String getId() {
-        return mId;
-    }
-
-    /**
-     * Gets a thumbnail image of the clock.
-     */
-    Bitmap getThumbnail() {
-        return mThumbnail.get();
-    }
-
-    /**
-     * Gets a potentially realistic preview image of the clock face.
-     */
-    Bitmap getPreview() {
-        return mPreview.get();
-    }
-
-    static Builder builder() {
-        return new Builder();
-    }
-
-    static class Builder {
-        private String mName;
-        private Supplier<String> mTitle;
-        private String mId;
-        private Supplier<Bitmap> mThumbnail;
-        private Supplier<Bitmap> mPreview;
-
-        public ClockInfo build() {
-            return new ClockInfo(mName, mTitle, mId, mThumbnail, mPreview);
-        }
-
-        public Builder setName(String name) {
-            mName = name;
-            return this;
-        }
-
-        public Builder setTitle(Supplier<String> title) {
-            mTitle = title;
-            return this;
-        }
-
-        public Builder setId(String id) {
-            mId = id;
-            return this;
-        }
-
-        public Builder setThumbnail(Supplier<Bitmap> thumbnail) {
-            mThumbnail = thumbnail;
-            return this;
-        }
-
-        public Builder setPreview(Supplier<Bitmap> preview) {
-            mPreview = preview;
-            return this;
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/ClockInfoModule.java b/packages/SystemUI/src/com/android/keyguard/clock/ClockInfoModule.java
deleted file mode 100644
index 72a44bd..0000000
--- a/packages/SystemUI/src/com/android/keyguard/clock/ClockInfoModule.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.clock;
-
-import java.util.List;
-
-import dagger.Module;
-import dagger.Provides;
-
-/**
- * Dagger Module for clock package.
- *
- * @deprecated Migrate to ClockRegistry
- */
-@Module
-@Deprecated
-public abstract class ClockInfoModule {
-
-    /** */
-    @Provides
-    public static List<ClockInfo> provideClockInfoList(ClockManager clockManager) {
-        return clockManager.getClockInfos();
-    }
-}
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/ClockLayout.java b/packages/SystemUI/src/com/android/keyguard/clock/ClockLayout.java
deleted file mode 100644
index d44d89e..0000000
--- a/packages/SystemUI/src/com/android/keyguard/clock/ClockLayout.java
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.clock;
-
-import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.util.AttributeSet;
-import android.util.MathUtils;
-import android.view.View;
-import android.widget.FrameLayout;
-
-import com.android.systemui.R;
-
-/**
- * Positions clock faces (analog, digital, typographic) and handles pixel shifting
- * to prevent screen burn-in.
- */
-public class ClockLayout extends FrameLayout {
-
-    private static final int ANALOG_CLOCK_SHIFT_FACTOR = 3;
-    /**
-     * Clock face views.
-     */
-    private View mAnalogClock;
-
-    /**
-     * Pixel shifting amplitudes used to prevent screen burn-in.
-     */
-    private int mBurnInPreventionOffsetX;
-    private int mBurnInPreventionOffsetY;
-
-    private float mDarkAmount;
-
-    public ClockLayout(Context context) {
-        this(context, null);
-    }
-
-    public ClockLayout(Context context, AttributeSet attrs) {
-        this(context, attrs, 0);
-    }
-
-    public ClockLayout(Context context, AttributeSet attrs, int defStyleAttr) {
-        super(context, attrs, defStyleAttr);
-    }
-
-    @Override
-    protected void onFinishInflate() {
-        super.onFinishInflate();
-        mAnalogClock = findViewById(R.id.analog_clock);
-
-        // Get pixel shifting X, Y amplitudes from resources.
-        Resources resources = getResources();
-        mBurnInPreventionOffsetX = resources.getDimensionPixelSize(
-            R.dimen.burn_in_prevention_offset_x);
-        mBurnInPreventionOffsetY = resources.getDimensionPixelSize(
-            R.dimen.burn_in_prevention_offset_y);
-    }
-
-    @Override
-    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
-        super.onLayout(changed, left, top, right, bottom);
-        positionChildren();
-    }
-
-    void onTimeChanged() {
-        positionChildren();
-    }
-
-    /**
-     * See {@link com.android.systemui.plugins.ClockPlugin#setDarkAmount(float)}.
-     */
-    void setDarkAmount(float darkAmount) {
-        mDarkAmount = darkAmount;
-        positionChildren();
-    }
-
-    private void positionChildren() {
-        final float offsetX = MathUtils.lerp(0f,
-                getBurnInOffset(mBurnInPreventionOffsetX * 2, true) - mBurnInPreventionOffsetX,
-                mDarkAmount);
-        final float offsetY = MathUtils.lerp(0f,
-                getBurnInOffset(mBurnInPreventionOffsetY * 2, false)
-                        - 0.5f * mBurnInPreventionOffsetY,
-                mDarkAmount);
-
-        // Put the analog clock in the middle of the screen.
-        if (mAnalogClock != null) {
-            mAnalogClock.setX(Math.max(0f, 0.5f * (getWidth() - mAnalogClock.getWidth()))
-                    + ANALOG_CLOCK_SHIFT_FACTOR * offsetX);
-            mAnalogClock.setY(Math.max(0f, 0.5f * (getHeight() - mAnalogClock.getHeight()))
-                    + ANALOG_CLOCK_SHIFT_FACTOR * offsetY);
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java b/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java
deleted file mode 100644
index 122c521..0000000
--- a/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java
+++ /dev/null
@@ -1,392 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.clock;
-
-import android.annotation.Nullable;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.res.Resources;
-import android.database.ContentObserver;
-import android.net.Uri;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.UserHandle;
-import android.provider.Settings;
-import android.util.ArrayMap;
-import android.util.DisplayMetrics;
-import android.view.LayoutInflater;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.VisibleForTesting;
-
-import com.android.systemui.colorextraction.SysuiColorExtractor;
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.dock.DockManager;
-import com.android.systemui.dock.DockManager.DockEventListener;
-import com.android.systemui.plugins.ClockPlugin;
-import com.android.systemui.plugins.PluginListener;
-import com.android.systemui.plugins.PluginManager;
-import com.android.systemui.settings.UserTracker;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.concurrent.Executor;
-import java.util.function.Supplier;
-
-import javax.inject.Inject;
-
-/**
- * Manages custom clock faces for AOD and lock screen.
- *
- * @deprecated Migrate to ClockRegistry
- */
-@SysUISingleton
-@Deprecated
-public final class ClockManager {
-
-    private static final String TAG = "ClockOptsProvider";
-
-    private final AvailableClocks mPreviewClocks;
-    private final List<Supplier<ClockPlugin>> mBuiltinClocks = new ArrayList<>();
-
-    private final Context mContext;
-    private final ContentResolver mContentResolver;
-    private final SettingsWrapper mSettingsWrapper;
-    private final Handler mMainHandler = new Handler(Looper.getMainLooper());
-    private final UserTracker mUserTracker;
-    private final Executor mMainExecutor;
-
-    /**
-     * Observe settings changes to know when to switch the clock face.
-     */
-    private final ContentObserver mContentObserver =
-            new ContentObserver(mMainHandler) {
-                @Override
-                public void onChange(boolean selfChange, Collection<Uri> uris,
-                        int flags, int userId) {
-                    if (Objects.equals(userId,
-                            mUserTracker.getUserId())) {
-                        reload();
-                    }
-                }
-            };
-
-    /**
-     * Observe user changes and react by potentially loading the custom clock for the new user.
-     */
-    private final UserTracker.Callback mUserChangedCallback =
-            new UserTracker.Callback() {
-                @Override
-                public void onUserChanged(int newUser, @NonNull Context userContext) {
-                    reload();
-                }
-            };
-
-    private final PluginManager mPluginManager;
-    @Nullable private final DockManager mDockManager;
-
-    /**
-     * Observe changes to dock state to know when to switch the clock face.
-     */
-    private final DockEventListener mDockEventListener =
-            new DockEventListener() {
-                @Override
-                public void onEvent(int event) {
-                    mIsDocked = (event == DockManager.STATE_DOCKED
-                            || event == DockManager.STATE_DOCKED_HIDE);
-                    reload();
-                }
-            };
-
-    /**
-     * When docked, the DOCKED_CLOCK_FACE setting will be checked for the custom clock face
-     * to show.
-     */
-    private boolean mIsDocked;
-
-    /**
-     * Listeners for onClockChanged event.
-     *
-     * Each listener must receive a separate clock plugin instance. Otherwise, there could be
-     * problems like attempting to attach a view that already has a parent. To deal with this issue,
-     * each listener is associated with a collection of available clocks. When onClockChanged is
-     * fired the current clock plugin instance is retrieved from that listeners available clocks.
-     */
-    private final Map<ClockChangedListener, AvailableClocks> mListeners = new ArrayMap<>();
-
-    private final int mWidth;
-    private final int mHeight;
-
-    @Inject
-    public ClockManager(Context context, LayoutInflater layoutInflater,
-            PluginManager pluginManager, SysuiColorExtractor colorExtractor,
-            @Nullable DockManager dockManager, UserTracker userTracker,
-            @Main Executor mainExecutor) {
-        this(context, layoutInflater, pluginManager, colorExtractor,
-                context.getContentResolver(), userTracker, mainExecutor,
-                new SettingsWrapper(context.getContentResolver()), dockManager);
-    }
-
-    @VisibleForTesting
-    ClockManager(Context context, LayoutInflater layoutInflater,
-            PluginManager pluginManager, SysuiColorExtractor colorExtractor,
-            ContentResolver contentResolver, UserTracker userTracker, Executor mainExecutor,
-            SettingsWrapper settingsWrapper, DockManager dockManager) {
-        mContext = context;
-        mPluginManager = pluginManager;
-        mContentResolver = contentResolver;
-        mSettingsWrapper = settingsWrapper;
-        mUserTracker = userTracker;
-        mMainExecutor = mainExecutor;
-        mDockManager = dockManager;
-        mPreviewClocks = new AvailableClocks();
-
-        Resources res = context.getResources();
-
-        addBuiltinClock(() -> new DefaultClockController(res, layoutInflater, colorExtractor));
-
-        // Store the size of the display for generation of clock preview.
-        DisplayMetrics dm = res.getDisplayMetrics();
-        mWidth = dm.widthPixels;
-        mHeight = dm.heightPixels;
-    }
-
-    /**
-     * Add listener to be notified when clock implementation should change.
-     */
-    public void addOnClockChangedListener(ClockChangedListener listener) {
-        if (mListeners.isEmpty()) {
-            register();
-        }
-        AvailableClocks availableClocks = new AvailableClocks();
-        for (int i = 0; i < mBuiltinClocks.size(); i++) {
-            availableClocks.addClockPlugin(mBuiltinClocks.get(i).get());
-        }
-        mListeners.put(listener, availableClocks);
-        mPluginManager.addPluginListener(availableClocks, ClockPlugin.class, true);
-        reload();
-    }
-
-    /**
-     * Remove listener added with {@link addOnClockChangedListener}.
-     */
-    public void removeOnClockChangedListener(ClockChangedListener listener) {
-        AvailableClocks availableClocks = mListeners.remove(listener);
-        mPluginManager.removePluginListener(availableClocks);
-        if (mListeners.isEmpty()) {
-            unregister();
-        }
-    }
-
-    /**
-     * Get information about available clock faces.
-     */
-    List<ClockInfo> getClockInfos() {
-        return mPreviewClocks.getInfo();
-    }
-
-    /**
-     * Get the current clock.
-     * @return current custom clock or null for default.
-     */
-    @Nullable
-    ClockPlugin getCurrentClock() {
-        return mPreviewClocks.getCurrentClock();
-    }
-
-    @VisibleForTesting
-    boolean isDocked() {
-        return mIsDocked;
-    }
-
-    @VisibleForTesting
-    ContentObserver getContentObserver() {
-        return mContentObserver;
-    }
-
-    @VisibleForTesting
-    void addBuiltinClock(Supplier<ClockPlugin> pluginSupplier) {
-        ClockPlugin plugin = pluginSupplier.get();
-        mPreviewClocks.addClockPlugin(plugin);
-        mBuiltinClocks.add(pluginSupplier);
-    }
-
-    private void register() {
-        mPluginManager.addPluginListener(mPreviewClocks, ClockPlugin.class, true);
-        mContentResolver.registerContentObserver(
-                Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE),
-                false, mContentObserver, UserHandle.USER_ALL);
-        mContentResolver.registerContentObserver(
-                Settings.Secure.getUriFor(Settings.Secure.DOCKED_CLOCK_FACE),
-                false, mContentObserver, UserHandle.USER_ALL);
-        mUserTracker.addCallback(mUserChangedCallback, mMainExecutor);
-        if (mDockManager != null) {
-            mDockManager.addListener(mDockEventListener);
-        }
-    }
-
-    private void unregister() {
-        mPluginManager.removePluginListener(mPreviewClocks);
-        mContentResolver.unregisterContentObserver(mContentObserver);
-        mUserTracker.removeCallback(mUserChangedCallback);
-        if (mDockManager != null) {
-            mDockManager.removeListener(mDockEventListener);
-        }
-    }
-
-    private void reload() {
-        mPreviewClocks.reloadCurrentClock();
-        mListeners.forEach((listener, clocks) -> {
-            clocks.reloadCurrentClock();
-            final ClockPlugin clock = clocks.getCurrentClock();
-            if (Looper.myLooper() == Looper.getMainLooper()) {
-                listener.onClockChanged(clock instanceof DefaultClockController ? null : clock);
-            } else {
-                mMainHandler.post(() -> listener.onClockChanged(
-                        clock instanceof DefaultClockController ? null : clock));
-            }
-        });
-    }
-
-    /**
-     * Listener for events that should cause the custom clock face to change.
-     */
-    public interface ClockChangedListener {
-        /**
-         * Called when custom clock should change.
-         *
-         * @param clock Custom clock face to use. A null value indicates the default clock face.
-         */
-        void onClockChanged(ClockPlugin clock);
-    }
-
-    /**
-     * Collection of available clocks.
-     */
-    private final class AvailableClocks implements PluginListener<ClockPlugin> {
-
-        /**
-         * Map from expected value stored in settings to plugin for custom clock face.
-         */
-        private final Map<String, ClockPlugin> mClocks = new ArrayMap<>();
-
-        /**
-         * Metadata about available clocks, such as name and preview images.
-         */
-        private final List<ClockInfo> mClockInfo = new ArrayList<>();
-
-        /**
-         * Active ClockPlugin.
-         */
-        @Nullable private ClockPlugin mCurrentClock;
-
-        @Override
-        public void onPluginConnected(ClockPlugin plugin, Context pluginContext) {
-            addClockPlugin(plugin);
-            reloadIfNeeded(plugin);
-        }
-
-        @Override
-        public void onPluginDisconnected(ClockPlugin plugin) {
-            removeClockPlugin(plugin);
-            reloadIfNeeded(plugin);
-        }
-
-        /**
-         * Get the current clock.
-         * @return current custom clock or null for default.
-         */
-        @Nullable
-        ClockPlugin getCurrentClock() {
-            return mCurrentClock;
-        }
-
-        /**
-         * Get information about available clock faces.
-         */
-        List<ClockInfo> getInfo() {
-            return mClockInfo;
-        }
-
-        /**
-         * Adds a clock plugin to the collection of available clocks.
-         *
-         * @param plugin The plugin to add.
-         */
-        void addClockPlugin(ClockPlugin plugin) {
-            final String id = plugin.getClass().getName();
-            mClocks.put(plugin.getClass().getName(), plugin);
-            mClockInfo.add(ClockInfo.builder()
-                    .setName(plugin.getName())
-                    .setTitle(plugin::getTitle)
-                    .setId(id)
-                    .setThumbnail(plugin::getThumbnail)
-                    .setPreview(() -> plugin.getPreview(mWidth, mHeight))
-                    .build());
-        }
-
-        private void removeClockPlugin(ClockPlugin plugin) {
-            final String id = plugin.getClass().getName();
-            mClocks.remove(id);
-            for (int i = 0; i < mClockInfo.size(); i++) {
-                if (id.equals(mClockInfo.get(i).getId())) {
-                    mClockInfo.remove(i);
-                    break;
-                }
-            }
-        }
-
-        private void reloadIfNeeded(ClockPlugin plugin) {
-            final boolean wasCurrentClock = plugin == mCurrentClock;
-            reloadCurrentClock();
-            final boolean isCurrentClock = plugin == mCurrentClock;
-            if (wasCurrentClock || isCurrentClock) {
-                ClockManager.this.reload();
-            }
-        }
-
-        /**
-         * Update the current clock.
-         */
-        void reloadCurrentClock() {
-            mCurrentClock = getClockPlugin();
-        }
-
-        private ClockPlugin getClockPlugin() {
-            ClockPlugin plugin = null;
-            if (ClockManager.this.isDocked()) {
-                final String name = mSettingsWrapper.getDockedClockFace(
-                        mUserTracker.getUserId());
-                if (name != null) {
-                    plugin = mClocks.get(name);
-                    if (plugin != null) {
-                        return plugin;
-                    }
-                }
-            }
-            final String name = mSettingsWrapper.getLockScreenCustomClockFace(
-                    mUserTracker.getUserId());
-            if (name != null) {
-                plugin = mClocks.get(name);
-            }
-            return plugin;
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/ClockOptionsProvider.java b/packages/SystemUI/src/com/android/keyguard/clock/ClockOptionsProvider.java
deleted file mode 100644
index b6413cb..0000000
--- a/packages/SystemUI/src/com/android/keyguard/clock/ClockOptionsProvider.java
+++ /dev/null
@@ -1,183 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.clock;
-
-import android.content.ContentProvider;
-import android.content.ContentValues;
-import android.database.Cursor;
-import android.database.MatrixCursor;
-import android.graphics.Bitmap;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.ParcelFileDescriptor;
-import android.os.ParcelFileDescriptor.AutoCloseOutputStream;
-import android.text.TextUtils;
-import android.util.Log;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-import java.io.FileNotFoundException;
-import java.util.List;
-
-import javax.inject.Inject;
-import javax.inject.Provider;
-
-/**
- * Exposes custom clock face options and provides realistic preview images.
- *
- * APIs:
- *
- *   /list_options: List the available clock faces, which has the following columns
- *     name: name of the clock face
- *     title: title of the clock face
- *     id: value used to set the clock face
- *     thumbnail: uri of the thumbnail image, should be /thumbnail/{name}
- *     preview: uri of the preview image, should be /preview/{name}
- *
- *   /thumbnail/{id}: Opens a file stream for the thumbnail image for clock face {id}.
- *
- *   /preview/{id}: Opens a file stream for the preview image for clock face {id}.
- */
-public final class ClockOptionsProvider extends ContentProvider {
-
-    private static final String TAG = "ClockOptionsProvider";
-    private static final String KEY_LIST_OPTIONS = "/list_options";
-    private static final String KEY_PREVIEW = "preview";
-    private static final String KEY_THUMBNAIL = "thumbnail";
-    private static final String COLUMN_NAME = "name";
-    private static final String COLUMN_TITLE = "title";
-    private static final String COLUMN_ID = "id";
-    private static final String COLUMN_THUMBNAIL = "thumbnail";
-    private static final String COLUMN_PREVIEW = "preview";
-    private static final String MIME_TYPE_PNG = "image/png";
-    private static final String CONTENT_SCHEME = "content";
-    private static final String AUTHORITY = "com.android.keyguard.clock";
-
-    @Inject
-    public Provider<List<ClockInfo>> mClockInfosProvider;
-
-    @VisibleForTesting
-    ClockOptionsProvider(Provider<List<ClockInfo>> clockInfosProvider) {
-        mClockInfosProvider = clockInfosProvider;
-    }
-
-    @Override
-    public boolean onCreate() {
-        return true;
-    }
-
-    @Override
-    public String getType(Uri uri) {
-        List<String> segments = uri.getPathSegments();
-        if (segments.size() > 0 && (KEY_PREVIEW.equals(segments.get(0))
-                || KEY_THUMBNAIL.equals(segments.get(0)))) {
-            return MIME_TYPE_PNG;
-        }
-        return "vnd.android.cursor.dir/clock_faces";
-    }
-
-    @Override
-    public Cursor query(Uri uri, String[] projection, String selection,
-            String[] selectionArgs, String sortOrder) {
-        if (!KEY_LIST_OPTIONS.equals(uri.getPath())) {
-            return null;
-        }
-        MatrixCursor cursor = new MatrixCursor(new String[] {
-                COLUMN_NAME, COLUMN_TITLE, COLUMN_ID, COLUMN_THUMBNAIL, COLUMN_PREVIEW});
-        List<ClockInfo> clocks = mClockInfosProvider.get();
-        for (int i = 0; i < clocks.size(); i++) {
-            ClockInfo clock = clocks.get(i);
-            cursor.newRow()
-                    .add(COLUMN_NAME, clock.getName())
-                    .add(COLUMN_TITLE, clock.getTitle())
-                    .add(COLUMN_ID, clock.getId())
-                    .add(COLUMN_THUMBNAIL, createThumbnailUri(clock))
-                    .add(COLUMN_PREVIEW, createPreviewUri(clock));
-        }
-        return cursor;
-    }
-
-    @Override
-    public Uri insert(Uri uri, ContentValues initialValues) {
-        return null;
-    }
-
-    @Override
-    public int delete(Uri uri, String selection, String[] selectionArgs) {
-        return 0;
-    }
-
-    @Override
-    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
-        return 0;
-    }
-
-    @Override
-    public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
-        List<String> segments = uri.getPathSegments();
-        if (segments.size() != 2 || !(KEY_PREVIEW.equals(segments.get(0))
-                || KEY_THUMBNAIL.equals(segments.get(0)))) {
-            throw new FileNotFoundException("Invalid preview url");
-        }
-        String id = segments.get(1);
-        if (TextUtils.isEmpty(id)) {
-            throw new FileNotFoundException("Invalid preview url, missing id");
-        }
-        ClockInfo clock = null;
-        List<ClockInfo> clocks = mClockInfosProvider.get();
-        for (int i = 0; i < clocks.size(); i++) {
-            if (id.equals(clocks.get(i).getId())) {
-                clock = clocks.get(i);
-                break;
-            }
-        }
-        if (clock == null) {
-            throw new FileNotFoundException("Invalid preview url, id not found");
-        }
-        return openPipeHelper(uri, MIME_TYPE_PNG, null, KEY_PREVIEW.equals(segments.get(0))
-                ? clock.getPreview() : clock.getThumbnail(), new MyWriter());
-    }
-
-    private Uri createThumbnailUri(ClockInfo clock) {
-        return new Uri.Builder()
-                .scheme(CONTENT_SCHEME)
-                .authority(AUTHORITY)
-                .appendPath(KEY_THUMBNAIL)
-                .appendPath(clock.getId())
-                .build();
-    }
-
-    private Uri createPreviewUri(ClockInfo clock) {
-        return new Uri.Builder()
-                .scheme(CONTENT_SCHEME)
-                .authority(AUTHORITY)
-                .appendPath(KEY_PREVIEW)
-                .appendPath(clock.getId())
-                .build();
-    }
-
-    private static class MyWriter implements ContentProvider.PipeDataWriter<Bitmap> {
-        @Override
-        public void writeDataToPipe(ParcelFileDescriptor output, Uri uri, String mimeType,
-                Bundle opts, Bitmap bitmap) {
-            try (AutoCloseOutputStream os = new AutoCloseOutputStream(output)) {
-                bitmap.compress(Bitmap.CompressFormat.PNG, 100, os);
-            } catch (Exception e) {
-                Log.w(TAG, "fail to write to pipe", e);
-            }
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/ClockPalette.kt b/packages/SystemUI/src/com/android/keyguard/clock/ClockPalette.kt
deleted file mode 100644
index 5c5493a..0000000
--- a/packages/SystemUI/src/com/android/keyguard/clock/ClockPalette.kt
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.clock
-
-import android.graphics.Color
-import android.util.MathUtils
-
-private const val PRIMARY_INDEX = 5
-private const val SECONDARY_DARK_INDEX = 8
-private const val SECONDARY_LIGHT_INDEX = 2
-
-/**
- * A helper class to extract colors from a clock face.
- */
-class ClockPalette {
-
-    private var darkAmount: Float = 0f
-    private var accentPrimary: Int = Color.WHITE
-    private var accentSecondaryLight: Int = Color.WHITE
-    private var accentSecondaryDark: Int = Color.BLACK
-    private val lightHSV: FloatArray = FloatArray(3)
-    private val darkHSV: FloatArray = FloatArray(3)
-    private val hsv: FloatArray = FloatArray(3)
-
-    /** Returns a color from the palette as an RGB packed int. */
-    fun getPrimaryColor(): Int {
-        return accentPrimary
-    }
-
-    /** Returns either a light or dark color from the palette as an RGB packed int. */
-    fun getSecondaryColor(): Int {
-        Color.colorToHSV(accentSecondaryLight, lightHSV)
-        Color.colorToHSV(accentSecondaryDark, darkHSV)
-        for (i in 0..2) {
-            hsv[i] = MathUtils.lerp(darkHSV[i], lightHSV[i], darkAmount)
-        }
-        return Color.HSVToColor(hsv)
-    }
-
-    /** See {@link ClockPlugin#setColorPalette}. */
-    fun setColorPalette(supportsDarkText: Boolean, colorPalette: IntArray?) {
-        if (colorPalette == null || colorPalette.isEmpty()) {
-            accentPrimary = Color.WHITE
-            accentSecondaryLight = Color.WHITE
-            accentSecondaryDark = if (supportsDarkText) Color.BLACK else Color.WHITE
-            return
-        }
-        val length = colorPalette.size
-        accentPrimary = colorPalette[Math.max(0, length - PRIMARY_INDEX)]
-        accentSecondaryLight = colorPalette[Math.max(0, length - SECONDARY_LIGHT_INDEX)]
-        accentSecondaryDark = colorPalette[Math.max(0,
-                length - if (supportsDarkText) SECONDARY_DARK_INDEX else SECONDARY_LIGHT_INDEX)]
-    }
-
-    /** See {@link ClockPlugin#setDarkAmount}. */
-    fun setDarkAmount(darkAmount: Float) {
-        this.darkAmount = darkAmount
-    }
-}
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/CrossFadeDarkController.java b/packages/SystemUI/src/com/android/keyguard/clock/CrossFadeDarkController.java
deleted file mode 100644
index 3c3f475..0000000
--- a/packages/SystemUI/src/com/android/keyguard/clock/CrossFadeDarkController.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.clock;
-
-import android.view.View;
-
-/**
- * Controls transition to dark state by cross fading between views.
- */
-final class CrossFadeDarkController {
-
-    private final View mFadeInView;
-    private final View mFadeOutView;
-
-    /**
-     * Creates a new controller that fades between views.
-     *
-     * @param fadeInView View to fade in when transitioning to AOD.
-     * @param fadeOutView View to fade out when transitioning to AOD.
-     */
-    CrossFadeDarkController(View fadeInView, View fadeOutView) {
-        mFadeInView = fadeInView;
-        mFadeOutView = fadeOutView;
-    }
-
-    /**
-     * Sets the amount the system has transitioned to the dark state.
-     *
-     * @param darkAmount Amount of transition to dark state: 1f for AOD and 0f for lock screen.
-     */
-    void setDarkAmount(float darkAmount) {
-        mFadeInView.setAlpha(Math.max(0f, 2f * darkAmount - 1f));
-        if (darkAmount == 0f) {
-            mFadeInView.setVisibility(View.GONE);
-        } else {
-            if (mFadeInView.getVisibility() == View.GONE) {
-                mFadeInView.setVisibility(View.VISIBLE);
-            }
-        }
-        mFadeOutView.setAlpha(Math.max(0f, 1f - 2f * darkAmount));
-        if (darkAmount == 1f) {
-            mFadeOutView.setVisibility(View.GONE);
-        } else {
-            if (mFadeOutView.getVisibility() == View.GONE) {
-                mFadeOutView.setVisibility(View.VISIBLE);
-            }
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/DefaultClockController.java b/packages/SystemUI/src/com/android/keyguard/clock/DefaultClockController.java
deleted file mode 100644
index c81935a..0000000
--- a/packages/SystemUI/src/com/android/keyguard/clock/DefaultClockController.java
+++ /dev/null
@@ -1,178 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.clock;
-
-import android.app.WallpaperManager;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.Color;
-import android.graphics.Paint.Style;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.TextView;
-
-import com.android.internal.colorextraction.ColorExtractor;
-import com.android.systemui.R;
-import com.android.systemui.colorextraction.SysuiColorExtractor;
-import com.android.systemui.plugins.ClockPlugin;
-
-import java.util.TimeZone;
-
-/**
- * Plugin for the default clock face used only to provide a preview.
- */
-public class DefaultClockController implements ClockPlugin {
-
-    /**
-     * Resources used to get title and thumbnail.
-     */
-    private final Resources mResources;
-
-    /**
-     * LayoutInflater used to inflate custom clock views.
-     */
-    private final LayoutInflater mLayoutInflater;
-
-    /**
-     * Extracts accent color from wallpaper.
-     */
-    private final SysuiColorExtractor mColorExtractor;
-
-    /**
-     * Renders preview from clock view.
-     */
-    private final ViewPreviewer mRenderer = new ViewPreviewer();
-
-    /**
-     * Root view of preview.
-     */
-    private View mView;
-
-    /**
-     * Text clock in preview view hierarchy.
-     */
-    private TextView mTextTime;
-
-    /**
-     * Date showing below time in preview view hierarchy.
-     */
-    private TextView mTextDate;
-
-    /**
-     * Create a DefaultClockController instance.
-     *
-     * @param res Resources contains title and thumbnail.
-     * @param inflater Inflater used to inflate custom clock views.
-     * @param colorExtractor Extracts accent color from wallpaper.
-     */
-    public DefaultClockController(Resources res, LayoutInflater inflater,
-            SysuiColorExtractor colorExtractor) {
-        mResources = res;
-        mLayoutInflater = inflater;
-        mColorExtractor = colorExtractor;
-    }
-
-    private void createViews() {
-        mView = mLayoutInflater.inflate(R.layout.default_clock_preview, null);
-        mTextTime = mView.findViewById(R.id.time);
-        mTextDate = mView.findViewById(R.id.date);
-    }
-
-    @Override
-    public void onDestroyView() {
-        mView = null;
-        mTextTime = null;
-        mTextDate = null;
-    }
-
-    @Override
-    public String getName() {
-        return "default";
-    }
-
-    @Override
-    public String getTitle() {
-        return mResources.getString(R.string.clock_title_default);
-    }
-
-    @Override
-    public Bitmap getThumbnail() {
-        return BitmapFactory.decodeResource(mResources, R.drawable.default_thumbnail);
-    }
-
-    @Override
-    public Bitmap getPreview(int width, int height) {
-
-        // Use the big clock view for the preview
-        View view = getBigClockView();
-
-        // Initialize state of plugin before generating preview.
-        setDarkAmount(1f);
-        setTextColor(Color.WHITE);
-        ColorExtractor.GradientColors colors = mColorExtractor.getColors(
-                WallpaperManager.FLAG_LOCK);
-        setColorPalette(colors.supportsDarkText(), colors.getColorPalette());
-        onTimeTick();
-
-        return mRenderer.createPreview(view, width, height);
-    }
-
-    @Override
-    public View getView() {
-        return null;
-    }
-
-    @Override
-    public View getBigClockView() {
-        if (mView == null) {
-            createViews();
-        }
-        return mView;
-    }
-
-    @Override
-    public int getPreferredY(int totalHeight) {
-        return totalHeight / 2;
-    }
-
-    @Override
-    public void setStyle(Style style) {}
-
-    @Override
-    public void setTextColor(int color) {
-        mTextTime.setTextColor(color);
-        mTextDate.setTextColor(color);
-    }
-
-    @Override
-    public void setColorPalette(boolean supportsDarkText, int[] colorPalette) {}
-
-    @Override
-    public void onTimeTick() {
-    }
-
-    @Override
-    public void setDarkAmount(float darkAmount) {}
-
-    @Override
-    public void onTimeZoneChanged(TimeZone timeZone) {}
-
-    @Override
-    public boolean shouldShowStatusArea() {
-        return true;
-    }
-}
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/ImageClock.java b/packages/SystemUI/src/com/android/keyguard/clock/ImageClock.java
deleted file mode 100644
index 34c041b..0000000
--- a/packages/SystemUI/src/com/android/keyguard/clock/ImageClock.java
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.clock;
-
-import android.content.Context;
-import android.text.format.DateFormat;
-import android.util.AttributeSet;
-import android.widget.FrameLayout;
-import android.widget.ImageView;
-
-import com.android.systemui.R;
-
-import java.text.SimpleDateFormat;
-import java.util.Calendar;
-import java.util.TimeZone;
-
-/**
- * Clock composed of two images that rotate with the time.
- *
- * The images are the clock hands. ImageClock expects two child ImageViews
- * with ids hour_hand and minute_hand.
- */
-public class ImageClock extends FrameLayout {
-
-    private ImageView mHourHand;
-    private ImageView mMinuteHand;
-    private final Calendar mTime = Calendar.getInstance(TimeZone.getDefault());
-    private String mDescFormat;
-    private TimeZone mTimeZone;
-
-    public ImageClock(Context context) {
-        this(context, null);
-    }
-
-    public ImageClock(Context context, AttributeSet attrs) {
-        this(context, attrs, 0);
-    }
-
-    public ImageClock(Context context, AttributeSet attrs, int defStyleAttr) {
-        super(context, attrs, defStyleAttr);
-        mDescFormat = ((SimpleDateFormat) DateFormat.getTimeFormat(context)).toLocalizedPattern();
-    }
-
-    /**
-     * Call when the time changes to update the rotation of the clock hands.
-     */
-    public void onTimeChanged() {
-        mTime.setTimeInMillis(System.currentTimeMillis());
-        final float hourAngle = mTime.get(Calendar.HOUR) * 30f + mTime.get(Calendar.MINUTE) * 0.5f;
-        mHourHand.setRotation(hourAngle);
-        final float minuteAngle = mTime.get(Calendar.MINUTE) * 6f;
-        mMinuteHand.setRotation(minuteAngle);
-        setContentDescription(DateFormat.format(mDescFormat, mTime));
-        invalidate();
-    }
-
-    /**
-     * Call when the time zone has changed to update clock hands.
-     *
-     * @param timeZone The updated time zone that will be used.
-     */
-    public void onTimeZoneChanged(TimeZone timeZone) {
-        mTimeZone = timeZone;
-        mTime.setTimeZone(timeZone);
-    }
-
-    /**
-     * Sets the colors to use on the clock face.
-     * @param dark Darker color obtained from color palette.
-     * @param light Lighter color obtained from color palette.
-     */
-    public void setClockColors(int dark, int light) {
-        mHourHand.setColorFilter(dark);
-        mMinuteHand.setColorFilter(light);
-    }
-
-    @Override
-    protected void onFinishInflate() {
-        super.onFinishInflate();
-        mHourHand = findViewById(R.id.hour_hand);
-        mMinuteHand = findViewById(R.id.minute_hand);
-    }
-
-    @Override
-    protected void onAttachedToWindow() {
-        super.onAttachedToWindow();
-        mTime.setTimeZone(mTimeZone != null ? mTimeZone : TimeZone.getDefault());
-        onTimeChanged();
-    }
-}
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/SettingsWrapper.java b/packages/SystemUI/src/com/android/keyguard/clock/SettingsWrapper.java
deleted file mode 100644
index 096e943..0000000
--- a/packages/SystemUI/src/com/android/keyguard/clock/SettingsWrapper.java
+++ /dev/null
@@ -1,137 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.clock;
-
-import android.annotation.Nullable;
-import android.content.ContentResolver;
-import android.provider.Settings;
-import android.util.Log;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-import org.json.JSONException;
-import org.json.JSONObject;
-
-/**
- * Wrapper around Settings used for testing.
- */
-public class SettingsWrapper {
-
-    private static final String TAG = "ClockFaceSettings";
-    private static final String CUSTOM_CLOCK_FACE = Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE;
-    private static final String DOCKED_CLOCK_FACE = Settings.Secure.DOCKED_CLOCK_FACE;
-    private static final String CLOCK_FIELD = "clock";
-
-    private final ContentResolver mContentResolver;
-    private final Migration mMigration;
-
-    SettingsWrapper(ContentResolver contentResolver) {
-        this(contentResolver, new Migrator(contentResolver));
-    }
-
-    @VisibleForTesting
-    SettingsWrapper(ContentResolver contentResolver, Migration migration) {
-        mContentResolver = contentResolver;
-        mMigration = migration;
-    }
-
-    /**
-     * Gets the value stored in settings for the custom clock face.
-     *
-     * @param userId ID of the user.
-     */
-    String getLockScreenCustomClockFace(int userId) {
-        return decode(
-                Settings.Secure.getStringForUser(mContentResolver, CUSTOM_CLOCK_FACE, userId),
-                userId);
-    }
-
-    /**
-     * Gets the value stored in settings for the clock face to use when docked.
-     *
-     * @param userId ID of the user.
-     */
-    String getDockedClockFace(int userId) {
-        return Settings.Secure.getStringForUser(mContentResolver, DOCKED_CLOCK_FACE, userId);
-    }
-
-    /**
-     * Decodes the string stored in settings, which should be formatted as JSON.
-     * @param value String stored in settings. If value is not JSON, then the settings is
-     *              overwritten with JSON containing the prior value.
-     * @return ID of the clock face to show on AOD and lock screen. If value is not JSON, the value
-     *         is returned.
-     */
-    @VisibleForTesting
-    String decode(@Nullable String value, int userId) {
-        if (value == null) {
-            return value;
-        }
-        JSONObject json;
-        try {
-            json = new JSONObject(value);
-        } catch (JSONException ex) {
-            Log.e(TAG, "Settings value is not valid JSON", ex);
-            // The settings value isn't JSON since it didn't parse so migrate the value to JSON.
-            // TODO(b/135674383): Remove this migration path in the following release.
-            mMigration.migrate(value, userId);
-            return value;
-        }
-        try {
-            return json.getString(CLOCK_FIELD);
-        } catch (JSONException ex) {
-            Log.e(TAG, "JSON object does not contain clock field.", ex);
-            return null;
-        }
-    }
-
-    interface Migration {
-        void migrate(String value, int userId);
-    }
-
-    /**
-     * Implementation of {@link Migration} that writes valid JSON back to Settings.
-     */
-    private static final class Migrator implements Migration {
-
-        private final ContentResolver mContentResolver;
-
-        Migrator(ContentResolver contentResolver) {
-            mContentResolver = contentResolver;
-        }
-
-        /**
-         * Migrate settings values that don't parse by converting to JSON format.
-         *
-         * Values in settings must be JSON to be backed up and restored. To help users maintain
-         * their current settings, convert existing values into the JSON format.
-         *
-         * TODO(b/135674383): Remove this migration code in the following release.
-         */
-        @Override
-        public void migrate(String value, int userId) {
-            try {
-                JSONObject json = new JSONObject();
-                json.put(CLOCK_FIELD, value);
-                Settings.Secure.putStringForUser(mContentResolver, CUSTOM_CLOCK_FACE,
-                        json.toString(),
-                        userId);
-            } catch (JSONException ex) {
-                Log.e(TAG, "Failed migrating settings value to JSON format", ex);
-            }
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/SmallClockPosition.java b/packages/SystemUI/src/com/android/keyguard/clock/SmallClockPosition.java
deleted file mode 100644
index 4e51b98..0000000
--- a/packages/SystemUI/src/com/android/keyguard/clock/SmallClockPosition.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.clock;
-
-import android.content.Context;
-import android.util.MathUtils;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.policy.SystemBarUtils;
-import com.android.systemui.R;
-
-/**
- * Computes preferred position of clock by considering height of status bar and lock icon.
- */
-class SmallClockPosition {
-
-    /**
-     * Dimensions used to determine preferred clock position.
-     */
-    private final int mStatusBarHeight;
-    private final int mKeyguardLockPadding;
-    private final int mKeyguardLockHeight;
-    private final int mBurnInOffsetY;
-
-    /**
-     * Amount of transition between AOD and lock screen.
-     */
-    private float mDarkAmount;
-
-    SmallClockPosition(Context context) {
-        this(SystemBarUtils.getStatusBarHeight(context),
-                context.getResources().getDimensionPixelSize(R.dimen.keyguard_lock_padding),
-                context.getResources().getDimensionPixelSize(R.dimen.keyguard_lock_height),
-                context.getResources().getDimensionPixelSize(R.dimen.burn_in_prevention_offset_y)
-        );
-    }
-
-    @VisibleForTesting
-    SmallClockPosition(int statusBarHeight, int lockPadding, int lockHeight, int burnInY) {
-        mStatusBarHeight = statusBarHeight;
-        mKeyguardLockPadding = lockPadding;
-        mKeyguardLockHeight = lockHeight;
-        mBurnInOffsetY = burnInY;
-    }
-
-    /**
-     * See {@link ClockPlugin#setDarkAmount}.
-     */
-    void setDarkAmount(float darkAmount) {
-        mDarkAmount = darkAmount;
-    }
-
-    /**
-     * Gets the preferred Y position accounting for status bar and lock icon heights.
-     */
-    int getPreferredY() {
-        // On AOD, clock needs to appear below the status bar with enough room for pixel shifting
-        int aodY = mStatusBarHeight + mKeyguardLockHeight + 2 * mKeyguardLockPadding
-                + mBurnInOffsetY;
-        // On lock screen, clock needs to appear below the lock icon
-        int lockY =  mStatusBarHeight + mKeyguardLockHeight + 2 * mKeyguardLockPadding;
-        return (int) MathUtils.lerp(lockY, aodY, mDarkAmount);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/ViewPreviewer.java b/packages/SystemUI/src/com/android/keyguard/clock/ViewPreviewer.java
deleted file mode 100644
index abd0dd2..0000000
--- a/packages/SystemUI/src/com/android/keyguard/clock/ViewPreviewer.java
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.clock;
-
-import android.annotation.Nullable;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.os.Handler;
-import android.os.Looper;
-import android.util.Log;
-import android.view.View;
-import android.view.ViewGroup;
-
-import java.util.concurrent.Callable;
-import java.util.concurrent.FutureTask;
-
-/**
- * Creates a preview image ({@link Bitmap}) of a {@link View} for a custom clock face.
- */
-final class ViewPreviewer {
-
-    private static final String TAG = "ViewPreviewer";
-
-    /**
-     * Handler used to run {@link View#draw(Canvas)} on the main thread.
-     */
-    private final Handler mMainHandler = new Handler(Looper.getMainLooper());
-
-    /**
-     * Generate a realistic preview of a clock face.
-     *
-     * @param view view is used to generate preview image.
-     * @param width width of the preview image, should be the same as device width in pixels.
-     * @param height height of the preview image, should be the same as device height in pixels.
-     * @return bitmap of view.
-     */
-    @Nullable
-    Bitmap createPreview(View view, int width, int height) {
-        if (view == null) {
-            return null;
-        }
-        FutureTask<Bitmap> task = new FutureTask<>(new Callable<Bitmap>() {
-            @Override
-            public Bitmap call() {
-                Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
-
-                // Draw clock view hierarchy to canvas.
-                Canvas canvas = new Canvas(bitmap);
-                canvas.drawColor(Color.BLACK);
-                dispatchVisibilityAggregated(view, true);
-                view.measure(View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY),
-                        View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY));
-                view.layout(0, 0, width, height);
-                view.draw(canvas);
-
-                return bitmap;
-            }
-        });
-
-        if (Looper.myLooper() == Looper.getMainLooper()) {
-            task.run();
-        } else {
-            mMainHandler.post(task);
-        }
-
-        try {
-            return task.get();
-        } catch (Exception e) {
-            Log.e(TAG, "Error completing task", e);
-            return null;
-        }
-    }
-
-    private void dispatchVisibilityAggregated(View view, boolean isVisible) {
-        // Similar to View.dispatchVisibilityAggregated implementation.
-        final boolean thisVisible = view.getVisibility() == View.VISIBLE;
-        if (thisVisible || !isVisible) {
-            view.onVisibilityAggregated(isVisible);
-        }
-
-        if (view instanceof ViewGroup) {
-            isVisible = thisVisible && isVisible;
-            ViewGroup vg = (ViewGroup) view;
-            int count = vg.getChildCount();
-
-            for (int i = 0; i < count; i++) {
-                dispatchVisibilityAggregated(vg.getChildAt(i), isVisible);
-            }
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
index d2b10d6..83fc278 100644
--- a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
@@ -71,7 +71,8 @@
                 context.getString(R.string.lockscreen_clock_id_fallback),
                 logBuffer,
                 /* keepAllLoaded = */ false,
-                /* subTag = */ "System");
+                /* subTag = */ "System",
+                /* isTransitClockEnabled = */ featureFlags.isEnabled(Flags.TRANSIT_CLOCK));
         registry.registerListeners();
         return registry;
     }
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerComponent.java b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerComponent.java
index 154b0ed..b57c2b5 100644
--- a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerComponent.java
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerComponent.java
@@ -20,7 +20,7 @@
 
 import com.android.keyguard.KeyguardSecurityContainerController;
 import com.android.systemui.dagger.qualifiers.RootView;
-import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor;
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
 
 import dagger.BindsInstance;
 import dagger.Subcomponent;
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerModule.java b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerModule.java
index 38f252a..893239b 100644
--- a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerModule.java
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerModule.java
@@ -28,7 +28,7 @@
 import com.android.systemui.R;
 import com.android.systemui.biometrics.SideFpsController;
 import com.android.systemui.dagger.qualifiers.RootView;
-import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor;
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
 
 import java.util.Optional;
 
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewModule.java b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewModule.java
index a7d4455..8762769 100644
--- a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewModule.java
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewModule.java
@@ -20,6 +20,7 @@
 import com.android.systemui.R;
 import com.android.systemui.battery.BatteryMeterView;
 import com.android.systemui.statusbar.phone.KeyguardStatusBarView;
+import com.android.systemui.statusbar.phone.StatusBarLocation;
 import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer;
 
 import dagger.Module;
@@ -44,6 +45,13 @@
     /** */
     @Provides
     @KeyguardStatusBarViewScope
+    static StatusBarLocation getStatusBarLocation() {
+        return StatusBarLocation.KEYGUARD;
+    }
+
+    /** */
+    @Provides
+    @KeyguardStatusBarViewScope
     static StatusBarUserSwitcherContainer getUserSwitcherContainer(KeyguardStatusBarView view) {
         return view.findViewById(R.id.user_switcher_container);
     }
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/BiometricMessageDeferralLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/BiometricMessageDeferralLogger.kt
index e7295ef..54738c6 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/BiometricMessageDeferralLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/BiometricMessageDeferralLogger.kt
@@ -18,7 +18,7 @@
 
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel.DEBUG
+import com.android.systemui.log.core.LogLevel.DEBUG
 import com.android.systemui.log.dagger.BiometricLog
 import javax.inject.Inject
 
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/BiometricUnlockLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/BiometricUnlockLogger.kt
index c00b2c6..cfad614 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/BiometricUnlockLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/BiometricUnlockLogger.kt
@@ -19,9 +19,9 @@
 import android.hardware.biometrics.BiometricSourceType
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
-import com.android.systemui.log.LogLevel.DEBUG
-import com.android.systemui.log.LogLevel.INFO
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.core.LogLevel.DEBUG
+import com.android.systemui.log.core.LogLevel.INFO
 import com.android.systemui.log.dagger.BiometricLog
 import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_DISMISS_BOUNCER
 import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_NONE
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/CarrierTextManagerLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/CarrierTextManagerLogger.kt
index 1978715..d02b72f 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/CarrierTextManagerLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/CarrierTextManagerLogger.kt
@@ -19,7 +19,7 @@
 import androidx.annotation.IntDef
 import com.android.keyguard.CarrierTextManager.CarrierTextCallbackInfo
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import com.android.systemui.log.dagger.CarrierTextManagerLog
 import javax.inject.Inject
 
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt
index 8b925b1..bddf3b0 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt
@@ -19,7 +19,7 @@
 import com.android.systemui.biometrics.AuthRippleController
 import com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import com.android.systemui.log.dagger.KeyguardLog
 import com.android.systemui.statusbar.KeyguardIndicationController
 import com.google.errorprone.annotations.CompileTimeConstant
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
index b596331..59e0cad 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
@@ -18,6 +18,7 @@
 
 import android.content.Intent
 import android.hardware.biometrics.BiometricConstants.LockoutMode
+import android.hardware.biometrics.BiometricSourceType
 import android.os.PowerManager
 import android.os.PowerManager.WakeReason
 import android.telephony.ServiceState
@@ -32,12 +33,12 @@
 import com.android.keyguard.TrustGrantFlags
 import com.android.settingslib.fuelgauge.BatteryStatus
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
-import com.android.systemui.log.LogLevel.DEBUG
-import com.android.systemui.log.LogLevel.ERROR
-import com.android.systemui.log.LogLevel.INFO
-import com.android.systemui.log.LogLevel.VERBOSE
-import com.android.systemui.log.LogLevel.WARNING
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.core.LogLevel.DEBUG
+import com.android.systemui.log.core.LogLevel.ERROR
+import com.android.systemui.log.core.LogLevel.INFO
+import com.android.systemui.log.core.LogLevel.VERBOSE
+import com.android.systemui.log.core.LogLevel.WARNING
 import com.android.systemui.log.dagger.KeyguardUpdateMonitorLog
 import com.google.errorprone.annotations.CompileTimeConstant
 import javax.inject.Inject
@@ -370,16 +371,14 @@
 
     fun logServiceProvidersUpdated(intent: Intent) {
         logBuffer.log(
-                TAG,
-                VERBOSE,
-                {
-                    int1 = intent.getIntExtra(EXTRA_SUBSCRIPTION_INDEX, INVALID_SUBSCRIPTION_ID)
-                    str1 = intent.getStringExtra(TelephonyManager.EXTRA_SPN)
-                    str2 = intent.getStringExtra(TelephonyManager.EXTRA_PLMN)
-                },
-                {
-                    "action SERVICE_PROVIDERS_UPDATED subId=$int1 spn=$str1 plmn=$str2"
-                }
+            TAG,
+            VERBOSE,
+            {
+                int1 = intent.getIntExtra(EXTRA_SUBSCRIPTION_INDEX, INVALID_SUBSCRIPTION_ID)
+                str1 = intent.getStringExtra(TelephonyManager.EXTRA_SPN)
+                str2 = intent.getStringExtra(TelephonyManager.EXTRA_PLMN)
+            },
+            { "action SERVICE_PROVIDERS_UPDATED subId=$int1 spn=$str1 plmn=$str2" }
         )
     }
 
@@ -601,6 +600,15 @@
         )
     }
 
+    fun allowFingerprintOnCurrentOccludingActivityChanged(allow: Boolean) {
+        logBuffer.log(
+                TAG,
+                VERBOSE,
+                { bool1 = allow },
+                { "allowFingerprintOnCurrentOccludingActivityChanged: $bool1" }
+        )
+    }
+
     fun logAssistantVisible(assistantVisible: Boolean) {
         logBuffer.log(
             TAG,
@@ -679,7 +687,7 @@
                     "userId: $int1 " +
                     "old: $bool1, " +
                     "new: $bool2 " +
-                    "context: $context"
+                    "context: $str1"
             }
         )
     }
@@ -710,4 +718,13 @@
     fun scheduleWatchdog(@CompileTimeConstant watchdogType: String) {
         logBuffer.log(TAG, DEBUG, "Scheduling biometric watchdog for $watchdogType")
     }
+
+    fun notifyAboutEnrollmentsChanged(biometricSourceType: BiometricSourceType) {
+        logBuffer.log(
+            TAG,
+            DEBUG,
+            { str1 = "$biometricSourceType" },
+            { "notifying about enrollments changed: $str1" }
+        )
+    }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/TrustRepositoryLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/TrustRepositoryLogger.kt
index cb764a8..57b5db1 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/TrustRepositoryLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/TrustRepositoryLogger.kt
@@ -21,8 +21,8 @@
 import com.android.systemui.keyguard.shared.model.TrustManagedModel
 import com.android.systemui.keyguard.shared.model.TrustModel
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
-import com.android.systemui.log.LogLevel.DEBUG
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.core.LogLevel.DEBUG
 import com.android.systemui.log.dagger.KeyguardUpdateMonitorLog
 import javax.inject.Inject
 
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index be5bb07..04acd0b 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -33,7 +33,6 @@
 import com.android.internal.util.Preconditions;
 import com.android.keyguard.KeyguardSecurityModel;
 import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.keyguard.clock.ClockManager;
 import com.android.settingslib.bluetooth.LocalBluetoothManager;
 import com.android.systemui.accessibility.AccessibilityButtonModeObserver;
 import com.android.systemui.accessibility.AccessibilityButtonTargetsObserver;
@@ -124,7 +123,6 @@
 import com.android.systemui.statusbar.policy.ZenModeController;
 import com.android.systemui.statusbar.window.StatusBarWindowController;
 import com.android.systemui.telephony.TelephonyListenerManager;
-import com.android.systemui.tracing.ProtoTracer;
 import com.android.systemui.tuner.TunablePadding.TunablePaddingService;
 import com.android.systemui.tuner.TunerService;
 import com.android.systemui.util.DeviceConfigProxy;
@@ -323,7 +321,6 @@
     @Inject @Named(LEAK_REPORT_EMAIL_NAME) Lazy<String> mLeakReportEmail;
     @Inject @Main Lazy<Executor> mMainExecutor;
     @Inject @Background Lazy<Executor> mBackgroundExecutor;
-    @Inject Lazy<ClockManager> mClockManager;
     @Inject Lazy<ActivityManagerWrapper> mActivityManagerWrapper;
     @Inject Lazy<DevicePolicyManagerWrapper> mDevicePolicyManagerWrapper;
     @Inject Lazy<PackageManagerWrapper> mPackageManagerWrapper;
@@ -337,7 +334,6 @@
     @Inject Lazy<IWallpaperManager> mWallpaperManager;
     @Inject Lazy<CommandQueue> mCommandQueue;
     @Inject Lazy<RecordingController> mRecordingController;
-    @Inject Lazy<ProtoTracer> mProtoTracer;
     @Inject Lazy<MediaOutputDialogFactory> mMediaOutputDialogFactory;
     @Inject Lazy<DeviceConfigProxy> mDeviceConfigProxy;
     @Inject Lazy<TelephonyListenerManager> mTelephonyListenerManager;
@@ -517,7 +513,6 @@
         mProviders.put(SmartReplyController.class, mSmartReplyController::get);
         mProviders.put(RemoteInputQuickSettingsDisabler.class,
                 mRemoteInputQuickSettingsDisabler::get);
-        mProviders.put(ClockManager.class, mClockManager::get);
         mProviders.put(PrivacyItemController.class, mPrivacyItemController::get);
         mProviders.put(ActivityManagerWrapper.class, mActivityManagerWrapper::get);
         mProviders.put(DevicePolicyManagerWrapper.class, mDevicePolicyManagerWrapper::get);
@@ -531,7 +526,6 @@
         mProviders.put(DozeParameters.class, mDozeParameters::get);
         mProviders.put(IWallpaperManager.class, mWallpaperManager::get);
         mProviders.put(CommandQueue.class, mCommandQueue::get);
-        mProviders.put(ProtoTracer.class, mProtoTracer::get);
         mProviders.put(DeviceConfigProxy.class, mDeviceConfigProxy::get);
         mProviders.put(TelephonyListenerManager.class, mTelephonyListenerManager::get);
 
diff --git a/packages/SystemUI/src/com/android/systemui/LatencyTester.java b/packages/SystemUI/src/com/android/systemui/LatencyTester.java
index 39d9714..6ea0fc3 100644
--- a/packages/SystemUI/src/com/android/systemui/LatencyTester.java
+++ b/packages/SystemUI/src/com/android/systemui/LatencyTester.java
@@ -80,7 +80,7 @@
 
     @Override
     public void start() {
-        registerForBroadcasts(mEnabled);
+        updateEnabled();
     }
 
     private void fakeWakeAndUnlock(BiometricSourceType type) {
diff --git a/packages/SystemUI/src/com/android/systemui/PhoneSystemUIAppComponentFactory.kt b/packages/SystemUI/src/com/android/systemui/PhoneSystemUIAppComponentFactory.kt
new file mode 100644
index 0000000..f06cb41
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/PhoneSystemUIAppComponentFactory.kt
@@ -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 com.android.systemui
+
+import android.content.Context
+
+class PhoneSystemUIAppComponentFactory : SystemUIAppComponentFactoryBase() {
+    override fun createSystemUIInitializer(context: Context) = SystemUIInitializerImpl(context)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorHwcLayer.kt b/packages/SystemUI/src/com/android/systemui/ScreenDecorHwcLayer.kt
index 99dd6b6..c465585 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorHwcLayer.kt
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorHwcLayer.kt
@@ -49,13 +49,21 @@
  * When the HWC of the device supports Composition.DISPLAY_DECORATION, we use this layer to draw
  * screen decorations.
  */
-class ScreenDecorHwcLayer(context: Context, displayDecorationSupport: DisplayDecorationSupport) :
-        DisplayCutoutBaseView(context) {
+class ScreenDecorHwcLayer(
+    context: Context,
+    displayDecorationSupport: DisplayDecorationSupport,
+    private val debug: Boolean,
+) : DisplayCutoutBaseView(context) {
     val colorMode: Int
     private val useInvertedAlphaColor: Boolean
-    private val color: Int
+    private var color: Int = Color.BLACK
+        set(value) {
+            field = value
+            paint.color = value
+        }
+
     private val bgColor: Int
-    private val cornerFilter: ColorFilter
+    private var cornerFilter: ColorFilter
     private val cornerBgFilter: ColorFilter
     private val clearPaint: Paint
     @JvmField val transparentRect: Rect = Rect()
@@ -74,7 +82,7 @@
             throw IllegalArgumentException("Attempting to use unsupported mode " +
                     "${PixelFormat.formatToString(displayDecorationSupport.format)}")
         }
-        if (DEBUG_COLOR) {
+        if (debug) {
             color = Color.GREEN
             bgColor = Color.TRANSPARENT
             colorMode = ActivityInfo.COLOR_MODE_DEFAULT
@@ -106,10 +114,16 @@
     override fun onAttachedToWindow() {
         super.onAttachedToWindow()
         parent.requestTransparentRegion(this)
-        if (!DEBUG_COLOR) {
+        updateColors()
+    }
+
+    private fun updateColors() {
+        if (!debug) {
             viewRootImpl.setDisplayDecoration(true)
         }
 
+        cornerFilter = PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN)
+
         if (useInvertedAlphaColor) {
             paint.set(clearPaint)
         } else {
@@ -118,6 +132,21 @@
         }
     }
 
+    fun setDebugColor(color: Int) {
+        if (!debug) {
+            return
+        }
+
+        if (this.color == color) {
+            return
+        }
+
+        this.color = color
+
+        updateColors()
+        invalidate()
+    }
+
     override fun onUpdate() {
         parent.requestTransparentRegion(this)
     }
@@ -143,12 +172,12 @@
     override fun gatherTransparentRegion(region: Region?): Boolean {
         region?.let {
             calculateTransparentRect()
-            if (DEBUG_COLOR) {
+            if (debug) {
                 // Since we're going to draw a rectangle where the layer would
                 // normally be transparent, treat the transparent region as
                 // empty. We still want this method to be called, though, so
                 // that it calculates the transparent rect at the right time
-                // to match !DEBUG_COLOR.
+                // to match ![debug]
                 region.setEmpty()
             } else {
                 region.op(transparentRect, Region.Op.INTERSECT)
@@ -364,7 +393,7 @@
     /**
      * Update the rounded corner drawables.
      */
-    fun updateRoundedCornerDrawable(top: Drawable, bottom: Drawable) {
+    fun updateRoundedCornerDrawable(top: Drawable?, bottom: Drawable?) {
         roundedCornerDrawableTop = top
         roundedCornerDrawableBottom = bottom
         updateRoundedCornerDrawableBounds()
@@ -421,8 +450,4 @@
         ipw.println("roundedCornerBottomSize=$roundedCornerBottomSize")
         ipw.decreaseIndent()
     }
-
-    companion object {
-        private val DEBUG_COLOR = ScreenDecorations.DEBUG_COLOR
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index ea0f343..ff395da 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -36,6 +36,7 @@
 import android.graphics.Paint;
 import android.graphics.Path;
 import android.graphics.PixelFormat;
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.hardware.graphics.common.AlphaInterpretation;
@@ -69,8 +70,9 @@
 import com.android.settingslib.Utils;
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.decor.CutoutDecorProviderFactory;
+import com.android.systemui.decor.DebugRoundedCornerDelegate;
+import com.android.systemui.decor.DebugRoundedCornerModel;
 import com.android.systemui.decor.DecorProvider;
 import com.android.systemui.decor.DecorProviderFactory;
 import com.android.systemui.decor.DecorProviderKt;
@@ -78,14 +80,14 @@
 import com.android.systemui.decor.OverlayWindow;
 import com.android.systemui.decor.PrivacyDotDecorProviderFactory;
 import com.android.systemui.decor.RoundedCornerDecorProviderFactory;
-import com.android.systemui.decor.RoundedCornerResDelegate;
+import com.android.systemui.decor.RoundedCornerResDelegateImpl;
+import com.android.systemui.decor.ScreenDecorCommand;
 import com.android.systemui.log.ScreenDecorationsLogger;
 import com.android.systemui.qs.SettingObserver;
 import com.android.systemui.settings.DisplayTracker;
 import com.android.systemui.settings.UserTracker;
+import com.android.systemui.statusbar.commandline.CommandRegistry;
 import com.android.systemui.statusbar.events.PrivacyDotViewController;
-import com.android.systemui.tuner.TunerService;
-import com.android.systemui.tuner.TunerService.Tunable;
 import com.android.systemui.util.concurrency.DelayableExecutor;
 import com.android.systemui.util.concurrency.ThreadFactory;
 import com.android.systemui.util.settings.SecureSettings;
@@ -96,7 +98,6 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
-import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
 
@@ -105,19 +106,17 @@
  * for antialiasing and emulation purposes.
  */
 @SysUISingleton
-public class ScreenDecorations implements CoreStartable, Tunable , Dumpable {
-    private static final boolean DEBUG = false;
+public class ScreenDecorations implements CoreStartable, Dumpable {
+    private static final boolean DEBUG_LOGGING = false;
     private static final String TAG = "ScreenDecorations";
 
-    public static final String SIZE = "sysui_rounded_size";
-    public static final String PADDING = "sysui_rounded_content_padding";
     // Provide a way for factory to disable ScreenDecorations to run the Display tests.
     private static final boolean DEBUG_DISABLE_SCREEN_DECORATIONS =
             SystemProperties.getBoolean("debug.disable_screen_decorations", false);
     private static final boolean DEBUG_SCREENSHOT_ROUNDED_CORNERS =
             SystemProperties.getBoolean("debug.screenshot_rounded_corners", false);
-    private static final boolean VERBOSE = false;
-    static final boolean DEBUG_COLOR = DEBUG_SCREENSHOT_ROUNDED_CORNERS;
+    private boolean mDebug = DEBUG_SCREENSHOT_ROUNDED_CORNERS;
+    private int mDebugColor = Color.RED;
 
     private static final int[] DISPLAY_CUTOUT_IDS = {
             R.id.display_cutout,
@@ -133,8 +132,7 @@
     @VisibleForTesting
     protected boolean mIsRegistered;
     private final Context mContext;
-    private final Executor mMainExecutor;
-    private final TunerService mTunerService;
+    private final CommandRegistry mCommandRegistry;
     private final SecureSettings mSecureSettings;
     @VisibleForTesting
     DisplayTracker.Callback mDisplayListener;
@@ -147,9 +145,13 @@
     public final int mFaceScanningViewId;
 
     @VisibleForTesting
-    protected RoundedCornerResDelegate mRoundedCornerResDelegate;
+    protected RoundedCornerResDelegateImpl mRoundedCornerResDelegate;
     @VisibleForTesting
     protected DecorProviderFactory mRoundedCornerFactory;
+    @VisibleForTesting
+    protected DebugRoundedCornerDelegate mDebugRoundedCornerDelegate =
+            new DebugRoundedCornerDelegate();
+    protected DecorProviderFactory mDebugRoundedCornerFactory;
     private CutoutDecorProviderFactory mCutoutFactory;
     private int mProviderRefreshToken = 0;
     @VisibleForTesting
@@ -170,7 +172,7 @@
     private int mTintColor = Color.BLACK;
     @VisibleForTesting
     protected DisplayDecorationSupport mHwcScreenDecorationSupport;
-    private Display.Mode mDisplayMode;
+    private final Point mDisplaySize = new Point();
     @VisibleForTesting
     protected DisplayInfo mDisplayInfo = new DisplayInfo();
     private DisplayCutout mDisplayCutout;
@@ -313,9 +315,8 @@
 
     @Inject
     public ScreenDecorations(Context context,
-            @Main Executor mainExecutor,
             SecureSettings secureSettings,
-            TunerService tunerService,
+            CommandRegistry commandRegistry,
             UserTracker userTracker,
             DisplayTracker displayTracker,
             PrivacyDotViewController dotViewController,
@@ -325,9 +326,8 @@
             ScreenDecorationsLogger logger,
             AuthController authController) {
         mContext = context;
-        mMainExecutor = mainExecutor;
         mSecureSettings = secureSettings;
-        mTunerService = tunerService;
+        mCommandRegistry = commandRegistry;
         mUserTracker = userTracker;
         mDisplayTracker = displayTracker;
         mDotViewController = dotViewController;
@@ -352,6 +352,45 @@
         }
     };
 
+    private final ScreenDecorCommand.Callback mScreenDecorCommandCallback = (cmd, pw) -> {
+        // If we are exiting debug mode, we can set it (false) and bail, otherwise we will
+        // ensure that debug mode is set
+        if (cmd.getDebug() != null && !cmd.getDebug()) {
+            setDebug(false);
+            return;
+        } else {
+            // setDebug is idempotent
+            setDebug(true);
+        }
+
+        if (cmd.getColor() != null) {
+            mDebugColor = cmd.getColor();
+            mExecutor.execute(() -> {
+                if (mScreenDecorHwcLayer != null) {
+                    mScreenDecorHwcLayer.setDebugColor(cmd.getColor());
+                }
+                updateColorInversionDefault();
+            });
+        }
+
+        DebugRoundedCornerModel roundedTop = null;
+        DebugRoundedCornerModel roundedBottom = null;
+        if (cmd.getRoundedTop() != null) {
+            roundedTop = cmd.getRoundedTop().toRoundedCornerDebugModel();
+        }
+        if (cmd.getRoundedBottom() != null) {
+            roundedBottom = cmd.getRoundedBottom().toRoundedCornerDebugModel();
+        }
+        if (roundedTop != null || roundedBottom != null) {
+            mDebugRoundedCornerDelegate.applyNewDebugCorners(roundedTop, roundedBottom);
+            mExecutor.execute(() -> {
+                removeAllOverlays();
+                removeHwcOverlay();
+                setupDecorations();
+            });
+        }
+    };
+
     @Override
     public void start() {
         if (DEBUG_DISABLE_SCREEN_DECORATIONS) {
@@ -363,6 +402,34 @@
         mExecutor.execute(this::startOnScreenDecorationsThread);
         mDotViewController.setUiExecutor(mExecutor);
         mAuthController.addCallback(mAuthControllerCallback);
+        mCommandRegistry.registerCommand(ScreenDecorCommand.SCREEN_DECOR_CMD_NAME,
+                () -> new ScreenDecorCommand(mScreenDecorCommandCallback));
+    }
+
+    /**
+     * Change the value of {@link ScreenDecorations#mDebug}. This operation is heavyweight, since
+     * it requires essentially re-init-ing this screen decorations process with the debug
+     * information taken into account.
+     */
+    @VisibleForTesting
+    protected void setDebug(boolean debug) {
+        if (mDebug == debug) {
+            return;
+        }
+
+        mDebug = debug;
+        if (!mDebug) {
+            mDebugRoundedCornerDelegate.removeDebugState();
+        }
+
+        mExecutor.execute(() -> {
+            // Re-trigger all of the screen decorations setup here so that the debug values
+            // can be picked up
+            removeAllOverlays();
+            removeHwcOverlay();
+            startOnScreenDecorationsThread();
+            updateColorInversionDefault();
+        });
     }
 
     private boolean isPrivacyDotEnabled() {
@@ -370,11 +437,16 @@
     }
 
     @NonNull
-    private List<DecorProvider> getProviders(boolean hasHwLayer) {
+    @VisibleForTesting
+    protected List<DecorProvider> getProviders(boolean hasHwLayer) {
         List<DecorProvider> decorProviders = new ArrayList<>(mDotFactory.getProviders());
         decorProviders.addAll(mFaceScanningFactory.getProviders());
         if (!hasHwLayer) {
-            decorProviders.addAll(mRoundedCornerFactory.getProviders());
+            if (mDebug && mDebugRoundedCornerFactory.getHasProviders()) {
+                decorProviders.addAll(mDebugRoundedCornerFactory.getProviders());
+            } else {
+                decorProviders.addAll(mRoundedCornerFactory.getProviders());
+            }
             decorProviders.addAll(mCutoutFactory.getProviders());
         }
         return decorProviders;
@@ -413,14 +485,17 @@
         mWindowManager = mContext.getSystemService(WindowManager.class);
         mContext.getDisplay().getDisplayInfo(mDisplayInfo);
         mRotation = mDisplayInfo.rotation;
-        mDisplayMode = mDisplayInfo.getMode();
+        mDisplaySize.x = mDisplayInfo.getNaturalWidth();
+        mDisplaySize.y = mDisplayInfo.getNaturalHeight();
         mDisplayUniqueId = mDisplayInfo.uniqueId;
         mDisplayCutout = mDisplayInfo.displayCutout;
-        mRoundedCornerResDelegate = new RoundedCornerResDelegate(mContext.getResources(),
-                mDisplayUniqueId);
+        mRoundedCornerResDelegate =
+                new RoundedCornerResDelegateImpl(mContext.getResources(), mDisplayUniqueId);
         mRoundedCornerResDelegate.setPhysicalPixelDisplaySizeRatio(
                 getPhysicalPixelDisplaySizeRatio());
         mRoundedCornerFactory = new RoundedCornerDecorProviderFactory(mRoundedCornerResDelegate);
+        mDebugRoundedCornerFactory =
+                new RoundedCornerDecorProviderFactory(mDebugRoundedCornerDelegate);
         mCutoutFactory = getCutoutFactory();
         mHwcScreenDecorationSupport = mContext.getDisplay().getDisplayDecorationSupport();
         updateHwLayerRoundedCornerDrawable();
@@ -432,10 +507,12 @@
             public void onDisplayChanged(int displayId) {
                 mContext.getDisplay().getDisplayInfo(mDisplayInfo);
                 final int newRotation = mDisplayInfo.rotation;
-                final Display.Mode newDisplayMode = mDisplayInfo.getMode();
                 if ((mOverlays != null || mScreenDecorHwcWindow != null)
                         && (mRotation != newRotation
-                        || displayModeChanged(mDisplayMode, newDisplayMode))) {
+                        || displaySizeChanged(mDisplaySize, mDisplayInfo))) {
+                    final Point newSize = new Point();
+                    newSize.x = mDisplayInfo.getNaturalWidth();
+                    newSize.y = mDisplayInfo.getNaturalHeight();
                     // We cannot immediately update the orientation. Otherwise
                     // WindowManager is still deferring layout until it has finished dispatching
                     // the config changes, which may cause divergence between what we draw
@@ -444,15 +521,11 @@
                     // - we are trying to redraw. This because WM resized our window and told us to.
                     // - the config change has been dispatched, so WM is no longer deferring layout.
                     mPendingConfigChange = true;
-                    if (DEBUG) {
-                        if (mRotation != newRotation) {
-                            Log.i(TAG, "Rotation changed, deferring " + newRotation
-                                            + ", staying at " + mRotation);
-                        }
-                        if (displayModeChanged(mDisplayMode, newDisplayMode)) {
-                            Log.i(TAG, "Resolution changed, deferring " + newDisplayMode
-                                    + ", staying at " + mDisplayMode);
-                        }
+                    if (mRotation != newRotation) {
+                        mLogger.logRotationChangeDeferred(mRotation, newRotation);
+                    }
+                    if (!mDisplaySize.equals(newSize)) {
+                        mLogger.logDisplaySizeChanged(mDisplaySize, newSize);
                     }
 
                     if (mOverlays != null) {
@@ -461,7 +534,7 @@
                                 final ViewGroup overlayView = mOverlays[i].getRootView();
                                 overlayView.getViewTreeObserver().addOnPreDrawListener(
                                         new RestartingPreDrawListener(
-                                                overlayView, i, newRotation, newDisplayMode));
+                                                overlayView, i, newRotation, newSize));
                             }
                         }
                     }
@@ -471,7 +544,7 @@
                                 new RestartingPreDrawListener(
                                         mScreenDecorHwcWindow,
                                         -1, // Pass -1 for views with no specific position.
-                                        newRotation, newDisplayMode));
+                                        newRotation, newSize));
                     }
                     if (mScreenDecorHwcLayer != null) {
                         mScreenDecorHwcLayer.pendingConfigChange = true;
@@ -608,12 +681,6 @@
                 return;
             }
 
-            mMainExecutor.execute(() -> {
-                Trace.beginSection("ScreenDecorations#addTunable");
-                mTunerService.addTunable(this, SIZE);
-                Trace.endSection();
-            });
-
             // Watch color inversion and invert the overlay as needed.
             if (mColorInversionSetting == null) {
                 mColorInversionSetting = new SettingObserver(mSecureSettings, mHandler,
@@ -632,12 +699,6 @@
             mUserTracker.addCallback(mUserChangedCallback, mExecutor);
             mIsRegistered = true;
         } else {
-            mMainExecutor.execute(() -> {
-                Trace.beginSection("ScreenDecorations#removeTunable");
-                mTunerService.removeTunable(this);
-                Trace.endSection();
-            });
-
             if (mColorInversionSetting != null) {
                 mColorInversionSetting.setListening(false);
             }
@@ -777,7 +838,8 @@
         }
         mScreenDecorHwcWindow = (ViewGroup) LayoutInflater.from(mContext).inflate(
                 R.layout.screen_decor_hwc_layer, null);
-        mScreenDecorHwcLayer = new ScreenDecorHwcLayer(mContext, mHwcScreenDecorationSupport);
+        mScreenDecorHwcLayer =
+                new ScreenDecorHwcLayer(mContext, mHwcScreenDecorationSupport, mDebug);
         mScreenDecorHwcWindow.addView(mScreenDecorHwcLayer, new FrameLayout.LayoutParams(
                 MATCH_PARENT, MATCH_PARENT, Gravity.TOP | Gravity.START));
         mWindowManager.addView(mScreenDecorHwcWindow, getHwcWindowLayoutParams());
@@ -825,7 +887,7 @@
         lp.height = MATCH_PARENT;
         lp.setTitle("ScreenDecorHwcOverlay");
         lp.gravity = Gravity.TOP | Gravity.START;
-        if (!DEBUG_COLOR) {
+        if (!mDebug) {
             lp.setColorMode(ActivityInfo.COLOR_MODE_A8);
         }
         return lp;
@@ -884,15 +946,8 @@
         }
     }
 
-    private static boolean displayModeChanged(Display.Mode oldMode, Display.Mode newMode) {
-        if (oldMode == null) {
-            return true;
-        }
-
-        // We purposely ignore refresh rate and id changes here, because we don't need to
-        // invalidate for those, and they can trigger the refresh rate to increase
-        return oldMode.getPhysicalWidth() != newMode.getPhysicalWidth()
-                || oldMode.getPhysicalHeight() != newMode.getPhysicalHeight();
+    private static boolean displaySizeChanged(Point size, DisplayInfo info) {
+        return size.x != info.getNaturalWidth() || size.y != info.getNaturalHeight();
     }
 
     private int getOverlayWindowGravity(@BoundsPosition int pos) {
@@ -933,19 +988,42 @@
             new UserTracker.Callback() {
                 @Override
                 public void onUserChanged(int newUser, @NonNull Context userContext) {
-                    if (DEBUG) {
-                        Log.d(TAG, "UserSwitched newUserId=" + newUser);
-                    }
+                    mLogger.logUserSwitched(newUser);
                     // update color inversion setting to the new user
                     mColorInversionSetting.setUserId(newUser);
                     updateColorInversion(mColorInversionSetting.getValue());
                 }
             };
 
+    /**
+     * Use the current value of {@link ScreenDecorations#mColorInversionSetting} and passes it
+     * to {@link ScreenDecorations#updateColorInversion}
+     */
+    private void updateColorInversionDefault() {
+        int inversion = 0;
+        if (mColorInversionSetting != null) {
+            inversion = mColorInversionSetting.getValue();
+        }
+
+        updateColorInversion(inversion);
+    }
+
+    /**
+     * Update the tint color of screen decoration assets. Defaults to Color.BLACK. In the case of
+     * a color inversion being set, use Color.WHITE (which inverts to black).
+     *
+     * When {@link ScreenDecorations#mDebug} is {@code true}, this value is updated to use
+     * {@link ScreenDecorations#mDebugColor}, and does not handle inversion.
+     *
+     * @param colorsInvertedValue if non-zero, assume that colors are inverted, and use Color.WHITE
+     *                            for screen decoration tint
+     */
     private void updateColorInversion(int colorsInvertedValue) {
         mTintColor = colorsInvertedValue != 0 ? Color.WHITE : Color.BLACK;
-        if (DEBUG_COLOR) {
-            mTintColor = Color.RED;
+        if (mDebug) {
+            mTintColor = mDebugColor;
+            mDebugRoundedCornerDelegate.setColor(mTintColor);
+            //TODO(b/285941724): update the hwc layer color here too (or disable it in debug mode)
         }
 
         updateOverlayProviderViews(new Integer[] {
@@ -986,7 +1064,9 @@
             int oldRotation = mRotation;
             mPendingConfigChange = false;
             updateConfiguration();
-            if (DEBUG) Log.i(TAG, "onConfigChanged from rot " + oldRotation + " to " + mRotation);
+            if (oldRotation != mRotation) {
+                mLogger.logRotationChanged(oldRotation, mRotation);
+            }
             setupDecorations();
             if (mOverlays != null) {
                 // Updating the layout params ensures that ViewRootImpl will call relayoutWindow(),
@@ -1016,6 +1096,7 @@
         if (DEBUG_DISABLE_SCREEN_DECORATIONS) {
             return;
         }
+        ipw.println("mDebug:" + mDebug);
 
         ipw.println("mIsPrivacyDotEnabled:" + isPrivacyDotEnabled());
         ipw.println("shouldOptimizeOverlayVisibility:" + shouldOptimizeVisibility());
@@ -1071,6 +1152,7 @@
             }
         }
         mRoundedCornerResDelegate.dump(pw, args);
+        mDebugRoundedCornerDelegate.dump(pw);
     }
 
     @VisibleForTesting
@@ -1084,17 +1166,18 @@
         if (mRotation != newRotation) {
             mDotViewController.setNewRotation(newRotation);
         }
-        final Display.Mode newMod = mDisplayInfo.getMode();
         final DisplayCutout newCutout = mDisplayInfo.displayCutout;
 
         if (!mPendingConfigChange
-                && (newRotation != mRotation || displayModeChanged(mDisplayMode, newMod)
+                && (newRotation != mRotation || displaySizeChanged(mDisplaySize, mDisplayInfo)
                 || !Objects.equals(newCutout, mDisplayCutout))) {
             mRotation = newRotation;
-            mDisplayMode = newMod;
+            mDisplaySize.x = mDisplayInfo.getNaturalWidth();
+            mDisplaySize.y = mDisplayInfo.getNaturalHeight();
             mDisplayCutout = newCutout;
-            mRoundedCornerResDelegate.setPhysicalPixelDisplaySizeRatio(
-                    getPhysicalPixelDisplaySizeRatio());
+            float ratio = getPhysicalPixelDisplaySizeRatio();
+            mRoundedCornerResDelegate.setPhysicalPixelDisplaySizeRatio(ratio);
+            mDebugRoundedCornerDelegate.setPhysicalPixelDisplaySizeRatio(ratio);
             if (mScreenDecorHwcLayer != null) {
                 mScreenDecorHwcLayer.pendingConfigChange = false;
                 mScreenDecorHwcLayer.updateConfiguration(mDisplayUniqueId);
@@ -1117,7 +1200,8 @@
     }
 
     private boolean hasRoundedCorners() {
-        return mRoundedCornerFactory.getHasProviders();
+        return mRoundedCornerFactory.getHasProviders()
+                || mDebugRoundedCornerFactory.getHasProviders();
     }
 
     private boolean shouldOptimizeVisibility() {
@@ -1170,43 +1254,19 @@
         Trace.endSection();
     }
 
-    @Override
-    public void onTuningChanged(String key, String newValue) {
-        if (DEBUG_DISABLE_SCREEN_DECORATIONS) {
-            Log.i(TAG, "ScreenDecorations is disabled");
-            return;
-        }
-        mExecutor.execute(() -> {
-            if (mOverlays == null || !SIZE.equals(key)) {
-                return;
-            }
-            Trace.beginSection("ScreenDecorations#onTuningChanged");
-            try {
-                final int sizeFactor = Integer.parseInt(newValue);
-                mRoundedCornerResDelegate.setTuningSizeFactor(sizeFactor);
-            } catch (NumberFormatException e) {
-                mRoundedCornerResDelegate.setTuningSizeFactor(null);
-            }
-            updateOverlayProviderViews(new Integer[] {
-                    R.id.rounded_corner_top_left,
-                    R.id.rounded_corner_top_right,
-                    R.id.rounded_corner_bottom_left,
-                    R.id.rounded_corner_bottom_right
-            });
-            updateHwLayerRoundedCornerExistAndSize();
-            Trace.endSection();
-        });
-    }
-
     private void updateHwLayerRoundedCornerDrawable() {
         if (mScreenDecorHwcLayer == null) {
             return;
         }
 
-        final Drawable topDrawable = mRoundedCornerResDelegate.getTopRoundedDrawable();
-        final Drawable bottomDrawable = mRoundedCornerResDelegate.getBottomRoundedDrawable();
+        Drawable topDrawable = mRoundedCornerResDelegate.getTopRoundedDrawable();
+        Drawable bottomDrawable = mRoundedCornerResDelegate.getBottomRoundedDrawable();
+        if (mDebug && (mDebugRoundedCornerFactory.getHasProviders())) {
+            topDrawable = mDebugRoundedCornerDelegate.getTopRoundedDrawable();
+            bottomDrawable = mDebugRoundedCornerDelegate.getBottomRoundedDrawable();
+        }
 
-        if (topDrawable == null || bottomDrawable == null) {
+        if (topDrawable == null && bottomDrawable == null) {
             return;
         }
         mScreenDecorHwcLayer.updateRoundedCornerDrawable(topDrawable, bottomDrawable);
@@ -1216,11 +1276,19 @@
         if (mScreenDecorHwcLayer == null) {
             return;
         }
-        mScreenDecorHwcLayer.updateRoundedCornerExistenceAndSize(
-                mRoundedCornerResDelegate.getHasTop(),
-                mRoundedCornerResDelegate.getHasBottom(),
-                mRoundedCornerResDelegate.getTopRoundedSize().getWidth(),
-                mRoundedCornerResDelegate.getBottomRoundedSize().getWidth());
+        if (mDebug && mDebugRoundedCornerFactory.getHasProviders()) {
+            mScreenDecorHwcLayer.updateRoundedCornerExistenceAndSize(
+                    mDebugRoundedCornerDelegate.getHasTop(),
+                    mDebugRoundedCornerDelegate.getHasBottom(),
+                    mDebugRoundedCornerDelegate.getTopRoundedSize().getWidth(),
+                    mDebugRoundedCornerDelegate.getBottomRoundedSize().getWidth());
+        } else {
+            mScreenDecorHwcLayer.updateRoundedCornerExistenceAndSize(
+                    mRoundedCornerResDelegate.getHasTop(),
+                    mRoundedCornerResDelegate.getHasBottom(),
+                    mRoundedCornerResDelegate.getTopRoundedSize().getWidth(),
+                    mRoundedCornerResDelegate.getBottomRoundedSize().getWidth());
+        }
     }
 
     @VisibleForTesting
@@ -1247,7 +1315,7 @@
 
             paint.setColor(mColor);
             paint.setStyle(Paint.Style.FILL);
-            if (DEBUG) {
+            if (DEBUG_LOGGING) {
                 getViewTreeObserver().addOnDrawListener(() -> Log.i(TAG,
                         getWindowTitleByPos(pos) + " drawn in rot " + mRotation));
             }
@@ -1422,31 +1490,29 @@
 
         private final View mView;
         private final int mTargetRotation;
-        private final Display.Mode mTargetDisplayMode;
+        private final Point mTargetDisplaySize;
         // Pass -1 for ScreenDecorHwcLayer since it's a fullscreen window and has no specific
         // position.
         private final int mPosition;
 
         private RestartingPreDrawListener(View view, @BoundsPosition int position,
-                int targetRotation, Display.Mode targetDisplayMode) {
+                int targetRotation, Point targetDisplaySize) {
             mView = view;
             mTargetRotation = targetRotation;
-            mTargetDisplayMode = targetDisplayMode;
+            mTargetDisplaySize = targetDisplaySize;
             mPosition = position;
         }
 
         @Override
         public boolean onPreDraw() {
             mView.getViewTreeObserver().removeOnPreDrawListener(this);
-            if (mTargetRotation == mRotation
-                    && !displayModeChanged(mDisplayMode, mTargetDisplayMode)) {
-                if (DEBUG) {
+            if (mTargetRotation == mRotation && mDisplaySize.equals(mTargetDisplaySize)) {
+                if (DEBUG_LOGGING) {
                     final String title = mPosition < 0 ? "ScreenDecorHwcLayer"
                             : getWindowTitleByPos(mPosition);
                     Log.i(TAG, title + " already in target rot "
                             + mTargetRotation + " and in target resolution "
-                            + mTargetDisplayMode.getPhysicalWidth() + "x"
-                            + mTargetDisplayMode.getPhysicalHeight()
+                            + mTargetDisplaySize.x + "x" + mTargetDisplaySize.y
                             + ", allow draw without restarting it");
                 }
                 return true;
@@ -1456,13 +1522,12 @@
             // This changes the window attributes - we need to restart the traversal for them to
             // take effect.
             updateConfiguration();
-            if (DEBUG) {
+            if (DEBUG_LOGGING) {
                 final String title = mPosition < 0 ? "ScreenDecorHwcLayer"
                         : getWindowTitleByPos(mPosition);
                 Log.i(TAG, title
                         + " restarting listener fired, restarting draw for rot " + mRotation
-                        + ", resolution " + mDisplayMode.getPhysicalWidth() + "x"
-                        + mDisplayMode.getPhysicalHeight());
+                        + ", resolution " + mDisplaySize.x + "x" + mDisplaySize.y);
             }
             mView.invalidate();
             return false;
@@ -1488,19 +1553,18 @@
         public boolean onPreDraw() {
             mContext.getDisplay().getDisplayInfo(mDisplayInfo);
             final int displayRotation = mDisplayInfo.rotation;
-            final Display.Mode displayMode = mDisplayInfo.getMode();
-            if ((displayRotation != mRotation || displayModeChanged(mDisplayMode, displayMode))
+            if ((displayRotation != mRotation || displaySizeChanged(mDisplaySize, mDisplayInfo))
                     && !mPendingConfigChange) {
-                if (DEBUG) {
+                if (DEBUG_LOGGING) {
                     if (displayRotation != mRotation) {
                         Log.i(TAG, "Drawing rot " + mRotation + ", but display is at rot "
                                 + displayRotation + ". Restarting draw");
                     }
-                    if (displayModeChanged(mDisplayMode, displayMode)) {
-                        Log.i(TAG, "Drawing at " + mDisplayMode.getPhysicalWidth()
-                                + "x" + mDisplayMode.getPhysicalHeight() + ", but display is at "
-                                + displayMode.getPhysicalWidth() + "x"
-                                + displayMode.getPhysicalHeight() + ". Restarting draw");
+                    if (displaySizeChanged(mDisplaySize, mDisplayInfo)) {
+                        Log.i(TAG, "Drawing at " + mDisplaySize.x + "x" + mDisplaySize.y
+                                + ", but display is at "
+                                + mDisplayInfo.getNaturalWidth() + "x"
+                                + mDisplayInfo.getNaturalHeight() + ". Restarting draw");
                     }
                 }
                 mView.invalidate();
diff --git a/packages/SystemUI/src/com/android/systemui/SlicePermissionActivity.java b/packages/SystemUI/src/com/android/systemui/SlicePermissionActivity.java
index 45077d2..6f99a24 100644
--- a/packages/SystemUI/src/com/android/systemui/SlicePermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/SlicePermissionActivity.java
@@ -51,7 +51,11 @@
         super.onCreate(savedInstanceState);
 
         // Verify intent is valid
-        mUri = getIntent().getParcelableExtra(SliceProvider.EXTRA_BIND_URI);
+        try {
+            mUri = getIntent().getParcelableExtra(SliceProvider.EXTRA_BIND_URI);
+        } catch (Exception e) {
+            Log.w(TAG, "Failed to getParcelableExtra", e);
+        }
         mCallingPkg = getIntent().getStringExtra(SliceProvider.EXTRA_PKG);
         if (mUri == null
                 || !SliceProvider.SLICE_TYPE.equals(getContentResolver().getType(mUri))
diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
index 806d1af..3ca74ac 100644
--- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
@@ -81,9 +81,6 @@
 
     protected final Handler mHandler;
 
-    private float mMinSwipeProgress = 0f;
-    private float mMaxSwipeProgress = 1f;
-
     private final SpringConfig mSnapBackSpringConfig =
             new SpringConfig(SpringForce.STIFFNESS_LOW, SpringForce.DAMPING_RATIO_LOW_BOUNCY);
 
@@ -227,18 +224,11 @@
         return v.getMeasuredWidth();
     }
 
-    public void setMinSwipeProgress(float minSwipeProgress) {
-        mMinSwipeProgress = minSwipeProgress;
-    }
-
-    public void setMaxSwipeProgress(float maxSwipeProgress) {
-        mMaxSwipeProgress = maxSwipeProgress;
-    }
-
     private float getSwipeProgressForOffset(View view, float translation) {
+        if (translation == 0) return 0;
         float viewSize = getSize(view);
         float result = Math.abs(translation / viewSize);
-        return Math.min(Math.max(mMinSwipeProgress, result), mMaxSwipeProgress);
+        return Math.min(Math.max(0, result), 1);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactory.java
deleted file mode 100644
index 527ce12..0000000
--- a/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactory.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui;
-
-import android.content.Context;
-
-/**
- * Starts up SystemUI using the AOSP {@link SystemUIInitializerImpl}.
- *
- * This initializer relies on reflection to start everything up and should be considered deprecated.
- * Instead, create your own {@link SystemUIAppComponentFactoryBase}, specify it in your
- * AndroidManifest.xml and construct your own {@link SystemUIInitializer} directly.
- *
- * @deprecated Define your own SystemUIAppComponentFactoryBase implementation and use that. This
- *             implementation may be changed or removed in future releases.
- */
-@Deprecated
-public class SystemUIAppComponentFactory extends SystemUIAppComponentFactoryBase {
-    @Override
-    protected SystemUIInitializer createSystemUIInitializer(Context context) {
-        return SystemUIInitializerFactory.createWithContext(context);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index 70c39df..522b397 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -54,7 +54,7 @@
  * Application class for SystemUI.
  */
 public class SystemUIApplication extends Application implements
-        SystemUIAppComponentFactory.ContextInitializer {
+        SystemUIAppComponentFactoryBase.ContextInitializer {
 
     public static final String TAG = "SystemUIService";
     private static final boolean DEBUG = false;
@@ -66,7 +66,7 @@
      */
     private CoreStartable[] mServices;
     private boolean mServicesStarted;
-    private SystemUIAppComponentFactory.ContextAvailableCallback mContextAvailableCallback;
+    private SystemUIAppComponentFactoryBase.ContextAvailableCallback mContextAvailableCallback;
     private SysUIComponent mSysUIComponent;
     private SystemUIInitializer mInitializer;
 
@@ -258,7 +258,7 @@
                 notifyBootCompleted(mServices[i]);
             }
 
-            dumpManager.registerDumpable(mServices[i].getClass().getName(), mServices[i]);
+            dumpManager.registerDumpable(mServices[i].getClass().getSimpleName(), mServices[i]);
         }
         mSysUIComponent.getInitController().executePostInitTasks();
         log.traceEnd();
@@ -366,7 +366,7 @@
 
     @Override
     public void setContextAvailableCallback(
-            SystemUIAppComponentFactory.ContextAvailableCallback callback) {
+            SystemUIAppComponentFactoryBase.ContextAvailableCallback callback) {
         mContextAvailableCallback = callback;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIInitializerFactory.kt b/packages/SystemUI/src/com/android/systemui/SystemUIInitializerFactory.kt
deleted file mode 100644
index b9454e8..0000000
--- a/packages/SystemUI/src/com/android/systemui/SystemUIInitializerFactory.kt
+++ /dev/null
@@ -1,72 +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.systemui
-
-import android.annotation.SuppressLint
-import android.content.Context
-import android.util.Log
-import com.android.internal.annotations.VisibleForTesting
-import com.android.systemui.util.Assert
-
-/**
- * Factory to reflectively lookup a [SystemUIInitializer] to start SystemUI with.
- */
-@Deprecated("Provide your own {@link SystemUIAppComponentFactoryBase} that doesn't need this.")
-object SystemUIInitializerFactory {
-    private const val TAG = "SysUIInitializerFactory"
-    @SuppressLint("StaticFieldLeak")
-    private var initializer: SystemUIInitializer? = null
-
-    /**
-     * Instantiate a [SystemUIInitializer] reflectively.
-     */
-    @JvmStatic
-    fun createWithContext(context: Context): SystemUIInitializer {
-        return createFromConfig(context)
-    }
-
-    /**
-     * Instantiate a [SystemUIInitializer] reflectively.
-     */
-    @JvmStatic
-    private fun createFromConfig(context: Context): SystemUIInitializer {
-        Assert.isMainThread()
-
-        return createFromConfigNoAssert(context)
-    }
-
-    @JvmStatic
-    @VisibleForTesting
-    fun createFromConfigNoAssert(context: Context): SystemUIInitializer {
-
-        return initializer ?: run {
-            val className = context.getString(R.string.config_systemUIFactoryComponent)
-            if (className.isEmpty()) {
-                throw RuntimeException("No SystemUIFactory component configured")
-            }
-            try {
-                val cls = context.classLoader.loadClass(className)
-                val constructor = cls.getConstructor(Context::class.java)
-                (constructor.newInstance(context) as SystemUIInitializer).apply {
-                    initializer = this
-                }
-            } catch (t: Throwable) {
-                Log.w(TAG, "Error creating SystemUIInitializer component: $className", t)
-                throw t
-            }
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIService.java b/packages/SystemUI/src/com/android/systemui/SystemUIService.java
index 50e0399..6cf9eff 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIService.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIService.java
@@ -30,8 +30,10 @@
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpHandler;
+import com.android.systemui.dump.LogBufferEulogizer;
 import com.android.systemui.dump.LogBufferFreezer;
 import com.android.systemui.dump.SystemUIAuxiliaryDumpService;
+import com.android.systemui.shared.system.UncaughtExceptionPreHandlerManager;
 import com.android.systemui.statusbar.policy.BatteryStateNotifier;
 
 import java.io.FileDescriptor;
@@ -44,22 +46,29 @@
     private final Handler mMainHandler;
     private final DumpHandler mDumpHandler;
     private final BroadcastDispatcher mBroadcastDispatcher;
+    private final LogBufferEulogizer mLogBufferEulogizer;
     private final LogBufferFreezer mLogBufferFreezer;
     private final BatteryStateNotifier mBatteryStateNotifier;
 
+    private final UncaughtExceptionPreHandlerManager mUncaughtExceptionPreHandlerManager;
+
     @Inject
     public SystemUIService(
             @Main Handler mainHandler,
             DumpHandler dumpHandler,
             BroadcastDispatcher broadcastDispatcher,
+            LogBufferEulogizer logBufferEulogizer,
             LogBufferFreezer logBufferFreezer,
-            BatteryStateNotifier batteryStateNotifier) {
+            BatteryStateNotifier batteryStateNotifier,
+            UncaughtExceptionPreHandlerManager uncaughtExceptionPreHandlerManager) {
         super();
         mMainHandler = mainHandler;
         mDumpHandler = dumpHandler;
         mBroadcastDispatcher = broadcastDispatcher;
+        mLogBufferEulogizer = logBufferEulogizer;
         mLogBufferFreezer = logBufferFreezer;
         mBatteryStateNotifier = batteryStateNotifier;
+        mUncaughtExceptionPreHandlerManager = uncaughtExceptionPreHandlerManager;
     }
 
     @Override
@@ -71,7 +80,10 @@
 
         // Finish initializing dump logic
         mLogBufferFreezer.attach(mBroadcastDispatcher);
-        mDumpHandler.init();
+
+        // Attempt to dump all LogBuffers for any uncaught exception
+        mUncaughtExceptionPreHandlerManager.registerHandler(
+                (thread, throwable) -> mLogBufferEulogizer.record(throwable));
 
         // If configured, set up a battery notification
         if (getResources().getBoolean(R.bool.config_showNotificationForUnknownBatteryState)) {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityLogger.kt b/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityLogger.kt
index 7b91596..9e3a778 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityLogger.kt
@@ -16,8 +16,11 @@
 
 package com.android.systemui.accessibility
 
+import com.android.internal.annotations.GuardedBy
 import com.android.internal.logging.UiEvent
 import com.android.internal.logging.UiEventLogger
+import com.android.internal.logging.UiEventLogger.UiEventEnum
+import com.android.systemui.util.time.SystemClock
 import javax.inject.Inject
 
 /**
@@ -25,16 +28,60 @@
  *
  * See go/uievent
  */
-class AccessibilityLogger @Inject constructor(private val uiEventLogger: UiEventLogger) {
+class AccessibilityLogger
+@Inject
+constructor(private val uiEventLogger: UiEventLogger, private val clock: SystemClock) {
+
+    @GuardedBy("clock") private var lastTimeThrottledMs: Long = 0
+    @GuardedBy("clock") private var lastEventThrottled: UiEventEnum? = null
+
+    /**
+     * Logs the event, but any additional calls within the given delay window are ignored. The
+     * window resets every time a new event is received. i.e. it will only log one time until you
+     * wait at least [delayBeforeLoggingMs] before sending the next event.
+     *
+     * <p>Additionally, if a different type of event is passed in, the delay window for the previous
+     * one is forgotten. e.g. if you send two types of events interlaced all within the delay
+     * window, e.g. A->B->A within 1000ms, all three will be logged.
+     */
+    @JvmOverloads fun logThrottled(event: UiEventEnum, delayBeforeLoggingMs: Int = 2000) {
+        synchronized(clock) {
+            val currentTimeMs = clock.elapsedRealtime()
+            val shouldThrottle =
+                event == lastEventThrottled &&
+                    currentTimeMs - lastTimeThrottledMs < delayBeforeLoggingMs
+            lastEventThrottled = event
+            lastTimeThrottledMs = currentTimeMs
+            if (shouldThrottle) {
+                return
+            }
+        }
+        log(event)
+    }
+
     /** Logs the given event */
-    fun log(event: UiEventLogger.UiEventEnum) {
+    fun log(event: UiEventEnum) {
         uiEventLogger.log(event)
     }
 
+    /**
+     * Logs the given event with an integer rank/position value.
+     *
+     * @param event the event to log
+     * @param position the rank or position value that the user interacted with in the UI
+     */
+    fun logWithPosition(event: UiEventEnum, position: Int) {
+        uiEventLogger.logWithPosition(event, /* uid= */ 0, /* packageName= */ null, position)
+    }
+
     /** Events regarding interaction with the magnifier settings panel */
     enum class MagnificationSettingsEvent constructor(private val id: Int) :
-        UiEventLogger.UiEventEnum {
-        @UiEvent(doc = "Magnification settings panel opened.")
+        UiEventEnum {
+        @UiEvent(
+            doc =
+                "Magnification settings panel opened. The selection rank is from which " +
+                    "magnifier mode it was opened (fullscreen or window)"
+        )
         MAGNIFICATION_SETTINGS_PANEL_OPENED(1381),
 
         @UiEvent(doc = "Magnification settings panel closed")
@@ -46,7 +93,14 @@
         @UiEvent(doc = "Magnification settings panel edit size save button clicked")
         MAGNIFICATION_SETTINGS_SIZE_EDITING_DEACTIVATED(1384),
 
-        @UiEvent(doc = "Magnification settings panel window size selected")
+        @UiEvent(doc = "Magnification settings panel zoom slider changed")
+        MAGNIFICATION_SETTINGS_ZOOM_SLIDER_CHANGED(1385),
+
+        @UiEvent(
+            doc =
+                "Magnification settings panel window size selected. The selection rank is " +
+                    "which size was selected."
+        )
         MAGNIFICATION_SETTINGS_WINDOW_SIZE_SELECTED(1386);
 
         override fun getId(): Int = this.id
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationSettingsController.java b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationSettingsController.java
index c3bb423..859e183 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationSettingsController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationSettingsController.java
@@ -87,14 +87,15 @@
     }
 
     /**
-     * Shows magnification settings panel {@link WindowMagnificationSettings}.
+     * Toggles the visibility of magnification settings panel {@link WindowMagnificationSettings}.
+     * We show the panel if it is not visible. Otherwise, hide the panel.
      */
-    void showMagnificationSettings() {
+    void toggleSettingsPanelVisibility() {
         if (!mWindowMagnificationSettings.isSettingPanelShowing()) {
             onConfigurationChanged(mContext.getResources().getConfiguration());
             mContext.registerComponentCallbacks(this);
         }
-        mWindowMagnificationSettings.showSettingPanel();
+        mWindowMagnificationSettings.toggleSettingsPanelVisibility();
     }
 
     void closeMagnificationSettings() {
@@ -106,6 +107,10 @@
         return mWindowMagnificationSettings.isSettingPanelShowing();
     }
 
+    void setMagnificationScale(float scale) {
+        mWindowMagnificationSettings.setMagnificationScale(scale);
+    }
+
     @Override
     public void onConfigurationChanged(@NonNull Configuration newConfig) {
         final int configDiff = newConfig.diff(mConfiguration);
@@ -159,8 +164,9 @@
          *
          * @param displayId The logical display id.
          * @param scale Magnification scale value.
+         * @param updatePersistence whether the new scale should be persisted.
          */
-        void onMagnifierScale(int displayId, float scale);
+        void onMagnifierScale(int displayId, float scale, boolean updatePersistence);
 
         /**
          * Called when magnification mode changed.
@@ -210,9 +216,9 @@
         }
 
         @Override
-        public void onMagnifierScale(float scale) {
+        public void onMagnifierScale(float scale, boolean updatePersistence) {
             mSettingsControllerCallback.onMagnifierScale(mDisplayId,
-                    A11Y_ACTION_SCALE_RANGE.clamp(scale));
+                    A11Y_ACTION_SCALE_RANGE.clamp(scale), updatePersistence);
         }
     };
 }
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
index 4158390..2f6a68c 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
@@ -329,7 +329,9 @@
         // binder calls
         final Optional<CentralSurfaces> centralSurfacesOptional =
                 mCentralSurfacesOptionalLazy.get();
-        if (centralSurfacesOptional.map(CentralSurfaces::isPanelExpanded).orElse(false)
+        if (centralSurfacesOptional.isPresent()
+                && centralSurfacesOptional.get().getShadeViewController() != null
+                && centralSurfacesOptional.get().getShadeViewController().isPanelExpanded()
                 && !centralSurfacesOptional.get().isKeyguardShowing()) {
             if (!mDismissNotificationShadeActionRegistered) {
                 mA11yManager.registerSystemAction(
@@ -485,13 +487,11 @@
     }
 
     private void handleNotifications() {
-        mCentralSurfacesOptionalLazy.get().ifPresent(
-                CentralSurfaces::animateExpandNotificationsPanel);
+        mShadeController.animateExpandShade();
     }
 
     private void handleQuickSettings() {
-        mCentralSurfacesOptionalLazy.get().ifPresent(
-                centralSurfaces -> centralSurfaces.animateExpandSettingsPanel(null));
+        mShadeController.animateExpandQs();
     }
 
     private void handlePowerDialog() {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/TEST_MAPPING b/packages/SystemUI/src/com/android/systemui/accessibility/TEST_MAPPING
new file mode 100644
index 0000000..055fad1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+  "presubmit-large": [
+    {
+      "name": "SystemUITests",
+      "options": [
+        {
+          "include-filter": "com.android.systemui.accessibility"
+        }
+      ]
+    }
+  ]
+}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
index e2b85fa..69041d3 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.accessibility;
 
+import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
 import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW;
 import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY;
 
@@ -28,6 +29,7 @@
 import android.graphics.Rect;
 import android.hardware.display.DisplayManager;
 import android.os.Handler;
+import android.util.SparseArray;
 import android.view.Display;
 import android.view.SurfaceControl;
 import android.view.WindowManagerGlobal;
@@ -72,6 +74,9 @@
     private WindowMagnificationConnectionImpl mWindowMagnificationConnectionImpl;
     private SysUiState mSysUiState;
 
+    @VisibleForTesting
+    SparseArray<SparseArray<Float>> mUsersScales = new SparseArray();
+
     private static class ControllerSupplier extends
             DisplayIdIndexSupplier<WindowMagnificationController> {
 
@@ -171,7 +176,7 @@
 
         mModeSwitchesController.setClickListenerDelegate(
                 displayId -> mHandler.post(() -> {
-                    showMagnificationSettingsPanel(displayId);
+                    toggleSettingsPanelVisibility(displayId);
                 }));
     }
 
@@ -254,11 +259,11 @@
     }
 
     @MainThread
-    void showMagnificationSettingsPanel(int displayId) {
+    void toggleSettingsPanelVisibility(int displayId) {
         final MagnificationSettingsController magnificationSettingsController =
                 mMagnificationSettingsSupplier.get(displayId);
         if (magnificationSettingsController != null) {
-            magnificationSettingsController.showMagnificationSettings();
+            magnificationSettingsController.toggleSettingsPanelVisibility();
         }
     }
 
@@ -295,6 +300,23 @@
         mModeSwitchesController.removeButton(displayId);
     }
 
+    @MainThread
+    void setUserMagnificationScale(int userId, int displayId, float scale) {
+        SparseArray<Float> scales = mUsersScales.get(userId);
+        if (scales == null) {
+            scales = new SparseArray<>();
+            mUsersScales.put(userId, scales);
+        }
+        if (scales.contains(displayId) && scales.get(displayId) == scale) {
+            return;
+        }
+        scales.put(displayId, scale);
+
+        final MagnificationSettingsController magnificationSettingsController =
+                mMagnificationSettingsSupplier.get(displayId);
+        magnificationSettingsController.setMagnificationScale(scale);
+    }
+
     @VisibleForTesting
     final WindowMagnifierCallback mWindowMagnifierCallback = new WindowMagnifierCallback() {
         @Override
@@ -312,9 +334,10 @@
         }
 
         @Override
-        public void onPerformScaleAction(int displayId, float scale) {
+        public void onPerformScaleAction(int displayId, float scale, boolean updatePersistence) {
             if (mWindowMagnificationConnectionImpl != null) {
-                mWindowMagnificationConnectionImpl.onPerformScaleAction(displayId, scale);
+                mWindowMagnificationConnectionImpl.onPerformScaleAction(
+                        displayId, scale, updatePersistence);
             }
         }
 
@@ -335,7 +358,7 @@
         @Override
         public void onClickSettingsButton(int displayId) {
             mHandler.post(() -> {
-                showMagnificationSettingsPanel(displayId);
+                toggleSettingsPanelVisibility(displayId);
             });
         }
     };
@@ -346,7 +369,10 @@
         @Override
         public void onSetMagnifierSize(int displayId, int index) {
             mHandler.post(() -> onSetMagnifierSizeInternal(displayId, index));
-            mA11yLogger.log(MagnificationSettingsEvent.MAGNIFICATION_SETTINGS_WINDOW_SIZE_SELECTED);
+            mA11yLogger.logWithPosition(
+                    MagnificationSettingsEvent.MAGNIFICATION_SETTINGS_WINDOW_SIZE_SELECTED,
+                    index
+            );
         }
 
         @Override
@@ -363,10 +389,14 @@
         }
 
         @Override
-        public void onMagnifierScale(int displayId, float scale) {
+        public void onMagnifierScale(int displayId, float scale, boolean updatePersistence) {
             if (mWindowMagnificationConnectionImpl != null) {
-                mWindowMagnificationConnectionImpl.onPerformScaleAction(displayId, scale);
+                mWindowMagnificationConnectionImpl.onPerformScaleAction(
+                        displayId, scale, updatePersistence);
             }
+            mA11yLogger.logThrottled(
+                    MagnificationSettingsEvent.MAGNIFICATION_SETTINGS_ZOOM_SLIDER_CHANGED
+            );
         }
 
         @Override
@@ -377,9 +407,6 @@
         @Override
         public void onSettingsPanelVisibilityChanged(int displayId, boolean shown) {
             mHandler.post(() -> onSettingsPanelVisibilityChangedInternal(displayId, shown));
-            mA11yLogger.log(shown
-                    ? MagnificationSettingsEvent.MAGNIFICATION_SETTINGS_PANEL_OPENED
-                    : MagnificationSettingsEvent.MAGNIFICATION_SETTINGS_PANEL_CLOSED);
         }
     };
 
@@ -433,8 +460,22 @@
     private void onSettingsPanelVisibilityChangedInternal(int displayId, boolean shown) {
         final WindowMagnificationController windowMagnificationController =
                 mMagnificationControllerSupplier.get(displayId);
-        if (windowMagnificationController != null && windowMagnificationController.isActivated()) {
-            windowMagnificationController.updateDragHandleResourcesIfNeeded(shown);
+        if (windowMagnificationController != null) {
+            boolean isWindowMagnifierActivated = windowMagnificationController.isActivated();
+            if (isWindowMagnifierActivated) {
+                windowMagnificationController.updateDragHandleResourcesIfNeeded(shown);
+            }
+
+            if (shown) {
+                mA11yLogger.logWithPosition(
+                        MagnificationSettingsEvent.MAGNIFICATION_SETTINGS_PANEL_OPENED,
+                        isWindowMagnifierActivated
+                                ? ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW
+                                : ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN
+                );
+            } else {
+                mA11yLogger.log(MagnificationSettingsEvent.MAGNIFICATION_SETTINGS_PANEL_CLOSED);
+            }
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationConnectionImpl.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationConnectionImpl.java
index c081893..928445b 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationConnectionImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationConnectionImpl.java
@@ -99,6 +99,12 @@
     }
 
     @Override
+    public void onUserMagnificationScaleChanged(int userId, int displayId, float scale) {
+        mHandler.post(() -> mWindowMagnification.setUserMagnificationScale(
+                userId, displayId, scale));
+    }
+
+    @Override
     public void setConnectionCallback(IWindowMagnificationConnectionCallback callback) {
         mConnectionCallback = callback;
     }
@@ -123,10 +129,10 @@
         }
     }
 
-    void onPerformScaleAction(int displayId, float scale) {
+    void onPerformScaleAction(int displayId, float scale, boolean updatePersistence) {
         if (mConnectionCallback != null) {
             try {
-                mConnectionCallback.onPerformScaleAction(displayId, scale);
+                mConnectionCallback.onPerformScaleAction(displayId, scale, updatePersistence);
             } catch (RemoteException e) {
                 Log.e(TAG, "Failed to inform performing scale action", e);
             }
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
index a67f706..602f817 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
@@ -76,6 +76,7 @@
 
 import androidx.core.math.MathUtils;
 
+import com.android.internal.accessibility.common.MagnificationConstants;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
 import com.android.systemui.R;
@@ -101,7 +102,9 @@
     // Delay to avoid updating state description too frequently.
     private static final int UPDATE_STATE_DESCRIPTION_DELAY_MS = 100;
     // It should be consistent with the value defined in WindowMagnificationGestureHandler.
-    private static final Range<Float> A11Y_ACTION_SCALE_RANGE = new Range<>(1.0f, 8.0f);
+    private static final Range<Float> A11Y_ACTION_SCALE_RANGE = new Range<>(
+            MagnificationConstants.SCALE_MIN_VALUE,
+            MagnificationConstants.SCALE_MAX_VALUE);
     private static final float A11Y_CHANGE_SCALE_DIFFERENCE = 1.0f;
     private static final float ANIMATION_BOUNCE_EFFECT_SCALE = 1.05f;
     private final SparseArray<Float> mMagnificationSizeScaleOptions = new SparseArray<>();
@@ -221,6 +224,7 @@
 
     private boolean mAllowDiagonalScrolling = false;
     private boolean mEditSizeEnable = false;
+    private boolean mSettingsPanelVisibility = false;
 
     @Nullable
     private final MirrorWindowControl mMirrorWindowControl;
@@ -1399,6 +1403,8 @@
             return;
         }
 
+        mSettingsPanelVisibility = settingsPanelIsShown;
+
         mDragView.setBackground(mContext.getResources().getDrawable(settingsPanelIsShown
                 ? R.drawable.accessibility_window_magnification_drag_handle_background_change
                 : R.drawable.accessibility_window_magnification_drag_handle_background));
@@ -1439,12 +1445,19 @@
 
     private class MirrorWindowA11yDelegate extends View.AccessibilityDelegate {
 
+        private CharSequence getClickAccessibilityActionLabel() {
+            return mSettingsPanelVisibility
+                    ? mContext.getResources().getString(
+                            R.string.magnification_close_settings_click_label)
+                    : mContext.getResources().getString(
+                            R.string.magnification_open_settings_click_label);
+        }
+
         @Override
         public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
             super.onInitializeAccessibilityNodeInfo(host, info);
             final AccessibilityAction clickAction = new AccessibilityAction(
-                    AccessibilityAction.ACTION_CLICK.getId(), mContext.getResources().getString(
-                    R.string.magnification_open_settings_click_label));
+                    AccessibilityAction.ACTION_CLICK.getId(), getClickAccessibilityActionLabel());
             info.addAction(clickAction);
             info.setClickable(true);
             info.addAction(
@@ -1478,13 +1491,9 @@
                 // Simulate tapping the drag view so it opens the Settings.
                 handleSingleTap(mDragView);
             } else if (action == R.id.accessibility_action_zoom_in) {
-                final float scale = mScale + A11Y_CHANGE_SCALE_DIFFERENCE;
-                mWindowMagnifierCallback.onPerformScaleAction(mDisplayId,
-                        A11Y_ACTION_SCALE_RANGE.clamp(scale));
+                performScale(mScale + A11Y_CHANGE_SCALE_DIFFERENCE);
             } else if (action == R.id.accessibility_action_zoom_out) {
-                final float scale = mScale - A11Y_CHANGE_SCALE_DIFFERENCE;
-                mWindowMagnifierCallback.onPerformScaleAction(mDisplayId,
-                        A11Y_ACTION_SCALE_RANGE.clamp(scale));
+                performScale(mScale - A11Y_CHANGE_SCALE_DIFFERENCE);
             } else if (action == R.id.accessibility_action_move_up) {
                 move(0, -mSourceBounds.height());
             } else if (action == R.id.accessibility_action_move_down) {
@@ -1499,5 +1508,11 @@
             mWindowMagnifierCallback.onAccessibilityActionPerformed(mDisplayId);
             return true;
         }
+
+        private void performScale(float scale) {
+            scale = A11Y_ACTION_SCALE_RANGE.clamp(scale);
+            mWindowMagnifierCallback.onPerformScaleAction(
+                    mDisplayId, scale, /* updatePersistence= */ true);
+        }
     }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
index 3b1d695..bb29d52 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
@@ -18,10 +18,12 @@
 
 import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL;
 import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
-import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_NONE;
 import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW;
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
 
+import static com.android.internal.accessibility.common.MagnificationConstants.SCALE_MAX_VALUE;
+import static com.android.internal.accessibility.common.MagnificationConstants.SCALE_MIN_VALUE;
+
 import android.annotation.IntDef;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -55,7 +57,6 @@
 import android.widget.Switch;
 import android.widget.TextView;
 
-import com.android.internal.accessibility.common.MagnificationConstants;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
 import com.android.systemui.R;
@@ -79,7 +80,8 @@
     private final Runnable mWindowInsetChangeRunnable;
     private final SfVsyncFrameCallbackProvider mSfVsyncFrameProvider;
 
-    private final LayoutParams mParams;
+    @VisibleForTesting
+    final LayoutParams mParams;
     @VisibleForTesting
     final Rect mDraggableWindowBounds = new Rect();
     private boolean mIsVisible = false;
@@ -92,7 +94,6 @@
     private Switch mAllowDiagonalScrollingSwitch;
     private LinearLayout mPanelView;
     private LinearLayout mSettingView;
-    private LinearLayout mButtonView;
     private ImageButton mSmallButton;
     private ImageButton mMediumButton;
     private ImageButton mLargeButton;
@@ -101,8 +102,13 @@
     private ImageButton mFullScreenButton;
     private int mLastSelectedButtonIndex = MagnificationSize.NONE;
     private boolean mAllowDiagonalScrolling = false;
-    private static final float A11Y_CHANGE_SCALE_DIFFERENCE = 1.0f;
-    private static final float A11Y_SCALE_MIN_VALUE = 1.0f;
+    /**
+     * Amount by which magnification scale changes compared to seekbar in settings.
+     * magnitude = 10 means, for every 1 scale increase, 10 progress increase in seekbar.
+     */
+    private int mSeekBarMagnitude;
+    private float mScale = SCALE_MIN_VALUE;
+
     private WindowMagnificationSettingsCallback mCallback;
 
     private ContentObserver mMagnificationCapabilityObserver;
@@ -157,19 +163,18 @@
         };
     }
 
-    private class ZoomSeekbarChangeListener implements SeekBar.OnSeekBarChangeListener {
+    private class ZoomSeekbarChangeListener implements
+            SeekBarWithIconButtonsView.OnSeekBarWithIconButtonsChangeListener {
         @Override
         public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
-            float scale = progress * A11Y_CHANGE_SCALE_DIFFERENCE + A11Y_SCALE_MIN_VALUE;
-            // Update persisted scale only when scale >= PERSISTED_SCALE_MIN_VALUE const.
-            // We assume if the scale is lower than the PERSISTED_SCALE_MIN_VALUE, there will be
-            // no obvious magnification effect.
-            if (scale >= MagnificationConstants.PERSISTED_SCALE_MIN_VALUE) {
-                Settings.Secure.putFloatForUser(mContext.getContentResolver(),
-                        Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, scale,
-                        UserHandle.USER_CURRENT);
+            // Notify the service to update the magnifier scale only when the progress changed is
+            // triggered by user interaction on seekbar
+            if (fromUser) {
+                final float scale = transformProgressToScale(progress);
+                // We don't need to update the persisted scale when the seekbar progress is
+                // changing. The update should be triggered when the changing is ended.
+                mCallback.onMagnifierScale(scale, /* updatePersistence= */ false);
             }
-            mCallback.onMagnifierScale(scale);
         }
 
         @Override
@@ -181,6 +186,18 @@
         public void onStopTrackingTouch(SeekBar seekBar) {
             // Do nothing
         }
+
+        @Override
+        public void onUserInteractionFinalized(SeekBar seekBar, @ControlUnitType int control) {
+            // Update the Settings persisted scale only when user interaction with seekbar ends
+            final int progress = seekBar.getProgress();
+            final float scale = transformProgressToScale(progress);
+            mCallback.onMagnifierScale(scale, /* updatePersistence= */ true);
+        }
+
+        private float transformProgressToScale(float progress) {
+            return (progress / (float) mSeekBarMagnitude) + SCALE_MIN_VALUE;
+        }
     }
 
     private final AccessibilityDelegate mPanelDelegate = new AccessibilityDelegate() {
@@ -311,6 +328,14 @@
         mCallback.onSettingsPanelVisibilityChanged(/* shown= */ false);
     }
 
+    public void toggleSettingsPanelVisibility() {
+        if (!mIsVisible) {
+            showSettingPanel();
+        } else {
+            hideSettingPanel();
+        }
+    }
+
     public void showSettingPanel() {
         showSettingPanel(true);
     }
@@ -320,7 +345,13 @@
     }
 
     public void setScaleSeekbar(float scale) {
-        setSeekbarProgress(scale);
+        int index = (int) ((scale - SCALE_MIN_VALUE) * mSeekBarMagnitude);
+        if (index < 0) {
+            index = 0;
+        } else if (index > mZoomSeekbar.getMax()) {
+            index = mZoomSeekbar.getMax();
+        }
+        mZoomSeekbar.setProgress(index);
     }
 
     private void transitToMagnificationMode(int mode) {
@@ -337,6 +368,7 @@
     private void showSettingPanel(boolean resetPosition) {
         if (!mIsVisible) {
             updateUIControlsIfNeeded();
+            setScaleSeekbar(getMagnificationScale());
             if (resetPosition) {
                 mDraggableWindowBounds.set(getDraggableWindowBounds());
                 mParams.x = mDraggableWindowBounds.right;
@@ -368,22 +400,45 @@
     }
 
     private int getMagnificationMode() {
+        // If current capability is window mode, we would like the default value of the mode to
+        // be WINDOW, otherwise, the default value would be FULLSCREEN.
+        int defaultValue =
+                (getMagnificationCapability() == ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW)
+                        ? ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW
+                        : ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
+
         return mSecureSettings.getIntForUser(
                 Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE,
-                ACCESSIBILITY_MAGNIFICATION_MODE_NONE,
+                defaultValue,
                 UserHandle.USER_CURRENT);
     }
 
     private int getMagnificationCapability() {
         return mSecureSettings.getIntForUser(
                 Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CAPABILITY,
-                ACCESSIBILITY_MAGNIFICATION_MODE_NONE,
+                ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN,
                 UserHandle.USER_CURRENT);
     }
 
+    /**
+     * Only called from outside to notify the controlling magnifier scale changed
+     *
+     * @param scale The new controlling magnifier scale
+     */
+    public void setMagnificationScale(float scale) {
+        mScale = scale;
+
+        if (isSettingPanelShowing()) {
+            setScaleSeekbar(scale);
+        }
+    }
+
+    private float getMagnificationScale() {
+        return mScale;
+    }
+
     private void updateUIControlsIfNeeded() {
         int capability = getMagnificationCapability();
-
         int selectedButtonIndex = mLastSelectedButtonIndex;
         switch (capability) {
             case ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW:
@@ -444,14 +499,6 @@
         }
     };
 
-    private void setSeekbarProgress(float scale) {
-        int index = (int) ((scale - A11Y_SCALE_MIN_VALUE) / A11Y_CHANGE_SCALE_DIFFERENCE);
-        if (index < 0) {
-            index = 0;
-        }
-        mZoomSeekbar.setProgress(index);
-    }
-
     void inflateView() {
         mSettingView = (LinearLayout) View.inflate(mContext,
                 R.layout.window_magnification_settings_view, null);
@@ -473,11 +520,11 @@
                 mSettingView.findViewById(R.id.magnifier_horizontal_lock_title);
 
         mZoomSeekbar = mSettingView.findViewById(R.id.magnifier_zoom_slider);
-        float scale = mSecureSettings.getFloatForUser(
-                Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, 0,
-                UserHandle.USER_CURRENT);
-        setSeekbarProgress(scale);
-        mZoomSeekbar.setOnSeekBarChangeListener(new ZoomSeekbarChangeListener());
+        mZoomSeekbar.setMax((int) (mZoomSeekbar.getChangeMagnitude()
+                * (SCALE_MAX_VALUE - SCALE_MIN_VALUE)));
+        mSeekBarMagnitude = mZoomSeekbar.getChangeMagnitude();
+        setScaleSeekbar(mScale);
+        mZoomSeekbar.setOnSeekBarWithIconButtonsChangeListener(new ZoomSeekbarChangeListener());
 
         mAllowDiagonalScrollingView =
                 (LinearLayout) mSettingView.findViewById(R.id.magnifier_horizontal_lock_view);
@@ -521,7 +568,6 @@
             // CONFIG_FONT_SCALE: font size change
             // CONFIG_LOCALE: language change
             // CONFIG_DENSITY: display size change
-
             mParams.accessibilityTitle = getAccessibilityWindowTitle(mContext);
 
             boolean showSettingPanelAfterConfigChange = mIsVisible;
@@ -533,16 +579,13 @@
             return;
         }
 
-        if ((configDiff & ActivityInfo.CONFIG_ORIENTATION) != 0) {
-            final Rect previousDraggableBounds = new Rect(mDraggableWindowBounds);
+        if ((configDiff & ActivityInfo.CONFIG_ORIENTATION) != 0
+                || (configDiff & ActivityInfo.CONFIG_SCREEN_SIZE) != 0) {
             mDraggableWindowBounds.set(getDraggableWindowBounds());
-            // Keep the Y position with the same height ratio before the window bounds and
-            // draggable bounds are changed.
-            final float windowHeightFraction = (float) (mParams.y - previousDraggableBounds.top)
-                    / previousDraggableBounds.height();
-            mParams.y = (int) (windowHeightFraction * mDraggableWindowBounds.height())
-                    + mDraggableWindowBounds.top;
-            return;
+            // reset the panel position to the right-bottom corner
+            mParams.x = mDraggableWindowBounds.right;
+            mParams.y = mDraggableWindowBounds.bottom;
+            updateButtonViewLayoutIfNeeded();
         }
     }
 
@@ -554,7 +597,8 @@
         mDraggableWindowBounds.set(newBounds);
     }
 
-    private void updateButtonViewLayoutIfNeeded() {
+    @VisibleForTesting
+    void updateButtonViewLayoutIfNeeded() {
         if (mIsVisible) {
             mParams.x = MathUtils.constrain(mParams.x, mDraggableWindowBounds.left,
                     mDraggableWindowBounds.right);
@@ -595,7 +639,7 @@
 
     @VisibleForTesting
     void setDiagonalScrolling(boolean enabled) {
-        Settings.Secure.putIntForUser(mContext.getContentResolver(),
+        mSecureSettings.putIntForUser(
                 Settings.Secure.ACCESSIBILITY_ALLOW_DIAGONAL_SCROLLING, enabled ? 1 : 0,
                 UserHandle.USER_CURRENT);
 
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettingsCallback.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettingsCallback.java
index 3dbff5d..2eee7a6 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettingsCallback.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettingsCallback.java
@@ -52,8 +52,9 @@
      * Called when set magnification scale.
      *
      * @param scale Magnification scale value.
+     * @param updatePersistence whether the scale should be persisted
      */
-    void onMagnifierScale(float scale);
+    void onMagnifierScale(float scale, boolean updatePersistence);
 
     /**
      * Called when magnification mode changed.
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnifierCallback.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnifierCallback.java
index e18161d..a25e9a2 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnifierCallback.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnifierCallback.java
@@ -44,8 +44,9 @@
      *
      * @param displayId The logical display id.
      * @param scale the target scale, or {@link Float#NaN} to leave unchanged
+     * @param updatePersistence whether the scale should be persisted
      */
-    void onPerformScaleAction(int displayId, float scale);
+    void onPerformScaleAction(int displayId, float scale, boolean updatePersistence);
 
     /**
      * Called when the accessibility action is performed.
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationController.java
index 7f4e7844..225a2f7 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationController.java
@@ -25,7 +25,7 @@
 import androidx.dynamicanimation.animation.DynamicAnimation;
 
 import com.android.systemui.R;
-import com.android.wm.shell.bubbles.DismissView;
+import com.android.wm.shell.common.bubbles.DismissView;
 import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
 
 /**
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
index 3f41a76..6ba40d6 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
@@ -61,7 +61,8 @@
 import com.android.internal.util.Preconditions;
 import com.android.systemui.R;
 import com.android.systemui.util.settings.SecureSettings;
-import com.android.wm.shell.bubbles.DismissView;
+import com.android.wm.shell.bubbles.DismissViewUtils;
+import com.android.wm.shell.common.bubbles.DismissView;
 import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
 
 import java.lang.annotation.Retention;
@@ -184,6 +185,7 @@
         mMenuAnimationController.setDismissCallback(this::hideMenuAndShowMessage);
         mMenuAnimationController.setSpringAnimationsEndAction(this::onSpringAnimationsEndAction);
         mDismissView = new DismissView(context);
+        DismissViewUtils.setup(mDismissView);
         mDismissAnimationController = new DismissAnimationController(mDismissView, mMenuView);
         mDismissAnimationController.setMagnetListener(new MagnetizedObject.MagnetListener() {
             @Override
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/fontscaling/FontScalingDialog.kt b/packages/SystemUI/src/com/android/systemui/accessibility/fontscaling/FontScalingDialog.kt
index e79b3f4..783460c 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/fontscaling/FontScalingDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/fontscaling/FontScalingDialog.kt
@@ -26,14 +26,16 @@
 import android.view.LayoutInflater
 import android.widget.Button
 import android.widget.SeekBar
-import android.widget.SeekBar.OnSeekBarChangeListener
 import android.widget.TextView
 import androidx.annotation.MainThread
 import androidx.annotation.WorkerThread
 import com.android.systemui.R
 import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView
+import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView.OnSeekBarWithIconButtonsChangeListener
+import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView.OnSeekBarWithIconButtonsChangeListener.ControlUnitType
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.phone.SystemUIDialog
 import com.android.systemui.util.concurrency.DelayableExecutor
 import com.android.systemui.util.settings.SecureSettings
@@ -48,6 +50,7 @@
     private val systemSettings: SystemSettings,
     private val secureSettings: SecureSettings,
     private val systemClock: SystemClock,
+    private val userTracker: UserTracker,
     @Main mainHandler: Handler,
     @Background private val backgroundDelayableExecutor: DelayableExecutor
 ) : SystemUIDialog(context) {
@@ -98,32 +101,37 @@
 
         seekBarWithIconButtonsView.setMax((strEntryValues).size - 1)
 
-        val currentScale = systemSettings.getFloat(Settings.System.FONT_SCALE, 1.0f)
+        val currentScale =
+            systemSettings.getFloatForUser(Settings.System.FONT_SCALE, 1.0f, userTracker.userId)
         lastProgress.set(fontSizeValueToIndex(currentScale))
         seekBarWithIconButtonsView.setProgress(lastProgress.get())
 
-        seekBarWithIconButtonsView.setOnSeekBarChangeListener(
-            object : OnSeekBarChangeListener {
-                var isTrackingTouch = false
-
+        seekBarWithIconButtonsView.setOnSeekBarWithIconButtonsChangeListener(
+            object : OnSeekBarWithIconButtonsChangeListener {
                 override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
                     // Always provide preview configuration for text first when there is a change
                     // in the seekbar progress.
                     createTextPreview(progress)
-
-                    if (!isTrackingTouch) {
-                        // The seekbar progress is changed by icon buttons
-                        changeFontSize(progress, CHANGE_BY_BUTTON_DELAY_MS)
-                    }
                 }
 
                 override fun onStartTrackingTouch(seekBar: SeekBar) {
-                    isTrackingTouch = true
+                    // Do nothing
                 }
 
                 override fun onStopTrackingTouch(seekBar: SeekBar) {
-                    isTrackingTouch = false
-                    changeFontSize(seekBar.progress, CHANGE_BY_SEEKBAR_DELAY_MS)
+                    // Do nothing
+                }
+
+                override fun onUserInteractionFinalized(
+                    seekBar: SeekBar,
+                    @ControlUnitType control: Int
+                ) {
+                    if (control == ControlUnitType.BUTTON) {
+                        // The seekbar progress is changed by icon buttons
+                        changeFontSize(seekBar.progress, CHANGE_BY_BUTTON_DELAY_MS)
+                    } else {
+                        changeFontSize(seekBar.progress, CHANGE_BY_SEEKBAR_DELAY_MS)
+                    }
                 }
             }
         )
@@ -195,18 +203,25 @@
 
     @WorkerThread
     fun updateFontScale() {
-        systemSettings.putString(Settings.System.FONT_SCALE, strEntryValues[lastProgress.get()])
+        systemSettings.putStringForUser(
+            Settings.System.FONT_SCALE,
+            strEntryValues[lastProgress.get()],
+            userTracker.userId
+        )
     }
 
     @WorkerThread
     fun updateSecureSettingsIfNeeded() {
         if (
-            secureSettings.getString(Settings.Secure.ACCESSIBILITY_FONT_SCALING_HAS_BEEN_CHANGED) !=
-                ON
-        ) {
-            secureSettings.putString(
+            secureSettings.getStringForUser(
                 Settings.Secure.ACCESSIBILITY_FONT_SCALING_HAS_BEEN_CHANGED,
-                ON
+                userTracker.userId
+            ) != ON
+        ) {
+            secureSettings.putStringForUser(
+                Settings.Secure.ACCESSIBILITY_FONT_SCALING_HAS_BEEN_CHANGED,
+                ON,
+                userTracker.userId
             )
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
index 665a398..2b83e6b 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
@@ -105,6 +105,8 @@
             AssistUtils.INVOCATION_TYPE_HOME_BUTTON_LONG_PRESS;
     public static final int INVOCATION_TYPE_POWER_BUTTON_LONG_PRESS =
             AssistUtils.INVOCATION_TYPE_POWER_BUTTON_LONG_PRESS;
+    public static final int INVOCATION_TYPE_NAV_HANDLE_LONG_PRESS =
+            AssistUtils.INVOCATION_TYPE_NAV_HANDLE_LONG_PRESS;
 
     public static final int DISMISS_REASON_INVOCATION_CANCELLED = 1;
     public static final int DISMISS_REASON_TAP = 2;
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/AuthenticationModule.kt b/packages/SystemUI/src/com/android/systemui/authentication/AuthenticationModule.kt
index 7c394a6..6128d91 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/AuthenticationModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/AuthenticationModule.kt
@@ -16,8 +16,11 @@
 
 package com.android.systemui.authentication
 
+import com.android.keyguard.KeyguardSecurityModel
 import com.android.systemui.authentication.data.repository.AuthenticationRepositoryModule
 import dagger.Module
+import dagger.Provides
+import java.util.function.Function
 
 @Module(
     includes =
@@ -25,4 +28,12 @@
             AuthenticationRepositoryModule::class,
         ],
 )
-object AuthenticationModule
+object AuthenticationModule {
+
+    @Provides
+    fun getSecurityMode(
+        model: KeyguardSecurityModel,
+    ): Function<Int, KeyguardSecurityModel.SecurityMode> {
+        return Function { userId -> model.getSecurityMode(userId) }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
index c684dc5..a977966 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
@@ -14,15 +14,41 @@
  * limitations under the License.
  */
 
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
 package com.android.systemui.authentication.data.repository
 
+import com.android.internal.widget.LockPatternChecker
+import com.android.internal.widget.LockPatternUtils
+import com.android.internal.widget.LockscreenCredential
+import com.android.keyguard.KeyguardSecurityModel
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.authentication.shared.model.AuthenticationResultModel
+import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import com.android.systemui.user.data.repository.UserRepository
+import com.android.systemui.util.time.SystemClock
 import dagger.Binds
 import dagger.Module
+import java.util.function.Function
 import javax.inject.Inject
+import kotlin.coroutines.resume
+import kotlin.coroutines.suspendCoroutine
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.withContext
 
 /** Defines interface for classes that can access authentication-related application state. */
 interface AuthenticationRepository {
@@ -38,70 +64,217 @@
     val isUnlocked: StateFlow<Boolean>
 
     /**
-     * The currently-configured authentication method. This determines how the authentication
-     * challenge is completed in order to unlock an otherwise locked device.
+     * Whether the auto confirm feature is enabled for the currently-selected user.
+     *
+     * Note that the length of the PIN is also important to take into consideration, please see
+     * [hintedPinLength].
      */
-    val authenticationMethod: StateFlow<AuthenticationMethodModel>
+    val isAutoConfirmEnabled: StateFlow<Boolean>
 
     /**
-     * Whether lock screen bypass is enabled. When enabled, the lock screen will be automatically
-     * dismisses once the authentication challenge is completed. For example, completing a biometric
-     * authentication challenge via face unlock or fingerprint sensor can automatically bypass the
-     * lock screen.
+     * The exact length a PIN should be for us to enable PIN length hinting.
+     *
+     * A PIN that's shorter or longer than this is not eligible for the UI to render hints showing
+     * how many digits the current PIN is, even if [isAutoConfirmEnabled] is enabled.
+     *
+     * Note that PIN length hinting is only available if the PIN auto confirmation feature is
+     * available.
      */
-    val isBypassEnabled: StateFlow<Boolean>
+    val hintedPinLength: Int
+
+    /** Whether the pattern should be visible for the currently-selected user. */
+    val isPatternVisible: StateFlow<Boolean>
+
+    /** The current throttling state, as cached via [setThrottling]. */
+    val throttling: StateFlow<AuthenticationThrottlingModel>
 
     /**
-     * Number of consecutively failed authentication attempts. This resets to `0` when
-     * authentication succeeds.
+     * Returns the currently-configured authentication method. This determines how the
+     * authentication challenge is completed in order to unlock an otherwise locked device.
      */
-    val failedAuthenticationAttempts: StateFlow<Int>
+    suspend fun getAuthenticationMethod(): AuthenticationMethodModel
 
-    /** See [isUnlocked]. */
-    fun setUnlocked(isUnlocked: Boolean)
+    /** Returns the length of the PIN or `0` if the current auth method is not PIN. */
+    suspend fun getPinLength(): Int
 
-    /** See [authenticationMethod]. */
-    fun setAuthenticationMethod(authenticationMethod: AuthenticationMethodModel)
+    /**
+     * Returns whether the lockscreen is enabled.
+     *
+     * When the lockscreen is not enabled, it shouldn't show in cases when the authentication method
+     * is considered not secure (for example, "swipe" is considered to be "none").
+     */
+    suspend fun isLockscreenEnabled(): Boolean
 
-    /** See [isBypassEnabled]. */
-    fun setBypassEnabled(isBypassEnabled: Boolean)
+    /** Reports an authentication attempt. */
+    suspend fun reportAuthenticationAttempt(isSuccessful: Boolean)
 
-    /** See [failedAuthenticationAttempts]. */
-    fun setFailedAuthenticationAttempts(failedAuthenticationAttempts: Int)
+    /** Returns the current number of failed authentication attempts. */
+    suspend fun getFailedAuthenticationAttemptCount(): Int
+
+    /**
+     * Returns the timestamp for when the current throttling will end, allowing the user to attempt
+     * authentication again.
+     *
+     * Note that this is in milliseconds and it matches [SystemClock.elapsedRealtime].
+     */
+    suspend fun getThrottlingEndTimestamp(): Long
+
+    /** Sets the cached throttling state, updating the [throttling] flow. */
+    fun setThrottling(throttlingModel: AuthenticationThrottlingModel)
+
+    /**
+     * Sets the throttling timeout duration (time during which the user should not be allowed to
+     * attempt authentication).
+     */
+    suspend fun setThrottleDuration(durationMs: Int)
+
+    /**
+     * Checks the given [LockscreenCredential] to see if it's correct, returning an
+     * [AuthenticationResultModel] representing what happened.
+     */
+    suspend fun checkCredential(credential: LockscreenCredential): AuthenticationResultModel
 }
 
-class AuthenticationRepositoryImpl @Inject constructor() : AuthenticationRepository {
-    // TODO(b/280883900): get data from real data sources in SysUI.
+class AuthenticationRepositoryImpl
+@Inject
+constructor(
+    @Application private val applicationScope: CoroutineScope,
+    private val getSecurityMode: Function<Int, KeyguardSecurityModel.SecurityMode>,
+    @Background private val backgroundDispatcher: CoroutineDispatcher,
+    private val userRepository: UserRepository,
+    keyguardRepository: KeyguardRepository,
+    private val lockPatternUtils: LockPatternUtils,
+) : AuthenticationRepository {
 
-    private val _isUnlocked = MutableStateFlow(false)
-    override val isUnlocked: StateFlow<Boolean> = _isUnlocked.asStateFlow()
+    override val isUnlocked = keyguardRepository.isKeyguardUnlocked
 
-    private val _authenticationMethod =
-        MutableStateFlow<AuthenticationMethodModel>(AuthenticationMethodModel.PIN(1234))
-    override val authenticationMethod: StateFlow<AuthenticationMethodModel> =
-        _authenticationMethod.asStateFlow()
-
-    private val _isBypassEnabled = MutableStateFlow(false)
-    override val isBypassEnabled: StateFlow<Boolean> = _isBypassEnabled.asStateFlow()
-
-    private val _failedAuthenticationAttempts = MutableStateFlow(0)
-    override val failedAuthenticationAttempts: StateFlow<Int> =
-        _failedAuthenticationAttempts.asStateFlow()
-
-    override fun setUnlocked(isUnlocked: Boolean) {
-        _isUnlocked.value = isUnlocked
+    override suspend fun isLockscreenEnabled(): Boolean {
+        return withContext(backgroundDispatcher) {
+            val selectedUserId = userRepository.selectedUserId
+            !lockPatternUtils.isLockPatternEnabled(selectedUserId)
+        }
     }
 
-    override fun setBypassEnabled(isBypassEnabled: Boolean) {
-        _isBypassEnabled.value = isBypassEnabled
+    override val isAutoConfirmEnabled: StateFlow<Boolean> =
+        userRepository.selectedUserInfo
+            .map { it.id }
+            .flatMapLatest { userId ->
+                flow { emit(lockPatternUtils.isAutoPinConfirmEnabled(userId)) }
+                    .flowOn(backgroundDispatcher)
+            }
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = false,
+            )
+
+    override val hintedPinLength: Int = 6
+
+    override val isPatternVisible: StateFlow<Boolean> =
+        userRepository.selectedUserInfo
+            .map { it.id }
+            .flatMapLatest { userId ->
+                flow { emit(lockPatternUtils.isVisiblePatternEnabled(userId)) }
+                    .flowOn(backgroundDispatcher)
+            }
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = true,
+            )
+
+    private val _throttling = MutableStateFlow(AuthenticationThrottlingModel())
+    override val throttling: StateFlow<AuthenticationThrottlingModel> = _throttling.asStateFlow()
+
+    private val UserRepository.selectedUserId: Int
+        get() = getSelectedUserInfo().id
+
+    override suspend fun getAuthenticationMethod(): AuthenticationMethodModel {
+        return withContext(backgroundDispatcher) {
+            val selectedUserId = userRepository.selectedUserId
+            when (getSecurityMode.apply(selectedUserId)) {
+                KeyguardSecurityModel.SecurityMode.PIN,
+                KeyguardSecurityModel.SecurityMode.SimPin,
+                KeyguardSecurityModel.SecurityMode.SimPuk -> AuthenticationMethodModel.Pin
+                KeyguardSecurityModel.SecurityMode.Password -> AuthenticationMethodModel.Password
+                KeyguardSecurityModel.SecurityMode.Pattern -> AuthenticationMethodModel.Pattern
+                KeyguardSecurityModel.SecurityMode.None -> AuthenticationMethodModel.None
+                KeyguardSecurityModel.SecurityMode.Invalid -> error("Invalid security mode!")
+            }
+        }
     }
 
-    override fun setAuthenticationMethod(authenticationMethod: AuthenticationMethodModel) {
-        _authenticationMethod.value = authenticationMethod
+    override suspend fun getPinLength(): Int {
+        return withContext(backgroundDispatcher) {
+            val selectedUserId = userRepository.selectedUserId
+            lockPatternUtils.getPinLength(selectedUserId)
+        }
     }
 
-    override fun setFailedAuthenticationAttempts(failedAuthenticationAttempts: Int) {
-        _failedAuthenticationAttempts.value = failedAuthenticationAttempts
+    override suspend fun reportAuthenticationAttempt(isSuccessful: Boolean) {
+        val selectedUserId = userRepository.selectedUserId
+        withContext(backgroundDispatcher) {
+            if (isSuccessful) {
+                lockPatternUtils.reportSuccessfulPasswordAttempt(selectedUserId)
+            } else {
+                lockPatternUtils.reportFailedPasswordAttempt(selectedUserId)
+            }
+        }
+    }
+
+    override suspend fun getFailedAuthenticationAttemptCount(): Int {
+        return withContext(backgroundDispatcher) {
+            val selectedUserId = userRepository.selectedUserId
+            lockPatternUtils.getCurrentFailedPasswordAttempts(selectedUserId)
+        }
+    }
+
+    override suspend fun getThrottlingEndTimestamp(): Long {
+        return withContext(backgroundDispatcher) {
+            val selectedUserId = userRepository.selectedUserId
+            lockPatternUtils.getLockoutAttemptDeadline(selectedUserId)
+        }
+    }
+
+    override fun setThrottling(throttlingModel: AuthenticationThrottlingModel) {
+        _throttling.value = throttlingModel
+    }
+
+    override suspend fun setThrottleDuration(durationMs: Int) {
+        withContext(backgroundDispatcher) {
+            lockPatternUtils.setLockoutAttemptDeadline(
+                userRepository.selectedUserId,
+                durationMs,
+            )
+        }
+    }
+
+    override suspend fun checkCredential(
+        credential: LockscreenCredential
+    ): AuthenticationResultModel {
+        return suspendCoroutine { continuation ->
+            LockPatternChecker.checkCredential(
+                lockPatternUtils,
+                credential,
+                userRepository.selectedUserId,
+                object : LockPatternChecker.OnCheckCallback {
+                    override fun onChecked(matched: Boolean, throttleTimeoutMs: Int) {
+                        continuation.resume(
+                            AuthenticationResultModel(
+                                isSuccessful = matched,
+                                throttleDurationMs = throttleTimeoutMs,
+                            )
+                        )
+                    }
+
+                    override fun onCancelled() {
+                        continuation.resume(AuthenticationResultModel(isSuccessful = false))
+                    }
+
+                    override fun onEarlyMatched() = Unit
+                }
+            )
+        }
     }
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
index 3984627..b482977 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
@@ -16,33 +16,46 @@
 
 package com.android.systemui.authentication.domain.interactor
 
+import com.android.internal.widget.LockPatternView
+import com.android.internal.widget.LockscreenCredential
 import com.android.systemui.authentication.data.repository.AuthenticationRepository
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import com.android.systemui.user.data.repository.UserRepository
+import com.android.systemui.util.time.SystemClock
 import javax.inject.Inject
+import kotlin.math.max
+import kotlin.time.Duration.Companion.seconds
+import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.async
+import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
 
 /** Hosts application business logic related to authentication. */
 @SysUISingleton
 class AuthenticationInteractor
 @Inject
 constructor(
-    @Application applicationScope: CoroutineScope,
+    @Application private val applicationScope: CoroutineScope,
     private val repository: AuthenticationRepository,
+    @Background private val backgroundDispatcher: CoroutineDispatcher,
+    private val userRepository: UserRepository,
+    private val keyguardRepository: KeyguardRepository,
+    private val clock: SystemClock,
 ) {
     /**
-     * The currently-configured authentication method. This determines how the authentication
-     * challenge is completed in order to unlock an otherwise locked device.
-     */
-    val authenticationMethod: StateFlow<AuthenticationMethodModel> = repository.authenticationMethod
-
-    /**
      * Whether the device is unlocked.
      *
      * A device that is not yet unlocked requires unlocking by completing an authentication
@@ -51,46 +64,80 @@
      * Note that this state has no real bearing on whether the lock screen is showing or dismissed.
      */
     val isUnlocked: StateFlow<Boolean> =
-        combine(authenticationMethod, repository.isUnlocked) { authMethod, isUnlocked ->
-                isUnlockedWithAuthMethod(
-                    isUnlocked = isUnlocked,
-                    authMethod = authMethod,
-                )
+        repository.isUnlocked
+            .map { isUnlocked ->
+                if (getAuthenticationMethod() is AuthenticationMethodModel.None) {
+                    true
+                } else {
+                    isUnlocked
+                }
             }
             .stateIn(
                 scope = applicationScope,
                 started = SharingStarted.Eagerly,
-                initialValue =
-                    isUnlockedWithAuthMethod(
-                        isUnlocked = repository.isUnlocked.value,
-                        authMethod = repository.authenticationMethod.value,
-                    )
+                initialValue = true,
             )
 
-    /**
-     * Whether lock screen bypass is enabled. When enabled, the lock screen will be automatically
-     * dismisses once the authentication challenge is completed. For example, completing a biometric
-     * authentication challenge via face unlock or fingerprint sensor can automatically bypass the
-     * lock screen.
-     */
-    val isBypassEnabled: StateFlow<Boolean> = repository.isBypassEnabled
+    /** The current authentication throttling state, only meaningful if [isThrottled] is `true`. */
+    val throttling: StateFlow<AuthenticationThrottlingModel> = repository.throttling
 
     /**
-     * Number of consecutively failed authentication attempts. This resets to `0` when
-     * authentication succeeds.
+     * Whether currently throttled and the user has to wait before being able to try another
+     * authentication attempt.
      */
-    val failedAuthenticationAttempts: StateFlow<Int> = repository.failedAuthenticationAttempts
+    val isThrottled: StateFlow<Boolean> =
+        throttling
+            .map { it.remainingMs > 0 }
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.Eagerly,
+                initialValue = throttling.value.remainingMs > 0,
+            )
 
-    init {
-        // UNLOCKS WHEN AUTH METHOD REMOVED.
-        //
-        // Unlocks the device if the auth method becomes None.
-        applicationScope.launch {
-            repository.authenticationMethod.collect {
-                if (it is AuthenticationMethodModel.None) {
-                    unlockDevice()
+    /** The length of the hinted PIN, or `null` if pin length hint should not be shown. */
+    val hintedPinLength: StateFlow<Int?> =
+        repository.isAutoConfirmEnabled
+            .map { isAutoConfirmEnabled ->
+                repository.getPinLength().takeIf {
+                    isAutoConfirmEnabled && it == repository.hintedPinLength
                 }
             }
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.Eagerly,
+                initialValue = null,
+            )
+
+    /** Whether the auto confirm feature is enabled for the currently-selected user. */
+    val isAutoConfirmEnabled: StateFlow<Boolean> = repository.isAutoConfirmEnabled
+
+    /** Whether the pattern should be visible for the currently-selected user. */
+    val isPatternVisible: StateFlow<Boolean> = repository.isPatternVisible
+
+    private var throttlingCountdownJob: Job? = null
+
+    init {
+        applicationScope.launch {
+            userRepository.selectedUserInfo
+                .map { it.id }
+                .distinctUntilChanged()
+                .collect { onSelectedUserChanged() }
+        }
+    }
+
+    /**
+     * Returns the currently-configured authentication method. This determines how the
+     * authentication challenge is completed in order to unlock an otherwise locked device.
+     */
+    suspend fun getAuthenticationMethod(): AuthenticationMethodModel {
+        val authMethod = repository.getAuthenticationMethod()
+        return if (
+            authMethod is AuthenticationMethodModel.None && repository.isLockscreenEnabled()
+        ) {
+            // We treat "None" as "Swipe" when the lockscreen is enabled.
+            AuthenticationMethodModel.Swipe
+        } else {
+            authMethod
         }
     }
 
@@ -98,118 +145,144 @@
      * Returns `true` if the device currently requires authentication before content can be viewed;
      * `false` if content can be displayed without unlocking first.
      */
-    fun isAuthenticationRequired(): Boolean {
-        return !isUnlocked.value && authenticationMethod.value.isSecure
+    suspend fun isAuthenticationRequired(): Boolean {
+        return !isUnlocked.value && getAuthenticationMethod().isSecure
     }
 
     /**
-     * Unlocks the device, assuming that the authentication challenge has been completed
-     * successfully.
+     * Whether lock screen bypass is enabled. When enabled, the lock screen will be automatically
+     * dismisses once the authentication challenge is completed. For example, completing a biometric
+     * authentication challenge via face unlock or fingerprint sensor can automatically bypass the
+     * lock screen.
      */
-    fun unlockDevice() {
-        repository.setUnlocked(true)
-    }
-
-    /**
-     * Locks the device. From now on, the device will remain locked until [authenticate] is called
-     * with the correct input.
-     */
-    fun lockDevice() {
-        repository.setUnlocked(false)
+    fun isBypassEnabled(): Boolean {
+        return keyguardRepository.isBypassEnabled()
     }
 
     /**
      * Attempts to authenticate the user and unlock the device.
      *
+     * If [tryAutoConfirm] is `true`, authentication is attempted if and only if the auth method
+     * supports auto-confirming, and the input's length is at least the code's length. Otherwise,
+     * `null` is returned.
+     *
      * @param input The input from the user to try to authenticate with. This can be a list of
      *   different things, based on the current authentication method.
-     * @return `true` if the authentication succeeded and the device is now unlocked; `false`
-     *   otherwise.
+     * @param tryAutoConfirm `true` if called while the user inputs the code, without an explicit
+     *   request to validate.
+     * @return `true` if the authentication succeeded and the device is now unlocked; `false` when
+     *   authentication failed, `null` if the check was not performed.
      */
-    fun authenticate(input: List<Any>): Boolean {
-        val isSuccessful =
-            when (val authMethod = this.authenticationMethod.value) {
-                is AuthenticationMethodModel.PIN -> input.asCode() == authMethod.code
-                is AuthenticationMethodModel.Password -> input.asPassword() == authMethod.password
-                is AuthenticationMethodModel.Pattern -> input.asPattern() == authMethod.coordinates
-                else -> true
-            }
+    suspend fun authenticate(input: List<Any>, tryAutoConfirm: Boolean = false): Boolean? {
+        if (input.isEmpty()) {
+            throw IllegalArgumentException("Input was empty!")
+        }
 
-        if (isSuccessful) {
-            repository.setFailedAuthenticationAttempts(0)
-            repository.setUnlocked(true)
-        } else {
-            repository.setFailedAuthenticationAttempts(
-                repository.failedAuthenticationAttempts.value + 1
+        val skipCheck =
+            when {
+                // We're being throttled, the UI layer should not have called this; skip the
+                // attempt.
+                isThrottled.value -> true
+                // Auto-confirm attempt when the feature is not enabled; skip the attempt.
+                tryAutoConfirm && !isAutoConfirmEnabled.value -> true
+                // Auto-confirm should skip the attempt if the pin entered is too short.
+                tryAutoConfirm && input.size < repository.getPinLength() -> true
+                else -> false
+            }
+        if (skipCheck) {
+            return null
+        }
+
+        // Attempt to authenticate:
+        val authMethod = getAuthenticationMethod()
+        val credential = authMethod.createCredential(input) ?: return null
+        val authenticationResult = repository.checkCredential(credential)
+        credential.zeroize()
+
+        if (authenticationResult.isSuccessful || !tryAutoConfirm) {
+            repository.reportAuthenticationAttempt(
+                isSuccessful = authenticationResult.isSuccessful,
             )
         }
 
-        return isSuccessful
-    }
-
-    /** Triggers a biometric-powered unlock of the device. */
-    fun biometricUnlock() {
-        // TODO(b/280883900): only allow this if the biometric is enabled and there's a match.
-        repository.setUnlocked(true)
-    }
-
-    /** See [authenticationMethod]. */
-    fun setAuthenticationMethod(authenticationMethod: AuthenticationMethodModel) {
-        repository.setAuthenticationMethod(authenticationMethod)
-    }
-
-    /** See [isBypassEnabled]. */
-    fun toggleBypassEnabled() {
-        repository.setBypassEnabled(!repository.isBypassEnabled.value)
-    }
-
-    companion object {
-        private fun isUnlockedWithAuthMethod(
-            isUnlocked: Boolean,
-            authMethod: AuthenticationMethodModel,
-        ): Boolean {
-            return if (authMethod is AuthenticationMethodModel.None) {
-                true
-            } else {
-                isUnlocked
-            }
+        // Check if we need to throttle and, if so, kick off the throttle countdown:
+        if (!authenticationResult.isSuccessful && authenticationResult.throttleDurationMs > 0) {
+            repository.setThrottleDuration(
+                durationMs = authenticationResult.throttleDurationMs,
+            )
+            startThrottlingCountdown()
         }
 
-        /**
-         * Returns a PIN code from the given list. It's assumed the given list elements are all
-         * [Int].
-         */
-        private fun List<Any>.asCode(): Int? {
-            if (isEmpty()) {
-                return null
-            }
-
-            var code = 0
-            map { it as Int }.forEach { integer -> code = code * 10 + integer }
-
-            return code
+        if (authenticationResult.isSuccessful) {
+            // Since authentication succeeded, we should refresh throttling to make sure that our
+            // state is completely reflecting the upstream source of truth.
+            refreshThrottling()
         }
 
-        /**
-         * Returns a password from the given list. It's assumed the given list elements are all
-         * [Char].
-         */
-        private fun List<Any>.asPassword(): String {
-            val anyList = this
-            return buildString { anyList.forEach { append(it as Char) } }
-        }
+        return authenticationResult.isSuccessful
+    }
 
-        /**
-         * Returns a list of [AuthenticationMethodModel.Pattern.PatternCoordinate] from the given
-         * list. It's assumed the given list elements are all
-         * [AuthenticationMethodModel.Pattern.PatternCoordinate].
-         */
-        private fun List<Any>.asPattern():
-            List<AuthenticationMethodModel.Pattern.PatternCoordinate> {
-            val anyList = this
-            return buildList {
-                anyList.forEach { add(it as AuthenticationMethodModel.Pattern.PatternCoordinate) }
+    /** Starts refreshing the throttling state every second. */
+    private suspend fun startThrottlingCountdown() {
+        cancelCountdown()
+        throttlingCountdownJob =
+            applicationScope.launch {
+                while (refreshThrottling() > 0) {
+                    delay(1.seconds.inWholeMilliseconds)
+                }
             }
+    }
+
+    /** Cancels any throttling state countdown started in [startThrottlingCountdown]. */
+    private fun cancelCountdown() {
+        throttlingCountdownJob?.cancel()
+        throttlingCountdownJob = null
+    }
+
+    /** Notifies that the currently-selected user has changed. */
+    private suspend fun onSelectedUserChanged() {
+        cancelCountdown()
+        if (refreshThrottling() > 0) {
+            startThrottlingCountdown()
+        }
+    }
+
+    /**
+     * Refreshes the throttling state, hydrating the repository with the latest state.
+     *
+     * @return The remaining time for the current throttling countdown, in milliseconds or `0` if
+     *   not being throttled.
+     */
+    private suspend fun refreshThrottling(): Long {
+        return withContext(backgroundDispatcher) {
+            val failedAttemptCount = async { repository.getFailedAuthenticationAttemptCount() }
+            val deadline = async { repository.getThrottlingEndTimestamp() }
+            val remainingMs = max(0, deadline.await() - clock.elapsedRealtime())
+            repository.setThrottling(
+                AuthenticationThrottlingModel(
+                    failedAttemptCount = failedAttemptCount.await(),
+                    remainingMs = remainingMs.toInt(),
+                ),
+            )
+            remainingMs
+        }
+    }
+
+    private fun AuthenticationMethodModel.createCredential(
+        input: List<Any>
+    ): LockscreenCredential? {
+        return when (this) {
+            is AuthenticationMethodModel.Pin ->
+                LockscreenCredential.createPin(input.joinToString(""))
+            is AuthenticationMethodModel.Password ->
+                LockscreenCredential.createPassword(input.joinToString(""))
+            is AuthenticationMethodModel.Pattern ->
+                LockscreenCredential.createPattern(
+                    input
+                        .map { it as AuthenticationMethodModel.Pattern.PatternCoordinate }
+                        .map { LockPatternView.Cell.of(it.y, it.x) }
+                )
+            else -> null
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationMethodModel.kt b/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationMethodModel.kt
index 6f008c3..97c6697 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationMethodModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationMethodModel.kt
@@ -32,15 +32,11 @@
     /** The most basic authentication method. The lock screen can be swiped away when displayed. */
     object Swipe : AuthenticationMethodModel(isSecure = false)
 
-    data class PIN(val code: Int) : AuthenticationMethodModel(isSecure = true)
+    object Pin : AuthenticationMethodModel(isSecure = true)
 
-    data class Password(val password: String) : AuthenticationMethodModel(isSecure = true)
+    object Password : AuthenticationMethodModel(isSecure = true)
 
-    data class Pattern(
-        val coordinates: List<PatternCoordinate>,
-        val isPatternVisible: Boolean = true,
-    ) : AuthenticationMethodModel(isSecure = true) {
-
+    object Pattern : AuthenticationMethodModel(isSecure = true) {
         data class PatternCoordinate(
             val x: Int,
             val y: Int,
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationResultModel.kt b/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationResultModel.kt
new file mode 100644
index 0000000..f2a3e74
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationResultModel.kt
@@ -0,0 +1,25 @@
+/*
+ * 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.systemui.authentication.shared.model
+
+/** Models the result of an authentication attempt. */
+data class AuthenticationResultModel(
+    /** Whether authentication was successful. */
+    val isSuccessful: Boolean = false,
+    /** If [isSuccessful] is `false`, how long the user must wait before trying again. */
+    val throttleDurationMs: Int = 0,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationThrottlingModel.kt b/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationThrottlingModel.kt
new file mode 100644
index 0000000..d0d398e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationThrottlingModel.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.systemui.authentication.shared.model
+
+/** Models a state for throttling the next authentication attempt. */
+data class AuthenticationThrottlingModel(
+
+    /** Number of failed authentication attempts so far. If not throttling this will be `0`. */
+    val failedAttemptCount: Int = 0,
+
+    /**
+     * Remaining amount of time, in milliseconds, before another authentication attempt can be done.
+     * If not throttling this will be `0`.
+     *
+     * This number is changed throughout the timeout.
+     */
+    val remainingMs: Int = 0,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/back/domain/interactor/BackActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/back/domain/interactor/BackActionInteractor.kt
new file mode 100644
index 0000000..3c74bf4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/back/domain/interactor/BackActionInteractor.kt
@@ -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.systemui.back.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.shade.QuickSettingsController
+import com.android.systemui.shade.ShadeController
+import com.android.systemui.shade.ShadeViewController
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
+import javax.inject.Inject
+
+/** Handles requests to go back either from a button or gesture. */
+@SysUISingleton
+class BackActionInteractor
+@Inject
+constructor(
+    private val statusBarStateController: StatusBarStateController,
+    private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager,
+    private val shadeController: ShadeController
+) {
+    private lateinit var shadeViewController: ShadeViewController
+    private lateinit var qsController: QuickSettingsController
+
+    fun setup(qsController: QuickSettingsController, svController: ShadeViewController) {
+        this.qsController = qsController
+        this.shadeViewController = svController
+    }
+
+    fun shouldBackBeHandled(): Boolean {
+        return statusBarStateController.state != StatusBarState.KEYGUARD &&
+            statusBarStateController.state != StatusBarState.SHADE_LOCKED &&
+            !statusBarKeyguardViewManager.isBouncerShowingOverDream
+    }
+
+    fun onBackRequested(): Boolean {
+        if (statusBarKeyguardViewManager.canHandleBackPressed()) {
+            statusBarKeyguardViewManager.onBackPressed()
+            return true
+        }
+        if (qsController.isCustomizing) {
+            qsController.closeQsCustomizer()
+            return true
+        }
+        if (qsController.expanded) {
+            shadeViewController.animateCollapseQs(false)
+            return true
+        }
+        if (shadeViewController.closeUserSwitcherIfOpen()) {
+            return true
+        }
+        if (shouldBackBeHandled()) {
+            if (shadeViewController.canBeCollapsed()) {
+                // this is the Shade dismiss animation, so make sure QQS closes when it ends.
+                shadeViewController.onBackPressed()
+                shadeController.animateCollapseShade()
+            }
+            return true
+        }
+        return false
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/battery/AccessorizedBatteryDrawable.kt b/packages/SystemUI/src/com/android/systemui/battery/AccessorizedBatteryDrawable.kt
index b52ddc1..b34f1b4 100644
--- a/packages/SystemUI/src/com/android/systemui/battery/AccessorizedBatteryDrawable.kt
+++ b/packages/SystemUI/src/com/android/systemui/battery/AccessorizedBatteryDrawable.kt
@@ -87,6 +87,10 @@
     }
 
     var displayShield: Boolean = false
+        set(value) {
+            field = value
+            postInvalidate()
+        }
 
     private fun updateSizes() {
         val b = bounds
@@ -204,4 +208,11 @@
         val shieldPathString = context.resources.getString(R.string.config_batterymeterShieldPath)
         shieldPath.set(PathParser.createPathFromPathData(shieldPathString))
     }
+
+    private val invalidateRunnable: () -> Unit = { invalidateSelf() }
+
+    private fun postInvalidate() {
+        unscheduleSelf(invalidateRunnable)
+        scheduleSelf(invalidateRunnable, 0)
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
index 263df33..234795f 100644
--- a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
+++ b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
@@ -331,7 +331,9 @@
         // TODO(b/140051051)
         final boolean systemSetting = 0 != whitelistIpcs(() -> Settings.System
                 .getIntForUser(getContext().getContentResolver(),
-                SHOW_BATTERY_PERCENT, 0, UserHandle.USER_CURRENT));
+                SHOW_BATTERY_PERCENT, getContext().getResources().getBoolean(
+                com.android.internal.R.bool.config_defaultBatteryPercentageSetting)
+                ? 1 : 0, UserHandle.USER_CURRENT));
         boolean shouldShow =
                 (mShowPercentAvailable && systemSetting && mShowPercentMode != MODE_OFF)
                 || mShowPercentMode == MODE_ON
@@ -348,7 +350,7 @@
                 updatePercentText();
                 addView(mBatteryPercentView, new LayoutParams(
                         LayoutParams.WRAP_CONTENT,
-                        LayoutParams.MATCH_PARENT));
+                        LayoutParams.WRAP_CONTENT));
             }
         } else {
             if (showing) {
@@ -464,9 +466,11 @@
 
     public void dump(PrintWriter pw, String[] args) {
         String powerSave = mDrawable == null ? null : mDrawable.getPowerSaveEnabled() + "";
+        String displayShield = mDrawable == null ? null : mDrawable.getDisplayShield() + "";
         CharSequence percent = mBatteryPercentView == null ? null : mBatteryPercentView.getText();
         pw.println("  BatteryMeterView:");
         pw.println("    mDrawable.getPowerSave: " + powerSave);
+        pw.println("    mDrawable.getDisplayShield: " + displayShield);
         pw.println("    mBatteryPercentView.getText(): " + percent);
         pw.println("    mTextColor: #" + Integer.toHexString(mTextColor));
         pw.println("    mBatteryStateUnknown: " + mBatteryStateUnknown);
diff --git a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java
index f6a10bd..6a5749c 100644
--- a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java
@@ -30,16 +30,18 @@
 
 import androidx.annotation.NonNull;
 
+import com.android.systemui.R;
 import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
+import com.android.systemui.statusbar.phone.StatusBarLocation;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.tuner.TunerService;
 import com.android.systemui.util.ViewController;
 
+import java.io.PrintWriter;
+
 import javax.inject.Inject;
 
 /** Controller for {@link BatteryMeterView}. **/
@@ -53,6 +55,7 @@
     private final String mSlotBattery;
     private final SettingObserver mSettingObserver;
     private final UserTracker mUserTracker;
+    private final StatusBarLocation mLocation;
 
     private final ConfigurationController.ConfigurationListener mConfigurationListener =
             new ConfigurationController.ConfigurationListener() {
@@ -94,6 +97,13 @@
                 public void onIsBatteryDefenderChanged(boolean isBatteryDefender) {
                     mView.onIsBatteryDefenderChanged(isBatteryDefender);
                 }
+
+                @Override
+                public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
+                    pw.print(super.toString());
+                    pw.println(" location=" + mLocation);
+                    mView.dump(pw, args);
+                }
             };
 
     private final UserTracker.Callback mUserChangedCallback =
@@ -113,14 +123,15 @@
     @Inject
     public BatteryMeterViewController(
             BatteryMeterView view,
+            StatusBarLocation location,
             UserTracker userTracker,
             ConfigurationController configurationController,
             TunerService tunerService,
             @Main Handler mainHandler,
             ContentResolver contentResolver,
-            FeatureFlags featureFlags,
             BatteryController batteryController) {
         super(view);
+        mLocation = location;
         mUserTracker = userTracker;
         mConfigurationController = configurationController;
         mTunerService = tunerService;
@@ -129,7 +140,8 @@
         mBatteryController = batteryController;
 
         mView.setBatteryEstimateFetcher(mBatteryController::getEstimatedTimeRemainingString);
-        mView.setDisplayShieldEnabled(featureFlags.isEnabled(Flags.BATTERY_SHIELD_ICON));
+        mView.setDisplayShieldEnabled(
+                getContext().getResources().getBoolean(R.bool.flag_battery_shield_icon));
 
         mSlotBattery = getResources().getString(com.android.internal.R.string.status_bar_battery);
         mSettingObserver = new SettingObserver(mMainHandler);
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
index cd8f04d..ed4b91c 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
@@ -737,7 +737,7 @@
         });
 
         mUseCredentialButton.setOnClickListener((view) -> {
-            startTransitionToCredentialUI();
+            startTransitionToCredentialUI(false /* isError */);
         });
 
         mConfirmButton.setOnClickListener((view) -> {
@@ -768,9 +768,12 @@
 
     /**
      * Kicks off the animation process and invokes the callback.
+     *
+     * @param isError if this was triggered due to an error and not a user action (unused,
+     *                previously for haptics).
      */
     @Override
-    public void startTransitionToCredentialUI() {
+    public void startTransitionToCredentialUI(boolean isError) {
         updateSize(AuthDialog.SIZE_LARGE);
         mCallback.onAction(Callback.ACTION_USE_DEVICE_CREDENTIAL);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricViewAdapter.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricViewAdapter.kt
index 631511c..68db564 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricViewAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricViewAdapter.kt
@@ -38,7 +38,7 @@
 
     fun onHelp(@BiometricAuthenticator.Modality modality: Int, help: String)
 
-    fun startTransitionToCredentialUI()
+    fun startTransitionToCredentialUI(isError: Boolean)
 
     fun requestLayout()
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index 7f70685..9df56fc 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -250,7 +250,7 @@
                 .setMessage(messageBody)
                 .setPositiveButton(android.R.string.ok, null)
                 .create();
-        alertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL);
+        alertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
         alertDialog.show();
     }
 
@@ -263,7 +263,7 @@
                 .setOnDismissListener(
                         dialog -> animateAway(AuthDialogCallback.DISMISSED_ERROR))
                 .create();
-        alertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL);
+        alertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
         alertDialog.show();
     }
 
@@ -374,7 +374,6 @@
         if (Utils.isBiometricAllowed(config.mPromptInfo)) {
             mPromptSelectorInteractorProvider.get().useBiometricsForAuthentication(
                     config.mPromptInfo,
-                    config.mRequireConfirmation,
                     config.mUserId,
                     config.mOperationId,
                     new BiometricModalities(fpProps, faceProps));
@@ -802,9 +801,9 @@
     }
 
     @Override
-    public void animateToCredentialUI() {
+    public void animateToCredentialUI(boolean isError) {
         if (mBiometricView != null) {
-            mBiometricView.startTransitionToCredentialUI();
+            mBiometricView.startTransitionToCredentialUI(isError);
         } else {
             Log.e(TAG, "animateToCredentialUI(): mBiometricView is null");
         }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 57f1928..3df7ca5 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -85,7 +85,6 @@
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.keyguard.data.repository.BiometricType;
 import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.VibratorHelper;
 import com.android.systemui.util.concurrency.DelayableExecutor;
 import com.android.systemui.util.concurrency.Execution;
 
@@ -185,23 +184,13 @@
     private final @Background DelayableExecutor mBackgroundExecutor;
     private final DisplayInfo mCachedDisplayInfo = new DisplayInfo();
 
-    private final VibratorHelper mVibratorHelper;
-
-    private void vibrateSuccess(int modality) {
-        mVibratorHelper.vibrateAuthSuccess(
-                getClass().getSimpleName() + ", modality = " + modality + "BP::success");
-    }
-
-    private void vibrateError(int modality) {
-        mVibratorHelper.vibrateAuthError(
-                getClass().getSimpleName() + ", modality = " + modality + "BP::error");
-    }
-
     @VisibleForTesting
     final TaskStackListener mTaskStackListener = new TaskStackListener() {
         @Override
         public void onTaskStackChanged() {
-            mHandler.post(AuthController.this::cancelIfOwnerIsNotInForeground);
+            if (!isOwnerInForeground()) {
+                mHandler.post(AuthController.this::cancelIfOwnerIsNotInForeground);
+            }
         }
     };
 
@@ -239,33 +228,39 @@
         }
     }
 
+    private boolean isOwnerInForeground() {
+        if (mCurrentDialog != null) {
+            final String clientPackage = mCurrentDialog.getOpPackageName();
+            final List<ActivityManager.RunningTaskInfo> runningTasks =
+                    mActivityTaskManager.getTasks(1);
+            if (!runningTasks.isEmpty()) {
+                final String topPackage = runningTasks.get(0).topActivity.getPackageName();
+                if (!topPackage.contentEquals(clientPackage)
+                        && !Utils.isSystem(mContext, clientPackage)) {
+                    Log.w(TAG, "Evicting client due to: " + topPackage);
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
     private void cancelIfOwnerIsNotInForeground() {
         mExecution.assertIsMainThread();
         if (mCurrentDialog != null) {
             try {
-                final String clientPackage = mCurrentDialog.getOpPackageName();
-                Log.w(TAG, "Task stack changed, current client: " + clientPackage);
-                final List<ActivityManager.RunningTaskInfo> runningTasks =
-                        mActivityTaskManager.getTasks(1);
-                if (!runningTasks.isEmpty()) {
-                    final String topPackage = runningTasks.get(0).topActivity.getPackageName();
-                    if (!topPackage.contentEquals(clientPackage)
-                            && !Utils.isSystem(mContext, clientPackage)) {
-                        Log.e(TAG, "Evicting client due to: " + topPackage);
-                        mCurrentDialog.dismissWithoutCallback(true /* animate */);
-                        mCurrentDialog = null;
+                mCurrentDialog.dismissWithoutCallback(true /* animate */);
+                mCurrentDialog = null;
 
-                        for (Callback cb : mCallbacks) {
-                            cb.onBiometricPromptDismissed();
-                        }
+                for (Callback cb : mCallbacks) {
+                    cb.onBiometricPromptDismissed();
+                }
 
-                        if (mReceiver != null) {
-                            mReceiver.onDialogDismissed(
-                                    BiometricPrompt.DISMISSED_REASON_USER_CANCEL,
-                                    null /* credentialAttestation */);
-                            mReceiver = null;
-                        }
-                    }
+                if (mReceiver != null) {
+                    mReceiver.onDialogDismissed(
+                            BiometricPrompt.DISMISSED_REASON_USER_CANCEL,
+                            null /* credentialAttestation */);
+                    mReceiver = null;
                 }
             } catch (RemoteException e) {
                 Log.e(TAG, "Remote exception", e);
@@ -776,7 +771,6 @@
             @NonNull InteractionJankMonitor jankMonitor,
             @Main Handler handler,
             @Background DelayableExecutor bgExecutor,
-            @NonNull VibratorHelper vibrator,
             @NonNull UdfpsUtils udfpsUtils) {
         mContext = context;
         mFeatureFlags = featureFlags;
@@ -798,7 +792,6 @@
         mUdfpsEnrolledForUser = new SparseBooleanArray();
         mSfpsEnrolledForUser = new SparseBooleanArray();
         mFaceEnrolledForUser = new SparseBooleanArray();
-        mVibratorHelper = vibrator;
         mUdfpsUtils = udfpsUtils;
         mApplicationCoroutineScope = applicationCoroutineScope;
 
@@ -987,8 +980,6 @@
     public void onBiometricAuthenticated(@Modality int modality) {
         if (DEBUG) Log.d(TAG, "onBiometricAuthenticated: ");
 
-        vibrateSuccess(modality);
-
         if (mCurrentDialog != null) {
             mCurrentDialog.onAuthenticationSucceeded(modality);
         } else {
@@ -1048,6 +1039,18 @@
         return false;
     }
 
+    private String getNotRecognizedString(@Modality int modality) {
+        final int messageRes;
+        final int userId = mCurrentDialogArgs.argi1;
+        if (isFaceAuthEnrolled(userId) && isFingerprintEnrolled(userId)) {
+            messageRes = modality == TYPE_FACE
+                    ? R.string.biometric_face_not_recognized
+                    : R.string.fingerprint_error_not_match;
+        } else {
+            messageRes = R.string.biometric_not_recognized;
+        }
+        return mContext.getString(messageRes);
+    }
 
     private String getErrorString(@Modality int modality, int error, int vendorCode) {
         switch (modality) {
@@ -1073,8 +1076,6 @@
             Log.d(TAG, String.format("onBiometricError(%d, %d, %d)", modality, error, vendorCode));
         }
 
-        vibrateError(modality);
-
         final boolean isLockout = (error == BiometricConstants.BIOMETRIC_ERROR_LOCKOUT)
                 || (error == BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT);
 
@@ -1091,10 +1092,11 @@
         if (mCurrentDialog != null) {
             if (mCurrentDialog.isAllowDeviceCredentials() && isLockout) {
                 if (DEBUG) Log.d(TAG, "onBiometricError, lockout");
-                mCurrentDialog.animateToCredentialUI();
+                mCurrentDialog.animateToCredentialUI(true /* isError */);
             } else if (isSoftError) {
-                final String errorMessage = (error == BiometricConstants.BIOMETRIC_PAUSED_REJECTED)
-                        ? mContext.getString(R.string.biometric_not_recognized)
+                final String errorMessage = (error == BiometricConstants.BIOMETRIC_PAUSED_REJECTED
+                        || error == BiometricConstants.BIOMETRIC_ERROR_TIMEOUT)
+                        ? getNotRecognizedString(modality)
                         : getErrorString(modality, error, vendorCode);
                 if (DEBUG) Log.d(TAG, "onBiometricError, soft error: " + errorMessage);
                 // The camera privacy error can return before the prompt initializes its state,
@@ -1204,8 +1206,11 @@
 
         final PromptInfo promptInfo = (PromptInfo) args.arg1;
         final int[] sensorIds = (int[]) args.arg3;
+
+        // TODO(b/251476085): remove these unused parameters (replaced with SSOT elsewhere)
         final boolean credentialAllowed = (boolean) args.arg4;
         final boolean requireConfirmation = (boolean) args.arg5;
+
         final int userId = args.argi1;
         final String opPackageName = (String) args.arg6;
         final long operationId = args.argl1;
@@ -1253,10 +1258,11 @@
             cb.onBiometricPromptShown();
         }
         mCurrentDialog = newDialog;
-        mCurrentDialog.show(mWindowManager, savedState);
 
-        if (!promptInfo.isAllowBackgroundAuthentication()) {
-            mHandler.post(this::cancelIfOwnerIsNotInForeground);
+        if (!promptInfo.isAllowBackgroundAuthentication() && !isOwnerInForeground()) {
+            cancelIfOwnerIsNotInForeground();
+        } else {
+            mCurrentDialog.show(mWindowManager, savedState);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java
index b6eabfa..3cfc6f2 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java
@@ -162,7 +162,7 @@
     /**
      * Animate to credential UI. Typically called after biometric is locked out.
      */
-    void animateToCredentialUI();
+    void animateToCredentialUI(boolean isError);
 
     /**
      * @return true if device credential is allowed.
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
index 6f0f633..946ddba 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
@@ -37,7 +37,7 @@
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.WakefulnessLifecycle
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.CircleReveal
 import com.android.systemui.statusbar.LiftReveal
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationService.java b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationService.java
index 6db266f..4b17be3 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationService.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationService.java
@@ -29,6 +29,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.hardware.biometrics.BiometricFaceConstants;
+import android.hardware.biometrics.BiometricFingerprintConstants;
 import android.hardware.biometrics.BiometricSourceType;
 import android.os.Handler;
 import android.os.UserHandle;
@@ -42,9 +43,6 @@
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
-
-import java.util.Optional;
-
 import javax.inject.Inject;
 
 /**
@@ -68,7 +66,6 @@
     private final Handler mHandler;
     private final NotificationManager mNotificationManager;
     private final BiometricNotificationBroadcastReceiver mBroadcastReceiver;
-    private final FingerprintReEnrollNotification mFingerprintReEnrollNotification;
     private NotificationChannel mNotificationChannel;
     private boolean mFaceNotificationQueued;
     private boolean mFingerprintNotificationQueued;
@@ -105,15 +102,8 @@
                         Settings.Secure.putIntForUser(mContext.getContentResolver(),
                                 Settings.Secure.FACE_UNLOCK_RE_ENROLL, REENROLL_REQUIRED,
                                 UserHandle.USER_CURRENT);
-                    }
-                }
-
-                @Override
-                public void onBiometricHelp(int msgId, String helpString,
-                        BiometricSourceType biometricSourceType) {
-                    if (biometricSourceType == BiometricSourceType.FINGERPRINT
-                            && mFingerprintReEnrollNotification.isFingerprintReEnrollRequired(
-                                    msgId)) {
+                    } else if (msgId == BiometricFingerprintConstants.BIOMETRIC_ERROR_RE_ENROLL
+                            && biometricSourceType == BiometricSourceType.FINGERPRINT) {
                         mFingerprintReenrollRequired = true;
                     }
                 }
@@ -125,16 +115,13 @@
             KeyguardUpdateMonitor keyguardUpdateMonitor,
             KeyguardStateController keyguardStateController,
             Handler handler, NotificationManager notificationManager,
-            BiometricNotificationBroadcastReceiver biometricNotificationBroadcastReceiver,
-            Optional<FingerprintReEnrollNotification> fingerprintReEnrollNotification) {
+            BiometricNotificationBroadcastReceiver biometricNotificationBroadcastReceiver) {
         mContext = context;
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         mKeyguardStateController = keyguardStateController;
         mHandler = handler;
         mNotificationManager = notificationManager;
         mBroadcastReceiver = biometricNotificationBroadcastReceiver;
-        mFingerprintReEnrollNotification = fingerprintReEnrollNotification.orElse(
-                new FingerprintReEnrollNotificationImpl());
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintReEnrollNotification.java b/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintReEnrollNotification.java
deleted file mode 100644
index ca94e99..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintReEnrollNotification.java
+++ /dev/null
@@ -1,25 +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.biometrics;
-
-/**
- * Checks if the fingerprint HAL has sent a re-enrollment request.
- */
-public interface FingerprintReEnrollNotification {
-    /** Returns true if msgId corresponds to FINGERPRINT_ACQUIRED_RE_ENROLL. */
-    boolean isFingerprintReEnrollRequired(int msgId);
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintReEnrollNotificationImpl.java b/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintReEnrollNotificationImpl.java
deleted file mode 100644
index 1f86bc6..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintReEnrollNotificationImpl.java
+++ /dev/null
@@ -1,29 +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.biometrics;
-
-import android.hardware.biometrics.BiometricFingerprintConstants;
-
-/**
- * Checks if the fingerprint HAL has sent a re-enrollment request.
- */
-public class FingerprintReEnrollNotificationImpl implements FingerprintReEnrollNotification{
-    @Override
-    public boolean isFingerprintReEnrollRequired(int msgId) {
-        return msgId == BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_RE_ENROLL;
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
index 54aef00..083e21f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
@@ -17,12 +17,7 @@
 
 import android.app.ActivityTaskManager
 import android.content.Context
-import android.content.res.Configuration
-import android.graphics.Color
 import android.graphics.PixelFormat
-import android.graphics.PorterDuff
-import android.graphics.PorterDuffColorFilter
-import android.graphics.Rect
 import android.hardware.biometrics.BiometricOverlayConstants
 import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD
 import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_SETTINGS
@@ -33,37 +28,34 @@
 import android.hardware.fingerprint.ISidefpsController
 import android.os.Handler
 import android.util.Log
-import android.util.RotationUtils
 import android.view.Display
 import android.view.DisplayInfo
 import android.view.Gravity
 import android.view.LayoutInflater
 import android.view.Surface
 import android.view.View
-import android.view.View.AccessibilityDelegate
 import android.view.ViewPropertyAnimator
 import android.view.WindowManager
 import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION
 import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY
-import android.view.accessibility.AccessibilityEvent
-import androidx.annotation.RawRes
 import com.airbnb.lottie.LottieAnimationView
-import com.airbnb.lottie.LottieProperty
-import com.airbnb.lottie.model.KeyPath
 import com.android.internal.annotations.VisibleForTesting
 import com.android.systemui.Dumpable
 import com.android.systemui.R
 import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
+import com.android.systemui.biometrics.ui.binder.SideFpsOverlayViewBinder
+import com.android.systemui.biometrics.ui.viewmodel.SideFpsOverlayViewModel
+import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.dump.DumpManager
-import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor
 import com.android.systemui.util.boundsOnScreen
 import com.android.systemui.util.concurrency.DelayableExecutor
 import com.android.systemui.util.traceSection
 import java.io.PrintWriter
 import javax.inject.Inject
+import javax.inject.Provider
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.launch
 
@@ -86,6 +78,7 @@
     @Main private val mainExecutor: DelayableExecutor,
     @Main private val handler: Handler,
     private val alternateBouncerInteractor: AlternateBouncerInteractor,
+    private val sideFpsOverlayViewModelFactory: Provider<SideFpsOverlayViewModel>,
     @Application private val scope: CoroutineScope,
     dumpManager: DumpManager
 ) : Dumpable {
@@ -250,105 +243,15 @@
     private fun createOverlayForDisplay(@BiometricOverlayConstants.ShowReason reason: Int) {
         val view = layoutInflater.inflate(R.layout.sidefps_view, null, false)
         overlayView = view
-        val display = context.display!!
-        // b/284098873 `context.display.rotation` may not up-to-date, we use displayInfo.rotation
-        display.getDisplayInfo(displayInfo)
-        val offsets =
-            sensorProps.getLocation(display.uniqueId).let { location ->
-                if (location == null) {
-                    Log.w(TAG, "No location specified for display: ${display.uniqueId}")
-                }
-                location ?: sensorProps.location
-            }
-        overlayOffsets = offsets
-
-        val lottie = view.findViewById(R.id.sidefps_animation) as LottieAnimationView
-        view.rotation =
-            display.asSideFpsAnimationRotation(
-                offsets.isYAligned(),
-                getRotationFromDefault(displayInfo.rotation)
-            )
-        lottie.setAnimation(
-            display.asSideFpsAnimation(
-                offsets.isYAligned(),
-                getRotationFromDefault(displayInfo.rotation)
-            )
+        SideFpsOverlayViewBinder.bind(
+            view = view,
+            viewModel = sideFpsOverlayViewModelFactory.get(),
+            overlayViewParams = overlayViewParams,
+            reason = reason,
+            context = context,
         )
-        lottie.addLottieOnCompositionLoadedListener {
-            // Check that view is not stale, and that overlayView has not been hidden/removed
-            if (overlayView != null && overlayView == view) {
-                updateOverlayParams(display, it.bounds)
-            }
-        }
         orientationReasonListener.reason = reason
-        lottie.addOverlayDynamicColor(context, reason)
-
-        /**
-         * Intercepts TYPE_WINDOW_STATE_CHANGED accessibility event, preventing Talkback from
-         * speaking @string/accessibility_fingerprint_label twice when sensor location indicator is
-         * in focus
-         */
-        view.setAccessibilityDelegate(
-            object : AccessibilityDelegate() {
-                override fun dispatchPopulateAccessibilityEvent(
-                    host: View,
-                    event: AccessibilityEvent
-                ): Boolean {
-                    return if (
-                        event.getEventType() === AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED
-                    ) {
-                        true
-                    } else {
-                        super.dispatchPopulateAccessibilityEvent(host, event)
-                    }
-                }
-            }
-        )
     }
-
-    @VisibleForTesting
-    fun updateOverlayParams(display: Display, bounds: Rect) {
-        val isNaturalOrientation = display.isNaturalOrientation()
-        val isDefaultOrientation =
-            if (isReverseDefaultRotation) !isNaturalOrientation else isNaturalOrientation
-        val size = windowManager.maximumWindowMetrics.bounds
-
-        val displayWidth = if (isDefaultOrientation) size.width() else size.height()
-        val displayHeight = if (isDefaultOrientation) size.height() else size.width()
-        val boundsWidth = if (isDefaultOrientation) bounds.width() else bounds.height()
-        val boundsHeight = if (isDefaultOrientation) bounds.height() else bounds.width()
-
-        val sensorBounds =
-            if (overlayOffsets.isYAligned()) {
-                Rect(
-                    displayWidth - boundsWidth,
-                    overlayOffsets.sensorLocationY,
-                    displayWidth,
-                    overlayOffsets.sensorLocationY + boundsHeight
-                )
-            } else {
-                Rect(
-                    overlayOffsets.sensorLocationX,
-                    0,
-                    overlayOffsets.sensorLocationX + boundsWidth,
-                    boundsHeight
-                )
-            }
-
-        RotationUtils.rotateBounds(
-            sensorBounds,
-            Rect(0, 0, displayWidth, displayHeight),
-            getRotationFromDefault(display.rotation)
-        )
-
-        overlayViewParams.x = sensorBounds.left
-        overlayViewParams.y = sensorBounds.top
-
-        windowManager.updateViewLayout(overlayView, overlayViewParams)
-    }
-
-    private fun getRotationFromDefault(rotation: Int): Int =
-        if (isReverseDefaultRotation) (rotation + 1) % 4 else rotation
 }
 
 private val FingerprintManager?.sideFpsSensorProperties: FingerprintSensorPropertiesInternal?
@@ -373,89 +276,12 @@
 private fun ActivityTaskManager.topClass(): String =
     getTasks(1).firstOrNull()?.topActivity?.className ?: ""
 
-@RawRes
-private fun Display.asSideFpsAnimation(yAligned: Boolean, rotationFromDefault: Int): Int =
-    when (rotationFromDefault) {
-        Surface.ROTATION_0 -> if (yAligned) R.raw.sfps_pulse else R.raw.sfps_pulse_landscape
-        Surface.ROTATION_180 -> if (yAligned) R.raw.sfps_pulse else R.raw.sfps_pulse_landscape
-        else -> if (yAligned) R.raw.sfps_pulse_landscape else R.raw.sfps_pulse
-    }
-
-private fun Display.asSideFpsAnimationRotation(yAligned: Boolean, rotationFromDefault: Int): Float =
-    when (rotationFromDefault) {
-        Surface.ROTATION_90 -> if (yAligned) 0f else 180f
-        Surface.ROTATION_180 -> 180f
-        Surface.ROTATION_270 -> if (yAligned) 180f else 0f
-        else -> 0f
-    }
-
 private fun SensorLocationInternal.isYAligned(): Boolean = sensorLocationY != 0
 
 private fun Display.isNaturalOrientation(): Boolean =
     rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180
 
-private fun LottieAnimationView.addOverlayDynamicColor(
-    context: Context,
-    @BiometricOverlayConstants.ShowReason reason: Int
-) {
-    fun update() {
-        val isKeyguard = reason == REASON_AUTH_KEYGUARD
-        if (isKeyguard) {
-            val color =
-                com.android.settingslib.Utils.getColorAttrDefaultColor(
-                    context,
-                    com.android.internal.R.attr.materialColorPrimaryFixed
-                )
-            val outerRimColor =
-                com.android.settingslib.Utils.getColorAttrDefaultColor(
-                    context,
-                    com.android.internal.R.attr.materialColorPrimaryFixedDim
-                )
-            val chevronFill =
-                com.android.settingslib.Utils.getColorAttrDefaultColor(
-                    context,
-                    com.android.internal.R.attr.materialColorOnPrimaryFixed
-                )
-            addValueCallback(KeyPath(".blue600", "**"), LottieProperty.COLOR_FILTER) {
-                PorterDuffColorFilter(color, PorterDuff.Mode.SRC_ATOP)
-            }
-            addValueCallback(KeyPath(".blue400", "**"), LottieProperty.COLOR_FILTER) {
-                PorterDuffColorFilter(outerRimColor, PorterDuff.Mode.SRC_ATOP)
-            }
-            addValueCallback(KeyPath(".black", "**"), LottieProperty.COLOR_FILTER) {
-                PorterDuffColorFilter(chevronFill, PorterDuff.Mode.SRC_ATOP)
-            }
-        } else {
-            if (!isDarkMode(context)) {
-                addValueCallback(KeyPath(".black", "**"), LottieProperty.COLOR_FILTER) {
-                    PorterDuffColorFilter(Color.WHITE, PorterDuff.Mode.SRC_ATOP)
-                }
-            }
-            for (key in listOf(".blue600", ".blue400")) {
-                addValueCallback(KeyPath(key, "**"), LottieProperty.COLOR_FILTER) {
-                    PorterDuffColorFilter(
-                        context.getColor(R.color.settingslib_color_blue400),
-                        PorterDuff.Mode.SRC_ATOP
-                    )
-                }
-            }
-        }
-    }
-
-    if (composition != null) {
-        update()
-    } else {
-        addLottieOnCompositionLoadedListener { update() }
-    }
-}
-
-private fun isDarkMode(context: Context): Boolean {
-    val darkMode = context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
-    return darkMode == Configuration.UI_MODE_NIGHT_YES
-}
-
-@VisibleForTesting
-class OrientationReasonListener(
+public class OrientationReasonListener(
     context: Context,
     displayManager: DisplayManager,
     handler: Handler,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt
index e6aeb43..802eea3 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt
@@ -37,4 +37,8 @@
     dumpManager
 ) {
     override val tag = "UdfpsBpViewController"
+
+    override fun shouldPauseAuth(): Boolean {
+        return false
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 10e45da..ebff0b0 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -75,6 +75,8 @@
 import com.android.systemui.biometrics.udfps.SinglePointerTouchProcessor;
 import com.android.systemui.biometrics.udfps.TouchProcessor;
 import com.android.systemui.biometrics.udfps.TouchProcessorResult;
+import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
+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.doze.DozeReceiver;
@@ -82,9 +84,9 @@
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
 import com.android.systemui.keyguard.ScreenLifecycle;
-import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
-import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor;
+import com.android.systemui.keyguard.ui.adapter.UdfpsKeyguardViewControllerAdapter;
+import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModels;
 import com.android.systemui.log.SessionTracker;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -153,6 +155,7 @@
     @NonNull private final SystemUIDialogManager mDialogManager;
     @NonNull private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     @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;
@@ -173,6 +176,7 @@
     @NonNull private final SecureSettings mSecureSettings;
     @NonNull private final UdfpsUtils mUdfpsUtils;
     @NonNull private final InputManager mInputManager;
+    @NonNull private final UdfpsKeyguardAccessibilityDelegate mUdfpsKeyguardAccessibilityDelegate;
     private final boolean mIgnoreRefreshRate;
 
     // Currently the UdfpsController supports a single UDFPS sensor. If devices have multiple
@@ -188,6 +192,8 @@
     @Nullable private VelocityTracker mVelocityTracker;
     // The ID of the pointer for which ACTION_DOWN has occurred. -1 means no pointer is active.
     private int mActivePointerId = -1;
+    // Whether a pointer has been pilfered for current gesture
+    private boolean mPointerPilfered = false;
     // The timestamp of the most recent touch log.
     private long mTouchLogTime;
     // The timestamp of the most recent log of a touch InteractionEvent.
@@ -257,10 +263,6 @@
         @Override
         public void showUdfpsOverlay(long requestId, int sensorId, int reason,
                 @NonNull IUdfpsOverlayControllerCallback callback) {
-            if (mFeatureFlags.isEnabled(Flags.NEW_UDFPS_OVERLAY)) {
-                return;
-            }
-
             mFgExecutor.execute(() -> UdfpsController.this.showUdfpsOverlay(
                     new UdfpsControllerOverlay(mContext, mFingerprintManager, mInflater,
                             mWindowManager, mAccessibilityManager, mStatusBarStateController,
@@ -272,15 +274,13 @@
                             mUdfpsDisplayMode, mSecureSettings, requestId, reason, callback,
                             (view, event, fromUdfpsView) -> onTouch(requestId, event,
                                     fromUdfpsView), mActivityLaunchAnimator, mFeatureFlags,
-                            mPrimaryBouncerInteractor, mAlternateBouncerInteractor, mUdfpsUtils)));
+                            mPrimaryBouncerInteractor, mAlternateBouncerInteractor, mUdfpsUtils,
+                            mUdfpsKeyguardAccessibilityDelegate,
+                            mUdfpsKeyguardViewModels)));
         }
 
         @Override
         public void hideUdfpsOverlay(int sensorId) {
-            if (mFeatureFlags.isEnabled(Flags.NEW_UDFPS_OVERLAY)) {
-                return;
-            }
-
             mFgExecutor.execute(() -> {
                 if (mKeyguardUpdateMonitor.isFingerprintDetectionRunning()) {
                     // if we get here, we expect keyguardUpdateMonitor's fingerprintRunningState
@@ -354,7 +354,8 @@
                 UdfpsController.this.mAlternateTouchProvider.onUiReady();
             } else {
                 final long requestId = (mOverlay != null) ? mOverlay.getRequestId() : 0L;
-                UdfpsController.this.mFingerprintManager.onUiReady(requestId, sensorId);
+                UdfpsController.this.mFingerprintManager.onUdfpsUiEvent(
+                        FingerprintManager.UDFPS_UI_READY, requestId, sensorId);
             }
         }
     }
@@ -557,6 +558,11 @@
                 || mPrimaryBouncerInteractor.isInTransit()) {
             return false;
         }
+        if (event.getAction() == MotionEvent.ACTION_DOWN
+                || event.getAction() == MotionEvent.ACTION_HOVER_ENTER) {
+            // Reset on ACTION_DOWN, start of new gesture
+            mPointerPilfered = false;
+        }
 
         final TouchProcessorResult result = mTouchProcessor.processTouch(event, mActivePointerId,
                 mOverlayParams);
@@ -636,10 +642,11 @@
             shouldPilfer = true;
         }
 
-        // Execute the pilfer
-        if (shouldPilfer) {
+        // Pilfer only once per gesture
+        if (shouldPilfer && !mPointerPilfered) {
             mInputManager.pilferPointers(
                     mOverlay.getOverlayView().getViewRootImpl().getInputToken());
+            mPointerPilfered = true;
         }
 
         return processedTouch.getTouchData().isWithinBounds(mOverlayParams.getNativeSensorBounds());
@@ -787,7 +794,7 @@
     private boolean shouldTryToDismissKeyguard() {
         return mOverlay != null
                 && mOverlay.getAnimationViewController()
-                instanceof UdfpsKeyguardViewControllerLegacy
+                instanceof UdfpsKeyguardViewControllerAdapter
                 && mKeyguardStateController.canDismissLockScreen()
                 && !mAttemptedToDismissKeyguard;
     }
@@ -831,7 +838,9 @@
             @NonNull SecureSettings secureSettings,
             @NonNull InputManager inputManager,
             @NonNull UdfpsUtils udfpsUtils,
-            @NonNull KeyguardFaceAuthInteractor keyguardFaceAuthInteractor) {
+            @NonNull KeyguardFaceAuthInteractor keyguardFaceAuthInteractor,
+            @NonNull UdfpsKeyguardAccessibilityDelegate udfpsKeyguardAccessibilityDelegate,
+            @NonNull Provider<UdfpsKeyguardViewModels> udfpsKeyguardViewModelsProvider) {
         mContext = context;
         mExecution = execution;
         mVibrator = vibrator;
@@ -877,6 +886,7 @@
         mSecureSettings = secureSettings;
         mUdfpsUtils = udfpsUtils;
         mInputManager = inputManager;
+        mUdfpsKeyguardAccessibilityDelegate = udfpsKeyguardAccessibilityDelegate;
 
         mTouchProcessor = mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)
                 ? singlePointerTouchProcessor : null;
@@ -896,6 +906,7 @@
                     return Unit.INSTANCE;
                 });
         mKeyguardFaceAuthInteractor = keyguardFaceAuthInteractor;
+        mUdfpsKeyguardViewModels = udfpsKeyguardViewModelsProvider;
 
         final UdfpsOverlayController mUdfpsOverlayController = new UdfpsOverlayController();
         mFingerprintManager.setUdfpsOverlayController(mUdfpsOverlayController);
@@ -958,6 +969,10 @@
             mOnFingerDown = false;
             mAttemptedToDismissKeyguard = false;
             mOrientationListener.enable();
+            if (mFingerprintManager != null) {
+                mFingerprintManager.onUdfpsUiEvent(FingerprintManager.UDFPS_UI_OVERLAY_SHOWN,
+                        overlay.getRequestId(), mSensorProps.sensorId);
+            }
         } else {
             Log.v(TAG, "showUdfpsOverlay | the overlay is already showing");
         }
@@ -1099,7 +1114,8 @@
                 mLatencyTracker.onActionEnd(LatencyTracker.ACTION_UDFPS_ILLUMINATE);
             });
         } else {
-            mFingerprintManager.onUiReady(requestId, mSensorProps.sensorId);
+            mFingerprintManager.onUdfpsUiEvent(FingerprintManager.UDFPS_UI_READY, requestId,
+                    mSensorProps.sensorId);
             mLatencyTracker.onActionEnd(LatencyTracker.ACTION_UDFPS_ILLUMINATE);
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
index cabe900..d6ef94d 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
@@ -46,15 +46,19 @@
 import androidx.annotation.LayoutRes
 import androidx.annotation.VisibleForTesting
 import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.settingslib.udfps.UdfpsUtils
 import com.android.settingslib.udfps.UdfpsOverlayParams
+import com.android.settingslib.udfps.UdfpsUtils
 import com.android.systemui.R
 import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.biometrics.ui.controller.UdfpsKeyguardViewController
+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.flags.Flags
-import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor
-import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor
+import com.android.systemui.flags.Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS
+import com.android.systemui.keyguard.ui.adapter.UdfpsKeyguardViewControllerAdapter
+import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModels
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.shade.ShadeExpansionStateManager
 import com.android.systemui.statusbar.LockscreenShadeTransitionController
@@ -64,6 +68,8 @@
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.settings.SecureSettings
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import javax.inject.Provider
 
 private const val TAG = "UdfpsControllerOverlay"
 
@@ -75,6 +81,7 @@
  * request. This state can persist across configuration changes via the [show] and [hide]
  * methods.
  */
+@ExperimentalCoroutinesApi
 @UiThread
 class UdfpsControllerOverlay @JvmOverloads constructor(
         private val context: Context,
@@ -103,7 +110,9 @@
         private val primaryBouncerInteractor: PrimaryBouncerInteractor,
         private val alternateBouncerInteractor: AlternateBouncerInteractor,
         private val isDebuggable: Boolean = Build.IS_DEBUGGABLE,
-        private val udfpsUtils: UdfpsUtils
+        private val udfpsUtils: UdfpsUtils,
+        private val udfpsKeyguardAccessibilityDelegate: UdfpsKeyguardAccessibilityDelegate,
+        private val udfpsKeyguardViewModels: Provider<UdfpsKeyguardViewModels>,
 ) {
     /** The view, when [isShowing], or null. */
     var overlayView: UdfpsView? = null
@@ -242,26 +251,40 @@
                 )
             }
             REASON_AUTH_KEYGUARD -> {
-                UdfpsKeyguardViewControllerLegacy(
-                    view.addUdfpsView(R.layout.udfps_keyguard_view_legacy) {
-                        updateSensorLocation(sensorBounds)
-                    },
-                    statusBarStateController,
-                    shadeExpansionStateManager,
-                    statusBarKeyguardViewManager,
-                    keyguardUpdateMonitor,
-                    dumpManager,
-                    transitionController,
-                    configurationController,
-                    keyguardStateController,
-                    unlockedScreenOffAnimationController,
-                    dialogManager,
-                    controller,
-                    activityLaunchAnimator,
-                    featureFlags,
-                    primaryBouncerInteractor,
-                    alternateBouncerInteractor,
-                )
+                if (featureFlags.isEnabled(REFACTOR_UDFPS_KEYGUARD_VIEWS)) {
+                    udfpsKeyguardViewModels.get().setSensorBounds(sensorBounds)
+                    UdfpsKeyguardViewController(
+                        view.addUdfpsView(R.layout.udfps_keyguard_view),
+                        statusBarStateController,
+                        shadeExpansionStateManager,
+                        dialogManager,
+                        dumpManager,
+                        alternateBouncerInteractor,
+                        udfpsKeyguardViewModels.get(),
+                    )
+                } else {
+                    UdfpsKeyguardViewControllerLegacy(
+                        view.addUdfpsView(R.layout.udfps_keyguard_view_legacy) {
+                            updateSensorLocation(sensorBounds)
+                        },
+                        statusBarStateController,
+                        shadeExpansionStateManager,
+                        statusBarKeyguardViewManager,
+                        keyguardUpdateMonitor,
+                        dumpManager,
+                        transitionController,
+                        configurationController,
+                        keyguardStateController,
+                        unlockedScreenOffAnimationController,
+                        dialogManager,
+                        controller,
+                        activityLaunchAnimator,
+                        featureFlags,
+                        primaryBouncerInteractor,
+                        alternateBouncerInteractor,
+                        udfpsKeyguardAccessibilityDelegate,
+                    )
+                }
             }
             REASON_AUTH_BP -> {
                 // note: empty controller, currently shows no visual affordance
@@ -413,7 +436,7 @@
     }
 
     private fun shouldRotate(animation: UdfpsAnimationViewController<*>?): Boolean {
-        if (animation !is UdfpsKeyguardViewControllerLegacy) {
+        if (animation !is UdfpsKeyguardViewControllerAdapter) {
             // always rotate view if we're not on the keyguard
             return true
         }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardAccessibilityDelegate.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardAccessibilityDelegate.kt
new file mode 100644
index 0000000..fb7b56e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardAccessibilityDelegate.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics
+
+import android.content.res.Resources
+import android.os.Bundle
+import android.view.View
+import android.view.accessibility.AccessibilityNodeInfo
+import com.android.systemui.R
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
+import javax.inject.Inject
+
+@SysUISingleton
+class UdfpsKeyguardAccessibilityDelegate
+@Inject
+constructor(
+    @Main private val resources: Resources,
+    private val keyguardViewManager: StatusBarKeyguardViewManager,
+) : View.AccessibilityDelegate() {
+    override fun onInitializeAccessibilityNodeInfo(host: View?, info: AccessibilityNodeInfo) {
+        super.onInitializeAccessibilityNodeInfo(host, info)
+        val clickAction =
+            AccessibilityNodeInfo.AccessibilityAction(
+                AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK.id,
+                resources.getString(R.string.accessibility_bouncer)
+            )
+        info.addAction(clickAction)
+    }
+
+    override fun performAccessibilityAction(host: View?, action: Int, args: Bundle?): Boolean {
+        // when an a11y service is enabled, double tapping on the fingerprint sensor should
+        // show the primary bouncer
+        return if (action == AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK.id) {
+            keyguardViewManager.showPrimaryBouncer(/* scrimmed */ true)
+            true
+        } else super.performAccessibilityAction(host, action, args)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.kt
new file mode 100644
index 0000000..8cc15da
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.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.biometrics
+
+import android.content.Context
+import android.util.AttributeSet
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+/** View corresponding with udfps_keyguard_view.xml */
+@ExperimentalCoroutinesApi
+class UdfpsKeyguardView(
+    context: Context,
+    attrs: AttributeSet?,
+) :
+    UdfpsAnimationView(
+        context,
+        attrs,
+    ) {
+    private val fingerprintDrawablePlaceHolder = UdfpsFpDrawable(context)
+    private var visible = false
+
+    override fun calculateAlpha(): Int {
+        return if (mPauseAuth) {
+            0
+        } else 255 // ViewModels handle animating alpha values
+    }
+
+    override fun getDrawable(): UdfpsDrawable {
+        return fingerprintDrawablePlaceHolder
+    }
+
+    fun useExpandedOverlay(useExpandedOverlay: Boolean) {
+        mUseExpandedOverlay = useExpandedOverlay
+    }
+
+    fun isVisible(): Boolean {
+        return visible
+    }
+
+    fun setVisible(isVisible: Boolean) {
+        visible = isVisible
+        isPauseAuth = !visible
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
index 3fc3e82..15bd731 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
@@ -20,6 +20,7 @@
 import android.content.res.Configuration
 import android.util.MathUtils
 import android.view.MotionEvent
+import android.view.View
 import androidx.annotation.VisibleForTesting
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
@@ -28,11 +29,12 @@
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.systemui.R
 import com.android.systemui.animation.ActivityLaunchAnimator
+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.flags.Flags
-import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor
-import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor
+import com.android.systemui.keyguard.ui.adapter.UdfpsKeyguardViewControllerAdapter
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.shade.ShadeExpansionListener
@@ -71,6 +73,7 @@
     featureFlags: FeatureFlags,
     private val primaryBouncerInteractor: PrimaryBouncerInteractor,
     private val alternateBouncerInteractor: AlternateBouncerInteractor,
+    private val udfpsKeyguardAccessibilityDelegate: UdfpsKeyguardAccessibilityDelegate,
 ) :
     UdfpsAnimationViewController<UdfpsKeyguardViewLegacy>(
         view,
@@ -78,7 +81,8 @@
         shadeExpansionStateManager,
         systemUIDialogManager,
         dumpManager,
-    ) {
+    ),
+    UdfpsKeyguardViewControllerAdapter {
     private val useExpandedOverlay: Boolean =
         featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)
     private var showingUdfpsBouncer = false
@@ -300,7 +304,10 @@
         lockScreenShadeTransitionController.mUdfpsKeyguardViewControllerLegacy = this
         activityLaunchAnimator.addListener(activityLaunchAnimatorListener)
         view.mUseExpandedOverlay = useExpandedOverlay
-        view.startIconAsyncInflate()
+        view.startIconAsyncInflate {
+            (view.findViewById(R.id.udfps_animation_view_internal) as View).accessibilityDelegate =
+                udfpsKeyguardAccessibilityDelegate
+        }
     }
 
     override fun onViewDetached() {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacy.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacy.java
index 056d692..b916810c 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacy.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacy.java
@@ -79,6 +79,7 @@
     private float mInterpolatedDarkAmount;
     private int mAnimationType = ANIMATION_NONE;
     private boolean mFullyInflated;
+    private Runnable mOnFinishInflateRunnable;
 
     public UdfpsKeyguardViewLegacy(Context context, @Nullable AttributeSet attrs) {
         super(context, attrs);
@@ -90,7 +91,12 @@
             .getDimensionPixelSize(R.dimen.udfps_burn_in_offset_y);
     }
 
-    public void startIconAsyncInflate() {
+    /**
+     * Inflate internal udfps view on a background thread and call the onFinishRunnable
+     * when inflation is finished.
+     */
+    public void startIconAsyncInflate(Runnable onFinishInflate) {
+        mOnFinishInflateRunnable = onFinishInflate;
         // inflate Lottie views on a background thread in case it takes a while to inflate
         AsyncLayoutInflater inflater = new AsyncLayoutInflater(mContext);
         inflater.inflate(R.layout.udfps_keyguard_view_internal, this,
@@ -330,6 +336,7 @@
                     frameInfo -> new PorterDuffColorFilter(mTextColorPrimary,
                             PorterDuff.Mode.SRC_ATOP)
             );
+            mOnFinishInflateRunnable.run();
         }
     };
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsLogger.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsLogger.kt
index 39199d1..2102a1f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsLogger.kt
@@ -17,10 +17,10 @@
 package com.android.systemui.biometrics
 
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
-import com.android.systemui.log.LogLevel.ERROR
-import com.android.systemui.log.LogLevel.VERBOSE
-import com.android.systemui.log.LogLevel.WARNING
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.core.LogLevel.ERROR
+import com.android.systemui.log.core.LogLevel.VERBOSE
+import com.android.systemui.log.core.LogLevel.WARNING
 import com.android.systemui.log.dagger.UdfpsLog
 import com.google.errorprone.annotations.CompileTimeConstant
 import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt
index ddf1457..a5e846a 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt
@@ -17,6 +17,8 @@
 package com.android.systemui.biometrics.dagger
 
 import com.android.settingslib.udfps.UdfpsUtils
+import com.android.systemui.biometrics.data.repository.FaceSettingsRepository
+import com.android.systemui.biometrics.data.repository.FaceSettingsRepositoryImpl
 import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository
 import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepositoryImpl
 import com.android.systemui.biometrics.data.repository.PromptRepository
@@ -47,6 +49,10 @@
 
     @Binds
     @SysUISingleton
+    fun faceSettings(impl: FaceSettingsRepositoryImpl): FaceSettingsRepository
+
+    @Binds
+    @SysUISingleton
     fun biometricPromptRepository(impl: PromptRepositoryImpl): PromptRepository
 
     @Binds
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FaceSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FaceSettingsRepository.kt
new file mode 100644
index 0000000..3d5ed82
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FaceSettingsRepository.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics.data.repository
+
+import android.os.Handler
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.util.settings.SecureSettings
+import java.util.concurrent.ConcurrentHashMap
+import javax.inject.Inject
+
+/**
+ * Repository for the global state of users Face Unlock preferences.
+ *
+ * Largely a wrapper around [SecureSettings]'s proxy to Settings.Secure.
+ */
+interface FaceSettingsRepository {
+
+    /** Get Settings for the given user [id]. */
+    fun forUser(id: Int?): FaceUserSettingsRepository
+}
+
+@SysUISingleton
+class FaceSettingsRepositoryImpl
+@Inject
+constructor(
+    @Main private val mainHandler: Handler,
+    private val secureSettings: SecureSettings,
+) : FaceSettingsRepository {
+
+    private val userSettings = ConcurrentHashMap<Int, FaceUserSettingsRepository>()
+
+    override fun forUser(id: Int?): FaceUserSettingsRepository =
+        if (id != null) {
+            userSettings.computeIfAbsent(id) { _ ->
+                FaceUserSettingsRepositoryImpl(id, mainHandler, secureSettings).also { repo ->
+                    repo.start()
+                }
+            }
+        } else {
+            FaceUserSettingsRepositoryImpl.Empty
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FaceUserSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FaceUserSettingsRepository.kt
new file mode 100644
index 0000000..68c4a10
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FaceUserSettingsRepository.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.
+ */
+
+package com.android.systemui.biometrics.data.repository
+
+import android.database.ContentObserver
+import android.os.Handler
+import android.provider.Settings.Secure.FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.util.settings.SecureSettings
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.flowOf
+
+/** Settings for a user. */
+interface FaceUserSettingsRepository {
+    /** The user's id. */
+    val userId: Int
+
+    /** If BiometricPrompt should always require confirmation (overrides app's preference). */
+    val alwaysRequireConfirmationInApps: Flow<Boolean>
+}
+
+class FaceUserSettingsRepositoryImpl(
+    override val userId: Int,
+    @Main private val mainHandler: Handler,
+    private val secureSettings: SecureSettings,
+) : FaceUserSettingsRepository {
+
+    /** Indefinitely subscribe to user preference changes. */
+    fun start() {
+        watch(
+            FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION,
+            _alwaysRequireConfirmationInApps,
+        )
+    }
+
+    private var _alwaysRequireConfirmationInApps = MutableStateFlow(false)
+    override val alwaysRequireConfirmationInApps: Flow<Boolean> =
+        _alwaysRequireConfirmationInApps.asStateFlow()
+
+    /** Defaults to use when no user is specified. */
+    object Empty : FaceUserSettingsRepository {
+        override val userId = -1
+        override val alwaysRequireConfirmationInApps = flowOf(false)
+    }
+
+    private fun watch(
+        key: String,
+        toUpdate: MutableStateFlow<Boolean>,
+        defaultValue: Boolean = false,
+    ) = secureSettings.watch(userId, mainHandler, key, defaultValue) { v -> toUpdate.value = v }
+}
+
+private fun SecureSettings.watch(
+    userId: Int,
+    handler: Handler,
+    key: String,
+    defaultValue: Boolean = false,
+    onChange: (Boolean) -> Unit,
+) {
+    fun fetch(): Boolean = getIntForUser(key, if (defaultValue) 1 else 0, userId) > 0
+
+    registerContentObserverForUser(
+        key,
+        false /* notifyForDescendants */,
+        object : ContentObserver(handler) {
+            override fun onChange(selfChange: Boolean) = onChange(fetch())
+        },
+        userId
+    )
+
+    onChange(fetch())
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt
index c43722f..efbde4c 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt
@@ -16,8 +16,11 @@
 
 package com.android.systemui.biometrics.data.repository
 
+import android.hardware.biometrics.ComponentInfoInternal
 import android.hardware.biometrics.SensorLocationInternal
+import android.hardware.biometrics.SensorProperties
 import android.hardware.fingerprint.FingerprintManager
+import android.hardware.fingerprint.FingerprintSensorProperties
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
 import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback
 import com.android.systemui.biometrics.shared.model.FingerprintSensorType
@@ -30,10 +33,8 @@
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.shareIn
 
 /**
@@ -43,22 +44,17 @@
  */
 interface FingerprintPropertyRepository {
 
-    /**
-     * If the repository is initialized or not. Other properties are defaults until this is true.
-     */
-    val isInitialized: Flow<Boolean>
-
     /** The id of fingerprint sensor. */
-    val sensorId: StateFlow<Int>
+    val sensorId: Flow<Int>
 
     /** The security strength of sensor (convenience, weak, strong). */
-    val strength: StateFlow<SensorStrength>
+    val strength: Flow<SensorStrength>
 
     /** The types of fingerprint sensor (rear, ultrasonic, optical, etc.). */
-    val sensorType: StateFlow<FingerprintSensorType>
+    val sensorType: Flow<FingerprintSensorType>
 
     /** The sensor location relative to each physical display. */
-    val sensorLocations: StateFlow<Map<String, SensorLocationInternal>>
+    val sensorLocations: Flow<Map<String, SensorLocationInternal>>
 }
 
 @SysUISingleton
@@ -66,10 +62,10 @@
 @Inject
 constructor(
     @Application private val applicationScope: CoroutineScope,
-    private val fingerprintManager: FingerprintManager
+    private val fingerprintManager: FingerprintManager?
 ) : FingerprintPropertyRepository {
 
-    override val isInitialized: Flow<Boolean> =
+    private val props: Flow<FingerprintSensorPropertiesInternal> =
         conflatedCallbackFlow {
                 val callback =
                     object : IFingerprintAuthenticatorsRegisteredCallback.Stub() {
@@ -77,45 +73,47 @@
                             sensors: List<FingerprintSensorPropertiesInternal>
                         ) {
                             if (sensors.isNotEmpty()) {
-                                setProperties(sensors[0])
-                                trySendWithFailureLogging(true, TAG, "initialize properties")
+                                trySendWithFailureLogging(sensors[0], TAG, "initialize properties")
+                            } else {
+                                trySendWithFailureLogging(
+                                    DEFAULT_PROPS,
+                                    TAG,
+                                    "initialize with default properties"
+                                )
                             }
                         }
                     }
-                fingerprintManager.addAuthenticatorsRegisteredCallback(callback)
-                trySendWithFailureLogging(false, TAG, "initial value defaulting to false")
+                fingerprintManager?.addAuthenticatorsRegisteredCallback(callback)
+                trySendWithFailureLogging(DEFAULT_PROPS, TAG, "initialize with default properties")
                 awaitClose {}
             }
             .shareIn(scope = applicationScope, started = SharingStarted.Eagerly, replay = 1)
 
-    private val _sensorId: MutableStateFlow<Int> = MutableStateFlow(-1)
-    override val sensorId: StateFlow<Int> = _sensorId.asStateFlow()
-
-    private val _strength: MutableStateFlow<SensorStrength> =
-        MutableStateFlow(SensorStrength.CONVENIENCE)
-    override val strength = _strength.asStateFlow()
-
-    private val _sensorType: MutableStateFlow<FingerprintSensorType> =
-        MutableStateFlow(FingerprintSensorType.UNKNOWN)
-    override val sensorType = _sensorType.asStateFlow()
-
-    private val _sensorLocations: MutableStateFlow<Map<String, SensorLocationInternal>> =
-        MutableStateFlow(mapOf("" to SensorLocationInternal.DEFAULT))
-    override val sensorLocations: StateFlow<Map<String, SensorLocationInternal>> =
-        _sensorLocations.asStateFlow()
-
-    private fun setProperties(prop: FingerprintSensorPropertiesInternal) {
-        _sensorId.value = prop.sensorId
-        _strength.value = sensorStrengthIntToObject(prop.sensorStrength)
-        _sensorType.value = sensorTypeIntToObject(prop.sensorType)
-        _sensorLocations.value =
-            prop.allLocations.associateBy { sensorLocationInternal ->
+    override val sensorId: Flow<Int> = props.map { it.sensorId }
+    override val strength: Flow<SensorStrength> =
+        props.map { sensorStrengthIntToObject(it.sensorStrength) }
+    override val sensorType: Flow<FingerprintSensorType> =
+        props.map { sensorTypeIntToObject(it.sensorType) }
+    override val sensorLocations: Flow<Map<String, SensorLocationInternal>> =
+        props.map {
+            it.allLocations.associateBy { sensorLocationInternal ->
                 sensorLocationInternal.displayId
             }
-    }
+        }
 
     companion object {
         private const val TAG = "FingerprintPropertyRepositoryImpl"
+        private val DEFAULT_PROPS =
+            FingerprintSensorPropertiesInternal(
+                -1 /* sensorId */,
+                SensorProperties.STRENGTH_CONVENIENCE,
+                0 /* maxEnrollmentsPerUser */,
+                listOf<ComponentInfoInternal>(),
+                FingerprintSensorProperties.TYPE_UNKNOWN,
+                false /* halControlsIllumination */,
+                true /* resetLockoutRequiresHardwareAuthToken */,
+                listOf<SensorLocationInternal>(SensorLocationInternal.DEFAULT)
+            )
     }
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt
index b4dc272..b35fbbc 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
 package com.android.systemui.biometrics.data.repository
 
 import android.hardware.biometrics.PromptInfo
@@ -12,6 +28,10 @@
 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.flatMapLatest
+import kotlinx.coroutines.flow.map
 
 /**
  * A repository for the global state of BiometricPrompt.
@@ -40,7 +60,7 @@
      *
      * Note: overlaps/conflicts with [PromptInfo.isConfirmationRequested], which needs clean up.
      */
-    val isConfirmationRequired: StateFlow<Boolean>
+    val isConfirmationRequired: Flow<Boolean>
 
     /** Update the prompt configuration, which should be set before [isShowing]. */
     fun setPrompt(
@@ -48,7 +68,6 @@
         userId: Int,
         gatekeeperChallenge: Long?,
         kind: PromptKind,
-        requireConfirmation: Boolean = false,
     )
 
     /** Unset the prompt info. */
@@ -56,8 +75,12 @@
 }
 
 @SysUISingleton
-class PromptRepositoryImpl @Inject constructor(private val authController: AuthController) :
-    PromptRepository {
+class PromptRepositoryImpl
+@Inject
+constructor(
+    private val faceSettings: FaceSettingsRepository,
+    private val authController: AuthController,
+) : PromptRepository {
 
     override val isShowing: Flow<Boolean> = conflatedCallbackFlow {
         val callback =
@@ -85,21 +108,30 @@
     private val _kind: MutableStateFlow<PromptKind> = MutableStateFlow(PromptKind.Biometric())
     override val kind = _kind.asStateFlow()
 
-    private val _isConfirmationRequired: MutableStateFlow<Boolean> = MutableStateFlow(false)
-    override val isConfirmationRequired = _isConfirmationRequired.asStateFlow()
+    private val _faceSettings =
+        _userId.map { id -> faceSettings.forUser(id) }.distinctUntilChanged()
+    private val _faceSettingAlwaysRequireConfirmation =
+        _faceSettings.flatMapLatest { it.alwaysRequireConfirmationInApps }.distinctUntilChanged()
+
+    private val _isConfirmationRequired = _promptInfo.map { it?.isConfirmationRequested ?: false }
+    override val isConfirmationRequired =
+        combine(_isConfirmationRequired, _faceSettingAlwaysRequireConfirmation) {
+                appRequiresConfirmation,
+                forceRequireConfirmation ->
+                forceRequireConfirmation || appRequiresConfirmation
+            }
+            .distinctUntilChanged()
 
     override fun setPrompt(
         promptInfo: PromptInfo,
         userId: Int,
         gatekeeperChallenge: Long?,
         kind: PromptKind,
-        requireConfirmation: Boolean,
     ) {
         _kind.value = kind
         _userId.value = userId
         _challenge.value = gatekeeperChallenge
         _promptInfo.value = promptInfo
-        _isConfirmationRequired.value = requireConfirmation
     }
 
     override fun unsetPrompt() {
@@ -107,7 +139,6 @@
         _userId.value = null
         _challenge.value = null
         _kind.value = PromptKind.Biometric()
-        _isConfirmationRequired.value = false
     }
 
     companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt
index d92c217..ac4b717 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt
@@ -23,7 +23,7 @@
 import com.android.systemui.biometrics.data.repository.PromptRepository
 import com.android.systemui.biometrics.domain.model.BiometricOperationInfo
 import com.android.systemui.biometrics.domain.model.BiometricPromptRequest
-import com.android.systemui.biometrics.domain.model.BiometricUserInfo
+import com.android.systemui.biometrics.shared.model.BiometricUserInfo
 import com.android.systemui.biometrics.shared.model.PromptKind
 import com.android.systemui.dagger.qualifiers.Background
 import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt
index e6e07f9..bb87dca 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt
@@ -25,7 +25,7 @@
 import com.android.systemui.biometrics.domain.model.BiometricModalities
 import com.android.systemui.biometrics.domain.model.BiometricOperationInfo
 import com.android.systemui.biometrics.domain.model.BiometricPromptRequest
-import com.android.systemui.biometrics.domain.model.BiometricUserInfo
+import com.android.systemui.biometrics.shared.model.BiometricUserInfo
 import com.android.systemui.biometrics.shared.model.PromptKind
 import com.android.systemui.dagger.SysUISingleton
 import javax.inject.Inject
@@ -59,13 +59,15 @@
      */
     val credentialKind: Flow<PromptKind>
 
-    /** If the API caller requested explicit confirmation after successful authentication. */
-    val isConfirmationRequested: Flow<Boolean>
+    /**
+     * If the API caller or the user's personal preferences require explicit confirmation after
+     * successful authentication.
+     */
+    val isConfirmationRequired: Flow<Boolean>
 
     /** Use biometrics for authentication. */
     fun useBiometricsForAuthentication(
         promptInfo: PromptInfo,
-        requireConfirmation: Boolean,
         userId: Int,
         challenge: Long,
         modalities: BiometricModalities,
@@ -114,10 +116,8 @@
             }
         }
 
-    override val isConfirmationRequested: Flow<Boolean> =
-        promptRepository.promptInfo
-            .map { info -> info?.isConfirmationRequested ?: false }
-            .distinctUntilChanged()
+    override val isConfirmationRequired: Flow<Boolean> =
+        promptRepository.isConfirmationRequired.distinctUntilChanged()
 
     override val isCredentialAllowed: Flow<Boolean> =
         promptRepository.promptInfo
@@ -142,7 +142,6 @@
 
     override fun useBiometricsForAuthentication(
         promptInfo: PromptInfo,
-        requireConfirmation: Boolean,
         userId: Int,
         challenge: Long,
         modalities: BiometricModalities
@@ -152,7 +151,6 @@
             userId = userId,
             gatekeeperChallenge = challenge,
             kind = PromptKind.Biometric(modalities),
-            requireConfirmation = requireConfirmation,
         )
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractor.kt
index aa85e5f3..37f39cb 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractor.kt
@@ -17,16 +17,24 @@
 package com.android.systemui.biometrics.domain.interactor
 
 import android.hardware.biometrics.SensorLocationInternal
-import android.util.Log
 import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository
 import com.android.systemui.dagger.SysUISingleton
 import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.combine
 
 /** Business logic for SideFps overlay offsets. */
 interface SideFpsOverlayInteractor {
+    /** The displayId of the current display. */
+    val displayId: Flow<String>
 
-    /** Get the corresponding offsets based on different displayId. */
-    fun getOverlayOffsets(displayId: String): SensorLocationInternal
+    /** The corresponding offsets based on different displayId. */
+    val overlayOffsets: Flow<SensorLocationInternal>
+
+    /** Update the displayId. */
+    fun changeDisplay(displayId: String?)
 }
 
 @SysUISingleton
@@ -35,14 +43,16 @@
 constructor(private val fingerprintPropertyRepository: FingerprintPropertyRepository) :
     SideFpsOverlayInteractor {
 
-    override fun getOverlayOffsets(displayId: String): SensorLocationInternal {
-        val offsets = fingerprintPropertyRepository.sensorLocations.value
-        return if (offsets.containsKey(displayId)) {
-            offsets[displayId]!!
-        } else {
-            Log.w(TAG, "No location specified for display: $displayId")
-            offsets[""]!!
+    private val _displayId: MutableStateFlow<String> = MutableStateFlow("")
+    override val displayId: Flow<String> = _displayId.asStateFlow()
+
+    override val overlayOffsets: Flow<SensorLocationInternal> =
+        combine(displayId, fingerprintPropertyRepository.sensorLocations) { displayId, offsets ->
+            offsets[displayId] ?: SensorLocationInternal.DEFAULT
         }
+
+    override fun changeDisplay(displayId: String?) {
+        _displayId.value = displayId ?: ""
     }
 
     companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricModality.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricModality.kt
deleted file mode 100644
index 3197c09..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricModality.kt
+++ /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 com.android.systemui.biometrics.domain.model
-
-import android.hardware.biometrics.BiometricAuthenticator
-
-/** Shadows [BiometricAuthenticator.Modality] for Kotlin use within SysUI. */
-enum class BiometricModality {
-    None,
-    Fingerprint,
-    Face,
-}
-
-/** Convert a framework [BiometricAuthenticator.Modality] to a SysUI [BiometricModality]. */
-@BiometricAuthenticator.Modality
-fun Int.asBiometricModality(): BiometricModality =
-    when (this) {
-        BiometricAuthenticator.TYPE_FINGERPRINT -> BiometricModality.Fingerprint
-        BiometricAuthenticator.TYPE_FACE -> BiometricModality.Face
-        else -> BiometricModality.None
-    }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt
index 75de47d..caebc30 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt
@@ -1,6 +1,7 @@
 package com.android.systemui.biometrics.domain.model
 
 import android.hardware.biometrics.PromptInfo
+import com.android.systemui.biometrics.shared.model.BiometricUserInfo
 
 /**
  * Preferences for BiometricPrompt, such as title & description, that are immutable while the prompt
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricUserInfo.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricUserInfo.kt
deleted file mode 100644
index 08da04d..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricUserInfo.kt
+++ /dev/null
@@ -1,7 +0,0 @@
-package com.android.systemui.biometrics.domain.model
-
-/** Metadata about the current user BiometricPrompt is being shown to. */
-data class BiometricUserInfo(
-    val userId: Int,
-    val deviceCredentialOwnerId: Int = userId,
-)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/BiometricModality.kt b/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/BiometricModality.kt
new file mode 100644
index 0000000..fb580ca
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/BiometricModality.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.biometrics.shared.model
+
+import android.hardware.biometrics.BiometricAuthenticator
+
+/** Shadows [BiometricAuthenticator.Modality] for Kotlin use within SysUI. */
+enum class BiometricModality {
+    None,
+    Fingerprint,
+    Face,
+}
+
+/** Convert a framework [BiometricAuthenticator.Modality] to a SysUI [BiometricModality]. */
+@BiometricAuthenticator.Modality
+fun Int.asBiometricModality(): BiometricModality =
+    when (this) {
+        BiometricAuthenticator.TYPE_FINGERPRINT -> BiometricModality.Fingerprint
+        BiometricAuthenticator.TYPE_FACE -> BiometricModality.Face
+        else -> BiometricModality.None
+    }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/BiometricUserInfo.kt b/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/BiometricUserInfo.kt
new file mode 100644
index 0000000..39689ec
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/BiometricUserInfo.kt
@@ -0,0 +1,12 @@
+package com.android.systemui.biometrics.shared.model
+
+/**
+ * Metadata about the current user BiometricPrompt is being shown to.
+ *
+ * If the user's fallback credential is owned by another profile user the [deviceCredentialOwnerId]
+ * will differ from the user's [userId].
+ */
+data class BiometricUserInfo(
+    val userId: Int,
+    val deviceCredentialOwnerId: Int = userId,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
index 8486c3f..e5a4d1a 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
@@ -46,9 +46,9 @@
 import com.android.systemui.biometrics.AuthPanelController
 import com.android.systemui.biometrics.Utils
 import com.android.systemui.biometrics.domain.model.BiometricModalities
-import com.android.systemui.biometrics.domain.model.BiometricModality
-import com.android.systemui.biometrics.domain.model.asBiometricModality
+import com.android.systemui.biometrics.shared.model.BiometricModality
 import com.android.systemui.biometrics.shared.model.PromptKind
+import com.android.systemui.biometrics.shared.model.asBiometricModality
 import com.android.systemui.biometrics.ui.BiometricPromptLayout
 import com.android.systemui.biometrics.ui.viewmodel.FingerprintStartMode
 import com.android.systemui.biometrics.ui.viewmodel.PromptMessage
@@ -158,7 +158,7 @@
                 view.updateFingerprintAffordanceSize(iconController)
             }
             if (iconController is HackyCoexIconController) {
-                iconController.faceMode = !viewModel.isConfirmationRequested.first()
+                iconController.faceMode = !viewModel.isConfirmationRequired.first()
             }
 
             // the icon controller must be created before this happens for the legacy
@@ -339,7 +339,13 @@
 
                             launch {
                                 delay(authState.delay)
-                                legacyCallback.onAction(Callback.ACTION_AUTHENTICATED)
+                                legacyCallback.onAction(
+                                    if (authState.isAuthenticatedAndExplicitlyConfirmed) {
+                                        Callback.ACTION_AUTHENTICATED_AND_CONFIRMED
+                                    } else {
+                                        Callback.ACTION_AUTHENTICATED
+                                    }
+                                )
                             }
                         }
                     }
@@ -390,7 +396,6 @@
 
     private var lifecycleScope: CoroutineScope? = null
     private var modalities: BiometricModalities = BiometricModalities()
-    private var faceFailedAtLeastOnce = false
     private var legacyCallback: Callback? = null
 
     override var legacyIconController: AuthIconController? = null
@@ -470,19 +475,15 @@
         viewModel.ensureFingerprintHasStarted(isDelayed = true)
 
         applicationScope.launch {
-            val suppress =
-                modalities.hasFaceAndFingerprint &&
-                    (failedModality == BiometricModality.Face) &&
-                    faceFailedAtLeastOnce
-            if (failedModality == BiometricModality.Face) {
-                faceFailedAtLeastOnce = true
-            }
-
             viewModel.showTemporaryError(
                 failureReason,
                 messageAfterError = modalities.asDefaultHelpMessage(applicationContext),
                 authenticateAfterError = modalities.hasFingerprint,
-                suppressIfErrorShowing = suppress,
+                suppressIf = { currentMessage ->
+                    modalities.hasFaceAndFingerprint &&
+                        failedModality == BiometricModality.Face &&
+                        currentMessage.isError
+                },
                 failedModality = failedModality,
             )
         }
@@ -495,11 +496,10 @@
         }
 
         applicationScope.launch {
-            val suppress =
-                modalities.hasFaceAndFingerprint && (errorModality == BiometricModality.Face)
             viewModel.showTemporaryError(
                 error,
-                suppressIfErrorShowing = suppress,
+                messageAfterError = modalities.asDefaultHelpMessage(applicationContext),
+                authenticateAfterError = modalities.hasFingerprint,
             )
             delay(BiometricPrompt.HIDE_DIALOG_DELAY.toLong())
             legacyCallback?.onAction(Callback.ACTION_ERROR)
@@ -512,9 +512,12 @@
         }
 
         applicationScope.launch {
-            viewModel.showTemporaryHelp(
+            // help messages from the HAL should be displayed as temporary (i.e. soft) errors
+            viewModel.showTemporaryError(
                 help,
-                messageAfterHelp = modalities.asDefaultHelpMessage(applicationContext),
+                messageAfterError = modalities.asDefaultHelpMessage(applicationContext),
+                authenticateAfterError = modalities.hasFingerprint,
+                hapticFeedback = false,
             )
         }
     }
@@ -527,7 +530,7 @@
             else -> false
         }
 
-    override fun startTransitionToCredentialUI() {
+    override fun startTransitionToCredentialUI(isError: Boolean) {
         applicationScope.launch {
             viewModel.onSwitchToCredential()
             legacyCallback?.onAction(Callback.ACTION_USE_DEVICE_CREDENTIAL)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
index 1dffa80..1a286cf 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
@@ -28,6 +28,7 @@
 import android.widget.TextView
 import androidx.core.animation.addListener
 import androidx.core.view.doOnLayout
+import androidx.core.view.isGone
 import androidx.lifecycle.lifecycleScope
 import com.android.systemui.R
 import com.android.systemui.biometrics.AuthDialog
@@ -78,9 +79,11 @@
 
         // cache the original position of the icon view (as done in legacy view)
         // this must happen before any size changes can be made
-        var iconHolderOriginalY = 0f
         view.doOnLayout {
-            iconHolderOriginalY = iconHolderView.y
+            // TODO(b/251476085): this old way of positioning has proven itself unreliable
+            // remove this and associated thing like (UdfpsDialogMeasureAdapter) and
+            // pin to the physical sensor
+            val iconHolderOriginalY = iconHolderView.y
 
             // bind to prompt
             // TODO(b/251476085): migrate the legacy panel controller and simplify this
@@ -141,7 +144,11 @@
                                         listOf(
                                             iconHolderView.asVerticalAnimator(
                                                 duration = duration.toLong(),
-                                                toY = iconHolderOriginalY,
+                                                toY =
+                                                    iconHolderOriginalY -
+                                                        viewsToHideWhenSmall
+                                                            .filter { it.isGone }
+                                                            .sumOf { it.height },
                                             ),
                                             viewsToFadeInOnSizeChange.asFadeInAnimator(
                                                 duration = duration.toLong(),
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPasswordViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPasswordViewBinder.kt
index 6fb8e34..c27d7152 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPasswordViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPasswordViewBinder.kt
@@ -1,5 +1,6 @@
 package com.android.systemui.biometrics.ui.binder
 
+import android.os.UserHandle
 import android.view.KeyEvent
 import android.view.View
 import android.view.inputmethod.EditorInfo
@@ -9,6 +10,7 @@
 import android.window.OnBackInvokedCallback
 import android.window.OnBackInvokedDispatcher
 import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.lifecycleScope
 import androidx.lifecycle.repeatOnLifecycle
 import com.android.systemui.R
 import com.android.systemui.biometrics.ui.CredentialPasswordView
@@ -16,6 +18,8 @@
 import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel
 import com.android.systemui.lifecycle.repeatWhenAttached
 import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.firstOrNull
 import kotlinx.coroutines.launch
 
 /** Sub-binder for the [CredentialPasswordView]. */
@@ -35,31 +39,22 @@
         val onBackInvokedCallback = OnBackInvokedCallback { host.onCredentialAborted() }
 
         view.repeatWhenAttached {
+            // the header info never changes - do it early
+            val header = viewModel.header.first()
+            passwordField.setTextOperationUser(UserHandle.of(header.user.deviceCredentialOwnerId))
+            viewModel.inputFlags.firstOrNull()?.let { flags -> passwordField.inputType = flags }
             if (requestFocusForInput) {
                 passwordField.requestFocus()
                 passwordField.scheduleShowSoftInput()
             }
+            passwordField.setOnEditorActionListener(
+                OnImeSubmitListener { text ->
+                    lifecycleScope.launch { viewModel.checkCredential(text, header) }
+                }
+            )
+            passwordField.setOnKeyListener(OnBackButtonListener(onBackInvokedCallback))
 
             repeatOnLifecycle(Lifecycle.State.STARTED) {
-                // observe credential validation attempts and submit/cancel buttons
-                launch {
-                    viewModel.header.collect { header ->
-                        passwordField.setTextOperationUser(header.user)
-                        passwordField.setOnEditorActionListener(
-                            OnImeSubmitListener { text ->
-                                launch { viewModel.checkCredential(text, header) }
-                            }
-                        )
-                        passwordField.setOnKeyListener(OnBackButtonListener(onBackInvokedCallback))
-                    }
-                }
-
-                launch {
-                    viewModel.inputFlags.collect { flags ->
-                        flags?.let { passwordField.inputType = it }
-                    }
-                }
-
                 // dismiss on a valid credential check
                 launch {
                     viewModel.validatedAttestation.collect { attestation ->
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt
new file mode 100644
index 0000000..0409519
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics.ui.binder
+
+import android.content.Context
+import android.content.res.Configuration
+import android.graphics.Color
+import android.graphics.PorterDuff
+import android.graphics.PorterDuffColorFilter
+import android.hardware.biometrics.BiometricOverlayConstants
+import android.view.View
+import android.view.WindowManager
+import android.view.accessibility.AccessibilityEvent
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.airbnb.lottie.LottieAnimationView
+import com.airbnb.lottie.LottieProperty
+import com.airbnb.lottie.model.KeyPath
+import com.android.systemui.R
+import com.android.systemui.biometrics.ui.viewmodel.SideFpsOverlayViewModel
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.lifecycle.repeatWhenAttached
+import kotlinx.coroutines.launch
+
+/** Sub-binder for SideFpsOverlayView. */
+object SideFpsOverlayViewBinder {
+
+    /** Bind the view. */
+    @JvmStatic
+    fun bind(
+        view: View,
+        viewModel: SideFpsOverlayViewModel,
+        overlayViewParams: WindowManager.LayoutParams,
+        @BiometricOverlayConstants.ShowReason reason: Int,
+        @Application context: Context
+    ) {
+        val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
+
+        val lottie = view.findViewById(R.id.sidefps_animation) as LottieAnimationView
+
+        viewModel.changeDisplay()
+
+        view.repeatWhenAttached {
+            repeatOnLifecycle(Lifecycle.State.STARTED) {
+                launch {
+                    viewModel.sideFpsAnimationRotation.collect { rotation ->
+                        view.rotation = rotation
+                    }
+                }
+
+                launch {
+                    // TODO(b/221037350, wenhuiy): Create a separate ViewBinder for sideFpsAnimation
+                    // in order to add scuba tests in the future.
+                    viewModel.sideFpsAnimation.collect { animation ->
+                        lottie.setAnimation(animation)
+                    }
+                }
+
+                launch {
+                    viewModel.sensorBounds.collect { sensorBounds ->
+                        overlayViewParams.x = sensorBounds.left
+                        overlayViewParams.y = sensorBounds.top
+
+                        windowManager.updateViewLayout(view, overlayViewParams)
+                    }
+                }
+
+                launch {
+                    viewModel.overlayOffsets.collect { overlayOffsets ->
+                        lottie.addLottieOnCompositionLoadedListener {
+                            viewModel.updateSensorBounds(
+                                it.bounds,
+                                windowManager.maximumWindowMetrics.bounds,
+                                overlayOffsets
+                            )
+                        }
+                    }
+                }
+            }
+        }
+
+        lottie.addOverlayDynamicColor(context, reason)
+
+        /**
+         * Intercepts TYPE_WINDOW_STATE_CHANGED accessibility event, preventing Talkback from
+         * speaking @string/accessibility_fingerprint_label twice when sensor location indicator is
+         * in focus
+         */
+        view.accessibilityDelegate =
+            object : View.AccessibilityDelegate() {
+                override fun dispatchPopulateAccessibilityEvent(
+                    host: View,
+                    event: AccessibilityEvent
+                ): Boolean {
+                    return if (
+                        event.getEventType() === AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED
+                    ) {
+                        true
+                    } else {
+                        super.dispatchPopulateAccessibilityEvent(host, event)
+                    }
+                }
+            }
+    }
+}
+
+private fun LottieAnimationView.addOverlayDynamicColor(
+    context: Context,
+    @BiometricOverlayConstants.ShowReason reason: Int
+) {
+    fun update() {
+        val isKeyguard = reason == BiometricOverlayConstants.REASON_AUTH_KEYGUARD
+        if (isKeyguard) {
+            val color = context.getColor(R.color.numpad_key_color_secondary) // match bouncer color
+            val chevronFill =
+                com.android.settingslib.Utils.getColorAttrDefaultColor(
+                    context,
+                    android.R.attr.textColorPrimaryInverse
+                )
+            for (key in listOf(".blue600", ".blue400")) {
+                addValueCallback(KeyPath(key, "**"), LottieProperty.COLOR_FILTER) {
+                    PorterDuffColorFilter(color, PorterDuff.Mode.SRC_ATOP)
+                }
+            }
+            addValueCallback(KeyPath(".black", "**"), LottieProperty.COLOR_FILTER) {
+                PorterDuffColorFilter(chevronFill, PorterDuff.Mode.SRC_ATOP)
+            }
+        } else if (!isDarkMode(context)) {
+            addValueCallback(KeyPath(".black", "**"), LottieProperty.COLOR_FILTER) {
+                PorterDuffColorFilter(Color.WHITE, PorterDuff.Mode.SRC_ATOP)
+            }
+        } else if (isDarkMode(context)) {
+            for (key in listOf(".blue600", ".blue400")) {
+                addValueCallback(KeyPath(key, "**"), LottieProperty.COLOR_FILTER) {
+                    PorterDuffColorFilter(
+                        context.getColor(R.color.settingslib_color_blue400),
+                        PorterDuff.Mode.SRC_ATOP
+                    )
+                }
+            }
+        }
+    }
+
+    if (composition != null) {
+        update()
+    } else {
+        addLottieOnCompositionLoadedListener { update() }
+    }
+}
+
+private fun isDarkMode(context: Context): Boolean {
+    val darkMode = context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
+    return darkMode == Configuration.UI_MODE_NIGHT_YES
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/controller/UdfpsKeyguardViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/controller/UdfpsKeyguardViewController.kt
new file mode 100644
index 0000000..2a9f3ea
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/controller/UdfpsKeyguardViewController.kt
@@ -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.systemui.biometrics.ui.controller
+
+import com.android.systemui.biometrics.UdfpsAnimationViewController
+import com.android.systemui.biometrics.UdfpsKeyguardView
+import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.keyguard.ui.adapter.UdfpsKeyguardViewControllerAdapter
+import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModels
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.shade.ShadeExpansionStateManager
+import com.android.systemui.statusbar.phone.SystemUIDialogManager
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+/** Class that coordinates non-HBM animations during keyguard authentication. */
+@ExperimentalCoroutinesApi
+open class UdfpsKeyguardViewController(
+    val view: UdfpsKeyguardView,
+    statusBarStateController: StatusBarStateController,
+    shadeExpansionStateManager: ShadeExpansionStateManager,
+    systemUIDialogManager: SystemUIDialogManager,
+    dumpManager: DumpManager,
+    private val alternateBouncerInteractor: AlternateBouncerInteractor,
+    udfpsKeyguardViewModels: UdfpsKeyguardViewModels,
+) :
+    UdfpsAnimationViewController<UdfpsKeyguardView>(
+        view,
+        statusBarStateController,
+        shadeExpansionStateManager,
+        systemUIDialogManager,
+        dumpManager,
+    ),
+    UdfpsKeyguardViewControllerAdapter {
+    override val tag: String
+        get() = TAG
+
+    init {
+        udfpsKeyguardViewModels.bindViews(view)
+    }
+
+    public override fun onViewAttached() {
+        super.onViewAttached()
+        alternateBouncerInteractor.setAlternateBouncerUIAvailable(true)
+    }
+
+    public override fun onViewDetached() {
+        super.onViewDetached()
+        alternateBouncerInteractor.setAlternateBouncerUIAvailable(false)
+    }
+
+    override fun shouldPauseAuth(): Boolean {
+        return !view.isVisible()
+    }
+
+    override fun listenForTouchesOutsideView(): Boolean {
+        return true
+    }
+
+    companion object {
+        private const val TAG = "UdfpsKeyguardViewController"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/CredentialHeaderViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/CredentialHeaderViewModel.kt
index a64798c..3257f20 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/CredentialHeaderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/CredentialHeaderViewModel.kt
@@ -1,11 +1,11 @@
 package com.android.systemui.biometrics.ui.viewmodel
 
 import android.graphics.drawable.Drawable
-import android.os.UserHandle
+import com.android.systemui.biometrics.shared.model.BiometricUserInfo
 
 /** View model for the top-level header / info area of BiometricPrompt. */
 interface CredentialHeaderViewModel {
-    val user: UserHandle
+    val user: BiometricUserInfo
     val title: String
     val subtitle: String
     val description: String
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/CredentialViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/CredentialViewModel.kt
index 9d7b940..a3b23ca 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/CredentialViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/CredentialViewModel.kt
@@ -2,7 +2,6 @@
 
 import android.content.Context
 import android.graphics.drawable.Drawable
-import android.os.UserHandle
 import android.text.InputType
 import com.android.internal.widget.LockPatternView
 import com.android.systemui.R
@@ -10,6 +9,7 @@
 import com.android.systemui.biometrics.domain.interactor.CredentialStatus
 import com.android.systemui.biometrics.domain.interactor.PromptCredentialInteractor
 import com.android.systemui.biometrics.domain.model.BiometricPromptRequest
+import com.android.systemui.biometrics.shared.model.BiometricUserInfo
 import com.android.systemui.dagger.qualifiers.Application
 import javax.inject.Inject
 import kotlin.reflect.KClass
@@ -36,7 +36,7 @@
             request ->
             BiometricPromptHeaderViewModelImpl(
                 request,
-                user = UserHandle.of(request.userInfo.userId),
+                user = request.userInfo,
                 title = request.title,
                 subtitle = request.subtitle,
                 description = request.description,
@@ -169,7 +169,7 @@
 
 private class BiometricPromptHeaderViewModelImpl(
     val request: BiometricPromptRequest.Credential,
-    override val user: UserHandle,
+    override val user: BiometricUserInfo,
     override val title: String,
     override val subtitle: String,
     override val description: String,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptAuthState.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptAuthState.kt
index 9cb91b3..2f9557f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptAuthState.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptAuthState.kt
@@ -16,7 +16,7 @@
 
 package com.android.systemui.biometrics.ui.viewmodel
 
-import com.android.systemui.biometrics.domain.model.BiometricModality
+import com.android.systemui.biometrics.shared.model.BiometricModality
 
 /**
  * The authenticated state with the [authenticatedModality] (when [isAuthenticated]) with an
@@ -29,10 +29,16 @@
     val needsUserConfirmation: Boolean = false,
     val delay: Long = 0,
 ) {
+    private var wasConfirmed = false
+
     /** If authentication was successful and the user has confirmed (or does not need to). */
     val isAuthenticatedAndConfirmed: Boolean
         get() = isAuthenticated && !needsUserConfirmation
 
+    /** Same as [isAuthenticatedAndConfirmed] but only true if the user clicked a confirm button. */
+    val isAuthenticatedAndExplicitlyConfirmed: Boolean
+        get() = isAuthenticated && wasConfirmed
+
     /** If a successful authentication has not occurred. */
     val isNotAuthenticated: Boolean
         get() = !isAuthenticated
@@ -45,12 +51,16 @@
     val isAuthenticatedByFingerprint: Boolean
         get() = isAuthenticated && authenticatedModality == BiometricModality.Fingerprint
 
-    /** Copies this state, but toggles [needsUserConfirmation] to false. */
-    fun asConfirmed(): PromptAuthState =
+    /**
+     * Copies this state, but toggles [needsUserConfirmation] to false and ensures that
+     * [isAuthenticatedAndExplicitlyConfirmed] is true.
+     */
+    fun asExplicitlyConfirmed(): PromptAuthState =
         PromptAuthState(
-            isAuthenticated = isAuthenticated,
-            authenticatedModality = authenticatedModality,
-            needsUserConfirmation = false,
-            delay = delay,
-        )
+                isAuthenticated = isAuthenticated,
+                authenticatedModality = authenticatedModality,
+                needsUserConfirmation = false,
+                delay = delay,
+            )
+            .apply { wasConfirmed = true }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptMessage.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptMessage.kt
index 219da71..50f4911 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptMessage.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptMessage.kt
@@ -33,9 +33,9 @@
                 else -> ""
             }
 
-    /** If this is an [Error] or [Help] message. */
-    val isErrorOrHelp: Boolean
-        get() = this is Error || this is Help
+    /** If this is an [Error]. */
+    val isError: Boolean
+        get() = this is Error
 
     /** An error message. */
     data class Error(val errorMessage: String) : PromptMessage
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
index 2f8ed09..8a2e405 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
@@ -20,8 +20,9 @@
 import com.android.systemui.biometrics.AuthBiometricView
 import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor
 import com.android.systemui.biometrics.domain.model.BiometricModalities
-import com.android.systemui.biometrics.domain.model.BiometricModality
+import com.android.systemui.biometrics.shared.model.BiometricModality
 import com.android.systemui.biometrics.shared.model.PromptKind
+import com.android.systemui.statusbar.VibratorHelper
 import javax.inject.Inject
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.coroutineScope
@@ -41,6 +42,7 @@
 @Inject
 constructor(
     private val interactor: PromptSelectorInteractor,
+    private val vibrator: VibratorHelper,
 ) {
     /** The set of modalities available for this prompt */
     val modalities: Flow<BiometricModalities> =
@@ -61,8 +63,11 @@
     /** If the user has successfully authenticated and confirmed (when explicitly required). */
     val isAuthenticated: Flow<PromptAuthState> = _isAuthenticated.asStateFlow()
 
-    /** If the API caller requested explicit confirmation after successful authentication. */
-    val isConfirmationRequested: Flow<Boolean> = interactor.isConfirmationRequested
+    /**
+     * If the API caller or the user's personal preferences require explicit confirmation after
+     * successful authentication.
+     */
+    val isConfirmationRequired: Flow<Boolean> = interactor.isConfirmationRequired
 
     /** The kind of credential the user has. */
     val credentialKind: Flow<PromptKind> = interactor.credentialKind
@@ -91,7 +96,7 @@
                 _forceLargeSize,
                 _forceMediumSize,
                 modalities,
-                interactor.isConfirmationRequested,
+                interactor.isConfirmationRequired,
                 fingerprintStartMode,
             ) { forceLarge, forceMedium, modalities, confirmationRequired, fpStartMode ->
                 when {
@@ -202,37 +207,43 @@
     private var messageJob: Job? = null
 
     /**
-     * Show a temporary error [message] associated with an optional [failedModality].
+     * Show a temporary error [message] associated with an optional [failedModality] and play
+     * [hapticFeedback].
      *
-     * An optional [messageAfterError] will be shown via [showAuthenticating] when
-     * [authenticateAfterError] is set (or via [showHelp] when not set) after the error is
-     * dismissed.
+     * The [messageAfterError] will be shown via [showAuthenticating] when [authenticateAfterError]
+     * is set (or via [showHelp] when not set) after the error is dismissed.
      *
-     * The error is ignored if the user has already authenticated and it is treated as
-     * [onSilentError] if [suppressIfErrorShowing] is set and an error message is already showing.
+     * The error is ignored if the user has already authenticated or if [suppressIf] is true given
+     * the currently showing [PromptMessage].
      */
     suspend fun showTemporaryError(
         message: String,
-        messageAfterError: String = "",
-        authenticateAfterError: Boolean = false,
-        suppressIfErrorShowing: Boolean = false,
+        messageAfterError: String,
+        authenticateAfterError: Boolean,
+        suppressIf: (PromptMessage) -> Boolean = { false },
+        hapticFeedback: Boolean = true,
         failedModality: BiometricModality = BiometricModality.None,
     ) = coroutineScope {
         if (_isAuthenticated.value.isAuthenticated) {
             return@coroutineScope
         }
-        if (_message.value.isErrorOrHelp && suppressIfErrorShowing) {
-            onSilentError(failedModality)
+
+        _canTryAgainNow.value = supportsRetry(failedModality)
+
+        if (suppressIf(_message.value)) {
             return@coroutineScope
         }
 
         _isAuthenticating.value = false
         _isAuthenticated.value = PromptAuthState(false)
         _forceMediumSize.value = true
-        _canTryAgainNow.value = supportsRetry(failedModality)
         _message.value = PromptMessage.Error(message)
         _legacyState.value = AuthBiometricView.STATE_ERROR
 
+        if (hapticFeedback) {
+            vibrator.error(failedModality)
+        }
+
         messageJob?.cancel()
         messageJob = launch {
             delay(BiometricPrompt.HIDE_DIALOG_DELAY.toLong())
@@ -245,18 +256,6 @@
     }
 
     /**
-     * Call instead of [showTemporaryError] if an error from the HAL should be silently ignored to
-     * enable retry (if the [failedModality] supports retrying).
-     *
-     * Ignored if the user has already authenticated.
-     */
-    private fun onSilentError(failedModality: BiometricModality = BiometricModality.None) {
-        if (_isAuthenticated.value.isNotAuthenticated) {
-            _canTryAgainNow.value = supportsRetry(failedModality)
-        }
-    }
-
-    /**
      * Call to ensure the fingerprint sensor has started. Either when the dialog is first shown
      * (most cases) or when it should be enabled after a first error (coex implicit flow).
      */
@@ -373,6 +372,10 @@
                 AuthBiometricView.STATE_AUTHENTICATED
             }
 
+        if (!needsUserConfirmation) {
+            vibrator.success(modality)
+        }
+
         messageJob?.cancel()
         messageJob = null
 
@@ -383,18 +386,18 @@
 
     private suspend fun needsExplicitConfirmation(modality: BiometricModality): Boolean {
         val availableModalities = modalities.first()
-        val confirmationRequested = interactor.isConfirmationRequested.first()
+        val confirmationRequired = isConfirmationRequired.first()
 
         if (availableModalities.hasFaceAndFingerprint) {
             // coex only needs confirmation when face is successful, unless it happens on the
             // first attempt (i.e. without failure) before fingerprint scanning starts
+            val fingerprintStarted = fingerprintStartMode.first() != FingerprintStartMode.Pending
             if (modality == BiometricModality.Face) {
-                return (fingerprintStartMode.first() != FingerprintStartMode.Pending) ||
-                    confirmationRequested
+                return fingerprintStarted || confirmationRequired
             }
         }
         if (availableModalities.hasFaceOnly) {
-            return confirmationRequested
+            return confirmationRequired
         }
         // fingerprint only never requires confirmation
         return false
@@ -409,15 +412,16 @@
     fun confirmAuthenticated() {
         val authState = _isAuthenticated.value
         if (authState.isNotAuthenticated) {
-            "Cannot show authenticated after authenticated"
             Log.w(TAG, "Cannot confirm authenticated when not authenticated")
             return
         }
 
-        _isAuthenticated.value = authState.asConfirmed()
+        _isAuthenticated.value = authState.asExplicitlyConfirmed()
         _message.value = PromptMessage.Empty
         _legacyState.value = AuthBiometricView.STATE_AUTHENTICATED
 
+        vibrator.success(authState.authenticatedModality)
+
         messageJob?.cancel()
         messageJob = null
     }
@@ -431,6 +435,12 @@
         _forceLargeSize.value = true
     }
 
+    private fun VibratorHelper.success(modality: BiometricModality) =
+        vibrateAuthSuccess("$TAG, modality = $modality BP::success")
+
+    private fun VibratorHelper.error(modality: BiometricModality = BiometricModality.None) =
+        vibrateAuthError("$TAG, modality = $modality BP::error")
+
     companion object {
         private const val TAG = "PromptViewModel"
     }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModel.kt
new file mode 100644
index 0000000..e938b4e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModel.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.biometrics.ui.viewmodel
+
+import android.content.Context
+import android.graphics.Rect
+import android.hardware.biometrics.SensorLocationInternal
+import android.util.RotationUtils
+import android.view.Display
+import android.view.DisplayInfo
+import android.view.Surface
+import androidx.annotation.RawRes
+import com.android.systemui.R
+import com.android.systemui.biometrics.domain.interactor.SideFpsOverlayInteractor
+import com.android.systemui.dagger.qualifiers.Application
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.map
+
+/** View-model for SideFpsOverlayView. */
+class SideFpsOverlayViewModel
+@Inject
+constructor(
+    @Application private val context: Context,
+    private val sideFpsOverlayInteractor: SideFpsOverlayInteractor,
+) {
+
+    private val isReverseDefaultRotation =
+        context.resources.getBoolean(com.android.internal.R.bool.config_reverseDefaultRotation)
+
+    private val _sensorBounds: MutableStateFlow<Rect> = MutableStateFlow(Rect())
+    val sensorBounds = _sensorBounds.asStateFlow()
+
+    val overlayOffsets: Flow<SensorLocationInternal> = sideFpsOverlayInteractor.overlayOffsets
+
+    /** Update the displayId. */
+    fun changeDisplay() {
+        sideFpsOverlayInteractor.changeDisplay(context.display!!.uniqueId)
+    }
+
+    /** Determine the rotation of the sideFps animation given the overlay offsets. */
+    val sideFpsAnimationRotation: Flow<Float> =
+        overlayOffsets.map { overlayOffsets ->
+            val display = context.display!!
+            val displayInfo: DisplayInfo = DisplayInfo()
+            // b/284098873 `context.display.rotation` may not up-to-date, we use
+            // displayInfo.rotation
+            display.getDisplayInfo(displayInfo)
+            val yAligned: Boolean = overlayOffsets.isYAligned()
+            when (getRotationFromDefault(displayInfo.rotation)) {
+                Surface.ROTATION_90 -> if (yAligned) 0f else 180f
+                Surface.ROTATION_180 -> 180f
+                Surface.ROTATION_270 -> if (yAligned) 180f else 0f
+                else -> 0f
+            }
+        }
+
+    /** Populate the sideFps animation from the overlay offsets. */
+    @RawRes
+    val sideFpsAnimation: Flow<Int> =
+        overlayOffsets.map { overlayOffsets ->
+            val display = context.display!!
+            val displayInfo: DisplayInfo = DisplayInfo()
+            // b/284098873 `context.display.rotation` may not up-to-date, we use
+            // displayInfo.rotation
+            display.getDisplayInfo(displayInfo)
+            val yAligned: Boolean = overlayOffsets.isYAligned()
+            when (getRotationFromDefault(displayInfo.rotation)) {
+                Surface.ROTATION_0 -> if (yAligned) R.raw.sfps_pulse else R.raw.sfps_pulse_landscape
+                Surface.ROTATION_180 ->
+                    if (yAligned) R.raw.sfps_pulse else R.raw.sfps_pulse_landscape
+                else -> if (yAligned) R.raw.sfps_pulse_landscape else R.raw.sfps_pulse
+            }
+        }
+
+    /**
+     * Calculate and update the bounds of the sensor based on the bounds of the overlay view, the
+     * maximum bounds of the window, and the offsets of the sensor location.
+     */
+    fun updateSensorBounds(
+        bounds: Rect,
+        maximumWindowBounds: Rect,
+        offsets: SensorLocationInternal
+    ) {
+        val isNaturalOrientation = context.display!!.isNaturalOrientation()
+        val isDefaultOrientation =
+            if (isReverseDefaultRotation) !isNaturalOrientation else isNaturalOrientation
+
+        val displayWidth =
+            if (isDefaultOrientation) maximumWindowBounds.width() else maximumWindowBounds.height()
+        val displayHeight =
+            if (isDefaultOrientation) maximumWindowBounds.height() else maximumWindowBounds.width()
+        val boundsWidth = if (isDefaultOrientation) bounds.width() else bounds.height()
+        val boundsHeight = if (isDefaultOrientation) bounds.height() else bounds.width()
+
+        val sensorBounds =
+            if (offsets.isYAligned()) {
+                Rect(
+                    displayWidth - boundsWidth,
+                    offsets.sensorLocationY,
+                    displayWidth,
+                    offsets.sensorLocationY + boundsHeight
+                )
+            } else {
+                Rect(
+                    offsets.sensorLocationX,
+                    0,
+                    offsets.sensorLocationX + boundsWidth,
+                    boundsHeight
+                )
+            }
+
+        val displayInfo: DisplayInfo = DisplayInfo()
+        context.display!!.getDisplayInfo(displayInfo)
+
+        RotationUtils.rotateBounds(
+            sensorBounds,
+            Rect(0, 0, displayWidth, displayHeight),
+            getRotationFromDefault(displayInfo.rotation)
+        )
+
+        _sensorBounds.value = sensorBounds
+    }
+
+    private fun getRotationFromDefault(rotation: Int): Int =
+        if (isReverseDefaultRotation) (rotation + 1) % 4 else rotation
+}
+
+private fun Display.isNaturalOrientation(): Boolean =
+    rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180
+
+private fun SensorLocationInternal.isYAligned(): Boolean = sensorLocationY != 0
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/BluetoothLogger.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/BluetoothLogger.kt
index 96af42b..2a457eb 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/BluetoothLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/BluetoothLogger.kt
@@ -18,7 +18,7 @@
 
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import com.android.systemui.log.dagger.BluetoothLog
 import javax.inject.Inject
 
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/data/factory/BouncerMessageFactory.kt b/packages/SystemUI/src/com/android/systemui/bouncer/data/factory/BouncerMessageFactory.kt
new file mode 100644
index 0000000..3206c00
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/data/factory/BouncerMessageFactory.kt
@@ -0,0 +1,360 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.data.factory
+
+import android.annotation.IntDef
+import com.android.keyguard.KeyguardSecurityModel
+import com.android.keyguard.KeyguardSecurityModel.SecurityMode
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_AFTER_LOCKOUT
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_DEFAULT
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_DEVICE_ADMIN
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_FACE_LOCKED_OUT
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_FINGERPRINT_LOCKED_OUT
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_INCORRECT_FACE_INPUT
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_INCORRECT_FINGERPRINT_INPUT
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_INCORRECT_PRIMARY_AUTH_INPUT
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_NONE
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_PREPARE_FOR_UPDATE
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_PRIMARY_AUTH_LOCKED_OUT
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_RESTART
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TIMEOUT
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TRUSTAGENT_EXPIRED
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_USER_REQUEST
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.R.string.bouncer_face_not_recognized
+import com.android.systemui.R.string.keyguard_enter_password
+import com.android.systemui.R.string.keyguard_enter_pattern
+import com.android.systemui.R.string.keyguard_enter_pin
+import com.android.systemui.R.string.kg_bio_too_many_attempts_password
+import com.android.systemui.R.string.kg_bio_too_many_attempts_pattern
+import com.android.systemui.R.string.kg_bio_too_many_attempts_pin
+import com.android.systemui.R.string.kg_bio_try_again_or_password
+import com.android.systemui.R.string.kg_bio_try_again_or_pattern
+import com.android.systemui.R.string.kg_bio_try_again_or_pin
+import com.android.systemui.R.string.kg_face_locked_out
+import com.android.systemui.R.string.kg_fp_locked_out
+import com.android.systemui.R.string.kg_fp_not_recognized
+import com.android.systemui.R.string.kg_primary_auth_locked_out_password
+import com.android.systemui.R.string.kg_primary_auth_locked_out_pattern
+import com.android.systemui.R.string.kg_primary_auth_locked_out_pin
+import com.android.systemui.R.string.kg_prompt_after_dpm_lock
+import com.android.systemui.R.string.kg_prompt_after_user_lockdown_password
+import com.android.systemui.R.string.kg_prompt_after_user_lockdown_pattern
+import com.android.systemui.R.string.kg_prompt_after_user_lockdown_pin
+import com.android.systemui.R.string.kg_prompt_auth_timeout
+import com.android.systemui.R.string.kg_prompt_password_auth_timeout
+import com.android.systemui.R.string.kg_prompt_pattern_auth_timeout
+import com.android.systemui.R.string.kg_prompt_pin_auth_timeout
+import com.android.systemui.R.string.kg_prompt_reason_restart_password
+import com.android.systemui.R.string.kg_prompt_reason_restart_pattern
+import com.android.systemui.R.string.kg_prompt_reason_restart_pin
+import com.android.systemui.R.string.kg_prompt_unattended_update
+import com.android.systemui.R.string.kg_too_many_failed_attempts_countdown
+import com.android.systemui.R.string.kg_trust_agent_disabled
+import com.android.systemui.R.string.kg_unlock_with_password_or_fp
+import com.android.systemui.R.string.kg_unlock_with_pattern_or_fp
+import com.android.systemui.R.string.kg_unlock_with_pin_or_fp
+import com.android.systemui.R.string.kg_wrong_input_try_fp_suggestion
+import com.android.systemui.R.string.kg_wrong_password_try_again
+import com.android.systemui.R.string.kg_wrong_pattern_try_again
+import com.android.systemui.R.string.kg_wrong_pin_try_again
+import com.android.systemui.bouncer.shared.model.BouncerMessageModel
+import com.android.systemui.bouncer.shared.model.Message
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+
+@SysUISingleton
+class BouncerMessageFactory
+@Inject
+constructor(
+    private val updateMonitor: KeyguardUpdateMonitor,
+    private val securityModel: KeyguardSecurityModel,
+) {
+
+    fun createFromPromptReason(
+        @BouncerPromptReason reason: Int,
+        userId: Int,
+    ): BouncerMessageModel? {
+        val pair =
+            getBouncerMessage(
+                reason,
+                securityModel.getSecurityMode(userId),
+                updateMonitor.isFingerprintAllowedInBouncer
+            )
+        return pair?.let {
+            BouncerMessageModel(
+                message = Message(messageResId = pair.first),
+                secondaryMessage = Message(messageResId = pair.second)
+            )
+        }
+    }
+
+    fun createFromString(
+        primaryMsg: String? = null,
+        secondaryMsg: String? = null
+    ): BouncerMessageModel =
+        BouncerMessageModel(
+            message = primaryMsg?.let { Message(message = it) },
+            secondaryMessage = secondaryMsg?.let { Message(message = it) },
+        )
+
+    /**
+     * Helper method that provides the relevant bouncer message that should be shown for different
+     * scenarios indicated by [reason]. [securityMode] & [fpAllowedInBouncer] parameters are used to
+     * provide a more specific message.
+     */
+    private fun getBouncerMessage(
+        @BouncerPromptReason reason: Int,
+        securityMode: SecurityMode,
+        fpAllowedInBouncer: Boolean = false
+    ): Pair<Int, Int>? {
+        return when (reason) {
+            PROMPT_REASON_RESTART -> authRequiredAfterReboot(securityMode)
+            PROMPT_REASON_TIMEOUT -> authRequiredAfterPrimaryAuthTimeout(securityMode)
+            PROMPT_REASON_DEVICE_ADMIN -> authRequiredAfterAdminLockdown(securityMode)
+            PROMPT_REASON_USER_REQUEST -> authRequiredAfterUserLockdown(securityMode)
+            PROMPT_REASON_AFTER_LOCKOUT -> biometricLockout(securityMode)
+            PROMPT_REASON_PREPARE_FOR_UPDATE -> authRequiredForUnattendedUpdate(securityMode)
+            PROMPT_REASON_FINGERPRINT_LOCKED_OUT -> fingerprintUnlockUnavailable(securityMode)
+            PROMPT_REASON_FACE_LOCKED_OUT -> faceUnlockUnavailable(securityMode)
+            PROMPT_REASON_INCORRECT_PRIMARY_AUTH_INPUT ->
+                if (fpAllowedInBouncer) incorrectSecurityInputWithFingerprint(securityMode)
+                else incorrectSecurityInput(securityMode)
+            PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT ->
+                if (fpAllowedInBouncer) nonStrongAuthTimeoutWithFingerprintAllowed(securityMode)
+                else nonStrongAuthTimeout(securityMode)
+            PROMPT_REASON_TRUSTAGENT_EXPIRED ->
+                if (fpAllowedInBouncer) trustAgentDisabledWithFingerprintAllowed(securityMode)
+                else trustAgentDisabled(securityMode)
+            PROMPT_REASON_INCORRECT_FACE_INPUT ->
+                if (fpAllowedInBouncer) incorrectFaceInputWithFingerprintAllowed(securityMode)
+                else incorrectFaceInput(securityMode)
+            PROMPT_REASON_INCORRECT_FINGERPRINT_INPUT -> incorrectFingerprintInput(securityMode)
+            PROMPT_REASON_DEFAULT ->
+                if (fpAllowedInBouncer) defaultMessageWithFingerprint(securityMode)
+                else defaultMessage(securityMode)
+            PROMPT_REASON_PRIMARY_AUTH_LOCKED_OUT -> primaryAuthLockedOut(securityMode)
+            else -> null
+        }
+    }
+}
+
+@Retention(AnnotationRetention.SOURCE)
+@IntDef(
+    PROMPT_REASON_TIMEOUT,
+    PROMPT_REASON_DEVICE_ADMIN,
+    PROMPT_REASON_USER_REQUEST,
+    PROMPT_REASON_AFTER_LOCKOUT,
+    PROMPT_REASON_PREPARE_FOR_UPDATE,
+    PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT,
+    PROMPT_REASON_TRUSTAGENT_EXPIRED,
+    PROMPT_REASON_INCORRECT_PRIMARY_AUTH_INPUT,
+    PROMPT_REASON_INCORRECT_FACE_INPUT,
+    PROMPT_REASON_INCORRECT_FINGERPRINT_INPUT,
+    PROMPT_REASON_FACE_LOCKED_OUT,
+    PROMPT_REASON_FINGERPRINT_LOCKED_OUT,
+    PROMPT_REASON_DEFAULT,
+    PROMPT_REASON_NONE,
+    PROMPT_REASON_RESTART,
+    PROMPT_REASON_PRIMARY_AUTH_LOCKED_OUT,
+)
+annotation class BouncerPromptReason
+
+private fun defaultMessage(securityMode: SecurityMode): Pair<Int, Int> {
+    return when (securityMode) {
+        SecurityMode.Pattern -> Pair(keyguard_enter_pattern, 0)
+        SecurityMode.Password -> Pair(keyguard_enter_password, 0)
+        SecurityMode.PIN -> Pair(keyguard_enter_pin, 0)
+        else -> Pair(0, 0)
+    }
+}
+
+private fun defaultMessageWithFingerprint(securityMode: SecurityMode): Pair<Int, Int> {
+    return when (securityMode) {
+        SecurityMode.Pattern -> Pair(kg_unlock_with_pattern_or_fp, 0)
+        SecurityMode.Password -> Pair(kg_unlock_with_password_or_fp, 0)
+        SecurityMode.PIN -> Pair(kg_unlock_with_pin_or_fp, 0)
+        else -> Pair(0, 0)
+    }
+}
+
+private fun incorrectSecurityInput(securityMode: SecurityMode): Pair<Int, Int> {
+    return when (securityMode) {
+        SecurityMode.Pattern -> Pair(kg_wrong_pattern_try_again, 0)
+        SecurityMode.Password -> Pair(kg_wrong_password_try_again, 0)
+        SecurityMode.PIN -> Pair(kg_wrong_pin_try_again, 0)
+        else -> Pair(0, 0)
+    }
+}
+
+private fun incorrectSecurityInputWithFingerprint(securityMode: SecurityMode): Pair<Int, Int> {
+    return when (securityMode) {
+        SecurityMode.Pattern -> Pair(kg_wrong_pattern_try_again, kg_wrong_input_try_fp_suggestion)
+        SecurityMode.Password -> Pair(kg_wrong_password_try_again, kg_wrong_input_try_fp_suggestion)
+        SecurityMode.PIN -> Pair(kg_wrong_pin_try_again, kg_wrong_input_try_fp_suggestion)
+        else -> Pair(0, 0)
+    }
+}
+
+private fun incorrectFingerprintInput(securityMode: SecurityMode): Pair<Int, Int> {
+    return when (securityMode) {
+        SecurityMode.Pattern -> Pair(kg_fp_not_recognized, kg_bio_try_again_or_pattern)
+        SecurityMode.Password -> Pair(kg_fp_not_recognized, kg_bio_try_again_or_password)
+        SecurityMode.PIN -> Pair(kg_fp_not_recognized, kg_bio_try_again_or_pin)
+        else -> Pair(0, 0)
+    }
+}
+
+private fun incorrectFaceInput(securityMode: SecurityMode): Pair<Int, Int> {
+    return when (securityMode) {
+        SecurityMode.Pattern -> Pair(bouncer_face_not_recognized, kg_bio_try_again_or_pattern)
+        SecurityMode.Password -> Pair(bouncer_face_not_recognized, kg_bio_try_again_or_password)
+        SecurityMode.PIN -> Pair(bouncer_face_not_recognized, kg_bio_try_again_or_pin)
+        else -> Pair(0, 0)
+    }
+}
+
+private fun incorrectFaceInputWithFingerprintAllowed(securityMode: SecurityMode): Pair<Int, Int> {
+    return when (securityMode) {
+        SecurityMode.Pattern -> Pair(kg_unlock_with_pattern_or_fp, bouncer_face_not_recognized)
+        SecurityMode.Password -> Pair(kg_unlock_with_password_or_fp, bouncer_face_not_recognized)
+        SecurityMode.PIN -> Pair(kg_unlock_with_pin_or_fp, bouncer_face_not_recognized)
+        else -> Pair(0, 0)
+    }
+}
+
+private fun biometricLockout(securityMode: SecurityMode): Pair<Int, Int> {
+    return when (securityMode) {
+        SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_bio_too_many_attempts_pattern)
+        SecurityMode.Password -> Pair(keyguard_enter_password, kg_bio_too_many_attempts_password)
+        SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_bio_too_many_attempts_pin)
+        else -> Pair(0, 0)
+    }
+}
+
+private fun authRequiredAfterReboot(securityMode: SecurityMode): Pair<Int, Int> {
+    return when (securityMode) {
+        SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_reason_restart_pattern)
+        SecurityMode.Password -> Pair(keyguard_enter_password, kg_prompt_reason_restart_password)
+        SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_reason_restart_pin)
+        else -> Pair(0, 0)
+    }
+}
+
+private fun authRequiredAfterAdminLockdown(securityMode: SecurityMode): Pair<Int, Int> {
+    return when (securityMode) {
+        SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_after_dpm_lock)
+        SecurityMode.Password -> Pair(keyguard_enter_password, kg_prompt_after_dpm_lock)
+        SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_after_dpm_lock)
+        else -> Pair(0, 0)
+    }
+}
+
+private fun authRequiredAfterUserLockdown(securityMode: SecurityMode): Pair<Int, Int> {
+    return when (securityMode) {
+        SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_after_user_lockdown_pattern)
+        SecurityMode.Password ->
+            Pair(keyguard_enter_password, kg_prompt_after_user_lockdown_password)
+        SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_after_user_lockdown_pin)
+        else -> Pair(0, 0)
+    }
+}
+
+private fun authRequiredForUnattendedUpdate(securityMode: SecurityMode): Pair<Int, Int> {
+    return when (securityMode) {
+        SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_unattended_update)
+        SecurityMode.Password -> Pair(keyguard_enter_password, kg_prompt_unattended_update)
+        SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_unattended_update)
+        else -> Pair(0, 0)
+    }
+}
+
+private fun authRequiredAfterPrimaryAuthTimeout(securityMode: SecurityMode): Pair<Int, Int> {
+    return when (securityMode) {
+        SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_pattern_auth_timeout)
+        SecurityMode.Password -> Pair(keyguard_enter_password, kg_prompt_password_auth_timeout)
+        SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_pin_auth_timeout)
+        else -> Pair(0, 0)
+    }
+}
+
+private fun nonStrongAuthTimeout(securityMode: SecurityMode): Pair<Int, Int> {
+    return when (securityMode) {
+        SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_auth_timeout)
+        SecurityMode.Password -> Pair(keyguard_enter_password, kg_prompt_auth_timeout)
+        SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_auth_timeout)
+        else -> Pair(0, 0)
+    }
+}
+
+private fun nonStrongAuthTimeoutWithFingerprintAllowed(securityMode: SecurityMode): Pair<Int, Int> {
+    return when (securityMode) {
+        SecurityMode.Pattern -> Pair(kg_unlock_with_pattern_or_fp, kg_prompt_auth_timeout)
+        SecurityMode.Password -> Pair(kg_unlock_with_password_or_fp, kg_prompt_auth_timeout)
+        SecurityMode.PIN -> Pair(kg_unlock_with_pin_or_fp, kg_prompt_auth_timeout)
+        else -> Pair(0, 0)
+    }
+}
+
+private fun faceUnlockUnavailable(securityMode: SecurityMode): Pair<Int, Int> {
+    return when (securityMode) {
+        SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_face_locked_out)
+        SecurityMode.Password -> Pair(keyguard_enter_password, kg_face_locked_out)
+        SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_face_locked_out)
+        else -> Pair(0, 0)
+    }
+}
+
+private fun fingerprintUnlockUnavailable(securityMode: SecurityMode): Pair<Int, Int> {
+    return when (securityMode) {
+        SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_fp_locked_out)
+        SecurityMode.Password -> Pair(keyguard_enter_password, kg_fp_locked_out)
+        SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_fp_locked_out)
+        else -> Pair(0, 0)
+    }
+}
+
+private fun trustAgentDisabled(securityMode: SecurityMode): Pair<Int, Int> {
+    return when (securityMode) {
+        SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_trust_agent_disabled)
+        SecurityMode.Password -> Pair(keyguard_enter_password, kg_trust_agent_disabled)
+        SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_trust_agent_disabled)
+        else -> Pair(0, 0)
+    }
+}
+
+private fun trustAgentDisabledWithFingerprintAllowed(securityMode: SecurityMode): Pair<Int, Int> {
+    return when (securityMode) {
+        SecurityMode.Pattern -> Pair(kg_unlock_with_pattern_or_fp, kg_trust_agent_disabled)
+        SecurityMode.Password -> Pair(kg_unlock_with_password_or_fp, kg_trust_agent_disabled)
+        SecurityMode.PIN -> Pair(kg_unlock_with_pin_or_fp, kg_trust_agent_disabled)
+        else -> Pair(0, 0)
+    }
+}
+
+private fun primaryAuthLockedOut(securityMode: SecurityMode): Pair<Int, Int> {
+    return when (securityMode) {
+        SecurityMode.Pattern ->
+            Pair(kg_too_many_failed_attempts_countdown, kg_primary_auth_locked_out_pattern)
+        SecurityMode.Password ->
+            Pair(kg_too_many_failed_attempts_countdown, kg_primary_auth_locked_out_password)
+        SecurityMode.PIN ->
+            Pair(kg_too_many_failed_attempts_countdown, kg_primary_auth_locked_out_pin)
+        else -> Pair(0, 0)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/data/repo/BouncerRepository.kt b/packages/SystemUI/src/com/android/systemui/bouncer/data/repo/BouncerRepository.kt
deleted file mode 100644
index 49a0a3c..0000000
--- a/packages/SystemUI/src/com/android/systemui/bouncer/data/repo/BouncerRepository.kt
+++ /dev/null
@@ -1,44 +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.bouncer.data.repo
-
-import com.android.systemui.bouncer.shared.model.AuthenticationThrottledModel
-import com.android.systemui.dagger.SysUISingleton
-import javax.inject.Inject
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.asStateFlow
-
-/** Provides access to bouncer-related application state. */
-@SysUISingleton
-class BouncerRepository @Inject constructor() {
-    private val _message = MutableStateFlow<String?>(null)
-    /** The user-facing message to show in the bouncer. */
-    val message: StateFlow<String?> = _message.asStateFlow()
-
-    private val _throttling = MutableStateFlow<AuthenticationThrottledModel?>(null)
-    /** The current authentication throttling state. If `null`, there's no throttling. */
-    val throttling: StateFlow<AuthenticationThrottledModel?> = _throttling.asStateFlow()
-
-    fun setMessage(message: String?) {
-        _message.value = message
-    }
-
-    fun setThrottling(throttling: AuthenticationThrottledModel?) {
-        _throttling.value = throttling
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/BouncerMessageRepository.kt b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/BouncerMessageRepository.kt
new file mode 100644
index 0000000..7e420cf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/BouncerMessageRepository.kt
@@ -0,0 +1,332 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.data.repository
+
+import android.hardware.biometrics.BiometricSourceType
+import android.hardware.biometrics.BiometricSourceType.FACE
+import android.hardware.biometrics.BiometricSourceType.FINGERPRINT
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_DEVICE_ADMIN
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_FACE_LOCKED_OUT
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_FINGERPRINT_LOCKED_OUT
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_INCORRECT_FACE_INPUT
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_INCORRECT_FINGERPRINT_INPUT
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_NONE
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_PREPARE_FOR_UPDATE
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_RESTART
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TIMEOUT
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TRUSTAGENT_EXPIRED
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_USER_REQUEST
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.KeyguardUpdateMonitorCallback
+import com.android.systemui.bouncer.data.factory.BouncerMessageFactory
+import com.android.systemui.bouncer.shared.model.BouncerMessageModel
+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.keyguard.data.repository.BiometricSettingsRepository
+import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.data.repository.TrustRepository
+import com.android.systemui.user.data.repository.UserRepository
+import javax.inject.Inject
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+
+/** Provide different sources of messages that needs to be shown on the bouncer. */
+interface BouncerMessageRepository {
+    /**
+     * Messages that are shown in response to the incorrect security attempts on the bouncer and
+     * primary authentication method being locked out, along with countdown messages before primary
+     * auth is active again.
+     */
+    val primaryAuthMessage: Flow<BouncerMessageModel?>
+
+    /**
+     * Help messages that are shown to the user on how to successfully perform authentication using
+     * face.
+     */
+    val faceAcquisitionMessage: Flow<BouncerMessageModel?>
+
+    /**
+     * Help messages that are shown to the user on how to successfully perform authentication using
+     * fingerprint.
+     */
+    val fingerprintAcquisitionMessage: Flow<BouncerMessageModel?>
+
+    /** Custom message that is displayed when the bouncer is being shown to launch an app. */
+    val customMessage: Flow<BouncerMessageModel?>
+
+    /**
+     * Messages that are shown in response to biometric authentication attempts through face or
+     * fingerprint.
+     */
+    val biometricAuthMessage: Flow<BouncerMessageModel?>
+
+    /** Messages that are shown when certain auth flags are set. */
+    val authFlagsMessage: Flow<BouncerMessageModel?>
+
+    /** Messages that are show after biometrics are locked out temporarily or permanently */
+    val biometricLockedOutMessage: Flow<BouncerMessageModel?>
+
+    /** Set the value for [primaryAuthMessage] */
+    fun setPrimaryAuthMessage(value: BouncerMessageModel?)
+
+    /** Set the value for [faceAcquisitionMessage] */
+    fun setFaceAcquisitionMessage(value: BouncerMessageModel?)
+    /** Set the value for [fingerprintAcquisitionMessage] */
+    fun setFingerprintAcquisitionMessage(value: BouncerMessageModel?)
+
+    /** Set the value for [customMessage] */
+    fun setCustomMessage(value: BouncerMessageModel?)
+
+    /**
+     * Clear any previously set messages for [primaryAuthMessage], [faceAcquisitionMessage],
+     * [fingerprintAcquisitionMessage] & [customMessage]
+     */
+    fun clearMessage()
+}
+
+@SysUISingleton
+class BouncerMessageRepositoryImpl
+@Inject
+constructor(
+    trustRepository: TrustRepository,
+    biometricSettingsRepository: BiometricSettingsRepository,
+    updateMonitor: KeyguardUpdateMonitor,
+    private val bouncerMessageFactory: BouncerMessageFactory,
+    private val userRepository: UserRepository,
+    fingerprintAuthRepository: DeviceEntryFingerprintAuthRepository,
+) : BouncerMessageRepository {
+
+    private val isFaceEnrolledAndEnabled =
+        and(
+            biometricSettingsRepository.isFaceAuthenticationEnabled,
+            biometricSettingsRepository.isFaceEnrolled
+        )
+
+    private val isFingerprintEnrolledAndEnabled =
+        and(
+            biometricSettingsRepository.isFingerprintEnabledByDevicePolicy,
+            biometricSettingsRepository.isFingerprintEnrolled
+        )
+
+    private val isAnyBiometricsEnabledAndEnrolled =
+        or(isFaceEnrolledAndEnabled, isFingerprintEnrolledAndEnabled)
+
+    private val authFlagsBasedPromptReason: Flow<Int> =
+        combine(
+                biometricSettingsRepository.authenticationFlags,
+                trustRepository.isCurrentUserTrustManaged,
+                isAnyBiometricsEnabledAndEnrolled,
+                ::Triple
+            )
+            .map { (flags, isTrustManaged, biometricsEnrolledAndEnabled) ->
+                val trustOrBiometricsAvailable = (isTrustManaged || biometricsEnrolledAndEnabled)
+                return@map if (
+                    trustOrBiometricsAvailable && flags.isPrimaryAuthRequiredAfterReboot
+                ) {
+                    PROMPT_REASON_RESTART
+                } else if (trustOrBiometricsAvailable && flags.isPrimaryAuthRequiredAfterTimeout) {
+                    PROMPT_REASON_TIMEOUT
+                } else if (flags.isPrimaryAuthRequiredAfterDpmLockdown) {
+                    PROMPT_REASON_DEVICE_ADMIN
+                } else if (isTrustManaged && flags.someAuthRequiredAfterUserRequest) {
+                    PROMPT_REASON_TRUSTAGENT_EXPIRED
+                } else if (isTrustManaged && flags.someAuthRequiredAfterTrustAgentExpired) {
+                    PROMPT_REASON_TRUSTAGENT_EXPIRED
+                } else if (trustOrBiometricsAvailable && flags.isInUserLockdown) {
+                    PROMPT_REASON_USER_REQUEST
+                } else if (
+                    trustOrBiometricsAvailable && flags.primaryAuthRequiredForUnattendedUpdate
+                ) {
+                    PROMPT_REASON_PREPARE_FOR_UPDATE
+                } else if (
+                    trustOrBiometricsAvailable &&
+                        flags.strongerAuthRequiredAfterNonStrongBiometricsTimeout
+                ) {
+                    PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT
+                } else {
+                    PROMPT_REASON_NONE
+                }
+            }
+
+    private val biometricAuthReason: Flow<Int> =
+        conflatedCallbackFlow {
+                val callback =
+                    object : KeyguardUpdateMonitorCallback() {
+                        override fun onBiometricAuthFailed(
+                            biometricSourceType: BiometricSourceType?
+                        ) {
+                            val promptReason =
+                                if (biometricSourceType == FINGERPRINT)
+                                    PROMPT_REASON_INCORRECT_FINGERPRINT_INPUT
+                                else if (
+                                    biometricSourceType == FACE && !updateMonitor.isFaceLockedOut
+                                ) {
+                                    PROMPT_REASON_INCORRECT_FACE_INPUT
+                                } else PROMPT_REASON_NONE
+                            trySendWithFailureLogging(promptReason, TAG, "onBiometricAuthFailed")
+                        }
+
+                        override fun onBiometricsCleared() {
+                            trySendWithFailureLogging(
+                                PROMPT_REASON_NONE,
+                                TAG,
+                                "onBiometricsCleared"
+                            )
+                        }
+
+                        override fun onBiometricAcquired(
+                            biometricSourceType: BiometricSourceType?,
+                            acquireInfo: Int
+                        ) {
+                            trySendWithFailureLogging(
+                                PROMPT_REASON_NONE,
+                                TAG,
+                                "clearBiometricPrompt for new auth session."
+                            )
+                        }
+
+                        override fun onBiometricAuthenticated(
+                            userId: Int,
+                            biometricSourceType: BiometricSourceType?,
+                            isStrongBiometric: Boolean
+                        ) {
+                            trySendWithFailureLogging(
+                                PROMPT_REASON_NONE,
+                                TAG,
+                                "onBiometricAuthenticated"
+                            )
+                        }
+                    }
+                updateMonitor.registerCallback(callback)
+                awaitClose { updateMonitor.removeCallback(callback) }
+            }
+            .distinctUntilChanged()
+
+    private val _primaryAuthMessage = MutableStateFlow<BouncerMessageModel?>(null)
+    override val primaryAuthMessage: Flow<BouncerMessageModel?> = _primaryAuthMessage
+
+    private val _faceAcquisitionMessage = MutableStateFlow<BouncerMessageModel?>(null)
+    override val faceAcquisitionMessage: Flow<BouncerMessageModel?> = _faceAcquisitionMessage
+
+    private val _fingerprintAcquisitionMessage = MutableStateFlow<BouncerMessageModel?>(null)
+    override val fingerprintAcquisitionMessage: Flow<BouncerMessageModel?> =
+        _fingerprintAcquisitionMessage
+
+    private val _customMessage = MutableStateFlow<BouncerMessageModel?>(null)
+    override val customMessage: Flow<BouncerMessageModel?> = _customMessage
+
+    override val biometricAuthMessage: Flow<BouncerMessageModel?> =
+        biometricAuthReason
+            .map {
+                if (it == PROMPT_REASON_NONE) null
+                else
+                    bouncerMessageFactory.createFromPromptReason(
+                        it,
+                        userRepository.getSelectedUserInfo().id
+                    )
+            }
+            .onStart { emit(null) }
+            .distinctUntilChanged()
+
+    override val authFlagsMessage: Flow<BouncerMessageModel?> =
+        authFlagsBasedPromptReason
+            .map {
+                if (it == PROMPT_REASON_NONE) null
+                else
+                    bouncerMessageFactory.createFromPromptReason(
+                        it,
+                        userRepository.getSelectedUserInfo().id
+                    )
+            }
+            .onStart { emit(null) }
+            .distinctUntilChanged()
+
+    // TODO (b/262838215): Replace with DeviceEntryFaceAuthRepository when the new face auth system
+    // has been launched.
+    private val faceLockedOut: Flow<Boolean> = conflatedCallbackFlow {
+        val callback =
+            object : KeyguardUpdateMonitorCallback() {
+                override fun onLockedOutStateChanged(biometricSourceType: BiometricSourceType?) {
+                    if (biometricSourceType == FACE) {
+                        trySendWithFailureLogging(
+                            updateMonitor.isFaceLockedOut,
+                            TAG,
+                            "face lock out state changed."
+                        )
+                    }
+                }
+            }
+        updateMonitor.registerCallback(callback)
+        trySendWithFailureLogging(updateMonitor.isFaceLockedOut, TAG, "face lockout initial value")
+        awaitClose { updateMonitor.removeCallback(callback) }
+    }
+
+    override val biometricLockedOutMessage: Flow<BouncerMessageModel?> =
+        combine(fingerprintAuthRepository.isLockedOut, faceLockedOut) { fp, face ->
+            return@combine if (fp) {
+                bouncerMessageFactory.createFromPromptReason(
+                    PROMPT_REASON_FINGERPRINT_LOCKED_OUT,
+                    userRepository.getSelectedUserInfo().id
+                )
+            } else if (face) {
+                bouncerMessageFactory.createFromPromptReason(
+                    PROMPT_REASON_FACE_LOCKED_OUT,
+                    userRepository.getSelectedUserInfo().id
+                )
+            } else null
+        }
+
+    override fun setPrimaryAuthMessage(value: BouncerMessageModel?) {
+        _primaryAuthMessage.value = value
+    }
+
+    override fun setFaceAcquisitionMessage(value: BouncerMessageModel?) {
+        _faceAcquisitionMessage.value = value
+    }
+
+    override fun setFingerprintAcquisitionMessage(value: BouncerMessageModel?) {
+        _fingerprintAcquisitionMessage.value = value
+    }
+
+    override fun setCustomMessage(value: BouncerMessageModel?) {
+        _customMessage.value = value
+    }
+
+    override fun clearMessage() {
+        _fingerprintAcquisitionMessage.value = null
+        _faceAcquisitionMessage.value = null
+        _primaryAuthMessage.value = null
+        _customMessage.value = null
+    }
+
+    companion object {
+        const val TAG = "BouncerDetailedMessageRepository"
+    }
+}
+
+private fun and(flow: Flow<Boolean>, anotherFlow: Flow<Boolean>) =
+    flow.combine(anotherFlow) { a, b -> a && b }
+
+private fun or(flow: Flow<Boolean>, anotherFlow: Flow<Boolean>) =
+    flow.combine(anotherFlow) { a, b -> a || b }
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/BouncerRepository.kt b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/BouncerRepository.kt
new file mode 100644
index 0000000..943216e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/BouncerRepository.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.bouncer.data.repository
+
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+/** Provides access to bouncer-related application state. */
+@SysUISingleton
+class BouncerRepository @Inject constructor() {
+    private val _message = MutableStateFlow<String?>(null)
+    /** The user-facing message to show in the bouncer. */
+    val message: StateFlow<String?> = _message.asStateFlow()
+
+    fun setMessage(message: String?) {
+        _message.value = message
+    }
+}
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
new file mode 100644
index 0000000..918e168
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepository.kt
@@ -0,0 +1,273 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.bouncer.data.repository
+
+import android.os.Build
+import android.util.Log
+import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN
+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.log.dagger.BouncerTableLog
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logDiffsForTable
+import com.android.systemui.util.time.SystemClock
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
+
+/**
+ * Encapsulates app state for the lock screen primary and alternate bouncer.
+ *
+ * Make sure to add newly added flows to the logger.
+ */
+interface KeyguardBouncerRepository {
+    /** Values associated with the PrimaryBouncer (pin/pattern/password) input. */
+    val primaryBouncerShow: StateFlow<Boolean>
+    val primaryBouncerShowingSoon: StateFlow<Boolean>
+    val primaryBouncerStartingToHide: StateFlow<Boolean>
+    val primaryBouncerStartingDisappearAnimation: StateFlow<Runnable?>
+    /** Determines if we want to instantaneously show the primary bouncer instead of translating. */
+    val primaryBouncerScrimmed: StateFlow<Boolean>
+    /**
+     * Set how much of the notification panel is showing on the screen.
+     *
+     * ```
+     *      0f = panel fully hidden = bouncer fully showing
+     *      1f = panel fully showing = bouncer fully hidden
+     * ```
+     */
+    val panelExpansionAmount: StateFlow<Float>
+    val keyguardPosition: StateFlow<Float>
+    val isBackButtonEnabled: StateFlow<Boolean?>
+    /** Determines if user is already unlocked */
+    val keyguardAuthenticated: StateFlow<Boolean?>
+    val showMessage: StateFlow<BouncerShowMessageModel?>
+    val resourceUpdateRequests: StateFlow<Boolean>
+    val alternateBouncerVisible: StateFlow<Boolean>
+    val alternateBouncerUIAvailable: StateFlow<Boolean>
+    val sideFpsShowing: StateFlow<Boolean>
+
+    var lastAlternateBouncerVisibleTime: Long
+
+    fun setPrimaryScrimmed(isScrimmed: Boolean)
+
+    fun setPrimaryShow(isShowing: Boolean)
+
+    fun setPrimaryShowingSoon(showingSoon: Boolean)
+
+    fun setPrimaryStartingToHide(startingToHide: Boolean)
+
+    fun setPrimaryStartDisappearAnimation(runnable: Runnable?)
+
+    fun setPanelExpansion(panelExpansion: Float)
+
+    fun setKeyguardPosition(keyguardPosition: Float)
+
+    fun setResourceUpdateRequests(willUpdateResources: Boolean)
+
+    fun setShowMessage(bouncerShowMessageModel: BouncerShowMessageModel?)
+
+    fun setKeyguardAuthenticated(keyguardAuthenticated: Boolean?)
+
+    fun setIsBackButtonEnabled(isBackButtonEnabled: Boolean)
+
+    fun setAlternateVisible(isVisible: Boolean)
+
+    fun setAlternateBouncerUIAvailable(isAvailable: Boolean)
+
+    fun setSideFpsShowing(isShowing: Boolean)
+}
+
+@SysUISingleton
+class KeyguardBouncerRepositoryImpl
+@Inject
+constructor(
+    private val clock: SystemClock,
+    @Application private val applicationScope: CoroutineScope,
+    @BouncerTableLog private val buffer: TableLogBuffer,
+) : KeyguardBouncerRepository {
+    /** Values associated with the PrimaryBouncer (pin/pattern/password) input. */
+    private val _primaryBouncerShow = MutableStateFlow(false)
+    override val primaryBouncerShow = _primaryBouncerShow.asStateFlow()
+    private val _primaryBouncerShowingSoon = MutableStateFlow(false)
+    override val primaryBouncerShowingSoon = _primaryBouncerShowingSoon.asStateFlow()
+    private val _primaryBouncerStartingToHide = MutableStateFlow(false)
+    override val primaryBouncerStartingToHide = _primaryBouncerStartingToHide.asStateFlow()
+    private val _primaryBouncerDisappearAnimation = MutableStateFlow<Runnable?>(null)
+    override val primaryBouncerStartingDisappearAnimation =
+        _primaryBouncerDisappearAnimation.asStateFlow()
+    /** Determines if we want to instantaneously show the primary bouncer instead of translating. */
+    private val _primaryBouncerScrimmed = MutableStateFlow(false)
+    override val primaryBouncerScrimmed = _primaryBouncerScrimmed.asStateFlow()
+    /**
+     * Set how much of the notification panel is showing on the screen.
+     *
+     * ```
+     *      0f = panel fully hidden = bouncer fully showing
+     *      1f = panel fully showing = bouncer fully hidden
+     * ```
+     */
+    private val _panelExpansionAmount = MutableStateFlow(EXPANSION_HIDDEN)
+    override val panelExpansionAmount = _panelExpansionAmount.asStateFlow()
+    private val _keyguardPosition = MutableStateFlow(0f)
+    override val keyguardPosition = _keyguardPosition.asStateFlow()
+    private val _isBackButtonEnabled = MutableStateFlow<Boolean?>(null)
+    override val isBackButtonEnabled = _isBackButtonEnabled.asStateFlow()
+    private val _keyguardAuthenticated = MutableStateFlow<Boolean?>(null)
+    /** Determines if user is already unlocked */
+    override val keyguardAuthenticated = _keyguardAuthenticated.asStateFlow()
+    private val _showMessage = MutableStateFlow<BouncerShowMessageModel?>(null)
+    override val showMessage = _showMessage.asStateFlow()
+    private val _resourceUpdateRequests = MutableStateFlow(false)
+    override val resourceUpdateRequests = _resourceUpdateRequests.asStateFlow()
+    /** Values associated with the AlternateBouncer */
+    private val _alternateBouncerVisible = MutableStateFlow(false)
+    override val alternateBouncerVisible = _alternateBouncerVisible.asStateFlow()
+    override var lastAlternateBouncerVisibleTime: Long = NOT_VISIBLE
+    private val _alternateBouncerUIAvailable = MutableStateFlow(false)
+    override val alternateBouncerUIAvailable: StateFlow<Boolean> =
+        _alternateBouncerUIAvailable.asStateFlow()
+    private val _sideFpsShowing = MutableStateFlow(false)
+    override val sideFpsShowing: StateFlow<Boolean> = _sideFpsShowing.asStateFlow()
+
+    init {
+        setUpLogging()
+    }
+
+    override fun setPrimaryScrimmed(isScrimmed: Boolean) {
+        _primaryBouncerScrimmed.value = isScrimmed
+    }
+
+    override fun setAlternateVisible(isVisible: Boolean) {
+        if (isVisible && !_alternateBouncerVisible.value) {
+            lastAlternateBouncerVisibleTime = clock.uptimeMillis()
+        } else if (!isVisible) {
+            lastAlternateBouncerVisibleTime = NOT_VISIBLE
+        }
+        _alternateBouncerVisible.value = isVisible
+    }
+
+    override fun setAlternateBouncerUIAvailable(isAvailable: Boolean) {
+        _alternateBouncerUIAvailable.value = isAvailable
+    }
+
+    override fun setPrimaryShow(isShowing: Boolean) {
+        _primaryBouncerShow.value = isShowing
+    }
+
+    override fun setPrimaryShowingSoon(showingSoon: Boolean) {
+        _primaryBouncerShowingSoon.value = showingSoon
+    }
+
+    override fun setPrimaryStartingToHide(startingToHide: Boolean) {
+        _primaryBouncerStartingToHide.value = startingToHide
+    }
+
+    override fun setPrimaryStartDisappearAnimation(runnable: Runnable?) {
+        _primaryBouncerDisappearAnimation.value = runnable
+    }
+
+    override fun setPanelExpansion(panelExpansion: Float) {
+        _panelExpansionAmount.value = panelExpansion
+    }
+
+    override fun setKeyguardPosition(keyguardPosition: Float) {
+        _keyguardPosition.value = keyguardPosition
+    }
+
+    override fun setResourceUpdateRequests(willUpdateResources: Boolean) {
+        _resourceUpdateRequests.value = willUpdateResources
+    }
+
+    override fun setShowMessage(bouncerShowMessageModel: BouncerShowMessageModel?) {
+        _showMessage.value = bouncerShowMessageModel
+    }
+
+    override fun setKeyguardAuthenticated(keyguardAuthenticated: Boolean?) {
+        _keyguardAuthenticated.value = keyguardAuthenticated
+    }
+
+    override fun setIsBackButtonEnabled(isBackButtonEnabled: Boolean) {
+        _isBackButtonEnabled.value = isBackButtonEnabled
+    }
+
+    override fun setSideFpsShowing(isShowing: Boolean) {
+        _sideFpsShowing.value = isShowing
+    }
+
+    /** Sets up logs for state flows. */
+    private fun setUpLogging() {
+        if (!Build.IS_DEBUGGABLE) {
+            return
+        }
+
+        primaryBouncerShow
+            .logDiffsForTable(buffer, "", "PrimaryBouncerShow", false)
+            .onEach { Log.d(TAG, "Keyguard Bouncer is ${if (it) "showing" else "hiding."}") }
+            .launchIn(applicationScope)
+        primaryBouncerShowingSoon
+            .logDiffsForTable(buffer, "", "PrimaryBouncerShowingSoon", false)
+            .launchIn(applicationScope)
+        primaryBouncerStartingToHide
+            .logDiffsForTable(buffer, "", "PrimaryBouncerStartingToHide", false)
+            .launchIn(applicationScope)
+        primaryBouncerStartingDisappearAnimation
+            .map { it != null }
+            .logDiffsForTable(buffer, "", "PrimaryBouncerStartingDisappearAnimation", false)
+            .launchIn(applicationScope)
+        primaryBouncerScrimmed
+            .logDiffsForTable(buffer, "", "PrimaryBouncerScrimmed", false)
+            .launchIn(applicationScope)
+        panelExpansionAmount
+            .map { (it * 1000).toInt() }
+            .logDiffsForTable(buffer, "", "PanelExpansionAmountMillis", -1)
+            .launchIn(applicationScope)
+        keyguardPosition
+            .map { it.toInt() }
+            .logDiffsForTable(buffer, "", "KeyguardPosition", -1)
+            .launchIn(applicationScope)
+        isBackButtonEnabled
+            .filterNotNull()
+            .logDiffsForTable(buffer, "", "IsBackButtonEnabled", false)
+            .launchIn(applicationScope)
+        showMessage
+            .map { it?.message }
+            .logDiffsForTable(buffer, "", "ShowMessage", null)
+            .launchIn(applicationScope)
+        resourceUpdateRequests
+            .logDiffsForTable(buffer, "", "ResourceUpdateRequests", false)
+            .launchIn(applicationScope)
+        alternateBouncerUIAvailable
+            .logDiffsForTable(buffer, "", "IsAlternateBouncerUIAvailable", false)
+            .launchIn(applicationScope)
+        sideFpsShowing
+            .logDiffsForTable(buffer, "", "isSideFpsShowing", false)
+            .launchIn(applicationScope)
+    }
+
+    companion object {
+        private const val NOT_VISIBLE = -1L
+        private const val TAG = "KeyguardBouncerRepositoryImpl"
+    }
+}
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
new file mode 100644
index 0000000..e3e9b3a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bouncer.domain.interactor
+
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
+import com.android.systemui.dagger.SysUISingleton
+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.flow.Flow
+
+/** Encapsulates business logic for interacting with the lock-screen alternate bouncer. */
+@SysUISingleton
+class AlternateBouncerInteractor
+@Inject
+constructor(
+    private val statusBarStateController: StatusBarStateController,
+    private val keyguardStateController: KeyguardStateController,
+    private val bouncerRepository: KeyguardBouncerRepository,
+    private val biometricSettingsRepository: BiometricSettingsRepository,
+    private val systemClock: SystemClock,
+    private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+) {
+    var receivedDownTouch = false
+    val isVisible: Flow<Boolean> = bouncerRepository.alternateBouncerVisible
+
+    /**
+     * Sets the correct bouncer states to show the alternate bouncer if it can show.
+     *
+     * @return whether alternateBouncer is visible
+     */
+    fun show(): Boolean {
+        bouncerRepository.setAlternateVisible(canShowAlternateBouncerForFingerprint())
+        return isVisibleState()
+    }
+
+    /**
+     * Sets the correct bouncer states to hide the bouncer. Should only be called through
+     * StatusBarKeyguardViewManager until ScrimController is refactored to use
+     * alternateBouncerInteractor.
+     *
+     * @return true if the alternate bouncer was newly hidden, else false.
+     */
+    fun hide(): Boolean {
+        receivedDownTouch = false
+        val wasAlternateBouncerVisible = isVisibleState()
+        bouncerRepository.setAlternateVisible(false)
+        return wasAlternateBouncerVisible && !isVisibleState()
+    }
+
+    fun isVisibleState(): Boolean {
+        return bouncerRepository.alternateBouncerVisible.value
+    }
+
+    fun setAlternateBouncerUIAvailable(isAvailable: Boolean) {
+        bouncerRepository.setAlternateBouncerUIAvailable(isAvailable)
+    }
+
+    fun canShowAlternateBouncerForFingerprint(): Boolean {
+        return bouncerRepository.alternateBouncerUIAvailable.value &&
+            biometricSettingsRepository.isFingerprintEnrolled.value &&
+            biometricSettingsRepository.isStrongBiometricAllowed.value &&
+            biometricSettingsRepository.isFingerprintEnabledByDevicePolicy.value &&
+            !keyguardUpdateMonitor.isFingerprintLockedOut &&
+            !keyguardStateController.isUnlocked &&
+            !statusBarStateController.isDozing
+    }
+
+    /**
+     * Whether the alt bouncer has shown for a minimum time before allowing touches to dismiss the
+     * alternate bouncer and show the primary bouncer.
+     */
+    fun hasAlternateBouncerShownWithMinTime(): Boolean {
+        return (systemClock.uptimeMillis() - bouncerRepository.lastAlternateBouncerVisibleTime) >
+            MIN_VISIBILITY_DURATION_UNTIL_TOUCHES_DISMISS_ALTERNATE_BOUNCER_MS
+    }
+    /**
+     * Should only be called through StatusBarKeyguardViewManager which propagates the source of
+     * truth to other concerned controllers. Will hide the alternate bouncer if it's no longer
+     * allowed to show.
+     *
+     * @return true if the alternate bouncer was newly hidden, else false.
+     */
+    fun maybeHide(): Boolean {
+        if (isVisibleState() && !canShowAlternateBouncerForFingerprint()) {
+            return hide()
+        }
+        return false
+    }
+
+    companion object {
+        private const val MIN_VISIBILITY_DURATION_UNTIL_TOUCHES_DISMISS_ALTERNATE_BOUNCER_MS = 200L
+    }
+}
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 1d2fce7..8e14237 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
@@ -14,24 +14,29 @@
  * limitations under the License.
  */
 
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
 package com.android.systemui.bouncer.domain.interactor
 
 import android.content.Context
-import androidx.annotation.VisibleForTesting
 import com.android.systemui.R
 import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
-import com.android.systemui.bouncer.data.repo.BouncerRepository
-import com.android.systemui.bouncer.shared.model.AuthenticationThrottledModel
+import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel
+import com.android.systemui.bouncer.data.repository.BouncerRepository
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
 import com.android.systemui.scene.domain.interactor.SceneInteractor
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.util.kotlin.pairwise
 import dagger.assisted.Assisted
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
+import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.delay
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
@@ -47,6 +52,7 @@
     private val repository: BouncerRepository,
     private val authenticationInteractor: AuthenticationInteractor,
     private val sceneInteractor: SceneInteractor,
+    featureFlags: FeatureFlags,
     @Assisted private val containerName: String,
 ) {
 
@@ -54,9 +60,10 @@
     val message: StateFlow<String?> =
         combine(
                 repository.message,
-                repository.throttling,
-            ) { message, throttling ->
-                messageOrThrottlingMessage(message, throttling)
+                authenticationInteractor.isThrottled,
+                authenticationInteractor.throttling,
+            ) { message, isThrottled, throttling ->
+                messageOrThrottlingMessage(message, isThrottled, throttling)
             }
             .stateIn(
                 scope = applicationScope,
@@ -64,48 +71,51 @@
                 initialValue =
                     messageOrThrottlingMessage(
                         repository.message.value,
-                        repository.throttling.value,
+                        authenticationInteractor.isThrottled.value,
+                        authenticationInteractor.throttling.value,
                     )
             )
 
-    /**
-     * The currently-configured authentication method. This determines how the authentication
-     * challenge is completed in order to unlock an otherwise locked device.
-     */
-    val authenticationMethod: StateFlow<AuthenticationMethodModel> =
-        authenticationInteractor.authenticationMethod
+    /** The current authentication throttling state, only meaningful if [isThrottled] is `true`. */
+    val throttling: StateFlow<AuthenticationThrottlingModel> = authenticationInteractor.throttling
 
-    /** The current authentication throttling state. If `null`, there's no throttling. */
-    val throttling: StateFlow<AuthenticationThrottledModel?> = repository.throttling
+    /**
+     * Whether currently throttled and the user has to wait before being able to try another
+     * authentication attempt.
+     */
+    val isThrottled: StateFlow<Boolean> = authenticationInteractor.isThrottled
+
+    /** Whether the auto confirm feature is enabled for the currently-selected user. */
+    val isAutoConfirmEnabled: StateFlow<Boolean> = authenticationInteractor.isAutoConfirmEnabled
+
+    /** The length of the hinted PIN, or `null`, if pin length hint should not be shown. */
+    val hintedPinLength: StateFlow<Int?> = authenticationInteractor.hintedPinLength
+
+    /** Whether the pattern should be visible for the currently-selected user. */
+    val isPatternVisible: StateFlow<Boolean> = authenticationInteractor.isPatternVisible
 
     init {
-        applicationScope.launch {
-            combine(
-                    sceneInteractor.currentScene(containerName),
-                    authenticationInteractor.authenticationMethod,
-                    ::Pair,
-                )
-                .collect { (currentScene, authMethod) ->
-                    if (currentScene.key == SceneKey.Bouncer) {
-                        when (authMethod) {
-                            is AuthenticationMethodModel.None ->
-                                sceneInteractor.setCurrentScene(
-                                    containerName,
-                                    SceneModel(SceneKey.Gone),
-                                )
-                            is AuthenticationMethodModel.Swipe ->
-                                sceneInteractor.setCurrentScene(
-                                    containerName,
-                                    SceneModel(SceneKey.Lockscreen),
-                                )
-                            else -> Unit
-                        }
+        if (featureFlags.isEnabled(Flags.SCENE_CONTAINER)) {
+            // Clear the message if moved from throttling to no-longer throttling.
+            applicationScope.launch {
+                isThrottled.pairwise().collect { (wasThrottled, currentlyThrottled) ->
+                    if (wasThrottled && !currentlyThrottled) {
+                        clearMessage()
                     }
                 }
+            }
         }
     }
 
     /**
+     * Returns the currently-configured authentication method. This determines how the
+     * authentication challenge is completed in order to unlock an otherwise locked device.
+     */
+    suspend fun getAuthenticationMethod(): AuthenticationMethodModel {
+        return authenticationInteractor.getAuthenticationMethod()
+    }
+
+    /**
      * Either shows the bouncer or unlocks the device, if the bouncer doesn't need to be shown.
      *
      * @param containerName The name of the scene container to show the bouncer in.
@@ -115,18 +125,19 @@
         containerName: String,
         message: String? = null,
     ) {
-        if (authenticationInteractor.isAuthenticationRequired()) {
-            repository.setMessage(message ?: promptMessage(authenticationMethod.value))
-            sceneInteractor.setCurrentScene(
-                containerName = containerName,
-                scene = SceneModel(SceneKey.Bouncer),
-            )
-        } else {
-            authenticationInteractor.unlockDevice()
-            sceneInteractor.setCurrentScene(
-                containerName = containerName,
-                scene = SceneModel(SceneKey.Gone),
-            )
+        applicationScope.launch {
+            if (authenticationInteractor.isAuthenticationRequired()) {
+                repository.setMessage(message ?: promptMessage(getAuthenticationMethod()))
+                sceneInteractor.setCurrentScene(
+                    containerName = containerName,
+                    scene = SceneModel(SceneKey.Bouncer),
+                )
+            } else {
+                sceneInteractor.setCurrentScene(
+                    containerName = containerName,
+                    scene = SceneModel(SceneKey.Gone),
+                )
+            }
         }
     }
 
@@ -135,7 +146,7 @@
      * method.
      */
     fun resetMessage() {
-        repository.setMessage(promptMessage(authenticationMethod.value))
+        applicationScope.launch { repository.setMessage(promptMessage(getAuthenticationMethod())) }
     }
 
     /** Removes the user-facing message. */
@@ -149,47 +160,31 @@
      * If the input is correct, the device will be unlocked and the lock screen and bouncer will be
      * dismissed and hidden.
      *
+     * If [tryAutoConfirm] is `true`, authentication is attempted if and only if the auth method
+     * supports auto-confirming, and the input's length is at least the code's length. Otherwise,
+     * `null` is returned.
+     *
      * @param input The input from the user to try to authenticate with. This can be a list of
      *   different things, based on the current authentication method.
-     * @return `true` if the authentication succeeded and the device is now unlocked; `false`
-     *   otherwise.
+     * @param tryAutoConfirm `true` if called while the user inputs the code, without an explicit
+     *   request to validate.
+     * @return `true` if the authentication succeeded and the device is now unlocked; `false` when
+     *   authentication failed, `null` if the check was not performed.
      */
-    fun authenticate(
+    suspend fun authenticate(
         input: List<Any>,
-    ): Boolean {
-        if (repository.throttling.value != null) {
-            return false
-        }
+        tryAutoConfirm: Boolean = false,
+    ): Boolean? {
+        val isAuthenticated =
+            authenticationInteractor.authenticate(input, tryAutoConfirm) ?: return null
 
-        val isAuthenticated = authenticationInteractor.authenticate(input)
-        val failedAttempts = authenticationInteractor.failedAuthenticationAttempts.value
-        when {
-            isAuthenticated -> {
-                repository.setThrottling(null)
-                sceneInteractor.setCurrentScene(
-                    containerName = containerName,
-                    scene = SceneModel(SceneKey.Gone),
-                )
-            }
-            failedAttempts >= THROTTLE_AGGRESSIVELY_AFTER || failedAttempts % THROTTLE_EVERY == 0 ->
-                applicationScope.launch {
-                    var remainingDurationSec = THROTTLE_DURATION_SEC
-                    while (remainingDurationSec > 0) {
-                        repository.setThrottling(
-                            AuthenticationThrottledModel(
-                                failedAttemptCount = failedAttempts,
-                                totalDurationSec = THROTTLE_DURATION_SEC,
-                                remainingDurationSec = remainingDurationSec,
-                            )
-                        )
-                        remainingDurationSec--
-                        delay(1000)
-                    }
-
-                    repository.setThrottling(null)
-                    clearMessage()
-                }
-            else -> repository.setMessage(errorMessage(authenticationMethod.value))
+        if (isAuthenticated) {
+            sceneInteractor.setCurrentScene(
+                containerName = containerName,
+                scene = SceneModel(SceneKey.Gone),
+            )
+        } else {
+            repository.setMessage(errorMessage(getAuthenticationMethod()))
         }
 
         return isAuthenticated
@@ -197,7 +192,7 @@
 
     private fun promptMessage(authMethod: AuthenticationMethodModel): String {
         return when (authMethod) {
-            is AuthenticationMethodModel.PIN ->
+            is AuthenticationMethodModel.Pin ->
                 applicationContext.getString(R.string.keyguard_enter_your_pin)
             is AuthenticationMethodModel.Password ->
                 applicationContext.getString(R.string.keyguard_enter_your_password)
@@ -209,7 +204,7 @@
 
     private fun errorMessage(authMethod: AuthenticationMethodModel): String {
         return when (authMethod) {
-            is AuthenticationMethodModel.PIN -> applicationContext.getString(R.string.kg_wrong_pin)
+            is AuthenticationMethodModel.Pin -> applicationContext.getString(R.string.kg_wrong_pin)
             is AuthenticationMethodModel.Password ->
                 applicationContext.getString(R.string.kg_wrong_password)
             is AuthenticationMethodModel.Pattern ->
@@ -220,13 +215,14 @@
 
     private fun messageOrThrottlingMessage(
         message: String?,
-        throttling: AuthenticationThrottledModel?,
+        isThrottled: Boolean,
+        throttlingModel: AuthenticationThrottlingModel,
     ): String {
         return when {
-            throttling != null ->
+            isThrottled ->
                 applicationContext.getString(
                     com.android.internal.R.string.lockscreen_too_many_failed_attempts_countdown,
-                    throttling.remainingDurationSec,
+                    throttlingModel.remainingMs.milliseconds.inWholeSeconds,
                 )
             message != null -> message
             else -> ""
@@ -239,10 +235,4 @@
             containerName: String,
         ): BouncerInteractor
     }
-
-    companion object {
-        @VisibleForTesting const val THROTTLE_DURATION_SEC = 30
-        @VisibleForTesting const val THROTTLE_AGGRESSIVELY_AFTER = 15
-        @VisibleForTesting const val THROTTLE_EVERY = 5
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageAuditLogger.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageAuditLogger.kt
new file mode 100644
index 0000000..497747f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageAuditLogger.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bouncer.domain.interactor
+
+import android.os.Build
+import android.util.Log
+import com.android.systemui.CoreStartable
+import com.android.systemui.bouncer.data.repository.BouncerMessageRepository
+import com.android.systemui.bouncer.shared.model.BouncerMessageModel
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.launch
+
+private val TAG = BouncerMessageAuditLogger::class.simpleName!!
+
+/** Logger that echoes bouncer messages state to logcat in debuggable builds. */
+@SysUISingleton
+class BouncerMessageAuditLogger
+@Inject
+constructor(
+    @Application private val scope: CoroutineScope,
+    private val repository: BouncerMessageRepository,
+    private val interactor: BouncerMessageInteractor,
+) : CoreStartable {
+    override fun start() {
+        if (Build.isDebuggable()) {
+            collectAndLog(repository.biometricAuthMessage, "biometricMessage: ")
+            collectAndLog(repository.primaryAuthMessage, "primaryAuthMessage: ")
+            collectAndLog(repository.customMessage, "customMessage: ")
+            collectAndLog(repository.faceAcquisitionMessage, "faceAcquisitionMessage: ")
+            collectAndLog(
+                repository.fingerprintAcquisitionMessage,
+                "fingerprintAcquisitionMessage: "
+            )
+            collectAndLog(repository.authFlagsMessage, "authFlagsMessage: ")
+            collectAndLog(interactor.bouncerMessage, "interactor.bouncerMessage: ")
+        }
+    }
+
+    private fun collectAndLog(flow: Flow<BouncerMessageModel?>, context: String) {
+        scope.launch { flow.collect { Log.d(TAG, context + it) } }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt
new file mode 100644
index 0000000..d06dd8e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bouncer.domain.interactor
+
+import android.os.CountDownTimer
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_DEFAULT
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_INCORRECT_PRIMARY_AUTH_INPUT
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_PRIMARY_AUTH_LOCKED_OUT
+import com.android.systemui.bouncer.data.factory.BouncerMessageFactory
+import com.android.systemui.bouncer.data.repository.BouncerMessageRepository
+import com.android.systemui.bouncer.shared.model.BouncerMessageModel
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags.REVAMPED_BOUNCER_MESSAGES
+import com.android.systemui.user.data.repository.UserRepository
+import javax.inject.Inject
+import kotlin.math.roundToInt
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+
+@SysUISingleton
+class BouncerMessageInteractor
+@Inject
+constructor(
+    private val repository: BouncerMessageRepository,
+    private val factory: BouncerMessageFactory,
+    private val userRepository: UserRepository,
+    private val countDownTimerUtil: CountDownTimerUtil,
+    private val featureFlags: FeatureFlags,
+) {
+    fun onPrimaryAuthLockedOut(secondsBeforeLockoutReset: Long) {
+        if (!featureFlags.isEnabled(REVAMPED_BOUNCER_MESSAGES)) return
+
+        val callback =
+            object : CountDownTimerCallback {
+                override fun onFinish() {
+                    repository.clearMessage()
+                }
+
+                override fun onTick(millisUntilFinished: Long) {
+                    val secondsRemaining = (millisUntilFinished / 1000.0).roundToInt()
+                    val message =
+                        factory.createFromPromptReason(
+                            reason = PROMPT_REASON_PRIMARY_AUTH_LOCKED_OUT,
+                            userId = userRepository.getSelectedUserInfo().id
+                        )
+                    message?.message?.animate = false
+                    message?.message?.formatterArgs =
+                        mutableMapOf<String, Any>(Pair("count", secondsRemaining))
+                    repository.setPrimaryAuthMessage(message)
+                }
+            }
+        countDownTimerUtil.startNewTimer(secondsBeforeLockoutReset * 1000, 1000, callback)
+    }
+
+    fun onPrimaryAuthIncorrectAttempt() {
+        if (!featureFlags.isEnabled(REVAMPED_BOUNCER_MESSAGES)) return
+
+        repository.setPrimaryAuthMessage(
+            factory.createFromPromptReason(
+                PROMPT_REASON_INCORRECT_PRIMARY_AUTH_INPUT,
+                userRepository.getSelectedUserInfo().id
+            )
+        )
+    }
+
+    fun setFingerprintAcquisitionMessage(value: String?) {
+        if (!featureFlags.isEnabled(REVAMPED_BOUNCER_MESSAGES)) return
+
+        repository.setFingerprintAcquisitionMessage(
+            if (value != null) {
+                factory.createFromString(secondaryMsg = value)
+            } else {
+                null
+            }
+        )
+    }
+
+    fun setFaceAcquisitionMessage(value: String?) {
+        if (!featureFlags.isEnabled(REVAMPED_BOUNCER_MESSAGES)) return
+
+        repository.setFaceAcquisitionMessage(
+            if (value != null) {
+                factory.createFromString(secondaryMsg = value)
+            } else {
+                null
+            }
+        )
+    }
+
+    fun setCustomMessage(value: String?) {
+        if (!featureFlags.isEnabled(REVAMPED_BOUNCER_MESSAGES)) return
+
+        repository.setCustomMessage(
+            if (value != null) {
+                factory.createFromString(secondaryMsg = value)
+            } else {
+                null
+            }
+        )
+    }
+
+    fun onPrimaryBouncerUserInput() {
+        if (!featureFlags.isEnabled(REVAMPED_BOUNCER_MESSAGES)) return
+
+        repository.clearMessage()
+    }
+
+    fun onBouncerBeingHidden() {
+        if (!featureFlags.isEnabled(REVAMPED_BOUNCER_MESSAGES)) return
+
+        repository.clearMessage()
+    }
+
+    private fun firstNonNullMessage(
+        oneMessageModel: Flow<BouncerMessageModel?>,
+        anotherMessageModel: Flow<BouncerMessageModel?>
+    ): Flow<BouncerMessageModel?> {
+        return oneMessageModel.combine(anotherMessageModel) { a, b -> a ?: b }
+    }
+
+    // Null if feature flag is enabled which gets ignored always or empty bouncer message model that
+    // always maps to an empty string.
+    private fun nullOrEmptyMessage() =
+        flowOf(
+            if (featureFlags.isEnabled(REVAMPED_BOUNCER_MESSAGES)) null
+            else factory.createFromString("", "")
+        )
+
+    val bouncerMessage =
+        listOf(
+                nullOrEmptyMessage(),
+                repository.primaryAuthMessage,
+                repository.biometricAuthMessage,
+                repository.fingerprintAcquisitionMessage,
+                repository.faceAcquisitionMessage,
+                repository.customMessage,
+                repository.authFlagsMessage,
+                repository.biometricLockedOutMessage,
+                userRepository.selectedUserInfo.map {
+                    factory.createFromPromptReason(PROMPT_REASON_DEFAULT, it.id)
+                },
+            )
+            .reduce(::firstNonNullMessage)
+            .distinctUntilChanged()
+}
+
+interface CountDownTimerCallback {
+    fun onFinish()
+    fun onTick(millisUntilFinished: Long)
+}
+
+@SysUISingleton
+open class CountDownTimerUtil @Inject constructor() {
+
+    /**
+     * Start a new count down timer that runs for [millisInFuture] with a tick every
+     * [millisInterval]
+     */
+    fun startNewTimer(
+        millisInFuture: Long,
+        millisInterval: Long,
+        callback: CountDownTimerCallback,
+    ): CountDownTimer {
+        return object : CountDownTimer(millisInFuture, millisInterval) {
+                override fun onFinish() = callback.onFinish()
+
+                override fun onTick(millisUntilFinished: Long) =
+                    callback.onTick(millisUntilFinished)
+            }
+            .start()
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerCallbackInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerCallbackInteractor.kt
new file mode 100644
index 0000000..5fbe99a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerCallbackInteractor.kt
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.bouncer.domain.interactor
+
+import android.view.View
+import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN
+import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.EXPANSION_VISIBLE
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.util.ListenerSet
+import javax.inject.Inject
+
+/** Interactor to add and remove callbacks for the bouncer. */
+@SysUISingleton
+class PrimaryBouncerCallbackInteractor @Inject constructor() {
+    private var resetCallbacks = ListenerSet<KeyguardResetCallback>()
+    private var expansionCallbacks = ArrayList<PrimaryBouncerExpansionCallback>()
+
+    /** Add a KeyguardResetCallback. */
+    fun addKeyguardResetCallback(callback: KeyguardResetCallback) {
+        resetCallbacks.addIfAbsent(callback)
+    }
+
+    /** Remove a KeyguardResetCallback. */
+    fun removeKeyguardResetCallback(callback: KeyguardResetCallback) {
+        resetCallbacks.remove(callback)
+    }
+
+    /** Adds a callback to listen to bouncer expansion updates. */
+    fun addBouncerExpansionCallback(callback: PrimaryBouncerExpansionCallback) {
+        if (!expansionCallbacks.contains(callback)) {
+            expansionCallbacks.add(callback)
+        }
+    }
+
+    /**
+     * Removes a previously added callback. If the callback was never added, this method does
+     * nothing.
+     */
+    fun removeBouncerExpansionCallback(callback: PrimaryBouncerExpansionCallback) {
+        expansionCallbacks.remove(callback)
+    }
+
+    /** Propagate fully shown to bouncer expansion callbacks. */
+    fun dispatchFullyShown() {
+        for (callback in expansionCallbacks) {
+            callback.onFullyShown()
+        }
+    }
+
+    /** Propagate starting to hide to bouncer expansion callbacks. */
+    fun dispatchStartingToHide() {
+        for (callback in expansionCallbacks) {
+            callback.onStartingToHide()
+        }
+    }
+
+    /** Propagate starting to show to bouncer expansion callbacks. */
+    fun dispatchStartingToShow() {
+        for (callback in expansionCallbacks) {
+            callback.onStartingToShow()
+        }
+    }
+
+    /** Propagate fully hidden to bouncer expansion callbacks. */
+    fun dispatchFullyHidden() {
+        for (callback in expansionCallbacks) {
+            callback.onFullyHidden()
+        }
+    }
+
+    /** Propagate expansion changes to bouncer expansion callbacks. */
+    fun dispatchExpansionChanged(expansion: Float) {
+        for (callback in expansionCallbacks) {
+            callback.onExpansionChanged(expansion)
+        }
+    }
+    /** Propagate visibility changes to bouncer expansion callbacks. */
+    fun dispatchVisibilityChanged(visibility: Int) {
+        for (callback in expansionCallbacks) {
+            callback.onVisibilityChanged(visibility == View.VISIBLE)
+        }
+    }
+
+    /** Propagate keyguard reset. */
+    fun dispatchReset() {
+        for (callback in resetCallbacks) {
+            callback.onKeyguardReset()
+        }
+    }
+
+    /** Callback updated when the primary bouncer's show and hide states change. */
+    interface PrimaryBouncerExpansionCallback {
+        /**
+         * Invoked when the bouncer expansion reaches [EXPANSION_VISIBLE]. This is NOT called each
+         * time the bouncer is shown, but rather only when the fully shown amount has changed based
+         * on the panel expansion. The bouncer's visibility can still change when the expansion
+         * amount hasn't changed. See [PrimaryBouncerInteractor.isFullyShowing] for the checks for
+         * the bouncer showing state.
+         */
+        fun onFullyShown() {}
+
+        /** Invoked when the bouncer is starting to transition to a hidden state. */
+        fun onStartingToHide() {}
+
+        /** Invoked when the bouncer is starting to transition to a visible state. */
+        fun onStartingToShow() {}
+
+        /** Invoked when the bouncer expansion reaches [EXPANSION_HIDDEN]. */
+        fun onFullyHidden() {}
+
+        /**
+         * From 0f [EXPANSION_VISIBLE] when fully visible to 1f [EXPANSION_HIDDEN] when fully hidden
+         */
+        fun onExpansionChanged(bouncerHideAmount: Float) {}
+
+        /**
+         * Invoked when visibility of KeyguardBouncer has changed. Note the bouncer expansion can be
+         * [EXPANSION_VISIBLE], but the view's visibility can be [View.INVISIBLE].
+         */
+        fun onVisibilityChanged(isVisible: Boolean) {}
+    }
+
+    interface KeyguardResetCallback {
+        fun onKeyguardReset()
+    }
+}
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
new file mode 100644
index 0000000..c486603
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt
@@ -0,0 +1,410 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bouncer.domain.interactor
+
+import android.content.Context
+import android.content.res.ColorStateList
+import android.hardware.biometrics.BiometricSourceType
+import android.os.Handler
+import android.os.Trace
+import android.util.Log
+import android.view.View
+import com.android.keyguard.KeyguardConstants
+import com.android.keyguard.KeyguardSecurityModel
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.KeyguardUpdateMonitorCallback
+import com.android.systemui.DejankUtils
+import com.android.systemui.R
+import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
+import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants
+import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN
+import com.android.systemui.bouncer.shared.model.BouncerShowMessageModel
+import com.android.systemui.bouncer.ui.BouncerView
+import com.android.systemui.classifier.FalsingCollector
+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.DismissCallbackRegistry
+import com.android.systemui.keyguard.data.repository.TrustRepository
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.shared.system.SysUiStatsLog
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
+
+/**
+ * Encapsulates business logic for interacting with the lock-screen primary (pin/pattern/password)
+ * bouncer.
+ */
+@SysUISingleton
+class PrimaryBouncerInteractor
+@Inject
+constructor(
+    private val repository: KeyguardBouncerRepository,
+    private val primaryBouncerView: BouncerView,
+    @Main private val mainHandler: Handler,
+    private val keyguardStateController: KeyguardStateController,
+    private val keyguardSecurityModel: KeyguardSecurityModel,
+    private val primaryBouncerCallbackInteractor: PrimaryBouncerCallbackInteractor,
+    private val falsingCollector: FalsingCollector,
+    private val dismissCallbackRegistry: DismissCallbackRegistry,
+    private val context: Context,
+    private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+    private val trustRepository: TrustRepository,
+    private val featureFlags: FeatureFlags,
+    @Application private val applicationScope: CoroutineScope,
+) {
+    private val passiveAuthBouncerDelay =
+        context.resources.getInteger(R.integer.primary_bouncer_passive_auth_delay).toLong()
+    /** Runnable to show the primary bouncer. */
+    val showRunnable = Runnable {
+        repository.setPrimaryShow(true)
+        repository.setPrimaryShowingSoon(false)
+        primaryBouncerCallbackInteractor.dispatchVisibilityChanged(View.VISIBLE)
+    }
+
+    val keyguardAuthenticated: Flow<Boolean> = repository.keyguardAuthenticated.filterNotNull()
+    val isShowing: StateFlow<Boolean> = repository.primaryBouncerShow
+    val startingToHide: Flow<Unit> = repository.primaryBouncerStartingToHide.filter { it }.map {}
+    val isBackButtonEnabled: Flow<Boolean> = repository.isBackButtonEnabled.filterNotNull()
+    val showMessage: Flow<BouncerShowMessageModel> = repository.showMessage.filterNotNull()
+    val startingDisappearAnimation: Flow<Runnable> =
+        repository.primaryBouncerStartingDisappearAnimation.filterNotNull()
+    val resourceUpdateRequests: Flow<Boolean> = repository.resourceUpdateRequests.filter { it }
+    val keyguardPosition: Flow<Float> = repository.keyguardPosition
+    val panelExpansionAmount: Flow<Float> = repository.panelExpansionAmount
+    /** 0f = bouncer fully hidden. 1f = bouncer fully visible. */
+    val bouncerExpansion: Flow<Float> =
+        combine(repository.panelExpansionAmount, repository.primaryBouncerShow) {
+            panelExpansion,
+            primaryBouncerIsShowing ->
+            if (primaryBouncerIsShowing) {
+                1f - panelExpansion
+            } else {
+                0f
+            }
+        }
+    /** Allow for interaction when just about fully visible */
+    val isInteractable: Flow<Boolean> = bouncerExpansion.map { it > 0.9 }
+    val sideFpsShowing: Flow<Boolean> = repository.sideFpsShowing
+    private var currentUserActiveUnlockRunning = false
+
+    /** This callback needs to be a class field so it does not get garbage collected. */
+    val keyguardUpdateMonitorCallback =
+        object : KeyguardUpdateMonitorCallback() {
+            override fun onBiometricRunningStateChanged(
+                running: Boolean,
+                biometricSourceType: BiometricSourceType?
+            ) {
+                updateSideFpsVisibility()
+            }
+
+            override fun onStrongAuthStateChanged(userId: Int) {
+                updateSideFpsVisibility()
+            }
+        }
+
+    init {
+        keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback)
+        if (featureFlags.isEnabled(Flags.DELAY_BOUNCER)) {
+            applicationScope.launch {
+                trustRepository.isCurrentUserActiveUnlockRunning.collect {
+                    currentUserActiveUnlockRunning = it
+                }
+            }
+        }
+    }
+
+    // TODO(b/243685699): Move isScrimmed logic to data layer.
+    // TODO(b/243695312): Encapsulate all of the show logic for the bouncer.
+    /** Show the bouncer if necessary and set the relevant states. */
+    @JvmOverloads
+    fun show(isScrimmed: Boolean) {
+        // Reset some states as we show the bouncer.
+        repository.setKeyguardAuthenticated(null)
+        repository.setPrimaryStartingToHide(false)
+
+        val resumeBouncer =
+            (isBouncerShowing() || repository.primaryBouncerShowingSoon.value) &&
+                needsFullscreenBouncer()
+
+        Trace.beginSection("KeyguardBouncer#show")
+        repository.setPrimaryScrimmed(isScrimmed)
+        if (isScrimmed) {
+            setPanelExpansion(KeyguardBouncerConstants.EXPANSION_VISIBLE)
+        }
+
+        // In this special case, we want to hide the bouncer and show it again. We want to emit
+        // show(true) again so that we can reinflate the new view.
+        if (resumeBouncer) {
+            repository.setPrimaryShow(false)
+        }
+
+        if (primaryBouncerView.delegate?.showNextSecurityScreenOrFinish() == true) {
+            // Keyguard is done.
+            return
+        }
+
+        repository.setPrimaryShowingSoon(true)
+        if (usePrimaryBouncerPassiveAuthDelay()) {
+            Log.d(TAG, "delay bouncer, passive auth may succeed")
+            mainHandler.postDelayed(showRunnable, passiveAuthBouncerDelay)
+        } else {
+            DejankUtils.postAfterTraversal(showRunnable)
+        }
+        keyguardStateController.notifyPrimaryBouncerShowing(true)
+        primaryBouncerCallbackInteractor.dispatchStartingToShow()
+        Trace.endSection()
+    }
+
+    /** Sets the correct bouncer states to hide the bouncer. */
+    fun hide() {
+        Trace.beginSection("KeyguardBouncer#hide")
+        if (isFullyShowing()) {
+            SysUiStatsLog.write(
+                SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED,
+                SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED__STATE__HIDDEN
+            )
+            dismissCallbackRegistry.notifyDismissCancelled()
+        }
+
+        repository.setPrimaryStartDisappearAnimation(null)
+        falsingCollector.onBouncerHidden()
+        keyguardStateController.notifyPrimaryBouncerShowing(false /* showing */)
+        cancelShowRunnable()
+        repository.setPrimaryShowingSoon(false)
+        repository.setPrimaryShow(false)
+        repository.setPanelExpansion(EXPANSION_HIDDEN)
+        primaryBouncerCallbackInteractor.dispatchVisibilityChanged(View.INVISIBLE)
+        Trace.endSection()
+    }
+
+    /**
+     * Sets the panel expansion which is calculated further upstream. Panel expansion is from 0f
+     * (panel fully hidden) to 1f (panel fully showing). As the panel shows (from 0f => 1f), the
+     * bouncer hides and as the panel becomes hidden (1f => 0f), the bouncer starts to show.
+     * Therefore, a panel expansion of 1f represents the bouncer fully hidden and a panel expansion
+     * of 0f represents the bouncer fully showing.
+     */
+    fun setPanelExpansion(expansion: Float) {
+        val oldExpansion = repository.panelExpansionAmount.value
+        val expansionChanged = oldExpansion != expansion
+        if (repository.primaryBouncerStartingDisappearAnimation.value == null) {
+            repository.setPanelExpansion(expansion)
+        }
+
+        if (
+            expansion == KeyguardBouncerConstants.EXPANSION_VISIBLE &&
+                oldExpansion != KeyguardBouncerConstants.EXPANSION_VISIBLE
+        ) {
+            falsingCollector.onBouncerShown()
+            primaryBouncerCallbackInteractor.dispatchFullyShown()
+        } else if (
+            expansion == KeyguardBouncerConstants.EXPANSION_HIDDEN &&
+                oldExpansion != KeyguardBouncerConstants.EXPANSION_HIDDEN
+        ) {
+            /*
+             * There are cases where #hide() was not invoked, such as when
+             * NotificationPanelViewController controls the hide animation. Make sure the state gets
+             * updated by calling #hide() directly.
+             */
+            hide()
+            DejankUtils.postAfterTraversal { primaryBouncerCallbackInteractor.dispatchReset() }
+            primaryBouncerCallbackInteractor.dispatchFullyHidden()
+        } else if (
+            expansion != KeyguardBouncerConstants.EXPANSION_VISIBLE &&
+                oldExpansion == KeyguardBouncerConstants.EXPANSION_VISIBLE
+        ) {
+            primaryBouncerCallbackInteractor.dispatchStartingToHide()
+            repository.setPrimaryStartingToHide(true)
+        }
+        if (expansionChanged) {
+            primaryBouncerCallbackInteractor.dispatchExpansionChanged(expansion)
+        }
+    }
+
+    /** Set the initial keyguard message to show when bouncer is shown. */
+    fun showMessage(message: String?, colorStateList: ColorStateList?) {
+        repository.setShowMessage(BouncerShowMessageModel(message, colorStateList))
+    }
+
+    /**
+     * Sets actions to the bouncer based on how the bouncer is dismissed. If the bouncer is
+     * unlocked, we will run the onDismissAction. If the bouncer is existed before unlocking, we
+     * call cancelAction.
+     */
+    fun setDismissAction(
+        onDismissAction: ActivityStarter.OnDismissAction?,
+        cancelAction: Runnable?
+    ) {
+        primaryBouncerView.delegate?.setDismissAction(onDismissAction, cancelAction)
+    }
+
+    /** Update the resources of the views. */
+    fun updateResources() {
+        repository.setResourceUpdateRequests(true)
+    }
+
+    /** Tell the bouncer that keyguard is authenticated. */
+    fun notifyKeyguardAuthenticated(strongAuth: Boolean) {
+        repository.setKeyguardAuthenticated(strongAuth)
+    }
+
+    /** Update the position of the bouncer when showing. */
+    fun setKeyguardPosition(position: Float) {
+        repository.setKeyguardPosition(position)
+    }
+
+    /** Notifies that the state change was handled. */
+    fun notifyKeyguardAuthenticatedHandled() {
+        repository.setKeyguardAuthenticated(null)
+    }
+
+    /** Notifies that the message was shown. */
+    fun onMessageShown() {
+        repository.setShowMessage(null)
+    }
+
+    /** Notify that the resources have been updated */
+    fun notifyUpdatedResources() {
+        repository.setResourceUpdateRequests(false)
+    }
+
+    /** Set whether back button is enabled when on the bouncer screen. */
+    fun setBackButtonEnabled(enabled: Boolean) {
+        repository.setIsBackButtonEnabled(enabled)
+    }
+
+    /** Tell the bouncer to start the pre hide animation. */
+    fun startDisappearAnimation(runnable: Runnable) {
+        if (willRunDismissFromKeyguard()) {
+            runnable.run()
+            return
+        }
+
+        repository.setPrimaryStartDisappearAnimation(runnable)
+    }
+
+    /** Determine whether to show the side fps animation. */
+    fun updateSideFpsVisibility() {
+        val sfpsEnabled: Boolean =
+            context.resources.getBoolean(R.bool.config_show_sidefps_hint_on_bouncer)
+        val fpsDetectionRunning: Boolean = keyguardUpdateMonitor.isFingerprintDetectionRunning
+        val isUnlockingWithFpAllowed: Boolean =
+            keyguardUpdateMonitor.isUnlockingWithFingerprintAllowed
+        val toShow =
+            (isBouncerShowing() &&
+                sfpsEnabled &&
+                fpsDetectionRunning &&
+                isUnlockingWithFpAllowed &&
+                !isAnimatingAway())
+
+        if (KeyguardConstants.DEBUG) {
+            Log.d(
+                TAG,
+                ("sideFpsToShow=$toShow\n" +
+                    "isBouncerShowing=${isBouncerShowing()}\n" +
+                    "configEnabled=$sfpsEnabled\n" +
+                    "fpsDetectionRunning=$fpsDetectionRunning\n" +
+                    "isUnlockingWithFpAllowed=$isUnlockingWithFpAllowed\n" +
+                    "isAnimatingAway=${isAnimatingAway()}")
+            )
+        }
+        repository.setSideFpsShowing(toShow)
+    }
+
+    /** Returns whether bouncer is fully showing. */
+    fun isFullyShowing(): Boolean {
+        return (repository.primaryBouncerShowingSoon.value || isBouncerShowing()) &&
+            repository.panelExpansionAmount.value == KeyguardBouncerConstants.EXPANSION_VISIBLE &&
+            repository.primaryBouncerStartingDisappearAnimation.value == null
+    }
+
+    /** Returns whether bouncer is scrimmed. */
+    fun isScrimmed(): Boolean {
+        return repository.primaryBouncerScrimmed.value
+    }
+
+    /** If bouncer expansion is between 0f and 1f non-inclusive. */
+    fun isInTransit(): Boolean {
+        return repository.primaryBouncerShowingSoon.value ||
+            repository.panelExpansionAmount.value != KeyguardBouncerConstants.EXPANSION_HIDDEN &&
+                repository.panelExpansionAmount.value != KeyguardBouncerConstants.EXPANSION_VISIBLE
+    }
+
+    /** Return whether bouncer is animating away. */
+    fun isAnimatingAway(): Boolean {
+        return repository.primaryBouncerStartingDisappearAnimation.value != null
+    }
+
+    /** Return whether bouncer will dismiss with actions */
+    fun willDismissWithAction(): Boolean {
+        return primaryBouncerView.delegate?.willDismissWithActions() == true
+    }
+
+    /** Will the dismissal run from the keyguard layout (instead of from bouncer) */
+    fun willRunDismissFromKeyguard(): Boolean {
+        return primaryBouncerView.delegate?.willRunDismissFromKeyguard() == true
+    }
+
+    /** Returns whether the bouncer should be full screen. */
+    private fun needsFullscreenBouncer(): Boolean {
+        val mode: KeyguardSecurityModel.SecurityMode =
+            keyguardSecurityModel.getSecurityMode(KeyguardUpdateMonitor.getCurrentUser())
+        return mode == KeyguardSecurityModel.SecurityMode.SimPin ||
+            mode == KeyguardSecurityModel.SecurityMode.SimPuk
+    }
+
+    /** Remove the show runnable from the main handler queue to improve performance. */
+    private fun cancelShowRunnable() {
+        DejankUtils.removeCallbacks(showRunnable)
+        mainHandler.removeCallbacks(showRunnable)
+    }
+
+    /** Returns whether the primary bouncer is currently showing. */
+    fun isBouncerShowing(): Boolean {
+        return isShowing.value
+    }
+
+    /** Whether we want to wait to show the bouncer in case passive auth succeeds. */
+    private fun usePrimaryBouncerPassiveAuthDelay(): Boolean {
+        val canRunFaceAuth =
+            keyguardStateController.isFaceAuthEnabled &&
+                keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(BiometricSourceType.FACE) &&
+                keyguardUpdateMonitor.doesCurrentPostureAllowFaceAuth()
+        val canRunActiveUnlock =
+            currentUserActiveUnlockRunning &&
+                keyguardUpdateMonitor.canTriggerActiveUnlockBasedOnDeviceState()
+
+        return featureFlags.isEnabled(Flags.DELAY_BOUNCER) &&
+            !needsFullscreenBouncer() &&
+            (canRunFaceAuth || canRunActiveUnlock)
+    }
+
+    companion object {
+        private const val TAG = "PrimaryBouncerInteractor"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/shared/constants/KeyguardBouncerConstants.kt b/packages/SystemUI/src/com/android/systemui/bouncer/shared/constants/KeyguardBouncerConstants.kt
new file mode 100644
index 0000000..9f17811
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/shared/constants/KeyguardBouncerConstants.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.bouncer.shared.constants
+
+object KeyguardBouncerConstants {
+    /**
+     * Values for the bouncer expansion represented as the panel expansion. Panel expansion 1f =
+     * panel fully showing = bouncer fully hidden Panel expansion 0f = panel fully hiding = bouncer
+     * fully showing
+     */
+    const val EXPANSION_HIDDEN = 1f
+    const val EXPANSION_VISIBLE = 0f
+    const val ALPHA_EXPANSION_THRESHOLD = 0.95f
+
+    /**
+     * This value is used for denoting the PIN length at which we want to layout the view in which
+     * PIN hinting is enabled
+     */
+    const val DEFAULT_PIN_LENGTH = 6
+
+    object ColorId {
+        const val TITLE = com.android.internal.R.attr.materialColorOnSurface
+        const val PIN_SHAPES = com.android.internal.R.attr.materialColorOnSurfaceVariant
+        const val NUM_PAD_BACKGROUND = com.android.internal.R.attr.materialColorSurfaceContainerHigh
+        const val NUM_PAD_BACKGROUND_PRESSED = com.android.internal.R.attr.materialColorPrimaryFixed
+        const val NUM_PAD_PRESSED = com.android.internal.R.attr.materialColorOnPrimaryFixed
+        const val NUM_PAD_KEY = com.android.internal.R.attr.materialColorOnSurface
+        const val NUM_PAD_BUTTON = com.android.internal.R.attr.materialColorOnSecondaryFixed
+        const val EMERGENCY_BUTTON = com.android.internal.R.attr.materialColorTertiaryFixed
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/shared/model/AuthenticationThrottledModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/shared/model/AuthenticationThrottledModel.kt
deleted file mode 100644
index cbea635..0000000
--- a/packages/SystemUI/src/com/android/systemui/bouncer/shared/model/AuthenticationThrottledModel.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.systemui.bouncer.shared.model
-
-/**
- * Models application state for when further authentication attempts are being throttled due to too
- * many consecutive failed authentication attempts.
- */
-data class AuthenticationThrottledModel(
-    /** Total number of failed attempts so far. */
-    val failedAttemptCount: Int,
-    /** Total amount of time the user has to wait before attempting again. */
-    val totalDurationSec: Int,
-    /** Remaining amount of time the user has to wait before attempting again. */
-    val remainingDurationSec: Int,
-)
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/shared/model/BouncerCallbackActionsModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/shared/model/BouncerCallbackActionsModel.kt
new file mode 100644
index 0000000..afddd39
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/shared/model/BouncerCallbackActionsModel.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.bouncer.shared.model
+
+import com.android.systemui.plugins.ActivityStarter
+
+/** Encapsulates callbacks to be invoked by the bouncer logic. */
+// TODO(b/243683121): Move dismiss logic from view controllers
+data class BouncerCallbackActionsModel(
+    val onDismissAction: ActivityStarter.OnDismissAction?,
+    val cancelAction: Runnable?
+)
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/shared/model/BouncerMessageModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/shared/model/BouncerMessageModel.kt
new file mode 100644
index 0000000..0e9e962
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/shared/model/BouncerMessageModel.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.bouncer.shared.model
+
+import android.content.res.ColorStateList
+
+/**
+ * Represents the message displayed on the bouncer. It has two parts, primary and a secondary
+ * message
+ */
+data class BouncerMessageModel(val message: Message? = null, val secondaryMessage: Message? = null)
+
+/**
+ * Representation of a single message on the bouncer. It can be either a string or a string resource
+ * ID
+ */
+data class Message(
+    val message: String? = null,
+    val messageResId: Int? = null,
+    val colorState: ColorStateList? = null,
+    /** Any plural formatter arguments that can used to format the [messageResId] */
+    var formatterArgs: Map<String, Any>? = null,
+    /** Specifies whether this text should be animated when it is shown. */
+    var animate: Boolean = true,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/shared/model/BouncerShowMessageModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/shared/model/BouncerShowMessageModel.kt
new file mode 100644
index 0000000..1878572
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/shared/model/BouncerShowMessageModel.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.bouncer.shared.model
+
+import android.content.res.ColorStateList
+
+/** Show a keyguard message to the bouncer. */
+data class BouncerShowMessageModel(val message: String?, val colorStateList: ColorStateList?)
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/BouncerMessageView.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/BouncerMessageView.kt
new file mode 100644
index 0000000..47fac2b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/BouncerMessageView.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bouncer.ui
+
+import android.content.Context
+import android.util.AttributeSet
+import android.widget.LinearLayout
+import com.android.keyguard.BouncerKeyguardMessageArea
+import com.android.keyguard.KeyguardMessageArea
+import com.android.keyguard.KeyguardMessageAreaController
+import com.android.systemui.R
+
+class BouncerMessageView : LinearLayout {
+    constructor(context: Context?) : super(context)
+
+    constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
+
+    init {
+        inflate(context, R.layout.bouncer_message_view, this)
+    }
+
+    var primaryMessageView: BouncerKeyguardMessageArea? = null
+    var secondaryMessageView: BouncerKeyguardMessageArea? = null
+    var primaryMessage: KeyguardMessageAreaController<KeyguardMessageArea>? = null
+    var secondaryMessage: KeyguardMessageAreaController<KeyguardMessageArea>? = null
+    override fun onFinishInflate() {
+        super.onFinishInflate()
+        primaryMessageView = findViewById(R.id.bouncer_primary_message_area)
+        secondaryMessageView = findViewById(R.id.bouncer_secondary_message_area)
+        primaryMessageView?.disable()
+        secondaryMessageView?.disable()
+    }
+
+    fun init(factory: KeyguardMessageAreaController.Factory) {
+        primaryMessage = factory.create(primaryMessageView)
+        primaryMessage?.init()
+        secondaryMessage = factory.create(secondaryMessageView)
+        secondaryMessage?.init()
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/BouncerView.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/BouncerView.kt
new file mode 100644
index 0000000..dc00321
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/BouncerView.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.bouncer.ui
+
+import android.view.KeyEvent
+import android.window.OnBackAnimationCallback
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.plugins.ActivityStarter
+import java.lang.ref.WeakReference
+import javax.inject.Inject
+
+/** An abstraction to interface with the ui layer, without changing state. */
+interface BouncerView {
+    var delegate: BouncerViewDelegate?
+}
+
+/** A lightweight class to hold reference to the ui delegate. */
+@SysUISingleton
+class BouncerViewImpl @Inject constructor() : BouncerView {
+    private var _delegate: WeakReference<BouncerViewDelegate?> = WeakReference(null)
+    override var delegate: BouncerViewDelegate?
+        get() = _delegate.get()
+        set(value) {
+            _delegate = WeakReference(value)
+        }
+}
+
+/** An abstraction that implements view logic. */
+interface BouncerViewDelegate {
+    fun isFullScreenBouncer(): Boolean
+    fun shouldDismissOnMenuPressed(): Boolean
+    fun interceptMediaKey(event: KeyEvent?): Boolean
+    fun dispatchBackKeyEventPreIme(): Boolean
+    fun showNextSecurityScreenOrFinish(): Boolean
+    fun resume()
+    fun setDismissAction(
+        onDismissAction: ActivityStarter.OnDismissAction?,
+        cancelAction: Runnable?,
+    )
+    fun willDismissWithActions(): Boolean
+    fun willRunDismissFromKeyguard(): Boolean
+    /** @return the {@link OnBackAnimationCallback} to animate Bouncer during a back gesture. */
+    fun getBackCallback(): OnBackAnimationCallback
+    fun showPromptReason(reason: Int)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/BouncerViewModule.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/BouncerViewModule.kt
new file mode 100644
index 0000000..0cbfb68
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/BouncerViewModule.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bouncer.ui
+
+import dagger.Binds
+import dagger.Module
+
+@Module
+interface BouncerViewModule {
+    /** Binds BouncerView to BouncerViewImpl and makes it injectable. */
+    @Binds fun bindBouncerView(bouncerViewImpl: BouncerViewImpl): BouncerView
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerMessageViewBinder.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerMessageViewBinder.kt
new file mode 100644
index 0000000..5a59012
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerMessageViewBinder.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.
+ */
+
+package com.android.systemui.bouncer.ui.binder
+
+import android.text.TextUtils
+import android.util.PluralsMessageFormatter
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.keyguard.BouncerKeyguardMessageArea
+import com.android.keyguard.KeyguardMessageArea
+import com.android.keyguard.KeyguardMessageAreaController
+import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor
+import com.android.systemui.bouncer.shared.model.Message
+import com.android.systemui.bouncer.ui.BouncerMessageView
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.log.BouncerLogger
+import kotlinx.coroutines.launch
+
+object BouncerMessageViewBinder {
+    @JvmStatic
+    fun bind(
+        view: BouncerMessageView,
+        interactor: BouncerMessageInteractor,
+        factory: KeyguardMessageAreaController.Factory,
+        bouncerLogger: BouncerLogger,
+        featureFlags: FeatureFlags
+    ) {
+        view.repeatWhenAttached {
+            if (!featureFlags.isEnabled(Flags.REVAMPED_BOUNCER_MESSAGES)) {
+                view.primaryMessageView?.disable()
+                view.secondaryMessageView?.disable()
+                return@repeatWhenAttached
+            }
+            view.init(factory)
+            view.primaryMessage?.setIsVisible(true)
+            view.secondaryMessage?.setIsVisible(true)
+            repeatOnLifecycle(Lifecycle.State.STARTED) {
+                bouncerLogger.startBouncerMessageInteractor()
+                launch {
+                    interactor.bouncerMessage.collect {
+                        bouncerLogger.bouncerMessageUpdated(it)
+                        updateView(
+                            view.primaryMessage,
+                            view.primaryMessageView,
+                            message = it?.message,
+                            allowTruncation = true,
+                        )
+                        updateView(
+                            view.secondaryMessage,
+                            view.secondaryMessageView,
+                            message = it?.secondaryMessage,
+                            allowTruncation = false,
+                        )
+                        view.requestLayout()
+                    }
+                }
+            }
+        }
+    }
+
+    private fun updateView(
+        controller: KeyguardMessageAreaController<KeyguardMessageArea>?,
+        view: BouncerKeyguardMessageArea?,
+        message: Message?,
+        allowTruncation: Boolean = false,
+    ) {
+        if (view == null || controller == null) return
+        if (message?.message != null || message?.messageResId != null) {
+            controller.setIsVisible(true)
+            var newMessage = message.message ?: ""
+            if (message.messageResId != null && message.messageResId != 0) {
+                newMessage = view.resources.getString(message.messageResId)
+                if (message.formatterArgs != null) {
+                    newMessage =
+                        PluralsMessageFormatter.format(
+                            view.resources,
+                            message.formatterArgs,
+                            message.messageResId
+                        )
+                }
+            }
+            controller.setMessage(newMessage, message.animate)
+        } else {
+            controller.setIsVisible(false)
+            controller.setMessage(0)
+        }
+        message?.colorState?.let { controller.setNextMessageColor(it) }
+        view.ellipsize =
+            if (allowTruncation) TextUtils.TruncateAt.END else TextUtils.TruncateAt.MARQUEE
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt
new file mode 100644
index 0000000..34e934b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt
@@ -0,0 +1,253 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.bouncer.ui.binder
+
+import android.view.KeyEvent
+import android.view.View
+import android.view.ViewGroup
+import android.window.OnBackAnimationCallback
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.keyguard.KeyguardMessageAreaController
+import com.android.keyguard.KeyguardSecurityContainerController
+import com.android.keyguard.KeyguardSecurityModel
+import com.android.keyguard.KeyguardSecurityView
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.dagger.KeyguardBouncerComponent
+import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor
+import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.EXPANSION_VISIBLE
+import com.android.systemui.bouncer.ui.BouncerViewDelegate
+import com.android.systemui.bouncer.ui.viewmodel.KeyguardBouncerViewModel
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.log.BouncerLogger
+import com.android.systemui.plugins.ActivityStarter
+import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.launch
+
+/** Binds the bouncer container to its view model. */
+object KeyguardBouncerViewBinder {
+    @JvmStatic
+    fun bind(
+        view: ViewGroup,
+        viewModel: KeyguardBouncerViewModel,
+        primaryBouncerToGoneTransitionViewModel: PrimaryBouncerToGoneTransitionViewModel,
+        componentFactory: KeyguardBouncerComponent.Factory,
+        messageAreaControllerFactory: KeyguardMessageAreaController.Factory,
+        bouncerMessageInteractor: BouncerMessageInteractor,
+        bouncerLogger: BouncerLogger,
+        featureFlags: FeatureFlags,
+    ) {
+        // Builds the KeyguardSecurityContainerController from bouncer view group.
+        val securityContainerController: KeyguardSecurityContainerController =
+            componentFactory.create(view).securityContainerController
+        securityContainerController.init()
+        val delegate =
+            object : BouncerViewDelegate {
+                override fun isFullScreenBouncer(): Boolean {
+                    val mode = securityContainerController.currentSecurityMode
+                    return mode == KeyguardSecurityModel.SecurityMode.SimPin ||
+                        mode == KeyguardSecurityModel.SecurityMode.SimPuk
+                }
+
+                override fun getBackCallback(): OnBackAnimationCallback {
+                    return securityContainerController.backCallback
+                }
+
+                override fun shouldDismissOnMenuPressed(): Boolean {
+                    return securityContainerController.shouldEnableMenuKey()
+                }
+
+                override fun interceptMediaKey(event: KeyEvent?): Boolean {
+                    return securityContainerController.interceptMediaKey(event)
+                }
+
+                override fun dispatchBackKeyEventPreIme(): Boolean {
+                    return securityContainerController.dispatchBackKeyEventPreIme()
+                }
+
+                override fun showNextSecurityScreenOrFinish(): Boolean {
+                    return securityContainerController.dismiss(
+                        KeyguardUpdateMonitor.getCurrentUser()
+                    )
+                }
+
+                override fun resume() {
+                    securityContainerController.showPrimarySecurityScreen(/* isTurningOff= */ false)
+                    securityContainerController.onResume(KeyguardSecurityView.SCREEN_ON)
+                }
+
+                override fun setDismissAction(
+                    onDismissAction: ActivityStarter.OnDismissAction?,
+                    cancelAction: Runnable?
+                ) {
+                    securityContainerController.setOnDismissAction(onDismissAction, cancelAction)
+                }
+
+                override fun willDismissWithActions(): Boolean {
+                    return securityContainerController.hasDismissActions()
+                }
+
+                override fun willRunDismissFromKeyguard(): Boolean {
+                    return securityContainerController.willRunDismissFromKeyguard()
+                }
+
+                override fun showPromptReason(reason: Int) {
+                    securityContainerController.showPromptReason(reason)
+                }
+            }
+        view.repeatWhenAttached {
+            repeatOnLifecycle(Lifecycle.State.CREATED) {
+                try {
+                    viewModel.setBouncerViewDelegate(delegate)
+                    launch {
+                        viewModel.isShowing.collect { isShowing ->
+                            view.visibility = if (isShowing) View.VISIBLE else View.INVISIBLE
+                            if (isShowing) {
+                                securityContainerController.reinflateViewFlipper {
+                                    // Reset Security Container entirely.
+                                    securityContainerController.onBouncerVisibilityChanged(
+                                        /* isVisible= */ true
+                                    )
+                                    securityContainerController.showPrimarySecurityScreen(
+                                        /* turningOff= */ false
+                                    )
+                                    securityContainerController.setInitialMessage()
+                                    securityContainerController.appear()
+                                    securityContainerController.onResume(
+                                        KeyguardSecurityView.SCREEN_ON
+                                    )
+                                    bouncerLogger.bindingBouncerMessageView()
+                                    it.bindMessageView(
+                                        bouncerMessageInteractor,
+                                        messageAreaControllerFactory,
+                                        bouncerLogger,
+                                        featureFlags
+                                    )
+                                }
+                            } else {
+                                bouncerMessageInteractor.onBouncerBeingHidden()
+                                securityContainerController.onBouncerVisibilityChanged(
+                                    /* isVisible= */ false
+                                )
+                                securityContainerController.cancelDismissAction()
+                                securityContainerController.reset()
+                                securityContainerController.onPause()
+                            }
+                        }
+                    }
+
+                    launch {
+                        viewModel.startingToHide.collect {
+                            securityContainerController.onStartingToHide()
+                        }
+                    }
+
+                    launch {
+                        viewModel.startDisappearAnimation.collect {
+                            securityContainerController.startDisappearAnimation(it)
+                        }
+                    }
+
+                    launch {
+                        viewModel.bouncerExpansionAmount.collect { expansion ->
+                            securityContainerController.setExpansion(expansion)
+                        }
+                    }
+
+                    launch {
+                        primaryBouncerToGoneTransitionViewModel.bouncerAlpha.collect { alpha ->
+                            securityContainerController.setAlpha(alpha)
+                        }
+                    }
+
+                    launch {
+                        viewModel.bouncerExpansionAmount
+                            .filter { it == EXPANSION_VISIBLE }
+                            .collect {
+                                securityContainerController.onResume(KeyguardSecurityView.SCREEN_ON)
+                                view.announceForAccessibility(securityContainerController.title)
+                            }
+                    }
+
+                    launch {
+                        viewModel.isInteractable.collect { isInteractable ->
+                            securityContainerController.setInteractable(isInteractable)
+                        }
+                    }
+
+                    launch {
+                        viewModel.keyguardPosition.collect { position ->
+                            securityContainerController.updateKeyguardPosition(position)
+                        }
+                    }
+
+                    launch {
+                        viewModel.updateResources.collect {
+                            securityContainerController.updateResources()
+                            viewModel.notifyUpdateResources()
+                        }
+                    }
+
+                    launch {
+                        viewModel.bouncerShowMessage.collect {
+                            securityContainerController.showMessage(
+                                it.message,
+                                it.colorStateList,
+                                /* animated= */ true
+                            )
+                            viewModel.onMessageShown()
+                        }
+                    }
+
+                    launch {
+                        viewModel.keyguardAuthenticated.collect {
+                            securityContainerController.finish(
+                                it,
+                                KeyguardUpdateMonitor.getCurrentUser()
+                            )
+                            viewModel.notifyKeyguardAuthenticated()
+                        }
+                    }
+
+                    launch {
+                        viewModel
+                            .observeOnIsBackButtonEnabled { view.systemUiVisibility }
+                            .collect { view.systemUiVisibility = it }
+                    }
+
+                    launch {
+                        viewModel.shouldUpdateSideFps.collect {
+                            viewModel.updateSideFpsVisibility()
+                        }
+                    }
+
+                    launch {
+                        viewModel.sideFpsShowing.collect {
+                            securityContainerController.updateSideFpsVisibility(it)
+                        }
+                    }
+                    awaitCancellation()
+                } finally {
+                    viewModel.setBouncerViewDelegate(null)
+                }
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
index 984d9ab..a4ef5ce 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
@@ -14,18 +14,26 @@
  * limitations under the License.
  */
 
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
 package com.android.systemui.bouncer.ui.viewmodel
 
 import android.content.Context
 import com.android.systemui.R
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
 import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
-import com.android.systemui.bouncer.shared.model.AuthenticationThrottledModel
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.util.kotlin.pairwise
 import dagger.assisted.Assisted
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
+import kotlin.math.ceil
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.channels.BufferOverflow
+import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
@@ -43,21 +51,23 @@
     @Application private val applicationContext: Context,
     @Application private val applicationScope: CoroutineScope,
     interactorFactory: BouncerInteractor.Factory,
+    featureFlags: FeatureFlags,
     @Assisted containerName: String,
 ) {
     private val interactor: BouncerInteractor = interactorFactory.create(containerName)
 
     private val isInputEnabled: StateFlow<Boolean> =
-        interactor.throttling
-            .map { it == null }
+        interactor.isThrottled
+            .map { !it }
             .stateIn(
                 scope = applicationScope,
                 started = SharingStarted.WhileSubscribed(),
-                initialValue = interactor.throttling.value == null,
+                initialValue = !interactor.isThrottled.value,
             )
 
     private val pin: PinBouncerViewModel by lazy {
         PinBouncerViewModel(
+            applicationContext = applicationContext,
             applicationScope = applicationScope,
             interactor = interactor,
             isInputEnabled = isInputEnabled,
@@ -66,6 +76,7 @@
 
     private val password: PasswordBouncerViewModel by lazy {
         PasswordBouncerViewModel(
+            applicationScope = applicationScope,
             interactor = interactor,
             isInputEnabled = isInputEnabled,
         )
@@ -81,22 +92,71 @@
     }
 
     /** View-model for the current UI, based on the current authentication method. */
+    private val _authMethod =
+        MutableSharedFlow<AuthMethodBouncerViewModel?>(
+            replay = 1,
+            onBufferOverflow = BufferOverflow.DROP_OLDEST,
+        )
     val authMethod: StateFlow<AuthMethodBouncerViewModel?> =
-        interactor.authenticationMethod
-            .map { authMethod -> toViewModel(authMethod) }
-            .stateIn(
-                scope = applicationScope,
-                started = SharingStarted.WhileSubscribed(),
-                initialValue = toViewModel(interactor.authenticationMethod.value),
-            )
+        _authMethod.stateIn(
+            scope = applicationScope,
+            started = SharingStarted.WhileSubscribed(),
+            initialValue = null,
+        )
+
+    init {
+        if (featureFlags.isEnabled(Flags.SCENE_CONTAINER)) {
+            applicationScope.launch {
+                interactor.isThrottled
+                    .map { isThrottled ->
+                        if (isThrottled) {
+                            when (interactor.getAuthenticationMethod()) {
+                                is AuthenticationMethodModel.Pin ->
+                                    R.string.kg_too_many_failed_pin_attempts_dialog_message
+                                is AuthenticationMethodModel.Password ->
+                                    R.string.kg_too_many_failed_password_attempts_dialog_message
+                                is AuthenticationMethodModel.Pattern ->
+                                    R.string.kg_too_many_failed_pattern_attempts_dialog_message
+                                else -> null
+                            }?.let { stringResourceId ->
+                                applicationContext.getString(
+                                    stringResourceId,
+                                    interactor.throttling.value.failedAttemptCount,
+                                    ceil(interactor.throttling.value.remainingMs / 1000f).toInt(),
+                                )
+                            }
+                        } else {
+                            null
+                        }
+                    }
+                    .distinctUntilChanged()
+                    .collect { dialogMessageOrNull ->
+                        if (dialogMessageOrNull != null) {
+                            _throttlingDialogMessage.value = dialogMessageOrNull
+                        }
+                    }
+            }
+
+            applicationScope.launch {
+                _authMethod.subscriptionCount
+                    .pairwise()
+                    .map { (previousCount, currentCount) -> currentCount > previousCount }
+                    .collect { subscriberAdded ->
+                        if (subscriberAdded) {
+                            reloadAuthMethod()
+                        }
+                    }
+            }
+        }
+    }
 
     /** The user-facing message to show in the bouncer. */
     val message: StateFlow<MessageViewModel> =
         combine(
                 interactor.message,
-                interactor.throttling,
-            ) { message, throttling ->
-                toMessageViewModel(message, throttling)
+                interactor.isThrottled,
+            ) { message, isThrottled ->
+                toMessageViewModel(message, isThrottled)
             }
             .stateIn(
                 scope = applicationScope,
@@ -104,7 +164,7 @@
                 initialValue =
                     toMessageViewModel(
                         message = interactor.message.value,
-                        throttling = interactor.throttling.value,
+                        isThrottled = interactor.isThrottled.value,
                     ),
             )
 
@@ -120,37 +180,6 @@
      */
     val throttlingDialogMessage: StateFlow<String?> = _throttlingDialogMessage.asStateFlow()
 
-    init {
-        applicationScope.launch {
-            interactor.throttling
-                .map { model ->
-                    model?.let {
-                        when (interactor.authenticationMethod.value) {
-                            is AuthenticationMethodModel.PIN ->
-                                R.string.kg_too_many_failed_pin_attempts_dialog_message
-                            is AuthenticationMethodModel.Password ->
-                                R.string.kg_too_many_failed_password_attempts_dialog_message
-                            is AuthenticationMethodModel.Pattern ->
-                                R.string.kg_too_many_failed_pattern_attempts_dialog_message
-                            else -> null
-                        }?.let { stringResourceId ->
-                            applicationContext.getString(
-                                stringResourceId,
-                                model.failedAttemptCount,
-                                model.totalDurationSec,
-                            )
-                        }
-                    }
-                }
-                .distinctUntilChanged()
-                .collect { dialogMessageOrNull ->
-                    if (dialogMessageOrNull != null) {
-                        _throttlingDialogMessage.value = dialogMessageOrNull
-                    }
-                }
-        }
-    }
-
     /** Notifies that the emergency services button was clicked. */
     fun onEmergencyServicesButtonClicked() {
         // TODO(b/280877228): implement this
@@ -161,24 +190,24 @@
         _throttlingDialogMessage.value = null
     }
 
-    private fun toViewModel(
-        authMethod: AuthenticationMethodModel,
-    ): AuthMethodBouncerViewModel? {
-        return when (authMethod) {
-            is AuthenticationMethodModel.PIN -> pin
-            is AuthenticationMethodModel.Password -> password
-            is AuthenticationMethodModel.Pattern -> pattern
-            else -> null
-        }
-    }
-
     private fun toMessageViewModel(
         message: String?,
-        throttling: AuthenticationThrottledModel?,
+        isThrottled: Boolean,
     ): MessageViewModel {
         return MessageViewModel(
             text = message ?: "",
-            isUpdateAnimated = throttling == null,
+            isUpdateAnimated = !isThrottled,
+        )
+    }
+
+    private suspend fun reloadAuthMethod() {
+        _authMethod.tryEmit(
+            when (interactor.getAuthenticationMethod()) {
+                is AuthenticationMethodModel.Pin -> pin
+                is AuthenticationMethodModel.Password -> password
+                is AuthenticationMethodModel.Pattern -> pattern
+                else -> null
+            }
         )
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModel.kt
new file mode 100644
index 0000000..6ba8439
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModel.kt
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.bouncer.ui.viewmodel
+
+import android.view.View
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
+import com.android.systemui.bouncer.shared.model.BouncerShowMessageModel
+import com.android.systemui.bouncer.ui.BouncerView
+import com.android.systemui.bouncer.ui.BouncerViewDelegate
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
+
+/** Models UI state for the lock screen bouncer; handles user input. */
+class KeyguardBouncerViewModel
+@Inject
+constructor(
+    private val view: BouncerView,
+    private val interactor: PrimaryBouncerInteractor,
+) {
+    /** Observe on bouncer expansion amount. */
+    val bouncerExpansionAmount: Flow<Float> = interactor.panelExpansionAmount
+
+    /** Can the user interact with the view? */
+    val isInteractable: Flow<Boolean> = interactor.isInteractable
+
+    /** Observe whether bouncer is showing or not. */
+    val isShowing: Flow<Boolean> = interactor.isShowing
+
+    /** Observe whether bouncer is starting to hide. */
+    val startingToHide: Flow<Unit> = interactor.startingToHide
+
+    /** Observe whether we want to start the disappear animation. */
+    val startDisappearAnimation: Flow<Runnable> = interactor.startingDisappearAnimation
+
+    /** Observe whether we want to update keyguard position. */
+    val keyguardPosition: Flow<Float> = interactor.keyguardPosition
+
+    /** Observe whether we want to update resources. */
+    val updateResources: Flow<Boolean> = interactor.resourceUpdateRequests
+
+    /** Observe whether we want to set a keyguard message when the bouncer shows. */
+    val bouncerShowMessage: Flow<BouncerShowMessageModel> = interactor.showMessage
+
+    /** Observe whether keyguard is authenticated already. */
+    val keyguardAuthenticated: Flow<Boolean> = interactor.keyguardAuthenticated
+
+    /** Observe whether the side fps is showing. */
+    val sideFpsShowing: Flow<Boolean> = interactor.sideFpsShowing
+
+    /** Observe whether we should update fps is showing. */
+    val shouldUpdateSideFps: Flow<Unit> =
+        merge(
+            interactor.isShowing.map {},
+            interactor.startingToHide,
+            interactor.startingDisappearAnimation.filterNotNull().map {}
+        )
+
+    /** Observe whether we want to update resources. */
+    fun notifyUpdateResources() {
+        interactor.notifyUpdatedResources()
+    }
+
+    /** Notify that keyguard authenticated was handled */
+    fun notifyKeyguardAuthenticated() {
+        interactor.notifyKeyguardAuthenticatedHandled()
+    }
+
+    /** Notifies that the message was shown. */
+    fun onMessageShown() {
+        interactor.onMessageShown()
+    }
+
+    fun updateSideFpsVisibility() {
+        interactor.updateSideFpsVisibility()
+    }
+
+    /** Observe whether back button is enabled. */
+    fun observeOnIsBackButtonEnabled(systemUiVisibility: () -> Int): Flow<Int> {
+        return interactor.isBackButtonEnabled.map { enabled ->
+            var vis: Int = systemUiVisibility()
+            vis =
+                if (enabled) {
+                    vis and View.STATUS_BAR_DISABLE_BACK.inv()
+                } else {
+                    vis or View.STATUS_BAR_DISABLE_BACK
+                }
+            vis
+        }
+    }
+
+    /** Set an abstraction that will hold reference to the ui delegate for the bouncer view. */
+    fun setBouncerViewDelegate(delegate: BouncerViewDelegate?) {
+        view.delegate = delegate
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
index 55929b5..ca15f4e 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
@@ -17,12 +17,15 @@
 package com.android.systemui.bouncer.ui.viewmodel
 
 import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
+import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.launch
 
 /** Holds UI state and handles user input for the password bouncer UI. */
 class PasswordBouncerViewModel(
+    private val applicationScope: CoroutineScope,
     private val interactor: BouncerInteractor,
     isInputEnabled: StateFlow<Boolean>,
 ) :
@@ -50,10 +53,13 @@
 
     /** Notifies that the user has pressed the key for attempting to authenticate the password. */
     fun onAuthenticateKeyPressed() {
-        if (!interactor.authenticate(password.value.toCharArray().toList())) {
-            showFailureAnimation()
-        }
-
+        val password = _password.value.toCharArray().toList()
         _password.value = ""
+
+        applicationScope.launch {
+            if (interactor.authenticate(password) != true) {
+                showFailureAnimation()
+            }
+        }
     }
 }
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 d9ef75d..4be539d 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
@@ -31,11 +31,12 @@
 import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
 
 /** Holds UI state and handles user input for the pattern bouncer UI. */
 class PatternBouncerViewModel(
     private val applicationContext: Context,
-    applicationScope: CoroutineScope,
+    private val applicationScope: CoroutineScope,
     private val interactor: BouncerInteractor,
     isInputEnabled: StateFlow<Boolean>,
 ) :
@@ -68,14 +69,7 @@
     val dots: StateFlow<List<PatternDotViewModel>> = _dots.asStateFlow()
 
     /** Whether the pattern itself should be rendered visibly. */
-    val isPatternVisible: StateFlow<Boolean> =
-        interactor.authenticationMethod
-            .map { authMethod -> isPatternVisible(authMethod) }
-            .stateIn(
-                scope = applicationScope,
-                started = SharingStarted.Eagerly,
-                initialValue = isPatternVisible(interactor.authenticationMethod.value),
-            )
+    val isPatternVisible: StateFlow<Boolean> = interactor.isPatternVisible
 
     /** Notifies that the UI has been shown to the user. */
     fun onShown() {
@@ -153,19 +147,16 @@
 
     /** Notifies that the user has ended the drag gesture across the dot grid. */
     fun onDragEnd() {
-        val isSuccessfullyAuthenticated =
-            interactor.authenticate(_selectedDots.value.map { it.toCoordinate() })
-        if (!isSuccessfullyAuthenticated) {
-            showFailureAnimation()
-        }
-
+        val pattern = _selectedDots.value.map { it.toCoordinate() }
         _dots.value = defaultDots()
         _currentDot.value = null
         _selectedDots.value = linkedSetOf()
-    }
 
-    private fun isPatternVisible(authMethodModel: AuthenticationMethodModel): Boolean {
-        return (authMethodModel as? AuthenticationMethodModel.Pattern)?.isPatternVisible ?: false
+        applicationScope.launch {
+            if (interactor.authenticate(pattern) != true) {
+                showFailureAnimation()
+            }
+        }
     }
 
     private fun defaultDots(): List<PatternDotViewModel> {
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
index 5c0fd92..1b14acc 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
@@ -16,21 +16,21 @@
 
 package com.android.systemui.bouncer.ui.viewmodel
 
-import androidx.annotation.VisibleForTesting
+import android.content.Context
+import com.android.keyguard.PinShapeAdapter
 import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
-import com.android.systemui.util.kotlin.pairwise
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.launch
 
 /** Holds UI state and handles user input for the PIN code bouncer UI. */
 class PinBouncerViewModel(
+    applicationContext: Context,
     private val applicationScope: CoroutineScope,
     private val interactor: BouncerInteractor,
     isInputEnabled: StateFlow<Boolean>,
@@ -39,21 +39,46 @@
         isInputEnabled = isInputEnabled,
     ) {
 
-    private val entered = MutableStateFlow<List<Int>>(emptyList())
-    /**
-     * The length of the PIN digits that were input so far, two values are supplied the previous and
-     * the current.
-     */
-    val pinLengths: StateFlow<Pair<Int, Int>> =
-        entered
-            .pairwise()
-            .map { it.previousValue.size to it.newValue.size }
+    val pinShapes = PinShapeAdapter(applicationContext)
+
+    private val mutablePinEntries = MutableStateFlow<List<EnteredKey>>(emptyList())
+    val pinEntries: StateFlow<List<EnteredKey>> = mutablePinEntries
+
+    /** The length of the PIN for which we should show a hint. */
+    val hintedPinLength: StateFlow<Int?> = interactor.hintedPinLength
+
+    /** Appearance of the backspace button. */
+    val backspaceButtonAppearance: StateFlow<ActionButtonAppearance> =
+        combine(
+                mutablePinEntries,
+                interactor.isAutoConfirmEnabled,
+            ) { mutablePinEntries, isAutoConfirmEnabled ->
+                computeBackspaceButtonAppearance(
+                    enteredPin = mutablePinEntries,
+                    isAutoConfirmEnabled = isAutoConfirmEnabled,
+                )
+            }
             .stateIn(
                 scope = applicationScope,
-                started = SharingStarted.WhileSubscribed(),
-                initialValue = 0 to 0,
+                started = SharingStarted.Eagerly,
+                initialValue = ActionButtonAppearance.Hidden,
             )
-    private var resetPinJob: Job? = null
+
+    /** Appearance of the confirm button. */
+    val confirmButtonAppearance: StateFlow<ActionButtonAppearance> =
+        interactor.isAutoConfirmEnabled
+            .map {
+                if (it) {
+                    ActionButtonAppearance.Hidden
+                } else {
+                    ActionButtonAppearance.Shown
+                }
+            }
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.Eagerly,
+                initialValue = ActionButtonAppearance.Hidden,
+            )
 
     /** Notifies that the UI has been shown to the user. */
     fun onShown() {
@@ -62,47 +87,83 @@
 
     /** Notifies that the user clicked on a PIN button with the given digit value. */
     fun onPinButtonClicked(input: Int) {
-        resetPinJob?.cancel()
-        resetPinJob = null
-
-        if (entered.value.isEmpty()) {
+        if (mutablePinEntries.value.isEmpty()) {
             interactor.clearMessage()
         }
 
-        entered.value += input
+        mutablePinEntries.value += EnteredKey(input)
+
+        tryAuthenticate(useAutoConfirm = true)
     }
 
     /** Notifies that the user clicked the backspace button. */
     fun onBackspaceButtonClicked() {
-        if (entered.value.isEmpty()) {
+        if (mutablePinEntries.value.isEmpty()) {
             return
         }
-
-        entered.value = entered.value.toMutableList().apply { removeLast() }
+        mutablePinEntries.value = mutablePinEntries.value.toMutableList().apply { removeLast() }
     }
 
     /** Notifies that the user long-pressed the backspace button. */
     fun onBackspaceButtonLongPressed() {
-        resetPinJob?.cancel()
-        resetPinJob =
-            applicationScope.launch {
-                while (entered.value.isNotEmpty()) {
-                    onBackspaceButtonClicked()
-                    delay(BACKSPACE_LONG_PRESS_DELAY_MS)
-                }
-            }
+        mutablePinEntries.value = emptyList()
     }
 
     /** Notifies that the user clicked the "enter" button. */
     fun onAuthenticateButtonClicked() {
-        if (!interactor.authenticate(entered.value)) {
-            showFailureAnimation()
+        tryAuthenticate(useAutoConfirm = false)
+    }
+
+    private fun tryAuthenticate(useAutoConfirm: Boolean) {
+        val pinCode = mutablePinEntries.value.map { it.input }
+
+        applicationScope.launch {
+            val isSuccess = interactor.authenticate(pinCode, useAutoConfirm) ?: return@launch
+
+            if (!isSuccess) {
+                showFailureAnimation()
+            }
+
+            mutablePinEntries.value = emptyList()
         }
-
-        entered.value = emptyList()
     }
 
-    companion object {
-        @VisibleForTesting const val BACKSPACE_LONG_PRESS_DELAY_MS = 80L
+    private fun computeBackspaceButtonAppearance(
+        enteredPin: List<EnteredKey>,
+        isAutoConfirmEnabled: Boolean,
+    ): ActionButtonAppearance {
+        val isEmpty = enteredPin.isEmpty()
+
+        return when {
+            isAutoConfirmEnabled && isEmpty -> ActionButtonAppearance.Hidden
+            isAutoConfirmEnabled -> ActionButtonAppearance.Subtle
+            else -> ActionButtonAppearance.Shown
+        }
     }
 }
+
+/** Appearance of pin-pad action buttons. */
+enum class ActionButtonAppearance {
+    /** Button must not be shown. */
+    Hidden,
+    /** Button is shown, but with no background to make it less prominent. */
+    Subtle,
+    /** Button is shown. */
+    Shown,
+}
+
+private var nextSequenceNumber = 1
+
+/**
+ * The pin bouncer [input] as digits 0-9, together with a [sequenceNumber] to indicate the ordering.
+ *
+ * Since the model only allows appending/removing [EnteredKey]s from the end, the [sequenceNumber]
+ * is strictly increasing in input order of the pin, but not guaranteed to be monotonic or start at
+ * a specific number.
+ */
+data class EnteredKey
+internal constructor(val input: Int, val sequenceNumber: Int = nextSequenceNumber++) :
+    Comparable<EnteredKey> {
+    override fun compareTo(other: EnteredKey): Int =
+        compareValuesBy(this, other, EnteredKey::sequenceNumber)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
index a0a892d..183a3cc 100644
--- a/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
@@ -87,7 +87,7 @@
     private val receiversByUser = SparseArray<UserBroadcastDispatcher>(20)
 
     fun initialize() {
-        dumpManager.registerDumpable(javaClass.name, this)
+        dumpManager.registerDumpable(this)
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/logging/BroadcastDispatcherLogger.kt b/packages/SystemUI/src/com/android/systemui/broadcast/logging/BroadcastDispatcherLogger.kt
index 5b3a982..068f329 100644
--- a/packages/SystemUI/src/com/android/systemui/broadcast/logging/BroadcastDispatcherLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/broadcast/logging/BroadcastDispatcherLogger.kt
@@ -21,10 +21,10 @@
 import android.content.Intent
 import android.content.IntentFilter
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
-import com.android.systemui.log.LogLevel.DEBUG
-import com.android.systemui.log.LogLevel.INFO
-import com.android.systemui.log.LogMessage
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.core.LogLevel.DEBUG
+import com.android.systemui.log.core.LogLevel.INFO
+import com.android.systemui.log.core.LogMessage
 import com.android.systemui.log.dagger.BroadcastDispatcherLog
 import javax.inject.Inject
 
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
index b2bcb05..25ccc16 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
@@ -396,6 +396,7 @@
                 || mDataProvider.isDocked()
                 || mAccessibilityManager.isTouchExplorationEnabled()
                 || mDataProvider.isA11yAction()
+                || mDataProvider.isFromTrackpad()
                 || (mFeatureFlags.isEnabled(Flags.FALSING_OFF_FOR_UNFOLDED)
                     && mDataProvider.isUnfolded());
     }
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java
index d6c85fb..809d5b2 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java
@@ -261,6 +261,16 @@
         return mLastMotionEvent.getY() < mFirstRecentMotionEvent.getY();
     }
 
+    public boolean isFromTrackpad() {
+        if (mRecentMotionEvents.isEmpty()) {
+            return false;
+        }
+
+        int classification = mRecentMotionEvents.get(0).getClassification();
+        return classification == MotionEvent.CLASSIFICATION_MULTI_FINGER_SWIPE
+                || classification == MotionEvent.CLASSIFICATION_TWO_FINGER_SWIPE;
+    }
+
     private void recalculateData() {
         if (!mDirty) {
             return;
@@ -343,7 +353,9 @@
                     motionEvent.getDeviceId(),
                     motionEvent.getEdgeFlags(),
                     motionEvent.getSource(),
-                    motionEvent.getFlags()
+                    motionEvent.getDisplayId(),
+                    motionEvent.getFlags(),
+                    motionEvent.getClassification()
             ));
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
index 757ebf4..ffd836b 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
@@ -18,6 +18,7 @@
 
 import static android.content.Intent.ACTION_CLOSE_SYSTEM_DIALOGS;
 
+
 import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.CLIPBOARD_OVERLAY_SHOW_ACTIONS;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ACTION_SHOWN;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ACTION_TAPPED;
@@ -33,6 +34,7 @@
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_TAP_OUTSIDE;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_TIMED_OUT;
 import static com.android.systemui.flags.Flags.CLIPBOARD_IMAGE_TIMEOUT;
+import static com.android.systemui.flags.Flags.CLIPBOARD_SHARED_TRANSITIONS;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -47,6 +49,7 @@
 import android.net.Uri;
 import android.os.Looper;
 import android.provider.DeviceConfig;
+import android.util.Log;
 import android.view.InputEvent;
 import android.view.InputEventReceiver;
 import android.view.InputMonitor;
@@ -54,6 +57,7 @@
 import android.view.WindowInsets;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.UiEventLogger;
@@ -73,7 +77,8 @@
 /**
  * Controls state and UI for the overlay that appears when something is added to the clipboard
  */
-public class ClipboardOverlayController implements ClipboardListener.ClipboardOverlay {
+public class ClipboardOverlayController implements ClipboardListener.ClipboardOverlay,
+        ClipboardOverlayView.ClipboardOverlayCallbacks {
     private static final String TAG = "ClipboardOverlayCtrlr";
 
     /** Constants for screenshot/copy deconflicting */
@@ -92,6 +97,7 @@
     private final FeatureFlags mFeatureFlags;
     private final Executor mBgExecutor;
     private final ClipboardImageLoader mClipboardImageLoader;
+    private final ClipboardTransitionExecutor mTransitionExecutor;
 
     private final ClipboardOverlayView mView;
 
@@ -179,10 +185,12 @@
             ClipboardOverlayUtils clipboardUtils,
             @Background Executor bgExecutor,
             ClipboardImageLoader clipboardImageLoader,
+            ClipboardTransitionExecutor transitionExecutor,
             UiEventLogger uiEventLogger) {
         mContext = context;
         mBroadcastDispatcher = broadcastDispatcher;
         mClipboardImageLoader = clipboardImageLoader;
+        mTransitionExecutor = transitionExecutor;
 
         mClipboardLogger = new ClipboardLogger(uiEventLogger);
 
@@ -200,7 +208,11 @@
         mClipboardUtils = clipboardUtils;
         mBgExecutor = bgExecutor;
 
-        mView.setCallbacks(mClipboardCallbacks);
+        if (mFeatureFlags.isEnabled(CLIPBOARD_SHARED_TRANSITIONS)) {
+            mView.setCallbacks(this);
+        } else {
+            mView.setCallbacks(mClipboardCallbacks);
+        }
 
         mWindow.withWindowAttached(() -> {
             mWindow.setContentView(mView);
@@ -209,16 +221,24 @@
         });
 
         mTimeoutHandler.setOnTimeoutRunnable(() -> {
-            mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_TIMED_OUT);
-            animateOut();
+            if (mFeatureFlags.isEnabled(CLIPBOARD_SHARED_TRANSITIONS)) {
+                finish(CLIPBOARD_OVERLAY_TIMED_OUT);
+            } else {
+                mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_TIMED_OUT);
+                animateOut();
+            }
         });
 
         mCloseDialogsReceiver = new BroadcastReceiver() {
             @Override
             public void onReceive(Context context, Intent intent) {
                 if (ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) {
-                    mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_DISMISSED_OTHER);
-                    animateOut();
+                    if (mFeatureFlags.isEnabled(CLIPBOARD_SHARED_TRANSITIONS)) {
+                        finish(CLIPBOARD_OVERLAY_DISMISSED_OTHER);
+                    } else {
+                        mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_DISMISSED_OTHER);
+                        animateOut();
+                    }
                 }
             }
         };
@@ -229,8 +249,12 @@
             @Override
             public void onReceive(Context context, Intent intent) {
                 if (SCREENSHOT_ACTION.equals(intent.getAction())) {
-                    mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_DISMISSED_OTHER);
-                    animateOut();
+                    if (mFeatureFlags.isEnabled(CLIPBOARD_SHARED_TRANSITIONS)) {
+                        finish(CLIPBOARD_OVERLAY_DISMISSED_OTHER);
+                    } else {
+                        mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_DISMISSED_OTHER);
+                        animateOut();
+                    }
                 }
             }
         };
@@ -457,8 +481,12 @@
                 remoteAction.ifPresent(action -> {
                     mClipboardLogger.logUnguarded(CLIPBOARD_OVERLAY_ACTION_SHOWN);
                     mView.post(() -> mView.setActionChip(action, () -> {
-                        mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_ACTION_TAPPED);
-                        animateOut();
+                        if (mFeatureFlags.isEnabled(CLIPBOARD_SHARED_TRANSITIONS)) {
+                            finish(CLIPBOARD_OVERLAY_ACTION_TAPPED);
+                        } else {
+                            mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_ACTION_TAPPED);
+                            animateOut();
+                        }
                     }));
                 });
             }
@@ -500,8 +528,12 @@
                     if (motionEvent.getActionMasked() == MotionEvent.ACTION_DOWN) {
                         if (!mView.isInTouchRegion(
                                 (int) motionEvent.getRawX(), (int) motionEvent.getRawY())) {
-                            mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_TAP_OUTSIDE);
-                            animateOut();
+                            if (mFeatureFlags.isEnabled(CLIPBOARD_SHARED_TRANSITIONS)) {
+                                finish(CLIPBOARD_OVERLAY_TAP_OUTSIDE);
+                            } else {
+                                mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_TAP_OUTSIDE);
+                                animateOut();
+                            }
                         }
                     }
                 }
@@ -551,12 +583,16 @@
         mEnterAnimator.start();
     }
 
+    private void finish(ClipboardOverlayEvent event) {
+        finish(event, null);
+    }
+
     private void animateOut() {
         if (mExitAnimator != null && mExitAnimator.isRunning()) {
             return;
         }
-        Animator anim = mView.getExitAnimation();
-        anim.addListener(new AnimatorListenerAdapter() {
+        mExitAnimator = mView.getExitAnimation();
+        mExitAnimator.addListener(new AnimatorListenerAdapter() {
             private boolean mCancelled;
 
             @Override
@@ -573,8 +609,47 @@
                 }
             }
         });
-        mExitAnimator = anim;
-        anim.start();
+        mExitAnimator.start();
+    }
+
+    private void finish(ClipboardOverlayEvent event, @Nullable Intent intent) {
+        if (mExitAnimator != null && mExitAnimator.isRunning()) {
+            return;
+        }
+        mExitAnimator = mView.getExitAnimation();
+        mExitAnimator.addListener(new AnimatorListenerAdapter() {
+            private boolean mCancelled;
+
+            @Override
+            public void onAnimationCancel(Animator animation) {
+                super.onAnimationCancel(animation);
+                mCancelled = true;
+            }
+
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                super.onAnimationEnd(animation);
+                if (!mCancelled) {
+                    mClipboardLogger.logSessionComplete(event);
+                    if (intent != null) {
+                        mContext.startActivity(intent);
+                    }
+                    hideImmediate();
+                }
+            }
+        });
+        mExitAnimator.start();
+    }
+
+    private void finishWithSharedTransition(ClipboardOverlayEvent event, Intent intent) {
+        if (mExitAnimator != null && mExitAnimator.isRunning()) {
+            return;
+        }
+        mClipboardLogger.logSessionComplete(event);
+        mExitAnimator = mView.getFadeOutAnimation();
+        mExitAnimator.start();
+        mTransitionExecutor.startSharedTransition(
+                mWindow, mView.getPreview(), intent, this::hideImmediate);
     }
 
     void hideImmediate() {
@@ -613,6 +688,76 @@
         mClipboardLogger.reset();
     }
 
+    @Override
+    public void onDismissButtonTapped() {
+        if (mFeatureFlags.isEnabled(CLIPBOARD_SHARED_TRANSITIONS)) {
+            finish(CLIPBOARD_OVERLAY_DISMISS_TAPPED);
+        }
+    }
+
+    @Override
+    public void onRemoteCopyButtonTapped() {
+        if (mFeatureFlags.isEnabled(CLIPBOARD_SHARED_TRANSITIONS)) {
+            finish(CLIPBOARD_OVERLAY_REMOTE_COPY_TAPPED,
+                    IntentCreator.getRemoteCopyIntent(mClipboardModel.getClipData(), mContext));
+        }
+    }
+
+    @Override
+    public void onShareButtonTapped() {
+        if (mFeatureFlags.isEnabled(CLIPBOARD_SHARED_TRANSITIONS)) {
+            if (mClipboardModel.getType() != ClipboardModel.Type.OTHER) {
+                finishWithSharedTransition(CLIPBOARD_OVERLAY_SHARE_TAPPED,
+                        IntentCreator.getShareIntent(mClipboardModel.getClipData(), mContext));
+            }
+        }
+    }
+
+    @Override
+    public void onPreviewTapped() {
+        if (mFeatureFlags.isEnabled(CLIPBOARD_SHARED_TRANSITIONS)) {
+            switch (mClipboardModel.getType()) {
+                case TEXT:
+                    finish(CLIPBOARD_OVERLAY_EDIT_TAPPED,
+                            IntentCreator.getTextEditorIntent(mContext));
+                    break;
+                case IMAGE:
+                    finishWithSharedTransition(CLIPBOARD_OVERLAY_EDIT_TAPPED,
+                            IntentCreator.getImageEditIntent(mClipboardModel.getUri(), mContext));
+                    break;
+                default:
+                    Log.w(TAG, "Got preview tapped callback for non-editable type "
+                            + mClipboardModel.getType());
+            }
+        }
+    }
+
+    @Override
+    public void onMinimizedViewTapped() {
+        animateFromMinimized();
+    }
+
+    @Override
+    public void onInteraction() {
+        if (!mClipboardModel.isRemote()) {
+            mTimeoutHandler.resetTimeout();
+        }
+    }
+
+    @Override
+    public void onSwipeDismissInitiated(Animator animator) {
+        if (mExitAnimator != null && mExitAnimator.isRunning()) {
+            mExitAnimator.cancel();
+        }
+        mExitAnimator = animator;
+        mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_SWIPE_DISMISSED);
+    }
+
+    @Override
+    public void onDismissComplete() {
+        hideImmediate();
+    }
+
     static class ClipboardLogger {
         private final UiEventLogger mUiEventLogger;
         private String mClipSource;
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java
index 28c57d3..a76d2ea 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java
@@ -254,6 +254,10 @@
                 });
     }
 
+    View getPreview() {
+        return mClipboardPreview;
+    }
+
     void showImagePreview(@Nullable Bitmap thumbnail) {
         if (thumbnail == null) {
             mHiddenPreview.setText(mContext.getString(R.string.clipboard_text_hidden));
@@ -368,6 +372,19 @@
         return enterAnim;
     }
 
+    Animator getFadeOutAnimation() {
+        ValueAnimator alphaAnim = ValueAnimator.ofFloat(1, 0);
+        alphaAnim.addUpdateListener(animation -> {
+            float alpha = (float) animation.getAnimatedValue();
+            mActionContainer.setAlpha(alpha);
+            mActionContainerBackground.setAlpha(alpha);
+            mPreviewBorder.setAlpha(alpha);
+            mDismissButton.setAlpha(alpha);
+        });
+        alphaAnim.setDuration(300);
+        return alphaAnim;
+    }
+
     Animator getExitAnimation() {
         TimeInterpolator linearInterpolator = new LinearInterpolator();
         TimeInterpolator scaleInterpolator = new PathInterpolator(.3f, 0, 1f, 1f);
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardTransitionExecutor.kt b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardTransitionExecutor.kt
new file mode 100644
index 0000000..0b8e83e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardTransitionExecutor.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.clipboardoverlay
+
+import android.app.ActivityOptions
+import android.app.ExitTransitionCoordinator
+import android.content.Context
+import android.content.Intent
+import android.os.RemoteException
+import android.util.Log
+import android.util.Pair
+import android.view.IRemoteAnimationFinishedCallback
+import android.view.IRemoteAnimationRunner
+import android.view.RemoteAnimationAdapter
+import android.view.RemoteAnimationTarget
+import android.view.View
+import android.view.Window
+import android.view.WindowManagerGlobal
+import com.android.internal.app.ChooserActivity
+import com.android.systemui.settings.DisplayTracker
+import javax.inject.Inject
+
+class ClipboardTransitionExecutor
+@Inject
+constructor(val context: Context, val displayTracker: DisplayTracker) {
+    fun startSharedTransition(window: Window, view: View, intent: Intent, onReady: Runnable) {
+        val transition: Pair<ActivityOptions, ExitTransitionCoordinator> =
+            ActivityOptions.startSharedElementAnimation(
+                window,
+                object : ExitTransitionCoordinator.ExitTransitionCallbacks {
+                    override fun isReturnTransitionAllowed(): Boolean {
+                        return false
+                    }
+
+                    override fun hideSharedElements() {
+                        onReady.run()
+                    }
+
+                    override fun onFinish() {}
+                },
+                null,
+                Pair.create(view, ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME)
+            )
+        transition.second.startExit()
+        context.startActivity(intent, transition.first.toBundle())
+        val runner = RemoteAnimationAdapter(NULL_ACTIVITY_TRANSITION, 0, 0)
+        try {
+            WindowManagerGlobal.getWindowManagerService()
+                .overridePendingAppTransitionRemote(runner, displayTracker.defaultDisplayId)
+        } catch (e: Exception) {
+            Log.e(TAG, "Error overriding clipboard app transition", e)
+        }
+    }
+
+    private val TAG: String = "ClipboardTransitionExec"
+
+    /**
+     * This is effectively a no-op, but we need something non-null to pass in, in order to
+     * successfully override the pending activity entrance animation.
+     */
+    private val NULL_ACTIVITY_TRANSITION: IRemoteAnimationRunner.Stub =
+        object : IRemoteAnimationRunner.Stub() {
+            override fun onAnimationStart(
+                transit: Int,
+                apps: Array<RemoteAnimationTarget>,
+                wallpapers: Array<RemoteAnimationTarget>,
+                nonApps: Array<RemoteAnimationTarget>,
+                finishedCallback: IRemoteAnimationFinishedCallback
+            ) {
+                try {
+                    finishedCallback.onAnimationFinished()
+                } catch (e: RemoteException) {
+                    Log.e(TAG, "Error finishing screenshot remote animation", e)
+                }
+            }
+
+            override fun onAnimationCancelled() {}
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsView.java b/packages/SystemUI/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsView.java
index de3a990..f362831 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsView.java
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsView.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.common.ui.view;
 
+import android.annotation.IntDef;
 import android.annotation.Nullable;
 import android.content.Context;
 import android.content.res.TypedArray;
@@ -27,8 +28,12 @@
 import android.widget.LinearLayout;
 import android.widget.SeekBar;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.R;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * The layout contains a seekbar whose progress could be modified
  * through the icons on two ends of the seekbar.
@@ -37,12 +42,16 @@
 
     private static final int DEFAULT_SEEKBAR_MAX = 6;
     private static final int DEFAULT_SEEKBAR_PROGRESS = 0;
+    private static final int DEFAULT_SEEKBAR_TICK_MARK = 0;
 
     private ViewGroup mIconStartFrame;
     private ViewGroup mIconEndFrame;
     private ImageView mIconStart;
     private ImageView mIconEnd;
     private SeekBar mSeekbar;
+    private int mSeekBarChangeMagnitude = 1;
+
+    private boolean mSetProgressFromButtonFlag = false;
 
     private SeekBarChangeListener mSeekBarListener = new SeekBarChangeListener();
     private String[] mStateLabels = null;
@@ -102,6 +111,15 @@
                         context.getString(iconEndFrameContentDescriptionId);
                 mIconEndFrame.setContentDescription(contentDescription);
             }
+            int tickMarkId = typedArray.getResourceId(
+                    R.styleable.SeekBarWithIconButtonsView_Layout_tickMark,
+                    DEFAULT_SEEKBAR_TICK_MARK);
+            if (tickMarkId != DEFAULT_SEEKBAR_TICK_MARK) {
+                mSeekbar.setTickMark(getResources().getDrawable(tickMarkId));
+            }
+            mSeekBarChangeMagnitude = typedArray.getInt(
+                    R.styleable.SeekBarWithIconButtonsView_Layout_seekBarChangeMagnitude,
+                    /* defValue= */ 1);
         } else {
             mSeekbar.setMax(DEFAULT_SEEKBAR_MAX);
             setProgress(DEFAULT_SEEKBAR_PROGRESS);
@@ -109,21 +127,8 @@
 
         mSeekbar.setOnSeekBarChangeListener(mSeekBarListener);
 
-        mIconStartFrame.setOnClickListener((view) -> {
-            final int progress = mSeekbar.getProgress();
-            if (progress > 0) {
-                mSeekbar.setProgress(progress - 1);
-                setIconViewAndFrameEnabled(mIconStart, mSeekbar.getProgress() > 0);
-            }
-        });
-
-        mIconEndFrame.setOnClickListener((view) -> {
-            final int progress = mSeekbar.getProgress();
-            if (progress < mSeekbar.getMax()) {
-                mSeekbar.setProgress(progress + 1);
-                setIconViewAndFrameEnabled(mIconEnd, mSeekbar.getProgress() < mSeekbar.getMax());
-            }
-        });
+        mIconStartFrame.setOnClickListener((view) -> onIconStartClicked());
+        mIconEndFrame.setOnClickListener((view) -> onIconEndClicked());
     }
 
     private static void setIconViewAndFrameEnabled(View iconView, boolean enabled) {
@@ -160,9 +165,25 @@
      * Sets a onSeekbarChangeListener to the seekbar in the layout.
      * We update the Start Icon and End Icon if needed when the seekbar progress is changed.
      */
-    public void setOnSeekBarChangeListener(
-            @Nullable SeekBar.OnSeekBarChangeListener onSeekBarChangeListener) {
-        mSeekBarListener.setOnSeekBarChangeListener(onSeekBarChangeListener);
+    public void setOnSeekBarWithIconButtonsChangeListener(
+            @Nullable OnSeekBarWithIconButtonsChangeListener onSeekBarChangeListener) {
+        mSeekBarListener.setOnSeekBarWithIconButtonsChangeListener(onSeekBarChangeListener);
+    }
+
+    /**
+     * Only for testing. Get previous set mOnSeekBarChangeListener to the seekbar.
+     */
+    @VisibleForTesting
+    public OnSeekBarWithIconButtonsChangeListener getOnSeekBarWithIconButtonsChangeListener() {
+        return mSeekBarListener.mOnSeekBarChangeListener;
+    }
+
+    /**
+     * Only for testing. Get {@link #mSeekbar} in the layout.
+     */
+    @VisibleForTesting
+    public SeekBar getSeekbar() {
+        return mSeekbar;
     }
 
     /**
@@ -183,6 +204,20 @@
     }
 
     /**
+     * Gets max to the seekbar in the layout.
+     */
+    public int getMax() {
+        return mSeekbar.getMax();
+    }
+
+    /**
+     * @return the magnitude by which seekbar progress changes when start and end icons are clicked.
+     */
+    public int getChangeMagnitude() {
+        return mSeekBarChangeMagnitude;
+    }
+
+    /**
      * Sets progress to the seekbar in the layout.
      * If the progress is smaller than or equals to 0, the IconStart will be disabled. If the
      * progress is larger than or equals to Max, the IconEnd will be disabled. The seekbar progress
@@ -190,11 +225,72 @@
      */
     public void setProgress(int progress) {
         mSeekbar.setProgress(progress);
-        updateIconViewIfNeeded(progress);
+        updateIconViewIfNeeded(mSeekbar.getProgress());
+    }
+
+    private void setProgressFromButton(int progress) {
+        mSetProgressFromButtonFlag = true;
+        mSeekbar.setProgress(progress);
+        updateIconViewIfNeeded(mSeekbar.getProgress());
+    }
+
+    private void onIconStartClicked() {
+        final int progress = mSeekbar.getProgress();
+        if (progress > 0) {
+            setProgressFromButton(progress - mSeekBarChangeMagnitude);
+        }
+    }
+
+    private void onIconEndClicked() {
+        final int progress = mSeekbar.getProgress();
+        if (progress < mSeekbar.getMax()) {
+            setProgressFromButton(progress + mSeekBarChangeMagnitude);
+        }
+    }
+
+    /**
+     * Get current seekbar progress
+     *
+     * @return
+     */
+    @VisibleForTesting
+    public int getProgress() {
+        return mSeekbar.getProgress();
+    }
+
+    /**
+     * Extended from {@link SeekBar.OnSeekBarChangeListener} to add callback to notify the listeners
+     * the user interaction with the SeekBarWithIconButtonsView is finalized.
+     */
+    public interface OnSeekBarWithIconButtonsChangeListener
+            extends SeekBar.OnSeekBarChangeListener {
+
+        @Retention(RetentionPolicy.SOURCE)
+        @IntDef({
+                ControlUnitType.SLIDER,
+                ControlUnitType.BUTTON
+        })
+        /** Denotes the Last user interacted control unit type. */
+        @interface ControlUnitType {
+            int SLIDER = 0;
+            int BUTTON = 1;
+        }
+
+        /**
+         * Notification that the user interaction with SeekBarWithIconButtonsView is finalized. This
+         * would be triggered after user ends dragging on the slider or clicks icon buttons.
+         *
+         * @param seekBar The SeekBar in which the user ends interaction with
+         * @param control The last user interacted control unit. It would be
+         *                {@link ControlUnitType#SLIDER} if the user was changing the seekbar
+         *                progress through dragging the slider, or {@link ControlUnitType#BUTTON}
+         *                is the user was clicking button to change the progress.
+         */
+        void onUserInteractionFinalized(SeekBar seekBar, @ControlUnitType int control);
     }
 
     private class SeekBarChangeListener implements SeekBar.OnSeekBarChangeListener {
-        private SeekBar.OnSeekBarChangeListener mOnSeekBarChangeListener = null;
+        private OnSeekBarWithIconButtonsChangeListener mOnSeekBarChangeListener = null;
 
         @Override
         public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
@@ -202,7 +298,17 @@
                 setSeekbarStateDescription();
             }
             if (mOnSeekBarChangeListener != null) {
-                mOnSeekBarChangeListener.onProgressChanged(seekBar, progress, fromUser);
+                if (mSetProgressFromButtonFlag) {
+                    mSetProgressFromButtonFlag = false;
+                    mOnSeekBarChangeListener.onProgressChanged(
+                            seekBar, progress, /* fromUser= */ true);
+                    // Directly trigger onUserInteractionFinalized since the interaction
+                    // (click button) is ended.
+                    mOnSeekBarChangeListener.onUserInteractionFinalized(
+                            seekBar, OnSeekBarWithIconButtonsChangeListener.ControlUnitType.BUTTON);
+                } else {
+                    mOnSeekBarChangeListener.onProgressChanged(seekBar, progress, fromUser);
+                }
             }
             updateIconViewIfNeeded(progress);
         }
@@ -218,10 +324,13 @@
         public void onStopTrackingTouch(SeekBar seekBar) {
             if (mOnSeekBarChangeListener != null) {
                 mOnSeekBarChangeListener.onStopTrackingTouch(seekBar);
+                mOnSeekBarChangeListener.onUserInteractionFinalized(
+                        seekBar, OnSeekBarWithIconButtonsChangeListener.ControlUnitType.SLIDER);
             }
         }
 
-        void setOnSeekBarChangeListener(SeekBar.OnSeekBarChangeListener listener) {
+        void setOnSeekBarWithIconButtonsChangeListener(
+                OnSeekBarWithIconButtonsChangeListener listener) {
             mOnSeekBarChangeListener = listener;
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt b/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt
index 4173bdc..b15c60e 100644
--- a/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt
+++ b/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt
@@ -24,6 +24,9 @@
 import com.android.systemui.multishade.ui.viewmodel.MultiShadeViewModel
 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
+import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
 import com.android.systemui.util.time.SystemClock
 
 /**
@@ -66,4 +69,11 @@
         viewModel: MultiShadeViewModel,
         clock: SystemClock,
     ): View
+
+    /** Create a [View] to represent [viewModel] on screen. */
+    fun createSceneContainerView(
+        context: Context,
+        viewModel: SceneContainerViewModel,
+        sceneByKey: Map<SceneKey, Scene>,
+    ): View
 }
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt
index 18bd467..ccbde01 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt
@@ -312,6 +312,7 @@
                 Log.d(TAG, "Canceling loadSubscribtion")
                 it.invoke()
             }
+            callback.error("Load cancelled")
         }
 
         override fun onSubscribe(token: IBinder, subs: IControlsSubscription) {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
index e8c97bf..7db5968 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
@@ -227,7 +227,7 @@
     }
 
     init {
-        dumpManager.registerDumpable(javaClass.name, this)
+        dumpManager.registerDumpable(this)
         resetFavorites()
         userChanging = false
         context.registerReceiver(
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/PackageUpdateMonitor.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/PackageUpdateMonitor.kt
index 1973b62..840225e 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/PackageUpdateMonitor.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/PackageUpdateMonitor.kt
@@ -52,7 +52,7 @@
     /** Start monitoring for package updates. No-op if already monitoring. */
     fun startMonitoring() {
         if (monitoring.compareAndSet(/* expected */ false, /* new */ true)) {
-            register(context, user, false, bgHandler)
+            register(context, user, bgHandler)
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt
index d629e3e..8e41974 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt
@@ -154,7 +154,7 @@
     private fun bindViews() {
         setContentView(R.layout.controls_management)
 
-        getLifecycle().addObserver(
+        lifecycle.addObserver(
             ControlsAnimations.observerForAnimations(
                 requireViewById<ViewGroup>(R.id.controls_management_root),
                 window,
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
index d3ffc95..d3aa449 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
@@ -268,7 +268,7 @@
     private fun bindViews() {
         setContentView(R.layout.controls_management)
 
-        getLifecycle().addObserver(
+        lifecycle.addObserver(
             ControlsAnimations.observerForAnimations(
                 requireViewById<ViewGroup>(R.id.controls_management_root),
                 window,
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt
index fb19ac9..d5b8693 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt
@@ -91,7 +91,7 @@
 
         setContentView(R.layout.controls_management)
 
-        getLifecycle().addObserver(
+        lifecycle.addObserver(
             ControlsAnimations.observerForAnimations(
                 requireViewById<ViewGroup>(R.id.controls_management_root),
                 window,
diff --git a/packages/SystemUI/src/com/android/systemui/controls/settings/ControlsSettingsDialogManager.kt b/packages/SystemUI/src/com/android/systemui/controls/settings/ControlsSettingsDialogManager.kt
index ce0f2e9..501bcf0 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/settings/ControlsSettingsDialogManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/settings/ControlsSettingsDialogManager.kt
@@ -135,7 +135,7 @@
         val listener = DialogListener(prefs, attempts, onAttemptCompleted)
         val d =
             dialogProvider(activityContext, R.style.Theme_SystemUI_Dialog).apply {
-                setIcon(R.drawable.ic_warning)
+                setIcon(R.drawable.ic_lock_locked)
                 setOnCancelListener(listener)
                 setNeutralButton(R.string.controls_settings_dialog_neutral_button, listener)
                 setPositiveButton(R.string.controls_settings_dialog_positive_button, listener)
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt
index 4a22e4e..557dcf4 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt
@@ -72,7 +72,7 @@
 
         setContentView(R.layout.controls_fullscreen)
 
-        getLifecycle().addObserver(
+        lifecycle.addObserver(
             ControlsAnimations.observerForAnimations(
                 requireViewById(R.id.control_detail_root),
                 window,
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
index 776b336e..64d4583 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
@@ -167,7 +167,7 @@
         get() = !hidden
 
     init {
-        dumpManager.registerDumpable(javaClass.name, this)
+        dumpManager.registerDumpable(this)
     }
 
     private fun createCallback(
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
index 0dcba50..c9517c2 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
@@ -646,6 +646,7 @@
 
     @Provides
     @Singleton
+    @Nullable
     static SmartspaceManager provideSmartspaceManager(Context context) {
         return context.getSystemService(SmartspaceManager.class);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSysUIComponent.java
index a431a59..94b2ab1 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSysUIComponent.java
@@ -16,7 +16,10 @@
 
 package com.android.systemui.dagger;
 
+import com.android.systemui.globalactions.ShutdownUiModule;
 import com.android.systemui.keyguard.CustomizationProvider;
+import com.android.systemui.scene.startable.SceneContainerStartableModule;
+import com.android.systemui.shade.ShadeModule;
 import com.android.systemui.statusbar.NotificationInsetsModule;
 import com.android.systemui.statusbar.QsFrameTranslateModule;
 
@@ -31,6 +34,9 @@
         DependencyProvider.class,
         NotificationInsetsModule.class,
         QsFrameTranslateModule.class,
+        ShadeModule.class,
+        ShutdownUiModule.class,
+        SceneContainerStartableModule.class,
         SystemUIBinder.class,
         SystemUIModule.class,
         SystemUICoreStartableModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
index 2262d8a..35cf4a1 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
@@ -40,7 +40,6 @@
 import com.android.systemui.recents.Recents;
 import com.android.systemui.recents.RecentsImplementation;
 import com.android.systemui.rotationlock.RotationLockModule;
-import com.android.systemui.scene.SceneContainerFrameworkModule;
 import com.android.systemui.screenshot.ReferenceScreenshotModule;
 import com.android.systemui.settings.dagger.MultiUserUtilsModule;
 import com.android.systemui.shade.NotificationShadeWindowControllerImpl;
@@ -72,6 +71,7 @@
 import com.android.systemui.statusbar.policy.SensorPrivacyController;
 import com.android.systemui.statusbar.policy.SensorPrivacyControllerImpl;
 import com.android.systemui.volume.dagger.VolumeModule;
+import com.android.systemui.wallpapers.dagger.WallpaperModule;
 
 import dagger.Binds;
 import dagger.Module;
@@ -104,10 +104,10 @@
         QSModule.class,
         ReferenceScreenshotModule.class,
         RotationLockModule.class,
-        SceneContainerFrameworkModule.class,
         StatusBarEventsModule.class,
         StartCentralSurfacesModule.class,
         VolumeModule.class,
+        WallpaperModule.class,
         KeyboardShortcutsModule.class
 })
 public abstract class ReferenceSystemUIModule {
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
index 52355f3..d82bf58 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
@@ -16,7 +16,6 @@
 
 package com.android.systemui.dagger;
 
-import com.android.keyguard.clock.ClockOptionsProvider;
 import com.android.systemui.BootCompleteCacheImpl;
 import com.android.systemui.CoreStartable;
 import com.android.systemui.Dependency;
@@ -252,10 +251,5 @@
     /**
      * Member injection into the supplied argument.
      */
-    void inject(ClockOptionsProvider clockOptionsProvider);
-
-    /**
-     * Member injection into the supplied argument.
-     */
     void inject(PeopleProvider peopleProvider);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index 25634f0..a560acc 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -34,6 +34,7 @@
 import com.android.systemui.keyboard.KeyboardUI
 import com.android.systemui.keyboard.PhysicalKeyboardCoreStartable
 import com.android.systemui.keyguard.KeyguardViewMediator
+import com.android.systemui.keyguard.KeyguardViewConfigurator
 import com.android.systemui.keyguard.data.quickaffordance.MuteQuickAffordanceCoreStartable
 import com.android.systemui.log.SessionTracker
 import com.android.systemui.media.RingtonePlayer
@@ -41,6 +42,7 @@
 import com.android.systemui.media.taptotransfer.MediaTttCommandLineHelper
 import com.android.systemui.media.taptotransfer.receiver.MediaTttChipControllerReceiver
 import com.android.systemui.media.taptotransfer.sender.MediaTttSenderCoordinator
+import com.android.systemui.mediaprojection.taskswitcher.MediaProjectionTaskSwitcherCoreStartable
 import com.android.systemui.power.PowerUI
 import com.android.systemui.reardisplay.RearDisplayDialogController
 import com.android.systemui.recents.Recents
@@ -48,6 +50,8 @@
 import com.android.systemui.shortcut.ShortcutKeyDispatcher
 import com.android.systemui.statusbar.notification.InstantAppNotifier
 import com.android.systemui.statusbar.phone.KeyguardLiftController
+import com.android.systemui.statusbar.phone.LockscreenWallpaper
+import com.android.systemui.statusbar.phone.ScrimController
 import com.android.systemui.stylus.StylusUsiPowerStartable
 import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
 import com.android.systemui.theme.ThemeOverlayController
@@ -56,6 +60,7 @@
 import com.android.systemui.util.NotificationChannels
 import com.android.systemui.util.StartBinderLoggerModule
 import com.android.systemui.volume.VolumeUI
+import com.android.systemui.wallpapers.dagger.WallpaperModule
 import com.android.systemui.wmshell.WMShell
 import dagger.Binds
 import dagger.Module
@@ -69,6 +74,7 @@
     MultiUserUtilsModule::class,
     StartControlsStartableModule::class,
     StartBinderLoggerModule::class,
+    WallpaperModule::class,
 ])
 abstract class SystemUICoreStartableModule {
     /** Inject into AuthController.  */
@@ -109,6 +115,14 @@
     @ClassKey(KeyboardUI::class)
     abstract fun bindKeyboardUI(sysui: KeyboardUI): CoreStartable
 
+    /** Inject into MediaProjectionTaskSwitcherCoreStartable. */
+    @Binds
+    @IntoMap
+    @ClassKey(MediaProjectionTaskSwitcherCoreStartable::class)
+    abstract fun bindProjectedTaskListener(
+            sysui: MediaProjectionTaskSwitcherCoreStartable
+    ): CoreStartable
+
     /** Inject into KeyguardBiometricLockoutLogger */
     @Binds
     @IntoMap
@@ -295,4 +309,19 @@
     @IntoMap
     @ClassKey(AssistantAttentionMonitor::class)
     abstract fun bindAssistantAttentionMonitor(sysui: AssistantAttentionMonitor): CoreStartable
+
+    @Binds
+    @IntoMap
+    @ClassKey(KeyguardViewConfigurator::class)
+    abstract fun bindKeyguardViewConfigurator(impl: KeyguardViewConfigurator): CoreStartable
+
+    @Binds
+    @IntoMap
+    @ClassKey(LockscreenWallpaper::class)
+    abstract fun bindLockscreenWallpaper(impl: LockscreenWallpaper): CoreStartable
+
+    @Binds
+    @IntoMap
+    @ClassKey(ScrimController::class)
+    abstract fun bindScrimController(impl: ScrimController): CoreStartable
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 5bcf32a..18b5612 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -23,7 +23,6 @@
 import androidx.annotation.Nullable;
 
 import com.android.internal.statusbar.IStatusBarService;
-import com.android.keyguard.clock.ClockInfoModule;
 import com.android.keyguard.dagger.ClockRegistryModule;
 import com.android.keyguard.dagger.KeyguardBouncerComponent;
 import com.android.systemui.BootCompleteCache;
@@ -35,10 +34,10 @@
 import com.android.systemui.authentication.AuthenticationModule;
 import com.android.systemui.biometrics.AlternateUdfpsTouchProvider;
 import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider;
-import com.android.systemui.biometrics.FingerprintReEnrollNotification;
 import com.android.systemui.biometrics.UdfpsDisplayModeProvider;
 import com.android.systemui.biometrics.dagger.BiometricsModule;
 import com.android.systemui.biometrics.dagger.UdfpsModule;
+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.ui.data.repository.CommonRepositoryModule;
@@ -47,17 +46,19 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dagger.qualifiers.SystemUser;
 import com.android.systemui.demomode.dagger.DemoModeModule;
+import com.android.systemui.display.DisplayModule;
 import com.android.systemui.doze.dagger.DozeComponent;
 import com.android.systemui.dreams.dagger.DreamModule;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.FlagsModule;
 import com.android.systemui.keyboard.KeyboardModule;
-import com.android.systemui.keyguard.data.BouncerViewModule;
+import com.android.systemui.keyguard.ui.view.layout.LockscreenLayoutModule;
 import com.android.systemui.log.dagger.LogModule;
 import com.android.systemui.log.dagger.MonitorLog;
 import com.android.systemui.log.table.TableLogBuffer;
 import com.android.systemui.mediaprojection.appselector.MediaProjectionModule;
+import com.android.systemui.mediaprojection.taskswitcher.MediaProjectionTaskSwitcherModule;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.motiontool.MotionToolModule;
 import com.android.systemui.navigationbar.NavigationBarComponent;
@@ -74,12 +75,12 @@
 import com.android.systemui.qs.footer.dagger.FooterActionsModule;
 import com.android.systemui.recents.Recents;
 import com.android.systemui.retail.dagger.RetailModeModule;
+import com.android.systemui.scene.SceneContainerFrameworkModule;
 import com.android.systemui.screenrecord.ScreenRecordModule;
 import com.android.systemui.screenshot.dagger.ScreenshotModule;
 import com.android.systemui.security.data.repository.SecurityRepositoryModule;
 import com.android.systemui.settings.DisplayTracker;
 import com.android.systemui.shade.ShadeController;
-import com.android.systemui.shade.ShadeModule;
 import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
 import com.android.systemui.shade.transition.LargeScreenShadeInterpolatorImpl;
 import com.android.systemui.shared.condition.Monitor;
@@ -88,6 +89,7 @@
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.connectivity.ConnectivityModule;
+import com.android.systemui.statusbar.disableflags.dagger.DisableFlagsModule;
 import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
 import com.android.systemui.statusbar.notification.NotifPipelineFlags;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
@@ -160,14 +162,15 @@
             BiometricsModule.class,
             BouncerViewModule.class,
             ClipboardOverlayModule.class,
-            ClockInfoModule.class,
             ClockRegistryModule.class,
             CommonRepositoryModule.class,
+            DisplayModule.class,
             ConnectivityModule.class,
             CoroutinesModule.class,
             DreamModule.class,
             ControlsModule.class,
             DemoModeModule.class,
+            DisableFlagsModule.class,
             FalsingModule.class,
             FlagsModule.class,
             SystemPropertiesFlagsModule.class,
@@ -175,8 +178,10 @@
             GarbageMonitorModule.class,
             KeyboardModule.class,
             LetterboxModule.class,
+            LockscreenLayoutModule.class,
             LogModule.class,
             MediaProjectionModule.class,
+            MediaProjectionTaskSwitcherModule.class,
             MotionToolModule.class,
             PeopleHubModule.class,
             PeopleModule.class,
@@ -186,12 +191,12 @@
             QRCodeScannerModule.class,
             QSFragmentStartableModule.class,
             RetailModeModule.class,
+            SceneContainerFrameworkModule.class,
             ScreenshotModule.class,
             SensorModule.class,
             SecurityRepositoryModule.class,
             ScreenRecordModule.class,
             SettingsUtilModule.class,
-            ShadeModule.class,
             SmartRepliesInflationModule.class,
             SmartspaceModule.class,
             StatusBarPipelineModule.class,
@@ -294,9 +299,6 @@
     @BindsOptionalOf
     abstract SystemStatusAnimationScheduler optionalSystemStatusAnimationScheduler();
 
-    @BindsOptionalOf
-    abstract FingerprintReEnrollNotification optionalFingerprintReEnrollNotification();
-
     @SysUISingleton
     @Binds
     abstract SystemClock bindSystemClock(SystemClockImpl systemClock);
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
index b71871e..d70c57f 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
@@ -16,12 +16,11 @@
 
 package com.android.systemui.dagger;
 
-import android.content.Context;
 import android.os.HandlerThread;
 
 import androidx.annotation.Nullable;
 
-import com.android.systemui.SystemUIInitializerFactory;
+import com.android.systemui.SystemUIInitializer;
 import com.android.systemui.tv.TvWMComponent;
 import com.android.wm.shell.back.BackAnimation;
 import com.android.wm.shell.bubbles.Bubbles;
@@ -49,7 +48,7 @@
 /**
  * Dagger Subcomponent for WindowManager.  This class explicitly describes the interfaces exported
  * from the WM component into the SysUI component (in
- * {@link SystemUIInitializerFactory#init(Context, boolean)}), and references the specific dependencies
+ * {@link SystemUIInitializer#init(boolean)}), and references the specific dependencies
  * provided by its particular device/form-factor SystemUI implementation.
  *
  * ie. {@link WMComponent} includes {@link WMShellModule}
diff --git a/packages/SystemUI/src/com/android/systemui/decor/DebugRoundedCornerDelegate.kt b/packages/SystemUI/src/com/android/systemui/decor/DebugRoundedCornerDelegate.kt
new file mode 100644
index 0000000..5571687
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/decor/DebugRoundedCornerDelegate.kt
@@ -0,0 +1,212 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.decor
+
+import android.graphics.Canvas
+import android.graphics.Color
+import android.graphics.ColorFilter
+import android.graphics.Paint
+import android.graphics.Path
+import android.graphics.PixelFormat
+import android.graphics.drawable.Drawable
+import android.util.Size
+import java.io.PrintWriter
+
+/**
+ * Rounded corner delegate that handles incoming debug commands and can convert them to path
+ * drawables to be shown instead of the system-defined rounded corners.
+ *
+ * These debug corners are expected to supersede the system-defined corners
+ */
+class DebugRoundedCornerDelegate : RoundedCornerResDelegate {
+    override var hasTop: Boolean = false
+        private set
+    override var topRoundedDrawable: Drawable? = null
+        private set
+    override var topRoundedSize: Size = Size(0, 0)
+        private set
+
+    override var hasBottom: Boolean = false
+        private set
+    override var bottomRoundedDrawable: Drawable? = null
+        private set
+    override var bottomRoundedSize: Size = Size(0, 0)
+        private set
+
+    override var physicalPixelDisplaySizeRatio: Float = 1f
+        set(value) {
+            if (field == value) {
+                return
+            }
+            field = value
+            reloadMeasures()
+        }
+
+    var color: Int = Color.RED
+        set(value) {
+            if (field == value) {
+                return
+            }
+
+            field = value
+            paint.color = field
+        }
+
+    var paint =
+        Paint().apply {
+            color = Color.RED
+            style = Paint.Style.FILL
+        }
+
+    override fun updateDisplayUniqueId(newDisplayUniqueId: String?, newReloadToken: Int?) {
+        // nop -- debug corners draw the same on every display
+    }
+
+    fun applyNewDebugCorners(
+        topCorner: DebugRoundedCornerModel?,
+        bottomCorner: DebugRoundedCornerModel?,
+    ) {
+        topCorner?.let {
+            hasTop = true
+            topRoundedDrawable = it.toPathDrawable(paint)
+            topRoundedSize = it.size()
+        }
+            ?: {
+                hasTop = false
+                topRoundedDrawable = null
+                topRoundedSize = Size(0, 0)
+            }
+
+        bottomCorner?.let {
+            hasBottom = true
+            bottomRoundedDrawable = it.toPathDrawable(paint)
+            bottomRoundedSize = it.size()
+        }
+            ?: {
+                hasBottom = false
+                bottomRoundedDrawable = null
+                bottomRoundedSize = Size(0, 0)
+            }
+    }
+
+    /**
+     * Remove accumulated debug state by clearing out the drawables and setting [hasTop] and
+     * [hasBottom] to false.
+     */
+    fun removeDebugState() {
+        hasTop = false
+        topRoundedDrawable = null
+        topRoundedSize = Size(0, 0)
+
+        hasBottom = false
+        bottomRoundedDrawable = null
+        bottomRoundedSize = Size(0, 0)
+    }
+
+    /**
+     * Scaling here happens when the display resolution is changed. This logic is exactly the same
+     * as in [RoundedCornerResDelegateImpl]
+     */
+    private fun reloadMeasures() {
+        topRoundedDrawable?.let { topRoundedSize = Size(it.intrinsicWidth, it.intrinsicHeight) }
+        bottomRoundedDrawable?.let {
+            bottomRoundedSize = Size(it.intrinsicWidth, it.intrinsicHeight)
+        }
+
+        if (physicalPixelDisplaySizeRatio != 1f) {
+            if (topRoundedSize.width != 0) {
+                topRoundedSize =
+                    Size(
+                        (physicalPixelDisplaySizeRatio * topRoundedSize.width + 0.5f).toInt(),
+                        (physicalPixelDisplaySizeRatio * topRoundedSize.height + 0.5f).toInt()
+                    )
+            }
+            if (bottomRoundedSize.width != 0) {
+                bottomRoundedSize =
+                    Size(
+                        (physicalPixelDisplaySizeRatio * bottomRoundedSize.width + 0.5f).toInt(),
+                        (physicalPixelDisplaySizeRatio * bottomRoundedSize.height + 0.5f).toInt()
+                    )
+            }
+        }
+    }
+
+    fun dump(pw: PrintWriter) {
+        pw.println("DebugRoundedCornerDelegate state:")
+        pw.println("  hasTop=$hasTop")
+        pw.println("  hasBottom=$hasBottom")
+        pw.println("  topRoundedSize(w,h)=(${topRoundedSize.width},${topRoundedSize.height})")
+        pw.println(
+            "  bottomRoundedSize(w,h)=(${bottomRoundedSize.width},${bottomRoundedSize.height})"
+        )
+        pw.println("  physicalPixelDisplaySizeRatio=$physicalPixelDisplaySizeRatio")
+    }
+}
+
+/** Encapsulates the data coming in from the command line args and turns into a [PathDrawable] */
+data class DebugRoundedCornerModel(
+    val path: Path,
+    val width: Int,
+    val height: Int,
+    val scaleX: Float,
+    val scaleY: Float,
+) {
+    fun size() = Size(width, height)
+
+    fun toPathDrawable(paint: Paint) =
+        PathDrawable(
+            path,
+            width,
+            height,
+            scaleX,
+            scaleY,
+            paint,
+        )
+}
+
+/**
+ * PathDrawable accepts paths from the command line via [DebugRoundedCornerModel], and renders them
+ * in the canvas provided by the screen decor rounded corner provider
+ */
+class PathDrawable(
+    val path: Path,
+    val width: Int,
+    val height: Int,
+    val scaleX: Float = 1f,
+    val scaleY: Float = 1f,
+    val paint: Paint,
+) : Drawable() {
+    private var cf: ColorFilter? = null
+
+    override fun draw(canvas: Canvas) {
+        if (scaleX != 1f || scaleY != 1f) {
+            canvas.scale(scaleX, scaleY)
+        }
+        canvas.drawPath(path, paint)
+    }
+
+    override fun getIntrinsicHeight(): Int = height
+    override fun getIntrinsicWidth(): Int = width
+
+    override fun getOpacity(): Int = PixelFormat.OPAQUE
+
+    override fun setAlpha(alpha: Int) {}
+
+    override fun setColorFilter(colorFilter: ColorFilter?) {
+        cf = colorFilter
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerResDelegate.kt b/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerResDelegate.kt
index 8b4aeef..c64766a 100644
--- a/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerResDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerResDelegate.kt
@@ -27,44 +27,50 @@
 import com.android.systemui.R
 import java.io.PrintWriter
 
-class RoundedCornerResDelegate(
+interface RoundedCornerResDelegate {
+    val hasTop: Boolean
+    val topRoundedDrawable: Drawable?
+    val topRoundedSize: Size
+
+    val hasBottom: Boolean
+    val bottomRoundedDrawable: Drawable?
+    val bottomRoundedSize: Size
+
+    var physicalPixelDisplaySizeRatio: Float
+
+    fun updateDisplayUniqueId(newDisplayUniqueId: String?, newReloadToken: Int?)
+}
+
+/**
+ * Delegate for the device-default rounded corners. These will always be loaded from the config
+ * values `R.array.config_roundedCornerTopDrawableArray` and `R.drawable.rounded_corner_top`
+ */
+class RoundedCornerResDelegateImpl(
     private val res: Resources,
     private var displayUniqueId: String?
-) : Dumpable {
-
-    private val density: Float
-        get() = res.displayMetrics.density
+) : RoundedCornerResDelegate, Dumpable {
 
     private var reloadToken: Int = 0
 
-    var hasTop: Boolean = false
+    override var hasTop: Boolean = false
         private set
 
-    var hasBottom: Boolean = false
+    override var hasBottom: Boolean = false
         private set
 
-    var topRoundedDrawable: Drawable? = null
+    override var topRoundedDrawable: Drawable? = null
         private set
 
-    var bottomRoundedDrawable: Drawable? = null
+    override var bottomRoundedDrawable: Drawable? = null
         private set
 
-    var topRoundedSize = Size(0, 0)
+    override var topRoundedSize = Size(0, 0)
         private set
 
-    var bottomRoundedSize = Size(0, 0)
+    override var bottomRoundedSize = Size(0, 0)
         private set
 
-    var tuningSizeFactor: Int? = null
-        set(value) {
-            if (field == value) {
-                return
-            }
-            field = value
-            reloadMeasures()
-        }
-
-    var physicalPixelDisplaySizeRatio: Float = 1f
+    override var physicalPixelDisplaySizeRatio: Float = 1f
         set(value) {
             if (field == value) {
                 return
@@ -78,7 +84,7 @@
         reloadMeasures()
     }
 
-    fun updateDisplayUniqueId(newDisplayUniqueId: String?, newReloadToken: Int?) {
+    override fun updateDisplayUniqueId(newDisplayUniqueId: String?, newReloadToken: Int?) {
         if (displayUniqueId != newDisplayUniqueId) {
             displayUniqueId = newDisplayUniqueId
             newReloadToken ?.let { reloadToken = it }
@@ -122,19 +128,6 @@
             bottomRoundedSize = Size(it.intrinsicWidth, it.intrinsicHeight)
         }
 
-        tuningSizeFactor?.let {
-            if (it <= 0) {
-                return
-            }
-            val length: Int = (it * density).toInt()
-            if (topRoundedSize.width > 0) {
-                topRoundedSize = Size(length, length)
-            }
-            if (bottomRoundedSize.width > 0) {
-                bottomRoundedSize = Size(length, length)
-            }
-        }
-
         if (physicalPixelDisplaySizeRatio != 1f) {
             if (topRoundedSize.width != 0) {
                 topRoundedSize = Size(
diff --git a/packages/SystemUI/src/com/android/systemui/decor/ScreenDecorCommand.kt b/packages/SystemUI/src/com/android/systemui/decor/ScreenDecorCommand.kt
new file mode 100644
index 0000000..fa1d898
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/decor/ScreenDecorCommand.kt
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.decor
+
+import android.graphics.Color
+import android.graphics.Path
+import android.util.PathParser
+import com.android.systemui.statusbar.commandline.ParseableCommand
+import com.android.systemui.statusbar.commandline.Type
+import com.android.systemui.statusbar.commandline.map
+import java.io.PrintWriter
+
+/** Debug screen-decor command to be handled by the SystemUI command line interface */
+class ScreenDecorCommand(
+    private val callback: Callback,
+) : ParseableCommand(SCREEN_DECOR_CMD_NAME) {
+    val debug: Boolean? by
+        param(
+            longName = "debug",
+            description =
+                "Enter or exits debug mode. Effectively makes the corners visible and allows " +
+                    "for overriding the path data for the anti-aliasing corner paths and display " +
+                    "cutout.",
+            valueParser = Type.Boolean,
+        )
+
+    val color: Int? by
+        param(
+            longName = "color",
+            shortName = "c",
+            description =
+                "Set a specific color for the debug assets. See Color#parseString() for " +
+                    "accepted inputs.",
+            valueParser = Type.String.map { it.toColorIntOrNull() }
+        )
+
+    val roundedTop: RoundedCornerSubCommand? by subCommand(RoundedCornerSubCommand("rounded-top"))
+
+    val roundedBottom: RoundedCornerSubCommand? by
+        subCommand(RoundedCornerSubCommand("rounded-bottom"))
+
+    override fun execute(pw: PrintWriter) {
+        callback.onExecute(this, pw)
+    }
+
+    override fun toString(): String {
+        return "ScreenDecorCommand(" +
+            "debug=$debug, " +
+            "color=$color, " +
+            "roundedTop=$roundedTop, " +
+            "roundedBottom=$roundedBottom)"
+    }
+
+    /** For use in ScreenDecorations.java, define a Callback */
+    interface Callback {
+        fun onExecute(cmd: ScreenDecorCommand, pw: PrintWriter)
+    }
+
+    companion object {
+        const val SCREEN_DECOR_CMD_NAME = "screen-decor"
+    }
+}
+
+/**
+ * Defines a subcommand suitable for `rounded-top` and `rounded-bottom`. They both have the same
+ * API.
+ */
+class RoundedCornerSubCommand(name: String) : ParseableCommand(name) {
+    val height by
+        param(
+                longName = "height",
+                description = "The height of a corner, in pixels.",
+                valueParser = Type.Int,
+            )
+            .required()
+
+    val width by
+        param(
+                longName = "width",
+                description =
+                    "The width of the corner, in pixels. Likely should be equal to the height.",
+                valueParser = Type.Int,
+            )
+            .required()
+
+    val pathData by
+        param(
+                longName = "path-data",
+                shortName = "d",
+                description =
+                    "PathParser-compatible path string to be rendered as the corner drawable. " +
+                        "This path should be a closed arc oriented as the top-left corner " +
+                        "of the device",
+                valueParser = Type.String.map { it.toPathOrNull() }
+            )
+            .required()
+
+    val viewportHeight: Float? by
+        param(
+            longName = "viewport-height",
+            description =
+                "The height of the viewport for the given path string. " +
+                    "If null, the corner height will be used.",
+            valueParser = Type.Float,
+        )
+
+    val scaleY: Float
+        get() = viewportHeight?.let { height.toFloat() / it } ?: 1.0f
+
+    val viewportWidth: Float? by
+        param(
+            longName = "viewport-width",
+            description =
+                "The width of the viewport for the given path string. " +
+                    "If null, the corner width will be used.",
+            valueParser = Type.Float,
+        )
+
+    val scaleX: Float
+        get() = viewportWidth?.let { width.toFloat() / it } ?: 1.0f
+
+    override fun execute(pw: PrintWriter) {
+        // Not needed for a subcommand
+    }
+
+    override fun toString(): String {
+        return "RoundedCornerSubCommand(" +
+            "height=$height," +
+            " width=$width," +
+            " pathData='$pathData'," +
+            " viewportHeight=$viewportHeight," +
+            " viewportWidth=$viewportWidth)"
+    }
+
+    fun toRoundedCornerDebugModel(): DebugRoundedCornerModel =
+        DebugRoundedCornerModel(
+            path = pathData,
+            width = width,
+            height = height,
+            scaleX = scaleX,
+            scaleY = scaleY,
+        )
+}
+
+fun String.toPathOrNull(): Path? =
+    try {
+        PathParser.createPathFromPathData(this)
+    } catch (e: Exception) {
+        null
+    }
+
+fun String.toColorIntOrNull(): Int? =
+    try {
+        Color.parseColor(this)
+    } catch (e: Exception) {
+        null
+    }
diff --git a/packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt b/packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt
new file mode 100644
index 0000000..65cd84b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/display/DisplayModule.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.display
+
+import com.android.systemui.display.data.repository.DisplayRepository
+import com.android.systemui.display.data.repository.DisplayRepositoryImpl
+import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor
+import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractorImpl
+import dagger.Binds
+import dagger.Module
+
+/** Module binding display related classes. */
+@Module
+interface DisplayModule {
+    @Binds
+    fun bindConnectedDisplayInteractor(
+        provider: ConnectedDisplayInteractorImpl
+    ): ConnectedDisplayInteractor
+
+    @Binds fun bindsDisplayRepository(displayRepository: DisplayRepositoryImpl): DisplayRepository
+}
diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayMetricsRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayMetricsRepository.kt
new file mode 100644
index 0000000..bcfeeb9e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayMetricsRepository.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.display.data.repository
+
+import android.content.Context
+import android.content.res.Configuration
+import android.util.DisplayMetrics
+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.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.dagger.DisplayMetricsRepoLog
+import com.android.systemui.statusbar.policy.ConfigurationController
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.stateIn
+
+/** Repository tracking display-related metrics like display height and width. */
+@SysUISingleton
+class DisplayMetricsRepository
+@Inject
+constructor(
+    @Application scope: CoroutineScope,
+    configurationController: ConfigurationController,
+    displayMetricsHolder: DisplayMetrics,
+    context: Context,
+    @DisplayMetricsRepoLog logBuffer: LogBuffer,
+) {
+
+    private val displayMetrics: StateFlow<DisplayMetrics> =
+        conflatedCallbackFlow {
+                val callback =
+                    object : ConfigurationController.ConfigurationListener {
+                        override fun onConfigChanged(newConfig: Configuration?) {
+                            context.display.getMetrics(displayMetricsHolder)
+                            trySend(displayMetricsHolder)
+                        }
+                    }
+                configurationController.addCallback(callback)
+                awaitClose { configurationController.removeCallback(callback) }
+            }
+            .onEach {
+                logBuffer.log(
+                    "DisplayMetrics",
+                    LogLevel.INFO,
+                    { str1 = it.toString() },
+                    { "New metrics: $str1" },
+                )
+            }
+            .stateIn(scope, SharingStarted.Eagerly, displayMetricsHolder)
+
+    /** Returns the current display height in pixels. */
+    val heightPixels: Int
+        get() = displayMetrics.value.heightPixels
+}
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
new file mode 100644
index 0000000..b18f7bf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
@@ -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.systemui.display.data.repository
+
+import android.hardware.display.DisplayManager
+import android.hardware.display.DisplayManager.DisplayListener
+import android.hardware.display.DisplayManager.EVENT_FLAG_DISPLAY_ADDED
+import android.hardware.display.DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
+import android.hardware.display.DisplayManager.EVENT_FLAG_DISPLAY_REMOVED
+import android.os.Handler
+import android.view.Display
+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.util.traceSection
+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.flowOn
+import kotlinx.coroutines.flow.stateIn
+
+/** Provides a [Flow] of [Display] as returned by [DisplayManager]. */
+interface DisplayRepository {
+    /** Provides a nullable set of displays. */
+    val displays: Flow<Set<Display>>
+}
+
+@SysUISingleton
+class DisplayRepositoryImpl
+@Inject
+constructor(
+    private val displayManager: DisplayManager,
+    @Background backgroundHandler: Handler,
+    @Application applicationScope: CoroutineScope,
+    @Background backgroundCoroutineDispatcher: CoroutineDispatcher
+) : DisplayRepository {
+
+    override val displays: Flow<Set<Display>> =
+        conflatedCallbackFlow {
+                val callback =
+                    object : DisplayListener {
+                        override fun onDisplayAdded(displayId: Int) {
+                            trySend(getDisplays())
+                        }
+
+                        override fun onDisplayRemoved(displayId: Int) {
+                            trySend(getDisplays())
+                        }
+
+                        override fun onDisplayChanged(displayId: Int) {
+                            trySend(getDisplays())
+                        }
+                    }
+                displayManager.registerDisplayListener(
+                    callback,
+                    backgroundHandler,
+                    EVENT_FLAG_DISPLAY_ADDED or
+                        EVENT_FLAG_DISPLAY_CHANGED or
+                        EVENT_FLAG_DISPLAY_REMOVED,
+                )
+                awaitClose { displayManager.unregisterDisplayListener(callback) }
+            }
+            .flowOn(backgroundCoroutineDispatcher)
+            .stateIn(
+                applicationScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = getDisplays()
+            )
+
+    fun getDisplays(): Set<Display> =
+        traceSection("DisplayRepository#getDisplays()") {
+            displayManager.displays?.toSet() ?: emptySet()
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractor.kt b/packages/SystemUI/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractor.kt
new file mode 100644
index 0000000..4b957c7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractor.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.display.domain.interactor
+
+import android.view.Display
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.display.data.repository.DisplayRepository
+import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.State
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
+
+/** Provides information about an external connected display. */
+interface ConnectedDisplayInteractor {
+    /**
+     * Provides the current external display state.
+     *
+     * The state is:
+     * - [State.CONNECTED] when there is at least one display with [TYPE_EXTERNAL].
+     * - [State.CONNECTED_SECURE] when is at least one display with both [TYPE_EXTERNAL] AND
+     *   [Display.FLAG_SECURE] set
+     */
+    val connectedDisplayState: Flow<State>
+
+    /** Possible connected display state. */
+    enum class State {
+        DISCONNECTED,
+        CONNECTED,
+        CONNECTED_SECURE,
+    }
+}
+
+@SysUISingleton
+class ConnectedDisplayInteractorImpl
+@Inject
+constructor(
+    displayRepository: DisplayRepository,
+) : ConnectedDisplayInteractor {
+
+    override val connectedDisplayState: Flow<State> =
+        displayRepository.displays
+            .map { displays ->
+                val externalDisplays =
+                    displays.filter { display -> display.type == Display.TYPE_EXTERNAL }
+
+                val secureExternalDisplays =
+                    externalDisplays.filter { it.flags and Display.FLAG_SECURE != 0 }
+
+                if (externalDisplays.isEmpty()) {
+                    State.DISCONNECTED
+                } else if (!secureExternalDisplays.isEmpty()) {
+                    State.CONNECTED_SECURE
+                } else {
+                    State.CONNECTED
+                }
+            }
+            .distinctUntilChanged()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
index 5369780..75b8e51 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
@@ -20,9 +20,9 @@
 import com.android.systemui.doze.DozeLog.Reason
 import com.android.systemui.doze.DozeLog.reasonToString
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel.DEBUG
-import com.android.systemui.log.LogLevel.ERROR
-import com.android.systemui.log.LogLevel.INFO
+import com.android.systemui.log.core.LogLevel.DEBUG
+import com.android.systemui.log.core.LogLevel.ERROR
+import com.android.systemui.log.core.LogLevel.INFO
 import com.android.systemui.log.dagger.DozeLog
 import com.android.systemui.statusbar.policy.DevicePostureController
 import com.google.errorprone.annotations.CompileTimeConstant
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamLogger.kt b/packages/SystemUI/src/com/android/systemui/dreams/DreamLogger.kt
index fdb7651..f3a07fc 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamLogger.kt
@@ -16,15 +16,52 @@
 
 package com.android.systemui.dreams
 
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
-import com.android.systemui.log.dagger.DreamLog
-import javax.inject.Inject
+import com.android.systemui.log.core.Logger
+import com.android.systemui.log.core.MessageBuffer
 
 /** Logs dream-related stuff to a {@link LogBuffer}. */
-class DreamLogger @Inject constructor(@DreamLog private val buffer: LogBuffer) {
-    /** Logs a debug message to the buffer. */
-    fun d(tag: String, message: String) {
-        buffer.log(tag, LogLevel.DEBUG, { str1 = message }, { message })
-    }
+class DreamLogger(buffer: MessageBuffer, tag: String) : Logger(buffer, tag) {
+    fun logDreamOverlayEnabled(enabled: Boolean) =
+        d({ "Dream overlay enabled: $bool1" }) { bool1 = enabled }
+
+    fun logIgnoreAddComplication(reason: String, complication: String) =
+        d({ "Ignore adding complication, reason: $str1, complication: $str2" }) {
+            str1 = reason
+            str2 = complication
+        }
+
+    fun logIgnoreRemoveComplication(reason: String, complication: String) =
+        d({ "Ignore removing complication, reason: $str1, complication: $str2" }) {
+            str1 = reason
+            str2 = complication
+        }
+
+    fun logAddComplication(complication: String) =
+        d({ "Add dream complication: $str1" }) { str1 = complication }
+
+    fun logRemoveComplication(complication: String) =
+        d({ "Remove dream complication: $str1" }) { str1 = complication }
+
+    fun logOverlayActive(active: Boolean) = d({ "Dream overlay active: $bool1" }) { bool1 = active }
+
+    fun logLowLightActive(active: Boolean) =
+        d({ "Low light mode active: $bool1" }) { bool1 = active }
+
+    fun logHasAssistantAttention(hasAttention: Boolean) =
+        d({ "Dream overlay has Assistant attention: $bool1" }) { bool1 = hasAttention }
+
+    fun logStatusBarVisible(visible: Boolean) =
+        d({ "Dream overlay status bar visible: $bool1" }) { bool1 = visible }
+
+    fun logAvailableComplicationTypes(types: Int) =
+        d({ "Available complication types: $int1" }) { int1 = types }
+
+    fun logShouldShowComplications(showComplications: Boolean) =
+        d({ "Dream overlay should show complications: $bool1" }) { bool1 = showComplications }
+
+    fun logShowOrHideStatusBarItem(show: Boolean, type: String) =
+        d({ "${if (bool1) "Showing" else "Hiding"} dream status bar item: $int1" }) {
+            bool1 = show
+            str1 = type
+        }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
index ee046c2..01fb522 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
@@ -26,6 +26,7 @@
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
 import com.android.app.animation.Interpolators
+import com.android.dream.lowlight.util.TruncatedInterpolator
 import com.android.systemui.R
 import com.android.systemui.complication.ComplicationHostViewController
 import com.android.systemui.complication.ComplicationLayoutParams
@@ -35,6 +36,9 @@
 import com.android.systemui.dreams.dagger.DreamOverlayModule
 import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel
 import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.Logger
+import com.android.systemui.log.dagger.DreamLog
 import com.android.systemui.statusbar.BlurUtils
 import com.android.systemui.statusbar.CrossFadeHelper
 import com.android.systemui.statusbar.policy.ConfigurationController
@@ -64,12 +68,14 @@
     private val mDreamInTranslationYDistance: Int,
     @Named(DreamOverlayModule.DREAM_IN_TRANSLATION_Y_DURATION)
     private val mDreamInTranslationYDurationMs: Long,
-    private val mLogger: DreamLogger,
+    @DreamLog logBuffer: LogBuffer,
 ) {
     companion object {
         private const val TAG = "DreamOverlayAnimationsController"
     }
 
+    private val logger = Logger(logBuffer, TAG)
+
     private var mAnimator: Animator? = null
     private lateinit var view: View
 
@@ -178,11 +184,11 @@
                 doOnEnd {
                     mAnimator = null
                     mOverlayStateController.setEntryAnimationsFinished(true)
-                    mLogger.d(TAG, "Dream overlay entry animations finished.")
+                    logger.d("Dream overlay entry animations finished.")
                 }
-                doOnCancel { mLogger.d(TAG, "Dream overlay entry animations canceled.") }
+                doOnCancel { logger.d("Dream overlay entry animations canceled.") }
                 start()
-                mLogger.d(TAG, "Dream overlay entry animations started.")
+                logger.d("Dream overlay entry animations started.")
             }
     }
 
@@ -204,31 +210,28 @@
                     translationYAnimator(
                         from = 0f,
                         to = -mDreamInTranslationYDistance.toFloat(),
-                        durationMs = mDreamInTranslationYDurationMs,
+                        durationMs = mDreamInComplicationsAnimDurationMs,
                         delayMs = 0,
-                        interpolator = Interpolators.EMPHASIZED
+                        // Truncate the animation from the full duration to match the alpha
+                        // animation so that the whole animation ends at the same time.
+                        interpolator =
+                            TruncatedInterpolator(
+                                Interpolators.EMPHASIZED,
+                                /*originalDuration=*/ mDreamInTranslationYDurationMs.toFloat(),
+                                /*newDuration=*/ mDreamInComplicationsAnimDurationMs.toFloat()
+                            )
                     ),
                     alphaAnimator(
-                            from =
-                                mCurrentAlphaAtPosition.getOrDefault(
-                                    key = POSITION_BOTTOM,
-                                    defaultValue = 1f
-                                ),
-                            to = 0f,
-                            durationMs = mDreamInComplicationsAnimDurationMs,
-                            delayMs = 0,
-                            positions = POSITION_BOTTOM
-                        )
-                        .apply {
-                            doOnEnd {
-                                // The logical end of the animation is once the alpha and blur
-                                // animations finish, end the animation so that any listeners are
-                                // notified. The Y translation animation is much longer than all of
-                                // the other animations due to how the spec is defined, but is not
-                                // expected to run to completion.
-                                mAnimator?.end()
-                            }
-                        },
+                        from =
+                            mCurrentAlphaAtPosition.getOrDefault(
+                                key = POSITION_BOTTOM,
+                                defaultValue = 1f
+                            ),
+                        to = 0f,
+                        durationMs = mDreamInComplicationsAnimDurationMs,
+                        delayMs = 0,
+                        positions = POSITION_BOTTOM
+                    ),
                     alphaAnimator(
                         from =
                             mCurrentAlphaAtPosition.getOrDefault(
@@ -244,11 +247,11 @@
                 doOnEnd {
                     mAnimator = null
                     mOverlayStateController.setExitAnimationsRunning(false)
-                    mLogger.d(TAG, "Dream overlay exit animations finished.")
+                    logger.d("Dream overlay exit animations finished.")
                 }
-                doOnCancel { mLogger.d(TAG, "Dream overlay exit animations canceled.") }
+                doOnCancel { logger.d("Dream overlay exit animations canceled.") }
                 start()
-                mLogger.d(TAG, "Dream overlay exit animations started.")
+                logger.d("Dream overlay exit animations started.")
             }
         mOverlayStateController.setExitAnimationsRunning(true)
         return mAnimator as AnimatorSet
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
index ff07bb6..78ac453 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
@@ -39,8 +39,8 @@
 import com.android.systemui.dreams.dagger.DreamOverlayComponent;
 import com.android.systemui.dreams.dagger.DreamOverlayModule;
 import com.android.systemui.dreams.touch.scrim.BouncerlessScrimController;
-import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor;
-import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor.PrimaryBouncerExpansionCallback;
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor;
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor.PrimaryBouncerExpansionCallback;
 import com.android.systemui.shade.ShadeExpansionChangeEvent;
 import com.android.systemui.statusbar.BlurUtils;
 import com.android.systemui.util.ViewController;
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayLifecycleOwner.kt b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayLifecycleOwner.kt
index 8325356..003d2c7 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayLifecycleOwner.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayLifecycleOwner.kt
@@ -28,7 +28,8 @@
 class DreamOverlayLifecycleOwner @Inject constructor() : LifecycleOwner {
     val registry: LifecycleRegistry = LifecycleRegistry(this)
 
-    override fun getLifecycle(): Lifecycle {
-        return registry
-    }
+    override val lifecycle: Lifecycle
+        get() {
+            return registry
+        }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index d509015..553405f 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -294,6 +294,7 @@
 
         mWindow.clearFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
         mWindow.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);
+        mWindow.addPrivateFlags(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS);
         mWindow.requestFeature(Window.FEATURE_NO_TITLE);
         // Hide all insets when the dream is showing
         mWindow.getDecorView().getWindowInsetsController().hide(WindowInsets.Type.systemBars());
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
index b141db1..c9748f9 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
@@ -19,7 +19,6 @@
 import static com.android.systemui.dreams.dagger.DreamModule.DREAM_OVERLAY_ENABLED;
 
 import android.service.dreams.DreamService;
-import android.util.Log;
 
 import androidx.annotation.NonNull;
 
@@ -29,6 +28,8 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
+import com.android.systemui.log.LogBuffer;
+import com.android.systemui.log.dagger.DreamLog;
 import com.android.systemui.statusbar.policy.CallbackController;
 
 import java.util.ArrayList;
@@ -52,7 +53,6 @@
 public class DreamOverlayStateController implements
         CallbackController<DreamOverlayStateController.Callback> {
     private static final String TAG = "DreamOverlayStateCtlr";
-    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
     public static final int STATE_DREAM_OVERLAY_ACTIVE = 1 << 0;
     public static final int STATE_LOW_LIGHT_ACTIVE = 1 << 1;
@@ -110,13 +110,17 @@
 
     private final int mSupportedTypes;
 
+    private final DreamLogger mLogger;
+
     @VisibleForTesting
     @Inject
     public DreamOverlayStateController(@Main Executor executor,
             @Named(DREAM_OVERLAY_ENABLED) boolean overlayEnabled,
-            FeatureFlags featureFlags) {
+            FeatureFlags featureFlags,
+            @DreamLog LogBuffer logBuffer) {
         mExecutor = executor;
         mOverlayEnabled = overlayEnabled;
+        mLogger = new DreamLogger(logBuffer, TAG);
         mFeatureFlags = featureFlags;
         if (mFeatureFlags.isEnabled(Flags.ALWAYS_SHOW_HOME_CONTROLS_ON_DREAMS)) {
             mSupportedTypes = Complication.COMPLICATION_TYPE_NONE
@@ -124,9 +128,7 @@
         } else {
             mSupportedTypes = Complication.COMPLICATION_TYPE_NONE;
         }
-        if (DEBUG) {
-            Log.d(TAG, "Dream overlay enabled:" + mOverlayEnabled);
-        }
+        mLogger.logDreamOverlayEnabled(mOverlayEnabled);
     }
 
     /**
@@ -134,18 +136,13 @@
      */
     public void addComplication(Complication complication) {
         if (!mOverlayEnabled) {
-            if (DEBUG) {
-                Log.d(TAG,
-                        "Ignoring adding complication due to overlay disabled:" + complication);
-            }
+            mLogger.logIgnoreAddComplication("overlay disabled", complication.toString());
             return;
         }
 
         mExecutor.execute(() -> {
             if (mComplications.add(complication)) {
-                if (DEBUG) {
-                    Log.d(TAG, "addComplication: added " + complication);
-                }
+                mLogger.logAddComplication(complication.toString());
                 mCallbacks.stream().forEach(callback -> callback.onComplicationsChanged());
             }
         });
@@ -156,18 +153,13 @@
      */
     public void removeComplication(Complication complication) {
         if (!mOverlayEnabled) {
-            if (DEBUG) {
-                Log.d(TAG,
-                        "Ignoring removing complication due to overlay disabled:" + complication);
-            }
+            mLogger.logIgnoreRemoveComplication("overlay disabled", complication.toString());
             return;
         }
 
         mExecutor.execute(() -> {
             if (mComplications.remove(complication)) {
-                if (DEBUG) {
-                    Log.d(TAG, "removeComplication: removed " + complication);
-                }
+                mLogger.logRemoveComplication(complication.toString());
                 mCallbacks.stream().forEach(callback -> callback.onComplicationsChanged());
             }
         });
@@ -313,6 +305,7 @@
      * @param active {@code true} if overlay is active, {@code false} otherwise.
      */
     public void setOverlayActive(boolean active) {
+        mLogger.logOverlayActive(active);
         modifyState(active ? OP_SET_STATE : OP_CLEAR_STATE, STATE_DREAM_OVERLAY_ACTIVE);
     }
 
@@ -321,6 +314,8 @@
      * @param active {@code true} if low light mode is active, {@code false} otherwise.
      */
     public void setLowLightActive(boolean active) {
+        mLogger.logLowLightActive(active);
+
         if (isLowLightActive() && !active) {
             // Notify that we're exiting low light only on the transition from active to not active.
             mCallbacks.forEach(Callback::onExitLowLight);
@@ -351,6 +346,7 @@
      * @param hasAttention {@code true} if has the user's attention, {@code false} otherwise.
      */
     public void setHasAssistantAttention(boolean hasAttention) {
+        mLogger.logHasAssistantAttention(hasAttention);
         modifyState(hasAttention ? OP_SET_STATE : OP_CLEAR_STATE, STATE_HAS_ASSISTANT_ATTENTION);
     }
 
@@ -359,6 +355,7 @@
      * @param visible {@code true} if the status bar is visible, {@code false} otherwise.
      */
     public void setDreamOverlayStatusBarVisible(boolean visible) {
+        mLogger.logStatusBarVisible(visible);
         modifyState(
                 visible ? OP_SET_STATE : OP_CLEAR_STATE, STATE_DREAM_OVERLAY_STATUS_BAR_VISIBLE);
     }
@@ -376,6 +373,7 @@
      */
     public void setAvailableComplicationTypes(@Complication.ComplicationType int types) {
         mExecutor.execute(() -> {
+            mLogger.logAvailableComplicationTypes(types);
             mAvailableComplicationTypes = types;
             mCallbacks.forEach(Callback::onAvailableComplicationTypesChanged);
         });
@@ -393,6 +391,7 @@
      */
     public void setShouldShowComplications(boolean shouldShowComplications) {
         mExecutor.execute(() -> {
+            mLogger.logShouldShowComplications(shouldShowComplications);
             mShouldShowComplications = shouldShowComplications;
             mCallbacks.forEach(Callback::onAvailableComplicationTypesChanged);
         });
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java
index 7c1bfed..1865c38 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java
@@ -141,6 +141,20 @@
         mExtraSystemStatusViewGroup = findViewById(R.id.dream_overlay_extra_items);
     }
 
+    protected static String getLoggableStatusIconType(@StatusIconType int type) {
+        return switch (type) {
+            case STATUS_ICON_NOTIFICATIONS -> "notifications";
+            case STATUS_ICON_WIFI_UNAVAILABLE -> "wifi_unavailable";
+            case STATUS_ICON_ALARM_SET -> "alarm_set";
+            case STATUS_ICON_CAMERA_DISABLED -> "camera_disabled";
+            case STATUS_ICON_MIC_DISABLED -> "mic_disabled";
+            case STATUS_ICON_MIC_CAMERA_DISABLED -> "mic_camera_disabled";
+            case STATUS_ICON_PRIORITY_MODE_ON -> "priority_mode_on";
+            case STATUS_ICON_ASSISTANT_ATTENTION_ACTIVE -> "assistant_attention_active";
+            default -> type + "(unknown)";
+        };
+    }
+
     void showIcon(@StatusIconType int iconType, boolean show, @Nullable String contentDescription) {
         View icon = mStatusIcons.get(iconType);
         if (icon == null) {
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
index c954f98..a6401b6 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
@@ -36,6 +36,8 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dreams.DreamOverlayStatusBarItemsProvider.StatusBarItem;
 import com.android.systemui.dreams.dagger.DreamOverlayComponent;
+import com.android.systemui.log.LogBuffer;
+import com.android.systemui.log.dagger.DreamLog;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.CrossFadeHelper;
 import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController;
@@ -61,6 +63,8 @@
  */
 @DreamOverlayComponent.DreamOverlayScope
 public class DreamOverlayStatusBarViewController extends ViewController<DreamOverlayStatusBarView> {
+    private static final String TAG = "DreamStatusBarCtrl";
+
     private final ConnectivityManager mConnectivityManager;
     private final TouchInsetManager.TouchInsetSession mTouchInsetSession;
     private final NextAlarmController mNextAlarmController;
@@ -78,6 +82,7 @@
     private final Executor mMainExecutor;
     private final List<DreamOverlayStatusBarItemsProvider.StatusBarItem> mExtraStatusBarItems =
             new ArrayList<>();
+    private final DreamLogger mLogger;
 
     private boolean mIsAttached;
 
@@ -157,7 +162,8 @@
             StatusBarWindowStateController statusBarWindowStateController,
             DreamOverlayStatusBarItemsProvider statusBarItemsProvider,
             DreamOverlayStateController dreamOverlayStateController,
-            UserTracker userTracker) {
+            UserTracker userTracker,
+            @DreamLog LogBuffer logBuffer) {
         super(view);
         mResources = resources;
         mMainExecutor = mainExecutor;
@@ -173,6 +179,7 @@
         mZenModeController = zenModeController;
         mDreamOverlayStateController = dreamOverlayStateController;
         mUserTracker = userTracker;
+        mLogger = new DreamLogger(logBuffer, TAG);
 
         // Register to receive show/hide updates for the system status bar. Our custom status bar
         // needs to hide when the system status bar is showing to ovoid overlapping status bars.
@@ -341,6 +348,8 @@
             @Nullable String contentDescription) {
         mMainExecutor.execute(() -> {
             if (mIsAttached) {
+                mLogger.logShowOrHideStatusBarItem(
+                        show, DreamOverlayStatusBarView.getLoggableStatusIconType(iconType));
                 mView.showIcon(iconType, show, contentDescription);
             }
         });
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamSmartspaceController.kt
index 4b297a3..ca4ef0d 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamSmartspaceController.kt
@@ -50,7 +50,7 @@
 @SysUISingleton
 class DreamSmartspaceController @Inject constructor(
     private val context: Context,
-    private val smartspaceManager: SmartspaceManager,
+    private val smartspaceManager: SmartspaceManager?,
     private val execution: Execution,
     @Main private val uiExecutor: Executor,
     private val smartspaceViewComponentFactory: SmartspaceViewComponent.Factory,
@@ -184,6 +184,9 @@
     }
 
     private fun connectSession() {
+        if (smartspaceManager == null) {
+            return
+        }
         if (plugin == null && weatherPlugin == null) {
             return
         }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java
index 570132e1..1cd3774 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java
@@ -35,9 +35,11 @@
 
 import com.android.internal.logging.UiEvent;
 import com.android.internal.logging.UiEventLogger;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants;
 import com.android.systemui.dreams.touch.scrim.ScrimController;
 import com.android.systemui.dreams.touch.scrim.ScrimManager;
-import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shade.ShadeExpansionChangeEvent;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
@@ -76,6 +78,8 @@
 
     private static final String TAG = "BouncerSwipeTouchHandler";
     private final NotificationShadeWindowController mNotificationShadeWindowController;
+    private final LockPatternUtils mLockPatternUtils;
+    private final UserTracker mUserTracker;
     private final float mBouncerZoneScreenPercentage;
 
     private final ScrimManager mScrimManager;
@@ -151,6 +155,11 @@
                         return true;
                     }
 
+                    // Don't set expansion if the user doesn't have a pin/password set.
+                    if (!mLockPatternUtils.isSecure(mUserTracker.getUserId())) {
+                        return true;
+                    }
+
                     // For consistency, we adopt the expansion definition found in the
                     // PanelViewController. In this case, expansion refers to the view above the
                     // bouncer. As that view's expansion shrinks, the bouncer appears. The bouncer
@@ -204,6 +213,8 @@
             NotificationShadeWindowController notificationShadeWindowController,
             ValueAnimatorCreator valueAnimatorCreator,
             VelocityTrackerFactory velocityTrackerFactory,
+            LockPatternUtils lockPatternUtils,
+            UserTracker userTracker,
             @Named(SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_OPENING)
                     FlingAnimationUtils flingAnimationUtils,
             @Named(SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_CLOSING)
@@ -213,6 +224,8 @@
         mCentralSurfaces = centralSurfaces;
         mScrimManager = scrimManager;
         mNotificationShadeWindowController = notificationShadeWindowController;
+        mLockPatternUtils = lockPatternUtils;
+        mUserTracker = userTracker;
         mBouncerZoneScreenPercentage = swipeRegionPercentage;
         mFlingAnimationUtils = flingAnimationUtils;
         mFlingAnimationUtilsClosing = flingAnimationUtilsClosing;
@@ -347,6 +360,11 @@
             return;
         }
 
+        // Don't set expansion if the user doesn't have a pin/password set.
+        if (!mLockPatternUtils.isSecure(mUserTracker.getUserId())) {
+            return;
+        }
+
         // The animation utils deal in pixel units, rather than expansion height.
         final float viewHeight = mTouchSession.getBounds().height();
         final float currentHeight = viewHeight * mCurrentExpansion;
diff --git a/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt b/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt
index 4b03fd3..75284fc 100644
--- a/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt
@@ -20,12 +20,16 @@
 import android.os.SystemClock
 import android.os.Trace
 import com.android.systemui.CoreStartable
+import com.android.systemui.ProtoDumpable
 import com.android.systemui.R
 import com.android.systemui.dump.DumpHandler.Companion.PRIORITY_ARG_CRITICAL
 import com.android.systemui.dump.DumpHandler.Companion.PRIORITY_ARG_NORMAL
+import com.android.systemui.dump.DumpsysEntry.DumpableEntry
+import com.android.systemui.dump.DumpsysEntry.LogBufferEntry
+import com.android.systemui.dump.DumpsysEntry.TableLogBufferEntry
 import com.android.systemui.dump.nano.SystemUIProtoDump
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.shared.system.UncaughtExceptionPreHandlerManager
+import com.android.systemui.log.table.TableLogBuffer
 import com.google.protobuf.nano.MessageNano
 import java.io.BufferedOutputStream
 import java.io.FileDescriptor
@@ -39,11 +43,11 @@
  *
  * Dump output is split into two sections, CRITICAL and NORMAL. In general, the CRITICAL section
  * contains all dumpables that were registered to the [DumpManager], while the NORMAL sections
- * contains all [LogBuffer]s (due to their length).
+ * contains all [LogBuffer]s and [TableLogBuffer]s (due to their length).
  *
- * The CRITICAL and NORMAL sections can be found within a bug report by searching for
- * "SERVICE com.android.systemui/.SystemUIService" and
- * "SERVICE com.android.systemui/.dump.SystemUIAuxiliaryDumpService", respectively.
+ * The CRITICAL and NORMAL sections can be found within a bug report by searching for "SERVICE
+ * com.android.systemui/.SystemUIService" and "SERVICE
+ * com.android.systemui/.dump.SystemUIAuxiliaryDumpService", respectively.
  *
  * Finally, some or all of the dump can be triggered on-demand via adb (see below).
  *
@@ -72,6 +76,7 @@
  * # To dump all dumpables or all buffers:
  * $ <invocation> dumpables
  * $ <invocation> buffers
+ * $ <invocation> tables
  *
  * # Finally, the following will simulate what we dump during the CRITICAL and NORMAL sections of a
  * # bug report:
@@ -83,37 +88,26 @@
  * $ <invocation> --help
  * ```
  */
-class DumpHandler @Inject constructor(
+class DumpHandler
+@Inject
+constructor(
     private val context: Context,
     private val dumpManager: DumpManager,
     private val logBufferEulogizer: LogBufferEulogizer,
     private val startables: MutableMap<Class<*>, Provider<CoreStartable>>,
-    private val uncaughtExceptionPreHandlerManager: UncaughtExceptionPreHandlerManager
 ) {
-    /**
-     * Registers an uncaught exception handler
-     */
-    fun init() {
-        uncaughtExceptionPreHandlerManager.registerHandler { _, e ->
-            if (e is Exception) {
-                logBufferEulogizer.record(e)
-            }
-        }
-    }
-
-    /**
-     * Dump the diagnostics! Behavior can be controlled via [args].
-     */
+    /** Dump the diagnostics! Behavior can be controlled via [args]. */
     fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<String>) {
         Trace.beginSection("DumpManager#dump()")
         val start = SystemClock.uptimeMillis()
 
-        val parsedArgs = try {
-            parseArgs(args)
-        } catch (e: ArgParseException) {
-            pw.println(e.message)
-            return
-        }
+        val parsedArgs =
+            try {
+                parseArgs(args)
+            } catch (e: ArgParseException) {
+                pw.println(e.message)
+                return
+            }
 
         when {
             parsedArgs.dumpPriority == PRIORITY_ARG_CRITICAL -> dumpCritical(pw, parsedArgs)
@@ -134,6 +128,7 @@
             "bugreport-normal" -> dumpNormal(pw, args)
             "dumpables" -> dumpDumpables(pw, args)
             "buffers" -> dumpBuffers(pw, args)
+            "tables" -> dumpTables(pw, args)
             "config" -> dumpConfig(pw)
             "help" -> dumpHelp(pw)
             else -> {
@@ -147,44 +142,65 @@
     }
 
     private fun dumpCritical(pw: PrintWriter, args: ParsedArgs) {
-        dumpManager.dumpCritical(pw, args.rawArgs)
+        val targets = dumpManager.getDumpables()
+        for (target in targets) {
+            if (target.priority == DumpPriority.CRITICAL) {
+                dumpDumpable(target, pw, args.rawArgs)
+            }
+        }
         dumpConfig(pw)
     }
 
     private fun dumpNormal(pw: PrintWriter, args: ParsedArgs) {
-        dumpManager.dumpNormal(pw, args.rawArgs, args.tailLength)
+        val targets = dumpManager.getDumpables()
+        for (target in targets) {
+            if (target.priority == DumpPriority.NORMAL) {
+                dumpDumpable(target, pw, args.rawArgs)
+            }
+        }
+
+        val buffers = dumpManager.getLogBuffers()
+        for (buffer in buffers) {
+            dumpBuffer(buffer, pw, args.tailLength)
+        }
+
+        val tableBuffers = dumpManager.getTableLogBuffers()
+        for (table in tableBuffers) {
+            dumpTableBuffer(table, pw, args.rawArgs)
+        }
+
         logBufferEulogizer.readEulogyIfPresent(pw)
     }
 
-    private fun dumpDumpables(pw: PrintWriter, args: ParsedArgs) {
-        if (args.listOnly) {
-            dumpManager.listDumpables(pw)
-        } else {
-            dumpManager.dumpDumpables(pw, args.rawArgs)
+    private fun dumpDumpables(pw: PrintWriter, args: ParsedArgs) =
+        dumpManager.getDumpables().listOrDumpEntries(pw, args)
+
+    private fun dumpBuffers(pw: PrintWriter, args: ParsedArgs) =
+        dumpManager.getLogBuffers().listOrDumpEntries(pw, args)
+
+    private fun dumpTables(pw: PrintWriter, args: ParsedArgs) =
+        dumpManager.getTableLogBuffers().listOrDumpEntries(pw, args)
+
+    private fun listTargetNames(targets: Collection<DumpsysEntry>, pw: PrintWriter) {
+        for (target in targets) {
+            pw.println(target.name)
         }
     }
 
-    private fun dumpBuffers(pw: PrintWriter, args: ParsedArgs) {
-        if (args.listOnly) {
-            dumpManager.listBuffers(pw)
-        } else {
-            dumpManager.dumpBuffers(pw, args.tailLength)
-        }
-    }
-
-    private fun dumpProtoTargets(
-            targets: List<String>,
-            fd: FileDescriptor,
-            args: ParsedArgs
-    ) {
+    private fun dumpProtoTargets(targets: List<String>, fd: FileDescriptor, args: ParsedArgs) {
         val systemUIProto = SystemUIProtoDump()
+        val dumpables = dumpManager.getDumpables()
         if (targets.isNotEmpty()) {
             for (target in targets) {
-                dumpManager.dumpProtoTarget(target, systemUIProto, args.rawArgs)
+                findBestProtoTargetMatch(dumpables, target)?.dumpProto(systemUIProto, args.rawArgs)
             }
         } else {
-            dumpManager.dumpProtoDumpables(systemUIProto, args.rawArgs)
+            // Dump all protos
+            for (dumpable in dumpables) {
+                (dumpable.dumpable as? ProtoDumpable)?.dumpProto(systemUIProto, args.rawArgs)
+            }
         }
+
         val buffer = BufferedOutputStream(FileOutputStream(fd))
         buffer.use {
             it.write(MessageNano.toByteArray(systemUIProto))
@@ -192,36 +208,70 @@
         }
     }
 
-    private fun dumpTargets(
-        targets: List<String>,
-        pw: PrintWriter,
-        args: ParsedArgs
-    ) {
+    // Attempts to dump the target list to the given PrintWriter. Since the arguments come in as
+    // a list of strings, we use the [findBestTargetMatch] method to determine the most-correct
+    // target with the given search string.
+    private fun dumpTargets(targets: List<String>, pw: PrintWriter, args: ParsedArgs) {
         if (targets.isNotEmpty()) {
-            for (target in targets) {
-                dumpManager.dumpTarget(target, pw, args.rawArgs, args.tailLength)
+            val dumpables = dumpManager.getDumpables()
+            val buffers = dumpManager.getLogBuffers()
+            val tableBuffers = dumpManager.getTableLogBuffers()
+
+            targets.forEach { target ->
+                findTargetInCollection(target, dumpables, buffers, tableBuffers)?.dump(pw, args)
             }
         } else {
             if (args.listOnly) {
+                val dumpables = dumpManager.getDumpables()
+                val buffers = dumpManager.getLogBuffers()
+
                 pw.println("Dumpables:")
-                dumpManager.listDumpables(pw)
+                listTargetNames(dumpables, pw)
                 pw.println()
 
                 pw.println("Buffers:")
-                dumpManager.listBuffers(pw)
+                listTargetNames(buffers, pw)
             } else {
                 pw.println("Nothing to dump :(")
             }
         }
     }
 
+    private fun findTargetInCollection(
+        target: String,
+        dumpables: Collection<DumpableEntry>,
+        logBuffers: Collection<LogBufferEntry>,
+        tableBuffers: Collection<TableLogBufferEntry>,
+    ) =
+        sequence {
+                findBestTargetMatch(dumpables, target)?.let { yield(it) }
+                findBestTargetMatch(logBuffers, target)?.let { yield(it) }
+                findBestTargetMatch(tableBuffers, target)?.let { yield(it) }
+            }
+            .sortedBy { it.name }
+            .minByOrNull { it.name.length }
+
+    private fun dumpDumpable(entry: DumpableEntry, pw: PrintWriter, args: Array<String>) {
+        pw.preamble(entry)
+        entry.dumpable.dump(pw, args)
+    }
+
+    private fun dumpBuffer(entry: LogBufferEntry, pw: PrintWriter, tailLength: Int) {
+        pw.preamble(entry)
+        entry.buffer.dump(pw, tailLength)
+    }
+
+    private fun dumpTableBuffer(buffer: TableLogBufferEntry, pw: PrintWriter, args: Array<String>) {
+        pw.preamble(buffer)
+        buffer.table.dump(pw, args)
+    }
+
     private fun dumpConfig(pw: PrintWriter) {
         pw.println("SystemUiServiceComponents configuration:")
         pw.print("vendor component: ")
         pw.println(context.resources.getString(R.string.config_systemUIVendorServiceComponent))
-        val services: MutableList<String> = startables.keys
-                .map({ cls: Class<*> -> cls.simpleName })
-                .toMutableList()
+        val services: MutableList<String> =
+            startables.keys.map({ cls: Class<*> -> cls.simpleName }).toMutableList()
 
         services.add(context.resources.getString(R.string.config_systemUIVendorServiceComponent))
         dumpServiceList(pw, "global", services.toTypedArray())
@@ -265,6 +315,7 @@
         pw.println("Special commands:")
         pw.println("$ <invocation> dumpables")
         pw.println("$ <invocation> buffers")
+        pw.println("$ <invocation> tables")
         pw.println("$ <invocation> bugreport-critical")
         pw.println("$ <invocation> bugreport-normal")
         pw.println("$ <invocation> config")
@@ -274,6 +325,7 @@
         pw.println("$ <invocation> --list")
         pw.println("$ <invocation> dumpables --list")
         pw.println("$ <invocation> buffers --list")
+        pw.println("$ <invocation> tables --list")
         pw.println()
 
         pw.println("Show only the most recent N lines of buffers")
@@ -291,24 +343,26 @@
                 iterator.remove()
                 when (arg) {
                     PRIORITY_ARG -> {
-                        pArgs.dumpPriority = readArgument(iterator, PRIORITY_ARG) {
-                            if (PRIORITY_OPTIONS.contains(it)) {
-                                it
-                            } else {
-                                throw IllegalArgumentException()
+                        pArgs.dumpPriority =
+                            readArgument(iterator, PRIORITY_ARG) {
+                                if (PRIORITY_OPTIONS.contains(it)) {
+                                    it
+                                } else {
+                                    throw IllegalArgumentException()
+                                }
                             }
-                        }
                     }
                     PROTO -> pArgs.proto = true
-                    "-t", "--tail" -> {
-                        pArgs.tailLength = readArgument(iterator, arg) {
-                            it.toInt()
-                        }
+                    "-t",
+                    "--tail" -> {
+                        pArgs.tailLength = readArgument(iterator, arg) { it.toInt() }
                     }
-                    "-l", "--list" -> {
+                    "-l",
+                    "--list" -> {
                         pArgs.listOnly = true
                     }
-                    "-h", "--help" -> {
+                    "-h",
+                    "--help" -> {
                         pArgs.command = "help"
                     }
                     // This flag is passed as part of the proto dump in Bug reports, we can ignore
@@ -345,29 +399,131 @@
         }
     }
 
+    private fun DumpsysEntry.dump(pw: PrintWriter, args: ParsedArgs) =
+        when (this) {
+            is DumpableEntry -> dumpDumpable(this, pw, args.rawArgs)
+            is LogBufferEntry -> dumpBuffer(this, pw, args.tailLength)
+            is TableLogBufferEntry -> dumpTableBuffer(this, pw, args.rawArgs)
+        }
+
+    private fun Collection<DumpsysEntry>.listOrDumpEntries(pw: PrintWriter, args: ParsedArgs) =
+        if (args.listOnly) {
+            listTargetNames(this, pw)
+        } else {
+            forEach { it.dump(pw, args) }
+        }
+
     companion object {
         const val PRIORITY_ARG = "--dump-priority"
         const val PRIORITY_ARG_CRITICAL = "CRITICAL"
         const val PRIORITY_ARG_NORMAL = "NORMAL"
         const val PROTO = "--proto"
+
+        /**
+         * Important: do not change this divider without updating any bug report processing tools
+         * (e.g. ABT), since this divider is used to determine boundaries for bug report views
+         */
+        const val DUMPSYS_DUMPABLE_DIVIDER =
+            "----------------------------------------------------------------------------"
+
+        /**
+         * Important: do not change this divider without updating any bug report processing tools
+         * (e.g. ABT), since this divider is used to determine boundaries for bug report views
+         */
+        const val DUMPSYS_BUFFER_DIVIDER =
+            "============================================================================"
+
+        private fun findBestTargetMatch(c: Collection<DumpsysEntry>, target: String) =
+            c.asSequence().filter { it.name.endsWith(target) }.minByOrNull { it.name.length }
+
+        private fun findBestProtoTargetMatch(
+            c: Collection<DumpableEntry>,
+            target: String
+        ): ProtoDumpable? =
+            c.asSequence()
+                .filter { it.name.endsWith(target) }
+                .filter { it.dumpable is ProtoDumpable }
+                .minByOrNull { it.name.length }
+                ?.dumpable as? ProtoDumpable
+
+        private fun PrintWriter.preamble(entry: DumpsysEntry) =
+            when (entry) {
+                // Historically TableLogBuffer was not separate from dumpables, so they have the
+                // same header
+                is DumpableEntry,
+                is TableLogBufferEntry -> {
+                    println()
+                    println(entry.name)
+                    println(DUMPSYS_DUMPABLE_DIVIDER)
+                }
+                is LogBufferEntry -> {
+                    println()
+                    println()
+                    println("BUFFER ${entry.name}:")
+                    println(DUMPSYS_BUFFER_DIVIDER)
+                }
+            }
+
+        /**
+         * Zero-arg utility to write a [DumpableEntry] to the given [PrintWriter] in a
+         * dumpsys-appropriate format.
+         */
+        private fun dumpDumpable(entry: DumpableEntry, pw: PrintWriter) {
+            pw.preamble(entry)
+            entry.dumpable.dump(pw, arrayOf())
+        }
+
+        /**
+         * Zero-arg utility to write a [LogBufferEntry] to the given [PrintWriter] in a
+         * dumpsys-appropriate format.
+         */
+        private fun dumpBuffer(entry: LogBufferEntry, pw: PrintWriter) {
+            pw.preamble(entry)
+            entry.buffer.dump(pw, 0)
+        }
+
+        /**
+         * Zero-arg utility to write a [TableLogBufferEntry] to the given [PrintWriter] in a
+         * dumpsys-appropriate format.
+         */
+        private fun dumpTableBuffer(entry: TableLogBufferEntry, pw: PrintWriter) {
+            pw.preamble(entry)
+            entry.table.dump(pw, arrayOf())
+        }
+
+        /**
+         * Zero-arg utility to write a [DumpsysEntry] to the given [PrintWriter] in a
+         * dumpsys-appropriate format.
+         */
+        fun DumpsysEntry.dump(pw: PrintWriter) {
+            when (this) {
+                is DumpableEntry -> dumpDumpable(this, pw)
+                is LogBufferEntry -> dumpBuffer(this, pw)
+                is TableLogBufferEntry -> dumpTableBuffer(this, pw)
+            }
+        }
+
+        /** Format [entries] in a dumpsys-appropriate way, using [pw] */
+        fun dumpEntries(entries: Collection<DumpsysEntry>, pw: PrintWriter) {
+            entries.forEach { it.dump(pw) }
+        }
     }
 }
 
 private val PRIORITY_OPTIONS = arrayOf(PRIORITY_ARG_CRITICAL, PRIORITY_ARG_NORMAL)
 
-private val COMMANDS = arrayOf(
+private val COMMANDS =
+    arrayOf(
         "bugreport-critical",
         "bugreport-normal",
         "buffers",
         "dumpables",
+        "tables",
         "config",
         "help"
-)
+    )
 
-private class ParsedArgs(
-    val rawArgs: Array<String>,
-    val nonFlagArgs: List<String>
-) {
+private class ParsedArgs(val rawArgs: Array<String>, val nonFlagArgs: List<String>) {
     var dumpPriority: String? = null
     var tailLength: Int = 0
     var command: String? = null
diff --git a/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt b/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
index 2d57633..c924df6 100644
--- a/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
@@ -18,9 +18,11 @@
 
 import com.android.systemui.Dumpable
 import com.android.systemui.ProtoDumpable
-import com.android.systemui.dump.nano.SystemUIProtoDump
+import com.android.systemui.dump.DumpsysEntry.DumpableEntry
+import com.android.systemui.dump.DumpsysEntry.LogBufferEntry
+import com.android.systemui.dump.DumpsysEntry.TableLogBufferEntry
 import com.android.systemui.log.LogBuffer
-import java.io.PrintWriter
+import com.android.systemui.log.table.TableLogBuffer
 import java.util.TreeMap
 import javax.inject.Inject
 import javax.inject.Singleton
@@ -37,8 +39,9 @@
 @Singleton
 open class DumpManager @Inject constructor() {
     // NOTE: Using TreeMap ensures that iteration is in a predictable & alphabetical order.
-    private val dumpables: MutableMap<String, RegisteredDumpable<Dumpable>> = TreeMap()
-    private val buffers: MutableMap<String, RegisteredDumpable<LogBuffer>> = TreeMap()
+    private val dumpables: MutableMap<String, DumpableEntry> = TreeMap()
+    private val buffers: MutableMap<String, LogBufferEntry> = TreeMap()
+    private val tableLogBuffers: MutableMap<String, TableLogBufferEntry> = TreeMap()
 
     /** See [registerCriticalDumpable]. */
     fun registerCriticalDumpable(module: Dumpable) {
@@ -77,14 +80,14 @@
      * Register a dumpable to be called during a bug report.
      *
      * @param name The name to register the dumpable under. This is typically the qualified class
-     * name of the thing being dumped (getClass().getName()), but can be anything as long as it
-     * doesn't clash with an existing registration.
+     *   name of the thing being dumped (getClass().getName()), but can be anything as long as it
+     *   doesn't clash with an existing registration.
      * @param priority the priority level of this dumpable, which affects at what point in the bug
-     * report this gets dump. By default, the dumpable will be called during the CRITICAL section of
-     * the bug report, so don't dump an excessive amount of stuff here.
+     *   report this gets dump. By default, the dumpable will be called during the CRITICAL section
+     *   of the bug report, so don't dump an excessive amount of stuff here.
      *
      * TODO(b/259973758): Replace all calls to this method with calls to [registerCriticalDumpable]
-     * or [registerNormalDumpable] instead.
+     *   or [registerNormalDumpable] instead.
      */
     @Synchronized
     @JvmOverloads
@@ -98,7 +101,7 @@
             throw IllegalArgumentException("'$name' is already registered")
         }
 
-        dumpables[name] = RegisteredDumpable(name, module, priority)
+        dumpables[name] = DumpableEntry(module, name, priority)
     }
 
     /**
@@ -110,217 +113,62 @@
         registerDumpable(module::class.java.simpleName, module)
     }
 
-    /**
-     * Unregisters a previously-registered dumpable.
-     */
+    /** Unregisters a previously-registered dumpable. */
     @Synchronized
     fun unregisterDumpable(name: String) {
         dumpables.remove(name)
     }
 
-    /**
-     * Register a [LogBuffer] to be dumped during a bug report.
-     */
+    /** Register a [LogBuffer] to be dumped during a bug report. */
     @Synchronized
     fun registerBuffer(name: String, buffer: LogBuffer) {
         if (!canAssignToNameLocked(name, buffer)) {
             throw IllegalArgumentException("'$name' is already registered")
         }
 
+        buffers[name] = LogBufferEntry(buffer, name)
+    }
+
+    /** Register a [TableLogBuffer] to be dumped during a bugreport */
+    @Synchronized
+    fun registerTableLogBuffer(name: String, buffer: TableLogBuffer) {
+        if (!canAssignToNameLocked(name, buffer)) {
+            throw IllegalArgumentException("'$name' is already registered")
+        }
+
         // All buffers must be priority NORMAL, not CRITICAL, because they often contain a lot of
         // data.
-        buffers[name] = RegisteredDumpable(name, buffer, DumpPriority.NORMAL)
+        tableLogBuffers[name] = TableLogBufferEntry(buffer, name)
     }
 
-    /**
-     * Dumps the alphabetically first, shortest-named dumpable or buffer whose registered name ends
-     * with [target].
-     */
-    @Synchronized
-    fun dumpTarget(
-        target: String,
-        pw: PrintWriter,
-        args: Array<String>,
-        tailLength: Int,
-    ) {
-        sequence {
-            findBestTargetMatch(dumpables, target)?.let {
-                yield(it.name to { dumpDumpable(it, pw, args) })
-            }
-            findBestTargetMatch(buffers, target)?.let {
-                yield(it.name to { dumpBuffer(it, pw, tailLength) })
-            }
-        }.sortedBy { it.first }.minByOrNull { it.first.length }?.second?.invoke()
-    }
+    @Synchronized fun getDumpables(): Collection<DumpableEntry> = dumpables.values.toList()
+
+    @Synchronized fun getLogBuffers(): Collection<LogBufferEntry> = buffers.values.toList()
 
     @Synchronized
-    fun dumpProtoTarget(
-        target: String,
-        protoDump: SystemUIProtoDump,
-        args: Array<String>
-    ) {
-        findBestProtoTargetMatch(dumpables, target)?.let {
-            dumpProtoDumpable(it, protoDump, args)
-        }
-    }
-
-    @Synchronized
-    fun dumpProtoDumpables(
-        systemUIProtoDump: SystemUIProtoDump,
-        args: Array<String>
-    ) {
-        for (dumpable in dumpables.values) {
-            if (dumpable.dumpable is ProtoDumpable) {
-                dumpProtoDumpable(
-                    dumpable.dumpable,
-                    systemUIProtoDump,
-                    args
-                )
-            }
-        }
-    }
-
-    /**
-     * Dumps all registered dumpables with critical priority to [pw]
-     */
-    @Synchronized
-    fun dumpCritical(pw: PrintWriter, args: Array<String>) {
-        for (dumpable in dumpables.values) {
-            if (dumpable.priority == DumpPriority.CRITICAL) {
-                dumpDumpable(dumpable, pw, args)
-            }
-        }
-    }
-
-    /**
-     * To [pw], dumps (1) all registered dumpables with normal priority; and (2) all [LogBuffer]s.
-     */
-    @Synchronized
-    fun dumpNormal(pw: PrintWriter, args: Array<String>, tailLength: Int = 0) {
-        for (dumpable in dumpables.values) {
-            if (dumpable.priority == DumpPriority.NORMAL) {
-                dumpDumpable(dumpable, pw, args)
-            }
-        }
-
-        for (buffer in buffers.values) {
-            dumpBuffer(buffer, pw, tailLength)
-        }
-    }
-
-    /**
-     * Dump all the instances of [Dumpable].
-     */
-    @Synchronized
-    fun dumpDumpables(pw: PrintWriter, args: Array<String>) {
-        for (module in dumpables.values) {
-            dumpDumpable(module, pw, args)
-        }
-    }
-
-    /**
-     * Dumps the names of all registered dumpables (one per line)
-     */
-    @Synchronized
-    fun listDumpables(pw: PrintWriter) {
-        for (module in dumpables.values) {
-            pw.println(module.name)
-        }
-    }
-
-    /**
-     * Dumps all registered [LogBuffer]s to [pw]
-     */
-    @Synchronized
-    fun dumpBuffers(pw: PrintWriter, tailLength: Int) {
-        for (buffer in buffers.values) {
-            dumpBuffer(buffer, pw, tailLength)
-        }
-    }
-
-    /**
-     * Dumps the names of all registered buffers (one per line)
-     */
-    @Synchronized
-    fun listBuffers(pw: PrintWriter) {
-        for (buffer in buffers.values) {
-            pw.println(buffer.name)
-        }
-    }
+    fun getTableLogBuffers(): Collection<TableLogBufferEntry> = tableLogBuffers.values.toList()
 
     @Synchronized
     fun freezeBuffers() {
         for (buffer in buffers.values) {
-            buffer.dumpable.freeze()
+            buffer.buffer.freeze()
         }
     }
 
     @Synchronized
     fun unfreezeBuffers() {
         for (buffer in buffers.values) {
-            buffer.dumpable.unfreeze()
+            buffer.buffer.unfreeze()
         }
     }
 
-    private fun dumpDumpable(
-        dumpable: RegisteredDumpable<Dumpable>,
-        pw: PrintWriter,
-        args: Array<String>
-    ) {
-        pw.println()
-        pw.println("${dumpable.name}:")
-        pw.println("----------------------------------------------------------------------------")
-        dumpable.dumpable.dump(pw, args)
-    }
-
-    private fun dumpBuffer(
-        buffer: RegisteredDumpable<LogBuffer>,
-        pw: PrintWriter,
-        tailLength: Int
-    ) {
-        pw.println()
-        pw.println()
-        pw.println("BUFFER ${buffer.name}:")
-        pw.println("============================================================================")
-        buffer.dumpable.dump(pw, tailLength)
-    }
-
-    private fun dumpProtoDumpable(
-        protoDumpable: ProtoDumpable,
-        systemUIProtoDump: SystemUIProtoDump,
-        args: Array<String>
-    ) {
-        protoDumpable.dumpProto(systemUIProtoDump, args)
-    }
-
     private fun canAssignToNameLocked(name: String, newDumpable: Any): Boolean {
-        val existingDumpable = dumpables[name]?.dumpable ?: buffers[name]?.dumpable
+        val existingDumpable =
+            dumpables[name]?.dumpable ?: buffers[name]?.buffer ?: tableLogBuffers[name]?.table
         return existingDumpable == null || newDumpable == existingDumpable
     }
-
-    private fun <V : Any> findBestTargetMatch(map: Map<String, V>, target: String): V? = map
-        .asSequence()
-        .filter { it.key.endsWith(target) }
-        .minByOrNull { it.key.length }
-        ?.value
-
-    private fun findBestProtoTargetMatch(
-        map: Map<String, RegisteredDumpable<Dumpable>>,
-        target: String
-    ): ProtoDumpable? = map
-        .asSequence()
-        .filter { it.key.endsWith(target) }
-        .filter { it.value.dumpable is ProtoDumpable }
-        .minByOrNull { it.key.length }
-        ?.value?.dumpable as? ProtoDumpable
 }
 
-private data class RegisteredDumpable<T>(
-    val name: String,
-    val dumpable: T,
-    val priority: DumpPriority,
-)
-
 /**
  * The priority level for a given dumpable, which affects at what point in the bug report this gets
  * dumped.
diff --git a/packages/SystemUI/src/com/android/systemui/dump/DumpsysEntry.kt b/packages/SystemUI/src/com/android/systemui/dump/DumpsysEntry.kt
new file mode 100644
index 0000000..cd3e1bb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dump/DumpsysEntry.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.dump
+
+import com.android.systemui.Dumpable
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.table.TableLogBuffer
+
+/**
+ * A DumpsysEntry is a named, registered entry tracked by [DumpManager] which can be addressed and
+ * used both in a bugreport / dumpsys invocation or in an individual CLI implementation.
+ *
+ * The idea here is that we define every type that [DumpManager] knows about and defines the minimum
+ * shared interface between each type. So far, just [name] and [priority]. This way, [DumpManager]
+ * can just store them in separate maps and do the minimal amount of work to discriminate between
+ * them.
+ *
+ * Individual consumers can request these participants in a list via the relevant get* methods on
+ * [DumpManager]
+ */
+sealed interface DumpsysEntry {
+    val name: String
+    val priority: DumpPriority
+
+    data class DumpableEntry(
+        val dumpable: Dumpable,
+        override val name: String,
+        override val priority: DumpPriority,
+    ) : DumpsysEntry
+
+    data class LogBufferEntry(
+        val buffer: LogBuffer,
+        override val name: String,
+    ) : DumpsysEntry {
+        // All buffers must be priority NORMAL, not CRITICAL, because they often contain a lot of
+        // data.
+        override val priority: DumpPriority = DumpPriority.NORMAL
+    }
+
+    data class TableLogBufferEntry(
+        val table: TableLogBuffer,
+        override val name: String,
+    ) : DumpsysEntry {
+        // All buffers must be priority NORMAL, not CRITICAL, because they often contain a lot of
+        // data.
+        override val priority: DumpPriority = DumpPriority.NORMAL
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dump/LogBufferEulogizer.kt b/packages/SystemUI/src/com/android/systemui/dump/LogBufferEulogizer.kt
index 2d5c9ae..bd43302 100644
--- a/packages/SystemUI/src/com/android/systemui/dump/LogBufferEulogizer.kt
+++ b/packages/SystemUI/src/com/android/systemui/dump/LogBufferEulogizer.kt
@@ -20,6 +20,7 @@
 import android.icu.text.SimpleDateFormat
 import android.util.Log
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dump.DumpHandler.Companion.dumpEntries
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.util.io.Files
 import com.android.systemui.util.time.SystemClock
@@ -48,20 +49,21 @@
     private val files: Files,
     private val logPath: Path,
     private val minWriteGap: Long,
-    private val maxLogAgeToDump: Long
+    private val maxLogAgeToDump: Long,
 ) {
-    @Inject constructor(
+    @Inject
+    constructor(
         context: Context,
         dumpManager: DumpManager,
         systemClock: SystemClock,
-        files: Files
+        files: Files,
     ) : this(
         dumpManager,
         systemClock,
         files,
         Paths.get(context.filesDir.toPath().toString(), "log_buffers.txt"),
         MIN_WRITE_GAP,
-        MAX_AGE_TO_DUMP
+        MAX_AGE_TO_DUMP,
     )
 
     /**
@@ -70,7 +72,7 @@
      * The file will be prefaced by the [reason], which will then be returned (presumably so it can
      * be thrown).
      */
-    fun <T : Exception> record(reason: T): T {
+    fun <T : Throwable> record(reason: T): T {
         val start = systemClock.uptimeMillis()
         var duration = 0L
 
@@ -91,7 +93,8 @@
                 pw.println()
                 pw.println("Dump triggered by exception:")
                 reason.printStackTrace(pw)
-                dumpManager.dumpBuffers(pw, 0)
+                val buffers = dumpManager.getLogBuffers()
+                dumpEntries(buffers, pw)
                 duration = systemClock.uptimeMillis() - start
                 pw.println()
                 pw.println("Buffer eulogy took ${duration}ms")
@@ -105,16 +108,17 @@
         return reason
     }
 
-    /**
-     * If a eulogy file is present, writes its contents to [pw].
-     */
+    /** If a eulogy file is present, writes its contents to [pw]. */
     fun readEulogyIfPresent(pw: PrintWriter) {
         try {
             val millisSinceLastWrite = getMillisSinceLastWrite(logPath)
             if (millisSinceLastWrite > maxLogAgeToDump) {
-                Log.i(TAG, "Not eulogizing buffers; they are " +
+                Log.i(
+                    TAG,
+                    "Not eulogizing buffers; they are " +
                         TimeUnit.HOURS.convert(millisSinceLastWrite, TimeUnit.MILLISECONDS) +
-                        " hours old")
+                        " hours old"
+                )
                 return
             }
 
@@ -122,9 +126,7 @@
                 pw.println()
                 pw.println()
                 pw.println("=============== BUFFERS FROM MOST RECENT CRASH ===============")
-                s.forEach { line ->
-                    pw.println(line)
-                }
+                s.forEach { line -> pw.println(line) }
             }
         } catch (e: IOException) {
             // File doesn't exist, okay
@@ -134,12 +136,13 @@
     }
 
     private fun getMillisSinceLastWrite(path: Path): Long {
-        val stats = try {
-            files.readAttributes(path, BasicFileAttributes::class.java)
-        } catch (e: IOException) {
-            // File doesn't exist
-            null
-        }
+        val stats =
+            try {
+                files.readAttributes(path, BasicFileAttributes::class.java)
+            } catch (e: IOException) {
+                // File doesn't exist
+                null
+            }
         return systemClock.currentTimeMillis() - (stats?.lastModifiedTime()?.toMillis() ?: 0)
     }
 }
@@ -147,4 +150,4 @@
 private const val TAG = "BufferEulogizer"
 private val MIN_WRITE_GAP = TimeUnit.MINUTES.toMillis(5)
 private val MAX_AGE_TO_DUMP = TimeUnit.HOURS.toMillis(48)
-private val DATE_FORMAT = SimpleDateFormat("MM-dd HH:mm:ss.SSS", Locale.US)
\ No newline at end of file
+private val DATE_FORMAT = SimpleDateFormat("MM-dd HH:mm:ss.SSS", Locale.US)
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 366056a..0670ec3 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -61,10 +61,6 @@
     // TODO(b/254512538): Tracking Bug
     val INSTANT_VOICE_REPLY = unreleasedFlag(111, "instant_voice_reply")
 
-    // TODO(b/279735475): Tracking Bug
-    @JvmField
-    val NEW_LIGHT_BAR_LOGIC = releasedFlag(279735475, "new_light_bar_logic")
-
     /**
      * This flag is server-controlled and should stay as [unreleasedFlag] since we never want to
      * enable it on release builds.
@@ -72,13 +68,6 @@
     val NOTIFICATION_MEMORY_LOGGING_ENABLED =
         unreleasedFlag(119, "notification_memory_logging_enabled")
 
-    @JvmField
-    val SIMPLIFIED_APPEAR_FRACTION =
-        releasedFlag(259395680, "simplified_appear_fraction")
-
-    // TODO(b/257315550): Tracking Bug
-    val NO_HUN_FOR_OLD_WHEN = releasedFlag(118, "no_hun_for_old_when")
-
     // TODO(b/260335638): Tracking Bug
     @JvmField
     val NOTIFICATION_INLINE_REPLY_ANIMATION =
@@ -93,7 +82,21 @@
     // TODO(b/277338665): Tracking Bug
     @JvmField
     val NOTIFICATION_SHELF_REFACTOR =
-        unreleasedFlag(271161129, "notification_shelf_refactor")
+        unreleasedFlag(271161129, "notification_shelf_refactor", teamfood = true)
+
+    // TODO(b/290787599): Tracking Bug
+    @JvmField
+    val NOTIFICATION_ICON_CONTAINER_REFACTOR =
+        unreleasedFlag(278765923, "notification_icon_container_refactor")
+
+    // TODO(b/288326013): Tracking Bug
+    @JvmField
+    val NOTIFICATION_ASYNC_HYBRID_VIEW_INFLATION =
+            unreleasedFlag(
+                    288326013,
+                    "notification_async_hybrid_view_inflation",
+                    teamfood = false
+            )
 
     @JvmField
     val ANIMATED_NOTIFICATION_SHADE_INSETS =
@@ -141,6 +144,14 @@
     val LOCKSCREEN_WITHOUT_SECURE_LOCK_WHEN_DREAMING = releasedFlag(208,
         "lockscreen_without_secure_lock_when_dreaming")
 
+    // TODO(b/286092087): Tracking Bug
+    @JvmField
+    val ENABLE_SYSTEM_UI_DREAM_CONTROLLER = unreleasedFlag(301, "enable_system_ui_dream_controller")
+
+    // TODO(b/288287730): Tracking Bug
+    @JvmField
+    val ENABLE_SYSTEM_UI_DREAM_HOSTING = unreleasedFlag(302, "enable_system_ui_dream_hosting")
+
     /**
      * Whether the clock on a wide lock screen should use the new "stepping" animation for moving
      * the digits when the clock moves.
@@ -155,22 +166,6 @@
     // TODO(b/255607168): Tracking Bug
     @JvmField val DOZING_MIGRATION_1 = unreleasedFlag(213, "dozing_migration_1")
 
-    // TODO(b/252897742): Tracking Bug
-    @JvmField val NEW_ELLIPSE_DETECTION = unreleasedFlag(214, "new_ellipse_detection")
-
-    // TODO(b/252897742): Tracking Bug
-    @JvmField val NEW_UDFPS_OVERLAY = unreleasedFlag(215, "new_udfps_overlay")
-
-    /**
-     * Whether to enable the code powering customizable lock screen quick affordances.
-     *
-     * This flag enables any new prebuilt quick affordances as well.
-     */
-    // TODO(b/255618149): Tracking Bug
-    @JvmField
-    val CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES =
-        releasedFlag(216, "customizable_lock_screen_quick_affordances")
-
     /**
      * Migrates control of the LightRevealScrim's reveal effect and amount from legacy code to the
      * new KeyguardTransitionRepository.
@@ -221,7 +216,7 @@
     // TODO(b/267722622): Tracking Bug
     @JvmField
     val WALLPAPER_PICKER_UI_FOR_AIWP =
-            unreleasedFlag(
+            releasedFlag(
                     229,
                     "wallpaper_picker_ui_for_aiwp"
             )
@@ -251,12 +246,25 @@
     /** Whether to delay showing bouncer UI when face auth or active unlock are enrolled. */
     // TODO(b/279794160): Tracking bug.
     @JvmField
-    val DELAY_BOUNCER = unreleasedFlag(235, "delay_bouncer")
+    val DELAY_BOUNCER = unreleasedFlag(235, "delay_bouncer", teamfood = true)
+
+
+    /** Keyguard Migration */
 
     /** Migrate the indication area to the new keyguard root view. */
     // TODO(b/280067944): Tracking bug.
     @JvmField
-    val MIGRATE_INDICATION_AREA = unreleasedFlag(236, "migrate_indication_area")
+    val MIGRATE_INDICATION_AREA = unreleasedFlag(236, "migrate_indication_area", teamfood = true)
+
+    /**
+     * Migrate the bottom area to the new keyguard root view.
+     * Because there is no such thing as a "bottom area" after this, this also breaks it up into
+     * many smaller, modular pieces.
+     */
+    // TODO(b/290652751): Tracking bug.
+    @JvmField
+    val MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA =
+        unreleasedFlag(290652751, "migrate_split_keyguard_bottom_area")
 
     /** Whether to listen for fingerprint authentication over keyguard occluding activities. */
     // TODO(b/283260512): Tracking bug.
@@ -268,6 +276,24 @@
     @JvmField
     val KEYGUARD_TALKBACK_FIX = releasedFlag(238, "keyguard_talkback_fix")
 
+    // TODO(b/287268101): Tracking bug.
+    @JvmField
+    val TRANSIT_CLOCK = unreleasedFlag(239, "lockscreen_custom_transit_clock")
+
+    /** Migrate the lock icon view to the new keyguard root view. */
+    // TODO(b/286552209): Tracking bug.
+    @JvmField
+    val MIGRATE_LOCK_ICON = unreleasedFlag(240, "migrate_lock_icon", teamfood = true)
+
+    // TODO(b/288276738): Tracking bug.
+    @JvmField
+    val WIDGET_ON_KEYGUARD = unreleasedFlag(241, "widget_on_keyguard")
+
+    /** Migrate the NSSL to the a sibling to both the panel and keyguard root view. */
+    // TODO(b/288074305): Tracking bug.
+    @JvmField
+    val MIGRATE_NSSL = unreleasedFlag(242, "migrate_nssl")
+
     // 300 - power menu
     // TODO(b/254512600): Tracking Bug
     @JvmField val POWER_MENU_LITE = releasedFlag(300, "power_menu_lite")
@@ -339,22 +365,10 @@
     // TODO(b/256614753): Tracking Bug
     val NEW_STATUS_BAR_MOBILE_ICONS = releasedFlag(606, "new_status_bar_mobile_icons")
 
-    // TODO(b/256614210): Tracking Bug
-    val NEW_STATUS_BAR_WIFI_ICON = releasedFlag(607, "new_status_bar_wifi_icon")
-
     // TODO(b/256614751): Tracking Bug
     val NEW_STATUS_BAR_MOBILE_ICONS_BACKEND =
         unreleasedFlag(608, "new_status_bar_mobile_icons_backend", teamfood = true)
 
-    // TODO(b/256613548): Tracking Bug
-    val NEW_STATUS_BAR_WIFI_ICON_BACKEND =
-        unreleasedFlag(609, "new_status_bar_wifi_icon_backend", teamfood = true)
-
-    // TODO(b/256623670): Tracking Bug
-    @JvmField
-    val BATTERY_SHIELD_ICON =
-        resourceBooleanFlag(610, R.bool.flag_battery_shield_icon, "battery_shield_icon")
-
     // TODO(b/260881289): Tracking Bug
     val NEW_STATUS_BAR_ICONS_DEBUG_COLORING =
         unreleasedFlag(611, "new_status_bar_icons_debug_coloring")
@@ -364,7 +378,7 @@
 
     // TODO(b/280426085): Tracking Bug
     @JvmField
-    val NEW_BLUETOOTH_REPOSITORY = unreleasedFlag(612, "new_bluetooth_repository")
+    val NEW_BLUETOOTH_REPOSITORY = releasedFlag(612, "new_bluetooth_repository")
 
     // 700 - dialer/calls
     // TODO(b/254512734): Tracking Bug
@@ -418,19 +432,12 @@
     // TODO(b/263272731): Tracking Bug
     val MEDIA_TTT_RECEIVER_SUCCESS_RIPPLE = releasedFlag(910, "media_ttt_receiver_success_ripple")
 
-    // TODO(b/263512203): Tracking Bug
-    val MEDIA_EXPLICIT_INDICATOR = releasedFlag(911, "media_explicit_indicator")
-
     // TODO(b/265813373): Tracking Bug
     val MEDIA_TAP_TO_TRANSFER_DISMISS_GESTURE = releasedFlag(912, "media_ttt_dismiss_gesture")
 
     // TODO(b/266157412): Tracking Bug
     val MEDIA_RETAIN_SESSIONS = unreleasedFlag(913, "media_retain_sessions")
 
-    // TODO(b/266739309): Tracking Bug
-    @JvmField
-    val MEDIA_RECOMMENDATION_CARD_UPDATE = releasedFlag(914, "media_recommendation_card_update")
-
     // TODO(b/267007629): Tracking Bug
     val MEDIA_RESUME_PROGRESS = releasedFlag(915, "media_resume_progress")
 
@@ -446,9 +453,6 @@
     // TODO(b/254512758): Tracking Bug
     @JvmField val ROUNDED_BOX_RIPPLE = releasedFlag(1002, "rounded_box_ripple")
 
-    // TODO(b/265045965): Tracking Bug
-    val SHOW_LOWLIGHT_ON_DIRECT_BOOT = releasedFlag(1003, "show_lowlight_on_direct_boot")
-
     // TODO(b/273509374): Tracking Bug
     @JvmField
     val ALWAYS_SHOW_HOME_CONTROLS_ON_DREAMS = releasedFlag(1006,
@@ -485,7 +489,7 @@
     @Keep
     @JvmField
     val WM_CAPTION_ON_SHELL =
-        sysPropBooleanFlag(1105, "persist.wm.debug.caption_on_shell", default = false)
+        sysPropBooleanFlag(1105, "persist.wm.debug.caption_on_shell", default = true)
 
     @Keep
     @JvmField
@@ -543,7 +547,7 @@
     // TODO(b/273443374): Tracking Bug
     @Keep
     @JvmField val LOCKSCREEN_LIVE_WALLPAPER =
-        sysPropBooleanFlag(1117, "persist.wm.debug.lockscreen_live_wallpaper", default = false)
+        sysPropBooleanFlag(1117, "persist.wm.debug.lockscreen_live_wallpaper", default = true)
 
     // TODO(b/281648899): Tracking bug
     @Keep
@@ -551,6 +555,12 @@
     val WALLPAPER_MULTI_CROP =
         sysPropBooleanFlag(1118, "persist.wm.debug.wallpaper_multi_crop", default = false)
 
+    // TODO(b/290220798): Tracking Bug
+    @Keep
+    @JvmField
+    val ENABLE_PIP2_IMPLEMENTATION =
+        sysPropBooleanFlag(1119, "persist.wm.debug.enable_pip2_implementation", default = false)
+
 
     // 1200 - predictive back
     @Keep
@@ -578,7 +588,7 @@
 
     // TODO(b/270987164): Tracking Bug
     @JvmField
-    val TRACKPAD_GESTURE_FEATURES = unreleasedFlag(1205, "trackpad_gesture_features", teamfood = true)
+    val TRACKPAD_GESTURE_FEATURES = releasedFlag(1205, "trackpad_gesture_features")
 
     // TODO(b/263826204): Tracking Bug
     @JvmField
@@ -637,6 +647,9 @@
     // TODO(b/265944639): Tracking Bug
     @JvmField val DUAL_SHADE = unreleasedFlag(1801, "dual_shade")
 
+    // TODO(b/283300105): Tracking Bug
+    @JvmField val SCENE_CONTAINER = unreleasedFlag(1802, "scene_container")
+
     // 1900
     @JvmField val NOTE_TASKS = releasedFlag(1900, "keycode_flag")
 
@@ -662,7 +675,7 @@
     @JvmField val UDFPS_NEW_TOUCH_DETECTION = releasedFlag(2200, "udfps_new_touch_detection")
     @JvmField val UDFPS_ELLIPSE_DETECTION = releasedFlag(2201, "udfps_ellipse_detection")
     // TODO(b/278622168): Tracking Bug
-    @JvmField val BIOMETRIC_BP_STRONG = unreleasedFlag(2202, "biometric_bp_strong")
+    @JvmField val BIOMETRIC_BP_STRONG = releasedFlag(2202, "biometric_bp_strong")
 
     // 2300 - stylus
     @JvmField val TRACK_STYLUS_EVER_USED = releasedFlag(2300, "track_stylus_ever_used")
@@ -671,8 +684,7 @@
     @JvmField
     val ENABLE_USI_BATTERY_NOTIFICATIONS =
         releasedFlag(2302, "enable_usi_battery_notifications")
-    @JvmField val ENABLE_STYLUS_EDUCATION =
-        unreleasedFlag(2303, "enable_stylus_education", teamfood = true)
+    @JvmField val ENABLE_STYLUS_EDUCATION = releasedFlag(2303, "enable_stylus_education")
 
     // 2400 - performance tools and debugging info
     // TODO(b/238923086): Tracking Bug
@@ -711,23 +723,22 @@
 
     // TODO(b/259428678): Tracking Bug
     @JvmField
-    val KEYBOARD_BACKLIGHT_INDICATOR =
-            unreleasedFlag(2601, "keyboard_backlight_indicator", teamfood = true)
+    val KEYBOARD_BACKLIGHT_INDICATOR = releasedFlag(2601, "keyboard_backlight_indicator")
 
     // TODO(b/277192623): Tracking Bug
     @JvmField
     val KEYBOARD_EDUCATION =
         unreleasedFlag(2603, "keyboard_education", teamfood = false)
 
-    // TODO(b/272036292): Tracking Bug
-    @JvmField
-    val LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION =
-            releasedFlag(2602, "large_shade_granular_alpha_interpolation")
-
     // TODO(b/277201412): Tracking Bug
     @JvmField
     val SPLIT_SHADE_SUBPIXEL_OPTIMIZATION =
-            unreleasedFlag(2805, "split_shade_subpixel_optimization", teamfood = true)
+            releasedFlag(2805, "split_shade_subpixel_optimization")
+
+    // TODO(b/288868056): Tracking Bug
+    @JvmField
+    val PARTIAL_SCREEN_SHARING_TASK_SWITCHER =
+            unreleasedFlag(288868056, "pss_task_switcher")
 
     // TODO(b/278761837): Tracking Bug
     @JvmField
@@ -760,9 +771,21 @@
     val ENABLE_NEW_PRIVACY_DIALOG =
             unreleasedFlag(283740863, "enable_new_privacy_dialog", teamfood = false)
 
+    // TODO(b/289573946): Tracking Bug
+    @JvmField
+    val PRECOMPUTED_TEXT =
+        unreleasedFlag(289573946, "precomputed_text")
+
     // 2900 - CentralSurfaces-related flags
 
     // TODO(b/285174336): Tracking Bug
     @JvmField
     val USE_REPOS_FOR_BOUNCER_SHOWING = unreleasedFlag(2900, "use_repos_for_bouncer_showing")
+
+    // 3100 - Haptic interactions
+
+    // TODO(b/290213663): Tracking Bug
+    @JvmField
+    val ONE_WAY_HAPTICS_API_MIGRATION =
+        unreleasedFlag(3100, "oneway_haptics_api_migration")
 }
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
index 76322ad..040ee79 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
@@ -125,6 +125,7 @@
 import com.android.systemui.plugins.GlobalActionsPanelPlugin;
 import com.android.systemui.scrim.ScrimDrawable;
 import com.android.systemui.settings.UserTracker;
+import com.android.systemui.shade.ShadeController;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.VibratorHelper;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
@@ -251,6 +252,7 @@
     private int mSmallestScreenWidthDp;
     private int mOrientation;
     private final Optional<CentralSurfaces> mCentralSurfacesOptional;
+    private final ShadeController mShadeController;
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     private final DialogLaunchAnimator mDialogLaunchAnimator;
 
@@ -361,6 +363,7 @@
             @Main Handler handler,
             PackageManager packageManager,
             Optional<CentralSurfaces> centralSurfacesOptional,
+            ShadeController shadeController,
             KeyguardUpdateMonitor keyguardUpdateMonitor,
             DialogLaunchAnimator dialogLaunchAnimator) {
         mContext = context;
@@ -394,6 +397,7 @@
         mSmallestScreenWidthDp = resources.getConfiguration().smallestScreenWidthDp;
         mOrientation = resources.getConfiguration().orientation;
         mCentralSurfacesOptional = centralSurfacesOptional;
+        mShadeController = shadeController;
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         mDialogLaunchAnimator = dialogLaunchAnimator;
 
@@ -702,7 +706,9 @@
                 mAdapter, mOverflowAdapter, mSysuiColorExtractor, mStatusBarService,
                 mLightBarController,
                 mNotificationShadeWindowController, this::onRefresh, mKeyguardShowing,
-                mPowerAdapter, mUiEventLogger, mCentralSurfacesOptional, mKeyguardUpdateMonitor,
+                mPowerAdapter, mUiEventLogger, mCentralSurfacesOptional,
+                mShadeController,
+                mKeyguardUpdateMonitor,
                 mLockPatternUtils);
 
         dialog.setOnDismissListener(this);
@@ -912,7 +918,7 @@
             mUiEventLogger.log(GlobalActionsEvent.GA_EMERGENCY_DIALER_PRESS);
             if (mTelecomManager != null) {
                 // Close shade so user sees the activity
-                mCentralSurfacesOptional.ifPresent(CentralSurfaces::collapseShade);
+                mShadeController.cancelExpansionAndCollapseShade();
                 Intent intent = mTelecomManager.createLaunchEmergencyDialerIntent(
                         null /* number */);
                 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
@@ -2209,6 +2215,7 @@
         private UiEventLogger mUiEventLogger;
         private GestureDetector mGestureDetector;
         private Optional<CentralSurfaces> mCentralSurfacesOptional;
+        private final ShadeController mShadeController;
         private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
         private LockPatternUtils mLockPatternUtils;
         private float mWindowDimAmount;
@@ -2282,6 +2289,7 @@
                 Runnable onRefreshCallback, boolean keyguardShowing,
                 MyPowerOptionsAdapter powerAdapter, UiEventLogger uiEventLogger,
                 Optional<CentralSurfaces> centralSurfacesOptional,
+                ShadeController shadeController,
                 KeyguardUpdateMonitor keyguardUpdateMonitor,
                 LockPatternUtils lockPatternUtils) {
             // We set dismissOnDeviceLock to false because we have a custom broadcast receiver to
@@ -2299,6 +2307,7 @@
             mKeyguardShowing = keyguardShowing;
             mUiEventLogger = uiEventLogger;
             mCentralSurfacesOptional = centralSurfacesOptional;
+            mShadeController = shadeController;
             mKeyguardUpdateMonitor = keyguardUpdateMonitor;
             mLockPatternUtils = lockPatternUtils;
             mGestureDetector = new GestureDetector(mContext, mGestureListener);
@@ -2348,12 +2357,10 @@
             mUiEventLogger.log(GlobalActionsEvent.GA_CLOSE_TAP_OUTSIDE);
             if (mCentralSurfacesOptional.map(CentralSurfaces::isKeyguardShowing).orElse(false)) {
                 // match existing lockscreen behavior to open QS when swiping from status bar
-                mCentralSurfacesOptional.ifPresent(
-                        centralSurfaces -> centralSurfaces.animateExpandSettingsPanel(null));
+                mShadeController.animateExpandQs();
             } else {
                 // otherwise, swiping down should expand notification shade
-                mCentralSurfacesOptional.ifPresent(
-                        centralSurfaces -> centralSurfaces.animateExpandNotificationsPanel());
+                mShadeController.animateExpandShade();
             }
             dismiss();
         }
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java
index 290bf0d..c5027cc 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java
@@ -15,27 +15,12 @@
 package com.android.systemui.globalactions;
 
 import static android.app.StatusBarManager.DISABLE2_GLOBAL_ACTIONS;
-import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
 
-import android.annotation.Nullable;
-import android.annotation.StringRes;
-import android.app.Dialog;
 import android.content.Context;
-import android.os.PowerManager;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.Window;
-import android.view.WindowManager;
-import android.widget.ProgressBar;
-import android.widget.TextView;
 
-import com.android.internal.R;
-import com.android.settingslib.Utils;
 import com.android.systemui.plugins.GlobalActions;
-import com.android.systemui.scrim.ScrimDrawable;
 import com.android.systemui.statusbar.BlurUtils;
 import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.phone.ScrimController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
@@ -50,12 +35,14 @@
     private final CommandQueue mCommandQueue;
     private final GlobalActionsDialogLite mGlobalActionsDialog;
     private boolean mDisabled;
+    private ShutdownUi mShutdownUi;
 
     @Inject
     public GlobalActionsImpl(Context context, CommandQueue commandQueue,
             GlobalActionsDialogLite globalActionsDialog, BlurUtils blurUtils,
             KeyguardStateController keyguardStateController,
-            DeviceProvisionedController deviceProvisionedController) {
+            DeviceProvisionedController deviceProvisionedController,
+            ShutdownUi shutdownUi) {
         mContext = context;
         mGlobalActionsDialog = globalActionsDialog;
         mKeyguardStateController = keyguardStateController;
@@ -63,6 +50,7 @@
         mCommandQueue = commandQueue;
         mBlurUtils = blurUtils;
         mCommandQueue.addCallback(this);
+        mShutdownUi = shutdownUi;
     }
 
     @Override
@@ -80,103 +68,8 @@
 
     @Override
     public void showShutdownUi(boolean isReboot, String reason) {
-        ScrimDrawable background = new ScrimDrawable();
-
-        final Dialog d = new Dialog(mContext,
-                com.android.systemui.R.style.Theme_SystemUI_Dialog_GlobalActions);
-
-        d.setOnShowListener(dialog -> {
-            if (mBlurUtils.supportsBlursOnWindows()) {
-                int backgroundAlpha = (int) (ScrimController.BUSY_SCRIM_ALPHA * 255);
-                background.setAlpha(backgroundAlpha);
-                mBlurUtils.applyBlur(d.getWindow().getDecorView().getViewRootImpl(),
-                        (int) mBlurUtils.blurRadiusOfRatio(1), backgroundAlpha == 255);
-            } else {
-                float backgroundAlpha = mContext.getResources().getFloat(
-                        com.android.systemui.R.dimen.shutdown_scrim_behind_alpha);
-                background.setAlpha((int) (backgroundAlpha * 255));
-            }
-        });
-
-        // Window initialization
-        Window window = d.getWindow();
-        window.requestFeature(Window.FEATURE_NO_TITLE);
-        window.getAttributes().systemUiVisibility |= View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
-                | View.SYSTEM_UI_FLAG_LAYOUT_STABLE
-                | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
-        // Inflate the decor view, so the attributes below are not overwritten by the theme.
-        window.getDecorView();
-        window.getAttributes().width = ViewGroup.LayoutParams.MATCH_PARENT;
-        window.getAttributes().height = ViewGroup.LayoutParams.MATCH_PARENT;
-        window.getAttributes().layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
-        window.setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY);
-        window.getAttributes().setFitInsetsTypes(0 /* types */);
-        window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
-        window.addFlags(
-                WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
-                        | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
-                        | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR
-                        | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
-                        | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
-                        | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
-        window.setBackgroundDrawable(background);
-        window.setWindowAnimations(com.android.systemui.R.style.Animation_ShutdownUi);
-
-        d.setContentView(R.layout.shutdown_dialog);
-        d.setCancelable(false);
-
-        int color;
-        if (mBlurUtils.supportsBlursOnWindows()) {
-            color = Utils.getColorAttrDefaultColor(mContext,
-                    com.android.systemui.R.attr.wallpaperTextColor);
-        } else {
-            color = mContext.getResources().getColor(
-                    com.android.systemui.R.color.global_actions_shutdown_ui_text);
-        }
-
-        ProgressBar bar = d.findViewById(R.id.progress);
-        bar.getIndeterminateDrawable().setTint(color);
-
-        TextView reasonView = d.findViewById(R.id.text1);
-        TextView messageView = d.findViewById(R.id.text2);
-
-        reasonView.setTextColor(color);
-        messageView.setTextColor(color);
-
-        messageView.setText(getRebootMessage(isReboot, reason));
-        String rebootReasonMessage = getReasonMessage(reason);
-        if (rebootReasonMessage != null) {
-            reasonView.setVisibility(View.VISIBLE);
-            reasonView.setText(rebootReasonMessage);
-        }
-
-        d.show();
+        mShutdownUi.showShutdownUi(isReboot, reason);
     }
-
-    @StringRes
-    private int getRebootMessage(boolean isReboot, @Nullable String reason) {
-        if (reason != null && reason.startsWith(PowerManager.REBOOT_RECOVERY_UPDATE)) {
-            return R.string.reboot_to_update_reboot;
-        } else if (reason != null && reason.equals(PowerManager.REBOOT_RECOVERY)) {
-            return R.string.reboot_to_reset_message;
-        } else if (isReboot) {
-            return R.string.reboot_to_reset_message;
-        } else {
-            return R.string.shutdown_progress;
-        }
-    }
-
-    @Nullable
-    private String getReasonMessage(@Nullable String reason) {
-        if (reason != null && reason.startsWith(PowerManager.REBOOT_RECOVERY_UPDATE)) {
-            return mContext.getString(R.string.reboot_to_update_title);
-        } else if (reason != null && reason.equals(PowerManager.REBOOT_RECOVERY)) {
-            return mContext.getString(R.string.reboot_to_reset_title);
-        } else {
-            return null;
-        }
-    }
-
     @Override
     public void disable(int displayId, int state1, int state2, boolean animate) {
         final boolean disabled = (state2 & DISABLE2_GLOBAL_ACTIONS) != 0;
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/ShutdownUi.java b/packages/SystemUI/src/com/android/systemui/globalactions/ShutdownUi.java
new file mode 100644
index 0000000..68dc1b3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/ShutdownUi.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.globalactions;
+
+import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+
+import android.annotation.Nullable;
+import android.annotation.StringRes;
+import android.app.Dialog;
+import android.content.Context;
+import android.os.PowerManager;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.view.WindowManager;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+
+import androidx.annotation.VisibleForTesting;
+
+import com.android.internal.R;
+import com.android.settingslib.Utils;
+import com.android.systemui.scrim.ScrimDrawable;
+import com.android.systemui.statusbar.BlurUtils;
+import com.android.systemui.statusbar.phone.ScrimController;
+
+/**
+ * Provides the UI shown during system shutdown.
+ */
+public class ShutdownUi {
+
+    private Context mContext;
+    private BlurUtils mBlurUtils;
+    public ShutdownUi(Context context, BlurUtils blurUtils) {
+        mContext = context;
+        mBlurUtils = blurUtils;
+    }
+
+    /**
+     * Display the shutdown UI.
+     * @param isReboot Whether the device will be rebooting after this shutdown.
+     * @param reason Cause for the shutdown.
+     */
+    public void showShutdownUi(boolean isReboot, String reason) {
+        ScrimDrawable background = new ScrimDrawable();
+
+        final Dialog d = new Dialog(mContext,
+                com.android.systemui.R.style.Theme_SystemUI_Dialog_GlobalActions);
+
+        d.setOnShowListener(dialog -> {
+            if (mBlurUtils.supportsBlursOnWindows()) {
+                int backgroundAlpha = (int) (ScrimController.BUSY_SCRIM_ALPHA * 255);
+                background.setAlpha(backgroundAlpha);
+                mBlurUtils.applyBlur(d.getWindow().getDecorView().getViewRootImpl(),
+                        (int) mBlurUtils.blurRadiusOfRatio(1), backgroundAlpha == 255);
+            } else {
+                float backgroundAlpha = mContext.getResources().getFloat(
+                        com.android.systemui.R.dimen.shutdown_scrim_behind_alpha);
+                background.setAlpha((int) (backgroundAlpha * 255));
+            }
+        });
+
+        // Window initialization
+        Window window = d.getWindow();
+        window.requestFeature(Window.FEATURE_NO_TITLE);
+        window.getAttributes().systemUiVisibility |= View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+                | View.SYSTEM_UI_FLAG_LAYOUT_STABLE
+                | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
+        // Inflate the decor view, so the attributes below are not overwritten by the theme.
+        window.getDecorView();
+        window.getAttributes().width = ViewGroup.LayoutParams.MATCH_PARENT;
+        window.getAttributes().height = ViewGroup.LayoutParams.MATCH_PARENT;
+        window.getAttributes().layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+        window.setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY);
+        window.getAttributes().setFitInsetsTypes(0 /* types */);
+        window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
+        window.addFlags(
+                WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+                        | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+                        | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR
+                        | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
+                        | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
+                        | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
+        window.setBackgroundDrawable(background);
+        window.setWindowAnimations(com.android.systemui.R.style.Animation_ShutdownUi);
+
+        d.setContentView(getShutdownDialogContent(isReboot));
+        d.setCancelable(false);
+
+        int color;
+        if (mBlurUtils.supportsBlursOnWindows()) {
+            color = Utils.getColorAttrDefaultColor(mContext,
+                    com.android.systemui.R.attr.wallpaperTextColor);
+        } else {
+            color = mContext.getResources().getColor(
+                    com.android.systemui.R.color.global_actions_shutdown_ui_text);
+        }
+
+        ProgressBar bar = d.findViewById(R.id.progress);
+        bar.getIndeterminateDrawable().setTint(color);
+
+        TextView reasonView = d.findViewById(R.id.text1);
+        TextView messageView = d.findViewById(R.id.text2);
+
+        reasonView.setTextColor(color);
+        messageView.setTextColor(color);
+
+        messageView.setText(getRebootMessage(isReboot, reason));
+        String rebootReasonMessage = getReasonMessage(reason);
+        if (rebootReasonMessage != null) {
+            reasonView.setVisibility(View.VISIBLE);
+            reasonView.setText(rebootReasonMessage);
+        }
+
+        d.show();
+    }
+
+    /**
+     * Returns the layout resource to use for UI while shutting down.
+     * @param isReboot Whether this is a reboot or a shutdown.
+     * @return
+     */
+    public int getShutdownDialogContent(boolean isReboot) {
+        return R.layout.shutdown_dialog;
+    }
+
+    @StringRes
+    @VisibleForTesting int getRebootMessage(boolean isReboot, @Nullable String reason) {
+        if (reason != null && reason.startsWith(PowerManager.REBOOT_RECOVERY_UPDATE)) {
+            return R.string.reboot_to_update_reboot;
+        } else if (reason != null && reason.equals(PowerManager.REBOOT_RECOVERY)) {
+            return R.string.reboot_to_reset_message;
+        } else if (isReboot) {
+            return R.string.reboot_to_reset_message;
+        } else {
+            return R.string.shutdown_progress;
+        }
+    }
+
+    @Nullable
+    @VisibleForTesting String getReasonMessage(@Nullable String reason) {
+        if (reason != null && reason.startsWith(PowerManager.REBOOT_RECOVERY_UPDATE)) {
+            return mContext.getString(R.string.reboot_to_update_title);
+        } else if (reason != null && reason.equals(PowerManager.REBOOT_RECOVERY)) {
+            return mContext.getString(R.string.reboot_to_reset_title);
+        } else {
+            return null;
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/ShutdownUiModule.kt b/packages/SystemUI/src/com/android/systemui/globalactions/ShutdownUiModule.kt
new file mode 100644
index 0000000..b7285da
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/ShutdownUiModule.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.globalactions
+
+import android.content.Context
+import com.android.systemui.statusbar.BlurUtils
+import dagger.Module
+import dagger.Provides
+
+/** Provides the UI shown during system shutdown. */
+@Module
+class ShutdownUiModule {
+    /** Shutdown UI provider. */
+    @Provides
+    fun provideShutdownUi(context: Context?, blurUtils: BlurUtils?): ShutdownUi {
+        return ShutdownUi(context, blurUtils)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardLifecyclesDispatcher.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardLifecyclesDispatcher.java
index 757afb6..2560fa7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardLifecyclesDispatcher.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardLifecyclesDispatcher.java
@@ -16,11 +16,19 @@
 
 package com.android.systemui.keyguard;
 
+import android.annotation.IntDef;
 import android.os.Handler;
+import android.os.Looper;
 import android.os.Message;
-import android.os.Trace;
+import android.os.TraceNameSupplier;
+
+import androidx.annotation.NonNull;
 
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 
 import javax.inject.Inject;
 
@@ -29,29 +37,55 @@
  */
 @SysUISingleton
 public class KeyguardLifecyclesDispatcher {
+    public static final int SCREEN_TURNING_ON = 0;
+    public static final int SCREEN_TURNED_ON = 1;
+    public static final int SCREEN_TURNING_OFF = 2;
+    public static final int SCREEN_TURNED_OFF = 3;
 
-    static final int SCREEN_TURNING_ON = 0;
-    static final int SCREEN_TURNED_ON = 1;
-    static final int SCREEN_TURNING_OFF = 2;
-    static final int SCREEN_TURNED_OFF = 3;
+    public static final int STARTED_WAKING_UP = 4;
+    public static final int FINISHED_WAKING_UP = 5;
+    public static final int STARTED_GOING_TO_SLEEP = 6;
+    public static final int FINISHED_GOING_TO_SLEEP = 7;
 
-    static final int STARTED_WAKING_UP = 4;
-    static final int FINISHED_WAKING_UP = 5;
-    static final int STARTED_GOING_TO_SLEEP = 6;
-    static final int FINISHED_GOING_TO_SLEEP = 7;
-    private static final String TAG = "KeyguardLifecyclesDispatcher";
-
-    private final ScreenLifecycle mScreenLifecycle;
-    private final WakefulnessLifecycle mWakefulnessLifecycle;
-
-    @Inject
-    public KeyguardLifecyclesDispatcher(ScreenLifecycle screenLifecycle,
-            WakefulnessLifecycle wakefulnessLifecycle) {
-        mScreenLifecycle = screenLifecycle;
-        mWakefulnessLifecycle = wakefulnessLifecycle;
+    @IntDef({
+            SCREEN_TURNING_ON,
+            SCREEN_TURNED_ON,
+            SCREEN_TURNING_OFF,
+            SCREEN_TURNED_OFF,
+            STARTED_WAKING_UP,
+            FINISHED_WAKING_UP,
+            STARTED_GOING_TO_SLEEP,
+            FINISHED_GOING_TO_SLEEP,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface KeyguardLifecycleMessageType {
     }
 
-    void dispatch(int what) {
+    private static String getNameOfMessage(@KeyguardLifecycleMessageType int what) {
+        return switch (what) {
+            case SCREEN_TURNING_ON -> "SCREEN_TURNING_ON";
+            case SCREEN_TURNED_ON -> "SCREEN_TURNED_ON";
+            case SCREEN_TURNING_OFF -> "SCREEN_TURNING_OFF";
+            case SCREEN_TURNED_OFF -> "SCREEN_TURNED_OFF";
+            case STARTED_WAKING_UP -> "STARTED_WAKING_UP";
+            case FINISHED_WAKING_UP -> "FINISHED_WAKING_UP";
+            case STARTED_GOING_TO_SLEEP -> "STARTED_GOING_TO_SLEEP";
+            case FINISHED_GOING_TO_SLEEP -> "FINISHED_GOING_TO_SLEEP";
+            default -> "UNKNOWN";
+        };
+    }
+
+    private final Handler mHandler;
+
+    @Inject
+    public KeyguardLifecyclesDispatcher(
+            @Main Looper mainLooper,
+            ScreenLifecycle screenLifecycle,
+            WakefulnessLifecycle wakefulnessLifecycle) {
+        mHandler = new KeyguardLifecycleHandler(mainLooper, screenLifecycle, wakefulnessLifecycle);
+    }
+
+    protected void dispatch(@KeyguardLifecycleMessageType int what) {
         mHandler.obtainMessage(what).sendToTarget();
     }
 
@@ -60,7 +94,7 @@
      * @param pmReason Reason this message was triggered - this should be a value from either
      * {@link PowerManager.WakeReason} or {@link PowerManager.GoToSleepReason}.
      */
-    void dispatch(int what, int pmReason) {
+    protected void dispatch(@KeyguardLifecycleMessageType int what, int pmReason) {
         final Message message = mHandler.obtainMessage(what);
         message.arg1 = pmReason;
         message.sendToTarget();
@@ -70,44 +104,48 @@
      * @param what Message to send.
      * @param object Object to send with the message
      */
-    void dispatch(int what, Object object) {
+    protected void dispatch(@KeyguardLifecycleMessageType int what, Object object) {
         mHandler.obtainMessage(what, object).sendToTarget();
     }
 
-    private Handler mHandler = new Handler() {
+    private static class KeyguardLifecycleHandler extends Handler {
+        private static final String TAG = "KeyguardLifecycleHandler";
+        private final ScreenLifecycle mScreenLifecycle;
+        private final WakefulnessLifecycle mWakefulnessLifecycle;
+
+        public KeyguardLifecycleHandler(Looper looper,
+                                         ScreenLifecycle screenLifecycle,
+                                         WakefulnessLifecycle wakefulnessLifecycle) {
+            super(looper);
+            mScreenLifecycle = screenLifecycle;
+            mWakefulnessLifecycle = wakefulnessLifecycle;
+        }
+
+        @NonNull
         @Override
-        public void handleMessage(Message msg) {
+        public String getTraceName(@NonNull Message msg) {
+            if (msg.getCallback() instanceof TraceNameSupplier || msg.getCallback() != null) {
+                return super.getTraceName(msg);
+            }
+            return TAG + "#" + getNameOfMessage(msg.what);
+        }
+
+        @Override
+        public void handleMessage(@NonNull Message msg) {
             switch (msg.what) {
-                case SCREEN_TURNING_ON:
-                    Trace.beginSection("KeyguardLifecyclesDispatcher#SCREEN_TURNING_ON");
-                    mScreenLifecycle.dispatchScreenTurningOn();
-                    Trace.endSection();
-                    break;
-                case SCREEN_TURNED_ON:
-                    mScreenLifecycle.dispatchScreenTurnedOn();
-                    break;
-                case SCREEN_TURNING_OFF:
-                    mScreenLifecycle.dispatchScreenTurningOff();
-                    break;
-                case SCREEN_TURNED_OFF:
-                    mScreenLifecycle.dispatchScreenTurnedOff();
-                    break;
-                case STARTED_WAKING_UP:
-                    mWakefulnessLifecycle.dispatchStartedWakingUp(msg.arg1 /* pmReason */);
-                    break;
-                case FINISHED_WAKING_UP:
-                    mWakefulnessLifecycle.dispatchFinishedWakingUp();
-                    break;
-                case STARTED_GOING_TO_SLEEP:
-                    mWakefulnessLifecycle.dispatchStartedGoingToSleep(msg.arg1 /* pmReason */);
-                    break;
-                case FINISHED_GOING_TO_SLEEP:
-                    mWakefulnessLifecycle.dispatchFinishedGoingToSleep();
-                    break;
-                default:
-                    throw new IllegalArgumentException("Unknown message: " + msg);
+                case SCREEN_TURNING_ON -> mScreenLifecycle.dispatchScreenTurningOn();
+                case SCREEN_TURNED_ON -> mScreenLifecycle.dispatchScreenTurnedOn();
+                case SCREEN_TURNING_OFF -> mScreenLifecycle.dispatchScreenTurningOff();
+                case SCREEN_TURNED_OFF -> mScreenLifecycle.dispatchScreenTurnedOff();
+                case STARTED_WAKING_UP ->
+                        mWakefulnessLifecycle.dispatchStartedWakingUp(msg.arg1 /* pmReason */);
+                case FINISHED_WAKING_UP -> mWakefulnessLifecycle.dispatchFinishedWakingUp();
+                case STARTED_GOING_TO_SLEEP ->
+                        mWakefulnessLifecycle.dispatchStartedGoingToSleep(msg.arg1 /* pmReason */);
+                case FINISHED_GOING_TO_SLEEP ->
+                        mWakefulnessLifecycle.dispatchFinishedGoingToSleep();
+                default -> throw new IllegalArgumentException("Unknown message: " + msg);
             }
         }
-    };
-
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
index 573de97..732102e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
@@ -49,7 +49,8 @@
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.systemui.R;
-import com.android.systemui.SystemUIAppComponentFactory;
+import com.android.systemui.SystemUIAppComponentFactoryBase;
+import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.NotificationMediaManager;
@@ -72,13 +73,13 @@
 /**
  * Simple Slice provider that shows the current date.
  *
- * Injection is handled by {@link SystemUIAppComponentFactory} +
+ * Injection is handled by {@link SystemUIAppComponentFactoryBase} +
  * {@link com.android.systemui.dagger.GlobalRootComponent#inject(KeyguardSliceProvider)}.
  */
 public class KeyguardSliceProvider extends SliceProvider implements
         NextAlarmController.NextAlarmChangeCallback, ZenModeController.Callback,
         NotificationMediaManager.MediaListener, StatusBarStateController.StateListener,
-        SystemUIAppComponentFactory.ContextInitializer {
+        SystemUIAppComponentFactoryBase.ContextInitializer {
 
     private static final String TAG = "KgdSliceProvider";
 
@@ -148,9 +149,12 @@
     protected boolean mDozing;
     private int mStatusBarState;
     private boolean mMediaIsVisible;
-    private SystemUIAppComponentFactory.ContextAvailableCallback mContextAvailableCallback;
+    private SystemUIAppComponentFactoryBase.ContextAvailableCallback mContextAvailableCallback;
     @Inject
     WakeLockLogger mWakeLockLogger;
+    @Inject
+    @Background
+    Handler mBgHandler;
 
     /**
      * Receiver responsible for time ticking and updating the date format.
@@ -502,7 +506,7 @@
     }
 
     protected void notifyChange() {
-        mContentResolver.notifyChange(mSliceUri, null /* observer */);
+        mBgHandler.post(() -> mContentResolver.notifyChange(mSliceUri, null /* observer */));
     }
 
     @Override
@@ -533,7 +537,7 @@
 
     @Override
     public void setContextAvailableCallback(
-            SystemUIAppComponentFactory.ContextAvailableCallback callback) {
+            SystemUIAppComponentFactoryBase.ContextAvailableCallback callback) {
         mContextAvailableCallback = callback;
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
index 68e72c5..3646144 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
@@ -906,10 +906,7 @@
     }
 
     fun setWallpaperAppearAmount(amount: Float) {
-        val animationAlpha = when {
-            !powerManager.isInteractive -> 0f
-            else -> amount
-        }
+        val animationAlpha = amount
 
         wallpaperTargets?.forEach { wallpaper ->
             // SyncRtSurfaceTransactionApplier cannot apply transaction when the target view is
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
new file mode 100644
index 0000000..23f6fa6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
@@ -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
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard
+
+import android.view.View
+import android.view.ViewGroup
+import com.android.systemui.CoreStartable
+import com.android.systemui.R
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.ui.binder.KeyguardIndicationAreaBinder
+import com.android.systemui.keyguard.ui.view.KeyguardRootView
+import com.android.systemui.keyguard.ui.view.layout.KeyguardLayoutManager
+import com.android.systemui.keyguard.ui.view.layout.KeyguardLayoutManagerCommandListener
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardIndicationAreaViewModel
+import com.android.systemui.shade.NotificationShadeWindowView
+import com.android.systemui.statusbar.KeyguardIndicationController
+import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
+import com.android.systemui.statusbar.notification.stack.ui.viewbinder.SharedNotificationContainerBinder
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.SharedNotificationContainerViewModel
+import javax.inject.Inject
+import kotlinx.coroutines.DisposableHandle
+
+/** Binds keyguard views on startup, and also exposes methods to allow rebinding if views change */
+@SysUISingleton
+class KeyguardViewConfigurator
+@Inject
+constructor(
+    private val keyguardRootView: KeyguardRootView,
+    private val sharedNotificationContainer: SharedNotificationContainer,
+    private val keyguardIndicationAreaViewModel: KeyguardIndicationAreaViewModel,
+    private val sharedNotificationContainerViewModel: SharedNotificationContainerViewModel,
+    private val notificationShadeWindowView: NotificationShadeWindowView,
+    private val featureFlags: FeatureFlags,
+    private val indicationController: KeyguardIndicationController,
+    private val keyguardLayoutManager: KeyguardLayoutManager,
+    private val keyguardLayoutManagerCommandListener: KeyguardLayoutManagerCommandListener,
+) : CoreStartable {
+
+    private var indicationAreaHandle: DisposableHandle? = null
+
+    override fun start() {
+        val notificationPanel =
+            notificationShadeWindowView.requireViewById(R.id.notification_panel) as ViewGroup
+        bindIndicationArea(notificationPanel)
+        bindLockIconView(notificationPanel)
+        setupNotificationStackScrollLayout(notificationPanel)
+
+        keyguardLayoutManager.layoutViews()
+        keyguardLayoutManagerCommandListener.start()
+    }
+
+    fun setupNotificationStackScrollLayout(legacyParent: ViewGroup) {
+        if (featureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
+            // This moves the existing NSSL view to a different parent, as the controller is a
+            // singleton and recreating it has other bad side effects
+            val nssl =
+                legacyParent.requireViewById<View>(R.id.notification_stack_scroller).also {
+                    (it.getParent() as ViewGroup).removeView(it)
+                }
+            sharedNotificationContainer.addNotificationStackScrollLayout(nssl)
+            SharedNotificationContainerBinder.bind(
+                sharedNotificationContainer,
+                sharedNotificationContainerViewModel
+            )
+        }
+    }
+
+    fun bindIndicationArea(legacyParent: ViewGroup) {
+        indicationAreaHandle?.dispose()
+
+        // At startup, 2 views with the ID `R.id.keyguard_indication_area` will be available.
+        // Disable one of them
+        if (featureFlags.isEnabled(Flags.MIGRATE_INDICATION_AREA)) {
+            legacyParent.findViewById<View>(R.id.keyguard_indication_area)?.let {
+                legacyParent.removeView(it)
+            }
+        } else {
+            keyguardRootView.findViewById<View?>(R.id.keyguard_indication_area)?.let {
+                keyguardRootView.removeView(it)
+            }
+        }
+
+        indicationAreaHandle =
+            KeyguardIndicationAreaBinder.bind(
+                notificationShadeWindowView,
+                keyguardIndicationAreaViewModel,
+                indicationController
+            )
+    }
+
+    private fun bindLockIconView(legacyParent: ViewGroup) {
+        if (featureFlags.isEnabled(Flags.MIGRATE_LOCK_ICON)) {
+            legacyParent.requireViewById<View>(R.id.lock_icon_view).let {
+                legacyParent.removeView(it)
+            }
+        } else {
+            keyguardRootView.findViewById<View?>(R.id.lock_icon_view)?.let {
+                keyguardRootView.removeView(it)
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 2d82c50..ddd4a2b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -17,6 +17,8 @@
 package com.android.systemui.keyguard;
 
 import static android.app.StatusBarManager.SESSION_KEYGUARD;
+import static android.provider.Settings.Secure.LOCK_SCREEN_LOCK_AFTER_TIMEOUT;
+import static android.provider.Settings.System.LOCKSCREEN_SOUNDS_ENABLED;
 import static android.provider.Settings.System.SCREEN_OFF_TIMEOUT;
 import static android.view.RemoteAnimationTarget.MODE_OPENING;
 import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_NO_WINDOW_ANIMATIONS;
@@ -42,9 +44,9 @@
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
-import android.app.ActivityTaskManager;
 import android.app.AlarmManager;
 import android.app.BroadcastOptions;
+import android.app.IActivityTaskManager;
 import android.app.PendingIntent;
 import android.app.StatusBarManager;
 import android.app.WallpaperManager;
@@ -73,7 +75,6 @@
 import android.os.PowerManager;
 import android.os.RemoteException;
 import android.os.ServiceManager;
-import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.Trace;
 import android.os.UserHandle;
@@ -162,6 +163,11 @@
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.UserSwitcherController;
 import com.android.systemui.util.DeviceConfigProxy;
+import com.android.systemui.util.kotlin.JavaAdapter;
+import com.android.systemui.util.settings.SecureSettings;
+import com.android.systemui.util.settings.SystemSettings;
+import com.android.systemui.util.time.SystemClock;
+import com.android.systemui.wallpapers.data.repository.WallpaperRepository;
 import com.android.wm.shell.keyguard.KeyguardTransitions;
 
 import dagger.Lazy;
@@ -226,7 +232,7 @@
 
     private final static String TAG = "KeyguardViewMediator";
 
-    private static final String DELAYED_KEYGUARD_ACTION =
+    public static final String DELAYED_KEYGUARD_ACTION =
         "com.android.internal.policy.impl.PhoneWindowManager.DELAYED_KEYGUARD";
     private static final String DELAYED_LOCK_PROFILE_ACTION =
             "com.android.internal.policy.impl.PhoneWindowManager.DELAYED_LOCK";
@@ -261,7 +267,7 @@
      * turning on the keyguard (i.e, the user has this much time to turn
      * the screen back on without having to face the keyguard).
      */
-    private static final int KEYGUARD_LOCK_AFTER_DELAY_DEFAULT = 5000;
+    public static final int KEYGUARD_LOCK_AFTER_DELAY_DEFAULT = 5000;
 
     /**
      * How long we'll wait for the {@link ViewMediatorCallback#keyguardDoneDrawing()}
@@ -286,6 +292,8 @@
     public static final String SYS_BOOT_REASON_PROP = "sys.boot.reason.last";
     public static final String REBOOT_MAINLINE_UPDATE = "reboot,mainline_update";
     private final DreamOverlayStateController mDreamOverlayStateController;
+    private final JavaAdapter mJavaAdapter;
+    private final WallpaperRepository mWallpaperRepository;
 
     /** The stream type that the lock sounds are tied to. */
     private int mUiSoundsStreamType;
@@ -320,6 +328,9 @@
 
     /** UserSwitcherController for creating guest user on boot complete */
     private final UserSwitcherController mUserSwitcherController;
+    private final SecureSettings mSecureSettings;
+    private final SystemSettings mSystemSettings;
+    private final SystemClock mSystemClock;
     private SystemPropertiesHelper mSystemPropertiesHelper;
 
     /**
@@ -578,17 +589,9 @@
         @Override
         public void onUserSwitching(int userId) {
             if (DEBUG) Log.d(TAG, String.format("onUserSwitching %d", userId));
-            // Note that the mLockPatternUtils user has already been updated from setCurrentUser.
-            // We need to force a reset of the views, since lockNow (called by
-            // ActivityManagerService) will not reconstruct the keyguard if it is already showing.
             synchronized (KeyguardViewMediator.this) {
                 resetKeyguardDonePendingLocked();
-                if (mLockPatternUtils.isLockScreenDisabled(userId)) {
-                    // If we are switching to a user that has keyguard disabled, dismiss keyguard.
-                    dismiss(null /* callback */, null /* message */);
-                } else {
-                    resetStateLocked();
-                }
+                dismiss(null /* callback */, null /* message */);
                 adjustStatusBarLocked();
             }
         }
@@ -596,16 +599,9 @@
         @Override
         public void onUserSwitchComplete(int userId) {
             if (DEBUG) Log.d(TAG, String.format("onUserSwitchComplete %d", userId));
-            if (userId != UserHandle.USER_SYSTEM) {
-                UserInfo info = UserManager.get(mContext).getUserInfo(userId);
-                // Don't try to dismiss if the user has Pin/Pattern/Password set
-                if (info == null || mLockPatternUtils.isSecure(userId)) {
-                    return;
-                } else if (info.isGuest() || info.isDemo()) {
-                    // If we just switched to a guest, try to dismiss keyguard.
-                    dismiss(null /* callback */, null /* message */);
-                }
-            }
+            // We are calling dismiss again and with a delay as there are race conditions
+            // in some scenarios caused by async layout listeners
+            mHandler.postDelayed(() -> dismiss(null /* callback */, null /* message */), 500);
         }
 
         @Override
@@ -830,6 +826,11 @@
         }
 
         @Override
+        public void onBouncerSwipeDown() {
+            mKeyguardViewControllerLazy.get().reset(/* hideBouncerWhenShowing= */ true);
+        }
+
+        @Override
         public void playTrustedSound() {
             KeyguardViewMediator.this.playTrustedSound();
         }
@@ -1286,6 +1287,7 @@
 
     private Lazy<ActivityLaunchAnimator> mActivityLaunchAnimator;
     private Lazy<ScrimController> mScrimControllerLazy;
+    private IActivityTaskManager mActivityTaskManagerService;
 
     private FeatureFlags mFeatureFlags;
     private final UiEventLogger mUiEventLogger;
@@ -1325,11 +1327,17 @@
             KeyguardTransitions keyguardTransitions,
             InteractionJankMonitor interactionJankMonitor,
             DreamOverlayStateController dreamOverlayStateController,
+            JavaAdapter javaAdapter,
+            WallpaperRepository wallpaperRepository,
             Lazy<ShadeController> shadeControllerLazy,
             Lazy<NotificationShadeWindowController> notificationShadeWindowControllerLazy,
             Lazy<ActivityLaunchAnimator> activityLaunchAnimator,
             Lazy<ScrimController> scrimControllerLazy,
+            IActivityTaskManager activityTaskManagerService,
             FeatureFlags featureFlags,
+            SecureSettings secureSettings,
+            SystemSettings systemSettings,
+            SystemClock systemClock,
             @Main CoroutineDispatcher mainDispatcher,
             Lazy<DreamingToLockscreenTransitionViewModel> dreamingToLockscreenTransitionViewModel,
             SystemPropertiesHelper systemPropertiesHelper) {
@@ -1346,12 +1354,15 @@
         mPM = powerManager;
         mTrustManager = trustManager;
         mUserSwitcherController = userSwitcherController;
+        mSecureSettings = secureSettings;
+        mSystemSettings = systemSettings;
+        mSystemClock = systemClock;
         mSystemPropertiesHelper = systemPropertiesHelper;
         mStatusBarService = IStatusBarService.Stub.asInterface(
                 ServiceManager.getService(Context.STATUS_BAR_SERVICE));
         mKeyguardDisplayManager = keyguardDisplayManager;
         mShadeController = shadeControllerLazy;
-        dumpManager.registerDumpable(getClass().getName(), this);
+        dumpManager.registerDumpable(this);
         mDeviceConfig = deviceConfig;
         mScreenOnCoordinator = screenOnCoordinator;
         mKeyguardTransitions = keyguardTransitions;
@@ -1379,9 +1390,12 @@
         mScreenOffAnimationController = screenOffAnimationController;
         mInteractionJankMonitor = interactionJankMonitor;
         mDreamOverlayStateController = dreamOverlayStateController;
+        mJavaAdapter = javaAdapter;
+        mWallpaperRepository = wallpaperRepository;
 
         mActivityLaunchAnimator = activityLaunchAnimator;
         mScrimControllerLazy = scrimControllerLazy;
+        mActivityTaskManagerService = activityTaskManagerService;
 
         mPowerButtonY = context.getResources().getDimensionPixelSize(
                 R.dimen.physical_power_button_center_screen_location_y);
@@ -1398,7 +1412,7 @@
     }
 
     public void userActivity() {
-        mPM.userActivity(SystemClock.uptimeMillis(), false);
+        mPM.userActivity(mSystemClock.uptimeMillis(), false);
     }
 
     private void setupLocked() {
@@ -1481,6 +1495,10 @@
                 com.android.internal.R.anim.lock_screen_behind_enter);
 
         mWorkLockController = new WorkLockActivityController(mContext, mUserTracker);
+
+        mJavaAdapter.alwaysCollectFlow(
+                mWallpaperRepository.getWallpaperSupportsAmbientMode(),
+                this::setWallpaperSupportsAmbientMode);
     }
 
     // TODO(b/273443374) remove, temporary util to get a feature flag
@@ -1603,7 +1621,7 @@
 
             if (cameraGestureTriggered) {
                 // Just to make sure, make sure the device is awake.
-                mContext.getSystemService(PowerManager.class).wakeUp(SystemClock.uptimeMillis(),
+                mContext.getSystemService(PowerManager.class).wakeUp(mSystemClock.uptimeMillis(),
                         PowerManager.WAKE_REASON_CAMERA_LAUNCH,
                         "com.android.systemui:CAMERA_GESTURE_PREVENT_LOCK");
                 setPendingLock(false);
@@ -1700,12 +1718,11 @@
         // to enable it a bit later (i.e, give the user a chance
         // to turn the screen back on within a certain window without
         // having to unlock the screen)
-        final ContentResolver cr = mContext.getContentResolver();
 
         // From SecuritySettings
-        final long lockAfterTimeout = Settings.Secure.getIntForUser(cr,
-                Settings.Secure.LOCK_SCREEN_LOCK_AFTER_TIMEOUT,
-                KEYGUARD_LOCK_AFTER_DELAY_DEFAULT, userId);
+        final long lockAfterTimeout = mSecureSettings.getIntForUser(LOCK_SCREEN_LOCK_AFTER_TIMEOUT,
+                KEYGUARD_LOCK_AFTER_DELAY_DEFAULT,
+                userId);
 
         // From DevicePolicyAdmin
         final long policyTimeout = mLockPatternUtils.getDevicePolicyManager()
@@ -1717,8 +1734,9 @@
             timeout = lockAfterTimeout;
         } else {
             // From DisplaySettings
-            long displayTimeout = Settings.System.getIntForUser(cr, SCREEN_OFF_TIMEOUT,
-                    KEYGUARD_DISPLAY_TIMEOUT_DELAY_DEFAULT, userId);
+            long displayTimeout = mSystemSettings.getIntForUser(SCREEN_OFF_TIMEOUT,
+                    KEYGUARD_DISPLAY_TIMEOUT_DELAY_DEFAULT,
+                    userId);
 
             // policy in effect. Make sure we don't go beyond policy limit.
             displayTimeout = Math.max(displayTimeout, 0); // ignore negative values
@@ -1739,7 +1757,7 @@
 
     private void doKeyguardLaterLocked(long timeout) {
         // Lock in the future
-        long when = SystemClock.elapsedRealtime() + timeout;
+        long when = mSystemClock.elapsedRealtime() + timeout;
         Intent intent = new Intent(DELAYED_KEYGUARD_ACTION);
         intent.setPackage(mContext.getPackageName());
         intent.putExtra("seq", mDelayedShowingSequence);
@@ -1761,7 +1779,7 @@
                 if (userTimeout == 0) {
                     doKeyguardForChildProfilesLocked();
                 } else {
-                    long userWhen = SystemClock.elapsedRealtime() + userTimeout;
+                    long userWhen = mSystemClock.elapsedRealtime() + userTimeout;
                     Intent lockIntent = new Intent(DELAYED_LOCK_PROFILE_ACTION);
                     lockIntent.setPackage(mContext.getPackageName());
                     lockIntent.putExtra("seq", mDelayedProfileShowingSequence);
@@ -1978,12 +1996,17 @@
     }
 
     /**
-     * Is the keyguard currently showing and not being force hidden?
+     * Is the keyguard currently showing, and not occluded (no activity is drawing over the
+     * lockscreen).
      */
     public boolean isShowingAndNotOccluded() {
         return mShowing && !mOccluded;
     }
 
+    public boolean isShowing() {
+        return mShowing;
+    }
+
     public boolean isOccludeAnimationPlaying() {
         return mOccludeAnimationPlaying;
     }
@@ -2384,58 +2407,72 @@
     private Handler mHandler = new Handler(Looper.myLooper(), null, true /*async*/) {
         @Override
         public void handleMessage(Message msg) {
+            String message = "";
             switch (msg.what) {
                 case SHOW:
+                    message = "SHOW";
                     handleShow((Bundle) msg.obj);
                     break;
                 case HIDE:
+                    message = "HIDE";
                     handleHide();
                     break;
                 case RESET:
+                    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();
                     break;
                 case NOTIFY_FINISHED_GOING_TO_SLEEP:
+                    message = "NOTIFY_FINISHED_GOING_TO_SLEEP";
                     handleNotifyFinishedGoingToSleep();
                     break;
                 case NOTIFY_STARTED_WAKING_UP:
+                    message = "NOTIFY_STARTED_WAKING_UP";
                     Trace.beginSection(
                             "KeyguardViewMediator#handleMessage NOTIFY_STARTED_WAKING_UP");
                     handleNotifyStartedWakingUp();
                     Trace.endSection();
                     break;
                 case KEYGUARD_DONE:
+                    message = "KEYGUARD_DONE";
                     Trace.beginSection("KeyguardViewMediator#handleMessage KEYGUARD_DONE");
                     handleKeyguardDone();
                     Trace.endSection();
                     break;
                 case KEYGUARD_DONE_DRAWING:
+                    message = "KEYGUARD_DONE_DRAWING";
                     Trace.beginSection("KeyguardViewMediator#handleMessage KEYGUARD_DONE_DRAWING");
                     handleKeyguardDoneDrawing();
                     Trace.endSection();
                     break;
                 case SET_OCCLUDED:
+                    message = "SET_OCCLUDED";
                     Trace.beginSection("KeyguardViewMediator#handleMessage SET_OCCLUDED");
                     handleSetOccluded(msg.arg1 != 0, msg.arg2 != 0);
                     Trace.endSection();
                     break;
                 case KEYGUARD_TIMEOUT:
+                    message = "KEYGUARD_TIMEOUT";
                     synchronized (KeyguardViewMediator.this) {
                         doKeyguardLocked((Bundle) msg.obj);
                     }
                     break;
                 case DISMISS:
-                    final DismissMessage message = (DismissMessage) msg.obj;
-                    handleDismiss(message.getCallback(), message.getMessage());
+                    message = "DISMISS";
+                    final DismissMessage dismissMsg = (DismissMessage) msg.obj;
+                    handleDismiss(dismissMsg.getCallback(), dismissMsg.getMessage());
                     break;
                 case START_KEYGUARD_EXIT_ANIM:
+                    message = "START_KEYGUARD_EXIT_ANIM";
                     Trace.beginSection(
                             "KeyguardViewMediator#handleMessage START_KEYGUARD_EXIT_ANIM");
                     synchronized (KeyguardViewMediator.this) {
@@ -2453,21 +2490,25 @@
                     Trace.endSection();
                     break;
                 case CANCEL_KEYGUARD_EXIT_ANIM:
+                    message = "CANCEL_KEYGUARD_EXIT_ANIM";
                     Trace.beginSection(
                             "KeyguardViewMediator#handleMessage CANCEL_KEYGUARD_EXIT_ANIM");
                     handleCancelKeyguardExitAnimation();
                     Trace.endSection();
                     break;
                 case KEYGUARD_DONE_PENDING_TIMEOUT:
+                    message = "KEYGUARD_DONE_PENDING_TIMEOUT";
                     Trace.beginSection("KeyguardViewMediator#handleMessage"
                             + " KEYGUARD_DONE_PENDING_TIMEOUT");
                     Log.w(TAG, "Timeout while waiting for activity drawn!");
                     Trace.endSection();
                     break;
                 case SYSTEM_READY:
+                    message = "SYSTEM_READY";
                     handleSystemReady();
                     break;
             }
+            Log.d(TAG, "KeyguardViewMediator queue processing message: " + message);
         }
     };
 
@@ -2570,9 +2611,7 @@
 
     private void playSound(int soundId) {
         if (soundId == 0) return;
-        final ContentResolver cr = mContext.getContentResolver();
-        int lockscreenSoundsEnabled = Settings.System.getIntForUser(cr,
-                Settings.System.LOCKSCREEN_SOUNDS_ENABLED, 1,
+        int lockscreenSoundsEnabled = mSystemSettings.getIntForUser(LOCKSCREEN_SOUNDS_ENABLED, 1,
                 KeyguardUpdateMonitor.getCurrentUser());
         if (lockscreenSoundsEnabled == 1) {
 
@@ -2608,7 +2647,7 @@
                 Log.d(TAG, "updateActivityLockScreenState(" + showing + ", " + aodShowing + ")");
             }
             try {
-                ActivityTaskManager.getService().setLockScreenShown(showing, aodShowing);
+                mActivityTaskManagerService.setLockScreenShown(showing, aodShowing);
             } catch (RemoteException e) {
             }
         });
@@ -2724,7 +2763,7 @@
             final int keyguardFlag = flags;
             mUiBgExecutor.execute(() -> {
                 try {
-                    ActivityTaskManager.getService().keyguardGoingAway(keyguardFlag);
+                    mActivityTaskManagerService.keyguardGoingAway(keyguardFlag);
                 } catch (RemoteException e) {
                     Log.e(TAG, "Error while calling WindowManager", e);
                 }
@@ -2749,31 +2788,29 @@
         // It's possible that the device was unlocked (via BOUNCER) while dozing. It's time to
         // wake up.
         if (mAodShowing) {
-            mPM.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_GESTURE,
+            mPM.wakeUp(mSystemClock.uptimeMillis(), PowerManager.WAKE_REASON_GESTURE,
                     "com.android.systemui:BOUNCER_DOZING");
         }
 
         synchronized (KeyguardViewMediator.this) {
             if (DEBUG) Log.d(TAG, "handleHide");
 
-            mHiding = true;
-
             if (mShowing && !mOccluded) {
+                mHiding = true;
                 mKeyguardGoingAwayRunnable.run();
             } else {
-                // TODO(bc-unlock): Fill parameters
-                mNotificationShadeWindowControllerLazy.get().batchApplyWindowLayoutParams(() -> {
-                    handleStartKeyguardExitAnimation(
-                            SystemClock.uptimeMillis() + mHideAnimation.getStartOffset(),
-                            mHideAnimation.getDuration(), null /* apps */, null /* wallpapers */,
-                            null /* nonApps */, null /* finishedCallback */);
-                });
+                Log.d(TAG, "Hiding keyguard while occluded. Just hide the keyguard view and exit.");
+
+                mKeyguardViewControllerLazy.get().hide(
+                        mSystemClock.uptimeMillis() + mHideAnimation.getStartOffset(),
+                        mHideAnimation.getDuration());
+                onKeyguardExitFinished();
             }
 
             // It's possible that the device was unlocked (via BOUNCER or Fingerprint) while
             // dreaming. It's time to wake up.
-            if (mDreamOverlayShowing) {
-                mPM.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_GESTURE,
+            if (mDreamOverlayShowing || mUpdateMonitor.isDreaming()) {
+                mPM.wakeUp(mSystemClock.uptimeMillis(), PowerManager.WAKE_REASON_GESTURE,
                         "com.android.systemui:UNLOCK_DREAMING");
             }
         }
@@ -3075,7 +3112,7 @@
                 flags |= KEYGUARD_GOING_AWAY_FLAG_TO_LAUNCHER_CLEAR_SNAPSHOT;
             }
 
-            ActivityTaskManager.getService().keyguardGoingAway(flags);
+            mActivityTaskManagerService.keyguardGoingAway(flags);
             mKeyguardStateController.notifyKeyguardGoingAway(true);
         } catch (RemoteException e) {
             mSurfaceBehindRemoteAnimationRequested = false;
@@ -3439,7 +3476,7 @@
      * In case it does support it, we have to fade in the incoming app, otherwise we'll reveal it
      * with the light reveal scrim.
      */
-    public void setWallpaperSupportsAmbientMode(boolean supportsAmbientMode) {
+    private void setWallpaperSupportsAmbientMode(boolean supportsAmbientMode) {
         mWallpaperSupportsAmbientMode = supportsAmbientMode;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/bouncer/data/factory/BouncerMessageFactory.kt b/packages/SystemUI/src/com/android/systemui/keyguard/bouncer/data/factory/BouncerMessageFactory.kt
deleted file mode 100644
index 11bc88a..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/bouncer/data/factory/BouncerMessageFactory.kt
+++ /dev/null
@@ -1,322 +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.keyguard.bouncer.data.factory
-
-import android.annotation.IntDef
-import com.android.keyguard.KeyguardSecurityModel
-import com.android.keyguard.KeyguardSecurityModel.SecurityMode
-import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_AFTER_LOCKOUT
-import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_DEFAULT
-import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_DEVICE_ADMIN
-import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_FACE_LOCKED_OUT
-import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_FINGERPRINT_LOCKED_OUT
-import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_INCORRECT_FACE_INPUT
-import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_INCORRECT_FINGERPRINT_INPUT
-import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_INCORRECT_PRIMARY_AUTH_INPUT
-import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_NONE
-import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT
-import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_PREPARE_FOR_UPDATE
-import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_PRIMARY_AUTH_LOCKED_OUT
-import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_RESTART
-import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TIMEOUT
-import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TRUSTAGENT_EXPIRED
-import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_USER_REQUEST
-import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.systemui.R.string.keyguard_enter_password
-import com.android.systemui.R.string.keyguard_enter_pattern
-import com.android.systemui.R.string.keyguard_enter_pin
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.bouncer.shared.model.BouncerMessageModel
-import com.android.systemui.keyguard.bouncer.shared.model.Message
-import javax.inject.Inject
-
-@SysUISingleton
-class BouncerMessageFactory
-@Inject
-constructor(
-    private val updateMonitor: KeyguardUpdateMonitor,
-    private val securityModel: KeyguardSecurityModel,
-) {
-
-    fun createFromPromptReason(
-        @BouncerPromptReason reason: Int,
-        userId: Int,
-    ): BouncerMessageModel? {
-        val pair =
-            getBouncerMessage(
-                reason,
-                securityModel.getSecurityMode(userId),
-                updateMonitor.isFingerprintAllowedInBouncer
-            )
-        return pair?.let {
-            BouncerMessageModel(
-                message = Message(messageResId = pair.first),
-                secondaryMessage = Message(messageResId = pair.second)
-            )
-        }
-    }
-
-    fun createFromString(
-        primaryMsg: String? = null,
-        secondaryMsg: String? = null
-    ): BouncerMessageModel =
-        BouncerMessageModel(
-            message = primaryMsg?.let { Message(message = it) },
-            secondaryMessage = secondaryMsg?.let { Message(message = it) },
-        )
-
-    /**
-     * Helper method that provides the relevant bouncer message that should be shown for different
-     * scenarios indicated by [reason]. [securityMode] & [fpAllowedInBouncer] parameters are used to
-     * provide a more specific message.
-     */
-    private fun getBouncerMessage(
-        @BouncerPromptReason reason: Int,
-        securityMode: SecurityMode,
-        fpAllowedInBouncer: Boolean = false
-    ): Pair<Int, Int>? {
-        return when (reason) {
-            PROMPT_REASON_RESTART -> authRequiredAfterReboot(securityMode)
-            PROMPT_REASON_TIMEOUT -> authRequiredAfterPrimaryAuthTimeout(securityMode)
-            PROMPT_REASON_DEVICE_ADMIN -> authRequiredAfterAdminLockdown(securityMode)
-            PROMPT_REASON_USER_REQUEST -> authRequiredAfterUserLockdown(securityMode)
-            PROMPT_REASON_AFTER_LOCKOUT -> biometricLockout(securityMode)
-            PROMPT_REASON_PREPARE_FOR_UPDATE -> authRequiredForUnattendedUpdate(securityMode)
-            PROMPT_REASON_FINGERPRINT_LOCKED_OUT -> fingerprintUnlockUnavailable(securityMode)
-            PROMPT_REASON_FACE_LOCKED_OUT -> faceUnlockUnavailable(securityMode)
-            PROMPT_REASON_INCORRECT_PRIMARY_AUTH_INPUT ->
-                if (fpAllowedInBouncer) incorrectSecurityInputWithFingerprint(securityMode)
-                else incorrectSecurityInput(securityMode)
-            PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT ->
-                if (fpAllowedInBouncer) nonStrongAuthTimeoutWithFingerprintAllowed(securityMode)
-                else nonStrongAuthTimeout(securityMode)
-            PROMPT_REASON_TRUSTAGENT_EXPIRED ->
-                if (fpAllowedInBouncer) trustAgentDisabledWithFingerprintAllowed(securityMode)
-                else trustAgentDisabled(securityMode)
-            PROMPT_REASON_INCORRECT_FACE_INPUT ->
-                if (fpAllowedInBouncer) incorrectFaceInputWithFingerprintAllowed(securityMode)
-                else incorrectFaceInput(securityMode)
-            PROMPT_REASON_INCORRECT_FINGERPRINT_INPUT -> incorrectFingerprintInput(securityMode)
-            PROMPT_REASON_DEFAULT ->
-                if (fpAllowedInBouncer) defaultMessageWithFingerprint(securityMode)
-                else defaultMessage(securityMode)
-            PROMPT_REASON_PRIMARY_AUTH_LOCKED_OUT -> primaryAuthLockedOut(securityMode)
-            else -> null
-        }
-    }
-}
-
-@Retention(AnnotationRetention.SOURCE)
-@IntDef(
-    PROMPT_REASON_TIMEOUT,
-    PROMPT_REASON_DEVICE_ADMIN,
-    PROMPT_REASON_USER_REQUEST,
-    PROMPT_REASON_AFTER_LOCKOUT,
-    PROMPT_REASON_PREPARE_FOR_UPDATE,
-    PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT,
-    PROMPT_REASON_TRUSTAGENT_EXPIRED,
-    PROMPT_REASON_INCORRECT_PRIMARY_AUTH_INPUT,
-    PROMPT_REASON_INCORRECT_FACE_INPUT,
-    PROMPT_REASON_INCORRECT_FINGERPRINT_INPUT,
-    PROMPT_REASON_FACE_LOCKED_OUT,
-    PROMPT_REASON_FINGERPRINT_LOCKED_OUT,
-    PROMPT_REASON_DEFAULT,
-    PROMPT_REASON_NONE,
-    PROMPT_REASON_RESTART,
-    PROMPT_REASON_PRIMARY_AUTH_LOCKED_OUT,
-)
-annotation class BouncerPromptReason
-
-private fun defaultMessage(securityMode: SecurityMode): Pair<Int, Int> {
-    return when (securityMode) {
-        SecurityMode.Pattern -> Pair(keyguard_enter_pattern, 0)
-        SecurityMode.Password -> Pair(keyguard_enter_password, 0)
-        SecurityMode.PIN -> Pair(keyguard_enter_pin, 0)
-        else -> Pair(0, 0)
-    }
-}
-
-private fun defaultMessageWithFingerprint(securityMode: SecurityMode): Pair<Int, Int> {
-    return when (securityMode) {
-        SecurityMode.Pattern -> Pair(keyguard_enter_pattern, 0)
-        SecurityMode.Password -> Pair(keyguard_enter_password, 0)
-        SecurityMode.PIN -> Pair(keyguard_enter_pin, 0)
-        else -> Pair(0, 0)
-    }
-}
-
-private fun incorrectSecurityInput(securityMode: SecurityMode): Pair<Int, Int> {
-    return when (securityMode) {
-        SecurityMode.Pattern -> Pair(keyguard_enter_pattern, 0)
-        SecurityMode.Password -> Pair(keyguard_enter_password, 0)
-        SecurityMode.PIN -> Pair(keyguard_enter_pin, 0)
-        else -> Pair(0, 0)
-    }
-}
-
-private fun incorrectSecurityInputWithFingerprint(securityMode: SecurityMode): Pair<Int, Int> {
-    return when (securityMode) {
-        SecurityMode.Pattern -> Pair(keyguard_enter_pattern, 0)
-        SecurityMode.Password -> Pair(keyguard_enter_password, 0)
-        SecurityMode.PIN -> Pair(keyguard_enter_pin, 0)
-        else -> Pair(0, 0)
-    }
-}
-
-private fun incorrectFingerprintInput(securityMode: SecurityMode): Pair<Int, Int> {
-    return when (securityMode) {
-        SecurityMode.Pattern -> Pair(keyguard_enter_pattern, 0)
-        SecurityMode.Password -> Pair(keyguard_enter_password, 0)
-        SecurityMode.PIN -> Pair(keyguard_enter_pin, 0)
-        else -> Pair(0, 0)
-    }
-}
-
-private fun incorrectFaceInput(securityMode: SecurityMode): Pair<Int, Int> {
-    return when (securityMode) {
-        SecurityMode.Pattern -> Pair(keyguard_enter_pattern, 0)
-        SecurityMode.Password -> Pair(keyguard_enter_password, 0)
-        SecurityMode.PIN -> Pair(keyguard_enter_pin, 0)
-        else -> Pair(0, 0)
-    }
-}
-
-private fun incorrectFaceInputWithFingerprintAllowed(securityMode: SecurityMode): Pair<Int, Int> {
-    return when (securityMode) {
-        SecurityMode.Pattern -> Pair(keyguard_enter_pattern, 0)
-        SecurityMode.Password -> Pair(keyguard_enter_password, 0)
-        SecurityMode.PIN -> Pair(keyguard_enter_pin, 0)
-        else -> Pair(0, 0)
-    }
-}
-
-private fun biometricLockout(securityMode: SecurityMode): Pair<Int, Int> {
-    return when (securityMode) {
-        SecurityMode.Pattern -> Pair(keyguard_enter_pattern, 0)
-        SecurityMode.Password -> Pair(keyguard_enter_password, 0)
-        SecurityMode.PIN -> Pair(keyguard_enter_pin, 0)
-        else -> Pair(0, 0)
-    }
-}
-
-private fun authRequiredAfterReboot(securityMode: SecurityMode): Pair<Int, Int> {
-    return when (securityMode) {
-        SecurityMode.Pattern -> Pair(keyguard_enter_pattern, 0)
-        SecurityMode.Password -> Pair(keyguard_enter_password, 0)
-        SecurityMode.PIN -> Pair(keyguard_enter_pin, 0)
-        else -> Pair(0, 0)
-    }
-}
-
-private fun authRequiredAfterAdminLockdown(securityMode: SecurityMode): Pair<Int, Int> {
-    return when (securityMode) {
-        SecurityMode.Pattern -> Pair(keyguard_enter_pattern, 0)
-        SecurityMode.Password -> Pair(keyguard_enter_password, 0)
-        SecurityMode.PIN -> Pair(keyguard_enter_pin, 0)
-        else -> Pair(0, 0)
-    }
-}
-
-private fun authRequiredAfterUserLockdown(securityMode: SecurityMode): Pair<Int, Int> {
-    return when (securityMode) {
-        SecurityMode.Pattern -> Pair(keyguard_enter_pattern, 0)
-        SecurityMode.Password -> Pair(keyguard_enter_password, 0)
-        SecurityMode.PIN -> Pair(keyguard_enter_pin, 0)
-        else -> Pair(0, 0)
-    }
-}
-
-private fun authRequiredForUnattendedUpdate(securityMode: SecurityMode): Pair<Int, Int> {
-    return when (securityMode) {
-        SecurityMode.Pattern -> Pair(keyguard_enter_pattern, 0)
-        SecurityMode.Password -> Pair(keyguard_enter_password, 0)
-        SecurityMode.PIN -> Pair(keyguard_enter_pin, 0)
-        else -> Pair(0, 0)
-    }
-}
-
-private fun authRequiredAfterPrimaryAuthTimeout(securityMode: SecurityMode): Pair<Int, Int> {
-    return when (securityMode) {
-        SecurityMode.Pattern -> Pair(keyguard_enter_pattern, 0)
-        SecurityMode.Password -> Pair(keyguard_enter_password, 0)
-        SecurityMode.PIN -> Pair(keyguard_enter_pin, 0)
-        else -> Pair(0, 0)
-    }
-}
-
-private fun nonStrongAuthTimeout(securityMode: SecurityMode): Pair<Int, Int> {
-    return when (securityMode) {
-        SecurityMode.Pattern -> Pair(keyguard_enter_pattern, 0)
-        SecurityMode.Password -> Pair(keyguard_enter_password, 0)
-        SecurityMode.PIN -> Pair(keyguard_enter_pin, 0)
-        else -> Pair(0, 0)
-    }
-}
-
-private fun nonStrongAuthTimeoutWithFingerprintAllowed(securityMode: SecurityMode): Pair<Int, Int> {
-    return when (securityMode) {
-        SecurityMode.Pattern -> Pair(keyguard_enter_pattern, 0)
-        SecurityMode.Password -> Pair(keyguard_enter_password, 0)
-        SecurityMode.PIN -> Pair(keyguard_enter_pin, 0)
-        else -> Pair(0, 0)
-    }
-}
-
-private fun faceUnlockUnavailable(securityMode: SecurityMode): Pair<Int, Int> {
-    return when (securityMode) {
-        SecurityMode.Pattern -> Pair(keyguard_enter_pattern, 0)
-        SecurityMode.Password -> Pair(keyguard_enter_password, 0)
-        SecurityMode.PIN -> Pair(keyguard_enter_pin, 0)
-        else -> Pair(0, 0)
-    }
-}
-
-private fun fingerprintUnlockUnavailable(securityMode: SecurityMode): Pair<Int, Int> {
-    return when (securityMode) {
-        SecurityMode.Pattern -> Pair(keyguard_enter_pattern, 0)
-        SecurityMode.Password -> Pair(keyguard_enter_password, 0)
-        SecurityMode.PIN -> Pair(keyguard_enter_pin, 0)
-        else -> Pair(0, 0)
-    }
-}
-
-private fun trustAgentDisabled(securityMode: SecurityMode): Pair<Int, Int> {
-    return when (securityMode) {
-        SecurityMode.Pattern -> Pair(keyguard_enter_pattern, 0)
-        SecurityMode.Password -> Pair(keyguard_enter_password, 0)
-        SecurityMode.PIN -> Pair(keyguard_enter_pin, 0)
-        else -> Pair(0, 0)
-    }
-}
-
-private fun trustAgentDisabledWithFingerprintAllowed(securityMode: SecurityMode): Pair<Int, Int> {
-    return when (securityMode) {
-        SecurityMode.Pattern -> Pair(keyguard_enter_pattern, 0)
-        SecurityMode.Password -> Pair(keyguard_enter_password, 0)
-        SecurityMode.PIN -> Pair(keyguard_enter_pin, 0)
-        else -> Pair(0, 0)
-    }
-}
-
-private fun primaryAuthLockedOut(securityMode: SecurityMode): Pair<Int, Int> {
-    return when (securityMode) {
-        SecurityMode.Pattern -> Pair(keyguard_enter_pattern, 0)
-        SecurityMode.Password -> Pair(keyguard_enter_password, 0)
-        SecurityMode.PIN -> Pair(keyguard_enter_pin, 0)
-        else -> Pair(0, 0)
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/bouncer/data/repository/BouncerMessageRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/bouncer/data/repository/BouncerMessageRepository.kt
deleted file mode 100644
index c4400bc..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/bouncer/data/repository/BouncerMessageRepository.kt
+++ /dev/null
@@ -1,332 +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.keyguard.bouncer.data.repository
-
-import android.hardware.biometrics.BiometricSourceType
-import android.hardware.biometrics.BiometricSourceType.FACE
-import android.hardware.biometrics.BiometricSourceType.FINGERPRINT
-import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_DEVICE_ADMIN
-import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_FACE_LOCKED_OUT
-import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_FINGERPRINT_LOCKED_OUT
-import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_INCORRECT_FACE_INPUT
-import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_INCORRECT_FINGERPRINT_INPUT
-import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_NONE
-import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT
-import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_PREPARE_FOR_UPDATE
-import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_RESTART
-import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TIMEOUT
-import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TRUSTAGENT_EXPIRED
-import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_USER_REQUEST
-import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.keyguard.KeyguardUpdateMonitorCallback
-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.keyguard.bouncer.data.factory.BouncerMessageFactory
-import com.android.systemui.keyguard.bouncer.shared.model.BouncerMessageModel
-import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
-import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
-import com.android.systemui.keyguard.data.repository.TrustRepository
-import com.android.systemui.user.data.repository.UserRepository
-import javax.inject.Inject
-import kotlinx.coroutines.channels.awaitClose
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.onStart
-
-/** Provide different sources of messages that needs to be shown on the bouncer. */
-interface BouncerMessageRepository {
-    /**
-     * Messages that are shown in response to the incorrect security attempts on the bouncer and
-     * primary authentication method being locked out, along with countdown messages before primary
-     * auth is active again.
-     */
-    val primaryAuthMessage: Flow<BouncerMessageModel?>
-
-    /**
-     * Help messages that are shown to the user on how to successfully perform authentication using
-     * face.
-     */
-    val faceAcquisitionMessage: Flow<BouncerMessageModel?>
-
-    /**
-     * Help messages that are shown to the user on how to successfully perform authentication using
-     * fingerprint.
-     */
-    val fingerprintAcquisitionMessage: Flow<BouncerMessageModel?>
-
-    /** Custom message that is displayed when the bouncer is being shown to launch an app. */
-    val customMessage: Flow<BouncerMessageModel?>
-
-    /**
-     * Messages that are shown in response to biometric authentication attempts through face or
-     * fingerprint.
-     */
-    val biometricAuthMessage: Flow<BouncerMessageModel?>
-
-    /** Messages that are shown when certain auth flags are set. */
-    val authFlagsMessage: Flow<BouncerMessageModel?>
-
-    /** Messages that are show after biometrics are locked out temporarily or permanently */
-    val biometricLockedOutMessage: Flow<BouncerMessageModel?>
-
-    /** Set the value for [primaryAuthMessage] */
-    fun setPrimaryAuthMessage(value: BouncerMessageModel?)
-
-    /** Set the value for [faceAcquisitionMessage] */
-    fun setFaceAcquisitionMessage(value: BouncerMessageModel?)
-    /** Set the value for [fingerprintAcquisitionMessage] */
-    fun setFingerprintAcquisitionMessage(value: BouncerMessageModel?)
-
-    /** Set the value for [customMessage] */
-    fun setCustomMessage(value: BouncerMessageModel?)
-
-    /**
-     * Clear any previously set messages for [primaryAuthMessage], [faceAcquisitionMessage],
-     * [fingerprintAcquisitionMessage] & [customMessage]
-     */
-    fun clearMessage()
-}
-
-@SysUISingleton
-class BouncerMessageRepositoryImpl
-@Inject
-constructor(
-    trustRepository: TrustRepository,
-    biometricSettingsRepository: BiometricSettingsRepository,
-    updateMonitor: KeyguardUpdateMonitor,
-    private val bouncerMessageFactory: BouncerMessageFactory,
-    private val userRepository: UserRepository,
-    fingerprintAuthRepository: DeviceEntryFingerprintAuthRepository,
-) : BouncerMessageRepository {
-
-    private val isFaceEnrolledAndEnabled =
-        and(
-            biometricSettingsRepository.isFaceAuthenticationEnabled,
-            biometricSettingsRepository.isFaceEnrolled
-        )
-
-    private val isFingerprintEnrolledAndEnabled =
-        and(
-            biometricSettingsRepository.isFingerprintEnabledByDevicePolicy,
-            biometricSettingsRepository.isFingerprintEnrolled
-        )
-
-    private val isAnyBiometricsEnabledAndEnrolled =
-        or(isFaceEnrolledAndEnabled, isFingerprintEnrolledAndEnabled)
-
-    private val authFlagsBasedPromptReason: Flow<Int> =
-        combine(
-                biometricSettingsRepository.authenticationFlags,
-                trustRepository.isCurrentUserTrustManaged,
-                isAnyBiometricsEnabledAndEnrolled,
-                ::Triple
-            )
-            .map { (flags, isTrustManaged, biometricsEnrolledAndEnabled) ->
-                val trustOrBiometricsAvailable = (isTrustManaged || biometricsEnrolledAndEnabled)
-                return@map if (
-                    trustOrBiometricsAvailable && flags.isPrimaryAuthRequiredAfterReboot
-                ) {
-                    PROMPT_REASON_RESTART
-                } else if (trustOrBiometricsAvailable && flags.isPrimaryAuthRequiredAfterTimeout) {
-                    PROMPT_REASON_TIMEOUT
-                } else if (flags.isPrimaryAuthRequiredAfterDpmLockdown) {
-                    PROMPT_REASON_DEVICE_ADMIN
-                } else if (isTrustManaged && flags.someAuthRequiredAfterUserRequest) {
-                    PROMPT_REASON_TRUSTAGENT_EXPIRED
-                } else if (isTrustManaged && flags.someAuthRequiredAfterTrustAgentExpired) {
-                    PROMPT_REASON_TRUSTAGENT_EXPIRED
-                } else if (trustOrBiometricsAvailable && flags.isInUserLockdown) {
-                    PROMPT_REASON_USER_REQUEST
-                } else if (
-                    trustOrBiometricsAvailable && flags.primaryAuthRequiredForUnattendedUpdate
-                ) {
-                    PROMPT_REASON_PREPARE_FOR_UPDATE
-                } else if (
-                    trustOrBiometricsAvailable &&
-                        flags.strongerAuthRequiredAfterNonStrongBiometricsTimeout
-                ) {
-                    PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT
-                } else {
-                    PROMPT_REASON_NONE
-                }
-            }
-
-    private val biometricAuthReason: Flow<Int> =
-        conflatedCallbackFlow {
-                val callback =
-                    object : KeyguardUpdateMonitorCallback() {
-                        override fun onBiometricAuthFailed(
-                            biometricSourceType: BiometricSourceType?
-                        ) {
-                            val promptReason =
-                                if (biometricSourceType == FINGERPRINT)
-                                    PROMPT_REASON_INCORRECT_FINGERPRINT_INPUT
-                                else if (
-                                    biometricSourceType == FACE && !updateMonitor.isFaceLockedOut
-                                ) {
-                                    PROMPT_REASON_INCORRECT_FACE_INPUT
-                                } else PROMPT_REASON_NONE
-                            trySendWithFailureLogging(promptReason, TAG, "onBiometricAuthFailed")
-                        }
-
-                        override fun onBiometricsCleared() {
-                            trySendWithFailureLogging(
-                                PROMPT_REASON_NONE,
-                                TAG,
-                                "onBiometricsCleared"
-                            )
-                        }
-
-                        override fun onBiometricAcquired(
-                            biometricSourceType: BiometricSourceType?,
-                            acquireInfo: Int
-                        ) {
-                            trySendWithFailureLogging(
-                                PROMPT_REASON_NONE,
-                                TAG,
-                                "clearBiometricPrompt for new auth session."
-                            )
-                        }
-
-                        override fun onBiometricAuthenticated(
-                            userId: Int,
-                            biometricSourceType: BiometricSourceType?,
-                            isStrongBiometric: Boolean
-                        ) {
-                            trySendWithFailureLogging(
-                                PROMPT_REASON_NONE,
-                                TAG,
-                                "onBiometricAuthenticated"
-                            )
-                        }
-                    }
-                updateMonitor.registerCallback(callback)
-                awaitClose { updateMonitor.removeCallback(callback) }
-            }
-            .distinctUntilChanged()
-
-    private val _primaryAuthMessage = MutableStateFlow<BouncerMessageModel?>(null)
-    override val primaryAuthMessage: Flow<BouncerMessageModel?> = _primaryAuthMessage
-
-    private val _faceAcquisitionMessage = MutableStateFlow<BouncerMessageModel?>(null)
-    override val faceAcquisitionMessage: Flow<BouncerMessageModel?> = _faceAcquisitionMessage
-
-    private val _fingerprintAcquisitionMessage = MutableStateFlow<BouncerMessageModel?>(null)
-    override val fingerprintAcquisitionMessage: Flow<BouncerMessageModel?> =
-        _fingerprintAcquisitionMessage
-
-    private val _customMessage = MutableStateFlow<BouncerMessageModel?>(null)
-    override val customMessage: Flow<BouncerMessageModel?> = _customMessage
-
-    override val biometricAuthMessage: Flow<BouncerMessageModel?> =
-        biometricAuthReason
-            .map {
-                if (it == PROMPT_REASON_NONE) null
-                else
-                    bouncerMessageFactory.createFromPromptReason(
-                        it,
-                        userRepository.getSelectedUserInfo().id
-                    )
-            }
-            .onStart { emit(null) }
-            .distinctUntilChanged()
-
-    override val authFlagsMessage: Flow<BouncerMessageModel?> =
-        authFlagsBasedPromptReason
-            .map {
-                if (it == PROMPT_REASON_NONE) null
-                else
-                    bouncerMessageFactory.createFromPromptReason(
-                        it,
-                        userRepository.getSelectedUserInfo().id
-                    )
-            }
-            .onStart { emit(null) }
-            .distinctUntilChanged()
-
-    // TODO (b/262838215): Replace with DeviceEntryFaceAuthRepository when the new face auth system
-    // has been launched.
-    private val faceLockedOut: Flow<Boolean> = conflatedCallbackFlow {
-        val callback =
-            object : KeyguardUpdateMonitorCallback() {
-                override fun onLockedOutStateChanged(biometricSourceType: BiometricSourceType?) {
-                    if (biometricSourceType == FACE) {
-                        trySendWithFailureLogging(
-                            updateMonitor.isFaceLockedOut,
-                            TAG,
-                            "face lock out state changed."
-                        )
-                    }
-                }
-            }
-        updateMonitor.registerCallback(callback)
-        trySendWithFailureLogging(updateMonitor.isFaceLockedOut, TAG, "face lockout initial value")
-        awaitClose { updateMonitor.removeCallback(callback) }
-    }
-
-    override val biometricLockedOutMessage: Flow<BouncerMessageModel?> =
-        combine(fingerprintAuthRepository.isLockedOut, faceLockedOut) { fp, face ->
-            return@combine if (fp) {
-                bouncerMessageFactory.createFromPromptReason(
-                    PROMPT_REASON_FINGERPRINT_LOCKED_OUT,
-                    userRepository.getSelectedUserInfo().id
-                )
-            } else if (face) {
-                bouncerMessageFactory.createFromPromptReason(
-                    PROMPT_REASON_FACE_LOCKED_OUT,
-                    userRepository.getSelectedUserInfo().id
-                )
-            } else null
-        }
-
-    override fun setPrimaryAuthMessage(value: BouncerMessageModel?) {
-        _primaryAuthMessage.value = value
-    }
-
-    override fun setFaceAcquisitionMessage(value: BouncerMessageModel?) {
-        _faceAcquisitionMessage.value = value
-    }
-
-    override fun setFingerprintAcquisitionMessage(value: BouncerMessageModel?) {
-        _fingerprintAcquisitionMessage.value = value
-    }
-
-    override fun setCustomMessage(value: BouncerMessageModel?) {
-        _customMessage.value = value
-    }
-
-    override fun clearMessage() {
-        _fingerprintAcquisitionMessage.value = null
-        _faceAcquisitionMessage.value = null
-        _primaryAuthMessage.value = null
-        _customMessage.value = null
-    }
-
-    companion object {
-        const val TAG = "BouncerDetailedMessageRepository"
-    }
-}
-
-private fun and(flow: Flow<Boolean>, anotherFlow: Flow<Boolean>) =
-    flow.combine(anotherFlow) { a, b -> a && b }
-
-private fun or(flow: Flow<Boolean>, anotherFlow: Flow<Boolean>) =
-    flow.combine(anotherFlow) { a, b -> a || b }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/bouncer/domain/interactor/BouncerMessageAuditLogger.kt b/packages/SystemUI/src/com/android/systemui/keyguard/bouncer/domain/interactor/BouncerMessageAuditLogger.kt
deleted file mode 100644
index 56f81fc..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/bouncer/domain/interactor/BouncerMessageAuditLogger.kt
+++ /dev/null
@@ -1,60 +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.keyguard.bouncer.domain.interactor
-
-import android.os.Build
-import android.util.Log
-import com.android.systemui.CoreStartable
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.keyguard.bouncer.data.repository.BouncerMessageRepository
-import com.android.systemui.keyguard.bouncer.shared.model.BouncerMessageModel
-import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.launch
-
-private val TAG = BouncerMessageAuditLogger::class.simpleName!!
-
-/** Logger that echoes bouncer messages state to logcat in debuggable builds. */
-@SysUISingleton
-class BouncerMessageAuditLogger
-@Inject
-constructor(
-    @Application private val scope: CoroutineScope,
-    private val repository: BouncerMessageRepository,
-    private val interactor: BouncerMessageInteractor,
-) : CoreStartable {
-    override fun start() {
-        if (Build.isDebuggable()) {
-            collectAndLog(repository.biometricAuthMessage, "biometricMessage: ")
-            collectAndLog(repository.primaryAuthMessage, "primaryAuthMessage: ")
-            collectAndLog(repository.customMessage, "customMessage: ")
-            collectAndLog(repository.faceAcquisitionMessage, "faceAcquisitionMessage: ")
-            collectAndLog(
-                repository.fingerprintAcquisitionMessage,
-                "fingerprintAcquisitionMessage: "
-            )
-            collectAndLog(repository.authFlagsMessage, "authFlagsMessage: ")
-            collectAndLog(interactor.bouncerMessage, "interactor.bouncerMessage: ")
-        }
-    }
-
-    private fun collectAndLog(flow: Flow<BouncerMessageModel?>, context: String) {
-        scope.launch { flow.collect { Log.d(TAG, context + it) } }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/bouncer/domain/interactor/BouncerMessageInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/bouncer/domain/interactor/BouncerMessageInteractor.kt
deleted file mode 100644
index 1754d93..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/bouncer/domain/interactor/BouncerMessageInteractor.kt
+++ /dev/null
@@ -1,190 +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.keyguard.bouncer.domain.interactor
-
-import android.os.CountDownTimer
-import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_DEFAULT
-import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_INCORRECT_PRIMARY_AUTH_INPUT
-import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_PRIMARY_AUTH_LOCKED_OUT
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags.REVAMPED_BOUNCER_MESSAGES
-import com.android.systemui.keyguard.bouncer.data.factory.BouncerMessageFactory
-import com.android.systemui.keyguard.bouncer.data.repository.BouncerMessageRepository
-import com.android.systemui.keyguard.bouncer.shared.model.BouncerMessageModel
-import com.android.systemui.user.data.repository.UserRepository
-import javax.inject.Inject
-import kotlin.math.roundToInt
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.flow.map
-
-@SysUISingleton
-class BouncerMessageInteractor
-@Inject
-constructor(
-    private val repository: BouncerMessageRepository,
-    private val factory: BouncerMessageFactory,
-    private val userRepository: UserRepository,
-    private val countDownTimerUtil: CountDownTimerUtil,
-    private val featureFlags: FeatureFlags,
-) {
-    fun onPrimaryAuthLockedOut(secondsBeforeLockoutReset: Long) {
-        if (!featureFlags.isEnabled(REVAMPED_BOUNCER_MESSAGES)) return
-
-        val callback =
-            object : CountDownTimerCallback {
-                override fun onFinish() {
-                    repository.clearMessage()
-                }
-
-                override fun onTick(millisUntilFinished: Long) {
-                    val secondsRemaining = (millisUntilFinished / 1000.0).roundToInt()
-                    val message =
-                        factory.createFromPromptReason(
-                            reason = PROMPT_REASON_PRIMARY_AUTH_LOCKED_OUT,
-                            userId = userRepository.getSelectedUserInfo().id
-                        )
-                    message?.message?.animate = false
-                    message?.message?.formatterArgs =
-                        mutableMapOf<String, Any>(Pair("count", secondsRemaining))
-                    repository.setPrimaryAuthMessage(message)
-                }
-            }
-        countDownTimerUtil.startNewTimer(secondsBeforeLockoutReset * 1000, 1000, callback)
-    }
-
-    fun onPrimaryAuthIncorrectAttempt() {
-        if (!featureFlags.isEnabled(REVAMPED_BOUNCER_MESSAGES)) return
-
-        repository.setPrimaryAuthMessage(
-            factory.createFromPromptReason(
-                PROMPT_REASON_INCORRECT_PRIMARY_AUTH_INPUT,
-                userRepository.getSelectedUserInfo().id
-            )
-        )
-    }
-
-    fun setFingerprintAcquisitionMessage(value: String?) {
-        if (!featureFlags.isEnabled(REVAMPED_BOUNCER_MESSAGES)) return
-
-        repository.setFingerprintAcquisitionMessage(
-            if (value != null) {
-                factory.createFromString(secondaryMsg = value)
-            } else {
-                null
-            }
-        )
-    }
-
-    fun setFaceAcquisitionMessage(value: String?) {
-        if (!featureFlags.isEnabled(REVAMPED_BOUNCER_MESSAGES)) return
-
-        repository.setFaceAcquisitionMessage(
-            if (value != null) {
-                factory.createFromString(secondaryMsg = value)
-            } else {
-                null
-            }
-        )
-    }
-
-    fun setCustomMessage(value: String?) {
-        if (!featureFlags.isEnabled(REVAMPED_BOUNCER_MESSAGES)) return
-
-        repository.setCustomMessage(
-            if (value != null) {
-                factory.createFromString(secondaryMsg = value)
-            } else {
-                null
-            }
-        )
-    }
-
-    fun onPrimaryBouncerUserInput() {
-        if (!featureFlags.isEnabled(REVAMPED_BOUNCER_MESSAGES)) return
-
-        repository.clearMessage()
-    }
-
-    fun onBouncerBeingHidden() {
-        if (!featureFlags.isEnabled(REVAMPED_BOUNCER_MESSAGES)) return
-
-        repository.clearMessage()
-    }
-
-    private fun firstNonNullMessage(
-        oneMessageModel: Flow<BouncerMessageModel?>,
-        anotherMessageModel: Flow<BouncerMessageModel?>
-    ): Flow<BouncerMessageModel?> {
-        return oneMessageModel.combine(anotherMessageModel) { a, b -> a ?: b }
-    }
-
-    // Null if feature flag is enabled which gets ignored always or empty bouncer message model that
-    // always maps to an empty string.
-    private fun nullOrEmptyMessage() =
-        flowOf(
-            if (featureFlags.isEnabled(REVAMPED_BOUNCER_MESSAGES)) null
-            else factory.createFromString("", "")
-        )
-
-    val bouncerMessage =
-        listOf(
-                nullOrEmptyMessage(),
-                repository.primaryAuthMessage,
-                repository.biometricAuthMessage,
-                repository.fingerprintAcquisitionMessage,
-                repository.faceAcquisitionMessage,
-                repository.customMessage,
-                repository.authFlagsMessage,
-                repository.biometricLockedOutMessage,
-                userRepository.selectedUserInfo.map {
-                    factory.createFromPromptReason(PROMPT_REASON_DEFAULT, it.id)
-                },
-            )
-            .reduce(::firstNonNullMessage)
-            .distinctUntilChanged()
-}
-
-interface CountDownTimerCallback {
-    fun onFinish()
-    fun onTick(millisUntilFinished: Long)
-}
-
-@SysUISingleton
-open class CountDownTimerUtil @Inject constructor() {
-
-    /**
-     * Start a new count down timer that runs for [millisInFuture] with a tick every
-     * [millisInterval]
-     */
-    fun startNewTimer(
-        millisInFuture: Long,
-        millisInterval: Long,
-        callback: CountDownTimerCallback,
-    ): CountDownTimer {
-        return object : CountDownTimer(millisInFuture, millisInterval) {
-                override fun onFinish() = callback.onFinish()
-
-                override fun onTick(millisUntilFinished: Long) =
-                    callback.onTick(millisUntilFinished)
-            }
-            .start()
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/bouncer/shared/model/BouncerMessageModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/bouncer/shared/model/BouncerMessageModel.kt
deleted file mode 100644
index 46e8873..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/bouncer/shared/model/BouncerMessageModel.kt
+++ /dev/null
@@ -1,39 +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.keyguard.bouncer.shared.model
-
-import android.content.res.ColorStateList
-
-/**
- * Represents the message displayed on the bouncer. It has two parts, primary and a secondary
- * message
- */
-data class BouncerMessageModel(val message: Message? = null, val secondaryMessage: Message? = null)
-
-/**
- * Representation of a single message on the bouncer. It can be either a string or a string resource
- * ID
- */
-data class Message(
-    val message: String? = null,
-    val messageResId: Int? = null,
-    val colorState: ColorStateList? = null,
-    /** Any plural formatter arguments that can used to format the [messageResId] */
-    var formatterArgs: Map<String, Any>? = null,
-    /** Specifies whether this text should be animated when it is shown. */
-    var animate: Boolean = true,
-)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/bouncer/ui/BouncerMessageView.kt b/packages/SystemUI/src/com/android/systemui/keyguard/bouncer/ui/BouncerMessageView.kt
deleted file mode 100644
index 4dc52ff..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/bouncer/ui/BouncerMessageView.kt
+++ /dev/null
@@ -1,54 +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.keyguard.bouncer.ui
-
-import android.content.Context
-import android.util.AttributeSet
-import android.widget.LinearLayout
-import com.android.keyguard.BouncerKeyguardMessageArea
-import com.android.keyguard.KeyguardMessageArea
-import com.android.keyguard.KeyguardMessageAreaController
-import com.android.systemui.R
-
-class BouncerMessageView : LinearLayout {
-    constructor(context: Context?) : super(context)
-
-    constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
-
-    init {
-        inflate(context, R.layout.bouncer_message_view, this)
-    }
-
-    var primaryMessageView: BouncerKeyguardMessageArea? = null
-    var secondaryMessageView: BouncerKeyguardMessageArea? = null
-    var primaryMessage: KeyguardMessageAreaController<KeyguardMessageArea>? = null
-    var secondaryMessage: KeyguardMessageAreaController<KeyguardMessageArea>? = null
-    override fun onFinishInflate() {
-        super.onFinishInflate()
-        primaryMessageView = findViewById(R.id.bouncer_primary_message_area)
-        secondaryMessageView = findViewById(R.id.bouncer_secondary_message_area)
-        primaryMessageView?.disable()
-        secondaryMessageView?.disable()
-    }
-
-    fun init(factory: KeyguardMessageAreaController.Factory) {
-        primaryMessage = factory.create(primaryMessageView)
-        primaryMessage?.init()
-        secondaryMessage = factory.create(secondaryMessageView)
-        secondaryMessage?.init()
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardFaceAuthNotSupportedModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardFaceAuthNotSupportedModule.kt
index a44df7e..d8eb81c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardFaceAuthNotSupportedModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardFaceAuthNotSupportedModule.kt
@@ -16,6 +16,8 @@
 
 package com.android.systemui.keyguard.dagger
 
+import com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthRepository
+import com.android.systemui.keyguard.data.repository.NoopDeviceEntryFaceAuthRepository
 import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor
 import com.android.systemui.keyguard.domain.interactor.NoopKeyguardFaceAuthInteractor
 import dagger.Binds
@@ -32,4 +34,9 @@
 interface KeyguardFaceAuthNotSupportedModule {
     @Binds
     fun keyguardFaceAuthInteractor(impl: NoopKeyguardFaceAuthInteractor): KeyguardFaceAuthInteractor
+
+    @Binds
+    fun deviceEntryFaceAuthRepository(
+        impl: NoopDeviceEntryFaceAuthRepository
+    ): DeviceEntryFaceAuthRepository
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index 1f121e9..a5ac7c7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.keyguard.dagger;
 
+import android.app.IActivityTaskManager;
 import android.app.trust.TrustManager;
 import android.content.Context;
 import android.os.PowerManager;
@@ -50,7 +51,6 @@
 import com.android.systemui.keyguard.data.repository.KeyguardFaceAuthModule;
 import com.android.systemui.keyguard.data.repository.KeyguardRepositoryModule;
 import com.android.systemui.keyguard.domain.interactor.StartKeyguardTransitionModule;
-import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceModule;
 import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLogger;
 import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLoggerImpl;
 import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel;
@@ -67,6 +67,11 @@
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.UserSwitcherController;
 import com.android.systemui.util.DeviceConfigProxy;
+import com.android.systemui.util.kotlin.JavaAdapter;
+import com.android.systemui.util.settings.SecureSettings;
+import com.android.systemui.util.settings.SystemSettings;
+import com.android.systemui.util.time.SystemClock;
+import com.android.systemui.wallpapers.data.repository.WallpaperRepository;
 import com.android.wm.shell.keyguard.KeyguardTransitions;
 
 import dagger.Lazy;
@@ -88,7 +93,6 @@
         includes = {
             FalsingModule.class,
             KeyguardDataQuickAffordanceModule.class,
-            KeyguardQuickAffordanceModule.class,
             KeyguardRepositoryModule.class,
             KeyguardFaceAuthModule.class,
             StartKeyguardTransitionModule.class,
@@ -129,11 +133,17 @@
             KeyguardTransitions keyguardTransitions,
             InteractionJankMonitor interactionJankMonitor,
             DreamOverlayStateController dreamOverlayStateController,
+            JavaAdapter javaAdapter,
+            WallpaperRepository wallpaperRepository,
             Lazy<ShadeController> shadeController,
             Lazy<NotificationShadeWindowController> notificationShadeWindowController,
             Lazy<ActivityLaunchAnimator> activityLaunchAnimator,
             Lazy<ScrimController> scrimControllerLazy,
+            IActivityTaskManager activityTaskManagerService,
             FeatureFlags featureFlags,
+            SecureSettings secureSettings,
+            SystemSettings systemSettings,
+            SystemClock systemClock,
             @Main CoroutineDispatcher mainDispatcher,
             Lazy<DreamingToLockscreenTransitionViewModel> dreamingToLockscreenTransitionViewModel,
             SystemPropertiesHelper systemPropertiesHelper) {
@@ -166,11 +176,17 @@
                 keyguardTransitions,
                 interactionJankMonitor,
                 dreamOverlayStateController,
+                javaAdapter,
+                wallpaperRepository,
                 shadeController,
                 notificationShadeWindowController,
                 activityLaunchAnimator,
                 scrimControllerLazy,
+                activityTaskManagerService,
                 featureFlags,
+                secureSettings,
+                systemSettings,
+                systemClock,
                 mainDispatcher,
                 dreamingToLockscreenTransitionViewModel,
                 systemPropertiesHelper);
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerView.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerView.kt
deleted file mode 100644
index 871a3ff..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerView.kt
+++ /dev/null
@@ -1,59 +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.systemui.keyguard.data
-
-import android.view.KeyEvent
-import android.window.OnBackAnimationCallback
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.plugins.ActivityStarter
-import java.lang.ref.WeakReference
-import javax.inject.Inject
-
-/** An abstraction to interface with the ui layer, without changing state. */
-interface BouncerView {
-    var delegate: BouncerViewDelegate?
-}
-
-/** A lightweight class to hold reference to the ui delegate. */
-@SysUISingleton
-class BouncerViewImpl @Inject constructor() : BouncerView {
-    private var _delegate: WeakReference<BouncerViewDelegate?> = WeakReference(null)
-    override var delegate: BouncerViewDelegate?
-        get() = _delegate.get()
-        set(value) {
-            _delegate = WeakReference(value)
-        }
-}
-
-/** An abstraction that implements view logic. */
-interface BouncerViewDelegate {
-    fun isFullScreenBouncer(): Boolean
-    fun shouldDismissOnMenuPressed(): Boolean
-    fun interceptMediaKey(event: KeyEvent?): Boolean
-    fun dispatchBackKeyEventPreIme(): Boolean
-    fun showNextSecurityScreenOrFinish(): Boolean
-    fun resume()
-    fun setDismissAction(
-        onDismissAction: ActivityStarter.OnDismissAction?,
-        cancelAction: Runnable?,
-    )
-    fun willDismissWithActions(): Boolean
-    fun willRunDismissFromKeyguard(): Boolean
-    /** @return the {@link OnBackAnimationCallback} to animate Bouncer during a back gesture. */
-    fun getBackCallback(): OnBackAnimationCallback
-    fun showPromptReason(reason: Int)
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerViewModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerViewModule.kt
deleted file mode 100644
index 390c54e..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerViewModule.kt
+++ /dev/null
@@ -1,26 +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.systemui.keyguard.data
-
-import dagger.Binds
-import dagger.Module
-
-@Module
-interface BouncerViewModule {
-    /** Binds BouncerView to BouncerViewImpl and makes it injectable. */
-    @Binds fun bindBouncerView(bouncerViewImpl: BouncerViewImpl): BouncerView
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartable.kt
index cd0805e..7dbe945 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartable.kt
@@ -24,8 +24,6 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
 import com.android.systemui.settings.UserFileManager
 import com.android.systemui.settings.UserTracker
@@ -42,7 +40,6 @@
  */
 @SysUISingleton
 class MuteQuickAffordanceCoreStartable @Inject constructor(
-    private val featureFlags: FeatureFlags,
     private val userTracker: UserTracker,
     private val ringerModeTracker: RingerModeTracker,
     private val userFileManager: UserFileManager,
@@ -54,8 +51,6 @@
     private val observer = Observer(this::updateLastNonSilentRingerMode)
 
     override fun start() {
-        if (!featureFlags.isEnabled(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES)) return
-
         // only listen to ringerModeInternal changes when Mute is one of the selected affordances
         keyguardQuickAffordanceRepository
             .selections
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt
index 0b6c7c4..ff3e77c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt
@@ -325,6 +325,9 @@
 private class StrongAuthTracker(private val userRepository: UserRepository, context: Context?) :
     LockPatternUtils.StrongAuthTracker(context) {
 
+    private val selectedUserId =
+        userRepository.selectedUserInfo.map { it.id }.distinctUntilChanged()
+
     // Backing field for onStrongAuthRequiredChanged
     private val _authFlags =
         MutableStateFlow(AuthenticationFlags(currentUserId, getStrongAuthForUser(currentUserId)))
@@ -336,15 +339,12 @@
         )
 
     val currentUserAuthFlags: Flow<AuthenticationFlags> =
-        userRepository.selectedUserInfo
-            .map { it.id }
-            .distinctUntilChanged()
-            .flatMapLatest { userId ->
-                _authFlags
-                    .map { AuthenticationFlags(userId, getStrongAuthForUser(userId)) }
-                    .onEach { Log.d(TAG, "currentUser authFlags changed, new value: $it") }
-                    .onStart { emit(AuthenticationFlags(userId, getStrongAuthForUser(userId))) }
-            }
+        selectedUserId.flatMapLatest { userId ->
+            _authFlags
+                .map { AuthenticationFlags(userId, getStrongAuthForUser(userId)) }
+                .onEach { Log.d(TAG, "currentUser authFlags changed, new value: $it") }
+                .onStart { emit(AuthenticationFlags(userId, getStrongAuthForUser(userId))) }
+        }
 
     /** isStrongBiometricAllowed for the current user. */
     val isStrongBiometricAllowed: Flow<Boolean> =
@@ -352,16 +352,17 @@
 
     /** isNonStrongBiometricAllowed for the current user. */
     val isNonStrongBiometricAllowed: Flow<Boolean> =
-        userRepository.selectedUserInfo
-            .map { it.id }
-            .distinctUntilChanged()
+        selectedUserId
             .flatMapLatest { userId ->
                 _nonStrongBiometricAllowed
                     .filter { it.first == userId }
                     .map { it.second }
-                    .onEach { Log.d(TAG, "isNonStrongBiometricAllowed changed for current user") }
+                    .onEach {
+                        Log.d(TAG, "isNonStrongBiometricAllowed changed for current user: $it")
+                    }
                     .onStart { emit(isNonStrongBiometricAllowedAfterIdleTimeout(userId)) }
             }
+            .and(isStrongBiometricAllowed)
 
     private val currentUserId
         get() = userRepository.getSelectedUserInfo().id
@@ -387,3 +388,6 @@
 
 private fun DevicePolicyManager.isNotActive(userId: Int, policy: Int): Boolean =
     (getKeyguardDisabledFeatures(null, userId) and policy) == 0
+
+private fun Flow<Boolean>.and(anotherFlow: Flow<Boolean>): Flow<Boolean> =
+    this.combine(anotherFlow) { a, b -> a && b }
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 9621f03..6edf40f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
@@ -26,24 +26,25 @@
 import com.android.keyguard.FaceAuthUiEvent
 import com.android.systemui.Dumpable
 import com.android.systemui.R
+import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
 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.Application
+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.FeatureFlags
 import com.android.systemui.flags.Flags
-import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.shared.model.AcquiredAuthenticationStatus
-import com.android.systemui.keyguard.shared.model.AuthenticationStatus
-import com.android.systemui.keyguard.shared.model.DetectionStatus
-import com.android.systemui.keyguard.shared.model.ErrorAuthenticationStatus
-import com.android.systemui.keyguard.shared.model.FailedAuthenticationStatus
-import com.android.systemui.keyguard.shared.model.HelpAuthenticationStatus
-import com.android.systemui.keyguard.shared.model.SuccessAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.AcquiredFaceAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.ErrorFaceAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.FaceAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.FaceDetectionStatus
+import com.android.systemui.keyguard.shared.model.FailedFaceAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.HelpFaceAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.SuccessFaceAuthenticationStatus
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.log.FaceAuthenticationLogger
 import com.android.systemui.log.SessionTracker
@@ -87,10 +88,10 @@
     val canRunFaceAuth: StateFlow<Boolean>
 
     /** Provide the current status of face authentication. */
-    val authenticationStatus: Flow<AuthenticationStatus>
+    val authenticationStatus: Flow<FaceAuthenticationStatus>
 
     /** Provide the current status of face detection. */
-    val detectionStatus: Flow<DetectionStatus>
+    val detectionStatus: Flow<FaceDetectionStatus>
 
     /** Current state of whether face authentication is locked out or not. */
     val isLockedOut: StateFlow<Boolean>
@@ -126,6 +127,7 @@
     private val keyguardBypassController: KeyguardBypassController? = null,
     @Application private val applicationScope: CoroutineScope,
     @Main private val mainDispatcher: CoroutineDispatcher,
+    @Background private val backgroundDispatcher: CoroutineDispatcher,
     private val sessionTracker: SessionTracker,
     private val uiEventsLogger: UiEventLogger,
     private val faceAuthLogger: FaceAuthenticationLogger,
@@ -149,13 +151,13 @@
     private var cancelNotReceivedHandlerJob: Job? = null
     private var halErrorRetryJob: Job? = null
 
-    private val _authenticationStatus: MutableStateFlow<AuthenticationStatus?> =
+    private val _authenticationStatus: MutableStateFlow<FaceAuthenticationStatus?> =
         MutableStateFlow(null)
-    override val authenticationStatus: Flow<AuthenticationStatus>
+    override val authenticationStatus: Flow<FaceAuthenticationStatus>
         get() = _authenticationStatus.filterNotNull()
 
-    private val _detectionStatus = MutableStateFlow<DetectionStatus?>(null)
-    override val detectionStatus: Flow<DetectionStatus>
+    private val _detectionStatus = MutableStateFlow<FaceDetectionStatus?>(null)
+    override val detectionStatus: Flow<FaceDetectionStatus>
         get() = _detectionStatus.filterNotNull()
 
     private val _isLockedOut = MutableStateFlow(false)
@@ -228,8 +230,12 @@
         keyguardTransitionInteractor.anyStateToGoneTransition
             .filter { it.transitionState == TransitionState.FINISHED }
             .onEach {
-                faceAuthLogger.watchdogScheduled()
-                faceManager?.scheduleWatchdog()
+                // We deliberately want to run this in background because scheduleWatchdog does
+                // a Binder IPC.
+                withContext(backgroundDispatcher) {
+                    faceAuthLogger.watchdogScheduled()
+                    faceManager?.scheduleWatchdog()
+                }
             }
             .launchIn(applicationScope)
     }
@@ -254,13 +260,17 @@
 
     private fun observeFaceDetectGatingChecks() {
         // Face detection can run only when lockscreen bypass is enabled
-        // & detection is supported & biometric unlock is not allowed.
+        // & detection is supported
+        //   & biometric unlock is not allowed
+        //     or user is trusted by trust manager & we want to run face detect to dismiss keyguard
         listOf(
                 canFaceAuthOrDetectRun(faceDetectLog),
                 logAndObserve(isBypassEnabled, "isBypassEnabled", faceDetectLog),
                 logAndObserve(
-                    biometricSettingsRepository.isNonStrongBiometricAllowed.isFalse(),
-                    "nonStrongBiometricIsNotAllowed",
+                    biometricSettingsRepository.isNonStrongBiometricAllowed
+                        .isFalse()
+                        .or(trustRepository.isCurrentUserTrusted),
+                    "nonStrongBiometricIsNotAllowedOrCurrentUserIsTrusted",
                     faceDetectLog
                 ),
                 // We don't want to run face detect if fingerprint can be used to unlock the device
@@ -312,18 +322,19 @@
                     tableLogBuffer
                 ),
                 logAndObserve(
-                    keyguardRepository.wakefulness.map { it.isStartingToSleepOrAsleep() }.isFalse(),
-                    "deviceNotSleepingOrNotStartingToSleep",
+                    keyguardRepository.wakefulness.map { it.isStartingToSleep() }.isFalse(),
+                    "deviceNotStartingToSleep",
                     tableLogBuffer
                 ),
                 logAndObserve(
-                    combine(
-                        keyguardInteractor.isSecureCameraActive,
-                        alternateBouncerInteractor.isVisible
-                    ) { a, b ->
-                        !a || b
-                    },
-                    "secureCameraNotActiveOrAltBouncerIsShowing",
+                    keyguardInteractor.isSecureCameraActive
+                        .isFalse()
+                        .or(
+                            alternateBouncerInteractor.isVisible.or(
+                                keyguardInteractor.primaryBouncerShowing
+                            )
+                        ),
+                    "secureCameraNotActiveOrAnyBouncerIsShowing",
                     tableLogBuffer
                 ),
                 logAndObserve(
@@ -335,6 +346,11 @@
                     biometricSettingsRepository.isCurrentUserInLockdown.isFalse(),
                     "userHasNotLockedDownDevice",
                     tableLogBuffer
+                ),
+                logAndObserve(
+                    keyguardRepository.isKeyguardShowing,
+                    "isKeyguardShowing",
+                    tableLogBuffer
                 )
             )
             .reduce(::and)
@@ -360,6 +376,7 @@
                     "nonStrongBiometricIsAllowed",
                     faceAuthLog
                 ),
+                logAndObserve(isAuthenticated.isFalse(), "faceNotAuthenticated", faceAuthLog),
             )
             .reduce(::and)
             .distinctUntilChanged()
@@ -379,18 +396,18 @@
     private val faceAuthCallback =
         object : FaceManager.AuthenticationCallback() {
             override fun onAuthenticationFailed() {
-                _authenticationStatus.value = FailedAuthenticationStatus
+                _authenticationStatus.value = FailedFaceAuthenticationStatus
                 _isAuthenticated.value = false
                 faceAuthLogger.authenticationFailed()
                 onFaceAuthRequestCompleted()
             }
 
             override fun onAuthenticationAcquired(acquireInfo: Int) {
-                _authenticationStatus.value = AcquiredAuthenticationStatus(acquireInfo)
+                _authenticationStatus.value = AcquiredFaceAuthenticationStatus(acquireInfo)
             }
 
             override fun onAuthenticationError(errorCode: Int, errString: CharSequence?) {
-                val errorStatus = ErrorAuthenticationStatus(errorCode, errString.toString())
+                val errorStatus = ErrorFaceAuthenticationStatus(errorCode, errString.toString())
                 if (errorStatus.isLockoutError()) {
                     _isLockedOut.value = true
                 }
@@ -416,11 +433,11 @@
                 if (faceAcquiredInfoIgnoreList.contains(code)) {
                     return
                 }
-                _authenticationStatus.value = HelpAuthenticationStatus(code, helpStr.toString())
+                _authenticationStatus.value = HelpFaceAuthenticationStatus(code, helpStr.toString())
             }
 
             override fun onAuthenticationSucceeded(result: FaceManager.AuthenticationResult) {
-                _authenticationStatus.value = SuccessAuthenticationStatus(result)
+                _authenticationStatus.value = SuccessFaceAuthenticationStatus(result)
                 _isAuthenticated.value = true
                 faceAuthLogger.faceAuthSuccess(result)
                 onFaceAuthRequestCompleted()
@@ -465,7 +482,7 @@
     private val detectionCallback =
         FaceManager.FaceDetectionCallback { sensorId, userId, isStrong ->
             faceAuthLogger.faceDetected()
-            _detectionStatus.value = DetectionStatus(sensorId, userId, isStrong)
+            _detectionStatus.value = FaceDetectionStatus(sensorId, userId, isStrong)
         }
 
     private var cancellationInProgress = false
@@ -598,7 +615,7 @@
         const val DEFAULT_CANCEL_SIGNAL_TIMEOUT = 3000L
 
         /** Number of allowed retries whenever there is a face hardware error */
-        const val HAL_ERROR_RETRY_MAX = 20
+        const val HAL_ERROR_RETRY_MAX = 5
 
         /** Timeout before retries whenever there is a HAL error. */
         const val HAL_ERROR_RETRY_TIMEOUT = 500L // ms
@@ -634,6 +651,10 @@
 private fun and(flow: Flow<Boolean>, anotherFlow: Flow<Boolean>) =
     flow.combine(anotherFlow) { a, b -> a && b }
 
+/** Combine two boolean flows by or-ing both of them */
+private fun Flow<Boolean>.or(anotherFlow: Flow<Boolean>) =
+    this.combine(anotherFlow) { a, b -> a || b }
+
 /** "Not" the given flow. The return [Flow] will be true when [this] flow is false. */
 private fun Flow<Boolean>.isFalse(): Flow<Boolean> {
     return this.map { !it }
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 52234b3..9bec300 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
@@ -21,27 +21,29 @@
 import android.hardware.biometrics.BiometricSourceType
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.keyguard.KeyguardUpdateMonitorCallback
-import com.android.systemui.Dumpable
 import com.android.systemui.biometrics.AuthController
 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.Application
-import com.android.systemui.dump.DumpManager
-import java.io.PrintWriter
+import com.android.systemui.keyguard.shared.model.AcquiredFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.FingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.HelpFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.stateIn
 
 /** Encapsulates state about device entry fingerprint auth mechanism. */
 interface DeviceEntryFingerprintAuthRepository {
     /** Whether the device entry fingerprint auth is locked out. */
-    val isLockedOut: StateFlow<Boolean>
+    val isLockedOut: Flow<Boolean>
 
     /**
      * Whether the fingerprint sensor is currently listening, this doesn't mean that the user is
@@ -53,6 +55,9 @@
      * Fingerprint sensor type present on the device, null if fingerprint sensor is not available.
      */
     val availableFpSensorType: Flow<BiometricType?>
+
+    /** Provide the current status of fingerprint authentication. */
+    val authenticationStatus: Flow<FingerprintAuthenticationStatus>
 }
 
 /**
@@ -69,16 +74,7 @@
     val authController: AuthController,
     val keyguardUpdateMonitor: KeyguardUpdateMonitor,
     @Application scope: CoroutineScope,
-    dumpManager: DumpManager,
-) : DeviceEntryFingerprintAuthRepository, Dumpable {
-
-    init {
-        dumpManager.registerDumpable(this)
-    }
-
-    override fun dump(pw: PrintWriter, args: Array<String?>) {
-        pw.println("isLockedOut=${isLockedOut.value}")
-    }
+) : DeviceEntryFingerprintAuthRepository {
 
     override val availableFpSensorType: Flow<BiometricType?>
         get() {
@@ -114,7 +110,7 @@
         else if (authController.isRearFpsSupported) BiometricType.REAR_FINGERPRINT else null
     }
 
-    override val isLockedOut: StateFlow<Boolean> =
+    override val isLockedOut: Flow<Boolean> =
         conflatedCallbackFlow {
                 val sendLockoutUpdate =
                     fun() {
@@ -138,7 +134,7 @@
                 sendLockoutUpdate()
                 awaitClose { keyguardUpdateMonitor.removeCallback(callback) }
             }
-            .stateIn(scope, started = SharingStarted.Eagerly, initialValue = false)
+            .stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = false)
 
     override val isRunning: Flow<Boolean>
         get() = conflatedCallbackFlow {
@@ -166,6 +162,93 @@
             awaitClose { keyguardUpdateMonitor.removeCallback(callback) }
         }
 
+    override val authenticationStatus: Flow<FingerprintAuthenticationStatus>
+        get() = conflatedCallbackFlow {
+            val callback =
+                object : KeyguardUpdateMonitorCallback() {
+                    override fun onBiometricAuthenticated(
+                        userId: Int,
+                        biometricSourceType: BiometricSourceType,
+                        isStrongBiometric: Boolean,
+                    ) {
+
+                        sendUpdateIfFingerprint(
+                            biometricSourceType,
+                            SuccessFingerprintAuthenticationStatus(
+                                userId,
+                                isStrongBiometric,
+                            ),
+                        )
+                    }
+
+                    override fun onBiometricError(
+                        msgId: Int,
+                        errString: String?,
+                        biometricSourceType: BiometricSourceType,
+                    ) {
+                        sendUpdateIfFingerprint(
+                            biometricSourceType,
+                            ErrorFingerprintAuthenticationStatus(
+                                msgId,
+                                errString,
+                            ),
+                        )
+                    }
+
+                    override fun onBiometricHelp(
+                        msgId: Int,
+                        helpString: String?,
+                        biometricSourceType: BiometricSourceType,
+                    ) {
+                        sendUpdateIfFingerprint(
+                            biometricSourceType,
+                            HelpFingerprintAuthenticationStatus(
+                                msgId,
+                                helpString,
+                            ),
+                        )
+                    }
+
+                    override fun onBiometricAuthFailed(
+                        biometricSourceType: BiometricSourceType,
+                    ) {
+                        sendUpdateIfFingerprint(
+                            biometricSourceType,
+                            FailFingerprintAuthenticationStatus,
+                        )
+                    }
+
+                    override fun onBiometricAcquired(
+                        biometricSourceType: BiometricSourceType,
+                        acquireInfo: Int,
+                    ) {
+                        sendUpdateIfFingerprint(
+                            biometricSourceType,
+                            AcquiredFingerprintAuthenticationStatus(
+                                acquireInfo,
+                            ),
+                        )
+                    }
+
+                    private fun sendUpdateIfFingerprint(
+                        biometricSourceType: BiometricSourceType,
+                        authenticationStatus: FingerprintAuthenticationStatus
+                    ) {
+                        if (biometricSourceType != BiometricSourceType.FINGERPRINT) {
+                            return
+                        }
+
+                        trySendWithFailureLogging(
+                            authenticationStatus,
+                            TAG,
+                            "new fingerprint authentication status"
+                        )
+                    }
+                }
+            keyguardUpdateMonitor.registerCallback(callback)
+            awaitClose { keyguardUpdateMonitor.removeCallback(callback) }
+        }
+
     companion object {
         const val TAG = "DeviceEntryFingerprintAuthRepositoryImpl"
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt
deleted file mode 100644
index 5d15e69..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt
+++ /dev/null
@@ -1,273 +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.systemui.keyguard.data.repository
-
-import android.os.Build
-import android.util.Log
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN
-import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
-import com.android.systemui.log.dagger.BouncerTableLog
-import com.android.systemui.log.table.TableLogBuffer
-import com.android.systemui.log.table.logDiffsForTable
-import com.android.systemui.util.time.SystemClock
-import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.flow.filterNotNull
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.onEach
-
-/**
- * Encapsulates app state for the lock screen primary and alternate bouncer.
- *
- * Make sure to add newly added flows to the logger.
- */
-interface KeyguardBouncerRepository {
-    /** Values associated with the PrimaryBouncer (pin/pattern/password) input. */
-    val primaryBouncerShow: StateFlow<Boolean>
-    val primaryBouncerShowingSoon: StateFlow<Boolean>
-    val primaryBouncerStartingToHide: StateFlow<Boolean>
-    val primaryBouncerStartingDisappearAnimation: StateFlow<Runnable?>
-    /** Determines if we want to instantaneously show the primary bouncer instead of translating. */
-    val primaryBouncerScrimmed: StateFlow<Boolean>
-    /**
-     * Set how much of the notification panel is showing on the screen.
-     *
-     * ```
-     *      0f = panel fully hidden = bouncer fully showing
-     *      1f = panel fully showing = bouncer fully hidden
-     * ```
-     */
-    val panelExpansionAmount: StateFlow<Float>
-    val keyguardPosition: StateFlow<Float>
-    val isBackButtonEnabled: StateFlow<Boolean?>
-    /** Determines if user is already unlocked */
-    val keyguardAuthenticated: StateFlow<Boolean?>
-    val showMessage: StateFlow<BouncerShowMessageModel?>
-    val resourceUpdateRequests: StateFlow<Boolean>
-    val alternateBouncerVisible: StateFlow<Boolean>
-    val alternateBouncerUIAvailable: StateFlow<Boolean>
-    val sideFpsShowing: StateFlow<Boolean>
-
-    var lastAlternateBouncerVisibleTime: Long
-
-    fun setPrimaryScrimmed(isScrimmed: Boolean)
-
-    fun setPrimaryShow(isShowing: Boolean)
-
-    fun setPrimaryShowingSoon(showingSoon: Boolean)
-
-    fun setPrimaryStartingToHide(startingToHide: Boolean)
-
-    fun setPrimaryStartDisappearAnimation(runnable: Runnable?)
-
-    fun setPanelExpansion(panelExpansion: Float)
-
-    fun setKeyguardPosition(keyguardPosition: Float)
-
-    fun setResourceUpdateRequests(willUpdateResources: Boolean)
-
-    fun setShowMessage(bouncerShowMessageModel: BouncerShowMessageModel?)
-
-    fun setKeyguardAuthenticated(keyguardAuthenticated: Boolean?)
-
-    fun setIsBackButtonEnabled(isBackButtonEnabled: Boolean)
-
-    fun setAlternateVisible(isVisible: Boolean)
-
-    fun setAlternateBouncerUIAvailable(isAvailable: Boolean)
-
-    fun setSideFpsShowing(isShowing: Boolean)
-}
-
-@SysUISingleton
-class KeyguardBouncerRepositoryImpl
-@Inject
-constructor(
-    private val clock: SystemClock,
-    @Application private val applicationScope: CoroutineScope,
-    @BouncerTableLog private val buffer: TableLogBuffer,
-) : KeyguardBouncerRepository {
-    /** Values associated with the PrimaryBouncer (pin/pattern/password) input. */
-    private val _primaryBouncerShow = MutableStateFlow(false)
-    override val primaryBouncerShow = _primaryBouncerShow.asStateFlow()
-    private val _primaryBouncerShowingSoon = MutableStateFlow(false)
-    override val primaryBouncerShowingSoon = _primaryBouncerShowingSoon.asStateFlow()
-    private val _primaryBouncerStartingToHide = MutableStateFlow(false)
-    override val primaryBouncerStartingToHide = _primaryBouncerStartingToHide.asStateFlow()
-    private val _primaryBouncerDisappearAnimation = MutableStateFlow<Runnable?>(null)
-    override val primaryBouncerStartingDisappearAnimation =
-        _primaryBouncerDisappearAnimation.asStateFlow()
-    /** Determines if we want to instantaneously show the primary bouncer instead of translating. */
-    private val _primaryBouncerScrimmed = MutableStateFlow(false)
-    override val primaryBouncerScrimmed = _primaryBouncerScrimmed.asStateFlow()
-    /**
-     * Set how much of the notification panel is showing on the screen.
-     *
-     * ```
-     *      0f = panel fully hidden = bouncer fully showing
-     *      1f = panel fully showing = bouncer fully hidden
-     * ```
-     */
-    private val _panelExpansionAmount = MutableStateFlow(EXPANSION_HIDDEN)
-    override val panelExpansionAmount = _panelExpansionAmount.asStateFlow()
-    private val _keyguardPosition = MutableStateFlow(0f)
-    override val keyguardPosition = _keyguardPosition.asStateFlow()
-    private val _isBackButtonEnabled = MutableStateFlow<Boolean?>(null)
-    override val isBackButtonEnabled = _isBackButtonEnabled.asStateFlow()
-    private val _keyguardAuthenticated = MutableStateFlow<Boolean?>(null)
-    /** Determines if user is already unlocked */
-    override val keyguardAuthenticated = _keyguardAuthenticated.asStateFlow()
-    private val _showMessage = MutableStateFlow<BouncerShowMessageModel?>(null)
-    override val showMessage = _showMessage.asStateFlow()
-    private val _resourceUpdateRequests = MutableStateFlow(false)
-    override val resourceUpdateRequests = _resourceUpdateRequests.asStateFlow()
-    /** Values associated with the AlternateBouncer */
-    private val _alternateBouncerVisible = MutableStateFlow(false)
-    override val alternateBouncerVisible = _alternateBouncerVisible.asStateFlow()
-    override var lastAlternateBouncerVisibleTime: Long = NOT_VISIBLE
-    private val _alternateBouncerUIAvailable = MutableStateFlow(false)
-    override val alternateBouncerUIAvailable: StateFlow<Boolean> =
-        _alternateBouncerUIAvailable.asStateFlow()
-    private val _sideFpsShowing = MutableStateFlow(false)
-    override val sideFpsShowing: StateFlow<Boolean> = _sideFpsShowing.asStateFlow()
-
-    init {
-        setUpLogging()
-    }
-
-    override fun setPrimaryScrimmed(isScrimmed: Boolean) {
-        _primaryBouncerScrimmed.value = isScrimmed
-    }
-
-    override fun setAlternateVisible(isVisible: Boolean) {
-        if (isVisible && !_alternateBouncerVisible.value) {
-            lastAlternateBouncerVisibleTime = clock.uptimeMillis()
-        } else if (!isVisible) {
-            lastAlternateBouncerVisibleTime = NOT_VISIBLE
-        }
-        _alternateBouncerVisible.value = isVisible
-    }
-
-    override fun setAlternateBouncerUIAvailable(isAvailable: Boolean) {
-        _alternateBouncerUIAvailable.value = isAvailable
-    }
-
-    override fun setPrimaryShow(isShowing: Boolean) {
-        _primaryBouncerShow.value = isShowing
-    }
-
-    override fun setPrimaryShowingSoon(showingSoon: Boolean) {
-        _primaryBouncerShowingSoon.value = showingSoon
-    }
-
-    override fun setPrimaryStartingToHide(startingToHide: Boolean) {
-        _primaryBouncerStartingToHide.value = startingToHide
-    }
-
-    override fun setPrimaryStartDisappearAnimation(runnable: Runnable?) {
-        _primaryBouncerDisappearAnimation.value = runnable
-    }
-
-    override fun setPanelExpansion(panelExpansion: Float) {
-        _panelExpansionAmount.value = panelExpansion
-    }
-
-    override fun setKeyguardPosition(keyguardPosition: Float) {
-        _keyguardPosition.value = keyguardPosition
-    }
-
-    override fun setResourceUpdateRequests(willUpdateResources: Boolean) {
-        _resourceUpdateRequests.value = willUpdateResources
-    }
-
-    override fun setShowMessage(bouncerShowMessageModel: BouncerShowMessageModel?) {
-        _showMessage.value = bouncerShowMessageModel
-    }
-
-    override fun setKeyguardAuthenticated(keyguardAuthenticated: Boolean?) {
-        _keyguardAuthenticated.value = keyguardAuthenticated
-    }
-
-    override fun setIsBackButtonEnabled(isBackButtonEnabled: Boolean) {
-        _isBackButtonEnabled.value = isBackButtonEnabled
-    }
-
-    override fun setSideFpsShowing(isShowing: Boolean) {
-        _sideFpsShowing.value = isShowing
-    }
-
-    /** Sets up logs for state flows. */
-    private fun setUpLogging() {
-        if (!Build.IS_DEBUGGABLE) {
-            return
-        }
-
-        primaryBouncerShow
-            .logDiffsForTable(buffer, "", "PrimaryBouncerShow", false)
-            .onEach { Log.d(TAG, "Keyguard Bouncer is ${if (it) "showing" else "hiding."}") }
-            .launchIn(applicationScope)
-        primaryBouncerShowingSoon
-            .logDiffsForTable(buffer, "", "PrimaryBouncerShowingSoon", false)
-            .launchIn(applicationScope)
-        primaryBouncerStartingToHide
-            .logDiffsForTable(buffer, "", "PrimaryBouncerStartingToHide", false)
-            .launchIn(applicationScope)
-        primaryBouncerStartingDisappearAnimation
-            .map { it != null }
-            .logDiffsForTable(buffer, "", "PrimaryBouncerStartingDisappearAnimation", false)
-            .launchIn(applicationScope)
-        primaryBouncerScrimmed
-            .logDiffsForTable(buffer, "", "PrimaryBouncerScrimmed", false)
-            .launchIn(applicationScope)
-        panelExpansionAmount
-            .map { (it * 1000).toInt() }
-            .logDiffsForTable(buffer, "", "PanelExpansionAmountMillis", -1)
-            .launchIn(applicationScope)
-        keyguardPosition
-            .map { it.toInt() }
-            .logDiffsForTable(buffer, "", "KeyguardPosition", -1)
-            .launchIn(applicationScope)
-        isBackButtonEnabled
-            .filterNotNull()
-            .logDiffsForTable(buffer, "", "IsBackButtonEnabled", false)
-            .launchIn(applicationScope)
-        showMessage
-            .map { it?.message }
-            .logDiffsForTable(buffer, "", "ShowMessage", null)
-            .launchIn(applicationScope)
-        resourceUpdateRequests
-            .logDiffsForTable(buffer, "", "ResourceUpdateRequests", false)
-            .launchIn(applicationScope)
-        alternateBouncerUIAvailable
-            .logDiffsForTable(buffer, "", "IsAlternateBouncerUIAvailable", false)
-            .launchIn(applicationScope)
-        sideFpsShowing
-            .logDiffsForTable(buffer, "", "isSideFpsShowing", false)
-            .launchIn(applicationScope)
-    }
-
-    companion object {
-        private const val NOT_VISIBLE = -1L
-        private const val TAG = "KeyguardBouncerRepositoryImpl"
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index 742e535..7475c42 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -25,32 +25,41 @@
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
 import com.android.systemui.common.shared.model.Position
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.doze.DozeMachine
 import com.android.systemui.doze.DozeTransitionCallback
 import com.android.systemui.doze.DozeTransitionListener
 import com.android.systemui.dreams.DreamOverlayCallbackController
+import com.android.systemui.keyguard.ScreenLifecycle
 import com.android.systemui.keyguard.WakefulnessLifecycle
 import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
 import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
 import com.android.systemui.keyguard.shared.model.DozeStateModel
 import com.android.systemui.keyguard.shared.model.DozeTransitionModel
+import com.android.systemui.keyguard.shared.model.ScreenModel
 import com.android.systemui.keyguard.shared.model.StatusBarState
 import com.android.systemui.keyguard.shared.model.WakefulnessModel
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.phone.BiometricUnlockController
 import com.android.systemui.statusbar.phone.BiometricUnlockController.WakeAndUnlockMode
 import com.android.systemui.statusbar.phone.DozeParameters
+import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.statusbar.policy.KeyguardStateController
 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.MutableSharedFlow
 import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asSharedFlow
 import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.stateIn
 
 /** Defines interface for classes that encapsulate application state for the keyguard. */
 interface KeyguardRepository {
@@ -83,7 +92,7 @@
     val isKeyguardShowing: Flow<Boolean>
 
     /** Is the keyguard in a unlocked state? */
-    val isKeyguardUnlocked: Flow<Boolean>
+    val isKeyguardUnlocked: StateFlow<Boolean>
 
     /** Is an activity showing over the keyguard? */
     val isKeyguardOccluded: Flow<Boolean>
@@ -117,6 +126,9 @@
     /** Observable for whether the device is dreaming with an overlay, see [DreamOverlayService] */
     val isDreamingWithOverlay: Flow<Boolean>
 
+    /** Observable for device dreaming state and the active dream is hosted in lockscreen */
+    val isActiveDreamLockscreenHosted: StateFlow<Boolean>
+
     /**
      * Observable for the amount of doze we are currently in.
      *
@@ -138,7 +150,10 @@
     val statusBarState: Flow<StatusBarState>
 
     /** Observable for device wake/sleep state */
-    val wakefulness: Flow<WakefulnessModel>
+    val wakefulness: StateFlow<WakefulnessModel>
+
+    /** Observable for device screen state */
+    val screenModel: StateFlow<ScreenModel>
 
     /** Observable for biometric unlock modes */
     val biometricUnlockState: Flow<BiometricUnlockModel>
@@ -155,6 +170,9 @@
     /** Whether quick settings or quick-quick settings is visible. */
     val isQuickSettingsVisible: Flow<Boolean>
 
+    /** Receive an event for doze time tick */
+    val dozeTimeTick: Flow<Unit>
+
     /**
      * Returns `true` if the keyguard is showing; `false` otherwise.
      *
@@ -164,6 +182,14 @@
      */
     fun isKeyguardShowing(): Boolean
 
+    /**
+     * Whether lock screen bypass is enabled. When enabled, the lock screen will be automatically
+     * dismissed once the authentication challenge is completed. For example, completing a biometric
+     * authentication challenge via face unlock or fingerprint sensor can automatically bypass the
+     * lock screen.
+     */
+    fun isBypassEnabled(): Boolean
+
     /** Sets whether the bottom area UI should animate the transition out of doze state. */
     fun setAnimateDozingTransitions(animate: Boolean)
 
@@ -186,6 +212,10 @@
     fun setLastDozeTapToWakePosition(position: Point)
 
     fun setIsDozing(isDozing: Boolean)
+
+    fun setIsActiveDreamLockscreenHosted(isLockscreenHosted: Boolean)
+
+    fun dozeTimeTick()
 }
 
 /** Encapsulates application state for the keyguard. */
@@ -195,14 +225,17 @@
 constructor(
     statusBarStateController: StatusBarStateController,
     wakefulnessLifecycle: WakefulnessLifecycle,
+    screenLifecycle: ScreenLifecycle,
     biometricUnlockController: BiometricUnlockController,
     private val keyguardStateController: KeyguardStateController,
+    private val keyguardBypassController: KeyguardBypassController,
     private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
     private val dozeTransitionListener: DozeTransitionListener,
     private val dozeParameters: DozeParameters,
     private val authController: AuthController,
     private val dreamOverlayCallbackController: DreamOverlayCallbackController,
-    @Main private val mainDispatcher: CoroutineDispatcher
+    @Main private val mainDispatcher: CoroutineDispatcher,
+    @Application private val scope: CoroutineScope,
 ) : KeyguardRepository {
     private val _animateBottomAreaDozingTransitions = MutableStateFlow(false)
     override val animateBottomAreaDozingTransitions =
@@ -242,23 +275,17 @@
     override val isAodAvailable: Flow<Boolean> =
         conflatedCallbackFlow {
                 val callback =
-                    object : DozeParameters.Callback {
-                        override fun onAlwaysOnChange() {
-                            trySendWithFailureLogging(
-                                dozeParameters.getAlwaysOn(),
-                                TAG,
-                                "updated isAodAvailable"
-                            )
-                        }
+                    DozeParameters.Callback {
+                        trySendWithFailureLogging(
+                            dozeParameters.alwaysOn,
+                            TAG,
+                            "updated isAodAvailable"
+                        )
                     }
 
                 dozeParameters.addCallback(callback)
                 // Adding the callback does not send an initial update.
-                trySendWithFailureLogging(
-                    dozeParameters.getAlwaysOn(),
-                    TAG,
-                    "initial isAodAvailable"
-                )
+                trySendWithFailureLogging(dozeParameters.alwaysOn, TAG, "initial isAodAvailable")
 
                 awaitClose { dozeParameters.removeCallback(callback) }
             }
@@ -289,7 +316,7 @@
             }
             .distinctUntilChanged()
 
-    override val isKeyguardUnlocked: Flow<Boolean> =
+    override val isKeyguardUnlocked: StateFlow<Boolean> =
         conflatedCallbackFlow {
                 val callback =
                     object : KeyguardStateController.Callback {
@@ -297,7 +324,15 @@
                             trySendWithFailureLogging(
                                 keyguardStateController.isUnlocked,
                                 TAG,
-                                "updated isKeyguardUnlocked"
+                                "updated isKeyguardUnlocked due to onUnlockedChanged"
+                            )
+                        }
+
+                        override fun onKeyguardShowingChanged() {
+                            trySendWithFailureLogging(
+                                keyguardStateController.isUnlocked,
+                                TAG,
+                                "updated isKeyguardUnlocked due to onKeyguardShowingChanged"
                             )
                         }
                     }
@@ -312,7 +347,11 @@
 
                 awaitClose { keyguardStateController.removeCallback(callback) }
             }
-            .distinctUntilChanged()
+            .stateIn(
+                scope = scope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = keyguardStateController.isUnlocked,
+            )
 
     override val isKeyguardGoingAway: Flow<Boolean> = conflatedCallbackFlow {
         val callback =
@@ -344,6 +383,13 @@
         _isDozing.value = isDozing
     }
 
+    private val _dozeTimeTick = MutableSharedFlow<Unit>()
+    override val dozeTimeTick = _dozeTimeTick.asSharedFlow()
+
+    override fun dozeTimeTick() {
+        _dozeTimeTick.tryEmit(Unit)
+    }
+
     private val _lastDozeTapToWakePosition = MutableStateFlow<Point?>(null)
     override val lastDozeTapToWakePosition = _lastDozeTapToWakePosition.asStateFlow()
 
@@ -442,6 +488,10 @@
         return keyguardStateController.isShowing
     }
 
+    override fun isBypassEnabled(): Boolean {
+        return keyguardBypassController.bypassEnabled
+    }
+
     override val statusBarState: Flow<StatusBarState> = conflatedCallbackFlow {
         val callback =
             object : StatusBarStateController.StateListener {
@@ -486,47 +536,84 @@
         awaitClose { biometricUnlockController.removeListener(callback) }
     }
 
-    override val wakefulness: Flow<WakefulnessModel> = conflatedCallbackFlow {
-        val observer =
-            object : WakefulnessLifecycle.Observer {
-                override fun onStartedWakingUp() {
-                    dispatchNewState()
-                }
+    override val wakefulness: StateFlow<WakefulnessModel> =
+        conflatedCallbackFlow {
+                val observer =
+                    object : WakefulnessLifecycle.Observer {
+                        override fun onStartedWakingUp() {
+                            dispatchNewState()
+                        }
 
-                override fun onFinishedWakingUp() {
-                    dispatchNewState()
-                }
+                        override fun onFinishedWakingUp() {
+                            dispatchNewState()
+                        }
 
-                override fun onPostFinishedWakingUp() {
-                    dispatchNewState()
-                }
+                        override fun onPostFinishedWakingUp() {
+                            dispatchNewState()
+                        }
 
-                override fun onStartedGoingToSleep() {
-                    dispatchNewState()
-                }
+                        override fun onStartedGoingToSleep() {
+                            dispatchNewState()
+                        }
 
-                override fun onFinishedGoingToSleep() {
-                    dispatchNewState()
-                }
+                        override fun onFinishedGoingToSleep() {
+                            dispatchNewState()
+                        }
 
-                private fun dispatchNewState() {
-                    trySendWithFailureLogging(
-                        WakefulnessModel.fromWakefulnessLifecycle(wakefulnessLifecycle),
-                        TAG,
-                        "updated wakefulness state"
-                    )
-                }
+                        private fun dispatchNewState() {
+                            trySendWithFailureLogging(
+                                WakefulnessModel.fromWakefulnessLifecycle(wakefulnessLifecycle),
+                                TAG,
+                                "updated wakefulness state",
+                            )
+                        }
+                    }
+
+                wakefulnessLifecycle.addObserver(observer)
+                awaitClose { wakefulnessLifecycle.removeObserver(observer) }
             }
+            .stateIn(
+                scope,
+                // Use Eagerly so that we're always listening and never miss an event.
+                SharingStarted.Eagerly,
+                initialValue = WakefulnessModel.fromWakefulnessLifecycle(wakefulnessLifecycle),
+            )
 
-        wakefulnessLifecycle.addObserver(observer)
-        trySendWithFailureLogging(
-            WakefulnessModel.fromWakefulnessLifecycle(wakefulnessLifecycle),
-            TAG,
-            "initial wakefulness state"
-        )
+    override val screenModel: StateFlow<ScreenModel> =
+        conflatedCallbackFlow {
+                val observer =
+                    object : ScreenLifecycle.Observer {
+                        override fun onScreenTurningOn() {
+                            dispatchNewState()
+                        }
+                        override fun onScreenTurnedOn() {
+                            dispatchNewState()
+                        }
+                        override fun onScreenTurningOff() {
+                            dispatchNewState()
+                        }
+                        override fun onScreenTurnedOff() {
+                            dispatchNewState()
+                        }
 
-        awaitClose { wakefulnessLifecycle.removeObserver(observer) }
-    }
+                        private fun dispatchNewState() {
+                            trySendWithFailureLogging(
+                                ScreenModel.fromScreenLifecycle(screenLifecycle),
+                                TAG,
+                                "updated screen state",
+                            )
+                        }
+                    }
+
+                screenLifecycle.addObserver(observer)
+                awaitClose { screenLifecycle.removeObserver(observer) }
+            }
+            .stateIn(
+                scope,
+                // Use Eagerly so that we're always listening and never miss an event.
+                SharingStarted.Eagerly,
+                initialValue = ScreenModel.fromScreenLifecycle(screenLifecycle),
+            )
 
     override val fingerprintSensorLocation: Flow<Point?> = conflatedCallbackFlow {
         fun sendFpLocation() {
@@ -596,6 +683,9 @@
     private val _isQuickSettingsVisible = MutableStateFlow(false)
     override val isQuickSettingsVisible: Flow<Boolean> = _isQuickSettingsVisible.asStateFlow()
 
+    private val _isActiveDreamLockscreenHosted = MutableStateFlow(false)
+    override val isActiveDreamLockscreenHosted = _isActiveDreamLockscreenHosted.asStateFlow()
+
     override fun setAnimateDozingTransitions(animate: Boolean) {
         _animateBottomAreaDozingTransitions.value = animate
     }
@@ -614,6 +704,10 @@
         _isQuickSettingsVisible.value = isVisible
     }
 
+    override fun setIsActiveDreamLockscreenHosted(isLockscreenHosted: Boolean) {
+        _isActiveDreamLockscreenHosted.value = isLockscreenHosted
+    }
+
     private fun statusBarStateIntToObject(value: Int): StatusBarState {
         return when (value) {
             0 -> StatusBarState.SHADE
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
index 4055fd0..246ee33 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
@@ -17,9 +17,11 @@
 package com.android.systemui.keyguard.data.repository
 
 import com.android.systemui.CoreStartable
-import com.android.systemui.keyguard.bouncer.data.repository.BouncerMessageRepository
-import com.android.systemui.keyguard.bouncer.data.repository.BouncerMessageRepositoryImpl
-import com.android.systemui.keyguard.bouncer.domain.interactor.BouncerMessageAuditLogger
+import com.android.systemui.bouncer.data.repository.BouncerMessageRepository
+import com.android.systemui.bouncer.data.repository.BouncerMessageRepositoryImpl
+import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
+import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepositoryImpl
+import com.android.systemui.bouncer.domain.interactor.BouncerMessageAuditLogger
 import dagger.Binds
 import dagger.Module
 import dagger.multibindings.ClassKey
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
index 358ab01..84cd3ef 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
@@ -47,6 +47,11 @@
  * To create or modify logic that controls when and how transitions get created, look at
  * [TransitionInteractor]. These interactors will call [startTransition] and [updateTransition] on
  * this repository.
+ *
+ * To print all transitions to logcat to help with debugging, run this command:
+ * adb shell settings put global systemui/buffer/KeyguardLog VERBOSE
+ *
+ * This will print all keyguard transitions to logcat with the KeyguardTransitionAuditLogger tag.
  */
 interface KeyguardTransitionRepository {
     /**
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
new file mode 100644
index 0000000..27e3a74
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/NoopDeviceEntryFaceAuthRepository.kt
@@ -0,0 +1,71 @@
+/*
+ *   Copyright (C) 2023 The Android Open Source Project
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+import com.android.keyguard.FaceAuthUiEvent
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.shared.model.FaceAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.FaceDetectionStatus
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.emptyFlow
+
+/**
+ * Implementation of the repository that noops all face auth operations.
+ *
+ * This is required for SystemUI variants that do not support face authentication but still inject
+ * other SysUI components that depend on [DeviceEntryFaceAuthRepository].
+ */
+@SysUISingleton
+class NoopDeviceEntryFaceAuthRepository @Inject constructor() : DeviceEntryFaceAuthRepository {
+    override val isAuthenticated: Flow<Boolean>
+        get() = emptyFlow()
+
+    private val _canRunFaceAuth = MutableStateFlow(false)
+    override val canRunFaceAuth: StateFlow<Boolean> = _canRunFaceAuth
+
+    override val authenticationStatus: Flow<FaceAuthenticationStatus>
+        get() = emptyFlow()
+
+    override val detectionStatus: Flow<FaceDetectionStatus>
+        get() = emptyFlow()
+
+    private val _isLockedOut = MutableStateFlow(false)
+    override val isLockedOut: StateFlow<Boolean> = _isLockedOut
+
+    private val _isAuthRunning = MutableStateFlow(false)
+    override val isAuthRunning: StateFlow<Boolean> = _isAuthRunning
+
+    override val isBypassEnabled: Flow<Boolean>
+        get() = emptyFlow()
+
+    /**
+     * Trigger face authentication.
+     *
+     * [uiEvent] provided should be logged whenever face authentication runs. Invocation should be
+     * ignored if face authentication is already running. Results should be propagated through
+     * [authenticationStatus]
+     *
+     * Run only face detection when [fallbackToDetection] is true and [canRunFaceAuth] is false.
+     */
+    override suspend fun authenticate(uiEvent: FaceAuthUiEvent, fallbackToDetection: Boolean) {}
+
+    /** Stop currently running face authentication or detection. */
+    override fun cancel() {}
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractor.kt
deleted file mode 100644
index 148d425..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractor.kt
+++ /dev/null
@@ -1,111 +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.systemui.keyguard.domain.interactor
-
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
-import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
-import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository
-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.flow.Flow
-
-/** Encapsulates business logic for interacting with the lock-screen alternate bouncer. */
-@SysUISingleton
-class AlternateBouncerInteractor
-@Inject
-constructor(
-    private val statusBarStateController: StatusBarStateController,
-    private val keyguardStateController: KeyguardStateController,
-    private val bouncerRepository: KeyguardBouncerRepository,
-    private val biometricSettingsRepository: BiometricSettingsRepository,
-    private val deviceEntryFingerprintAuthRepository: DeviceEntryFingerprintAuthRepository,
-    private val systemClock: SystemClock,
-) {
-    var receivedDownTouch = false
-    val isVisible: Flow<Boolean> = bouncerRepository.alternateBouncerVisible
-
-    /**
-     * Sets the correct bouncer states to show the alternate bouncer if it can show.
-     *
-     * @return whether alternateBouncer is visible
-     */
-    fun show(): Boolean {
-        bouncerRepository.setAlternateVisible(canShowAlternateBouncerForFingerprint())
-        return isVisibleState()
-    }
-
-    /**
-     * Sets the correct bouncer states to hide the bouncer. Should only be called through
-     * StatusBarKeyguardViewManager until ScrimController is refactored to use
-     * alternateBouncerInteractor.
-     *
-     * @return true if the alternate bouncer was newly hidden, else false.
-     */
-    fun hide(): Boolean {
-        receivedDownTouch = false
-        val wasAlternateBouncerVisible = isVisibleState()
-        bouncerRepository.setAlternateVisible(false)
-        return wasAlternateBouncerVisible && !isVisibleState()
-    }
-
-    fun isVisibleState(): Boolean {
-        return bouncerRepository.alternateBouncerVisible.value
-    }
-
-    fun setAlternateBouncerUIAvailable(isAvailable: Boolean) {
-        bouncerRepository.setAlternateBouncerUIAvailable(isAvailable)
-    }
-
-    fun canShowAlternateBouncerForFingerprint(): Boolean {
-        return bouncerRepository.alternateBouncerUIAvailable.value &&
-            biometricSettingsRepository.isFingerprintEnrolled.value &&
-            biometricSettingsRepository.isStrongBiometricAllowed.value &&
-            biometricSettingsRepository.isFingerprintEnabledByDevicePolicy.value &&
-            !deviceEntryFingerprintAuthRepository.isLockedOut.value &&
-            !keyguardStateController.isUnlocked &&
-            !statusBarStateController.isDozing
-    }
-
-    /**
-     * Whether the alt bouncer has shown for a minimum time before allowing touches to dismiss the
-     * alternate bouncer and show the primary bouncer.
-     */
-    fun hasAlternateBouncerShownWithMinTime(): Boolean {
-        return (systemClock.uptimeMillis() - bouncerRepository.lastAlternateBouncerVisibleTime) >
-            MIN_VISIBILITY_DURATION_UNTIL_TOUCHES_DISMISS_ALTERNATE_BOUNCER_MS
-    }
-    /**
-     * Should only be called through StatusBarKeyguardViewManager which propagates the source of
-     * truth to other concerned controllers. Will hide the alternate bouncer if it's no longer
-     * allowed to show.
-     *
-     * @return true if the alternate bouncer was newly hidden, else false.
-     */
-    fun maybeHide(): Boolean {
-        if (isVisibleState() && !canShowAlternateBouncerForFingerprint()) {
-            return hide()
-        }
-        return false
-    }
-
-    companion object {
-        private const val MIN_VISIBILITY_DURATION_UNTIL_TOUCHES_DISMISS_ALTERNATE_BOUNCER_MS = 200L
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BiometricMessageInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BiometricMessageInteractor.kt
new file mode 100644
index 0000000..c849b84
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BiometricMessageInteractor.kt
@@ -0,0 +1,138 @@
+/*
+ *  Copyright (C) 2023 The Android Open Source Project
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS 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 android.content.res.Resources
+import android.hardware.biometrics.BiometricSourceType
+import android.hardware.biometrics.BiometricSourceType.FINGERPRINT
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED
+import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository
+import com.android.systemui.biometrics.shared.model.FingerprintSensorType
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.HelpFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.util.IndicationHelper
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.filterNot
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.map
+
+/**
+ * BiometricMessage business logic. Filters biometric error/acquired/fail/success events for
+ * authentication events that should never surface a message to the user at the current device
+ * state.
+ */
+@ExperimentalCoroutinesApi
+@SysUISingleton
+class BiometricMessageInteractor
+@Inject
+constructor(
+    @Main private val resources: Resources,
+    private val fingerprintAuthRepository: DeviceEntryFingerprintAuthRepository,
+    private val fingerprintPropertyRepository: FingerprintPropertyRepository,
+    private val indicationHelper: IndicationHelper,
+    private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+) {
+    val fingerprintErrorMessage: Flow<BiometricMessage> =
+        fingerprintAuthRepository.authenticationStatus
+            .filter {
+                it is ErrorFingerprintAuthenticationStatus &&
+                    !indicationHelper.shouldSuppressErrorMsg(FINGERPRINT, it.msgId)
+            }
+            .map {
+                val errorStatus = it as ErrorFingerprintAuthenticationStatus
+                BiometricMessage(
+                    FINGERPRINT,
+                    BiometricMessageType.ERROR,
+                    errorStatus.msgId,
+                    errorStatus.msg,
+                )
+            }
+
+    val fingerprintHelpMessage: Flow<BiometricMessage> =
+        fingerprintAuthRepository.authenticationStatus
+            .filter { it is HelpFingerprintAuthenticationStatus }
+            .filterNot { isPrimaryAuthRequired() }
+            .map {
+                val helpStatus = it as HelpFingerprintAuthenticationStatus
+                BiometricMessage(
+                    FINGERPRINT,
+                    BiometricMessageType.HELP,
+                    helpStatus.msgId,
+                    helpStatus.msg,
+                )
+            }
+
+    val fingerprintFailMessage: Flow<BiometricMessage> =
+        isUdfps().flatMapLatest { isUdfps ->
+            fingerprintAuthRepository.authenticationStatus
+                .filter { it is FailFingerprintAuthenticationStatus }
+                .filterNot { isPrimaryAuthRequired() }
+                .map {
+                    BiometricMessage(
+                        FINGERPRINT,
+                        BiometricMessageType.FAIL,
+                        BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED,
+                        if (isUdfps) {
+                            resources.getString(
+                                com.android.internal.R.string.fingerprint_udfps_error_not_match
+                            )
+                        } else {
+                            resources.getString(
+                                com.android.internal.R.string.fingerprint_error_not_match
+                            )
+                        },
+                    )
+                }
+        }
+
+    private fun isUdfps() =
+        fingerprintPropertyRepository.sensorType.map {
+            it == FingerprintSensorType.UDFPS_OPTICAL ||
+                it == FingerprintSensorType.UDFPS_ULTRASONIC
+        }
+
+    private fun isPrimaryAuthRequired(): Boolean {
+        // Only checking if unlocking with Biometric is allowed (no matter strong or non-strong
+        // as long as primary auth, i.e. PIN/pattern/password, is required), so it's ok to
+        // pass true for isStrongBiometric to isUnlockingWithBiometricAllowed() to bypass the
+        // check of whether non-strong biometric is allowed since strong biometrics can still be
+        // used.
+        return !keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(true /* isStrongBiometric */)
+    }
+}
+
+data class BiometricMessage(
+    val source: BiometricSourceType,
+    val type: BiometricMessageType,
+    val id: Int,
+    val message: String?,
+)
+
+enum class BiometricMessageType {
+    HELP,
+    ERROR,
+    FAIL,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DozeInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DozeInteractor.kt
index 2efcd0c..0c898be 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DozeInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DozeInteractor.kt
@@ -35,4 +35,8 @@
     fun setLastTapToWakePosition(position: Point) {
         keyguardRepository.setLastDozeTapToWakePosition(position)
     }
+
+    fun dozeTimeTick() {
+        keyguardRepository.dozeTimeTick()
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
index 38eacce..8f0b91b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
@@ -22,7 +22,6 @@
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
-import com.android.systemui.keyguard.shared.model.TransitionInfo
 import com.android.systemui.keyguard.shared.model.WakefulnessState
 import com.android.systemui.util.kotlin.Utils.Companion.toQuad
 import com.android.systemui.util.kotlin.Utils.Companion.toQuint
@@ -38,11 +37,14 @@
 class FromAlternateBouncerTransitionInteractor
 @Inject
 constructor(
+    override val transitionRepository: KeyguardTransitionRepository,
+    override val transitionInteractor: KeyguardTransitionInteractor,
     @Application private val scope: CoroutineScope,
     private val keyguardInteractor: KeyguardInteractor,
-    private val keyguardTransitionRepository: KeyguardTransitionRepository,
-    private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
-) : TransitionInteractor(FromAlternateBouncerTransitionInteractor::class.simpleName!!) {
+) :
+    TransitionInteractor(
+        fromState = KeyguardState.ALTERNATE_BOUNCER,
+    ) {
 
     override fun start() {
         listenForAlternateBouncerToGone()
@@ -60,7 +62,7 @@
                 .sample(
                     combine(
                         keyguardInteractor.primaryBouncerShowing,
-                        keyguardTransitionInteractor.startedKeyguardTransitionStep,
+                        transitionInteractor.startedKeyguardTransitionStep,
                         keyguardInteractor.wakefulnessModel,
                         keyguardInteractor.isAodAvailable,
                         ::toQuad
@@ -92,14 +94,7 @@
                             } else {
                                 KeyguardState.LOCKSCREEN
                             }
-                        keyguardTransitionRepository.startTransition(
-                            TransitionInfo(
-                                ownerName = name,
-                                from = KeyguardState.ALTERNATE_BOUNCER,
-                                to = to,
-                                animator = getAnimator(),
-                            )
-                        )
+                        startTransitionTo(to)
                     }
                 }
         }
@@ -108,17 +103,10 @@
     private fun listenForAlternateBouncerToGone() {
         scope.launch {
             keyguardInteractor.isKeyguardGoingAway
-                .sample(keyguardTransitionInteractor.finishedKeyguardState, ::Pair)
+                .sample(transitionInteractor.finishedKeyguardState, ::Pair)
                 .collect { (isKeyguardGoingAway, keyguardState) ->
                     if (isKeyguardGoingAway && keyguardState == KeyguardState.ALTERNATE_BOUNCER) {
-                        keyguardTransitionRepository.startTransition(
-                            TransitionInfo(
-                                ownerName = name,
-                                from = KeyguardState.ALTERNATE_BOUNCER,
-                                to = KeyguardState.GONE,
-                                animator = getAnimator(),
-                            )
-                        )
+                        startTransitionTo(KeyguardState.GONE)
                     }
                 }
         }
@@ -127,26 +115,19 @@
     private fun listenForAlternateBouncerToPrimaryBouncer() {
         scope.launch {
             keyguardInteractor.primaryBouncerShowing
-                .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
+                .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
                 .collect { (isPrimaryBouncerShowing, startedKeyguardState) ->
                     if (
                         isPrimaryBouncerShowing &&
                             startedKeyguardState.to == KeyguardState.ALTERNATE_BOUNCER
                     ) {
-                        keyguardTransitionRepository.startTransition(
-                            TransitionInfo(
-                                ownerName = name,
-                                from = KeyguardState.ALTERNATE_BOUNCER,
-                                to = KeyguardState.PRIMARY_BOUNCER,
-                                animator = getAnimator(),
-                            )
-                        )
+                        startTransitionTo(KeyguardState.PRIMARY_BOUNCER)
                     }
                 }
         }
     }
 
-    private fun getAnimator(): ValueAnimator {
+    override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator {
         return ValueAnimator().apply {
             interpolator = Interpolators.LINEAR
             duration = TRANSITION_DURATION_MS
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
index 7e9cbc1..2085c87 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
@@ -24,7 +24,6 @@
 import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.Companion.isWakeAndUnlock
 import com.android.systemui.keyguard.shared.model.DozeStateModel
 import com.android.systemui.keyguard.shared.model.KeyguardState
-import com.android.systemui.keyguard.shared.model.TransitionInfo
 import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
@@ -34,11 +33,14 @@
 class FromAodTransitionInteractor
 @Inject
 constructor(
+    override val transitionRepository: KeyguardTransitionRepository,
+    override val transitionInteractor: KeyguardTransitionInteractor,
     @Application private val scope: CoroutineScope,
     private val keyguardInteractor: KeyguardInteractor,
-    private val keyguardTransitionRepository: KeyguardTransitionRepository,
-    private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
-) : TransitionInteractor(FromAodTransitionInteractor::class.simpleName!!) {
+) :
+    TransitionInteractor(
+        fromState = KeyguardState.AOD,
+    ) {
 
     override fun start() {
         listenForAodToLockscreen()
@@ -49,18 +51,11 @@
         scope.launch {
             keyguardInteractor
                 .dozeTransitionTo(DozeStateModel.FINISH)
-                .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
+                .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
                 .collect { pair ->
                     val (dozeToAod, lastStartedStep) = pair
                     if (lastStartedStep.to == KeyguardState.AOD) {
-                        keyguardTransitionRepository.startTransition(
-                            TransitionInfo(
-                                name,
-                                KeyguardState.AOD,
-                                KeyguardState.LOCKSCREEN,
-                                getAnimator(),
-                            )
-                        )
+                        startTransitionTo(KeyguardState.LOCKSCREEN)
                     }
                 }
         }
@@ -69,29 +64,22 @@
     private fun listenForAodToGone() {
         scope.launch {
             keyguardInteractor.biometricUnlockState
-                .sample(keyguardTransitionInteractor.finishedKeyguardState, ::Pair)
+                .sample(transitionInteractor.finishedKeyguardState, ::Pair)
                 .collect { pair ->
                     val (biometricUnlockState, keyguardState) = pair
                     if (
                         keyguardState == KeyguardState.AOD && isWakeAndUnlock(biometricUnlockState)
                     ) {
-                        keyguardTransitionRepository.startTransition(
-                            TransitionInfo(
-                                name,
-                                KeyguardState.AOD,
-                                KeyguardState.GONE,
-                                getAnimator(),
-                            )
-                        )
+                        startTransitionTo(KeyguardState.GONE)
                     }
                 }
         }
     }
 
-    private fun getAnimator(): ValueAnimator {
+    override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator {
         return ValueAnimator().apply {
-            setInterpolator(Interpolators.LINEAR)
-            setDuration(TRANSITION_DURATION_MS)
+            interpolator = Interpolators.LINEAR
+            duration = TRANSITION_DURATION_MS
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
index ee2c2df..c867c43 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
@@ -23,10 +23,8 @@
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.Companion.isWakeAndUnlock
 import com.android.systemui.keyguard.shared.model.KeyguardState
-import com.android.systemui.keyguard.shared.model.TransitionInfo
 import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
-import kotlin.time.Duration
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.launch
@@ -35,11 +33,14 @@
 class FromDozingTransitionInteractor
 @Inject
 constructor(
+    override val transitionRepository: KeyguardTransitionRepository,
+    override val transitionInteractor: KeyguardTransitionInteractor,
     @Application private val scope: CoroutineScope,
     private val keyguardInteractor: KeyguardInteractor,
-    private val keyguardTransitionRepository: KeyguardTransitionRepository,
-    private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
-) : TransitionInteractor(FromDozingTransitionInteractor::class.simpleName!!) {
+) :
+    TransitionInteractor(
+        fromState = KeyguardState.DOZING,
+    ) {
 
     override fun start() {
         listenForDozingToLockscreen()
@@ -49,20 +50,13 @@
     private fun listenForDozingToLockscreen() {
         scope.launch {
             keyguardInteractor.wakefulnessModel
-                .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
+                .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
                 .collect { (wakefulnessModel, lastStartedTransition) ->
                     if (
                         wakefulnessModel.isStartingToWakeOrAwake() &&
                             lastStartedTransition.to == KeyguardState.DOZING
                     ) {
-                        keyguardTransitionRepository.startTransition(
-                            TransitionInfo(
-                                name,
-                                KeyguardState.DOZING,
-                                KeyguardState.LOCKSCREEN,
-                                getAnimator(),
-                            )
-                        )
+                        startTransitionTo(KeyguardState.LOCKSCREEN)
                     }
                 }
         }
@@ -71,29 +65,22 @@
     private fun listenForDozingToGone() {
         scope.launch {
             keyguardInteractor.biometricUnlockState
-                .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
+                .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
                 .collect { (biometricUnlockState, lastStartedTransition) ->
                     if (
                         lastStartedTransition.to == KeyguardState.DOZING &&
                             isWakeAndUnlock(biometricUnlockState)
                     ) {
-                        keyguardTransitionRepository.startTransition(
-                            TransitionInfo(
-                                name,
-                                KeyguardState.DOZING,
-                                KeyguardState.GONE,
-                                getAnimator(),
-                            )
-                        )
+                        startTransitionTo(KeyguardState.GONE)
                     }
                 }
         }
     }
 
-    private fun getAnimator(duration: Duration = DEFAULT_DURATION): ValueAnimator {
+    override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator {
         return ValueAnimator().apply {
-            setInterpolator(Interpolators.LINEAR)
-            setDuration(duration.inWholeMilliseconds)
+            interpolator = Interpolators.LINEAR
+            duration = DEFAULT_DURATION.inWholeMilliseconds
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
index ccf4bc1..98d7434 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
@@ -24,11 +24,9 @@
 import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
 import com.android.systemui.keyguard.shared.model.DozeStateModel
 import com.android.systemui.keyguard.shared.model.KeyguardState
-import com.android.systemui.keyguard.shared.model.TransitionInfo
 import com.android.systemui.util.kotlin.Utils.Companion.toTriple
 import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
-import kotlin.time.Duration
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.delay
@@ -40,11 +38,14 @@
 class FromDreamingTransitionInteractor
 @Inject
 constructor(
+    override val transitionRepository: KeyguardTransitionRepository,
+    override val transitionInteractor: KeyguardTransitionInteractor,
     @Application private val scope: CoroutineScope,
     private val keyguardInteractor: KeyguardInteractor,
-    private val keyguardTransitionRepository: KeyguardTransitionRepository,
-    private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
-) : TransitionInteractor(FromDreamingTransitionInteractor::class.simpleName!!) {
+) :
+    TransitionInteractor(
+        fromState = KeyguardState.DREAMING,
+    ) {
 
     override fun start() {
         listenForDreamingToOccluded()
@@ -54,15 +55,8 @@
 
     fun startToLockscreenTransition() {
         scope.launch {
-            if (keyguardTransitionInteractor.startedKeyguardState.value == KeyguardState.DREAMING) {
-                keyguardTransitionRepository.startTransition(
-                    TransitionInfo(
-                        name,
-                        KeyguardState.DREAMING,
-                        KeyguardState.LOCKSCREEN,
-                        getAnimator(TO_LOCKSCREEN_DURATION),
-                    )
-                )
+            if (transitionInteractor.startedKeyguardState.value == KeyguardState.DREAMING) {
+                startTransitionTo(KeyguardState.LOCKSCREEN)
             }
         }
     }
@@ -76,7 +70,7 @@
                 .sample(
                     combine(
                         keyguardInteractor.isKeyguardOccluded,
-                        keyguardTransitionInteractor.startedKeyguardTransitionStep,
+                        transitionInteractor.startedKeyguardTransitionStep,
                         ::Pair,
                     ),
                     ::toTriple
@@ -92,14 +86,7 @@
                         // action. There's no great signal to determine when the dream is ending
                         // and a transition to OCCLUDED is beginning directly. For now, the solution
                         // is DREAMING->LOCKSCREEN->OCCLUDED
-                        keyguardTransitionRepository.startTransition(
-                            TransitionInfo(
-                                name,
-                                lastStartedTransition.to,
-                                KeyguardState.OCCLUDED,
-                                getAnimator(),
-                            )
-                        )
+                        startTransitionTo(KeyguardState.OCCLUDED)
                     }
                 }
         }
@@ -109,14 +96,7 @@
         scope.launch {
             keyguardInteractor.biometricUnlockState.collect { biometricUnlockState ->
                 if (biometricUnlockState == BiometricUnlockModel.WAKE_AND_UNLOCK_FROM_DREAM) {
-                    keyguardTransitionRepository.startTransition(
-                        TransitionInfo(
-                            name,
-                            KeyguardState.DREAMING,
-                            KeyguardState.GONE,
-                            getAnimator(),
-                        )
-                    )
+                    startTransitionTo(KeyguardState.GONE)
                 }
             }
         }
@@ -126,7 +106,7 @@
         scope.launch {
             combine(
                     keyguardInteractor.dozeTransitionModel,
-                    keyguardTransitionInteractor.finishedKeyguardState,
+                    transitionInteractor.finishedKeyguardState,
                     ::Pair
                 )
                 .collect { (dozeTransitionModel, keyguardState) ->
@@ -134,23 +114,18 @@
                         dozeTransitionModel.to == DozeStateModel.DOZE &&
                             keyguardState == KeyguardState.DREAMING
                     ) {
-                        keyguardTransitionRepository.startTransition(
-                            TransitionInfo(
-                                name,
-                                KeyguardState.DREAMING,
-                                KeyguardState.DOZING,
-                                getAnimator(),
-                            )
-                        )
+                        startTransitionTo(KeyguardState.DOZING)
                     }
                 }
         }
     }
 
-    private fun getAnimator(duration: Duration = DEFAULT_DURATION): ValueAnimator {
+    override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator {
         return ValueAnimator().apply {
-            setInterpolator(Interpolators.LINEAR)
-            setDuration(duration.inWholeMilliseconds)
+            interpolator = Interpolators.LINEAR
+            duration =
+                if (toState == KeyguardState.LOCKSCREEN) TO_LOCKSCREEN_DURATION.inWholeMilliseconds
+                else DEFAULT_DURATION.inWholeMilliseconds
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
index cfcb654..f82633f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
@@ -22,12 +22,10 @@
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
-import com.android.systemui.keyguard.shared.model.TransitionInfo
 import com.android.systemui.keyguard.shared.model.WakefulnessState
 import com.android.systemui.util.kotlin.Utils.Companion.toTriple
 import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
-import kotlin.time.Duration
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.combine
@@ -37,11 +35,14 @@
 class FromGoneTransitionInteractor
 @Inject
 constructor(
+    override val transitionRepository: KeyguardTransitionRepository,
+    override val transitionInteractor: KeyguardTransitionInteractor,
     @Application private val scope: CoroutineScope,
     private val keyguardInteractor: KeyguardInteractor,
-    private val keyguardTransitionRepository: KeyguardTransitionRepository,
-    private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
-) : TransitionInteractor(FromGoneTransitionInteractor::class.simpleName!!) {
+) :
+    TransitionInteractor(
+        fromState = KeyguardState.GONE,
+    ) {
 
     override fun start() {
         listenForGoneToAodOrDozing()
@@ -53,17 +54,10 @@
     private fun listenForGoneToLockscreen() {
         scope.launch {
             keyguardInteractor.isKeyguardShowing
-                .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
+                .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
                 .collect { (isKeyguardShowing, lastStartedStep) ->
                     if (isKeyguardShowing && lastStartedStep.to == KeyguardState.GONE) {
-                        keyguardTransitionRepository.startTransition(
-                            TransitionInfo(
-                                name,
-                                KeyguardState.GONE,
-                                KeyguardState.LOCKSCREEN,
-                                getAnimator(),
-                            )
-                        )
+                        startTransitionTo(KeyguardState.LOCKSCREEN)
                     }
                 }
         }
@@ -72,17 +66,10 @@
     private fun listenForGoneToDreaming() {
         scope.launch {
             keyguardInteractor.isAbleToDream
-                .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
+                .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
                 .collect { (isAbleToDream, lastStartedStep) ->
                     if (isAbleToDream && lastStartedStep.to == KeyguardState.GONE) {
-                        keyguardTransitionRepository.startTransition(
-                            TransitionInfo(
-                                name,
-                                KeyguardState.GONE,
-                                KeyguardState.DREAMING,
-                                getAnimator(TO_DREAMING_DURATION),
-                            )
-                        )
+                        startTransitionTo(KeyguardState.DREAMING)
                     }
                 }
         }
@@ -93,7 +80,7 @@
             keyguardInteractor.wakefulnessModel
                 .sample(
                     combine(
-                        keyguardTransitionInteractor.startedKeyguardTransitionStep,
+                        transitionInteractor.startedKeyguardTransitionStep,
                         keyguardInteractor.isAodAvailable,
                         ::Pair
                     ),
@@ -104,30 +91,24 @@
                         lastStartedStep.to == KeyguardState.GONE &&
                             wakefulnessState.state == WakefulnessState.STARTING_TO_SLEEP
                     ) {
-                        keyguardTransitionRepository.startTransition(
-                            TransitionInfo(
-                                name,
-                                KeyguardState.GONE,
-                                if (isAodAvailable) {
-                                    KeyguardState.AOD
-                                } else {
-                                    KeyguardState.DOZING
-                                },
-                                getAnimator(),
-                            )
+                        startTransitionTo(
+                            if (isAodAvailable) KeyguardState.AOD else KeyguardState.DOZING
                         )
                     }
                 }
         }
     }
 
-    private fun getAnimator(duration: Duration = DEFAULT_DURATION): ValueAnimator {
+    override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator {
         return ValueAnimator().apply {
-            setInterpolator(Interpolators.LINEAR)
-            setDuration(duration.inWholeMilliseconds)
+            interpolator = Interpolators.LINEAR
+            duration =
+                when (toState) {
+                    KeyguardState.DREAMING -> TO_DREAMING_DURATION
+                    else -> DEFAULT_DURATION
+                }.inWholeMilliseconds
         }
     }
-
     companion object {
         private val DEFAULT_DURATION = 500.milliseconds
         val TO_DREAMING_DURATION = 933.milliseconds
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
index b5e289f..ed1bf3e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
@@ -32,7 +32,6 @@
 import com.android.systemui.util.kotlin.sample
 import java.util.UUID
 import javax.inject.Inject
-import kotlin.time.Duration
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.combine
@@ -42,12 +41,15 @@
 class FromLockscreenTransitionInteractor
 @Inject
 constructor(
+    override val transitionRepository: KeyguardTransitionRepository,
+    override val transitionInteractor: KeyguardTransitionInteractor,
     @Application private val scope: CoroutineScope,
     private val keyguardInteractor: KeyguardInteractor,
     private val shadeRepository: ShadeRepository,
-    private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
-    private val keyguardTransitionRepository: KeyguardTransitionRepository,
-) : TransitionInteractor(FromLockscreenTransitionInteractor::class.simpleName!!) {
+) :
+    TransitionInteractor(
+        fromState = KeyguardState.LOCKSCREEN,
+    ) {
 
     override fun start() {
         listenForLockscreenToGone()
@@ -66,8 +68,8 @@
             keyguardInteractor.isAbleToDream
                 .sample(
                     combine(
-                        keyguardTransitionInteractor.startedKeyguardTransitionStep,
-                        keyguardTransitionInteractor.finishedKeyguardState,
+                        transitionInteractor.startedKeyguardTransitionStep,
+                        transitionInteractor.finishedKeyguardState,
                         ::Pair
                     ),
                     ::toTriple
@@ -78,14 +80,7 @@
                         lastStartedTransition.to == KeyguardState.LOCKSCREEN &&
                             !invalidFromStates.contains(lastStartedTransition.from)
                     if (isAbleToDream && (isOnLockscreen || isTransitionInterruptible)) {
-                        keyguardTransitionRepository.startTransition(
-                            TransitionInfo(
-                                name,
-                                KeyguardState.LOCKSCREEN,
-                                KeyguardState.DREAMING,
-                                getAnimator(TO_DREAMING_DURATION),
-                            )
-                        )
+                        startTransitionTo(KeyguardState.DREAMING)
                     }
                 }
         }
@@ -94,20 +89,13 @@
     private fun listenForLockscreenToPrimaryBouncer() {
         scope.launch {
             keyguardInteractor.primaryBouncerShowing
-                .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
+                .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
                 .collect { pair ->
                     val (isBouncerShowing, lastStartedTransitionStep) = pair
                     if (
                         isBouncerShowing && lastStartedTransitionStep.to == KeyguardState.LOCKSCREEN
                     ) {
-                        keyguardTransitionRepository.startTransition(
-                            TransitionInfo(
-                                ownerName = name,
-                                from = KeyguardState.LOCKSCREEN,
-                                to = KeyguardState.PRIMARY_BOUNCER,
-                                animator = getAnimator(),
-                            )
-                        )
+                        startTransitionTo(KeyguardState.PRIMARY_BOUNCER)
                     }
                 }
         }
@@ -116,21 +104,14 @@
     private fun listenForLockscreenToAlternateBouncer() {
         scope.launch {
             keyguardInteractor.alternateBouncerShowing
-                .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
+                .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
                 .collect { pair ->
                     val (isAlternateBouncerShowing, lastStartedTransitionStep) = pair
                     if (
                         isAlternateBouncerShowing &&
                             lastStartedTransitionStep.to == KeyguardState.LOCKSCREEN
                     ) {
-                        keyguardTransitionRepository.startTransition(
-                            TransitionInfo(
-                                ownerName = name,
-                                from = KeyguardState.LOCKSCREEN,
-                                to = KeyguardState.ALTERNATE_BOUNCER,
-                                animator = getAnimator(),
-                            )
-                        )
+                        startTransitionTo(KeyguardState.ALTERNATE_BOUNCER)
                     }
                 }
         }
@@ -143,7 +124,7 @@
             shadeRepository.shadeModel
                 .sample(
                     combine(
-                        keyguardTransitionInteractor.startedKeyguardTransitionStep,
+                        transitionInteractor.startedKeyguardTransitionStep,
                         keyguardInteractor.statusBarState,
                         keyguardInteractor.isKeyguardUnlocked,
                         ::Triple
@@ -164,7 +145,7 @@
                                 } else {
                                     TransitionState.RUNNING
                                 }
-                            keyguardTransitionRepository.updateTransition(
+                            transitionRepository.updateTransition(
                                 id,
                                 1f - shadeModel.expansionAmount,
                                 nextState,
@@ -178,13 +159,18 @@
                             }
 
                             // If canceled, just put the state back
+                            // TODO: This logic should happen in FromPrimaryBouncerInteractor.
                             if (nextState == TransitionState.CANCELED) {
-                                keyguardTransitionRepository.startTransition(
+                                transitionRepository.startTransition(
                                     TransitionInfo(
                                         ownerName = name,
                                         from = KeyguardState.PRIMARY_BOUNCER,
                                         to = KeyguardState.LOCKSCREEN,
-                                        animator = getAnimator(0.milliseconds)
+                                        animator =
+                                            getDefaultAnimatorForTransitionsToState(
+                                                    KeyguardState.LOCKSCREEN
+                                                )
+                                                .apply { duration = 0 }
                                     )
                                 )
                             }
@@ -198,15 +184,7 @@
                                 !isKeyguardUnlocked &&
                                 statusBarState == KEYGUARD
                         ) {
-                            transitionId =
-                                keyguardTransitionRepository.startTransition(
-                                    TransitionInfo(
-                                        ownerName = name,
-                                        from = KeyguardState.LOCKSCREEN,
-                                        to = KeyguardState.PRIMARY_BOUNCER,
-                                        animator = null,
-                                    )
-                                )
+                            transitionId = startTransitionTo(KeyguardState.PRIMARY_BOUNCER)
                         }
                     }
                 }
@@ -216,18 +194,11 @@
     private fun listenForLockscreenToGone() {
         scope.launch {
             keyguardInteractor.isKeyguardGoingAway
-                .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
+                .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
                 .collect { pair ->
                     val (isKeyguardGoingAway, lastStartedStep) = pair
                     if (isKeyguardGoingAway && lastStartedStep.to == KeyguardState.LOCKSCREEN) {
-                        keyguardTransitionRepository.startTransition(
-                            TransitionInfo(
-                                name,
-                                KeyguardState.LOCKSCREEN,
-                                KeyguardState.GONE,
-                                getAnimator(),
-                            )
-                        )
+                        startTransitionTo(KeyguardState.GONE)
                     }
                 }
         }
@@ -238,7 +209,7 @@
             keyguardInteractor.isKeyguardOccluded
                 .sample(
                     combine(
-                        keyguardTransitionInteractor.finishedKeyguardState,
+                        transitionInteractor.finishedKeyguardState,
                         keyguardInteractor.isDreaming,
                         ::Pair
                     ),
@@ -246,14 +217,7 @@
                 )
                 .collect { (isOccluded, keyguardState, isDreaming) ->
                     if (isOccluded && !isDreaming && keyguardState == KeyguardState.LOCKSCREEN) {
-                        keyguardTransitionRepository.startTransition(
-                            TransitionInfo(
-                                name,
-                                keyguardState,
-                                KeyguardState.OCCLUDED,
-                                getAnimator(TO_OCCLUDED_DURATION),
-                            )
-                        )
+                        startTransitionTo(KeyguardState.OCCLUDED)
                     }
                 }
         }
@@ -263,7 +227,7 @@
     private fun listenForLockscreenToCamera() {
         scope.launch {
             keyguardInteractor.onCameraLaunchDetected
-                .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
+                .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
                 .collect { (_, lastStartedStep) ->
                     // DREAMING/AOD/OFF may trigger on the first power button push, so include this
                     // state in order to cancel and correct the transition
@@ -274,14 +238,7 @@
                             lastStartedStep.to == KeyguardState.AOD ||
                             lastStartedStep.to == KeyguardState.OFF
                     ) {
-                        keyguardTransitionRepository.startTransition(
-                            TransitionInfo(
-                                name,
-                                KeyguardState.LOCKSCREEN,
-                                KeyguardState.OCCLUDED,
-                                getAnimator(TO_OCCLUDED_DURATION),
-                            )
-                        )
+                        startTransitionTo(KeyguardState.OCCLUDED)
                     }
                 }
         }
@@ -292,7 +249,7 @@
             keyguardInteractor.wakefulnessModel
                 .sample(
                     combine(
-                        keyguardTransitionInteractor.startedKeyguardTransitionStep,
+                        transitionInteractor.startedKeyguardTransitionStep,
                         keyguardInteractor.isAodAvailable,
                         ::Pair
                     ),
@@ -303,27 +260,23 @@
                         lastStartedStep.to == KeyguardState.LOCKSCREEN &&
                             wakefulnessState.state == WakefulnessState.STARTING_TO_SLEEP
                     ) {
-                        keyguardTransitionRepository.startTransition(
-                            TransitionInfo(
-                                name,
-                                KeyguardState.LOCKSCREEN,
-                                if (isAodAvailable) {
-                                    KeyguardState.AOD
-                                } else {
-                                    KeyguardState.DOZING
-                                },
-                                getAnimator(),
-                            )
+                        startTransitionTo(
+                            if (isAodAvailable) KeyguardState.AOD else KeyguardState.DOZING
                         )
                     }
                 }
         }
     }
 
-    private fun getAnimator(duration: Duration = DEFAULT_DURATION): ValueAnimator {
+    override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator {
         return ValueAnimator().apply {
-            setInterpolator(Interpolators.LINEAR)
-            setDuration(duration.inWholeMilliseconds)
+            interpolator = Interpolators.LINEAR
+            duration =
+                when (toState) {
+                    KeyguardState.DREAMING -> TO_DREAMING_DURATION
+                    KeyguardState.OCCLUDED -> TO_OCCLUDED_DURATION
+                    else -> DEFAULT_DURATION
+                }.inWholeMilliseconds
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
index b0dbc59..ff0db34 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
@@ -22,12 +22,10 @@
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
-import com.android.systemui.keyguard.shared.model.TransitionInfo
 import com.android.systemui.keyguard.shared.model.WakefulnessState
 import com.android.systemui.util.kotlin.Utils.Companion.toTriple
 import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
-import kotlin.time.Duration
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.combine
@@ -37,11 +35,14 @@
 class FromOccludedTransitionInteractor
 @Inject
 constructor(
+    override val transitionRepository: KeyguardTransitionRepository,
+    override val transitionInteractor: KeyguardTransitionInteractor,
     @Application private val scope: CoroutineScope,
     private val keyguardInteractor: KeyguardInteractor,
-    private val keyguardTransitionRepository: KeyguardTransitionRepository,
-    private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
-) : TransitionInteractor(FromOccludedTransitionInteractor::class.simpleName!!) {
+) :
+    TransitionInteractor(
+        fromState = KeyguardState.OCCLUDED,
+    ) {
 
     override fun start() {
         listenForOccludedToLockscreen()
@@ -54,18 +55,11 @@
     private fun listenForOccludedToDreaming() {
         scope.launch {
             keyguardInteractor.isAbleToDream
-                .sample(keyguardTransitionInteractor.finishedKeyguardState, ::Pair)
+                .sample(transitionInteractor.finishedKeyguardState, ::Pair)
                 .collect { pair ->
                     val (isAbleToDream, keyguardState) = pair
                     if (isAbleToDream && keyguardState == KeyguardState.OCCLUDED) {
-                        keyguardTransitionRepository.startTransition(
-                            TransitionInfo(
-                                name,
-                                KeyguardState.OCCLUDED,
-                                KeyguardState.DREAMING,
-                                getAnimator(),
-                            )
-                        )
+                        startTransitionTo(KeyguardState.DREAMING)
                     }
                 }
         }
@@ -77,7 +71,7 @@
                 .sample(
                     combine(
                         keyguardInteractor.isKeyguardShowing,
-                        keyguardTransitionInteractor.startedKeyguardTransitionStep,
+                        transitionInteractor.startedKeyguardTransitionStep,
                         ::Pair
                     ),
                     ::toTriple
@@ -90,14 +84,7 @@
                             isShowing &&
                             lastStartedKeyguardState.to == KeyguardState.OCCLUDED
                     ) {
-                        keyguardTransitionRepository.startTransition(
-                            TransitionInfo(
-                                name,
-                                KeyguardState.OCCLUDED,
-                                KeyguardState.LOCKSCREEN,
-                                getAnimator(TO_LOCKSCREEN_DURATION),
-                            )
-                        )
+                        startTransitionTo(KeyguardState.LOCKSCREEN)
                     }
                 }
         }
@@ -109,7 +96,7 @@
                 .sample(
                     combine(
                         keyguardInteractor.isKeyguardShowing,
-                        keyguardTransitionInteractor.startedKeyguardTransitionStep,
+                        transitionInteractor.startedKeyguardTransitionStep,
                         ::Pair
                     ),
                     ::toTriple
@@ -122,14 +109,7 @@
                             !isShowing &&
                             lastStartedKeyguardState.to == KeyguardState.OCCLUDED
                     ) {
-                        keyguardTransitionRepository.startTransition(
-                            TransitionInfo(
-                                name,
-                                KeyguardState.OCCLUDED,
-                                KeyguardState.GONE,
-                                getAnimator(),
-                            )
-                        )
+                        startTransitionTo(KeyguardState.GONE)
                     }
                 }
         }
@@ -140,7 +120,7 @@
             keyguardInteractor.wakefulnessModel
                 .sample(
                     combine(
-                        keyguardTransitionInteractor.startedKeyguardTransitionStep,
+                        transitionInteractor.startedKeyguardTransitionStep,
                         keyguardInteractor.isAodAvailable,
                         ::Pair
                     ),
@@ -151,17 +131,8 @@
                         lastStartedStep.to == KeyguardState.OCCLUDED &&
                             wakefulnessState.state == WakefulnessState.STARTING_TO_SLEEP
                     ) {
-                        keyguardTransitionRepository.startTransition(
-                            TransitionInfo(
-                                name,
-                                KeyguardState.OCCLUDED,
-                                if (isAodAvailable) {
-                                    KeyguardState.AOD
-                                } else {
-                                    KeyguardState.DOZING
-                                },
-                                getAnimator(),
-                            )
+                        startTransitionTo(
+                            if (isAodAvailable) KeyguardState.AOD else KeyguardState.DOZING
                         )
                     }
                 }
@@ -171,29 +142,31 @@
     private fun listenForOccludedToAlternateBouncer() {
         scope.launch {
             keyguardInteractor.alternateBouncerShowing
-                .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
+                .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
                 .collect { (isAlternateBouncerShowing, lastStartedTransitionStep) ->
                     if (
                         isAlternateBouncerShowing &&
                             lastStartedTransitionStep.to == KeyguardState.OCCLUDED
                     ) {
-                        keyguardTransitionRepository.startTransition(
-                            TransitionInfo(
-                                ownerName = name,
-                                from = KeyguardState.OCCLUDED,
-                                to = KeyguardState.ALTERNATE_BOUNCER,
-                                animator = getAnimator(),
-                            )
-                        )
+                        startTransitionTo(KeyguardState.ALTERNATE_BOUNCER)
                     }
                 }
         }
     }
 
-    private fun getAnimator(duration: Duration = DEFAULT_DURATION): ValueAnimator {
+    override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator {
         return ValueAnimator().apply {
-            setInterpolator(Interpolators.LINEAR)
-            setDuration(duration.inWholeMilliseconds)
+            interpolator =
+                when (toState) {
+                    KeyguardState.ALTERNATE_BOUNCER -> Interpolators.FAST_OUT_SLOW_IN
+                    else -> Interpolators.LINEAR
+                }
+
+            duration =
+                when (toState) {
+                    KeyguardState.LOCKSCREEN -> TO_LOCKSCREEN_DURATION
+                    else -> DEFAULT_DURATION
+                }.inWholeMilliseconds
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
index da09e1f..e1754f5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
@@ -25,12 +25,10 @@
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
-import com.android.systemui.keyguard.shared.model.TransitionInfo
 import com.android.systemui.keyguard.shared.model.WakefulnessState
 import com.android.systemui.util.kotlin.Utils.Companion.toQuad
 import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
-import kotlin.time.Duration
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.combine
@@ -40,12 +38,15 @@
 class FromPrimaryBouncerTransitionInteractor
 @Inject
 constructor(
+    override val transitionRepository: KeyguardTransitionRepository,
+    override val transitionInteractor: KeyguardTransitionInteractor,
     @Application private val scope: CoroutineScope,
     private val keyguardInteractor: KeyguardInteractor,
-    private val keyguardTransitionRepository: KeyguardTransitionRepository,
-    private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
     private val keyguardSecurityModel: KeyguardSecurityModel,
-) : TransitionInteractor(FromPrimaryBouncerTransitionInteractor::class.simpleName!!) {
+) :
+    TransitionInteractor(
+        fromState = KeyguardState.PRIMARY_BOUNCER,
+    ) {
 
     override fun start() {
         listenForPrimaryBouncerToGone()
@@ -59,7 +60,7 @@
                 .sample(
                     combine(
                         keyguardInteractor.wakefulnessModel,
-                        keyguardTransitionInteractor.startedKeyguardTransitionStep,
+                        transitionInteractor.startedKeyguardTransitionStep,
                         keyguardInteractor.isKeyguardOccluded,
                         ::Triple
                     ),
@@ -73,20 +74,8 @@
                             (wakefulnessState.state == WakefulnessState.AWAKE ||
                                 wakefulnessState.state == WakefulnessState.STARTING_TO_WAKE)
                     ) {
-                        val to =
-                            if (occluded) {
-                                KeyguardState.OCCLUDED
-                            } else {
-                                KeyguardState.LOCKSCREEN
-                            }
-
-                        keyguardTransitionRepository.startTransition(
-                            TransitionInfo(
-                                ownerName = name,
-                                from = KeyguardState.PRIMARY_BOUNCER,
-                                to = to,
-                                animator = getAnimator(),
-                            )
+                        startTransitionTo(
+                            if (occluded) KeyguardState.OCCLUDED else KeyguardState.LOCKSCREEN
                         )
                     }
                 }
@@ -99,7 +88,7 @@
                 .sample(
                     combine(
                         keyguardInteractor.wakefulnessModel,
-                        keyguardTransitionInteractor.startedKeyguardTransitionStep,
+                        transitionInteractor.startedKeyguardTransitionStep,
                         keyguardInteractor.isAodAvailable,
                         ::Triple
                     ),
@@ -114,20 +103,8 @@
                             (wakefulnessState.state == WakefulnessState.STARTING_TO_SLEEP ||
                                 wakefulnessState.state == WakefulnessState.ASLEEP)
                     ) {
-                        val to =
-                            if (isAodAvailable) {
-                                KeyguardState.AOD
-                            } else {
-                                KeyguardState.DOZING
-                            }
-
-                        keyguardTransitionRepository.startTransition(
-                            TransitionInfo(
-                                ownerName = name,
-                                from = KeyguardState.PRIMARY_BOUNCER,
-                                to = to,
-                                animator = getAnimator(),
-                            )
+                        startTransitionTo(
+                            if (isAodAvailable) KeyguardState.AOD else KeyguardState.DOZING
                         )
                     }
                 }
@@ -137,7 +114,7 @@
     private fun listenForPrimaryBouncerToGone() {
         scope.launch {
             keyguardInteractor.isKeyguardGoingAway
-                .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
+                .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
                 .collect { (isKeyguardGoingAway, lastStartedTransitionStep) ->
                     if (
                         isKeyguardGoingAway &&
@@ -154,24 +131,24 @@
                             } else {
                                 TO_GONE_DURATION
                             }
-                        keyguardTransitionRepository.startTransition(
-                            TransitionInfo(
-                                ownerName = name,
-                                from = KeyguardState.PRIMARY_BOUNCER,
-                                to = KeyguardState.GONE,
-                                animator = getAnimator(duration),
-                            ),
-                            resetIfCanceled = true,
+
+                        startTransitionTo(
+                            toState = KeyguardState.GONE,
+                            animator =
+                                getDefaultAnimatorForTransitionsToState(KeyguardState.GONE).apply {
+                                    this.duration = duration.inWholeMilliseconds
+                                },
+                            resetIfCancelled = true
                         )
                     }
                 }
         }
     }
 
-    private fun getAnimator(duration: Duration = DEFAULT_DURATION): ValueAnimator {
+    override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator {
         return ValueAnimator().apply {
-            setInterpolator(Interpolators.LINEAR)
-            setDuration(duration.inWholeMilliseconds)
+            interpolator = Interpolators.LINEAR
+            duration = DEFAULT_DURATION.inWholeMilliseconds
         }
     }
 
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 74ef7a5..141b130 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
@@ -16,8 +16,8 @@
 
 package com.android.systemui.keyguard.domain.interactor
 
-import com.android.systemui.keyguard.shared.model.AuthenticationStatus
-import com.android.systemui.keyguard.shared.model.DetectionStatus
+import com.android.systemui.keyguard.shared.model.FaceAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.FaceDetectionStatus
 import kotlinx.coroutines.flow.Flow
 
 /**
@@ -27,10 +27,10 @@
 interface KeyguardFaceAuthInteractor {
 
     /** Current authentication status */
-    val authenticationStatus: Flow<AuthenticationStatus>
+    val authenticationStatus: Flow<FaceAuthenticationStatus>
 
     /** Current detection status */
-    val detectionStatus: Flow<DetectionStatus>
+    val detectionStatus: Flow<FaceDetectionStatus>
 
     /** Can face auth be run right now */
     fun canFaceAuthRun(): Boolean
@@ -72,8 +72,8 @@
  */
 interface FaceAuthenticationListener {
     /** Receive face authentication status updates */
-    fun onAuthenticationStatusChanged(status: AuthenticationStatus)
+    fun onAuthenticationStatusChanged(status: FaceAuthenticationStatus)
 
     /** Receive status updates whenever face detection runs */
-    fun onDetectionStatusChanged(status: DetectionStatus)
+    fun onDetectionStatusChanged(status: FaceDetectionStatus)
 }
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 c44435e..1553525 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
@@ -19,12 +19,13 @@
 
 import android.app.StatusBarManager
 import android.graphics.Point
+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.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.KeyguardBouncerRepository
 import com.android.systemui.keyguard.data.repository.KeyguardRepository
 import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
 import com.android.systemui.keyguard.shared.model.CameraLaunchSourceModel
@@ -39,6 +40,7 @@
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.filter
@@ -59,6 +61,7 @@
     private val commandQueue: CommandQueue,
     featureFlags: FeatureFlags,
     bouncerRepository: KeyguardBouncerRepository,
+    configurationRepository: ConfigurationRepository,
 ) {
     /**
      * The amount of doze the system is in, where `1.0` is fully dozing and `0.0` is not dozing at
@@ -67,6 +70,8 @@
     val dozeAmount: Flow<Float> = repository.linearDozeAmount
     /** Whether the system is in doze mode. */
     val isDozing: Flow<Boolean> = repository.isDozing
+    /** Receive an event for doze time tick */
+    val dozeTimeTick: Flow<Unit> = repository.dozeTimeTick
     /** Whether Always-on Display mode is available. */
     val isAodAvailable: Flow<Boolean> = repository.isAodAvailable
     /** Doze transition information. */
@@ -78,6 +83,8 @@
     val isDreaming: Flow<Boolean> = repository.isDreaming
     /** Whether the system is dreaming with an overlay active */
     val isDreamingWithOverlay: Flow<Boolean> = repository.isDreamingWithOverlay
+    /** Whether the system is dreaming and the active dream is hosted in lockscreen */
+    val isActiveDreamLockscreenHosted: Flow<Boolean> = repository.isActiveDreamLockscreenHosted
     /** Event for when the camera gesture is detected */
     val onCameraLaunchDetected: Flow<CameraLaunchSourceModel> = conflatedCallbackFlow {
         val callback =
@@ -97,7 +104,7 @@
     }
 
     /** The device wake/sleep state */
-    val wakefulnessModel: Flow<WakefulnessModel> = repository.wakefulness
+    val wakefulnessModel: StateFlow<WakefulnessModel> = repository.wakefulness
 
     /**
      * Dozing and dreaming have overlapping events. If the doze state remains in FINISH, it means
@@ -172,6 +179,9 @@
     /** The approximate location on the screen of the face unlock sensor, if one is available. */
     val faceSensorLocation: Flow<Point?> = repository.faceSensorLocation
 
+    /** Notifies when a new configuration is set */
+    val configurationChange: Flow<Unit> = configurationRepository.onAnyConfigurationChange
+
     fun dozeTransitionTo(vararg states: DozeStateModel): Flow<DozeTransitionModel> {
         return dozeTransitionModel.filter { states.contains(it.to) }
     }
@@ -192,6 +202,10 @@
         }
     }
 
+    fun setIsActiveDreamLockscreenHosted(isLockscreenHosted: Boolean) {
+        repository.setIsActiveDreamLockscreenHosted(isLockscreenHosted)
+    }
+
     /** Sets whether quick settings or quick-quick settings is visible. */
     fun setQuickSettingsVisible(isVisible: Boolean) {
         repository.setQuickSettingsVisible(isVisible)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
index ea9c2b2..324d443 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
@@ -38,7 +38,6 @@
 import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
 import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
 import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel
-import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceRegistry
 import com.android.systemui.keyguard.shared.model.KeyguardPickerFlag
 import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePickerRepresentation
 import com.android.systemui.keyguard.shared.model.KeyguardSlotPickerRepresentation
@@ -49,6 +48,7 @@
 import com.android.systemui.shared.customization.data.content.CustomizationProviderContract as Contract
 import com.android.systemui.statusbar.phone.SystemUIDialog
 import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.TraceUtils.Companion.traceAsync
 import dagger.Lazy
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
@@ -67,7 +67,6 @@
 @Inject
 constructor(
     private val keyguardInteractor: KeyguardInteractor,
-    private val registry: KeyguardQuickAffordanceRegistry<out KeyguardQuickAffordanceConfig>,
     private val lockPatternUtils: LockPatternUtils,
     private val keyguardStateController: KeyguardStateController,
     private val userTracker: UserTracker,
@@ -82,20 +81,13 @@
     @Background private val backgroundDispatcher: CoroutineDispatcher,
     @Application private val appContext: Context,
 ) {
-    private val isUsingRepository: Boolean
-        get() = featureFlags.isEnabled(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES)
 
     /**
      * Whether the UI should use the long press gesture to activate quick affordances.
      *
      * If `false`, the UI goes back to using single taps.
      */
-    fun useLongPress(): Flow<Boolean> =
-        if (featureFlags.isEnabled(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES)) {
-            dockManager.retrieveIsDocked().map { !it }
-        } else {
-            flowOf(false)
-        }
+    fun useLongPress(): Flow<Boolean> = dockManager.retrieveIsDocked().map { !it }
 
     /** Returns an observable for the quick affordance at the given position. */
     suspend fun quickAffordance(
@@ -146,14 +138,9 @@
         expandable: Expandable?,
         slotId: String,
     ) {
-        @Suppress("UNCHECKED_CAST")
+        val (decodedSlotId, decodedConfigKey) = configKey.decode()
         val config =
-            if (isUsingRepository) {
-                val (slotId, decodedConfigKey) = configKey.decode()
-                repository.get().selections.value[slotId]?.find { it.key == decodedConfigKey }
-            } else {
-                registry.get(configKey)
-            }
+            repository.get().selections.value[decodedSlotId]?.find { it.key == decodedConfigKey }
         if (config == null) {
             Log.e(TAG, "Affordance config with key of \"$configKey\" not found!")
             return
@@ -182,7 +169,6 @@
      * @return `true` if the affordance was selected successfully; `false` otherwise.
      */
     suspend fun select(slotId: String, affordanceId: String): Boolean {
-        check(isUsingRepository)
         if (isFeatureDisabledByDevicePolicy()) {
             return false
         }
@@ -225,7 +211,6 @@
      *   the affordance was not on the slot to begin with).
      */
     suspend fun unselect(slotId: String, affordanceId: String?): Boolean {
-        check(isUsingRepository)
         if (isFeatureDisabledByDevicePolicy()) {
             return false
         }
@@ -285,17 +270,12 @@
 
     private fun quickAffordanceInternal(
         position: KeyguardQuickAffordancePosition
-    ): Flow<KeyguardQuickAffordanceModel> {
-        return if (isUsingRepository) {
-            repository
-                .get()
-                .selections
-                .map { it[position.toSlotId()] ?: emptyList() }
-                .flatMapLatest { configs -> combinedConfigs(position, configs) }
-        } else {
-            combinedConfigs(position, registry.getAll(position))
-        }
-    }
+    ): Flow<KeyguardQuickAffordanceModel> =
+        repository
+            .get()
+            .selections
+            .map { it[position.toSlotId()] ?: emptyList() }
+            .flatMapLatest { configs -> combinedConfigs(position, configs) }
 
     private fun combinedConfigs(
         position: KeyguardQuickAffordancePosition,
@@ -325,12 +305,7 @@
                     states[index] as KeyguardQuickAffordanceConfig.LockScreenState.Visible
                 val configKey = configs[index].key
                 KeyguardQuickAffordanceModel.Visible(
-                    configKey =
-                        if (isUsingRepository) {
-                            configKey.encode(position.toSlotId())
-                        } else {
-                            configKey
-                        },
+                    configKey = configKey.encode(position.toSlotId()),
                     icon = visibleState.icon,
                     activationState = visibleState.activationState,
                 )
@@ -396,8 +371,6 @@
     }
 
     suspend fun getSlotPickerRepresentations(): List<KeyguardSlotPickerRepresentation> {
-        check(isUsingRepository)
-
         if (isFeatureDisabledByDevicePolicy()) {
             return emptyList()
         }
@@ -415,7 +388,6 @@
                 name = Contract.FlagsTable.FLAG_NAME_CUSTOM_LOCK_SCREEN_QUICK_AFFORDANCES_ENABLED,
                 value =
                     !isFeatureDisabledByDevicePolicy() &&
-                        featureFlags.isEnabled(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES) &&
                         appContext.resources.getBoolean(R.bool.custom_lockscreen_shortcuts_enabled),
             ),
             KeyguardPickerFlag(
@@ -433,13 +405,19 @@
             KeyguardPickerFlag(
                 name = Contract.FlagsTable.FLAG_NAME_WALLPAPER_PICKER_UI_FOR_AIWP,
                 value = featureFlags.isEnabled(Flags.WALLPAPER_PICKER_UI_FOR_AIWP)
+            ),
+            KeyguardPickerFlag(
+                name = Contract.FlagsTable.FLAG_NAME_TRANSIT_CLOCK,
+                value = featureFlags.isEnabled(Flags.TRANSIT_CLOCK)
             )
         )
     }
 
     private suspend fun isFeatureDisabledByDevicePolicy(): Boolean =
-        withContext(backgroundDispatcher) {
-            devicePolicyManager.areKeyguardShortcutsDisabled(userId = userTracker.userId)
+        traceAsync(TAG, "isFeatureDisabledByDevicePolicy") {
+            withContext(backgroundDispatcher) {
+                devicePolicyManager.areKeyguardShortcutsDisabled(userId = userTracker.userId)
+            }
         }
 
     companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
index 7c5641f..4f7abd4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
@@ -19,7 +19,7 @@
 import com.android.keyguard.logging.KeyguardLogger
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.log.LogLevel.VERBOSE
+import com.android.systemui.log.core.LogLevel.VERBOSE
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.launch
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 41a81a8..45bf20d 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
@@ -21,6 +21,7 @@
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER
 import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
 import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
 import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
@@ -47,6 +48,8 @@
     private val repository: KeyguardTransitionRepository,
     @Application val scope: CoroutineScope,
 ) {
+    private val TAG = this::class.simpleName
+
     /** (any)->GONE transition information */
     val anyStateToGoneTransition: Flow<TransitionStep> =
         repository.transitions.filter { step -> step.to == GONE }
@@ -59,6 +62,26 @@
     val fromDreamingTransition: Flow<TransitionStep> =
         repository.transitions.filter { step -> step.from == DREAMING }
 
+    /** (any)->Lockscreen transition information */
+    val anyStateToLockscreenTransition: Flow<TransitionStep> =
+        repository.transitions.filter { step -> step.to == LOCKSCREEN }
+
+    /** (any)->Occluded transition information */
+    val anyStateToOccludedTransition: Flow<TransitionStep> =
+        repository.transitions.filter { step -> step.to == OCCLUDED }
+
+    /** (any)->PrimaryBouncer transition information */
+    val anyStateToPrimaryBouncerTransition: Flow<TransitionStep> =
+        repository.transitions.filter { step -> step.to == PRIMARY_BOUNCER }
+
+    /** (any)->Dreaming transition information */
+    val anyStateToDreamingTransition: Flow<TransitionStep> =
+        repository.transitions.filter { step -> step.to == DREAMING }
+
+    /** (any)->AlternateBouncer transition information */
+    val anyStateToAlternateBouncerTransition: Flow<TransitionStep> =
+        repository.transitions.filter { step -> step.to == ALTERNATE_BOUNCER }
+
     /** AOD->LOCKSCREEN transition information. */
     val aodToLockscreenTransition: Flow<TransitionStep> = repository.transition(AOD, LOCKSCREEN)
 
@@ -66,6 +89,9 @@
     val dreamingToLockscreenTransition: Flow<TransitionStep> =
         repository.transition(DREAMING, LOCKSCREEN)
 
+    /** GONE->AOD transition information. */
+    val goneToAodTransition: Flow<TransitionStep> = repository.transition(GONE, AOD)
+
     /** GONE->DREAMING transition information. */
     val goneToDreamingTransition: Flow<TransitionStep> = repository.transition(GONE, DREAMING)
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractor.kt
index d0bc25f..1c200b0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractor.kt
@@ -20,21 +20,14 @@
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
 import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
 import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.scene.domain.interactor.SceneInteractor
-import com.android.systemui.scene.shared.model.SceneKey
-import com.android.systemui.scene.shared.model.SceneModel
-import com.android.systemui.util.kotlin.pairwise
 import dagger.assisted.Assisted
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
-import kotlinx.coroutines.launch
 
 /** Hosts business and application state accessing logic for the lockscreen scene. */
 class LockscreenSceneInteractor
@@ -43,7 +36,6 @@
     @Application applicationScope: CoroutineScope,
     private val authenticationInteractor: AuthenticationInteractor,
     bouncerInteractorFactory: BouncerInteractor.Factory,
-    private val sceneInteractor: SceneInteractor,
     @Assisted private val containerName: String,
 ) {
     private val bouncerInteractor: BouncerInteractor =
@@ -61,122 +53,23 @@
 
     /** Whether it's currently possible to swipe up to dismiss the lockscreen. */
     val isSwipeToDismissEnabled: StateFlow<Boolean> =
-        combine(
-                authenticationInteractor.isUnlocked,
-                authenticationInteractor.authenticationMethod,
-            ) { isUnlocked, authMethod ->
-                isSwipeToUnlockEnabled(
-                    isUnlocked = isUnlocked,
-                    authMethod = authMethod,
-                )
+        authenticationInteractor.isUnlocked
+            .map { isUnlocked ->
+                !isUnlocked &&
+                    authenticationInteractor.getAuthenticationMethod() is
+                        AuthenticationMethodModel.Swipe
             }
             .stateIn(
                 scope = applicationScope,
                 started = SharingStarted.WhileSubscribed(),
-                initialValue =
-                    isSwipeToUnlockEnabled(
-                        isUnlocked = authenticationInteractor.isUnlocked.value,
-                        authMethod = authenticationInteractor.authenticationMethod.value,
-                    ),
+                initialValue = false,
             )
 
-    init {
-        // LOCKING SHOWS Lockscreen.
-        //
-        // Move to the lockscreen scene if the device becomes locked while in any scene.
-        applicationScope.launch {
-            authenticationInteractor.isUnlocked
-                .map { !it }
-                .distinctUntilChanged()
-                .collect { isLocked ->
-                    if (isLocked) {
-                        sceneInteractor.setCurrentScene(
-                            containerName = containerName,
-                            scene = SceneModel(SceneKey.Lockscreen),
-                        )
-                    }
-                }
-        }
-
-        // BYPASS UNLOCK.
-        //
-        // Moves to the gone scene if bypass is enabled and the device becomes unlocked while in the
-        // lockscreen scene.
-        applicationScope.launch {
-            combine(
-                    authenticationInteractor.isBypassEnabled,
-                    authenticationInteractor.isUnlocked,
-                    sceneInteractor.currentScene(containerName),
-                    ::Triple,
-                )
-                .collect { (isBypassEnabled, isUnlocked, currentScene) ->
-                    if (isBypassEnabled && isUnlocked && currentScene.key == SceneKey.Lockscreen) {
-                        sceneInteractor.setCurrentScene(
-                            containerName = containerName,
-                            scene = SceneModel(SceneKey.Gone),
-                        )
-                    }
-                }
-        }
-
-        // SWIPE TO DISMISS Lockscreen.
-        //
-        // If switched from the lockscreen to the gone scene and the auth method was a swipe,
-        // unlocks the device.
-        applicationScope.launch {
-            combine(
-                    authenticationInteractor.authenticationMethod,
-                    sceneInteractor.currentScene(containerName).pairwise(),
-                    ::Pair,
-                )
-                .collect { (authMethod, scenes) ->
-                    val (previousScene, currentScene) = scenes
-                    if (
-                        authMethod is AuthenticationMethodModel.Swipe &&
-                            previousScene.key == SceneKey.Lockscreen &&
-                            currentScene.key == SceneKey.Gone
-                    ) {
-                        authenticationInteractor.unlockDevice()
-                    }
-                }
-        }
-
-        // DISMISS Lockscreen IF AUTH METHOD IS REMOVED.
-        //
-        // If the auth method becomes None while on the lockscreen scene, dismisses the lock
-        // screen.
-        applicationScope.launch {
-            combine(
-                    authenticationInteractor.authenticationMethod,
-                    sceneInteractor.currentScene(containerName),
-                    ::Pair,
-                )
-                .collect { (authMethod, scene) ->
-                    if (
-                        scene.key == SceneKey.Lockscreen &&
-                            authMethod == AuthenticationMethodModel.None
-                    ) {
-                        sceneInteractor.setCurrentScene(
-                            containerName = containerName,
-                            scene = SceneModel(SceneKey.Gone),
-                        )
-                    }
-                }
-        }
-    }
-
     /** Attempts to dismiss the lockscreen. This will cause the bouncer to show, if needed. */
     fun dismissLockscreen() {
         bouncerInteractor.showOrUnlockDevice(containerName = containerName)
     }
 
-    private fun isSwipeToUnlockEnabled(
-        isUnlocked: Boolean,
-        authMethod: AuthenticationMethodModel,
-    ): Boolean {
-        return !isUnlocked && authMethod is AuthenticationMethodModel.Swipe
-    }
-
     @AssistedFactory
     interface Factory {
         fun create(
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 5005b6c..10dd900 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
@@ -17,8 +17,8 @@
 package com.android.systemui.keyguard.domain.interactor
 
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.shared.model.AuthenticationStatus
-import com.android.systemui.keyguard.shared.model.DetectionStatus
+import com.android.systemui.keyguard.shared.model.FaceAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.FaceDetectionStatus
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.emptyFlow
@@ -31,9 +31,9 @@
  */
 @SysUISingleton
 class NoopKeyguardFaceAuthInteractor @Inject constructor() : KeyguardFaceAuthInteractor {
-    override val authenticationStatus: Flow<AuthenticationStatus>
+    override val authenticationStatus: Flow<FaceAuthenticationStatus>
         get() = emptyFlow()
-    override val detectionStatus: Flow<DetectionStatus>
+    override val detectionStatus: Flow<FaceDetectionStatus>
         get() = emptyFlow()
 
     override fun canFaceAuthRun(): Boolean = false
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerCallbackInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerCallbackInteractor.kt
deleted file mode 100644
index 3099a49..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerCallbackInteractor.kt
+++ /dev/null
@@ -1,140 +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.systemui.keyguard.domain.interactor
-
-import android.view.View
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN
-import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_VISIBLE
-import com.android.systemui.util.ListenerSet
-import javax.inject.Inject
-
-/** Interactor to add and remove callbacks for the bouncer. */
-@SysUISingleton
-class PrimaryBouncerCallbackInteractor @Inject constructor() {
-    private var resetCallbacks = ListenerSet<KeyguardResetCallback>()
-    private var expansionCallbacks = ArrayList<PrimaryBouncerExpansionCallback>()
-
-    /** Add a KeyguardResetCallback. */
-    fun addKeyguardResetCallback(callback: KeyguardResetCallback) {
-        resetCallbacks.addIfAbsent(callback)
-    }
-
-    /** Remove a KeyguardResetCallback. */
-    fun removeKeyguardResetCallback(callback: KeyguardResetCallback) {
-        resetCallbacks.remove(callback)
-    }
-
-    /** Adds a callback to listen to bouncer expansion updates. */
-    fun addBouncerExpansionCallback(callback: PrimaryBouncerExpansionCallback) {
-        if (!expansionCallbacks.contains(callback)) {
-            expansionCallbacks.add(callback)
-        }
-    }
-
-    /**
-     * Removes a previously added callback. If the callback was never added, this method does
-     * nothing.
-     */
-    fun removeBouncerExpansionCallback(callback: PrimaryBouncerExpansionCallback) {
-        expansionCallbacks.remove(callback)
-    }
-
-    /** Propagate fully shown to bouncer expansion callbacks. */
-    fun dispatchFullyShown() {
-        for (callback in expansionCallbacks) {
-            callback.onFullyShown()
-        }
-    }
-
-    /** Propagate starting to hide to bouncer expansion callbacks. */
-    fun dispatchStartingToHide() {
-        for (callback in expansionCallbacks) {
-            callback.onStartingToHide()
-        }
-    }
-
-    /** Propagate starting to show to bouncer expansion callbacks. */
-    fun dispatchStartingToShow() {
-        for (callback in expansionCallbacks) {
-            callback.onStartingToShow()
-        }
-    }
-
-    /** Propagate fully hidden to bouncer expansion callbacks. */
-    fun dispatchFullyHidden() {
-        for (callback in expansionCallbacks) {
-            callback.onFullyHidden()
-        }
-    }
-
-    /** Propagate expansion changes to bouncer expansion callbacks. */
-    fun dispatchExpansionChanged(expansion: Float) {
-        for (callback in expansionCallbacks) {
-            callback.onExpansionChanged(expansion)
-        }
-    }
-    /** Propagate visibility changes to bouncer expansion callbacks. */
-    fun dispatchVisibilityChanged(visibility: Int) {
-        for (callback in expansionCallbacks) {
-            callback.onVisibilityChanged(visibility == View.VISIBLE)
-        }
-    }
-
-    /** Propagate keyguard reset. */
-    fun dispatchReset() {
-        for (callback in resetCallbacks) {
-            callback.onKeyguardReset()
-        }
-    }
-
-    /** Callback updated when the primary bouncer's show and hide states change. */
-    interface PrimaryBouncerExpansionCallback {
-        /**
-         * Invoked when the bouncer expansion reaches [EXPANSION_VISIBLE]. This is NOT called each
-         * time the bouncer is shown, but rather only when the fully shown amount has changed based
-         * on the panel expansion. The bouncer's visibility can still change when the expansion
-         * amount hasn't changed. See [PrimaryBouncerInteractor.isFullyShowing] for the checks for
-         * the bouncer showing state.
-         */
-        fun onFullyShown() {}
-
-        /** Invoked when the bouncer is starting to transition to a hidden state. */
-        fun onStartingToHide() {}
-
-        /** Invoked when the bouncer is starting to transition to a visible state. */
-        fun onStartingToShow() {}
-
-        /** Invoked when the bouncer expansion reaches [EXPANSION_HIDDEN]. */
-        fun onFullyHidden() {}
-
-        /**
-         * From 0f [EXPANSION_VISIBLE] when fully visible to 1f [EXPANSION_HIDDEN] when fully hidden
-         */
-        fun onExpansionChanged(bouncerHideAmount: Float) {}
-
-        /**
-         * Invoked when visibility of KeyguardBouncer has changed. Note the bouncer expansion can be
-         * [EXPANSION_VISIBLE], but the view's visibility can be [View.INVISIBLE].
-         */
-        fun onVisibilityChanged(isVisible: Boolean) {}
-    }
-
-    interface KeyguardResetCallback {
-        fun onKeyguardReset()
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
deleted file mode 100644
index 54bc349..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
+++ /dev/null
@@ -1,406 +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.systemui.keyguard.domain.interactor
-
-import android.content.Context
-import android.content.res.ColorStateList
-import android.hardware.biometrics.BiometricSourceType
-import android.os.Handler
-import android.os.Trace
-import android.util.Log
-import android.view.View
-import com.android.keyguard.KeyguardConstants
-import com.android.keyguard.KeyguardSecurityModel
-import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.keyguard.KeyguardUpdateMonitorCallback
-import com.android.systemui.DejankUtils
-import com.android.systemui.R
-import com.android.systemui.classifier.FalsingCollector
-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.DismissCallbackRegistry
-import com.android.systemui.keyguard.data.BouncerView
-import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository
-import com.android.systemui.keyguard.data.repository.TrustRepository
-import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants
-import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN
-import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
-import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.shared.system.SysUiStatsLog
-import com.android.systemui.statusbar.policy.KeyguardStateController
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.collect
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.filter
-import kotlinx.coroutines.flow.filterNotNull
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.launch
-import javax.inject.Inject
-
-/**
- * Encapsulates business logic for interacting with the lock-screen primary (pin/pattern/password)
- * bouncer.
- */
-@SysUISingleton
-class PrimaryBouncerInteractor
-@Inject
-constructor(
-    private val repository: KeyguardBouncerRepository,
-    private val primaryBouncerView: BouncerView,
-    @Main private val mainHandler: Handler,
-    private val keyguardStateController: KeyguardStateController,
-    private val keyguardSecurityModel: KeyguardSecurityModel,
-    private val primaryBouncerCallbackInteractor: PrimaryBouncerCallbackInteractor,
-    private val falsingCollector: FalsingCollector,
-    private val dismissCallbackRegistry: DismissCallbackRegistry,
-    private val context: Context,
-    private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
-    private val trustRepository: TrustRepository,
-    private val featureFlags: FeatureFlags,
-    @Application private val applicationScope: CoroutineScope,
-) {
-    private val passiveAuthBouncerDelay = context.resources.getInteger(
-            R.integer.primary_bouncer_passive_auth_delay).toLong()
-    /** Runnable to show the primary bouncer. */
-    val showRunnable = Runnable {
-        repository.setPrimaryShow(true)
-        repository.setPrimaryShowingSoon(false)
-        primaryBouncerCallbackInteractor.dispatchVisibilityChanged(View.VISIBLE)
-    }
-
-    val keyguardAuthenticated: Flow<Boolean> = repository.keyguardAuthenticated.filterNotNull()
-    val isShowing: Flow<Boolean> = repository.primaryBouncerShow
-    val startingToHide: Flow<Unit> = repository.primaryBouncerStartingToHide.filter { it }.map {}
-    val isBackButtonEnabled: Flow<Boolean> = repository.isBackButtonEnabled.filterNotNull()
-    val showMessage: Flow<BouncerShowMessageModel> = repository.showMessage.filterNotNull()
-    val startingDisappearAnimation: Flow<Runnable> =
-        repository.primaryBouncerStartingDisappearAnimation.filterNotNull()
-    val resourceUpdateRequests: Flow<Boolean> = repository.resourceUpdateRequests.filter { it }
-    val keyguardPosition: Flow<Float> = repository.keyguardPosition
-    val panelExpansionAmount: Flow<Float> = repository.panelExpansionAmount
-    /** 0f = bouncer fully hidden. 1f = bouncer fully visible. */
-    val bouncerExpansion: Flow<Float> =
-        combine(repository.panelExpansionAmount, repository.primaryBouncerShow) {
-            panelExpansion,
-            primaryBouncerIsShowing ->
-            if (primaryBouncerIsShowing) {
-                1f - panelExpansion
-            } else {
-                0f
-            }
-        }
-    /** Allow for interaction when just about fully visible */
-    val isInteractable: Flow<Boolean> = bouncerExpansion.map { it > 0.9 }
-    val sideFpsShowing: Flow<Boolean> = repository.sideFpsShowing
-    private var currentUserActiveUnlockRunning = false
-
-    /** This callback needs to be a class field so it does not get garbage collected. */
-    val keyguardUpdateMonitorCallback =
-        object : KeyguardUpdateMonitorCallback() {
-            override fun onBiometricRunningStateChanged(
-                running: Boolean,
-                biometricSourceType: BiometricSourceType?
-            ) {
-                updateSideFpsVisibility()
-            }
-
-            override fun onStrongAuthStateChanged(userId: Int) {
-                updateSideFpsVisibility()
-            }
-        }
-
-    init {
-        keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback)
-        if (featureFlags.isEnabled(Flags.DELAY_BOUNCER)) {
-            applicationScope.launch {
-                trustRepository.isCurrentUserActiveUnlockRunning.collect {
-                    currentUserActiveUnlockRunning = it
-                }
-            }
-        }
-    }
-
-    // TODO(b/243685699): Move isScrimmed logic to data layer.
-    // TODO(b/243695312): Encapsulate all of the show logic for the bouncer.
-    /** Show the bouncer if necessary and set the relevant states. */
-    @JvmOverloads
-    fun show(isScrimmed: Boolean) {
-        // Reset some states as we show the bouncer.
-        repository.setKeyguardAuthenticated(null)
-        repository.setPrimaryStartingToHide(false)
-
-        val resumeBouncer =
-            (isBouncerShowing() || repository.primaryBouncerShowingSoon.value) &&
-                needsFullscreenBouncer()
-
-        Trace.beginSection("KeyguardBouncer#show")
-        repository.setPrimaryScrimmed(isScrimmed)
-        if (isScrimmed) {
-            setPanelExpansion(KeyguardBouncerConstants.EXPANSION_VISIBLE)
-        }
-
-        // In this special case, we want to hide the bouncer and show it again. We want to emit
-        // show(true) again so that we can reinflate the new view.
-        if (resumeBouncer) {
-            repository.setPrimaryShow(false)
-        }
-
-        if (primaryBouncerView.delegate?.showNextSecurityScreenOrFinish() == true) {
-            // Keyguard is done.
-            return
-        }
-
-        repository.setPrimaryShowingSoon(true)
-        if (usePrimaryBouncerPassiveAuthDelay()) {
-            Log.d(TAG, "delay bouncer, passive auth may succeed")
-            mainHandler.postDelayed(showRunnable, passiveAuthBouncerDelay)
-        } else {
-            DejankUtils.postAfterTraversal(showRunnable)
-        }
-        keyguardStateController.notifyPrimaryBouncerShowing(true)
-        primaryBouncerCallbackInteractor.dispatchStartingToShow()
-        Trace.endSection()
-    }
-
-    /** Sets the correct bouncer states to hide the bouncer. */
-    fun hide() {
-        Trace.beginSection("KeyguardBouncer#hide")
-        if (isFullyShowing()) {
-            SysUiStatsLog.write(
-                SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED,
-                SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED__STATE__HIDDEN
-            )
-            dismissCallbackRegistry.notifyDismissCancelled()
-        }
-
-        repository.setPrimaryStartDisappearAnimation(null)
-        falsingCollector.onBouncerHidden()
-        keyguardStateController.notifyPrimaryBouncerShowing(false /* showing */)
-        cancelShowRunnable()
-        repository.setPrimaryShowingSoon(false)
-        repository.setPrimaryShow(false)
-        repository.setPanelExpansion(EXPANSION_HIDDEN)
-        primaryBouncerCallbackInteractor.dispatchVisibilityChanged(View.INVISIBLE)
-        Trace.endSection()
-    }
-
-    /**
-     * Sets the panel expansion which is calculated further upstream. Panel expansion is from 0f
-     * (panel fully hidden) to 1f (panel fully showing). As the panel shows (from 0f => 1f), the
-     * bouncer hides and as the panel becomes hidden (1f => 0f), the bouncer starts to show.
-     * Therefore, a panel expansion of 1f represents the bouncer fully hidden and a panel expansion
-     * of 0f represents the bouncer fully showing.
-     */
-    fun setPanelExpansion(expansion: Float) {
-        val oldExpansion = repository.panelExpansionAmount.value
-        val expansionChanged = oldExpansion != expansion
-        if (repository.primaryBouncerStartingDisappearAnimation.value == null) {
-            repository.setPanelExpansion(expansion)
-        }
-
-        if (
-            expansion == KeyguardBouncerConstants.EXPANSION_VISIBLE &&
-                oldExpansion != KeyguardBouncerConstants.EXPANSION_VISIBLE
-        ) {
-            falsingCollector.onBouncerShown()
-            primaryBouncerCallbackInteractor.dispatchFullyShown()
-        } else if (
-            expansion == KeyguardBouncerConstants.EXPANSION_HIDDEN &&
-                oldExpansion != KeyguardBouncerConstants.EXPANSION_HIDDEN
-        ) {
-            /*
-             * There are cases where #hide() was not invoked, such as when
-             * NotificationPanelViewController controls the hide animation. Make sure the state gets
-             * updated by calling #hide() directly.
-             */
-            hide()
-            DejankUtils.postAfterTraversal { primaryBouncerCallbackInteractor.dispatchReset() }
-            primaryBouncerCallbackInteractor.dispatchFullyHidden()
-        } else if (
-            expansion != KeyguardBouncerConstants.EXPANSION_VISIBLE &&
-                oldExpansion == KeyguardBouncerConstants.EXPANSION_VISIBLE
-        ) {
-            primaryBouncerCallbackInteractor.dispatchStartingToHide()
-            repository.setPrimaryStartingToHide(true)
-        }
-        if (expansionChanged) {
-            primaryBouncerCallbackInteractor.dispatchExpansionChanged(expansion)
-        }
-    }
-
-    /** Set the initial keyguard message to show when bouncer is shown. */
-    fun showMessage(message: String?, colorStateList: ColorStateList?) {
-        repository.setShowMessage(BouncerShowMessageModel(message, colorStateList))
-    }
-
-    /**
-     * Sets actions to the bouncer based on how the bouncer is dismissed. If the bouncer is
-     * unlocked, we will run the onDismissAction. If the bouncer is existed before unlocking, we
-     * call cancelAction.
-     */
-    fun setDismissAction(
-        onDismissAction: ActivityStarter.OnDismissAction?,
-        cancelAction: Runnable?
-    ) {
-        primaryBouncerView.delegate?.setDismissAction(onDismissAction, cancelAction)
-    }
-
-    /** Update the resources of the views. */
-    fun updateResources() {
-        repository.setResourceUpdateRequests(true)
-    }
-
-    /** Tell the bouncer that keyguard is authenticated. */
-    fun notifyKeyguardAuthenticated(strongAuth: Boolean) {
-        repository.setKeyguardAuthenticated(strongAuth)
-    }
-
-    /** Update the position of the bouncer when showing. */
-    fun setKeyguardPosition(position: Float) {
-        repository.setKeyguardPosition(position)
-    }
-
-    /** Notifies that the state change was handled. */
-    fun notifyKeyguardAuthenticatedHandled() {
-        repository.setKeyguardAuthenticated(null)
-    }
-
-    /** Notifies that the message was shown. */
-    fun onMessageShown() {
-        repository.setShowMessage(null)
-    }
-
-    /** Notify that the resources have been updated */
-    fun notifyUpdatedResources() {
-        repository.setResourceUpdateRequests(false)
-    }
-
-    /** Set whether back button is enabled when on the bouncer screen. */
-    fun setBackButtonEnabled(enabled: Boolean) {
-        repository.setIsBackButtonEnabled(enabled)
-    }
-
-    /** Tell the bouncer to start the pre hide animation. */
-    fun startDisappearAnimation(runnable: Runnable) {
-        if (willRunDismissFromKeyguard()) {
-            runnable.run()
-            return
-        }
-
-        repository.setPrimaryStartDisappearAnimation(runnable)
-    }
-
-    /** Determine whether to show the side fps animation. */
-    fun updateSideFpsVisibility() {
-        val sfpsEnabled: Boolean =
-            context.resources.getBoolean(R.bool.config_show_sidefps_hint_on_bouncer)
-        val fpsDetectionRunning: Boolean = keyguardUpdateMonitor.isFingerprintDetectionRunning
-        val isUnlockingWithFpAllowed: Boolean =
-            keyguardUpdateMonitor.isUnlockingWithFingerprintAllowed
-        val toShow =
-            (isBouncerShowing() &&
-                sfpsEnabled &&
-                fpsDetectionRunning &&
-                isUnlockingWithFpAllowed &&
-                !isAnimatingAway())
-
-        if (KeyguardConstants.DEBUG) {
-            Log.d(
-                TAG,
-                ("sideFpsToShow=$toShow\n" +
-                    "isBouncerShowing=${isBouncerShowing()}\n" +
-                    "configEnabled=$sfpsEnabled\n" +
-                    "fpsDetectionRunning=$fpsDetectionRunning\n" +
-                    "isUnlockingWithFpAllowed=$isUnlockingWithFpAllowed\n" +
-                    "isAnimatingAway=${isAnimatingAway()}")
-            )
-        }
-        repository.setSideFpsShowing(toShow)
-    }
-
-    /** Returns whether bouncer is fully showing. */
-    fun isFullyShowing(): Boolean {
-        return (repository.primaryBouncerShowingSoon.value || isBouncerShowing()) &&
-            repository.panelExpansionAmount.value == KeyguardBouncerConstants.EXPANSION_VISIBLE &&
-            repository.primaryBouncerStartingDisappearAnimation.value == null
-    }
-
-    /** Returns whether bouncer is scrimmed. */
-    fun isScrimmed(): Boolean {
-        return repository.primaryBouncerScrimmed.value
-    }
-
-    /** If bouncer expansion is between 0f and 1f non-inclusive. */
-    fun isInTransit(): Boolean {
-        return repository.primaryBouncerShowingSoon.value ||
-            repository.panelExpansionAmount.value != KeyguardBouncerConstants.EXPANSION_HIDDEN &&
-                repository.panelExpansionAmount.value != KeyguardBouncerConstants.EXPANSION_VISIBLE
-    }
-
-    /** Return whether bouncer is animating away. */
-    fun isAnimatingAway(): Boolean {
-        return repository.primaryBouncerStartingDisappearAnimation.value != null
-    }
-
-    /** Return whether bouncer will dismiss with actions */
-    fun willDismissWithAction(): Boolean {
-        return primaryBouncerView.delegate?.willDismissWithActions() == true
-    }
-
-    /** Will the dismissal run from the keyguard layout (instead of from bouncer) */
-    fun willRunDismissFromKeyguard(): Boolean {
-        return primaryBouncerView.delegate?.willRunDismissFromKeyguard() == true
-    }
-
-    /** Returns whether the bouncer should be full screen. */
-    private fun needsFullscreenBouncer(): Boolean {
-        val mode: KeyguardSecurityModel.SecurityMode =
-            keyguardSecurityModel.getSecurityMode(KeyguardUpdateMonitor.getCurrentUser())
-        return mode == KeyguardSecurityModel.SecurityMode.SimPin ||
-            mode == KeyguardSecurityModel.SecurityMode.SimPuk
-    }
-
-    /** Remove the show runnable from the main handler queue to improve performance. */
-    private fun cancelShowRunnable() {
-        DejankUtils.removeCallbacks(showRunnable)
-        mainHandler.removeCallbacks(showRunnable)
-    }
-
-    private fun isBouncerShowing(): Boolean {
-        return repository.primaryBouncerShow.value
-    }
-
-    /** Whether we want to wait to show the bouncer in case passive auth succeeds. */
-    private fun usePrimaryBouncerPassiveAuthDelay(): Boolean {
-        val canRunFaceAuth = keyguardStateController.isFaceAuthEnabled &&
-                keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(BiometricSourceType.FACE)
-        val canRunActiveUnlock = currentUserActiveUnlockRunning &&
-                keyguardUpdateMonitor.canTriggerActiveUnlockBasedOnDeviceState()
-
-        return featureFlags.isEnabled(Flags.DELAY_BOUNCER) &&
-                !needsFullscreenBouncer() &&
-                (canRunFaceAuth || canRunActiveUnlock)
-    }
-
-    companion object {
-        private const val TAG = "PrimaryBouncerInteractor"
-    }
-}
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 6b515da..6e7a20b 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
@@ -16,15 +16,22 @@
 
 package com.android.systemui.keyguard.domain.interactor
 
+import android.content.Context
+import android.hardware.biometrics.BiometricFaceConstants
 import com.android.keyguard.FaceAuthUiEvent
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.systemui.CoreStartable
+import com.android.systemui.R
+import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
 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.DeviceEntryFaceAuthRepository
+import com.android.systemui.keyguard.shared.model.ErrorFaceAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.FaceAuthenticationStatus
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.log.FaceAuthenticationLogger
 import com.android.systemui.util.kotlin.pairwise
@@ -32,7 +39,9 @@
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.map
@@ -48,6 +57,7 @@
 class SystemUIKeyguardFaceAuthInteractor
 @Inject
 constructor(
+    private val context: Context,
     @Application private val applicationScope: CoroutineScope,
     @Main private val mainDispatcher: CoroutineDispatcher,
     private val repository: DeviceEntryFaceAuthRepository,
@@ -155,17 +165,28 @@
         repository.cancel()
     }
 
+    private val faceAuthenticationStatusOverride = MutableStateFlow<FaceAuthenticationStatus?>(null)
     /** Provide the status of face authentication */
-    override val authenticationStatus = repository.authenticationStatus
+    override val authenticationStatus =
+        merge(faceAuthenticationStatusOverride.filterNotNull(), repository.authenticationStatus)
 
     /** Provide the status of face detection */
     override val detectionStatus = repository.detectionStatus
 
     private fun runFaceAuth(uiEvent: FaceAuthUiEvent, fallbackToDetect: Boolean) {
         if (featureFlags.isEnabled(Flags.FACE_AUTH_REFACTOR)) {
-            applicationScope.launch {
-                faceAuthenticationLogger.authRequested(uiEvent)
-                repository.authenticate(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 {
+                faceAuthenticationStatusOverride.value = null
+                applicationScope.launch {
+                    faceAuthenticationLogger.authRequested(uiEvent)
+                    repository.authenticate(uiEvent, fallbackToDetection = fallbackToDetect)
+                }
             }
         } else {
             faceAuthenticationLogger.ignoredFaceAuthTrigger(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
index b7dd1a5..0dda625 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
@@ -15,6 +15,14 @@
  */
 
 package com.android.systemui.keyguard.domain.interactor
+
+import android.animation.ValueAnimator
+import android.util.Log
+import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionInfo
+import java.util.UUID
+
 /**
  * Each TransitionInteractor is responsible for determining under which conditions to notify
  * [KeyguardTransitionRepository] to signal a transition. When (and if) the transition occurs is
@@ -26,6 +34,50 @@
  * MUST list implementing classes in dagger module [StartKeyguardTransitionModule] and also in the
  * 'when' clause of [KeyguardTransitionCoreStartable]
  */
-sealed class TransitionInteractor(val name: String) {
+sealed class TransitionInteractor(
+    val fromState: KeyguardState,
+) {
+    val name = this::class.simpleName ?: "UnknownTransitionInteractor"
+
+    abstract val transitionRepository: KeyguardTransitionRepository
+    abstract val transitionInteractor: KeyguardTransitionInteractor
     abstract fun start()
+
+    fun startTransitionTo(
+        toState: KeyguardState,
+        animator: ValueAnimator? = getDefaultAnimatorForTransitionsToState(toState),
+        resetIfCancelled: Boolean = false
+    ): UUID? {
+        if (
+            fromState != transitionInteractor.startedKeyguardState.value &&
+                fromState != transitionInteractor.finishedKeyguardState.value
+        ) {
+            Log.e(
+                name,
+                "startTransition: We were asked to transition from " +
+                    "$fromState to $toState, however we last finished a transition to " +
+                    "${transitionInteractor.finishedKeyguardState.value}, " +
+                    "and last started a transition to " +
+                    "${transitionInteractor.startedKeyguardState.value}. " +
+                    "Ignoring startTransition, but this should never happen."
+            )
+            return null
+        }
+
+        return transitionRepository.startTransition(
+            TransitionInfo(
+                name,
+                fromState,
+                toState,
+                animator,
+            ),
+            resetIfCancelled
+        )
+    }
+
+    /**
+     * Returns a ValueAnimator to be used for transitions to [toState], if one is not explicitly
+     * passed to [startTransitionTo].
+     */
+    abstract fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator?
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractor.kt
new file mode 100644
index 0000000..bba0e37
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractor.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.keyguard.domain.interactor
+
+import android.animation.FloatEvaluator
+import android.animation.IntEvaluator
+import com.android.systemui.common.ui.data.repository.ConfigurationRepository
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+
+/** Encapsulates business logic for transitions between UDFPS states on the keyguard. */
+@ExperimentalCoroutinesApi
+@SysUISingleton
+class UdfpsKeyguardInteractor
+@Inject
+constructor(
+    configRepo: ConfigurationRepository,
+    burnInInteractor: BurnInInteractor,
+    keyguardInteractor: KeyguardInteractor,
+) {
+    private val intEvaluator = IntEvaluator()
+    private val floatEvaluator = FloatEvaluator()
+
+    val dozeAmount = keyguardInteractor.dozeAmount
+    val scaleForResolution = configRepo.scaleForResolution
+
+    /** Burn-in offsets for the UDFPS view to mitigate burn-in on AOD. */
+    val burnInOffsets: Flow<BurnInOffsets> =
+        combine(
+            keyguardInteractor.dozeAmount,
+            burnInInteractor.udfpsBurnInXOffset,
+            burnInInteractor.udfpsBurnInYOffset,
+            burnInInteractor.udfpsBurnInProgress
+        ) { dozeAmount, fullyDozingBurnInX, fullyDozingBurnInY, fullyDozingBurnInProgress ->
+            BurnInOffsets(
+                intEvaluator.evaluate(dozeAmount, 0, fullyDozingBurnInX),
+                intEvaluator.evaluate(dozeAmount, 0, fullyDozingBurnInY),
+                floatEvaluator.evaluate(dozeAmount, 0, fullyDozingBurnInProgress),
+            )
+        }
+}
+
+data class BurnInOffsets(
+    val burnInXOffset: Int, // current x burn in offset based on the aodTransitionAmount
+    val burnInYOffset: Int, // current y burn in offset based on the aodTransitionAmount
+    val burnInProgress: Float, // current progress based on the aodTransitionAmount
+)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceModule.kt
deleted file mode 100644
index b48acb6..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceModule.kt
+++ /dev/null
@@ -1,30 +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.systemui.keyguard.domain.quickaffordance
-
-import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
-import dagger.Binds
-import dagger.Module
-
-@Module
-interface KeyguardQuickAffordanceModule {
-    @Binds
-    fun keyguardQuickAffordanceRegistry(
-        impl: KeyguardQuickAffordanceRegistryImpl
-    ): KeyguardQuickAffordanceRegistry<out KeyguardQuickAffordanceConfig>
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceRegistry.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceRegistry.kt
deleted file mode 100644
index 8526ada..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceRegistry.kt
+++ /dev/null
@@ -1,66 +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.systemui.keyguard.domain.quickaffordance
-
-import com.android.systemui.keyguard.data.quickaffordance.HomeControlsKeyguardQuickAffordanceConfig
-import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
-import com.android.systemui.keyguard.data.quickaffordance.QrCodeScannerKeyguardQuickAffordanceConfig
-import com.android.systemui.keyguard.data.quickaffordance.QuickAccessWalletKeyguardQuickAffordanceConfig
-import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
-import javax.inject.Inject
-
-/** Central registry of all known quick affordance configs. */
-interface KeyguardQuickAffordanceRegistry<T : KeyguardQuickAffordanceConfig> {
-    fun getAll(position: KeyguardQuickAffordancePosition): List<T>
-    fun get(key: String): T
-}
-
-class KeyguardQuickAffordanceRegistryImpl
-@Inject
-constructor(
-    homeControls: HomeControlsKeyguardQuickAffordanceConfig,
-    quickAccessWallet: QuickAccessWalletKeyguardQuickAffordanceConfig,
-    qrCodeScanner: QrCodeScannerKeyguardQuickAffordanceConfig,
-) : KeyguardQuickAffordanceRegistry<KeyguardQuickAffordanceConfig> {
-    private val configsByPosition =
-        mapOf(
-            KeyguardQuickAffordancePosition.BOTTOM_START to
-                listOf(
-                    homeControls,
-                ),
-            KeyguardQuickAffordancePosition.BOTTOM_END to
-                listOf(
-                    quickAccessWallet,
-                    qrCodeScanner,
-                ),
-        )
-    private val configByKey =
-        configsByPosition.values.flatten().associateBy { config -> config.key }
-
-    override fun getAll(
-        position: KeyguardQuickAffordancePosition,
-    ): List<KeyguardQuickAffordanceConfig> {
-        return configsByPosition.getValue(position)
-    }
-
-    override fun get(
-        key: String,
-    ): KeyguardQuickAffordanceConfig {
-        return configByKey.getValue(key)
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/constants/KeyguardBouncerConstants.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/constants/KeyguardBouncerConstants.kt
deleted file mode 100644
index c45faf0..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/constants/KeyguardBouncerConstants.kt
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package com.android.systemui.keyguard.shared.constants
-
-object KeyguardBouncerConstants {
-    /**
-     * Values for the bouncer expansion represented as the panel expansion. Panel expansion 1f =
-     * panel fully showing = bouncer fully hidden Panel expansion 0f = panel fully hiding = bouncer
-     * fully showing
-     */
-    const val EXPANSION_HIDDEN = 1f
-    const val EXPANSION_VISIBLE = 0f
-    const val ALPHA_EXPANSION_THRESHOLD = 0.95f
-
-    /**
-     * This value is used for denoting the PIN length at which we want to layout the view in which
-     * PIN hinting is enabled
-     */
-    const val DEFAULT_PIN_LENGTH = 6
-
-    object ColorId {
-        const val TITLE = com.android.internal.R.attr.materialColorOnSurface
-        const val PIN_SHAPES = com.android.internal.R.attr.materialColorOnSurfaceVariant
-        const val NUM_PAD_BACKGROUND = com.android.internal.R.attr.materialColorSurfaceContainerHigh
-        const val NUM_PAD_BACKGROUND_PRESSED = com.android.internal.R.attr.materialColorPrimaryFixed
-        const val NUM_PAD_PRESSED = com.android.internal.R.attr.materialColorOnPrimaryFixed
-        const val NUM_PAD_KEY = com.android.internal.R.attr.materialColorOnSurface
-        const val NUM_PAD_BUTTON = com.android.internal.R.attr.materialColorOnSecondaryFixed
-        const val EMERGENCY_BUTTON = com.android.internal.R.attr.materialColorTertiaryFixed
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BouncerCallbackActionsModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BouncerCallbackActionsModel.kt
deleted file mode 100644
index 81cf5b4..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BouncerCallbackActionsModel.kt
+++ /dev/null
@@ -1,26 +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.systemui.keyguard.shared.model
-
-import com.android.systemui.plugins.ActivityStarter
-
-/** Encapsulates callbacks to be invoked by the bouncer logic. */
-// TODO(b/243683121): Move dismiss logic from view controllers
-data class BouncerCallbackActionsModel(
-    val onDismissAction: ActivityStarter.OnDismissAction?,
-    val cancelAction: Runnable?
-)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BouncerShowMessageModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BouncerShowMessageModel.kt
deleted file mode 100644
index 05cdeaa..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BouncerShowMessageModel.kt
+++ /dev/null
@@ -1,22 +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.systemui.keyguard.shared.model
-
-import android.content.res.ColorStateList
-
-/** Show a keyguard message to the bouncer. */
-data class BouncerShowMessageModel(val message: String?, val colorStateList: ColorStateList?)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FaceAuthenticationModels.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FaceAuthenticationModels.kt
index 9e7dec4..0f6d82e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FaceAuthenticationModels.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FaceAuthenticationModels.kt
@@ -17,34 +17,41 @@
 package com.android.systemui.keyguard.shared.model
 
 import android.hardware.face.FaceManager
+import android.os.SystemClock.elapsedRealtime
 
 /**
  * Authentication status provided by
  * [com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthRepository]
  */
-sealed class AuthenticationStatus
+sealed class FaceAuthenticationStatus
 
 /** Success authentication status. */
-data class SuccessAuthenticationStatus(val successResult: FaceManager.AuthenticationResult) :
-    AuthenticationStatus()
+data class SuccessFaceAuthenticationStatus(val successResult: FaceManager.AuthenticationResult) :
+    FaceAuthenticationStatus()
 
 /** Face authentication help message. */
-data class HelpAuthenticationStatus(val msgId: Int, val msg: String?) : AuthenticationStatus()
+data class HelpFaceAuthenticationStatus(val msgId: Int, val msg: String?) :
+    FaceAuthenticationStatus()
 
 /** Face acquired message. */
-data class AcquiredAuthenticationStatus(val acquiredInfo: Int) : AuthenticationStatus()
+data class AcquiredFaceAuthenticationStatus(val acquiredInfo: Int) : FaceAuthenticationStatus()
 
 /** Face authentication failed message. */
-object FailedAuthenticationStatus : AuthenticationStatus()
+object FailedFaceAuthenticationStatus : FaceAuthenticationStatus()
 
 /** Face authentication error message */
-data class ErrorAuthenticationStatus(val msgId: Int, val msg: String? = null) :
-    AuthenticationStatus() {
+data class ErrorFaceAuthenticationStatus(
+    val msgId: Int,
+    val msg: String? = null,
+    // present to break equality check if the same error occurs repeatedly.
+    val createdAt: Long = elapsedRealtime()
+) : FaceAuthenticationStatus() {
     /**
      * Method that checks if [msgId] is a lockout error. A lockout error means that face
      * authentication is locked out.
      */
-    fun isLockoutError() = msgId == FaceManager.FACE_ERROR_LOCKOUT_PERMANENT
+    fun isLockoutError() =
+        msgId == FaceManager.FACE_ERROR_LOCKOUT_PERMANENT || msgId == FaceManager.FACE_ERROR_LOCKOUT
 
     /**
      * Method that checks if [msgId] is a cancellation error. This means that face authentication
@@ -59,4 +66,4 @@
 }
 
 /** Face detection success message. */
-data class DetectionStatus(val sensorId: Int, val userId: Int, val isStrongBiometric: Boolean)
+data class FaceDetectionStatus(val sensorId: Int, val userId: Int, val isStrongBiometric: Boolean)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt
new file mode 100644
index 0000000..7fc6016
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt
@@ -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.systemui.keyguard.shared.model
+
+import android.os.SystemClock.elapsedRealtime
+
+/**
+ * Fingerprint authentication status provided by
+ * [com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository]
+ */
+sealed class FingerprintAuthenticationStatus
+
+/** Fingerprint authentication success status. */
+data class SuccessFingerprintAuthenticationStatus(
+    val userId: Int,
+    val isStrongBiometric: Boolean,
+) : FingerprintAuthenticationStatus()
+
+/** Fingerprint authentication help message. */
+data class HelpFingerprintAuthenticationStatus(
+    val msgId: Int,
+    val msg: String?,
+) : FingerprintAuthenticationStatus()
+
+/** Fingerprint acquired message. */
+data class AcquiredFingerprintAuthenticationStatus(val acquiredInfo: Int) :
+    FingerprintAuthenticationStatus()
+
+/** Fingerprint authentication failed message. */
+object FailFingerprintAuthenticationStatus : FingerprintAuthenticationStatus()
+
+/** Fingerprint authentication error message */
+data class ErrorFingerprintAuthenticationStatus(
+    val msgId: Int,
+    val msg: String? = null,
+    // present to break equality check if the same error occurs repeatedly.
+    val createdAt: Long = elapsedRealtime(),
+) : FingerprintAuthenticationStatus()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/ScreenModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/ScreenModel.kt
new file mode 100644
index 0000000..80a1b75
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/ScreenModel.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.shared.model
+
+import com.android.systemui.keyguard.ScreenLifecycle
+
+/** Model device screen lifecycle states. */
+data class ScreenModel(
+    val state: ScreenState,
+) {
+    companion object {
+        fun fromScreenLifecycle(screenLifecycle: ScreenLifecycle): ScreenModel {
+            return ScreenModel(ScreenState.fromScreenLifecycleInt(screenLifecycle.getScreenState()))
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/ScreenState.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/ScreenState.kt
new file mode 100644
index 0000000..fe5d935
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/ScreenState.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.shared.model
+
+import com.android.systemui.keyguard.ScreenLifecycle
+
+enum class ScreenState {
+    /** Screen is fully off. */
+    SCREEN_OFF,
+    /** Signal that the screen is turning on. */
+    SCREEN_TURNING_ON,
+    /** Screen is fully on. */
+    SCREEN_ON,
+    /** Signal that the screen is turning off. */
+    SCREEN_TURNING_OFF;
+
+    companion object {
+        fun fromScreenLifecycleInt(value: Int): ScreenState {
+            return when (value) {
+                ScreenLifecycle.SCREEN_OFF -> SCREEN_OFF
+                ScreenLifecycle.SCREEN_TURNING_ON -> SCREEN_TURNING_ON
+                ScreenLifecycle.SCREEN_ON -> SCREEN_ON
+                ScreenLifecycle.SCREEN_TURNING_OFF -> SCREEN_TURNING_OFF
+                else -> throw IllegalArgumentException("Invalid screen value: $value")
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt
index 767fd58..0fa6f4f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt
@@ -16,7 +16,9 @@
 package com.android.systemui.keyguard.shared.model
 
 /** This information will flow from the [KeyguardTransitionRepository] to control the UI layer */
-data class TransitionStep(
+data class TransitionStep
+@JvmOverloads
+constructor(
     val from: KeyguardState = KeyguardState.OFF,
     val to: KeyguardState = KeyguardState.OFF,
     val value: Float = 0f, // constrained [0.0, 1.0]
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakeSleepReason.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakeSleepReason.kt
index 51ce7ff..fb685da 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakeSleepReason.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakeSleepReason.kt
@@ -23,9 +23,12 @@
     /** The physical power button was pressed to wake up or sleep the device. */
     POWER_BUTTON,
 
-    /** The user has taped or double tapped to wake the screen */
+    /** The user has tapped or double tapped to wake the screen. */
     TAP,
 
+    /** The user performed some sort of gesture to wake the screen. */
+    GESTURE,
+
     /** Something else happened to wake up or sleep the device. */
     OTHER;
 
@@ -34,6 +37,7 @@
             return when (reason) {
                 PowerManager.WAKE_REASON_POWER_BUTTON -> POWER_BUTTON
                 PowerManager.WAKE_REASON_TAP -> TAP
+                PowerManager.WAKE_REASON_GESTURE -> GESTURE
                 else -> OTHER
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessModel.kt
index 52e15be..cfd9e08 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessModel.kt
@@ -27,7 +27,11 @@
 
     fun isStartingToSleep() = state == WakefulnessState.STARTING_TO_SLEEP
 
-    fun isStartingToSleepOrAsleep() = isStartingToSleep() || state == WakefulnessState.ASLEEP
+    private fun isAsleep() = state == WakefulnessState.ASLEEP
+
+    fun isStartingToSleepOrAsleep() = isStartingToSleep() || isAsleep()
+
+    fun isDeviceInteractive() = !isAsleep()
 
     fun isStartingToWakeOrAwake() = isStartingToWake() || state == WakefulnessState.AWAKE
 
@@ -43,6 +47,11 @@
     fun isAwakeFromTap() =
         state == WakefulnessState.STARTING_TO_WAKE && lastWakeReason == WakeSleepReason.TAP
 
+    fun isDeviceInteractiveFromTapOrGesture(): Boolean {
+        return isDeviceInteractive() &&
+            (lastWakeReason == WakeSleepReason.TAP || lastWakeReason == WakeSleepReason.GESTURE)
+    }
+
     companion object {
         fun fromWakefulnessLifecycle(wakefulnessLifecycle: WakefulnessLifecycle): WakefulnessModel {
             return WakefulnessModel(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/adapter/UdfpsKeyguardViewControllerAdapter.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/adapter/UdfpsKeyguardViewControllerAdapter.kt
new file mode 100644
index 0000000..ebf1beb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/adapter/UdfpsKeyguardViewControllerAdapter.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.adapter
+
+/**
+ * Temporary adapter class while
+ * [com.android.systemui.biometrics.ui.controller.UdfpsKeyguardViewController] is being refactored
+ * before [com.android.systemui.biometrics.UdfpsKeyguardViewControllerLegacy] is removed.
+ *
+ * TODO (b/278719514): Delete once udfps keyguard view is fully refactored.
+ */
+interface UdfpsKeyguardViewControllerAdapter
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 5ad6e51..db84268 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
@@ -21,12 +21,10 @@
 import android.graphics.Rect
 import android.graphics.drawable.Animatable2
 import android.util.Size
-import android.util.TypedValue
 import android.view.View
 import android.view.ViewGroup
 import android.view.ViewPropertyAnimator
 import android.widget.ImageView
-import android.widget.TextView
 import androidx.core.view.isInvisible
 import androidx.core.view.isVisible
 import androidx.core.view.updateLayoutParams
@@ -108,14 +106,10 @@
         activityStarter: ActivityStarter?,
         messageDisplayer: (Int) -> Unit,
     ): Binding {
-        val indicationArea: View = view.requireViewById(R.id.keyguard_indication_area)
         val ambientIndicationArea: View? = view.findViewById(R.id.ambient_indication_container)
         val startButton: ImageView = view.requireViewById(R.id.start_button)
         val endButton: ImageView = view.requireViewById(R.id.end_button)
         val overlayContainer: View = view.requireViewById(R.id.overlay_container)
-        val indicationText: TextView = view.requireViewById(R.id.keyguard_indication_text)
-        val indicationTextBottom: TextView =
-            view.requireViewById(R.id.keyguard_indication_text_bottom)
         val settingsMenu: LaunchableLinearLayout =
             view.requireViewById(R.id.keyguard_settings_button)
 
@@ -183,7 +177,6 @@
                                 }
 
                             ambientIndicationArea?.alpha = alpha
-                            indicationArea.alpha = alpha
                         }
                     }
 
@@ -205,50 +198,23 @@
 
                     launch {
                         viewModel.indicationAreaTranslationX.collect { translationX ->
-                            indicationArea.translationX = translationX
                             ambientIndicationArea?.translationX = translationX
                         }
                     }
 
                     launch {
-                        combine(
-                                viewModel.isIndicationAreaPadded,
-                                configurationBasedDimensions.map { it.indicationAreaPaddingPx },
-                            ) { isPadded, paddingIfPaddedPx ->
-                                if (isPadded) {
-                                    paddingIfPaddedPx
-                                } else {
-                                    0
-                                }
-                            }
-                            .collect { paddingPx ->
-                                indicationArea.setPadding(paddingPx, 0, paddingPx, 0)
-                            }
-                    }
-
-                    launch {
                         configurationBasedDimensions
                             .map { it.defaultBurnInPreventionYOffsetPx }
                             .flatMapLatest { defaultBurnInOffsetY ->
                                 viewModel.indicationAreaTranslationY(defaultBurnInOffsetY)
                             }
                             .collect { translationY ->
-                                indicationArea.translationY = translationY
                                 ambientIndicationArea?.translationY = translationY
                             }
                     }
 
                     launch {
                         configurationBasedDimensions.collect { dimensions ->
-                            indicationText.setTextSize(
-                                TypedValue.COMPLEX_UNIT_PX,
-                                dimensions.indicationTextSizePx.toFloat(),
-                            )
-                            indicationTextBottom.setTextSize(
-                                TypedValue.COMPLEX_UNIT_PX,
-                                dimensions.indicationTextSizePx.toFloat(),
-                            )
-
                             startButton.updateLayoutParams<ViewGroup.LayoutParams> {
                                 width = dimensions.buttonSizePx.width
                                 height = dimensions.buttonSizePx.height
@@ -305,7 +271,7 @@
 
         return object : Binding {
             override fun getIndicationAreaAnimators(): List<ViewPropertyAnimator> {
-                return listOf(indicationArea, ambientIndicationArea).mapNotNull { it?.animate() }
+                return listOf(ambientIndicationArea).mapNotNull { it?.animate() }
             }
 
             override fun onConfigurationChanged() {
@@ -517,12 +483,6 @@
         return ConfigurationBasedDimensions(
             defaultBurnInPreventionYOffsetPx =
                 view.resources.getDimensionPixelOffset(R.dimen.default_burn_in_prevention_offset),
-            indicationAreaPaddingPx =
-                view.resources.getDimensionPixelOffset(R.dimen.keyguard_indication_area_padding),
-            indicationTextSizePx =
-                view.resources.getDimensionPixelSize(
-                    com.android.internal.R.dimen.text_size_small_material,
-                ),
             buttonSizePx =
                 Size(
                     view.resources.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_width),
@@ -552,8 +512,6 @@
 
     private data class ConfigurationBasedDimensions(
         val defaultBurnInPreventionYOffsetPx: Int,
-        val indicationAreaPaddingPx: Int,
-        val indicationTextSizePx: Int,
         val buttonSizePx: Size,
     )
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
deleted file mode 100644
index c1aefc7..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
+++ /dev/null
@@ -1,237 +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.systemui.keyguard.ui.binder
-
-import android.view.KeyEvent
-import android.view.View
-import android.view.ViewGroup
-import android.window.OnBackAnimationCallback
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.repeatOnLifecycle
-import com.android.keyguard.KeyguardSecurityContainerController
-import com.android.keyguard.KeyguardSecurityModel
-import com.android.keyguard.KeyguardSecurityView
-import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.keyguard.dagger.KeyguardBouncerComponent
-import com.android.systemui.keyguard.data.BouncerViewDelegate
-import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_VISIBLE
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel
-import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel
-import com.android.systemui.lifecycle.repeatWhenAttached
-import com.android.systemui.plugins.ActivityStarter
-import kotlinx.coroutines.awaitCancellation
-import kotlinx.coroutines.flow.filter
-import kotlinx.coroutines.launch
-
-/** Binds the bouncer container to its view model. */
-object KeyguardBouncerViewBinder {
-    @JvmStatic
-    fun bind(
-        view: ViewGroup,
-        viewModel: KeyguardBouncerViewModel,
-        primaryBouncerToGoneTransitionViewModel: PrimaryBouncerToGoneTransitionViewModel,
-        componentFactory: KeyguardBouncerComponent.Factory
-    ) {
-        // Builds the KeyguardSecurityContainerController from bouncer view group.
-        val securityContainerController: KeyguardSecurityContainerController =
-            componentFactory.create(view).securityContainerController
-        securityContainerController.init()
-        val delegate =
-            object : BouncerViewDelegate {
-                override fun isFullScreenBouncer(): Boolean {
-                    val mode = securityContainerController.currentSecurityMode
-                    return mode == KeyguardSecurityModel.SecurityMode.SimPin ||
-                        mode == KeyguardSecurityModel.SecurityMode.SimPuk
-                }
-
-                override fun getBackCallback(): OnBackAnimationCallback {
-                    return securityContainerController.backCallback
-                }
-
-                override fun shouldDismissOnMenuPressed(): Boolean {
-                    return securityContainerController.shouldEnableMenuKey()
-                }
-
-                override fun interceptMediaKey(event: KeyEvent?): Boolean {
-                    return securityContainerController.interceptMediaKey(event)
-                }
-
-                override fun dispatchBackKeyEventPreIme(): Boolean {
-                    return securityContainerController.dispatchBackKeyEventPreIme()
-                }
-
-                override fun showNextSecurityScreenOrFinish(): Boolean {
-                    return securityContainerController.dismiss(
-                        KeyguardUpdateMonitor.getCurrentUser()
-                    )
-                }
-
-                override fun resume() {
-                    securityContainerController.showPrimarySecurityScreen(/* isTurningOff= */ false)
-                    securityContainerController.onResume(KeyguardSecurityView.SCREEN_ON)
-                }
-
-                override fun setDismissAction(
-                    onDismissAction: ActivityStarter.OnDismissAction?,
-                    cancelAction: Runnable?
-                ) {
-                    securityContainerController.setOnDismissAction(onDismissAction, cancelAction)
-                }
-
-                override fun willDismissWithActions(): Boolean {
-                    return securityContainerController.hasDismissActions()
-                }
-
-                override fun willRunDismissFromKeyguard(): Boolean {
-                    return securityContainerController.willRunDismissFromKeyguard()
-                }
-
-                override fun showPromptReason(reason: Int) {
-                    securityContainerController.showPromptReason(reason)
-                }
-            }
-        view.repeatWhenAttached {
-            repeatOnLifecycle(Lifecycle.State.CREATED) {
-                try {
-                    viewModel.setBouncerViewDelegate(delegate)
-                    launch {
-                        viewModel.isShowing.collect { isShowing ->
-                            view.visibility = if (isShowing) View.VISIBLE else View.INVISIBLE
-                            if (isShowing) {
-                                securityContainerController.reinflateViewFlipper {
-                                    // Reset Security Container entirely.
-                                    securityContainerController.onBouncerVisibilityChanged(
-                                        /* isVisible= */ true
-                                    )
-                                    securityContainerController.showPrimarySecurityScreen(
-                                        /* turningOff= */ false
-                                    )
-                                    securityContainerController.setInitialMessage()
-                                    securityContainerController.appear()
-                                    securityContainerController.onResume(
-                                        KeyguardSecurityView.SCREEN_ON
-                                    )
-                                }
-                            } else {
-                                securityContainerController.onBouncerVisibilityChanged(
-                                    /* isVisible= */ false
-                                )
-                                securityContainerController.cancelDismissAction()
-                                securityContainerController.reset()
-                                securityContainerController.onPause()
-                            }
-                        }
-                    }
-
-                    launch {
-                        viewModel.startingToHide.collect {
-                            securityContainerController.onStartingToHide()
-                        }
-                    }
-
-                    launch {
-                        viewModel.startDisappearAnimation.collect {
-                            securityContainerController.startDisappearAnimation(it)
-                        }
-                    }
-
-                    launch {
-                        viewModel.bouncerExpansionAmount.collect { expansion ->
-                            securityContainerController.setExpansion(expansion)
-                        }
-                    }
-
-                    launch {
-                        primaryBouncerToGoneTransitionViewModel.bouncerAlpha.collect { alpha ->
-                            securityContainerController.setAlpha(alpha)
-                        }
-                    }
-
-                    launch {
-                        viewModel.bouncerExpansionAmount
-                            .filter { it == EXPANSION_VISIBLE }
-                            .collect {
-                                securityContainerController.onResume(KeyguardSecurityView.SCREEN_ON)
-                                view.announceForAccessibility(securityContainerController.title)
-                            }
-                    }
-
-                    launch {
-                        viewModel.isInteractable.collect { isInteractable ->
-                            securityContainerController.setInteractable(isInteractable)
-                        }
-                    }
-
-                    launch {
-                        viewModel.keyguardPosition.collect { position ->
-                            securityContainerController.updateKeyguardPosition(position)
-                        }
-                    }
-
-                    launch {
-                        viewModel.updateResources.collect {
-                            securityContainerController.updateResources()
-                            viewModel.notifyUpdateResources()
-                        }
-                    }
-
-                    launch {
-                        viewModel.bouncerShowMessage.collect {
-                            securityContainerController.showMessage(
-                                it.message,
-                                it.colorStateList,
-                                /* animated= */ true
-                            )
-                            viewModel.onMessageShown()
-                        }
-                    }
-
-                    launch {
-                        viewModel.keyguardAuthenticated.collect {
-                            securityContainerController.finish(
-                                it,
-                                KeyguardUpdateMonitor.getCurrentUser()
-                            )
-                            viewModel.notifyKeyguardAuthenticated()
-                        }
-                    }
-
-                    launch {
-                        viewModel
-                            .observeOnIsBackButtonEnabled { view.systemUiVisibility }
-                            .collect { view.systemUiVisibility = it }
-                    }
-
-                    launch {
-                        viewModel.shouldUpdateSideFps.collect {
-                            viewModel.updateSideFpsVisibility()
-                        }
-                    }
-
-                    launch {
-                        viewModel.sideFpsShowing.collect {
-                            securityContainerController.updateSideFpsVisibility(it)
-                        }
-                    }
-                    awaitCancellation()
-                } finally {
-                    viewModel.setBouncerViewDelegate(null)
-                }
-            }
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt
new file mode 100644
index 0000000..02e6765
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.binder
+
+import android.util.TypedValue
+import android.view.View
+import android.view.ViewGroup
+import android.widget.TextView
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.R
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardIndicationAreaViewModel
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.statusbar.KeyguardIndicationController
+import kotlinx.coroutines.DisposableHandle
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
+
+/**
+ * Binds a keyguard indication area 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.
+ */
+@OptIn(ExperimentalCoroutinesApi::class)
+object KeyguardIndicationAreaBinder {
+
+    /** Binds the view to the view-model, continuing to update the former based on the latter. */
+    @JvmStatic
+    fun bind(
+        view: ViewGroup,
+        viewModel: KeyguardIndicationAreaViewModel,
+        indicationController: KeyguardIndicationController,
+    ): DisposableHandle {
+        val indicationArea: ViewGroup = view.requireViewById(R.id.keyguard_indication_area)
+        indicationController.setIndicationArea(indicationArea)
+
+        val indicationText: TextView = indicationArea.requireViewById(R.id.keyguard_indication_text)
+        val indicationTextBottom: TextView =
+            indicationArea.requireViewById(R.id.keyguard_indication_text_bottom)
+
+        view.clipChildren = false
+        view.clipToPadding = false
+
+        val configurationBasedDimensions = MutableStateFlow(loadFromResources(view))
+        val disposableHandle =
+            view.repeatWhenAttached {
+                repeatOnLifecycle(Lifecycle.State.STARTED) {
+                    launch {
+                        viewModel.alpha.collect { alpha ->
+                            view.importantForAccessibility =
+                                if (alpha == 0f) {
+                                    View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
+                                } else {
+                                    View.IMPORTANT_FOR_ACCESSIBILITY_AUTO
+                                }
+
+                            indicationArea.alpha = alpha
+                        }
+                    }
+
+                    launch {
+                        viewModel.indicationAreaTranslationX.collect { translationX ->
+                            indicationArea.translationX = translationX
+                        }
+                    }
+
+                    launch {
+                        combine(
+                                viewModel.isIndicationAreaPadded,
+                                configurationBasedDimensions.map { it.indicationAreaPaddingPx },
+                            ) { isPadded, paddingIfPaddedPx ->
+                                if (isPadded) {
+                                    paddingIfPaddedPx
+                                } else {
+                                    0
+                                }
+                            }
+                            .collect { paddingPx ->
+                                indicationArea.setPadding(paddingPx, 0, paddingPx, 0)
+                            }
+                    }
+
+                    launch {
+                        configurationBasedDimensions
+                            .map { it.defaultBurnInPreventionYOffsetPx }
+                            .flatMapLatest { defaultBurnInOffsetY ->
+                                viewModel.indicationAreaTranslationY(defaultBurnInOffsetY)
+                            }
+                            .collect { translationY -> indicationArea.translationY = translationY }
+                    }
+
+                    launch {
+                        configurationBasedDimensions.collect { dimensions ->
+                            indicationText.setTextSize(
+                                TypedValue.COMPLEX_UNIT_PX,
+                                dimensions.indicationTextSizePx.toFloat(),
+                            )
+                            indicationTextBottom.setTextSize(
+                                TypedValue.COMPLEX_UNIT_PX,
+                                dimensions.indicationTextSizePx.toFloat(),
+                            )
+                        }
+                    }
+
+                    launch {
+                        viewModel.configurationChange.collect {
+                            configurationBasedDimensions.value = loadFromResources(view)
+                        }
+                    }
+                }
+            }
+        return disposableHandle
+    }
+
+    private fun loadFromResources(view: View): ConfigurationBasedDimensions {
+        return ConfigurationBasedDimensions(
+            defaultBurnInPreventionYOffsetPx =
+                view.resources.getDimensionPixelOffset(R.dimen.default_burn_in_prevention_offset),
+            indicationAreaPaddingPx =
+                view.resources.getDimensionPixelOffset(R.dimen.keyguard_indication_area_padding),
+            indicationTextSizePx =
+                view.resources.getDimensionPixelSize(
+                    com.android.internal.R.dimen.text_size_small_material,
+                ),
+        )
+    }
+
+    private data class ConfigurationBasedDimensions(
+        val defaultBurnInPreventionYOffsetPx: Int,
+        val indicationAreaPaddingPx: Int,
+        val indicationTextSizePx: Int,
+    )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsAodFingerprintViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsAodFingerprintViewBinder.kt
new file mode 100644
index 0000000..9872d97
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsAodFingerprintViewBinder.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.keyguard.ui.binder
+
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.airbnb.lottie.LottieAnimationView
+import com.android.systemui.keyguard.ui.viewmodel.UdfpsAodViewModel
+import com.android.systemui.lifecycle.repeatWhenAttached
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.launch
+
+@ExperimentalCoroutinesApi
+object UdfpsAodFingerprintViewBinder {
+
+    /**
+     * Drives UI for the UDFPS aod fingerprint view. See [UdfpsFingerprintViewBinder] and
+     * [UdfpsBackgroundViewBinder].
+     */
+    @JvmStatic
+    fun bind(
+        view: LottieAnimationView,
+        viewModel: UdfpsAodViewModel,
+    ) {
+        view.alpha = 0f
+        view.repeatWhenAttached {
+            repeatOnLifecycle(Lifecycle.State.STARTED) {
+                launch {
+                    viewModel.burnInOffsets.collect { burnInOffsets ->
+                        view.progress = burnInOffsets.burnInProgress
+                        view.translationX = burnInOffsets.burnInXOffset.toFloat()
+                        view.translationY = burnInOffsets.burnInYOffset.toFloat()
+                    }
+                }
+
+                launch { viewModel.alpha.collect { alpha -> view.alpha = alpha } }
+
+                launch {
+                    viewModel.padding.collect { padding ->
+                        view.setPadding(padding, padding, padding, padding)
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsBackgroundViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsBackgroundViewBinder.kt
new file mode 100644
index 0000000..0113628
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsBackgroundViewBinder.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.ui.binder
+
+import android.content.res.ColorStateList
+import android.widget.ImageView
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.keyguard.ui.viewmodel.BackgroundViewModel
+import com.android.systemui.lifecycle.repeatWhenAttached
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.launch
+
+@ExperimentalCoroutinesApi
+object UdfpsBackgroundViewBinder {
+
+    /**
+     * Drives UI for the udfps background view. See [UdfpsAodFingerprintViewBinder] and
+     * [UdfpsFingerprintViewBinder].
+     */
+    @JvmStatic
+    fun bind(
+        view: ImageView,
+        viewModel: BackgroundViewModel,
+    ) {
+        view.alpha = 0f
+        view.repeatWhenAttached {
+            repeatOnLifecycle(Lifecycle.State.STARTED) {
+                launch {
+                    viewModel.transition.collect {
+                        view.alpha = it.alpha
+                        view.scaleX = it.scale
+                        view.scaleY = it.scale
+                        view.imageTintList = ColorStateList.valueOf(it.color)
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsFingerprintViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsFingerprintViewBinder.kt
new file mode 100644
index 0000000..bab04f2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsFingerprintViewBinder.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.keyguard.ui.binder
+
+import android.graphics.PorterDuff
+import android.graphics.PorterDuffColorFilter
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.airbnb.lottie.LottieAnimationView
+import com.airbnb.lottie.LottieProperty
+import com.airbnb.lottie.model.KeyPath
+import com.android.systemui.keyguard.ui.viewmodel.FingerprintViewModel
+import com.android.systemui.lifecycle.repeatWhenAttached
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.launch
+
+@ExperimentalCoroutinesApi
+object UdfpsFingerprintViewBinder {
+    private var udfpsIconColor = 0
+
+    /**
+     * Drives UI for the UDFPS fingerprint view when it's NOT on aod. See
+     * [UdfpsAodFingerprintViewBinder] and [UdfpsBackgroundViewBinder].
+     */
+    @JvmStatic
+    fun bind(
+        view: LottieAnimationView,
+        viewModel: FingerprintViewModel,
+    ) {
+        view.alpha = 0f
+        view.repeatWhenAttached {
+            repeatOnLifecycle(Lifecycle.State.STARTED) {
+                launch {
+                    viewModel.transition.collect {
+                        view.alpha = it.alpha
+                        view.scaleX = it.scale
+                        view.scaleY = it.scale
+                        if (udfpsIconColor != (it.color)) {
+                            udfpsIconColor = it.color
+                            view.invalidate()
+                        }
+                    }
+                }
+
+                launch {
+                    viewModel.burnInOffsets.collect { burnInOffsets ->
+                        view.translationX = burnInOffsets.burnInXOffset.toFloat()
+                        view.translationY = burnInOffsets.burnInYOffset.toFloat()
+                    }
+                }
+
+                launch {
+                    viewModel.dozeAmount.collect { dozeAmount ->
+                        // Lottie progress represents: aod=0 to lockscreen=1
+                        view.progress = 1f - dozeAmount
+                    }
+                }
+
+                launch {
+                    viewModel.padding.collect { padding ->
+                        view.setPadding(padding, padding, padding, padding)
+                    }
+                }
+            }
+        }
+
+        // Add a callback that updates the color to `udfpsIconColor` whenever invalidate is called
+        view.addValueCallback(KeyPath("**"), LottieProperty.COLOR_FILTER) {
+            PorterDuffColorFilter(udfpsIconColor, PorterDuff.Mode.SRC_ATOP)
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardInternalViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardInternalViewBinder.kt
new file mode 100644
index 0000000..b568a9a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardInternalViewBinder.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.biometrics.ui.binder
+
+import android.view.View
+import com.android.systemui.R
+import com.android.systemui.keyguard.ui.binder.UdfpsAodFingerprintViewBinder
+import com.android.systemui.keyguard.ui.binder.UdfpsBackgroundViewBinder
+import com.android.systemui.keyguard.ui.binder.UdfpsFingerprintViewBinder
+import com.android.systemui.keyguard.ui.viewmodel.BackgroundViewModel
+import com.android.systemui.keyguard.ui.viewmodel.FingerprintViewModel
+import com.android.systemui.keyguard.ui.viewmodel.UdfpsAodViewModel
+import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardInternalViewModel
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+@ExperimentalCoroutinesApi
+object UdfpsKeyguardInternalViewBinder {
+
+    @JvmStatic
+    fun bind(
+        view: View,
+        viewModel: UdfpsKeyguardInternalViewModel,
+        aodViewModel: UdfpsAodViewModel,
+        fingerprintViewModel: FingerprintViewModel,
+        backgroundViewModel: BackgroundViewModel,
+    ) {
+        view.accessibilityDelegate = viewModel.accessibilityDelegate
+
+        // bind child views
+        UdfpsAodFingerprintViewBinder.bind(view.findViewById(R.id.udfps_aod_fp), aodViewModel)
+        UdfpsFingerprintViewBinder.bind(
+            view.findViewById(R.id.udfps_lockscreen_fp),
+            fingerprintViewModel
+        )
+        UdfpsBackgroundViewBinder.bind(
+            view.findViewById(R.id.udfps_keyguard_fp_bg),
+            backgroundViewModel
+        )
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardViewBinder.kt
new file mode 100644
index 0000000..667abae
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardViewBinder.kt
@@ -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 com.android.systemui.keyguard.ui.binder
+
+import android.graphics.RectF
+import android.view.View
+import android.widget.FrameLayout
+import androidx.asynclayoutinflater.view.AsyncLayoutInflater
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.R
+import com.android.systemui.biometrics.UdfpsKeyguardView
+import com.android.systemui.biometrics.ui.binder.UdfpsKeyguardInternalViewBinder
+import com.android.systemui.keyguard.ui.viewmodel.BackgroundViewModel
+import com.android.systemui.keyguard.ui.viewmodel.FingerprintViewModel
+import com.android.systemui.keyguard.ui.viewmodel.UdfpsAodViewModel
+import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardInternalViewModel
+import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModel
+import com.android.systemui.lifecycle.repeatWhenAttached
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.launch
+
+@ExperimentalCoroutinesApi
+object UdfpsKeyguardViewBinder {
+    /**
+     * Drives UI for the keyguard UDFPS view. Inflates child views on a background thread. For view
+     * binders for its child views, see [UdfpsFingerprintViewBinder], [UdfpsBackgroundViewBinder] &
+     * [UdfpsAodFingerprintViewBinder].
+     */
+    @JvmStatic
+    fun bind(
+        view: UdfpsKeyguardView,
+        viewModel: UdfpsKeyguardViewModel,
+        udfpsKeyguardInternalViewModel: UdfpsKeyguardInternalViewModel,
+        aodViewModel: UdfpsAodViewModel,
+        fingerprintViewModel: FingerprintViewModel,
+        backgroundViewModel: BackgroundViewModel,
+    ) {
+        view.useExpandedOverlay(viewModel.useExpandedOverlay())
+
+        val layoutInflaterFinishListener =
+            AsyncLayoutInflater.OnInflateFinishedListener { inflatedInternalView, _, parent ->
+                UdfpsKeyguardInternalViewBinder.bind(
+                    inflatedInternalView,
+                    udfpsKeyguardInternalViewModel,
+                    aodViewModel,
+                    fingerprintViewModel,
+                    backgroundViewModel,
+                )
+                if (viewModel.useExpandedOverlay()) {
+                    val lp = inflatedInternalView.layoutParams as FrameLayout.LayoutParams
+                    lp.width = viewModel.sensorBounds.width()
+                    lp.height = viewModel.sensorBounds.height()
+                    val relativeToView =
+                        getBoundsRelativeToView(
+                            inflatedInternalView,
+                            RectF(viewModel.sensorBounds),
+                        )
+                    lp.setMarginsRelative(
+                        relativeToView.left.toInt(),
+                        relativeToView.top.toInt(),
+                        relativeToView.right.toInt(),
+                        relativeToView.bottom.toInt(),
+                    )
+                    parent!!.addView(inflatedInternalView, lp)
+                } else {
+                    parent!!.addView(inflatedInternalView)
+                }
+            }
+        val inflater = AsyncLayoutInflater(view.context)
+        inflater.inflate(R.layout.udfps_keyguard_view_internal, view, layoutInflaterFinishListener)
+
+        view.repeatWhenAttached {
+            repeatOnLifecycle(Lifecycle.State.CREATED) {
+                launch {
+                    combine(aodViewModel.isVisible, fingerprintViewModel.visible) {
+                            isAodVisible,
+                            isFingerprintVisible ->
+                            isAodVisible || isFingerprintVisible
+                        }
+                        .collect { view.setVisible(it) }
+                }
+            }
+        }
+    }
+
+    /**
+     * Converts coordinates of RectF relative to the screen to coordinates relative to this view.
+     *
+     * @param bounds RectF based off screen coordinates in current orientation
+     */
+    private fun getBoundsRelativeToView(view: View, bounds: RectF): RectF {
+        val pos: IntArray = view.locationOnScreen
+        return RectF(
+            bounds.left - pos[0],
+            bounds.top - pos[1],
+            bounds.right - pos[0],
+            bounds.bottom - pos[1]
+        )
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/KeyguardIndicationArea.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/KeyguardIndicationArea.kt
new file mode 100644
index 0000000..890d565
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/KeyguardIndicationArea.kt
@@ -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.systemui.keyguard.ui.view
+
+import android.content.Context
+import android.text.TextUtils
+import android.util.AttributeSet
+import android.view.Gravity
+import android.view.View
+import android.view.ViewGroup.LayoutParams.MATCH_PARENT
+import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
+import android.widget.LinearLayout
+import com.android.systemui.R
+import com.android.systemui.statusbar.phone.KeyguardIndicationTextView
+
+class KeyguardIndicationArea(
+    context: Context,
+    private val attrs: AttributeSet?,
+) :
+    LinearLayout(
+        context,
+        attrs,
+    ) {
+
+    init {
+        setId(R.id.keyguard_indication_area)
+        orientation = LinearLayout.VERTICAL
+
+        addView(indicationTopRow(), LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT))
+        addView(
+            indicationBottomRow(),
+            LinearLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT).apply {
+                gravity = Gravity.CENTER_HORIZONTAL
+            }
+        )
+    }
+
+    private fun indicationTopRow(): KeyguardIndicationTextView {
+        return KeyguardIndicationTextView(context, attrs).apply {
+            id = R.id.keyguard_indication_text
+            gravity = Gravity.CENTER
+            accessibilityLiveRegion = View.ACCESSIBILITY_LIVE_REGION_POLITE
+            setTextAppearance(R.style.TextAppearance_Keyguard_BottomArea)
+
+            val padding = R.dimen.keyguard_indication_text_padding.dp()
+            setPaddingRelative(padding, 0, padding, 0)
+        }
+    }
+
+    private fun indicationBottomRow(): KeyguardIndicationTextView {
+        return KeyguardIndicationTextView(context, attrs).apply {
+            id = R.id.keyguard_indication_text_bottom
+            gravity = Gravity.CENTER
+            accessibilityLiveRegion = View.ACCESSIBILITY_LIVE_REGION_POLITE
+
+            setTextAppearance(R.style.TextAppearance_Keyguard_BottomArea)
+            setEllipsize(TextUtils.TruncateAt.END)
+            setAlpha(0.8f)
+            setMinHeight(R.dimen.keyguard_indication_text_min_height.dp())
+            setMaxLines(2)
+            setVisibility(View.GONE)
+
+            val padding = R.dimen.keyguard_indication_text_padding.dp()
+            setPaddingRelative(padding, 0, padding, 0)
+        }
+    }
+
+    private fun Int.dp(): Int {
+        return context.resources.getDimensionPixelSize(this)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/KeyguardRootView.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/KeyguardRootView.kt
new file mode 100644
index 0000000..0077f2d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/KeyguardRootView.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.keyguard.ui.view
+
+import android.content.Context
+import android.util.AttributeSet
+import androidx.constraintlayout.widget.ConstraintLayout
+import com.android.keyguard.LockIconView
+import com.android.systemui.R
+
+/** Provides a container for all keyguard ui content. */
+class KeyguardRootView(
+    context: Context,
+    private val attrs: AttributeSet?,
+) :
+    ConstraintLayout(
+        context,
+        attrs,
+    ) {
+
+    init {
+        addIndicationTextArea()
+        addLockIconView()
+    }
+
+    private fun addIndicationTextArea() {
+        val view = KeyguardIndicationArea(context, attrs)
+        addView(view)
+    }
+
+    private fun addLockIconView() {
+        val view = LockIconView(context, attrs).apply { id = R.id.lock_icon_view }
+        addView(view)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/DefaultLockscreenLayout.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/DefaultLockscreenLayout.kt
new file mode 100644
index 0000000..baaeb60
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/DefaultLockscreenLayout.kt
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.ui.view.layout
+
+import android.content.Context
+import android.graphics.Point
+import android.graphics.Rect
+import android.util.DisplayMetrics
+import android.view.View
+import android.view.ViewGroup.LayoutParams.MATCH_PARENT
+import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
+import android.view.WindowManager
+import androidx.annotation.VisibleForTesting
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.constraintlayout.widget.ConstraintSet
+import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
+import androidx.constraintlayout.widget.ConstraintSet.END
+import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
+import androidx.constraintlayout.widget.ConstraintSet.START
+import androidx.constraintlayout.widget.ConstraintSet.TOP
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.R
+import com.android.systemui.biometrics.AuthController
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.ui.view.KeyguardRootView
+import javax.inject.Inject
+
+/**
+ * Positions elements of the lockscreen to the default position.
+ *
+ * This will be the most common use case for phones in portrait mode.
+ */
+@SysUISingleton
+class DefaultLockscreenLayout
+@Inject
+constructor(
+    private val authController: AuthController,
+    private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+    private val windowManager: WindowManager,
+    private val context: Context,
+) : LockscreenLayout {
+    override val id: String = DEFAULT
+
+    override fun layoutIndicationArea(rootView: KeyguardRootView) {
+        val indicationArea = rootView.findViewById<View>(R.id.keyguard_indication_area) ?: return
+
+        rootView.getConstraintSet().apply {
+            constrainWidth(indicationArea.id, MATCH_PARENT)
+            constrainHeight(indicationArea.id, WRAP_CONTENT)
+            connect(
+                indicationArea.id,
+                BOTTOM,
+                PARENT_ID,
+                BOTTOM,
+                R.dimen.keyguard_indication_margin_bottom.dp()
+            )
+            connect(indicationArea.id, START, PARENT_ID, START)
+            connect(indicationArea.id, END, PARENT_ID, END)
+            applyTo(rootView)
+        }
+    }
+
+    override fun layoutLockIcon(rootView: KeyguardRootView) {
+        val isUdfpsSupported = keyguardUpdateMonitor.isUdfpsSupported
+        val scaleFactor: Float = authController.scaleFactor
+        val mBottomPaddingPx = R.dimen.lock_icon_margin_bottom.dp()
+        val mDefaultPaddingPx = R.dimen.lock_icon_padding.dp()
+        val scaledPadding: Int = (mDefaultPaddingPx * scaleFactor).toInt()
+        val bounds = windowManager.currentWindowMetrics.bounds
+        val widthPixels = bounds.right.toFloat()
+        val heightPixels = bounds.bottom.toFloat()
+        val defaultDensity =
+            DisplayMetrics.DENSITY_DEVICE_STABLE.toFloat() /
+                DisplayMetrics.DENSITY_DEFAULT.toFloat()
+        val lockIconRadiusPx = (defaultDensity * 36).toInt()
+
+        if (isUdfpsSupported) {
+            authController.udfpsLocation?.let { udfpsLocation ->
+                centerLockIcon(udfpsLocation, authController.udfpsRadius, scaledPadding, rootView)
+            }
+        } else {
+            centerLockIcon(
+                Point(
+                    (widthPixels / 2).toInt(),
+                    (heightPixels - ((mBottomPaddingPx + lockIconRadiusPx) * scaleFactor)).toInt()
+                ),
+                lockIconRadiusPx * scaleFactor,
+                scaledPadding,
+                rootView
+            )
+        }
+    }
+
+    @VisibleForTesting
+    internal fun centerLockIcon(
+        center: Point,
+        radius: Float,
+        drawablePadding: Int,
+        rootView: KeyguardRootView,
+    ) {
+        val lockIconView = rootView.findViewById<View>(R.id.lock_icon_view) ?: return
+        val lockIcon = lockIconView.findViewById<View>(R.id.lock_icon) ?: return
+        lockIcon.setPadding(drawablePadding, drawablePadding, drawablePadding, drawablePadding)
+
+        val sensorRect =
+            Rect().apply {
+                set(
+                    center.x - radius.toInt(),
+                    center.y - radius.toInt(),
+                    center.x + radius.toInt(),
+                    center.y + radius.toInt(),
+                )
+            }
+
+        rootView.getConstraintSet().apply {
+            constrainWidth(lockIconView.id, sensorRect.right - sensorRect.left)
+            constrainHeight(lockIconView.id, sensorRect.bottom - sensorRect.top)
+            connect(lockIconView.id, TOP, PARENT_ID, TOP, sensorRect.top)
+            connect(lockIconView.id, START, PARENT_ID, START, sensorRect.left)
+            applyTo(rootView)
+        }
+    }
+
+    private fun Int.dp(): Int {
+        return context.resources.getDimensionPixelSize(this)
+    }
+
+    private fun ConstraintLayout.getConstraintSet(): ConstraintSet {
+        val cs = ConstraintSet()
+        cs.clone(this)
+        return cs
+    }
+
+    companion object {
+        const val DEFAULT = "default"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/KeyguardLayoutManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/KeyguardLayoutManager.kt
new file mode 100644
index 0000000..9bc6302
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/KeyguardLayoutManager.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.ui.view.layout
+
+import android.content.res.Configuration
+import androidx.constraintlayout.widget.ConstraintSet
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.ui.view.KeyguardRootView
+import com.android.systemui.keyguard.ui.view.layout.DefaultLockscreenLayout.Companion.DEFAULT
+import com.android.systemui.statusbar.policy.ConfigurationController
+import javax.inject.Inject
+
+/**
+ * Manages layout changes for the lockscreen.
+ *
+ * To add a layout, add an entry to the map with a unique id and call #transitionToLayout(string).
+ */
+@SysUISingleton
+class KeyguardLayoutManager
+@Inject
+constructor(
+    configurationController: ConfigurationController,
+    layouts: Set<@JvmSuppressWildcards LockscreenLayout>,
+    private val keyguardRootView: KeyguardRootView,
+) {
+    internal val layoutIdMap: Map<String, LockscreenLayout> = layouts.associateBy { it.id }
+    private var layout: LockscreenLayout? = layoutIdMap[DEFAULT]
+
+    init {
+        configurationController.addCallback(
+            object : ConfigurationController.ConfigurationListener {
+                override fun onConfigChanged(newConfig: Configuration?) {
+                    layoutViews()
+                }
+            }
+        )
+    }
+
+    /**
+     * Transitions to a layout.
+     *
+     * @param layoutId
+     * @return whether the transition has succeeded.
+     */
+    fun transitionToLayout(layoutId: String): Boolean {
+        layout = layoutIdMap[layoutId] ?: return false
+        layoutViews()
+        return true
+    }
+
+    fun layoutViews() {
+        layout?.layoutViews(keyguardRootView)
+    }
+
+    companion object {
+        const val TAG = "KeyguardLayoutManager"
+    }
+}
+
+interface LockscreenLayout {
+    val id: String
+
+    fun layoutViews(rootView: KeyguardRootView) {
+        // Clear constraints.
+        ConstraintSet()
+            .apply {
+                clone(rootView)
+                knownIds.forEach { getConstraint(it).layout.copyFrom(ConstraintSet.Layout()) }
+            }
+            .applyTo(rootView)
+        layoutIndicationArea(rootView)
+        layoutLockIcon(rootView)
+    }
+    fun layoutIndicationArea(rootView: KeyguardRootView)
+    fun layoutLockIcon(rootView: KeyguardRootView)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/KeyguardLayoutManagerCommandListener.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/KeyguardLayoutManagerCommandListener.kt
new file mode 100644
index 0000000..b351ea8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/KeyguardLayoutManagerCommandListener.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.keyguard.ui.view.layout
+
+import com.android.systemui.statusbar.commandline.Command
+import com.android.systemui.statusbar.commandline.CommandRegistry
+import java.io.PrintWriter
+import javax.inject.Inject
+
+/** Uses $ adb shell cmd statusbar layout <LayoutId> */
+class KeyguardLayoutManagerCommandListener
+@Inject
+constructor(
+    private val commandRegistry: CommandRegistry,
+    private val keyguardLayoutManager: KeyguardLayoutManager
+) {
+    private val layoutCommand = KeyguardLayoutManagerCommand()
+
+    fun start() {
+        commandRegistry.registerCommand(COMMAND) { layoutCommand }
+    }
+
+    internal inner class KeyguardLayoutManagerCommand : Command {
+        override fun execute(pw: PrintWriter, args: List<String>) {
+            val arg = args.getOrNull(0)
+            if (arg == null || arg.lowercase() == "help") {
+                help(pw)
+                return
+            }
+
+            if (keyguardLayoutManager.transitionToLayout(arg)) {
+                pw.println("Transition succeeded!")
+            } else {
+                pw.println("Invalid argument! To see available layout ids, run:")
+                pw.println("$ adb shell cmd statusbar layout help")
+            }
+        }
+
+        override fun help(pw: PrintWriter) {
+            pw.println("Usage: $ adb shell cmd statusbar layout <layoutId>")
+            pw.println("Existing Layout Ids: ")
+            keyguardLayoutManager.layoutIdMap.forEach { entry -> pw.println("${entry.key}") }
+        }
+    }
+
+    companion object {
+        internal const val COMMAND = "layout"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/LockscreenLayoutModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/LockscreenLayoutModule.kt
new file mode 100644
index 0000000..00f93e3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/LockscreenLayoutModule.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.ui.view.layout
+
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.IntoSet
+
+@Module
+abstract class LockscreenLayoutModule {
+    @Binds
+    @IntoSet
+    abstract fun bindDefaultLayout(
+        defaultLockscreenLayout: DefaultLockscreenLayout
+    ): LockscreenLayout
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt
index 62a1a9e..3e6f8e6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt
@@ -57,7 +57,7 @@
      * in the wallpaper picker application. This should _always_ be `false` for the real lock screen
      * experience.
      */
-    private val previewMode = MutableStateFlow(PreviewMode())
+    val previewMode = MutableStateFlow(PreviewMode())
 
     /**
      * ID of the slot that's currently selected in the preview that renders exclusively in the
@@ -101,12 +101,6 @@
                 bottomAreaInteractor.alpha.distinctUntilChanged()
             }
         }
-    /** An observable for whether the indication area should be padded. */
-    val isIndicationAreaPadded: Flow<Boolean> =
-        combine(startButton, endButton) { startButtonModel, endButtonModel ->
-                startButtonModel.isVisible || endButtonModel.isVisible
-            }
-            .distinctUntilChanged()
     /** An observable for the x-offset by which the indication area should be translated. */
     val indicationAreaTranslationX: Flow<Float> =
         bottomAreaInteractor.clockPosition.map { it.x.toFloat() }.distinctUntilChanged()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt
deleted file mode 100644
index 9602888..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt
+++ /dev/null
@@ -1,112 +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.systemui.keyguard.ui.viewmodel
-
-import android.view.View
-import com.android.systemui.keyguard.data.BouncerView
-import com.android.systemui.keyguard.data.BouncerViewDelegate
-import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor
-import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.filterNotNull
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.merge
-
-/** Models UI state for the lock screen bouncer; handles user input. */
-class KeyguardBouncerViewModel
-@Inject
-constructor(
-    private val view: BouncerView,
-    private val interactor: PrimaryBouncerInteractor,
-) {
-    /** Observe on bouncer expansion amount. */
-    val bouncerExpansionAmount: Flow<Float> = interactor.panelExpansionAmount
-
-    /** Can the user interact with the view? */
-    val isInteractable: Flow<Boolean> = interactor.isInteractable
-
-    /** Observe whether bouncer is showing or not. */
-    val isShowing: Flow<Boolean> = interactor.isShowing
-
-    /** Observe whether bouncer is starting to hide. */
-    val startingToHide: Flow<Unit> = interactor.startingToHide
-
-    /** Observe whether we want to start the disappear animation. */
-    val startDisappearAnimation: Flow<Runnable> = interactor.startingDisappearAnimation
-
-    /** Observe whether we want to update keyguard position. */
-    val keyguardPosition: Flow<Float> = interactor.keyguardPosition
-
-    /** Observe whether we want to update resources. */
-    val updateResources: Flow<Boolean> = interactor.resourceUpdateRequests
-
-    /** Observe whether we want to set a keyguard message when the bouncer shows. */
-    val bouncerShowMessage: Flow<BouncerShowMessageModel> = interactor.showMessage
-
-    /** Observe whether keyguard is authenticated already. */
-    val keyguardAuthenticated: Flow<Boolean> = interactor.keyguardAuthenticated
-
-    /** Observe whether the side fps is showing. */
-    val sideFpsShowing: Flow<Boolean> = interactor.sideFpsShowing
-
-    /** Observe whether we should update fps is showing. */
-    val shouldUpdateSideFps: Flow<Unit> =
-        merge(
-            interactor.isShowing.map {},
-            interactor.startingToHide,
-            interactor.startingDisappearAnimation.filterNotNull().map {}
-        )
-
-    /** Observe whether we want to update resources. */
-    fun notifyUpdateResources() {
-        interactor.notifyUpdatedResources()
-    }
-
-    /** Notify that keyguard authenticated was handled */
-    fun notifyKeyguardAuthenticated() {
-        interactor.notifyKeyguardAuthenticatedHandled()
-    }
-
-    /** Notifies that the message was shown. */
-    fun onMessageShown() {
-        interactor.onMessageShown()
-    }
-
-    fun updateSideFpsVisibility() {
-        interactor.updateSideFpsVisibility()
-    }
-
-    /** Observe whether back button is enabled. */
-    fun observeOnIsBackButtonEnabled(systemUiVisibility: () -> Int): Flow<Int> {
-        return interactor.isBackButtonEnabled.map { enabled ->
-            var vis: Int = systemUiVisibility()
-            vis =
-                if (enabled) {
-                    vis and View.STATUS_BAR_DISABLE_BACK.inv()
-                } else {
-                    vis or View.STATUS_BAR_DISABLE_BACK
-                }
-            vis
-        }
-    }
-
-    /** Set an abstraction that will hold reference to the ui delegate for the bouncer view. */
-    fun setBouncerViewDelegate(delegate: BouncerViewDelegate?) {
-        view.delegate = delegate
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModel.kt
new file mode 100644
index 0000000..389cf76
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModel.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.keyguard.ui.viewmodel
+
+import com.android.systemui.doze.util.BurnInHelperWrapper
+import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
+
+/** View-model for the keyguard indication area view */
+@OptIn(ExperimentalCoroutinesApi::class)
+class KeyguardIndicationAreaViewModel
+@Inject
+constructor(
+    private val keyguardInteractor: KeyguardInteractor,
+    private val bottomAreaInteractor: KeyguardBottomAreaInteractor,
+    private val keyguardBottomAreaViewModel: KeyguardBottomAreaViewModel,
+    private val burnInHelperWrapper: BurnInHelperWrapper,
+) {
+
+    /** Notifies when a new configuration is set */
+    val configurationChange: Flow<Unit> = keyguardInteractor.configurationChange
+
+    /** An observable for the alpha level for the entire bottom area. */
+    val alpha: Flow<Float> = keyguardBottomAreaViewModel.alpha
+
+    /** An observable for whether the indication area should be padded. */
+    val isIndicationAreaPadded: Flow<Boolean> =
+        combine(keyguardBottomAreaViewModel.startButton, keyguardBottomAreaViewModel.endButton) {
+                startButtonModel,
+                endButtonModel ->
+                startButtonModel.isVisible || endButtonModel.isVisible
+            }
+            .distinctUntilChanged()
+    /** An observable for the x-offset by which the indication area should be translated. */
+    val indicationAreaTranslationX: Flow<Float> =
+        bottomAreaInteractor.clockPosition.map { it.x.toFloat() }.distinctUntilChanged()
+
+    /** Returns an observable for the y-offset by which the indication area should be translated. */
+    fun indicationAreaTranslationY(defaultBurnInOffset: Int): Flow<Float> {
+        return keyguardInteractor.dozeAmount
+            .map { dozeAmount ->
+                dozeAmount *
+                    (burnInHelperWrapper.burnInOffset(
+                        /* amplitude = */ defaultBurnInOffset * 2,
+                        /* xAxis= */ false,
+                    ) - defaultBurnInOffset)
+            }
+            .distinctUntilChanged()
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
index 68810f9..44e1fd1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
@@ -17,10 +17,10 @@
 package com.android.systemui.keyguard.ui.viewmodel
 
 import com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor.Companion.TO_GONE_DURATION
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor
 import com.android.systemui.keyguard.shared.model.ScrimAlpha
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
 import com.android.systemui.statusbar.SysuiStatusBarStateController
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModel.kt
new file mode 100644
index 0000000..667c2f1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModel.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.keyguard.ui.viewmodel
+
+import android.content.Context
+import com.android.systemui.R
+import com.android.systemui.keyguard.domain.interactor.BurnInOffsets
+import com.android.systemui.keyguard.domain.interactor.UdfpsKeyguardInteractor
+import javax.inject.Inject
+import kotlin.math.roundToInt
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+
+/** View-model for UDFPS AOD view. */
+@ExperimentalCoroutinesApi
+class UdfpsAodViewModel
+@Inject
+constructor(
+    val interactor: UdfpsKeyguardInteractor,
+    val context: Context,
+) {
+    val alpha: Flow<Float> = interactor.dozeAmount
+    val burnInOffsets: Flow<BurnInOffsets> = interactor.burnInOffsets
+    val isVisible: Flow<Boolean> = alpha.map { it != 0f }
+
+    // Padding between the fingerprint icon and its bounding box in pixels.
+    val padding: Flow<Int> =
+        interactor.scaleForResolution.map { scale ->
+            (context.resources.getDimensionPixelSize(R.dimen.lock_icon_padding) * scale)
+                .roundToInt()
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardInternalViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardInternalViewModel.kt
new file mode 100644
index 0000000..d894a11
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardInternalViewModel.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.keyguard.ui.viewmodel
+
+import com.android.systemui.biometrics.UdfpsKeyguardAccessibilityDelegate
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+@ExperimentalCoroutinesApi
+class UdfpsKeyguardInternalViewModel
+@Inject
+constructor(val accessibilityDelegate: UdfpsKeyguardAccessibilityDelegate)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardViewModel.kt
new file mode 100644
index 0000000..929f27f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardViewModel.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 android.graphics.Rect
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+@ExperimentalCoroutinesApi
+class UdfpsKeyguardViewModel
+@Inject
+constructor(
+    private val featureFlags: FeatureFlags,
+) {
+    var sensorBounds: Rect = Rect()
+
+    fun useExpandedOverlay(): Boolean {
+        return featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardViewModels.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardViewModels.kt
new file mode 100644
index 0000000..098b481
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardViewModels.kt
@@ -0,0 +1,36 @@
+package com.android.systemui.keyguard.ui.viewmodel
+
+import android.graphics.Rect
+import com.android.systemui.biometrics.UdfpsKeyguardView
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.ui.binder.UdfpsKeyguardViewBinder
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+@ExperimentalCoroutinesApi
+@SysUISingleton
+class UdfpsKeyguardViewModels
+@Inject
+constructor(
+    private val viewModel: UdfpsKeyguardViewModel,
+    private val internalViewModel: UdfpsKeyguardInternalViewModel,
+    private val aodViewModel: UdfpsAodViewModel,
+    private val lockscreenFingerprintViewModel: FingerprintViewModel,
+    private val lockscreenBackgroundViewModel: BackgroundViewModel,
+) {
+
+    fun setSensorBounds(sensorBounds: Rect) {
+        viewModel.sensorBounds = sensorBounds
+    }
+
+    fun bindViews(view: UdfpsKeyguardView) {
+        UdfpsKeyguardViewBinder.bind(
+            view,
+            viewModel,
+            internalViewModel,
+            aodViewModel,
+            lockscreenFingerprintViewModel,
+            lockscreenBackgroundViewModel
+        )
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModel.kt
new file mode 100644
index 0000000..fd4b666
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModel.kt
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import android.content.Context
+import androidx.annotation.ColorInt
+import com.android.settingslib.Utils.getColorAttrDefaultColor
+import com.android.systemui.R
+import com.android.systemui.keyguard.domain.interactor.BurnInOffsets
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.UdfpsKeyguardInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import javax.inject.Inject
+import kotlin.math.roundToInt
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
+
+/** View-model for UDFPS lockscreen views. */
+@ExperimentalCoroutinesApi
+open class UdfpsLockscreenViewModel(
+    context: Context,
+    lockscreenColorResId: Int,
+    alternateBouncerColorResId: Int,
+    transitionInteractor: KeyguardTransitionInteractor,
+) {
+    private val toLockscreen: Flow<TransitionViewModel> =
+        transitionInteractor.anyStateToLockscreenTransition.map {
+            TransitionViewModel(
+                alpha =
+                    if (it.from == KeyguardState.AOD) {
+                        it.value // animate
+                    } else {
+                        1f
+                    },
+                scale = 1f,
+                color = getColorAttrDefaultColor(context, lockscreenColorResId),
+            )
+        }
+
+    private val toAlternateBouncer: Flow<TransitionViewModel> =
+        transitionInteractor.anyStateToAlternateBouncerTransition.map {
+            TransitionViewModel(
+                alpha = 1f,
+                scale =
+                    if (visibleInKeyguardState(it.from)) {
+                        1f
+                    } else {
+                        it.value
+                    },
+                color = getColorAttrDefaultColor(context, alternateBouncerColorResId),
+            )
+        }
+
+    private val fadeOut: Flow<TransitionViewModel> =
+        merge(
+                transitionInteractor.anyStateToGoneTransition,
+                transitionInteractor.anyStateToAodTransition,
+                transitionInteractor.anyStateToOccludedTransition,
+                transitionInteractor.anyStateToPrimaryBouncerTransition,
+                transitionInteractor.anyStateToDreamingTransition,
+            )
+            .map {
+                TransitionViewModel(
+                    alpha =
+                        if (visibleInKeyguardState(it.from)) {
+                            1f - it.value
+                        } else {
+                            0f
+                        },
+                    scale = 1f,
+                    color =
+                        if (it.from == KeyguardState.ALTERNATE_BOUNCER) {
+                            getColorAttrDefaultColor(context, alternateBouncerColorResId)
+                        } else {
+                            getColorAttrDefaultColor(context, lockscreenColorResId)
+                        },
+                )
+            }
+
+    private fun visibleInKeyguardState(state: KeyguardState): Boolean {
+        return when (state) {
+            KeyguardState.OFF,
+            KeyguardState.DOZING,
+            KeyguardState.DREAMING,
+            KeyguardState.AOD,
+            KeyguardState.PRIMARY_BOUNCER,
+            KeyguardState.GONE,
+            KeyguardState.OCCLUDED -> false
+            KeyguardState.LOCKSCREEN,
+            KeyguardState.ALTERNATE_BOUNCER -> true
+        }
+    }
+
+    val transition: Flow<TransitionViewModel> =
+        merge(
+            toAlternateBouncer,
+            toLockscreen,
+            fadeOut,
+        )
+    val visible: Flow<Boolean> = transition.map { it.alpha != 0f }
+}
+
+@ExperimentalCoroutinesApi
+class FingerprintViewModel
+@Inject
+constructor(
+    val context: Context,
+    transitionInteractor: KeyguardTransitionInteractor,
+    interactor: UdfpsKeyguardInteractor,
+) :
+    UdfpsLockscreenViewModel(
+        context,
+        android.R.attr.textColorPrimary,
+        com.android.internal.R.attr.materialColorOnPrimaryFixed,
+        transitionInteractor,
+    ) {
+    val dozeAmount: Flow<Float> = interactor.dozeAmount
+    val burnInOffsets: Flow<BurnInOffsets> = interactor.burnInOffsets
+
+    // Padding between the fingerprint icon and its bounding box in pixels.
+    val padding: Flow<Int> =
+        interactor.scaleForResolution.map { scale ->
+            (context.resources.getDimensionPixelSize(R.dimen.lock_icon_padding) * scale)
+                .roundToInt()
+        }
+}
+
+@ExperimentalCoroutinesApi
+class BackgroundViewModel
+@Inject
+constructor(
+    val context: Context,
+    transitionInteractor: KeyguardTransitionInteractor,
+) :
+    UdfpsLockscreenViewModel(
+        context,
+        com.android.internal.R.attr.colorSurface,
+        com.android.internal.R.attr.materialColorPrimaryFixed,
+        transitionInteractor,
+    )
+
+data class TransitionViewModel(
+    val alpha: Float,
+    val scale: Float,
+    @ColorInt val color: Int,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/util/IndicationHelper.kt b/packages/SystemUI/src/com/android/systemui/keyguard/util/IndicationHelper.kt
new file mode 100644
index 0000000..7129001
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/util/IndicationHelper.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.keyguard.util
+
+import android.hardware.biometrics.BiometricFaceConstants
+import android.hardware.biometrics.BiometricFingerprintConstants
+import android.hardware.biometrics.BiometricSourceType
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+
+@SysUISingleton
+class IndicationHelper
+@Inject
+constructor(
+    val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+) {
+    fun shouldSuppressErrorMsg(biometricSource: BiometricSourceType, msgId: Int): Boolean {
+        return when (biometricSource) {
+            BiometricSourceType.FINGERPRINT ->
+                (isPrimaryAuthRequired() && !isFingerprintLockoutErrorMsg(msgId)) ||
+                    msgId == BiometricFingerprintConstants.FINGERPRINT_ERROR_CANCELED ||
+                    msgId == BiometricFingerprintConstants.FINGERPRINT_ERROR_USER_CANCELED ||
+                    msgId == BiometricFingerprintConstants.BIOMETRIC_ERROR_POWER_PRESSED
+            BiometricSourceType.FACE ->
+                (isPrimaryAuthRequired() && !isFaceLockoutErrorMsg(msgId)) ||
+                    msgId == BiometricFaceConstants.FACE_ERROR_CANCELED ||
+                    msgId == BiometricFaceConstants.FACE_ERROR_UNABLE_TO_PROCESS
+            else -> false
+        }
+    }
+
+    private fun isFingerprintLockoutErrorMsg(msgId: Int): Boolean {
+        return msgId == BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT ||
+            msgId == BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT_PERMANENT
+    }
+
+    fun isFaceLockoutErrorMsg(msgId: Int): Boolean {
+        return msgId == BiometricFaceConstants.FACE_ERROR_LOCKOUT ||
+            msgId == BiometricFaceConstants.FACE_ERROR_LOCKOUT_PERMANENT
+    }
+
+    private fun isPrimaryAuthRequired(): Boolean {
+        // Only checking if unlocking with Biometric is allowed (no matter strong or non-strong
+        // as long as primary auth, i.e. PIN/pattern/password, is required), so it's ok to
+        // pass true for isStrongBiometric to isUnlockingWithBiometricAllowed() to bypass the
+        // check of whether non-strong biometric is allowed since strong biometrics can still be
+        // used.
+        return !keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(true /* isStrongBiometric */)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt b/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt
index 34a6740..e064839 100644
--- a/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt
+++ b/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt
@@ -167,9 +167,10 @@
         registry.currentState = Lifecycle.State.DESTROYED
     }
 
-    override fun getLifecycle(): Lifecycle {
-        return registry
-    }
+    override val lifecycle: Lifecycle
+        get() {
+            return registry
+        }
 
     private fun updateState() {
         registry.currentState =
diff --git a/packages/SystemUI/src/com/android/systemui/log/BouncerLogger.kt b/packages/SystemUI/src/com/android/systemui/log/BouncerLogger.kt
index 3be4499..d4b799f 100644
--- a/packages/SystemUI/src/com/android/systemui/log/BouncerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/BouncerLogger.kt
@@ -16,8 +16,9 @@
 
 package com.android.systemui.log
 
+import com.android.systemui.bouncer.shared.model.BouncerMessageModel
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.bouncer.shared.model.BouncerMessageModel
+import com.android.systemui.log.core.LogLevel
 import com.android.systemui.log.dagger.BouncerLog
 import javax.inject.Inject
 
diff --git a/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt b/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt
index fefa1b2..373f705 100644
--- a/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt
@@ -4,9 +4,9 @@
 import android.hardware.face.FaceSensorPropertiesInternal
 import com.android.keyguard.FaceAuthUiEvent
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.shared.model.ErrorAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.ErrorFaceAuthenticationStatus
 import com.android.systemui.keyguard.shared.model.TransitionStep
-import com.android.systemui.log.LogLevel.DEBUG
+import com.android.systemui.log.core.LogLevel.DEBUG
 import com.android.systemui.log.dagger.FaceAuthLog
 import javax.inject.Inject
 
@@ -240,7 +240,7 @@
         )
     }
 
-    fun hardwareError(errorStatus: ErrorAuthenticationStatus) {
+    fun hardwareError(errorStatus: ErrorFaceAuthenticationStatus) {
         logBuffer.log(
             TAG,
             DEBUG,
diff --git a/packages/SystemUI/src/com/android/systemui/log/ScreenDecorationsLogger.kt b/packages/SystemUI/src/com/android/systemui/log/ScreenDecorationsLogger.kt
index f727784..702a23e 100644
--- a/packages/SystemUI/src/com/android/systemui/log/ScreenDecorationsLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/ScreenDecorationsLogger.kt
@@ -21,9 +21,9 @@
 import android.graphics.RectF
 import androidx.core.graphics.toRectF
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel.DEBUG
-import com.android.systemui.log.LogLevel.ERROR
+import com.android.systemui.log.core.LogLevel.DEBUG
+import com.android.systemui.log.core.LogLevel.ERROR
+import com.android.systemui.log.core.LogLevel.INFO
 import com.android.systemui.log.dagger.ScreenDecorationsLog
 import com.google.errorprone.annotations.CompileTimeConstant
 import javax.inject.Inject
@@ -164,4 +164,49 @@
     fun cameraProtectionEvent(@CompileTimeConstant cameraProtectionEvent: String) {
         logBuffer.log(TAG, DEBUG, cameraProtectionEvent)
     }
+
+    fun logRotationChangeDeferred(currentRot: Int, newRot: Int) {
+        logBuffer.log(
+            TAG,
+            INFO,
+            {
+                int1 = currentRot
+                int2 = newRot
+            },
+            { "Rotation changed, deferring $int2, staying at $int2" },
+        )
+    }
+
+    fun logRotationChanged(oldRot: Int, newRot: Int) {
+        logBuffer.log(
+            TAG,
+            INFO,
+            {
+                int1 = oldRot
+                int2 = newRot
+            },
+            { "Rotation changed from $int1 to $int2" }
+        )
+    }
+
+    fun logDisplaySizeChanged(currentSize: Point, newSize: Point) {
+        logBuffer.log(
+            TAG,
+            INFO,
+            {
+                str1 = currentSize.flattenToString()
+                str2 = newSize.flattenToString()
+            },
+            { "Resolution changed, deferring size change to $str2, staying at $str1" },
+        )
+    }
+
+    fun logUserSwitched(newUser: Int) {
+        logBuffer.log(
+            TAG,
+            DEBUG,
+            { int1 = newUser },
+            { "UserSwitched newUserId=$int1. Updating color inversion setting" },
+        )
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/DisableFlagsRepositoryLog.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/DisableFlagsRepositoryLog.kt
new file mode 100644
index 0000000..e141291
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/DisableFlagsRepositoryLog.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.log.dagger
+
+import com.android.systemui.log.LogBuffer
+import javax.inject.Qualifier
+
+/** A [LogBuffer] for changes to [DisableFlagsRepository]. */
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class DisableFlagsRepositoryLog
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/DisplayMetricsRepoLog.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/DisplayMetricsRepoLog.kt
new file mode 100644
index 0000000..fa9ec88
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/DisplayMetricsRepoLog.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.log.dagger
+
+import javax.inject.Qualifier
+
+/** A [com.android.systemui.log.LogBuffer] for display metrics related logging. */
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class DisplayMetricsRepoLog
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 0261ee5..b5759e3 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -224,6 +224,15 @@
                 false /* systrace */);
     }
 
+    /** Provides a logging buffer for the disable flags repository. */
+    @Provides
+    @SysUISingleton
+    @DisableFlagsRepositoryLog
+    public static LogBuffer provideDisableFlagsRepositoryLogBuffer(LogBufferFactory factory) {
+        return factory.create("DisableFlagsRepository", 40 /* maxSize */,
+                false /* systrace */);
+    }
+
     /** Provides a logging buffer for logs related to swipe up gestures. */
     @Provides
     @SysUISingleton
@@ -479,4 +488,12 @@
     public static LogBuffer provideDreamLogBuffer(LogBufferFactory factory) {
         return factory.create("DreamLog", 250);
     }
+
+    /** Provides a {@link LogBuffer} for display metrics related logs. */
+    @Provides
+    @SysUISingleton
+    @DisplayMetricsRepoLog
+    public static LogBuffer provideDisplayMetricsRepoLogBuffer(LogBufferFactory factory) {
+        return factory.create("DisplayMetricsRepo", 50);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt
index 8d622ae..67a985e 100644
--- a/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt
@@ -21,8 +21,8 @@
 import com.android.systemui.Dumpable
 import com.android.systemui.common.buffer.RingBuffer
 import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.log.LogLevel
 import com.android.systemui.log.LogcatEchoTracker
+import com.android.systemui.log.core.LogLevel
 import com.android.systemui.plugins.log.TableLogBufferBase
 import com.android.systemui.util.time.SystemClock
 import java.io.PrintWriter
diff --git a/packages/SystemUI/src/com/android/systemui/log/table/TableLogBufferFactory.kt b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBufferFactory.kt
index 19e1124..1e2f71f 100644
--- a/packages/SystemUI/src/com/android/systemui/log/table/TableLogBufferFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBufferFactory.kt
@@ -61,7 +61,7 @@
                 bgDispatcher,
                 coroutineScope,
             )
-        dumpManager.registerNormalDumpable(name, tableBuffer)
+        dumpManager.registerTableLogBuffer(name, tableBuffer)
         tableBuffer.init()
         return tableBuffer
     }
diff --git a/packages/SystemUI/src/com/android/systemui/logcat/LogAccessDialogActivity.java b/packages/SystemUI/src/com/android/systemui/logcat/LogAccessDialogActivity.java
index 81de607..2cfe5a7 100644
--- a/packages/SystemUI/src/com/android/systemui/logcat/LogAccessDialogActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/logcat/LogAccessDialogActivity.java
@@ -41,6 +41,7 @@
 import android.widget.Button;
 import android.widget.TextView;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.ILogAccessDialogCallback;
 import com.android.systemui.R;
 
@@ -69,7 +70,8 @@
 
     private AlertDialog.Builder mAlertDialog;
     private AlertDialog mAlert;
-    private View mAlertView;
+    @VisibleForTesting
+    protected View mAlertView;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
@@ -115,7 +117,10 @@
         mAlertDialog = new AlertDialog.Builder(this, themeId);
         mAlertDialog.setView(mAlertView);
         mAlertDialog.setOnCancelListener(dialog -> declineLogAccess());
-        mAlertDialog.setOnDismissListener(dialog -> finish());
+        mAlertDialog.setOnDismissListener(dialog -> {
+            mAlert = null;
+            finish();
+        });
 
         // show Alert
         mAlert = mAlertDialog.create();
@@ -127,12 +132,11 @@
     }
 
     @Override
-    protected void onDestroy() {
-        super.onDestroy();
-        if (!isChangingConfigurations() && mAlert != null && mAlert.isShowing()) {
-            mAlert.dismiss();
+    protected void onStop() {
+        super.onStop();
+        if (!isChangingConfigurations() && mAlert != null) {
+            mAlert.cancel();
         }
-        mAlert = null;
     }
 
     private boolean readIntentInfo(Intent intent) {
@@ -170,7 +174,6 @@
                 case MSG_DISMISS_DIALOG:
                     if (mAlert != null) {
                         mAlert.dismiss();
-                        mAlert = null;
                         declineLogAccess();
                     }
                     break;
@@ -181,7 +184,7 @@
         }
     };
 
-    private String getTitleString(Context context, String callingPackage, int uid)
+    protected String getTitleString(Context context, String callingPackage, int uid)
             throws NameNotFoundException {
         PackageManager pm = context.getPackageManager();
 
@@ -238,13 +241,13 @@
         try {
             if (view.getId() == R.id.log_access_dialog_allow_button) {
                 mCallback.approveAccessForClient(mUid, mPackageName);
-                finish();
             } else if (view.getId() == R.id.log_access_dialog_deny_button) {
                 declineLogAccess();
-                finish();
             }
-        } catch (RemoteException e) {
-            finish();
+        } catch (RemoteException ignored) {
+            // Do nothing.
+        } finally {
+            mAlert.dismiss();
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/OWNERS b/packages/SystemUI/src/com/android/systemui/media/OWNERS
index 69ea57b..b2d00df 100644
--- a/packages/SystemUI/src/com/android/systemui/media/OWNERS
+++ b/packages/SystemUI/src/com/android/systemui/media/OWNERS
@@ -1 +1,5 @@
 per-file MediaProjectionPermissionActivity.java = michaelwr@google.com
+
+# Haptics team also works on Ringtone
+per-file NotificationPlayer.java = file:/services/core/java/com/android/server/vibrator/OWNERS
+per-file RingtonePlayer.java = file:/services/core/java/com/android/server/vibrator/OWNERS
diff --git a/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java b/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java
index 05e04a1..6d9844d 100644
--- a/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java
+++ b/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java
@@ -16,6 +16,9 @@
 
 package com.android.systemui.media;
 
+import static java.util.Objects.requireNonNull;
+
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -34,6 +37,7 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.UserHandle;
+import android.os.VibrationEffect;
 import android.provider.MediaStore;
 import android.util.Log;
 
@@ -86,11 +90,11 @@
      */
     private class Client implements IBinder.DeathRecipient {
         private final IBinder mToken;
-        private final Ringtone mRingtone;
+        private Ringtone mRingtone;
 
-        Client(IBinder token, Ringtone ringtone) {
-            mToken = token;
-            mRingtone = ringtone;
+        Client(@NonNull IBinder token, @NonNull Ringtone ringtone) {
+            mToken = requireNonNull(token);
+            mRingtone = requireNonNull(ringtone);
         }
 
         @Override
@@ -107,45 +111,56 @@
         @Override
         public void play(IBinder token, Uri uri, AudioAttributes aa, float volume, boolean looping)
                 throws RemoteException {
-            playWithVolumeShaping(token, uri, aa, volume, looping, null);
+            playRemoteRingtone(token, uri, aa, true, Ringtone.MEDIA_SOUND,
+                    null, volume, looping, /* hapticGenerator= */ false,
+                    null);
         }
+
         @Override
-        public void playWithVolumeShaping(IBinder token, Uri uri, AudioAttributes aa, float volume,
-                boolean looping, @Nullable VolumeShaper.Configuration volumeShaperConfig)
+        public void playRemoteRingtone(IBinder token, Uri uri, AudioAttributes aa,
+                boolean useExactAudioAttributes,
+                @Ringtone.RingtoneMedia int enabledMedia, @Nullable VibrationEffect vibrationEffect,
+                float volume,
+                boolean looping, boolean isHapticGeneratorEnabled,
+                @Nullable VolumeShaper.Configuration volumeShaperConfig)
                 throws RemoteException {
             if (LOGD) {
                 Log.d(TAG, "play(token=" + token + ", uri=" + uri + ", uid="
                         + Binder.getCallingUid() + ")");
             }
+
+            // Don't hold the lock while constructing the ringtone, since it can be slow. The caller
+            // shouldn't call play on the same ringtone from 2 threads, so this shouldn't race and
+            // waste the build.
             Client client;
             synchronized (mClients) {
                 client = mClients.get(token);
             }
-            // Don't hold the lock while constructing the ringtone, since it can be slow. The caller
-            // shouldn't call play on the same ringtone from 2 threads, so this shouldn't race and
-            // waste the build.
             if (client == null) {
                 final UserHandle user = Binder.getCallingUserHandle();
-                Ringtone ringtone = new Ringtone(getContextForUser(user), false);
-                ringtone.setAudioAttributesField(aa);
-                ringtone.setUri(uri, volumeShaperConfig);
-                ringtone.createLocalMediaPlayer();
+                Ringtone ringtone = new Ringtone.Builder(getContextForUser(user), enabledMedia, aa)
+                        .setLocalOnly()
+                        .setUri(uri)
+                        .setLooping(looping)
+                        .setInitialSoundVolume(volume)
+                        .setUseExactAudioAttributes(useExactAudioAttributes)
+                        .setEnableHapticGenerator(isHapticGeneratorEnabled)
+                        .setVibrationEffect(vibrationEffect)
+                        .setVolumeShaperConfig(volumeShaperConfig)
+                        .build();
+                if (ringtone == null) {
+                    return;
+                }
                 synchronized (mClients) {
                     client = mClients.get(token);
                     if (client == null) {
                         client = new Client(token, ringtone);
                         token.linkToDeath(client, 0);
                         mClients.put(token, client);
-                        ringtone = null;  // "owned" by the client now.
                     }
                 }
-                // Clean up ringtone if it was abandoned (a client already existed).
-                if (ringtone != null) {
-                    ringtone.stop();
-                }
             }
-            client.mRingtone.setLooping(looping);
-            client.mRingtone.setVolume(volume);
+            // Ensure the client is initialized outside the all-clients lock, as it can be slow.
             client.mRingtone.play();
         }
 
@@ -177,18 +192,36 @@
         }
 
         @Override
-        public void setPlaybackProperties(IBinder token, float volume, boolean looping,
-                boolean hapticGeneratorEnabled) {
+        public void setHapticGeneratorEnabled(IBinder token, boolean hapticGeneratorEnabled) {
+            Client client;
+            synchronized (mClients) {
+                client = mClients.get(token);
+            }
+            if (client != null) {
+                client.mRingtone.setHapticGeneratorEnabled(hapticGeneratorEnabled);
+            }
+        }
+
+        @Override
+        public void setLooping(IBinder token, boolean looping) {
+            Client client;
+            synchronized (mClients) {
+                client = mClients.get(token);
+            }
+            if (client != null) {
+                client.mRingtone.setLooping(looping);
+            }
+        }
+
+        @Override
+        public void setVolume(IBinder token, float volume) {
             Client client;
             synchronized (mClients) {
                 client = mClients.get(token);
             }
             if (client != null) {
                 client.mRingtone.setVolume(volume);
-                client.mRingtone.setLooping(looping);
-                client.mRingtone.setHapticGeneratorEnabled(hapticGeneratorEnabled);
             }
-            // else no client for token when setting playback properties but will be set at play()
         }
 
         @Override
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/RecommendationViewHolder.kt b/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/RecommendationViewHolder.kt
index 0b33904..258284e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/RecommendationViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/RecommendationViewHolder.kt
@@ -31,15 +31,12 @@
 private const val TAG = "RecommendationViewHolder"
 
 /** ViewHolder for a Smartspace media recommendation. */
-class RecommendationViewHolder private constructor(itemView: View, updatedView: Boolean) {
+class RecommendationViewHolder private constructor(itemView: View) {
 
     val recommendations = itemView as TransitionLayout
 
     // Recommendation screen
-    lateinit var cardIcon: ImageView
-    lateinit var mediaAppIcons: List<CachingIconView>
-    lateinit var mediaProgressBars: List<SeekBar>
-    lateinit var cardTitle: TextView
+    val cardTitle: TextView = itemView.requireViewById(R.id.media_rec_title)
 
     val mediaCoverContainers =
         listOf<ViewGroup>(
@@ -47,53 +44,25 @@
             itemView.requireViewById(R.id.media_cover2_container),
             itemView.requireViewById(R.id.media_cover3_container)
         )
+    val mediaAppIcons: List<CachingIconView> =
+        mediaCoverContainers.map { it.requireViewById(R.id.media_rec_app_icon) }
     val mediaTitles: List<TextView> =
-        if (updatedView) {
-            mediaCoverContainers.map { it.requireViewById(R.id.media_title) }
-        } else {
-            listOf(
-                itemView.requireViewById(R.id.media_title1),
-                itemView.requireViewById(R.id.media_title2),
-                itemView.requireViewById(R.id.media_title3)
-            )
-        }
+        mediaCoverContainers.map { it.requireViewById(R.id.media_title) }
     val mediaSubtitles: List<TextView> =
-        if (updatedView) {
-            mediaCoverContainers.map { it.requireViewById(R.id.media_subtitle) }
-        } else {
-            listOf(
-                itemView.requireViewById(R.id.media_subtitle1),
-                itemView.requireViewById(R.id.media_subtitle2),
-                itemView.requireViewById(R.id.media_subtitle3)
-            )
+        mediaCoverContainers.map { it.requireViewById(R.id.media_subtitle) }
+    val mediaProgressBars: List<SeekBar> =
+        mediaCoverContainers.map {
+            it.requireViewById<SeekBar?>(R.id.media_progress_bar).apply {
+                // Media playback is in the direction of tape, not time, so it stays LTR
+                layoutDirection = View.LAYOUT_DIRECTION_LTR
+            }
         }
 
     val mediaCoverItems: List<ImageView> =
-        if (updatedView) {
-            mediaCoverContainers.map { it.requireViewById(R.id.media_cover) }
-        } else {
-            listOf(
-                itemView.requireViewById(R.id.media_cover1),
-                itemView.requireViewById(R.id.media_cover2),
-                itemView.requireViewById(R.id.media_cover3)
-            )
-        }
+        mediaCoverContainers.map { it.requireViewById(R.id.media_cover) }
     val gutsViewHolder = GutsViewHolder(itemView)
 
     init {
-        if (updatedView) {
-            mediaAppIcons = mediaCoverContainers.map { it.requireViewById(R.id.media_rec_app_icon) }
-            cardTitle = itemView.requireViewById(R.id.media_rec_title)
-            mediaProgressBars =
-                mediaCoverContainers.map {
-                    it.requireViewById<SeekBar?>(R.id.media_progress_bar).apply {
-                        // Media playback is in the direction of tape, not time, so it stays LTR
-                        layoutDirection = View.LAYOUT_DIRECTION_LTR
-                    }
-                }
-        } else {
-            cardIcon = itemView.requireViewById<ImageView>(R.id.recommendation_card_icon)
-        }
         (recommendations.background as IlluminationDrawable).let { background ->
             mediaCoverContainers.forEach { background.registerLightSource(it) }
             background.registerLightSource(gutsViewHolder.cancel)
@@ -114,63 +83,31 @@
          * @param parent Parent of inflated view.
          */
         @JvmStatic
-        fun create(
-            inflater: LayoutInflater,
-            parent: ViewGroup,
-            updatedView: Boolean,
-        ): RecommendationViewHolder {
+        fun create(inflater: LayoutInflater, parent: ViewGroup): RecommendationViewHolder {
             val itemView =
-                if (updatedView) {
-                    inflater.inflate(
-                        R.layout.media_recommendations,
-                        parent,
-                        false /* attachToRoot */
-                    )
-                } else {
-                    inflater.inflate(
-                        R.layout.media_smartspace_recommendations,
-                        parent,
-                        false /* attachToRoot */
-                    )
-                }
+                inflater.inflate(R.layout.media_recommendations, parent, false /* attachToRoot */)
             // Because this media view (a TransitionLayout) is used to measure and layout the views
             // in various states before being attached to its parent, we can't depend on the default
             // LAYOUT_DIRECTION_INHERIT to correctly resolve the ltr direction.
             itemView.layoutDirection = View.LAYOUT_DIRECTION_LOCALE
-            return RecommendationViewHolder(itemView, updatedView)
+            return RecommendationViewHolder(itemView)
         }
 
         // Res Ids for the control components on the recommendation view.
         val controlsIds =
             setOf(
-                R.id.recommendation_card_icon,
                 R.id.media_rec_title,
-                R.id.media_cover1,
-                R.id.media_cover2,
-                R.id.media_cover3,
                 R.id.media_cover,
                 R.id.media_cover1_container,
                 R.id.media_cover2_container,
                 R.id.media_cover3_container,
-                R.id.media_title1,
-                R.id.media_title2,
-                R.id.media_title3,
                 R.id.media_title,
-                R.id.media_subtitle1,
-                R.id.media_subtitle2,
-                R.id.media_subtitle3,
                 R.id.media_subtitle,
             )
 
         val mediaTitlesAndSubtitlesIds =
             setOf(
-                R.id.media_title1,
-                R.id.media_title2,
-                R.id.media_title3,
                 R.id.media_title,
-                R.id.media_subtitle1,
-                R.id.media_subtitle2,
-                R.id.media_subtitle3,
                 R.id.media_subtitle,
             )
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/LocalMediaManagerFactory.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/LocalMediaManagerFactory.kt
index ff763d8..f908481 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/LocalMediaManagerFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/LocalMediaManagerFactory.kt
@@ -18,8 +18,8 @@
 
 import android.content.Context
 import com.android.settingslib.bluetooth.LocalBluetoothManager
-import com.android.settingslib.media.InfoMediaManager
 import com.android.settingslib.media.LocalMediaManager
+import com.android.settingslib.media.ManagerInfoMediaManager
 import javax.inject.Inject
 
 /** Factory to create [LocalMediaManager] objects. */
@@ -31,7 +31,7 @@
 ) {
     /** Creates a [LocalMediaManager] for the given package. */
     fun create(packageName: String): LocalMediaManager {
-        return InfoMediaManager(context, packageName, null, localBluetoothManager).run {
+        return ManagerInfoMediaManager(context, packageName, null, localBluetoothManager).run {
             LocalMediaManager(context, localBluetoothManager, this, packageName)
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
index 6b993ce..55dcd88 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
@@ -184,7 +184,7 @@
     private val tunerService: TunerService,
     private val mediaFlags: MediaFlags,
     private val logger: MediaUiEventLogger,
-    private val smartspaceManager: SmartspaceManager,
+    private val smartspaceManager: SmartspaceManager?,
     private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
 ) : Dumpable, BcSmartspaceDataPlugin.SmartspaceTargetListener {
 
@@ -260,7 +260,7 @@
         tunerService: TunerService,
         mediaFlags: MediaFlags,
         logger: MediaUiEventLogger,
-        smartspaceManager: SmartspaceManager,
+        smartspaceManager: SmartspaceManager?,
         keyguardUpdateMonitor: KeyguardUpdateMonitor,
     ) : this(
         context,
@@ -347,7 +347,7 @@
         // Register for Smartspace data updates.
         smartspaceMediaDataProvider.registerListener(this)
         smartspaceSession =
-            smartspaceManager.createSmartspaceSession(
+            smartspaceManager?.createSmartspaceSession(
                 SmartspaceConfig.Builder(context, SMARTSPACE_UI_SURFACE_LABEL).build()
             )
         smartspaceSession?.let {
@@ -716,8 +716,7 @@
         val appUid = currentEntry?.appUid ?: Process.INVALID_UID
         val isExplicit =
             desc.extras?.getLong(MediaConstants.METADATA_KEY_IS_EXPLICIT) ==
-                MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT &&
-                mediaFlags.isExplicitIndicatorEnabled()
+                MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT
 
         val progress =
             if (mediaFlags.isResumeProgressEnabled()) {
@@ -826,12 +825,10 @@
 
         // Explicit Indicator
         var isExplicit = false
-        if (mediaFlags.isExplicitIndicatorEnabled()) {
-            val mediaMetadataCompat = MediaMetadataCompat.fromMediaMetadata(metadata)
-            isExplicit =
-                mediaMetadataCompat?.getLong(MediaConstants.METADATA_KEY_IS_EXPLICIT) ==
-                    MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT
-        }
+        val mediaMetadataCompat = MediaMetadataCompat.fromMediaMetadata(metadata)
+        isExplicit =
+            mediaMetadataCompat?.getLong(MediaConstants.METADATA_KEY_IS_EXPLICIT) ==
+                MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT
 
         // Artist name
         var artist: CharSequence? = metadata?.getString(MediaMetadata.METADATA_KEY_ARTIST)
@@ -1253,6 +1250,9 @@
         return try {
             val options = BroadcastOptions.makeBasic()
             options.setInteractive(true)
+            options.setPendingIntentBackgroundActivityStartMode(
+                BroadcastOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
+            )
             intent.send(options.toBundle())
             true
         } catch (e: PendingIntent.CanceledException) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDeviceManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDeviceManager.kt
index 3fc3ad6..6ad36ca 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDeviceManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDeviceManager.kt
@@ -71,7 +71,7 @@
     private val entries: MutableMap<String, Entry> = mutableMapOf()
 
     init {
-        dumpManager.registerDumpable(javaClass.name, this)
+        dumpManager.registerDumpable(this)
     }
 
     /** Add a listener for changes to the media route (ie. device). */
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaTimeoutLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaTimeoutLogger.kt
index e2e269d..534241e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaTimeoutLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaTimeoutLogger.kt
@@ -19,7 +19,7 @@
 import android.media.session.PlaybackState
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import com.android.systemui.log.dagger.MediaTimeoutListenerLog
 import javax.inject.Inject
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowserLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowserLogger.kt
index 9e53d77..888b9c7 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowserLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowserLogger.kt
@@ -19,7 +19,7 @@
 import android.content.ComponentName
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import com.android.systemui.log.dagger.MediaBrowserLog
 import javax.inject.Inject
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
index 70b5e75..398dcf2 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
@@ -744,11 +744,7 @@
 
             val newRecs = mediaControlPanelFactory.get()
             newRecs.attachRecommendation(
-                RecommendationViewHolder.create(
-                    LayoutInflater.from(context),
-                    mediaContent,
-                    mediaFlags.isRecommendationCardUpdateEnabled()
-                )
+                RecommendationViewHolder.create(LayoutInflater.from(context), mediaContent)
             )
             newRecs.mediaViewController.sizeChangedListener = this::updateCarouselDimensions
             val lp =
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselControllerLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselControllerLogger.kt
index 0ed2434..3dc0000 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselControllerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselControllerLogger.kt
@@ -18,7 +18,7 @@
 
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import com.android.systemui.log.dagger.MediaCarouselControllerLog
 import javax.inject.Inject
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
index 35082fd..a12bc2c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
@@ -23,6 +23,7 @@
 import android.animation.Animator;
 import android.animation.AnimatorInflater;
 import android.animation.AnimatorSet;
+import android.app.ActivityOptions;
 import android.app.BroadcastOptions;
 import android.app.PendingIntent;
 import android.app.WallpaperColors;
@@ -535,7 +536,10 @@
                         mLockscreenUserManager.getCurrentUserId());
                 if (showOverLockscreen) {
                     try {
-                        clickIntent.send();
+                        ActivityOptions opts = ActivityOptions.makeBasic();
+                        opts.setPendingIntentBackgroundActivityStartMode(
+                                ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
+                        clickIntent.send(opts.toBundle());
                     } catch (PendingIntent.CanceledException e) {
                         Log.e(TAG, "Pending intent for " + key + " was cancelled");
                     }
@@ -684,6 +688,8 @@
                                 try {
                                     BroadcastOptions options = BroadcastOptions.makeBasic();
                                     options.setInteractive(true);
+                                    options.setPendingIntentBackgroundActivityStartMode(
+                                            ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
                                     deviceIntent.send(options.toBundle());
                                 } catch (PendingIntent.CanceledException e) {
                                     Log.e(TAG, "Device pending intent was canceled");
@@ -778,14 +784,7 @@
             contentDescription =
                     mRecommendationViewHolder.getGutsViewHolder().getGutsText().getText();
         } else if (data != null) {
-            if (mFeatureFlags.isEnabled(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE)) {
-                contentDescription = mContext.getString(
-                        R.string.controls_media_smartspace_rec_header);
-            } else {
-                contentDescription = mContext.getString(
-                        R.string.controls_media_smartspace_rec_description,
-                        data.getAppName(mContext));
-            }
+            contentDescription = mContext.getString(R.string.controls_media_smartspace_rec_header);
         } else {
             contentDescription = null;
         }
@@ -1371,10 +1370,6 @@
         PackageManager packageManager = mContext.getPackageManager();
         // Set up media source app's logo.
         Drawable icon = packageManager.getApplicationIcon(applicationInfo);
-        if (!mFeatureFlags.isEnabled(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE)) {
-            ImageView headerLogoImageView = mRecommendationViewHolder.getCardIcon();
-            headerLogoImageView.setImageDrawable(icon);
-        }
         fetchAndUpdateRecommendationColors(icon);
 
         // Set up media rec card's tap action if applicable.
@@ -1395,16 +1390,7 @@
 
             // Set up media item cover.
             ImageView mediaCoverImageView = mediaCoverItems.get(itemIndex);
-            if (mFeatureFlags.isEnabled(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE)) {
-                bindRecommendationArtwork(
-                        recommendation,
-                        data.getPackageName(),
-                        itemIndex
-                );
-            } else {
-                mediaCoverImageView.post(
-                        () -> mediaCoverImageView.setImageIcon(recommendation.getIcon()));
-            }
+            bindRecommendationArtwork(recommendation, data.getPackageName(), itemIndex);
 
             // Set up the media item's click listener if applicable.
             ViewGroup mediaCoverContainer = mediaCoverContainers.get(itemIndex);
@@ -1449,21 +1435,18 @@
             subtitleView.setText(subtitle);
 
             // Set up progress bar
-            if (mFeatureFlags.isEnabled(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE)) {
-                SeekBar mediaProgressBar =
-                        mRecommendationViewHolder.getMediaProgressBars().get(itemIndex);
-                TextView mediaSubtitle =
-                        mRecommendationViewHolder.getMediaSubtitles().get(itemIndex);
-                // show progress bar if the recommended album is played.
-                Double progress = MediaDataUtils.getDescriptionProgress(recommendation.getExtras());
-                if (progress == null || progress <= 0.0) {
-                    mediaProgressBar.setVisibility(View.GONE);
-                    mediaSubtitle.setVisibility(View.VISIBLE);
-                } else {
-                    mediaProgressBar.setProgress((int) (progress * 100));
-                    mediaProgressBar.setVisibility(View.VISIBLE);
-                    mediaSubtitle.setVisibility(View.GONE);
-                }
+            SeekBar mediaProgressBar =
+                    mRecommendationViewHolder.getMediaProgressBars().get(itemIndex);
+            TextView mediaSubtitle = mRecommendationViewHolder.getMediaSubtitles().get(itemIndex);
+            // show progress bar if the recommended album is played.
+            Double progress = MediaDataUtils.getDescriptionProgress(recommendation.getExtras());
+            if (progress == null || progress <= 0.0) {
+                mediaProgressBar.setVisibility(View.GONE);
+                mediaSubtitle.setVisibility(View.VISIBLE);
+            } else {
+                mediaProgressBar.setProgress((int) (progress * 100));
+                mediaProgressBar.setVisibility(View.VISIBLE);
+                mediaSubtitle.setVisibility(View.GONE);
             }
         }
         mSmartspaceMediaItemsCount = NUM_REQUIRED_RECOMMENDATIONS;
@@ -1582,9 +1565,7 @@
         int textPrimaryColor = MediaColorSchemesKt.textPrimaryFromScheme(colorScheme);
         int textSecondaryColor = MediaColorSchemesKt.textSecondaryFromScheme(colorScheme);
 
-        if (mFeatureFlags.isEnabled(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE)) {
-            mRecommendationViewHolder.getCardTitle().setTextColor(textPrimaryColor);
-        }
+        mRecommendationViewHolder.getCardTitle().setTextColor(textPrimaryColor);
 
         mRecommendationViewHolder.getRecommendations()
                 .setBackgroundTintList(ColorStateList.valueOf(backgroundColor));
@@ -1592,12 +1573,9 @@
                 (title) -> title.setTextColor(textPrimaryColor));
         mRecommendationViewHolder.getMediaSubtitles().forEach(
                 (subtitle) -> subtitle.setTextColor(textSecondaryColor));
-        if (mFeatureFlags.isEnabled(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE)) {
-            mRecommendationViewHolder.getMediaProgressBars().forEach(
-                    (progressBar) -> progressBar.setProgressTintList(
-                            ColorStateList.valueOf(textPrimaryColor))
-            );
-        }
+        mRecommendationViewHolder.getMediaProgressBars().forEach(
+                (progressBar) -> progressBar.setProgressTintList(
+                        ColorStateList.valueOf(textPrimaryColor)));
 
         mRecommendationViewHolder.getGutsViewHolder().setColors(colorScheme);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
index 4bca778..1dd969f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
@@ -655,13 +655,8 @@
                 expandedLayout.load(context, R.xml.media_session_expanded)
             }
             TYPE.RECOMMENDATION -> {
-                if (mediaFlags.isRecommendationCardUpdateEnabled()) {
-                    collapsedLayout.load(context, R.xml.media_recommendations_view_collapsed)
-                    expandedLayout.load(context, R.xml.media_recommendations_view_expanded)
-                } else {
-                    collapsedLayout.load(context, R.xml.media_recommendation_collapsed)
-                    expandedLayout.load(context, R.xml.media_recommendation_expanded)
-                }
+                collapsedLayout.load(context, R.xml.media_recommendations_collapsed)
+                expandedLayout.load(context, R.xml.media_recommendations_expanded)
             }
         }
         refreshState()
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewLogger.kt
index c781b76..8f1595d 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewLogger.kt
@@ -18,7 +18,7 @@
 
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import com.android.systemui.log.dagger.MediaViewLog
 import javax.inject.Inject
 
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 9bc66f6..09aef88 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
@@ -43,19 +43,12 @@
      */
     fun areNearbyMediaDevicesEnabled() = featureFlags.isEnabled(Flags.MEDIA_NEARBY_DEVICES)
 
-    /** Check whether we show explicit indicator on UMO */
-    fun isExplicitIndicatorEnabled() = featureFlags.isEnabled(Flags.MEDIA_EXPLICIT_INDICATOR)
-
     /**
      * If true, keep active media controls for the lifetime of the MediaSession, regardless of
      * whether the underlying notification was dismissed
      */
     fun isRetainingPlayersEnabled() = featureFlags.isEnabled(Flags.MEDIA_RETAIN_SESSIONS)
 
-    /** Check whether we show the updated recommendation card. */
-    fun isRecommendationCardUpdateEnabled() =
-        featureFlags.isEnabled(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE)
-
     /** Check whether to get progress information for resume players */
     fun isResumeProgressEnabled() = featureFlags.isEnabled(Flags.MEDIA_RESUME_PROGRESS)
 
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 cc75478..f87f53c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
@@ -76,6 +76,7 @@
 import com.android.settingslib.bluetooth.LocalBluetoothManager;
 import com.android.settingslib.media.InfoMediaManager;
 import com.android.settingslib.media.LocalMediaManager;
+import com.android.settingslib.media.ManagerInfoMediaManager;
 import com.android.settingslib.media.MediaDevice;
 import com.android.settingslib.utils.ThreadUtils;
 import com.android.systemui.R;
@@ -194,7 +195,7 @@
         mKeyGuardManager = keyGuardManager;
         mFeatureFlags = featureFlags;
         mUserTracker = userTracker;
-        InfoMediaManager imm = new InfoMediaManager(mContext, packageName, null, lbm);
+        InfoMediaManager imm = new ManagerInfoMediaManager(mContext, packageName, null, lbm);
         mLocalMediaManager = new LocalMediaManager(mContext, lbm, imm, packageName);
         mMetricLogger = new MediaOutputMetricLogger(mContext, mPackageName);
         mDialogLaunchAnimator = dialogLaunchAnimator;
@@ -836,7 +837,7 @@
 
     void adjustVolume(MediaDevice device, int volume) {
         ThreadUtils.postOnBackgroundThread(() -> {
-            device.requestSetVolume(volume);
+            mLocalMediaManager.adjustDeviceVolume(device, volume);
         });
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitLogger.kt b/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitLogger.kt
index bbcf259..4171682 100644
--- a/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitLogger.kt
@@ -3,7 +3,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.log.dagger.MediaMuteAwaitLog
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import javax.inject.Inject
 
 /** Log messages for [MediaMuteAwaitConnectionManager]. */
diff --git a/packages/SystemUI/src/com/android/systemui/media/nearby/NearbyMediaDevicesLogger.kt b/packages/SystemUI/src/com/android/systemui/media/nearby/NearbyMediaDevicesLogger.kt
index 66399d5..46c0132 100644
--- a/packages/SystemUI/src/com/android/systemui/media/nearby/NearbyMediaDevicesLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/nearby/NearbyMediaDevicesLogger.kt
@@ -3,7 +3,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.log.dagger.NearbyMediaDevicesLog
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import javax.inject.Inject
 
 /** Log messages for [NearbyMediaDevicesManager]. */
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerUtils.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerUtils.kt
index eeda1027..3c2226f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerUtils.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerUtils.kt
@@ -17,7 +17,7 @@
 package com.android.systemui.media.taptotransfer.common
 
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 
 /** A helper for logging media tap-to-transfer events. */
 object MediaTttLoggerUtils {
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLogger.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLogger.kt
index 206e5e3..503afd3 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLogger.kt
@@ -20,7 +20,7 @@
 import com.android.internal.logging.InstanceId
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import com.android.systemui.media.taptotransfer.common.MediaTttLoggerUtils
 import javax.inject.Inject
 
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherCoreStartable.kt
new file mode 100644
index 0000000..3c50127
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherCoreStartable.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.mediaprojection.taskswitcher
+
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.mediaprojection.taskswitcher.ui.TaskSwitcherNotificationCoordinator
+import javax.inject.Inject
+
+@SysUISingleton
+class MediaProjectionTaskSwitcherCoreStartable
+@Inject
+constructor(
+    private val notificationCoordinator: TaskSwitcherNotificationCoordinator,
+    private val featureFlags: FeatureFlags,
+) : CoreStartable {
+
+    override fun start() {
+        if (featureFlags.isEnabled(Flags.PARTIAL_SCREEN_SHARING_TASK_SWITCHER)) {
+            notificationCoordinator.start()
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherModule.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherModule.kt
new file mode 100644
index 0000000..22ad07e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherModule.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.mediaprojection.taskswitcher
+
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.ActivityTaskManagerTasksRepository
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.MediaProjectionManagerRepository
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.MediaProjectionRepository
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.TasksRepository
+import dagger.Binds
+import dagger.Module
+
+@Module
+interface MediaProjectionTaskSwitcherModule {
+
+    @Binds fun mediaRepository(impl: MediaProjectionManagerRepository): MediaProjectionRepository
+
+    @Binds fun tasksRepository(impl: ActivityTaskManagerTasksRepository): TasksRepository
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/model/MediaProjectionState.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/model/MediaProjectionState.kt
new file mode 100644
index 0000000..9938f11
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/model/MediaProjectionState.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.mediaprojection.taskswitcher.data.model
+
+import android.app.TaskInfo
+
+/** Represents the state of media projection. */
+sealed interface MediaProjectionState {
+    object NotProjecting : MediaProjectionState
+    object EntireScreen : MediaProjectionState
+    data class SingleTask(val task: TaskInfo) : MediaProjectionState
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepository.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepository.kt
new file mode 100644
index 0000000..492d482
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepository.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.mediaprojection.taskswitcher.data.repository
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.app.ActivityTaskManager
+import android.app.TaskStackListener
+import android.os.IBinder
+import android.util.Log
+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.Application
+import com.android.systemui.dagger.qualifiers.Background
+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.shareIn
+import kotlinx.coroutines.withContext
+
+/** Implementation of [TasksRepository] that uses [ActivityTaskManager] as the data source. */
+@SysUISingleton
+class ActivityTaskManagerTasksRepository
+@Inject
+constructor(
+    private val activityTaskManager: ActivityTaskManager,
+    @Application private val applicationScope: CoroutineScope,
+    @Background private val backgroundDispatcher: CoroutineDispatcher,
+) : TasksRepository {
+
+    override suspend fun findRunningTaskFromWindowContainerToken(
+        windowContainerToken: IBinder
+    ): RunningTaskInfo? =
+        getRunningTasks().firstOrNull { taskInfo ->
+            taskInfo.token.asBinder() == windowContainerToken
+        }
+
+    private suspend fun getRunningTasks(): List<RunningTaskInfo> =
+        withContext(backgroundDispatcher) { activityTaskManager.getTasks(Integer.MAX_VALUE) }
+
+    override val foregroundTask: Flow<RunningTaskInfo> =
+        conflatedCallbackFlow {
+                val listener =
+                    object : TaskStackListener() {
+                        override fun onTaskMovedToFront(taskInfo: RunningTaskInfo) {
+                            Log.d(TAG, "onTaskMovedToFront: $taskInfo")
+                            trySendWithFailureLogging(taskInfo, TAG)
+                        }
+                    }
+                activityTaskManager.registerTaskStackListener(listener)
+                awaitClose { activityTaskManager.unregisterTaskStackListener(listener) }
+            }
+            .shareIn(applicationScope, SharingStarted.Lazily, replay = 1)
+
+    companion object {
+        private const val TAG = "TasksRepository"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepository.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepository.kt
new file mode 100644
index 0000000..38d4e69
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepository.kt
@@ -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.systemui.mediaprojection.taskswitcher.data.repository
+
+import android.media.projection.MediaProjectionInfo
+import android.media.projection.MediaProjectionManager
+import android.os.Handler
+import android.util.Log
+import android.view.ContentRecordingSession
+import android.view.ContentRecordingSession.RECORD_CONTENT_DISPLAY
+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.Application
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.mediaprojection.taskswitcher.data.model.MediaProjectionState
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.shareIn
+import kotlinx.coroutines.launch
+
+@SysUISingleton
+class MediaProjectionManagerRepository
+@Inject
+constructor(
+    private val mediaProjectionManager: MediaProjectionManager,
+    @Main private val handler: Handler,
+    @Application private val applicationScope: CoroutineScope,
+    private val tasksRepository: TasksRepository,
+) : MediaProjectionRepository {
+
+    override val mediaProjectionState: Flow<MediaProjectionState> =
+        conflatedCallbackFlow {
+                val callback =
+                    object : MediaProjectionManager.Callback() {
+                        override fun onStart(info: MediaProjectionInfo?) {
+                            Log.d(TAG, "MediaProjectionManager.Callback#onStart")
+                            trySendWithFailureLogging(MediaProjectionState.NotProjecting, TAG)
+                        }
+
+                        override fun onStop(info: MediaProjectionInfo?) {
+                            Log.d(TAG, "MediaProjectionManager.Callback#onStop")
+                            trySendWithFailureLogging(MediaProjectionState.NotProjecting, TAG)
+                        }
+
+                        override fun onRecordingSessionSet(
+                            info: MediaProjectionInfo,
+                            session: ContentRecordingSession?
+                        ) {
+                            Log.d(TAG, "MediaProjectionManager.Callback#onSessionStarted: $session")
+                            launch { trySendWithFailureLogging(stateForSession(session), TAG) }
+                        }
+                    }
+                mediaProjectionManager.addCallback(callback, handler)
+                awaitClose { mediaProjectionManager.removeCallback(callback) }
+            }
+            .shareIn(scope = applicationScope, started = SharingStarted.Lazily, replay = 1)
+
+    private suspend fun stateForSession(session: ContentRecordingSession?): MediaProjectionState {
+        if (session == null) {
+            return MediaProjectionState.NotProjecting
+        }
+        if (session.contentToRecord == RECORD_CONTENT_DISPLAY || session.tokenToRecord == null) {
+            return MediaProjectionState.EntireScreen
+        }
+        val matchingTask =
+            tasksRepository.findRunningTaskFromWindowContainerToken(session.tokenToRecord)
+                ?: return MediaProjectionState.EntireScreen
+        return MediaProjectionState.SingleTask(matchingTask)
+    }
+
+    companion object {
+        private const val TAG = "MediaProjectionMngrRepo"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionRepository.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionRepository.kt
new file mode 100644
index 0000000..5bec692
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionRepository.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.mediaprojection.taskswitcher.data.repository
+
+import com.android.systemui.mediaprojection.taskswitcher.data.model.MediaProjectionState
+import kotlinx.coroutines.flow.Flow
+
+/** Represents a repository to retrieve and change data related to media projection. */
+interface MediaProjectionRepository {
+
+    /** Represents the current [MediaProjectionState]. */
+    val mediaProjectionState: Flow<MediaProjectionState>
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/NoOpMediaProjectionRepository.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/NoOpMediaProjectionRepository.kt
new file mode 100644
index 0000000..544eb6b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/NoOpMediaProjectionRepository.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.mediaprojection.taskswitcher.data.repository
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.mediaprojection.taskswitcher.data.model.MediaProjectionState
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.emptyFlow
+
+/**
+ * No-op implementation of [MediaProjectionRepository] that does nothing. Currently used as a
+ * placeholder, while the real implementation is not completed.
+ */
+@SysUISingleton
+class NoOpMediaProjectionRepository @Inject constructor() : MediaProjectionRepository {
+
+    override val mediaProjectionState: Flow<MediaProjectionState> = emptyFlow()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/TasksRepository.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/TasksRepository.kt
new file mode 100644
index 0000000..6a535e4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/TasksRepository.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.mediaprojection.taskswitcher.data.repository
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.os.IBinder
+import kotlinx.coroutines.flow.Flow
+
+/** Repository responsible for retrieving data related to running tasks. */
+interface TasksRepository {
+
+    /**
+     * Tries to find a [RunningTaskInfo] with a matching window container token. Returns `null` when
+     * no matching task was found.
+     */
+    suspend fun findRunningTaskFromWindowContainerToken(
+        windowContainerToken: IBinder
+    ): RunningTaskInfo?
+
+    /**
+     * Emits a stream of [RunningTaskInfo] that have been moved to the foreground.
+     *
+     * Note: when subscribing for the first time, it will not immediately emit the current
+     * foreground task. Only after a change in foreground task has occurred.
+     */
+    val foregroundTask: Flow<RunningTaskInfo>
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractor.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractor.kt
new file mode 100644
index 0000000..fc5cf7d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractor.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.mediaprojection.taskswitcher.domain.interactor
+
+import android.app.TaskInfo
+import android.content.Intent
+import android.util.Log
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.mediaprojection.taskswitcher.data.model.MediaProjectionState
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.MediaProjectionRepository
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.TasksRepository
+import com.android.systemui.mediaprojection.taskswitcher.domain.model.TaskSwitchState
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+
+/** Interactor with logic related to task switching in the context of media projection. */
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class TaskSwitchInteractor
+@Inject
+constructor(
+    mediaProjectionRepository: MediaProjectionRepository,
+    private val tasksRepository: TasksRepository,
+) {
+
+    /**
+     * Emits a stream of changes to the state of task switching, in the context of media projection.
+     */
+    val taskSwitchChanges: Flow<TaskSwitchState> =
+        mediaProjectionRepository.mediaProjectionState.flatMapLatest { projectionState ->
+            Log.d(TAG, "MediaProjectionState -> $projectionState")
+            when (projectionState) {
+                is MediaProjectionState.SingleTask -> {
+                    val projectedTask = projectionState.task
+                    tasksRepository.foregroundTask.map { foregroundTask ->
+                        if (hasForegroundTaskSwitched(projectedTask, foregroundTask)) {
+                            TaskSwitchState.TaskSwitched(projectedTask, foregroundTask)
+                        } else {
+                            TaskSwitchState.TaskUnchanged
+                        }
+                    }
+                }
+                is MediaProjectionState.EntireScreen,
+                is MediaProjectionState.NotProjecting -> {
+                    flowOf(TaskSwitchState.NotProjectingTask)
+                }
+            }
+        }
+
+    /**
+     * Returns whether tasks have been switched.
+     *
+     * Always returns `false` when launcher is in the foreground. The reason is that when going to
+     * recents to switch apps, launcher becomes the new foreground task, and we don't want to show
+     * the notification then.
+     */
+    private fun hasForegroundTaskSwitched(projectedTask: TaskInfo, foregroundTask: TaskInfo) =
+        projectedTask.taskId != foregroundTask.taskId && !foregroundTask.isLauncher
+
+    private val TaskInfo.isLauncher
+        get() =
+            baseIntent.hasCategory(Intent.CATEGORY_HOME) && baseIntent.action == Intent.ACTION_MAIN
+
+    companion object {
+        private const val TAG = "TaskSwitchInteractor"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/domain/model/TaskSwitchState.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/domain/model/TaskSwitchState.kt
new file mode 100644
index 0000000..cd1258e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/domain/model/TaskSwitchState.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.mediaprojection.taskswitcher.domain.model
+
+import android.app.TaskInfo
+
+/** Represents tha state of task switching in the context of single task media projection. */
+sealed interface TaskSwitchState {
+    /** Currently no task is being projected. */
+    object NotProjectingTask : TaskSwitchState
+    /** The foreground task is the same as the task that is currently being projected. */
+    object TaskUnchanged : TaskSwitchState
+    /** The foreground task is a different one to the task it currently being projected. */
+    data class TaskSwitched(val projectedTask: TaskInfo, val foregroundTask: TaskInfo) :
+        TaskSwitchState
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinator.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinator.kt
new file mode 100644
index 0000000..a437139
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinator.kt
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.mediaprojection.taskswitcher.ui
+
+import android.app.Notification
+import android.app.NotificationChannel
+import android.app.NotificationManager
+import android.content.Context
+import android.util.Log
+import com.android.systemui.R
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.mediaprojection.taskswitcher.ui.model.TaskSwitcherNotificationUiState.NotShowing
+import com.android.systemui.mediaprojection.taskswitcher.ui.model.TaskSwitcherNotificationUiState.Showing
+import com.android.systemui.mediaprojection.taskswitcher.ui.viewmodel.TaskSwitcherNotificationViewModel
+import com.android.systemui.util.NotificationChannels
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.launch
+
+/** Coordinator responsible for showing/hiding the task switcher notification. */
+@SysUISingleton
+class TaskSwitcherNotificationCoordinator
+@Inject
+constructor(
+    private val context: Context,
+    private val notificationManager: NotificationManager,
+    @Application private val applicationScope: CoroutineScope,
+    @Main private val mainDispatcher: CoroutineDispatcher,
+    private val viewModel: TaskSwitcherNotificationViewModel,
+) {
+    fun start() {
+        applicationScope.launch {
+            viewModel.uiState.flowOn(mainDispatcher).collect { uiState ->
+                Log.d(TAG, "uiState -> $uiState")
+                when (uiState) {
+                    is Showing -> showNotification()
+                    is NotShowing -> hideNotification()
+                }
+            }
+        }
+    }
+
+    private fun showNotification() {
+        notificationManager.notify(TAG, NOTIFICATION_ID, createNotification())
+    }
+
+    private fun createNotification(): Notification {
+        // TODO(b/286201261): implement actions
+        val actionSwitch =
+            Notification.Action.Builder(
+                    /* icon = */ null,
+                    context.getString(R.string.media_projection_task_switcher_action_switch),
+                    /* intent = */ null
+                )
+                .build()
+
+        val actionBack =
+            Notification.Action.Builder(
+                    /* icon = */ null,
+                    context.getString(R.string.media_projection_task_switcher_action_back),
+                    /* intent = */ null
+                )
+                .build()
+
+        val channel =
+            NotificationChannel(
+                NotificationChannels.HINTS,
+                context.getString(R.string.media_projection_task_switcher_notification_channel),
+                NotificationManager.IMPORTANCE_HIGH
+            )
+        notificationManager.createNotificationChannel(channel)
+        return Notification.Builder(context, channel.id)
+            .setSmallIcon(R.drawable.qs_screen_record_icon_on)
+            .setAutoCancel(true)
+            .setContentText(context.getString(R.string.media_projection_task_switcher_text))
+            .addAction(actionSwitch)
+            .addAction(actionBack)
+            .setPriority(Notification.PRIORITY_HIGH)
+            .setDefaults(Notification.DEFAULT_VIBRATE)
+            .build()
+    }
+
+    private fun hideNotification() {
+        notificationManager.cancel(NOTIFICATION_ID)
+    }
+
+    companion object {
+        private const val TAG = "TaskSwitchNotifCoord"
+        private const val NOTIFICATION_ID = 5566
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/model/TaskSwitcherNotificationUiState.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/model/TaskSwitcherNotificationUiState.kt
new file mode 100644
index 0000000..21aee72
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/model/TaskSwitcherNotificationUiState.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.mediaprojection.taskswitcher.ui.model
+
+import android.app.TaskInfo
+
+/** Represents the UI state for the task switcher notification. */
+sealed interface TaskSwitcherNotificationUiState {
+    /** The notification should not be shown. */
+    object NotShowing : TaskSwitcherNotificationUiState
+    /** The notification should be shown. */
+    data class Showing(
+        val projectedTask: TaskInfo,
+        val foregroundTask: TaskInfo,
+    ) : TaskSwitcherNotificationUiState
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModel.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModel.kt
new file mode 100644
index 0000000..d9754d4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModel.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.mediaprojection.taskswitcher.ui.viewmodel
+
+import android.util.Log
+import com.android.systemui.mediaprojection.taskswitcher.domain.interactor.TaskSwitchInteractor
+import com.android.systemui.mediaprojection.taskswitcher.domain.model.TaskSwitchState
+import com.android.systemui.mediaprojection.taskswitcher.ui.model.TaskSwitcherNotificationUiState
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+
+class TaskSwitcherNotificationViewModel @Inject constructor(interactor: TaskSwitchInteractor) {
+
+    val uiState: Flow<TaskSwitcherNotificationUiState> =
+        interactor.taskSwitchChanges.map { taskSwitchChange ->
+            Log.d(TAG, "taskSwitchChange: $taskSwitchChange")
+            when (taskSwitchChange) {
+                is TaskSwitchState.TaskSwitched -> {
+                    TaskSwitcherNotificationUiState.Showing(
+                        projectedTask = taskSwitchChange.projectedTask,
+                        foregroundTask = taskSwitchChange.foregroundTask,
+                    )
+                }
+                is TaskSwitchState.NotProjectingTask,
+                is TaskSwitchState.TaskUnchanged -> {
+                    TaskSwitcherNotificationUiState.NotShowing
+                }
+            }
+        }
+
+    companion object {
+        private const val TAG = "TaskSwitchNotifVM"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/model/SysUiState.java b/packages/SystemUI/src/com/android/systemui/model/SysUiState.java
index 8d80990..07846b5 100644
--- a/packages/SystemUI/src/com/android/systemui/model/SysUiState.java
+++ b/packages/SystemUI/src/com/android/systemui/model/SysUiState.java
@@ -114,7 +114,7 @@
         pw.print("  mSysUiStateFlags="); pw.println(mFlags);
         pw.println("    " + QuickStepContract.getSystemUiStateString(mFlags));
         pw.print("    backGestureDisabled=");
-        pw.println(QuickStepContract.isBackGestureDisabled(mFlags));
+        pw.println(QuickStepContract.isBackGestureDisabled(mFlags, false /* forTrackpad */));
         pw.print("    assistantGestureDisabled=");
         pw.println(QuickStepContract.isAssistantGestureDisabled(mFlags));
     }
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
index 1da8718..8225c47 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
@@ -51,7 +51,6 @@
 import android.view.IWindowManager;
 import android.view.View;
 import android.view.WindowInsets;
-import android.view.WindowManagerGlobal;
 import android.view.accessibility.AccessibilityManager;
 
 import androidx.annotation.NonNull;
@@ -69,10 +68,13 @@
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.phone.BarTransitions.TransitionMode;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
+import dagger.Lazy;
+
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
@@ -80,8 +82,6 @@
 
 import javax.inject.Inject;
 
-import dagger.Lazy;
-
 /**
  * Extracts shared elements between navbar and taskbar delegate to de-dupe logic and help them
  * experience the joys of friendship.
@@ -111,6 +111,7 @@
     private final AccessibilityButtonTargetsObserver mAccessibilityButtonTargetsObserver;
     private final List<NavbarTaskbarStateUpdater> mStateListeners = new ArrayList<>();
     private final Context mContext;
+    private final NotificationShadeWindowController mNotificationShadeWindowController;
     private final CommandQueue mCommandQueue;
     private final ContentResolver mContentResolver;
     private final EdgeBackGestureHandler mEdgeBackGestureHandler;
@@ -182,9 +183,11 @@
             IWindowManager wm,
             UserTracker userTracker,
             DisplayTracker displayTracker,
+            NotificationShadeWindowController notificationShadeWindowController,
             DumpManager dumpManager,
             CommandQueue commandQueue) {
         mContext = context;
+        mNotificationShadeWindowController = notificationShadeWindowController;
         mCommandQueue = commandQueue;
         mContentResolver = mContext.getContentResolver();
         mAccessibilityManager = accessibilityManager;
@@ -460,11 +463,7 @@
      * {@link InputMethodService} and the keyguard states.
      */
     public boolean isImeShown(int vis) {
-        View shadeWindowView = null;
-        if (mCentralSurfacesOptionalLazy.get().isPresent()) {
-            shadeWindowView =
-                    mCentralSurfacesOptionalLazy.get().get().getNotificationShadeWindowView();
-        }
+        View shadeWindowView =  mNotificationShadeWindowController.getWindowRootView();
         boolean isKeyguardShowing = mKeyguardStateController.isShowing();
         boolean imeVisibleOnShade = shadeWindowView != null && shadeWindowView.isAttachedToWindow()
                 && shadeWindowView.getRootWindowInsets().isVisible(WindowInsets.Type.ime());
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationModeController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationModeController.java
index 63276fe..99daf36 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationModeController.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationModeController.java
@@ -28,18 +28,21 @@
 import android.os.PatternMatcher;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.Trace;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.provider.Settings.Secure;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
+
 import com.android.systemui.Dumpable;
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dagger.qualifiers.UiBackground;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -64,22 +67,21 @@
     private Context mCurrentUserContext;
     private final IOverlayManager mOverlayManager;
     private final Executor mUiBgExecutor;
+    private final UserTracker mUserTracker;
 
     private ArrayList<ModeChangedListener> mListeners = new ArrayList<>();
 
-    private final DeviceProvisionedController.DeviceProvisionedListener mDeviceProvisionedCallback =
-            new DeviceProvisionedController.DeviceProvisionedListener() {
-                @Override
-                public void onUserSwitched() {
-                    if (DEBUG) {
-                        Log.d(TAG, "onUserSwitched: "
-                                + ActivityManagerWrapper.getInstance().getCurrentUserId());
-                    }
+    private final UserTracker.Callback mUserTrackerCallback = new UserTracker.Callback() {
+        @Override
+        public void onUserChanged(int newUser, @NonNull Context userContext) {
+            if (DEBUG) {
+                Log.d(TAG, "onUserChanged: "
+                        + newUser);
+            }
 
-                    // Update the nav mode for the current user
-                    updateCurrentInteractionMode(true /* notify */);
-                }
-            };
+            updateCurrentInteractionMode(true /* notify */);
+        }
+    };
 
     // The primary user SysUI process doesn't get AppInfo changes from overlay package changes for
     // the secondary user (b/158613864), so we need to update the interaction mode here as well
@@ -97,19 +99,20 @@
 
     @Inject
     public NavigationModeController(Context context,
-            DeviceProvisionedController deviceProvisionedController,
             ConfigurationController configurationController,
+            UserTracker userTracker,
+            @Main Executor mainExecutor,
             @UiBackground Executor uiBgExecutor,
             DumpManager dumpManager) {
         mContext = context;
         mCurrentUserContext = context;
+        mUserTracker = userTracker;
+        mUserTracker.addCallback(mUserTrackerCallback, mainExecutor);
         mOverlayManager = IOverlayManager.Stub.asInterface(
                 ServiceManager.getService(Context.OVERLAY_SERVICE));
         mUiBgExecutor = uiBgExecutor;
         dumpManager.registerDumpable(getClass().getSimpleName(), this);
 
-        deviceProvisionedController.addCallback(mDeviceProvisionedCallback);
-
         IntentFilter overlayFilter = new IntentFilter(ACTION_OVERLAY_CHANGED);
         overlayFilter.addDataScheme("package");
         overlayFilter.addDataSchemeSpecificPart("android", PatternMatcher.PATTERN_LITERAL);
@@ -129,6 +132,7 @@
     }
 
     public void updateCurrentInteractionMode(boolean notify) {
+        Trace.beginSection("NMC#updateCurrentInteractionMode");
         mCurrentUserContext = getCurrentUserContext();
         int mode = getCurrentInteractionMode(mCurrentUserContext);
         mUiBgExecutor.execute(() ->
@@ -144,6 +148,7 @@
                 mListeners.get(i).onNavigationModeChanged(mode);
             }
         }
+        Trace.endSection();
     }
 
     public int addListener(ModeChangedListener listener) {
@@ -171,7 +176,7 @@
     }
 
     public Context getCurrentUserContext() {
-        int userId = ActivityManagerWrapper.getInstance().getCurrentUserId();
+        int userId = mUserTracker.getUserId();
         if (DEBUG) {
             Log.d(TAG, "getCurrentUserContext: contextUser=" + mContext.getUserId()
                     + " currentUser=" + userId);
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 8b3d7a6..b21b001 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -15,11 +15,13 @@
  */
 package com.android.systemui.navigationbar.gestural;
 
+import static android.view.InputDevice.SOURCE_MOUSE;
+import static android.view.InputDevice.SOURCE_TOUCHPAD;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION;
 
 import static com.android.systemui.classifier.Classifier.BACK_GESTURE;
-import static com.android.systemui.navigationbar.gestural.Utilities.isTrackpadFourFingerSwipe;
-import static com.android.systemui.navigationbar.gestural.Utilities.isTrackpadMultiFingerSwipe;
+import static com.android.systemui.navigationbar.gestural.Utilities.isTrackpadScroll;
+import static com.android.systemui.navigationbar.gestural.Utilities.isTrackpadThreeFingerSwipe;
 
 import android.annotation.NonNull;
 import android.app.ActivityManager;
@@ -37,6 +39,7 @@
 import android.graphics.Region;
 import android.hardware.input.InputManager;
 import android.icu.text.SimpleDateFormat;
+import android.os.Handler;
 import android.os.Looper;
 import android.os.RemoteException;
 import android.os.SystemClock;
@@ -85,11 +88,7 @@
 import com.android.systemui.shared.system.SysUiStatsLog;
 import com.android.systemui.shared.system.TaskStackChangeListener;
 import com.android.systemui.shared.system.TaskStackChangeListeners;
-import com.android.systemui.shared.tracing.ProtoTraceable;
 import com.android.systemui.statusbar.phone.LightBarController;
-import com.android.systemui.tracing.ProtoTracer;
-import com.android.systemui.tracing.nano.EdgeBackGestureHandlerProto;
-import com.android.systemui.tracing.nano.SystemUiTraceProto;
 import com.android.systemui.util.Assert;
 import com.android.wm.shell.back.BackAnimation;
 import com.android.wm.shell.desktopmode.DesktopMode;
@@ -112,8 +111,7 @@
 /**
  * Utility class to handle edge swipes for back gesture
  */
-public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBackPlugin>,
-        ProtoTraceable<SystemUiTraceProto> {
+public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBackPlugin> {
 
     private static final String TAG = "EdgeBackGestureHandler";
     private static final int MAX_LONG_PRESS_TIMEOUT = SystemProperties.getInt(
@@ -189,12 +187,12 @@
     private Consumer<Boolean> mButtonForcedVisibleCallback;
 
     private final PluginManager mPluginManager;
-    private final ProtoTracer mProtoTracer;
     private final NavigationModeController mNavigationModeController;
     private final BackPanelController.Factory mBackPanelControllerFactory;
     private final ViewConfiguration mViewConfiguration;
     private final WindowManager mWindowManager;
     private final IWindowManager mWindowManagerService;
+    private final InputManager mInputManager;
     private final Optional<Pip> mPipOptional;
     private final Optional<DesktopMode> mDesktopModeOptional;
     private final FalsingManager mFalsingManager;
@@ -206,6 +204,7 @@
     private final int mDisplayId;
 
     private final Executor mMainExecutor;
+    private final Handler mMainHandler;
     private final Executor mBackgroundExecutor;
 
     private final Rect mPipExcludedBounds = new Rect();
@@ -249,13 +248,17 @@
     private boolean mDeferSetIsOnLeftEdge;
 
     private boolean mIsAttached;
-    private boolean mIsGesturalModeEnabled;
+    private boolean mIsGestureHandlingEnabled;
+    private boolean mIsTrackpadConnected;
+    private boolean mInGestureNavMode;
+    private boolean mUsingThreeButtonNav;
     private boolean mIsEnabled;
     private boolean mIsNavBarShownTransiently;
     private boolean mIsBackGestureAllowed;
     private boolean mGestureBlockingActivityRunning;
     private boolean mIsNewBackAffordanceEnabled;
     private boolean mIsTrackpadGestureFeaturesEnabled;
+    private boolean mIsTrackpadThreeFingerSwipe;
     private boolean mIsButtonForcedVisible;
 
     private InputMonitor mInputMonitor;
@@ -349,20 +352,56 @@
                 }
             };
 
+    private final InputManager.InputDeviceListener mInputDeviceListener =
+            new InputManager.InputDeviceListener() {
+
+        // Only one trackpad can be connected to a device at a time, since it takes over the
+        // only USB port.
+        private int mTrackpadDeviceId;
+
+        @Override
+        public void onInputDeviceAdded(int deviceId) {
+            if (isTrackpadDevice(deviceId)) {
+                mTrackpadDeviceId = deviceId;
+                update(true /* isTrackpadConnected */);
+            }
+        }
+
+        @Override
+        public void onInputDeviceChanged(int deviceId) { }
+
+        @Override
+        public void onInputDeviceRemoved(int deviceId) {
+            if (mTrackpadDeviceId == deviceId) {
+                update(false /* isTrackpadConnected */);
+            }
+        }
+
+        private void update(boolean isTrackpadConnected) {
+            boolean isPreviouslyTrackpadConnected = mIsTrackpadConnected;
+            mIsTrackpadConnected = isTrackpadConnected;
+            if (isPreviouslyTrackpadConnected != mIsTrackpadConnected) {
+                updateIsEnabled();
+                updateCurrentUserResources();
+            }
+        }
+    };
+
     EdgeBackGestureHandler(
             Context context,
             OverviewProxyService overviewProxyService,
             SysUiState sysUiState,
             PluginManager pluginManager,
             @Main Executor executor,
+            @Main Handler handler,
             @Background Executor backgroundExecutor,
             UserTracker userTracker,
-            ProtoTracer protoTracer,
             NavigationModeController navigationModeController,
             BackPanelController.Factory backPanelControllerFactory,
             ViewConfiguration viewConfiguration,
             WindowManager windowManager,
             IWindowManager windowManagerService,
+            InputManager inputManager,
             Optional<Pip> pipOptional,
             Optional<DesktopMode> desktopModeOptional,
             FalsingManager falsingManager,
@@ -373,17 +412,18 @@
         mContext = context;
         mDisplayId = context.getDisplayId();
         mMainExecutor = executor;
+        mMainHandler = handler;
         mBackgroundExecutor = backgroundExecutor;
         mUserTracker = userTracker;
         mOverviewProxyService = overviewProxyService;
         mSysUiState = sysUiState;
         mPluginManager = pluginManager;
-        mProtoTracer = protoTracer;
         mNavigationModeController = navigationModeController;
         mBackPanelControllerFactory = backPanelControllerFactory;
         mViewConfiguration = viewConfiguration;
         mWindowManager = windowManager;
         mWindowManagerService = windowManagerService;
+        mInputManager = inputManager;
         mPipOptional = pipOptional;
         mDesktopModeOptional = desktopModeOptional;
         mFalsingManager = falsingManager;
@@ -392,6 +432,8 @@
         mFeatureFlags = featureFlags;
         mLightBarControllerProvider = lightBarControllerProvider;
         mLastReportedConfig.setTo(mContext.getResources().getConfiguration());
+        mIsTrackpadGestureFeaturesEnabled = mFeatureFlags.isEnabled(
+                Flags.TRACKPAD_GESTURE_FEATURES);
         ComponentName recentsComponentName = ComponentName.unflattenFromString(
                 context.getString(com.android.internal.R.string.config_recentsComponentName));
         if (recentsComponentName != null) {
@@ -423,7 +465,7 @@
                 ViewConfiguration.getLongPressTimeout());
 
         mGestureNavigationSettingsObserver = new GestureNavigationSettingsObserver(
-                mContext.getMainThreadHandler(), mContext, this::onNavigationSettingsChanged);
+                mMainHandler, mContext, this::onNavigationSettingsChanged);
 
         updateCurrentUserResources();
     }
@@ -507,9 +549,15 @@
      */
     public void onNavBarAttached() {
         mIsAttached = true;
-        mProtoTracer.add(this);
         mOverviewProxyService.addCallback(mQuickSwitchListener);
         mSysUiState.addCallback(mSysUiStateCallback);
+        if (mIsTrackpadGestureFeaturesEnabled) {
+            mInputManager.registerInputDeviceListener(mInputDeviceListener, mMainHandler);
+            int [] inputDevices = mInputManager.getInputDeviceIds();
+            for (int inputDeviceId : inputDevices) {
+                mInputDeviceListener.onInputDeviceAdded(inputDeviceId);
+            }
+        }
         updateIsEnabled();
         mUserTracker.addCallback(mUserChangedCallback, mMainExecutor);
     }
@@ -519,9 +567,9 @@
      */
     public void onNavBarDetached() {
         mIsAttached = false;
-        mProtoTracer.remove(this);
         mOverviewProxyService.removeCallback(mQuickSwitchListener);
         mSysUiState.removeCallback(mSysUiStateCallback);
+        mInputManager.unregisterInputDeviceListener(mInputDeviceListener);
         updateIsEnabled();
         mUserTracker.removeCallback(mUserChangedCallback);
     }
@@ -530,7 +578,8 @@
      * @see NavigationModeController.ModeChangedListener#onNavigationModeChanged
      */
     public void onNavigationModeChanged(int mode) {
-        mIsGesturalModeEnabled = QuickStepContract.isGesturalMode(mode);
+        mUsingThreeButtonNav = QuickStepContract.isLegacyMode(mode);
+        mInGestureNavMode = QuickStepContract.isGesturalMode(mode);
         updateIsEnabled();
         updateCurrentUserResources();
     }
@@ -553,83 +602,83 @@
     private void updateIsEnabled() {
         try {
             Trace.beginSection("EdgeBackGestureHandler#updateIsEnabled");
-            updateIsEnabledTraced();
+
+            mIsGestureHandlingEnabled =
+                    mInGestureNavMode || (mIsTrackpadGestureFeaturesEnabled && mUsingThreeButtonNav
+                            && mIsTrackpadConnected);
+            boolean isEnabled = mIsAttached && mIsGestureHandlingEnabled;
+            if (isEnabled == mIsEnabled) {
+                return;
+            }
+            mIsEnabled = isEnabled;
+            disposeInputChannel();
+
+            if (mEdgeBackPlugin != null) {
+                mEdgeBackPlugin.onDestroy();
+                mEdgeBackPlugin = null;
+            }
+
+            if (!mIsEnabled) {
+                mGestureNavigationSettingsObserver.unregister();
+                if (DEBUG_MISSING_GESTURE) {
+                    Log.d(DEBUG_MISSING_GESTURE_TAG, "Unregister display listener");
+                }
+                mPluginManager.removePluginListener(this);
+                TaskStackChangeListeners.getInstance().unregisterTaskStackListener(
+                        mTaskStackListener);
+                DeviceConfig.removeOnPropertiesChangedListener(mOnPropertiesChangedListener);
+                mPipOptional.ifPresent(pip -> pip.setOnIsInPipStateChangedListener(null));
+
+                try {
+                    mWindowManagerService.unregisterSystemGestureExclusionListener(
+                            mGestureExclusionListener, mDisplayId);
+                } catch (RemoteException | IllegalArgumentException e) {
+                    Log.e(TAG, "Failed to unregister window manager callbacks", e);
+                }
+
+            } else {
+                mGestureNavigationSettingsObserver.register();
+                updateDisplaySize();
+                if (DEBUG_MISSING_GESTURE) {
+                    Log.d(DEBUG_MISSING_GESTURE_TAG, "Register display listener");
+                }
+                TaskStackChangeListeners.getInstance().registerTaskStackListener(
+                        mTaskStackListener);
+                DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI,
+                        mMainExecutor::execute, mOnPropertiesChangedListener);
+                mPipOptional.ifPresent(pip -> pip.setOnIsInPipStateChangedListener(
+                        mOnIsInPipStateChangedListener));
+                mDesktopModeOptional.ifPresent(
+                        dm -> dm.addDesktopGestureExclusionRegionListener(
+                                mDesktopCornersChangedListener, mMainExecutor));
+
+                try {
+                    mWindowManagerService.registerSystemGestureExclusionListener(
+                            mGestureExclusionListener, mDisplayId);
+                } catch (RemoteException | IllegalArgumentException e) {
+                    Log.e(TAG, "Failed to register window manager callbacks", e);
+                }
+
+                // Register input event receiver
+                mInputMonitor = mContext.getSystemService(InputManager.class).monitorGestureInput(
+                        "edge-swipe", mDisplayId);
+                mInputEventReceiver = new InputChannelCompat.InputEventReceiver(
+                        mInputMonitor.getInputChannel(), Looper.getMainLooper(),
+                        Choreographer.getInstance(), this::onInputEvent);
+
+                // Add a nav bar panel window
+                mIsNewBackAffordanceEnabled = mFeatureFlags.isEnabled(Flags.NEW_BACK_AFFORDANCE);
+                resetEdgeBackPlugin();
+                mPluginManager.addPluginListener(
+                        this, NavigationEdgeBackPlugin.class, /*allowMultiple=*/ false);
+            }
+            // Update the ML model resources.
+            updateMLModelState();
         } finally {
             Trace.endSection();
         }
     }
 
-    private void updateIsEnabledTraced() {
-        boolean isEnabled = mIsAttached && mIsGesturalModeEnabled;
-        if (isEnabled == mIsEnabled) {
-            return;
-        }
-        mIsEnabled = isEnabled;
-        disposeInputChannel();
-
-        if (mEdgeBackPlugin != null) {
-            mEdgeBackPlugin.onDestroy();
-            mEdgeBackPlugin = null;
-        }
-
-        if (!mIsEnabled) {
-            mGestureNavigationSettingsObserver.unregister();
-            if (DEBUG_MISSING_GESTURE) {
-                Log.d(DEBUG_MISSING_GESTURE_TAG, "Unregister display listener");
-            }
-            mPluginManager.removePluginListener(this);
-            TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mTaskStackListener);
-            DeviceConfig.removeOnPropertiesChangedListener(mOnPropertiesChangedListener);
-            mPipOptional.ifPresent(pip -> pip.setOnIsInPipStateChangedListener(null));
-
-            try {
-                mWindowManagerService.unregisterSystemGestureExclusionListener(
-                        mGestureExclusionListener, mDisplayId);
-            } catch (RemoteException | IllegalArgumentException e) {
-                Log.e(TAG, "Failed to unregister window manager callbacks", e);
-            }
-
-        } else {
-            mGestureNavigationSettingsObserver.register();
-            updateDisplaySize();
-            if (DEBUG_MISSING_GESTURE) {
-                Log.d(DEBUG_MISSING_GESTURE_TAG, "Register display listener");
-            }
-            TaskStackChangeListeners.getInstance().registerTaskStackListener(mTaskStackListener);
-            DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI,
-                    mMainExecutor::execute, mOnPropertiesChangedListener);
-            mPipOptional.ifPresent(
-                    pip -> pip.setOnIsInPipStateChangedListener(mOnIsInPipStateChangedListener));
-            mDesktopModeOptional.ifPresent(
-                    dm -> dm.addDesktopGestureExclusionRegionListener(
-                            mDesktopCornersChangedListener, mMainExecutor));
-
-            try {
-                mWindowManagerService.registerSystemGestureExclusionListener(
-                        mGestureExclusionListener, mDisplayId);
-            } catch (RemoteException | IllegalArgumentException e) {
-                Log.e(TAG, "Failed to register window manager callbacks", e);
-            }
-
-            // Register input event receiver
-            mInputMonitor = mContext.getSystemService(InputManager.class).monitorGestureInput(
-                    "edge-swipe", mDisplayId);
-            mInputEventReceiver = new InputChannelCompat.InputEventReceiver(
-                    mInputMonitor.getInputChannel(), Looper.getMainLooper(),
-                    Choreographer.getInstance(), this::onInputEvent);
-
-            // Add a nav bar panel window
-            mIsNewBackAffordanceEnabled = mFeatureFlags.isEnabled(Flags.NEW_BACK_AFFORDANCE);
-            mIsTrackpadGestureFeaturesEnabled = mFeatureFlags.isEnabled(
-                    Flags.TRACKPAD_GESTURE_FEATURES);
-            resetEdgeBackPlugin();
-            mPluginManager.addPluginListener(
-                    this, NavigationEdgeBackPlugin.class, /*allowMultiple=*/ false);
-        }
-        // Update the ML model resources.
-        updateMLModelState();
-    }
-
     @Override
     public void onPluginConnected(NavigationEdgeBackPlugin plugin, Context context) {
         setEdgeBackPlugin(plugin);
@@ -704,9 +753,9 @@
     }
 
     private void updateMLModelState() {
-        boolean newState =
-                mIsGesturalModeEnabled && DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
-                        SystemUiDeviceConfigFlags.USE_BACK_GESTURE_ML_MODEL, false);
+        boolean newState = mIsGestureHandlingEnabled && DeviceConfig.getBoolean(
+                DeviceConfig.NAMESPACE_SYSTEMUI,
+                SystemUiDeviceConfigFlags.USE_BACK_GESTURE_ML_MODEL, false);
 
         if (newState == mUseMLModel) {
             return;
@@ -825,6 +874,15 @@
                 mDisplaySize.y - insets.bottom);
     }
 
+    private boolean isTrackpadDevice(int deviceId) {
+        InputDevice inputDevice = mInputManager.getInputDevice(deviceId);
+        if (inputDevice == null) {
+            return false;
+        }
+        return inputDevice.getSources() == (InputDevice.SOURCE_MOUSE
+                | InputDevice.SOURCE_TOUCHPAD);
+    }
+
     private boolean desktopExcludeRegionContains(int x, int y) {
         return mDesktopModeExcludeRegion.contains(x, y);
     }
@@ -919,18 +977,21 @@
                 (int) mEndPoint.x, (int) mEndPoint.y,
                 mEdgeWidthLeft + mLeftInset,
                 mDisplaySize.x - (mEdgeWidthRight + mRightInset),
-                mUseMLModel ? mMLResults : -2, logPackageName);
+                mUseMLModel ? mMLResults : -2, logPackageName,
+                mIsTrackpadThreeFingerSwipe ? SysUiStatsLog.BACK_GESTURE__INPUT_TYPE__TRACKPAD
+                        : SysUiStatsLog.BACK_GESTURE__INPUT_TYPE__TOUCH);
     }
 
     private void onMotionEvent(MotionEvent ev) {
         int action = ev.getActionMasked();
-        boolean isTrackpadMultiFingerSwipe = isTrackpadMultiFingerSwipe(
-                mIsTrackpadGestureFeaturesEnabled, ev);
         if (action == MotionEvent.ACTION_DOWN) {
             if (DEBUG_MISSING_GESTURE) {
                 Log.d(DEBUG_MISSING_GESTURE_TAG, "Start gesture: " + ev);
             }
 
+            mIsTrackpadThreeFingerSwipe = isTrackpadThreeFingerSwipe(
+                    mIsTrackpadGestureFeaturesEnabled, ev);
+
             // ACTION_UP or ACTION_CANCEL is not guaranteed to be called before a new
             // ACTION_DOWN, in that case we should just reuse the old instance.
             mVelocityTracker.clear();
@@ -938,7 +999,7 @@
             // Verify if this is in within the touch region and we aren't in immersive mode, and
             // either the bouncer is showing or the notification panel is hidden
             mInputEventReceiver.setBatchingEnabled(false);
-            if (isTrackpadMultiFingerSwipe) {
+            if (mIsTrackpadThreeFingerSwipe) {
                 // Since trackpad gestures don't have zones, this will be determined later by the
                 // direction of the gesture. {@code mIsOnLeftEdge} is set to false to begin with.
                 mDeferSetIsOnLeftEdge = true;
@@ -950,20 +1011,27 @@
             mLogGesture = false;
             mInRejectedExclusion = false;
             boolean isWithinInsets = isWithinInsets((int) ev.getX(), (int) ev.getY());
-            // Trackpad back gestures don't have zones, so we don't need to check if the down event
-            // is within insets.
-            mAllowGesture = !mDisabledForQuickstep && mIsBackGestureAllowed
-                    && (isTrackpadMultiFingerSwipe || isWithinInsets)
+            boolean isBackAllowedCommon = !mDisabledForQuickstep && mIsBackGestureAllowed
                     && !mGestureBlockingActivityRunning
-                    && !QuickStepContract.isBackGestureDisabled(mSysUiFlags)
-                    && (isValidTrackpadBackGesture(isTrackpadMultiFingerSwipe)
-                        || isWithinTouchRegion((int) ev.getX(), (int) ev.getY()));
+                    && !QuickStepContract.isBackGestureDisabled(mSysUiFlags,
+                            mIsTrackpadThreeFingerSwipe)
+                    && !isTrackpadScroll(mIsTrackpadGestureFeaturesEnabled, ev);
+            if (mIsTrackpadThreeFingerSwipe) {
+                // Trackpad back gestures don't have zones, so we don't need to check if the down
+                // event is within insets.
+                mAllowGesture = isBackAllowedCommon && isValidTrackpadBackGesture(
+                        true /* isTrackpadEvent */);
+            } else {
+                mAllowGesture = isBackAllowedCommon && !mUsingThreeButtonNav && isWithinInsets
+                    && isWithinTouchRegion((int) ev.getX(), (int) ev.getY())
+                    && !isButtonPressFromTrackpad(ev);
+            }
             if (mAllowGesture) {
                 mEdgeBackPlugin.setIsLeftPanel(mIsOnLeftEdge);
                 mEdgeBackPlugin.onMotionEvent(ev);
                 dispatchToBackAnimation(ev);
             }
-            if (mLogGesture || isTrackpadMultiFingerSwipe) {
+            if (mLogGesture || mIsTrackpadThreeFingerSwipe) {
                 mDownPoint.set(ev.getX(), ev.getY());
                 mEndPoint.set(-1, -1);
                 mThresholdCrossed = false;
@@ -974,20 +1042,20 @@
             mTmpLogDate.setTime(curTime);
             String curTimeStr = mLogDateFormat.format(mTmpLogDate);
             (isWithinInsets ? mGestureLogInsideInsets : mGestureLogOutsideInsets).log(String.format(
-                    "Gesture [%d [%s],alw=%B, mltf=%B, left=%B, defLeft=%B, backAlw=%B, disbld=%B,"
+                    "Gesture [%d [%s],alw=%B, t3fs=%B, left=%B, defLeft=%B, backAlw=%B, disbld=%B,"
                             + " qsDisbld=%b, blkdAct=%B, pip=%B,"
                             + " disp=%s, wl=%d, il=%d, wr=%d, ir=%d, excl=%s]",
-                    curTime, curTimeStr, mAllowGesture, isTrackpadMultiFingerSwipe,
+                    curTime, curTimeStr, mAllowGesture, mIsTrackpadThreeFingerSwipe,
                     mIsOnLeftEdge, mDeferSetIsOnLeftEdge, mIsBackGestureAllowed,
-                    QuickStepContract.isBackGestureDisabled(mSysUiFlags), mDisabledForQuickstep,
-                    mGestureBlockingActivityRunning, mIsInPip, mDisplaySize,
+                    QuickStepContract.isBackGestureDisabled(mSysUiFlags,
+                            mIsTrackpadThreeFingerSwipe),
+                    mDisabledForQuickstep, mGestureBlockingActivityRunning, mIsInPip, mDisplaySize,
                     mEdgeWidthLeft, mLeftInset, mEdgeWidthRight, mRightInset, mExcludeRegion));
         } else if (mAllowGesture || mLogGesture) {
             if (!mThresholdCrossed) {
                 mEndPoint.x = (int) ev.getX();
                 mEndPoint.y = (int) ev.getY();
-                if (action == MotionEvent.ACTION_POINTER_DOWN && (!isTrackpadMultiFingerSwipe
-                        || isTrackpadFourFingerSwipe(mIsTrackpadGestureFeaturesEnabled, ev))) {
+                if (action == MotionEvent.ACTION_POINTER_DOWN && !mIsTrackpadThreeFingerSwipe) {
                     if (mAllowGesture) {
                         logGesture(SysUiStatsLog.BACK_GESTURE__TYPE__INCOMPLETE_MULTI_TOUCH);
                         if (DEBUG_MISSING_GESTURE) {
@@ -999,11 +1067,7 @@
                     mLogGesture = false;
                     return;
                 } else if (action == MotionEvent.ACTION_MOVE) {
-                    if (isTrackpadFourFingerSwipe(isTrackpadMultiFingerSwipe, ev)) {
-                        cancelGesture(ev);
-                        return;
-                    }
-                    if (isTrackpadMultiFingerSwipe && mDeferSetIsOnLeftEdge) {
+                    if (mIsTrackpadThreeFingerSwipe && mDeferSetIsOnLeftEdge) {
                         // mIsOnLeftEdge is determined by the relative position between the down
                         // and the current motion event for trackpad gestures instead of zoning.
                         mIsOnLeftEdge = mEndPoint.x > mDownPoint.x;
@@ -1061,8 +1125,13 @@
                 dispatchToBackAnimation(ev);
             }
         }
+    }
 
-        mProtoTracer.scheduleFrameUpdate();
+    private boolean isButtonPressFromTrackpad(MotionEvent ev) {
+        // We don't allow back for button press from the trackpad, and yet we do with a mouse.
+        int sources = InputManager.getInstance().getInputDevice(ev.getDeviceId()).getSources();
+        int sourceTrackpad = (SOURCE_MOUSE | SOURCE_TOUCHPAD);
+        return (sources & sourceTrackpad) == sourceTrackpad && ev.getButtonState() != 0;
     }
 
     private void dispatchToBackAnimation(MotionEvent event) {
@@ -1159,7 +1228,7 @@
         pw.println("  mIsEnabled=" + mIsEnabled);
         pw.println("  mIsAttached=" + mIsAttached);
         pw.println("  mIsBackGestureAllowed=" + mIsBackGestureAllowed);
-        pw.println("  mIsGesturalModeEnabled=" + mIsGesturalModeEnabled);
+        pw.println("  mIsGestureHandlingEnabled=" + mIsGestureHandlingEnabled);
         pw.println("  mIsNavBarShownTransiently=" + mIsNavBarShownTransiently);
         pw.println("  mGestureBlockingActivityRunning=" + mGestureBlockingActivityRunning);
         pw.println("  mAllowGesture=" + mAllowGesture);
@@ -1184,6 +1253,8 @@
         pw.println("  mPredictionLog=" + String.join("\n", mPredictionLog));
         pw.println("  mGestureLogInsideInsets=" + String.join("\n", mGestureLogInsideInsets));
         pw.println("  mGestureLogOutsideInsets=" + String.join("\n", mGestureLogOutsideInsets));
+        pw.println("  mIsTrackpadConnected=" + mIsTrackpadConnected);
+        pw.println("  mUsingThreeButtonNav=" + mUsingThreeButtonNav);
         pw.println("  mEdgeBackPlugin=" + mEdgeBackPlugin);
         if (mEdgeBackPlugin != null) {
             mEdgeBackPlugin.dump(pw);
@@ -1202,14 +1273,6 @@
         return topActivity != null && mGestureBlockingActivities.contains(topActivity);
     }
 
-    @Override
-    public void writeToProto(SystemUiTraceProto proto) {
-        if (proto.edgeBackGestureHandler == null) {
-            proto.edgeBackGestureHandler = new EdgeBackGestureHandlerProto();
-        }
-        proto.edgeBackGestureHandler.allowGesture = mAllowGesture;
-    }
-
     public void setBackAnimation(BackAnimation backAnimation) {
         mBackAnimation = backAnimation;
         updateBackAnimationThresholds();
@@ -1233,14 +1296,15 @@
         private final SysUiState mSysUiState;
         private final PluginManager mPluginManager;
         private final Executor mExecutor;
+        private final Handler mHandler;
         private final Executor mBackgroundExecutor;
         private final UserTracker mUserTracker;
-        private final ProtoTracer mProtoTracer;
         private final NavigationModeController mNavigationModeController;
         private final BackPanelController.Factory mBackPanelControllerFactory;
         private final ViewConfiguration mViewConfiguration;
         private final WindowManager mWindowManager;
         private final IWindowManager mWindowManagerService;
+        private final InputManager mInputManager;
         private final Optional<Pip> mPipOptional;
         private final Optional<DesktopMode> mDesktopModeOptional;
         private final FalsingManager mFalsingManager;
@@ -1255,14 +1319,15 @@
                        SysUiState sysUiState,
                        PluginManager pluginManager,
                        @Main Executor executor,
+                       @Main Handler handler,
                        @Background Executor backgroundExecutor,
                        UserTracker userTracker,
-                       ProtoTracer protoTracer,
                        NavigationModeController navigationModeController,
                        BackPanelController.Factory backPanelControllerFactory,
                        ViewConfiguration viewConfiguration,
                        WindowManager windowManager,
                        IWindowManager windowManagerService,
+                       InputManager inputManager,
                        Optional<Pip> pipOptional,
                        Optional<DesktopMode> desktopModeOptional,
                        FalsingManager falsingManager,
@@ -1275,14 +1340,15 @@
             mSysUiState = sysUiState;
             mPluginManager = pluginManager;
             mExecutor = executor;
+            mHandler = handler;
             mBackgroundExecutor = backgroundExecutor;
             mUserTracker = userTracker;
-            mProtoTracer = protoTracer;
             mNavigationModeController = navigationModeController;
             mBackPanelControllerFactory = backPanelControllerFactory;
             mViewConfiguration = viewConfiguration;
             mWindowManager = windowManager;
             mWindowManagerService = windowManagerService;
+            mInputManager = inputManager;
             mPipOptional = pipOptional;
             mDesktopModeOptional = desktopModeOptional;
             mFalsingManager = falsingManager;
@@ -1300,14 +1366,15 @@
                     mSysUiState,
                     mPluginManager,
                     mExecutor,
+                    mHandler,
                     mBackgroundExecutor,
                     mUserTracker,
-                    mProtoTracer,
                     mNavigationModeController,
                     mBackPanelControllerFactory,
                     mViewConfiguration,
                     mWindowManager,
                     mWindowManagerService,
+                    mInputManager,
                     mPipOptional,
                     mDesktopModeOptional,
                     mFalsingManager,
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/Utilities.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/Utilities.java
index 50e8aa7..10a88c8 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/Utilities.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/Utilities.java
@@ -16,27 +16,24 @@
 
 package com.android.systemui.navigationbar.gestural;
 
+import static android.view.MotionEvent.AXIS_GESTURE_SWIPE_FINGER_COUNT;
 import static android.view.MotionEvent.CLASSIFICATION_MULTI_FINGER_SWIPE;
+import static android.view.MotionEvent.CLASSIFICATION_TWO_FINGER_SWIPE;
 
 import android.view.MotionEvent;
 
 public final class Utilities {
 
-    public static boolean isTrackpadMultiFingerSwipe(boolean isTrackpadGestureFeaturesEnabled,
+    public static boolean isTrackpadScroll(boolean isTrackpadGestureFeaturesEnabled,
             MotionEvent event) {
         return isTrackpadGestureFeaturesEnabled
-                && event.getClassification() == CLASSIFICATION_MULTI_FINGER_SWIPE;
+                && event.getClassification() == CLASSIFICATION_TWO_FINGER_SWIPE;
     }
 
     public static boolean isTrackpadThreeFingerSwipe(boolean isTrackpadGestureFeaturesEnabled,
             MotionEvent event) {
-        return isTrackpadMultiFingerSwipe(isTrackpadGestureFeaturesEnabled, event)
-                && event.getPointerCount() == 3;
-    }
-
-    public static boolean isTrackpadFourFingerSwipe(boolean isTrackpadGestureFeaturesEnabled,
-            MotionEvent event) {
-        return isTrackpadMultiFingerSwipe(isTrackpadGestureFeaturesEnabled, event)
-                && event.getPointerCount() == 4;
+        return isTrackpadGestureFeaturesEnabled
+                && event.getClassification() == CLASSIFICATION_MULTI_FINGER_SWIPE
+                && event.getAxisValue(AXIS_GESTURE_SWIPE_FINGER_COUNT) == 3;
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/INoteTaskBubblesService.aidl b/packages/SystemUI/src/com/android/systemui/notetask/INoteTaskBubblesService.aidl
new file mode 100644
index 0000000..3e947d9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/notetask/INoteTaskBubblesService.aidl
@@ -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.notetask;
+
+import android.content.Intent;
+import android.graphics.drawable.Icon;
+import android.os.UserHandle;
+
+/** A service to help with controlling the state of notes app bubble through the system user. */
+interface INoteTaskBubblesService {
+
+    boolean areBubblesAvailable();
+
+    void showOrHideAppBubble(in Intent intent, in UserHandle userHandle, in Icon icon);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskBubblesController.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskBubblesController.kt
new file mode 100644
index 0000000..ec205f8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskBubblesController.kt
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.notetask
+
+import android.app.Service
+import android.content.Context
+import android.content.Intent
+import android.graphics.drawable.Icon
+import android.os.IBinder
+import android.os.UserHandle
+import com.android.internal.infra.ServiceConnector
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.log.DebugLogger.debugLog
+import com.android.wm.shell.bubbles.Bubbles
+import java.util.Optional
+import javax.inject.Inject
+import kotlin.coroutines.resume
+import kotlin.coroutines.suspendCoroutine
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.withContext
+
+/**
+ * A utility class to help interact with [Bubbles] as system user. The SysUI instance running as
+ * system user is the only instance that has the instance of [Bubbles] that manages the notes app
+ * bubble for all users.
+ *
+ * <p>Note: This class is made overridable so that a fake can be created for as mocking suspending
+ * functions is not supported by the Android tree's version of mockito.
+ */
+@SysUISingleton
+open class NoteTaskBubblesController
+@Inject
+constructor(
+    @Application private val context: Context,
+    @Background private val bgDispatcher: CoroutineDispatcher
+) {
+
+    private val serviceConnector: ServiceConnector<INoteTaskBubblesService> =
+        ServiceConnector.Impl(
+            context,
+            Intent(context, NoteTaskBubblesService::class.java),
+            Context.BIND_AUTO_CREATE or Context.BIND_WAIVE_PRIORITY or Context.BIND_NOT_VISIBLE,
+            UserHandle.USER_SYSTEM,
+            INoteTaskBubblesService.Stub::asInterface
+        )
+
+    /** Returns whether notes app bubble is supported. */
+    open suspend fun areBubblesAvailable(): Boolean =
+        withContext(bgDispatcher) {
+            suspendCoroutine { continuation ->
+                serviceConnector
+                    .postForResult { it.areBubblesAvailable() }
+                    .whenComplete { available, error ->
+                        if (error != null) {
+                            debugLog(error = error) { "Failed to query Bubbles as system user." }
+                        }
+                        continuation.resume(available ?: false)
+                    }
+            }
+        }
+
+    /** Calls the [Bubbles.showOrHideAppBubble] API as [UserHandle.USER_SYSTEM]. */
+    open suspend fun showOrHideAppBubble(
+        intent: Intent,
+        userHandle: UserHandle,
+        icon: Icon
+    ) {
+        withContext(bgDispatcher) {
+            serviceConnector
+                .post { it.showOrHideAppBubble(intent, userHandle, icon) }
+                .whenComplete { _, error ->
+                    if (error != null) {
+                        debugLog(error = error) {
+                            "Failed to show notes app bubble for intent $intent, " +
+                                "user $userHandle, and icon $icon."
+                        }
+                    } else {
+                        debugLog {
+                            "Call to show notes app bubble for intent $intent, " +
+                                "user $userHandle, and icon $icon successful."
+                        }
+                    }
+                }
+        }
+    }
+
+    /**
+     * A helper service to call [Bubbles] APIs that should always be called from the system user
+     * instance of SysUI.
+     *
+     * <p>Note: This service always runs in the SysUI process running on the system user
+     * irrespective of which user started the service. This is required so that the correct instance
+     * of {@link Bubbles} is injected. This is set via attribute {@code android:singleUser=”true”}
+     * in AndroidManifest.
+     */
+    class NoteTaskBubblesService
+    @Inject
+    constructor(private val mOptionalBubbles: Optional<Bubbles>) : Service() {
+
+        override fun onBind(intent: Intent): IBinder {
+            return object : INoteTaskBubblesService.Stub() {
+                override fun areBubblesAvailable() = mOptionalBubbles.isPresent
+
+                override fun showOrHideAppBubble(
+                    intent: Intent,
+                    userHandle: UserHandle,
+                    icon: Icon
+                ) {
+                    mOptionalBubbles.ifPresentOrElse(
+                        { bubbles -> bubbles.showOrHideAppBubble(intent, userHandle, icon) },
+                        {
+                            debugLog {
+                                "Failed to show or hide bubble for intent $intent," +
+                                    "user $user, and icon $icon as bubble is empty."
+                            }
+                        }
+                    )
+                }
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
index ccfbaf1..48790c2 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
@@ -33,25 +33,26 @@
 import android.graphics.drawable.Icon
 import android.os.UserHandle
 import android.os.UserManager
+import android.provider.Settings
 import android.widget.Toast
 import androidx.annotation.VisibleForTesting
 import com.android.systemui.R
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.devicepolicy.areKeyguardShortcutsDisabled
 import com.android.systemui.log.DebugLogger.debugLog
 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.notetask.shortcut.LaunchNoteTaskManagedProfileProxyActivity
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.shared.system.ActivityManagerKt.isInForeground
-import com.android.systemui.util.kotlin.getOrNull
+import com.android.systemui.util.settings.SecureSettings
 import com.android.wm.shell.bubbles.Bubble
-import com.android.wm.shell.bubbles.Bubbles
 import com.android.wm.shell.bubbles.Bubbles.BubbleExpandListener
-import java.util.Optional
 import java.util.concurrent.atomic.AtomicReference
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
 
 /**
  * Entry point for creating and managing note.
@@ -69,13 +70,15 @@
     private val shortcutManager: ShortcutManager,
     private val resolver: NoteTaskInfoResolver,
     private val eventLogger: NoteTaskEventLogger,
-    private val optionalBubbles: Optional<Bubbles>,
+    private val noteTaskBubblesController: NoteTaskBubblesController,
     private val userManager: UserManager,
     private val keyguardManager: KeyguardManager,
     private val activityManager: ActivityManager,
     @NoteTaskEnabledKey private val isEnabled: Boolean,
     private val devicePolicyManager: DevicePolicyManager,
     private val userTracker: UserTracker,
+    private val secureSettings: SecureSettings,
+    @Application private val applicationScope: CoroutineScope
 ) {
 
     @VisibleForTesting val infoReference = AtomicReference<NoteTaskInfo?>()
@@ -100,18 +103,6 @@
         }
     }
 
-    /** Starts [LaunchNoteTaskProxyActivity] on the given [user]. */
-    fun startNoteTaskProxyActivityForUser(user: UserHandle) {
-        context.startActivityAsUser(
-            Intent().apply {
-                component =
-                    ComponentName(context, LaunchNoteTaskManagedProfileProxyActivity::class.java)
-                addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
-            },
-            user
-        )
-    }
-
     /** Starts the notes role setting. */
     fun startNotesRoleSetting(activityContext: Context, entryPoint: NoteTaskEntryPoint?) {
         val user =
@@ -146,7 +137,7 @@
             userTracker.userProfiles.firstOrNull { userManager.isManagedProfile(it.id) }?.userHandle
                 ?: userTracker.userHandle
         } else {
-            userTracker.userHandle
+            secureSettings.preferredUser
         }
 
     /**
@@ -175,7 +166,19 @@
     ) {
         if (!isEnabled) return
 
-        val bubbles = optionalBubbles.getOrNull() ?: return
+        applicationScope.launch { awaitShowNoteTaskAsUser(entryPoint, user) }
+    }
+
+    private suspend fun awaitShowNoteTaskAsUser(
+        entryPoint: NoteTaskEntryPoint,
+        user: UserHandle,
+    ) {
+        if (!isEnabled) return
+
+        if (!noteTaskBubblesController.areBubblesAvailable()) {
+            debugLog { "Bubbles not available in the system user SysUI instance" }
+            return
+        }
 
         // TODO(b/249954038): We should handle direct boot (isUserUnlocked). For now, we do nothing.
         if (!userManager.isUserUnlocked) return
@@ -210,7 +213,7 @@
                     val intent = createNoteTaskIntent(info)
                     val icon =
                         Icon.createWithResource(context, R.drawable.ic_note_task_shortcut_widget)
-                    bubbles.showOrHideAppBubble(intent, user, icon)
+                    noteTaskBubblesController.showOrHideAppBubble(intent, user, icon)
                     // App bubble logging happens on `onBubbleExpandChanged`.
                     debugLog { "onShowNoteTask - opened as app bubble: $info" }
                 }
@@ -284,15 +287,55 @@
     }
 
     /**
+     * Like [updateNoteTaskAsUser] but automatically apply to the current user and all its work
+     * profiles.
+     *
+     * @see updateNoteTaskAsUser
+     * @see UserTracker.userHandle
+     * @see UserTracker.userProfiles
+     */
+    fun updateNoteTaskForCurrentUserAndManagedProfiles() {
+        updateNoteTaskAsUser(userTracker.userHandle)
+        for (profile in userTracker.userProfiles) {
+            if (userManager.isManagedProfile(profile.id)) {
+                updateNoteTaskAsUser(profile.userHandle)
+            }
+        }
+    }
+
+    /**
      * Updates all [NoteTaskController] related information, including but not exclusively the
      * widget shortcut created by the [user] - by default it will use the current user.
      *
+     * If the user is not current user, the update will be dispatched to run in that user's process.
+     *
      * Keep in mind the shortcut API has a
      * [rate limiting](https://developer.android.com/develop/ui/views/launch/shortcuts/managing-shortcuts#rate-limiting)
      * and may not be updated in real-time. To reduce the chance of stale shortcuts, we run the
      * function during System UI initialization.
      */
     fun updateNoteTaskAsUser(user: UserHandle) {
+        if (!userManager.isUserUnlocked(user)) {
+            debugLog { "updateNoteTaskAsUser call but user locked: user=$user" }
+            return
+        }
+
+        if (user == userTracker.userHandle) {
+            updateNoteTaskAsUserInternal(user)
+        } else {
+            // TODO(b/278729185): Replace fire and forget service with a bounded service.
+            val intent = NoteTaskControllerUpdateService.createIntent(context)
+            context.startServiceAsUser(intent, user)
+        }
+    }
+
+    @InternalNoteTaskApi
+    fun updateNoteTaskAsUserInternal(user: UserHandle) {
+        if (!userManager.isUserUnlocked(user)) {
+            debugLog { "updateNoteTaskAsUserInternal call but user locked: user=$user" }
+            return
+        }
+
         val packageName = roleManager.getDefaultRoleHolderAsUser(ROLE_NOTES, user)
         val hasNotesRoleHolder = isEnabled && !packageName.isNullOrEmpty()
 
@@ -310,20 +353,20 @@
     /** @see OnRoleHoldersChangedListener */
     fun onRoleHoldersChanged(roleName: String, user: UserHandle) {
         if (roleName != ROLE_NOTES) return
-        if (!userManager.isUserUnlocked(user)) {
-            debugLog { "onRoleHoldersChanged call but user locked: role=$roleName, user=$user" }
-            return
-        }
 
-        if (user == userTracker.userHandle) {
-            updateNoteTaskAsUser(user)
-        } else {
-            // TODO(b/278729185): Replace fire and forget service with a bounded service.
-            val intent = NoteTaskControllerUpdateService.createIntent(context)
-            context.startServiceAsUser(intent, user)
-        }
+        updateNoteTaskAsUser(user)
     }
 
+    private val SecureSettings.preferredUser: UserHandle
+        get() {
+            val userId =
+                secureSettings.getInt(
+                    Settings.Secure.DEFAULT_NOTE_TASK_PROFILE,
+                    userTracker.userHandle.identifier,
+                )
+            return UserHandle.of(userId)
+        }
+
     companion object {
         val TAG = NoteTaskController::class.simpleName.orEmpty()
 
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskControllerUpdateService.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskControllerUpdateService.kt
index 26b35cc..3e352af 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskControllerUpdateService.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskControllerUpdateService.kt
@@ -44,7 +44,7 @@
     override fun onCreate() {
         super.onCreate()
         // TODO(b/278729185): Replace fire and forget service with a bounded service.
-        controller.updateNoteTaskAsUser(user)
+        controller.updateNoteTaskAsUserInternal(user)
         stopSelf()
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
index 221ff65..fe1034a 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
@@ -15,7 +15,10 @@
  */
 package com.android.systemui.notetask
 
+import android.app.role.OnRoleHoldersChangedListener
 import android.app.role.RoleManager
+import android.content.Context
+import android.content.pm.UserInfo
 import android.os.UserHandle
 import android.view.KeyEvent
 import android.view.KeyEvent.KEYCODE_N
@@ -54,6 +57,7 @@
         initializeHandleSystemKey()
         initializeOnRoleHoldersChanged()
         initializeOnUserUnlocked()
+        initializeUserTracker()
     }
 
     /**
@@ -79,7 +83,7 @@
     private fun initializeOnRoleHoldersChanged() {
         roleManager.addOnRoleHoldersChangedListenerAsUser(
             backgroundExecutor,
-            controller::onRoleHoldersChanged,
+            callbacks,
             UserHandle.ALL,
         )
     }
@@ -93,18 +97,41 @@
      */
     private fun initializeOnUserUnlocked() {
         if (keyguardUpdateMonitor.isUserUnlocked(userTracker.userId)) {
-            controller.setNoteTaskShortcutEnabled(true, userTracker.userHandle)
-        } else {
-            keyguardUpdateMonitor.registerCallback(onUserUnlockedCallback)
+            controller.updateNoteTaskForCurrentUserAndManagedProfiles()
         }
+        keyguardUpdateMonitor.registerCallback(callbacks)
     }
 
-    // KeyguardUpdateMonitor.registerCallback uses a weak reference, so we need a hard reference.
-    private val onUserUnlockedCallback =
-        object : KeyguardUpdateMonitorCallback() {
+    private fun initializeUserTracker() {
+        userTracker.addCallback(callbacks, backgroundExecutor)
+    }
+
+    // Some callbacks use a weak reference, so we play safe and keep a hard reference to them all.
+    private val callbacks =
+        object :
+            KeyguardUpdateMonitorCallback(),
+            CommandQueue.Callbacks,
+            UserTracker.Callback,
+            OnRoleHoldersChangedListener {
+
+            override fun handleSystemKey(key: KeyEvent) {
+                key.toNoteTaskEntryPointOrNull()?.let(controller::showNoteTask)
+            }
+
+            override fun onRoleHoldersChanged(roleName: String, user: UserHandle) {
+                controller.onRoleHoldersChanged(roleName, user)
+            }
+
             override fun onUserUnlocked() {
-                controller.setNoteTaskShortcutEnabled(true, userTracker.userHandle)
-                keyguardUpdateMonitor.removeCallback(this)
+                controller.updateNoteTaskForCurrentUserAndManagedProfiles()
+            }
+
+            override fun onUserChanged(newUser: Int, userContext: Context) {
+                controller.updateNoteTaskForCurrentUserAndManagedProfiles()
+            }
+
+            override fun onProfilesChanged(profiles: List<UserInfo>) {
+                controller.updateNoteTaskForCurrentUserAndManagedProfiles()
             }
         }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt
index 109cfee..c0e688f 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt
@@ -26,7 +26,6 @@
 import com.android.systemui.notetask.quickaffordance.NoteTaskQuickAffordanceModule
 import com.android.systemui.notetask.shortcut.CreateNoteTaskShortcutActivity
 import com.android.systemui.notetask.shortcut.LaunchNoteTaskActivity
-import com.android.systemui.notetask.shortcut.LaunchNoteTaskManagedProfileProxyActivity
 import dagger.Binds
 import dagger.Module
 import dagger.Provides
@@ -40,12 +39,12 @@
     @[Binds IntoMap ClassKey(NoteTaskControllerUpdateService::class)]
     fun NoteTaskControllerUpdateService.bindNoteTaskControllerUpdateService(): Service
 
+    @[Binds IntoMap ClassKey(NoteTaskBubblesController.NoteTaskBubblesService::class)]
+    fun NoteTaskBubblesController.NoteTaskBubblesService.bindNoteTaskBubblesService(): Service
+
     @[Binds IntoMap ClassKey(LaunchNoteTaskActivity::class)]
     fun LaunchNoteTaskActivity.bindNoteTaskLauncherActivity(): Activity
 
-    @[Binds IntoMap ClassKey(LaunchNoteTaskManagedProfileProxyActivity::class)]
-    fun LaunchNoteTaskManagedProfileProxyActivity.bindNoteTaskLauncherProxyActivity(): Activity
-
     @[Binds IntoMap ClassKey(LaunchNotesRoleSettingsTrampolineActivity::class)]
     fun LaunchNotesRoleSettingsTrampolineActivity.bindLaunchNotesRoleSettingsTrampolineActivity():
         Activity
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskRoleManagerExt.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskRoleManagerExt.kt
index 754c365..4d30634 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskRoleManagerExt.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskRoleManagerExt.kt
@@ -19,6 +19,7 @@
 import android.app.role.RoleManager
 import android.app.role.RoleManager.ROLE_NOTES
 import android.content.Context
+import android.content.pm.PackageManager
 import android.content.pm.ShortcutInfo
 import android.graphics.drawable.Icon
 import android.os.PersistableBundle
@@ -42,21 +43,42 @@
         context: Context,
         user: UserHandle,
     ): ShortcutInfo {
+        val packageName = getDefaultRoleHolderAsUser(ROLE_NOTES, user)
+
         val extras = PersistableBundle()
-        getDefaultRoleHolderAsUser(ROLE_NOTES, user)?.let { packageName ->
+        if (packageName != null) {
             // Set custom app badge using the icon from ROLES_NOTES default app.
             extras.putString(NoteTaskController.EXTRA_SHORTCUT_BADGE_OVERRIDE_PACKAGE, packageName)
         }
 
+        val shortLabel = context.getString(R.string.note_task_button_label)
+
+        val applicationLabel = context.packageManager.getApplicationLabel(packageName)
+        val longLabel =
+            if (applicationLabel == null) {
+                shortLabel
+            } else {
+                context.getString(
+                    R.string.note_task_shortcut_long_label,
+                    applicationLabel,
+                )
+            }
+
         val icon = Icon.createWithResource(context, R.drawable.ic_note_task_shortcut_widget)
 
         return ShortcutInfo.Builder(context, NoteTaskController.SHORTCUT_ID)
             .setIntent(LaunchNoteTaskActivity.createIntent(context))
             .setActivity(LaunchNoteTaskActivity.createComponent(context))
-            .setShortLabel(context.getString(R.string.note_task_button_label))
+            .setShortLabel(shortLabel)
+            .setLongLabel(longLabel)
             .setLongLived(true)
             .setIcon(icon)
             .setExtras(extras)
             .build()
     }
+
+    private fun PackageManager.getApplicationLabel(packageName: String?): String? =
+        runCatching { getApplicationInfo(packageName, /* flags= */ 0)!! }
+            .getOrNull()
+            ?.let { info -> getApplicationLabel(info).toString() }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt b/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt
index 7ef149d..d00a79e 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt
@@ -20,67 +20,24 @@
 import android.content.Context
 import android.content.Intent
 import android.os.Bundle
-import android.os.UserHandle
-import android.os.UserManager
 import androidx.activity.ComponentActivity
-import com.android.systemui.log.DebugLogger.debugLog
 import com.android.systemui.notetask.NoteTaskController
 import com.android.systemui.notetask.NoteTaskEntryPoint
-import com.android.systemui.settings.UserTracker
 import javax.inject.Inject
 
 /** Activity responsible for launching the note experience, and finish. */
-class LaunchNoteTaskActivity
-@Inject
-constructor(
-    private val controller: NoteTaskController,
-    private val userManager: UserManager,
-    private val userTracker: UserTracker,
-) : ComponentActivity() {
+class LaunchNoteTaskActivity @Inject constructor(private val controller: NoteTaskController) :
+    ComponentActivity() {
 
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
-
-        // Under the hood, notes app shortcuts are shown in a floating window, called Bubble.
-        // Bubble API is only available in the main user but not work profile.
-        //
-        // On devices with work profile (WP), SystemUI provides both personal notes app shortcuts &
-        // work profile notes app shortcuts. In order to make work profile notes app shortcuts to
-        // show in Bubble, a few redirections across users are required:
-        // 1. When `LaunchNoteTaskActivity` is started in the work profile user, we launch
-        //    `LaunchNoteTaskManagedProfileProxyActivity` on the main user, which has access to the
-        //    Bubble API.
-        // 2. `LaunchNoteTaskManagedProfileProxyActivity` calls `Bubble#showOrHideAppBubble` with
-        //     the work profile user ID.
-        // 3. Bubble renders the work profile notes app activity in a floating window, which is
-        //    hosted in the main user.
-        //
-        //            WP                                main user
-        //  ------------------------          -------------------------------------------
-        // | LaunchNoteTaskActivity |   ->   | LaunchNoteTaskManagedProfileProxyActivity |
-        //  ------------------------          -------------------------------------------
-        //                                                        |
-        //                 main user                              |
-        //         ----------------------------                   |
-        //        | Bubble#showOrHideAppBubble |   <--------------
-        //        |      (with WP user ID)     |
-        //         ----------------------------
-        val mainUser: UserHandle? = userManager.mainUser
-        if (userManager.isManagedProfile) {
-            if (mainUser == null) {
-                debugLog { "Can't find the main user. Skipping the notes app launch." }
+        val entryPoint =
+            if (isInMultiWindowMode) {
+                NoteTaskEntryPoint.WIDGET_PICKER_SHORTCUT_IN_MULTI_WINDOW_MODE
             } else {
-                controller.startNoteTaskProxyActivityForUser(mainUser)
+                NoteTaskEntryPoint.WIDGET_PICKER_SHORTCUT
             }
-        } else {
-            val entryPoint =
-                if (isInMultiWindowMode) {
-                    NoteTaskEntryPoint.WIDGET_PICKER_SHORTCUT_IN_MULTI_WINDOW_MODE
-                } else {
-                    NoteTaskEntryPoint.WIDGET_PICKER_SHORTCUT
-                }
-            controller.showNoteTask(entryPoint)
-        }
+        controller.showNoteTaskAsUser(entryPoint, user)
         finish()
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskManagedProfileProxyActivity.kt b/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskManagedProfileProxyActivity.kt
deleted file mode 100644
index 3259b0d..0000000
--- a/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskManagedProfileProxyActivity.kt
+++ /dev/null
@@ -1,67 +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.notetask.shortcut
-
-import android.os.Build
-import android.os.Bundle
-import android.os.UserManager
-import android.util.Log
-import androidx.activity.ComponentActivity
-import com.android.systemui.notetask.NoteTaskController
-import com.android.systemui.notetask.NoteTaskEntryPoint
-import com.android.systemui.settings.UserTracker
-import javax.inject.Inject
-
-/**
- * An internal proxy activity that starts notes app in the work profile.
- *
- * If there is no work profile, this activity finishes gracefully.
- *
- * This activity MUST NOT be exported because that would expose the INTERACT_ACROSS_USER privilege
- * to any apps.
- */
-class LaunchNoteTaskManagedProfileProxyActivity
-@Inject
-constructor(
-    private val controller: NoteTaskController,
-    private val userTracker: UserTracker,
-    private val userManager: UserManager,
-) : ComponentActivity() {
-
-    override fun onCreate(savedInstanceState: Bundle?) {
-        super.onCreate(savedInstanceState)
-
-        val managedProfileUser =
-            userTracker.userProfiles.firstOrNull { userManager.isManagedProfile(it.id) }
-
-        if (managedProfileUser == null) {
-            logDebug { "Fail to find the work profile user." }
-        } else {
-            controller.showNoteTaskAsUser(
-                entryPoint = NoteTaskEntryPoint.WIDGET_PICKER_SHORTCUT,
-                user = managedProfileUser.userHandle
-            )
-        }
-        finish()
-    }
-}
-
-private inline fun logDebug(message: () -> String) {
-    if (Build.IS_DEBUGGABLE) {
-        Log.d(NoteTaskController.TAG, message())
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/power/data/repository/PowerRepository.kt b/packages/SystemUI/src/com/android/systemui/power/data/repository/PowerRepository.kt
index b2e04bb..69cb611 100644
--- a/packages/SystemUI/src/com/android/systemui/power/data/repository/PowerRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/power/data/repository/PowerRepository.kt
@@ -26,6 +26,8 @@
 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.Application
+import com.android.systemui.util.time.SystemClock
 import javax.inject.Inject
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
@@ -34,13 +36,18 @@
 interface PowerRepository {
     /** Whether the device is interactive. Starts with the current state. */
     val isInteractive: Flow<Boolean>
+
+    /** Wakes up the device. */
+    fun wakeUp(why: String, @PowerManager.WakeReason wakeReason: Int)
 }
 
 @SysUISingleton
 class PowerRepositoryImpl
 @Inject
 constructor(
-    manager: PowerManager,
+    private val manager: PowerManager,
+    @Application private val applicationContext: Context,
+    private val systemClock: SystemClock,
     dispatcher: BroadcastDispatcher,
 ) : PowerRepository {
 
@@ -68,6 +75,14 @@
         awaitClose { dispatcher.unregisterReceiver(receiver) }
     }
 
+    override fun wakeUp(why: String, wakeReason: Int) {
+        manager.wakeUp(
+            systemClock.uptimeMillis(),
+            wakeReason,
+            "${applicationContext.packageName}:$why",
+        )
+    }
+
     companion object {
         private const val TAG = "PowerRepository"
     }
diff --git a/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt b/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt
index 3f799f7..809edc0 100644
--- a/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt
@@ -17,8 +17,13 @@
 
 package com.android.systemui.power.domain.interactor
 
+import android.os.PowerManager
+import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.power.data.repository.PowerRepository
+import com.android.systemui.statusbar.phone.ScreenOffAnimationController
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
 
@@ -27,8 +32,58 @@
 class PowerInteractor
 @Inject
 constructor(
-    repository: PowerRepository,
+    private val repository: PowerRepository,
+    private val keyguardRepository: KeyguardRepository,
+    private val falsingCollector: FalsingCollector,
+    private val screenOffAnimationController: ScreenOffAnimationController,
+    private val statusBarStateController: StatusBarStateController,
 ) {
     /** Whether the screen is on or off. */
     val isInteractive: Flow<Boolean> = repository.isInteractive
+
+    /**
+     * Wakes up the device if the device was dozing.
+     *
+     * @param why a string explaining why we're waking the device for debugging purposes. Should be
+     *   in SCREAMING_SNAKE_CASE.
+     * @param wakeReason the PowerManager-based reason why we're waking the device.
+     */
+    fun wakeUpIfDozing(why: String, @PowerManager.WakeReason wakeReason: Int) {
+        if (
+            statusBarStateController.isDozing && screenOffAnimationController.allowWakeUpIfDozing()
+        ) {
+            repository.wakeUp(why, wakeReason)
+            falsingCollector.onScreenOnFromTouch()
+        }
+    }
+
+    /**
+     * Wakes up the device if the device was dozing or going to sleep in order to display a
+     * full-screen intent.
+     */
+    fun wakeUpForFullScreenIntent() {
+        if (
+            keyguardRepository.wakefulness.value.isStartingToSleep() ||
+                statusBarStateController.isDozing
+        ) {
+            repository.wakeUp(why = FSI_WAKE_WHY, wakeReason = PowerManager.WAKE_REASON_APPLICATION)
+        }
+    }
+
+    /**
+     * Wakes up the device if dreaming with a screensaver.
+     *
+     * @param why a string explaining why we're waking the device for debugging purposes. Should be
+     *   in SCREAMING_SNAKE_CASE.
+     * @param wakeReason the PowerManager-based reason why we're waking the device.
+     */
+    fun wakeUpIfDreaming(why: String, @PowerManager.WakeReason wakeReason: Int) {
+        if (statusBarStateController.isDreaming) {
+            repository.wakeUp(why, wakeReason)
+        }
+    }
+
+    companion object {
+        private const val FSI_WAKE_WHY = "full_screen_intent"
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt
index 79167f2..310d234 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt
@@ -15,6 +15,8 @@
 package com.android.systemui.privacy
 
 import android.content.Context
+import android.content.pm.ActivityInfo
+import android.content.res.Configuration
 import android.util.AttributeSet
 import android.view.Gravity.CENTER_VERTICAL
 import android.view.Gravity.END
@@ -25,7 +27,7 @@
 import android.widget.LinearLayout
 import com.android.settingslib.Utils
 import com.android.systemui.R
-import com.android.systemui.animation.LaunchableFrameLayout
+import com.android.systemui.animation.view.LaunchableFrameLayout
 import com.android.systemui.statusbar.events.BackgroundAnimatableView
 
 class OngoingPrivacyChip @JvmOverloads constructor(
@@ -35,6 +37,7 @@
     defStyleRes: Int = 0
 ) : LaunchableFrameLayout(context, attrs, defStyleAttrs, defStyleRes), BackgroundAnimatableView {
 
+    private var configuration: Configuration
     private var iconMargin = 0
     private var iconSize = 0
     private var iconColor = 0
@@ -54,6 +57,7 @@
         clipChildren = true
         clipToPadding = true
         iconsContainer = requireViewById(R.id.icons_container)
+        configuration = Configuration(context.resources.configuration)
         updateResources()
     }
 
@@ -102,6 +106,17 @@
                 R.string.ongoing_privacy_chip_content_multiple_apps, typesText)
     }
 
+    override fun onConfigurationChanged(newConfig: Configuration?) {
+        super.onConfigurationChanged(newConfig)
+        if (newConfig != null) {
+            val diff = newConfig.diff(configuration)
+            configuration.setTo(newConfig)
+            if (diff.and(ActivityInfo.CONFIG_DENSITY.or(ActivityInfo.CONFIG_FONT_SCALE)) != 0) {
+                updateResources()
+            }
+        }
+    }
+
     private fun updateResources() {
         iconMargin = context.resources
                 .getDimensionPixelSize(R.dimen.ongoing_appops_chip_icon_margin)
@@ -110,8 +125,11 @@
         iconColor =
                 Utils.getColorAttrDefaultColor(context, com.android.internal.R.attr.colorPrimary)
 
+        val height = context.resources
+                .getDimensionPixelSize(R.dimen.ongoing_appops_chip_height)
         val padding = context.resources
                 .getDimensionPixelSize(R.dimen.ongoing_appops_chip_side_padding)
+        iconsContainer.layoutParams.height = height
         iconsContainer.setPaddingRelative(padding, 0, padding, 0)
         iconsContainer.background = context.getDrawable(R.drawable.statusbar_privacy_chip_bg)
     }
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/logging/PrivacyLogger.kt b/packages/SystemUI/src/com/android/systemui/privacy/logging/PrivacyLogger.kt
index e56106d..f934346 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/logging/PrivacyLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/logging/PrivacyLogger.kt
@@ -20,8 +20,8 @@
 import android.permission.PermissionGroupUsage
 import com.android.systemui.log.dagger.PrivacyLog
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
-import com.android.systemui.log.LogMessage
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.core.LogMessage
 import com.android.systemui.privacy.PrivacyDialog
 import com.android.systemui.privacy.PrivacyItem
 import java.util.Locale
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/television/TvPrivacyChipsController.java b/packages/SystemUI/src/com/android/systemui/privacy/television/TvPrivacyChipsController.java
index 1c47799..5e4c797 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/television/TvPrivacyChipsController.java
+++ b/packages/SystemUI/src/com/android/systemui/privacy/television/TvPrivacyChipsController.java
@@ -348,9 +348,16 @@
             return;
         }
 
-        TransitionManager.beginDelayedTransition(mChipsContainer, mCollapseTransition);
+        boolean hasExpandedChip = false;
         for (PrivacyItemsChip chip : mChips) {
-            chip.collapse();
+            hasExpandedChip |= chip.isExpanded();
+        }
+
+        if (mChipsContainer != null && hasExpandedChip) {
+            TransitionManager.beginDelayedTransition(mChipsContainer, mCollapseTransition);
+            for (PrivacyItemsChip chip : mChips) {
+                chip.collapse();
+            }
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index b8c2fad..a2b2a89 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -52,7 +52,6 @@
 import com.android.systemui.compose.ComposeFacade;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
 import com.android.systemui.media.controls.ui.MediaHost;
 import com.android.systemui.plugins.qs.QS;
 import com.android.systemui.plugins.qs.QSContainerController;
@@ -245,7 +244,7 @@
         mQSContainerImplController = qsFragmentComponent.getQSContainerImplController();
         mQSContainerImplController.init();
         mContainer = mQSContainerImplController.getView();
-        mDumpManager.registerDumpable(mContainer.getClass().getName(), mContainer);
+        mDumpManager.registerDumpable(mContainer.getClass().getSimpleName(), mContainer);
 
         mQSAnimator = qsFragmentComponent.getQSAnimator();
         mQSSquishinessController = qsFragmentComponent.getQSSquishinessController();
@@ -318,7 +317,7 @@
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-        mDumpManager.registerDumpable(getClass().getName(), this);
+        mDumpManager.registerDumpable(getClass().getSimpleName(), this);
     }
 
     @Override
@@ -333,9 +332,9 @@
         }
         mScrollListener = null;
         if (mContainer != null) {
-            mDumpManager.unregisterDumpable(mContainer.getClass().getName());
+            mDumpManager.unregisterDumpable(mContainer.getClass().getSimpleName());
         }
-        mDumpManager.unregisterDumpable(getClass().getName());
+        mDumpManager.unregisterDumpable(getClass().getSimpleName());
         mListeningAndVisibilityLifecycleOwner.destroy();
     }
 
@@ -755,8 +754,7 @@
             // Alpha progress should be linear on lockscreen shade expansion.
             return progress;
         }
-        if (mIsSmallScreen || !mFeatureFlags.isEnabled(
-                Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION)) {
+        if (mIsSmallScreen) {
             return ShadeInterpolation.getContentAlpha(progress);
         } else {
             return mLargeScreenShadeInterpolator.getQsAlpha(progress);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragmentDisableFlagsLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/QSFragmentDisableFlagsLogger.kt
index cd52ec2..6563e42 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragmentDisableFlagsLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragmentDisableFlagsLogger.kt
@@ -2,7 +2,7 @@
 
 import com.android.systemui.log.dagger.QSFragmentDisableLog
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import com.android.systemui.statusbar.disableflags.DisableFlagsLogger
 import javax.inject.Inject
 
@@ -34,7 +34,6 @@
             },
             {
                 disableFlagsLogger.getDisableFlagsString(
-                    old = null,
                     new = DisableFlagsLogger.DisableState(int1, int2),
                     newAfterLocalModification =
                         DisableFlagsLogger.DisableState(long1.toInt(), long2.toInt())
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
index 83b373d..856a92e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
@@ -58,6 +58,7 @@
     private final BrightnessSliderController mBrightnessSliderController;
     private final BrightnessMirrorHandler mBrightnessMirrorHandler;
     private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+    private boolean mListening;
 
     private View.OnTouchListener mTileLayoutTouchListener = new View.OnTouchListener() {
         @Override
@@ -159,12 +160,15 @@
     public void setListening(boolean listening, boolean expanded) {
         setListening(listening && expanded);
 
-        // Set the listening as soon as the QS fragment starts listening regardless of the
-        //expansion, so it will update the current brightness before the slider is visible.
-        if (listening) {
-            mBrightnessController.registerCallbacks();
-        } else {
-            mBrightnessController.unregisterCallbacks();
+        if (listening != mListening) {
+            mListening = listening;
+            // Set the listening as soon as the QS fragment starts listening regardless of the
+            //expansion, so it will update the current brightness before the slider is visible.
+            if (listening) {
+                mBrightnessController.registerCallbacks();
+            } else {
+                mBrightnessController.unregisterCallbacks();
+            }
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
index 868dbfa..432147f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
@@ -52,8 +52,8 @@
 import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor;
 import com.android.systemui.settings.UserFileManager;
 import com.android.systemui.settings.UserTracker;
+import com.android.systemui.shade.ShadeController;
 import com.android.systemui.statusbar.phone.AutoTileManager;
-import com.android.systemui.statusbar.phone.CentralSurfaces;
 import com.android.systemui.tuner.TunerService;
 import com.android.systemui.tuner.TunerService.Tunable;
 import com.android.systemui.util.settings.SecureSettings;
@@ -66,7 +66,6 @@
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Objects;
-import java.util.Optional;
 import java.util.Set;
 import java.util.concurrent.Executor;
 import java.util.function.Predicate;
@@ -108,7 +107,7 @@
     private AutoTileManager mAutoTiles;
     private final ArrayList<QSFactory> mQsFactories = new ArrayList<>();
     private int mCurrentUser;
-    private final Optional<CentralSurfaces> mCentralSurfacesOptional;
+    private final ShadeController mShadeController;
     private Context mUserContext;
     private UserTracker mUserTracker;
     private SecureSettings mSecureSettings;
@@ -129,7 +128,7 @@
             PluginManager pluginManager,
             TunerService tunerService,
             Provider<AutoTileManager> autoTiles,
-            Optional<CentralSurfaces> centralSurfacesOptional,
+            ShadeController shadeController,
             QSLogger qsLogger,
             UserTracker userTracker,
             SecureSettings secureSettings,
@@ -148,11 +147,12 @@
         mUserFileManager = userFileManager;
         mFeatureFlags = featureFlags;
 
-        mCentralSurfacesOptional = centralSurfacesOptional;
+        mShadeController = shadeController;
 
         mQsFactories.add(defaultFactory);
         pluginManager.addPluginListener(this, QSFactory.class, true);
         mUserTracker = userTracker;
+        mCurrentUser = userTracker.getUserId();
         mSecureSettings = secureSettings;
         mCustomTileStatePersister = customTileStatePersister;
 
@@ -162,7 +162,9 @@
             // finishes before creating any tiles.
             tunerService.addTunable(this, TILES_SETTING);
             // AutoTileManager can modify mTiles so make sure mTiles has already been initialized.
-            mAutoTiles = autoTiles.get();
+            if (!mFeatureFlags.isEnabled(Flags.QS_PIPELINE_AUTO_ADD)) {
+                mAutoTiles = autoTiles.get();
+            }
         });
     }
 
@@ -209,17 +211,17 @@
 
     @Override
     public void collapsePanels() {
-        mCentralSurfacesOptional.ifPresent(CentralSurfaces::postAnimateCollapsePanels);
+        mShadeController.postAnimateCollapseShade();
     }
 
     @Override
     public void forceCollapsePanels() {
-        mCentralSurfacesOptional.ifPresent(CentralSurfaces::postAnimateForceCollapsePanels);
+        mShadeController.postAnimateForceCollapseShade();
     }
 
     @Override
     public void openPanels() {
-        mCentralSurfacesOptional.ifPresent(CentralSurfaces::postAnimateOpenPanels);
+        mShadeController.postAnimateExpandQs();
     }
 
     @Override
@@ -273,6 +275,13 @@
         if (!TILES_SETTING.equals(key)) {
             return;
         }
+        int currentUser = mUserTracker.getUserId();
+        if (currentUser != mCurrentUser) {
+            mUserContext = mUserTracker.getUserContext();
+            if (mAutoTiles != null) {
+                mAutoTiles.changeUser(UserHandle.of(currentUser));
+            }
+        }
         // Do not process tiles if the flag is enabled.
         if (mFeatureFlags.isEnabled(Flags.QS_PIPELINE_NEW_HOST)) {
             return;
@@ -281,13 +290,6 @@
             newValue = mContext.getResources().getString(R.string.quick_settings_tiles_retail_mode);
         }
         final List<String> tileSpecs = loadTileSpecs(mContext, newValue);
-        int currentUser = mUserTracker.getUserId();
-        if (currentUser != mCurrentUser) {
-            mUserContext = mUserTracker.getUserContext();
-            if (mAutoTiles != null) {
-                mAutoTiles.changeUser(UserHandle.of(currentUser));
-            }
-        }
         if (tileSpecs.equals(mTileSpecs) && currentUser == mCurrentUser) return;
         Log.d(TAG, "Recreating tiles: " + tileSpecs);
         mTiles.entrySet().stream().filter(tile -> !tileSpecs.contains(tile.getKey())).forEach(
diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
index dffe7fd..03de3a0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
@@ -19,9 +19,9 @@
 import static com.android.systemui.qs.dagger.QSFlagsModule.RBC_AVAILABLE;
 
 import android.content.Context;
-import android.hardware.display.NightDisplayListener;
 import android.os.Handler;
 
+import com.android.systemui.dagger.NightDisplayListenerModule;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.media.dagger.MediaModule;
@@ -41,14 +41,14 @@
 import com.android.systemui.statusbar.policy.WalletController;
 import com.android.systemui.util.settings.SecureSettings;
 
-import dagger.Module;
-import dagger.Provides;
-import dagger.multibindings.Multibinds;
-
 import java.util.Map;
 
 import javax.inject.Named;
 
+import dagger.Module;
+import dagger.Provides;
+import dagger.multibindings.Multibinds;
+
 /**
  * Module for QS dependencies
  */
@@ -79,7 +79,7 @@
             HotspotController hotspotController,
             DataSaverController dataSaverController,
             ManagedProfileController managedProfileController,
-            NightDisplayListener nightDisplayListener,
+            NightDisplayListenerModule.Builder nightDisplayListenerBuilder,
             CastController castController,
             ReduceBrightColorsController reduceBrightColorsController,
             DeviceControlsController deviceControlsController,
@@ -95,7 +95,7 @@
                 hotspotController,
                 dataSaverController,
                 managedProfileController,
-                nightDisplayListener,
+                nightDisplayListenerBuilder,
                 castController,
                 reduceBrightColorsController,
                 deviceControlsController,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileRequestDialog.kt b/packages/SystemUI/src/com/android/systemui/qs/external/TileRequestDialog.kt
index 6265b3c..3432628 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileRequestDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileRequestDialog.kt
@@ -18,6 +18,7 @@
 
 import android.content.Context
 import android.graphics.drawable.Icon
+import android.view.ContextThemeWrapper
 import android.view.LayoutInflater
 import android.view.ViewGroup
 import android.widget.TextView
@@ -66,7 +67,8 @@
     }
 
     private fun createTileView(tileData: TileData): QSTileView {
-        val tile = QSTileViewImpl(context, QSIconViewImpl(context), true)
+        val themedContext = ContextThemeWrapper(context, R.style.Theme_SystemUI_QuickSettings)
+        val tile = QSTileViewImpl(themedContext, QSIconViewImpl(themedContext), true)
         val state = QSTile.BooleanState().apply {
             label = tileData.label
             handlesLongClick = false
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt
index 9c9ad33..3c53d77 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt
@@ -244,8 +244,8 @@
 
         val backgroundResource =
             when (model.backgroundColor) {
-                R.attr.offStateColor -> R.drawable.qs_footer_action_circle
-                com.android.internal.R.attr.colorAccent -> R.drawable.qs_footer_action_circle_color
+                R.attr.shadeInactive -> R.drawable.qs_footer_action_circle
+                R.attr.shadeActive -> R.drawable.qs_footer_action_circle_color
                 else -> error("Unsupported icon background resource ${model.backgroundColor}")
             }
         buttonView.setBackgroundResource(backgroundResource)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
index b3596a2..32146b5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
@@ -145,8 +145,12 @@
                 R.drawable.ic_settings,
                 ContentDescription.Resource(R.string.accessibility_quick_settings_settings)
             ),
-            iconTint = null,
-            backgroundColor = R.attr.offStateColor,
+            iconTint =
+                Utils.getColorAttrDefaultColor(
+                    context,
+                    R.attr.onShadeInactiveVariant,
+                ),
+            backgroundColor = R.attr.shadeInactive,
             this::onSettingsButtonClicked,
         )
 
@@ -162,9 +166,9 @@
                 iconTint =
                     Utils.getColorAttrDefaultColor(
                         context,
-                        com.android.internal.R.attr.textColorOnAccent,
+                        R.attr.onShadeActive,
                     ),
-                backgroundColor = com.android.internal.R.attr.colorAccent,
+                backgroundColor = R.attr.shadeActive,
                 this::onPowerButtonClicked,
             )
         } else {
@@ -264,7 +268,7 @@
                     ),
                 ),
             iconTint = null,
-            backgroundColor = R.attr.offStateColor,
+            backgroundColor = R.attr.shadeInactive,
             onClick = this::onUserSwitcherClicked,
         )
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt
index c00a81c..39745c8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt
@@ -24,9 +24,9 @@
 import com.android.systemui.log.ConstantStringsLogger
 import com.android.systemui.log.ConstantStringsLoggerImpl
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel.DEBUG
-import com.android.systemui.log.LogLevel.ERROR
-import com.android.systemui.log.LogLevel.VERBOSE
+import com.android.systemui.log.core.LogLevel.DEBUG
+import com.android.systemui.log.core.LogLevel.ERROR
+import com.android.systemui.log.core.LogLevel.VERBOSE
 import com.android.systemui.log.dagger.QSConfigLog
 import com.android.systemui.log.dagger.QSLog
 import com.android.systemui.plugins.qs.QSTile
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/BaseAutoAddableModule.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/BaseAutoAddableModule.kt
new file mode 100644
index 0000000..adea26e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/BaseAutoAddableModule.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.pipeline.dagger
+
+import android.content.res.Resources
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.qs.pipeline.domain.autoaddable.AutoAddableSetting
+import com.android.systemui.qs.pipeline.domain.autoaddable.AutoAddableSettingList
+import com.android.systemui.qs.pipeline.domain.autoaddable.CastAutoAddable
+import com.android.systemui.qs.pipeline.domain.autoaddable.DataSaverAutoAddable
+import com.android.systemui.qs.pipeline.domain.autoaddable.DeviceControlsAutoAddable
+import com.android.systemui.qs.pipeline.domain.autoaddable.HotspotAutoAddable
+import com.android.systemui.qs.pipeline.domain.autoaddable.NightDisplayAutoAddable
+import com.android.systemui.qs.pipeline.domain.autoaddable.ReduceBrightColorsAutoAddable
+import com.android.systemui.qs.pipeline.domain.autoaddable.WalletAutoAddable
+import com.android.systemui.qs.pipeline.domain.autoaddable.WorkTileAutoAddable
+import com.android.systemui.qs.pipeline.domain.model.AutoAddable
+import dagger.Binds
+import dagger.Module
+import dagger.Provides
+import dagger.multibindings.ElementsIntoSet
+import dagger.multibindings.IntoSet
+
+@Module
+interface BaseAutoAddableModule {
+
+    companion object {
+        @Provides
+        @ElementsIntoSet
+        fun providesAutoAddableSetting(
+            @Main resources: Resources,
+            autoAddableSettingFactory: AutoAddableSetting.Factory
+        ): Set<AutoAddable> {
+            return AutoAddableSettingList.parseSettingsResource(
+                    resources,
+                    autoAddableSettingFactory
+                )
+                .toSet()
+        }
+    }
+
+    @Binds @IntoSet fun bindCastAutoAddable(impl: CastAutoAddable): AutoAddable
+
+    @Binds @IntoSet fun bindDataSaverAutoAddable(impl: DataSaverAutoAddable): AutoAddable
+
+    @Binds @IntoSet fun bindDeviceControlsAutoAddable(impl: DeviceControlsAutoAddable): AutoAddable
+
+    @Binds @IntoSet fun bindHotspotAutoAddable(impl: HotspotAutoAddable): AutoAddable
+
+    @Binds @IntoSet fun bindNightDisplayAutoAddable(impl: NightDisplayAutoAddable): AutoAddable
+
+    @Binds
+    @IntoSet
+    fun bindReduceBrightColorsAutoAddable(impl: ReduceBrightColorsAutoAddable): AutoAddable
+
+    @Binds @IntoSet fun bindWalletAutoAddable(impl: WalletAutoAddable): AutoAddable
+
+    @Binds @IntoSet fun bindWorkModeAutoAddable(impl: WorkTileAutoAddable): AutoAddable
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSAutoAddLog.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSAutoAddLog.kt
new file mode 100644
index 0000000..91cb5bb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSAutoAddLog.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.pipeline.dagger
+
+import javax.inject.Qualifier
+
+/** A [LogBuffer] for the QS pipeline to track auto-added tiles */
+@Qualifier @MustBeDocumented @Retention(AnnotationRetention.RUNTIME) annotation class QSAutoAddLog
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSAutoAddModule.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSAutoAddModule.kt
index 9979228..a010ac4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSAutoAddModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSAutoAddModule.kt
@@ -16,13 +16,40 @@
 
 package com.android.systemui.qs.pipeline.dagger
 
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogBufferFactory
 import com.android.systemui.qs.pipeline.data.repository.AutoAddRepository
 import com.android.systemui.qs.pipeline.data.repository.AutoAddSettingRepository
+import com.android.systemui.qs.pipeline.domain.model.AutoAddable
+import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
 import dagger.Binds
 import dagger.Module
+import dagger.Provides
+import dagger.multibindings.Multibinds
 
-@Module
+@Module(
+    includes =
+        [
+            BaseAutoAddableModule::class,
+        ]
+)
 abstract class QSAutoAddModule {
 
     @Binds abstract fun bindAutoAddRepository(impl: AutoAddSettingRepository): AutoAddRepository
+
+    @Multibinds abstract fun providesAutoAddableSet(): Set<AutoAddable>
+
+    companion object {
+        /**
+         * Provides a logging buffer for all logs related to the new Quick Settings pipeline to log
+         * auto added tiles.
+         */
+        @Provides
+        @SysUISingleton
+        @QSAutoAddLog
+        fun provideQSAutoAddLogBuffer(factory: LogBufferFactory): LogBuffer {
+            return factory.create(QSPipelineLogger.AUTO_ADD_TAG, maxSize = 100, systrace = false)
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt
index d7ae575..a4600fb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt
@@ -26,7 +26,7 @@
 import com.android.systemui.qs.pipeline.data.repository.TileSpecSettingsRepository
 import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor
 import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractorImpl
-import com.android.systemui.qs.pipeline.prototyping.PrototypeCoreStartable
+import com.android.systemui.qs.pipeline.domain.startable.QSPipelineCoreStartable
 import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
 import dagger.Binds
 import dagger.Module
@@ -53,8 +53,8 @@
 
     @Binds
     @IntoMap
-    @ClassKey(PrototypeCoreStartable::class)
-    abstract fun providePrototypeCoreStartable(startable: PrototypeCoreStartable): CoreStartable
+    @ClassKey(QSPipelineCoreStartable::class)
+    abstract fun provideCoreStartable(startable: QSPipelineCoreStartable): CoreStartable
 
     companion object {
         /**
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSTileListLog.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSTileListLog.kt
index ad8bfea..c56ca8c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSTileListLog.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSTileListLog.kt
@@ -19,5 +19,5 @@
 import java.lang.annotation.RetentionPolicy
 import javax.inject.Qualifier
 
-/** A {@link LogBuffer} for the new QS Pipeline for logging changes to the set of current tiles. */
+/** A [LogBuffer] for the new QS Pipeline for logging changes to the set of current tiles. */
 @Qualifier @MustBeDocumented @Retention(RetentionPolicy.RUNTIME) annotation class QSTileListLog
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepository.kt
index 498f403..6e7e099 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepository.kt
@@ -51,12 +51,26 @@
 @Inject
 constructor(
     @Application private val applicationContext: Context,
-    private val packageManager: PackageManager,
     @Background private val backgroundDispatcher: CoroutineDispatcher,
 ) : InstalledTilesComponentRepository {
 
-    override fun getInstalledTilesComponents(userId: Int): Flow<Set<ComponentName>> =
-        conflatedCallbackFlow {
+    override fun getInstalledTilesComponents(userId: Int): Flow<Set<ComponentName>> {
+        /*
+         * In order to query [PackageManager] for different users, this implementation will call
+         * [Context.createContextAsUser] and retrieve the [PackageManager] from that context.
+         */
+        val packageManager =
+            if (applicationContext.userId == userId) {
+                applicationContext.packageManager
+            } else {
+                applicationContext
+                    .createContextAsUser(
+                        UserHandle.of(userId),
+                        /* flags */ 0,
+                    )
+                    .packageManager
+            }
+        return conflatedCallbackFlow {
                 val receiver =
                     object : BroadcastReceiver() {
                         override fun onReceive(context: Context?, intent: Intent?) {
@@ -74,12 +88,13 @@
                 awaitClose { applicationContext.unregisterReceiver(receiver) }
             }
             .onStart { emit(Unit) }
-            .map { reloadComponents(userId) }
+            .map { reloadComponents(userId, packageManager) }
             .distinctUntilChanged()
             .flowOn(backgroundDispatcher)
+    }
 
     @WorkerThread
-    private fun reloadComponents(userId: Int): Set<ComponentName> {
+    private fun reloadComponents(userId: Int, packageManager: PackageManager): Set<ComponentName> {
         return packageManager
             .queryIntentServicesAsUser(INTENT, FLAGS, userId)
             .mapNotNull { it.serviceInfo }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSetting.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSetting.kt
new file mode 100644
index 0000000..45129b9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSetting.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.systemui.qs.pipeline.domain.autoaddable
+
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
+import com.android.systemui.qs.pipeline.domain.model.AutoAddable
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.util.settings.SecureSettings
+import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import java.util.Objects
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+
+/**
+ * It tracks a specific `Secure` int [setting] and when its value changes to non-zero, it will emit
+ * a [AutoAddSignal.Add] for [spec].
+ */
+class AutoAddableSetting
+@AssistedInject
+constructor(
+    private val secureSettings: SecureSettings,
+    @Background private val bgDispatcher: CoroutineDispatcher,
+    @Assisted private val setting: String,
+    @Assisted private val spec: TileSpec,
+) : AutoAddable {
+
+    override fun autoAddSignal(userId: Int): Flow<AutoAddSignal> {
+        return secureSettings
+            .observerFlow(userId, setting)
+            .onStart { emit(Unit) }
+            .map { secureSettings.getIntForUser(setting, 0, userId) != 0 }
+            .distinctUntilChanged()
+            .filter { it }
+            .map { AutoAddSignal.Add(spec) }
+            .flowOn(bgDispatcher)
+    }
+
+    override val autoAddTracking = AutoAddTracking.IfNotAdded(spec)
+
+    override val description = "AutoAddableSetting: $setting:$spec ($autoAddTracking)"
+
+    override fun equals(other: Any?): Boolean {
+        return other is AutoAddableSetting && spec == other.spec && setting == other.setting
+    }
+
+    override fun hashCode(): Int {
+        return Objects.hash(spec, setting)
+    }
+
+    override fun toString(): String {
+        return description
+    }
+
+    @AssistedFactory
+    interface Factory {
+        fun create(setting: String, spec: TileSpec): AutoAddableSetting
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSettingList.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSettingList.kt
new file mode 100644
index 0000000..b1c7433
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSettingList.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.autoaddable
+
+import android.content.res.Resources
+import android.util.Log
+import com.android.systemui.R
+import com.android.systemui.qs.pipeline.domain.model.AutoAddable
+import com.android.systemui.qs.pipeline.shared.TileSpec
+
+object AutoAddableSettingList {
+
+    /** Parses [R.array.config_quickSettingsAutoAdd] into a collection of [AutoAddableSetting]. */
+    fun parseSettingsResource(
+        resources: Resources,
+        autoAddableSettingFactory: AutoAddableSetting.Factory,
+    ): Iterable<AutoAddable> {
+        val autoAddList = resources.getStringArray(R.array.config_quickSettingsAutoAdd)
+        return autoAddList.mapNotNull {
+            val elements = it.split(SETTING_SEPARATOR, limit = 2)
+            if (elements.size == 2) {
+                val setting = elements[0]
+                val spec = elements[1]
+                val tileSpec = TileSpec.create(spec)
+                if (tileSpec == TileSpec.Invalid) {
+                    Log.w(TAG, "Malformed item in array: $it")
+                    null
+                } else {
+                    autoAddableSettingFactory.create(setting, TileSpec.create(spec))
+                }
+            } else {
+                Log.w(TAG, "Malformed item in array: $it")
+                null
+            }
+        }
+    }
+
+    private const val SETTING_SEPARATOR = ":"
+    private const val TAG = "AutoAddableSettingList"
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/CallbackControllerAutoAddable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/CallbackControllerAutoAddable.kt
new file mode 100644
index 0000000..88a49ee
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/CallbackControllerAutoAddable.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.autoaddable
+
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
+import com.android.systemui.qs.pipeline.domain.model.AutoAddable
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.statusbar.policy.CallbackController
+import kotlinx.coroutines.channels.ProducerScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+
+/** Generic [AutoAddable] for tiles that are added based on a signal from a [CallbackController]. */
+abstract class CallbackControllerAutoAddable<
+    Callback : Any, Controller : CallbackController<Callback>>(
+    private val controller: Controller,
+) : AutoAddable {
+
+    /** [TileSpec] for the tile to add. */
+    protected abstract val spec: TileSpec
+
+    /**
+     * Callback to be used to determine when to add the tile. When the callback determines that the
+     * feature has been enabled, it should call [sendAdd].
+     */
+    protected abstract fun ProducerScope<AutoAddSignal>.getCallback(): Callback
+
+    /** Sends an [AutoAddSignal.Add] for [spec]. */
+    protected fun ProducerScope<AutoAddSignal>.sendAdd() {
+        trySend(AutoAddSignal.Add(spec))
+    }
+
+    final override fun autoAddSignal(userId: Int): Flow<AutoAddSignal> {
+        return conflatedCallbackFlow {
+            val callback = getCallback()
+            controller.addCallback(callback)
+
+            awaitClose { controller.removeCallback(callback) }
+        }
+    }
+
+    override val autoAddTracking: AutoAddTracking
+        get() = AutoAddTracking.IfNotAdded(spec)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/CastAutoAddable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/CastAutoAddable.kt
new file mode 100644
index 0000000..b5bef9f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/CastAutoAddable.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.pipeline.domain.autoaddable
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.CastTile
+import com.android.systemui.statusbar.policy.CastController
+import javax.inject.Inject
+import kotlinx.coroutines.channels.ProducerScope
+
+/**
+ * [AutoAddable] for [CastTile.TILE_SPEC].
+ *
+ * It will send a signal to add the tile when there's a casting device connected or connecting.
+ */
+@SysUISingleton
+class CastAutoAddable
+@Inject
+constructor(
+    private val controller: CastController,
+) : CallbackControllerAutoAddable<CastController.Callback, CastController>(controller) {
+
+    override val spec: TileSpec
+        get() = TileSpec.create(CastTile.TILE_SPEC)
+
+    override fun ProducerScope<AutoAddSignal>.getCallback(): CastController.Callback {
+        return CastController.Callback {
+            val isCasting =
+                controller.castDevices.any {
+                    it.state == CastController.CastDevice.STATE_CONNECTED ||
+                        it.state == CastController.CastDevice.STATE_CONNECTING
+                }
+            if (isCasting) {
+                sendAdd()
+            }
+        }
+    }
+
+    override val description = "CastAutoAddable ($autoAddTracking)"
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/DataSaverAutoAddable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/DataSaverAutoAddable.kt
new file mode 100644
index 0000000..a877aee
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/DataSaverAutoAddable.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.autoaddable
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.DataSaverTile
+import com.android.systemui.statusbar.policy.DataSaverController
+import javax.inject.Inject
+import kotlinx.coroutines.channels.ProducerScope
+
+/**
+ * [AutoAddable] for [DataSaverTile.TILE_SPEC].
+ *
+ * It will send a signal to add the tile when data saver is enabled.
+ */
+@SysUISingleton
+class DataSaverAutoAddable
+@Inject
+constructor(
+    dataSaverController: DataSaverController,
+) :
+    CallbackControllerAutoAddable<DataSaverController.Listener, DataSaverController>(
+        dataSaverController
+    ) {
+
+    override val spec
+        get() = TileSpec.create(DataSaverTile.TILE_SPEC)
+
+    override fun ProducerScope<AutoAddSignal>.getCallback(): DataSaverController.Listener {
+        return DataSaverController.Listener { enabled ->
+            if (enabled) {
+                sendAdd()
+            }
+        }
+    }
+
+    override val description = "DataSaverAutoAddable ($autoAddTracking)"
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/DeviceControlsAutoAddable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/DeviceControlsAutoAddable.kt
new file mode 100644
index 0000000..76bfad9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/DeviceControlsAutoAddable.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.qs.pipeline.domain.autoaddable
+
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
+import com.android.systemui.qs.pipeline.domain.model.AutoAddable
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.DeviceControlsTile
+import com.android.systemui.statusbar.policy.DeviceControlsController
+import javax.inject.Inject
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * [AutoAddable] for [DeviceControlsTile.TILE_SPEC].
+ *
+ * It will send a signal to add the tile when updating to a device that supports device controls. It
+ * will send a signal to remove the tile when the device does not support controls.
+ */
+@SysUISingleton
+class DeviceControlsAutoAddable
+@Inject
+constructor(
+    private val deviceControlsController: DeviceControlsController,
+) : AutoAddable {
+
+    private val spec = TileSpec.create(DeviceControlsTile.TILE_SPEC)
+
+    override fun autoAddSignal(userId: Int): Flow<AutoAddSignal> {
+        return conflatedCallbackFlow {
+            val callback =
+                object : DeviceControlsController.Callback {
+                    override fun onControlsUpdate(position: Int?) {
+                        position?.let { trySend(AutoAddSignal.Add(spec, position)) }
+                        deviceControlsController.removeCallback()
+                    }
+
+                    override fun removeControlsAutoTracker() {
+                        trySend(AutoAddSignal.Remove(spec))
+                    }
+                }
+
+            deviceControlsController.setCallback(callback)
+
+            awaitClose { deviceControlsController.removeCallback() }
+        }
+    }
+
+    override val autoAddTracking: AutoAddTracking
+        get() = AutoAddTracking.Always
+
+    override val description = "DeviceControlsAutoAddable ($autoAddTracking)"
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/HotspotAutoAddable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/HotspotAutoAddable.kt
new file mode 100644
index 0000000..9c59e12
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/HotspotAutoAddable.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.autoaddable
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.HotspotTile
+import com.android.systemui.statusbar.policy.HotspotController
+import javax.inject.Inject
+import kotlinx.coroutines.channels.ProducerScope
+
+/**
+ * [AutoAddable] for [HotspotTile.TILE_SPEC].
+ *
+ * It will send a signal to add the tile when hotspot is enabled.
+ */
+@SysUISingleton
+class HotspotAutoAddable
+@Inject
+constructor(
+    hotspotController: HotspotController,
+) :
+    CallbackControllerAutoAddable<HotspotController.Callback, HotspotController>(
+        hotspotController
+    ) {
+
+    override val spec
+        get() = TileSpec.create(HotspotTile.TILE_SPEC)
+
+    override fun ProducerScope<AutoAddSignal>.getCallback(): HotspotController.Callback {
+        return HotspotController.Callback { enabled, _ ->
+            if (enabled) {
+                sendAdd()
+            }
+        }
+    }
+
+    override val description = "HotspotAutoAddable ($autoAddTracking)"
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/NightDisplayAutoAddable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/NightDisplayAutoAddable.kt
new file mode 100644
index 0000000..31ea734
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/NightDisplayAutoAddable.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.autoaddable
+
+import android.content.Context
+import android.hardware.display.ColorDisplayManager
+import android.hardware.display.NightDisplayListener
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.NightDisplayListenerModule
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
+import com.android.systemui.qs.pipeline.domain.model.AutoAddable
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.NightDisplayTile
+import javax.inject.Inject
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * [AutoAddable] for [NightDisplayTile.TILE_SPEC].
+ *
+ * It will send a signal to add the tile when night display is enabled or when the auto mode changes
+ * to one that supports night display.
+ */
+@SysUISingleton
+class NightDisplayAutoAddable
+@Inject
+constructor(
+    private val nightDisplayListenerBuilder: NightDisplayListenerModule.Builder,
+    context: Context,
+) : AutoAddable {
+
+    private val enabled = ColorDisplayManager.isNightDisplayAvailable(context)
+    private val spec = TileSpec.create(NightDisplayTile.TILE_SPEC)
+
+    override fun autoAddSignal(userId: Int): Flow<AutoAddSignal> {
+        return conflatedCallbackFlow {
+            val nightDisplayListener = nightDisplayListenerBuilder.setUser(userId).build()
+
+            val callback =
+                object : NightDisplayListener.Callback {
+                    override fun onActivated(activated: Boolean) {
+                        if (activated) {
+                            sendAdd()
+                        }
+                    }
+
+                    override fun onAutoModeChanged(autoMode: Int) {
+                        if (
+                            autoMode == ColorDisplayManager.AUTO_MODE_CUSTOM_TIME ||
+                                autoMode == ColorDisplayManager.AUTO_MODE_TWILIGHT
+                        ) {
+                            sendAdd()
+                        }
+                    }
+
+                    private fun sendAdd() {
+                        trySend(AutoAddSignal.Add(spec))
+                    }
+                }
+
+            nightDisplayListener.setCallback(callback)
+
+            awaitClose { nightDisplayListener.setCallback(null) }
+        }
+    }
+
+    override val autoAddTracking =
+        if (enabled) {
+            AutoAddTracking.IfNotAdded(spec)
+        } else {
+            AutoAddTracking.Disabled
+        }
+
+    override val description = "NightDisplayAutoAddable ($autoAddTracking)"
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/ReduceBrightColorsAutoAddable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/ReduceBrightColorsAutoAddable.kt
new file mode 100644
index 0000000..267e2b7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/ReduceBrightColorsAutoAddable.kt
@@ -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.systemui.qs.pipeline.domain.autoaddable
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.ReduceBrightColorsController
+import com.android.systemui.qs.dagger.QSFlagsModule.RBC_AVAILABLE
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.ReduceBrightColorsTile
+import javax.inject.Inject
+import javax.inject.Named
+import kotlinx.coroutines.channels.ProducerScope
+
+/**
+ * [AutoAddable] for [ReduceBrightColorsTile.TILE_SPEC].
+ *
+ * It will send a signal to add the tile when reduce bright colors is enabled.
+ */
+@SysUISingleton
+class ReduceBrightColorsAutoAddable
+@Inject
+constructor(
+    controller: ReduceBrightColorsController,
+    @Named(RBC_AVAILABLE) private val available: Boolean,
+) :
+    CallbackControllerAutoAddable<
+        ReduceBrightColorsController.Listener, ReduceBrightColorsController
+    >(controller) {
+
+    override val spec: TileSpec
+        get() = TileSpec.create(ReduceBrightColorsTile.TILE_SPEC)
+
+    override fun ProducerScope<AutoAddSignal>.getCallback(): ReduceBrightColorsController.Listener {
+        return object : ReduceBrightColorsController.Listener {
+            override fun onActivated(activated: Boolean) {
+                if (activated) {
+                    sendAdd()
+                }
+            }
+        }
+    }
+
+    override val autoAddTracking
+        get() =
+            if (available) {
+                super.autoAddTracking
+            } else {
+                AutoAddTracking.Disabled
+            }
+
+    override val description = "ReduceBrightColorsAutoAddable ($autoAddTracking)"
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/SafetyCenterAutoAddable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/SafetyCenterAutoAddable.kt
new file mode 100644
index 0000000..58a31bc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/SafetyCenterAutoAddable.kt
@@ -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.systemui.qs.pipeline.domain.autoaddable
+
+import android.content.ComponentName
+import android.content.pm.PackageManager
+import android.content.res.Resources
+import android.text.TextUtils
+import com.android.systemui.R
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
+import com.android.systemui.qs.pipeline.domain.model.AutoAddable
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.statusbar.policy.SafetyController
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.withContext
+
+/**
+ * [AutoAddable] for the safety tile.
+ *
+ * It will send a signal to add the tile when the feature is enabled, indicating the component
+ * corresponding to the tile. If the feature is disabled, it will send a signal to remove the tile.
+ */
+@SysUISingleton
+class SafetyCenterAutoAddable
+@Inject
+constructor(
+    private val safetyController: SafetyController,
+    private val packageManager: PackageManager,
+    @Main private val resources: Resources,
+    @Background private val bgDispatcher: CoroutineDispatcher,
+) : AutoAddable {
+
+    private suspend fun getSpec(): TileSpec? {
+        val specClass = resources.getString(R.string.safety_quick_settings_tile_class)
+        return if (TextUtils.isEmpty(specClass)) {
+            null
+        } else {
+            val packageName =
+                withContext(bgDispatcher) { packageManager.permissionControllerPackageName }
+            TileSpec.create(ComponentName(packageName, specClass))
+        }
+    }
+
+    override fun autoAddSignal(userId: Int): Flow<AutoAddSignal> {
+        return conflatedCallbackFlow {
+            val spec = getSpec()
+            if (spec != null) {
+                // If not added, we always try to add it
+                trySend(AutoAddSignal.Add(spec))
+                val listener =
+                    SafetyController.Listener { isSafetyCenterEnabled ->
+                        if (isSafetyCenterEnabled) {
+                            trySend(AutoAddSignal.Add(spec))
+                        } else {
+                            trySend(AutoAddSignal.Remove(spec))
+                        }
+                    }
+
+                safetyController.addCallback(listener)
+
+                awaitClose { safetyController.removeCallback(listener) }
+            } else {
+                awaitClose {}
+            }
+        }
+    }
+
+    override val autoAddTracking: AutoAddTracking
+        get() = AutoAddTracking.Always
+
+    override val description = "SafetyCenterAutoAddable ($autoAddTracking)"
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/WalletAutoAddable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/WalletAutoAddable.kt
new file mode 100644
index 0000000..b3bc25f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/WalletAutoAddable.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.autoaddable
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
+import com.android.systemui.qs.pipeline.domain.model.AutoAddable
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.QuickAccessWalletTile
+import com.android.systemui.statusbar.policy.WalletController
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flow
+
+/**
+ * [AutoAddable] for [QuickAccessWalletTile.TILE_SPEC].
+ *
+ * It will always try to add the tile if [WalletController.getWalletPosition] is non-null.
+ */
+@SysUISingleton
+class WalletAutoAddable
+@Inject
+constructor(
+    private val walletController: WalletController,
+) : AutoAddable {
+
+    private val spec = TileSpec.create(QuickAccessWalletTile.TILE_SPEC)
+
+    override fun autoAddSignal(userId: Int): Flow<AutoAddSignal> {
+        return flow {
+            val position = walletController.getWalletPosition()
+            if (position != null) {
+                emit(AutoAddSignal.Add(spec, position))
+            }
+        }
+    }
+
+    override val autoAddTracking: AutoAddTracking
+        get() = AutoAddTracking.IfNotAdded(spec)
+
+    override val description = "WalletAutoAddable ($autoAddTracking)"
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/WorkTileAutoAddable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/WorkTileAutoAddable.kt
new file mode 100644
index 0000000..5e3c348
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/WorkTileAutoAddable.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.pipeline.domain.autoaddable
+
+import android.content.pm.UserInfo
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
+import com.android.systemui.qs.pipeline.domain.model.AutoAddable
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.WorkModeTile
+import com.android.systemui.settings.UserTracker
+import javax.inject.Inject
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * [AutoAddable] for [WorkModeTile.TILE_SPEC].
+ *
+ * It will send a signal to add the tile when there is a managed profile for the current user, and a
+ * signal to remove it if there is not.
+ */
+@SysUISingleton
+class WorkTileAutoAddable @Inject constructor(private val userTracker: UserTracker) : AutoAddable {
+
+    private val spec = TileSpec.create(WorkModeTile.TILE_SPEC)
+
+    override fun autoAddSignal(userId: Int): Flow<AutoAddSignal> {
+        return conflatedCallbackFlow {
+            fun maybeSend(profiles: List<UserInfo>) {
+                if (profiles.any { it.id == userId }) {
+                    // We are looking at the profiles of the correct user.
+                    if (profiles.any { it.isManagedProfile }) {
+                        trySend(AutoAddSignal.Add(spec))
+                    } else {
+                        trySend(AutoAddSignal.Remove(spec))
+                    }
+                }
+            }
+
+            val callback =
+                object : UserTracker.Callback {
+                    override fun onProfilesChanged(profiles: List<UserInfo>) {
+                        maybeSend(profiles)
+                    }
+                }
+
+            userTracker.addCallback(callback) { it.run() }
+            maybeSend(userTracker.userProfiles)
+
+            awaitClose { userTracker.removeCallback(callback) }
+        }
+    }
+
+    override val autoAddTracking = AutoAddTracking.Always
+
+    override val description = "WorkTileAutoAddable ($autoAddTracking)"
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractor.kt
new file mode 100644
index 0000000..b747393
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractor.kt
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.pipeline.domain.interactor
+
+import com.android.systemui.Dumpable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.qs.pipeline.data.repository.AutoAddRepository
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
+import com.android.systemui.qs.pipeline.domain.model.AutoAddable
+import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
+import com.android.systemui.util.asIndenting
+import com.android.systemui.util.indentIfPossible
+import java.io.PrintWriter
+import java.util.concurrent.atomic.AtomicBoolean
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.filterIsInstance
+import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.flow.take
+import kotlinx.coroutines.launch
+
+/**
+ * Collects the signals coming from all registered [AutoAddable] and adds/removes tiles accordingly.
+ */
+@SysUISingleton
+class AutoAddInteractor
+@Inject
+constructor(
+    private val autoAddables: Set<@JvmSuppressWildcards AutoAddable>,
+    private val repository: AutoAddRepository,
+    private val dumpManager: DumpManager,
+    private val qsPipelineLogger: QSPipelineLogger,
+    @Application private val scope: CoroutineScope,
+) : Dumpable {
+
+    private val initialized = AtomicBoolean(false)
+
+    /** Start collection of signals following the user from [currentTilesInteractor]. */
+    fun init(currentTilesInteractor: CurrentTilesInteractor) {
+        if (!initialized.compareAndSet(false, true)) {
+            return
+        }
+
+        dumpManager.registerNormalDumpable(TAG, this)
+
+        scope.launch {
+            currentTilesInteractor.userId.collectLatest { userId ->
+                coroutineScope {
+                    val previouslyAdded = repository.autoAddedTiles(userId).stateIn(this)
+
+                    autoAddables
+                        .map { addable ->
+                            val autoAddSignal = addable.autoAddSignal(userId)
+                            when (val lifecycle = addable.autoAddTracking) {
+                                is AutoAddTracking.Always -> autoAddSignal
+                                is AutoAddTracking.Disabled -> emptyFlow()
+                                is AutoAddTracking.IfNotAdded -> {
+                                    if (lifecycle.spec !in previouslyAdded.value) {
+                                        autoAddSignal.filterIsInstance<AutoAddSignal.Add>().take(1)
+                                    } else {
+                                        emptyFlow()
+                                    }
+                                }
+                            }
+                        }
+                        .merge()
+                        .collect { signal ->
+                            when (signal) {
+                                is AutoAddSignal.Add -> {
+                                    if (signal.spec !in previouslyAdded.value) {
+                                        currentTilesInteractor.addTile(signal.spec, signal.position)
+                                        qsPipelineLogger.logTileAutoAdded(
+                                            userId,
+                                            signal.spec,
+                                            signal.position
+                                        )
+                                        repository.markTileAdded(userId, signal.spec)
+                                    }
+                                }
+                                is AutoAddSignal.Remove -> {
+                                    currentTilesInteractor.removeTiles(setOf(signal.spec))
+                                    qsPipelineLogger.logTileAutoRemoved(userId, signal.spec)
+                                    repository.unmarkTileAdded(userId, signal.spec)
+                                }
+                            }
+                        }
+                }
+            }
+        }
+    }
+
+    override fun dump(pw: PrintWriter, args: Array<out String>) {
+        with(pw.asIndenting()) {
+            println("AutoAddables:")
+            indentIfPossible { autoAddables.forEach { println(it.description) } }
+        }
+    }
+
+    companion object {
+        private const val TAG = "AutoAddInteractor"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/PanelInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/PanelInteractor.kt
index 260caa7..fa6de8d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/PanelInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/PanelInteractor.kt
@@ -16,8 +16,7 @@
 package com.android.systemui.qs.pipeline.domain.interactor
 
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.statusbar.phone.CentralSurfaces
-import java.util.Optional
+import com.android.systemui.shade.ShadeController
 import javax.inject.Inject
 
 /** Encapsulates business logic for interacting with the QS panel. */
@@ -37,17 +36,17 @@
 class PanelInteractorImpl
 @Inject
 constructor(
-    private val centralSurfaces: Optional<CentralSurfaces>,
+    private val shadeController: ShadeController,
 ) : PanelInteractor {
     override fun collapsePanels() {
-        centralSurfaces.ifPresent { it.postAnimateCollapsePanels() }
+        shadeController.postAnimateCollapseShade()
     }
 
     override fun forceCollapsePanels() {
-        centralSurfaces.ifPresent { it.postAnimateForceCollapsePanels() }
+        shadeController.postAnimateForceCollapseShade()
     }
 
     override fun openPanels() {
-        centralSurfaces.ifPresent { it.postAnimateOpenPanels() }
+        shadeController.postAnimateExpandQs()
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/model/AutoAddSignal.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/model/AutoAddSignal.kt
new file mode 100644
index 0000000..ed7b8bd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/model/AutoAddSignal.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.model
+
+import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository.Companion.POSITION_AT_END
+import com.android.systemui.qs.pipeline.shared.TileSpec
+
+/** Signal indicating when a tile needs to be auto-added or removed */
+sealed interface AutoAddSignal {
+    /** Tile for this object */
+    val spec: TileSpec
+
+    /** Signal for auto-adding a tile at [position]. */
+    data class Add(
+        override val spec: TileSpec,
+        val position: Int = POSITION_AT_END,
+    ) : AutoAddSignal
+
+    /** Signal for removing a tile. */
+    data class Remove(
+        override val spec: TileSpec,
+    ) : AutoAddSignal
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/model/AutoAddTracking.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/model/AutoAddTracking.kt
new file mode 100644
index 0000000..154d045
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/model/AutoAddTracking.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.model
+
+import com.android.systemui.qs.pipeline.shared.TileSpec
+
+/** Strategy for when to track a particular [AutoAddable]. */
+sealed interface AutoAddTracking {
+
+    /**
+     * Indicates that the signals from the associated [AutoAddable] should all be collected and
+     * reacted accordingly. It may have [AutoAddSignal.Add] and [AutoAddSignal.Remove].
+     */
+    object Always : AutoAddTracking {
+        override fun toString(): String {
+            return "Always"
+        }
+    }
+
+    /**
+     * Indicates that the associated [AutoAddable] is [Disabled] and doesn't need to be collected.
+     */
+    object Disabled : AutoAddTracking {
+        override fun toString(): String {
+            return "Disabled"
+        }
+    }
+
+    /**
+     * Only the first [AutoAddSignal.Add] for each flow of signals needs to be collected, and only
+     * if the tile hasn't been auto-added yet. The associated [AutoAddable] will only emit
+     * [AutoAddSignal.Add].
+     */
+    data class IfNotAdded(val spec: TileSpec) : AutoAddTracking
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/model/AutoAddable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/model/AutoAddable.kt
new file mode 100644
index 0000000..61fe5b4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/model/AutoAddable.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.qs.pipeline.domain.model
+
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * Tracks conditions for auto-adding or removing specific tiles.
+ *
+ * When creating a new [AutoAddable], it needs to be registered in a [Module] like
+ * [BaseAutoAddableModule], for example:
+ * ```
+ * @Binds
+ * @IntoSet
+ * fun providesMyAutoAddable(autoAddable: MyAutoAddable): AutoAddable
+ * ```
+ */
+interface AutoAddable {
+
+    /**
+     * Signals associated with a particular user indicating whether a particular tile needs to be
+     * auto-added or auto-removed.
+     */
+    fun autoAddSignal(userId: Int): Flow<AutoAddSignal>
+
+    /**
+     * Lifecycle for this object. It indicates in which cases [autoAddSignal] should be collected
+     */
+    val autoAddTracking: AutoAddTracking
+
+    /** Human readable description */
+    val description: String
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/startable/QSPipelineCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/startable/QSPipelineCoreStartable.kt
new file mode 100644
index 0000000..224fc1a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/startable/QSPipelineCoreStartable.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.startable
+
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.qs.pipeline.domain.interactor.AutoAddInteractor
+import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor
+import javax.inject.Inject
+
+@SysUISingleton
+class QSPipelineCoreStartable
+@Inject
+constructor(
+    private val currentTilesInteractor: CurrentTilesInteractor,
+    private val autoAddInteractor: AutoAddInteractor,
+    private val featureFlags: FeatureFlags,
+) : CoreStartable {
+
+    override fun start() {
+        if (
+            featureFlags.isEnabled(Flags.QS_PIPELINE_NEW_HOST) &&
+                featureFlags.isEnabled(Flags.QS_PIPELINE_AUTO_ADD)
+        ) {
+            autoAddInteractor.init(currentTilesInteractor)
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/prototyping/PrototypeCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/prototyping/PrototypeCoreStartable.kt
deleted file mode 100644
index bbd7234..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/prototyping/PrototypeCoreStartable.kt
+++ /dev/null
@@ -1,120 +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.qs.pipeline.prototyping
-
-import android.util.Log
-import com.android.systemui.CoreStartable
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
-import com.android.systemui.qs.pipeline.data.repository.AutoAddRepository
-import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository
-import com.android.systemui.qs.pipeline.shared.TileSpec
-import com.android.systemui.statusbar.commandline.Command
-import com.android.systemui.statusbar.commandline.CommandRegistry
-import com.android.systemui.user.data.repository.UserRepository
-import java.io.PrintWriter
-import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.launch
-
-/**
- * Class for observing results while prototyping.
- *
- * The flows do their own logging, so we just need to make sure that they collect.
- *
- * This will be torn down together with the last of the new pipeline flags remaining here.
- */
-// TODO(b/270385608)
-@SysUISingleton
-class PrototypeCoreStartable
-@Inject
-constructor(
-    private val tileSpecRepository: TileSpecRepository,
-    private val autoAddRepository: AutoAddRepository,
-    private val userRepository: UserRepository,
-    private val featureFlags: FeatureFlags,
-    @Application private val scope: CoroutineScope,
-    private val commandRegistry: CommandRegistry,
-) : CoreStartable {
-
-    @OptIn(ExperimentalCoroutinesApi::class)
-    override fun start() {
-        if (featureFlags.isEnabled(Flags.QS_PIPELINE_NEW_HOST)) {
-            scope.launch {
-                userRepository.selectedUserInfo
-                    .flatMapLatest { user -> tileSpecRepository.tilesSpecs(user.id) }
-                    .collect {}
-            }
-            if (featureFlags.isEnabled(Flags.QS_PIPELINE_AUTO_ADD)) {
-                scope.launch {
-                    userRepository.selectedUserInfo
-                        .flatMapLatest { user -> autoAddRepository.autoAddedTiles(user.id) }
-                        .collect { tiles -> Log.d(TAG, "Auto-added tiles: $tiles") }
-                }
-            }
-            commandRegistry.registerCommand(COMMAND, ::CommandExecutor)
-        }
-    }
-
-    private inner class CommandExecutor : Command {
-        override fun execute(pw: PrintWriter, args: List<String>) {
-            if (args.size < 2) {
-                pw.println("Error: needs at least two arguments")
-                return
-            }
-            val spec = TileSpec.create(args[1])
-            if (spec == TileSpec.Invalid) {
-                pw.println("Error: Invalid tile spec ${args[1]}")
-            }
-            if (args[0] == "add") {
-                performAdd(args, spec)
-                pw.println("Requested tile added")
-            } else if (args[0] == "remove") {
-                performRemove(args, spec)
-                pw.println("Requested tile removed")
-            } else {
-                pw.println("Error: unknown command")
-            }
-        }
-
-        private fun performAdd(args: List<String>, spec: TileSpec) {
-            val position = args.getOrNull(2)?.toInt() ?: TileSpecRepository.POSITION_AT_END
-            val user = args.getOrNull(3)?.toInt() ?: userRepository.getSelectedUserInfo().id
-            scope.launch { tileSpecRepository.addTile(user, spec, position) }
-        }
-
-        private fun performRemove(args: List<String>, spec: TileSpec) {
-            val user = args.getOrNull(2)?.toInt() ?: userRepository.getSelectedUserInfo().id
-            scope.launch { tileSpecRepository.removeTiles(user, listOf(spec)) }
-        }
-
-        override fun help(pw: PrintWriter) {
-            pw.println("Usage: adb shell cmd statusbar $COMMAND:")
-            pw.println("  add <spec> [position] [user]")
-            pw.println("  remove <spec> [user]")
-        }
-    }
-
-    companion object {
-        private const val COMMAND = "qs-pipeline"
-        private const val TAG = "PrototypeCoreStartable"
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TileSpec.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TileSpec.kt
index af1cd09..11b5dd7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TileSpec.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TileSpec.kt
@@ -52,7 +52,11 @@
     internal constructor(
         override val spec: String,
         val componentName: ComponentName,
-    ) : TileSpec(spec)
+    ) : TileSpec(spec) {
+        override fun toString(): String {
+            return "CustomTileSpec(${componentName.toShortString()})"
+        }
+    }
 
     companion object {
         /** Create a [TileSpec] from the string [spec]. */
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt
index 8318ec9..573cb715 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt
@@ -18,7 +18,8 @@
 
 import android.annotation.UserIdInt
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.qs.pipeline.dagger.QSAutoAddLog
 import com.android.systemui.qs.pipeline.dagger.QSTileListLog
 import com.android.systemui.qs.pipeline.shared.TileSpec
 import javax.inject.Inject
@@ -32,10 +33,12 @@
 @Inject
 constructor(
     @QSTileListLog private val tileListLogBuffer: LogBuffer,
+    @QSAutoAddLog private val tileAutoAddLogBuffer: LogBuffer,
 ) {
 
     companion object {
         const val TILE_LIST_TAG = "QSTileListLog"
+        const val AUTO_ADD_TAG = "QSAutoAddableLog"
     }
 
     /**
@@ -136,6 +139,31 @@
         )
     }
 
+    fun logTileAutoAdded(userId: Int, spec: TileSpec, position: Int) {
+        tileAutoAddLogBuffer.log(
+            AUTO_ADD_TAG,
+            LogLevel.DEBUG,
+            {
+                int1 = userId
+                int2 = position
+                str1 = spec.toString()
+            },
+            { "Tile $str1 auto added for user $int1 at position $int2" }
+        )
+    }
+
+    fun logTileAutoRemoved(userId: Int, spec: TileSpec) {
+        tileAutoAddLogBuffer.log(
+            AUTO_ADD_TAG,
+            LogLevel.DEBUG,
+            {
+                int1 = userId
+                str1 = spec.toString()
+            },
+            { "Tile $str1 auto removed for user $int1" }
+        )
+    }
+
     /** Reasons for destroying an existing tile. */
     enum class TileDestroyedReason(val readable: String) {
         TILE_REMOVED("Tile removed from  current set"),
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
index cd69f4e..7e45491 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
@@ -130,6 +130,11 @@
                 d.setLayoutDirection(getLayoutDirection());
             }
 
+            final Drawable lastDrawable = iv.getDrawable();
+            if (lastDrawable instanceof Animatable2) {
+                ((Animatable2) lastDrawable).clearAnimationCallbacks();
+            }
+
             if (iv instanceof SlashImageView) {
                 ((SlashImageView) iv).setAnimationEnabled(shouldAnimate);
                 ((SlashImageView) iv).setState(null, d);
@@ -243,13 +248,11 @@
      */
     private static int getIconColorForState(Context context, QSTile.State state) {
         if (state.disabledByPolicy || state.state == Tile.STATE_UNAVAILABLE) {
-            return Utils.getColorAttrDefaultColor(
-                    context, com.android.internal.R.attr.textColorTertiary);
+            return Utils.getColorAttrDefaultColor(context, R.attr.outline);
         } else if (state.state == Tile.STATE_INACTIVE) {
-            return Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary);
+            return Utils.getColorAttrDefaultColor(context, R.attr.onShadeInactiveVariant);
         } else if (state.state == Tile.STATE_ACTIVE) {
-            return Utils.getColorAttrDefaultColor(context,
-                    com.android.internal.R.attr.textColorOnAccent);
+            return Utils.getColorAttrDefaultColor(context, R.attr.onShadeActive);
         } else {
             Log.e("QSIconView", "Invalid state " + state);
             return 0;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
index 2a9e7d0..1ca2a96 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
@@ -330,7 +330,9 @@
         final int eventId = mClickEventId++;
         mQSLogger.logTileLongClick(mTileSpec, mStatusBarStateController.getState(), mState.state,
                 eventId);
-        mHandler.obtainMessage(H.LONG_CLICK, eventId, 0, view).sendToTarget();
+        if (!mFalsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY)) {
+            mHandler.obtainMessage(H.LONG_CLICK, eventId, 0, view).sendToTarget();
+        }
     }
 
     public LogMaker populate(LogMaker logMaker) {
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 b806683..764ef68 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
@@ -29,6 +29,7 @@
 import android.service.quicksettings.Tile
 import android.text.TextUtils
 import android.util.Log
+import android.util.TypedValue
 import android.view.Gravity
 import android.view.LayoutInflater
 import android.view.View
@@ -92,24 +93,21 @@
             updateHeight()
         }
 
-    private val colorActive = Utils.getColorAttrDefaultColor(context,
-            com.android.internal.R.attr.colorAccentPrimary)
-    private val colorInactive = Utils.getColorAttrDefaultColor(context, R.attr.offStateColor)
-    private val colorUnavailable = Utils.applyAlpha(UNAVAILABLE_ALPHA, colorInactive)
+    private val colorActive = Utils.getColorAttrDefaultColor(context, R.attr.shadeActive)
+    private val colorInactive = Utils.getColorAttrDefaultColor(context, R.attr.shadeInactive)
+    private val colorUnavailable = Utils.getColorAttrDefaultColor(context, R.attr.shadeDisabled)
 
-    private val colorLabelActive =
-            Utils.getColorAttrDefaultColor(context, com.android.internal.R.attr.textColorOnAccent)
-    private val colorLabelInactive =
-            Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary)
+    private val colorLabelActive = Utils.getColorAttrDefaultColor(context, R.attr.onShadeActive)
+    private val colorLabelInactive = Utils.getColorAttrDefaultColor(context, R.attr.onShadeInactive)
     private val colorLabelUnavailable =
-        Utils.getColorAttrDefaultColor(context, com.android.internal.R.attr.textColorTertiary)
+        Utils.getColorAttrDefaultColor(context, R.attr.outline)
 
     private val colorSecondaryLabelActive =
-            Utils.getColorAttrDefaultColor(context, android.R.attr.textColorSecondaryInverse)
+        Utils.getColorAttrDefaultColor(context, R.attr.onShadeActiveVariant)
     private val colorSecondaryLabelInactive =
-            Utils.getColorAttrDefaultColor(context, android.R.attr.textColorSecondary)
+            Utils.getColorAttrDefaultColor(context, R.attr.onShadeInactiveVariant)
     private val colorSecondaryLabelUnavailable =
-        Utils.getColorAttrDefaultColor(context, com.android.internal.R.attr.textColorTertiary)
+        Utils.getColorAttrDefaultColor(context, R.attr.outline)
 
     private lateinit var label: TextView
     protected lateinit var secondaryLabel: TextView
@@ -118,6 +116,10 @@
     private lateinit var customDrawableView: ImageView
     private lateinit var chevronView: ImageView
     private var mQsLogger: QSLogger? = null
+
+    /**
+     * Controls if tile background is set to a [RippleDrawable] see [setClickable]
+     */
     protected var showRippleEffect = true
 
     private lateinit var ripple: RippleDrawable
@@ -151,6 +153,11 @@
     private val locInScreen = IntArray(2)
 
     init {
+        val typedValue = TypedValue()
+        if (!getContext().theme.resolveAttribute(R.attr.isQsTheme, typedValue, true)) {
+            throw IllegalStateException("QSViewImpl must be inflated with a theme that contains " +
+                    "Theme.SystemUI.QuickSettings")
+        }
         setId(generateViewId())
         orientation = LinearLayout.HORIZONTAL
         gravity = Gravity.CENTER_VERTICAL or Gravity.START
@@ -437,7 +444,6 @@
 
     protected open fun handleStateChanged(state: QSTile.State) {
         val allowAnimations = animationsEnabled()
-        showRippleEffect = state.showRippleEffect
         isClickable = state.state != Tile.STATE_UNAVAILABLE
         isLongClickable = state.handlesLongClick
         icon.setIcon(state, allowAnimations)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java
index a444e76..9f10e6f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java
@@ -155,7 +155,6 @@
         state.contentDescription = state.label;
         state.value = mPowerSave;
         state.expandedAccessibilityClassName = Switch.class.getName();
-        state.showRippleEffect = mSetting.getValue() == 0;
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java
index eef4c1d..b5e6a0f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java
@@ -166,17 +166,6 @@
     }
 
     @Override
-    protected void handleLongClick(@Nullable View view) {
-        try {
-            // Need to wake on long click so bouncer->settings works.
-            mDreamManager.awaken();
-        } catch (RemoteException e) {
-            Log.e(LOG_TAG, "Can't awaken", e);
-        }
-        super.handleLongClick(view);
-    }
-
-    @Override
     protected void handleUpdateState(BooleanState state, Object arg) {
         state.label = getTileLabel();
         state.secondaryLabel = getActiveDreamName();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt
index c013486..423fa80 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt
@@ -38,7 +38,9 @@
 import com.android.systemui.qs.QsEventLogger
 import com.android.systemui.qs.logging.QSLogger
 import com.android.systemui.qs.tileimpl.QSTileImpl
+import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.concurrency.DelayableExecutor
 import com.android.systemui.util.settings.SecureSettings
 import com.android.systemui.util.settings.SystemSettings
@@ -57,11 +59,13 @@
     statusBarStateController: StatusBarStateController,
     activityStarter: ActivityStarter,
     qsLogger: QSLogger,
+    private val keyguardStateController: KeyguardStateController,
     private val dialogLaunchAnimator: DialogLaunchAnimator,
     private val systemSettings: SystemSettings,
     private val secureSettings: SecureSettings,
     private val systemClock: SystemClock,
     private val featureFlags: FeatureFlags,
+    private val userTracker: UserTracker,
     @Background private val backgroundDelayableExecutor: DelayableExecutor
 ) :
     QSTileImpl<QSTile.State?>(
@@ -86,26 +90,40 @@
     }
 
     override fun handleClick(view: View?) {
-        mUiHandler.post {
+        // We animate from the touched view only if we are not on the keyguard
+        val animateFromView: Boolean = view != null && !keyguardStateController.isShowing
+
+        val runnable = Runnable {
             val dialog: SystemUIDialog =
                 FontScalingDialog(
                     mContext,
                     systemSettings,
                     secureSettings,
                     systemClock,
+                    userTracker,
                     mainHandler,
                     backgroundDelayableExecutor
                 )
-            if (view != null) {
+            if (animateFromView) {
                 dialogLaunchAnimator.showFromView(
                     dialog,
-                    view,
+                    view!!,
                     DialogCuj(InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN, INTERACTION_JANK_TAG)
                 )
             } else {
                 dialog.show()
             }
         }
+
+        mainHandler.post {
+            mActivityStarter.executeRunnableDismissingKeyguard(
+                runnable,
+                /* cancelAction= */ null,
+                /* dismissShade= */ true,
+                /* afterKeyguardGone= */ true,
+                /* deferred= */ false
+            )
+        }
     }
 
     override fun handleUpdateState(state: QSTile.State?, arg: Any?) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java
index a60d1ad..9ffcba6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java
@@ -169,7 +169,6 @@
         state.icon = ResourceIcon.get(state.state == Tile.STATE_ACTIVE
                 ? R.drawable.qs_light_dark_theme_icon_on
                 : R.drawable.qs_light_dark_theme_icon_off);
-        state.showRippleEffect = false;
         state.expandedAccessibilityClassName = Switch.class.getName();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
index 17e72e5..8f9cf4b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
@@ -53,7 +53,8 @@
 
     public static final String TILE_SPEC = "work";
 
-    private final Icon mIcon = ResourceIcon.get(R.drawable.stat_sys_managed_profile_status);
+    private final Icon mIcon = ResourceIcon.get(
+            com.android.internal.R.drawable.stat_sys_managed_profile_status);
 
     private final ManagedProfileController mProfileController;
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
index ad65845..6e1ef91 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
@@ -284,7 +284,6 @@
         mHandler.removeCallbacks(mHideProgressBarRunnable);
         mHandler.removeCallbacks(mHideSearchingRunnable);
         mMobileNetworkLayout.setOnClickListener(null);
-        mMobileDataToggle.setOnCheckedChangeListener(null);
         mConnectedWifListLayout.setOnClickListener(null);
         if (mSecondaryMobileNetworkLayout != null) {
             mSecondaryMobileNetworkLayout.setOnClickListener(null);
@@ -349,18 +348,16 @@
             }
             mInternetDialogController.connectCarrierNetwork();
         });
-        mMobileDataToggle.setOnCheckedChangeListener(
-                (buttonView, isChecked) -> {
-                    if (!isChecked && shouldShowMobileDialog()) {
-                        showTurnOffMobileDialog();
-                    } else if (!shouldShowMobileDialog()) {
-                        if (mInternetDialogController.isMobileDataEnabled() == isChecked) {
-                            return;
-                        }
-                        mInternetDialogController.setMobileDataEnabled(mContext, mDefaultDataSubId,
-                                isChecked, false);
-                    }
-                });
+        mMobileDataToggle.setOnClickListener(v -> {
+            boolean isChecked = mMobileDataToggle.isChecked();
+            if (!isChecked && shouldShowMobileDialog()) {
+                mMobileDataToggle.setChecked(true);
+                showTurnOffMobileDialog();
+            } else if (mInternetDialogController.isMobileDataEnabled() != isChecked) {
+                mInternetDialogController.setMobileDataEnabled(mContext, mDefaultDataSubId,
+                        isChecked, false);
+            }
+        });
         mConnectedWifListLayout.setOnClickListener(this::onClickConnectedWifi);
         mSeeAllLayout.setOnClickListener(this::onClickSeeMoreButton);
         mWiFiToggle.setOnCheckedChangeListener(
@@ -686,9 +683,7 @@
         mAlertDialog = new Builder(mContext)
                 .setTitle(R.string.mobile_data_disable_title)
                 .setMessage(mContext.getString(R.string.mobile_data_disable_message, carrierName))
-                .setNegativeButton(android.R.string.cancel, (d, w) -> {
-                    mMobileDataToggle.setChecked(true);
-                })
+                .setNegativeButton(android.R.string.cancel, (d, w) -> {})
                 .setPositiveButton(
                         com.android.internal.R.string.alert_windows_notification_turn_off_action,
                         (d, w) -> {
@@ -698,7 +693,6 @@
                             Prefs.putBoolean(mContext, QS_HAS_TURNED_OFF_MOBILE_DATA, true);
                         })
                 .create();
-        mAlertDialog.setOnCancelListener(dialog -> mMobileDataToggle.setChecked(true));
         mAlertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
         SystemUIDialog.setShowForAllUsers(mAlertDialog, true);
         SystemUIDialog.registerDismissListener(mAlertDialog);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
index abeb5af..ec12580 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
@@ -172,7 +172,9 @@
     private Executor mExecutor;
     private AccessPointController mAccessPointController;
     private IntentFilter mConnectionStateFilter;
-    private InternetDialogCallback mCallback;
+    @VisibleForTesting
+    @Nullable
+    InternetDialogCallback mCallback;
     private UiEventLogger mUiEventLogger;
     private BroadcastDispatcher mBroadcastDispatcher;
     private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@@ -215,12 +217,16 @@
             new KeyguardUpdateMonitorCallback() {
                 @Override
                 public void onRefreshCarrierInfo() {
-                    mCallback.onRefreshCarrierInfo();
+                    if (mCallback != null) {
+                        mCallback.onRefreshCarrierInfo();
+                    }
                 }
 
                 @Override
                 public void onSimStateChanged(int subId, int slotId, int simState) {
-                    mCallback.onSimStateChanged();
+                    if (mCallback != null) {
+                        mCallback.onSimStateChanged();
+                    }
                 }
             };
 
@@ -331,6 +337,7 @@
         mKeyguardUpdateMonitor.removeCallback(mKeyguardUpdateCallback);
         mConnectivityManager.unregisterNetworkCallback(mConnectivityManagerNetworkCallback);
         mConnectedWifiInternetMonitor.unregisterCallback();
+        mCallback = null;
     }
 
     @VisibleForTesting
@@ -724,7 +731,7 @@
         ActivityLaunchAnimator.Controller controller =
                 mDialogLaunchAnimator.createActivityLaunchController(view);
 
-        if (controller == null) {
+        if (controller == null && mCallback != null) {
             mCallback.dismissDialog();
         }
 
@@ -1095,7 +1102,9 @@
             mHasWifiEntries = false;
         }
 
-        mCallback.onAccessPointsChanged(wifiEntries, connectedEntry, hasMoreWifiEntries);
+        if (mCallback != null) {
+            mCallback.onAccessPointsChanged(wifiEntries, connectedEntry, hasMoreWifiEntries);
+        }
     }
 
     @Override
@@ -1117,34 +1126,46 @@
 
         @Override
         public void onServiceStateChanged(@NonNull ServiceState serviceState) {
-            mCallback.onServiceStateChanged(serviceState);
+            if (mCallback != null) {
+                mCallback.onServiceStateChanged(serviceState);
+            }
         }
 
         @Override
         public void onDataConnectionStateChanged(int state, int networkType) {
-            mCallback.onDataConnectionStateChanged(state, networkType);
+            if (mCallback != null) {
+                mCallback.onDataConnectionStateChanged(state, networkType);
+            }
         }
 
         @Override
         public void onSignalStrengthsChanged(@NonNull SignalStrength signalStrength) {
-            mCallback.onSignalStrengthsChanged(signalStrength);
+            if (mCallback != null) {
+                mCallback.onSignalStrengthsChanged(signalStrength);
+            }
         }
 
         @Override
         public void onDisplayInfoChanged(@NonNull TelephonyDisplayInfo telephonyDisplayInfo) {
             mSubIdTelephonyDisplayInfoMap.put(mSubId, telephonyDisplayInfo);
-            mCallback.onDisplayInfoChanged(telephonyDisplayInfo);
+            if (mCallback != null) {
+                mCallback.onDisplayInfoChanged(telephonyDisplayInfo);
+            }
         }
 
         @Override
         public void onUserMobileDataStateChanged(boolean enabled) {
-            mCallback.onUserMobileDataStateChanged(enabled);
+            if (mCallback != null) {
+                mCallback.onUserMobileDataStateChanged(enabled);
+            }
         }
 
         @Override
         public void onCarrierNetworkChange(boolean active) {
             mCarrierNetworkChangeMode = active;
-            mCallback.onCarrierNetworkChange(active);
+            if (mCallback != null) {
+                mCallback.onCarrierNetworkChange(active);
+            }
         }
     }
 
@@ -1171,14 +1192,18 @@
                 scanWifiAccessPoints();
             }
             // update UI
-            mCallback.onCapabilitiesChanged(network, capabilities);
+            if (mCallback != null) {
+                mCallback.onCapabilitiesChanged(network, capabilities);
+            }
         }
 
         @Override
         @WorkerThread
         public void onLost(@NonNull Network network) {
             mHasEthernet = false;
-            mCallback.onLost(network);
+            if (mCallback != null) {
+                mCallback.onLost(network);
+            }
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 07259c2..207cc139 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -25,11 +25,9 @@
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON;
 
 import static com.android.internal.accessibility.common.ShortcutConstants.CHOOSER_PACKAGE_NAME;
-import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SUPPORTS_WINDOW_CORNERS;
 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SYSUI_PROXY;
 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_UNFOLD_ANIMATION_FORWARDER;
 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_UNLOCK_ANIMATION_CONTROLLER;
-import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_WINDOW_CORNER_RADIUS;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_AWAKE;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DEVICE_DOZING;
@@ -173,8 +171,6 @@
     private boolean mInputFocusTransferStarted;
     private float mInputFocusTransferStartY;
     private long mInputFocusTransferStartMillis;
-    private float mWindowCornerRadius;
-    private boolean mSupportsRoundedCornersOnWindows;
     private int mNavBarMode = NAV_BAR_MODE_3BUTTON;
 
     @VisibleForTesting
@@ -200,8 +196,8 @@
 
         // TODO: change the method signature to use (boolean inputFocusTransferStarted)
         @Override
-        public void onStatusBarMotionEvent(MotionEvent event) {
-            verifyCallerAndClearCallingIdentity("onStatusBarMotionEvent", () -> {
+        public void onStatusBarTouchEvent(MotionEvent event) {
+            verifyCallerAndClearCallingIdentity("onStatusBarTouchEvent", () -> {
                 // TODO move this logic to message queue
                 mCentralSurfacesOptionalLazy.get().ifPresent(centralSurfaces -> {
                     if (event.getActionMasked() == ACTION_DOWN) {
@@ -236,6 +232,13 @@
         }
 
         @Override
+        public void onStatusBarTrackpadEvent(MotionEvent event) {
+            verifyCallerAndClearCallingIdentityPostMain("onStatusBarTrackpadEvent", () ->
+                    mCentralSurfacesOptionalLazy.get().ifPresent(centralSurfaces ->
+                            centralSurfaces.onStatusBarTrackpadEvent(event)));
+        }
+
+        @Override
         public void onBackPressed() {
             verifyCallerAndClearCallingIdentityPostMain("onBackPressed", () -> {
                 sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK);
@@ -345,7 +348,7 @@
 
         @Override
         public void expandNotificationPanel() {
-            verifyCallerAndClearCallingIdentity("expandNotificationPanel",
+            verifyCallerAndClearCallingIdentityPostMain("expandNotificationPanel",
                     () -> mCommandQueue.handleSystemKey(new KeyEvent(KeyEvent.ACTION_DOWN,
                             KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN)));
         }
@@ -353,7 +356,7 @@
         @Override
         public void toggleNotificationPanel() {
             verifyCallerAndClearCallingIdentityPostMain("toggleNotificationPanel", () ->
-                    mCentralSurfacesOptionalLazy.get().ifPresent(CentralSurfaces::togglePanel));
+                    mCommandQueue.togglePanel());
         }
 
         private boolean verifyCaller(String reason) {
@@ -447,8 +450,6 @@
 
             Bundle params = new Bundle();
             params.putBinder(KEY_EXTRA_SYSUI_PROXY, mSysUiProxy.asBinder());
-            params.putFloat(KEY_EXTRA_WINDOW_CORNER_RADIUS, mWindowCornerRadius);
-            params.putBoolean(KEY_EXTRA_SUPPORTS_WINDOW_CORNERS, mSupportsRoundedCornersOnWindows);
             params.putBinder(KEY_EXTRA_UNLOCK_ANIMATION_CONTROLLER,
                     mSysuiUnlockAnimationController.asBinder());
             mUnfoldTransitionProgressForwarder.ifPresent(
@@ -581,9 +582,6 @@
                 com.android.internal.R.string.config_recentsComponentName));
         mQuickStepIntent = new Intent(ACTION_QUICKSTEP)
                 .setPackage(mRecentsComponentName.getPackageName());
-        mWindowCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(mContext);
-        mSupportsRoundedCornersOnWindows = ScreenDecorationsUtils
-                .supportsRoundedCornersOnWindows(mContext.getResources());
         mSysUiState = sysUiState;
         mSysUiState.addCallback(this::notifySystemUiStateFlags);
         mUiEventLogger = uiEventLogger;
@@ -1077,8 +1075,6 @@
         pw.print("  mInputFocusTransferStarted="); pw.println(mInputFocusTransferStarted);
         pw.print("  mInputFocusTransferStartY="); pw.println(mInputFocusTransferStartY);
         pw.print("  mInputFocusTransferStartMillis="); pw.println(mInputFocusTransferStartMillis);
-        pw.print("  mWindowCornerRadius="); pw.println(mWindowCornerRadius);
-        pw.print("  mSupportsRoundedCornersOnWindows="); pw.println(mSupportsRoundedCornersOnWindows);
         pw.print("  mActiveNavBarRegion="); pw.println(mActiveNavBarRegion);
         pw.print("  mNavigationBarSurface="); pw.println(mNavigationBarSurface);
         pw.print("  mNavBarMode="); pw.println(mNavBarMode);
diff --git a/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt b/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt
index 752471d..26c5219 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt
@@ -16,7 +16,8 @@
 
 package com.android.systemui.scene
 
-import com.android.systemui.scene.data.model.SceneContainerConfigModule
+import com.android.systemui.scene.domain.startable.SceneContainerStartableModule
+import com.android.systemui.scene.shared.model.SceneContainerConfigModule
 import com.android.systemui.scene.ui.composable.SceneModule
 import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModelModule
 import dagger.Module
@@ -25,6 +26,7 @@
     includes =
         [
             SceneContainerConfigModule::class,
+            SceneContainerStartableModule::class,
             SceneContainerViewModelModule::class,
             SceneModule::class,
         ],
diff --git a/packages/SystemUI/src/com/android/systemui/scene/data/model/SceneContainerConfig.kt b/packages/SystemUI/src/com/android/systemui/scene/data/model/SceneContainerConfig.kt
deleted file mode 100644
index d0769eb..0000000
--- a/packages/SystemUI/src/com/android/systemui/scene/data/model/SceneContainerConfig.kt
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.scene.data.model
-
-import com.android.systemui.scene.shared.model.SceneKey
-
-/** Models the configuration of a single scene container. */
-data class SceneContainerConfig(
-    /** Container name. Must be unique across all containers in System UI. */
-    val name: String,
-
-    /**
-     * The keys to all scenes in the container, sorted by z-order such that the last one renders on
-     * top of all previous ones. Scene keys within the same container must not repeat but it's okay
-     * to have the same scene keys in different containers.
-     */
-    val sceneKeys: List<SceneKey>,
-
-    /**
-     * The key of the scene that is the initial current scene when the container is first set up,
-     * before taking any application state in to account.
-     */
-    val initialSceneKey: SceneKey,
-) {
-    init {
-        check(sceneKeys.isNotEmpty()) { "A container must have at least one scene key." }
-
-        check(sceneKeys.contains(initialSceneKey)) {
-            "The initial key \"$initialSceneKey\" is not present in this container."
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/data/model/SceneContainerConfigModule.kt b/packages/SystemUI/src/com/android/systemui/scene/data/model/SceneContainerConfigModule.kt
deleted file mode 100644
index 0af8094..0000000
--- a/packages/SystemUI/src/com/android/systemui/scene/data/model/SceneContainerConfigModule.kt
+++ /dev/null
@@ -1,61 +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.scene.data.model
-
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.scene.shared.model.SceneContainerNames
-import com.android.systemui.scene.shared.model.SceneKey
-import dagger.Module
-import dagger.Provides
-import javax.inject.Named
-
-@Module
-object SceneContainerConfigModule {
-
-    @Provides
-    fun containerConfigs(): Map<String, SceneContainerConfig> {
-        return mapOf(
-            SceneContainerNames.SYSTEM_UI_DEFAULT to
-                SceneContainerConfig(
-                    name = SceneContainerNames.SYSTEM_UI_DEFAULT,
-                    // Note that this list is in z-order. The first one is the bottom-most and the
-                    // last
-                    // one is top-most.
-                    sceneKeys =
-                        listOf(
-                            SceneKey.Gone,
-                            SceneKey.Lockscreen,
-                            SceneKey.Bouncer,
-                            SceneKey.Shade,
-                            SceneKey.QuickSettings,
-                        ),
-                    initialSceneKey = SceneKey.Lockscreen,
-                ),
-        )
-    }
-
-    @Provides
-    @SysUISingleton
-    @Named(SceneContainerNames.SYSTEM_UI_DEFAULT)
-    fun provideDefaultSceneContainerConfig(
-        configs: Map<String, SceneContainerConfig>,
-    ): SceneContainerConfig {
-        return checkNotNull(configs[SceneContainerNames.SYSTEM_UI_DEFAULT]) {
-            "No SceneContainerConfig named \"${SceneContainerNames.SYSTEM_UI_DEFAULT}\"."
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt b/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt
index 61b162b..0a86d35 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt
@@ -16,9 +16,10 @@
 
 package com.android.systemui.scene.data.repository
 
-import com.android.systemui.scene.data.model.SceneContainerConfig
+import com.android.systemui.scene.shared.model.SceneContainerConfig
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.scene.shared.model.SceneTransitionModel
 import javax.inject.Inject
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
@@ -28,11 +29,9 @@
 class SceneContainerRepository
 @Inject
 constructor(
-    containerConfigurations: Set<SceneContainerConfig>,
+    private val containerConfigByName: Map<String, SceneContainerConfig>,
 ) {
 
-    private val containerConfigByName: Map<String, SceneContainerConfig> =
-        containerConfigurations.associateBy { config -> config.name }
     private val containerVisibilityByName: Map<String, MutableStateFlow<Boolean>> =
         containerConfigByName
             .map { (containerName, _) -> containerName to MutableStateFlow(true) }
@@ -47,21 +46,9 @@
         containerConfigByName
             .map { (containerName, _) -> containerName to MutableStateFlow(1f) }
             .toMap()
-
-    init {
-        val repeatedContainerNames =
-            containerConfigurations
-                .groupingBy { config -> config.name }
-                .eachCount()
-                .filter { (_, count) -> count > 1 }
-        check(repeatedContainerNames.isEmpty()) {
-            "Container names must be unique. The following container names appear more than once: ${
-                repeatedContainerNames
-                        .map { (name, count) -> "\"$name\" appears $count times" }
-                        .joinToString(", ")
-            }"
-        }
-    }
+    private val sceneTransitionByContainerName:
+        Map<String, MutableStateFlow<SceneTransitionModel?>> =
+        containerConfigByName.keys.associateWith { MutableStateFlow(null) }
 
     /**
      * Returns the keys to all scenes in the container with the given name.
@@ -87,11 +74,43 @@
         currentSceneByContainerName.setValue(containerName, scene)
     }
 
+    /** Sets the scene transition in the container with the given name. */
+    fun setSceneTransition(containerName: String, from: SceneKey, to: SceneKey) {
+        check(allSceneKeys(containerName).contains(from)) {
+            """
+                Cannot set current scene key to "$from". The container "$containerName" does
+                not contain a scene with that key.
+            """
+                .trimIndent()
+        }
+        check(allSceneKeys(containerName).contains(to)) {
+            """
+                Cannot set current scene key to "$to". The container "$containerName" does
+                not contain a scene with that key.
+            """
+                .trimIndent()
+        }
+
+        sceneTransitionByContainerName.setValue(
+            containerName,
+            SceneTransitionModel(from = from, to = to)
+        )
+    }
+
     /** The current scene in the container with the given name. */
     fun currentScene(containerName: String): StateFlow<SceneModel> {
         return currentSceneByContainerName.mutableOrError(containerName).asStateFlow()
     }
 
+    /**
+     * Scene transitions as pairs of keys. A new value is emitted exactly once, each time a scene
+     * transition occurs. The flow begins with a `null` value at first, because the initial scene is
+     * not something that we transition to from another scene.
+     */
+    fun sceneTransitions(containerName: String): StateFlow<SceneTransitionModel?> {
+        return sceneTransitionByContainerName.mutableOrError(containerName).asStateFlow()
+    }
+
     /** Sets whether the container with the given name is visible. */
     fun setVisible(containerName: String, isVisible: Boolean) {
         containerVisibilityByName.setValue(containerName, isVisible)
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
index 1e55975..4582370 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
@@ -20,10 +20,21 @@
 import com.android.systemui.scene.data.repository.SceneContainerRepository
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.scene.shared.model.SceneTransitionModel
 import javax.inject.Inject
 import kotlinx.coroutines.flow.StateFlow
 
-/** Business logic and app state accessors for the scene framework. */
+/**
+ * Generic business logic and app state accessors for the scene framework.
+ *
+ * Note that scene container specific business logic does not belong in this class. Instead, it
+ * should be hoisted to a class that is specific to that scene container, for an example, please see
+ * [SystemUiDefaultSceneContainerStartable].
+ *
+ * Also note that this class should not depend on state or logic of other modules or features.
+ * Instead, other feature modules should depend on and call into this class when their parts of the
+ * application state change.
+ */
 @SysUISingleton
 class SceneInteractor
 @Inject
@@ -43,7 +54,9 @@
 
     /** Sets the scene in the container with the given name. */
     fun setCurrentScene(containerName: String, scene: SceneModel) {
+        val currentSceneKey = repository.currentScene(containerName).value.key
         repository.setCurrentScene(containerName, scene)
+        repository.setSceneTransition(containerName, from = currentSceneKey, to = scene.key)
     }
 
     /** The current scene in the container with the given name. */
@@ -70,4 +83,13 @@
     fun sceneTransitionProgress(containerName: String): StateFlow<Float> {
         return repository.sceneTransitionProgress(containerName)
     }
+
+    /**
+     * Scene transitions as pairs of keys. A new value is emitted exactly once, each time a scene
+     * transition occurs. The flow begins with a `null` value at first, because the initial scene is
+     * not something that we transition to from another scene.
+     */
+    fun sceneTransitions(containerName: String): StateFlow<SceneTransitionModel?> {
+        return repository.sceneTransitions(containerName)
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartableModule.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartableModule.kt
new file mode 100644
index 0000000..b3de2d1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartableModule.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.systemui.scene.domain.startable
+
+import com.android.systemui.CoreStartable
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+
+@Module
+interface SceneContainerStartableModule {
+
+    @Binds
+    @IntoMap
+    @ClassKey(SystemUiDefaultSceneContainerStartable::class)
+    fun bind(impl: SystemUiDefaultSceneContainerStartable): CoreStartable
+}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SystemUiDefaultSceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SystemUiDefaultSceneContainerStartable.kt
new file mode 100644
index 0000000..b23c4ec
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SystemUiDefaultSceneContainerStartable.kt
@@ -0,0 +1,134 @@
+/*
+ * 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.systemui.scene.domain.startable
+
+import com.android.systemui.CoreStartable
+import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.shared.model.WakefulnessState
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.model.SceneContainerNames
+import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.SceneModel
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
+
+/**
+ * Hooks up business logic that manipulates the state of the [SceneInteractor] for the default
+ * system UI scene container (the one named [SceneContainerNames.SYSTEM_UI_DEFAULT]) based on state
+ * from other systems.
+ */
+@SysUISingleton
+class SystemUiDefaultSceneContainerStartable
+@Inject
+constructor(
+    @Application private val applicationScope: CoroutineScope,
+    private val sceneInteractor: SceneInteractor,
+    private val authenticationInteractor: AuthenticationInteractor,
+    private val keyguardInteractor: KeyguardInteractor,
+    private val featureFlags: FeatureFlags,
+) : CoreStartable {
+
+    override fun start() {
+        if (featureFlags.isEnabled(Flags.SCENE_CONTAINER)) {
+            hydrateVisibility()
+            automaticallySwitchScenes()
+        }
+    }
+
+    /** Updates the visibility of the scene container based on the current scene. */
+    private fun hydrateVisibility() {
+        applicationScope.launch {
+            sceneInteractor
+                .currentScene(CONTAINER_NAME)
+                .map { it.key }
+                .distinctUntilChanged()
+                .collect { sceneKey ->
+                    sceneInteractor.setVisible(CONTAINER_NAME, sceneKey != SceneKey.Gone)
+                }
+        }
+    }
+
+    /** Switches between scenes based on ever-changing application state. */
+    private fun automaticallySwitchScenes() {
+        applicationScope.launch {
+            authenticationInteractor.isUnlocked
+                .map { isUnlocked ->
+                    val currentSceneKey = sceneInteractor.currentScene(CONTAINER_NAME).value.key
+                    val isBypassEnabled = authenticationInteractor.isBypassEnabled()
+                    when {
+                        isUnlocked ->
+                            when (currentSceneKey) {
+                                // When the device becomes unlocked in Bouncer, go to Gone.
+                                is SceneKey.Bouncer -> SceneKey.Gone
+                                // When the device becomes unlocked in Lockscreen, go to Gone if
+                                // bypass is enabled.
+                                is SceneKey.Lockscreen -> SceneKey.Gone.takeIf { isBypassEnabled }
+                                // We got unlocked while on a scene that's not Lockscreen or
+                                // Bouncer, no need to change scenes.
+                                else -> null
+                            }
+                        // When the device becomes locked, to Lockscreen.
+                        !isUnlocked ->
+                            when (currentSceneKey) {
+                                // Already on lockscreen or bouncer, no need to change scenes.
+                                is SceneKey.Lockscreen,
+                                is SceneKey.Bouncer -> null
+                                // We got locked while on a scene that's not Lockscreen or Bouncer,
+                                // go to Lockscreen.
+                                else -> SceneKey.Lockscreen
+                            }
+                        else -> null
+                    }
+                }
+                .filterNotNull()
+                .collect { targetSceneKey -> switchToScene(targetSceneKey) }
+        }
+
+        applicationScope.launch {
+            keyguardInteractor.wakefulnessModel
+                .map { it.state == WakefulnessState.ASLEEP }
+                .distinctUntilChanged()
+                .collect { isAsleep ->
+                    if (isAsleep) {
+                        // When the device goes to sleep, reset the current scene.
+                        val isUnlocked = authenticationInteractor.isUnlocked.value
+                        switchToScene(if (isUnlocked) SceneKey.Gone else SceneKey.Lockscreen)
+                    }
+                }
+        }
+    }
+
+    private fun switchToScene(targetSceneKey: SceneKey) {
+        sceneInteractor.setCurrentScene(
+            containerName = CONTAINER_NAME,
+            scene = SceneModel(targetSceneKey),
+        )
+    }
+
+    companion object {
+        private const val CONTAINER_NAME = SceneContainerNames.SYSTEM_UI_DEFAULT
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneContainerConfig.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneContainerConfig.kt
new file mode 100644
index 0000000..0327edb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneContainerConfig.kt
@@ -0,0 +1,44 @@
+/*
+ * 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.systemui.scene.shared.model
+
+/** Models the configuration of a single scene container. */
+data class SceneContainerConfig(
+    /** Container name. Must be unique across all containers in System UI. */
+    val name: String,
+
+    /**
+     * The keys to all scenes in the container, sorted by z-order such that the last one renders on
+     * top of all previous ones. Scene keys within the same container must not repeat but it's okay
+     * to have the same scene keys in different containers.
+     */
+    val sceneKeys: List<SceneKey>,
+
+    /**
+     * The key of the scene that is the initial current scene when the container is first set up,
+     * before taking any application state in to account.
+     */
+    val initialSceneKey: SceneKey,
+) {
+    init {
+        check(sceneKeys.isNotEmpty()) { "A container must have at least one scene key." }
+
+        check(sceneKeys.contains(initialSceneKey)) {
+            "The initial key \"$initialSceneKey\" is not present in this container."
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneContainerConfigModule.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneContainerConfigModule.kt
new file mode 100644
index 0000000..7562a5a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneContainerConfigModule.kt
@@ -0,0 +1,59 @@
+/*
+ * 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.systemui.scene.shared.model
+
+import com.android.systemui.dagger.SysUISingleton
+import dagger.Module
+import dagger.Provides
+import javax.inject.Named
+
+@Module
+object SceneContainerConfigModule {
+
+    @Provides
+    fun containerConfigs(): Map<String, SceneContainerConfig> {
+        return mapOf(
+            SceneContainerNames.SYSTEM_UI_DEFAULT to
+                SceneContainerConfig(
+                    name = SceneContainerNames.SYSTEM_UI_DEFAULT,
+                    // Note that this list is in z-order. The first one is the bottom-most and the
+                    // last
+                    // one is top-most.
+                    sceneKeys =
+                        listOf(
+                            SceneKey.Gone,
+                            SceneKey.Lockscreen,
+                            SceneKey.Bouncer,
+                            SceneKey.Shade,
+                            SceneKey.QuickSettings,
+                        ),
+                    initialSceneKey = SceneKey.Lockscreen,
+                ),
+        )
+    }
+
+    @Provides
+    @SysUISingleton
+    @Named(SceneContainerNames.SYSTEM_UI_DEFAULT)
+    fun provideDefaultSceneContainerConfig(
+        configs: Map<String, SceneContainerConfig>,
+    ): SceneContainerConfig {
+        return checkNotNull(configs[SceneContainerNames.SYSTEM_UI_DEFAULT]) {
+            "No SceneContainerConfig named \"${SceneContainerNames.SYSTEM_UI_DEFAULT}\"."
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneTransitionModel.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneTransitionModel.kt
new file mode 100644
index 0000000..c8f46a7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneTransitionModel.kt
@@ -0,0 +1,25 @@
+/*
+ * 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.systemui.scene.shared.model
+
+/** Models a transition between two scenes. */
+data class SceneTransitionModel(
+    /** The scene we transitioned away from. */
+    val from: SceneKey,
+    /** The scene we transitioned into. */
+    val to: SceneKey,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/scene/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/startable/SceneContainerStartable.kt
new file mode 100644
index 0000000..a29e92a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/scene/startable/SceneContainerStartable.kt
@@ -0,0 +1,60 @@
+/*
+ * 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.systemui.scene.startable
+
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.scene.shared.model.Scene
+import com.android.systemui.scene.shared.model.SceneContainerConfig
+import com.android.systemui.scene.shared.model.SceneContainerNames
+import com.android.systemui.scene.ui.view.SceneWindowRootView
+import com.android.systemui.scene.ui.view.WindowRootView
+import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+import javax.inject.Inject
+import javax.inject.Named
+
+@SysUISingleton
+class SceneContainerStartable
+@Inject
+constructor(
+    private val view: WindowRootView,
+    @Named(SceneContainerNames.SYSTEM_UI_DEFAULT) private val viewModel: SceneContainerViewModel,
+    @Named(SceneContainerNames.SYSTEM_UI_DEFAULT) private val containerConfig: SceneContainerConfig,
+    @Named(SceneContainerNames.SYSTEM_UI_DEFAULT)
+    private val scenes: Set<@JvmSuppressWildcards Scene>,
+) : CoreStartable {
+
+    override fun start() {
+        (view as? SceneWindowRootView)?.init(
+            viewModel = viewModel,
+            containerConfig = containerConfig,
+            scenes = scenes,
+        )
+    }
+}
+
+@Module
+interface SceneContainerStartableModule {
+    @Binds
+    @IntoMap
+    @ClassKey(SceneContainerStartable::class)
+    fun bind(impl: SceneContainerStartable): CoreStartable
+}
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
new file mode 100644
index 0000000..c456be6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt
@@ -0,0 +1,39 @@
+package com.android.systemui.scene.ui.view
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.View
+import com.android.systemui.scene.shared.model.Scene
+import com.android.systemui.scene.shared.model.SceneContainerConfig
+import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
+
+/** A root view of the main SysUI window that supports scenes. */
+class SceneWindowRootView(
+    context: Context,
+    attrs: AttributeSet?,
+) :
+    WindowRootView(
+        context,
+        attrs,
+    ) {
+    fun init(
+        viewModel: SceneContainerViewModel,
+        containerConfig: SceneContainerConfig,
+        scenes: Set<Scene>,
+    ) {
+        SceneWindowRootViewBinder.bind(
+            view = this@SceneWindowRootView,
+            viewModel = viewModel,
+            containerConfig = containerConfig,
+            scenes = scenes,
+            onVisibilityChangedInternal = { isVisible ->
+                super.setVisibility(if (isVisible) View.VISIBLE else View.INVISIBLE)
+            }
+        )
+    }
+
+    override fun setVisibility(visibility: Int) {
+        // Do nothing. We don't want external callers to invoke this. Instead, we drive our own
+        // visibility from our view-binder.
+    }
+}
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
new file mode 100644
index 0000000..f9324a9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
@@ -0,0 +1,125 @@
+/*
+ * 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.systemui.scene.ui.view
+
+import android.view.Gravity
+import android.view.View
+import android.view.ViewGroup
+import android.widget.FrameLayout
+import androidx.activity.OnBackPressedDispatcher
+import androidx.activity.OnBackPressedDispatcherOwner
+import androidx.activity.setViewTreeOnBackPressedDispatcherOwner
+import androidx.core.view.isVisible
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.R
+import com.android.systemui.compose.ComposeFacade
+import com.android.systemui.lifecycle.repeatWhenAttached
+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 java.time.Instant
+import kotlinx.coroutines.launch
+
+object SceneWindowRootViewBinder {
+
+    /** Binds between the view and view-model pertaining to a specific scene container. */
+    fun bind(
+        view: ViewGroup,
+        viewModel: SceneContainerViewModel,
+        containerConfig: SceneContainerConfig,
+        scenes: Set<Scene>,
+        onVisibilityChangedInternal: (isVisible: Boolean) -> Unit,
+    ) {
+        val unsortedSceneByKey: Map<SceneKey, Scene> = scenes.associateBy { scene -> scene.key }
+        val sortedSceneByKey: Map<SceneKey, Scene> = buildMap {
+            containerConfig.sceneKeys.forEach { sceneKey ->
+                val scene =
+                    checkNotNull(unsortedSceneByKey[sceneKey]) {
+                        "Scene not found for key \"$sceneKey\"!"
+                    }
+
+                put(sceneKey, scene)
+            }
+        }
+
+        view.repeatWhenAttached {
+            lifecycleScope.launch {
+                repeatOnLifecycle(Lifecycle.State.CREATED) {
+                    view.setViewTreeOnBackPressedDispatcherOwner(
+                        object : OnBackPressedDispatcherOwner {
+                            override val onBackPressedDispatcher =
+                                OnBackPressedDispatcher().apply {
+                                    setOnBackInvokedDispatcher(
+                                        view.viewRootImpl.onBackInvokedDispatcher
+                                    )
+                                }
+
+                            override val lifecycle: Lifecycle = this@repeatWhenAttached.lifecycle
+                        }
+                    )
+
+                    view.addView(
+                        ComposeFacade.createSceneContainerView(
+                            context = view.context,
+                            viewModel = viewModel,
+                            sceneByKey = sortedSceneByKey,
+                        )
+                    )
+
+                    val legacyView = view.requireViewById<View>(R.id.legacy_window_root)
+                    view.addView(createVisibilityToggleView(legacyView))
+
+                    launch {
+                        viewModel.isVisible.collect { isVisible ->
+                            onVisibilityChangedInternal(isVisible)
+                        }
+                    }
+                }
+
+                // Here when destroyed.
+                view.removeAllViews()
+            }
+        }
+    }
+
+    private var clickCount = 0
+    private var lastClick = Instant.now()
+
+    /**
+     * A temporary UI to toggle on/off the visibility of the given [otherView]. It is toggled by
+     * tapping 5 times in quick succession on the device camera (top center).
+     */
+    // TODO(b/291321285): Remove this when the Flexiglass UI is mature enough to turn off legacy
+    //  SysUI altogether.
+    private fun createVisibilityToggleView(otherView: View): View {
+        val toggleView = View(otherView.context)
+        toggleView.layoutParams = FrameLayout.LayoutParams(200, 200, Gravity.CENTER_HORIZONTAL)
+        toggleView.setOnClickListener {
+            val now = Instant.now()
+            clickCount = if (now.minusSeconds(2) > lastClick) 1 else clickCount + 1
+            if (clickCount == 5) {
+                otherView.isVisible = !otherView.isVisible
+                clickCount = 0
+            }
+            lastClick = now
+        }
+        return toggleView
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootView.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootView.kt
new file mode 100644
index 0000000..860ebcb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootView.kt
@@ -0,0 +1,39 @@
+package com.android.systemui.scene.ui.view
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.View
+import android.widget.FrameLayout
+import com.android.systemui.compose.ComposeFacade
+
+/** A view that can serve as the root of the main SysUI window. */
+open class WindowRootView(
+    context: Context,
+    attrs: AttributeSet?,
+) :
+    FrameLayout(
+        context,
+        attrs,
+    ) {
+
+    override fun onAttachedToWindow() {
+        super.onAttachedToWindow()
+
+        if (ComposeFacade.isComposeAvailable() && isRoot()) {
+            ComposeFacade.composeInitializer().onAttachedToWindow(this)
+        }
+    }
+
+    override fun onDetachedFromWindow() {
+        super.onDetachedFromWindow()
+
+        if (ComposeFacade.isComposeAvailable() && isRoot()) {
+            ComposeFacade.composeInitializer().onDetachedFromWindow(this)
+        }
+    }
+
+    private fun isRoot(): Boolean {
+        // TODO(b/283300105): remove this check once there's only one subclass of WindowRootView.
+        return parent.let { it !is View || it.id == android.R.id.content }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionProxyReceiver.java b/packages/SystemUI/src/com/android/systemui/screenshot/ActionProxyReceiver.java
index 3aefcb3..7e234ae 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ActionProxyReceiver.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionProxyReceiver.java
@@ -71,6 +71,8 @@
             ActivityOptions opts = ActivityOptions.makeBasic();
             opts.setDisallowEnterPictureInPictureWhileLaunching(
                     intent.getBooleanExtra(EXTRA_DISALLOW_ENTER_PIP, false));
+            opts.setPendingIntentBackgroundActivityStartMode(
+                    ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
             try {
                 actionIntent.send(context, 0, null, null, null, null, opts.toBundle());
                 if (intent.getBooleanExtra(ScreenshotController.EXTRA_OVERRIDE_TRANSITION, false)) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java b/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java
index a9cecaa..6f2256e 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java
@@ -117,18 +117,22 @@
 
     @Override
     protected Parcelable onSaveInstanceState() {
+        Log.d(TAG, "onSaveInstanceState");
         Parcelable superState = super.onSaveInstanceState();
 
         SavedState ss = new SavedState(superState);
         ss.mCrop = mCrop;
+        Log.d(TAG, "saving mCrop=" + mCrop);
+
         return ss;
     }
 
     @Override
     protected void onRestoreInstanceState(Parcelable state) {
+        Log.d(TAG, "onRestoreInstanceState");
         SavedState ss = (SavedState) state;
         super.onRestoreInstanceState(ss.getSuperState());
-
+        Log.d(TAG, "restoring mCrop=" + ss.mCrop + " (was " + mCrop + ")");
         mCrop = ss.mCrop;
     }
 
@@ -242,6 +246,7 @@
      * Set the given boundary to the given value without animation.
      */
     public void setBoundaryPosition(CropBoundary boundary, float position) {
+        Log.i(TAG, "setBoundaryPosition: " + boundary + ", position=" + position);
         position = (float) getAllowedValues(boundary).clamp(position);
         switch (boundary) {
             case TOP:
@@ -260,6 +265,7 @@
                 Log.w(TAG, "No boundary selected");
                 break;
         }
+        Log.i(TAG,  "Updated mCrop: " + mCrop);
 
         invalidate();
     }
@@ -350,26 +356,31 @@
         mCropInteractionListener = listener;
     }
 
-    private Range getAllowedValues(CropBoundary boundary) {
+    private Range<Float> getAllowedValues(CropBoundary boundary) {
+        float upper = 0f;
+        float lower = 1f;
         switch (boundary) {
             case TOP:
-                return new Range<>(0f,
-                        mCrop.bottom - pixelDistanceToFraction(mCropTouchMargin,
-                                CropBoundary.BOTTOM));
+                lower = 0f;
+                upper = mCrop.bottom - pixelDistanceToFraction(mCropTouchMargin,
+                        CropBoundary.BOTTOM);
+                break;
             case BOTTOM:
-                return new Range<>(
-                        mCrop.top + pixelDistanceToFraction(mCropTouchMargin,
-                                CropBoundary.TOP), 1f);
+                lower = mCrop.top + pixelDistanceToFraction(mCropTouchMargin, CropBoundary.TOP);
+                upper = 1;
+                break;
             case LEFT:
-                return new Range<>(0f,
-                        mCrop.right - pixelDistanceToFraction(mCropTouchMargin,
-                                CropBoundary.RIGHT));
+                lower = 0f;
+                upper = mCrop.right - pixelDistanceToFraction(mCropTouchMargin, CropBoundary.RIGHT);
+                break;
             case RIGHT:
-                return new Range<>(
-                        mCrop.left + pixelDistanceToFraction(mCropTouchMargin,
-                                CropBoundary.LEFT), 1f);
+                lower = mCrop.left + pixelDistanceToFraction(mCropTouchMargin, CropBoundary.LEFT);
+                upper = 1;
+                break;
         }
-        return null;
+        Log.i(TAG, "getAllowedValues: " + boundary + ", "
+                + "result=[lower=" + lower + ", upper=" + upper + "]");
+        return new Range<>(lower, upper);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
index 4bc7ec8..e6e1fac 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
@@ -36,17 +36,18 @@
 import android.util.Log;
 import android.view.ScrollCaptureResponse;
 import android.view.View;
-import android.view.ViewTreeObserver;
 import android.widget.ImageView;
 
 import androidx.constraintlayout.widget.ConstraintLayout;
 
 import com.android.internal.app.ChooserActivity;
 import com.android.internal.logging.UiEventLogger;
+import com.android.internal.view.OneShotPreDrawListener;
 import com.android.systemui.R;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.screenshot.CropView.CropBoundary;
 import com.android.systemui.screenshot.ScrollCaptureController.LongScreenshot;
 import com.android.systemui.settings.UserTracker;
 
@@ -215,6 +216,7 @@
         mPreview.setImageDrawable(drawable);
         mMagnifierView.setDrawable(mLongScreenshot.getDrawable(),
                 mLongScreenshot.getWidth(), mLongScreenshot.getHeight());
+        Log.i(TAG, "Completed: " + longScreenshot);
         // Original boundaries go from the image tile set's y=0 to y=pageSize, so
         // we animate to that as a starting crop position.
         float topFraction = Math.max(0,
@@ -223,31 +225,26 @@
                 1 - (mLongScreenshot.getBottom() - mLongScreenshot.getPageHeight())
                         / (float) mLongScreenshot.getHeight());
 
+        Log.i(TAG, "topFraction: " + topFraction);
+        Log.i(TAG, "bottomFraction: " + bottomFraction);
+
         mEnterTransitionView.setImageDrawable(drawable);
-        mEnterTransitionView.getViewTreeObserver().addOnPreDrawListener(
-                new ViewTreeObserver.OnPreDrawListener() {
-                    @Override
-                    public boolean onPreDraw() {
-                        mEnterTransitionView.getViewTreeObserver().removeOnPreDrawListener(this);
-                        updateImageDimensions();
-                        mEnterTransitionView.post(() -> {
-                            Rect dest = new Rect();
-                            mEnterTransitionView.getBoundsOnScreen(dest);
-                            mLongScreenshotHolder.takeTransitionDestinationCallback()
-                                    .setTransitionDestination(dest, () -> {
-                                        mPreview.animate().alpha(1f);
-                                        mCropView.setBoundaryPosition(
-                                                CropView.CropBoundary.TOP, topFraction);
-                                        mCropView.setBoundaryPosition(
-                                                CropView.CropBoundary.BOTTOM, bottomFraction);
-                                        mCropView.animateEntrance();
-                                        mCropView.setVisibility(View.VISIBLE);
-                                        setButtonsEnabled(true);
-                                    });
+        OneShotPreDrawListener.add(mEnterTransitionView, () -> {
+            updateImageDimensions();
+            mEnterTransitionView.post(() -> {
+                Rect dest = new Rect();
+                mEnterTransitionView.getBoundsOnScreen(dest);
+                mLongScreenshotHolder.takeTransitionDestinationCallback()
+                        .setTransitionDestination(dest, () -> {
+                            mPreview.animate().alpha(1f);
+                            mCropView.setBoundaryPosition(CropBoundary.TOP, topFraction);
+                            mCropView.setBoundaryPosition(CropBoundary.BOTTOM, bottomFraction);
+                            mCropView.animateEntrance();
+                            mCropView.setVisibility(View.VISIBLE);
+                            setButtonsEnabled(true);
                         });
-                        return true;
-                    }
-                });
+            });
+        });
 
         // Immediately export to temp image file for saved state
         mCacheSaveFuture = mImageExporter.exportToRawFile(mBackgroundExecutor,
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/OverlayActionChip.java b/packages/SystemUI/src/com/android/systemui/screenshot/OverlayActionChip.java
index 13678b0..9e8ea3a 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/OverlayActionChip.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/OverlayActionChip.java
@@ -99,6 +99,8 @@
             try {
                 BroadcastOptions options = BroadcastOptions.makeBasic();
                 options.setInteractive(true);
+                options.setPendingIntentBackgroundActivityStartMode(
+                        BroadcastOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
                 intent.send(options.toBundle());
                 finisher.run();
             } catch (PendingIntent.CanceledException e) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java
index 93e5021..e93f737 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java
@@ -365,6 +365,9 @@
         @Override
         public void onImageAvailable(ImageReader reader) {
             synchronized (mLock) {
+                if (mCapturedImage != null) {
+                    mCapturedImage.close();
+                }
                 mCapturedImage = mReader.acquireLatestImage();
                 if (mCapturedArea != null) {
                     completeCaptureRequest();
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
index 30a0b8f..bb34ede 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
@@ -130,8 +130,14 @@
 
         @Override
         public String toString() {
-            return "LongScreenshot{w=" + mImageTileSet.getWidth()
-                    + ", h=" + mImageTileSet.getHeight() + "}";
+            return "LongScreenshot{"
+                    + "l=" + mImageTileSet.getLeft() + ", "
+                    + "t=" + mImageTileSet.getTop() + ", "
+                    + "r=" + mImageTileSet.getRight() + ", "
+                    + "b=" + mImageTileSet.getBottom() + ", "
+                    + "w=" + mImageTileSet.getWidth() + ", "
+                    + "h=" + mImageTileSet.getHeight()
+                    + "}";
         }
 
         public Drawable getDrawable() {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsReceiver.java b/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsReceiver.java
index 9761f59..ef58b9d 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsReceiver.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsReceiver.java
@@ -55,7 +55,8 @@
             Log.d(TAG, "Executing smart action [" + actionType + "]:" + pendingIntent.getIntent());
         }
         ActivityOptions opts = ActivityOptions.makeBasic();
-
+        opts.setPendingIntentBackgroundActivityStartMode(
+                ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
         try {
             pendingIntent.send(context, 0, fillIn, null, null, null, opts.toBundle());
         } catch (PendingIntent.CanceledException e) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsCrossProcessHelper.java b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsCrossProcessHelper.java
index afc8bff..7de22b1 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsCrossProcessHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsCrossProcessHelper.java
@@ -19,7 +19,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.Bitmap;
-import android.os.UserManager;
+import android.os.UserHandle;
 
 import androidx.annotation.Nullable;
 
@@ -39,15 +39,14 @@
     private final DisplayTracker mDisplayTracker;
 
     @Inject
-    AppClipsCrossProcessHelper(@Application Context context, UserManager userManager,
-            DisplayTracker displayTracker) {
+    AppClipsCrossProcessHelper(@Application Context context, DisplayTracker displayTracker) {
         // Start a service as main user so that even if the app clips activity is running as work
         // profile user the service is able to use correct instance of Bubbles to grab a screenshot
         // excluding the bubble layer.
         mProxyConnector = new ServiceConnector.Impl<>(context,
                 new Intent(context, AppClipsScreenshotHelperService.class),
                 Context.BIND_AUTO_CREATE | Context.BIND_WAIVE_PRIORITY
-                        | Context.BIND_NOT_VISIBLE, userManager.getMainUser().getIdentifier(),
+                        | Context.BIND_NOT_VISIBLE, UserHandle.USER_SYSTEM,
                 IAppClipsScreenshotHelperService.Stub::asInterface);
         mDisplayTracker = displayTracker;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsScreenshotHelperService.java b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsScreenshotHelperService.java
index 4c01315..4cc2f62 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsScreenshotHelperService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsScreenshotHelperService.java
@@ -33,6 +33,11 @@
 /**
  * A helper service that runs in SysUI process and helps {@link AppClipsActivity} which runs in its
  * own separate process take a screenshot.
+ *
+ * <p>Note: This service always runs in the SysUI process running on the system user irrespective of
+ * which user started the service. This is required so that the correct instance of {@link Bubbles}
+ * instance is injected. This is set via attribute {@code android:singleUser=”true”} in
+ * AndroidManifest.
  */
 public class AppClipsScreenshotHelperService extends Service {
 
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsService.java b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsService.java
index 3949492..dce8c81 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsService.java
@@ -16,6 +16,11 @@
 
 package com.android.systemui.screenshot.appclips;
 
+import static android.content.Intent.CAPTURE_CONTENT_FOR_NOTE_BLOCKED_BY_ADMIN;
+import static android.content.Intent.CAPTURE_CONTENT_FOR_NOTE_FAILED;
+import static android.content.Intent.CAPTURE_CONTENT_FOR_NOTE_SUCCESS;
+import static android.content.Intent.CAPTURE_CONTENT_FOR_NOTE_WINDOW_MODE_UNSUPPORTED;
+
 import static com.android.systemui.flags.Flags.SCREENSHOT_APP_CLIPS;
 
 import android.app.Activity;
@@ -25,17 +30,12 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.Intent.CaptureContentForNoteStatusCodes;
 import android.content.res.Resources;
 import android.os.IBinder;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.util.Log;
 
 import androidx.annotation.Nullable;
-import androidx.annotation.VisibleForTesting;
 
-import com.android.internal.infra.AndroidFuture;
-import com.android.internal.infra.ServiceConnector;
 import com.android.internal.statusbar.IAppClipsService;
 import com.android.systemui.R;
 import com.android.systemui.dagger.qualifiers.Application;
@@ -43,73 +43,36 @@
 import com.android.wm.shell.bubbles.Bubbles;
 
 import java.util.Optional;
-import java.util.concurrent.ExecutionException;
 
 import javax.inject.Inject;
 
 /**
  * A service that communicates with {@link StatusBarManager} to support the
- * {@link StatusBarManager#canLaunchCaptureContentActivityForNote(Activity)} API.
+ * {@link StatusBarManager#canLaunchCaptureContentActivityForNote(Activity)} API. Also used by
+ * {@link AppClipsTrampolineActivity} to query if an app should be allowed to user App Clips.
+ *
+ * <p>Note: This service always runs in the SysUI process running on the system user irrespective of
+ * which user started the service. This is required so that the correct instance of {@link Bubbles}
+ * instance is injected. This is set via attribute {@code android:singleUser=”true”} in
+ * AndroidManifest.
  */
 public class AppClipsService extends Service {
 
-    private static final String TAG = AppClipsService.class.getSimpleName();
-
     @Application private final Context mContext;
     private final FeatureFlags mFeatureFlags;
     private final Optional<Bubbles> mOptionalBubbles;
     private final DevicePolicyManager mDevicePolicyManager;
-    private final UserManager mUserManager;
-
     private final boolean mAreTaskAndTimeIndependentPrerequisitesMet;
 
-    @VisibleForTesting()
-    @Nullable ServiceConnector<IAppClipsService> mProxyConnectorToMainProfile;
-
     @Inject
     public AppClipsService(@Application Context context, FeatureFlags featureFlags,
-            Optional<Bubbles> optionalBubbles, DevicePolicyManager devicePolicyManager,
-            UserManager userManager) {
+            Optional<Bubbles> optionalBubbles, DevicePolicyManager devicePolicyManager) {
         mContext = context;
         mFeatureFlags = featureFlags;
         mOptionalBubbles = optionalBubbles;
         mDevicePolicyManager = devicePolicyManager;
-        mUserManager = userManager;
-
-        // The consumer of this service are apps that call through StatusBarManager API to query if
-        // it can use app clips API. Since these apps can be launched as work profile users, this
-        // service will start as work profile user. SysUI doesn't share injected instances for
-        // different users. This is why the bubbles instance injected will be incorrect. As the apps
-        // don't generally have permission to connect to a service running as different user, we
-        // start a proxy connection to communicate with the main user's version of this service.
-        if (mUserManager.isManagedProfile()) {
-            // No need to check for prerequisites in this case as those are incorrect for work
-            // profile user instance of the service and the main user version of the service will
-            // take care of this check.
-            mAreTaskAndTimeIndependentPrerequisitesMet = false;
-
-            // Get the main user so that we can connect to the main user's version of the service.
-            UserHandle mainUser = mUserManager.getMainUser();
-            if (mainUser == null) {
-                // If main user is not available there isn't much we can do, no apps can use app
-                // clips.
-                return;
-            }
-
-            // Set up the connection to be used later during onBind callback.
-            mProxyConnectorToMainProfile =
-                    new ServiceConnector.Impl<>(
-                            context,
-                            new Intent(context, AppClipsService.class),
-                            Context.BIND_AUTO_CREATE | Context.BIND_WAIVE_PRIORITY
-                                    | Context.BIND_NOT_VISIBLE,
-                            mainUser.getIdentifier(),
-                            IAppClipsService.Stub::asInterface);
-            return;
-        }
 
         mAreTaskAndTimeIndependentPrerequisitesMet = checkIndependentVariables();
-        mProxyConnectorToMainProfile = null;
     }
 
     private boolean checkIndependentVariables() {
@@ -144,40 +107,25 @@
         return new IAppClipsService.Stub() {
             @Override
             public boolean canLaunchCaptureContentActivityForNote(int taskId) {
-                // In case of managed profile, use the main user's instance of the service. Callers
-                // cannot directly connect to the main user's instance as they may not have the
-                // permission to interact across users.
-                if (mUserManager.isManagedProfile()) {
-                    return canLaunchCaptureContentActivityForNoteFromMainUser(taskId);
-                }
+                return canLaunchCaptureContentActivityForNoteInternal(taskId)
+                        == CAPTURE_CONTENT_FOR_NOTE_SUCCESS;
+            }
 
+            @Override
+            @CaptureContentForNoteStatusCodes
+            public int canLaunchCaptureContentActivityForNoteInternal(int taskId) {
                 if (!mAreTaskAndTimeIndependentPrerequisitesMet) {
-                    return false;
+                    return CAPTURE_CONTENT_FOR_NOTE_FAILED;
                 }
 
                 if (!mOptionalBubbles.get().isAppBubbleTaskId(taskId)) {
-                    return false;
+                    return CAPTURE_CONTENT_FOR_NOTE_WINDOW_MODE_UNSUPPORTED;
                 }
 
-                return !mDevicePolicyManager.getScreenCaptureDisabled(null);
+                return mDevicePolicyManager.getScreenCaptureDisabled(null)
+                        ? CAPTURE_CONTENT_FOR_NOTE_BLOCKED_BY_ADMIN
+                        : CAPTURE_CONTENT_FOR_NOTE_SUCCESS;
             }
         };
     }
-
-    /** Returns whether the app clips API can be used by querying the service as the main user. */
-    private boolean canLaunchCaptureContentActivityForNoteFromMainUser(int taskId) {
-        if (mProxyConnectorToMainProfile == null) {
-            return false;
-        }
-
-        try {
-            AndroidFuture<Boolean> future = mProxyConnectorToMainProfile.postForResult(
-                    service -> service.canLaunchCaptureContentActivityForNote(taskId));
-            return future.get();
-        } catch (ExecutionException | InterruptedException e) {
-            Log.d(TAG, "Exception from service\n" + e);
-        }
-
-        return false;
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivity.java
index f00803c..6e5cef4 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivity.java
@@ -22,41 +22,41 @@
 import static android.content.Intent.CAPTURE_CONTENT_FOR_NOTE_WINDOW_MODE_UNSUPPORTED;
 import static android.content.Intent.EXTRA_CAPTURE_CONTENT_FOR_NOTE_STATUS_CODE;
 
-import static com.android.systemui.flags.Flags.SCREENSHOT_APP_CLIPS;
 import static com.android.systemui.screenshot.appclips.AppClipsEvent.SCREENSHOT_FOR_NOTE_TRIGGERED;
 
 import android.app.Activity;
-import android.app.admin.DevicePolicyManager;
 import android.content.ActivityNotFoundException;
 import android.content.ComponentName;
+import android.content.Context;
 import android.content.Intent;
+import android.content.Intent.CaptureContentForNoteStatusCodes;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.ApplicationInfoFlags;
 import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.UserInfo;
-import android.content.res.Resources;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Parcel;
 import android.os.ResultReceiver;
 import android.os.UserHandle;
-import android.os.UserManager;
 import android.util.Log;
 
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 
+import com.android.internal.infra.AndroidFuture;
+import com.android.internal.infra.ServiceConnector;
 import com.android.internal.logging.UiEventLogger;
+import com.android.internal.statusbar.IAppClipsService;
 import com.android.systemui.R;
+import com.android.systemui.broadcast.BroadcastSender;
+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.notetask.NoteTaskController;
 import com.android.systemui.notetask.NoteTaskEntryPoint;
-import com.android.systemui.settings.UserTracker;
-import com.android.wm.shell.bubbles.Bubbles;
 
-import java.util.Optional;
+import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
 
@@ -82,39 +82,57 @@
     private static final String TAG = AppClipsTrampolineActivity.class.getSimpleName();
     static final String PERMISSION_SELF = "com.android.systemui.permission.SELF";
     static final String EXTRA_SCREENSHOT_URI = TAG + "SCREENSHOT_URI";
-    @VisibleForTesting
-    static final String EXTRA_USE_WP_USER = TAG + "USE_WP_USER";
     static final String ACTION_FINISH_FROM_TRAMPOLINE = TAG + "FINISH_FROM_TRAMPOLINE";
     static final String EXTRA_RESULT_RECEIVER = TAG + "RESULT_RECEIVER";
     static final String EXTRA_CALLING_PACKAGE_NAME = TAG + "CALLING_PACKAGE_NAME";
     private static final ApplicationInfoFlags APPLICATION_INFO_FLAGS = ApplicationInfoFlags.of(0);
 
-    private final DevicePolicyManager mDevicePolicyManager;
-    private final FeatureFlags mFeatureFlags;
-    private final Optional<Bubbles> mOptionalBubbles;
     private final NoteTaskController mNoteTaskController;
     private final PackageManager mPackageManager;
-    private final UserTracker mUserTracker;
     private final UiEventLogger mUiEventLogger;
-    private final UserManager mUserManager;
+    private final BroadcastSender mBroadcastSender;
+    @Background
+    private final Executor mBgExecutor;
+    @Main
+    private final Executor mMainExecutor;
     private final ResultReceiver mResultReceiver;
 
+    private final ServiceConnector<IAppClipsService> mAppClipsServiceConnector;
+
+    private UserHandle mUserHandle;
     private Intent mKillAppClipsBroadcastIntent;
-    private UserHandle mNotesAppUser;
 
     @Inject
-    public AppClipsTrampolineActivity(DevicePolicyManager devicePolicyManager, FeatureFlags flags,
-            Optional<Bubbles> optionalBubbles, NoteTaskController noteTaskController,
-            PackageManager packageManager, UserTracker userTracker, UiEventLogger uiEventLogger,
-            UserManager userManager, @Main Handler mainHandler) {
-        mDevicePolicyManager = devicePolicyManager;
-        mFeatureFlags = flags;
-        mOptionalBubbles = optionalBubbles;
+    public AppClipsTrampolineActivity(@Application Context context,
+            NoteTaskController noteTaskController, PackageManager packageManager,
+            UiEventLogger uiEventLogger, BroadcastSender broadcastSender,
+            @Background Executor bgExecutor, @Main Executor mainExecutor,
+            @Main Handler mainHandler) {
         mNoteTaskController = noteTaskController;
         mPackageManager = packageManager;
-        mUserTracker = userTracker;
         mUiEventLogger = uiEventLogger;
-        mUserManager = userManager;
+        mBroadcastSender = broadcastSender;
+        mBgExecutor = bgExecutor;
+        mMainExecutor = mainExecutor;
+
+        mResultReceiver = createResultReceiver(mainHandler);
+        mAppClipsServiceConnector = createServiceConnector(context);
+    }
+
+    /** A constructor used only for testing to verify interactions with {@link ServiceConnector}. */
+    @VisibleForTesting
+    AppClipsTrampolineActivity(ServiceConnector<IAppClipsService> appClipsServiceConnector,
+            NoteTaskController noteTaskController, PackageManager packageManager,
+            UiEventLogger uiEventLogger, BroadcastSender broadcastSender,
+            @Background Executor bgExecutor, @Main Executor mainExecutor,
+            @Main Handler mainHandler) {
+        mAppClipsServiceConnector = appClipsServiceConnector;
+        mNoteTaskController = noteTaskController;
+        mPackageManager = packageManager;
+        mUiEventLogger = uiEventLogger;
+        mBroadcastSender = broadcastSender;
+        mBgExecutor = bgExecutor;
+        mMainExecutor = mainExecutor;
 
         mResultReceiver = createResultReceiver(mainHandler);
     }
@@ -127,62 +145,62 @@
             return;
         }
 
-        if (mUserManager.isManagedProfile()) {
-            maybeStartActivityForWPUser();
-            finish();
+        mUserHandle = getUser();
+
+        mBgExecutor.execute(() -> {
+            AndroidFuture<Integer> statusCodeFuture = mAppClipsServiceConnector.postForResult(
+                    service -> service.canLaunchCaptureContentActivityForNoteInternal(getTaskId()));
+            statusCodeFuture.whenCompleteAsync(this::handleAppClipsStatusCode, mMainExecutor);
+        });
+    }
+
+    @Override
+    protected void onDestroy() {
+        if (isFinishing() && mKillAppClipsBroadcastIntent != null) {
+            mBroadcastSender.sendBroadcast(mKillAppClipsBroadcastIntent, PERMISSION_SELF);
+        }
+
+        super.onDestroy();
+    }
+
+    private void handleAppClipsStatusCode(@CaptureContentForNoteStatusCodes int statusCode,
+            Throwable error) {
+        if (isFinishing()) {
+            // It's too late, trampoline activity is finishing or already finished. Return early.
             return;
         }
 
-        if (!mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)) {
-            finish();
+        if (error != null) {
+            Log.d(TAG, "Error querying app clips service", error);
+            setErrorResultAndFinish(statusCode);
             return;
         }
 
-        if (mOptionalBubbles.isEmpty()) {
-            setErrorResultAndFinish(CAPTURE_CONTENT_FOR_NOTE_FAILED);
-            return;
-        }
+        switch (statusCode) {
+            case CAPTURE_CONTENT_FOR_NOTE_SUCCESS:
+                launchAppClipsActivity();
+                break;
 
-        if (!mOptionalBubbles.get().isAppBubbleTaskId(getTaskId())) {
-            setErrorResultAndFinish(CAPTURE_CONTENT_FOR_NOTE_WINDOW_MODE_UNSUPPORTED);
-            return;
+            case CAPTURE_CONTENT_FOR_NOTE_FAILED:
+            case CAPTURE_CONTENT_FOR_NOTE_WINDOW_MODE_UNSUPPORTED:
+            case CAPTURE_CONTENT_FOR_NOTE_BLOCKED_BY_ADMIN:
+            default:
+                setErrorResultAndFinish(statusCode);
         }
+    }
 
-        if (mDevicePolicyManager.getScreenCaptureDisabled(null)) {
-            setErrorResultAndFinish(CAPTURE_CONTENT_FOR_NOTE_BLOCKED_BY_ADMIN);
-            return;
-        }
-
-        ComponentName componentName;
-        try {
-            componentName = ComponentName.unflattenFromString(
+    private void launchAppClipsActivity() {
+        ComponentName componentName = ComponentName.unflattenFromString(
                     getString(R.string.config_screenshotAppClipsActivityComponent));
-        } catch (Resources.NotFoundException e) {
-            setErrorResultAndFinish(CAPTURE_CONTENT_FOR_NOTE_FAILED);
-            return;
-        }
-
-        if (componentName == null || componentName.getPackageName().isEmpty()
-                || componentName.getClassName().isEmpty()) {
-            setErrorResultAndFinish(CAPTURE_CONTENT_FOR_NOTE_FAILED);
-            return;
-        }
-
-        mNotesAppUser = getUser();
-        if (getIntent().getBooleanExtra(EXTRA_USE_WP_USER, /* defaultValue= */ false)) {
-            // Get the work profile user internally instead of passing around via intent extras as
-            // this activity is exported apps could potentially mess around with intent extras.
-            mNotesAppUser = getWorkProfileUser().orElse(mNotesAppUser);
-        }
-
         String callingPackageName = getCallingPackage();
-        Intent intent = new Intent().setComponent(componentName)
+
+        Intent intent = new Intent()
+                .setComponent(componentName)
                 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
                 .putExtra(EXTRA_RESULT_RECEIVER, mResultReceiver)
                 .putExtra(EXTRA_CALLING_PACKAGE_NAME, callingPackageName);
         try {
-            // Start the App Clips activity for the user corresponding to the notes app user.
-            startActivityAsUser(intent, mNotesAppUser);
+            startActivity(intent);
 
             // Set up the broadcast intent that will inform the above App Clips activity to finish
             // when this trampoline activity is finished.
@@ -198,39 +216,6 @@
         }
     }
 
-    @Override
-    protected void onDestroy() {
-        super.onDestroy();
-
-        if (isFinishing() && mKillAppClipsBroadcastIntent != null) {
-            sendBroadcast(mKillAppClipsBroadcastIntent, PERMISSION_SELF);
-        }
-    }
-
-    private Optional<UserHandle> getWorkProfileUser() {
-        return mUserTracker.getUserProfiles().stream()
-                .filter(profile -> mUserManager.isManagedProfile(profile.id))
-                .findFirst()
-                .map(UserInfo::getUserHandle);
-    }
-
-    private void maybeStartActivityForWPUser() {
-        UserHandle mainUser = mUserManager.getMainUser();
-        if (mainUser == null) {
-            setErrorResultAndFinish(CAPTURE_CONTENT_FOR_NOTE_FAILED);
-            return;
-        }
-
-        // Start the activity as the main user with activity result forwarding. Set the intent extra
-        // so that the newly started trampoline activity starts the actual app clips activity as the
-        // work profile user. Starting the app clips activity as the work profile user is required
-        // to save the screenshot in work profile user storage and grant read permission to the URI.
-        startActivityAsUser(
-                new Intent(this, AppClipsTrampolineActivity.class)
-                        .putExtra(EXTRA_USE_WP_USER, /* value= */ true)
-                        .addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT), mainUser);
-    }
-
     private void setErrorResultAndFinish(int errorCode) {
         setResult(RESULT_OK,
                 new Intent().putExtra(EXTRA_CAPTURE_CONTENT_FOR_NOTE_STATUS_CODE, errorCode));
@@ -241,7 +226,7 @@
         int callingPackageUid = 0;
         try {
             callingPackageUid = mPackageManager.getApplicationInfoAsUser(callingPackageName,
-                    APPLICATION_INFO_FLAGS, mNotesAppUser.getIdentifier()).uid;
+                    APPLICATION_INFO_FLAGS, mUserHandle.getIdentifier()).uid;
         } catch (NameNotFoundException e) {
             Log.d(TAG, "Couldn't find notes app UID " + e);
         }
@@ -281,7 +266,7 @@
             mKillAppClipsBroadcastIntent = null;
 
             // Expand the note bubble before returning the result.
-            mNoteTaskController.showNoteTaskAsUser(NoteTaskEntryPoint.APP_CLIPS, mNotesAppUser);
+            mNoteTaskController.showNoteTaskAsUser(NoteTaskEntryPoint.APP_CLIPS, mUserHandle);
             setResult(RESULT_OK, convertedData);
             finish();
         }
@@ -298,11 +283,18 @@
         appClipsResultReceiver.writeToParcel(parcel, 0);
         parcel.setDataPosition(0);
 
-        ResultReceiver resultReceiver  = ResultReceiver.CREATOR.createFromParcel(parcel);
+        ResultReceiver resultReceiver = ResultReceiver.CREATOR.createFromParcel(parcel);
         parcel.recycle();
         return resultReceiver;
     }
 
+    private ServiceConnector<IAppClipsService> createServiceConnector(
+            @Application Context context) {
+        return new ServiceConnector.Impl<>(context, new Intent(context, AppClipsService.class),
+                Context.BIND_AUTO_CREATE | Context.BIND_WAIVE_PRIORITY | Context.BIND_NOT_VISIBLE,
+                UserHandle.USER_SYSTEM, IAppClipsService.Stub::asInterface);
+    }
+
     /** This is a test only API for mocking response from {@link AppClipsActivity}. */
     @VisibleForTesting
     public ResultReceiver getResultReceiverForTest() {
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
index 9594ba3..6af9b73 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
@@ -22,7 +22,6 @@
 
 import android.animation.ValueAnimator;
 import android.annotation.NonNull;
-import android.content.ContentResolver;
 import android.content.Context;
 import android.database.ContentObserver;
 import android.hardware.display.BrightnessInfo;
@@ -31,10 +30,10 @@
 import android.os.AsyncTask;
 import android.os.Handler;
 import android.os.HandlerExecutor;
+import android.os.Looper;
 import android.os.Message;
 import android.os.PowerManager;
 import android.os.RemoteException;
-import android.os.ServiceManager;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
@@ -43,6 +42,8 @@
 import android.util.Log;
 import android.util.MathUtils;
 
+import androidx.annotation.Nullable;
+
 import com.android.internal.display.BrightnessSynchronizer;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
@@ -52,10 +53,13 @@
 import com.android.systemui.settings.DisplayTracker;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.policy.BrightnessMirrorController;
+import com.android.systemui.util.settings.SecureSettings;
 
 import java.util.concurrent.Executor;
 
-import javax.inject.Inject;
+import dagger.assisted.Assisted;
+import dagger.assisted.AssistedFactory;
+import dagger.assisted.AssistedInject;
 
 public class BrightnessController implements ToggleSlider.Listener, MirroredBrightnessController {
     private static final String TAG = "CentralSurfaces.BrightnessController";
@@ -75,8 +79,11 @@
     private final DisplayManager mDisplayManager;
     private final UserTracker mUserTracker;
     private final DisplayTracker mDisplayTracker;
+    @Nullable
     private final IVrManager mVrManager;
 
+    private final SecureSettings mSecureSettings;
+
     private final Executor mMainExecutor;
     private final Handler mBackgroundHandler;
     private final BrightnessObserver mBrightnessObserver;
@@ -106,6 +113,8 @@
     /** ContentObserver to watch brightness */
     private class BrightnessObserver extends ContentObserver {
 
+        private boolean mObserving = false;
+
         BrightnessObserver(Handler handler) {
             super(handler);
         }
@@ -124,19 +133,17 @@
         }
 
         public void startObserving() {
-            final ContentResolver cr = mContext.getContentResolver();
-            cr.unregisterContentObserver(this);
-            cr.registerContentObserver(
-                    BRIGHTNESS_MODE_URI,
-                    false, this, UserHandle.USER_ALL);
-            mDisplayTracker.addBrightnessChangeCallback(mBrightnessListener,
-                    new HandlerExecutor(mHandler));
+            if (!mObserving) {
+                mObserving = true;
+                mSecureSettings.registerContentObserverForUser(
+                        BRIGHTNESS_MODE_URI,
+                        false, this, UserHandle.USER_ALL);
+            }
         }
 
         public void stopObserving() {
-            final ContentResolver cr = mContext.getContentResolver();
-            cr.unregisterContentObserver(this);
-            mDisplayTracker.removeCallback(mBrightnessListener);
+            mSecureSettings.unregisterContentObserver(this);
+            mObserving = false;
         }
 
     }
@@ -159,6 +166,8 @@
             }
 
             mBrightnessObserver.startObserving();
+            mDisplayTracker.addBrightnessChangeCallback(mBrightnessListener,
+                    new HandlerExecutor(mMainHandler));
             mUserTracker.addCallback(mUserChangedCallback, mMainExecutor);
 
             // Update the slider and mode before attaching the listener so we don't
@@ -166,7 +175,7 @@
             mUpdateModeRunnable.run();
             mUpdateSliderRunnable.run();
 
-            mHandler.sendEmptyMessage(MSG_ATTACH_LISTENER);
+            mMainHandler.sendEmptyMessage(MSG_ATTACH_LISTENER);
         }
     };
 
@@ -187,9 +196,10 @@
             }
 
             mBrightnessObserver.stopObserving();
+            mDisplayTracker.removeCallback(mBrightnessListener);
             mUserTracker.removeCallback(mUserChangedCallback);
 
-            mHandler.sendEmptyMessage(MSG_DETACH_LISTENER);
+            mMainHandler.sendEmptyMessage(MSG_DETACH_LISTENER);
         }
     };
 
@@ -225,7 +235,7 @@
             mBrightnessMin = info.brightnessMinimum;
             // Value is passed as intbits, since this is what the message takes.
             final int valueAsIntBits = Float.floatToIntBits(info.brightness);
-            mHandler.obtainMessage(MSG_UPDATE_SLIDER, valueAsIntBits,
+            mMainHandler.obtainMessage(MSG_UPDATE_SLIDER, valueAsIntBits,
                     inVrMode ? 1 : 0).sendToTarget();
         }
     };
@@ -233,14 +243,14 @@
     private final IVrStateCallbacks mVrStateCallbacks = new IVrStateCallbacks.Stub() {
         @Override
         public void onVrStateChanged(boolean enabled) {
-            mHandler.obtainMessage(MSG_VR_MODE_CHANGED, enabled ? 1 : 0, 0)
+            mMainHandler.obtainMessage(MSG_VR_MODE_CHANGED, enabled ? 1 : 0, 0)
                     .sendToTarget();
         }
     };
 
-    private final Handler mHandler = new Handler() {
+    private final Handler.Callback mHandlerCallback = new Handler.Callback() {
         @Override
-        public void handleMessage(Message msg) {
+        public boolean handleMessage(Message msg) {
             mExternalChange = true;
             try {
                 switch (msg.what) {
@@ -257,14 +267,18 @@
                         updateVrMode(msg.arg1 != 0);
                         break;
                     default:
-                        super.handleMessage(msg);
+                        return false;
+
                 }
             } finally {
                 mExternalChange = false;
             }
+            return true;
         }
     };
 
+    private final Handler mMainHandler;
+
     private final UserTracker.Callback mUserChangedCallback =
             new UserTracker.Callback() {
                 @Override
@@ -274,12 +288,17 @@
                 }
             };
 
+    @AssistedInject
     public BrightnessController(
             Context context,
-            ToggleSlider control,
+            @Assisted ToggleSlider control,
             UserTracker userTracker,
             DisplayTracker displayTracker,
+            DisplayManager displayManager,
+            SecureSettings secureSettings,
+            @Nullable IVrManager iVrManager,
             @Main Executor mainExecutor,
+            @Main Looper mainLooper,
             @Background Handler bgHandler) {
         mContext = context;
         mControl = control;
@@ -288,22 +307,23 @@
         mBackgroundHandler = bgHandler;
         mUserTracker = userTracker;
         mDisplayTracker = displayTracker;
-        mBrightnessObserver = new BrightnessObserver(mHandler);
-
+        mSecureSettings = secureSettings;
         mDisplayId = mContext.getDisplayId();
-        PowerManager pm = context.getSystemService(PowerManager.class);
+        mDisplayManager = displayManager;
+        mVrManager = iVrManager;
 
-        mDisplayManager = context.getSystemService(DisplayManager.class);
-        mVrManager = IVrManager.Stub.asInterface(ServiceManager.getService(
-                Context.VR_SERVICE));
+        mMainHandler = new Handler(mainLooper, mHandlerCallback);
+        mBrightnessObserver = new BrightnessObserver(mMainHandler);
     }
 
     public void registerCallbacks() {
+        mBackgroundHandler.removeCallbacks(mStartListeningRunnable);
         mBackgroundHandler.post(mStartListeningRunnable);
     }
 
     /** Unregister all call backs, both to and from the controller */
     public void unregisterCallbacks() {
+        mBackgroundHandler.removeCallbacks(mStopListeningRunnable);
         mBackgroundHandler.post(mStopListeningRunnable);
         mControlValueInitialized = false;
     }
@@ -418,38 +438,12 @@
         mSliderAnimator.start();
     }
 
+
+
     /** Factory for creating a {@link BrightnessController}. */
-    public static class Factory {
-        private final Context mContext;
-        private final UserTracker mUserTracker;
-        private final DisplayTracker mDisplayTracker;
-        private final Executor mMainExecutor;
-        private final Handler mBackgroundHandler;
-
-        @Inject
-        public Factory(
-                Context context,
-                UserTracker userTracker,
-                DisplayTracker displayTracker,
-                @Main Executor mainExecutor,
-                @Background Handler bgHandler) {
-            mContext = context;
-            mUserTracker = userTracker;
-            mDisplayTracker = displayTracker;
-            mMainExecutor = mainExecutor;
-            mBackgroundHandler = bgHandler;
-        }
-
+    @AssistedFactory
+    public interface Factory {
         /** Create a {@link BrightnessController} */
-        public BrightnessController create(ToggleSlider toggleSlider) {
-            return new BrightnessController(
-                    mContext,
-                    toggleSlider,
-                    mUserTracker,
-                    mDisplayTracker,
-                    mMainExecutor,
-                    mBackgroundHandler);
-        }
+        BrightnessController create(ToggleSlider toggleSlider);
     }
-
 }
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
index 8879501..38b1f14 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
@@ -18,54 +18,56 @@
 
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+import static android.view.WindowManagerPolicyConstants.EXTRA_FROM_BRIGHTNESS_KEY;
 
 import android.app.Activity;
 import android.graphics.Rect;
 import android.os.Bundle;
-import android.os.Handler;
 import android.view.Gravity;
 import android.view.KeyEvent;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.Window;
 import android.view.WindowManager;
+import android.view.accessibility.AccessibilityManager;
 import android.widget.FrameLayout;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.systemui.R;
-import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.settings.DisplayTracker;
-import com.android.systemui.settings.UserTracker;
+import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
+import com.android.systemui.util.concurrency.DelayableExecutor;
 
 import java.util.List;
-import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
 
 /** A dialog that provides controls for adjusting the screen brightness. */
 public class BrightnessDialog extends Activity {
 
+    @VisibleForTesting
+    static final int DIALOG_TIMEOUT_MILLIS = 3000;
+
     private BrightnessController mBrightnessController;
     private final BrightnessSliderController.Factory mToggleSliderFactory;
-    private final UserTracker mUserTracker;
-    private final DisplayTracker mDisplayTracker;
-    private final Executor mMainExecutor;
-    private final Handler mBackgroundHandler;
+    private final BrightnessController.Factory mBrightnessControllerFactory;
+    private final DelayableExecutor mMainExecutor;
+    private final AccessibilityManagerWrapper mAccessibilityMgr;
+    private Runnable mCancelTimeoutRunnable;
 
     @Inject
     public BrightnessDialog(
-            UserTracker userTracker,
-            DisplayTracker displayTracker,
-            BrightnessSliderController.Factory factory,
-            @Main Executor mainExecutor,
-            @Background Handler bgHandler) {
-        mUserTracker = userTracker;
-        mDisplayTracker = displayTracker;
-        mToggleSliderFactory = factory;
+            BrightnessSliderController.Factory brightnessSliderfactory,
+            BrightnessController.Factory brightnessControllerFactory,
+            @Main DelayableExecutor mainExecutor,
+            AccessibilityManagerWrapper accessibilityMgr
+    ) {
+        mToggleSliderFactory = brightnessSliderfactory;
+        mBrightnessControllerFactory = brightnessControllerFactory;
         mMainExecutor = mainExecutor;
-        mBackgroundHandler = bgHandler;
+        mAccessibilityMgr = accessibilityMgr;
     }
 
 
@@ -84,6 +86,7 @@
         window.getDecorView();
         window.setLayout(
                 WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.WRAP_CONTENT);
+        getTheme().applyStyle(R.style.Theme_SystemUI_QuickSettings, false);
 
         setContentView(R.layout.brightness_mirror_container);
         FrameLayout frame = findViewById(R.id.brightness_mirror_container);
@@ -109,8 +112,7 @@
         controller.init();
         frame.addView(controller.getRootView(), MATCH_PARENT, WRAP_CONTENT);
 
-        mBrightnessController = new BrightnessController(
-                this, controller, mUserTracker, mDisplayTracker, mMainExecutor, mBackgroundHandler);
+        mBrightnessController = mBrightnessControllerFactory.create(controller);
     }
 
     @Override
@@ -121,6 +123,14 @@
     }
 
     @Override
+    protected void onResume() {
+        super.onResume();
+        if (triggeredByBrightnessKey()) {
+            scheduleTimeout();
+        }
+    }
+
+    @Override
     protected void onPause() {
         super.onPause();
         overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
@@ -138,9 +148,25 @@
         if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN
                 || keyCode == KeyEvent.KEYCODE_VOLUME_UP
                 || keyCode == KeyEvent.KEYCODE_VOLUME_MUTE) {
+            if (mCancelTimeoutRunnable != null) {
+                mCancelTimeoutRunnable.run();
+            }
             finish();
         }
 
         return super.onKeyDown(keyCode, event);
     }
+
+    private boolean triggeredByBrightnessKey() {
+        return getIntent().getBooleanExtra(EXTRA_FROM_BRIGHTNESS_KEY, false);
+    }
+
+    private void scheduleTimeout() {
+        if (mCancelTimeoutRunnable != null) {
+            mCancelTimeoutRunnable.run();
+        }
+        final int timeout = mAccessibilityMgr.getRecommendedTimeoutMillis(DIALOG_TIMEOUT_MILLIS,
+                AccessibilityManager.FLAG_CONTENT_CONTROLS);
+        mCancelTimeoutRunnable = mMainExecutor.executeDelayed(this::finish, timeout);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/CombinedShadeHeadersConstraintManagerImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/CombinedShadeHeadersConstraintManagerImpl.kt
index 7e0f504..a9b3d0a 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/CombinedShadeHeadersConstraintManagerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/CombinedShadeHeadersConstraintManagerImpl.kt
@@ -31,8 +31,7 @@
         val constraintAlpha = if (visible) 0f else 1f
         return ConstraintsChanges(
             qqsConstraintsChanges = {
-                setAlpha(R.id.statusIcons, constraintAlpha)
-                setAlpha(R.id.batteryRemainingIcon, constraintAlpha)
+                setAlpha(R.id.shade_header_system_icons, constraintAlpha)
             }
         )
     }
@@ -45,14 +44,15 @@
                     R.id.barrier,
                     ConstraintSet.START,
                     0,
-                    R.id.statusIcons,
+                    R.id.shade_header_system_icons,
                     R.id.privacy_container
                 )
-                connect(R.id.statusIcons, ConstraintSet.START, R.id.date, ConstraintSet.END)
+                connect(R.id.shade_header_system_icons, ConstraintSet.START, R.id.date,
+                    ConstraintSet.END)
                 connect(R.id.privacy_container, ConstraintSet.START, R.id.date, ConstraintSet.END)
-                constrainWidth(R.id.statusIcons, ViewGroup.LayoutParams.WRAP_CONTENT)
+                constrainWidth(R.id.shade_header_system_icons, ViewGroup.LayoutParams.WRAP_CONTENT)
                 constrainedWidth(R.id.date, true)
-                constrainedWidth(R.id.statusIcons, true)
+                constrainedWidth(R.id.shade_header_system_icons, true)
             }
         )
     }
@@ -84,7 +84,7 @@
                 setGuidelineEnd(centerEnd, offsetFromEdge)
                 connect(R.id.date, ConstraintSet.END, centerStart, ConstraintSet.START)
                 connect(
-                    R.id.statusIcons,
+                    R.id.shade_header_system_icons,
                     ConstraintSet.START,
                     centerEnd,
                     ConstraintSet.END
@@ -96,7 +96,7 @@
                     ConstraintSet.END
                 )
                 constrainedWidth(R.id.date, true)
-                constrainedWidth(R.id.statusIcons, true)
+                constrainedWidth(R.id.shade_header_system_icons, true)
             },
             qsConstraintsChanges = {
                 setGuidelineBegin(centerStart, offsetFromEdge)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/LockscreenHostedDreamGestureListener.kt b/packages/SystemUI/src/com/android/systemui/shade/LockscreenHostedDreamGestureListener.kt
new file mode 100644
index 0000000..45fc68a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/LockscreenHostedDreamGestureListener.kt
@@ -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.systemui.shade
+
+import android.os.PowerManager
+import android.view.GestureDetector
+import android.view.MotionEvent
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.plugins.FalsingManager.LOW_PENALTY
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.statusbar.StatusBarState
+import javax.inject.Inject
+
+/**
+ * This gestureListener will wake up by tap when the device is dreaming but not dozing, and the
+ * selected screensaver is hosted in lockscreen. Tap is gated by the falsing manager.
+ *
+ * Touches go through the [NotificationShadeWindowViewController].
+ */
+@SysUISingleton
+class LockscreenHostedDreamGestureListener
+@Inject
+constructor(
+    private val falsingManager: FalsingManager,
+    private val powerInteractor: PowerInteractor,
+    private val statusBarStateController: StatusBarStateController,
+    private val primaryBouncerInteractor: PrimaryBouncerInteractor,
+    private val keyguardRepository: KeyguardRepository,
+    private val shadeLogger: ShadeLogger,
+) : GestureDetector.SimpleOnGestureListener() {
+    private val TAG = this::class.simpleName
+
+    override fun onSingleTapUp(e: MotionEvent): Boolean {
+        if (shouldHandleMotionEvent()) {
+            if (!falsingManager.isFalseTap(LOW_PENALTY)) {
+                shadeLogger.d("$TAG#onSingleTapUp tap handled, requesting wakeUpIfDreaming")
+                powerInteractor.wakeUpIfDreaming(
+                    "DREAMING_SINGLE_TAP",
+                    PowerManager.WAKE_REASON_TAP
+                )
+            } else {
+                shadeLogger.d("$TAG#onSingleTapUp false tap ignored")
+            }
+            return true
+        }
+        return false
+    }
+
+    private fun shouldHandleMotionEvent(): Boolean {
+        return keyguardRepository.isActiveDreamLockscreenHosted.value &&
+            statusBarStateController.state == StatusBarState.KEYGUARD &&
+            !primaryBouncerInteractor.isBouncerShowing()
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 267b147..9399d48 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -17,8 +17,6 @@
 package com.android.systemui.shade;
 
 import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
-import static android.view.MotionEvent.CLASSIFICATION_MULTI_FINGER_SWIPE;
-import static android.view.MotionEvent.CLASSIFICATION_TWO_FINGER_SWIPE;
 import static android.view.View.INVISIBLE;
 import static android.view.View.VISIBLE;
 
@@ -30,6 +28,8 @@
 import static com.android.systemui.classifier.Classifier.GENERIC;
 import static com.android.systemui.classifier.Classifier.QUICK_SETTINGS;
 import static com.android.systemui.classifier.Classifier.UNLOCK;
+import static com.android.systemui.navigationbar.gestural.Utilities.isTrackpadScroll;
+import static com.android.systemui.navigationbar.gestural.Utilities.isTrackpadThreeFingerSwipe;
 import static com.android.systemui.shade.ShadeExpansionStateManagerKt.STATE_CLOSED;
 import static com.android.systemui.shade.ShadeExpansionStateManagerKt.STATE_OPEN;
 import static com.android.systemui.shade.ShadeExpansionStateManagerKt.STATE_OPENING;
@@ -90,7 +90,6 @@
 
 import com.android.app.animation.Interpolators;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.policy.SystemBarUtils;
@@ -114,8 +113,11 @@
 import com.android.systemui.animation.ActivityLaunchAnimator;
 import com.android.systemui.animation.LaunchAnimator;
 import com.android.systemui.biometrics.AuthController;
+import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
+import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants;
 import com.android.systemui.classifier.Classifier;
 import com.android.systemui.classifier.FalsingCollector;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.DisplayId;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.doze.DozeLog;
@@ -125,14 +127,14 @@
 import com.android.systemui.flags.Flags;
 import com.android.systemui.fragments.FragmentService;
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
-import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor;
+import com.android.systemui.keyguard.KeyguardViewConfigurator;
 import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
-import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants;
 import com.android.systemui.keyguard.shared.model.TransitionState;
 import com.android.systemui.keyguard.shared.model.TransitionStep;
+import com.android.systemui.keyguard.shared.model.WakefulnessModel;
 import com.android.systemui.keyguard.ui.binder.KeyguardLongPressViewBinder;
 import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel;
 import com.android.systemui.keyguard.ui.viewmodel.GoneToDreamingTransitionViewModel;
@@ -156,6 +158,7 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
 import com.android.systemui.shade.transition.ShadeTransitionController;
+import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
 import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.GestureRecorder;
@@ -165,7 +168,6 @@
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.NotificationShelfController;
 import com.android.systemui.statusbar.PulseExpansionHandler;
-import com.android.systemui.statusbar.RemoteInputController;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.VibratorHelper;
@@ -206,7 +208,6 @@
 import com.android.systemui.statusbar.phone.StatusBarTouchableRegionManager;
 import com.android.systemui.statusbar.phone.TapAgainViewController;
 import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
-import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent;
 import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardQsUserSwitchController;
@@ -236,7 +237,7 @@
 
 import kotlinx.coroutines.CoroutineDispatcher;
 
-@CentralSurfacesComponent.CentralSurfacesScope
+@SysUISingleton
 public final class NotificationPanelViewController implements ShadeSurface, Dumpable {
 
     public static final String TAG = NotificationPanelView.class.getSimpleName();
@@ -347,7 +348,6 @@
     private final NotificationGutsManager mGutsManager;
     private final AlternateBouncerInteractor mAlternateBouncerInteractor;
     private final QuickSettingsController mQsController;
-    private final InteractionJankMonitor mInteractionJankMonitor;
     private final TouchHandler mTouchHandler = new TouchHandler();
 
     private long mDownTime;
@@ -360,6 +360,7 @@
     /** The current squish amount for the predictive back animation */
     private float mCurrentBackProgress = 0.0f;
     private boolean mTracking;
+    private boolean mIsTrackingExpansionFromStatusBar;
     private boolean mHintAnimationRunning;
     private KeyguardBottomAreaView mKeyguardBottomArea;
     private boolean mExpanding;
@@ -438,8 +439,6 @@
     private final FalsingCollector mFalsingCollector;
     private final ShadeHeadsUpTrackerImpl mShadeHeadsUpTracker = new ShadeHeadsUpTrackerImpl();
     private final ShadeFoldAnimator mShadeFoldAnimator = new ShadeFoldAnimatorImpl();
-    private final ShadeNotificationPresenterImpl mShadeNotificationPresenter =
-            new ShadeNotificationPresenterImpl();
 
     private boolean mShowIconsWhenExpanded;
     private int mIndicationBottomPadding;
@@ -599,6 +598,7 @@
 
     private final KeyguardTransitionInteractor mKeyguardTransitionInteractor;
     private final KeyguardInteractor mKeyguardInteractor;
+    private final KeyguardViewConfigurator mKeyguardViewConfigurator;
     private final @Nullable MultiShadeInteractor mMultiShadeInteractor;
     private final CoroutineDispatcher mMainDispatcher;
     private boolean mIsAnyMultiShadeExpanded;
@@ -724,7 +724,6 @@
             NotificationStackSizeCalculator notificationStackSizeCalculator,
             UnlockedScreenOffAnimationController unlockedScreenOffAnimationController,
             ShadeTransitionController shadeTransitionController,
-            InteractionJankMonitor interactionJankMonitor,
             SystemClock systemClock,
             KeyguardBottomAreaViewModel keyguardBottomAreaViewModel,
             KeyguardBottomAreaInteractor keyguardBottomAreaInteractor,
@@ -741,8 +740,8 @@
             KeyguardLongPressViewModel keyguardLongPressViewModel,
             KeyguardInteractor keyguardInteractor,
             ActivityStarter activityStarter,
+            KeyguardViewConfigurator keyguardViewConfigurator,
             KeyguardFaceAuthInteractor keyguardFaceAuthInteractor) {
-        mInteractionJankMonitor = interactionJankMonitor;
         keyguardStateController.addCallback(new KeyguardStateController.Callback() {
             @Override
             public void onKeyguardFadingAwayChanged() {
@@ -763,6 +762,7 @@
         mLockscreenToOccludedTransitionViewModel = lockscreenToOccludedTransitionViewModel;
         mKeyguardTransitionInteractor = keyguardTransitionInteractor;
         mKeyguardInteractor = keyguardInteractor;
+        mKeyguardViewConfigurator = keyguardViewConfigurator;
         mView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
             @Override
             public void onViewAttachedToWindow(View v) {
@@ -1029,7 +1029,7 @@
                 new NsslHeightChangedListener());
         mNotificationStackScrollLayoutController.setOnEmptySpaceClickListener(
                 mOnEmptySpaceClickListener);
-        mQsController.initNotificationStackScrollLayoutController();
+        mQsController.init();
         mShadeExpansionStateManager.addQsExpansionListener(this::onQsExpansionChanged);
         mShadeHeadsUpTracker.addTrackingHeadsUpListener(
                 mNotificationStackScrollLayoutController::setTrackingHeadsUp);
@@ -1073,6 +1073,8 @@
 
         mTapAgainViewController.init();
         mShadeHeaderController.init();
+        mShadeHeaderController.setShadeCollapseAction(
+                () -> collapse(/* delayed= */ false , /* speedUpFactor= */ 1.0f));
         mKeyguardUnfoldTransition.ifPresent(u -> u.setup(mView));
         mNotificationPanelUnfoldAnimationController.ifPresent(controller ->
                 controller.setup(mNotificationContainerParent));
@@ -1323,7 +1325,6 @@
         mKeyguardBottomArea.initFrom(oldBottomArea);
         mView.addView(mKeyguardBottomArea, index);
         initBottomArea();
-        mKeyguardIndicationController.setIndicationArea(mKeyguardBottomArea);
         mStatusBarStateListener.onDozeAmountChanged(mStatusBarStateController.getDozeAmount(),
                 mStatusBarStateController.getInterpolatedDozeAmount());
 
@@ -1365,6 +1366,9 @@
                         mKeyguardIndicationController.showTransientIndication(stringResourceId),
                 mVibratorHelper,
                 mActivityStarter);
+
+        // Rebind (for now), as a new bottom area and indication area may have been created
+        mKeyguardViewConfigurator.bindIndicationArea(mKeyguardBottomArea);
     }
 
     @VisibleForTesting
@@ -1402,14 +1406,15 @@
 
     private void setKeyguardBottomArea(KeyguardBottomAreaView keyguardBottomArea) {
         mKeyguardBottomArea = keyguardBottomArea;
-        mKeyguardIndicationController.setIndicationArea(mKeyguardBottomArea);
     }
 
-    void setOpenCloseListener(OpenCloseListener openCloseListener) {
+    @Override
+    public void setOpenCloseListener(OpenCloseListener openCloseListener) {
         mOpenCloseListener = openCloseListener;
     }
 
-    void setTrackingStartedListener(TrackingStartedListener trackingStartedListener) {
+    @Override
+    public void setTrackingStartedListener(TrackingStartedListener trackingStartedListener) {
         mTrackingStartedListener = trackingStartedListener;
     }
 
@@ -1465,7 +1470,7 @@
                 // so we should not add a padding for them
                 stackScrollerPadding = 0;
             } else {
-                stackScrollerPadding = mQsController.getUnlockedStackScrollerPadding();
+                stackScrollerPadding = mQsController.getHeaderHeight();
             }
         } else {
             stackScrollerPadding = mClockPositionResult.stackScrollerPaddingExpanded;
@@ -1512,7 +1517,7 @@
                 userSwitcherPreferredY,
                 darkAmount, mOverStretchAmount,
                 bypassEnabled,
-                mQsController.getUnlockedStackScrollerPadding(),
+                mQsController.getHeaderHeight(),
                 mQsController.computeExpansionFraction(),
                 mDisplayTopInset,
                 mSplitShadeEnabled,
@@ -1819,6 +1824,7 @@
 
             // Set after notifyExpandingStarted, as notifyExpandingStarted resets the closing state.
             setClosing(true);
+            mUpdateFlingOnLayout = false;
             if (delayed) {
                 mNextCollapseSpeedUpFactor = speedUpFactor;
                 this.mView.postDelayed(mFlingCollapseRunnable, 120);
@@ -2151,10 +2157,14 @@
     }
 
     int getFalsingThreshold() {
-        float factor = mCentralSurfaces.isWakeUpComingFromTouch() ? 1.5f : 1.0f;
+        float factor = ShadeViewController.getFalsingThresholdFactor(getWakefulness());
         return (int) (mQsController.getFalsingThreshold() * factor);
     }
 
+    private WakefulnessModel getWakefulness() {
+        return mKeyguardInteractor.getWakefulnessModel().getValue();
+    }
+
     private void maybeAnimateBottomAreaAlpha() {
         mBottomAreaShadeAlphaAnimator.cancel();
         if (mBarState == StatusBarState.SHADE_LOCKED) {
@@ -2432,6 +2442,7 @@
         boolean isExpanded = !isFullyCollapsed() || mExpectingSynthesizedDown;
         if (mPanelExpanded != isExpanded) {
             mPanelExpanded = isExpanded;
+            updateSystemUiStateFlags();
             mShadeExpansionStateManager.onShadeExpansionFullyChanged(isExpanded);
             if (!isExpanded) {
                 mQsController.closeQsCustomizer();
@@ -2439,6 +2450,7 @@
         }
     }
 
+    @Override
     public boolean isPanelExpanded() {
         return mPanelExpanded;
     }
@@ -2466,7 +2478,8 @@
             alpha = getFadeoutAlpha();
         }
         if (mBarState == KEYGUARD && !mHintAnimationRunning
-                && !mKeyguardBypassController.getBypassEnabled()) {
+                && !mKeyguardBypassController.getBypassEnabled()
+                && !mQsController.getFullyExpanded()) {
             alpha *= mClockPositionResult.clockAlpha;
         }
         mNotificationStackScrollLayoutController.setAlpha(alpha);
@@ -2574,7 +2587,7 @@
                                         this);
                                 return;
                             }
-                            if (mCentralSurfaces.getNotificationShadeWindowView()
+                            if (mNotificationShadeWindowController.getWindowRootView()
                                     .isVisibleToUser()) {
                                 mView.getViewTreeObserver().removeOnGlobalLayoutListener(
                                         this);
@@ -2655,6 +2668,8 @@
     private void onTrackingStopped(boolean expand) {
         mFalsingCollector.onTrackingStopped();
         mTracking = false;
+        maybeStopTrackingExpansionFromStatusBar(expand);
+
         updateExpansionAndVisibility();
         if (expand) {
             mNotificationStackScrollLayoutController.setOverScrollAmount(0.0f, true /* onTop */,
@@ -2896,9 +2911,7 @@
         float scrimMinFraction;
         if (mSplitShadeEnabled) {
             boolean highHun = mHeadsUpStartHeight * 2.5
-                    >
-                    (mFeatureFlags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION)
-                    ? mSplitShadeFullTransitionDistance : mSplitShadeScrimTransitionDistance);
+                    > mSplitShadeFullTransitionDistance;
             // if HUN height is higher than 40% of predefined transition distance, it means HUN
             // is too high for regular transition. In that case we need to calculate transition
             // distance - here we take scrim transition distance as equal to shade transition
@@ -3029,12 +3042,6 @@
         mKeyguardStatusViewController.setStatusAccessibilityImportance(mode);
     }
 
-    //TODO(b/254875405): this should be removed.
-    @Override
-    public KeyguardBottomAreaView getKeyguardBottomAreaView() {
-        return mKeyguardBottomArea;
-    }
-
     @Override
     public void applyLaunchAnimationProgress(float linearProgress) {
         boolean hideIcons = LaunchAnimator.getProgress(ActivityLaunchAnimator.TIMINGS,
@@ -3313,23 +3320,6 @@
         ).printTableData(ipw);
     }
 
-    private final class ShadeNotificationPresenterImpl implements ShadeNotificationPresenter{
-        @Override
-        public RemoteInputController.Delegate createRemoteInputDelegate() {
-            return mNotificationStackScrollLayoutController.createDelegate();
-        }
-
-        @Override
-        public boolean hasPulsingNotifications() {
-            return mNotificationListContainer.hasPulsingNotifications();
-        }
-    }
-
-    @Override
-    public ShadeNotificationPresenter getShadeNotificationPresenter() {
-        return mShadeNotificationPresenter;
-    }
-
     @Override
     public void initDependencies(
             CentralSurfaces centralSurfaces,
@@ -3375,11 +3365,13 @@
         ViewGroupFadeHelper.reset(mView);
     }
 
-    void addOnGlobalLayoutListener(ViewTreeObserver.OnGlobalLayoutListener listener) {
+    @Override
+    public void addOnGlobalLayoutListener(ViewTreeObserver.OnGlobalLayoutListener listener) {
         mView.getViewTreeObserver().addOnGlobalLayoutListener(listener);
     }
 
-    void removeOnGlobalLayoutListener(ViewTreeObserver.OnGlobalLayoutListener listener) {
+    @Override
+    public void removeOnGlobalLayoutListener(ViewTreeObserver.OnGlobalLayoutListener listener) {
         mView.getViewTreeObserver().removeOnGlobalLayoutListener(listener);
     }
 
@@ -3438,9 +3430,8 @@
             Log.d(TAG, "Updating panel sysui state flags: fullyExpanded="
                     + isFullyExpanded() + " inQs=" + mQsController.getExpanded());
         }
-        boolean isPanelVisible = mCentralSurfaces != null && mCentralSurfaces.isPanelExpanded();
         mSysUiState
-                .setFlag(SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE, isPanelVisible)
+                .setFlag(SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE, mPanelExpanded)
                 .setFlag(SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED,
                         isFullyExpanded() && !mQsController.getExpanded())
                 .setFlag(SYSUI_STATE_QUICK_SETTINGS_EXPANDED,
@@ -3600,8 +3591,10 @@
                 mShadeLog.logEndMotionEvent("endMotionEvent: flingExpands", forceCancel, expand);
             }
 
-            mDozeLog.traceFling(expand, mTouchAboveFalsingThreshold,
-                    mCentralSurfaces.isWakeUpComingFromTouch());
+            mDozeLog.traceFling(
+                    expand,
+                    mTouchAboveFalsingThreshold,
+                    /* screenOnFromTouch=*/ getWakefulness().isDeviceInteractiveFromTapOrGesture());
             // Log collapse gesture if on lock screen.
             if (!expand && onKeyguard) {
                 float displayDensity = mCentralSurfaces.getDisplayDensity();
@@ -3848,8 +3841,8 @@
         return !isFullyCollapsed() && !mTracking && !mClosing;
     }
 
-    /** Collapses the shade instantly without animation. */
-    void instantCollapse() {
+    @Override
+    public void instantCollapse() {
         abortAnimations();
         setExpandedFraction(0f);
         if (mExpanding) {
@@ -4022,8 +4015,8 @@
         mFixedDuration = NO_FIXED_DURATION;
     }
 
-    /** */
-    boolean postToView(Runnable action) {
+    @Override
+    public boolean postToView(Runnable action) {
         return mView.post(action);
     }
 
@@ -4038,6 +4031,42 @@
     }
 
     @Override
+    public void startTrackingExpansionFromStatusBar() {
+        mIsTrackingExpansionFromStatusBar = true;
+        InteractionJankMonitorWrapper.begin(
+                mView, InteractionJankMonitorWrapper.CUJ_SHADE_EXPAND_FROM_STATUS_BAR);
+    }
+
+    /**
+     * Stops tracking an expansion that originated from the status bar (if we had started tracking
+     * it).
+     *
+     * @param expand the expand boolean passed to {@link #onTrackingStopped(boolean)}.
+     */
+    private void maybeStopTrackingExpansionFromStatusBar(boolean expand) {
+        if (!mIsTrackingExpansionFromStatusBar) {
+            return;
+        }
+        mIsTrackingExpansionFromStatusBar = false;
+
+        // Determine whether the shade actually expanded due to the status bar touch:
+        // - If the user just taps on the status bar, then #isExpanded is false but
+        // #onTrackingStopped is called with `true`.
+        // - If the user drags down on the status bar but doesn't drag down far enough, then
+        // #onTrackingStopped is called with `false` but #isExpanded is true.
+        // So, we need *both* #onTrackingStopped called with `true` *and* #isExpanded to be true in
+        // order to confirm that the shade successfully opened.
+        boolean shadeExpansionFromStatusBarSucceeded = expand && isExpanded();
+        if (shadeExpansionFromStatusBarSucceeded) {
+            InteractionJankMonitorWrapper.end(
+                    InteractionJankMonitorWrapper.CUJ_SHADE_EXPAND_FROM_STATUS_BAR);
+        } else {
+            InteractionJankMonitorWrapper.cancel(
+                    InteractionJankMonitorWrapper.CUJ_SHADE_EXPAND_FROM_STATUS_BAR);
+        }
+    }
+
+    @Override
     public void updateTouchableRegion() {
         //A layout will ensure that onComputeInternalInsets will be called and after that we can
         // resize the layout. Make sure that the window stays small for one frame until the
@@ -4668,6 +4697,9 @@
             final float x = event.getX(pointerIndex);
             final float y = event.getY(pointerIndex);
             boolean canCollapsePanel = canCollapsePanelOnTouch();
+            final boolean isTrackpadTwoOrThreeFingerSwipe = isTrackpadScroll(
+                    mTrackpadGestureFeaturesEnabled, event) || isTrackpadThreeFingerSwipe(
+                    mTrackpadGestureFeaturesEnabled, event);
 
             switch (event.getActionMasked()) {
                 case MotionEvent.ACTION_DOWN:
@@ -4702,7 +4734,7 @@
                     addMovement(event);
                     break;
                 case MotionEvent.ACTION_POINTER_UP:
-                    if (isTrackpadMotionEvent(event)) {
+                    if (isTrackpadTwoOrThreeFingerSwipe) {
                         break;
                     }
                     final int upPointer = event.getPointerId(event.getActionIndex());
@@ -4718,7 +4750,7 @@
                     mShadeLog.logMotionEventStatusBarState(event,
                             mStatusBarStateController.getState(),
                             "onInterceptTouchEvent: pointer down action");
-                    if (!isTrackpadMotionEvent(event)
+                    if (!isTrackpadTwoOrThreeFingerSwipe
                             && mStatusBarStateController.getState() == StatusBarState.KEYGUARD) {
                         mMotionAborted = true;
                         mVelocityTracker.clear();
@@ -4868,9 +4900,13 @@
                 return false;
             }
 
+            final boolean isTrackpadTwoOrThreeFingerSwipe = isTrackpadScroll(
+                    mTrackpadGestureFeaturesEnabled, event) || isTrackpadThreeFingerSwipe(
+                    mTrackpadGestureFeaturesEnabled, event);
+
             // On expanding, single mouse click expands the panel instead of dragging.
             if (isFullyCollapsed() && (event.isFromSource(InputDevice.SOURCE_MOUSE)
-                    && !isTrackpadMotionEvent(event))) {
+                    && !isTrackpadTwoOrThreeFingerSwipe)) {
                 if (event.getAction() == MotionEvent.ACTION_UP) {
                     expand(true /* animate */);
                 }
@@ -4932,7 +4968,7 @@
                     break;
 
                 case MotionEvent.ACTION_POINTER_UP:
-                    if (isTrackpadMotionEvent(event)) {
+                    if (isTrackpadTwoOrThreeFingerSwipe) {
                         break;
                     }
                     final int upPointer = event.getPointerId(event.getActionIndex());
@@ -4951,7 +4987,7 @@
                     mShadeLog.logMotionEventStatusBarState(event,
                             mStatusBarStateController.getState(),
                             "handleTouch: pointer down action");
-                    if (!isTrackpadMotionEvent(event)
+                    if (!isTrackpadTwoOrThreeFingerSwipe
                             && mStatusBarStateController.getState() == StatusBarState.KEYGUARD) {
                         mMotionAborted = true;
                         endMotionEvent(event, x, y, true /* forceCancel */);
@@ -5025,12 +5061,6 @@
             }
             return !mGestureWaitForTouchSlop || mTracking;
         }
-
-        private boolean isTrackpadMotionEvent(MotionEvent ev) {
-            return mTrackpadGestureFeaturesEnabled && (
-                    ev.getClassification() == CLASSIFICATION_MULTI_FINGER_SWIPE
-                            || ev.getClassification() == CLASSIFICATION_TWO_FINGER_SWIPE);
-        }
     }
 
     private final class HeadsUpNotificationViewControllerImpl implements
@@ -5081,18 +5111,5 @@
             return super.performAccessibilityAction(host, action, args);
         }
     }
-
-    /** Listens for when touch tracking begins. */
-    interface TrackingStartedListener {
-        void onTrackingStarted();
-    }
-
-    /** Listens for when shade begins opening of finishes closing. */
-    interface OpenCloseListener {
-        /** Called when the shade finishes closing. */
-        void onClosingFinished();
-        /** Called when the shade starts opening. */
-        void onOpenStarted();
-    }
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
index 0c800d4..b328c55 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
@@ -103,7 +103,7 @@
     private final KeyguardViewMediator mKeyguardViewMediator;
     private final KeyguardBypassController mKeyguardBypassController;
     private final AuthController mAuthController;
-    private ViewGroup mNotificationShadeView;
+    private ViewGroup mWindowRootView;
     private LayoutParams mLp;
     private boolean mHasTopUi;
     private boolean mHasTopUiChanged;
@@ -154,7 +154,7 @@
         mKeyguardBypassController = keyguardBypassController;
         mColorExtractor = colorExtractor;
         mScreenOffAnimationController = screenOffAnimationController;
-        dumpManager.registerDumpable(getClass().getName(), this);
+        dumpManager.registerDumpable(this);
         mAuthController = authController;
         mLastKeyguardRotationAllowed = mKeyguardStateController.isKeyguardScreenRotationAllowed();
         mLockScreenDisplayTimeout = context.getResources()
@@ -262,7 +262,7 @@
         mLp.privateFlags |= PRIVATE_FLAG_BEHAVIOR_CONTROLLED;
         mLp.insetsFlags.behavior = BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
 
-        mWindowManager.addView(mNotificationShadeView, mLp);
+        mWindowManager.addView(mWindowRootView, mLp);
 
         mLpChanged.copyFrom(mLp);
         onThemeChanged();
@@ -274,13 +274,13 @@
     }
 
     @Override
-    public void setNotificationShadeView(ViewGroup view) {
-        mNotificationShadeView = view;
+    public void setWindowRootView(ViewGroup view) {
+        mWindowRootView = view;
     }
 
     @Override
-    public ViewGroup getNotificationShadeView() {
-        return mNotificationShadeView;
+    public ViewGroup getWindowRootView() {
+        return mWindowRootView;
     }
 
     @Override
@@ -289,7 +289,7 @@
     }
 
     private void setKeyguardDark(boolean dark) {
-        int vis = mNotificationShadeView.getSystemUiVisibility();
+        int vis = mWindowRootView.getSystemUiVisibility();
         if (dark) {
             vis = vis | View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
             vis = vis | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
@@ -297,7 +297,7 @@
             vis = vis & ~View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
             vis = vis & ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
         }
-        mNotificationShadeView.setSystemUiVisibility(vis);
+        mWindowRootView.setSystemUiVisibility(vis);
     }
 
     private void applyKeyguardFlags(NotificationShadeWindowState state) {
@@ -413,11 +413,11 @@
             visible = true;
             mLogger.d("Visibility forced to be true");
         }
-        if (mNotificationShadeView != null) {
+        if (mWindowRootView != null) {
             if (visible) {
-                mNotificationShadeView.setVisibility(View.VISIBLE);
+                mWindowRootView.setVisibility(View.VISIBLE);
             } else {
-                mNotificationShadeView.setVisibility(View.INVISIBLE);
+                mWindowRootView.setVisibility(View.INVISIBLE);
             }
         }
     }
@@ -439,10 +439,10 @@
 
     private void applyFitsSystemWindows(NotificationShadeWindowState state) {
         boolean fitsSystemWindows = !state.isKeyguardShowingAndNotOccluded();
-        if (mNotificationShadeView != null
-                && mNotificationShadeView.getFitsSystemWindows() != fitsSystemWindows) {
-            mNotificationShadeView.setFitsSystemWindows(fitsSystemWindows);
-            mNotificationShadeView.requestApplyInsets();
+        if (mWindowRootView != null
+                && mWindowRootView.getFitsSystemWindows() != fitsSystemWindows) {
+            mWindowRootView.setFitsSystemWindows(fitsSystemWindows);
+            mWindowRootView.requestApplyInsets();
         }
     }
 
@@ -480,9 +480,8 @@
 
     private void applyWindowLayoutParams() {
         if (mDeferWindowLayoutParams == 0 && mLp != null && mLp.copyFrom(mLpChanged) != 0) {
-            mLogger.logApplyingWindowLayoutParams(mLp);
             Trace.beginSection("updateViewLayout");
-            mWindowManager.updateViewLayout(mNotificationShadeView, mLp);
+            mWindowManager.updateViewLayout(mWindowRootView, mLp);
             Trace.endSection();
         }
     }
@@ -544,7 +543,6 @@
                 state.forceUserActivity,
                 state.launchingActivityFromNotification,
                 state.mediaBackdropShowing,
-                state.wallpaperSupportsAmbientMode,
                 state.windowNotTouchable,
                 state.componentsForcingTopUi,
                 state.forceOpenTokens,
@@ -608,7 +606,7 @@
         try {
             final IWindowSession session = WindowManagerGlobal.getWindowSession();
             session.updateTapExcludeRegion(
-                    IWindow.Stub.asInterface(getNotificationShadeView().getWindowToken()),
+                    IWindow.Stub.asInterface(getWindowRootView().getWindowToken()),
                     region);
         } catch (RemoteException e) {
             Log.e(TAG, "could not update the tap exclusion region:" + e);
@@ -735,12 +733,6 @@
         apply(mCurrentState);
     }
 
-    @Override
-    public void setWallpaperSupportsAmbientMode(boolean supportsAmbientMode) {
-        mCurrentState.wallpaperSupportsAmbientMode = supportsAmbientMode;
-        apply(mCurrentState);
-    }
-
     /**
      * @param state The {@link StatusBarStateController} of the status bar.
      */
@@ -847,8 +839,8 @@
         pw.println("  mKeyguardPreferredRefreshRate=" + mKeyguardPreferredRefreshRate);
         pw.println("  mDeferWindowLayoutParams=" + mDeferWindowLayoutParams);
         pw.println(mCurrentState);
-        if (mNotificationShadeView != null && mNotificationShadeView.getViewRootImpl() != null) {
-            mNotificationShadeView.getViewRootImpl().dump("  ", pw);
+        if (mWindowRootView != null && mWindowRootView.getViewRootImpl() != null) {
+            mWindowRootView.getViewRootImpl().dump("  ", pw);
         }
         new DumpsysTableLogger(
                 TAG,
@@ -864,7 +856,7 @@
 
     @Override
     public void onThemeChanged() {
-        if (mNotificationShadeView == null) {
+        if (mWindowRootView == null) {
             return;
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt
index 7812f07..d252943 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt
@@ -46,7 +46,6 @@
     @JvmField var forceUserActivity: Boolean = false,
     @JvmField var launchingActivityFromNotification: Boolean = false,
     @JvmField var mediaBackdropShowing: Boolean = false,
-    @JvmField var wallpaperSupportsAmbientMode: Boolean = false,
     @JvmField var windowNotTouchable: Boolean = false,
     @JvmField var componentsForcingTopUi: MutableSet<String> = mutableSetOf(),
     @JvmField var forceOpenTokens: MutableSet<Any> = mutableSetOf(),
@@ -84,7 +83,6 @@
             forceUserActivity.toString(),
             launchingActivityFromNotification.toString(),
             mediaBackdropShowing.toString(),
-            wallpaperSupportsAmbientMode.toString(),
             windowNotTouchable.toString(),
             componentsForcingTopUi.toString(),
             forceOpenTokens.toString(),
@@ -124,7 +122,6 @@
             forceUserActivity: Boolean,
             launchingActivity: Boolean,
             backdropShowing: Boolean,
-            wallpaperSupportsAmbientMode: Boolean,
             notTouchable: Boolean,
             componentsForcingTopUi: MutableSet<String>,
             forceOpenTokens: MutableSet<Any>,
@@ -153,7 +150,6 @@
                 this.forceUserActivity = forceUserActivity
                 this.launchingActivityFromNotification = launchingActivity
                 this.mediaBackdropShowing = backdropShowing
-                this.wallpaperSupportsAmbientMode = wallpaperSupportsAmbientMode
                 this.windowNotTouchable = notTouchable
                 this.componentsForcingTopUi.clear()
                 this.componentsForcingTopUi.addAll(componentsForcingTopUi)
@@ -200,7 +196,6 @@
                 "forceUserActivity",
                 "launchingActivity",
                 "backdropShowing",
-                "wallpaperSupportsAmbientMode",
                 "notTouchable",
                 "componentsForcingTopUi",
                 "forceOpenTokens",
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
index d75190e..2b62b7d 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
@@ -58,12 +58,14 @@
 import com.android.internal.view.FloatingActionMode;
 import com.android.internal.widget.floatingtoolbar.FloatingToolbar;
 import com.android.systemui.R;
-import com.android.systemui.compose.ComposeFacade;
+import com.android.systemui.scene.ui.view.WindowRootView;
 
 /**
- * Combined keyguard and notification panel view. Also holding backdrop and scrims.
+ * Combined keyguard and notification panel view. Also holding backdrop and scrims. This view can
+ * serve as the root view of the main SysUI window, but because other views can also serve that
+ * purpose, users of this class cannot assume it is the root.
  */
-public class NotificationShadeWindowView extends FrameLayout {
+public class NotificationShadeWindowView extends WindowRootView {
     public static final String TAG = "NotificationShadeWindowView";
 
     private int mRightInset = 0;
@@ -146,18 +148,6 @@
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
         setWillNotDraw(!DEBUG);
-
-        if (ComposeFacade.INSTANCE.isComposeAvailable()) {
-            ComposeFacade.INSTANCE.composeInitializer().onAttachedToWindow(this);
-        }
-    }
-
-    @Override
-    protected void onDetachedFromWindow() {
-        super.onDetachedFromWindow();
-        if (ComposeFacade.INSTANCE.isComposeAvailable()) {
-            ComposeFacade.INSTANCE.composeInitializer().onDetachedFromWindow(this);
-        }
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index 2f7644e..108ea68 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.shade;
 
+import static com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED;
 import static com.android.systemui.flags.Flags.TRACKPAD_GESTURE_COMMON;
 import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
 
@@ -36,11 +37,17 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.keyguard.AuthKeyguardMessageArea;
+import com.android.keyguard.KeyguardMessageAreaController;
 import com.android.keyguard.LockIconViewController;
 import com.android.keyguard.dagger.KeyguardBouncerComponent;
 import com.android.systemui.R;
+import com.android.systemui.back.domain.interactor.BackActionInteractor;
+import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor;
+import com.android.systemui.bouncer.ui.binder.KeyguardBouncerViewBinder;
+import com.android.systemui.bouncer.ui.viewmodel.KeyguardBouncerViewModel;
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.compose.ComposeFacade;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
@@ -48,12 +55,12 @@
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
 import com.android.systemui.keyguard.shared.model.TransitionState;
 import com.android.systemui.keyguard.shared.model.TransitionStep;
-import com.android.systemui.keyguard.ui.binder.KeyguardBouncerViewBinder;
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel;
 import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel;
+import com.android.systemui.log.BouncerLogger;
 import com.android.systemui.multishade.domain.interactor.MultiShadeInteractor;
 import com.android.systemui.multishade.domain.interactor.MultiShadeMotionEventInteractor;
 import com.android.systemui.multishade.ui.view.MultiShadeView;
+import com.android.systemui.power.domain.interactor.PowerInteractor;
 import com.android.systemui.shared.animation.DisableSubpixelTextTransitionListener;
 import com.android.systemui.statusbar.DragDownHelper;
 import com.android.systemui.statusbar.LockscreenShadeTransitionController;
@@ -67,7 +74,6 @@
 import com.android.systemui.statusbar.phone.CentralSurfaces;
 import com.android.systemui.statusbar.phone.PhoneStatusBarViewController;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
-import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent;
 import com.android.systemui.statusbar.window.StatusBarWindowStateController;
 import com.android.systemui.unfold.UnfoldTransitionProgressProvider;
 import com.android.systemui.util.time.SystemClock;
@@ -82,7 +88,7 @@
 /**
  * Controller for {@link NotificationShadeWindowView}.
  */
-@CentralSurfacesComponent.CentralSurfacesScope
+@SysUISingleton
 public class NotificationShadeWindowViewController {
     private static final String TAG = "NotifShadeWindowVC";
     private final FalsingCollector mFalsingCollector;
@@ -97,9 +103,12 @@
     private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
     private final AmbientState mAmbientState;
     private final PulsingGestureListener mPulsingGestureListener;
+    private final LockscreenHostedDreamGestureListener mLockscreenHostedDreamGestureListener;
     private final NotificationInsetsController mNotificationInsetsController;
     private final boolean mIsTrackpadCommonEnabled;
+    private final FeatureFlags mFeatureFlags;
     private GestureDetector mPulsingWakeupGestureHandler;
+    private GestureDetector mDreamingWakeupGestureHandler;
     private View mBrightnessMirror;
     private boolean mTouchActive;
     private boolean mTouchCancelled;
@@ -108,6 +117,8 @@
     private NotificationStackScrollLayout mStackScrollLayout;
     private PhoneStatusBarViewController mStatusBarViewController;
     private final CentralSurfaces mService;
+    private final BackActionInteractor mBackActionInteractor;
+    private final PowerInteractor mPowerInteractor;
     private final NotificationShadeWindowController mNotificationShadeWindowController;
     private DragDownHelper mDragDownHelper;
     private boolean mExpandingBelowNotch;
@@ -141,20 +152,26 @@
             StatusBarWindowStateController statusBarWindowStateController,
             LockIconViewController lockIconViewController,
             CentralSurfaces centralSurfaces,
+            BackActionInteractor backActionInteractor,
+            PowerInteractor powerInteractor,
             NotificationShadeWindowController controller,
             Optional<UnfoldTransitionProgressProvider> unfoldTransitionProgressProvider,
             KeyguardUnlockAnimationController keyguardUnlockAnimationController,
             NotificationInsetsController notificationInsetsController,
             AmbientState ambientState,
             PulsingGestureListener pulsingGestureListener,
+            LockscreenHostedDreamGestureListener lockscreenHostedDreamGestureListener,
             KeyguardBouncerViewModel keyguardBouncerViewModel,
             KeyguardBouncerComponent.Factory keyguardBouncerComponentFactory,
+            KeyguardMessageAreaController.Factory messageAreaControllerFactory,
             KeyguardTransitionInteractor keyguardTransitionInteractor,
             PrimaryBouncerToGoneTransitionViewModel primaryBouncerToGoneTransitionViewModel,
             FeatureFlags featureFlags,
             Provider<MultiShadeInteractor> multiShadeInteractorProvider,
             SystemClock clock,
-            Provider<MultiShadeMotionEventInteractor> multiShadeMotionEventInteractorProvider) {
+            Provider<MultiShadeMotionEventInteractor> multiShadeMotionEventInteractorProvider,
+            BouncerMessageInteractor bouncerMessageInteractor,
+            BouncerLogger bouncerLogger) {
         mLockscreenShadeTransitionController = transitionController;
         mFalsingCollector = falsingCollector;
         mStatusBarStateController = statusBarStateController;
@@ -167,14 +184,18 @@
         mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
         mStatusBarWindowStateController = statusBarWindowStateController;
         mLockIconViewController = lockIconViewController;
+        mBackActionInteractor = backActionInteractor;
         mLockIconViewController.init();
         mService = centralSurfaces;
+        mPowerInteractor = powerInteractor;
         mNotificationShadeWindowController = controller;
         mKeyguardUnlockAnimationController = keyguardUnlockAnimationController;
         mAmbientState = ambientState;
         mPulsingGestureListener = pulsingGestureListener;
+        mLockscreenHostedDreamGestureListener = lockscreenHostedDreamGestureListener;
         mNotificationInsetsController = notificationInsetsController;
         mIsTrackpadCommonEnabled = featureFlags.isEnabled(TRACKPAD_GESTURE_COMMON);
+        mFeatureFlags = featureFlags;
 
         // This view is not part of the newly inflated expanded status bar.
         mBrightnessMirror = mView.findViewById(R.id.brightness_mirror_container);
@@ -183,7 +204,11 @@
                 mView.findViewById(R.id.keyguard_bouncer_container),
                 keyguardBouncerViewModel,
                 primaryBouncerToGoneTransitionViewModel,
-                keyguardBouncerComponentFactory);
+                keyguardBouncerComponentFactory,
+                messageAreaControllerFactory,
+                bouncerMessageInteractor,
+                bouncerLogger,
+                featureFlags);
 
         collectFlow(mView, keyguardTransitionInteractor.getLockscreenToDreamingTransition(),
                 mLockscreenToDreamingTransition);
@@ -219,7 +244,10 @@
         mStackScrollLayout = mView.findViewById(R.id.notification_stack_scroller);
         mPulsingWakeupGestureHandler = new GestureDetector(mView.getContext(),
                 mPulsingGestureListener);
-
+        if (mFeatureFlags.isEnabled(LOCKSCREEN_WALLPAPER_DREAM_ENABLED)) {
+            mDreamingWakeupGestureHandler = new GestureDetector(mView.getContext(),
+                    mLockscreenHostedDreamGestureListener);
+        }
         mView.setLayoutInsetsController(mNotificationInsetsController);
         mView.setInteractionEventHandler(new NotificationShadeWindowView.InteractionEventHandler() {
             @Override
@@ -273,6 +301,10 @@
 
                 mFalsingCollector.onTouchEvent(ev);
                 mPulsingWakeupGestureHandler.onTouchEvent(ev);
+                if (mDreamingWakeupGestureHandler != null
+                        && mDreamingWakeupGestureHandler.onTouchEvent(ev)) {
+                    return true;
+                }
                 if (mStatusBarKeyguardViewManager.dispatchTouchEvent(ev)) {
                     return true;
                 }
@@ -297,8 +329,7 @@
                         /* onGestureDetectedRunnable */
                         () -> {
                             mService.userActivity();
-                            mService.wakeUpIfDozing(
-                                    mClock.uptimeMillis(),
+                            mPowerInteractor.wakeUpIfDozing(
                                     "LOCK_ICON_TOUCH",
                                     PowerManager.WAKE_REASON_GESTURE);
                         }
@@ -433,7 +464,7 @@
                 switch (event.getKeyCode()) {
                     case KeyEvent.KEYCODE_BACK:
                         if (!down) {
-                            mService.onBackPressed();
+                            mBackActionInteractor.onBackRequested();
                         }
                         return true;
                     case KeyEvent.KEYCODE_MENU:
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
index 81fe3ca..5c1dd56 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
@@ -27,7 +27,10 @@
 import androidx.constraintlayout.widget.ConstraintSet.START
 import androidx.constraintlayout.widget.ConstraintSet.TOP
 import com.android.systemui.R
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
 import com.android.systemui.fragments.FragmentService
 import com.android.systemui.navigationbar.NavigationModeController
 import com.android.systemui.plugins.qs.QS
@@ -35,6 +38,7 @@
 import com.android.systemui.recents.OverviewProxyService
 import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener
 import com.android.systemui.shared.system.QuickStepContract
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
 import com.android.systemui.util.LargeScreenUtils
 import com.android.systemui.util.ViewController
 import com.android.systemui.util.concurrency.DelayableExecutor
@@ -45,6 +49,7 @@
 @VisibleForTesting
 internal const val INSET_DEBOUNCE_MILLIS = 500L
 
+@SysUISingleton
 class NotificationsQSContainerController @Inject constructor(
         view: NotificationsQuickSettingsContainer,
         private val navigationModeController: NavigationModeController,
@@ -52,7 +57,10 @@
         private val shadeHeaderController: ShadeHeaderController,
         private val shadeExpansionStateManager: ShadeExpansionStateManager,
         private val fragmentService: FragmentService,
-        @Main private val delayableExecutor: DelayableExecutor
+        @Main private val delayableExecutor: DelayableExecutor,
+        private val featureFlags: FeatureFlags,
+        private val
+            notificationStackScrollLayoutController: NotificationStackScrollLayoutController,
 ) : ViewController<NotificationsQuickSettingsContainer>(view), QSContainerController {
 
     private var qsExpanded = false
@@ -116,6 +124,9 @@
             isGestureNavigation = QuickStepContract.isGesturalMode(mode)
         }
         isGestureNavigation = QuickStepContract.isGesturalMode(currentMode)
+
+        mView.setStackScroller(notificationStackScrollLayoutController.getView())
+        mView.setMigratingNSSL(featureFlags.isEnabled(Flags.MIGRATE_NSSL))
     }
 
     public override fun onViewAttached() {
@@ -252,14 +263,17 @@
     }
 
     private fun setNotificationsConstraints(constraintSet: ConstraintSet) {
+        if (featureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
+            return
+        }
         val startConstraintId = if (splitShadeEnabled) R.id.qs_edge_guideline else PARENT_ID
+        val nsslId = R.id.notification_stack_scroller
         constraintSet.apply {
-            connect(R.id.notification_stack_scroller, START, startConstraintId, START)
-            setMargin(R.id.notification_stack_scroller, START,
-                    if (splitShadeEnabled) 0 else panelMarginHorizontal)
-            setMargin(R.id.notification_stack_scroller, END, panelMarginHorizontal)
-            setMargin(R.id.notification_stack_scroller, TOP, topMargin)
-            setMargin(R.id.notification_stack_scroller, BOTTOM, notificationsBottomMargin)
+            connect(nsslId, START, startConstraintId, START)
+            setMargin(nsslId, START, if (splitShadeEnabled) 0 else panelMarginHorizontal)
+            setMargin(nsslId, END, panelMarginHorizontal)
+            setMargin(nsslId, TOP, topMargin)
+            setMargin(nsslId, BOTTOM, notificationsBottomMargin)
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java
index e5b84bd..3b3df50 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java
@@ -23,6 +23,7 @@
 import android.graphics.Rect;
 import android.util.AttributeSet;
 import android.view.View;
+import android.view.ViewGroup.MarginLayoutParams;
 import android.view.WindowInsets;
 
 import androidx.annotation.Nullable;
@@ -56,6 +57,7 @@
     private QS mQs;
     private View mQSContainer;
     private int mLastQSPaddingBottom;
+    private boolean mIsMigratingNSSL;
 
     /**
      *  These are used to compute the bounding box containing the shade and the notification scrim,
@@ -75,10 +77,13 @@
     protected void onFinishInflate() {
         super.onFinishInflate();
         mQsFrame = findViewById(R.id.qs_frame);
-        mStackScroller = findViewById(R.id.notification_stack_scroller);
         mKeyguardStatusBar = findViewById(R.id.keyguard_header);
     }
 
+    void setStackScroller(View stackScroller) {
+        mStackScroller = stackScroller;
+    }
+
     @Override
     public void onFragmentViewCreated(String tag, Fragment fragment) {
         mQs = (QS) fragment;
@@ -108,7 +113,7 @@
     }
 
     public void setNotificationsMarginBottom(int margin) {
-        LayoutParams params = (LayoutParams) mStackScroller.getLayoutParams();
+        MarginLayoutParams params = (MarginLayoutParams) mStackScroller.getLayoutParams();
         params.bottomMargin = margin;
         mStackScroller.setLayoutParams(params);
     }
@@ -173,8 +178,15 @@
         super.dispatchDraw(canvas);
     }
 
+    void setMigratingNSSL(boolean isMigrating) {
+        mIsMigratingNSSL = isMigrating;
+    }
+
     @Override
     protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
+        if (mIsMigratingNSSL) {
+            return super.drawChild(canvas, child, drawingTime);
+        }
         int layoutIndex = mLayoutDrawingOrder.indexOf(child);
         if (layoutIndex >= 0) {
             return super.drawChild(canvas, mDrawingOrderedChildren.get(layoutIndex), drawingTime);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt b/packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt
index fd82e2f..ee4e98e 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt
@@ -18,19 +18,18 @@
 
 import android.hardware.display.AmbientDisplayConfiguration
 import android.os.PowerManager
-import android.os.SystemClock
 import android.provider.Settings
 import android.view.GestureDetector
 import android.view.MotionEvent
 import com.android.systemui.Dumpable
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dock.DockManager
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.plugins.FalsingManager.LOW_PENALTY
 import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.settings.UserTracker
-import com.android.systemui.statusbar.phone.CentralSurfaces
-import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent
 import com.android.systemui.tuner.TunerService
 import com.android.systemui.tuner.TunerService.Tunable
 import java.io.PrintWriter
@@ -45,12 +44,11 @@
  * screen is still ON and not in the true AoD display state. When the device is in the true AoD
  * display state, wake-ups are handled by [com.android.systemui.doze.DozeSensors].
  */
-@CentralSurfacesComponent.CentralSurfacesScope
+@SysUISingleton
 class PulsingGestureListener @Inject constructor(
-        private val notificationShadeWindowView: NotificationShadeWindowView,
         private val falsingManager: FalsingManager,
         private val dockManager: DockManager,
-        private val centralSurfaces: CentralSurfaces,
+        private val powerInteractor: PowerInteractor,
         private val ambientDisplayConfiguration: AmbientDisplayConfiguration,
         private val statusBarStateController: StatusBarStateController,
         private val shadeLogger: ShadeLogger,
@@ -88,11 +86,7 @@
             shadeLogger.logSingleTapUpFalsingState(proximityIsNotNear, isNotAFalseTap)
             if (proximityIsNotNear && isNotAFalseTap) {
                 shadeLogger.d("Single tap handled, requesting centralSurfaces.wakeUpIfDozing")
-                centralSurfaces.wakeUpIfDozing(
-                    SystemClock.uptimeMillis(),
-                    "PULSING_SINGLE_TAP",
-                    PowerManager.WAKE_REASON_TAP
-                )
+                powerInteractor.wakeUpIfDozing("PULSING_SINGLE_TAP", PowerManager.WAKE_REASON_TAP)
             }
             return true
         }
@@ -113,11 +107,7 @@
                 !falsingManager.isProximityNear &&
                 !falsingManager.isFalseDoubleTap
         ) {
-            centralSurfaces.wakeUpIfDozing(
-                    SystemClock.uptimeMillis(),
-                    "PULSING_DOUBLE_TAP",
-                    PowerManager.WAKE_REASON_TAP
-            )
+            powerInteractor.wakeUpIfDozing("PULSING_DOUBLE_TAP", PowerManager.WAKE_REASON_TAP)
             return true
         }
         return false
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
index 7d66a0b..baac57c 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
@@ -68,6 +68,7 @@
 import com.android.systemui.R;
 import com.android.systemui.classifier.Classifier;
 import com.android.systemui.classifier.FalsingCollector;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.fragments.FragmentHostManager;
@@ -78,6 +79,7 @@
 import com.android.systemui.plugins.qs.QS;
 import com.android.systemui.screenrecord.RecordingController;
 import com.android.systemui.shade.data.repository.ShadeRepository;
+import com.android.systemui.shade.domain.interactor.ShadeInteractor;
 import com.android.systemui.shade.transition.ShadeTransitionController;
 import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.statusbar.LockscreenShadeTransitionController;
@@ -97,10 +99,10 @@
 import com.android.systemui.statusbar.phone.ScrimController;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
 import com.android.systemui.statusbar.phone.StatusBarTouchableRegionManager;
-import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent;
 import com.android.systemui.statusbar.policy.CastController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.LargeScreenUtils;
+import com.android.systemui.util.kotlin.JavaAdapter;
 
 import dagger.Lazy;
 
@@ -111,7 +113,7 @@
 /** Handles QuickSettings touch handling, expansion and animation state
  * TODO (b/264460656) make this dumpable
  */
-@CentralSurfacesComponent.CentralSurfacesScope
+@SysUISingleton
 public class QuickSettingsController implements Dumpable {
     public static final String TAG = "QuickSettingsController";
 
@@ -153,6 +155,8 @@
     private final FeatureFlags mFeatureFlags;
     private final InteractionJankMonitor mInteractionJankMonitor;
     private final ShadeRepository mShadeRepository;
+    private final ShadeInteractor mShadeInteractor;
+    private final JavaAdapter mJavaAdapter;
     private final FalsingManager mFalsingManager;
     private final AccessibilityManager mAccessibilityManager;
     private final MetricsLogger mMetricsLogger;
@@ -229,7 +233,6 @@
     private int mMaxExpansionHeight;
     /** Expansion fraction of the notification shade */
     private float mShadeExpandedFraction;
-    private int mPeekHeight;
     private float mLastOverscroll;
     private boolean mExpansionFromOverscroll;
     private boolean mExpansionEnabledPolicy = true;
@@ -342,6 +345,8 @@
             DumpManager dumpManager,
             KeyguardFaceAuthInteractor keyguardFaceAuthInteractor,
             ShadeRepository shadeRepository,
+            ShadeInteractor shadeInteractor,
+            JavaAdapter javaAdapter,
             CastController castController
     ) {
         mPanelViewControllerLazy = panelViewControllerLazy;
@@ -387,6 +392,8 @@
         mFeatureFlags = featureFlags;
         mInteractionJankMonitor = interactionJankMonitor;
         mShadeRepository = shadeRepository;
+        mShadeInteractor = shadeInteractor;
+        mJavaAdapter = javaAdapter;
 
         mLockscreenShadeTransitionController.addCallback(new LockscreenShadeTransitionCallback());
         dumpManager.registerDumpable(this);
@@ -421,7 +428,6 @@
         final ViewConfiguration configuration = ViewConfiguration.get(this.mPanelView.getContext());
         mTouchSlop = configuration.getScaledTouchSlop();
         mSlopMultiplier = configuration.getScaledAmbiguousGestureMultiplier();
-        mPeekHeight = mResources.getDimensionPixelSize(R.dimen.qs_peek_height);
         mStatusBarMinHeight = SystemBarUtils.getStatusBarHeight(mPanelView.getContext());
         mScrimCornerRadius = mResources.getDimensionPixelSize(
                 R.dimen.notification_scrim_corner_radius);
@@ -459,7 +465,13 @@
     }
 
     // TODO (b/265054088): move this and others to a CoreStartable
-    void initNotificationStackScrollLayoutController() {
+    void init() {
+        initNotificationStackScrollLayoutController();
+        mJavaAdapter.alwaysCollectFlow(
+                mShadeInteractor.isExpandToQsEnabled(), this::setExpansionEnabledPolicy);
+    }
+
+    private void initNotificationStackScrollLayoutController() {
         mNotificationStackScrollLayoutController.setOverscrollTopChangedListener(
                 new NsslOverscrollTopChangedListener());
         mNotificationStackScrollLayoutController.setOnStackYChanged(this::onStackYChanged);
@@ -486,12 +498,7 @@
     }
 
     int getHeaderHeight() {
-        return mQs.getHeader().getHeight();
-    }
-
-    /** Returns the padding of the stackscroller when unlocked */
-    int getUnlockedStackScrollerPadding() {
-        return (mQs != null ? mQs.getHeader().getHeight() : 0) + mPeekHeight;
+        return isQsFragmentCreated() ? mQs.getHeader().getHeight() : 0;
     }
 
     private boolean isRemoteInputActiveWithKeyboardUp() {
@@ -780,7 +787,6 @@
 
     /** update Qs height state */
     public void setExpansionHeight(float height) {
-        checkCorrectSplitShadeState(height);
         int maxHeight = getMaxExpansionHeight();
         height = Math.min(Math.max(
                 height, getMinExpansionHeight()), maxHeight);
@@ -802,14 +808,6 @@
         }
     }
 
-    /** TODO(b/269742565) Remove this logging */
-    private void checkCorrectSplitShadeState(float height) {
-        if (mSplitShadeEnabled && height == 0
-                && mPanelViewControllerLazy.get().isShadeFullyExpanded()) {
-            Log.wtfStack(TAG, "qsExpansion set to 0 while split shade is expanding or open");
-        }
-    }
-
     /** */
     public void setHeightOverrideToDesiredHeight() {
         if (isSizeChangeAnimationRunning() && isQsFragmentCreated()) {
@@ -892,7 +890,7 @@
     }
 
     /** */
-    public void setExpansionEnabledPolicy(boolean expansionEnabledPolicy) {
+    private void setExpansionEnabledPolicy(boolean expansionEnabledPolicy) {
         mExpansionEnabledPolicy = expansionEnabledPolicy;
         if (mQs != null) {
             mQs.setHeaderClickable(isExpansionEnabled());
@@ -1215,14 +1213,15 @@
         if (mIsFullWidth) {
             clipStatusView = qsVisible;
             float screenCornerRadius =
-                    !mSplitShadeEnabled || mRecordingController.isRecording()
-                            || mCastController.hasConnectedCastDevice()
+                    mRecordingController.isRecording() || mCastController.hasConnectedCastDevice()
                             ? 0 : mScreenCornerRadius;
             radius = (int) MathUtils.lerp(screenCornerRadius, mScrimCornerRadius,
                     Math.min(top / (float) mScrimCornerRadius, 1f));
 
-            float bottomRadius = mExpanded ? screenCornerRadius :
-                    calculateBottomCornerRadius(screenCornerRadius);
+            float bottomRadius = mSplitShadeEnabled ? screenCornerRadius : 0;
+            if (!mExpanded) {
+                bottomRadius = calculateBottomCornerRadius(bottomRadius);
+            }
             mScrimController.setNotificationBottomRadius(bottomRadius);
         }
         if (isQsFragmentCreated()) {
@@ -1888,7 +1887,7 @@
                 target = getMaxExpansionHeight();
                 break;
             case FLING_COLLAPSE:
-                if (mSplitShadeEnabled) { // TODO:(b/269742565) remove below log
+                if (mSplitShadeEnabled) {
                     Log.wtfStack(TAG, "FLING_COLLAPSE called in split shade");
                 }
                 setExpandImmediate(false);
@@ -2084,8 +2083,6 @@
         ipw.println(mMaxExpansionHeight);
         ipw.print("mShadeExpandedFraction=");
         ipw.println(mShadeExpandedFraction);
-        ipw.print("mPeekHeight=");
-        ipw.println(mPeekHeight);
         ipw.print("mLastOverscroll=");
         ipw.println(mLastOverscroll);
         ipw.print("mExpansionFromOverscroll=");
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java
index d0a3cbb..02f337a 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java
@@ -18,6 +18,8 @@
 
 import android.view.MotionEvent;
 
+import com.android.systemui.CoreStartable;
+import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.NotificationPresenter;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
@@ -30,7 +32,7 @@
  * these are coordinated with {@link StatusBarKeyguardViewManager} via
  * {@link com.android.systemui.keyguard.KeyguardViewMediator} and others.
  */
-public interface ShadeController {
+public interface ShadeController extends CoreStartable {
 
     /** Make our window larger and the shade expanded */
     void instantExpandShade();
@@ -38,23 +40,49 @@
     /** Collapse the shade instantly with no animation. */
     void instantCollapseShade();
 
-    /** See {@link #animateCollapsePanels(int, boolean, boolean, float)}. */
-    void animateCollapseShade();
+    /** See {@link #animateCollapseShade(int, boolean, boolean, float)}. */
+    default void animateCollapseShade() {
+        animateCollapseShade(CommandQueue.FLAG_EXCLUDE_NONE);
+    }
 
-    /** See {@link #animateCollapsePanels(int, boolean, boolean, float)}. */
-    void animateCollapseShade(int flags);
+    /** See {@link #animateCollapseShade(int, boolean, boolean, float)}. */
+    default void animateCollapseShade(int flags) {
+        animateCollapseShade(flags, false, false, 1.0f);
+    }
 
-    /** See {@link #animateCollapsePanels(int, boolean, boolean, float)}. */
-    void animateCollapseShadeForced();
+    /** See {@link #animateCollapseShade(int, boolean, boolean, float)}. */
+    default void animateCollapseShadeForced() {
+        animateCollapseShade(CommandQueue.FLAG_EXCLUDE_NONE, true, false, 1.0f);
+    }
 
-    /** See {@link #animateCollapsePanels(int, boolean, boolean, float)}. */
-    void animateCollapseShadeDelayed();
+    /** See {@link #animateCollapseShade(int, boolean, boolean, float)}. */
+    default void animateCollapseShadeForcedDelayed() {
+        animateCollapseShade(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true, true, 1.0f);
+    }
 
     /**
      * Collapse the shade animated, showing the bouncer when on {@link StatusBarState#KEYGUARD} or
      * dismissing status bar when on {@link StatusBarState#SHADE}.
      */
-    void animateCollapsePanels(int flags, boolean force, boolean delayed, float speedUpFactor);
+    void animateCollapseShade(int flags, boolean force, boolean delayed, float speedUpFactor);
+
+    /** Expand the shade with an animation. */
+    void animateExpandShade();
+
+    /** Expand the shade with quick settings expanded with an animation. */
+    void animateExpandQs();
+
+    /** Posts a request to collapse the shade. */
+    void postAnimateCollapseShade();
+
+    /** Posts a request to force collapse the shade. */
+    void postAnimateForceCollapseShade();
+
+    /** Posts a request to expand the shade to quick settings. */
+    void postAnimateExpandQs();
+
+    /** Cancels any ongoing expansion touch handling and collapses the shade. */
+    void cancelExpansionAndCollapseShade();
 
     /**
      * If the shade is not fully expanded, collapse it animated.
@@ -115,6 +143,9 @@
      */
     void collapseShade(boolean animate);
 
+    /** Calls #collapseShade if already on the main thread. If not, posts a call to it. */
+    void collapseOnMainThread();
+
     /** Makes shade expanded but not visible. */
     void makeExpandedInvisible();
 
@@ -127,22 +158,21 @@
     /** Handle status bar touch event. */
     void onStatusBarTouch(MotionEvent event);
 
-    /** Called when the shade finishes collapsing. */
-    void onClosingFinished();
+    /** Called when a launch animation was cancelled. */
+    void onLaunchAnimationCancelled(boolean isLaunchForActivity);
+
+    /** Called when a launch animation ends. */
+    void onLaunchAnimationEnd(boolean launchIsFullScreen);
 
     /** Sets the listener for when the visibility of the shade changes. */
-    void setVisibilityListener(ShadeVisibilityListener listener);
+    default void setVisibilityListener(ShadeVisibilityListener listener) {}
 
     /** */
-    void setNotificationPresenter(NotificationPresenter presenter);
+    default void setNotificationPresenter(NotificationPresenter presenter) {}
 
     /** */
-    void setNotificationShadeWindowViewController(
-            NotificationShadeWindowViewController notificationShadeWindowViewController);
-
-    /** */
-    void setNotificationPanelViewController(
-            NotificationPanelViewController notificationPanelViewController);
+    default void setNotificationShadeWindowViewController(
+            NotificationShadeWindowViewController notificationShadeWindowViewController) {}
 
     /** Listens for shade visibility changes. */
     interface ShadeVisibilityListener {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerEmptyImpl.kt
new file mode 100644
index 0000000..5f95bca
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerEmptyImpl.kt
@@ -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.systemui.shade
+
+import android.view.MotionEvent
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+
+/** Empty implementation of ShadeController for variants of Android without shades. */
+@SysUISingleton
+open class ShadeControllerEmptyImpl @Inject constructor() : ShadeController {
+    override fun start() {}
+    override fun instantExpandShade() {}
+    override fun instantCollapseShade() {}
+    override fun animateCollapseShade(
+        flags: Int,
+        force: Boolean,
+        delayed: Boolean,
+        speedUpFactor: Float
+    ) {}
+    override fun animateExpandShade() {}
+    override fun animateExpandQs() {}
+    override fun postAnimateCollapseShade() {}
+    override fun postAnimateForceCollapseShade() {}
+    override fun postAnimateExpandQs() {}
+    override fun cancelExpansionAndCollapseShade() {}
+    override fun closeShadeIfOpen(): Boolean {
+        return false
+    }
+    override fun isKeyguard(): Boolean {
+        return false
+    }
+    override fun isShadeFullyOpen(): Boolean {
+        return false
+    }
+    override fun isExpandingOrCollapsing(): Boolean {
+        return false
+    }
+    override fun postOnShadeExpanded(action: Runnable?) {}
+    override fun addPostCollapseAction(action: Runnable?) {}
+    override fun runPostCollapseRunnables() {}
+    override fun collapseShade(): Boolean {
+        return false
+    }
+    override fun collapseShade(animate: Boolean) {}
+    override fun collapseOnMainThread() {}
+    override fun makeExpandedInvisible() {}
+    override fun makeExpandedVisible(force: Boolean) {}
+    override fun isExpandedVisible(): Boolean {
+        return false
+    }
+    override fun onStatusBarTouch(event: MotionEvent?) {}
+    override fun onLaunchAnimationCancelled(isLaunchForActivity: Boolean) {}
+    override fun onLaunchAnimationEnd(launchIsFullScreen: Boolean) {}
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
index d00dab6..22c6381 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
@@ -17,6 +17,7 @@
 package com.android.systemui.shade;
 
 import android.content.ComponentCallbacks2;
+import android.os.Looper;
 import android.util.Log;
 import android.view.MotionEvent;
 import android.view.ViewTreeObserver;
@@ -25,6 +26,7 @@
 
 import com.android.systemui.assist.AssistManager;
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.NotificationPresenter;
@@ -32,12 +34,14 @@
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.window.StatusBarWindowController;
 
 import dagger.Lazy;
 
 import java.util.ArrayList;
+import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
 
@@ -51,12 +55,15 @@
     private final int mDisplayId;
 
     private final CommandQueue mCommandQueue;
+    private final Executor mMainExecutor;
     private final KeyguardStateController mKeyguardStateController;
     private final NotificationShadeWindowController mNotificationShadeWindowController;
     private final StatusBarStateController mStatusBarStateController;
     private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
     private final StatusBarWindowController mStatusBarWindowController;
+    private final DeviceProvisionedController mDeviceProvisionedController;
 
+    private final Lazy<ShadeViewController> mShadeViewControllerLazy;
     private final Lazy<AssistManager> mAssistManagerLazy;
     private final Lazy<NotificationGutsManager> mGutsManager;
 
@@ -64,7 +71,6 @@
 
     private boolean mExpandedVisible;
 
-    private NotificationPanelViewController mNotificationPanelViewController;
     private NotificationPresenter mPresenter;
     private NotificationShadeWindowViewController mNotificationShadeWindowViewController;
     private ShadeVisibilityListener mShadeVisibilityListener;
@@ -72,18 +78,24 @@
     @Inject
     public ShadeControllerImpl(
             CommandQueue commandQueue,
+            @Main Executor mainExecutor,
             KeyguardStateController keyguardStateController,
             StatusBarStateController statusBarStateController,
             StatusBarKeyguardViewManager statusBarKeyguardViewManager,
             StatusBarWindowController statusBarWindowController,
+            DeviceProvisionedController deviceProvisionedController,
             NotificationShadeWindowController notificationShadeWindowController,
             WindowManager windowManager,
+            Lazy<ShadeViewController> shadeViewControllerLazy,
             Lazy<AssistManager> assistManagerLazy,
             Lazy<NotificationGutsManager> gutsManager
     ) {
         mCommandQueue = commandQueue;
+        mMainExecutor = mainExecutor;
+        mShadeViewControllerLazy = shadeViewControllerLazy;
         mStatusBarStateController = statusBarStateController;
         mStatusBarWindowController = statusBarWindowController;
+        mDeviceProvisionedController = deviceProvisionedController;
         mGutsManager = gutsManager;
         mNotificationShadeWindowController = notificationShadeWindowController;
         mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
@@ -96,32 +108,12 @@
     public void instantExpandShade() {
         // Make our window larger and the panel expanded.
         makeExpandedVisible(true /* force */);
-        mNotificationPanelViewController.expand(false /* animate */);
+        getShadeViewController().expand(false /* animate */);
         mCommandQueue.recomputeDisableFlags(mDisplayId, false /* animate */);
     }
 
     @Override
-    public void animateCollapseShade() {
-        animateCollapseShade(CommandQueue.FLAG_EXCLUDE_NONE);
-    }
-
-    @Override
-    public void animateCollapseShade(int flags) {
-        animateCollapsePanels(flags, false, false, 1.0f);
-    }
-
-    @Override
-    public void animateCollapseShadeForced() {
-        animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE, true, false, 1.0f);
-    }
-
-    @Override
-    public void animateCollapseShadeDelayed() {
-        animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true, true, 1.0f);
-    }
-
-    @Override
-    public void animateCollapsePanels(int flags, boolean force, boolean delayed,
+    public void animateCollapseShade(int flags, boolean force, boolean delayed,
             float speedUpFactor) {
         if (!force && mStatusBarStateController.getState() != StatusBarState.SHADE) {
             runPostCollapseRunnables();
@@ -132,19 +124,38 @@
                     "animateCollapse(): mExpandedVisible=" + mExpandedVisible + "flags=" + flags);
         }
         if (getNotificationShadeWindowView() != null
-                && mNotificationPanelViewController.canBeCollapsed()
+                && getShadeViewController().canBeCollapsed()
                 && (flags & CommandQueue.FLAG_EXCLUDE_NOTIFICATION_PANEL) == 0) {
             // release focus immediately to kick off focus change transition
             mNotificationShadeWindowController.setNotificationShadeFocusable(false);
 
             mNotificationShadeWindowViewController.cancelExpandHelper();
-            mNotificationPanelViewController.collapse(true, delayed, speedUpFactor);
+            getShadeViewController().collapse(true, delayed, speedUpFactor);
         }
     }
 
     @Override
+    public void animateExpandShade() {
+        if (!mCommandQueue.panelsEnabled()) {
+            return;
+        }
+        getShadeViewController().expandToNotifications();
+    }
+
+    @Override
+    public void animateExpandQs() {
+        if (!mCommandQueue.panelsEnabled()) {
+            return;
+        }
+        // Settings are not available in setup
+        if (!mDeviceProvisionedController.isCurrentUserSetup()) return;
+
+        getShadeViewController().expandToQs();
+    }
+
+    @Override
     public boolean closeShadeIfOpen() {
-        if (!mNotificationPanelViewController.isFullyCollapsed()) {
+        if (!getShadeViewController().isFullyCollapsed()) {
             mCommandQueue.animateCollapsePanels(
                     CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */);
             notifyVisibilityChanged(false);
@@ -160,23 +171,37 @@
 
     @Override
     public boolean isShadeFullyOpen() {
-        return mNotificationPanelViewController.isShadeFullyExpanded();
+        return getShadeViewController().isShadeFullyExpanded();
     }
 
     @Override
     public boolean isExpandingOrCollapsing() {
-        return mNotificationPanelViewController.isExpandingOrCollapsing();
+        return getShadeViewController().isExpandingOrCollapsing();
+    }
+    @Override
+    public void postAnimateCollapseShade() {
+        mMainExecutor.execute(this::animateCollapseShade);
+    }
+
+    @Override
+    public void postAnimateForceCollapseShade() {
+        mMainExecutor.execute(this::animateCollapseShadeForced);
+    }
+
+    @Override
+    public void postAnimateExpandQs() {
+        mMainExecutor.execute(this::animateExpandQs);
     }
 
     @Override
     public void postOnShadeExpanded(Runnable executable) {
-        mNotificationPanelViewController.addOnGlobalLayoutListener(
+        getShadeViewController().addOnGlobalLayoutListener(
                 new ViewTreeObserver.OnGlobalLayoutListener() {
                     @Override
                     public void onGlobalLayout() {
                         if (getNotificationShadeWindowView().isVisibleToUser()) {
-                            mNotificationPanelViewController.removeOnGlobalLayoutListener(this);
-                            mNotificationPanelViewController.postToView(executable);
+                            getShadeViewController().removeOnGlobalLayoutListener(this);
+                            getShadeViewController().postToView(executable);
                         }
                     }
                 });
@@ -200,9 +225,9 @@
 
     @Override
     public boolean collapseShade() {
-        if (!mNotificationPanelViewController.isFullyCollapsed()) {
+        if (!getShadeViewController().isFullyCollapsed()) {
             // close the shade if it was open
-            animateCollapseShadeDelayed();
+            animateCollapseShadeForcedDelayed();
             notifyVisibilityChanged(false);
 
             return true;
@@ -227,6 +252,26 @@
     }
 
     @Override
+    public void cancelExpansionAndCollapseShade() {
+        if (getShadeViewController().isTracking()) {
+            mNotificationShadeWindowViewController.cancelCurrentTouch();
+        }
+        if (getShadeViewController().isPanelExpanded()
+                && mStatusBarStateController.getState() == StatusBarState.SHADE) {
+            animateCollapseShade();
+        }
+    }
+
+    @Override
+    public void collapseOnMainThread() {
+        if (Looper.getMainLooper().isCurrentThread()) {
+            collapseShade();
+        } else {
+            mMainExecutor.execute(this::collapseShade);
+        }
+    }
+
+    @Override
     public void onStatusBarTouch(MotionEvent event) {
         if (event.getAction() == MotionEvent.ACTION_UP) {
             if (mExpandedVisible) {
@@ -235,8 +280,7 @@
         }
     }
 
-    @Override
-    public void onClosingFinished() {
+    private void onClosingFinished() {
         runPostCollapseRunnables();
         if (!mPresenter.isPresenterFullyCollapsed()) {
             // if we set it not to be focusable when collapsing, we have to undo it when we aborted
@@ -246,8 +290,29 @@
     }
 
     @Override
+    public void onLaunchAnimationCancelled(boolean isLaunchForActivity) {
+        if (mPresenter.isPresenterFullyCollapsed()
+                && !mPresenter.isCollapsing()
+                && isLaunchForActivity) {
+            onClosingFinished();
+        } else {
+            collapseShade(true /* animate */);
+        }
+    }
+
+    @Override
+    public void onLaunchAnimationEnd(boolean launchIsFullScreen) {
+        if (!mPresenter.isCollapsing()) {
+            onClosingFinished();
+        }
+        if (launchIsFullScreen) {
+            instantCollapseShade();
+        }
+    }
+
+    @Override
     public void instantCollapseShade() {
-        mNotificationPanelViewController.instantCollapse();
+        getShadeViewController().instantCollapse();
         runPostCollapseRunnables();
     }
 
@@ -278,7 +343,7 @@
         }
 
         // Ensure the panel is fully collapsed (just in case; bug 6765842, 7260868)
-        mNotificationPanelViewController.collapse(false, false, 1.0f);
+        getShadeViewController().collapse(false, false, 1.0f);
 
         mExpandedVisible = false;
         notifyVisibilityChanged(false);
@@ -300,7 +365,7 @@
         notifyExpandedVisibleChanged(false);
         mCommandQueue.recomputeDisableFlags(
                 mDisplayId,
-                mNotificationPanelViewController.shouldHideStatusBarIconsWhenExpanded());
+                getShadeViewController().shouldHideStatusBarIconsWhenExpanded());
 
         // Trimming will happen later if Keyguard is showing - doing it here might cause a jank in
         // the bouncer appear animation.
@@ -342,13 +407,15 @@
         return mNotificationShadeWindowViewController.getView();
     }
 
+    private ShadeViewController getShadeViewController() {
+        return mShadeViewControllerLazy.get();
+    }
+
     @Override
-    public void setNotificationPanelViewController(
-            NotificationPanelViewController notificationPanelViewController) {
-        mNotificationPanelViewController = notificationPanelViewController;
-        mNotificationPanelViewController.setTrackingStartedListener(this::runPostCollapseRunnables);
-        mNotificationPanelViewController.setOpenCloseListener(
-                new NotificationPanelViewController.OpenCloseListener() {
+    public void start() {
+        getShadeViewController().setTrackingStartedListener(this::runPostCollapseRunnables);
+        getShadeViewController().setOpenCloseListener(
+                new OpenCloseListener() {
                     @Override
                     public void onClosingFinished() {
                         ShadeControllerImpl.this.onClosingFinished();
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
index 3af75ce..8b89ff4 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
@@ -122,6 +122,8 @@
             }
     }
 
+    var shadeCollapseAction: Runnable? = null
+
     private lateinit var iconManager: StatusBarIconController.TintedIconManager
     private lateinit var carrierIconSlots: List<String>
     private lateinit var mShadeCarrierGroupController: ShadeCarrierGroupController
@@ -131,6 +133,7 @@
     private val date: TextView = header.findViewById(R.id.date)
     private val iconContainer: StatusIconContainer = header.findViewById(R.id.statusIcons)
     private val mShadeCarrierGroup: ShadeCarrierGroup = header.findViewById(R.id.carrier_group)
+    private val systemIcons: View = header.findViewById(R.id.shade_header_system_icons)
 
     private var roundedCorners = 0
     private var cutout: DisplayCutout? = null
@@ -195,7 +198,9 @@
         set(value) {
             if (visible && field != value) {
                 field = value
+                iconContainer.setQsExpansionTransitioning(value > 0f && value < 1.0f)
                 updatePosition()
+                updateIgnoredSlots()
             }
         }
 
@@ -216,6 +221,8 @@
             view.onApplyWindowInsets(insets)
         }
 
+    private var singleCarrier = false
+
     private val demoModeReceiver =
         object : DemoMode {
             override fun demoCommands() = listOf(DemoMode.COMMAND_CLOCK)
@@ -250,6 +257,14 @@
                     header.paddingRight,
                     header.paddingBottom
                 )
+                systemIcons.setPaddingRelative(
+                    resources.getDimensionPixelSize(
+                        R.dimen.shade_header_system_icons_padding_start
+                    ),
+                    systemIcons.paddingTop,
+                    resources.getDimensionPixelSize(R.dimen.shade_header_system_icons_padding_end),
+                    systemIcons.paddingBottom
+                )
             }
 
             override fun onDensityOrFontScaleChanged() {
@@ -262,6 +277,7 @@
                 lastInsets?.let { updateConstraintsForInsets(header, it) }
                 updateResources()
                 updateCarrierGroupPadding()
+                clock.onDensityOrFontScaleChanged()
             }
         }
 
@@ -455,9 +471,11 @@
         if (largeScreenActive) {
             logInstantEvent("Large screen constraints set")
             header.setTransition(LARGE_SCREEN_HEADER_TRANSITION_ID)
+            systemIcons.setOnClickListener { shadeCollapseAction?.run() }
         } else {
             logInstantEvent("Small screen constraints set")
             header.setTransition(HEADER_TRANSITION_ID)
+            systemIcons.setOnClickListener(null)
         }
         header.jumpToState(header.startState)
         updatePosition()
@@ -479,17 +497,20 @@
     private fun updateListeners() {
         mShadeCarrierGroupController.setListening(visible)
         if (visible) {
-            updateSingleCarrier(mShadeCarrierGroupController.isSingleCarrier)
+            singleCarrier = mShadeCarrierGroupController.isSingleCarrier
+            updateIgnoredSlots()
             mShadeCarrierGroupController.setOnSingleCarrierChangedListener {
-                updateSingleCarrier(it)
+                singleCarrier = it
+                updateIgnoredSlots()
             }
         } else {
             mShadeCarrierGroupController.setOnSingleCarrierChangedListener(null)
         }
     }
 
-    private fun updateSingleCarrier(singleCarrier: Boolean) {
-        if (singleCarrier) {
+    private fun updateIgnoredSlots() {
+        // switching from QQS to QS state halfway through the transition
+        if (singleCarrier || qsExpandedFraction < 0.5) {
             iconContainer.removeIgnoredSlots(carrierIconSlots)
         } else {
             iconContainer.addIgnoredSlots(carrierIconSlots)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
index 4fdd6e1..1c30bdd 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
@@ -19,7 +19,7 @@
 import android.view.MotionEvent
 import com.android.systemui.log.dagger.ShadeLog
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import com.android.systemui.shade.ShadeViewController.Companion.FLING_COLLAPSE
 import com.android.systemui.shade.ShadeViewController.Companion.FLING_EXPAND
 import com.android.systemui.shade.ShadeViewController.Companion.FLING_HIDE
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
index b7551cf..be21df1 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.shade
 
+import android.annotation.SuppressLint
 import android.content.ContentResolver
 import android.os.Handler
 import android.view.LayoutInflater
@@ -28,13 +29,24 @@
 import com.android.systemui.battery.BatteryMeterViewController
 import com.android.systemui.biometrics.AuthRippleController
 import com.android.systemui.biometrics.AuthRippleView
+import com.android.systemui.compose.ComposeFacade
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.ui.view.KeyguardRootView
 import com.android.systemui.privacy.OngoingPrivacyChip
+import com.android.systemui.scene.ui.view.WindowRootView
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.LightRevealScrim
+import com.android.systemui.statusbar.NotificationShelf
+import com.android.systemui.statusbar.NotificationShelfController
+import com.android.systemui.statusbar.notification.row.dagger.NotificationShelfComponent
+import com.android.systemui.statusbar.notification.shelf.ui.viewbinder.NotificationShelfViewBinderWrapperControllerImpl
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
+import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
+import com.android.systemui.statusbar.phone.KeyguardBottomAreaView
+import com.android.systemui.statusbar.phone.StatusBarLocation
 import com.android.systemui.statusbar.phone.StatusIconContainer
 import com.android.systemui.statusbar.phone.TapAgainView
 import com.android.systemui.statusbar.policy.BatteryController
@@ -46,9 +58,10 @@
 import dagger.multibindings.ClassKey
 import dagger.multibindings.IntoMap
 import javax.inject.Named
+import javax.inject.Provider
 
 /** Module for classes related to the notification shade. */
-@Module
+@Module(includes = [StartShadeModule::class])
 abstract class ShadeModule {
 
     @Binds
@@ -56,22 +69,47 @@
     @ClassKey(AuthRippleController::class)
     abstract fun bindAuthRippleController(controller: AuthRippleController): CoreStartable
 
+    @Binds
+    @SysUISingleton
+    abstract fun bindsShadeViewController(
+        notificationPanelViewController: NotificationPanelViewController
+    ): ShadeViewController
+
     companion object {
         const val SHADE_HEADER = "large_screen_shade_header"
 
+        @SuppressLint("InflateParams") // Root views don't have parents.
+        @Provides
+        @SysUISingleton
+        fun providesWindowRootView(
+            layoutInflater: LayoutInflater,
+            featureFlags: FeatureFlags,
+        ): WindowRootView {
+            return if (
+                featureFlags.isEnabled(Flags.SCENE_CONTAINER) && ComposeFacade.isComposeAvailable()
+            ) {
+                layoutInflater.inflate(R.layout.scene_window_root, null)
+            } else {
+                layoutInflater.inflate(R.layout.super_notification_shade, null)
+            }
+                as WindowRootView?
+                ?: throw IllegalStateException("Window root view could not be properly inflated")
+        }
+
         @Provides
         @SysUISingleton
         // TODO(b/277762009): Do something similar to
         //  {@link StatusBarWindowModule.InternalWindowView} so that only
         //  {@link NotificationShadeWindowViewController} can inject this view.
         fun providesNotificationShadeWindowView(
-            layoutInflater: LayoutInflater,
+            root: WindowRootView,
+            featureFlags: FeatureFlags,
         ): NotificationShadeWindowView {
-            return layoutInflater.inflate(R.layout.super_notification_shade, /* root= */ null)
-                as NotificationShadeWindowView?
-                ?: throw IllegalStateException(
-                    "R.layout.super_notification_shade could not be properly inflated"
-                )
+            if (featureFlags.isEnabled(Flags.SCENE_CONTAINER)) {
+                return root.findViewById(R.id.legacy_window_root)
+            }
+            return root as NotificationShadeWindowView?
+                ?: throw IllegalStateException("root view not a NotificationShadeWindowView")
         }
 
         // TODO(b/277762009): Only allow this view's controller to inject the view. See above.
@@ -83,6 +121,32 @@
             return notificationShadeWindowView.findViewById(R.id.notification_stack_scroller)
         }
 
+        @Provides
+        @SysUISingleton
+        fun providesNotificationShelfController(
+            featureFlags: FeatureFlags,
+            newImpl: Provider<NotificationShelfViewBinderWrapperControllerImpl>,
+            notificationShelfComponentBuilder: NotificationShelfComponent.Builder,
+            layoutInflater: LayoutInflater,
+            notificationStackScrollLayout: NotificationStackScrollLayout,
+        ): NotificationShelfController {
+            return if (featureFlags.isEnabled(Flags.NOTIFICATION_SHELF_REFACTOR)) {
+                newImpl.get()
+            } else {
+                val shelfView =
+                    layoutInflater.inflate(
+                        R.layout.status_bar_notification_shelf,
+                        notificationStackScrollLayout,
+                        false
+                    ) as NotificationShelf
+                val component =
+                    notificationShelfComponentBuilder.notificationShelf(shelfView).build()
+                val notificationShelfController = component.notificationShelfController
+                notificationShelfController.init()
+                notificationShelfController
+            }
+        }
+
         // TODO(b/277762009): Only allow this view's controller to inject the view. See above.
         @Provides
         @SysUISingleton
@@ -92,6 +156,20 @@
             return notificationShadeWindowView.findViewById(R.id.notification_panel)
         }
 
+        /**
+         * Constructs a new, unattached [KeyguardBottomAreaView].
+         *
+         * Note that this is explicitly _not_ a singleton, as we want to be able to reinflate it
+         */
+        @Provides
+        fun providesKeyguardBottomAreaView(
+            npv: NotificationPanelView,
+            layoutInflater: LayoutInflater,
+        ): KeyguardBottomAreaView {
+            return layoutInflater.inflate(R.layout.keyguard_bottom_area, npv, false)
+                as KeyguardBottomAreaView
+        }
+
         @Provides
         @SysUISingleton
         fun providesLightRevealScrim(
@@ -100,6 +178,22 @@
             return notificationShadeWindowView.findViewById(R.id.light_reveal_scrim)
         }
 
+        @Provides
+        @SysUISingleton
+        fun providesKeyguardRootView(
+            notificationShadeWindowView: NotificationShadeWindowView,
+        ): KeyguardRootView {
+            return notificationShadeWindowView.findViewById(R.id.keyguard_root_view)
+        }
+
+        @Provides
+        @SysUISingleton
+        fun providesSharedNotificationContainer(
+            notificationShadeWindowView: NotificationShadeWindowView,
+        ): SharedNotificationContainer {
+            return notificationShadeWindowView.findViewById(R.id.shared_notification_container)
+        }
+
         // TODO(b/277762009): Only allow this view's controller to inject the view. See above.
         @Provides
         @SysUISingleton
@@ -113,9 +207,15 @@
         @Provides
         @SysUISingleton
         fun providesLockIconView(
-            notificationShadeWindowView: NotificationShadeWindowView,
+            keyguardRootView: KeyguardRootView,
+            notificationPanelView: NotificationPanelView,
+            featureFlags: FeatureFlags
         ): LockIconView {
-            return notificationShadeWindowView.findViewById(R.id.lock_icon_view)
+            if (featureFlags.isEnabled(Flags.MIGRATE_LOCK_ICON)) {
+                return keyguardRootView.findViewById(R.id.lock_icon_view)
+            } else {
+                return notificationPanelView.findViewById(R.id.lock_icon_view)
+            }
         }
 
         // TODO(b/277762009): Only allow this view's controller to inject the view. See above.
@@ -130,6 +230,15 @@
         // TODO(b/277762009): Only allow this view's controller to inject the view. See above.
         @Provides
         @SysUISingleton
+        fun providesNotificationsQuickSettingsContainer(
+            notificationShadeWindowView: NotificationShadeWindowView,
+        ): NotificationsQuickSettingsContainer {
+            return notificationShadeWindowView.findViewById(R.id.notification_container_parent)
+        }
+
+        // TODO(b/277762009): Only allow this view's controller to inject the view. See above.
+        @Provides
+        @SysUISingleton
         @Named(SHADE_HEADER)
         fun providesShadeHeaderView(
             notificationShadeWindowView: NotificationShadeWindowView,
@@ -164,17 +273,16 @@
             tunerService: TunerService,
             @Main mainHandler: Handler,
             contentResolver: ContentResolver,
-            featureFlags: FeatureFlags,
             batteryController: BatteryController,
         ): BatteryMeterViewController {
             return BatteryMeterViewController(
                 batteryMeterView,
+                StatusBarLocation.QS,
                 userTracker,
                 configurationController,
                 tunerService,
                 mainHandler,
                 contentResolver,
-                featureFlags,
                 batteryController,
             )
         }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt
index 5ac36bf..2532bad 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt
@@ -18,6 +18,7 @@
 import android.view.ViewPropertyAnimator
 import com.android.systemui.statusbar.GestureRecorder
 import com.android.systemui.statusbar.NotificationShelfController
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
 import com.android.systemui.statusbar.phone.CentralSurfaces
 import com.android.systemui.statusbar.phone.HeadsUpManagerPhone
 
@@ -36,33 +37,12 @@
         headsUpManager: HeadsUpManagerPhone
     )
 
-    /**
-     * Animate QS collapse by flinging it. If QS is expanded, it will collapse into QQS and stop. If
-     * in split shade, it will collapse the whole shade.
-     *
-     * @param fullyCollapse Do not stop when QS becomes QQS. Fling until QS isn't visible anymore.
-     */
-    fun animateCollapseQs(fullyCollapse: Boolean)
-
-    /** Returns whether the shade can be collapsed. */
-    fun canBeCollapsed(): Boolean
-
     /** Cancels any pending collapses. */
     fun cancelPendingCollapse()
 
     /** Cancels the views current animation. */
     fun cancelAnimation()
 
-    /**
-     * Close the keyguard user switcher if it is open and capable of closing.
-     *
-     * Has no effect if user switcher isn't supported, if the user switcher is already closed, or if
-     * the user switcher uses "simple" mode. The simple user switcher cannot be closed.
-     *
-     * @return true if the keyguard user switcher was open, and is now closed
-     */
-    fun closeUserSwitcherIfOpen(): Boolean
-
     /** Input focus transfer is about to happen. */
     fun startWaitingForExpandGesture()
 
@@ -84,6 +64,9 @@
     /** Animates the view from its current alpha to zero then runs the runnable. */
     fun fadeOut(startDelayMs: Long, durationMs: Long, endAction: Runnable): ViewPropertyAnimator
 
+    /** Returns the NSSL controller. */
+    val notificationStackScrollLayoutController: NotificationStackScrollLayoutController
+
     /** Set whether the bouncer is showing. */
     fun setBouncerShowing(bouncerShowing: Boolean)
 
@@ -116,9 +99,6 @@
     /** Sets the view's alpha to max. */
     fun resetAlpha()
 
-    /** Called when Back gesture has been committed (i.e. a back event has definitely occurred) */
-    fun onBackPressed()
-
     /** Sets progress of the predictive back animation. */
     fun onBackProgressed(progressFraction: Float)
 
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
index f75047c..d5b5c87 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
@@ -17,11 +17,10 @@
 
 import android.view.MotionEvent
 import android.view.ViewGroup
-import com.android.systemui.statusbar.RemoteInputController
+import android.view.ViewTreeObserver
+import com.android.systemui.keyguard.shared.model.WakefulnessModel
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
-import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
 import com.android.systemui.statusbar.phone.HeadsUpAppearanceController
-import com.android.systemui.statusbar.phone.KeyguardBottomAreaView
 import com.android.systemui.statusbar.phone.KeyguardStatusBarView
 import com.android.systemui.statusbar.phone.KeyguardStatusBarViewController
 import java.util.function.Consumer
@@ -60,7 +59,7 @@
      * Returns whether the shade height is greater than zero or the shade is expecting a synthesized
      * down event.
      */
-    @get:Deprecated("use {@link #isExpanded()} instead") val isPanelExpanded: Boolean
+    val isPanelExpanded: Boolean
 
     /** Returns whether the shade is fully expanded in either QS or QQS. */
     val isShadeFullyExpanded: Boolean
@@ -77,6 +76,20 @@
     /** Collapses the shade with an animation duration in milliseconds. */
     fun collapseWithDuration(animationDuration: Int)
 
+    /** Collapses the shade instantly without animation. */
+    fun instantCollapse()
+
+    /**
+     * Animate QS collapse by flinging it. If QS is expanded, it will collapse into QQS and stop. If
+     * in split shade, it will collapse the whole shade.
+     *
+     * @param fullyCollapse Do not stop when QS becomes QQS. Fling until QS isn't visible anymore.
+     */
+    fun animateCollapseQs(fullyCollapse: Boolean)
+
+    /** Returns whether the shade can be collapsed. */
+    fun canBeCollapsed(): Boolean
+
     /** Returns whether the shade is in the process of collapsing. */
     val isCollapsing: Boolean
 
@@ -89,6 +102,9 @@
     /** Returns whether the shade's top level view is enabled. */
     val isViewEnabled: Boolean
 
+    /** Sets a listener to be notified when the shade starts opening or finishes closing. */
+    fun setOpenCloseListener(openCloseListener: OpenCloseListener)
+
     /** Returns whether status bar icons should be hidden when the shade is expanded. */
     fun shouldHideStatusBarIconsWhenExpanded(): Boolean
 
@@ -98,6 +114,9 @@
      */
     fun blockExpansionForCurrentTouch()
 
+    /** Sets a listener to be notified when touch tracking begins. */
+    fun setTrackingStartedListener(trackingStartedListener: TrackingStartedListener)
+
     /**
      * Disables the shade header.
      *
@@ -120,19 +139,22 @@
     /** Returns the StatusBarState. */
     val barState: Int
 
-    /**
-     * Returns the bottom part of the keyguard, which contains quick affordances.
-     *
-     * TODO(b/275550429): this should be removed.
-     */
-    val keyguardBottomAreaView: KeyguardBottomAreaView?
-
-    /** Returns the NSSL controller. */
-    val notificationStackScrollLayoutController: NotificationStackScrollLayoutController
-
     /** Sets the amount of progress in the status bar launch animation. */
     fun applyLaunchAnimationProgress(linearProgress: Float)
 
+    /**
+     * Close the keyguard user switcher if it is open and capable of closing.
+     *
+     * Has no effect if user switcher isn't supported, if the user switcher is already closed, or if
+     * the user switcher uses "simple" mode. The simple user switcher cannot be closed.
+     *
+     * @return true if the keyguard user switcher was open, and is now closed
+     */
+    fun closeUserSwitcherIfOpen(): Boolean
+
+    /** Called when Back gesture has been committed (i.e. a back event has definitely occurred) */
+    fun onBackPressed()
+
     /** Sets whether the status bar launch animation is currently running. */
     fun setIsLaunchAnimationRunning(running: Boolean)
 
@@ -161,6 +183,15 @@
     /** Ensures that the touchable region is updated. */
     fun updateTouchableRegion()
 
+    /** Adds a global layout listener. */
+    fun addOnGlobalLayoutListener(listener: ViewTreeObserver.OnGlobalLayoutListener)
+
+    /** Removes a global layout listener. */
+    fun removeOnGlobalLayoutListener(listener: ViewTreeObserver.OnGlobalLayoutListener)
+
+    /** Posts the given runnable to the view. */
+    fun postToView(action: Runnable): Boolean
+
     // ******* Begin Keyguard Section *********
     /** Animate to expanded shade after a delay in ms. Used for lockscreen to shade transition. */
     fun transitionToExpandedShade(delay: Long)
@@ -214,6 +245,9 @@
     /** Sends an external (e.g. Status Bar) touch event to the Shade touch handler. */
     fun handleExternalTouch(event: MotionEvent): Boolean
 
+    /** Starts tracking a shade expansion gesture that originated from the status bar. */
+    fun startTrackingExpansionFromStatusBar()
+
     // ******* End Keyguard Section *********
 
     /** Returns the ShadeHeadsUpTracker. */
@@ -222,10 +256,17 @@
     /** Returns the ShadeFoldAnimator. */
     val shadeFoldAnimator: ShadeFoldAnimator
 
-    /** Returns the ShadeNotificationPresenter. */
-    val shadeNotificationPresenter: ShadeNotificationPresenter
-
     companion object {
+        /**
+         * Returns a multiplicative factor to use when determining the falsing threshold for touches
+         * on the shade. The factor will be larger when the device is waking up due to a touch or
+         * gesture.
+         */
+        @JvmStatic
+        fun getFalsingThresholdFactor(wakefulness: WakefulnessModel): Float {
+            return if (wakefulness.isDeviceInteractiveFromTapOrGesture()) 1.5f else 1.0f
+        }
+
         const val WAKEUP_ANIMATION_DELAY_MS = 250
         const val FLING_MAX_LENGTH_SECONDS = 0.6f
         const val FLING_SPEED_UP_FACTOR = 0.6f
@@ -276,16 +317,7 @@
     fun cancelFoldToAodAnimation()
 
     /** Returns the main view of the shade. */
-    val view: ViewGroup
-}
-
-/** Handles the shade's interactions with StatusBarNotificationPresenter. */
-interface ShadeNotificationPresenter {
-    /** Returns a new delegate for some view controller pieces of the remote input process. */
-    fun createRemoteInputDelegate(): RemoteInputController.Delegate
-
-    /** Returns whether the screen has temporarily woken up to display notifications. */
-    fun hasPulsingNotifications(): Boolean
+    val view: ViewGroup?
 }
 
 /**
@@ -307,3 +339,17 @@
     /** Return the fraction of the shade that's expanded, when in lockscreen. */
     val lockscreenShadeDragProgress: Float
 }
+
+/** Listens for when touch tracking begins. */
+interface TrackingStartedListener {
+    fun onTrackingStarted()
+}
+
+/** Listens for when shade begins opening or finishes closing. */
+interface OpenCloseListener {
+    /** Called when the shade finishes closing. */
+    fun onClosingFinished()
+
+    /** Called when the shade starts opening. */
+    fun onOpenStarted()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
new file mode 100644
index 0000000..287ac52
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade
+
+import android.view.MotionEvent
+import android.view.ViewGroup
+import android.view.ViewTreeObserver
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
+import com.android.systemui.statusbar.phone.HeadsUpAppearanceController
+import java.util.function.Consumer
+import javax.inject.Inject
+
+/** Empty implementation of ShadeViewController for variants with no shade. */
+class ShadeViewControllerEmptyImpl @Inject constructor() : ShadeViewController {
+    override fun expand(animate: Boolean) {}
+    override fun expandToQs() {}
+    override fun expandToNotifications() {}
+    override val isExpandingOrCollapsing: Boolean = false
+    override val isExpanded: Boolean = false
+    override val isPanelExpanded: Boolean = false
+    override val isShadeFullyExpanded: Boolean = false
+    override fun collapse(delayed: Boolean, speedUpFactor: Float) {}
+    override fun collapse(animate: Boolean, delayed: Boolean, speedUpFactor: Float) {}
+    override fun collapseWithDuration(animationDuration: Int) {}
+    override fun instantCollapse() {}
+    override fun animateCollapseQs(fullyCollapse: Boolean) {}
+    override fun canBeCollapsed(): Boolean = false
+    override val isCollapsing: Boolean = false
+    override val isFullyCollapsed: Boolean = false
+    override val isTracking: Boolean = false
+    override val isViewEnabled: Boolean = false
+    override fun setOpenCloseListener(openCloseListener: OpenCloseListener) {}
+    override fun shouldHideStatusBarIconsWhenExpanded() = false
+    override fun blockExpansionForCurrentTouch() {}
+    override fun setTrackingStartedListener(trackingStartedListener: TrackingStartedListener) {}
+    override fun disableHeader(state1: Int, state2: Int, animated: Boolean) {}
+    override fun startExpandLatencyTracking() {}
+    override fun startBouncerPreHideAnimation() {}
+    override fun dozeTimeTick() {}
+    override fun resetViews(animate: Boolean) {}
+    override val barState: Int = 0
+    override fun applyLaunchAnimationProgress(linearProgress: Float) {}
+    override fun closeUserSwitcherIfOpen(): Boolean {
+        return false
+    }
+    override fun onBackPressed() {}
+    override fun setIsLaunchAnimationRunning(running: Boolean) {}
+    override fun setAlpha(alpha: Int, animate: Boolean) {}
+    override fun setAlphaChangeAnimationEndAction(r: Runnable) {}
+    override fun setPulsing(pulsing: Boolean) {}
+    override fun setQsScrimEnabled(qsScrimEnabled: Boolean) {}
+    override fun setAmbientIndicationTop(ambientIndicationTop: Int, ambientTextVisible: Boolean) {}
+    override fun updateSystemUiStateFlags() {}
+    override fun updateTouchableRegion() {}
+    override fun addOnGlobalLayoutListener(listener: ViewTreeObserver.OnGlobalLayoutListener) {}
+    override fun removeOnGlobalLayoutListener(listener: ViewTreeObserver.OnGlobalLayoutListener) {}
+    override fun postToView(action: Runnable): Boolean {
+        return false
+    }
+    override fun transitionToExpandedShade(delay: Long) {}
+    override val isUnlockHintRunning: Boolean = false
+
+    override fun resetViewGroupFade() {}
+    override fun setKeyguardTransitionProgress(keyguardAlpha: Float, keyguardTranslationY: Int) {}
+    override fun setOverStretchAmount(amount: Float) {}
+    override fun setKeyguardStatusBarAlpha(alpha: Float) {}
+    override fun showAodUi() {}
+    override fun isFullyExpanded(): Boolean {
+        return false
+    }
+    override fun handleExternalTouch(event: MotionEvent): Boolean {
+        return false
+    }
+    override fun startTrackingExpansionFromStatusBar() {}
+    override val shadeHeadsUpTracker = ShadeHeadsUpTrackerEmptyImpl()
+    override val shadeFoldAnimator = ShadeFoldAnimatorEmptyImpl()
+}
+
+class ShadeHeadsUpTrackerEmptyImpl : ShadeHeadsUpTracker {
+    override fun addTrackingHeadsUpListener(listener: Consumer<ExpandableNotificationRow>) {}
+    override fun removeTrackingHeadsUpListener(listener: Consumer<ExpandableNotificationRow>) {}
+    override fun setHeadsUpAppearanceController(
+        headsUpAppearanceController: HeadsUpAppearanceController?
+    ) {}
+    override val trackedHeadsUpNotification: ExpandableNotificationRow? = null
+}
+
+class ShadeFoldAnimatorEmptyImpl : ShadeFoldAnimator {
+    override fun prepareFoldToAodAnimation() {}
+    override fun startFoldToAodAnimation(
+        startAction: Runnable,
+        endAction: Runnable,
+        cancelAction: Runnable,
+    ) {}
+    override fun cancelFoldToAodAnimation() {}
+    override val view: ViewGroup? = null
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeWindowLogger.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeWindowLogger.kt
index d06634b..e7a397b 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeWindowLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeWindowLogger.kt
@@ -16,14 +16,13 @@
 
 package com.android.systemui.shade
 
-import android.view.WindowManager
-import com.android.systemui.log.dagger.ShadeWindowLog
 import com.android.systemui.log.ConstantStringsLogger
 import com.android.systemui.log.ConstantStringsLoggerImpl
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
-import com.android.systemui.log.LogLevel.DEBUG
-import com.android.systemui.log.LogMessage
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.core.LogLevel.DEBUG
+import com.android.systemui.log.core.LogMessage
+import com.android.systemui.log.dagger.ShadeWindowLog
 import javax.inject.Inject
 
 private const val TAG = "systemui.shadewindow"
@@ -31,15 +30,6 @@
 class ShadeWindowLogger @Inject constructor(@ShadeWindowLog private val buffer: LogBuffer) :
     ConstantStringsLogger by ConstantStringsLoggerImpl(buffer, TAG) {
 
-    fun logApplyingWindowLayoutParams(lp: WindowManager.LayoutParams) {
-        buffer.log(
-            TAG,
-            DEBUG,
-            { str1 = lp.toString() },
-            { "Applying new window layout params: $str1" }
-        )
-    }
-
     fun logNewState(state: Any) {
         buffer.log(
             TAG,
diff --git a/packages/SystemUI/src/com/android/systemui/shade/StartShadeModule.kt b/packages/SystemUI/src/com/android/systemui/shade/StartShadeModule.kt
new file mode 100644
index 0000000..c50693c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/StartShadeModule.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.shade
+
+import com.android.systemui.CoreStartable
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+
+@Module
+internal abstract class StartShadeModule {
+    @Binds
+    @IntoMap
+    @ClassKey(ShadeController::class)
+    abstract fun bind(shadeController: ShadeController): CoreStartable
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
new file mode 100644
index 0000000..6fde84a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
@@ -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.systemui.shade.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import com.android.systemui.statusbar.disableflags.data.repository.DisableFlagsRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepository
+import com.android.systemui.statusbar.policy.DeviceProvisionedController
+import com.android.systemui.user.domain.interactor.UserInteractor
+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.map
+import kotlinx.coroutines.flow.stateIn
+
+/** Business logic for shade interactions. */
+@SysUISingleton
+class ShadeInteractor
+@Inject
+constructor(
+    @Application scope: CoroutineScope,
+    disableFlagsRepository: DisableFlagsRepository,
+    keyguardRepository: KeyguardRepository,
+    userSetupRepository: UserSetupRepository,
+    deviceProvisionedController: DeviceProvisionedController,
+    userInteractor: UserInteractor,
+) {
+    /** Emits true if the shade is currently allowed and false otherwise. */
+    val isShadeEnabled: StateFlow<Boolean> =
+        disableFlagsRepository.disableFlags
+            .map { it.isShadeEnabled() }
+            .stateIn(scope, SharingStarted.Eagerly, initialValue = false)
+
+    /** Emits true if the shade can be expanded from QQS to QS and false otherwise. */
+    val isExpandToQsEnabled: Flow<Boolean> =
+        combine(
+            disableFlagsRepository.disableFlags,
+            isShadeEnabled,
+            keyguardRepository.isDozing,
+            userSetupRepository.isUserSetupFlow,
+        ) { disableFlags, isShadeEnabled, isDozing, isUserSetup ->
+            deviceProvisionedController.isDeviceProvisioned &&
+                // Disallow QS during setup if it's a simple user switcher. (The user intends to
+                // use the lock screen user switcher, QS is not needed.)
+                (isUserSetup || !userInteractor.isSimpleUserSwitcher) &&
+                isShadeEnabled &&
+                disableFlags.isQuickSettingsEnabled() &&
+                !isDozing
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/transition/ScrimShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/shade/transition/ScrimShadeTransitionController.kt
index 4e1c272..abb69f6 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/transition/ScrimShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/transition/ScrimShadeTransitionController.kt
@@ -16,24 +16,11 @@
 
 package com.android.systemui.shade.transition
 
-import android.content.res.Configuration
-import android.content.res.Resources
-import android.util.MathUtils.constrain
-import com.android.systemui.R
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.dump.DumpManager
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
 import com.android.systemui.shade.PanelState
-import com.android.systemui.shade.STATE_OPENING
 import com.android.systemui.shade.ShadeExpansionChangeEvent
-import com.android.systemui.statusbar.StatusBarState
-import com.android.systemui.statusbar.SysuiStatusBarStateController
 import com.android.systemui.statusbar.phone.ScrimController
-import com.android.systemui.statusbar.policy.ConfigurationController
-import com.android.systemui.statusbar.policy.HeadsUpManager
-import com.android.systemui.util.LargeScreenUtils
 import java.io.PrintWriter
 import javax.inject.Inject
 
@@ -42,37 +29,19 @@
 class ScrimShadeTransitionController
 @Inject
 constructor(
-    configurationController: ConfigurationController,
     dumpManager: DumpManager,
     private val scrimController: ScrimController,
-    @Main private val resources: Resources,
-    private val statusBarStateController: SysuiStatusBarStateController,
-    private val headsUpManager: HeadsUpManager,
-    private val featureFlags: FeatureFlags,
 ) {
 
-    private var inSplitShade = false
-    private var splitShadeScrimTransitionDistance = 0
     private var lastExpansionFraction: Float? = null
     private var lastExpansionEvent: ShadeExpansionChangeEvent? = null
     private var currentPanelState: Int? = null
 
     init {
-        updateResources()
-        configurationController.addCallback(
-            object : ConfigurationController.ConfigurationListener {
-                override fun onConfigChanged(newConfig: Configuration?) {
-                    updateResources()
-                }
-            })
         dumpManager.registerDumpable(
-            ScrimShadeTransitionController::class.java.simpleName, this::dump)
-    }
-
-    private fun updateResources() {
-        inSplitShade = LargeScreenUtils.shouldUseSplitNotificationShade(resources)
-        splitShadeScrimTransitionDistance =
-            resources.getDimensionPixelSize(R.dimen.split_shade_scrim_transition_distance)
+            ScrimShadeTransitionController::class.java.simpleName,
+            this::dump
+        )
     }
 
     fun onPanelStateChanged(@PanelState state: Int) {
@@ -87,46 +56,25 @@
 
     private fun onStateChanged() {
         val expansionEvent = lastExpansionEvent ?: return
-        val panelState = currentPanelState
-        val expansionFraction = calculateScrimExpansionFraction(expansionEvent, panelState)
+        val expansionFraction = calculateScrimExpansionFraction(expansionEvent)
         scrimController.setRawPanelExpansionFraction(expansionFraction)
         lastExpansionFraction = expansionFraction
     }
 
-    private fun calculateScrimExpansionFraction(
-        expansionEvent: ShadeExpansionChangeEvent,
-        @PanelState panelState: Int?
-    ): Float {
-        return if (canUseCustomFraction(panelState)) {
-            constrain(expansionEvent.dragDownPxAmount / splitShadeScrimTransitionDistance, 0f, 1f)
-        } else {
-            expansionEvent.fraction
-        }
+    private fun calculateScrimExpansionFraction(expansionEvent: ShadeExpansionChangeEvent): Float {
+        return expansionEvent.fraction
     }
 
-    private fun canUseCustomFraction(panelState: Int?) =
-        inSplitShade && isScreenUnlocked() && panelState == STATE_OPENING &&
-                // in case of HUN we can't always use predefined distances to manage scrim
-                // transition because dragDownPxAmount can start from value bigger than
-                // splitShadeScrimTransitionDistance
-                !headsUpManager.isTrackingHeadsUp &&
-                !featureFlags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION)
-
-    private fun isScreenUnlocked() =
-        statusBarStateController.currentOrUpcomingState == StatusBarState.SHADE
-
     private fun dump(printWriter: PrintWriter, args: Array<String>) {
         printWriter.println(
             """
                 ScrimShadeTransitionController:
-                  Resources:
-                    inSplitShade: $inSplitShade
-                    isScreenUnlocked: ${isScreenUnlocked()}
-                    splitShadeScrimTransitionDistance: $splitShadeScrimTransitionDistance
                   State:
                     currentPanelState: $currentPanelState
                     lastExpansionFraction: $lastExpansionFraction
                     lastExpansionEvent: $lastExpansionEvent
-            """.trimIndent())
+            """
+                .trimIndent()
+        )
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ActionClickLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/ActionClickLogger.kt
index e008ec0..d3c19b7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ActionClickLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ActionClickLogger.kt
@@ -19,7 +19,7 @@
 import android.app.PendingIntent
 import com.android.systemui.log.dagger.NotifInteractionLog
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import javax.inject.Inject
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java
index 77d98d2..faffb3e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java
@@ -105,7 +105,7 @@
 
         alertEntry.mEntry.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
         if (alert) {
-            alertEntry.updateEntry(true /* updatePostTime */);
+            alertEntry.updateEntry(true /* updatePostTime */, "updateNotification");
         }
     }
 
@@ -273,15 +273,15 @@
             mRemoveAlertRunnable = removeAlertRunnable;
 
             mPostTime = calculatePostTime();
-            updateEntry(true /* updatePostTime */);
+            updateEntry(true /* updatePostTime */, "setEntry");
         }
 
         /**
          * Updates an entry's removal time.
          * @param updatePostTime whether or not to refresh the post time
          */
-        public void updateEntry(boolean updatePostTime) {
-            mLogger.logUpdateEntry(mEntry, updatePostTime);
+        public void updateEntry(boolean updatePostTime, @Nullable String reason) {
+            mLogger.logUpdateEntry(mEntry, updatePostTime, reason);
 
             final long now = mClock.currentTimeMillis();
             mEarliestRemovaltime = now + mMinimumDisplayTime;
@@ -289,7 +289,7 @@
             if (updatePostTime) {
                 mPostTime = Math.max(mPostTime, now);
             }
-            removeAutoRemovalCallbacks();
+            removeAutoRemovalCallbacks("updateEntry (will be rescheduled)");
 
             if (!isSticky()) {
                 final long finishTime = calculateFinishTime();
@@ -329,16 +329,17 @@
         }
 
         public void reset() {
+            removeAutoRemovalCallbacks("reset()");
             mEntry = null;
-            removeAutoRemovalCallbacks();
             mRemoveAlertRunnable = null;
         }
 
         /**
          * Clear any pending removal runnables.
          */
-        public void removeAutoRemovalCallbacks() {
+        public void removeAutoRemovalCallbacks(@Nullable String reason) {
             if (mRemoveAlertRunnable != null) {
+                mLogger.logAutoRemoveCanceled(mEntry, reason);
                 mHandler.removeCallbacks(mRemoveAlertRunnable);
             }
         }
@@ -348,7 +349,7 @@
          */
         public void removeAsSoonAsPossible() {
             if (mRemoveAlertRunnable != null) {
-                removeAutoRemovalCallbacks();
+                removeAutoRemovalCallbacks("removeAsSoonAsPossible (will be rescheduled)");
 
                 final long timeLeft = mEarliestRemovaltime - mClock.currentTimeMillis();
                 mHandler.postDelayed(mRemoveAlertRunnable, timeLeft);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBarFrameLayout.kt b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBarFrameLayout.kt
index ce730ba..5d06f8d0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBarFrameLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBarFrameLayout.kt
@@ -21,8 +21,8 @@
 
 /**
  * A temporary base class that's shared between our old status bar connectivity view implementations
- * ([StatusBarWifiView], [StatusBarMobileView]) and our new status bar implementations (
- * [ModernStatusBarWifiView], [ModernStatusBarMobileView]).
+ * ([StatusBarMobileView]) and our new status bar implementations ([ModernStatusBarWifiView],
+ * [ModernStatusBarMobileView]).
  *
  * Once our refactor is over, we should be able to delete this go-between class and the old view
  * class.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt b/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt
index 39d213d..a0cbdf3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt
@@ -48,7 +48,7 @@
     private var earlyWakeupEnabled = false
 
     init {
-        dumpManager.registerDumpable(javaClass.name, this)
+        dumpManager.registerDumpable(this)
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index a532195..92df78b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -73,7 +73,6 @@
 import com.android.systemui.statusbar.CommandQueue.Callbacks;
 import com.android.systemui.statusbar.commandline.CommandRegistry;
 import com.android.systemui.statusbar.policy.CallbackController;
-import com.android.systemui.tracing.ProtoTracer;
 
 import java.io.FileDescriptor;
 import java.io.FileOutputStream;
@@ -190,7 +189,6 @@
      */
     private int mLastUpdatedImeDisplayId = INVALID_DISPLAY;
     private final DisplayTracker mDisplayTracker;
-    private ProtoTracer mProtoTracer;
     private final @Nullable CommandRegistry mRegistry;
     private final @Nullable DumpHandler mDumpHandler;
 
@@ -504,18 +502,16 @@
 
     @VisibleForTesting
     public CommandQueue(Context context, DisplayTracker displayTracker) {
-        this(context, displayTracker, null, null, null);
+        this(context, displayTracker, null, null);
     }
 
     public CommandQueue(
             Context context,
             DisplayTracker displayTracker,
-            ProtoTracer protoTracer,
             CommandRegistry registry,
             DumpHandler dumpHandler
     ) {
         mDisplayTracker = displayTracker;
-        mProtoTracer = protoTracer;
         mRegistry = registry;
         mDumpHandler = dumpHandler;
         mDisplayTracker.addDisplayChangeCallback(new DisplayTracker.Callback() {
@@ -1160,9 +1156,6 @@
     @Override
     public void startTracing() {
         synchronized (mLock) {
-            if (mProtoTracer != null) {
-                mProtoTracer.start();
-            }
             mHandler.obtainMessage(MSG_TRACING_STATE_CHANGED, true).sendToTarget();
         }
     }
@@ -1170,9 +1163,6 @@
     @Override
     public void stopTracing() {
         synchronized (mLock) {
-            if (mProtoTracer != null) {
-                mProtoTracer.stop();
-            }
             mHandler.obtainMessage(MSG_TRACING_STATE_CHANGED, false).sendToTarget();
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ConnectedDisplayChip.kt b/packages/SystemUI/src/com/android/systemui/statusbar/ConnectedDisplayChip.kt
new file mode 100644
index 0000000..76636ab8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ConnectedDisplayChip.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.statusbar
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.content.res.Configuration
+import android.util.AttributeSet
+import android.widget.FrameLayout
+import com.android.systemui.R
+import com.android.systemui.statusbar.events.BackgroundAnimatableView
+
+/** Chip that appears in the status bar when an external display is connected. */
+class ConnectedDisplayChip
+@JvmOverloads
+constructor(context: Context, attrs: AttributeSet? = null) :
+    FrameLayout(context, attrs), BackgroundAnimatableView {
+
+    private val iconContainer: FrameLayout
+    init {
+        inflate(context, R.layout.connected_display_chip, this)
+        iconContainer = requireViewById(R.id.icons_rounded_container)
+    }
+
+    /**
+     * When animating as a chip in the status bar, we want to animate the width for the rounded
+     * container. We have to subtract our own top and left offset because the bounds come to us as
+     * absolute on-screen bounds.
+     */
+    override fun setBoundsForAnimation(l: Int, t: Int, r: Int, b: Int) {
+        iconContainer.setLeftTopRightBottom(l - left, t - top, r - left, b - top)
+    }
+
+    override fun onConfigurationChanged(newConfig: Configuration) {
+        super.onConfigurationChanged(newConfig)
+        updateResources()
+    }
+
+    @SuppressLint("UseCompatLoadingForDrawables")
+    private fun updateResources() {
+        iconContainer.background = context.getDrawable(R.drawable.statusbar_chip_bg)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
index 906c5ed..ec66e99 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
@@ -56,7 +56,6 @@
 import android.view.ViewGroup;
 import android.view.Window;
 import android.view.WindowManager;
-import android.view.WindowManager.KeyboardShortcutsReceiver;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.Button;
 import android.widget.EditText;
@@ -337,6 +336,12 @@
         mSpecialCharacterNames.put(KeyEvent.KEYCODE_MUHENKAN, "無変換");
         mSpecialCharacterNames.put(KeyEvent.KEYCODE_HENKAN, "変換");
         mSpecialCharacterNames.put(KeyEvent.KEYCODE_KATAKANA_HIRAGANA, "かな");
+        mSpecialCharacterNames.put(KeyEvent.KEYCODE_ALT_LEFT, "Alt");
+        mSpecialCharacterNames.put(KeyEvent.KEYCODE_ALT_RIGHT, "Alt");
+        mSpecialCharacterNames.put(KeyEvent.KEYCODE_CTRL_LEFT, "Ctrl");
+        mSpecialCharacterNames.put(KeyEvent.KEYCODE_CTRL_RIGHT, "Ctrl");
+        mSpecialCharacterNames.put(KeyEvent.KEYCODE_SHIFT_LEFT, "Shift");
+        mSpecialCharacterNames.put(KeyEvent.KEYCODE_SHIFT_RIGHT, "Shift");
 
         mModifierNames.put(KeyEvent.META_META_ON, "Meta");
         mModifierNames.put(KeyEvent.META_CTRL_ON, "Ctrl");
@@ -411,27 +416,45 @@
         mKeyCharacterMap = mBackupKeyCharacterMap;
     }
 
+    private boolean mAppShortcutsReceived;
+    private boolean mImeShortcutsReceived;
+
     @VisibleForTesting
     void showKeyboardShortcuts(int deviceId) {
         retrieveKeyCharacterMap(deviceId);
-        mWindowManager.requestAppKeyboardShortcuts(new KeyboardShortcutsReceiver() {
-            @Override
-            public void onKeyboardShortcutsReceived(
-                    final List<KeyboardShortcutGroup> result) {
-                // Add specific app shortcuts
-                if (result.isEmpty()) {
-                    mKeySearchResultMap.put(SHORTCUT_SPECIFICAPP_INDEX, false);
-                } else {
-                    mSpecificAppGroup = reMapToKeyboardShortcutMultiMappingGroup(result);
-                    mKeySearchResultMap.put(SHORTCUT_SPECIFICAPP_INDEX, true);
-                }
-                mFullShortsGroup.add(SHORTCUT_SYSTEM_INDEX, mSystemGroup);
-                mFullShortsGroup.add(SHORTCUT_INPUT_INDEX, mInputGroup);
-                mFullShortsGroup.add(SHORTCUT_OPENAPPS_INDEX, mOpenAppsGroup);
-                mFullShortsGroup.add(SHORTCUT_SPECIFICAPP_INDEX, mSpecificAppGroup);
-                showKeyboardShortcutSearchList(mFullShortsGroup);
+        mAppShortcutsReceived = false;
+        mImeShortcutsReceived = false;
+        mWindowManager.requestAppKeyboardShortcuts(result -> {
+            // Add specific app shortcuts
+            if (result.isEmpty()) {
+                mKeySearchResultMap.put(SHORTCUT_SPECIFICAPP_INDEX, false);
+            } else {
+                mSpecificAppGroup.addAll(reMapToKeyboardShortcutMultiMappingGroup(result));
+                mKeySearchResultMap.put(SHORTCUT_SPECIFICAPP_INDEX, true);
+            }
+            mAppShortcutsReceived = true;
+            if (mImeShortcutsReceived) {
+                mergeAndShowKeyboardShortcutsGroups();
             }
         }, deviceId);
+        mWindowManager.requestImeKeyboardShortcuts(result -> {
+            // Add specific Ime shortcuts
+            if (!result.isEmpty()) {
+                mInputGroup.addAll(reMapToKeyboardShortcutMultiMappingGroup(result));
+            }
+            mImeShortcutsReceived = true;
+            if (mAppShortcutsReceived) {
+                mergeAndShowKeyboardShortcutsGroups();
+            }
+        }, deviceId);
+    }
+
+    private void mergeAndShowKeyboardShortcutsGroups() {
+        mFullShortsGroup.add(SHORTCUT_SYSTEM_INDEX, mSystemGroup);
+        mFullShortsGroup.add(SHORTCUT_INPUT_INDEX, mInputGroup);
+        mFullShortsGroup.add(SHORTCUT_OPENAPPS_INDEX, mOpenAppsGroup);
+        mFullShortsGroup.add(SHORTCUT_SPECIFICAPP_INDEX, mSpecificAppGroup);
+        showKeyboardShortcutSearchList(mFullShortsGroup);
     }
 
     // The original data structure is only for 1-to-1 shortcut mapping, so remap the old
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java
index 43fbc7c..ae3d41e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java
@@ -56,7 +56,6 @@
 import android.view.ViewGroup;
 import android.view.Window;
 import android.view.WindowManager;
-import android.view.WindowManager.KeyboardShortcutsReceiver;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
@@ -129,6 +128,9 @@
     private KeyCharacterMap mKeyCharacterMap;
     private KeyCharacterMap mBackupKeyCharacterMap;
 
+    @Nullable private List<KeyboardShortcutGroup> mReceivedAppShortcutGroups = null;
+    @Nullable private List<KeyboardShortcutGroup> mReceivedImeShortcutGroups = null;
+
     @VisibleForTesting
     KeyboardShortcuts(Context context, WindowManager windowManager) {
         this.mContext = new ContextThemeWrapper(
@@ -324,6 +326,12 @@
         mSpecialCharacterNames.put(KeyEvent.KEYCODE_MUHENKAN, "無変換");
         mSpecialCharacterNames.put(KeyEvent.KEYCODE_HENKAN, "変換");
         mSpecialCharacterNames.put(KeyEvent.KEYCODE_KATAKANA_HIRAGANA, "かな");
+        mSpecialCharacterNames.put(KeyEvent.KEYCODE_ALT_LEFT, "Alt");
+        mSpecialCharacterNames.put(KeyEvent.KEYCODE_ALT_RIGHT, "Alt");
+        mSpecialCharacterNames.put(KeyEvent.KEYCODE_CTRL_LEFT, "Ctrl");
+        mSpecialCharacterNames.put(KeyEvent.KEYCODE_CTRL_RIGHT, "Ctrl");
+        mSpecialCharacterNames.put(KeyEvent.KEYCODE_SHIFT_LEFT, "Shift");
+        mSpecialCharacterNames.put(KeyEvent.KEYCODE_SHIFT_RIGHT, "Shift");
 
         mModifierNames.put(KeyEvent.META_META_ON, "Meta");
         mModifierNames.put(KeyEvent.META_CTRL_ON, "Ctrl");
@@ -382,18 +390,36 @@
     @VisibleForTesting
     void showKeyboardShortcuts(int deviceId) {
         retrieveKeyCharacterMap(deviceId);
-        mWindowManager.requestAppKeyboardShortcuts(new KeyboardShortcutsReceiver() {
-            @Override
-            public void onKeyboardShortcutsReceived(
-                    final List<KeyboardShortcutGroup> result) {
-                result.add(getSystemShortcuts());
-                final KeyboardShortcutGroup appShortcuts = getDefaultApplicationShortcuts();
-                if (appShortcuts != null) {
-                    result.add(appShortcuts);
-                }
-                showKeyboardShortcutsDialog(result);
-            }
-        }, deviceId);
+        mReceivedAppShortcutGroups = null;
+        mReceivedImeShortcutGroups = null;
+        mWindowManager.requestAppKeyboardShortcuts(
+                result -> {
+                    mReceivedAppShortcutGroups = result;
+                    maybeMergeAndShowKeyboardShortcuts();
+                }, deviceId);
+        mWindowManager.requestImeKeyboardShortcuts(
+                result -> {
+                    mReceivedImeShortcutGroups = result;
+                    maybeMergeAndShowKeyboardShortcuts();
+                }, deviceId);
+    }
+
+    private void maybeMergeAndShowKeyboardShortcuts() {
+        if (mReceivedAppShortcutGroups == null || mReceivedImeShortcutGroups == null) {
+            return;
+        }
+        List<KeyboardShortcutGroup> shortcutGroups = mReceivedAppShortcutGroups;
+        shortcutGroups.addAll(mReceivedImeShortcutGroups);
+        mReceivedAppShortcutGroups = null;
+        mReceivedImeShortcutGroups = null;
+
+        final KeyboardShortcutGroup defaultAppShortcuts =
+                getDefaultApplicationShortcuts();
+        if (defaultAppShortcuts != null) {
+            shortcutGroups.add(defaultAppShortcuts);
+        }
+        shortcutGroups.add(getSystemShortcuts());
+        showKeyboardShortcutsDialog(shortcutGroups);
     }
 
     private void dismissKeyboardShortcuts() {
@@ -414,10 +440,15 @@
                 mContext.getString(R.string.keyboard_shortcut_group_system_back),
                 KeyEvent.KEYCODE_DEL,
                 KeyEvent.META_META_ON));
-        systemGroup.addItem(new KeyboardShortcutInfo(
-                mContext.getString(R.string.keyboard_shortcut_group_system_recents),
-                KeyEvent.KEYCODE_TAB,
-                KeyEvent.META_ALT_ON));
+
+        // Some devices (like TV) don't have recents
+        if (mContext.getResources().getBoolean(com.android.internal.R.bool.config_hasRecents)) {
+            systemGroup.addItem(new KeyboardShortcutInfo(
+                    mContext.getString(R.string.keyboard_shortcut_group_system_recents),
+                    KeyEvent.KEYCODE_TAB,
+                    KeyEvent.META_ALT_ON));
+        }
+
         systemGroup.addItem(new KeyboardShortcutInfo(
                 mContext.getString(
                         R.string.keyboard_shortcut_group_system_notifications),
@@ -657,8 +688,10 @@
                 ViewGroup shortcutItemsContainer = (ViewGroup) shortcutView
                         .findViewById(R.id.keyboard_shortcuts_item_container);
                 final int shortcutKeysSize = shortcutKeys.size();
+                final List<String> humanReadableShortcuts = new ArrayList<>();
                 for (int k = 0; k < shortcutKeysSize; k++) {
                     StringDrawableContainer shortcutRepresentation = shortcutKeys.get(k);
+                    humanReadableShortcuts.add(shortcutRepresentation.mString);
                     if (shortcutRepresentation.mDrawable != null) {
                         ImageView shortcutKeyIconView = (ImageView) inflater.inflate(
                                 R.layout.keyboard_shortcuts_key_icon_view, shortcutItemsContainer,
@@ -688,6 +721,11 @@
                         shortcutItemsContainer.addView(shortcutKeyTextView);
                     }
                 }
+                CharSequence contentDescription = info.getLabel();
+                if (!humanReadableShortcuts.isEmpty()) {
+                    contentDescription += ": " + String.join(", ", humanReadableShortcuts);
+                }
+                shortcutView.setContentDescription(contentDescription);
                 shortcutContainer.addView(shortcutView);
             }
             keyboardShortcutsLayout.addView(shortcutContainer);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index b847fb6..73d8445 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -29,6 +29,7 @@
 import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_RECOGNIZED;
 import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED;
 import static com.android.systemui.DejankUtils.whitelistIpcs;
+import static com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED;
 import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.IMPORTANT_MSG_MIN_DURATION;
 import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_ALIGNMENT;
 import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_BATTERY;
@@ -41,8 +42,9 @@
 import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_TRUST;
 import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_USER_LOCKED;
 import static com.android.systemui.keyguard.ScreenLifecycle.SCREEN_ON;
-import static com.android.systemui.log.LogLevel.ERROR;
+import static com.android.systemui.log.core.LogLevel.ERROR;
 import static com.android.systemui.plugins.FalsingManager.LOW_PENALTY;
+import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
 
 import android.app.AlarmManager;
 import android.app.admin.DevicePolicyManager;
@@ -56,7 +58,6 @@
 import android.graphics.Color;
 import android.hardware.biometrics.BiometricSourceType;
 import android.hardware.face.FaceManager;
-import android.hardware.fingerprint.FingerprintManager;
 import android.os.BatteryManager;
 import android.os.Handler;
 import android.os.Looper;
@@ -86,6 +87,8 @@
 import com.android.systemui.R;
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.biometrics.FaceHelpMessageDeferral;
+import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
+import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Background;
@@ -95,8 +98,9 @@
 import com.android.systemui.keyguard.KeyguardIndication;
 import com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController;
 import com.android.systemui.keyguard.ScreenLifecycle;
-import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor;
-import com.android.systemui.log.LogLevel;
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
+import com.android.systemui.keyguard.util.IndicationHelper;
+import com.android.systemui.log.core.LogLevel;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.settings.UserTracker;
@@ -113,6 +117,7 @@
 import java.text.NumberFormat;
 import java.util.HashSet;
 import java.util.Set;
+import java.util.function.Consumer;
 
 import javax.inject.Inject;
 
@@ -148,6 +153,7 @@
     private final AuthController mAuthController;
     private final KeyguardLogger mKeyguardLogger;
     private final UserTracker mUserTracker;
+    private final BouncerMessageInteractor mBouncerMessageInteractor;
     private ViewGroup mIndicationArea;
     private KeyguardIndicationTextView mTopIndicationView;
     private KeyguardIndicationTextView mLockScreenIndicationView;
@@ -169,7 +175,7 @@
     public KeyguardIndicationRotateTextViewController mRotateTextViewController;
     private BroadcastReceiver mBroadcastReceiver;
     private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
-
+    private KeyguardInteractor mKeyguardInteractor;
     private String mPersistentUnlockMessage;
     private String mAlignmentIndication;
     private CharSequence mTrustGrantedIndication;
@@ -203,7 +209,17 @@
     private KeyguardUpdateMonitorCallback mUpdateMonitorCallback;
 
     private boolean mDozing;
+    private boolean mIsActiveDreamLockscreenHosted;
     private final ScreenLifecycle mScreenLifecycle;
+    @VisibleForTesting
+    final Consumer<Boolean> mIsActiveDreamLockscreenHostedCallback =
+            (Boolean isLockscreenHosted) -> {
+                if (mIsActiveDreamLockscreenHosted == isLockscreenHosted) {
+                    return;
+                }
+                mIsActiveDreamLockscreenHosted = isLockscreenHosted;
+                updateDeviceEntryIndication(false);
+            };
     private final ScreenLifecycle.Observer mScreenObserver =
             new ScreenLifecycle.Observer() {
         @Override
@@ -225,7 +241,8 @@
     // triggered while the device is asleep
     private final AlarmTimeout mHideTransientMessageHandler;
     private final AlarmTimeout mHideBiometricMessageHandler;
-    private FeatureFlags mFeatureFlags;
+    private final FeatureFlags mFeatureFlags;
+    private final IndicationHelper mIndicationHelper;
 
     /**
      * Creates a new KeyguardIndicationController and registers callbacks.
@@ -256,7 +273,10 @@
             AlternateBouncerInteractor alternateBouncerInteractor,
             AlarmManager alarmManager,
             UserTracker userTracker,
-            FeatureFlags flags
+            BouncerMessageInteractor bouncerMessageInteractor,
+            FeatureFlags flags,
+            IndicationHelper indicationHelper,
+            KeyguardInteractor keyguardInteractor
     ) {
         mContext = context;
         mBroadcastDispatcher = broadcastDispatcher;
@@ -281,7 +301,10 @@
         mScreenLifecycle.addObserver(mScreenObserver);
         mAlternateBouncerInteractor = alternateBouncerInteractor;
         mUserTracker = userTracker;
+        mBouncerMessageInteractor = bouncerMessageInteractor;
         mFeatureFlags = flags;
+        mIndicationHelper = indicationHelper;
+        mKeyguardInteractor = keyguardInteractor;
 
         mFaceAcquiredMessageDeferral = faceHelpMessageDeferral;
         mCoExFaceAcquisitionMsgIdsToShow = new HashSet<>();
@@ -364,6 +387,10 @@
             intentFilter.addAction(Intent.ACTION_USER_REMOVED);
             mBroadcastDispatcher.registerReceiver(mBroadcastReceiver, intentFilter);
         }
+        if (mFeatureFlags.isEnabled(LOCKSCREEN_WALLPAPER_DREAM_ENABLED)) {
+            collectFlow(mIndicationArea, mKeyguardInteractor.isActiveDreamLockscreenHosted(),
+                    mIsActiveDreamLockscreenHostedCallback);
+        }
     }
 
     /**
@@ -871,6 +898,12 @@
             return;
         }
 
+        // Device is dreaming and the dream is hosted in lockscreen
+        if (mIsActiveDreamLockscreenHosted) {
+            mIndicationArea.setVisibility(GONE);
+            return;
+        }
+
         // A few places might need to hide the indication, so always start by making it visible
         mIndicationArea.setVisibility(VISIBLE);
 
@@ -1062,6 +1095,7 @@
         pw.println("  mBiometricMessageFollowUp: " + mBiometricMessageFollowUp);
         pw.println("  mBatteryLevel: " + mBatteryLevel);
         pw.println("  mBatteryPresent: " + mBatteryPresent);
+        pw.println("  mIsActiveDreamLockscreenHosted: " + mIsActiveDreamLockscreenHosted);
         pw.println("  AOD text: " + (
                 mTopIndicationView == null ? null : mTopIndicationView.getText()));
         pw.println("  computePowerIndication(): " + computePowerIndication());
@@ -1156,6 +1190,11 @@
                         msgId,
                         helpString);
             } else if (mStatusBarKeyguardViewManager.isBouncerShowing()) {
+                if (biometricSourceType == FINGERPRINT && !fpAuthFailed) {
+                    mBouncerMessageInteractor.setFingerprintAcquisitionMessage(helpString);
+                } else if (faceAuthSoftError) {
+                    mBouncerMessageInteractor.setFaceAcquisitionMessage(helpString);
+                }
                 mStatusBarKeyguardViewManager.setKeyguardMessage(helpString,
                         mInitialTextColorState);
             } else if (mScreenLifecycle.getScreenState() == SCREEN_ON) {
@@ -1211,6 +1250,8 @@
             if (biometricSourceType == FACE) {
                 mFaceAcquiredMessageDeferral.reset();
             }
+            mBouncerMessageInteractor.setFaceAcquisitionMessage(null);
+            mBouncerMessageInteractor.setFingerprintAcquisitionMessage(null);
         }
 
         @Override
@@ -1231,18 +1272,20 @@
             } else if (biometricSourceType == FINGERPRINT) {
                 onFingerprintAuthError(msgId, errString);
             }
+            mBouncerMessageInteractor.setFaceAcquisitionMessage(null);
+            mBouncerMessageInteractor.setFingerprintAcquisitionMessage(null);
         }
 
         private void onFaceAuthError(int msgId, String errString) {
             CharSequence deferredFaceMessage = mFaceAcquiredMessageDeferral.getDeferredMessage();
             mFaceAcquiredMessageDeferral.reset();
-            if (shouldSuppressFaceError(msgId)) {
-                mKeyguardLogger.logBiometricMessage("suppressingFaceError", msgId, errString);
+            if (mIndicationHelper.shouldSuppressErrorMsg(FACE, msgId)) {
+                mKeyguardLogger.logBiometricMessage("KIC suppressingFaceError", msgId, errString);
                 return;
             }
             if (msgId == FaceManager.FACE_ERROR_TIMEOUT) {
                 handleFaceAuthTimeoutError(deferredFaceMessage);
-            } else if (isLockoutError(msgId)) {
+            } else if (mIndicationHelper.isFaceLockoutErrorMsg(msgId)) {
                 handleFaceLockoutError(errString);
             } else {
                 showErrorMessageNowOrLater(errString, null);
@@ -1250,8 +1293,8 @@
         }
 
         private void onFingerprintAuthError(int msgId, String errString) {
-            if (shouldSuppressFingerprintError(msgId)) {
-                mKeyguardLogger.logBiometricMessage("suppressingFingerprintError",
+            if (mIndicationHelper.shouldSuppressErrorMsg(FINGERPRINT, msgId)) {
+                mKeyguardLogger.logBiometricMessage("KIC suppressingFingerprintError",
                         msgId,
                         errString);
             } else {
@@ -1259,19 +1302,6 @@
             }
         }
 
-        private boolean shouldSuppressFingerprintError(int msgId) {
-            return ((isPrimaryAuthRequired() && !isLockoutError(msgId))
-                    || msgId == FingerprintManager.FINGERPRINT_ERROR_CANCELED
-                    || msgId == FingerprintManager.FINGERPRINT_ERROR_USER_CANCELED
-                    || msgId == FingerprintManager.BIOMETRIC_ERROR_POWER_PRESSED);
-        }
-
-        private boolean shouldSuppressFaceError(int msgId) {
-            return ((isPrimaryAuthRequired() && msgId != FaceManager.FACE_ERROR_LOCKOUT_PERMANENT)
-                    || msgId == FaceManager.FACE_ERROR_CANCELED
-                    || msgId == FaceManager.FACE_ERROR_UNABLE_TO_PROCESS);
-        }
-
         @Override
         public void onTrustChanged(int userId) {
             if (!isCurrentUser(userId)) return;
@@ -1315,6 +1345,8 @@
                     showActionToUnlock();
                 }
             }
+            mBouncerMessageInteractor.setFaceAcquisitionMessage(null);
+            mBouncerMessageInteractor.setFingerprintAcquisitionMessage(null);
         }
 
         @Override
@@ -1393,11 +1425,6 @@
         return mContext.getString(followupMsgId);
     }
 
-    private static boolean isLockoutError(int msgId) {
-        return msgId == FaceManager.FACE_ERROR_LOCKOUT_PERMANENT
-                || msgId == FaceManager.FACE_ERROR_LOCKOUT;
-    }
-
     private void handleFaceAuthTimeoutError(@Nullable CharSequence deferredFaceMessage) {
         mKeyguardLogger.logBiometricMessage("deferred message after face auth timeout",
                 null, String.valueOf(deferredFaceMessage));
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LegacyNotificationShelfControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/LegacyNotificationShelfControllerImpl.java
index dcd9dc3..4ec5f46 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LegacyNotificationShelfControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LegacyNotificationShelfControllerImpl.java
@@ -52,7 +52,7 @@
         mActivatableNotificationViewController = activatableNotificationViewController;
         mKeyguardBypassController = keyguardBypassController;
         mStatusBarStateController = statusBarStateController;
-        mView.setSensitiveRevealAnimEndabled(featureFlags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM));
+        mView.setSensitiveRevealAnimEnabled(featureFlags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM));
         mOnAttachStateChangeListener = new View.OnAttachStateChangeListener() {
             @Override
             public void onViewAttachedToWindow(View v) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
index c098f45..4710574 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
@@ -6,7 +6,6 @@
 import android.content.Context
 import android.content.res.Configuration
 import android.os.PowerManager
-import android.os.SystemClock
 import android.util.IndentingPrintWriter
 import android.util.MathUtils
 import android.view.MotionEvent
@@ -30,8 +29,10 @@
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.plugins.qs.QS
 import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.shade.ShadeViewController
 import com.android.systemui.shade.data.repository.ShadeRepository
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
 import com.android.systemui.statusbar.notification.row.ExpandableView
@@ -76,6 +77,8 @@
     dumpManager: DumpManager,
     qsTransitionControllerFactory: LockscreenShadeQsTransitionController.Factory,
     private val shadeRepository: ShadeRepository,
+    private val shadeInteractor: ShadeInteractor,
+    private val powerInteractor: PowerInteractor,
 ) : Dumpable {
     private var pulseHeight: Float = 0f
     @get:VisibleForTesting
@@ -278,11 +281,7 @@
         // Bind the click listener of the shelf to go to the full shade
         notificationShelfController.setOnClickListener {
             if (statusBarStateController.state == StatusBarState.KEYGUARD) {
-                centralSurfaces.wakeUpIfDozing(
-                        SystemClock.uptimeMillis(),
-                        "SHADE_CLICK",
-                        PowerManager.WAKE_REASON_GESTURE,
-                )
+                powerInteractor.wakeUpIfDozing("SHADE_CLICK", PowerManager.WAKE_REASON_GESTURE)
                 goToLockedShade(it)
             }
         }
@@ -561,7 +560,7 @@
         animationHandler: ((Long) -> Unit)? = null,
         cancelAction: Runnable? = null
     ) {
-        if (centralSurfaces.isShadeDisabled) {
+        if (!shadeInteractor.isShadeEnabled.value) {
             cancelAction?.run()
             logger.logShadeDisabledOnGoToLockedShade()
             return
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index e6e3e7e..ea5ca27 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -19,6 +19,7 @@
 
 import static com.android.systemui.DejankUtils.whitelistIpcs;
 
+import android.app.ActivityOptions;
 import android.app.KeyguardManager;
 import android.app.Notification;
 import android.app.admin.DevicePolicyManager;
@@ -158,7 +159,11 @@
                     final String notificationKey = intent.getStringExtra(Intent.EXTRA_INDEX);
                     if (intentSender != null) {
                         try {
-                            mContext.startIntentSender(intentSender, null, 0, 0, 0);
+                            ActivityOptions options = ActivityOptions.makeBasic();
+                            options.setPendingIntentBackgroundActivityStartMode(
+                                    ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
+                            mContext.startIntentSender(intentSender, null, 0, 0, 0,
+                                    options.toBundle());
                         } catch (IntentSender.SendIntentException e) {
                             /* ignore */
                         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java
index 1c9bc60..5dcf6d1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java
@@ -43,6 +43,11 @@
     void onUserSwitched(int newUserId);
 
     /**
+     * Called when a new row is created and bound to a notification.
+     */
+    void onBindRow(ExpandableNotificationRow row);
+
+    /**
      * @return true iff the device is in vr mode
      */
     boolean isDeviceInVrMode();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
index c519115..da84afe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
@@ -27,7 +27,6 @@
 import android.os.PowerManager;
 import android.os.RemoteException;
 import android.os.ServiceManager;
-import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.UserManager;
 import android.service.notification.StatusBarNotification;
@@ -52,6 +51,7 @@
 import com.android.systemui.R;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.power.domain.interactor.PowerInteractor;
 import com.android.systemui.statusbar.dagger.CentralSurfacesDependenciesModule;
 import com.android.systemui.statusbar.notification.NotifPipelineFlags;
 import com.android.systemui.statusbar.notification.RemoteInputControllerLogger;
@@ -60,19 +60,15 @@
 import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import com.android.systemui.statusbar.phone.CentralSurfaces;
 import com.android.systemui.statusbar.policy.RemoteInputUriController;
 import com.android.systemui.statusbar.policy.RemoteInputView;
 import com.android.systemui.util.DumpUtilsKt;
 import com.android.systemui.util.ListenerSet;
 
-import dagger.Lazy;
-
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
-import java.util.Optional;
 import java.util.function.Consumer;
 
 /**
@@ -95,10 +91,8 @@
     private final NotificationLockscreenUserManager mLockscreenUserManager;
     private final SmartReplyController mSmartReplyController;
     private final NotificationVisibilityProvider mVisibilityProvider;
+    private final PowerInteractor mPowerInteractor;
     private final ActionClickLogger mLogger;
-
-    private final Lazy<Optional<CentralSurfaces>> mCentralSurfacesOptionalLazy;
-
     protected final Context mContext;
     protected final NotifPipelineFlags mNotifPipelineFlags;
     private final UserManager mUserManager;
@@ -122,10 +116,8 @@
         @Override
         public boolean onInteraction(
                 View view, PendingIntent pendingIntent, RemoteViews.RemoteResponse response) {
-            mCentralSurfacesOptionalLazy.get().ifPresent(
-                    centralSurfaces -> centralSurfaces.wakeUpIfDozing(
-                            SystemClock.uptimeMillis(), "NOTIFICATION_CLICK",
-                            PowerManager.WAKE_REASON_GESTURE));
+            mPowerInteractor.wakeUpIfDozing(
+                    "NOTIFICATION_CLICK", PowerManager.WAKE_REASON_GESTURE);
 
             final NotificationEntry entry = getNotificationForParent(view.getParent());
             mLogger.logInitialClick(entry, pendingIntent);
@@ -259,7 +251,7 @@
             NotificationLockscreenUserManager lockscreenUserManager,
             SmartReplyController smartReplyController,
             NotificationVisibilityProvider visibilityProvider,
-            Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy,
+            PowerInteractor powerInteractor,
             StatusBarStateController statusBarStateController,
             RemoteInputUriController remoteInputUriController,
             RemoteInputControllerLogger remoteInputControllerLogger,
@@ -271,7 +263,7 @@
         mLockscreenUserManager = lockscreenUserManager;
         mSmartReplyController = smartReplyController;
         mVisibilityProvider = visibilityProvider;
-        mCentralSurfacesOptionalLazy = centralSurfacesOptionalLazy;
+        mPowerInteractor = powerInteractor;
         mLogger = logger;
         mBarService = IStatusBarService.Stub.asInterface(
                 ServiceManager.getService(Context.STATUS_BAR_SERVICE));
@@ -402,7 +394,7 @@
         while (p != null) {
             if (p instanceof View) {
                 View pv = (View) p;
-                if (pv.isRootNamespace()) {
+                if (pv.getId() == com.android.internal.R.id.status_bar_latest_event_content) {
                     riv = findRemoteInputView(pv);
                     row = (ExpandableNotificationRow) pv.getTag(R.id.row_tag_for_content_view);
                     break;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java
index 2ca0b00..5ac542b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java
@@ -60,11 +60,11 @@
     default void attach() {}
 
     /** Sets the notification shade view. */
-    default void setNotificationShadeView(ViewGroup view) {}
+    default void setWindowRootView(ViewGroup view) {}
 
     /** Gets the notification shade view. */
     @Nullable
-    default ViewGroup getNotificationShadeView() {
+    default ViewGroup getWindowRootView() {
         return null;
     }
 
@@ -112,9 +112,6 @@
     /** Sets the state of whether heads up is showing or not. */
     default void setHeadsUpShowing(boolean showing) {}
 
-    /** Sets whether the wallpaper supports ambient mode or not. */
-    default void setWallpaperSupportsAmbientMode(boolean supportsAmbientMode) {}
-
     /** Gets whether the wallpaper is showing or not. */
     default boolean isShowingWallpaper() {
         return false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index 9d7f3be..25a1dc6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -40,8 +40,6 @@
 import com.android.internal.policy.SystemBarUtils;
 import com.android.systemui.R;
 import com.android.systemui.animation.ShadeInterpolation;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
 import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
 import com.android.systemui.statusbar.notification.NotificationUtils;
@@ -97,7 +95,7 @@
     private float mCornerAnimationDistance;
     private NotificationShelfController mController;
     private float mActualWidth = -1;
-    private boolean mSensitiveRevealAnimEndabled;
+    private boolean mSensitiveRevealAnimEnabled;
     private boolean mShelfRefactorFlagEnabled;
     private boolean mCanModifyColorOfNotifications;
     private boolean mCanInteract;
@@ -226,9 +224,7 @@
                 if (ambientState.isBouncerInTransit()) {
                     viewState.setAlpha(aboutToShowBouncerProgress(expansion));
                 } else {
-                    FeatureFlags flags = ambientState.getFeatureFlags();
-                    if (ambientState.isSmallScreen() || !flags.isEnabled(
-                            Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION)) {
+                    if (ambientState.isSmallScreen()) {
                         viewState.setAlpha(ShadeInterpolation.getContentAlpha(expansion));
                     } else {
                         LargeScreenShadeInterpolator interpolator =
@@ -265,21 +261,21 @@
                     viewState.hidden = true;
                 }
             }
-
-            final float stackEnd = ambientState.getStackY() + ambientState.getStackHeight();
-            if (mSensitiveRevealAnimEndabled && viewState.hidden) {
-                // if the shelf is hidden, position it at the end of the stack (plus the clip
-                // padding), such that when it appears animated, it will smoothly move in from the
-                // bottom, without jump cutting any notifications
-                viewState.setYTranslation(stackEnd + mPaddingBetweenElements);
-            } else {
-                viewState.setYTranslation(stackEnd - viewState.height);
-            }
         } else {
             viewState.hidden = true;
             viewState.location = ExpandableViewState.LOCATION_GONE;
             viewState.hasItemsInStableShelf = false;
         }
+
+        final float stackEnd = ambientState.getStackY() + ambientState.getStackHeight();
+        if (mSensitiveRevealAnimEnabled && viewState.hidden) {
+            // if the shelf is hidden, position it at the end of the stack (plus the clip
+            // padding), such that when it appears animated, it will smoothly move in from the
+            // bottom, without jump cutting any notifications
+            viewState.setYTranslation(stackEnd + mPaddingBetweenElements);
+        } else {
+            viewState.setYTranslation(stackEnd - viewState.height);
+        }
     }
 
     private int getSpeedBumpIndex() {
@@ -417,7 +413,7 @@
                     expandingAnimated, isLastChild, shelfClipStart);
 
             // TODO(b/172289889) scale mPaddingBetweenElements with expansion amount
-            if ((!mSensitiveRevealAnimEndabled && ((isLastChild && !child.isInShelf())
+            if ((!mSensitiveRevealAnimEnabled && ((isLastChild && !child.isInShelf())
                     || backgroundForceHidden)) || aboveShelf) {
                 notificationClipEnd = shelfStart + getIntrinsicHeight();
             } else {
@@ -466,7 +462,7 @@
                 // if the shelf is visible, but if the shelf is hidden, it causes incorrect curling.
                 // notificationClipEnd handles the discrepancy between a visible and hidden shelf,
                 // so we use that when on the keyguard (and while animating away) to reduce curling.
-                final float keyguardSafeShelfStart = !mSensitiveRevealAnimEndabled
+                final float keyguardSafeShelfStart = !mSensitiveRevealAnimEnabled
                         && mAmbientState.isOnKeyguard() ? notificationClipEnd : shelfStart;
                 updateCornerRoundnessOnScroll(anv, viewStart, keyguardSafeShelfStart);
             }
@@ -1068,8 +1064,8 @@
      * Set whether the sensitive reveal animation feature flag is enabled
      * @param enabled true if enabled
      */
-    public void setSensitiveRevealAnimEndabled(boolean enabled) {
-        mSensitiveRevealAnimEndabled = enabled;
+    public void setSensitiveRevealAnimEnabled(boolean enabled) {
+        mSensitiveRevealAnimEnabled = enabled;
     }
 
     public void setRefactorFlagEnabled(boolean enabled) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/QsFrameTranslateController.java b/packages/SystemUI/src/com/android/systemui/statusbar/QsFrameTranslateController.java
index 59afb18..9702bfc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/QsFrameTranslateController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/QsFrameTranslateController.java
@@ -18,19 +18,19 @@
 
 import android.view.View;
 
+import com.android.systemui.display.data.repository.DisplayMetricsRepository;
 import com.android.systemui.plugins.qs.QS;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
-import com.android.systemui.statusbar.phone.CentralSurfaces;
 
 /**
  * Calculates and moves the QS frame vertically.
  */
 public abstract class QsFrameTranslateController {
 
-    protected CentralSurfaces mCentralSurfaces;
+    protected DisplayMetricsRepository mDisplayMetricsRepository;
 
-    public QsFrameTranslateController(CentralSurfaces centralSurfaces) {
-        mCentralSurfaces = centralSurfaces;
+    public QsFrameTranslateController(DisplayMetricsRepository displayMetricsRepository) {
+        mDisplayMetricsRepository = displayMetricsRepository;
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/QsFrameTranslateImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/QsFrameTranslateImpl.java
index 85b522c..e429b8b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/QsFrameTranslateImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/QsFrameTranslateImpl.java
@@ -19,9 +19,9 @@
 import android.view.View;
 
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.display.data.repository.DisplayMetricsRepository;
 import com.android.systemui.plugins.qs.QS;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
-import com.android.systemui.statusbar.phone.CentralSurfaces;
 
 import javax.inject.Inject;
 
@@ -34,8 +34,8 @@
 public class QsFrameTranslateImpl extends QsFrameTranslateController {
 
     @Inject
-    public QsFrameTranslateImpl(CentralSurfaces centralSurfaces) {
-        super(centralSurfaces);
+    public QsFrameTranslateImpl(DisplayMetricsRepository displayMetricsRepository) {
+        super(displayMetricsRepository);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
index 91c08a0..fbbee53 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
@@ -26,6 +26,7 @@
 import android.app.ActivityManager;
 import android.app.Notification;
 import android.content.Context;
+import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.res.ColorStateList;
 import android.content.res.Configuration;
@@ -41,18 +42,19 @@
 import android.os.UserHandle;
 import android.service.notification.StatusBarNotification;
 import android.text.TextUtils;
-import android.util.AttributeSet;
 import android.util.FloatProperty;
 import android.util.Log;
 import android.util.Property;
 import android.util.TypedValue;
 import android.view.ViewDebug;
+import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.animation.Interpolator;
 
 import androidx.core.graphics.ColorUtils;
 
 import com.android.app.animation.Interpolators;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.statusbar.StatusBarIcon;
 import com.android.internal.util.ContrastColorUtil;
 import com.android.systemui.R;
@@ -131,10 +133,12 @@
         }
     };
 
-    private boolean mAlwaysScaleIcon;
     private int mStatusBarIconDrawingSizeIncreased = 1;
-    private int mStatusBarIconDrawingSize = 1;
-    private int mStatusBarIconSize = 1;
+    @VisibleForTesting int mStatusBarIconDrawingSize = 1;
+
+    @VisibleForTesting int mOriginalStatusBarIconSize = 1;
+    @VisibleForTesting int mNewStatusBarIconSize = 1;
+    @VisibleForTesting float mScaleToFitNewIconSize = 1;
     private StatusBarIcon mIcon;
     @ViewDebug.ExportedProperty private String mSlot;
     private Drawable mNumberBackground;
@@ -144,7 +148,7 @@
     private String mNumberText;
     private StatusBarNotification mNotification;
     private final boolean mBlocked;
-    private int mDensity;
+    private Configuration mConfiguration;
     private boolean mNightMode;
     private float mIconScale = 1.0f;
     private final Paint mDotPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
@@ -156,7 +160,6 @@
     private ObjectAnimator mIconAppearAnimator;
     private ObjectAnimator mDotAnimator;
     private float mDotAppearAmount;
-    private OnVisibilityChangedListener mOnVisibilityChangedListener;
     private int mDrawableColor;
     private int mIconColor;
     private int mDecorColor;
@@ -175,7 +178,6 @@
     private int mCachedContrastBackgroundColor = NO_COLOR;
     private float[] mMatrix;
     private ColorMatrixColorFilter mMatrixColorFilter;
-    private boolean mIsInShelf;
     private Runnable mLayoutRunnable;
     private boolean mDismissed;
     private Runnable mOnDismissListener;
@@ -198,30 +200,20 @@
         mNumberPain.setAntiAlias(true);
         setNotification(sbn);
         setScaleType(ScaleType.CENTER);
-        mDensity = context.getResources().getDisplayMetrics().densityDpi;
-        Configuration configuration = context.getResources().getConfiguration();
-        mNightMode = (configuration.uiMode & Configuration.UI_MODE_NIGHT_MASK)
+        mConfiguration = new Configuration(context.getResources().getConfiguration());
+        mNightMode = (mConfiguration.uiMode & Configuration.UI_MODE_NIGHT_MASK)
                 == Configuration.UI_MODE_NIGHT_YES;
         initializeDecorColor();
         reloadDimens();
         maybeUpdateIconScaleDimens();
     }
 
-    public StatusBarIconView(Context context, AttributeSet attrs) {
-        super(context, attrs);
-        mDozer = new NotificationIconDozeHelper(context);
-        mBlocked = false;
-        mAlwaysScaleIcon = true;
-        reloadDimens();
-        maybeUpdateIconScaleDimens();
-        mDensity = context.getResources().getDisplayMetrics().densityDpi;
-    }
-
     /** Should always be preceded by {@link #reloadDimens()} */
-    private void maybeUpdateIconScaleDimens() {
+    @VisibleForTesting
+    public void maybeUpdateIconScaleDimens() {
         // We do not resize and scale system icons (on the right), only notification icons (on the
         // left).
-        if (mNotification != null || mAlwaysScaleIcon) {
+        if (isNotification()) {
             updateIconScaleForNotifications();
         } else {
             updateIconScaleForSystemIcons();
@@ -229,22 +221,63 @@
     }
 
     private void updateIconScaleForNotifications() {
+        float iconScale;
+        // we need to scale the image size to be same as the original size
+        // (fit mOriginalStatusBarIconSize), then we can scale it with mScaleToFitNewIconSize
+        // to fit mNewStatusBarIconSize
+        float scaleToOriginalDrawingSize = 1.0f;
+        ViewGroup.LayoutParams lp = getLayoutParams();
+        if (getDrawable() != null && (lp != null && lp.width > 0 && lp.height > 0)) {
+            final int iconViewWidth = lp.width;
+            final int iconViewHeight = lp.height;
+            // first we estimate the image exact size when put the drawable in scaled iconView size,
+            // then we can compute the scaleToOriginalDrawingSize to make the image size fit in
+            // mOriginalStatusBarIconSize
+            final int drawableWidth = getDrawable().getIntrinsicWidth();
+            final int drawableHeight = getDrawable().getIntrinsicHeight();
+            float scaleToFitIconView = Math.min(
+                    (float) iconViewWidth / drawableWidth,
+                    (float) iconViewHeight / drawableHeight);
+            // if the drawable size <= the icon view size, the drawable won't be scaled
+            if (scaleToFitIconView > 1.0f) {
+                scaleToFitIconView = 1.0f;
+            }
+            final float scaledImageWidth = drawableWidth * scaleToFitIconView;
+            final float scaledImageHeight = drawableHeight * scaleToFitIconView;
+            // if the scaled image size <= mOriginalStatusBarIconSize, we don't need to enlarge it
+            scaleToOriginalDrawingSize = Math.min(
+                    (float) mOriginalStatusBarIconSize / scaledImageWidth,
+                    (float) mOriginalStatusBarIconSize / scaledImageHeight);
+            if (scaleToOriginalDrawingSize > 1.0f) {
+                scaleToOriginalDrawingSize = 1.0f;
+            }
+        }
+        iconScale = scaleToOriginalDrawingSize;
+
         final float imageBounds = mIncreasedSize ?
                 mStatusBarIconDrawingSizeIncreased : mStatusBarIconDrawingSize;
-        final int outerBounds = mStatusBarIconSize;
-        mIconScale = imageBounds / (float)outerBounds;
+        final int originalOuterBounds = mOriginalStatusBarIconSize;
+        iconScale = iconScale * (imageBounds / (float) originalOuterBounds);
+
+        // scale image to fit new icon size
+        mIconScale = iconScale * mScaleToFitNewIconSize;
+
         updatePivot();
     }
 
     // Makes sure that all icons are scaled to the same height (15dp). If we cannot get a height
     // for the icon, it uses the default SCALE (15f / 17f) which is the old behavior
     private void updateIconScaleForSystemIcons() {
+        float iconScale;
         float iconHeight = getIconHeight();
         if (iconHeight != 0) {
-            mIconScale = mSystemIconDesiredHeight / iconHeight;
+            iconScale = mSystemIconDesiredHeight / iconHeight;
         } else {
-            mIconScale = mSystemIconDefaultScale;
+            iconScale = mSystemIconDefaultScale;
         }
+
+        // scale image to fit new icon size
+        mIconScale = iconScale * mScaleToFitNewIconSize;
     }
 
     private float getIconHeight() {
@@ -267,12 +300,10 @@
     @Override
     protected void onConfigurationChanged(Configuration newConfig) {
         super.onConfigurationChanged(newConfig);
-        int density = newConfig.densityDpi;
-        if (density != mDensity) {
-            mDensity = density;
-            reloadDimens();
-            updateDrawable();
-            maybeUpdateIconScaleDimens();
+        final int configDiff = newConfig.diff(mConfiguration);
+        mConfiguration.setTo(newConfig);
+        if ((configDiff & (ActivityInfo.CONFIG_DENSITY | ActivityInfo.CONFIG_FONT_SCALE)) != 0) {
+            updateIconDimens();
         }
         boolean nightMode = (newConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK)
                 == Configuration.UI_MODE_NIGHT_YES;
@@ -282,11 +313,22 @@
         }
     }
 
+    /**
+     * Update the icon dimens and drawable with current resources
+     */
+    public void updateIconDimens() {
+        reloadDimens();
+        updateDrawable();
+        maybeUpdateIconScaleDimens();
+    }
+
     private void reloadDimens() {
         boolean applyRadius = mDotRadius == mStaticDotRadius;
         Resources res = getResources();
         mStaticDotRadius = res.getDimensionPixelSize(R.dimen.overflow_dot_radius);
-        mStatusBarIconSize = res.getDimensionPixelSize(R.dimen.status_bar_icon_size);
+        mOriginalStatusBarIconSize = res.getDimensionPixelSize(R.dimen.status_bar_icon_size);
+        mNewStatusBarIconSize = res.getDimensionPixelSize(R.dimen.status_bar_icon_size_sp);
+        mScaleToFitNewIconSize = (float) mNewStatusBarIconSize / mOriginalStatusBarIconSize;
         mStatusBarIconDrawingSizeIncreased =
                 res.getDimensionPixelSize(R.dimen.status_bar_icon_drawing_size_dark);
         mStatusBarIconDrawingSize =
@@ -309,17 +351,8 @@
         maybeUpdateIconScaleDimens();
     }
 
-    private static boolean streq(String a, String b) {
-        if (a == b) {
-            return true;
-        }
-        if (a == null && b != null) {
-            return false;
-        }
-        if (a != null && b == null) {
-            return false;
-        }
-        return a.equals(b);
+    private boolean isNotification() {
+        return mNotification != null;
     }
 
     public boolean equalIcons(Icon a, Icon b) {
@@ -416,7 +449,7 @@
 
     Drawable getIcon(StatusBarIcon icon) {
         Context notifContext = getContext();
-        if (mNotification != null) {
+        if (isNotification()) {
             notifContext = mNotification.getPackageContext(getContext());
         }
         return getIcon(getContext(), notifContext != null ? notifContext : getContext(), icon);
@@ -471,7 +504,7 @@
     @Override
     public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
         super.onInitializeAccessibilityEvent(event);
-        if (mNotification != null) {
+        if (isNotification()) {
             event.setParcelableData(mNotification.getNotification());
         }
     }
@@ -491,11 +524,29 @@
     }
 
     @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+        if (!isNotification()) {
+            // for system icons, calculated measured width from super is for image drawable real
+            // width (17dp). We may scale the image with font scale, so we also need to scale the
+            // measured width so that scaled measured width and image width would be fit.
+            int measuredWidth = getMeasuredWidth();
+            int measuredHeight = getMeasuredHeight();
+            setMeasuredDimension((int) (measuredWidth * mScaleToFitNewIconSize), measuredHeight);
+        }
+    }
+
+    @Override
     protected void onDraw(Canvas canvas) {
+        // In this method, for width/height division computation we intend to discard the
+        // fractional part as the original behavior.
         if (mIconAppearAmount > 0.0f) {
             canvas.save();
+            int px = getWidth() / 2;
+            int py = getHeight() / 2;
             canvas.scale(mIconScale * mIconAppearAmount, mIconScale * mIconAppearAmount,
-                    getWidth() / 2, getHeight() / 2);
+                    (float) px, (float) py);
             super.onDraw(canvas);
             canvas.restore();
         }
@@ -512,10 +563,15 @@
             } else {
                 float fadeOutAmount = mDotAppearAmount - 1.0f;
                 alpha = alpha * (1.0f - fadeOutAmount);
-                radius = NotificationUtils.interpolate(mDotRadius, getWidth() / 4, fadeOutAmount);
+                int end = getWidth() / 4;
+                radius = NotificationUtils.interpolate(mDotRadius, (float) end, fadeOutAmount);
             }
             mDotPaint.setAlpha((int) (alpha * 255));
-            canvas.drawCircle(mStatusBarIconSize / 2, getHeight() / 2, radius, mDotPaint);
+            int cx = mNewStatusBarIconSize / 2;
+            int cy = getHeight() / 2;
+            canvas.drawCircle(
+                    (float) cx, (float) cy,
+                    radius, mDotPaint);
         }
     }
 
@@ -624,7 +680,7 @@
     }
 
     private void initializeDecorColor() {
-        if (mNotification != null) {
+        if (isNotification()) {
             setDecorColor(getContext().getColor(mNightMode
                     ? com.android.internal.R.color.notification_default_color_dark
                     : com.android.internal.R.color.notification_default_color_light));
@@ -837,7 +893,7 @@
                 if (targetAmount != currentAmount) {
                     mDotAnimator = ObjectAnimator.ofFloat(this, DOT_APPEAR_AMOUNT,
                             currentAmount, targetAmount);
-                    mDotAnimator.setInterpolator(interpolator);;
+                    mDotAnimator.setInterpolator(interpolator);
                     mDotAnimator.setDuration(duration == 0 ? ANIMATION_DURATION_FAST
                             : duration);
                     final boolean runRunnable = !runnableAdded;
@@ -894,22 +950,10 @@
         }
     }
 
-    @Override
-    public void setVisibility(int visibility) {
-        super.setVisibility(visibility);
-        if (mOnVisibilityChangedListener != null) {
-            mOnVisibilityChangedListener.onVisibilityChanged(visibility);
-        }
-    }
-
     public float getDotAppearAmount() {
         return mDotAppearAmount;
     }
 
-    public void setOnVisibilityChangedListener(OnVisibilityChangedListener listener) {
-        mOnVisibilityChangedListener = listener;
-    }
-
     public void setDozing(boolean dozing, boolean fade, long delay) {
         mDozer.setDozing(f -> {
             mDozeAmount = f;
@@ -943,14 +987,6 @@
         outRect.bottom += translationY;
     }
 
-    public void setIsInShelf(boolean isInShelf) {
-        mIsInShelf = isInShelf;
-    }
-
-    public boolean isInShelf() {
-        return mIsInShelf;
-    }
-
     @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
         super.onLayout(changed, left, top, right, bottom);
@@ -1032,8 +1068,4 @@
     public boolean showsConversation() {
         return mShowsConversation;
     }
-
-    public interface OnVisibilityChangedListener {
-        void onVisibilityChanged(int newVisibility);
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java
index fdad101..d6f6c2c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java
@@ -135,7 +135,7 @@
         mDotView = new StatusBarIconView(mContext, mSlot, null);
         mDotView.setVisibleState(STATE_DOT);
 
-        int width = mContext.getResources().getDimensionPixelSize(R.dimen.status_bar_icon_size);
+        int width = mContext.getResources().getDimensionPixelSize(R.dimen.status_bar_icon_size_sp);
         LayoutParams lp = new LayoutParams(width, width);
         lp.gravity = Gravity.CENTER_VERTICAL | Gravity.START;
         addView(mDotView, lp);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWifiView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWifiView.java
deleted file mode 100644
index decc70d..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWifiView.java
+++ /dev/null
@@ -1,249 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar;
-
-import static com.android.systemui.plugins.DarkIconDispatcher.getTint;
-import static com.android.systemui.statusbar.StatusBarIconView.STATE_DOT;
-import static com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN;
-import static com.android.systemui.statusbar.StatusBarIconView.STATE_ICON;
-
-import android.content.Context;
-import android.content.res.ColorStateList;
-import android.graphics.Rect;
-import android.util.AttributeSet;
-import android.view.Gravity;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-
-import com.android.systemui.R;
-import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
-import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
-
-import java.util.ArrayList;
-
-/**
- * Start small: StatusBarWifiView will be able to layout from a WifiIconState
- */
-public class StatusBarWifiView extends BaseStatusBarFrameLayout implements DarkReceiver {
-    private static final String TAG = "StatusBarWifiView";
-
-    /// Used to show etc dots
-    private StatusBarIconView mDotView;
-    /// Contains the main icon layout
-    private LinearLayout mWifiGroup;
-    private ImageView mWifiIcon;
-    private ImageView mIn;
-    private ImageView mOut;
-    private View mInoutContainer;
-    private View mSignalSpacer;
-    private View mAirplaneSpacer;
-    private WifiIconState mState;
-    private String mSlot;
-    @StatusBarIconView.VisibleState
-    private int mVisibleState = STATE_HIDDEN;
-
-    public static StatusBarWifiView fromContext(Context context, String slot) {
-        LayoutInflater inflater = LayoutInflater.from(context);
-        StatusBarWifiView v = (StatusBarWifiView) inflater.inflate(R.layout.status_bar_wifi_group, null);
-        v.setSlot(slot);
-        v.init();
-        v.setVisibleState(STATE_ICON);
-        return v;
-    }
-
-    public StatusBarWifiView(Context context) {
-        super(context);
-    }
-
-    public StatusBarWifiView(Context context, AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    public StatusBarWifiView(Context context, AttributeSet attrs, int defStyleAttr) {
-        super(context, attrs, defStyleAttr);
-    }
-
-    public void setSlot(String slot) {
-        mSlot = slot;
-    }
-
-    @Override
-    public void setStaticDrawableColor(int color) {
-        ColorStateList list = ColorStateList.valueOf(color);
-        mWifiIcon.setImageTintList(list);
-        mIn.setImageTintList(list);
-        mOut.setImageTintList(list);
-        mDotView.setDecorColor(color);
-    }
-
-    @Override
-    public void setDecorColor(int color) {
-        mDotView.setDecorColor(color);
-    }
-
-    @Override
-    public String getSlot() {
-        return mSlot;
-    }
-
-    @Override
-    public boolean isIconVisible() {
-        return mState != null && mState.visible;
-    }
-
-    @Override
-    public void setVisibleState(@StatusBarIconView.VisibleState int state, boolean animate) {
-        if (state == mVisibleState) {
-            return;
-        }
-        mVisibleState = state;
-
-        switch (state) {
-            case STATE_ICON:
-                mWifiGroup.setVisibility(View.VISIBLE);
-                mDotView.setVisibility(View.GONE);
-                break;
-            case STATE_DOT:
-                mWifiGroup.setVisibility(View.GONE);
-                mDotView.setVisibility(View.VISIBLE);
-                break;
-            case STATE_HIDDEN:
-            default:
-                mWifiGroup.setVisibility(View.GONE);
-                mDotView.setVisibility(View.GONE);
-                break;
-        }
-    }
-
-    @Override
-    @StatusBarIconView.VisibleState
-    public int getVisibleState() {
-        return mVisibleState;
-    }
-
-    @Override
-    public void getDrawingRect(Rect outRect) {
-        super.getDrawingRect(outRect);
-        float translationX = getTranslationX();
-        float translationY = getTranslationY();
-        outRect.left += translationX;
-        outRect.right += translationX;
-        outRect.top += translationY;
-        outRect.bottom += translationY;
-    }
-
-    private void init() {
-        mWifiGroup = findViewById(R.id.wifi_group);
-        mWifiIcon = findViewById(R.id.wifi_signal);
-        mIn = findViewById(R.id.wifi_in);
-        mOut = findViewById(R.id.wifi_out);
-        mSignalSpacer = findViewById(R.id.wifi_signal_spacer);
-        mAirplaneSpacer = findViewById(R.id.wifi_airplane_spacer);
-        mInoutContainer = findViewById(R.id.inout_container);
-
-        initDotView();
-    }
-
-    private void initDotView() {
-        mDotView = new StatusBarIconView(mContext, mSlot, null);
-        mDotView.setVisibleState(STATE_DOT);
-
-        int width = mContext.getResources().getDimensionPixelSize(R.dimen.status_bar_icon_size);
-        LayoutParams lp = new LayoutParams(width, width);
-        lp.gravity = Gravity.CENTER_VERTICAL | Gravity.START;
-        addView(mDotView, lp);
-    }
-
-    public void applyWifiState(WifiIconState state) {
-        boolean requestLayout = false;
-
-        if (state == null) {
-            requestLayout = getVisibility() != View.GONE;
-            setVisibility(View.GONE);
-            mState = null;
-        } else if (mState == null) {
-            requestLayout = true;
-            mState = state.copy();
-            initViewState();
-        } else if (!mState.equals(state)) {
-            requestLayout = updateState(state.copy());
-        }
-
-        if (requestLayout) {
-            requestLayout();
-        }
-    }
-
-    private boolean updateState(WifiIconState state) {
-        setContentDescription(state.contentDescription);
-        if (mState.resId != state.resId && state.resId >= 0) {
-            mWifiIcon.setImageDrawable(mContext.getDrawable(state.resId));
-        }
-
-        mIn.setVisibility(state.activityIn ? View.VISIBLE : View.GONE);
-        mOut.setVisibility(state.activityOut ? View.VISIBLE : View.GONE);
-        mInoutContainer.setVisibility(
-                (state.activityIn || state.activityOut) ? View.VISIBLE : View.GONE);
-        mAirplaneSpacer.setVisibility(state.airplaneSpacerVisible ? View.VISIBLE : View.GONE);
-        mSignalSpacer.setVisibility(state.signalSpacerVisible ? View.VISIBLE : View.GONE);
-
-        boolean needsLayout = state.activityIn != mState.activityIn
-                ||state.activityOut != mState.activityOut;
-
-        if (mState.visible != state.visible) {
-            needsLayout |= true;
-            setVisibility(state.visible ? View.VISIBLE : View.GONE);
-        }
-
-        mState = state;
-        return needsLayout;
-    }
-
-    private void initViewState() {
-        setContentDescription(mState.contentDescription);
-        if (mState.resId >= 0) {
-            mWifiIcon.setImageDrawable(mContext.getDrawable(mState.resId));
-        }
-
-        mIn.setVisibility(mState.activityIn ? View.VISIBLE : View.GONE);
-        mOut.setVisibility(mState.activityOut ? View.VISIBLE : View.GONE);
-        mInoutContainer.setVisibility(
-                (mState.activityIn || mState.activityOut) ? View.VISIBLE : View.GONE);
-        mAirplaneSpacer.setVisibility(mState.airplaneSpacerVisible ? View.VISIBLE : View.GONE);
-        mSignalSpacer.setVisibility(mState.signalSpacerVisible ? View.VISIBLE : View.GONE);
-        setVisibility(mState.visible ? View.VISIBLE : View.GONE);
-    }
-
-    @Override
-    public void onDarkChanged(ArrayList<Rect> areas, float darkIntensity, int tint) {
-        int areaTint = getTint(areas, this, tint);
-        ColorStateList color = ColorStateList.valueOf(areaTint);
-        mWifiIcon.setImageTintList(color);
-        mIn.setImageTintList(color);
-        mOut.setImageTintList(color);
-        mDotView.setDecorColor(areaTint);
-        mDotView.setIconColor(areaTint, false);
-    }
-
-
-    @Override
-    public String toString() {
-        return "StatusBarWifiView(slot=" + mSlot + " state=" + mState + ")";
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/commandline/CommandParser.kt b/packages/SystemUI/src/com/android/systemui/statusbar/commandline/CommandParser.kt
new file mode 100644
index 0000000..de369c3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/commandline/CommandParser.kt
@@ -0,0 +1,327 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.commandline
+
+/**
+ * [CommandParser] defines the collection of tokens which can be parsed from an incoming command
+ * list, and parses them into their respective containers. Supported tokens are of the following
+ * forms:
+ * ```
+ * Flag: boolean value, false by default. always optional.
+ * Param: named parameter, taking N args all of a given type. Currently only single arg parameters
+ *        are supported.
+ * SubCommand: named command created by adding a command to a parent. Supports all fields above, but
+ *             not other subcommands.
+ * ```
+ *
+ * Tokens are added via the factory methods for each token type. They can be made `required` by
+ * calling the [require] method for the appropriate type, as follows:
+ * ```
+ * val requiredParam = parser.require(parser.param(...))
+ * ```
+ *
+ * The reason for having an explicit require is so that generic type arguments can be handled
+ * properly. See [SingleArgParam] and [SingleArgParamOptional] for the difference between an
+ * optional parameter and a required one.
+ *
+ * Typical usage of a required parameter, however, will occur within the context of a
+ * [ParseableCommand], which defines a convenience `require()` method:
+ * ```
+ * class MyCommand : ParseableCommand {
+ *   val requiredParam = param(...).require()
+ * }
+ * ```
+ *
+ * This parser defines two modes of parsing, both of which validate for required parameters.
+ * 1. [parse] is a top-level parsing method. This parser will walk the given arg list and populate
+ *    all of the delegate classes based on their type. It will handle SubCommands, and after parsing
+ *    will check for any required-but-missing SubCommands or Params.
+ *
+ *    **This method requires that every received token is represented in its grammar.**
+ * 2. [parseAsSubCommand] is a second-level parsing method suitable for any [SubCommand]. This
+ *    method will handle _only_ flags and params. It will return parsing control to its parent
+ *    parser on the first unknown token rather than throwing.
+ */
+class CommandParser {
+    private val _flags = mutableListOf<Flag>()
+    val flags: List<Flag> = _flags
+    private val _params = mutableListOf<Param>()
+    val params: List<Param> = _params
+    private val _subCommands = mutableListOf<SubCommand>()
+    val subCommands: List<SubCommand> = _subCommands
+
+    private val tokenSet = mutableSetOf<String>()
+
+    /**
+     * Parse the arg list into the fields defined in the containing class.
+     *
+     * @return true if all required fields are present after parsing
+     * @throws ArgParseError on any failure to process args
+     */
+    fun parse(args: List<String>): Boolean {
+        if (args.isEmpty()) {
+            return false
+        }
+
+        val iterator = args.listIterator()
+        var tokenHandled: Boolean
+        while (iterator.hasNext()) {
+            val token = iterator.next()
+            tokenHandled = false
+
+            flags
+                .find { it.matches(token) }
+                ?.let {
+                    it.inner = true
+                    tokenHandled = true
+                }
+
+            if (tokenHandled) continue
+
+            params
+                .find { it.matches(token) }
+                ?.let {
+                    it.parseArgsFromIter(iterator)
+                    tokenHandled = true
+                }
+
+            if (tokenHandled) continue
+
+            subCommands
+                .find { it.matches(token) }
+                ?.let {
+                    it.parseSubCommandArgs(iterator)
+                    tokenHandled = true
+                }
+
+            if (!tokenHandled) {
+                throw ArgParseError("Unknown token: $token")
+            }
+        }
+
+        return validateRequiredParams()
+    }
+
+    /**
+     * Parse a subset of the commands that came in from the top-level [parse] method, for the
+     * subcommand that this parser represents. Note that subcommands may not contain other
+     * subcommands. But they may contain flags and params.
+     *
+     * @return true if all required fields are present after parsing
+     * @throws ArgParseError on any failure to process args
+     */
+    fun parseAsSubCommand(iter: ListIterator<String>): Boolean {
+        // arg[-1] is our subcommand name, so the rest of the args are either for this
+        // subcommand, OR for the top-level command to handle. Therefore, we bail on the first
+        // failure, but still check our own required params
+
+        // The mere presence of a subcommand (similar to a flag) is a valid subcommand
+        if (flags.isEmpty() && params.isEmpty()) {
+            return validateRequiredParams()
+        }
+
+        var tokenHandled: Boolean
+        while (iter.hasNext()) {
+            val token = iter.next()
+            tokenHandled = false
+
+            flags
+                .find { it.matches(token) }
+                ?.let {
+                    it.inner = true
+                    tokenHandled = true
+                }
+
+            if (tokenHandled) continue
+
+            params
+                .find { it.matches(token) }
+                ?.let {
+                    it.parseArgsFromIter(iter)
+                    tokenHandled = true
+                }
+
+            if (!tokenHandled) {
+                // Move the cursor position backwards since we've arrived at a token
+                // that we don't own
+                iter.previous()
+                break
+            }
+        }
+
+        return validateRequiredParams()
+    }
+
+    /**
+     * If [parse] or [parseAsSubCommand] does not produce a valid result, generate a list of errors
+     * based on missing elements
+     */
+    fun generateValidationErrorMessages(): List<String> {
+        val missingElements = mutableListOf<String>()
+
+        if (unhandledParams.isNotEmpty()) {
+            val names = unhandledParams.map { it.longName }
+            missingElements.add("No values passed for required params: $names")
+        }
+
+        if (unhandledSubCmds.isNotEmpty()) {
+            missingElements.addAll(unhandledSubCmds.map { it.longName })
+            val names = unhandledSubCmds.map { it.shortName }
+            missingElements.add("No values passed for required sub-commands: $names")
+        }
+
+        return missingElements
+    }
+
+    /** Check for any missing, required params, or any invalid subcommands */
+    private fun validateRequiredParams(): Boolean =
+        unhandledParams.isEmpty() && unhandledSubCmds.isEmpty() && unvalidatedSubCmds.isEmpty()
+
+    // If any required param (aka non-optional) hasn't handled a field, then return false
+    private val unhandledParams: List<Param>
+        get() = params.filter { (it is SingleArgParam<*>) && !it.handled }
+
+    private val unhandledSubCmds: List<SubCommand>
+        get() = subCommands.filter { (it is RequiredSubCommand<*> && !it.handled) }
+
+    private val unvalidatedSubCmds: List<SubCommand>
+        get() = subCommands.filter { !it.validationStatus }
+
+    private fun checkCliNames(short: String?, long: String): String? {
+        if (short != null && tokenSet.contains(short)) {
+            return short
+        }
+
+        if (tokenSet.contains(long)) {
+            return long
+        }
+
+        return null
+    }
+
+    private fun subCommandContainsSubCommands(cmd: ParseableCommand): Boolean =
+        cmd.parser.subCommands.isNotEmpty()
+
+    private fun registerNames(short: String?, long: String) {
+        if (short != null) {
+            tokenSet.add(short)
+        }
+        tokenSet.add(long)
+    }
+
+    /**
+     * Turns a [SingleArgParamOptional]<T> into a [SingleArgParam] by converting the [T?] into [T]
+     *
+     * @return a [SingleArgParam] property delegate
+     */
+    fun <T : Any> require(old: SingleArgParamOptional<T>): SingleArgParam<T> {
+        val newParam =
+            SingleArgParam(
+                longName = old.longName,
+                shortName = old.shortName,
+                description = old.description,
+                valueParser = old.valueParser,
+            )
+
+        replaceWithRequired(old, newParam)
+        return newParam
+    }
+
+    private fun <T : Any> replaceWithRequired(
+        old: SingleArgParamOptional<T>,
+        new: SingleArgParam<T>,
+    ) {
+        _params.remove(old)
+        _params.add(new)
+    }
+
+    /**
+     * Turns an [OptionalSubCommand] into a [RequiredSubCommand] by converting the [T?] in to [T]
+     *
+     * @return a [RequiredSubCommand] property delegate
+     */
+    fun <T : ParseableCommand> require(optional: OptionalSubCommand<T>): RequiredSubCommand<T> {
+        val newCmd = RequiredSubCommand(optional.cmd)
+        replaceWithRequired(optional, newCmd)
+        return newCmd
+    }
+
+    private fun <T : ParseableCommand> replaceWithRequired(
+        old: OptionalSubCommand<T>,
+        new: RequiredSubCommand<T>,
+    ) {
+        _subCommands.remove(old)
+        _subCommands.add(new)
+    }
+
+    internal fun flag(
+        longName: String,
+        shortName: String? = null,
+        description: String = "",
+    ): Flag {
+        checkCliNames(shortName, longName)?.let {
+            throw IllegalArgumentException("Detected reused flag name ($it)")
+        }
+        registerNames(shortName, longName)
+
+        val flag = Flag(shortName, longName, description)
+        _flags.add(flag)
+        return flag
+    }
+
+    internal fun <T : Any> param(
+        longName: String,
+        shortName: String? = null,
+        description: String = "",
+        valueParser: ValueParser<T>,
+    ): SingleArgParamOptional<T> {
+        checkCliNames(shortName, longName)?.let {
+            throw IllegalArgumentException("Detected reused param name ($it)")
+        }
+        registerNames(shortName, longName)
+
+        val param =
+            SingleArgParamOptional(
+                shortName = shortName,
+                longName = longName,
+                description = description,
+                valueParser = valueParser,
+            )
+        _params.add(param)
+        return param
+    }
+
+    internal fun <T : ParseableCommand> subCommand(
+        command: T,
+    ): OptionalSubCommand<T> {
+        checkCliNames(null, command.name)?.let {
+            throw IllegalArgumentException("Cannot re-use name for subcommand ($it)")
+        }
+
+        if (subCommandContainsSubCommands(command)) {
+            throw IllegalArgumentException(
+                "SubCommands may not contain other SubCommands. $command"
+            )
+        }
+
+        registerNames(null, command.name)
+
+        val subCmd = OptionalSubCommand(command)
+        _subCommands.add(subCmd)
+        return subCmd
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/commandline/Parameters.kt b/packages/SystemUI/src/com/android/systemui/statusbar/commandline/Parameters.kt
new file mode 100644
index 0000000..6ed5eed
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/commandline/Parameters.kt
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.commandline
+
+import android.util.IndentingPrintWriter
+import kotlin.properties.ReadOnlyProperty
+import kotlin.reflect.KProperty
+
+/**
+ * Definitions for all parameter types usable by [ParseableCommand]. Parameters are command line
+ * tokens that accept a fixed number of arguments and convert them to a parsed type.
+ *
+ * Example:
+ * ```
+ * my_command --single-arg-param arg
+ * ```
+ *
+ * In the example, `my_command` is the name of the command, `--single-arg-param` is the parameter,
+ * and `arg` is the value parsed by that parameter into its eventual type.
+ *
+ * Note on generics: The intended usage for parameters is to be able to return the parsed type from
+ * the given command as a `val` via property delegation. For example, let's say we have a command
+ * that has one optional and one required parameter:
+ * ```
+ * class MyCommand : ParseableCommand {
+ *   val requiredParam: Int by parser.param(...).required()
+ *   val optionalParam: Int? by parser.param(...)
+ * }
+ * ```
+ *
+ * In order to make the simple `param` method return the correct type, we need to do two things:
+ * 1. Break out the generic type into 2 pieces (TParsed and T)
+ * 2. Create two different underlying Parameter subclasses to handle the property delegation. One
+ *    handles `T?` and the other handles `T`. Note that in both cases, `TParsed` is always non-null
+ *    since the value parsed from the argument will throw an exception if missing or if it cannot be
+ *    parsed.
+ */
+
+/** A param type knows the number of arguments it expects */
+sealed interface Param : Describable {
+    val numArgs: Int
+
+    /**
+     * Consume [numArgs] items from the iterator and relay the result into its corresponding
+     * delegated type.
+     */
+    fun parseArgsFromIter(iterator: Iterator<String>)
+}
+
+/**
+ * Base class for required and optional SingleArgParam classes. For convenience, UnaryParam is
+ * defined as a [MultipleArgParam] where numArgs = 1. The benefit is that we can define the parsing
+ * in a single place, and yet on the client side we can unwrap the underlying list of params
+ * automatically.
+ */
+abstract class UnaryParamBase<out T, out TParsed : T>(val wrapped: MultipleArgParam<T, TParsed>) :
+    Param, ReadOnlyProperty<Any?, T> {
+    var handled = false
+
+    override fun describe(pw: IndentingPrintWriter) {
+        if (shortName != null) {
+            pw.print("$shortName, ")
+        }
+        pw.print(longName)
+        pw.println(" ${typeDescription()}")
+        if (description != null) {
+            pw.indented { pw.println(description) }
+        }
+    }
+
+    /**
+     * Try to describe the arg type. We can know if it's one of the base types what kind of input it
+     * takes. Otherwise just print "<arg>" and let the clients describe in the help text
+     */
+    private fun typeDescription() =
+        when (wrapped.valueParser) {
+            Type.Int -> "<int>"
+            Type.Float -> "<float>"
+            Type.String -> "<string>"
+            Type.Boolean -> "<boolean>"
+            else -> "<arg>"
+        }
+}
+
+/** Required single-arg parameter, delegating a non-null type to the client. */
+class SingleArgParam<out T : Any>(
+    override val longName: String,
+    override val shortName: String? = null,
+    override val description: String? = null,
+    val valueParser: ValueParser<T>,
+) :
+    UnaryParamBase<T, T>(
+        MultipleArgParam(
+            longName,
+            shortName,
+            1,
+            description,
+            valueParser,
+        )
+    ) {
+
+    override fun getValue(thisRef: Any?, property: KProperty<*>): T =
+        if (handled) {
+            wrapped.getValue(thisRef, property)[0]
+        } else {
+            throw IllegalStateException("Attempt to read property before parse() has executed")
+        }
+
+    override val numArgs: Int = 1
+
+    override fun parseArgsFromIter(iterator: Iterator<String>) {
+        wrapped.parseArgsFromIter(iterator)
+        handled = true
+    }
+}
+
+/** Optional single-argument parameter, delegating a nullable type to the client. */
+class SingleArgParamOptional<out T : Any>(
+    override val longName: String,
+    override val shortName: String? = null,
+    override val description: String? = null,
+    val valueParser: ValueParser<T>,
+) :
+    UnaryParamBase<T?, T>(
+        MultipleArgParam(
+            longName,
+            shortName,
+            1,
+            description,
+            valueParser,
+        )
+    ) {
+    override fun getValue(thisRef: Any?, property: KProperty<*>): T? =
+        wrapped.getValue(thisRef, property).getOrNull(0)
+
+    override val numArgs: Int = 1
+
+    override fun parseArgsFromIter(iterator: Iterator<String>) {
+        wrapped.parseArgsFromIter(iterator)
+        handled = true
+    }
+}
+
+/**
+ * Parses a list of args into the underlying [T] data type. The resultant value is an ordered list
+ * of type [TParsed].
+ *
+ * [T] and [TParsed] are split out here in the case where the entire param is optional. I.e., a
+ * MultipleArgParam<T?, T> indicates a command line argument that can be omitted. In that case, the
+ * inner list is List<T>?, NOT List<T?>. If the argument is provided, then the type is always going
+ * to be parsed into T rather than T?.
+ */
+class MultipleArgParam<out T, out TParsed : T>(
+    override val longName: String,
+    override val shortName: String? = null,
+    override val numArgs: Int = 1,
+    override val description: String? = null,
+    val valueParser: ValueParser<TParsed>,
+) : ReadOnlyProperty<Any?, List<TParsed>>, Param {
+    private val inner: MutableList<TParsed> = mutableListOf()
+
+    override fun getValue(thisRef: Any?, property: KProperty<*>): List<TParsed> = inner
+
+    /**
+     * Consumes [numArgs] values of the iterator and parses them into [TParsed].
+     *
+     * @throws ArgParseError on the first failure
+     */
+    override fun parseArgsFromIter(iterator: Iterator<String>) {
+        if (!iterator.hasNext()) {
+            throw ArgParseError("no argument provided for $shortName")
+        }
+        for (i in 0 until numArgs) {
+            valueParser
+                .parseValue(iterator.next())
+                .fold(onSuccess = { inner.add(it) }, onFailure = { throw it })
+        }
+    }
+}
+
+data class ArgParseError(override val message: String) : Exception(message)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/commandline/ParseableCommand.kt b/packages/SystemUI/src/com/android/systemui/statusbar/commandline/ParseableCommand.kt
new file mode 100644
index 0000000..ecd3fa6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/commandline/ParseableCommand.kt
@@ -0,0 +1,395 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.commandline
+
+import android.util.IndentingPrintWriter
+import java.io.PrintWriter
+import java.lang.IllegalArgumentException
+import kotlin.properties.ReadOnlyProperty
+import kotlin.reflect.KProperty
+
+/**
+ * An implementation of [Command] that includes a [CommandParser] which can set all delegated
+ * properties.
+ *
+ * As the number of registrants to [CommandRegistry] grows, we should have a default mechanism for
+ * parsing common command line arguments. We are not expecting to build an arbitrarily-functional
+ * CLI, nor a GNU arg parse compliant interface here, we simply want to be able to empower clients
+ * to create simple CLI grammars such as:
+ * ```
+ * $ my_command [-f|--flag]
+ * $ my_command [-a|--arg] <params...>
+ * $ my_command [subcommand1] [subcommand2]
+ * $ my_command <positional_arg ...> # not-yet implemented
+ * ```
+ *
+ * Note that the flags `-h` and `--help` are reserved for the base class. It seems prudent to just
+ * avoid them in your implementation.
+ *
+ * Usage:
+ *
+ * The intended usage tries to be clever enough to enable good ergonomics, while not too clever as
+ * to be unmaintainable. Using the default parser is done using property delegates, and looks like:
+ * ```
+ * class MyCommand(
+ *     onExecute: (cmd: MyCommand, pw: PrintWriter) -> ()
+ * ) : ParseableCommand(name) {
+ *     val flag1 by flag(
+ *         shortName = "-f",
+ *         longName = "--flag",
+ *         required = false,
+ *     )
+ *     val param1: String by param(
+ *         shortName = "-a",
+ *         longName = "--args",
+ *         valueParser = Type.String
+ *     ).required()
+ *     val param2: Int by param(..., valueParser = Type.Int)
+ *     val subCommand by subCommand(...)
+ *
+ *     override fun execute(pw: PrintWriter) {
+ *         onExecute(this, pw)
+ *     }
+ *
+ *     companion object {
+ *        const val name = "my_command"
+ *     }
+ * }
+ *
+ * fun main() {
+ *     fun printArgs(cmd: MyCommand, pw: PrintWriter) {
+ *         pw.println("${cmd.flag1}")
+ *         pw.println("${cmd.param1}")
+ *         pw.println("${cmd.param2}")
+ *         pw.println("${cmd.subCommand}")
+ *     }
+ *
+ *     commandRegistry.registerCommand(MyCommand.companion.name) {
+ *         MyCommand() { (cmd, pw) ->
+ *             printArgs(cmd, pw)
+ *         }
+ *     }
+ * }
+ *
+ * ```
+ */
+abstract class ParseableCommand(val name: String, val description: String? = null) : Command {
+    val parser: CommandParser = CommandParser()
+
+    val help by flag(longName = "help", shortName = "h", description = "Print help and return")
+
+    /**
+     * After [execute(pw, args)] is called, this class goes through a parsing stage and sets all
+     * delegated properties. It is safe to read any delegated properties here.
+     *
+     * This method is never called for [SubCommand]s, since they are associated with a top-level
+     * command that handles [execute]
+     */
+    abstract fun execute(pw: PrintWriter)
+
+    /**
+     * Given a command string list, [execute] parses the incoming command and validates the input.
+     * If this command or any of its subcommands is passed `-h` or `--help`, then execute will only
+     * print the relevant help message and exit.
+     *
+     * If any error is thrown during parsing, we will catch and log the error. This process should
+     * _never_ take down its process. Override [onParseFailed] to handle an [ArgParseError].
+     *
+     * Important: none of the delegated fields can be read before this stage.
+     */
+    override fun execute(pw: PrintWriter, args: List<String>) {
+        val success: Boolean
+        try {
+            success = parser.parse(args)
+        } catch (e: ArgParseError) {
+            pw.println(e.message)
+            onParseFailed(e)
+            return
+        } catch (e: Exception) {
+            pw.println("Unknown exception encountered during parse")
+            pw.println(e)
+            return
+        }
+
+        // Now we've parsed the incoming command without error. There are two things to check:
+        // 1. If any help is requested, print the help message and return
+        // 2. Otherwise, make sure required params have been passed in, and execute
+
+        val helpSubCmds = subCmdsRequestingHelp()
+
+        // Top-level help encapsulates subcommands. Otherwise, if _any_ subcommand requests
+        // help then defer to them. Else, just execute
+        if (help) {
+            help(pw)
+        } else if (helpSubCmds.isNotEmpty()) {
+            helpSubCmds.forEach { it.help(pw) }
+        } else {
+            if (!success) {
+                parser.generateValidationErrorMessages().forEach { pw.println(it) }
+            } else {
+                execute(pw)
+            }
+        }
+    }
+
+    /**
+     * Returns a list of all commands that asked for help. If non-empty, parsing will stop to print
+     * help. It is not guaranteed that delegates are fulfilled if help is requested
+     */
+    private fun subCmdsRequestingHelp(): List<ParseableCommand> =
+        parser.subCommands.filter { it.cmd.help }.map { it.cmd }
+
+    /** Override to do something when parsing fails */
+    open fun onParseFailed(error: ArgParseError) {}
+
+    /** Override to print a usage clause. E.g. `usage: my-cmd <arg1> <arg2>` */
+    open fun usage(pw: IndentingPrintWriter) {}
+
+    /**
+     * Print out the list of tokens, their received types if any, and their description in a
+     * formatted string.
+     *
+     * Example:
+     * ```
+     * my-command:
+     *   MyCmd.description
+     *
+     * [optional] usage block
+     *
+     * Flags:
+     *   -f
+     *     description
+     *   --flag2
+     *     description
+     *
+     * Parameters:
+     *   Required:
+     *     -p1 [Param.Type]
+     *       description
+     *     --param2 [Param.Type]
+     *       description
+     *   Optional:
+     *     same as above
+     *
+     * SubCommands:
+     *   Required:
+     *     ...
+     *   Optional:
+     *     ...
+     * ```
+     */
+    override fun help(pw: PrintWriter) {
+        val ipw = IndentingPrintWriter(pw)
+        ipw.printBoxed(name)
+        ipw.println()
+
+        // Allow for a simple `usage` block for clients
+        ipw.indented { usage(ipw) }
+
+        if (description != null) {
+            ipw.indented { ipw.println(description) }
+            ipw.println()
+        }
+
+        val flags = parser.flags
+        if (flags.isNotEmpty()) {
+            ipw.println("FLAGS:")
+            ipw.indented {
+                flags.forEach {
+                    it.describe(ipw)
+                    ipw.println()
+                }
+            }
+        }
+
+        val (required, optional) = parser.params.partition { it is SingleArgParam<*> }
+        if (required.isNotEmpty()) {
+            ipw.println("REQUIRED PARAMS:")
+            required.describe(ipw)
+        }
+        if (optional.isNotEmpty()) {
+            ipw.println("OPTIONAL PARAMS:")
+            optional.describe(ipw)
+        }
+
+        val (reqSub, optSub) = parser.subCommands.partition { it is RequiredSubCommand<*> }
+        if (reqSub.isNotEmpty()) {
+            ipw.println("REQUIRED SUBCOMMANDS:")
+            reqSub.describe(ipw)
+        }
+        if (optSub.isNotEmpty()) {
+            ipw.println("OPTIONAL SUBCOMMANDS:")
+            optSub.describe(ipw)
+        }
+    }
+
+    fun flag(
+        longName: String,
+        shortName: String? = null,
+        description: String = "",
+    ): Flag {
+        if (!checkShortName(shortName)) {
+            throw IllegalArgumentException(
+                "Flag short name must be one character long, or null. Got ($shortName)"
+            )
+        }
+
+        if (!checkLongName(longName)) {
+            throw IllegalArgumentException("Flags must not start with '-'. Got $($longName)")
+        }
+
+        val short = shortName?.let { "-$shortName" }
+        val long = "--$longName"
+
+        return parser.flag(long, short, description)
+    }
+
+    fun <T : Any> param(
+        longName: String,
+        shortName: String? = null,
+        description: String = "",
+        valueParser: ValueParser<T>,
+    ): SingleArgParamOptional<T> {
+        if (!checkShortName(shortName)) {
+            throw IllegalArgumentException(
+                "Parameter short name must be one character long, or null. Got ($shortName)"
+            )
+        }
+
+        if (!checkLongName(longName)) {
+            throw IllegalArgumentException("Parameters must not start with '-'. Got $($longName)")
+        }
+
+        val short = shortName?.let { "-$shortName" }
+        val long = "--$longName"
+
+        return parser.param(long, short, description, valueParser)
+    }
+
+    fun <T : ParseableCommand> subCommand(
+        command: T,
+    ) = parser.subCommand(command)
+
+    /** For use in conjunction with [param], makes the parameter required */
+    fun <T : Any> SingleArgParamOptional<T>.required(): SingleArgParam<T> = parser.require(this)
+
+    /** For use in conjunction with [subCommand], makes the given [SubCommand] required */
+    fun <T : ParseableCommand> OptionalSubCommand<T>.required(): RequiredSubCommand<T> =
+        parser.require(this)
+
+    private fun checkShortName(short: String?): Boolean {
+        return short == null || short.length == 1
+    }
+
+    private fun checkLongName(long: String): Boolean {
+        return !long.startsWith("-")
+    }
+
+    companion object {
+        fun Iterable<Describable>.describe(pw: IndentingPrintWriter) {
+            pw.indented {
+                forEach {
+                    it.describe(pw)
+                    pw.println()
+                }
+            }
+        }
+    }
+}
+
+/**
+ * A flag is a boolean value passed over the command line. It can have a short form or long form.
+ * The value is [Boolean.true] if the flag is found, else false
+ */
+data class Flag(
+    override val shortName: String? = null,
+    override val longName: String,
+    override val description: String? = null,
+) : ReadOnlyProperty<Any?, Boolean>, Describable {
+    var inner: Boolean = false
+
+    override fun getValue(thisRef: Any?, property: KProperty<*>) = inner
+}
+
+/**
+ * Named CLI token. Can have a short or long name. Note: consider renaming to "primary" and
+ * "secondary" names since we don't actually care what the strings are
+ *
+ * Flags and params will have [shortName]s that are always prefixed with a single dash, while
+ * [longName]s are prefixed by a double dash. E.g., `my_command -f --flag`.
+ *
+ * Subcommands do not do any prefixing, and register their name as the [longName]
+ *
+ * Can be matched against an incoming token
+ */
+interface CliNamed {
+    val shortName: String?
+    val longName: String
+
+    fun matches(token: String) = shortName == token || longName == token
+}
+
+interface Describable : CliNamed {
+    val description: String?
+
+    fun describe(pw: IndentingPrintWriter) {
+        if (shortName != null) {
+            pw.print("$shortName, ")
+        }
+        pw.print(longName)
+        pw.println()
+        if (description != null) {
+            pw.indented { pw.println(description) }
+        }
+    }
+}
+
+/**
+ * Print [s] inside of a unicode character box, like so:
+ * ```
+ *  ╔═══════════╗
+ *  ║ my-string ║
+ *  ╚═══════════╝
+ * ```
+ */
+fun PrintWriter.printDoubleBoxed(s: String) {
+    val length = s.length
+    println("╔${"═".repeat(length + 2)}╗")
+    println("║ $s ║")
+    println("╚${"═".repeat(length + 2)}╝")
+}
+
+/**
+ * Print [s] inside of a unicode character box, like so:
+ * ```
+ *  ┌───────────┐
+ *  │ my-string │
+ *  └───────────┘
+ * ```
+ */
+fun PrintWriter.printBoxed(s: String) {
+    val length = s.length
+    println("┌${"─".repeat(length + 2)}┐")
+    println("│ $s │")
+    println("└${"─".repeat(length + 2)}┘")
+}
+
+fun IndentingPrintWriter.indented(block: () -> Unit) {
+    increaseIndent()
+    block()
+    decreaseIndent()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/commandline/SubCommand.kt b/packages/SystemUI/src/com/android/systemui/statusbar/commandline/SubCommand.kt
new file mode 100644
index 0000000..41bac86
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/commandline/SubCommand.kt
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.commandline
+
+import android.util.IndentingPrintWriter
+import kotlin.properties.ReadOnlyProperty
+import kotlin.reflect.KProperty
+
+/**
+ * Sub commands wrap [ParseableCommand]s and are attached to a parent [ParseableCommand]. As such
+ * they have their own parser which will parse the args as a subcommand. I.e., the subcommand's
+ * parser will consume the iterator created by the parent, reversing the index when it reaches an
+ * unknown token.
+ *
+ * In order to keep subcommands relatively simple and not have to do complicated validation, sub
+ * commands will return control to the parent parser as soon as they discover a token that they do
+ * not own. They will throw an [ArgParseError] if parsing fails or if they don't receive arguments
+ * for a required parameter.
+ */
+sealed interface SubCommand : Describable {
+    val cmd: ParseableCommand
+
+    /** Checks if all of the required elements were passed in to [parseSubCommandArgs] */
+    var validationStatus: Boolean
+
+    /**
+     * To keep parsing simple, [parseSubCommandArgs] requires a [ListIterator] so that it can rewind
+     * the iterator when it yields control upwards
+     */
+    fun parseSubCommandArgs(iterator: ListIterator<String>)
+}
+
+/**
+ * Note that the delegated type from the subcommand is `T: ParseableCommand?`. SubCommands are
+ * created via adding a fully-formed [ParseableCommand] to parent command.
+ *
+ * At this point in time, I don't recommend nesting subcommands.
+ */
+class OptionalSubCommand<T : ParseableCommand>(
+    override val cmd: T,
+) : SubCommand, ReadOnlyProperty<Any?, ParseableCommand?> {
+    override val shortName: String? = null
+    override val longName: String = cmd.name
+    override val description: String? = cmd.description
+    override var validationStatus = true
+
+    private var isPresent = false
+
+    /** Consume tokens from the iterator and pass them to the wrapped command */
+    override fun parseSubCommandArgs(iterator: ListIterator<String>) {
+        validationStatus = cmd.parser.parseAsSubCommand(iterator)
+        isPresent = true
+    }
+
+    override fun getValue(thisRef: Any?, property: KProperty<*>): T? =
+        if (isPresent) {
+            cmd
+        } else {
+            null
+        }
+
+    override fun describe(pw: IndentingPrintWriter) {
+        cmd.help(pw)
+    }
+}
+
+/**
+ * Non-optional subcommand impl. Top-level parser is expected to throw [ArgParseError] if this token
+ * is not present in the incoming command
+ */
+class RequiredSubCommand<T : ParseableCommand>(
+    override val cmd: T,
+) : SubCommand, ReadOnlyProperty<Any?, ParseableCommand> {
+    override val shortName: String? = null
+    override val longName: String = cmd.name
+    override val description: String? = cmd.description
+    override var validationStatus = true
+
+    /** Unhandled, required subcommands are an error */
+    var handled = false
+
+    override fun parseSubCommandArgs(iterator: ListIterator<String>) {
+        validationStatus = cmd.parser.parseAsSubCommand(iterator)
+        handled = true
+    }
+
+    override fun getValue(thisRef: Any?, property: KProperty<*>): ParseableCommand = cmd
+
+    override fun describe(pw: IndentingPrintWriter) {
+        cmd.help(pw)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/commandline/ValueParser.kt b/packages/SystemUI/src/com/android/systemui/statusbar/commandline/ValueParser.kt
new file mode 100644
index 0000000..01083d9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/commandline/ValueParser.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.systemui.statusbar.commandline
+
+import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.InvocationKind
+import kotlin.contracts.contract
+
+/**
+ * Utilities for parsing the [String] command line arguments. Arguments are related to the
+ * [Parameter] type, which declares the number of, and resulting type of, the arguments that it
+ * takes when parsing. For Example:
+ * ```
+ * my-command --param <str> --param2 <int>
+ * ```
+ *
+ * Defines 2 parameters, the first of which takes a string, and the second requires an int. Because
+ * fundamentally _everything_ is a string, we have to define a convenient way to get from the
+ * incoming `StringArg` to the resulting `T`-arg, where `T` is the type required by the client.
+ *
+ * Parsing is therefore a relatively straightforward operation: (String) -> T. However, since
+ * parsing can always fail, the type is actually (String) -> Result<T>. We will always want to fail
+ * on the first error and propagate it to the caller (typically this results in printing the `help`
+ * message of the command`).
+ *
+ * The identity parsing is trivial:
+ * ```
+ * (s: String) -> String = { s -> s }
+ * ```
+ *
+ * Basic mappings are actually even provided by Kotlin's stdlib:
+ * ```
+ * (s: String) -> Boolean = { s -> s.toBooleanOrNull() }
+ * (s: String) -> Int = { s -> s.toIntOrNull() }
+ * ...
+ * ```
+ *
+ * In order to properly encode errors, we will ascribe an error type to any `null` values, such that
+ * parsing looks like this:
+ * ```
+ * val mapping: (String) -> T? = {...} // for some T
+ * val parser: (String) -> Result<T> = { s ->
+ *   mapping(s)?.let {
+ *     Result.success(it)
+ *   } ?: Result.failure(/* some failure type */)
+ * }
+ * ```
+ *
+ * Composition
+ *
+ * The ability to compose value parsing enables us to provide a couple of reasonable default parsers
+ * and allow clients to seamlessly build upon that using map functions. Consider the case where we
+ * want to validate that a value is an [Int] between 0 and 100. We start with the generic [Int]
+ * parser, and a validator, of the type (Int) -> Result<Int>:
+ * ```
+ * val intParser = { s ->
+ *   s.toStringOrNull().?let {...} ?: ...
+ * }
+ *
+ * val validator = { i ->
+ *   if (i > 100 || i < 0) {
+ *     Result.failure(...)
+ *   } else {
+ *     Result.success(i)
+ *   }
+ * ```
+ *
+ * In order to combine these functions, we need to define a new [flatMap] function that can get us
+ * from a `Result<T>` to a `Result<R>`, and short-circuit on any error. We want to see this:
+ * ```
+ * val validatingParser = { s ->
+ *   intParser.invoke(s).flatMap { i ->
+ *     validator(i)
+ *   }
+ * }
+ * ```
+ *
+ * The flatMap is relatively simply defined, we can mimic the existing definition for [Result.map],
+ * though the implementation is uglier because of the `internal` definition for `value`
+ *
+ * ```
+ * inline fun <R, T> Result<T>.flatMap(transform: (value: T) -> Result<R>): Result<R> {
+ *   return when {
+ *     isSuccess -> transform(getOrThrow())
+ *     else -> Result.failure(exceptionOrNull()!!)
+ *   }
+ * }
+ * ```
+ */
+
+/**
+ * Given a [transform] that returns a [Result], apply the transform to this result, unwrapping the
+ * return value so that
+ *
+ * These [contract] and [callsInPlace] methods are copied from the [Result.map] definition
+ */
+@OptIn(ExperimentalContracts::class)
+inline fun <R, T> Result<T>.flatMap(transform: (value: T) -> Result<R>): Result<R> {
+    contract { callsInPlace(transform, InvocationKind.AT_MOST_ONCE) }
+
+    return when {
+        // Should never throw, we just don't have access to [this.value]
+        isSuccess -> transform(getOrThrow())
+        // Exception should never be null here
+        else -> Result.failure(exceptionOrNull()!!)
+    }
+}
+
+/**
+ * ValueParser turns a [String] into a Result<A> by applying a transform. See the default
+ * implementations below for starting points. The intention here is to provide the base mappings and
+ * allow clients to attach their own transforms. They are expected to succeed or return null on
+ * failure. The failure is propagated to the command parser as a Result and will fail on any
+ * [Result.failure]
+ */
+fun interface ValueParser<out A> {
+    fun parseValue(value: String): Result<A>
+}
+
+/** Map a [ValueParser] of type A to one of type B, by applying the given [transform] */
+inline fun <A, B> ValueParser<A>.map(crossinline transform: (A) -> B?): ValueParser<B> {
+    return ValueParser<B> { value ->
+        this.parseValue(value).flatMap { a ->
+            transform(a)?.let { b -> Result.success(b) }
+                ?: Result.failure(ArgParseError("Failed to transform value $value"))
+        }
+    }
+}
+
+/**
+ * Base type parsers are provided by the lib, and can be simply composed upon by [ValueParser.map]
+ * functions on the parser
+ */
+
+/** String parsing always succeeds if the value exists */
+private val parseString: ValueParser<String> = ValueParser { value -> Result.success(value) }
+
+private val parseBoolean: ValueParser<Boolean> = ValueParser { value ->
+    value.toBooleanStrictOrNull()?.let { Result.success(it) }
+        ?: Result.failure(ArgParseError("Failed to parse $value as a boolean"))
+}
+
+private val parseInt: ValueParser<Int> = ValueParser { value ->
+    value.toIntOrNull()?.let { Result.success(it) }
+        ?: Result.failure(ArgParseError("Failed to parse $value as an int"))
+}
+
+private val parseFloat: ValueParser<Float> = ValueParser { value ->
+    value.toFloatOrNull()?.let { Result.success(it) }
+        ?: Result.failure(ArgParseError("Failed to parse $value as a float"))
+}
+
+/** Default parsers that can be use as-is, or [map]ped to another type */
+object Type {
+    val Boolean = parseBoolean
+    val Int = parseInt
+    val Float = parseFloat
+    val String = parseString
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
index 2465c21..9aa28c3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
@@ -18,10 +18,6 @@
 
 import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
-import static android.net.wifi.WifiManager.TrafficStateCallback.DATA_ACTIVITY_IN;
-import static android.net.wifi.WifiManager.TrafficStateCallback.DATA_ACTIVITY_INOUT;
-import static android.net.wifi.WifiManager.TrafficStateCallback.DATA_ACTIVITY_NONE;
-import static android.net.wifi.WifiManager.TrafficStateCallback.DATA_ACTIVITY_OUT;
 import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
 
 import android.annotation.Nullable;
@@ -74,7 +70,7 @@
 import com.android.systemui.demomode.DemoModeController;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.log.LogBuffer;
-import com.android.systemui.log.LogLevel;
+import com.android.systemui.log.core.LogLevel;
 import com.android.systemui.log.dagger.StatusBarNetworkControllerLog;
 import com.android.systemui.qs.tiles.dialog.InternetDialogFactory;
 import com.android.systemui.settings.UserTracker;
@@ -557,10 +553,6 @@
         mBroadcastDispatcher.unregisterReceiver(this);
     }
 
-    public int getConnectedWifiLevel() {
-        return mWifiSignalController.getState().level;
-    }
-
     @Override
     public AccessPointController getAccessPointController() {
         return mAccessPoints;
@@ -654,14 +646,6 @@
         return mWifiSignalController.isCarrierMergedWifi(subId);
     }
 
-    boolean hasDefaultNetwork() {
-        return !mNoDefaultNetwork;
-    }
-
-    boolean isNonCarrierWifiNetworkAvailable() {
-        return !mNoNetworksAvailable;
-    }
-
     boolean isEthernetDefault() {
         return mConnectedTransports.get(NetworkCapabilities.TRANSPORT_ETHERNET);
     }
@@ -1242,15 +1226,12 @@
     }
 
     private boolean mDemoInetCondition;
-    private WifiState mDemoWifiState;
 
     @Override
     public void onDemoModeStarted() {
         if (DEBUG) Log.d(TAG, "Entering demo mode");
         unregisterListeners();
         mDemoInetCondition = mInetCondition;
-        mDemoWifiState = mWifiSignalController.getState();
-        mDemoWifiState.ssid = "DemoMode";
     }
 
     @Override
@@ -1300,41 +1281,6 @@
                 controller.updateConnectivity(connected, connected);
             }
         }
-        String wifi = args.getString("wifi");
-        if (wifi != null && !mStatusBarPipelineFlags.runNewWifiIconBackend()) {
-            boolean show = wifi.equals("show");
-            String level = args.getString("level");
-            if (level != null) {
-                mDemoWifiState.level = level.equals("null") ? -1
-                        : Math.min(Integer.parseInt(level), WifiIcons.WIFI_LEVEL_COUNT - 1);
-                mDemoWifiState.connected = mDemoWifiState.level >= 0;
-            }
-            String activity = args.getString("activity");
-            if (activity != null) {
-                switch (activity) {
-                    case "inout":
-                        mWifiSignalController.setActivity(DATA_ACTIVITY_INOUT);
-                        break;
-                    case "in":
-                        mWifiSignalController.setActivity(DATA_ACTIVITY_IN);
-                        break;
-                    case "out":
-                        mWifiSignalController.setActivity(DATA_ACTIVITY_OUT);
-                        break;
-                    default:
-                        mWifiSignalController.setActivity(DATA_ACTIVITY_NONE);
-                        break;
-                }
-            } else {
-                mWifiSignalController.setActivity(DATA_ACTIVITY_NONE);
-            }
-            String ssid = args.getString("ssid");
-            if (ssid != null) {
-                mDemoWifiState.ssid = ssid;
-            }
-            mDemoWifiState.enabled = show;
-            mWifiSignalController.notifyListeners();
-        }
         String sims = args.getString("sims");
         if (sims != null && !mStatusBarPipelineFlags.useNewMobileIcons()) {
             int num = MathUtils.constrain(Integer.parseInt(sims), 1, 8);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
index f6c9a5c..e5ba3ce 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
@@ -28,6 +28,7 @@
 import com.android.systemui.animation.ActivityLaunchAnimator;
 import com.android.systemui.animation.AnimationFeatureFlags;
 import com.android.systemui.animation.DialogLaunchAnimator;
+import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
@@ -35,11 +36,13 @@
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
-import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor;
 import com.android.systemui.media.controls.pipeline.MediaDataManager;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.power.domain.interactor.PowerInteractor;
 import com.android.systemui.settings.DisplayTracker;
+import com.android.systemui.shade.NotificationPanelViewController;
+import com.android.systemui.shade.ShadeSurface;
 import com.android.systemui.shade.carrier.ShadeCarrierGroupController;
 import com.android.systemui.statusbar.ActionClickLogger;
 import com.android.systemui.statusbar.CommandQueue;
@@ -75,18 +78,17 @@
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.RemoteInputUriController;
 import com.android.systemui.statusbar.window.StatusBarWindowController;
-import com.android.systemui.tracing.ProtoTracer;
 import com.android.systemui.util.concurrency.DelayableExecutor;
 import com.android.systemui.util.time.SystemClock;
 
-import java.util.Optional;
-import java.util.concurrent.Executor;
-
 import dagger.Binds;
 import dagger.Lazy;
 import dagger.Module;
 import dagger.Provides;
 
+import java.util.Optional;
+import java.util.concurrent.Executor;
+
 /**
  * This module provides instances needed to construct {@link CentralSurfacesImpl}. These are moved to
  * this separate from {@link CentralSurfacesModule} module so that components that wish to build
@@ -104,7 +106,7 @@
             NotificationLockscreenUserManager lockscreenUserManager,
             SmartReplyController smartReplyController,
             NotificationVisibilityProvider visibilityProvider,
-            Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy,
+            PowerInteractor powerInteractor,
             StatusBarStateController statusBarStateController,
             RemoteInputUriController remoteInputUriController,
             RemoteInputControllerLogger remoteInputControllerLogger,
@@ -117,7 +119,7 @@
                 lockscreenUserManager,
                 smartReplyController,
                 visibilityProvider,
-                centralSurfacesOptionalLazy,
+                powerInteractor,
                 statusBarStateController,
                 remoteInputUriController,
                 remoteInputControllerLogger,
@@ -192,11 +194,10 @@
     static CommandQueue provideCommandQueue(
             Context context,
             DisplayTracker displayTracker,
-            ProtoTracer protoTracer,
             CommandRegistry registry,
             DumpHandler dumpHandler
     ) {
-        return new CommandQueue(context, displayTracker, protoTracer, registry, dumpHandler);
+        return new CommandQueue(context, displayTracker, registry, dumpHandler);
     }
 
     /**
@@ -272,6 +273,21 @@
         return ongoingCallController;
     }
 
+    /**
+     * {@link NotificationPanelViewController} implements two interfaces:
+     *  - {@link com.android.systemui.shade.ShadeViewController}, which can be used by any class
+     *    needing access to the shade.
+     *  - {@link ShadeSurface}, which should *only* be used by {@link CentralSurfacesImpl}.
+     *
+     * Since {@link ShadeSurface} should only be accessible by {@link CentralSurfacesImpl}, it's
+     * *only* bound in this CentralSurfaces dependencies module.
+     * The {@link com.android.systemui.shade.ShadeViewController} interface is bound in
+     * {@link com.android.systemui.shade.ShadeModule} so others can access it.
+     */
+    @Binds
+    @SysUISingleton
+    ShadeSurface provideShadeSurface(NotificationPanelViewController impl);
+
     /** */
     @Binds
     ShadeCarrierGroupController.SlotIndexResolver provideSlotIndexResolver(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/disableflags/DisableFlagsLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/disableflags/DisableFlagsLogger.kt
index f04159c..dc86869 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/disableflags/DisableFlagsLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/disableflags/DisableFlagsLogger.kt
@@ -71,15 +71,14 @@
     }
 
     /**
-     * Returns a string representing the, old, new, and new-after-modification disable flag states,
-     * as well as the differences between each of the states.
+     * Returns a string representing the new and new-after-modification disable flag states,
+     * as well as the differences between them (if there are any).
      *
-     * Example if [old], [new], and [newAfterLocalModification] are all different:
-     *   Old: EnaiHbcRso.qINgr | New: EnaihBcRso.qiNGR (changed: hB.iGR) | New after local
-     *   modification: EnaihBcRso.qInGR (changed: .n)
+     * Example if [new] and [newAfterLocalModification] are different:
+     *   New: EnaihBcRso.qiNGR | New after local modification: EnaihBCRso.qInGR (changed: C.In)
      *
-     * Example if [old] and [new] are the same:
-     *   EnaihBcRso.qiNGR (unchanged)
+     * Example if [new] and [newAfterLocalModification] are the same:
+     *   New: EnaihBcRso.qiNGR
      *
      * A capital character signifies the flag is set and a lowercase character signifies that the
      * flag isn't set. The flag states will be logged in the same order as the passed-in lists.
@@ -88,37 +87,17 @@
      * is no difference. the new-after-modification state also won't be included if there's no
      * difference from the new state.
      *
-     * @param old the disable state that had been previously sent. Null if we don't need to log the
-     *   previously sent state.
      * @param new the new disable state that has just been sent.
      * @param newAfterLocalModification the new disable states after a class has locally modified
      *   them. Null if the class does not locally modify.
      */
     fun getDisableFlagsString(
-        old: DisableState? = null,
         new: DisableState,
         newAfterLocalModification: DisableState? = null
     ): String {
         val builder = StringBuilder("Received new disable state: ")
 
-        // This if/else has slightly repetitive code but is easier to read.
-        if (old != null && old != new) {
-            builder.append("Old: ")
-            builder.append(getFlagsString(old))
-            builder.append(" | ")
-            builder.append("New: ")
-            builder.append(getFlagsString(new))
-            builder.append(" ")
-            builder.append(getDiffString(old, new))
-        } else if (old != null && old == new) {
-            // If old and new are the same, we only need to print one of them.
-            builder.append(getFlagsString(new))
-            builder.append(" ")
-            builder.append(getDiffString(old, new))
-        } else { // old == null
-            builder.append(getFlagsString(new))
-            // Don't get a diff string because we have no [old] to compare with.
-        }
+        builder.append(getFlagsString(new))
 
         if (newAfterLocalModification != null && new != newAfterLocalModification) {
             builder.append(" | New after local modification: ")
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/disableflags/dagger/DisableFlagsModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/disableflags/dagger/DisableFlagsModule.kt
new file mode 100644
index 0000000..3d5f8df
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/disableflags/dagger/DisableFlagsModule.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.disableflags.dagger
+
+import com.android.systemui.statusbar.disableflags.data.repository.DisableFlagsRepository
+import com.android.systemui.statusbar.disableflags.data.repository.DisableFlagsRepositoryImpl
+import dagger.Binds
+import dagger.Module
+
+/** Provides information related to disable flags. */
+@Module
+abstract class DisableFlagsModule {
+    @Binds
+    abstract fun bindDisableFlagsRepo(impl: DisableFlagsRepositoryImpl): DisableFlagsRepository
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/disableflags/data/model/DisableFlagsModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/disableflags/data/model/DisableFlagsModel.kt
new file mode 100644
index 0000000..2bb4765
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/disableflags/data/model/DisableFlagsModel.kt
@@ -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.systemui.statusbar.disableflags.data.model
+
+import android.app.StatusBarManager.DISABLE2_NONE
+import android.app.StatusBarManager.DISABLE2_NOTIFICATION_SHADE
+import android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS
+import android.app.StatusBarManager.DISABLE_NONE
+import android.app.StatusBarManager.DISABLE_NOTIFICATION_ALERTS
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.statusbar.disableflags.DisableFlagsLogger
+
+/**
+ * Model for the disable flags that come from [IStatusBar].
+ *
+ * For clients of the disable flags: do *not* refer to the disable integers directly. Instead,
+ * re-use or define a helper method that internally processes the flags. (We want to hide the
+ * bitwise logic here so no one else has to worry about it.)
+ */
+data class DisableFlagsModel(
+    private val disable1: Int = DISABLE_NONE,
+    private val disable2: Int = DISABLE2_NONE,
+) {
+    /** Returns true if notification alerts are allowed based on the flags. */
+    fun areNotificationAlertsEnabled(): Boolean {
+        return (disable1 and DISABLE_NOTIFICATION_ALERTS) == 0
+    }
+
+    /** Returns true if the shade is allowed based on the flags. */
+    fun isShadeEnabled(): Boolean {
+        return (disable2 and DISABLE2_NOTIFICATION_SHADE) == 0
+    }
+
+    /** Returns true if full quick settings are allowed based on the flags. */
+    fun isQuickSettingsEnabled(): Boolean {
+        return (disable2 and DISABLE2_QUICK_SETTINGS) == 0
+    }
+
+    /** Logs the change to the provided buffer. */
+    fun logChange(buffer: LogBuffer, disableFlagsLogger: DisableFlagsLogger) {
+        buffer.log(
+            TAG,
+            LogLevel.INFO,
+            {
+                int1 = disable1
+                int2 = disable2
+            },
+            {
+                disableFlagsLogger.getDisableFlagsString(
+                    new = DisableFlagsLogger.DisableState(int1, int2),
+                )
+            }
+        )
+    }
+
+    private companion object {
+        const val TAG = "DisableFlagsModel"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/disableflags/data/repository/DisableFlagsRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/disableflags/data/repository/DisableFlagsRepository.kt
new file mode 100644
index 0000000..13b74b4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/disableflags/data/repository/DisableFlagsRepository.kt
@@ -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.systemui.statusbar.disableflags.data.repository
+
+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.DisplayId
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.dagger.DisableFlagsRepositoryLog
+import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.statusbar.disableflags.DisableFlagsLogger
+import com.android.systemui.statusbar.disableflags.data.model.DisableFlagsModel
+import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.stateIn
+
+/** Repository for the disable flags received from external systems. See [IStatusBar.disable]. */
+interface DisableFlagsRepository {
+    /** A model of the disable flags last received from [IStatusBar]. */
+    val disableFlags: StateFlow<DisableFlagsModel>
+}
+
+@SysUISingleton
+class DisableFlagsRepositoryImpl
+@Inject
+constructor(
+    commandQueue: CommandQueue,
+    @DisplayId private val thisDisplayId: Int,
+    @Application scope: CoroutineScope,
+    remoteInputQuickSettingsDisabler: RemoteInputQuickSettingsDisabler,
+    @DisableFlagsRepositoryLog private val logBuffer: LogBuffer,
+    private val disableFlagsLogger: DisableFlagsLogger,
+) : DisableFlagsRepository {
+    override val disableFlags: StateFlow<DisableFlagsModel> =
+        conflatedCallbackFlow {
+                val callback =
+                    object : CommandQueue.Callbacks {
+                        override fun disable(
+                            displayId: Int,
+                            state1: Int,
+                            state2: Int,
+                            animate: Boolean,
+                        ) {
+                            if (displayId != thisDisplayId) {
+                                return
+                            }
+                            trySend(
+                                DisableFlagsModel(
+                                    state1,
+                                    // Ideally, [RemoteInputQuickSettingsDisabler] should instead
+                                    // expose a flow that gets `combine`d with this [disableFlags]
+                                    // flow in a [DisableFlagsInteractor] or
+                                    // [QuickSettingsInteractor]-type class. However, that's out of
+                                    // scope for the CentralSurfaces removal project.
+                                    remoteInputQuickSettingsDisabler.adjustDisableFlags(state2),
+                                )
+                            )
+                        }
+                    }
+                commandQueue.addCallback(callback)
+                awaitClose { commandQueue.removeCallback(callback) }
+            }
+            .distinctUntilChanged()
+            .onEach { it.logChange(logBuffer, disableFlagsLogger) }
+            // Use Eagerly because we always need to know about disable flags
+            .stateIn(scope, SharingStarted.Eagerly, DisableFlagsModel())
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusBarEventsModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusBarEventsModule.kt
index 3d6d489..84796f9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusBarEventsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusBarEventsModule.kt
@@ -22,6 +22,8 @@
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogBufferFactory
 import com.android.systemui.statusbar.window.StatusBarWindowController
 import com.android.systemui.util.concurrency.DelayableExecutor
 import com.android.systemui.util.time.SystemClock
@@ -36,6 +38,13 @@
 
         @Provides
         @SysUISingleton
+        @SystemStatusAnimationSchedulerLog
+        fun provideSystemStatusAnimationSchedulerLogBuffer(factory: LogBufferFactory): LogBuffer {
+            return factory.create("SystemStatusAnimationSchedulerLog", 60)
+        }
+
+        @Provides
+        @SysUISingleton
         fun provideSystemStatusAnimationScheduler(
                 featureFlags: FeatureFlags,
                 coordinator: SystemEventCoordinator,
@@ -44,7 +53,8 @@
                 dumpManager: DumpManager,
                 systemClock: SystemClock,
                 @Application coroutineScope: CoroutineScope,
-                @Main executor: DelayableExecutor
+                @Main executor: DelayableExecutor,
+                logger: SystemStatusAnimationSchedulerLogger
         ): SystemStatusAnimationScheduler {
             return if (featureFlags.isEnabled(Flags.PLUG_IN_STATUS_BAR_CHIP)) {
                 SystemStatusAnimationSchedulerImpl(
@@ -53,7 +63,8 @@
                         statusBarWindowController,
                         dumpManager,
                         systemClock,
-                        coroutineScope
+                        coroutineScope,
+                        logger
                 )
             } else {
                 SystemStatusAnimationSchedulerLegacyImpl(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusEvent.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusEvent.kt
index e5849c0..bde298d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusEvent.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusEvent.kt
@@ -24,6 +24,7 @@
 import com.android.systemui.privacy.OngoingPrivacyChip
 import com.android.systemui.privacy.PrivacyItem
 import com.android.systemui.statusbar.BatteryStatusChip
+import com.android.systemui.statusbar.ConnectedDisplayChip
 
 typealias ViewCreator = (context: Context) -> BackgroundAnimatableView
 
@@ -87,6 +88,23 @@
     }
 }
 
+/** Event that triggers a connected display chip in the status bar. */
+class ConnectedDisplayEvent : StatusEvent {
+    /** Priority is set higher than [BatteryEvent]. */
+    override val priority = 60
+    override var forceVisible = false
+    override val showAnimation = true
+    override var contentDescription: String? = ""
+
+    override val viewCreator: ViewCreator = { context ->
+        ConnectedDisplayChip(context)
+    }
+
+    override fun toString(): String {
+        return javaClass.simpleName
+    }
+}
+
 /** open only for testing purposes. (See [FakeStatusEvent.kt]) */
 open class PrivacyEvent(override val showAnimation: Boolean = true) : StatusEvent {
     override var contentDescription: String? = null
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt
index 776956a..5639000 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt
@@ -30,9 +30,11 @@
 import androidx.core.animation.AnimatorListenerAdapter
 import androidx.core.animation.AnimatorSet
 import androidx.core.animation.ValueAnimator
+import com.android.internal.annotations.VisibleForTesting
 import com.android.systemui.R
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
+import com.android.systemui.statusbar.phone.StatusBarContentInsetsChangedListener
 import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider
 import com.android.systemui.statusbar.window.StatusBarWindowController
 import com.android.systemui.util.animation.AnimationUtil.Companion.frames
@@ -46,7 +48,7 @@
     private val context: Context,
     private val statusBarWindowController: StatusBarWindowController,
     private val contentInsetsProvider: StatusBarContentInsetsProvider,
-    private val featureFlags: FeatureFlags
+    private val featureFlags: FeatureFlags,
 ) : SystemStatusAnimationCallback {
 
     private lateinit var animationWindowView: FrameLayout
@@ -56,7 +58,8 @@
 
     // Left for LTR, Right for RTL
     private var animationDirection = LEFT
-    private var chipBounds = Rect()
+
+    @VisibleForTesting var chipBounds = Rect()
     private val chipWidth get() = chipBounds.width()
     private val chipRight get() = chipBounds.right
     private val chipLeft get() = chipBounds.left
@@ -69,7 +72,7 @@
     private var animRect = Rect()
 
     // TODO: move to dagger
-    private var initialized = false
+    @VisibleForTesting var initialized = false
 
     /**
      * Give the chip controller a chance to inflate and configure the chip view before we start
@@ -98,23 +101,7 @@
                     View.MeasureSpec.makeMeasureSpec(
                             (animationWindowView.parent as View).height, AT_MOST))
 
-            // decide which direction we're animating from, and then set some screen coordinates
-            val contentRect = contentInsetsProvider.getStatusBarContentAreaForCurrentRotation()
-            val chipTop = ((animationWindowView.parent as View).height - it.view.measuredHeight) / 2
-            val chipBottom = chipTop + it.view.measuredHeight
-            val chipRight: Int
-            val chipLeft: Int
-            when (animationDirection) {
-                LEFT -> {
-                    chipRight = contentRect.right
-                    chipLeft = contentRect.right - it.chipWidth
-                }
-                else /* RIGHT */ -> {
-                    chipLeft = contentRect.left
-                    chipRight = contentRect.left + it.chipWidth
-                }
-            }
-            chipBounds = Rect(chipLeft, chipTop, chipRight, chipBottom)
+            updateChipBounds(it, contentInsetsProvider.getStatusBarContentAreaForCurrentRotation())
         }
     }
 
@@ -253,16 +240,67 @@
         return animSet
     }
 
-    private fun init() {
+    fun init() {
         initialized = true
         themedContext = ContextThemeWrapper(context, R.style.Theme_SystemUI_QuickSettings)
         animationWindowView = LayoutInflater.from(themedContext)
                 .inflate(R.layout.system_event_animation_window, null) as FrameLayout
-        val lp = FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)
-        lp.gravity = Gravity.END or Gravity.CENTER_VERTICAL
+        // Matches status_bar.xml
+        val height = themedContext.resources.getDimensionPixelSize(R.dimen.status_bar_height)
+        val lp = FrameLayout.LayoutParams(MATCH_PARENT, height)
+        lp.gravity = Gravity.END or Gravity.TOP
         statusBarWindowController.addViewToWindow(animationWindowView, lp)
         animationWindowView.clipToPadding = false
         animationWindowView.clipChildren = false
+
+        // Use contentInsetsProvider rather than configuration controller, since we only care
+        // about status bar dimens
+        contentInsetsProvider.addCallback(object : StatusBarContentInsetsChangedListener {
+            override fun onStatusBarContentInsetsChanged() {
+                val newContentArea = contentInsetsProvider
+                    .getStatusBarContentAreaForCurrentRotation()
+                updateDimens(newContentArea)
+
+                // If we are currently animating, we have to re-solve for the chip bounds. If we're
+                // not animating then [prepareChipAnimation] will take care of it for us
+                currentAnimatedView?.let {
+                    updateChipBounds(it, newContentArea)
+                }
+            }
+        })
+    }
+
+    private fun updateDimens(contentArea: Rect) {
+        val lp = animationWindowView.layoutParams as FrameLayout.LayoutParams
+        lp.height = contentArea.height()
+
+        animationWindowView.layoutParams = lp
+    }
+
+    /**
+     * Use the current status bar content area and the current chip's measured size to update
+     * the animation rect and chipBounds. This method can be called at any time and will update
+     * the current animation values properly during e.g. a rotation.
+     */
+    private fun updateChipBounds(chip: BackgroundAnimatableView, contentArea: Rect) {
+        // decide which direction we're animating from, and then set some screen coordinates
+        val chipTop = (contentArea.bottom - chip.view.measuredHeight) / 2
+        val chipBottom = chipTop + chip.view.measuredHeight
+        val chipRight: Int
+        val chipLeft: Int
+
+        when (animationDirection) {
+            LEFT -> {
+                chipRight = contentArea.right
+                chipLeft = contentArea.right - chip.chipWidth
+            }
+            else /* RIGHT */ -> {
+                chipLeft = contentArea.left
+                chipRight = contentArea.left + chip.chipWidth
+            }
+        }
+        chipBounds = Rect(chipLeft, chipTop, chipRight, chipBottom)
+        animRect.set(chipBounds)
     }
 
     private fun layoutParamsDefault(marginEnd: Int): FrameLayout.LayoutParams =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventCoordinator.kt
index 26fd230..23edf17 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventCoordinator.kt
@@ -22,6 +22,9 @@
 import android.provider.DeviceConfig.NAMESPACE_PRIVACY
 import com.android.systemui.R
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor
+import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.State
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
 import com.android.systemui.privacy.PrivacyChipBuilder
@@ -30,29 +33,45 @@
 import com.android.systemui.statusbar.policy.BatteryController
 import com.android.systemui.util.time.SystemClock
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
 
 /**
- * Listens for system events (battery, privacy, connectivity) and allows listeners
- * to show status bar animations when they happen
+ * Listens for system events (battery, privacy, connectivity) and allows listeners to show status
+ * bar animations when they happen
  */
 @SysUISingleton
-class SystemEventCoordinator @Inject constructor(
+class SystemEventCoordinator
+@Inject
+constructor(
     private val systemClock: SystemClock,
     private val batteryController: BatteryController,
     private val privacyController: PrivacyItemController,
     private val context: Context,
-    private val featureFlags: FeatureFlags
+    private val featureFlags: FeatureFlags,
+    @Application private val appScope: CoroutineScope,
+    connectedDisplayInteractor: ConnectedDisplayInteractor
 ) {
+    private val onDisplayConnectedFlow =
+        connectedDisplayInteractor.connectedDisplayState
+                .filter { it != State.DISCONNECTED }
+
+    private var connectedDisplayCollectionJob: Job? = null
     private lateinit var scheduler: SystemStatusAnimationScheduler
 
     fun startObserving() {
         batteryController.addCallback(batteryStateListener)
         privacyController.addCallback(privacyStateListener)
+        startConnectedDisplayCollection()
     }
 
     fun stopObserving() {
         batteryController.removeCallback(batteryStateListener)
         privacyController.removeCallback(privacyStateListener)
+        connectedDisplayCollectionJob?.cancel()
     }
 
     fun attachScheduler(s: SystemStatusAnimationScheduler) {
@@ -80,6 +99,13 @@
         scheduler.onStatusEvent(event)
     }
 
+    private fun startConnectedDisplayCollection() {
+        connectedDisplayCollectionJob =
+                onDisplayConnectedFlow
+                        .onEach { scheduler.onStatusEvent(ConnectedDisplayEvent()) }
+                        .launchIn(appScope)
+    }
+
     private val batteryStateListener = object : BatteryController.BatteryStateChangeCallback {
         private var plugged = false
         private var stateKnown = false
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt
index 56ea703..6fc715a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt
@@ -18,7 +18,6 @@
 
 import android.os.Process
 import android.provider.DeviceConfig
-import android.util.Log
 import androidx.core.animation.Animator
 import androidx.core.animation.AnimatorListenerAdapter
 import androidx.core.animation.AnimatorSet
@@ -69,7 +68,8 @@
     private val statusBarWindowController: StatusBarWindowController,
     dumpManager: DumpManager,
     private val systemClock: SystemClock,
-    @Application private val coroutineScope: CoroutineScope
+    @Application private val coroutineScope: CoroutineScope,
+    private val logger: SystemStatusAnimationSchedulerLogger?
 ) : SystemStatusAnimationScheduler {
 
     companion object {
@@ -121,6 +121,10 @@
                     }
                 }
         }
+
+        coroutineScope.launch {
+            animationState.collect { logger?.logAnimationStateUpdate(it) }
+        }
     }
 
     @SystemAnimationState override fun getAnimationState(): Int = animationState.value
@@ -140,32 +144,17 @@
         ) {
             // a event can only be scheduled if no other event is in progress or it has a higher
             // priority. If a persistent dot is currently displayed, don't schedule the event.
-            if (DEBUG) {
-                Log.d(TAG, "scheduling event $event")
-            }
-
+            logger?.logScheduleEvent(event)
             scheduleEvent(event)
         } else if (currentlyDisplayedEvent?.shouldUpdateFromEvent(event) == true) {
-            if (DEBUG) {
-                Log.d(
-                    TAG,
-                    "updating current event from: $event. animationState=${animationState.value}"
-                )
-            }
+            logger?.logUpdateEvent(event, animationState.value)
             currentlyDisplayedEvent?.updateFromEvent(event)
             if (event.forceVisible) hasPersistentDot = true
         } else if (scheduledEvent.value?.shouldUpdateFromEvent(event) == true) {
-            if (DEBUG) {
-                Log.d(
-                    TAG,
-                    "updating scheduled event from: $event. animationState=${animationState.value}"
-                )
-            }
+            logger?.logUpdateEvent(event, animationState.value)
             scheduledEvent.value?.updateFromEvent(event)
         } else {
-            if (DEBUG) {
-                Log.d(TAG, "ignoring event $event")
-            }
+            logger?.logIgnoreEvent(event)
         }
     }
 
@@ -356,6 +345,7 @@
     }
 
     private fun notifyTransitionToPersistentDot(): Animator? {
+        logger?.logTransitionToPersistentDotCallbackInvoked()
         val anims: List<Animator> =
             listeners.mapNotNull {
                 it.onSystemStatusAnimationTransitionToPersistentDot(
@@ -373,6 +363,7 @@
 
     private fun notifyHidePersistentDot(): Animator? {
         Assert.isMainThread()
+        logger?.logHidePersistentDotCallbackInvoked()
         val anims: List<Animator> = listeners.mapNotNull { it.onHidePersistentDot() }
 
         if (animationState.value == SHOWING_PERSISTENT_DOT) {
@@ -424,5 +415,4 @@
     }
 }
 
-private const val DEBUG = false
 private const val TAG = "SystemStatusAnimationSchedulerImpl"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerLog.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerLog.kt
new file mode 100644
index 0000000..4ac94a6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerLog.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.events
+
+import javax.inject.Qualifier
+
+/** Logs for the SystemStatusAnimationScheduler. */
+@Qualifier
+@MustBeDocumented
+@kotlin.annotation.Retention(AnnotationRetention.RUNTIME)
+annotation class SystemStatusAnimationSchedulerLog
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerLogger.kt
new file mode 100644
index 0000000..22b0b69
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerLogger.kt
@@ -0,0 +1,92 @@
+package com.android.systemui.statusbar.events
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+import javax.inject.Inject
+
+/** Logs for the SystemStatusAnimationScheduler. */
+@SysUISingleton
+class SystemStatusAnimationSchedulerLogger
+@Inject
+constructor(
+    @SystemStatusAnimationSchedulerLog private val logBuffer: LogBuffer,
+) {
+
+    fun logScheduleEvent(event: StatusEvent) {
+        logBuffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            {
+                str1 = event.javaClass.simpleName
+                int1 = event.priority
+                bool1 = event.forceVisible
+                bool2 = event.showAnimation
+            },
+            { "Scheduling event: $str1(forceVisible=$bool1, priority=$int1, showAnimation=$bool2)" }
+        )
+    }
+
+    fun logUpdateEvent(event: StatusEvent, @SystemAnimationState animationState: Int) {
+        logBuffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            {
+                str1 = event.javaClass.simpleName
+                int1 = event.priority
+                bool1 = event.forceVisible
+                bool2 = event.showAnimation
+                int2 = animationState
+            },
+            {
+                "Updating current event from: $str1(forceVisible=$bool1, priority=$int1, " +
+                    "showAnimation=$bool2), animationState=${animationState.name()}"
+            }
+        )
+    }
+
+    fun logIgnoreEvent(event: StatusEvent) {
+        logBuffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            {
+                str1 = event.javaClass.simpleName
+                int1 = event.priority
+                bool1 = event.forceVisible
+                bool2 = event.showAnimation
+            },
+            { "Ignore event: $str1(forceVisible=$bool1, priority=$int1, showAnimation=$bool2)" }
+        )
+    }
+
+    fun logHidePersistentDotCallbackInvoked() {
+        logBuffer.log(TAG, LogLevel.DEBUG, "Hide persistent dot callback invoked")
+    }
+
+    fun logTransitionToPersistentDotCallbackInvoked() {
+        logBuffer.log(TAG, LogLevel.DEBUG, "Transition to persistent dot callback invoked")
+    }
+
+    fun logAnimationStateUpdate(@SystemAnimationState animationState: Int) {
+        logBuffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            { int1 = animationState },
+            { "AnimationState update: ${int1.name()}" }
+        )
+        animationState.name()
+    }
+
+    private fun @receiver:SystemAnimationState Int.name() =
+        when (this) {
+            IDLE -> "IDLE"
+            ANIMATION_QUEUED -> "ANIMATION_QUEUED"
+            ANIMATING_IN -> "ANIMATING_IN"
+            RUNNING_CHIP_ANIM -> "RUNNING_CHIP_ANIM"
+            ANIMATING_OUT -> "ANIMATING_OUT"
+            SHOWING_PERSISTENT_DOT -> "SHOWING_PERSISTENT_DOT"
+            else -> "UNKNOWN_ANIMATION_STATE"
+        }
+}
+
+private const val TAG = "SystemStatusAnimationSchedulerLog"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeUpGestureLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeUpGestureLogger.kt
index a67c26c..96725fc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeUpGestureLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeUpGestureLogger.kt
@@ -19,7 +19,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.log.dagger.SwipeUpLog
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import javax.inject.Inject
 
 /** Log messages for [SwipeUpGestureHandler]. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
index 6c1dc8c..94251ff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
@@ -78,7 +78,7 @@
 constructor(
         private val context: Context,
         private val featureFlags: FeatureFlags,
-        private val smartspaceManager: SmartspaceManager,
+        private val smartspaceManager: SmartspaceManager?,
         private val activityStarter: ActivityStarter,
         private val falsingManager: FalsingManager,
         private val systemClock: SystemClock,
@@ -139,6 +139,19 @@
 
             updateTextColorFromWallpaper()
             statusBarStateListener.onDozeAmountChanged(0f, statusBarStateController.dozeAmount)
+
+            if (regionSamplingEnabled && (!regionSamplers.containsKey(v))) {
+                var regionSampler = RegionSampler(
+                        v as View,
+                        uiExecutor,
+                        bgExecutor,
+                        regionSamplingEnabled,
+                        isLockscreen = true,
+                ) { updateTextColorFromRegionSampler() }
+                initializeTextColors(regionSampler)
+                regionSamplers[v] = regionSampler
+                regionSampler.startRegionSampler()
+            }
         }
 
         override fun onViewDetachedFromWindow(v: View) {
@@ -165,7 +178,16 @@
                     now.isBefore(Instant.ofEpochMilli(t.expiryTimeMillis))
         }
         if (weatherTarget != null) {
-            val weatherData = WeatherData.fromBundle(weatherTarget.baseAction.extras)
+            val clickIntent = weatherTarget.headerAction?.intent
+            val weatherData = WeatherData.fromBundle(weatherTarget.baseAction.extras, { v ->
+                if (!falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+                    activityStarter.startActivity(
+                        clickIntent,
+                        true, /* dismissShade */
+                        null,
+                        false)
+                }
+            })
             if (weatherData != null) {
                 keyguardUpdateMonitor.sendWeatherData(weatherData)
             }
@@ -173,23 +195,6 @@
 
         val filteredTargets = targets.filter(::filterSmartspaceTarget)
         plugin?.onTargetsAvailable(filteredTargets)
-        if (!isRegionSamplersCreated) {
-            for (v in smartspaceViews) {
-                if (regionSamplingEnabled) {
-                    var regionSampler = RegionSampler(
-                        v as View,
-                        uiExecutor,
-                        bgExecutor,
-                        regionSamplingEnabled,
-                        isLockscreen = true,
-                    ) { updateTextColorFromRegionSampler() }
-                    initializeTextColors(regionSampler)
-                    regionSamplers[v] = regionSampler
-                    regionSampler.startRegionSampler()
-                }
-            }
-            isRegionSamplersCreated = true
-        }
     }
 
     private val userTrackerCallback = object : UserTracker.Callback {
@@ -382,6 +387,7 @@
     }
 
     private fun connectSession() {
+        if (smartspaceManager == null) return
         if (datePlugin == null && weatherPlugin == null && plugin == null) return
         if (session != null || smartspaceViews.isEmpty()) {
             return
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
index 5f28ecb..577ad20 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
@@ -16,27 +16,21 @@
 
 package com.android.systemui.statusbar.notification
 
-import android.content.Context
 import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.FlagResolver
 import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
 import javax.inject.Inject
 
-class NotifPipelineFlags @Inject constructor(
-    val context: Context,
-    val featureFlags: FeatureFlags,
-    val sysPropFlags: FlagResolver,
+class NotifPipelineFlags
+@Inject
+constructor(
+    private val featureFlags: FeatureFlags,
+    private val sysPropFlags: FlagResolver,
 ) {
     fun isDevLoggingEnabled(): Boolean =
         featureFlags.isEnabled(Flags.NOTIFICATION_PIPELINE_DEVELOPER_LOGGING)
 
     fun allowDismissOngoing(): Boolean =
-            sysPropFlags.isEnabled(NotificationFlags.ALLOW_DISMISS_ONGOING)
-
-    fun isOtpRedactionEnabled(): Boolean =
-            sysPropFlags.isEnabled(NotificationFlags.OTP_REDACTION)
-
-    val isNoHunForOldWhenEnabled: Boolean
-        get() = featureFlags.isEnabled(Flags.NO_HUN_FOR_OLD_WHEN)
+        sysPropFlags.isEnabled(NotificationFlags.ALLOW_DISMISS_ONGOING)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java
index bab553e..d10fac6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java
@@ -17,15 +17,14 @@
 
 import android.app.Notification;
 import android.os.PowerManager;
-import android.os.SystemClock;
 import android.service.notification.StatusBarNotification;
 import android.util.Log;
 import android.view.View;
 
 import com.android.systemui.DejankUtils;
+import com.android.systemui.power.domain.interactor.PowerInteractor;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import com.android.systemui.statusbar.phone.CentralSurfaces;
 import com.android.wm.shell.bubbles.Bubbles;
 
 import java.util.Optional;
@@ -40,7 +39,7 @@
     private static final String TAG = "NotificationClicker";
 
     private final NotificationClickerLogger mLogger;
-    private final Optional<CentralSurfaces> mCentralSurfacesOptional;
+    private final PowerInteractor mPowerInteractor;
     private final Optional<Bubbles> mBubblesOptional;
     private final NotificationActivityStarter mNotificationActivityStarter;
 
@@ -54,11 +53,11 @@
 
     private NotificationClicker(
             NotificationClickerLogger logger,
-            Optional<CentralSurfaces> centralSurfacesOptional,
+            PowerInteractor powerInteractor,
             Optional<Bubbles> bubblesOptional,
             NotificationActivityStarter notificationActivityStarter) {
         mLogger = logger;
-        mCentralSurfacesOptional = centralSurfacesOptional;
+        mPowerInteractor = powerInteractor;
         mBubblesOptional = bubblesOptional;
         mNotificationActivityStarter = notificationActivityStarter;
     }
@@ -70,9 +69,7 @@
             return;
         }
 
-        mCentralSurfacesOptional.ifPresent(centralSurfaces -> centralSurfaces.wakeUpIfDozing(
-                SystemClock.uptimeMillis(), "NOTIFICATION_CLICK",
-                PowerManager.WAKE_REASON_GESTURE));
+        mPowerInteractor.wakeUpIfDozing("NOTIFICATION_CLICK", PowerManager.WAKE_REASON_GESTURE);
 
         final ExpandableNotificationRow row = (ExpandableNotificationRow) v;
         final NotificationEntry entry = row.getEntry();
@@ -131,21 +128,22 @@
     /** Daggerized builder for NotificationClicker. */
     public static class Builder {
         private final NotificationClickerLogger mLogger;
+        private final PowerInteractor mPowerInteractor;
 
         @Inject
-        public Builder(NotificationClickerLogger logger) {
+        public Builder(NotificationClickerLogger logger, PowerInteractor powerInteractor) {
             mLogger = logger;
+            mPowerInteractor = powerInteractor;
         }
 
         /** Builds an instance. */
         public NotificationClicker build(
-                Optional<CentralSurfaces> centralSurfacesOptional,
                 Optional<Bubbles> bubblesOptional,
                 NotificationActivityStarter notificationActivityStarter
         ) {
             return new NotificationClicker(
                     mLogger,
-                    centralSurfacesOptional,
+                    mPowerInteractor,
                     bubblesOptional,
                     notificationActivityStarter);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClickerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClickerLogger.kt
index a3a72d9..cea2b59 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClickerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClickerLogger.kt
@@ -18,7 +18,7 @@
 
 import com.android.systemui.log.dagger.NotifInteractionLog
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import javax.inject.Inject
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLogger.kt
index f7679ed..502e1d9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLogger.kt
@@ -14,7 +14,7 @@
 package com.android.systemui.statusbar.notification
 
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel.DEBUG
+import com.android.systemui.log.core.LogLevel.DEBUG
 import com.android.systemui.log.dagger.NotificationLockscreenLog
 import com.android.systemui.statusbar.StatusBarState
 import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/RemoteInputControllerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/RemoteInputControllerLogger.kt
index 487a5f8..7809eaa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/RemoteInputControllerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/RemoteInputControllerLogger.kt
@@ -18,7 +18,7 @@
 
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel.DEBUG
+import com.android.systemui.log.core.LogLevel.DEBUG
 import com.android.systemui.log.dagger.NotificationRemoteInputLog
 import javax.inject.Inject
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt
index 212f2c215..1cf9c1e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt
@@ -3,7 +3,10 @@
 import android.util.FloatProperty
 import android.view.View
 import androidx.annotation.FloatRange
+import com.android.systemui.Dependency
 import com.android.systemui.R
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
 import com.android.systemui.statusbar.notification.stack.AnimationProperties
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator
 import kotlin.math.abs
@@ -20,6 +23,8 @@
     /** Properties required for a Roundable */
     val roundableState: RoundableState
 
+    val clipHeight: Int
+
     /** Current top roundness */
     @get:FloatRange(from = 0.0, to = 1.0)
     @JvmDefault
@@ -40,12 +45,16 @@
     /** Current top corner in pixel, based on [topRoundness] and [maxRadius] */
     @JvmDefault
     val topCornerRadius: Float
-        get() = topRoundness * maxRadius
+        get() =
+            if (roundableState.newHeadsUpAnimFlagEnabled) roundableState.topCornerRadius
+            else topRoundness * maxRadius
 
     /** Current bottom corner in pixel, based on [bottomRoundness] and [maxRadius] */
     @JvmDefault
     val bottomCornerRadius: Float
-        get() = bottomRoundness * maxRadius
+        get() =
+            if (roundableState.newHeadsUpAnimFlagEnabled) roundableState.bottomCornerRadius
+            else bottomRoundness * maxRadius
 
     /** Get and update the current radii */
     @JvmDefault
@@ -320,14 +329,20 @@
  * @param roundable Target of the radius animation
  * @param maxRadius Max corner radius in pixels
  */
-class RoundableState(
+class RoundableState
+@JvmOverloads
+constructor(
     internal val targetView: View,
     private val roundable: Roundable,
     maxRadius: Float,
+    private val featureFlags: FeatureFlags = Dependency.get(FeatureFlags::class.java)
 ) {
     internal var maxRadius = maxRadius
         private set
 
+    internal val newHeadsUpAnimFlagEnabled
+        get() = featureFlags.isEnabled(Flags.IMPROVED_HUN_ANIMATIONS)
+
     /** Animatable for top roundness */
     private val topAnimatable = topAnimatable(roundable)
 
@@ -344,6 +359,41 @@
     internal var bottomRoundness = 0f
         private set
 
+    internal val topCornerRadius: Float
+        get() {
+            val height = roundable.clipHeight
+            val topRadius = topRoundness * maxRadius
+            val bottomRadius = bottomRoundness * maxRadius
+
+            if (height == 0) {
+                return 0f
+            } else if (topRadius + bottomRadius > height) {
+                // The sum of top and bottom corner radii should be at max the clipped height
+                val overShoot = topRadius + bottomRadius - height
+                return topRadius - (overShoot * topRoundness / (topRoundness + bottomRoundness))
+            }
+
+            return topRadius
+        }
+
+    internal val bottomCornerRadius: Float
+        get() {
+            val height = roundable.clipHeight
+            val topRadius = topRoundness * maxRadius
+            val bottomRadius = bottomRoundness * maxRadius
+
+            if (height == 0) {
+                return 0f
+            } else if (topRadius + bottomRadius > height) {
+                // The sum of top and bottom corner radii should be at max the clipped height
+                val overShoot = topRadius + bottomRadius - height
+                return bottomRadius -
+                    (overShoot * bottomRoundness / (topRoundness + bottomRoundness))
+            }
+
+            return bottomRadius
+        }
+
     /** Last requested top roundness associated by [SourceType] */
     internal val topRoundnessMap = mutableMapOf<SourceType, Float>()
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
index d2c6d4b..5c72731 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
@@ -61,6 +61,7 @@
 import android.service.notification.NotificationListenerService.RankingMap;
 import android.service.notification.StatusBarNotification;
 import android.util.ArrayMap;
+import android.util.Log;
 import android.util.Pair;
 
 import androidx.annotation.NonNull;
@@ -274,10 +275,7 @@
         Assert.isMainThread();
         checkForReentrantCall();
 
-        // TODO (b/206842750): This method is called from (silent) clear all and non-clear all
-        // contexts and should be checking the NO_CLEAR flag, rather than depending on NSSL
-        // to pass in a properly filtered list of notifications
-
+        final int entryCount = entriesToDismiss.size();
         final List<NotificationEntry> entriesToLocallyDismiss = new ArrayList<>();
         for (int i = 0; i < entriesToDismiss.size(); i++) {
             NotificationEntry entry = entriesToDismiss.get(i).first;
@@ -286,28 +284,34 @@
             requireNonNull(stats);
             NotificationEntry storedEntry = mNotificationSet.get(entry.getKey());
             if (storedEntry == null) {
-                mLogger.logNonExistentNotifDismissed(entry);
+                mLogger.logDismissNonExistentNotif(entry, i, entryCount);
                 continue;
             }
             if (entry != storedEntry) {
                 throw mEulogizer.record(
                         new IllegalStateException("Invalid entry: "
                                 + "different stored and dismissed entries for " + logKey(entry)
+                                + " (" + i + "/" + entryCount + ")"
+                                + " dismissed=@" + Integer.toHexString(entry.hashCode())
                                 + " stored=@" + Integer.toHexString(storedEntry.hashCode())));
             }
 
             if (entry.getDismissState() == DISMISSED) {
+                mLogger.logDismissAlreadyDismissedNotif(entry, i, entryCount);
                 continue;
+            } else if (entry.getDismissState() == PARENT_DISMISSED) {
+                mLogger.logDismissAlreadyParentDismissedNotif(entry, i, entryCount);
             }
 
             updateDismissInterceptors(entry);
             if (isDismissIntercepted(entry)) {
-                mLogger.logNotifDismissedIntercepted(entry);
+                mLogger.logNotifDismissedIntercepted(entry, i, entryCount);
                 continue;
             }
 
             entriesToLocallyDismiss.add(entry);
             if (!entry.isCanceled()) {
+                int finalI = i;
                 // send message to system server if this notification hasn't already been cancelled
                 mBgExecutor.execute(() -> {
                     try {
@@ -320,7 +324,7 @@
                                 stats.notificationVisibility);
                     } catch (RemoteException e) {
                         // system process is dead if we're here.
-                        mLogger.logRemoteExceptionOnNotificationClear(entry, e);
+                        mLogger.logRemoteExceptionOnNotificationClear(entry, finalI, entryCount, e);
                     }
                 });
             }
@@ -357,14 +361,16 @@
         }
 
         final List<NotificationEntry> entries = new ArrayList<>(getAllNotifs());
+        final int initialEntryCount = entries.size();
         for (int i = entries.size() - 1; i >= 0; i--) {
             NotificationEntry entry = entries.get(i);
+
             if (!shouldDismissOnClearAll(entry, userId)) {
                 // system server won't be removing these notifications, but we still give dismiss
                 // interceptors the chance to filter the notification
                 updateDismissInterceptors(entry);
                 if (isDismissIntercepted(entry)) {
-                    mLogger.logNotifClearAllDismissalIntercepted(entry);
+                    mLogger.logNotifClearAllDismissalIntercepted(entry, i, initialEntryCount);
                 }
                 entries.remove(i);
             }
@@ -380,25 +386,46 @@
      */
     private void locallyDismissNotifications(List<NotificationEntry> entries) {
         final List<NotificationEntry> canceledEntries = new ArrayList<>();
-
+        final int entryCount = entries.size();
         for (int i = 0; i < entries.size(); i++) {
             NotificationEntry entry = entries.get(i);
 
+            final NotificationEntry storedEntry = mNotificationSet.get(entry.getKey());
+            if (storedEntry == null) {
+                mLogger.logLocallyDismissNonExistentNotif(entry, i, entryCount);
+            } else if (storedEntry != entry) {
+                mLogger.logLocallyDismissMismatchedEntry(entry, i, entryCount, storedEntry);
+            }
+
+            if (entry.getDismissState() == DISMISSED) {
+                mLogger.logLocallyDismissAlreadyDismissedNotif(entry, i, entryCount);
+            } else if (entry.getDismissState() == PARENT_DISMISSED) {
+                mLogger.logLocallyDismissAlreadyParentDismissedNotif(entry, i, entryCount);
+            }
+
             entry.setDismissState(DISMISSED);
-            mLogger.logNotifDismissed(entry);
+            mLogger.logLocallyDismissed(entry, i, entryCount);
 
             if (entry.isCanceled()) {
                 canceledEntries.add(entry);
-            } else {
-                // Mark any children as dismissed as system server will auto-dismiss them as well
-                if (entry.getSbn().getNotification().isGroupSummary()) {
-                    for (NotificationEntry otherEntry : mNotificationSet.values()) {
-                        if (shouldAutoDismissChildren(otherEntry, entry.getSbn().getGroupKey())) {
-                            otherEntry.setDismissState(PARENT_DISMISSED);
-                            mLogger.logChildDismissed(otherEntry);
-                            if (otherEntry.isCanceled()) {
-                                canceledEntries.add(otherEntry);
-                            }
+                continue;
+            }
+
+            // Mark any children as dismissed as system server will auto-dismiss them as well
+            if (entry.getSbn().getNotification().isGroupSummary()) {
+                for (NotificationEntry otherEntry : mNotificationSet.values()) {
+                    if (shouldAutoDismissChildren(otherEntry, entry.getSbn().getGroupKey())) {
+                        if (otherEntry.getDismissState() == DISMISSED) {
+                            mLogger.logLocallyDismissAlreadyDismissedChild(
+                                    otherEntry, entry, i, entryCount);
+                        } else if (otherEntry.getDismissState() == PARENT_DISMISSED) {
+                            mLogger.logLocallyDismissAlreadyParentDismissedChild(
+                                    otherEntry, entry, i, entryCount);
+                        }
+                        otherEntry.setDismissState(PARENT_DISMISSED);
+                        mLogger.logLocallyDismissedChild(otherEntry, entry, i, entryCount);
+                        if (otherEntry.isCanceled()) {
+                            canceledEntries.add(otherEntry);
                         }
                     }
                 }
@@ -408,7 +435,7 @@
         // Immediately remove any dismissed notifs that have already been canceled by system server
         // (probably due to being lifetime-extended up until this point).
         for (NotificationEntry canceledEntry : canceledEntries) {
-            mLogger.logDismissOnAlreadyCanceledEntry(canceledEntry);
+            mLogger.logLocallyDismissedAlreadyCanceledEntry(canceledEntry);
             tryRemoveNotification(canceledEntry);
         }
     }
@@ -517,10 +544,16 @@
      * @return True if the notification was removed, false otherwise.
      */
     private boolean tryRemoveNotification(NotificationEntry entry) {
-        if (mNotificationSet.get(entry.getKey()) != entry) {
+        final NotificationEntry storedEntry = mNotificationSet.get(entry.getKey());
+        if (storedEntry == null) {
+            Log.wtf(TAG, "TRY REMOVE non-existent notification " + logKey(entry));
+            return false;
+        } else if (storedEntry != entry) {
             throw mEulogizer.record(
-                    new IllegalStateException("No notification to remove with key "
-                            + logKey(entry)));
+                    new IllegalStateException("Mismatched stored and tryRemoved entries"
+                            + " for key " + logKey(entry) + ":"
+                            + " stored=@" + Integer.toHexString(storedEntry.hashCode())
+                            + " tryRemoved=@" + Integer.toHexString(entry.hashCode())));
         }
 
         if (!entry.isCanceled()) {
@@ -734,14 +767,16 @@
     }
 
     private void cancelLocalDismissal(NotificationEntry entry) {
-        if (entry.getDismissState() != NOT_DISMISSED) {
-            entry.setDismissState(NOT_DISMISSED);
-            if (entry.getSbn().getNotification().isGroupSummary()) {
-                for (NotificationEntry otherEntry : mNotificationSet.values()) {
-                    if (otherEntry.getSbn().getGroupKey().equals(entry.getSbn().getGroupKey())
-                            && otherEntry.getDismissState() == PARENT_DISMISSED) {
-                        otherEntry.setDismissState(NOT_DISMISSED);
-                    }
+        if (entry.getDismissState() == NOT_DISMISSED) {
+            mLogger.logCancelLocalDismissalNotDismissedNotif(entry);
+            return;
+        }
+        entry.setDismissState(NOT_DISMISSED);
+        if (entry.getSbn().getNotification().isGroupSummary()) {
+            for (NotificationEntry otherEntry : mNotificationSet.values()) {
+                if (otherEntry.getSbn().getGroupKey().equals(entry.getSbn().getGroupKey())
+                        && otherEntry.getDismissState() == PARENT_DISMISSED) {
+                    otherEntry.setDismissState(NOT_DISMISSED);
                 }
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerLogger.kt
index 39d0833..0ab348d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerLogger.kt
@@ -18,7 +18,7 @@
 
 import com.android.systemui.log.dagger.NotificationLog
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import javax.inject.Inject
 
 class GroupCoalescerLogger @Inject constructor(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DreamCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DreamCoordinator.kt
new file mode 100644
index 0000000..d268e35
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DreamCoordinator.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection.coordinator
+
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.SysuiStatusBarStateController
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+
+/**
+ * Filter out notifications on the lockscreen if the lockscreen hosted dream is active. If the user
+ * stops dreaming, pulls the shade down or unlocks the device, then the notifications are unhidden.
+ */
+@CoordinatorScope
+class DreamCoordinator
+@Inject
+constructor(
+    private val statusBarStateController: SysuiStatusBarStateController,
+    @Application private val scope: CoroutineScope,
+    private val keyguardRepository: KeyguardRepository,
+) : Coordinator {
+    private var isOnKeyguard = false
+    private var isLockscreenHostedDream = false
+
+    override fun attach(pipeline: NotifPipeline) {
+        pipeline.addPreGroupFilter(filter)
+        statusBarStateController.addCallback(statusBarStateListener)
+        scope.launch { attachFilterOnDreamingStateChange() }
+        recordStatusBarState(statusBarStateController.state)
+    }
+
+    private val filter =
+        object : NotifFilter("LockscreenHostedDreamFilter") {
+            var isFiltering = false
+            override fun shouldFilterOut(entry: NotificationEntry, now: Long): Boolean {
+                return isFiltering
+            }
+            inline fun update(msg: () -> String) {
+                val wasFiltering = isFiltering
+                isFiltering = isLockscreenHostedDream && isOnKeyguard
+                if (wasFiltering != isFiltering) {
+                    invalidateList(msg())
+                }
+            }
+        }
+
+    private val statusBarStateListener =
+        object : StatusBarStateController.StateListener {
+            override fun onStateChanged(newState: Int) {
+                recordStatusBarState(newState)
+            }
+        }
+
+    private suspend fun attachFilterOnDreamingStateChange() {
+        keyguardRepository.isActiveDreamLockscreenHosted.collect { isDreaming ->
+            recordDreamingState(isDreaming)
+        }
+    }
+
+    private fun recordStatusBarState(newState: Int) {
+        isOnKeyguard = newState == StatusBarState.KEYGUARD
+        filter.update { "recordStatusBarState: " + StatusBarState.toString(newState) }
+    }
+
+    private fun recordDreamingState(isDreaming: Boolean) {
+        isLockscreenHostedDream = isDreaming
+        filter.update { "recordLockscreenHostedDreamState: $isDreaming" }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorLogger.kt
index 79c63e6..bd1141e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorLogger.kt
@@ -2,7 +2,7 @@
 
 import com.android.systemui.log.dagger.NotificationLog
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import com.android.systemui.statusbar.notification.row.NotificationGuts
 import javax.inject.Inject
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt
index e17ce5c..496fb83 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt
@@ -4,7 +4,7 @@
 
 import com.android.systemui.log.dagger.NotificationHeadsUpLog
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import javax.inject.Inject
 
 private const val TAG = "HeadsUpCoordinator"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt
index 2fa070c..07eb8a00 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt
@@ -28,12 +28,9 @@
 import com.android.systemui.keyguard.data.repository.KeyguardRepository
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 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.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.StatusBarState
 import com.android.systemui.statusbar.expansionChanges
-import com.android.systemui.statusbar.notification.NotifPipelineFlags
 import com.android.systemui.statusbar.notification.collection.NotifPipeline
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
@@ -50,30 +47,29 @@
 import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
 import java.io.PrintWriter
 import javax.inject.Inject
-import kotlin.time.Duration
 import kotlin.time.Duration.Companion.seconds
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.Job
 import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.collectLatest
 import kotlinx.coroutines.flow.conflate
 import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.emitAll
-import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.flow.onStart
-import kotlinx.coroutines.flow.transformLatest
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.yield
 
 /**
  * Filters low priority and privacy-sensitive notifications from the lockscreen, and hides section
- * headers on the lockscreen.
+ * headers on the lockscreen. If enabled, it will also track and hide seen notifications on the
+ * lockscreen.
  */
 @CoordinatorScope
 class KeyguardCoordinator
@@ -86,7 +82,6 @@
     private val keyguardRepository: KeyguardRepository,
     private val keyguardTransitionRepository: KeyguardTransitionRepository,
     private val logger: KeyguardCoordinatorLogger,
-    private val notifPipelineFlags: NotifPipelineFlags,
     @Application private val scope: CoroutineScope,
     private val sectionHeaderVisibilityProvider: SectionHeaderVisibilityProvider,
     private val secureSettings: SecureSettings,
@@ -95,6 +90,8 @@
 ) : Coordinator, Dumpable {
 
     private val unseenNotifications = mutableSetOf<NotificationEntry>()
+    private val unseenEntryAdded = MutableSharedFlow<NotificationEntry>(extraBufferCapacity = 1)
+    private val unseenEntryRemoved = MutableSharedFlow<NotificationEntry>(extraBufferCapacity = 1)
     private var unseenFilterEnabled = false
 
     override fun attach(pipeline: NotifPipeline) {
@@ -109,79 +106,130 @@
     private fun attachUnseenFilter(pipeline: NotifPipeline) {
         pipeline.addFinalizeFilter(unseenNotifFilter)
         pipeline.addCollectionListener(collectionListener)
-        scope.launch { trackUnseenNotificationsWhileUnlocked() }
-        scope.launch { invalidateWhenUnseenSettingChanges() }
+        scope.launch { trackUnseenFilterSettingChanges() }
         dumpManager.registerDumpable(this)
     }
 
-    private suspend fun trackUnseenNotificationsWhileUnlocked() {
-        // Whether or not we're actively tracking unseen notifications to mark them as seen when
-        // appropriate.
-        val isTrackingUnseen: Flow<Boolean> =
-            keyguardRepository.isKeyguardShowing
-                // transformLatest so that we can cancel listening to keyguard transitions once
-                // isKeyguardShowing changes (after a successful transition to the keyguard).
-                .transformLatest { isShowing ->
-                    if (isShowing) {
-                        // If the keyguard is showing, we're not tracking unseen.
-                        emit(false)
-                    } else {
-                        // If the keyguard stops showing, then start tracking unseen notifications.
-                        emit(true)
-                        // If the screen is turning off, stop tracking, but if that transition is
-                        // cancelled, then start again.
-                        emitAll(
-                            keyguardTransitionRepository.transitions.map { step ->
-                                !step.isScreenTurningOff
-                            }
-                        )
-                    }
-                }
-                // Prevent double emit of `false` caused by transition to AOD, followed by keyguard
-                // showing
+    private suspend fun trackSeenNotifications() {
+        // Whether or not keyguard is visible (or occluded).
+        val isKeyguardPresent: Flow<Boolean> =
+            keyguardTransitionRepository.transitions
+                .map { step -> step.to != KeyguardState.GONE }
                 .distinctUntilChanged()
                 .onEach { trackingUnseen -> logger.logTrackingUnseen(trackingUnseen) }
 
-        // Use collectLatest so that trackUnseenNotifications() is cancelled when the keyguard is
-        // showing again
-        var clearUnseenOnBeginTracking = false
-        isTrackingUnseen.collectLatest { trackingUnseen ->
-            if (!trackingUnseen) {
-                // Wait for the user to spend enough time on the lock screen before clearing unseen
-                // set when unlocked
-                awaitTimeSpentNotDozing(SEEN_TIMEOUT)
-                clearUnseenOnBeginTracking = true
-                logger.logSeenOnLockscreen()
+        // Separately track seen notifications while the device is locked, applying once the device
+        // is unlocked.
+        val notificationsSeenWhileLocked = mutableSetOf<NotificationEntry>()
+
+        // Use [collectLatest] to cancel any running jobs when [trackingUnseen] changes.
+        isKeyguardPresent.collectLatest { isKeyguardPresent: Boolean ->
+            if (isKeyguardPresent) {
+                // Keyguard is not gone, notifications need to be visible for a certain threshold
+                // before being marked as seen
+                trackSeenNotificationsWhileLocked(notificationsSeenWhileLocked)
             } else {
-                if (clearUnseenOnBeginTracking) {
-                    clearUnseenOnBeginTracking = false
-                    logger.logAllMarkedSeenOnUnlock()
-                    unseenNotifications.clear()
+                // Mark all seen-while-locked notifications as seen for real.
+                if (notificationsSeenWhileLocked.isNotEmpty()) {
+                    unseenNotifications.removeAll(notificationsSeenWhileLocked)
+                    logger.logAllMarkedSeenOnUnlock(
+                        seenCount = notificationsSeenWhileLocked.size,
+                        remainingUnseenCount = unseenNotifications.size
+                    )
+                    notificationsSeenWhileLocked.clear()
                 }
                 unseenNotifFilter.invalidateList("keyguard no longer showing")
-                trackUnseenNotifications()
+                // Keyguard is gone, notifications can be immediately marked as seen when they
+                // become visible.
+                trackSeenNotificationsWhileUnlocked()
             }
         }
     }
 
-    private suspend fun awaitTimeSpentNotDozing(duration: Duration) {
-        keyguardRepository.isDozing
-            // Use transformLatest so that the timeout delay is cancelled if the device enters doze,
-            // and is restarted when doze ends.
-            .transformLatest { isDozing ->
-                if (!isDozing) {
-                    delay(duration)
-                    // Signal timeout has completed
-                    emit(Unit)
+    /**
+     * Keep [notificationsSeenWhileLocked] updated to represent which notifications have actually
+     * been "seen" while the device is on the keyguard.
+     */
+    private suspend fun trackSeenNotificationsWhileLocked(
+        notificationsSeenWhileLocked: MutableSet<NotificationEntry>,
+    ) = coroutineScope {
+        // Remove removed notifications from the set
+        launch {
+            unseenEntryRemoved.collect { entry ->
+                if (notificationsSeenWhileLocked.remove(entry)) {
+                    logger.logRemoveSeenOnLockscreen(entry)
                 }
             }
-            // Suspend until the first emission
-            .first()
+        }
+        // Use collectLatest so that the timeout delay is cancelled if the device enters doze, and
+        // is restarted when doze ends.
+        keyguardRepository.isDozing.collectLatest { isDozing ->
+            if (!isDozing) {
+                trackSeenNotificationsWhileLockedAndNotDozing(notificationsSeenWhileLocked)
+            }
+        }
     }
 
-    // Track "unseen" notifications, marking them as seen when either shade is expanded or the
+    /**
+     * Keep [notificationsSeenWhileLocked] updated to represent which notifications have actually
+     * been "seen" while the device is on the keyguard and not dozing. Any new and existing unseen
+     * notifications are not marked as seen until they are visible for the [SEEN_TIMEOUT] duration.
+     */
+    private suspend fun trackSeenNotificationsWhileLockedAndNotDozing(
+        notificationsSeenWhileLocked: MutableSet<NotificationEntry>
+    ) = coroutineScope {
+        // All child tracking jobs will be cancelled automatically when this is cancelled.
+        val trackingJobsByEntry = mutableMapOf<NotificationEntry, Job>()
+
+        /**
+         * Wait for the user to spend enough time on the lock screen before removing notification
+         * from unseen set upon unlock.
+         */
+        suspend fun trackSeenDurationThreshold(entry: NotificationEntry) {
+            if (notificationsSeenWhileLocked.remove(entry)) {
+                logger.logResetSeenOnLockscreen(entry)
+            }
+            delay(SEEN_TIMEOUT)
+            notificationsSeenWhileLocked.add(entry)
+            trackingJobsByEntry.remove(entry)
+            logger.logSeenOnLockscreen(entry)
+        }
+
+        /** Stop any unseen tracking when a notification is removed. */
+        suspend fun stopTrackingRemovedNotifs(): Nothing =
+            unseenEntryRemoved.collect { entry ->
+                trackingJobsByEntry.remove(entry)?.let {
+                    it.cancel()
+                    logger.logStopTrackingLockscreenSeenDuration(entry)
+                }
+            }
+
+        /** Start tracking new notifications when they are posted. */
+        suspend fun trackNewUnseenNotifs(): Nothing = coroutineScope {
+            unseenEntryAdded.collect { entry ->
+                logger.logTrackingLockscreenSeenDuration(entry)
+                // If this is an update, reset the tracking.
+                trackingJobsByEntry[entry]?.let {
+                    it.cancel()
+                    logger.logResetSeenOnLockscreen(entry)
+                }
+                trackingJobsByEntry[entry] = launch { trackSeenDurationThreshold(entry) }
+            }
+        }
+
+        // Start tracking for all notifications that are currently unseen.
+        logger.logTrackingLockscreenSeenDuration(unseenNotifications)
+        unseenNotifications.forEach { entry ->
+            trackingJobsByEntry[entry] = launch { trackSeenDurationThreshold(entry) }
+        }
+
+        launch { trackNewUnseenNotifs() }
+        launch { stopTrackingRemovedNotifs() }
+    }
+
+    // Track "seen" notifications, marking them as such when either shade is expanded or the
     // notification becomes heads up.
-    private suspend fun trackUnseenNotifications() {
+    private suspend fun trackSeenNotificationsWhileUnlocked() {
         coroutineScope {
             launch { clearUnseenNotificationsWhenShadeIsExpanded() }
             launch { markHeadsUpNotificationsAsSeen() }
@@ -212,7 +260,7 @@
         }
     }
 
-    private suspend fun invalidateWhenUnseenSettingChanges() {
+    private suspend fun trackUnseenFilterSettingChanges() {
         secureSettings
             // emit whenever the setting has changed
             .observerFlow(
@@ -228,17 +276,23 @@
                     UserHandle.USER_CURRENT,
                 ) == 1
             }
+            // don't emit anything if nothing has changed
+            .distinctUntilChanged()
             // perform lookups on the bg thread pool
             .flowOn(bgDispatcher)
             // only track the most recent emission, if events are happening faster than they can be
             // consumed
             .conflate()
-            // update local field and invalidate if necessary
-            .collect { setting ->
+            .collectLatest { setting ->
+                // update local field and invalidate if necessary
                 if (setting != unseenFilterEnabled) {
                     unseenFilterEnabled = setting
                     unseenNotifFilter.invalidateList("unseen setting changed")
                 }
+                // if the setting is enabled, then start tracking and filtering unseen notifications
+                if (setting) {
+                    trackSeenNotifications()
+                }
             }
     }
 
@@ -250,6 +304,7 @@
                 ) {
                     logger.logUnseenAdded(entry.key)
                     unseenNotifications.add(entry)
+                    unseenEntryAdded.tryEmit(entry)
                 }
             }
 
@@ -259,12 +314,14 @@
                 ) {
                     logger.logUnseenUpdated(entry.key)
                     unseenNotifications.add(entry)
+                    unseenEntryAdded.tryEmit(entry)
                 }
             }
 
             override fun onEntryRemoved(entry: NotificationEntry, reason: Int) {
                 if (unseenNotifications.remove(entry)) {
                     logger.logUnseenRemoved(entry.key)
+                    unseenEntryRemoved.tryEmit(entry)
                 }
             }
         }
@@ -347,6 +404,3 @@
         private val SEEN_TIMEOUT = 5.seconds
     }
 }
-
-private val TransitionStep.isScreenTurningOff: Boolean
-    get() = transitionState == TransitionState.STARTED && to != KeyguardState.GONE
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorLogger.kt
index 1f8ec34..788659e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorLogger.kt
@@ -17,8 +17,9 @@
 package com.android.systemui.statusbar.notification.collection.coordinator
 
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import com.android.systemui.log.dagger.UnseenNotificationLog
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import javax.inject.Inject
 
 private const val TAG = "KeyguardCoordinator"
@@ -28,11 +29,14 @@
 constructor(
     @UnseenNotificationLog private val buffer: LogBuffer,
 ) {
-    fun logSeenOnLockscreen() =
+    fun logSeenOnLockscreen(entry: NotificationEntry) =
         buffer.log(
             TAG,
             LogLevel.DEBUG,
-            "Notifications on lockscreen will be marked as seen when unlocked."
+            messageInitializer = { str1 = entry.key },
+            messagePrinter = {
+                "Notification [$str1] on lockscreen will be marked as seen when unlocked."
+            },
         )
 
     fun logTrackingUnseen(trackingUnseen: Boolean) =
@@ -43,11 +47,21 @@
             messagePrinter = { "${if (bool1) "Start" else "Stop"} tracking unseen notifications." },
         )
 
-    fun logAllMarkedSeenOnUnlock() =
+    fun logAllMarkedSeenOnUnlock(
+        seenCount: Int,
+        remainingUnseenCount: Int,
+    ) =
         buffer.log(
             TAG,
             LogLevel.DEBUG,
-            "Notifications have been marked as seen now that device is unlocked."
+            messageInitializer = {
+                int1 = seenCount
+                int2 = remainingUnseenCount
+            },
+            messagePrinter = {
+                "$int1 Notifications have been marked as seen now that device is unlocked. " +
+                    "$int2 notifications remain unseen."
+            },
         )
 
     fun logShadeExpanded() =
@@ -96,4 +110,60 @@
             messageInitializer = { str1 = key },
             messagePrinter = { "Unseen notif has become heads up: $str1" },
         )
+
+    fun logTrackingLockscreenSeenDuration(unseenNotifications: Set<NotificationEntry>) {
+        buffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            messageInitializer = {
+                str1 = unseenNotifications.joinToString { it.key }
+                int1 = unseenNotifications.size
+            },
+            messagePrinter = {
+                "Tracking $int1 unseen notifications for lockscreen seen duration threshold: $str1"
+            },
+        )
+    }
+
+    fun logTrackingLockscreenSeenDuration(entry: NotificationEntry) {
+        buffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            messageInitializer = { str1 = entry.key },
+            messagePrinter = {
+                "Tracking new notification for lockscreen seen duration threshold: $str1"
+            },
+        )
+    }
+
+    fun logStopTrackingLockscreenSeenDuration(entry: NotificationEntry) {
+        buffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            messageInitializer = { str1 = entry.key },
+            messagePrinter = {
+                "Stop tracking removed notification for lockscreen seen duration threshold: $str1"
+            },
+        )
+    }
+
+    fun logResetSeenOnLockscreen(entry: NotificationEntry) {
+        buffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            messageInitializer = { str1 = entry.key },
+            messagePrinter = {
+                "Reset tracking updated notification for lockscreen seen duration threshold: $str1"
+            },
+        )
+    }
+
+    fun logRemoveSeenOnLockscreen(entry: NotificationEntry) {
+        buffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            messageInitializer = { str1 = entry.key },
+            messagePrinter = { "Notification marked as seen on lockscreen removed: $str1" },
+        )
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
index e5953cf..0ccab9e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
@@ -15,6 +15,8 @@
  */
 package com.android.systemui.statusbar.notification.collection.coordinator
 
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED
 import com.android.systemui.statusbar.notification.collection.NotifPipeline
 import com.android.systemui.statusbar.notification.collection.PipelineDumpable
 import com.android.systemui.statusbar.notification.collection.PipelineDumper
@@ -31,32 +33,34 @@
 
 @CoordinatorScope
 class NotifCoordinatorsImpl @Inject constructor(
-        sectionStyleProvider: SectionStyleProvider,
-        dataStoreCoordinator: DataStoreCoordinator,
-        hideLocallyDismissedNotifsCoordinator: HideLocallyDismissedNotifsCoordinator,
-        hideNotifsForOtherUsersCoordinator: HideNotifsForOtherUsersCoordinator,
-        keyguardCoordinator: KeyguardCoordinator,
-        rankingCoordinator: RankingCoordinator,
-        appOpsCoordinator: AppOpsCoordinator,
-        deviceProvisionedCoordinator: DeviceProvisionedCoordinator,
-        bubbleCoordinator: BubbleCoordinator,
-        headsUpCoordinator: HeadsUpCoordinator,
-        gutsCoordinator: GutsCoordinator,
-        conversationCoordinator: ConversationCoordinator,
-        debugModeCoordinator: DebugModeCoordinator,
-        groupCountCoordinator: GroupCountCoordinator,
-        groupWhenCoordinator: GroupWhenCoordinator,
-        mediaCoordinator: MediaCoordinator,
-        preparationCoordinator: PreparationCoordinator,
-        remoteInputCoordinator: RemoteInputCoordinator,
-        rowAppearanceCoordinator: RowAppearanceCoordinator,
-        stackCoordinator: StackCoordinator,
-        shadeEventCoordinator: ShadeEventCoordinator,
-        smartspaceDedupingCoordinator: SmartspaceDedupingCoordinator,
-        viewConfigCoordinator: ViewConfigCoordinator,
-        visualStabilityCoordinator: VisualStabilityCoordinator,
-        sensitiveContentCoordinator: SensitiveContentCoordinator,
-        dismissibilityCoordinator: DismissibilityCoordinator,
+    sectionStyleProvider: SectionStyleProvider,
+    featureFlags: FeatureFlags,
+    dataStoreCoordinator: DataStoreCoordinator,
+    hideLocallyDismissedNotifsCoordinator: HideLocallyDismissedNotifsCoordinator,
+    hideNotifsForOtherUsersCoordinator: HideNotifsForOtherUsersCoordinator,
+    keyguardCoordinator: KeyguardCoordinator,
+    rankingCoordinator: RankingCoordinator,
+    appOpsCoordinator: AppOpsCoordinator,
+    deviceProvisionedCoordinator: DeviceProvisionedCoordinator,
+    bubbleCoordinator: BubbleCoordinator,
+    headsUpCoordinator: HeadsUpCoordinator,
+    gutsCoordinator: GutsCoordinator,
+    conversationCoordinator: ConversationCoordinator,
+    debugModeCoordinator: DebugModeCoordinator,
+    groupCountCoordinator: GroupCountCoordinator,
+    groupWhenCoordinator: GroupWhenCoordinator,
+    mediaCoordinator: MediaCoordinator,
+    preparationCoordinator: PreparationCoordinator,
+    remoteInputCoordinator: RemoteInputCoordinator,
+    rowAppearanceCoordinator: RowAppearanceCoordinator,
+    stackCoordinator: StackCoordinator,
+    shadeEventCoordinator: ShadeEventCoordinator,
+    smartspaceDedupingCoordinator: SmartspaceDedupingCoordinator,
+    viewConfigCoordinator: ViewConfigCoordinator,
+    visualStabilityCoordinator: VisualStabilityCoordinator,
+    sensitiveContentCoordinator: SensitiveContentCoordinator,
+    dismissibilityCoordinator: DismissibilityCoordinator,
+    dreamCoordinator: DreamCoordinator,
 ) : NotifCoordinators {
 
     private val mCoreCoordinators: MutableList<CoreCoordinator> = ArrayList()
@@ -96,6 +100,10 @@
         mCoordinators.add(remoteInputCoordinator)
         mCoordinators.add(dismissibilityCoordinator)
 
+        if (featureFlags.isEnabled(LOCKSCREEN_WALLPAPER_DREAM_ENABLED)) {
+            mCoordinators.add(dreamCoordinator)
+        }
+
         // Manually add Ordered Sections
         mOrderedSections.add(headsUpCoordinator.sectioner) // HeadsUp
         mOrderedSections.add(appOpsCoordinator.sectioner) // ForegroundService
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt
index 6271d38..bf65043 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt
@@ -18,7 +18,7 @@
 
 import com.android.systemui.log.dagger.NotificationLog
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import com.android.systemui.statusbar.notification.collection.GroupEntry
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.logKey
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorLogger.kt
index 1f4861a..0d9681f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorLogger.kt
@@ -18,7 +18,7 @@
 
 import com.android.systemui.log.dagger.NotificationLog
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import javax.inject.Inject
 
 private const val TAG = "ShadeEventCoordinator"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
index bdb206b..1896080 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
@@ -71,7 +71,6 @@
 
     private NotificationPresenter mPresenter;
     private NotificationListContainer mListContainer;
-    private BindRowCallback mBindRowCallback;
     private NotificationClicker mNotificationClicker;
     private FeatureFlags mFeatureFlags;
 
@@ -103,11 +102,9 @@
      * Sets up late-bound dependencies for this component.
      */
     public void setUpWithPresenter(NotificationPresenter presenter,
-            NotificationListContainer listContainer,
-            BindRowCallback bindRowCallback) {
+            NotificationListContainer listContainer) {
         mPresenter = presenter;
         mListContainer = listContainer;
-        mBindRowCallback = bindRowCallback;
 
         mIconManager.attach();
     }
@@ -143,7 +140,6 @@
                                         .expandableNotificationRow(row)
                                         .notificationEntry(entry)
                                         .onExpandClickListener(mPresenter)
-                                        .listContainer(mListContainer)
                                         .build();
                         ExpandableNotificationRowController rowController =
                                 component.getExpandableNotificationRowController();
@@ -179,7 +175,7 @@
         mNotificationRemoteInputManager.bindRow(row);
         entry.setRow(row);
         mNotifBindPipeline.manageRow(entry, row);
-        mBindRowCallback.onBindRow(row);
+        mPresenter.onBindRow(row);
         row.setInlineReplyAnimationFlagEnabled(
                 mFeatureFlags.isEnabled(NOTIFICATION_INLINE_REPLY_ANIMATION));
     }
@@ -235,12 +231,4 @@
             }
         });
     }
-
-    /** Callback for when a row is bound to an entry. */
-    public interface BindRowCallback {
-        /**
-         * Called when a new row is created and bound to a notification.
-         */
-        void onBindRow(ExpandableNotificationRow row);
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt
index f13ff68..a8409d0c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt
@@ -18,9 +18,9 @@
 
 import com.android.systemui.log.dagger.NotificationLog
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel.DEBUG
-import com.android.systemui.log.LogLevel.INFO
-import com.android.systemui.log.LogLevel.WARNING
+import com.android.systemui.log.core.LogLevel.DEBUG
+import com.android.systemui.log.core.LogLevel.INFO
+import com.android.systemui.log.core.LogLevel.WARNING
 import com.android.systemui.statusbar.notification.NotifPipelineFlags
 import com.android.systemui.statusbar.notification.collection.GroupEntry
 import com.android.systemui.statusbar.notification.collection.ListEntry
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt
index 20de785..014ac54 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt
@@ -22,11 +22,11 @@
 import android.service.notification.StatusBarNotification
 import com.android.systemui.log.dagger.NotificationLog
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel.DEBUG
-import com.android.systemui.log.LogLevel.ERROR
-import com.android.systemui.log.LogLevel.INFO
-import com.android.systemui.log.LogLevel.WARNING
-import com.android.systemui.log.LogLevel.WTF
+import com.android.systemui.log.core.LogLevel.DEBUG
+import com.android.systemui.log.core.LogLevel.ERROR
+import com.android.systemui.log.core.LogLevel.INFO
+import com.android.systemui.log.core.LogLevel.WARNING
+import com.android.systemui.log.core.LogLevel.WTF
 import com.android.systemui.statusbar.notification.collection.NotifCollection
 import com.android.systemui.statusbar.notification.collection.NotifCollection.CancellationReason
 import com.android.systemui.statusbar.notification.collection.NotifCollection.FutureDismissal
@@ -108,27 +108,39 @@
         })
     }
 
-    fun logNotifDismissed(entry: NotificationEntry) {
+    fun logLocallyDismissed(entry: NotificationEntry, index: Int, count: Int) {
         buffer.log(TAG, INFO, {
             str1 = entry.logKey
+            int1 = index
+            int2 = count
         }, {
-            "DISMISSED $str1"
+            "LOCALLY DISMISSED $str1 ($int1/$int2)"
         })
     }
 
-    fun logNonExistentNotifDismissed(entry: NotificationEntry) {
+    fun logDismissNonExistentNotif(entry: NotificationEntry, index: Int, count: Int) {
         buffer.log(TAG, INFO, {
             str1 = entry.logKey
+            int1 = index
+            int2 = count
         }, {
-            "DISMISSED Non Existent $str1"
+            "DISMISS Non Existent $str1 ($int1/$int2)"
         })
     }
 
-    fun logChildDismissed(entry: NotificationEntry) {
+    fun logLocallyDismissedChild(
+            child: NotificationEntry,
+            parent: NotificationEntry,
+            parentIndex: Int,
+            parentCount: Int
+    ) {
         buffer.log(TAG, DEBUG, {
-            str1 = entry.logKey
+            str1 = child.logKey
+            str2 = parent.logKey
+            int1 = parentIndex
+            int2 = parentCount
         }, {
-            "CHILD DISMISSED (inferred): $str1"
+            "LOCALLY DISMISSED CHILD (inferred): $str1 of parent $str2 ($int1/$int2)"
         })
     }
 
@@ -140,27 +152,31 @@
         })
     }
 
-    fun logDismissOnAlreadyCanceledEntry(entry: NotificationEntry) {
+    fun logLocallyDismissedAlreadyCanceledEntry(entry: NotificationEntry) {
         buffer.log(TAG, DEBUG, {
             str1 = entry.logKey
         }, {
-            "Dismiss on $str1, which was already canceled. Trying to remove..."
+            "LOCALLY DISMISSED Already Canceled $str1. Trying to remove."
         })
     }
 
-    fun logNotifDismissedIntercepted(entry: NotificationEntry) {
+    fun logNotifDismissedIntercepted(entry: NotificationEntry, index: Int, count: Int) {
         buffer.log(TAG, INFO, {
             str1 = entry.logKey
+            int1 = index
+            int2 = count
         }, {
-            "DISMISS INTERCEPTED $str1"
+            "DISMISS INTERCEPTED $str1 ($int1/$int2)"
         })
     }
 
-    fun logNotifClearAllDismissalIntercepted(entry: NotificationEntry) {
+    fun logNotifClearAllDismissalIntercepted(entry: NotificationEntry, index: Int, count: Int) {
         buffer.log(TAG, INFO, {
             str1 = entry.logKey
+            int1 = index
+            int2 = count
         }, {
-            "CLEAR ALL DISMISSAL INTERCEPTED $str1"
+            "CLEAR ALL DISMISSAL INTERCEPTED $str1 ($int1/$int2)"
         })
     }
 
@@ -251,12 +267,19 @@
         })
     }
 
-    fun logRemoteExceptionOnNotificationClear(entry: NotificationEntry, e: RemoteException) {
+    fun logRemoteExceptionOnNotificationClear(
+            entry: NotificationEntry,
+            index: Int,
+            count: Int,
+            e: RemoteException
+    ) {
         buffer.log(TAG, WTF, {
             str1 = entry.logKey
+            int1 = index
+            int2 = count
             str2 = e.toString()
         }, {
-            "RemoteException while attempting to clear $str1:\n$str2"
+            "RemoteException while attempting to clear $str1 ($int1/$int2):\n$str2"
         })
     }
 
@@ -387,6 +410,126 @@
             "Mismatch: current $str2 is $str3 for: $str1"
         })
     }
+
+    fun logDismissAlreadyDismissedNotif(entry: NotificationEntry, index: Int, count: Int) {
+        buffer.log(TAG, DEBUG, {
+            str1 = entry.logKey
+            int1 = index
+            int2 = count
+        }, {
+            "DISMISS Already Dismissed $str1 ($int1/$int2)"
+        })
+    }
+
+    fun logDismissAlreadyParentDismissedNotif(
+            childEntry: NotificationEntry,
+            childIndex: Int,
+            childCount: Int
+    ) {
+        buffer.log(TAG, DEBUG, {
+            str1 = childEntry.logKey
+            int1 = childIndex
+            int2 = childCount
+            str2 = childEntry.parent?.summary?.logKey ?: "(null)"
+        }, {
+            "DISMISS Already Parent-Dismissed $str1 ($int1/$int2) with summary $str2"
+        })
+    }
+
+    fun logLocallyDismissNonExistentNotif(entry: NotificationEntry, index: Int, count: Int) {
+        buffer.log(TAG, INFO, {
+            str1 = entry.logKey
+            int1 = index
+            int2 = count
+        }, {
+            "LOCALLY DISMISS Non Existent $str1 ($int1/$int2)"
+        })
+    }
+
+    fun logLocallyDismissMismatchedEntry(
+            entry: NotificationEntry,
+            index: Int,
+            count: Int,
+            storedEntry: NotificationEntry
+    ) {
+        buffer.log(TAG, INFO, {
+            str1 = entry.logKey
+            int1 = index
+            int2 = count
+            str2 = Integer.toHexString(entry.hashCode())
+            str3 = Integer.toHexString(storedEntry.hashCode())
+        }, {
+            "LOCALLY DISMISS Mismatch $str1 ($int1/$int2): dismissing @$str2 but stored @$str3"
+        })
+    }
+
+    fun logLocallyDismissAlreadyDismissedNotif(
+            entry: NotificationEntry,
+            index: Int,
+            count: Int
+    ) {
+        buffer.log(TAG, INFO, {
+            str1 = entry.logKey
+            int1 = index
+            int2 = count
+        }, {
+            "LOCALLY DISMISS Already Dismissed $str1 ($int1/$int2)"
+        })
+    }
+
+    fun logLocallyDismissAlreadyParentDismissedNotif(
+            entry: NotificationEntry,
+            index: Int,
+            count: Int
+    ) {
+        buffer.log(TAG, INFO, {
+            str1 = entry.logKey
+            int1 = index
+            int2 = count
+        }, {
+            "LOCALLY DISMISS Already Dismissed $str1 ($int1/$int2)"
+        })
+    }
+
+    fun logLocallyDismissAlreadyDismissedChild(
+            childEntry: NotificationEntry,
+            parentEntry: NotificationEntry,
+            parentIndex: Int,
+            parentCount: Int
+    ) {
+        buffer.log(TAG, INFO, {
+            str1 = childEntry.logKey
+            str2 = parentEntry.logKey
+            int1 = parentIndex
+            int2 = parentCount
+        }, {
+            "LOCALLY DISMISS Already Dismissed Child $str1 of parent $str2 ($int1/$int2)"
+        })
+    }
+
+    fun logLocallyDismissAlreadyParentDismissedChild(
+            childEntry: NotificationEntry,
+            parentEntry: NotificationEntry,
+            parentIndex: Int,
+            parentCount: Int
+    ) {
+        buffer.log(TAG, INFO, {
+            str1 = childEntry.logKey
+            str2 = parentEntry.logKey
+            int1 = parentIndex
+            int2 = parentCount
+        }, {
+            "LOCALLY DISMISS Already Parent-Dismissed Child $str1 of parent $str2 ($int1/$int2)"
+        })
+    }
+
+    fun logCancelLocalDismissalNotDismissedNotif(entry: NotificationEntry) {
+        buffer.log(TAG, INFO, {
+            str1 = entry.logKey
+        }, {
+            "CANCEL LOCAL DISMISS Not Dismissed $str1"
+        })
+    }
 }
 
 private const val TAG = "NotifCollection"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderLogger.kt
index 07fd349..e61f9bd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderLogger.kt
@@ -18,7 +18,7 @@
 
 import com.android.systemui.log.dagger.NotificationLog
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import com.android.systemui.statusbar.notification.NotifPipelineFlags
 import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection
 import com.android.systemui.util.Compile
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt
index a880b71..082f308 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt
@@ -18,7 +18,7 @@
 
 import com.android.systemui.log.dagger.NotificationLog
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import java.lang.RuntimeException
 import javax.inject.Inject
 
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 b100d44..31d4ab9 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
@@ -63,8 +63,12 @@
 import com.android.systemui.statusbar.notification.logging.NotificationPanelLoggerImpl;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
 import com.android.systemui.statusbar.notification.row.OnUserInteractionCallback;
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.ActivatableNotificationViewModelModule;
+import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager;
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
 import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm;
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationListViewModelModule;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 
 import dagger.Binds;
@@ -85,6 +89,8 @@
         ShadeEventsModule.class,
         NotifPipelineChoreographerModule.class,
         NotificationSectionHeadersModule.class,
+        NotificationListViewModelModule.class,
+        ActivatableNotificationViewModelModule.class,
 })
 public interface NotificationsModule {
     @Binds
@@ -159,6 +165,14 @@
         }
     }
 
+    /** Provides the container for the notification list. */
+    @Provides
+    @SysUISingleton
+    static NotificationListContainer provideListContainer(
+            NotificationStackScrollLayoutController nsslController) {
+        return nsslController.getNotificationListContainer();
+    }
+
     /**
      * Provide the active notification collection managing the notifications to render.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsInteractor.kt
new file mode 100644
index 0000000..8f7e269
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsInteractor.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.statusbar.notification.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.disableflags.data.repository.DisableFlagsRepository
+import javax.inject.Inject
+
+/** Interactor for notifications in general. */
+@SysUISingleton
+class NotificationsInteractor
+@Inject
+constructor(
+    private val disableFlagsRepository: DisableFlagsRepository,
+) {
+    /** Returns true if notification alerts are allowed. */
+    fun areNotificationAlertsEnabled(): Boolean {
+        return disableFlagsRepository.disableFlags.value.areNotificationAlertsEnabled()
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsController.kt
index a5278c3d..76e228b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsController.kt
@@ -20,10 +20,8 @@
 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption
 import com.android.systemui.statusbar.NotificationPresenter
 import com.android.systemui.statusbar.notification.NotificationActivityStarter
-import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl
 import com.android.systemui.statusbar.notification.collection.render.NotifStackController
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer
-import com.android.systemui.statusbar.phone.CentralSurfaces
 
 /**
  * The master controller for all notifications-related work
@@ -33,12 +31,10 @@
  */
 interface NotificationsController {
     fun initialize(
-        centralSurfaces: CentralSurfaces,
         presenter: NotificationPresenter,
         listContainer: NotificationListContainer,
         stackController: NotifStackController,
         notificationActivityStarter: NotificationActivityStarter,
-        bindRowCallback: NotificationRowBinderImpl.BindRowCallback
     )
 
     fun resetUserExpandedStates()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
index 0ed4175..f7bd177 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
@@ -42,7 +42,6 @@
 import com.android.systemui.statusbar.notification.logging.NotificationMemoryMonitor
 import com.android.systemui.statusbar.notification.row.NotifBindPipelineInitializer
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer
-import com.android.systemui.statusbar.phone.CentralSurfaces
 import com.android.wm.shell.bubbles.Bubbles
 import dagger.Lazy
 import java.util.Optional
@@ -78,12 +77,10 @@
 ) : NotificationsController {
 
     override fun initialize(
-        centralSurfaces: CentralSurfaces,
         presenter: NotificationPresenter,
         listContainer: NotificationListContainer,
         stackController: NotifStackController,
         notificationActivityStarter: NotificationActivityStarter,
-        bindRowCallback: NotificationRowBinderImpl.BindRowCallback
     ) {
         notificationListener.registerAsSystemService()
 
@@ -94,13 +91,8 @@
         })
 
         notificationRowBinder.setNotificationClicker(
-                clickerBuilder.build(
-                    Optional.ofNullable(centralSurfaces), bubblesOptional,
-                        notificationActivityStarter))
-        notificationRowBinder.setUpWithPresenter(
-                presenter,
-                listContainer,
-                bindRowCallback)
+            clickerBuilder.build(bubblesOptional, notificationActivityStarter))
+        notificationRowBinder.setUpWithPresenter(presenter, listContainer)
         headsUpViewBinder.setPresenter(presenter)
         notifBindPipelineInitializer.initialize()
         animatedImageNotificationManager.bind()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerStub.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerStub.kt
index 14856da..65ba6de 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerStub.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerStub.kt
@@ -21,10 +21,8 @@
 import com.android.systemui.statusbar.NotificationListener
 import com.android.systemui.statusbar.NotificationPresenter
 import com.android.systemui.statusbar.notification.NotificationActivityStarter
-import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl
 import com.android.systemui.statusbar.notification.collection.render.NotifStackController
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer
-import com.android.systemui.statusbar.phone.CentralSurfaces
 import javax.inject.Inject
 
 /**
@@ -35,12 +33,10 @@
 ) : NotificationsController {
 
     override fun initialize(
-        centralSurfaces: CentralSurfaces,
         presenter: NotificationPresenter,
         listContainer: NotificationListContainer,
         stackController: NotifStackController,
         notificationActivityStarter: NotificationActivityStarter,
-        bindRowCallback: NotificationRowBinderImpl.BindRowCallback
     ) {
         // Always connect the listener even if notification-handling is disabled. Being a listener
         // grants special permissions and it's not clear if other things will break if we lose those
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderLogger.kt
index 0b31265..c6d2861 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderLogger.kt
@@ -2,7 +2,7 @@
 
 import com.android.systemui.log.dagger.NotificationHeadsUpLog
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel.INFO
+import com.android.systemui.log.core.LogLevel.INFO
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.logKey
 import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt
index 90014c2..78225df 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt
@@ -87,6 +87,7 @@
 
     private val userTrackerCallback = object : UserTracker.Callback {
         override fun onUserChanged(newUser: Int, userContext: Context) {
+            readShowSilentNotificationSetting()
             if (isLockedOrLocking) {
                 // maybe public mode changed
                 notifyStateChanged("onUserSwitched")
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 5bac2a9..4a823a4 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
@@ -20,9 +20,9 @@
 
 import com.android.systemui.log.dagger.NotificationInterruptLog
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel.DEBUG
-import com.android.systemui.log.LogLevel.INFO
-import com.android.systemui.log.LogLevel.WARNING
+import com.android.systemui.log.core.LogLevel.DEBUG
+import com.android.systemui.log.core.LogLevel.INFO
+import com.android.systemui.log.core.LogLevel.WARNING
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.logKey
 import com.android.systemui.util.Compile
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 609f9d4..0c43da0 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
@@ -582,10 +582,6 @@
     }
 
     private boolean shouldSuppressHeadsUpWhenAwakeForOldWhen(NotificationEntry entry, boolean log) {
-        if (!mFlags.isNoHunForOldWhenEnabled()) {
-            return false;
-        }
-
         final Notification notification = entry.getSbn().getNotification();
         if (notification == null) {
             return false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationRoundnessLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationRoundnessLogger.kt
index fe03b2a..0e1f66f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationRoundnessLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationRoundnessLogger.kt
@@ -17,7 +17,7 @@
 package com.android.systemui.statusbar.notification.logging
 
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel.INFO
+import com.android.systemui.log.core.LogLevel.INFO
 import com.android.systemui.log.dagger.NotificationRenderLog
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
 import com.android.systemui.statusbar.notification.row.ExpandableView
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
index 417d4ac..908c11a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
@@ -321,8 +321,7 @@
     protected void setBackgroundTintColor(int color) {
         if (color != mCurrentBackgroundTint) {
             mCurrentBackgroundTint = color;
-            // TODO(282173943): re-enable this tinting optimization when Resources are thread-safe
-            if (false && color == mNormalColor) {
+            if (color == mNormalColor) {
                 // We don't need to tint a normal notification
                 color = 0;
             }
@@ -360,9 +359,9 @@
     }
 
     @Override
-    public long performRemoveAnimation(long duration, long delay,
-            float translationDirection, boolean isHeadsUpAnimation, float endLocation,
-            Runnable onFinishedRunnable, AnimatorListenerAdapter animationListener) {
+    public long performRemoveAnimation(long duration, long delay, float translationDirection,
+            boolean isHeadsUpAnimation, Runnable onFinishedRunnable,
+            AnimatorListenerAdapter animationListener) {
         enableAppearDrawing(true);
         mIsHeadsUpAnimation = isHeadsUpAnimation;
         if (mDrawingAppearAnimation) {
@@ -567,12 +566,20 @@
 
     @Override
     public float getTopCornerRadius() {
+        if (isNewHeadsUpAnimFlagEnabled()) {
+            return super.getTopCornerRadius();
+        }
+
         float fraction = getInterpolatedAppearAnimationFraction();
         return MathUtils.lerp(0, super.getTopCornerRadius(), fraction);
     }
 
     @Override
     public float getBottomCornerRadius() {
+        if (isNewHeadsUpAnimFlagEnabled()) {
+            return super.getBottomCornerRadius();
+        }
+
         float fraction = getInterpolatedAppearAnimationFraction();
         return MathUtils.lerp(0, super.getBottomCornerRadius(), fraction);
     }
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 9f397fe..b34c281 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
@@ -53,6 +53,7 @@
 import android.view.NotificationHeaderView;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.ViewParent;
 import android.view.ViewStub;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityNodeInfo;
@@ -103,6 +104,8 @@
 import com.android.systemui.statusbar.notification.stack.AnimationProperties;
 import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
 import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainer;
+import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainerLogger;
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
 import com.android.systemui.statusbar.notification.stack.SwipeableView;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
@@ -170,6 +173,7 @@
     private PeopleNotificationIdentifier mPeopleNotificationIdentifier;
     private Optional<BubblesManager> mBubblesManagerOptional;
     private MetricsLogger mMetricsLogger;
+    private NotificationChildrenContainerLogger mChildrenContainerLogger;
     private NotificationDismissibilityProvider mDismissibilityProvider;
     private FeatureFlags mFeatureFlags;
     private int mIconTransformContentShift;
@@ -1627,6 +1631,51 @@
                 NotificationEntry child,
                 NotificationEntry newParent
         );
+
+        /**
+         * Called when an ExpandableNotificationRow transient view is removed from the
+         * NotificationChildrenContainer
+         */
+        void logRemoveTransientFromContainer(
+                NotificationEntry childEntry,
+                NotificationEntry containerEntry
+        );
+
+        /**
+         * Called when an ExpandableNotificationRow transient view is removed from the
+         * NotificationStackScrollLayout
+         */
+        void logRemoveTransientFromNssl(
+                NotificationEntry childEntry
+        );
+
+        /**
+         * Called when an ExpandableNotificationRow transient view is removed from a ViewGroup that
+         * is not NotificationChildrenContainer or NotificationStackScrollLayout
+         */
+        void logRemoveTransientFromViewGroup(
+                NotificationEntry childEntry,
+                ViewGroup containerView
+        );
+
+        /**
+         * Called when an ExpandableNotificationRow transient view is added to this
+         * ExpandableNotificationRow
+         */
+        void logAddTransientRow(
+                NotificationEntry childEntry,
+                NotificationEntry containerEntry,
+                int index
+        );
+
+        /**
+         * Called when an ExpandableNotificationRow transient view is removed from this
+         * ExpandableNotificationRow
+         */
+        void logRemoveTransientRow(
+                NotificationEntry childEntry,
+                NotificationEntry containerEntry
+        );
     }
 
     /**
@@ -1668,6 +1717,7 @@
             NotificationGutsManager gutsManager,
             NotificationDismissibilityProvider dismissibilityProvider,
             MetricsLogger metricsLogger,
+            NotificationChildrenContainerLogger childrenContainerLogger,
             SmartReplyConstants smartReplyConstants,
             SmartReplyController smartReplyController,
             FeatureFlags featureFlags,
@@ -1706,6 +1756,7 @@
         mBubblesManagerOptional = bubblesManagerOptional;
         mNotificationGutsManager = gutsManager;
         mMetricsLogger = metricsLogger;
+        mChildrenContainerLogger = childrenContainerLogger;
         mDismissibilityProvider = dismissibilityProvider;
         mFeatureFlags = featureFlags;
     }
@@ -1874,6 +1925,7 @@
             mChildrenContainer.setIsLowPriority(mIsLowPriority);
             mChildrenContainer.setContainingNotification(ExpandableNotificationRow.this);
             mChildrenContainer.onNotificationUpdated();
+            mChildrenContainer.setLogger(mChildrenContainerLogger);
 
             mTranslateableViews.add(mChildrenContainer);
         });
@@ -2673,7 +2725,12 @@
     }
 
     public boolean isExpanded(boolean allowOnKeyguard) {
-        return (!mOnKeyguard || allowOnKeyguard)
+        if (DEBUG) {
+            if (!mShowingPublicInitialized && !allowOnKeyguard) {
+                Log.d(TAG, "mShowingPublic is not initialized.");
+            }
+        }
+        return !mShowingPublic && (!mOnKeyguard || allowOnKeyguard)
                 && (!hasUserChangedExpansion() && (isSystemExpanded() || isSystemChildExpanded())
                 || isUserExpanded());
     }
@@ -2918,7 +2975,6 @@
             long delay,
             float translationDirection,
             boolean isHeadsUpAnimation,
-            float endLocation,
             Runnable onFinishedRunnable,
             AnimatorListenerAdapter animationListener) {
         if (mMenuRow != null && mMenuRow.isMenuVisible()) {
@@ -2929,7 +2985,7 @@
                     public void onAnimationEnd(Animator animation) {
                         ExpandableNotificationRow.super.performRemoveAnimation(
                                 duration, delay, translationDirection, isHeadsUpAnimation,
-                                endLocation, onFinishedRunnable, animationListener);
+                                onFinishedRunnable, animationListener);
                     }
                 });
                 anim.start();
@@ -2937,7 +2993,7 @@
             }
         }
         return super.performRemoveAnimation(duration, delay, translationDirection,
-                isHeadsUpAnimation, endLocation, onFinishedRunnable, animationListener);
+                isHeadsUpAnimation, onFinishedRunnable, animationListener);
     }
 
     @Override
@@ -3620,6 +3676,8 @@
             pw.print(", mOnUserInteractionCallback null: " + (mOnUserInteractionCallback == null));
             pw.print(", removed: " + isRemoved());
             pw.print(", expandAnimationRunning: " + mExpandAnimationRunning);
+            pw.print(", mShowingPublic: " + mShowingPublic);
+            pw.print(", mShowingPublicInitialized: " + mShowingPublicInitialized);
             NotificationContentView showingLayout = getShowingLayout();
             pw.print(", privateShowing: " + (showingLayout == mPrivateLayout));
             pw.println();
@@ -3707,4 +3765,73 @@
     public boolean getShowSnooze() {
         return mShowSnooze;
     }
+
+    @Override
+    public void removeFromTransientContainer() {
+        final ViewGroup transientContainer = getTransientContainer();
+        final ViewParent parent = getParent();
+        // Only log when there is real removal of transient views
+        if (transientContainer == null || transientContainer != parent) {
+            super.removeFromTransientContainer();
+            return;
+        }
+        logRemoveFromTransientContainer(transientContainer);
+        super.removeFromTransientContainer();
+    }
+
+    /**
+     * Log calls to removeFromTransientContainer when the container is NotificationChildrenContainer
+     * or NotificationStackScrollLayout.
+     */
+    public void logRemoveFromTransientContainer(ViewGroup transientContainer) {
+        if (mLogger == null) {
+            return;
+        }
+        if (transientContainer instanceof NotificationChildrenContainer) {
+            mLogger.logRemoveTransientFromContainer(
+                    /* childEntry = */ getEntry(),
+                    /* containerEntry = */ ((NotificationChildrenContainer) transientContainer)
+                            .getContainingNotification().getEntry()
+            );
+        } else if (transientContainer instanceof NotificationStackScrollLayout) {
+            mLogger.logRemoveTransientFromNssl(
+                    /* childEntry = */ getEntry()
+            );
+        } else {
+            mLogger.logRemoveTransientFromViewGroup(
+                    /* childEntry = */ getEntry(),
+                    /* containerView = */ transientContainer
+            );
+        }
+    }
+
+    @Override
+    public void addTransientView(View view, int index) {
+        if (view instanceof ExpandableNotificationRow) {
+            logAddTransientRow((ExpandableNotificationRow) view, index);
+        }
+        super.addTransientView(view, index);
+    }
+
+    private void logAddTransientRow(ExpandableNotificationRow row, int index) {
+        if (mLogger == null) {
+            return;
+        }
+        mLogger.logAddTransientRow(row.getEntry(), getEntry(), index);
+    }
+
+    @Override
+    public void removeTransientView(View view) {
+        if (view instanceof ExpandableNotificationRow) {
+            logRemoveTransientRow((ExpandableNotificationRow) view);
+        }
+        super.removeTransientView(view);
+    }
+
+    private void logRemoveTransientRow(ExpandableNotificationRow row) {
+        if (mLogger == null) {
+            return;
+        }
+        mLogger.logRemoveTransientRow(row.getEntry(), getEntry());
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
index 1acc9f9..a4e8c2e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
@@ -50,6 +50,7 @@
 import com.android.systemui.statusbar.notification.row.dagger.AppName;
 import com.android.systemui.statusbar.notification.row.dagger.NotificationKey;
 import com.android.systemui.statusbar.notification.row.dagger.NotificationRowScope;
+import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainerLogger;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
@@ -88,6 +89,7 @@
     private final ExpandableNotificationRow.OnExpandClickListener mOnExpandClickListener;
     private final StatusBarStateController mStatusBarStateController;
     private final MetricsLogger mMetricsLogger;
+    private final NotificationChildrenContainerLogger mChildrenContainerLogger;
     private final ExpandableNotificationRow.CoordinateOnClickListener mOnFeedbackClickListener;
     private final NotificationGutsManager mNotificationGutsManager;
     private final OnUserInteractionCallback mOnUserInteractionCallback;
@@ -125,6 +127,46 @@
                 ) {
                     mLogBufferLogger.logSkipAttachingKeepInParentChild(child, newParent);
                 }
+
+                @Override
+                public void logRemoveTransientFromContainer(
+                        NotificationEntry childEntry,
+                        NotificationEntry containerEntry
+                ) {
+                    mLogBufferLogger.logRemoveTransientFromContainer(childEntry, containerEntry);
+                }
+
+                @Override
+                public void logRemoveTransientFromNssl(
+                        NotificationEntry childEntry
+                ) {
+                    mLogBufferLogger.logRemoveTransientFromNssl(childEntry);
+                }
+
+                @Override
+                public void logRemoveTransientFromViewGroup(
+                        NotificationEntry childEntry,
+                        ViewGroup containerView
+                ) {
+                    mLogBufferLogger.logRemoveTransientFromViewGroup(childEntry, containerView);
+                }
+
+                @Override
+                public void logAddTransientRow(
+                        NotificationEntry childEntry,
+                        NotificationEntry containerEntry,
+                        int index
+                ) {
+                    mLogBufferLogger.logAddTransientRow(childEntry, containerEntry, index);
+                }
+
+                @Override
+                public void logRemoveTransientRow(
+                        NotificationEntry childEntry,
+                        NotificationEntry containerEntry
+                ) {
+                    mLogBufferLogger.logRemoveTransientRow(childEntry, containerEntry);
+                }
             };
 
 
@@ -135,6 +177,7 @@
             RemoteInputViewSubcomponent.Factory rivSubcomponentFactory,
             MetricsLogger metricsLogger,
             NotificationRowLogger logBufferLogger,
+            NotificationChildrenContainerLogger childrenContainerLogger,
             NotificationListContainer listContainer,
             SmartReplyConstants smartReplyConstants,
             SmartReplyController smartReplyController,
@@ -188,6 +231,7 @@
         mBubblesManagerOptional = bubblesManagerOptional;
         mDragController = dragController;
         mMetricsLogger = metricsLogger;
+        mChildrenContainerLogger = childrenContainerLogger;
         mLogBufferLogger = logBufferLogger;
         mSmartReplyConstants = smartReplyConstants;
         mSmartReplyController = smartReplyController;
@@ -222,6 +266,7 @@
                 mNotificationGutsManager,
                 mDismissibilityProvider,
                 mMetricsLogger,
+                mChildrenContainerLogger,
                 mSmartReplyConstants,
                 mSmartReplyController,
                 mFeatureFlags,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java
index 7a2bee9..b950187 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java
@@ -148,7 +148,7 @@
     private void dismissShade() {
         // Speed up dismissing the shade since the drag needs to be handled by
         // the shell layer underneath
-        mShadeController.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE, true /* force */,
+        mShadeController.animateCollapseShade(CommandQueue.FLAG_EXCLUDE_NONE, true /* force */,
                 false /* delayed */, 1.1f /* speedUpFactor */);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java
index 9aa50e9..7f23c1b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java
@@ -28,7 +28,10 @@
 import android.view.View;
 import android.view.ViewOutlineProvider;
 
+import com.android.systemui.Dependency;
 import com.android.systemui.R;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.statusbar.notification.RoundableState;
 import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainer;
 import com.android.systemui.util.DumpUtilsKt;
@@ -47,12 +50,14 @@
     private float mOutlineAlpha = -1f;
     private boolean mAlwaysRoundBothCorners;
     private Path mTmpPath = new Path();
+    private final FeatureFlags mFeatureFlags;
 
     /**
      * {@code false} if the children views of the {@link ExpandableOutlineView} are translated when
      * it is moved. Otherwise, the translation is set on the {@code ExpandableOutlineView} itself.
      */
     protected boolean mDismissUsingRowTranslationX = true;
+
     private float[] mTmpCornerRadii = new float[8];
 
     private final ViewOutlineProvider mProvider = new ViewOutlineProvider() {
@@ -81,6 +86,15 @@
         return mRoundableState;
     }
 
+    @Override
+    public int getClipHeight() {
+        if (mCustomOutline) {
+            return mOutlineRect.height();
+        }
+
+        return super.getClipHeight();
+    }
+
     protected Path getClipPath(boolean ignoreTranslation) {
         int left;
         int top;
@@ -112,7 +126,7 @@
             return EMPTY_PATH;
         }
         float bottomRadius = mAlwaysRoundBothCorners ? getMaxRadius() : getBottomCornerRadius();
-        if (topRadius + bottomRadius > height) {
+        if (!isNewHeadsUpAnimFlagEnabled() && (topRadius + bottomRadius > height)) {
             float overShoot = topRadius + bottomRadius - height;
             float currentTopRoundness = getTopRoundness();
             float currentBottomRoundness = getBottomRoundness();
@@ -153,6 +167,7 @@
         super(context, attrs);
         setOutlineProvider(mProvider);
         initDimens();
+        mFeatureFlags = Dependency.get(FeatureFlags.class);
     }
 
     @Override
@@ -360,4 +375,9 @@
             }
         });
     }
+
+    // TODO(b/290365128) replace with ViewRefactorFlag
+    protected boolean isNewHeadsUpAnimFlagEnabled() {
+        return mFeatureFlags.isEnabled(Flags.IMPROVED_HUN_ANIMATIONS);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
index f0e15c2..c4c116b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
@@ -93,6 +93,12 @@
         return mRoundableState;
     }
 
+    @Override
+    public int getClipHeight() {
+        int clipHeight = Math.max(mActualHeight - mClipTopAmount - mClipBottomAmount, 0);
+        return Math.max(clipHeight, mMinimumHeightForClipping);
+    }
+
     private void initDimens() {
         mContentShift = getResources().getDimensionPixelSize(
                 R.dimen.shelf_transform_content_shift);
@@ -367,7 +373,6 @@
      *                             such that the  child appears to be going away to the top. 1
      *                             Should mean the opposite.
      * @param isHeadsUpAnimation Is this a headsUp animation.
-     * @param endLocation The location where the horizonal heads up disappear animation should end.
      * @param onFinishedRunnable A runnable which should be run when the animation is finished.
      * @param animationListener An animation listener to add to the animation.
      *
@@ -375,7 +380,7 @@
      * animation starts.
      */
     public abstract long performRemoveAnimation(long duration,
-            long delay, float translationDirection, boolean isHeadsUpAnimation, float endLocation,
+            long delay, float translationDirection, boolean isHeadsUpAnimation,
             Runnable onFinishedRunnable,
             AnimatorListenerAdapter animationListener);
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java
index 0989df6..6bbeebf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java
@@ -16,15 +16,11 @@
 
 package com.android.systemui.statusbar.notification.row;
 
-import static android.graphics.PorterDuff.Mode.SRC_ATOP;
-
 import android.annotation.ColorInt;
 import android.content.Context;
 import android.content.res.ColorStateList;
 import android.content.res.Configuration;
 import android.content.res.Resources;
-import android.graphics.ColorFilter;
-import android.graphics.PorterDuffColorFilter;
 import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
 import android.util.IndentingPrintWriter;
@@ -161,20 +157,10 @@
      */
     public void updateColors() {
         Resources.Theme theme = mContext.getTheme();
-        final @ColorInt int textColor = getResources().getColor(R.color.notif_pill_text, theme);
-        final Drawable clearAllBg = theme.getDrawable(R.drawable.notif_footer_btn_background);
-        final Drawable manageBg = theme.getDrawable(R.drawable.notif_footer_btn_background);
-        // TODO(b/282173943): Remove redundant tinting once Resources are thread-safe
-        final @ColorInt int buttonBgColor =
-                Utils.getColorAttrDefaultColor(mContext, com.android.internal.R.attr.colorSurface);
-        final ColorFilter bgColorFilter = new PorterDuffColorFilter(buttonBgColor, SRC_ATOP);
-        if (buttonBgColor != 0) {
-            clearAllBg.setColorFilter(bgColorFilter);
-            manageBg.setColorFilter(bgColorFilter);
-        }
-        mClearAllButton.setBackground(clearAllBg);
+        int textColor = getResources().getColor(R.color.notif_pill_text, theme);
+        mClearAllButton.setBackground(theme.getDrawable(R.drawable.notif_footer_btn_background));
         mClearAllButton.setTextColor(textColor);
-        mManageButton.setBackground(manageBg);
+        mManageButton.setBackground(theme.getDrawable(R.drawable.notif_footer_btn_background));
         mManageButton.setTextColor(textColor);
         final @ColorInt int labelTextColor =
                 Utils.getColorAttrDefaultColor(mContext, android.R.attr.textColorPrimary);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt
index 45be0b1..3856700 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt
@@ -18,7 +18,7 @@
 
 import com.android.systemui.log.dagger.NotificationLog
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel.INFO
+import com.android.systemui.log.core.LogLevel.INFO
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.logKey
 import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactory.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactory.kt
new file mode 100644
index 0000000..4429939
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactory.kt
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row
+
+import android.content.Context
+import android.util.AttributeSet
+import android.util.Log
+import android.view.LayoutInflater
+import android.view.View
+import com.android.systemui.Dumpable
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.statusbar.notification.row.NotificationRowModule.NOTIF_REMOTEVIEWS_FACTORIES
+import com.android.systemui.util.asIndenting
+import com.android.systemui.util.withIncreasedIndent
+import java.io.PrintWriter
+import javax.inject.Inject
+import javax.inject.Named
+
+/**
+ * Implementation of [NotifLayoutInflaterFactory]. This class uses a set of
+ * [NotifRemoteViewsFactory] objects to create replacement views for Notification RemoteViews.
+ */
+open class NotifLayoutInflaterFactory
+@Inject
+constructor(
+    dumpManager: DumpManager,
+    @Named(NOTIF_REMOTEVIEWS_FACTORIES)
+    private val remoteViewsFactories: Set<@JvmSuppressWildcards NotifRemoteViewsFactory>
+) : LayoutInflater.Factory2, Dumpable {
+    init {
+        dumpManager.registerNormalDumpable(TAG, this)
+    }
+
+    override fun onCreateView(
+        parent: View?,
+        name: String,
+        context: Context,
+        attrs: AttributeSet
+    ): View? {
+        var view: View? = null
+        var handledFactory: NotifRemoteViewsFactory? = null
+        for (layoutFactory in remoteViewsFactories) {
+            view = layoutFactory.instantiate(parent, name, context, attrs)
+            if (view != null) {
+                check(handledFactory == null) {
+                    "${layoutFactory.javaClass.name} tries to produce view. However, " +
+                        "${handledFactory?.javaClass?.name} produced view for $name before."
+                }
+                handledFactory = layoutFactory
+            }
+        }
+        logOnCreateView(name, view, handledFactory)
+        return view
+    }
+
+    override fun onCreateView(name: String, context: Context, attrs: AttributeSet): View? =
+        onCreateView(null, name, context, attrs)
+
+    override fun dump(pw: PrintWriter, args: Array<out String>) {
+        val indentingPW = pw.asIndenting()
+
+        indentingPW.appendLine("$TAG ReplacementFactories:")
+        indentingPW.withIncreasedIndent {
+            remoteViewsFactories.forEach { indentingPW.appendLine(it.javaClass.simpleName) }
+        }
+    }
+
+    private fun logOnCreateView(
+        name: String,
+        replacedView: View?,
+        factory: NotifRemoteViewsFactory?
+    ) {
+        if (SPEW && replacedView != null && factory != null) {
+            Log.d(TAG, "$factory produced view for $name: $replacedView")
+        }
+    }
+
+    private companion object {
+        private const val TAG = "NotifLayoutInflaterFac"
+        private val SPEW = Log.isLoggable(TAG, Log.VERBOSE)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewsFactory.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewsFactory.kt
new file mode 100644
index 0000000..eebd4d4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewsFactory.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.row
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.View
+
+/** Interface used to create replacement view instances in Notification RemoteViews. */
+interface NotifRemoteViewsFactory {
+
+    /** return the replacement view instance for the given view name */
+    fun instantiate(parent: View?, name: String, context: Context, attrs: AttributeSet): View?
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
index b4bfded..0ad77bd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
@@ -79,6 +79,7 @@
     private final ConversationNotificationProcessor mConversationProcessor;
     private final Executor mBgExecutor;
     private final SmartReplyStateInflater mSmartReplyStateInflater;
+    private final NotifLayoutInflaterFactory mNotifLayoutInflaterFactory;
 
     @Inject
     NotificationContentInflater(
@@ -87,13 +88,15 @@
             ConversationNotificationProcessor conversationProcessor,
             MediaFeatureFlag mediaFeatureFlag,
             @Background Executor bgExecutor,
-            SmartReplyStateInflater smartRepliesInflater) {
+            SmartReplyStateInflater smartRepliesInflater,
+            NotifLayoutInflaterFactory notifLayoutInflaterFactory) {
         mRemoteViewCache = remoteViewCache;
         mRemoteInputManager = remoteInputManager;
         mConversationProcessor = conversationProcessor;
         mIsMediaInQS = mediaFeatureFlag.getEnabled();
         mBgExecutor = bgExecutor;
         mSmartReplyStateInflater = smartRepliesInflater;
+        mNotifLayoutInflaterFactory = notifLayoutInflaterFactory;
     }
 
     @Override
@@ -137,7 +140,8 @@
                 callback,
                 mRemoteInputManager.getRemoteViewsOnClickHandler(),
                 mIsMediaInQS,
-                mSmartReplyStateInflater);
+                mSmartReplyStateInflater,
+                mNotifLayoutInflaterFactory);
         if (mInflateSynchronously) {
             task.onPostExecute(task.doInBackground());
         } else {
@@ -160,7 +164,8 @@
                 bindParams.isLowPriority,
                 bindParams.usesIncreasedHeight,
                 bindParams.usesIncreasedHeadsUpHeight,
-                packageContext);
+                packageContext,
+                mNotifLayoutInflaterFactory);
 
         result = inflateSmartReplyViews(result, reInflateFlags, entry,
                 row.getContext(), packageContext,
@@ -298,7 +303,8 @@
 
     private static InflationProgress createRemoteViews(@InflationFlag int reInflateFlags,
             Notification.Builder builder, boolean isLowPriority, boolean usesIncreasedHeight,
-            boolean usesIncreasedHeadsUpHeight, Context packageContext) {
+            boolean usesIncreasedHeadsUpHeight, Context packageContext,
+            NotifLayoutInflaterFactory notifLayoutInflaterFactory) {
         InflationProgress result = new InflationProgress();
 
         if ((reInflateFlags & FLAG_CONTENT_VIEW_CONTRACTED) != 0) {
@@ -316,7 +322,7 @@
         if ((reInflateFlags & FLAG_CONTENT_VIEW_PUBLIC) != 0) {
             result.newPublicView = builder.makePublicContentView(isLowPriority);
         }
-
+        setNotifsViewsInflaterFactory(result, notifLayoutInflaterFactory);
         result.packageContext = packageContext;
         result.headsUpStatusBarText = builder.getHeadsUpStatusBarText(false /* showingPublic */);
         result.headsUpStatusBarTextPublic = builder.getHeadsUpStatusBarText(
@@ -324,6 +330,22 @@
         return result;
     }
 
+    private static void setNotifsViewsInflaterFactory(InflationProgress result,
+            NotifLayoutInflaterFactory notifLayoutInflaterFactory) {
+        setRemoteViewsInflaterFactory(result.newContentView, notifLayoutInflaterFactory);
+        setRemoteViewsInflaterFactory(result.newExpandedView,
+                notifLayoutInflaterFactory);
+        setRemoteViewsInflaterFactory(result.newHeadsUpView, notifLayoutInflaterFactory);
+        setRemoteViewsInflaterFactory(result.newPublicView, notifLayoutInflaterFactory);
+    }
+
+    private static void setRemoteViewsInflaterFactory(RemoteViews remoteViews,
+            NotifLayoutInflaterFactory notifLayoutInflaterFactory) {
+        if (remoteViews != null) {
+            remoteViews.setLayoutInflaterFactory(notifLayoutInflaterFactory);
+        }
+    }
+
     private static CancellationSignal apply(
             Executor bgExecutor,
             boolean inflateSynchronously,
@@ -348,7 +370,6 @@
                 public void setResultView(View v) {
                     result.inflatedContentView = v;
                 }
-
                 @Override
                 public RemoteViews getRemoteView() {
                     return result.newContentView;
@@ -356,7 +377,7 @@
             };
             applyRemoteView(bgExecutor, inflateSynchronously, result, reInflateFlags, flag,
                     remoteViewCache, entry, row, isNewView, remoteViewClickHandler, callback,
-                    privateLayout,  privateLayout.getContractedChild(),
+                    privateLayout, privateLayout.getContractedChild(),
                     privateLayout.getVisibleWrapper(
                             NotificationContentView.VISIBLE_TYPE_CONTRACTED),
                     runningInflations, applyCallback);
@@ -474,7 +495,6 @@
                             parentLayout,
                             remoteViewClickHandler);
                     validateView(v, entry, row.getResources());
-                    v.setIsRootNamespace(true);
                     applyCallback.setResultView(v);
                 } else {
                     newContentView.reapply(
@@ -511,7 +531,6 @@
                     return;
                 }
                 if (isNewView) {
-                    v.setIsRootNamespace(true);
                     applyCallback.setResultView(v);
                 } else if (existingWrapper != null) {
                     existingWrapper.onReinflated();
@@ -760,8 +779,8 @@
      * @param oldView The old view that was applied to the existing view before
      * @return {@code true} if the RemoteViews are the same and the view can be reused to reapply.
      */
-     @VisibleForTesting
-     static boolean canReapplyRemoteView(final RemoteViews newView,
+    @VisibleForTesting
+    static boolean canReapplyRemoteView(final RemoteViews newView,
             final RemoteViews oldView) {
         return (newView == null && oldView == null) ||
                 (newView != null && oldView != null
@@ -802,6 +821,7 @@
         private final ConversationNotificationProcessor mConversationProcessor;
         private final boolean mIsMediaInQS;
         private final SmartReplyStateInflater mSmartRepliesInflater;
+        private final NotifLayoutInflaterFactory mNotifLayoutInflaterFactory;
 
         private AsyncInflationTask(
                 Executor bgExecutor,
@@ -817,7 +837,8 @@
                 InflationCallback callback,
                 RemoteViews.InteractionHandler remoteViewClickHandler,
                 boolean isMediaFlagEnabled,
-                SmartReplyStateInflater smartRepliesInflater) {
+                SmartReplyStateInflater smartRepliesInflater,
+                NotifLayoutInflaterFactory notifLayoutInflaterFactory) {
             mEntry = entry;
             mRow = row;
             mBgExecutor = bgExecutor;
@@ -833,6 +854,7 @@
             mCallback = callback;
             mConversationProcessor = conversationProcessor;
             mIsMediaInQS = isMediaFlagEnabled;
+            mNotifLayoutInflaterFactory = notifLayoutInflaterFactory;
             entry.setInflationTask(this);
         }
 
@@ -876,7 +898,8 @@
                 }
                 InflationProgress inflationProgress = createRemoteViews(mReInflateFlags,
                         recoveredBuilder, mIsLowPriority, mUsesIncreasedHeight,
-                        mUsesIncreasedHeadsUpHeight, packageContext);
+                        mUsesIncreasedHeadsUpHeight, packageContext,
+                        mNotifLayoutInflaterFactory);
                 InflatedSmartReplyState previousSmartReplyState = mRow.getExistingSmartReplyState();
                 InflationProgress result = inflateSmartReplyViews(
                         inflationProgress,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
index 124df8c..20f4429 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
@@ -208,6 +208,23 @@
         mSmartReplyConstants = smartReplyConstants;
         mSmartReplyController = smartReplyController;
         mStatusBarService = statusBarService;
+        // We set root namespace so that we avoid searching children for id. Notification  might
+        // contain custom view and their ids may clash with ids already existing in shade or
+        // notification panel
+        setIsRootNamespace(true);
+    }
+
+    @Override
+    public View focusSearch(View focused, int direction) {
+        // This implementation is copied from ViewGroup but with removed special handling of
+        // setIsRootNamespace. This view is set as tree root using setIsRootNamespace and it
+        // causes focus to be stuck inside of it. We need to be root to avoid id conflicts
+        // but we don't want to behave like root when it comes to focusing.
+        if (mParent != null) {
+            return mParent.focusSearch(focused, direction);
+        }
+        Log.wtf(TAG, "NotificationContentView doesn't have parent");
+        return null;
     }
 
     public void reinflate() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowLogger.kt
index c3dd92a..4f5a04f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowLogger.kt
@@ -17,14 +17,21 @@
 
 package com.android.systemui.statusbar.notification.row
 
+import android.view.ViewGroup
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import com.android.systemui.log.dagger.NotificationLog
+import com.android.systemui.log.dagger.NotificationRenderLog
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.logKey
 import javax.inject.Inject
 
-class NotificationRowLogger @Inject constructor(@NotificationLog private val buffer: LogBuffer) {
+class NotificationRowLogger
+@Inject
+constructor(
+    @NotificationLog private val buffer: LogBuffer,
+    @NotificationRenderLog private val notificationRenderBuffer: LogBuffer
+) {
     fun logKeepInParentChildDetached(child: NotificationEntry, oldParent: NotificationEntry?) {
         buffer.log(
             TAG,
@@ -48,6 +55,79 @@
             { "Skipping to attach $str1 to $str2, because it still flagged to keep in parent" }
         )
     }
+
+    fun logRemoveTransientFromContainer(
+        childEntry: NotificationEntry,
+        containerEntry: NotificationEntry
+    ) {
+        notificationRenderBuffer.log(
+            TAG,
+            LogLevel.INFO,
+            {
+                str1 = childEntry.logKey
+                str2 = containerEntry.logKey
+            },
+            { "RemoveTransientRow from ChildrenContainer: childKey: $str1 -- containerKey: $str2" }
+        )
+    }
+
+    fun logRemoveTransientFromNssl(
+        childEntry: NotificationEntry,
+    ) {
+        notificationRenderBuffer.log(
+            TAG,
+            LogLevel.INFO,
+            { str1 = childEntry.logKey },
+            { "RemoveTransientRow from Nssl: childKey: $str1" }
+        )
+    }
+
+    fun logRemoveTransientFromViewGroup(
+        childEntry: NotificationEntry,
+        containerView: ViewGroup,
+    ) {
+        notificationRenderBuffer.log(
+            TAG,
+            LogLevel.WARNING,
+            {
+                str1 = childEntry.logKey
+                str2 = containerView.toString()
+            },
+            { "RemoveTransientRow from other ViewGroup: childKey: $str1 -- ViewGroup: $str2" }
+        )
+    }
+
+    fun logAddTransientRow(
+        childEntry: NotificationEntry,
+        containerEntry: NotificationEntry,
+        index: Int
+    ) {
+        notificationRenderBuffer.log(
+            TAG,
+            LogLevel.ERROR,
+            {
+                str1 = childEntry.logKey
+                str2 = containerEntry.logKey
+                int1 = index
+            },
+            { "addTransientRow to row: childKey: $str1 -- containerKey: $str2 -- index: $int1" }
+        )
+    }
+
+    fun logRemoveTransientRow(
+        childEntry: NotificationEntry,
+        containerEntry: NotificationEntry,
+    ) {
+        notificationRenderBuffer.log(
+            TAG,
+            LogLevel.ERROR,
+            {
+                str1 = childEntry.logKey
+                str2 = containerEntry.logKey
+            },
+            { "removeTransientRow from row: childKey: $str1 -- containerKey: $str2" }
+        )
+    }
 }
 
 private const val TAG = "NotifRow"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java
index 111b575..867e08b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java
@@ -17,15 +17,27 @@
 package com.android.systemui.statusbar.notification.row;
 
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
 
 import dagger.Binds;
 import dagger.Module;
+import dagger.Provides;
+import dagger.multibindings.ElementsIntoSet;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.inject.Named;
 
 /**
  * Dagger Module containing notification row and view inflation implementations.
  */
 @Module
 public abstract class NotificationRowModule {
+    public static final String NOTIF_REMOTEVIEWS_FACTORIES =
+            "notif_remoteviews_factories";
+
     /**
      * Provides notification row content binder instance.
      */
@@ -41,4 +53,19 @@
     @SysUISingleton
     public abstract NotifRemoteViewCache provideNotifRemoteViewCache(
             NotifRemoteViewCacheImpl cacheImpl);
+
+    /** Provides view factories to be inflated in notification content. */
+    @Provides
+    @ElementsIntoSet
+    @Named(NOTIF_REMOTEVIEWS_FACTORIES)
+    static Set<NotifRemoteViewsFactory> provideNotifRemoteViewsFactories(
+            FeatureFlags featureFlags,
+            PrecomputedTextViewFactory precomputedTextViewFactory
+    ) {
+        final Set<NotifRemoteViewsFactory> replacementFactories = new HashSet<>();
+        if (featureFlags.isEnabled(Flags.PRECOMPUTED_TEXT)) {
+            replacementFactories.add(precomputedTextViewFactory);
+        }
+        return replacementFactories;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PrecomputedImageFloatingTextView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PrecomputedImageFloatingTextView.kt
new file mode 100644
index 0000000..57c82dc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PrecomputedImageFloatingTextView.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row
+
+import android.content.Context
+import android.util.AttributeSet
+import android.widget.RemoteViews
+import com.android.internal.widget.ImageFloatingTextView
+
+/** Precomputed version of [ImageFloatingTextView] */
+@RemoteViews.RemoteView
+class PrecomputedImageFloatingTextView
+@JvmOverloads
+constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) :
+    ImageFloatingTextView(context, attrs, defStyleAttr), TextPrecomputer {
+
+    override fun setTextAsync(text: CharSequence?): Runnable = precompute(this, text)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PrecomputedTextView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PrecomputedTextView.kt
new file mode 100644
index 0000000..8508b1f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PrecomputedTextView.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.statusbar.notification.row
+
+import android.content.Context
+import android.util.AttributeSet
+import android.widget.RemoteViews
+import android.widget.TextView
+
+/**
+ * A user interface element that uses the PrecomputedText API to display text in a notification,
+ * with the help of RemoteViews.
+ */
+@RemoteViews.RemoteView
+class PrecomputedTextView
+@JvmOverloads
+constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) :
+    TextView(context, attrs, defStyleAttr), TextPrecomputer {
+
+    override fun setTextAsync(text: CharSequence?): Runnable = precompute(this, text)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PrecomputedTextViewFactory.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PrecomputedTextViewFactory.kt
new file mode 100644
index 0000000..b002330
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PrecomputedTextViewFactory.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.statusbar.notification.row
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.View
+import android.widget.TextView
+import com.android.internal.widget.ImageFloatingTextView
+import javax.inject.Inject
+
+class PrecomputedTextViewFactory @Inject constructor() : NotifRemoteViewsFactory {
+    override fun instantiate(
+        parent: View?,
+        name: String,
+        context: Context,
+        attrs: AttributeSet
+    ): View? {
+        return when (name) {
+            TextView::class.java.name,
+            TextView::class.java.simpleName -> PrecomputedTextView(context, attrs)
+            ImageFloatingTextView::class.java.name ->
+                PrecomputedImageFloatingTextView(context, attrs)
+            else -> null
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStageLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStageLogger.kt
index 684a276..02627fd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStageLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStageLogger.kt
@@ -18,7 +18,7 @@
 
 import com.android.systemui.log.dagger.NotificationLog
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel.INFO
+import com.android.systemui.log.core.LogLevel.INFO
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.logKey
 import javax.inject.Inject
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 b24cec1..0c686be 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
@@ -235,7 +235,7 @@
 
     @Override
     public long performRemoveAnimation(long duration, long delay,
-            float translationDirection, boolean isHeadsUpAnimation, float endLocation,
+            float translationDirection, boolean isHeadsUpAnimation,
             Runnable onFinishedRunnable,
             AnimatorListenerAdapter animationListener) {
         // TODO: Use duration
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/TextPrecomputer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/TextPrecomputer.kt
new file mode 100644
index 0000000..49f4a33
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/TextPrecomputer.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.row
+
+import android.text.PrecomputedText
+import android.text.Spannable
+import android.util.Log
+import android.widget.TextView
+
+internal interface TextPrecomputer {
+    /**
+     * Creates PrecomputedText from given text and returns a runnable which sets precomputed text to
+     * the textview on main thread.
+     *
+     * @param text text to be converted to PrecomputedText
+     * @return Runnable that sets precomputed text on the main thread
+     */
+    fun precompute(
+        textView: TextView,
+        text: CharSequence?,
+        logException: Boolean = true
+    ): Runnable {
+        val precomputedText: Spannable? =
+            text?.let { PrecomputedText.create(it, textView.textMetricsParams) }
+
+        return Runnable {
+            try {
+                textView.text = precomputedText
+            } catch (exception: IllegalArgumentException) {
+                if (logException) {
+                    Log.wtf(
+                        /* tag = */ TAG,
+                        /* msg = */ "PrecomputedText setText failed for TextView:$textView",
+                        /* tr = */ exception
+                    )
+                }
+                textView.text = text
+            }
+        }
+    }
+
+    private companion object {
+        private const val TAG = "TextPrecomputer"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/ExpandableNotificationRowComponent.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/ExpandableNotificationRowComponent.java
index 1a7417a..3588621 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/ExpandableNotificationRowComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/ExpandableNotificationRowComponent.java
@@ -25,7 +25,6 @@
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowController;
-import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
 
 import dagger.Binds;
@@ -59,8 +58,6 @@
         Builder notificationEntry(NotificationEntry entry);
         @BindsInstance
         Builder onExpandClickListener(ExpandableNotificationRow.OnExpandClickListener presenter);
-        @BindsInstance
-        Builder listContainer(NotificationListContainer listContainer);
         ExpandableNotificationRowComponent build();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
index ef5e86f06..87205e2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
@@ -120,6 +120,11 @@
     }
 
     @Override
+    public int getClipHeight() {
+        return mView.getHeight();
+    }
+
+    @Override
     public void applyRoundnessAndInvalidate() {
         if (mRoundnessChangedListener != null) {
             // We cannot apply the rounded corner to this View, so our parents (in drawChild()) will
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractor.kt
index 014406f..4b89615 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractor.kt
@@ -17,26 +17,24 @@
 package com.android.systemui.statusbar.notification.shelf.domain.interactor
 
 import android.os.PowerManager
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthRepository
 import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.statusbar.LockscreenShadeTransitionController
 import com.android.systemui.statusbar.NotificationShelf
-import com.android.systemui.statusbar.phone.CentralSurfaces
-import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent
-import com.android.systemui.util.time.SystemClock
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
 
 /** Interactor for the [NotificationShelf] */
-@CentralSurfacesComponent.CentralSurfacesScope
+@SysUISingleton
 class NotificationShelfInteractor
 @Inject
 constructor(
     private val keyguardRepository: KeyguardRepository,
     private val deviceEntryFaceAuthRepository: DeviceEntryFaceAuthRepository,
-    private val centralSurfaces: CentralSurfaces,
-    private val systemClock: SystemClock,
+    private val powerInteractor: PowerInteractor,
     private val keyguardTransitionController: LockscreenShadeTransitionController,
 ) {
     /** Is the shelf showing on the keyguard? */
@@ -55,11 +53,7 @@
 
     /** Transition keyguard to the locked shade, triggered by the shelf. */
     fun goToLockedShadeFromShelf() {
-        centralSurfaces.wakeUpIfDozing(
-            systemClock.uptimeMillis(),
-            "SHADE_CLICK",
-            PowerManager.WAKE_REASON_GESTURE,
-        )
+        powerInteractor.wakeUpIfDozing("SHADE_CLICK", PowerManager.WAKE_REASON_GESTURE)
         keyguardTransitionController.goToLockedShade(null)
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt
index 12956ab..23a58d2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt
@@ -19,6 +19,7 @@
 import android.view.View
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
 import com.android.systemui.lifecycle.repeatWhenAttached
@@ -32,7 +33,6 @@
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
 import com.android.systemui.statusbar.phone.NotificationIconAreaController
 import com.android.systemui.statusbar.phone.NotificationIconContainer
-import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent.CentralSurfacesScope
 import javax.inject.Inject
 import kotlinx.coroutines.awaitCancellation
 import kotlinx.coroutines.launch
@@ -43,7 +43,7 @@
  * [NotificationShelfController] interface. Once the [LegacyNotificationShelfControllerImpl] is
  * removed, this class can go away and the ViewBinder can be used directly.
  */
-@CentralSurfacesScope
+@SysUISingleton
 class NotificationShelfViewBinderWrapperControllerImpl @Inject constructor() :
     NotificationShelfController {
 
@@ -81,7 +81,7 @@
         ActivatableNotificationViewBinder.bind(viewModel, shelf, falsingManager)
         shelf.apply {
             setRefactorFlagEnabled(true)
-            setSensitiveRevealAnimEndabled(featureFlags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM))
+            setSensitiveRevealAnimEnabled(featureFlags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM))
             // TODO(278765923): Replace with eventual NotificationIconContainerViewBinder#bind()
             notificationIconAreaController.setShelfIcons(shelfIcons)
             repeatWhenAttached {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModel.kt
index fb19443..5ca8b53 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModel.kt
@@ -16,16 +16,16 @@
 
 package com.android.systemui.statusbar.notification.shelf.ui.viewmodel
 
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.statusbar.NotificationShelf
 import com.android.systemui.statusbar.notification.row.ui.viewmodel.ActivatableNotificationViewModel
 import com.android.systemui.statusbar.notification.shelf.domain.interactor.NotificationShelfInteractor
-import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent.CentralSurfacesScope
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.map
 
 /** ViewModel for [NotificationShelf]. */
-@CentralSurfacesScope
+@SysUISingleton
 class NotificationShelfViewModel
 @Inject
 constructor(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt
index b8f28b5..a8d8a8e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt
@@ -30,8 +30,8 @@
  */
 class MediaContainerView(context: Context, attrs: AttributeSet?) : ExpandableView(context, attrs) {
 
+    override var clipHeight = 0
     var cornerRadius = 0f
-    var clipHeight = 0
     var clipRect = RectF()
     var clipPath = Path()
 
@@ -69,11 +69,14 @@
         canvas.clipPath(clipPath)
     }
 
-
-    override fun performRemoveAnimation(duration: Long, delay: Long, translationDirection: Float,
-                                        isHeadsUpAnimation: Boolean, endLocation: Float,
-                                        onFinishedRunnable: Runnable?,
-                                        animationListener: AnimatorListenerAdapter?): Long {
+    override fun performRemoveAnimation(
+            duration: Long,
+            delay: Long,
+            translationDirection: Float,
+            isHeadsUpAnimation: Boolean,
+            onFinishedRunnable: Runnable?,
+            animationListener: AnimatorListenerAdapter?
+    ): Long {
         return 0
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
index f8e374d..626f851 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
@@ -132,6 +132,8 @@
     private boolean mContainingNotificationIsFaded = false;
     private RoundableState mRoundableState;
 
+    private NotificationChildrenContainerLogger mLogger;
+
     public NotificationChildrenContainer(Context context) {
         this(context, null);
     }
@@ -190,6 +192,11 @@
     }
 
     @Override
+    public int getClipHeight() {
+        return Math.max(mActualHeight - mClipBottomAmount, 0);
+    }
+
+    @Override
     protected void onLayout(boolean changed, int l, int t, int r, int b) {
         int childCount =
                 Math.min(mAttachedChildren.size(), NUMBER_OF_CHILDREN_WHEN_CHILDREN_EXPANDED);
@@ -1507,6 +1514,33 @@
         return mNotificationHeaderWrapper;
     }
 
+    public void setLogger(NotificationChildrenContainerLogger logger) {
+        mLogger = logger;
+    }
+
+    @Override
+    public void addTransientView(View view, int index) {
+        if (mLogger != null && view instanceof ExpandableNotificationRow) {
+            mLogger.addTransientRow(
+                    ((ExpandableNotificationRow) view).getEntry(),
+                    getContainingNotification().getEntry(),
+                    index
+            );
+        }
+        super.addTransientView(view, index);
+    }
+
+    @Override
+    public void removeTransientView(View view) {
+        if (mLogger != null && view instanceof ExpandableNotificationRow) {
+            mLogger.removeTransientRow(
+                    ((ExpandableNotificationRow) view).getEntry(),
+                    getContainingNotification().getEntry()
+            );
+        }
+        super.removeTransientView(view);
+    }
+
     public String debugString() {
         return TAG + " { "
                 + "visibility: " + getVisibility()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerLogger.kt
new file mode 100644
index 0000000..4986b63
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerLogger.kt
@@ -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.systemui.statusbar.notification.stack
+
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.dagger.NotificationRenderLog
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.logKey
+import javax.inject.Inject
+
+class NotificationChildrenContainerLogger
+@Inject
+constructor(@NotificationRenderLog private val notificationRenderBuffer: LogBuffer) {
+    fun addTransientRow(
+        childEntry: NotificationEntry,
+        containerEntry: NotificationEntry,
+        index: Int
+    ) {
+        notificationRenderBuffer.log(
+            TAG,
+            LogLevel.INFO,
+            {
+                str1 = childEntry.logKey
+                str2 = containerEntry.logKey
+                int1 = index
+            },
+            { "addTransientRow: childKey: $str1 -- containerKey: $str2 -- index: $int1" }
+        )
+    }
+
+    fun removeTransientRow(
+        childEntry: NotificationEntry,
+        containerEntry: NotificationEntry,
+    ) {
+        notificationRenderBuffer.log(
+            TAG,
+            LogLevel.INFO,
+            {
+                str1 = childEntry.logKey
+                str2 = containerEntry.logKey
+            },
+            { "removeTransientRow: childKey: $str1 -- containerKey: $str2" }
+        )
+    }
+
+    companion object {
+        private const val TAG = "NotifChildrenContainer"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsLogger.kt
index f953187..2da5582 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsLogger.kt
@@ -19,7 +19,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.log.dagger.NotificationSectionLog
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import javax.inject.Inject
 
 private const val TAG = "NotifSections"
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 df6fe3d..c1ceb3c 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
@@ -104,13 +104,13 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
 import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
+import com.android.systemui.statusbar.notification.init.NotificationsController;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.ExpandableView;
 import com.android.systemui.statusbar.notification.row.FooterView;
 import com.android.systemui.statusbar.notification.row.StackScrollerDecorView;
-import com.android.systemui.statusbar.phone.CentralSurfaces;
 import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
 import com.android.systemui.statusbar.phone.HeadsUpTouchHelper;
 import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
@@ -197,7 +197,6 @@
      */
     private Set<Integer> mDebugTextUsedYPositions;
     private final boolean mDebugRemoveAnimation;
-    private final boolean mSimplifiedAppearFraction;
     private final boolean mSensitiveRevealAnimEndabled;
     private boolean mAnimatedInsets;
 
@@ -316,7 +315,7 @@
         }
     };
     private NotificationStackScrollLogger mLogger;
-    private CentralSurfaces mCentralSurfaces;
+    private NotificationsController mNotificationsController;
     private ActivityStarter mActivityStarter;
     private final int[] mTempInt2 = new int[2];
     private boolean mGenerateChildOrderChangedEvent;
@@ -621,7 +620,6 @@
         FeatureFlags featureFlags = Dependency.get(FeatureFlags.class);
         mDebugLines = featureFlags.isEnabled(Flags.NSSL_DEBUG_LINES);
         mDebugRemoveAnimation = featureFlags.isEnabled(Flags.NSSL_DEBUG_REMOVE_ANIMATION);
-        mSimplifiedAppearFraction = featureFlags.isEnabled(Flags.SIMPLIFIED_APPEAR_FRACTION);
         mSensitiveRevealAnimEndabled = featureFlags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM);
         setAnimatedInsetsEnabled(featureFlags.isEnabled(Flags.ANIMATED_NOTIFICATION_SHADE_INSETS));
         mSectionsManager = Dependency.get(NotificationSectionsManager.class);
@@ -1638,14 +1636,6 @@
         return mAmbientState.getTrackedHeadsUpRow() != null;
     }
 
-    // TODO(b/246353296): remove it when Flags.SIMPLIFIED_APPEAR_FRACTION is removed
-    public float calculateAppearFractionOld(float height) {
-        float appearEndPosition = getAppearEndPosition();
-        float appearStartPosition = getAppearStartPosition();
-        return (height - appearStartPosition)
-                / (appearEndPosition - appearStartPosition);
-    }
-
     /**
      * @param height the height of the panel
      * @return Fraction of the appear animation that has been performed. Normally follows expansion
@@ -1653,33 +1643,24 @@
      * when HUN is swiped up.
      */
     @FloatRange(from = -1.0, to = 1.0)
-    public float simplifiedAppearFraction(float height) {
+    public float calculateAppearFraction(float height) {
         if (isHeadsUpTransition()) {
             // HUN is a special case because fraction can go negative if swiping up. And for now
             // it must go negative as other pieces responsible for proper translation up assume
             // negative value for HUN going up.
             // 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. Let's use old implementation for now and
-            // see if we can figure out something better
-            return MathUtils.constrain(calculateAppearFractionOld(height), -1, 1);
+            // and that makes translation jump immediately.
+            float appearEndPosition = getAppearEndPosition();
+            float appearStartPosition = getAppearStartPosition();
+            float hunAppearFraction = (height - appearStartPosition)
+                    / (appearEndPosition - appearStartPosition);
+            return MathUtils.constrain(hunAppearFraction, -1, 1);
         } else {
             return mAmbientState.getExpansionFraction();
         }
     }
 
-    public float calculateAppearFraction(float height) {
-        if (mSimplifiedAppearFraction) {
-            return simplifiedAppearFraction(height);
-        } else if (mShouldUseSplitNotificationShade) {
-            // for split shade we want to always use the new way of calculating appear fraction
-            // because without it heads-up experience is very broken and it's less risky change
-            return simplifiedAppearFraction(height);
-        } else {
-            return calculateAppearFractionOld(height);
-        }
-    }
-
     public float getStackTranslation() {
         return mStackTranslation;
     }
@@ -2778,6 +2759,7 @@
         boolean animationGenerated = container != null && generateRemoveAnimation(child);
         if (animationGenerated) {
             if (!mSwipedOutViews.contains(child) || !isFullySwipedOut(child)) {
+                logAddTransientChild(child, container);
                 container.addTransientView(child, 0);
                 child.setTransientContainer(container);
             }
@@ -2793,6 +2775,46 @@
         focusNextViewIfFocused(child);
     }
 
+    private void logAddTransientChild(ExpandableView child, ViewGroup container) {
+        if (mLogger == null) {
+            return;
+        }
+        if (child instanceof ExpandableNotificationRow) {
+            if (container instanceof NotificationChildrenContainer) {
+                mLogger.addTransientChildNotificationToChildContainer(
+                        ((ExpandableNotificationRow) child).getEntry(),
+                        ((NotificationChildrenContainer) container)
+                                .getContainingNotification().getEntry()
+                );
+            } else if (container instanceof NotificationStackScrollLayout) {
+                mLogger.addTransientChildNotificationToNssl(
+                        ((ExpandableNotificationRow) child).getEntry()
+                );
+            } else {
+                mLogger.addTransientChildNotificationToViewGroup(
+                        ((ExpandableNotificationRow) child).getEntry(),
+                        container
+                );
+            }
+        }
+    }
+
+    @Override
+    public void addTransientView(View view, int index) {
+        if (mLogger != null && view instanceof ExpandableNotificationRow) {
+            mLogger.addTransientRow(((ExpandableNotificationRow) view).getEntry(), index);
+        }
+        super.addTransientView(view, index);
+    }
+
+    @Override
+    public void removeTransientView(View view) {
+        if (mLogger != null && view instanceof ExpandableNotificationRow) {
+            mLogger.removeTransientRow(((ExpandableNotificationRow) view).getEntry());
+        }
+        super.removeTransientView(view);
+    }
+
     /**
      * Has this view been fully swiped out such that it's not visible anymore.
      */
@@ -3052,7 +3074,9 @@
         if (!animationsEnabled) {
             mSwipedOutViews.clear();
             mChildrenToRemoveAnimated.clear();
-            clearTemporaryViewsInGroup(this);
+            clearTemporaryViewsInGroup(
+                    /* viewGroup = */ this,
+                    /* reason = */ "setAnimationsEnabled");
         }
     }
 
@@ -3734,20 +3758,20 @@
             case MotionEvent.ACTION_UP:
                 if (mStatusBarState != StatusBarState.KEYGUARD && mTouchIsClick &&
                         isBelowLastNotification(mInitialTouchX, mInitialTouchY)) {
-                    debugLog("handleEmptySpaceClick: touch event propagated further");
+                    debugShadeLog("handleEmptySpaceClick: touch event propagated further");
                     mOnEmptySpaceClickListener.onEmptySpaceClicked(mInitialTouchX, mInitialTouchY);
                 }
                 break;
             default:
-                debugLog("handleEmptySpaceClick: MotionEvent ignored");
+                debugShadeLog("handleEmptySpaceClick: MotionEvent ignored");
         }
     }
 
-    private void debugLog(@CompileTimeConstant final String s) {
+    private void debugShadeLog(@CompileTimeConstant final String s) {
         if (mLogger == null) {
             return;
         }
-        mLogger.d(s);
+        mLogger.logShadeDebugEvent(s);
     }
 
     private void logEmptySpaceClick(MotionEvent ev, boolean isTouchBelowLastNotification,
@@ -4008,7 +4032,7 @@
         mAmbientState.setExpansionChanging(false);
         if (!mIsExpanded) {
             resetScrollPosition();
-            mCentralSurfaces.resetUserExpandedStates();
+            mNotificationsController.resetUserExpandedStates();
             clearTemporaryViews();
             clearUserLockedViews();
             resetAllSwipeState();
@@ -4027,26 +4051,48 @@
 
     private void clearTemporaryViews() {
         // lets make sure nothing is transient anymore
-        clearTemporaryViewsInGroup(this);
+        clearTemporaryViewsInGroup(
+                /* viewGroup = */ this,
+                /* reason = */ "clearTemporaryViews"
+        );
         for (int i = 0; i < getChildCount(); i++) {
             ExpandableView child = getChildAtIndex(i);
             if (child instanceof ExpandableNotificationRow) {
                 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
-                clearTemporaryViewsInGroup(row.getChildrenContainer());
+                clearTemporaryViewsInGroup(
+                        /* viewGroup = */ row.getChildrenContainer(),
+                        /* reason = */ "clearTemporaryViewsInGroup(row.getChildrenContainer())"
+                );
             }
         }
     }
 
-    private void clearTemporaryViewsInGroup(ViewGroup viewGroup) {
+    private void clearTemporaryViewsInGroup(ViewGroup viewGroup, String reason) {
         while (viewGroup != null && viewGroup.getTransientViewCount() != 0) {
             final View transientView = viewGroup.getTransientView(0);
             viewGroup.removeTransientView(transientView);
             if (transientView instanceof ExpandableView) {
                 ((ExpandableView) transientView).setTransientContainer(null);
+                if (transientView instanceof ExpandableNotificationRow) {
+                    logTransientNotificationRowTraversalCleaned(
+                            (ExpandableNotificationRow) transientView,
+                            reason
+                    );
+                }
             }
         }
     }
 
+    private void logTransientNotificationRowTraversalCleaned(
+            ExpandableNotificationRow transientView,
+            String reason
+    ) {
+        if (mLogger == null) {
+            return;
+        }
+        mLogger.transientNotificationRowTraversalCleaned(transientView.getEntry(), reason);
+    }
+
     void onPanelTrackingStarted() {
         mPanelTracking = true;
         mAmbientState.setPanelTracking(true);
@@ -4602,8 +4648,8 @@
         return max + getStackTranslation();
     }
 
-    public void setCentralSurfaces(CentralSurfaces centralSurfaces) {
-        this.mCentralSurfaces = centralSurfaces;
+    public void setNotificationsController(NotificationsController notificationsController) {
+        this.mNotificationsController = notificationsController;
     }
 
     public void setActivityStarter(ActivityStarter activityStarter) {
@@ -5070,6 +5116,9 @@
             println(pw, "intrinsicPadding", mIntrinsicPadding);
             println(pw, "topPadding", mTopPadding);
             println(pw, "bottomPadding", mBottomPadding);
+            println(pw, "translationX", getTranslationX());
+            println(pw, "translationY", getTranslationY());
+            println(pw, "translationZ", getTranslationZ());
             mNotificationStackSizeCalculator.dump(pw, args);
         });
         pw.println();
@@ -5870,7 +5919,17 @@
         Trace.beginSection("NSSL.resetAllSwipeState()");
         mSwipeHelper.resetTouchState();
         for (int i = 0; i < getChildCount(); i++) {
-            mSwipeHelper.forceResetSwipeState(getChildAt(i));
+            View child = getChildAt(i);
+            mSwipeHelper.forceResetSwipeState(child);
+            if (child instanceof ExpandableNotificationRow) {
+                ExpandableNotificationRow childRow = (ExpandableNotificationRow) child;
+                List<ExpandableNotificationRow> grandchildren = childRow.getAttachedChildren();
+                if (grandchildren != null) {
+                    for (ExpandableNotificationRow grandchild : grandchildren) {
+                        mSwipeHelper.forceResetSwipeState(grandchild);
+                    }
+                }
+            }
         }
         updateContinuousShadowDrawing();
         Trace.endSection();
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 11ba753..ef7375a 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
@@ -29,6 +29,7 @@
 import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_HIGH_PRIORITY;
 import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.SelectedRows;
 import static com.android.systemui.statusbar.phone.NotificationIconAreaController.HIGH_PRIORITY;
+import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
 
 import android.content.res.Configuration;
 import android.content.res.Resources;
@@ -56,11 +57,18 @@
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.systemui.ExpandHelper;
 import com.android.systemui.Gefingerpoken;
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
 import com.android.systemui.classifier.Classifier;
 import com.android.systemui.classifier.FalsingCollector;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
+import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository;
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
+import com.android.systemui.keyguard.shared.model.KeyguardState;
+import com.android.systemui.keyguard.shared.model.TransitionStep;
 import com.android.systemui.media.controls.ui.KeyguardMediaController;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.FalsingManager;
@@ -69,6 +77,7 @@
 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shade.ShadeController;
+import com.android.systemui.shade.ShadeViewController;
 import com.android.systemui.statusbar.LockscreenShadeTransitionController;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager.UserChangedListener;
@@ -98,6 +107,7 @@
 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.init.NotificationsController;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -107,14 +117,12 @@
 import com.android.systemui.statusbar.notification.row.NotificationSnooze;
 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.CentralSurfaces;
 import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
 import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
 import com.android.systemui.statusbar.phone.HeadsUpTouchHelper;
 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.phone.dagger.CentralSurfacesComponent;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
@@ -137,13 +145,14 @@
 /**
  * Controller for {@link NotificationStackScrollLayout}.
  */
-@CentralSurfacesComponent.CentralSurfacesScope
+@SysUISingleton
 public class NotificationStackScrollLayoutController {
     private static final String TAG = "StackScrollerController";
     private static final boolean DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG);
 
     private final boolean mAllowLongPress;
     private final NotificationGutsManager mNotificationGutsManager;
+    private final NotificationsController mNotificationsController;
     private final NotificationVisibilityProvider mVisibilityProvider;
     private final HeadsUpManagerPhone mHeadsUpManager;
     private final NotificationRoundnessManager mNotificationRoundnessManager;
@@ -170,9 +179,9 @@
     private final KeyguardMediaController mKeyguardMediaController;
     private final SysuiStatusBarStateController mStatusBarStateController;
     private final KeyguardBypassController mKeyguardBypassController;
+    private final KeyguardInteractor mKeyguardInteractor;
+    private final PrimaryBouncerInteractor mPrimaryBouncerInteractor;
     private final NotificationLockscreenUserManager mLockscreenUserManager;
-    // TODO: CentralSurfaces should be encapsulated behind a Controller
-    private final CentralSurfaces mCentralSurfaces;
     private final SectionHeaderController mSilentHeaderController;
     private final LockscreenShadeTransitionController mLockscreenShadeTransitionController;
     private final InteractionJankMonitor mJankMonitor;
@@ -184,13 +193,17 @@
     private final GroupExpansionManager mGroupExpansionManager;
     private final NotifPipelineFlags mNotifPipelineFlags;
     private final SeenNotificationsProvider mSeenNotificationsProvider;
+    private final KeyguardTransitionRepository mKeyguardTransitionRepo;
 
     private NotificationStackScrollLayout mView;
     private NotificationSwipeHelper mSwipeHelper;
     @Nullable
     private Boolean mHistoryEnabled;
     private int mBarState;
+    private boolean mIsBouncerShowingFromCentralSurfaces;
     private HeadsUpAppearanceController mHeadsUpAppearanceController;
+    private boolean mIsInTransitionToAod = false;
+
     private final FeatureFlags mFeatureFlags;
     private final NotificationTargetsHelper mNotificationTargetsHelper;
     private final SecureSettings mSecureSettings;
@@ -430,7 +443,7 @@
                 @Override
                 public void onSnooze(StatusBarNotification sbn,
                                      NotificationSwipeActionHelper.SnoozeOption snoozeOption) {
-                    mCentralSurfaces.setNotificationSnoozed(sbn, snoozeOption);
+                    mNotificationsController.setNotificationSnoozed(sbn, snoozeOption);
                 }
 
                 @Override
@@ -561,7 +574,8 @@
 
                 @Override
                 public float getFalsingThresholdFactor() {
-                    return mCentralSurfaces.isWakeUpComingFromTouch() ? 1.5f : 1.0f;
+                    return ShadeViewController.getFalsingThresholdFactor(
+                            mKeyguardInteractor.getWakefulnessModel().getValue());
                 }
 
                 @Override
@@ -615,6 +629,7 @@
             NotificationStackScrollLayout view,
             @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowLongPress,
             NotificationGutsManager notificationGutsManager,
+            NotificationsController notificationsController,
             NotificationVisibilityProvider visibilityProvider,
             HeadsUpManagerPhone headsUpManager,
             NotificationRoundnessManager notificationRoundnessManager,
@@ -625,6 +640,9 @@
             SysuiStatusBarStateController statusBarStateController,
             KeyguardMediaController keyguardMediaController,
             KeyguardBypassController keyguardBypassController,
+            KeyguardInteractor keyguardInteractor,
+            PrimaryBouncerInteractor primaryBouncerInteractor,
+            KeyguardTransitionRepository keyguardTransitionRepo,
             ZenModeController zenModeController,
             NotificationLockscreenUserManager lockscreenUserManager,
             Optional<NotificationListViewModel> nsslViewModel,
@@ -634,7 +652,6 @@
             FalsingManager falsingManager,
             @Main Resources resources,
             NotificationSwipeHelper.Builder notificationSwipeHelperBuilder,
-            CentralSurfaces centralSurfaces,
             ScrimController scrimController,
             GroupExpansionManager groupManager,
             @SilentHeader SectionHeaderController silentHeaderController,
@@ -658,10 +675,12 @@
             NotificationDismissibilityProvider dismissibilityProvider,
             ActivityStarter activityStarter) {
         mView = view;
+        mKeyguardTransitionRepo = keyguardTransitionRepo;
         mStackStateLogger = stackLogger;
         mLogger = logger;
         mAllowLongPress = allowLongPress;
         mNotificationGutsManager = notificationGutsManager;
+        mNotificationsController = notificationsController;
         mVisibilityProvider = visibilityProvider;
         mHeadsUpManager = headsUpManager;
         mNotificationRoundnessManager = notificationRoundnessManager;
@@ -672,6 +691,8 @@
         mStatusBarStateController = statusBarStateController;
         mKeyguardMediaController = keyguardMediaController;
         mKeyguardBypassController = keyguardBypassController;
+        mKeyguardInteractor = keyguardInteractor;
+        mPrimaryBouncerInteractor = primaryBouncerInteractor;
         mZenModeController = zenModeController;
         mLockscreenUserManager = lockscreenUserManager;
         mViewModel = nsslViewModel;
@@ -682,7 +703,6 @@
         mFalsingManager = falsingManager;
         mResources = resources;
         mNotificationSwipeHelperBuilder = notificationSwipeHelperBuilder;
-        mCentralSurfaces = centralSurfaces;
         mScrimController = scrimController;
         mJankMonitor = jankMonitor;
         mNotificationStackSizeCalculator = notificationStackSizeCalculator;
@@ -711,7 +731,7 @@
         mView.setController(this);
         mView.setLogger(mLogger);
         mView.setTouchHandler(new TouchHandler());
-        mView.setCentralSurfaces(mCentralSurfaces);
+        mView.setNotificationsController(mNotificationsController);
         mView.setActivityStarter(mActivityStarter);
         mView.setClearAllAnimationListener(this::onAnimationEnd);
         mView.setClearAllListener((selection) -> mUiEventLogger.log(
@@ -810,6 +830,9 @@
         mViewModel.ifPresent(
                 vm -> NotificationListViewBinder
                         .bind(mView, vm, mFalsingManager, mFeatureFlags, mNotifIconAreaController));
+
+        collectFlow(mView, mKeyguardTransitionRepo.getTransitions(),
+                this::onKeyguardTransitionChanged);
     }
 
     private boolean isInVisibleLocation(NotificationEntry entry) {
@@ -1198,6 +1221,14 @@
     }
 
     /**
+     * Sets whether the bouncer is currently showing. Should only be called from
+     * {@link CentralSurfaces}.
+     */
+    public void setBouncerShowingFromCentralSurfaces(boolean bouncerShowing) {
+        mIsBouncerShowingFromCentralSurfaces = bouncerShowing;
+    }
+
+    /**
      * Set the visibility of the view, and propagate it to specific children.
      *
      * @param visible either the view is visible or not.
@@ -1224,11 +1255,12 @@
 
         final boolean shouldShow = getVisibleNotificationCount() == 0
                 && !mView.isQsFullScreen()
-                // Hide empty shade view when in transition to Keyguard.
+                // Hide empty shade view when in transition to AOD.
                 // That avoids "No Notifications" to blink when transitioning to AOD.
                 // For more details, see: b/228790482
-                && !isInTransitionToKeyguard()
-                && !mCentralSurfaces.isBouncerShowing();
+                && !mIsInTransitionToAod
+                // Don't show any notification content if the bouncer is showing. See b/267060171.
+                && !isBouncerShowing();
 
         mView.updateEmptyShadeView(shouldShow, mZenModeController.areNotificationsHiddenInShade());
 
@@ -1236,6 +1268,24 @@
     }
 
     /**
+     * Returns whether the bouncer is currently showing.
+     *
+     * There's a possible timing difference between when CentralSurfaces marks the bouncer as not
+     * showing and when PrimaryBouncerInteractor marks the bouncer as not showing. (CentralSurfaces
+     * appears to mark the bouncer as showing for 10-200ms longer than PrimaryBouncerInteractor.)
+     *
+     * This timing difference could be load bearing, which is why we have a feature flag protecting
+     * where we fetch the value from. This flag is intended to be short-lived.
+     */
+    private boolean isBouncerShowing() {
+        if (mFeatureFlags.isEnabled(Flags.USE_REPOS_FOR_BOUNCER_SHOWING)) {
+            return mPrimaryBouncerInteractor.isBouncerShowing();
+        } else {
+            return mIsBouncerShowingFromCentralSurfaces;
+        }
+    }
+
+    /**
      * Update the importantForAccessibility of NotificationStackScrollLayout.
      * <p>
      * We want the NSSL to be unimportant for accessibility when there's no
@@ -1426,7 +1476,7 @@
         return mNotificationRoundnessManager;
     }
 
-    NotificationListContainer getNotificationListContainer() {
+    public NotificationListContainer getNotificationListContainer() {
         return mNotificationListContainer;
     }
 
@@ -1607,6 +1657,16 @@
         return shelf == null ? 0 : shelf.getIntrinsicHeight();
     }
 
+    @VisibleForTesting
+    void onKeyguardTransitionChanged(TransitionStep transitionStep) {
+        boolean isTransitionToAod = transitionStep.getFrom().equals(KeyguardState.GONE)
+                && transitionStep.getTo().equals(KeyguardState.AOD);
+        if (mIsInTransitionToAod != isTransitionToAod) {
+            mIsInTransitionToAod = isTransitionToAod;
+            updateShowEmptyShadeView();
+        }
+    }
+
     /**
      * Enum for UiEvent logged from this class
      */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutListContainerModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutListContainerModule.java
deleted file mode 100644
index 3dcaae2..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutListContainerModule.java
+++ /dev/null
@@ -1,32 +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.systemui.statusbar.notification.stack;
-
-import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent;
-
-import dagger.Module;
-import dagger.Provides;
-
-@Module
-public abstract class NotificationStackScrollLayoutListContainerModule {
-    @Provides
-    @CentralSurfacesComponent.CentralSurfacesScope
-    static NotificationListContainer provideListContainer(
-            NotificationStackScrollLayoutController nsslController) {
-        return nsslController.getNotificationListContainer();
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt
index 5b0ec1d..3396306 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt
@@ -1,9 +1,13 @@
 package com.android.systemui.statusbar.notification.stack
 
-import com.android.systemui.log.dagger.NotificationHeadsUpLog
+import android.view.ViewGroup
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel.DEBUG
-import com.android.systemui.log.LogLevel.INFO
+import com.android.systemui.log.core.LogLevel.DEBUG
+import com.android.systemui.log.core.LogLevel.INFO
+import com.android.systemui.log.core.LogLevel.ERROR
+import com.android.systemui.log.dagger.NotificationHeadsUpLog
+import com.android.systemui.log.dagger.NotificationRenderLog
+import com.android.systemui.log.dagger.ShadeLog
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.logKey
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_ADD
@@ -15,7 +19,9 @@
 import javax.inject.Inject
 
 class NotificationStackScrollLogger @Inject constructor(
-    @NotificationHeadsUpLog private val buffer: LogBuffer
+    @NotificationHeadsUpLog private val buffer: LogBuffer,
+    @NotificationRenderLog private val notificationRenderBuffer: LogBuffer,
+    @ShadeLog private val shadeLogBuffer: LogBuffer,
 ) {
     fun hunAnimationSkipped(entry: NotificationEntry, reason: String) {
         buffer.log(TAG, INFO, {
@@ -59,7 +65,7 @@
         })
     }
 
-    fun d(@CompileTimeConstant msg: String) = buffer.log(TAG, DEBUG, msg)
+    fun logShadeDebugEvent(@CompileTimeConstant msg: String) = shadeLogBuffer.log(TAG, DEBUG, msg)
 
     fun logEmptySpaceClick(
         isBelowLastNotification: Boolean,
@@ -67,7 +73,7 @@
         touchIsClick: Boolean,
         motionEventDesc: String
     ) {
-        buffer.log(TAG, DEBUG, {
+        shadeLogBuffer.log(TAG, DEBUG, {
             int1 = statusBarState
             bool1 = touchIsClick
             bool2 = isBelowLastNotification
@@ -77,6 +83,79 @@
                     "isTouchBelowNotification: $bool2 motionEvent: $str1"
         })
     }
+
+    fun transientNotificationRowTraversalCleaned(entry: NotificationEntry, reason: String) {
+        notificationRenderBuffer.log(TAG, INFO, {
+            str1 = entry.logKey
+            str2 = reason
+        }, {
+            "transientNotificationRowTraversalCleaned: key: $str1 reason: $str2"
+        })
+    }
+
+    fun addTransientChildNotificationToChildContainer(
+            childEntry: NotificationEntry,
+            containerEntry: NotificationEntry,
+    ) {
+        notificationRenderBuffer.log(TAG, INFO, {
+            str1 = childEntry.logKey
+            str2 = containerEntry.logKey
+        }, {
+            "addTransientChildToContainer from onViewRemovedInternal: childKey: $str1 " +
+                    "-- containerKey: $str2"
+        })
+    }
+
+    fun addTransientChildNotificationToNssl(
+            childEntry: NotificationEntry,
+    ) {
+        notificationRenderBuffer.log(TAG, INFO, {
+            str1 = childEntry.logKey
+        }, {
+            "addTransientRowToNssl from onViewRemovedInternal: childKey: $str1"
+        })
+    }
+
+    fun addTransientChildNotificationToViewGroup(
+            childEntry: NotificationEntry,
+            container: ViewGroup
+    ) {
+        notificationRenderBuffer.log(TAG, ERROR, {
+            str1 = childEntry.logKey
+            str2 = container.toString()
+        }, {
+            "addTransientRowTo unhandled ViewGroup from onViewRemovedInternal: childKey: $str1 " +
+                    "-- ViewGroup: $str2"
+        })
+    }
+
+    fun addTransientRow(
+            childEntry: NotificationEntry,
+            index: Int
+    ) {
+        notificationRenderBuffer.log(
+                TAG,
+                INFO,
+                {
+                    str1 = childEntry.logKey
+                    int1 = index
+                },
+                { "addTransientRow to NSSL: childKey: $str1 -- index: $int1" }
+        )
+    }
+
+    fun removeTransientRow(
+            childEntry: NotificationEntry,
+    ) {
+        notificationRenderBuffer.log(
+                TAG,
+                INFO,
+                {
+                    str1 = childEntry.logKey
+                },
+                { "removeTransientRow from NSSL: childKey: $str1" }
+        )
+    }
 }
 
 private const val TAG = "NotificationStackScroll"
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index efb7926..b2d26d9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -29,8 +29,6 @@
 import com.android.keyguard.BouncerPanelExpansionCalculator;
 import com.android.systemui.R;
 import com.android.systemui.animation.ShadeInterpolation;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
 import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
 import com.android.systemui.statusbar.EmptyShadeView;
 import com.android.systemui.statusbar.NotificationShelf;
@@ -189,9 +187,7 @@
 
     private float interpolateFooterAlpha(AmbientState ambientState) {
         float expansion = ambientState.getExpansionFraction();
-        FeatureFlags flags = ambientState.getFeatureFlags();
-        if (ambientState.isSmallScreen()
-                || !flags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION)) {
+        if (ambientState.isSmallScreen()) {
             return ShadeInterpolation.getContentAlpha(expansion);
         }
         LargeScreenShadeInterpolator interpolator = ambientState.getLargeScreenShadeInterpolator();
@@ -200,9 +196,7 @@
 
     private float interpolateNotificationContentAlpha(AmbientState ambientState) {
         float expansion = ambientState.getExpansionFraction();
-        FeatureFlags flags = ambientState.getFeatureFlags();
-        if (ambientState.isSmallScreen()
-                || !flags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION)) {
+        if (ambientState.isSmallScreen()) {
             return ShadeInterpolation.getContentAlpha(expansion);
         }
         LargeScreenShadeInterpolator interpolator = ambientState.getLargeScreenShadeInterpolator();
@@ -772,8 +766,9 @@
             boolean isTopEntry = topHeadsUpEntry == row;
             float unmodifiedEndLocation = childState.getYTranslation() + childState.height;
             if (mIsExpanded) {
-                if (row.mustStayOnScreen() && !childState.headsUpIsVisible
-                        && !row.showingPulsing() && !ambientState.isOnKeyguard()) {
+                if (shouldHunBeVisibleWhenScrolled(row.mustStayOnScreen(),
+                        childState.headsUpIsVisible, row.showingPulsing(),
+                        ambientState.isOnKeyguard(), row.getEntry().isStickyAndNotDemoted())) {
                     // Ensure that the heads up is always visible even when scrolled off
                     clampHunToTop(mQuickQsOffsetHeight, ambientState.getStackTranslation(),
                             row.getCollapsedHeight(), childState);
@@ -821,7 +816,15 @@
         }
     }
 
-    /**
+    @VisibleForTesting
+    boolean shouldHunBeVisibleWhenScrolled(boolean mustStayOnScreen, boolean headsUpIsVisible,
+            boolean showingPulsing, boolean isOnKeyguard, boolean headsUpOnKeyguard) {
+        return mustStayOnScreen && !headsUpIsVisible
+                        && !showingPulsing
+                        && (!isOnKeyguard || headsUpOnKeyguard);
+    }
+
+     /**
      * When shade is open and we are scrolled to the bottom of notifications,
      * clamp incoming HUN in its collapsed form, right below qs offset.
      * Transition pinned collapsed HUN to full height when scrolling back up.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
index f07dd00..2742a23 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
@@ -27,8 +27,6 @@
 import com.android.systemui.R;
 import com.android.systemui.shared.clocks.AnimatableClockView;
 import com.android.systemui.statusbar.NotificationShelf;
-import com.android.systemui.statusbar.StatusBarIconView;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.ExpandableView;
 import com.android.systemui.statusbar.notification.row.StackScrollerDecorView;
@@ -347,10 +345,12 @@
             final ExpandableView changingView = (ExpandableView) event.mChangingView;
             boolean loggable = false;
             boolean isHeadsUp = false;
+            boolean isGroupChild = false;
             String key = null;
             if (changingView instanceof ExpandableNotificationRow && mLogger != null) {
                 loggable = true;
                 isHeadsUp = ((ExpandableNotificationRow) changingView).isHeadsUp();
+                isGroupChild = changingView.isChildInGroup();
                 key = ((ExpandableNotificationRow) changingView).getEntry().getKey();
             }
             if (event.animationType ==
@@ -407,17 +407,25 @@
 
                 }
                 Runnable postAnimation = changingView::removeFromTransientContainer;
-                if (loggable && isHeadsUp) {
-                    mLogger.logHUNViewDisappearingWithRemoveEvent(key);
+                if (loggable) {
                     String finalKey = key;
-                    postAnimation = () -> {
-                        mLogger.disappearAnimationEnded(finalKey);
-                        changingView.removeFromTransientContainer();
-                    };
+                    if (isHeadsUp) {
+                        mLogger.logHUNViewDisappearingWithRemoveEvent(key);
+                        postAnimation = () -> {
+                            mLogger.disappearAnimationEnded(finalKey);
+                            changingView.removeFromTransientContainer();
+                        };
+                    } else if (isGroupChild) {
+                        mLogger.groupChildRemovalEventProcessed(key);
+                        postAnimation = () -> {
+                            mLogger.groupChildRemovalAnimationEnded(finalKey);
+                            changingView.removeFromTransientContainer();
+                        };
+                    }
                 }
                 changingView.performRemoveAnimation(ANIMATION_DURATION_APPEAR_DISAPPEAR,
                         0 /* delay */, translationDirection, false /* isHeadsUpAppear */,
-                        0, postAnimation, null);
+                        postAnimation, null);
             } else if (event.animationType ==
                 NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT) {
                 if (mHostLayout.isFullySwipedOut(changingView)) {
@@ -464,28 +472,12 @@
                     mTmpState.initFrom(changingView);
                     endRunnable = changingView::removeFromTransientContainer;
                 }
-                float targetLocation = 0;
                 boolean needsAnimation = true;
                 if (changingView instanceof ExpandableNotificationRow) {
                     ExpandableNotificationRow row = (ExpandableNotificationRow) changingView;
                     if (row.isDismissed()) {
                         needsAnimation = false;
                     }
-
-                    NotificationEntry entry = row.getEntry();
-                    StatusBarIconView icon = entry.getIcons().getStatusBarIcon();
-                    final StatusBarIconView centeredIcon = entry.getIcons().getCenteredIcon();
-                    if (centeredIcon != null && centeredIcon.getParent() != null) {
-                        icon = centeredIcon;
-                    }
-                    if (icon.getParent() != null) {
-                        icon.getLocationOnScreen(mTmpLocation);
-                        float iconPosition = mTmpLocation[0] - icon.getTranslationX()
-                                + ViewState.getFinalTranslationX(icon)
-                                + icon.getWidth() * 0.25f;
-                        mHostLayout.getLocationOnScreen(mTmpLocation);
-                        targetLocation = iconPosition - mTmpLocation[0];
-                    }
                 }
 
                 if (needsAnimation) {
@@ -505,7 +497,7 @@
                     }
                     long removeAnimationDelay = changingView.performRemoveAnimation(
                             ANIMATION_DURATION_HEADS_UP_DISAPPEAR,
-                            0, 0.0f, true /* isHeadsUpAppear */, targetLocation,
+                            0, 0.0f, true /* isHeadsUpAppear */,
                             postAnimation, getGlobalAnimationFinishedListener());
                     mAnimationProperties.delay += removeAnimationDelay;
                 } else if (endRunnable != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt
index cca84b3..0b2c486 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt
@@ -1,13 +1,15 @@
 package com.android.systemui.statusbar.notification.stack
 
-import com.android.systemui.log.dagger.NotificationHeadsUpLog
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.dagger.NotificationHeadsUpLog
+import com.android.systemui.log.dagger.NotificationRenderLog
 import com.android.systemui.statusbar.notification.logKey
 import javax.inject.Inject
 
 class StackStateLogger @Inject constructor(
-    @NotificationHeadsUpLog private val buffer: LogBuffer
+    @NotificationHeadsUpLog private val buffer: LogBuffer,
+    @NotificationRenderLog private val notificationRenderBuffer: LogBuffer
 ) {
     fun logHUNViewDisappearing(key: String) {
         buffer.log(TAG, LogLevel.INFO, {
@@ -56,6 +58,21 @@
             "Heads up notification appear animation ended $str1 "
         })
     }
+
+    fun groupChildRemovalEventProcessed(key: String) {
+        notificationRenderBuffer.log(TAG, LogLevel.DEBUG, {
+            str1 = logKey(key)
+        }, {
+            "Group Child Notification removal event processed $str1 for ANIMATION_TYPE_REMOVE"
+        })
+    }
+    fun groupChildRemovalAnimationEnded(key: String) {
+        notificationRenderBuffer.log(TAG, LogLevel.INFO, {
+            str1 = logKey(key)
+        }, {
+            "Group child notification removal animation ended $str1 "
+        })
+    }
 }
 
 private const val TAG = "StackScroll"
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractor.kt
new file mode 100644
index 0000000..874450b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractor.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.statusbar.notification.stack.domain.interactor
+
+import android.content.Context
+import com.android.systemui.R
+import com.android.systemui.common.ui.data.repository.ConfigurationRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+
+/** Encapsulates business-logic specifically related to the shared notification stack container. */
+class SharedNotificationContainerInteractor
+@Inject
+constructor(
+    configurationRepository: ConfigurationRepository,
+    private val context: Context,
+) {
+    val configurationBasedDimensions: Flow<ConfigurationBasedDimensions> =
+        configurationRepository.onAnyConfigurationChange
+            .onStart { emit(Unit) }
+            .map { _ ->
+                with(context.resources) {
+                    ConfigurationBasedDimensions(
+                        useSplitShade = getBoolean(R.bool.config_use_split_notification_shade),
+                        useLargeScreenHeader =
+                            getBoolean(R.bool.config_use_large_screen_shade_header),
+                        marginHorizontal =
+                            getDimensionPixelSize(R.dimen.notification_panel_margin_horizontal),
+                        marginBottom =
+                            getDimensionPixelSize(R.dimen.notification_panel_margin_bottom),
+                        marginTop = getDimensionPixelSize(R.dimen.notification_panel_margin_top),
+                        marginTopLargeScreen =
+                            getDimensionPixelSize(R.dimen.large_screen_shade_header_height),
+                    )
+                }
+            }
+            .distinctUntilChanged()
+
+    data class ConfigurationBasedDimensions(
+        val useSplitShade: Boolean,
+        val useLargeScreenHeader: Boolean,
+        val marginHorizontal: Int,
+        val marginBottom: Int,
+        val marginTop: Int,
+        val marginTopLargeScreen: Int,
+    )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/SharedNotificationContainer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/SharedNotificationContainer.kt
new file mode 100644
index 0000000..688843d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/SharedNotificationContainer.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.
+ *
+ */
+package com.android.systemui.statusbar.notification.stack.ui.view
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.View
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.constraintlayout.widget.ConstraintSet
+import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
+import androidx.constraintlayout.widget.ConstraintSet.END
+import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
+import androidx.constraintlayout.widget.ConstraintSet.START
+import androidx.constraintlayout.widget.ConstraintSet.TOP
+import androidx.constraintlayout.widget.ConstraintSet.VERTICAL
+import com.android.systemui.R
+
+/**
+ * Container for the stack scroller, so that the bounds can be externally specified, such as from
+ * the keyguard or shade scenes.
+ */
+class SharedNotificationContainer(
+    context: Context,
+    private val attrs: AttributeSet?,
+) :
+    ConstraintLayout(
+        context,
+        attrs,
+    ) {
+
+    private val baseConstraintSet = ConstraintSet()
+
+    init {
+        baseConstraintSet.apply {
+            create(R.id.nssl_guideline, VERTICAL)
+            setGuidelinePercent(R.id.nssl_guideline, 0.5f)
+        }
+        baseConstraintSet.applyTo(this)
+    }
+
+    fun addNotificationStackScrollLayout(nssl: View) {
+        addView(nssl)
+    }
+
+    fun updateConstraints(
+        useSplitShade: Boolean,
+        marginStart: Int,
+        marginTop: Int,
+        marginEnd: Int,
+        marginBottom: Int
+    ) {
+        val constraintSet = ConstraintSet()
+        constraintSet.clone(baseConstraintSet)
+
+        val startConstraintId =
+            if (useSplitShade) {
+                R.id.nssl_guideline
+            } else {
+                PARENT_ID
+            }
+        val nsslId = R.id.notification_stack_scroller
+        constraintSet.apply {
+            connect(nsslId, START, startConstraintId, START)
+            connect(nsslId, END, PARENT_ID, END)
+            connect(nsslId, BOTTOM, PARENT_ID, BOTTOM)
+            connect(nsslId, TOP, PARENT_ID, TOP)
+            setMargin(nsslId, START, marginStart)
+            setMargin(nsslId, END, marginEnd)
+            setMargin(nsslId, TOP, marginTop)
+            setMargin(nsslId, BOTTOM, marginBottom)
+        }
+        constraintSet.applyTo(this)
+    }
+}
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
new file mode 100644
index 0000000..fb1d55d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.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.statusbar.notification.stack.ui.viewbinder
+
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.SharedNotificationContainerViewModel
+import kotlinx.coroutines.launch
+
+/** Binds the shared notification container to its view-model. */
+object SharedNotificationContainerBinder {
+
+    @JvmStatic
+    fun bind(
+        view: SharedNotificationContainer,
+        viewModel: SharedNotificationContainerViewModel,
+    ) {
+        view.repeatWhenAttached {
+            repeatOnLifecycle(Lifecycle.State.STARTED) {
+                launch {
+                    viewModel.configurationBasedDimensions.collect {
+                        view.updateConstraints(
+                            useSplitShade = it.useSplitShade,
+                            marginStart = it.marginStart,
+                            marginTop = it.marginTop,
+                            marginEnd = it.marginEnd,
+                            marginBottom = it.marginBottom,
+                        )
+                    }
+                }
+            }
+        }
+    }
+}
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 aab1c2b..11f68e0 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,6 +16,7 @@
 
 package com.android.systemui.statusbar.notification.stack.ui.viewmodel
 
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
 import com.android.systemui.statusbar.notification.shelf.ui.viewmodel.NotificationShelfViewModel
@@ -33,6 +34,7 @@
 object NotificationListViewModelModule {
     @JvmStatic
     @Provides
+    @SysUISingleton
     fun maybeProvideViewModel(
         featureFlags: FeatureFlags,
         shelfViewModel: Provider<NotificationShelfViewModel>,
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
new file mode 100644
index 0000000..b2e5ac1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.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.statusbar.notification.stack.ui.viewmodel
+
+import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
+
+/** View-model for the shared notification container */
+class SharedNotificationContainerViewModel
+@Inject
+constructor(
+    interactor: SharedNotificationContainerInteractor,
+) {
+    val configurationBasedDimensions: Flow<ConfigurationBasedDimensions> =
+        interactor.configurationBasedDimensions
+            .map {
+                ConfigurationBasedDimensions(
+                    marginStart = if (it.useSplitShade) 0 else it.marginHorizontal,
+                    marginEnd = it.marginHorizontal,
+                    marginBottom = it.marginBottom,
+                    marginTop =
+                        if (it.useLargeScreenHeader) it.marginTopLargeScreen else it.marginTop,
+                    useSplitShade = it.useSplitShade,
+                )
+            }
+            .distinctUntilChanged()
+
+    data class ConfigurationBasedDimensions(
+        val marginStart: Int,
+        val marginTop: Int,
+        val marginEnd: Int,
+        val marginBottom: Int,
+        val useSplitShade: Boolean,
+    )
+}
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 1827a46..26b51a9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
@@ -37,6 +37,7 @@
 import com.android.systemui.assist.AssistManager
 import com.android.systemui.camera.CameraIntents.Companion.isInsecureCameraIntent
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.DisplayId
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.keyguard.KeyguardViewMediator
 import com.android.systemui.keyguard.WakefulnessLifecycle
@@ -45,6 +46,7 @@
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.shade.ShadeController
 import com.android.systemui.statusbar.NotificationLockscreenUserManager
+import com.android.systemui.statusbar.NotificationShadeWindowController
 import com.android.systemui.statusbar.SysuiStatusBarStateController
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
 import com.android.systemui.statusbar.policy.DeviceProvisionedController
@@ -68,8 +70,10 @@
     private val keyguardViewMediatorLazy: Lazy<KeyguardViewMediator>,
     private val shadeControllerLazy: Lazy<ShadeController>,
     private val statusBarKeyguardViewManagerLazy: Lazy<StatusBarKeyguardViewManager>,
+    private val notifShadeWindowControllerLazy: Lazy<NotificationShadeWindowController>,
     private val activityLaunchAnimator: ActivityLaunchAnimator,
     private val context: Context,
+    @DisplayId private val displayId: Int,
     private val lockScreenUserManager: NotificationLockscreenUserManager,
     private val statusBarWindowController: StatusBarWindowController,
     private val wakefulnessLifecycle: WakefulnessLifecycle,
@@ -387,6 +391,35 @@
     }
 
     /**
+     * Whether we should animate an activity launch.
+     *
+     * Note: This method must be called *before* dismissing the keyguard.
+     */
+    private fun shouldAnimateLaunch(
+        isActivityIntent: Boolean,
+        showOverLockscreen: Boolean,
+    ): Boolean {
+        // TODO(b/184121838): Support launch animations when occluded.
+        if (keyguardStateController.isOccluded) {
+            return false
+        }
+
+        // Always animate if we are not showing the keyguard or if we animate over the lockscreen
+        // (without unlocking it).
+        if (showOverLockscreen || !keyguardStateController.isShowing) {
+            return true
+        }
+
+        // We don't animate non-activity launches as they can break the animation.
+        // TODO(b/184121838): Support non activity launches on the lockscreen.
+        return isActivityIntent
+    }
+
+    override fun shouldAnimateLaunch(isActivityIntent: Boolean): Boolean {
+        return shouldAnimateLaunch(isActivityIntent, false)
+    }
+
+    /**
      * Encapsulates the activity logic for activity starter.
      *
      * Logic is duplicated in {@link CentralSurfacesImpl}
@@ -417,7 +450,7 @@
             val animate =
                 animationController != null &&
                     !willLaunchResolverActivity &&
-                    centralSurfaces?.shouldAnimateLaunch(true /* isActivityIntent */) == true
+                    shouldAnimateLaunch(isActivityIntent = true)
             val animController =
                 wrapAnimationController(
                     animationController = animationController,
@@ -440,9 +473,7 @@
                     intent.getPackage()
                 ) { adapter: RemoteAnimationAdapter? ->
                     val options =
-                        ActivityOptions(
-                            CentralSurfaces.getActivityOptions(centralSurfaces!!.displayId, adapter)
-                        )
+                        ActivityOptions(CentralSurfaces.getActivityOptions(displayId, adapter))
 
                     // We know that the intent of the caller is to dismiss the keyguard and
                     // this runnable is called right after the keyguard is solved, so we tell
@@ -536,7 +567,7 @@
             val animate =
                 !willLaunchResolverActivity &&
                     animationController != null &&
-                    centralSurfaces?.shouldAnimateLaunch(intent.isActivity) == true
+                    shouldAnimateLaunch(intent.isActivity)
 
             // If we animate, don't collapse the shade and defer the keyguard dismiss (in case we
             // run the animation on the keyguard). The animation will take care of (instantly)
@@ -565,7 +596,7 @@
                                     val options =
                                         ActivityOptions(
                                             CentralSurfaces.getActivityOptions(
-                                                centralSurfaces!!.displayId,
+                                                displayId,
                                                 animationAdapter
                                             )
                                         )
@@ -593,7 +624,7 @@
                         Log.w(TAG, "Sending intent failed: $e")
                         if (!collapse) {
                             // executeRunnableDismissingKeyguard did not collapse for us already.
-                            centralSurfaces?.collapsePanelOnMainThread()
+                            shadeControllerLazy.get().collapseOnMainThread()
                         }
                         // TODO: Dismiss Keyguard.
                     }
@@ -635,7 +666,7 @@
 
             val animate =
                 animationController != null &&
-                    centralSurfaces?.shouldAnimateLaunch(
+                    shouldAnimateLaunch(
                         /* isActivityIntent= */ true,
                         showOverLockscreenWhenLocked
                     ) == true
@@ -713,7 +744,7 @@
             } else if (dismissShade) {
                 // The animation will take care of dismissing the shade at the end of the animation.
                 // If we don't animate, collapse it directly.
-                centralSurfaces?.collapseShade()
+                shadeControllerLazy.get().cancelExpansionAndCollapseShade()
             }
 
             // We should exit the dream to prevent the activity from starting below the
@@ -731,7 +762,7 @@
                 TaskStackBuilder.create(context)
                     .addNextIntent(intent)
                     .startActivities(
-                        CentralSurfaces.getActivityOptions(centralSurfaces!!.displayId, adapter),
+                        CentralSurfaces.getActivityOptions(displayId, adapter),
                         userHandle
                     )
             }
@@ -802,7 +833,7 @@
                                 shadeControllerLazy.get().isExpandedVisible &&
                                     !statusBarKeyguardViewManagerLazy.get().isBouncerShowing
                             ) {
-                                shadeControllerLazy.get().animateCollapseShadeDelayed()
+                                shadeControllerLazy.get().animateCollapseShadeForcedDelayed()
                             } else {
                                 // Do it after DismissAction has been processed to conserve the
                                 // needed ordering.
@@ -865,7 +896,9 @@
                 if (dismissShade) {
                     return StatusBarLaunchAnimatorController(
                         animationController,
-                        it,
+                        it.shadeViewController,
+                        shadeControllerLazy.get(),
+                        notifShadeWindowControllerLazy.get(),
                         isLaunchForActivity
                     )
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
index f6d53b3..5eafa9e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
@@ -20,6 +20,7 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.res.Resources;
+import android.database.ContentObserver;
 import android.hardware.display.ColorDisplayManager;
 import android.hardware.display.NightDisplayListener;
 import android.os.Handler;
@@ -28,6 +29,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.R;
+import com.android.systemui.dagger.NightDisplayListenerModule;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.qs.AutoAddTracker;
@@ -82,7 +84,8 @@
     private final HotspotController mHotspotController;
     private final DataSaverController mDataSaverController;
     private final ManagedProfileController mManagedProfileController;
-    private final NightDisplayListener mNightDisplayListener;
+    private final NightDisplayListenerModule.Builder mNightDisplayListenerBuilder;
+    private NightDisplayListener mNightDisplayListener;
     private final CastController mCastController;
     private final DeviceControlsController mDeviceControlsController;
     private final WalletController mWalletController;
@@ -98,7 +101,7 @@
             HotspotController hotspotController,
             DataSaverController dataSaverController,
             ManagedProfileController managedProfileController,
-            NightDisplayListener nightDisplayListener,
+            NightDisplayListenerModule.Builder nightDisplayListenerBuilder,
             CastController castController,
             ReduceBrightColorsController reduceBrightColorsController,
             DeviceControlsController deviceControlsController,
@@ -114,7 +117,7 @@
         mHotspotController = hotspotController;
         mDataSaverController = dataSaverController;
         mManagedProfileController = managedProfileController;
-        mNightDisplayListener = nightDisplayListener;
+        mNightDisplayListenerBuilder = nightDisplayListenerBuilder;
         mCastController = castController;
         mReduceBrightColorsController = reduceBrightColorsController;
         mIsReduceBrightColorsAvailable = isReduceBrightColorsAvailable;
@@ -157,6 +160,10 @@
             mDataSaverController.addCallback(mDataSaverListener);
         }
         mManagedProfileController.addCallback(mProfileCallback);
+
+        mNightDisplayListener = mNightDisplayListenerBuilder
+                .setUser(mCurrentUser.getIdentifier())
+                .build();
         if (!mAutoTracker.isAdded(NIGHT)
                 && ColorDisplayManager.isNightDisplayAvailable(mContext)) {
             mNightDisplayListener.setCallback(mNightDisplayCallback);
@@ -193,7 +200,8 @@
         mHotspotController.removeCallback(mHotspotCallback);
         mDataSaverController.removeCallback(mDataSaverListener);
         mManagedProfileController.removeCallback(mProfileCallback);
-        if (ColorDisplayManager.isNightDisplayAvailable(mContext)) {
+        if (ColorDisplayManager.isNightDisplayAvailable(mContext)
+                && mNightDisplayListener != null) {
             mNightDisplayListener.setCallback(null);
         }
         if (mIsReduceBrightColorsAvailable) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index 1f9c9f2..dc021fe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -312,7 +312,7 @@
         mSystemClock = systemClock;
         mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
 
-        dumpManager.registerDumpable(getClass().getName(), this);
+        dumpManager.registerDumpable(this);
     }
 
     public void setKeyguardViewController(KeyguardViewController keyguardViewController) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
index 86bf7cb..acd6e49 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -24,13 +24,11 @@
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.os.Bundle;
-import android.os.PowerManager;
 import android.os.UserHandle;
-import android.service.notification.StatusBarNotification;
 import android.view.KeyEvent;
+import android.view.MotionEvent;
 import android.view.RemoteAnimationAdapter;
 import android.view.View;
-import android.view.ViewGroup;
 import android.window.RemoteTransition;
 import android.window.SplashScreen;
 
@@ -39,19 +37,15 @@
 import androidx.lifecycle.LifecycleOwner;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.statusbar.RegisterStatusBarResult;
 import com.android.keyguard.AuthKeyguardMessageArea;
 import com.android.systemui.Dumpable;
 import com.android.systemui.animation.ActivityLaunchAnimator;
+import com.android.systemui.display.data.repository.DisplayMetricsRepository;
 import com.android.systemui.navigationbar.NavigationBarView;
 import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
-import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
 import com.android.systemui.qs.QSPanelController;
-import com.android.systemui.shade.NotificationShadeWindowView;
-import com.android.systemui.shade.NotificationShadeWindowViewController;
 import com.android.systemui.shade.ShadeViewController;
 import com.android.systemui.shared.system.RemoteAnimationRunnerCompat;
-import com.android.systemui.statusbar.LightRevealScrim;
 import com.android.systemui.statusbar.NotificationPresenter;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.util.Compile;
@@ -69,14 +63,11 @@
     String TAG = "CentralSurfaces";
     boolean DEBUG = false;
     boolean SPEW = false;
-    boolean DUMPTRUCK = true; // extra dumpsys info
     boolean DEBUG_GESTURES = false;
     boolean DEBUG_MEDIA_FAKE_ARTWORK = false;
     boolean DEBUG_CAMERA_LIFT = false;
     boolean DEBUG_WINDOW_STATE = false;
     boolean DEBUG_WAKEUP_DELAY = Compile.IS_DEBUG;
-    // additional instrumentation for testing purposes; intended to be left on during development
-    boolean CHATTY = DEBUG;
     boolean SHOW_LOCKSCREEN_MEDIA_ARTWORK = true;
     String ACTION_FAKE_ARTWORK = "fake_artwork";
     int FADE_KEYGUARD_START_DELAY = 100;
@@ -194,14 +185,6 @@
         return contextForUser.getPackageManager();
     }
 
-    void animateExpandNotificationsPanel();
-
-    void animateExpandSettingsPanel(@Nullable String subpanel);
-
-    void collapsePanelOnMainThread();
-
-    void togglePanel();
-
     void start();
 
     boolean updateIsKeyguard();
@@ -212,15 +195,6 @@
     @Override
     Lifecycle getLifecycle();
 
-    /**
-     * Wakes up the device if the device was dozing.
-     */
-    void wakeUpIfDozing(long time, String why, @PowerManager.WakeReason int wakeReason);
-
-    NotificationShadeWindowView getNotificationShadeWindowView();
-
-    NotificationShadeWindowViewController getNotificationShadeWindowViewController();
-
     /** */
     ShadeViewController getShadeViewController();
 
@@ -229,46 +203,32 @@
 
     int getStatusBarHeight();
 
-    void updateQsExpansionEnabled();
-
-    boolean isShadeDisabled();
-
     boolean isLaunchingActivityOverLockscreen();
 
-    boolean isWakeUpComingFromTouch();
-
     void onKeyguardViewManagerStatesUpdated();
 
-    ViewGroup getNotificationScrollLayout();
-
     boolean isPulsing();
 
     boolean isOccluded();
 
-    //TODO: These can / should probably be moved to NotificationPresenter or ShadeController
-    void onLaunchAnimationCancelled(boolean isLaunchForActivity);
-
-    void onLaunchAnimationEnd(boolean launchIsFullScreen);
-
-    boolean shouldAnimateLaunch(boolean isActivityIntent, boolean showOverLockscreen);
-
-    boolean shouldAnimateLaunch(boolean isActivityIntent);
-
     boolean isDeviceInVrMode();
 
     NotificationPresenter getPresenter();
 
-    void postAnimateCollapsePanels();
-
-    void postAnimateForceCollapsePanels();
-
-    void postAnimateOpenPanels();
-
-    boolean isPanelExpanded();
-
+    /**
+     * Used to dispatch initial touch events before crossing the threshold to pull down the
+     * notification shade. After that, since the launcher window is set to slippery, input
+     * frameworks take care of routing the events to the notification shade.
+     */
     void onInputFocusTransfer(boolean start, boolean cancel, float velocity);
 
-    void animateCollapseQuickSettings();
+    /**
+     * Dispatches status bar motion event to the notification shade. This is different from
+     * {@link #onInputFocusTransfer(boolean, boolean, float)} as it doesn't rely on setting the
+     * launcher window slippery to allow the frameworks to route those events after passing the
+     * initial threshold.
+     */
+    default void onStatusBarTrackpadEvent(MotionEvent event) {}
 
     /** */
     boolean getCommandQueuePanelsEnabled();
@@ -289,18 +249,16 @@
     @Override
     void dump(PrintWriter pwOriginal, String[] args);
 
-    void createAndAddWindows(@Nullable RegisterStatusBarResult result);
-
+    /** @deprecated Use {@link DisplayMetricsRepository} instead. */
+    @Deprecated
     float getDisplayWidth();
 
+    /** @deprecated Use {@link DisplayMetricsRepository} instead. */
+    @Deprecated
     float getDisplayHeight();
 
     void readyForKeyguardDone();
 
-    void resetUserExpandedStates();
-
-    void setLockscreenUser(int newUserId);
-
     void showKeyguard();
 
     boolean hideKeyguard();
@@ -333,15 +291,11 @@
     /** Should the keyguard be hidden immediately in response to a back press/gesture. */
     boolean shouldKeyguardHideImmediately();
 
-    boolean onBackPressed();
-
     boolean onSpacePressed();
 
     void showBouncerWithDimissAndCancelIfKeyguard(OnDismissAction performAction,
             Runnable cancelAction);
 
-    LightRevealScrim getLightRevealScrim();
-
     // TODO: Figure out way to remove these.
     NavigationBarView getNavigationBarView();
 
@@ -353,10 +307,6 @@
 
     void setBouncerShowing(boolean bouncerShowing);
 
-    void setBouncerShowingOverDream(boolean bouncerShowingOverDream);
-
-    void collapseShade();
-
     int getWakefulnessState();
 
     boolean isScreenFullyOff();
@@ -389,9 +339,6 @@
 
     boolean isDeviceInteractive();
 
-    void setNotificationSnoozed(StatusBarNotification sbn,
-            NotificationSwipeActionHelper.SnoozeOption snoozeOption);
-
     void awakenDreams();
 
     void clearNotificationEffects();
@@ -406,15 +353,11 @@
 
     void updateNotificationPanelTouchState();
 
-    int getDisplayId();
-
     int getRotation();
 
     @VisibleForTesting
     void setBarStateForTest(int state);
 
-    void wakeUpForFullScreenIntent();
-
     void showTransientUnchecked();
 
     void clearTransient();
@@ -429,14 +372,6 @@
 
     void resendMessage(Object msg);
 
-    int getDisabled1();
-
-    void setDisabled1(int disabled);
-
-    int getDisabled2();
-
-    void setDisabled2(int disabled);
-
     void setLastCameraLaunchSource(int source);
 
     void setLaunchCameraOnFinishedGoingToSleep(boolean launch);
@@ -449,14 +384,12 @@
 
     QSPanelController getQSPanelController();
 
-    boolean areNotificationAlertsDisabled();
-
+    /** @deprecated Use {@link DisplayMetricsRepository} instead. */
+    @Deprecated
     float getDisplayDensity();
 
     void extendDozePulse();
 
-    boolean shouldDelayWakeUpAnimation();
-
     public static class KeyboardShortcutsMessage {
         final int mDeviceId;
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
index 0ccc819..6431ef9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
@@ -111,6 +111,9 @@
     private static final VibrationAttributes HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES =
             VibrationAttributes.createForUsage(VibrationAttributes.USAGE_HARDWARE_FEEDBACK);
 
+    private int mDisabled1;
+    private int mDisabled2;
+
     @Inject
     CentralSurfacesCommandQueueCallbacks(
             CentralSurfaces centralSurfaces,
@@ -208,7 +211,7 @@
 
     @Override
     public void animateCollapsePanels(int flags, boolean force) {
-        mShadeController.animateCollapsePanels(flags, force, false /* delayed */,
+        mShadeController.animateCollapseShade(flags, force, false /* delayed */,
                 1.0f /* speedUpFactor */);
     }
 
@@ -218,11 +221,7 @@
             Log.d(CentralSurfaces.TAG,
                     "animateExpand: mExpandedVisible=" + mShadeController.isExpandedVisible());
         }
-        if (!mCommandQueue.panelsEnabled()) {
-            return;
-        }
-
-        mShadeViewController.expandToNotifications();
+        mShadeController.animateExpandShade();
     }
 
     @Override
@@ -231,14 +230,7 @@
             Log.d(CentralSurfaces.TAG,
                     "animateExpand: mExpandedVisible=" + mShadeController.isExpandedVisible());
         }
-        if (!mCommandQueue.panelsEnabled()) {
-            return;
-        }
-
-        // Settings are not available in setup
-        if (!mDeviceProvisionedController.isCurrentUserSetup()) return;
-
-        mShadeViewController.expandToQs();
+        mShadeController.animateExpandQs();
     }
 
     @Override
@@ -255,31 +247,26 @@
     }
     /**
      * State is one or more of the DISABLE constants from StatusBarManager.
+     *
+     * @deprecated If you need to react to changes in disable flags, listen to
+     * {@link com.android.systemui.statusbar.disableflags.data.repository.DisableFlagsRepository}
+     * instead.
      */
     @Override
+    @Deprecated
     public void disable(int displayId, int state1, int state2, boolean animate) {
         if (displayId != mDisplayId) {
             return;
         }
 
-        int state2BeforeAdjustment = state2;
-        state2 = mRemoteInputQuickSettingsDisabler.adjustDisableFlags(state2);
-        Log.d(CentralSurfaces.TAG,
-                mDisableFlagsLogger.getDisableFlagsString(
-                        /* old= */ new DisableFlagsLogger.DisableState(
-                                mCentralSurfaces.getDisabled1(), mCentralSurfaces.getDisabled2()),
-                        /* new= */ new DisableFlagsLogger.DisableState(
-                                state1, state2BeforeAdjustment),
-                        /* newStateAfterLocalModification= */ new DisableFlagsLogger.DisableState(
-                                state1, state2)));
-
-        final int old1 = mCentralSurfaces.getDisabled1();
+        final int old1 = mDisabled1;
         final int diff1 = state1 ^ old1;
-        mCentralSurfaces.setDisabled1(state1);
+        mDisabled1 = state1;
 
-        final int old2 = mCentralSurfaces.getDisabled2();
+        state2 = mRemoteInputQuickSettingsDisabler.adjustDisableFlags(state2);
+        final int old2 = mDisabled2;
         final int diff2 = state2 ^ old2;
-        mCentralSurfaces.setDisabled2(state2);
+        mDisabled2 = state2;
 
         if ((diff1 & StatusBarManager.DISABLE_EXPAND) != 0) {
             if ((state1 & StatusBarManager.DISABLE_EXPAND) != 0) {
@@ -288,17 +275,12 @@
         }
 
         if ((diff1 & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0) {
-            if (mCentralSurfaces.areNotificationAlertsDisabled()) {
+            if ((state1 & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0) {
                 mHeadsUpManager.releaseAllImmediately();
             }
         }
 
-        if ((diff2 & StatusBarManager.DISABLE2_QUICK_SETTINGS) != 0) {
-            mCentralSurfaces.updateQsExpansionEnabled();
-        }
-
         if ((diff2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) != 0) {
-            mCentralSurfaces.updateQsExpansionEnabled();
             if ((state2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) != 0) {
                 mShadeController.animateCollapseShade();
             }
@@ -556,10 +538,10 @@
 
     @Override
     public void togglePanel() {
-        if (mCentralSurfaces.isPanelExpanded()) {
+        if (mShadeViewController.isPanelExpanded()) {
             mShadeController.animateCollapseShade();
         } else {
-            animateExpandNotificationsPanel();
+            mShadeController.animateExpandShade();
         }
     }
 
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 648ece5..8361d6b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -42,6 +42,7 @@
 
 import android.annotation.Nullable;
 import android.app.ActivityManager;
+import android.app.ActivityOptions;
 import android.app.IWallpaperManager;
 import android.app.KeyguardManager;
 import android.app.Notification;
@@ -50,7 +51,6 @@
 import android.app.StatusBarManager;
 import android.app.TaskInfo;
 import android.app.UiModeManager;
-import android.app.WallpaperInfo;
 import android.app.WallpaperManager;
 import android.app.admin.DevicePolicyManager;
 import android.content.BroadcastReceiver;
@@ -69,7 +69,6 @@
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.Handler;
-import android.os.Looper;
 import android.os.PowerManager;
 import android.os.RemoteException;
 import android.os.ServiceManager;
@@ -91,6 +90,7 @@
 import android.view.IRemoteAnimationRunner;
 import android.view.IWindowManager;
 import android.view.KeyEvent;
+import android.view.MotionEvent;
 import android.view.ThreadedRenderer;
 import android.view.View;
 import android.view.ViewGroup;
@@ -135,7 +135,9 @@
 import com.android.systemui.accessibility.floatingmenu.AccessibilityFloatingMenuController;
 import com.android.systemui.animation.ActivityLaunchAnimator;
 import com.android.systemui.assist.AssistManager;
+import com.android.systemui.back.domain.interactor.BackActionInteractor;
 import com.android.systemui.biometrics.AuthRippleController;
+import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.camera.CameraIntents;
 import com.android.systemui.charging.WiredChargingRippleController;
@@ -157,7 +159,6 @@
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.keyguard.ScreenLifecycle;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
-import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor;
 import com.android.systemui.keyguard.ui.binder.LightRevealScrimViewBinder;
 import com.android.systemui.keyguard.ui.viewmodel.LightRevealScrimViewModel;
 import com.android.systemui.navigationbar.NavigationBarController;
@@ -172,8 +173,8 @@
 import com.android.systemui.plugins.PluginListener;
 import com.android.systemui.plugins.PluginManager;
 import com.android.systemui.plugins.qs.QS;
-import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.power.domain.interactor.PowerInteractor;
 import com.android.systemui.qs.QSFragment;
 import com.android.systemui.qs.QSPanelController;
 import com.android.systemui.recents.ScreenPinningRequest;
@@ -181,7 +182,6 @@
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.settings.brightness.BrightnessSliderController;
 import com.android.systemui.shade.CameraLauncher;
-import com.android.systemui.shade.NotificationPanelViewController;
 import com.android.systemui.shade.NotificationShadeWindowView;
 import com.android.systemui.shade.NotificationShadeWindowViewController;
 import com.android.systemui.shade.QuickSettingsController;
@@ -311,7 +311,7 @@
     private final DeviceStateManager mDeviceStateManager;
     private CentralSurfacesCommandQueueCallbacks mCommandQueueCallbacks;
     private float mTransitionToFullShadeProgress = 0f;
-    private NotificationListContainer mNotifListContainer;
+    private final NotificationListContainer mNotifListContainer;
     private boolean mIsShortcutListSearchEnabled;
 
     private final KeyguardStateController.Callback mKeyguardStateControllerCallback =
@@ -362,26 +362,6 @@
     }
 
     @Override
-    public int getDisabled1() {
-        return mDisabled1;
-    }
-
-    @Override
-    public void setDisabled1(int disabled) {
-        mDisabled1 = disabled;
-    }
-
-    @Override
-    public int getDisabled2() {
-        return mDisabled2;
-    }
-
-    @Override
-    public void setDisabled2(int disabled) {
-        mDisabled2 = disabled;
-    }
-
-    @Override
     public void setLastCameraLaunchSource(int source) {
         mLastCameraLaunchSource = source;
     }
@@ -411,23 +391,6 @@
         return mQSPanelController;
     }
 
-    /** */
-    @Override
-    public void animateExpandNotificationsPanel() {
-        mCommandQueueCallbacks.animateExpandNotificationsPanel();
-    }
-
-    /** */
-    @Override
-    public void animateExpandSettingsPanel(@Nullable String subpanel) {
-        mCommandQueueCallbacks.animateExpandSettingsPanel(subpanel);
-    }
-
-    /** */
-    @Override
-    public void togglePanel() {
-        mCommandQueueCallbacks.togglePanel();
-    }
     /**
      * The {@link StatusBarState} of the status bar.
      */
@@ -449,13 +412,12 @@
 
     private final Point mCurrentDisplaySize = new Point();
 
-    protected NotificationShadeWindowView mNotificationShadeWindowView;
     protected PhoneStatusBarView mStatusBarView;
     private PhoneStatusBarViewController mPhoneStatusBarViewController;
     private PhoneStatusBarTransitions mStatusBarTransitions;
     private final AuthRippleController mAuthRippleController;
     @WindowVisibleState private int mStatusBarWindowState = WINDOW_STATE_SHOWING;
-    protected final NotificationShadeWindowController mNotificationShadeWindowController;
+    private final NotificationShadeWindowController mNotificationShadeWindowController;
     private final StatusBarInitializer mStatusBarInitializer;
     private final StatusBarWindowController mStatusBarWindowController;
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@@ -464,8 +426,6 @@
     private final LightRevealScrim mLightRevealScrim;
     private PowerButtonReveal mPowerButtonReveal;
 
-    private boolean mWakeUpComingFromTouch;
-
     /**
      * Whether we should delay the wakeup animation (which shows the notifications and moves the
      * clock view). This is typically done when waking up from a 'press to unlock' gesture on a
@@ -494,7 +454,8 @@
     private final FalsingManager mFalsingManager;
     private final BroadcastDispatcher mBroadcastDispatcher;
     private final ConfigurationController mConfigurationController;
-    protected NotificationShadeWindowViewController mNotificationShadeWindowViewController;
+    private final Lazy<NotificationShadeWindowViewController>
+            mNotificationShadeWindowViewControllerLazy;
     private final DozeParameters mDozeParameters;
     private final Lazy<BiometricUnlockController> mBiometricUnlockControllerLazy;
     private final CentralSurfacesComponent.Factory mCentralSurfacesComponentFactory;
@@ -505,7 +466,6 @@
     private final AlternateBouncerInteractor mAlternateBouncerInteractor;
 
     private final PluginDependencyProvider mPluginDependencyProvider;
-    private final KeyguardDismissUtil mKeyguardDismissUtil;
     private final ExtensionController mExtensionController;
     private final UserInfoControllerImpl mUserInfoControllerImpl;
     private final DemoModeController mDemoModeController;
@@ -516,14 +476,12 @@
     private final Lazy<LightRevealScrimViewModel> mLightRevealScrimViewModelLazy;
 
     /** Controller for the Shade. */
-    @VisibleForTesting
-    ShadeSurface mShadeSurface;
+    private final ShadeSurface mShadeSurface;
     private final ShadeLogger mShadeLogger;
 
     // settings
     private QSPanelController mQSPanelController;
-    @VisibleForTesting
-    QuickSettingsController mQsController;
+    private final QuickSettingsController mQsController;
 
     KeyguardIndicationController mKeyguardIndicationController;
 
@@ -549,11 +507,6 @@
 
     private CentralSurfacesComponent mCentralSurfacesComponent;
 
-    // Flags for disabling the status bar
-    // Two variables because the first one evidently ran out of room for new flags.
-    private int mDisabled1 = 0;
-    private int mDisabled2 = 0;
-
     /**
      * This keeps track of whether we have (or haven't) registered the predictive back callback.
      * Since we can have visible -> visible transitions, we need to avoid
@@ -627,6 +580,7 @@
     private final ViewMediatorCallback mKeyguardViewMediatorCallback;
     private final ScrimController mScrimController;
     protected DozeScrimController mDozeScrimController;
+    private final BackActionInteractor mBackActionInteractor;
     private final Executor mUiBgExecutor;
 
     protected boolean mDozing;
@@ -655,7 +609,6 @@
     private final UserSwitcherController mUserSwitcherController;
     private final LifecycleRegistry mLifecycle = new LifecycleRegistry(this);
     protected final BatteryController mBatteryController;
-    protected boolean mPanelExpanded;
     private UiModeManager mUiModeManager;
     private LogMaker mStatusBarStateLog;
     protected final NotificationIconAreaController mNotificationIconAreaController;
@@ -663,6 +616,7 @@
     private final SysuiColorExtractor mColorExtractor;
     private final ScreenLifecycle mScreenLifecycle;
     private final WakefulnessLifecycle mWakefulnessLifecycle;
+    protected final PowerInteractor mPowerInteractor;
 
     private boolean mNoAnimationOnNextBarModeChange;
     private final SysuiStatusBarStateController mStatusBarStateController;
@@ -677,7 +631,8 @@
     private final Optional<StartingSurface> mStartingSurfaceOptional;
 
     private final ActivityIntentHelper mActivityIntentHelper;
-    private NotificationStackScrollLayoutController mStackScrollerController;
+
+    private final NotificationStackScrollLayoutController mStackScrollerController;
 
     private final ColorExtractor.OnColorsChangedListener mOnColorsChangedListener =
             (extractor, which) -> updateTheme();
@@ -685,17 +640,7 @@
     private final InteractionJankMonitor mJankMonitor;
 
     /** Existing callback that handles back gesture invoked for the Shade. */
-    private final OnBackInvokedCallback mOnBackInvokedCallback = () -> {
-        if (DEBUG) {
-            Log.d(TAG, "mOnBackInvokedCallback() called");
-        }
-        onBackPressed();
-    };
-
-    private boolean shouldBackBeHandled() {
-        return (mState != StatusBarState.KEYGUARD && mState != StatusBarState.SHADE_LOCKED
-                && !isBouncerShowingOverDream());
-    }
+    private final OnBackInvokedCallback mOnBackInvokedCallback;
 
     /**
      *  New callback that handles back gesture invoked, cancel, progress
@@ -705,12 +650,12 @@
     private final OnBackAnimationCallback mOnBackAnimationCallback = new OnBackAnimationCallback() {
         @Override
         public void onBackInvoked() {
-            onBackPressed();
+            mBackActionInteractor.onBackRequested();
         }
 
         @Override
         public void onBackProgressed(BackEvent event) {
-            if (shouldBackBeHandled()) {
+            if (mBackActionInteractor.shouldBackBeHandled()) {
                 if (mShadeSurface.canBeCollapsed()) {
                     float fraction = event.getProgress();
                     mShadeSurface.onBackProgressed(fraction);
@@ -756,14 +701,17 @@
             MetricsLogger metricsLogger,
             ShadeLogger shadeLogger,
             @UiBackground Executor uiBgExecutor,
+            ShadeSurface shadeSurface,
             NotificationMediaManager notificationMediaManager,
             NotificationLockscreenUserManager lockScreenUserManager,
             NotificationRemoteInputManager remoteInputManager,
+            QuickSettingsController quickSettingsController,
             UserSwitcherController userSwitcherController,
             BatteryController batteryController,
             SysuiColorExtractor colorExtractor,
             ScreenLifecycle screenLifecycle,
             WakefulnessLifecycle wakefulnessLifecycle,
+            PowerInteractor powerInteractor,
             SysuiStatusBarStateController statusBarStateController,
             Optional<Bubbles> bubblesOptional,
             Lazy<NoteTaskController> noteTaskControllerLazy,
@@ -773,12 +721,16 @@
             Lazy<AssistManager> assistManagerLazy,
             ConfigurationController configurationController,
             NotificationShadeWindowController notificationShadeWindowController,
+            Lazy<NotificationShadeWindowViewController> notificationShadeWindowViewControllerLazy,
+            NotificationShelfController notificationShelfController,
+            NotificationStackScrollLayoutController notificationStackScrollLayoutController,
             DozeParameters dozeParameters,
             ScrimController scrimController,
             Lazy<LockscreenWallpaper> lockscreenWallpaperLazy,
             Lazy<BiometricUnlockController> biometricUnlockControllerLazy,
             AuthRippleController authRippleController,
             DozeServiceHost dozeServiceHost,
+            BackActionInteractor backActionInteractor,
             PowerManager powerManager,
             ScreenPinningRequest screenPinningRequest,
             DozeScrimController dozeScrimController,
@@ -792,7 +744,6 @@
             InitController initController,
             @Named(TIME_TICK_HANDLER_NAME) Handler timeTickHandler,
             PluginDependencyProvider pluginDependencyProvider,
-            KeyguardDismissUtil keyguardDismissUtil,
             ExtensionController extensionController,
             UserInfoControllerImpl userInfoControllerImpl,
             PhoneStatusBarPolicy phoneStatusBarPolicy,
@@ -839,6 +790,7 @@
         mKeyguardBypassController = keyguardBypassController;
         mKeyguardStateController = keyguardStateController;
         mHeadsUpManager = headsUpManagerPhone;
+        mBackActionInteractor = backActionInteractor;
         mKeyguardIndicationController = keyguardIndicationController;
         mStatusBarTouchableRegionManager = statusBarTouchableRegionManager;
         mFalsingCollector = falsingCollector;
@@ -853,14 +805,17 @@
         mMetricsLogger = metricsLogger;
         mShadeLogger = shadeLogger;
         mUiBgExecutor = uiBgExecutor;
+        mShadeSurface = shadeSurface;
         mMediaManager = notificationMediaManager;
         mLockscreenUserManager = lockScreenUserManager;
         mRemoteInputManager = remoteInputManager;
+        mQsController = quickSettingsController;
         mUserSwitcherController = userSwitcherController;
         mBatteryController = batteryController;
         mColorExtractor = colorExtractor;
         mScreenLifecycle = screenLifecycle;
         mWakefulnessLifecycle = wakefulnessLifecycle;
+        mPowerInteractor = powerInteractor;
         mStatusBarStateController = statusBarStateController;
         mBubblesOptional = bubblesOptional;
         mNoteTaskControllerLazy = noteTaskControllerLazy;
@@ -870,6 +825,11 @@
         mAssistManagerLazy = assistManagerLazy;
         mConfigurationController = configurationController;
         mNotificationShadeWindowController = notificationShadeWindowController;
+        mNotificationShadeWindowViewControllerLazy = notificationShadeWindowViewControllerLazy;
+        mNotificationShelfController = notificationShelfController;
+        mStackScrollerController = notificationStackScrollLayoutController;
+        mStackScroller = mStackScrollerController.getView();
+        mNotifListContainer = mStackScrollerController.getNotificationListContainer();
         mDozeServiceHost = dozeServiceHost;
         mPowerManager = powerManager;
         mDozeParameters = dozeParameters;
@@ -889,7 +849,6 @@
         mKeyguardViewMediatorCallback = viewMediatorCallback;
         mInitController = initController;
         mPluginDependencyProvider = pluginDependencyProvider;
-        mKeyguardDismissUtil = keyguardDismissUtil;
         mExtensionController = extensionController;
         mUserInfoControllerImpl = userInfoControllerImpl;
         mIconPolicy = phoneStatusBarPolicy;
@@ -958,6 +917,12 @@
         }
         // Based on teamfood flag, enable predictive back animation for the Shade.
         mAnimateBack = mFeatureFlags.isEnabled(Flags.WM_SHADE_ANIMATE_BACK_GESTURE);
+        mOnBackInvokedCallback = () -> {
+            if (DEBUG) {
+                Log.d(TAG, "mOnBackInvokedCallback() called");
+            }
+            mBackActionInteractor.onBackRequested();
+        };
     }
 
     private void initBubbles(Bubbles bubbles) {
@@ -1014,16 +979,6 @@
 
         createAndAddWindows(result);
 
-        if (mWallpaperSupported) {
-            // Make sure we always have the most current wallpaper info.
-            IntentFilter wallpaperChangedFilter = new IntentFilter(Intent.ACTION_WALLPAPER_CHANGED);
-            mBroadcastDispatcher.registerReceiver(mWallpaperChangedReceiver, wallpaperChangedFilter,
-                    null /* handler */, UserHandle.ALL);
-            mWallpaperChangedReceiver.onReceive(mContext, null);
-        } else if (DEBUG) {
-            Log.v(TAG, "start(): no wallpaper service ");
-        }
-
         // Set up the initial notification state. This needs to happen before CommandQueue.disable()
         setUpPresenter();
 
@@ -1109,7 +1064,7 @@
         mDozeServiceHost.initialize(
                 this,
                 mStatusBarKeyguardViewManager,
-                mNotificationShadeWindowViewController,
+                getNotificationShadeWindowViewController(),
                 mShadeSurface,
                 mAmbientIndicationContainer);
         updateLightRevealScrimVisibility();
@@ -1151,7 +1106,8 @@
                     @Override
                     public void onPluginConnected(OverlayPlugin plugin, Context pluginContext) {
                         mMainExecutor.execute(
-                                () -> plugin.setup(getNotificationShadeWindowView(),
+                                () -> plugin.setup(
+                                        mNotificationShadeWindowController.getWindowRootView(),
                                         getNavigationBarView(),
                                         new Callback(plugin), mDozeParameters));
                     }
@@ -1270,8 +1226,8 @@
         updateTheme();
 
         inflateStatusBarWindow();
-        mNotificationShadeWindowView.setOnTouchListener(getStatusBarWindowTouchListener());
-        mWallpaperController.setRootView(mNotificationShadeWindowView);
+        getNotificationShadeWindowView().setOnTouchListener(getStatusBarWindowTouchListener());
+        mWallpaperController.setRootView(getNotificationShadeWindowView());
 
         // TODO: Deal with the ugliness that comes from having some of the status bar broken out
         // into fragments, but the rest here, it leaves some awkward lifecycle and whatnot.
@@ -1292,7 +1248,7 @@
                     mStatusBarView = statusBarView;
                     mPhoneStatusBarViewController = statusBarViewController;
                     mStatusBarTransitions = statusBarTransitions;
-                    mNotificationShadeWindowViewController
+                    getNotificationShadeWindowViewController()
                             .setStatusBarViewController(mPhoneStatusBarViewController);
                     // Ensure we re-propagate panel expansion values to the panel controller and
                     // any listeners it may have, such as PanelBar. This will also ensure we
@@ -1306,7 +1262,7 @@
         mStatusBarInitializer.initializeStatusBar(
                 mCentralSurfacesComponent::createCollapsedStatusBarFragment);
 
-        mStatusBarTouchableRegionManager.setup(this, mNotificationShadeWindowView);
+        mStatusBarTouchableRegionManager.setup(this, getNotificationShadeWindowView());
 
         createNavigationBar(result);
 
@@ -1314,7 +1270,7 @@
             mLockscreenWallpaper = mLockscreenWallpaperLazy.get();
         }
 
-        mAmbientIndicationContainer = mNotificationShadeWindowView.findViewById(
+        mAmbientIndicationContainer = getNotificationShadeWindowView().findViewById(
                 R.id.ambient_indication_container);
 
         mAutoHideController.setStatusBar(new AutoHideUiElement() {
@@ -1339,10 +1295,10 @@
             }
         });
 
-        ScrimView scrimBehind = mNotificationShadeWindowView.findViewById(R.id.scrim_behind);
-        ScrimView notificationsScrim = mNotificationShadeWindowView
+        ScrimView scrimBehind = getNotificationShadeWindowView().findViewById(R.id.scrim_behind);
+        ScrimView notificationsScrim = getNotificationShadeWindowView()
                 .findViewById(R.id.scrim_notifications);
-        ScrimView scrimInFront = mNotificationShadeWindowView.findViewById(R.id.scrim_in_front);
+        ScrimView scrimInFront = getNotificationShadeWindowView().findViewById(R.id.scrim_in_front);
 
         mScrimController.setScrimVisibleListener(scrimsVisible -> {
             mNotificationShadeWindowController.setScrimsVisibility(scrimsVisible);
@@ -1380,7 +1336,7 @@
                 mNotificationShelfController,
                 mHeadsUpManager);
 
-        BackDropView backdrop = mNotificationShadeWindowView.findViewById(R.id.backdrop);
+        BackDropView backdrop = getNotificationShadeWindowView().findViewById(R.id.backdrop);
         if (mWallpaperManager.isLockscreenLiveWallpaperEnabled()) {
             mMediaManager.setup(null, null, null, mScrimController, null);
         } else {
@@ -1399,7 +1355,7 @@
         });
 
         // Set up the quick settings tile panel
-        final View container = mNotificationShadeWindowView.findViewById(R.id.qs_frame);
+        final View container = getNotificationShadeWindowView().findViewById(R.id.qs_frame);
         if (container != null) {
             FragmentHostManager fragmentHostManager =
                     mFragmentService.getFragmentHostManager(container);
@@ -1414,7 +1370,7 @@
                             .withDefault(this::createDefaultQSFragment)
                             .build());
             mBrightnessMirrorController = new BrightnessMirrorController(
-                    mNotificationShadeWindowView,
+                    getNotificationShadeWindowView(),
                     mShadeSurface,
                     mNotificationShadeDepthControllerLazy.get(),
                     mBrightnessSliderFactory,
@@ -1431,7 +1387,7 @@
             });
         }
 
-        mReportRejectedTouch = mNotificationShadeWindowView
+        mReportRejectedTouch = getNotificationShadeWindowView()
                 .findViewById(R.id.report_rejected_touch);
         if (mReportRejectedTouch != null) {
             updateReportRejectedTouchVisibility();
@@ -1551,22 +1507,15 @@
 
     @VisibleForTesting
     void onShadeExpansionFullyChanged(Boolean isExpanded) {
-        if (mPanelExpanded != isExpanded) {
-            mPanelExpanded = isExpanded;
-            if (getShadeViewController() != null) {
-                // Needed to update SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE
-                getShadeViewController().updateSystemUiStateFlags();
+        if (isExpanded && mStatusBarStateController.getState() != StatusBarState.KEYGUARD) {
+            if (DEBUG) {
+                Log.v(TAG, "clearing notification effects from Height");
             }
-            if (isExpanded && mStatusBarStateController.getState() != StatusBarState.KEYGUARD) {
-                if (DEBUG) {
-                    Log.v(TAG, "clearing notification effects from Height");
-                }
-                clearNotificationEffects();
-            }
+            clearNotificationEffects();
+        }
 
-            if (!isExpanded) {
-                mRemoteInputManager.onPanelCollapsed();
-            }
+        if (!isExpanded) {
+            mRemoteInputManager.onPanelCollapsed();
         }
     }
 
@@ -1586,7 +1535,7 @@
 
     protected QS createDefaultQSFragment() {
         return mFragmentService
-                .getFragmentHostManager(mNotificationShadeWindowView)
+                .getFragmentHostManager(getNotificationShadeWindowView())
                 .create(QSFragment.class);
     }
 
@@ -1595,7 +1544,7 @@
         mActivityLaunchAnimator.setCallback(mActivityLaunchAnimatorCallback);
         mActivityLaunchAnimator.addListener(mActivityLaunchAnimatorListener);
         mNotificationAnimationProvider = new NotificationLaunchAnimatorControllerProvider(
-                mNotificationShadeWindowViewController,
+                getNotificationShadeWindowViewController(),
                 mNotifListContainer,
                 mHeadsUpManager,
                 mJankMonitor);
@@ -1604,12 +1553,10 @@
         mGutsManager.setNotificationActivityStarter(mNotificationActivityStarter);
         mShadeController.setNotificationPresenter(mPresenter);
         mNotificationsController.initialize(
-                this,
                 mPresenter,
                 mNotifListContainer,
                 mStackScrollerController.getNotifStackController(),
-                mNotificationActivityStarter,
-                mCentralSurfacesComponent.getBindRowCallback());
+                mNotificationActivityStarter);
     }
 
     /**
@@ -1621,22 +1568,6 @@
         mCommandQueue.disable(mDisplayId, state1, state2, false /* animate */);
     }
 
-    /**
-     * Ask the display to wake up if currently dozing, else do nothing
-     *
-     * @param time when to wake up
-     * @param why the reason for the wake up
-     */
-    @Override
-    public void wakeUpIfDozing(long time, String why, @PowerManager.WakeReason int wakeReason) {
-        if (mDozing && mScreenOffAnimationController.allowWakeUpIfDozing()) {
-            mPowerManager.wakeUp(
-                    time, wakeReason, "com.android.systemui:" + why);
-            mWakeUpComingFromTouch = true;
-            mFalsingCollector.onScreenOnFromTouch();
-        }
-    }
-
     // TODO(b/117478341): This was left such that CarStatusBar can override this method.
     // Try to remove this.
     protected void createNavigationBar(@Nullable RegisterStatusBarResult result) {
@@ -1652,7 +1583,7 @@
             mAutoHideController.checkUserAutoHide(event);
             mRemoteInputManager.checkRemoteInputOutside(event);
             mShadeController.onStatusBarTouch(event);
-            return mNotificationShadeWindowView.onTouchEvent(event);
+            return getNotificationShadeWindowView().onTouchEvent(event);
         };
     }
 
@@ -1665,27 +1596,16 @@
                 CollapsedStatusBarFragment.class,
                 mCentralSurfacesComponent::createCollapsedStatusBarFragment);
 
-        mNotificationShadeWindowView = mCentralSurfacesComponent.getNotificationShadeWindowView();
-        mNotificationShadeWindowViewController = mCentralSurfacesComponent
-                .getNotificationShadeWindowViewController();
+        ViewGroup windowRootView = mCentralSurfacesComponent.getWindowRootView();
         // TODO(b/277762009): Inject [NotificationShadeWindowView] directly into the controller.
         //  (Right now, there's a circular dependency.)
-        mNotificationShadeWindowController.setNotificationShadeView(mNotificationShadeWindowView);
-        mNotificationShadeWindowViewController.setupExpandedStatusBar();
-        NotificationPanelViewController npvc =
-                mCentralSurfacesComponent.getNotificationPanelViewController();
-        mShadeSurface = npvc;
-        mShadeController.setNotificationPanelViewController(npvc);
+        mNotificationShadeWindowController.setWindowRootView(windowRootView);
+        getNotificationShadeWindowViewController().setupExpandedStatusBar();
         mShadeController.setNotificationShadeWindowViewController(
-                mNotificationShadeWindowViewController);
-        mStackScrollerController =
-                mCentralSurfacesComponent.getNotificationStackScrollLayoutController();
-        mQsController = mCentralSurfacesComponent.getQuickSettingsController();
-        mStackScroller = mStackScrollerController.getView();
-        mNotifListContainer = mCentralSurfacesComponent.getNotificationListContainer();
+                getNotificationShadeWindowViewController());
+        mBackActionInteractor.setup(mQsController, mShadeSurface);
         mPresenter = mCentralSurfacesComponent.getNotificationPresenter();
         mNotificationActivityStarter = mCentralSurfacesComponent.getNotificationActivityStarter();
-        mNotificationShelfController = mCentralSurfacesComponent.getNotificationShelfController();
 
         mHeadsUpManager.addListener(mCentralSurfacesComponent.getStatusBarHeadsUpChangeListener());
 
@@ -1701,6 +1621,14 @@
         mCommandQueue.addCallback(mCommandQueueCallbacks);
     }
 
+    protected NotificationShadeWindowViewController getNotificationShadeWindowViewController() {
+        return mNotificationShadeWindowViewControllerLazy.get();
+    }
+
+    protected NotificationShadeWindowView getNotificationShadeWindowView() {
+        return getNotificationShadeWindowViewController().getView();
+    }
+
     protected void startKeyguard() {
         Trace.beginSection("CentralSurfaces#startKeyguard");
         mStatusBarStateController.addCallback(mStateListener,
@@ -1746,28 +1674,17 @@
 
         mLightBarController.setBiometricUnlockController(mBiometricUnlockController);
         mMediaManager.setBiometricUnlockController(mBiometricUnlockController);
-        mKeyguardDismissUtil.setDismissHandler(this::executeWhenUnlocked);
         Trace.endSection();
     }
 
     @Override
-    public NotificationShadeWindowView getNotificationShadeWindowView() {
-        return mNotificationShadeWindowView;
-    }
-
-    @Override
-    public NotificationShadeWindowViewController getNotificationShadeWindowViewController() {
-        return mNotificationShadeWindowViewController;
-    }
-
-    @Override
     public ShadeViewController getShadeViewController() {
         return mShadeSurface;
     }
 
     @Override
     public AuthKeyguardMessageArea getKeyguardMessageArea() {
-        return mNotificationShadeWindowViewController.getKeyguardMessageArea();
+        return getNotificationShadeWindowViewController().getKeyguardMessageArea();
     }
 
     @Override
@@ -1775,28 +1692,6 @@
         return mStatusBarWindowController.getStatusBarHeight();
     }
 
-    /**
-     * Disable QS if device not provisioned.
-     * If the user switcher is simple then disable QS during setup because
-     * the user intends to use the lock screen user switcher, QS in not needed.
-     */
-    @Override
-    public void updateQsExpansionEnabled() {
-        final boolean expandEnabled = mDeviceProvisionedController.isDeviceProvisioned()
-                && (mUserSetup || mUserSwitcherController == null
-                        || !mUserSwitcherController.isSimpleUserSwitcher())
-                && !isShadeDisabled()
-                && ((mDisabled2 & StatusBarManager.DISABLE2_QUICK_SETTINGS) == 0)
-                && !mDozing;
-        mQsController.setExpansionEnabledPolicy(expandEnabled);
-        Log.d(TAG, "updateQsExpansionEnabled - QS Expand enabled: " + expandEnabled);
-    }
-
-    @Override
-    public boolean isShadeDisabled() {
-        return (mDisabled2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) != 0;
-    }
-
     private void updateReportRejectedTouchVisibility() {
         if (mReportRejectedTouch == null) {
             return;
@@ -1805,11 +1700,6 @@
                 && mFalsingCollector.isReportingEnabled() ? View.VISIBLE : View.INVISIBLE);
     }
 
-    @Override
-    public boolean areNotificationAlertsDisabled() {
-        return (mDisabled1 & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0;
-    }
-
     /**
      * Whether we are currently animating an activity launch above the lockscreen (occluding
      * activity).
@@ -1819,11 +1709,6 @@
         return mIsLaunchingActivityOverLockscreen;
     }
 
-    @Override
-    public boolean isWakeUpComingFromTouch() {
-        return mWakeUpComingFromTouch;
-    }
-
     /**
      * To be called when there's a state change in StatusBarKeyguardViewManager.
      */
@@ -1833,11 +1718,6 @@
     }
 
     @Override
-    public ViewGroup getNotificationScrollLayout() {
-        return mStackScroller;
-    }
-
-    @Override
     public boolean isPulsing() {
         return mDozeServiceHost.isPulsing();
     }
@@ -1853,58 +1733,6 @@
         return mKeyguardStateController.isOccluded();
     }
 
-    /** A launch animation was cancelled. */
-    //TODO: These can / should probably be moved to NotificationPresenter or ShadeController
-    @Override
-    public void onLaunchAnimationCancelled(boolean isLaunchForActivity) {
-        if (mPresenter.isPresenterFullyCollapsed() && !mPresenter.isCollapsing()
-                && isLaunchForActivity) {
-            mShadeController.onClosingFinished();
-        } else {
-            mShadeController.collapseShade(true /* animate */);
-        }
-    }
-
-    /** A launch animation ended. */
-    @Override
-    public void onLaunchAnimationEnd(boolean launchIsFullScreen) {
-        if (!mPresenter.isCollapsing()) {
-            mShadeController.onClosingFinished();
-        }
-        if (launchIsFullScreen) {
-            mShadeController.instantCollapseShade();
-        }
-    }
-
-    /**
-     * Whether we should animate an activity launch.
-     *
-     * Note: This method must be called *before* dismissing the keyguard.
-     */
-    @Override
-    public boolean shouldAnimateLaunch(boolean isActivityIntent, boolean showOverLockscreen) {
-        // TODO(b/184121838): Support launch animations when occluded.
-        if (isOccluded()) {
-            return false;
-        }
-
-        // Always animate if we are not showing the keyguard or if we animate over the lockscreen
-        // (without unlocking it).
-        if (showOverLockscreen || !mKeyguardStateController.isShowing()) {
-            return true;
-        }
-
-        // We don't animate non-activity launches as they can break the animation.
-        // TODO(b/184121838): Support non activity launches on the lockscreen.
-        return isActivityIntent;
-    }
-
-    /** Whether we should animate an activity launch. */
-    @Override
-    public boolean shouldAnimateLaunch(boolean isActivityIntent) {
-        return shouldAnimateLaunch(isActivityIntent, false /* showOverLockscreen */);
-    }
-
     @Override
     public boolean isDeviceInVrMode() {
         return mPresenter.isDeviceInVrMode();
@@ -1940,8 +1768,11 @@
                 try {
                     EventLog.writeEvent(EventLogTags.SYSUI_HEADS_UP_ESCALATION,
                             sbn.getKey());
-                    wakeUpForFullScreenIntent();
-                    notification.fullScreenIntent.send();
+                    mPowerInteractor.wakeUpForFullScreenIntent();
+                    ActivityOptions opts = ActivityOptions.makeBasic();
+                    opts.setPendingIntentBackgroundActivityStartMode(
+                            ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
+                    notification.fullScreenIntent.send(opts.toBundle());
                     entry.notifyFullScreenIntentLaunched();
                 } catch (PendingIntent.CanceledException e) {
                 }
@@ -1950,37 +1781,6 @@
         mHeadsUpManager.releaseAllImmediately();
     }
 
-    @Override
-    public void wakeUpForFullScreenIntent() {
-        if (isGoingToSleep() || mDozing) {
-            mPowerManager.wakeUp(
-                    SystemClock.uptimeMillis(),
-                    PowerManager.WAKE_REASON_APPLICATION,
-                    "com.android.systemui:full_screen_intent");
-            mWakeUpComingFromTouch = false;
-        }
-    }
-
-    @Override
-    public void postAnimateCollapsePanels() {
-        mMainExecutor.execute(mShadeController::animateCollapseShade);
-    }
-
-    @Override
-    public void postAnimateForceCollapsePanels() {
-        mMainExecutor.execute(mShadeController::animateCollapseShadeForced);
-    }
-
-    @Override
-    public void postAnimateOpenPanels() {
-        mMessageRouter.sendMessage(MSG_OPEN_SETTINGS_PANEL);
-    }
-
-    @Override
-    public boolean isPanelExpanded() {
-        return mPanelExpanded;
-    }
-
     /**
      * Called when another window is about to transfer it's input focus.
      */
@@ -1998,11 +1798,8 @@
     }
 
     @Override
-    public void animateCollapseQuickSettings() {
-        if (mState == StatusBarState.SHADE) {
-            mShadeSurface.collapse(
-                    true, false /* delayed */, 1.0f /* speedUpFactor */);
-        }
+    public void onStatusBarTrackpadEvent(MotionEvent event) {
+        mShadeSurface.handleExternalTouch(event);
     }
 
     private void onExpandedInvisible() {
@@ -2181,11 +1978,9 @@
         pw.print("  mWallpaperSupported= "); pw.println(mWallpaperSupported);
 
         pw.println("  ShadeWindowView: ");
-        if (mNotificationShadeWindowViewController != null) {
-            mNotificationShadeWindowViewController.dump(pw, args);
-            CentralSurfaces.dumpBarTransitions(
-                    pw, "PhoneStatusBarTransitions", mStatusBarTransitions);
-        }
+        getNotificationShadeWindowViewController().dump(pw, args);
+        CentralSurfaces.dumpBarTransitions(
+                pw, "PhoneStatusBarTransitions", mStatusBarTransitions);
 
         pw.println("  mMediaManager: ");
         if (mMediaManager != null) {
@@ -2256,8 +2051,7 @@
                 + CameraIntents.getOverrideCameraPackage(mContext));
     }
 
-    @Override
-    public void createAndAddWindows(@Nullable RegisterStatusBarResult result) {
+    private void createAndAddWindows(@Nullable RegisterStatusBarResult result) {
         makeStatusBarView(result);
         mNotificationShadeWindowController.attach();
         mStatusBarWindowController.attach();
@@ -2274,16 +2068,19 @@
     }
 
     @Override
+    @Deprecated
     public float getDisplayDensity() {
         return mDisplayMetrics.density;
     }
 
     @Override
+    @Deprecated
     public float getDisplayWidth() {
         return mDisplayMetrics.widthPixels;
     }
 
     @Override
+    @Deprecated
     public float getDisplayHeight() {
         return mDisplayMetrics.heightPixels;
     }
@@ -2294,11 +2091,6 @@
     }
 
     @Override
-    public int getDisplayId() {
-        return mDisplayId;
-    }
-
-    @Override
     public void readyForKeyguardDone() {
         mStatusBarKeyguardViewManager.readyForKeyguardDone();
     }
@@ -2341,7 +2133,7 @@
                     mNotificationShadeWindowController.setNotTouchable(false);
                 }
                 finishBarAnimations();
-                resetUserExpandedStates();
+                mNotificationsController.resetUserExpandedStates();
             }
             Trace.endSection();
         }
@@ -2360,36 +2152,6 @@
         }
     };
 
-    @Override
-    public void resetUserExpandedStates() {
-        mNotificationsController.resetUserExpandedStates();
-    }
-
-    private void executeWhenUnlocked(OnDismissAction action, boolean requiresShadeOpen,
-            boolean afterKeyguardGone) {
-        if (mKeyguardStateController.isShowing() && requiresShadeOpen) {
-            mStatusBarStateController.setLeaveOpenOnKeyguardHide(true);
-        }
-        mActivityStarter.dismissKeyguardThenExecute(action, null /* cancelAction */,
-                afterKeyguardGone /* afterKeyguardGone */);
-    }
-
-    /**
-     * Notify the shade controller that the current user changed
-     *
-     * @param newUserId userId of the new user
-     */
-    @Override
-    public void setLockscreenUser(int newUserId) {
-        if (mLockscreenWallpaper != null && !mWallpaperManager.isLockscreenLiveWallpaperEnabled()) {
-            mLockscreenWallpaper.setCurrentUser(newUserId);
-        }
-        mScrimController.setCurrentUser(newUserId);
-        if (mWallpaperSupported) {
-            mWallpaperChangedReceiver.onReceive(mContext, null);
-        }
-    }
-
     /**
      * Reload some of our resources when the configuration changes.
      *
@@ -2860,7 +2622,6 @@
                 || (mDozing && mDozeParameters.shouldControlScreenOff() && keyguardVisibleOrWillBe);
 
         mShadeSurface.setDozing(mDozing, animate);
-        updateQsExpansionEnabled();
         Trace.endSection();
     }
 
@@ -2889,7 +2650,7 @@
             case KeyEvent.KEYCODE_BACK:
                 if (mState == StatusBarState.KEYGUARD
                         && mStatusBarKeyguardViewManager.dispatchBackKeyEventPreIme()) {
-                    return onBackPressed();
+                    return mBackActionInteractor.onBackRequested();
                 }
         }
         return false;
@@ -2929,34 +2690,6 @@
     }
 
     @Override
-    public boolean onBackPressed() {
-        if (mStatusBarKeyguardViewManager.canHandleBackPressed()) {
-            mStatusBarKeyguardViewManager.onBackPressed();
-            return true;
-        }
-        if (mQsController.isCustomizing()) {
-            mQsController.closeQsCustomizer();
-            return true;
-        }
-        if (mQsController.getExpanded()) {
-            mShadeSurface.animateCollapseQs(false);
-            return true;
-        }
-        if (mShadeSurface.closeUserSwitcherIfOpen()) {
-            return true;
-        }
-        if (shouldBackBeHandled()) {
-            if (mShadeSurface.canBeCollapsed()) {
-                // this is the Shade dismiss animation, so make sure QQS closes when it ends.
-                mShadeSurface.onBackPressed();
-                mShadeController.animateCollapseShade();
-            }
-            return true;
-        }
-        return false;
-    }
-
-    @Override
     public boolean onSpacePressed() {
         if (mDeviceInteractive && mState != StatusBarState.SHADE) {
             mShadeController.animateCollapseShadeForced();
@@ -2999,19 +2732,6 @@
     }
 
     /**
-     * Collapse the panel directly if we are on the main thread, post the collapsing on the main
-     * thread if we are not.
-     */
-    @Override
-    public void collapsePanelOnMainThread() {
-        if (Looper.getMainLooper().isCurrentThread()) {
-            mShadeController.collapseShade();
-        } else {
-            mContext.getMainExecutor().execute(mShadeController::collapseShade);
-        }
-    }
-
-    /**
      * Updates the light reveal effect to reflect the reason we're waking or sleeping (for example,
      * from the power button).
      * @param wakingUp Whether we're updating because we're waking up (true) or going to sleep
@@ -3047,11 +2767,6 @@
         }
     }
 
-    @Override
-    public LightRevealScrim getLightRevealScrim() {
-        return mLightRevealScrim;
-    }
-
     // TODO: Figure out way to remove these.
     @Override
     public NavigationBarView getNavigationBarView() {
@@ -3074,9 +2789,8 @@
     }
 
     protected ViewRootImpl getViewRootImpl()  {
-        NotificationShadeWindowView nswv = getNotificationShadeWindowView();
-        if (nswv != null) return nswv.getViewRootImpl();
-
+        View root = mNotificationShadeWindowController.getWindowRootView();
+        if (root != null) return root.getViewRootImpl();
         return null;
     }
     /**
@@ -3087,12 +2801,12 @@
         mBouncerShowing = bouncerShowing;
         mKeyguardBypassController.setBouncerShowing(bouncerShowing);
         mPulseExpansionHandler.setBouncerShowing(bouncerShowing);
+        mStackScrollerController.setBouncerShowingFromCentralSurfaces(bouncerShowing);
         setBouncerShowingForStatusBarComponents(bouncerShowing);
         mStatusBarHideIconsForBouncerManager.setBouncerShowingAndTriggerUpdate(bouncerShowing);
         mCommandQueue.recomputeDisableFlags(mDisplayId, true /* animate */);
         if (mBouncerShowing) {
-            wakeUpIfDozing(SystemClock.uptimeMillis(), "BOUNCER_VISIBLE",
-                    PowerManager.WAKE_REASON_GESTURE);
+            mPowerInteractor.wakeUpIfDozing("BOUNCER_VISIBLE", PowerManager.WAKE_REASON_GESTURE);
         }
         updateScrimController();
         if (!mBouncerShowing) {
@@ -3101,17 +2815,6 @@
     }
 
     /**
-     * Sets whether the bouncer over dream is showing. Note that the bouncer over dream is handled
-     * independently of the rest of the notification panel. As a result, setting this state via
-     * {@link #setBouncerShowing(boolean)} leads to unintended side effects from states modified
-     * behind the dream.
-     */
-    @Override
-    public void setBouncerShowingOverDream(boolean bouncerShowingOverDream) {
-        mBouncerShowingOverDream = bouncerShowingOverDream;
-    }
-
-    /**
      * Propagate the bouncer state to status bar components.
      *
      * Separate from {@link #setBouncerShowing} because we sometimes re-create the status bar and
@@ -3128,19 +2831,6 @@
         mShadeSurface.setBouncerShowing(bouncerShowing);
     }
 
-    /**
-     * Collapses the notification shade if it is tracking or expanded.
-     */
-    @Override
-    public void collapseShade() {
-        if (mShadeSurface.isTracking()) {
-            mNotificationShadeWindowViewController.cancelCurrentTouch();
-        }
-        if (mPanelExpanded && mState == StatusBarState.SHADE) {
-            mShadeController.animateCollapseShade();
-        }
-    }
-
     @VisibleForTesting
     final WakefulnessLifecycle.Observer mWakefulnessObserver = new WakefulnessLifecycle.Observer() {
         @Override
@@ -3149,11 +2839,10 @@
             releaseGestureWakeLock();
             mLaunchCameraWhenFinishedWaking = false;
             mDeviceInteractive = false;
-            mWakeUpComingFromTouch = false;
             updateVisibleToUser();
 
             updateNotificationPanelTouchState();
-            mNotificationShadeWindowViewController.cancelCurrentTouch();
+            getNotificationShadeWindowViewController().cancelCurrentTouch();
             if (mLaunchCameraOnFinishedGoingToSleep) {
                 mLaunchCameraOnFinishedGoingToSleep = false;
 
@@ -3465,12 +3154,6 @@
         updateScrimController();
     }
 
-    @VisibleForTesting
-    public void setNotificationShadeWindowViewController(
-            NotificationShadeWindowViewController nswvc) {
-        mNotificationShadeWindowViewController = nswvc;
-    }
-
     /**
      * Set 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
@@ -3505,7 +3188,7 @@
         mScrimController.setExpansionAffectsAlpha(!unlocking);
 
         if (mAlternateBouncerInteractor.isVisibleState()) {
-            if ((!isOccluded() || isPanelExpanded())
+            if ((!isOccluded() || mShadeSurface.isPanelExpanded())
                     && (mState == StatusBarState.SHADE || mState == StatusBarState.SHADE_LOCKED
                     || mTransitionToFullShadeProgress > 0f)) {
                 mScrimController.transitionTo(ScrimState.AUTH_SCRIMMED_SHADE);
@@ -3572,7 +3255,7 @@
     protected IStatusBarService mBarService;
 
     // all notifications
-    protected NotificationStackScrollLayout mStackScroller;
+    private final NotificationStackScrollLayout mStackScroller;
 
     protected AccessibilityManager mAccessibilityManager;
 
@@ -3602,7 +3285,7 @@
     protected Display mDisplay;
     private int mDisplayId;
 
-    protected NotificationShelfController mNotificationShelfController;
+    private final NotificationShelfController mNotificationShelfController;
 
     private final Lazy<AssistManager> mAssistManagerLazy;
 
@@ -3635,12 +3318,6 @@
     };
 
     @Override
-    public void setNotificationSnoozed(StatusBarNotification sbn, SnoozeOption snoozeOption) {
-        mNotificationsController.setNotificationSnoozed(sbn, snoozeOption);
-    }
-
-
-    @Override
     public void awakenDreams() {
         mUiBgExecutor.execute(() -> {
             try {
@@ -3711,7 +3388,7 @@
             mVisible = visible;
             if (visible) {
                 DejankUtils.notifyRendererOfExpensiveFrame(
-                        mNotificationShadeWindowView, "onShadeVisibilityChanged");
+                        getNotificationShadeWindowView(), "onShadeVisibilityChanged");
             } else {
                 mGutsManager.closeAndSaveGuts(true /* removeLeavebehind */, true /* force */,
                         true /* removeControls */, -1 /* x */, -1 /* y */, true /* resetMenu */);
@@ -3841,38 +3518,14 @@
 
             if (userSetup != mUserSetup) {
                 mUserSetup = userSetup;
-                if (!mUserSetup) {
-                    animateCollapseQuickSettings();
+                if (!mUserSetup && mState == StatusBarState.SHADE) {
+                    mShadeSurface.collapse(true /* animate */, false  /* delayed */,
+                            1.0f  /* speedUpFactor */);
                 }
-                updateQsExpansionEnabled();
             }
         }
     };
 
-    private final BroadcastReceiver mWallpaperChangedReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            if (!mWallpaperSupported) {
-                // Receiver should not have been registered at all...
-                Log.wtf(TAG, "WallpaperManager not supported");
-                return;
-            }
-            WallpaperInfo info = mWallpaperManager.getWallpaperInfoForUser(
-                    mUserTracker.getUserId());
-            mWallpaperController.onWallpaperInfoUpdated(info);
-
-            final boolean deviceSupportsAodWallpaper = mContext.getResources().getBoolean(
-                    com.android.internal.R.bool.config_dozeSupportsAodWallpaper);
-            // If WallpaperInfo is null, it must be ImageWallpaper.
-            final boolean supportsAmbientMode = deviceSupportsAodWallpaper
-                    && (info != null && info.supportsAmbientMode());
-
-            mNotificationShadeWindowController.setWallpaperSupportsAmbientMode(supportsAmbientMode);
-            mScrimController.setWallpaperSupportsAmbientMode(supportsAmbientMode);
-            mKeyguardViewMediator.setWallpaperSupportsAmbientMode(supportsAmbientMode);
-        }
-    };
-
     private final ConfigurationListener mConfigurationListener = new ConfigurationListener() {
         @Override
         public void onConfigChanged(Configuration newConfig) {
@@ -3979,7 +3632,6 @@
                     // reset them again when we're waking up
                     mShadeSurface.resetViews(dozingAnimated && isDozing);
 
-                    updateQsExpansionEnabled();
                     mKeyguardViewMediator.setDozing(mDozing);
 
                     updateDozingState();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
index 39b5b5a..8e9f382 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
@@ -35,11 +35,9 @@
 import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
 import com.android.systemui.statusbar.StatusBarIconView;
 import com.android.systemui.statusbar.StatusBarMobileView;
-import com.android.systemui.statusbar.StatusBarWifiView;
 import com.android.systemui.statusbar.StatusIconDisplayable;
 import com.android.systemui.statusbar.connectivity.ui.MobileContextProvider;
 import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
-import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
 import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger;
 import com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernStatusBarMobileView;
 import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel;
@@ -58,7 +56,6 @@
     private final ArrayList<ModernStatusBarMobileView> mModernMobileViews = new ArrayList<>();
     private final int mIconSize;
 
-    private StatusBarWifiView mWifiView;
     private ModernStatusBarWifiView mModernWifiView;
     private boolean mDemoMode;
     private int mColor;
@@ -238,36 +235,6 @@
         addView(v, 0, createLayoutParams());
     }
 
-    public void addDemoWifiView(WifiIconState state) {
-        Log.d(TAG, "addDemoWifiView: ");
-        StatusBarWifiView view = StatusBarWifiView.fromContext(mContext, state.slot);
-
-        int viewIndex = getChildCount();
-        // If we have mobile views, put wifi before them
-        for (int i = 0; i < getChildCount(); i++) {
-            View child = getChildAt(i);
-            if (child instanceof StatusBarMobileView
-                    || child instanceof ModernStatusBarMobileView) {
-                viewIndex = i;
-                break;
-            }
-        }
-
-        mWifiView = view;
-        mWifiView.applyWifiState(state);
-        mWifiView.setStaticDrawableColor(mColor);
-        addView(view, viewIndex, createLayoutParams());
-    }
-
-    public void updateWifiState(WifiIconState state) {
-        Log.d(TAG, "updateWifiState: ");
-        if (mWifiView == null) {
-            addDemoWifiView(state);
-        } else {
-            mWifiView.applyWifiState(state);
-        }
-    }
-
     /**
      * Add a new mobile icon view
      */
@@ -352,10 +319,7 @@
 
     public void onRemoveIcon(StatusIconDisplayable view) {
         if (view.getSlot().equals("wifi")) {
-            if (view instanceof StatusBarWifiView) {
-                removeView(mWifiView);
-                mWifiView = null;
-            } else if (view instanceof ModernStatusBarWifiView) {
+            if (view instanceof ModernStatusBarWifiView) {
                 Log.d(TAG, "onRemoveIcon: removing modern wifi view");
                 removeView(mModernWifiView);
                 mModernWifiView = null;
@@ -409,9 +373,6 @@
     public void onDarkChanged(ArrayList<Rect> areas, float darkIntensity, int tint) {
         setColor(DarkIconDispatcher.getTint(areas, mStatusIcons, tint));
 
-        if (mWifiView != null) {
-            mWifiView.onDarkChanged(areas, darkIntensity, tint);
-        }
         if (mModernWifiView != null) {
             mModernWifiView.onDarkChanged(areas, darkIntensity, tint);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
index 7312db6..ed9722e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
@@ -311,6 +311,7 @@
 
     @Override
     public void dozeTimeTick() {
+        mDozeInteractor.dozeTimeTick();
         mNotificationPanel.dozeTimeTick();
         mAuthController.dozeTimeTick();
         if (mAmbientIndicationContainer instanceof DozeReceiver) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
index 1a84dde..4f0cd80 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
@@ -263,9 +263,9 @@
         if (headsUpEntry != null && headsUpEntry.remoteInputActive != remoteInputActive) {
             headsUpEntry.remoteInputActive = remoteInputActive;
             if (remoteInputActive) {
-                headsUpEntry.removeAutoRemovalCallbacks();
+                headsUpEntry.removeAutoRemovalCallbacks("setRemoteInputActive(true)");
             } else {
-                headsUpEntry.updateEntry(false /* updatePostTime */);
+                headsUpEntry.updateEntry(false /* updatePostTime */, "setRemoteInputActive(false)");
             }
         }
     }
@@ -446,8 +446,8 @@
         }
 
         @Override
-        public void updateEntry(boolean updatePostTime) {
-            super.updateEntry(updatePostTime);
+        public void updateEntry(boolean updatePostTime, String reason) {
+            super.updateEntry(updatePostTime, reason);
 
             if (mEntriesToRemoveAfterExpand.contains(mEntry)) {
                 mEntriesToRemoveAfterExpand.remove(mEntry);
@@ -465,9 +465,9 @@
 
             this.expanded = expanded;
             if (expanded) {
-                removeAutoRemovalCallbacks();
+                removeAutoRemovalCallbacks("setExpanded(true)");
             } else {
-                updateEntry(false /* updatePostTime */);
+                updateEntry(false /* updatePostTime */, "setExpanded(false)");
             }
         }
 
@@ -478,9 +478,9 @@
 
             mGutsShownPinned = gutsShownPinned;
             if (gutsShownPinned) {
-                removeAutoRemovalCallbacks();
+                removeAutoRemovalCallbacks("setGutsShownPinned(true)");
             } else {
-                updateEntry(false /* updatePostTime */);
+                updateEntry(false /* updatePostTime */, "setGutsShownPinned(false)");
             }
         }
 
@@ -494,7 +494,7 @@
         private void extendPulse() {
             if (!extended) {
                 extended = true;
-                updateEntry(false);
+                updateEntry(false, "extendPulse()");
             }
         }
 
@@ -544,7 +544,7 @@
                 // Let's make sure all huns we got while dozing time out within the normal timeout
                 // duration. Otherwise they could get stuck for a very long time
                 for (AlertEntry entry : mAlertEntries.values()) {
-                    entry.updateEntry(true /* updatePostTime */);
+                    entry.updateEntry(true /* updatePostTime */, "onDozingChanged(false)");
                 }
             }
         }
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 ff1b31d..924aac4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
@@ -50,12 +50,6 @@
     @DevicePostureInt private var postureState: Int = DEVICE_POSTURE_UNKNOWN
     private var pendingUnlock: PendingUnlock? = null
     private val listeners = mutableListOf<OnBypassStateChangedListener>()
-    private val postureCallback = DevicePostureController.Callback { posture ->
-        if (postureState != posture) {
-            postureState = posture
-            notifyListeners()
-        }
-    }
     private val faceAuthEnabledChangedCallback = object : KeyguardStateController.Callback {
         override fun onFaceAuthEnabledChanged() = notifyListeners()
     }
@@ -162,10 +156,8 @@
 
         val dismissByDefault = if (context.resources.getBoolean(
                         com.android.internal.R.bool.config_faceAuthDismissesKeyguard)) 1 else 0
-        tunerService.addTunable(object : TunerService.Tunable {
-            override fun onTuningChanged(key: String?, newValue: String?) {
-                bypassEnabled = tunerService.getValue(key, dismissByDefault) != 0
-            }
+        tunerService.addTunable({ key, _ ->
+            bypassEnabled = tunerService.getValue(key, dismissByDefault) != 0
         }, Settings.Secure.FACE_UNLOCK_DISMISSES_KEYGUARD)
         lockscreenUserManager.addUserChangedListener(
                 object : NotificationLockscreenUserManager.UserChangedListener {
@@ -281,8 +273,6 @@
     }
 
     companion object {
-        const val BYPASS_FADE_DURATION = 67
-
         private const val FACE_UNLOCK_BYPASS_NO_OVERRIDE = 0
         private const val FACE_UNLOCK_BYPASS_ALWAYS = 1
         private const val FACE_UNLOCK_BYPASS_NEVER = 2
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardDismissUtil.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardDismissUtil.java
index 27b68f2..1c90c0d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardDismissUtil.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardDismissUtil.java
@@ -16,48 +16,41 @@
 
 package com.android.systemui.statusbar.phone;
 
-import android.util.Log;
-
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
+import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import javax.inject.Inject;
 
 /**
- * Executes actions that require the screen to be unlocked. Delegates the actual handling to an
- * implementation passed via {@link #setDismissHandler}.
+ * Executes actions that require the screen to be unlocked.
  */
 @SysUISingleton
 public class KeyguardDismissUtil implements KeyguardDismissHandler {
-    private static final String TAG = "KeyguardDismissUtil";
+    private final KeyguardStateController mKeyguardStateController;
 
-    private volatile KeyguardDismissHandler mDismissHandler;
+    private final SysuiStatusBarStateController mStatusBarStateController;
+
+    private final ActivityStarter mActivityStarter;
 
     @Inject
-    public KeyguardDismissUtil() {
+    public KeyguardDismissUtil(KeyguardStateController keyguardStateController,
+            SysuiStatusBarStateController statusBarStateController,
+            ActivityStarter activityStarter) {
+        mKeyguardStateController = keyguardStateController;
+        mStatusBarStateController = statusBarStateController;
+        mActivityStarter = activityStarter;
     }
 
-    /** Sets the actual {@link KeyguardDismissHandler} implementation. */
-    public void setDismissHandler(KeyguardDismissHandler dismissHandler) {
-        mDismissHandler = dismissHandler;
-    }
-
-    /**
-     * Executes an action that requires the screen to be unlocked.
-     *
-     * <p>Must be called after {@link #setDismissHandler}.
-     *
-     * @param requiresShadeOpen does the shade need to be forced open when hiding the keyguard?
-     */
     @Override
     public void executeWhenUnlocked(OnDismissAction action, boolean requiresShadeOpen,
             boolean afterKeyguardGone) {
-        KeyguardDismissHandler dismissHandler = mDismissHandler;
-        if (dismissHandler == null) {
-            Log.wtf(TAG, "KeyguardDismissHandler not set.");
-            action.onDismiss();
-            return;
+        if (mKeyguardStateController.isShowing() && requiresShadeOpen) {
+            mStatusBarStateController.setLeaveOpenOnKeyguardHide(true);
         }
-        dismissHandler.executeWhenUnlocked(action, requiresShadeOpen, afterKeyguardGone);
+        mActivityStarter.dismissKeyguardThenExecute(action, null /* cancelAction */,
+                afterKeyguardGone /* afterKeyguardGone */);
     }
 }
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 74ab47f..bde5c32 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
@@ -62,7 +62,7 @@
     }
 
     private fun init() {
-        dumpManager.registerDumpable(javaClass.name, this)
+        dumpManager.registerDumpable(this)
         statusBarStateController.addCallback(statusBarStateListener)
         keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback)
         updateListeningState()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
index 4d716c2..680f19a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
@@ -45,7 +45,7 @@
 import com.android.systemui.R;
 import com.android.systemui.battery.BatteryMeterViewController;
 import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.log.LogLevel;
+import com.android.systemui.log.core.LogLevel;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shade.ShadeViewStateProvider;
 import com.android.systemui.statusbar.CommandQueue;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LSShadeTransitionLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LSShadeTransitionLogger.kt
index 5c357d7..686efb7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LSShadeTransitionLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LSShadeTransitionLogger.kt
@@ -21,7 +21,7 @@
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent
 import com.android.systemui.log.dagger.LSShadeTransitionLog
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
 import com.android.systemui.statusbar.notification.row.ExpandableView
 import javax.inject.Inject
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 f26a84b..dc5eb0d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
@@ -38,8 +38,6 @@
 import com.android.systemui.R;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.settings.DisplayTracker;
@@ -65,7 +63,6 @@
 
     private final SysuiDarkIconDispatcher mStatusBarIconController;
     private final BatteryController mBatteryController;
-    private final boolean mUseNewLightBarLogic;
     private BiometricUnlockController mBiometricUnlockController;
 
     private LightBarTransitionsController mNavigationBarController;
@@ -123,10 +120,8 @@
             DarkIconDispatcher darkIconDispatcher,
             BatteryController batteryController,
             NavigationModeController navModeController,
-            FeatureFlags featureFlags,
             DumpManager dumpManager,
             DisplayTracker displayTracker) {
-        mUseNewLightBarLogic = featureFlags.isEnabled(Flags.NEW_LIGHT_BAR_LOGIC);
         mDarkIconColor = ctx.getColor(R.color.dark_mode_icon_color_single_tone);
         mLightIconColor = ctx.getColor(R.color.light_mode_icon_color_single_tone);
         mStatusBarIconController = (SysuiDarkIconDispatcher) darkIconDispatcher;
@@ -188,51 +183,31 @@
             final boolean last = mNavigationLight;
             mHasLightNavigationBar = isLight(appearance, navigationBarMode,
                     APPEARANCE_LIGHT_NAVIGATION_BARS);
-            if (mUseNewLightBarLogic) {
-                final boolean ignoreScrimForce = mDirectReplying && mNavbarColorManagedByIme;
-                final boolean darkForScrim = mForceDarkForScrim && !ignoreScrimForce;
-                final boolean lightForScrim = mForceLightForScrim && !ignoreScrimForce;
-                final boolean darkForQs = (mQsCustomizing || mQsExpanded) && !mBouncerVisible;
-                final boolean darkForTop = darkForQs || mGlobalActionsVisible;
-                mNavigationLight =
-                        ((mHasLightNavigationBar && !darkForScrim) || lightForScrim) && !darkForTop;
-                if (DEBUG_NAVBAR) {
-                    mLastNavigationBarAppearanceChangedLog = getLogStringBuilder()
-                            .append("onNavigationBarAppearanceChanged()")
-                            .append(" appearance=").append(appearance)
-                            .append(" nbModeChanged=").append(nbModeChanged)
-                            .append(" navigationBarMode=").append(navigationBarMode)
-                            .append(" navbarColorManagedByIme=").append(navbarColorManagedByIme)
-                            .append(" mHasLightNavigationBar=").append(mHasLightNavigationBar)
-                            .append(" ignoreScrimForce=").append(ignoreScrimForce)
-                            .append(" darkForScrim=").append(darkForScrim)
-                            .append(" lightForScrim=").append(lightForScrim)
-                            .append(" darkForQs=").append(darkForQs)
-                            .append(" darkForTop=").append(darkForTop)
-                            .append(" mNavigationLight=").append(mNavigationLight)
-                            .append(" last=").append(last)
-                            .append(" timestamp=").append(System.currentTimeMillis())
-                            .toString();
-                    if (DEBUG_LOGS) Log.d(TAG, mLastNavigationBarAppearanceChangedLog);
-                }
-            } else {
-                mNavigationLight = mHasLightNavigationBar
-                        && (mDirectReplying && mNavbarColorManagedByIme || !mForceDarkForScrim)
-                        && !mQsCustomizing;
-                if (DEBUG_NAVBAR) {
-                    mLastNavigationBarAppearanceChangedLog = getLogStringBuilder()
-                            .append("onNavigationBarAppearanceChanged()")
-                            .append(" appearance=").append(appearance)
-                            .append(" nbModeChanged=").append(nbModeChanged)
-                            .append(" navigationBarMode=").append(navigationBarMode)
-                            .append(" navbarColorManagedByIme=").append(navbarColorManagedByIme)
-                            .append(" mHasLightNavigationBar=").append(mHasLightNavigationBar)
-                            .append(" mNavigationLight=").append(mNavigationLight)
-                            .append(" last=").append(last)
-                            .append(" timestamp=").append(System.currentTimeMillis())
-                            .toString();
-                    if (DEBUG_LOGS) Log.d(TAG, mLastNavigationBarAppearanceChangedLog);
-                }
+            final boolean ignoreScrimForce = mDirectReplying && mNavbarColorManagedByIme;
+            final boolean darkForScrim = mForceDarkForScrim && !ignoreScrimForce;
+            final boolean lightForScrim = mForceLightForScrim && !ignoreScrimForce;
+            final boolean darkForQs = (mQsCustomizing || mQsExpanded) && !mBouncerVisible;
+            final boolean darkForTop = darkForQs || mGlobalActionsVisible;
+            mNavigationLight =
+                    ((mHasLightNavigationBar && !darkForScrim) || lightForScrim) && !darkForTop;
+            if (DEBUG_NAVBAR) {
+                mLastNavigationBarAppearanceChangedLog = getLogStringBuilder()
+                        .append("onNavigationBarAppearanceChanged()")
+                        .append(" appearance=").append(appearance)
+                        .append(" nbModeChanged=").append(nbModeChanged)
+                        .append(" navigationBarMode=").append(navigationBarMode)
+                        .append(" navbarColorManagedByIme=").append(navbarColorManagedByIme)
+                        .append(" mHasLightNavigationBar=").append(mHasLightNavigationBar)
+                        .append(" ignoreScrimForce=").append(ignoreScrimForce)
+                        .append(" darkForScrim=").append(darkForScrim)
+                        .append(" lightForScrim=").append(lightForScrim)
+                        .append(" darkForQs=").append(darkForQs)
+                        .append(" darkForTop=").append(darkForTop)
+                        .append(" mNavigationLight=").append(mNavigationLight)
+                        .append(" last=").append(last)
+                        .append(" timestamp=").append(System.currentTimeMillis())
+                        .toString();
+                if (DEBUG_LOGS) Log.d(TAG, mLastNavigationBarAppearanceChangedLog);
             }
             if (mNavigationLight != last) {
                 updateNavigation();
@@ -311,66 +286,39 @@
 
     public void setScrimState(ScrimState scrimState, float scrimBehindAlpha,
             GradientColors scrimInFrontColor) {
-        if (mUseNewLightBarLogic) {
-            boolean bouncerVisibleLast = mBouncerVisible;
-            boolean forceDarkForScrimLast = mForceDarkForScrim;
-            boolean forceLightForScrimLast = mForceLightForScrim;
-            mBouncerVisible =
-                    scrimState == ScrimState.BOUNCER || scrimState == ScrimState.BOUNCER_SCRIMMED;
-            final boolean forceForScrim = mBouncerVisible
-                    || scrimBehindAlpha >= NAV_BAR_INVERSION_SCRIM_ALPHA_THRESHOLD;
-            final boolean scrimColorIsLight = scrimInFrontColor.supportsDarkText();
+        boolean bouncerVisibleLast = mBouncerVisible;
+        boolean forceDarkForScrimLast = mForceDarkForScrim;
+        boolean forceLightForScrimLast = mForceLightForScrim;
+        mBouncerVisible =
+                scrimState == ScrimState.BOUNCER || scrimState == ScrimState.BOUNCER_SCRIMMED;
+        final boolean forceForScrim = mBouncerVisible
+                || scrimBehindAlpha >= NAV_BAR_INVERSION_SCRIM_ALPHA_THRESHOLD;
+        final boolean scrimColorIsLight = scrimInFrontColor.supportsDarkText();
 
-            mForceDarkForScrim = forceForScrim && !scrimColorIsLight;
-            mForceLightForScrim = forceForScrim && scrimColorIsLight;
-            if (mBouncerVisible != bouncerVisibleLast) {
-                reevaluate();
-            } else if (mHasLightNavigationBar) {
-                if (mForceDarkForScrim != forceDarkForScrimLast) reevaluate();
-            } else {
-                if (mForceLightForScrim != forceLightForScrimLast) reevaluate();
-            }
-            if (DEBUG_NAVBAR) {
-                mLastSetScrimStateLog = getLogStringBuilder()
-                        .append("setScrimState()")
-                        .append(" scrimState=").append(scrimState)
-                        .append(" scrimBehindAlpha=").append(scrimBehindAlpha)
-                        .append(" scrimInFrontColor=").append(scrimInFrontColor)
-                        .append(" forceForScrim=").append(forceForScrim)
-                        .append(" scrimColorIsLight=").append(scrimColorIsLight)
-                        .append(" mHasLightNavigationBar=").append(mHasLightNavigationBar)
-                        .append(" mBouncerVisible=").append(mBouncerVisible)
-                        .append(" mForceDarkForScrim=").append(mForceDarkForScrim)
-                        .append(" mForceLightForScrim=").append(mForceLightForScrim)
-                        .append(" timestamp=").append(System.currentTimeMillis())
-                        .toString();
-                if (DEBUG_LOGS) Log.d(TAG, mLastSetScrimStateLog);
-            }
+        mForceDarkForScrim = forceForScrim && !scrimColorIsLight;
+        mForceLightForScrim = forceForScrim && scrimColorIsLight;
+        if (mBouncerVisible != bouncerVisibleLast) {
+            reevaluate();
+        } else if (mHasLightNavigationBar) {
+            if (mForceDarkForScrim != forceDarkForScrimLast) reevaluate();
         } else {
-            boolean forceDarkForScrimLast = mForceDarkForScrim;
-            // For BOUNCER/BOUNCER_SCRIMMED cases, we assume that alpha is always below threshold.
-            // This enables IMEs to control the navigation bar color.
-            // For other cases, scrim should be able to veto the light navigation bar.
-            // NOTE: this was also wrong for S and has been removed in the new logic.
-            mForceDarkForScrim = scrimState != ScrimState.BOUNCER
-                    && scrimState != ScrimState.BOUNCER_SCRIMMED
-                    && scrimBehindAlpha >= NAV_BAR_INVERSION_SCRIM_ALPHA_THRESHOLD
-                    && !scrimInFrontColor.supportsDarkText();
-            if (mHasLightNavigationBar && (mForceDarkForScrim != forceDarkForScrimLast)) {
-                reevaluate();
-            }
-            if (DEBUG_NAVBAR) {
-                mLastSetScrimStateLog = getLogStringBuilder()
-                        .append("setScrimState()")
-                        .append(" scrimState=").append(scrimState)
-                        .append(" scrimBehindAlpha=").append(scrimBehindAlpha)
-                        .append(" scrimInFrontColor=").append(scrimInFrontColor)
-                        .append(" mHasLightNavigationBar=").append(mHasLightNavigationBar)
-                        .append(" mForceDarkForScrim=").append(mForceDarkForScrim)
-                        .append(" timestamp=").append(System.currentTimeMillis())
-                        .toString();
-                if (DEBUG_LOGS) Log.d(TAG, mLastSetScrimStateLog);
-            }
+            if (mForceLightForScrim != forceLightForScrimLast) reevaluate();
+        }
+        if (DEBUG_NAVBAR) {
+            mLastSetScrimStateLog = getLogStringBuilder()
+                    .append("setScrimState()")
+                    .append(" scrimState=").append(scrimState)
+                    .append(" scrimBehindAlpha=").append(scrimBehindAlpha)
+                    .append(" scrimInFrontColor=").append(scrimInFrontColor)
+                    .append(" forceForScrim=").append(forceForScrim)
+                    .append(" scrimColorIsLight=").append(scrimColorIsLight)
+                    .append(" mHasLightNavigationBar=").append(mHasLightNavigationBar)
+                    .append(" mBouncerVisible=").append(mBouncerVisible)
+                    .append(" mForceDarkForScrim=").append(mForceDarkForScrim)
+                    .append(" mForceLightForScrim=").append(mForceLightForScrim)
+                    .append(" timestamp=").append(System.currentTimeMillis())
+                    .toString();
+            if (DEBUG_LOGS) Log.d(TAG, mLastSetScrimStateLog);
         }
     }
 
@@ -498,7 +446,6 @@
         private final DarkIconDispatcher mDarkIconDispatcher;
         private final BatteryController mBatteryController;
         private final NavigationModeController mNavModeController;
-        private final FeatureFlags mFeatureFlags;
         private final DumpManager mDumpManager;
         private final DisplayTracker mDisplayTracker;
 
@@ -507,14 +454,12 @@
                 DarkIconDispatcher darkIconDispatcher,
                 BatteryController batteryController,
                 NavigationModeController navModeController,
-                FeatureFlags featureFlags,
                 DumpManager dumpManager,
                 DisplayTracker displayTracker) {
 
             mDarkIconDispatcher = darkIconDispatcher;
             mBatteryController = batteryController;
             mNavModeController = navModeController;
-            mFeatureFlags = featureFlags;
             mDumpManager = dumpManager;
             mDisplayTracker = displayTracker;
         }
@@ -522,7 +467,7 @@
         /** Create an {@link LightBarController} */
         public LightBarController create(Context context) {
             return new LightBarController(context, mDarkIconDispatcher, mBatteryController,
-                    mNavModeController, mFeatureFlags, mDumpManager, mDisplayTracker);
+                    mNavModeController, mDumpManager, mDisplayTracker);
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
index c07b5e0..b2c39f7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
@@ -40,12 +40,17 @@
 
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.systemui.CoreStartable;
 import com.android.systemui.Dumpable;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.NotificationMediaManager;
+import com.android.systemui.user.data.model.SelectedUserModel;
+import com.android.systemui.user.data.model.SelectionStatus;
+import com.android.systemui.user.data.repository.UserRepository;
+import com.android.systemui.util.kotlin.JavaAdapter;
 
 import libcore.io.IoUtils;
 
@@ -59,7 +64,7 @@
  */
 @SysUISingleton
 public class LockscreenWallpaper extends IWallpaperManagerCallback.Stub implements Runnable,
-        Dumpable {
+        Dumpable, CoreStartable {
 
     private static final String TAG = "LockscreenWallpaper";
 
@@ -72,6 +77,8 @@
     private final WallpaperManager mWallpaperManager;
     private final KeyguardUpdateMonitor mUpdateMonitor;
     private final Handler mH;
+    private final JavaAdapter mJavaAdapter;
+    private final UserRepository mUserRepository;
 
     private boolean mCached;
     private Bitmap mCache;
@@ -88,6 +95,8 @@
             DumpManager dumpManager,
             NotificationMediaManager mediaManager,
             @Main Handler mainHandler,
+            JavaAdapter javaAdapter,
+            UserRepository userRepository,
             UserTracker userTracker) {
         dumpManager.registerDumpable(getClass().getSimpleName(), this);
         mWallpaperManager = wallpaperManager;
@@ -95,6 +104,8 @@
         mUpdateMonitor = keyguardUpdateMonitor;
         mMediaManager = mediaManager;
         mH = mainHandler;
+        mJavaAdapter = javaAdapter;
+        mUserRepository = userRepository;
 
         if (iWallpaperManager != null && !mWallpaperManager.isLockscreenLiveWallpaperEnabled()) {
             // Service is disabled on some devices like Automotive
@@ -106,6 +117,14 @@
         }
     }
 
+    @Override
+    public void start() {
+        if (!isLockscreenLiveWallpaperEnabled()) {
+            mJavaAdapter.alwaysCollectFlow(
+                    mUserRepository.getSelectedUser(), this::setSelectedUser);
+        }
+    }
+
     public Bitmap getBitmap() {
         assertLockscreenLiveWallpaperNotEnabled();
 
@@ -169,9 +188,15 @@
         }
     }
 
-    public void setCurrentUser(int user) {
+    private void setSelectedUser(SelectedUserModel selectedUserModel) {
         assertLockscreenLiveWallpaperNotEnabled();
 
+        if (selectedUserModel.getSelectionStatus().equals(SelectionStatus.SELECTION_IN_PROGRESS)) {
+            // Wait until the selection has finished before updating.
+            return;
+        }
+
+        int user = selectedUserModel.getUserInfo().id;
         if (user != mCurrentUserId) {
             if (mSelectedUser == null || user != mSelectedUser.getIdentifier()) {
                 mCached = false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
index 313410a..e18c9d8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
@@ -33,14 +33,11 @@
 import com.android.systemui.statusbar.NotificationShelfController;
 import com.android.systemui.statusbar.StatusBarIconView;
 import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.notification.AnimatableProperty;
 import com.android.systemui.statusbar.notification.NotificationUtils;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
-import com.android.systemui.statusbar.notification.PropertyAnimator;
 import com.android.systemui.statusbar.notification.collection.ListEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider;
-import com.android.systemui.statusbar.notification.stack.AnimationProperties;
 import com.android.systemui.statusbar.window.StatusBarWindowController;
 import com.android.wm.shell.bubbles.Bubbles;
 
@@ -91,8 +88,6 @@
     private final ArrayList<Rect> mTintAreas = new ArrayList<>();
     private final Context mContext;
 
-    private final DemoModeController mDemoModeController;
-
     private final FeatureFlags mFeatureFlags;
 
     private int mAodIconAppearTranslation;
@@ -139,8 +134,7 @@
         wakeUpCoordinator.addListener(this);
         mBypassController = keyguardBypassController;
         mBubblesOptional = bubblesOptional;
-        mDemoModeController = demoModeController;
-        mDemoModeController.addCallback(this);
+        demoModeController.addCallback(this);
         mStatusBarWindowController = statusBarWindowController;
         mScreenOffAnimationController = screenOffAnimationController;
         notificationListener.addNotificationSettingsListener(mSettingsListener);
@@ -163,7 +157,6 @@
         LayoutInflater layoutInflater = LayoutInflater.from(context);
         mNotificationIconArea = inflateIconArea(layoutInflater);
         mNotificationIcons = mNotificationIconArea.findViewById(R.id.notificationIcons);
-
     }
 
     /**
@@ -185,16 +178,6 @@
         updateIconLayoutParams(mContext);
     }
 
-    /**
-     * Update position of the view, with optional animation
-     */
-    public void updatePosition(int x, AnimationProperties props, boolean animate) {
-        if (mAodIcons != null) {
-            PropertyAnimator.setProperty(mAodIcons, AnimatableProperty.TRANSLATION_X, x, props,
-                    animate);
-        }
-    }
-
     public void setupShelf(NotificationShelfController notificationShelfController) {
         NotificationShelfController.assertRefactorFlagDisabled(mFeatureFlags);
         mShelfIcons = notificationShelfController.getShelfIcons();
@@ -239,8 +222,8 @@
 
     private void reloadDimens(Context context) {
         Resources res = context.getResources();
-        mIconSize = res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_icon_size);
-        mIconHPadding = res.getDimensionPixelSize(R.dimen.status_bar_icon_padding);
+        mIconSize = res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_icon_size_sp);
+        mIconHPadding = res.getDimensionPixelSize(R.dimen.status_bar_icon_horizontal_margin);
         mAodIconAppearTranslation = res.getDimensionPixelSize(
                 R.dimen.shelf_appear_translation);
     }
@@ -303,6 +286,7 @@
         }
         return true;
     }
+
     /**
      * Updates the notifications with the given list of notifications to display.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
index bef422c..3770c1d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
@@ -54,19 +54,13 @@
  * correctly on the screen.
  */
 public class NotificationIconContainer extends ViewGroup {
-    /**
-     * A float value indicating how much before the overflow start the icons should transform into
-     * a dot. A value of 0 means that they are exactly at the end and a value of 1 means it starts
-     * 1 icon width early.
-     */
-    public static final float OVERFLOW_EARLY_AMOUNT = 0.2f;
     private static final int NO_VALUE = Integer.MIN_VALUE;
     private static final String TAG = "NotificationIconContainer";
     private static final boolean DEBUG = false;
     private static final boolean DEBUG_OVERFLOW = false;
     private static final int CANNED_ANIMATION_DURATION = 100;
     private static final AnimationProperties DOT_ANIMATION_PROPERTIES = new AnimationProperties() {
-        private AnimationFilter mAnimationFilter = new AnimationFilter().animateX();
+        private final AnimationFilter mAnimationFilter = new AnimationFilter().animateX();
 
         @Override
         public AnimationFilter getAnimationFilter() {
@@ -75,7 +69,7 @@
     }.setDuration(200);
 
     private static final AnimationProperties ICON_ANIMATION_PROPERTIES = new AnimationProperties() {
-        private AnimationFilter mAnimationFilter = new AnimationFilter()
+        private final AnimationFilter mAnimationFilter = new AnimationFilter()
                 .animateX()
                 .animateY()
                 .animateAlpha()
@@ -92,7 +86,7 @@
      * Temporary AnimationProperties to avoid unnecessary allocations.
      */
     private static final AnimationProperties sTempProperties = new AnimationProperties() {
-        private AnimationFilter mAnimationFilter = new AnimationFilter();
+        private final AnimationFilter mAnimationFilter = new AnimationFilter();
 
         @Override
         public AnimationFilter getAnimationFilter() {
@@ -101,7 +95,7 @@
     };
 
     private static final AnimationProperties ADD_ICON_PROPERTIES = new AnimationProperties() {
-        private AnimationFilter mAnimationFilter = new AnimationFilter().animateAlpha();
+        private final AnimationFilter mAnimationFilter = new AnimationFilter().animateAlpha();
 
         @Override
         public AnimationFilter getAnimationFilter() {
@@ -115,7 +109,7 @@
      */
     private static final AnimationProperties UNISOLATION_PROPERTY_OTHERS
             = new AnimationProperties() {
-        private AnimationFilter mAnimationFilter = new AnimationFilter().animateAlpha();
+        private final AnimationFilter mAnimationFilter = new AnimationFilter().animateAlpha();
 
         @Override
         public AnimationFilter getAnimationFilter() {
@@ -128,7 +122,7 @@
      * This animates the translation back to the right position.
      */
     private static final AnimationProperties UNISOLATION_PROPERTY = new AnimationProperties() {
-        private AnimationFilter mAnimationFilter = new AnimationFilter().animateX();
+        private final AnimationFilter mAnimationFilter = new AnimationFilter().animateX();
 
         @Override
         public AnimationFilter getAnimationFilter() {
@@ -147,7 +141,6 @@
     private boolean mIsStaticLayout = true;
     private final HashMap<View, IconState> mIconStates = new HashMap<>();
     private int mDotPadding;
-    private int mStaticDotRadius;
     private int mStaticDotDiameter;
     private int mActualLayoutWidth = NO_VALUE;
     private float mActualPaddingEnd = NO_VALUE;
@@ -170,7 +163,7 @@
     private boolean mIsShowingOverflowDot;
     private StatusBarIconView mIsolatedIcon;
     private Rect mIsolatedIconLocation;
-    private int[] mAbsolutePosition = new int[2];
+    private final int[] mAbsolutePosition = new int[2];
     private View mIsolatedIconForAnimation;
     private int mThemedTextColorPrimary;
 
@@ -186,8 +179,8 @@
         mMaxStaticIcons = getResources().getInteger(R.integer.max_notif_static_icons);
 
         mDotPadding = getResources().getDimensionPixelSize(R.dimen.overflow_icon_dot_padding);
-        mStaticDotRadius = getResources().getDimensionPixelSize(R.dimen.overflow_dot_radius);
-        mStaticDotDiameter = 2 * mStaticDotRadius;
+        int staticDotRadius = getResources().getDimensionPixelSize(R.dimen.overflow_dot_radius);
+        mStaticDotDiameter = 2 * staticDotRadius;
 
         final Context themedContext = new ContextThemeWrapper(getContext(),
                 com.android.internal.R.style.Theme_DeviceDefault_DayNight);
@@ -339,6 +332,7 @@
             }
         }
         if (child instanceof StatusBarIconView) {
+            ((StatusBarIconView) child).updateIconDimens();
             ((StatusBarIconView) child).setDozing(mDozing, false, 0);
         }
     }
@@ -447,9 +441,14 @@
     @VisibleForTesting
     boolean isOverflowing(boolean isLastChild, float translationX, float layoutEnd,
             float iconSize) {
-        // Layout end, as used here, does not include padding end.
-        final float overflowX = isLastChild ? layoutEnd : layoutEnd - iconSize;
-        return translationX >= overflowX;
+        if (isLastChild) {
+            return translationX + iconSize > layoutEnd;
+        } else {
+            // If the child is not the last child, we need to ensure that we have room for the next
+            // icon and the dot. The dot could be as large as an icon, so verify that we have room
+            // for 2 icons.
+            return translationX + iconSize * 2f > layoutEnd;
+        }
     }
 
     /**
@@ -489,10 +488,7 @@
             // First icon to overflow.
             if (firstOverflowIndex == -1 && isOverflowing) {
                 firstOverflowIndex = i;
-                mVisualOverflowStart = layoutEnd - mIconSize;
-                if (forceOverflow || mIsStaticLayout) {
-                    mVisualOverflowStart = Math.min(translationX, mVisualOverflowStart);
-                }
+                mVisualOverflowStart = translationX;
             }
             final float drawingScale = mOnLockScreen && view instanceof StatusBarIconView
                     ? ((StatusBarIconView) view).getIconScaleIncreased()
@@ -537,9 +533,10 @@
             IconState iconState = mIconStates.get(mIsolatedIcon);
             if (iconState != null) {
                 // Most of the time the icon isn't yet added when this is called but only happening
-                // later
-                iconState.setXTranslation(mIsolatedIconLocation.left - mAbsolutePosition[0]
-                        - (1 - mIsolatedIcon.getIconScale()) * mIsolatedIcon.getWidth() / 2.0f);
+                // later. The isolated icon position left should equal to the mIsolatedIconLocation
+                // to ensure the icon be put at the center of the HUN icon placeholder,
+                // {@See HeadsUpAppearanceController#updateIsolatedIconLocation}.
+                iconState.setXTranslation(mIsolatedIconLocation.left - mAbsolutePosition[0]);
                 iconState.visibleState = StatusBarIconView.STATE_ICON;
             }
         }
@@ -695,7 +692,6 @@
     }
 
     public class IconState extends ViewState {
-        public static final int NO_VALUE = NotificationIconContainer.NO_VALUE;
         public float iconAppearAmount = 1.0f;
         public float clampedAppearAmount = 1.0f;
         public int visibleState;
@@ -813,8 +809,6 @@
                     super.applyToView(view);
                 }
                 sTempProperties.setAnimationEndAction(null);
-                boolean inShelf = iconAppearAmount == 1.0f;
-                icon.setIsInShelf(inShelf);
             }
             justAdded = false;
             justReplaced = false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index e6b76ad..1d934d6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -48,6 +48,7 @@
 import com.android.systemui.dagger.qualifiers.DisplayId;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dagger.qualifiers.UiBackground;
+import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor;
 import com.android.systemui.privacy.PrivacyItem;
 import com.android.systemui.privacy.PrivacyItemController;
 import com.android.systemui.privacy.PrivacyType;
@@ -74,6 +75,7 @@
 import com.android.systemui.statusbar.policy.UserInfoController;
 import com.android.systemui.statusbar.policy.ZenModeController;
 import com.android.systemui.util.RingerModeTracker;
+import com.android.systemui.util.kotlin.JavaAdapter;
 import com.android.systemui.util.time.DateFormatUtil;
 
 import java.io.PrintWriter;
@@ -121,9 +123,12 @@
     private final String mSlotCamera;
     private final String mSlotSensorsOff;
     private final String mSlotScreenRecord;
+    private final String mSlotConnectedDisplay;
     private final int mDisplayId;
     private final SharedPreferences mSharedPreferences;
     private final DateFormatUtil mDateFormatUtil;
+    private final JavaAdapter mJavaAdapter;
+    private final ConnectedDisplayInteractor mConnectedDisplayInteractor;
     private final TelecomManager mTelecomManager;
 
     private final Handler mHandler;
@@ -158,7 +163,7 @@
     private boolean mMuteVisible;
     private boolean mCurrentUserSetup;
 
-    private boolean mManagedProfileIconVisible = false;
+    private boolean mProfileIconVisible = false;
 
     private BluetoothController mBluetooth;
     private AlarmManager.AlarmClockInfo mNextAlarm;
@@ -182,9 +187,13 @@
             @Main SharedPreferences sharedPreferences, DateFormatUtil dateFormatUtil,
             RingerModeTracker ringerModeTracker,
             PrivacyItemController privacyItemController,
-            PrivacyLogger privacyLogger) {
+            PrivacyLogger privacyLogger,
+            ConnectedDisplayInteractor connectedDisplayInteractor,
+            JavaAdapter javaAdapter
+    ) {
         mIconController = iconController;
         mCommandQueue = commandQueue;
+        mConnectedDisplayInteractor = connectedDisplayInteractor;
         mBroadcastDispatcher = broadcastDispatcher;
         mHandler = new Handler(looper);
         mResources = resources;
@@ -211,8 +220,11 @@
         mTelecomManager = telecomManager;
         mRingerModeTracker = ringerModeTracker;
         mPrivacyLogger = privacyLogger;
+        mJavaAdapter = javaAdapter;
 
         mSlotCast = resources.getString(com.android.internal.R.string.status_bar_cast);
+        mSlotConnectedDisplay = resources.getString(
+                com.android.internal.R.string.status_bar_connected_display);
         mSlotHotspot = resources.getString(com.android.internal.R.string.status_bar_hotspot);
         mSlotBluetooth = resources.getString(com.android.internal.R.string.status_bar_bluetooth);
         mSlotTty = resources.getString(com.android.internal.R.string.status_bar_tty);
@@ -247,7 +259,9 @@
         filter.addAction(TelecomManager.ACTION_CURRENT_TTY_MODE_CHANGED);
         filter.addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE);
         filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE);
-        filter.addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED);
+        filter.addAction(Intent.ACTION_PROFILE_REMOVED);
+        filter.addAction(Intent.ACTION_PROFILE_ACCESSIBLE);
+        filter.addAction(Intent.ACTION_PROFILE_INACCESSIBLE);
         mBroadcastDispatcher.registerReceiverWithHandler(mIntentReceiver, filter, mHandler);
         Observer<Integer> observer = ringer -> mHandler.post(this::updateVolumeZen);
 
@@ -285,13 +299,17 @@
         mIconController.setIcon(mSlotCast, R.drawable.stat_sys_cast, null);
         mIconController.setIconVisibility(mSlotCast, false);
 
+        // connected display
+        mIconController.setIcon(mSlotConnectedDisplay, R.drawable.stat_sys_connected_display, null);
+        mIconController.setIconVisibility(mSlotConnectedDisplay, false);
+
         // hotspot
         mIconController.setIcon(mSlotHotspot, R.drawable.stat_sys_hotspot,
                 mResources.getString(R.string.accessibility_status_bar_hotspot));
         mIconController.setIconVisibility(mSlotHotspot, mHotspot.isHotspotEnabled());
 
-        // managed profile
-        updateManagedProfile();
+        // profile
+        updateProfileIcon();
 
         // data saver
         mIconController.setIcon(mSlotDataSaver, R.drawable.stat_sys_data_saver,
@@ -342,6 +360,8 @@
         mSensorPrivacyController.addCallback(mSensorPrivacyListener);
         mLocationController.addCallback(this);
         mRecordingController.addCallback(this);
+        mJavaAdapter.alwaysCollectFlow(mConnectedDisplayInteractor.getConnectedDisplayState(),
+                this::onConnectedDisplayAvailabilityChanged);
 
         mCommandQueue.addCallback(this);
     }
@@ -518,34 +538,34 @@
         }
     }
 
-    private void updateManagedProfile() {
+    private void updateProfileIcon() {
         // getLastResumedActivityUserId needs to acquire the AM lock, which may be contended in
         // some cases. Since it doesn't really matter here whether it's updated in this frame
         // or in the next one, we call this method from our UI offload thread.
         mUiBgExecutor.execute(() -> {
-            final int userId;
             try {
-                userId = ActivityTaskManager.getService().getLastResumedActivityUserId();
-                boolean isManagedProfile = mUserManager.isManagedProfile(userId);
+                final int userId = ActivityTaskManager.getService().getLastResumedActivityUserId();
+                final int iconResId = mUserManager.getUserStatusBarIconResId(userId);
+                // TODO(b/170249807, b/230779281): Handle non-managed-profile String
                 String accessibilityString = getManagedProfileAccessibilityString();
                 mHandler.post(() -> {
                     final boolean showIcon;
-                    if (isManagedProfile && (!mKeyguardStateController.isShowing()
+                    if (iconResId != Resources.ID_NULL && (!mKeyguardStateController.isShowing()
                             || mKeyguardStateController.isOccluded())) {
                         showIcon = true;
                         mIconController.setIcon(mSlotManagedProfile,
-                                R.drawable.stat_sys_managed_profile_status,
+                                iconResId,
                                 accessibilityString);
                     } else {
                         showIcon = false;
                     }
-                    if (mManagedProfileIconVisible != showIcon) {
+                    if (mProfileIconVisible != showIcon) {
                         mIconController.setIconVisibility(mSlotManagedProfile, showIcon);
-                        mManagedProfileIconVisible = showIcon;
+                        mProfileIconVisible = showIcon;
                     }
                 });
             } catch (RemoteException e) {
-                Log.w(TAG, "updateManagedProfile: ", e);
+                Log.w(TAG, "updateProfileIcon: ", e);
             }
         });
     }
@@ -561,7 +581,7 @@
                 public void onUserChanged(int newUser, Context userContext) {
                     mHandler.post(() -> {
                         updateAlarm();
-                        updateManagedProfile();
+                        updateProfileIcon();
                         onUserSetupChanged();
                     });
                 }
@@ -604,13 +624,13 @@
     public void appTransitionStarting(int displayId, long startTime, long duration,
             boolean forced) {
         if (mDisplayId == displayId) {
-            updateManagedProfile();
+            updateProfileIcon();
         }
     }
 
     @Override
     public void onKeyguardShowingChanged() {
-        updateManagedProfile();
+        updateProfileIcon();
     }
 
     @Override
@@ -733,8 +753,10 @@
                     break;
                 case Intent.ACTION_MANAGED_PROFILE_AVAILABLE:
                 case Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE:
-                case Intent.ACTION_MANAGED_PROFILE_REMOVED:
-                    updateManagedProfile();
+                case Intent.ACTION_PROFILE_REMOVED:
+                case Intent.ACTION_PROFILE_ACCESSIBLE:
+                case Intent.ACTION_PROFILE_INACCESSIBLE:
+                    updateProfileIcon();
                     break;
                 case AudioManager.ACTION_HEADSET_PLUG:
                     updateHeadsetPlug(intent);
@@ -800,4 +822,14 @@
         if (DEBUG) Log.d(TAG, "screenrecord: hiding icon");
         mHandler.post(() -> mIconController.setIconVisibility(mSlotScreenRecord, false));
     }
+
+    private void onConnectedDisplayAvailabilityChanged(ConnectedDisplayInteractor.State state) {
+        boolean visible = state != ConnectedDisplayInteractor.State.DISCONNECTED;
+
+        if (DEBUG) {
+            Log.d(TAG, "connected_display: " + (visible ? "showing" : "hiding") + " icon");
+        }
+
+        mIconController.setIconVisibility(mSlotConnectedDisplay, visible);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
index e6cb68f..862f169 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
@@ -30,6 +30,7 @@
 import com.android.systemui.flags.Flags
 import com.android.systemui.shade.ShadeController
 import com.android.systemui.shade.ShadeLogger
+import com.android.systemui.shade.ShadeViewController
 import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.unfold.SysUIUnfoldComponent
@@ -51,6 +52,7 @@
     @Named(UNFOLD_STATUS_BAR) private val progressProvider: ScopedUnfoldTransitionProgressProvider?,
     private val centralSurfaces: CentralSurfaces,
     private val shadeController: ShadeController,
+    private val shadeViewController: ShadeViewController,
     private val shadeLogger: ShadeLogger,
     private val moveFromCenterAnimationController: StatusBarMoveFromCenterAnimationController?,
     private val userChipViewModel: StatusBarUserChipViewModel,
@@ -165,19 +167,20 @@
             if (event.action == MotionEvent.ACTION_DOWN) {
                 // If the view that would receive the touch is disabled, just have status
                 // bar eat the gesture.
-                if (!centralSurfaces.shadeViewController.isViewEnabled) {
+                if (!shadeViewController.isViewEnabled) {
                     shadeLogger.logMotionEvent(event,
                             "onTouchForwardedFromStatusBar: panel view disabled")
                     return true
                 }
-                if (centralSurfaces.shadeViewController.isFullyCollapsed &&
+                if (shadeViewController.isFullyCollapsed &&
                         event.y < 1f) {
                     // b/235889526 Eat events on the top edge of the phone when collapsed
                     shadeLogger.logMotionEvent(event, "top edge touch ignored")
                     return true
                 }
+                shadeViewController.startTrackingExpansionFromStatusBar()
             }
-            return centralSurfaces.shadeViewController.handleExternalTouch(event)
+            return shadeViewController.handleExternalTouch(event)
         }
     }
 
@@ -221,6 +224,7 @@
         private val userChipViewModel: StatusBarUserChipViewModel,
         private val centralSurfaces: CentralSurfaces,
         private val shadeController: ShadeController,
+        private val shadeViewController: ShadeViewController,
         private val shadeLogger: ShadeLogger,
         private val viewUtil: ViewUtil,
         private val configurationController: ConfigurationController,
@@ -240,6 +244,7 @@
                     progressProvider.getOrNull(),
                     centralSurfaces,
                     shadeController,
+                    shadeViewController,
                     shadeLogger,
                     statusBarMoveFromCenterAnimationController,
                     userChipViewModel,
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 25ecf1a..e82ac59 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -48,18 +48,17 @@
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.settingslib.Utils;
+import com.android.systemui.CoreStartable;
 import com.android.systemui.DejankUtils;
 import com.android.systemui.Dumpable;
 import com.android.systemui.R;
 import com.android.systemui.animation.ShadeInterpolation;
+import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dock.DockManager;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
-import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants;
 import com.android.systemui.keyguard.shared.model.ScrimAlpha;
 import com.android.systemui.keyguard.shared.model.TransitionState;
 import com.android.systemui.keyguard.shared.model.TransitionStep;
@@ -71,8 +70,10 @@
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.AlarmTimeout;
+import com.android.systemui.util.kotlin.JavaAdapter;
 import com.android.systemui.util.wakelock.DelayedWakeLock;
 import com.android.systemui.util.wakelock.WakeLock;
+import com.android.systemui.wallpapers.data.repository.WallpaperRepository;
 
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
@@ -89,7 +90,8 @@
  * security method gets shown).
  */
 @SysUISingleton
-public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dumpable {
+public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dumpable,
+        CoreStartable {
 
     static final String TAG = "ScrimController";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
@@ -207,6 +209,7 @@
     private final KeyguardVisibilityCallback mKeyguardVisibilityCallback;
     private final Handler mHandler;
     private final Executor mMainExecutor;
+    private final JavaAdapter mJavaAdapter;
     private final ScreenOffAnimationController mScreenOffAnimationController;
     private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
     private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
@@ -249,8 +252,6 @@
     private int mScrimsVisibility;
     private final TriConsumer<ScrimState, Float, GradientColors> mScrimStateListener;
     private final LargeScreenShadeInterpolator mLargeScreenShadeInterpolator;
-    private final FeatureFlags mFeatureFlags;
-    private final boolean mUseNewLightBarLogic;
     private Consumer<Integer> mScrimVisibleListener;
     private boolean mBlankScreen;
     private boolean mScreenBlankingCallbackCalled;
@@ -268,6 +269,7 @@
     private boolean mKeyguardOccluded;
 
     private KeyguardTransitionInteractor mKeyguardTransitionInteractor;
+    private final WallpaperRepository mWallpaperRepository;
     private CoroutineDispatcher mMainDispatcher;
     private boolean mIsBouncerToGoneTransitionRunning = false;
     private PrimaryBouncerToGoneTransitionViewModel mPrimaryBouncerToGoneTransitionViewModel;
@@ -297,18 +299,17 @@
             DockManager dockManager,
             ConfigurationController configurationController,
             @Main Executor mainExecutor,
+            JavaAdapter javaAdapter,
             ScreenOffAnimationController screenOffAnimationController,
             KeyguardUnlockAnimationController keyguardUnlockAnimationController,
             StatusBarKeyguardViewManager statusBarKeyguardViewManager,
             PrimaryBouncerToGoneTransitionViewModel primaryBouncerToGoneTransitionViewModel,
             KeyguardTransitionInteractor keyguardTransitionInteractor,
+            WallpaperRepository wallpaperRepository,
             @Main CoroutineDispatcher mainDispatcher,
-            LargeScreenShadeInterpolator largeScreenShadeInterpolator,
-            FeatureFlags featureFlags) {
+            LargeScreenShadeInterpolator largeScreenShadeInterpolator) {
         mScrimStateListener = lightBarController::setScrimState;
         mLargeScreenShadeInterpolator = largeScreenShadeInterpolator;
-        mFeatureFlags = featureFlags;
-        mUseNewLightBarLogic = featureFlags.isEnabled(Flags.NEW_LIGHT_BAR_LOGIC);
         mDefaultScrimAlpha = BUSY_SCRIM_ALPHA;
 
         mKeyguardStateController = keyguardStateController;
@@ -317,6 +318,7 @@
         mKeyguardVisibilityCallback = new KeyguardVisibilityCallback();
         mHandler = handler;
         mMainExecutor = mainExecutor;
+        mJavaAdapter = javaAdapter;
         mScreenOffAnimationController = screenOffAnimationController;
         mTimeTicker = new AlarmTimeout(alarmManager, this::onHideWallpaperTimeout,
                 "hide_aod_wallpaper", mHandler);
@@ -348,9 +350,17 @@
         mColors = new GradientColors();
         mPrimaryBouncerToGoneTransitionViewModel = primaryBouncerToGoneTransitionViewModel;
         mKeyguardTransitionInteractor = keyguardTransitionInteractor;
+        mWallpaperRepository = wallpaperRepository;
         mMainDispatcher = mainDispatcher;
     }
 
+    @Override
+    public void start() {
+        mJavaAdapter.alwaysCollectFlow(
+                mWallpaperRepository.getWallpaperSupportsAmbientMode(),
+                this::setWallpaperSupportsAmbientMode);
+    }
+
     /**
      * Attach the controller to the supplied views.
      */
@@ -881,24 +891,11 @@
                     mBehindAlpha = 1;
                     mNotificationsAlpha = behindFraction * mDefaultScrimAlpha;
                 } else {
-                    if (mFeatureFlags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION)) {
-                        mBehindAlpha = mLargeScreenShadeInterpolator.getBehindScrimAlpha(
-                                mPanelExpansionFraction * mDefaultScrimAlpha);
-                        mNotificationsAlpha =
-                                mLargeScreenShadeInterpolator.getNotificationScrimAlpha(
-                                        mPanelExpansionFraction);
-                    } else {
-                        // Behind scrim will finish fading in at 30% expansion.
-                        float behindFraction = MathUtils
-                                .constrainedMap(0f, 1f, 0f, 0.3f, mPanelExpansionFraction);
-                        mBehindAlpha = behindFraction * mDefaultScrimAlpha;
-                        // Delay fade-in of notification scrim a bit further, to coincide with the
-                        // behind scrim finishing fading in.
-                        // Also to coincide with the view starting to fade in, otherwise the empty
-                        // panel can be quite jarring.
-                        mNotificationsAlpha = MathUtils
-                                .constrainedMap(0f, 1f, 0.3f, 0.75f, mPanelExpansionFraction);
-                    }
+                    mBehindAlpha = mLargeScreenShadeInterpolator.getBehindScrimAlpha(
+                            mPanelExpansionFraction * mDefaultScrimAlpha);
+                    mNotificationsAlpha =
+                            mLargeScreenShadeInterpolator.getNotificationScrimAlpha(
+                                    mPanelExpansionFraction);
                 }
                 mBehindTint = mState.getBehindTint();
                 mInFrontAlpha = 0;
@@ -1166,13 +1163,7 @@
         if (mClipsQsScrim && mQsBottomVisible) {
             alpha = mNotificationsAlpha;
         }
-        if (mUseNewLightBarLogic) {
-            mScrimStateListener.accept(mState, alpha, mColors);
-        } else {
-            // NOTE: This wasn't wrong, but it implied that each scrim might have different colors,
-            //  when in fact they all share the same GradientColors instance, which we own.
-            mScrimStateListener.accept(mState, alpha, mScrimInFront.getColors());
-        }
+        mScrimStateListener.accept(mState, alpha, mColors);
     }
 
     private void dispatchScrimsVisible() {
@@ -1489,10 +1480,6 @@
         }
     }
 
-    public void setCurrentUser(int currentUser) {
-        // Don't care in the base class.
-    }
-
     private void updateThemeColors() {
         if (mScrimBehind == null) return;
         int background = Utils.getColorAttr(mScrimBehind.getContext(),
@@ -1500,15 +1487,15 @@
         int accent = Utils.getColorAccent(mScrimBehind.getContext()).getDefaultColor();
         mColors.setMainColor(background);
         mColors.setSecondaryColor(accent);
-        if (mUseNewLightBarLogic) {
-            final boolean isBackgroundLight = !ContrastColorUtil.isColorDark(background);
-            mColors.setSupportsDarkText(isBackgroundLight);
-        } else {
-            // NOTE: This was totally backward, but LightBarController was flipping it back.
-            // There may be other consumers of this which would struggle though
-            mColors.setSupportsDarkText(
-                    ColorUtils.calculateContrast(mColors.getMainColor(), Color.WHITE) > 4.5);
+        final boolean isBackgroundLight = !ContrastColorUtil.isColorDark(background);
+        mColors.setSupportsDarkText(isBackgroundLight);
+
+        int surface = Utils.getColorAttr(mScrimBehind.getContext(),
+                com.android.internal.R.attr.materialColorSurface).getDefaultColor();
+        for (ScrimState state : ScrimState.values()) {
+            state.setSurfaceColor(surface);
         }
+
         mNeedsDrawableColorUpdate = true;
     }
 
@@ -1561,7 +1548,7 @@
         pw.println(mState.getMaxLightRevealScrimAlpha());
     }
 
-    public void setWallpaperSupportsAmbientMode(boolean wallpaperSupportsAmbientMode) {
+    private void setWallpaperSupportsAmbientMode(boolean wallpaperSupportsAmbientMode) {
         mWallpaperSupportsAmbientMode = wallpaperSupportsAmbientMode;
         ScrimState[] states = ScrimState.values();
         for (int i = 0; i < states.length; i++) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
index 7b20283..e3b65ab 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
@@ -122,11 +122,19 @@
         @Override
         public void prepare(ScrimState previousState) {
             mBehindAlpha = mClipQsScrim ? 1 : mDefaultScrimAlpha;
-            mBehindTint = mClipQsScrim ? Color.BLACK : Color.TRANSPARENT;
+            mBehindTint = mClipQsScrim ? Color.BLACK : mSurfaceColor;
             mNotifAlpha = mClipQsScrim ? mDefaultScrimAlpha : 0;
             mNotifTint = Color.TRANSPARENT;
             mFrontAlpha = 0f;
         }
+
+        @Override
+        public void setSurfaceColor(int surfaceColor) {
+            super.setSurfaceColor(surfaceColor);
+            if (!mClipQsScrim) {
+                mBehindTint = mSurfaceColor;
+            }
+        }
     },
 
     /**
@@ -295,6 +303,7 @@
     int mFrontTint = Color.TRANSPARENT;
     int mBehindTint = Color.TRANSPARENT;
     int mNotifTint = Color.TRANSPARENT;
+    int mSurfaceColor = Color.TRANSPARENT;
 
     boolean mAnimateChange = true;
     float mAodFrontScrimAlpha;
@@ -409,6 +418,10 @@
         mDefaultScrimAlpha = defaultScrimAlpha;
     }
 
+    public void setSurfaceColor(int surfaceColor) {
+        mSurfaceColor = surfaceColor;
+    }
+
     public void setWallpaperSupportsAmbientMode(boolean wallpaperSupportsAmbientMode) {
         mWallpaperSupportsAmbientMode = wallpaperSupportsAmbientMode;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java
index 481cf3c..9a295e6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java
@@ -21,6 +21,7 @@
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
 import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent;
 import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
 import com.android.systemui.statusbar.window.StatusBarWindowController;
@@ -35,6 +36,7 @@
     private final NotificationShadeWindowController mNotificationShadeWindowController;
     private final StatusBarWindowController mStatusBarWindowController;
     private final ShadeViewController mShadeViewController;
+    private final NotificationStackScrollLayoutController mNsslController;
     private final KeyguardBypassController mKeyguardBypassController;
     private final HeadsUpManagerPhone mHeadsUpManager;
     private final StatusBarStateController mStatusBarStateController;
@@ -45,14 +47,15 @@
             NotificationShadeWindowController notificationShadeWindowController,
             StatusBarWindowController statusBarWindowController,
             ShadeViewController shadeViewController,
+            NotificationStackScrollLayoutController nsslController,
             KeyguardBypassController keyguardBypassController,
             HeadsUpManagerPhone headsUpManager,
             StatusBarStateController statusBarStateController,
             NotificationRemoteInputManager notificationRemoteInputManager) {
-
         mNotificationShadeWindowController = notificationShadeWindowController;
         mStatusBarWindowController = statusBarWindowController;
         mShadeViewController = shadeViewController;
+        mNsslController = nsslController;
         mKeyguardBypassController = keyguardBypassController;
         mHeadsUpManager = headsUpManager;
         mStatusBarStateController = statusBarStateController;
@@ -85,8 +88,7 @@
                 //animation
                 // is finished.
                 mHeadsUpManager.setHeadsUpGoingAway(true);
-                mShadeViewController.getNotificationStackScrollLayoutController()
-                        .runAfterAnimationFinished(() -> {
+                mNsslController.runAfterAnimationFinished(() -> {
                     if (!mHeadsUpManager.hasPinnedHeadsUp()) {
                         mNotificationShadeWindowController.setHeadsUpShowing(false);
                         mHeadsUpManager.setHeadsUpGoingAway(false);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
index a8a834f..42b0a4f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
@@ -17,7 +17,6 @@
 import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_ICON;
 import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_MOBILE;
 import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_MOBILE_NEW;
-import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_WIFI;
 import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_WIFI_NEW;
 
 import android.annotation.Nullable;
@@ -43,12 +42,10 @@
 import com.android.systemui.statusbar.BaseStatusBarFrameLayout;
 import com.android.systemui.statusbar.StatusBarIconView;
 import com.android.systemui.statusbar.StatusBarMobileView;
-import com.android.systemui.statusbar.StatusBarWifiView;
 import com.android.systemui.statusbar.StatusIconDisplayable;
 import com.android.systemui.statusbar.connectivity.ui.MobileContextProvider;
 import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.CallIndicatorIconState;
 import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
-import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
 import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags;
 import com.android.systemui.statusbar.pipeline.mobile.ui.MobileUiAdapter;
 import com.android.systemui.statusbar.pipeline.mobile.ui.binder.MobileIconsBinder;
@@ -97,16 +94,9 @@
      */
     void setIcon(String slot, int resourceId, CharSequence contentDescription);
 
-    /** */
-    void setWifiIcon(String slot, WifiIconState state);
-
     /**
      * Sets up a wifi icon using the new data pipeline. No effect if the wifi icon has already been
      * set up (inflated and added to the view hierarchy).
-     *
-     * This method completely replaces {@link #setWifiIcon} with the information from the new wifi
-     * data pipeline. Icons will automatically keep their state up to date, so we don't have to
-     * worry about funneling state objects through anymore.
      */
     void setNewWifiIcon();
 
@@ -173,7 +163,7 @@
      */
     class DarkIconManager extends IconManager {
         private final DarkIconDispatcher mDarkIconDispatcher;
-        private int mIconHPadding;
+        private final int mIconHorizontalMargin;
 
         public DarkIconManager(
                 LinearLayout linearLayout,
@@ -189,8 +179,8 @@
                     wifiUiAdapter,
                     mobileUiAdapter,
                     mobileContextProvider);
-            mIconHPadding = mContext.getResources().getDimensionPixelSize(
-                    R.dimen.status_bar_icon_padding);
+            mIconHorizontalMargin = mContext.getResources().getDimensionPixelSize(
+                    R.dimen.status_bar_icon_horizontal_margin);
             mDarkIconDispatcher = darkIconDispatcher;
         }
 
@@ -205,7 +195,7 @@
         protected LayoutParams onCreateLayoutParams() {
             LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
                     ViewGroup.LayoutParams.WRAP_CONTENT, mIconSize);
-            lp.setMargins(mIconHPadding, 0, mIconHPadding, 0);
+            lp.setMargins(mIconHorizontalMargin, 0, mIconHorizontalMargin, 0);
             return lp;
         }
 
@@ -370,7 +360,7 @@
         private final MobileIconsViewModel mMobileIconsViewModel;
 
         protected final Context mContext;
-        protected final int mIconSize;
+        protected int mIconSize;
         // Whether or not these icons show up in dumpsys
         protected boolean mShouldLog = false;
         private StatusBarIconController mController;
@@ -395,10 +385,10 @@
             mStatusBarPipelineFlags = statusBarPipelineFlags;
             mMobileContextProvider = mobileContextProvider;
             mContext = group.getContext();
-            mIconSize = mContext.getResources().getDimensionPixelSize(
-                    com.android.internal.R.dimen.status_bar_icon_size);
             mLocation = location;
 
+            reloadDimens();
+
             if (statusBarPipelineFlags.runNewMobileIconsBackend()) {
                 // This starts the flow for the new pipeline, and will notify us of changes if
                 // {@link StatusBarPipelineFlags#useNewMobileIcons} is also true.
@@ -408,13 +398,7 @@
                 mMobileIconsViewModel = null;
             }
 
-            if (statusBarPipelineFlags.runNewWifiIconBackend()) {
-                // This starts the flow for the new pipeline, and will notify us of changes if
-                // {@link StatusBarPipelineFlags#useNewWifiIcon} is also true.
-                mWifiViewModel = wifiUiAdapter.bindGroup(mGroup, mLocation);
-            } else {
-                mWifiViewModel = null;
-            }
+            mWifiViewModel = wifiUiAdapter.bindGroup(mGroup, mLocation);
         }
 
         public boolean isDemoable() {
@@ -462,9 +446,6 @@
                 case TYPE_ICON:
                     return addIcon(index, slot, blocked, holder.getIcon());
 
-                case TYPE_WIFI:
-                    return addWifiIcon(index, slot, holder.getWifiState());
-
                 case TYPE_WIFI_NEW:
                     return addNewWifiIcon(index, slot);
 
@@ -487,29 +468,7 @@
             return view;
         }
 
-        @VisibleForTesting
-        protected StatusIconDisplayable addWifiIcon(int index, String slot, WifiIconState state) {
-            if (mStatusBarPipelineFlags.useNewWifiIcon()) {
-                throw new IllegalStateException("Attempting to add a wifi icon while the new "
-                        + "icons are enabled is not supported");
-            }
-
-            final StatusBarWifiView view = onCreateStatusBarWifiView(slot);
-            view.applyWifiState(state);
-            mGroup.addView(view, index, onCreateLayoutParams());
-
-            if (mIsInDemoMode) {
-                mDemoStatusIcons.addDemoWifiView(state);
-            }
-            return view;
-        }
-
         protected StatusIconDisplayable addNewWifiIcon(int index, String slot) {
-            if (!mStatusBarPipelineFlags.useNewWifiIcon()) {
-                throw new IllegalStateException("Attempting to add a wifi icon using the new"
-                        + "pipeline, but the enabled flag is false.");
-            }
-
             ModernStatusBarWifiView view = onCreateModernStatusBarWifiView(slot);
             mGroup.addView(view, index, onCreateLayoutParams());
 
@@ -573,11 +532,6 @@
             return new StatusBarIconView(mContext, slot, null, blocked);
         }
 
-        private StatusBarWifiView onCreateStatusBarWifiView(String slot) {
-            StatusBarWifiView view = StatusBarWifiView.fromContext(mContext, slot);
-            return view;
-        }
-
         private ModernStatusBarWifiView onCreateModernStatusBarWifiView(String slot) {
             return ModernStatusBarWifiView.constructAndBind(mContext, slot, mWifiViewModel);
         }
@@ -609,13 +563,9 @@
             mGroup.removeAllViews();
         }
 
-        protected void onDensityOrFontScaleChanged() {
-            for (int i = 0; i < mGroup.getChildCount(); i++) {
-                View child = mGroup.getChildAt(i);
-                LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
-                        ViewGroup.LayoutParams.WRAP_CONTENT, mIconSize);
-                child.setLayoutParams(lp);
-            }
+        protected void reloadDimens() {
+            mIconSize = mContext.getResources().getDimensionPixelSize(
+                    com.android.internal.R.dimen.status_bar_icon_size_sp);
         }
 
         private void setHeightAndCenter(ImageView imageView, int height) {
@@ -644,9 +594,6 @@
                 case TYPE_ICON:
                     onSetIcon(viewIndex, holder.getIcon());
                     return;
-                case TYPE_WIFI:
-                    onSetWifiIcon(viewIndex, holder.getWifiState());
-                    return;
                 case TYPE_MOBILE:
                     onSetMobileIcon(viewIndex, holder.getMobileState());
                     return;
@@ -659,23 +606,6 @@
             }
         }
 
-        public void onSetWifiIcon(int viewIndex, WifiIconState state) {
-            View view = mGroup.getChildAt(viewIndex);
-            if (view instanceof StatusBarWifiView) {
-                ((StatusBarWifiView) view).applyWifiState(state);
-            } else if (view instanceof ModernStatusBarWifiView) {
-                // ModernStatusBarWifiView will automatically apply state based on its callbacks, so
-                // we don't need to call applyWifiState.
-            } else {
-                throw new IllegalStateException("View at " + viewIndex + " must be of type "
-                        + "StatusBarWifiView or ModernStatusBarWifiView");
-            }
-
-            if (mIsInDemoMode) {
-                mDemoStatusIcons.updateWifiState(state);
-            }
-        }
-
         public void onSetMobileIcon(int viewIndex, MobileIconState state) {
             View view = mGroup.getChildAt(viewIndex);
             if (view instanceof StatusBarMobileView) {
@@ -707,9 +637,7 @@
             mIsInDemoMode = true;
             if (mDemoStatusIcons == null) {
                 mDemoStatusIcons = createDemoStatusIcons();
-                if (mStatusBarPipelineFlags.useNewWifiIcon()) {
-                    mDemoStatusIcons.addModernWifiView(mWifiViewModel);
-                }
+                mDemoStatusIcons.addModernWifiView(mWifiViewModel);
             }
             mDemoStatusIcons.onDemoModeStarted();
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
index 3a18423..d1a02d6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
@@ -40,7 +40,6 @@
 import com.android.systemui.statusbar.StatusIconDisplayable;
 import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.CallIndicatorIconState;
 import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
-import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
 import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
@@ -109,6 +108,7 @@
         }
 
         group.setController(this);
+        group.reloadDimens();
         mIconGroups.add(group);
         List<Slot> allSlots = mStatusBarIconList.getSlots();
         for (int i = 0; i < allSlots.size(); i++) {
@@ -201,35 +201,7 @@
     }
 
     @Override
-    public void setWifiIcon(String slot, WifiIconState state) {
-        if (mStatusBarPipelineFlags.useNewWifiIcon()) {
-            Log.d(TAG, "ignoring old pipeline callback because the new wifi icon is enabled");
-            return;
-        }
-
-        if (state == null) {
-            removeIcon(slot, 0);
-            return;
-        }
-
-        StatusBarIconHolder holder = mStatusBarIconList.getIconHolder(slot, 0);
-        if (holder == null) {
-            holder = StatusBarIconHolder.fromWifiIconState(state);
-            setIcon(slot, holder);
-        } else {
-            holder.setWifiState(state);
-            handleSet(slot, holder);
-        }
-    }
-
-
-    @Override
     public void setNewWifiIcon() {
-        if (!mStatusBarPipelineFlags.useNewWifiIcon()) {
-            Log.d(TAG, "ignoring new pipeline callback because the new wifi icon is disabled");
-            return;
-        }
-
         String slot = mContext.getString(com.android.internal.R.string.status_bar_wifi);
         StatusBarIconHolder holder = mStatusBarIconList.getIconHolder(slot, /* tag= */ 0);
         if (holder == null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
index 833cb93..01fd247 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
@@ -25,7 +25,6 @@
 import com.android.internal.statusbar.StatusBarIcon;
 import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.CallIndicatorIconState;
 import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
-import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
 import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconViewModel;
 
 import java.lang.annotation.Retention;
@@ -36,7 +35,6 @@
  */
 public class StatusBarIconHolder {
     public static final int TYPE_ICON = 0;
-    public static final int TYPE_WIFI = 1;
     public static final int TYPE_MOBILE = 2;
     /**
      * TODO (b/249790733): address this once the new pipeline is in place
@@ -65,7 +63,6 @@
 
     @IntDef({
             TYPE_ICON,
-            TYPE_WIFI,
             TYPE_MOBILE,
             TYPE_MOBILE_NEW,
             TYPE_WIFI_NEW
@@ -74,7 +71,6 @@
     @interface IconType {}
 
     private StatusBarIcon mIcon;
-    private WifiIconState mWifiState;
     private MobileIconState mMobileState;
     private @IconType int mType = TYPE_ICON;
     private int mTag = 0;
@@ -83,7 +79,6 @@
     public static String getTypeString(@IconType int type) {
         switch(type) {
             case TYPE_ICON: return "ICON";
-            case TYPE_WIFI: return "WIFI_OLD";
             case TYPE_MOBILE: return "MOBILE_OLD";
             case TYPE_MOBILE_NEW: return "MOBILE_NEW";
             case TYPE_WIFI_NEW: return "WIFI_NEW";
@@ -101,25 +96,6 @@
         return wrapper;
     }
 
-    /** */
-    public static StatusBarIconHolder fromResId(
-            Context context,
-            int resId,
-            CharSequence contentDescription) {
-        StatusBarIconHolder holder = new StatusBarIconHolder();
-        holder.mIcon = new StatusBarIcon(UserHandle.SYSTEM, context.getPackageName(),
-                Icon.createWithResource( context, resId), 0, 0, contentDescription);
-        return holder;
-    }
-
-    /** */
-    public static StatusBarIconHolder fromWifiIconState(WifiIconState state) {
-        StatusBarIconHolder holder = new StatusBarIconHolder();
-        holder.mWifiState = state;
-        holder.mType = TYPE_WIFI;
-        return holder;
-    }
-
     /** Creates a new holder with for the new wifi icon. */
     public static StatusBarIconHolder forNewWifiIcon() {
         StatusBarIconHolder holder = new StatusBarIconHolder();
@@ -178,15 +154,6 @@
     }
 
     @Nullable
-    public WifiIconState getWifiState() {
-        return mWifiState;
-    }
-
-    public void setWifiState(WifiIconState state) {
-        mWifiState = state;
-    }
-
-    @Nullable
     public MobileIconState getMobileState() {
         return mMobileState;
     }
@@ -199,8 +166,6 @@
         switch (mType) {
             case TYPE_ICON:
                 return mIcon.visible;
-            case TYPE_WIFI:
-                return mWifiState.visible;
             case TYPE_MOBILE:
                 return mMobileState.visible;
             case TYPE_MOBILE_NEW:
@@ -223,10 +188,6 @@
                 mIcon.visible = visible;
                 break;
 
-            case TYPE_WIFI:
-                mWifiState.visible = visible;
-                break;
-
             case TYPE_MOBILE:
                 mMobileState.visible = visible;
                 break;
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 d97e64e..5c1dfbe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -18,7 +18,7 @@
 
 import static android.view.WindowInsets.Type.navigationBars;
 
-import static com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN;
+import static com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN;
 import static com.android.systemui.plugins.ActivityStarter.OnDismissAction;
 import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK;
 import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING;
@@ -54,16 +54,16 @@
 import com.android.keyguard.TrustGrantFlags;
 import com.android.keyguard.ViewMediatorCallback;
 import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor;
+import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor;
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor.PrimaryBouncerExpansionCallback;
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
+import com.android.systemui.bouncer.ui.BouncerView;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.dreams.DreamOverlayStateController;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
-import com.android.systemui.keyguard.data.BouncerView;
-import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor;
-import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor;
-import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor.PrimaryBouncerExpansionCallback;
-import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor;
 import com.android.systemui.navigationbar.NavigationBarView;
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.navigationbar.TaskbarDelegate;
@@ -145,6 +145,7 @@
     // Local cache of expansion events, to avoid duplicates
     private float mFraction = -1f;
     private boolean mTracking = false;
+    private boolean mBouncerShowingOverDream;
 
     private final PrimaryBouncerExpansionCallback mExpansionCallback =
             new PrimaryBouncerExpansionCallback() {
@@ -183,8 +184,8 @@
 
             @Override
             public void onVisibilityChanged(boolean isVisible) {
-                mCentralSurfaces.setBouncerShowingOverDream(
-                        isVisible && mDreamOverlayStateController.isOverlayActive());
+                mBouncerShowingOverDream =
+                        isVisible && mDreamOverlayStateController.isOverlayActive();
 
                 if (!isVisible) {
                     mCentralSurfaces.setPrimaryBouncerHiddenFraction(EXPANSION_HIDDEN);
@@ -712,7 +713,7 @@
 
     @Override
     public void reset(boolean hideBouncerWhenShowing) {
-        if (mKeyguardStateController.isShowing()) {
+        if (mKeyguardStateController.isShowing() && !bouncerIsAnimatingAway()) {
             final boolean isOccluded = mKeyguardStateController.isOccluded();
             // Hide quick settings.
             mShadeViewController.resetViews(/* animate= */ !isOccluded);
@@ -760,7 +761,7 @@
 
     @Override
     public void onStartedWakingUp() {
-        mCentralSurfaces.getNotificationShadeWindowView().getWindowInsetsController()
+        mNotificationShadeWindowController.getWindowRootView().getWindowInsetsController()
                 .setAnimationsDisabled(false);
         NavigationBarView navBarView = mCentralSurfaces.getNavigationBarView();
         if (navBarView != null) {
@@ -774,7 +775,7 @@
 
     @Override
     public void onStartedGoingToSleep() {
-        mCentralSurfaces.getNotificationShadeWindowView().getWindowInsetsController()
+        mNotificationShadeWindowController.getWindowRootView().getWindowInsetsController()
                 .setAnimationsDisabled(true);
         NavigationBarView navBarView = mCentralSurfaces.getNavigationBarView();
         if (navBarView != null) {
@@ -833,6 +834,11 @@
     }
 
     @Override
+    public boolean isBouncerShowingOverDream() {
+        return mBouncerShowingOverDream;
+    }
+
+    @Override
     public void setOccluded(boolean occluded, boolean animate) {
         final boolean wasOccluded = mKeyguardStateController.isOccluded();
         final boolean isOccluding = !wasOccluded && occluded;
@@ -1124,7 +1130,7 @@
             if (view != null) {
                 view.setVisibility(View.VISIBLE);
             }
-            mCentralSurfaces.getNotificationShadeWindowView().getWindowInsetsController()
+            mNotificationShadeWindowController.getWindowRootView().getWindowInsetsController()
                     .show(navigationBars());
         }
     };
@@ -1202,7 +1208,7 @@
                 }
             } else {
                 mNotificationContainer.removeCallbacks(mMakeNavigationBarVisibleRunnable);
-                mCentralSurfaces.getNotificationShadeWindowView().getWindowInsetsController()
+                mNotificationShadeWindowController.getWindowRootView().getWindowInsetsController()
                         .hide(navigationBars());
             }
         }
@@ -1322,7 +1328,7 @@
 
     @Override
     public ViewRootImpl getViewRootImpl() {
-        ViewGroup viewGroup = mNotificationShadeWindowController.getNotificationShadeView();
+        ViewGroup viewGroup = mNotificationShadeWindowController.getWindowRootView();
         if (viewGroup != null) {
             return viewGroup.getViewRootImpl();
         } else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt
index 9f69db9..b67ec58 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt
@@ -3,6 +3,9 @@
 import android.view.View
 import com.android.systemui.animation.ActivityLaunchAnimator
 import com.android.systemui.animation.LaunchAnimator
+import com.android.systemui.shade.ShadeController
+import com.android.systemui.shade.ShadeViewController
+import com.android.systemui.statusbar.NotificationShadeWindowController
 
 /**
  * A [ActivityLaunchAnimator.Controller] that takes care of collapsing the status bar at the right
@@ -10,36 +13,38 @@
  */
 class StatusBarLaunchAnimatorController(
     private val delegate: ActivityLaunchAnimator.Controller,
-    private val centralSurfaces: CentralSurfaces,
+    private val shadeViewController: ShadeViewController,
+    private val shadeController: ShadeController,
+    private val notificationShadeWindowController: NotificationShadeWindowController,
     private val isLaunchForActivity: Boolean = true
 ) : ActivityLaunchAnimator.Controller by delegate {
     // Always sync the opening window with the shade, given that we draw a hole punch in the shade
     // of the same size and position as the opening app to make it visible.
     override val openingWindowSyncView: View?
-        get() = centralSurfaces.notificationShadeWindowView
+        get() = notificationShadeWindowController.windowRootView
 
     override fun onIntentStarted(willAnimate: Boolean) {
         delegate.onIntentStarted(willAnimate)
         if (willAnimate) {
-            centralSurfaces.shadeViewController.setIsLaunchAnimationRunning(true)
+            shadeViewController.setIsLaunchAnimationRunning(true)
         } else {
-            centralSurfaces.collapsePanelOnMainThread()
+            shadeController.collapseOnMainThread()
         }
     }
 
     override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
         delegate.onLaunchAnimationStart(isExpandingFullyAbove)
-        centralSurfaces.shadeViewController.setIsLaunchAnimationRunning(true)
+        shadeViewController.setIsLaunchAnimationRunning(true)
         if (!isExpandingFullyAbove) {
-            centralSurfaces.shadeViewController.collapseWithDuration(
+            shadeViewController.collapseWithDuration(
                 ActivityLaunchAnimator.TIMINGS.totalDuration.toInt())
         }
     }
 
     override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
         delegate.onLaunchAnimationEnd(isExpandingFullyAbove)
-        centralSurfaces.shadeViewController.setIsLaunchAnimationRunning(false)
-        centralSurfaces.onLaunchAnimationEnd(isExpandingFullyAbove)
+        shadeViewController.setIsLaunchAnimationRunning(false)
+        shadeController.onLaunchAnimationEnd(isExpandingFullyAbove)
     }
 
     override fun onLaunchAnimationProgress(
@@ -48,12 +53,12 @@
         linearProgress: Float
     ) {
         delegate.onLaunchAnimationProgress(state, progress, linearProgress)
-        centralSurfaces.shadeViewController.applyLaunchAnimationProgress(linearProgress)
+        shadeViewController.applyLaunchAnimationProgress(linearProgress)
     }
 
     override fun onLaunchAnimationCancelled(newKeyguardOccludedState: Boolean?) {
         delegate.onLaunchAnimationCancelled()
-        centralSurfaces.shadeViewController.setIsLaunchAnimationRunning(false)
-        centralSurfaces.onLaunchAnimationCancelled(isLaunchForActivity)
+        shadeViewController.setIsLaunchAnimationRunning(false)
+        shadeController.onLaunchAnimationCancelled(isLaunchForActivity)
     }
 }
\ No newline at end of file
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 7bbb03b..ec0c00e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -54,8 +54,10 @@
 import com.android.systemui.EventLogTags;
 import com.android.systemui.animation.ActivityLaunchAnimator;
 import com.android.systemui.assist.AssistManager;
+import com.android.systemui.dagger.qualifiers.DisplayId;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.power.domain.interactor.PowerInteractor;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shade.ShadeController;
 import com.android.systemui.shade.ShadeViewController;
@@ -63,6 +65,7 @@
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationPresenter;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.notification.NotificationActivityStarter;
 import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorControllerProvider;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -93,6 +96,7 @@
 class StatusBarNotificationActivityStarter implements NotificationActivityStarter {
 
     private final Context mContext;
+    private final int mDisplayId;
 
     private final Handler mMainThreadHandler;
     private final Executor mUiBgExecutor;
@@ -119,11 +123,12 @@
     private final MetricsLogger mMetricsLogger;
     private final StatusBarNotificationActivityStarterLogger mLogger;
 
-    private final CentralSurfaces mCentralSurfaces;
     private final NotificationPresenter mPresenter;
-    private final ShadeViewController mNotificationPanel;
+    private final ShadeViewController mShadeViewController;
+    private final NotificationShadeWindowController mNotificationShadeWindowController;
     private final ActivityLaunchAnimator mActivityLaunchAnimator;
     private final NotificationLaunchAnimatorControllerProvider mNotificationAnimationProvider;
+    private final PowerInteractor mPowerInteractor;
     private final UserTracker mUserTracker;
     private final OnUserInteractionCallback mOnUserInteractionCallback;
 
@@ -132,6 +137,7 @@
     @Inject
     StatusBarNotificationActivityStarter(
             Context context,
+            @DisplayId int displayId,
             Handler mainThreadHandler,
             Executor uiBgExecutor,
             NotificationVisibilityProvider visibilityProvider,
@@ -154,15 +160,17 @@
             MetricsLogger metricsLogger,
             StatusBarNotificationActivityStarterLogger logger,
             OnUserInteractionCallback onUserInteractionCallback,
-            CentralSurfaces centralSurfaces,
             NotificationPresenter presenter,
-            ShadeViewController panel,
+            ShadeViewController shadeViewController,
+            NotificationShadeWindowController notificationShadeWindowController,
             ActivityLaunchAnimator activityLaunchAnimator,
             NotificationLaunchAnimatorControllerProvider notificationAnimationProvider,
             LaunchFullScreenIntentProvider launchFullScreenIntentProvider,
+            PowerInteractor powerInteractor,
             FeatureFlags featureFlags,
             UserTracker userTracker) {
         mContext = context;
+        mDisplayId = displayId;
         mMainThreadHandler = mainThreadHandler;
         mUiBgExecutor = uiBgExecutor;
         mVisibilityProvider = visibilityProvider;
@@ -182,16 +190,16 @@
         mLockPatternUtils = lockPatternUtils;
         mStatusBarRemoteInputCallback = remoteInputCallback;
         mActivityIntentHelper = activityIntentHelper;
+        mNotificationShadeWindowController = notificationShadeWindowController;
         mFeatureFlags = featureFlags;
         mMetricsLogger = metricsLogger;
         mLogger = logger;
         mOnUserInteractionCallback = onUserInteractionCallback;
-        // TODO: use KeyguardStateController#isOccluded to remove this dependency
-        mCentralSurfaces = centralSurfaces;
         mPresenter = presenter;
-        mNotificationPanel = panel;
+        mShadeViewController = shadeViewController;
         mActivityLaunchAnimator = activityLaunchAnimator;
         mNotificationAnimationProvider = notificationAnimationProvider;
+        mPowerInteractor = powerInteractor;
         mUserTracker = userTracker;
 
         launchFullScreenIntentProvider.registerListener(entry -> launchFullScreenIntent(entry));
@@ -233,7 +241,7 @@
                 && mActivityIntentHelper.wouldPendingLaunchResolverActivity(intent,
                 mLockscreenUserManager.getCurrentUserId());
         final boolean animate = !willLaunchResolverActivity
-                && mCentralSurfaces.shouldAnimateLaunch(isActivityIntent);
+                && mActivityStarter.shouldAnimateLaunch(isActivityIntent);
         boolean showOverLockscreen = mKeyguardStateController.isShowing() && intent != null
                 && mActivityIntentHelper.wouldPendingShowOverLockscreen(intent,
                 mLockscreenUserManager.getCurrentUserId());
@@ -276,7 +284,7 @@
             mShadeController.addPostCollapseAction(runnable);
             mShadeController.collapseShade(true /* animate */);
         } else if (mKeyguardStateController.isShowing()
-                && mCentralSurfaces.isOccluded()) {
+                && mKeyguardStateController.isOccluded()) {
             mStatusBarKeyguardViewManager.addAfterKeyguardGoneRunnable(runnable);
             mShadeController.collapseShade();
         } else {
@@ -284,7 +292,7 @@
         }
 
         // Always defer the keyguard dismiss when animating.
-        return animate || !mNotificationPanel.isFullyCollapsed();
+        return animate || !mShadeViewController.isFullyCollapsed();
     }
 
     private void handleNotificationClickAfterPanelCollapsed(
@@ -319,7 +327,7 @@
                     removeHunAfterClick(row);
                     // Show work challenge, do not run PendingIntent and
                     // remove notification
-                    collapseOnMainThread();
+                    mShadeController.collapseOnMainThread();
                     return;
                 }
             }
@@ -436,7 +444,9 @@
             ActivityLaunchAnimator.Controller animationController =
                     new StatusBarLaunchAnimatorController(
                             mNotificationAnimationProvider.getAnimatorController(row, null),
-                            mCentralSurfaces,
+                            mShadeViewController,
+                            mShadeController,
+                            mNotificationShadeWindowController,
                             isActivityIntent);
             mActivityLaunchAnimator.startPendingIntentWithAnimation(
                     animationController,
@@ -446,11 +456,11 @@
                         long eventTime = row.getAndResetLastActionUpTime();
                         Bundle options = eventTime > 0
                                 ? getActivityOptions(
-                                mCentralSurfaces.getDisplayId(),
+                                mDisplayId,
                                 adapter,
                                 mKeyguardStateController.isShowing(),
                                 eventTime)
-                                : getActivityOptions(mCentralSurfaces.getDisplayId(), adapter);
+                                : getActivityOptions(mDisplayId, adapter);
                         int result = intent.sendAndReturnResult(mContext, 0, fillInIntent, null,
                                 null, null, options);
                         mLogger.logSendPendingIntent(entry, intent, result);
@@ -467,7 +477,7 @@
     @Override
     public void startNotificationGutsIntent(final Intent intent, final int appUid,
             ExpandableNotificationRow row) {
-        boolean animate = mCentralSurfaces.shouldAnimateLaunch(true /* isActivityIntent */);
+        boolean animate = mActivityStarter.shouldAnimateLaunch(true /* isActivityIntent */);
         ActivityStarter.OnDismissAction onDismissAction = new ActivityStarter.OnDismissAction() {
             @Override
             public boolean onDismiss() {
@@ -475,14 +485,17 @@
                     ActivityLaunchAnimator.Controller animationController =
                             new StatusBarLaunchAnimatorController(
                                     mNotificationAnimationProvider.getAnimatorController(row),
-                                    mCentralSurfaces, true /* isActivityIntent */);
+                                    mShadeViewController,
+                                    mShadeController,
+                                    mNotificationShadeWindowController,
+                                    true /* isActivityIntent */);
 
                     mActivityLaunchAnimator.startIntentWithAnimation(
                             animationController, animate, intent.getPackage(),
                             (adapter) -> TaskStackBuilder.create(mContext)
                                     .addNextIntentWithParentStack(intent)
                                     .startActivities(getActivityOptions(
-                                            mCentralSurfaces.getDisplayId(),
+                                            mDisplayId,
                                             adapter),
                                             new UserHandle(UserHandle.getUserId(appUid))));
                 });
@@ -500,7 +513,7 @@
 
     @Override
     public void startHistoryIntent(View view, boolean showHistory) {
-        boolean animate = mCentralSurfaces.shouldAnimateLaunch(true /* isActivityIntent */);
+        boolean animate = mActivityStarter.shouldAnimateLaunch(true /* isActivityIntent */);
         ActivityStarter.OnDismissAction onDismissAction = new ActivityStarter.OnDismissAction() {
             @Override
             public boolean onDismiss() {
@@ -520,14 +533,17 @@
                             );
                     ActivityLaunchAnimator.Controller animationController =
                             viewController == null ? null
-                                : new StatusBarLaunchAnimatorController(viewController,
-                                        mCentralSurfaces,
-                                    true /* isActivityIntent */);
+                                : new StatusBarLaunchAnimatorController(
+                                        viewController,
+                                        mShadeViewController,
+                                        mShadeController,
+                                        mNotificationShadeWindowController,
+                                        true /* isActivityIntent */);
 
                     mActivityLaunchAnimator.startIntentWithAnimation(animationController, animate,
                             intent.getPackage(),
                             (adapter) -> tsb.startActivities(
-                                    getActivityOptions(mCentralSurfaces.getDisplayId(), adapter),
+                                    getActivityOptions(mDisplayId, adapter),
                                     mUserTracker.getUserHandle()));
                 });
                 return true;
@@ -580,7 +596,7 @@
         try {
             EventLog.writeEvent(EventLogTags.SYSUI_FULLSCREEN_NOTIFICATION,
                     entry.getKey());
-            mCentralSurfaces.wakeUpForFullScreenIntent();
+            mPowerInteractor.wakeUpForFullScreenIntent();
 
             ActivityOptions options = ActivityOptions.makeBasic();
             options.setPendingIntentBackgroundActivityStartMode(
@@ -621,11 +637,4 @@
         return true;
     }
 
-    private void collapseOnMainThread() {
-        if (Looper.getMainLooper().isCurrentThread()) {
-            mShadeController.collapseShade();
-        } else {
-            mMainThreadHandler.post(mShadeController::collapseShade);
-        }
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt
index 12f023b..d07378e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt
@@ -19,10 +19,10 @@
 import android.app.PendingIntent
 import com.android.systemui.log.dagger.NotifInteractionLog
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel.DEBUG
-import com.android.systemui.log.LogLevel.ERROR
-import com.android.systemui.log.LogLevel.INFO
-import com.android.systemui.log.LogLevel.WARNING
+import com.android.systemui.log.core.LogLevel.DEBUG
+import com.android.systemui.log.core.LogLevel.ERROR
+import com.android.systemui.log.core.LogLevel.INFO
+import com.android.systemui.log.core.LogLevel.WARNING
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.logKey
 import javax.inject.Inject
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 dfaee4c..a9135ac 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -22,25 +22,23 @@
 import android.os.PowerManager;
 import android.os.RemoteException;
 import android.os.ServiceManager;
-import android.os.SystemClock;
 import android.service.notification.StatusBarNotification;
 import android.service.vr.IVrManager;
 import android.service.vr.IVrStateCallbacks;
 import android.util.Log;
 import android.util.Slog;
 import android.view.View;
-import android.view.accessibility.AccessibilityManager;
 
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.systemui.InitController;
 import com.android.systemui.R;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
+import com.android.systemui.power.domain.interactor.PowerInteractor;
 import com.android.systemui.shade.NotificationShadeWindowView;
 import com.android.systemui.shade.QuickSettingsController;
 import com.android.systemui.shade.ShadeViewController;
 import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.KeyguardIndicationController;
 import com.android.systemui.statusbar.LockscreenShadeTransitionController;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationMediaManager;
@@ -51,16 +49,14 @@
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.notification.AboveShelfObserver;
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
-import com.android.systemui.statusbar.notification.NotifPipelineFlags;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
 import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource;
+import com.android.systemui.statusbar.notification.domain.interactor.NotificationsInteractor;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptSuppressor;
 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;
-import com.android.systemui.statusbar.notification.row.NotificationInfo.CheckSaveListener;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
 import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent;
@@ -69,9 +65,7 @@
 import javax.inject.Inject;
 
 @CentralSurfacesComponent.CentralSurfacesScope
-class StatusBarNotificationPresenter implements NotificationPresenter,
-        NotificationRowBinderImpl.BindRowCallback,
-        CommandQueue.Callbacks {
+class StatusBarNotificationPresenter implements NotificationPresenter, CommandQueue.Callbacks {
     private static final String TAG = "StatusBarNotificationPresenter";
 
     private final ActivityStarter mActivityStarter;
@@ -81,20 +75,18 @@
     private final NotifShadeEventSource mNotifShadeEventSource;
     private final NotificationMediaManager mMediaManager;
     private final NotificationGutsManager mGutsManager;
-
     private final ShadeViewController mNotificationPanel;
     private final HeadsUpManagerPhone mHeadsUpManager;
     private final AboveShelfObserver mAboveShelfObserver;
     private final DozeScrimController mDozeScrimController;
-    private final KeyguardIndicationController mKeyguardIndicationController;
     private final CentralSurfaces mCentralSurfaces;
+    private final NotificationsInteractor mNotificationsInteractor;
+    private final NotificationStackScrollLayoutController mNsslController;
     private final LockscreenShadeTransitionController mShadeTransitionController;
+    private final PowerInteractor mPowerInteractor;
     private final CommandQueue mCommandQueue;
-
-    private final AccessibilityManager mAccessibilityManager;
     private final KeyguardManager mKeyguardManager;
     private final NotificationShadeWindowController mNotificationShadeWindowController;
-    private final NotifPipelineFlags mNotifPipelineFlags;
     private final IStatusBarService mBarService;
     private final DynamicPrivacyController mDynamicPrivacyController;
     private final NotificationListContainer mNotifListContainer;
@@ -115,20 +107,19 @@
             NotificationShadeWindowController notificationShadeWindowController,
             DynamicPrivacyController dynamicPrivacyController,
             KeyguardStateController keyguardStateController,
-            KeyguardIndicationController keyguardIndicationController,
             CentralSurfaces centralSurfaces,
+            NotificationsInteractor notificationsInteractor,
             LockscreenShadeTransitionController shadeTransitionController,
+            PowerInteractor powerInteractor,
             CommandQueue commandQueue,
             NotificationLockscreenUserManager lockscreenUserManager,
             SysuiStatusBarStateController sysuiStatusBarStateController,
             NotifShadeEventSource notifShadeEventSource,
             NotificationMediaManager notificationMediaManager,
             NotificationGutsManager notificationGutsManager,
-            LockscreenGestureLogger lockscreenGestureLogger,
             InitController initController,
             NotificationInterruptStateProvider notificationInterruptStateProvider,
             NotificationRemoteInputManager remoteInputManager,
-            NotifPipelineFlags notifPipelineFlags,
             NotificationRemoteInputManager.Callback remoteInputManagerCallback,
             NotificationListContainer notificationListContainer) {
         mActivityStarter = activityStarter;
@@ -137,10 +128,12 @@
         mQsController = quickSettingsController;
         mHeadsUpManager = headsUp;
         mDynamicPrivacyController = dynamicPrivacyController;
-        mKeyguardIndicationController = keyguardIndicationController;
         // TODO: use KeyguardStateController#isOccluded to remove this dependency
         mCentralSurfaces = centralSurfaces;
+        mNotificationsInteractor = notificationsInteractor;
+        mNsslController = stackScrollerController;
         mShadeTransitionController = shadeTransitionController;
+        mPowerInteractor = powerInteractor;
         mCommandQueue = commandQueue;
         mLockscreenUserManager = lockscreenUserManager;
         mStatusBarStateController = sysuiStatusBarStateController;
@@ -149,10 +142,8 @@
         mGutsManager = notificationGutsManager;
         mAboveShelfObserver = new AboveShelfObserver(stackScrollerController.getView());
         mNotificationShadeWindowController = notificationShadeWindowController;
-        mNotifPipelineFlags = notifPipelineFlags;
         mAboveShelfObserver.setListener(statusBarWindow.findViewById(
                 R.id.notification_container_parent));
-        mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
         mDozeScrimController = dozeScrimController;
         mKeyguardManager = context.getSystemService(KeyguardManager.class);
         mBarService = IStatusBarService.Stub.asInterface(
@@ -170,10 +161,9 @@
         }
         remoteInputManager.setUpWithCallback(
                 remoteInputManagerCallback,
-                mNotificationPanel.getShadeNotificationPresenter().createRemoteInputDelegate());
+                mNsslController.createDelegate());
 
         initController.addPostInitTask(() -> {
-            mKeyguardIndicationController.init();
             mNotifShadeEventSource.setShadeEmptiedCallback(this::maybeClosePanelForShadeEmptied);
             mNotifShadeEventSource.setNotifRemovedByUserCallback(this::maybeEndAmbientPulse);
             notificationInterruptStateProvider.addSuppressor(mInterruptSuppressor);
@@ -203,7 +193,7 @@
     }
 
     private void maybeEndAmbientPulse() {
-        if (mNotificationPanel.getShadeNotificationPresenter().hasPulsingNotifications()
+        if (mNsslController.getNotificationListContainer().hasPulsingNotifications()
                 && !mHeadsUpManager.hasNotifications()) {
             // We were showing a pulse for a notification, but no notifications are pulsing anymore.
             // Finish the pulse.
@@ -218,7 +208,6 @@
         // End old BaseStatusBar.userSwitched
         mCommandQueue.animateCollapsePanels();
         mMediaManager.clearCurrentMediaNotification();
-        mCentralSurfaces.setLockscreenUser(newUserId);
         updateMediaMetaData(true, false);
     }
 
@@ -242,9 +231,7 @@
     public void onExpandClicked(NotificationEntry clickedEntry, View clickedView,
             boolean nowExpanded) {
         mHeadsUpManager.setExpanded(clickedEntry, nowExpanded);
-        mCentralSurfaces.wakeUpIfDozing(
-                SystemClock.uptimeMillis(), "NOTIFICATION_CLICK",
-                PowerManager.WAKE_REASON_GESTURE);
+        mPowerInteractor.wakeUpIfDozing("NOTIFICATION_CLICK", PowerManager.WAKE_REASON_GESTURE);
         if (nowExpanded) {
             if (mStatusBarStateController.getState() == StatusBarState.KEYGUARD) {
                 mShadeTransitionController.goToLockedShade(clickedEntry.getRow());
@@ -275,22 +262,6 @@
         }
     };
 
-    private final CheckSaveListener mCheckSaveListener = new CheckSaveListener() {
-        @Override
-        public void checkSave(Runnable saveImportance, StatusBarNotification sbn) {
-            // If the user has security enabled, show challenge if the setting is changed.
-            if (mLockscreenUserManager.isLockscreenPublicMode(sbn.getUser().getIdentifier())
-                    && mKeyguardManager.isKeyguardLocked()) {
-                onLockedNotificationImportanceChange(() -> {
-                    saveImportance.run();
-                    return true;
-                });
-            } else {
-                saveImportance.run();
-            }
-        }
-    };
-
     private final OnSettingsClickListener mOnSettingsClickListener = new OnSettingsClickListener() {
         @Override
         public void onSettingsClick(String key) {
@@ -341,7 +312,7 @@
 
         @Override
         public boolean suppressInterruptions(NotificationEntry entry) {
-            return mCentralSurfaces.areNotificationAlertsDisabled();
+            return !mNotificationsInteractor.areNotificationAlertsEnabled();
         }
     };
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterModule.java
index 26c483c..d94e434 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterModule.java
@@ -17,7 +17,6 @@
 package com.android.systemui.statusbar.phone;
 
 import com.android.systemui.statusbar.NotificationPresenter;
-import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
 
 import dagger.Binds;
 import dagger.Module;
@@ -26,8 +25,4 @@
 public abstract class StatusBarNotificationPresenterModule {
     @Binds
     abstract NotificationPresenter bindPresenter(StatusBarNotificationPresenter impl);
-
-    @Binds
-    abstract NotificationRowBinderImpl.BindRowCallback bindBindRowCallback(
-            StatusBarNotificationPresenter impl);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
index d731f88..6919996 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
@@ -30,7 +30,6 @@
 import com.android.systemui.statusbar.connectivity.MobileDataIndicators;
 import com.android.systemui.statusbar.connectivity.NetworkController;
 import com.android.systemui.statusbar.connectivity.SignalCallback;
-import com.android.systemui.statusbar.connectivity.WifiIndicators;
 import com.android.systemui.statusbar.policy.SecurityController;
 import com.android.systemui.tuner.TunerService;
 import com.android.systemui.tuner.TunerService.Tunable;
@@ -51,7 +50,6 @@
 
     private final String mSlotAirplane;
     private final String mSlotMobile;
-    private final String mSlotWifi;
     private final String mSlotEthernet;
     private final String mSlotVpn;
     private final String mSlotNoCalling;
@@ -67,17 +65,14 @@
 
     private boolean mHideAirplane;
     private boolean mHideMobile;
-    private boolean mHideWifi;
     private boolean mHideEthernet;
     private boolean mActivityEnabled;
 
     // Track as little state as possible, and only for padding purposes
     private boolean mIsAirplaneMode = false;
-    private boolean mIsWifiEnabled = false;
 
     private ArrayList<MobileIconState> mMobileStates = new ArrayList<>();
     private ArrayList<CallIndicatorIconState> mCallIndicatorStates = new ArrayList<>();
-    private WifiIconState mWifiIconState = new WifiIconState();
     private boolean mInitialized;
 
     @Inject
@@ -99,7 +94,6 @@
 
         mSlotAirplane = mContext.getString(com.android.internal.R.string.status_bar_airplane);
         mSlotMobile   = mContext.getString(com.android.internal.R.string.status_bar_mobile);
-        mSlotWifi     = mContext.getString(com.android.internal.R.string.status_bar_wifi);
         mSlotEthernet = mContext.getString(com.android.internal.R.string.status_bar_ethernet);
         mSlotVpn      = mContext.getString(com.android.internal.R.string.status_bar_vpn);
         mSlotNoCalling = mContext.getString(com.android.internal.R.string.status_bar_no_calling);
@@ -154,15 +148,13 @@
         ArraySet<String> hideList = StatusBarIconController.getIconHideList(mContext, newValue);
         boolean hideAirplane = hideList.contains(mSlotAirplane);
         boolean hideMobile = hideList.contains(mSlotMobile);
-        boolean hideWifi = hideList.contains(mSlotWifi);
         boolean hideEthernet = hideList.contains(mSlotEthernet);
 
         if (hideAirplane != mHideAirplane || hideMobile != mHideMobile
-                || hideEthernet != mHideEthernet || hideWifi != mHideWifi) {
+                || hideEthernet != mHideEthernet) {
             mHideAirplane = hideAirplane;
             mHideMobile = hideMobile;
             mHideEthernet = hideEthernet;
-            mHideWifi = hideWifi;
             // Re-register to get new callbacks.
             mNetworkController.removeCallback(this);
             mNetworkController.addCallback(this);
@@ -170,56 +162,6 @@
     }
 
     @Override
-    public void setWifiIndicators(@NonNull WifiIndicators indicators) {
-        if (DEBUG) {
-            Log.d(TAG, "setWifiIndicators: " + indicators);
-        }
-        boolean visible = indicators.statusIcon.visible && !mHideWifi;
-        boolean in = indicators.activityIn && mActivityEnabled && visible;
-        boolean out = indicators.activityOut && mActivityEnabled && visible;
-        mIsWifiEnabled = indicators.enabled;
-
-        WifiIconState newState = mWifiIconState.copy();
-
-        if (mWifiIconState.noDefaultNetwork && mWifiIconState.noNetworksAvailable
-                && !mIsAirplaneMode) {
-            newState.visible = true;
-            newState.resId = R.drawable.ic_qs_no_internet_unavailable;
-        } else if (mWifiIconState.noDefaultNetwork && !mWifiIconState.noNetworksAvailable
-                && (!mIsAirplaneMode || (mIsAirplaneMode && mIsWifiEnabled))) {
-            newState.visible = true;
-            newState.resId = R.drawable.ic_qs_no_internet_available;
-        } else {
-            newState.visible = visible;
-            newState.resId = indicators.statusIcon.icon;
-            newState.activityIn = in;
-            newState.activityOut = out;
-            newState.contentDescription = indicators.statusIcon.contentDescription;
-            MobileIconState first = getFirstMobileState();
-            newState.signalSpacerVisible = first != null && first.typeId != 0;
-        }
-        newState.slot = mSlotWifi;
-        newState.airplaneSpacerVisible = mIsAirplaneMode;
-        updateWifiIconWithState(newState);
-        mWifiIconState = newState;
-    }
-
-    private void updateShowWifiSignalSpacer(WifiIconState state) {
-        MobileIconState first = getFirstMobileState();
-        state.signalSpacerVisible = first != null && first.typeId != 0;
-    }
-
-    private void updateWifiIconWithState(WifiIconState state) {
-        if (DEBUG) Log.d(TAG, "WifiIconState: " + state == null ? "" : state.toString());
-        if (state.visible && state.resId > 0) {
-            mIconController.setWifiIcon(mSlotWifi, state);
-            mIconController.setIconVisibility(mSlotWifi, true);
-        } else {
-            mIconController.setIconVisibility(mSlotWifi, false);
-        }
-    }
-
-    @Override
     public void setCallIndicator(@NonNull IconState statusIcon, int subId) {
         if (DEBUG) {
             Log.d(TAG, "setCallIndicator: "
@@ -257,10 +199,6 @@
             return;
         }
 
-        // Visibility of the data type indicator changed
-        boolean typeChanged = indicators.statusType != state.typeId
-                && (indicators.statusType == 0 || state.typeId == 0);
-
         state.visible = indicators.statusIcon.visible && !mHideMobile;
         state.strengthId = indicators.statusIcon.icon;
         state.typeId = indicators.statusType;
@@ -277,15 +215,6 @@
         }
         // Always send a copy to maintain value type semantics
         mIconController.setMobileIcons(mSlotMobile, MobileIconState.copyStates(mMobileStates));
-
-        if (typeChanged) {
-            WifiIconState wifiCopy = mWifiIconState.copy();
-            updateShowWifiSignalSpacer(wifiCopy);
-            if (!Objects.equals(wifiCopy, mWifiIconState)) {
-                updateWifiIconWithState(wifiCopy);
-                mWifiIconState = wifiCopy;
-            }
-        }
     }
 
     private CallIndicatorIconState getNoCallingState(int subId) {
@@ -308,15 +237,6 @@
         return null;
     }
 
-    private MobileIconState getFirstMobileState() {
-        if (mMobileStates.size() > 0) {
-            return mMobileStates.get(0);
-        }
-
-        return null;
-    }
-
-
     /**
      * It is expected that a call to setSubs will be immediately followed by setMobileDataIndicators
      * so we don't have to update the icon manager at this point, just remove the old ones
@@ -504,60 +424,6 @@
         }
     }
 
-    public static class WifiIconState extends SignalIconState{
-        public int resId;
-        public boolean airplaneSpacerVisible;
-        public boolean signalSpacerVisible;
-        public boolean noDefaultNetwork;
-        public boolean noValidatedNetwork;
-        public boolean noNetworksAvailable;
-
-        @Override
-        public boolean equals(Object o) {
-            // Skipping reference equality bc this should be more of a value type
-            if (o == null || getClass() != o.getClass()) {
-                return false;
-            }
-            if (!super.equals(o)) {
-                return false;
-            }
-            WifiIconState that = (WifiIconState) o;
-            return resId == that.resId
-                    && airplaneSpacerVisible == that.airplaneSpacerVisible
-                    && signalSpacerVisible == that.signalSpacerVisible
-                    && noDefaultNetwork == that.noDefaultNetwork
-                    && noValidatedNetwork == that.noValidatedNetwork
-                    && noNetworksAvailable == that.noNetworksAvailable;
-        }
-
-        public void copyTo(WifiIconState other) {
-            super.copyTo(other);
-            other.resId = resId;
-            other.airplaneSpacerVisible = airplaneSpacerVisible;
-            other.signalSpacerVisible = signalSpacerVisible;
-            other.noDefaultNetwork = noDefaultNetwork;
-            other.noValidatedNetwork = noValidatedNetwork;
-            other.noNetworksAvailable = noNetworksAvailable;
-        }
-
-        public WifiIconState copy() {
-            WifiIconState newState = new WifiIconState();
-            copyTo(newState);
-            return newState;
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hash(super.hashCode(),
-                    resId, airplaneSpacerVisible, signalSpacerVisible, noDefaultNetwork,
-                    noValidatedNetwork, noNetworksAvailable);
-        }
-
-        @Override public String toString() {
-            return "WifiIconState(resId=" + resId + ", visible=" + visible + ")";
-        }
-    }
-
     /**
      * A little different. This one delegates to SignalDrawable instead of a specific resId
      */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java
index 26c1767..d83664f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java
@@ -22,6 +22,8 @@
 
 import android.annotation.Nullable;
 import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.res.Configuration;
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Paint;
@@ -64,6 +66,7 @@
     private boolean mNeedsUnderflow;
     // Individual StatusBarIconViews draw their etc dots centered in this width
     private int mIconDotFrameWidth;
+    private boolean mQsExpansionTransitioning;
     private boolean mShouldRestrictIcons = true;
     // Used to count which states want to be visible during layout
     private ArrayList<StatusIconState> mLayoutStates = new ArrayList<>();
@@ -72,13 +75,16 @@
     // Any ignored icon will never be added as a child
     private ArrayList<String> mIgnoredSlots = new ArrayList<>();
 
+    private Configuration mConfiguration;
+
     public StatusIconContainer(Context context) {
         this(context, null);
     }
 
     public StatusIconContainer(Context context, AttributeSet attrs) {
         super(context, attrs);
-        initDimens();
+        mConfiguration = new Configuration(context.getResources().getConfiguration());
+        reloadDimens();
         setWillNotDraw(!DEBUG_OVERFLOW);
     }
 
@@ -87,6 +93,10 @@
         super.onFinishInflate();
     }
 
+    public void setQsExpansionTransitioning(boolean expansionTransitioning) {
+        mQsExpansionTransitioning = expansionTransitioning;
+    }
+
     public void setShouldRestrictIcons(boolean should) {
         mShouldRestrictIcons = should;
     }
@@ -95,10 +105,10 @@
         return mShouldRestrictIcons;
     }
 
-    private void initDimens() {
+    private void reloadDimens() {
         // This is the same value that StatusBarIconView uses
         mIconDotFrameWidth = getResources().getDimensionPixelSize(
-                com.android.internal.R.dimen.status_bar_icon_size);
+                com.android.internal.R.dimen.status_bar_icon_size_sp);
         mDotPadding = getResources().getDimensionPixelSize(R.dimen.overflow_icon_dot_padding);
         mIconSpacing = getResources().getDimensionPixelSize(R.dimen.status_bar_system_icon_spacing);
         int radius = getResources().getDimensionPixelSize(R.dimen.overflow_dot_radius);
@@ -145,8 +155,8 @@
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         mMeasureViews.clear();
-        int mode = MeasureSpec.getMode(widthMeasureSpec);
-        final int width = MeasureSpec.getSize(widthMeasureSpec);
+        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+        final int specWidth = MeasureSpec.getSize(widthMeasureSpec);
         final int count = getChildCount();
         // Collect all of the views which want to be laid out
         for (int i = 0; i < count; i++) {
@@ -163,7 +173,7 @@
         boolean trackWidth = true;
 
         // Measure all children so that they report the correct width
-        int childWidthSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.UNSPECIFIED);
+        int childWidthSpec = MeasureSpec.makeMeasureSpec(specWidth, MeasureSpec.UNSPECIFIED);
         mNeedsUnderflow = mShouldRestrictIcons && visibleCount > MAX_ICONS;
         for (int i = 0; i < visibleCount; i++) {
             // Walking backwards
@@ -182,18 +192,35 @@
                 totalWidth += getViewTotalMeasuredWidth(child) + spacing;
             }
         }
+        setMeasuredDimension(
+                getMeasuredWidth(widthMode, specWidth, totalWidth),
+                getMeasuredHeight(heightMeasureSpec, mMeasureViews));
+    }
 
-        if (mode == MeasureSpec.EXACTLY) {
-            if (!mNeedsUnderflow && totalWidth > width) {
-                mNeedsUnderflow = true;
-            }
-            setMeasuredDimension(width, MeasureSpec.getSize(heightMeasureSpec));
+    private int getMeasuredHeight(int heightMeasureSpec, List<View> measuredChildren) {
+        if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY) {
+            return MeasureSpec.getSize(heightMeasureSpec);
         } else {
-            if (mode == MeasureSpec.AT_MOST && totalWidth > width) {
-                mNeedsUnderflow = true;
-                totalWidth = width;
+            int highest = 0;
+            for (View child : measuredChildren) {
+                highest = Math.max(child.getMeasuredHeight(), highest);
             }
-            setMeasuredDimension(totalWidth, MeasureSpec.getSize(heightMeasureSpec));
+            return highest + getPaddingTop() + getPaddingBottom();
+        }
+    }
+
+    private int getMeasuredWidth(int widthMode, int specWidth, int totalWidth) {
+        if (widthMode == MeasureSpec.EXACTLY) {
+            if (!mNeedsUnderflow && totalWidth > specWidth) {
+                mNeedsUnderflow = true;
+            }
+            return specWidth;
+        } else {
+            if (widthMode == MeasureSpec.AT_MOST && totalWidth > specWidth) {
+                mNeedsUnderflow = true;
+                totalWidth = specWidth;
+            }
+            return totalWidth;
         }
     }
 
@@ -211,6 +238,16 @@
         child.setTag(R.id.status_bar_view_state_tag, null);
     }
 
+    @Override
+    protected void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        final int configDiff = newConfig.diff(mConfiguration);
+        mConfiguration.setTo(newConfig);
+        if ((configDiff & (ActivityInfo.CONFIG_DENSITY | ActivityInfo.CONFIG_FONT_SCALE)) != 0) {
+            reloadDimens();
+        }
+    }
+
     /**
      * Add a name of an icon slot to be ignored. It will not show up nor be measured
      * @param slotName name of the icon as it exists in
@@ -280,34 +317,6 @@
     }
 
     /**
-     * Sets the list of ignored icon slots clearing the current list.
-     * @param slots names of the icons to ignore
-     */
-    public void setIgnoredSlots(List<String> slots) {
-        mIgnoredSlots.clear();
-        addIgnoredSlots(slots);
-    }
-
-    /**
-     * Returns the view corresponding to a particular slot.
-     *
-     * Use it solely to manipulate how it is presented.
-     * @param slot name of the slot to find. Names are defined in
-     *            {@link com.android.internal.R.config_statusBarIcons}
-     * @return a view for the slot if this container has it, else {@code null}
-     */
-    public View getViewForSlot(String slot) {
-        for (int i = 0; i < getChildCount(); i++) {
-            View child = getChildAt(i);
-            if (child instanceof StatusIconDisplayable
-                    && ((StatusIconDisplayable) child).getSlot().equals(slot)) {
-                return child;
-            }
-        }
-        return null;
-    }
-
-    /**
      * Layout is happening from end -> start
      */
     private void calculateIconTranslations() {
@@ -348,13 +357,17 @@
         int totalVisible = mLayoutStates.size();
         int maxVisible = totalVisible <= MAX_ICONS ? MAX_ICONS : MAX_ICONS - 1;
 
-        mUnderflowStart = 0;
+        // Init mUnderflowStart value with the offset to let the dot be placed next to battery icon.
+        // This is to prevent if the underflow happens at rightest(totalVisible - 1) child then
+        // break the for loop with mUnderflowStart staying 0(initial value), causing the dot be
+        // placed at the leftest side.
+        mUnderflowStart = (int) Math.max(contentStart, width - getPaddingEnd() - mUnderflowWidth);
         int visible = 0;
         int firstUnderflowIndex = -1;
         for (int i = totalVisible - 1; i >= 0; i--) {
             StatusIconState state = mLayoutStates.get(i);
             // Allow room for underflow if we found we need it in onMeasure
-            if (mNeedsUnderflow && (state.getXTranslation() < (contentStart + mUnderflowWidth))
+            if ((mNeedsUnderflow && (state.getXTranslation() < (contentStart + mUnderflowWidth)))
                     || (mShouldRestrictIcons && (visible >= maxVisible))) {
                 firstUnderflowIndex = i;
                 break;
@@ -397,6 +410,7 @@
             StatusIconState vs = getViewStateFromChild(child);
             if (vs != null) {
                 vs.applyToView(child);
+                vs.qsExpansionTransitioning = mQsExpansionTransitioning;
             }
         }
     }
@@ -431,6 +445,7 @@
         /// StatusBarIconView.STATE_*
         public int visibleState = STATE_ICON;
         public boolean justAdded = true;
+        public boolean qsExpansionTransitioning = false;
 
         // How far we are from the end of the view actually is the most relevant for animation
         float distanceToViewEnd = -1;
@@ -473,12 +488,13 @@
             }
 
             icon.setVisibleState(visibleState, animateVisibility);
-            if (animationProperties != null) {
+            if (animationProperties != null && !qsExpansionTransitioning) {
                 animateTo(view, animationProperties);
             } else {
                 super.applyToView(view);
             }
 
+            qsExpansionTransitioning = false;
             justAdded = false;
             distanceToViewEnd = currentDistanceToEnd;
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
index 592d1aa..96a4d90 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
@@ -22,6 +22,7 @@
 import com.android.systemui.keyguard.WakefulnessLifecycle
 import com.android.systemui.statusbar.CircleReveal
 import com.android.systemui.statusbar.LightRevealScrim
+import com.android.systemui.statusbar.NotificationShadeWindowController
 import com.android.systemui.statusbar.StatusBarState
 import com.android.systemui.statusbar.StatusBarStateControllerImpl
 import com.android.systemui.statusbar.notification.AnimatableProperty
@@ -60,6 +61,7 @@
     private val keyguardStateController: KeyguardStateController,
     private val dozeParameters: dagger.Lazy<DozeParameters>,
     private val globalSettings: GlobalSettings,
+    private val notifShadeWindowControllerLazy: dagger.Lazy<NotificationShadeWindowController>,
     private val interactionJankMonitor: InteractionJankMonitor,
     private val powerManager: PowerManager,
     private val handler: Handler = Handler()
@@ -114,7 +116,7 @@
 
             override fun onAnimationStart(animation: Animator?) {
                 interactionJankMonitor.begin(
-                    mCentralSurfaces.notificationShadeWindowView, CUJ_SCREEN_OFF)
+                        notifShadeWindowControllerLazy.get().windowRootView, CUJ_SCREEN_OFF)
             }
         })
     }
@@ -218,7 +220,7 @@
                 .setCustomInterpolator(View.ALPHA, Interpolators.FAST_OUT_SLOW_IN),
             true /* animate */)
         interactionJankMonitor.begin(
-            mCentralSurfaces.notificationShadeWindowView,
+                notifShadeWindowControllerLazy.get().windowRootView,
             CUJ_SCREEN_OFF_SHOW_AOD
         )
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java
index 273e783..e77f419 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java
@@ -20,18 +20,10 @@
 
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
-import com.android.systemui.shade.NotificationPanelViewController;
-import com.android.systemui.shade.NotificationShadeWindowView;
-import com.android.systemui.shade.NotificationShadeWindowViewController;
-import com.android.systemui.shade.QuickSettingsController;
+import com.android.systemui.scene.ui.view.WindowRootView;
 import com.android.systemui.shade.ShadeHeaderController;
 import com.android.systemui.statusbar.NotificationPresenter;
-import com.android.systemui.statusbar.NotificationShelfController;
 import com.android.systemui.statusbar.notification.NotificationActivityStarter;
-import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
-import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
-import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
-import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutListContainerModule;
 import com.android.systemui.statusbar.phone.CentralSurfacesCommandQueueCallbacks;
 import com.android.systemui.statusbar.phone.CentralSurfacesImpl;
 import com.android.systemui.statusbar.phone.StatusBarHeadsUpChangeListener;
@@ -57,7 +49,6 @@
  * outside the component. Should more items be moved *into* this component to avoid so many getters?
  */
 @Subcomponent(modules = {
-        NotificationStackScrollLayoutListContainerModule.class,
         StatusBarViewModule.class,
         StatusBarNotificationActivityStarterModule.class,
         StatusBarNotificationPresenterModule.class,
@@ -80,29 +71,8 @@
     @Scope
     @interface CentralSurfacesScope {}
 
-    /**
-     * Creates a {@link NotificationShadeWindowView}.
-     */
-    NotificationShadeWindowView getNotificationShadeWindowView();
-
-    /** */
-    NotificationShelfController getNotificationShelfController();
-
-    /** */
-    NotificationStackScrollLayoutController getNotificationStackScrollLayoutController();
-
-    /**
-     * Creates a NotificationShadeWindowViewController.
-     */
-    NotificationShadeWindowViewController getNotificationShadeWindowViewController();
-
-    /**
-     * Creates a NotificationPanelViewController.
-     */
-    NotificationPanelViewController getNotificationPanelViewController();
-
-    /** Creates a QuickSettingsController. */
-    QuickSettingsController getQuickSettingsController();
+    /** Creates the root view of the main SysUI window}. */
+    WindowRootView getWindowRootView();
 
     /**
      * Creates a StatusBarHeadsUpChangeListener.
@@ -129,8 +99,4 @@
     NotificationActivityStarter getNotificationActivityStarter();
 
     NotificationPresenter getNotificationPresenter();
-
-    NotificationRowBinderImpl.BindRowCallback getBindRowCallback();
-
-    NotificationListContainer getNotificationListContainer();
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
index 4ccbc5a..ebdde78 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
@@ -16,39 +16,20 @@
 
 package com.android.systemui.statusbar.phone.dagger;
 
-import android.view.LayoutInflater;
-
 import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.systemui.R;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.shade.NotificationPanelView;
-import com.android.systemui.shade.NotificationPanelViewController;
-import com.android.systemui.shade.NotificationShadeWindowView;
-import com.android.systemui.shade.NotificationsQuickSettingsContainer;
 import com.android.systemui.shade.ShadeExpansionStateManager;
 import com.android.systemui.shade.ShadeViewController;
 import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.LegacyNotificationShelfControllerImpl;
-import com.android.systemui.statusbar.NotificationShelf;
-import com.android.systemui.statusbar.NotificationShelfController;
 import com.android.systemui.statusbar.OperatorNameViewController;
 import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
-import com.android.systemui.statusbar.notification.row.dagger.NotificationShelfComponent;
-import com.android.systemui.statusbar.notification.row.ui.viewmodel.ActivatableNotificationViewModelModule;
-import com.android.systemui.statusbar.notification.shelf.ui.viewbinder.NotificationShelfViewBinderWrapperControllerImpl;
-import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
-import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationListViewModelModule;
-import com.android.systemui.statusbar.phone.KeyguardBottomAreaView;
 import com.android.systemui.statusbar.phone.NotificationIconAreaController;
-import com.android.systemui.statusbar.phone.StatusBarBoundsProvider;
 import com.android.systemui.statusbar.phone.StatusBarHideIconsForBouncerManager;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
 import com.android.systemui.statusbar.phone.StatusBarLocationPublisher;
-import com.android.systemui.statusbar.phone.SystemBarAttributesListener;
 import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment;
 import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragmentLogger;
 import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent;
@@ -60,81 +41,25 @@
 import com.android.systemui.util.CarrierConfigTracker;
 import com.android.systemui.util.settings.SecureSettings;
 
-import dagger.Binds;
 import dagger.Module;
 import dagger.Provides;
-import dagger.multibindings.IntoSet;
 
 import java.util.concurrent.Executor;
 
 import javax.inject.Named;
-import javax.inject.Provider;
 
-@Module(subcomponents = StatusBarFragmentComponent.class,
-        includes = {
-                ActivatableNotificationViewModelModule.class,
-                NotificationListViewModelModule.class,
-        })
+/**
+ * A module for {@link CentralSurfacesComponent.CentralSurfacesScope} components.
+ *
+ * @deprecated CentralSurfacesScope will be removed shortly (b/277762009). Classes should be
+ *   annotated with @SysUISingleton instead.
+ */
+@Module(subcomponents = StatusBarFragmentComponent.class)
+@Deprecated
 public abstract class StatusBarViewModule {
 
     public static final String STATUS_BAR_FRAGMENT = "status_bar_fragment";
 
-    /** */
-    @Provides
-    @CentralSurfacesComponent.CentralSurfacesScope
-    public static NotificationShelf providesNotificationShelf(LayoutInflater layoutInflater,
-            NotificationStackScrollLayout notificationStackScrollLayout) {
-        NotificationShelf view = (NotificationShelf) layoutInflater.inflate(
-                R.layout.status_bar_notification_shelf, notificationStackScrollLayout, false);
-
-        if (view == null) {
-            throw new IllegalStateException(
-                    "R.layout.status_bar_notification_shelf could not be properly inflated");
-        }
-        return view;
-    }
-
-    /** */
-    @Provides
-    @CentralSurfacesComponent.CentralSurfacesScope
-    public static NotificationShelfController providesStatusBarWindowView(
-            FeatureFlags featureFlags,
-            Provider<NotificationShelfViewBinderWrapperControllerImpl> newImpl,
-            NotificationShelfComponent.Builder notificationShelfComponentBuilder,
-            NotificationShelf notificationShelf) {
-        if (featureFlags.isEnabled(Flags.NOTIFICATION_SHELF_REFACTOR)) {
-            return newImpl.get();
-        } else {
-            NotificationShelfComponent component = notificationShelfComponentBuilder
-                    .notificationShelf(notificationShelf)
-                    .build();
-            LegacyNotificationShelfControllerImpl notificationShelfController =
-                    component.getNotificationShelfController();
-            notificationShelfController.init();
-
-            return notificationShelfController;
-        }
-    }
-
-    /** */
-    @Binds
-    @CentralSurfacesComponent.CentralSurfacesScope
-    abstract ShadeViewController bindsShadeViewController(
-            NotificationPanelViewController notificationPanelViewController);
-
-    /** */
-    @Provides
-    @CentralSurfacesComponent.CentralSurfacesScope
-    public static NotificationsQuickSettingsContainer getNotificationsQuickSettingsContainer(
-            NotificationShadeWindowView notificationShadeWindowView) {
-        return notificationShadeWindowView.findViewById(R.id.notification_container_parent);
-    }
-
-    @Binds
-    @IntoSet
-    abstract StatusBarBoundsProvider.BoundsChangeListener sysBarAttrsListenerAsBoundsListener(
-            SystemBarAttributesListener systemBarAttributesListener);
-
     /**
      * Creates a new {@link CollapsedStatusBarFragment}.
      *
@@ -199,17 +124,4 @@
                 statusBarWindowStateController,
                 keyguardUpdateMonitor);
     }
-
-    /**
-     * Constructs a new, unattached {@link KeyguardBottomAreaView}.
-     *
-     * Note that this is explicitly _not_ a singleton, as we want to be able to reinflate it
-     */
-    @Provides
-    public static KeyguardBottomAreaView providesKeyguardBottomAreaView(
-            NotificationPanelView npv, LayoutInflater layoutInflater) {
-        return (KeyguardBottomAreaView) layoutInflater.inflate(R
-                .layout.keyguard_bottom_area, npv, 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 fcae23b..2efdf8a 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
@@ -14,9 +14,6 @@
 
 package com.android.systemui.statusbar.phone.fragment;
 
-import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.IDLE;
-import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.SHOWING_PERSISTENT_DOT;
-
 import android.annotation.Nullable;
 import android.annotation.SuppressLint;
 import android.app.Fragment;
@@ -39,6 +36,7 @@
 import androidx.core.animation.Animator;
 
 import com.android.app.animation.Interpolators;
+import com.android.app.animation.InterpolatorsAndroidX;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.Dumpable;
 import com.android.systemui.R;
@@ -77,6 +75,8 @@
 import com.android.systemui.util.CarrierConfigTracker.DefaultDataSubscriptionChangedListener;
 import com.android.systemui.util.settings.SecureSettings;
 
+import kotlin.Unit;
+
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -99,12 +99,16 @@
     private static final String EXTRA_PANEL_STATE = "panel_state";
     public static final String STATUS_BAR_ICON_MANAGER_TAG = "status_bar_icon_manager";
     public static final int FADE_IN_DURATION = 320;
+    public static final int FADE_OUT_DURATION = 160;
     public static final int FADE_IN_DELAY = 50;
+    private static final int SOURCE_SYSTEM_EVENT_ANIMATOR = 1;
+    private static final int SOURCE_OTHER = 2;
     private StatusBarFragmentComponent mStatusBarFragmentComponent;
     private PhoneStatusBarView mStatusBar;
     private final StatusBarStateController mStatusBarStateController;
     private final KeyguardStateController mKeyguardStateController;
     private final ShadeViewController mShadeViewController;
+    private MultiSourceMinAlphaController mEndSideAlphaController;
     private LinearLayout mEndSideContent;
     private View mClockView;
     private View mOngoingCallChip;
@@ -149,7 +153,7 @@
         }
     };
     private OperatorNameViewController mOperatorNameViewController;
-    private StatusBarSystemEventAnimator mSystemEventAnimator;
+    private StatusBarSystemEventDefaultAnimator mSystemEventAnimator;
 
     private final CarrierConfigChangedListener mCarrierConfigCallback =
             new CarrierConfigChangedListener() {
@@ -297,14 +301,14 @@
         updateBlockedIcons();
         mStatusBarIconController.addIconGroup(mDarkIconManager);
         mEndSideContent = mStatusBar.findViewById(R.id.status_bar_end_side_content);
+        mEndSideAlphaController = new MultiSourceMinAlphaController(mEndSideContent);
         mClockView = mStatusBar.findViewById(R.id.clock);
         mOngoingCallChip = mStatusBar.findViewById(R.id.ongoing_call_chip);
         showEndSideContent(false);
         showClock(false);
         initOperatorName();
         initNotificationIconArea();
-        mSystemEventAnimator =
-                new StatusBarSystemEventAnimator(mEndSideContent, getResources());
+        mSystemEventAnimator = getSystemEventAnimator();
         mCarrierConfigTracker.addCallback(mCarrierConfigCallback);
         mCarrierConfigTracker.addDefaultDataSubscriptionChangedListener(mDefaultDataListener);
 
@@ -593,18 +597,27 @@
     }
 
     private void hideEndSideContent(boolean animate) {
-        animateHide(mEndSideContent, animate);
+        if (!animate) {
+            mEndSideAlphaController.setAlpha(/*alpha*/ 0f, SOURCE_OTHER);
+        } else {
+            mEndSideAlphaController.animateToAlpha(/*alpha*/ 0f, SOURCE_OTHER, FADE_OUT_DURATION,
+                    InterpolatorsAndroidX.ALPHA_OUT, /*startDelay*/ 0);
+        }
     }
 
     private void showEndSideContent(boolean animate) {
-        // Only show the system icon area if we are not currently animating
-        int state = mAnimationScheduler.getAnimationState();
-        if (state == IDLE || state == SHOWING_PERSISTENT_DOT) {
-            animateShow(mEndSideContent, animate);
+        if (!animate) {
+            mEndSideAlphaController.setAlpha(1f, SOURCE_OTHER);
+            return;
+        }
+        if (mKeyguardStateController.isKeyguardFadingAway()) {
+            mEndSideAlphaController.animateToAlpha(/*alpha*/ 1f, SOURCE_OTHER,
+                    mKeyguardStateController.getKeyguardFadingAwayDuration(),
+                    InterpolatorsAndroidX.LINEAR_OUT_SLOW_IN,
+                    mKeyguardStateController.getKeyguardFadingAwayDelay());
         } else {
-            // We are in the middle of a system status event animation, which will animate the
-            // alpha (but not the visibility). Allow the view to become visible again
-            mEndSideContent.setVisibility(View.VISIBLE);
+            mEndSideAlphaController.animateToAlpha(/*alpha*/ 1f, SOURCE_OTHER, FADE_IN_DURATION,
+                    InterpolatorsAndroidX.ALPHA_IN, FADE_IN_DELAY);
         }
     }
 
@@ -671,7 +684,7 @@
 
         v.animate()
                 .alpha(0f)
-                .setDuration(160)
+                .setDuration(FADE_OUT_DURATION)
                 .setStartDelay(0)
                 .setInterpolator(Interpolators.ALPHA_OUT)
                 .withEndAction(() -> v.setVisibility(state));
@@ -718,9 +731,9 @@
     private void initOperatorName() {
         int subId = SubscriptionManager.getDefaultDataSubscriptionId();
         if (mCarrierConfigTracker.getShowOperatorNameInStatusBarConfig(subId)) {
-            ViewStub stub = mStatusBar.findViewById(R.id.operator_name);
+            View view = mStatusBar.findViewById(R.id.operator_name);
             mOperatorNameViewController =
-                    mOperatorNameViewControllerFactory.create((OperatorNameView) stub.inflate());
+                    mOperatorNameViewControllerFactory.create((OperatorNameView) view);
             mOperatorNameViewController.init();
             // This view should not be visible on lock-screen
             if (mKeyguardStateController.isShowing()) {
@@ -754,6 +767,16 @@
         return mSystemEventAnimator.onSystemEventAnimationFinish(hasPersistentDot);
     }
 
+    private StatusBarSystemEventDefaultAnimator getSystemEventAnimator() {
+        return new StatusBarSystemEventDefaultAnimator(getResources(), (alpha) -> {
+            mEndSideAlphaController.setAlpha(alpha, SOURCE_SYSTEM_EVENT_ANIMATOR);
+            return Unit.INSTANCE;
+        }, (translationX) -> {
+            mEndSideContent.setTranslationX(translationX);
+            return Unit.INSTANCE;
+        }, /*isAnimationRunning*/ false);
+    }
+
     private void updateStatusBarLocation(int left, int right) {
         int leftMargin = left - mStatusBar.getLeft();
         int rightMargin = mStatusBar.getRight() - right;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLogger.kt
index 8c19fb4..7cdb9c0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLogger.kt
@@ -18,7 +18,7 @@
 
 import com.android.systemui.log.dagger.CollapsedSbFragmentLog
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import com.android.systemui.statusbar.disableflags.DisableFlagsLogger
 import javax.inject.Inject
 
@@ -33,7 +33,6 @@
      * modifications that were made to the flags locally.
      *
      * @param new see [DisableFlagsLogger.getDisableFlagsString]
-     * @param newAfterLocalModification see [DisableFlagsLogger.getDisableFlagsString]
      */
     fun logDisableFlagChange(
         new: DisableFlagsLogger.DisableState,
@@ -47,8 +46,7 @@
                 },
                 {
                     disableFlagsLogger.getDisableFlagsString(
-                        old = null,
-                        new = DisableFlagsLogger.DisableState(int1, int2),
+                        DisableFlagsLogger.DisableState(int1, int2),
                     )
                 }
         )
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaController.kt
new file mode 100644
index 0000000..c8836e4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaController.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.systemui.statusbar.phone.fragment
+
+import android.view.View
+import androidx.core.animation.Interpolator
+import androidx.core.animation.ValueAnimator
+import com.android.app.animation.InterpolatorsAndroidX
+
+/**
+ * A controller that keeps track of multiple sources applying alpha value changes to a view. It will
+ * always apply the minimum alpha value of all sources.
+ */
+internal class MultiSourceMinAlphaController
+@JvmOverloads
+constructor(private val view: View, private val initialAlpha: Float = 1f) {
+
+    private val alphas = mutableMapOf<Int, Float>()
+    private val animators = mutableMapOf<Int, ValueAnimator>()
+
+    /**
+     * Sets the alpha of the provided source and applies it to the view (if no other source has set
+     * a lower alpha currently). If an animator of the same source is still running (i.e.
+     * [animateToAlpha] was called before), that animator is cancelled.
+     */
+    fun setAlpha(alpha: Float, sourceId: Int) {
+        animators[sourceId]?.cancel()
+        updateAlpha(alpha, sourceId)
+    }
+
+    /** Animates to the alpha of the provided source. */
+    fun animateToAlpha(
+        alpha: Float,
+        sourceId: Int,
+        duration: Long,
+        interpolator: Interpolator = InterpolatorsAndroidX.ALPHA_IN,
+        startDelay: Long = 0
+    ) {
+        animators[sourceId]?.cancel()
+        val animator = ValueAnimator.ofFloat(getMinAlpha(), alpha)
+        animator.duration = duration
+        animator.startDelay = startDelay
+        animator.interpolator = interpolator
+        animator.addUpdateListener { updateAlpha(animator.animatedValue as Float, sourceId) }
+        animator.start()
+        animators[sourceId] = animator
+    }
+
+    fun reset() {
+        alphas.clear()
+        animators.forEach { it.value.cancel() }
+        animators.clear()
+        applyAlphaToView()
+    }
+
+    private fun updateAlpha(alpha: Float, sourceId: Int) {
+        alphas[sourceId] = alpha
+        applyAlphaToView()
+    }
+
+    private fun applyAlphaToView() {
+        val minAlpha = getMinAlpha()
+        view.visibility = if (minAlpha != 0f) View.VISIBLE else View.INVISIBLE
+        view.alpha = minAlpha
+    }
+
+    private fun getMinAlpha() = alphas.minOfOrNull { it.value } ?: initialAlpha
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java
index 730ecde..cd6ccb5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.phone.fragment.dagger;
 
 import android.view.View;
+import android.view.ViewStub;
 
 import com.android.systemui.R;
 import com.android.systemui.battery.BatteryMeterView;
@@ -26,20 +27,24 @@
 import com.android.systemui.statusbar.phone.PhoneStatusBarView;
 import com.android.systemui.statusbar.phone.PhoneStatusBarViewController;
 import com.android.systemui.statusbar.phone.StatusBarBoundsProvider;
+import com.android.systemui.statusbar.phone.StatusBarLocation;
+import com.android.systemui.statusbar.phone.SystemBarAttributesListener;
 import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment;
 import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer;
 import com.android.systemui.statusbar.policy.Clock;
 import com.android.systemui.statusbar.window.StatusBarWindowController;
 
+import dagger.Binds;
+import dagger.Module;
+import dagger.Provides;
+import dagger.multibindings.IntoSet;
+import dagger.multibindings.Multibinds;
+
 import java.util.Optional;
 import java.util.Set;
 
 import javax.inject.Named;
 
-import dagger.Module;
-import dagger.Provides;
-import dagger.multibindings.Multibinds;
-
 /** Dagger module for {@link StatusBarFragmentComponent}. */
 @Module
 public interface StatusBarFragmentModule {
@@ -69,6 +74,13 @@
     /** */
     @Provides
     @StatusBarFragmentScope
+    static StatusBarLocation getStatusBarLocation() {
+        return StatusBarLocation.HOME;
+    }
+
+    /** */
+    @Provides
+    @StatusBarFragmentScope
     @Named(START_SIDE_CONTENT)
     static View startSideContent(@RootView PhoneStatusBarView view) {
         return view.findViewById(R.id.status_bar_start_side_content);
@@ -95,7 +107,7 @@
     @StatusBarFragmentScope
     @Named(OPERATOR_NAME_VIEW)
     static View provideOperatorNameView(@RootView PhoneStatusBarView view) {
-        return view.findViewById(R.id.operator_name);
+        return ((ViewStub) view.findViewById(R.id.operator_name_stub)).inflate();
     }
 
     /** */
@@ -151,4 +163,10 @@
     /** */
     @Multibinds
     Set<StatusBarBoundsProvider.BoundsChangeListener> boundsChangeListeners();
+
+    /** */
+    @Binds
+    @IntoSet
+    StatusBarBoundsProvider.BoundsChangeListener sysBarAttrsListenerAsBoundsListener(
+            SystemBarAttributesListener systemBarAttributesListener);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt
index 4a684d9..29829e4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt
@@ -45,18 +45,6 @@
     fun runNewMobileIconsBackend(): Boolean =
         featureFlags.isEnabled(Flags.NEW_STATUS_BAR_MOBILE_ICONS_BACKEND) || useNewMobileIcons()
 
-    /** True if we should display the wifi icon using the new status bar data pipeline. */
-    fun useNewWifiIcon(): Boolean = featureFlags.isEnabled(Flags.NEW_STATUS_BAR_WIFI_ICON)
-
-    /**
-     * True if we should run the new wifi icon backend to get the logging.
-     *
-     * Does *not* affect whether we render the wifi icon using the new backend data. See
-     * [useNewWifiIcon] for that.
-     */
-    fun runNewWifiIconBackend(): Boolean =
-        featureFlags.isEnabled(Flags.NEW_STATUS_BAR_WIFI_ICON_BACKEND) || useNewWifiIcon()
-
     /**
      * Returns true if we should apply some coloring to the icons that were rendered with the new
      * pipeline to help with debugging.
@@ -71,5 +59,5 @@
      * @return true if this icon is controlled by any of the status bar pipeline flags
      */
     fun isIconControlledByFlags(slotName: String): Boolean =
-        slotName == wifiSlot && useNewWifiIcon() || slotName == mobileSlot && useNewMobileIcons()
+        slotName == wifiSlot || (slotName == mobileSlot && useNewMobileIcons())
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
index 27cc64f..0e99c67 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
@@ -19,12 +19,10 @@
 import android.net.wifi.WifiManager
 import com.android.systemui.CoreStartable
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.LogBufferFactory
 import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.log.table.TableLogBufferFactory
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.CollapsedStatusBarViewModel
-import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.CollapsedStatusBarViewModelImpl
 import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepository
 import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepositoryImpl
 import com.android.systemui.statusbar.pipeline.airplane.ui.viewmodel.AirplaneModeViewModel
@@ -46,6 +44,8 @@
 import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl
 import com.android.systemui.statusbar.pipeline.shared.ui.binder.CollapsedStatusBarViewBinder
 import com.android.systemui.statusbar.pipeline.shared.ui.binder.CollapsedStatusBarViewBinderImpl
+import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.CollapsedStatusBarViewModel
+import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.CollapsedStatusBarViewModelImpl
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.RealWifiRepository
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositorySwitcher
@@ -58,9 +58,9 @@
 import dagger.Provides
 import dagger.multibindings.ClassKey
 import dagger.multibindings.IntoMap
-import kotlinx.coroutines.flow.Flow
 import java.util.function.Supplier
 import javax.inject.Named
+import kotlinx.coroutines.flow.Flow
 
 @Module
 abstract class StatusBarPipelineModule {
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 b3a1c40..051e88f 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
@@ -23,7 +23,7 @@
 import com.android.settingslib.mobile.MobileMappings
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import com.android.systemui.statusbar.pipeline.dagger.MobileInputLog
 import javax.inject.Inject
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
index 74352d29..54948a4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
@@ -250,15 +250,8 @@
             .distinctUntilChanged()
             .onEach { logger.logDefaultMobileIconGroup(it) }
 
-    override fun getRepoForSubId(subId: Int): FullMobileConnectionRepository {
-        if (!isValidSubId(subId)) {
-            throw IllegalArgumentException(
-                "subscriptionId $subId is not in the list of valid subscriptions"
-            )
-        }
-
-        return getOrCreateRepoForSubId(subId)
-    }
+    override fun getRepoForSubId(subId: Int): FullMobileConnectionRepository =
+        getOrCreateRepoForSubId(subId)
 
     private fun getOrCreateRepoForSubId(subId: Int) =
         subIdRepositoryCache[subId]
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLogger.kt
index 7e0c145..cea6654 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLogger.kt
@@ -21,7 +21,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import com.android.systemui.statusbar.pipeline.dagger.MobileViewLog
 import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.LocationBasedMobileViewModel
 import java.io.PrintWriter
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/VerboseMobileViewLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/VerboseMobileViewLogger.kt
index 507549b..f4c5723 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/VerboseMobileViewLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/VerboseMobileViewLogger.kt
@@ -20,7 +20,7 @@
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import com.android.systemui.statusbar.pipeline.dagger.VerboseMobileViewLog
 import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger.Companion.getIdForLogging
 import com.android.systemui.statusbar.pipeline.mobile.ui.model.SignalIconModel
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityInputLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityInputLogger.kt
index cac0ae3..8a4d14e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityInputLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityInputLogger.kt
@@ -20,7 +20,7 @@
 import android.net.NetworkCapabilities
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import com.android.systemui.statusbar.pipeline.dagger.SharedConnectivityInputLog
 import com.android.systemui.statusbar.pipeline.shared.data.model.DefaultConnectionModel
 import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/LoggerHelper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/LoggerHelper.kt
index 328d901..4b9de85 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/LoggerHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/LoggerHelper.kt
@@ -19,7 +19,7 @@
 import android.net.Network
 import android.net.NetworkCapabilities
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 
 /** Helper object for logs that are shared between wifi and mobile. */
 object LoggerHelper {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/model/DefaultConnectionModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/model/DefaultConnectionModel.kt
index 058eda4..9a504c9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/model/DefaultConnectionModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/model/DefaultConnectionModel.kt
@@ -17,7 +17,7 @@
 package com.android.systemui.statusbar.pipeline.shared.data.model
 
 import android.net.NetworkCapabilities
-import com.android.systemui.log.LogMessage
+import com.android.systemui.log.core.LogMessage
 
 /**
  * A model for all of the current default connections(s).
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt
index 1a13404..a1b96dd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt
@@ -101,12 +101,7 @@
         this.binding = bindingCreator.invoke()
     }
 
-    /**
-     * Creates a [StatusBarIconView] that is always in DOT mode and adds it to this view.
-     *
-     * Mostly duplicated from [com.android.systemui.statusbar.StatusBarWifiView] and
-     * [com.android.systemui.statusbar.StatusBarMobileView].
-     */
+    /** Creates a [StatusBarIconView] that is always in DOT mode and adds it to this view. */
     private fun initDotView() {
         // TODO(b/238425913): Could we just have this dot view be part of the layout with a dot
         //  drawable so we don't need to inflate it manually? Would that not work with animations?
@@ -118,7 +113,7 @@
                 it.visibleState = STATE_DOT
             }
 
-        val width = mContext.resources.getDimensionPixelSize(R.dimen.status_bar_icon_size)
+        val width = mContext.resources.getDimensionPixelSize(R.dimen.status_bar_icon_size_sp)
         val lp = LayoutParams(width, width)
         lp.gravity = Gravity.CENTER_VERTICAL or Gravity.START
         addView(dotView, lp)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt
index a4fbc2c..a57be66 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt
@@ -96,7 +96,7 @@
             networkId = DEMO_NET_ID,
             isValidated = validated ?: true,
             level = level ?: 0,
-            ssid = ssid,
+            ssid = ssid ?: DEMO_NET_SSID,
 
             // These fields below aren't supported in demo mode, since they aren't needed to satisfy
             // the interface.
@@ -115,5 +115,6 @@
 
     companion object {
         private const val DEMO_NET_ID = 1234
+        private const val DEMO_NET_SSID = "Demo SSID"
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
index 4e52be9..7f35dfb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
@@ -34,6 +34,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.Background
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.log.table.logDiffsForTable
@@ -48,6 +49,7 @@
 import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
 import java.util.concurrent.Executor
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.channels.awaitClose
@@ -60,7 +62,9 @@
 import kotlinx.coroutines.flow.mapLatest
 import kotlinx.coroutines.flow.merge
 import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.onStart
 import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.withContext
 
 /** Real implementation of [WifiRepository]. */
 @Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
@@ -76,8 +80,9 @@
     logger: WifiInputLogger,
     @WifiTableLog wifiTableLogBuffer: TableLogBuffer,
     @Main mainExecutor: Executor,
+    @Background private val bgDispatcher: CoroutineDispatcher,
     @Application scope: CoroutineScope,
-    wifiManager: WifiManager,
+    private val wifiManager: WifiManager,
 ) : RealWifiRepository {
 
     private val wifiStateChangeEvents: Flow<Unit> =
@@ -93,20 +98,25 @@
     // have changed.
     override val isWifiEnabled: StateFlow<Boolean> =
         merge(wifiNetworkChangeEvents, wifiStateChangeEvents)
-            .mapLatest { wifiManager.isWifiEnabled }
+            .onStart { emit(Unit) }
+            .mapLatest { isWifiEnabled() }
             .distinctUntilChanged()
             .logDiffsForTable(
                 wifiTableLogBuffer,
                 columnPrefix = "",
                 columnName = "isEnabled",
-                initialValue = wifiManager.isWifiEnabled,
+                initialValue = false,
             )
             .stateIn(
                 scope = scope,
-                started = SharingStarted.WhileSubscribed(),
-                initialValue = wifiManager.isWifiEnabled,
+                started = SharingStarted.Eagerly,
+                initialValue = false,
             )
 
+    // [WifiManager.isWifiEnabled] is a blocking IPC call, so fetch it in the background.
+    private suspend fun isWifiEnabled(): Boolean =
+        withContext(bgDispatcher) { wifiManager.isWifiEnabled }
+
     override val isWifiDefault: StateFlow<Boolean> =
         connectivityRepository.defaultConnections
             // TODO(b/274493701): Should wifi be considered default if it's carrier merged?
@@ -289,6 +299,7 @@
         private val logger: WifiInputLogger,
         @WifiTableLog private val wifiTableLogBuffer: TableLogBuffer,
         @Main private val mainExecutor: Executor,
+        @Background private val bgDispatcher: CoroutineDispatcher,
         @Application private val scope: CoroutineScope,
     ) {
         fun create(wifiManager: WifiManager): WifiRepositoryImpl {
@@ -299,6 +310,7 @@
                 logger,
                 wifiTableLogBuffer,
                 mainExecutor,
+                bgDispatcher,
                 scope,
                 wifiManager,
             )
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiInputLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiInputLogger.kt
index 4a9ceac..f244376 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiInputLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiInputLogger.kt
@@ -20,7 +20,7 @@
 import android.net.NetworkCapabilities
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import com.android.systemui.statusbar.pipeline.dagger.WifiInputLog
 import com.android.systemui.statusbar.pipeline.shared.LoggerHelper
 import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/WifiUiAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/WifiUiAdapter.kt
index 174298a..6d71823 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/WifiUiAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/WifiUiAdapter.kt
@@ -68,12 +68,7 @@
                 launch {
                     locationViewModel.wifiIcon.collect { wifiIcon ->
                         // Only notify the icon controller if we want to *render* the new icon.
-                        // Note that this flow may still run if
-                        // [statusBarPipelineFlags.runNewWifiIconBackend] is true because we may
-                        // want to get the logging data without rendering.
-                        if (
-                            wifiIcon is WifiIcon.Visible && statusBarPipelineFlags.useNewWifiIcon()
-                        ) {
+                        if (wifiIcon is WifiIcon.Visible) {
                             iconController.setNewWifiIcon()
                         }
                     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
index 3d16591..7df083afc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
@@ -19,6 +19,9 @@
 import android.annotation.Nullable;
 import android.view.View;
 
+import androidx.annotation.NonNull;
+
+import com.android.systemui.Dumpable;
 import com.android.systemui.demomode.DemoMode;
 import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
 
@@ -136,7 +139,7 @@
      * A listener that will be notified whenever a change in battery level or power save mode has
      * occurred.
      */
-    interface BatteryStateChangeCallback {
+    interface BatteryStateChangeCallback extends Dumpable {
 
         default void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) {
         }
@@ -158,6 +161,11 @@
 
         default void onIsBatteryDefenderChanged(boolean isBatteryDefender) {
         }
+
+        @Override
+        default void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
+            pw.println(this);
+        }
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
index e69d86c..d5d8f4d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
@@ -22,6 +22,7 @@
 import static android.os.BatteryManager.EXTRA_PRESENT;
 
 import static com.android.settingslib.fuelgauge.BatterySaverLogging.SAVER_ENABLED_QS;
+import static com.android.systemui.util.DumpUtilsKt.asIndenting;
 
 import android.annotation.WorkerThread;
 import android.content.BroadcastReceiver;
@@ -33,6 +34,7 @@
 import android.os.Handler;
 import android.os.PowerManager;
 import android.os.PowerSaveState;
+import android.util.IndentingPrintWriter;
 import android.util.Log;
 import android.view.View;
 
@@ -157,15 +159,29 @@
     }
 
     @Override
-    public void dump(PrintWriter pw, String[] args) {
-        pw.println("BatteryController state:");
-        pw.print("  mLevel="); pw.println(mLevel);
-        pw.print("  mPluggedIn="); pw.println(mPluggedIn);
-        pw.print("  mCharging="); pw.println(mCharging);
-        pw.print("  mCharged="); pw.println(mCharged);
-        pw.print("  mIsBatteryDefender="); pw.println(mIsBatteryDefender);
-        pw.print("  mPowerSave="); pw.println(mPowerSave);
-        pw.print("  mStateUnknown="); pw.println(mStateUnknown);
+    public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
+        IndentingPrintWriter ipw = asIndenting(pw);
+        ipw.println("BatteryController state:");
+        ipw.increaseIndent();
+        ipw.print("mHasReceivedBattery="); ipw.println(mHasReceivedBattery);
+        ipw.print("mLevel="); ipw.println(mLevel);
+        ipw.print("mPluggedIn="); ipw.println(mPluggedIn);
+        ipw.print("mCharging="); ipw.println(mCharging);
+        ipw.print("mCharged="); ipw.println(mCharged);
+        ipw.print("mIsBatteryDefender="); ipw.println(mIsBatteryDefender);
+        ipw.print("mPowerSave="); ipw.println(mPowerSave);
+        ipw.print("mStateUnknown="); ipw.println(mStateUnknown);
+        ipw.println("Callbacks:------------------");
+        // Since the above lines are already indented, we need to indent twice for the callbacks.
+        ipw.increaseIndent();
+        synchronized (mChangeCallbacks) {
+            final int n = mChangeCallbacks.size();
+            for (int i = 0; i < n; i++) {
+                mChangeCallbacks.get(i).dump(ipw, args);
+            }
+        }
+        ipw.decreaseIndent();
+        ipw.println("------------------");
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
index 6875b52..f994372 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
@@ -374,6 +374,13 @@
 
     @Override
     public void onDensityOrFontScaleChanged() {
+        reloadDimens();
+    }
+
+    private void reloadDimens() {
+        // reset mCachedWidth so the new width would be updated properly when next onMeasure
+        mCachedWidth = -1;
+
         FontSizeUtils.updateFontSize(this, R.dimen.status_bar_clock_size);
         setPaddingRelative(
                 mContext.getResources().getDimensionPixelSize(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerLogger.kt
index 6ba2a81..096ad1f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerLogger.kt
@@ -22,7 +22,7 @@
 import android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_UNLOCKED
 import com.android.internal.R
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel.VERBOSE
+import com.android.systemui.log.core.LogLevel.VERBOSE
 import com.android.systemui.log.dagger.DeviceStateAutoRotationLog
 import javax.inject.Inject
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
index 92a7854..d472c35 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
@@ -343,7 +343,7 @@
             HeadsUpEntry entry = getHeadsUpEntry(key);
             setEntryPinned(entry, false /* isPinned */);
             // maybe it got un sticky
-            entry.updateEntry(false /* updatePostTime */);
+            entry.updateEntry(false /* updatePostTime */, "unpinAll");
 
             // when the user unpinned all of HUNs by moving one HUN, all of HUNs should not stay
             // on the screen.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt
index 06ed1fd..ef07eed 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt
@@ -18,8 +18,8 @@
 
 import com.android.systemui.log.dagger.NotificationHeadsUpLog
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel.INFO
-import com.android.systemui.log.LogLevel.VERBOSE
+import com.android.systemui.log.core.LogLevel.INFO
+import com.android.systemui.log.core.LogLevel.VERBOSE
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.logKey
 import javax.inject.Inject
@@ -66,6 +66,15 @@
         })
     }
 
+    fun logAutoRemoveCanceled(entry: NotificationEntry, reason: String?) {
+        buffer.log(TAG, INFO, {
+            str1 = entry.logKey
+            str2 = reason ?: "unknown"
+        }, {
+            "cancel auto remove of $str1 reason: $str2"
+        })
+    }
+
     fun logRemoveNotification(key: String, releaseImmediately: Boolean) {
         buffer.log(TAG, INFO, {
             str1 = logKey(key)
@@ -89,16 +98,17 @@
             bool1 = alert
             bool2 = hasEntry
         }, {
-            "update notification $str1 alert: $bool1 hasEntry: $bool2"
+            "update notification $str1 alert: $bool1 hasEntry: $bool2 reason: $str2"
         })
     }
 
-    fun logUpdateEntry(entry: NotificationEntry, updatePostTime: Boolean) {
+    fun logUpdateEntry(entry: NotificationEntry, updatePostTime: Boolean, reason: String?) {
         buffer.log(TAG, INFO, {
             str1 = entry.logKey
             bool1 = updatePostTime
+            str2 = reason ?: "unknown"
         }, {
-            "update entry $str1 updatePostTime: $bool1"
+            "update entry $str1 updatePostTime: $bool1 reason: $str2"
         })
     }
 
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 a82646a..710588c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.statusbar.policy;
 
+import static android.hardware.biometrics.BiometricSourceType.FACE;
+
 import android.annotation.NonNull;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -199,6 +201,11 @@
         Trace.endSection();
     }
 
+    private void notifyKeyguardFaceAuthEnabledChanged() {
+        // Copy the list to allow removal during callback.
+        new ArrayList<>(mCallbacks).forEach(Callback::onFaceAuthEnabledChanged);
+    }
+
     private void notifyUnlockedChanged() {
         Trace.beginSection("KeyguardStateController#notifyUnlockedChanged");
         // Copy the list to allow removal during callback.
@@ -419,6 +426,16 @@
         }
 
         @Override
+        public void onBiometricEnrollmentStateChanged(BiometricSourceType biometricSourceType) {
+            if (biometricSourceType == FACE) {
+                // We only care about enrollment state here. Keyguard face auth enabled is just
+                // same as face auth enrolled
+                update(false);
+                notifyKeyguardFaceAuthEnabledChanged();
+            }
+        }
+
+        @Override
         public void onStartedWakingUp() {
             update(false /* updateAlways */);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt
index 22b4c9d..736b145 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.policy
 
+import android.app.ActivityOptions
 import android.app.Notification
 import android.app.PendingIntent
 import android.app.RemoteInput
@@ -275,7 +276,10 @@
                 entry.sbn.instanceId)
 
         try {
-            pendingIntent.send(view.context, 0, intent)
+            val options = ActivityOptions.makeBasic()
+            options.setPendingIntentBackgroundActivityStartMode(
+                    ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED)
+            pendingIntent.send(view.context, 0, intent, null, null, null, options.toBundle())
         } catch (e: PendingIntent.CanceledException) {
             Log.i(TAG, "Unable to send remote input result", e)
             uiEventLogger.logWithInstanceId(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt
index cac5e32..1776e5b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.policy
 
+import android.app.ActivityOptions
 import android.app.Notification
 import android.app.Notification.Action.SEMANTIC_ACTION_MARK_CONVERSATION_AS_PRIORITY
 import android.app.PendingIntent
@@ -491,7 +492,11 @@
             entry.setHasSentReply()
             try {
                 val intent = createRemoteInputIntent(smartReplies, choice)
-                smartReplies.pendingIntent.send(context, 0, intent)
+                val opts = ActivityOptions.makeBasic()
+                opts.setPendingIntentBackgroundActivityStartMode(
+                        ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED)
+                smartReplies.pendingIntent.send(context, 0, intent, /* onFinished */null,
+                        /* handler */ null, /* requiredPermission */ null, opts.toBundle())
             } catch (e: PendingIntent.CanceledException) {
                 Log.w(TAG, "Unable to send smart reply", e)
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
index b1b8341..d35d340 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
@@ -27,11 +27,12 @@
 import com.android.systemui.assist.AssistManager;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.statusbar.CommandQueue;
-
-import javax.inject.Inject;
+import com.android.systemui.statusbar.KeyboardShortcuts;
 
 import dagger.Lazy;
 
+import javax.inject.Inject;
+
 /**
  * Status bar implementation for "large screen" products that mostly present no on-screen nav.
  * Serves as a collection of UI components, rather than showing its own UI.
@@ -78,4 +79,9 @@
                 new Intent(ACTION_SHOW_PIP_MENU).setPackage(mContext.getPackageName()),
                 SYSTEMUI_PERMISSION);
     }
+
+    @Override
+    public void toggleKeyboardShortcutsMenu(int deviceId) {
+        KeyboardShortcuts.show(mContext, deviceId);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationAdapter.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationAdapter.java
index 3362097..fd7c30f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationAdapter.java
@@ -103,6 +103,8 @@
                 if (mPendingIntent != null) {
                     BroadcastOptions options = BroadcastOptions.makeBasic();
                     options.setInteractive(true);
+                    options.setPendingIntentBackgroundActivityStartMode(
+                            BroadcastOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
                     mPendingIntent.send(options.toBundle());
                 }
             } catch (PendingIntent.CanceledException e) {
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 bcf3b0c..24987ab 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java
@@ -240,8 +240,10 @@
                     Insets.of(0, safeTouchRegionHeight, 0, 0));
         }
         lp.providedInsets = new InsetsFrameProvider[] {
-                new InsetsFrameProvider(mInsetsSourceOwner, 0, statusBars()),
-                new InsetsFrameProvider(mInsetsSourceOwner, 0, tappableElement()),
+                new InsetsFrameProvider(mInsetsSourceOwner, 0, statusBars())
+                        .setInsetsSize(getInsets(height)),
+                new InsetsFrameProvider(mInsetsSourceOwner, 0, tappableElement())
+                        .setInsetsSize(getInsets(height)),
                 gestureInsetsProvider
         };
         return lp;
@@ -306,12 +308,37 @@
         mLpChanged.height =
                 state.mIsLaunchAnimationRunning ? ViewGroup.LayoutParams.MATCH_PARENT : mBarHeight;
         for (int rot = Surface.ROTATION_0; rot <= Surface.ROTATION_270; rot++) {
+            int height = SystemBarUtils.getStatusBarHeightForRotation(mContext, rot);
             mLpChanged.paramsForRotation[rot].height =
-                    state.mIsLaunchAnimationRunning ? ViewGroup.LayoutParams.MATCH_PARENT :
-                    SystemBarUtils.getStatusBarHeightForRotation(mContext, rot);
+                    state.mIsLaunchAnimationRunning ? ViewGroup.LayoutParams.MATCH_PARENT : height;
+            // The status bar height could change at runtime if one display has a cutout while
+            // another doesn't (like some foldables). It could also change when using debug cutouts.
+            // So, we need to re-fetch the height and re-apply it to the insets each time to avoid
+            // bugs like b/290300359.
+            InsetsFrameProvider[] providers = mLpChanged.paramsForRotation[rot].providedInsets;
+            if (providers != null) {
+                for (InsetsFrameProvider provider : providers) {
+                    provider.setInsetsSize(getInsets(height));
+                }
+            }
         }
     }
 
+    /**
+     * Get the insets that should be applied to the status bar window given the current status bar
+     * height.
+     *
+     * The status bar window height can sometimes be full-screen (see {@link #applyHeight(State)}.
+     * However, the status bar *insets* should *not* be full-screen, because this would prevent apps
+     * from drawing any content and can cause animations to be cancelled (see b/283958440). Instead,
+     * the status bar insets should always be equal to the space occupied by the actual status bar
+     * content -- setting the insets correctly will prevent window manager from unnecessarily
+     * re-drawing this window and other windows. This method provides the correct insets.
+     */
+    private Insets getInsets(int height) {
+        return Insets.of(0, height, 0, 0);
+    }
+
     private void apply(State state) {
         if (!mIsAttached) {
             return;
diff --git a/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerUI.kt b/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerUI.kt
index 3e1c13c..c1ac800 100644
--- a/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerUI.kt
+++ b/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerUI.kt
@@ -64,9 +64,9 @@
     // These values must only be accessed on the handler.
     private var batteryCapacity = 1.0f
     private var suppressed = false
-    private var inputDeviceId: Int? = null
     private var instanceId: InstanceId? = null
-
+    @VisibleForTesting var inputDeviceId: Int? = null
+      private set
     @VisibleForTesting var instanceIdSequence = InstanceIdSequence(1 shl 13)
 
     fun init() {
@@ -110,10 +110,10 @@
 
     fun updateBatteryState(deviceId: Int, batteryState: BatteryState) {
         handler.post updateBattery@{
+            inputDeviceId = deviceId
             if (batteryState.capacity == batteryCapacity || batteryState.capacity <= 0f)
                 return@updateBattery
 
-            inputDeviceId = deviceId
             batteryCapacity = batteryState.capacity
             debugLog {
                 "Updating notification battery state to $batteryCapacity " +
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt
index 066ac04..a9d2029 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt
@@ -18,7 +18,7 @@
 
 import android.view.View
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 
 /** A logger for temporary view changes -- see [TemporaryViewDisplayController]. */
 open class TemporaryViewLogger<T : TemporaryViewInfo>(
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarLogger.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarLogger.kt
index d55751b..6706873 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarLogger.kt
@@ -18,7 +18,7 @@
 
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import com.android.systemui.temporarydisplay.TemporaryViewLogger
 import com.android.systemui.temporarydisplay.dagger.ChipbarLog
 import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt b/packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt
index dfe748a..324ef4b 100644
--- a/packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt
@@ -18,9 +18,9 @@
 
 import com.android.systemui.log.dagger.ToastLog
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
-import com.android.systemui.log.LogLevel.DEBUG
-import com.android.systemui.log.LogMessage
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.core.LogLevel.DEBUG
+import com.android.systemui.log.core.LogMessage
 import javax.inject.Inject
 
 private const val TAG = "ToastLog"
@@ -58,6 +58,16 @@
         })
     }
 
+    fun logOnSkipToastForInvalidDisplay(packageName: String, token: String, displayId: Int) {
+        log(DEBUG, {
+            str1 = packageName
+            str2 = token
+            int1 = displayId
+        }, {
+            "[$str2] Skip toast for [$str1] scheduled on unavailable display #$int1"
+        })
+    }
+
     private inline fun log(
         logLevel: LogLevel,
         initializer: LogMessage.() -> Unit,
diff --git a/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java b/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
index ed14c8a..ae8128d 100644
--- a/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
+++ b/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
@@ -32,6 +32,7 @@
 import android.os.ServiceManager;
 import android.os.UserHandle;
 import android.util.Log;
+import android.view.Display;
 import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.IAccessibilityManager;
 import android.widget.ToastPresenter;
@@ -115,8 +116,14 @@
             Context context = mContext.createContextAsUser(userHandle, 0);
 
             DisplayManager mDisplayManager = mContext.getSystemService(DisplayManager.class);
-            Context displayContext = context.createDisplayContext(
-                    mDisplayManager.getDisplay(displayId));
+            Display display = mDisplayManager.getDisplay(displayId);
+            if (display == null) {
+                // Display for which this toast was scheduled for is no longer available.
+                mToastLogger.logOnSkipToastForInvalidDisplay(packageName, token.toString(),
+                        displayId);
+                return;
+            }
+            Context displayContext = context.createDisplayContext(display);
             mToast = mToastFactory.createToast(mContext /* sysuiContext */, text, packageName,
                     userHandle.getIdentifier(), mOrientation);
 
diff --git a/packages/SystemUI/src/com/android/systemui/tracing/ProtoTracer.java b/packages/SystemUI/src/com/android/systemui/tracing/ProtoTracer.java
deleted file mode 100644
index b54d156..0000000
--- a/packages/SystemUI/src/com/android/systemui/tracing/ProtoTracer.java
+++ /dev/null
@@ -1,149 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.tracing;
-
-import static com.android.systemui.tracing.nano.SystemUiTraceFileProto.MAGIC_NUMBER_H;
-import static com.android.systemui.tracing.nano.SystemUiTraceFileProto.MAGIC_NUMBER_L;
-
-import android.content.Context;
-import android.os.SystemClock;
-
-import androidx.annotation.NonNull;
-
-import com.android.systemui.Dumpable;
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.shared.tracing.FrameProtoTracer;
-import com.android.systemui.shared.tracing.FrameProtoTracer.ProtoTraceParams;
-import com.android.systemui.shared.tracing.ProtoTraceable;
-import com.android.systemui.tracing.nano.SystemUiTraceEntryProto;
-import com.android.systemui.tracing.nano.SystemUiTraceFileProto;
-import com.android.systemui.tracing.nano.SystemUiTraceProto;
-
-import com.google.protobuf.nano.MessageNano;
-
-import java.io.File;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.Queue;
-
-import javax.inject.Inject;
-
-/**
- * Controller for coordinating winscope proto tracing.
- */
-@SysUISingleton
-public class ProtoTracer implements
-        Dumpable,
-        ProtoTraceParams<
-                MessageNano,
-                SystemUiTraceFileProto,
-                SystemUiTraceEntryProto,
-                SystemUiTraceProto> {
-
-    private static final String TAG = "ProtoTracer";
-    private static final long MAGIC_NUMBER_VALUE = ((long) MAGIC_NUMBER_H << 32) | MAGIC_NUMBER_L;
-
-    private final Context mContext;
-    private final FrameProtoTracer<MessageNano, SystemUiTraceFileProto, SystemUiTraceEntryProto,
-            SystemUiTraceProto> mProtoTracer;
-
-    @Inject
-    public ProtoTracer(Context context, DumpManager dumpManager) {
-        mContext = context;
-        mProtoTracer = new FrameProtoTracer<>(this);
-        dumpManager.registerDumpable(this);
-    }
-
-    @Override
-    public File getTraceFile() {
-        return new File(mContext.getFilesDir(), "sysui_trace.pb");
-    }
-
-    @Override
-    public SystemUiTraceFileProto getEncapsulatingTraceProto() {
-        return new SystemUiTraceFileProto();
-    }
-
-    @Override
-    public SystemUiTraceEntryProto updateBufferProto(SystemUiTraceEntryProto reuseObj,
-            ArrayList<ProtoTraceable<SystemUiTraceProto>> traceables) {
-        SystemUiTraceEntryProto proto = reuseObj != null
-                ? reuseObj
-                : new SystemUiTraceEntryProto();
-        proto.elapsedRealtimeNanos = SystemClock.elapsedRealtimeNanos();
-        proto.systemUi = proto.systemUi != null ? proto.systemUi : new SystemUiTraceProto();
-        for (ProtoTraceable t : traceables) {
-            t.writeToProto(proto.systemUi);
-        }
-        return proto;
-    }
-
-    @Override
-    public byte[] serializeEncapsulatingProto(SystemUiTraceFileProto encapsulatingProto,
-            Queue<SystemUiTraceEntryProto> buffer) {
-        encapsulatingProto.magicNumber = MAGIC_NUMBER_VALUE;
-        encapsulatingProto.entry = buffer.toArray(new SystemUiTraceEntryProto[0]);
-        return MessageNano.toByteArray(encapsulatingProto);
-    }
-
-    @Override
-    public byte[] getProtoBytes(MessageNano proto) {
-        return MessageNano.toByteArray(proto);
-    }
-
-    @Override
-    public int getProtoSize(MessageNano proto) {
-        return proto.getCachedSize();
-    }
-
-    public void start() {
-        mProtoTracer.start();
-    }
-
-    public void stop() {
-        mProtoTracer.stop();
-    }
-
-    public boolean isEnabled() {
-        return mProtoTracer.isEnabled();
-    }
-
-    public void add(ProtoTraceable<SystemUiTraceProto> traceable) {
-        mProtoTracer.add(traceable);
-    }
-
-    public void remove(ProtoTraceable<SystemUiTraceProto> traceable) {
-        mProtoTracer.remove(traceable);
-    }
-
-    public void scheduleFrameUpdate() {
-        mProtoTracer.scheduleFrameUpdate();
-    }
-
-    public void update() {
-        mProtoTracer.update();
-    }
-
-    @Override
-    public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
-        pw.println("ProtoTracer:");
-        pw.print("    "); pw.println("enabled: " + mProtoTracer.isEnabled());
-        pw.print("    "); pw.println("usagePct: " + mProtoTracer.getBufferUsagePct());
-        pw.print("    "); pw.println("file: " + getTraceFile());
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/tracing/sysui_trace.proto b/packages/SystemUI/src/com/android/systemui/tracing/sysui_trace.proto
deleted file mode 100644
index d940a6b..0000000
--- a/packages/SystemUI/src/com/android/systemui/tracing/sysui_trace.proto
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-syntax = "proto2";
-
-package com.android.systemui.tracing;
-
-option java_multiple_files = true;
-
-message SystemUiTraceProto {
-
-    optional EdgeBackGestureHandlerProto edge_back_gesture_handler = 1;
-}
-
-message EdgeBackGestureHandlerProto {
-
-    optional bool allow_gesture = 1;
-}
-
-/* represents a file full of system ui trace entries.
-   Encoded, it should start with 0x9 0x53 0x59 0x53 0x55 0x49 0x54 0x52 0x43 (.SYSUITRC), such
-   that they can be easily identified. */
-message SystemUiTraceFileProto {
-
-    /* constant; MAGIC_NUMBER = (long) MAGIC_NUMBER_H << 32 | MagicNumber.MAGIC_NUMBER_L
-       (this is needed because enums have to be 32 bits and there's no nice way to put 64bit
-        constants into .proto files. */
-    enum MagicNumber {
-        INVALID = 0;
-        MAGIC_NUMBER_L = 0x55535953;  /* SYSU (little-endian ASCII) */
-        MAGIC_NUMBER_H = 0x43525449;  /* ITRC (little-endian ASCII) */
-    }
-
-    optional fixed64 magic_number = 1;  /* Must be the first field, set to value in MagicNumber */
-    repeated SystemUiTraceEntryProto entry = 2;
-}
-
-/* one system ui trace entry. */
-message SystemUiTraceEntryProto {
-    /* required: elapsed realtime in nanos since boot of when this entry was logged */
-    optional fixed64 elapsed_realtime_nanos = 1;
-
-    optional SystemUiTraceProto system_ui = 3;
-}
diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvSysUIComponent.java b/packages/SystemUI/src/com/android/systemui/tv/TvSysUIComponent.java
index 640adcc..82589d3 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/TvSysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/TvSysUIComponent.java
@@ -20,14 +20,14 @@
 import com.android.systemui.dagger.DependencyProvider;
 import com.android.systemui.dagger.SysUIComponent;
 import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.SystemUIBinder;
 import com.android.systemui.dagger.SystemUIModule;
+import com.android.systemui.globalactions.ShutdownUiModule;
+import com.android.systemui.keyguard.dagger.KeyguardModule;
+import com.android.systemui.recents.RecentsModule;
 import com.android.systemui.statusbar.dagger.CentralSurfacesDependenciesModule;
 import com.android.systemui.statusbar.notification.dagger.NotificationsModule;
 import com.android.systemui.statusbar.notification.row.NotificationRowModule;
-
-import com.android.systemui.keyguard.dagger.KeyguardModule;
-import com.android.systemui.recents.RecentsModule;
+import com.android.systemui.wallpapers.dagger.NoopWallpaperModule;
 
 import dagger.Subcomponent;
 
@@ -40,9 +40,11 @@
         DefaultComponentBinder.class,
         DependencyProvider.class,
         KeyguardModule.class,
+        NoopWallpaperModule.class,
         NotificationRowModule.class,
         NotificationsModule.class,
         RecentsModule.class,
+        ShutdownUiModule.class,
         SystemUIModule.class,
         TvSystemUIBinder.class,
         TVSystemUICoreStartableModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
index d9a8e0c..8cf71a0 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
@@ -45,9 +45,10 @@
 import com.android.systemui.settings.dagger.MultiUserUtilsModule;
 import com.android.systemui.shade.NotificationShadeWindowControllerImpl;
 import com.android.systemui.shade.ShadeController;
-import com.android.systemui.shade.ShadeControllerImpl;
+import com.android.systemui.shade.ShadeControllerEmptyImpl;
 import com.android.systemui.shade.ShadeExpansionStateManager;
 import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.KeyboardShortcutsModule;
 import com.android.systemui.statusbar.NotificationListener;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationLockscreenUserManagerImpl;
@@ -96,6 +97,7 @@
                 ReferenceScreenshotModule.class,
                 StatusBarEventsModule.class,
                 VolumeModule.class,
+                KeyboardShortcutsModule.class
         }
 )
 public abstract class TvSystemUIModule {
@@ -138,7 +140,7 @@
     abstract DockManager bindDockManager(DockManagerImpl dockManager);
 
     @Binds
-    abstract ShadeController provideShadeController(ShadeControllerImpl shadeController);
+    abstract ShadeController provideShadeController(ShadeControllerEmptyImpl shadeController);
 
     @SysUISingleton
     @Provides
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt b/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt
index eed7950..cbe4020 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt
@@ -94,7 +94,7 @@
         wakefulnessLifecycle.addObserver(this)
 
         // TODO(b/254878364): remove this call to NPVC.getView()
-        getShadeFoldAnimator().view.repeatWhenAttached {
+        getShadeFoldAnimator().view?.repeatWhenAttached {
             repeatOnLifecycle(Lifecycle.State.STARTED) { listenForDozing(this) }
         }
     }
@@ -161,10 +161,9 @@
             // but we should wait for the initial animation preparations to be drawn
             // (setting initial alpha/translation)
             // TODO(b/254878364): remove this call to NPVC.getView()
-            OneShotPreDrawListener.add(
-                getShadeFoldAnimator().view,
-                onReady
-            )
+            getShadeFoldAnimator().view?.let {
+                OneShotPreDrawListener.add(it, onReady)
+            }
         } else {
             // No animation, call ready callback immediately
             onReady.run()
diff --git a/packages/SystemUI/src/com/android/systemui/usb/UsbContaminantActivity.java b/packages/SystemUI/src/com/android/systemui/usb/UsbContaminantActivity.java
index d10e890..534049b 100644
--- a/packages/SystemUI/src/com/android/systemui/usb/UsbContaminantActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/usb/UsbContaminantActivity.java
@@ -75,7 +75,12 @@
             mLearnMore.setVisibility(View.VISIBLE);
         }
 
-        mEnableUsb.setOnClickListener(this);
+        if (!mUsbPort.supportsEnableContaminantPresenceDetection()) {
+            mEnableUsb.setVisibility(View.GONE);
+        } else {
+            mEnableUsb.setOnClickListener(this);
+        }
+
         mGotIt.setOnClickListener(this);
         mLearnMore.setOnClickListener(this);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/user/data/model/SelectedUserModel.kt b/packages/SystemUI/src/com/android/systemui/user/data/model/SelectedUserModel.kt
new file mode 100644
index 0000000..cefd466
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/user/data/model/SelectedUserModel.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.user.data.model
+
+import android.content.pm.UserInfo
+
+/** A model for the currently selected user. */
+data class SelectedUserModel(
+    /** Information about the user. */
+    val userInfo: UserInfo,
+    /** The current status of the selection. */
+    val selectionStatus: SelectionStatus,
+)
+
+/** The current status of the selection. */
+enum class SelectionStatus {
+    /** This user has started being selected but the selection hasn't completed. */
+    SELECTION_IN_PROGRESS,
+    /** The selection of this user has completed. */
+    SELECTION_COMPLETE,
+}
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 3de75ca..954765c 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
@@ -33,6 +33,8 @@
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR
 import com.android.systemui.settings.UserTracker
+import com.android.systemui.user.data.model.SelectedUserModel
+import com.android.systemui.user.data.model.SelectionStatus
 import com.android.systemui.user.data.model.UserSwitcherSettingsModel
 import com.android.systemui.util.settings.GlobalSettings
 import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
@@ -69,6 +71,9 @@
     /** List of all users on the device. */
     val userInfos: Flow<List<UserInfo>>
 
+    /** Information about the currently-selected user, including [UserInfo] and other details. */
+    val selectedUser: StateFlow<SelectedUserModel>
+
     /** [UserInfo] of the currently-selected user. */
     val selectedUserInfo: Flow<UserInfo>
 
@@ -146,9 +151,6 @@
     private val _userInfos = MutableStateFlow<List<UserInfo>?>(null)
     override val userInfos: Flow<List<UserInfo>> = _userInfos.filterNotNull()
 
-    private val _selectedUserInfo = MutableStateFlow<UserInfo?>(null)
-    override val selectedUserInfo: Flow<UserInfo> = _selectedUserInfo.filterNotNull()
-
     override var mainUserId: Int = UserHandle.USER_NULL
         private set
     override var lastSelectedNonGuestUserId: Int = UserHandle.USER_NULL
@@ -174,12 +176,57 @@
     override var isRefreshUsersPaused: Boolean = false
 
     init {
-        observeSelectedUser()
         if (featureFlags.isEnabled(FACE_AUTH_REFACTOR)) {
             observeUserSwitching()
         }
     }
 
+    override val selectedUser: StateFlow<SelectedUserModel> = run {
+        // Some callbacks don't modify the selection status, so maintain the current value.
+        var currentSelectionStatus = SelectionStatus.SELECTION_COMPLETE
+        conflatedCallbackFlow {
+                fun send(selectionStatus: SelectionStatus) {
+                    currentSelectionStatus = selectionStatus
+                    trySendWithFailureLogging(
+                        SelectedUserModel(tracker.userInfo, selectionStatus),
+                        TAG,
+                    )
+                }
+
+                val callback =
+                    object : UserTracker.Callback {
+                        override fun onUserChanging(newUser: Int, userContext: Context) {
+                            send(SelectionStatus.SELECTION_IN_PROGRESS)
+                        }
+
+                        override fun onUserChanged(newUser: Int, userContext: Context) {
+                            send(SelectionStatus.SELECTION_COMPLETE)
+                        }
+
+                        override fun onProfilesChanged(profiles: List<UserInfo>) {
+                            send(currentSelectionStatus)
+                        }
+                    }
+
+                tracker.addCallback(callback, mainDispatcher.asExecutor())
+                send(currentSelectionStatus)
+
+                awaitClose { tracker.removeCallback(callback) }
+            }
+            .onEach {
+                if (!it.userInfo.isGuest) {
+                    lastSelectedNonGuestUserId = it.userInfo.id
+                }
+            }
+            .stateIn(
+                applicationScope,
+                SharingStarted.Eagerly,
+                initialValue = SelectedUserModel(tracker.userInfo, currentSelectionStatus)
+            )
+    }
+
+    override val selectedUserInfo: Flow<UserInfo> = selectedUser.map { it.userInfo }
+
     override fun refreshUsers() {
         applicationScope.launch {
             val result = withContext(backgroundDispatcher) { manager.aliveUsers }
@@ -201,7 +248,7 @@
     }
 
     override fun getSelectedUserInfo(): UserInfo {
-        return checkNotNull(_selectedUserInfo.value)
+        return selectedUser.value.userInfo
     }
 
     override fun isSimpleUserSwitcher(): Boolean {
@@ -234,38 +281,6 @@
             .launchIn(applicationScope)
     }
 
-    private fun observeSelectedUser() {
-        conflatedCallbackFlow {
-                fun send() {
-                    trySendWithFailureLogging(tracker.userInfo, TAG)
-                }
-
-                val callback =
-                    object : UserTracker.Callback {
-                        override fun onUserChanging(newUser: Int, userContext: Context) {
-                            send()
-                        }
-
-                        override fun onProfilesChanged(profiles: List<UserInfo>) {
-                            send()
-                        }
-                    }
-
-                tracker.addCallback(callback, mainDispatcher.asExecutor())
-                send()
-
-                awaitClose { tracker.removeCallback(callback) }
-            }
-            .onEach {
-                if (!it.isGuest) {
-                    lastSelectedNonGuestUserId = it.id
-                }
-
-                _selectedUserInfo.value = it
-            }
-            .launchIn(applicationScope)
-    }
-
     private suspend fun getSettings(): UserSwitcherSettingsModel {
         return withContext(backgroundDispatcher) {
             val isSimpleUserSwitcher =
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
index a487f53..4d506f0 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
@@ -27,6 +27,7 @@
 import android.graphics.drawable.BitmapDrawable
 import android.graphics.drawable.Drawable
 import android.graphics.drawable.Icon
+import android.os.Process
 import android.os.RemoteException
 import android.os.UserHandle
 import android.os.UserManager
@@ -334,6 +335,7 @@
                 onBroadcastReceived(intent, previousSelectedUser)
             }
             .launchIn(applicationScope)
+        restartSecondaryService(repository.getSelectedUserInfo().id)
         keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback)
     }
 
@@ -530,6 +532,12 @@
         }
     }
 
+    /** Returns the ID of the currently-selected user. */
+    @UserIdInt
+    fun getSelectedUserId(): Int {
+        return repository.getSelectedUserInfo().id
+    }
+
     private fun showDialog(request: ShowDialogRequestModel) {
         _dialogShowRequests.value = request
     }
@@ -646,7 +654,7 @@
 
         // Connect to the new secondary user's service (purely to ensure that a persistent
         // SystemUI application is created for that user)
-        if (userId != UserHandle.USER_SYSTEM) {
+        if (userId != Process.myUserHandle().identifier) {
             applicationContext.startServiceAsUser(
                 intent,
                 UserHandle.of(userId),
@@ -772,17 +780,16 @@
         }
 
         // TODO(b/246631653): cache the bitmaps to avoid the background work to fetch them.
-        val userIcon = withContext(backgroundDispatcher) {
-            manager.getUserIcon(userId)
-                ?.let { bitmap ->
+        val userIcon =
+            withContext(backgroundDispatcher) {
+                manager.getUserIcon(userId)?.let { bitmap ->
                     val iconSize =
-                        applicationContext
-                            .resources
-                            .getDimensionPixelSize(R.dimen.bouncer_user_switcher_icon_size)
+                        applicationContext.resources.getDimensionPixelSize(
+                            R.dimen.bouncer_user_switcher_icon_size
+                        )
                     Icon.scaleDownIfNecessary(bitmap, iconSize, iconSize)
                 }
-        }
-
+            }
 
         if (userIcon != null) {
             return BitmapDrawable(userIcon)
diff --git a/packages/SystemUI/src/com/android/systemui/util/WallpaperController.kt b/packages/SystemUI/src/com/android/systemui/util/WallpaperController.kt
index db2aca8..65a0218 100644
--- a/packages/SystemUI/src/com/android/systemui/util/WallpaperController.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/WallpaperController.kt
@@ -16,32 +16,34 @@
 
 package com.android.systemui.util
 
-import android.app.WallpaperInfo
 import android.app.WallpaperManager
 import android.util.Log
 import android.view.View
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.wallpapers.data.repository.WallpaperRepository
 import javax.inject.Inject
 import kotlin.math.max
 
 private const val TAG = "WallpaperController"
 
+/**
+ * Controller for wallpaper-related logic.
+ *
+ * Note: New logic should be added to [WallpaperRepository], not this class.
+ */
 @SysUISingleton
-class WallpaperController @Inject constructor(private val wallpaperManager: WallpaperManager) {
+class WallpaperController @Inject constructor(
+    private val wallpaperManager: WallpaperManager,
+    private val wallpaperRepository: WallpaperRepository,
+) {
 
     var rootView: View? = null
 
     private var notificationShadeZoomOut: Float = 0f
     private var unfoldTransitionZoomOut: Float = 0f
 
-    private var wallpaperInfo: WallpaperInfo? = null
-
-    fun onWallpaperInfoUpdated(wallpaperInfo: WallpaperInfo?) {
-        this.wallpaperInfo = wallpaperInfo
-    }
-
     private val shouldUseDefaultUnfoldTransition: Boolean
-        get() = wallpaperInfo?.shouldUseDefaultUnfoldTransition()
+        get() = wallpaperRepository.wallpaperInfo.value?.shouldUseDefaultUnfoldTransition()
             ?: true
 
     fun setNotificationShadeZoom(zoomOut: Float) {
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt
index d6b3b22..12d7b4d 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt
@@ -19,12 +19,44 @@
 import android.view.View
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.lifecycle.repeatWhenAttached
 import java.util.function.Consumer
+import javax.inject.Inject
 import kotlin.coroutines.CoroutineContext
 import kotlin.coroutines.EmptyCoroutineContext
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
+
+/** A class allowing Java classes to collect on Kotlin flows. */
+@SysUISingleton
+class JavaAdapter
+@Inject
+constructor(
+    @Application private val scope: CoroutineScope,
+) {
+    /**
+     * Collect information for the given [flow], calling [consumer] for each emitted event.
+     *
+     * Important: This will immediately start collection and *never* stop it. This should only be
+     * used by classes that *need* to always be collecting a value and processing it. Whenever
+     * possible, please use [collectFlow] instead; that method will stop the collection when a view
+     * has disappeared, which will ensure that we don't perform unnecessary work.
+     *
+     * Do *not* call this method in a class's constructor. Instead, call it in
+     * [com.android.systemui.CoreStartable.start] or similar method.
+     */
+    fun <T> alwaysCollectFlow(
+        flow: Flow<T>,
+        consumer: Consumer<T>,
+    ): Job {
+        return scope.launch { flow.collect { consumer.accept(it) } }
+    }
+}
 
 /**
  * Collect information for the given [flow], calling [consumer] for each emitted event. Defaults to
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/PackageManagerExt.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/PackageManagerExt.kt
index 891ee0c..e32ed5f 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/PackageManagerExt.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/PackageManagerExt.kt
@@ -16,12 +16,19 @@
 
 package com.android.systemui.util.kotlin
 
+import android.annotation.UserHandleAware
 import android.annotation.WorkerThread
 import android.content.pm.ComponentInfo
 import android.content.pm.PackageManager
 import com.android.systemui.util.Assert
 
+/**
+ * Determines whether a component is actually enabled (not just its default value).
+ *
+ * @throws IllegalArgumentException if the component is not found
+ */
 @WorkerThread
+@UserHandleAware
 fun PackageManager.isComponentActuallyEnabled(componentInfo: ComponentInfo): Boolean {
     Assert.isNotMainThread()
     return when (getComponentEnabledSetting(componentInfo.componentName)) {
diff --git a/packages/SystemUI/src/com/android/systemui/util/wakelock/WakeLockLogger.kt b/packages/SystemUI/src/com/android/systemui/util/wakelock/WakeLockLogger.kt
index 0926800..d69b10f 100644
--- a/packages/SystemUI/src/com/android/systemui/util/wakelock/WakeLockLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/wakelock/WakeLockLogger.kt
@@ -18,7 +18,7 @@
 
 import android.os.PowerManager
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import javax.inject.Inject
 
 class WakeLockLogger @Inject constructor(@WakeLockLog private val buffer: LogBuffer) {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 9362220..87b2697 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -34,6 +34,7 @@
 
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_VOLUME_CONTROL;
 import static com.android.internal.jank.InteractionJankMonitor.Configuration.Builder;
+import static com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION;
 import static com.android.systemui.volume.Events.DISMISS_REASON_POSTURE_CHANGED;
 import static com.android.systemui.volume.Events.DISMISS_REASON_SETTINGS_CLICKED;
 
@@ -82,6 +83,7 @@
 import android.util.SparseBooleanArray;
 import android.view.ContextThemeWrapper;
 import android.view.Gravity;
+import android.view.HapticFeedbackConstants;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.View.AccessibilityDelegate;
@@ -120,6 +122,7 @@
 import com.android.systemui.Prefs;
 import com.android.systemui.R;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.media.dialog.MediaOutputDialogFactory;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.VolumeDialog;
@@ -168,6 +171,13 @@
     /** Volume dialog slider animation. */
     private static final String TYPE_UPDATE = "update";
 
+    /**
+     *  TODO(b/290612381): remove lingering animations or tolerate them
+     *  When false, this will cause this class to not listen to animator events and not record jank
+     *  events. This should never be false in production code, and only is false for unit tests for
+     *  this class. This flag should be true in Scenario/Integration tests.
+     */
+    private final boolean mShouldListenForJank;
     private final int mDialogShowAnimationDurationMs;
     private final int mDialogHideAnimationDurationMs;
     private int mDialogWidth;
@@ -293,6 +303,7 @@
     private final DevicePostureController mDevicePostureController;
     private @DevicePostureController.DevicePostureInt int mDevicePosture;
     private int mOrientation;
+    private final FeatureFlags mFeatureFlags;
 
     public VolumeDialogImpl(
             Context context,
@@ -304,13 +315,18 @@
             VolumePanelFactory volumePanelFactory,
             ActivityStarter activityStarter,
             InteractionJankMonitor interactionJankMonitor,
+            boolean shouldListenForJank,
             CsdWarningDialog.Factory csdWarningDialogFactory,
             DevicePostureController devicePostureController,
             Looper looper,
-            DumpManager dumpManager) {
+            DumpManager dumpManager,
+            FeatureFlags featureFlags) {
+        mFeatureFlags = featureFlags;
         mContext =
                 new ContextThemeWrapper(context, R.style.volume_dialog_theme);
         mHandler = new H(looper);
+
+        mShouldListenForJank = shouldListenForJank;
         mController = volumeDialogController;
         mKeyguard = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
         mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
@@ -1309,12 +1325,14 @@
 
     private void provideTouchFeedbackH(int newRingerMode) {
         VibrationEffect effect = null;
+        int hapticConstant = HapticFeedbackConstants.NO_HAPTICS;
         switch (newRingerMode) {
             case RINGER_MODE_NORMAL:
                 mController.scheduleTouchFeedback();
                 break;
             case RINGER_MODE_SILENT:
                 effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
+                hapticConstant = HapticFeedbackConstants.TOGGLE_OFF;
                 break;
             case RINGER_MODE_VIBRATE:
                 // Feedback handled by onStateChange, for feedback both when user toggles
@@ -1322,8 +1340,11 @@
                 break;
             default:
                 effect = VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK);
+                hapticConstant = HapticFeedbackConstants.TOGGLE_ON;
         }
-        if (effect != null) {
+        if (mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
+            mDialogView.performHapticFeedback(hapticConstant);
+        } else if (effect != null) {
             mController.vibrate(effect);
         }
     }
@@ -1368,7 +1389,10 @@
     }
 
     private Animator.AnimatorListener getJankListener(View v, String type, long timeout) {
-        return new Animator.AnimatorListener() {
+        if (!mShouldListenForJank) {
+            // TODO(b/290612381): temporary fix to prevent null pointers on leftover JankMonitors
+            return null;
+        } else return new Animator.AnimatorListener() {
             @Override
             public void onAnimationStart(@NonNull Animator animation) {
                 if (!v.isAttachedToWindow()) {
@@ -1757,7 +1781,22 @@
                 && mState.ringerModeInternal != -1
                 && mState.ringerModeInternal != state.ringerModeInternal
                 && state.ringerModeInternal == AudioManager.RINGER_MODE_VIBRATE) {
-            mController.vibrate(VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK));
+
+            if (mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
+                if (mShowing) {
+                    // The dialog view is responsible for triggering haptics in the oneway API
+                    mDialogView.performHapticFeedback(HapticFeedbackConstants.TOGGLE_ON);
+                }
+                /*
+                TODO(b/290642122): If the dialog is not showing, we have the case where haptics is
+                enabled by dragging the volume slider of Settings to a value of 0. This must be
+                handled by view Slices in Settings whilst using the performHapticFeedback API.
+                 */
+
+            } else {
+                // Old behavior only active if the oneway API is not used.
+                mController.vibrate(VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK));
+            }
         }
         mState = state;
         mDynamic.clear();
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
index aa4ee54..cc9f3e1 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
@@ -22,6 +22,7 @@
 
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.media.dialog.MediaOutputDialogFactory;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.VolumeDialog;
@@ -61,7 +62,8 @@
             InteractionJankMonitor interactionJankMonitor,
             CsdWarningDialog.Factory csdFactory,
             DevicePostureController devicePostureController,
-            DumpManager dumpManager) {
+            DumpManager dumpManager,
+            FeatureFlags featureFlags) {
         VolumeDialogImpl impl = new VolumeDialogImpl(
                 context,
                 volumeDialogController,
@@ -72,10 +74,12 @@
                 volumePanelFactory,
                 activityStarter,
                 interactionJankMonitor,
+                true, /* should listen for jank */
                 csdFactory,
                 devicePostureController,
                 Looper.getMainLooper(),
-                dumpManager);
+                dumpManager,
+                featureFlags);
         impl.setStreamImportant(AudioManager.STREAM_SYSTEM, false);
         impl.setAutomute(true);
         impl.setSilentMode(false);
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/dagger/NoopWallpaperModule.kt b/packages/SystemUI/src/com/android/systemui/wallpapers/dagger/NoopWallpaperModule.kt
new file mode 100644
index 0000000..baf88b0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/dagger/NoopWallpaperModule.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.wallpapers.dagger
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.wallpapers.data.repository.NoopWallpaperRepository
+import com.android.systemui.wallpapers.data.repository.WallpaperRepository
+import dagger.Binds
+import dagger.Module
+
+@Module
+interface NoopWallpaperModule {
+    @Binds
+    @SysUISingleton
+    fun bindWallpaperRepository(impl: NoopWallpaperRepository): WallpaperRepository
+}
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/dagger/WallpaperModule.kt b/packages/SystemUI/src/com/android/systemui/wallpapers/dagger/WallpaperModule.kt
new file mode 100644
index 0000000..1b89978
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/dagger/WallpaperModule.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.wallpapers.dagger
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.wallpapers.data.repository.WallpaperRepository
+import com.android.systemui.wallpapers.data.repository.WallpaperRepositoryImpl
+import dagger.Binds
+import dagger.Module
+
+@Module
+interface WallpaperModule {
+    @Binds
+    @SysUISingleton
+    fun bindWallpaperRepository(impl: WallpaperRepositoryImpl): WallpaperRepository
+}
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/NoopWallpaperRepository.kt b/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/NoopWallpaperRepository.kt
new file mode 100644
index 0000000..b45b8cd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/NoopWallpaperRepository.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.wallpapers.data.repository
+
+import android.app.WallpaperInfo
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+/**
+ * A no-op implementation of [WallpaperRepository].
+ *
+ * Used for variants of SysUI that do not support wallpaper but require other SysUI classes that
+ * have a wallpaper dependency.
+ */
+@SysUISingleton
+class NoopWallpaperRepository @Inject constructor() : WallpaperRepository {
+    override val wallpaperInfo: StateFlow<WallpaperInfo?> = MutableStateFlow(null).asStateFlow()
+    override val wallpaperSupportsAmbientMode = MutableStateFlow(false).asStateFlow()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperRepository.kt b/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperRepository.kt
new file mode 100644
index 0000000..b8f9583
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperRepository.kt
@@ -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 com.android.systemui.wallpapers.data.repository
+
+import android.app.WallpaperInfo
+import android.app.WallpaperManager
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.os.UserHandle
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.user.data.model.SelectedUserModel
+import com.android.systemui.user.data.model.SelectionStatus
+import com.android.systemui.user.data.repository.UserRepository
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+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.map
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.stateIn
+
+/** A repository storing information about the current wallpaper. */
+interface WallpaperRepository {
+    /** Emits the current user's current wallpaper. */
+    val wallpaperInfo: StateFlow<WallpaperInfo?>
+
+    /** Emits true if the current user's current wallpaper supports ambient mode. */
+    val wallpaperSupportsAmbientMode: StateFlow<Boolean>
+}
+
+@SysUISingleton
+class WallpaperRepositoryImpl
+@Inject
+constructor(
+    @Application scope: CoroutineScope,
+    broadcastDispatcher: BroadcastDispatcher,
+    userRepository: UserRepository,
+    private val wallpaperManager: WallpaperManager,
+    context: Context,
+) : WallpaperRepository {
+    private val deviceSupportsAodWallpaper =
+        context.resources.getBoolean(com.android.internal.R.bool.config_dozeSupportsAodWallpaper)
+
+    private val wallpaperChanged: Flow<Unit> =
+        broadcastDispatcher
+            .broadcastFlow(
+                IntentFilter(Intent.ACTION_WALLPAPER_CHANGED),
+                user = UserHandle.ALL,
+            )
+            // The `combine` defining `wallpaperSupportsAmbientMode` will not run until both of the
+            // input flows emit at least once. Since this flow is an input flow, it needs to emit
+            // when it starts up to ensure that the `combine` will run if the user changes before we
+            // receive a ACTION_WALLPAPER_CHANGED intent.
+            // Note that the `selectedUser` flow does *not* need to emit on start because
+            // [UserRepository.selectedUser] is a state flow which will automatically emit a value
+            // on start.
+            .onStart { emit(Unit) }
+
+    private val selectedUser: Flow<SelectedUserModel> =
+        userRepository.selectedUser
+            // Only update the wallpaper status once the user selection has finished.
+            .filter { it.selectionStatus == SelectionStatus.SELECTION_COMPLETE }
+
+    override val wallpaperInfo: StateFlow<WallpaperInfo?> =
+        if (!wallpaperManager.isWallpaperSupported || !deviceSupportsAodWallpaper) {
+            MutableStateFlow(null).asStateFlow()
+        } else {
+            combine(wallpaperChanged, selectedUser) { _, selectedUser ->
+                    getWallpaper(selectedUser)
+                }
+                .stateIn(
+                    scope,
+                    // Always be listening for wallpaper changes.
+                    SharingStarted.Eagerly,
+                    initialValue = getWallpaper(userRepository.selectedUser.value),
+                )
+        }
+
+    override val wallpaperSupportsAmbientMode: StateFlow<Boolean> =
+        wallpaperInfo
+            .map {
+                // If WallpaperInfo is null, it's ImageWallpaper which never supports ambient mode.
+                it?.supportsAmbientMode() == true
+            }
+            .stateIn(
+                scope,
+                // Always be listening for wallpaper changes.
+                SharingStarted.Eagerly,
+                initialValue = wallpaperInfo.value?.supportsAmbientMode() == true,
+            )
+
+    private fun getWallpaper(selectedUser: SelectedUserModel): WallpaperInfo? {
+        return wallpaperManager.getWallpaperInfoForUser(selectedUser.userInfo.id)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index 5144d19..943e906 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -51,15 +51,11 @@
 import com.android.systemui.notetask.NoteTaskInitializer;
 import com.android.systemui.settings.DisplayTracker;
 import com.android.systemui.settings.UserTracker;
-import com.android.systemui.shared.tracing.ProtoTraceable;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.tracing.ProtoTracer;
-import com.android.systemui.tracing.nano.SystemUiTraceProto;
 import com.android.wm.shell.desktopmode.DesktopMode;
 import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
-import com.android.wm.shell.nano.WmShellTraceProto;
 import com.android.wm.shell.onehanded.OneHanded;
 import com.android.wm.shell.onehanded.OneHandedEventCallback;
 import com.android.wm.shell.onehanded.OneHandedTransitionCallback;
@@ -94,8 +90,7 @@
 @SysUISingleton
 public final class WMShell implements
         CoreStartable,
-        CommandQueue.Callbacks,
-        ProtoTraceable<SystemUiTraceProto> {
+        CommandQueue.Callbacks {
     private static final String TAG = WMShell.class.getName();
     private static final int INVALID_SYSUI_STATE_MASK =
             SYSUI_STATE_DIALOG_SHOWING
@@ -122,7 +117,6 @@
     private final ScreenLifecycle mScreenLifecycle;
     private final SysUiState mSysUiState;
     private final WakefulnessLifecycle mWakefulnessLifecycle;
-    private final ProtoTracer mProtoTracer;
     private final UserTracker mUserTracker;
     private final DisplayTracker mDisplayTracker;
     private final NoteTaskInitializer mNoteTaskInitializer;
@@ -184,7 +178,6 @@
             KeyguardUpdateMonitor keyguardUpdateMonitor,
             ScreenLifecycle screenLifecycle,
             SysUiState sysUiState,
-            ProtoTracer protoTracer,
             WakefulnessLifecycle wakefulnessLifecycle,
             UserTracker userTracker,
             DisplayTracker displayTracker,
@@ -203,7 +196,6 @@
         mOneHandedOptional = oneHandedOptional;
         mDesktopModeOptional = desktopMode;
         mWakefulnessLifecycle = wakefulnessLifecycle;
-        mProtoTracer = protoTracer;
         mUserTracker = userTracker;
         mDisplayTracker = displayTracker;
         mNoteTaskInitializer = noteTaskInitializer;
@@ -223,7 +215,6 @@
         // Subscribe to user changes
         mUserTracker.addCallback(mUserChangedCallback, mContext.getMainExecutor());
 
-        mProtoTracer.add(this);
         mCommandQueue.addCallback(this);
         mPipOptional.ifPresent(this::initPip);
         mSplitScreenOptional.ifPresent(this::initSplitScreen);
@@ -361,12 +352,6 @@
     }
 
     @Override
-    public void writeToProto(SystemUiTraceProto proto) {
-        // Dump to WMShell proto here
-        // TODO: Figure out how we want to synchronize while dumping to proto
-    }
-
-    @Override
     public void dump(PrintWriter pw, String[] args) {
         // Handle commands if provided
         if (mShell.handleCommand(args, pw)) {
diff --git a/packages/SystemUI/tests/AndroidManifest.xml b/packages/SystemUI/tests/AndroidManifest.xml
index 080be6d..03fde9f 100644
--- a/packages/SystemUI/tests/AndroidManifest.xml
+++ b/packages/SystemUI/tests/AndroidManifest.xml
@@ -192,7 +192,7 @@
             android:excludeFromRecents="true" />
 
         <activity
-            android:name="com.android.systemui.notetask.shortcut.LaunchNoteTaskManagedProfileProxyActivity"
+            android:name="com.android.systemui.logcat.LogAccessDialogActivityTest$DialogTestable"
             android:exported="false"
             android:permission="com.android.systemui.permission.SELF"
             android:excludeFromRecents="true" />
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java
index f5cd0ca..d506584 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java
@@ -33,12 +33,15 @@
 import android.app.admin.IKeyguardClient;
 import android.content.ComponentName;
 import android.content.Intent;
+import android.hardware.display.DisplayManager;
+import android.os.Binder;
 import android.os.Handler;
 import android.os.RemoteException;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
 import android.testing.ViewUtils;
+import android.view.Display;
 import android.view.SurfaceControlViewHost;
 import android.view.SurfaceView;
 import android.view.View;
@@ -77,7 +80,7 @@
     private KeyguardSecurityCallback mKeyguardCallback;
     @Mock
     private KeyguardUpdateMonitor mUpdateMonitor;
-    @Mock
+
     private SurfaceControlViewHost.SurfacePackage mSurfacePackage;
 
     @Before
@@ -97,6 +100,11 @@
         when(mKeyguardClient.queryLocalInterface(anyString())).thenReturn(mKeyguardClient);
         when(mKeyguardClient.asBinder()).thenReturn(mKeyguardClient);
 
+        Display display = mContext.getSystemService(DisplayManager.class).getDisplay(
+                Display.DEFAULT_DISPLAY);
+        mSurfacePackage = (new SurfaceControlViewHost(mContext, display,
+                new Binder())).getSurfacePackage();
+
         mTestController = new AdminSecondaryLockScreenController.Factory(
                 mContext, mKeyguardSecurityContainer, mUpdateMonitor, mHandler)
                 .create(mKeyguardCallback);
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
index e9a9f3a..b67f280 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
@@ -18,24 +18,26 @@
 import android.content.BroadcastReceiver
 import android.testing.AndroidTestingRunner
 import android.view.View
-import android.widget.TextView
+import android.view.ViewTreeObserver
+import android.widget.FrameLayout
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
 import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
 import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
+import com.android.systemui.log.LogBuffer
 import com.android.systemui.plugins.ClockAnimations
 import com.android.systemui.plugins.ClockController
 import com.android.systemui.plugins.ClockEvents
-import com.android.systemui.plugins.ClockFaceController
 import com.android.systemui.plugins.ClockFaceConfig
+import com.android.systemui.plugins.ClockFaceController
 import com.android.systemui.plugins.ClockFaceEvents
 import com.android.systemui.plugins.ClockTickRate
-import com.android.systemui.log.LogBuffer
 import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.statusbar.policy.BatteryController
 import com.android.systemui.statusbar.policy.ConfigurationController
@@ -54,6 +56,7 @@
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyBoolean
 import org.mockito.ArgumentMatchers.anyFloat
 import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.Mock
@@ -81,7 +84,13 @@
     @Mock private lateinit var bgExecutor: Executor
     @Mock private lateinit var featureFlags: FeatureFlags
     @Mock private lateinit var smallClockController: ClockFaceController
+    @Mock private lateinit var smallClockView: View
+    @Mock private lateinit var smallClockViewTreeObserver: ViewTreeObserver
+    @Mock private lateinit var smallClockFrame: FrameLayout
+    @Mock private lateinit var smallClockFrameViewTreeObserver: ViewTreeObserver
     @Mock private lateinit var largeClockController: ClockFaceController
+    @Mock private lateinit var largeClockView: View
+    @Mock private lateinit var largeClockViewTreeObserver: ViewTreeObserver
     @Mock private lateinit var smallClockEvents: ClockFaceEvents
     @Mock private lateinit var largeClockEvents: ClockFaceEvents
     @Mock private lateinit var parentView: View
@@ -97,8 +106,12 @@
     fun setUp() {
         whenever(clock.smallClock).thenReturn(smallClockController)
         whenever(clock.largeClock).thenReturn(largeClockController)
-        whenever(smallClockController.view).thenReturn(TextView(context))
-        whenever(largeClockController.view).thenReturn(TextView(context))
+        whenever(smallClockController.view).thenReturn(smallClockView)
+        whenever(smallClockView.parent).thenReturn(smallClockFrame)
+        whenever(smallClockView.viewTreeObserver).thenReturn(smallClockViewTreeObserver)
+        whenever(smallClockFrame.viewTreeObserver).thenReturn(smallClockFrameViewTreeObserver)
+        whenever(largeClockController.view).thenReturn(largeClockView)
+        whenever(largeClockView.viewTreeObserver).thenReturn(largeClockViewTreeObserver)
         whenever(smallClockController.events).thenReturn(smallClockEvents)
         whenever(largeClockController.events).thenReturn(largeClockEvents)
         whenever(clock.events).thenReturn(events)
@@ -118,8 +131,11 @@
                 commandQueue = commandQueue,
                 featureFlags = featureFlags,
                 bouncerRepository = bouncerRepository,
+                configurationRepository = FakeConfigurationRepository(),
             ),
-            KeyguardTransitionInteractor(transitionRepository, TestScope().backgroundScope),
+            KeyguardTransitionInteractorFactory.create(
+                    scope = TestScope().backgroundScope,
+            ).keyguardTransitionInteractor,
             broadcastDispatcher,
             batteryController,
             keyguardUpdateMonitor,
@@ -156,9 +172,8 @@
 
     @Test
     fun themeChanged_verifyClockPaletteUpdated() = runBlocking(IMMEDIATE) {
-        // TODO(b/266103601): delete this test and add more coverage for updateColors()
-        // verify(smallClockEvents).onRegionDarknessChanged(anyBoolean())
-        // verify(largeClockEvents).onRegionDarknessChanged(anyBoolean())
+         verify(smallClockEvents).onRegionDarknessChanged(anyBoolean())
+         verify(largeClockEvents).onRegionDarknessChanged(anyBoolean())
 
         val captor = argumentCaptor<ConfigurationController.ConfigurationListener>()
         verify(configurationController).addCallback(capture(captor))
@@ -298,8 +313,38 @@
         verify(configurationController).removeCallback(any())
         verify(batteryController).removeCallback(any())
         verify(keyguardUpdateMonitor).removeCallback(any())
+        verify(smallClockController.view)
+                .removeOnAttachStateChangeListener(underTest.smallClockOnAttachStateChangeListener)
+        verify(largeClockController.view)
+                .removeOnAttachStateChangeListener(underTest.largeClockOnAttachStateChangeListener)
     }
 
+    @Test
+    fun registerOnAttachStateChangeListener_validate() = runBlocking(IMMEDIATE) {
+        verify(smallClockController.view)
+            .addOnAttachStateChangeListener(underTest.smallClockOnAttachStateChangeListener)
+        verify(largeClockController.view)
+            .addOnAttachStateChangeListener(underTest.largeClockOnAttachStateChangeListener)
+    }
+
+    @Test
+    fun registerAndRemoveOnGlobalLayoutListener_correctly() = runBlocking(IMMEDIATE) {
+        underTest.smallClockOnAttachStateChangeListener!!.onViewAttachedToWindow(smallClockView)
+        verify(smallClockFrame.viewTreeObserver).addOnGlobalLayoutListener(any())
+        underTest.smallClockOnAttachStateChangeListener!!.onViewDetachedFromWindow(smallClockView)
+        verify(smallClockFrame.viewTreeObserver).removeOnGlobalLayoutListener(any())
+    }
+
+    @Test
+    fun registerOnGlobalLayoutListener_RemoveOnAttachStateChangeListener_correctly() =
+        runBlocking(IMMEDIATE) {
+            underTest.smallClockOnAttachStateChangeListener!!
+                .onViewAttachedToWindow(smallClockView)
+            verify(smallClockFrame.viewTreeObserver).addOnGlobalLayoutListener(any())
+            underTest.unregisterListeners()
+            verify(smallClockFrame.viewTreeObserver).removeOnGlobalLayoutListener(any())
+        }
+
     companion object {
         private val IMMEDIATE = Dispatchers.Main.immediate
     }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java
new file mode 100644
index 0000000..ac04bc4
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 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 org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.res.Resources;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.LinearLayout;
+import android.widget.RelativeLayout;
+
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FakeFeatureFlags;
+import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory;
+import com.android.systemui.log.LogBuffer;
+import com.android.systemui.plugins.ClockAnimations;
+import com.android.systemui.plugins.ClockController;
+import com.android.systemui.plugins.ClockEvents;
+import com.android.systemui.plugins.ClockFaceConfig;
+import com.android.systemui.plugins.ClockFaceController;
+import com.android.systemui.plugins.ClockFaceEvents;
+import com.android.systemui.plugins.ClockTickRate;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.shared.clocks.AnimatableClockView;
+import com.android.systemui.shared.clocks.ClockRegistry;
+import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController;
+import com.android.systemui.statusbar.phone.NotificationIconAreaController;
+import com.android.systemui.statusbar.phone.NotificationIconContainer;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.settings.SecureSettings;
+import com.android.systemui.util.time.FakeSystemClock;
+
+import org.junit.Before;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+public class KeyguardClockSwitchControllerBaseTest extends SysuiTestCase {
+
+    @Mock
+    protected KeyguardClockSwitch mView;
+    @Mock
+    protected StatusBarStateController mStatusBarStateController;
+    @Mock
+    protected ClockRegistry mClockRegistry;
+    @Mock
+    KeyguardSliceViewController mKeyguardSliceViewController;
+    @Mock
+    NotificationIconAreaController mNotificationIconAreaController;
+    @Mock
+    LockscreenSmartspaceController mSmartspaceController;
+
+    @Mock
+    Resources mResources;
+    @Mock
+    KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
+    @Mock
+    protected ClockController mClockController;
+    @Mock
+    protected ClockFaceController mLargeClockController;
+    @Mock
+    protected ClockFaceController mSmallClockController;
+    @Mock
+    protected ClockAnimations mClockAnimations;
+    @Mock
+    protected ClockEvents mClockEvents;
+    @Mock
+    protected ClockFaceEvents mClockFaceEvents;
+    @Mock
+    DumpManager mDumpManager;
+    @Mock
+    ClockEventController mClockEventController;
+
+    @Mock
+    protected NotificationIconContainer mNotificationIcons;
+    @Mock
+    protected AnimatableClockView mSmallClockView;
+    @Mock
+    protected AnimatableClockView mLargeClockView;
+    @Mock
+    protected FrameLayout mSmallClockFrame;
+    @Mock
+    protected FrameLayout mLargeClockFrame;
+    @Mock
+    protected SecureSettings mSecureSettings;
+    @Mock
+    protected LogBuffer mLogBuffer;
+
+    protected final View mFakeDateView = (View) (new ViewGroup(mContext) {
+        @Override
+        protected void onLayout(boolean changed, int l, int t, int r, int b) {}
+    });
+    protected final View mFakeWeatherView = new View(mContext);
+    protected final View mFakeSmartspaceView = new View(mContext);
+
+    protected KeyguardClockSwitchController mController;
+    protected View mSliceView;
+    protected LinearLayout mStatusArea;
+    protected FakeExecutor mExecutor;
+    protected FakeFeatureFlags mFakeFeatureFlags;
+    @Captor protected ArgumentCaptor<View.OnAttachStateChangeListener> mAttachCaptor =
+            ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class);
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+
+        when(mView.findViewById(R.id.left_aligned_notification_icon_container))
+                .thenReturn(mNotificationIcons);
+        when(mNotificationIcons.getLayoutParams()).thenReturn(
+                mock(RelativeLayout.LayoutParams.class));
+        when(mView.getContext()).thenReturn(getContext());
+        when(mView.getResources()).thenReturn(mResources);
+        when(mResources.getDimensionPixelSize(R.dimen.keyguard_clock_top_margin))
+                .thenReturn(100);
+        when(mResources.getDimensionPixelSize(R.dimen.keyguard_large_clock_top_margin))
+                .thenReturn(-200);
+        when(mResources.getInteger(R.integer.keyguard_date_weather_view_invisibility))
+                .thenReturn(INVISIBLE);
+
+        when(mView.findViewById(R.id.lockscreen_clock_view_large)).thenReturn(mLargeClockFrame);
+        when(mView.findViewById(R.id.lockscreen_clock_view)).thenReturn(mSmallClockFrame);
+        when(mSmallClockView.getContext()).thenReturn(getContext());
+        when(mLargeClockView.getContext()).thenReturn(getContext());
+
+        when(mView.isAttachedToWindow()).thenReturn(true);
+        when(mSmartspaceController.buildAndConnectDateView(any())).thenReturn(mFakeDateView);
+        when(mSmartspaceController.buildAndConnectWeatherView(any())).thenReturn(mFakeWeatherView);
+        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);
+        mController = new KeyguardClockSwitchController(
+                mView,
+                mStatusBarStateController,
+                mClockRegistry,
+                mKeyguardSliceViewController,
+                mNotificationIconAreaController,
+                mSmartspaceController,
+                mKeyguardUnlockAnimationController,
+                mSecureSettings,
+                mExecutor,
+                mDumpManager,
+                mClockEventController,
+                mLogBuffer,
+                KeyguardInteractorFactory.create(mFakeFeatureFlags).getKeyguardInteractor(),
+                mFakeFeatureFlags
+        );
+
+        when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE);
+        when(mLargeClockController.getView()).thenReturn(mLargeClockView);
+        when(mSmallClockController.getView()).thenReturn(mSmallClockView);
+        when(mClockController.getLargeClock()).thenReturn(mLargeClockController);
+        when(mClockController.getSmallClock()).thenReturn(mSmallClockController);
+        when(mClockController.getEvents()).thenReturn(mClockEvents);
+        when(mSmallClockController.getEvents()).thenReturn(mClockFaceEvents);
+        when(mLargeClockController.getEvents()).thenReturn(mClockFaceEvents);
+        when(mLargeClockController.getAnimations()).thenReturn(mClockAnimations);
+        when(mSmallClockController.getAnimations()).thenReturn(mClockAnimations);
+        when(mClockRegistry.createCurrentClock()).thenReturn(mClockController);
+        when(mClockEventController.getClock()).thenReturn(mClockController);
+        when(mSmallClockController.getConfig())
+                .thenReturn(new ClockFaceConfig(ClockTickRate.PER_MINUTE, false, false));
+        when(mLargeClockController.getConfig())
+                .thenReturn(new ClockFaceConfig(ClockTickRate.PER_MINUTE, false, false));
+
+        mSliceView = new View(getContext());
+        when(mView.findViewById(R.id.keyguard_slice_view)).thenReturn(mSliceView);
+        mStatusArea = new LinearLayout(getContext());
+        when(mView.findViewById(R.id.keyguard_status_area)).thenReturn(mStatusArea);
+    }
+
+    protected void init() {
+        mController.init();
+
+        verify(mView, atLeast(1)).addOnAttachStateChangeListener(mAttachCaptor.capture());
+        mAttachCaptor.getValue().onViewAttachedToWindow(mView);
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
index 9e561ed..e64ef04 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
@@ -21,186 +21,33 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.testing.AndroidTestingRunner;
 import android.view.View;
-import android.view.ViewGroup;
-import android.widget.FrameLayout;
-import android.widget.LinearLayout;
-import android.widget.RelativeLayout;
 
 import androidx.test.filters.SmallTest;
 
-import com.android.systemui.R;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
-import com.android.systemui.log.LogBuffer;
-import com.android.systemui.plugins.ClockAnimations;
-import com.android.systemui.plugins.ClockController;
-import com.android.systemui.plugins.ClockEvents;
 import com.android.systemui.plugins.ClockFaceConfig;
-import com.android.systemui.plugins.ClockFaceController;
-import com.android.systemui.plugins.ClockFaceEvents;
 import com.android.systemui.plugins.ClockTickRate;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.shared.clocks.AnimatableClockView;
 import com.android.systemui.shared.clocks.ClockRegistry;
 import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController;
-import com.android.systemui.statusbar.phone.NotificationIconAreaController;
-import com.android.systemui.statusbar.phone.NotificationIconContainer;
-import com.android.systemui.util.concurrency.FakeExecutor;
-import com.android.systemui.util.settings.SecureSettings;
-import com.android.systemui.util.time.FakeSystemClock;
 
-import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
 import org.mockito.verification.VerificationMode;
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
-public class KeyguardClockSwitchControllerTest extends SysuiTestCase {
-
-    @Mock
-    private KeyguardClockSwitch mView;
-    @Mock
-    private StatusBarStateController mStatusBarStateController;
-    @Mock
-    private ClockRegistry mClockRegistry;
-    @Mock
-    KeyguardSliceViewController mKeyguardSliceViewController;
-    @Mock
-    NotificationIconAreaController mNotificationIconAreaController;
-    @Mock
-    LockscreenSmartspaceController mSmartspaceController;
-
-    @Mock
-    Resources mResources;
-    @Mock
-    KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
-    @Mock
-    private ClockController mClockController;
-    @Mock
-    private ClockFaceController mLargeClockController;
-    @Mock
-    private ClockFaceController mSmallClockController;
-    @Mock
-    private ClockAnimations mClockAnimations;
-    @Mock
-    private ClockEvents mClockEvents;
-    @Mock
-    private ClockFaceEvents mClockFaceEvents;
-    @Mock
-    DumpManager mDumpManager;
-    @Mock
-    ClockEventController mClockEventController;
-
-    @Mock
-    private NotificationIconContainer mNotificationIcons;
-    @Mock
-    private AnimatableClockView mSmallClockView;
-    @Mock
-    private AnimatableClockView mLargeClockView;
-    @Mock
-    private FrameLayout mSmallClockFrame;
-    @Mock
-    private FrameLayout mLargeClockFrame;
-    @Mock
-    private SecureSettings mSecureSettings;
-    @Mock
-    private LogBuffer mLogBuffer;
-
-    private final View mFakeDateView = (View) (new ViewGroup(mContext) {
-        @Override
-        protected void onLayout(boolean changed, int l, int t, int r, int b) {}
-    });
-    private final View mFakeWeatherView = new View(mContext);
-    private final View mFakeSmartspaceView = new View(mContext);
-
-    private KeyguardClockSwitchController mController;
-    private View mSliceView;
-    private FakeExecutor mExecutor;
-
-    @Before
-    public void setup() {
-        MockitoAnnotations.initMocks(this);
-
-        when(mView.findViewById(R.id.left_aligned_notification_icon_container))
-                .thenReturn(mNotificationIcons);
-        when(mNotificationIcons.getLayoutParams()).thenReturn(
-                mock(RelativeLayout.LayoutParams.class));
-        when(mView.getContext()).thenReturn(getContext());
-        when(mView.getResources()).thenReturn(mResources);
-        when(mResources.getDimensionPixelSize(R.dimen.keyguard_clock_top_margin))
-                .thenReturn(100);
-        when(mResources.getDimensionPixelSize(R.dimen.keyguard_large_clock_top_margin))
-                .thenReturn(-200);
-        when(mResources.getInteger(R.integer.keyguard_date_weather_view_invisibility))
-                .thenReturn(View.INVISIBLE);
-
-        when(mView.findViewById(R.id.lockscreen_clock_view_large)).thenReturn(mLargeClockFrame);
-        when(mView.findViewById(R.id.lockscreen_clock_view)).thenReturn(mSmallClockFrame);
-        when(mSmallClockView.getContext()).thenReturn(getContext());
-        when(mLargeClockView.getContext()).thenReturn(getContext());
-
-        when(mView.isAttachedToWindow()).thenReturn(true);
-        when(mSmartspaceController.buildAndConnectDateView(any())).thenReturn(mFakeDateView);
-        when(mSmartspaceController.buildAndConnectWeatherView(any())).thenReturn(mFakeWeatherView);
-        when(mSmartspaceController.buildAndConnectView(any())).thenReturn(mFakeSmartspaceView);
-        mExecutor = new FakeExecutor(new FakeSystemClock());
-        mController = new KeyguardClockSwitchController(
-                mView,
-                mStatusBarStateController,
-                mClockRegistry,
-                mKeyguardSliceViewController,
-                mNotificationIconAreaController,
-                mSmartspaceController,
-                mKeyguardUnlockAnimationController,
-                mSecureSettings,
-                mExecutor,
-                mDumpManager,
-                mClockEventController,
-                mLogBuffer
-        );
-
-        when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE);
-        when(mLargeClockController.getView()).thenReturn(mLargeClockView);
-        when(mSmallClockController.getView()).thenReturn(mSmallClockView);
-        when(mClockController.getLargeClock()).thenReturn(mLargeClockController);
-        when(mClockController.getSmallClock()).thenReturn(mSmallClockController);
-        when(mClockController.getEvents()).thenReturn(mClockEvents);
-        when(mSmallClockController.getEvents()).thenReturn(mClockFaceEvents);
-        when(mLargeClockController.getEvents()).thenReturn(mClockFaceEvents);
-        when(mLargeClockController.getAnimations()).thenReturn(mClockAnimations);
-        when(mSmallClockController.getAnimations()).thenReturn(mClockAnimations);
-        when(mClockRegistry.createCurrentClock()).thenReturn(mClockController);
-        when(mClockEventController.getClock()).thenReturn(mClockController);
-        when(mSmallClockController.getConfig())
-                .thenReturn(new ClockFaceConfig(ClockTickRate.PER_MINUTE, false, false));
-        when(mLargeClockController.getConfig())
-                .thenReturn(new ClockFaceConfig(ClockTickRate.PER_MINUTE, false, false));
-
-        mSliceView = new View(getContext());
-        when(mView.findViewById(R.id.keyguard_slice_view)).thenReturn(mSliceView);
-        when(mView.findViewById(R.id.keyguard_status_area)).thenReturn(
-                new LinearLayout(getContext()));
-    }
-
+public class KeyguardClockSwitchControllerTest extends KeyguardClockSwitchControllerBaseTest {
     @Test
     public void testInit_viewAlreadyAttached() {
         mController.init();
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerWithCoroutinesTest.kt
new file mode 100644
index 0000000..9a1a4e2
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerWithCoroutinesTest.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.keyguard
+
+import android.testing.AndroidTestingRunner
+import android.view.View
+import androidx.test.filters.SmallTest
+import com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.runBlocking
+import org.junit.Assert.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class KeyguardClockSwitchControllerWithCoroutinesTest : KeyguardClockSwitchControllerBaseTest() {
+
+    @Test
+    fun testStatusAreaVisibility_onLockscreenHostedDreamStateChanged() =
+        runBlocking(IMMEDIATE) {
+            // GIVEN starting state for the keyguard clock and wallpaper dream enabled
+            mFakeFeatureFlags.set(LOCKSCREEN_WALLPAPER_DREAM_ENABLED, true)
+            init()
+
+            // WHEN dreaming starts
+            mController.mIsActiveDreamLockscreenHostedCallback.accept(
+                true /* isActiveDreamLockscreenHosted */
+            )
+
+            // THEN the status area is hidden
+            mExecutor.runAllReady()
+            assertEquals(View.INVISIBLE, mStatusArea.visibility)
+
+            // WHEN dreaming stops
+            mController.mIsActiveDreamLockscreenHostedCallback.accept(
+                false /* isActiveDreamLockscreenHosted */
+            )
+            mExecutor.runAllReady()
+
+            // THEN status area view is visible
+            assertEquals(View.VISIBLE, mStatusArea.visibility)
+        }
+
+    companion object {
+        private val IMMEDIATE = Dispatchers.Main.immediate
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
index 061340e..65c677e 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
@@ -16,6 +16,7 @@
 
 package com.android.keyguard;
 
+import static android.view.View.INVISIBLE;
 import static android.view.View.VISIBLE;
 
 import static com.android.keyguard.KeyguardClockSwitch.LARGE;
@@ -192,6 +193,7 @@
         assertThat(mLargeClockFrame.getAlpha()).isEqualTo(1);
         assertThat(mLargeClockFrame.getVisibility()).isEqualTo(VISIBLE);
         assertThat(mSmallClockFrame.getAlpha()).isEqualTo(0);
+        assertThat(mSmallClockFrame.getVisibility()).isEqualTo(INVISIBLE);
     }
 
     @Test
@@ -201,6 +203,7 @@
         assertThat(mLargeClockFrame.getAlpha()).isEqualTo(1);
         assertThat(mLargeClockFrame.getVisibility()).isEqualTo(VISIBLE);
         assertThat(mSmallClockFrame.getAlpha()).isEqualTo(0);
+        assertThat(mSmallClockFrame.getVisibility()).isEqualTo(INVISIBLE);
     }
 
     @Test
@@ -216,6 +219,7 @@
         // only big clock is removed at switch
         assertThat(mLargeClockFrame.getParent()).isNull();
         assertThat(mLargeClockFrame.getAlpha()).isEqualTo(0);
+        assertThat(mLargeClockFrame.getVisibility()).isEqualTo(INVISIBLE);
     }
 
     @Test
@@ -227,6 +231,7 @@
         // only big clock is removed at switch
         assertThat(mLargeClockFrame.getParent()).isNull();
         assertThat(mLargeClockFrame.getAlpha()).isEqualTo(0);
+        assertThat(mLargeClockFrame.getVisibility()).isEqualTo(INVISIBLE);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
index 71a57c7..cb18229 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
@@ -18,10 +18,12 @@
 
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
+import android.view.View
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.constraintlayout.widget.ConstraintSet
 import androidx.test.filters.SmallTest
 import com.android.internal.util.LatencyTracker
 import com.android.internal.widget.LockPatternUtils
-import com.android.internal.widget.LockPatternView
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.classifier.FalsingCollector
@@ -29,12 +31,19 @@
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags
 import com.android.systemui.statusbar.policy.DevicePostureController
+import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_HALF_OPENED
+import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_OPENED
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.whenever
+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.ArgumentMatchers.anyBoolean
 import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.ArgumentMatchers.anyString
+import org.mockito.Captor
 import org.mockito.Mock
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
@@ -45,7 +54,7 @@
 @RunWith(AndroidTestingRunner::class)
 @TestableLooper.RunWithLooper
 class KeyguardPatternViewControllerTest : SysuiTestCase() {
-  @Mock private lateinit var mKeyguardPatternView: KeyguardPatternView
+   private lateinit var mKeyguardPatternView: KeyguardPatternView
 
   @Mock private lateinit var mKeyguardUpdateMonitor: KeyguardUpdateMonitor
 
@@ -63,54 +72,98 @@
   @Mock
   private lateinit var mKeyguardMessageAreaControllerFactory: KeyguardMessageAreaController.Factory
 
-  @Mock private lateinit var mKeyguardMessageArea: BouncerKeyguardMessageArea
-
   @Mock
   private lateinit var mKeyguardMessageAreaController:
       KeyguardMessageAreaController<BouncerKeyguardMessageArea>
 
-  @Mock private lateinit var mLockPatternView: LockPatternView
-
   @Mock private lateinit var mPostureController: DevicePostureController
 
   private lateinit var mKeyguardPatternViewController: KeyguardPatternViewController
   private lateinit var fakeFeatureFlags: FakeFeatureFlags
 
-  @Before
-  fun setup() {
-    MockitoAnnotations.initMocks(this)
-    `when`(mKeyguardPatternView.isAttachedToWindow).thenReturn(true)
-    `when`(
-            mKeyguardPatternView.requireViewById<BouncerKeyguardMessageArea>(
-                R.id.bouncer_message_area))
-        .thenReturn(mKeyguardMessageArea)
-    `when`(mKeyguardPatternView.findViewById<LockPatternView>(R.id.lockPatternView))
-        .thenReturn(mLockPatternView)
-    `when`(mKeyguardMessageAreaControllerFactory.create(mKeyguardMessageArea))
-        .thenReturn(mKeyguardMessageAreaController)
-    `when`(mKeyguardPatternView.resources).thenReturn(context.resources)
-    fakeFeatureFlags = FakeFeatureFlags()
-    fakeFeatureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, false)
-    mKeyguardPatternViewController =
-        KeyguardPatternViewController(
-            mKeyguardPatternView,
-            mKeyguardUpdateMonitor,
-            mSecurityMode,
-            mLockPatternUtils,
-            mKeyguardSecurityCallback,
-            mLatencyTracker,
-            mFalsingCollector,
-            mEmergencyButtonController,
-            mKeyguardMessageAreaControllerFactory,
-            mPostureController,
-            fakeFeatureFlags)
-  }
+  @Captor
+  lateinit var postureCallbackCaptor: ArgumentCaptor<DevicePostureController.Callback>
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        whenever(mKeyguardMessageAreaControllerFactory.create(any()))
+            .thenReturn(mKeyguardMessageAreaController)
+        fakeFeatureFlags = FakeFeatureFlags()
+        fakeFeatureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, false)
+        mKeyguardPatternView = View.inflate(mContext, R.layout.keyguard_pattern_view, null)
+                as KeyguardPatternView
+
+
+        mKeyguardPatternViewController =
+            KeyguardPatternViewController(
+                mKeyguardPatternView,
+                mKeyguardUpdateMonitor,
+                mSecurityMode,
+                mLockPatternUtils,
+                mKeyguardSecurityCallback,
+                mLatencyTracker,
+                mFalsingCollector,
+                mEmergencyButtonController,
+                mKeyguardMessageAreaControllerFactory,
+                mPostureController,
+                fakeFeatureFlags
+            )
+        mKeyguardPatternView.onAttachedToWindow()
+    }
+
+    @Test
+    fun onViewAttached_deviceHalfFolded_propagatedToPatternView() {
+        overrideResource(R.dimen.half_opened_bouncer_height_ratio, 0.5f)
+        whenever(mPostureController.devicePosture).thenReturn(DEVICE_POSTURE_HALF_OPENED)
+
+        mKeyguardPatternViewController.onViewAttached()
+
+        assertThat(getPatternTopGuideline()).isEqualTo(getHalfOpenedBouncerHeightRatio())
+    }
+
+    @Test
+    fun onDevicePostureChanged_deviceOpened_propagatedToPatternView() {
+        overrideResource(R.dimen.half_opened_bouncer_height_ratio, 0.5f)
+        whenever(mPostureController.devicePosture)
+                .thenReturn(DEVICE_POSTURE_HALF_OPENED)
+
+        mKeyguardPatternViewController.onViewAttached()
+
+        // Verify view begins in posture state DEVICE_POSTURE_HALF_OPENED
+        assertThat(getPatternTopGuideline()).isEqualTo(getHalfOpenedBouncerHeightRatio())
+
+        // Simulate posture change to state DEVICE_POSTURE_OPENED with callback
+        verify(mPostureController).addCallback(postureCallbackCaptor.capture())
+        val postureCallback: DevicePostureController.Callback = postureCallbackCaptor.value
+        postureCallback.onPostureChanged(DEVICE_POSTURE_OPENED)
+
+        // Simulate posture change to same state with callback
+        assertThat(getPatternTopGuideline()).isNotEqualTo(getHalfOpenedBouncerHeightRatio())
+
+        postureCallback.onPostureChanged(DEVICE_POSTURE_OPENED)
+
+        // Verify view is still in posture state DEVICE_POSTURE_OPENED
+        assertThat(getPatternTopGuideline()).isNotEqualTo(getHalfOpenedBouncerHeightRatio())
+    }
+
+    private fun getPatternTopGuideline(): Float {
+        val cs = ConstraintSet()
+        val container =
+            mKeyguardPatternView.findViewById(R.id.pattern_container) as ConstraintLayout
+        cs.clone(container)
+        return cs.getConstraint(R.id.pattern_top_guideline).layout.guidePercent
+    }
+
+    private fun getHalfOpenedBouncerHeightRatio(): Float {
+        return mContext.resources.getFloat(R.dimen.half_opened_bouncer_height_ratio)
+    }
 
   @Test
   fun withFeatureFlagOn_oldMessage_isHidden() {
     fakeFeatureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true)
 
-    mKeyguardPatternViewController.init()
+    mKeyguardPatternViewController.onViewAttached()
 
     verify<KeyguardMessageAreaController<*>>(mKeyguardMessageAreaController).disable()
   }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
index d3b4190..4dc7652 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
@@ -19,6 +19,8 @@
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import android.view.View
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.constraintlayout.widget.ConstraintSet
 import androidx.test.filters.SmallTest
 import com.android.internal.util.LatencyTracker
 import com.android.internal.widget.LockPatternUtils
@@ -30,12 +32,18 @@
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
 import com.android.systemui.statusbar.policy.DevicePostureController
+import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_HALF_OPENED
+import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_OPENED
+import com.android.systemui.util.mockito.whenever
+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.ArgumentMatchers.anyBoolean
 import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.ArgumentMatchers.anyString
+import org.mockito.Captor
 import org.mockito.Mock
 import org.mockito.Mockito
 import org.mockito.Mockito.any
@@ -47,7 +55,10 @@
 @RunWith(AndroidTestingRunner::class)
 @TestableLooper.RunWithLooper
 class KeyguardPinViewControllerTest : SysuiTestCase() {
-    @Mock private lateinit var keyguardPinView: KeyguardPINView
+
+    private lateinit var objectKeyguardPINView: KeyguardPINView
+
+    @Mock private lateinit var mockKeyguardPinView: KeyguardPINView
 
     @Mock private lateinit var keyguardMessageArea: BouncerKeyguardMessageArea
 
@@ -79,57 +90,123 @@
     @Mock lateinit var deleteButton: NumPadButton
     @Mock lateinit var enterButton: View
 
-    lateinit var pinViewController: KeyguardPinViewController
+    @Captor lateinit var postureCallbackCaptor: ArgumentCaptor<DevicePostureController.Callback>
 
     @Before
     fun setup() {
         MockitoAnnotations.initMocks(this)
-        Mockito.`when`(keyguardPinView.requireViewById<View>(R.id.bouncer_message_area))
+        Mockito.`when`(mockKeyguardPinView.requireViewById<View>(R.id.bouncer_message_area))
             .thenReturn(keyguardMessageArea)
         Mockito.`when`(
                 keyguardMessageAreaControllerFactory.create(any(KeyguardMessageArea::class.java))
             )
             .thenReturn(keyguardMessageAreaController)
-        `when`(keyguardPinView.passwordTextViewId).thenReturn(R.id.pinEntry)
-        `when`(keyguardPinView.findViewById<PasswordTextView>(R.id.pinEntry))
+        `when`(mockKeyguardPinView.passwordTextViewId).thenReturn(R.id.pinEntry)
+        `when`(mockKeyguardPinView.findViewById<PasswordTextView>(R.id.pinEntry))
             .thenReturn(passwordTextView)
-        `when`(keyguardPinView.resources).thenReturn(context.resources)
-        `when`(keyguardPinView.findViewById<NumPadButton>(R.id.delete_button))
+        `when`(mockKeyguardPinView.resources).thenReturn(context.resources)
+        `when`(mockKeyguardPinView.findViewById<NumPadButton>(R.id.delete_button))
             .thenReturn(deleteButton)
-        `when`(keyguardPinView.findViewById<View>(R.id.key_enter)).thenReturn(enterButton)
-        pinViewController =
-            KeyguardPinViewController(
-                keyguardPinView,
-                keyguardUpdateMonitor,
-                securityMode,
-                lockPatternUtils,
-                mKeyguardSecurityCallback,
-                keyguardMessageAreaControllerFactory,
-                mLatencyTracker,
-                liftToActivateListener,
-                mEmergencyButtonController,
-                falsingCollector,
-                postureController,
-                featureFlags
-            )
+        `when`(mockKeyguardPinView.findViewById<View>(R.id.key_enter)).thenReturn(enterButton)
+        // For posture tests:
+        `when`(mockKeyguardPinView.buttons).thenReturn(arrayOf())
+        `when`(lockPatternUtils.getPinLength(anyInt())).thenReturn(6)
+
+        objectKeyguardPINView =
+            View.inflate(mContext, R.layout.keyguard_pin_view, null)
+                .findViewById(R.id.keyguard_pin_view) as KeyguardPINView
+    }
+
+    private fun constructPinViewController(
+        mKeyguardPinView: KeyguardPINView
+    ): KeyguardPinViewController {
+        return KeyguardPinViewController(
+            mKeyguardPinView,
+            keyguardUpdateMonitor,
+            securityMode,
+            lockPatternUtils,
+            mKeyguardSecurityCallback,
+            keyguardMessageAreaControllerFactory,
+            mLatencyTracker,
+            liftToActivateListener,
+            mEmergencyButtonController,
+            falsingCollector,
+            postureController,
+            featureFlags
+        )
+    }
+
+    @Test
+    fun onViewAttached_deviceHalfFolded_propagatedToPatternView() {
+        val pinViewController = constructPinViewController(objectKeyguardPINView)
+        overrideResource(R.dimen.half_opened_bouncer_height_ratio, 0.5f)
+        whenever(postureController.devicePosture).thenReturn(DEVICE_POSTURE_HALF_OPENED)
+
+        pinViewController.onViewAttached()
+
+        assertThat(getPinTopGuideline()).isEqualTo(getHalfOpenedBouncerHeightRatio())
+    }
+
+    @Test
+    fun onDevicePostureChanged_deviceOpened_propagatedToPatternView() {
+        val pinViewController = constructPinViewController(objectKeyguardPINView)
+        overrideResource(R.dimen.half_opened_bouncer_height_ratio, 0.5f)
+
+        whenever(postureController.devicePosture).thenReturn(DEVICE_POSTURE_HALF_OPENED)
+        pinViewController.onViewAttached()
+
+        // Verify view begins in posture state DEVICE_POSTURE_HALF_OPENED
+        assertThat(getPinTopGuideline()).isEqualTo(getHalfOpenedBouncerHeightRatio())
+
+        // Simulate posture change to state DEVICE_POSTURE_OPENED with callback
+        verify(postureController).addCallback(postureCallbackCaptor.capture())
+        val postureCallback: DevicePostureController.Callback = postureCallbackCaptor.value
+        postureCallback.onPostureChanged(DEVICE_POSTURE_OPENED)
+
+        // Verify view is now in posture state DEVICE_POSTURE_OPENED
+        assertThat(getPinTopGuideline()).isNotEqualTo(getHalfOpenedBouncerHeightRatio())
+
+        // Simulate posture change to same state with callback
+        postureCallback.onPostureChanged(DEVICE_POSTURE_OPENED)
+
+        // Verify view is still in posture state DEVICE_POSTURE_OPENED
+        assertThat(getPinTopGuideline()).isNotEqualTo(getHalfOpenedBouncerHeightRatio())
+    }
+
+    private fun getPinTopGuideline(): Float {
+        val cs = ConstraintSet()
+        val container = objectKeyguardPINView.findViewById(R.id.pin_container) as ConstraintLayout
+        cs.clone(container)
+        return cs.getConstraint(R.id.pin_pad_top_guideline).layout.guidePercent
+    }
+
+    private fun getHalfOpenedBouncerHeightRatio(): Float {
+        return mContext.resources.getFloat(R.dimen.half_opened_bouncer_height_ratio)
     }
 
     @Test
     fun startAppearAnimation() {
+        val pinViewController = constructPinViewController(mockKeyguardPinView)
+
         pinViewController.startAppearAnimation()
+
         verify(keyguardMessageAreaController)
             .setMessage(context.resources.getString(R.string.keyguard_enter_your_pin), false)
     }
 
     @Test
     fun startAppearAnimation_withExistingMessage() {
+        val pinViewController = constructPinViewController(mockKeyguardPinView)
         Mockito.`when`(keyguardMessageAreaController.message).thenReturn("Unlock to continue.")
+
         pinViewController.startAppearAnimation()
+
         verify(keyguardMessageAreaController, Mockito.never()).setMessage(anyString(), anyBoolean())
     }
 
     @Test
     fun startAppearAnimation_withAutoPinConfirmationFailedPasswordAttemptsLessThan5() {
+        val pinViewController = constructPinViewController(mockKeyguardPinView)
         `when`(featureFlags.isEnabled(Flags.AUTO_PIN_CONFIRMATION)).thenReturn(true)
         `when`(lockPatternUtils.getPinLength(anyInt())).thenReturn(6)
         `when`(lockPatternUtils.isAutoPinConfirmEnabled(anyInt())).thenReturn(true)
@@ -137,6 +214,7 @@
         `when`(passwordTextView.text).thenReturn("")
 
         pinViewController.startAppearAnimation()
+
         verify(deleteButton).visibility = View.INVISIBLE
         verify(enterButton).visibility = View.INVISIBLE
         verify(passwordTextView).setUsePinShapes(true)
@@ -145,6 +223,7 @@
 
     @Test
     fun startAppearAnimation_withAutoPinConfirmationFailedPasswordAttemptsMoreThan5() {
+        val pinViewController = constructPinViewController(mockKeyguardPinView)
         `when`(featureFlags.isEnabled(Flags.AUTO_PIN_CONFIRMATION)).thenReturn(true)
         `when`(lockPatternUtils.getPinLength(anyInt())).thenReturn(6)
         `when`(lockPatternUtils.isAutoPinConfirmEnabled(anyInt())).thenReturn(true)
@@ -152,6 +231,7 @@
         `when`(passwordTextView.text).thenReturn("")
 
         pinViewController.startAppearAnimation()
+
         verify(deleteButton).visibility = View.VISIBLE
         verify(enterButton).visibility = View.VISIBLE
         verify(passwordTextView).setUsePinShapes(true)
@@ -160,7 +240,10 @@
 
     @Test
     fun handleLockout_readsNumberOfErrorAttempts() {
+        val pinViewController = constructPinViewController(mockKeyguardPinView)
+
         pinViewController.handleAttemptLockout(0)
+
         verify(lockPatternUtils).getCurrentFailedPasswordAttempts(anyInt())
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
deleted file mode 100644
index 2f20f76..0000000
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
+++ /dev/null
@@ -1,707 +0,0 @@
-/*
- * 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.
- */
-
-package com.android.keyguard;
-
-import static com.android.keyguard.KeyguardSecurityContainer.MODE_DEFAULT;
-import static com.android.keyguard.KeyguardSecurityContainer.MODE_ONE_HANDED;
-import static com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_VISIBLE;
-
-import static com.google.common.truth.Truth.assertThat;
-
-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.argThat;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.atLeastOnce;
-import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.hardware.biometrics.BiometricOverlayConstants;
-import android.media.AudioManager;
-import android.telephony.TelephonyManager;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-import android.testing.TestableResources;
-import android.view.Gravity;
-import android.view.LayoutInflater;
-import android.view.MotionEvent;
-import android.view.WindowInsetsController;
-import android.widget.FrameLayout;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.UiEventLogger;
-import com.android.internal.widget.LockPatternUtils;
-import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
-import com.android.systemui.R;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.biometrics.SideFpsController;
-import com.android.systemui.biometrics.SideFpsUiRequestSource;
-import com.android.systemui.classifier.FalsingA11yDelegate;
-import com.android.systemui.classifier.FalsingCollector;
-import com.android.systemui.flags.FakeFeatureFlags;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
-import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
-import com.android.systemui.log.SessionTracker;
-import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.statusbar.policy.UserSwitcherController;
-import com.android.systemui.util.settings.GlobalSettings;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.ArgumentMatcher;
-import org.mockito.Captor;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
-
-import java.util.Optional;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper()
-public class KeyguardSecurityContainerControllerTest extends SysuiTestCase {
-    private static final int TARGET_USER_ID = 100;
-    @Rule
-    public MockitoRule mRule = MockitoJUnit.rule();
-    @Mock
-    private KeyguardSecurityContainer mView;
-    @Mock
-    private AdminSecondaryLockScreenController.Factory mAdminSecondaryLockScreenControllerFactory;
-    @Mock
-    private AdminSecondaryLockScreenController mAdminSecondaryLockScreenController;
-    @Mock
-    private LockPatternUtils mLockPatternUtils;
-    @Mock
-    private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
-    @Mock
-    private KeyguardSecurityModel mKeyguardSecurityModel;
-    @Mock
-    private MetricsLogger mMetricsLogger;
-    @Mock
-    private UiEventLogger mUiEventLogger;
-    @Mock
-    private KeyguardStateController mKeyguardStateController;
-    @Mock
-    private KeyguardInputViewController mInputViewController;
-    @Mock
-    private WindowInsetsController mWindowInsetsController;
-    @Mock
-    private KeyguardSecurityViewFlipper mSecurityViewFlipper;
-    @Mock
-    private KeyguardSecurityViewFlipperController mKeyguardSecurityViewFlipperController;
-    @Mock
-    private KeyguardMessageAreaController.Factory mKeyguardMessageAreaControllerFactory;
-    @Mock
-    private KeyguardMessageAreaController mKeyguardMessageAreaController;
-    @Mock
-    private BouncerKeyguardMessageArea mKeyguardMessageArea;
-    @Mock
-    private ConfigurationController mConfigurationController;
-    @Mock
-    private EmergencyButtonController mEmergencyButtonController;
-    @Mock
-    private FalsingCollector mFalsingCollector;
-    @Mock
-    private FalsingManager mFalsingManager;
-    @Mock
-    private GlobalSettings mGlobalSettings;
-    @Mock
-    private FeatureFlags mFeatureFlags;
-    @Mock
-    private UserSwitcherController mUserSwitcherController;
-    @Mock
-    private SessionTracker mSessionTracker;
-    @Mock
-    private KeyguardViewController mKeyguardViewController;
-    @Mock
-    private SideFpsController mSideFpsController;
-    @Mock
-    private KeyguardPasswordViewController mKeyguardPasswordViewControllerMock;
-    @Mock
-    private FalsingA11yDelegate mFalsingA11yDelegate;
-    @Mock
-    private TelephonyManager mTelephonyManager;
-    @Mock
-    private ViewMediatorCallback mViewMediatorCallback;
-    @Mock
-    private AudioManager mAudioManager;
-
-    @Captor
-    private ArgumentCaptor<KeyguardUpdateMonitorCallback> mKeyguardUpdateMonitorCallback;
-    @Captor
-    private ArgumentCaptor<KeyguardSecurityContainer.SwipeListener> mSwipeListenerArgumentCaptor;
-
-    @Captor
-    private ArgumentCaptor<KeyguardSecurityViewFlipperController.OnViewInflatedCallback>
-            mOnViewInflatedCallbackArgumentCaptor;
-
-    private KeyguardSecurityContainerController mKeyguardSecurityContainerController;
-    private KeyguardPasswordViewController mKeyguardPasswordViewController;
-    private KeyguardPasswordView mKeyguardPasswordView;
-    private TestableResources mTestableResources;
-
-    @Before
-    public void setup() {
-        mTestableResources = mContext.getOrCreateTestableResources();
-        mTestableResources.getResources().getConfiguration().orientation =
-                Configuration.ORIENTATION_UNDEFINED;
-
-        when(mView.getContext()).thenReturn(mContext);
-        when(mView.getResources()).thenReturn(mTestableResources.getResources());
-        FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(/* width=  */ 0, /* height= */
-                0);
-        lp.gravity = 0;
-        when(mView.getLayoutParams()).thenReturn(lp);
-        when(mAdminSecondaryLockScreenControllerFactory.create(any(KeyguardSecurityCallback.class)))
-                .thenReturn(mAdminSecondaryLockScreenController);
-        when(mSecurityViewFlipper.getWindowInsetsController()).thenReturn(mWindowInsetsController);
-        mKeyguardPasswordView = spy((KeyguardPasswordView) LayoutInflater.from(mContext).inflate(
-                R.layout.keyguard_password_view, null));
-        when(mKeyguardPasswordView.getRootView()).thenReturn(mSecurityViewFlipper);
-        when(mKeyguardPasswordView.requireViewById(R.id.bouncer_message_area))
-                .thenReturn(mKeyguardMessageArea);
-        when(mKeyguardMessageAreaControllerFactory.create(any(KeyguardMessageArea.class)))
-                .thenReturn(mKeyguardMessageAreaController);
-        when(mKeyguardPasswordView.getWindowInsetsController()).thenReturn(mWindowInsetsController);
-        when(mKeyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(SecurityMode.PIN);
-        when(mKeyguardStateController.canDismissLockScreen()).thenReturn(true);
-        FakeFeatureFlags featureFlags = new FakeFeatureFlags();
-        featureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true);
-
-        mKeyguardPasswordViewController = new KeyguardPasswordViewController(
-                (KeyguardPasswordView) mKeyguardPasswordView, mKeyguardUpdateMonitor,
-                SecurityMode.Password, mLockPatternUtils, null,
-                mKeyguardMessageAreaControllerFactory, null, null, mEmergencyButtonController,
-                null, mock(Resources.class), null, mKeyguardViewController,
-                featureFlags);
-
-        mKeyguardSecurityContainerController = new KeyguardSecurityContainerController(
-                mView, mAdminSecondaryLockScreenControllerFactory, mLockPatternUtils,
-                mKeyguardUpdateMonitor, mKeyguardSecurityModel, mMetricsLogger, mUiEventLogger,
-                mKeyguardStateController, mKeyguardSecurityViewFlipperController,
-                mConfigurationController, mFalsingCollector, mFalsingManager,
-                mUserSwitcherController, mFeatureFlags, mGlobalSettings,
-                mSessionTracker, Optional.of(mSideFpsController), mFalsingA11yDelegate,
-                mTelephonyManager, mViewMediatorCallback, mAudioManager,
-                mock(KeyguardFaceAuthInteractor.class));
-    }
-
-    @Test
-    public void onInitConfiguresViewMode() {
-        mKeyguardSecurityContainerController.onInit();
-        verify(mView).initMode(eq(MODE_DEFAULT), eq(mGlobalSettings), eq(mFalsingManager),
-                eq(mUserSwitcherController),
-                any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class),
-                eq(mFalsingA11yDelegate));
-    }
-
-    @Test
-    public void showSecurityScreen_canInflateAllModes() {
-        SecurityMode[] modes = SecurityMode.values();
-        for (SecurityMode mode : modes) {
-            when(mInputViewController.getSecurityMode()).thenReturn(mode);
-            mKeyguardSecurityContainerController.showSecurityScreen(mode);
-            if (mode == SecurityMode.Invalid) {
-                verify(mKeyguardSecurityViewFlipperController, never()).getSecurityView(
-                        any(SecurityMode.class), any(KeyguardSecurityCallback.class), any(
-                                KeyguardSecurityViewFlipperController.OnViewInflatedCallback.class)
-                );
-            } else {
-                verify(mKeyguardSecurityViewFlipperController).getSecurityView(
-                        eq(mode), any(KeyguardSecurityCallback.class), any(
-                                KeyguardSecurityViewFlipperController.OnViewInflatedCallback.class)
-                );
-            }
-        }
-    }
-
-    @Test
-    public void onResourcesUpdate_callsThroughOnRotationChange() {
-        clearInvocations(mView);
-
-        // Rotation is the same, shouldn't cause an update
-        mKeyguardSecurityContainerController.updateResources();
-        verify(mView, never()).initMode(eq(MODE_DEFAULT), eq(mGlobalSettings), eq(mFalsingManager),
-                eq(mUserSwitcherController),
-                any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class),
-                eq(mFalsingA11yDelegate));
-
-        // Update rotation. Should trigger update
-        mTestableResources.getResources().getConfiguration().orientation =
-                Configuration.ORIENTATION_LANDSCAPE;
-
-        mKeyguardSecurityContainerController.updateResources();
-        verify(mView).initMode(eq(MODE_DEFAULT), eq(mGlobalSettings), eq(mFalsingManager),
-                eq(mUserSwitcherController),
-                any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class),
-                eq(mFalsingA11yDelegate));
-    }
-
-    private void touchDown() {
-        mKeyguardSecurityContainerController.mGlobalTouchListener.onTouchEvent(
-                MotionEvent.obtain(
-                        /* downTime= */0,
-                        /* eventTime= */0,
-                        MotionEvent.ACTION_DOWN,
-                        /* x= */0,
-                        /* y= */0,
-                        /* metaState= */0));
-    }
-
-    @Test
-    public void onInterceptTap_inhibitsFalsingInSidedSecurityMode() {
-
-        when(mView.isTouchOnTheOtherSideOfSecurity(any())).thenReturn(false);
-        touchDown();
-        verify(mFalsingCollector, never()).avoidGesture();
-
-        when(mView.isTouchOnTheOtherSideOfSecurity(any())).thenReturn(true);
-        touchDown();
-        verify(mFalsingCollector).avoidGesture();
-    }
-
-    @Test
-    public void showSecurityScreen_oneHandedMode_flagDisabled_noOneHandedMode() {
-        mTestableResources.addOverride(R.bool.can_use_one_handed_bouncer, false);
-        setupGetSecurityView(SecurityMode.Pattern);
-
-        mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.Pattern);
-        verify(mView).initMode(eq(MODE_DEFAULT), eq(mGlobalSettings), eq(mFalsingManager),
-                eq(mUserSwitcherController),
-                any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class),
-                eq(mFalsingA11yDelegate));
-    }
-
-    @Test
-    public void showSecurityScreen_oneHandedMode_flagEnabled_oneHandedMode() {
-        mTestableResources.addOverride(R.bool.can_use_one_handed_bouncer, true);
-        setupGetSecurityView(SecurityMode.Pattern);
-        verify(mView).initMode(eq(MODE_ONE_HANDED), eq(mGlobalSettings), eq(mFalsingManager),
-                eq(mUserSwitcherController),
-                any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class),
-                eq(mFalsingA11yDelegate));
-    }
-
-    @Test
-    public void showSecurityScreen_twoHandedMode_flagEnabled_noOneHandedMode() {
-        mTestableResources.addOverride(R.bool.can_use_one_handed_bouncer, true);
-        setupGetSecurityView(SecurityMode.Password);
-
-        verify(mView).initMode(eq(MODE_DEFAULT), eq(mGlobalSettings), eq(mFalsingManager),
-                eq(mUserSwitcherController),
-                any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class),
-                eq(mFalsingA11yDelegate));
-    }
-
-    @Test
-    public void addUserSwitcherCallback() {
-        ArgumentCaptor<KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback>
-                captor = ArgumentCaptor.forClass(
-                KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class);
-        setupGetSecurityView(SecurityMode.Password);
-
-        verify(mView).initMode(anyInt(), any(GlobalSettings.class), any(FalsingManager.class),
-                any(UserSwitcherController.class),
-                captor.capture(),
-                eq(mFalsingA11yDelegate));
-        captor.getValue().showUnlockToContinueMessage();
-        getViewControllerImmediately();
-        verify(mKeyguardPasswordViewControllerMock).showMessage(
-                /* message= */ getContext().getString(R.string.keyguard_unlock_to_continue),
-                /* colorState= */ null,
-                /* animated= */ true);
-    }
-
-    @Test
-    public void addUserSwitchCallback() {
-        mKeyguardSecurityContainerController.onViewAttached();
-        verify(mUserSwitcherController)
-                .addUserSwitchCallback(any(UserSwitcherController.UserSwitchCallback.class));
-        mKeyguardSecurityContainerController.onViewDetached();
-        verify(mUserSwitcherController)
-                .removeUserSwitchCallback(any(UserSwitcherController.UserSwitchCallback.class));
-    }
-
-    @Test
-    public void onBouncerVisibilityChanged_resetsScale() {
-        mKeyguardSecurityContainerController.onBouncerVisibilityChanged(false);
-        verify(mView).resetScale();
-    }
-
-    @Test
-    public void showNextSecurityScreenOrFinish_setsSecurityScreenToPinAfterSimPinUnlock() {
-        // GIVEN the current security method is SimPin
-        when(mKeyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(false);
-        when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(TARGET_USER_ID)).thenReturn(false);
-        mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.SimPin);
-
-        // WHEN a request is made from the SimPin screens to show the next security method
-        when(mKeyguardSecurityModel.getSecurityMode(TARGET_USER_ID)).thenReturn(SecurityMode.PIN);
-        mKeyguardSecurityContainerController.showNextSecurityScreenOrFinish(
-                /* authenticated= */true,
-                TARGET_USER_ID,
-                /* bypassSecondaryLockScreen= */true,
-                SecurityMode.SimPin);
-
-        // THEN the next security method of PIN is set, and the keyguard is not marked as done
-
-        verify(mViewMediatorCallback, never()).keyguardDonePending(anyBoolean(), anyInt());
-        verify(mViewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt());
-        assertThat(mKeyguardSecurityContainerController.getCurrentSecurityMode())
-                .isEqualTo(SecurityMode.PIN);
-    }
-
-    @Test
-    public void showNextSecurityScreenOrFinish_DeviceNotSecure() {
-        // GIVEN the current security method is SimPin
-        when(mKeyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(false);
-        when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(TARGET_USER_ID)).thenReturn(false);
-        mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.SimPin);
-
-        // WHEN a request is made from the SimPin screens to show the next security method
-        when(mKeyguardSecurityModel.getSecurityMode(TARGET_USER_ID)).thenReturn(SecurityMode.None);
-        mKeyguardSecurityContainerController.showNextSecurityScreenOrFinish(
-                /* authenticated= */true,
-                TARGET_USER_ID,
-                /* bypassSecondaryLockScreen= */true,
-                SecurityMode.SimPin);
-
-        // THEN the next security method of None will dismiss keyguard.
-        verify(mViewMediatorCallback).keyguardDone(anyBoolean(), anyInt());
-    }
-
-    @Test
-    public void showNextSecurityScreenOrFinish_ignoresCallWhenSecurityMethodHasChanged() {
-        //GIVEN current security mode has been set to PIN
-        mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.PIN);
-
-        //WHEN a request comes from SimPin to dismiss the security screens
-        boolean keyguardDone = mKeyguardSecurityContainerController.showNextSecurityScreenOrFinish(
-                /* authenticated= */true,
-                TARGET_USER_ID,
-                /* bypassSecondaryLockScreen= */true,
-                SecurityMode.SimPin);
-
-        //THEN no action has happened, which will not dismiss the security screens
-        assertThat(keyguardDone).isEqualTo(false);
-        verify(mKeyguardUpdateMonitor, never()).getUserHasTrust(anyInt());
-    }
-
-    @Test
-    public void onSwipeUp_whenFaceDetectionIsNotRunning_initiatesFaceAuth() {
-        KeyguardSecurityContainer.SwipeListener registeredSwipeListener =
-                getRegisteredSwipeListener();
-        when(mKeyguardUpdateMonitor.isFaceDetectionRunning()).thenReturn(false);
-        setupGetSecurityView(SecurityMode.Password);
-
-        registeredSwipeListener.onSwipeUp();
-
-        verify(mKeyguardUpdateMonitor).requestFaceAuth(
-                FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER);
-    }
-
-    @Test
-    public void onSwipeUp_whenFaceDetectionIsRunning_doesNotInitiateFaceAuth() {
-        KeyguardSecurityContainer.SwipeListener registeredSwipeListener =
-                getRegisteredSwipeListener();
-        when(mKeyguardUpdateMonitor.isFaceDetectionRunning()).thenReturn(true);
-
-        registeredSwipeListener.onSwipeUp();
-
-        verify(mKeyguardUpdateMonitor, never())
-                .requestFaceAuth(FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER);
-    }
-
-    @Test
-    public void onSwipeUp_whenFaceDetectionIsTriggered_hidesBouncerMessage() {
-        KeyguardSecurityContainer.SwipeListener registeredSwipeListener =
-                getRegisteredSwipeListener();
-        when(mKeyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER))
-                .thenReturn(true);
-        setupGetSecurityView(SecurityMode.Password);
-
-        clearInvocations(mKeyguardSecurityViewFlipperController);
-        registeredSwipeListener.onSwipeUp();
-        getViewControllerImmediately();
-
-        verify(mKeyguardPasswordViewControllerMock).showMessage(/* message= */
-                null, /* colorState= */ null, /* animated= */ true);
-    }
-
-    @Test
-    public void onSwipeUp_whenFaceDetectionIsNotTriggered_retainsBouncerMessage() {
-        KeyguardSecurityContainer.SwipeListener registeredSwipeListener =
-                getRegisteredSwipeListener();
-        when(mKeyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER))
-                .thenReturn(false);
-        setupGetSecurityView(SecurityMode.Password);
-
-        registeredSwipeListener.onSwipeUp();
-
-        verify(mKeyguardPasswordViewControllerMock, never()).showMessage(/* message= */
-                null, /* colorState= */ null, /* animated= */ true);
-    }
-
-    @Test
-    public void onDensityOrFontScaleChanged() {
-        ArgumentCaptor<ConfigurationController.ConfigurationListener>
-                configurationListenerArgumentCaptor = ArgumentCaptor.forClass(
-                ConfigurationController.ConfigurationListener.class);
-        mKeyguardSecurityContainerController.onViewAttached();
-        verify(mConfigurationController).addCallback(configurationListenerArgumentCaptor.capture());
-        clearInvocations(mKeyguardSecurityViewFlipperController);
-
-        configurationListenerArgumentCaptor.getValue().onDensityOrFontScaleChanged();
-
-        verify(mKeyguardSecurityViewFlipperController).clearViews();
-        verify(mKeyguardSecurityViewFlipperController).asynchronouslyInflateView(
-                eq(SecurityMode.PIN),
-                any(KeyguardSecurityCallback.class),
-                mOnViewInflatedCallbackArgumentCaptor.capture());
-
-        mOnViewInflatedCallbackArgumentCaptor.getValue().onViewInflated(mInputViewController);
-
-        verify(mView).onDensityOrFontScaleChanged();
-    }
-
-    @Test
-    public void onThemeChanged() {
-        ArgumentCaptor<ConfigurationController.ConfigurationListener>
-                configurationListenerArgumentCaptor = ArgumentCaptor.forClass(
-                ConfigurationController.ConfigurationListener.class);
-        mKeyguardSecurityContainerController.onViewAttached();
-        verify(mConfigurationController).addCallback(configurationListenerArgumentCaptor.capture());
-        clearInvocations(mKeyguardSecurityViewFlipperController);
-
-        configurationListenerArgumentCaptor.getValue().onThemeChanged();
-
-        verify(mKeyguardSecurityViewFlipperController).clearViews();
-        verify(mKeyguardSecurityViewFlipperController).asynchronouslyInflateView(
-                eq(SecurityMode.PIN),
-                any(KeyguardSecurityCallback.class),
-                mOnViewInflatedCallbackArgumentCaptor.capture());
-
-        mOnViewInflatedCallbackArgumentCaptor.getValue().onViewInflated(mInputViewController);
-
-        verify(mView).reset();
-        verify(mKeyguardSecurityViewFlipperController).reset();
-        verify(mView).reloadColors();
-    }
-
-    @Test
-    public void onUiModeChanged() {
-        ArgumentCaptor<ConfigurationController.ConfigurationListener>
-                configurationListenerArgumentCaptor = ArgumentCaptor.forClass(
-                ConfigurationController.ConfigurationListener.class);
-        mKeyguardSecurityContainerController.onViewAttached();
-        verify(mConfigurationController).addCallback(configurationListenerArgumentCaptor.capture());
-        clearInvocations(mKeyguardSecurityViewFlipperController);
-
-        configurationListenerArgumentCaptor.getValue().onUiModeChanged();
-
-        verify(mKeyguardSecurityViewFlipperController).clearViews();
-        verify(mKeyguardSecurityViewFlipperController).asynchronouslyInflateView(
-                eq(SecurityMode.PIN),
-                any(KeyguardSecurityCallback.class),
-                mOnViewInflatedCallbackArgumentCaptor.capture());
-
-        mOnViewInflatedCallbackArgumentCaptor.getValue().onViewInflated(mInputViewController);
-
-        verify(mView).reloadColors();
-    }
-
-    @Test
-    public void testHasDismissActions() {
-        assertFalse("Action not set yet", mKeyguardSecurityContainerController.hasDismissActions());
-        mKeyguardSecurityContainerController.setOnDismissAction(mock(
-                        ActivityStarter.OnDismissAction.class),
-                null /* cancelAction */);
-        assertTrue("Action should exist", mKeyguardSecurityContainerController.hasDismissActions());
-    }
-
-    @Test
-    public void testWillRunDismissFromKeyguardIsTrue() {
-        ActivityStarter.OnDismissAction action = mock(ActivityStarter.OnDismissAction.class);
-        when(action.willRunAnimationOnKeyguard()).thenReturn(true);
-        mKeyguardSecurityContainerController.setOnDismissAction(action, null /* cancelAction */);
-
-        mKeyguardSecurityContainerController.finish(false /* strongAuth */, 0 /* currentUser */);
-
-        assertThat(mKeyguardSecurityContainerController.willRunDismissFromKeyguard()).isTrue();
-    }
-
-    @Test
-    public void testWillRunDismissFromKeyguardIsFalse() {
-        ActivityStarter.OnDismissAction action = mock(ActivityStarter.OnDismissAction.class);
-        when(action.willRunAnimationOnKeyguard()).thenReturn(false);
-        mKeyguardSecurityContainerController.setOnDismissAction(action, null /* cancelAction */);
-
-        mKeyguardSecurityContainerController.finish(false /* strongAuth */, 0 /* currentUser */);
-
-        assertThat(mKeyguardSecurityContainerController.willRunDismissFromKeyguard()).isFalse();
-    }
-
-    @Test
-    public void testWillRunDismissFromKeyguardIsFalseWhenNoDismissActionSet() {
-        mKeyguardSecurityContainerController.setOnDismissAction(null /* action */,
-                null /* cancelAction */);
-
-        mKeyguardSecurityContainerController.finish(false /* strongAuth */, 0 /* currentUser */);
-
-        assertThat(mKeyguardSecurityContainerController.willRunDismissFromKeyguard()).isFalse();
-    }
-
-    @Test
-    public void testOnStartingToHide() {
-        mKeyguardSecurityContainerController.onStartingToHide();
-        verify(mKeyguardSecurityViewFlipperController).getSecurityView(any(SecurityMode.class),
-                any(KeyguardSecurityCallback.class),
-                mOnViewInflatedCallbackArgumentCaptor.capture());
-
-        mOnViewInflatedCallbackArgumentCaptor.getValue().onViewInflated(mInputViewController);
-        verify(mInputViewController).onStartingToHide();
-    }
-
-    @Test
-    public void testGravityReappliedOnConfigurationChange() {
-        // Set initial gravity
-        mTestableResources.addOverride(R.integer.keyguard_host_view_gravity,
-                Gravity.CENTER);
-        mTestableResources.addOverride(
-                R.bool.can_use_one_handed_bouncer, false);
-
-        // Kick off the initial pass...
-        mKeyguardSecurityContainerController.onInit();
-        verify(mView).setLayoutParams(any());
-        clearInvocations(mView);
-
-        // Now simulate a config change
-        mTestableResources.addOverride(R.integer.keyguard_host_view_gravity,
-                Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM);
-
-        mKeyguardSecurityContainerController.updateResources();
-        verify(mView).setLayoutParams(any());
-    }
-
-    @Test
-    public void testGravityUsesOneHandGravityWhenApplicable() {
-        mTestableResources.addOverride(
-                R.integer.keyguard_host_view_gravity,
-                Gravity.CENTER);
-        mTestableResources.addOverride(
-                R.integer.keyguard_host_view_one_handed_gravity,
-                Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM);
-
-        // Start disabled.
-        mTestableResources.addOverride(
-                R.bool.can_use_one_handed_bouncer, false);
-
-        mKeyguardSecurityContainerController.onInit();
-        verify(mView).setLayoutParams(argThat(
-                (ArgumentMatcher<FrameLayout.LayoutParams>) argument ->
-                        argument.gravity == Gravity.CENTER));
-        clearInvocations(mView);
-
-        // And enable
-        mTestableResources.addOverride(
-                R.bool.can_use_one_handed_bouncer, true);
-
-        mKeyguardSecurityContainerController.updateResources();
-        verify(mView).setLayoutParams(argThat(
-                (ArgumentMatcher<FrameLayout.LayoutParams>) argument ->
-                        argument.gravity == (Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM)));
-    }
-
-    @Test
-    public void testUpdateKeyguardPositionDelegatesToSecurityContainer() {
-        mKeyguardSecurityContainerController.updateKeyguardPosition(1.0f);
-        verify(mView).updatePositionByTouchX(1.0f);
-    }
-
-    @Test
-    public void testReinflateViewFlipper() {
-        KeyguardSecurityViewFlipperController.OnViewInflatedCallback onViewInflatedCallback =
-                controller -> {
-                };
-        mKeyguardSecurityContainerController.reinflateViewFlipper(onViewInflatedCallback);
-        verify(mKeyguardSecurityViewFlipperController).clearViews();
-        verify(mKeyguardSecurityViewFlipperController).asynchronouslyInflateView(
-                any(SecurityMode.class),
-                any(KeyguardSecurityCallback.class), eq(onViewInflatedCallback));
-    }
-
-    @Test
-    public void testSideFpsControllerShow() {
-        mKeyguardSecurityContainerController.updateSideFpsVisibility(/* isVisible= */ true);
-        verify(mSideFpsController).show(
-                SideFpsUiRequestSource.PRIMARY_BOUNCER,
-                BiometricOverlayConstants.REASON_AUTH_KEYGUARD);
-    }
-
-    @Test
-    public void testSideFpsControllerHide() {
-        mKeyguardSecurityContainerController.updateSideFpsVisibility(/* isVisible= */ false);
-        verify(mSideFpsController).hide(SideFpsUiRequestSource.PRIMARY_BOUNCER);
-    }
-
-    @Test
-    public void setExpansion_setsAlpha() {
-        mKeyguardSecurityContainerController.setExpansion(EXPANSION_VISIBLE);
-
-        verify(mView).setAlpha(1f);
-        verify(mView).setTranslationY(0f);
-    }
-
-    private KeyguardSecurityContainer.SwipeListener getRegisteredSwipeListener() {
-        mKeyguardSecurityContainerController.onViewAttached();
-        verify(mView).setSwipeListener(mSwipeListenerArgumentCaptor.capture());
-        return mSwipeListenerArgumentCaptor.getValue();
-    }
-
-    private void setupGetSecurityView(SecurityMode securityMode) {
-        mKeyguardSecurityContainerController.showSecurityScreen(securityMode);
-        getViewControllerImmediately();
-    }
-
-    private void getViewControllerImmediately() {
-        verify(mKeyguardSecurityViewFlipperController, atLeastOnce()).getSecurityView(
-                any(SecurityMode.class), any(),
-                mOnViewInflatedCallbackArgumentCaptor.capture());
-        mOnViewInflatedCallbackArgumentCaptor.getValue().onViewInflated(
-                (KeyguardInputViewController) mKeyguardPasswordViewControllerMock);
-
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
new file mode 100644
index 0000000..d447174
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
@@ -0,0 +1,810 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.keyguard
+
+import android.content.res.Configuration
+import android.hardware.biometrics.BiometricOverlayConstants
+import android.media.AudioManager
+import android.telephony.TelephonyManager
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import android.testing.TestableResources
+import android.view.Gravity
+import android.view.LayoutInflater
+import android.view.MotionEvent
+import android.view.WindowInsetsController
+import android.widget.FrameLayout
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.MetricsLogger
+import com.android.internal.logging.UiEventLogger
+import com.android.internal.widget.LockPatternUtils
+import com.android.keyguard.KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback
+import com.android.keyguard.KeyguardSecurityModel.SecurityMode
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.SideFpsController
+import com.android.systemui.biometrics.SideFpsUiRequestSource
+import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants
+import com.android.systemui.classifier.FalsingA11yDelegate
+import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.log.SessionTracker
+import com.android.systemui.plugins.ActivityStarter.OnDismissAction
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.statusbar.policy.UserSwitcherController
+import com.android.systemui.user.domain.interactor.UserInteractor
+import com.android.systemui.util.kotlin.JavaAdapter
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argThat
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.settings.GlobalSettings
+import com.google.common.truth.Truth
+import java.util.Optional
+import junit.framework.Assert
+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
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatcher
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.atLeastOnce
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.never
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+class KeyguardSecurityContainerControllerTest : SysuiTestCase() {
+
+    @Mock private lateinit var view: KeyguardSecurityContainer
+    @Mock
+    private lateinit var adminSecondaryLockScreenControllerFactory:
+        AdminSecondaryLockScreenController.Factory
+    @Mock
+    private lateinit var adminSecondaryLockScreenController: AdminSecondaryLockScreenController
+    @Mock private lateinit var lockPatternUtils: LockPatternUtils
+    @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+    @Mock private lateinit var keyguardSecurityModel: KeyguardSecurityModel
+    @Mock private lateinit var metricsLogger: MetricsLogger
+    @Mock private lateinit var uiEventLogger: UiEventLogger
+    @Mock private lateinit var keyguardStateController: KeyguardStateController
+    @Mock private lateinit var inputViewController: KeyguardInputViewController<KeyguardInputView>
+    @Mock private lateinit var windowInsetsController: WindowInsetsController
+    @Mock private lateinit var securityViewFlipper: KeyguardSecurityViewFlipper
+    @Mock private lateinit var viewFlipperController: KeyguardSecurityViewFlipperController
+    @Mock private lateinit var messageAreaControllerFactory: KeyguardMessageAreaController.Factory
+    @Mock private lateinit var keyguardMessageAreaController: KeyguardMessageAreaController<*>
+    @Mock private lateinit var keyguardMessageArea: BouncerKeyguardMessageArea
+    @Mock private lateinit var configurationController: ConfigurationController
+    @Mock private lateinit var emergencyButtonController: EmergencyButtonController
+    @Mock private lateinit var falsingCollector: FalsingCollector
+    @Mock private lateinit var falsingManager: FalsingManager
+    @Mock private lateinit var globalSettings: GlobalSettings
+    @Mock private lateinit var userSwitcherController: UserSwitcherController
+    @Mock private lateinit var sessionTracker: SessionTracker
+    @Mock private lateinit var keyguardViewController: KeyguardViewController
+    @Mock private lateinit var sideFpsController: SideFpsController
+    @Mock private lateinit var keyguardPasswordViewControllerMock: KeyguardPasswordViewController
+    @Mock private lateinit var falsingA11yDelegate: FalsingA11yDelegate
+    @Mock private lateinit var telephonyManager: TelephonyManager
+    @Mock private lateinit var viewMediatorCallback: ViewMediatorCallback
+    @Mock private lateinit var audioManager: AudioManager
+    @Mock private lateinit var userInteractor: UserInteractor
+
+    @Captor
+    private lateinit var swipeListenerArgumentCaptor:
+        ArgumentCaptor<KeyguardSecurityContainer.SwipeListener>
+    @Captor
+    private lateinit var onViewInflatedCallbackArgumentCaptor:
+        ArgumentCaptor<KeyguardSecurityViewFlipperController.OnViewInflatedCallback>
+
+    private lateinit var featureFlags: FakeFeatureFlags
+    private lateinit var keyguardPasswordViewController: KeyguardPasswordViewController
+    private lateinit var keyguardPasswordView: KeyguardPasswordView
+    private lateinit var testableResources: TestableResources
+    private lateinit var sceneTestUtils: SceneTestUtils
+    private lateinit var sceneInteractor: SceneInteractor
+
+    private lateinit var underTest: KeyguardSecurityContainerController
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        testableResources = mContext.getOrCreateTestableResources()
+        testableResources.resources.configuration.orientation = Configuration.ORIENTATION_UNDEFINED
+        whenever(view.context).thenReturn(mContext)
+        whenever(view.resources).thenReturn(testableResources.resources)
+
+        val lp = FrameLayout.LayoutParams(/* width=  */ 0, /* height= */ 0)
+        lp.gravity = 0
+        whenever(view.layoutParams).thenReturn(lp)
+
+        whenever(adminSecondaryLockScreenControllerFactory.create(any()))
+            .thenReturn(adminSecondaryLockScreenController)
+        whenever(securityViewFlipper.windowInsetsController).thenReturn(windowInsetsController)
+        keyguardPasswordView =
+            spy(
+                LayoutInflater.from(mContext).inflate(R.layout.keyguard_password_view, null)
+                    as KeyguardPasswordView
+            )
+        whenever(keyguardPasswordView.rootView).thenReturn(securityViewFlipper)
+        whenever<Any?>(keyguardPasswordView.requireViewById(R.id.bouncer_message_area))
+            .thenReturn(keyguardMessageArea)
+        whenever(messageAreaControllerFactory.create(any()))
+            .thenReturn(keyguardMessageAreaController)
+        whenever(keyguardPasswordView.windowInsetsController).thenReturn(windowInsetsController)
+        whenever(keyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(SecurityMode.PIN)
+        whenever(keyguardStateController.canDismissLockScreen()).thenReturn(true)
+
+        featureFlags = FakeFeatureFlags()
+        featureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true)
+        featureFlags.set(Flags.SCENE_CONTAINER, false)
+        featureFlags.set(Flags.BOUNCER_USER_SWITCHER, false)
+
+        keyguardPasswordViewController =
+            KeyguardPasswordViewController(
+                keyguardPasswordView,
+                keyguardUpdateMonitor,
+                SecurityMode.Password,
+                lockPatternUtils,
+                null,
+                messageAreaControllerFactory,
+                null,
+                null,
+                emergencyButtonController,
+                null,
+                mock(),
+                null,
+                keyguardViewController,
+                featureFlags
+            )
+
+        whenever(userInteractor.getSelectedUserId()).thenReturn(TARGET_USER_ID)
+        sceneTestUtils = SceneTestUtils(this)
+        sceneInteractor = sceneTestUtils.sceneInteractor()
+
+        underTest =
+            KeyguardSecurityContainerController(
+                view,
+                adminSecondaryLockScreenControllerFactory,
+                lockPatternUtils,
+                keyguardUpdateMonitor,
+                keyguardSecurityModel,
+                metricsLogger,
+                uiEventLogger,
+                keyguardStateController,
+                viewFlipperController,
+                configurationController,
+                falsingCollector,
+                falsingManager,
+                userSwitcherController,
+                featureFlags,
+                globalSettings,
+                sessionTracker,
+                Optional.of(sideFpsController),
+                falsingA11yDelegate,
+                telephonyManager,
+                viewMediatorCallback,
+                audioManager,
+                mock(),
+                mock(),
+                { JavaAdapter(sceneTestUtils.testScope.backgroundScope) },
+                userInteractor,
+            ) {
+                sceneInteractor
+            }
+    }
+
+    @Test
+    fun onInitConfiguresViewMode() {
+        underTest.onInit()
+        verify(view)
+            .initMode(
+                eq(KeyguardSecurityContainer.MODE_DEFAULT),
+                eq(globalSettings),
+                eq(falsingManager),
+                eq(userSwitcherController),
+                any(),
+                eq(falsingA11yDelegate)
+            )
+    }
+
+    @Test
+    fun showSecurityScreen_canInflateAllModes() {
+        val modes = SecurityMode.values()
+        for (mode in modes) {
+            whenever(inputViewController.securityMode).thenReturn(mode)
+            underTest.showSecurityScreen(mode)
+            if (mode == SecurityMode.Invalid) {
+                verify(viewFlipperController, never()).getSecurityView(any(), any(), any())
+            } else {
+                verify(viewFlipperController).getSecurityView(eq(mode), any(), any())
+            }
+        }
+    }
+
+    @Test
+    fun onResourcesUpdate_callsThroughOnRotationChange() {
+        clearInvocations(view)
+
+        // Rotation is the same, shouldn't cause an update
+        underTest.updateResources()
+        verify(view, never())
+            .initMode(
+                eq(KeyguardSecurityContainer.MODE_DEFAULT),
+                eq(globalSettings),
+                eq(falsingManager),
+                eq(userSwitcherController),
+                any(),
+                eq(falsingA11yDelegate)
+            )
+
+        // Update rotation. Should trigger update
+        testableResources.resources.configuration.orientation = Configuration.ORIENTATION_LANDSCAPE
+        underTest.updateResources()
+        verify(view)
+            .initMode(
+                eq(KeyguardSecurityContainer.MODE_DEFAULT),
+                eq(globalSettings),
+                eq(falsingManager),
+                eq(userSwitcherController),
+                any(),
+                eq(falsingA11yDelegate)
+            )
+    }
+
+    private fun touchDown() {
+        underTest.mGlobalTouchListener.onTouchEvent(
+            MotionEvent.obtain(
+                /* downTime= */ 0,
+                /* eventTime= */ 0,
+                MotionEvent.ACTION_DOWN,
+                /* x= */ 0f,
+                /* y= */ 0f,
+                /* metaState= */ 0
+            )
+        )
+    }
+
+    @Test
+    fun onInterceptTap_inhibitsFalsingInSidedSecurityMode() {
+        whenever(view.isTouchOnTheOtherSideOfSecurity(any())).thenReturn(false)
+        touchDown()
+        verify(falsingCollector, never()).avoidGesture()
+        whenever(view.isTouchOnTheOtherSideOfSecurity(any())).thenReturn(true)
+        touchDown()
+        verify(falsingCollector).avoidGesture()
+    }
+
+    @Test
+    fun showSecurityScreen_oneHandedMode_flagDisabled_noOneHandedMode() {
+        testableResources.addOverride(R.bool.can_use_one_handed_bouncer, false)
+        setupGetSecurityView(SecurityMode.Pattern)
+        underTest.showSecurityScreen(SecurityMode.Pattern)
+        verify(view)
+            .initMode(
+                eq(KeyguardSecurityContainer.MODE_DEFAULT),
+                eq(globalSettings),
+                eq(falsingManager),
+                eq(userSwitcherController),
+                any(),
+                eq(falsingA11yDelegate)
+            )
+    }
+
+    @Test
+    fun showSecurityScreen_oneHandedMode_flagEnabled_oneHandedMode() {
+        testableResources.addOverride(R.bool.can_use_one_handed_bouncer, true)
+        setupGetSecurityView(SecurityMode.Pattern)
+        verify(view)
+            .initMode(
+                eq(KeyguardSecurityContainer.MODE_ONE_HANDED),
+                eq(globalSettings),
+                eq(falsingManager),
+                eq(userSwitcherController),
+                any(),
+                eq(falsingA11yDelegate)
+            )
+    }
+
+    @Test
+    fun showSecurityScreen_twoHandedMode_flagEnabled_noOneHandedMode() {
+        testableResources.addOverride(R.bool.can_use_one_handed_bouncer, true)
+        setupGetSecurityView(SecurityMode.Password)
+        verify(view)
+            .initMode(
+                eq(KeyguardSecurityContainer.MODE_DEFAULT),
+                eq(globalSettings),
+                eq(falsingManager),
+                eq(userSwitcherController),
+                any(),
+                eq(falsingA11yDelegate)
+            )
+    }
+
+    @Test
+    fun addUserSwitcherCallback() {
+        val captor = ArgumentCaptor.forClass(UserSwitcherCallback::class.java)
+        setupGetSecurityView(SecurityMode.Password)
+        verify(view)
+            .initMode(anyInt(), any(), any(), any(), captor.capture(), eq(falsingA11yDelegate))
+        captor.value.showUnlockToContinueMessage()
+        viewControllerImmediately
+        verify(keyguardPasswordViewControllerMock)
+            .showMessage(
+                /* message= */ context.getString(R.string.keyguard_unlock_to_continue),
+                /* colorState= */ null,
+                /* animated= */ true
+            )
+    }
+
+    @Test
+    fun addUserSwitchCallback() {
+        underTest.onViewAttached()
+        verify(userSwitcherController).addUserSwitchCallback(any())
+        underTest.onViewDetached()
+        verify(userSwitcherController).removeUserSwitchCallback(any())
+    }
+
+    @Test
+    fun onBouncerVisibilityChanged_resetsScale() {
+        underTest.onBouncerVisibilityChanged(false)
+        verify(view).resetScale()
+    }
+
+    @Test
+    fun showNextSecurityScreenOrFinish_setsSecurityScreenToPinAfterSimPinUnlock() {
+        // GIVEN the current security method is SimPin
+        whenever(keyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(false)
+        whenever(keyguardUpdateMonitor.getUserUnlockedWithBiometric(TARGET_USER_ID))
+            .thenReturn(false)
+        underTest.showSecurityScreen(SecurityMode.SimPin)
+
+        // WHEN a request is made from the SimPin screens to show the next security method
+        whenever(keyguardSecurityModel.getSecurityMode(TARGET_USER_ID)).thenReturn(SecurityMode.PIN)
+        underTest.showNextSecurityScreenOrFinish(
+            /* authenticated= */ true,
+            TARGET_USER_ID,
+            /* bypassSecondaryLockScreen= */ true,
+            SecurityMode.SimPin
+        )
+
+        // THEN the next security method of PIN is set, and the keyguard is not marked as done
+        verify(viewMediatorCallback, never()).keyguardDonePending(anyBoolean(), anyInt())
+        verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt())
+        Truth.assertThat(underTest.currentSecurityMode).isEqualTo(SecurityMode.PIN)
+    }
+
+    @Test
+    fun showNextSecurityScreenOrFinish_DeviceNotSecure() {
+        // GIVEN the current security method is SimPin
+        whenever(keyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(false)
+        whenever(keyguardUpdateMonitor.getUserUnlockedWithBiometric(TARGET_USER_ID))
+            .thenReturn(false)
+        underTest.showSecurityScreen(SecurityMode.SimPin)
+
+        // WHEN a request is made from the SimPin screens to show the next security method
+        whenever(keyguardSecurityModel.getSecurityMode(TARGET_USER_ID))
+            .thenReturn(SecurityMode.None)
+        whenever(lockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(true)
+        underTest.showNextSecurityScreenOrFinish(
+            /* authenticated= */ true,
+            TARGET_USER_ID,
+            /* bypassSecondaryLockScreen= */ true,
+            SecurityMode.SimPin
+        )
+
+        // THEN the next security method of None will dismiss keyguard.
+        verify(viewMediatorCallback).keyguardDone(anyBoolean(), anyInt())
+    }
+
+    @Test
+    fun showNextSecurityScreenOrFinish_ignoresCallWhenSecurityMethodHasChanged() {
+        // GIVEN current security mode has been set to PIN
+        underTest.showSecurityScreen(SecurityMode.PIN)
+
+        // WHEN a request comes from SimPin to dismiss the security screens
+        val keyguardDone =
+            underTest.showNextSecurityScreenOrFinish(
+                /* authenticated= */ true,
+                TARGET_USER_ID,
+                /* bypassSecondaryLockScreen= */ true,
+                SecurityMode.SimPin
+            )
+
+        // THEN no action has happened, which will not dismiss the security screens
+        Truth.assertThat(keyguardDone).isEqualTo(false)
+        verify(keyguardUpdateMonitor, never()).getUserHasTrust(anyInt())
+    }
+
+    @Test
+    fun showNextSecurityScreenOrFinish_SimPin_Swipe() {
+        // GIVEN the current security method is SimPin
+        whenever(keyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(false)
+        whenever(keyguardUpdateMonitor.getUserUnlockedWithBiometric(TARGET_USER_ID))
+            .thenReturn(false)
+        underTest.showSecurityScreen(SecurityMode.SimPin)
+
+        // WHEN a request is made from the SimPin screens to show the next security method
+        whenever(keyguardSecurityModel.getSecurityMode(TARGET_USER_ID))
+            .thenReturn(SecurityMode.None)
+        // WHEN security method is SWIPE
+        whenever(lockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(false)
+        underTest.showNextSecurityScreenOrFinish(
+            /* authenticated= */ true,
+            TARGET_USER_ID,
+            /* bypassSecondaryLockScreen= */ true,
+            SecurityMode.SimPin
+        )
+
+        // THEN the next security method of None will dismiss keyguard.
+        verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt())
+    }
+
+    @Test
+    fun onSwipeUp_whenFaceDetectionIsNotRunning_initiatesFaceAuth() {
+        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)
+    }
+
+    @Test
+    fun onDensityOrFontScaleChanged() {
+        val configurationListenerArgumentCaptor =
+            ArgumentCaptor.forClass(ConfigurationController.ConfigurationListener::class.java)
+        underTest.onViewAttached()
+        verify(configurationController).addCallback(configurationListenerArgumentCaptor.capture())
+        clearInvocations(viewFlipperController)
+        configurationListenerArgumentCaptor.value.onDensityOrFontScaleChanged()
+        verify(viewFlipperController).clearViews()
+        verify(viewFlipperController)
+            .asynchronouslyInflateView(
+                eq(SecurityMode.PIN),
+                any(),
+                onViewInflatedCallbackArgumentCaptor.capture()
+            )
+        onViewInflatedCallbackArgumentCaptor.value.onViewInflated(inputViewController)
+        verify(view).onDensityOrFontScaleChanged()
+    }
+
+    @Test
+    fun onThemeChanged() {
+        val configurationListenerArgumentCaptor =
+            ArgumentCaptor.forClass(ConfigurationController.ConfigurationListener::class.java)
+        underTest.onViewAttached()
+        verify(configurationController).addCallback(configurationListenerArgumentCaptor.capture())
+        clearInvocations(viewFlipperController)
+        configurationListenerArgumentCaptor.value.onThemeChanged()
+        verify(viewFlipperController).clearViews()
+        verify(viewFlipperController)
+            .asynchronouslyInflateView(
+                eq(SecurityMode.PIN),
+                any(),
+                onViewInflatedCallbackArgumentCaptor.capture()
+            )
+        onViewInflatedCallbackArgumentCaptor.value.onViewInflated(inputViewController)
+        verify(view).reset()
+        verify(viewFlipperController).reset()
+        verify(view).reloadColors()
+    }
+
+    @Test
+    fun onUiModeChanged() {
+        val configurationListenerArgumentCaptor =
+            ArgumentCaptor.forClass(ConfigurationController.ConfigurationListener::class.java)
+        underTest.onViewAttached()
+        verify(configurationController).addCallback(configurationListenerArgumentCaptor.capture())
+        clearInvocations(viewFlipperController)
+        configurationListenerArgumentCaptor.value.onUiModeChanged()
+        verify(viewFlipperController).clearViews()
+        verify(viewFlipperController)
+            .asynchronouslyInflateView(
+                eq(SecurityMode.PIN),
+                any(),
+                onViewInflatedCallbackArgumentCaptor.capture()
+            )
+        onViewInflatedCallbackArgumentCaptor.value.onViewInflated(inputViewController)
+        verify(view).reloadColors()
+    }
+
+    @Test
+    fun hasDismissActions() {
+        Assert.assertFalse("Action not set yet", underTest.hasDismissActions())
+        underTest.setOnDismissAction(mock(), null /* cancelAction */)
+        Assert.assertTrue("Action should exist", underTest.hasDismissActions())
+    }
+
+    @Test
+    fun willRunDismissFromKeyguardIsTrue() {
+        val action: OnDismissAction = mock()
+        whenever(action.willRunAnimationOnKeyguard()).thenReturn(true)
+        underTest.setOnDismissAction(action, null /* cancelAction */)
+        underTest.finish(false /* strongAuth */, 0 /* currentUser */)
+        Truth.assertThat(underTest.willRunDismissFromKeyguard()).isTrue()
+    }
+
+    @Test
+    fun willRunDismissFromKeyguardIsFalse() {
+        val action: OnDismissAction = mock()
+        whenever(action.willRunAnimationOnKeyguard()).thenReturn(false)
+        underTest.setOnDismissAction(action, null /* cancelAction */)
+        underTest.finish(false /* strongAuth */, 0 /* currentUser */)
+        Truth.assertThat(underTest.willRunDismissFromKeyguard()).isFalse()
+    }
+
+    @Test
+    fun willRunDismissFromKeyguardIsFalseWhenNoDismissActionSet() {
+        underTest.setOnDismissAction(null /* action */, null /* cancelAction */)
+        underTest.finish(false /* strongAuth */, 0 /* currentUser */)
+        Truth.assertThat(underTest.willRunDismissFromKeyguard()).isFalse()
+    }
+
+    @Test
+    fun onStartingToHide() {
+        underTest.onStartingToHide()
+        verify(viewFlipperController)
+            .getSecurityView(any(), any(), onViewInflatedCallbackArgumentCaptor.capture())
+        onViewInflatedCallbackArgumentCaptor.value.onViewInflated(inputViewController)
+        verify(inputViewController).onStartingToHide()
+    }
+
+    @Test
+    fun gravityReappliedOnConfigurationChange() {
+        // Set initial gravity
+        testableResources.addOverride(R.integer.keyguard_host_view_gravity, Gravity.CENTER)
+        testableResources.addOverride(R.bool.can_use_one_handed_bouncer, false)
+
+        // Kick off the initial pass...
+        underTest.onInit()
+        verify(view).layoutParams = any()
+        clearInvocations(view)
+
+        // Now simulate a config change
+        testableResources.addOverride(
+            R.integer.keyguard_host_view_gravity,
+            Gravity.CENTER_HORIZONTAL or Gravity.BOTTOM
+        )
+        underTest.updateResources()
+        verify(view).layoutParams = any()
+    }
+
+    @Test
+    fun gravityUsesOneHandGravityWhenApplicable() {
+        testableResources.addOverride(R.integer.keyguard_host_view_gravity, Gravity.CENTER)
+        testableResources.addOverride(
+            R.integer.keyguard_host_view_one_handed_gravity,
+            Gravity.CENTER_HORIZONTAL or Gravity.BOTTOM
+        )
+
+        // Start disabled.
+        testableResources.addOverride(R.bool.can_use_one_handed_bouncer, false)
+        underTest.onInit()
+        verify(view).layoutParams =
+            argThat(
+                ArgumentMatcher { argument: FrameLayout.LayoutParams ->
+                    argument.gravity == Gravity.CENTER
+                }
+                    as ArgumentMatcher<FrameLayout.LayoutParams>
+            )
+        clearInvocations(view)
+
+        // And enable
+        testableResources.addOverride(R.bool.can_use_one_handed_bouncer, true)
+        underTest.updateResources()
+        verify(view).layoutParams =
+            argThat(
+                ArgumentMatcher { argument: FrameLayout.LayoutParams ->
+                    argument.gravity == Gravity.CENTER_HORIZONTAL or Gravity.BOTTOM
+                }
+                    as ArgumentMatcher<FrameLayout.LayoutParams>
+            )
+    }
+
+    @Test
+    fun updateKeyguardPositionDelegatesToSecurityContainer() {
+        underTest.updateKeyguardPosition(1.0f)
+        verify(view).updatePositionByTouchX(1.0f)
+    }
+
+    @Test
+    fun reinflateViewFlipper() {
+        val onViewInflatedCallback = KeyguardSecurityViewFlipperController.OnViewInflatedCallback {}
+        underTest.reinflateViewFlipper(onViewInflatedCallback)
+        verify(viewFlipperController).clearViews()
+        verify(viewFlipperController)
+            .asynchronouslyInflateView(any(), any(), eq(onViewInflatedCallback))
+    }
+
+    @Test
+    fun sideFpsControllerShow() {
+        underTest.updateSideFpsVisibility(/* isVisible= */ true)
+        verify(sideFpsController)
+            .show(
+                SideFpsUiRequestSource.PRIMARY_BOUNCER,
+                BiometricOverlayConstants.REASON_AUTH_KEYGUARD
+            )
+    }
+
+    @Test
+    fun sideFpsControllerHide() {
+        underTest.updateSideFpsVisibility(/* isVisible= */ false)
+        verify(sideFpsController).hide(SideFpsUiRequestSource.PRIMARY_BOUNCER)
+    }
+
+    @Test
+    fun setExpansion_setsAlpha() {
+        underTest.setExpansion(KeyguardBouncerConstants.EXPANSION_VISIBLE)
+        verify(view).alpha = 1f
+        verify(view).translationY = 0f
+    }
+
+    @Test
+    fun dismissesKeyguard_whenSceneChangesFromBouncerToGone() =
+        sceneTestUtils.testScope.runTest {
+            featureFlags.set(Flags.SCENE_CONTAINER, true)
+
+            // Upon init, we have never dismisses the keyguard.
+            underTest.onInit()
+            runCurrent()
+            verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt())
+
+            // Once the view is attached, we start listening but simply going to the bouncer scene
+            // is
+            // not enough to trigger a dismissal of the keyguard.
+            underTest.onViewAttached()
+            sceneInteractor.setCurrentScene(
+                SceneTestUtils.CONTAINER_1,
+                SceneModel(SceneKey.Bouncer, null)
+            )
+            runCurrent()
+            verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt())
+
+            // While listening, going from the bouncer scene to the gone scene, does dismiss the
+            // keyguard.
+            sceneInteractor.setCurrentScene(
+                SceneTestUtils.CONTAINER_1,
+                SceneModel(SceneKey.Gone, null)
+            )
+            runCurrent()
+            verify(viewMediatorCallback).keyguardDone(anyBoolean(), anyInt())
+
+            // While listening, moving back to the bouncer scene does not dismiss the keyguard
+            // again.
+            clearInvocations(viewMediatorCallback)
+            sceneInteractor.setCurrentScene(
+                SceneTestUtils.CONTAINER_1,
+                SceneModel(SceneKey.Bouncer, null)
+            )
+            runCurrent()
+            verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt())
+
+            // Detaching the view stops listening, so moving from the bouncer scene to the gone
+            // scene
+            // does not dismiss the keyguard while we're not listening.
+            underTest.onViewDetached()
+            sceneInteractor.setCurrentScene(
+                SceneTestUtils.CONTAINER_1,
+                SceneModel(SceneKey.Gone, null)
+            )
+            runCurrent()
+            verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt())
+
+            // While not listening, moving back to the bouncer does not dismiss the keyguard.
+            sceneInteractor.setCurrentScene(
+                SceneTestUtils.CONTAINER_1,
+                SceneModel(SceneKey.Bouncer, null)
+            )
+            runCurrent()
+            verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt())
+
+            // Reattaching the view starts listening again so moving from the bouncer scene to the
+            // gone
+            // scene now does dismiss the keyguard again.
+            underTest.onViewAttached()
+            sceneInteractor.setCurrentScene(
+                SceneTestUtils.CONTAINER_1,
+                SceneModel(SceneKey.Gone, null)
+            )
+            runCurrent()
+            verify(viewMediatorCallback).keyguardDone(anyBoolean(), anyInt())
+        }
+
+    private val registeredSwipeListener: KeyguardSecurityContainer.SwipeListener
+        get() {
+            underTest.onViewAttached()
+            verify(view).setSwipeListener(swipeListenerArgumentCaptor.capture())
+            return swipeListenerArgumentCaptor.value
+        }
+
+    private fun setupGetSecurityView(securityMode: SecurityMode) {
+        underTest.showSecurityScreen(securityMode)
+        viewControllerImmediately
+    }
+
+    private val viewControllerImmediately: Unit
+        get() {
+            verify(viewFlipperController, atLeastOnce())
+                .getSecurityView(any(), any(), onViewInflatedCallbackArgumentCaptor.capture())
+            @Suppress("UNCHECKED_CAST")
+            onViewInflatedCallbackArgumentCaptor.value.onViewInflated(
+                keyguardPasswordViewControllerMock as KeyguardInputViewController<KeyguardInputView>
+            )
+        }
+
+    companion object {
+        private const val TARGET_USER_ID = 100
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 3cb4c0c..5abab62 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -17,6 +17,7 @@
 package com.android.keyguard;
 
 import static android.app.StatusBarManager.SESSION_KEYGUARD;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
 import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT;
 import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT_PERMANENT;
@@ -38,6 +39,7 @@
 import static com.android.keyguard.KeyguardUpdateMonitor.DEFAULT_CANCEL_SIGNAL_TIMEOUT;
 import static com.android.keyguard.KeyguardUpdateMonitor.HAL_POWER_PRESS_TIMEOUT;
 import static com.android.keyguard.KeyguardUpdateMonitor.getCurrentUser;
+import static com.android.systemui.flags.Flags.FP_LISTEN_OCCLUDING_APPS;
 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;
@@ -64,6 +66,8 @@
 import static org.mockito.Mockito.when;
 
 import android.app.Activity;
+import android.app.ActivityTaskManager;
+import android.app.IActivityTaskManager;
 import android.app.admin.DevicePolicyManager;
 import android.app.trust.IStrongAuthTracker;
 import android.app.trust.TrustManager;
@@ -79,6 +83,7 @@
 import android.content.pm.UserInfo;
 import android.database.ContentObserver;
 import android.hardware.SensorPrivacyManager;
+import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricConstants;
 import android.hardware.biometrics.BiometricManager;
 import android.hardware.biometrics.BiometricSourceType;
@@ -115,6 +120,7 @@
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
+import android.text.TextUtils;
 
 import androidx.annotation.NonNull;
 
@@ -134,9 +140,12 @@
 import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FakeFeatureFlags;
 import com.android.systemui.log.SessionTracker;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.settings.UserTracker;
+import com.android.systemui.shared.system.TaskStackChangeListener;
+import com.android.systemui.shared.system.TaskStackChangeListeners;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.policy.DevicePostureController;
@@ -173,6 +182,8 @@
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
 public class KeyguardUpdateMonitorTest extends SysuiTestCase {
+    private static final String PKG_ALLOWING_FP_LISTEN_ON_OCCLUDING_ACTIVITY =
+            "test_app_fp_listen_on_occluding_activity";
     private static final String TEST_CARRIER = "TEST_CARRIER";
     private static final String TEST_CARRIER_2 = "TEST_CARRIER_2";
     private static final int TEST_CARRIER_ID = 1;
@@ -262,6 +273,10 @@
     private UsbPortStatus mUsbPortStatus;
     @Mock
     private Uri mURI;
+    @Mock
+    private TaskStackChangeListeners mTaskStackChangeListeners;
+    @Mock
+    private IActivityTaskManager mActivityTaskManager;
 
     private List<FaceSensorPropertiesInternal> mFaceSensorProperties;
     private List<FingerprintSensorPropertiesInternal> mFingerprintSensorProperties;
@@ -278,6 +293,7 @@
     private final Executor mBackgroundExecutor = Runnable::run;
     private final Executor mMainExecutor = Runnable::run;
     private TestableLooper mTestableLooper;
+    private FakeFeatureFlags mFeatureFlags;
     private Handler mHandler;
     private TestableKeyguardUpdateMonitor mKeyguardUpdateMonitor;
     private MockitoSession mMockitoSession;
@@ -324,8 +340,14 @@
                 mDumpManager
         );
 
+        mContext.getOrCreateTestableResources().addOverride(com.android.systemui
+                        .R.array.config_fingerprint_listen_on_occluding_activity_packages,
+                new String[]{ PKG_ALLOWING_FP_LISTEN_ON_OCCLUDING_ACTIVITY });
+
         mTestableLooper = TestableLooper.get(this);
         allowTestableLooperAsMainThread();
+        mFeatureFlags = new FakeFeatureFlags();
+        mFeatureFlags.set(FP_LISTEN_OCCLUDING_APPS, false);
 
         when(mSecureSettings.getUriFor(anyString())).thenReturn(mURI);
 
@@ -1555,6 +1577,50 @@
     }
 
     @Test
+    public void listenForFingerprint_whenOccludingAppPkgOnAllowlist()
+            throws RemoteException {
+        mFeatureFlags.set(FP_LISTEN_OCCLUDING_APPS, true);
+
+        // GIVEN keyguard isn't visible (app occluding)
+        mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
+        mKeyguardUpdateMonitor.setKeyguardShowing(true, true);
+        when(mStrongAuthTracker.hasUserAuthenticatedSinceBoot()).thenReturn(true);
+
+        // GIVEN the top activity is from a package that allows fingerprint listening over its
+        // occluding activities
+        setTopStandardActivity(PKG_ALLOWING_FP_LISTEN_ON_OCCLUDING_ACTIVITY);
+        onTaskStackChanged();
+
+        // THEN we SHOULD listen for non-UDFPS fingerprint
+        assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(false)).isEqualTo(true);
+
+        // THEN we should listen for udfps (hiding mechanism to actually auth is
+        // controlled by UdfpsKeyguardViewController)
+        assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(true)).isEqualTo(true);
+    }
+
+    @Test
+    public void doNotListenForFingerprint_whenOccludingAppPkgNotOnAllowlist()
+            throws RemoteException {
+        mFeatureFlags.set(FP_LISTEN_OCCLUDING_APPS, true);
+
+        // GIVEN keyguard isn't visible (app occluding)
+        mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
+        mKeyguardUpdateMonitor.setKeyguardShowing(true, true);
+        when(mStrongAuthTracker.hasUserAuthenticatedSinceBoot()).thenReturn(true);
+
+        // GIVEN top activity is not in the allowlist for listening to fp over occluding activities
+        setTopStandardActivity("notInAllowList");
+
+        // THEN we should not listen for non-UDFPS fingerprint
+        assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(false)).isEqualTo(false);
+
+        // THEN we should listen for udfps (hiding mechanism to actually auth is
+        // controlled by UdfpsKeyguardViewController)
+        assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(true)).isEqualTo(true);
+    }
+
+    @Test
     public void testOccludingAppFingerprintListeningState() {
         // GIVEN keyguard isn't visible (app occluding)
         mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
@@ -2945,6 +3011,24 @@
                 TelephonyManager.SIM_STATE_NOT_READY);
     }
 
+    @Test
+    public void onAuthEnrollmentChangesCallbacksAreNotified() {
+        KeyguardUpdateMonitorCallback callback = mock(KeyguardUpdateMonitorCallback.class);
+        ArgumentCaptor<AuthController.Callback> authCallback = ArgumentCaptor.forClass(
+                AuthController.Callback.class);
+        verify(mAuthController).addCallback(authCallback.capture());
+
+        mKeyguardUpdateMonitor.registerCallback(callback);
+
+        authCallback.getValue().onEnrollmentsChanged(TYPE_FINGERPRINT);
+        mTestableLooper.processAllMessages();
+        verify(callback).onBiometricEnrollmentStateChanged(BiometricSourceType.FINGERPRINT);
+
+        authCallback.getValue().onEnrollmentsChanged(BiometricAuthenticator.TYPE_FACE);
+        mTestableLooper.processAllMessages();
+        verify(callback).onBiometricEnrollmentStateChanged(BiometricSourceType.FACE);
+    }
+
     private void verifyFingerprintAuthenticateNeverCalled() {
         verify(mFingerprintManager, never()).authenticate(any(), any(), any(), any(), any());
         verify(mFingerprintManager, never()).authenticate(any(), any(), any(), any(), anyInt(),
@@ -3255,6 +3339,23 @@
                 BatteryManager.CHARGING_POLICY_ADAPTIVE_LONGLIFE);
     }
 
+    private void setTopStandardActivity(String pkgName) throws RemoteException {
+        final ActivityTaskManager.RootTaskInfo taskInfo = new ActivityTaskManager.RootTaskInfo();
+        taskInfo.visible = true;
+        taskInfo.topActivity = TextUtils.isEmpty(pkgName)
+                ? null : new ComponentName(pkgName, "testClass");
+        when(mActivityTaskManager.getRootTaskInfo(anyInt(), eq(ACTIVITY_TYPE_STANDARD)))
+                .thenReturn(taskInfo);
+    }
+
+    private void onTaskStackChanged() {
+        ArgumentCaptor<TaskStackChangeListener> taskStackChangeListenerCaptor =
+                ArgumentCaptor.forClass(TaskStackChangeListener.class);
+        verify(mTaskStackChangeListeners).registerTaskStackListener(
+                taskStackChangeListenerCaptor.capture());
+        taskStackChangeListenerCaptor.getValue().onTaskStackChangedBackground();
+    }
+
     private class TestableKeyguardUpdateMonitor extends KeyguardUpdateMonitor {
         AtomicBoolean mSimStateChanged = new AtomicBoolean(false);
         AtomicInteger mCachedSimState = new AtomicInteger(-1);
@@ -3272,7 +3373,8 @@
                     mDreamManager, mDevicePolicyManager, mSensorPrivacyManager, mTelephonyManager,
                     mPackageManager, mFaceManager, mFingerprintManager, mBiometricManager,
                     mFaceWakeUpTriggersConfig, mDevicePostureController,
-                    Optional.of(mInteractiveToAuthProvider));
+                    Optional.of(mInteractiveToAuthProvider), mFeatureFlags,
+                    mTaskStackChangeListeners, 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 403bd8c..956e0b81 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
@@ -18,6 +18,9 @@
 
 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_WALLPAPER_DREAM_ENABLED;
+import static com.android.systemui.flags.Flags.MIGRATE_LOCK_ICON;
 
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyInt;
@@ -40,18 +43,15 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.biometrics.AuthRippleController;
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
 import com.android.systemui.doze.util.BurnInHelperKt;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository;
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;
+import com.android.systemui.flags.FakeFeatureFlags;
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository;
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory;
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
-import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.VibratorHelper;
 import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -92,13 +92,11 @@
     protected @Mock ConfigurationController mConfigurationController;
     protected @Mock VibratorHelper mVibrator;
     protected @Mock AuthRippleController mAuthRippleController;
-    protected @Mock FeatureFlags mFeatureFlags;
     protected @Mock KeyguardTransitionRepository mTransitionRepository;
-    protected @Mock CommandQueue mCommandQueue;
     protected FakeExecutor mDelayableExecutor = new FakeExecutor(new FakeSystemClock());
+    protected FakeFeatureFlags mFeatureFlags;
     protected @Mock PrimaryBouncerInteractor mPrimaryBouncerInteractor;
 
-
     protected LockIconViewController mUnderTest;
 
     // Capture listeners so that they can be used to send events
@@ -147,6 +145,10 @@
         when(mStatusBarStateController.isDozing()).thenReturn(false);
         when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD);
 
+        mFeatureFlags = new FakeFeatureFlags();
+        mFeatureFlags.set(FACE_AUTH_REFACTOR, false);
+        mFeatureFlags.set(MIGRATE_LOCK_ICON, false);
+        mFeatureFlags.set(LOCKSCREEN_WALLPAPER_DREAM_ENABLED, false);
         mUnderTest = new LockIconViewController(
                 mLockIconView,
                 mStatusBarStateController,
@@ -164,12 +166,7 @@
                 mResources,
                 new KeyguardTransitionInteractor(mTransitionRepository,
                         TestScopeProvider.getTestScope().getBackgroundScope()),
-                new KeyguardInteractor(
-                        new FakeKeyguardRepository(),
-                        mCommandQueue,
-                        mFeatureFlags,
-                        new FakeKeyguardBouncerRepository()
-                ),
+                KeyguardInteractorFactory.create(mFeatureFlags).getKeyguardInteractor(),
                 mFeatureFlags,
                 mPrimaryBouncerInteractor
         );
@@ -231,7 +228,7 @@
     }
 
     protected void init(boolean useMigrationFlag) {
-        when(mFeatureFlags.isEnabled(DOZING_MIGRATION_1)).thenReturn(useMigrationFlag);
+        mFeatureFlags.set(DOZING_MIGRATION_1, useMigrationFlag);
         mUnderTest.init();
 
         verify(mLockIconView, atLeast(1)).addOnAttachStateChangeListener(mAttachCaptor.capture());
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerWithCoroutinesTest.kt
index d2c54b4..c372f45 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerWithCoroutinesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerWithCoroutinesTest.kt
@@ -17,9 +17,11 @@
 package com.android.keyguard
 
 import android.testing.AndroidTestingRunner
+import android.view.View
 import androidx.test.filters.SmallTest
 import com.android.keyguard.LockIconView.ICON_LOCK
 import com.android.systemui.doze.util.getBurnInOffset
+import com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED
 import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
 import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
 import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED
@@ -117,6 +119,33 @@
             verify(mLockIconView).setTranslationX(0f)
         }
 
+    @Test
+    fun testHideLockIconView_onLockscreenHostedDreamStateChanged() =
+        runBlocking(IMMEDIATE) {
+            // GIVEN starting state for the lock icon (keyguard) and wallpaper dream enabled
+            mFeatureFlags.set(LOCKSCREEN_WALLPAPER_DREAM_ENABLED, true)
+            setupShowLockIcon()
+            init(/* useMigrationFlag= */ true)
+            reset(mLockIconView)
+
+            // WHEN dream starts
+            mUnderTest.mIsActiveDreamLockscreenHostedCallback.accept(
+                true /* isActiveDreamLockscreenHosted */
+            )
+
+            // THEN the lock icon is hidden
+            verify(mLockIconView).visibility = View.INVISIBLE
+            reset(mLockIconView)
+
+            // WHEN the device is no longer dreaming
+            mUnderTest.mIsActiveDreamLockscreenHostedCallback.accept(
+                false /* isActiveDreamLockscreenHosted */
+            )
+
+            // THEN lock icon is visible
+            verify(mLockIconView).visibility = View.VISIBLE
+        }
+
     companion object {
         private val IMMEDIATE = Dispatchers.Main.immediate
     }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/TestScopeProvider.kt b/packages/SystemUI/tests/src/com/android/keyguard/TestScopeProvider.kt
index 073c7fe..6c35734 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/TestScopeProvider.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/TestScopeProvider.kt
@@ -13,11 +13,8 @@
  * See the License for the specific language governing permissions and
  * limitations under the License
  */
-
 package com.android.keyguard
-
 import kotlinx.coroutines.test.TestScope
-
 class TestScopeProvider {
     companion object {
         @JvmStatic fun getTestScope() = TestScope()
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/clock/AnalogClockControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/clock/AnalogClockControllerTest.java
deleted file mode 100644
index ef3af8a..0000000
--- a/packages/SystemUI/tests/src/com/android/keyguard/clock/AnalogClockControllerTest.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.clock;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.content.res.Resources;
-import android.graphics.Color;
-import android.test.suitebuilder.annotation.SmallTest;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper.RunWithLooper;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.TextView;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.colorextraction.SysuiColorExtractor;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@RunWithLooper
-public final class AnalogClockControllerTest extends SysuiTestCase {
-
-    private AnalogClockController mClockController;
-    @Mock SysuiColorExtractor mMockColorExtractor;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-
-        Resources res = getContext().getResources();
-        LayoutInflater layoutInflater = LayoutInflater.from(getContext());
-        mClockController = new AnalogClockController(res, layoutInflater,
-                mMockColorExtractor);
-    }
-
-    @Test
-    public void setDarkAmount_AOD() {
-        ViewGroup smallClockFrame = (ViewGroup) mClockController.getView();
-        View smallClock = smallClockFrame.getChildAt(0);
-        // WHEN dark amount is set to AOD
-        mClockController.setDarkAmount(1f);
-        // THEN small clock should be shown.
-        assertThat(smallClock.getVisibility()).isEqualTo(View.VISIBLE);
-    }
-
-    @Test
-    public void setColorPalette_setDigitalClock() {
-        ViewGroup smallClock = (ViewGroup) mClockController.getView();
-        // WHEN color palette is set
-        mClockController.setColorPalette(true, new int[]{Color.RED});
-        // THEN child of small clock should have text color set.
-        TextView digitalClock = (TextView) smallClock.getChildAt(0);
-        assertThat(digitalClock.getCurrentTextColor()).isEqualTo(Color.RED);
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/clock/BubbleClockControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/clock/BubbleClockControllerTest.java
deleted file mode 100644
index b56986e..0000000
--- a/packages/SystemUI/tests/src/com/android/keyguard/clock/BubbleClockControllerTest.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.clock;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.content.res.Resources;
-import android.graphics.Color;
-import android.test.suitebuilder.annotation.SmallTest;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper.RunWithLooper;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.TextView;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.colorextraction.SysuiColorExtractor;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@RunWithLooper
-public final class BubbleClockControllerTest extends SysuiTestCase {
-
-    private BubbleClockController mClockController;
-    @Mock SysuiColorExtractor mMockColorExtractor;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-
-        Resources res = getContext().getResources();
-        LayoutInflater layoutInflater = LayoutInflater.from(getContext());
-        mClockController = new BubbleClockController(res, layoutInflater, mMockColorExtractor);
-    }
-
-    @Test
-    public void setDarkAmount_AOD() {
-        ViewGroup smallClockFrame = (ViewGroup) mClockController.getView();
-        View smallClock = smallClockFrame.getChildAt(0);
-        // WHEN dark amount is set to AOD
-        mClockController.setDarkAmount(1f);
-        // THEN small clock should not be shown.
-        assertThat(smallClock.getVisibility()).isEqualTo(View.VISIBLE);
-    }
-
-    @Test
-    public void setColorPalette_setDigitalClock() {
-        ViewGroup smallClock = (ViewGroup) mClockController.getView();
-        // WHEN text color is set
-        mClockController.setColorPalette(true, new int[]{Color.RED});
-        // THEN child of small clock should have text color set.
-        TextView digitalClock = (TextView) smallClock.getChildAt(0);
-        assertThat(digitalClock.getCurrentTextColor()).isEqualTo(Color.RED);
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/clock/ClockInfoTest.java b/packages/SystemUI/tests/src/com/android/keyguard/clock/ClockInfoTest.java
deleted file mode 100644
index 4c0890a..0000000
--- a/packages/SystemUI/tests/src/com/android/keyguard/clock/ClockInfoTest.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.clock;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.Mockito.verify;
-
-import android.graphics.Bitmap;
-import android.test.suitebuilder.annotation.SmallTest;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper.RunWithLooper;
-
-import com.android.systemui.SysuiTestCase;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.function.Supplier;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@RunWithLooper
-public final class ClockInfoTest extends SysuiTestCase {
-
-    @Mock
-    private Supplier<Bitmap> mMockSupplier;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-    }
-
-    @Test
-    public void testGetName() {
-        final String name = "name";
-        ClockInfo info = ClockInfo.builder().setName(name).build();
-        assertThat(info.getName()).isEqualTo(name);
-    }
-
-    @Test
-    public void testGetTitle() {
-        final String title = "title";
-        ClockInfo info = ClockInfo.builder().setTitle(() -> title).build();
-        assertThat(info.getTitle()).isEqualTo(title);
-    }
-
-    @Test
-    public void testGetId() {
-        final String id = "id";
-        ClockInfo info = ClockInfo.builder().setId(id).build();
-        assertThat(info.getId()).isEqualTo(id);
-    }
-
-    @Test
-    public void testGetThumbnail() {
-        ClockInfo info = ClockInfo.builder().setThumbnail(mMockSupplier).build();
-        info.getThumbnail();
-        verify(mMockSupplier).get();
-    }
-
-    @Test
-    public void testGetPreview() {
-        ClockInfo info = ClockInfo.builder().setPreview(mMockSupplier).build();
-        info.getPreview();
-        verify(mMockSupplier).get();
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/clock/ClockManagerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/clock/ClockManagerTest.java
deleted file mode 100644
index 7a5b772..0000000
--- a/packages/SystemUI/tests/src/com/android/keyguard/clock/ClockManagerTest.java
+++ /dev/null
@@ -1,258 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.clock;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.content.ContentResolver;
-import android.database.ContentObserver;
-import android.net.Uri;
-import android.test.suitebuilder.annotation.SmallTest;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper.RunWithLooper;
-import android.view.LayoutInflater;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.colorextraction.SysuiColorExtractor;
-import com.android.systemui.dock.DockManager;
-import com.android.systemui.dock.DockManagerFake;
-import com.android.systemui.plugins.ClockPlugin;
-import com.android.systemui.plugins.PluginManager;
-import com.android.systemui.settings.UserTracker;
-import com.android.systemui.util.concurrency.FakeExecutor;
-import com.android.systemui.util.time.FakeSystemClock;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.Arrays;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-// Need to run tests on main looper to allow for onClockChanged operation to happen synchronously.
-@RunWithLooper(setAsMainLooper = true)
-public final class ClockManagerTest extends SysuiTestCase {
-
-    private static final String BUBBLE_CLOCK = BubbleClockController.class.getName();
-    private static final Class<?> BUBBLE_CLOCK_CLASS = BubbleClockController.class;
-    private static final int MAIN_USER_ID = 0;
-    private static final int SECONDARY_USER_ID = 11;
-    private static final Uri SETTINGS_URI = null;
-
-    private final FakeSystemClock mFakeSystemClock = new FakeSystemClock();
-    private final FakeExecutor mMainExecutor = new FakeExecutor(mFakeSystemClock);
-    private ClockManager mClockManager;
-    private ContentObserver mContentObserver;
-    private DockManagerFake mFakeDockManager;
-    private ArgumentCaptor<UserTracker.Callback> mUserTrackerCallbackCaptor;
-    @Mock PluginManager mMockPluginManager;
-    @Mock SysuiColorExtractor mMockColorExtractor;
-    @Mock ContentResolver mMockContentResolver;
-    @Mock UserTracker mUserTracker;
-    @Mock SettingsWrapper mMockSettingsWrapper;
-    @Mock ClockManager.ClockChangedListener mMockListener1;
-    @Mock ClockManager.ClockChangedListener mMockListener2;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-
-        LayoutInflater inflater = LayoutInflater.from(getContext());
-
-        mFakeDockManager = new DockManagerFake();
-
-        when(mUserTracker.getUserId()).thenReturn(MAIN_USER_ID);
-        mUserTrackerCallbackCaptor = ArgumentCaptor.forClass(UserTracker.Callback.class);
-
-        mClockManager = new ClockManager(getContext(), inflater,
-                mMockPluginManager, mMockColorExtractor, mMockContentResolver,
-                mUserTracker, mMainExecutor, mMockSettingsWrapper, mFakeDockManager);
-
-        mClockManager.addBuiltinClock(() -> new BubbleClockController(
-                getContext().getResources(), inflater, mMockColorExtractor));
-        mClockManager.addOnClockChangedListener(mMockListener1);
-        mClockManager.addOnClockChangedListener(mMockListener2);
-        verify(mUserTracker).addCallback(mUserTrackerCallbackCaptor.capture(), any());
-        reset(mMockListener1, mMockListener2);
-
-        mContentObserver = mClockManager.getContentObserver();
-    }
-
-    @After
-    public void tearDown() {
-        mClockManager.removeOnClockChangedListener(mMockListener1);
-        mClockManager.removeOnClockChangedListener(mMockListener2);
-    }
-
-    @Test
-    public void dockEvent() {
-        mFakeDockManager.setDockEvent(DockManager.STATE_DOCKED);
-        assertThat(mClockManager.isDocked()).isTrue();
-    }
-
-    @Test
-    public void undockEvent() {
-        mFakeDockManager.setDockEvent(DockManager.STATE_NONE);
-        assertThat(mClockManager.isDocked()).isFalse();
-    }
-
-    @Test
-    public void getCurrentClock_default() {
-        // GIVEN that settings doesn't contain any values
-        when(mMockSettingsWrapper.getLockScreenCustomClockFace(anyInt())).thenReturn(null);
-        when(mMockSettingsWrapper.getDockedClockFace(anyInt())).thenReturn(null);
-        // WHEN settings change event is fired
-        mContentObserver.onChange(false, Arrays.asList(SETTINGS_URI), 0, MAIN_USER_ID);
-        // THEN the result is null, indicated the default clock face should be used.
-        assertThat(mClockManager.getCurrentClock()).isNull();
-    }
-
-    @Test
-    public void getCurrentClock_customClock() {
-        // GIVEN that settings is set to the bubble clock face
-        when(mMockSettingsWrapper.getLockScreenCustomClockFace(anyInt())).thenReturn(BUBBLE_CLOCK);
-        // WHEN settings change event is fired
-        mContentObserver.onChange(false, Arrays.asList(SETTINGS_URI), 0, MAIN_USER_ID);
-        // THEN the plugin is the bubble clock face.
-        assertThat(mClockManager.getCurrentClock()).isInstanceOf(BUBBLE_CLOCK_CLASS);
-    }
-
-    @Test
-    public void onClockChanged_customClock() {
-        // GIVEN that settings is set to the bubble clock face
-        when(mMockSettingsWrapper.getLockScreenCustomClockFace(anyInt())).thenReturn(BUBBLE_CLOCK);
-        // WHEN settings change event is fired
-        mContentObserver.onChange(false, Arrays.asList(SETTINGS_URI), 0, MAIN_USER_ID);
-        // THEN the plugin is the bubble clock face.
-        ArgumentCaptor<ClockPlugin> captor = ArgumentCaptor.forClass(ClockPlugin.class);
-        verify(mMockListener1).onClockChanged(captor.capture());
-        assertThat(captor.getValue()).isInstanceOf(BUBBLE_CLOCK_CLASS);
-    }
-
-    @Test
-    public void onClockChanged_uniqueInstances() {
-        // GIVEN that settings is set to the bubble clock face
-        when(mMockSettingsWrapper.getLockScreenCustomClockFace(anyInt())).thenReturn(BUBBLE_CLOCK);
-        // WHEN settings change event is fired
-        mContentObserver.onChange(false, Arrays.asList(SETTINGS_URI), 0, MAIN_USER_ID);
-        // THEN the listeners receive separate instances of the Bubble clock plugin.
-        ArgumentCaptor<ClockPlugin> captor1 = ArgumentCaptor.forClass(ClockPlugin.class);
-        ArgumentCaptor<ClockPlugin> captor2 = ArgumentCaptor.forClass(ClockPlugin.class);
-        verify(mMockListener1).onClockChanged(captor1.capture());
-        verify(mMockListener2).onClockChanged(captor2.capture());
-        assertThat(captor1.getValue()).isInstanceOf(BUBBLE_CLOCK_CLASS);
-        assertThat(captor2.getValue()).isInstanceOf(BUBBLE_CLOCK_CLASS);
-        assertThat(captor1.getValue()).isNotSameInstanceAs(captor2.getValue());
-    }
-
-    @Test
-    public void getCurrentClock_badSettingsValue() {
-        // GIVEN that settings contains a value that doesn't correspond to a
-        // custom clock face.
-        when(mMockSettingsWrapper.getLockScreenCustomClockFace(anyInt())).thenReturn("bad value");
-        // WHEN settings change event is fired
-        mContentObserver.onChange(false, Arrays.asList(SETTINGS_URI), 0, MAIN_USER_ID);
-        // THEN the result is null.
-        assertThat(mClockManager.getCurrentClock()).isNull();
-    }
-
-    @Test
-    public void getCurrentClock_dockedDefault() {
-        // WHEN dock event is fired
-        mFakeDockManager.setDockEvent(DockManager.STATE_DOCKED);
-        // THEN the result is null, indicating the default clock face.
-        assertThat(mClockManager.getCurrentClock()).isNull();
-    }
-
-    @Test
-    public void getCurrentClock_dockedCustomClock() {
-        // GIVEN settings is set to the bubble clock face
-        when(mMockSettingsWrapper.getDockedClockFace(anyInt())).thenReturn(BUBBLE_CLOCK);
-        // WHEN dock event fires
-        mFakeDockManager.setDockEvent(DockManager.STATE_DOCKED);
-        // THEN the plugin is the bubble clock face.
-        assertThat(mClockManager.getCurrentClock()).isInstanceOf(BUBBLE_CLOCK_CLASS);
-    }
-
-    @Test
-    public void getCurrentClock_badDockedSettingsValue() {
-        // GIVEN settings contains a value that doesn't correspond to an available clock face.
-        when(mMockSettingsWrapper.getDockedClockFace(anyInt())).thenReturn("bad value");
-        // WHEN dock event fires
-        mFakeDockManager.setDockEvent(DockManager.STATE_DOCKED);
-        // THEN the result is null.
-        assertThat(mClockManager.getCurrentClock()).isNull();
-    }
-
-    @Test
-    public void getCurrentClock_badDockedSettingsFallback() {
-        // GIVEN settings contains a value that doesn't correspond to an available clock face, but
-        // locked screen settings is set to bubble clock.
-        when(mMockSettingsWrapper.getDockedClockFace(anyInt())).thenReturn("bad value");
-        when(mMockSettingsWrapper.getLockScreenCustomClockFace(anyInt())).thenReturn(BUBBLE_CLOCK);
-        // WHEN dock event is fired
-        mFakeDockManager.setDockEvent(DockManager.STATE_DOCKED);
-        // THEN the plugin is the bubble clock face.
-        assertThat(mClockManager.getCurrentClock()).isInstanceOf(BUBBLE_CLOCK_CLASS);
-    }
-
-    @Test
-    public void onUserChanged_defaultClock() {
-        // WHEN the user changes
-        switchUser(SECONDARY_USER_ID);
-        // THEN the plugin is null for the default clock face
-        assertThat(mClockManager.getCurrentClock()).isNull();
-    }
-
-    @Test
-    public void onUserChanged_customClock() {
-        // GIVEN that a second user has selected the bubble clock face
-        when(mMockSettingsWrapper.getLockScreenCustomClockFace(SECONDARY_USER_ID)).thenReturn(
-                BUBBLE_CLOCK);
-        // WHEN the user changes
-        switchUser(SECONDARY_USER_ID);
-        // THEN the plugin is the bubble clock face.
-        assertThat(mClockManager.getCurrentClock()).isInstanceOf(BUBBLE_CLOCK_CLASS);
-    }
-
-    @Test
-    public void onUserChanged_docked() {
-        // GIVEN device is docked
-        mFakeDockManager.setDockEvent(DockManager.STATE_DOCKED);
-        // AND the second user as selected the bubble clock for the dock
-        when(mMockSettingsWrapper.getDockedClockFace(SECONDARY_USER_ID)).thenReturn(BUBBLE_CLOCK);
-        // WHEN the user changes
-        switchUser(SECONDARY_USER_ID);
-        // THEN the plugin is the bubble clock face.
-        assertThat(mClockManager.getCurrentClock()).isInstanceOf(BUBBLE_CLOCK_CLASS);
-    }
-
-    private void switchUser(int newUser) {
-        when(mUserTracker.getUserId()).thenReturn(newUser);
-        mUserTrackerCallbackCaptor.getValue().onUserChanged(newUser, mContext);
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/clock/ClockOptionsProviderTest.java b/packages/SystemUI/tests/src/com/android/keyguard/clock/ClockOptionsProviderTest.java
deleted file mode 100644
index d2832fb9..0000000
--- a/packages/SystemUI/tests/src/com/android/keyguard/clock/ClockOptionsProviderTest.java
+++ /dev/null
@@ -1,189 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.clock;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.Mockito.verify;
-
-import android.database.Cursor;
-import android.graphics.Bitmap;
-import android.net.Uri;
-import android.test.suitebuilder.annotation.SmallTest;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper.RunWithLooper;
-
-import com.android.systemui.SysuiTestCase;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.function.Supplier;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@RunWithLooper
-public final class ClockOptionsProviderTest extends SysuiTestCase {
-
-    private static final String CONTENT_SCHEME = "content";
-    private static final String AUTHORITY = "com.android.keyguard.clock";
-    private static final String LIST_OPTIONS = "list_options";
-    private static final String PREVIEW = "preview";
-    private static final String THUMBNAIL = "thumbnail";
-    private static final String MIME_TYPE_LIST_OPTIONS = "vnd.android.cursor.dir/clock_faces";
-    private static final String MIME_TYPE_PNG = "image/png";
-    private static final String NAME_COLUMN = "name";
-    private static final String TITLE_COLUMN = "title";
-    private static final String ID_COLUMN = "id";
-    private static final String PREVIEW_COLUMN = "preview";
-    private static final String THUMBNAIL_COLUMN = "thumbnail";
-
-    private ClockOptionsProvider mProvider;
-    private Supplier<List<ClockInfo>> mMockSupplier;
-    private List<ClockInfo> mClocks;
-    private Uri mListOptionsUri;
-    @Mock
-    private Supplier<Bitmap> mMockBitmapSupplier;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-        mClocks = new ArrayList<>();
-        mProvider = new ClockOptionsProvider(() -> mClocks);
-        mListOptionsUri = new Uri.Builder()
-                .scheme(CONTENT_SCHEME)
-                .authority(AUTHORITY)
-                .appendPath(LIST_OPTIONS)
-                .build();
-    }
-
-    @Test
-    public void testGetType_listOptions() {
-        Uri uri = new Uri.Builder()
-                .scheme(CONTENT_SCHEME)
-                .authority(AUTHORITY)
-                .appendPath(LIST_OPTIONS)
-                .build();
-        assertThat(mProvider.getType(uri)).isEqualTo(MIME_TYPE_LIST_OPTIONS);
-    }
-
-    @Test
-    public void testGetType_preview() {
-        Uri uri = new Uri.Builder()
-                .scheme(CONTENT_SCHEME)
-                .authority(AUTHORITY)
-                .appendPath(PREVIEW)
-                .appendPath("id")
-                .build();
-        assertThat(mProvider.getType(uri)).isEqualTo(MIME_TYPE_PNG);
-    }
-
-    @Test
-    public void testGetType_thumbnail() {
-        Uri uri = new Uri.Builder()
-                .scheme(CONTENT_SCHEME)
-                .authority(AUTHORITY)
-                .appendPath(THUMBNAIL)
-                .appendPath("id")
-                .build();
-        assertThat(mProvider.getType(uri)).isEqualTo(MIME_TYPE_PNG);
-    }
-
-    @Test
-    public void testQuery_noClocks() {
-        Cursor cursor = mProvider.query(mListOptionsUri, null, null, null);
-        assertThat(cursor.getCount()).isEqualTo(0);
-    }
-
-    @Test
-    public void testQuery_listOptions() {
-        mClocks.add(ClockInfo.builder()
-                .setName("name_a")
-                .setTitle(() -> "title_a")
-                .setId("id_a")
-                .build());
-        mClocks.add(ClockInfo.builder()
-                .setName("name_b")
-                .setTitle(() -> "title_b")
-                .setId("id_b")
-                .build());
-        Cursor cursor = mProvider.query(mListOptionsUri, null, null, null);
-        assertThat(cursor.getCount()).isEqualTo(2);
-        cursor.moveToFirst();
-        assertThat(cursor.getString(
-                cursor.getColumnIndex(NAME_COLUMN))).isEqualTo("name_a");
-        assertThat(cursor.getString(
-                cursor.getColumnIndex(TITLE_COLUMN))).isEqualTo("title_a");
-        assertThat(cursor.getString(
-                cursor.getColumnIndex(ID_COLUMN))).isEqualTo("id_a");
-        assertThat(cursor.getString(
-                cursor.getColumnIndex(PREVIEW_COLUMN)))
-                .isEqualTo("content://com.android.keyguard.clock/preview/id_a");
-        assertThat(cursor.getString(
-                cursor.getColumnIndex(THUMBNAIL_COLUMN)))
-                .isEqualTo("content://com.android.keyguard.clock/thumbnail/id_a");
-        cursor.moveToNext();
-        assertThat(cursor.getString(
-                cursor.getColumnIndex(NAME_COLUMN))).isEqualTo("name_b");
-        assertThat(cursor.getString(
-                cursor.getColumnIndex(TITLE_COLUMN))).isEqualTo("title_b");
-        assertThat(cursor.getString(
-                cursor.getColumnIndex(ID_COLUMN))).isEqualTo("id_b");
-        assertThat(cursor.getString(
-                cursor.getColumnIndex(PREVIEW_COLUMN)))
-                .isEqualTo("content://com.android.keyguard.clock/preview/id_b");
-        assertThat(cursor.getString(
-                cursor.getColumnIndex(THUMBNAIL_COLUMN)))
-                .isEqualTo("content://com.android.keyguard.clock/thumbnail/id_b");
-    }
-
-    @Test
-    public void testOpenFile_preview() throws Exception {
-        mClocks.add(ClockInfo.builder()
-                .setId("id")
-                .setPreview(mMockBitmapSupplier)
-                .build());
-        Uri uri = new Uri.Builder()
-                .scheme(CONTENT_SCHEME)
-                .authority(AUTHORITY)
-                .appendPath(PREVIEW)
-                .appendPath("id")
-                .build();
-        mProvider.openFile(uri, "r").close();
-        verify(mMockBitmapSupplier).get();
-    }
-
-    @Test
-    public void testOpenFile_thumbnail() throws Exception {
-        mClocks.add(ClockInfo.builder()
-                .setId("id")
-                .setThumbnail(mMockBitmapSupplier)
-                .build());
-        Uri uri = new Uri.Builder()
-                .scheme(CONTENT_SCHEME)
-                .authority(AUTHORITY)
-                .appendPath(THUMBNAIL)
-                .appendPath("id")
-                .build();
-        mProvider.openFile(uri, "r").close();
-        verify(mMockBitmapSupplier).get();
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/clock/ClockPaletteTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/clock/ClockPaletteTest.kt
deleted file mode 100644
index 347b26d..0000000
--- a/packages/SystemUI/tests/src/com/android/keyguard/clock/ClockPaletteTest.kt
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.clock
-
-import android.graphics.Color
-import android.testing.AndroidTestingRunner
-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
-
-@RunWith(AndroidTestingRunner::class)
-@SmallTest
-class ClockPaletteTest : SysuiTestCase() {
-
-    private lateinit var clockPalette: ClockPalette
-    private lateinit var colors: IntArray
-
-    @Before
-    fun setUp() {
-        clockPalette = ClockPalette()
-        // colors used are reds from light to dark.
-        val hsv: FloatArray = FloatArray(3)
-        Color.colorToHSV(Color.RED, hsv)
-        colors = IntArray(10)
-        val step: Float = (0f - hsv[2]) / colors.size
-        for (i in 0 until colors.size) {
-            hsv[2] += step
-            colors[i] = Color.HSVToColor(hsv)
-        }
-    }
-
-    @Test
-    fun testDark() {
-        // GIVEN on AOD
-        clockPalette.setDarkAmount(1f)
-        // AND GIVEN that wallpaper doesn't support dark text
-        clockPalette.setColorPalette(false, colors)
-        // THEN the secondary color should be lighter than the primary color
-        assertThat(value(clockPalette.getPrimaryColor()))
-                .isGreaterThan(value(clockPalette.getSecondaryColor()))
-    }
-
-    @Test
-    fun testDarkText() {
-        // GIVEN on lock screen
-        clockPalette.setDarkAmount(0f)
-        // AND GIVEN that wallpaper supports dark text
-        clockPalette.setColorPalette(true, colors)
-        // THEN the secondary color should be darker the primary color
-        assertThat(value(clockPalette.getPrimaryColor()))
-                .isLessThan(value(clockPalette.getSecondaryColor()))
-    }
-
-    @Test
-    fun testLightText() {
-        // GIVEN on lock screen
-        clockPalette.setDarkAmount(0f)
-        // AND GIVEN that wallpaper doesn't support dark text
-        clockPalette.setColorPalette(false, colors)
-        // THEN the secondary color should be darker than the primary color
-        assertThat(value(clockPalette.getPrimaryColor()))
-                .isGreaterThan(value(clockPalette.getSecondaryColor()))
-    }
-
-    @Test
-    fun testNullColors() {
-        // GIVEN on AOD
-        clockPalette.setDarkAmount(1f)
-        // AND GIVEN that wallpaper colors are null
-        clockPalette.setColorPalette(false, null)
-        // THEN the primary color should be whilte
-        assertThat(clockPalette.getPrimaryColor()).isEqualTo(Color.WHITE)
-    }
-
-    private fun value(color: Int): Float {
-        val hsv: FloatArray = FloatArray(3)
-        Color.colorToHSV(color, hsv)
-        return hsv[2]
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/clock/CrossFadeDarkControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/clock/CrossFadeDarkControllerTest.java
deleted file mode 100644
index fd7657f..0000000
--- a/packages/SystemUI/tests/src/com/android/keyguard/clock/CrossFadeDarkControllerTest.java
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.clock;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.test.suitebuilder.annotation.SmallTest;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper.RunWithLooper;
-import android.view.View;
-import android.widget.TextView;
-
-import com.android.systemui.SysuiTestCase;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@RunWithLooper
-public final class CrossFadeDarkControllerTest extends SysuiTestCase {
-
-    private View mViewFadeIn;
-    private View mViewFadeOut;
-    private CrossFadeDarkController mDarkController;
-
-    @Before
-    public void setUp() {
-        mViewFadeIn = new TextView(getContext());
-        mViewFadeIn.setVisibility(View.VISIBLE);
-        mViewFadeIn.setAlpha(1f);
-        mViewFadeOut = new TextView(getContext());
-        mViewFadeOut.setVisibility(View.VISIBLE);
-        mViewFadeOut.setAlpha(1f);
-
-        mDarkController = new CrossFadeDarkController(mViewFadeIn, mViewFadeOut);
-    }
-
-    @Test
-    public void setDarkAmount_fadeIn() {
-        // WHEN dark amount corresponds to AOD
-        mDarkController.setDarkAmount(1f);
-        // THEN fade in view should be faded in and fade out view faded out.
-        assertThat(mViewFadeIn.getAlpha()).isEqualTo(1f);
-        assertThat(mViewFadeIn.getVisibility()).isEqualTo(View.VISIBLE);
-        assertThat(mViewFadeOut.getAlpha()).isEqualTo(0f);
-        assertThat(mViewFadeOut.getVisibility()).isEqualTo(View.GONE);
-    }
-
-    @Test
-    public void setDarkAmount_fadeOut() {
-        // WHEN dark amount corresponds to lock screen
-        mDarkController.setDarkAmount(0f);
-        // THEN fade out view should bed faded out and fade in view faded in.
-        assertThat(mViewFadeIn.getAlpha()).isEqualTo(0f);
-        assertThat(mViewFadeIn.getVisibility()).isEqualTo(View.GONE);
-        assertThat(mViewFadeOut.getAlpha()).isEqualTo(1f);
-        assertThat(mViewFadeOut.getVisibility()).isEqualTo(View.VISIBLE);
-    }
-
-    @Test
-    public void setDarkAmount_partialFadeIn() {
-        // WHEN dark amount corresponds to a partial transition
-        mDarkController.setDarkAmount(0.9f);
-        // THEN views should have intermediate alpha value.
-        assertThat(mViewFadeIn.getAlpha()).isGreaterThan(0f);
-        assertThat(mViewFadeIn.getAlpha()).isLessThan(1f);
-        assertThat(mViewFadeIn.getVisibility()).isEqualTo(View.VISIBLE);
-    }
-
-    @Test
-    public void setDarkAmount_partialFadeOut() {
-        // WHEN dark amount corresponds to a partial transition
-        mDarkController.setDarkAmount(0.1f);
-        // THEN views should have intermediate alpha value.
-        assertThat(mViewFadeOut.getAlpha()).isGreaterThan(0f);
-        assertThat(mViewFadeOut.getAlpha()).isLessThan(1f);
-        assertThat(mViewFadeOut.getVisibility()).isEqualTo(View.VISIBLE);
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/clock/SettingsWrapperTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/clock/SettingsWrapperTest.kt
deleted file mode 100644
index 573581d..0000000
--- a/packages/SystemUI/tests/src/com/android/keyguard/clock/SettingsWrapperTest.kt
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.clock
-
-import android.testing.AndroidTestingRunner
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.google.common.truth.Truth.assertThat
-import org.json.JSONObject
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.never
-import org.mockito.Mockito.verify
-
-private const val PACKAGE = "com.android.keyguard.clock.Clock"
-private const val CLOCK_FIELD = "clock"
-private const val TIMESTAMP_FIELD = "_applied_timestamp"
-private const val USER_ID = 0
-
-@RunWith(AndroidTestingRunner::class)
-@SmallTest
-class SettingsWrapperTest : SysuiTestCase() {
-
-    private lateinit var wrapper: SettingsWrapper
-    private lateinit var migration: SettingsWrapper.Migration
-
-    @Before
-    fun setUp() {
-        migration = mock(SettingsWrapper.Migration::class.java)
-        wrapper = SettingsWrapper(getContext().contentResolver, migration)
-    }
-
-    @Test
-    fun testDecodeUnnecessary() {
-        // GIVEN a settings value that doesn't need to be decoded
-        val value = PACKAGE
-        // WHEN the value is decoded
-        val decoded = wrapper.decode(value, USER_ID)
-        // THEN the same value is returned, because decoding isn't necessary.
-        // TODO(b/135674383): Null should be returned when the migration code in removed.
-        assertThat(decoded).isEqualTo(value)
-        // AND the value is migrated to JSON format
-        verify(migration).migrate(value, USER_ID)
-    }
-
-    @Test
-    fun testDecodeJSON() {
-        // GIVEN a settings value that is encoded in JSON
-        val json: JSONObject = JSONObject()
-        json.put(CLOCK_FIELD, PACKAGE)
-        json.put(TIMESTAMP_FIELD, System.currentTimeMillis())
-        val value = json.toString()
-        // WHEN the value is decoded
-        val decoded = wrapper.decode(value, USER_ID)
-        // THEN the clock field should have been extracted
-        assertThat(decoded).isEqualTo(PACKAGE)
-    }
-
-    @Test
-    fun testDecodeJSONWithoutClockField() {
-        // GIVEN a settings value that doesn't contain the CLOCK_FIELD
-        val json: JSONObject = JSONObject()
-        json.put(TIMESTAMP_FIELD, System.currentTimeMillis())
-        val value = json.toString()
-        // WHEN the value is decoded
-        val decoded = wrapper.decode(value, USER_ID)
-        // THEN null is returned
-        assertThat(decoded).isNull()
-        // AND the value is not migrated to JSON format
-        verify(migration, never()).migrate(value, USER_ID)
-    }
-
-    @Test
-    fun testDecodeNullJSON() {
-        assertThat(wrapper.decode(null, USER_ID)).isNull()
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/clock/SmallClockPositionTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/clock/SmallClockPositionTest.kt
deleted file mode 100644
index 3a27e35..0000000
--- a/packages/SystemUI/tests/src/com/android/keyguard/clock/SmallClockPositionTest.kt
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.clock
-
-import android.testing.AndroidTestingRunner
-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
-
-@RunWith(AndroidTestingRunner::class)
-@SmallTest
-class SmallClockPositionTest : SysuiTestCase() {
-
-    private val statusBarHeight = 100
-    private val lockPadding = 15
-    private val lockHeight = 35
-    private val burnInY = 20
-
-    private lateinit var position: SmallClockPosition
-
-    @Before
-    fun setUp() {
-        position = SmallClockPosition(statusBarHeight, lockPadding, lockHeight, burnInY)
-    }
-
-    @Test
-    fun loadResources() {
-        // Cover constructor taking Resources object.
-        position = SmallClockPosition(context)
-        position.setDarkAmount(1f)
-        assertThat(position.preferredY).isGreaterThan(0)
-    }
-
-    @Test
-    fun darkPosition() {
-        // GIVEN on AOD
-        position.setDarkAmount(1f)
-        // THEN Y is sum of statusBarHeight, lockPadding, lockHeight, lockPadding, burnInY
-        assertThat(position.preferredY).isEqualTo(185)
-    }
-
-    @Test
-    fun lockPosition() {
-        // GIVEN on lock screen
-        position.setDarkAmount(0f)
-        // THEN Y position is statusBarHeight + lockPadding + lockHeight + lockPadding
-        // (100 + 15 + 35 + 15 = 165)
-        assertThat(position.preferredY).isEqualTo(165)
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/clock/ViewPreviewerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/clock/ViewPreviewerTest.kt
deleted file mode 100644
index 5ece6ef..0000000
--- a/packages/SystemUI/tests/src/com/android/keyguard/clock/ViewPreviewerTest.kt
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.clock
-
-import android.content.Context
-import com.google.common.truth.Truth.assertThat
-
-import android.graphics.Canvas
-import android.graphics.Color
-import android.testing.AndroidTestingRunner
-import android.view.View
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(AndroidTestingRunner::class)
-@SmallTest
-class ViewPreviewerTest : SysuiTestCase() {
-
-    private lateinit var previewer: ViewPreviewer
-    private lateinit var view: View
-
-    @Before
-    fun setUp() {
-        previewer = ViewPreviewer()
-        view = TestView(context)
-    }
-
-    @Test
-    fun testCreatePreview() {
-        val width = 100
-        val height = 100
-        // WHEN a preview image is created
-        val bitmap = previewer.createPreview(view, width, height)!!
-        // THEN the bitmap has the expected width and height
-        assertThat(bitmap.height).isEqualTo(height)
-        assertThat(bitmap.width).isEqualTo(width)
-        assertThat(bitmap.getPixel(0, 0)).isEqualTo(Color.RED)
-    }
-
-    class TestView(context: Context) : View(context) {
-        override fun onDraw(canvas: Canvas?) {
-            super.onDraw(canvas)
-            canvas?.drawColor(Color.RED)
-        }
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java b/packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java
index a0fdc8f..24a5e80 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java
@@ -48,8 +48,7 @@
     @Test
     public void testInitDependency() throws ExecutionException, InterruptedException {
         Dependency.clearDependencies();
-        SystemUIInitializer initializer =
-                SystemUIInitializerFactory.createFromConfigNoAssert(mContext);
+        SystemUIInitializer initializer = new SystemUIInitializerImpl(mContext);
         initializer.init(true);
         Dependency dependency = initializer.getSysUIComponent().createDependency();
         dependency.start();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorHwcLayerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorHwcLayerTest.kt
index 8207fa6..d500b5a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorHwcLayerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorHwcLayerTest.kt
@@ -74,7 +74,8 @@
 
         val decorationSupport = DisplayDecorationSupport()
         decorationSupport.format = PixelFormat.R_8
-        decorHwcLayer = Mockito.spy(ScreenDecorHwcLayer(mContext, decorationSupport))
+        decorHwcLayer =
+            Mockito.spy(ScreenDecorHwcLayer(mContext, decorationSupport, /* debug */ false))
         whenever(decorHwcLayer.width).thenReturn(displayWidth)
         whenever(decorHwcLayer.height).thenReturn(displayHeight)
         whenever(decorHwcLayer.context).thenReturn(mockContext)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
index 4cf5a4b..796e665 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
@@ -43,7 +43,6 @@
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -63,6 +62,7 @@
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
+import android.util.PathParser;
 import android.util.Size;
 import android.view.Display;
 import android.view.DisplayCutout;
@@ -83,6 +83,7 @@
 import com.android.systemui.decor.CornerDecorProvider;
 import com.android.systemui.decor.CutoutDecorProviderFactory;
 import com.android.systemui.decor.CutoutDecorProviderImpl;
+import com.android.systemui.decor.DebugRoundedCornerModel;
 import com.android.systemui.decor.DecorProvider;
 import com.android.systemui.decor.DecorProviderFactory;
 import com.android.systemui.decor.FaceScanningOverlayProviderImpl;
@@ -95,8 +96,8 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.settings.FakeDisplayTracker;
 import com.android.systemui.settings.UserTracker;
+import com.android.systemui.statusbar.commandline.CommandRegistry;
 import com.android.systemui.statusbar.events.PrivacyDotViewController;
-import com.android.systemui.tuner.TunerService;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.concurrency.FakeThreadFactory;
 import com.android.systemui.util.settings.FakeSettings;
@@ -139,7 +140,7 @@
     @Mock
     private Display mDisplay;
     @Mock
-    private TunerService mTunerService;
+    private CommandRegistry mCommandRegistry;
     @Mock
     private UserTracker mUserTracker;
     @Mock
@@ -233,8 +234,9 @@
                 mExecutor,
                 new ScreenDecorationsLogger(logcatLogBuffer("TestLogBuffer"))));
 
-        mScreenDecorations = spy(new ScreenDecorations(mContext, mExecutor, mSecureSettings,
-                mTunerService, mUserTracker, mDisplayTracker, mDotViewController, mThreadFactory,
+        mScreenDecorations = spy(new ScreenDecorations(mContext, mSecureSettings,
+                mCommandRegistry, mUserTracker, mDisplayTracker, mDotViewController,
+                mThreadFactory,
                 mPrivacyDotDecorProviderFactory, mFaceScanningProviderFactory,
                 new ScreenDecorationsLogger(logcatLogBuffer("TestLogBuffer")),
                 mAuthController) {
@@ -251,12 +253,6 @@
             }
 
             @Override
-            public void onTuningChanged(String key, String newValue) {
-                super.onTuningChanged(key, newValue);
-                mExecutor.runAllReady();
-            }
-
-            @Override
             protected void updateOverlayWindowVisibilityIfViewExists(@Nullable View view) {
                 super.updateOverlayWindowVisibilityIfViewExists(view);
                 mExecutor.runAllReady();
@@ -268,9 +264,10 @@
             }
         });
         mScreenDecorations.mDisplayInfo = mDisplayInfo;
+        // Make sure tests are never run starting in debug mode
+        mScreenDecorations.setDebug(false);
         doReturn(1f).when(mScreenDecorations).getPhysicalPixelDisplaySizeRatio();
         doNothing().when(mScreenDecorations).updateOverlayProviderViews(any());
-        reset(mTunerService);
 
         try {
             mPrivacyDotShowingListener = mScreenDecorations.mPrivacyDotShowingListener.getClass()
@@ -464,8 +461,6 @@
         mScreenDecorations.start();
         // No views added.
         verifyOverlaysExistAndAdded(false, false, false, false, null);
-        // No Tuners tuned.
-        verify(mTunerService, never()).addTunable(any(), any());
         // No dot controller init
         verify(mDotViewController, never()).initialize(any(), any(), any(), any());
     }
@@ -497,8 +492,6 @@
         // Face scanning doesn't exist
         verifyFaceScanningViewExists(false);
 
-        // One tunable.
-        verify(mTunerService, times(1)).addTunable(any(), any());
         // Dot controller init
         verify(mDotViewController, times(1)).initialize(
                 isA(View.class), isA(View.class), isA(View.class), isA(View.class));
@@ -528,8 +521,6 @@
         // Face scanning doesn't exist
         verifyFaceScanningViewExists(false);
 
-        // One tunable.
-        verify(mTunerService, times(1)).addTunable(any(), any());
         // No dot controller init
         verify(mDotViewController, never()).initialize(any(), any(), any(), any());
     }
@@ -560,8 +551,6 @@
         // Face scanning doesn't exist
         verifyFaceScanningViewExists(false);
 
-        // One tunable.
-        verify(mTunerService, times(1)).addTunable(any(), any());
         // Dot controller init
         verify(mDotViewController, times(1)).initialize(
                 isA(View.class), isA(View.class), isA(View.class), isA(View.class));
@@ -1073,18 +1062,89 @@
     }
 
     @Test
+    public void testDebugRoundedCorners_noDeviceCornersSet() {
+        setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
+                null /* roundedTopDrawable */, null /* roundedBottomDrawable */,
+                0 /* roundedPadding */, false /* privacyDot */, false /* faceScanning */);
+
+        mScreenDecorations.start();
+        // No rounded corners exist at this point
+        verifyOverlaysExistAndAdded(false, false, false, false, View.VISIBLE);
+
+        // Path from rounded.xml, scaled by 10x to produce 80x80 corners
+        Path debugPath = PathParser.createPathFromPathData("M8,0H0v8C0,3.6,3.6,0,8,0z");
+        // WHEN debug corners are added to the delegate
+        DebugRoundedCornerModel debugCorner = new DebugRoundedCornerModel(
+                debugPath,
+                80,
+                80,
+                10f,
+                10f
+        );
+        mScreenDecorations.mDebugRoundedCornerDelegate
+                .applyNewDebugCorners(debugCorner, debugCorner);
+
+        // AND debug mode is entered
+        mScreenDecorations.setDebug(true);
+        mExecutor.runAllReady();
+
+        // THEN the debug corners provide decor
+        List<DecorProvider> providers = mScreenDecorations.getProviders(false);
+        assertEquals(4, providers.size());
+
+        // Top and bottom overlays contain the debug rounded corners
+        verifyOverlaysExistAndAdded(false, true, false, true, View.VISIBLE);
+    }
+
+    @Test
+    public void testDebugRoundedCornersRemoved_noDeviceCornersSet() {
+        // GIVEN a device with no rounded corners defined
+        setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
+                null /* roundedTopDrawable */, null /* roundedBottomDrawable */,
+                0 /* roundedPadding */, false /* privacyDot */, false /* faceScanning */);
+
+        mScreenDecorations.start();
+        // No rounded corners exist at this point
+        verifyOverlaysExistAndAdded(false, false, false, false, View.VISIBLE);
+
+        // Path from rounded.xml, scaled by 10x to produce 80x80 corners
+        Path debugPath = PathParser.createPathFromPathData("M8,0H0v8C0,3.6,3.6,0,8,0z");
+        // WHEN debug corners are added to the delegate
+        DebugRoundedCornerModel debugCorner = new DebugRoundedCornerModel(
+                debugPath,
+                80,
+                80,
+                10f,
+                10f
+        );
+        mScreenDecorations.mDebugRoundedCornerDelegate
+                .applyNewDebugCorners(debugCorner, debugCorner);
+
+        // AND debug mode is entered
+        mScreenDecorations.setDebug(true);
+        mExecutor.runAllReady();
+
+        // Top and bottom overlays contain the debug rounded corners
+        verifyOverlaysExistAndAdded(false, true, false, true, View.VISIBLE);
+
+        // WHEN debug is exited
+        mScreenDecorations.setDebug(false);
+        mExecutor.runAllReady();
+
+        // THEN the decor is removed
+        verifyOverlaysExistAndAdded(false, false, false, false, View.VISIBLE);
+        assertThat(mScreenDecorations.mDebugRoundedCornerDelegate.getHasBottom()).isFalse();
+        assertThat(mScreenDecorations.mDebugRoundedCornerDelegate.getHasTop()).isFalse();
+    }
+
+    @Test
     public void testRegistration_From_NoOverlay_To_HasOverlays() {
         doReturn(false).when(mScreenDecorations).hasOverlays();
         mScreenDecorations.start();
-        verify(mTunerService, times(0)).addTunable(any(), any());
-        verify(mTunerService, times(1)).removeTunable(any());
         assertThat(mScreenDecorations.mIsRegistered, is(false));
-        reset(mTunerService);
 
         doReturn(true).when(mScreenDecorations).hasOverlays();
         mScreenDecorations.onConfigurationChanged(new Configuration());
-        verify(mTunerService, times(1)).addTunable(any(), any());
-        verify(mTunerService, times(0)).removeTunable(any());
         assertThat(mScreenDecorations.mIsRegistered, is(true));
     }
 
@@ -1093,14 +1153,9 @@
         doReturn(true).when(mScreenDecorations).hasOverlays();
 
         mScreenDecorations.start();
-        verify(mTunerService, times(1)).addTunable(any(), any());
-        verify(mTunerService, times(0)).removeTunable(any());
         assertThat(mScreenDecorations.mIsRegistered, is(true));
-        reset(mTunerService);
 
         mScreenDecorations.onConfigurationChanged(new Configuration());
-        verify(mTunerService, times(0)).addTunable(any(), any());
-        verify(mTunerService, times(0)).removeTunable(any());
         assertThat(mScreenDecorations.mIsRegistered, is(true));
     }
 
@@ -1109,15 +1164,10 @@
         doReturn(true).when(mScreenDecorations).hasOverlays();
 
         mScreenDecorations.start();
-        verify(mTunerService, times(1)).addTunable(any(), any());
-        verify(mTunerService, times(0)).removeTunable(any());
         assertThat(mScreenDecorations.mIsRegistered, is(true));
-        reset(mTunerService);
 
         doReturn(false).when(mScreenDecorations).hasOverlays();
         mScreenDecorations.onConfigurationChanged(new Configuration());
-        verify(mTunerService, times(0)).addTunable(any(), any());
-        verify(mTunerService, times(1)).removeTunable(any());
         assertThat(mScreenDecorations.mIsRegistered, is(false));
     }
 
@@ -1180,8 +1230,9 @@
         mFaceScanningProviders.add(mFaceScanningDecorProvider);
         when(mFaceScanningProviderFactory.getProviders()).thenReturn(mFaceScanningProviders);
         when(mFaceScanningProviderFactory.getHasProviders()).thenReturn(true);
-        ScreenDecorations screenDecorations = new ScreenDecorations(mContext, mExecutor,
-                mSecureSettings, mTunerService, mUserTracker, mDisplayTracker, mDotViewController,
+        ScreenDecorations screenDecorations = new ScreenDecorations(mContext,
+                mSecureSettings, mCommandRegistry, mUserTracker, mDisplayTracker,
+                mDotViewController,
                 mThreadFactory, mPrivacyDotDecorProviderFactory, mFaceScanningProviderFactory,
                 new ScreenDecorationsLogger(logcatLogBuffer("TestLogBuffer")), mAuthController);
         screenDecorations.start();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/AccessibilityLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/accessibility/AccessibilityLoggerTest.kt
new file mode 100644
index 0000000..deacac3
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/AccessibilityLoggerTest.kt
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.accessibility
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.UiEventLogger
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.accessibility.AccessibilityLogger.MagnificationSettingsEvent.MAGNIFICATION_SETTINGS_PANEL_CLOSED
+import com.android.systemui.accessibility.AccessibilityLogger.MagnificationSettingsEvent.MAGNIFICATION_SETTINGS_PANEL_OPENED
+import com.android.systemui.util.time.FakeSystemClock
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mock
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.junit.MockitoJUnit
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class AccessibilityLoggerTest : SysuiTestCase() {
+    @JvmField @Rule val mockito = MockitoJUnit.rule()
+
+    private val fakeClock = FakeSystemClock()
+    @Mock private lateinit var fakeLogger: UiEventLogger
+
+    private lateinit var a11yLogger: AccessibilityLogger
+
+    @Before
+    fun setup() {
+        a11yLogger = AccessibilityLogger(fakeLogger, fakeClock)
+    }
+
+    @Test
+    fun logThrottled_onceWithinWindow() {
+        a11yLogger.logThrottled(MAGNIFICATION_SETTINGS_PANEL_OPENED, 1000)
+        a11yLogger.logThrottled(MAGNIFICATION_SETTINGS_PANEL_OPENED, 1000)
+        a11yLogger.logThrottled(MAGNIFICATION_SETTINGS_PANEL_OPENED, 1000)
+        fakeClock.advanceTime(100L)
+        a11yLogger.logThrottled(MAGNIFICATION_SETTINGS_PANEL_OPENED, 1000)
+        fakeClock.advanceTime(900L)
+        a11yLogger.logThrottled(MAGNIFICATION_SETTINGS_PANEL_OPENED, 1000)
+        fakeClock.advanceTime(1100L)
+        a11yLogger.logThrottled(MAGNIFICATION_SETTINGS_PANEL_OPENED, 1000)
+
+        verify(fakeLogger, times(2)).log(eq(MAGNIFICATION_SETTINGS_PANEL_OPENED))
+    }
+
+    @Test
+    fun logThrottled_interlacedLogsAllWithinWindow() {
+        a11yLogger.logThrottled(MAGNIFICATION_SETTINGS_PANEL_OPENED, 1000)
+        a11yLogger.logThrottled(MAGNIFICATION_SETTINGS_PANEL_CLOSED, 1000)
+        fakeClock.advanceTime(100L)
+        a11yLogger.logThrottled(MAGNIFICATION_SETTINGS_PANEL_CLOSED, 1000)
+        fakeClock.advanceTime(200L)
+        a11yLogger.logThrottled(MAGNIFICATION_SETTINGS_PANEL_OPENED, 1000)
+        fakeClock.advanceTime(1100L)
+        a11yLogger.logThrottled(MAGNIFICATION_SETTINGS_PANEL_OPENED, 1000)
+
+        verify(fakeLogger, times(3)).log(eq(MAGNIFICATION_SETTINGS_PANEL_OPENED))
+        verify(fakeLogger).log(eq(MAGNIFICATION_SETTINGS_PANEL_CLOSED))
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IWindowMagnificationConnectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IWindowMagnificationConnectionTest.java
index f64db78f..67d6aa8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IWindowMagnificationConnectionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IWindowMagnificationConnectionTest.java
@@ -16,8 +16,10 @@
 
 package com.android.systemui.accessibility;
 
+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.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doAnswer;
@@ -185,6 +187,20 @@
         verify(mMagnificationSettingsController).closeMagnificationSettings();
     }
 
+    @Test
+    public void onUserMagnificationScaleChanged() throws RemoteException {
+        final int testUserId = 1;
+        final float testScale = 3.0f;
+        mIWindowMagnificationConnection.onUserMagnificationScaleChanged(
+                testUserId, TEST_DISPLAY, testScale);
+        waitForIdleSync();
+
+        assertTrue(mWindowMagnification.mUsersScales.contains(testUserId));
+        assertEquals(mWindowMagnification.mUsersScales.get(testUserId).get(TEST_DISPLAY),
+                (Float) testScale);
+        verify(mMagnificationSettingsController).setMagnificationScale(eq(testScale));
+    }
+
     private class FakeControllerSupplier extends
             DisplayIdIndexSupplier<WindowMagnificationController> {
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationSettingsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationSettingsControllerTest.java
index 665246b..9eead6a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationSettingsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationSettingsControllerTest.java
@@ -73,9 +73,9 @@
 
     @Test
     public void testShowSettingsPanel() {
-        mMagnificationSettingsController.showMagnificationSettings();
+        mMagnificationSettingsController.toggleSettingsPanelVisibility();
 
-        verify(mWindowMagnificationSettings).showSettingPanel();
+        verify(mWindowMagnificationSettings).toggleSettingsPanelVisibility();
     }
 
     @Test
@@ -86,6 +86,14 @@
     }
 
     @Test
+    public void testSetMagnificationScale() {
+        final float scale = 3.0f;
+        mMagnificationSettingsController.setMagnificationScale(scale);
+
+        verify(mWindowMagnificationSettings).setMagnificationScale(eq(scale));
+    }
+
+    @Test
     public void testOnConfigurationChanged_notifySettingsPanel() {
         mMagnificationSettingsController.onConfigurationChanged(ActivityInfo.CONFIG_DENSITY);
 
@@ -145,10 +153,11 @@
     @Test
     public void testPanelOnMagnifierScale_delegateToCallback() {
         final float scale = 3.0f;
+        final boolean updatePersistence = true;
         mMagnificationSettingsController.mWindowMagnificationSettingsCallback
-                .onMagnifierScale(scale);
+                .onMagnifierScale(scale, updatePersistence);
 
         verify(mMagnificationSettingControllerCallback).onMagnifierScale(
-                eq(mContext.getDisplayId()), eq(scale));
+                eq(mContext.getDisplayId()), eq(scale), eq(updatePersistence));
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/TEST_MAPPING b/packages/SystemUI/tests/src/com/android/systemui/accessibility/TEST_MAPPING
new file mode 100644
index 0000000..d3ab4ad
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "imports": [
+    {
+      "path": "packages/SystemUI/src/com/android/systemui/accessibility/TEST_MAPPING"
+    }
+  ]
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
index b5e0df5..09d0eeb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
@@ -53,7 +53,6 @@
 
 import org.junit.After;
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Answers;
@@ -66,7 +65,6 @@
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicReference;
 
-@Ignore
 @LargeTest
 @RunWith(AndroidTestingRunner.class)
 public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
@@ -74,8 +72,11 @@
     private static final float DEFAULT_SCALE = 4.0f;
     private static final float DEFAULT_CENTER_X = 400.0f;
     private static final float DEFAULT_CENTER_Y = 500.0f;
-    // The duration couldn't too short, otherwise the ValueAnimator won't work in expectation.
-    private static final long ANIMATION_DURATION_MS = 300;
+    // The duration and period couldn't too short, otherwise the ValueAnimator and
+    //    Instrumentation.runOnMainSync won't work in expectation. (b/288926821)
+    private static final long ANIMATION_DURATION_MS = 600;
+    private static final long WAIT_FULL_ANIMATION_PERIOD = 1000;
+    private static final long WAIT_INTERMEDIATE_ANIMATION_PERIOD = 250;
 
     private AtomicReference<Float> mCurrentScale = new AtomicReference<>((float) 0);
     private AtomicReference<Float> mCurrentCenterX = new AtomicReference<>((float) 0);
@@ -117,8 +118,8 @@
         mWindowManager = spy(new TestableWindowManager(wm));
         mContext.addMockSystemService(Context.WINDOW_SERVICE, mWindowManager);
 
-        mWaitingAnimationPeriod = 2 * ANIMATION_DURATION_MS;
-        mWaitIntermediateAnimationPeriod = ANIMATION_DURATION_MS / 2;
+        mWaitingAnimationPeriod = WAIT_FULL_ANIMATION_PERIOD;
+        mWaitIntermediateAnimationPeriod = WAIT_INTERMEDIATE_ANIMATION_PERIOD;
         mWindowMagnificationAnimationController = new WindowMagnificationAnimationController(
                 mContext, newValueAnimator());
         mController = new SpyWindowMagnificationController(mContext, mHandler,
@@ -511,10 +512,11 @@
         assertNotNull(mirrorView);
         mirrorView.getBoundsOnScreen(mirrorViewBound);
 
-        assertEquals(mirrorViewBound.exactCenterX() - windowBounds.exactCenterX(),
-                Math.round(offsetRatio * mirrorViewBound.width() / 2), 0.1f);
-        assertEquals(mirrorViewBound.exactCenterY() - windowBounds.exactCenterY(),
-                Math.round(offsetRatio * mirrorViewBound.height() / 2), 0.1f);
+        assertEquals((int) (offsetRatio * mirrorViewBound.width() / 2),
+                (int) (mirrorViewBound.exactCenterX() - windowBounds.exactCenterX()));
+        assertEquals((int) (offsetRatio * mirrorViewBound.height() / 2),
+                (int) (mirrorViewBound.exactCenterY() - windowBounds.exactCenterY()));
+
     }
 
     @Test
@@ -748,7 +750,7 @@
     }
 
     @Test
-    public void moveWindowMagnifier_enabled_vertical_only_expectedValue() {
+    public void moveWindowMagnifierWithYXRatioLargerThanBase_enabled_movedOnlyVertically() {
         enableWindowMagnificationWithoutAnimation();
 
         // should move vertically since offsetY/offsetX > HORIZONTAL_LOCK_BASE
@@ -764,7 +766,7 @@
     }
 
     @Test
-    public void moveWindowMagnifier_enabled_horinzontal_only_expectedValue() {
+    public void moveWindowMagnifierWithYXRatioLessThanBase_enabled_movedOnlyHorizontally() {
         enableWindowMagnificationWithoutAnimation();
 
         // should move vertically since offsetY/offsetX <= HORIZONTAL_LOCK_BASE
@@ -780,7 +782,7 @@
     }
 
     @Test
-    public void moveWindowMagnifier_enabled_setDiagonalEnabled_expectedValues() {
+    public void moveWindowMagnifier_magnifierEnabledAndDiagonalEnabled_movedDiagonally() {
         enableWindowMagnificationWithoutAnimation();
 
         final float offsetX = 50.0f;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
index 9c36af3..56f8160 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.accessibility;
 
+import static android.content.pm.PackageManager.FEATURE_WINDOW_MAGNIFICATION;
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
 import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
@@ -96,6 +97,7 @@
 import com.google.common.util.concurrent.AtomicDouble;
 
 import org.junit.After;
+import org.junit.Assume;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -147,8 +149,15 @@
     private View.OnTouchListener mTouchListener;
     private MotionEventHelper mMotionEventHelper = new MotionEventHelper();
 
+    /**
+     *  return whether window magnification is supported for current test context.
+     */
+    private boolean isWindowModeSupported() {
+        return getContext().getPackageManager().hasSystemFeature(FEATURE_WINDOW_MAGNIFICATION);
+    }
+
     @Before
-    public void setUp() throws RemoteException {
+    public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
         mContext = Mockito.spy(getContext());
         mHandler = new FakeHandler(TestableLooper.get(this).getLooper());
@@ -202,6 +211,9 @@
             return null;
         }).when(mSpyView).setOnTouchListener(
                 any(View.OnTouchListener.class));
+
+        // skip test if window magnification is not supported to prevent fail results. (b/279820875)
+        Assume.assumeTrue(isWindowModeSupported());
     }
 
     @After
@@ -633,10 +645,12 @@
         assertTrue(
                 mirrorView.performAccessibilityAction(R.id.accessibility_action_zoom_out, null));
         // Minimum scale is 1.0.
-        verify(mWindowMagnifierCallback).onPerformScaleAction(eq(displayId), eq(1.0f));
+        verify(mWindowMagnifierCallback).onPerformScaleAction(
+                eq(displayId), /* scale= */ eq(1.0f), /* updatePersistence= */ eq(true));
 
         assertTrue(mirrorView.performAccessibilityAction(R.id.accessibility_action_zoom_in, null));
-        verify(mWindowMagnifierCallback).onPerformScaleAction(eq(displayId), eq(2.5f));
+        verify(mWindowMagnifierCallback).onPerformScaleAction(
+                eq(displayId), /* scale= */ eq(2.5f), /* updatePersistence= */ eq(true));
 
         // TODO: Verify the final state when the mirror surface is visible.
         assertTrue(mirrorView.performAccessibilityAction(R.id.accessibility_action_move_up, null));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java
index ce96708..eddb8d1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java
@@ -19,7 +19,6 @@
 import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CAPABILITY;
 import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL;
 import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
-import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_NONE;
 import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -28,7 +27,11 @@
 import static junit.framework.Assert.assertNotNull;
 
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -37,6 +40,7 @@
 import android.content.Context;
 import android.content.pm.ActivityInfo;
 import android.database.ContentObserver;
+import android.graphics.Rect;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.testing.AndroidTestingRunner;
@@ -49,23 +53,27 @@
 import android.widget.CompoundButton;
 import android.widget.LinearLayout;
 
+import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView;
+import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView.OnSeekBarWithIconButtonsChangeListener;
 import com.android.systemui.util.settings.SecureSettings;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper(setAsMainLooper = true)
+@TestableLooper.RunWithLooper
 public class WindowMagnificationSettingsTest extends SysuiTestCase {
 
     private static final int MAGNIFICATION_SIZE_SMALL = 1;
@@ -73,6 +81,7 @@
     private static final int MAGNIFICATION_SIZE_LARGE = 3;
 
     private ViewGroup mSettingView;
+    private SeekBarWithIconButtonsView mZoomSeekbar;
     @Mock
     private AccessibilityManager mAccessibilityManager;
     @Mock
@@ -85,6 +94,11 @@
     private WindowMagnificationSettings mWindowMagnificationSettings;
     private MotionEventHelper mMotionEventHelper = new MotionEventHelper();
 
+    private ArgumentCaptor<Float> mSecureSettingsScaleCaptor;
+    private ArgumentCaptor<String> mSecureSettingsNameCaptor;
+    private ArgumentCaptor<Integer> mSecureSettingsUserHandleCaptor;
+    private ArgumentCaptor<Float> mCallbackMagnifierScaleCaptor;
+
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
@@ -100,6 +114,11 @@
                 mSecureSettings);
 
         mSettingView = mWindowMagnificationSettings.getSettingView();
+        mZoomSeekbar = mSettingView.findViewById(R.id.magnifier_zoom_slider);
+        mSecureSettingsScaleCaptor = ArgumentCaptor.forClass(Float.class);
+        mSecureSettingsNameCaptor = ArgumentCaptor.forClass(String.class);
+        mSecureSettingsUserHandleCaptor = ArgumentCaptor.forClass(Integer.class);
+        mCallbackMagnifierScaleCaptor = ArgumentCaptor.forClass(Float.class);
     }
 
     @After
@@ -275,6 +294,39 @@
     }
 
     @Test
+    public void onScreenSizeChanged_resetPositionToRightBottomCorner() {
+        setupMagnificationCapabilityAndMode(
+                /* capability= */ ACCESSIBILITY_MAGNIFICATION_MODE_ALL,
+                /* mode= */ ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+        mWindowMagnificationSettings.showSettingPanel();
+
+        // move the panel to the center of draggable window bounds
+        mWindowMagnificationSettings.mParams.x =
+                mWindowMagnificationSettings.mDraggableWindowBounds.centerX();
+        mWindowMagnificationSettings.mParams.y =
+                mWindowMagnificationSettings.mDraggableWindowBounds.centerY();
+        mWindowMagnificationSettings.updateButtonViewLayoutIfNeeded();
+
+        final Rect testWindowBounds = new Rect(
+                mWindowManager.getCurrentWindowMetrics().getBounds());
+        testWindowBounds.set(testWindowBounds.left, testWindowBounds.top,
+                testWindowBounds.right + 200, testWindowBounds.bottom + 50);
+        mWindowManager.setWindowBounds(testWindowBounds);
+
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+            mWindowMagnificationSettings.onConfigurationChanged(ActivityInfo.CONFIG_SCREEN_SIZE);
+        });
+
+        // the panel position should be reset to the bottom-right corner
+        assertEquals(
+                mWindowMagnificationSettings.mParams.x,
+                mWindowMagnificationSettings.mDraggableWindowBounds.right);
+        assertEquals(
+                mWindowMagnificationSettings.mParams.y,
+                mWindowMagnificationSettings.mDraggableWindowBounds.bottom);
+    }
+
+    @Test
     public void showSettingsPanel_observerRegistered() {
         setupMagnificationCapabilityAndMode(
                 /* capability= */ ACCESSIBILITY_MAGNIFICATION_MODE_ALL,
@@ -300,6 +352,160 @@
         verify(mSecureSettings).unregisterContentObserver(any(ContentObserver.class));
     }
 
+    @Test
+    public void seekbarProgress_justInflated_maxValueAndProgressSetCorrectly() {
+        mWindowMagnificationSettings.setMagnificationScale(2f);
+        mWindowMagnificationSettings.inflateView();
+
+        // inflateView() would create new settingsView in WindowMagnificationSettings so we
+        // need to retrieve the new mZoomSeekbar
+        mSettingView = mWindowMagnificationSettings.getSettingView();
+        mZoomSeekbar = mSettingView.findViewById(R.id.magnifier_zoom_slider);
+        assertThat(mZoomSeekbar.getProgress()).isEqualTo(10);
+        assertThat(mZoomSeekbar.getMax()).isEqualTo(70);
+    }
+
+    @Test
+    public void seekbarProgress_minMagnification_seekbarProgressIsCorrect() {
+        mWindowMagnificationSettings.setMagnificationScale(1f);
+        setupMagnificationCapabilityAndMode(
+                /* capability= */ ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW,
+                /* mode= */ ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+
+        mWindowMagnificationSettings.showSettingPanel();
+
+        // Seekbar index from 0 to 70. 1.0f scale (A11Y_SCALE_MIN_VALUE) would correspond to 0.
+        assertThat(mZoomSeekbar.getProgress()).isEqualTo(0);
+    }
+
+    @Test
+    public void seekbarProgress_belowMinMagnification_seekbarProgressIsZero() {
+        mWindowMagnificationSettings.setMagnificationScale(0f);
+        setupMagnificationCapabilityAndMode(
+                /* capability= */ ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW,
+                /* mode= */ ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+
+        mWindowMagnificationSettings.showSettingPanel();
+
+        assertThat(mZoomSeekbar.getProgress()).isEqualTo(0);
+    }
+
+    @Test
+    public void seekbarProgress_magnificationBefore_seekbarProgressIsHalf() {
+        mWindowMagnificationSettings.setMagnificationScale(4f);
+        setupMagnificationCapabilityAndMode(
+                /* capability= */ ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW,
+                /* mode= */ ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+
+        mWindowMagnificationSettings.showSettingPanel();
+
+        // float scale : from 1.0f to 8.0f, seekbar index from 0 to 70.
+        // 4.0f would correspond to 30.
+        assertThat(mZoomSeekbar.getProgress()).isEqualTo(30);
+    }
+
+    @Test
+    public void seekbarProgress_maxMagnificationBefore_seekbarProgressIsMax() {
+        mWindowMagnificationSettings.setMagnificationScale(8f);
+        setupMagnificationCapabilityAndMode(
+                /* capability= */ ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW,
+                /* mode= */ ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+
+        mWindowMagnificationSettings.showSettingPanel();
+
+        // 8.0f is max magnification {@link MagnificationScaleProvider#MAX_SCALE}.
+        // Max zoom seek bar is 70.
+        assertThat(mZoomSeekbar.getProgress()).isEqualTo(70);
+    }
+
+    @Test
+    public void seekbarProgress_aboveMaxMagnificationBefore_seekbarProgressIsMax() {
+        mWindowMagnificationSettings.setMagnificationScale(9f);
+        setupMagnificationCapabilityAndMode(
+                /* capability= */ ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW,
+                /* mode= */ ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+
+        mWindowMagnificationSettings.showSettingPanel();
+
+        // Max zoom seek bar is 70.
+        assertThat(mZoomSeekbar.getProgress()).isEqualTo(70);
+    }
+
+    @Test
+    public void onSeekBarProgressChanged_fromUserFalse_callbackNotTriggered() {
+        OnSeekBarWithIconButtonsChangeListener onChangeListener =
+                mZoomSeekbar.getOnSeekBarWithIconButtonsChangeListener();
+        onChangeListener.onProgressChanged(
+                mZoomSeekbar.getSeekbar(), /* progress= */ 30, /* fromUser= */ false);
+
+        verify(mWindowMagnificationSettingsCallback, never())
+                .onMagnifierScale(/* scale= */ anyFloat(), /* updatePersistence= */ eq(false));
+    }
+
+    @Test
+    public void onSeekBarProgressChangedToRoughlyHalf_fromUserTrue_callbackUpdated() {
+        OnSeekBarWithIconButtonsChangeListener onChangeListener =
+                mZoomSeekbar.getOnSeekBarWithIconButtonsChangeListener();
+        onChangeListener.onProgressChanged(
+                mZoomSeekbar.getSeekbar(), /* progress= */ 30, /* fromUser= */ true);
+
+        verifyCallbackOnMagnifierScale(4f);
+    }
+
+    @Test
+    public void onSeekBarProgressChangedToMin_fromUserTrue_callbackUpdated() {
+        OnSeekBarWithIconButtonsChangeListener onChangeListener =
+                mZoomSeekbar.getOnSeekBarWithIconButtonsChangeListener();
+        onChangeListener.onProgressChanged(
+                mZoomSeekbar.getSeekbar(), /* progress= */ 0, /* fromUser= */ true);
+
+        verifyCallbackOnMagnifierScale(1f);
+    }
+
+    @Test
+    public void onSeekBarProgressChangedToMax_fromUserTrue_callbackUpdated() {
+        OnSeekBarWithIconButtonsChangeListener onChangeListener =
+                mZoomSeekbar.getOnSeekBarWithIconButtonsChangeListener();
+        onChangeListener.onProgressChanged(
+                mZoomSeekbar.getSeekbar(), /* progress= */ 70, /* fromUser= */ true);
+
+        verifyCallbackOnMagnifierScale(8f);
+    }
+
+    @Test
+    public void onSeekbarUserInteractionFinalized_persistedScaleUpdated() {
+        OnSeekBarWithIconButtonsChangeListener onChangeListener =
+                mZoomSeekbar.getOnSeekBarWithIconButtonsChangeListener();
+
+        mZoomSeekbar.setProgress(30);
+        onChangeListener.onUserInteractionFinalized(
+                mZoomSeekbar.getSeekbar(),
+                OnSeekBarWithIconButtonsChangeListener.ControlUnitType.SLIDER);
+
+        // should trigger callback to update magnifier scale and persist the scale
+        verify(mWindowMagnificationSettingsCallback)
+                .onMagnifierScale(/* scale= */ eq(4f), /* updatePersistence= */ eq(true));
+    }
+
+    @Test
+    public void seekbarProgress_scaleUpdatedAfterSettingPanelOpened_progressAlsoUpdated() {
+        setupMagnificationCapabilityAndMode(
+                /* capability= */ ACCESSIBILITY_MAGNIFICATION_MODE_ALL,
+                /* mode= */ ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+        mWindowMagnificationSettings.showSettingPanel();
+
+        // Simulate outside changes.
+        mWindowMagnificationSettings.setMagnificationScale(4f);
+
+        assertThat(mZoomSeekbar.getProgress()).isEqualTo(30);
+    }
+
+    private void verifyCallbackOnMagnifierScale(float scale) {
+        verify(mWindowMagnificationSettingsCallback)
+                .onMagnifierScale(mCallbackMagnifierScaleCaptor.capture(), anyBoolean());
+        assertThat(mCallbackMagnifierScaleCaptor.getValue()).isWithin(0.01f).of(scale);
+    }
+
     private <T extends View> T getInternalView(@IdRes int idRes) {
         T view = mSettingView.findViewById(idRes);
         assertNotNull(view);
@@ -308,12 +514,12 @@
 
     private void setupMagnificationCapabilityAndMode(int capability, int mode) {
         when(mSecureSettings.getIntForUser(
-                Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CAPABILITY,
-                ACCESSIBILITY_MAGNIFICATION_MODE_NONE,
-                UserHandle.USER_CURRENT)).thenReturn(capability);
+                eq(Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CAPABILITY),
+                anyInt(),
+                eq(UserHandle.USER_CURRENT))).thenReturn(capability);
         when(mSecureSettings.getIntForUser(
-                Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE,
-                ACCESSIBILITY_MAGNIFICATION_MODE_NONE,
-                UserHandle.USER_CURRENT)).thenReturn(mode);
+                eq(Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE),
+                anyInt(),
+                eq(UserHandle.USER_CURRENT))).thenReturn(mode);
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java
index 38ecec0..d7b6602 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java
@@ -110,13 +110,15 @@
             mWindowMagnification.mMagnificationSettingsControllerCallback
                     .onSettingsPanelVisibilityChanged(TEST_DISPLAY, /* shown= */ true);
             return null;
-        }).when(mMagnificationSettingsController).showMagnificationSettings();
+        }).when(mMagnificationSettingsController).toggleSettingsPanelVisibility();
         doAnswer(invocation -> {
             mWindowMagnification.mMagnificationSettingsControllerCallback
                     .onSettingsPanelVisibilityChanged(TEST_DISPLAY, /* shown= */ false);
             return null;
         }).when(mMagnificationSettingsController).closeMagnificationSettings();
 
+        when(mWindowMagnificationController.isActivated()).thenReturn(true);
+
         mCommandQueue = new CommandQueue(getContext(), mDisplayTracker);
         mWindowMagnification = new WindowMagnification(getContext(),
                 getContext().getMainThreadHandler(), mCommandQueue, mModeSwitchesController,
@@ -163,13 +165,15 @@
     @Test
     public void onPerformScaleAction_enabled_notifyCallback() throws RemoteException {
         final float newScale = 4.0f;
+        final boolean updatePersistence = true;
         mCommandQueue.requestWindowMagnificationConnection(true);
         waitForIdleSync();
 
         mWindowMagnification.mWindowMagnifierCallback
-                .onPerformScaleAction(TEST_DISPLAY, newScale);
+                .onPerformScaleAction(TEST_DISPLAY, newScale, updatePersistence);
 
-        verify(mConnectionCallback).onPerformScaleAction(TEST_DISPLAY, newScale);
+        verify(mConnectionCallback).onPerformScaleAction(
+                eq(TEST_DISPLAY), eq(newScale), eq(updatePersistence));
     }
 
     @Test
@@ -198,9 +202,11 @@
         mWindowMagnification.mWindowMagnifierCallback.onClickSettingsButton(TEST_DISPLAY);
         waitForIdleSync();
 
-        verify(mMagnificationSettingsController).showMagnificationSettings();
-        verify(mA11yLogger).log(
-                eq(MagnificationSettingsEvent.MAGNIFICATION_SETTINGS_PANEL_OPENED));
+        verify(mMagnificationSettingsController).toggleSettingsPanelVisibility();
+        verify(mA11yLogger).logWithPosition(
+                eq(MagnificationSettingsEvent.MAGNIFICATION_SETTINGS_PANEL_OPENED),
+                eq(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW)
+        );
     }
 
     @Test
@@ -211,8 +217,10 @@
         waitForIdleSync();
 
         verify(mWindowMagnificationController).changeMagnificationSize(eq(index));
-        verify(mA11yLogger).log(
-                eq(MagnificationSettingsEvent.MAGNIFICATION_SETTINGS_WINDOW_SIZE_SELECTED));
+        verify(mA11yLogger).logWithPosition(
+                eq(MagnificationSettingsEvent.MAGNIFICATION_SETTINGS_WINDOW_SIZE_SELECTED),
+                eq(index)
+        );
     }
 
     @Test
@@ -249,10 +257,14 @@
         mCommandQueue.requestWindowMagnificationConnection(true);
         waitForIdleSync();
         final float scale = 3.0f;
+        final boolean updatePersistence = false;
         mWindowMagnification.mMagnificationSettingsControllerCallback.onMagnifierScale(
-                TEST_DISPLAY, scale);
+                TEST_DISPLAY, scale, updatePersistence);
 
-        verify(mConnectionCallback).onPerformScaleAction(eq(TEST_DISPLAY), eq(scale));
+        verify(mConnectionCallback).onPerformScaleAction(
+                eq(TEST_DISPLAY), eq(scale), eq(updatePersistence));
+        verify(mA11yLogger).logThrottled(
+                eq(MagnificationSettingsEvent.MAGNIFICATION_SETTINGS_ZOOM_SLIDER_CHANGED));
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java
index f6ca938..fd258e3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java
@@ -29,7 +29,8 @@
 
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.util.settings.SecureSettings;
-import com.android.wm.shell.bubbles.DismissView;
+import com.android.wm.shell.bubbles.DismissViewUtils;
+import com.android.wm.shell.common.bubbles.DismissView;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -63,6 +64,7 @@
         final MenuView stubMenuView = new MenuView(mContext, stubMenuViewModel,
                 stubMenuViewAppearance);
         mDismissView = spy(new DismissView(mContext));
+        DismissViewUtils.setup(mDismissView);
         mDismissAnimationController = new DismissAnimationController(mDismissView, stubMenuView);
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
index d4efbe4..98be49f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
@@ -40,7 +40,8 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.accessibility.MotionEventHelper;
 import com.android.systemui.util.settings.SecureSettings;
-import com.android.wm.shell.bubbles.DismissView;
+import com.android.wm.shell.bubbles.DismissViewUtils;
+import com.android.wm.shell.common.bubbles.DismissView;
 
 import org.junit.After;
 import org.junit.Before;
@@ -88,6 +89,7 @@
         mStubMenuView.setTranslationY(0);
         mMenuAnimationController = spy(new MenuAnimationController(mStubMenuView));
         mDismissView = spy(new DismissView(mContext));
+        DismissViewUtils.setup(mDismissView);
         mDismissAnimationController =
                 spy(new DismissAnimationController(mDismissView, mStubMenuView));
         mTouchHandler = new MenuListViewTouchHandler(mMenuAnimationController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogTest.kt
index f10c21b..d5e6881 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogTest.kt
@@ -25,6 +25,8 @@
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView
+import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView.OnSeekBarWithIconButtonsChangeListener
+import com.android.systemui.settings.UserTracker
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.mockito.capture
 import com.android.systemui.util.mockito.whenever
@@ -38,6 +40,7 @@
 import org.junit.runner.RunWith
 import org.mockito.ArgumentCaptor
 import org.mockito.Captor
+import org.mockito.Mock
 import org.mockito.Mockito.spy
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
@@ -63,8 +66,9 @@
             .getResources()
             .getStringArray(com.android.settingslib.R.array.entryvalues_font_size)
 
+    @Mock private lateinit var userTracker: UserTracker
     @Captor
-    private lateinit var seekBarChangeCaptor: ArgumentCaptor<SeekBar.OnSeekBarChangeListener>
+    private lateinit var seekBarChangeCaptor: ArgumentCaptor<OnSeekBarWithIconButtonsChangeListener>
 
     @Before
     fun setUp() {
@@ -72,7 +76,7 @@
         val mainHandler = Handler(TestableLooper.get(this).getLooper())
         systemSettings = FakeSettings()
         // Guarantee that the systemSettings always starts with the default font scale.
-        systemSettings.putFloat(Settings.System.FONT_SCALE, 1.0f)
+        systemSettings.putFloatForUser(Settings.System.FONT_SCALE, 1.0f, userTracker.userId)
         secureSettings = FakeSettings()
         systemClock = FakeSystemClock()
         backgroundDelayableExecutor = FakeExecutor(systemClock)
@@ -82,6 +86,7 @@
                 systemSettings,
                 secureSettings,
                 systemClock,
+                userTracker,
                 mainHandler,
                 backgroundDelayableExecutor
             )
@@ -93,7 +98,12 @@
 
         val seekBar: SeekBar = fontScalingDialog.findViewById<SeekBar>(R.id.seekbar)!!
         val progress: Int = seekBar.getProgress()
-        val currentScale = systemSettings.getFloat(Settings.System.FONT_SCALE, /* def= */ 1.0f)
+        val currentScale =
+            systemSettings.getFloatForUser(
+                Settings.System.FONT_SCALE,
+                /* def= */ 1.0f,
+                userTracker.userId
+            )
 
         assertThat(currentScale).isEqualTo(fontSizeValueArray[progress].toFloat())
 
@@ -119,7 +129,12 @@
         backgroundDelayableExecutor.advanceClockToNext()
         backgroundDelayableExecutor.runAllReady()
 
-        val currentScale = systemSettings.getFloat(Settings.System.FONT_SCALE, /* def= */ 1.0f)
+        val currentScale =
+            systemSettings.getFloatForUser(
+                Settings.System.FONT_SCALE,
+                /* def= */ 1.0f,
+                userTracker.userId
+            )
         assertThat(seekBar.getProgress()).isEqualTo(1)
         assertThat(currentScale).isEqualTo(fontSizeValueArray[1].toFloat())
 
@@ -145,7 +160,12 @@
         backgroundDelayableExecutor.advanceClockToNext()
         backgroundDelayableExecutor.runAllReady()
 
-        val currentScale = systemSettings.getFloat(Settings.System.FONT_SCALE, /* def= */ 1.0f)
+        val currentScale =
+            systemSettings.getFloatForUser(
+                Settings.System.FONT_SCALE,
+                /* def= */ 1.0f,
+                userTracker.userId
+            )
         assertThat(seekBar.getProgress()).isEqualTo(fontSizeValueArray.size - 2)
         assertThat(currentScale)
             .isEqualTo(fontSizeValueArray[fontSizeValueArray.size - 2].toFloat())
@@ -157,18 +177,24 @@
     fun progressChanged_keyWasNotSetBefore_fontScalingHasBeenChangedIsOn() {
         fontScalingDialog.show()
 
-        val seekBarWithIconButtonsView: SeekBarWithIconButtonsView =
-            fontScalingDialog.findViewById(R.id.font_scaling_slider)!!
-        secureSettings.putInt(Settings.Secure.ACCESSIBILITY_FONT_SCALING_HAS_BEEN_CHANGED, OFF)
+        val iconStartFrame: ViewGroup = fontScalingDialog.findViewById(R.id.icon_start_frame)!!
+        secureSettings.putIntForUser(
+            Settings.Secure.ACCESSIBILITY_FONT_SCALING_HAS_BEEN_CHANGED,
+            OFF,
+            userTracker.userId
+        )
 
-        // Default seekbar progress for font size is 1, set it to another progress 0
-        seekBarWithIconButtonsView.setProgress(0)
+        // Default seekbar progress for font size is 1, click start icon to decrease the progress
+        iconStartFrame.performClick()
+        backgroundDelayableExecutor.runAllReady()
+        backgroundDelayableExecutor.advanceClockToNext()
         backgroundDelayableExecutor.runAllReady()
 
         val currentSettings =
-            secureSettings.getInt(
+            secureSettings.getIntForUser(
                 Settings.Secure.ACCESSIBILITY_FONT_SCALING_HAS_BEEN_CHANGED,
-                /* def = */ OFF
+                /* def = */ OFF,
+                userTracker.userId
             )
         assertThat(currentSettings).isEqualTo(ON)
 
@@ -184,7 +210,7 @@
             )
             .thenReturn(slider)
         fontScalingDialog.show()
-        verify(slider).setOnSeekBarChangeListener(capture(seekBarChangeCaptor))
+        verify(slider).setOnSeekBarWithIconButtonsChangeListener(capture(seekBarChangeCaptor))
         val seekBar: SeekBar = slider.findViewById(R.id.seekbar)!!
 
         // Default seekbar progress for font size is 1, simulate dragging to 0 without
@@ -199,7 +225,12 @@
         backgroundDelayableExecutor.runAllReady()
 
         // Verify that the scale of font size remains the default value 1.0f.
-        var systemScale = systemSettings.getFloat(Settings.System.FONT_SCALE, /* def= */ 1.0f)
+        var systemScale =
+            systemSettings.getFloatForUser(
+                Settings.System.FONT_SCALE,
+                /* def= */ 1.0f,
+                userTracker.userId
+            )
         assertThat(systemScale).isEqualTo(1.0f)
 
         // Simulate releasing the finger from the seekbar.
@@ -208,8 +239,22 @@
         backgroundDelayableExecutor.advanceClockToNext()
         backgroundDelayableExecutor.runAllReady()
 
+        // SeekBar interaction is finalized.
+        seekBarChangeCaptor.value.onUserInteractionFinalized(
+            seekBar,
+            OnSeekBarWithIconButtonsChangeListener.ControlUnitType.SLIDER
+        )
+        backgroundDelayableExecutor.runAllReady()
+        backgroundDelayableExecutor.advanceClockToNext()
+        backgroundDelayableExecutor.runAllReady()
+
         // Verify that the scale of font size has been updated.
-        systemScale = systemSettings.getFloat(Settings.System.FONT_SCALE, /* def= */ 1.0f)
+        systemScale =
+            systemSettings.getFloatForUser(
+                Settings.System.FONT_SCALE,
+                /* def= */ 1.0f,
+                userTracker.userId
+            )
         assertThat(systemScale).isEqualTo(fontSizeValueArray[0].toFloat())
 
         fontScalingDialog.dismiss()
@@ -224,7 +269,7 @@
             )
             .thenReturn(slider)
         fontScalingDialog.show()
-        verify(slider).setOnSeekBarChangeListener(capture(seekBarChangeCaptor))
+        verify(slider).setOnSeekBarWithIconButtonsChangeListener(capture(seekBarChangeCaptor))
         val seekBar: SeekBar = slider.findViewById(R.id.seekbar)!!
 
         // Default seekbar progress for font size is 1, simulate dragging to 0 without
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewLaunchAnimatorControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewLaunchAnimatorControllerTest.kt
index 0798d73..d1ac0e8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewLaunchAnimatorControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewLaunchAnimatorControllerTest.kt
@@ -21,6 +21,7 @@
 import android.widget.FrameLayout
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.view.LaunchableFrameLayout
 import org.junit.Assert.assertThrows
 import org.junit.Test
 import org.junit.runner.RunWith
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt
index da9ceb4..212dad7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt
@@ -8,6 +8,7 @@
 import android.widget.LinearLayout
 import android.widget.RelativeLayout
 import androidx.test.filters.SmallTest
+import com.android.app.animation.Interpolators
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.util.children
 import junit.framework.Assert.assertEquals
@@ -19,7 +20,6 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import com.android.app.animation.Interpolators
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
@@ -178,7 +178,7 @@
     }
 
     @Test
-    fun animatesRootAndChildren() {
+    fun animatesRootAndChildren_withoutExcludedViews() {
         setUpRootWithChildren()
 
         val success = ViewHierarchyAnimator.animate(rootView)
@@ -208,6 +208,40 @@
     }
 
     @Test
+    fun animatesRootAndChildren_withExcludedViews() {
+        setUpRootWithChildren()
+
+        val success = ViewHierarchyAnimator.animate(
+            rootView,
+            excludedViews = setOf(rootView.getChildAt(0))
+        )
+        // Change all bounds.
+        rootView.measure(
+                View.MeasureSpec.makeMeasureSpec(180, View.MeasureSpec.EXACTLY),
+                View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY)
+        )
+        rootView.layout(10 /* l */, 20 /* t */, 200 /* r */, 120 /* b */)
+
+        assertTrue(success)
+        assertNotNull(rootView.getTag(R.id.tag_animator))
+        assertNull(rootView.getChildAt(0).getTag(R.id.tag_animator))
+        assertNotNull(rootView.getChildAt(1).getTag(R.id.tag_animator))
+        // The initial values for the affected views should be those of the previous layout, while
+        // the excluded view should be at the final values from the beginning.
+        checkBounds(rootView, l = 0, t = 0, r = 200, b = 100)
+        checkBounds(rootView.getChildAt(0), l = 0, t = 0, r = 90, b = 100)
+        checkBounds(rootView.getChildAt(1), l = 100, t = 0, r = 200, b = 100)
+        endAnimation(rootView)
+        assertNull(rootView.getTag(R.id.tag_animator))
+        assertNull(rootView.getChildAt(0).getTag(R.id.tag_animator))
+        assertNull(rootView.getChildAt(1).getTag(R.id.tag_animator))
+        // The end values should be those of the latest layout.
+        checkBounds(rootView, l = 10, t = 20, r = 200, b = 120)
+        checkBounds(rootView.getChildAt(0), l = 0, t = 0, r = 90, b = 100)
+        checkBounds(rootView.getChildAt(1), l = 90, t = 0, r = 180, b = 100)
+    }
+
+    @Test
     fun animatesInvisibleViews() {
         rootView.layout(10 /* l */, 10 /* t */, 50 /* r */, 50 /* b */)
         rootView.visibility = View.INVISIBLE
diff --git a/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
index 1990c8f..a6ad4b2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
@@ -16,15 +16,20 @@
 
 package com.android.systemui.authentication.domain.interactor
 
+import android.app.admin.DevicePolicyManager
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.authentication.data.repository.AuthenticationRepository
+import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.scene.SceneTestUtils
 import com.google.common.truth.Truth.assertThat
+import kotlin.time.Duration.Companion.milliseconds
+import kotlin.time.Duration.Companion.seconds
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.advanceTimeBy
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
@@ -36,8 +41,8 @@
 @RunWith(JUnit4::class)
 class AuthenticationInteractorTest : SysuiTestCase() {
 
-    private val testScope = TestScope()
-    private val utils = SceneTestUtils(this, testScope)
+    private val utils = SceneTestUtils(this)
+    private val testScope = utils.testScope
     private val repository: AuthenticationRepository = utils.authenticationRepository()
     private val underTest =
         utils.authenticationInteractor(
@@ -45,69 +50,66 @@
         )
 
     @Test
-    fun authMethod() =
+    fun getAuthenticationMethod() =
         testScope.runTest {
-            val authMethod by collectLastValue(underTest.authenticationMethod)
-            assertThat(authMethod).isEqualTo(AuthenticationMethodModel.PIN(1234))
+            assertThat(underTest.getAuthenticationMethod()).isEqualTo(AuthenticationMethodModel.Pin)
 
-            underTest.setAuthenticationMethod(AuthenticationMethodModel.Password("password"))
-            assertThat(authMethod).isEqualTo(AuthenticationMethodModel.Password("password"))
+            utils.authenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Password
+            )
+
+            assertThat(underTest.getAuthenticationMethod())
+                .isEqualTo(AuthenticationMethodModel.Password)
         }
 
     @Test
-    fun isUnlocked_whenAuthMethodIsNone_isTrue() =
+    fun getAuthenticationMethod_noneTreatedAsSwipe_whenLockscreenEnabled() =
         testScope.runTest {
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+            utils.authenticationRepository.setLockscreenEnabled(true)
+
+            assertThat(underTest.getAuthenticationMethod())
+                .isEqualTo(AuthenticationMethodModel.Swipe)
+        }
+
+    @Test
+    fun getAuthenticationMethod_none_whenLockscreenDisabled() =
+        testScope.runTest {
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+            utils.authenticationRepository.setLockscreenEnabled(false)
+
+            assertThat(underTest.getAuthenticationMethod())
+                .isEqualTo(AuthenticationMethodModel.None)
+        }
+
+    @Test
+    fun isUnlocked_whenAuthMethodIsNoneAndLockscreenDisabled_isTrue() =
+        testScope.runTest {
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+            utils.authenticationRepository.setLockscreenEnabled(false)
+
             val isUnlocked by collectLastValue(underTest.isUnlocked)
-            assertThat(isUnlocked).isFalse()
-
-            underTest.setAuthenticationMethod(AuthenticationMethodModel.None)
-
             assertThat(isUnlocked).isTrue()
         }
 
     @Test
-    fun unlockDevice() =
+    fun isUnlocked_whenAuthMethodIsNoneAndLockscreenEnabled_isFalse() =
         testScope.runTest {
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+            utils.authenticationRepository.setLockscreenEnabled(true)
+
             val isUnlocked by collectLastValue(underTest.isUnlocked)
             assertThat(isUnlocked).isFalse()
-
-            underTest.unlockDevice()
-            runCurrent()
-
-            assertThat(isUnlocked).isTrue()
-        }
-
-    @Test
-    fun biometricUnlock() =
-        testScope.runTest {
-            val isUnlocked by collectLastValue(underTest.isUnlocked)
-            assertThat(isUnlocked).isFalse()
-
-            underTest.biometricUnlock()
-            runCurrent()
-
-            assertThat(isUnlocked).isTrue()
-        }
-
-    @Test
-    fun toggleBypassEnabled() =
-        testScope.runTest {
-            val isBypassEnabled by collectLastValue(underTest.isBypassEnabled)
-            assertThat(isBypassEnabled).isFalse()
-
-            underTest.toggleBypassEnabled()
-            assertThat(isBypassEnabled).isTrue()
-
-            underTest.toggleBypassEnabled()
-            assertThat(isBypassEnabled).isFalse()
         }
 
     @Test
     fun isAuthenticationRequired_lockedAndSecured_true() =
         testScope.runTest {
-            underTest.lockDevice()
+            utils.authenticationRepository.setUnlocked(false)
             runCurrent()
-            underTest.setAuthenticationMethod(AuthenticationMethodModel.Password("password"))
+            utils.authenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Password
+            )
 
             assertThat(underTest.isAuthenticationRequired()).isTrue()
         }
@@ -115,9 +117,9 @@
     @Test
     fun isAuthenticationRequired_lockedAndNotSecured_false() =
         testScope.runTest {
-            underTest.lockDevice()
+            utils.authenticationRepository.setUnlocked(false)
             runCurrent()
-            underTest.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
 
             assertThat(underTest.isAuthenticationRequired()).isFalse()
         }
@@ -125,9 +127,11 @@
     @Test
     fun isAuthenticationRequired_unlockedAndSecured_false() =
         testScope.runTest {
-            underTest.unlockDevice()
+            utils.authenticationRepository.setUnlocked(true)
             runCurrent()
-            underTest.setAuthenticationMethod(AuthenticationMethodModel.Password("password"))
+            utils.authenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Password
+            )
 
             assertThat(underTest.isAuthenticationRequired()).isFalse()
         }
@@ -135,137 +139,96 @@
     @Test
     fun isAuthenticationRequired_unlockedAndNotSecured_false() =
         testScope.runTest {
-            underTest.unlockDevice()
+            utils.authenticationRepository.setUnlocked(true)
             runCurrent()
-            underTest.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
 
             assertThat(underTest.isAuthenticationRequired()).isFalse()
         }
 
     @Test
-    fun authenticate_withCorrectPin_returnsTrueAndUnlocksDevice() =
+    fun authenticate_withCorrectPin_returnsTrue() =
         testScope.runTest {
-            val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
-            val isUnlocked by collectLastValue(underTest.isUnlocked)
-            underTest.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
-            assertThat(isUnlocked).isFalse()
-
-            assertThat(underTest.authenticate(listOf(1, 2, 3, 4))).isTrue()
-            assertThat(isUnlocked).isTrue()
-            assertThat(failedAttemptCount).isEqualTo(0)
+            val isThrottled by collectLastValue(underTest.isThrottled)
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)).isTrue()
+            assertThat(isThrottled).isFalse()
         }
 
     @Test
-    fun authenticate_withIncorrectPin_returnsFalseAndDoesNotUnlockDevice() =
+    fun authenticate_withIncorrectPin_returnsFalse() =
         testScope.runTest {
-            val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
-            val isUnlocked by collectLastValue(underTest.isUnlocked)
-            underTest.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
-            assertThat(isUnlocked).isFalse()
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            assertThat(underTest.authenticate(listOf(9, 8, 7, 6, 5, 4))).isFalse()
+        }
 
-            assertThat(underTest.authenticate(listOf(9, 8, 7))).isFalse()
-            assertThat(isUnlocked).isFalse()
-            assertThat(failedAttemptCount).isEqualTo(1)
+    @Test(expected = IllegalArgumentException::class)
+    fun authenticate_withEmptyPin_throwsException() =
+        testScope.runTest {
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            underTest.authenticate(listOf())
         }
 
     @Test
-    fun authenticate_withCorrectPassword_returnsTrueAndUnlocksDevice() =
+    fun authenticate_withCorrectMaxLengthPin_returnsTrue() =
         testScope.runTest {
-            val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
-            val isUnlocked by collectLastValue(underTest.isUnlocked)
-            underTest.setAuthenticationMethod(AuthenticationMethodModel.Password("password"))
-            assertThat(isUnlocked).isFalse()
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            val pin = List(16) { 9 }
+            utils.authenticationRepository.overrideCredential(pin)
+            assertThat(underTest.authenticate(pin)).isTrue()
+        }
+
+    @Test
+    fun authenticate_withCorrectTooLongPin_returnsFalse() =
+        testScope.runTest {
+            // Max pin length is 16 digits. To avoid issues with overflows, this test ensures
+            // that all pins > 16 decimal digits are rejected.
+
+            // If the policy changes, there is work to do in SysUI.
+            assertThat(DevicePolicyManager.MAX_PASSWORD_LENGTH).isLessThan(17)
+
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            assertThat(underTest.authenticate(List(17) { 9 })).isFalse()
+        }
+
+    @Test
+    fun authenticate_withCorrectPassword_returnsTrue() =
+        testScope.runTest {
+            val isThrottled by collectLastValue(underTest.isThrottled)
+            utils.authenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Password
+            )
 
             assertThat(underTest.authenticate("password".toList())).isTrue()
-            assertThat(isUnlocked).isTrue()
-            assertThat(failedAttemptCount).isEqualTo(0)
+            assertThat(isThrottled).isFalse()
         }
 
     @Test
-    fun authenticate_withIncorrectPassword_returnsFalseAndDoesNotUnlockDevice() =
+    fun authenticate_withIncorrectPassword_returnsFalse() =
         testScope.runTest {
-            val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
-            val isUnlocked by collectLastValue(underTest.isUnlocked)
-            underTest.setAuthenticationMethod(AuthenticationMethodModel.Password("password"))
-            assertThat(isUnlocked).isFalse()
+            utils.authenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Password
+            )
 
             assertThat(underTest.authenticate("alohomora".toList())).isFalse()
-            assertThat(isUnlocked).isFalse()
-            assertThat(failedAttemptCount).isEqualTo(1)
         }
 
     @Test
-    fun authenticate_withCorrectPattern_returnsTrueAndUnlocksDevice() =
+    fun authenticate_withCorrectPattern_returnsTrue() =
         testScope.runTest {
-            val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
-            val isUnlocked by collectLastValue(underTest.isUnlocked)
-            underTest.setAuthenticationMethod(
-                AuthenticationMethodModel.Pattern(
-                    listOf(
-                        AuthenticationMethodModel.Pattern.PatternCoordinate(
-                            x = 0,
-                            y = 0,
-                        ),
-                        AuthenticationMethodModel.Pattern.PatternCoordinate(
-                            x = 0,
-                            y = 1,
-                        ),
-                        AuthenticationMethodModel.Pattern.PatternCoordinate(
-                            x = 0,
-                            y = 2,
-                        ),
-                    )
-                )
+            utils.authenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pattern
             )
-            assertThat(isUnlocked).isFalse()
 
-            assertThat(
-                    underTest.authenticate(
-                        listOf(
-                            AuthenticationMethodModel.Pattern.PatternCoordinate(
-                                x = 0,
-                                y = 0,
-                            ),
-                            AuthenticationMethodModel.Pattern.PatternCoordinate(
-                                x = 0,
-                                y = 1,
-                            ),
-                            AuthenticationMethodModel.Pattern.PatternCoordinate(
-                                x = 0,
-                                y = 2,
-                            ),
-                        )
-                    )
-                )
-                .isTrue()
-            assertThat(isUnlocked).isTrue()
-            assertThat(failedAttemptCount).isEqualTo(0)
+            assertThat(underTest.authenticate(FakeAuthenticationRepository.PATTERN)).isTrue()
         }
 
     @Test
-    fun authenticate_withIncorrectPattern_returnsFalseAndDoesNotUnlockDevice() =
+    fun authenticate_withIncorrectPattern_returnsFalse() =
         testScope.runTest {
-            val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
-            val isUnlocked by collectLastValue(underTest.isUnlocked)
-            underTest.setAuthenticationMethod(
-                AuthenticationMethodModel.Pattern(
-                    listOf(
-                        AuthenticationMethodModel.Pattern.PatternCoordinate(
-                            x = 0,
-                            y = 0,
-                        ),
-                        AuthenticationMethodModel.Pattern.PatternCoordinate(
-                            x = 0,
-                            y = 1,
-                        ),
-                        AuthenticationMethodModel.Pattern.PatternCoordinate(
-                            x = 0,
-                            y = 2,
-                        ),
-                    )
-                )
+            utils.authenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pattern
             )
-            assertThat(isUnlocked).isFalse()
 
             assertThat(
                     underTest.authenticate(
@@ -286,18 +249,243 @@
                     )
                 )
                 .isFalse()
-            assertThat(isUnlocked).isFalse()
-            assertThat(failedAttemptCount).isEqualTo(1)
         }
 
     @Test
-    fun unlocksDevice_whenAuthMethodBecomesNone() =
+    fun tryAutoConfirm_withAutoConfirmPinAndShorterPin_returnsNullAndHasNoEffect() =
+        testScope.runTest {
+            val isThrottled by collectLastValue(underTest.isThrottled)
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            utils.authenticationRepository.setAutoConfirmEnabled(true)
+            assertThat(
+                    underTest.authenticate(
+                        FakeAuthenticationRepository.DEFAULT_PIN.toMutableList().apply {
+                            removeLast()
+                        },
+                        tryAutoConfirm = true
+                    )
+                )
+                .isNull()
+            assertThat(isThrottled).isFalse()
+        }
+
+    @Test
+    fun tryAutoConfirm_withAutoConfirmWrongPinCorrectLength_returnsFalseAndDoesNotUnlockDevice() =
         testScope.runTest {
             val isUnlocked by collectLastValue(underTest.isUnlocked)
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            utils.authenticationRepository.setAutoConfirmEnabled(true)
+            assertThat(
+                    underTest.authenticate(
+                        FakeAuthenticationRepository.DEFAULT_PIN.map { it + 1 },
+                        tryAutoConfirm = true
+                    )
+                )
+                .isFalse()
             assertThat(isUnlocked).isFalse()
+        }
 
-            repository.setAuthenticationMethod(AuthenticationMethodModel.None)
+    @Test
+    fun tryAutoConfirm_withAutoConfirmLongerPin_returnsFalseAndDoesNotUnlockDevice() =
+        testScope.runTest {
+            val isUnlocked by collectLastValue(underTest.isUnlocked)
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            utils.authenticationRepository.setAutoConfirmEnabled(true)
+            assertThat(
+                    underTest.authenticate(
+                        FakeAuthenticationRepository.DEFAULT_PIN + listOf(7),
+                        tryAutoConfirm = true
+                    )
+                )
+                .isFalse()
+            assertThat(isUnlocked).isFalse()
+        }
 
+    @Test
+    fun tryAutoConfirm_withAutoConfirmCorrectPin_returnsTrueAndUnlocksDevice() =
+        testScope.runTest {
+            val isUnlocked by collectLastValue(underTest.isUnlocked)
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            utils.authenticationRepository.setAutoConfirmEnabled(true)
+            assertThat(
+                    underTest.authenticate(
+                        FakeAuthenticationRepository.DEFAULT_PIN,
+                        tryAutoConfirm = true
+                    )
+                )
+                .isTrue()
             assertThat(isUnlocked).isTrue()
         }
+
+    @Test
+    fun tryAutoConfirm_withoutAutoConfirmButCorrectPin_returnsNullAndHasNoEffects() =
+        testScope.runTest {
+            val isUnlocked by collectLastValue(underTest.isUnlocked)
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            utils.authenticationRepository.setAutoConfirmEnabled(false)
+            assertThat(
+                    underTest.authenticate(
+                        FakeAuthenticationRepository.DEFAULT_PIN,
+                        tryAutoConfirm = true
+                    )
+                )
+                .isNull()
+            assertThat(isUnlocked).isFalse()
+        }
+
+    @Test
+    fun tryAutoConfirm_withoutCorrectPassword_returnsNullAndHasNoEffects() =
+        testScope.runTest {
+            val isUnlocked by collectLastValue(underTest.isUnlocked)
+            utils.authenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Password
+            )
+
+            assertThat(underTest.authenticate("password".toList(), tryAutoConfirm = true)).isNull()
+            assertThat(isUnlocked).isFalse()
+        }
+
+    @Test
+    fun throttling() =
+        testScope.runTest {
+            val isUnlocked by collectLastValue(underTest.isUnlocked)
+            val throttling by collectLastValue(underTest.throttling)
+            val isThrottled by collectLastValue(underTest.isThrottled)
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)
+            assertThat(isUnlocked).isTrue()
+            assertThat(isThrottled).isFalse()
+            assertThat(throttling).isEqualTo(AuthenticationThrottlingModel())
+
+            utils.authenticationRepository.setUnlocked(false)
+            assertThat(isUnlocked).isFalse()
+            assertThat(isThrottled).isFalse()
+            assertThat(throttling).isEqualTo(AuthenticationThrottlingModel())
+
+            // Make many wrong attempts, but just shy of what's needed to get throttled:
+            repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING - 1) {
+                underTest.authenticate(listOf(5, 6, 7)) // Wrong PIN
+                assertThat(isUnlocked).isFalse()
+                assertThat(isThrottled).isFalse()
+                assertThat(throttling).isEqualTo(AuthenticationThrottlingModel())
+            }
+
+            // Make one more wrong attempt, leading to throttling:
+            underTest.authenticate(listOf(5, 6, 7)) // Wrong PIN
+            assertThat(isUnlocked).isFalse()
+            assertThat(isThrottled).isTrue()
+            assertThat(throttling)
+                .isEqualTo(
+                    AuthenticationThrottlingModel(
+                        failedAttemptCount =
+                            FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING,
+                        remainingMs = FakeAuthenticationRepository.THROTTLE_DURATION_MS,
+                    )
+                )
+
+            // Correct PIN, but throttled, so doesn't attempt it:
+            assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)).isNull()
+            assertThat(isUnlocked).isFalse()
+            assertThat(isThrottled).isTrue()
+            assertThat(throttling)
+                .isEqualTo(
+                    AuthenticationThrottlingModel(
+                        failedAttemptCount =
+                            FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING,
+                        remainingMs = FakeAuthenticationRepository.THROTTLE_DURATION_MS,
+                    )
+                )
+
+            // Move the clock forward to ALMOST skip the throttling, leaving one second to go:
+            val throttleTimeoutSec =
+                FakeAuthenticationRepository.THROTTLE_DURATION_MS.milliseconds.inWholeSeconds
+                    .toInt()
+            repeat(throttleTimeoutSec - 1) { time ->
+                advanceTimeBy(1000)
+                assertThat(isThrottled).isTrue()
+                assertThat(throttling)
+                    .isEqualTo(
+                        AuthenticationThrottlingModel(
+                            failedAttemptCount =
+                                FakeAuthenticationRepository
+                                    .MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING,
+                            remainingMs =
+                                ((throttleTimeoutSec - (time + 1)).seconds.inWholeMilliseconds)
+                                    .toInt(),
+                        )
+                    )
+            }
+
+            // Move the clock forward one more second, to completely finish the throttling period:
+            advanceTimeBy(1000)
+            assertThat(isUnlocked).isFalse()
+            assertThat(isThrottled).isFalse()
+            assertThat(throttling)
+                .isEqualTo(
+                    AuthenticationThrottlingModel(
+                        failedAttemptCount =
+                            FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING,
+                        remainingMs = 0,
+                    )
+                )
+
+            // Correct PIN and no longer throttled so unlocks successfully:
+            assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)).isTrue()
+            assertThat(isUnlocked).isTrue()
+            assertThat(isThrottled).isFalse()
+            assertThat(throttling).isEqualTo(AuthenticationThrottlingModel())
+        }
+
+    @Test
+    fun hintedPinLength_withoutAutoConfirm_isNull() =
+        testScope.runTest {
+            val hintedPinLength by collectLastValue(underTest.hintedPinLength)
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            utils.authenticationRepository.setAutoConfirmEnabled(false)
+
+            assertThat(hintedPinLength).isNull()
+        }
+
+    @Test
+    fun hintedPinLength_withAutoConfirmPinTooShort_isNull() =
+        testScope.runTest {
+            val hintedPinLength by collectLastValue(underTest.hintedPinLength)
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            utils.authenticationRepository.overrideCredential(
+                buildList {
+                    repeat(utils.authenticationRepository.hintedPinLength - 1) { add(it + 1) }
+                }
+            )
+            utils.authenticationRepository.setAutoConfirmEnabled(true)
+
+            assertThat(hintedPinLength).isNull()
+        }
+
+    @Test
+    fun hintedPinLength_withAutoConfirmPinAtRightLength_isSameLength() =
+        testScope.runTest {
+            val hintedPinLength by collectLastValue(underTest.hintedPinLength)
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            utils.authenticationRepository.setAutoConfirmEnabled(true)
+            utils.authenticationRepository.overrideCredential(
+                buildList { repeat(utils.authenticationRepository.hintedPinLength) { add(it + 1) } }
+            )
+
+            assertThat(hintedPinLength).isEqualTo(utils.authenticationRepository.hintedPinLength)
+        }
+
+    @Test
+    fun hintedPinLength_withAutoConfirmPinTooLong_isNull() =
+        testScope.runTest {
+            val hintedPinLength by collectLastValue(underTest.hintedPinLength)
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            utils.authenticationRepository.overrideCredential(
+                buildList {
+                    repeat(utils.authenticationRepository.hintedPinLength + 1) { add(it + 1) }
+                }
+            )
+            utils.authenticationRepository.setAutoConfirmEnabled(true)
+
+            assertThat(hintedPinLength).isNull()
+        }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
new file mode 100644
index 0000000..88c710a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.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.systemui.back.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.shade.QuickSettingsController
+import com.android.systemui.shade.ShadeController
+import com.android.systemui.shade.ShadeViewController
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
+import com.android.systemui.util.mockito.whenever
+import junit.framework.Assert.assertFalse
+import junit.framework.Assert.assertTrue
+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.anyBoolean
+import org.mockito.Mockito.atLeastOnce
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.junit.MockitoJUnit
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class BackActionInteractorTest : SysuiTestCase() {
+    @JvmField @Rule var mockitoRule = MockitoJUnit.rule()
+
+    @Mock private lateinit var statusBarStateController: StatusBarStateController
+    @Mock private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager
+    @Mock private lateinit var shadeController: ShadeController
+    @Mock private lateinit var qsController: QuickSettingsController
+    @Mock private lateinit var shadeViewController: ShadeViewController
+
+    private lateinit var backActionInteractor: BackActionInteractor
+
+    @Before
+    fun setup() {
+        backActionInteractor =
+            BackActionInteractor(
+                statusBarStateController,
+                statusBarKeyguardViewManager,
+                shadeController,
+            )
+        backActionInteractor.setup(qsController, shadeViewController)
+    }
+
+    @Test
+    fun testOnBackRequested_keyguardCanHandleBackPressed() {
+        whenever(statusBarKeyguardViewManager.canHandleBackPressed()).thenReturn(true)
+
+        val result = backActionInteractor.onBackRequested()
+
+        assertTrue(result)
+        verify(statusBarKeyguardViewManager, atLeastOnce()).onBackPressed()
+    }
+
+    @Test
+    fun testOnBackRequested_quickSettingsIsCustomizing() {
+        whenever(qsController.isCustomizing).thenReturn(true)
+
+        val result = backActionInteractor.onBackRequested()
+
+        assertTrue(result)
+        verify(qsController, atLeastOnce()).closeQsCustomizer()
+        verify(statusBarKeyguardViewManager, never()).onBackPressed()
+    }
+
+    @Test
+    fun testOnBackRequested_quickSettingsExpanded() {
+        whenever(qsController.expanded).thenReturn(true)
+
+        val result = backActionInteractor.onBackRequested()
+
+        assertTrue(result)
+        verify(shadeViewController, atLeastOnce()).animateCollapseQs(anyBoolean())
+        verify(statusBarKeyguardViewManager, never()).onBackPressed()
+    }
+
+    @Test
+    fun testOnBackRequested_closeUserSwitcherIfOpen() {
+        whenever(shadeViewController.closeUserSwitcherIfOpen()).thenReturn(true)
+
+        val result = backActionInteractor.onBackRequested()
+
+        assertTrue(result)
+        verify(statusBarKeyguardViewManager, never()).onBackPressed()
+        verify(shadeViewController, never()).animateCollapseQs(anyBoolean())
+    }
+
+    @Test
+    fun testOnBackRequested_returnsFalse() {
+        // make shouldBackBeHandled return false
+        whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
+
+        val result = backActionInteractor.onBackRequested()
+
+        assertFalse(result)
+        verify(statusBarKeyguardViewManager, never()).onBackPressed()
+        verify(shadeViewController, never()).animateCollapseQs(anyBoolean())
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewControllerTest.java
index 1482f29..40b5729 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewControllerTest.java
@@ -33,10 +33,10 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.flags.FakeFeatureFlags;
-import com.android.systemui.flags.Flags;
 import com.android.systemui.settings.UserTracker;
+import com.android.systemui.statusbar.phone.StatusBarLocation;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.tuner.TunerService;
@@ -61,7 +61,6 @@
     private Handler mHandler;
     @Mock
     private ContentResolver mContentResolver;
-    private FakeFeatureFlags mFeatureFlags;
     @Mock
     private BatteryController mBatteryController;
 
@@ -74,8 +73,8 @@
         when(mBatteryMeterView.getContext()).thenReturn(mContext);
         when(mBatteryMeterView.getResources()).thenReturn(mContext.getResources());
 
-        mFeatureFlags = new FakeFeatureFlags();
-        mFeatureFlags.set(Flags.BATTERY_SHIELD_ICON, false);
+        mContext.getOrCreateTestableResources().addOverride(
+                R.bool.flag_battery_shield_icon, false);
     }
 
     @Test
@@ -134,7 +133,8 @@
 
     @Test
     public void shieldFlagDisabled_viewNotified() {
-        mFeatureFlags.set(Flags.BATTERY_SHIELD_ICON, false);
+        mContext.getOrCreateTestableResources().addOverride(
+                R.bool.flag_battery_shield_icon, false);
 
         initController();
 
@@ -143,7 +143,8 @@
 
     @Test
     public void shieldFlagEnabled_viewNotified() {
-        mFeatureFlags.set(Flags.BATTERY_SHIELD_ICON, true);
+        mContext.getOrCreateTestableResources().addOverride(
+                R.bool.flag_battery_shield_icon, true);
 
         initController();
 
@@ -153,12 +154,12 @@
     private void initController() {
         mController = new BatteryMeterViewController(
                 mBatteryMeterView,
+                StatusBarLocation.HOME,
                 mUserTracker,
                 mConfigurationController,
                 mTunerService,
                 mHandler,
                 mContentResolver,
-                mFeatureFlags,
                 mBatteryController
         );
     }
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 d31a86a..e3e6130 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
@@ -51,6 +51,7 @@
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.statusbar.VibratorHelper
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
@@ -99,6 +100,8 @@
     lateinit var windowToken: IBinder
     @Mock
     lateinit var interactionJankMonitor: InteractionJankMonitor
+    @Mock
+    lateinit var vibrator: VibratorHelper
 
     // TODO(b/278622168): remove with flag
     open val useNewBiometricPrompt = false
@@ -325,7 +328,7 @@
             authenticators = BiometricManager.Authenticators.BIOMETRIC_WEAK or
                     BiometricManager.Authenticators.DEVICE_CREDENTIAL
         )
-        container.animateToCredentialUI()
+        container.animateToCredentialUI(false)
         waitForIdleSync()
 
         assertThat(container.hasCredentialView()).isTrue()
@@ -514,7 +517,7 @@
         { authBiometricFingerprintViewModel },
         { promptSelectorInteractor },
         { bpCredentialInteractor },
-        PromptViewModel(promptSelectorInteractor),
+        PromptViewModel(promptSelectorInteractor, vibrator),
         { credentialViewModel },
         Handler(TestableLooper.get(this).looper),
         fakeExecutor
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 b9f92a0..3d4171f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -191,6 +191,10 @@
     private ArgumentCaptor<IFaceAuthenticatorsRegisteredCallback> mFaceAuthenticatorsRegisteredCaptor;
     @Captor
     private ArgumentCaptor<BiometricStateListener> mBiometricStateCaptor;
+    @Captor
+    private ArgumentCaptor<Integer> mModalityCaptor;
+    @Captor
+    private ArgumentCaptor<String> mMessageCaptor;
     @Mock
     private Resources mResources;
 
@@ -202,9 +206,6 @@
     private TestableAuthController mAuthController;
     private FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
 
-    @Mock
-    private VibratorHelper mVibratorHelper;
-
     @Before
     public void setup() throws RemoteException {
         // TODO(b/278622168): remove with flag
@@ -267,7 +268,6 @@
                         true /* supportsSelfIllumination */,
                         true /* resetLockoutRequireHardwareAuthToken */));
         when(mFaceManager.getSensorPropertiesInternal()).thenReturn(faceProps);
-        when(mVibratorHelper.hasVibrator()).thenReturn(true);
 
         mAuthController = new TestableAuthController(mContextSpy);
 
@@ -482,16 +482,63 @@
                 BiometricConstants.BIOMETRIC_PAUSED_REJECTED,
                 0 /* vendorCode */);
 
-        ArgumentCaptor<Integer> modalityCaptor = ArgumentCaptor.forClass(Integer.class);
-        ArgumentCaptor<String> messageCaptor = ArgumentCaptor.forClass(String.class);
-        verify(mDialog1).onAuthenticationFailed(modalityCaptor.capture(), messageCaptor.capture());
+        verify(mDialog1).onAuthenticationFailed(mModalityCaptor.capture(), mMessageCaptor.capture());
 
-        assertEquals(modalityCaptor.getValue().intValue(), modality);
-        assertEquals(messageCaptor.getValue(),
+        assertEquals(mModalityCaptor.getValue().intValue(), modality);
+        assertEquals(mMessageCaptor.getValue(),
                 mContext.getString(R.string.biometric_not_recognized));
     }
 
     @Test
+    public void testOnAuthenticationFailedInvoked_coex_whenFaceAuthRejected_withPaused() {
+        testOnAuthenticationFailedInvoked_coex_whenFaceAuthRejected(
+                BiometricConstants.BIOMETRIC_PAUSED_REJECTED);
+    }
+
+    @Test
+    public void testOnAuthenticationFailedInvoked_coex_whenFaceAuthRejected_withTimeout() {
+        testOnAuthenticationFailedInvoked_coex_whenFaceAuthRejected(
+                BiometricConstants.BIOMETRIC_ERROR_TIMEOUT);
+    }
+
+    private void testOnAuthenticationFailedInvoked_coex_whenFaceAuthRejected(int error) {
+        final int modality = BiometricAuthenticator.TYPE_FACE;
+        final int userId = 0;
+
+        enrollFingerprintAndFace(userId);
+
+        showDialog(new int[] {1} /* sensorIds */, false /* credentialAllowed */);
+
+        mAuthController.onBiometricError(modality, error, 0 /* vendorCode */);
+
+        verify(mDialog1).onAuthenticationFailed(mModalityCaptor.capture(), mMessageCaptor.capture());
+
+        assertThat(mModalityCaptor.getValue().intValue()).isEqualTo(modality);
+        assertThat(mMessageCaptor.getValue()).isEqualTo(
+                mContext.getString(R.string.biometric_face_not_recognized));
+    }
+
+    @Test
+    public void testOnAuthenticationFailedInvoked_whenFingerprintAuthRejected() {
+        final int modality = BiometricAuthenticator.TYPE_FINGERPRINT;
+        final int userId = 0;
+
+        enrollFingerprintAndFace(userId);
+
+        showDialog(new int[] {1} /* sensorIds */, false /* credentialAllowed */);
+
+        mAuthController.onBiometricError(modality,
+                BiometricConstants.BIOMETRIC_PAUSED_REJECTED,
+                0 /* vendorCode */);
+
+        verify(mDialog1).onAuthenticationFailed(mModalityCaptor.capture(), mMessageCaptor.capture());
+
+        assertThat(mModalityCaptor.getValue().intValue()).isEqualTo(modality);
+        assertThat(mMessageCaptor.getValue()).isEqualTo(
+                mContext.getString(R.string.fingerprint_error_not_match));
+    }
+
+    @Test
     public void testOnAuthenticationFailedInvoked_whenBiometricTimedOut() {
         showDialog(new int[] {1} /* sensorIds */, false /* credentialAllowed */);
         final int modality = BiometricAuthenticator.TYPE_FACE;
@@ -499,13 +546,11 @@
         final int vendorCode = 0;
         mAuthController.onBiometricError(modality, error, vendorCode);
 
-        ArgumentCaptor<Integer> modalityCaptor = ArgumentCaptor.forClass(Integer.class);
-        ArgumentCaptor<String> messageCaptor = ArgumentCaptor.forClass(String.class);
-        verify(mDialog1).onAuthenticationFailed(modalityCaptor.capture(), messageCaptor.capture());
+        verify(mDialog1).onAuthenticationFailed(mModalityCaptor.capture(), mMessageCaptor.capture());
 
-        assertEquals(modalityCaptor.getValue().intValue(), modality);
-        assertEquals(messageCaptor.getValue(),
-                FaceManager.getErrorString(mContext, error, vendorCode));
+        assertThat(mModalityCaptor.getValue().intValue()).isEqualTo(modality);
+        assertThat(mMessageCaptor.getValue()).isEqualTo(
+                mContext.getString(R.string.biometric_not_recognized));
     }
 
     @Test
@@ -515,12 +560,10 @@
         final String helpMessage = "help";
         mAuthController.onBiometricHelp(modality, helpMessage);
 
-        ArgumentCaptor<Integer> modalityCaptor = ArgumentCaptor.forClass(Integer.class);
-        ArgumentCaptor<String> messageCaptor = ArgumentCaptor.forClass(String.class);
-        verify(mDialog1).onHelp(modalityCaptor.capture(), messageCaptor.capture());
+        verify(mDialog1).onHelp(mModalityCaptor.capture(), mMessageCaptor.capture());
 
-        assertEquals(modalityCaptor.getValue().intValue(), modality);
-        assertEquals(messageCaptor.getValue(), helpMessage);
+        assertThat(mModalityCaptor.getValue().intValue()).isEqualTo(modality);
+        assertThat(mMessageCaptor.getValue()).isEqualTo(helpMessage);
     }
 
     @Test
@@ -531,12 +574,10 @@
         final int vendorCode = 0;
         mAuthController.onBiometricError(modality, error, vendorCode);
 
-        ArgumentCaptor<Integer> modalityCaptor = ArgumentCaptor.forClass(Integer.class);
-        ArgumentCaptor<String> messageCaptor = ArgumentCaptor.forClass(String.class);
-        verify(mDialog1).onError(modalityCaptor.capture(), messageCaptor.capture());
+        verify(mDialog1).onError(mModalityCaptor.capture(), mMessageCaptor.capture());
 
-        assertEquals(modalityCaptor.getValue().intValue(), modality);
-        assertEquals(messageCaptor.getValue(),
+        assertThat(mModalityCaptor.getValue().intValue()).isEqualTo(modality);
+        assertThat(mMessageCaptor.getValue()).isEqualTo(
                 FaceManager.getErrorString(mContext, error, vendorCode));
     }
 
@@ -550,7 +591,7 @@
 
         mAuthController.onBiometricError(BiometricAuthenticator.TYPE_FACE, error, vendorCode);
         verify(mDialog1, never()).onError(anyInt(), anyString());
-        verify(mDialog1).animateToCredentialUI();
+        verify(mDialog1).animateToCredentialUI(eq(true));
     }
 
     @Test
@@ -563,7 +604,7 @@
 
         mAuthController.onBiometricError(BiometricAuthenticator.TYPE_FACE, error, vendorCode);
         verify(mDialog1, never()).onError(anyInt(), anyString());
-        verify(mDialog1).animateToCredentialUI();
+        verify(mDialog1).animateToCredentialUI(eq(true));
     }
 
     @Test
@@ -578,7 +619,7 @@
         mAuthController.onBiometricError(modality, error, vendorCode);
         verify(mDialog1).onError(
                 eq(modality), eq(FaceManager.getErrorString(mContext, error, vendorCode)));
-        verify(mDialog1, never()).animateToCredentialUI();
+        verify(mDialog1, never()).animateToCredentialUI(eq(true));
     }
 
     @Test
@@ -593,7 +634,7 @@
         mAuthController.onBiometricError(modality, error, vendorCode);
         verify(mDialog1).onError(
                 eq(modality), eq(FaceManager.getErrorString(mContext, error, vendorCode)));
-        verify(mDialog1, never()).animateToCredentialUI();
+        verify(mDialog1, never()).animateToCredentialUI(eq(true));
     }
 
     @Test
@@ -954,6 +995,25 @@
                 eq(null) /* credentialAttestation */);
     }
 
+    @Test
+    public void testShowDialog_whenOwnerNotInForeground() {
+        PromptInfo promptInfo = createTestPromptInfo();
+        promptInfo.setAllowBackgroundAuthentication(false);
+        switchTask("other_package");
+        mAuthController.showAuthenticationDialog(promptInfo,
+                mReceiver /* receiver */,
+                new int[]{1} /* sensorIds */,
+                false /* credentialAllowed */,
+                true /* requireConfirmation */,
+                0 /* userId */,
+                0 /* operationId */,
+                "testPackage",
+                REQUEST_ID);
+
+        assertNull(mAuthController.mCurrentDialog);
+        verify(mDialog1, never()).show(any(), any());
+    }
+
     private void showDialog(int[] sensorIds, boolean credentialAllowed) {
         mAuthController.showAuthenticationDialog(createTestPromptInfo(),
                 mReceiver /* receiver */,
@@ -998,6 +1058,31 @@
         return HAT;
     }
 
+    private void enrollFingerprintAndFace(final int userId) {
+
+        // Enroll fingerprint
+        verify(mFingerprintManager).registerBiometricStateListener(
+                mBiometricStateCaptor.capture());
+        assertFalse(mAuthController.isFingerprintEnrolled(userId));
+
+        mBiometricStateCaptor.getValue().onEnrollmentsChanged(userId,
+                1 /* sensorId */, true /* hasEnrollments */);
+        waitForIdleSync();
+
+        assertTrue(mAuthController.isFingerprintEnrolled(userId));
+
+        // Enroll face
+        verify(mFaceManager).registerBiometricStateListener(
+                mBiometricStateCaptor.capture());
+        assertFalse(mAuthController.isFaceAuthEnrolled(userId));
+
+        mBiometricStateCaptor.getValue().onEnrollmentsChanged(userId,
+                2 /* sensorId */, true /* hasEnrollments */);
+        waitForIdleSync();
+
+        assertTrue(mAuthController.isFaceAuthEnrolled(userId));
+    }
+
     private final class TestableAuthController extends AuthController {
         private int mBuildCount = 0;
         private PromptInfo mLastBiometricPromptInfo;
@@ -1012,7 +1097,7 @@
                     () -> mBiometricPromptCredentialInteractor, () -> mPromptSelectionInteractor,
                     () -> mCredentialViewModel, () -> mPromptViewModel,
                     mInteractionJankMonitor, mHandler,
-                    mBackgroundExecutor, mVibratorHelper, mUdfpsUtils);
+                    mBackgroundExecutor, mUdfpsUtils);
         }
 
         @Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricNotificationServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricNotificationServiceTest.java
index 38c9caf..b8bca3a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricNotificationServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricNotificationServiceTest.java
@@ -29,6 +29,7 @@
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.hardware.biometrics.BiometricFaceConstants;
+import android.hardware.biometrics.BiometricFingerprintConstants;
 import android.hardware.biometrics.BiometricSourceType;
 import android.os.Handler;
 import android.testing.AndroidTestingRunner;
@@ -50,8 +51,6 @@
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
 
-import java.util.Optional;
-
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
@@ -65,16 +64,11 @@
     KeyguardStateController mKeyguardStateController;
     @Mock
     NotificationManager mNotificationManager;
-    @Mock
-    Optional<FingerprintReEnrollNotification> mFingerprintReEnrollNotificationOptional;
-    @Mock
-    FingerprintReEnrollNotification mFingerprintReEnrollNotification;
 
     private static final String TAG = "BiometricNotificationService";
     private static final int FACE_NOTIFICATION_ID = 1;
     private static final int FINGERPRINT_NOTIFICATION_ID = 2;
     private static final long SHOW_NOTIFICATION_DELAY_MS = 5_000L; // 5 seconds
-    private static final int FINGERPRINT_ACQUIRED_RE_ENROLL = 0;
 
     private final ArgumentCaptor<Notification> mNotificationArgumentCaptor =
             ArgumentCaptor.forClass(Notification.class);
@@ -84,11 +78,6 @@
 
     @Before
     public void setUp() {
-        when(mFingerprintReEnrollNotificationOptional.orElse(any()))
-                .thenReturn(mFingerprintReEnrollNotification);
-        when(mFingerprintReEnrollNotification.isFingerprintReEnrollRequired(
-                FINGERPRINT_ACQUIRED_RE_ENROLL)).thenReturn(true);
-
         mLooper = TestableLooper.get(this);
         Handler handler = new Handler(mLooper.getLooper());
         BiometricNotificationDialogFactory dialogFactory = new BiometricNotificationDialogFactory();
@@ -98,8 +87,7 @@
                 new BiometricNotificationService(mContext,
                         mKeyguardUpdateMonitor, mKeyguardStateController, handler,
                         mNotificationManager,
-                        broadcastReceiver,
-                        mFingerprintReEnrollNotificationOptional);
+                        broadcastReceiver);
         biometricNotificationService.start();
 
         ArgumentCaptor<KeyguardUpdateMonitorCallback> updateMonitorCallbackArgumentCaptor =
@@ -120,8 +108,8 @@
     public void testShowFingerprintReEnrollNotification() {
         when(mKeyguardStateController.isShowing()).thenReturn(false);
 
-        mKeyguardUpdateMonitorCallback.onBiometricHelp(
-                FINGERPRINT_ACQUIRED_RE_ENROLL,
+        mKeyguardUpdateMonitorCallback.onBiometricError(
+                BiometricFingerprintConstants.BIOMETRIC_ERROR_RE_ENROLL,
                 "Testing Fingerprint Re-enrollment" /* errString */,
                 BiometricSourceType.FINGERPRINT
         );
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/OWNERS b/packages/SystemUI/tests/src/com/android/systemui/biometrics/OWNERS
index adb10f0..5420c37 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/OWNERS
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/OWNERS
@@ -1,4 +1,5 @@
 set noparent
 
+# Bug component: 879035
 include /services/core/java/com/android/server/biometrics/OWNERS
 beverlyt@google.com
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 2908e75..ecc776b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
@@ -44,25 +44,27 @@
 import android.view.ViewPropertyAnimator
 import android.view.WindowInsets
 import android.view.WindowManager
-import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION
-import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY
-import android.view.WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG
 import android.view.WindowMetrics
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.airbnb.lottie.LottieAnimationView
+import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.systemui.R
 import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.SysuiTestableContext
+import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
 import com.android.systemui.biometrics.data.repository.FakeRearDisplayStateRepository
 import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
 import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractorImpl
+import com.android.systemui.biometrics.domain.interactor.SideFpsOverlayInteractorImpl
+import com.android.systemui.biometrics.shared.model.FingerprintSensorType
+import com.android.systemui.biometrics.shared.model.SensorStrength
+import com.android.systemui.biometrics.ui.viewmodel.SideFpsOverlayViewModel
+import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
+import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
-import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
-import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository
-import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.concurrency.FakeExecutor
@@ -99,7 +101,7 @@
 @SmallTest
 @RoboPilotTest
 @RunWith(AndroidJUnit4::class)
-@TestableLooper.RunWithLooper
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
 class SideFpsControllerTest : SysuiTestCase() {
 
     @JvmField @Rule var rule = MockitoJUnit.rule()
@@ -118,6 +120,8 @@
     private lateinit var keyguardBouncerRepository: FakeKeyguardBouncerRepository
     private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor
     private lateinit var displayStateInteractor: DisplayStateInteractor
+    private lateinit var sideFpsOverlayViewModel: SideFpsOverlayViewModel
+    private val fingerprintRepository = FakeFingerprintPropertyRepository()
 
     private val executor = FakeExecutor(FakeSystemClock())
     private val rearDisplayStateRepository = FakeRearDisplayStateRepository()
@@ -149,8 +153,8 @@
                 mock(KeyguardStateController::class.java),
                 keyguardBouncerRepository,
                 FakeBiometricSettingsRepository(),
-                FakeDeviceEntryFingerprintAuthRepository(),
                 FakeSystemClock(),
+                mock(KeyguardUpdateMonitor::class.java),
             )
         displayStateInteractor =
             DisplayStateInteractorImpl(
@@ -159,6 +163,15 @@
                 executor,
                 rearDisplayStateRepository
             )
+        sideFpsOverlayViewModel =
+            SideFpsOverlayViewModel(context, SideFpsOverlayInteractorImpl(fingerprintRepository))
+
+        fingerprintRepository.setProperties(
+            sensorId = 1,
+            strength = SensorStrength.STRONG,
+            sensorType = FingerprintSensorType.REAR,
+            sensorLocations = mapOf("" to SensorLocationInternal("", 2500, 0, 0))
+        )
 
         context.addMockSystemService(DisplayManager::class.java, displayManager)
         context.addMockSystemService(WindowManager::class.java, windowManager)
@@ -265,6 +278,7 @@
                 executor,
                 handler,
                 alternateBouncerInteractor,
+                { sideFpsOverlayViewModel },
                 TestCoroutineScope(),
                 dumpManager
             )
@@ -683,106 +697,6 @@
         verify(windowManager).removeView(any())
     }
 
-    /**
-     * {@link SideFpsController#updateOverlayParams} calculates indicator placement for ROTATION_0,
-     * and uses RotateUtils.rotateBounds to map to the correct indicator location given the device
-     * rotation. Assuming RotationUtils.rotateBounds works correctly, tests for indicator placement
-     * in other rotations have been omitted.
-     */
-    @Test
-    fun verifiesIndicatorPlacementForXAlignedSensor_0() =
-        testWithDisplay(
-            deviceConfig = DeviceConfig.X_ALIGNED,
-            isReverseDefaultRotation = false,
-            { rotation = Surface.ROTATION_0 }
-        ) {
-            sideFpsController.overlayOffsets = sensorLocation
-
-            sideFpsController.updateOverlayParams(windowManager.defaultDisplay, indicatorBounds)
-
-            overlayController.show(SENSOR_ID, REASON_UNKNOWN)
-            executor.runAllReady()
-
-            verify(windowManager).updateViewLayout(any(), overlayViewParamsCaptor.capture())
-            assertThat(overlayViewParamsCaptor.value.x).isEqualTo(sensorLocation.sensorLocationX)
-            assertThat(overlayViewParamsCaptor.value.y).isEqualTo(0)
-        }
-
-    /**
-     * {@link SideFpsController#updateOverlayParams} calculates indicator placement for ROTATION_270
-     * in reverse default rotation. It then uses RotateUtils.rotateBounds to map to the correct
-     * indicator location given the device rotation. Assuming RotationUtils.rotateBounds works
-     * correctly, tests for indicator placement in other rotations have been omitted.
-     */
-    @Test
-    fun verifiesIndicatorPlacementForXAlignedSensor_InReverseDefaultRotation_270() =
-        testWithDisplay(
-            deviceConfig = DeviceConfig.X_ALIGNED,
-            isReverseDefaultRotation = true,
-            { rotation = Surface.ROTATION_270 }
-        ) {
-            sideFpsController.overlayOffsets = sensorLocation
-
-            sideFpsController.updateOverlayParams(windowManager.defaultDisplay, indicatorBounds)
-
-            overlayController.show(SENSOR_ID, REASON_UNKNOWN)
-            executor.runAllReady()
-
-            verify(windowManager).updateViewLayout(any(), overlayViewParamsCaptor.capture())
-            assertThat(overlayViewParamsCaptor.value.x).isEqualTo(sensorLocation.sensorLocationX)
-            assertThat(overlayViewParamsCaptor.value.y).isEqualTo(0)
-        }
-
-    /**
-     * {@link SideFpsController#updateOverlayParams} calculates indicator placement for ROTATION_0,
-     * and uses RotateUtils.rotateBounds to map to the correct indicator location given the device
-     * rotation. Assuming RotationUtils.rotateBounds works correctly, tests for indicator placement
-     * in other rotations have been omitted.
-     */
-    @Test
-    fun verifiesIndicatorPlacementForYAlignedSensor_0() =
-        testWithDisplay(
-            deviceConfig = DeviceConfig.Y_ALIGNED,
-            isReverseDefaultRotation = false,
-            { rotation = Surface.ROTATION_0 }
-        ) {
-            sideFpsController.overlayOffsets = sensorLocation
-
-            sideFpsController.updateOverlayParams(windowManager.defaultDisplay, indicatorBounds)
-
-            overlayController.show(SENSOR_ID, REASON_UNKNOWN)
-            executor.runAllReady()
-
-            verify(windowManager).updateViewLayout(any(), overlayViewParamsCaptor.capture())
-            assertThat(overlayViewParamsCaptor.value.x).isEqualTo(displayWidth - boundsWidth)
-            assertThat(overlayViewParamsCaptor.value.y).isEqualTo(sensorLocation.sensorLocationY)
-        }
-
-    /**
-     * {@link SideFpsController#updateOverlayParams} calculates indicator placement for ROTATION_270
-     * in reverse default rotation. It then uses RotateUtils.rotateBounds to map to the correct
-     * indicator location given the device rotation. Assuming RotationUtils.rotateBounds works
-     * correctly, tests for indicator placement in other rotations have been omitted.
-     */
-    @Test
-    fun verifiesIndicatorPlacementForYAlignedSensor_InReverseDefaultRotation_270() =
-        testWithDisplay(
-            deviceConfig = DeviceConfig.Y_ALIGNED,
-            isReverseDefaultRotation = true,
-            { rotation = Surface.ROTATION_270 }
-        ) {
-            sideFpsController.overlayOffsets = sensorLocation
-
-            sideFpsController.updateOverlayParams(windowManager.defaultDisplay, indicatorBounds)
-
-            overlayController.show(SENSOR_ID, REASON_UNKNOWN)
-            executor.runAllReady()
-
-            verify(windowManager).updateViewLayout(any(), overlayViewParamsCaptor.capture())
-            assertThat(overlayViewParamsCaptor.value.x).isEqualTo(displayWidth - boundsWidth)
-            assertThat(overlayViewParamsCaptor.value.y).isEqualTo(sensorLocation.sensorLocationY)
-        }
-
     @Test
     fun hasSideFpsSensor_withSensorProps_returnsTrue() = testWithDisplay {
         // By default all those tests assume the side fps sensor is available.
@@ -795,51 +709,6 @@
 
         assertThat(fingerprintManager.hasSideFpsSensor()).isFalse()
     }
-
-    @Test
-    fun testLayoutParams_isKeyguardDialogType() =
-        testWithDisplay(deviceConfig = DeviceConfig.Y_ALIGNED) {
-            sideFpsController.overlayOffsets = sensorLocation
-            sideFpsController.updateOverlayParams(windowManager.defaultDisplay, indicatorBounds)
-            overlayController.show(SENSOR_ID, REASON_UNKNOWN)
-            executor.runAllReady()
-
-            verify(windowManager).updateViewLayout(any(), overlayViewParamsCaptor.capture())
-
-            val lpType = overlayViewParamsCaptor.value.type
-
-            assertThat((lpType and TYPE_KEYGUARD_DIALOG) != 0).isTrue()
-        }
-
-    @Test
-    fun testLayoutParams_hasNoMoveAnimationWindowFlag() =
-        testWithDisplay(deviceConfig = DeviceConfig.Y_ALIGNED) {
-            sideFpsController.overlayOffsets = sensorLocation
-            sideFpsController.updateOverlayParams(windowManager.defaultDisplay, indicatorBounds)
-            overlayController.show(SENSOR_ID, REASON_UNKNOWN)
-            executor.runAllReady()
-
-            verify(windowManager).updateViewLayout(any(), overlayViewParamsCaptor.capture())
-
-            val lpFlags = overlayViewParamsCaptor.value.privateFlags
-
-            assertThat((lpFlags and PRIVATE_FLAG_NO_MOVE_ANIMATION) != 0).isTrue()
-        }
-
-    @Test
-    fun testLayoutParams_hasTrustedOverlayWindowFlag() =
-        testWithDisplay(deviceConfig = DeviceConfig.Y_ALIGNED) {
-            sideFpsController.overlayOffsets = sensorLocation
-            sideFpsController.updateOverlayParams(windowManager.defaultDisplay, indicatorBounds)
-            overlayController.show(SENSOR_ID, REASON_UNKNOWN)
-            executor.runAllReady()
-
-            verify(windowManager).updateViewLayout(any(), overlayViewParamsCaptor.capture())
-
-            val lpFlags = overlayViewParamsCaptor.value.privateFlags
-
-            assertThat((lpFlags and PRIVATE_FLAG_TRUSTED_OVERLAY) != 0).isTrue()
-        }
 }
 
 private fun insetsForSmallNavbar() = insetsWithBottom(60)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsBpViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsBpViewControllerTest.kt
new file mode 100644
index 0000000..7de78a6
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsBpViewControllerTest.kt
@@ -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.systemui.biometrics
+
+import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.shade.ShadeExpansionStateManager
+import com.android.systemui.statusbar.phone.SystemUIDialogManager
+import org.junit.Assert.assertFalse
+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
+
+@SmallTest
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
+@TestableLooper.RunWithLooper
+class UdfpsBpViewControllerTest : SysuiTestCase() {
+
+    @JvmField @Rule var rule = MockitoJUnit.rule()
+
+    @Mock lateinit var udfpsBpView: UdfpsBpView
+    @Mock lateinit var statusBarStateController: StatusBarStateController
+    @Mock lateinit var shadeExpansionStateManager: ShadeExpansionStateManager
+    @Mock lateinit var systemUIDialogManager: SystemUIDialogManager
+    @Mock lateinit var dumpManager: DumpManager
+
+    private lateinit var udfpsBpViewController: UdfpsBpViewController
+
+    @Before
+    fun setup() {
+        udfpsBpViewController =
+            UdfpsBpViewController(
+                udfpsBpView,
+                statusBarStateController,
+                shadeExpansionStateManager,
+                systemUIDialogManager,
+                dumpManager
+            )
+    }
+
+    @Test
+    fun testShouldNeverPauseAuth() {
+        assertFalse(udfpsBpViewController.shouldPauseAuth())
+    }
+}
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 9dcdc46..0e0d0e3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
@@ -43,11 +43,12 @@
 import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.ActivityLaunchAnimator
+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.flags.Flags
-import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor
-import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor
+import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModels
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.shade.ShadeExpansionStateManager
 import com.android.systemui.statusbar.LockscreenShadeTransitionController
@@ -58,6 +59,7 @@
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.settings.SecureSettings
 import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
@@ -70,6 +72,7 @@
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.verify
 import org.mockito.junit.MockitoJUnit
+import javax.inject.Provider
 import org.mockito.Mockito.`when` as whenever
 
 private const val REQUEST_ID = 2L
@@ -80,6 +83,7 @@
 private const val SENSOR_WIDTH = 30
 private const val SENSOR_HEIGHT = 60
 
+@ExperimentalCoroutinesApi
 @SmallTest
 @RoboPilotTest
 @RunWith(AndroidJUnit4::class)
@@ -114,6 +118,9 @@
     @Mock private lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor
     @Mock private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor
     @Mock private lateinit var udfpsUtils: UdfpsUtils
+    @Mock private lateinit var udfpsKeyguardAccessibilityDelegate:
+            UdfpsKeyguardAccessibilityDelegate
+    @Mock private lateinit var udfpsKeyguardViewModels: Provider<UdfpsKeyguardViewModels>
     @Captor private lateinit var layoutParamsCaptor: ArgumentCaptor<WindowManager.LayoutParams>
 
     private val onTouch = { _: View, _: MotionEvent, _: Boolean -> true }
@@ -144,7 +151,9 @@
             configurationController, keyguardStateController, unlockedScreenOffAnimationController,
             udfpsDisplayMode, secureSettings, REQUEST_ID, reason,
             controllerCallback, onTouch, activityLaunchAnimator, featureFlags,
-            primaryBouncerInteractor, alternateBouncerInteractor, isDebuggable, udfpsUtils
+            primaryBouncerInteractor, alternateBouncerInteractor, isDebuggable, udfpsUtils,
+            udfpsKeyguardAccessibilityDelegate,
+            udfpsKeyguardViewModels,
         )
         block()
     }
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 7834191..58982d1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -85,13 +85,14 @@
 import com.android.systemui.biometrics.udfps.NormalizedTouchData;
 import com.android.systemui.biometrics.udfps.SinglePointerTouchProcessor;
 import com.android.systemui.biometrics.udfps.TouchProcessorResult;
+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.flags.Flags;
 import com.android.systemui.keyguard.ScreenLifecycle;
-import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
-import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor;
+import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModels;
 import com.android.systemui.log.SessionTracker;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -219,6 +220,10 @@
     private AlternateBouncerInteractor mAlternateBouncerInteractor;
     @Mock
     private SecureSettings mSecureSettings;
+    @Mock
+    private UdfpsKeyguardAccessibilityDelegate mUdfpsKeyguardAccessibilityDelegate;
+    @Mock
+    private Provider<UdfpsKeyguardViewModels> mUdfpsKeyguardViewModels;
 
     // Capture listeners so that they can be used to send events
     @Captor
@@ -317,7 +322,8 @@
                 mActivityLaunchAnimator, alternateTouchProvider, mBiometricExecutor,
                 mPrimaryBouncerInteractor, mSinglePointerTouchProcessor, mSessionTracker,
                 mAlternateBouncerInteractor, mSecureSettings, mInputManager, mUdfpsUtils,
-                mock(KeyguardFaceAuthInteractor.class));
+                mock(KeyguardFaceAuthInteractor.class),
+                mUdfpsKeyguardAccessibilityDelegate, mUdfpsKeyguardViewModels);
         verify(mFingerprintManager).setUdfpsOverlayController(mOverlayCaptor.capture());
         mOverlayController = mOverlayCaptor.getValue();
         verify(mScreenLifecycle).addObserver(mScreenObserverCaptor.capture());
@@ -442,6 +448,16 @@
     }
 
     @Test
+    public void showUdfpsOverlay_callsListener() throws RemoteException {
+        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
+                BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
+        mFgExecutor.runAllReady();
+
+        verify(mFingerprintManager).onUdfpsUiEvent(FingerprintManager.UDFPS_UI_OVERLAY_SHOWN,
+                TEST_REQUEST_ID, mOpticalProps.sensorId);
+    }
+
+    @Test
     public void testSubscribesToOrientationChangesWhenShowingOverlay() throws Exception {
         mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
                 BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
@@ -761,17 +777,20 @@
                 inOrder.verify(mAlternateTouchProvider).onUiReady();
                 inOrder.verify(mLatencyTracker).onActionEnd(
                         eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE));
-                verify(mFingerprintManager, never()).onUiReady(anyLong(), anyInt());
+                verify(mFingerprintManager, never()).onUdfpsUiEvent(
+                        eq(FingerprintManager.UDFPS_UI_READY), anyLong(), anyInt());
             } else {
                 InOrder inOrder = inOrder(mFingerprintManager, mLatencyTracker);
-                inOrder.verify(mFingerprintManager).onUiReady(eq(TEST_REQUEST_ID),
+                inOrder.verify(mFingerprintManager).onUdfpsUiEvent(
+                        eq(FingerprintManager.UDFPS_UI_READY), eq(TEST_REQUEST_ID),
                         eq(testParams.sensorProps.sensorId));
                 inOrder.verify(mLatencyTracker).onActionEnd(
                         eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE));
                 verify(mAlternateTouchProvider, never()).onUiReady();
             }
         } else {
-            verify(mFingerprintManager, never()).onUiReady(anyLong(), anyInt());
+            verify(mFingerprintManager, never()).onUdfpsUiEvent(
+                    eq(FingerprintManager.UDFPS_UI_READY), anyLong(), anyInt());
             verify(mAlternateTouchProvider, never()).onUiReady();
             verify(mLatencyTracker, never()).onActionEnd(
                     eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE));
@@ -1327,12 +1346,22 @@
         // WHEN ACTION_DOWN is received
         when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn(
                 processorResultDown);
-        MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0);
-        mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent);
+        MotionEvent event = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0);
+        mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event);
         mBiometricExecutor.runAllReady();
-        downEvent.recycle();
 
-        // THEN the touch is pilfered
+        // WHEN ACTION_MOVE is received after
+        final TouchProcessorResult processorResultUnchanged =
+                new TouchProcessorResult.ProcessedTouch(
+                        InteractionEvent.UNCHANGED, 1 /* pointerId */, touchData);
+        when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn(
+                processorResultUnchanged);
+        event.setAction(ACTION_MOVE);
+        mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event);
+        mBiometricExecutor.runAllReady();
+        event.recycle();
+
+        // THEN only pilfer once on the initial down
         verify(mInputManager).pilferPointers(any());
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardAccessibilityDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardAccessibilityDelegateTest.kt
new file mode 100644
index 0000000..921ff09
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardAccessibilityDelegateTest.kt
@@ -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.systemui.biometrics
+
+import android.testing.TestableLooper
+import android.view.View
+import android.view.accessibility.AccessibilityNodeInfo
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
+import com.android.systemui.util.mockito.argumentCaptor
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.Mock
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+@TestableLooper.RunWithLooper
+class UdfpsKeyguardAccessibilityDelegateTest : SysuiTestCase() {
+
+    @Mock private lateinit var keyguardViewManager: StatusBarKeyguardViewManager
+    @Mock private lateinit var hostView: View
+    private lateinit var underTest: UdfpsKeyguardAccessibilityDelegate
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        underTest =
+            UdfpsKeyguardAccessibilityDelegate(
+                context.resources,
+                keyguardViewManager,
+            )
+    }
+
+    @Test
+    fun onInitializeAccessibilityNodeInfo_clickActionAdded() {
+        // WHEN node is initialized
+        val mockedNodeInfo = mock(AccessibilityNodeInfo::class.java)
+        underTest.onInitializeAccessibilityNodeInfo(hostView, mockedNodeInfo)
+
+        // THEN a11y action is added
+        val argumentCaptor = argumentCaptor<AccessibilityNodeInfo.AccessibilityAction>()
+        verify(mockedNodeInfo).addAction(argumentCaptor.capture())
+
+        // AND the a11y action is a click action
+        assertEquals(
+            AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK.id,
+            argumentCaptor.value.id
+        )
+    }
+
+    @Test
+    fun performAccessibilityAction_actionClick_showsPrimaryBouncer() {
+        // WHEN click action is performed
+        val mockedNodeInfo = mock(AccessibilityNodeInfo::class.java)
+        underTest.performAccessibilityAction(
+            hostView,
+            AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK.id,
+            null
+        )
+
+        // THEN primary bouncer shows
+        verify(keyguardViewManager).showPrimaryBouncer(anyBoolean())
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java
index 0d3b394..032753a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java
@@ -31,8 +31,8 @@
 import com.android.systemui.flags.FakeFeatureFlags;
 import com.android.systemui.flags.Flags;
 import com.android.systemui.keyguard.KeyguardViewMediator;
-import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor;
-import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor;
+import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shade.ShadeExpansionChangeEvent;
 import com.android.systemui.shade.ShadeExpansionListener;
@@ -73,6 +73,7 @@
     protected @Mock ActivityLaunchAnimator mActivityLaunchAnimator;
     protected @Mock PrimaryBouncerInteractor mPrimaryBouncerInteractor;
     protected @Mock AlternateBouncerInteractor mAlternateBouncerInteractor;
+    protected @Mock UdfpsKeyguardAccessibilityDelegate mUdfpsKeyguardAccessibilityDelegate;
 
     protected FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
 
@@ -167,7 +168,8 @@
                 mActivityLaunchAnimator,
                 mFeatureFlags,
                 mPrimaryBouncerInteractor,
-                mAlternateBouncerInteractor);
+                mAlternateBouncerInteractor,
+                mUdfpsKeyguardAccessibilityDelegate);
         return controller;
     }
 }
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 ffad326..8dfeb3b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt
@@ -22,20 +22,19 @@
 import androidx.test.filters.SmallTest
 import com.android.keyguard.KeyguardSecurityModel
 import com.android.systemui.RoboPilotTest
+import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
+import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepositoryImpl
+import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
+import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants
+import com.android.systemui.bouncer.ui.BouncerView
 import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.DismissCallbackRegistry
-import com.android.systemui.keyguard.data.BouncerView
 import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
-import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
 import com.android.systemui.keyguard.data.repository.FakeTrustRepository
-import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository
-import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepositoryImpl
-import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor
-import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor
-import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor
-import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants
 import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.StatusBarState
@@ -106,8 +105,8 @@
                 mock(KeyguardStateController::class.java),
                 keyguardBouncerRepository,
                 mock(BiometricSettingsRepository::class.java),
-                mock(DeviceEntryFingerprintAuthRepository::class.java),
                 mock(SystemClock::class.java),
+                mKeyguardUpdateMonitor,
             )
         return createUdfpsKeyguardViewController(
             /* useModernBouncer */ true, /* useExpandedOverlay */
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FaceSettingsRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FaceSettingsRepositoryImplTest.kt
new file mode 100644
index 0000000..0df4fbf
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FaceSettingsRepositoryImplTest.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.biometrics.data.repository
+
+import android.database.ContentObserver
+import android.os.Handler
+import android.provider.Settings.Secure.FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.util.mockito.captureMany
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.mockito.withArgCaptor
+import com.android.systemui.util.settings.SecureSettings
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.junit.MockitoJUnit
+
+private const val USER_ID = 8
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class FaceSettingsRepositoryImplTest : SysuiTestCase() {
+
+    @JvmField @Rule var mockitoRule = MockitoJUnit.rule()
+    private val testScope = TestScope()
+
+    @Mock private lateinit var mainHandler: Handler
+    @Mock private lateinit var secureSettings: SecureSettings
+
+    private lateinit var repository: FaceSettingsRepositoryImpl
+
+    @Before
+    fun setup() {
+        repository = FaceSettingsRepositoryImpl(mainHandler, secureSettings)
+    }
+
+    @Test
+    fun createsOneRepositoryPerUser() =
+        testScope.runTest {
+            val userRepo = repository.forUser(USER_ID)
+
+            assertThat(userRepo.userId).isEqualTo(USER_ID)
+
+            assertThat(repository.forUser(USER_ID)).isSameInstanceAs(userRepo)
+            assertThat(repository.forUser(USER_ID + 1)).isNotSameInstanceAs(userRepo)
+        }
+
+    @Test
+    fun startsRepoImmediatelyWithAllSettingKeys() =
+        testScope.runTest {
+            val userRepo = repository.forUser(USER_ID)
+
+            val keys =
+                captureMany<String> {
+                    verify(secureSettings)
+                        .registerContentObserverForUser(capture(), anyBoolean(), any(), eq(USER_ID))
+                }
+
+            assertThat(keys).containsExactly(FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION)
+        }
+
+    @Test
+    fun forwardsSettingsValues() = runTest {
+        val userRepo = repository.forUser(USER_ID)
+
+        val intAsBooleanSettings =
+            listOf(
+                FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION to
+                    collectLastValue(userRepo.alwaysRequireConfirmationInApps)
+            )
+
+        for ((setting, accessor) in intAsBooleanSettings) {
+            val observer =
+                withArgCaptor<ContentObserver> {
+                    verify(secureSettings)
+                        .registerContentObserverForUser(
+                            eq(setting),
+                            anyBoolean(),
+                            capture(),
+                            eq(USER_ID)
+                        )
+                }
+
+            for (value in listOf(true, false)) {
+                secureSettings.mockIntSetting(setting, if (value) 1 else 0)
+                observer.onChange(false)
+                assertThat(accessor()).isEqualTo(value)
+            }
+        }
+    }
+
+    private fun SecureSettings.mockIntSetting(key: String, value: Int) {
+        whenever(getIntForUser(eq(key), anyInt(), eq(USER_ID))).thenReturn(value)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FingerprintRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FingerprintRepositoryImplTest.kt
index 239e317..ea25615 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FingerprintRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FingerprintRepositoryImplTest.kt
@@ -73,10 +73,15 @@
     @Test
     fun initializeProperties() =
         testScope.runTest {
-            val isInitialized = collectLastValue(repository.isInitialized)
+            val sensorId by collectLastValue(repository.sensorId)
+            val strength by collectLastValue(repository.strength)
+            val sensorType by collectLastValue(repository.sensorType)
+            val sensorLocations by collectLastValue(repository.sensorLocations)
 
-            assertDefaultProperties()
-            assertThat(isInitialized()).isFalse()
+            // Assert default properties.
+            assertThat(sensorId).isEqualTo(-1)
+            assertThat(strength).isEqualTo(SensorStrength.CONVENIENCE)
+            assertThat(sensorType).isEqualTo(FingerprintSensorType.UNKNOWN)
 
             val fingerprintProps =
                 listOf(
@@ -115,31 +120,24 @@
 
             fingerprintAuthenticatorsCaptor.value.onAllAuthenticatorsRegistered(fingerprintProps)
 
-            assertThat(repository.sensorId.value).isEqualTo(1)
-            assertThat(repository.strength.value).isEqualTo(SensorStrength.STRONG)
-            assertThat(repository.sensorType.value).isEqualTo(FingerprintSensorType.REAR)
+            assertThat(sensorId).isEqualTo(1)
+            assertThat(strength).isEqualTo(SensorStrength.STRONG)
+            assertThat(sensorType).isEqualTo(FingerprintSensorType.REAR)
 
-            assertThat(repository.sensorLocations.value.size).isEqualTo(2)
-            assertThat(repository.sensorLocations.value).containsKey("display_id_1")
-            with(repository.sensorLocations.value["display_id_1"]!!) {
+            assertThat(sensorLocations?.size).isEqualTo(2)
+            assertThat(sensorLocations).containsKey("display_id_1")
+            with(sensorLocations?.get("display_id_1")!!) {
                 assertThat(displayId).isEqualTo("display_id_1")
                 assertThat(sensorLocationX).isEqualTo(100)
                 assertThat(sensorLocationY).isEqualTo(300)
                 assertThat(sensorRadius).isEqualTo(20)
             }
-            assertThat(repository.sensorLocations.value).containsKey("")
-            with(repository.sensorLocations.value[""]!!) {
+            assertThat(sensorLocations).containsKey("")
+            with(sensorLocations?.get("")!!) {
                 assertThat(displayId).isEqualTo("")
                 assertThat(sensorLocationX).isEqualTo(540)
                 assertThat(sensorLocationY).isEqualTo(1636)
                 assertThat(sensorRadius).isEqualTo(130)
             }
-            assertThat(isInitialized()).isTrue()
         }
-
-    private fun assertDefaultProperties() {
-        assertThat(repository.sensorId.value).isEqualTo(-1)
-        assertThat(repository.strength.value).isEqualTo(SensorStrength.CONVENIENCE)
-        assertThat(repository.sensorType.value).isEqualTo(FingerprintSensorType.UNKNOWN)
-    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt
index 4836af6..ec7ce63 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
 package com.android.systemui.biometrics.data.repository
 
 import android.hardware.biometrics.PromptInfo
@@ -5,12 +21,16 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.biometrics.AuthController
 import com.android.systemui.biometrics.shared.model.PromptKind
+import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.mockito.withArgCaptor
 import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.toList
 import kotlinx.coroutines.launch
-import kotlinx.coroutines.test.runBlockingTest
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
@@ -21,61 +41,109 @@
 import org.mockito.Mockito.verify
 import org.mockito.junit.MockitoJUnit
 
+private const val USER_ID = 9
+private const val CHALLENGE = 90L
+
+@OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(JUnit4::class)
 class PromptRepositoryImplTest : SysuiTestCase() {
 
     @JvmField @Rule var mockitoRule = MockitoJUnit.rule()
 
+    private val testScope = TestScope()
+    private val faceSettings = FakeFaceSettingsRepository()
+
     @Mock private lateinit var authController: AuthController
 
     private lateinit var repository: PromptRepositoryImpl
 
     @Before
     fun setup() {
-        repository = PromptRepositoryImpl(authController)
+        repository = PromptRepositoryImpl(faceSettings, authController)
     }
 
     @Test
-    fun isShowing() = runBlockingTest {
-        whenever(authController.isShowing).thenReturn(true)
+    fun isShowing() =
+        testScope.runTest {
+            whenever(authController.isShowing).thenReturn(true)
 
-        val values = mutableListOf<Boolean>()
-        val job = launch { repository.isShowing.toList(values) }
-        assertThat(values).containsExactly(true)
+            val values = mutableListOf<Boolean>()
+            val job = launch { repository.isShowing.toList(values) }
+            runCurrent()
 
-        withArgCaptor<AuthController.Callback> {
-            verify(authController).addCallback(capture())
+            assertThat(values).containsExactly(true)
 
-            value.onBiometricPromptShown()
-            assertThat(values).containsExactly(true, true)
+            withArgCaptor<AuthController.Callback> {
+                verify(authController).addCallback(capture())
 
-            value.onBiometricPromptDismissed()
-            assertThat(values).containsExactly(true, true, false).inOrder()
+                value.onBiometricPromptShown()
+                runCurrent()
+                assertThat(values).containsExactly(true, true)
 
-            job.cancel()
-            verify(authController).removeCallback(eq(value))
+                value.onBiometricPromptDismissed()
+                runCurrent()
+                assertThat(values).containsExactly(true, true, false).inOrder()
+
+                job.cancel()
+                runCurrent()
+                verify(authController).removeCallback(eq(value))
+            }
         }
-    }
 
     @Test
-    fun setsAndUnsetsPrompt() = runBlockingTest {
-        val kind = PromptKind.Pin
-        val uid = 8
-        val challenge = 90L
-        val promptInfo = PromptInfo()
+    fun isConfirmationRequired_whenNotForced() =
+        testScope.runTest {
+            faceSettings.setUserSettings(USER_ID, alwaysRequireConfirmationInApps = false)
+            val isConfirmationRequired by collectLastValue(repository.isConfirmationRequired)
 
-        repository.setPrompt(promptInfo, uid, challenge, kind)
+            for (case in listOf(true, false)) {
+                repository.setPrompt(
+                    PromptInfo().apply { isConfirmationRequested = case },
+                    USER_ID,
+                    CHALLENGE,
+                    PromptKind.Biometric()
+                )
 
-        assertThat(repository.kind.value).isEqualTo(kind)
-        assertThat(repository.userId.value).isEqualTo(uid)
-        assertThat(repository.challenge.value).isEqualTo(challenge)
-        assertThat(repository.promptInfo.value).isSameInstanceAs(promptInfo)
+                assertThat(isConfirmationRequired).isEqualTo(case)
+            }
+        }
 
-        repository.unsetPrompt()
+    @Test
+    fun isConfirmationRequired_whenForced() =
+        testScope.runTest {
+            faceSettings.setUserSettings(USER_ID, alwaysRequireConfirmationInApps = true)
+            val isConfirmationRequired by collectLastValue(repository.isConfirmationRequired)
 
-        assertThat(repository.promptInfo.value).isNull()
-        assertThat(repository.userId.value).isNull()
-        assertThat(repository.challenge.value).isNull()
-    }
+            for (case in listOf(true, false)) {
+                repository.setPrompt(
+                    PromptInfo().apply { isConfirmationRequested = case },
+                    USER_ID,
+                    CHALLENGE,
+                    PromptKind.Biometric()
+                )
+
+                assertThat(isConfirmationRequired).isTrue()
+            }
+        }
+
+    @Test
+    fun setsAndUnsetsPrompt() =
+        testScope.runTest {
+            val kind = PromptKind.Pin
+            val promptInfo = PromptInfo()
+
+            repository.setPrompt(promptInfo, USER_ID, CHALLENGE, kind)
+
+            assertThat(repository.kind.value).isEqualTo(kind)
+            assertThat(repository.userId.value).isEqualTo(USER_ID)
+            assertThat(repository.challenge.value).isEqualTo(CHALLENGE)
+            assertThat(repository.promptInfo.value).isSameInstanceAs(promptInfo)
+
+            repository.unsetPrompt()
+
+            assertThat(repository.promptInfo.value).isNull()
+            assertThat(repository.userId.value).isNull()
+            assertThat(repository.challenge.value).isNull()
+        }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractorImplTest.kt
index 97d3e68..5eda2b2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractorImplTest.kt
@@ -11,8 +11,8 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.biometrics.domain.model.BiometricOperationInfo
 import com.android.systemui.biometrics.domain.model.BiometricPromptRequest
-import com.android.systemui.biometrics.domain.model.BiometricUserInfo
 import com.android.systemui.biometrics.promptInfo
+import com.android.systemui.biometrics.shared.model.BiometricUserInfo
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.whenever
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractorImplTest.kt
index 263ce1a..8f8004f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractorImplTest.kt
@@ -7,7 +7,7 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -54,10 +54,11 @@
             LogContextInteractorImpl(
                 testScope.backgroundScope,
                 foldProvider,
-                KeyguardTransitionInteractor(
-                    keyguardTransitionRepository,
-                    testScope.backgroundScope
-                ),
+                KeyguardTransitionInteractorFactory.create(
+                        repository = keyguardTransitionRepository,
+                        scope = testScope.backgroundScope,
+                    )
+                    .keyguardTransitionInteractor,
             )
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractorTest.kt
index 720a35c9..dcefea2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractorTest.kt
@@ -7,8 +7,8 @@
 import com.android.systemui.biometrics.data.repository.FakePromptRepository
 import com.android.systemui.biometrics.domain.model.BiometricOperationInfo
 import com.android.systemui.biometrics.domain.model.BiometricPromptRequest
-import com.android.systemui.biometrics.domain.model.BiometricUserInfo
 import com.android.systemui.biometrics.promptInfo
+import com.android.systemui.biometrics.shared.model.BiometricUserInfo
 import com.android.systemui.coroutines.collectLastValue
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt
index a62ea3b..81cbaea 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt
@@ -106,17 +106,11 @@
         val currentPrompt by collectLastValue(interactor.prompt)
         val credentialKind by collectLastValue(interactor.credentialKind)
         val isCredentialAllowed by collectLastValue(interactor.isCredentialAllowed)
-        val isExplicitConfirmationRequired by collectLastValue(interactor.isConfirmationRequested)
+        val isExplicitConfirmationRequired by collectLastValue(interactor.isConfirmationRequired)
 
         assertThat(currentPrompt).isNull()
 
-        interactor.useBiometricsForAuthentication(
-            info,
-            confirmationRequired,
-            USER_ID,
-            CHALLENGE,
-            modalities
-        )
+        interactor.useBiometricsForAuthentication(info, USER_ID, CHALLENGE, modalities)
 
         assertThat(currentPrompt).isNotNull()
         assertThat(currentPrompt?.title).isEqualTo(TITLE)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractorTest.kt
index fd96cf4..896f9b11 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractorTest.kt
@@ -22,6 +22,7 @@
 import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
 import com.android.systemui.biometrics.shared.model.FingerprintSensorType
 import com.android.systemui.biometrics.shared.model.SensorStrength
+import com.android.systemui.coroutines.collectLastValue
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.test.StandardTestDispatcher
 import kotlinx.coroutines.test.TestScope
@@ -51,8 +52,9 @@
     }
 
     @Test
-    fun testGetOverlayOffsets() =
+    fun testGetOverlayoffsets() =
         testScope.runTest {
+            // Arrange.
             fingerprintRepository.setProperties(
                 sensorId = 1,
                 strength = SensorStrength.STRONG,
@@ -76,16 +78,33 @@
                     )
             )
 
-            var offsets = interactor.getOverlayOffsets("display_id_1")
-            assertThat(offsets.displayId).isEqualTo("display_id_1")
-            assertThat(offsets.sensorLocationX).isEqualTo(100)
-            assertThat(offsets.sensorLocationY).isEqualTo(300)
-            assertThat(offsets.sensorRadius).isEqualTo(20)
+            // Act.
+            val offsets by collectLastValue(interactor.overlayOffsets)
+            val displayId by collectLastValue(interactor.displayId)
 
-            offsets = interactor.getOverlayOffsets("invalid_display_id")
-            assertThat(offsets.displayId).isEqualTo("")
-            assertThat(offsets.sensorLocationX).isEqualTo(540)
-            assertThat(offsets.sensorLocationY).isEqualTo(1636)
-            assertThat(offsets.sensorRadius).isEqualTo(130)
+            // Assert offsets of empty displayId.
+            assertThat(displayId).isEqualTo("")
+            assertThat(offsets?.displayId).isEqualTo("")
+            assertThat(offsets?.sensorLocationX).isEqualTo(540)
+            assertThat(offsets?.sensorLocationY).isEqualTo(1636)
+            assertThat(offsets?.sensorRadius).isEqualTo(130)
+
+            // Offsets should be updated correctly.
+            interactor.changeDisplay("display_id_1")
+            assertThat(displayId).isEqualTo("display_id_1")
+            assertThat(offsets?.displayId).isEqualTo("display_id_1")
+            assertThat(offsets?.sensorLocationX).isEqualTo(100)
+            assertThat(offsets?.sensorLocationY).isEqualTo(300)
+            assertThat(offsets?.sensorRadius).isEqualTo(20)
+
+            // Should return default offset when the displayId is invalid.
+            interactor.changeDisplay("invalid_display_id")
+            assertThat(displayId).isEqualTo("invalid_display_id")
+            assertThat(offsets?.displayId).isEqualTo(SensorLocationInternal.DEFAULT.displayId)
+            assertThat(offsets?.sensorLocationX)
+                .isEqualTo(SensorLocationInternal.DEFAULT.sensorLocationX)
+            assertThat(offsets?.sensorLocationY)
+                .isEqualTo(SensorLocationInternal.DEFAULT.sensorLocationY)
+            assertThat(offsets?.sensorRadius).isEqualTo(SensorLocationInternal.DEFAULT.sensorRadius)
         }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequestTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequestTest.kt
index e352905..be0276a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequestTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequestTest.kt
@@ -4,6 +4,7 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.biometrics.fingerprintSensorPropertiesInternal
 import com.android.systemui.biometrics.promptInfo
+import com.android.systemui.biometrics.shared.model.BiometricUserInfo
 import com.google.common.truth.Truth.assertThat
 import org.junit.Test
 import org.junit.runner.RunWith
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/AuthBiometricFingerprintViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/AuthBiometricFingerprintViewModelTest.kt
index 0c210e5..785f1be 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/AuthBiometricFingerprintViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/AuthBiometricFingerprintViewModelTest.kt
@@ -48,6 +48,7 @@
     fun iconUpdates_onConfigurationChanged() {
         testScope.runTest {
             runCurrent()
+
             val testConfig = Configuration()
             val folded = INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP - 1
             val unfolded = INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP + 1
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptAuthStateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptAuthStateTest.kt
index 689bb00..278a43e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptAuthStateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptAuthStateTest.kt
@@ -18,7 +18,7 @@
 
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.biometrics.domain.model.BiometricModality
+import com.android.systemui.biometrics.shared.model.BiometricModality
 import com.google.common.truth.Truth.assertThat
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -33,6 +33,7 @@
         with(PromptAuthState(isAuthenticated = false)) {
             assertThat(isNotAuthenticated).isTrue()
             assertThat(isAuthenticatedAndConfirmed).isFalse()
+            assertThat(isAuthenticatedAndExplicitlyConfirmed).isFalse()
             assertThat(isAuthenticatedByFace).isFalse()
             assertThat(isAuthenticatedByFingerprint).isFalse()
         }
@@ -43,6 +44,7 @@
         with(PromptAuthState(isAuthenticated = true)) {
             assertThat(isNotAuthenticated).isFalse()
             assertThat(isAuthenticatedAndConfirmed).isTrue()
+            assertThat(isAuthenticatedAndExplicitlyConfirmed).isFalse()
             assertThat(isAuthenticatedByFace).isFalse()
             assertThat(isAuthenticatedByFingerprint).isFalse()
         }
@@ -50,10 +52,12 @@
         with(PromptAuthState(isAuthenticated = true, needsUserConfirmation = true)) {
             assertThat(isNotAuthenticated).isFalse()
             assertThat(isAuthenticatedAndConfirmed).isFalse()
+            assertThat(isAuthenticatedAndExplicitlyConfirmed).isFalse()
             assertThat(isAuthenticatedByFace).isFalse()
             assertThat(isAuthenticatedByFingerprint).isFalse()
 
-            assertThat(asConfirmed().isAuthenticatedAndConfirmed).isTrue()
+            assertThat(asExplicitlyConfirmed().isAuthenticatedAndConfirmed).isTrue()
+            assertThat(asExplicitlyConfirmed().isAuthenticatedAndExplicitlyConfirmed).isTrue()
         }
     }
 
@@ -64,6 +68,7 @@
         ) {
             assertThat(isNotAuthenticated).isFalse()
             assertThat(isAuthenticatedAndConfirmed).isTrue()
+            assertThat(isAuthenticatedAndExplicitlyConfirmed).isFalse()
             assertThat(isAuthenticatedByFace).isTrue()
             assertThat(isAuthenticatedByFingerprint).isFalse()
         }
@@ -79,6 +84,7 @@
         ) {
             assertThat(isNotAuthenticated).isFalse()
             assertThat(isAuthenticatedAndConfirmed).isTrue()
+            assertThat(isAuthenticatedAndExplicitlyConfirmed).isFalse()
             assertThat(isAuthenticatedByFace).isFalse()
             assertThat(isAuthenticatedByFingerprint).isTrue()
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
index 3ba6004..91140a9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
@@ -27,11 +27,14 @@
 import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor
 import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractorImpl
 import com.android.systemui.biometrics.domain.model.BiometricModalities
-import com.android.systemui.biometrics.domain.model.BiometricModality
 import com.android.systemui.biometrics.extractAuthenticatorTypes
 import com.android.systemui.biometrics.faceSensorPropertiesInternal
 import com.android.systemui.biometrics.fingerprintSensorPropertiesInternal
+import com.android.systemui.biometrics.shared.model.BiometricModality
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.statusbar.VibratorHelper
+import com.android.systemui.util.mockito.any
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.first
@@ -45,6 +48,9 @@
 import org.junit.runner.RunWith
 import org.junit.runners.Parameterized
 import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
 import org.mockito.junit.MockitoJUnit
 
 private const val USER_ID = 4
@@ -58,6 +64,7 @@
     @JvmField @Rule var mockitoRule = MockitoJUnit.rule()
 
     @Mock private lateinit var lockPatternUtils: LockPatternUtils
+    @Mock private lateinit var vibrator: VibratorHelper
 
     private val testScope = TestScope()
     private val promptRepository = FakePromptRepository()
@@ -70,11 +77,11 @@
         selector = PromptSelectorInteractorImpl(promptRepository, lockPatternUtils)
         selector.resetPrompt()
 
-        viewModel = PromptViewModel(selector)
+        viewModel = PromptViewModel(selector, vibrator)
     }
 
     @Test
-    fun `start idle and show authenticating`() =
+    fun start_idle_and_show_authenticating() =
         runGenericTest(doNotStart = true) {
             val expectedSize =
                 if (testCase.shouldStartAsImplicitFlow) PromptSize.SMALL else PromptSize.MEDIUM
@@ -107,7 +114,7 @@
         }
 
     @Test
-    fun `shows authenticated - no errors`() = runGenericTest {
+    fun shows_authenticated_with_no_errors() = runGenericTest {
         // this case can't happen until fingerprint is started
         // trigger it now since no error has occurred in this test
         val forceError = testCase.isCoex && testCase.authenticatedByFingerprint
@@ -124,6 +131,24 @@
         )
     }
 
+    @Test
+    fun play_haptic_on_confirm_when_confirmation_required_otherwise_on_authenticated() =
+        runGenericTest {
+            val expectConfirmation = testCase.expectConfirmation(atLeastOneFailure = false)
+
+            viewModel.showAuthenticated(testCase.authenticatedModality, 1_000L)
+
+            verify(vibrator, if (expectConfirmation) never() else times(1))
+                .vibrateAuthSuccess(any())
+
+            if (expectConfirmation) {
+                viewModel.confirmAuthenticated()
+            }
+
+            verify(vibrator).vibrateAuthSuccess(any())
+            verify(vibrator, never()).vibrateAuthError(any())
+        }
+
     private suspend fun TestScope.showAuthenticated(
         authenticatedModality: BiometricModality,
         expectConfirmation: Boolean,
@@ -172,7 +197,7 @@
     }
 
     @Test
-    fun `shows temporary errors`() = runGenericTest {
+    fun shows_temporary_errors() = runGenericTest {
         val checkAtEnd = suspend { assertButtonsVisible(negative = true) }
 
         showTemporaryErrors(restart = false) { checkAtEnd() }
@@ -180,6 +205,32 @@
         showTemporaryErrors(restart = true) { checkAtEnd() }
     }
 
+    @Test
+    fun plays_haptic_on_errors() = runGenericTest {
+        viewModel.showTemporaryError(
+            "so sad",
+            messageAfterError = "",
+            authenticateAfterError = false,
+            hapticFeedback = true,
+        )
+
+        verify(vibrator).vibrateAuthError(any())
+        verify(vibrator, never()).vibrateAuthSuccess(any())
+    }
+
+    @Test
+    fun plays_haptic_on_errors_unless_skipped() = runGenericTest {
+        viewModel.showTemporaryError(
+            "still sad",
+            messageAfterError = "",
+            authenticateAfterError = false,
+            hapticFeedback = false,
+        )
+
+        verify(vibrator, never()).vibrateAuthError(any())
+        verify(vibrator, never()).vibrateAuthSuccess(any())
+    }
+
     private suspend fun TestScope.showTemporaryErrors(
         restart: Boolean,
         helpAfterError: String = "",
@@ -233,7 +284,7 @@
     }
 
     @Test
-    fun `no errors or temporary help after authenticated`() = runGenericTest {
+    fun no_errors_or_temporary_help_after_authenticated() = runGenericTest {
         val authenticating by collectLastValue(viewModel.isAuthenticating)
         val authenticated by collectLastValue(viewModel.isAuthenticated)
         val message by collectLastValue(viewModel.message)
@@ -249,7 +300,13 @@
             assertThat(canTryAgain).isFalse()
         }
 
-        val errorJob = launch { viewModel.showTemporaryError("error") }
+        val errorJob = launch {
+            viewModel.showTemporaryError(
+                "error",
+                messageAfterError = "",
+                authenticateAfterError = false,
+            )
+        }
         verifyNoError()
         errorJob.join()
         verifyNoError()
@@ -268,16 +325,70 @@
         assertThat(messageIsShowing).isTrue()
     }
 
-    //    @Test
-    fun `suppress errors`() = runGenericTest {
-        val errorMessage = "woot"
-        val message by collectLastValue(viewModel.message)
+    @Test
+    fun suppress_temporary_error() = runGenericTest {
+        val messages by collectValues(viewModel.message)
 
-        val errorJob = launch { viewModel.showTemporaryError(errorMessage) }
+        for (error in listOf("never", "see", "me")) {
+            launch {
+                viewModel.showTemporaryError(
+                    error,
+                    messageAfterError = "or me",
+                    authenticateAfterError = false,
+                    suppressIf = { _ -> true },
+                )
+            }
+        }
+
+        testScheduler.advanceUntilIdle()
+        assertThat(messages).containsExactly(PromptMessage.Empty)
     }
 
     @Test
-    fun `authenticated at most once`() = runGenericTest {
+    fun suppress_temporary_error_when_already_showing_when_requested() =
+        suppress_temporary_error_when_already_showing(suppress = true)
+
+    @Test
+    fun do_not_suppress_temporary_error_when_already_showing_when_not_requested() =
+        suppress_temporary_error_when_already_showing(suppress = false)
+
+    private fun suppress_temporary_error_when_already_showing(suppress: Boolean) = runGenericTest {
+        val errors = listOf("woot", "oh yeah", "nope")
+        val afterSuffix = "(after)"
+        val expectedErrorMessage = if (suppress) errors.first() else errors.last()
+        val messages by collectValues(viewModel.message)
+
+        for (error in errors) {
+            launch {
+                viewModel.showTemporaryError(
+                    error,
+                    messageAfterError = "$error $afterSuffix",
+                    authenticateAfterError = false,
+                    suppressIf = { currentMessage -> suppress && currentMessage.isError },
+                )
+            }
+        }
+
+        testScheduler.runCurrent()
+        assertThat(messages)
+            .containsExactly(
+                PromptMessage.Empty,
+                PromptMessage.Error(expectedErrorMessage),
+            )
+            .inOrder()
+
+        testScheduler.advanceUntilIdle()
+        assertThat(messages)
+            .containsExactly(
+                PromptMessage.Empty,
+                PromptMessage.Error(expectedErrorMessage),
+                PromptMessage.Help("$expectedErrorMessage $afterSuffix"),
+            )
+            .inOrder()
+    }
+
+    @Test
+    fun authenticated_at_most_once() = runGenericTest {
         val authenticating by collectLastValue(viewModel.isAuthenticating)
         val authenticated by collectLastValue(viewModel.isAuthenticated)
 
@@ -293,7 +404,7 @@
     }
 
     @Test
-    fun `authenticating cannot restart after authenticated`() = runGenericTest {
+    fun authenticating_cannot_restart_after_authenticated() = runGenericTest {
         val authenticating by collectLastValue(viewModel.isAuthenticating)
         val authenticated by collectLastValue(viewModel.isAuthenticated)
 
@@ -309,7 +420,7 @@
     }
 
     @Test
-    fun `confirm authentication`() = runGenericTest {
+    fun confirm_authentication() = runGenericTest {
         val expectConfirmation = testCase.expectConfirmation(atLeastOneFailure = false)
 
         viewModel.showAuthenticated(testCase.authenticatedModality, 0)
@@ -341,7 +452,7 @@
     }
 
     @Test
-    fun `cannot confirm unless authenticated`() = runGenericTest {
+    fun cannot_confirm_unless_authenticated() = runGenericTest {
         val authenticating by collectLastValue(viewModel.isAuthenticating)
         val authenticated by collectLastValue(viewModel.isAuthenticated)
 
@@ -360,7 +471,7 @@
     }
 
     @Test
-    fun `shows help - before authenticated`() = runGenericTest {
+    fun shows_help_before_authenticated() = runGenericTest {
         val helpMessage = "please help yourself to some cookies"
         val message by collectLastValue(viewModel.message)
         val messageVisible by collectLastValue(viewModel.isIndicatorMessageVisible)
@@ -379,7 +490,7 @@
     }
 
     @Test
-    fun `shows help - after authenticated`() = runGenericTest {
+    fun shows_help_after_authenticated() = runGenericTest {
         val expectConfirmation = testCase.expectConfirmation(atLeastOneFailure = false)
         val helpMessage = "more cookies please"
         val authenticating by collectLastValue(viewModel.isAuthenticating)
@@ -409,7 +520,7 @@
     }
 
     @Test
-    fun `retries after failure`() = runGenericTest {
+    fun retries_after_failure() = runGenericTest {
         val errorMessage = "bad"
         val helpMessage = "again?"
         val expectTryAgainButton = testCase.isFaceOnly
@@ -455,7 +566,7 @@
     }
 
     @Test
-    fun `switch to credential fallback`() = runGenericTest {
+    fun switch_to_credential_fallback() = runGenericTest {
         val size by collectLastValue(viewModel.size)
 
         // TODO(b/251476085): remove Spaghetti, migrate logic, and update this test
@@ -631,7 +742,6 @@
         }
     useBiometricsForAuthentication(
         info,
-        requireConfirmation,
         USER_ID,
         CHALLENGE,
         BiometricModalities(fingerprintProperties = fingerprint, faceProperties = face),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
new file mode 100644
index 0000000..a859321
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
@@ -0,0 +1,263 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics.ui.viewmodel
+
+import android.graphics.Rect
+import android.hardware.biometrics.SensorLocationInternal
+import android.hardware.display.DisplayManagerGlobal
+import android.view.Display
+import android.view.DisplayAdjustments
+import android.view.DisplayInfo
+import android.view.Surface
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.SysuiTestableContext
+import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
+import com.android.systemui.biometrics.domain.interactor.SideFpsOverlayInteractor
+import com.android.systemui.biometrics.domain.interactor.SideFpsOverlayInteractorImpl
+import com.android.systemui.biometrics.shared.model.FingerprintSensorType
+import com.android.systemui.biometrics.shared.model.SensorStrength
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.ArgumentMatchers
+import org.mockito.Mockito
+import org.mockito.junit.MockitoJUnit
+
+private const val DISPLAY_ID = 2
+
+@SmallTest
+@RunWith(JUnit4::class)
+class SideFpsOverlayViewModelTest : SysuiTestCase() {
+
+    @JvmField @Rule var mockitoRule = MockitoJUnit.rule()
+    private var testScope: TestScope = TestScope(StandardTestDispatcher())
+
+    private val fingerprintRepository = FakeFingerprintPropertyRepository()
+    private lateinit var interactor: SideFpsOverlayInteractor
+    private lateinit var viewModel: SideFpsOverlayViewModel
+
+    enum class DeviceConfig {
+        X_ALIGNED,
+        Y_ALIGNED,
+    }
+
+    private lateinit var deviceConfig: DeviceConfig
+    private lateinit var indicatorBounds: Rect
+    private lateinit var displayBounds: Rect
+    private lateinit var sensorLocation: SensorLocationInternal
+    private var displayWidth: Int = 0
+    private var displayHeight: Int = 0
+    private var boundsWidth: Int = 0
+    private var boundsHeight: Int = 0
+
+    @Before
+    fun setup() {
+        interactor = SideFpsOverlayInteractorImpl(fingerprintRepository)
+
+        fingerprintRepository.setProperties(
+            sensorId = 1,
+            strength = SensorStrength.STRONG,
+            sensorType = FingerprintSensorType.REAR,
+            sensorLocations =
+                mapOf(
+                    "" to
+                        SensorLocationInternal(
+                            "" /* displayId */,
+                            540 /* sensorLocationX */,
+                            1636 /* sensorLocationY */,
+                            130 /* sensorRadius */
+                        ),
+                    "display_id_1" to
+                        SensorLocationInternal(
+                            "display_id_1" /* displayId */,
+                            100 /* sensorLocationX */,
+                            300 /* sensorLocationY */,
+                            20 /* sensorRadius */
+                        )
+                )
+        )
+    }
+
+    @Test
+    fun testOverlayOffsets() =
+        testScope.runTest {
+            viewModel = SideFpsOverlayViewModel(mContext, interactor)
+
+            val interactorOffsets by collectLastValue(interactor.overlayOffsets)
+            val viewModelOffsets by collectLastValue(viewModel.overlayOffsets)
+
+            assertThat(viewModelOffsets).isEqualTo(interactorOffsets)
+        }
+
+    private fun testWithDisplay(
+        deviceConfig: DeviceConfig = DeviceConfig.X_ALIGNED,
+        isReverseDefaultRotation: Boolean = false,
+        initInfo: DisplayInfo.() -> Unit = {},
+        block: () -> Unit
+    ) {
+        this.deviceConfig = deviceConfig
+
+        when (deviceConfig) {
+            DeviceConfig.X_ALIGNED -> {
+                displayWidth = 3000
+                displayHeight = 1500
+                sensorLocation = SensorLocationInternal("", 2500, 0, 0)
+                boundsWidth = 200
+                boundsHeight = 100
+            }
+            DeviceConfig.Y_ALIGNED -> {
+                displayWidth = 2500
+                displayHeight = 2000
+                sensorLocation = SensorLocationInternal("", 0, 300, 0)
+                boundsWidth = 100
+                boundsHeight = 200
+            }
+        }
+
+        indicatorBounds = Rect(0, 0, boundsWidth, boundsHeight)
+        displayBounds = Rect(0, 0, displayWidth, displayHeight)
+
+        val displayInfo = DisplayInfo()
+        displayInfo.initInfo()
+
+        val dmGlobal = Mockito.mock(DisplayManagerGlobal::class.java)
+        val display =
+            Display(
+                dmGlobal,
+                DISPLAY_ID,
+                displayInfo,
+                DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS
+            )
+
+        whenever(dmGlobal.getDisplayInfo(ArgumentMatchers.eq(DISPLAY_ID))).thenReturn(displayInfo)
+
+        val sideFpsOverlayViewModelContext =
+            context.createDisplayContext(display) as SysuiTestableContext
+        sideFpsOverlayViewModelContext.orCreateTestableResources.addOverride(
+            com.android.internal.R.bool.config_reverseDefaultRotation,
+            isReverseDefaultRotation
+        )
+        viewModel = SideFpsOverlayViewModel(sideFpsOverlayViewModelContext, interactor)
+
+        block()
+    }
+
+    /**
+     * {@link SideFpsOverlayViewModel#updateSensorBounds} calculates indicator placement for
+     * ROTATION_0, and uses RotateUtils.rotateBounds to map to the correct indicator location given
+     * the device rotation. Assuming RotationUtils.rotateBounds works correctly, tests for indicator
+     * placement in other rotations have been omitted.
+     */
+    @Test
+    fun verifiesIndicatorPlacementForXAlignedSensor_0() =
+        testScope.runTest {
+            testWithDisplay(
+                deviceConfig = DeviceConfig.X_ALIGNED,
+                isReverseDefaultRotation = false,
+                { rotation = Surface.ROTATION_0 }
+            ) {
+                viewModel.updateSensorBounds(indicatorBounds, displayBounds, sensorLocation)
+
+                val displayInfo: DisplayInfo = DisplayInfo()
+                context.display!!.getDisplayInfo(displayInfo)
+                assertThat(displayInfo.rotation).isEqualTo(Surface.ROTATION_0)
+
+                assertThat(viewModel.sensorBounds.value).isNotNull()
+                assertThat(viewModel.sensorBounds.value.left)
+                    .isEqualTo(sensorLocation.sensorLocationX)
+                assertThat(viewModel.sensorBounds.value.top).isEqualTo(0)
+            }
+        }
+
+    /**
+     * {@link SideFpsOverlayViewModel#updateSensorBounds} calculates indicator placement for
+     * ROTATION_270 in reverse default rotation. It then uses RotateUtils.rotateBounds to map to the
+     * correct indicator location given the device rotation. Assuming RotationUtils.rotateBounds
+     * works correctly, tests for indicator placement in other rotations have been omitted.
+     */
+    @Test
+    fun verifiesIndicatorPlacementForXAlignedSensor_InReverseDefaultRotation_270() =
+        testScope.runTest {
+            testWithDisplay(
+                deviceConfig = DeviceConfig.X_ALIGNED,
+                isReverseDefaultRotation = true,
+                { rotation = Surface.ROTATION_270 }
+            ) {
+                viewModel.updateSensorBounds(indicatorBounds, displayBounds, sensorLocation)
+
+                assertThat(viewModel.sensorBounds.value).isNotNull()
+                assertThat(viewModel.sensorBounds.value.left)
+                    .isEqualTo(sensorLocation.sensorLocationX)
+                assertThat(viewModel.sensorBounds.value.top).isEqualTo(0)
+            }
+        }
+
+    /**
+     * {@link SideFpsOverlayViewModel#updateSensorBounds} calculates indicator placement for
+     * ROTATION_0, and uses RotateUtils.rotateBounds to map to the correct indicator location given
+     * the device rotation. Assuming RotationUtils.rotateBounds works correctly, tests for indicator
+     * placement in other rotations have been omitted.
+     */
+    @Test
+    fun verifiesIndicatorPlacementForYAlignedSensor_0() =
+        testScope.runTest {
+            testWithDisplay(
+                deviceConfig = DeviceConfig.Y_ALIGNED,
+                isReverseDefaultRotation = false,
+                { rotation = Surface.ROTATION_0 }
+            ) {
+                viewModel.updateSensorBounds(indicatorBounds, displayBounds, sensorLocation)
+
+                assertThat(viewModel.sensorBounds.value).isNotNull()
+                assertThat(viewModel.sensorBounds.value.left).isEqualTo(displayWidth - boundsWidth)
+                assertThat(viewModel.sensorBounds.value.top)
+                    .isEqualTo(sensorLocation.sensorLocationY)
+            }
+        }
+
+    /**
+     * {@link SideFpsOverlayViewModel#updateSensorBounds} calculates indicator placement for
+     * ROTATION_270 in reverse default rotation. It then uses RotateUtils.rotateBounds to map to the
+     * correct indicator location given the device rotation. Assuming RotationUtils.rotateBounds
+     * works correctly, tests for indicator placement in other rotations have been omitted.
+     */
+    @Test
+    fun verifiesIndicatorPlacementForYAlignedSensor_InReverseDefaultRotation_270() =
+        testScope.runTest {
+            testWithDisplay(
+                deviceConfig = DeviceConfig.Y_ALIGNED,
+                isReverseDefaultRotation = true,
+                { rotation = Surface.ROTATION_270 }
+            ) {
+                viewModel.updateSensorBounds(indicatorBounds, displayBounds, sensorLocation)
+
+                assertThat(viewModel.sensorBounds.value).isNotNull()
+                assertThat(viewModel.sensorBounds.value.left).isEqualTo(displayWidth - boundsWidth)
+                assertThat(viewModel.sensorBounds.value.top)
+                    .isEqualTo(sensorLocation.sensorLocationY)
+            }
+        }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/data/factory/BouncerMessageFactoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/data/factory/BouncerMessageFactoryTest.kt
new file mode 100644
index 0000000..992ee1a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/data/factory/BouncerMessageFactoryTest.kt
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.data.factory
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardSecurityModel
+import com.android.keyguard.KeyguardSecurityModel.SecurityMode.PIN
+import com.android.keyguard.KeyguardSecurityModel.SecurityMode.Password
+import com.android.keyguard.KeyguardSecurityModel.SecurityMode.Pattern
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_DEFAULT
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_INCORRECT_PRIMARY_AUTH_INPUT
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.bouncer.shared.model.BouncerMessageModel
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.StringSubject
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+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.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class BouncerMessageFactoryTest : SysuiTestCase() {
+    private lateinit var underTest: BouncerMessageFactory
+
+    @Mock private lateinit var updateMonitor: KeyguardUpdateMonitor
+
+    @Mock private lateinit var securityModel: KeyguardSecurityModel
+
+    private lateinit var testScope: TestScope
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        testScope = TestScope()
+        underTest = BouncerMessageFactory(updateMonitor, securityModel)
+    }
+
+    @Test
+    fun bouncerMessages_choosesTheRightMessage_basedOnSecurityModeAndFpAllowedInBouncer() =
+        testScope.runTest {
+            primaryMessage(PROMPT_REASON_DEFAULT, mode = PIN, fpAllowedInBouncer = false)
+                .isEqualTo("Enter PIN")
+            primaryMessage(PROMPT_REASON_DEFAULT, mode = PIN, fpAllowedInBouncer = true)
+                .isEqualTo("Unlock with PIN or fingerprint")
+
+            primaryMessage(PROMPT_REASON_DEFAULT, mode = Password, fpAllowedInBouncer = false)
+                .isEqualTo("Enter password")
+            primaryMessage(PROMPT_REASON_DEFAULT, mode = Password, fpAllowedInBouncer = true)
+                .isEqualTo("Unlock with password or fingerprint")
+
+            primaryMessage(PROMPT_REASON_DEFAULT, mode = Pattern, fpAllowedInBouncer = false)
+                .isEqualTo("Draw pattern")
+            primaryMessage(PROMPT_REASON_DEFAULT, mode = Pattern, fpAllowedInBouncer = true)
+                .isEqualTo("Unlock with pattern or fingerprint")
+        }
+
+    @Test
+    fun bouncerMessages_setsPrimaryAndSecondaryMessage_basedOnSecurityModeAndFpAllowedInBouncer() =
+        testScope.runTest {
+            primaryMessage(
+                    PROMPT_REASON_INCORRECT_PRIMARY_AUTH_INPUT,
+                    mode = PIN,
+                    fpAllowedInBouncer = true
+                )
+                .isEqualTo("Wrong PIN. Try again.")
+            secondaryMessage(
+                    PROMPT_REASON_INCORRECT_PRIMARY_AUTH_INPUT,
+                    mode = PIN,
+                    fpAllowedInBouncer = true
+                )
+                .isEqualTo("Or unlock with fingerprint")
+
+            primaryMessage(
+                    PROMPT_REASON_INCORRECT_PRIMARY_AUTH_INPUT,
+                    mode = Password,
+                    fpAllowedInBouncer = true
+                )
+                .isEqualTo("Wrong password. Try again.")
+            secondaryMessage(
+                    PROMPT_REASON_INCORRECT_PRIMARY_AUTH_INPUT,
+                    mode = Password,
+                    fpAllowedInBouncer = true
+                )
+                .isEqualTo("Or unlock with fingerprint")
+
+            primaryMessage(
+                    PROMPT_REASON_INCORRECT_PRIMARY_AUTH_INPUT,
+                    mode = Pattern,
+                    fpAllowedInBouncer = true
+                )
+                .isEqualTo("Wrong pattern. Try again.")
+            secondaryMessage(
+                    PROMPT_REASON_INCORRECT_PRIMARY_AUTH_INPUT,
+                    mode = Pattern,
+                    fpAllowedInBouncer = true
+                )
+                .isEqualTo("Or unlock with fingerprint")
+        }
+
+    private fun primaryMessage(
+        reason: Int,
+        mode: KeyguardSecurityModel.SecurityMode,
+        fpAllowedInBouncer: Boolean
+    ): StringSubject {
+        return assertThat(
+            context.resources.getString(
+                bouncerMessageModel(mode, fpAllowedInBouncer, reason)!!.message!!.messageResId!!
+            )
+        )!!
+    }
+
+    private fun secondaryMessage(
+        reason: Int,
+        mode: KeyguardSecurityModel.SecurityMode,
+        fpAllowedInBouncer: Boolean
+    ): StringSubject {
+        return assertThat(
+            context.resources.getString(
+                bouncerMessageModel(mode, fpAllowedInBouncer, reason)!!
+                    .secondaryMessage!!
+                    .messageResId!!
+            )
+        )!!
+    }
+
+    private fun bouncerMessageModel(
+        mode: KeyguardSecurityModel.SecurityMode,
+        fpAllowedInBouncer: Boolean,
+        reason: Int
+    ): BouncerMessageModel? {
+        whenever(securityModel.getSecurityMode(0)).thenReturn(mode)
+        whenever(updateMonitor.isFingerprintAllowedInBouncer).thenReturn(fpAllowedInBouncer)
+
+        return underTest.createFromPromptReason(reason, 0)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/data/repo/BouncerMessageRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/data/repo/BouncerMessageRepositoryTest.kt
new file mode 100644
index 0000000..de712da
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/data/repo/BouncerMessageRepositoryTest.kt
@@ -0,0 +1,365 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.data.repo
+
+import android.content.pm.UserInfo
+import android.hardware.biometrics.BiometricSourceType
+import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED
+import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST
+import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED
+import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT
+import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW
+import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT
+import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT
+import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT
+import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN
+import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE
+import com.android.keyguard.KeyguardSecurityModel
+import com.android.keyguard.KeyguardSecurityModel.SecurityMode.PIN
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.KeyguardUpdateMonitorCallback
+import com.android.systemui.R
+import com.android.systemui.R.string.keyguard_enter_pin
+import com.android.systemui.R.string.kg_prompt_after_dpm_lock
+import com.android.systemui.R.string.kg_prompt_after_user_lockdown_pin
+import com.android.systemui.R.string.kg_prompt_auth_timeout
+import com.android.systemui.R.string.kg_prompt_pin_auth_timeout
+import com.android.systemui.R.string.kg_prompt_reason_restart_pin
+import com.android.systemui.R.string.kg_prompt_unattended_update
+import com.android.systemui.R.string.kg_trust_agent_disabled
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.bouncer.data.factory.BouncerMessageFactory
+import com.android.systemui.bouncer.data.repository.BouncerMessageRepository
+import com.android.systemui.bouncer.data.repository.BouncerMessageRepositoryImpl
+import com.android.systemui.bouncer.shared.model.BouncerMessageModel
+import com.android.systemui.bouncer.shared.model.Message
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
+import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.data.repository.FakeTrustRepository
+import com.android.systemui.keyguard.shared.model.AuthenticationFlags
+import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+@RunWith(AndroidJUnit4::class)
+class BouncerMessageRepositoryTest : SysuiTestCase() {
+
+    @Mock private lateinit var updateMonitor: KeyguardUpdateMonitor
+    @Mock private lateinit var securityModel: KeyguardSecurityModel
+    @Captor
+    private lateinit var updateMonitorCallback: ArgumentCaptor<KeyguardUpdateMonitorCallback>
+
+    private lateinit var underTest: BouncerMessageRepository
+    private lateinit var trustRepository: FakeTrustRepository
+    private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository
+    private lateinit var userRepository: FakeUserRepository
+    private lateinit var fingerprintRepository: FakeDeviceEntryFingerprintAuthRepository
+    private lateinit var testScope: TestScope
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        trustRepository = FakeTrustRepository()
+        biometricSettingsRepository = FakeBiometricSettingsRepository()
+        userRepository = FakeUserRepository()
+        userRepository.setUserInfos(listOf(PRIMARY_USER))
+        fingerprintRepository = FakeDeviceEntryFingerprintAuthRepository()
+        testScope = TestScope()
+
+        whenever(updateMonitor.isFingerprintAllowedInBouncer).thenReturn(false)
+        whenever(securityModel.getSecurityMode(PRIMARY_USER_ID)).thenReturn(PIN)
+        underTest =
+            BouncerMessageRepositoryImpl(
+                trustRepository = trustRepository,
+                biometricSettingsRepository = biometricSettingsRepository,
+                updateMonitor = updateMonitor,
+                bouncerMessageFactory = BouncerMessageFactory(updateMonitor, securityModel),
+                userRepository = userRepository,
+                fingerprintAuthRepository = fingerprintRepository
+            )
+    }
+
+    @Test
+    fun setCustomMessage_propagatesState() =
+        testScope.runTest {
+            underTest.setCustomMessage(message("not empty"))
+
+            val customMessage = collectLastValue(underTest.customMessage)
+
+            assertThat(customMessage()).isEqualTo(message("not empty"))
+        }
+
+    @Test
+    fun setFaceMessage_propagatesState() =
+        testScope.runTest {
+            underTest.setFaceAcquisitionMessage(message("not empty"))
+
+            val faceAcquisitionMessage = collectLastValue(underTest.faceAcquisitionMessage)
+
+            assertThat(faceAcquisitionMessage()).isEqualTo(message("not empty"))
+        }
+
+    @Test
+    fun setFpMessage_propagatesState() =
+        testScope.runTest {
+            underTest.setFingerprintAcquisitionMessage(message("not empty"))
+
+            val fpAcquisitionMsg = collectLastValue(underTest.fingerprintAcquisitionMessage)
+
+            assertThat(fpAcquisitionMsg()).isEqualTo(message("not empty"))
+        }
+
+    @Test
+    fun setPrimaryAuthMessage_propagatesState() =
+        testScope.runTest {
+            underTest.setPrimaryAuthMessage(message("not empty"))
+
+            val primaryAuthMessage = collectLastValue(underTest.primaryAuthMessage)
+
+            assertThat(primaryAuthMessage()).isEqualTo(message("not empty"))
+        }
+
+    @Test
+    fun biometricAuthMessage_propagatesBiometricAuthMessages() =
+        testScope.runTest {
+            userRepository.setSelectedUserInfo(PRIMARY_USER)
+            val biometricAuthMessage = collectLastValue(underTest.biometricAuthMessage)
+            runCurrent()
+
+            verify(updateMonitor).registerCallback(updateMonitorCallback.capture())
+
+            updateMonitorCallback.value.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT)
+
+            assertThat(biometricAuthMessage())
+                .isEqualTo(message(R.string.kg_fp_not_recognized, R.string.kg_bio_try_again_or_pin))
+
+            updateMonitorCallback.value.onBiometricAuthFailed(BiometricSourceType.FACE)
+
+            assertThat(biometricAuthMessage())
+                .isEqualTo(
+                    message(R.string.bouncer_face_not_recognized, R.string.kg_bio_try_again_or_pin)
+                )
+
+            updateMonitorCallback.value.onBiometricAcquired(BiometricSourceType.FACE, 0)
+
+            assertThat(biometricAuthMessage()).isNull()
+        }
+
+    @Test
+    fun onFaceLockout_propagatesState() =
+        testScope.runTest {
+            userRepository.setSelectedUserInfo(PRIMARY_USER)
+            val lockoutMessage = collectLastValue(underTest.biometricLockedOutMessage)
+            runCurrent()
+            verify(updateMonitor).registerCallback(updateMonitorCallback.capture())
+
+            whenever(updateMonitor.isFaceLockedOut).thenReturn(true)
+            updateMonitorCallback.value.onLockedOutStateChanged(BiometricSourceType.FACE)
+
+            assertThat(lockoutMessage())
+                .isEqualTo(message(keyguard_enter_pin, R.string.kg_face_locked_out))
+
+            whenever(updateMonitor.isFaceLockedOut).thenReturn(false)
+            updateMonitorCallback.value.onLockedOutStateChanged(BiometricSourceType.FACE)
+            assertThat(lockoutMessage()).isNull()
+        }
+
+    @Test
+    fun onFingerprintLockout_propagatesState() =
+        testScope.runTest {
+            userRepository.setSelectedUserInfo(PRIMARY_USER)
+            val lockedOutMessage = collectLastValue(underTest.biometricLockedOutMessage)
+            runCurrent()
+
+            fingerprintRepository.setLockedOut(true)
+
+            assertThat(lockedOutMessage())
+                .isEqualTo(message(keyguard_enter_pin, R.string.kg_fp_locked_out))
+
+            fingerprintRepository.setLockedOut(false)
+            assertThat(lockedOutMessage()).isNull()
+        }
+
+    @Test
+    fun onAuthFlagsChanged_withTrustNotManagedAndNoBiometrics_isANoop() =
+        testScope.runTest {
+            userRepository.setSelectedUserInfo(PRIMARY_USER)
+            trustRepository.setCurrentUserTrustManaged(false)
+            biometricSettingsRepository.setFaceEnrolled(false)
+            biometricSettingsRepository.setFingerprintEnrolled(false)
+
+            verifyMessagesForAuthFlag(
+                STRONG_AUTH_NOT_REQUIRED to null,
+                STRONG_AUTH_REQUIRED_AFTER_BOOT to null,
+                SOME_AUTH_REQUIRED_AFTER_USER_REQUEST to null,
+                STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to null,
+                STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to null,
+                STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to null,
+                STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to null,
+                SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED to null,
+                STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to null,
+                STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to
+                    Pair(keyguard_enter_pin, kg_prompt_after_dpm_lock),
+            )
+        }
+
+    @Test
+    fun authFlagsChanges_withTrustManaged_providesDifferentMessages() =
+        testScope.runTest {
+            userRepository.setSelectedUserInfo(PRIMARY_USER)
+            biometricSettingsRepository.setFaceEnrolled(false)
+            biometricSettingsRepository.setFingerprintEnrolled(false)
+
+            trustRepository.setCurrentUserTrustManaged(true)
+
+            verifyMessagesForAuthFlag(
+                STRONG_AUTH_NOT_REQUIRED to null,
+                STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to null,
+                STRONG_AUTH_REQUIRED_AFTER_BOOT to
+                    Pair(keyguard_enter_pin, kg_prompt_reason_restart_pin),
+                STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to
+                    Pair(keyguard_enter_pin, kg_prompt_pin_auth_timeout),
+                STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to
+                    Pair(keyguard_enter_pin, kg_prompt_after_dpm_lock),
+                SOME_AUTH_REQUIRED_AFTER_USER_REQUEST to
+                    Pair(keyguard_enter_pin, kg_trust_agent_disabled),
+                SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED to
+                    Pair(keyguard_enter_pin, kg_trust_agent_disabled),
+                STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to
+                    Pair(keyguard_enter_pin, kg_prompt_after_user_lockdown_pin),
+                STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to
+                    Pair(keyguard_enter_pin, kg_prompt_unattended_update),
+                STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to
+                    Pair(keyguard_enter_pin, kg_prompt_auth_timeout),
+            )
+        }
+
+    @Test
+    fun authFlagsChanges_withFaceEnrolled_providesDifferentMessages() =
+        testScope.runTest {
+            userRepository.setSelectedUserInfo(PRIMARY_USER)
+            trustRepository.setCurrentUserTrustManaged(false)
+            biometricSettingsRepository.setFingerprintEnrolled(false)
+
+            biometricSettingsRepository.setIsFaceAuthEnabled(true)
+            biometricSettingsRepository.setFaceEnrolled(true)
+
+            verifyMessagesForAuthFlag(
+                STRONG_AUTH_NOT_REQUIRED to null,
+                STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to null,
+                SOME_AUTH_REQUIRED_AFTER_USER_REQUEST to null,
+                SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED to null,
+                STRONG_AUTH_REQUIRED_AFTER_BOOT to
+                    Pair(keyguard_enter_pin, kg_prompt_reason_restart_pin),
+                STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to
+                    Pair(keyguard_enter_pin, kg_prompt_pin_auth_timeout),
+                STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to
+                    Pair(keyguard_enter_pin, kg_prompt_after_dpm_lock),
+                STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to
+                    Pair(keyguard_enter_pin, kg_prompt_after_user_lockdown_pin),
+                STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to
+                    Pair(keyguard_enter_pin, kg_prompt_unattended_update),
+                STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to
+                    Pair(keyguard_enter_pin, kg_prompt_auth_timeout),
+            )
+        }
+
+    @Test
+    fun authFlagsChanges_withFingerprintEnrolled_providesDifferentMessages() =
+        testScope.runTest {
+            userRepository.setSelectedUserInfo(PRIMARY_USER)
+            trustRepository.setCurrentUserTrustManaged(false)
+            biometricSettingsRepository.setIsFaceAuthEnabled(false)
+            biometricSettingsRepository.setFaceEnrolled(false)
+
+            biometricSettingsRepository.setFingerprintEnrolled(true)
+            biometricSettingsRepository.setFingerprintEnabledByDevicePolicy(true)
+
+            verifyMessagesForAuthFlag(
+                STRONG_AUTH_NOT_REQUIRED to null,
+                STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to null,
+                SOME_AUTH_REQUIRED_AFTER_USER_REQUEST to null,
+                SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED to null,
+                STRONG_AUTH_REQUIRED_AFTER_BOOT to
+                    Pair(keyguard_enter_pin, kg_prompt_reason_restart_pin),
+                STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to
+                    Pair(keyguard_enter_pin, kg_prompt_pin_auth_timeout),
+                STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to
+                    Pair(keyguard_enter_pin, kg_prompt_after_dpm_lock),
+                STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to
+                    Pair(keyguard_enter_pin, kg_prompt_after_user_lockdown_pin),
+                STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to
+                    Pair(keyguard_enter_pin, kg_prompt_unattended_update),
+                STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to
+                    Pair(keyguard_enter_pin, kg_prompt_auth_timeout),
+            )
+        }
+
+    private fun TestScope.verifyMessagesForAuthFlag(
+        vararg authFlagToExpectedMessages: Pair<Int, Pair<Int, Int>?>
+    ) {
+        val authFlagsMessage = collectLastValue(underTest.authFlagsMessage)
+
+        authFlagToExpectedMessages.forEach { (flag, messagePair) ->
+            biometricSettingsRepository.setAuthenticationFlags(
+                AuthenticationFlags(PRIMARY_USER_ID, flag)
+            )
+
+            assertThat(authFlagsMessage())
+                .isEqualTo(messagePair?.let { message(it.first, it.second) })
+        }
+    }
+
+    private fun message(primaryResId: Int, secondaryResId: Int): BouncerMessageModel {
+        return BouncerMessageModel(
+            message = Message(messageResId = primaryResId),
+            secondaryMessage = Message(messageResId = secondaryResId)
+        )
+    }
+    private fun message(value: String): BouncerMessageModel {
+        return BouncerMessageModel(message = Message(message = value))
+    }
+
+    companion object {
+        private const val PRIMARY_USER_ID = 0
+        private val PRIMARY_USER =
+            UserInfo(
+                /* id= */ PRIMARY_USER_ID,
+                /* name= */ "primary user",
+                /* flags= */ UserInfo.FLAG_PRIMARY
+            )
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepositoryTest.kt
new file mode 100644
index 0000000..a049191
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepositoryTest.kt
@@ -0,0 +1,49 @@
+package com.android.systemui.bouncer.data.repository
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.keyguard.ViewMediatorCallback
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.time.SystemClock
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.TestCoroutineScope
+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
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class KeyguardBouncerRepositoryTest : SysuiTestCase() {
+
+    @Mock private lateinit var systemClock: SystemClock
+    @Mock private lateinit var viewMediatorCallback: ViewMediatorCallback
+    @Mock private lateinit var bouncerLogger: TableLogBuffer
+    lateinit var underTest: KeyguardBouncerRepository
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        val testCoroutineScope = TestCoroutineScope()
+        underTest =
+            KeyguardBouncerRepositoryImpl(
+                systemClock,
+                testCoroutineScope,
+                bouncerLogger,
+            )
+    }
+
+    @Test
+    fun changingFlowValueTriggersLogging() = runBlocking {
+        underTest.setPrimaryShow(true)
+        Mockito.verify(bouncerLogger)
+            .logChange(eq(""), eq("PrimaryBouncerShow"), value = eq(false), any())
+    }
+}
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
new file mode 100644
index 0000000..186df02
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bouncer.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.RoboPilotTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
+import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepositoryImpl
+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
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.FakeSystemClock
+import com.android.systemui.util.time.SystemClock
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestCoroutineScope
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
+class AlternateBouncerInteractorTest : SysuiTestCase() {
+    private lateinit var underTest: AlternateBouncerInteractor
+    private lateinit var bouncerRepository: KeyguardBouncerRepository
+    private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository
+    private lateinit var deviceEntryFingerprintAuthRepository:
+        FakeDeviceEntryFingerprintAuthRepository
+    @Mock private lateinit var statusBarStateController: StatusBarStateController
+    @Mock private lateinit var keyguardStateController: KeyguardStateController
+    @Mock private lateinit var systemClock: SystemClock
+    @Mock private lateinit var bouncerLogger: TableLogBuffer
+    @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        bouncerRepository =
+            KeyguardBouncerRepositoryImpl(
+                FakeSystemClock(),
+                TestCoroutineScope(),
+                bouncerLogger,
+            )
+        biometricSettingsRepository = FakeBiometricSettingsRepository()
+        deviceEntryFingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository()
+        underTest =
+            AlternateBouncerInteractor(
+                statusBarStateController,
+                keyguardStateController,
+                bouncerRepository,
+                biometricSettingsRepository,
+                systemClock,
+                keyguardUpdateMonitor,
+            )
+    }
+
+    @Test
+    fun canShowAlternateBouncerForFingerprint_givenCanShow() {
+        givenCanShowAlternateBouncer()
+        assertTrue(underTest.canShowAlternateBouncerForFingerprint())
+    }
+
+    @Test
+    fun canShowAlternateBouncerForFingerprint_alternateBouncerUIUnavailable() {
+        givenCanShowAlternateBouncer()
+        bouncerRepository.setAlternateBouncerUIAvailable(false)
+
+        assertFalse(underTest.canShowAlternateBouncerForFingerprint())
+    }
+
+    @Test
+    fun canShowAlternateBouncerForFingerprint_noFingerprintsEnrolled() {
+        givenCanShowAlternateBouncer()
+        biometricSettingsRepository.setFingerprintEnrolled(false)
+
+        assertFalse(underTest.canShowAlternateBouncerForFingerprint())
+    }
+
+    @Test
+    fun canShowAlternateBouncerForFingerprint_strongBiometricNotAllowed() {
+        givenCanShowAlternateBouncer()
+        biometricSettingsRepository.setStrongBiometricAllowed(false)
+
+        assertFalse(underTest.canShowAlternateBouncerForFingerprint())
+    }
+
+    @Test
+    fun canShowAlternateBouncerForFingerprint_devicePolicyDoesNotAllowFingerprint() {
+        givenCanShowAlternateBouncer()
+        biometricSettingsRepository.setFingerprintEnabledByDevicePolicy(false)
+
+        assertFalse(underTest.canShowAlternateBouncerForFingerprint())
+    }
+
+    @Test
+    fun canShowAlternateBouncerForFingerprint_fingerprintLockedOut() {
+        givenCanShowAlternateBouncer()
+        whenever(keyguardUpdateMonitor.isFingerprintLockedOut).thenReturn(true)
+
+        assertFalse(underTest.canShowAlternateBouncerForFingerprint())
+    }
+
+    @Test
+    fun show_whenCanShow() {
+        givenCanShowAlternateBouncer()
+
+        assertTrue(underTest.show())
+        assertTrue(bouncerRepository.alternateBouncerVisible.value)
+    }
+
+    @Test
+    fun canShowAlternateBouncerForFingerprint_butCanDismissLockScreen() {
+        givenCanShowAlternateBouncer()
+        whenever(keyguardStateController.isUnlocked).thenReturn(true)
+
+        assertFalse(underTest.canShowAlternateBouncerForFingerprint())
+    }
+
+    @Test
+    fun show_whenCannotShow() {
+        givenCannotShowAlternateBouncer()
+
+        assertFalse(underTest.show())
+        assertFalse(bouncerRepository.alternateBouncerVisible.value)
+    }
+
+    @Test
+    fun hide_wasPreviouslyShowing() {
+        bouncerRepository.setAlternateVisible(true)
+
+        assertTrue(underTest.hide())
+        assertFalse(bouncerRepository.alternateBouncerVisible.value)
+    }
+
+    @Test
+    fun hide_wasNotPreviouslyShowing() {
+        bouncerRepository.setAlternateVisible(false)
+
+        assertFalse(underTest.hide())
+        assertFalse(bouncerRepository.alternateBouncerVisible.value)
+    }
+
+    private fun givenCanShowAlternateBouncer() {
+        bouncerRepository.setAlternateBouncerUIAvailable(true)
+        biometricSettingsRepository.setFingerprintEnrolled(true)
+        biometricSettingsRepository.setStrongBiometricAllowed(true)
+        biometricSettingsRepository.setFingerprintEnabledByDevicePolicy(true)
+        whenever(keyguardUpdateMonitor.isFingerprintLockedOut).thenReturn(false)
+        whenever(keyguardStateController.isUnlocked).thenReturn(false)
+    }
+
+    private fun givenCannotShowAlternateBouncer() {
+        biometricSettingsRepository.setFingerprintEnrolled(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 374c28d6..6babf04 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
@@ -19,14 +19,17 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.scene.SceneTestUtils
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
 import com.google.common.truth.Truth.assertThat
+import kotlin.math.ceil
+import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.advanceTimeBy
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
@@ -40,8 +43,8 @@
 @RunWith(JUnit4::class)
 class BouncerInteractorTest : SysuiTestCase() {
 
-    private val testScope = TestScope()
-    private val utils = SceneTestUtils(this, testScope)
+    private val utils = SceneTestUtils(this)
+    private val testScope = utils.testScope
     private val authenticationInteractor =
         utils.authenticationInteractor(
             repository = utils.authenticationRepository(),
@@ -66,12 +69,13 @@
     @Test
     fun pinAuthMethod() =
         testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.currentScene("container1"))
+            val currentScene by
+                collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
             val message by collectLastValue(underTest.message)
 
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
-            authenticationInteractor.lockDevice()
-            underTest.showOrUnlockDevice("container1")
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            utils.authenticationRepository.setUnlocked(false)
+            underTest.showOrUnlockDevice(SceneTestUtils.CONTAINER_1)
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PIN)
 
@@ -90,20 +94,88 @@
             assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PIN)
 
             // Correct input.
-            assertThat(underTest.authenticate(listOf(1, 2, 3, 4))).isTrue()
+            assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)).isTrue()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
         }
 
     @Test
+    fun pinAuthMethod_tryAutoConfirm_withAutoConfirmPin() =
+        testScope.runTest {
+            val currentScene by
+                collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
+            val message by collectLastValue(underTest.message)
+
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            utils.authenticationRepository.setAutoConfirmEnabled(true)
+            utils.authenticationRepository.setUnlocked(false)
+            underTest.showOrUnlockDevice(SceneTestUtils.CONTAINER_1)
+            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
+            assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PIN)
+            underTest.clearMessage()
+
+            // Incomplete input.
+            assertThat(underTest.authenticate(listOf(1, 2), tryAutoConfirm = true)).isNull()
+            assertThat(message).isEmpty()
+            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
+
+            // Wrong 6-digit pin
+            assertThat(underTest.authenticate(listOf(1, 2, 3, 5, 5, 6), tryAutoConfirm = true))
+                .isFalse()
+            assertThat(message).isEqualTo(MESSAGE_WRONG_PIN)
+            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
+
+            // Correct input.
+            assertThat(
+                    underTest.authenticate(
+                        FakeAuthenticationRepository.DEFAULT_PIN,
+                        tryAutoConfirm = true
+                    )
+                )
+                .isTrue()
+            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
+        }
+
+    @Test
+    fun pinAuthMethod_tryAutoConfirm_withoutAutoConfirmPin() =
+        testScope.runTest {
+            val currentScene by
+                collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
+            val message by collectLastValue(underTest.message)
+
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            utils.authenticationRepository.setUnlocked(false)
+            underTest.showOrUnlockDevice(SceneTestUtils.CONTAINER_1)
+            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
+            underTest.clearMessage()
+
+            // Incomplete input.
+            assertThat(underTest.authenticate(listOf(1, 2), tryAutoConfirm = true)).isNull()
+            assertThat(message).isEmpty()
+            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
+
+            // Correct input.
+            assertThat(
+                    underTest.authenticate(
+                        FakeAuthenticationRepository.DEFAULT_PIN,
+                        tryAutoConfirm = true
+                    )
+                )
+                .isNull()
+            assertThat(message).isEmpty()
+            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
+        }
+
+    @Test
     fun passwordAuthMethod() =
         testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.currentScene("container1"))
+            val currentScene by
+                collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
             val message by collectLastValue(underTest.message)
-            authenticationInteractor.setAuthenticationMethod(
-                AuthenticationMethodModel.Password("password")
+            utils.authenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Password
             )
-            authenticationInteractor.lockDevice()
-            underTest.showOrUnlockDevice("container1")
+            utils.authenticationRepository.setUnlocked(false)
+            underTest.showOrUnlockDevice(SceneTestUtils.CONTAINER_1)
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PASSWORD)
 
@@ -129,13 +201,14 @@
     @Test
     fun patternAuthMethod() =
         testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.currentScene("container1"))
+            val currentScene by
+                collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
             val message by collectLastValue(underTest.message)
-            authenticationInteractor.setAuthenticationMethod(
-                AuthenticationMethodModel.Pattern(emptyList())
+            utils.authenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pattern
             )
-            authenticationInteractor.lockDevice()
-            underTest.showOrUnlockDevice("container1")
+            utils.authenticationRepository.setUnlocked(false)
+            underTest.showOrUnlockDevice(SceneTestUtils.CONTAINER_1)
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PATTERN)
 
@@ -148,7 +221,7 @@
             // Wrong input.
             assertThat(
                     underTest.authenticate(
-                        listOf(AuthenticationMethodModel.Pattern.PatternCoordinate(3, 4))
+                        listOf(AuthenticationMethodModel.Pattern.PatternCoordinate(1, 2))
                     )
                 )
                 .isFalse()
@@ -159,19 +232,20 @@
             assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PATTERN)
 
             // Correct input.
-            assertThat(underTest.authenticate(emptyList())).isTrue()
+            assertThat(underTest.authenticate(FakeAuthenticationRepository.PATTERN)).isTrue()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
         }
 
     @Test
     fun showOrUnlockDevice_notLocked_switchesToGoneScene() =
         testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.currentScene("container1"))
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
-            authenticationInteractor.unlockDevice()
+            val currentScene by
+                collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            utils.authenticationRepository.setUnlocked(true)
             runCurrent()
 
-            underTest.showOrUnlockDevice("container1")
+            underTest.showOrUnlockDevice(SceneTestUtils.CONTAINER_1)
 
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
         }
@@ -179,11 +253,12 @@
     @Test
     fun showOrUnlockDevice_authMethodNotSecure_switchesToGoneScene() =
         testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.currentScene("container1"))
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
-            authenticationInteractor.lockDevice()
+            val currentScene by
+                collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
+            utils.authenticationRepository.setUnlocked(false)
 
-            underTest.showOrUnlockDevice("container1")
+            underTest.showOrUnlockDevice(SceneTestUtils.CONTAINER_1)
 
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
         }
@@ -191,15 +266,16 @@
     @Test
     fun showOrUnlockDevice_customMessageShown() =
         testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.currentScene("container1"))
+            val currentScene by
+                collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
             val message by collectLastValue(underTest.message)
-            authenticationInteractor.setAuthenticationMethod(
-                AuthenticationMethodModel.Password("password")
+            utils.authenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Password
             )
-            authenticationInteractor.lockDevice()
+            utils.authenticationRepository.setUnlocked(false)
 
             val customMessage = "Hello there!"
-            underTest.showOrUnlockDevice("container1", customMessage)
+            underTest.showOrUnlockDevice(SceneTestUtils.CONTAINER_1, customMessage)
 
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             assertThat(message).isEqualTo(customMessage)
@@ -208,44 +284,78 @@
     @Test
     fun throttling() =
         testScope.runTest {
+            val isThrottled by collectLastValue(underTest.isThrottled)
             val throttling by collectLastValue(underTest.throttling)
             val message by collectLastValue(underTest.message)
-            val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
-            assertThat(throttling).isNull()
-            assertThat(message).isEqualTo("")
-            assertThat(isUnlocked).isFalse()
-            repeat(BouncerInteractor.THROTTLE_EVERY) { times ->
+            val currentScene by
+                collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            runCurrent()
+            underTest.showOrUnlockDevice(SceneTestUtils.CONTAINER_1)
+            runCurrent()
+            assertThat(currentScene?.key).isEqualTo(SceneKey.Bouncer)
+            assertThat(isThrottled).isFalse()
+            assertThat(throttling).isEqualTo(AuthenticationThrottlingModel())
+            assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PIN)
+            repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING) { times ->
                 // Wrong PIN.
                 assertThat(underTest.authenticate(listOf(6, 7, 8, 9))).isFalse()
-                if (times < BouncerInteractor.THROTTLE_EVERY - 1) {
+                if (
+                    times < FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING - 1
+                ) {
                     assertThat(message).isEqualTo(MESSAGE_WRONG_PIN)
                 }
             }
-            assertThat(throttling).isNotNull()
-            assertTryAgainMessage(message, BouncerInteractor.THROTTLE_DURATION_SEC)
+            assertThat(isThrottled).isTrue()
+            assertThat(throttling)
+                .isEqualTo(
+                    AuthenticationThrottlingModel(
+                        failedAttemptCount =
+                            FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING,
+                        remainingMs = FakeAuthenticationRepository.THROTTLE_DURATION_MS,
+                    )
+                )
+            assertTryAgainMessage(
+                message,
+                FakeAuthenticationRepository.THROTTLE_DURATION_MS.milliseconds.inWholeSeconds
+                    .toInt()
+            )
 
-            // Correct PIN, but throttled, so doesn't unlock:
-            assertThat(underTest.authenticate(listOf(1, 2, 3, 4))).isFalse()
-            assertThat(isUnlocked).isFalse()
-            assertTryAgainMessage(message, BouncerInteractor.THROTTLE_DURATION_SEC)
+            // Correct PIN, but throttled, so doesn't change away from the bouncer scene:
+            assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)).isNull()
+            assertThat(currentScene?.key).isEqualTo(SceneKey.Bouncer)
+            assertTryAgainMessage(
+                message,
+                FakeAuthenticationRepository.THROTTLE_DURATION_MS.milliseconds.inWholeSeconds
+                    .toInt()
+            )
 
-            throttling?.totalDurationSec?.let { seconds ->
+            throttling?.remainingMs?.let { remainingMs ->
+                val seconds = ceil(remainingMs / 1000f).toInt()
                 repeat(seconds) { time ->
                     advanceTimeBy(1000)
-                    val remainingTime = seconds - time - 1
-                    if (remainingTime > 0) {
-                        assertTryAgainMessage(message, remainingTime)
+                    val remainingTimeSec = seconds - time - 1
+                    if (remainingTimeSec > 0) {
+                        assertTryAgainMessage(message, remainingTimeSec)
                     }
                 }
             }
             assertThat(message).isEqualTo("")
-            assertThat(throttling).isNull()
-            assertThat(isUnlocked).isFalse()
+            assertThat(isThrottled).isFalse()
+            assertThat(throttling)
+                .isEqualTo(
+                    AuthenticationThrottlingModel(
+                        failedAttemptCount =
+                            FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING,
+                    )
+                )
+            assertThat(currentScene?.key).isEqualTo(SceneKey.Bouncer)
 
-            // Correct PIN and no longer throttled so unlocks:
-            assertThat(underTest.authenticate(listOf(1, 2, 3, 4))).isTrue()
-            assertThat(isUnlocked).isTrue()
+            // Correct PIN and no longer throttled so changes to the Gone scene:
+            assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)).isTrue()
+            assertThat(currentScene?.key).isEqualTo(SceneKey.Gone)
+            assertThat(isThrottled).isFalse()
+            assertThat(throttling).isEqualTo(AuthenticationThrottlingModel())
         }
 
     private fun assertTryAgainMessage(
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
new file mode 100644
index 0000000..8e5256e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt
@@ -0,0 +1,285 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.domain.interactor
+
+import android.content.pm.UserInfo
+import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardSecurityModel
+import com.android.keyguard.KeyguardSecurityModel.SecurityMode.PIN
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.R.string.keyguard_enter_pin
+import com.android.systemui.R.string.kg_too_many_failed_attempts_countdown
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.bouncer.data.factory.BouncerMessageFactory
+import com.android.systemui.bouncer.data.repository.FakeBouncerMessageRepository
+import com.android.systemui.bouncer.shared.model.BouncerMessageModel
+import com.android.systemui.bouncer.shared.model.Message
+import com.android.systemui.coroutines.FlowValue
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.util.mockito.KotlinArgumentCaptor
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+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.ArgumentMatchers.eq
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+@RunWith(AndroidJUnit4::class)
+class BouncerMessageInteractorTest : SysuiTestCase() {
+
+    @Mock private lateinit var securityModel: KeyguardSecurityModel
+    @Mock private lateinit var updateMonitor: KeyguardUpdateMonitor
+    @Mock private lateinit var countDownTimerUtil: CountDownTimerUtil
+    private lateinit var countDownTimerCallback: KotlinArgumentCaptor<CountDownTimerCallback>
+    private lateinit var underTest: BouncerMessageInteractor
+    private lateinit var repository: FakeBouncerMessageRepository
+    private lateinit var userRepository: FakeUserRepository
+    private lateinit var testScope: TestScope
+    private lateinit var bouncerMessage: FlowValue<BouncerMessageModel?>
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        repository = FakeBouncerMessageRepository()
+        userRepository = FakeUserRepository()
+        userRepository.setUserInfos(listOf(PRIMARY_USER))
+        testScope = TestScope()
+        countDownTimerCallback = KotlinArgumentCaptor(CountDownTimerCallback::class.java)
+
+        allowTestableLooperAsMainThread()
+        whenever(securityModel.getSecurityMode(PRIMARY_USER_ID)).thenReturn(PIN)
+        whenever(updateMonitor.isFingerprintAllowedInBouncer).thenReturn(false)
+    }
+
+    suspend fun TestScope.init() {
+        userRepository.setSelectedUserInfo(PRIMARY_USER)
+        val featureFlags = FakeFeatureFlags()
+        featureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true)
+        underTest =
+            BouncerMessageInteractor(
+                repository = repository,
+                factory = BouncerMessageFactory(updateMonitor, securityModel),
+                userRepository = userRepository,
+                countDownTimerUtil = countDownTimerUtil,
+                featureFlags = featureFlags
+            )
+        bouncerMessage = collectLastValue(underTest.bouncerMessage)
+    }
+
+    @Test
+    fun onIncorrectSecurityInput_setsTheBouncerModelInTheRepository() =
+        testScope.runTest {
+            init()
+            underTest.onPrimaryAuthIncorrectAttempt()
+
+            assertThat(repository.primaryAuthMessage).isNotNull()
+            assertThat(
+                    context.resources.getString(
+                        repository.primaryAuthMessage.value!!.message!!.messageResId!!
+                    )
+                )
+                .isEqualTo("Wrong PIN. Try again.")
+        }
+
+    @Test
+    fun onUserStartsPrimaryAuthInput_clearsAllSetBouncerMessages() =
+        testScope.runTest {
+            init()
+            repository.setCustomMessage(message("not empty"))
+            repository.setFaceAcquisitionMessage(message("not empty"))
+            repository.setFingerprintAcquisitionMessage(message("not empty"))
+            repository.setPrimaryAuthMessage(message("not empty"))
+
+            underTest.onPrimaryBouncerUserInput()
+
+            assertThat(repository.customMessage.value).isNull()
+            assertThat(repository.faceAcquisitionMessage.value).isNull()
+            assertThat(repository.fingerprintAcquisitionMessage.value).isNull()
+            assertThat(repository.primaryAuthMessage.value).isNull()
+        }
+
+    @Test
+    fun onBouncerBeingHidden_clearsAllSetBouncerMessages() =
+        testScope.runTest {
+            init()
+            repository.setCustomMessage(message("not empty"))
+            repository.setFaceAcquisitionMessage(message("not empty"))
+            repository.setFingerprintAcquisitionMessage(message("not empty"))
+            repository.setPrimaryAuthMessage(message("not empty"))
+
+            underTest.onBouncerBeingHidden()
+
+            assertThat(repository.customMessage.value).isNull()
+            assertThat(repository.faceAcquisitionMessage.value).isNull()
+            assertThat(repository.fingerprintAcquisitionMessage.value).isNull()
+            assertThat(repository.primaryAuthMessage.value).isNull()
+        }
+
+    @Test
+    fun setCustomMessage_setsRepositoryValue() =
+        testScope.runTest {
+            init()
+
+            underTest.setCustomMessage("not empty")
+
+            assertThat(repository.customMessage.value)
+                .isEqualTo(BouncerMessageModel(secondaryMessage = Message(message = "not empty")))
+
+            underTest.setCustomMessage(null)
+            assertThat(repository.customMessage.value).isNull()
+        }
+
+    @Test
+    fun setFaceMessage_setsRepositoryValue() =
+        testScope.runTest {
+            init()
+
+            underTest.setFaceAcquisitionMessage("not empty")
+
+            assertThat(repository.faceAcquisitionMessage.value)
+                .isEqualTo(BouncerMessageModel(secondaryMessage = Message(message = "not empty")))
+
+            underTest.setFaceAcquisitionMessage(null)
+            assertThat(repository.faceAcquisitionMessage.value).isNull()
+        }
+
+    @Test
+    fun setFingerprintMessage_setsRepositoryValue() =
+        testScope.runTest {
+            init()
+
+            underTest.setFingerprintAcquisitionMessage("not empty")
+
+            assertThat(repository.fingerprintAcquisitionMessage.value)
+                .isEqualTo(BouncerMessageModel(secondaryMessage = Message(message = "not empty")))
+
+            underTest.setFingerprintAcquisitionMessage(null)
+            assertThat(repository.fingerprintAcquisitionMessage.value).isNull()
+        }
+
+    @Test
+    fun onPrimaryAuthLockout_startsTimerForSpecifiedNumberOfSeconds() =
+        testScope.runTest {
+            init()
+
+            underTest.onPrimaryAuthLockedOut(3)
+
+            verify(countDownTimerUtil)
+                .startNewTimer(eq(3000L), eq(1000L), countDownTimerCallback.capture())
+
+            countDownTimerCallback.value.onTick(2000L)
+
+            val primaryMessage = repository.primaryAuthMessage.value!!.message!!
+            assertThat(primaryMessage.messageResId!!)
+                .isEqualTo(kg_too_many_failed_attempts_countdown)
+            assertThat(primaryMessage.formatterArgs).isEqualTo(mapOf(Pair("count", 2)))
+        }
+
+    @Test
+    fun onPrimaryAuthLockout_timerComplete_resetsRepositoryMessages() =
+        testScope.runTest {
+            init()
+            repository.setCustomMessage(message("not empty"))
+            repository.setFaceAcquisitionMessage(message("not empty"))
+            repository.setFingerprintAcquisitionMessage(message("not empty"))
+            repository.setPrimaryAuthMessage(message("not empty"))
+
+            underTest.onPrimaryAuthLockedOut(3)
+
+            verify(countDownTimerUtil)
+                .startNewTimer(eq(3000L), eq(1000L), countDownTimerCallback.capture())
+
+            countDownTimerCallback.value.onFinish()
+
+            assertThat(repository.customMessage.value).isNull()
+            assertThat(repository.faceAcquisitionMessage.value).isNull()
+            assertThat(repository.fingerprintAcquisitionMessage.value).isNull()
+            assertThat(repository.primaryAuthMessage.value).isNull()
+        }
+
+    @Test
+    fun bouncerMessage_hasPriorityOrderOfMessages() =
+        testScope.runTest {
+            init()
+            repository.setBiometricAuthMessage(message("biometric message"))
+            repository.setFaceAcquisitionMessage(message("face acquisition message"))
+            repository.setFingerprintAcquisitionMessage(message("fingerprint acquisition message"))
+            repository.setPrimaryAuthMessage(message("primary auth message"))
+            repository.setAuthFlagsMessage(message("auth flags message"))
+            repository.setBiometricLockedOutMessage(message("biometrics locked out"))
+            repository.setCustomMessage(message("custom message"))
+
+            assertThat(bouncerMessage()).isEqualTo(message("primary auth message"))
+
+            repository.setPrimaryAuthMessage(null)
+
+            assertThat(bouncerMessage()).isEqualTo(message("biometric message"))
+
+            repository.setBiometricAuthMessage(null)
+
+            assertThat(bouncerMessage()).isEqualTo(message("fingerprint acquisition message"))
+
+            repository.setFingerprintAcquisitionMessage(null)
+
+            assertThat(bouncerMessage()).isEqualTo(message("face acquisition message"))
+
+            repository.setFaceAcquisitionMessage(null)
+
+            assertThat(bouncerMessage()).isEqualTo(message("custom message"))
+
+            repository.setCustomMessage(null)
+
+            assertThat(bouncerMessage()).isEqualTo(message("auth flags message"))
+
+            repository.setAuthFlagsMessage(null)
+
+            assertThat(bouncerMessage()).isEqualTo(message("biometrics locked out"))
+
+            repository.setBiometricLockedOutMessage(null)
+
+            // sets the default message if everything else is null
+            assertThat(bouncerMessage()!!.message!!.messageResId).isEqualTo(keyguard_enter_pin)
+        }
+
+    private fun message(value: String): BouncerMessageModel {
+        return BouncerMessageModel(message = Message(message = value))
+    }
+
+    companion object {
+        private const val PRIMARY_USER_ID = 0
+        private val PRIMARY_USER =
+            UserInfo(
+                /* id= */ PRIMARY_USER_ID,
+                /* name= */ "primary user",
+                /* flags= */ UserInfo.FLAG_PRIMARY
+            )
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerCallbackInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerCallbackInteractorTest.kt
new file mode 100644
index 0000000..a81ca86
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerCallbackInteractorTest.kt
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bouncer.domain.interactor
+
+import android.view.View
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
+import com.android.systemui.SysuiTestCase
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
+class PrimaryBouncerCallbackInteractorTest : SysuiTestCase() {
+    private val mPrimaryBouncerCallbackInteractor = PrimaryBouncerCallbackInteractor()
+    @Mock
+    private lateinit var mPrimaryBouncerExpansionCallback:
+        PrimaryBouncerCallbackInteractor.PrimaryBouncerExpansionCallback
+    @Mock
+    private lateinit var keyguardResetCallback:
+        PrimaryBouncerCallbackInteractor.KeyguardResetCallback
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        mPrimaryBouncerCallbackInteractor.addBouncerExpansionCallback(
+            mPrimaryBouncerExpansionCallback
+        )
+        mPrimaryBouncerCallbackInteractor.addKeyguardResetCallback(keyguardResetCallback)
+    }
+
+    @Test
+    fun testOnFullyShown() {
+        mPrimaryBouncerCallbackInteractor.dispatchFullyShown()
+        verify(mPrimaryBouncerExpansionCallback).onFullyShown()
+    }
+
+    @Test
+    fun testOnFullyHidden() {
+        mPrimaryBouncerCallbackInteractor.dispatchFullyHidden()
+        verify(mPrimaryBouncerExpansionCallback).onFullyHidden()
+    }
+
+    @Test
+    fun testOnExpansionChanged() {
+        mPrimaryBouncerCallbackInteractor.dispatchExpansionChanged(5f)
+        verify(mPrimaryBouncerExpansionCallback).onExpansionChanged(5f)
+    }
+
+    @Test
+    fun testOnVisibilityChanged() {
+        mPrimaryBouncerCallbackInteractor.dispatchVisibilityChanged(View.INVISIBLE)
+        verify(mPrimaryBouncerExpansionCallback).onVisibilityChanged(false)
+    }
+
+    @Test
+    fun testOnStartingToHide() {
+        mPrimaryBouncerCallbackInteractor.dispatchStartingToHide()
+        verify(mPrimaryBouncerExpansionCallback).onStartingToHide()
+    }
+
+    @Test
+    fun testOnStartingToShow() {
+        mPrimaryBouncerCallbackInteractor.dispatchStartingToShow()
+        verify(mPrimaryBouncerExpansionCallback).onStartingToShow()
+    }
+
+    @Test
+    fun testOnKeyguardReset() {
+        mPrimaryBouncerCallbackInteractor.dispatchReset()
+        verify(keyguardResetCallback).onKeyguardReset()
+    }
+}
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
new file mode 100644
index 0000000..f892453
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt
@@ -0,0 +1,489 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.bouncer.domain.interactor
+
+import android.hardware.biometrics.BiometricSourceType
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import android.testing.TestableResources
+import android.view.View
+import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardSecurityModel
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.DejankUtils
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
+import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN
+import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.EXPANSION_VISIBLE
+import com.android.systemui.bouncer.shared.model.BouncerShowMessageModel
+import com.android.systemui.bouncer.ui.BouncerView
+import com.android.systemui.bouncer.ui.BouncerViewDelegate
+import com.android.systemui.classifier.FalsingCollector
+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.FakeTrustRepository
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.utils.os.FakeHandler
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Answers
+import org.mockito.ArgumentCaptor
+import org.mockito.Mock
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWithLooper(setAsMainLooper = true)
+@RunWith(AndroidTestingRunner::class)
+class PrimaryBouncerInteractorTest : SysuiTestCase() {
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+    private lateinit var repository: KeyguardBouncerRepository
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS) private lateinit var bouncerView: BouncerView
+    @Mock private lateinit var bouncerViewDelegate: BouncerViewDelegate
+    @Mock private lateinit var keyguardStateController: KeyguardStateController
+    @Mock private lateinit var keyguardSecurityModel: KeyguardSecurityModel
+    @Mock private lateinit var mPrimaryBouncerCallbackInteractor: PrimaryBouncerCallbackInteractor
+    @Mock private lateinit var falsingCollector: FalsingCollector
+    @Mock private lateinit var dismissCallbackRegistry: DismissCallbackRegistry
+    @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+    private lateinit var mainHandler: FakeHandler
+    private lateinit var underTest: PrimaryBouncerInteractor
+    private lateinit var resources: TestableResources
+    private lateinit var trustRepository: FakeTrustRepository
+    private lateinit var featureFlags: FakeFeatureFlags
+    private lateinit var testScope: TestScope
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        whenever(keyguardSecurityModel.getSecurityMode(anyInt()))
+            .thenReturn(KeyguardSecurityModel.SecurityMode.PIN)
+
+        DejankUtils.setImmediate(true)
+        testScope = TestScope()
+        mainHandler = FakeHandler(android.os.Looper.getMainLooper())
+        trustRepository = FakeTrustRepository()
+        featureFlags = FakeFeatureFlags().apply { set(Flags.DELAY_BOUNCER, true) }
+        underTest =
+            PrimaryBouncerInteractor(
+                repository,
+                bouncerView,
+                mainHandler,
+                keyguardStateController,
+                keyguardSecurityModel,
+                mPrimaryBouncerCallbackInteractor,
+                falsingCollector,
+                dismissCallbackRegistry,
+                context,
+                keyguardUpdateMonitor,
+                trustRepository,
+                featureFlags,
+                testScope.backgroundScope,
+            )
+        whenever(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
+        whenever(repository.primaryBouncerShow.value).thenReturn(false)
+        whenever(bouncerView.delegate).thenReturn(bouncerViewDelegate)
+        resources = context.orCreateTestableResources
+    }
+
+    @Test
+    fun testShow_isScrimmed() {
+        underTest.show(true)
+        verify(repository).setKeyguardAuthenticated(null)
+        verify(repository).setPrimaryStartingToHide(false)
+        verify(repository).setPrimaryScrimmed(true)
+        verify(repository).setPanelExpansion(EXPANSION_VISIBLE)
+        verify(repository).setPrimaryShowingSoon(true)
+        verify(keyguardStateController).notifyPrimaryBouncerShowing(true)
+        verify(mPrimaryBouncerCallbackInteractor).dispatchStartingToShow()
+        verify(repository).setPrimaryShow(true)
+        verify(repository).setPrimaryShowingSoon(false)
+        verify(mPrimaryBouncerCallbackInteractor).dispatchVisibilityChanged(View.VISIBLE)
+    }
+
+    @Test
+    fun testShow_isNotScrimmed() {
+        verify(repository, never()).setPanelExpansion(EXPANSION_VISIBLE)
+    }
+
+    @Test
+    fun testShow_keyguardIsDone() {
+        whenever(bouncerView.delegate?.showNextSecurityScreenOrFinish()).thenReturn(true)
+        verify(keyguardStateController, never()).notifyPrimaryBouncerShowing(true)
+        verify(mPrimaryBouncerCallbackInteractor, never()).dispatchStartingToShow()
+    }
+
+    @Test
+    fun testShow_isResumed() {
+        whenever(repository.primaryBouncerShow.value).thenReturn(true)
+        whenever(keyguardSecurityModel.getSecurityMode(anyInt()))
+            .thenReturn(KeyguardSecurityModel.SecurityMode.SimPuk)
+
+        underTest.show(true)
+        verify(repository).setPrimaryShow(false)
+        verify(repository).setPrimaryShow(true)
+    }
+
+    @Test
+    fun testHide() {
+        underTest.hide()
+        verify(falsingCollector).onBouncerHidden()
+        verify(keyguardStateController).notifyPrimaryBouncerShowing(false)
+        verify(repository).setPrimaryShowingSoon(false)
+        verify(repository).setPrimaryShow(false)
+        verify(mPrimaryBouncerCallbackInteractor).dispatchVisibilityChanged(View.INVISIBLE)
+        verify(repository).setPrimaryStartDisappearAnimation(null)
+        verify(repository).setPanelExpansion(EXPANSION_HIDDEN)
+    }
+
+    @Test
+    fun testExpansion() {
+        whenever(repository.panelExpansionAmount.value).thenReturn(0.5f)
+        underTest.setPanelExpansion(0.6f)
+        verify(repository).setPanelExpansion(0.6f)
+        verify(mPrimaryBouncerCallbackInteractor).dispatchExpansionChanged(0.6f)
+    }
+
+    @Test
+    fun testExpansion_fullyShown() {
+        whenever(repository.panelExpansionAmount.value).thenReturn(0.5f)
+        whenever(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
+        underTest.setPanelExpansion(EXPANSION_VISIBLE)
+        verify(falsingCollector).onBouncerShown()
+        verify(mPrimaryBouncerCallbackInteractor).dispatchFullyShown()
+    }
+
+    @Test
+    fun testExpansion_fullyHidden() {
+        whenever(repository.panelExpansionAmount.value).thenReturn(0.5f)
+        whenever(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
+        underTest.setPanelExpansion(EXPANSION_HIDDEN)
+        verify(repository).setPrimaryShow(false)
+        verify(falsingCollector).onBouncerHidden()
+        verify(mPrimaryBouncerCallbackInteractor).dispatchReset()
+        verify(mPrimaryBouncerCallbackInteractor).dispatchFullyHidden()
+    }
+
+    @Test
+    fun testExpansion_startingToHide() {
+        whenever(repository.panelExpansionAmount.value).thenReturn(EXPANSION_VISIBLE)
+        underTest.setPanelExpansion(0.1f)
+        verify(repository).setPrimaryStartingToHide(true)
+        verify(mPrimaryBouncerCallbackInteractor).dispatchStartingToHide()
+    }
+
+    @Test
+    fun testShowMessage() {
+        val argCaptor = ArgumentCaptor.forClass(BouncerShowMessageModel::class.java)
+        underTest.showMessage("abc", null)
+        verify(repository).setShowMessage(argCaptor.capture())
+        assertThat(argCaptor.value.message).isEqualTo("abc")
+    }
+
+    @Test
+    fun testDismissAction() {
+        val onDismissAction = mock(ActivityStarter.OnDismissAction::class.java)
+        val cancelAction = mock(Runnable::class.java)
+        underTest.setDismissAction(onDismissAction, cancelAction)
+        verify(bouncerViewDelegate).setDismissAction(onDismissAction, cancelAction)
+    }
+
+    @Test
+    fun testUpdateResources() {
+        underTest.updateResources()
+        verify(repository).setResourceUpdateRequests(true)
+    }
+
+    @Test
+    fun testNotifyKeyguardAuthenticated() {
+        underTest.notifyKeyguardAuthenticated(true)
+        verify(repository).setKeyguardAuthenticated(true)
+    }
+
+    @Test
+    fun testNotifyShowedMessage() {
+        underTest.onMessageShown()
+        verify(repository).setShowMessage(null)
+    }
+
+    @Test
+    fun testSetKeyguardPosition() {
+        underTest.setKeyguardPosition(0f)
+        verify(repository).setKeyguardPosition(0f)
+    }
+
+    @Test
+    fun testNotifyKeyguardAuthenticatedHandled() {
+        underTest.notifyKeyguardAuthenticatedHandled()
+        verify(repository).setKeyguardAuthenticated(null)
+    }
+
+    @Test
+    fun testNotifyUpdatedResources() {
+        underTest.notifyUpdatedResources()
+        verify(repository).setResourceUpdateRequests(false)
+    }
+
+    @Test
+    fun testSetBackButtonEnabled() {
+        underTest.setBackButtonEnabled(true)
+        verify(repository).setIsBackButtonEnabled(true)
+    }
+
+    @Test
+    fun testStartDisappearAnimation_willRunDismissFromKeyguard() {
+        whenever(bouncerViewDelegate.willRunDismissFromKeyguard()).thenReturn(true)
+
+        val runnable = mock(Runnable::class.java)
+        underTest.startDisappearAnimation(runnable)
+        // End runnable should run immediately
+        verify(runnable).run()
+        // ... while the disappear animation should never be run
+        verify(repository, never()).setPrimaryStartDisappearAnimation(any(Runnable::class.java))
+    }
+
+    @Test
+    fun testStartDisappearAnimation_willNotRunDismissFromKeyguard_() {
+        whenever(bouncerViewDelegate.willRunDismissFromKeyguard()).thenReturn(false)
+
+        val runnable = mock(Runnable::class.java)
+        underTest.startDisappearAnimation(runnable)
+        verify(repository).setPrimaryStartDisappearAnimation(any(Runnable::class.java))
+    }
+
+    @Test
+    fun testIsFullShowing() {
+        whenever(repository.primaryBouncerShow.value).thenReturn(true)
+        whenever(repository.panelExpansionAmount.value).thenReturn(EXPANSION_VISIBLE)
+        whenever(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
+        assertThat(underTest.isFullyShowing()).isTrue()
+        whenever(repository.primaryBouncerShow.value).thenReturn(false)
+        assertThat(underTest.isFullyShowing()).isFalse()
+    }
+
+    @Test
+    fun testIsScrimmed() {
+        whenever(repository.primaryBouncerScrimmed.value).thenReturn(true)
+        assertThat(underTest.isScrimmed()).isTrue()
+        whenever(repository.primaryBouncerScrimmed.value).thenReturn(false)
+        assertThat(underTest.isScrimmed()).isFalse()
+    }
+
+    @Test
+    fun testIsInTransit() {
+        whenever(repository.primaryBouncerShowingSoon.value).thenReturn(true)
+        assertThat(underTest.isInTransit()).isTrue()
+        whenever(repository.primaryBouncerShowingSoon.value).thenReturn(false)
+        assertThat(underTest.isInTransit()).isFalse()
+        whenever(repository.panelExpansionAmount.value).thenReturn(0.5f)
+        assertThat(underTest.isInTransit()).isTrue()
+    }
+
+    @Test
+    fun testIsAnimatingAway() {
+        whenever(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(Runnable {})
+        assertThat(underTest.isAnimatingAway()).isTrue()
+        whenever(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
+        assertThat(underTest.isAnimatingAway()).isFalse()
+    }
+
+    @Test
+    fun testWillDismissWithAction() {
+        whenever(bouncerViewDelegate.willDismissWithActions()).thenReturn(true)
+        assertThat(underTest.willDismissWithAction()).isTrue()
+        whenever(bouncerViewDelegate.willDismissWithActions()).thenReturn(false)
+        assertThat(underTest.willDismissWithAction()).isFalse()
+    }
+
+    @Test
+    fun testSideFpsVisibility() {
+        updateSideFpsVisibilityParameters(
+            isVisible = true,
+            sfpsEnabled = true,
+            fpsDetectionRunning = true,
+            isUnlockingWithFpAllowed = true,
+            isAnimatingAway = false
+        )
+        underTest.updateSideFpsVisibility()
+        verify(repository).setSideFpsShowing(true)
+    }
+
+    @Test
+    fun testSideFpsVisibility_notVisible() {
+        updateSideFpsVisibilityParameters(
+            isVisible = false,
+            sfpsEnabled = true,
+            fpsDetectionRunning = true,
+            isUnlockingWithFpAllowed = true,
+            isAnimatingAway = false
+        )
+        underTest.updateSideFpsVisibility()
+        verify(repository).setSideFpsShowing(false)
+    }
+
+    @Test
+    fun testSideFpsVisibility_sfpsNotEnabled() {
+        updateSideFpsVisibilityParameters(
+            isVisible = true,
+            sfpsEnabled = false,
+            fpsDetectionRunning = true,
+            isUnlockingWithFpAllowed = true,
+            isAnimatingAway = false
+        )
+        underTest.updateSideFpsVisibility()
+        verify(repository).setSideFpsShowing(false)
+    }
+
+    @Test
+    fun testSideFpsVisibility_fpsDetectionNotRunning() {
+        updateSideFpsVisibilityParameters(
+            isVisible = true,
+            sfpsEnabled = true,
+            fpsDetectionRunning = false,
+            isUnlockingWithFpAllowed = true,
+            isAnimatingAway = false
+        )
+        underTest.updateSideFpsVisibility()
+        verify(repository).setSideFpsShowing(false)
+    }
+
+    @Test
+    fun testSideFpsVisibility_UnlockingWithFpNotAllowed() {
+        updateSideFpsVisibilityParameters(
+            isVisible = true,
+            sfpsEnabled = true,
+            fpsDetectionRunning = true,
+            isUnlockingWithFpAllowed = false,
+            isAnimatingAway = false
+        )
+        underTest.updateSideFpsVisibility()
+        verify(repository).setSideFpsShowing(false)
+    }
+
+    @Test
+    fun testSideFpsVisibility_AnimatingAway() {
+        updateSideFpsVisibilityParameters(
+            isVisible = true,
+            sfpsEnabled = true,
+            fpsDetectionRunning = true,
+            isUnlockingWithFpAllowed = true,
+            isAnimatingAway = true
+        )
+        underTest.updateSideFpsVisibility()
+        verify(repository).setSideFpsShowing(false)
+    }
+
+    @Test
+    fun delayBouncerWhenFaceAuthPossible() {
+        mainHandler.setMode(FakeHandler.Mode.QUEUEING)
+
+        // GIVEN bouncer should be delayed due to face auth
+        whenever(keyguardStateController.isFaceAuthEnabled).thenReturn(true)
+        whenever(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(BiometricSourceType.FACE))
+            .thenReturn(true)
+        whenever(keyguardUpdateMonitor.doesCurrentPostureAllowFaceAuth()).thenReturn(true)
+
+        // WHEN bouncer show is requested
+        underTest.show(true)
+
+        // THEN primary show & primary showing soon aren't updated immediately
+        verify(repository, never()).setPrimaryShow(true)
+        verify(repository, never()).setPrimaryShowingSoon(false)
+
+        // WHEN all queued messages are dispatched
+        mainHandler.dispatchQueuedMessages()
+
+        // THEN primary show & primary showing soon are updated
+        verify(repository).setPrimaryShow(true)
+        verify(repository).setPrimaryShowingSoon(false)
+    }
+
+    @Test
+    fun noDelayBouncer_biometricsAllowed_postureDoesNotAllowFaceAuth() {
+        mainHandler.setMode(FakeHandler.Mode.QUEUEING)
+
+        // GIVEN bouncer should not be delayed because device isn't in the right posture for
+        // face auth
+        whenever(keyguardStateController.isFaceAuthEnabled).thenReturn(true)
+        whenever(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(BiometricSourceType.FACE))
+            .thenReturn(true)
+        whenever(keyguardUpdateMonitor.doesCurrentPostureAllowFaceAuth()).thenReturn(false)
+
+        // WHEN bouncer show is requested
+        underTest.show(true)
+
+        // THEN primary show & primary showing soon are updated immediately
+        verify(repository).setPrimaryShow(true)
+        verify(repository).setPrimaryShowingSoon(false)
+    }
+
+    @Test
+    fun delayBouncerWhenActiveUnlockPossible() {
+        testScope.run {
+            mainHandler.setMode(FakeHandler.Mode.QUEUEING)
+
+            // GIVEN bouncer should be delayed due to active unlock
+            trustRepository.setCurrentUserActiveUnlockAvailable(true)
+            whenever(keyguardUpdateMonitor.canTriggerActiveUnlockBasedOnDeviceState())
+                .thenReturn(true)
+            runCurrent()
+
+            // WHEN bouncer show is requested
+            underTest.show(true)
+
+            // THEN primary show & primary showing soon were scheduled to update
+            verify(repository, never()).setPrimaryShow(true)
+            verify(repository, never()).setPrimaryShowingSoon(false)
+
+            // WHEN all queued messages are dispatched
+            mainHandler.dispatchQueuedMessages()
+
+            // THEN primary show & primary showing soon are updated
+            verify(repository).setPrimaryShow(true)
+            verify(repository).setPrimaryShowingSoon(false)
+        }
+    }
+
+    private fun updateSideFpsVisibilityParameters(
+        isVisible: Boolean,
+        sfpsEnabled: Boolean,
+        fpsDetectionRunning: Boolean,
+        isUnlockingWithFpAllowed: Boolean,
+        isAnimatingAway: Boolean
+    ) {
+        whenever(repository.primaryBouncerShow.value).thenReturn(isVisible)
+        resources.addOverride(R.bool.config_show_sidefps_hint_on_bouncer, sfpsEnabled)
+        whenever(keyguardUpdateMonitor.isFingerprintDetectionRunning)
+            .thenReturn(fpsDetectionRunning)
+        whenever(keyguardUpdateMonitor.isUnlockingWithFingerprintAllowed)
+            .thenReturn(isUnlockingWithFpAllowed)
+        whenever(repository.primaryBouncerStartingDisappearAnimation.value)
+            .thenReturn(if (isAnimatingAway) Runnable {} else null)
+    }
+}
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
new file mode 100644
index 0000000..665456d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.bouncer.domain.interactor
+
+import android.os.Looper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardSecurityModel
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.RoboPilotTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
+import com.android.systemui.bouncer.ui.BouncerView
+import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.coroutines.collectLastValue
+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.TrustRepository
+import com.android.systemui.statusbar.phone.KeyguardBypassController
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.utils.os.FakeHandler
+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
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
+class PrimaryBouncerInteractorWithCoroutinesTest : SysuiTestCase() {
+    private lateinit var repository: FakeKeyguardBouncerRepository
+    @Mock private lateinit var bouncerView: BouncerView
+    @Mock private lateinit var keyguardStateController: KeyguardStateController
+    @Mock private lateinit var keyguardSecurityModel: KeyguardSecurityModel
+    @Mock private lateinit var primaryBouncerCallbackInteractor: PrimaryBouncerCallbackInteractor
+    @Mock private lateinit var falsingCollector: FalsingCollector
+    @Mock private lateinit var dismissCallbackRegistry: DismissCallbackRegistry
+    @Mock private lateinit var keyguardBypassController: KeyguardBypassController
+    @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+    private val mainHandler = FakeHandler(Looper.getMainLooper())
+    private lateinit var underTest: PrimaryBouncerInteractor
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        repository = FakeKeyguardBouncerRepository()
+        underTest =
+            PrimaryBouncerInteractor(
+                repository,
+                bouncerView,
+                mainHandler,
+                keyguardStateController,
+                keyguardSecurityModel,
+                primaryBouncerCallbackInteractor,
+                falsingCollector,
+                dismissCallbackRegistry,
+                context,
+                keyguardUpdateMonitor,
+                Mockito.mock(TrustRepository::class.java),
+                FakeFeatureFlags().apply { set(Flags.DELAY_BOUNCER, true) },
+                TestScope().backgroundScope,
+            )
+    }
+
+    @Test
+    fun notInteractableWhenExpansionIsBelow90Percent() = runTest {
+        val isInteractable = collectLastValue(underTest.isInteractable)
+
+        repository.setPrimaryShow(true)
+        repository.setPanelExpansion(0.15f)
+
+        assertThat(isInteractable()).isFalse()
+    }
+
+    @Test
+    fun notInteractableWhenExpansionAbove90PercentButNotVisible() = runTest {
+        val isInteractable = collectLastValue(underTest.isInteractable)
+
+        repository.setPrimaryShow(false)
+        repository.setPanelExpansion(0.05f)
+
+        assertThat(isInteractable()).isFalse()
+    }
+
+    @Test
+    fun isInteractableWhenExpansionAbove90PercentAndVisible() = runTest {
+        var isInteractable = collectLastValue(underTest.isInteractable)
+
+        repository.setPrimaryShow(true)
+        repository.setPanelExpansion(0.09f)
+
+        assertThat(isInteractable()).isTrue()
+
+        repository.setPanelExpansion(0.12f)
+        assertThat(isInteractable()).isFalse()
+
+        repository.setPanelExpansion(0f)
+        assertThat(isInteractable()).isTrue()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt
index 1642410..2cc9493 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt
@@ -18,13 +18,13 @@
 
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.scene.SceneTestUtils
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -35,14 +35,15 @@
 @RunWith(JUnit4::class)
 class AuthMethodBouncerViewModelTest : SysuiTestCase() {
 
-    private val testScope = TestScope()
-    private val utils = SceneTestUtils(this, testScope)
+    private val utils = SceneTestUtils(this)
+    private val testScope = utils.testScope
     private val authenticationInteractor =
         utils.authenticationInteractor(
             utils.authenticationRepository(),
         )
     private val underTest =
         PinBouncerViewModel(
+            applicationContext = context,
             applicationScope = testScope.backgroundScope,
             interactor =
                 utils.bouncerInteractor(
@@ -55,15 +56,14 @@
     @Test
     fun animateFailure() =
         testScope.runTest {
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
             val animateFailure by collectLastValue(underTest.animateFailure)
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
             assertThat(animateFailure).isFalse()
 
             // Wrong PIN:
-            underTest.onPinButtonClicked(3)
-            underTest.onPinButtonClicked(4)
-            underTest.onPinButtonClicked(5)
-            underTest.onPinButtonClicked(6)
+            FakeAuthenticationRepository.DEFAULT_PIN.drop(2).forEach { digit ->
+                underTest.onPinButtonClicked(digit)
+            }
             underTest.onAuthenticateButtonClicked()
             assertThat(animateFailure).isTrue()
 
@@ -71,10 +71,9 @@
             assertThat(animateFailure).isFalse()
 
             // Correct PIN:
-            underTest.onPinButtonClicked(1)
-            underTest.onPinButtonClicked(2)
-            underTest.onPinButtonClicked(3)
-            underTest.onPinButtonClicked(4)
+            FakeAuthenticationRepository.DEFAULT_PIN.forEach { digit ->
+                underTest.onPinButtonClicked(digit)
+            }
             underTest.onAuthenticateButtonClicked()
             assertThat(animateFailure).isFalse()
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
index e8c946c..0df0a17 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
@@ -18,16 +18,19 @@
 
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
-import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.scene.SceneTestUtils
 import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.emptyFlow
 import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.test.advanceTimeBy
+import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -38,8 +41,8 @@
 @RunWith(JUnit4::class)
 class BouncerViewModelTest : SysuiTestCase() {
 
-    private val testScope = TestScope()
-    private val utils = SceneTestUtils(this, testScope)
+    private val utils = SceneTestUtils(this)
+    private val testScope = utils.testScope
     private val authenticationInteractor =
         utils.authenticationInteractor(
             repository = utils.authenticationRepository(),
@@ -49,21 +52,34 @@
             authenticationInteractor = authenticationInteractor,
             sceneInteractor = utils.sceneInteractor(),
         )
-    private val underTest = utils.bouncerViewModel(bouncerInteractor)
+    private val underTest =
+        utils.bouncerViewModel(
+            bouncerInteractor = bouncerInteractor,
+        )
 
     @Test
     fun authMethod_nonNullForSecureMethods_nullForNotSecureMethods() =
         testScope.runTest {
-            val authMethodViewModel: AuthMethodBouncerViewModel? by
-                collectLastValue(underTest.authMethod)
+            var authMethodViewModel: AuthMethodBouncerViewModel? = null
+
             authMethodsToTest().forEach { authMethod ->
-                authenticationInteractor.setAuthenticationMethod(authMethod)
+                utils.authenticationRepository.setAuthenticationMethod(authMethod)
+                val job = underTest.authMethod.onEach { authMethodViewModel = it }.launchIn(this)
+                runCurrent()
 
                 if (authMethod.isSecure) {
-                    assertThat(authMethodViewModel).isNotNull()
+                    assertWithMessage("View-model unexpectedly null for auth method $authMethod")
+                        .that(authMethodViewModel)
+                        .isNotNull()
                 } else {
-                    assertThat(authMethodViewModel).isNull()
+                    assertWithMessage(
+                            "View-model unexpectedly non-null for auth method $authMethod"
+                        )
+                        .that(authMethodViewModel)
+                        .isNull()
                 }
+
+                job.cancel()
             }
         }
 
@@ -75,13 +91,13 @@
                 collectLastValue(underTest.authMethod)
             // First pass, populate our "seen" map:
             authMethodsToTest().forEach { authMethod ->
-                authenticationInteractor.setAuthenticationMethod(authMethod)
+                utils.authenticationRepository.setAuthenticationMethod(authMethod)
                 authMethodViewModel?.let { seen[authMethod] = it }
             }
 
             // Second pass, assert same instances are reused:
             authMethodsToTest().forEach { authMethod ->
-                authenticationInteractor.setAuthenticationMethod(authMethod)
+                utils.authenticationRepository.setAuthenticationMethod(authMethod)
                 authMethodViewModel?.let { assertThat(it).isSameInstanceAs(seen[authMethod]) }
             }
         }
@@ -97,16 +113,16 @@
         testScope.runTest {
             val message by collectLastValue(underTest.message)
             val throttling by collectLastValue(bouncerInteractor.throttling)
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
             assertThat(message?.isUpdateAnimated).isTrue()
 
-            repeat(BouncerInteractor.THROTTLE_EVERY) {
+            repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING) {
                 // Wrong PIN.
                 bouncerInteractor.authenticate(listOf(3, 4, 5, 6))
             }
             assertThat(message?.isUpdateAnimated).isFalse()
 
-            throttling?.totalDurationSec?.let { seconds -> advanceTimeBy(seconds * 1000L) }
+            throttling?.remainingMs?.let { remainingMs -> advanceTimeBy(remainingMs.toLong()) }
             assertThat(message?.isUpdateAnimated).isTrue()
         }
 
@@ -120,16 +136,16 @@
                     }
                 )
             val throttling by collectLastValue(bouncerInteractor.throttling)
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
             assertThat(isInputEnabled).isTrue()
 
-            repeat(BouncerInteractor.THROTTLE_EVERY) {
+            repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING) {
                 // Wrong PIN.
                 bouncerInteractor.authenticate(listOf(3, 4, 5, 6))
             }
             assertThat(isInputEnabled).isFalse()
 
-            throttling?.totalDurationSec?.let { seconds -> advanceTimeBy(seconds * 1000L) }
+            throttling?.remainingMs?.let { milliseconds -> advanceTimeBy(milliseconds.toLong()) }
             assertThat(isInputEnabled).isTrue()
         }
 
@@ -137,9 +153,9 @@
     fun throttlingDialogMessage() =
         testScope.runTest {
             val throttlingDialogMessage by collectLastValue(underTest.throttlingDialogMessage)
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
 
-            repeat(BouncerInteractor.THROTTLE_EVERY) {
+            repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING) {
                 // Wrong PIN.
                 assertThat(throttlingDialogMessage).isNull()
                 bouncerInteractor.authenticate(listOf(3, 4, 5, 6))
@@ -154,11 +170,9 @@
         return listOf(
             AuthenticationMethodModel.None,
             AuthenticationMethodModel.Swipe,
-            AuthenticationMethodModel.PIN(1234),
-            AuthenticationMethodModel.Password("password"),
-            AuthenticationMethodModel.Pattern(
-                listOf(AuthenticationMethodModel.Pattern.PatternCoordinate(1, 1))
-            ),
+            AuthenticationMethodModel.Pin,
+            AuthenticationMethodModel.Password,
+            AuthenticationMethodModel.Pattern,
         )
     }
 }
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
new file mode 100644
index 0000000..8236165
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModelTest.kt
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.bouncer.ui.viewmodel
+
+import android.os.Looper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardSecurityModel
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.RoboPilotTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
+import com.android.systemui.bouncer.shared.model.BouncerShowMessageModel
+import com.android.systemui.bouncer.ui.BouncerView
+import com.android.systemui.classifier.FalsingCollector
+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.TrustRepository
+import com.android.systemui.statusbar.phone.KeyguardBypassController
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.utils.os.FakeHandler
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
+@kotlinx.coroutines.ExperimentalCoroutinesApi
+class KeyguardBouncerViewModelTest : SysuiTestCase() {
+    lateinit var underTest: KeyguardBouncerViewModel
+    lateinit var bouncerInteractor: PrimaryBouncerInteractor
+    @Mock lateinit var bouncerView: BouncerView
+    @Mock private lateinit var keyguardStateController: KeyguardStateController
+    @Mock private lateinit var keyguardSecurityModel: KeyguardSecurityModel
+    @Mock private lateinit var primaryBouncerCallbackInteractor: PrimaryBouncerCallbackInteractor
+    @Mock private lateinit var falsingCollector: FalsingCollector
+    @Mock private lateinit var dismissCallbackRegistry: DismissCallbackRegistry
+    @Mock private lateinit var keyguardBypassController: KeyguardBypassController
+    @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+    private val mainHandler = FakeHandler(Looper.getMainLooper())
+    val repository = FakeKeyguardBouncerRepository()
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        bouncerInteractor =
+            PrimaryBouncerInteractor(
+                repository,
+                bouncerView,
+                mainHandler,
+                keyguardStateController,
+                keyguardSecurityModel,
+                primaryBouncerCallbackInteractor,
+                falsingCollector,
+                dismissCallbackRegistry,
+                context,
+                keyguardUpdateMonitor,
+                Mockito.mock(TrustRepository::class.java),
+                FakeFeatureFlags().apply { set(Flags.DELAY_BOUNCER, true) },
+                TestScope().backgroundScope,
+            )
+        underTest = KeyguardBouncerViewModel(bouncerView, bouncerInteractor)
+    }
+
+    @Test
+    fun setMessage() = runTest {
+        var message: BouncerShowMessageModel? = null
+        val job = underTest.bouncerShowMessage.onEach { message = it }.launchIn(this)
+
+        repository.setShowMessage(BouncerShowMessageModel("abc", null))
+        // Run the tasks that are pending at this point of virtual time.
+        runCurrent()
+        assertThat(message?.message).isEqualTo("abc")
+        job.cancel()
+    }
+
+    @Test
+    fun shouldUpdateSideFps_show() = runTest {
+        var count = 0
+        val job = underTest.shouldUpdateSideFps.onEach { count++ }.launchIn(this)
+        repository.setPrimaryShow(true)
+        // Run the tasks that are pending at this point of virtual time.
+        runCurrent()
+        assertThat(count).isEqualTo(1)
+        job.cancel()
+    }
+
+    @Test
+    fun shouldUpdateSideFps_hide() = runTest {
+        repository.setPrimaryShow(true)
+        var count = 0
+        val job = underTest.shouldUpdateSideFps.onEach { count++ }.launchIn(this)
+        repository.setPrimaryShow(false)
+        // Run the tasks that are pending at this point of virtual time.
+        runCurrent()
+        assertThat(count).isEqualTo(1)
+        job.cancel()
+    }
+
+    @Test
+    fun sideFpsShowing() = runTest {
+        var sideFpsIsShowing = false
+        val job = underTest.sideFpsShowing.onEach { sideFpsIsShowing = it }.launchIn(this)
+        repository.setSideFpsShowing(true)
+        // Run the tasks that are pending at this point of virtual time.
+        runCurrent()
+        assertThat(sideFpsIsShowing).isEqualTo(true)
+        job.cancel()
+    }
+
+    @Test
+    fun isShowing() = runTest {
+        var isShowing: Boolean? = null
+        val job = underTest.isShowing.onEach { isShowing = it }.launchIn(this)
+        repository.setPrimaryShow(true)
+        // Run the tasks that are pending at this point of virtual time.
+        runCurrent()
+        assertThat(isShowing).isEqualTo(true)
+        job.cancel()
+    }
+
+    @Test
+    fun isNotShowing() = runTest {
+        var isShowing: Boolean? = null
+        val job = underTest.isShowing.onEach { isShowing = it }.launchIn(this)
+        repository.setPrimaryShow(false)
+        // Run the tasks that are pending at this point of virtual time.
+        runCurrent()
+        assertThat(isShowing).isEqualTo(false)
+        job.cancel()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
index f436aa3..b1533fe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
@@ -28,7 +28,7 @@
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
@@ -40,8 +40,8 @@
 @RunWith(JUnit4::class)
 class PasswordBouncerViewModelTest : SysuiTestCase() {
 
-    private val testScope = TestScope()
-    private val utils = SceneTestUtils(this, testScope)
+    private val utils = SceneTestUtils(this)
+    private val testScope = utils.testScope
     private val authenticationInteractor =
         utils.authenticationInteractor(
             repository = utils.authenticationRepository(),
@@ -58,6 +58,7 @@
         )
     private val underTest =
         PasswordBouncerViewModel(
+            applicationScope = testScope.backgroundScope,
             interactor = bouncerInteractor,
             isInputEnabled = MutableStateFlow(true).asStateFlow(),
         )
@@ -71,84 +72,90 @@
     @Test
     fun onShown() =
         testScope.runTest {
-            val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
-            val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+            val currentScene by
+                collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
             val message by collectLastValue(bouncerViewModel.message)
             val password by collectLastValue(underTest.password)
-            authenticationInteractor.setAuthenticationMethod(
-                AuthenticationMethodModel.Password("password")
+            utils.authenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Password
             )
-            authenticationInteractor.lockDevice()
-            sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
-            assertThat(isUnlocked).isFalse()
+            utils.authenticationRepository.setUnlocked(false)
+            sceneInteractor.setCurrentScene(
+                SceneTestUtils.CONTAINER_1,
+                SceneModel(SceneKey.Bouncer)
+            )
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
 
             underTest.onShown()
 
             assertThat(message?.text).isEqualTo(ENTER_YOUR_PASSWORD)
             assertThat(password).isEqualTo("")
-            assertThat(isUnlocked).isFalse()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
         }
 
     @Test
     fun onPasswordInputChanged() =
         testScope.runTest {
-            val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
-            val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+            val currentScene by
+                collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
             val message by collectLastValue(bouncerViewModel.message)
             val password by collectLastValue(underTest.password)
-            authenticationInteractor.setAuthenticationMethod(
-                AuthenticationMethodModel.Password("password")
+            utils.authenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Password
             )
-            authenticationInteractor.lockDevice()
-            sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
-            assertThat(isUnlocked).isFalse()
+            utils.authenticationRepository.setUnlocked(false)
+            sceneInteractor.setCurrentScene(
+                SceneTestUtils.CONTAINER_1,
+                SceneModel(SceneKey.Bouncer)
+            )
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             underTest.onShown()
+            runCurrent()
 
             underTest.onPasswordInputChanged("password")
 
             assertThat(message?.text).isEmpty()
             assertThat(password).isEqualTo("password")
-            assertThat(isUnlocked).isFalse()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
         }
 
     @Test
     fun onAuthenticateKeyPressed_whenCorrect() =
         testScope.runTest {
-            val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
-            val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
-            authenticationInteractor.setAuthenticationMethod(
-                AuthenticationMethodModel.Password("password")
+            val currentScene by
+                collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
+            utils.authenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Password
             )
-            authenticationInteractor.lockDevice()
-            sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
-            assertThat(isUnlocked).isFalse()
+            utils.authenticationRepository.setUnlocked(false)
+            sceneInteractor.setCurrentScene(
+                SceneTestUtils.CONTAINER_1,
+                SceneModel(SceneKey.Bouncer)
+            )
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             underTest.onShown()
             underTest.onPasswordInputChanged("password")
 
             underTest.onAuthenticateKeyPressed()
 
-            assertThat(isUnlocked).isTrue()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
         }
 
     @Test
     fun onAuthenticateKeyPressed_whenWrong() =
         testScope.runTest {
-            val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
-            val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+            val currentScene by
+                collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
             val message by collectLastValue(bouncerViewModel.message)
             val password by collectLastValue(underTest.password)
-            authenticationInteractor.setAuthenticationMethod(
-                AuthenticationMethodModel.Password("password")
+            utils.authenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Password
             )
-            authenticationInteractor.lockDevice()
-            sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
-            assertThat(isUnlocked).isFalse()
+            utils.authenticationRepository.setUnlocked(false)
+            sceneInteractor.setCurrentScene(
+                SceneTestUtils.CONTAINER_1,
+                SceneModel(SceneKey.Bouncer)
+            )
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             underTest.onShown()
             underTest.onPasswordInputChanged("wrong")
@@ -157,30 +164,30 @@
 
             assertThat(password).isEqualTo("")
             assertThat(message?.text).isEqualTo(WRONG_PASSWORD)
-            assertThat(isUnlocked).isFalse()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
         }
 
     @Test
     fun onAuthenticateKeyPressed_correctAfterWrong() =
         testScope.runTest {
-            val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
-            val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+            val currentScene by
+                collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
             val message by collectLastValue(bouncerViewModel.message)
             val password by collectLastValue(underTest.password)
-            authenticationInteractor.setAuthenticationMethod(
-                AuthenticationMethodModel.Password("password")
+            utils.authenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Password
             )
-            authenticationInteractor.lockDevice()
-            sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
-            assertThat(isUnlocked).isFalse()
+            utils.authenticationRepository.setUnlocked(false)
+            sceneInteractor.setCurrentScene(
+                SceneTestUtils.CONTAINER_1,
+                SceneModel(SceneKey.Bouncer)
+            )
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             underTest.onShown()
             underTest.onPasswordInputChanged("wrong")
             underTest.onAuthenticateKeyPressed()
             assertThat(password).isEqualTo("")
             assertThat(message?.text).isEqualTo(WRONG_PASSWORD)
-            assertThat(isUnlocked).isFalse()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
 
             // Enter the correct password:
@@ -189,12 +196,10 @@
 
             underTest.onAuthenticateKeyPressed()
 
-            assertThat(isUnlocked).isTrue()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
         }
 
     companion object {
-        private const val CONTAINER_NAME = "container1"
         private const val ENTER_YOUR_PASSWORD = "Enter your password"
         private const val WRONG_PASSWORD = "Wrong password"
     }
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 d7d7154..f69cbb8 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
@@ -19,6 +19,7 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.scene.SceneTestUtils
@@ -29,7 +30,7 @@
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
@@ -41,8 +42,8 @@
 @RunWith(JUnit4::class)
 class PatternBouncerViewModelTest : SysuiTestCase() {
 
-    private val testScope = TestScope()
-    private val utils = SceneTestUtils(this, testScope)
+    private val utils = SceneTestUtils(this)
+    private val testScope = utils.testScope
     private val authenticationInteractor =
         utils.authenticationInteractor(
             repository = utils.authenticationRepository(),
@@ -74,17 +75,19 @@
     @Test
     fun onShown() =
         testScope.runTest {
-            val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
-            val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+            val currentScene by
+                collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
             val message by collectLastValue(bouncerViewModel.message)
             val selectedDots by collectLastValue(underTest.selectedDots)
             val currentDot by collectLastValue(underTest.currentDot)
-            authenticationInteractor.setAuthenticationMethod(
-                AuthenticationMethodModel.Pattern(CORRECT_PATTERN)
+            utils.authenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pattern
             )
-            authenticationInteractor.lockDevice()
-            sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
-            assertThat(isUnlocked).isFalse()
+            utils.authenticationRepository.setUnlocked(false)
+            sceneInteractor.setCurrentScene(
+                SceneTestUtils.CONTAINER_1,
+                SceneModel(SceneKey.Bouncer)
+            )
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
 
             underTest.onShown()
@@ -92,49 +95,52 @@
             assertThat(message?.text).isEqualTo(ENTER_YOUR_PATTERN)
             assertThat(selectedDots).isEmpty()
             assertThat(currentDot).isNull()
-            assertThat(isUnlocked).isFalse()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
         }
 
     @Test
     fun onDragStart() =
         testScope.runTest {
-            val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
-            val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+            val currentScene by
+                collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
             val message by collectLastValue(bouncerViewModel.message)
             val selectedDots by collectLastValue(underTest.selectedDots)
             val currentDot by collectLastValue(underTest.currentDot)
-            authenticationInteractor.setAuthenticationMethod(
-                AuthenticationMethodModel.Pattern(CORRECT_PATTERN)
+            utils.authenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pattern
             )
-            authenticationInteractor.lockDevice()
-            sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
-            assertThat(isUnlocked).isFalse()
+            utils.authenticationRepository.setUnlocked(false)
+            sceneInteractor.setCurrentScene(
+                SceneTestUtils.CONTAINER_1,
+                SceneModel(SceneKey.Bouncer)
+            )
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             underTest.onShown()
+            runCurrent()
 
             underTest.onDragStart()
 
             assertThat(message?.text).isEmpty()
             assertThat(selectedDots).isEmpty()
             assertThat(currentDot).isNull()
-            assertThat(isUnlocked).isFalse()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
         }
 
     @Test
     fun onDragEnd_whenCorrect() =
         testScope.runTest {
-            val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
-            val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+            val currentScene by
+                collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
             val selectedDots by collectLastValue(underTest.selectedDots)
             val currentDot by collectLastValue(underTest.currentDot)
-            authenticationInteractor.setAuthenticationMethod(
-                AuthenticationMethodModel.Pattern(CORRECT_PATTERN)
+            utils.authenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pattern
             )
-            authenticationInteractor.lockDevice()
-            sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
-            assertThat(isUnlocked).isFalse()
+            utils.authenticationRepository.setUnlocked(false)
+            sceneInteractor.setCurrentScene(
+                SceneTestUtils.CONTAINER_1,
+                SceneModel(SceneKey.Bouncer)
+            )
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             underTest.onShown()
             underTest.onDragStart()
@@ -168,24 +174,25 @@
 
             underTest.onDragEnd()
 
-            assertThat(isUnlocked).isTrue()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
         }
 
     @Test
     fun onDragEnd_whenWrong() =
         testScope.runTest {
-            val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
-            val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+            val currentScene by
+                collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
             val message by collectLastValue(bouncerViewModel.message)
             val selectedDots by collectLastValue(underTest.selectedDots)
             val currentDot by collectLastValue(underTest.currentDot)
-            authenticationInteractor.setAuthenticationMethod(
-                AuthenticationMethodModel.Pattern(CORRECT_PATTERN)
+            utils.authenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pattern
             )
-            authenticationInteractor.lockDevice()
-            sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
-            assertThat(isUnlocked).isFalse()
+            utils.authenticationRepository.setUnlocked(false)
+            sceneInteractor.setCurrentScene(
+                SceneTestUtils.CONTAINER_1,
+                SceneModel(SceneKey.Bouncer)
+            )
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             underTest.onShown()
             underTest.onDragStart()
@@ -203,24 +210,25 @@
             assertThat(selectedDots).isEmpty()
             assertThat(currentDot).isNull()
             assertThat(message?.text).isEqualTo(WRONG_PATTERN)
-            assertThat(isUnlocked).isFalse()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
         }
 
     @Test
     fun onDragEnd_correctAfterWrong() =
         testScope.runTest {
-            val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
-            val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+            val currentScene by
+                collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
             val message by collectLastValue(bouncerViewModel.message)
             val selectedDots by collectLastValue(underTest.selectedDots)
             val currentDot by collectLastValue(underTest.currentDot)
-            authenticationInteractor.setAuthenticationMethod(
-                AuthenticationMethodModel.Pattern(CORRECT_PATTERN)
+            utils.authenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pattern
             )
-            authenticationInteractor.lockDevice()
-            sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
-            assertThat(isUnlocked).isFalse()
+            utils.authenticationRepository.setUnlocked(false)
+            sceneInteractor.setCurrentScene(
+                SceneTestUtils.CONTAINER_1,
+                SceneModel(SceneKey.Bouncer)
+            )
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             underTest.onShown()
             underTest.onDragStart()
@@ -236,7 +244,6 @@
             assertThat(selectedDots).isEmpty()
             assertThat(currentDot).isNull()
             assertThat(message?.text).isEqualTo(WRONG_PATTERN)
-            assertThat(isUnlocked).isFalse()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
 
             // Enter the correct pattern:
@@ -251,25 +258,12 @@
 
             underTest.onDragEnd()
 
-            assertThat(isUnlocked).isTrue()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
         }
 
     companion object {
-        private const val CONTAINER_NAME = "container1"
         private const val ENTER_YOUR_PATTERN = "Enter your pattern"
         private const val WRONG_PATTERN = "Wrong pattern"
-        private val CORRECT_PATTERN =
-            listOf(
-                AuthenticationMethodModel.Pattern.PatternCoordinate(x = 1, y = 1),
-                AuthenticationMethodModel.Pattern.PatternCoordinate(x = 0, y = 1),
-                AuthenticationMethodModel.Pattern.PatternCoordinate(x = 0, y = 0),
-                AuthenticationMethodModel.Pattern.PatternCoordinate(x = 1, y = 0),
-                AuthenticationMethodModel.Pattern.PatternCoordinate(x = 2, y = 0),
-                AuthenticationMethodModel.Pattern.PatternCoordinate(x = 2, y = 1),
-                AuthenticationMethodModel.Pattern.PatternCoordinate(x = 2, y = 2),
-                AuthenticationMethodModel.Pattern.PatternCoordinate(x = 1, y = 2),
-                AuthenticationMethodModel.Pattern.PatternCoordinate(x = 0, y = 2),
-            )
+        private val CORRECT_PATTERN = FakeAuthenticationRepository.PATTERN
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
index 3bdaf05..45d1af7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
@@ -19,8 +19,8 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
-import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.scene.SceneTestUtils
 import com.android.systemui.scene.shared.model.SceneKey
@@ -29,8 +29,7 @@
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.asStateFlow
-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
@@ -42,8 +41,8 @@
 @RunWith(JUnit4::class)
 class PinBouncerViewModelTest : SysuiTestCase() {
 
-    private val testScope = TestScope()
-    private val utils = SceneTestUtils(this, testScope)
+    private val utils = SceneTestUtils(this)
+    private val testScope = utils.testScope
     private val sceneInteractor = utils.sceneInteractor()
     private val authenticationInteractor =
         utils.authenticationInteractor(
@@ -55,19 +54,12 @@
             sceneInteractor = sceneInteractor,
         )
     private val bouncerViewModel =
-        BouncerViewModel(
-            applicationContext = context,
-            applicationScope = testScope.backgroundScope,
-            interactorFactory =
-                object : BouncerInteractor.Factory {
-                    override fun create(containerName: String): BouncerInteractor {
-                        return bouncerInteractor
-                    }
-                },
-            containerName = CONTAINER_NAME,
+        utils.bouncerViewModel(
+            bouncerInteractor = bouncerInteractor,
         )
     private val underTest =
         PinBouncerViewModel(
+            applicationContext = context,
             applicationScope = testScope.backgroundScope,
             interactor = bouncerInteractor,
             isInputEnabled = MutableStateFlow(true).asStateFlow(),
@@ -82,133 +74,166 @@
     @Test
     fun onShown() =
         testScope.runTest {
-            val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
-            val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+            val currentScene by
+                collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
             val message by collectLastValue(bouncerViewModel.message)
-            val pinLengths by collectLastValue(underTest.pinLengths)
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
-            authenticationInteractor.lockDevice()
-            sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
-            assertThat(isUnlocked).isFalse()
+            val entries by collectLastValue(underTest.pinEntries)
+            utils.authenticationRepository.setUnlocked(false)
+            sceneInteractor.setCurrentScene(
+                SceneTestUtils.CONTAINER_1,
+                SceneModel(SceneKey.Bouncer)
+            )
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
 
             underTest.onShown()
 
             assertThat(message?.text).isEqualTo(ENTER_YOUR_PIN)
-            assertThat(pinLengths).isEqualTo(0 to 0)
-            assertThat(isUnlocked).isFalse()
+            assertThat(entries).hasSize(0)
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
         }
 
     @Test
     fun onPinButtonClicked() =
         testScope.runTest {
-            val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
-            val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+            val currentScene by
+                collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
             val message by collectLastValue(bouncerViewModel.message)
-            val pinLengths by collectLastValue(underTest.pinLengths)
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
-            authenticationInteractor.lockDevice()
-            sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
-            assertThat(isUnlocked).isFalse()
+            val entries by collectLastValue(underTest.pinEntries)
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            utils.authenticationRepository.setUnlocked(false)
+            sceneInteractor.setCurrentScene(
+                SceneTestUtils.CONTAINER_1,
+                SceneModel(SceneKey.Bouncer)
+            )
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             underTest.onShown()
+            runCurrent()
 
             underTest.onPinButtonClicked(1)
 
             assertThat(message?.text).isEmpty()
-            assertThat(pinLengths).isEqualTo(0 to 1)
-            assertThat(isUnlocked).isFalse()
+            assertThat(entries).hasSize(1)
+            assertThat(entries?.map { it.input }).containsExactly(1)
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
         }
 
     @Test
     fun onBackspaceButtonClicked() =
         testScope.runTest {
-            val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
-            val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+            val currentScene by
+                collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
             val message by collectLastValue(bouncerViewModel.message)
-            val pinLengths by collectLastValue(underTest.pinLengths)
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
-            authenticationInteractor.lockDevice()
-            sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
-            assertThat(isUnlocked).isFalse()
+            val entries by collectLastValue(underTest.pinEntries)
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            utils.authenticationRepository.setUnlocked(false)
+            sceneInteractor.setCurrentScene(
+                SceneTestUtils.CONTAINER_1,
+                SceneModel(SceneKey.Bouncer)
+            )
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             underTest.onShown()
+            runCurrent()
             underTest.onPinButtonClicked(1)
-            assertThat(pinLengths).isEqualTo(0 to 1)
+            assertThat(entries).hasSize(1)
 
             underTest.onBackspaceButtonClicked()
 
             assertThat(message?.text).isEmpty()
-            assertThat(pinLengths).isEqualTo(1 to 0)
-            assertThat(isUnlocked).isFalse()
+            assertThat(entries).hasSize(0)
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
         }
 
     @Test
+    fun onPinEdit() =
+        testScope.runTest {
+            val currentScene by
+                collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
+            val entries by collectLastValue(underTest.pinEntries)
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            utils.authenticationRepository.setUnlocked(false)
+            sceneInteractor.setCurrentScene(
+                SceneTestUtils.CONTAINER_1,
+                SceneModel(SceneKey.Bouncer)
+            )
+            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
+            underTest.onShown()
+
+            underTest.onPinButtonClicked(1)
+            underTest.onPinButtonClicked(2)
+            underTest.onPinButtonClicked(3)
+            underTest.onBackspaceButtonClicked()
+            underTest.onBackspaceButtonClicked()
+            underTest.onPinButtonClicked(4)
+            underTest.onPinButtonClicked(5)
+
+            assertThat(entries).hasSize(3)
+            assertThat(entries?.map { it.input }).containsExactly(1, 4, 5).inOrder()
+            assertThat(entries?.map { it.sequenceNumber }).isInStrictOrder()
+        }
+
+    @Test
     fun onBackspaceButtonLongPressed() =
         testScope.runTest {
-            val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
-            val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+            val currentScene by
+                collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
             val message by collectLastValue(bouncerViewModel.message)
-            val pinLengths by collectLastValue(underTest.pinLengths)
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
-            authenticationInteractor.lockDevice()
-            sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
-            assertThat(isUnlocked).isFalse()
+            val entries by collectLastValue(underTest.pinEntries)
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            utils.authenticationRepository.setUnlocked(false)
+            sceneInteractor.setCurrentScene(
+                SceneTestUtils.CONTAINER_1,
+                SceneModel(SceneKey.Bouncer)
+            )
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             underTest.onShown()
+            runCurrent()
             underTest.onPinButtonClicked(1)
             underTest.onPinButtonClicked(2)
             underTest.onPinButtonClicked(3)
             underTest.onPinButtonClicked(4)
 
             underTest.onBackspaceButtonLongPressed()
-            repeat(4) { index ->
-                assertThat(pinLengths).isEqualTo(4 - index to 3 - index)
-                advanceTimeBy(PinBouncerViewModel.BACKSPACE_LONG_PRESS_DELAY_MS)
-            }
 
             assertThat(message?.text).isEmpty()
-            assertThat(pinLengths).isEqualTo(1 to 0)
-            assertThat(isUnlocked).isFalse()
+            assertThat(entries).hasSize(0)
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
         }
 
     @Test
     fun onAuthenticateButtonClicked_whenCorrect() =
         testScope.runTest {
-            val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
-            val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
-            authenticationInteractor.lockDevice()
-            sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
-            assertThat(isUnlocked).isFalse()
+            val currentScene by
+                collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            utils.authenticationRepository.setUnlocked(false)
+            sceneInteractor.setCurrentScene(
+                SceneTestUtils.CONTAINER_1,
+                SceneModel(SceneKey.Bouncer)
+            )
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             underTest.onShown()
-            underTest.onPinButtonClicked(1)
-            underTest.onPinButtonClicked(2)
-            underTest.onPinButtonClicked(3)
-            underTest.onPinButtonClicked(4)
+            FakeAuthenticationRepository.DEFAULT_PIN.forEach { digit ->
+                underTest.onPinButtonClicked(digit)
+            }
 
             underTest.onAuthenticateButtonClicked()
 
-            assertThat(isUnlocked).isTrue()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
         }
 
     @Test
     fun onAuthenticateButtonClicked_whenWrong() =
         testScope.runTest {
-            val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
-            val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+            val currentScene by
+                collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
             val message by collectLastValue(bouncerViewModel.message)
-            val pinLengths by collectLastValue(underTest.pinLengths)
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
-            authenticationInteractor.lockDevice()
-            sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
-            assertThat(isUnlocked).isFalse()
+            val entries by collectLastValue(underTest.pinEntries)
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            utils.authenticationRepository.setUnlocked(false)
+            sceneInteractor.setCurrentScene(
+                SceneTestUtils.CONTAINER_1,
+                SceneModel(SceneKey.Bouncer)
+            )
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             underTest.onShown()
             underTest.onPinButtonClicked(1)
@@ -219,23 +244,24 @@
 
             underTest.onAuthenticateButtonClicked()
 
-            assertThat(pinLengths).isEqualTo(0 to 0)
+            assertThat(entries).hasSize(0)
             assertThat(message?.text).isEqualTo(WRONG_PIN)
-            assertThat(isUnlocked).isFalse()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
         }
 
     @Test
     fun onAuthenticateButtonClicked_correctAfterWrong() =
         testScope.runTest {
-            val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
-            val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+            val currentScene by
+                collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
             val message by collectLastValue(bouncerViewModel.message)
-            val pinLengths by collectLastValue(underTest.pinLengths)
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
-            authenticationInteractor.lockDevice()
-            sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
-            assertThat(isUnlocked).isFalse()
+            val entries by collectLastValue(underTest.pinEntries)
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            utils.authenticationRepository.setUnlocked(false)
+            sceneInteractor.setCurrentScene(
+                SceneTestUtils.CONTAINER_1,
+                SceneModel(SceneKey.Bouncer)
+            )
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             underTest.onShown()
             underTest.onPinButtonClicked(1)
@@ -245,25 +271,122 @@
             underTest.onPinButtonClicked(5) // PIN is now wrong!
             underTest.onAuthenticateButtonClicked()
             assertThat(message?.text).isEqualTo(WRONG_PIN)
-            assertThat(pinLengths).isEqualTo(0 to 0)
-            assertThat(isUnlocked).isFalse()
+            assertThat(entries).hasSize(0)
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
 
             // Enter the correct PIN:
-            underTest.onPinButtonClicked(1)
-            underTest.onPinButtonClicked(2)
-            underTest.onPinButtonClicked(3)
-            underTest.onPinButtonClicked(4)
+            FakeAuthenticationRepository.DEFAULT_PIN.forEach { digit ->
+                underTest.onPinButtonClicked(digit)
+            }
             assertThat(message?.text).isEmpty()
 
             underTest.onAuthenticateButtonClicked()
 
-            assertThat(isUnlocked).isTrue()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
         }
 
+    @Test
+    fun onAutoConfirm_whenCorrect() =
+        testScope.runTest {
+            val currentScene by
+                collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            utils.authenticationRepository.setUnlocked(false)
+            utils.authenticationRepository.setAutoConfirmEnabled(true)
+            sceneInteractor.setCurrentScene(
+                SceneTestUtils.CONTAINER_1,
+                SceneModel(SceneKey.Bouncer)
+            )
+            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
+            underTest.onShown()
+            FakeAuthenticationRepository.DEFAULT_PIN.forEach { digit ->
+                underTest.onPinButtonClicked(digit)
+            }
+
+            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
+        }
+
+    @Test
+    fun onAutoConfirm_whenWrong() =
+        testScope.runTest {
+            val currentScene by
+                collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
+            val message by collectLastValue(bouncerViewModel.message)
+            val entries by collectLastValue(underTest.pinEntries)
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            utils.authenticationRepository.setUnlocked(false)
+            utils.authenticationRepository.setAutoConfirmEnabled(true)
+            sceneInteractor.setCurrentScene(
+                SceneTestUtils.CONTAINER_1,
+                SceneModel(SceneKey.Bouncer)
+            )
+            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
+            underTest.onShown()
+            FakeAuthenticationRepository.DEFAULT_PIN.dropLast(1).forEach { digit ->
+                underTest.onPinButtonClicked(digit)
+            }
+            underTest.onPinButtonClicked(
+                FakeAuthenticationRepository.DEFAULT_PIN.last() + 1
+            ) // PIN is now wrong!
+
+            assertThat(entries).hasSize(0)
+            assertThat(message?.text).isEqualTo(WRONG_PIN)
+            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
+        }
+
+    @Test
+    fun backspaceButtonAppearance_withoutAutoConfirm_alwaysShown() =
+        testScope.runTest {
+            val backspaceButtonAppearance by collectLastValue(underTest.backspaceButtonAppearance)
+
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+
+            assertThat(backspaceButtonAppearance).isEqualTo(ActionButtonAppearance.Shown)
+        }
+
+    @Test
+    fun backspaceButtonAppearance_withAutoConfirmButNoInput_isHidden() =
+        testScope.runTest {
+            val backspaceButtonAppearance by collectLastValue(underTest.backspaceButtonAppearance)
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            utils.authenticationRepository.setAutoConfirmEnabled(true)
+
+            assertThat(backspaceButtonAppearance).isEqualTo(ActionButtonAppearance.Hidden)
+        }
+
+    @Test
+    fun backspaceButtonAppearance_withAutoConfirmAndInput_isShownQuiet() =
+        testScope.runTest {
+            val backspaceButtonAppearance by collectLastValue(underTest.backspaceButtonAppearance)
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            utils.authenticationRepository.setAutoConfirmEnabled(true)
+
+            underTest.onPinButtonClicked(1)
+
+            assertThat(backspaceButtonAppearance).isEqualTo(ActionButtonAppearance.Subtle)
+        }
+
+    @Test
+    fun confirmButtonAppearance_withoutAutoConfirm_alwaysShown() =
+        testScope.runTest {
+            val confirmButtonAppearance by collectLastValue(underTest.confirmButtonAppearance)
+
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+
+            assertThat(confirmButtonAppearance).isEqualTo(ActionButtonAppearance.Shown)
+        }
+
+    @Test
+    fun confirmButtonAppearance_withAutoConfirm_isHidden() =
+        testScope.runTest {
+            val confirmButtonAppearance by collectLastValue(underTest.confirmButtonAppearance)
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            utils.authenticationRepository.setAutoConfirmEnabled(true)
+
+            assertThat(confirmButtonAppearance).isEqualTo(ActionButtonAppearance.Hidden)
+        }
+
     companion object {
-        private const val CONTAINER_NAME = "container1"
         private const val ENTER_YOUR_PIN = "Enter your pin"
         private const val WRONG_PIN = "Wrong pin"
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java
index 292fdff..f770a38 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java
@@ -187,4 +187,11 @@
         when(mFalsingDataProvider.isUnfolded()).thenReturn(true);
         assertThat(mBrightLineFalsingManager.isFalseTouch(Classifier.GENERIC)).isFalse();
     }
+
+    @Test
+    public void testTrackpadGesture() {
+        assertThat(mBrightLineFalsingManager.isFalseTouch(Classifier.GENERIC)).isTrue();
+        when(mFalsingDataProvider.isFromTrackpad()).thenReturn(true);
+        assertThat(mBrightLineFalsingManager.isFalseTouch(Classifier.GENERIC)).isFalse();
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/ClassifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/ClassifierTest.java
index 9254617..4da151c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/ClassifierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/ClassifierTest.java
@@ -18,8 +18,11 @@
 
 import android.hardware.devicestate.DeviceStateManager.FoldStateListener;
 import android.util.DisplayMetrics;
+import android.view.InputDevice;
 import android.view.MotionEvent;
 
+import androidx.test.uiautomator.Configurator;
+
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.dock.DockManagerFake;
 import com.android.systemui.statusbar.policy.BatteryController;
@@ -80,6 +83,10 @@
         mDataProvider.onSessionEnd();
     }
 
+    protected static int getPointerAction(int actionType, int index) {
+        return actionType + (index << MotionEvent.ACTION_POINTER_INDEX_SHIFT);
+    }
+
     protected MotionEvent appendDownEvent(float x, float y) {
         return appendMotionEvent(MotionEvent.ACTION_DOWN, x, y);
     }
@@ -124,4 +131,55 @@
 
         return motionEvent;
     }
+
+    protected MotionEvent appendTrackpadDownEvent(float x, float y) {
+        return appendTrackpadMotionEvent(MotionEvent.ACTION_DOWN, x, y, 1);
+    }
+
+    protected MotionEvent appendTrackpadMoveEvent(float x, float y, int pointerCount) {
+        return appendTrackpadMotionEvent(MotionEvent.ACTION_MOVE, x, y, pointerCount);
+    }
+
+    protected MotionEvent appendTrackpadPointerDownEvent(int actionType, float x, float y,
+            int pointerCount) {
+        return appendTrackpadMotionEvent(actionType, x, y, pointerCount);
+    }
+
+    private MotionEvent appendTrackpadMotionEvent(int actionType, float x, float y,
+            int pointerCount) {
+        long eventTime = mMotionEvents.isEmpty() ? 1 : mMotionEvents.get(
+                mMotionEvents.size() - 1).getEventTime() + 1;
+        return appendTrackpadMotionEvent(actionType, x, y, pointerCount, eventTime);
+    }
+
+    private MotionEvent appendTrackpadMotionEvent(int actionType, float x, float y,
+            int pointerCount, long eventTime) {
+        MotionEvent.PointerProperties[] pointerProperties =
+                new MotionEvent.PointerProperties[pointerCount];
+        MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[pointerCount];
+        for (int i = 0; i < pointerCount; i++) {
+            pointerProperties[i] = getPointerProperties(i);
+            pointerCoords[i] = getPointerCoords(x, y);
+        }
+        return MotionEvent.obtain(1, eventTime, actionType, pointerCount, pointerProperties,
+                pointerCoords, 0, 0, 1.0f, 1.0f, 0, 0,
+                InputDevice.SOURCE_TOUCHPAD | InputDevice.SOURCE_MOUSE, 0, 0,
+                MotionEvent.CLASSIFICATION_MULTI_FINGER_SWIPE);
+    }
+
+    private static MotionEvent.PointerProperties getPointerProperties(int pointerId) {
+        MotionEvent.PointerProperties properties = new MotionEvent.PointerProperties();
+        properties.id = pointerId;
+        properties.toolType = Configurator.getInstance().getToolType();
+        return properties;
+    }
+
+    private static MotionEvent.PointerCoords getPointerCoords(float x, float y) {
+        MotionEvent.PointerCoords coords = new MotionEvent.PointerCoords();
+        coords.pressure = 1;
+        coords.size = 1;
+        coords.x = x;
+        coords.y = y;
+        return coords;
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingDataProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingDataProviderTest.java
index 7e06680..0353f87 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingDataProviderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingDataProviderTest.java
@@ -282,6 +282,22 @@
     }
 
     @Test
+    public void test_IsFromTrackpad() {
+        MotionEvent motionEventOrigin = appendTrackpadDownEvent(0, 0);
+
+        mDataProvider.onMotionEvent(motionEventOrigin);
+        mDataProvider.onMotionEvent(
+                appendTrackpadPointerDownEvent(getPointerAction(MotionEvent.ACTION_POINTER_DOWN, 1),
+                        0, 0, 2));
+        mDataProvider.onMotionEvent(
+                appendTrackpadPointerDownEvent(getPointerAction(MotionEvent.ACTION_POINTER_DOWN, 2),
+                        0, 0, 3));
+        mDataProvider.onMotionEvent(appendTrackpadMoveEvent(1, -1, 3));
+        assertThat(mDataProvider.isFromTrackpad()).isTrue();
+        mDataProvider.onSessionEnd();
+    }
+
+    @Test
     public void test_isWirelessCharging() {
         assertThat(mDataProvider.isDocked()).isFalse();
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
index 39fb7b4..9671966 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
@@ -26,6 +26,7 @@
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SHOWN_MINIMIZED;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SWIPE_DISMISSED;
 import static com.android.systemui.flags.Flags.CLIPBOARD_IMAGE_TIMEOUT;
+import static com.android.systemui.flags.Flags.CLIPBOARD_SHARED_TRANSITIONS;
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -93,12 +94,16 @@
     @Mock
     private ClipboardImageLoader mClipboardImageLoader;
     @Mock
+    private ClipboardTransitionExecutor mClipboardTransitionExecutor;
+    @Mock
     private UiEventLogger mUiEventLogger;
     private FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext);
     private FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
 
     @Mock
     private Animator mAnimator;
+    private ArgumentCaptor<Animator.AnimatorListener> mAnimatorListenerCaptor =
+            ArgumentCaptor.forClass(Animator.AnimatorListener.class);
 
     private ClipData mSampleClipData;
 
@@ -117,6 +122,7 @@
 
         when(mClipboardOverlayView.getEnterAnimation()).thenReturn(mAnimator);
         when(mClipboardOverlayView.getExitAnimation()).thenReturn(mAnimator);
+        when(mClipboardOverlayView.getFadeOutAnimation()).thenReturn(mAnimator);
         when(mClipboardOverlayWindow.getWindowInsets()).thenReturn(
                 getImeInsets(new Rect(0, 0, 0, 0)));
 
@@ -124,7 +130,16 @@
                 new ClipData.Item("Test Item"));
 
         mFeatureFlags.set(CLIPBOARD_IMAGE_TIMEOUT, true); // turned off for legacy tests
+        mFeatureFlags.set(CLIPBOARD_SHARED_TRANSITIONS, true); // turned off for old tests
+    }
 
+    /**
+     * Needs to be done after setting flags for legacy tests, since the value of
+     * CLIPBOARD_SHARED_TRANSITIONS is checked during construction. This can be moved back into
+     * the setup method once CLIPBOARD_SHARED_TRANSITIONS is fully released and the tests where it
+     * is false are removed.[
+     */
+    private void initController() {
         mOverlayController = new ClipboardOverlayController(
                 mContext,
                 mClipboardOverlayView,
@@ -136,6 +151,7 @@
                 mClipboardUtils,
                 mExecutor,
                 mClipboardImageLoader,
+                mClipboardTransitionExecutor,
                 mUiEventLogger);
         verify(mClipboardOverlayView).setCallbacks(mOverlayCallbacksCaptor.capture());
         mCallbacks = mOverlayCallbacksCaptor.getValue();
@@ -148,6 +164,8 @@
 
     @Test
     public void test_setClipData_invalidImageData_legacy() {
+        initController();
+
         ClipData clipData = new ClipData("", new String[]{"image/png"},
                 new ClipData.Item(Uri.parse("")));
         mFeatureFlags.set(CLIPBOARD_IMAGE_TIMEOUT, false);
@@ -161,6 +179,8 @@
 
     @Test
     public void test_setClipData_nonImageUri_legacy() {
+        initController();
+
         ClipData clipData = new ClipData("", new String[]{"resource/png"},
                 new ClipData.Item(Uri.parse("")));
         mFeatureFlags.set(CLIPBOARD_IMAGE_TIMEOUT, false);
@@ -175,6 +195,8 @@
     @Test
     public void test_setClipData_textData_legacy() {
         mFeatureFlags.set(CLIPBOARD_IMAGE_TIMEOUT, false);
+        initController();
+
         mOverlayController.setClipData(mSampleClipData, "abc");
 
         verify(mClipboardOverlayView, times(1)).showTextPreview("Test Item", false);
@@ -186,6 +208,8 @@
     @Test
     public void test_setClipData_sensitiveTextData_legacy() {
         mFeatureFlags.set(CLIPBOARD_IMAGE_TIMEOUT, false);
+        initController();
+
         ClipDescription description = mSampleClipData.getDescription();
         PersistableBundle b = new PersistableBundle();
         b.putBoolean(ClipDescription.EXTRA_IS_SENSITIVE, true);
@@ -202,6 +226,7 @@
     public void test_setClipData_repeatedCalls_legacy() {
         when(mAnimator.isRunning()).thenReturn(true);
         mFeatureFlags.set(CLIPBOARD_IMAGE_TIMEOUT, false);
+        initController();
 
         mOverlayController.setClipData(mSampleClipData, "");
         mOverlayController.setClipData(mSampleClipData, "");
@@ -211,6 +236,8 @@
 
     @Test
     public void test_setClipData_invalidImageData() {
+        initController();
+
         ClipData clipData = new ClipData("", new String[]{"image/png"},
                 new ClipData.Item(Uri.parse("")));
 
@@ -223,6 +250,7 @@
 
     @Test
     public void test_setClipData_nonImageUri() {
+        initController();
         ClipData clipData = new ClipData("", new String[]{"resource/png"},
                 new ClipData.Item(Uri.parse("")));
 
@@ -235,6 +263,7 @@
 
     @Test
     public void test_setClipData_textData() {
+        initController();
         mOverlayController.setClipData(mSampleClipData, "abc");
 
         verify(mClipboardOverlayView, times(1)).showTextPreview("Test Item", false);
@@ -245,6 +274,7 @@
 
     @Test
     public void test_setClipData_sensitiveTextData() {
+        initController();
         ClipDescription description = mSampleClipData.getDescription();
         PersistableBundle b = new PersistableBundle();
         b.putBoolean(ClipDescription.EXTRA_IS_SENSITIVE, true);
@@ -259,6 +289,7 @@
 
     @Test
     public void test_setClipData_repeatedCalls() {
+        initController();
         when(mAnimator.isRunning()).thenReturn(true);
 
         mOverlayController.setClipData(mSampleClipData, "");
@@ -268,7 +299,9 @@
     }
 
     @Test
-    public void test_viewCallbacks_onShareTapped() {
+    public void test_viewCallbacks_onShareTapped_sharedTransitionsOff() {
+        mFeatureFlags.set(CLIPBOARD_SHARED_TRANSITIONS, false);
+        initController();
         mOverlayController.setClipData(mSampleClipData, "");
 
         mCallbacks.onShareButtonTapped();
@@ -278,7 +311,22 @@
     }
 
     @Test
-    public void test_viewCallbacks_onDismissTapped() {
+    public void test_viewCallbacks_onShareTapped() {
+        initController();
+        mOverlayController.setClipData(mSampleClipData, "");
+
+        mCallbacks.onShareButtonTapped();
+        verify(mAnimator).addListener(mAnimatorListenerCaptor.capture());
+        mAnimatorListenerCaptor.getValue().onAnimationEnd(mAnimator);
+
+        verify(mUiEventLogger, times(1)).log(CLIPBOARD_OVERLAY_SHARE_TAPPED, 0, "");
+        verify(mClipboardOverlayView, times(1)).getFadeOutAnimation();
+    }
+
+    @Test
+    public void test_viewCallbacks_onDismissTapped_sharedTransitionsOff() {
+        mFeatureFlags.set(CLIPBOARD_SHARED_TRANSITIONS, false);
+        initController();
         mOverlayController.setClipData(mSampleClipData, "");
 
         mCallbacks.onDismissButtonTapped();
@@ -288,7 +336,35 @@
     }
 
     @Test
+    public void test_viewCallbacks_onDismissTapped() {
+        initController();
+
+        mCallbacks.onDismissButtonTapped();
+        verify(mAnimator).addListener(mAnimatorListenerCaptor.capture());
+        mAnimatorListenerCaptor.getValue().onAnimationEnd(mAnimator);
+
+        // package name is null since we haven't actually set a source for this test
+        verify(mUiEventLogger, times(1)).log(CLIPBOARD_OVERLAY_DISMISS_TAPPED, 0, null);
+        verify(mClipboardOverlayView, times(1)).getExitAnimation();
+    }
+
+    @Test
+    public void test_multipleDismissals_dismissesOnce_sharedTransitionsOff() {
+        mFeatureFlags.set(CLIPBOARD_SHARED_TRANSITIONS, false);
+        initController();
+        mCallbacks.onSwipeDismissInitiated(mAnimator);
+        mCallbacks.onDismissButtonTapped();
+        mCallbacks.onSwipeDismissInitiated(mAnimator);
+        mCallbacks.onDismissButtonTapped();
+
+        verify(mUiEventLogger, times(1)).log(CLIPBOARD_OVERLAY_SWIPE_DISMISSED, 0, null);
+        verify(mUiEventLogger, never()).log(CLIPBOARD_OVERLAY_DISMISS_TAPPED);
+    }
+
+    @Test
     public void test_multipleDismissals_dismissesOnce() {
+        initController();
+
         mCallbacks.onSwipeDismissInitiated(mAnimator);
         mCallbacks.onDismissButtonTapped();
         mCallbacks.onSwipeDismissInitiated(mAnimator);
@@ -300,6 +376,7 @@
 
     @Test
     public void test_remoteCopy_withFlagOn() {
+        initController();
         when(mClipboardUtils.isRemoteCopy(any(), any(), any())).thenReturn(true);
 
         mOverlayController.setClipData(mSampleClipData, "");
@@ -309,6 +386,7 @@
 
     @Test
     public void test_nonRemoteCopy() {
+        initController();
         when(mClipboardUtils.isRemoteCopy(any(), any(), any())).thenReturn(false);
 
         mOverlayController.setClipData(mSampleClipData, "");
@@ -318,13 +396,16 @@
 
     @Test
     public void test_logsUseLastClipSource() {
-        mOverlayController.setClipData(mSampleClipData, "first.package");
-        mCallbacks.onDismissButtonTapped();
-        mOverlayController.setClipData(mSampleClipData, "second.package");
-        mCallbacks.onDismissButtonTapped();
+        initController();
 
-        verify(mUiEventLogger).log(CLIPBOARD_OVERLAY_DISMISS_TAPPED, 0, "first.package");
-        verify(mUiEventLogger).log(CLIPBOARD_OVERLAY_DISMISS_TAPPED, 0, "second.package");
+        mOverlayController.setClipData(mSampleClipData, "first.package");
+        mCallbacks.onShareButtonTapped();
+
+        mOverlayController.setClipData(mSampleClipData, "second.package");
+        mCallbacks.onShareButtonTapped();
+
+        verify(mUiEventLogger).log(CLIPBOARD_OVERLAY_SHARE_TAPPED, 0, "first.package");
+        verify(mUiEventLogger).log(CLIPBOARD_OVERLAY_SHARE_TAPPED, 0, "second.package");
         verify(mUiEventLogger).log(CLIPBOARD_OVERLAY_SHOWN_EXPANDED, 0, "first.package");
         verify(mUiEventLogger).log(CLIPBOARD_OVERLAY_SHOWN_EXPANDED, 0, "second.package");
         verifyNoMoreInteractions(mUiEventLogger);
@@ -332,6 +413,7 @@
 
     @Test
     public void test_logOnClipboardActionsShown() {
+        initController();
         ClipData.Item item = mSampleClipData.getItemAt(0);
         item.setTextLinks(Mockito.mock(TextLinks.class));
         when(mClipboardUtils.isRemoteCopy(any(Context.class), any(ClipData.class), anyString()))
@@ -357,6 +439,7 @@
 
     @Test
     public void test_noInsets_showsExpanded() {
+        initController();
         mOverlayController.setClipData(mSampleClipData, "");
 
         verify(mClipboardOverlayView, never()).setMinimized(true);
@@ -366,6 +449,7 @@
 
     @Test
     public void test_insets_showsMinimized() {
+        initController();
         when(mClipboardOverlayWindow.getWindowInsets()).thenReturn(
                 getImeInsets(new Rect(0, 0, 0, 1)));
         mOverlayController.setClipData(mSampleClipData, "abc");
@@ -389,6 +473,7 @@
 
     @Test
     public void test_insetsChanged_minimizes() {
+        initController();
         mOverlayController.setClipData(mSampleClipData, "");
         verify(mClipboardOverlayView, never()).setMinimized(true);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsViewTest.java
index afd9be5..f0006e5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsViewTest.java
@@ -18,6 +18,13 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.view.ViewGroup;
@@ -28,10 +35,15 @@
 
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView.OnSeekBarWithIconButtonsChangeListener;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
 
 /**
  * Tests for {@link SeekBarWithIconButtonsView}
@@ -48,14 +60,22 @@
     private SeekBar mSeekbar;
     private SeekBarWithIconButtonsView mIconDiscreteSliderLinearLayout;
 
+    @Mock
+    private OnSeekBarWithIconButtonsChangeListener mOnSeekBarChangeListener;
+
     @Before
     public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
         mIconDiscreteSliderLinearLayout = new SeekBarWithIconButtonsView(mContext);
         mIconStart = mIconDiscreteSliderLinearLayout.findViewById(R.id.icon_start);
         mIconEnd = mIconDiscreteSliderLinearLayout.findViewById(R.id.icon_end);
         mIconStartFrame = mIconDiscreteSliderLinearLayout.findViewById(R.id.icon_start_frame);
         mIconEndFrame = mIconDiscreteSliderLinearLayout.findViewById(R.id.icon_end_frame);
         mSeekbar = mIconDiscreteSliderLinearLayout.findViewById(R.id.seekbar);
+
+        mIconDiscreteSliderLinearLayout.setOnSeekBarWithIconButtonsChangeListener(
+                mOnSeekBarChangeListener);
     }
 
     @Test
@@ -109,6 +129,49 @@
     }
 
     @Test
+    public void setProgress_onlyOnProgressChangedTriggeredWithFromUserFalse() {
+        reset(mOnSeekBarChangeListener);
+        mIconDiscreteSliderLinearLayout.setProgress(1);
+
+        verify(mOnSeekBarChangeListener).onProgressChanged(
+                eq(mSeekbar), /* progress= */ eq(1), /* fromUser= */ eq(false));
+        verify(mOnSeekBarChangeListener, never()).onStartTrackingTouch(/* seekBar= */ any());
+        verify(mOnSeekBarChangeListener, never()).onStopTrackingTouch(/* seekBar= */ any());
+        verify(mOnSeekBarChangeListener, never()).onUserInteractionFinalized(
+                /* seekBar= */any(), /* control= */ anyInt());
+    }
+
+    @Test
+    public void clickIconEnd_triggerCallbacksInSequence() {
+        final int magnitude = mIconDiscreteSliderLinearLayout.getChangeMagnitude();
+        mIconDiscreteSliderLinearLayout.setProgress(0);
+        reset(mOnSeekBarChangeListener);
+
+        mIconEndFrame.performClick();
+
+        InOrder inOrder = Mockito.inOrder(mOnSeekBarChangeListener);
+        inOrder.verify(mOnSeekBarChangeListener).onProgressChanged(
+                eq(mSeekbar), /* progress= */ eq(magnitude), /* fromUser= */ eq(true));
+        inOrder.verify(mOnSeekBarChangeListener).onUserInteractionFinalized(
+                eq(mSeekbar), eq(OnSeekBarWithIconButtonsChangeListener.ControlUnitType.BUTTON));
+    }
+
+    @Test
+    public void clickIconStart_triggerCallbacksInSequence() {
+        final int magnitude = mIconDiscreteSliderLinearLayout.getChangeMagnitude();
+        mIconDiscreteSliderLinearLayout.setProgress(magnitude);
+        reset(mOnSeekBarChangeListener);
+
+        mIconStartFrame.performClick();
+
+        InOrder inOrder = Mockito.inOrder(mOnSeekBarChangeListener);
+        inOrder.verify(mOnSeekBarChangeListener).onProgressChanged(
+                eq(mSeekbar), /* progress= */ eq(0), /* fromUser= */ eq(true));
+        inOrder.verify(mOnSeekBarChangeListener).onUserInteractionFinalized(
+                eq(mSeekbar), eq(OnSeekBarWithIconButtonsChangeListener.ControlUnitType.BUTTON));
+    }
+
+    @Test
     public void setProgressStateLabels_getExpectedStateDescriptionOnInitialization() {
         String[] stateLabels = new String[]{"1", "2", "3", "4", "5"};
         mIconDiscreteSliderLinearLayout.setMax(stateLabels.length);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationCollectionLiveDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationCollectionLiveDataTest.java
index ca6282c..40f0ed3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationCollectionLiveDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationCollectionLiveDataTest.java
@@ -31,6 +31,7 @@
 import com.android.systemui.dreams.DreamOverlayStateController;
 import com.android.systemui.flags.FakeFeatureFlags;
 import com.android.systemui.flags.Flags;
+import com.android.systemui.log.core.FakeLogBuffer;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.time.FakeSystemClock;
 
@@ -66,7 +67,8 @@
         mStateController = new DreamOverlayStateController(
                 mExecutor,
                 /* overlayEnabled= */ true,
-                mFeatureFlags);
+                mFeatureFlags,
+                FakeLogBuffer.Factory.Companion.create());
         mLiveData = new ComplicationCollectionLiveData(mStateController);
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsBindingControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsBindingControllerImplTest.kt
index 26cbd77..724c9d1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsBindingControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsBindingControllerImplTest.kt
@@ -116,11 +116,7 @@
 
     @Test
     fun testBindAndLoad_cancel() {
-        val callback = object : ControlsBindingController.LoadCallback {
-            override fun error(message: String) {}
-
-            override fun accept(t: List<Control>) {}
-        }
+        val callback = mock(ControlsBindingController.LoadCallback::class.java)
         val subscription = mock(IControlsSubscription::class.java)
 
         val canceller = controller.bindAndLoad(TEST_COMPONENT_NAME_1, callback)
@@ -130,6 +126,7 @@
 
         canceller.run()
         verify(providers[0]).cancelSubscription(subscription)
+        verify(callback).error(any())
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/PackageUpdateMonitorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/PackageUpdateMonitorTest.kt
index 6954710..9260f63 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/PackageUpdateMonitorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/PackageUpdateMonitorTest.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.controls.controller
 
 import android.content.Context
+import android.content.pm.PackageManager
 import android.os.Handler
 import android.os.UserHandle
 import android.testing.AndroidTestingRunner
@@ -34,6 +35,7 @@
 import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.Mockito.`when` as whenever
 import org.mockito.MockitoAnnotations
 
 @SmallTest
@@ -42,12 +44,14 @@
 
     @Mock private lateinit var context: Context
     @Mock private lateinit var bgHandler: Handler
+    @Mock private lateinit var packageManager: PackageManager
 
     private lateinit var underTest: PackageUpdateMonitor
 
     @Before
     fun setup() {
         MockitoAnnotations.initMocks(this)
+        whenever(context.packageManager).thenReturn(packageManager)
     }
 
     @Test
@@ -58,9 +62,16 @@
         // There are two receivers registered
         verify(context, times(2))
             .registerReceiverAsUser(any(), eq(USER), any(), eq(null), eq(bgHandler))
+        verify(packageManager).registerPackageMonitorCallback(any(), eq(USER.getIdentifier()))
+        // context will be used to get PackageManager, the test should clear invocations
+        // for next startMonitoring() assertion
+        clearInvocations(context)
 
         underTest.startMonitoring()
+        // No more interactions for registerReceiverAsUser
         verifyNoMoreInteractions(context)
+        // No more interactions for registerPackageMonitorCallback
+        verifyNoMoreInteractions(packageManager)
     }
 
     @Test
@@ -69,12 +80,20 @@
 
         underTest.startMonitoring()
         clearInvocations(context)
+        clearInvocations(packageManager)
 
         underTest.stopMonitoring()
         verify(context).unregisterReceiver(any())
+        verify(packageManager).unregisterPackageMonitorCallback(any())
+        // context will be used to get PackageManager, the test should clear invocations
+        // for next stopMonitoring() assertion
+        clearInvocations(context)
 
         underTest.stopMonitoring()
+        // No more interactions for unregisterReceiver
         verifyNoMoreInteractions(context)
+        // No more interactions for unregisterPackageMonitorCallback
+        verifyNoMoreInteractions(packageManager)
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsEditingActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsEditingActivityTest.kt
index 4210675..71d2ec1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsEditingActivityTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsEditingActivityTest.kt
@@ -11,9 +11,9 @@
 import android.window.OnBackInvokedDispatcher
 import androidx.test.filters.SmallTest
 import androidx.test.rule.ActivityTestRule
-import androidx.test.runner.intercepting.SingleActivityFactory
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.activity.SingleActivityFactory
 import com.android.systemui.controls.CustomIconCache
 import com.android.systemui.controls.controller.ControlsControllerImpl
 import com.android.systemui.flags.FakeFeatureFlags
@@ -63,24 +63,19 @@
     @JvmField
     var activityRule =
         ActivityTestRule(
-            object :
-                SingleActivityFactory<TestableControlsEditingActivity>(
-                    TestableControlsEditingActivity::class.java
-                ) {
-                override fun create(intent: Intent?): TestableControlsEditingActivity {
-                    return TestableControlsEditingActivity(
-                        featureFlags,
-                        uiExecutor,
-                        controller,
-                        userTracker,
-                        customIconCache,
-                        mockDispatcher,
-                        latch
-                    )
-                }
+            /* activityFactory= */ SingleActivityFactory {
+                TestableControlsEditingActivity(
+                    featureFlags,
+                    uiExecutor,
+                    controller,
+                    userTracker,
+                    customIconCache,
+                    mockDispatcher,
+                    latch
+                )
             },
-            false,
-            false
+            /* initialTouchMode= */ false,
+            /* launchActivity= */ false,
         )
 
     @Before
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsFavoritingActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsFavoritingActivityTest.kt
index f4cc8bc..f11c296 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsFavoritingActivityTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsFavoritingActivityTest.kt
@@ -13,9 +13,9 @@
 import androidx.test.filters.FlakyTest
 import androidx.test.filters.SmallTest
 import androidx.test.rule.ActivityTestRule
-import androidx.test.runner.intercepting.SingleActivityFactory
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.activity.SingleActivityFactory
 import com.android.systemui.controls.ControlStatus
 import com.android.systemui.controls.ControlsServiceInfo
 import com.android.systemui.controls.controller.ControlsController
@@ -91,24 +91,19 @@
     @JvmField
     var activityRule =
         ActivityTestRule(
-            object :
-                SingleActivityFactory<TestableControlsFavoritingActivity>(
-                    TestableControlsFavoritingActivity::class.java
-                ) {
-                override fun create(intent: Intent?): TestableControlsFavoritingActivity {
-                    return TestableControlsFavoritingActivity(
-                        featureFlags,
-                        executor,
-                        controller,
-                        listingController,
-                        userTracker,
-                        mockDispatcher,
-                        latch
-                    )
-                }
+            /* activityFactory= */ SingleActivityFactory {
+                TestableControlsFavoritingActivity(
+                    featureFlags,
+                    executor,
+                    controller,
+                    listingController,
+                    userTracker,
+                    mockDispatcher,
+                    latch
+                )
             },
-            false,
-            false
+            /* initialTouchMode= */ false,
+            /* launchActivity= */ false,
         )
 
     @Before
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsProviderSelectorActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsProviderSelectorActivityTest.kt
index 4ba6718..d17495f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsProviderSelectorActivityTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsProviderSelectorActivityTest.kt
@@ -29,8 +29,8 @@
 import android.window.OnBackInvokedDispatcher
 import androidx.test.filters.SmallTest
 import androidx.test.rule.ActivityTestRule
-import androidx.test.runner.intercepting.SingleActivityFactory
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.activity.SingleActivityFactory
 import com.android.systemui.controls.ControlsServiceInfo
 import com.android.systemui.controls.controller.ControlsController
 import com.android.systemui.controls.panels.AuthorizedPanelsRepository
@@ -91,26 +91,21 @@
     @JvmField
     var activityRule =
         ActivityTestRule(
-            object :
-                SingleActivityFactory<TestableControlsProviderSelectorActivity>(
-                    TestableControlsProviderSelectorActivity::class.java
-                ) {
-                override fun create(intent: Intent?): TestableControlsProviderSelectorActivity {
-                    return TestableControlsProviderSelectorActivity(
-                        executor,
-                        backExecutor,
-                        listingController,
-                        controlsController,
-                        userTracker,
-                        authorizedPanelsRepository,
-                        dialogFactory,
-                        mockDispatcher,
-                        latch
-                    )
-                }
+            /* activityFactory= */ SingleActivityFactory {
+                TestableControlsProviderSelectorActivity(
+                    executor,
+                    backExecutor,
+                    listingController,
+                    controlsController,
+                    userTracker,
+                    authorizedPanelsRepository,
+                    dialogFactory,
+                    mockDispatcher,
+                    latch
+                )
             },
-            false,
-            false
+            /* initialTouchMode= */ false,
+            /* launchActivity= */ false,
         )
 
     @Before
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsRequestDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsRequestDialogTest.kt
index 314b176..ca970bb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsRequestDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsRequestDialogTest.kt
@@ -30,8 +30,8 @@
 import androidx.lifecycle.Lifecycle
 import androidx.test.filters.MediumTest
 import androidx.test.rule.ActivityTestRule
-import androidx.test.runner.intercepting.SingleActivityFactory
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.activity.SingleActivityFactory
 import com.android.systemui.controls.controller.ControlInfo
 import com.android.systemui.controls.controller.ControlsController
 import com.android.systemui.settings.UserTracker
@@ -81,19 +81,18 @@
 
     @Rule
     @JvmField
-    var activityRule = ActivityTestRule<TestControlsRequestDialog>(
-            object : SingleActivityFactory<TestControlsRequestDialog>(
-                    TestControlsRequestDialog::class.java
-            ) {
-                    override fun create(intent: Intent?): TestControlsRequestDialog {
-                        return TestControlsRequestDialog(
-                                mainExecutor,
-                                controller,
-                                userTracker,
-                                listingController
-                        )
-                    }
-            }, false, false)
+    var activityRule = ActivityTestRule(
+        /* activityFactory= */ SingleActivityFactory {
+            TestControlsRequestDialog(
+                    mainExecutor,
+                    controller,
+                    userTracker,
+                    listingController
+            )
+        },
+        /* initialTouchMode= */ false,
+        /* launchActivity= */ false,
+    )
 
     private lateinit var control: Control
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsActivityTest.kt
index 2d3e10e..e279d28 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsActivityTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsActivityTest.kt
@@ -23,8 +23,8 @@
 import android.testing.TestableLooper
 import androidx.test.filters.SmallTest
 import androidx.test.rule.ActivityTestRule
-import androidx.test.runner.intercepting.SingleActivityFactory
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.activity.SingleActivityFactory
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.controls.settings.ControlsSettingsDialogManager
 import com.android.systemui.flags.FeatureFlags
@@ -53,23 +53,18 @@
     @JvmField
     var activityRule =
         ActivityTestRule(
-            object :
-                SingleActivityFactory<TestableControlsActivity>(
-                    TestableControlsActivity::class.java
-                ) {
-                override fun create(intent: Intent?): TestableControlsActivity {
-                    return TestableControlsActivity(
-                        uiController,
-                        broadcastDispatcher,
-                        dreamManager,
-                        featureFlags,
-                        controlsSettingsDialogManager,
-                        keyguardStateController,
-                    )
-                }
+            /* activityFactory= */ SingleActivityFactory {
+                TestableControlsActivity(
+                    uiController,
+                    broadcastDispatcher,
+                    dreamManager,
+                    featureFlags,
+                    controlsSettingsDialogManager,
+                    keyguardStateController,
+                )
             },
-            false,
-            false
+            /* initialTouchMode= */ false,
+            /* launchActivity= */ false,
         )
 
     @Before
diff --git a/packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerDecorProviderFactoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerDecorProviderFactoryTest.kt
index fcc3589..8f0b193 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerDecorProviderFactoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerDecorProviderFactoryTest.kt
@@ -39,7 +39,7 @@
 
     @Before
     fun setUp() {
-        roundedCornerResDelegate = spy(RoundedCornerResDelegate(mContext.resources, null))
+        roundedCornerResDelegate = spy(RoundedCornerResDelegateImpl(mContext.resources, null))
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerResDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerResDelegateTest.kt
index 93a1868..4feba7b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerResDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerResDelegateTest.kt
@@ -48,7 +48,7 @@
     @Test
     fun testTopAndBottomRoundedCornerExist() {
         setupResources(radius = 5)
-        roundedCornerResDelegate = RoundedCornerResDelegate(mContext.resources, null)
+        roundedCornerResDelegate = RoundedCornerResDelegateImpl(mContext.resources, null)
         assertEquals(true, roundedCornerResDelegate.hasTop)
         assertEquals(true, roundedCornerResDelegate.hasBottom)
     }
@@ -56,7 +56,7 @@
     @Test
     fun testTopRoundedCornerExist() {
         setupResources(radiusTop = 10)
-        roundedCornerResDelegate = RoundedCornerResDelegate(mContext.resources, null)
+        roundedCornerResDelegate = RoundedCornerResDelegateImpl(mContext.resources, null)
         assertEquals(true, roundedCornerResDelegate.hasTop)
         assertEquals(false, roundedCornerResDelegate.hasBottom)
     }
@@ -64,7 +64,7 @@
     @Test
     fun testBottomRoundedCornerExist() {
         setupResources(radiusBottom = 15)
-        roundedCornerResDelegate = RoundedCornerResDelegate(mContext.resources, null)
+        roundedCornerResDelegate = RoundedCornerResDelegateImpl(mContext.resources, null)
         assertEquals(false, roundedCornerResDelegate.hasTop)
         assertEquals(true, roundedCornerResDelegate.hasBottom)
     }
@@ -75,7 +75,7 @@
                 roundedTopDrawable = getTestsDrawable(R.drawable.rounded3px),
                 roundedBottomDrawable = getTestsDrawable(R.drawable.rounded4px))
 
-        roundedCornerResDelegate = RoundedCornerResDelegate(mContext.resources, null)
+        roundedCornerResDelegate = RoundedCornerResDelegateImpl(mContext.resources, null)
 
         assertEquals(Size(3, 3), roundedCornerResDelegate.topRoundedSize)
         assertEquals(Size(4, 4), roundedCornerResDelegate.bottomRoundedSize)
@@ -96,7 +96,7 @@
                 roundedTopDrawable = getTestsDrawable(R.drawable.rounded3px),
                 roundedBottomDrawable = getTestsDrawable(R.drawable.rounded4px))
 
-        roundedCornerResDelegate = RoundedCornerResDelegate(mContext.resources, null)
+        roundedCornerResDelegate = RoundedCornerResDelegateImpl(mContext.resources, null)
 
         assertEquals(Size(3, 3), roundedCornerResDelegate.topRoundedSize)
         assertEquals(Size(4, 4), roundedCornerResDelegate.bottomRoundedSize)
@@ -109,33 +109,12 @@
     }
 
     @Test
-    fun testUpdateTuningSizeFactor() {
-        setupResources(radius = 100,
-                roundedTopDrawable = getTestsDrawable(R.drawable.rounded3px),
-                roundedBottomDrawable = getTestsDrawable(R.drawable.rounded4px))
-
-        roundedCornerResDelegate = RoundedCornerResDelegate(mContext.resources, null)
-
-        val factor = 5
-        roundedCornerResDelegate.tuningSizeFactor = factor
-        val length = (factor * mContext.resources.displayMetrics.density).toInt()
-
-        assertEquals(Size(length, length), roundedCornerResDelegate.topRoundedSize)
-        assertEquals(Size(length, length), roundedCornerResDelegate.bottomRoundedSize)
-
-        roundedCornerResDelegate.tuningSizeFactor = null
-
-        assertEquals(Size(3, 3), roundedCornerResDelegate.topRoundedSize)
-        assertEquals(Size(4, 4), roundedCornerResDelegate.bottomRoundedSize)
-    }
-
-    @Test
     fun testPhysicalPixelDisplaySizeChanged() {
         setupResources(
                 roundedTopDrawable = getTestsDrawable(R.drawable.rounded4px),
                 roundedBottomDrawable = getTestsDrawable(R.drawable.rounded4px))
 
-        roundedCornerResDelegate = RoundedCornerResDelegate(mContext.resources, null)
+        roundedCornerResDelegate = RoundedCornerResDelegateImpl(mContext.resources, null)
         assertEquals(Size(4, 4), roundedCornerResDelegate.topRoundedSize)
         assertEquals(Size(4, 4), roundedCornerResDelegate.bottomRoundedSize)
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/demomode/DemoModeControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/demomode/DemoModeControllerTest.kt
index 75eec72..68ea7eb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/demomode/DemoModeControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/demomode/DemoModeControllerTest.kt
@@ -94,7 +94,7 @@
         intent.putExtra("command", command)
         args.forEach { arg -> intent.putExtra(arg.key, arg.value) }
 
-        fakeBroadcastDispatcher.registeredReceivers.forEach { it.onReceive(context, intent) }
+        fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(context, intent)
     }
 
     companion object {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayMetricsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayMetricsRepositoryTest.kt
new file mode 100644
index 0000000..dd741b4
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayMetricsRepositoryTest.kt
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.display.data.repository
+
+import android.content.Context
+import android.util.DisplayMetrics
+import android.view.Display
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.statusbar.policy.FakeConfigurationController
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+
+@SmallTest
+@OptIn(ExperimentalCoroutinesApi::class)
+class DisplayMetricsRepositoryTest : SysuiTestCase() {
+    private lateinit var underTest: DisplayMetricsRepository
+
+    private val testScope = TestScope(StandardTestDispatcher())
+    private val configurationController = FakeConfigurationController()
+
+    private val displayMetrics =
+        DisplayMetrics().apply { this.heightPixels = INITIAL_HEIGHT_PIXELS }
+    private val mockContext: Context = mock()
+    private val mockDisplay: Display = mock()
+
+    @Before
+    fun setUp() {
+        underTest =
+            DisplayMetricsRepository(
+                testScope.backgroundScope,
+                configurationController,
+                displayMetrics,
+                mockContext,
+                LogBuffer("TestBuffer", maxSize = 10, logcatEchoTracker = mock())
+            )
+        whenever(mockContext.display).thenReturn(mockDisplay)
+    }
+
+    @Test
+    fun heightPixels_getsInitialValue() {
+        assertThat(underTest.heightPixels).isEqualTo(INITIAL_HEIGHT_PIXELS)
+    }
+
+    @Test
+    fun heightPixels_configChanged_heightUpdated() =
+        testScope.runTest {
+            runCurrent()
+
+            updateDisplayMetrics(456)
+            configurationController.notifyConfigurationChanged()
+            runCurrent()
+
+            assertThat(underTest.heightPixels).isEqualTo(456)
+
+            updateDisplayMetrics(23)
+            configurationController.notifyConfigurationChanged()
+            runCurrent()
+
+            assertThat(underTest.heightPixels).isEqualTo(23)
+        }
+
+    private fun updateDisplayMetrics(newHeight: Int) {
+        whenever(mockDisplay.getMetrics(displayMetrics)).thenAnswer {
+            it.getArgument<DisplayMetrics>(0).heightPixels = newHeight
+            Unit
+        }
+    }
+
+    private companion object {
+        const val INITIAL_HEIGHT_PIXELS = 345
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt
new file mode 100644
index 0000000..9be54fb
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.display.data.repository
+
+import android.hardware.display.DisplayManager
+import android.os.Looper
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.view.Display
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.FlowValue
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.kotlinArgumentCaptor
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.utils.os.FakeHandler
+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
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyLong
+import org.mockito.Mockito.never
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+class DisplayRepositoryTest : SysuiTestCase() {
+
+    private val displayManager = mock<DisplayManager>()
+    private val displayListener = kotlinArgumentCaptor<DisplayManager.DisplayListener>()
+
+    private val testHandler = FakeHandler(Looper.getMainLooper())
+    private val testScope = TestScope(UnconfinedTestDispatcher())
+
+    private lateinit var displayRepository: DisplayRepositoryImpl
+
+    @Before
+    fun setup() {
+        setDisplays(emptyList())
+        displayRepository =
+            DisplayRepositoryImpl(
+                displayManager,
+                testHandler,
+                TestScope(UnconfinedTestDispatcher()),
+                UnconfinedTestDispatcher()
+            )
+        verify(displayManager, never()).registerDisplayListener(any(), any())
+    }
+
+    @Test
+    fun onFlowCollection_displayListenerRegistered() =
+        testScope.runTest {
+            val value by latestDisplayFlowValue()
+
+            assertThat(value).isEmpty()
+
+            verify(displayManager).registerDisplayListener(any(), eq(testHandler), anyLong())
+        }
+
+    @Test
+    fun afterFlowCollection_displayListenerUnregistered() {
+        testScope.runTest {
+            val value by latestDisplayFlowValue()
+
+            assertThat(value).isEmpty()
+
+            verify(displayManager).registerDisplayListener(any(), eq(testHandler), anyLong())
+        }
+        verify(displayManager).unregisterDisplayListener(any())
+    }
+
+    @Test
+    fun afterFlowCollection_multipleSusbcriptions_oneRemoved_displayListenerNotUnregistered() {
+        testScope.runTest {
+            val firstSubscriber by latestDisplayFlowValue()
+
+            assertThat(firstSubscriber).isEmpty()
+            verify(displayManager, times(1))
+                .registerDisplayListener(displayListener.capture(), eq(testHandler), anyLong())
+
+            val innerScope = TestScope()
+            innerScope.runTest {
+                val secondSubscriber by latestDisplayFlowValue()
+                assertThat(secondSubscriber).isEmpty()
+
+                // No new registration, just the precedent one.
+                verify(displayManager, times(1))
+                    .registerDisplayListener(any(), eq(testHandler), anyLong())
+            }
+
+            // Let's make sure it has *NOT* been unregistered, as there is still a subscriber.
+            setDisplays(1)
+            displayListener.value.onDisplayAdded(1)
+            assertThat(firstSubscriber?.ids()).containsExactly(1)
+        }
+
+        // All subscribers are done, unregister should have been called.
+        verify(displayManager).unregisterDisplayListener(any())
+    }
+    @Test
+    fun onDisplayAdded_propagated() =
+        testScope.runTest {
+            val value by latestDisplayFlowValue()
+
+            setDisplays(1)
+            displayListener.value.onDisplayAdded(1)
+
+            assertThat(value?.ids()).containsExactly(1)
+        }
+
+    @Test
+    fun onDisplayRemoved_propagated() =
+        testScope.runTest {
+            val value by latestDisplayFlowValue()
+
+            setDisplays(1, 2, 3, 4)
+            displayListener.value.onDisplayAdded(1)
+            displayListener.value.onDisplayAdded(2)
+            displayListener.value.onDisplayAdded(3)
+            displayListener.value.onDisplayAdded(4)
+
+            setDisplays(1, 2, 3)
+            displayListener.value.onDisplayRemoved(4)
+
+            assertThat(value?.ids()).containsExactly(1, 2, 3)
+        }
+
+    @Test
+    fun onDisplayChanged_propagated() =
+        testScope.runTest {
+            val value by latestDisplayFlowValue()
+
+            setDisplays(1, 2, 3, 4)
+            displayListener.value.onDisplayAdded(1)
+            displayListener.value.onDisplayAdded(2)
+            displayListener.value.onDisplayAdded(3)
+            displayListener.value.onDisplayAdded(4)
+
+            displayListener.value.onDisplayChanged(4)
+
+            assertThat(value?.ids()).containsExactly(1, 2, 3, 4)
+        }
+
+    private fun Iterable<Display>.ids(): List<Int> = map { it.displayId }
+
+    // Wrapper to capture the displayListener.
+    private fun TestScope.latestDisplayFlowValue(): FlowValue<Set<Display>?> {
+        val flowValue = collectLastValue(displayRepository.displays)
+        verify(displayManager)
+            .registerDisplayListener(displayListener.capture(), eq(testHandler), anyLong())
+        return flowValue
+    }
+
+    private fun setDisplays(displays: List<Display>) {
+        whenever(displayManager.displays).thenReturn(displays.toTypedArray())
+    }
+
+    private fun setDisplays(vararg ids: Int) {
+        setDisplays(ids.map { display(it) })
+    }
+
+    private fun display(id: Int): Display {
+        return mock<Display>().also { mockDisplay ->
+            whenever(mockDisplay.displayId).thenReturn(id)
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractorTest.kt
new file mode 100644
index 0000000..1b597f4
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractorTest.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.display.domain.interactor
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.view.Display
+import android.view.Display.TYPE_EXTERNAL
+import android.view.Display.TYPE_INTERNAL
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.FlowValue
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.display.data.repository.DisplayRepository
+import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.State
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+class ConnectedDisplayInteractorTest : SysuiTestCase() {
+
+    private val fakeDisplayRepository = FakeDisplayRepository()
+    private val connectedDisplayStateProvider: ConnectedDisplayInteractor =
+        ConnectedDisplayInteractorImpl(fakeDisplayRepository)
+    private val testScope = TestScope(UnconfinedTestDispatcher())
+
+    @Test
+    fun displayState_nullDisplays_disconnected() =
+        testScope.runTest {
+            val value by lastValue()
+
+            fakeDisplayRepository.emit(emptySet())
+
+            assertThat(value).isEqualTo(State.DISCONNECTED)
+        }
+
+    @Test
+    fun displayState_emptyDisplays_disconnected() =
+        testScope.runTest {
+            val value by lastValue()
+
+            fakeDisplayRepository.emit(emptySet())
+
+            assertThat(value).isEqualTo(State.DISCONNECTED)
+        }
+
+    @Test
+    fun displayState_internalDisplay_disconnected() =
+        testScope.runTest {
+            val value by lastValue()
+
+            fakeDisplayRepository.emit(setOf(display(type = TYPE_INTERNAL)))
+
+            assertThat(value).isEqualTo(State.DISCONNECTED)
+        }
+
+    @Test
+    fun displayState_externalDisplay_connected() =
+        testScope.runTest {
+            val value by lastValue()
+
+            fakeDisplayRepository.emit(setOf(display(type = TYPE_EXTERNAL)))
+
+            assertThat(value).isEqualTo(State.CONNECTED)
+        }
+
+    @Test
+    fun displayState_multipleExternalDisplays_connected() =
+        testScope.runTest {
+            val value by lastValue()
+
+            fakeDisplayRepository.emit(
+                setOf(display(type = TYPE_EXTERNAL), display(type = TYPE_EXTERNAL))
+            )
+
+            assertThat(value).isEqualTo(State.CONNECTED)
+        }
+
+    @Test
+    fun displayState_externalSecure_connectedSecure() =
+        testScope.runTest {
+            val value by lastValue()
+
+            fakeDisplayRepository.emit(
+                setOf(display(type = TYPE_EXTERNAL, flags = Display.FLAG_SECURE))
+            )
+
+            assertThat(value).isEqualTo(State.CONNECTED_SECURE)
+        }
+
+    @Test
+    fun displayState_multipleExternal_onlyOneSecure_connectedSecure() =
+        testScope.runTest {
+            val value by lastValue()
+
+            fakeDisplayRepository.emit(
+                setOf(
+                    display(type = TYPE_EXTERNAL, flags = Display.FLAG_SECURE),
+                    display(type = TYPE_EXTERNAL, flags = 0)
+                )
+            )
+
+            assertThat(value).isEqualTo(State.CONNECTED_SECURE)
+        }
+
+    private fun TestScope.lastValue(): FlowValue<State?> =
+        collectLastValue(connectedDisplayStateProvider.connectedDisplayState)
+
+    private fun display(type: Int, flags: Int = 0): Display {
+        return mock<Display>().also { mockDisplay ->
+            whenever(mockDisplay.type).thenReturn(type)
+            whenever(mockDisplay.flags).thenReturn(flags)
+        }
+    }
+
+    private class FakeDisplayRepository : DisplayRepository {
+        private val flow = MutableSharedFlow<Set<Display>>()
+        suspend fun emit(value: Set<Display>) = flow.emit(value)
+        override val displays: Flow<Set<Display>>
+            get() = flow
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt
index a00e545..57307fc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt
@@ -9,6 +9,7 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.complication.ComplicationHostViewController
 import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel
+import com.android.systemui.log.core.FakeLogBuffer
 import com.android.systemui.statusbar.BlurUtils
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.util.mockito.argumentCaptor
@@ -47,7 +48,7 @@
     @Mock private lateinit var stateController: DreamOverlayStateController
     @Mock private lateinit var configController: ConfigurationController
     @Mock private lateinit var transitionViewModel: DreamingToLockscreenTransitionViewModel
-    @Mock private lateinit var logger: DreamLogger
+    private val logBuffer = FakeLogBuffer.Factory.create()
     private lateinit var controller: DreamOverlayAnimationsController
 
     @Before
@@ -66,7 +67,7 @@
                 DREAM_IN_COMPLICATIONS_ANIMATION_DURATION,
                 DREAM_IN_TRANSLATION_Y_DISTANCE,
                 DREAM_IN_TRANSLATION_Y_DURATION,
-                logger
+                logBuffer
             )
 
         val mockView: View = mock()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
index 47b7d49..8786520 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
@@ -41,8 +41,8 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.complication.ComplicationHostViewController;
 import com.android.systemui.dreams.touch.scrim.BouncerlessScrimController;
-import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor;
-import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor.PrimaryBouncerExpansionCallback;
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor;
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor.PrimaryBouncerExpansionCallback;
 import com.android.systemui.statusbar.BlurUtils;
 
 import org.junit.Before;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
index d0d348d..d99f0da 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.dreams;
 
+import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
@@ -507,4 +509,23 @@
         mService.onWakeUp();
         verify(mDreamOverlayContainerViewController, never()).wakeUp();
     }
+
+    @Test
+    public void testSystemFlagShowForAllUsersSetOnWindow() throws RemoteException {
+        final IDreamOverlayClient client = getClient();
+
+        // Inform the overlay service of dream starting. Do not show dream complications.
+        client.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
+                false /*shouldShowComplication*/);
+        mMainExecutor.runAllReady();
+
+        final ArgumentCaptor<WindowManager.LayoutParams> paramsCaptor =
+                ArgumentCaptor.forClass(WindowManager.LayoutParams.class);
+
+        // Verify that a new window is added.
+        verify(mWindowManager).addView(any(), paramsCaptor.capture());
+
+        assertThat((paramsCaptor.getValue().privateFlags & SYSTEM_FLAG_SHOW_FOR_ALL_USERS)
+                == SYSTEM_FLAG_SHOW_FOR_ALL_USERS).isTrue();
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
index 7b41605..44a78ac 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
@@ -34,6 +34,8 @@
 import com.android.systemui.complication.Complication;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
+import com.android.systemui.log.LogBuffer;
+import com.android.systemui.log.core.FakeLogBuffer;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.time.FakeSystemClock;
 
@@ -58,6 +60,8 @@
     @Mock
     private FeatureFlags mFeatureFlags;
 
+    private final LogBuffer mLogBuffer = FakeLogBuffer.Factory.Companion.create();
+
     final FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
 
     @Before
@@ -405,6 +409,11 @@
     }
 
     private DreamOverlayStateController getDreamOverlayStateController(boolean overlayEnabled) {
-        return new DreamOverlayStateController(mExecutor, overlayEnabled, mFeatureFlags);
+        return new DreamOverlayStateController(
+                mExecutor,
+                overlayEnabled,
+                mFeatureFlags,
+                mLogBuffer
+        );
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java
index d16b757..4e74f451 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java
@@ -48,6 +48,8 @@
 
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.log.LogBuffer;
+import com.android.systemui.log.core.FakeLogBuffer;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController;
 import com.android.systemui.statusbar.policy.NextAlarmController;
@@ -114,6 +116,8 @@
     @Mock
     UserTracker mUserTracker;
 
+    LogBuffer mLogBuffer = FakeLogBuffer.Factory.Companion.create();
+
     @Captor
     private ArgumentCaptor<DreamOverlayStateController.Callback> mCallbackCaptor;
 
@@ -146,7 +150,8 @@
                 mStatusBarWindowStateController,
                 mDreamOverlayStatusBarItemsProvider,
                 mDreamOverlayStateController,
-                mUserTracker);
+                mUserTracker,
+                mLogBuffer);
     }
 
     @Test
@@ -289,7 +294,8 @@
                 mStatusBarWindowStateController,
                 mDreamOverlayStatusBarItemsProvider,
                 mDreamOverlayStateController,
-                mUserTracker);
+                mUserTracker,
+                mLogBuffer);
         controller.onViewAttached();
         verify(mView, never()).showIcon(
                 eq(DreamOverlayStatusBarView.STATUS_ICON_NOTIFICATIONS), eq(true), any());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java
index 1a89076..3f9b198 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java
@@ -28,6 +28,7 @@
 
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
+import android.content.pm.UserInfo;
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.testing.AndroidTestingRunner;
@@ -39,10 +40,12 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.logging.UiEventLogger;
+import com.android.internal.widget.LockPatternUtils;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants;
 import com.android.systemui.dreams.touch.scrim.ScrimController;
 import com.android.systemui.dreams.touch.scrim.ScrimManager;
-import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants;
+import com.android.systemui.settings.FakeUserTracker;
 import com.android.systemui.shade.ShadeExpansionChangeEvent;
 import com.android.systemui.shared.system.InputChannelCompat;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
@@ -57,6 +60,7 @@
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
+import java.util.Collections;
 import java.util.Optional;
 
 @SmallTest
@@ -100,21 +104,34 @@
     @Mock
     UiEventLogger mUiEventLogger;
 
+    @Mock
+    LockPatternUtils mLockPatternUtils;
+
+    FakeUserTracker mUserTracker;
+
     private static final float TOUCH_REGION = .3f;
     private static final int SCREEN_WIDTH_PX = 1024;
     private static final int SCREEN_HEIGHT_PX = 100;
 
     private static final Rect SCREEN_BOUNDS = new Rect(0, 0, 1024, 100);
+    private static final UserInfo CURRENT_USER_INFO = new UserInfo(
+            10,
+            /* name= */ "user10",
+            /* flags= */ 0
+    );
 
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
+        mUserTracker = new FakeUserTracker();
         mTouchHandler = new BouncerSwipeTouchHandler(
                 mScrimManager,
                 Optional.of(mCentralSurfaces),
                 mNotificationShadeWindowController,
                 mValueAnimatorCreator,
                 mVelocityTrackerFactory,
+                mLockPatternUtils,
+                mUserTracker,
                 mFlingAnimationUtils,
                 mFlingAnimationUtilsClosing,
                 TOUCH_REGION,
@@ -126,6 +143,9 @@
         when(mVelocityTrackerFactory.obtain()).thenReturn(mVelocityTracker);
         when(mFlingAnimationUtils.getMinVelocityPxPerSecond()).thenReturn(Float.MAX_VALUE);
         when(mTouchSession.getBounds()).thenReturn(SCREEN_BOUNDS);
+        when(mLockPatternUtils.isSecure(CURRENT_USER_INFO.id)).thenReturn(true);
+
+        mUserTracker.set(Collections.singletonList(CURRENT_USER_INFO), 0);
     }
 
     /**
@@ -265,6 +285,32 @@
         verifyScroll(.7f, Direction.DOWN, true, gestureListener);
     }
 
+    /**
+     * Makes sure the expansion amount is proportional to (1 - scroll).
+     */
+    @Test
+    public void testSwipeUp_keyguardNotSecure_doesNotExpand() {
+        when(mLockPatternUtils.isSecure(CURRENT_USER_INFO.id)).thenReturn(false);
+        mTouchHandler.onSessionStart(mTouchSession);
+        ArgumentCaptor<GestureDetector.OnGestureListener> gestureListenerCaptor =
+                ArgumentCaptor.forClass(GestureDetector.OnGestureListener.class);
+        verify(mTouchSession).registerGestureListener(gestureListenerCaptor.capture());
+
+        final OnGestureListener gestureListener = gestureListenerCaptor.getValue();
+
+        final float distanceY = SCREEN_HEIGHT_PX * 0.3f;
+        final MotionEvent event1 = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE,
+                0, SCREEN_HEIGHT_PX, 0);
+        final MotionEvent event2 = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE,
+                0, SCREEN_HEIGHT_PX - distanceY, 0);
+
+        reset(mScrimController);
+        assertThat(gestureListener.onScroll(event1, event2, 0, distanceY))
+                .isTrue();
+        // We should not expand since the keyguard is not secure
+        verify(mScrimController, never()).expand(any());
+    }
+
     private void verifyScroll(float percent, Direction direction,
             boolean isBouncerInitiallyShowing, GestureDetector.OnGestureListener gestureListener) {
         final float distanceY = SCREEN_HEIGHT_PX * percent;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dump/DumpHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/dump/DumpHandlerTest.kt
index e8cbdf3..2830476 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dump/DumpHandlerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/dump/DumpHandlerTest.kt
@@ -22,7 +22,7 @@
 import com.android.systemui.ProtoDumpable
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.shared.system.UncaughtExceptionPreHandlerManager
+import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.eq
 import com.google.common.truth.Truth.assertThat
@@ -45,8 +45,6 @@
 
     @Mock
     private lateinit var logBufferEulogizer: LogBufferEulogizer
-    @Mock
-    private lateinit var exceptionHandlerManager: UncaughtExceptionPreHandlerManager
 
     @Mock
     private lateinit var pw: PrintWriter
@@ -70,6 +68,11 @@
     @Mock
     private lateinit var buffer2: LogBuffer
 
+    @Mock
+    private lateinit var table1: TableLogBuffer
+    @Mock
+    private lateinit var table2: TableLogBuffer
+
     private val dumpManager = DumpManager()
 
     @Before
@@ -83,21 +86,22 @@
             mutableMapOf(
                 EmptyCoreStartable::class.java to Provider { EmptyCoreStartable() }
             ),
-            exceptionHandlerManager
         )
     }
 
     @Test
     fun testDumpablesCanBeDumpedSelectively() {
         // GIVEN a variety of registered dumpables and buffers
-        dumpManager.registerDumpable("dumpable1", dumpable1)
-        dumpManager.registerDumpable("dumpable2", dumpable2)
-        dumpManager.registerDumpable("dumpable3", dumpable3)
+        dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
+        dumpManager.registerCriticalDumpable("dumpable2", dumpable2)
+        dumpManager.registerCriticalDumpable("dumpable3", dumpable3)
         dumpManager.registerBuffer("buffer1", buffer1)
         dumpManager.registerBuffer("buffer2", buffer2)
+        dumpManager.registerTableLogBuffer("table1", table1)
+        dumpManager.registerTableLogBuffer("table2", table2)
 
         // WHEN some of them are dumped explicitly
-        val args = arrayOf("dumpable1", "dumpable3", "buffer2")
+        val args = arrayOf("dumpable1", "dumpable3", "buffer2", "table2")
         dumpHandler.dump(fd, pw, args)
 
         // THEN only the requested ones have their dump() method called
@@ -108,12 +112,14 @@
         verify(dumpable3).dump(pw, args)
         verify(buffer1, never()).dump(any(PrintWriter::class.java), anyInt())
         verify(buffer2).dump(pw, 0)
+        verify(table1, never()).dump(any(), any())
+        verify(table2).dump(pw, args)
     }
 
     @Test
     fun testDumpableMatchingIsBasedOnEndOfTag() {
         // GIVEN a dumpable registered to the manager
-        dumpManager.registerDumpable("com.android.foo.bar.dumpable1", dumpable1)
+        dumpManager.registerCriticalDumpable("com.android.foo.bar.dumpable1", dumpable1)
 
         // WHEN that module is dumped
         val args = arrayOf("dumpable1")
@@ -131,6 +137,8 @@
         dumpManager.registerNormalDumpable("dumpable3", dumpable3)
         dumpManager.registerBuffer("buffer1", buffer1)
         dumpManager.registerBuffer("buffer2", buffer2)
+        dumpManager.registerTableLogBuffer("table1", table1)
+        dumpManager.registerTableLogBuffer("table2", table2)
 
         // WHEN a critical dump is requested
         val args = arrayOf("--dump-priority", "CRITICAL")
@@ -144,6 +152,8 @@
             any(Array<String>::class.java))
         verify(buffer1, never()).dump(any(PrintWriter::class.java), anyInt())
         verify(buffer2, never()).dump(any(PrintWriter::class.java), anyInt())
+        verify(table1, never()).dump(any(), any())
+        verify(table2, never()).dump(any(), any())
     }
 
     @Test
@@ -154,6 +164,8 @@
         dumpManager.registerNormalDumpable("dumpable3", dumpable3)
         dumpManager.registerBuffer("buffer1", buffer1)
         dumpManager.registerBuffer("buffer2", buffer2)
+        dumpManager.registerTableLogBuffer("table1", table1)
+        dumpManager.registerTableLogBuffer("table2", table2)
 
         // WHEN a normal dump is requested
         val args = arrayOf("--dump-priority", "NORMAL")
@@ -169,6 +181,8 @@
         verify(dumpable3).dump(pw, args)
         verify(buffer1).dump(pw, 0)
         verify(buffer2).dump(pw, 0)
+        verify(table1).dump(pw, args)
+        verify(table2).dump(pw, args)
     }
 
     @Test
@@ -184,6 +198,81 @@
     }
 
     @Test
+    fun testDumpBuffers() {
+        // GIVEN a variety of registered dumpables and buffers and tables
+        dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
+        dumpManager.registerCriticalDumpable("dumpable2", dumpable2)
+        dumpManager.registerNormalDumpable("dumpable3", dumpable3)
+        dumpManager.registerBuffer("buffer1", buffer1)
+        dumpManager.registerBuffer("buffer2", buffer2)
+        dumpManager.registerTableLogBuffer("table1", table1)
+        dumpManager.registerTableLogBuffer("table2", table2)
+
+        // WHEN a buffer dump is requested
+        val args = arrayOf("buffers", "--tail", "1")
+        dumpHandler.dump(fd, pw, args)
+
+        // THEN all buffers are dumped (and no dumpables or tables)
+        verify(dumpable1, never()).dump(any(), any())
+        verify(dumpable2, never()).dump(any(), any())
+        verify(dumpable3, never()).dump(any(), any())
+        verify(buffer1).dump(pw, tailLength = 1)
+        verify(buffer2).dump(pw, tailLength = 1)
+        verify(table1, never()).dump(any(), any())
+        verify(table2, never()).dump(any(), any())
+    }
+
+    @Test
+    fun testDumpDumpables() {
+        // GIVEN a variety of registered dumpables and buffers and tables
+        dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
+        dumpManager.registerCriticalDumpable("dumpable2", dumpable2)
+        dumpManager.registerNormalDumpable("dumpable3", dumpable3)
+        dumpManager.registerBuffer("buffer1", buffer1)
+        dumpManager.registerBuffer("buffer2", buffer2)
+        dumpManager.registerTableLogBuffer("table1", table1)
+        dumpManager.registerTableLogBuffer("table2", table2)
+
+        // WHEN a dumpable dump is requested
+        val args = arrayOf("dumpables")
+        dumpHandler.dump(fd, pw, args)
+
+        // THEN all dumpables are dumped (both critical and normal) (and no dumpables)
+        verify(dumpable1).dump(pw, args)
+        verify(dumpable2).dump(pw, args)
+        verify(dumpable3).dump(pw, args)
+        verify(buffer1, never()).dump(any(), anyInt())
+        verify(buffer2, never()).dump(any(), anyInt())
+        verify(table1, never()).dump(any(), any())
+        verify(table2, never()).dump(any(), any())
+    }
+
+    @Test
+    fun testDumpTables() {
+        // GIVEN a variety of registered dumpables and buffers and tables
+        dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
+        dumpManager.registerCriticalDumpable("dumpable2", dumpable2)
+        dumpManager.registerNormalDumpable("dumpable3", dumpable3)
+        dumpManager.registerBuffer("buffer1", buffer1)
+        dumpManager.registerBuffer("buffer2", buffer2)
+        dumpManager.registerTableLogBuffer("table1", table1)
+        dumpManager.registerTableLogBuffer("table2", table2)
+
+        // WHEN a dumpable dump is requested
+        val args = arrayOf("tables")
+        dumpHandler.dump(fd, pw, args)
+
+        // THEN all dumpables are dumped (both critical and normal) (and no dumpables)
+        verify(dumpable1, never()).dump(any(), any())
+        verify(dumpable2, never()).dump(any(), any())
+        verify(dumpable3, never()).dump(any(), any())
+        verify(buffer1, never()).dump(any(), anyInt())
+        verify(buffer2, never()).dump(any(), anyInt())
+        verify(table1).dump(pw, args)
+        verify(table2).dump(pw, args)
+    }
+
+    @Test
     fun testDumpAllProtoDumpables() {
         dumpManager.registerDumpable("protoDumpable1", protoDumpable1)
         dumpManager.registerDumpable("protoDumpable2", protoDumpable2)
@@ -207,6 +296,123 @@
         verify(protoDumpable2, never()).dumpProto(any(), any())
     }
 
+    @Test
+    fun testDumpTarget_selectsShortestNamedDumpable() {
+        // GIVEN a variety of registered dumpables and buffers
+        dumpManager.registerCriticalDumpable("first-dumpable", dumpable1)
+        dumpManager.registerCriticalDumpable("scnd-dumpable", dumpable2)
+        dumpManager.registerCriticalDumpable("third-dumpable", dumpable3)
+
+        // WHEN a dumpable is dumped by a suffix that matches multiple options
+        val args = arrayOf("dumpable")
+        dumpHandler.dump(fd, pw, args)
+
+        // THEN the matching dumpable with the shorter name is dumped
+        verify(dumpable1, never()).dump(any(), any())
+        verify(dumpable2).dump(pw, args)
+        verify(dumpable3, never()).dump(any(), any())
+    }
+
+    @Test
+    fun testDumpTarget_selectsShortestNamedBuffer() {
+        // GIVEN a variety of registered dumpables and buffers
+        dumpManager.registerBuffer("first-buffer", buffer1)
+        dumpManager.registerBuffer("scnd-buffer", buffer2)
+
+        // WHEN a dumpable is dumped by a suffix that matches multiple options
+        val args = arrayOf("buffer", "--tail", "14")
+        dumpHandler.dump(fd, pw, args)
+
+        // THEN the matching buffer with the shorter name is dumped
+        verify(buffer1, never()).dump(any(), anyInt())
+        verify(buffer2).dump(pw, tailLength = 14)
+    }
+
+    @Test
+    fun testDumpTarget_selectsShortestNamedMatch_dumpable() {
+        // GIVEN a variety of registered dumpables and buffers
+        dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
+        dumpManager.registerCriticalDumpable("dumpable2", dumpable2)
+        dumpManager.registerCriticalDumpable("dumpable3", dumpable3)
+        dumpManager.registerBuffer("big-buffer1", buffer1)
+        dumpManager.registerBuffer("big-buffer2", buffer2)
+
+        // WHEN a dumpable is dumped by a suffix that matches multiple options
+        val args = arrayOf("2")
+        dumpHandler.dump(fd, pw, args)
+
+        // THEN the matching dumpable with the shorter name is dumped
+        verify(dumpable1, never()).dump(any(), any())
+        verify(dumpable2).dump(pw, args)
+        verify(dumpable3, never()).dump(any(), any())
+        verify(buffer1, never()).dump(any(), anyInt())
+        verify(buffer2, never()).dump(any(), anyInt())
+    }
+
+    @Test
+    fun testDumpTarget_selectsShortestNamedMatch_buffer() {
+        // GIVEN a variety of registered dumpables and buffers
+        dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
+        dumpManager.registerCriticalDumpable("dumpable2", dumpable2)
+        dumpManager.registerCriticalDumpable("dumpable3", dumpable3)
+        dumpManager.registerBuffer("buffer1", buffer1)
+        dumpManager.registerBuffer("buffer2", buffer2)
+
+        // WHEN a dumpable is dumped by a suffix that matches multiple options
+        val args = arrayOf("2", "--tail", "14")
+        dumpHandler.dump(fd, pw, args)
+
+        // THEN the matching buffer with the shorter name is dumped
+        verify(dumpable1, never()).dump(any(), any())
+        verify(dumpable2, never()).dump(any(), any())
+        verify(dumpable3, never()).dump(any(), any())
+        verify(buffer1, never()).dump(any(), anyInt())
+        verify(buffer2).dump(pw, tailLength = 14)
+    }
+
+    @Test
+    fun testDumpTarget_selectsTheAlphabeticallyFirstShortestMatch_dumpable() {
+        // GIVEN a variety of registered dumpables and buffers
+        dumpManager.registerCriticalDumpable("d1x", dumpable1)
+        dumpManager.registerCriticalDumpable("d2x", dumpable2)
+        dumpManager.registerCriticalDumpable("a3x", dumpable3)
+        dumpManager.registerBuffer("ab1x", buffer1)
+        dumpManager.registerBuffer("b2x", buffer2)
+
+        // WHEN a dumpable is dumped by a suffix that matches multiple options
+        val args = arrayOf("x")
+        dumpHandler.dump(fd, pw, args)
+
+        // THEN the alphabetically first dumpable/buffer (of the 3 letter names) is dumped
+        verify(dumpable1, never()).dump(any(), any())
+        verify(dumpable2, never()).dump(any(), any())
+        verify(dumpable3).dump(pw, args)
+        verify(buffer1, never()).dump(any(), anyInt())
+        verify(buffer2, never()).dump(any(), anyInt())
+    }
+
+    @Test
+    fun testDumpTarget_selectsTheAlphabeticallyFirstShortestMatch_buffer() {
+        // GIVEN a variety of registered dumpables and buffers
+        dumpManager.registerCriticalDumpable("d1x", dumpable1)
+        dumpManager.registerCriticalDumpable("d2x", dumpable2)
+        dumpManager.registerCriticalDumpable("az1x", dumpable3)
+        dumpManager.registerBuffer("b1x", buffer1)
+        dumpManager.registerBuffer("b2x", buffer2)
+
+        // WHEN a dumpable is dumped by a suffix that matches multiple options
+        val args = arrayOf("x", "--tail", "14")
+        dumpHandler.dump(fd, pw, args)
+
+        // THEN the alphabetically first dumpable/buffer (of the 3 letter names) is dumped
+        verify(dumpable1, never()).dump(any(), any())
+        verify(dumpable2, never()).dump(any(), any())
+        verify(dumpable3, never()).dump(any(), any())
+        verify(buffer1).dump(pw, tailLength = 14)
+        verify(buffer2, never()).dump(any(), anyInt())
+    }
+
+
     private class EmptyCoreStartable : CoreStartable {
         override fun start() {}
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dump/DumpManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/dump/DumpManagerTest.kt
index 02555cf..6d5226f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dump/DumpManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/dump/DumpManagerTest.kt
@@ -20,21 +20,17 @@
 import com.android.systemui.Dumpable
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.util.mockito.any
-import java.io.PrintWriter
+import com.android.systemui.log.table.TableLogBuffer
+import com.google.common.truth.Truth.assertThat
+import org.junit.Assert.assertThrows
 import org.junit.Before
 import org.junit.Test
 import org.mockito.Mock
-import org.mockito.Mockito.anyInt
-import org.mockito.Mockito.never
-import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
 @SmallTest
 class DumpManagerTest : SysuiTestCase() {
 
-    @Mock private lateinit var pw: PrintWriter
-
     @Mock private lateinit var dumpable1: Dumpable
     @Mock private lateinit var dumpable2: Dumpable
     @Mock private lateinit var dumpable3: Dumpable
@@ -42,6 +38,9 @@
     @Mock private lateinit var buffer1: LogBuffer
     @Mock private lateinit var buffer2: LogBuffer
 
+    @Mock private lateinit var table1: TableLogBuffer
+    @Mock private lateinit var table2: TableLogBuffer
+
     private val dumpManager = DumpManager()
 
     @Before
@@ -50,276 +49,144 @@
     }
 
     @Test
-    fun testDumpTarget_dumpable() {
-        // GIVEN a variety of registered dumpables and buffers
-        dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
-        dumpManager.registerCriticalDumpable("dumpable2", dumpable2)
-        dumpManager.registerCriticalDumpable("dumpable3", dumpable3)
-        dumpManager.registerBuffer("buffer1", buffer1)
-        dumpManager.registerBuffer("buffer2", buffer2)
-
-        // WHEN a dumpable is dumped explicitly
-        val args = arrayOf<String>()
-        dumpManager.dumpTarget("dumpable2", pw, args, tailLength = 0)
-
-        // THEN only the requested one has their dump() method called
-        verify(dumpable1, never()).dump(any(), any())
-        verify(dumpable2).dump(pw, args)
-        verify(dumpable3, never()).dump(any(), any())
-        verify(buffer1, never()).dump(any(), anyInt())
-        verify(buffer2, never()).dump(any(), anyInt())
-    }
-
-    @Test
-    fun testDumpTarget_buffer() {
-        // GIVEN a variety of registered dumpables and buffers
-        dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
-        dumpManager.registerCriticalDumpable("dumpable2", dumpable2)
-        dumpManager.registerCriticalDumpable("dumpable3", dumpable3)
-        dumpManager.registerBuffer("buffer1", buffer1)
-        dumpManager.registerBuffer("buffer2", buffer2)
-
-        // WHEN a buffer is dumped explicitly
-        val args = arrayOf<String>()
-        dumpManager.dumpTarget("buffer1", pw, args, tailLength = 14)
-
-        // THEN only the requested one has their dump() method called
-        verify(dumpable1, never()).dump(any(), any())
-        verify(dumpable2, never()).dump(any(), any())
-        verify(dumpable3, never()).dump(any(), any())
-        verify(buffer1).dump(pw, tailLength = 14)
-        verify(buffer2, never()).dump(any(), anyInt())
-    }
-
-    @Test
-    fun testDumpableMatchingIsBasedOnEndOfTag() {
-        // GIVEN a dumpable registered to the manager
-        dumpManager.registerCriticalDumpable("com.android.foo.bar.dumpable1", dumpable1)
-
-        // WHEN that module is dumped
-        val args = arrayOf<String>()
-        dumpManager.dumpTarget("dumpable1", pw, arrayOf(), tailLength = 14)
-
-        // THEN its dump() method is called
-        verify(dumpable1).dump(pw, args)
-    }
-
-    @Test
-    fun testDumpTarget_selectsShortestNamedDumpable() {
-        // GIVEN a variety of registered dumpables and buffers
-        dumpManager.registerCriticalDumpable("first-dumpable", dumpable1)
-        dumpManager.registerCriticalDumpable("scnd-dumpable", dumpable2)
-        dumpManager.registerCriticalDumpable("third-dumpable", dumpable3)
-
-        // WHEN a dumpable is dumped by a suffix that matches multiple options
-        val args = arrayOf<String>()
-        dumpManager.dumpTarget("dumpable", pw, args, tailLength = 0)
-
-        // THEN the matching dumpable with the shorter name is dumped
-        verify(dumpable1, never()).dump(any(), any())
-        verify(dumpable2).dump(pw, args)
-        verify(dumpable3, never()).dump(any(), any())
-    }
-
-    @Test
-    fun testDumpTarget_selectsShortestNamedBuffer() {
-        // GIVEN a variety of registered dumpables and buffers
-        dumpManager.registerBuffer("first-buffer", buffer1)
-        dumpManager.registerBuffer("scnd-buffer", buffer2)
-
-        // WHEN a dumpable is dumped by a suffix that matches multiple options
-        val args = arrayOf<String>()
-        dumpManager.dumpTarget("buffer", pw, args, tailLength = 14)
-
-        // THEN the matching buffer with the shorter name is dumped
-        verify(buffer1, never()).dump(any(), anyInt())
-        verify(buffer2).dump(pw, tailLength = 14)
-    }
-
-    @Test
-    fun testDumpTarget_selectsShortestNamedMatch_dumpable() {
-        // GIVEN a variety of registered dumpables and buffers
-        dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
-        dumpManager.registerCriticalDumpable("dumpable2", dumpable2)
-        dumpManager.registerCriticalDumpable("dumpable3", dumpable3)
-        dumpManager.registerBuffer("big-buffer1", buffer1)
-        dumpManager.registerBuffer("big-buffer2", buffer2)
-
-        // WHEN a dumpable is dumped by a suffix that matches multiple options
-        val args = arrayOf<String>()
-        dumpManager.dumpTarget("2", pw, args, tailLength = 14)
-
-        // THEN the matching dumpable with the shorter name is dumped
-        verify(dumpable1, never()).dump(any(), any())
-        verify(dumpable2).dump(pw, args)
-        verify(dumpable3, never()).dump(any(), any())
-        verify(buffer1, never()).dump(any(), anyInt())
-        verify(buffer2, never()).dump(any(), anyInt())
-    }
-
-    @Test
-    fun testDumpTarget_selectsShortestNamedMatch_buffer() {
-        // GIVEN a variety of registered dumpables and buffers
-        dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
-        dumpManager.registerCriticalDumpable("dumpable2", dumpable2)
-        dumpManager.registerCriticalDumpable("dumpable3", dumpable3)
-        dumpManager.registerBuffer("buffer1", buffer1)
-        dumpManager.registerBuffer("buffer2", buffer2)
-
-        // WHEN a dumpable is dumped by a suffix that matches multiple options
-        val args = arrayOf<String>()
-        dumpManager.dumpTarget("2", pw, args, tailLength = 14)
-
-        // THEN the matching buffer with the shorter name is dumped
-        verify(dumpable1, never()).dump(any(), any())
-        verify(dumpable2, never()).dump(any(), any())
-        verify(dumpable3, never()).dump(any(), any())
-        verify(buffer1, never()).dump(any(), anyInt())
-        verify(buffer2).dump(pw, tailLength = 14)
-    }
-
-    @Test
-    fun testDumpTarget_selectsTheAlphabeticallyFirstShortestMatch_dumpable() {
-        // GIVEN a variety of registered dumpables and buffers
-        dumpManager.registerCriticalDumpable("d1x", dumpable1)
-        dumpManager.registerCriticalDumpable("d2x", dumpable2)
-        dumpManager.registerCriticalDumpable("a3x", dumpable3)
-        dumpManager.registerBuffer("ab1x", buffer1)
-        dumpManager.registerBuffer("b2x", buffer2)
-
-        // WHEN a dumpable is dumped by a suffix that matches multiple options
-        val args = arrayOf<String>()
-        dumpManager.dumpTarget("x", pw, args, tailLength = 14)
-
-        // THEN the alphabetically first dumpable/buffer (of the 3 letter names) is dumped
-        verify(dumpable1, never()).dump(any(), any())
-        verify(dumpable2, never()).dump(any(), any())
-        verify(dumpable3).dump(pw, args)
-        verify(buffer1, never()).dump(any(), anyInt())
-        verify(buffer2, never()).dump(any(), anyInt())
-    }
-
-    @Test
-    fun testDumpTarget_selectsTheAlphabeticallyFirstShortestMatch_buffer() {
-        // GIVEN a variety of registered dumpables and buffers
-        dumpManager.registerCriticalDumpable("d1x", dumpable1)
-        dumpManager.registerCriticalDumpable("d2x", dumpable2)
-        dumpManager.registerCriticalDumpable("az1x", dumpable3)
-        dumpManager.registerBuffer("b1x", buffer1)
-        dumpManager.registerBuffer("b2x", buffer2)
-
-        // WHEN a dumpable is dumped by a suffix that matches multiple options
-        val args = arrayOf<String>()
-        dumpManager.dumpTarget("x", pw, args, tailLength = 14)
-
-        // THEN the alphabetically first dumpable/buffer (of the 3 letter names) is dumped
-        verify(dumpable1, never()).dump(any(), any())
-        verify(dumpable2, never()).dump(any(), any())
-        verify(dumpable3, never()).dump(any(), any())
-        verify(buffer1).dump(pw, tailLength = 14)
-        verify(buffer2, never()).dump(any(), anyInt())
-    }
-
-    @Test
-    fun testDumpDumpables() {
-        // GIVEN a variety of registered dumpables and buffers
-        dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
-        dumpManager.registerCriticalDumpable("dumpable2", dumpable2)
-        dumpManager.registerNormalDumpable("dumpable3", dumpable3)
-        dumpManager.registerBuffer("buffer1", buffer1)
-        dumpManager.registerBuffer("buffer2", buffer2)
-
-        // WHEN a dumpable dump is requested
-        val args = arrayOf<String>()
-        dumpManager.dumpDumpables(pw, args)
-
-        // THEN all dumpables are dumped (both critical and normal) (and no dumpables)
-        verify(dumpable1).dump(pw, args)
-        verify(dumpable2).dump(pw, args)
-        verify(dumpable3).dump(pw, args)
-        verify(buffer1, never()).dump(any(), anyInt())
-        verify(buffer2, never()).dump(any(), anyInt())
-    }
-
-    @Test
-    fun testDumpBuffers() {
-        // GIVEN a variety of registered dumpables and buffers
-        dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
-        dumpManager.registerCriticalDumpable("dumpable2", dumpable2)
-        dumpManager.registerNormalDumpable("dumpable3", dumpable3)
-        dumpManager.registerBuffer("buffer1", buffer1)
-        dumpManager.registerBuffer("buffer2", buffer2)
-
-        // WHEN a buffer dump is requested
-        dumpManager.dumpBuffers(pw, tailLength = 1)
-
-        // THEN all buffers are dumped (and no dumpables)
-        verify(dumpable1, never()).dump(any(), any())
-        verify(dumpable2, never()).dump(any(), any())
-        verify(dumpable3, never()).dump(any(), any())
-        verify(buffer1).dump(pw, tailLength = 1)
-        verify(buffer2).dump(pw, tailLength = 1)
-    }
-
-    @Test
-    fun testCriticalDump() {
-        // GIVEN a variety of registered dumpables and buffers
-        dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
-        dumpManager.registerCriticalDumpable("dumpable2", dumpable2)
-        dumpManager.registerNormalDumpable("dumpable3", dumpable3)
-        dumpManager.registerBuffer("buffer1", buffer1)
-        dumpManager.registerBuffer("buffer2", buffer2)
-
-        // WHEN a critical dump is requested
-        val args = arrayOf<String>()
-        dumpManager.dumpCritical(pw, args)
-
-        // THEN only critical modules are dumped (and no buffers)
-        verify(dumpable1).dump(pw, args)
-        verify(dumpable2).dump(pw, args)
-        verify(dumpable3, never()).dump(any(), any())
-        verify(buffer1, never()).dump(any(), anyInt())
-        verify(buffer2, never()).dump(any(), anyInt())
-    }
-
-    @Test
-    fun testNormalDump() {
-        // GIVEN a variety of registered dumpables and buffers
-        dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
-        dumpManager.registerCriticalDumpable("dumpable2", dumpable2)
-        dumpManager.registerNormalDumpable("dumpable3", dumpable3)
-        dumpManager.registerBuffer("buffer1", buffer1)
-        dumpManager.registerBuffer("buffer2", buffer2)
-
-        // WHEN a normal dump is requested
-        val args = arrayOf<String>()
-        dumpManager.dumpNormal(pw, args, tailLength = 2)
-
-        // THEN the normal module and all buffers are dumped
-        verify(dumpable1, never()).dump(any(), any())
-        verify(dumpable2, never()).dump(any(), any())
-        verify(dumpable3).dump(pw, args)
-        verify(buffer1).dump(pw, tailLength = 2)
-        verify(buffer2).dump(pw, tailLength = 2)
-    }
-
-    @Test
-    fun testUnregister() {
-        // GIVEN a variety of registered dumpables and buffers
+    fun testRegisterUnregister_dumpables() {
+        // GIVEN a variety of registered dumpables
         dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
         dumpManager.registerCriticalDumpable("dumpable2", dumpable2)
         dumpManager.registerNormalDumpable("dumpable3", dumpable3)
 
+        // WHEN the collection is requested
+        var dumpables = dumpManager.getDumpables().map { it.dumpable }
+
+        // THEN it contains the registered entries
+        assertThat(dumpables).containsExactly(dumpable1, dumpable2, dumpable3)
+
+        // WHEN the dumpables are unregistered
         dumpManager.unregisterDumpable("dumpable2")
         dumpManager.unregisterDumpable("dumpable3")
 
-        // WHEN a dumpables dump is requested
-        val args = arrayOf<String>()
-        dumpManager.dumpDumpables(pw, args)
+        // WHEN the dumpable collection is requests
+        dumpables = dumpManager.getDumpables().map { it.dumpable }
 
-        // THEN the unregistered dumpables (both normal and critical) are not dumped
-        verify(dumpable1).dump(pw, args)
-        verify(dumpable2, never()).dump(any(), any())
-        verify(dumpable3, never()).dump(any(), any())
+        // THEN it contains only the currently-registered entry
+        assertThat(dumpables).containsExactly(dumpable1)
+    }
+
+    @Test
+    fun testRegister_buffers() {
+        // GIVEN a set of registered buffers
+        dumpManager.registerBuffer("buffer1", buffer1)
+        dumpManager.registerBuffer("buffer2", buffer2)
+
+        // WHEN the collection is requested
+        val dumpables = dumpManager.getLogBuffers().map { it.buffer }
+
+        // THEN it contains the registered entries
+        assertThat(dumpables).containsExactly(buffer1, buffer2)
+    }
+
+    @Test
+    fun testRegister_tableLogBuffers() {
+        // GIVEN a set of registered buffers
+        dumpManager.registerTableLogBuffer("table1", table1)
+        dumpManager.registerTableLogBuffer("table2", table2)
+
+        // WHEN the collection is requested
+        val tables = dumpManager.getTableLogBuffers().map { it.table }
+
+        // THEN it contains the registered entries
+        assertThat(tables).containsExactly(table1, table2)
+    }
+
+    @Test
+    fun registerDumpable_throwsWhenNameCannotBeAssigned() {
+        // GIVEN dumpable1 and buffer1 and table1 are registered
+        dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
+        dumpManager.registerBuffer("buffer1", buffer1)
+        dumpManager.registerTableLogBuffer("table1", table1)
+
+        // THEN an exception is thrown when trying to re-register a new dumpable under the same key
+        assertThrows(IllegalArgumentException::class.java) {
+            dumpManager.registerCriticalDumpable("dumpable1", dumpable2)
+        }
+        assertThrows(IllegalArgumentException::class.java) {
+            dumpManager.registerBuffer("buffer1", buffer2)
+        }
+        assertThrows(IllegalArgumentException::class.java) {
+            dumpManager.registerTableLogBuffer("table1", table2)
+        }
+    }
+
+    @Test
+    fun registerDumpable_doesNotThrowWhenReRegistering() {
+        // GIVEN dumpable1 and buffer1 are registered
+        dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
+        dumpManager.registerBuffer("buffer1", buffer1)
+
+        // THEN no exception is thrown when trying to re-register a new dumpable under the same key
+        dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
+        dumpManager.registerBuffer("buffer1", buffer1)
+
+        // No exception thrown
+    }
+
+    @Test
+    fun getDumpables_returnsSafeCollection() {
+        // GIVEN a variety of registered dumpables
+        dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
+        dumpManager.registerCriticalDumpable("dumpable2", dumpable2)
+        dumpManager.registerNormalDumpable("dumpable3", dumpable3)
+
+        // WHEN the collection is retrieved
+        val dumpables = dumpManager.getDumpables()
+
+        // WHEN the collection changes from underneath
+        dumpManager.unregisterDumpable("dumpable1")
+        dumpManager.unregisterDumpable("dumpable2")
+        dumpManager.unregisterDumpable("dumpable3")
+
+        // THEN new collections are empty
+        assertThat(dumpManager.getDumpables()).isEmpty()
+
+        // AND the collection is still safe to use
+        assertThat(dumpables).hasSize(3)
+    }
+
+    @Test
+    fun getBuffers_returnsSafeCollection() {
+        // GIVEN a set of registered buffers
+        dumpManager.registerBuffer("buffer1", buffer1)
+        dumpManager.registerBuffer("buffer2", buffer2)
+
+        // WHEN the collection is requested
+        val buffers = dumpManager.getLogBuffers()
+
+        // WHEN the collection changes
+        dumpManager.registerBuffer("buffer3", buffer1)
+
+        // THEN the new entry is represented
+        assertThat(dumpManager.getLogBuffers()).hasSize(3)
+
+        // AND the previous collection is unchanged
+        assertThat(buffers).hasSize(2)
+    }
+
+    @Test
+    fun getTableBuffers_returnsSafeCollection() {
+        // GIVEN a set of registered buffers
+        dumpManager.registerTableLogBuffer("table1", table1)
+        dumpManager.registerTableLogBuffer("table2", table2)
+
+        // WHEN the collection is requested
+        val tables = dumpManager.getTableLogBuffers()
+
+        // WHEN the collection changes
+        dumpManager.registerTableLogBuffer("table3", table1)
+
+        // THEN the new entry is represented
+        assertThat(dumpManager.getTableLogBuffers()).hasSize(3)
+
+        // AND the previous collection is unchanged
+        assertThat(tables).hasSize(2)
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dump/LogBufferHelper.kt b/packages/SystemUI/tests/src/com/android/systemui/dump/LogBufferHelper.kt
index bd029a7..a341ca3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dump/LogBufferHelper.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/dump/LogBufferHelper.kt
@@ -17,7 +17,7 @@
 package com.android.systemui.dump
 
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import com.android.systemui.log.LogcatEchoTracker
 
 /**
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dump/LogEulogizerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/dump/LogEulogizerTest.kt
index cb38846..3ff7202 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dump/LogEulogizerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/dump/LogEulogizerTest.kt
@@ -18,20 +18,14 @@
 
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.DumpHandler.Companion.dump
+import com.android.systemui.log.LogBuffer
 import com.android.systemui.util.io.FakeBasicFileAttributes
 import com.android.systemui.util.io.Files
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.time.FakeSystemClock
-import org.junit.Assert.assertEquals
-import org.junit.Assert.assertTrue
-import org.junit.Before
-import org.junit.Test
-import org.mockito.Mock
-import org.mockito.Mockito
-import org.mockito.Mockito.never
-import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
 import java.io.BufferedWriter
 import java.io.ByteArrayOutputStream
 import java.io.IOException
@@ -42,17 +36,29 @@
 import java.nio.file.Paths
 import java.nio.file.attribute.BasicFileAttributes
 import java.util.Arrays
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
 
 @SmallTest
 class LogEulogizerTest : SysuiTestCase() {
 
     lateinit var eulogizer: LogBufferEulogizer
 
-    @Mock
-    lateinit var dumpManager: DumpManager
+    @Mock lateinit var dumpManager: DumpManager
+    @Mock lateinit var logBuffer1: LogBuffer
+    lateinit var logBufferEntry1: DumpsysEntry.LogBufferEntry
+    @Mock lateinit var logBuffer2: LogBuffer
+    lateinit var logBufferEntry2: DumpsysEntry.LogBufferEntry
 
-    @Mock
-    lateinit var files: Files
+    @Mock lateinit var files: Files
 
     private val clock = FakeSystemClock()
 
@@ -67,37 +73,47 @@
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
+        logBufferEntry1 = DumpsysEntry.LogBufferEntry(logBuffer1, "logbuffer1")
+        logBufferEntry2 = DumpsysEntry.LogBufferEntry(logBuffer2, "logbuffer2")
 
-        eulogizer =
-                LogBufferEulogizer(dumpManager, clock, files, path, MIN_WRITE_GAP, MAX_READ_AGE)
+        eulogizer = LogBufferEulogizer(dumpManager, clock, files, path, MIN_WRITE_GAP, MAX_READ_AGE)
 
         Mockito.`when`(files.newBufferedWriter(eq(path), any(OpenOption::class.java)))
-                .thenReturn(fileWriter)
+            .thenReturn(fileWriter)
 
         Mockito.`when`(
-                files.readAttributes(eq(path),
-                eq(BasicFileAttributes::class.java),
-                any(LinkOption::class.java))
-        ).thenReturn(fileAttrs)
+                files.readAttributes(
+                    eq(path),
+                    eq(BasicFileAttributes::class.java),
+                    any(LinkOption::class.java)
+                )
+            )
+            .thenReturn(fileAttrs)
 
         Mockito.`when`(files.lines(eq(path))).thenReturn(Arrays.stream(FAKE_LINES))
+
+        whenever(dumpManager.getLogBuffers()).thenReturn(listOf(logBufferEntry1, logBufferEntry2))
     }
 
     @Test
     fun testFileIsCreated() {
         // GIVEN that the log file doesn't already exist
         Mockito.`when`(
-                files.readAttributes(eq(path),
-                        eq(BasicFileAttributes::class.java),
-                        any(LinkOption::class.java))
-        ).thenThrow(IOException("File not found"))
+                files.readAttributes(
+                    eq(path),
+                    eq(BasicFileAttributes::class.java),
+                    any(LinkOption::class.java)
+                )
+            )
+            .thenThrow(IOException("File not found"))
 
         // WHEN .record() is called
         val exception = RuntimeException("Something bad happened")
         assertEquals(exception, eulogizer.record(exception))
 
         // THEN the buffers are dumped to the file
-        verify(dumpManager).dumpBuffers(any(PrintWriter::class.java), Mockito.anyInt())
+        verify(logBuffer1).dump(any(PrintWriter::class.java), anyInt())
+        verify(logBuffer2).dump(any(PrintWriter::class.java), anyInt())
         assertTrue(fileStream.toString().isNotEmpty())
     }
 
@@ -111,7 +127,8 @@
         assertEquals(exception, eulogizer.record(exception))
 
         // THEN the buffers are dumped to the file
-        verify(dumpManager).dumpBuffers(any(PrintWriter::class.java), Mockito.anyInt())
+        verify(logBuffer1).dump(any(PrintWriter::class.java), anyInt())
+        verify(logBuffer2).dump(any(PrintWriter::class.java), anyInt())
         assertTrue(fileStream.toString().isNotEmpty())
     }
 
@@ -125,7 +142,8 @@
         assertEquals(exception, eulogizer.record(exception))
 
         // THEN the file isn't written to
-        verify(dumpManager, never()).dumpBuffers(any(PrintWriter::class.java), Mockito.anyInt())
+        verify(logBuffer1, never()).dump(any(PrintWriter::class.java), anyInt())
+        verify(logBuffer2, never()).dump(any(PrintWriter::class.java), anyInt())
         assertTrue(fileStream.toString().isEmpty())
     }
 
@@ -161,9 +179,4 @@
 private const val MIN_WRITE_GAP = 10L
 private const val MAX_READ_AGE = 100L
 
-private val FAKE_LINES =
-        arrayOf(
-                "First line",
-                "Second line",
-                "Third line"
-        )
+private val FAKE_LINES = arrayOf("First line", "Second line", "Third line")
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
index c9ee1e8..6aa5a00 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
@@ -67,6 +67,7 @@
 import com.android.systemui.plugins.GlobalActions;
 import com.android.systemui.settings.UserContextProvider;
 import com.android.systemui.settings.UserTracker;
+import com.android.systemui.shade.ShadeController;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.VibratorHelper;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
@@ -128,6 +129,7 @@
     @Mock private UserContextProvider mUserContextProvider;
     @Mock private VibratorHelper mVibratorHelper;
     @Mock private CentralSurfaces mCentralSurfaces;
+    @Mock private ShadeController mShadeController;
     @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     @Mock private DialogLaunchAnimator mDialogLaunchAnimator;
     @Mock private OnBackInvokedDispatcher mOnBackInvokedDispatcher;
@@ -177,6 +179,7 @@
                 mHandler,
                 mPackageManager,
                 Optional.of(mCentralSurfaces),
+                mShadeController,
                 mKeyguardUpdateMonitor,
                 mDialogLaunchAnimator);
         mGlobalActionsDialogLite.setZeroDialogPressDelayForTesting();
@@ -317,7 +320,7 @@
         MotionEvent end = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 500, 0);
         gestureListener.onFling(start, end, 0, 1000);
         verifyLogPosted(GlobalActionsDialogLite.GlobalActionsEvent.GA_CLOSE_TAP_OUTSIDE);
-        verify(mCentralSurfaces).animateExpandSettingsPanel(null);
+        verify(mShadeController).animateExpandQs();
     }
 
     @Test
@@ -341,7 +344,7 @@
         MotionEvent end = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 500, 0);
         gestureListener.onFling(start, end, 0, 1000);
         verifyLogPosted(GlobalActionsDialogLite.GlobalActionsEvent.GA_CLOSE_TAP_OUTSIDE);
-        verify(mCentralSurfaces).animateExpandNotificationsPanel();
+        verify(mShadeController).animateExpandShade();
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/ShutdownUiTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/ShutdownUiTest.java
new file mode 100644
index 0000000..9d9b263
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/ShutdownUiTest.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
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.globalactions;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNull;
+
+import android.os.PowerManager;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.R;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.BlurUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class ShutdownUiTest extends SysuiTestCase {
+
+    ShutdownUi mShutdownUi;
+    @Mock
+    BlurUtils mBlurUtils;
+
+    @Before
+    public void setUp() throws Exception {
+        mShutdownUi = new ShutdownUi(getContext(), mBlurUtils);
+    }
+
+    @Test
+    public void getRebootMessage_update() {
+        int messageId = mShutdownUi.getRebootMessage(true, PowerManager.REBOOT_RECOVERY_UPDATE);
+        assertEquals(messageId, R.string.reboot_to_update_reboot);
+    }
+
+    @Test
+    public void getRebootMessage_rebootDefault() {
+        int messageId = mShutdownUi.getRebootMessage(true, "anything-else");
+        assertEquals(messageId, R.string.reboot_to_reset_message);
+    }
+
+    @Test
+    public void getRebootMessage_shutdown() {
+        int messageId = mShutdownUi.getRebootMessage(false, "anything-else");
+        assertEquals(messageId, R.string.shutdown_progress);
+    }
+
+    @Test
+    public void getReasonMessage_update() {
+        String message = mShutdownUi.getReasonMessage(PowerManager.REBOOT_RECOVERY_UPDATE);
+        assertEquals(message, mContext.getString(R.string.reboot_to_update_title));
+    }
+
+    @Test
+    public void getReasonMessage_rebootDefault() {
+        String message = mShutdownUi.getReasonMessage(PowerManager.REBOOT_RECOVERY);
+        assertEquals(message, mContext.getString(R.string.reboot_to_reset_title));
+    }
+
+    @Test
+    public void getRebootMessage_defaultToNone() {
+        String message = mShutdownUi.getReasonMessage("anything-else");
+        assertNull(message);
+    }
+}
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 cdc99af3..e73d580 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
@@ -34,6 +34,8 @@
 import com.android.systemui.SystemUIAppComponentFactoryBase
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.DialogLaunchAnimator
+import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
+import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
 import com.android.systemui.dock.DockManagerFake
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags
@@ -43,7 +45,6 @@
 import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLocalUserSelectionManager
 import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceRemoteUserSelectionManager
 import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
-import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
@@ -174,7 +175,6 @@
             )
         val featureFlags =
             FakeFeatureFlags().apply {
-                set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, true)
                 set(Flags.LOCKSCREEN_CUSTOM_CLOCKS, true)
                 set(Flags.REVAMPED_WALLPAPER_UI, true)
                 set(Flags.WALLPAPER_FULLSCREEN_PREVIEW, true)
@@ -188,8 +188,8 @@
                         commandQueue = commandQueue,
                         featureFlags = featureFlags,
                         bouncerRepository = FakeKeyguardBouncerRepository(),
+                        configurationRepository = FakeConfigurationRepository(),
                     ),
-                registry = mock(),
                 lockPatternUtils = lockPatternUtils,
                 keyguardStateController = keyguardStateController,
                 userTracker = userTracker,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
index 729a1cc..ce8028c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
@@ -19,6 +19,7 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyString;
 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.reset;
@@ -31,6 +32,7 @@
 import android.media.MediaMetadata;
 import android.media.session.PlaybackState;
 import android.net.Uri;
+import android.os.Handler;
 import android.provider.Settings;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
@@ -166,6 +168,7 @@
 
     @Test
     public void updatesClock() {
+        clearInvocations(mContentResolver);
         mProvider.mKeyguardUpdateMonitorCallback.onTimeChanged();
         TestableLooper.get(this).processAllMessages();
         verify(mContentResolver).notifyChange(eq(mProvider.getUri()), eq(null));
@@ -217,11 +220,13 @@
         reset(mContentResolver);
         mProvider.onPrimaryMetadataOrStateChanged(mock(MediaMetadata.class),
                 PlaybackState.STATE_PLAYING);
+        TestableLooper.get(this).processAllMessages();
         verify(mContentResolver).notifyChange(eq(mProvider.getUri()), eq(null));
 
         // Hides after waking up
         reset(mContentResolver);
         mProvider.onDozingChanged(false);
+        TestableLooper.get(this).processAllMessages();
         verify(mContentResolver).notifyChange(eq(mProvider.getUri()), eq(null));
     }
 
@@ -231,6 +236,7 @@
         mProvider.onPrimaryMetadataOrStateChanged(mock(MediaMetadata.class),
                 PlaybackState.STATE_PLAYING);
         reset(mContentResolver);
+        TestableLooper.get(this).processAllMessages();
         // Show media when dozing
         mProvider.onDozingChanged(true);
         verify(mContentResolver).notifyChange(eq(mProvider.getUri()), eq(null));
@@ -272,6 +278,8 @@
             mMediaManager = KeyguardSliceProviderTest.this.mNotificationMediaManager;
             mKeyguardUpdateMonitor = KeyguardSliceProviderTest.this.mKeyguardUpdateMonitor;
             mUserTracker = KeyguardSliceProviderTest.this.mUserTracker;
+            mBgHandler =
+                    new Handler(TestableLooper.get(KeyguardSliceProviderTest.this).getLooper());
         }
 
         @Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt
index 2230841..9a2936e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt
@@ -385,8 +385,6 @@
         // We expect that we've set the surface behind to alpha = 0f since we're not interactive.
         assertEquals(0f, params.alpha)
         assertTrue(params.matrix.isIdentity)
-        assertEquals("Wallpaper surface was expected to have opacity 0",
-                0f, captorWp.getLastValue().alpha)
 
         verifyNoMoreInteractions(surfaceTransactionApplier)
     }
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 7a501a8..00f67a3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -16,33 +16,49 @@
 
 package com.android.systemui.keyguard;
 
+import static android.os.PowerManager.WAKE_REASON_WAKE_MOTION;
+import static android.provider.Settings.Secure.LOCK_SCREEN_LOCK_AFTER_TIMEOUT;
 import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_GOING_AWAY;
+import static android.view.WindowManagerPolicyConstants.OFF_BECAUSE_OF_TIMEOUT;
 import static android.view.WindowManagerPolicyConstants.OFF_BECAUSE_OF_USER;
 
 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_NON_STRONG_BIOMETRICS_TIMEOUT;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
+import static com.android.systemui.keyguard.KeyguardViewMediator.DELAYED_KEYGUARD_ACTION;
+import static com.android.systemui.keyguard.KeyguardViewMediator.KEYGUARD_LOCK_AFTER_DELAY_DEFAULT;
 import static com.android.systemui.keyguard.KeyguardViewMediator.REBOOT_MAINLINE_UPDATE;
 import static com.android.systemui.keyguard.KeyguardViewMediator.SYS_BOOT_REASON_PROP;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 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.Mockito.atLeast;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.app.AlarmManager;
 import android.app.IActivityManager;
+import android.app.IActivityTaskManager;
+import android.app.PendingIntent;
 import android.app.admin.DevicePolicyManager;
 import android.app.trust.TrustManager;
+import android.content.Context;
 import android.os.PowerManager;
 import android.os.PowerManager.WakeLock;
+import android.os.RemoteException;
 import android.telephony.TelephonyManager;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
@@ -62,6 +78,7 @@
 import com.android.keyguard.KeyguardSecurityView;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.keyguard.TestScopeProvider;
 import com.android.keyguard.mediator.ScreenOnCoordinator;
 import com.android.systemui.DejankUtils;
 import com.android.systemui.SysuiTestCase;
@@ -99,19 +116,26 @@
 import com.android.systemui.util.DeviceConfigProxy;
 import com.android.systemui.util.DeviceConfigProxyFake;
 import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.kotlin.JavaAdapter;
+import com.android.systemui.util.settings.SecureSettings;
+import com.android.systemui.util.settings.SystemSettings;
 import com.android.systemui.util.time.FakeSystemClock;
+import com.android.systemui.wallpapers.data.repository.FakeWallpaperRepository;
 import com.android.wm.shell.keyguard.KeyguardTransitions;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Captor;
+import org.mockito.InOrder;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
 import kotlinx.coroutines.CoroutineDispatcher;
 import kotlinx.coroutines.flow.Flow;
+import kotlinx.coroutines.test.TestScope;
 
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
@@ -119,6 +143,9 @@
 public class KeyguardViewMediatorTest extends SysuiTestCase {
     private KeyguardViewMediator mViewMediator;
 
+    private final TestScope mTestScope = TestScopeProvider.getTestScope();
+    private final JavaAdapter mJavaAdapter = new JavaAdapter(mTestScope.getBackgroundScope());
+
     private @Mock UserTracker mUserTracker;
     private @Mock DevicePolicyManager mDevicePolicyManager;
     private @Mock LockPatternUtils mLockPatternUtils;
@@ -150,6 +177,7 @@
     private @Mock DreamOverlayStateController mDreamOverlayStateController;
     private @Mock ActivityLaunchAnimator mActivityLaunchAnimator;
     private @Mock ScrimController mScrimController;
+    private @Mock IActivityTaskManager mActivityTaskManagerService;
     private @Mock SysuiColorExtractor mColorExtractor;
     private @Mock AuthController mAuthController;
     private @Mock ShadeExpansionStateManager mShadeExpansionStateManager;
@@ -166,21 +194,32 @@
     private @Mock CentralSurfaces mCentralSurfaces;
     private @Mock UiEventLogger mUiEventLogger;
     private @Mock SessionTracker mSessionTracker;
-    private @Mock SystemPropertiesHelper mSystemPropertiesHelper;
+    private @Mock SystemSettings mSystemSettings;
+    private @Mock SecureSettings mSecureSettings;
+    private @Mock AlarmManager mAlarmManager;
+    private FakeSystemClock mSystemClock;
+    private final FakeWallpaperRepository mWallpaperRepository = new FakeWallpaperRepository();
+
+    /** Most recent value passed to {@link KeyguardStateController#notifyKeyguardGoingAway}. */
+    private boolean mKeyguardGoingAway = false;
+
     private @Mock CoroutineDispatcher mDispatcher;
     private @Mock DreamingToLockscreenTransitionViewModel mDreamingToLockscreenTransitionViewModel;
+    private @Mock SystemPropertiesHelper mSystemPropertiesHelper;
 
     private FakeFeatureFlags mFeatureFlags;
+    private int mInitialUserId;
 
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
         mFalsingCollector = new FalsingCollectorFake();
-
+        mSystemClock = new FakeSystemClock();
         when(mLockPatternUtils.getDevicePolicyManager()).thenReturn(mDevicePolicyManager);
         when(mPowerManager.newWakeLock(anyInt(), any())).thenReturn(mock(WakeLock.class));
         when(mInteractionJankMonitor.begin(any(), anyInt())).thenReturn(true);
         when(mInteractionJankMonitor.end(anyInt())).thenReturn(true);
+        mContext.addMockSystemService(Context.ALARM_SERVICE, mAlarmManager);
         final ViewRootImpl testViewRoot = mock(ViewRootImpl.class);
         when(testViewRoot.getView()).thenReturn(mock(View.class));
         when(mStatusBarKeyguardViewManager.getViewRootImpl()).thenReturn(testViewRoot);
@@ -199,7 +238,42 @@
 
         DejankUtils.setImmediate(true);
 
+        // Keep track of what we told KeyguardStateController about whether we're going away or
+        // not.
+        mKeyguardGoingAway = false;
+        doAnswer(invocation -> {
+            mKeyguardGoingAway = invocation.getArgument(0);
+            return null;
+        }).when(mKeyguardStateController).notifyKeyguardGoingAway(anyBoolean());
+
         createAndStartViewMediator();
+        mInitialUserId = KeyguardUpdateMonitor.getCurrentUser();
+    }
+
+    @After
+    public void teardown() {
+        KeyguardUpdateMonitor.setCurrentUser(mInitialUserId);
+    }
+
+    /**
+     * After each test, verify that System UI's going away/showing state matches the most recent
+     * calls we made to ATMS.
+     *
+     * This will help us catch showing and going away state mismatch issues.
+     */
+    @After
+    public void assertATMSAndKeyguardViewMediatorStatesMatch() {
+        try {
+            if (mKeyguardGoingAway) {
+                assertATMSKeyguardGoingAway();
+            } else {
+                assertATMSLockScreenShowing(mViewMediator.isShowing());
+            }
+
+        } catch (Exception e) {
+            // Just so we don't have to add the exception signature to every test.
+            fail();
+        }
     }
 
     @Test
@@ -269,7 +343,7 @@
 
     @Test
     public void testRegisterDumpable() {
-        verify(mDumpManager).registerDumpable(KeyguardViewMediator.class.getName(), mViewMediator);
+        verify(mDumpManager).registerDumpable(mViewMediator);
         verify(mStatusBarKeyguardViewManager, never()).setKeyguardGoingAwayState(anyBoolean());
     }
 
@@ -298,6 +372,27 @@
 
     @Test
     @TestableLooper.RunWithLooper(setAsMainLooper = true)
+    public void wakeupFromDreamingWhenKeyguardHides() {
+        mViewMediator.onSystemReady();
+        TestableLooper.get(this).processAllMessages();
+
+        // Given device is dreaming
+        when(mUpdateMonitor.isDreaming()).thenReturn(true);
+
+        // When keyguard is going away
+        mKeyguardStateController.notifyKeyguardGoingAway(true);
+
+        // And keyguard is disabled which will call #handleHide
+        mViewMediator.setKeyguardEnabled(false);
+        TestableLooper.get(this).processAllMessages();
+
+        // Then dream should wake up
+        verify(mPowerManager).wakeUp(anyLong(), anyInt(),
+                eq("com.android.systemui:UNLOCK_DREAMING"));
+    }
+
+    @Test
+    @TestableLooper.RunWithLooper(setAsMainLooper = true)
     public void restoreBouncerWhenSimLockedAndKeyguardIsGoingAway() {
         // When showing and provisioned
         mViewMediator.onSystemReady();
@@ -440,6 +535,49 @@
     }
 
     @Test
+    public void lockAfterScreenTimeoutUsesValueFromSettings() {
+        int currentUserId = 99;
+        int userSpecificTimeout = 5999;
+        KeyguardUpdateMonitor.setCurrentUser(currentUserId);
+
+        when(mKeyguardStateController.isKeyguardGoingAway()).thenReturn(false);
+        when(mDevicePolicyManager.getMaximumTimeToLock(null, currentUserId)).thenReturn(0L);
+        when(mSecureSettings.getIntForUser(LOCK_SCREEN_LOCK_AFTER_TIMEOUT,
+                KEYGUARD_LOCK_AFTER_DELAY_DEFAULT, currentUserId)).thenReturn(userSpecificTimeout);
+        mSystemClock.setElapsedRealtime(0L);
+        ArgumentCaptor<PendingIntent> pendingIntent = ArgumentCaptor.forClass(PendingIntent.class);
+
+        mViewMediator.onStartedGoingToSleep(OFF_BECAUSE_OF_TIMEOUT);
+
+        verify(mAlarmManager).setExactAndAllowWhileIdle(eq(AlarmManager.ELAPSED_REALTIME_WAKEUP),
+                eq(Long.valueOf(userSpecificTimeout)), pendingIntent.capture());
+        assertEquals(DELAYED_KEYGUARD_ACTION, pendingIntent.getValue().getIntent().getAction());
+    }
+
+    @Test
+    public void lockAfterSpecifiedAfterDreamStarted() {
+        int currentUserId = 99;
+        int userSpecificTimeout = 5999;
+        KeyguardUpdateMonitor.setCurrentUser(currentUserId);
+
+        // set mDeviceInteractive to true
+        mViewMediator.onStartedWakingUp(WAKE_REASON_WAKE_MOTION, false);
+        mFeatureFlags.set(Flags.LOCKSCREEN_WITHOUT_SECURE_LOCK_WHEN_DREAMING, false);
+        when(mLockPatternUtils.isSecure(currentUserId)).thenReturn(true);
+        when(mDevicePolicyManager.getMaximumTimeToLock(null, currentUserId)).thenReturn(0L);
+        when(mSecureSettings.getIntForUser(LOCK_SCREEN_LOCK_AFTER_TIMEOUT,
+                KEYGUARD_LOCK_AFTER_DELAY_DEFAULT, currentUserId)).thenReturn(userSpecificTimeout);
+        mSystemClock.setElapsedRealtime(0L);
+        ArgumentCaptor<PendingIntent> pendingIntent = ArgumentCaptor.forClass(PendingIntent.class);
+
+        mViewMediator.onDreamingStarted();
+
+        verify(mAlarmManager).setExactAndAllowWhileIdle(eq(AlarmManager.ELAPSED_REALTIME_WAKEUP),
+                eq(Long.valueOf(userSpecificTimeout)), pendingIntent.capture());
+        assertEquals(DELAYED_KEYGUARD_ACTION, pendingIntent.getValue().getIntent().getAction());
+    }
+
+    @Test
     public void testHideSurfaceBehindKeyguardMarksKeyguardNotGoingAway() {
         mViewMediator.hideSurfaceBehindKeyguard();
 
@@ -518,38 +656,7 @@
     public void testStartKeyguardExitAnimation_expectSurfaceBehindRemoteAnimation() {
         startMockKeyguardExitAnimation();
         assertTrue(mViewMediator.isAnimatingBetweenKeyguardAndSurfaceBehind());
-    }
 
-    /**
-     * Configures mocks appropriately, then starts the keyguard exit animation.
-     */
-    private void startMockKeyguardExitAnimation() {
-        mViewMediator.onSystemReady();
-        TestableLooper.get(this).processAllMessages();
-
-        mViewMediator.setShowingLocked(true);
-
-        RemoteAnimationTarget[] apps = new RemoteAnimationTarget[]{
-                mock(RemoteAnimationTarget.class)
-        };
-        RemoteAnimationTarget[] wallpapers = new RemoteAnimationTarget[]{
-                mock(RemoteAnimationTarget.class)
-        };
-        IRemoteAnimationFinishedCallback callback = mock(IRemoteAnimationFinishedCallback.class);
-
-        when(mKeyguardStateController.isKeyguardGoingAway()).thenReturn(true);
-        mViewMediator.startKeyguardExitAnimation(TRANSIT_OLD_KEYGUARD_GOING_AWAY, apps, wallpapers,
-                null, callback);
-        TestableLooper.get(this).processAllMessages();
-    }
-
-    /**
-     * Configures mocks appropriately, then cancels the keyguard exit animation.
-     */
-    private void cancelMockKeyguardExitAnimation() {
-        when(mKeyguardStateController.isKeyguardGoingAway()).thenReturn(false);
-        mViewMediator.cancelKeyguardExitAnimation();
-        TestableLooper.get(this).processAllMessages();
     }
 
     @Test
@@ -631,6 +738,107 @@
 
     @Test
     @TestableLooper.RunWithLooper(setAsMainLooper = true)
+    public void testStartKeyguardExitAnimation_thenCancelImmediately_doesNotResetAndUpdatesWM() {
+        startMockKeyguardExitAnimation();
+        cancelMockKeyguardExitAnimation();
+
+        // This will trigger doKeyguardLocked and we can verify that we ask ATMS to show the
+        // keyguard explicitly, even though we're already showing, because we cancelled immediately.
+        mViewMediator.onSystemReady();
+        reset(mActivityTaskManagerService);
+        processAllMessagesAndBgExecutorMessages();
+
+        verify(mStatusBarKeyguardViewManager, never()).reset(anyBoolean());
+        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.
+     */
+    private void processAllMessagesAndBgExecutorMessages() {
+        TestableLooper.get(this).processAllMessages();
+        mUiBgExecutor.runAllReady();
+    }
+
+    /**
+     * Configures mocks appropriately, then starts the keyguard exit animation.
+     */
+    private void startMockKeyguardExitAnimation() {
+        mViewMediator.onSystemReady();
+        processAllMessagesAndBgExecutorMessages();
+
+        mViewMediator.setShowingLocked(true);
+
+        RemoteAnimationTarget[] apps = new RemoteAnimationTarget[]{
+                mock(RemoteAnimationTarget.class)
+        };
+        RemoteAnimationTarget[] wallpapers = new RemoteAnimationTarget[]{
+                mock(RemoteAnimationTarget.class)
+        };
+        IRemoteAnimationFinishedCallback callback = mock(IRemoteAnimationFinishedCallback.class);
+
+        when(mKeyguardStateController.isKeyguardGoingAway()).thenReturn(true);
+        mViewMediator.startKeyguardExitAnimation(TRANSIT_OLD_KEYGUARD_GOING_AWAY, apps, wallpapers,
+                null, callback);
+        processAllMessagesAndBgExecutorMessages();
+    }
+
+    /**
+     * Configures mocks appropriately, then cancels the keyguard exit animation.
+     */
+    private void cancelMockKeyguardExitAnimation() {
+        when(mKeyguardStateController.isKeyguardGoingAway()).thenReturn(false);
+        mViewMediator.cancelKeyguardExitAnimation();
+        processAllMessagesAndBgExecutorMessages();
+    }
+    /**
+     * Asserts the last value passed to ATMS#setLockScreenShown. This should be confirmed alongside
+     * {@link KeyguardViewMediator#isShowingAndNotOccluded()} to verify that state is not mismatched
+     * between SysUI and WM.
+     */
+    private void assertATMSLockScreenShowing(boolean showing)
+            throws RemoteException {
+        // ATMS is called via bgExecutor, so make sure to run all of those calls first.
+        processAllMessagesAndBgExecutorMessages();
+
+        final InOrder orderedSetLockScreenShownCalls = inOrder(mActivityTaskManagerService);
+        final ArgumentCaptor<Boolean> showingCaptor = ArgumentCaptor.forClass(Boolean.class);
+        orderedSetLockScreenShownCalls
+                .verify(mActivityTaskManagerService, atLeastOnce())
+                .setLockScreenShown(showingCaptor.capture(), anyBoolean());
+
+        // The captor will have the most recent setLockScreenShown call's value.
+        assertEquals(showing, showingCaptor.getValue());
+
+        // We're now just after the last setLockScreenShown call. If we expect the lockscreen to be
+        // showing, ensure that we didn't subsequently ask for it to go away.
+        if (showing) {
+            orderedSetLockScreenShownCalls.verify(mActivityTaskManagerService, never())
+                    .keyguardGoingAway(anyInt());
+        }
+    }
+
+    /**
+     * Asserts that we eventually called ATMS#keyguardGoingAway and did not subsequently call
+     * ATMS#setLockScreenShown(true) which would cancel the going away.
+     */
+    private void assertATMSKeyguardGoingAway() throws RemoteException {
+        // ATMS is called via bgExecutor, so make sure to run all of those calls first.
+        processAllMessagesAndBgExecutorMessages();
+
+        final InOrder orderedGoingAwayCalls = inOrder(mActivityTaskManagerService);
+        orderedGoingAwayCalls.verify(mActivityTaskManagerService, atLeastOnce())
+                .keyguardGoingAway(anyInt());
+
+        // Advance the inOrder to just past the last goingAway call. Let's make sure we didn't
+        // re-show the lockscreen, which would cancel going away.
+        orderedGoingAwayCalls.verify(mActivityTaskManagerService, never())
+                .setLockScreenShown(eq(true), anyBoolean());
+    }
+
+    @Test
+    @TestableLooper.RunWithLooper(setAsMainLooper = true)
     public void testNotStartingKeyguardWhenFlagIsDisabled() {
         mViewMediator.setShowingLocked(false);
         when(mKeyguardStateController.isShowing()).thenReturn(false);
@@ -693,6 +901,11 @@
         assertTrue(mViewMediator.isShowingAndNotOccluded());
     }
 
+    @Test
+    public void testBouncerSwipeDown() {
+        mViewMediator.getViewMediatorCallback().onBouncerSwipeDown();
+        verify(mStatusBarKeyguardViewManager).reset(true);
+    }
     private void createAndStartViewMediator() {
         mViewMediator = new KeyguardViewMediator(
                 mContext,
@@ -723,11 +936,17 @@
                 mKeyguardTransitions,
                 mInteractionJankMonitor,
                 mDreamOverlayStateController,
+                mJavaAdapter,
+                mWallpaperRepository,
                 () -> mShadeController,
                 () -> mNotificationShadeWindowController,
                 () -> mActivityLaunchAnimator,
                 () -> mScrimController,
+                mActivityTaskManagerService,
                 mFeatureFlags,
+                mSecureSettings,
+                mSystemSettings,
+                mSystemClock,
                 mDispatcher,
                 () -> mDreamingToLockscreenTransitionViewModel,
                 mSystemPropertiesHelper);
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 6447368..925ac30 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt
@@ -7,12 +7,10 @@
 import com.android.systemui.SysuiTestCase
 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.FakeKeyguardBouncerRepository
 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.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.keyguard.shared.model.WakeSleepReason
@@ -60,20 +58,20 @@
         keyguardRepository.setDozeAmount(0f)
         keyguardRepository.setKeyguardGoingAway(false)
 
-        val keyguardInteractor =
-            KeyguardInteractor(
-                keyguardRepository,
-                FakeCommandQueue(),
-                featureFlags,
-                FakeKeyguardBouncerRepository()
+        val withDeps =
+            KeyguardInteractorFactory.create(
+                repository = keyguardRepository,
+                featureFlags = featureFlags,
             )
+        val keyguardInteractor = withDeps.keyguardInteractor
         resourceTrimmer =
             ResourceTrimmer(
                 keyguardInteractor,
-                KeyguardTransitionInteractor(
-                    keyguardTransitionRepository,
-                    testScope.backgroundScope
-                ),
+                KeyguardTransitionInteractorFactory.create(
+                        scope = TestScope().backgroundScope,
+                        repository = keyguardTransitionRepository,
+                    )
+                    .keyguardTransitionInteractor,
                 globalWindowManager,
                 testScope.backgroundScope,
                 testDispatcher,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/bouncer/data/factory/BouncerMessageFactoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/bouncer/data/factory/BouncerMessageFactoryTest.kt
deleted file mode 100644
index 0ef77e8..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/bouncer/data/factory/BouncerMessageFactoryTest.kt
+++ /dev/null
@@ -1,161 +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.keyguard.bouncer.data.factory
-
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.keyguard.KeyguardSecurityModel
-import com.android.keyguard.KeyguardSecurityModel.SecurityMode.PIN
-import com.android.keyguard.KeyguardSecurityModel.SecurityMode.Password
-import com.android.keyguard.KeyguardSecurityModel.SecurityMode.Pattern
-import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_DEFAULT
-import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_INCORRECT_PRIMARY_AUTH_INPUT
-import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.keyguard.bouncer.shared.model.BouncerMessageModel
-import com.android.systemui.util.mockito.whenever
-import com.google.common.truth.StringSubject
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
-import org.junit.Ignore
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.MockitoAnnotations
-
-@OptIn(ExperimentalCoroutinesApi::class)
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-@Ignore("b/236891644")
-class BouncerMessageFactoryTest : SysuiTestCase() {
-    private lateinit var underTest: BouncerMessageFactory
-
-    @Mock private lateinit var updateMonitor: KeyguardUpdateMonitor
-
-    @Mock private lateinit var securityModel: KeyguardSecurityModel
-
-    private lateinit var testScope: TestScope
-
-    @Before
-    fun setUp() {
-        MockitoAnnotations.initMocks(this)
-        testScope = TestScope()
-        underTest = BouncerMessageFactory(updateMonitor, securityModel)
-    }
-
-    @Test
-    fun bouncerMessages_choosesTheRightMessage_basedOnSecurityModeAndFpAllowedInBouncer() =
-        testScope.runTest {
-            primaryMessage(PROMPT_REASON_DEFAULT, mode = PIN, fpAllowedInBouncer = false)
-                .isEqualTo("Enter PIN")
-            primaryMessage(PROMPT_REASON_DEFAULT, mode = PIN, fpAllowedInBouncer = true)
-                .isEqualTo("Unlock with PIN or fingerprint")
-
-            primaryMessage(PROMPT_REASON_DEFAULT, mode = Password, fpAllowedInBouncer = false)
-                .isEqualTo("Enter password")
-            primaryMessage(PROMPT_REASON_DEFAULT, mode = Password, fpAllowedInBouncer = true)
-                .isEqualTo("Unlock with password or fingerprint")
-
-            primaryMessage(PROMPT_REASON_DEFAULT, mode = Pattern, fpAllowedInBouncer = false)
-                .isEqualTo("Draw pattern")
-            primaryMessage(PROMPT_REASON_DEFAULT, mode = Pattern, fpAllowedInBouncer = true)
-                .isEqualTo("Unlock with pattern or fingerprint")
-        }
-
-    @Test
-    fun bouncerMessages_setsPrimaryAndSecondaryMessage_basedOnSecurityModeAndFpAllowedInBouncer() =
-        testScope.runTest {
-            primaryMessage(
-                    PROMPT_REASON_INCORRECT_PRIMARY_AUTH_INPUT,
-                    mode = PIN,
-                    fpAllowedInBouncer = true
-                )
-                .isEqualTo("Wrong PIN. Try again.")
-            secondaryMessage(
-                    PROMPT_REASON_INCORRECT_PRIMARY_AUTH_INPUT,
-                    mode = PIN,
-                    fpAllowedInBouncer = true
-                )
-                .isEqualTo("Or unlock with fingerprint")
-
-            primaryMessage(
-                    PROMPT_REASON_INCORRECT_PRIMARY_AUTH_INPUT,
-                    mode = Password,
-                    fpAllowedInBouncer = true
-                )
-                .isEqualTo("Wrong password. Try again.")
-            secondaryMessage(
-                    PROMPT_REASON_INCORRECT_PRIMARY_AUTH_INPUT,
-                    mode = Password,
-                    fpAllowedInBouncer = true
-                )
-                .isEqualTo("Or unlock with fingerprint")
-
-            primaryMessage(
-                    PROMPT_REASON_INCORRECT_PRIMARY_AUTH_INPUT,
-                    mode = Pattern,
-                    fpAllowedInBouncer = true
-                )
-                .isEqualTo("Wrong pattern. Try again.")
-            secondaryMessage(
-                    PROMPT_REASON_INCORRECT_PRIMARY_AUTH_INPUT,
-                    mode = Pattern,
-                    fpAllowedInBouncer = true
-                )
-                .isEqualTo("Or unlock with fingerprint")
-        }
-
-    private fun primaryMessage(
-        reason: Int,
-        mode: KeyguardSecurityModel.SecurityMode,
-        fpAllowedInBouncer: Boolean
-    ): StringSubject {
-        return assertThat(
-            context.resources.getString(
-                bouncerMessageModel(mode, fpAllowedInBouncer, reason)!!.message!!.messageResId!!
-            )
-        )!!
-    }
-
-    private fun secondaryMessage(
-        reason: Int,
-        mode: KeyguardSecurityModel.SecurityMode,
-        fpAllowedInBouncer: Boolean
-    ): StringSubject {
-        return assertThat(
-            context.resources.getString(
-                bouncerMessageModel(mode, fpAllowedInBouncer, reason)!!
-                    .secondaryMessage!!
-                    .messageResId!!
-            )
-        )!!
-    }
-
-    private fun bouncerMessageModel(
-        mode: KeyguardSecurityModel.SecurityMode,
-        fpAllowedInBouncer: Boolean,
-        reason: Int
-    ): BouncerMessageModel? {
-        whenever(securityModel.getSecurityMode(0)).thenReturn(mode)
-        whenever(updateMonitor.isFingerprintAllowedInBouncer).thenReturn(fpAllowedInBouncer)
-
-        return underTest.createFromPromptReason(reason, 0)
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/bouncer/data/repository/BouncerMessageRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/bouncer/data/repository/BouncerMessageRepositoryTest.kt
deleted file mode 100644
index a729de5..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/bouncer/data/repository/BouncerMessageRepositoryTest.kt
+++ /dev/null
@@ -1,365 +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.keyguard.bouncer.data.repository
-
-import android.content.pm.UserInfo
-import android.hardware.biometrics.BiometricSourceType
-import android.testing.TestableLooper
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED
-import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST
-import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED
-import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT
-import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW
-import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT
-import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT
-import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT
-import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN
-import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE
-import com.android.keyguard.KeyguardSecurityModel
-import com.android.keyguard.KeyguardSecurityModel.SecurityMode.PIN
-import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.keyguard.KeyguardUpdateMonitorCallback
-import com.android.systemui.R
-import com.android.systemui.R.string.keyguard_enter_pin
-import com.android.systemui.R.string.kg_prompt_after_dpm_lock
-import com.android.systemui.R.string.kg_prompt_after_user_lockdown_pin
-import com.android.systemui.R.string.kg_prompt_auth_timeout
-import com.android.systemui.R.string.kg_prompt_pin_auth_timeout
-import com.android.systemui.R.string.kg_prompt_reason_restart_pin
-import com.android.systemui.R.string.kg_prompt_unattended_update
-import com.android.systemui.R.string.kg_trust_agent_disabled
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.keyguard.bouncer.data.factory.BouncerMessageFactory
-import com.android.systemui.keyguard.bouncer.shared.model.BouncerMessageModel
-import com.android.systemui.keyguard.bouncer.shared.model.Message
-import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
-import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
-import com.android.systemui.keyguard.data.repository.FakeTrustRepository
-import com.android.systemui.keyguard.shared.model.AuthenticationFlags
-import com.android.systemui.user.data.repository.FakeUserRepository
-import com.android.systemui.util.mockito.whenever
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.runCurrent
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
-import org.junit.Ignore
-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
-
-@OptIn(ExperimentalCoroutinesApi::class)
-@SmallTest
-@TestableLooper.RunWithLooper(setAsMainLooper = true)
-@RunWith(AndroidJUnit4::class)
-@Ignore("b/236891644")
-class BouncerMessageRepositoryTest : SysuiTestCase() {
-
-    @Mock private lateinit var updateMonitor: KeyguardUpdateMonitor
-    @Mock private lateinit var securityModel: KeyguardSecurityModel
-    @Captor
-    private lateinit var updateMonitorCallback: ArgumentCaptor<KeyguardUpdateMonitorCallback>
-
-    private lateinit var underTest: BouncerMessageRepository
-    private lateinit var trustRepository: FakeTrustRepository
-    private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository
-    private lateinit var userRepository: FakeUserRepository
-    private lateinit var fingerprintRepository: FakeDeviceEntryFingerprintAuthRepository
-    private lateinit var testScope: TestScope
-
-    @Before
-    fun setUp() {
-        MockitoAnnotations.initMocks(this)
-        trustRepository = FakeTrustRepository()
-        biometricSettingsRepository = FakeBiometricSettingsRepository()
-        userRepository = FakeUserRepository()
-        userRepository.setUserInfos(listOf(PRIMARY_USER))
-        fingerprintRepository = FakeDeviceEntryFingerprintAuthRepository()
-        testScope = TestScope()
-
-        whenever(updateMonitor.isFingerprintAllowedInBouncer).thenReturn(false)
-        whenever(securityModel.getSecurityMode(PRIMARY_USER_ID)).thenReturn(PIN)
-        underTest =
-            BouncerMessageRepositoryImpl(
-                trustRepository = trustRepository,
-                biometricSettingsRepository = biometricSettingsRepository,
-                updateMonitor = updateMonitor,
-                bouncerMessageFactory = BouncerMessageFactory(updateMonitor, securityModel),
-                userRepository = userRepository,
-                fingerprintAuthRepository = fingerprintRepository
-            )
-    }
-
-    @Test
-    fun setCustomMessage_propagatesState() =
-        testScope.runTest {
-            underTest.setCustomMessage(message("not empty"))
-
-            val customMessage = collectLastValue(underTest.customMessage)
-
-            assertThat(customMessage()).isEqualTo(message("not empty"))
-        }
-
-    @Test
-    fun setFaceMessage_propagatesState() =
-        testScope.runTest {
-            underTest.setFaceAcquisitionMessage(message("not empty"))
-
-            val faceAcquisitionMessage = collectLastValue(underTest.faceAcquisitionMessage)
-
-            assertThat(faceAcquisitionMessage()).isEqualTo(message("not empty"))
-        }
-
-    @Test
-    fun setFpMessage_propagatesState() =
-        testScope.runTest {
-            underTest.setFingerprintAcquisitionMessage(message("not empty"))
-
-            val fpAcquisitionMsg = collectLastValue(underTest.fingerprintAcquisitionMessage)
-
-            assertThat(fpAcquisitionMsg()).isEqualTo(message("not empty"))
-        }
-
-    @Test
-    fun setPrimaryAuthMessage_propagatesState() =
-        testScope.runTest {
-            underTest.setPrimaryAuthMessage(message("not empty"))
-
-            val primaryAuthMessage = collectLastValue(underTest.primaryAuthMessage)
-
-            assertThat(primaryAuthMessage()).isEqualTo(message("not empty"))
-        }
-
-    @Test
-    fun biometricAuthMessage_propagatesBiometricAuthMessages() =
-        testScope.runTest {
-            userRepository.setSelectedUserInfo(PRIMARY_USER)
-            val biometricAuthMessage = collectLastValue(underTest.biometricAuthMessage)
-            runCurrent()
-
-            verify(updateMonitor).registerCallback(updateMonitorCallback.capture())
-
-            updateMonitorCallback.value.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT)
-
-            assertThat(biometricAuthMessage())
-                .isEqualTo(message(R.string.kg_fp_not_recognized, R.string.kg_bio_try_again_or_pin))
-
-            updateMonitorCallback.value.onBiometricAuthFailed(BiometricSourceType.FACE)
-
-            assertThat(biometricAuthMessage())
-                .isEqualTo(
-                    message(R.string.bouncer_face_not_recognized, R.string.kg_bio_try_again_or_pin)
-                )
-
-            updateMonitorCallback.value.onBiometricAcquired(BiometricSourceType.FACE, 0)
-
-            assertThat(biometricAuthMessage()).isNull()
-        }
-
-    @Test
-    fun onFaceLockout_propagatesState() =
-        testScope.runTest {
-            userRepository.setSelectedUserInfo(PRIMARY_USER)
-            val lockoutMessage = collectLastValue(underTest.biometricLockedOutMessage)
-            runCurrent()
-            verify(updateMonitor).registerCallback(updateMonitorCallback.capture())
-
-            whenever(updateMonitor.isFaceLockedOut).thenReturn(true)
-            updateMonitorCallback.value.onLockedOutStateChanged(BiometricSourceType.FACE)
-
-            assertThat(lockoutMessage())
-                .isEqualTo(message(keyguard_enter_pin, R.string.kg_face_locked_out))
-
-            whenever(updateMonitor.isFaceLockedOut).thenReturn(false)
-            updateMonitorCallback.value.onLockedOutStateChanged(BiometricSourceType.FACE)
-            assertThat(lockoutMessage()).isNull()
-        }
-
-    @Test
-    fun onFingerprintLockout_propagatesState() =
-        testScope.runTest {
-            userRepository.setSelectedUserInfo(PRIMARY_USER)
-            val lockedOutMessage = collectLastValue(underTest.biometricLockedOutMessage)
-            runCurrent()
-
-            fingerprintRepository.setLockedOut(true)
-
-            assertThat(lockedOutMessage())
-                .isEqualTo(message(keyguard_enter_pin, R.string.kg_fp_locked_out))
-
-            fingerprintRepository.setLockedOut(false)
-            assertThat(lockedOutMessage()).isNull()
-        }
-
-    @Test
-    fun onAuthFlagsChanged_withTrustNotManagedAndNoBiometrics_isANoop() =
-        testScope.runTest {
-            userRepository.setSelectedUserInfo(PRIMARY_USER)
-            trustRepository.setCurrentUserTrustManaged(false)
-            biometricSettingsRepository.setFaceEnrolled(false)
-            biometricSettingsRepository.setFingerprintEnrolled(false)
-
-            verifyMessagesForAuthFlag(
-                STRONG_AUTH_NOT_REQUIRED to null,
-                STRONG_AUTH_REQUIRED_AFTER_BOOT to null,
-                SOME_AUTH_REQUIRED_AFTER_USER_REQUEST to null,
-                STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to null,
-                STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to null,
-                STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to null,
-                STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to null,
-                SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED to null,
-                STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to null,
-                STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to
-                    Pair(keyguard_enter_pin, kg_prompt_after_dpm_lock),
-            )
-        }
-
-    @Test
-    fun authFlagsChanges_withTrustManaged_providesDifferentMessages() =
-        testScope.runTest {
-            userRepository.setSelectedUserInfo(PRIMARY_USER)
-            biometricSettingsRepository.setFaceEnrolled(false)
-            biometricSettingsRepository.setFingerprintEnrolled(false)
-
-            trustRepository.setCurrentUserTrustManaged(true)
-
-            verifyMessagesForAuthFlag(
-                STRONG_AUTH_NOT_REQUIRED to null,
-                STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to null,
-                STRONG_AUTH_REQUIRED_AFTER_BOOT to
-                    Pair(keyguard_enter_pin, kg_prompt_reason_restart_pin),
-                STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to
-                    Pair(keyguard_enter_pin, kg_prompt_pin_auth_timeout),
-                STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to
-                    Pair(keyguard_enter_pin, kg_prompt_after_dpm_lock),
-                SOME_AUTH_REQUIRED_AFTER_USER_REQUEST to
-                    Pair(keyguard_enter_pin, kg_trust_agent_disabled),
-                SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED to
-                    Pair(keyguard_enter_pin, kg_trust_agent_disabled),
-                STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to
-                    Pair(keyguard_enter_pin, kg_prompt_after_user_lockdown_pin),
-                STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to
-                    Pair(keyguard_enter_pin, kg_prompt_unattended_update),
-                STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to
-                    Pair(keyguard_enter_pin, kg_prompt_auth_timeout),
-            )
-        }
-
-    @Test
-    fun authFlagsChanges_withFaceEnrolled_providesDifferentMessages() =
-        testScope.runTest {
-            userRepository.setSelectedUserInfo(PRIMARY_USER)
-            trustRepository.setCurrentUserTrustManaged(false)
-            biometricSettingsRepository.setFingerprintEnrolled(false)
-
-            biometricSettingsRepository.setIsFaceAuthEnabled(true)
-            biometricSettingsRepository.setFaceEnrolled(true)
-
-            verifyMessagesForAuthFlag(
-                STRONG_AUTH_NOT_REQUIRED to null,
-                STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to null,
-                SOME_AUTH_REQUIRED_AFTER_USER_REQUEST to null,
-                SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED to null,
-                STRONG_AUTH_REQUIRED_AFTER_BOOT to
-                    Pair(keyguard_enter_pin, kg_prompt_reason_restart_pin),
-                STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to
-                    Pair(keyguard_enter_pin, kg_prompt_pin_auth_timeout),
-                STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to
-                    Pair(keyguard_enter_pin, kg_prompt_after_dpm_lock),
-                STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to
-                    Pair(keyguard_enter_pin, kg_prompt_after_user_lockdown_pin),
-                STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to
-                    Pair(keyguard_enter_pin, kg_prompt_unattended_update),
-                STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to
-                    Pair(keyguard_enter_pin, kg_prompt_auth_timeout),
-            )
-        }
-
-    @Test
-    fun authFlagsChanges_withFingerprintEnrolled_providesDifferentMessages() =
-        testScope.runTest {
-            userRepository.setSelectedUserInfo(PRIMARY_USER)
-            trustRepository.setCurrentUserTrustManaged(false)
-            biometricSettingsRepository.setIsFaceAuthEnabled(false)
-            biometricSettingsRepository.setFaceEnrolled(false)
-
-            biometricSettingsRepository.setFingerprintEnrolled(true)
-            biometricSettingsRepository.setFingerprintEnabledByDevicePolicy(true)
-
-            verifyMessagesForAuthFlag(
-                STRONG_AUTH_NOT_REQUIRED to null,
-                STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to null,
-                SOME_AUTH_REQUIRED_AFTER_USER_REQUEST to null,
-                SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED to null,
-                STRONG_AUTH_REQUIRED_AFTER_BOOT to
-                    Pair(keyguard_enter_pin, kg_prompt_reason_restart_pin),
-                STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to
-                    Pair(keyguard_enter_pin, kg_prompt_pin_auth_timeout),
-                STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to
-                    Pair(keyguard_enter_pin, kg_prompt_after_dpm_lock),
-                STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to
-                    Pair(keyguard_enter_pin, kg_prompt_after_user_lockdown_pin),
-                STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to
-                    Pair(keyguard_enter_pin, kg_prompt_unattended_update),
-                STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to
-                    Pair(keyguard_enter_pin, kg_prompt_auth_timeout),
-            )
-        }
-
-    private fun TestScope.verifyMessagesForAuthFlag(
-        vararg authFlagToExpectedMessages: Pair<Int, Pair<Int, Int>?>
-    ) {
-        val authFlagsMessage = collectLastValue(underTest.authFlagsMessage)
-
-        authFlagToExpectedMessages.forEach { (flag, messagePair) ->
-            biometricSettingsRepository.setAuthenticationFlags(
-                AuthenticationFlags(PRIMARY_USER_ID, flag)
-            )
-
-            assertThat(authFlagsMessage())
-                .isEqualTo(messagePair?.let { message(it.first, it.second) })
-        }
-    }
-
-    private fun message(primaryResId: Int, secondaryResId: Int): BouncerMessageModel {
-        return BouncerMessageModel(
-            message = Message(messageResId = primaryResId),
-            secondaryMessage = Message(messageResId = secondaryResId)
-        )
-    }
-    private fun message(value: String): BouncerMessageModel {
-        return BouncerMessageModel(message = Message(message = value))
-    }
-
-    companion object {
-        private const val PRIMARY_USER_ID = 0
-        private val PRIMARY_USER =
-            UserInfo(
-                /* id= */ PRIMARY_USER_ID,
-                /* name= */ "primary user",
-                /* flags= */ UserInfo.FLAG_PRIMARY
-            )
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/bouncer/domain/interactor/BouncerMessageInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/bouncer/domain/interactor/BouncerMessageInteractorTest.kt
deleted file mode 100644
index 3a87680..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/bouncer/domain/interactor/BouncerMessageInteractorTest.kt
+++ /dev/null
@@ -1,287 +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.keyguard.bouncer.domain.interactor
-
-import android.content.pm.UserInfo
-import android.testing.TestableLooper
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.keyguard.KeyguardSecurityModel
-import com.android.keyguard.KeyguardSecurityModel.SecurityMode.PIN
-import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.systemui.R.string.keyguard_enter_pin
-import com.android.systemui.R.string.kg_too_many_failed_attempts_countdown
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.coroutines.FlowValue
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags
-import com.android.systemui.keyguard.bouncer.data.factory.BouncerMessageFactory
-import com.android.systemui.keyguard.bouncer.shared.model.BouncerMessageModel
-import com.android.systemui.keyguard.bouncer.shared.model.Message
-import com.android.systemui.keyguard.data.repository.FakeBouncerMessageRepository
-import com.android.systemui.user.data.repository.FakeUserRepository
-import com.android.systemui.util.mockito.KotlinArgumentCaptor
-import com.android.systemui.util.mockito.whenever
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
-import org.junit.Ignore
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.ArgumentMatchers.eq
-import org.mockito.Mock
-import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-
-@OptIn(ExperimentalCoroutinesApi::class)
-@SmallTest
-@TestableLooper.RunWithLooper(setAsMainLooper = true)
-@RunWith(AndroidJUnit4::class)
-@Ignore("b/236891644")
-class BouncerMessageInteractorTest : SysuiTestCase() {
-
-    @Mock private lateinit var securityModel: KeyguardSecurityModel
-    @Mock private lateinit var updateMonitor: KeyguardUpdateMonitor
-    @Mock private lateinit var countDownTimerUtil: CountDownTimerUtil
-    private lateinit var countDownTimerCallback: KotlinArgumentCaptor<CountDownTimerCallback>
-    private lateinit var underTest: BouncerMessageInteractor
-    private lateinit var repository: FakeBouncerMessageRepository
-    private lateinit var userRepository: FakeUserRepository
-    private lateinit var testScope: TestScope
-    private lateinit var bouncerMessage: FlowValue<BouncerMessageModel?>
-
-    @Before
-    fun setUp() {
-        MockitoAnnotations.initMocks(this)
-        repository = FakeBouncerMessageRepository()
-        userRepository = FakeUserRepository()
-        userRepository.setUserInfos(listOf(PRIMARY_USER))
-        testScope = TestScope()
-        countDownTimerCallback = KotlinArgumentCaptor(CountDownTimerCallback::class.java)
-
-        allowTestableLooperAsMainThread()
-        whenever(securityModel.getSecurityMode(PRIMARY_USER_ID)).thenReturn(PIN)
-        whenever(updateMonitor.isFingerprintAllowedInBouncer).thenReturn(false)
-    }
-
-    suspend fun TestScope.init() {
-        userRepository.setSelectedUserInfo(PRIMARY_USER)
-        val featureFlags = FakeFeatureFlags()
-        featureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true)
-        underTest =
-            BouncerMessageInteractor(
-                repository = repository,
-                factory = BouncerMessageFactory(updateMonitor, securityModel),
-                userRepository = userRepository,
-                countDownTimerUtil = countDownTimerUtil,
-                featureFlags = featureFlags
-            )
-        bouncerMessage = collectLastValue(underTest.bouncerMessage)
-    }
-
-    @Test
-    fun onIncorrectSecurityInput_setsTheBouncerModelInTheRepository() =
-        testScope.runTest {
-            init()
-            underTest.onPrimaryAuthIncorrectAttempt()
-
-            assertThat(repository.primaryAuthMessage).isNotNull()
-            assertThat(
-                    context.resources.getString(
-                        repository.primaryAuthMessage.value!!.message!!.messageResId!!
-                    )
-                )
-                .isEqualTo("Wrong PIN. Try again.")
-        }
-
-    @Test
-    fun onUserStartsPrimaryAuthInput_clearsAllSetBouncerMessages() =
-        testScope.runTest {
-            init()
-            repository.setCustomMessage(message("not empty"))
-            repository.setFaceAcquisitionMessage(message("not empty"))
-            repository.setFingerprintAcquisitionMessage(message("not empty"))
-            repository.setPrimaryAuthMessage(message("not empty"))
-
-            underTest.onPrimaryBouncerUserInput()
-
-            assertThat(repository.customMessage.value).isNull()
-            assertThat(repository.faceAcquisitionMessage.value).isNull()
-            assertThat(repository.fingerprintAcquisitionMessage.value).isNull()
-            assertThat(repository.primaryAuthMessage.value).isNull()
-        }
-
-    @Test
-    fun onBouncerBeingHidden_clearsAllSetBouncerMessages() =
-        testScope.runTest {
-            init()
-            repository.setCustomMessage(message("not empty"))
-            repository.setFaceAcquisitionMessage(message("not empty"))
-            repository.setFingerprintAcquisitionMessage(message("not empty"))
-            repository.setPrimaryAuthMessage(message("not empty"))
-
-            underTest.onBouncerBeingHidden()
-
-            assertThat(repository.customMessage.value).isNull()
-            assertThat(repository.faceAcquisitionMessage.value).isNull()
-            assertThat(repository.fingerprintAcquisitionMessage.value).isNull()
-            assertThat(repository.primaryAuthMessage.value).isNull()
-        }
-
-    @Test
-    fun setCustomMessage_setsRepositoryValue() =
-        testScope.runTest {
-            init()
-
-            underTest.setCustomMessage("not empty")
-
-            assertThat(repository.customMessage.value)
-                .isEqualTo(BouncerMessageModel(secondaryMessage = Message(message = "not empty")))
-
-            underTest.setCustomMessage(null)
-            assertThat(repository.customMessage.value).isNull()
-        }
-
-    @Test
-    fun setFaceMessage_setsRepositoryValue() =
-        testScope.runTest {
-            init()
-
-            underTest.setFaceAcquisitionMessage("not empty")
-
-            assertThat(repository.faceAcquisitionMessage.value)
-                .isEqualTo(BouncerMessageModel(secondaryMessage = Message(message = "not empty")))
-
-            underTest.setFaceAcquisitionMessage(null)
-            assertThat(repository.faceAcquisitionMessage.value).isNull()
-        }
-
-    @Test
-    fun setFingerprintMessage_setsRepositoryValue() =
-        testScope.runTest {
-            init()
-
-            underTest.setFingerprintAcquisitionMessage("not empty")
-
-            assertThat(repository.fingerprintAcquisitionMessage.value)
-                .isEqualTo(BouncerMessageModel(secondaryMessage = Message(message = "not empty")))
-
-            underTest.setFingerprintAcquisitionMessage(null)
-            assertThat(repository.fingerprintAcquisitionMessage.value).isNull()
-        }
-
-    @Test
-    fun onPrimaryAuthLockout_startsTimerForSpecifiedNumberOfSeconds() =
-        testScope.runTest {
-            init()
-
-            underTest.onPrimaryAuthLockedOut(3)
-
-            verify(countDownTimerUtil)
-                .startNewTimer(eq(3000L), eq(1000L), countDownTimerCallback.capture())
-
-            countDownTimerCallback.value.onTick(2000L)
-
-            val primaryMessage = repository.primaryAuthMessage.value!!.message!!
-            assertThat(primaryMessage.messageResId!!)
-                .isEqualTo(kg_too_many_failed_attempts_countdown)
-            assertThat(primaryMessage.formatterArgs).isEqualTo(mapOf(Pair("count", 2)))
-        }
-
-    @Test
-    fun onPrimaryAuthLockout_timerComplete_resetsRepositoryMessages() =
-        testScope.runTest {
-            init()
-            repository.setCustomMessage(message("not empty"))
-            repository.setFaceAcquisitionMessage(message("not empty"))
-            repository.setFingerprintAcquisitionMessage(message("not empty"))
-            repository.setPrimaryAuthMessage(message("not empty"))
-
-            underTest.onPrimaryAuthLockedOut(3)
-
-            verify(countDownTimerUtil)
-                .startNewTimer(eq(3000L), eq(1000L), countDownTimerCallback.capture())
-
-            countDownTimerCallback.value.onFinish()
-
-            assertThat(repository.customMessage.value).isNull()
-            assertThat(repository.faceAcquisitionMessage.value).isNull()
-            assertThat(repository.fingerprintAcquisitionMessage.value).isNull()
-            assertThat(repository.primaryAuthMessage.value).isNull()
-        }
-
-    @Test
-    fun bouncerMessage_hasPriorityOrderOfMessages() =
-        testScope.runTest {
-            init()
-            repository.setBiometricAuthMessage(message("biometric message"))
-            repository.setFaceAcquisitionMessage(message("face acquisition message"))
-            repository.setFingerprintAcquisitionMessage(message("fingerprint acquisition message"))
-            repository.setPrimaryAuthMessage(message("primary auth message"))
-            repository.setAuthFlagsMessage(message("auth flags message"))
-            repository.setBiometricLockedOutMessage(message("biometrics locked out"))
-            repository.setCustomMessage(message("custom message"))
-
-            assertThat(bouncerMessage()).isEqualTo(message("primary auth message"))
-
-            repository.setPrimaryAuthMessage(null)
-
-            assertThat(bouncerMessage()).isEqualTo(message("biometric message"))
-
-            repository.setBiometricAuthMessage(null)
-
-            assertThat(bouncerMessage()).isEqualTo(message("fingerprint acquisition message"))
-
-            repository.setFingerprintAcquisitionMessage(null)
-
-            assertThat(bouncerMessage()).isEqualTo(message("face acquisition message"))
-
-            repository.setFaceAcquisitionMessage(null)
-
-            assertThat(bouncerMessage()).isEqualTo(message("custom message"))
-
-            repository.setCustomMessage(null)
-
-            assertThat(bouncerMessage()).isEqualTo(message("auth flags message"))
-
-            repository.setAuthFlagsMessage(null)
-
-            assertThat(bouncerMessage()).isEqualTo(message("biometrics locked out"))
-
-            repository.setBiometricLockedOutMessage(null)
-
-            // sets the default message if everything else is null
-            assertThat(bouncerMessage()!!.message!!.messageResId).isEqualTo(keyguard_enter_pin)
-        }
-
-    private fun message(value: String): BouncerMessageModel {
-        return BouncerMessageModel(message = Message(message = value))
-    }
-
-    companion object {
-        private const val PRIMARY_USER_ID = 0
-        private val PRIMARY_USER =
-            UserInfo(
-                /* id= */ PRIMARY_USER_ID,
-                /* name= */ "primary user",
-                /* flags= */ UserInfo.FLAG_PRIMARY
-            )
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManagerTest.kt
index b2528c5..edaff44 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManagerTest.kt
@@ -25,6 +25,7 @@
 import com.android.systemui.R
 import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.backup.BackupHelper
 import com.android.systemui.settings.FakeUserTracker
 import com.android.systemui.settings.UserFileManager
 import com.android.systemui.util.FakeSharedPreferences
@@ -360,7 +361,10 @@
             }
         clearInvocations(userFileManager)
 
-        fakeBroadcastDispatcher.registeredReceivers.firstOrNull()?.onReceive(context, Intent())
+        fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+            context,
+            Intent(BackupHelper.ACTION_RESTORE_FINISHED),
+        )
 
         verify(userFileManager, atLeastOnce()).getSharedPreferences(anyString(), anyInt(), anyInt())
         job.cancel()
@@ -374,12 +378,13 @@
             arrayOf("leftTest:testShortcut1", "rightTest:testShortcut2")
         )
 
-        assertThat(underTest.getSelections()).isEqualTo(
-            mapOf(
-                "leftTest" to listOf("testShortcut1"),
-                "rightTest" to listOf("testShortcut2"),
+        assertThat(underTest.getSelections())
+            .isEqualTo(
+                mapOf(
+                    "leftTest" to listOf("testShortcut1"),
+                    "rightTest" to listOf("testShortcut2"),
+                )
             )
-        )
     }
 
     private fun assertSelections(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartableTest.kt
index f243d7b..df1833e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartableTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartableTest.kt
@@ -24,8 +24,6 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
 import com.android.systemui.settings.UserFileManager
 import com.android.systemui.settings.UserTracker
@@ -59,8 +57,6 @@
 class MuteQuickAffordanceCoreStartableTest : SysuiTestCase() {
 
     @Mock
-    private lateinit var featureFlags: FeatureFlags
-    @Mock
     private lateinit var userTracker: UserTracker
     @Mock
     private lateinit var ringerModeTracker: RingerModeTracker
@@ -78,8 +74,6 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
 
-        whenever(featureFlags.isEnabled(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES)).thenReturn(true)
-
         val config: KeyguardQuickAffordanceConfig = mock()
         whenever(config.key).thenReturn(BuiltInKeyguardQuickAffordanceKeys.MUTE)
 
@@ -90,7 +84,6 @@
         testScope = TestScope(testDispatcher)
 
         underTest = MuteQuickAffordanceCoreStartable(
-            featureFlags,
             userTracker,
             ringerModeTracker,
             userFileManager,
@@ -101,20 +94,7 @@
     }
 
     @Test
-    fun featureFlagIsOFF_doNothingWithKeyguardQuickAffordanceRepository() = testScope.runTest {
-        //given
-        whenever(featureFlags.isEnabled(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES)).thenReturn(false)
-
-        //when
-        underTest.start()
-
-        //then
-        verifyZeroInteractions(keyguardQuickAffordanceRepository)
-        coroutineContext.cancelChildren()
-    }
-
-    @Test
-    fun featureFlagIsON_callToKeyguardQuickAffordanceRepository() = testScope.runTest {
+    fun callToKeyguardQuickAffordanceRepository() = testScope.runTest {
         //given
         val ringerModeInternal = mock<MutableLiveData<Int>>()
         whenever(ringerModeTracker.ringerModeInternal).thenReturn(ringerModeInternal)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt
index b925aeb..c6a2fa5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt
@@ -162,11 +162,11 @@
     @Test
     fun convenienceBiometricAllowedChange() =
         testScope.runTest {
+            overrideResource(com.android.internal.R.bool.config_strongAuthRequiredOnBoot, false)
             createBiometricSettingsRepository()
             val convenienceBiometricAllowed =
                 collectLastValue(underTest.isNonStrongBiometricAllowed)
             runCurrent()
-
             onNonStrongAuthChanged(true, PRIMARY_USER_ID)
             assertThat(convenienceBiometricAllowed()).isTrue()
 
@@ -175,6 +175,45 @@
 
             onNonStrongAuthChanged(false, PRIMARY_USER_ID)
             assertThat(convenienceBiometricAllowed()).isFalse()
+            mContext.orCreateTestableResources.removeOverride(
+                com.android.internal.R.bool.config_strongAuthRequiredOnBoot
+            )
+        }
+
+    @Test
+    fun whenStrongAuthRequiredAfterBoot_nonStrongBiometricNotAllowed() =
+        testScope.runTest {
+            overrideResource(com.android.internal.R.bool.config_strongAuthRequiredOnBoot, true)
+            createBiometricSettingsRepository()
+
+            val convenienceBiometricAllowed =
+                collectLastValue(underTest.isNonStrongBiometricAllowed)
+            runCurrent()
+            onNonStrongAuthChanged(true, PRIMARY_USER_ID)
+
+            assertThat(convenienceBiometricAllowed()).isFalse()
+            mContext.orCreateTestableResources.removeOverride(
+                com.android.internal.R.bool.config_strongAuthRequiredOnBoot
+            )
+        }
+
+    @Test
+    fun whenStrongBiometricAuthIsNotAllowed_nonStrongBiometrics_alsoNotAllowed() =
+        testScope.runTest {
+            overrideResource(com.android.internal.R.bool.config_strongAuthRequiredOnBoot, false)
+            createBiometricSettingsRepository()
+
+            val convenienceBiometricAllowed =
+                collectLastValue(underTest.isNonStrongBiometricAllowed)
+            runCurrent()
+            onNonStrongAuthChanged(true, PRIMARY_USER_ID)
+            assertThat(convenienceBiometricAllowed()).isTrue()
+
+            onStrongAuthChanged(STRONG_AUTH_REQUIRED_AFTER_TIMEOUT, PRIMARY_USER_ID)
+            assertThat(convenienceBiometricAllowed()).isFalse()
+            mContext.orCreateTestableResources.removeOverride(
+                com.android.internal.R.bool.config_strongAuthRequiredOnBoot
+            )
         }
 
     private fun onStrongAuthChanged(flags: Int, userId: Int) {
@@ -461,12 +500,10 @@
     }
 
     private fun broadcastDPMStateChange() {
-        fakeBroadcastDispatcher.registeredReceivers.forEach { receiver ->
-            receiver.onReceive(
-                context,
-                Intent(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED)
-            )
-        }
+        fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+            context,
+            Intent(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED),
+        )
     }
 
     companion object {
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 1090c15..47c662c 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
@@ -36,24 +36,27 @@
 import com.android.keyguard.FaceAuthUiEvent
 import com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN
 import com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER
+import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.systemui.R
 import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
+import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
 import com.android.systemui.coroutines.FlowValue
 import com.android.systemui.coroutines.collectLastValue
 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.keyguard.domain.interactor.AlternateBouncerInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.shared.model.AuthenticationStatus
-import com.android.systemui.keyguard.shared.model.DetectionStatus
-import com.android.systemui.keyguard.shared.model.ErrorAuthenticationStatus
-import com.android.systemui.keyguard.shared.model.HelpAuthenticationStatus
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
+import com.android.systemui.keyguard.shared.model.ErrorFaceAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.FaceAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.FaceDetectionStatus
+import com.android.systemui.keyguard.shared.model.HelpFaceAuthenticationStatus
 import com.android.systemui.keyguard.shared.model.KeyguardState
-import com.android.systemui.keyguard.shared.model.SuccessAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.SuccessFaceAuthenticationStatus
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.keyguard.shared.model.WakeSleepReason
@@ -111,6 +114,7 @@
     @Mock private lateinit var sessionTracker: SessionTracker
     @Mock private lateinit var uiEventLogger: UiEventLogger
     @Mock private lateinit var dumpManager: DumpManager
+    @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
 
     @Captor
     private lateinit var authenticationCallback: ArgumentCaptor<FaceManager.AuthenticationCallback>
@@ -129,8 +133,8 @@
     private lateinit var keyguardTransitionRepository: FakeKeyguardTransitionRepository
     private lateinit var testScope: TestScope
     private lateinit var fakeUserRepository: FakeUserRepository
-    private lateinit var authStatus: FlowValue<AuthenticationStatus?>
-    private lateinit var detectStatus: FlowValue<DetectionStatus?>
+    private lateinit var authStatus: FlowValue<FaceAuthenticationStatus?>
+    private lateinit var detectStatus: FlowValue<FaceDetectionStatus?>
     private lateinit var authRunning: FlowValue<Boolean?>
     private lateinit var bypassEnabled: FlowValue<Boolean?>
     private lateinit var lockedOut: FlowValue<Boolean?>
@@ -159,25 +163,24 @@
         biometricSettingsRepository = FakeBiometricSettingsRepository()
         deviceEntryFingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository()
         trustRepository = FakeTrustRepository()
-        keyguardRepository = FakeKeyguardRepository()
-        bouncerRepository = FakeKeyguardBouncerRepository()
         featureFlags = FakeFeatureFlags().apply { set(FACE_AUTH_REFACTOR, true) }
-        fakeCommandQueue = FakeCommandQueue()
-        keyguardInteractor =
-            KeyguardInteractor(
-                keyguardRepository,
-                fakeCommandQueue,
-                featureFlags,
-                bouncerRepository
+        val withDeps =
+            KeyguardInteractorFactory.create(
+                featureFlags = featureFlags,
             )
+        keyguardInteractor = withDeps.keyguardInteractor
+        keyguardRepository = withDeps.repository
+        bouncerRepository = withDeps.bouncerRepository
+        fakeCommandQueue = withDeps.commandQueue
+
         alternateBouncerInteractor =
             AlternateBouncerInteractor(
                 bouncerRepository = bouncerRepository,
                 biometricSettingsRepository = biometricSettingsRepository,
-                deviceEntryFingerprintAuthRepository = deviceEntryFingerprintAuthRepository,
                 systemClock = mock(SystemClock::class.java),
                 keyguardStateController = FakeKeyguardStateController(),
                 statusBarStateController = mock(StatusBarStateController::class.java),
+                keyguardUpdateMonitor = keyguardUpdateMonitor,
             )
 
         bypassStateChangedListener =
@@ -215,7 +218,11 @@
             )
         keyguardTransitionRepository = FakeKeyguardTransitionRepository()
         val keyguardTransitionInteractor =
-            KeyguardTransitionInteractor(keyguardTransitionRepository, testScope.backgroundScope)
+            KeyguardTransitionInteractorFactory.create(
+                    scope = TestScope().backgroundScope,
+                    repository = keyguardTransitionRepository,
+                )
+                .keyguardTransitionInteractor
         return DeviceEntryFaceAuthRepositoryImpl(
             mContext,
             fmOverride,
@@ -223,6 +230,7 @@
             bypassControllerOverride,
             testScope.backgroundScope,
             testDispatcher,
+            testDispatcher,
             sessionTracker,
             uiEventLogger,
             FaceAuthenticationLogger(logcatLogBuffer("DeviceEntryFaceAuthRepositoryLog")),
@@ -256,9 +264,10 @@
             val successResult = successResult()
             authenticationCallback.value.onAuthenticationSucceeded(successResult)
 
-            assertThat(authStatus()).isEqualTo(SuccessAuthenticationStatus(successResult))
+            assertThat(authStatus()).isEqualTo(SuccessFaceAuthenticationStatus(successResult))
             assertThat(authenticated()).isTrue()
             assertThat(authRunning()).isFalse()
+            assertThat(canFaceAuthRun()).isFalse()
         }
 
     private fun uiEventIsLogged(faceAuthUiEvent: FaceAuthUiEvent) {
@@ -376,7 +385,7 @@
 
             detectionCallback.value.onFaceDetected(1, 1, true)
 
-            assertThat(detectStatus()).isEqualTo(DetectionStatus(1, 1, true))
+            assertThat(detectStatus()).isEqualTo(FaceDetectionStatus(1, 1, true))
         }
 
     @Test
@@ -416,13 +425,9 @@
                 FACE_ERROR_CANCELED,
                 "First auth attempt cancellation completed"
             )
-            assertThat(authStatus())
-                .isEqualTo(
-                    ErrorAuthenticationStatus(
-                        FACE_ERROR_CANCELED,
-                        "First auth attempt cancellation completed"
-                    )
-                )
+            val value = authStatus() as ErrorFaceAuthenticationStatus
+            assertThat(value.msgId).isEqualTo(FACE_ERROR_CANCELED)
+            assertThat(value.msg).isEqualTo("First auth attempt cancellation completed")
 
             faceAuthenticateIsCalled()
             uiEventIsLogged(FACE_AUTH_TRIGGERED_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN)
@@ -462,7 +467,7 @@
             authenticationCallback.value.onAuthenticationHelp(10, "Ignored help msg")
             authenticationCallback.value.onAuthenticationHelp(11, "Ignored help msg")
 
-            assertThat(authStatus()).isEqualTo(HelpAuthenticationStatus(9, "help msg"))
+            assertThat(authStatus()).isEqualTo(HelpFaceAuthenticationStatus(9, "help msg"))
         }
 
     @Test
@@ -506,6 +511,18 @@
         }
 
     @Test
+    fun authenticateDoesNotRunIfKeyguardIsNotShowing() =
+        testScope.runTest {
+            testGatingCheckForFaceAuth { keyguardRepository.setKeyguardShowing(false) }
+        }
+
+    @Test
+    fun detectDoesNotRunIfKeyguardIsNotShowing() =
+        testScope.runTest {
+            testGatingCheckForDetect { keyguardRepository.setKeyguardShowing(false) }
+        }
+
+    @Test
     fun authenticateDoesNotRunWhenFpIsLockedOut() =
         testScope.runTest {
             testGatingCheckForFaceAuth { deviceEntryFingerprintAuthRepository.setLockedOut(true) }
@@ -538,20 +555,6 @@
         }
 
     @Test
-    fun authenticateDoesNotRunWhenDeviceIsSleeping() =
-        testScope.runTest {
-            testGatingCheckForFaceAuth {
-                keyguardRepository.setWakefulnessModel(
-                    WakefulnessModel(
-                        state = WakefulnessState.ASLEEP,
-                        lastWakeReason = WakeSleepReason.OTHER,
-                        lastSleepReason = WakeSleepReason.OTHER,
-                    )
-                )
-            }
-        }
-
-    @Test
     fun authenticateDoesNotRunWhenNonStrongBiometricIsNotAllowed() =
         testScope.runTest {
             testGatingCheckForFaceAuth {
@@ -564,6 +567,8 @@
         testScope.runTest {
             testGatingCheckForFaceAuth {
                 bouncerRepository.setAlternateVisible(false)
+                // Keyguard is occluded when secure camera is active.
+                keyguardRepository.setKeyguardOccluded(true)
                 fakeCommandQueue.doForEachCallback {
                     it.onCameraLaunchGestureDetected(CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP)
                 }
@@ -571,6 +576,29 @@
         }
 
     @Test
+    fun authenticateRunsWhenSecureCameraIsActiveIfBouncerIsShowing() =
+        testScope.runTest {
+            initCollectors()
+            allPreconditionsToRunFaceAuthAreTrue()
+            bouncerRepository.setAlternateVisible(false)
+            bouncerRepository.setPrimaryShow(false)
+
+            assertThat(canFaceAuthRun()).isTrue()
+
+            // launch secure camera
+            fakeCommandQueue.doForEachCallback {
+                it.onCameraLaunchGestureDetected(CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP)
+            }
+            keyguardRepository.setKeyguardOccluded(true)
+            runCurrent()
+            assertThat(canFaceAuthRun()).isFalse()
+
+            // but bouncer is shown after that.
+            bouncerRepository.setPrimaryShow(true)
+            assertThat(canFaceAuthRun()).isTrue()
+        }
+
+    @Test
     fun authenticateDoesNotRunOnUnsupportedPosture() =
         testScope.runTest {
             testGatingCheckForFaceAuth {
@@ -601,6 +629,27 @@
         }
 
     @Test
+    fun authenticateFallbacksToDetectionWhenUserIsAlreadyTrustedByTrustManager() =
+        testScope.runTest {
+            whenever(faceManager.sensorPropertiesInternal)
+                .thenReturn(listOf(createFaceSensorProperties(supportsFaceDetection = true)))
+            whenever(bypassController.bypassEnabled).thenReturn(true)
+            underTest = createDeviceEntryFaceAuthRepositoryImpl()
+            initCollectors()
+            allPreconditionsToRunFaceAuthAreTrue()
+
+            trustRepository.setCurrentUserTrusted(true)
+            assertThat(canFaceAuthRun()).isFalse()
+            underTest.authenticate(
+                FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER,
+                fallbackToDetection = true
+            )
+            faceAuthenticateIsNotCalled()
+
+            faceDetectIsCalled()
+        }
+
+    @Test
     fun everythingWorksWithFaceAuthRefactorFlagDisabled() =
         testScope.runTest {
             featureFlags.set(FACE_AUTH_REFACTOR, false)
@@ -773,6 +822,8 @@
         testScope.runTest {
             testGatingCheckForDetect {
                 bouncerRepository.setAlternateVisible(false)
+                // Keyguard is occluded when secure camera is active.
+                keyguardRepository.setKeyguardOccluded(true)
                 fakeCommandQueue.doForEachCallback {
                     it.onCameraLaunchGestureDetected(CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP)
                 }
@@ -1010,6 +1061,7 @@
         fakeUserRepository.setSelectedUserInfo(primaryUser)
         biometricSettingsRepository.setIsFaceAuthSupportedInCurrentPosture(true)
         bouncerRepository.setAlternateVisible(true)
+        keyguardRepository.setKeyguardShowing(true)
         runCurrent()
     }
 
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 264328b..def016a 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
@@ -26,7 +26,11 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.biometrics.AuthController
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.dump.DumpManager
+import com.android.systemui.keyguard.shared.model.AcquiredFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.HelpFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -49,7 +53,6 @@
 @RunWith(AndroidJUnit4::class)
 class DeviceEntryFingerprintAuthRepositoryTest : SysuiTestCase() {
     @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
-    @Mock private lateinit var dumpManager: DumpManager
     @Mock private lateinit var authController: AuthController
     @Captor
     private lateinit var updateMonitorCallback: ArgumentCaptor<KeyguardUpdateMonitorCallback>
@@ -68,7 +71,6 @@
                 authController,
                 keyguardUpdateMonitor,
                 testScope.backgroundScope,
-                dumpManager,
             )
     }
 
@@ -177,4 +179,129 @@
             callback.value.onAllAuthenticatorsRegistered(TYPE_FINGERPRINT)
             assertThat(availableFpSensorType()).isEqualTo(BiometricType.UNDER_DISPLAY_FINGERPRINT)
         }
+
+    @Test
+    fun onFingerprintSuccess_successAuthenticationStatus() =
+        testScope.runTest {
+            val authenticationStatus by collectLastValue(underTest.authenticationStatus)
+            runCurrent()
+
+            verify(keyguardUpdateMonitor).registerCallback(updateMonitorCallback.capture())
+            updateMonitorCallback.value.onBiometricAuthenticated(
+                0,
+                BiometricSourceType.FINGERPRINT,
+                true,
+            )
+
+            val status = authenticationStatus as SuccessFingerprintAuthenticationStatus
+            assertThat(status.userId).isEqualTo(0)
+            assertThat(status.isStrongBiometric).isEqualTo(true)
+        }
+
+    @Test
+    fun onFingerprintFailed_failedAuthenticationStatus() =
+        testScope.runTest {
+            val authenticationStatus by collectLastValue(underTest.authenticationStatus)
+            runCurrent()
+
+            verify(keyguardUpdateMonitor).registerCallback(updateMonitorCallback.capture())
+            updateMonitorCallback.value.onBiometricAuthFailed(
+                BiometricSourceType.FINGERPRINT,
+            )
+
+            assertThat(authenticationStatus)
+                .isInstanceOf(FailFingerprintAuthenticationStatus::class.java)
+        }
+
+    @Test
+    fun onFingerprintError_errorAuthenticationStatus() =
+        testScope.runTest {
+            val authenticationStatus by collectLastValue(underTest.authenticationStatus)
+            runCurrent()
+
+            verify(keyguardUpdateMonitor).registerCallback(updateMonitorCallback.capture())
+            updateMonitorCallback.value.onBiometricError(
+                1,
+                "test_string",
+                BiometricSourceType.FINGERPRINT,
+            )
+
+            val status = authenticationStatus as ErrorFingerprintAuthenticationStatus
+            assertThat(status.msgId).isEqualTo(1)
+            assertThat(status.msg).isEqualTo("test_string")
+        }
+
+    @Test
+    fun onFingerprintHelp_helpAuthenticationStatus() =
+        testScope.runTest {
+            val authenticationStatus by collectLastValue(underTest.authenticationStatus)
+            runCurrent()
+
+            verify(keyguardUpdateMonitor).registerCallback(updateMonitorCallback.capture())
+            updateMonitorCallback.value.onBiometricHelp(
+                1,
+                "test_string",
+                BiometricSourceType.FINGERPRINT,
+            )
+
+            val status = authenticationStatus as HelpFingerprintAuthenticationStatus
+            assertThat(status.msgId).isEqualTo(1)
+            assertThat(status.msg).isEqualTo("test_string")
+        }
+
+    @Test
+    fun onFingerprintAcquired_acquiredAuthenticationStatus() =
+        testScope.runTest {
+            val authenticationStatus by collectLastValue(underTest.authenticationStatus)
+            runCurrent()
+
+            verify(keyguardUpdateMonitor).registerCallback(updateMonitorCallback.capture())
+            updateMonitorCallback.value.onBiometricAcquired(
+                BiometricSourceType.FINGERPRINT,
+                5,
+            )
+
+            val status = authenticationStatus as AcquiredFingerprintAuthenticationStatus
+            assertThat(status.acquiredInfo).isEqualTo(5)
+        }
+
+    @Test
+    fun onFaceCallbacks_fingerprintAuthenticationStatusIsUnchanged() =
+        testScope.runTest {
+            val authenticationStatus by collectLastValue(underTest.authenticationStatus)
+            runCurrent()
+
+            verify(keyguardUpdateMonitor).registerCallback(updateMonitorCallback.capture())
+            updateMonitorCallback.value.onBiometricAuthenticated(
+                0,
+                BiometricSourceType.FACE,
+                true,
+            )
+            assertThat(authenticationStatus).isNull()
+
+            updateMonitorCallback.value.onBiometricAuthFailed(
+                BiometricSourceType.FACE,
+            )
+            assertThat(authenticationStatus).isNull()
+
+            updateMonitorCallback.value.onBiometricHelp(
+                1,
+                "test_string",
+                BiometricSourceType.FACE,
+            )
+            assertThat(authenticationStatus).isNull()
+
+            updateMonitorCallback.value.onBiometricAcquired(
+                BiometricSourceType.FACE,
+                5,
+            )
+            assertThat(authenticationStatus).isNull()
+
+            updateMonitorCallback.value.onBiometricError(
+                1,
+                "test_string",
+                BiometricSourceType.FACE,
+            )
+            assertThat(authenticationStatus).isNull()
+        }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepositoryTest.kt
deleted file mode 100644
index b3104b7..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepositoryTest.kt
+++ /dev/null
@@ -1,64 +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.systemui.keyguard.data.repository
-
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.keyguard.ViewMediatorCallback
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.log.table.TableLogBuffer
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.time.SystemClock
-import kotlinx.coroutines.runBlocking
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestCoroutineScope
-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 KeyguardBouncerRepositoryTest : SysuiTestCase() {
-
-    @Mock private lateinit var systemClock: SystemClock
-    @Mock private lateinit var viewMediatorCallback: ViewMediatorCallback
-    @Mock private lateinit var bouncerLogger: TableLogBuffer
-    lateinit var underTest: KeyguardBouncerRepository
-
-    @Before
-    fun setup() {
-        MockitoAnnotations.initMocks(this)
-        val testCoroutineScope = TestCoroutineScope()
-        underTest =
-            KeyguardBouncerRepositoryImpl(
-                systemClock,
-                testCoroutineScope,
-                bouncerLogger,
-            )
-    }
-
-    @Test
-    fun changingFlowValueTriggersLogging() = runBlocking {
-        underTest.setPrimaryShow(true)
-        verify(bouncerLogger).logChange(eq(""), eq("PrimaryBouncerShow"), value = eq(false), any())
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
index 4b797cb..ba7d349 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
@@ -31,16 +31,20 @@
 import com.android.systemui.doze.DozeTransitionCallback
 import com.android.systemui.doze.DozeTransitionListener
 import com.android.systemui.dreams.DreamOverlayCallbackController
+import com.android.systemui.keyguard.ScreenLifecycle
 import com.android.systemui.keyguard.WakefulnessLifecycle
 import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
 import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
 import com.android.systemui.keyguard.shared.model.DozeStateModel
 import com.android.systemui.keyguard.shared.model.DozeTransitionModel
+import com.android.systemui.keyguard.shared.model.ScreenModel
+import com.android.systemui.keyguard.shared.model.ScreenState
 import com.android.systemui.keyguard.shared.model.WakefulnessModel
 import com.android.systemui.keyguard.shared.model.WakefulnessState
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.phone.BiometricUnlockController
 import com.android.systemui.statusbar.phone.DozeParameters
+import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.whenever
@@ -70,9 +74,11 @@
     @Mock private lateinit var statusBarStateController: StatusBarStateController
     @Mock private lateinit var keyguardStateController: KeyguardStateController
     @Mock private lateinit var wakefulnessLifecycle: WakefulnessLifecycle
+    @Mock private lateinit var screenLifecycle: ScreenLifecycle
     @Mock private lateinit var biometricUnlockController: BiometricUnlockController
     @Mock private lateinit var dozeTransitionListener: DozeTransitionListener
     @Mock private lateinit var authController: AuthController
+    @Mock private lateinit var keyguardBypassController: KeyguardBypassController
     @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
     @Mock private lateinit var dreamOverlayCallbackController: DreamOverlayCallbackController
     @Mock private lateinit var dozeParameters: DozeParameters
@@ -90,14 +96,17 @@
             KeyguardRepositoryImpl(
                 statusBarStateController,
                 wakefulnessLifecycle,
+                screenLifecycle,
                 biometricUnlockController,
                 keyguardStateController,
+                keyguardBypassController,
                 keyguardUpdateMonitor,
                 dozeTransitionListener,
                 dozeParameters,
                 authController,
                 dreamOverlayCallbackController,
-                mainDispatcher
+                mainDispatcher,
+                testScope.backgroundScope,
             )
     }
 
@@ -156,6 +165,16 @@
         }
 
     @Test
+    fun dozeTimeTick() =
+        testScope.runTest {
+            var dozeTimeTickValue = collectLastValue(underTest.dozeTimeTick)
+            underTest.dozeTimeTick()
+            runCurrent()
+
+            assertThat(dozeTimeTickValue()).isNull()
+        }
+
+    @Test
     fun isKeyguardShowing() =
         testScope.runTest {
             whenever(keyguardStateController.isShowing).thenReturn(false)
@@ -185,6 +204,20 @@
         }
 
     @Test
+    fun isBypassEnabled_disabledInController() {
+        whenever(keyguardBypassController.isBypassEnabled).thenReturn(false)
+        whenever(keyguardBypassController.bypassEnabled).thenReturn(false)
+        assertThat(underTest.isBypassEnabled()).isFalse()
+    }
+
+    @Test
+    fun isBypassEnabled_enabledInController() {
+        whenever(keyguardBypassController.isBypassEnabled).thenReturn(true)
+        whenever(keyguardBypassController.bypassEnabled).thenReturn(true)
+        assertThat(underTest.isBypassEnabled()).isTrue()
+    }
+
+    @Test
     fun isAodAvailable() = runTest {
         val flow = underTest.isAodAvailable
         var isAodAvailable = collectLastValue(flow)
@@ -234,11 +267,10 @@
     fun isKeyguardUnlocked() =
         testScope.runTest {
             whenever(keyguardStateController.isUnlocked).thenReturn(false)
-            var latest: Boolean? = null
-            val job = underTest.isKeyguardUnlocked.onEach { latest = it }.launchIn(this)
+            val isKeyguardUnlocked by collectLastValue(underTest.isKeyguardUnlocked)
 
             runCurrent()
-            assertThat(latest).isFalse()
+            assertThat(isKeyguardUnlocked).isFalse()
 
             val captor = argumentCaptor<KeyguardStateController.Callback>()
             verify(keyguardStateController).addCallback(captor.capture())
@@ -246,14 +278,12 @@
             whenever(keyguardStateController.isUnlocked).thenReturn(true)
             captor.value.onUnlockedChanged()
             runCurrent()
-            assertThat(latest).isTrue()
+            assertThat(isKeyguardUnlocked).isTrue()
 
             whenever(keyguardStateController.isUnlocked).thenReturn(false)
-            captor.value.onUnlockedChanged()
+            captor.value.onKeyguardShowingChanged()
             runCurrent()
-            assertThat(latest).isFalse()
-
-            job.cancel()
+            assertThat(isKeyguardUnlocked).isFalse()
         }
 
     @Test
@@ -301,6 +331,16 @@
         }
 
     @Test
+    fun isActiveDreamLockscreenHosted() =
+        testScope.runTest {
+            underTest.setIsActiveDreamLockscreenHosted(true)
+            assertThat(underTest.isActiveDreamLockscreenHosted.value).isEqualTo(true)
+
+            underTest.setIsActiveDreamLockscreenHosted(false)
+            assertThat(underTest.isActiveDreamLockscreenHosted.value).isEqualTo(false)
+        }
+
+    @Test
     fun wakefulness() =
         testScope.runTest {
             val values = mutableListOf<WakefulnessModel>()
@@ -343,8 +383,48 @@
                 )
 
             job.cancel()
+        }
+
+    @Test
+    fun screenModel() =
+        testScope.runTest {
+            val values = mutableListOf<ScreenModel>()
+            val job = underTest.screenModel.onEach(values::add).launchIn(this)
+
             runCurrent()
-            verify(wakefulnessLifecycle).removeObserver(captor.value)
+            val captor = argumentCaptor<ScreenLifecycle.Observer>()
+            verify(screenLifecycle).addObserver(captor.capture())
+
+            whenever(screenLifecycle.getScreenState()).thenReturn(ScreenLifecycle.SCREEN_TURNING_ON)
+            captor.value.onScreenTurningOn()
+            runCurrent()
+
+            whenever(screenLifecycle.getScreenState()).thenReturn(ScreenLifecycle.SCREEN_ON)
+            captor.value.onScreenTurnedOn()
+            runCurrent()
+
+            whenever(screenLifecycle.getScreenState())
+                .thenReturn(ScreenLifecycle.SCREEN_TURNING_OFF)
+            captor.value.onScreenTurningOff()
+            runCurrent()
+
+            whenever(screenLifecycle.getScreenState()).thenReturn(ScreenLifecycle.SCREEN_OFF)
+            captor.value.onScreenTurnedOff()
+            runCurrent()
+
+            assertThat(values.map { it.state })
+                .isEqualTo(
+                    listOf(
+                        // Initial value will be OFF
+                        ScreenState.SCREEN_OFF,
+                        ScreenState.SCREEN_TURNING_ON,
+                        ScreenState.SCREEN_ON,
+                        ScreenState.SCREEN_TURNING_OFF,
+                        ScreenState.SCREEN_OFF,
+                    )
+                )
+
+            job.cancel()
         }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt
deleted file mode 100644
index ca6b8d5..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt
+++ /dev/null
@@ -1,178 +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.systemui.keyguard.domain.interactor
-
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.systemui.RoboPilotTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
-import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
-import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository
-import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepositoryImpl
-import com.android.systemui.log.table.TableLogBuffer
-import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.statusbar.policy.KeyguardStateController
-import com.android.systemui.util.mockito.whenever
-import com.android.systemui.util.time.FakeSystemClock
-import com.android.systemui.util.time.SystemClock
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestCoroutineScope
-import org.junit.Assert.assertFalse
-import org.junit.Assert.assertTrue
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.MockitoAnnotations
-
-@OptIn(ExperimentalCoroutinesApi::class)
-@SmallTest
-@RoboPilotTest
-@RunWith(AndroidJUnit4::class)
-class AlternateBouncerInteractorTest : SysuiTestCase() {
-    private lateinit var underTest: AlternateBouncerInteractor
-    private lateinit var bouncerRepository: KeyguardBouncerRepository
-    private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository
-    private lateinit var deviceEntryFingerprintAuthRepository:
-        FakeDeviceEntryFingerprintAuthRepository
-    @Mock private lateinit var statusBarStateController: StatusBarStateController
-    @Mock private lateinit var keyguardStateController: KeyguardStateController
-    @Mock private lateinit var systemClock: SystemClock
-    @Mock private lateinit var bouncerLogger: TableLogBuffer
-
-    @Before
-    fun setup() {
-        MockitoAnnotations.initMocks(this)
-        bouncerRepository =
-            KeyguardBouncerRepositoryImpl(
-                FakeSystemClock(),
-                TestCoroutineScope(),
-                bouncerLogger,
-            )
-        biometricSettingsRepository = FakeBiometricSettingsRepository()
-        deviceEntryFingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository()
-        underTest =
-            AlternateBouncerInteractor(
-                statusBarStateController,
-                keyguardStateController,
-                bouncerRepository,
-                biometricSettingsRepository,
-                deviceEntryFingerprintAuthRepository,
-                systemClock,
-            )
-    }
-
-    @Test
-    fun canShowAlternateBouncerForFingerprint_givenCanShow() {
-        givenCanShowAlternateBouncer()
-        assertTrue(underTest.canShowAlternateBouncerForFingerprint())
-    }
-
-    @Test
-    fun canShowAlternateBouncerForFingerprint_alternateBouncerUIUnavailable() {
-        givenCanShowAlternateBouncer()
-        bouncerRepository.setAlternateBouncerUIAvailable(false)
-
-        assertFalse(underTest.canShowAlternateBouncerForFingerprint())
-    }
-
-    @Test
-    fun canShowAlternateBouncerForFingerprint_noFingerprintsEnrolled() {
-        givenCanShowAlternateBouncer()
-        biometricSettingsRepository.setFingerprintEnrolled(false)
-
-        assertFalse(underTest.canShowAlternateBouncerForFingerprint())
-    }
-
-    @Test
-    fun canShowAlternateBouncerForFingerprint_strongBiometricNotAllowed() {
-        givenCanShowAlternateBouncer()
-        biometricSettingsRepository.setStrongBiometricAllowed(false)
-
-        assertFalse(underTest.canShowAlternateBouncerForFingerprint())
-    }
-
-    @Test
-    fun canShowAlternateBouncerForFingerprint_devicePolicyDoesNotAllowFingerprint() {
-        givenCanShowAlternateBouncer()
-        biometricSettingsRepository.setFingerprintEnabledByDevicePolicy(false)
-
-        assertFalse(underTest.canShowAlternateBouncerForFingerprint())
-    }
-
-    @Test
-    fun canShowAlternateBouncerForFingerprint_fingerprintLockedOut() {
-        givenCanShowAlternateBouncer()
-        deviceEntryFingerprintAuthRepository.setLockedOut(true)
-
-        assertFalse(underTest.canShowAlternateBouncerForFingerprint())
-    }
-
-    @Test
-    fun show_whenCanShow() {
-        givenCanShowAlternateBouncer()
-
-        assertTrue(underTest.show())
-        assertTrue(bouncerRepository.alternateBouncerVisible.value)
-    }
-
-    @Test
-    fun canShowAlternateBouncerForFingerprint_butCanDismissLockScreen() {
-        givenCanShowAlternateBouncer()
-        whenever(keyguardStateController.isUnlocked).thenReturn(true)
-
-        assertFalse(underTest.canShowAlternateBouncerForFingerprint())
-    }
-
-    @Test
-    fun show_whenCannotShow() {
-        givenCannotShowAlternateBouncer()
-
-        assertFalse(underTest.show())
-        assertFalse(bouncerRepository.alternateBouncerVisible.value)
-    }
-
-    @Test
-    fun hide_wasPreviouslyShowing() {
-        bouncerRepository.setAlternateVisible(true)
-
-        assertTrue(underTest.hide())
-        assertFalse(bouncerRepository.alternateBouncerVisible.value)
-    }
-
-    @Test
-    fun hide_wasNotPreviouslyShowing() {
-        bouncerRepository.setAlternateVisible(false)
-
-        assertFalse(underTest.hide())
-        assertFalse(bouncerRepository.alternateBouncerVisible.value)
-    }
-
-    private fun givenCanShowAlternateBouncer() {
-        bouncerRepository.setAlternateBouncerUIAvailable(true)
-        biometricSettingsRepository.setFingerprintEnrolled(true)
-        biometricSettingsRepository.setStrongBiometricAllowed(true)
-        biometricSettingsRepository.setFingerprintEnabledByDevicePolicy(true)
-        deviceEntryFingerprintAuthRepository.setLockedOut(false)
-        whenever(keyguardStateController.isUnlocked).thenReturn(false)
-    }
-
-    private fun givenCannotShowAlternateBouncer() {
-        biometricSettingsRepository.setFingerprintEnrolled(false)
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BiometricMessageInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BiometricMessageInteractorTest.kt
new file mode 100644
index 0000000..3389fa9
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BiometricMessageInteractorTest.kt
@@ -0,0 +1,260 @@
+/*
+ *  Copyright (C) 2023 The Android Open Source Project
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS 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 android.hardware.biometrics.BiometricSourceType.FINGERPRINT
+import android.hardware.fingerprint.FingerprintManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
+import com.android.systemui.biometrics.shared.model.FingerprintSensorType
+import com.android.systemui.biometrics.shared.model.SensorStrength
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.HelpFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.util.IndicationHelper
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+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.ArgumentMatchers.anyBoolean
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class BiometricMessageInteractorTest : SysuiTestCase() {
+
+    private lateinit var underTest: BiometricMessageInteractor
+    private lateinit var testScope: TestScope
+    private lateinit var fingerprintPropertyRepository: FakeFingerprintPropertyRepository
+    private lateinit var fingerprintAuthRepository: FakeDeviceEntryFingerprintAuthRepository
+
+    @Mock private lateinit var indicationHelper: IndicationHelper
+    @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        testScope = TestScope()
+        fingerprintPropertyRepository = FakeFingerprintPropertyRepository()
+        fingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository()
+        underTest =
+            BiometricMessageInteractor(
+                mContext.resources,
+                fingerprintAuthRepository,
+                fingerprintPropertyRepository,
+                indicationHelper,
+                keyguardUpdateMonitor,
+            )
+    }
+
+    @Test
+    fun fingerprintErrorMessage() =
+        testScope.runTest {
+            val fingerprintErrorMessage by collectLastValue(underTest.fingerprintErrorMessage)
+
+            // GIVEN FINGERPRINT_ERROR_HW_UNAVAILABLE should NOT be suppressed
+            whenever(
+                    indicationHelper.shouldSuppressErrorMsg(
+                        FINGERPRINT,
+                        FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE
+                    )
+                )
+                .thenReturn(false)
+
+            // WHEN authentication status error is FINGERPRINT_ERROR_HW_UNAVAILABLE
+            fingerprintAuthRepository.setAuthenticationStatus(
+                ErrorFingerprintAuthenticationStatus(
+                    msgId = FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE,
+                    msg = "test"
+                )
+            )
+
+            // THEN fingerprintErrorMessage is updated
+            assertThat(fingerprintErrorMessage?.source).isEqualTo(FINGERPRINT)
+            assertThat(fingerprintErrorMessage?.type).isEqualTo(BiometricMessageType.ERROR)
+            assertThat(fingerprintErrorMessage?.id)
+                .isEqualTo(FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE)
+            assertThat(fingerprintErrorMessage?.message).isEqualTo("test")
+        }
+
+    @Test
+    fun fingerprintErrorMessage_suppressedError() =
+        testScope.runTest {
+            val fingerprintErrorMessage by collectLastValue(underTest.fingerprintErrorMessage)
+
+            // GIVEN FINGERPRINT_ERROR_HW_UNAVAILABLE should be suppressed
+            whenever(
+                    indicationHelper.shouldSuppressErrorMsg(
+                        FINGERPRINT,
+                        FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE
+                    )
+                )
+                .thenReturn(true)
+
+            // WHEN authentication status error is FINGERPRINT_ERROR_HW_UNAVAILABLE
+            fingerprintAuthRepository.setAuthenticationStatus(
+                ErrorFingerprintAuthenticationStatus(
+                    msgId = FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE,
+                    msg = "test"
+                )
+            )
+
+            // THEN fingerprintErrorMessage isn't update - it's still null
+            assertThat(fingerprintErrorMessage).isNull()
+        }
+
+    @Test
+    fun fingerprintHelpMessage() =
+        testScope.runTest {
+            val fingerprintHelpMessage by collectLastValue(underTest.fingerprintHelpMessage)
+
+            // GIVEN primary auth is NOT required
+            whenever(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean()))
+                .thenReturn(true)
+
+            // WHEN authentication status help is FINGERPRINT_ACQUIRED_IMAGER_DIRTY
+            fingerprintAuthRepository.setAuthenticationStatus(
+                HelpFingerprintAuthenticationStatus(
+                    msgId = FingerprintManager.FINGERPRINT_ACQUIRED_IMAGER_DIRTY,
+                    msg = "test"
+                )
+            )
+
+            // THEN fingerprintHelpMessage is updated
+            assertThat(fingerprintHelpMessage?.source).isEqualTo(FINGERPRINT)
+            assertThat(fingerprintHelpMessage?.type).isEqualTo(BiometricMessageType.HELP)
+            assertThat(fingerprintHelpMessage?.id)
+                .isEqualTo(FingerprintManager.FINGERPRINT_ACQUIRED_IMAGER_DIRTY)
+            assertThat(fingerprintHelpMessage?.message).isEqualTo("test")
+        }
+
+    @Test
+    fun fingerprintHelpMessage_primaryAuthRequired() =
+        testScope.runTest {
+            val fingerprintHelpMessage by collectLastValue(underTest.fingerprintHelpMessage)
+
+            // GIVEN primary auth is required
+            whenever(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean()))
+                .thenReturn(false)
+
+            // WHEN authentication status help is FINGERPRINT_ACQUIRED_IMAGER_DIRTY
+            fingerprintAuthRepository.setAuthenticationStatus(
+                HelpFingerprintAuthenticationStatus(
+                    msgId = FingerprintManager.FINGERPRINT_ACQUIRED_IMAGER_DIRTY,
+                    msg = "test"
+                )
+            )
+
+            // THEN fingerprintHelpMessage isn't update - it's still null
+            assertThat(fingerprintHelpMessage).isNull()
+        }
+
+    @Test
+    fun fingerprintFailMessage_nonUdfps() =
+        testScope.runTest {
+            val fingerprintFailMessage by collectLastValue(underTest.fingerprintFailMessage)
+
+            // GIVEN primary auth is NOT required
+            whenever(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean()))
+                .thenReturn(true)
+
+            // GIVEN rear fingerprint (not UDFPS)
+            fingerprintPropertyRepository.setProperties(
+                0,
+                SensorStrength.STRONG,
+                FingerprintSensorType.REAR,
+                mapOf()
+            )
+
+            // WHEN authentication status fail
+            fingerprintAuthRepository.setAuthenticationStatus(FailFingerprintAuthenticationStatus)
+
+            // THEN fingerprintFailMessage is updated
+            assertThat(fingerprintFailMessage?.source).isEqualTo(FINGERPRINT)
+            assertThat(fingerprintFailMessage?.type).isEqualTo(BiometricMessageType.FAIL)
+            assertThat(fingerprintFailMessage?.id)
+                .isEqualTo(BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED)
+            assertThat(fingerprintFailMessage?.message)
+                .isEqualTo(
+                    mContext.resources.getString(
+                        com.android.internal.R.string.fingerprint_error_not_match
+                    )
+                )
+        }
+
+    @Test
+    fun fingerprintFailMessage_udfps() =
+        testScope.runTest {
+            val fingerprintFailMessage by collectLastValue(underTest.fingerprintFailMessage)
+
+            // GIVEN primary auth is NOT required
+            whenever(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean()))
+                .thenReturn(true)
+
+            // GIVEN UDFPS
+            fingerprintPropertyRepository.setProperties(
+                0,
+                SensorStrength.STRONG,
+                FingerprintSensorType.UDFPS_OPTICAL,
+                mapOf()
+            )
+
+            // WHEN authentication status fail
+            fingerprintAuthRepository.setAuthenticationStatus(FailFingerprintAuthenticationStatus)
+
+            // THEN fingerprintFailMessage is updated to udfps message
+            assertThat(fingerprintFailMessage?.source).isEqualTo(FINGERPRINT)
+            assertThat(fingerprintFailMessage?.type).isEqualTo(BiometricMessageType.FAIL)
+            assertThat(fingerprintFailMessage?.id)
+                .isEqualTo(BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED)
+            assertThat(fingerprintFailMessage?.message)
+                .isEqualTo(
+                    mContext.resources.getString(
+                        com.android.internal.R.string.fingerprint_udfps_error_not_match
+                    )
+                )
+        }
+
+    @Test
+    fun fingerprintFailedMessage_primaryAuthRequired() =
+        testScope.runTest {
+            val fingerprintFailedMessage by collectLastValue(underTest.fingerprintFailMessage)
+
+            // GIVEN primary auth is required
+            whenever(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean()))
+                .thenReturn(false)
+
+            // WHEN authentication status fail
+            fingerprintAuthRepository.setAuthenticationStatus(FailFingerprintAuthenticationStatus)
+
+            // THEN fingerprintFailedMessage isn't update - it's still null
+            assertThat(fingerprintFailedMessage).isNull()
+        }
+}
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 71e73cbd..ced0a21 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
@@ -17,6 +17,7 @@
 
 package com.android.systemui.keyguard.domain.interactor
 
+import android.hardware.biometrics.BiometricFaceConstants
 import android.os.Handler
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
@@ -24,18 +25,22 @@
 import com.android.keyguard.KeyguardSecurityModel
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
+import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
+import com.android.systemui.bouncer.ui.BouncerView
 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.BouncerView
 import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
 import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepository
-import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
-import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.data.repository.FakeTrustRepository
+import com.android.systemui.keyguard.shared.model.ErrorFaceAuthenticationStatus
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -83,10 +88,15 @@
         faceAuthRepository = FakeDeviceEntryFaceAuthRepository()
         keyguardTransitionRepository = FakeKeyguardTransitionRepository()
         keyguardTransitionInteractor =
-            KeyguardTransitionInteractor(keyguardTransitionRepository, testScope.backgroundScope)
+            KeyguardTransitionInteractorFactory.create(
+                    scope = TestScope().backgroundScope,
+                    repository = keyguardTransitionRepository,
+                )
+                .keyguardTransitionInteractor
 
         underTest =
             SystemUIKeyguardFaceAuthInteractor(
+                mContext,
                 testScope.backgroundScope,
                 dispatcher,
                 faceAuthRepository,
@@ -110,8 +120,8 @@
                     mock(KeyguardStateController::class.java),
                     bouncerRepository,
                     mock(BiometricSettingsRepository::class.java),
-                    FakeDeviceEntryFingerprintAuthRepository(),
                     FakeSystemClock(),
+                    keyguardUpdateMonitor,
                 ),
                 keyguardTransitionInteractor,
                 featureFlags,
@@ -141,6 +151,22 @@
         }
 
     @Test
+    fun whenFaceIsLockedOutAnyAttemptsToTriggerFaceAuthMustProvideLockoutError() =
+        testScope.runTest {
+            underTest.start()
+            val authenticationStatus = collectLastValue(underTest.authenticationStatus)
+            faceAuthRepository.setLockedOut(true)
+
+            underTest.onDeviceLifted()
+
+            val outputValue = authenticationStatus()!! as ErrorFaceAuthenticationStatus
+            assertThat(outputValue.msgId)
+                .isEqualTo(BiometricFaceConstants.FACE_ERROR_LOCKOUT_PERMANENT)
+            assertThat(outputValue.msg).isEqualTo("Face Unlock unavailable")
+            assertThat(faceAuthRepository.runningAuthRequest.value).isNull()
+        }
+
+    @Test
     fun faceAuthIsRequestedWhenLockscreenBecomesVisibleFromAodState() =
         testScope.runTest {
             underTest.start()
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 0d695aa..4b09468 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
@@ -22,11 +22,12 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
+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.FACE_AUTH_REFACTOR
 import com.android.systemui.keyguard.data.repository.FakeCommandQueue
-import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.keyguard.shared.model.CameraLaunchSourceModel
 import com.google.common.truth.Truth.assertThat
@@ -50,6 +51,7 @@
     private lateinit var underTest: KeyguardInteractor
     private lateinit var repository: FakeKeyguardRepository
     private lateinit var bouncerRepository: FakeKeyguardBouncerRepository
+    private lateinit var configurationRepository: FakeConfigurationRepository
 
     @Before
     fun setUp() {
@@ -59,12 +61,14 @@
         testScope = TestScope()
         repository = FakeKeyguardRepository()
         bouncerRepository = FakeKeyguardBouncerRepository()
+        configurationRepository = FakeConfigurationRepository()
         underTest =
             KeyguardInteractor(
                 repository,
                 commandQueue,
                 featureFlags,
                 bouncerRepository,
+                configurationRepository,
             )
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt
index f63be61..0050d64 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt
@@ -194,9 +194,10 @@
             underTest.onLongPress()
             assertThat(isMenuVisible).isTrue()
 
-            fakeBroadcastDispatcher.registeredReceivers.forEach { broadcastReceiver ->
-                broadcastReceiver.onReceive(context, Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS))
-            }
+            fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+                context,
+                Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS),
+            )
 
             assertThat(isMenuVisible).isFalse()
         }
@@ -291,10 +292,11 @@
                 appContext = mContext,
                 scope = testScope.backgroundScope,
                 transitionInteractor =
-                    KeyguardTransitionInteractor(
-                        keyguardTransitionRepository,
-                        testScope.backgroundScope
-                    ),
+                    KeyguardTransitionInteractorFactory.create(
+                            scope = testScope.backgroundScope,
+                            repository = keyguardTransitionRepository,
+                        )
+                        .keyguardTransitionInteractor,
                 repository = keyguardRepository,
                 logger = logger,
                 featureFlags =
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 2c90d53..3858cfd 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
@@ -39,17 +39,13 @@
 import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLocalUserSelectionManager
 import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceRemoteUserSelectionManager
 import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
-import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
-import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceRegistry
 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.settings.FakeUserTracker
 import com.android.systemui.settings.UserFileManager
 import com.android.systemui.settings.UserTracker
-import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.FakeSharedPreferences
 import com.android.systemui.util.mockito.any
@@ -226,7 +222,6 @@
     @Mock private lateinit var animationController: ActivityLaunchAnimator.Controller
     @Mock private lateinit var expandable: Expandable
     @Mock private lateinit var launchAnimator: DialogLaunchAnimator
-    @Mock private lateinit var commandQueue: CommandQueue
     @Mock private lateinit var devicePolicyManager: DevicePolicyManager
     @Mock private lateinit var logger: KeyguardQuickAffordancesMetricsLogger
 
@@ -304,7 +299,6 @@
             )
         val featureFlags =
             FakeFeatureFlags().apply {
-                set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, false)
                 set(Flags.FACE_AUTH_REFACTOR, true)
             }
         val testDispatcher = StandardTestDispatcher()
@@ -312,26 +306,10 @@
         underTest =
             KeyguardQuickAffordanceInteractor(
                 keyguardInteractor =
-                    KeyguardInteractor(
-                        repository = FakeKeyguardRepository(),
-                        commandQueue = commandQueue,
-                        featureFlags = featureFlags,
-                        bouncerRepository = FakeKeyguardBouncerRepository(),
-                    ),
-                registry =
-                    FakeKeyguardQuickAffordanceRegistry(
-                        mapOf(
-                            KeyguardQuickAffordancePosition.BOTTOM_START to
-                                listOf(
-                                    homeControls,
-                                ),
-                            KeyguardQuickAffordancePosition.BOTTOM_END to
-                                listOf(
-                                    quickAccessWallet,
-                                    qrCodeScanner,
-                                ),
-                        ),
-                    ),
+                    KeyguardInteractorFactory.create(
+                            featureFlags = featureFlags,
+                        )
+                        .keyguardInteractor,
                 lockPatternUtils = lockPatternUtils,
                 keyguardStateController = keyguardStateController,
                 userTracker = userTracker,
@@ -351,6 +329,7 @@
     @Test
     fun onQuickAffordanceTriggered() =
         testScope.runTest {
+            val key = BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS
             setUpMocks(
                 needStrongAuthAfterBoot = needStrongAuthAfterBoot,
                 keyguardIsUnlocked = keyguardIsUnlocked,
@@ -372,11 +351,11 @@
                     KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
                 }
 
-        underTest.onQuickAffordanceTriggered(
-            configKey = BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS,
-            expandable = expandable,
-            slotId = "",
-        )
+            underTest.onQuickAffordanceTriggered(
+                configKey = "${KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId()}::${key}",
+                expandable = expandable,
+                slotId = "",
+            )
 
             if (startActivity) {
                 if (needsToUnlockFirst) {
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 7cab680..07caf59 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
@@ -41,11 +41,9 @@
 import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLocalUserSelectionManager
 import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceRemoteUserSelectionManager
 import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
-import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
 import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel
-import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceRegistry
 import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePickerRepresentation
 import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
 import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
@@ -54,7 +52,6 @@
 import com.android.systemui.settings.UserFileManager
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
-import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.FakeSharedPreferences
 import com.android.systemui.util.mockito.mock
@@ -85,7 +82,6 @@
     @Mock private lateinit var userTracker: UserTracker
     @Mock private lateinit var activityStarter: ActivityStarter
     @Mock private lateinit var launchAnimator: DialogLaunchAnimator
-    @Mock private lateinit var commandQueue: CommandQueue
     @Mock private lateinit var devicePolicyManager: DevicePolicyManager
     @Mock private lateinit var logger: KeyguardQuickAffordancesMetricsLogger
 
@@ -105,6 +101,15 @@
         MockitoAnnotations.initMocks(this)
 
         overrideResource(R.bool.custom_lockscreen_shortcuts_enabled, true)
+        overrideResource(
+            R.array.config_keyguardQuickAffordanceDefaults,
+            arrayOf(
+                KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START + ":" +
+                    BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS,
+                KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END + ":" +
+                    BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
+            )
+        )
 
         repository = FakeKeyguardRepository()
         repository.setKeyguardShowing(true)
@@ -167,33 +172,17 @@
             )
         featureFlags =
             FakeFeatureFlags().apply {
-                set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, false)
                 set(Flags.FACE_AUTH_REFACTOR, true)
             }
 
+        val withDeps =
+            KeyguardInteractorFactory.create(
+                featureFlags = featureFlags,
+                repository = repository,
+            )
         underTest =
             KeyguardQuickAffordanceInteractor(
-                keyguardInteractor =
-                    KeyguardInteractor(
-                        repository = repository,
-                        commandQueue = commandQueue,
-                        featureFlags = featureFlags,
-                        bouncerRepository = FakeKeyguardBouncerRepository(),
-                    ),
-                registry =
-                    FakeKeyguardQuickAffordanceRegistry(
-                        mapOf(
-                            KeyguardQuickAffordancePosition.BOTTOM_START to
-                                listOf(
-                                    homeControls,
-                                ),
-                            KeyguardQuickAffordancePosition.BOTTOM_END to
-                                listOf(
-                                    quickAccessWallet,
-                                    qrCodeScanner,
-                                ),
-                        ),
-                    ),
+                keyguardInteractor = withDeps.keyguardInteractor,
                 lockPatternUtils = lockPatternUtils,
                 keyguardStateController = keyguardStateController,
                 userTracker = userTracker,
@@ -229,7 +218,9 @@
             assertThat(collectedValue())
                 .isInstanceOf(KeyguardQuickAffordanceModel.Visible::class.java)
             val visibleModel = collectedValue() as KeyguardQuickAffordanceModel.Visible
-            assertThat(visibleModel.configKey).isEqualTo(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))
@@ -254,7 +245,9 @@
             assertThat(collectedValue())
                 .isInstanceOf(KeyguardQuickAffordanceModel.Visible::class.java)
             val visibleModel = collectedValue() as KeyguardQuickAffordanceModel.Visible
-            assertThat(visibleModel.configKey).isEqualTo(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))
@@ -391,7 +384,9 @@
             assertThat(collectedValue())
                 .isInstanceOf(KeyguardQuickAffordanceModel.Visible::class.java)
             val visibleModel = collectedValue() as KeyguardQuickAffordanceModel.Visible
-            assertThat(visibleModel.configKey).isEqualTo(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))
@@ -405,8 +400,6 @@
                 R.array.config_keyguardQuickAffordanceDefaults,
                 arrayOf<String>(),
             )
-
-            featureFlags.set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, true)
             homeControls.setState(
                 KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = ICON)
             )
@@ -547,7 +540,6 @@
     @Test
     fun unselect_one() =
         testScope.runTest {
-            featureFlags.set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, true)
             homeControls.setState(
                 KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = ICON)
             )
@@ -624,7 +616,6 @@
     @Test
     fun useLongPress_whenDocked_isFalse() =
         testScope.runTest {
-            featureFlags.set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, true)
             dockManager.setIsDocked(true)
 
             val useLongPress by collectLastValue(underTest.useLongPress())
@@ -635,7 +626,6 @@
     @Test
     fun useLongPress_whenNotDocked_isTrue() =
         testScope.runTest {
-            featureFlags.set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, true)
             dockManager.setIsDocked(false)
 
             val useLongPress by collectLastValue(underTest.useLongPress())
@@ -646,7 +636,6 @@
     @Test
     fun useLongPress_whenNotDocked_isTrue_changedTo_whenDocked_isFalse() =
         testScope.runTest {
-            featureFlags.set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, true)
             dockManager.setIsDocked(false)
             val firstUseLongPress by collectLastValue(underTest.useLongPress())
             runCurrent()
@@ -664,7 +653,6 @@
     @Test
     fun unselect_all() =
         testScope.runTest {
-            featureFlags.set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, true)
             homeControls.setState(
                 KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = ICON)
             )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
index fa4941c..9e9c25e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
@@ -17,9 +17,9 @@
 
 package com.android.systemui.keyguard.domain.interactor
 
-import com.android.systemui.RoboPilotTest
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectValues
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
@@ -54,7 +54,10 @@
     @Before
     fun setUp() {
         repository = FakeKeyguardTransitionRepository()
-        underTest = KeyguardTransitionInteractor(repository, testScope.backgroundScope)
+        underTest = KeyguardTransitionInteractorFactory.create(
+                scope = testScope.backgroundScope,
+                repository = repository,
+        ).keyguardTransitionInteractor
     }
 
     @Test
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 30dba23..d01a46e 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
@@ -19,14 +19,13 @@
 import androidx.test.filters.SmallTest
 import com.android.keyguard.KeyguardSecurityModel
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode.PIN
+import com.android.keyguard.TestScopeProvider
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
 import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
-import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
 import com.android.systemui.keyguard.shared.model.DozeStateModel
 import com.android.systemui.keyguard.shared.model.DozeTransitionModel
@@ -39,7 +38,6 @@
 import com.android.systemui.keyguard.shared.model.WakefulnessState
 import com.android.systemui.shade.data.repository.FakeShadeRepository
 import com.android.systemui.shade.data.repository.ShadeRepository
-import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.mockito.withArgCaptor
@@ -58,6 +56,7 @@
 import org.mockito.Mock
 import org.mockito.Mockito.never
 import org.mockito.Mockito.reset
+import org.mockito.Mockito.spy
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
@@ -74,10 +73,10 @@
     private lateinit var bouncerRepository: FakeKeyguardBouncerRepository
     private lateinit var shadeRepository: ShadeRepository
     private lateinit var transitionRepository: FakeKeyguardTransitionRepository
+    private lateinit var transitionInteractor: KeyguardTransitionInteractor
+    private lateinit var featureFlags: FakeFeatureFlags
 
     // Used to verify transition requests for test output
-    @Mock private lateinit var mockTransitionRepository: KeyguardTransitionRepository
-    @Mock private lateinit var commandQueue: CommandQueue
     @Mock private lateinit var keyguardSecurityModel: KeyguardSecurityModel
 
     private lateinit var fromLockscreenTransitionInteractor: FromLockscreenTransitionInteractor
@@ -94,97 +93,97 @@
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
-        testScope = TestScope()
+        testScope = TestScopeProvider.getTestScope()
 
         keyguardRepository = FakeKeyguardRepository()
         bouncerRepository = FakeKeyguardBouncerRepository()
         shadeRepository = FakeShadeRepository()
-        transitionRepository = FakeKeyguardTransitionRepository()
+        transitionRepository = spy(FakeKeyguardTransitionRepository())
 
         whenever(keyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(PIN)
 
-        val featureFlags = FakeFeatureFlags().apply { set(Flags.FACE_AUTH_REFACTOR, true) }
+        featureFlags = FakeFeatureFlags().apply { set(Flags.FACE_AUTH_REFACTOR, true) }
+
+        transitionInteractor =
+            KeyguardTransitionInteractorFactory.create(
+                    scope = testScope,
+                    repository = transitionRepository,
+                )
+                .keyguardTransitionInteractor
+
         fromLockscreenTransitionInteractor =
             FromLockscreenTransitionInteractor(
-                scope = testScope,
-                keyguardInteractor = createKeyguardInteractor(featureFlags),
-                shadeRepository = shadeRepository,
-                keyguardTransitionRepository = mockTransitionRepository,
-                keyguardTransitionInteractor =
-                    KeyguardTransitionInteractor(transitionRepository, testScope.backgroundScope),
-            )
-        fromLockscreenTransitionInteractor.start()
-
-        fromDreamingTransitionInteractor =
-            FromDreamingTransitionInteractor(
-                scope = testScope,
-                keyguardInteractor = createKeyguardInteractor(featureFlags),
-                keyguardTransitionRepository = mockTransitionRepository,
-                keyguardTransitionInteractor =
-                    KeyguardTransitionInteractor(transitionRepository, testScope.backgroundScope),
-            )
-        fromDreamingTransitionInteractor.start()
-
-        fromAodTransitionInteractor =
-            FromAodTransitionInteractor(
-                scope = testScope,
-                keyguardInteractor = createKeyguardInteractor(featureFlags),
-                keyguardTransitionRepository = mockTransitionRepository,
-                keyguardTransitionInteractor =
-                    KeyguardTransitionInteractor(transitionRepository, testScope.backgroundScope),
-            )
-        fromAodTransitionInteractor.start()
-
-        fromGoneTransitionInteractor =
-            FromGoneTransitionInteractor(
-                scope = testScope,
-                keyguardInteractor = createKeyguardInteractor(featureFlags),
-                keyguardTransitionRepository = mockTransitionRepository,
-                keyguardTransitionInteractor =
-                    KeyguardTransitionInteractor(transitionRepository, testScope.backgroundScope),
-            )
-        fromGoneTransitionInteractor.start()
-
-        fromDozingTransitionInteractor =
-            FromDozingTransitionInteractor(
-                scope = testScope,
-                keyguardInteractor = createKeyguardInteractor(featureFlags),
-                keyguardTransitionRepository = mockTransitionRepository,
-                keyguardTransitionInteractor =
-                    KeyguardTransitionInteractor(transitionRepository, testScope.backgroundScope),
-            )
-        fromDozingTransitionInteractor.start()
-
-        fromOccludedTransitionInteractor =
-            FromOccludedTransitionInteractor(
-                scope = testScope,
-                keyguardInteractor = createKeyguardInteractor(featureFlags),
-                keyguardTransitionRepository = mockTransitionRepository,
-                keyguardTransitionInteractor =
-                    KeyguardTransitionInteractor(transitionRepository, testScope.backgroundScope),
-            )
-        fromOccludedTransitionInteractor.start()
-
-        fromAlternateBouncerTransitionInteractor =
-            FromAlternateBouncerTransitionInteractor(
-                scope = testScope,
-                keyguardInteractor = createKeyguardInteractor(featureFlags),
-                keyguardTransitionRepository = mockTransitionRepository,
-                keyguardTransitionInteractor =
-                    KeyguardTransitionInteractor(transitionRepository, testScope.backgroundScope),
-            )
-        fromAlternateBouncerTransitionInteractor.start()
+                    scope = testScope,
+                    keyguardInteractor = createKeyguardInteractor(),
+                    transitionRepository = transitionRepository,
+                    transitionInteractor = transitionInteractor,
+                    shadeRepository = shadeRepository,
+                )
+                .apply { start() }
 
         fromPrimaryBouncerTransitionInteractor =
             FromPrimaryBouncerTransitionInteractor(
-                scope = testScope,
-                keyguardInteractor = createKeyguardInteractor(featureFlags),
-                keyguardTransitionRepository = mockTransitionRepository,
-                keyguardTransitionInteractor =
-                    KeyguardTransitionInteractor(transitionRepository, testScope.backgroundScope),
-                keyguardSecurityModel = keyguardSecurityModel,
-            )
-        fromPrimaryBouncerTransitionInteractor.start()
+                    scope = testScope,
+                    keyguardInteractor = createKeyguardInteractor(),
+                    transitionRepository = transitionRepository,
+                    transitionInteractor = transitionInteractor,
+                    keyguardSecurityModel = keyguardSecurityModel,
+                )
+                .apply { start() }
+
+        fromDreamingTransitionInteractor =
+            FromDreamingTransitionInteractor(
+                    scope = testScope,
+                    keyguardInteractor = createKeyguardInteractor(),
+                    transitionRepository = transitionRepository,
+                    transitionInteractor = transitionInteractor,
+                )
+                .apply { start() }
+
+        fromAodTransitionInteractor =
+            FromAodTransitionInteractor(
+                    scope = testScope,
+                    keyguardInteractor = createKeyguardInteractor(),
+                    transitionRepository = transitionRepository,
+                    transitionInteractor = transitionInteractor,
+                )
+                .apply { start() }
+
+        fromGoneTransitionInteractor =
+            FromGoneTransitionInteractor(
+                    scope = testScope,
+                    keyguardInteractor = createKeyguardInteractor(),
+                    transitionRepository = transitionRepository,
+                    transitionInteractor = transitionInteractor,
+                )
+                .apply { start() }
+
+        fromDozingTransitionInteractor =
+            FromDozingTransitionInteractor(
+                    scope = testScope,
+                    keyguardInteractor = createKeyguardInteractor(),
+                    transitionRepository = transitionRepository,
+                    transitionInteractor = transitionInteractor,
+                )
+                .apply { start() }
+
+        fromOccludedTransitionInteractor =
+            FromOccludedTransitionInteractor(
+                    scope = testScope,
+                    keyguardInteractor = createKeyguardInteractor(),
+                    transitionRepository = transitionRepository,
+                    transitionInteractor = transitionInteractor,
+                )
+                .apply { start() }
+
+        fromAlternateBouncerTransitionInteractor =
+            FromAlternateBouncerTransitionInteractor(
+                    scope = testScope,
+                    keyguardInteractor = createKeyguardInteractor(),
+                    transitionRepository = transitionRepository,
+                    transitionInteractor = transitionInteractor,
+                )
+                .apply { start() }
     }
 
     @Test
@@ -203,7 +202,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture(), anyBoolean())
                 }
             // THEN a transition to PRIMARY_BOUNCER should occur
             assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
@@ -230,7 +229,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture(), anyBoolean())
                 }
             // THEN a transition to DOZING should occur
             assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor")
@@ -257,7 +256,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture(), anyBoolean())
                 }
             // THEN a transition to DOZING should occur
             assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor")
@@ -288,7 +287,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture(), anyBoolean())
                 }
             // THEN a transition to DREAMING should occur
             assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
@@ -315,7 +314,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture(), anyBoolean())
                 }
             // THEN a transition to DOZING should occur
             assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
@@ -342,7 +341,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture(), anyBoolean())
                 }
             // THEN a transition to DOZING should occur
             assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
@@ -365,7 +364,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture(), anyBoolean())
                 }
             // THEN a transition to DOZING should occur
             assertThat(info.ownerName).isEqualTo("FromDozingTransitionInteractor")
@@ -390,14 +389,14 @@
                 )
             )
             runCurrent()
-            reset(mockTransitionRepository)
+            reset(transitionRepository)
 
             // WHEN a signal comes that dreaming is enabled
             keyguardRepository.setDreamingWithOverlay(true)
             advanceUntilIdle()
 
             // THEN the transition is ignored
-            verify(mockTransitionRepository, never()).startTransition(any(), anyBoolean())
+            verify(transitionRepository, never()).startTransition(any(), anyBoolean())
 
             coroutineContext.cancelChildren()
         }
@@ -414,7 +413,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture(), anyBoolean())
                 }
             // THEN a transition to DOZING should occur
             assertThat(info.ownerName).isEqualTo("FromDozingTransitionInteractor")
@@ -441,7 +440,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture(), anyBoolean())
                 }
             // THEN a transition to DOZING should occur
             assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor")
@@ -468,7 +467,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture(), anyBoolean())
                 }
             // THEN a transition to AOD should occur
             assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor")
@@ -491,7 +490,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture(), anyBoolean())
                 }
             // THEN a transition to AOD should occur
             assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor")
@@ -522,7 +521,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture(), anyBoolean())
                 }
             // THEN a transition to DREAMING should occur
             assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor")
@@ -545,7 +544,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture(), anyBoolean())
                 }
             // THEN a transition to PRIMARY_BOUNCER should occur
             assertThat(info.ownerName).isEqualTo("FromAlternateBouncerTransitionInteractor")
@@ -574,7 +573,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture(), anyBoolean())
                 }
             // THEN a transition to AOD should occur
             assertThat(info.ownerName).isEqualTo("FromAlternateBouncerTransitionInteractor")
@@ -604,7 +603,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture(), anyBoolean())
                 }
             // THEN a transition to DOZING should occur
             assertThat(info.ownerName).isEqualTo("FromAlternateBouncerTransitionInteractor")
@@ -632,7 +631,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture(), anyBoolean())
                 }
             // THEN a transition to LOCKSCREEN should occur
             assertThat(info.ownerName).isEqualTo("FromAlternateBouncerTransitionInteractor")
@@ -660,7 +659,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture(), anyBoolean())
                 }
             // THEN a transition to AOD should occur
             assertThat(info.ownerName).isEqualTo("FromPrimaryBouncerTransitionInteractor")
@@ -688,7 +687,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture(), anyBoolean())
                 }
             // THEN a transition to DOZING should occur
             assertThat(info.ownerName).isEqualTo("FromPrimaryBouncerTransitionInteractor")
@@ -715,7 +714,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture(), anyBoolean())
                 }
             // THEN a transition to LOCKSCREEN should occur
             assertThat(info.ownerName).isEqualTo("FromPrimaryBouncerTransitionInteractor")
@@ -746,7 +745,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture(), anyBoolean())
                 }
             // THEN a transition to GONE should occur
             assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor")
@@ -775,7 +774,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture(), anyBoolean())
                 }
             // THEN a transition to LOCKSCREEN should occur
             assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor")
@@ -801,7 +800,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture(), anyBoolean())
                 }
             // THEN a transition to AlternateBouncer should occur
             assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor")
@@ -830,7 +829,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture(), anyBoolean())
                 }
             // THEN a transition to AlternateBouncer should occur
             assertThat(info.ownerName).isEqualTo("FromPrimaryBouncerTransitionInteractor")
@@ -855,13 +854,13 @@
             WakeSleepReason.OTHER
         )
 
-    private fun createKeyguardInteractor(featureFlags: FeatureFlags): KeyguardInteractor {
-        return KeyguardInteractor(
-            keyguardRepository,
-            commandQueue,
-            featureFlags,
-            bouncerRepository,
-        )
+    private fun createKeyguardInteractor(): KeyguardInteractor {
+        return KeyguardInteractorFactory.create(
+                featureFlags = featureFlags,
+                repository = keyguardRepository,
+                bouncerRepository = bouncerRepository,
+            )
+            .keyguardInteractor
     }
 
     private suspend fun TestScope.runTransition(from: KeyguardState, to: KeyguardState) {
@@ -892,6 +891,6 @@
             )
         )
         runCurrent()
-        reset(mockTransitionRepository)
+        reset(transitionRepository)
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt
index 08e99dc..6e7ba6d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt
@@ -46,7 +46,11 @@
     private val fakeLightRevealScrimRepository = FakeLightRevealScrimRepository()
 
     private val keyguardTransitionInteractor =
-        KeyguardTransitionInteractor(fakeKeyguardTransitionRepository, TestScope().backgroundScope)
+        KeyguardTransitionInteractorFactory.create(
+                scope = TestScope().backgroundScope,
+                repository = fakeKeyguardTransitionRepository,
+            )
+            .keyguardTransitionInteractor
 
     private lateinit var underTest: LightRevealScrimInteractor
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractorTest.kt
index d622f1c..ca6a5b6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractorTest.kt
@@ -26,7 +26,6 @@
 import com.android.systemui.scene.shared.model.SceneModel
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
@@ -38,8 +37,8 @@
 @RunWith(JUnit4::class)
 class LockscreenSceneInteractorTest : SysuiTestCase() {
 
-    private val testScope = TestScope()
-    private val utils = SceneTestUtils(this, testScope)
+    private val utils = SceneTestUtils(this)
+    private val testScope = utils.testScope
     private val sceneInteractor = utils.sceneInteractor()
     private val authenticationInteractor =
         utils.authenticationInteractor(
@@ -48,7 +47,6 @@
     private val underTest =
         utils.lockScreenSceneInteractor(
             authenticationInteractor = authenticationInteractor,
-            sceneInteractor = sceneInteractor,
             bouncerInteractor =
                 utils.bouncerInteractor(
                     authenticationInteractor = authenticationInteractor,
@@ -61,10 +59,10 @@
         testScope.runTest {
             val isDeviceLocked by collectLastValue(underTest.isDeviceLocked)
 
-            authenticationInteractor.lockDevice()
+            utils.authenticationRepository.setUnlocked(false)
             assertThat(isDeviceLocked).isTrue()
 
-            authenticationInteractor.unlockDevice()
+            utils.authenticationRepository.setUnlocked(true)
             assertThat(isDeviceLocked).isFalse()
         }
 
@@ -73,8 +71,8 @@
         testScope.runTest {
             val isSwipeToDismissEnabled by collectLastValue(underTest.isSwipeToDismissEnabled)
 
-            authenticationInteractor.lockDevice()
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
+            utils.authenticationRepository.setUnlocked(false)
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
 
             assertThat(isSwipeToDismissEnabled).isTrue()
         }
@@ -84,8 +82,8 @@
         testScope.runTest {
             val isSwipeToDismissEnabled by collectLastValue(underTest.isSwipeToDismissEnabled)
 
-            authenticationInteractor.unlockDevice()
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
+            utils.authenticationRepository.setUnlocked(true)
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
 
             assertThat(isSwipeToDismissEnabled).isFalse()
         }
@@ -94,8 +92,8 @@
     fun dismissLockScreen_deviceLockedWithSecureAuthMethod_switchesToBouncer() =
         testScope.runTest {
             val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
-            authenticationInteractor.lockDevice()
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
+            utils.authenticationRepository.setUnlocked(false)
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
 
             underTest.dismissLockscreen()
@@ -107,8 +105,8 @@
     fun dismissLockScreen_deviceUnlocked_switchesToGone() =
         testScope.runTest {
             val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
-            authenticationInteractor.unlockDevice()
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
+            utils.authenticationRepository.setUnlocked(true)
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
 
             underTest.dismissLockscreen()
@@ -120,8 +118,8 @@
     fun dismissLockScreen_deviceLockedWithInsecureAuthMethod_switchesToGone() =
         testScope.runTest {
             val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
-            authenticationInteractor.lockDevice()
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
+            utils.authenticationRepository.setUnlocked(false)
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
 
             underTest.dismissLockscreen()
@@ -130,72 +128,11 @@
         }
 
     @Test
-    fun deviceLockedInNonLockScreenScene_switchesToLockScreenScene() =
-        testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
-            runCurrent()
-            sceneInteractor.setCurrentScene(CONTAINER_1, SceneModel(SceneKey.Gone))
-            runCurrent()
-            authenticationInteractor.unlockDevice()
-            runCurrent()
-            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
-
-            authenticationInteractor.lockDevice()
-
-            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
-        }
-
-    @Test
-    fun deviceBiometricUnlockedInLockScreen_bypassEnabled_switchesToGone() =
-        testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
-            authenticationInteractor.lockDevice()
-            sceneInteractor.setCurrentScene(CONTAINER_1, SceneModel(SceneKey.Lockscreen))
-            if (!authenticationInteractor.isBypassEnabled.value) {
-                authenticationInteractor.toggleBypassEnabled()
-            }
-            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
-
-            authenticationInteractor.biometricUnlock()
-
-            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
-        }
-
-    @Test
-    fun deviceBiometricUnlockedInLockScreen_bypassNotEnabled_doesNotSwitch() =
-        testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
-            authenticationInteractor.lockDevice()
-            sceneInteractor.setCurrentScene(CONTAINER_1, SceneModel(SceneKey.Lockscreen))
-            if (authenticationInteractor.isBypassEnabled.value) {
-                authenticationInteractor.toggleBypassEnabled()
-            }
-            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
-
-            authenticationInteractor.biometricUnlock()
-
-            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
-        }
-
-    @Test
-    fun switchFromLockScreenToGone_authMethodSwipe_unlocksDevice() =
-        testScope.runTest {
-            val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
-            sceneInteractor.setCurrentScene(CONTAINER_1, SceneModel(SceneKey.Lockscreen))
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
-            assertThat(isUnlocked).isFalse()
-
-            sceneInteractor.setCurrentScene(CONTAINER_1, SceneModel(SceneKey.Gone))
-
-            assertThat(isUnlocked).isTrue()
-        }
-
-    @Test
     fun switchFromLockScreenToGone_authMethodNotSwipe_doesNotUnlockDevice() =
         testScope.runTest {
             val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
             sceneInteractor.setCurrentScene(CONTAINER_1, SceneModel(SceneKey.Lockscreen))
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
             assertThat(isUnlocked).isFalse()
 
             sceneInteractor.setCurrentScene(CONTAINER_1, SceneModel(SceneKey.Gone))
@@ -210,7 +147,7 @@
             runCurrent()
             sceneInteractor.setCurrentScene(CONTAINER_1, SceneModel(SceneKey.Shade))
             runCurrent()
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
             runCurrent()
             assertThat(isUnlocked).isFalse()
 
@@ -220,29 +157,16 @@
         }
 
     @Test
-    fun authMethodChangedToNone_onLockScreenScene_dismissesLockScreen() =
-        testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
-            sceneInteractor.setCurrentScene(CONTAINER_1, SceneModel(SceneKey.Lockscreen))
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
-            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
-
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.None)
-
-            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
-        }
-
-    @Test
     fun authMethodChangedToNone_notOnLockScreenScene_doesNotDismissLockScreen() =
         testScope.runTest {
             val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
             runCurrent()
             sceneInteractor.setCurrentScene(CONTAINER_1, SceneModel(SceneKey.QuickSettings))
             runCurrent()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.QuickSettings))
 
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.None)
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
 
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.QuickSettings))
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerCallbackInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerCallbackInteractorTest.kt
deleted file mode 100644
index 2b135cc..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerCallbackInteractorTest.kt
+++ /dev/null
@@ -1,93 +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.systemui.keyguard.domain.interactor
-
-import android.view.View
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.systemui.RoboPilotTest
-import com.android.systemui.SysuiTestCase
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-
-@SmallTest
-@RoboPilotTest
-@RunWith(AndroidJUnit4::class)
-class PrimaryBouncerCallbackInteractorTest : SysuiTestCase() {
-    private val mPrimaryBouncerCallbackInteractor = PrimaryBouncerCallbackInteractor()
-    @Mock
-    private lateinit var mPrimaryBouncerExpansionCallback:
-        PrimaryBouncerCallbackInteractor.PrimaryBouncerExpansionCallback
-    @Mock
-    private lateinit var keyguardResetCallback:
-        PrimaryBouncerCallbackInteractor.KeyguardResetCallback
-
-    @Before
-    fun setup() {
-        MockitoAnnotations.initMocks(this)
-        mPrimaryBouncerCallbackInteractor.addBouncerExpansionCallback(
-            mPrimaryBouncerExpansionCallback
-        )
-        mPrimaryBouncerCallbackInteractor.addKeyguardResetCallback(keyguardResetCallback)
-    }
-
-    @Test
-    fun testOnFullyShown() {
-        mPrimaryBouncerCallbackInteractor.dispatchFullyShown()
-        verify(mPrimaryBouncerExpansionCallback).onFullyShown()
-    }
-
-    @Test
-    fun testOnFullyHidden() {
-        mPrimaryBouncerCallbackInteractor.dispatchFullyHidden()
-        verify(mPrimaryBouncerExpansionCallback).onFullyHidden()
-    }
-
-    @Test
-    fun testOnExpansionChanged() {
-        mPrimaryBouncerCallbackInteractor.dispatchExpansionChanged(5f)
-        verify(mPrimaryBouncerExpansionCallback).onExpansionChanged(5f)
-    }
-
-    @Test
-    fun testOnVisibilityChanged() {
-        mPrimaryBouncerCallbackInteractor.dispatchVisibilityChanged(View.INVISIBLE)
-        verify(mPrimaryBouncerExpansionCallback).onVisibilityChanged(false)
-    }
-
-    @Test
-    fun testOnStartingToHide() {
-        mPrimaryBouncerCallbackInteractor.dispatchStartingToHide()
-        verify(mPrimaryBouncerExpansionCallback).onStartingToHide()
-    }
-
-    @Test
-    fun testOnStartingToShow() {
-        mPrimaryBouncerCallbackInteractor.dispatchStartingToShow()
-        verify(mPrimaryBouncerExpansionCallback).onStartingToShow()
-    }
-
-    @Test
-    fun testOnKeyguardReset() {
-        mPrimaryBouncerCallbackInteractor.dispatchReset()
-        verify(keyguardResetCallback).onKeyguardReset()
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt
deleted file mode 100644
index b5cb44a..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt
+++ /dev/null
@@ -1,469 +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.systemui.keyguard.domain.interactor
-
-import android.hardware.biometrics.BiometricSourceType
-import android.testing.AndroidTestingRunner
-import android.testing.TestableLooper.RunWithLooper
-import android.testing.TestableResources
-import android.view.View
-import androidx.test.filters.SmallTest
-import com.android.keyguard.KeyguardSecurityModel
-import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.systemui.DejankUtils
-import com.android.systemui.R
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.classifier.FalsingCollector
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags
-import com.android.systemui.keyguard.DismissCallbackRegistry
-import com.android.systemui.keyguard.data.BouncerView
-import com.android.systemui.keyguard.data.BouncerViewDelegate
-import com.android.systemui.keyguard.data.repository.FakeTrustRepository
-import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository
-import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN
-import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_VISIBLE
-import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
-import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.statusbar.policy.KeyguardStateController
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.whenever
-import com.android.systemui.utils.os.FakeHandler
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.runCurrent
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Answers
-import org.mockito.ArgumentCaptor
-import org.mockito.Mock
-import org.mockito.Mockito.anyInt
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.never
-import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-
-@SmallTest
-@RunWithLooper(setAsMainLooper = true)
-@RunWith(AndroidTestingRunner::class)
-class PrimaryBouncerInteractorTest : SysuiTestCase() {
-    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
-    private lateinit var repository: KeyguardBouncerRepository
-    @Mock(answer = Answers.RETURNS_DEEP_STUBS) private lateinit var bouncerView: BouncerView
-    @Mock private lateinit var bouncerViewDelegate: BouncerViewDelegate
-    @Mock private lateinit var keyguardStateController: KeyguardStateController
-    @Mock private lateinit var keyguardSecurityModel: KeyguardSecurityModel
-    @Mock private lateinit var mPrimaryBouncerCallbackInteractor: PrimaryBouncerCallbackInteractor
-    @Mock private lateinit var falsingCollector: FalsingCollector
-    @Mock private lateinit var dismissCallbackRegistry: DismissCallbackRegistry
-    @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
-    private lateinit var mainHandler: FakeHandler
-    private lateinit var underTest: PrimaryBouncerInteractor
-    private lateinit var resources: TestableResources
-    private lateinit var trustRepository: FakeTrustRepository
-    private lateinit var featureFlags: FakeFeatureFlags
-    private lateinit var testScope: TestScope
-
-    @Before
-    fun setUp() {
-        MockitoAnnotations.initMocks(this)
-        whenever(keyguardSecurityModel.getSecurityMode(anyInt()))
-            .thenReturn(KeyguardSecurityModel.SecurityMode.PIN)
-
-        DejankUtils.setImmediate(true)
-        testScope = TestScope()
-        mainHandler = FakeHandler(android.os.Looper.getMainLooper())
-        trustRepository = FakeTrustRepository()
-        featureFlags = FakeFeatureFlags().apply { set(Flags.DELAY_BOUNCER, true) }
-        underTest =
-            PrimaryBouncerInteractor(
-                repository,
-                bouncerView,
-                mainHandler,
-                keyguardStateController,
-                keyguardSecurityModel,
-                mPrimaryBouncerCallbackInteractor,
-                falsingCollector,
-                dismissCallbackRegistry,
-                context,
-                keyguardUpdateMonitor,
-                trustRepository,
-                featureFlags,
-                testScope.backgroundScope,
-            )
-        whenever(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
-        whenever(repository.primaryBouncerShow.value).thenReturn(false)
-        whenever(bouncerView.delegate).thenReturn(bouncerViewDelegate)
-        resources = context.orCreateTestableResources
-    }
-
-    @Test
-    fun testShow_isScrimmed() {
-        underTest.show(true)
-        verify(repository).setKeyguardAuthenticated(null)
-        verify(repository).setPrimaryStartingToHide(false)
-        verify(repository).setPrimaryScrimmed(true)
-        verify(repository).setPanelExpansion(EXPANSION_VISIBLE)
-        verify(repository).setPrimaryShowingSoon(true)
-        verify(keyguardStateController).notifyPrimaryBouncerShowing(true)
-        verify(mPrimaryBouncerCallbackInteractor).dispatchStartingToShow()
-        verify(repository).setPrimaryShow(true)
-        verify(repository).setPrimaryShowingSoon(false)
-        verify(mPrimaryBouncerCallbackInteractor).dispatchVisibilityChanged(View.VISIBLE)
-    }
-
-    @Test
-    fun testShow_isNotScrimmed() {
-        verify(repository, never()).setPanelExpansion(EXPANSION_VISIBLE)
-    }
-
-    @Test
-    fun testShow_keyguardIsDone() {
-        whenever(bouncerView.delegate?.showNextSecurityScreenOrFinish()).thenReturn(true)
-        verify(keyguardStateController, never()).notifyPrimaryBouncerShowing(true)
-        verify(mPrimaryBouncerCallbackInteractor, never()).dispatchStartingToShow()
-    }
-
-    @Test
-    fun testShow_isResumed() {
-        whenever(repository.primaryBouncerShow.value).thenReturn(true)
-        whenever(keyguardSecurityModel.getSecurityMode(anyInt()))
-            .thenReturn(KeyguardSecurityModel.SecurityMode.SimPuk)
-
-        underTest.show(true)
-        verify(repository).setPrimaryShow(false)
-        verify(repository).setPrimaryShow(true)
-    }
-
-    @Test
-    fun testHide() {
-        underTest.hide()
-        verify(falsingCollector).onBouncerHidden()
-        verify(keyguardStateController).notifyPrimaryBouncerShowing(false)
-        verify(repository).setPrimaryShowingSoon(false)
-        verify(repository).setPrimaryShow(false)
-        verify(mPrimaryBouncerCallbackInteractor).dispatchVisibilityChanged(View.INVISIBLE)
-        verify(repository).setPrimaryStartDisappearAnimation(null)
-        verify(repository).setPanelExpansion(EXPANSION_HIDDEN)
-    }
-
-    @Test
-    fun testExpansion() {
-        whenever(repository.panelExpansionAmount.value).thenReturn(0.5f)
-        underTest.setPanelExpansion(0.6f)
-        verify(repository).setPanelExpansion(0.6f)
-        verify(mPrimaryBouncerCallbackInteractor).dispatchExpansionChanged(0.6f)
-    }
-
-    @Test
-    fun testExpansion_fullyShown() {
-        whenever(repository.panelExpansionAmount.value).thenReturn(0.5f)
-        whenever(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
-        underTest.setPanelExpansion(EXPANSION_VISIBLE)
-        verify(falsingCollector).onBouncerShown()
-        verify(mPrimaryBouncerCallbackInteractor).dispatchFullyShown()
-    }
-
-    @Test
-    fun testExpansion_fullyHidden() {
-        whenever(repository.panelExpansionAmount.value).thenReturn(0.5f)
-        whenever(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
-        underTest.setPanelExpansion(EXPANSION_HIDDEN)
-        verify(repository).setPrimaryShow(false)
-        verify(falsingCollector).onBouncerHidden()
-        verify(mPrimaryBouncerCallbackInteractor).dispatchReset()
-        verify(mPrimaryBouncerCallbackInteractor).dispatchFullyHidden()
-    }
-
-    @Test
-    fun testExpansion_startingToHide() {
-        whenever(repository.panelExpansionAmount.value).thenReturn(EXPANSION_VISIBLE)
-        underTest.setPanelExpansion(0.1f)
-        verify(repository).setPrimaryStartingToHide(true)
-        verify(mPrimaryBouncerCallbackInteractor).dispatchStartingToHide()
-    }
-
-    @Test
-    fun testShowMessage() {
-        val argCaptor = ArgumentCaptor.forClass(BouncerShowMessageModel::class.java)
-        underTest.showMessage("abc", null)
-        verify(repository).setShowMessage(argCaptor.capture())
-        assertThat(argCaptor.value.message).isEqualTo("abc")
-    }
-
-    @Test
-    fun testDismissAction() {
-        val onDismissAction = mock(ActivityStarter.OnDismissAction::class.java)
-        val cancelAction = mock(Runnable::class.java)
-        underTest.setDismissAction(onDismissAction, cancelAction)
-        verify(bouncerViewDelegate).setDismissAction(onDismissAction, cancelAction)
-    }
-
-    @Test
-    fun testUpdateResources() {
-        underTest.updateResources()
-        verify(repository).setResourceUpdateRequests(true)
-    }
-
-    @Test
-    fun testNotifyKeyguardAuthenticated() {
-        underTest.notifyKeyguardAuthenticated(true)
-        verify(repository).setKeyguardAuthenticated(true)
-    }
-
-    @Test
-    fun testNotifyShowedMessage() {
-        underTest.onMessageShown()
-        verify(repository).setShowMessage(null)
-    }
-
-    @Test
-    fun testSetKeyguardPosition() {
-        underTest.setKeyguardPosition(0f)
-        verify(repository).setKeyguardPosition(0f)
-    }
-
-    @Test
-    fun testNotifyKeyguardAuthenticatedHandled() {
-        underTest.notifyKeyguardAuthenticatedHandled()
-        verify(repository).setKeyguardAuthenticated(null)
-    }
-
-    @Test
-    fun testNotifyUpdatedResources() {
-        underTest.notifyUpdatedResources()
-        verify(repository).setResourceUpdateRequests(false)
-    }
-
-    @Test
-    fun testSetBackButtonEnabled() {
-        underTest.setBackButtonEnabled(true)
-        verify(repository).setIsBackButtonEnabled(true)
-    }
-
-    @Test
-    fun testStartDisappearAnimation_willRunDismissFromKeyguard() {
-        whenever(bouncerViewDelegate.willRunDismissFromKeyguard()).thenReturn(true)
-
-        val runnable = mock(Runnable::class.java)
-        underTest.startDisappearAnimation(runnable)
-        // End runnable should run immediately
-        verify(runnable).run()
-        // ... while the disappear animation should never be run
-        verify(repository, never()).setPrimaryStartDisappearAnimation(any(Runnable::class.java))
-    }
-
-    @Test
-    fun testStartDisappearAnimation_willNotRunDismissFromKeyguard_() {
-        whenever(bouncerViewDelegate.willRunDismissFromKeyguard()).thenReturn(false)
-
-        val runnable = mock(Runnable::class.java)
-        underTest.startDisappearAnimation(runnable)
-        verify(repository).setPrimaryStartDisappearAnimation(any(Runnable::class.java))
-    }
-
-    @Test
-    fun testIsFullShowing() {
-        whenever(repository.primaryBouncerShow.value).thenReturn(true)
-        whenever(repository.panelExpansionAmount.value).thenReturn(EXPANSION_VISIBLE)
-        whenever(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
-        assertThat(underTest.isFullyShowing()).isTrue()
-        whenever(repository.primaryBouncerShow.value).thenReturn(false)
-        assertThat(underTest.isFullyShowing()).isFalse()
-    }
-
-    @Test
-    fun testIsScrimmed() {
-        whenever(repository.primaryBouncerScrimmed.value).thenReturn(true)
-        assertThat(underTest.isScrimmed()).isTrue()
-        whenever(repository.primaryBouncerScrimmed.value).thenReturn(false)
-        assertThat(underTest.isScrimmed()).isFalse()
-    }
-
-    @Test
-    fun testIsInTransit() {
-        whenever(repository.primaryBouncerShowingSoon.value).thenReturn(true)
-        assertThat(underTest.isInTransit()).isTrue()
-        whenever(repository.primaryBouncerShowingSoon.value).thenReturn(false)
-        assertThat(underTest.isInTransit()).isFalse()
-        whenever(repository.panelExpansionAmount.value).thenReturn(0.5f)
-        assertThat(underTest.isInTransit()).isTrue()
-    }
-
-    @Test
-    fun testIsAnimatingAway() {
-        whenever(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(Runnable {})
-        assertThat(underTest.isAnimatingAway()).isTrue()
-        whenever(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
-        assertThat(underTest.isAnimatingAway()).isFalse()
-    }
-
-    @Test
-    fun testWillDismissWithAction() {
-        whenever(bouncerViewDelegate.willDismissWithActions()).thenReturn(true)
-        assertThat(underTest.willDismissWithAction()).isTrue()
-        whenever(bouncerViewDelegate.willDismissWithActions()).thenReturn(false)
-        assertThat(underTest.willDismissWithAction()).isFalse()
-    }
-
-    @Test
-    fun testSideFpsVisibility() {
-        updateSideFpsVisibilityParameters(
-            isVisible = true,
-            sfpsEnabled = true,
-            fpsDetectionRunning = true,
-            isUnlockingWithFpAllowed = true,
-            isAnimatingAway = false
-        )
-        underTest.updateSideFpsVisibility()
-        verify(repository).setSideFpsShowing(true)
-    }
-
-    @Test
-    fun testSideFpsVisibility_notVisible() {
-        updateSideFpsVisibilityParameters(
-            isVisible = false,
-            sfpsEnabled = true,
-            fpsDetectionRunning = true,
-            isUnlockingWithFpAllowed = true,
-            isAnimatingAway = false
-        )
-        underTest.updateSideFpsVisibility()
-        verify(repository).setSideFpsShowing(false)
-    }
-
-    @Test
-    fun testSideFpsVisibility_sfpsNotEnabled() {
-        updateSideFpsVisibilityParameters(
-            isVisible = true,
-            sfpsEnabled = false,
-            fpsDetectionRunning = true,
-            isUnlockingWithFpAllowed = true,
-            isAnimatingAway = false
-        )
-        underTest.updateSideFpsVisibility()
-        verify(repository).setSideFpsShowing(false)
-    }
-
-    @Test
-    fun testSideFpsVisibility_fpsDetectionNotRunning() {
-        updateSideFpsVisibilityParameters(
-            isVisible = true,
-            sfpsEnabled = true,
-            fpsDetectionRunning = false,
-            isUnlockingWithFpAllowed = true,
-            isAnimatingAway = false
-        )
-        underTest.updateSideFpsVisibility()
-        verify(repository).setSideFpsShowing(false)
-    }
-
-    @Test
-    fun testSideFpsVisibility_UnlockingWithFpNotAllowed() {
-        updateSideFpsVisibilityParameters(
-            isVisible = true,
-            sfpsEnabled = true,
-            fpsDetectionRunning = true,
-            isUnlockingWithFpAllowed = false,
-            isAnimatingAway = false
-        )
-        underTest.updateSideFpsVisibility()
-        verify(repository).setSideFpsShowing(false)
-    }
-
-    @Test
-    fun testSideFpsVisibility_AnimatingAway() {
-        updateSideFpsVisibilityParameters(
-            isVisible = true,
-            sfpsEnabled = true,
-            fpsDetectionRunning = true,
-            isUnlockingWithFpAllowed = true,
-            isAnimatingAway = true
-        )
-        underTest.updateSideFpsVisibility()
-        verify(repository).setSideFpsShowing(false)
-    }
-
-    @Test
-    fun delayBouncerWhenFaceAuthPossible() {
-        mainHandler.setMode(FakeHandler.Mode.QUEUEING)
-
-        // GIVEN bouncer should be delayed due to face auth
-        whenever(keyguardStateController.isFaceAuthEnabled).thenReturn(true)
-        whenever(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(BiometricSourceType.FACE))
-            .thenReturn(true)
-
-        // WHEN bouncer show is requested
-        underTest.show(true)
-
-        // THEN primary show & primary showing soon aren't updated immediately
-        verify(repository, never()).setPrimaryShow(true)
-        verify(repository, never()).setPrimaryShowingSoon(false)
-
-        // WHEN all queued messages are dispatched
-        mainHandler.dispatchQueuedMessages()
-
-        // THEN primary show & primary showing soon are updated
-        verify(repository).setPrimaryShow(true)
-        verify(repository).setPrimaryShowingSoon(false)
-    }
-
-    @Test
-    fun delayBouncerWhenActiveUnlockPossible() {
-        testScope.run {
-            mainHandler.setMode(FakeHandler.Mode.QUEUEING)
-
-            // GIVEN bouncer should be delayed due to active unlock
-            trustRepository.setCurrentUserActiveUnlockAvailable(true)
-            whenever(keyguardUpdateMonitor.canTriggerActiveUnlockBasedOnDeviceState())
-                .thenReturn(true)
-            runCurrent()
-
-            // WHEN bouncer show is requested
-            underTest.show(true)
-
-            // THEN primary show & primary showing soon were scheduled to update
-            verify(repository, never()).setPrimaryShow(true)
-            verify(repository, never()).setPrimaryShowingSoon(false)
-
-            // WHEN all queued messages are dispatched
-            mainHandler.dispatchQueuedMessages()
-
-            // THEN primary show & primary showing soon are updated
-            verify(repository).setPrimaryShow(true)
-            verify(repository).setPrimaryShowingSoon(false)
-        }
-    }
-
-    private fun updateSideFpsVisibilityParameters(
-        isVisible: Boolean,
-        sfpsEnabled: Boolean,
-        fpsDetectionRunning: Boolean,
-        isUnlockingWithFpAllowed: Boolean,
-        isAnimatingAway: Boolean
-    ) {
-        whenever(repository.primaryBouncerShow.value).thenReturn(isVisible)
-        resources.addOverride(R.bool.config_show_sidefps_hint_on_bouncer, sfpsEnabled)
-        whenever(keyguardUpdateMonitor.isFingerprintDetectionRunning)
-            .thenReturn(fpsDetectionRunning)
-        whenever(keyguardUpdateMonitor.isUnlockingWithFingerprintAllowed)
-            .thenReturn(isUnlockingWithFpAllowed)
-        whenever(repository.primaryBouncerStartingDisappearAnimation.value)
-            .thenReturn(if (isAnimatingAway) Runnable {} else null)
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt
deleted file mode 100644
index b288fbc..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt
+++ /dev/null
@@ -1,120 +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.systemui.keyguard.domain.interactor
-
-import android.os.Looper
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.keyguard.KeyguardSecurityModel
-import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.systemui.RoboPilotTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.classifier.FalsingCollector
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags
-import com.android.systemui.keyguard.DismissCallbackRegistry
-import com.android.systemui.keyguard.data.BouncerView
-import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository
-import com.android.systemui.keyguard.data.repository.TrustRepository
-import com.android.systemui.statusbar.phone.KeyguardBypassController
-import com.android.systemui.statusbar.policy.KeyguardStateController
-import com.android.systemui.utils.os.FakeHandler
-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
-@RoboPilotTest
-@RunWith(AndroidJUnit4::class)
-class PrimaryBouncerInteractorWithCoroutinesTest : SysuiTestCase() {
-    private lateinit var repository: FakeKeyguardBouncerRepository
-    @Mock private lateinit var bouncerView: BouncerView
-    @Mock private lateinit var keyguardStateController: KeyguardStateController
-    @Mock private lateinit var keyguardSecurityModel: KeyguardSecurityModel
-    @Mock private lateinit var primaryBouncerCallbackInteractor: PrimaryBouncerCallbackInteractor
-    @Mock private lateinit var falsingCollector: FalsingCollector
-    @Mock private lateinit var dismissCallbackRegistry: DismissCallbackRegistry
-    @Mock private lateinit var keyguardBypassController: KeyguardBypassController
-    @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
-    private val mainHandler = FakeHandler(Looper.getMainLooper())
-    private lateinit var underTest: PrimaryBouncerInteractor
-
-    @Before
-    fun setUp() {
-        MockitoAnnotations.initMocks(this)
-        repository = FakeKeyguardBouncerRepository()
-        underTest =
-            PrimaryBouncerInteractor(
-                repository,
-                bouncerView,
-                mainHandler,
-                keyguardStateController,
-                keyguardSecurityModel,
-                primaryBouncerCallbackInteractor,
-                falsingCollector,
-                dismissCallbackRegistry,
-                context,
-                keyguardUpdateMonitor,
-                Mockito.mock(TrustRepository::class.java),
-                FakeFeatureFlags().apply { set(Flags.DELAY_BOUNCER, true) },
-                TestScope().backgroundScope,
-            )
-    }
-
-    @Test
-    fun notInteractableWhenExpansionIsBelow90Percent() = runTest {
-        val isInteractable = collectLastValue(underTest.isInteractable)
-
-        repository.setPrimaryShow(true)
-        repository.setPanelExpansion(0.15f)
-
-        assertThat(isInteractable()).isFalse()
-    }
-
-    @Test
-    fun notInteractableWhenExpansionAbove90PercentButNotVisible() = runTest {
-        val isInteractable = collectLastValue(underTest.isInteractable)
-
-        repository.setPrimaryShow(false)
-        repository.setPanelExpansion(0.05f)
-
-        assertThat(isInteractable()).isFalse()
-    }
-
-    @Test
-    fun isInteractableWhenExpansionAbove90PercentAndVisible() = runTest {
-        var isInteractable = collectLastValue(underTest.isInteractable)
-
-        repository.setPrimaryShow(true)
-        repository.setPanelExpansion(0.09f)
-
-        assertThat(isInteractable()).isTrue()
-
-        repository.setPanelExpansion(0.12f)
-        assertThat(isInteractable()).isFalse()
-
-        repository.setPanelExpansion(0f)
-        assertThat(isInteractable()).isTrue()
-    }
-}
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
new file mode 100644
index 0000000..1baca21
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
+import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
+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
+import com.android.systemui.keyguard.shared.model.WakeSleepReason
+import com.android.systemui.keyguard.shared.model.WakefulnessModel
+import com.android.systemui.keyguard.shared.model.WakefulnessState
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@ExperimentalCoroutinesApi
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class UdfpsKeyguardInteractorTest : SysuiTestCase() {
+    private val burnInProgress = 1f
+    private val burnInYOffset = 20
+    private val burnInXOffset = 10
+
+    private lateinit var testScope: TestScope
+    private lateinit var configRepository: FakeConfigurationRepository
+    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
+
+    @Mock private lateinit var burnInHelper: BurnInHelperWrapper
+
+    private lateinit var underTest: UdfpsKeyguardInteractor
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        testScope = TestScope()
+        configRepository = FakeConfigurationRepository()
+        keyguardRepository = FakeKeyguardRepository()
+        bouncerRepository = FakeKeyguardBouncerRepository()
+        fakeCommandQueue = FakeCommandQueue()
+        featureFlags =
+            FakeFeatureFlags().apply {
+                set(Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS, true)
+                set(Flags.FACE_AUTH_REFACTOR, false)
+            }
+        burnInInteractor =
+            BurnInInteractor(
+                context,
+                burnInHelper,
+                testScope.backgroundScope,
+                configRepository,
+                FakeSystemClock(),
+            )
+
+        underTest =
+            UdfpsKeyguardInteractor(
+                configRepository,
+                burnInInteractor,
+                KeyguardInteractor(
+                    keyguardRepository,
+                    fakeCommandQueue,
+                    featureFlags,
+                    bouncerRepository,
+                    configRepository,
+                ),
+            )
+    }
+
+    @Test
+    fun dozeChanges_updatesUdfpsAodModel() =
+        testScope.runTest {
+            val burnInOffsets by collectLastValue(underTest.burnInOffsets)
+            initializeBurnInOffsets()
+
+            // WHEN we're not dozing
+            setAwake()
+            runCurrent()
+
+            // THEN burn in offsets are 0
+            assertThat(burnInOffsets?.burnInProgress).isEqualTo(0f)
+            assertThat(burnInOffsets?.burnInYOffset).isEqualTo(0)
+            assertThat(burnInOffsets?.burnInXOffset).isEqualTo(0)
+
+            // WHEN we're in the middle of the doze amount change
+            keyguardRepository.setDozeAmount(.50f)
+            runCurrent()
+
+            // THEN burn in is updated (between 0 and the full offset)
+            assertThat(burnInOffsets?.burnInProgress).isGreaterThan(0f)
+            assertThat(burnInOffsets?.burnInYOffset).isGreaterThan(0)
+            assertThat(burnInOffsets?.burnInXOffset).isGreaterThan(0)
+            assertThat(burnInOffsets?.burnInProgress).isLessThan(burnInProgress)
+            assertThat(burnInOffsets?.burnInYOffset).isLessThan(burnInYOffset)
+            assertThat(burnInOffsets?.burnInXOffset).isLessThan(burnInXOffset)
+
+            // WHEN we're fully dozing
+            keyguardRepository.setDozeAmount(1f)
+            runCurrent()
+
+            // THEN burn in offsets are updated to final current values (for the given time)
+            assertThat(burnInOffsets?.burnInProgress).isEqualTo(burnInProgress)
+            assertThat(burnInOffsets?.burnInYOffset).isEqualTo(burnInYOffset)
+            assertThat(burnInOffsets?.burnInXOffset).isEqualTo(burnInXOffset)
+        }
+
+    private fun initializeBurnInOffsets() {
+        whenever(burnInHelper.burnInProgressOffset()).thenReturn(burnInProgress)
+        whenever(burnInHelper.burnInOffset(anyInt(), /* xAxis */ eq(true)))
+            .thenReturn(burnInXOffset)
+        whenever(burnInHelper.burnInOffset(anyInt(), /* xAxis */ eq(false)))
+            .thenReturn(burnInYOffset)
+    }
+
+    private fun setAwake() {
+        keyguardRepository.setDozeAmount(0f)
+        burnInInteractor.dozeTimeTick()
+
+        bouncerRepository.setAlternateVisible(false)
+        keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
+        bouncerRepository.setPrimaryShow(false)
+        keyguardRepository.setWakefulnessModel(
+            WakefulnessModel(
+                WakefulnessState.AWAKE,
+                WakeSleepReason.POWER_BUTTON,
+                WakeSleepReason.POWER_BUTTON,
+            )
+        )
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/FakeKeyguardQuickAffordanceRegistry.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/FakeKeyguardQuickAffordanceRegistry.kt
deleted file mode 100644
index 13e2768..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/FakeKeyguardQuickAffordanceRegistry.kt
+++ /dev/null
@@ -1,40 +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.systemui.keyguard.domain.quickaffordance
-
-import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
-import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
-
-/** Fake implementation of [FakeKeyguardQuickAffordanceRegistry], for tests. */
-class FakeKeyguardQuickAffordanceRegistry(
-    private val configsByPosition:
-        Map<KeyguardQuickAffordancePosition, List<FakeKeyguardQuickAffordanceConfig>>,
-) : KeyguardQuickAffordanceRegistry<FakeKeyguardQuickAffordanceConfig> {
-
-    override fun getAll(
-        position: KeyguardQuickAffordancePosition
-    ): List<FakeKeyguardQuickAffordanceConfig> {
-        return configsByPosition.getValue(position)
-    }
-
-    override fun get(
-        key: String,
-    ): FakeKeyguardQuickAffordanceConfig {
-        return configsByPosition.values.flatten().associateBy { config -> config.key }.getValue(key)
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/DefaultLockscreenLayoutTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/DefaultLockscreenLayoutTest.kt
new file mode 100644
index 0000000..2e97208
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/DefaultLockscreenLayoutTest.kt
@@ -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.systemui.keyguard.ui.view.layout
+
+import android.graphics.Point
+import android.view.ViewGroup
+import android.view.WindowManager
+import androidx.constraintlayout.widget.ConstraintSet
+import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.AuthController
+import com.android.systemui.keyguard.ui.view.KeyguardRootView
+import com.android.systemui.monet.utils.ArgbSubject.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Answers
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@RunWith(JUnit4::class)
+@SmallTest
+class DefaultLockscreenLayoutTest : SysuiTestCase() {
+    private lateinit var defaultLockscreenLayout: DefaultLockscreenLayout
+    private lateinit var rootView: KeyguardRootView
+    @Mock private lateinit var authController: AuthController
+    @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS) private lateinit var windowManager: WindowManager
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        rootView = KeyguardRootView(context, null)
+        defaultLockscreenLayout =
+            DefaultLockscreenLayout(authController, keyguardUpdateMonitor, windowManager, context)
+    }
+
+    @Test
+    fun testLayoutViews_KeyguardIndicationArea() {
+        defaultLockscreenLayout.layoutViews(rootView)
+        val constraint = getViewConstraint(R.id.keyguard_indication_area)
+        assertThat(constraint.layout.bottomToBottom).isEqualTo(ConstraintSet.PARENT_ID)
+        assertThat(constraint.layout.startToStart).isEqualTo(ConstraintSet.PARENT_ID)
+        assertThat(constraint.layout.endToEnd).isEqualTo(ConstraintSet.PARENT_ID)
+        assertThat(constraint.layout.mWidth).isEqualTo(ViewGroup.LayoutParams.MATCH_PARENT)
+        assertThat(constraint.layout.mHeight).isEqualTo(ViewGroup.LayoutParams.WRAP_CONTENT)
+    }
+
+    @Test
+    fun testLayoutViews_lockIconView() {
+        defaultLockscreenLayout.layoutViews(rootView)
+        val constraint = getViewConstraint(R.id.lock_icon_view)
+        assertThat(constraint.layout.topToTop).isEqualTo(ConstraintSet.PARENT_ID)
+        assertThat(constraint.layout.startToStart).isEqualTo(ConstraintSet.PARENT_ID)
+    }
+
+    @Test
+    fun testCenterLockIcon() {
+        defaultLockscreenLayout.centerLockIcon(Point(5, 6), 1F, 5, rootView)
+        val constraint = getViewConstraint(R.id.lock_icon_view)
+
+        assertThat(constraint.layout.mWidth).isEqualTo(2)
+        assertThat(constraint.layout.mHeight).isEqualTo(2)
+        assertThat(constraint.layout.topToTop).isEqualTo(ConstraintSet.PARENT_ID)
+        assertThat(constraint.layout.startToStart).isEqualTo(ConstraintSet.PARENT_ID)
+        assertThat(constraint.layout.topMargin).isEqualTo(5)
+        assertThat(constraint.layout.startMargin).isEqualTo(4)
+    }
+
+    /** Get the ConstraintLayout constraint of the view. */
+    private fun getViewConstraint(viewId: Int): ConstraintSet.Constraint {
+        val constraintSet = ConstraintSet()
+        constraintSet.clone(rootView)
+        return constraintSet.getConstraint(viewId)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/KeyguardLayoutManagerCommandListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/KeyguardLayoutManagerCommandListenerTest.kt
new file mode 100644
index 0000000..145b2fd
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/KeyguardLayoutManagerCommandListenerTest.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.
+ */
+
+package com.android.systemui.keyguard.ui.view.layout
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.commandline.Command
+import com.android.systemui.statusbar.commandline.CommandRegistry
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.mockito.withArgCaptor
+import java.io.PrintWriter
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.ArgumentMatchers.anyString
+import org.mockito.Mock
+import org.mockito.Mockito.atLeastOnce
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@RunWith(JUnit4::class)
+@SmallTest
+class KeyguardLayoutManagerCommandListenerTest : SysuiTestCase() {
+    private lateinit var keyguardLayoutManagerCommandListener: KeyguardLayoutManagerCommandListener
+    @Mock private lateinit var commandRegistry: CommandRegistry
+    @Mock private lateinit var keyguardLayoutManager: KeyguardLayoutManager
+    @Mock private lateinit var pw: PrintWriter
+    private lateinit var command: () -> Command
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        keyguardLayoutManagerCommandListener =
+            KeyguardLayoutManagerCommandListener(
+                commandRegistry,
+                keyguardLayoutManager,
+            )
+        keyguardLayoutManagerCommandListener.start()
+        command =
+            withArgCaptor<() -> Command> {
+                verify(commandRegistry).registerCommand(eq("layout"), capture())
+            }
+    }
+
+    @Test
+    fun testHelp() {
+        command().execute(pw, listOf("help"))
+        verify(pw, atLeastOnce()).println(anyString())
+        verify(keyguardLayoutManager, never()).transitionToLayout(anyString())
+    }
+
+    @Test
+    fun testBlank() {
+        command().execute(pw, listOf())
+        verify(pw, atLeastOnce()).println(anyString())
+        verify(keyguardLayoutManager, never()).transitionToLayout(anyString())
+    }
+
+    @Test
+    fun testValidArg() {
+        bindFakeIdMapToLayoutManager()
+        command().execute(pw, listOf("fake"))
+        verify(keyguardLayoutManager).transitionToLayout("fake")
+    }
+
+    private fun bindFakeIdMapToLayoutManager() {
+        val map = mapOf("fake" to mock(LockscreenLayout::class.java))
+        whenever(keyguardLayoutManager.layoutIdMap).thenReturn(map)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/KeyguardLayoutManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/KeyguardLayoutManagerTest.kt
new file mode 100644
index 0000000..95b2030
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/KeyguardLayoutManagerTest.kt
@@ -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.systemui.keyguard.ui.view.layout
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.ui.view.KeyguardRootView
+import com.android.systemui.keyguard.ui.view.layout.DefaultLockscreenLayout.Companion.DEFAULT
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@RunWith(JUnit4::class)
+@SmallTest
+class KeyguardLayoutManagerTest : SysuiTestCase() {
+    private lateinit var keyguardLayoutManager: KeyguardLayoutManager
+    @Mock lateinit var configurationController: ConfigurationController
+    @Mock lateinit var defaultLockscreenLayout: DefaultLockscreenLayout
+    @Mock lateinit var keyguardRootView: KeyguardRootView
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        whenever(defaultLockscreenLayout.id).thenReturn(DEFAULT)
+        keyguardLayoutManager =
+            KeyguardLayoutManager(
+                configurationController,
+                setOf(defaultLockscreenLayout),
+                keyguardRootView
+            )
+    }
+
+    @Test
+    fun testDefaultLayout() {
+        keyguardLayoutManager.transitionToLayout(DEFAULT)
+        verify(defaultLockscreenLayout).layoutViews(keyguardRootView)
+    }
+
+    @Test
+    fun testTransitionToLayout_validId() {
+        assertThat(keyguardLayoutManager.transitionToLayout(DEFAULT)).isTrue()
+    }
+    @Test
+    fun testTransitionToLayout_invalidId() {
+        assertThat(keyguardLayoutManager.transitionToLayout("abc")).isFalse()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt
index ab994b7..c67f535 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt
@@ -22,6 +22,8 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
+import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
 import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
 import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
@@ -55,7 +57,12 @@
     @Before
     fun setUp() {
         repository = FakeKeyguardTransitionRepository()
-        val interactor = KeyguardTransitionInteractor(repository, TestScope().backgroundScope)
+        val interactor =
+            KeyguardTransitionInteractorFactory.create(
+                    scope = TestScope().backgroundScope,
+                    repository = repository,
+                )
+                .keyguardTransitionInteractor
         underTest = DreamingToLockscreenTransitionViewModel(interactor, mock())
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt
index 694539b..75c8bff 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt
@@ -21,7 +21,7 @@
 import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -46,7 +46,12 @@
     @Before
     fun setUp() {
         repository = FakeKeyguardTransitionRepository()
-        val interactor = KeyguardTransitionInteractor(repository, TestScope().backgroundScope)
+        val interactor =
+            KeyguardTransitionInteractorFactory.create(
+                    scope = TestScope().backgroundScope,
+                    repository = repository,
+                )
+                .keyguardTransitionInteractor
         underTest = GoneToDreamingTransitionViewModel(interactor)
     }
 
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 b6368cf..06bf7f0 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,6 +22,7 @@
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.testing.UiEventLoggerFake
 import com.android.internal.widget.LockPatternUtils
+import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.DialogLaunchAnimator
 import com.android.systemui.animation.Expandable
@@ -40,16 +41,13 @@
 import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLocalUserSelectionManager
 import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceRemoteUserSelectionManager
 import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
-import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
 import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
 import com.android.systemui.keyguard.domain.interactor.KeyguardLongPressInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceRegistry
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
 import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
 import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
 import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLogger
@@ -57,7 +55,6 @@
 import com.android.systemui.settings.UserFileManager
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
-import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.FakeSharedPreferences
@@ -96,7 +93,6 @@
     @Mock private lateinit var userTracker: UserTracker
     @Mock private lateinit var activityStarter: ActivityStarter
     @Mock private lateinit var launchAnimator: DialogLaunchAnimator
-    @Mock private lateinit var commandQueue: CommandQueue
     @Mock private lateinit var devicePolicyManager: DevicePolicyManager
     @Mock private lateinit var logger: KeyguardQuickAffordancesMetricsLogger
     @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
@@ -106,7 +102,6 @@
 
     private lateinit var testScope: TestScope
     private lateinit var repository: FakeKeyguardRepository
-    private lateinit var registry: FakeKeyguardQuickAffordanceRegistry
     private lateinit var homeControlsQuickAffordanceConfig: FakeKeyguardQuickAffordanceConfig
     private lateinit var quickAccessWalletAffordanceConfig: FakeKeyguardQuickAffordanceConfig
     private lateinit var qrCodeScannerAffordanceConfig: FakeKeyguardQuickAffordanceConfig
@@ -116,6 +111,18 @@
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
+
+        overrideResource(R.bool.custom_lockscreen_shortcuts_enabled, true)
+        overrideResource(
+            R.array.config_keyguardQuickAffordanceDefaults,
+            arrayOf(
+                KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START + ":" +
+                    BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS,
+                KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END + ":" +
+                    BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
+            )
+        )
+
         whenever(burnInHelperWrapper.burnInOffset(anyInt(), any()))
             .thenReturn(RETURNED_BURN_IN_OFFSET)
 
@@ -129,38 +136,18 @@
             FakeKeyguardQuickAffordanceConfig(BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER)
         dockManager = DockManagerFake()
         biometricSettingsRepository = FakeBiometricSettingsRepository()
-        registry =
-            FakeKeyguardQuickAffordanceRegistry(
-                mapOf(
-                    KeyguardQuickAffordancePosition.BOTTOM_START to
-                        listOf(
-                            homeControlsQuickAffordanceConfig,
-                        ),
-                    KeyguardQuickAffordancePosition.BOTTOM_END to
-                        listOf(
-                            quickAccessWalletAffordanceConfig,
-                            qrCodeScannerAffordanceConfig,
-                        ),
-                ),
-            )
-        repository = FakeKeyguardRepository()
         val featureFlags =
             FakeFeatureFlags().apply {
-                set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, false)
                 set(Flags.FACE_AUTH_REFACTOR, true)
                 set(Flags.LOCK_SCREEN_LONG_PRESS_ENABLED, false)
                 set(Flags.LOCK_SCREEN_LONG_PRESS_DIRECT_TO_WPP, false)
             }
 
-        val keyguardInteractor =
-            KeyguardInteractor(
-                repository = repository,
-                commandQueue = commandQueue,
-                featureFlags = featureFlags,
-                bouncerRepository = FakeKeyguardBouncerRepository(),
-            )
+        val withDeps = KeyguardInteractorFactory.create(featureFlags = featureFlags)
+        val keyguardInteractor = withDeps.keyguardInteractor
+        repository = withDeps.repository
+
         whenever(userTracker.userHandle).thenReturn(mock())
-        whenever(userTracker.userId).thenReturn(10)
         whenever(lockPatternUtils.getStrongAuthForUser(anyInt()))
             .thenReturn(LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED)
         val testDispatcher = StandardTestDispatcher()
@@ -217,10 +204,10 @@
                 appContext = mContext,
                 scope = testScope.backgroundScope,
                 transitionInteractor =
-                    KeyguardTransitionInteractor(
-                        repository = FakeKeyguardTransitionRepository(),
-                        scope = testScope.backgroundScope
-                    ),
+                    KeyguardTransitionInteractorFactory.create(
+                            scope = TestScope().backgroundScope,
+                        )
+                        .keyguardTransitionInteractor,
                 repository = repository,
                 logger = UiEventLoggerFake(),
                 featureFlags = featureFlags,
@@ -233,7 +220,6 @@
                 quickAffordanceInteractor =
                     KeyguardQuickAffordanceInteractor(
                         keyguardInteractor = keyguardInteractor,
-                        registry = registry,
                         lockPatternUtils = lockPatternUtils,
                         keyguardStateController = keyguardStateController,
                         userTracker = userTracker,
@@ -555,91 +541,6 @@
         }
 
     @Test
-    fun isIndicationAreaPadded() =
-        testScope.runTest {
-            repository.setKeyguardShowing(true)
-            val value = collectLastValue(underTest.isIndicationAreaPadded)
-
-            assertThat(value()).isFalse()
-            setUpQuickAffordanceModel(
-                position = KeyguardQuickAffordancePosition.BOTTOM_START,
-                testConfig =
-                    TestConfig(
-                        isVisible = true,
-                        isClickable = true,
-                        icon = mock(),
-                        canShowWhileLocked = true,
-                        slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(),
-                    )
-            )
-            assertThat(value()).isTrue()
-            setUpQuickAffordanceModel(
-                position = KeyguardQuickAffordancePosition.BOTTOM_END,
-                testConfig =
-                    TestConfig(
-                        isVisible = true,
-                        isClickable = true,
-                        icon = mock(),
-                        canShowWhileLocked = false,
-                        slotId = KeyguardQuickAffordancePosition.BOTTOM_END.toSlotId(),
-                    )
-            )
-            assertThat(value()).isTrue()
-            setUpQuickAffordanceModel(
-                position = KeyguardQuickAffordancePosition.BOTTOM_START,
-                testConfig =
-                    TestConfig(
-                        isVisible = false,
-                        slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(),
-                    )
-            )
-            assertThat(value()).isTrue()
-            setUpQuickAffordanceModel(
-                position = KeyguardQuickAffordancePosition.BOTTOM_END,
-                testConfig =
-                    TestConfig(
-                        isVisible = false,
-                        slotId = KeyguardQuickAffordancePosition.BOTTOM_END.toSlotId(),
-                    )
-            )
-            assertThat(value()).isFalse()
-        }
-
-    @Test
-    fun indicationAreaTranslationX() =
-        testScope.runTest {
-            val value = collectLastValue(underTest.indicationAreaTranslationX)
-
-            assertThat(value()).isEqualTo(0f)
-            repository.setClockPosition(100, 100)
-            assertThat(value()).isEqualTo(100f)
-            repository.setClockPosition(200, 100)
-            assertThat(value()).isEqualTo(200f)
-            repository.setClockPosition(200, 200)
-            assertThat(value()).isEqualTo(200f)
-            repository.setClockPosition(300, 100)
-            assertThat(value()).isEqualTo(300f)
-        }
-
-    @Test
-    fun indicationAreaTranslationY() =
-        testScope.runTest {
-            val value =
-                collectLastValue(underTest.indicationAreaTranslationY(DEFAULT_BURN_IN_OFFSET))
-
-            // Negative 0 - apparently there's a difference in floating point arithmetic - FML
-            assertThat(value()).isEqualTo(-0f)
-            val expected1 = setDozeAmountAndCalculateExpectedTranslationY(0.1f)
-            assertThat(value()).isEqualTo(expected1)
-            val expected2 = setDozeAmountAndCalculateExpectedTranslationY(0.2f)
-            assertThat(value()).isEqualTo(expected2)
-            val expected3 = setDozeAmountAndCalculateExpectedTranslationY(0.5f)
-            assertThat(value()).isEqualTo(expected3)
-            val expected4 = setDozeAmountAndCalculateExpectedTranslationY(1f)
-            assertThat(value()).isEqualTo(expected4)
-        }
-
-    @Test
     fun isClickable_trueWhenAlphaAtThreshold() =
         testScope.runTest {
             repository.setKeyguardShowing(true)
@@ -762,11 +663,6 @@
             )
         }
 
-    private fun setDozeAmountAndCalculateExpectedTranslationY(dozeAmount: Float): Float {
-        repository.setDozeAmount(dozeAmount)
-        return dozeAmount * (RETURNED_BURN_IN_OFFSET - DEFAULT_BURN_IN_OFFSET)
-    }
-
     private suspend fun setUpQuickAffordanceModel(
         position: KeyguardQuickAffordancePosition,
         testConfig: TestConfig,
@@ -798,7 +694,8 @@
                 KeyguardQuickAffordanceConfig.LockScreenState.Hidden
             }
         config.setState(lockScreenState)
-        return config.key
+
+        return "${position.toSlotId()}::${config.key}"
     }
 
     private fun assertQuickAffordanceViewModel(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModelTest.kt
deleted file mode 100644
index 15707c9..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModelTest.kt
+++ /dev/null
@@ -1,159 +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.systemui.keyguard.ui.viewmodel
-
-import android.os.Looper
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.keyguard.KeyguardSecurityModel
-import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.systemui.RoboPilotTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.classifier.FalsingCollector
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags
-import com.android.systemui.keyguard.DismissCallbackRegistry
-import com.android.systemui.keyguard.data.BouncerView
-import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository
-import com.android.systemui.keyguard.data.repository.TrustRepository
-import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor
-import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor
-import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
-import com.android.systemui.statusbar.phone.KeyguardBypassController
-import com.android.systemui.statusbar.policy.KeyguardStateController
-import com.android.systemui.utils.os.FakeHandler
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.runCurrent
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.Mockito
-import org.mockito.MockitoAnnotations
-
-@SmallTest
-@RoboPilotTest
-@RunWith(AndroidJUnit4::class)
-@kotlinx.coroutines.ExperimentalCoroutinesApi
-class KeyguardBouncerViewModelTest : SysuiTestCase() {
-    lateinit var underTest: KeyguardBouncerViewModel
-    lateinit var bouncerInteractor: PrimaryBouncerInteractor
-    @Mock lateinit var bouncerView: BouncerView
-    @Mock private lateinit var keyguardStateController: KeyguardStateController
-    @Mock private lateinit var keyguardSecurityModel: KeyguardSecurityModel
-    @Mock private lateinit var primaryBouncerCallbackInteractor: PrimaryBouncerCallbackInteractor
-    @Mock private lateinit var falsingCollector: FalsingCollector
-    @Mock private lateinit var dismissCallbackRegistry: DismissCallbackRegistry
-    @Mock private lateinit var keyguardBypassController: KeyguardBypassController
-    @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
-    private val mainHandler = FakeHandler(Looper.getMainLooper())
-    val repository = FakeKeyguardBouncerRepository()
-
-    @Before
-    fun setup() {
-        MockitoAnnotations.initMocks(this)
-        bouncerInteractor =
-            PrimaryBouncerInteractor(
-                repository,
-                bouncerView,
-                mainHandler,
-                keyguardStateController,
-                keyguardSecurityModel,
-                primaryBouncerCallbackInteractor,
-                falsingCollector,
-                dismissCallbackRegistry,
-                context,
-                keyguardUpdateMonitor,
-                Mockito.mock(TrustRepository::class.java),
-                FakeFeatureFlags().apply { set(Flags.DELAY_BOUNCER, true) },
-                TestScope().backgroundScope,
-            )
-        underTest = KeyguardBouncerViewModel(bouncerView, bouncerInteractor)
-    }
-
-    @Test
-    fun setMessage() = runTest {
-        var message: BouncerShowMessageModel? = null
-        val job = underTest.bouncerShowMessage.onEach { message = it }.launchIn(this)
-
-        repository.setShowMessage(BouncerShowMessageModel("abc", null))
-        // Run the tasks that are pending at this point of virtual time.
-        runCurrent()
-        assertThat(message?.message).isEqualTo("abc")
-        job.cancel()
-    }
-
-    @Test
-    fun shouldUpdateSideFps_show() = runTest {
-        var count = 0
-        val job = underTest.shouldUpdateSideFps.onEach { count++ }.launchIn(this)
-        repository.setPrimaryShow(true)
-        // Run the tasks that are pending at this point of virtual time.
-        runCurrent()
-        assertThat(count).isEqualTo(1)
-        job.cancel()
-    }
-
-    @Test
-    fun shouldUpdateSideFps_hide() = runTest {
-        repository.setPrimaryShow(true)
-        var count = 0
-        val job = underTest.shouldUpdateSideFps.onEach { count++ }.launchIn(this)
-        repository.setPrimaryShow(false)
-        // Run the tasks that are pending at this point of virtual time.
-        runCurrent()
-        assertThat(count).isEqualTo(1)
-        job.cancel()
-    }
-
-    @Test
-    fun sideFpsShowing() = runTest {
-        var sideFpsIsShowing = false
-        val job = underTest.sideFpsShowing.onEach { sideFpsIsShowing = it }.launchIn(this)
-        repository.setSideFpsShowing(true)
-        // Run the tasks that are pending at this point of virtual time.
-        runCurrent()
-        assertThat(sideFpsIsShowing).isEqualTo(true)
-        job.cancel()
-    }
-
-    @Test
-    fun isShowing() = runTest {
-        var isShowing: Boolean? = null
-        val job = underTest.isShowing.onEach { isShowing = it }.launchIn(this)
-        repository.setPrimaryShow(true)
-        // Run the tasks that are pending at this point of virtual time.
-        runCurrent()
-        assertThat(isShowing).isEqualTo(true)
-        job.cancel()
-    }
-
-    @Test
-    fun isNotShowing() = runTest {
-        var isShowing: Boolean? = null
-        val job = underTest.isShowing.onEach { isShowing = it }.launchIn(this)
-        repository.setPrimaryShow(false)
-        // Run the tasks that are pending at this point of virtual time.
-        runCurrent()
-        assertThat(isShowing).isEqualTo(false)
-        job.cancel()
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModelTest.kt
new file mode 100644
index 0000000..dff0f29
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModelTest.kt
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.doze.util.BurnInHelperWrapper
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
+import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class KeyguardIndicationAreaViewModelTest : SysuiTestCase() {
+
+    @Mock private lateinit var burnInHelperWrapper: BurnInHelperWrapper
+
+    private lateinit var underTest: KeyguardIndicationAreaViewModel
+    private lateinit var repository: FakeKeyguardRepository
+
+    private val startButtonFlow =
+        MutableStateFlow<KeyguardQuickAffordanceViewModel>(
+            KeyguardQuickAffordanceViewModel(
+                slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId()
+            )
+        )
+    private val endButtonFlow =
+        MutableStateFlow<KeyguardQuickAffordanceViewModel>(
+            KeyguardQuickAffordanceViewModel(
+                slotId = KeyguardQuickAffordancePosition.BOTTOM_END.toSlotId()
+            )
+        )
+    private val alphaFlow = MutableStateFlow<Float>(1f)
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        whenever(burnInHelperWrapper.burnInOffset(anyInt(), any()))
+            .thenReturn(RETURNED_BURN_IN_OFFSET)
+
+        val withDeps = KeyguardInteractorFactory.create()
+        val keyguardInteractor = withDeps.keyguardInteractor
+        repository = withDeps.repository
+
+        val bottomAreaViewModel: KeyguardBottomAreaViewModel = mock()
+        whenever(bottomAreaViewModel.startButton).thenReturn(startButtonFlow)
+        whenever(bottomAreaViewModel.endButton).thenReturn(endButtonFlow)
+        whenever(bottomAreaViewModel.alpha).thenReturn(alphaFlow)
+        underTest =
+            KeyguardIndicationAreaViewModel(
+                keyguardInteractor = keyguardInteractor,
+                bottomAreaInteractor = KeyguardBottomAreaInteractor(repository = repository),
+                keyguardBottomAreaViewModel = bottomAreaViewModel,
+                burnInHelperWrapper = burnInHelperWrapper,
+            )
+    }
+
+    @Test
+    fun alpha() = runTest {
+        val value = collectLastValue(underTest.alpha)
+
+        assertThat(value()).isEqualTo(1f)
+        alphaFlow.value = 0.1f
+        assertThat(value()).isEqualTo(0.1f)
+        alphaFlow.value = 0.5f
+        assertThat(value()).isEqualTo(0.5f)
+        alphaFlow.value = 0.2f
+        assertThat(value()).isEqualTo(0.2f)
+        alphaFlow.value = 0f
+        assertThat(value()).isEqualTo(0f)
+    }
+
+    @Test
+    fun isIndicationAreaPadded() = runTest {
+        repository.setKeyguardShowing(true)
+        val value = collectLastValue(underTest.isIndicationAreaPadded)
+
+        assertThat(value()).isFalse()
+        startButtonFlow.value = startButtonFlow.value.copy(isVisible = true)
+        assertThat(value()).isTrue()
+        endButtonFlow.value = endButtonFlow.value.copy(isVisible = true)
+        assertThat(value()).isTrue()
+        startButtonFlow.value = startButtonFlow.value.copy(isVisible = false)
+        assertThat(value()).isTrue()
+        endButtonFlow.value = endButtonFlow.value.copy(isVisible = false)
+        assertThat(value()).isFalse()
+    }
+
+    @Test
+    fun indicationAreaTranslationX() = runTest {
+        val value = collectLastValue(underTest.indicationAreaTranslationX)
+
+        assertThat(value()).isEqualTo(0f)
+        repository.setClockPosition(100, 100)
+        assertThat(value()).isEqualTo(100f)
+        repository.setClockPosition(200, 100)
+        assertThat(value()).isEqualTo(200f)
+        repository.setClockPosition(200, 200)
+        assertThat(value()).isEqualTo(200f)
+        repository.setClockPosition(300, 100)
+        assertThat(value()).isEqualTo(300f)
+    }
+
+    @Test
+    fun indicationAreaTranslationY() = runTest {
+        val value = collectLastValue(underTest.indicationAreaTranslationY(DEFAULT_BURN_IN_OFFSET))
+
+        // Negative 0 - apparently there's a difference in floating point arithmetic - FML
+        assertThat(value()).isEqualTo(-0f)
+        val expected1 = setDozeAmountAndCalculateExpectedTranslationY(0.1f)
+        assertThat(value()).isEqualTo(expected1)
+        val expected2 = setDozeAmountAndCalculateExpectedTranslationY(0.2f)
+        assertThat(value()).isEqualTo(expected2)
+        val expected3 = setDozeAmountAndCalculateExpectedTranslationY(0.5f)
+        assertThat(value()).isEqualTo(expected3)
+        val expected4 = setDozeAmountAndCalculateExpectedTranslationY(1f)
+        assertThat(value()).isEqualTo(expected4)
+    }
+
+    private fun setDozeAmountAndCalculateExpectedTranslationY(dozeAmount: Float): Float {
+        repository.setDozeAmount(dozeAmount)
+        return dozeAmount * (RETURNED_BURN_IN_OFFSET - DEFAULT_BURN_IN_OFFSET)
+    }
+
+    companion object {
+        private const val DEFAULT_BURN_IN_OFFSET = 5
+        private const val RETURNED_BURN_IN_OFFSET = 3
+    }
+}
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 8ba3f0f..ba8e0f2 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
@@ -29,7 +29,6 @@
 import com.android.systemui.scene.shared.model.SceneModel
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
@@ -41,8 +40,8 @@
 @RunWith(JUnit4::class)
 class LockscreenSceneViewModelTest : SysuiTestCase() {
 
-    private val testScope = TestScope()
-    private val utils = SceneTestUtils(this, testScope)
+    private val utils = SceneTestUtils(this)
+    private val testScope = utils.testScope
     private val sceneInteractor = utils.sceneInteractor()
     private val authenticationInteractor =
         utils.authenticationInteractor(
@@ -57,7 +56,6 @@
                     override fun create(containerName: String): LockscreenSceneInteractor {
                         return utils.lockScreenSceneInteractor(
                             authenticationInteractor = authenticationInteractor,
-                            sceneInteractor = sceneInteractor,
                             bouncerInteractor =
                                 utils.bouncerInteractor(
                                     authenticationInteractor = authenticationInteractor,
@@ -73,10 +71,10 @@
     fun lockButtonIcon_whenLocked() =
         testScope.runTest {
             val lockButtonIcon by collectLastValue(underTest.lockButtonIcon)
-            authenticationInteractor.setAuthenticationMethod(
-                AuthenticationMethodModel.Password("password")
+            utils.authenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Password
             )
-            authenticationInteractor.lockDevice()
+            utils.authenticationRepository.setUnlocked(false)
 
             assertThat((lockButtonIcon as? Icon.Resource)?.res)
                 .isEqualTo(R.drawable.ic_device_lock_on)
@@ -86,10 +84,10 @@
     fun lockButtonIcon_whenUnlocked() =
         testScope.runTest {
             val lockButtonIcon by collectLastValue(underTest.lockButtonIcon)
-            authenticationInteractor.setAuthenticationMethod(
-                AuthenticationMethodModel.Password("password")
+            utils.authenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Password
             )
-            authenticationInteractor.unlockDevice()
+            utils.authenticationRepository.setUnlocked(true)
 
             assertThat((lockButtonIcon as? Icon.Resource)?.res)
                 .isEqualTo(R.drawable.ic_device_lock_off)
@@ -99,8 +97,8 @@
     fun upTransitionSceneKey_swipeToUnlockedEnabled_gone() =
         testScope.runTest {
             val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
-            authenticationInteractor.lockDevice()
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
+            utils.authenticationRepository.setUnlocked(false)
 
             assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Gone)
         }
@@ -109,8 +107,8 @@
     fun upTransitionSceneKey_swipeToUnlockedNotEnabled_bouncer() =
         testScope.runTest {
             val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
-            authenticationInteractor.lockDevice()
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            utils.authenticationRepository.setUnlocked(false)
 
             assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Bouncer)
         }
@@ -119,8 +117,8 @@
     fun onLockButtonClicked_deviceLockedSecurely_switchesToBouncer() =
         testScope.runTest {
             val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
-            authenticationInteractor.lockDevice()
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            utils.authenticationRepository.setUnlocked(false)
             runCurrent()
 
             underTest.onLockButtonClicked()
@@ -132,8 +130,8 @@
     fun onContentClicked_deviceUnlocked_switchesToGone() =
         testScope.runTest {
             val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
-            authenticationInteractor.unlockDevice()
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            utils.authenticationRepository.setUnlocked(true)
             runCurrent()
 
             underTest.onContentClicked()
@@ -145,8 +143,8 @@
     fun onContentClicked_deviceLockedSecurely_switchesToBouncer() =
         testScope.runTest {
             val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
-            authenticationInteractor.lockDevice()
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            utils.authenticationRepository.setUnlocked(false)
             runCurrent()
 
             underTest.onContentClicked()
@@ -158,8 +156,8 @@
     fun onLockButtonClicked_deviceUnlocked_switchesToGone() =
         testScope.runTest {
             val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
-            authenticationInteractor.unlockDevice()
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            utils.authenticationRepository.setUnlocked(true)
             runCurrent()
 
             underTest.onLockButtonClicked()
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 ea17751..12fe07f 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
@@ -21,7 +21,7 @@
 import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -46,7 +46,12 @@
     @Before
     fun setUp() {
         repository = FakeKeyguardTransitionRepository()
-        val interactor = KeyguardTransitionInteractor(repository, TestScope().backgroundScope)
+        val interactor =
+            KeyguardTransitionInteractorFactory.create(
+                    scope = TestScope().backgroundScope,
+                    repository = repository,
+                )
+                .keyguardTransitionInteractor
         underTest = LockscreenToDreamingTransitionViewModel(interactor)
     }
 
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 bf56a98..83ae631 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
@@ -21,7 +21,7 @@
 import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -46,7 +46,12 @@
     @Before
     fun setUp() {
         repository = FakeKeyguardTransitionRepository()
-        val interactor = KeyguardTransitionInteractor(repository, TestScope().backgroundScope)
+        val interactor =
+            KeyguardTransitionInteractorFactory.create(
+                    scope = TestScope().backgroundScope,
+                    repository = repository,
+                )
+                .keyguardTransitionInteractor
         underTest = LockscreenToOccludedTransitionViewModel(interactor)
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt
index 34da26e..8860399 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt
@@ -21,7 +21,7 @@
 import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -46,7 +46,12 @@
     @Before
     fun setUp() {
         repository = FakeKeyguardTransitionRepository()
-        val interactor = KeyguardTransitionInteractor(repository, TestScope().backgroundScope)
+        val interactor =
+            KeyguardTransitionInteractorFactory.create(
+                    scope = TestScope().backgroundScope,
+                    repository = repository,
+                )
+                .keyguardTransitionInteractor
         underTest = OccludedToLockscreenTransitionViewModel(interactor)
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
index 65b2ef7..d8c78eb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
@@ -20,9 +20,9 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.ScrimAlpha
 import com.android.systemui.keyguard.shared.model.TransitionState
@@ -55,7 +55,12 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
         repository = FakeKeyguardTransitionRepository()
-        val interactor = KeyguardTransitionInteractor(repository, TestScope().backgroundScope)
+        val interactor =
+            KeyguardTransitionInteractorFactory.create(
+                    scope = TestScope().backgroundScope,
+                    repository = repository,
+                )
+                .keyguardTransitionInteractor
         underTest =
             PrimaryBouncerToGoneTransitionViewModel(
                 interactor,
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
new file mode 100644
index 0000000..436c09c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModelTest.kt
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
+import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
+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.domain.interactor.BurnInInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.UdfpsKeyguardInteractor
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@ExperimentalCoroutinesApi
+@SmallTest
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
+class UdfpsAodViewModelTest : SysuiTestCase() {
+    private val defaultPadding = 12
+    private lateinit var underTest: UdfpsAodViewModel
+
+    private lateinit var testScope: TestScope
+    private lateinit var configRepository: FakeConfigurationRepository
+    private lateinit var bouncerRepository: KeyguardBouncerRepository
+    private lateinit var keyguardRepository: FakeKeyguardRepository
+    private lateinit var fakeCommandQueue: FakeCommandQueue
+    private lateinit var featureFlags: FakeFeatureFlags
+
+    @Mock private lateinit var burnInHelper: BurnInHelperWrapper
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        overrideResource(com.android.systemui.R.dimen.lock_icon_padding, defaultPadding)
+        testScope = TestScope()
+        configRepository = FakeConfigurationRepository()
+        keyguardRepository = FakeKeyguardRepository()
+        bouncerRepository = FakeKeyguardBouncerRepository()
+        fakeCommandQueue = FakeCommandQueue()
+        featureFlags =
+            FakeFeatureFlags().apply {
+                set(Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS, true)
+                set(Flags.FACE_AUTH_REFACTOR, false)
+            }
+
+        val udfpsKeyguardInteractor =
+            UdfpsKeyguardInteractor(
+                configRepository,
+                BurnInInteractor(
+                    context,
+                    burnInHelper,
+                    testScope.backgroundScope,
+                    configRepository,
+                    FakeSystemClock(),
+                ),
+                KeyguardInteractor(
+                    keyguardRepository,
+                    fakeCommandQueue,
+                    featureFlags,
+                    bouncerRepository,
+                    configRepository,
+                ),
+            )
+
+        underTest =
+            UdfpsAodViewModel(
+                udfpsKeyguardInteractor,
+                context,
+            )
+    }
+
+    @Test
+    fun alphaAndVisibleUpdates_onDozeAmountChanges() =
+        testScope.runTest {
+            val alpha by collectLastValue(underTest.alpha)
+            val visible by collectLastValue(underTest.isVisible)
+
+            keyguardRepository.setDozeAmount(0f)
+            runCurrent()
+            assertThat(alpha).isEqualTo(0f)
+            assertThat(visible).isFalse()
+
+            keyguardRepository.setDozeAmount(.65f)
+            runCurrent()
+            assertThat(alpha).isEqualTo(.65f)
+            assertThat(visible).isTrue()
+
+            keyguardRepository.setDozeAmount(.23f)
+            runCurrent()
+            assertThat(alpha).isEqualTo(.23f)
+            assertThat(visible).isTrue()
+
+            keyguardRepository.setDozeAmount(1f)
+            runCurrent()
+            assertThat(alpha).isEqualTo(1f)
+            assertThat(visible).isTrue()
+        }
+
+    @Test
+    fun paddingUpdates_onScaleForResolutionChanges() =
+        testScope.runTest {
+            val padding by collectLastValue(underTest.padding)
+
+            configRepository.setScaleForResolution(1f)
+            runCurrent()
+            assertThat(padding).isEqualTo(defaultPadding)
+
+            configRepository.setScaleForResolution(2f)
+            runCurrent()
+            assertThat(padding).isEqualTo(defaultPadding * 2)
+
+            configRepository.setScaleForResolution(.5f)
+            runCurrent()
+            assertThat(padding).isEqualTo((defaultPadding * .5f).toInt())
+        }
+}
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
new file mode 100644
index 0000000..a30e2a6
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsFingerprintViewModelTest.kt
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
+import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
+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
+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.keyguard.domain.interactor.UdfpsKeyguardInteractor
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+/** Tests UdfpsFingerprintViewModel specific flows. */
+@ExperimentalCoroutinesApi
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class UdfpsFingerprintViewModelTest : SysuiTestCase() {
+    private val defaultPadding = 12
+    private lateinit var underTest: FingerprintViewModel
+
+    private lateinit var testScope: TestScope
+    private lateinit var configRepository: FakeConfigurationRepository
+    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
+
+    @Mock private lateinit var burnInHelper: BurnInHelperWrapper
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        overrideResource(com.android.systemui.R.dimen.lock_icon_padding, defaultPadding)
+        testScope = TestScope()
+        configRepository = FakeConfigurationRepository()
+        keyguardRepository = FakeKeyguardRepository()
+        bouncerRepository = FakeKeyguardBouncerRepository()
+        fakeCommandQueue = FakeCommandQueue()
+        featureFlags =
+            FakeFeatureFlags().apply {
+                set(Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS, true)
+                set(Flags.FACE_AUTH_REFACTOR, false)
+            }
+        bouncerRepository = FakeKeyguardBouncerRepository()
+        transitionRepository = FakeKeyguardTransitionRepository()
+        val transitionInteractor =
+            KeyguardTransitionInteractor(
+                transitionRepository,
+                testScope.backgroundScope,
+            )
+        val udfpsKeyguardInteractor =
+            UdfpsKeyguardInteractor(
+                configRepository,
+                BurnInInteractor(
+                    context,
+                    burnInHelper,
+                    testScope.backgroundScope,
+                    configRepository,
+                    FakeSystemClock(),
+                ),
+                KeyguardInteractor(
+                    keyguardRepository,
+                    fakeCommandQueue,
+                    featureFlags,
+                    bouncerRepository,
+                    configRepository,
+                ),
+            )
+
+        underTest =
+            FingerprintViewModel(
+                context,
+                transitionInteractor,
+                udfpsKeyguardInteractor,
+            )
+    }
+
+    @Test
+    fun paddingUpdates_onScaleForResolutionChanges() =
+        testScope.runTest {
+            val padding by collectLastValue(underTest.padding)
+
+            configRepository.setScaleForResolution(1f)
+            runCurrent()
+            assertThat(padding).isEqualTo(defaultPadding)
+
+            configRepository.setScaleForResolution(2f)
+            runCurrent()
+            assertThat(padding).isEqualTo(defaultPadding * 2)
+
+            configRepository.setScaleForResolution(.5f)
+            runCurrent()
+            assertThat(padding).isEqualTo((defaultPadding * .5).toInt())
+        }
+}
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
new file mode 100644
index 0000000..d58ceee
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModelTest.kt
@@ -0,0 +1,505 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.settingslib.Utils
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+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.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.google.common.collect.Range
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.MockitoAnnotations
+
+/** Tests UDFPS lockscreen view model transitions. */
+@ExperimentalCoroutinesApi
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class UdfpsLockscreenViewModelTest : SysuiTestCase() {
+    private val lockscreenColorResId = android.R.attr.textColorPrimary
+    private val alternateBouncerResId = com.android.internal.R.attr.materialColorOnPrimaryFixed
+    private val lockscreenColor = Utils.getColorAttrDefaultColor(context, lockscreenColorResId)
+    private val alternateBouncerColor =
+        Utils.getColorAttrDefaultColor(context, alternateBouncerResId)
+
+    private lateinit var underTest: UdfpsLockscreenViewModel
+    private lateinit var testScope: TestScope
+    private lateinit var transitionRepository: FakeKeyguardTransitionRepository
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        testScope = TestScope()
+        transitionRepository = FakeKeyguardTransitionRepository()
+        val transitionInteractor =
+            KeyguardTransitionInteractor(
+                transitionRepository,
+                testScope.backgroundScope,
+            )
+        underTest =
+            UdfpsLockscreenViewModel(
+                context,
+                lockscreenColorResId,
+                alternateBouncerResId,
+                transitionInteractor,
+            )
+    }
+
+    @Test
+    fun goneToAodTransition() =
+        testScope.runTest {
+            val transition by collectLastValue(underTest.transition)
+            val visible by collectLastValue(underTest.visible)
+
+            // TransitionState.STARTED: gone -> AOD
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.GONE,
+                    to = KeyguardState.AOD,
+                    value = 0f,
+                    transitionState = TransitionState.STARTED,
+                    ownerName = "goneToAodTransition",
+                )
+            )
+            runCurrent()
+            assertThat(transition?.alpha).isEqualTo(0f)
+            assertThat(visible).isFalse()
+
+            // TransitionState.RUNNING: gone -> AOD
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.GONE,
+                    to = KeyguardState.AOD,
+                    value = .6f,
+                    transitionState = TransitionState.RUNNING,
+                    ownerName = "goneToAodTransition",
+                )
+            )
+            runCurrent()
+            assertThat(transition?.alpha).isEqualTo(0f)
+            assertThat(visible).isFalse()
+
+            // TransitionState.FINISHED: gone -> AOD
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.GONE,
+                    to = KeyguardState.AOD,
+                    value = 1f,
+                    transitionState = TransitionState.FINISHED,
+                    ownerName = "goneToAodTransition",
+                )
+            )
+            runCurrent()
+            assertThat(transition?.alpha).isEqualTo(0f)
+            assertThat(visible).isFalse()
+        }
+
+    @Test
+    fun lockscreenToAod() =
+        testScope.runTest {
+            val transition by collectLastValue(underTest.transition)
+            val visible by collectLastValue(underTest.visible)
+
+            // TransitionState.STARTED: lockscreen -> AOD
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.AOD,
+                    value = 0f,
+                    transitionState = TransitionState.STARTED,
+                    ownerName = "lockscreenToAod",
+                )
+            )
+            runCurrent()
+            assertThat(transition?.alpha).isEqualTo(1f)
+            assertThat(transition?.scale).isEqualTo(1f)
+            assertThat(transition?.color).isEqualTo(lockscreenColor)
+            assertThat(visible).isTrue()
+
+            // TransitionState.RUNNING: lockscreen -> AOD
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.AOD,
+                    value = .6f,
+                    transitionState = TransitionState.RUNNING,
+                    ownerName = "lockscreenToAod",
+                )
+            )
+            runCurrent()
+            assertThat(transition?.alpha).isIn(Range.closed(.39f, .41f))
+            assertThat(transition?.scale).isEqualTo(1f)
+            assertThat(transition?.color).isEqualTo(lockscreenColor)
+            assertThat(visible).isTrue()
+
+            // TransitionState.FINISHED: lockscreen -> AOD
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.AOD,
+                    value = 1f,
+                    transitionState = TransitionState.FINISHED,
+                    ownerName = "lockscreenToAod",
+                )
+            )
+            runCurrent()
+            assertThat(transition?.alpha).isEqualTo(0f)
+            assertThat(transition?.scale).isEqualTo(1f)
+            assertThat(transition?.color).isEqualTo(lockscreenColor)
+            assertThat(visible).isFalse()
+        }
+
+    @Test
+    fun aodToLockscreen() =
+        testScope.runTest {
+            val transition by collectLastValue(underTest.transition)
+            val visible by collectLastValue(underTest.visible)
+
+            // TransitionState.STARTED: AOD -> lockscreen
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.AOD,
+                    to = KeyguardState.LOCKSCREEN,
+                    value = 0f,
+                    transitionState = TransitionState.STARTED,
+                    ownerName = "aodToLockscreen",
+                )
+            )
+            runCurrent()
+            assertThat(transition?.alpha).isEqualTo(0f)
+            assertThat(transition?.scale).isEqualTo(1f)
+            assertThat(transition?.color).isEqualTo(lockscreenColor)
+            assertThat(visible).isFalse()
+
+            // TransitionState.RUNNING: AOD -> lockscreen
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.AOD,
+                    to = KeyguardState.LOCKSCREEN,
+                    value = .6f,
+                    transitionState = TransitionState.RUNNING,
+                    ownerName = "aodToLockscreen",
+                )
+            )
+            runCurrent()
+            assertThat(transition?.alpha).isIn(Range.closed(.59f, .61f))
+            assertThat(transition?.scale).isEqualTo(1f)
+            assertThat(transition?.color).isEqualTo(lockscreenColor)
+            assertThat(visible).isTrue()
+
+            // TransitionState.FINISHED: AOD -> lockscreen
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.AOD,
+                    to = KeyguardState.LOCKSCREEN,
+                    value = 1f,
+                    transitionState = TransitionState.FINISHED,
+                    ownerName = "aodToLockscreen",
+                )
+            )
+            runCurrent()
+            assertThat(transition?.alpha).isEqualTo(1f)
+            assertThat(transition?.scale).isEqualTo(1f)
+            assertThat(transition?.color).isEqualTo(lockscreenColor)
+            assertThat(visible).isTrue()
+        }
+
+    @Test
+    fun lockscreenToAlternateBouncer() =
+        testScope.runTest {
+            val transition by collectLastValue(underTest.transition)
+            val visible by collectLastValue(underTest.visible)
+
+            // TransitionState.STARTED: lockscreen -> alternate bouncer
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.ALTERNATE_BOUNCER,
+                    value = 0f,
+                    transitionState = TransitionState.STARTED,
+                    ownerName = "lockscreenToAlternateBouncer",
+                )
+            )
+            runCurrent()
+            assertThat(transition?.alpha).isEqualTo(1f)
+            assertThat(transition?.scale).isEqualTo(1f)
+            assertThat(transition?.color).isEqualTo(alternateBouncerColor)
+            assertThat(visible).isTrue()
+
+            // TransitionState.RUNNING: lockscreen -> alternate bouncer
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.ALTERNATE_BOUNCER,
+                    value = .6f,
+                    transitionState = TransitionState.RUNNING,
+                    ownerName = "lockscreenToAlternateBouncer",
+                )
+            )
+            runCurrent()
+            assertThat(transition?.alpha).isEqualTo(1f)
+            assertThat(transition?.scale).isEqualTo(1f)
+            assertThat(transition?.color).isEqualTo(alternateBouncerColor)
+            assertThat(visible).isTrue()
+
+            // TransitionState.FINISHED: lockscreen -> alternate bouncer
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.ALTERNATE_BOUNCER,
+                    value = 1f,
+                    transitionState = TransitionState.FINISHED,
+                    ownerName = "lockscreenToAlternateBouncer",
+                )
+            )
+            runCurrent()
+            assertThat(transition?.alpha).isEqualTo(1f)
+            assertThat(transition?.scale).isEqualTo(1f)
+            assertThat(transition?.color).isEqualTo(alternateBouncerColor)
+            assertThat(visible).isTrue()
+        }
+
+    fun alternateBouncerToPrimaryBouncer() =
+        testScope.runTest {
+            val transition by collectLastValue(underTest.transition)
+            val visible by collectLastValue(underTest.visible)
+
+            // TransitionState.STARTED: alternate bouncer -> primary bouncer
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.ALTERNATE_BOUNCER,
+                    to = KeyguardState.PRIMARY_BOUNCER,
+                    value = 0f,
+                    transitionState = TransitionState.STARTED,
+                    ownerName = "alternateBouncerToPrimaryBouncer",
+                )
+            )
+            runCurrent()
+            assertThat(transition?.alpha).isEqualTo(1f)
+            assertThat(transition?.scale).isEqualTo(1f)
+            assertThat(transition?.color).isEqualTo(alternateBouncerColor)
+            assertThat(visible).isTrue()
+
+            // TransitionState.RUNNING: alternate bouncer -> primary bouncer
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.ALTERNATE_BOUNCER,
+                    to = KeyguardState.PRIMARY_BOUNCER,
+                    value = .6f,
+                    transitionState = TransitionState.RUNNING,
+                    ownerName = "alternateBouncerToPrimaryBouncer",
+                )
+            )
+            runCurrent()
+            assertThat(transition?.alpha).isIn(Range.closed(.59f, .61f))
+            assertThat(transition?.scale).isEqualTo(1f)
+            assertThat(transition?.color).isEqualTo(alternateBouncerColor)
+            assertThat(visible).isTrue()
+
+            // TransitionState.FINISHED: alternate bouncer -> primary bouncer
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.ALTERNATE_BOUNCER,
+                    to = KeyguardState.PRIMARY_BOUNCER,
+                    value = 1f,
+                    transitionState = TransitionState.FINISHED,
+                    ownerName = "alternateBouncerToPrimaryBouncer",
+                )
+            )
+            runCurrent()
+            assertThat(transition?.alpha).isEqualTo(0f)
+            assertThat(transition?.scale).isEqualTo(1f)
+            assertThat(transition?.color).isEqualTo(alternateBouncerColor)
+            assertThat(visible).isFalse()
+        }
+
+    fun alternateBouncerToAod() =
+        testScope.runTest {
+            val transition by collectLastValue(underTest.transition)
+            val visible by collectLastValue(underTest.visible)
+
+            // TransitionState.STARTED: alternate bouncer -> aod
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.ALTERNATE_BOUNCER,
+                    to = KeyguardState.AOD,
+                    value = 0f,
+                    transitionState = TransitionState.STARTED,
+                    ownerName = "alternateBouncerToAod",
+                )
+            )
+            runCurrent()
+            assertThat(transition?.alpha).isEqualTo(1f)
+            assertThat(transition?.scale).isEqualTo(1f)
+            assertThat(transition?.color).isEqualTo(alternateBouncerColor)
+            assertThat(visible).isTrue()
+
+            // TransitionState.RUNNING: alternate bouncer -> aod
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.ALTERNATE_BOUNCER,
+                    to = KeyguardState.AOD,
+                    value = .6f,
+                    transitionState = TransitionState.RUNNING,
+                    ownerName = "alternateBouncerToAod",
+                )
+            )
+            runCurrent()
+            assertThat(transition?.alpha).isIn(Range.closed(.39f, .41f))
+            assertThat(transition?.scale).isEqualTo(1f)
+            assertThat(transition?.color).isEqualTo(alternateBouncerColor)
+            assertThat(visible).isTrue()
+
+            // TransitionState.FINISHED: alternate bouncer -> aod
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.ALTERNATE_BOUNCER,
+                    to = KeyguardState.AOD,
+                    value = 1f,
+                    transitionState = TransitionState.FINISHED,
+                    ownerName = "alternateBouncerToAod",
+                )
+            )
+            runCurrent()
+            assertThat(transition?.alpha).isEqualTo(0f)
+            assertThat(transition?.scale).isEqualTo(1f)
+            assertThat(transition?.color).isEqualTo(alternateBouncerColor)
+            assertThat(visible).isFalse()
+        }
+
+    @Test
+    fun lockscreenToOccluded() =
+        testScope.runTest {
+            val transition by collectLastValue(underTest.transition)
+            val visible by collectLastValue(underTest.visible)
+
+            // TransitionState.STARTED: lockscreen -> occluded
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.OCCLUDED,
+                    value = 0f,
+                    transitionState = TransitionState.STARTED,
+                    ownerName = "lockscreenToOccluded",
+                )
+            )
+            runCurrent()
+            assertThat(transition?.alpha).isEqualTo(1f)
+            assertThat(transition?.scale).isEqualTo(1f)
+            assertThat(transition?.color).isEqualTo(lockscreenColor)
+            assertThat(visible).isTrue()
+
+            // TransitionState.RUNNING: lockscreen -> occluded
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.OCCLUDED,
+                    value = .6f,
+                    transitionState = TransitionState.RUNNING,
+                    ownerName = "lockscreenToOccluded",
+                )
+            )
+            runCurrent()
+            assertThat(transition?.alpha).isIn(Range.closed(.39f, .41f))
+            assertThat(transition?.scale).isEqualTo(1f)
+            assertThat(transition?.color).isEqualTo(lockscreenColor)
+            assertThat(visible).isTrue()
+
+            // TransitionState.FINISHED: lockscreen -> occluded
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.OCCLUDED,
+                    value = 1f,
+                    transitionState = TransitionState.FINISHED,
+                    ownerName = "lockscreenToOccluded",
+                )
+            )
+            runCurrent()
+            assertThat(transition?.alpha).isEqualTo(0f)
+            assertThat(transition?.scale).isEqualTo(1f)
+            assertThat(transition?.color).isEqualTo(lockscreenColor)
+            assertThat(visible).isFalse()
+        }
+
+    @Test
+    fun occludedToLockscreen() =
+        testScope.runTest {
+            val transition by collectLastValue(underTest.transition)
+            val visible by collectLastValue(underTest.visible)
+
+            // TransitionState.STARTED: occluded -> lockscreen
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.OCCLUDED,
+                    to = KeyguardState.LOCKSCREEN,
+                    value = 0f,
+                    transitionState = TransitionState.STARTED,
+                    ownerName = "occludedToLockscreen",
+                )
+            )
+            runCurrent()
+            assertThat(transition?.alpha).isEqualTo(1f)
+            assertThat(transition?.scale).isEqualTo(1f)
+            assertThat(transition?.color).isEqualTo(lockscreenColor)
+            assertThat(visible).isTrue()
+
+            // TransitionState.RUNNING: occluded -> lockscreen
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.OCCLUDED,
+                    to = KeyguardState.LOCKSCREEN,
+                    value = .6f,
+                    transitionState = TransitionState.RUNNING,
+                    ownerName = "occludedToLockscreen",
+                )
+            )
+            runCurrent()
+            assertThat(transition?.alpha).isEqualTo(1f)
+            assertThat(transition?.scale).isEqualTo(1f)
+            assertThat(transition?.color).isEqualTo(lockscreenColor)
+            assertThat(visible).isTrue()
+
+            // TransitionState.FINISHED: occluded -> lockscreen
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.OCCLUDED,
+                    to = KeyguardState.LOCKSCREEN,
+                    value = 1f,
+                    transitionState = TransitionState.FINISHED,
+                    ownerName = "occludedToLockscreen",
+                )
+            )
+            runCurrent()
+            assertThat(transition?.alpha).isEqualTo(1f)
+            assertThat(transition?.scale).isEqualTo(1f)
+            assertThat(transition?.color).isEqualTo(lockscreenColor)
+            assertThat(visible).isTrue()
+        }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/util/IndicationHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/util/IndicationHelperTest.kt
new file mode 100644
index 0000000..fd0ff9b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/util/IndicationHelperTest.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.systemui.keyguard.util
+
+import android.hardware.biometrics.BiometricFaceConstants.BIOMETRIC_ERROR_POWER_PRESSED
+import android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_CANCELED
+import android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_LOCKOUT
+import android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_LOCKOUT_PERMANENT
+import android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_TIMEOUT
+import android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_UNABLE_TO_PROCESS
+import android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_VENDOR
+import android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_CANCELED
+import android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT
+import android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT_PERMANENT
+import android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_TIMEOUT
+import android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_USER_CANCELED
+import android.hardware.biometrics.BiometricSourceType
+import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.whenever
+import junit.framework.Assert.assertFalse
+import junit.framework.Assert.assertTrue
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.Mock
+import org.mockito.junit.MockitoJUnit
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@TestableLooper.RunWithLooper
+class IndicationHelperTest : SysuiTestCase() {
+
+    @JvmField @Rule var mockitoRule = MockitoJUnit.rule()
+
+    @Mock lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+    private lateinit var underTest: IndicationHelper
+
+    @Before
+    fun setup() {
+        underTest =
+            IndicationHelper(
+                keyguardUpdateMonitor,
+            )
+    }
+
+    @Test
+    fun suppressErrorMsg_faceErrorCancelled() {
+        givenPrimaryAuthNotRequired()
+        assertTrue(underTest.shouldSuppressErrorMsg(BiometricSourceType.FACE, FACE_ERROR_CANCELED))
+    }
+
+    @Test
+    fun suppressErrorMsg_faceErrorUnableToProcess() {
+        givenPrimaryAuthNotRequired()
+        assertTrue(
+            underTest.shouldSuppressErrorMsg(BiometricSourceType.FACE, FACE_ERROR_UNABLE_TO_PROCESS)
+        )
+    }
+
+    @Test
+    fun suppressErrorMsg_facePrimaryAuthRequired() {
+        givenPrimaryAuthRequired()
+        assertTrue(underTest.shouldSuppressErrorMsg(BiometricSourceType.FACE, FACE_ERROR_TIMEOUT))
+    }
+
+    @Test
+    fun doNotSuppressErrorMsg_facePrimaryAuthRequired_faceLockout() {
+        givenPrimaryAuthRequired()
+        assertFalse(underTest.shouldSuppressErrorMsg(BiometricSourceType.FACE, FACE_ERROR_LOCKOUT))
+        assertFalse(
+            underTest.shouldSuppressErrorMsg(BiometricSourceType.FACE, FACE_ERROR_LOCKOUT_PERMANENT)
+        )
+    }
+
+    @Test
+    fun suppressErrorMsg_fingerprintErrorCancelled() {
+        givenPrimaryAuthNotRequired()
+        assertTrue(
+            underTest.shouldSuppressErrorMsg(
+                BiometricSourceType.FINGERPRINT,
+                FINGERPRINT_ERROR_CANCELED
+            )
+        )
+    }
+
+    @Test
+    fun suppressErrorMsg_fingerprintErrorUserCancelled() {
+        givenPrimaryAuthNotRequired()
+        assertTrue(
+            underTest.shouldSuppressErrorMsg(
+                BiometricSourceType.FINGERPRINT,
+                FINGERPRINT_ERROR_USER_CANCELED
+            )
+        )
+    }
+
+    @Test
+    fun suppressErrorMsg_fingerprintErrorPowerPressed() {
+        givenPrimaryAuthNotRequired()
+        assertTrue(
+            underTest.shouldSuppressErrorMsg(
+                BiometricSourceType.FINGERPRINT,
+                BIOMETRIC_ERROR_POWER_PRESSED
+            )
+        )
+    }
+
+    @Test
+    fun suppressErrorMsg_fingerprintPrimaryAuthRequired() {
+        givenPrimaryAuthRequired()
+        assertTrue(
+            underTest.shouldSuppressErrorMsg(BiometricSourceType.FACE, FINGERPRINT_ERROR_TIMEOUT)
+        )
+    }
+
+    @Test
+    fun doNotSuppressErrorMsg_fingerprintPrimaryAuthRequired_fingerprintLockout() {
+        givenPrimaryAuthRequired()
+        assertFalse(
+            underTest.shouldSuppressErrorMsg(
+                BiometricSourceType.FINGERPRINT,
+                FINGERPRINT_ERROR_LOCKOUT
+            )
+        )
+        assertFalse(
+            underTest.shouldSuppressErrorMsg(
+                BiometricSourceType.FACE,
+                FINGERPRINT_ERROR_LOCKOUT_PERMANENT
+            )
+        )
+    }
+
+    @Test
+    fun isFaceLockoutErrorMsgId() {
+        givenPrimaryAuthRequired()
+        assertTrue(underTest.isFaceLockoutErrorMsg(FACE_ERROR_LOCKOUT))
+        assertTrue(underTest.isFaceLockoutErrorMsg(FACE_ERROR_LOCKOUT_PERMANENT))
+        assertFalse(underTest.isFaceLockoutErrorMsg(FACE_ERROR_TIMEOUT))
+        assertFalse(underTest.isFaceLockoutErrorMsg(FACE_ERROR_CANCELED))
+        assertFalse(underTest.isFaceLockoutErrorMsg(FACE_ERROR_UNABLE_TO_PROCESS))
+        assertFalse(underTest.isFaceLockoutErrorMsg(FACE_ERROR_VENDOR))
+    }
+
+    private fun givenPrimaryAuthNotRequired() {
+        whenever(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean()))
+            .thenReturn(true)
+    }
+
+    private fun givenPrimaryAuthRequired() {
+        whenever(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean()))
+            .thenReturn(false)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/LogBufferTest.kt b/packages/SystemUI/tests/src/com/android/systemui/log/LogBufferTest.kt
index 0cf6d3d..b5eae5b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/log/LogBufferTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/log/LogBufferTest.kt
@@ -2,6 +2,7 @@
 
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.log.core.Logger
 import com.google.common.truth.Truth.assertThat
 import java.io.PrintWriter
 import java.io.StringWriter
@@ -32,7 +33,8 @@
 
     @Test
     fun log_shouldSaveLogToBuffer() {
-        buffer.log("Test", LogLevel.INFO, "Some test message")
+        val logger = Logger(buffer, "Test")
+        logger.i("Some test message")
 
         val dumpedString = dumpBuffer()
 
@@ -41,8 +43,9 @@
 
     @Test
     fun log_shouldRotateIfLogBufferIsFull() {
-        buffer.log("Test", LogLevel.INFO, "This should be rotated")
-        buffer.log("Test", LogLevel.INFO, "New test message")
+        val logger = Logger(buffer, "Test")
+        logger.i("This should be rotated")
+        logger.i("New test message")
 
         val dumpedString = dumpBuffer()
 
@@ -53,7 +56,8 @@
     fun dump_writesExceptionAndStacktrace() {
         buffer = createBuffer()
         val exception = createTestException("Exception message", "TestClass")
-        buffer.log("Tag", LogLevel.ERROR, { str1 = "Extra message" }, { str1!! }, exception)
+        val logger = Logger(buffer, "Test")
+        logger.e("Extra message", exception)
 
         val dumpedString = dumpBuffer()
 
@@ -72,7 +76,8 @@
                 "TestClass",
                 cause = createTestException("The real cause!", "TestClass")
             )
-        buffer.log("Tag", LogLevel.ERROR, { str1 = "Extra message" }, { str1!! }, exception)
+        val logger = Logger(buffer, "Test")
+        logger.e("Extra message", exception)
 
         val dumpedString = dumpBuffer()
 
@@ -93,7 +98,8 @@
             )
         )
         exception.addSuppressed(createTestException("Second suppressed exception", "SecondClass"))
-        buffer.log("Tag", LogLevel.ERROR, { str1 = "Extra message" }, { str1!! }, exception)
+        val logger = Logger(buffer, "Test")
+        logger.e("Extra message", exception)
 
         val dumpedStr = dumpBuffer()
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/core/FakeLogBuffer.kt b/packages/SystemUI/tests/src/com/android/systemui/log/core/FakeLogBuffer.kt
new file mode 100644
index 0000000..272d686
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/log/core/FakeLogBuffer.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.log.core
+
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogMessageImpl
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.nullable
+import com.android.systemui.util.mockito.whenever
+import org.mockito.Mockito.anyString
+
+/**
+ * A fake [LogBuffer] used for testing that obtains a real [LogMessage] to prevent a
+ * [NullPointerException].
+ */
+class FakeLogBuffer private constructor() {
+    class Factory private constructor() {
+        companion object {
+            fun create(): LogBuffer {
+                val logBuffer = mock<LogBuffer>()
+                whenever(
+                        logBuffer.obtain(
+                            tag = anyString(),
+                            level = any(),
+                            messagePrinter = any(),
+                            exception = nullable(),
+                        )
+                    )
+                    .thenReturn(LogMessageImpl.Factory.create())
+                return logBuffer
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/core/LoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/log/core/LoggerTest.kt
new file mode 100644
index 0000000..ab19b3a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/log/core/LoggerTest.kt
@@ -0,0 +1,140 @@
+package com.android.systemui.log.core
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.log.LogMessageImpl
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.nullable
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.anyString
+import org.mockito.Mockito.isNull
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+import org.mockito.junit.MockitoJUnitRunner
+
+@SmallTest
+@RunWith(MockitoJUnitRunner::class)
+class LoggerTest : SysuiTestCase() {
+    @Mock private lateinit var buffer: MessageBuffer
+    private lateinit var message: LogMessage
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        whenever(buffer.obtain(any(), any(), any(), isNull())).thenAnswer {
+            message = LogMessageImpl.Factory.create()
+            return@thenAnswer message
+        }
+    }
+
+    @Test
+    fun log_shouldCommitLogMessage() {
+        val logger = Logger(buffer, "LoggerTest")
+        logger.log(LogLevel.DEBUG, { "count=$int1" }) {
+            int1 = 1
+            str1 = "test"
+            bool1 = true
+        }
+
+        assertThat(message.int1).isEqualTo(1)
+        assertThat(message.str1).isEqualTo("test")
+        assertThat(message.bool1).isEqualTo(true)
+    }
+
+    @Test
+    fun log_shouldUseCorrectLoggerTag() {
+        val logger = Logger(buffer, "LoggerTest")
+        logger.log(LogLevel.DEBUG, { "count=$int1" }) { int1 = 1 }
+        verify(buffer).obtain(eq("LoggerTest"), any(), any(), nullable())
+    }
+
+    @Test
+    fun v_withMessageInitializer_shouldLogAtCorrectLevel() {
+        val logger = Logger(buffer, "LoggerTest")
+        logger.v({ "count=$int1" }) { int1 = 1 }
+        verify(buffer).obtain(anyString(), eq(LogLevel.VERBOSE), any(), nullable())
+    }
+
+    @Test
+    fun v_withCompileTimeMessage_shouldLogAtCorrectLevel() {
+        val logger = Logger(buffer, "LoggerTest")
+        logger.v("Message")
+        verify(buffer).obtain(anyString(), eq(LogLevel.VERBOSE), any(), nullable())
+    }
+
+    @Test
+    fun d_withMessageInitializer_shouldLogAtCorrectLevel() {
+        val logger = Logger(buffer, "LoggerTest")
+        logger.d({ "count=$int1" }) { int1 = 1 }
+        verify(buffer).obtain(anyString(), eq(LogLevel.DEBUG), any(), nullable())
+    }
+
+    @Test
+    fun d_withCompileTimeMessage_shouldLogAtCorrectLevel() {
+        val logger = Logger(buffer, "LoggerTest")
+        logger.d("Message")
+        verify(buffer).obtain(anyString(), eq(LogLevel.DEBUG), any(), nullable())
+    }
+
+    @Test
+    fun i_withMessageInitializer_shouldLogAtCorrectLevel() {
+        val logger = Logger(buffer, "LoggerTest")
+        logger.i({ "count=$int1" }) { int1 = 1 }
+        verify(buffer).obtain(anyString(), eq(LogLevel.INFO), any(), nullable())
+    }
+
+    @Test
+    fun i_withCompileTimeMessage_shouldLogAtCorrectLevel() {
+        val logger = Logger(buffer, "LoggerTest")
+        logger.i("Message")
+        verify(buffer).obtain(anyString(), eq(LogLevel.INFO), any(), nullable())
+    }
+
+    @Test
+    fun w_withMessageInitializer_shouldLogAtCorrectLevel() {
+        val logger = Logger(buffer, "LoggerTest")
+        logger.w({ "count=$int1" }) { int1 = 1 }
+        verify(buffer).obtain(anyString(), eq(LogLevel.WARNING), any(), nullable())
+    }
+
+    @Test
+    fun w_withCompileTimeMessage_shouldLogAtCorrectLevel() {
+        val logger = Logger(buffer, "LoggerTest")
+        logger.w("Message")
+        verify(buffer).obtain(anyString(), eq(LogLevel.WARNING), any(), nullable())
+    }
+
+    @Test
+    fun e_withMessageInitializer_shouldLogAtCorrectLevel() {
+        val logger = Logger(buffer, "LoggerTest")
+        logger.e({ "count=$int1" }) { int1 = 1 }
+        verify(buffer).obtain(anyString(), eq(LogLevel.ERROR), any(), nullable())
+    }
+
+    @Test
+    fun e_withCompileTimeMessage_shouldLogAtCorrectLevel() {
+        val logger = Logger(buffer, "LoggerTest")
+        logger.e("Message")
+        verify(buffer).obtain(anyString(), eq(LogLevel.ERROR), any(), nullable())
+    }
+
+    @Test
+    fun wtf_withMessageInitializer_shouldLogAtCorrectLevel() {
+        val logger = Logger(buffer, "LoggerTest")
+        logger.wtf({ "count=$int1" }) { int1 = 1 }
+        verify(buffer).obtain(anyString(), eq(LogLevel.WTF), any(), nullable())
+    }
+
+    @Test
+    fun wtf_withCompileTimeMessage_shouldLogAtCorrectLevel() {
+        val logger = Logger(buffer, "LoggerTest")
+        logger.wtf("Message")
+        verify(buffer).obtain(anyString(), eq(LogLevel.WTF), any(), nullable())
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferTest.kt b/packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferTest.kt
index 12f4689..83182c5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferTest.kt
@@ -18,8 +18,8 @@
 
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.log.LogLevel
 import com.android.systemui.log.LogcatEchoTracker
+import com.android.systemui.log.core.LogLevel
 import com.android.systemui.log.table.TableChange.Companion.IS_INITIAL_PREFIX
 import com.android.systemui.log.table.TableChange.Companion.MAX_STRING_LENGTH
 import com.android.systemui.util.mockito.any
diff --git a/packages/SystemUI/tests/src/com/android/systemui/logcat/LogAccessDialogActivityTest.java b/packages/SystemUI/tests/src/com/android/systemui/logcat/LogAccessDialogActivityTest.java
new file mode 100644
index 0000000..1d7d5df
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/logcat/LogAccessDialogActivityTest.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.logcat;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.view.View;
+
+import androidx.test.core.app.ActivityScenario;
+import androidx.test.ext.junit.rules.ActivityScenarioRule;
+import androidx.test.filters.LargeTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.internal.app.ILogAccessDialogCallback;
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@LargeTest
+public class LogAccessDialogActivityTest extends SysuiTestCase {
+
+    public static final String EXTRA_CALLBACK = "EXTRA_CALLBACK";
+    private final DialogCallbackTestable mDialogCallback = new DialogCallbackTestable();
+    @Rule
+    public ActivityScenarioRule<DialogTestable> mActivityScenarioRule =
+            new ActivityScenarioRule<>(getIntent());
+
+    static final class DialogCallbackTestable extends ILogAccessDialogCallback.Stub {
+
+        int mNumOfApprove = 0;
+        int mNumOfDecline = 0;
+        CountDownLatch mCountDownLatch = new CountDownLatch(1);
+
+        @Override
+        public void approveAccessForClient(int i, String s) {
+            mNumOfApprove++;
+            mCountDownLatch.countDown();
+        }
+
+        @Override
+        public void declineAccessForClient(int i, String s) {
+            mNumOfDecline++;
+            mCountDownLatch.countDown();
+        }
+    }
+
+    @Before
+    public void setUp() {
+        mDialogCallback.mNumOfDecline = 0;
+        mDialogCallback.mNumOfApprove = 0;
+        mDialogCallback.mCountDownLatch = new CountDownLatch(1);
+    }
+
+    private Intent getIntent() {
+        Context context = InstrumentationRegistry.getInstrumentation().getContext();
+        final Intent intent = new Intent(context, DialogTestable.class);
+
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+        intent.putExtra(Intent.EXTRA_PACKAGE_NAME, "packageName");
+        intent.putExtra(Intent.EXTRA_UID, 1);
+
+        intent.putExtra(EXTRA_CALLBACK, mDialogCallback.asBinder());
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+        return intent;
+    }
+
+    @Test
+    public void test_dialogDisappear_withoutClick_autoDeclined() throws InterruptedException {
+        ActivityScenario<DialogTestable> activityScenario = mActivityScenarioRule.getScenario();
+        activityScenario.onActivity(Activity::finish);
+
+        assertTrue(mDialogCallback.mCountDownLatch.await(2, TimeUnit.SECONDS));
+        assertEquals(mDialogCallback.mNumOfDecline, 1);
+        assertEquals(mDialogCallback.mNumOfApprove, 0);
+
+    }
+
+    @Test
+    public void test_clickAllow() throws InterruptedException {
+        ActivityScenario<DialogTestable> activityScenario = mActivityScenarioRule.getScenario();
+
+        activityScenario.onActivity(activity -> {
+            View allowButton =
+                    activity.mAlertView.findViewById(R.id.log_access_dialog_allow_button);
+            assertNotNull(allowButton);
+            allowButton.performClick();
+        });
+
+        assertTrue(mDialogCallback.mCountDownLatch.await(10, TimeUnit.SECONDS));
+        assertEquals(mDialogCallback.mNumOfDecline, 0);
+        assertEquals(mDialogCallback.mNumOfApprove, 1);
+    }
+
+    @Test
+    public void test_clickDeny() throws InterruptedException {
+        ActivityScenario<DialogTestable> activityScenario = mActivityScenarioRule.getScenario();
+
+        activityScenario.onActivity(activity -> {
+            View denyButton =
+                    activity.mAlertView.findViewById(R.id.log_access_dialog_deny_button);
+            assertNotNull(denyButton);
+            denyButton.performClick();
+        });
+
+        assertTrue(mDialogCallback.mCountDownLatch.await(10, TimeUnit.SECONDS));
+        assertEquals(mDialogCallback.mNumOfDecline, 1);
+        assertEquals(mDialogCallback.mNumOfApprove, 0);
+    }
+
+    public static class DialogTestable extends LogAccessDialogActivity {
+
+        @Override
+        protected String getTitleString(Context context, String callingPackage, int uid) {
+            return "DialogTitle";
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/OWNERS b/packages/SystemUI/tests/src/com/android/systemui/media/OWNERS
new file mode 100644
index 0000000..142862d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/OWNERS
@@ -0,0 +1,2 @@
+# Haptics team also works on Ringtones (RingtonePlayer/NotificationPlayer)
+file:/services/core/java/com/android/server/vibrator/OWNERS
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
index 56698e0..d1299d4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
@@ -261,7 +261,6 @@
         whenever(mediaSmartspaceTarget.creationTimeMillis).thenReturn(SMARTSPACE_CREATION_TIME)
         whenever(mediaSmartspaceTarget.expiryTimeMillis).thenReturn(SMARTSPACE_EXPIRY_TIME)
         whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(false)
-        whenever(mediaFlags.isExplicitIndicatorEnabled()).thenReturn(true)
         whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(false)
         whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(false)
         whenever(mediaFlags.isRemoteResumeAllowed()).thenReturn(false)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
index f902be3..9a90a5c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
@@ -216,9 +216,6 @@
     @Mock private lateinit var recCardTitle: TextView
     @Mock private lateinit var coverItem: ImageView
     @Mock private lateinit var matrix: Matrix
-    private lateinit var coverItem1: ImageView
-    private lateinit var coverItem2: ImageView
-    private lateinit var coverItem3: ImageView
     private lateinit var recTitle1: TextView
     private lateinit var recTitle2: TextView
     private lateinit var recTitle3: TextView
@@ -233,8 +230,6 @@
         FakeFeatureFlags().apply {
             this.set(Flags.UMO_SURFACE_RIPPLE, false)
             this.set(Flags.UMO_TURBULENCE_NOISE, false)
-            this.set(Flags.MEDIA_EXPLICIT_INDICATOR, true)
-            this.set(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE, false)
         }
     @Mock private lateinit var globalSettings: GlobalSettings
 
@@ -468,21 +463,25 @@
         recSubtitle3 = TextView(context)
 
         whenever(recommendationViewHolder.recommendations).thenReturn(view)
-        whenever(recommendationViewHolder.cardIcon).thenReturn(appIcon)
-
-        // Add a recommendation item
-        coverItem1 = ImageView(context).also { it.setId(R.id.media_cover1) }
-        coverItem2 = ImageView(context).also { it.setId(R.id.media_cover2) }
-        coverItem3 = ImageView(context).also { it.setId(R.id.media_cover3) }
-
+        whenever(recommendationViewHolder.mediaAppIcons)
+            .thenReturn(listOf(recAppIconItem, recAppIconItem, recAppIconItem))
+        whenever(recommendationViewHolder.cardTitle).thenReturn(recCardTitle)
         whenever(recommendationViewHolder.mediaCoverItems)
-            .thenReturn(listOf(coverItem1, coverItem2, coverItem3))
+            .thenReturn(listOf(coverItem, coverItem, coverItem))
         whenever(recommendationViewHolder.mediaCoverContainers)
             .thenReturn(listOf(coverContainer1, coverContainer2, coverContainer3))
         whenever(recommendationViewHolder.mediaTitles)
             .thenReturn(listOf(recTitle1, recTitle2, recTitle3))
         whenever(recommendationViewHolder.mediaSubtitles)
             .thenReturn(listOf(recSubtitle1, recSubtitle2, recSubtitle3))
+        whenever(recommendationViewHolder.mediaProgressBars)
+            .thenReturn(listOf(recProgressBar1, recProgressBar2, recProgressBar3))
+        whenever(coverItem.imageMatrix).thenReturn(matrix)
+
+        // set ids for recommendation containers
+        whenever(coverContainer1.id).thenReturn(1)
+        whenever(coverContainer2.id).thenReturn(2)
+        whenever(coverContainer3.id).thenReturn(3)
 
         whenever(recommendationViewHolder.gutsViewHolder).thenReturn(gutsViewHolder)
 
@@ -1562,7 +1561,8 @@
         verify(viewHolder.player).contentDescription = descriptionCaptor.capture()
         val description = descriptionCaptor.value.toString()
 
-        assertThat(description).contains(REC_APP_NAME)
+        assertThat(description)
+            .isEqualTo(context.getString(R.string.controls_media_smartspace_rec_header))
     }
 
     @Test
@@ -1586,7 +1586,8 @@
         verify(viewHolder.player).contentDescription = descriptionCaptor.capture()
         val description = descriptionCaptor.value.toString()
 
-        assertThat(description).contains(REC_APP_NAME)
+        assertThat(description)
+            .isEqualTo(context.getString(R.string.controls_media_smartspace_rec_header))
     }
 
     @Test
@@ -1793,7 +1794,7 @@
         // THEN it sends the PendingIntent without dismissing keyguard first,
         // and does not use the Intent directly (see b/271845008)
         captor.value.onClick(viewHolder.player)
-        verify(pendingIntent).send()
+        verify(pendingIntent).send(any(Bundle::class.java))
         verify(pendingIntent, never()).getIntent()
         verify(activityStarter, never()).postStartActivityDismissingKeyguard(eq(clickIntent), any())
     }
@@ -2152,7 +2153,6 @@
 
     @Test
     fun bindRecommendation_setAfterExecutors() {
-        setupUpdatedRecommendationViewHolder()
         val albumArt = getColorIcon(Color.RED)
         val data =
             smartspaceData.copy(
@@ -2190,7 +2190,6 @@
     @Test
     fun bindRecommendationWithProgressBars() {
         useRealConstraintSets()
-        setupUpdatedRecommendationViewHolder()
         val albumArt = getColorIcon(Color.RED)
         val bundle =
             Bundle().apply {
@@ -2237,7 +2236,6 @@
     @Test
     fun bindRecommendation_carouselNotFitThreeRecs_OrientationPortrait() {
         useRealConstraintSets()
-        setupUpdatedRecommendationViewHolder()
         val albumArt = getColorIcon(Color.RED)
         val data =
             smartspaceData.copy(
@@ -2291,7 +2289,6 @@
     @Test
     fun bindRecommendation_carouselNotFitThreeRecs_OrientationLandscape() {
         useRealConstraintSets()
-        setupUpdatedRecommendationViewHolder()
         val albumArt = getColorIcon(Color.RED)
         val data =
             smartspaceData.copy(
@@ -2506,27 +2503,6 @@
         verify(activityStarter).postStartActivityDismissingKeyguard(eq(pendingIntent))
     }
 
-    private fun setupUpdatedRecommendationViewHolder() {
-        fakeFeatureFlag.set(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE, true)
-        whenever(recommendationViewHolder.mediaAppIcons)
-            .thenReturn(listOf(recAppIconItem, recAppIconItem, recAppIconItem))
-        whenever(recommendationViewHolder.cardTitle).thenReturn(recCardTitle)
-        whenever(recommendationViewHolder.mediaCoverContainers)
-            .thenReturn(listOf(coverContainer1, coverContainer2, coverContainer3))
-        whenever(recommendationViewHolder.mediaCoverItems)
-            .thenReturn(listOf(coverItem, coverItem, coverItem))
-        whenever(recommendationViewHolder.mediaProgressBars)
-            .thenReturn(listOf(recProgressBar1, recProgressBar2, recProgressBar3))
-        whenever(recommendationViewHolder.mediaSubtitles)
-            .thenReturn(listOf(recSubtitle1, recSubtitle2, recSubtitle3))
-        whenever(coverItem.imageMatrix).thenReturn(matrix)
-
-        // set ids for recommendation containers
-        whenever(coverContainer1.id).thenReturn(1)
-        whenever(coverContainer2.id).thenReturn(2)
-        whenever(coverContainer3.id).thenReturn(3)
-    }
-
     private fun getColorIcon(color: Int): Icon {
         val bmp = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888)
         val canvas = Canvas(bmp)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt
index c9956f3..ba97df9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt
@@ -201,8 +201,8 @@
         whenever(mockCopiedState.widgetStates)
             .thenReturn(
                 mutableMapOf(
-                    R.id.media_title1 to mediaTitleWidgetState,
-                    R.id.media_subtitle1 to mediaSubTitleWidgetState,
+                    R.id.media_title to mediaTitleWidgetState,
+                    R.id.media_subtitle to mediaSubTitleWidgetState,
                     R.id.media_cover1_container to mediaContainerWidgetState
                 )
             )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherCoreStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherCoreStartableTest.kt
new file mode 100644
index 0000000..bcbf666
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherCoreStartableTest.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.systemui.mediaprojection.taskswitcher
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.mediaprojection.taskswitcher.ui.TaskSwitcherNotificationCoordinator
+import com.android.systemui.util.mockito.whenever
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyZeroInteractions
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class MediaProjectionTaskSwitcherCoreStartableTest : SysuiTestCase() {
+
+    @Mock private lateinit var flags: FeatureFlags
+    @Mock private lateinit var coordinator: TaskSwitcherNotificationCoordinator
+
+    private lateinit var coreStartable: MediaProjectionTaskSwitcherCoreStartable
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        coreStartable = MediaProjectionTaskSwitcherCoreStartable(coordinator, flags)
+    }
+
+    @Test
+    fun start_flagEnabled_startsCoordinator() {
+        whenever(flags.isEnabled(Flags.PARTIAL_SCREEN_SHARING_TASK_SWITCHER)).thenReturn(true)
+
+        coreStartable.start()
+
+        verify(coordinator).start()
+    }
+
+    @Test
+    fun start_flagDisabled_doesNotStartCoordinator() {
+        whenever(flags.isEnabled(Flags.PARTIAL_SCREEN_SHARING_TASK_SWITCHER)).thenReturn(false)
+
+        coreStartable.start()
+
+        verifyZeroInteractions(coordinator)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepositoryTest.kt
new file mode 100644
index 0000000..83932b0
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepositoryTest.kt
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.mediaprojection.taskswitcher.data.repository
+
+import android.os.Binder
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager.Companion.createTask
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager.Companion.createToken
+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.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class ActivityTaskManagerTasksRepositoryTest : SysuiTestCase() {
+
+    private val fakeActivityTaskManager = FakeActivityTaskManager()
+
+    private val dispatcher = UnconfinedTestDispatcher()
+    private val testScope = TestScope(dispatcher)
+
+    private val repo =
+        ActivityTaskManagerTasksRepository(
+            activityTaskManager = fakeActivityTaskManager.activityTaskManager,
+            applicationScope = testScope.backgroundScope,
+            backgroundDispatcher = dispatcher
+        )
+
+    @Test
+    fun findRunningTaskFromWindowContainerToken_noMatch_returnsNull() {
+        fakeActivityTaskManager.addRunningTasks(createTask(taskId = 1), createTask(taskId = 2))
+
+        testScope.runTest {
+            val matchingTask =
+                repo.findRunningTaskFromWindowContainerToken(windowContainerToken = Binder())
+
+            assertThat(matchingTask).isNull()
+        }
+    }
+
+    @Test
+    fun findRunningTaskFromWindowContainerToken_matchingToken_returnsTaskInfo() {
+        val expectedToken = createToken()
+        val expectedTask = createTask(taskId = 1, token = expectedToken)
+
+        fakeActivityTaskManager.addRunningTasks(
+            createTask(taskId = 2),
+            expectedTask,
+        )
+
+        testScope.runTest {
+            val actualTask =
+                repo.findRunningTaskFromWindowContainerToken(
+                    windowContainerToken = expectedToken.asBinder()
+                )
+
+            assertThat(actualTask).isEqualTo(expectedTask)
+        }
+    }
+
+    @Test
+    fun foregroundTask_returnsStreamOfTasksMovedToFront() =
+        testScope.runTest {
+            val foregroundTask by collectLastValue(repo.foregroundTask)
+
+            fakeActivityTaskManager.moveTaskToForeground(createTask(taskId = 1))
+            assertThat(foregroundTask?.taskId).isEqualTo(1)
+
+            fakeActivityTaskManager.moveTaskToForeground(createTask(taskId = 2))
+            assertThat(foregroundTask?.taskId).isEqualTo(2)
+
+            fakeActivityTaskManager.moveTaskToForeground(createTask(taskId = 3))
+            assertThat(foregroundTask?.taskId).isEqualTo(3)
+        }
+
+    @Test
+    fun foregroundTask_lastValueIsCached() =
+        testScope.runTest {
+            val foregroundTaskA by collectLastValue(repo.foregroundTask)
+            fakeActivityTaskManager.moveTaskToForeground(createTask(taskId = 1))
+            assertThat(foregroundTaskA?.taskId).isEqualTo(1)
+
+            val foregroundTaskB by collectLastValue(repo.foregroundTask)
+            assertThat(foregroundTaskB?.taskId).isEqualTo(1)
+        }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeActivityTaskManager.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeActivityTaskManager.kt
new file mode 100644
index 0000000..1c4870b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeActivityTaskManager.kt
@@ -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.systemui.mediaprojection.taskswitcher.data.repository
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.app.ActivityTaskManager
+import android.app.TaskStackListener
+import android.content.Intent
+import android.window.IWindowContainerToken
+import android.window.WindowContainerToken
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+
+class FakeActivityTaskManager {
+
+    private val runningTasks = mutableListOf<RunningTaskInfo>()
+    private val taskTaskListeners = mutableListOf<TaskStackListener>()
+
+    val activityTaskManager = mock<ActivityTaskManager>()
+
+    init {
+        whenever(activityTaskManager.registerTaskStackListener(any())).thenAnswer {
+            taskTaskListeners += it.arguments[0] as TaskStackListener
+            return@thenAnswer Unit
+        }
+        whenever(activityTaskManager.unregisterTaskStackListener(any())).thenAnswer {
+            taskTaskListeners -= it.arguments[0] as TaskStackListener
+            return@thenAnswer Unit
+        }
+        whenever(activityTaskManager.getTasks(any())).thenAnswer {
+            val maxNumTasks = it.arguments[0] as Int
+            return@thenAnswer runningTasks.take(maxNumTasks)
+        }
+    }
+
+    fun moveTaskToForeground(task: RunningTaskInfo) {
+        taskTaskListeners.forEach { it.onTaskMovedToFront(task) }
+    }
+
+    fun addRunningTasks(vararg tasks: RunningTaskInfo) {
+        runningTasks += tasks
+    }
+
+    companion object {
+
+        fun createTask(
+            taskId: Int,
+            token: WindowContainerToken = createToken(),
+            baseIntent: Intent = Intent()
+        ) =
+            RunningTaskInfo().apply {
+                this.taskId = taskId
+                this.token = token
+                this.baseIntent = baseIntent
+            }
+
+        fun createToken(): WindowContainerToken {
+            val realToken = object : IWindowContainerToken.Stub() {}
+            return WindowContainerToken(realToken)
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeMediaProjectionManager.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeMediaProjectionManager.kt
new file mode 100644
index 0000000..45f0a8c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeMediaProjectionManager.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.mediaprojection.taskswitcher.data.repository
+
+import android.media.projection.MediaProjectionInfo
+import android.media.projection.MediaProjectionManager
+import android.os.Binder
+import android.os.IBinder
+import android.os.UserHandle
+import android.view.ContentRecordingSession
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+
+class FakeMediaProjectionManager {
+
+    val mediaProjectionManager = mock<MediaProjectionManager>()
+
+    private val callbacks = mutableListOf<MediaProjectionManager.Callback>()
+
+    init {
+        whenever(mediaProjectionManager.addCallback(any(), any())).thenAnswer {
+            callbacks += it.arguments[0] as MediaProjectionManager.Callback
+            return@thenAnswer Unit
+        }
+        whenever(mediaProjectionManager.removeCallback(any())).thenAnswer {
+            callbacks -= it.arguments[0] as MediaProjectionManager.Callback
+            return@thenAnswer Unit
+        }
+    }
+
+    fun dispatchOnStart(info: MediaProjectionInfo = DEFAULT_INFO) {
+        callbacks.forEach { it.onStart(info) }
+    }
+
+    fun dispatchOnStop(info: MediaProjectionInfo = DEFAULT_INFO) {
+        callbacks.forEach { it.onStop(info) }
+    }
+
+    fun dispatchOnSessionSet(
+        info: MediaProjectionInfo = DEFAULT_INFO,
+        session: ContentRecordingSession?
+    ) {
+        callbacks.forEach { it.onRecordingSessionSet(info, session) }
+    }
+
+    companion object {
+        fun createDisplaySession(): ContentRecordingSession =
+            ContentRecordingSession.createDisplaySession(/* displayToMirror = */ 123)
+        fun createSingleTaskSession(token: IBinder = Binder()): ContentRecordingSession =
+            ContentRecordingSession.createTaskSession(token)
+
+        private const val DEFAULT_PACKAGE_NAME = "com.media.projection.test"
+        private val DEFAULT_USER_HANDLE = UserHandle.getUserHandleForUid(UserHandle.myUserId())
+        private val DEFAULT_INFO = MediaProjectionInfo(DEFAULT_PACKAGE_NAME, DEFAULT_USER_HANDLE)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepositoryTest.kt
new file mode 100644
index 0000000..3a74c72
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepositoryTest.kt
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.mediaprojection.taskswitcher.data.repository
+
+import android.os.Binder
+import android.os.Handler
+import android.testing.AndroidTestingRunner
+import android.view.ContentRecordingSession
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.mediaprojection.taskswitcher.data.model.MediaProjectionState
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager.Companion.createTask
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager.Companion.createToken
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class MediaProjectionManagerRepositoryTest : SysuiTestCase() {
+
+    private val dispatcher = StandardTestDispatcher()
+    private val testScope = TestScope(dispatcher)
+
+    private val fakeMediaProjectionManager = FakeMediaProjectionManager()
+    private val fakeActivityTaskManager = FakeActivityTaskManager()
+
+    private val tasksRepo =
+        ActivityTaskManagerTasksRepository(
+            activityTaskManager = fakeActivityTaskManager.activityTaskManager,
+            applicationScope = testScope.backgroundScope,
+            backgroundDispatcher = dispatcher
+        )
+
+    private val repo =
+        MediaProjectionManagerRepository(
+            mediaProjectionManager = fakeMediaProjectionManager.mediaProjectionManager,
+            handler = Handler.getMain(),
+            applicationScope = testScope.backgroundScope,
+            tasksRepository = tasksRepo
+        )
+
+    @Test
+    fun mediaProjectionState_onStart_emitsNotProjecting() =
+        testScope.runTest {
+            val state by collectLastValue(repo.mediaProjectionState)
+            runCurrent()
+
+            fakeMediaProjectionManager.dispatchOnStart()
+
+            assertThat(state).isEqualTo(MediaProjectionState.NotProjecting)
+        }
+
+    @Test
+    fun mediaProjectionState_onStop_emitsNotProjecting() =
+        testScope.runTest {
+            val state by collectLastValue(repo.mediaProjectionState)
+            runCurrent()
+
+            fakeMediaProjectionManager.dispatchOnStop()
+
+            assertThat(state).isEqualTo(MediaProjectionState.NotProjecting)
+        }
+
+    @Test
+    fun mediaProjectionState_onSessionSet_sessionNull_emitsNotProjecting() =
+        testScope.runTest {
+            val state by collectLastValue(repo.mediaProjectionState)
+            runCurrent()
+
+            fakeMediaProjectionManager.dispatchOnSessionSet(session = null)
+
+            assertThat(state).isEqualTo(MediaProjectionState.NotProjecting)
+        }
+
+    @Test
+    fun mediaProjectionState_onSessionSet_contentToRecordDisplay_emitsEntireScreen() =
+        testScope.runTest {
+            val state by collectLastValue(repo.mediaProjectionState)
+            runCurrent()
+
+            fakeMediaProjectionManager.dispatchOnSessionSet(
+                session = ContentRecordingSession.createDisplaySession(/* displayToMirror= */ 123)
+            )
+
+            assertThat(state).isEqualTo(MediaProjectionState.EntireScreen)
+        }
+
+    @Test
+    fun mediaProjectionState_onSessionSet_tokenNull_emitsEntireScreen() =
+        testScope.runTest {
+            val state by collectLastValue(repo.mediaProjectionState)
+            runCurrent()
+
+            fakeMediaProjectionManager.dispatchOnSessionSet(
+                session =
+                    ContentRecordingSession.createTaskSession(/* taskWindowContainerToken= */ null)
+            )
+
+            assertThat(state).isEqualTo(MediaProjectionState.EntireScreen)
+        }
+
+    @Test
+    fun mediaProjectionState_sessionSet_taskWithToken_noMatchingRunningTask_emitsEntireScreen() =
+        testScope.runTest {
+            val state by collectLastValue(repo.mediaProjectionState)
+            runCurrent()
+
+            val taskWindowContainerToken = Binder()
+            fakeMediaProjectionManager.dispatchOnSessionSet(
+                session = ContentRecordingSession.createTaskSession(taskWindowContainerToken)
+            )
+
+            assertThat(state).isEqualTo(MediaProjectionState.EntireScreen)
+        }
+
+    @Test
+    fun mediaProjectionState_sessionSet_taskWithToken_matchingRunningTask_emitsSingleTask() =
+        testScope.runTest {
+            val token = createToken()
+            val task = createTask(taskId = 1, token = token)
+            fakeActivityTaskManager.addRunningTasks(task)
+            val state by collectLastValue(repo.mediaProjectionState)
+            runCurrent()
+
+            fakeMediaProjectionManager.dispatchOnSessionSet(
+                session = ContentRecordingSession.createTaskSession(token.asBinder())
+            )
+
+            assertThat(state).isEqualTo(MediaProjectionState.SingleTask(task))
+        }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractorTest.kt
new file mode 100644
index 0000000..b2ebe1bc
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractorTest.kt
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.mediaprojection.taskswitcher.domain.interactor
+
+import android.content.Intent
+import android.os.Handler
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.ActivityTaskManagerTasksRepository
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager.Companion.createTask
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeMediaProjectionManager
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeMediaProjectionManager.Companion.createSingleTaskSession
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.MediaProjectionManagerRepository
+import com.android.systemui.mediaprojection.taskswitcher.domain.model.TaskSwitchState
+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.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class TaskSwitchInteractorTest : SysuiTestCase() {
+
+    private val dispatcher = UnconfinedTestDispatcher()
+    private val testScope = TestScope(dispatcher)
+
+    private val fakeActivityTaskManager = FakeActivityTaskManager()
+    private val fakeMediaProjectionManager = FakeMediaProjectionManager()
+
+    private val tasksRepo =
+        ActivityTaskManagerTasksRepository(
+            activityTaskManager = fakeActivityTaskManager.activityTaskManager,
+            applicationScope = testScope.backgroundScope,
+            backgroundDispatcher = dispatcher
+        )
+
+    private val mediaRepo =
+        MediaProjectionManagerRepository(
+            mediaProjectionManager = fakeMediaProjectionManager.mediaProjectionManager,
+            handler = Handler.getMain(),
+            applicationScope = testScope.backgroundScope,
+            tasksRepository = tasksRepo,
+        )
+
+    private val interactor = TaskSwitchInteractor(mediaRepo, tasksRepo)
+
+    @Test
+    fun taskSwitchChanges_notProjecting_foregroundTaskChange_emitsNotProjectingTask() =
+        testScope.runTest {
+            val backgroundTask = createTask(taskId = 0)
+            val foregroundTask = createTask(taskId = 1)
+            val taskSwitchState by collectLastValue(interactor.taskSwitchChanges)
+
+            fakeActivityTaskManager.addRunningTasks(backgroundTask, foregroundTask)
+            fakeMediaProjectionManager.dispatchOnStop()
+            fakeActivityTaskManager.moveTaskToForeground(foregroundTask)
+
+            assertThat(taskSwitchState).isEqualTo(TaskSwitchState.NotProjectingTask)
+        }
+
+    @Test
+    fun taskSwitchChanges_projectingScreen_foregroundTaskChange_emitsNotProjectingTask() =
+        testScope.runTest {
+            val backgroundTask = createTask(taskId = 0)
+            val foregroundTask = createTask(taskId = 1)
+            val taskSwitchState by collectLastValue(interactor.taskSwitchChanges)
+
+            fakeActivityTaskManager.addRunningTasks(backgroundTask, foregroundTask)
+            fakeMediaProjectionManager.dispatchOnSessionSet(
+                session = FakeMediaProjectionManager.createDisplaySession()
+            )
+            fakeActivityTaskManager.moveTaskToForeground(foregroundTask)
+
+            assertThat(taskSwitchState).isEqualTo(TaskSwitchState.NotProjectingTask)
+        }
+
+    @Test
+    fun taskSwitchChanges_projectingTask_foregroundTaskDifferent_emitsTaskChanged() =
+        testScope.runTest {
+            val projectedTask = createTask(taskId = 0)
+            val foregroundTask = createTask(taskId = 1)
+            val taskSwitchState by collectLastValue(interactor.taskSwitchChanges)
+
+            fakeActivityTaskManager.addRunningTasks(projectedTask, foregroundTask)
+            fakeMediaProjectionManager.dispatchOnSessionSet(
+                session = createSingleTaskSession(token = projectedTask.token.asBinder())
+            )
+            fakeActivityTaskManager.moveTaskToForeground(foregroundTask)
+
+            assertThat(taskSwitchState)
+                .isEqualTo(
+                    TaskSwitchState.TaskSwitched(
+                        projectedTask = projectedTask,
+                        foregroundTask = foregroundTask
+                    )
+                )
+        }
+
+    @Test
+    fun taskSwitchChanges_projectingTask_foregroundTaskLauncher_emitsTaskUnchanged() =
+        testScope.runTest {
+            val projectedTask = createTask(taskId = 0)
+            val foregroundTask = createTask(taskId = 1, baseIntent = LAUNCHER_INTENT)
+            val taskSwitchState by collectLastValue(interactor.taskSwitchChanges)
+
+            fakeActivityTaskManager.addRunningTasks(projectedTask, foregroundTask)
+            fakeMediaProjectionManager.dispatchOnSessionSet(
+                session = createSingleTaskSession(projectedTask.token.asBinder())
+            )
+            fakeActivityTaskManager.moveTaskToForeground(foregroundTask)
+
+            assertThat(taskSwitchState).isEqualTo(TaskSwitchState.TaskUnchanged)
+        }
+
+    @Test
+    fun taskSwitchChanges_projectingTask_foregroundTaskSame_emitsTaskUnchanged() =
+        testScope.runTest {
+            val projectedTask = createTask(taskId = 0)
+            val taskSwitchState by collectLastValue(interactor.taskSwitchChanges)
+
+            fakeActivityTaskManager.addRunningTasks(projectedTask)
+            fakeMediaProjectionManager.dispatchOnSessionSet(
+                session = createSingleTaskSession(projectedTask.token.asBinder())
+            )
+            fakeActivityTaskManager.moveTaskToForeground(projectedTask)
+
+            assertThat(taskSwitchState).isEqualTo(TaskSwitchState.TaskUnchanged)
+        }
+
+    companion object {
+        private val LAUNCHER_INTENT: Intent =
+            Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinatorTest.kt
new file mode 100644
index 0000000..b396caf
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinatorTest.kt
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.mediaprojection.taskswitcher.ui
+
+import android.app.Notification
+import android.app.NotificationManager
+import android.os.Handler
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.ActivityTaskManagerTasksRepository
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeMediaProjectionManager
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.MediaProjectionManagerRepository
+import com.android.systemui.mediaprojection.taskswitcher.domain.interactor.TaskSwitchInteractor
+import com.android.systemui.mediaprojection.taskswitcher.ui.viewmodel.TaskSwitcherNotificationViewModel
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import junit.framework.Assert.assertEquals
+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
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Mockito.verify
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class TaskSwitcherNotificationCoordinatorTest : SysuiTestCase() {
+
+    private val notificationManager: NotificationManager = mock()
+
+    private val dispatcher = UnconfinedTestDispatcher()
+    private val testScope = TestScope(dispatcher)
+
+    private val fakeActivityTaskManager = FakeActivityTaskManager()
+    private val fakeMediaProjectionManager = FakeMediaProjectionManager()
+
+    private val tasksRepo =
+        ActivityTaskManagerTasksRepository(
+            activityTaskManager = fakeActivityTaskManager.activityTaskManager,
+            applicationScope = testScope.backgroundScope,
+            backgroundDispatcher = dispatcher
+        )
+
+    private val mediaRepo =
+        MediaProjectionManagerRepository(
+            mediaProjectionManager = fakeMediaProjectionManager.mediaProjectionManager,
+            handler = Handler.getMain(),
+            applicationScope = testScope.backgroundScope,
+            tasksRepository = tasksRepo,
+        )
+
+    private val interactor = TaskSwitchInteractor(mediaRepo, tasksRepo)
+    private val viewModel = TaskSwitcherNotificationViewModel(interactor)
+
+    private val coordinator =
+        TaskSwitcherNotificationCoordinator(
+            context,
+            notificationManager,
+            testScope.backgroundScope,
+            dispatcher,
+            viewModel
+        )
+
+    @Before
+    fun setup() {
+        coordinator.start()
+    }
+
+    @Test
+    fun showNotification() {
+        testScope.runTest {
+            switchTask()
+
+            val notification = ArgumentCaptor.forClass(Notification::class.java)
+            verify(notificationManager).notify(any(), any(), notification.capture())
+            assertNotification(notification)
+        }
+    }
+
+    @Test
+    fun hideNotification() {
+        testScope.runTest {
+            fakeMediaProjectionManager.dispatchOnStop()
+
+            verify(notificationManager).cancel(any())
+        }
+    }
+
+    @Test
+    fun notificationIdIsConsistent() {
+        testScope.runTest {
+            fakeMediaProjectionManager.dispatchOnStop()
+            val idCancel = argumentCaptor<Int>()
+            verify(notificationManager).cancel(idCancel.capture())
+
+            switchTask()
+            val idNotify = argumentCaptor<Int>()
+            verify(notificationManager).notify(any(), idNotify.capture(), any())
+
+            assertEquals(idCancel.value, idNotify.value)
+        }
+    }
+
+    private fun switchTask() {
+        val projectedTask = FakeActivityTaskManager.createTask(taskId = 1)
+        val foregroundTask = FakeActivityTaskManager.createTask(taskId = 2)
+        fakeActivityTaskManager.addRunningTasks(projectedTask, foregroundTask)
+        fakeMediaProjectionManager.dispatchOnSessionSet(
+            session =
+                FakeMediaProjectionManager.createSingleTaskSession(projectedTask.token.asBinder())
+        )
+        fakeActivityTaskManager.moveTaskToForeground(foregroundTask)
+    }
+
+    private fun assertNotification(notification: ArgumentCaptor<Notification>) {
+        val text = notification.value.extras.getCharSequence(Notification.EXTRA_TEXT)
+        assertEquals(context.getString(R.string.media_projection_task_switcher_text), text)
+
+        val actions = notification.value.actions
+        assertThat(actions).hasLength(2)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModelTest.kt
new file mode 100644
index 0000000..7d38de4
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModelTest.kt
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.mediaprojection.taskswitcher.ui.viewmodel
+
+import android.content.Intent
+import android.os.Handler
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.ActivityTaskManagerTasksRepository
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager.Companion.createTask
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeMediaProjectionManager
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeMediaProjectionManager.Companion.createDisplaySession
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeMediaProjectionManager.Companion.createSingleTaskSession
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.MediaProjectionManagerRepository
+import com.android.systemui.mediaprojection.taskswitcher.domain.interactor.TaskSwitchInteractor
+import com.android.systemui.mediaprojection.taskswitcher.ui.model.TaskSwitcherNotificationUiState
+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.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class TaskSwitcherNotificationViewModelTest : SysuiTestCase() {
+
+    private val dispatcher = UnconfinedTestDispatcher()
+    private val testScope = TestScope(dispatcher)
+
+    private val fakeActivityTaskManager = FakeActivityTaskManager()
+    private val fakeMediaProjectionManager = FakeMediaProjectionManager()
+
+    private val tasksRepo =
+        ActivityTaskManagerTasksRepository(
+            activityTaskManager = fakeActivityTaskManager.activityTaskManager,
+            applicationScope = testScope.backgroundScope,
+            backgroundDispatcher = dispatcher
+        )
+
+    private val mediaRepo =
+        MediaProjectionManagerRepository(
+            mediaProjectionManager = fakeMediaProjectionManager.mediaProjectionManager,
+            handler = Handler.getMain(),
+            applicationScope = testScope.backgroundScope,
+            tasksRepository = tasksRepo,
+        )
+
+    private val interactor = TaskSwitchInteractor(mediaRepo, tasksRepo)
+
+    private val viewModel = TaskSwitcherNotificationViewModel(interactor)
+
+    @Test
+    fun uiState_notProjecting_emitsNotShowing() =
+        testScope.runTest {
+            val uiState by collectLastValue(viewModel.uiState)
+
+            fakeMediaProjectionManager.dispatchOnStop()
+
+            assertThat(uiState).isEqualTo(TaskSwitcherNotificationUiState.NotShowing)
+        }
+
+    @Test
+    fun uiState_notProjecting_foregroundTaskChanged_emitsNotShowing() =
+        testScope.runTest {
+            val backgroundTask = createTask(taskId = 0)
+            val foregroundTask = createTask(taskId = 1)
+            val uiState by collectLastValue(viewModel.uiState)
+
+            fakeActivityTaskManager.addRunningTasks(backgroundTask, foregroundTask)
+            fakeMediaProjectionManager.dispatchOnStop()
+            fakeActivityTaskManager.moveTaskToForeground(foregroundTask)
+
+            assertThat(uiState).isEqualTo(TaskSwitcherNotificationUiState.NotShowing)
+        }
+
+    @Test
+    fun uiState_projectingEntireScreen_emitsNotShowing() =
+        testScope.runTest {
+            val uiState by collectLastValue(viewModel.uiState)
+
+            fakeMediaProjectionManager.dispatchOnSessionSet(session = createDisplaySession())
+
+            assertThat(uiState).isEqualTo(TaskSwitcherNotificationUiState.NotShowing)
+        }
+
+    @Test
+    fun uiState_projectingEntireScreen_foregroundTaskChanged_emitsNotShowing() =
+        testScope.runTest {
+            val backgroundTask = createTask(taskId = 0)
+            val foregroundTask = createTask(taskId = 1)
+            val uiState by collectLastValue(viewModel.uiState)
+
+            fakeActivityTaskManager.addRunningTasks(backgroundTask, foregroundTask)
+            fakeMediaProjectionManager.dispatchOnSessionSet(session = createDisplaySession())
+            fakeActivityTaskManager.moveTaskToForeground(foregroundTask)
+
+            assertThat(uiState).isEqualTo(TaskSwitcherNotificationUiState.NotShowing)
+        }
+
+    @Test
+    fun uiState_projectingTask_foregroundTaskChanged_different_emitsShowing() =
+        testScope.runTest {
+            val projectedTask = createTask(taskId = 1)
+            val foregroundTask = createTask(taskId = 2)
+            val uiState by collectLastValue(viewModel.uiState)
+
+            fakeActivityTaskManager.addRunningTasks(projectedTask, foregroundTask)
+            fakeMediaProjectionManager.dispatchOnSessionSet(
+                session = createSingleTaskSession(projectedTask.token.asBinder())
+            )
+            fakeActivityTaskManager.moveTaskToForeground(foregroundTask)
+
+            assertThat(uiState)
+                .isEqualTo(TaskSwitcherNotificationUiState.Showing(projectedTask, foregroundTask))
+        }
+
+    @Test
+    fun uiState_projectingTask_foregroundTaskChanged_same_emitsNotShowing() =
+        testScope.runTest {
+            val projectedTask = createTask(taskId = 1)
+            val uiState by collectLastValue(viewModel.uiState)
+
+            fakeActivityTaskManager.addRunningTasks(projectedTask)
+            fakeMediaProjectionManager.dispatchOnSessionSet(
+                session = createSingleTaskSession(projectedTask.token.asBinder())
+            )
+            fakeActivityTaskManager.moveTaskToForeground(projectedTask)
+
+            assertThat(uiState).isEqualTo(TaskSwitcherNotificationUiState.NotShowing)
+        }
+
+    @Test
+    fun uiState_projectingTask_foregroundTaskChanged_different_taskIsLauncher_emitsNotShowing() =
+        testScope.runTest {
+            val projectedTask = createTask(taskId = 1)
+            val foregroundTask = createTask(taskId = 2, baseIntent = LAUNCHER_INTENT)
+            val uiState by collectLastValue(viewModel.uiState)
+
+            fakeActivityTaskManager.addRunningTasks(projectedTask, foregroundTask)
+            fakeMediaProjectionManager.dispatchOnSessionSet(
+                session = createSingleTaskSession(projectedTask.token.asBinder())
+            )
+            fakeActivityTaskManager.moveTaskToForeground(foregroundTask)
+
+            assertThat(uiState).isEqualTo(TaskSwitcherNotificationUiState.NotShowing)
+        }
+
+    companion object {
+        private val LAUNCHER_INTENT: Intent =
+            Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractorTest.kt
index 9e54224..5890cbd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractorTest.kt
@@ -25,7 +25,7 @@
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -97,10 +97,11 @@
                 multiShadeInteractor = interactor,
                 featureFlags = featureFlags,
                 keyguardTransitionInteractor =
-                    KeyguardTransitionInteractor(
-                        repository = keyguardTransitionRepository,
-                        scope = testScope.backgroundScope
-                    ),
+                    KeyguardTransitionInteractorFactory.create(
+                            scope = TestScope().backgroundScope,
+                            repository = keyguardTransitionRepository,
+                        )
+                        .keyguardTransitionInteractor,
                 falsingManager = falsingManager,
                 shadeController = shadeController,
             )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java
index 9b8605d..8d306cce 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java
@@ -52,6 +52,7 @@
 import com.android.systemui.settings.DisplayTracker;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
@@ -112,6 +113,9 @@
     EdgeBackGestureHandler mEdgeBackGestureHandler;
     @Mock
     EdgeBackGestureHandler.Factory mEdgeBackGestureHandlerFactory;
+    @Mock
+    NotificationShadeWindowController mNotificationShadeWindowController;
+
     private AccessibilityManager.AccessibilityServicesStateChangeListener
             mAccessibilityServicesStateChangeListener;
 
@@ -136,7 +140,7 @@
                 mSystemActions, mOverviewProxyService, mAssistManagerLazy,
                 () -> Optional.of(mock(CentralSurfaces.class)), mock(KeyguardStateController.class),
                 mNavigationModeController, mEdgeBackGestureHandlerFactory, mWm, mUserTracker,
-                mDisplayTracker, mDumpManager, mCommandQueue);
+                mDisplayTracker, mNotificationShadeWindowController, mDumpManager, mCommandQueue);
 
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
index f062ec7..25d494c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
@@ -99,6 +99,7 @@
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.NotificationShadeDepthController;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.phone.AutoHideController;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
 import com.android.systemui.statusbar.phone.LightBarController;
@@ -209,6 +210,8 @@
     private EdgeBackGestureHandler.Factory mEdgeBackGestureHandlerFactory;
     @Mock
     private EdgeBackGestureHandler mEdgeBackGestureHandler;
+    @Mock
+    private NotificationShadeWindowController mNotificationShadeWindowController;
     private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
     private DeviceConfigProxyFake mDeviceConfigProxyFake = new DeviceConfigProxyFake();
     private TaskStackChangeListeners mTaskStackChangeListeners =
@@ -256,7 +259,8 @@
                     () -> mock(AssistManager.class), () -> Optional.of(mCentralSurfaces),
                     mKeyguardStateController, mock(NavigationModeController.class),
                     mEdgeBackGestureHandlerFactory, mock(IWindowManager.class),
-                    mock(UserTracker.class), mock(DisplayTracker.class), mock(DumpManager.class),
+                    mock(UserTracker.class), mock(DisplayTracker.class),
+                    mNotificationShadeWindowController, mock(DumpManager.class),
                     mock(CommandQueue.class)));
             mNavigationBar = createNavBar(mContext);
             mExternalDisplayNavigationBar = createNavBar(mSysuiTestableContextExternal);
@@ -339,7 +343,6 @@
         NotificationShadeWindowView mockShadeWindowView = mock(NotificationShadeWindowView.class);
         WindowInsets windowInsets = new WindowInsets.Builder().setVisible(ime(), false).build();
         doReturn(windowInsets).when(mockShadeWindowView).getRootWindowInsets();
-        doReturn(mockShadeWindowView).when(mCentralSurfaces).getNotificationShadeWindowView();
         doReturn(true).when(mockShadeWindowView).isAttachedToWindow();
         doNothing().when(defaultNavBar).checkNavBarModes();
         doNothing().when(externalNavBar).checkNavBarModes();
@@ -375,7 +378,7 @@
     @Test
     public void testSetImeWindowStatusWhenKeyguardLockingAndImeInsetsChange() {
         NotificationShadeWindowView mockShadeWindowView = mock(NotificationShadeWindowView.class);
-        doReturn(mockShadeWindowView).when(mCentralSurfaces).getNotificationShadeWindowView();
+        doReturn(mockShadeWindowView).when(mNotificationShadeWindowController).getWindowRootView();
         doReturn(true).when(mockShadeWindowView).isAttachedToWindow();
         doNothing().when(mNavigationBar).checkNavBarModes();
         mNavigationBar.init();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/FakeNoteTaskBubbleController.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/FakeNoteTaskBubbleController.kt
new file mode 100644
index 0000000..450aadd
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/FakeNoteTaskBubbleController.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.notetask
+
+import android.content.Context
+import android.content.Intent
+import android.graphics.drawable.Icon
+import android.os.UserHandle
+import com.android.wm.shell.bubbles.Bubbles
+import java.util.Optional
+import kotlinx.coroutines.CoroutineDispatcher
+
+/**
+ * Fake for [NoteTaskBubblesController] as mocking suspending functions is not supported in the
+ * Android tree's version of mockito. Ideally the [NoteTaskBubblesController] should be implemented
+ * using an interface for effectively providing multiple implementations but as this fake primarily
+ * for dealing with old version of mockito there isn't any benefit in adding complexity.
+ */
+class FakeNoteTaskBubbleController(
+    unUsed1: Context,
+    unsUsed2: CoroutineDispatcher,
+    private val optionalBubbles: Optional<Bubbles>
+) : NoteTaskBubblesController(unUsed1, unsUsed2) {
+    override suspend fun areBubblesAvailable() = optionalBubbles.isPresent
+
+    override suspend fun showOrHideAppBubble(intent: Intent, userHandle: UserHandle, icon: Icon) {
+        optionalBubbles.ifPresentOrElse(
+            { bubbles -> bubbles.showOrHideAppBubble(intent, userHandle, icon) },
+            { throw IllegalAccessException() }
+        )
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/LaunchNotesRoleSettingsTrampolineActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/LaunchNotesRoleSettingsTrampolineActivityTest.kt
index 36b913f..bdb095a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/LaunchNotesRoleSettingsTrampolineActivityTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/LaunchNotesRoleSettingsTrampolineActivityTest.kt
@@ -22,9 +22,9 @@
 import android.testing.TestableLooper
 import androidx.test.filters.SmallTest
 import androidx.test.rule.ActivityTestRule
-import androidx.test.runner.intercepting.SingleActivityFactory
 import com.android.dx.mockito.inline.extended.ExtendedMockito.verify
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.activity.SingleActivityFactory
 import com.android.systemui.notetask.LaunchNotesRoleSettingsTrampolineActivity.Companion.ACTION_MANAGE_NOTES_ROLE_FROM_QUICK_AFFORDANCE
 import com.android.systemui.notetask.NoteTaskEntryPoint.QUICK_AFFORDANCE
 import com.android.systemui.util.mockito.any
@@ -47,13 +47,9 @@
     @Rule
     @JvmField
     val activityRule =
-        ActivityTestRule<LaunchNotesRoleSettingsTrampolineActivity>(
-            /* activityFactory= */ object :
-                SingleActivityFactory<LaunchNotesRoleSettingsTrampolineActivity>(
-                    LaunchNotesRoleSettingsTrampolineActivity::class.java
-                ) {
-                override fun create(intent: Intent?) =
-                    LaunchNotesRoleSettingsTrampolineActivity(noteTaskController)
+        ActivityTestRule(
+            /* activityFactory= */ SingleActivityFactory {
+                LaunchNotesRoleSettingsTrampolineActivity(noteTaskController)
             },
             /* initialTouchMode= */ false,
             /* launchActivity= */ false,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskBubblesServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskBubblesServiceTest.kt
new file mode 100644
index 0000000..baac9e0
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskBubblesServiceTest.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.notetask
+
+import android.content.Intent
+import android.graphics.drawable.Icon
+import android.os.UserHandle
+import androidx.test.filters.SmallTest
+import androidx.test.runner.AndroidJUnit4
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.notetask.NoteTaskBubblesController.NoteTaskBubblesService
+import com.android.wm.shell.bubbles.Bubbles
+import com.google.common.truth.Truth.assertThat
+import java.util.Optional
+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
+
+/** atest SystemUITests:NoteTaskBubblesServiceTest */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+internal class NoteTaskBubblesServiceTest : SysuiTestCase() {
+
+    @Mock private lateinit var bubbles: Bubbles
+
+    private fun createServiceBinder(bubbles: Bubbles? = this.bubbles) =
+        NoteTaskBubblesService(Optional.ofNullable(bubbles)).onBind(Intent())
+            as INoteTaskBubblesService
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+    }
+
+    @Test
+    fun areBubblesAvailable_bubblesNotNull_shouldReturnTrue() {
+        assertThat(createServiceBinder().areBubblesAvailable()).isTrue()
+    }
+
+    @Test
+    fun areBubblesAvailable_bubblesNull_shouldReturnFalse() {
+        assertThat(createServiceBinder(bubbles = null).areBubblesAvailable()).isFalse()
+    }
+
+    @Test
+    fun showOrHideAppBubble() {
+        val intent = Intent()
+        val user = UserHandle.SYSTEM
+        val icon = Icon.createWithResource(context, R.drawable.ic_note_task_shortcut_widget)
+
+        createServiceBinder().showOrHideAppBubble(intent, user, icon)
+
+        verify(bubbles).showOrHideAppBubble(intent, user, icon)
+    }
+}
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 0954f6f..a76af8e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
@@ -36,12 +36,12 @@
 import android.content.pm.PackageManager
 import android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED
 import android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED
-import android.content.pm.ShortcutInfo
 import android.content.pm.ShortcutManager
 import android.content.pm.UserInfo
 import android.graphics.drawable.Icon
 import android.os.UserHandle
 import android.os.UserManager
+import android.provider.Settings
 import androidx.test.ext.truth.content.IntentSubject.assertThat
 import androidx.test.filters.SmallTest
 import androidx.test.runner.AndroidJUnit4
@@ -56,17 +56,22 @@
 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.notetask.shortcut.LaunchNoteTaskManagedProfileProxyActivity
 import com.android.systemui.settings.FakeUserTracker
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.capture
 import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.mockito.withArgCaptor
+import com.android.systemui.util.settings.SecureSettings
 import com.android.wm.shell.bubbles.Bubble
 import com.android.wm.shell.bubbles.Bubbles
 import com.google.common.truth.Truth.assertThat
 import java.util.Optional
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -81,6 +86,7 @@
 import org.mockito.MockitoAnnotations
 
 /** atest SystemUITests:NoteTaskControllerTest */
+@OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 internal class NoteTaskControllerTest : SysuiTestCase() {
@@ -98,7 +104,10 @@
     @Mock private lateinit var shortcutManager: ShortcutManager
     @Mock private lateinit var activityManager: ActivityManager
     @Mock private lateinit var devicePolicyManager: DevicePolicyManager
+    @Mock private lateinit var secureSettings: SecureSettings
     private val userTracker = FakeUserTracker()
+    private val testDispatcher = UnconfinedTestDispatcher()
+    private val testScope = TestScope(testDispatcher)
 
     @Before
     fun setUp() {
@@ -106,7 +115,11 @@
 
         whenever(context.getString(R.string.note_task_button_label))
             .thenReturn(NOTE_TASK_SHORT_LABEL)
+        whenever(context.getString(eq(R.string.note_task_shortcut_long_label), any()))
+            .thenReturn(NOTE_TASK_LONG_LABEL)
         whenever(context.packageManager).thenReturn(packageManager)
+        whenever(packageManager.getApplicationInfo(any(), any<Int>())).thenReturn(mock())
+        whenever(packageManager.getApplicationLabel(any())).thenReturn(NOTE_TASK_LONG_LABEL)
         whenever(resolver.resolveInfo(any(), any(), any())).thenReturn(NOTE_TASK_INFO)
         whenever(userManager.isUserUnlocked).thenReturn(true)
         whenever(userManager.isUserUnlocked(any<Int>())).thenReturn(true)
@@ -123,6 +136,7 @@
         whenever(activityManager.getRunningTasks(anyInt())).thenReturn(emptyList())
         whenever(userManager.isManagedProfile(workUserInfo.id)).thenReturn(true)
         whenever(context.resources).thenReturn(getContext().resources)
+        whenever(secureSettings.userTracker).thenReturn(userTracker)
     }
 
     private fun createNoteTaskController(
@@ -133,7 +147,6 @@
             context = context,
             resolver = resolver,
             eventLogger = eventLogger,
-            optionalBubbles = Optional.ofNullable(bubbles),
             userManager = userManager,
             keyguardManager = keyguardManager,
             isEnabled = isEnabled,
@@ -142,6 +155,10 @@
             roleManager = roleManager,
             shortcutManager = shortcutManager,
             activityManager = activityManager,
+            secureSettings = secureSettings,
+            noteTaskBubblesController =
+                FakeNoteTaskBubbleController(context, testDispatcher, Optional.ofNullable(bubbles)),
+            applicationScope = testScope,
         )
 
     // region onBubbleExpandChanged
@@ -157,7 +174,7 @@
             )
 
         verify(eventLogger).logNoteTaskOpened(expectedInfo)
-        verifyZeroInteractions(context, bubbles, keyguardManager, userManager)
+        verifyZeroInteractions(bubbles, keyguardManager, userManager)
     }
 
     @Test
@@ -172,7 +189,7 @@
             )
 
         verify(eventLogger).logNoteTaskClosed(expectedInfo)
-        verifyZeroInteractions(context, bubbles, keyguardManager, userManager)
+        verifyZeroInteractions(bubbles, keyguardManager, userManager)
     }
 
     @Test
@@ -186,7 +203,7 @@
                 key = Bubble.getAppBubbleKeyForApp(expectedInfo.packageName, expectedInfo.user),
             )
 
-        verifyZeroInteractions(context, bubbles, keyguardManager, userManager, eventLogger)
+        verifyZeroInteractions(bubbles, keyguardManager, userManager, eventLogger)
     }
 
     @Test
@@ -200,7 +217,7 @@
                 key = Bubble.getAppBubbleKeyForApp(expectedInfo.packageName, expectedInfo.user),
             )
 
-        verifyZeroInteractions(context, bubbles, keyguardManager, userManager, eventLogger)
+        verifyZeroInteractions(bubbles, keyguardManager, userManager, eventLogger)
     }
 
     @Test
@@ -211,7 +228,7 @@
                 key = "any other key",
             )
 
-        verifyZeroInteractions(context, bubbles, keyguardManager, userManager, eventLogger)
+        verifyZeroInteractions(bubbles, keyguardManager, userManager, eventLogger)
     }
 
     @Test
@@ -222,16 +239,19 @@
                 key = Bubble.getAppBubbleKeyForApp(NOTE_TASK_INFO.packageName, NOTE_TASK_INFO.user),
             )
 
-        verifyZeroInteractions(context, bubbles, keyguardManager, userManager, eventLogger)
+        verifyZeroInteractions(bubbles, keyguardManager, userManager, eventLogger)
     }
     // endregion
 
     // region showNoteTask
-    @Test
     fun showNoteTaskAsUser_keyguardIsLocked_shouldStartActivityWithExpectedUserAndLogUiEvent() {
         val user10 = UserHandle.of(/* userId= */ 10)
         val expectedInfo =
-            NOTE_TASK_INFO.copy(entryPoint = TAIL_BUTTON, isKeyguardLocked = true, user = user10)
+            NOTE_TASK_INFO.copy(
+                entryPoint = TAIL_BUTTON,
+                isKeyguardLocked = true,
+                user = user10,
+            )
         whenever(keyguardManager.isKeyguardLocked).thenReturn(expectedInfo.isKeyguardLocked)
         whenever(resolver.resolveInfo(any(), any(), any())).thenReturn(expectedInfo)
 
@@ -332,10 +352,48 @@
     }
 
     @Test
+    fun showNoteTask_defaultUserSet_shouldStartActivityWithExpectedUserAndLogUiEvent() {
+        whenever(secureSettings.getInt(eq(Settings.Secure.DEFAULT_NOTE_TASK_PROFILE), any()))
+            .thenReturn(10)
+        val user10 = UserHandle.of(/* userId= */ 10)
+
+        val expectedInfo =
+            NOTE_TASK_INFO.copy(
+                entryPoint = NoteTaskEntryPoint.TAIL_BUTTON,
+                isKeyguardLocked = true,
+                user = user10,
+            )
+        whenever(keyguardManager.isKeyguardLocked).thenReturn(expectedInfo.isKeyguardLocked)
+        whenever(resolver.resolveInfo(any(), any(), any())).thenReturn(expectedInfo)
+
+        createNoteTaskController()
+            .showNoteTask(
+                entryPoint = expectedInfo.entryPoint!!,
+            )
+
+        val intentCaptor = argumentCaptor<Intent>()
+        val userCaptor = argumentCaptor<UserHandle>()
+        verify(context).startActivityAsUser(capture(intentCaptor), capture(userCaptor))
+        intentCaptor.value.let { intent ->
+            assertThat(intent.action).isEqualTo(Intent.ACTION_CREATE_NOTE)
+            assertThat(intent.`package`).isEqualTo(NOTE_TASK_PACKAGE_NAME)
+            assertThat(intent.flags and FLAG_ACTIVITY_NEW_TASK).isEqualTo(FLAG_ACTIVITY_NEW_TASK)
+            assertThat(intent.flags and FLAG_ACTIVITY_MULTIPLE_TASK)
+                .isEqualTo(FLAG_ACTIVITY_MULTIPLE_TASK)
+            assertThat(intent.flags and FLAG_ACTIVITY_NEW_DOCUMENT)
+                .isEqualTo(FLAG_ACTIVITY_NEW_DOCUMENT)
+            assertThat(intent.getBooleanExtra(Intent.EXTRA_USE_STYLUS_MODE, false)).isTrue()
+        }
+        assertThat(userCaptor.value).isEqualTo(user10)
+        verify(eventLogger).logNoteTaskOpened(expectedInfo)
+        verifyZeroInteractions(bubbles)
+    }
+
+    @Test
     fun showNoteTask_bubblesIsNull_shouldDoNothing() {
         createNoteTaskController(bubbles = null).showNoteTask(entryPoint = TAIL_BUTTON)
 
-        verifyZeroInteractions(context, bubbles, eventLogger)
+        verifyZeroInteractions(bubbles, eventLogger)
     }
 
     @Test
@@ -347,14 +405,14 @@
         noteTaskController.showNoteTask(entryPoint = TAIL_BUTTON)
 
         verify(noteTaskController).showNoDefaultNotesAppToast()
-        verifyZeroInteractions(context, bubbles, eventLogger)
+        verifyZeroInteractions(bubbles, eventLogger)
     }
 
     @Test
     fun showNoteTask_flagDisabled_shouldDoNothing() {
         createNoteTaskController(isEnabled = false).showNoteTask(entryPoint = TAIL_BUTTON)
 
-        verifyZeroInteractions(context, bubbles, eventLogger)
+        verifyZeroInteractions(bubbles, eventLogger)
     }
 
     @Test
@@ -363,7 +421,7 @@
 
         createNoteTaskController().showNoteTask(entryPoint = TAIL_BUTTON)
 
-        verifyZeroInteractions(context, bubbles, eventLogger)
+        verifyZeroInteractions(bubbles, eventLogger)
     }
 
     @Test
@@ -483,7 +541,7 @@
 
         createNoteTaskController().showNoteTask(entryPoint = QUICK_AFFORDANCE)
 
-        verifyZeroInteractions(context, bubbles, eventLogger)
+        verifyZeroInteractions(bubbles, eventLogger)
     }
 
     @Test
@@ -499,7 +557,7 @@
 
         createNoteTaskController().showNoteTask(entryPoint = QUICK_AFFORDANCE)
 
-        verifyZeroInteractions(context, bubbles, eventLogger)
+        verifyZeroInteractions(bubbles, eventLogger)
     }
 
     @Test
@@ -611,11 +669,11 @@
 
         createNoteTaskController(isEnabled = true).onRoleHoldersChanged("NOT_NOTES", user)
 
-        verifyZeroInteractions(context)
+        verify(context, never()).startActivityAsUser(any(), any())
     }
 
     @Test
-    fun onRoleHoldersChanged_notesRole_sameUser_shouldUpdateShortcuts() {
+    fun onRoleHoldersChanged_notesRole_shouldUpdateShortcuts() {
         val user = userTracker.userHandle
         val controller = spy(createNoteTaskController())
         doNothing().whenever(controller).updateNoteTaskAsUser(any())
@@ -624,22 +682,41 @@
 
         verify(controller).updateNoteTaskAsUser(user)
     }
-
-    @Test
-    fun onRoleHoldersChanged_notesRole_differentUser_shouldUpdateShortcutsInUserProcess() {
-        // FakeUserTracker will default to UserHandle.SYSTEM.
-        val user = UserHandle.CURRENT
-
-        createNoteTaskController(isEnabled = true).onRoleHoldersChanged(ROLE_NOTES, user)
-
-        verify(context).startServiceAsUser(any(), eq(user))
-    }
     // endregion
 
     // region updateNoteTaskAsUser
     @Test
-    fun updateNoteTaskAsUser_withNotesRole_withShortcuts_shouldUpdateShortcuts() {
-        createNoteTaskController(isEnabled = true).updateNoteTaskAsUser(userTracker.userHandle)
+    fun updateNoteTaskAsUser_sameUser_shouldUpdateShortcuts() {
+        val user = userTracker.userHandle
+        val controller = spy(createNoteTaskController())
+        doNothing().whenever(controller).updateNoteTaskAsUserInternal(any())
+
+        controller.updateNoteTaskAsUser(user)
+
+        verify(controller).updateNoteTaskAsUserInternal(user)
+        verify(context, never()).startServiceAsUser(any(), any())
+    }
+
+    @Test
+    fun updateNoteTaskAsUser_differentUser_shouldUpdateShortcutsInUserProcess() {
+        // FakeUserTracker will default to UserHandle.SYSTEM.
+        val user = UserHandle.CURRENT
+        val controller = spy(createNoteTaskController(isEnabled = true))
+        doNothing().whenever(controller).updateNoteTaskAsUserInternal(any())
+
+        controller.updateNoteTaskAsUser(user)
+
+        verify(controller, never()).updateNoteTaskAsUserInternal(any())
+        val intent = withArgCaptor { verify(context).startServiceAsUser(capture(), eq(user)) }
+        assertThat(intent).hasComponentClass(NoteTaskControllerUpdateService::class.java)
+    }
+    // endregion
+
+    // region internalUpdateNoteTaskAsUser
+    @Test
+    fun updateNoteTaskAsUserInternal_withNotesRole_withShortcuts_shouldUpdateShortcuts() {
+        createNoteTaskController(isEnabled = true)
+            .updateNoteTaskAsUserInternal(userTracker.userHandle)
 
         val actualComponent = argumentCaptor<ComponentName>()
         verify(context.packageManager)
@@ -652,27 +729,29 @@
             .isEqualTo(CreateNoteTaskShortcutActivity::class.java.name)
         verify(shortcutManager, never()).disableShortcuts(any())
         verify(shortcutManager).enableShortcuts(listOf(SHORTCUT_ID))
-        val actualShortcuts = argumentCaptor<List<ShortcutInfo>>()
-        verify(shortcutManager).updateShortcuts(actualShortcuts.capture())
-        val actualShortcut = actualShortcuts.value.first()
-        assertThat(actualShortcut.id).isEqualTo(SHORTCUT_ID)
-        assertThat(actualShortcut.intent).run {
-            hasComponentClass(LaunchNoteTaskActivity::class.java)
-            hasAction(ACTION_CREATE_NOTE)
+        val shortcutInfo = withArgCaptor { verify(shortcutManager).updateShortcuts(capture()) }
+        with(shortcutInfo.first()) {
+            assertThat(id).isEqualTo(SHORTCUT_ID)
+            assertThat(intent).run {
+                hasComponentClass(LaunchNoteTaskActivity::class.java)
+                hasAction(ACTION_CREATE_NOTE)
+            }
+            assertThat(shortLabel).isEqualTo(NOTE_TASK_SHORT_LABEL)
+            assertThat(longLabel).isEqualTo(NOTE_TASK_LONG_LABEL)
+            assertThat(isLongLived).isEqualTo(true)
+            assertThat(icon.resId).isEqualTo(R.drawable.ic_note_task_shortcut_widget)
+            assertThat(extras?.getString(EXTRA_SHORTCUT_BADGE_OVERRIDE_PACKAGE))
+                .isEqualTo(NOTE_TASK_PACKAGE_NAME)
         }
-        assertThat(actualShortcut.shortLabel).isEqualTo(NOTE_TASK_SHORT_LABEL)
-        assertThat(actualShortcut.isLongLived).isEqualTo(true)
-        assertThat(actualShortcut.icon.resId).isEqualTo(R.drawable.ic_note_task_shortcut_widget)
-        assertThat(actualShortcut.extras?.getString(EXTRA_SHORTCUT_BADGE_OVERRIDE_PACKAGE))
-            .isEqualTo(NOTE_TASK_PACKAGE_NAME)
     }
 
     @Test
-    fun updateNoteTaskAsUser_noNotesRole_shouldDisableShortcuts() {
+    fun updateNoteTaskAsUserInternal_noNotesRole_shouldDisableShortcuts() {
         whenever(roleManager.getRoleHoldersAsUser(ROLE_NOTES, userTracker.userHandle))
             .thenReturn(emptyList())
 
-        createNoteTaskController(isEnabled = true).updateNoteTaskAsUser(userTracker.userHandle)
+        createNoteTaskController(isEnabled = true)
+            .updateNoteTaskAsUserInternal(userTracker.userHandle)
 
         val argument = argumentCaptor<ComponentName>()
         verify(context.packageManager)
@@ -689,8 +768,9 @@
     }
 
     @Test
-    fun updateNoteTaskAsUser_flagDisabled_shouldDisableShortcuts() {
-        createNoteTaskController(isEnabled = false).updateNoteTaskAsUser(userTracker.userHandle)
+    fun updateNoteTaskAsUserInternal_flagDisabled_shouldDisableShortcuts() {
+        createNoteTaskController(isEnabled = false)
+            .updateNoteTaskAsUserInternal(userTracker.userHandle)
 
         val argument = argumentCaptor<ComponentName>()
         verify(context.packageManager)
@@ -707,18 +787,17 @@
     }
     // endregion
 
-    // startregion startNoteTaskProxyActivityForUser
+    // startregion updateNoteTaskForAllUsers
     @Test
-    fun startNoteTaskProxyActivityForUser_shouldStartLaunchNoteTaskProxyActivityWithExpectedUser() {
-        val user0 = UserHandle.of(0)
-        createNoteTaskController().startNoteTaskProxyActivityForUser(user0)
+    fun updateNoteTaskForAllUsers_shouldRunUpdateForCurrentUserAndProfiles() {
+        userTracker.set(mainAndWorkProfileUsers, mainAndWorkProfileUsers.indexOf(mainUserInfo))
+        val controller = spy(createNoteTaskController())
+        doNothing().whenever(controller).updateNoteTaskAsUser(any())
 
-        val intentCaptor = argumentCaptor<Intent>()
-        verify(context).startActivityAsUser(intentCaptor.capture(), eq(user0))
-        assertThat(intentCaptor.value).run {
-            hasComponentClass(LaunchNoteTaskManagedProfileProxyActivity::class.java)
-            hasFlags(FLAG_ACTIVITY_NEW_TASK)
-        }
+        controller.updateNoteTaskForCurrentUserAndManagedProfiles()
+
+        verify(controller).updateNoteTaskAsUser(mainUserInfo.userHandle)
+        verify(controller).updateNoteTaskAsUser(workUserInfo.userHandle)
     }
     // endregion
 
@@ -838,7 +917,8 @@
     // endregion
 
     private companion object {
-        const val NOTE_TASK_SHORT_LABEL = "Notetaking"
+        const val NOTE_TASK_SHORT_LABEL = "Note-taking"
+        const val NOTE_TASK_LONG_LABEL = "Note-taking, App"
         const val NOTE_TASK_ACTIVITY_NAME = "NoteTaskActivity"
         const val NOTE_TASK_PACKAGE_NAME = "com.android.note.app"
         const val NOTE_TASK_UID = 123456
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt
index 4e85b6c..95bb3e0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt
@@ -32,9 +32,12 @@
 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.mock
 import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.mockito.withArgCaptor
 import com.android.systemui.util.time.FakeSystemClock
 import com.android.wm.shell.bubbles.Bubbles
+import com.google.common.truth.Truth.assertThat
 import java.util.Optional
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import org.junit.Before
@@ -71,19 +74,19 @@
     }
 
     private fun createUnderTest(
-        isEnabled: Boolean,
-        bubbles: Bubbles?,
+            isEnabled: Boolean,
+            bubbles: Bubbles?,
     ): NoteTaskInitializer =
-        NoteTaskInitializer(
-            controller = controller,
-            commandQueue = commandQueue,
-            optionalBubbles = Optional.ofNullable(bubbles),
-            isEnabled = isEnabled,
-            roleManager = roleManager,
-            userTracker = userTracker,
-            keyguardUpdateMonitor = keyguardMonitor,
-            backgroundExecutor = executor,
-        )
+            NoteTaskInitializer(
+                    controller = controller,
+                    commandQueue = commandQueue,
+                    optionalBubbles = Optional.ofNullable(bubbles),
+                    isEnabled = isEnabled,
+                    roleManager = roleManager,
+                    userTracker = userTracker,
+                    keyguardUpdateMonitor = keyguardMonitor,
+                    backgroundExecutor = executor,
+            )
 
     @Test
     fun initialize_withUserUnlocked() {
@@ -93,8 +96,8 @@
 
         verify(commandQueue).addCallback(any())
         verify(roleManager).addOnRoleHoldersChangedListenerAsUser(any(), any(), any())
-        verify(controller).setNoteTaskShortcutEnabled(any(), any())
-        verify(keyguardMonitor, never()).registerCallback(any())
+        verify(controller).updateNoteTaskForCurrentUserAndManagedProfiles()
+        verify(keyguardMonitor).registerCallback(any())
     }
 
     @Test
@@ -107,6 +110,7 @@
         verify(roleManager).addOnRoleHoldersChangedListenerAsUser(any(), any(), any())
         verify(controller, never()).setNoteTaskShortcutEnabled(any(), any())
         verify(keyguardMonitor).registerCallback(any())
+        assertThat(userTracker.callbacks).isNotEmpty()
     }
 
     @Test
@@ -116,12 +120,12 @@
         underTest.initialize()
 
         verifyZeroInteractions(
-            commandQueue,
-            bubbles,
-            controller,
-            roleManager,
-            userManager,
-            keyguardMonitor,
+                commandQueue,
+                bubbles,
+                controller,
+                roleManager,
+                userManager,
+                keyguardMonitor,
         )
     }
 
@@ -132,12 +136,12 @@
         underTest.initialize()
 
         verifyZeroInteractions(
-            commandQueue,
-            bubbles,
-            controller,
-            roleManager,
-            userManager,
-            keyguardMonitor,
+                commandQueue,
+                bubbles,
+                controller,
+                roleManager,
+                userManager,
+                keyguardMonitor,
         )
     }
 
@@ -146,7 +150,7 @@
         val expectedKeyEvent = KeyEvent(ACTION_DOWN, KEYCODE_STYLUS_BUTTON_TAIL)
         val underTest = createUnderTest(isEnabled = true, bubbles = bubbles)
         underTest.initialize()
-        val callback = captureArgument { verify(commandQueue).addCallback(capture()) }
+        val callback = withArgCaptor { verify(commandQueue).addCallback(capture()) }
 
         callback.handleSystemKey(expectedKeyEvent)
 
@@ -154,31 +158,49 @@
     }
 
     @Test
-    fun initialize_userUnlocked() {
+    fun initialize_userUnlocked_shouldUpdateNoteTask() {
         whenever(keyguardMonitor.isUserUnlocked(userTracker.userId)).thenReturn(false)
         val underTest = createUnderTest(isEnabled = true, bubbles = bubbles)
         underTest.initialize()
-        val callback = captureArgument { verify(keyguardMonitor).registerCallback(capture()) }
+        val callback = withArgCaptor { verify(keyguardMonitor).registerCallback(capture()) }
         whenever(keyguardMonitor.isUserUnlocked(userTracker.userId)).thenReturn(true)
 
         callback.onUserUnlocked()
-        verify(controller).setNoteTaskShortcutEnabled(any(), any())
+
+        verify(controller).updateNoteTaskForCurrentUserAndManagedProfiles()
     }
 
     @Test
-    fun initialize_onRoleHoldersChanged() {
+    fun initialize_onRoleHoldersChanged_shouldRunOnRoleHoldersChanged() {
         val underTest = createUnderTest(isEnabled = true, bubbles = bubbles)
         underTest.initialize()
-        val callback = captureArgument {
+        val callback = withArgCaptor {
             verify(roleManager)
-                .addOnRoleHoldersChangedListenerAsUser(any(), capture(), eq(UserHandle.ALL))
+                    .addOnRoleHoldersChangedListenerAsUser(any(), capture(), eq(UserHandle.ALL))
         }
 
         callback.onRoleHoldersChanged(ROLE_NOTES, userTracker.userHandle)
 
         verify(controller).onRoleHoldersChanged(ROLE_NOTES, userTracker.userHandle)
     }
-}
 
-private inline fun <reified T : Any> captureArgument(block: ArgumentCaptor<T>.() -> Unit) =
-    argumentCaptor<T>().apply(block).value
+    @Test
+    fun initialize_onProfilesChanged_shouldUpdateNoteTask() {
+        val underTest = createUnderTest(isEnabled = true, bubbles = bubbles)
+        underTest.initialize()
+
+        userTracker.callbacks.first().onProfilesChanged(emptyList())
+
+        verify(controller, times(2)).updateNoteTaskForCurrentUserAndManagedProfiles()
+    }
+
+    @Test
+    fun initialize_onUserChanged_shouldUpdateNoteTask() {
+        val underTest = createUnderTest(isEnabled = true, bubbles = bubbles)
+        underTest.initialize()
+
+        userTracker.callbacks.first().onUserChanged(0, mock())
+
+        verify(controller, times(2)).updateNoteTaskForCurrentUserAndManagedProfiles()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivityTest.kt
index a0c376f..1f0f0d7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivityTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivityTest.kt
@@ -16,30 +16,23 @@
 
 package com.android.systemui.notetask.shortcut
 
-import android.content.Intent
-import android.content.pm.UserInfo
-import android.os.UserHandle
-import android.os.UserManager
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import androidx.test.filters.SmallTest
 import androidx.test.rule.ActivityTestRule
-import androidx.test.runner.intercepting.SingleActivityFactory
 import com.android.dx.mockito.inline.extended.ExtendedMockito.verify
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.activity.SingleActivityFactory
 import com.android.systemui.notetask.NoteTaskController
 import com.android.systemui.notetask.NoteTaskEntryPoint
-import com.android.systemui.settings.FakeUserTracker
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.whenever
 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
-import org.mockito.Mockito.never
 import org.mockito.MockitoAnnotations
 
 @RunWith(AndroidTestingRunner::class)
@@ -48,21 +41,13 @@
 class LaunchNoteTaskActivityTest : SysuiTestCase() {
 
     @Mock lateinit var noteTaskController: NoteTaskController
-    @Mock lateinit var userManager: UserManager
-    private val userTracker: FakeUserTracker = FakeUserTracker()
 
     @Rule
     @JvmField
     val activityRule =
         ActivityTestRule<LaunchNoteTaskActivity>(
-            /* activityFactory= */ object :
-                SingleActivityFactory<LaunchNoteTaskActivity>(LaunchNoteTaskActivity::class.java) {
-                override fun create(intent: Intent?) =
-                    LaunchNoteTaskActivity(
-                        controller = noteTaskController,
-                        userManager = userManager,
-                        userTracker = userTracker
-                    )
+            /* activityFactory= */ SingleActivityFactory {
+                LaunchNoteTaskActivity(controller = noteTaskController)
             },
             /* initialTouchMode= */ false,
             /* launchActivity= */ false,
@@ -71,7 +56,6 @@
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
-        whenever(userManager.isManagedProfile(eq(workProfileUser.id))).thenReturn(true)
     }
 
     @After
@@ -83,36 +67,7 @@
     fun startActivityOnNonWorkProfileUser_shouldLaunchNoteTask() {
         activityRule.launchActivity(/* startIntent= */ null)
 
-        verify(noteTaskController).showNoteTask(eq(NoteTaskEntryPoint.WIDGET_PICKER_SHORTCUT))
-    }
-
-    @Test
-    fun startActivityOnWorkProfileUser_shouldLaunchProxyActivity() {
-        val mainUserHandle: UserHandle = mainUser.userHandle
-        userTracker.set(listOf(mainUser, workProfileUser), selectedUserIndex = 1)
-        whenever(userManager.isManagedProfile).thenReturn(true)
-        whenever(userManager.mainUser).thenReturn(mainUserHandle)
-
-        activityRule.launchActivity(/* startIntent= */ null)
-
-        verify(noteTaskController).startNoteTaskProxyActivityForUser(eq(mainUserHandle))
-    }
-
-    @Test
-    fun startActivityOnWorkProfileUser_noMainUser_shouldNotLaunch() {
-        userTracker.set(listOf(mainUser, workProfileUser), selectedUserIndex = 1)
-        whenever(userManager.isManagedProfile).thenReturn(true)
-        whenever(userManager.mainUser).thenReturn(null)
-
-        activityRule.launchActivity(/* startIntent= */ null)
-
-        verify(noteTaskController, never()).showNoteTask(any())
-        verify(noteTaskController, never()).startNoteTaskProxyActivityForUser(any())
-    }
-
-    private companion object {
-        val mainUser = UserInfo(/* id= */ 0, /* name= */ "primary", /* flags= */ UserInfo.FLAG_MAIN)
-        val workProfileUser =
-            UserInfo(/* id= */ 10, /* name= */ "work", /* flags= */ UserInfo.FLAG_PROFILE)
+        verify(noteTaskController)
+            .showNoteTaskAsUser(eq(NoteTaskEntryPoint.WIDGET_PICKER_SHORTCUT), any())
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskManagedProfileProxyActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskManagedProfileProxyActivityTest.kt
deleted file mode 100644
index 6347c34..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskManagedProfileProxyActivityTest.kt
+++ /dev/null
@@ -1,111 +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.notetask.shortcut
-
-import android.content.Intent
-import android.content.pm.UserInfo
-import android.os.UserHandle
-import android.os.UserManager
-import android.testing.AndroidTestingRunner
-import android.testing.TestableLooper
-import androidx.test.filters.SmallTest
-import androidx.test.rule.ActivityTestRule
-import androidx.test.runner.intercepting.SingleActivityFactory
-import com.android.dx.mockito.inline.extended.ExtendedMockito.never
-import com.android.dx.mockito.inline.extended.ExtendedMockito.verify
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.notetask.NoteTaskController
-import com.android.systemui.notetask.NoteTaskEntryPoint
-import com.android.systemui.settings.FakeUserTracker
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.whenever
-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
-import org.mockito.MockitoAnnotations
-
-@RunWith(AndroidTestingRunner::class)
-@SmallTest
-@TestableLooper.RunWithLooper
-class LaunchNoteTaskManagedProfileProxyActivityTest : SysuiTestCase() {
-
-    @Mock lateinit var noteTaskController: NoteTaskController
-    @Mock lateinit var userManager: UserManager
-    private val userTracker = FakeUserTracker()
-
-    @Rule
-    @JvmField
-    val activityRule =
-        ActivityTestRule<LaunchNoteTaskManagedProfileProxyActivity>(
-            /* activityFactory= */ object :
-                SingleActivityFactory<LaunchNoteTaskManagedProfileProxyActivity>(
-                    LaunchNoteTaskManagedProfileProxyActivity::class.java
-                ) {
-                override fun create(intent: Intent?) =
-                    LaunchNoteTaskManagedProfileProxyActivity(
-                        controller = noteTaskController,
-                        userManager = userManager,
-                        userTracker = userTracker
-                    )
-            },
-            /* initialTouchMode= */ false,
-            /* launchActivity= */ false,
-        )
-
-    @Before
-    fun setUp() {
-        MockitoAnnotations.initMocks(this)
-        whenever(userManager.isManagedProfile(eq(workProfileUser.id))).thenReturn(true)
-    }
-
-    @After
-    fun tearDown() {
-        activityRule.finishActivity()
-    }
-
-    @Test
-    fun startActivity_noWorkProfileUser_shouldNotLaunchNoteTask() {
-        userTracker.set(listOf(mainUser), selectedUserIndex = 0)
-        activityRule.launchActivity(/* startIntent= */ null)
-
-        verify(noteTaskController, never()).showNoteTaskAsUser(any(), any())
-    }
-
-    @Test
-    fun startActivity_hasWorkProfileUser_shouldLaunchNoteTaskOnTheWorkProfileUser() {
-        userTracker.set(mainAndWorkProfileUsers, mainAndWorkProfileUsers.indexOf(mainUser))
-        activityRule.launchActivity(/* startIntent= */ null)
-
-        val workProfileUserHandle: UserHandle = workProfileUser.userHandle
-        verify(noteTaskController)
-            .showNoteTaskAsUser(
-                eq(NoteTaskEntryPoint.WIDGET_PICKER_SHORTCUT),
-                eq(workProfileUserHandle)
-            )
-    }
-
-    private companion object {
-        val mainUser = UserInfo(/* id= */ 0, /* name= */ "primary", /* flags= */ UserInfo.FLAG_MAIN)
-        val workProfileUser =
-            UserInfo(/* id= */ 10, /* name= */ "work", /* flags= */ UserInfo.FLAG_PROFILE)
-        val mainAndWorkProfileUsers = listOf(mainUser, workProfileUser)
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/data/repository/PowerRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/power/data/repository/PowerRepositoryImplTest.kt
index bb3b3f7..a01394f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/power/data/repository/PowerRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/power/data/repository/PowerRepositoryImplTest.kt
@@ -24,7 +24,11 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.flow.launchIn
@@ -47,6 +51,8 @@
 @RunWith(JUnit4::class)
 class PowerRepositoryImplTest : SysuiTestCase() {
 
+    private val systemClock = FakeSystemClock()
+
     @Mock private lateinit var manager: PowerManager
     @Mock private lateinit var dispatcher: BroadcastDispatcher
     @Captor private lateinit var receiverCaptor: ArgumentCaptor<BroadcastReceiver>
@@ -62,7 +68,13 @@
         isInteractive = true
         whenever(manager.isInteractive).then { isInteractive }
 
-        underTest = PowerRepositoryImpl(manager = manager, dispatcher = dispatcher)
+        underTest =
+            PowerRepositoryImpl(
+                manager,
+                context.applicationContext,
+                systemClock,
+                dispatcher,
+            )
     }
 
     @Test
@@ -160,6 +172,27 @@
             job.cancel()
         }
 
+    @Test
+    fun wakeUp_notifiesPowerManager() {
+        systemClock.setUptimeMillis(345000)
+
+        underTest.wakeUp("fakeWhy", PowerManager.WAKE_REASON_GESTURE)
+
+        val reasonCaptor = argumentCaptor<String>()
+        verify(manager)
+            .wakeUp(eq(345000L), eq(PowerManager.WAKE_REASON_GESTURE), capture(reasonCaptor))
+        assertThat(reasonCaptor.value).contains("fakeWhy")
+    }
+
+    @Test
+    fun wakeUp_usesApplicationPackageName() {
+        underTest.wakeUp("fakeWhy", PowerManager.WAKE_REASON_GESTURE)
+
+        val reasonCaptor = argumentCaptor<String>()
+        verify(manager).wakeUp(any(), any(), capture(reasonCaptor))
+        assertThat(reasonCaptor.value).contains(context.applicationContext.packageName)
+    }
+
     private fun verifyRegistered() {
         // We must verify with all arguments, even those that are optional because they have default
         // values because Mockito is forcing us to. Once we can use mockito-kotlin, we should be
diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/domain/interactor/PowerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/power/domain/interactor/PowerInteractorTest.kt
index 31d4512..435a1f1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/power/domain/interactor/PowerInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/power/domain/interactor/PowerInteractorTest.kt
@@ -17,9 +17,18 @@
 
 package com.android.systemui.power.domain.interactor
 
+import android.os.PowerManager
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.shared.model.WakeSleepReason
+import com.android.systemui.keyguard.shared.model.WakefulnessModel
+import com.android.systemui.keyguard.shared.model.WakefulnessState
+import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.power.data.repository.FakePowerRepository
+import com.android.systemui.statusbar.phone.ScreenOffAnimationController
+import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.flow.launchIn
@@ -29,6 +38,9 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
 
 @SmallTest
 @RunWith(JUnit4::class)
@@ -36,14 +48,27 @@
 
     private lateinit var underTest: PowerInteractor
     private lateinit var repository: FakePowerRepository
+    private val keyguardRepository = FakeKeyguardRepository()
+    @Mock private lateinit var falsingCollector: FalsingCollector
+    @Mock private lateinit var screenOffAnimationController: ScreenOffAnimationController
+    @Mock private lateinit var statusBarStateController: StatusBarStateController
 
     @Before
     fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
         repository =
             FakePowerRepository(
                 initialInteractive = true,
             )
-        underTest = PowerInteractor(repository = repository)
+        underTest =
+            PowerInteractor(
+                repository,
+                keyguardRepository,
+                falsingCollector,
+                screenOffAnimationController,
+                statusBarStateController,
+            )
     }
 
     @Test
@@ -72,6 +97,117 @@
             job.cancel()
         }
 
+    @Test
+    fun wakeUpIfDozing_notDozing_notWoken() {
+        whenever(statusBarStateController.isDozing).thenReturn(false)
+        whenever(screenOffAnimationController.allowWakeUpIfDozing()).thenReturn(true)
+
+        underTest.wakeUpIfDozing("why", PowerManager.WAKE_REASON_TAP)
+
+        assertThat(repository.lastWakeWhy).isNull()
+        assertThat(repository.lastWakeReason).isNull()
+    }
+
+    @Test
+    fun wakeUpIfDozing_notAllowed_notWoken() {
+        whenever(screenOffAnimationController.allowWakeUpIfDozing()).thenReturn(false)
+        whenever(statusBarStateController.isDozing).thenReturn(true)
+
+        underTest.wakeUpIfDozing("why", PowerManager.WAKE_REASON_TAP)
+
+        assertThat(repository.lastWakeWhy).isNull()
+        assertThat(repository.lastWakeReason).isNull()
+    }
+
+    @Test
+    fun wakeUpIfDozing_dozingAndAllowed_wokenAndFalsingNotified() {
+        whenever(statusBarStateController.isDozing).thenReturn(true)
+        whenever(screenOffAnimationController.allowWakeUpIfDozing()).thenReturn(true)
+
+        underTest.wakeUpIfDozing("testReason", PowerManager.WAKE_REASON_GESTURE)
+
+        assertThat(repository.lastWakeWhy).isEqualTo("testReason")
+        assertThat(repository.lastWakeReason).isEqualTo(PowerManager.WAKE_REASON_GESTURE)
+        verify(falsingCollector).onScreenOnFromTouch()
+    }
+
+    @Test
+    fun wakeUpForFullScreenIntent_notGoingToSleepAndNotDozing_notWoken() {
+        keyguardRepository.setWakefulnessModel(
+            WakefulnessModel(
+                state = WakefulnessState.AWAKE,
+                lastWakeReason = WakeSleepReason.OTHER,
+                lastSleepReason = WakeSleepReason.OTHER,
+            )
+        )
+        whenever(statusBarStateController.isDozing).thenReturn(false)
+
+        underTest.wakeUpForFullScreenIntent()
+
+        assertThat(repository.lastWakeWhy).isNull()
+        assertThat(repository.lastWakeReason).isNull()
+    }
+
+    @Test
+    fun wakeUpForFullScreenIntent_startingToSleep_woken() {
+        keyguardRepository.setWakefulnessModel(
+            WakefulnessModel(
+                state = WakefulnessState.STARTING_TO_SLEEP,
+                lastWakeReason = WakeSleepReason.OTHER,
+                lastSleepReason = WakeSleepReason.OTHER,
+            )
+        )
+        whenever(statusBarStateController.isDozing).thenReturn(false)
+
+        underTest.wakeUpForFullScreenIntent()
+
+        assertThat(repository.lastWakeWhy).isNotNull()
+        assertThat(repository.lastWakeReason).isEqualTo(PowerManager.WAKE_REASON_APPLICATION)
+    }
+
+    @Test
+    fun wakeUpForFullScreenIntent_dozing_woken() {
+        whenever(statusBarStateController.isDozing).thenReturn(true)
+        keyguardRepository.setWakefulnessModel(
+            WakefulnessModel(
+                state = WakefulnessState.AWAKE,
+                lastWakeReason = WakeSleepReason.OTHER,
+                lastSleepReason = WakeSleepReason.OTHER,
+            )
+        )
+
+        underTest.wakeUpForFullScreenIntent()
+
+        assertThat(repository.lastWakeWhy).isNotNull()
+        assertThat(repository.lastWakeReason).isEqualTo(PowerManager.WAKE_REASON_APPLICATION)
+    }
+
+    @Test
+    fun wakeUpIfDreaming_dreaming_woken() {
+        // GIVEN device is dreaming
+        whenever(statusBarStateController.isDreaming).thenReturn(true)
+
+        // WHEN wakeUpIfDreaming is called
+        underTest.wakeUpIfDreaming("testReason", PowerManager.WAKE_REASON_GESTURE)
+
+        // THEN device is woken up
+        assertThat(repository.lastWakeWhy).isEqualTo("testReason")
+        assertThat(repository.lastWakeReason).isEqualTo(PowerManager.WAKE_REASON_GESTURE)
+    }
+
+    @Test
+    fun wakeUpIfDreaming_notDreaming_notWoken() {
+        // GIVEN device is not dreaming
+        whenever(statusBarStateController.isDreaming).thenReturn(false)
+
+        // WHEN wakeUpIfDreaming is called
+        underTest.wakeUpIfDreaming("why", PowerManager.WAKE_REASON_TAP)
+
+        // THEN device is not woken
+        assertThat(repository.lastWakeWhy).isNull()
+        assertThat(repository.lastWakeReason).isNull()
+    }
+
     companion object {
         private val IMMEDIATE = Dispatchers.Main.immediate
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentDisableFlagsLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentDisableFlagsLoggerTest.kt
index aacc695..93f316e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentDisableFlagsLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentDisableFlagsLoggerTest.kt
@@ -49,7 +49,7 @@
         buffer.dump(PrintWriter(stringWriter), tailLength = 0)
         val actualString = stringWriter.toString()
         val expectedLogString = disableFlagsLogger.getDisableFlagsString(
-            old = null, new = state, newAfterLocalModification = state
+            new = state, newAfterLocalModification = state
         )
 
         assertThat(actualString).contains(expectedLogString)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
index 87ca9df..fcda5f5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
@@ -50,10 +50,8 @@
 import com.android.keyguard.BouncerPanelExpansionCalculator;
 import com.android.systemui.R;
 import com.android.systemui.SysuiBaseFragmentTest;
-import com.android.systemui.animation.ShadeInterpolation;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
 import com.android.systemui.media.controls.ui.MediaHost;
 import com.android.systemui.qs.customize.QSCustomizerController;
 import com.android.systemui.qs.dagger.QSFragmentComponent;
@@ -169,9 +167,7 @@
     }
 
     @Test
-    public void transitionToFullShade_largeScreen_flagEnabled_alphaLargeScreenShadeInterpolator() {
-        when(mFeatureFlags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION))
-                .thenReturn(true);
+    public void transitionToFullShade_largeScreen_alphaLargeScreenShadeInterpolator() {
         QSFragment fragment = resumeAndGetFragment();
         setIsLargeScreen();
         setStatusBarCurrentAndUpcomingState(StatusBarState.SHADE);
@@ -188,25 +184,6 @@
     }
 
     @Test
-    public void transitionToFullShade_largeScreen_flagDisabled_alphaStandardInterpolator() {
-        when(mFeatureFlags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION))
-                .thenReturn(false);
-        QSFragment fragment = resumeAndGetFragment();
-        setIsLargeScreen();
-        setStatusBarCurrentAndUpcomingState(StatusBarState.SHADE);
-        boolean isTransitioningToFullShade = true;
-        float transitionProgress = 0.5f;
-        float squishinessFraction = 0.5f;
-        when(mLargeScreenShadeInterpolator.getQsAlpha(transitionProgress)).thenReturn(123f);
-
-        fragment.setTransitionToFullShadeProgress(isTransitioningToFullShade, transitionProgress,
-                squishinessFraction);
-
-        assertThat(mQsFragmentView.getAlpha())
-                .isEqualTo(ShadeInterpolation.getContentAlpha(transitionProgress));
-    }
-
-    @Test
     public void
             transitionToFullShade_onKeyguard_noBouncer_setsAlphaUsingLinearInterpolator() {
         QSFragment fragment = resumeAndGetFragment();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
index a0d8f98..9d9d0c7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
@@ -154,6 +154,21 @@
         verify(qsPanel).setCanCollapse(true)
     }
 
+    @Test
+    fun multipleListeningOnlyCallsBrightnessControllerOnce() {
+        controller.setListening(true, true)
+        controller.setListening(true, false)
+        controller.setListening(true, true)
+
+        verify(brightnessController).registerCallbacks()
+
+        controller.setListening(false, true)
+        controller.setListening(false, false)
+        controller.setListening(false, true)
+
+        verify(brightnessController).unregisterCallbacks()
+    }
+
     private fun setShouldUseSplitShade(shouldUse: Boolean) {
         testableResources.addOverride(R.bool.config_use_split_notification_shade, shouldUse)
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt
index a60dad4..fe6c9b3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt
@@ -15,9 +15,11 @@
 
 import android.graphics.Rect
 import android.testing.AndroidTestingRunner
+import android.testing.TestableContext
 import android.testing.TestableLooper
 import android.testing.TestableLooper.RunWithLooper
 import android.testing.ViewUtils
+import android.view.ContextThemeWrapper
 import android.view.View
 import android.view.ViewGroup.LayoutParams.MATCH_PARENT
 import android.view.accessibility.AccessibilityNodeInfo
@@ -55,19 +57,24 @@
 
     private lateinit var footer: View
 
+    private val themedContext = TestableContext(
+            ContextThemeWrapper(context, R.style.Theme_SystemUI_QuickSettings)
+    )
+
     @Before
     @Throws(Exception::class)
     fun setup() {
         MockitoAnnotations.initMocks(this)
         testableLooper = TestableLooper.get(this)
+        // Apply only the values of the theme that are not defined
 
         testableLooper.runWithLooper {
-            qsPanel = QSPanel(context, null)
+            qsPanel = QSPanel(themedContext, null)
             qsPanel.mUsingMediaPlayer = true
 
             qsPanel.initialize(qsLogger)
             // QSPanel inflates a footer inside of it, mocking it here
-            footer = LinearLayout(context).apply { id = R.id.qs_footer }
+            footer = LinearLayout(themedContext).apply { id = R.id.qs_footer }
             qsPanel.addView(footer, MATCH_PARENT, 100)
             qsPanel.onFinishInflate()
             // Provides a parent with non-zero size for QSPanel
@@ -105,12 +112,12 @@
         qsPanel.tileLayout?.addTile(
                 QSPanelControllerBase.TileRecord(
                     mock(QSTile::class.java),
-                    QSTileViewImpl(context, QSIconViewImpl(context))
+                    QSTileViewImpl(themedContext, QSIconViewImpl(themedContext))
                 )
             )
 
-        val mediaView = FrameLayout(context)
-        mediaView.addView(View(context), MATCH_PARENT, 800)
+        val mediaView = FrameLayout(themedContext)
+        mediaView.addView(View(themedContext), MATCH_PARENT, 800)
 
         qsPanel.setUsingHorizontalLayout(/* horizontal */ true, mediaView, /* force */ true)
         qsPanel.measure(
@@ -135,12 +142,12 @@
         qsPanel.tileLayout?.addTile(
             QSPanelControllerBase.TileRecord(
                 mock(QSTile::class.java),
-                QSTileViewImpl(context, QSIconViewImpl(context))
+                QSTileViewImpl(themedContext, QSIconViewImpl(themedContext))
             )
         )
 
-        val mediaView = FrameLayout(context)
-        mediaView.addView(View(context), MATCH_PARENT, 800)
+        val mediaView = FrameLayout(themedContext)
+        mediaView.addView(View(themedContext), MATCH_PARENT, 800)
 
         qsPanel.setUsingHorizontalLayout(/* horizontal */ true, mediaView, /* force */ true)
         qsPanel.measure(
@@ -161,7 +168,10 @@
     @Test
     fun testBottomPadding() {
         val padding = 10
-        context.orCreateTestableResources.addOverride(R.dimen.qs_panel_padding_bottom, padding)
+        themedContext.orCreateTestableResources.addOverride(
+                R.dimen.qs_panel_padding_bottom,
+                padding
+        )
         qsPanel.updatePadding()
         assertThat(qsPanel.paddingBottom).isEqualTo(padding)
     }
@@ -170,8 +180,11 @@
     fun testTopPadding() {
         val padding = 10
         val paddingCombined = 100
-        context.orCreateTestableResources.addOverride(R.dimen.qs_panel_padding_top, padding)
-        context.orCreateTestableResources.addOverride(R.dimen.qs_panel_padding_top, paddingCombined)
+        themedContext.orCreateTestableResources.addOverride(R.dimen.qs_panel_padding_top, padding)
+        themedContext.orCreateTestableResources.addOverride(
+                R.dimen.qs_panel_padding_top,
+                paddingCombined
+        )
 
         qsPanel.updatePadding()
         assertThat(qsPanel.paddingTop).isEqualTo(paddingCombined)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
index 587c49d..9781baa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
@@ -25,6 +25,7 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
@@ -67,8 +68,8 @@
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.settings.UserFileManager;
 import com.android.systemui.settings.UserTracker;
+import com.android.systemui.shade.ShadeController;
 import com.android.systemui.statusbar.phone.AutoTileManager;
-import com.android.systemui.statusbar.phone.CentralSurfaces;
 import com.android.systemui.tuner.TunerService;
 import com.android.systemui.util.FakeSharedPreferences;
 import com.android.systemui.util.concurrency.FakeExecutor;
@@ -86,7 +87,6 @@
 import java.io.PrintWriter;
 import java.io.StringWriter;
 import java.util.List;
-import java.util.Optional;
 import java.util.concurrent.Executor;
 
 import javax.inject.Provider;
@@ -108,9 +108,9 @@
     @Mock
     private TunerService mTunerService;
     @Mock
-    private Provider<AutoTileManager> mAutoTiles;
+    private AutoTileManager mAutoTiles;
     @Mock
-    private CentralSurfaces mCentralSurfaces;
+    private ShadeController mShadeController;
     @Mock
     private QSLogger mQSLogger;
     @Mock
@@ -141,6 +141,7 @@
         mFeatureFlags = new FakeFeatureFlags();
 
         mFeatureFlags.set(Flags.QS_PIPELINE_NEW_HOST, false);
+        mFeatureFlags.set(Flags.QS_PIPELINE_AUTO_ADD, false);
 
         mMainExecutor = new FakeExecutor(new FakeSystemClock());
 
@@ -161,9 +162,10 @@
         mSecureSettings = new FakeSettings();
         saveSetting("");
         mQSTileHost = new TestQSTileHost(mContext, mDefaultFactory, mMainExecutor,
-                mPluginManager, mTunerService, mAutoTiles, mCentralSurfaces,
+                mPluginManager, mTunerService, () -> mAutoTiles, mShadeController,
                 mQSLogger, mUserTracker, mSecureSettings, mCustomTileStatePersister,
                 mTileLifecycleManagerFactory, mUserFileManager, mFeatureFlags);
+        mMainExecutor.runAllReady();
 
         mSecureSettings.registerContentObserverForUser(SETTING, new ContentObserver(null) {
             @Override
@@ -682,6 +684,17 @@
         assertEquals(CUSTOM_TILE.getClassName(), proto.tiles[1].getComponentName().className);
     }
 
+    @Test
+    public void testUserChange_flagOn_autoTileManagerNotified() {
+        mFeatureFlags.set(Flags.QS_PIPELINE_NEW_HOST, true);
+        int currentUser = mUserTracker.getUserId();
+        clearInvocations(mAutoTiles);
+        when(mUserTracker.getUserId()).thenReturn(currentUser + 1);
+
+        mQSTileHost.onTuningChanged(SETTING, "a,b");
+        verify(mAutoTiles).changeUser(UserHandle.of(currentUser + 1));
+    }
+
     private SharedPreferences getSharedPreferencesForUser(int user) {
         return mUserFileManager.getSharedPreferences(QSTileHost.TILES, 0, user);
     }
@@ -691,13 +704,13 @@
                 QSFactory defaultFactory, Executor mainExecutor,
                 PluginManager pluginManager, TunerService tunerService,
                 Provider<AutoTileManager> autoTiles,
-                CentralSurfaces centralSurfaces, QSLogger qsLogger,
+                ShadeController shadeController, QSLogger qsLogger,
                 UserTracker userTracker, SecureSettings secureSettings,
                 CustomTileStatePersister customTileStatePersister,
                 TileLifecycleManager.Factory tileLifecycleManagerFactory,
                 UserFileManager userFileManager, FeatureFlags featureFlags) {
             super(context, defaultFactory, mainExecutor, pluginManager,
-                    tunerService, autoTiles,  Optional.of(centralSurfaces), qsLogger,
+                    tunerService, autoTiles,  shadeController, qsLogger,
                     userTracker, secureSettings, customTileStatePersister,
                     tileLifecycleManagerFactory, userFileManager, featureFlags);
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/TileLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/TileLayoutTest.java
index 8789253..f55ef65 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/TileLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/TileLayoutTest.java
@@ -26,12 +26,14 @@
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.content.Context;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.TestableLooper;
+import android.view.ContextThemeWrapper;
 import android.view.View;
 import android.view.accessibility.AccessibilityNodeInfo;
 
@@ -56,14 +58,17 @@
     private Resources mResources;
     private int mLayoutSizeForOneTile;
     private TileLayout mTileLayout; // under test
+    private Context mSpyContext;
+
 
     @Before
     public void setUp() throws Exception {
-        Context context = Mockito.spy(mContext);
-        mResources = Mockito.spy(context.getResources());
-        Mockito.when(mContext.getResources()).thenReturn(mResources);
+        mSpyContext = Mockito.spy(
+                new ContextThemeWrapper(mContext, R.style.Theme_SystemUI_QuickSettings));
+        mResources = Mockito.spy(mSpyContext.getResources());
+        when(mSpyContext.getResources()).thenReturn(mResources);
 
-        mTileLayout = new TileLayout(context);
+        mTileLayout = new TileLayout(mSpyContext);
         // Layout needs to leave space for the tile margins. Three times the margin size is
         // sufficient for any number of columns.
         mLayoutSizeForOneTile =
@@ -73,7 +78,7 @@
     private QSPanelControllerBase.TileRecord createTileRecord() {
         return new QSPanelControllerBase.TileRecord(
                 mock(QSTile.class),
-                spy(new QSTileViewImpl(mContext, new QSIconViewImpl(mContext))));
+                spy(new QSTileViewImpl(mSpyContext, new QSIconViewImpl(mSpyContext))));
     }
 
     @Test
@@ -161,7 +166,7 @@
                 .layout(left2.capture(), top2.capture(), right2.capture(), bottom2.capture());
 
         // We assume two tiles will always fit side-by-side.
-        assertTrue(mContext.getResources().getInteger(R.integer.quick_settings_num_columns) > 1);
+        assertTrue(mSpyContext.getResources().getInteger(R.integer.quick_settings_num_columns) > 1);
 
         // left <= right, top <= bottom
         assertTrue(left1.getValue() <= right1.getValue());
@@ -218,16 +223,16 @@
 
     @Test
     public void resourcesChanged_updateResources_returnsTrue() {
-        Mockito.when(mResources.getInteger(R.integer.quick_settings_num_columns)).thenReturn(1);
+        when(mResources.getInteger(R.integer.quick_settings_num_columns)).thenReturn(1);
         mTileLayout.updateResources(); // setup with 1
-        Mockito.when(mResources.getInteger(R.integer.quick_settings_num_columns)).thenReturn(2);
+        when(mResources.getInteger(R.integer.quick_settings_num_columns)).thenReturn(2);
 
         assertEquals(true, mTileLayout.updateResources());
     }
 
     @Test
     public void resourcesSame_updateResources_returnsFalse() {
-        Mockito.when(mResources.getInteger(R.integer.quick_settings_num_columns)).thenReturn(1);
+        when(mResources.getInteger(R.integer.quick_settings_num_columns)).thenReturn(1);
         mTileLayout.updateResources(); // setup with 1
 
         assertEquals(false, mTileLayout.updateResources());
@@ -250,7 +255,7 @@
         QSPanelControllerBase.TileRecord tileRecord = createTileRecord();
         mTileLayout.addTile(tileRecord);
 
-        FakeTileView tileView = new FakeTileView(mContext);
+        FakeTileView tileView = new FakeTileView(mSpyContext);
         QSTile.State state = new QSTile.State();
         state.label = "TEST LABEL";
         state.secondaryLabel = "TEST SECONDARY LABEL";
@@ -276,9 +281,10 @@
     }
 
     private void changeFontScaling(float scale) {
-        Configuration configuration = new Configuration(mContext.getResources().getConfiguration());
+        Configuration configuration =
+                new Configuration(mSpyContext.getResources().getConfiguration());
         configuration.fontScale = scale;
         // updateConfiguration could help update on both resource configuration and displayMetrics
-        mContext.getResources().updateConfiguration(configuration, null, null);
+        mSpyContext.getResources().updateConfiguration(configuration, null, null);
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt
index 198ed4a..41240e5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt
@@ -35,7 +35,7 @@
 import com.android.internal.logging.MetricsLogger
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.ActivityLaunchAnimator
-import com.android.systemui.animation.LaunchableFrameLayout
+import com.android.systemui.animation.view.LaunchableFrameLayout
 import com.android.systemui.classifier.FalsingManagerFake
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.plugins.qs.QSTile
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt
index 2cc6709..d647d6a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt
@@ -21,6 +21,7 @@
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import android.testing.TestableLooper.RunWithLooper
+import android.view.ContextThemeWrapper
 import androidx.test.filters.SmallTest
 import com.android.settingslib.Utils
 import com.android.settingslib.drawable.UserIconDrawable
@@ -63,6 +64,8 @@
     private val testScope = TestScope()
     private lateinit var utils: FooterActionsTestUtils
 
+    private val themedContext = ContextThemeWrapper(context, R.style.Theme_SystemUI_QuickSettings)
+
     @Before
     fun setUp() {
         utils = FooterActionsTestUtils(context, TestableLooper.get(this), testScope.testScheduler)
@@ -84,8 +87,14 @@
                     ContentDescription.Resource(R.string.accessibility_quick_settings_settings)
                 )
             )
-        assertThat(settings.backgroundColor).isEqualTo(R.attr.offStateColor)
-        assertThat(settings.iconTint).isNull()
+        assertThat(settings.backgroundColor).isEqualTo(R.attr.shadeInactive)
+        assertThat(settings.iconTint)
+            .isEqualTo(
+                Utils.getColorAttrDefaultColor(
+                    themedContext,
+                    R.attr.onShadeInactiveVariant,
+                )
+            )
     }
 
     @Test
@@ -105,12 +114,12 @@
                     ContentDescription.Resource(R.string.accessibility_quick_settings_power_menu)
                 )
             )
-        assertThat(power.backgroundColor).isEqualTo(com.android.internal.R.attr.colorAccent)
+        assertThat(power.backgroundColor).isEqualTo(R.attr.shadeActive)
         assertThat(power.iconTint)
             .isEqualTo(
                 Utils.getColorAttrDefaultColor(
-                    context,
-                    com.android.internal.R.attr.textColorOnAccent,
+                    themedContext,
+                    R.attr.onShadeActive,
                 ),
             )
     }
@@ -170,7 +179,7 @@
         assertThat(userSwitcher).isNotNull()
         assertThat(userSwitcher!!.icon)
             .isEqualTo(Icon.Loaded(picture, ContentDescription.Loaded("Signed in as foo")))
-        assertThat(userSwitcher.backgroundColor).isEqualTo(R.attr.offStateColor)
+        assertThat(userSwitcher.backgroundColor).isEqualTo(R.attr.shadeInactive)
 
         // Change the current user name.
         userSwitcherControllerWrapper.currentUserName = "bar"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepositoryImplTest.kt
index 18f3837..dc0fae5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepositoryImplTest.kt
@@ -38,6 +38,7 @@
 import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.capture
 import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.nullable
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
@@ -75,6 +76,9 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
 
+        whenever(context.createContextAsUser(any(), anyInt())).thenReturn(context)
+        whenever(context.packageManager).thenReturn(packageManager)
+
         // Use the default value set in the ServiceInfo
         whenever(packageManager.getComponentEnabledSetting(any()))
             .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT)
@@ -86,7 +90,6 @@
         underTest =
             InstalledTilesComponentRepositoryImpl(
                 context,
-                packageManager,
                 testDispatcher,
             )
     }
@@ -224,6 +227,52 @@
             assertThat(componentNames).isEmpty()
         }
 
+    @Test
+    fun packageOnlyInSecondaryUser_noException() =
+        testScope.runTest {
+            val userId = 10
+            val secondaryUserContext: Context = mock()
+            whenever(context.userId).thenReturn(0) // System context
+            whenever(context.createContextAsUser(eq(UserHandle.of(userId)), anyInt()))
+                .thenReturn(secondaryUserContext)
+
+            val secondaryUserPackageManager: PackageManager = mock()
+            whenever(secondaryUserContext.packageManager).thenReturn(secondaryUserPackageManager)
+
+            // Use the default value set in the ServiceInfo
+            whenever(secondaryUserPackageManager.getComponentEnabledSetting(any()))
+                .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT)
+            // System User package manager throws exception if the component doesn't exist for that
+            // user
+            whenever(packageManager.getComponentEnabledSetting(TEST_COMPONENT))
+                .thenThrow(IllegalArgumentException()) // The package is not in the system user
+
+            val resolveInfo =
+                ResolveInfo(TEST_COMPONENT, hasPermission = true, defaultEnabled = true)
+            // Both package manager should return the same (because the query is for the secondary
+            // user)
+            whenever(
+                    secondaryUserPackageManager.queryIntentServicesAsUser(
+                        matchIntent(),
+                        matchFlags(),
+                        eq(userId)
+                    )
+                )
+                .thenReturn(listOf(resolveInfo))
+            whenever(
+                    packageManager.queryIntentServicesAsUser(
+                        matchIntent(),
+                        matchFlags(),
+                        eq(userId)
+                    )
+                )
+                .thenReturn(listOf(resolveInfo))
+
+            val componentNames by collectLastValue(underTest.getInstalledTilesComponents(userId))
+
+            assertThat(componentNames).containsExactly(TEST_COMPONENT)
+        }
+
     private fun getRegisteredReceiver(): BroadcastReceiver {
         verify(context)
             .registerReceiverAsUser(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSettingListTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSettingListTest.kt
new file mode 100644
index 0000000..817ac61
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSettingListTest.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.systemui.qs.pipeline.domain.autoaddable
+
+import android.content.ComponentName
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class AutoAddableSettingListTest : SysuiTestCase() {
+
+    private val factory =
+        object : AutoAddableSetting.Factory {
+            override fun create(setting: String, spec: TileSpec): AutoAddableSetting {
+                return AutoAddableSetting(
+                    mock(),
+                    mock(),
+                    setting,
+                    spec,
+                )
+            }
+        }
+
+    @Test
+    fun correctLines_correctAutoAddables() {
+        val setting1 = "setting1"
+        val setting2 = "setting2"
+        val spec1 = TileSpec.create("spec1")
+        val spec2 = TileSpec.create(ComponentName("pkg", "cls"))
+
+        context.orCreateTestableResources.addOverride(
+            R.array.config_quickSettingsAutoAdd,
+            arrayOf(toStringLine(setting1, spec1), toStringLine(setting2, spec2))
+        )
+
+        val autoAddables = AutoAddableSettingList.parseSettingsResource(context.resources, factory)
+
+        assertThat(autoAddables)
+            .containsExactly(factory.create(setting1, spec1), factory.create(setting2, spec2))
+    }
+
+    @Test
+    fun malformedLine_ignored() {
+        val setting = "setting"
+        val spec = TileSpec.create("spec")
+
+        context.orCreateTestableResources.addOverride(
+            R.array.config_quickSettingsAutoAdd,
+            arrayOf(toStringLine(setting, spec), "bad_line")
+        )
+
+        val autoAddables = AutoAddableSettingList.parseSettingsResource(context.resources, factory)
+
+        assertThat(autoAddables).containsExactly(factory.create(setting, spec))
+    }
+
+    @Test
+    fun invalidSpec_ignored() {
+        val setting = "setting"
+        val spec = TileSpec.create("spec")
+
+        context.orCreateTestableResources.addOverride(
+            R.array.config_quickSettingsAutoAdd,
+            arrayOf(toStringLine(setting, spec), "invalid:")
+        )
+
+        val autoAddables = AutoAddableSettingList.parseSettingsResource(context.resources, factory)
+
+        assertThat(autoAddables).containsExactly(factory.create(setting, spec))
+    }
+
+    companion object {
+        private fun toStringLine(setting: String, spec: TileSpec) =
+            "$setting$SETTINGS_SEPARATOR${spec.spec}"
+        private const val SETTINGS_SEPARATOR = ":"
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSettingTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSettingTest.kt
new file mode 100644
index 0000000..36c3c9d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSettingTest.kt
@@ -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 com.android.systemui.qs.pipeline.domain.autoaddable
+
+import android.testing.AndroidTestingRunner
+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.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.util.settings.FakeSettings
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class AutoAddableSettingTest : SysuiTestCase() {
+
+    private val testDispatcher = StandardTestDispatcher()
+    private val testScope = TestScope(testDispatcher)
+
+    private val secureSettings = FakeSettings()
+    private val underTest =
+        AutoAddableSetting(
+            secureSettings,
+            testDispatcher,
+            SETTING,
+            SPEC,
+        )
+
+    @Test
+    fun settingNotSet_noSignal() =
+        testScope.runTest {
+            val userId = 0
+            val signal by collectLastValue(underTest.autoAddSignal(userId))
+
+            assertThat(signal).isNull() // null means no emitted value
+        }
+
+    @Test
+    fun settingSetTo0_noSignal() =
+        testScope.runTest {
+            val userId = 0
+            val signal by collectLastValue(underTest.autoAddSignal(userId))
+
+            secureSettings.putIntForUser(SETTING, 0, userId)
+
+            assertThat(signal).isNull() // null means no emitted value
+        }
+
+    @Test
+    fun settingSetToNon0_signal() =
+        testScope.runTest {
+            val userId = 0
+            val signal by collectLastValue(underTest.autoAddSignal(userId))
+
+            secureSettings.putIntForUser(SETTING, 42, userId)
+
+            assertThat(signal).isEqualTo(AutoAddSignal.Add(SPEC))
+        }
+
+    @Test
+    fun settingSetForUser_onlySignalInThatUser() =
+        testScope.runTest {
+            val signal0 by collectLastValue(underTest.autoAddSignal(0))
+            val signal1 by collectLastValue(underTest.autoAddSignal(1))
+
+            secureSettings.putIntForUser(SETTING, /* value */ 42, /* userHandle */ 1)
+
+            assertThat(signal0).isNull()
+            assertThat(signal1).isEqualTo(AutoAddSignal.Add(SPEC))
+        }
+
+    @Test
+    fun multipleNonZeroChanges_onlyOneSignal() =
+        testScope.runTest {
+            val userId = 0
+            val signals by collectValues(underTest.autoAddSignal(userId))
+
+            secureSettings.putIntForUser(SETTING, 1, userId)
+            secureSettings.putIntForUser(SETTING, 2, userId)
+
+            assertThat(signals.size).isEqualTo(1)
+        }
+
+    @Test
+    fun strategyIfNotAdded() {
+        assertThat(underTest.autoAddTracking).isEqualTo(AutoAddTracking.IfNotAdded(SPEC))
+    }
+
+    companion object {
+        private const val SETTING = "setting"
+        private val SPEC = TileSpec.create("spec")
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/CallbackControllerAutoAddableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/CallbackControllerAutoAddableTest.kt
new file mode 100644
index 0000000..afb43c7
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/CallbackControllerAutoAddableTest.kt
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.pipeline.domain.autoaddable
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.statusbar.policy.CallbackController
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.channels.ProducerScope
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class CallbackControllerAutoAddableTest : SysuiTestCase() {
+
+    @Test
+    fun callbackAddedAndRemoved() = runTest {
+        val controller = TestableController()
+        val callback = object : TestableController.Callback {}
+        val underTest =
+            object :
+                CallbackControllerAutoAddable<TestableController.Callback, TestableController>(
+                    controller
+                ) {
+                override val description: String = ""
+                override val spec: TileSpec
+                    get() = SPEC
+
+                override fun ProducerScope<AutoAddSignal>.getCallback():
+                    TestableController.Callback {
+                    return callback
+                }
+            }
+
+        val job = launch { underTest.autoAddSignal(0).collect {} }
+        runCurrent()
+        assertThat(controller.callbacks).containsExactly(callback)
+        job.cancel()
+        runCurrent()
+        assertThat(controller.callbacks).isEmpty()
+    }
+
+    @Test
+    fun sendAddFromCallback() = runTest {
+        val controller = TestableController()
+        val underTest =
+            object :
+                CallbackControllerAutoAddable<TestableController.Callback, TestableController>(
+                    controller
+                ) {
+                override val description: String = ""
+
+                override val spec: TileSpec
+                    get() = SPEC
+
+                override fun ProducerScope<AutoAddSignal>.getCallback():
+                    TestableController.Callback {
+                    return object : TestableController.Callback {
+                        override fun change() {
+                            sendAdd()
+                        }
+                    }
+                }
+            }
+
+        val signal by collectLastValue(underTest.autoAddSignal(0))
+        assertThat(signal).isNull()
+
+        controller.callbacks.first().change()
+
+        assertThat(signal).isEqualTo(AutoAddSignal.Add(SPEC))
+    }
+
+    @Test
+    fun strategyIfNotAdded() {
+        val underTest =
+            object :
+                CallbackControllerAutoAddable<TestableController.Callback, TestableController>(
+                    TestableController()
+                ) {
+                override val description: String = ""
+                override val spec: TileSpec
+                    get() = SPEC
+
+                override fun ProducerScope<AutoAddSignal>.getCallback():
+                    TestableController.Callback {
+                    return object : TestableController.Callback {}
+                }
+            }
+
+        assertThat(underTest.autoAddTracking).isEqualTo(AutoAddTracking.IfNotAdded(SPEC))
+    }
+
+    private class TestableController : CallbackController<TestableController.Callback> {
+
+        val callbacks = mutableSetOf<Callback>()
+
+        override fun addCallback(listener: Callback) {
+            callbacks.add(listener)
+        }
+
+        override fun removeCallback(listener: Callback) {
+            callbacks.remove(listener)
+        }
+
+        interface Callback {
+            fun change() {}
+        }
+    }
+
+    companion object {
+        private val SPEC = TileSpec.create("test")
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/CastAutoAddableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/CastAutoAddableTest.kt
new file mode 100644
index 0000000..a357dad
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/CastAutoAddableTest.kt
@@ -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 com.android.systemui.qs.pipeline.domain.autoaddable
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.CastTile
+import com.android.systemui.statusbar.policy.CastController
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.whenever
+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
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class CastAutoAddableTest : SysuiTestCase() {
+
+    @Mock private lateinit var castController: CastController
+    @Captor private lateinit var callbackCaptor: ArgumentCaptor<CastController.Callback>
+
+    private lateinit var underTest: CastAutoAddable
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        underTest = CastAutoAddable(castController)
+    }
+
+    @Test
+    fun onCastDevicesChanged_noDevices_noSignal() = runTest {
+        val signal by collectLastValue(underTest.autoAddSignal(0))
+        runCurrent()
+
+        verify(castController).addCallback(capture(callbackCaptor))
+
+        callbackCaptor.value.onCastDevicesChanged()
+
+        assertThat(signal).isNull()
+    }
+
+    @Test
+    fun onCastDevicesChanged_deviceNotConnectedOrConnecting_noSignal() = runTest {
+        val device =
+            CastController.CastDevice().apply {
+                state = CastController.CastDevice.STATE_DISCONNECTED
+            }
+        whenever(castController.castDevices).thenReturn(listOf(device))
+
+        val signal by collectLastValue(underTest.autoAddSignal(0))
+        runCurrent()
+
+        verify(castController).addCallback(capture(callbackCaptor))
+
+        callbackCaptor.value.onCastDevicesChanged()
+
+        assertThat(signal).isNull()
+    }
+
+    @Test
+    fun onCastDevicesChanged_someDeviceConnecting_addSignal() = runTest {
+        val disconnectedDevice =
+            CastController.CastDevice().apply {
+                state = CastController.CastDevice.STATE_DISCONNECTED
+            }
+        val connectingDevice =
+            CastController.CastDevice().apply { state = CastController.CastDevice.STATE_CONNECTING }
+        whenever(castController.castDevices)
+            .thenReturn(listOf(disconnectedDevice, connectingDevice))
+
+        val signal by collectLastValue(underTest.autoAddSignal(0))
+        runCurrent()
+
+        verify(castController).addCallback(capture(callbackCaptor))
+
+        callbackCaptor.value.onCastDevicesChanged()
+
+        assertThat(signal).isEqualTo(AutoAddSignal.Add(SPEC))
+    }
+
+    @Test
+    fun onCastDevicesChanged_someDeviceConnected_addSignal() = runTest {
+        val disconnectedDevice =
+            CastController.CastDevice().apply {
+                state = CastController.CastDevice.STATE_DISCONNECTED
+            }
+        val connectedDevice =
+            CastController.CastDevice().apply { state = CastController.CastDevice.STATE_CONNECTED }
+        whenever(castController.castDevices).thenReturn(listOf(disconnectedDevice, connectedDevice))
+
+        val signal by collectLastValue(underTest.autoAddSignal(0))
+        runCurrent()
+
+        verify(castController).addCallback(capture(callbackCaptor))
+
+        callbackCaptor.value.onCastDevicesChanged()
+
+        assertThat(signal).isEqualTo(AutoAddSignal.Add(SPEC))
+    }
+
+    companion object {
+        private val SPEC = TileSpec.create(CastTile.TILE_SPEC)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/DataSaverAutoAddableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/DataSaverAutoAddableTest.kt
new file mode 100644
index 0000000..098ffc3
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/DataSaverAutoAddableTest.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.pipeline.domain.autoaddable
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.DataSaverTile
+import com.android.systemui.statusbar.policy.DataSaverController
+import com.android.systemui.util.mockito.capture
+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
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class DataSaverAutoAddableTest : SysuiTestCase() {
+
+    @Mock private lateinit var dataSaverController: DataSaverController
+    @Captor private lateinit var callbackCaptor: ArgumentCaptor<DataSaverController.Listener>
+
+    private lateinit var underTest: DataSaverAutoAddable
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        underTest = DataSaverAutoAddable(dataSaverController)
+    }
+
+    @Test
+    fun dataSaverNotEnabled_NoSignal() = runTest {
+        val signal by collectLastValue(underTest.autoAddSignal(0))
+        runCurrent()
+
+        verify(dataSaverController).addCallback(capture(callbackCaptor))
+
+        callbackCaptor.value.onDataSaverChanged(false)
+
+        assertThat(signal).isNull()
+    }
+
+    @Test
+    fun dataSaverEnabled_addSignal() = runTest {
+        val signal by collectLastValue(underTest.autoAddSignal(0))
+        runCurrent()
+
+        verify(dataSaverController).addCallback(capture(callbackCaptor))
+
+        callbackCaptor.value.onDataSaverChanged(true)
+
+        assertThat(signal).isEqualTo(AutoAddSignal.Add(SPEC))
+    }
+
+    companion object {
+        private val SPEC = TileSpec.create(DataSaverTile.TILE_SPEC)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/DeviceControlsAutoAddableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/DeviceControlsAutoAddableTest.kt
new file mode 100644
index 0000000..a2e3538
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/DeviceControlsAutoAddableTest.kt
@@ -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.systemui.qs.pipeline.domain.autoaddable
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.DeviceControlsTile
+import com.android.systemui.statusbar.policy.DeviceControlsController
+import com.android.systemui.util.mockito.capture
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
+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.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class DeviceControlsAutoAddableTest : SysuiTestCase() {
+
+    @Mock private lateinit var deviceControlsController: DeviceControlsController
+    @Captor private lateinit var callbackCaptor: ArgumentCaptor<DeviceControlsController.Callback>
+
+    private lateinit var underTest: DeviceControlsAutoAddable
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        underTest = DeviceControlsAutoAddable(deviceControlsController)
+    }
+
+    @Test
+    fun strategyAlways() {
+        assertThat(underTest.autoAddTracking).isEqualTo(AutoAddTracking.Always)
+    }
+
+    @Test
+    fun onControlsUpdate_position_addSignal() = runTest {
+        val signal by collectLastValue(underTest.autoAddSignal(0))
+        val position = 5
+        runCurrent()
+
+        verify(deviceControlsController).setCallback(capture(callbackCaptor))
+        callbackCaptor.value.onControlsUpdate(position)
+
+        assertThat(signal).isEqualTo(AutoAddSignal.Add(SPEC, position))
+        verify(deviceControlsController).removeCallback()
+    }
+
+    @Test
+    fun onControlsUpdate_nullPosition_noAddSignal() = runTest {
+        val signal by collectLastValue(underTest.autoAddSignal(0))
+        runCurrent()
+
+        verify(deviceControlsController).setCallback(capture(callbackCaptor))
+        callbackCaptor.value.onControlsUpdate(null)
+
+        assertThat(signal).isNull()
+        verify(deviceControlsController).removeCallback()
+    }
+
+    @Test
+    fun onRemoveControlsAutoTracker_removeSignal() = runTest {
+        val signal by collectLastValue(underTest.autoAddSignal(0))
+        runCurrent()
+
+        verify(deviceControlsController).setCallback(capture(callbackCaptor))
+        callbackCaptor.value.removeControlsAutoTracker()
+
+        assertThat(signal).isEqualTo(AutoAddSignal.Remove(SPEC))
+    }
+
+    @Test
+    fun flowCancelled_removeCallback() = runTest {
+        val job = launch { underTest.autoAddSignal(0).collect() }
+        runCurrent()
+
+        job.cancel()
+        runCurrent()
+        verify(deviceControlsController).removeCallback()
+    }
+
+    companion object {
+        private val SPEC = TileSpec.create(DeviceControlsTile.TILE_SPEC)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/HotspotAutoAddableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/HotspotAutoAddableTest.kt
new file mode 100644
index 0000000..ee96b47
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/HotspotAutoAddableTest.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.pipeline.domain.autoaddable
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.HotspotTile
+import com.android.systemui.statusbar.policy.HotspotController
+import com.android.systemui.util.mockito.capture
+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
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class HotspotAutoAddableTest : SysuiTestCase() {
+
+    @Mock private lateinit var hotspotController: HotspotController
+    @Captor private lateinit var callbackCaptor: ArgumentCaptor<HotspotController.Callback>
+
+    private lateinit var underTest: HotspotAutoAddable
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        underTest = HotspotAutoAddable(hotspotController)
+    }
+
+    @Test
+    fun enabled_addSignal() = runTest {
+        val signal by collectLastValue(underTest.autoAddSignal(0))
+        runCurrent()
+
+        verify(hotspotController).addCallback(capture(callbackCaptor))
+        callbackCaptor.value.onHotspotChanged(/* enabled = */ true, /* numDevices = */ 5)
+
+        assertThat(signal).isEqualTo(AutoAddSignal.Add(SPEC))
+    }
+
+    @Test
+    fun notEnabled_noSignal() = runTest {
+        val signal by collectLastValue(underTest.autoAddSignal(0))
+        runCurrent()
+
+        verify(hotspotController).addCallback(capture(callbackCaptor))
+        callbackCaptor.value.onHotspotChanged(/* enabled = */ false, /* numDevices = */ 0)
+
+        assertThat(signal).isNull()
+    }
+
+    companion object {
+        private val SPEC = TileSpec.create(HotspotTile.TILE_SPEC)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/NightDisplayAutoAddableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/NightDisplayAutoAddableTest.kt
new file mode 100644
index 0000000..e03072a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/NightDisplayAutoAddableTest.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.qs.pipeline.domain.autoaddable
+
+import android.hardware.display.NightDisplayListener
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.dagger.NightDisplayListenerModule
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.NightDisplayTile
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.TestResult
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Answers
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.inOrder
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class NightDisplayAutoAddableTest : SysuiTestCase() {
+
+    @Mock(answer = Answers.RETURNS_SELF)
+    private lateinit var nightDisplayListenerBuilder: NightDisplayListenerModule.Builder
+    @Mock private lateinit var nightDisplayListener: NightDisplayListener
+    @Captor private lateinit var callbackCaptor: ArgumentCaptor<NightDisplayListener.Callback>
+
+    private lateinit var underTest: NightDisplayAutoAddable
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        whenever(nightDisplayListenerBuilder.build()).thenReturn(nightDisplayListener)
+    }
+
+    @Test
+    fun disabled_strategyDisabled() =
+        testWithFeatureAvailability(enabled = false) {
+            assertThat(underTest.autoAddTracking).isEqualTo(AutoAddTracking.Disabled)
+        }
+
+    @Test
+    fun enabled_strategyIfNotAdded() = testWithFeatureAvailability {
+        assertThat(underTest.autoAddTracking).isEqualTo(AutoAddTracking.IfNotAdded(SPEC))
+    }
+
+    @Test
+    fun listenerCreatedForCorrectUser() = testWithFeatureAvailability {
+        val user = 42
+        backgroundScope.launch { underTest.autoAddSignal(user).collect() }
+        runCurrent()
+
+        val inOrder = inOrder(nightDisplayListenerBuilder)
+        inOrder.verify(nightDisplayListenerBuilder).setUser(user)
+        inOrder.verify(nightDisplayListenerBuilder).build()
+    }
+
+    @Test
+    fun onCancelFlow_removeCallback() = testWithFeatureAvailability {
+        val job = launch { underTest.autoAddSignal(0).collect() }
+        runCurrent()
+        job.cancel()
+        runCurrent()
+        verify(nightDisplayListener).setCallback(null)
+    }
+
+    @Test
+    fun onActivatedTrue_addSignal() = testWithFeatureAvailability {
+        val signal by collectLastValue(underTest.autoAddSignal(0))
+        runCurrent()
+
+        verify(nightDisplayListener).setCallback(capture(callbackCaptor))
+        callbackCaptor.value.onActivated(true)
+
+        assertThat(signal).isEqualTo(AutoAddSignal.Add(SPEC))
+    }
+
+    private fun testWithFeatureAvailability(
+        enabled: Boolean = true,
+        body: suspend TestScope.() -> TestResult
+    ) = runTest {
+        context.orCreateTestableResources.addOverride(
+            com.android.internal.R.bool.config_nightDisplayAvailable,
+            enabled
+        )
+        underTest = NightDisplayAutoAddable(nightDisplayListenerBuilder, context)
+        body()
+    }
+
+    companion object {
+        private val SPEC = TileSpec.create(NightDisplayTile.TILE_SPEC)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/ReduceBrightColorsAutoAddableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/ReduceBrightColorsAutoAddableTest.kt
new file mode 100644
index 0000000..7b4a55e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/ReduceBrightColorsAutoAddableTest.kt
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.pipeline.domain.autoaddable
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.qs.ReduceBrightColorsController
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.ReduceBrightColorsTile
+import com.android.systemui.util.mockito.capture
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestResult
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class ReduceBrightColorsAutoAddableTest : SysuiTestCase() {
+
+    @Mock private lateinit var reduceBrightColorsController: ReduceBrightColorsController
+    @Captor
+    private lateinit var reduceBrightColorsListenerCaptor:
+        ArgumentCaptor<ReduceBrightColorsController.Listener>
+
+    private lateinit var underTest: ReduceBrightColorsAutoAddable
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+    }
+
+    @Test
+    fun notAvailable_strategyDisabled() =
+        testWithFeatureAvailability(available = false) {
+            assertThat(underTest.autoAddTracking).isEqualTo(AutoAddTracking.Disabled)
+        }
+
+    @Test
+    fun available_strategyIfNotAdded() =
+        testWithFeatureAvailability(available = true) {
+            assertThat(underTest.autoAddTracking).isEqualTo(AutoAddTracking.IfNotAdded(SPEC))
+        }
+
+    @Test
+    fun activated_addSignal() = testWithFeatureAvailability {
+        val signal by collectLastValue(underTest.autoAddSignal(0))
+        runCurrent()
+
+        verify(reduceBrightColorsController).addCallback(capture(reduceBrightColorsListenerCaptor))
+
+        reduceBrightColorsListenerCaptor.value.onActivated(true)
+
+        assertThat(signal).isEqualTo(AutoAddSignal.Add(SPEC))
+    }
+
+    @Test
+    fun notActivated_noSignal() = testWithFeatureAvailability {
+        val signal by collectLastValue(underTest.autoAddSignal(0))
+        runCurrent()
+
+        verify(reduceBrightColorsController).addCallback(capture(reduceBrightColorsListenerCaptor))
+
+        reduceBrightColorsListenerCaptor.value.onActivated(false)
+
+        assertThat(signal).isNull()
+    }
+
+    private fun testWithFeatureAvailability(
+        available: Boolean = true,
+        body: suspend TestScope.() -> TestResult
+    ) = runTest {
+        underTest = ReduceBrightColorsAutoAddable(reduceBrightColorsController, available)
+        body()
+    }
+
+    companion object {
+        private val SPEC = TileSpec.create(ReduceBrightColorsTile.TILE_SPEC)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/SafetyCenterAutoAddableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/SafetyCenterAutoAddableTest.kt
new file mode 100644
index 0000000..fb35a3a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/SafetyCenterAutoAddableTest.kt
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.pipeline.domain.autoaddable
+
+import android.content.ComponentName
+import android.content.pm.PackageManager
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.statusbar.policy.SafetyController
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.collect
+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.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.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class SafetyCenterAutoAddableTest : SysuiTestCase() {
+    private val testDispatcher = StandardTestDispatcher()
+    private val testScope = TestScope(testDispatcher)
+
+    @Mock private lateinit var safetyController: SafetyController
+    @Mock private lateinit var packageManager: PackageManager
+    @Captor
+    private lateinit var safetyControllerListenerCaptor: ArgumentCaptor<SafetyController.Listener>
+
+    private lateinit var underTest: SafetyCenterAutoAddable
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        context.ensureTestableResources()
+
+        // Set these by default, will also test special cases
+        context.orCreateTestableResources.addOverride(
+            R.string.safety_quick_settings_tile_class,
+            SAFETY_TILE_CLASS_NAME
+        )
+        whenever(packageManager.permissionControllerPackageName)
+            .thenReturn(PERMISSION_CONTROLLER_PACKAGE_NAME)
+
+        underTest =
+            SafetyCenterAutoAddable(
+                safetyController,
+                packageManager,
+                context.resources,
+                testDispatcher,
+            )
+    }
+
+    @Test
+    fun strategyAlwaysTrack() =
+        testScope.runTest {
+            assertThat(underTest.autoAddTracking).isEqualTo(AutoAddTracking.Always)
+        }
+
+    @Test
+    fun tileAlwaysAdded() =
+        testScope.runTest {
+            val signal by collectLastValue(underTest.autoAddSignal(0))
+
+            assertThat(signal).isEqualTo(AutoAddSignal.Add(SPEC))
+        }
+
+    @Test
+    fun safetyCenterDisabled_removeSignal() =
+        testScope.runTest {
+            val signal by collectLastValue(underTest.autoAddSignal(0))
+            runCurrent()
+
+            verify(safetyController).addCallback(capture(safetyControllerListenerCaptor))
+            safetyControllerListenerCaptor.value.onSafetyCenterEnableChanged(false)
+
+            assertThat(signal).isEqualTo(AutoAddSignal.Remove(SPEC))
+        }
+
+    @Test
+    fun safetyCenterEnabled_newAddSignal() =
+        testScope.runTest {
+            val signals by collectValues(underTest.autoAddSignal(0))
+            runCurrent()
+
+            verify(safetyController).addCallback(capture(safetyControllerListenerCaptor))
+            safetyControllerListenerCaptor.value.onSafetyCenterEnableChanged(true)
+
+            assertThat(signals.size).isEqualTo(2)
+            assertThat(signals.last()).isEqualTo(AutoAddSignal.Add(SPEC))
+        }
+
+    @Test
+    fun flowCancelled_removeListener() =
+        testScope.runTest {
+            val job = launch { underTest.autoAddSignal(0).collect() }
+            runCurrent()
+
+            verify(safetyController).addCallback(capture(safetyControllerListenerCaptor))
+
+            job.cancel()
+            runCurrent()
+            verify(safetyController).removeCallback(safetyControllerListenerCaptor.value)
+        }
+
+    @Test
+    fun emptyClassName_noSignals() =
+        testScope.runTest {
+            context.orCreateTestableResources.addOverride(
+                R.string.safety_quick_settings_tile_class,
+                ""
+            )
+            val signal by collectLastValue(underTest.autoAddSignal(0))
+            runCurrent()
+
+            verify(safetyController, never()).addCallback(any())
+
+            assertThat(signal).isNull()
+        }
+
+    companion object {
+        private const val SAFETY_TILE_CLASS_NAME = "cls"
+        private const val PERMISSION_CONTROLLER_PACKAGE_NAME = "pkg"
+        private val SPEC =
+            TileSpec.create(
+                ComponentName(PERMISSION_CONTROLLER_PACKAGE_NAME, SAFETY_TILE_CLASS_NAME)
+            )
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/WalletAutoAddableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/WalletAutoAddableTest.kt
new file mode 100644
index 0000000..6b250f4
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/WalletAutoAddableTest.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.autoaddable
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.QuickAccessWalletTile
+import com.android.systemui.statusbar.policy.WalletController
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+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
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class WalletAutoAddableTest : SysuiTestCase() {
+
+    @Mock private lateinit var walletController: WalletController
+
+    private lateinit var underTest: WalletAutoAddable
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        underTest = WalletAutoAddable(walletController)
+    }
+
+    @Test
+    fun strategyIfNotAdded() {
+        assertThat(underTest.autoAddTracking).isEqualTo(AutoAddTracking.IfNotAdded(SPEC))
+    }
+
+    @Test
+    fun walletPositionNull_noSignal() = runTest {
+        whenever(walletController.getWalletPosition()).thenReturn(null)
+
+        val signal by collectLastValue(underTest.autoAddSignal(0))
+
+        assertThat(signal).isNull()
+    }
+
+    @Test
+    fun walletPositionNumber_addedInThatPosition() = runTest {
+        val position = 4
+        whenever(walletController.getWalletPosition()).thenReturn(4)
+
+        val signal by collectLastValue(underTest.autoAddSignal(0))
+
+        assertThat(signal).isEqualTo(AutoAddSignal.Add(SPEC, position))
+    }
+
+    companion object {
+        private val SPEC = TileSpec.create(QuickAccessWalletTile.TILE_SPEC)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/WorkTileAutoAddableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/WorkTileAutoAddableTest.kt
new file mode 100644
index 0000000..e9f7c8ab
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/WorkTileAutoAddableTest.kt
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.pipeline.domain.autoaddable
+
+import android.content.pm.UserInfo
+import android.content.pm.UserInfo.FLAG_FULL
+import android.content.pm.UserInfo.FLAG_MANAGED_PROFILE
+import android.content.pm.UserInfo.FLAG_PRIMARY
+import android.content.pm.UserInfo.FLAG_PROFILE
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.WorkModeTile
+import com.android.systemui.settings.FakeUserTracker
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class WorkTileAutoAddableTest : SysuiTestCase() {
+
+    private lateinit var userTracker: FakeUserTracker
+
+    private lateinit var underTest: WorkTileAutoAddable
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+
+        userTracker =
+            FakeUserTracker(
+                _userId = USER_INFO_0.id,
+                _userInfo = USER_INFO_0,
+                _userProfiles = listOf(USER_INFO_0)
+            )
+
+        underTest = WorkTileAutoAddable(userTracker)
+    }
+
+    @Test
+    fun changeInProfiles_hasManagedProfile_sendsAddSignal() = runTest {
+        val signal by collectLastValue(underTest.autoAddSignal(0))
+
+        userTracker.set(listOf(USER_INFO_0, USER_INFO_WORK), selectedUserIndex = 0)
+
+        assertThat(signal).isEqualTo(AutoAddSignal.Add(SPEC))
+    }
+
+    @Test
+    fun changeInProfiles_noManagedProfile_sendsRemoveSignal() = runTest {
+        userTracker.set(listOf(USER_INFO_0, USER_INFO_WORK), selectedUserIndex = 0)
+
+        val signal by collectLastValue(underTest.autoAddSignal(0))
+
+        userTracker.set(listOf(USER_INFO_0), selectedUserIndex = 0)
+
+        assertThat(signal).isEqualTo(AutoAddSignal.Remove(SPEC))
+    }
+
+    @Test
+    fun startingWithManagedProfile_sendsAddSignal() = runTest {
+        userTracker.set(listOf(USER_INFO_0, USER_INFO_WORK), selectedUserIndex = 0)
+
+        val signal by collectLastValue(underTest.autoAddSignal(0))
+
+        assertThat(signal).isEqualTo(AutoAddSignal.Add(SPEC))
+    }
+
+    @Test
+    fun userChangeToUserWithProfile_noSignalForOriginalUser() = runTest {
+        val signal by collectLastValue(underTest.autoAddSignal(0))
+
+        userTracker.set(listOf(USER_INFO_1, USER_INFO_WORK), selectedUserIndex = 0)
+
+        assertThat(signal).isNotEqualTo(AutoAddSignal.Add(SPEC))
+    }
+
+    @Test
+    fun userChangeToUserWithoutProfile_noSignalForOriginalUser() = runTest {
+        userTracker.set(listOf(USER_INFO_0, USER_INFO_WORK), selectedUserIndex = 0)
+        val signal by collectLastValue(underTest.autoAddSignal(0))
+
+        userTracker.set(listOf(USER_INFO_1), selectedUserIndex = 0)
+
+        assertThat(signal).isNotEqualTo(AutoAddSignal.Remove(SPEC))
+    }
+
+    @Test
+    fun strategyAlways() {
+        assertThat(underTest.autoAddTracking).isEqualTo(AutoAddTracking.Always)
+    }
+
+    companion object {
+        private val SPEC = TileSpec.create(WorkModeTile.TILE_SPEC)
+        private val USER_INFO_0 = UserInfo(0, "", FLAG_PRIMARY or FLAG_FULL)
+        private val USER_INFO_1 = UserInfo(1, "", FLAG_FULL)
+        private val USER_INFO_WORK = UserInfo(10, "", FLAG_PROFILE or FLAG_MANAGED_PROFILE)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractorTest.kt
new file mode 100644
index 0000000..f924b35
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractorTest.kt
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.pipeline.domain.interactor
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.qs.pipeline.data.repository.FakeAutoAddRepository
+import com.android.systemui.qs.pipeline.domain.autoaddable.FakeAutoAddable
+import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
+import com.android.systemui.qs.pipeline.domain.model.AutoAddable
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.Mockito.inOrder
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@OptIn(ExperimentalCoroutinesApi::class)
+class AutoAddInteractorTest : SysuiTestCase() {
+    private val testScope = TestScope()
+
+    private val autoAddRepository = FakeAutoAddRepository()
+
+    @Mock private lateinit var dumpManager: DumpManager
+    @Mock private lateinit var currentTilesInteractor: CurrentTilesInteractor
+    @Mock private lateinit var logger: QSPipelineLogger
+    private lateinit var underTest: AutoAddInteractor
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        whenever(currentTilesInteractor.userId).thenReturn(MutableStateFlow(USER))
+    }
+
+    @Test
+    fun autoAddable_alwaysTrack_addSignal_tileAddedAndMarked() =
+        testScope.runTest {
+            val fakeAutoAddable = FakeAutoAddable(SPEC, AutoAddTracking.Always)
+            val autoAddedTiles by collectLastValue(autoAddRepository.autoAddedTiles(USER))
+
+            underTest = createInteractor(setOf(fakeAutoAddable))
+
+            val position = 3
+            fakeAutoAddable.sendAddSignal(USER, position)
+            runCurrent()
+
+            verify(currentTilesInteractor).addTile(SPEC, position)
+            assertThat(autoAddedTiles).contains(SPEC)
+        }
+
+    @Test
+    fun autoAddable_alwaysTrack_addThenRemoveSignal_tileAddedAndRemoved() =
+        testScope.runTest {
+            val fakeAutoAddable = FakeAutoAddable(SPEC, AutoAddTracking.Always)
+            val autoAddedTiles by collectLastValue(autoAddRepository.autoAddedTiles(USER))
+
+            underTest = createInteractor(setOf(fakeAutoAddable))
+
+            val position = 3
+            fakeAutoAddable.sendAddSignal(USER, position)
+            runCurrent()
+            fakeAutoAddable.sendRemoveSignal(USER)
+            runCurrent()
+
+            val inOrder = inOrder(currentTilesInteractor)
+            inOrder.verify(currentTilesInteractor).addTile(SPEC, position)
+            inOrder.verify(currentTilesInteractor).removeTiles(setOf(SPEC))
+            assertThat(autoAddedTiles).doesNotContain(SPEC)
+        }
+
+    @Test
+    fun autoAddable_alwaysTrack_addSignalWhenAddedPreviously_noop() =
+        testScope.runTest {
+            val fakeAutoAddable = FakeAutoAddable(SPEC, AutoAddTracking.Always)
+            autoAddRepository.markTileAdded(USER, SPEC)
+            runCurrent()
+
+            underTest = createInteractor(setOf(fakeAutoAddable))
+
+            val position = 3
+            fakeAutoAddable.sendAddSignal(USER, position)
+            runCurrent()
+
+            verify(currentTilesInteractor, never()).addTile(SPEC, position)
+        }
+
+    @Test
+    fun autoAddable_disabled_noInteractionsWithCurrentTilesInteractor() =
+        testScope.runTest {
+            val fakeAutoAddable = FakeAutoAddable(SPEC, AutoAddTracking.Disabled)
+            val autoAddedTiles by collectLastValue(autoAddRepository.autoAddedTiles(USER))
+
+            underTest = createInteractor(setOf(fakeAutoAddable))
+
+            val position = 3
+            fakeAutoAddable.sendAddSignal(USER, position)
+            runCurrent()
+            fakeAutoAddable.sendRemoveSignal(USER)
+            runCurrent()
+
+            verify(currentTilesInteractor, never()).addTile(any(), anyInt())
+            verify(currentTilesInteractor, never()).removeTiles(any())
+            assertThat(autoAddedTiles).doesNotContain(SPEC)
+        }
+
+    @Test
+    fun autoAddable_trackIfNotAdded_removeSignal_noop() =
+        testScope.runTest {
+            val fakeAutoAddable = FakeAutoAddable(SPEC, AutoAddTracking.IfNotAdded(SPEC))
+            runCurrent()
+
+            underTest = createInteractor(setOf(fakeAutoAddable))
+
+            fakeAutoAddable.sendRemoveSignal(USER)
+            runCurrent()
+
+            verify(currentTilesInteractor, never()).addTile(any(), anyInt())
+            verify(currentTilesInteractor, never()).removeTiles(any())
+        }
+
+    @Test
+    fun autoAddable_trackIfNotAdded_addSignalWhenPreviouslyAdded_noop() =
+        testScope.runTest {
+            val fakeAutoAddable = FakeAutoAddable(SPEC, AutoAddTracking.IfNotAdded(SPEC))
+            autoAddRepository.markTileAdded(USER, SPEC)
+            runCurrent()
+
+            underTest = createInteractor(setOf(fakeAutoAddable))
+
+            fakeAutoAddable.sendAddSignal(USER)
+            runCurrent()
+
+            verify(currentTilesInteractor, never()).addTile(any(), anyInt())
+            verify(currentTilesInteractor, never()).removeTiles(any())
+        }
+
+    @Test
+    fun autoAddable_trackIfNotAdded_addSignal_addedAndMarked() =
+        testScope.runTest {
+            val fakeAutoAddable = FakeAutoAddable(SPEC, AutoAddTracking.IfNotAdded(SPEC))
+            val autoAddedTiles by collectLastValue(autoAddRepository.autoAddedTiles(USER))
+
+            underTest = createInteractor(setOf(fakeAutoAddable))
+
+            val position = 3
+            fakeAutoAddable.sendAddSignal(USER, position)
+            runCurrent()
+
+            verify(currentTilesInteractor).addTile(SPEC, position)
+            assertThat(autoAddedTiles).contains(SPEC)
+        }
+
+    private fun createInteractor(autoAddables: Set<AutoAddable>): AutoAddInteractor {
+        return AutoAddInteractor(
+                autoAddables,
+                autoAddRepository,
+                dumpManager,
+                logger,
+                testScope.backgroundScope
+            )
+            .apply { init(currentTilesInteractor) }
+    }
+
+    companion object {
+        private val SPEC = TileSpec.create("spec")
+        private val USER = 10
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
index e7ad489..30cea2d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
@@ -100,6 +100,7 @@
         MockitoAnnotations.initMocks(this)
 
         featureFlags.set(Flags.QS_PIPELINE_NEW_HOST, true)
+        featureFlags.set(Flags.QS_PIPELINE_AUTO_ADD, true)
 
         userRepository.setUserInfos(listOf(USER_INFO_0, USER_INFO_1))
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/PanelInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/PanelInteractorImplTest.kt
index 45783ab..6556cfd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/PanelInteractorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/PanelInteractorImplTest.kt
@@ -18,8 +18,7 @@
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.statusbar.phone.CentralSurfaces
-import java.util.Optional
+import com.android.systemui.shade.ShadeController
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -31,7 +30,7 @@
 @SmallTest
 class PanelInteractorImplTest : SysuiTestCase() {
 
-    @Mock private lateinit var centralSurfaces: CentralSurfaces
+    @Mock private lateinit var shadeController: ShadeController
 
     @Before
     fun setup() {
@@ -40,37 +39,28 @@
 
     @Test
     fun openPanels_callsCentralSurfaces() {
-        val underTest = PanelInteractorImpl(Optional.of(centralSurfaces))
+        val underTest = PanelInteractorImpl(shadeController)
 
         underTest.openPanels()
 
-        verify(centralSurfaces).postAnimateOpenPanels()
+        verify(shadeController).postAnimateExpandQs()
     }
 
     @Test
     fun collapsePanels_callsCentralSurfaces() {
-        val underTest = PanelInteractorImpl(Optional.of(centralSurfaces))
+        val underTest = PanelInteractorImpl(shadeController)
 
         underTest.collapsePanels()
 
-        verify(centralSurfaces).postAnimateCollapsePanels()
+        verify(shadeController).postAnimateCollapseShade()
     }
 
     @Test
     fun forceCollapsePanels_callsCentralSurfaces() {
-        val underTest = PanelInteractorImpl(Optional.of(centralSurfaces))
+        val underTest = PanelInteractorImpl(shadeController)
 
         underTest.forceCollapsePanels()
 
-        verify(centralSurfaces).postAnimateForceCollapsePanels()
-    }
-
-    @Test
-    fun whenOptionalEmpty_doesnThrow() {
-        val underTest = PanelInteractorImpl(Optional.empty())
-
-        underTest.openPanels()
-        underTest.collapsePanels()
-        underTest.forceCollapsePanels()
+        verify(shadeController).postAnimateForceCollapseShade()
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java
index f55d262..180fed9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java
@@ -155,6 +155,28 @@
         inOrder.verify(d).stop();
     }
 
+    @Test
+    public void testAnimatorCallbackRemovedOnOldDrawable() {
+        ImageView iv = new ImageView(mContext);
+        AnimatedVectorDrawable d1 = mock(AnimatedVectorDrawable.class);
+        when(d1.getConstantState()).thenReturn(fakeConstantState(d1));
+        AnimatedVectorDrawable d2 = mock(AnimatedVectorDrawable.class);
+        when(d2.getConstantState()).thenReturn(fakeConstantState(d2));
+        State s = new State();
+        s.isTransient = true;
+
+        // When set Animatable2 d1
+        s.icon = new QSTileImpl.DrawableIcon(d1);
+        mIconView.updateIcon(iv, s, true);
+
+        // And then set Animatable2 d2
+        s.icon = new QSTileImpl.DrawableIcon(d2);
+        mIconView.updateIcon(iv, s, true);
+
+        // Then d1 has its callback cleared
+        verify(d1).clearAnimationCallbacks();
+    }
+
     private static Drawable.ConstantState fakeConstantState(Drawable otherDrawable) {
         return new Drawable.ConstantState() {
             @Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
index 962b537..22b1c7b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
@@ -204,6 +204,19 @@
     }
 
     @Test
+    public void testLongClick_falsing() {
+        mFalsingManager.setFalseLongTap(true);
+        mTile.longClick(null /* view */);
+        mTestableLooper.processAllMessages();
+        assertThat(mTile.mLongClicked).isFalse();
+
+        mFalsingManager.setFalseLongTap(false);
+        mTile.longClick(null /* view */);
+        mTestableLooper.processAllMessages();
+        assertThat(mTile.mLongClicked).isTrue();
+    }
+
+    @Test
     public void testSecondaryClick_Metrics() {
         mTile.secondaryClick(null /* view */);
         verify(mMetricsLogger).write(argThat(new TileLogMatcher(ACTION_QS_SECONDARY_CLICK)));
@@ -518,6 +531,7 @@
     }
     private static class TileImpl extends QSTileImpl<QSTile.BooleanState> {
         boolean mClicked;
+        boolean mLongClicked;
         int mRefreshes = 0;
 
         protected TileImpl(
@@ -551,6 +565,11 @@
         }
 
         @Override
+        protected void handleLongClick(@Nullable View view) {
+            mLongClicked = true;
+        }
+
+        @Override
         protected void handleUpdateState(BooleanState state, Object arg) {
             mRefreshes++;
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt
index 28aeba4..3c66772 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt
@@ -22,6 +22,7 @@
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import android.text.TextUtils
+import android.view.ContextThemeWrapper
 import android.view.View
 import android.view.accessibility.AccessibilityNodeInfo
 import android.widget.TextView
@@ -386,7 +387,11 @@
         context: Context,
         icon: QSIconView,
         collapsed: Boolean
-    ) : QSTileViewImpl(context, icon, collapsed) {
+    ) : QSTileViewImpl(
+            ContextThemeWrapper(context, R.style.Theme_SystemUI_QuickSettings),
+            icon,
+            collapsed
+    ) {
         fun changeState(state: QSTile.State) {
             handleStateChanged(state)
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt
index f0e4e3a..77a4436 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt
@@ -25,6 +25,7 @@
 import android.provider.Settings.Global.ZEN_MODE_OFF
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
+import android.view.ContextThemeWrapper
 import android.view.View
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.MetricsLogger
@@ -110,7 +111,9 @@
 
         whenever(qsHost.userId).thenReturn(DEFAULT_USER)
 
-        val wrappedContext = object : ContextWrapper(context) {
+        val wrappedContext = object : ContextWrapper(
+                ContextThemeWrapper(context, R.style.Theme_SystemUI_QuickSettings)
+        ) {
             override fun getSharedPreferences(file: File?, mode: Int): SharedPreferences {
                 return sharedPreferences
             }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FontScalingTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FontScalingTileTest.kt
index ddbfca5..cbc3553 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FontScalingTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FontScalingTileTest.kt
@@ -33,6 +33,8 @@
 import com.android.systemui.qs.QSHost
 import com.android.systemui.qs.QsEventLogger
 import com.android.systemui.qs.logging.QSLogger
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.eq
@@ -44,8 +46,11 @@
 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.anyBoolean
+import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
@@ -61,6 +66,8 @@
     @Mock private lateinit var qsLogger: QSLogger
     @Mock private lateinit var dialogLaunchAnimator: DialogLaunchAnimator
     @Mock private lateinit var uiEventLogger: QsEventLogger
+    @Mock private lateinit var userTracker: UserTracker
+    @Mock private lateinit var keyguardStateController: KeyguardStateController
 
     private lateinit var testableLooper: TestableLooper
     private lateinit var systemClock: FakeSystemClock
@@ -69,6 +76,8 @@
 
     val featureFlags = FakeFeatureFlags()
 
+    @Captor private lateinit var argumentCaptor: ArgumentCaptor<Runnable>
+
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
@@ -88,11 +97,13 @@
                 statusBarStateController,
                 activityStarter,
                 qsLogger,
+                keyguardStateController,
                 dialogLaunchAnimator,
                 FakeSettings(),
                 FakeSettings(),
                 FakeSystemClock(),
                 featureFlags,
+                userTracker,
                 backgroundDelayableExecutor,
             )
         fontScalingTile.initialize()
@@ -124,15 +135,45 @@
     }
 
     @Test
-    fun clickTile_showDialog() {
+    fun clickTile_screenUnlocked_showDialogAnimationFromView() {
+        `when`(keyguardStateController.isShowing).thenReturn(false)
         val view = View(context)
         fontScalingTile.click(view)
         testableLooper.processAllMessages()
 
+        verify(activityStarter)
+            .executeRunnableDismissingKeyguard(
+                argumentCaptor.capture(),
+                eq(null),
+                eq(true),
+                eq(true),
+                eq(false)
+            )
+        argumentCaptor.value.run()
         verify(dialogLaunchAnimator).showFromView(any(), eq(view), nullable(), anyBoolean())
     }
 
     @Test
+    fun clickTile_onLockScreen_neverShowDialogAnimationFromView() {
+        `when`(keyguardStateController.isShowing).thenReturn(true)
+        val view = View(context)
+        fontScalingTile.click(view)
+        testableLooper.processAllMessages()
+
+        verify(activityStarter)
+            .executeRunnableDismissingKeyguard(
+                argumentCaptor.capture(),
+                eq(null),
+                eq(true),
+                eq(true),
+                eq(false)
+            )
+        argumentCaptor.value.run()
+        verify(dialogLaunchAnimator, never())
+            .showFromView(any(), eq(view), nullable(), anyBoolean())
+    }
+
+    @Test
     fun getLongClickIntent_getExpectedIntent() {
         val intent: Intent? = fontScalingTile.getLongClickIntent()
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
index 6614392..5b30687 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
@@ -968,6 +968,7 @@
         assertThat(mInternetDialogController.mSubIdTelephonyManagerMap.get(SUB_ID)).isEqualTo(
                 mTelephonyManager);
         assertThat(mInternetDialogController.mSubIdTelephonyCallbackMap.get(SUB_ID)).isNotNull();
+        assertThat(mInternetDialogController.mCallback).isNotNull();
 
         mInternetDialogController.onStop();
 
@@ -980,6 +981,7 @@
         verify(mAccessPointController).removeAccessPointCallback(mInternetDialogController);
         verify(mConnectivityManager).unregisterNetworkCallback(
                 any(ConnectivityManager.NetworkCallback.class));
+        assertThat(mInternetDialogController.mCallback).isNull();
     }
 
     private String getResourcesString(String name) {
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 105387d..ed7a59e 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
@@ -27,7 +27,6 @@
 import com.android.systemui.scene.shared.model.SceneModel
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
@@ -39,8 +38,8 @@
 @RunWith(JUnit4::class)
 class QuickSettingsSceneViewModelTest : SysuiTestCase() {
 
-    private val testScope = TestScope()
-    private val utils = SceneTestUtils(this, testScope)
+    private val utils = SceneTestUtils(this)
+    private val testScope = utils.testScope
     private val sceneInteractor = utils.sceneInteractor()
     private val authenticationInteractor =
         utils.authenticationInteractor(
@@ -54,7 +53,6 @@
                     override fun create(containerName: String): LockscreenSceneInteractor {
                         return utils.lockScreenSceneInteractor(
                             authenticationInteractor = authenticationInteractor,
-                            sceneInteractor = sceneInteractor,
                             bouncerInteractor =
                                 utils.bouncerInteractor(
                                     authenticationInteractor = authenticationInteractor,
@@ -70,8 +68,8 @@
     fun onContentClicked_deviceUnlocked_switchesToGone() =
         testScope.runTest {
             val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
-            authenticationInteractor.unlockDevice()
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            utils.authenticationRepository.setUnlocked(true)
             runCurrent()
 
             underTest.onContentClicked()
@@ -83,8 +81,8 @@
     fun onContentClicked_deviceLockedSecurely_switchesToBouncer() =
         testScope.runTest {
             val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
-            authenticationInteractor.lockDevice()
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            utils.authenticationRepository.setUnlocked(false)
             runCurrent()
 
             underTest.onContentClicked()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt
index de15c77..9ce378d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt
@@ -24,6 +24,7 @@
 import com.android.systemui.scene.SceneTestUtils
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.scene.shared.model.SceneTransitionModel
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.runTest
@@ -40,7 +41,7 @@
     @Test
     fun allSceneKeys() {
         val underTest = utils.fakeSceneContainerRepository()
-        assertThat(underTest.allSceneKeys("container1"))
+        assertThat(underTest.allSceneKeys(SceneTestUtils.CONTAINER_1))
             .isEqualTo(
                 listOf(
                     SceneKey.QuickSettings,
@@ -61,10 +62,10 @@
     @Test
     fun currentScene() = runTest {
         val underTest = utils.fakeSceneContainerRepository()
-        val currentScene by collectLastValue(underTest.currentScene("container1"))
+        val currentScene by collectLastValue(underTest.currentScene(SceneTestUtils.CONTAINER_1))
         assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
 
-        underTest.setCurrentScene("container1", SceneModel(SceneKey.Shade))
+        underTest.setCurrentScene(SceneTestUtils.CONTAINER_1, SceneModel(SceneKey.Shade))
         assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Shade))
     }
 
@@ -85,26 +86,26 @@
         val underTest =
             utils.fakeSceneContainerRepository(
                 setOf(
-                    utils.fakeSceneContainerConfig("container1"),
+                    utils.fakeSceneContainerConfig(SceneTestUtils.CONTAINER_1),
                     utils.fakeSceneContainerConfig(
-                        "container2",
+                        SceneTestUtils.CONTAINER_2,
                         listOf(SceneKey.QuickSettings, SceneKey.Lockscreen)
                     ),
                 )
             )
-        underTest.setCurrentScene("container2", SceneModel(SceneKey.Shade))
+        underTest.setCurrentScene(SceneTestUtils.CONTAINER_2, SceneModel(SceneKey.Shade))
     }
 
     @Test
     fun isVisible() = runTest {
         val underTest = utils.fakeSceneContainerRepository()
-        val isVisible by collectLastValue(underTest.isVisible("container1"))
+        val isVisible by collectLastValue(underTest.isVisible(SceneTestUtils.CONTAINER_1))
         assertThat(isVisible).isTrue()
 
-        underTest.setVisible("container1", false)
+        underTest.setVisible(SceneTestUtils.CONTAINER_1, false)
         assertThat(isVisible).isFalse()
 
-        underTest.setVisible("container1", true)
+        underTest.setVisible(SceneTestUtils.CONTAINER_1, true)
         assertThat(isVisible).isTrue()
     }
 
@@ -124,13 +125,13 @@
     fun sceneTransitionProgress() = runTest {
         val underTest = utils.fakeSceneContainerRepository()
         val sceneTransitionProgress by
-            collectLastValue(underTest.sceneTransitionProgress("container1"))
+            collectLastValue(underTest.sceneTransitionProgress(SceneTestUtils.CONTAINER_1))
         assertThat(sceneTransitionProgress).isEqualTo(1f)
 
-        underTest.setSceneTransitionProgress("container1", 0.1f)
+        underTest.setSceneTransitionProgress(SceneTestUtils.CONTAINER_1, 0.1f)
         assertThat(sceneTransitionProgress).isEqualTo(0.1f)
 
-        underTest.setSceneTransitionProgress("container1", 0.9f)
+        underTest.setSceneTransitionProgress(SceneTestUtils.CONTAINER_1, 0.9f)
         assertThat(sceneTransitionProgress).isEqualTo(0.9f)
     }
 
@@ -139,4 +140,75 @@
         val underTest = utils.fakeSceneContainerRepository()
         underTest.sceneTransitionProgress("nonExistingContainer")
     }
+
+    @Test
+    fun setSceneTransition() = runTest {
+        val underTest =
+            utils.fakeSceneContainerRepository(
+                setOf(
+                    utils.fakeSceneContainerConfig(SceneTestUtils.CONTAINER_1),
+                    utils.fakeSceneContainerConfig(
+                        SceneTestUtils.CONTAINER_2,
+                        listOf(SceneKey.QuickSettings, SceneKey.Lockscreen)
+                    ),
+                )
+            )
+        val sceneTransition by
+            collectLastValue(underTest.sceneTransitions(SceneTestUtils.CONTAINER_2))
+        assertThat(sceneTransition).isNull()
+
+        underTest.setSceneTransition(
+            SceneTestUtils.CONTAINER_2,
+            SceneKey.Lockscreen,
+            SceneKey.QuickSettings
+        )
+        assertThat(sceneTransition)
+            .isEqualTo(
+                SceneTransitionModel(from = SceneKey.Lockscreen, to = SceneKey.QuickSettings)
+            )
+    }
+
+    @Test(expected = IllegalStateException::class)
+    fun setSceneTransition_noSuchContainer_throws() {
+        val underTest = utils.fakeSceneContainerRepository()
+        underTest.setSceneTransition("nonExistingContainer", SceneKey.Lockscreen, SceneKey.Shade)
+    }
+
+    @Test(expected = IllegalStateException::class)
+    fun setSceneTransition_noFromSceneInContainer_throws() {
+        val underTest =
+            utils.fakeSceneContainerRepository(
+                setOf(
+                    utils.fakeSceneContainerConfig(SceneTestUtils.CONTAINER_1),
+                    utils.fakeSceneContainerConfig(
+                        SceneTestUtils.CONTAINER_2,
+                        listOf(SceneKey.QuickSettings, SceneKey.Lockscreen)
+                    ),
+                )
+            )
+        underTest.setSceneTransition(
+            SceneTestUtils.CONTAINER_2,
+            SceneKey.Shade,
+            SceneKey.Lockscreen
+        )
+    }
+
+    @Test(expected = IllegalStateException::class)
+    fun setSceneTransition_noToSceneInContainer_throws() {
+        val underTest =
+            utils.fakeSceneContainerRepository(
+                setOf(
+                    utils.fakeSceneContainerConfig(SceneTestUtils.CONTAINER_1),
+                    utils.fakeSceneContainerConfig(
+                        SceneTestUtils.CONTAINER_2,
+                        listOf(SceneKey.QuickSettings, SceneKey.Lockscreen)
+                    ),
+                )
+            )
+        underTest.setSceneTransition(
+            SceneTestUtils.CONTAINER_2,
+            SceneKey.Shade,
+            SceneKey.Lockscreen
+        )
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
index ee4f6c2..3050c4e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
@@ -24,6 +24,7 @@
 import com.android.systemui.scene.SceneTestUtils
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.scene.shared.model.SceneTransitionModel
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.runTest
@@ -40,36 +41,63 @@
 
     @Test
     fun allSceneKeys() {
-        assertThat(underTest.allSceneKeys("container1")).isEqualTo(utils.fakeSceneKeys())
+        assertThat(underTest.allSceneKeys(SceneTestUtils.CONTAINER_1))
+            .isEqualTo(utils.fakeSceneKeys())
     }
 
     @Test
-    fun sceneTransitions() = runTest {
-        val currentScene by collectLastValue(underTest.currentScene("container1"))
+    fun currentScene() = runTest {
+        val currentScene by collectLastValue(underTest.currentScene(SceneTestUtils.CONTAINER_1))
         assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
 
-        underTest.setCurrentScene("container1", SceneModel(SceneKey.Shade))
+        underTest.setCurrentScene(SceneTestUtils.CONTAINER_1, SceneModel(SceneKey.Shade))
         assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Shade))
     }
 
     @Test
     fun sceneTransitionProgress() = runTest {
-        val progress by collectLastValue(underTest.sceneTransitionProgress("container1"))
+        val progress by
+            collectLastValue(underTest.sceneTransitionProgress(SceneTestUtils.CONTAINER_1))
         assertThat(progress).isEqualTo(1f)
 
-        underTest.setSceneTransitionProgress("container1", 0.55f)
+        underTest.setSceneTransitionProgress(SceneTestUtils.CONTAINER_1, 0.55f)
         assertThat(progress).isEqualTo(0.55f)
     }
 
     @Test
     fun isVisible() = runTest {
-        val isVisible by collectLastValue(underTest.isVisible("container1"))
+        val isVisible by collectLastValue(underTest.isVisible(SceneTestUtils.CONTAINER_1))
         assertThat(isVisible).isTrue()
 
-        underTest.setVisible("container1", false)
+        underTest.setVisible(SceneTestUtils.CONTAINER_1, false)
         assertThat(isVisible).isFalse()
 
-        underTest.setVisible("container1", true)
+        underTest.setVisible(SceneTestUtils.CONTAINER_1, true)
         assertThat(isVisible).isTrue()
     }
+
+    @Test
+    fun sceneTransitions() = runTest {
+        val transitions by collectLastValue(underTest.sceneTransitions(SceneTestUtils.CONTAINER_1))
+        assertThat(transitions).isNull()
+
+        val initialSceneKey = underTest.currentScene(SceneTestUtils.CONTAINER_1).value.key
+        underTest.setCurrentScene(SceneTestUtils.CONTAINER_1, SceneModel(SceneKey.Shade))
+        assertThat(transitions)
+            .isEqualTo(
+                SceneTransitionModel(
+                    from = initialSceneKey,
+                    to = SceneKey.Shade,
+                )
+            )
+
+        underTest.setCurrentScene(SceneTestUtils.CONTAINER_1, SceneModel(SceneKey.QuickSettings))
+        assertThat(transitions)
+            .isEqualTo(
+                SceneTransitionModel(
+                    from = SceneKey.Shade,
+                    to = SceneKey.QuickSettings,
+                )
+            )
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SystemUiDefaultSceneContainerStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SystemUiDefaultSceneContainerStartableTest.kt
new file mode 100644
index 0000000..5638d70
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SystemUiDefaultSceneContainerStartableTest.kt
@@ -0,0 +1,400 @@
+/*
+ * 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.systemui.scene.domain.startable
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.shared.model.WakeSleepReason
+import com.android.systemui.keyguard.shared.model.WakefulnessModel
+import com.android.systemui.keyguard.shared.model.WakefulnessState
+import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.shared.model.SceneContainerNames
+import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.SceneModel
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@SmallTest
+@RunWith(JUnit4::class)
+class SystemUiDefaultSceneContainerStartableTest : SysuiTestCase() {
+
+    private val utils = SceneTestUtils(this)
+    private val testScope = utils.testScope
+    private val sceneInteractor = utils.sceneInteractor()
+    private val featureFlags = utils.featureFlags
+    private val authenticationRepository = utils.authenticationRepository()
+    private val authenticationInteractor =
+        utils.authenticationInteractor(
+            repository = authenticationRepository,
+        )
+    private val keyguardRepository = utils.keyguardRepository()
+    private val keyguardInteractor =
+        utils.keyguardInteractor(
+            repository = keyguardRepository,
+        )
+
+    private val underTest =
+        SystemUiDefaultSceneContainerStartable(
+            applicationScope = testScope.backgroundScope,
+            sceneInteractor = sceneInteractor,
+            authenticationInteractor = authenticationInteractor,
+            keyguardInteractor = keyguardInteractor,
+            featureFlags = featureFlags,
+        )
+
+    @Before
+    fun setUp() {
+        prepareState()
+    }
+
+    @Test
+    fun hydrateVisibility_featureEnabled() =
+        testScope.runTest {
+            val currentSceneKey by
+                collectLastValue(
+                    sceneInteractor.currentScene(SceneContainerNames.SYSTEM_UI_DEFAULT).map {
+                        it.key
+                    }
+                )
+            val isVisible by
+                collectLastValue(sceneInteractor.isVisible(SceneContainerNames.SYSTEM_UI_DEFAULT))
+            prepareState(
+                isFeatureEnabled = true,
+                isDeviceUnlocked = true,
+                initialSceneKey = SceneKey.Gone,
+            )
+            assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
+            assertThat(isVisible).isTrue()
+
+            underTest.start()
+
+            assertThat(isVisible).isFalse()
+
+            sceneInteractor.setCurrentScene(
+                SceneContainerNames.SYSTEM_UI_DEFAULT,
+                SceneModel(SceneKey.Shade)
+            )
+            assertThat(isVisible).isTrue()
+        }
+
+    @Test
+    fun hydrateVisibility_featureDisabled() =
+        testScope.runTest {
+            val currentSceneKey by
+                collectLastValue(
+                    sceneInteractor.currentScene(SceneContainerNames.SYSTEM_UI_DEFAULT).map {
+                        it.key
+                    }
+                )
+            val isVisible by
+                collectLastValue(sceneInteractor.isVisible(SceneContainerNames.SYSTEM_UI_DEFAULT))
+            prepareState(
+                isFeatureEnabled = false,
+                isDeviceUnlocked = true,
+                initialSceneKey = SceneKey.Lockscreen,
+            )
+            assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
+            assertThat(isVisible).isTrue()
+
+            underTest.start()
+            assertThat(isVisible).isTrue()
+
+            sceneInteractor.setCurrentScene(
+                SceneContainerNames.SYSTEM_UI_DEFAULT,
+                SceneModel(SceneKey.Gone)
+            )
+            assertThat(isVisible).isTrue()
+
+            sceneInteractor.setCurrentScene(
+                SceneContainerNames.SYSTEM_UI_DEFAULT,
+                SceneModel(SceneKey.Shade)
+            )
+            assertThat(isVisible).isTrue()
+        }
+
+    @Test
+    fun switchToLockscreenWhenDeviceLocks_featureEnabled() =
+        testScope.runTest {
+            val currentSceneKey by
+                collectLastValue(
+                    sceneInteractor.currentScene(SceneContainerNames.SYSTEM_UI_DEFAULT).map {
+                        it.key
+                    }
+                )
+            prepareState(
+                isFeatureEnabled = true,
+                isDeviceUnlocked = true,
+                initialSceneKey = SceneKey.Gone,
+            )
+            assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
+            underTest.start()
+
+            authenticationRepository.setUnlocked(false)
+
+            assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
+        }
+
+    @Test
+    fun switchToLockscreenWhenDeviceLocks_featureDisabled() =
+        testScope.runTest {
+            val currentSceneKey by
+                collectLastValue(
+                    sceneInteractor.currentScene(SceneContainerNames.SYSTEM_UI_DEFAULT).map {
+                        it.key
+                    }
+                )
+            prepareState(
+                isFeatureEnabled = false,
+                isDeviceUnlocked = false,
+                initialSceneKey = SceneKey.Gone,
+            )
+            assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
+            underTest.start()
+
+            authenticationRepository.setUnlocked(false)
+
+            assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
+        }
+
+    @Test
+    fun switchFromBouncerToGoneWhenDeviceUnlocked_featureEnabled() =
+        testScope.runTest {
+            val currentSceneKey by
+                collectLastValue(
+                    sceneInteractor.currentScene(SceneContainerNames.SYSTEM_UI_DEFAULT).map {
+                        it.key
+                    }
+                )
+            prepareState(
+                isFeatureEnabled = true,
+                isDeviceUnlocked = false,
+                initialSceneKey = SceneKey.Bouncer,
+            )
+            assertThat(currentSceneKey).isEqualTo(SceneKey.Bouncer)
+            underTest.start()
+
+            authenticationRepository.setUnlocked(true)
+
+            assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
+        }
+
+    @Test
+    fun switchFromBouncerToGoneWhenDeviceUnlocked_featureDisabled() =
+        testScope.runTest {
+            val currentSceneKey by
+                collectLastValue(
+                    sceneInteractor.currentScene(SceneContainerNames.SYSTEM_UI_DEFAULT).map {
+                        it.key
+                    }
+                )
+            prepareState(
+                isFeatureEnabled = false,
+                isDeviceUnlocked = false,
+                initialSceneKey = SceneKey.Bouncer,
+            )
+            assertThat(currentSceneKey).isEqualTo(SceneKey.Bouncer)
+            underTest.start()
+
+            authenticationRepository.setUnlocked(true)
+
+            assertThat(currentSceneKey).isEqualTo(SceneKey.Bouncer)
+        }
+
+    @Test
+    fun switchFromLockscreenToGoneWhenDeviceUnlocksWithBypassOn_featureOn_bypassOn() =
+        testScope.runTest {
+            val currentSceneKey by
+                collectLastValue(
+                    sceneInteractor.currentScene(SceneContainerNames.SYSTEM_UI_DEFAULT).map {
+                        it.key
+                    }
+                )
+            prepareState(
+                isFeatureEnabled = true,
+                isBypassEnabled = true,
+                initialSceneKey = SceneKey.Lockscreen,
+            )
+            assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
+            underTest.start()
+
+            authenticationRepository.setUnlocked(true)
+
+            assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
+        }
+
+    @Test
+    fun switchFromLockscreenToGoneWhenDeviceUnlocksWithBypassOn_featureOn_bypassOff() =
+        testScope.runTest {
+            val currentSceneKey by
+                collectLastValue(
+                    sceneInteractor.currentScene(SceneContainerNames.SYSTEM_UI_DEFAULT).map {
+                        it.key
+                    }
+                )
+            prepareState(
+                isFeatureEnabled = true,
+                isBypassEnabled = false,
+                initialSceneKey = SceneKey.Lockscreen,
+            )
+            assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
+            underTest.start()
+
+            authenticationRepository.setUnlocked(true)
+
+            assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
+        }
+
+    @Test
+    fun switchFromLockscreenToGoneWhenDeviceUnlocksWithBypassOn_featureOff_bypassOn() =
+        testScope.runTest {
+            val currentSceneKey by
+                collectLastValue(
+                    sceneInteractor.currentScene(SceneContainerNames.SYSTEM_UI_DEFAULT).map {
+                        it.key
+                    }
+                )
+            prepareState(
+                isFeatureEnabled = false,
+                isBypassEnabled = true,
+                initialSceneKey = SceneKey.Lockscreen,
+            )
+            assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
+            underTest.start()
+
+            authenticationRepository.setUnlocked(true)
+
+            assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
+        }
+
+    @Test
+    fun switchToGoneWhenDeviceSleepsUnlocked_featureEnabled() =
+        testScope.runTest {
+            val currentSceneKey by
+                collectLastValue(
+                    sceneInteractor.currentScene(SceneContainerNames.SYSTEM_UI_DEFAULT).map {
+                        it.key
+                    }
+                )
+            prepareState(
+                isFeatureEnabled = true,
+                isDeviceUnlocked = true,
+                initialSceneKey = SceneKey.Shade,
+            )
+            assertThat(currentSceneKey).isEqualTo(SceneKey.Shade)
+            underTest.start()
+
+            keyguardRepository.setWakefulnessModel(ASLEEP)
+
+            assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
+        }
+
+    @Test
+    fun switchToGoneWhenDeviceSleepsUnlocked_featureDisabled() =
+        testScope.runTest {
+            val currentSceneKey by
+                collectLastValue(
+                    sceneInteractor.currentScene(SceneContainerNames.SYSTEM_UI_DEFAULT).map {
+                        it.key
+                    }
+                )
+            prepareState(
+                isFeatureEnabled = false,
+                isDeviceUnlocked = true,
+                initialSceneKey = SceneKey.Shade,
+            )
+            assertThat(currentSceneKey).isEqualTo(SceneKey.Shade)
+            underTest.start()
+
+            keyguardRepository.setWakefulnessModel(ASLEEP)
+
+            assertThat(currentSceneKey).isEqualTo(SceneKey.Shade)
+        }
+
+    @Test
+    fun switchToLockscreenWhenDeviceSleepsLocked_featureEnabled() =
+        testScope.runTest {
+            val currentSceneKey by
+                collectLastValue(
+                    sceneInteractor.currentScene(SceneContainerNames.SYSTEM_UI_DEFAULT).map {
+                        it.key
+                    }
+                )
+            prepareState(
+                isFeatureEnabled = true,
+                isDeviceUnlocked = false,
+                initialSceneKey = SceneKey.Shade,
+            )
+            assertThat(currentSceneKey).isEqualTo(SceneKey.Shade)
+            underTest.start()
+
+            keyguardRepository.setWakefulnessModel(ASLEEP)
+
+            assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
+        }
+
+    @Test
+    fun switchToLockscreenWhenDeviceSleepsLocked_featureDisabled() =
+        testScope.runTest {
+            val currentSceneKey by
+                collectLastValue(
+                    sceneInteractor.currentScene(SceneContainerNames.SYSTEM_UI_DEFAULT).map {
+                        it.key
+                    }
+                )
+            prepareState(
+                isFeatureEnabled = false,
+                isDeviceUnlocked = false,
+                initialSceneKey = SceneKey.Shade,
+            )
+            assertThat(currentSceneKey).isEqualTo(SceneKey.Shade)
+            underTest.start()
+
+            keyguardRepository.setWakefulnessModel(ASLEEP)
+
+            assertThat(currentSceneKey).isEqualTo(SceneKey.Shade)
+        }
+
+    private fun prepareState(
+        isFeatureEnabled: Boolean = true,
+        isDeviceUnlocked: Boolean = false,
+        isBypassEnabled: Boolean = false,
+        initialSceneKey: SceneKey? = null,
+    ) {
+        featureFlags.set(Flags.SCENE_CONTAINER, isFeatureEnabled)
+        authenticationRepository.setUnlocked(isDeviceUnlocked)
+        keyguardRepository.setBypassEnabled(isBypassEnabled)
+        initialSceneKey?.let {
+            sceneInteractor.setCurrentScene(SceneContainerNames.SYSTEM_UI_DEFAULT, SceneModel(it))
+        }
+    }
+
+    companion object {
+        private val ASLEEP =
+            WakefulnessModel(
+                state = WakefulnessState.ASLEEP,
+                lastWakeReason = WakeSleepReason.POWER_BUTTON,
+                lastSleepReason = WakeSleepReason.POWER_BUTTON
+            )
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
index cd2f5af..6882be7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
@@ -40,7 +40,7 @@
     private val underTest =
         SceneContainerViewModel(
             interactor = interactor,
-            containerName = "container1",
+            containerName = SceneTestUtils.CONTAINER_1,
         )
 
     @Test
@@ -48,10 +48,10 @@
         val isVisible by collectLastValue(underTest.isVisible)
         assertThat(isVisible).isTrue()
 
-        interactor.setVisible("container1", false)
+        interactor.setVisible(SceneTestUtils.CONTAINER_1, false)
         assertThat(isVisible).isFalse()
 
-        interactor.setVisible("container1", true)
+        interactor.setVisible(SceneTestUtils.CONTAINER_1, true)
         assertThat(isVisible).isTrue()
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java
index 8744aa3..59b5953 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java
@@ -16,9 +16,11 @@
 
 package com.android.systemui.screenrecord;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertThrows;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.doThrow;
@@ -32,6 +34,7 @@
 import android.os.Binder;
 import android.os.Handler;
 import android.os.RemoteException;
+import android.os.UserHandle;
 import android.testing.AndroidTestingRunner;
 
 import androidx.test.filters.SmallTest;
@@ -41,7 +44,9 @@
 import com.android.systemui.media.MediaProjectionCaptureTarget;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.settings.UserContextProvider;
+import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -77,20 +82,38 @@
     private UserContextProvider mUserContextTracker;
     @Captor
     private ArgumentCaptor<Runnable> mRunnableCaptor;
-    private KeyguardDismissUtil mKeyguardDismissUtil = new KeyguardDismissUtil() {
-        public void executeWhenUnlocked(ActivityStarter.OnDismissAction action,
-                boolean requiresShadeOpen) {
-            action.onDismiss();
-        }
-    };
+    @Mock
+    private KeyguardStateController mKeyguardStateController;
+    @Mock
+    private SysuiStatusBarStateController mStatusBarStateController;
+    @Mock
+    private ActivityStarter mActivityStarter;
+
+    private static final String PERMISSION_SELF = "com.android.systemui.permission.SELF";
+
+    private KeyguardDismissUtil mKeyguardDismissUtil = new KeyguardDismissUtil(
+            mKeyguardStateController, mStatusBarStateController, mActivityStarter);
 
     private RecordingService mRecordingService;
 
+    private class RecordingServiceTestable extends RecordingService {
+        RecordingServiceTestable(
+                RecordingController controller, Executor executor,
+                Handler handler, UiEventLogger uiEventLogger,
+                NotificationManager notificationManager,
+                UserContextProvider userContextTracker, KeyguardDismissUtil keyguardDismissUtil) {
+            super(controller, executor, handler,
+                    uiEventLogger, notificationManager, userContextTracker, keyguardDismissUtil);
+            attachBaseContext(mContext);
+        }
+    }
+
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
-        mRecordingService = Mockito.spy(new RecordingService(mController, mExecutor, mHandler,
-                mUiEventLogger, mNotificationManager, mUserContextTracker, mKeyguardDismissUtil));
+        mRecordingService = Mockito.spy(new RecordingServiceTestable(mController, mExecutor,
+                mHandler, mUiEventLogger, mNotificationManager,
+                mUserContextTracker, mKeyguardDismissUtil));
 
         // Return actual context info
         doReturn(mContext).when(mRecordingService).getApplicationContext();
@@ -160,8 +183,7 @@
         Intent startIntent = RecordingService.getStartIntent(mContext, 0, 0, false, null);
         mRecordingService.onStartCommand(startIntent, 0, 0);
 
-        // Then the state is set to not recording
-        verify(mController).updateState(false);
+        assertUpdateState(false);
     }
 
     @Test
@@ -179,7 +201,7 @@
 
         mRecordingService.onStopped();
 
-        verify(mController).updateState(false);
+        assertUpdateState(false);
     }
 
     @Test
@@ -235,8 +257,21 @@
         verify(mExecutor).execute(mRunnableCaptor.capture());
         mRunnableCaptor.getValue().run();
 
-        // Then the state is set to not recording and we cancel the notification
-        verify(mController).updateState(false);
+        assertUpdateState(false);
         verify(mNotificationManager).cancelAsUser(any(), anyInt(), any());
     }
+
+    private void assertUpdateState(boolean state) {
+        // Then the state is set to not recording, and we cancel the notification
+        // non SYSTEM user doesn't have the reference to the correct controller,
+        // so a broadcast is sent in case of non SYSTEM user.
+        if (UserHandle.USER_SYSTEM == mContext.getUserId()) {
+            verify(mController).updateState(state);
+        } else {
+            ArgumentCaptor<Intent> argumentCaptor = ArgumentCaptor.forClass(Intent.class);
+            verify(mRecordingService).sendBroadcast(argumentCaptor.capture(), eq(PERMISSION_SELF));
+            assertEquals(RecordingController.INTENT_UPDATE_STATE,
+                    argumentCaptor.getValue().getAction());
+        }
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsServiceTest.java
index 67b1099..d8897e9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsServiceTest.java
@@ -16,14 +16,17 @@
 
 package com.android.systemui.screenshot.appclips;
 
+import static android.content.Intent.CAPTURE_CONTENT_FOR_NOTE_BLOCKED_BY_ADMIN;
+import static android.content.Intent.CAPTURE_CONTENT_FOR_NOTE_FAILED;
+import static android.content.Intent.CAPTURE_CONTENT_FOR_NOTE_SUCCESS;
+import static android.content.Intent.CAPTURE_CONTENT_FOR_NOTE_WINDOW_MODE_UNSUPPORTED;
+
 import static com.android.systemui.flags.Flags.SCREENSHOT_APP_CLIPS;
 
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.app.admin.DevicePolicyManager;
@@ -31,8 +34,6 @@
 import android.content.Intent;
 import android.os.IBinder;
 import android.os.RemoteException;
-import android.os.UserHandle;
-import android.os.UserManager;
 
 import androidx.test.runner.AndroidJUnit4;
 
@@ -46,7 +47,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
-import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
 import java.util.Optional;
@@ -63,7 +63,6 @@
     @Mock private Optional<Bubbles> mOptionalBubbles;
     @Mock private Bubbles mBubbles;
     @Mock private DevicePolicyManager mDevicePolicyManager;
-    @Mock private UserManager mUserManager;
 
     private AppClipsService mAppClipsService;
 
@@ -81,51 +80,84 @@
     }
 
     @Test
+    public void flagOff_internal_shouldReturnFailed() throws RemoteException {
+        when(mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)).thenReturn(false);
+
+        assertThat(getInterfaceWithRealContext()
+                .canLaunchCaptureContentActivityForNoteInternal(FAKE_TASK_ID))
+                .isEqualTo(CAPTURE_CONTENT_FOR_NOTE_FAILED);
+    }
+
+    @Test
     public void emptyBubbles_shouldReturnFalse() throws RemoteException {
-        when(mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)).thenReturn(true);
-        when(mOptionalBubbles.isEmpty()).thenReturn(true);
+        mockForEmptyBubbles();
 
         assertThat(getInterfaceWithRealContext()
                 .canLaunchCaptureContentActivityForNote(FAKE_TASK_ID)).isFalse();
     }
 
     @Test
+    public void emptyBubbles_internal_shouldReturnFailed() throws RemoteException {
+        mockForEmptyBubbles();
+
+        assertThat(getInterfaceWithRealContext()
+                .canLaunchCaptureContentActivityForNoteInternal(FAKE_TASK_ID))
+                .isEqualTo(CAPTURE_CONTENT_FOR_NOTE_FAILED);
+    }
+
+    @Test
     public void taskIdNotAppBubble_shouldReturnFalse() throws RemoteException {
-        when(mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)).thenReturn(true);
-        when(mOptionalBubbles.isEmpty()).thenReturn(false);
-        when(mOptionalBubbles.get()).thenReturn(mBubbles);
-        when(mBubbles.isAppBubbleTaskId(eq((FAKE_TASK_ID)))).thenReturn(false);
+        mockForTaskIdNotAppBubble();
 
         assertThat(getInterfaceWithRealContext()
                 .canLaunchCaptureContentActivityForNote(FAKE_TASK_ID)).isFalse();
     }
 
     @Test
+    public void taskIdNotAppBubble_internal_shouldReturnWindowUnsupported() throws RemoteException {
+        mockForTaskIdNotAppBubble();
+
+        assertThat(getInterfaceWithRealContext()
+                .canLaunchCaptureContentActivityForNoteInternal(FAKE_TASK_ID))
+                .isEqualTo(CAPTURE_CONTENT_FOR_NOTE_WINDOW_MODE_UNSUPPORTED);
+    }
+
+    @Test
     public void dpmScreenshotBlocked_shouldReturnFalse() throws RemoteException {
-        when(mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)).thenReturn(true);
-        when(mOptionalBubbles.isEmpty()).thenReturn(false);
-        when(mOptionalBubbles.get()).thenReturn(mBubbles);
-        when(mBubbles.isAppBubbleTaskId(eq((FAKE_TASK_ID)))).thenReturn(true);
-        when(mDevicePolicyManager.getScreenCaptureDisabled(eq(null))).thenReturn(true);
+        mockForScreenshotBlocked();
 
         assertThat(getInterfaceWithRealContext()
                 .canLaunchCaptureContentActivityForNote(FAKE_TASK_ID)).isFalse();
     }
 
     @Test
+    public void dpmScreenshotBlocked_internal_shouldReturnBlockedByAdmin() throws RemoteException {
+        mockForScreenshotBlocked();
+
+        assertThat(getInterfaceWithRealContext()
+                .canLaunchCaptureContentActivityForNoteInternal(FAKE_TASK_ID))
+                .isEqualTo(CAPTURE_CONTENT_FOR_NOTE_BLOCKED_BY_ADMIN);
+    }
+
+    @Test
     public void configComponentNameNotValid_shouldReturnFalse() throws RemoteException {
-        when(mMockContext.getString(anyInt())).thenReturn(EMPTY);
-        when(mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)).thenReturn(true);
-        when(mOptionalBubbles.isEmpty()).thenReturn(false);
-        when(mOptionalBubbles.get()).thenReturn(mBubbles);
-        when(mBubbles.isAppBubbleTaskId(eq((FAKE_TASK_ID)))).thenReturn(true);
-        when(mDevicePolicyManager.getScreenCaptureDisabled(eq(null))).thenReturn(false);
+        mockForInvalidConfigComponentName();
 
         assertThat(getInterfaceWithMockContext()
                 .canLaunchCaptureContentActivityForNote(FAKE_TASK_ID)).isFalse();
     }
 
     @Test
+    public void configComponentNameNotValid_internal_shouldReturnFailed() throws RemoteException {
+        mockForInvalidConfigComponentName();
+
+        assertThat(getInterfaceWithMockContext()
+                .canLaunchCaptureContentActivityForNoteInternal(FAKE_TASK_ID))
+                .isEqualTo(CAPTURE_CONTENT_FOR_NOTE_FAILED);
+    }
+
+
+    @Test
     public void allPrerequisitesSatisfy_shouldReturnTrue() throws RemoteException {
         mockToSatisfyAllPrerequisites();
 
@@ -134,28 +166,44 @@
     }
 
     @Test
-    public void isManagedProfile_shouldUseProxyConnection() throws RemoteException {
-        when(mUserManager.isManagedProfile()).thenReturn(true);
-        when(mUserManager.getMainUser()).thenReturn(UserHandle.SYSTEM);
-        IAppClipsService service = getInterfaceWithRealContext();
-        mAppClipsService.mProxyConnectorToMainProfile =
-                Mockito.spy(mAppClipsService.mProxyConnectorToMainProfile);
+    public void allPrerequisitesSatisfy_internal_shouldReturnSuccess() throws RemoteException {
+        mockToSatisfyAllPrerequisites();
 
-        service.canLaunchCaptureContentActivityForNote(FAKE_TASK_ID);
-
-        verify(mAppClipsService.mProxyConnectorToMainProfile).postForResult(any());
+        assertThat(getInterfaceWithRealContext()
+                .canLaunchCaptureContentActivityForNoteInternal(FAKE_TASK_ID))
+                .isEqualTo(CAPTURE_CONTENT_FOR_NOTE_SUCCESS);
     }
 
-    @Test
-    public void isManagedProfile_noMainUser_shouldReturnFalse() {
-        when(mUserManager.isManagedProfile()).thenReturn(true);
-        when(mUserManager.getMainUser()).thenReturn(null);
-
-        getInterfaceWithRealContext();
-
-        assertThat(mAppClipsService.mProxyConnectorToMainProfile).isNull();
+    private void mockForEmptyBubbles() {
+        when(mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)).thenReturn(true);
+        when(mOptionalBubbles.isEmpty()).thenReturn(true);
     }
 
+    private void mockForTaskIdNotAppBubble() {
+        when(mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)).thenReturn(true);
+        when(mOptionalBubbles.isEmpty()).thenReturn(false);
+        when(mOptionalBubbles.get()).thenReturn(mBubbles);
+        when(mBubbles.isAppBubbleTaskId(eq((FAKE_TASK_ID)))).thenReturn(false);
+    }
+
+    private void mockForScreenshotBlocked() {
+        when(mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)).thenReturn(true);
+        when(mOptionalBubbles.isEmpty()).thenReturn(false);
+        when(mOptionalBubbles.get()).thenReturn(mBubbles);
+        when(mBubbles.isAppBubbleTaskId(eq((FAKE_TASK_ID)))).thenReturn(true);
+        when(mDevicePolicyManager.getScreenCaptureDisabled(eq(null))).thenReturn(true);
+    }
+
+    private void mockForInvalidConfigComponentName() {
+        when(mMockContext.getString(anyInt())).thenReturn(EMPTY);
+        when(mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)).thenReturn(true);
+        when(mOptionalBubbles.isEmpty()).thenReturn(false);
+        when(mOptionalBubbles.get()).thenReturn(mBubbles);
+        when(mBubbles.isAppBubbleTaskId(eq((FAKE_TASK_ID)))).thenReturn(true);
+        when(mDevicePolicyManager.getScreenCaptureDisabled(eq(null))).thenReturn(false);
+    }
+
+
     private void mockToSatisfyAllPrerequisites() {
         when(mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)).thenReturn(true);
         when(mOptionalBubbles.isEmpty()).thenReturn(false);
@@ -166,13 +214,13 @@
 
     private IAppClipsService getInterfaceWithRealContext() {
         mAppClipsService = new AppClipsService(getContext(), mFeatureFlags,
-                mOptionalBubbles, mDevicePolicyManager, mUserManager);
+                mOptionalBubbles, mDevicePolicyManager);
         return getInterfaceFromService(mAppClipsService);
     }
 
     private IAppClipsService getInterfaceWithMockContext() {
         mAppClipsService = new AppClipsService(mMockContext, mFeatureFlags,
-                mOptionalBubbles, mDevicePolicyManager, mUserManager);
+                mOptionalBubbles, mDevicePolicyManager);
         return getInterfaceFromService(mAppClipsService);
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivityTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivityTest.java
index e9007ff..7fad972 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivityTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivityTest.java
@@ -24,22 +24,19 @@
 import static android.content.Intent.CAPTURE_CONTENT_FOR_NOTE_WINDOW_MODE_UNSUPPORTED;
 import static android.content.Intent.EXTRA_CAPTURE_CONTENT_FOR_NOTE_STATUS_CODE;
 
-import static com.android.systemui.flags.Flags.SCREENSHOT_APP_CLIPS;
+import static com.android.internal.infra.AndroidFuture.completedFuture;
 import static com.android.systemui.screenshot.appclips.AppClipsEvent.SCREENSHOT_FOR_NOTE_TRIGGERED;
 import static com.android.systemui.screenshot.appclips.AppClipsTrampolineActivity.EXTRA_SCREENSHOT_URI;
-import static com.android.systemui.screenshot.appclips.AppClipsTrampolineActivity.EXTRA_USE_WP_USER;
 
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assume.assumeFalse;
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.app.Activity;
-import android.app.admin.DevicePolicyManager;
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
@@ -52,20 +49,22 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.UserHandle;
-import android.os.UserManager;
 import android.testing.AndroidTestingRunner;
 
 import androidx.test.rule.ActivityTestRule;
 import androidx.test.runner.intercepting.SingleActivityFactory;
 
+import com.android.internal.infra.ServiceConnector;
 import com.android.internal.logging.UiEventLogger;
+import com.android.internal.statusbar.IAppClipsService;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.broadcast.BroadcastSender;
+import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.notetask.NoteTaskController;
-import com.android.systemui.settings.UserTracker;
-import com.android.wm.shell.bubbles.Bubbles;
+
+import com.google.common.util.concurrent.MoreExecutors;
 
 import org.junit.After;
 import org.junit.Before;
@@ -75,8 +74,7 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
-import java.util.List;
-import java.util.Optional;
+import java.util.concurrent.Executor;
 
 @RunWith(AndroidTestingRunner.class)
 public final class AppClipsTrampolineActivityTest extends SysuiTestCase {
@@ -86,25 +84,19 @@
     private static final int TEST_UID = 42;
     private static final String TEST_CALLING_PACKAGE = "test-calling-package";
 
-    @Mock
-    private DevicePolicyManager mDevicePolicyManager;
-    @Mock
-    private FeatureFlags mFeatureFlags;
-    @Mock
-    private Optional<Bubbles> mOptionalBubbles;
-    @Mock
-    private Bubbles mBubbles;
+    @Mock private ServiceConnector<IAppClipsService> mServiceConnector;
     @Mock
     private NoteTaskController mNoteTaskController;
     @Mock
     private PackageManager mPackageManager;
     @Mock
-    private UserTracker mUserTracker;
-    @Mock
     private UiEventLogger mUiEventLogger;
     @Mock
-    private UserManager mUserManager;
-
+    private BroadcastSender mBroadcastSender;
+    @Background
+    private Executor mBgExecutor;
+    @Main
+    private Executor mMainExecutor;
     @Main
     private Handler mMainHandler;
 
@@ -114,9 +106,9 @@
             new SingleActivityFactory<>(AppClipsTrampolineActivityTestable.class) {
                 @Override
                 protected AppClipsTrampolineActivityTestable create(Intent unUsed) {
-                    return new AppClipsTrampolineActivityTestable(mDevicePolicyManager,
-                            mFeatureFlags, mOptionalBubbles, mNoteTaskController, mPackageManager,
-                            mUserTracker, mUiEventLogger, mUserManager, mMainHandler);
+                    return new AppClipsTrampolineActivityTestable(mServiceConnector,
+                            mNoteTaskController, mPackageManager, mUiEventLogger, mBroadcastSender,
+                            mBgExecutor, mMainExecutor, mMainHandler);
                 }
             };
 
@@ -133,6 +125,8 @@
             mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH));
 
         MockitoAnnotations.initMocks(this);
+        mBgExecutor = MoreExecutors.directExecutor();
+        mMainExecutor = MoreExecutors.directExecutor();
         mMainHandler = mContext.getMainThreadHandler();
 
         mActivityIntent = new Intent(mContext, AppClipsTrampolineActivityTestable.class);
@@ -169,19 +163,9 @@
     }
 
     @Test
-    public void flagOff_shouldFinishWithResultCancel() {
-        when(mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)).thenReturn(false);
-
-        mActivityRule.launchActivity(mActivityIntent);
-
-        assertThat(mActivityRule.getActivityResult().getResultCode())
-                .isEqualTo(Activity.RESULT_CANCELED);
-    }
-
-    @Test
-    public void bubblesEmpty_shouldFinishWithFailed() {
-        when(mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)).thenReturn(true);
-        when(mOptionalBubbles.isEmpty()).thenReturn(true);
+    public void queryService_returnedFailed_shouldFinishWithFailed() {
+        when(mServiceConnector.postForResult(any()))
+                .thenReturn(completedFuture(CAPTURE_CONTENT_FOR_NOTE_FAILED));
 
         mActivityRule.launchActivity(mActivityIntent);
 
@@ -189,14 +173,13 @@
         assertThat(actualResult.getResultCode()).isEqualTo(Activity.RESULT_OK);
         assertThat(getStatusCodeExtra(actualResult.getResultData()))
                 .isEqualTo(CAPTURE_CONTENT_FOR_NOTE_FAILED);
+        assertThat(mActivityRule.getActivity().isFinishing()).isTrue();
     }
 
     @Test
-    public void taskIdNotAppBubble_shouldFinishWithWindowModeUnsupported() {
-        when(mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)).thenReturn(true);
-        when(mOptionalBubbles.isEmpty()).thenReturn(false);
-        when(mOptionalBubbles.get()).thenReturn(mBubbles);
-        when(mBubbles.isAppBubbleTaskId(anyInt())).thenReturn(false);
+    public void queryService_returnedWindowModeUnsupported_shouldFinishWithWindowModeUnsupported() {
+        when(mServiceConnector.postForResult(any()))
+                .thenReturn(completedFuture(CAPTURE_CONTENT_FOR_NOTE_WINDOW_MODE_UNSUPPORTED));
 
         mActivityRule.launchActivity(mActivityIntent);
 
@@ -204,15 +187,13 @@
         assertThat(actualResult.getResultCode()).isEqualTo(Activity.RESULT_OK);
         assertThat(getStatusCodeExtra(actualResult.getResultData()))
                 .isEqualTo(CAPTURE_CONTENT_FOR_NOTE_WINDOW_MODE_UNSUPPORTED);
+        assertThat(mActivityRule.getActivity().isFinishing()).isTrue();
     }
 
     @Test
-    public void dpmScreenshotBlocked_shouldFinishWithBlockedByAdmin() {
-        when(mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)).thenReturn(true);
-        when(mOptionalBubbles.isEmpty()).thenReturn(false);
-        when(mOptionalBubbles.get()).thenReturn(mBubbles);
-        when(mBubbles.isAppBubbleTaskId(anyInt())).thenReturn(true);
-        when(mDevicePolicyManager.getScreenCaptureDisabled(eq(null))).thenReturn(true);
+    public void queryService_returnedScreenshotBlocked_shouldFinishWithBlockedByAdmin() {
+        when(mServiceConnector.postForResult(any()))
+                .thenReturn(completedFuture(CAPTURE_CONTENT_FOR_NOTE_BLOCKED_BY_ADMIN));
 
         mActivityRule.launchActivity(mActivityIntent);
 
@@ -220,6 +201,7 @@
         assertThat(actualResult.getResultCode()).isEqualTo(Activity.RESULT_OK);
         assertThat(getStatusCodeExtra(actualResult.getResultData()))
                 .isEqualTo(CAPTURE_CONTENT_FOR_NOTE_BLOCKED_BY_ADMIN);
+        assertThat(mActivityRule.getActivity().isFinishing()).isTrue();
     }
 
     @Test
@@ -240,6 +222,7 @@
         assertThat(actualResult.getResultCode()).isEqualTo(Activity.RESULT_OK);
         assertThat(getStatusCodeExtra(actualResult.getResultData()))
                 .isEqualTo(CAPTURE_CONTENT_FOR_NOTE_USER_CANCELED);
+        assertThat(mActivityRule.getActivity().isFinishing()).isTrue();
     }
 
     @Test
@@ -261,6 +244,7 @@
         assertThat(getStatusCodeExtra(actualResult.getResultData()))
                 .isEqualTo(CAPTURE_CONTENT_FOR_NOTE_SUCCESS);
         assertThat(actualResult.getResultData().getData()).isEqualTo(TEST_URI);
+        assertThat(mActivityRule.getActivity().isFinishing()).isTrue();
     }
 
     @Test
@@ -274,48 +258,9 @@
         verify(mUiEventLogger).log(SCREENSHOT_FOR_NOTE_TRIGGERED, TEST_UID, TEST_CALLING_PACKAGE);
     }
 
-    @Test
-    public void startAppClipsActivity_throughWPUser_shouldStartMainUserActivity()
-            throws NameNotFoundException {
-        when(mUserManager.isManagedProfile()).thenReturn(true);
-        when(mUserManager.getMainUser()).thenReturn(UserHandle.SYSTEM);
-        mockToSatisfyAllPrerequisites();
-
-        AppClipsTrampolineActivityTestable activity = mActivityRule.launchActivity(mActivityIntent);
-        waitForIdleSync();
-
-        Intent actualIntent = activity.mStartedIntent;
-        assertThat(actualIntent.getComponent()).isEqualTo(
-                new ComponentName(mContext, AppClipsTrampolineActivity.class));
-        assertThat(actualIntent.getFlags()).isEqualTo(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
-        assertThat(actualIntent.getBooleanExtra(EXTRA_USE_WP_USER, false)).isTrue();
-        assertThat(activity.mStartingUser).isEqualTo(UserHandle.SYSTEM);
-    }
-
-    @Test
-    public void startAppClipsActivity_throughWPUser_noMainUser_shouldFinishWithFailed()
-            throws NameNotFoundException {
-        when(mUserManager.isManagedProfile()).thenReturn(true);
-        when(mUserManager.getMainUser()).thenReturn(null);
-
-        mockToSatisfyAllPrerequisites();
-
-        mActivityRule.launchActivity(mActivityIntent);
-        waitForIdleSync();
-
-        ActivityResult actualResult = mActivityRule.getActivityResult();
-        assertThat(actualResult.getResultCode()).isEqualTo(Activity.RESULT_OK);
-        assertThat(getStatusCodeExtra(actualResult.getResultData()))
-                .isEqualTo(CAPTURE_CONTENT_FOR_NOTE_FAILED);
-    }
-
     private void mockToSatisfyAllPrerequisites() throws NameNotFoundException {
-        when(mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)).thenReturn(true);
-        when(mOptionalBubbles.isEmpty()).thenReturn(false);
-        when(mOptionalBubbles.get()).thenReturn(mBubbles);
-        when(mBubbles.isAppBubbleTaskId(anyInt())).thenReturn(true);
-        when(mDevicePolicyManager.getScreenCaptureDisabled(eq(null))).thenReturn(false);
-        when(mUserTracker.getUserProfiles()).thenReturn(List.of());
+        when(mServiceConnector.postForResult(any()))
+                .thenReturn(completedFuture(CAPTURE_CONTENT_FOR_NOTE_SUCCESS));
 
         ApplicationInfo testApplicationInfo = new ApplicationInfo();
         testApplicationInfo.uid = TEST_UID;
@@ -330,17 +275,14 @@
         Intent mStartedIntent;
         UserHandle mStartingUser;
 
-        public AppClipsTrampolineActivityTestable(DevicePolicyManager devicePolicyManager,
-                FeatureFlags flags,
-                Optional<Bubbles> optionalBubbles,
-                NoteTaskController noteTaskController,
-                PackageManager packageManager,
-                UserTracker userTracker,
-                UiEventLogger uiEventLogger,
-                UserManager userManager,
+        public AppClipsTrampolineActivityTestable(
+                ServiceConnector<IAppClipsService> serviceServiceConnector,
+                NoteTaskController noteTaskController, PackageManager packageManager,
+                UiEventLogger uiEventLogger, BroadcastSender broadcastSender,
+                @Background Executor bgExecutor, @Main Executor mainExecutor,
                 @Main Handler mainHandler) {
-            super(devicePolicyManager, flags, optionalBubbles, noteTaskController, packageManager,
-                    userTracker, uiEventLogger, userManager, mainHandler);
+            super(serviceServiceConnector, noteTaskController, packageManager, uiEventLogger,
+                    broadcastSender, bgExecutor, mainExecutor, mainHandler);
         }
 
         @Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessControllerTest.kt
new file mode 100644
index 0000000..2b78405
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessControllerTest.kt
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.settings.brightness
+
+import android.hardware.display.DisplayManager
+import android.os.Handler
+import android.service.vr.IVrManager
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.testing.TestableLooper.RunWithLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.settings.DisplayTracker
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.settings.FakeSettings
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+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
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+class BrightnessControllerTest : SysuiTestCase() {
+
+    private val executor = FakeExecutor(FakeSystemClock())
+    private val secureSettings = FakeSettings()
+    @Mock private lateinit var toggleSlider: ToggleSlider
+    @Mock private lateinit var userTracker: UserTracker
+    @Mock private lateinit var displayTracker: DisplayTracker
+    @Mock private lateinit var displayManager: DisplayManager
+    @Mock private lateinit var iVrManager: IVrManager
+
+    private lateinit var testableLooper: TestableLooper
+
+    private lateinit var underTest: BrightnessController
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        testableLooper = TestableLooper.get(this)
+
+        underTest =
+            BrightnessController(
+                context,
+                toggleSlider,
+                userTracker,
+                displayTracker,
+                displayManager,
+                secureSettings,
+                iVrManager,
+                executor,
+                mock(),
+                Handler(testableLooper.looper)
+            )
+    }
+
+    @Test
+    fun registerCallbacksMultipleTimes_onlyOneRegistration() {
+        val repeats = 100
+        repeat(repeats) { underTest.registerCallbacks() }
+        val messagesProcessed = testableLooper.processMessagesNonBlocking(repeats)
+
+        verify(displayTracker).addBrightnessChangeCallback(any(), any())
+        verify(iVrManager).registerListener(any())
+
+        assertThat(messagesProcessed).isEqualTo(1)
+    }
+
+    @Test
+    fun unregisterCallbacksMultipleTimes_onlyOneUnregistration() {
+        val repeats = 100
+        underTest.registerCallbacks()
+        testableLooper.processAllMessages()
+
+        repeat(repeats) { underTest.unregisterCallbacks() }
+        val messagesProcessed = testableLooper.processMessagesNonBlocking(repeats)
+
+        verify(displayTracker).removeCallback(any())
+        verify(iVrManager).unregisterListener(any())
+
+        assertThat(messagesProcessed).isEqualTo(1)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt
index 7646193..ed1397f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt
@@ -18,27 +18,30 @@
 
 import android.content.Intent
 import android.graphics.Rect
-import android.os.Handler
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import android.view.View
 import android.view.ViewGroup
+import android.view.WindowManagerPolicyConstants.EXTRA_FROM_BRIGHTNESS_KEY
 import androidx.test.filters.SmallTest
 import androidx.test.rule.ActivityTestRule
-import androidx.test.runner.intercepting.SingleActivityFactory
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.settings.FakeDisplayTracker
-import com.android.systemui.settings.UserTracker
+import com.android.systemui.activity.SingleActivityFactory
+import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper
+import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.mockito.any
+import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
-import java.util.concurrent.Executor
 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
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.eq
 import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
 
@@ -47,31 +50,29 @@
 @TestableLooper.RunWithLooper
 class BrightnessDialogTest : SysuiTestCase() {
 
-    @Mock private lateinit var userTracker: UserTracker
     @Mock private lateinit var brightnessSliderControllerFactory: BrightnessSliderController.Factory
-    @Mock private lateinit var mainExecutor: Executor
-    @Mock private lateinit var backgroundHandler: Handler
     @Mock private lateinit var brightnessSliderController: BrightnessSliderController
+    @Mock private lateinit var brightnessControllerFactory: BrightnessController.Factory
+    @Mock private lateinit var brightnessController: BrightnessController
+    @Mock private lateinit var accessibilityMgr: AccessibilityManagerWrapper
 
-    private var displayTracker = FakeDisplayTracker(mContext)
+    private val clock = FakeSystemClock()
+    private val mainExecutor = FakeExecutor(clock)
 
     @Rule
     @JvmField
     var activityRule =
         ActivityTestRule(
-            object : SingleActivityFactory<TestDialog>(TestDialog::class.java) {
-                override fun create(intent: Intent?): TestDialog {
-                    return TestDialog(
-                        userTracker,
-                        displayTracker,
-                        brightnessSliderControllerFactory,
-                        mainExecutor,
-                        backgroundHandler
-                    )
-                }
+            /* activityFactory= */ SingleActivityFactory {
+                TestDialog(
+                    brightnessSliderControllerFactory,
+                    brightnessControllerFactory,
+                    mainExecutor,
+                    accessibilityMgr
+                )
             },
-            false,
-            false
+            /* initialTouchMode= */ false,
+            /* launchActivity= */ false,
         )
 
     @Before
@@ -80,8 +81,7 @@
         `when`(brightnessSliderControllerFactory.create(any(), any()))
             .thenReturn(brightnessSliderController)
         `when`(brightnessSliderController.rootView).thenReturn(View(context))
-
-        activityRule.launchActivity(null)
+        `when`(brightnessControllerFactory.create(any())).thenReturn(brightnessController)
     }
 
     @After
@@ -91,6 +91,7 @@
 
     @Test
     fun testGestureExclusion() {
+        activityRule.launchActivity(Intent(Intent.ACTION_SHOW_BRIGHTNESS_DIALOG))
         val frame = activityRule.activity.requireViewById<View>(R.id.brightness_mirror_container)
 
         val lp = frame.layoutParams as ViewGroup.MarginLayoutParams
@@ -107,18 +108,79 @@
             .isEqualTo(Rect(-horizontalMargin, 0, frame.width + horizontalMargin, frame.height))
     }
 
+    @Test
+    fun testTimeout() {
+        `when`(
+                accessibilityMgr.getRecommendedTimeoutMillis(
+                    eq(BrightnessDialog.DIALOG_TIMEOUT_MILLIS),
+                    anyInt()
+                )
+            )
+            .thenReturn(BrightnessDialog.DIALOG_TIMEOUT_MILLIS)
+        val intent = Intent(Intent.ACTION_SHOW_BRIGHTNESS_DIALOG)
+        intent.putExtra(EXTRA_FROM_BRIGHTNESS_KEY, true)
+        activityRule.launchActivity(intent)
+
+        assertThat(activityRule.activity.isFinishing()).isFalse()
+
+        clock.advanceTime(BrightnessDialog.DIALOG_TIMEOUT_MILLIS.toLong())
+        assertThat(activityRule.activity.isFinishing()).isTrue()
+    }
+
+    @Test
+    fun testRestartTimeout() {
+        `when`(
+                accessibilityMgr.getRecommendedTimeoutMillis(
+                    eq(BrightnessDialog.DIALOG_TIMEOUT_MILLIS),
+                    anyInt()
+                )
+            )
+            .thenReturn(BrightnessDialog.DIALOG_TIMEOUT_MILLIS)
+        val intent = Intent(Intent.ACTION_SHOW_BRIGHTNESS_DIALOG)
+        intent.putExtra(EXTRA_FROM_BRIGHTNESS_KEY, true)
+        activityRule.launchActivity(intent)
+
+        assertThat(activityRule.activity.isFinishing()).isFalse()
+
+        clock.advanceTime(BrightnessDialog.DIALOG_TIMEOUT_MILLIS.toLong() / 2)
+        // Restart the timeout
+        activityRule.activity.onResume()
+
+        clock.advanceTime(BrightnessDialog.DIALOG_TIMEOUT_MILLIS.toLong() / 2)
+        // The dialog should not have disappeared yet
+        assertThat(activityRule.activity.isFinishing()).isFalse()
+
+        clock.advanceTime(BrightnessDialog.DIALOG_TIMEOUT_MILLIS.toLong() / 2)
+        assertThat(activityRule.activity.isFinishing()).isTrue()
+    }
+
+    @Test
+    fun testNoTimeoutIfNotStartedByBrightnessKey() {
+        `when`(
+                accessibilityMgr.getRecommendedTimeoutMillis(
+                    eq(BrightnessDialog.DIALOG_TIMEOUT_MILLIS),
+                    anyInt()
+                )
+            )
+            .thenReturn(BrightnessDialog.DIALOG_TIMEOUT_MILLIS)
+        activityRule.launchActivity(Intent(Intent.ACTION_SHOW_BRIGHTNESS_DIALOG))
+
+        assertThat(activityRule.activity.isFinishing()).isFalse()
+
+        clock.advanceTime(BrightnessDialog.DIALOG_TIMEOUT_MILLIS.toLong())
+        assertThat(activityRule.activity.isFinishing()).isFalse()
+    }
+
     class TestDialog(
-        userTracker: UserTracker,
-        displayTracker: FakeDisplayTracker,
         brightnessSliderControllerFactory: BrightnessSliderController.Factory,
-        mainExecutor: Executor,
-        backgroundHandler: Handler
+        brightnessControllerFactory: BrightnessController.Factory,
+        mainExecutor: DelayableExecutor,
+        accessibilityMgr: AccessibilityManagerWrapper
     ) :
         BrightnessDialog(
-            userTracker,
-            displayTracker,
             brightnessSliderControllerFactory,
+            brightnessControllerFactory,
             mainExecutor,
-            backgroundHandler
+            accessibilityMgr
         )
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt
index 3706859..0a1eca6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt
@@ -62,9 +62,9 @@
             assertThat(getConstraint(R.id.clock).layout.startToStart).isEqualTo(R.id.begin_guide)
             assertThat(getConstraint(R.id.clock).layout.horizontalBias).isEqualTo(0f)
 
-            assertThat(getConstraint(R.id.batteryRemainingIcon).layout.endToEnd)
+            assertThat(getConstraint(R.id.shade_header_system_icons).layout.endToEnd)
                 .isEqualTo(R.id.end_guide)
-            assertThat(getConstraint(R.id.batteryRemainingIcon).layout.horizontalBias)
+            assertThat(getConstraint(R.id.shade_header_system_icons).layout.horizontalBias)
                 .isEqualTo(1f)
 
             assertThat(getConstraint(R.id.privacy_container).layout.endToEnd)
@@ -95,9 +95,9 @@
             assertThat(getConstraint(R.id.date).layout.startToStart).isEqualTo(PARENT_ID)
             assertThat(getConstraint(R.id.date).layout.horizontalBias).isEqualTo(0.5f)
 
-            assertThat(getConstraint(R.id.batteryRemainingIcon).layout.endToEnd)
+            assertThat(getConstraint(R.id.shade_header_system_icons).layout.endToEnd)
                 .isEqualTo(PARENT_ID)
-            assertThat(getConstraint(R.id.batteryRemainingIcon).layout.horizontalBias)
+            assertThat(getConstraint(R.id.shade_header_system_icons).layout.horizontalBias)
                 .isEqualTo(0.5f)
 
             assertThat(getConstraint(R.id.privacy_container).layout.endToEnd)
@@ -133,18 +133,15 @@
         changes()
 
         with(qqsConstraint) {
-            assertThat(getConstraint(R.id.statusIcons).propertySet.alpha).isEqualTo(1f)
-            assertThat(getConstraint(R.id.batteryRemainingIcon).propertySet.alpha).isEqualTo(1f)
+            assertThat(systemIconsAlphaConstraint).isEqualTo(1f)
         }
 
         with(qsConstraint) {
-            assertThat(getConstraint(R.id.statusIcons).propertySet.alpha).isEqualTo(1f)
-            assertThat(getConstraint(R.id.batteryRemainingIcon).propertySet.alpha).isEqualTo(1f)
+            assertThat(systemIconsAlphaConstraint).isEqualTo(1f)
         }
 
         with(largeScreenConstraint) {
-            assertThat(getConstraint(R.id.statusIcons).propertySet.alpha).isEqualTo(1f)
-            assertThat(getConstraint(R.id.batteryRemainingIcon).propertySet.alpha).isEqualTo(1f)
+            assertThat(systemIconsAlphaConstraint).isEqualTo(1f)
         }
     }
 
@@ -155,18 +152,15 @@
         changes()
 
         with(qqsConstraint) {
-            assertThat(getConstraint(R.id.statusIcons).propertySet.alpha).isEqualTo(0f)
-            assertThat(getConstraint(R.id.batteryRemainingIcon).propertySet.alpha).isEqualTo(0f)
+            assertThat(systemIconsAlphaConstraint).isEqualTo(0f)
         }
 
         with(qsConstraint) {
-            assertThat(getConstraint(R.id.statusIcons).propertySet.alpha).isEqualTo(1f)
-            assertThat(getConstraint(R.id.batteryRemainingIcon).propertySet.alpha).isEqualTo(1f)
+            assertThat(systemIconsAlphaConstraint).isEqualTo(1f)
         }
 
         with(largeScreenConstraint) {
-            assertThat(getConstraint(R.id.statusIcons).propertySet.alpha).isEqualTo(1f)
-            assertThat(getConstraint(R.id.batteryRemainingIcon).propertySet.alpha).isEqualTo(1f)
+            assertThat(systemIconsAlphaConstraint).isEqualTo(1f)
         }
     }
 
@@ -181,12 +175,13 @@
 
         with(qqsConstraint) {
             // In this case, the date is constrained on the end by a Barrier determined by either
-            // privacy or statusIcons
+            // privacy or clickableIcons
             assertThat(getConstraint(R.id.date).layout.endToStart).isEqualTo(R.id.barrier)
-            assertThat(getConstraint(R.id.statusIcons).layout.startToEnd).isEqualTo(R.id.date)
+            assertThat(getConstraint(R.id.shade_header_system_icons).layout.startToEnd)
+                .isEqualTo(R.id.date)
             assertThat(getConstraint(R.id.privacy_container).layout.startToEnd).isEqualTo(R.id.date)
             assertThat(getConstraint(R.id.barrier).layout.mReferenceIds).asList().containsExactly(
-                R.id.statusIcons,
+                R.id.shade_header_system_icons,
                 R.id.privacy_container
             )
             assertThat(getConstraint(R.id.barrier).layout.mBarrierDirection).isEqualTo(START)
@@ -272,7 +267,7 @@
             assertThat(getConstraint(R.id.center_left).layout.guideBegin).isEqualTo(offsetFromEdge)
             assertThat(getConstraint(R.id.center_right).layout.guideEnd).isEqualTo(offsetFromEdge)
             assertThat(getConstraint(R.id.date).layout.endToStart).isEqualTo(R.id.center_left)
-            assertThat(getConstraint(R.id.statusIcons).layout.startToEnd)
+            assertThat(getConstraint(R.id.shade_header_system_icons).layout.startToEnd)
                 .isEqualTo(R.id.center_right)
             assertThat(getConstraint(R.id.privacy_container).layout.startToEnd)
                 .isEqualTo(R.id.center_right)
@@ -285,9 +280,9 @@
             assertThat(getConstraint(R.id.date).layout.endToStart).isNotEqualTo(R.id.center_left)
             assertThat(getConstraint(R.id.date).layout.endToStart).isNotEqualTo(R.id.center_right)
 
-            assertThat(getConstraint(R.id.statusIcons).layout.startToEnd)
+            assertThat(getConstraint(R.id.shade_header_system_icons).layout.startToEnd)
                 .isNotEqualTo(R.id.center_left)
-            assertThat(getConstraint(R.id.statusIcons).layout.startToEnd)
+            assertThat(getConstraint(R.id.shade_header_system_icons).layout.startToEnd)
                 .isNotEqualTo(R.id.center_right)
 
             assertThat(getConstraint(R.id.privacy_container).layout.startToEnd)
@@ -311,7 +306,7 @@
             assertThat(getConstraint(R.id.center_left).layout.guideEnd).isEqualTo(offsetFromEdge)
             assertThat(getConstraint(R.id.center_right).layout.guideBegin).isEqualTo(offsetFromEdge)
             assertThat(getConstraint(R.id.date).layout.endToStart).isEqualTo(R.id.center_right)
-            assertThat(getConstraint(R.id.statusIcons).layout.startToEnd)
+            assertThat(getConstraint(R.id.shade_header_system_icons).layout.startToEnd)
                 .isEqualTo(R.id.center_left)
             assertThat(getConstraint(R.id.privacy_container).layout.startToEnd)
                 .isEqualTo(R.id.center_left)
@@ -324,9 +319,9 @@
             assertThat(getConstraint(R.id.date).layout.endToStart).isNotEqualTo(R.id.center_left)
             assertThat(getConstraint(R.id.date).layout.endToStart).isNotEqualTo(R.id.center_right)
 
-            assertThat(getConstraint(R.id.statusIcons).layout.startToEnd)
+            assertThat(getConstraint(R.id.shade_header_system_icons).layout.startToEnd)
                 .isNotEqualTo(R.id.center_left)
-            assertThat(getConstraint(R.id.statusIcons).layout.startToEnd)
+            assertThat(getConstraint(R.id.shade_header_system_icons).layout.startToEnd)
                 .isNotEqualTo(R.id.center_right)
 
             assertThat(getConstraint(R.id.privacy_container).layout.startToEnd)
@@ -382,7 +377,8 @@
         CombinedShadeHeadersConstraintManagerImpl.emptyCutoutConstraints()()
 
         assertThat(qqsConstraint.getConstraint(R.id.date).layout.constrainedWidth).isTrue()
-        assertThat(qqsConstraint.getConstraint(R.id.statusIcons).layout.constrainedWidth).isTrue()
+        val shadeHeaderConstraint = qqsConstraint.getConstraint(R.id.shade_header_system_icons)
+        assertThat(shadeHeaderConstraint.layout.constrainedWidth).isTrue()
     }
 
     @Test
@@ -390,9 +386,13 @@
         CombinedShadeHeadersConstraintManagerImpl.centerCutoutConstraints(false, 10)()
 
         assertThat(qqsConstraint.getConstraint(R.id.date).layout.constrainedWidth).isTrue()
-        assertThat(qqsConstraint.getConstraint(R.id.statusIcons).layout.constrainedWidth).isTrue()
+        val shadeHeaderConstraint = qqsConstraint.getConstraint(R.id.shade_header_system_icons)
+        assertThat(shadeHeaderConstraint.layout.constrainedWidth).isTrue()
     }
 
+    private val ConstraintSet.systemIconsAlphaConstraint
+        get() = getConstraint(R.id.shade_header_system_icons).propertySet.alpha
+
     private operator fun ConstraintsChanges.invoke() {
         qqsConstraintsChanges?.invoke(qqsConstraint)
         qsConstraintsChanges?.invoke(qsConstraint)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/LockscreenHostedDreamGestureListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/LockscreenHostedDreamGestureListenerTest.kt
new file mode 100644
index 0000000..24d62fb
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/LockscreenHostedDreamGestureListenerTest.kt
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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
+
+import android.os.PowerManager
+import android.testing.AndroidTestingRunner
+import android.view.MotionEvent
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
+import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.power.data.repository.FakePowerRepository
+import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.phone.ScreenOffAnimationController
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth
+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
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidTestingRunner::class)
+class LockscreenHostedDreamGestureListenerTest : SysuiTestCase() {
+    @Mock private lateinit var falsingManager: FalsingManager
+    @Mock private lateinit var falsingCollector: FalsingCollector
+    @Mock private lateinit var statusBarStateController: StatusBarStateController
+    @Mock private lateinit var shadeLogger: ShadeLogger
+    @Mock private lateinit var screenOffAnimationController: ScreenOffAnimationController
+    @Mock private lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor
+
+    private val testDispatcher = UnconfinedTestDispatcher()
+    private val testScope = TestScope(testDispatcher)
+
+    private lateinit var powerRepository: FakePowerRepository
+    private lateinit var keyguardRepository: FakeKeyguardRepository
+    private lateinit var underTest: LockscreenHostedDreamGestureListener
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        powerRepository = FakePowerRepository()
+        keyguardRepository = FakeKeyguardRepository()
+
+        underTest =
+            LockscreenHostedDreamGestureListener(
+                falsingManager,
+                PowerInteractor(
+                    powerRepository,
+                    keyguardRepository,
+                    falsingCollector,
+                    screenOffAnimationController,
+                    statusBarStateController,
+                ),
+                statusBarStateController,
+                primaryBouncerInteractor,
+                keyguardRepository,
+                shadeLogger,
+            )
+        whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
+        whenever(primaryBouncerInteractor.isBouncerShowing()).thenReturn(false)
+    }
+
+    @Test
+    fun testGestureDetector_onSingleTap_whileDreaming() =
+        testScope.runTest {
+            // GIVEN device dreaming and the dream is hosted in lockscreen
+            whenever(statusBarStateController.isDreaming).thenReturn(true)
+            keyguardRepository.setIsActiveDreamLockscreenHosted(true)
+            testScope.runCurrent()
+
+            // GIVEN the falsing manager does NOT think the tap is a false tap
+            whenever(falsingManager.isFalseTap(ArgumentMatchers.anyInt())).thenReturn(false)
+
+            // WHEN there's a tap
+            underTest.onSingleTapUp(upEv)
+
+            // THEN wake up device if dreaming
+            Truth.assertThat(powerRepository.lastWakeWhy).isNotNull()
+            Truth.assertThat(powerRepository.lastWakeReason).isEqualTo(PowerManager.WAKE_REASON_TAP)
+        }
+
+    @Test
+    fun testGestureDetector_onSingleTap_notOnKeyguard() =
+        testScope.runTest {
+            // GIVEN device dreaming and the dream is hosted in lockscreen
+            whenever(statusBarStateController.isDreaming).thenReturn(true)
+            keyguardRepository.setIsActiveDreamLockscreenHosted(true)
+            testScope.runCurrent()
+
+            // GIVEN shade is open
+            whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE)
+
+            // GIVEN the falsing manager does NOT think the tap is a false tap
+            whenever(falsingManager.isFalseTap(ArgumentMatchers.anyInt())).thenReturn(false)
+
+            // WHEN there's a tap
+            underTest.onSingleTapUp(upEv)
+
+            // THEN the falsing manager never gets a call
+            verify(falsingManager, never()).isFalseTap(ArgumentMatchers.anyInt())
+        }
+
+    @Test
+    fun testGestureDetector_onSingleTap_bouncerShown() =
+        testScope.runTest {
+            // GIVEN device dreaming and the dream is hosted in lockscreen
+            whenever(statusBarStateController.isDreaming).thenReturn(true)
+            keyguardRepository.setIsActiveDreamLockscreenHosted(true)
+            testScope.runCurrent()
+
+            // GIVEN bouncer is expanded
+            whenever(primaryBouncerInteractor.isBouncerShowing()).thenReturn(true)
+
+            // GIVEN the falsing manager does NOT think the tap is a false tap
+            whenever(falsingManager.isFalseTap(ArgumentMatchers.anyInt())).thenReturn(false)
+
+            // WHEN there's a tap
+            underTest.onSingleTapUp(upEv)
+
+            // THEN the falsing manager never gets a call
+            verify(falsingManager, never()).isFalseTap(ArgumentMatchers.anyInt())
+        }
+
+    @Test
+    fun testGestureDetector_onSingleTap_falsing() =
+        testScope.runTest {
+            // GIVEN device dreaming and the dream is hosted in lockscreen
+            whenever(statusBarStateController.isDreaming).thenReturn(true)
+            keyguardRepository.setIsActiveDreamLockscreenHosted(true)
+            testScope.runCurrent()
+
+            // GIVEN the falsing manager thinks the tap is a false tap
+            whenever(falsingManager.isFalseTap(ArgumentMatchers.anyInt())).thenReturn(true)
+
+            // WHEN there's a tap
+            underTest.onSingleTapUp(upEv)
+
+            // THEN the device doesn't wake up
+            Truth.assertThat(powerRepository.lastWakeWhy).isNull()
+            Truth.assertThat(powerRepository.lastWakeReason).isNull()
+        }
+
+    @Test
+    fun testSingleTap_notDreaming_noFalsingCheck() =
+        testScope.runTest {
+            // GIVEN device not dreaming with lockscreen hosted dream
+            whenever(statusBarStateController.isDreaming).thenReturn(false)
+            keyguardRepository.setIsActiveDreamLockscreenHosted(false)
+            testScope.runCurrent()
+
+            // WHEN there's a tap
+            underTest.onSingleTapUp(upEv)
+
+            // THEN the falsing manager never gets a call
+            verify(falsingManager, never()).isFalseTap(ArgumentMatchers.anyInt())
+        }
+}
+
+private val upEv = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 0f, 0f, 0)
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 fe89a14..9188293 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -80,6 +80,7 @@
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.biometrics.AuthController;
+import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
 import com.android.systemui.classifier.FalsingCollectorFake;
 import com.android.systemui.classifier.FalsingManagerFake;
 import com.android.systemui.common.ui.view.LongPressHandlingView;
@@ -89,12 +90,12 @@
 import com.android.systemui.fragments.FragmentHostManager;
 import com.android.systemui.fragments.FragmentService;
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
-import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository;
+import com.android.systemui.keyguard.KeyguardViewConfigurator;
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;
-import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory;
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
 import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel;
 import com.android.systemui.keyguard.ui.viewmodel.GoneToDreamingTransitionViewModel;
@@ -116,6 +117,7 @@
 import com.android.systemui.qs.QSFragment;
 import com.android.systemui.screenrecord.RecordingController;
 import com.android.systemui.shade.data.repository.ShadeRepository;
+import com.android.systemui.shade.domain.interactor.ShadeInteractor;
 import com.android.systemui.shade.transition.ShadeTransitionController;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.KeyguardIndicationController;
@@ -167,6 +169,7 @@
 import com.android.systemui.statusbar.policy.KeyguardUserSwitcherView;
 import com.android.systemui.statusbar.window.StatusBarWindowStateController;
 import com.android.systemui.unfold.SysUIUnfoldComponent;
+import com.android.systemui.util.kotlin.JavaAdapter;
 import com.android.systemui.util.time.FakeSystemClock;
 import com.android.systemui.util.time.SystemClock;
 import com.android.wm.shell.animation.FlingAnimationUtils;
@@ -253,6 +256,7 @@
     @Mock protected UserManager mUserManager;
     @Mock protected UiEventLogger mUiEventLogger;
     @Mock protected LockIconViewController mLockIconViewController;
+    @Mock protected KeyguardViewConfigurator mKeyguardViewConfigurator;
     @Mock protected KeyguardMediaController mKeyguardMediaController;
     @Mock protected NavigationModeController mNavigationModeController;
     @Mock protected NavigationBarController mNavigationBarController;
@@ -309,10 +313,13 @@
     @Mock protected ActivityStarter mActivityStarter;
     @Mock protected KeyguardFaceAuthInteractor mKeyguardFaceAuthInteractor;
     @Mock protected ShadeRepository mShadeRepository;
+    @Mock private ShadeInteractor mShadeInteractor;
+    @Mock private JavaAdapter mJavaAdapter;
     @Mock private CastController mCastController;
 
     protected final int mMaxUdfpsBurnInOffsetY = 5;
     protected KeyguardBottomAreaInteractor mKeyguardBottomAreaInteractor;
+    protected FakeKeyguardRepository mFakeKeyguardRepository;
     protected KeyguardInteractor mKeyguardInteractor;
     protected NotificationPanelViewController.TouchHandler mTouchHandler;
     protected ConfigurationController mConfigurationController;
@@ -340,10 +347,12 @@
     public void setup() {
         MockitoAnnotations.initMocks(this);
         mMainDispatcher = getMainDispatcher();
-        mKeyguardBottomAreaInteractor = new KeyguardBottomAreaInteractor(
-                new FakeKeyguardRepository());
-        mKeyguardInteractor = new KeyguardInteractor(new FakeKeyguardRepository(), mCommandQueue,
-                mFeatureFlags, new FakeKeyguardBouncerRepository());
+        KeyguardInteractorFactory.WithDependencies keyguardInteractorDeps =
+                KeyguardInteractorFactory.create();
+        mFakeKeyguardRepository = keyguardInteractorDeps.getRepository();
+        mKeyguardBottomAreaInteractor = new KeyguardBottomAreaInteractor(mFakeKeyguardRepository);
+        mKeyguardInteractor = keyguardInteractorDeps.getKeyguardInteractor();
+
         SystemClock systemClock = new FakeSystemClock();
         mStatusBarStateController = new StatusBarStateControllerImpl(mUiEventLogger, mDumpManager,
                 mInteractionJankMonitor, mShadeExpansionStateManager);
@@ -594,7 +603,6 @@
                 mNotificationStackSizeCalculator,
                 mUnlockedScreenOffAnimationController,
                 mShadeTransitionController,
-                mInteractionJankMonitor,
                 systemClock,
                 mKeyguardBottomAreaViewModel,
                 mKeyguardBottomAreaInteractor,
@@ -611,6 +619,7 @@
                 mKeyuardLongPressViewModel,
                 mKeyguardInteractor,
                 mActivityStarter,
+                mKeyguardViewConfigurator,
                 mKeyguardFaceAuthInteractor);
         mNotificationPanelViewController.initDependencies(
                 mCentralSurfaces,
@@ -620,7 +629,7 @@
                 mHeadsUpManager);
         mNotificationPanelViewController.setTrackingStartedListener(() -> {});
         mNotificationPanelViewController.setOpenCloseListener(
-                new NotificationPanelViewController.OpenCloseListener() {
+                new OpenCloseListener() {
                     @Override
                     public void onClosingFinished() {}
 
@@ -682,6 +691,8 @@
                 mDumpManager,
                 mKeyguardFaceAuthInteractor,
                 mShadeRepository,
+                mShadeInteractor,
+                mJavaAdapter,
                 mCastController
         );
     }
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 987e09c..eb4ae1a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -34,6 +34,7 @@
 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;
 import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.mock;
@@ -58,6 +59,9 @@
 import com.android.keyguard.FaceAuthApiRequestReason;
 import com.android.systemui.DejankUtils;
 import com.android.systemui.R;
+import com.android.systemui.keyguard.shared.model.WakeSleepReason;
+import com.android.systemui.keyguard.shared.model.WakefulnessModel;
+import com.android.systemui.keyguard.shared.model.WakefulnessState;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.notification.row.ExpandableView;
 import com.android.systemui.statusbar.notification.row.ExpandableView.OnHeightChangedListener;
@@ -741,6 +745,47 @@
     }
 
     @Test
+    public void onQsSetExpansionHeightCalled_qsFullyExpandedOnKeyguard_showNSSL() {
+        // GIVEN
+        mStatusBarStateController.setState(KEYGUARD);
+        when(mKeyguardBypassController.getBypassEnabled()).thenReturn(false);
+        when(mQsController.getFullyExpanded()).thenReturn(true);
+        when(mQsController.getExpanded()).thenReturn(true);
+
+        // WHEN
+        int transitionDistance = mNotificationPanelViewController.getMaxPanelTransitionDistance();
+        mNotificationPanelViewController.setExpandedHeight(transitionDistance);
+
+        // THEN
+        // We are interested in the last value of the stack alpha.
+        ArgumentCaptor<Float> alphaCaptor = ArgumentCaptor.forClass(Float.class);
+        verify(mNotificationStackScrollLayoutController, atLeastOnce())
+                .setAlpha(alphaCaptor.capture());
+        assertThat(alphaCaptor.getValue()).isEqualTo(1.0f);
+    }
+
+    @Test
+    public void onQsSetExpansionHeightCalled_qsFullyExpandedOnKeyguard_hideNSSL() {
+        // GIVEN
+        mStatusBarStateController.setState(KEYGUARD);
+        when(mKeyguardBypassController.getBypassEnabled()).thenReturn(false);
+        when(mQsController.getFullyExpanded()).thenReturn(false);
+        when(mQsController.getExpanded()).thenReturn(true);
+
+        // WHEN
+        int transitionDistance = mNotificationPanelViewController
+                .getMaxPanelTransitionDistance() / 2;
+        mNotificationPanelViewController.setExpandedHeight(transitionDistance);
+
+        // THEN
+        // We are interested in the last value of the stack alpha.
+        ArgumentCaptor<Float> alphaCaptor = ArgumentCaptor.forClass(Float.class);
+        verify(mNotificationStackScrollLayoutController, atLeastOnce())
+                .setAlpha(alphaCaptor.capture());
+        assertThat(alphaCaptor.getValue()).isEqualTo(0.0f);
+    }
+
+    @Test
     public void testSwitchesToBigClockInSplitShadeOnAodAnimateDisabled() {
         when(mScreenOffAnimationController.shouldAnimateClockChange()).thenReturn(false);
         mStatusBarStateController.setState(KEYGUARD);
@@ -1180,4 +1225,43 @@
         when(mUnlockedScreenOffAnimationController.isAnimationPlaying()).thenReturn(true);
         assertThat(mNotificationPanelViewController.isExpanded()).isTrue();
     }
+
+    @Test
+    public void getFalsingThreshold_deviceNotInteractive_isQsThreshold() {
+        mFakeKeyguardRepository.setWakefulnessModel(
+                new WakefulnessModel(
+                        WakefulnessState.ASLEEP,
+                        /* lastWakeReason= */ WakeSleepReason.TAP,
+                        /* lastSleepReason= */ WakeSleepReason.POWER_BUTTON)
+        );
+        when(mQsController.getFalsingThreshold()).thenReturn(14);
+
+        assertThat(mNotificationPanelViewController.getFalsingThreshold()).isEqualTo(14);
+    }
+
+    @Test
+    public void getFalsingThreshold_lastWakeNotDueToTouch_isQsThreshold() {
+        mFakeKeyguardRepository.setWakefulnessModel(
+                new WakefulnessModel(
+                        WakefulnessState.AWAKE,
+                        /* lastWakeReason= */ WakeSleepReason.POWER_BUTTON,
+                        /* lastSleepReason= */ WakeSleepReason.POWER_BUTTON)
+        );
+        when(mQsController.getFalsingThreshold()).thenReturn(14);
+
+        assertThat(mNotificationPanelViewController.getFalsingThreshold()).isEqualTo(14);
+    }
+
+    @Test
+    public void getFalsingThreshold_lastWakeDueToTouch_greaterThanQsThreshold() {
+        mFakeKeyguardRepository.setWakefulnessModel(
+                new WakefulnessModel(
+                        WakefulnessState.AWAKE,
+                        /* lastWakeReason= */ WakeSleepReason.TAP,
+                        /* lastSleepReason= */ WakeSleepReason.POWER_BUTTON)
+        );
+        when(mQsController.getFalsingThreshold()).thenReturn(14);
+
+        assertThat(mNotificationPanelViewController.getFalsingThreshold()).isGreaterThan(14);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationQSContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationQSContainerControllerTest.kt
deleted file mode 100644
index 168cbb7..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationQSContainerControllerTest.kt
+++ /dev/null
@@ -1,512 +0,0 @@
-package com.android.systemui.shade
-
-import android.testing.AndroidTestingRunner
-import android.testing.TestableLooper
-import android.view.View
-import android.view.ViewGroup
-import android.view.WindowInsets
-import android.view.WindowManagerPolicyConstants
-import androidx.annotation.IdRes
-import androidx.constraintlayout.widget.ConstraintLayout
-import androidx.constraintlayout.widget.ConstraintSet
-import androidx.test.filters.SmallTest
-import com.android.systemui.R
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.fragments.FragmentHostManager
-import com.android.systemui.fragments.FragmentService
-import com.android.systemui.navigationbar.NavigationModeController
-import com.android.systemui.navigationbar.NavigationModeController.ModeChangedListener
-import com.android.systemui.plugins.qs.QS
-import com.android.systemui.recents.OverviewProxyService
-import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener
-import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.time.FakeSystemClock
-import com.google.common.truth.Truth.assertThat
-import java.util.function.Consumer
-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
-import org.mockito.Mockito.RETURNS_DEEP_STUBS
-import org.mockito.Mockito.any
-import org.mockito.Mockito.anyInt
-import org.mockito.Mockito.doNothing
-import org.mockito.Mockito.eq
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.never
-import org.mockito.Mockito.reset
-import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-import org.mockito.Mockito.`when` as whenever
-
-@SmallTest
-@RunWith(AndroidTestingRunner::class)
-@TestableLooper.RunWithLooper
-class NotificationQSContainerControllerTest : SysuiTestCase() {
-
-    companion object {
-        const val STABLE_INSET_BOTTOM = 100
-        const val CUTOUT_HEIGHT = 50
-        const val GESTURES_NAVIGATION = WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL
-        const val BUTTONS_NAVIGATION = WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON
-        const val NOTIFICATIONS_MARGIN = 50
-        const val SCRIM_MARGIN = 10
-        const val FOOTER_ACTIONS_INSET = 2
-        const val FOOTER_ACTIONS_PADDING = 2
-        const val FOOTER_ACTIONS_OFFSET = FOOTER_ACTIONS_INSET + FOOTER_ACTIONS_PADDING
-        const val QS_PADDING_OFFSET = SCRIM_MARGIN + FOOTER_ACTIONS_OFFSET
-    }
-
-    @Mock
-    private lateinit var navigationModeController: NavigationModeController
-    @Mock
-    private lateinit var overviewProxyService: OverviewProxyService
-    @Mock
-    private lateinit var notificationsQSContainer: NotificationsQuickSettingsContainer
-    @Mock
-    private lateinit var mShadeHeaderController: ShadeHeaderController
-    @Mock
-    private lateinit var shadeExpansionStateManager: ShadeExpansionStateManager
-    @Mock
-    private lateinit var fragmentService: FragmentService
-    @Mock
-    private lateinit var fragmentHostManager: FragmentHostManager
-    @Captor
-    lateinit var navigationModeCaptor: ArgumentCaptor<ModeChangedListener>
-    @Captor
-    lateinit var taskbarVisibilityCaptor: ArgumentCaptor<OverviewProxyListener>
-    @Captor
-    lateinit var windowInsetsCallbackCaptor: ArgumentCaptor<Consumer<WindowInsets>>
-    @Captor
-    lateinit var constraintSetCaptor: ArgumentCaptor<ConstraintSet>
-    @Captor
-    lateinit var attachStateListenerCaptor: ArgumentCaptor<View.OnAttachStateChangeListener>
-
-    private lateinit var controller: NotificationsQSContainerController
-    private lateinit var navigationModeCallback: ModeChangedListener
-    private lateinit var taskbarVisibilityCallback: OverviewProxyListener
-    private lateinit var windowInsetsCallback: Consumer<WindowInsets>
-    private lateinit var delayableExecutor: FakeExecutor
-    private lateinit var fakeSystemClock: FakeSystemClock
-
-    @Before
-    fun setup() {
-        MockitoAnnotations.initMocks(this)
-        mContext.ensureTestableResources()
-        whenever(notificationsQSContainer.context).thenReturn(mContext)
-        whenever(notificationsQSContainer.resources).thenReturn(mContext.resources)
-        whenever(fragmentService.getFragmentHostManager(any())).thenReturn(fragmentHostManager)
-        fakeSystemClock = FakeSystemClock()
-        delayableExecutor = FakeExecutor(fakeSystemClock)
-
-        controller = NotificationsQSContainerController(
-                notificationsQSContainer,
-                navigationModeController,
-                overviewProxyService,
-                mShadeHeaderController,
-                shadeExpansionStateManager,
-                fragmentService,
-                delayableExecutor
-        )
-
-        overrideResource(R.dimen.split_shade_notifications_scrim_margin_bottom, SCRIM_MARGIN)
-        overrideResource(R.dimen.notification_panel_margin_bottom, NOTIFICATIONS_MARGIN)
-        overrideResource(R.bool.config_use_split_notification_shade, false)
-        overrideResource(R.dimen.qs_footer_actions_bottom_padding, FOOTER_ACTIONS_PADDING)
-        overrideResource(R.dimen.qs_footer_action_inset, FOOTER_ACTIONS_INSET)
-        whenever(navigationModeController.addListener(navigationModeCaptor.capture()))
-                .thenReturn(GESTURES_NAVIGATION)
-        doNothing().`when`(overviewProxyService).addCallback(taskbarVisibilityCaptor.capture())
-        doNothing().`when`(notificationsQSContainer)
-                .setInsetsChangedListener(windowInsetsCallbackCaptor.capture())
-        doNothing().`when`(notificationsQSContainer).applyConstraints(constraintSetCaptor.capture())
-        doNothing().`when`(notificationsQSContainer)
-            .addOnAttachStateChangeListener(attachStateListenerCaptor.capture())
-        controller.init()
-        attachStateListenerCaptor.value.onViewAttachedToWindow(notificationsQSContainer)
-
-        navigationModeCallback = navigationModeCaptor.value
-        taskbarVisibilityCallback = taskbarVisibilityCaptor.value
-        windowInsetsCallback = windowInsetsCallbackCaptor.value
-    }
-
-    @Test
-    fun testTaskbarVisibleInSplitShade() {
-        enableSplitShade()
-
-        given(taskbarVisible = true,
-                navigationMode = GESTURES_NAVIGATION,
-                insets = windowInsets().withStableBottom())
-        then(expectedContainerPadding = 0, // taskbar should disappear when shade is expanded
-                expectedNotificationsMargin = NOTIFICATIONS_MARGIN,
-                expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET)
-
-        given(taskbarVisible = true,
-                navigationMode = BUTTONS_NAVIGATION,
-                insets = windowInsets().withStableBottom())
-        then(expectedContainerPadding = STABLE_INSET_BOTTOM,
-                expectedNotificationsMargin = NOTIFICATIONS_MARGIN,
-                expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET)
-    }
-
-    @Test
-    fun testTaskbarNotVisibleInSplitShade() {
-        // when taskbar is not visible, it means we're on the home screen
-        enableSplitShade()
-
-        given(taskbarVisible = false,
-                navigationMode = GESTURES_NAVIGATION,
-                insets = windowInsets().withStableBottom())
-        then(expectedContainerPadding = 0,
-                expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET)
-
-        given(taskbarVisible = false,
-                navigationMode = BUTTONS_NAVIGATION,
-                insets = windowInsets().withStableBottom())
-        then(expectedContainerPadding = 0, // qs goes full height as it's not obscuring nav buttons
-                expectedNotificationsMargin = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN,
-                expectedQsPadding = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET)
-    }
-
-    @Test
-    fun testTaskbarNotVisibleInSplitShadeWithCutout() {
-        enableSplitShade()
-
-        given(taskbarVisible = false,
-                navigationMode = GESTURES_NAVIGATION,
-                insets = windowInsets().withCutout())
-        then(expectedContainerPadding = CUTOUT_HEIGHT,
-            expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET)
-
-        given(taskbarVisible = false,
-                navigationMode = BUTTONS_NAVIGATION,
-                insets = windowInsets().withCutout().withStableBottom())
-        then(expectedContainerPadding = 0,
-                expectedNotificationsMargin = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN,
-                expectedQsPadding = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET)
-    }
-
-    @Test
-    fun testTaskbarVisibleInSinglePaneShade() {
-        disableSplitShade()
-
-        given(taskbarVisible = true,
-                navigationMode = GESTURES_NAVIGATION,
-                insets = windowInsets().withStableBottom())
-        then(expectedContainerPadding = 0,
-                expectedQsPadding = STABLE_INSET_BOTTOM)
-
-        given(taskbarVisible = true,
-                navigationMode = BUTTONS_NAVIGATION,
-                insets = windowInsets().withStableBottom())
-        then(expectedContainerPadding = STABLE_INSET_BOTTOM,
-                expectedQsPadding = STABLE_INSET_BOTTOM)
-    }
-
-    @Test
-    fun testTaskbarNotVisibleInSinglePaneShade() {
-        disableSplitShade()
-
-        given(taskbarVisible = false,
-                navigationMode = GESTURES_NAVIGATION,
-                insets = emptyInsets())
-        then(expectedContainerPadding = 0)
-
-        given(taskbarVisible = false,
-                navigationMode = GESTURES_NAVIGATION,
-                insets = windowInsets().withCutout().withStableBottom())
-        then(expectedContainerPadding = CUTOUT_HEIGHT, expectedQsPadding = STABLE_INSET_BOTTOM)
-
-        given(taskbarVisible = false,
-                navigationMode = BUTTONS_NAVIGATION,
-                insets = windowInsets().withStableBottom())
-        then(expectedContainerPadding = 0,
-                expectedNotificationsMargin = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN,
-                expectedQsPadding = STABLE_INSET_BOTTOM)
-    }
-
-    @Test
-    fun testDetailShowingInSinglePaneShade() {
-        disableSplitShade()
-        controller.setDetailShowing(true)
-
-        // always sets spacings to 0
-        given(taskbarVisible = false,
-                navigationMode = GESTURES_NAVIGATION,
-                insets = windowInsets().withStableBottom())
-        then(expectedContainerPadding = 0,
-                expectedNotificationsMargin = 0)
-
-        given(taskbarVisible = false,
-                navigationMode = BUTTONS_NAVIGATION,
-                insets = emptyInsets())
-        then(expectedContainerPadding = 0,
-                expectedNotificationsMargin = 0)
-    }
-
-    @Test
-    fun testDetailShowingInSplitShade() {
-        enableSplitShade()
-        controller.setDetailShowing(true)
-
-        given(taskbarVisible = false,
-                navigationMode = GESTURES_NAVIGATION,
-                insets = windowInsets().withStableBottom())
-        then(expectedContainerPadding = 0)
-
-        // should not influence spacing
-        given(taskbarVisible = false,
-                navigationMode = BUTTONS_NAVIGATION,
-                insets = emptyInsets())
-        then(expectedContainerPadding = 0)
-    }
-
-    @Test
-    fun testNotificationsMarginBottomIsUpdated() {
-        Mockito.clearInvocations(notificationsQSContainer)
-        enableSplitShade()
-        verify(notificationsQSContainer).setNotificationsMarginBottom(NOTIFICATIONS_MARGIN)
-
-        overrideResource(R.dimen.notification_panel_margin_bottom, 100)
-        disableSplitShade()
-        verify(notificationsQSContainer).setNotificationsMarginBottom(100)
-    }
-
-    @Test
-    fun testSplitShadeLayout_isAlignedToGuideline() {
-        enableSplitShade()
-        controller.updateResources()
-        assertThat(getConstraintSetLayout(R.id.qs_frame).endToEnd)
-                .isEqualTo(R.id.qs_edge_guideline)
-        assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).startToStart)
-                .isEqualTo(R.id.qs_edge_guideline)
-    }
-
-    @Test
-    fun testSinglePaneLayout_childrenHaveEqualMargins() {
-        disableSplitShade()
-        controller.updateResources()
-        val qsStartMargin = getConstraintSetLayout(R.id.qs_frame).startMargin
-        val qsEndMargin = getConstraintSetLayout(R.id.qs_frame).endMargin
-        val notifStartMargin = getConstraintSetLayout(R.id.notification_stack_scroller).startMargin
-        val notifEndMargin = getConstraintSetLayout(R.id.notification_stack_scroller).endMargin
-        assertThat(qsStartMargin == qsEndMargin &&
-                notifStartMargin == notifEndMargin &&
-                qsStartMargin == notifStartMargin
-        ).isTrue()
-    }
-
-    @Test
-    fun testSplitShadeLayout_childrenHaveInsideMarginsOfZero() {
-        enableSplitShade()
-        controller.updateResources()
-        assertThat(getConstraintSetLayout(R.id.qs_frame).endMargin).isEqualTo(0)
-        assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).startMargin)
-                .isEqualTo(0)
-    }
-
-    @Test
-    fun testSplitShadeLayout_qsFrameHasHorizontalMarginsOfZero() {
-        enableSplitShade()
-        controller.updateResources()
-        assertThat(getConstraintSetLayout(R.id.qs_frame).endMargin).isEqualTo(0)
-        assertThat(getConstraintSetLayout(R.id.qs_frame).startMargin).isEqualTo(0)
-    }
-
-    @Test
-    fun testLargeScreenLayout_qsAndNotifsTopMarginIsOfHeaderHeight() {
-        setLargeScreen()
-        val largeScreenHeaderHeight = 100
-        overrideResource(R.dimen.large_screen_shade_header_height, largeScreenHeaderHeight)
-
-        controller.updateResources()
-
-        assertThat(getConstraintSetLayout(R.id.qs_frame).topMargin)
-                .isEqualTo(largeScreenHeaderHeight)
-        assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).topMargin)
-                .isEqualTo(largeScreenHeaderHeight)
-    }
-
-    @Test
-    fun testSmallScreenLayout_qsAndNotifsTopMarginIsZero() {
-        setSmallScreen()
-        controller.updateResources()
-        assertThat(getConstraintSetLayout(R.id.qs_frame).topMargin).isEqualTo(0)
-        assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).topMargin)
-                .isEqualTo(0)
-    }
-
-    @Test
-    fun testSinglePaneShadeLayout_qsFrameHasHorizontalMarginsSetToCorrectValue() {
-        disableSplitShade()
-        controller.updateResources()
-        val notificationPanelMarginHorizontal = context.resources
-                .getDimensionPixelSize(R.dimen.notification_panel_margin_horizontal)
-        assertThat(getConstraintSetLayout(R.id.qs_frame).endMargin)
-                .isEqualTo(notificationPanelMarginHorizontal)
-        assertThat(getConstraintSetLayout(R.id.qs_frame).startMargin)
-                .isEqualTo(notificationPanelMarginHorizontal)
-    }
-
-    @Test
-    fun testSinglePaneShadeLayout_isAlignedToParent() {
-        disableSplitShade()
-        controller.updateResources()
-        assertThat(getConstraintSetLayout(R.id.qs_frame).endToEnd)
-                .isEqualTo(ConstraintSet.PARENT_ID)
-        assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).startToStart)
-                .isEqualTo(ConstraintSet.PARENT_ID)
-    }
-
-    @Test
-    fun testAllChildrenOfNotificationContainer_haveIds() {
-        // set dimen to 0 to avoid triggering updating bottom spacing
-        overrideResource(R.dimen.split_shade_notifications_scrim_margin_bottom, 0)
-        val container = NotificationsQuickSettingsContainer(context, null)
-        container.removeAllViews()
-        container.addView(newViewWithId(1))
-        container.addView(newViewWithId(View.NO_ID))
-        val controller = NotificationsQSContainerController(
-                container,
-                navigationModeController,
-                overviewProxyService,
-                mShadeHeaderController,
-                shadeExpansionStateManager,
-                fragmentService,
-                delayableExecutor
-        )
-        controller.updateConstraints()
-
-        assertThat(container.getChildAt(0).id).isEqualTo(1)
-        assertThat(container.getChildAt(1).id).isNotEqualTo(View.NO_ID)
-    }
-
-    @Test
-    fun testWindowInsetDebounce() {
-        disableSplitShade()
-
-        given(taskbarVisible = false,
-            navigationMode = GESTURES_NAVIGATION,
-            insets = emptyInsets(),
-            applyImmediately = false)
-        fakeSystemClock.advanceTime(INSET_DEBOUNCE_MILLIS / 2)
-        windowInsetsCallback.accept(windowInsets().withStableBottom())
-
-        delayableExecutor.advanceClockToLast()
-        delayableExecutor.runAllReady()
-
-        verify(notificationsQSContainer, never()).setQSContainerPaddingBottom(0)
-        verify(notificationsQSContainer).setQSContainerPaddingBottom(STABLE_INSET_BOTTOM)
-    }
-
-    @Test
-    fun testStartCustomizingWithDuration() {
-        controller.setCustomizerShowing(true, 100L)
-        verify(mShadeHeaderController).startCustomizingAnimation(true, 100L)
-    }
-
-    @Test
-    fun testEndCustomizingWithDuration() {
-        controller.setCustomizerShowing(true, 0L) // Only tracks changes
-        reset(mShadeHeaderController)
-
-        controller.setCustomizerShowing(false, 100L)
-        verify(mShadeHeaderController).startCustomizingAnimation(false, 100L)
-    }
-
-    @Test
-    fun testTagListenerAdded() {
-        verify(fragmentHostManager).addTagListener(eq(QS.TAG), eq(notificationsQSContainer))
-    }
-
-    @Test
-    fun testTagListenerRemoved() {
-        attachStateListenerCaptor.value.onViewDetachedFromWindow(notificationsQSContainer)
-        verify(fragmentHostManager).removeTagListener(eq(QS.TAG), eq(notificationsQSContainer))
-    }
-
-    private fun disableSplitShade() {
-        setSplitShadeEnabled(false)
-    }
-
-    private fun enableSplitShade() {
-        setSplitShadeEnabled(true)
-    }
-
-    private fun setSplitShadeEnabled(enabled: Boolean) {
-        overrideResource(R.bool.config_use_split_notification_shade, enabled)
-        controller.updateResources()
-    }
-
-    private fun setSmallScreen() {
-        setLargeScreenEnabled(false)
-    }
-
-    private fun setLargeScreen() {
-        setLargeScreenEnabled(true)
-    }
-
-    private fun setLargeScreenEnabled(enabled: Boolean) {
-        overrideResource(R.bool.config_use_large_screen_shade_header, enabled)
-    }
-
-    private fun given(
-        taskbarVisible: Boolean,
-        navigationMode: Int,
-        insets: WindowInsets,
-        applyImmediately: Boolean = true
-    ) {
-        Mockito.clearInvocations(notificationsQSContainer)
-        taskbarVisibilityCallback.onTaskbarStatusUpdated(taskbarVisible, false)
-        navigationModeCallback.onNavigationModeChanged(navigationMode)
-        windowInsetsCallback.accept(insets)
-        if (applyImmediately) {
-            delayableExecutor.advanceClockToLast()
-            delayableExecutor.runAllReady()
-        }
-    }
-
-    fun then(
-        expectedContainerPadding: Int,
-        expectedNotificationsMargin: Int = NOTIFICATIONS_MARGIN,
-        expectedQsPadding: Int = 0
-    ) {
-        verify(notificationsQSContainer)
-                .setPadding(anyInt(), anyInt(), anyInt(), eq(expectedContainerPadding))
-        verify(notificationsQSContainer).setNotificationsMarginBottom(expectedNotificationsMargin)
-        verify(notificationsQSContainer)
-                    .setQSContainerPaddingBottom(expectedQsPadding)
-        Mockito.clearInvocations(notificationsQSContainer)
-    }
-
-    private fun windowInsets() = mock(WindowInsets::class.java, RETURNS_DEEP_STUBS)
-
-    private fun emptyInsets() = mock(WindowInsets::class.java)
-
-    private fun WindowInsets.withCutout(): WindowInsets {
-        whenever(displayCutout.safeInsetBottom).thenReturn(CUTOUT_HEIGHT)
-        return this
-    }
-
-    private fun WindowInsets.withStableBottom(): WindowInsets {
-        whenever(stableInsetBottom).thenReturn(STABLE_INSET_BOTTOM)
-        return this
-    }
-
-    private fun getConstraintSetLayout(@IdRes id: Int): ConstraintSet.Layout {
-        return constraintSetCaptor.value.getConstraint(id).layout
-    }
-
-    private fun newViewWithId(id: Int): View {
-        val view = View(mContext)
-        view.id = id
-        val layoutParams = ConstraintLayout.LayoutParams(
-                ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)
-        // required as cloning ConstraintSet fails if view doesn't have layout params
-        view.layoutParams = layoutParams
-        return view
-    }
-}
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 526dc8d..cde6ac0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
@@ -126,7 +126,7 @@
                     }
             };
         mNotificationShadeWindowController.setScrimsVisibilityListener((visibility) -> {});
-        mNotificationShadeWindowController.setNotificationShadeView(mNotificationShadeWindowView);
+        mNotificationShadeWindowController.setWindowRootView(mNotificationShadeWindowView);
 
         mNotificationShadeWindowController.attach();
         verify(mWindowManager).addView(eq(mNotificationShadeWindowView), any());
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 3995e71..2a398c55 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -21,26 +21,35 @@
 import android.view.MotionEvent
 import android.view.ViewGroup
 import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardMessageAreaController
 import com.android.keyguard.KeyguardSecurityContainerController
 import com.android.keyguard.LockIconViewController
 import com.android.keyguard.dagger.KeyguardBouncerComponent
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.back.domain.interactor.BackActionInteractor
+import com.android.systemui.bouncer.data.factory.BouncerMessageFactory
+import com.android.systemui.bouncer.data.repository.FakeBouncerMessageRepository
+import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor
+import com.android.systemui.bouncer.domain.interactor.CountDownTimerUtil
+import com.android.systemui.bouncer.ui.viewmodel.KeyguardBouncerViewModel
 import com.android.systemui.classifier.FalsingCollectorFake
 import com.android.systemui.classifier.FalsingManagerFake
 import com.android.systemui.dock.DockManager
+import com.android.systemui.dump.logcatLogBuffer
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
 import com.android.systemui.keyguard.shared.model.TransitionStep
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel
 import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel
+import com.android.systemui.log.BouncerLogger
 import com.android.systemui.multishade.data.remoteproxy.MultiShadeInputProxy
 import com.android.systemui.multishade.data.repository.MultiShadeRepository
 import com.android.systemui.multishade.domain.interactor.MultiShadeInteractor
 import com.android.systemui.multishade.domain.interactor.MultiShadeMotionEventInteractor
+import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.shade.NotificationShadeWindowView.InteractionEventHandler
 import com.android.systemui.statusbar.LockscreenShadeTransitionController
 import com.android.systemui.statusbar.NotificationInsetsController
@@ -54,6 +63,7 @@
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
 import com.android.systemui.statusbar.window.StatusBarWindowStateController
 import com.android.systemui.unfold.UnfoldTransitionProgressProvider
+import com.android.systemui.user.data.repository.FakeUserRepository
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
@@ -70,9 +80,9 @@
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
 import org.mockito.MockitoAnnotations
 import java.util.Optional
+import org.mockito.Mockito.`when` as whenever
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
@@ -83,6 +93,8 @@
     @Mock private lateinit var view: NotificationShadeWindowView
     @Mock private lateinit var sysuiStatusBarStateController: SysuiStatusBarStateController
     @Mock private lateinit var centralSurfaces: CentralSurfaces
+    @Mock private lateinit var backActionInteractor: BackActionInteractor
+    @Mock private lateinit var powerInteractor: PowerInteractor
     @Mock private lateinit var dockManager: DockManager
     @Mock private lateinit var notificationPanelViewController: NotificationPanelViewController
     @Mock private lateinit var notificationShadeDepthController: NotificationShadeDepthController
@@ -99,6 +111,8 @@
     @Mock private lateinit var lockIconViewController: LockIconViewController
     @Mock private lateinit var phoneStatusBarViewController: PhoneStatusBarViewController
     @Mock private lateinit var pulsingGestureListener: PulsingGestureListener
+    @Mock
+    private lateinit var mLockscreenHostedDreamGestureListener: LockscreenHostedDreamGestureListener
     @Mock private lateinit var notificationInsetsController: NotificationInsetsController
     @Mock lateinit var keyguardBouncerComponentFactory: KeyguardBouncerComponent.Factory
     @Mock lateinit var keyguardBouncerComponent: KeyguardBouncerComponent
@@ -134,6 +148,8 @@
         featureFlags.set(Flags.TRACKPAD_GESTURE_FEATURES, false)
         featureFlags.set(Flags.DUAL_SHADE, false)
         featureFlags.set(Flags.SPLIT_SHADE_SUBPIXEL_OPTIMIZATION, true)
+        featureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true)
+        featureFlags.set(Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED, false)
 
         val inputProxy = MultiShadeInputProxy()
         testScope = TestScope()
@@ -162,14 +178,18 @@
                 statusBarWindowStateController,
                 lockIconViewController,
                 centralSurfaces,
+                backActionInteractor,
+                powerInteractor,
                 notificationShadeWindowController,
                 unfoldTransitionProgressProvider,
                 keyguardUnlockAnimationController,
                 notificationInsetsController,
                 ambientState,
                 pulsingGestureListener,
+                mLockscreenHostedDreamGestureListener,
                 keyguardBouncerViewModel,
                 keyguardBouncerComponentFactory,
+                mock(KeyguardMessageAreaController.Factory::class.java),
                 keyguardTransitionInteractor,
                 primaryBouncerToGoneTransitionViewModel,
                 featureFlags,
@@ -182,14 +202,17 @@
                         multiShadeInteractor = multiShadeInteractor,
                         featureFlags = featureFlags,
                         keyguardTransitionInteractor =
-                            KeyguardTransitionInteractor(
-                                repository = FakeKeyguardTransitionRepository(),
-                                scope = testScope.backgroundScope
-                            ),
+                            KeyguardTransitionInteractorFactory.create(
+                                    scope = TestScope().backgroundScope,
+                            ).keyguardTransitionInteractor,
                         falsingManager = FalsingManagerFake(),
                         shadeController = shadeController,
                     )
                 },
+                BouncerMessageInteractor(FakeBouncerMessageRepository(),
+                        mock(BouncerMessageFactory::class.java),
+                        FakeUserRepository(), CountDownTimerUtil(), featureFlags),
+                BouncerLogger(logcatLogBuffer("BouncerLog"))
             )
         underTest.setupExpandedStatusBar()
 
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 fe95742..d9eb9b9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
@@ -21,25 +21,34 @@
 import android.view.MotionEvent
 import android.widget.FrameLayout
 import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardMessageAreaController
 import com.android.keyguard.KeyguardSecurityContainerController
 import com.android.keyguard.LockIconViewController
 import com.android.keyguard.dagger.KeyguardBouncerComponent
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.back.domain.interactor.BackActionInteractor
+import com.android.systemui.bouncer.data.factory.BouncerMessageFactory
+import com.android.systemui.bouncer.data.repository.FakeBouncerMessageRepository
+import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor
+import com.android.systemui.bouncer.domain.interactor.CountDownTimerUtil
+import com.android.systemui.bouncer.ui.viewmodel.KeyguardBouncerViewModel
 import com.android.systemui.classifier.FalsingCollectorFake
 import com.android.systemui.classifier.FalsingManagerFake
 import com.android.systemui.dock.DockManager
+import com.android.systemui.dump.logcatLogBuffer
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
 import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel
+import com.android.systemui.log.BouncerLogger
 import com.android.systemui.multishade.data.remoteproxy.MultiShadeInputProxy
 import com.android.systemui.multishade.data.repository.MultiShadeRepository
 import com.android.systemui.multishade.domain.interactor.MultiShadeInteractor
 import com.android.systemui.multishade.domain.interactor.MultiShadeMotionEventInteractor
+import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.shade.NotificationShadeWindowView.InteractionEventHandler
 import com.android.systemui.statusbar.DragDownHelper
 import com.android.systemui.statusbar.LockscreenShadeTransitionController
@@ -54,6 +63,7 @@
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
 import com.android.systemui.statusbar.window.StatusBarWindowStateController
 import com.android.systemui.unfold.UnfoldTransitionProgressProvider
+import com.android.systemui.user.data.repository.FakeUserRepository
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
@@ -70,6 +80,7 @@
 import org.mockito.ArgumentCaptor
 import org.mockito.Captor
 import org.mockito.Mock
+import org.mockito.Mockito
 import org.mockito.Mockito.spy
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
@@ -84,6 +95,8 @@
     @Mock private lateinit var statusBarStateController: SysuiStatusBarStateController
     @Mock private lateinit var shadeController: ShadeController
     @Mock private lateinit var centralSurfaces: CentralSurfaces
+    @Mock private lateinit var backActionInteractor: BackActionInteractor
+    @Mock private lateinit var powerInteractor: PowerInteractor
     @Mock private lateinit var dockManager: DockManager
     @Mock private lateinit var notificationPanelViewController: NotificationPanelViewController
     @Mock private lateinit var notificationStackScrollLayout: NotificationStackScrollLayout
@@ -100,6 +113,8 @@
     @Mock private lateinit var keyguardUnlockAnimationController: KeyguardUnlockAnimationController
     @Mock private lateinit var ambientState: AmbientState
     @Mock private lateinit var pulsingGestureListener: PulsingGestureListener
+    @Mock
+    private lateinit var mLockscreenHostedDreamGestureListener: LockscreenHostedDreamGestureListener
     @Mock private lateinit var keyguardBouncerViewModel: KeyguardBouncerViewModel
     @Mock private lateinit var keyguardBouncerComponentFactory: KeyguardBouncerComponent.Factory
     @Mock private lateinit var keyguardBouncerComponent: KeyguardBouncerComponent
@@ -147,6 +162,8 @@
         featureFlags.set(Flags.TRACKPAD_GESTURE_FEATURES, false)
         featureFlags.set(Flags.DUAL_SHADE, false)
         featureFlags.set(Flags.SPLIT_SHADE_SUBPIXEL_OPTIMIZATION, true)
+        featureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true)
+        featureFlags.set(Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED, false)
         val inputProxy = MultiShadeInputProxy()
         testScope = TestScope()
         val multiShadeInteractor =
@@ -174,14 +191,18 @@
                 statusBarWindowStateController,
                 lockIconViewController,
                 centralSurfaces,
+                backActionInteractor,
+                powerInteractor,
                 notificationShadeWindowController,
                 unfoldTransitionProgressProvider,
                 keyguardUnlockAnimationController,
                 notificationInsetsController,
                 ambientState,
                 pulsingGestureListener,
+                mLockscreenHostedDreamGestureListener,
                 keyguardBouncerViewModel,
                 keyguardBouncerComponentFactory,
+                Mockito.mock(KeyguardMessageAreaController.Factory::class.java),
                 keyguardTransitionInteractor,
                 primaryBouncerToGoneTransitionViewModel,
                 featureFlags,
@@ -194,14 +215,22 @@
                         multiShadeInteractor = multiShadeInteractor,
                         featureFlags = featureFlags,
                         keyguardTransitionInteractor =
-                            KeyguardTransitionInteractor(
-                                repository = FakeKeyguardTransitionRepository(),
-                                scope = testScope.backgroundScope
-                            ),
+                            KeyguardTransitionInteractorFactory.create(
+                                    scope = TestScope().backgroundScope,
+                                )
+                                .keyguardTransitionInteractor,
                         falsingManager = FalsingManagerFake(),
                         shadeController = shadeController,
                     )
                 },
+                BouncerMessageInteractor(
+                    FakeBouncerMessageRepository(),
+                    Mockito.mock(BouncerMessageFactory::class.java),
+                    FakeUserRepository(),
+                    CountDownTimerUtil(),
+                    featureFlags
+                ),
+                BouncerLogger(logcatLogBuffer("BouncerLog"))
             )
 
         controller.setupExpandedStatusBar()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt
new file mode 100644
index 0000000..2bc112d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt
@@ -0,0 +1,596 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.testing.TestableResources
+import android.view.View
+import android.view.ViewGroup
+import android.view.WindowInsets
+import android.view.WindowManagerPolicyConstants
+import androidx.annotation.IdRes
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.constraintlayout.widget.ConstraintSet
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.fragments.FragmentHostManager
+import com.android.systemui.fragments.FragmentService
+import com.android.systemui.navigationbar.NavigationModeController
+import com.android.systemui.navigationbar.NavigationModeController.ModeChangedListener
+import com.android.systemui.plugins.qs.QS
+import com.android.systemui.recents.OverviewProxyService
+import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import java.util.function.Consumer
+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
+import org.mockito.Mockito.RETURNS_DEEP_STUBS
+import org.mockito.Mockito.any
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.doNothing
+import org.mockito.Mockito.eq
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+/** Uses Flags.MIGRATE_NSSL set to false. If all goes well, this set of tests will be deleted. */
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+@SmallTest
+class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() {
+
+    @Mock lateinit var view: NotificationsQuickSettingsContainer
+    @Mock lateinit var navigationModeController: NavigationModeController
+    @Mock lateinit var overviewProxyService: OverviewProxyService
+    @Mock lateinit var shadeHeaderController: ShadeHeaderController
+    @Mock lateinit var shadeExpansionStateManager: ShadeExpansionStateManager
+    @Mock lateinit var fragmentService: FragmentService
+    @Mock lateinit var fragmentHostManager: FragmentHostManager
+    @Mock
+    lateinit var notificationStackScrollLayoutController: NotificationStackScrollLayoutController
+
+    @Captor lateinit var navigationModeCaptor: ArgumentCaptor<ModeChangedListener>
+    @Captor lateinit var taskbarVisibilityCaptor: ArgumentCaptor<OverviewProxyListener>
+    @Captor lateinit var windowInsetsCallbackCaptor: ArgumentCaptor<Consumer<WindowInsets>>
+    @Captor lateinit var constraintSetCaptor: ArgumentCaptor<ConstraintSet>
+    @Captor lateinit var attachStateListenerCaptor: ArgumentCaptor<View.OnAttachStateChangeListener>
+
+    lateinit var underTest: NotificationsQSContainerController
+
+    private lateinit var fakeResources: TestableResources
+    private lateinit var featureFlags: FakeFeatureFlags
+    private lateinit var navigationModeCallback: ModeChangedListener
+    private lateinit var taskbarVisibilityCallback: OverviewProxyListener
+    private lateinit var windowInsetsCallback: Consumer<WindowInsets>
+    private lateinit var fakeSystemClock: FakeSystemClock
+    private lateinit var delayableExecutor: FakeExecutor
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        fakeSystemClock = FakeSystemClock()
+        delayableExecutor = FakeExecutor(fakeSystemClock)
+        featureFlags = FakeFeatureFlags().apply { set(Flags.MIGRATE_NSSL, false) }
+        mContext.ensureTestableResources()
+        whenever(view.context).thenReturn(mContext)
+        whenever(view.resources).thenReturn(mContext.resources)
+
+        whenever(fragmentService.getFragmentHostManager(any())).thenReturn(fragmentHostManager)
+
+        underTest =
+            NotificationsQSContainerController(
+                view,
+                navigationModeController,
+                overviewProxyService,
+                shadeHeaderController,
+                shadeExpansionStateManager,
+                fragmentService,
+                delayableExecutor,
+                featureFlags,
+                notificationStackScrollLayoutController,
+            )
+
+        overrideResource(R.dimen.split_shade_notifications_scrim_margin_bottom, SCRIM_MARGIN)
+        overrideResource(R.dimen.notification_panel_margin_bottom, NOTIFICATIONS_MARGIN)
+        overrideResource(R.bool.config_use_split_notification_shade, false)
+        overrideResource(R.dimen.qs_footer_actions_bottom_padding, FOOTER_ACTIONS_PADDING)
+        overrideResource(R.dimen.qs_footer_action_inset, FOOTER_ACTIONS_INSET)
+        whenever(navigationModeController.addListener(navigationModeCaptor.capture()))
+            .thenReturn(GESTURES_NAVIGATION)
+        doNothing().`when`(overviewProxyService).addCallback(taskbarVisibilityCaptor.capture())
+        doNothing().`when`(view).setInsetsChangedListener(windowInsetsCallbackCaptor.capture())
+        doNothing().`when`(view).applyConstraints(constraintSetCaptor.capture())
+        doNothing().`when`(view).addOnAttachStateChangeListener(attachStateListenerCaptor.capture())
+        underTest.init()
+        attachStateListenerCaptor.value.onViewAttachedToWindow(view)
+
+        navigationModeCallback = navigationModeCaptor.value
+        taskbarVisibilityCallback = taskbarVisibilityCaptor.value
+        windowInsetsCallback = windowInsetsCallbackCaptor.value
+
+        Mockito.clearInvocations(view)
+    }
+
+    @Test
+    fun testSmallScreen_updateResources_splitShadeHeightIsSet() {
+        overrideResource(R.bool.config_use_large_screen_shade_header, false)
+        overrideResource(R.dimen.qs_header_height, 1)
+        overrideResource(R.dimen.large_screen_shade_header_height, 2)
+
+        underTest.updateResources()
+
+        val captor = ArgumentCaptor.forClass(ConstraintSet::class.java)
+        verify(view).applyConstraints(capture(captor))
+        assertThat(captor.value.getHeight(R.id.split_shade_status_bar)).isEqualTo(1)
+    }
+
+    @Test
+    fun testLargeScreen_updateResources_splitShadeHeightIsSet() {
+        overrideResource(R.bool.config_use_large_screen_shade_header, true)
+        overrideResource(R.dimen.qs_header_height, 1)
+        overrideResource(R.dimen.large_screen_shade_header_height, 2)
+
+        underTest.updateResources()
+
+        val captor = ArgumentCaptor.forClass(ConstraintSet::class.java)
+        verify(view).applyConstraints(capture(captor))
+        assertThat(captor.value.getHeight(R.id.split_shade_status_bar)).isEqualTo(2)
+    }
+
+    @Test
+    fun testTaskbarVisibleInSplitShade() {
+        enableSplitShade()
+
+        given(
+            taskbarVisible = true,
+            navigationMode = GESTURES_NAVIGATION,
+            insets = windowInsets().withStableBottom()
+        )
+        then(
+            expectedContainerPadding = 0, // taskbar should disappear when shade is expanded
+            expectedNotificationsMargin = NOTIFICATIONS_MARGIN,
+            expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET
+        )
+
+        given(
+            taskbarVisible = true,
+            navigationMode = BUTTONS_NAVIGATION,
+            insets = windowInsets().withStableBottom()
+        )
+        then(
+            expectedContainerPadding = STABLE_INSET_BOTTOM,
+            expectedNotificationsMargin = NOTIFICATIONS_MARGIN,
+            expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET
+        )
+    }
+
+    @Test
+    fun testTaskbarNotVisibleInSplitShade() {
+        // when taskbar is not visible, it means we're on the home screen
+        enableSplitShade()
+
+        given(
+            taskbarVisible = false,
+            navigationMode = GESTURES_NAVIGATION,
+            insets = windowInsets().withStableBottom()
+        )
+        then(
+            expectedContainerPadding = 0,
+            expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET
+        )
+
+        given(
+            taskbarVisible = false,
+            navigationMode = BUTTONS_NAVIGATION,
+            insets = windowInsets().withStableBottom()
+        )
+        then(
+            expectedContainerPadding = 0, // qs goes full height as it's not obscuring nav buttons
+            expectedNotificationsMargin = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN,
+            expectedQsPadding = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET
+        )
+    }
+
+    @Test
+    fun testTaskbarNotVisibleInSplitShadeWithCutout() {
+        enableSplitShade()
+
+        given(
+            taskbarVisible = false,
+            navigationMode = GESTURES_NAVIGATION,
+            insets = windowInsets().withCutout()
+        )
+        then(
+            expectedContainerPadding = CUTOUT_HEIGHT,
+            expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET
+        )
+
+        given(
+            taskbarVisible = false,
+            navigationMode = BUTTONS_NAVIGATION,
+            insets = windowInsets().withCutout().withStableBottom()
+        )
+        then(
+            expectedContainerPadding = 0,
+            expectedNotificationsMargin = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN,
+            expectedQsPadding = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET
+        )
+    }
+
+    @Test
+    fun testTaskbarVisibleInSinglePaneShade() {
+        disableSplitShade()
+
+        given(
+            taskbarVisible = true,
+            navigationMode = GESTURES_NAVIGATION,
+            insets = windowInsets().withStableBottom()
+        )
+        then(expectedContainerPadding = 0, expectedQsPadding = STABLE_INSET_BOTTOM)
+
+        given(
+            taskbarVisible = true,
+            navigationMode = BUTTONS_NAVIGATION,
+            insets = windowInsets().withStableBottom()
+        )
+        then(
+            expectedContainerPadding = STABLE_INSET_BOTTOM,
+            expectedQsPadding = STABLE_INSET_BOTTOM
+        )
+    }
+
+    @Test
+    fun testTaskbarNotVisibleInSinglePaneShade() {
+        disableSplitShade()
+
+        given(taskbarVisible = false, navigationMode = GESTURES_NAVIGATION, insets = emptyInsets())
+        then(expectedContainerPadding = 0)
+
+        given(
+            taskbarVisible = false,
+            navigationMode = GESTURES_NAVIGATION,
+            insets = windowInsets().withCutout().withStableBottom()
+        )
+        then(expectedContainerPadding = CUTOUT_HEIGHT, expectedQsPadding = STABLE_INSET_BOTTOM)
+
+        given(
+            taskbarVisible = false,
+            navigationMode = BUTTONS_NAVIGATION,
+            insets = windowInsets().withStableBottom()
+        )
+        then(
+            expectedContainerPadding = 0,
+            expectedNotificationsMargin = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN,
+            expectedQsPadding = STABLE_INSET_BOTTOM
+        )
+    }
+
+    @Test
+    fun testDetailShowingInSinglePaneShade() {
+        disableSplitShade()
+        underTest.setDetailShowing(true)
+
+        // always sets spacings to 0
+        given(
+            taskbarVisible = false,
+            navigationMode = GESTURES_NAVIGATION,
+            insets = windowInsets().withStableBottom()
+        )
+        then(expectedContainerPadding = 0, expectedNotificationsMargin = 0)
+
+        given(taskbarVisible = false, navigationMode = BUTTONS_NAVIGATION, insets = emptyInsets())
+        then(expectedContainerPadding = 0, expectedNotificationsMargin = 0)
+    }
+
+    @Test
+    fun testDetailShowingInSplitShade() {
+        enableSplitShade()
+        underTest.setDetailShowing(true)
+
+        given(
+            taskbarVisible = false,
+            navigationMode = GESTURES_NAVIGATION,
+            insets = windowInsets().withStableBottom()
+        )
+        then(expectedContainerPadding = 0)
+
+        // should not influence spacing
+        given(taskbarVisible = false, navigationMode = BUTTONS_NAVIGATION, insets = emptyInsets())
+        then(expectedContainerPadding = 0)
+    }
+
+    @Test
+    fun testNotificationsMarginBottomIsUpdated() {
+        Mockito.clearInvocations(view)
+        enableSplitShade()
+        verify(view).setNotificationsMarginBottom(NOTIFICATIONS_MARGIN)
+
+        overrideResource(R.dimen.notification_panel_margin_bottom, 100)
+        disableSplitShade()
+        verify(view).setNotificationsMarginBottom(100)
+    }
+
+    @Test
+    fun testSplitShadeLayout_isAlignedToGuideline() {
+        enableSplitShade()
+        underTest.updateResources()
+        assertThat(getConstraintSetLayout(R.id.qs_frame).endToEnd).isEqualTo(R.id.qs_edge_guideline)
+        assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).startToStart)
+            .isEqualTo(R.id.qs_edge_guideline)
+    }
+
+    @Test
+    fun testSinglePaneLayout_childrenHaveEqualMargins() {
+        disableSplitShade()
+        underTest.updateResources()
+        val qsStartMargin = getConstraintSetLayout(R.id.qs_frame).startMargin
+        val qsEndMargin = getConstraintSetLayout(R.id.qs_frame).endMargin
+        val notifStartMargin = getConstraintSetLayout(R.id.notification_stack_scroller).startMargin
+        val notifEndMargin = getConstraintSetLayout(R.id.notification_stack_scroller).endMargin
+        assertThat(
+                qsStartMargin == qsEndMargin &&
+                    notifStartMargin == notifEndMargin &&
+                    qsStartMargin == notifStartMargin
+            )
+            .isTrue()
+    }
+
+    @Test
+    fun testSplitShadeLayout_childrenHaveInsideMarginsOfZero() {
+        enableSplitShade()
+        underTest.updateResources()
+        assertThat(getConstraintSetLayout(R.id.qs_frame).endMargin).isEqualTo(0)
+        assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).startMargin)
+            .isEqualTo(0)
+    }
+
+    @Test
+    fun testSplitShadeLayout_qsFrameHasHorizontalMarginsOfZero() {
+        enableSplitShade()
+        underTest.updateResources()
+        assertThat(getConstraintSetLayout(R.id.qs_frame).endMargin).isEqualTo(0)
+        assertThat(getConstraintSetLayout(R.id.qs_frame).startMargin).isEqualTo(0)
+    }
+
+    @Test
+    fun testLargeScreenLayout_qsAndNotifsTopMarginIsOfHeaderHeight() {
+        setLargeScreen()
+        val largeScreenHeaderHeight = 100
+        overrideResource(R.dimen.large_screen_shade_header_height, largeScreenHeaderHeight)
+
+        underTest.updateResources()
+
+        assertThat(getConstraintSetLayout(R.id.qs_frame).topMargin)
+            .isEqualTo(largeScreenHeaderHeight)
+        assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).topMargin)
+            .isEqualTo(largeScreenHeaderHeight)
+    }
+
+    @Test
+    fun testSmallScreenLayout_qsAndNotifsTopMarginIsZero() {
+        setSmallScreen()
+        underTest.updateResources()
+        assertThat(getConstraintSetLayout(R.id.qs_frame).topMargin).isEqualTo(0)
+        assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).topMargin).isEqualTo(0)
+    }
+
+    @Test
+    fun testSinglePaneShadeLayout_qsFrameHasHorizontalMarginsSetToCorrectValue() {
+        disableSplitShade()
+        underTest.updateResources()
+        val notificationPanelMarginHorizontal =
+            mContext.resources.getDimensionPixelSize(R.dimen.notification_panel_margin_horizontal)
+        assertThat(getConstraintSetLayout(R.id.qs_frame).endMargin)
+            .isEqualTo(notificationPanelMarginHorizontal)
+        assertThat(getConstraintSetLayout(R.id.qs_frame).startMargin)
+            .isEqualTo(notificationPanelMarginHorizontal)
+    }
+
+    @Test
+    fun testSinglePaneShadeLayout_isAlignedToParent() {
+        disableSplitShade()
+        underTest.updateResources()
+        assertThat(getConstraintSetLayout(R.id.qs_frame).endToEnd)
+            .isEqualTo(ConstraintSet.PARENT_ID)
+        assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).startToStart)
+            .isEqualTo(ConstraintSet.PARENT_ID)
+    }
+
+    @Test
+    fun testAllChildrenOfNotificationContainer_haveIds() {
+        // set dimen to 0 to avoid triggering updating bottom spacing
+        overrideResource(R.dimen.split_shade_notifications_scrim_margin_bottom, 0)
+        val container = NotificationsQuickSettingsContainer(mContext, null)
+        container.removeAllViews()
+        container.addView(newViewWithId(1))
+        container.addView(newViewWithId(View.NO_ID))
+        val controller =
+            NotificationsQSContainerController(
+                container,
+                navigationModeController,
+                overviewProxyService,
+                shadeHeaderController,
+                shadeExpansionStateManager,
+                fragmentService,
+                delayableExecutor,
+                featureFlags,
+                notificationStackScrollLayoutController,
+            )
+        controller.updateConstraints()
+
+        assertThat(container.getChildAt(0).id).isEqualTo(1)
+        assertThat(container.getChildAt(1).id).isNotEqualTo(View.NO_ID)
+    }
+
+    @Test
+    fun testWindowInsetDebounce() {
+        disableSplitShade()
+
+        given(
+            taskbarVisible = false,
+            navigationMode = GESTURES_NAVIGATION,
+            insets = emptyInsets(),
+            applyImmediately = false
+        )
+        fakeSystemClock.advanceTime(INSET_DEBOUNCE_MILLIS / 2)
+        windowInsetsCallback.accept(windowInsets().withStableBottom())
+
+        delayableExecutor.advanceClockToLast()
+        delayableExecutor.runAllReady()
+
+        verify(view, never()).setQSContainerPaddingBottom(0)
+        verify(view).setQSContainerPaddingBottom(STABLE_INSET_BOTTOM)
+    }
+
+    @Test
+    fun testStartCustomizingWithDuration() {
+        underTest.setCustomizerShowing(true, 100L)
+        verify(shadeHeaderController).startCustomizingAnimation(true, 100L)
+    }
+
+    @Test
+    fun testEndCustomizingWithDuration() {
+        underTest.setCustomizerShowing(true, 0L) // Only tracks changes
+        reset(shadeHeaderController)
+
+        underTest.setCustomizerShowing(false, 100L)
+        verify(shadeHeaderController).startCustomizingAnimation(false, 100L)
+    }
+
+    @Test
+    fun testTagListenerAdded() {
+        verify(fragmentHostManager).addTagListener(eq(QS.TAG), eq(view))
+    }
+
+    @Test
+    fun testTagListenerRemoved() {
+        attachStateListenerCaptor.value.onViewDetachedFromWindow(view)
+        verify(fragmentHostManager).removeTagListener(eq(QS.TAG), eq(view))
+    }
+
+    private fun disableSplitShade() {
+        setSplitShadeEnabled(false)
+    }
+
+    private fun enableSplitShade() {
+        setSplitShadeEnabled(true)
+    }
+
+    private fun setSplitShadeEnabled(enabled: Boolean) {
+        overrideResource(R.bool.config_use_split_notification_shade, enabled)
+        underTest.updateResources()
+    }
+
+    private fun setSmallScreen() {
+        setLargeScreenEnabled(false)
+    }
+
+    private fun setLargeScreen() {
+        setLargeScreenEnabled(true)
+    }
+
+    private fun setLargeScreenEnabled(enabled: Boolean) {
+        overrideResource(R.bool.config_use_large_screen_shade_header, enabled)
+    }
+
+    private fun given(
+        taskbarVisible: Boolean,
+        navigationMode: Int,
+        insets: WindowInsets,
+        applyImmediately: Boolean = true
+    ) {
+        Mockito.clearInvocations(view)
+        taskbarVisibilityCallback.onTaskbarStatusUpdated(taskbarVisible, false)
+        navigationModeCallback.onNavigationModeChanged(navigationMode)
+        windowInsetsCallback.accept(insets)
+        if (applyImmediately) {
+            delayableExecutor.advanceClockToLast()
+            delayableExecutor.runAllReady()
+        }
+    }
+
+    fun then(
+        expectedContainerPadding: Int,
+        expectedNotificationsMargin: Int = NOTIFICATIONS_MARGIN,
+        expectedQsPadding: Int = 0
+    ) {
+        verify(view).setPadding(anyInt(), anyInt(), anyInt(), eq(expectedContainerPadding))
+        verify(view).setNotificationsMarginBottom(expectedNotificationsMargin)
+        verify(view).setQSContainerPaddingBottom(expectedQsPadding)
+        Mockito.clearInvocations(view)
+    }
+
+    private fun windowInsets() = mock(WindowInsets::class.java, RETURNS_DEEP_STUBS)
+
+    private fun emptyInsets() = mock(WindowInsets::class.java)
+
+    private fun WindowInsets.withCutout(): WindowInsets {
+        whenever(displayCutout.safeInsetBottom).thenReturn(CUTOUT_HEIGHT)
+        return this
+    }
+
+    private fun WindowInsets.withStableBottom(): WindowInsets {
+        whenever(stableInsetBottom).thenReturn(STABLE_INSET_BOTTOM)
+        return this
+    }
+
+    private fun getConstraintSetLayout(@IdRes id: Int): ConstraintSet.Layout {
+        return constraintSetCaptor.value.getConstraint(id).layout
+    }
+
+    private fun newViewWithId(id: Int): View {
+        val view = View(mContext)
+        view.id = id
+        val layoutParams =
+            ConstraintLayout.LayoutParams(
+                ViewGroup.LayoutParams.WRAP_CONTENT,
+                ViewGroup.LayoutParams.WRAP_CONTENT
+            )
+        // required as cloning ConstraintSet fails if view doesn't have layout params
+        view.layoutParams = layoutParams
+        return view
+    }
+
+    companion object {
+        const val STABLE_INSET_BOTTOM = 100
+        const val CUTOUT_HEIGHT = 50
+        const val GESTURES_NAVIGATION = WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL
+        const val BUTTONS_NAVIGATION = WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON
+        const val NOTIFICATIONS_MARGIN = 50
+        const val SCRIM_MARGIN = 10
+        const val FOOTER_ACTIONS_INSET = 2
+        const val FOOTER_ACTIONS_PADDING = 2
+        const val FOOTER_ACTIONS_OFFSET = FOOTER_ACTIONS_INSET + FOOTER_ACTIONS_PADDING
+        const val QS_PADDING_OFFSET = SCRIM_MARGIN + FOOTER_ACTIONS_OFFSET
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt
index d4751c8..a504818 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt
@@ -19,24 +19,47 @@
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import android.testing.TestableResources
+import android.view.View
+import android.view.ViewGroup
+import android.view.WindowInsets
+import android.view.WindowManagerPolicyConstants
+import androidx.annotation.IdRes
+import androidx.constraintlayout.widget.ConstraintLayout
 import androidx.constraintlayout.widget.ConstraintSet
 import androidx.test.filters.SmallTest
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.fragments.FragmentHostManager
 import com.android.systemui.fragments.FragmentService
 import com.android.systemui.navigationbar.NavigationModeController
+import com.android.systemui.navigationbar.NavigationModeController.ModeChangedListener
+import com.android.systemui.plugins.qs.QS
 import com.android.systemui.recents.OverviewProxyService
-import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.mockito.capture
 import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
+import java.util.function.Consumer
 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
+import org.mockito.Mockito.RETURNS_DEEP_STUBS
+import org.mockito.Mockito.any
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.doNothing
+import org.mockito.Mockito.eq
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.reset
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
@@ -51,19 +74,37 @@
     @Mock lateinit var shadeHeaderController: ShadeHeaderController
     @Mock lateinit var shadeExpansionStateManager: ShadeExpansionStateManager
     @Mock lateinit var fragmentService: FragmentService
+    @Mock lateinit var fragmentHostManager: FragmentHostManager
+    @Mock
+    lateinit var notificationStackScrollLayoutController: NotificationStackScrollLayoutController
+
+    @Captor lateinit var navigationModeCaptor: ArgumentCaptor<ModeChangedListener>
+    @Captor lateinit var taskbarVisibilityCaptor: ArgumentCaptor<OverviewProxyListener>
+    @Captor lateinit var windowInsetsCallbackCaptor: ArgumentCaptor<Consumer<WindowInsets>>
+    @Captor lateinit var constraintSetCaptor: ArgumentCaptor<ConstraintSet>
+    @Captor lateinit var attachStateListenerCaptor: ArgumentCaptor<View.OnAttachStateChangeListener>
 
     lateinit var underTest: NotificationsQSContainerController
 
     private lateinit var fakeResources: TestableResources
-
-    private val delayableExecutor: DelayableExecutor = FakeExecutor(FakeSystemClock())
+    private lateinit var featureFlags: FakeFeatureFlags
+    private lateinit var navigationModeCallback: ModeChangedListener
+    private lateinit var taskbarVisibilityCallback: OverviewProxyListener
+    private lateinit var windowInsetsCallback: Consumer<WindowInsets>
+    private lateinit var fakeSystemClock: FakeSystemClock
+    private lateinit var delayableExecutor: FakeExecutor
 
     @Before
     fun setup() {
         MockitoAnnotations.initMocks(this)
-        fakeResources = TestableResources(context.resources)
+        fakeSystemClock = FakeSystemClock()
+        delayableExecutor = FakeExecutor(fakeSystemClock)
+        featureFlags = FakeFeatureFlags().apply { set(Flags.MIGRATE_NSSL, true) }
+        mContext.ensureTestableResources()
+        whenever(view.context).thenReturn(mContext)
+        whenever(view.resources).thenReturn(mContext.resources)
 
-        whenever(view.resources).thenReturn(fakeResources.resources)
+        whenever(fragmentService.getFragmentHostManager(any())).thenReturn(fragmentHostManager)
 
         underTest =
             NotificationsQSContainerController(
@@ -74,16 +115,36 @@
                 shadeExpansionStateManager,
                 fragmentService,
                 delayableExecutor,
+                featureFlags,
+                notificationStackScrollLayoutController,
             )
+
+        overrideResource(R.dimen.split_shade_notifications_scrim_margin_bottom, SCRIM_MARGIN)
+        overrideResource(R.dimen.notification_panel_margin_bottom, NOTIFICATIONS_MARGIN)
+        overrideResource(R.bool.config_use_split_notification_shade, false)
+        overrideResource(R.dimen.qs_footer_actions_bottom_padding, FOOTER_ACTIONS_PADDING)
+        overrideResource(R.dimen.qs_footer_action_inset, FOOTER_ACTIONS_INSET)
+        whenever(navigationModeController.addListener(navigationModeCaptor.capture()))
+            .thenReturn(GESTURES_NAVIGATION)
+        doNothing().`when`(overviewProxyService).addCallback(taskbarVisibilityCaptor.capture())
+        doNothing().`when`(view).setInsetsChangedListener(windowInsetsCallbackCaptor.capture())
+        doNothing().`when`(view).applyConstraints(constraintSetCaptor.capture())
+        doNothing().`when`(view).addOnAttachStateChangeListener(attachStateListenerCaptor.capture())
+        underTest.init()
+        attachStateListenerCaptor.value.onViewAttachedToWindow(view)
+
+        navigationModeCallback = navigationModeCaptor.value
+        taskbarVisibilityCallback = taskbarVisibilityCaptor.value
+        windowInsetsCallback = windowInsetsCallbackCaptor.value
+
+        Mockito.clearInvocations(view)
     }
 
     @Test
     fun testSmallScreen_updateResources_splitShadeHeightIsSet() {
-        with(fakeResources) {
-            addOverride(R.bool.config_use_large_screen_shade_header, false)
-            addOverride(R.dimen.qs_header_height, 1)
-            addOverride(R.dimen.large_screen_shade_header_height, 2)
-        }
+        overrideResource(R.bool.config_use_large_screen_shade_header, false)
+        overrideResource(R.dimen.qs_header_height, 1)
+        overrideResource(R.dimen.large_screen_shade_header_height, 2)
 
         underTest.updateResources()
 
@@ -94,11 +155,9 @@
 
     @Test
     fun testLargeScreen_updateResources_splitShadeHeightIsSet() {
-        with(fakeResources) {
-            addOverride(R.bool.config_use_large_screen_shade_header, true)
-            addOverride(R.dimen.qs_header_height, 1)
-            addOverride(R.dimen.large_screen_shade_header_height, 2)
-        }
+        overrideResource(R.bool.config_use_large_screen_shade_header, true)
+        overrideResource(R.dimen.qs_header_height, 1)
+        overrideResource(R.dimen.large_screen_shade_header_height, 2)
 
         underTest.updateResources()
 
@@ -106,4 +165,415 @@
         verify(view).applyConstraints(capture(captor))
         assertThat(captor.value.getHeight(R.id.split_shade_status_bar)).isEqualTo(2)
     }
+
+    @Test
+    fun testTaskbarVisibleInSplitShade() {
+        enableSplitShade()
+
+        given(
+            taskbarVisible = true,
+            navigationMode = GESTURES_NAVIGATION,
+            insets = windowInsets().withStableBottom()
+        )
+        then(
+            expectedContainerPadding = 0, // taskbar should disappear when shade is expanded
+            expectedNotificationsMargin = NOTIFICATIONS_MARGIN,
+            expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET
+        )
+
+        given(
+            taskbarVisible = true,
+            navigationMode = BUTTONS_NAVIGATION,
+            insets = windowInsets().withStableBottom()
+        )
+        then(
+            expectedContainerPadding = STABLE_INSET_BOTTOM,
+            expectedNotificationsMargin = NOTIFICATIONS_MARGIN,
+            expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET
+        )
+    }
+
+    @Test
+    fun testTaskbarNotVisibleInSplitShade() {
+        // when taskbar is not visible, it means we're on the home screen
+        enableSplitShade()
+
+        given(
+            taskbarVisible = false,
+            navigationMode = GESTURES_NAVIGATION,
+            insets = windowInsets().withStableBottom()
+        )
+        then(
+            expectedContainerPadding = 0,
+            expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET
+        )
+
+        given(
+            taskbarVisible = false,
+            navigationMode = BUTTONS_NAVIGATION,
+            insets = windowInsets().withStableBottom()
+        )
+        then(
+            expectedContainerPadding = 0, // qs goes full height as it's not obscuring nav buttons
+            expectedNotificationsMargin = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN,
+            expectedQsPadding = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET
+        )
+    }
+
+    @Test
+    fun testTaskbarNotVisibleInSplitShadeWithCutout() {
+        enableSplitShade()
+
+        given(
+            taskbarVisible = false,
+            navigationMode = GESTURES_NAVIGATION,
+            insets = windowInsets().withCutout()
+        )
+        then(
+            expectedContainerPadding = CUTOUT_HEIGHT,
+            expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET
+        )
+
+        given(
+            taskbarVisible = false,
+            navigationMode = BUTTONS_NAVIGATION,
+            insets = windowInsets().withCutout().withStableBottom()
+        )
+        then(
+            expectedContainerPadding = 0,
+            expectedNotificationsMargin = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN,
+            expectedQsPadding = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET
+        )
+    }
+
+    @Test
+    fun testTaskbarVisibleInSinglePaneShade() {
+        disableSplitShade()
+
+        given(
+            taskbarVisible = true,
+            navigationMode = GESTURES_NAVIGATION,
+            insets = windowInsets().withStableBottom()
+        )
+        then(expectedContainerPadding = 0, expectedQsPadding = STABLE_INSET_BOTTOM)
+
+        given(
+            taskbarVisible = true,
+            navigationMode = BUTTONS_NAVIGATION,
+            insets = windowInsets().withStableBottom()
+        )
+        then(
+            expectedContainerPadding = STABLE_INSET_BOTTOM,
+            expectedQsPadding = STABLE_INSET_BOTTOM
+        )
+    }
+
+    @Test
+    fun testTaskbarNotVisibleInSinglePaneShade() {
+        disableSplitShade()
+
+        given(taskbarVisible = false, navigationMode = GESTURES_NAVIGATION, insets = emptyInsets())
+        then(expectedContainerPadding = 0)
+
+        given(
+            taskbarVisible = false,
+            navigationMode = GESTURES_NAVIGATION,
+            insets = windowInsets().withCutout().withStableBottom()
+        )
+        then(expectedContainerPadding = CUTOUT_HEIGHT, expectedQsPadding = STABLE_INSET_BOTTOM)
+
+        given(
+            taskbarVisible = false,
+            navigationMode = BUTTONS_NAVIGATION,
+            insets = windowInsets().withStableBottom()
+        )
+        then(
+            expectedContainerPadding = 0,
+            expectedNotificationsMargin = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN,
+            expectedQsPadding = STABLE_INSET_BOTTOM
+        )
+    }
+
+    @Test
+    fun testDetailShowingInSinglePaneShade() {
+        disableSplitShade()
+        underTest.setDetailShowing(true)
+
+        // always sets spacings to 0
+        given(
+            taskbarVisible = false,
+            navigationMode = GESTURES_NAVIGATION,
+            insets = windowInsets().withStableBottom()
+        )
+        then(expectedContainerPadding = 0, expectedNotificationsMargin = 0)
+
+        given(taskbarVisible = false, navigationMode = BUTTONS_NAVIGATION, insets = emptyInsets())
+        then(expectedContainerPadding = 0, expectedNotificationsMargin = 0)
+    }
+
+    @Test
+    fun testDetailShowingInSplitShade() {
+        enableSplitShade()
+        underTest.setDetailShowing(true)
+
+        given(
+            taskbarVisible = false,
+            navigationMode = GESTURES_NAVIGATION,
+            insets = windowInsets().withStableBottom()
+        )
+        then(expectedContainerPadding = 0)
+
+        // should not influence spacing
+        given(taskbarVisible = false, navigationMode = BUTTONS_NAVIGATION, insets = emptyInsets())
+        then(expectedContainerPadding = 0)
+    }
+
+    @Test
+    fun testNotificationsMarginBottomIsUpdated() {
+        Mockito.clearInvocations(view)
+        enableSplitShade()
+        verify(view).setNotificationsMarginBottom(NOTIFICATIONS_MARGIN)
+
+        overrideResource(R.dimen.notification_panel_margin_bottom, 100)
+        disableSplitShade()
+        verify(view).setNotificationsMarginBottom(100)
+    }
+
+    @Test
+    fun testSplitShadeLayout_isAlignedToGuideline() {
+        enableSplitShade()
+        underTest.updateResources()
+        assertThat(getConstraintSetLayout(R.id.qs_frame).endToEnd).isEqualTo(R.id.qs_edge_guideline)
+    }
+
+    @Test
+    fun testSinglePaneLayout_childrenHaveEqualMargins() {
+        disableSplitShade()
+        underTest.updateResources()
+        val qsStartMargin = getConstraintSetLayout(R.id.qs_frame).startMargin
+        val qsEndMargin = getConstraintSetLayout(R.id.qs_frame).endMargin
+        assertThat(qsStartMargin == qsEndMargin).isTrue()
+    }
+
+    @Test
+    fun testSplitShadeLayout_childrenHaveInsideMarginsOfZero() {
+        enableSplitShade()
+        underTest.updateResources()
+        assertThat(getConstraintSetLayout(R.id.qs_frame).endMargin).isEqualTo(0)
+    }
+
+    @Test
+    fun testSplitShadeLayout_qsFrameHasHorizontalMarginsOfZero() {
+        enableSplitShade()
+        underTest.updateResources()
+        assertThat(getConstraintSetLayout(R.id.qs_frame).endMargin).isEqualTo(0)
+        assertThat(getConstraintSetLayout(R.id.qs_frame).startMargin).isEqualTo(0)
+    }
+
+    @Test
+    fun testLargeScreenLayout_qsAndNotifsTopMarginIsOfHeaderHeight() {
+        setLargeScreen()
+        val largeScreenHeaderHeight = 100
+        overrideResource(R.dimen.large_screen_shade_header_height, largeScreenHeaderHeight)
+
+        underTest.updateResources()
+
+        assertThat(getConstraintSetLayout(R.id.qs_frame).topMargin)
+            .isEqualTo(largeScreenHeaderHeight)
+    }
+
+    @Test
+    fun testSmallScreenLayout_qsAndNotifsTopMarginIsZero() {
+        setSmallScreen()
+        underTest.updateResources()
+        assertThat(getConstraintSetLayout(R.id.qs_frame).topMargin).isEqualTo(0)
+    }
+
+    @Test
+    fun testSinglePaneShadeLayout_qsFrameHasHorizontalMarginsSetToCorrectValue() {
+        disableSplitShade()
+        underTest.updateResources()
+        val notificationPanelMarginHorizontal =
+            mContext.resources.getDimensionPixelSize(R.dimen.notification_panel_margin_horizontal)
+        assertThat(getConstraintSetLayout(R.id.qs_frame).endMargin)
+            .isEqualTo(notificationPanelMarginHorizontal)
+        assertThat(getConstraintSetLayout(R.id.qs_frame).startMargin)
+            .isEqualTo(notificationPanelMarginHorizontal)
+    }
+
+    @Test
+    fun testSinglePaneShadeLayout_isAlignedToParent() {
+        disableSplitShade()
+        underTest.updateResources()
+        assertThat(getConstraintSetLayout(R.id.qs_frame).endToEnd)
+            .isEqualTo(ConstraintSet.PARENT_ID)
+    }
+
+    @Test
+    fun testAllChildrenOfNotificationContainer_haveIds() {
+        // set dimen to 0 to avoid triggering updating bottom spacing
+        overrideResource(R.dimen.split_shade_notifications_scrim_margin_bottom, 0)
+        val container = NotificationsQuickSettingsContainer(mContext, null)
+        container.removeAllViews()
+        container.addView(newViewWithId(1))
+        container.addView(newViewWithId(View.NO_ID))
+        val controller =
+            NotificationsQSContainerController(
+                container,
+                navigationModeController,
+                overviewProxyService,
+                shadeHeaderController,
+                shadeExpansionStateManager,
+                fragmentService,
+                delayableExecutor,
+                featureFlags,
+                notificationStackScrollLayoutController,
+            )
+        controller.updateConstraints()
+
+        assertThat(container.getChildAt(0).id).isEqualTo(1)
+        assertThat(container.getChildAt(1).id).isNotEqualTo(View.NO_ID)
+    }
+
+    @Test
+    fun testWindowInsetDebounce() {
+        disableSplitShade()
+
+        given(
+            taskbarVisible = false,
+            navigationMode = GESTURES_NAVIGATION,
+            insets = emptyInsets(),
+            applyImmediately = false
+        )
+        fakeSystemClock.advanceTime(INSET_DEBOUNCE_MILLIS / 2)
+        windowInsetsCallback.accept(windowInsets().withStableBottom())
+
+        delayableExecutor.advanceClockToLast()
+        delayableExecutor.runAllReady()
+
+        verify(view, never()).setQSContainerPaddingBottom(0)
+        verify(view).setQSContainerPaddingBottom(STABLE_INSET_BOTTOM)
+    }
+
+    @Test
+    fun testStartCustomizingWithDuration() {
+        underTest.setCustomizerShowing(true, 100L)
+        verify(shadeHeaderController).startCustomizingAnimation(true, 100L)
+    }
+
+    @Test
+    fun testEndCustomizingWithDuration() {
+        underTest.setCustomizerShowing(true, 0L) // Only tracks changes
+        reset(shadeHeaderController)
+
+        underTest.setCustomizerShowing(false, 100L)
+        verify(shadeHeaderController).startCustomizingAnimation(false, 100L)
+    }
+
+    @Test
+    fun testTagListenerAdded() {
+        verify(fragmentHostManager).addTagListener(eq(QS.TAG), eq(view))
+    }
+
+    @Test
+    fun testTagListenerRemoved() {
+        attachStateListenerCaptor.value.onViewDetachedFromWindow(view)
+        verify(fragmentHostManager).removeTagListener(eq(QS.TAG), eq(view))
+    }
+
+    private fun disableSplitShade() {
+        setSplitShadeEnabled(false)
+    }
+
+    private fun enableSplitShade() {
+        setSplitShadeEnabled(true)
+    }
+
+    private fun setSplitShadeEnabled(enabled: Boolean) {
+        overrideResource(R.bool.config_use_split_notification_shade, enabled)
+        underTest.updateResources()
+    }
+
+    private fun setSmallScreen() {
+        setLargeScreenEnabled(false)
+    }
+
+    private fun setLargeScreen() {
+        setLargeScreenEnabled(true)
+    }
+
+    private fun setLargeScreenEnabled(enabled: Boolean) {
+        overrideResource(R.bool.config_use_large_screen_shade_header, enabled)
+    }
+
+    private fun given(
+        taskbarVisible: Boolean,
+        navigationMode: Int,
+        insets: WindowInsets,
+        applyImmediately: Boolean = true
+    ) {
+        Mockito.clearInvocations(view)
+        taskbarVisibilityCallback.onTaskbarStatusUpdated(taskbarVisible, false)
+        navigationModeCallback.onNavigationModeChanged(navigationMode)
+        windowInsetsCallback.accept(insets)
+        if (applyImmediately) {
+            delayableExecutor.advanceClockToLast()
+            delayableExecutor.runAllReady()
+        }
+    }
+
+    fun then(
+        expectedContainerPadding: Int,
+        expectedNotificationsMargin: Int = NOTIFICATIONS_MARGIN,
+        expectedQsPadding: Int = 0
+    ) {
+        verify(view).setPadding(anyInt(), anyInt(), anyInt(), eq(expectedContainerPadding))
+        verify(view).setNotificationsMarginBottom(expectedNotificationsMargin)
+        verify(view).setQSContainerPaddingBottom(expectedQsPadding)
+        Mockito.clearInvocations(view)
+    }
+
+    private fun windowInsets() = mock(WindowInsets::class.java, RETURNS_DEEP_STUBS)
+
+    private fun emptyInsets() = mock(WindowInsets::class.java)
+
+    private fun WindowInsets.withCutout(): WindowInsets {
+        whenever(displayCutout.safeInsetBottom).thenReturn(CUTOUT_HEIGHT)
+        return this
+    }
+
+    private fun WindowInsets.withStableBottom(): WindowInsets {
+        whenever(stableInsetBottom).thenReturn(STABLE_INSET_BOTTOM)
+        return this
+    }
+
+    private fun getConstraintSetLayout(@IdRes id: Int): ConstraintSet.Layout {
+        return constraintSetCaptor.value.getConstraint(id).layout
+    }
+
+    private fun newViewWithId(id: Int): View {
+        val view = View(mContext)
+        view.id = id
+        val layoutParams =
+            ConstraintLayout.LayoutParams(
+                ViewGroup.LayoutParams.WRAP_CONTENT,
+                ViewGroup.LayoutParams.WRAP_CONTENT
+            )
+        // required as cloning ConstraintSet fails if view doesn't have layout params
+        view.layoutParams = layoutParams
+        return view
+    }
+
+    companion object {
+        const val STABLE_INSET_BOTTOM = 100
+        const val CUTOUT_HEIGHT = 50
+        const val GESTURES_NAVIGATION = WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL
+        const val BUTTONS_NAVIGATION = WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON
+        const val NOTIFICATIONS_MARGIN = 50
+        const val SCRIM_MARGIN = 10
+        const val FOOTER_ACTIONS_INSET = 2
+        const val FOOTER_ACTIONS_PADDING = 2
+        const val FOOTER_ACTIONS_OFFSET = FOOTER_ACTIONS_INSET + FOOTER_ACTIONS_PADDING
+        const val QS_PADDING_OFFSET = SCRIM_MARGIN + FOOTER_ACTIONS_OFFSET
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt
index d7c06a7..77a22ac 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt
@@ -25,22 +25,25 @@
 import android.view.MotionEvent
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.dock.DockManager
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.power.data.repository.FakePowerRepository
+import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.settings.UserTracker
-import com.android.systemui.statusbar.phone.CentralSurfaces
+import com.android.systemui.statusbar.phone.ScreenOffAnimationController
 import com.android.systemui.tuner.TunerService
 import com.android.systemui.tuner.TunerService.Tunable
 import com.android.systemui.util.mockito.eq
+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.ArgumentMatchers.anyInt
-import org.mockito.ArgumentMatchers.anyLong
-import org.mockito.ArgumentMatchers.anyString
 import org.mockito.Mock
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
@@ -52,14 +55,12 @@
 @SmallTest
 class PulsingGestureListenerTest : SysuiTestCase() {
     @Mock
-    private lateinit var view: NotificationShadeWindowView
-    @Mock
-    private lateinit var centralSurfaces: CentralSurfaces
-    @Mock
     private lateinit var dockManager: DockManager
     @Mock
     private lateinit var falsingManager: FalsingManager
     @Mock
+    private lateinit var falsingCollector: FalsingCollector
+    @Mock
     private lateinit var ambientDisplayConfiguration: AmbientDisplayConfiguration
     @Mock
     private lateinit var tunerService: TunerService
@@ -71,7 +72,10 @@
     private lateinit var shadeLogger: ShadeLogger
     @Mock
     private lateinit var userTracker: UserTracker
+    @Mock
+    private lateinit var screenOffAnimationController: ScreenOffAnimationController
 
+    private lateinit var powerRepository: FakePowerRepository
     private lateinit var tunableCaptor: ArgumentCaptor<Tunable>
     private lateinit var underTest: PulsingGestureListener
 
@@ -79,11 +83,18 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
 
+        powerRepository = FakePowerRepository()
+
         underTest = PulsingGestureListener(
-                view,
                 falsingManager,
                 dockManager,
-                centralSurfaces,
+                PowerInteractor(
+                    powerRepository,
+                    FakeKeyguardRepository(),
+                    falsingCollector,
+                    screenOffAnimationController,
+                    statusBarStateController,
+                ),
                 ambientDisplayConfiguration,
                 statusBarStateController,
                 shadeLogger,
@@ -92,6 +103,7 @@
                 dumpManager
         )
         whenever(dockManager.isDocked).thenReturn(false)
+        whenever(screenOffAnimationController.allowWakeUpIfDozing()).thenReturn(true)
     }
 
     @Test
@@ -110,8 +122,8 @@
         underTest.onSingleTapUp(upEv)
 
         // THEN wake up device if dozing
-        verify(centralSurfaces).wakeUpIfDozing(
-                anyLong(), anyString(), eq(PowerManager.WAKE_REASON_TAP))
+        assertThat(powerRepository.lastWakeWhy).isNotNull()
+        assertThat(powerRepository.lastWakeReason).isEqualTo(PowerManager.WAKE_REASON_TAP)
     }
 
     @Test
@@ -130,8 +142,8 @@
         underTest.onDoubleTapEvent(upEv)
 
         // THEN wake up device if dozing
-        verify(centralSurfaces).wakeUpIfDozing(
-                anyLong(), anyString(), eq(PowerManager.WAKE_REASON_TAP))
+        assertThat(powerRepository.lastWakeWhy).isNotNull()
+        assertThat(powerRepository.lastWakeReason).isEqualTo(PowerManager.WAKE_REASON_TAP)
     }
 
     @Test
@@ -162,8 +174,8 @@
         underTest.onSingleTapUp(upEv)
 
         // THEN the device doesn't wake up
-        verify(centralSurfaces, never()).wakeUpIfDozing(
-                anyLong(), anyString(), anyInt())
+        assertThat(powerRepository.lastWakeWhy).isNull()
+        assertThat(powerRepository.lastWakeReason).isNull()
     }
 
     @Test
@@ -210,8 +222,8 @@
         underTest.onDoubleTapEvent(upEv)
 
         // THEN the device doesn't wake up
-        verify(centralSurfaces, never()).wakeUpIfDozing(
-                anyLong(), anyString(), anyInt())
+        assertThat(powerRepository.lastWakeWhy).isNull()
+        assertThat(powerRepository.lastWakeReason).isNull()
     }
 
     @Test
@@ -230,8 +242,8 @@
         underTest.onSingleTapUp(upEv)
 
         // THEN the device doesn't wake up
-        verify(centralSurfaces, never()).wakeUpIfDozing(
-                anyLong(), anyString(), anyInt())
+        assertThat(powerRepository.lastWakeWhy).isNull()
+        assertThat(powerRepository.lastWakeReason).isNull()
     }
 
     @Test
@@ -250,8 +262,8 @@
         underTest.onDoubleTapEvent(upEv)
 
         // THEN the device doesn't wake up
-        verify(centralSurfaces, never()).wakeUpIfDozing(
-                anyLong(), anyString(), anyInt())
+        assertThat(powerRepository.lastWakeWhy).isNull()
+        assertThat(powerRepository.lastWakeReason).isNull()
     }
 
     fun updateSettings() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
new file mode 100644
index 0000000..a2c2912
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
@@ -0,0 +1,266 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.res.Resources;
+import android.os.Handler;
+import android.os.Looper;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+import android.view.accessibility.AccessibilityManager;
+
+import com.android.internal.jank.InteractionJankMonitor;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.UiEventLogger;
+import com.android.keyguard.KeyguardStatusView;
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.TestScopeProvider;
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.classifier.FalsingCollector;
+import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.fragments.FragmentHostManager;
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;
+import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
+import com.android.systemui.media.controls.pipeline.MediaDataManager;
+import com.android.systemui.media.controls.ui.MediaHierarchyManager;
+import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.plugins.qs.QS;
+import com.android.systemui.qs.QSFragment;
+import com.android.systemui.screenrecord.RecordingController;
+import com.android.systemui.shade.data.repository.ShadeRepository;
+import com.android.systemui.shade.domain.interactor.ShadeInteractor;
+import com.android.systemui.shade.transition.ShadeTransitionController;
+import com.android.systemui.statusbar.LockscreenShadeTransitionController;
+import com.android.systemui.statusbar.NotificationRemoteInputManager;
+import com.android.systemui.statusbar.NotificationShadeDepthController;
+import com.android.systemui.statusbar.PulseExpansionHandler;
+import com.android.systemui.statusbar.QsFrameTranslateController;
+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.stack.AmbientState;
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
+import com.android.systemui.statusbar.phone.KeyguardBottomAreaView;
+import com.android.systemui.statusbar.phone.KeyguardBypassController;
+import com.android.systemui.statusbar.phone.KeyguardStatusBarView;
+import com.android.systemui.statusbar.phone.LightBarController;
+import com.android.systemui.statusbar.phone.LockscreenGestureLogger;
+import com.android.systemui.statusbar.phone.ScrimController;
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
+import com.android.systemui.statusbar.phone.StatusBarTouchableRegionManager;
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository;
+import com.android.systemui.statusbar.policy.CastController;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.user.domain.interactor.UserInteractor;
+import com.android.systemui.util.kotlin.JavaAdapter;
+
+import dagger.Lazy;
+
+import org.junit.After;
+import org.junit.Before;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import kotlinx.coroutines.test.TestScope;
+
+public class QuickSettingsControllerBaseTest extends SysuiTestCase {
+    protected static final float QS_FRAME_START_X = 0f;
+    protected static final int QS_FRAME_WIDTH = 1000;
+    protected static final int QS_FRAME_TOP = 0;
+    protected static final int QS_FRAME_BOTTOM = 1000;
+    protected static final int DEFAULT_HEIGHT = 1000;
+    // In split shade min = max
+    protected static final int DEFAULT_MIN_HEIGHT_SPLIT_SHADE = DEFAULT_HEIGHT;
+    protected static final int DEFAULT_MIN_HEIGHT = 300;
+
+    protected QuickSettingsController mQsController;
+
+    protected TestScope mTestScope = TestScopeProvider.getTestScope();
+
+    @Mock
+    protected Resources mResources;
+    @Mock protected KeyguardBottomAreaView mQsFrame;
+    @Mock protected KeyguardStatusBarView mKeyguardStatusBar;
+    @Mock protected QS mQs;
+    @Mock protected QSFragment mQSFragment;
+    @Mock protected Lazy<NotificationPanelViewController> mPanelViewControllerLazy;
+    @Mock protected NotificationPanelViewController mNotificationPanelViewController;
+    @Mock protected NotificationPanelView mPanelView;
+    @Mock protected ViewGroup mQsHeader;
+    @Mock protected ViewParent mPanelViewParent;
+    @Mock protected QsFrameTranslateController mQsFrameTranslateController;
+    @Mock protected ShadeTransitionController mShadeTransitionController;
+    @Mock protected PulseExpansionHandler mPulseExpansionHandler;
+    @Mock protected NotificationRemoteInputManager mNotificationRemoteInputManager;
+    @Mock protected StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+    @Mock protected LightBarController mLightBarController;
+    @Mock protected NotificationStackScrollLayoutController
+            mNotificationStackScrollLayoutController;
+    @Mock protected LockscreenShadeTransitionController mLockscreenShadeTransitionController;
+    @Mock protected NotificationShadeDepthController mNotificationShadeDepthController;
+    @Mock protected ShadeHeaderController mShadeHeaderController;
+    @Mock protected StatusBarTouchableRegionManager mStatusBarTouchableRegionManager;
+    @Mock protected KeyguardStateController mKeyguardStateController;
+    @Mock protected KeyguardBypassController mKeyguardBypassController;
+    @Mock protected KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+    @Mock protected ScrimController mScrimController;
+    @Mock protected MediaDataManager mMediaDataManager;
+    @Mock protected MediaHierarchyManager mMediaHierarchyManager;
+    @Mock protected AmbientState mAmbientState;
+    @Mock protected RecordingController mRecordingController;
+    @Mock protected FalsingManager mFalsingManager;
+    @Mock protected FalsingCollector mFalsingCollector;
+    @Mock protected AccessibilityManager mAccessibilityManager;
+    @Mock protected LockscreenGestureLogger mLockscreenGestureLogger;
+    @Mock protected MetricsLogger mMetricsLogger;
+    @Mock protected FeatureFlags mFeatureFlags;
+    @Mock protected InteractionJankMonitor mInteractionJankMonitor;
+    @Mock protected ShadeLogger mShadeLogger;
+    @Mock protected DumpManager mDumpManager;
+    @Mock protected UiEventLogger mUiEventLogger;
+    @Mock protected CastController mCastController;
+    @Mock protected DeviceProvisionedController mDeviceProvisionedController;
+    @Mock protected UserInteractor mUserInteractor;
+    protected FakeDisableFlagsRepository mDisableFlagsRepository =
+            new FakeDisableFlagsRepository();
+    protected FakeKeyguardRepository mKeyguardRepository = new FakeKeyguardRepository();
+
+    protected SysuiStatusBarStateController mStatusBarStateController;
+    protected ShadeInteractor mShadeInteractor;
+
+    protected Handler mMainHandler;
+    protected LockscreenShadeTransitionController.Callback mLockscreenShadeTransitionCallback;
+
+    protected final ShadeExpansionStateManager mShadeExpansionStateManager =
+            new ShadeExpansionStateManager();
+
+    protected FragmentHostManager.FragmentListener mFragmentListener;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        when(mPanelViewControllerLazy.get()).thenReturn(mNotificationPanelViewController);
+        mStatusBarStateController = new StatusBarStateControllerImpl(mUiEventLogger, mDumpManager,
+                mInteractionJankMonitor, mShadeExpansionStateManager);
+
+        when(mDeviceProvisionedController.isDeviceProvisioned()).thenReturn(true);
+        mShadeInteractor =
+                new ShadeInteractor(
+                        mTestScope.getBackgroundScope(),
+                        mDisableFlagsRepository,
+                        mKeyguardRepository,
+                        new FakeUserSetupRepository(),
+                        mDeviceProvisionedController,
+                        mUserInteractor
+                );
+
+        KeyguardStatusView keyguardStatusView = new KeyguardStatusView(mContext);
+        keyguardStatusView.setId(R.id.keyguard_status_view);
+
+        when(mResources.getDimensionPixelSize(
+                R.dimen.lockscreen_shade_qs_transition_distance)).thenReturn(DEFAULT_HEIGHT);
+        when(mPanelView.getResources()).thenReturn(mResources);
+        when(mPanelView.getContext()).thenReturn(getContext());
+        when(mPanelView.findViewById(R.id.keyguard_header)).thenReturn(mKeyguardStatusBar);
+        when(mNotificationStackScrollLayoutController.getHeight()).thenReturn(1000);
+        when(mPanelView.findViewById(R.id.qs_frame)).thenReturn(mQsFrame);
+        when(mQsFrame.getX()).thenReturn(QS_FRAME_START_X);
+        when(mQsFrame.getWidth()).thenReturn(QS_FRAME_WIDTH);
+        when(mQsHeader.getTop()).thenReturn(QS_FRAME_TOP);
+        when(mQsHeader.getBottom()).thenReturn(QS_FRAME_BOTTOM);
+        when(mPanelView.getY()).thenReturn((float) QS_FRAME_TOP);
+        when(mPanelView.getHeight()).thenReturn(QS_FRAME_BOTTOM);
+        when(mPanelView.findViewById(R.id.keyguard_status_view))
+                .thenReturn(mock(KeyguardStatusView.class));
+        when(mQs.getView()).thenReturn(mPanelView);
+        when(mQSFragment.getView()).thenReturn(mPanelView);
+
+        when(mNotificationRemoteInputManager.isRemoteInputActive())
+                .thenReturn(false);
+        when(mInteractionJankMonitor.begin(any(), anyInt()))
+                .thenReturn(true);
+        when(mInteractionJankMonitor.end(anyInt()))
+                .thenReturn(true);
+
+        when(mPanelView.getParent()).thenReturn(mPanelViewParent);
+        when(mQs.getHeader()).thenReturn(mQsHeader);
+
+        doAnswer(invocation -> {
+            mLockscreenShadeTransitionCallback = invocation.getArgument(0);
+            return null;
+        }).when(mLockscreenShadeTransitionController).addCallback(any());
+
+
+        mMainHandler = new Handler(Looper.getMainLooper());
+
+        mQsController = new QuickSettingsController(
+                mPanelViewControllerLazy,
+                mPanelView,
+                mQsFrameTranslateController,
+                mShadeTransitionController,
+                mPulseExpansionHandler,
+                mNotificationRemoteInputManager,
+                mShadeExpansionStateManager,
+                mStatusBarKeyguardViewManager,
+                mLightBarController,
+                mNotificationStackScrollLayoutController,
+                mLockscreenShadeTransitionController,
+                mNotificationShadeDepthController,
+                mShadeHeaderController,
+                mStatusBarTouchableRegionManager,
+                mKeyguardStateController,
+                mKeyguardBypassController,
+                mKeyguardUpdateMonitor,
+                mScrimController,
+                mMediaDataManager,
+                mMediaHierarchyManager,
+                mAmbientState,
+                mRecordingController,
+                mFalsingManager,
+                mFalsingCollector,
+                mAccessibilityManager,
+                mLockscreenGestureLogger,
+                mMetricsLogger,
+                mFeatureFlags,
+                mInteractionJankMonitor,
+                mShadeLogger,
+                mDumpManager,
+                mock(KeyguardFaceAuthInteractor.class),
+                mock(ShadeRepository.class),
+                mShadeInteractor,
+                new JavaAdapter(mTestScope.getBackgroundScope()),
+                mCastController
+        );
+        mQsController.init();
+
+        mFragmentListener = mQsController.getQsFragmentListener();
+    }
+
+    @After
+    public void tearDown() {
+        mMainHandler.removeCallbacksAndMessages(null);
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java
index ff047aa..6d288e1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java
@@ -28,238 +28,32 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyFloat;
-import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.atLeastOnce;
-import static org.mockito.Mockito.doAnswer;
-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.content.res.Resources;
-import android.os.Handler;
-import android.os.Looper;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.view.MotionEvent;
-import android.view.ViewGroup;
-import android.view.ViewParent;
-import android.view.accessibility.AccessibilityManager;
 
 import androidx.test.filters.SmallTest;
 
-import com.android.internal.jank.InteractionJankMonitor;
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.UiEventLogger;
-import com.android.keyguard.KeyguardStatusView;
-import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.R;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.classifier.FalsingCollector;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.fragments.FragmentHostManager;
-import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
-import com.android.systemui.media.controls.pipeline.MediaDataManager;
-import com.android.systemui.media.controls.ui.MediaHierarchyManager;
-import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.qs.QS;
-import com.android.systemui.qs.QSFragment;
-import com.android.systemui.screenrecord.RecordingController;
-import com.android.systemui.shade.data.repository.ShadeRepository;
-import com.android.systemui.shade.transition.ShadeTransitionController;
-import com.android.systemui.statusbar.LockscreenShadeTransitionController;
-import com.android.systemui.statusbar.NotificationRemoteInputManager;
-import com.android.systemui.statusbar.NotificationShadeDepthController;
-import com.android.systemui.statusbar.PulseExpansionHandler;
-import com.android.systemui.statusbar.QsFrameTranslateController;
-import com.android.systemui.statusbar.StatusBarStateControllerImpl;
-import com.android.systemui.statusbar.SysuiStatusBarStateController;
-import com.android.systemui.statusbar.notification.stack.AmbientState;
-import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
-import com.android.systemui.statusbar.phone.KeyguardBottomAreaView;
-import com.android.systemui.statusbar.phone.KeyguardBypassController;
-import com.android.systemui.statusbar.phone.KeyguardStatusBarView;
-import com.android.systemui.statusbar.phone.LightBarController;
-import com.android.systemui.statusbar.phone.LockscreenGestureLogger;
-import com.android.systemui.statusbar.phone.ScrimController;
-import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
-import com.android.systemui.statusbar.phone.StatusBarTouchableRegionManager;
-import com.android.systemui.statusbar.policy.CastController;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
 
-import dagger.Lazy;
-
-import org.junit.After;
-import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
 
 import java.util.List;
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
-public class QuickSettingsControllerTest extends SysuiTestCase {
-
-    private static final float QS_FRAME_START_X = 0f;
-    private static final int QS_FRAME_WIDTH = 1000;
-    private static final int QS_FRAME_TOP = 0;
-    private static final int QS_FRAME_BOTTOM = 1000;
-    private static final int DEFAULT_HEIGHT = 1000;
-    // In split shade min = max
-    private static final int DEFAULT_MIN_HEIGHT_SPLIT_SHADE = DEFAULT_HEIGHT;
-    private static final int DEFAULT_MIN_HEIGHT = 300;
-
-    private QuickSettingsController mQsController;
-
-    @Mock private Resources mResources;
-    @Mock private KeyguardBottomAreaView mQsFrame;
-    @Mock private KeyguardStatusBarView mKeyguardStatusBar;
-    @Mock private QS mQs;
-    @Mock private QSFragment mQSFragment;
-    @Mock private Lazy<NotificationPanelViewController> mPanelViewControllerLazy;
-    @Mock private NotificationPanelViewController mNotificationPanelViewController;
-    @Mock private NotificationPanelView mPanelView;
-    @Mock private ViewGroup mQsHeader;
-    @Mock private ViewParent mPanelViewParent;
-    @Mock private QsFrameTranslateController mQsFrameTranslateController;
-    @Mock private ShadeTransitionController mShadeTransitionController;
-    @Mock private PulseExpansionHandler mPulseExpansionHandler;
-    @Mock private NotificationRemoteInputManager mNotificationRemoteInputManager;
-    @Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
-    @Mock private LightBarController mLightBarController;
-    @Mock private NotificationStackScrollLayoutController mNotificationStackScrollLayoutController;
-    @Mock private LockscreenShadeTransitionController mLockscreenShadeTransitionController;
-    @Mock private NotificationShadeDepthController mNotificationShadeDepthController;
-    @Mock private ShadeHeaderController mShadeHeaderController;
-    @Mock private StatusBarTouchableRegionManager mStatusBarTouchableRegionManager;
-    @Mock private KeyguardStateController mKeyguardStateController;
-    @Mock private KeyguardBypassController mKeyguardBypassController;
-    @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
-    @Mock private ScrimController mScrimController;
-    @Mock private MediaDataManager mMediaDataManager;
-    @Mock private MediaHierarchyManager mMediaHierarchyManager;
-    @Mock private AmbientState mAmbientState;
-    @Mock private RecordingController mRecordingController;
-    @Mock private FalsingManager mFalsingManager;
-    @Mock private FalsingCollector mFalsingCollector;
-    @Mock private AccessibilityManager mAccessibilityManager;
-    @Mock private LockscreenGestureLogger mLockscreenGestureLogger;
-    @Mock private MetricsLogger mMetricsLogger;
-    @Mock private FeatureFlags mFeatureFlags;
-    @Mock private InteractionJankMonitor mInteractionJankMonitor;
-    @Mock private ShadeLogger mShadeLogger;
-    @Mock private DumpManager mDumpManager;
-    @Mock private UiEventLogger mUiEventLogger;
-    @Mock private CastController mCastController;
-
-    private SysuiStatusBarStateController mStatusBarStateController;
-
-    private Handler mMainHandler;
-    private LockscreenShadeTransitionController.Callback mLockscreenShadeTransitionCallback;
-
-    private final ShadeExpansionStateManager mShadeExpansionStateManager =
-            new ShadeExpansionStateManager();
-
-    private FragmentHostManager.FragmentListener mFragmentListener;
-
-    @Before
-    public void setup() {
-        MockitoAnnotations.initMocks(this);
-        when(mPanelViewControllerLazy.get()).thenReturn(mNotificationPanelViewController);
-        mStatusBarStateController = new StatusBarStateControllerImpl(mUiEventLogger, mDumpManager,
-                mInteractionJankMonitor, mShadeExpansionStateManager);
-
-        KeyguardStatusView keyguardStatusView = new KeyguardStatusView(mContext);
-        keyguardStatusView.setId(R.id.keyguard_status_view);
-
-        when(mResources.getDimensionPixelSize(
-                R.dimen.lockscreen_shade_qs_transition_distance)).thenReturn(DEFAULT_HEIGHT);
-        when(mPanelView.getResources()).thenReturn(mResources);
-        when(mPanelView.getContext()).thenReturn(getContext());
-        when(mPanelView.findViewById(R.id.keyguard_header)).thenReturn(mKeyguardStatusBar);
-        when(mNotificationStackScrollLayoutController.getHeight()).thenReturn(1000);
-        when(mPanelView.findViewById(R.id.qs_frame)).thenReturn(mQsFrame);
-        when(mQsFrame.getX()).thenReturn(QS_FRAME_START_X);
-        when(mQsFrame.getWidth()).thenReturn(QS_FRAME_WIDTH);
-        when(mQsHeader.getTop()).thenReturn(QS_FRAME_TOP);
-        when(mQsHeader.getBottom()).thenReturn(QS_FRAME_BOTTOM);
-        when(mPanelView.getY()).thenReturn((float) QS_FRAME_TOP);
-        when(mPanelView.getHeight()).thenReturn(QS_FRAME_BOTTOM);
-        when(mPanelView.findViewById(R.id.keyguard_status_view))
-                .thenReturn(mock(KeyguardStatusView.class));
-        when(mQs.getView()).thenReturn(mPanelView);
-        when(mQSFragment.getView()).thenReturn(mPanelView);
-
-        when(mNotificationRemoteInputManager.isRemoteInputActive())
-                .thenReturn(false);
-        when(mInteractionJankMonitor.begin(any(), anyInt()))
-                .thenReturn(true);
-        when(mInteractionJankMonitor.end(anyInt()))
-                .thenReturn(true);
-
-        when(mPanelView.getParent()).thenReturn(mPanelViewParent);
-        when(mQs.getHeader()).thenReturn(mQsHeader);
-
-        doAnswer(invocation -> {
-            mLockscreenShadeTransitionCallback = invocation.getArgument(0);
-            return null;
-        }).when(mLockscreenShadeTransitionController).addCallback(any());
-
-
-        mMainHandler = new Handler(Looper.getMainLooper());
-
-        mQsController = new QuickSettingsController(
-                mPanelViewControllerLazy,
-                mPanelView,
-                mQsFrameTranslateController,
-                mShadeTransitionController,
-                mPulseExpansionHandler,
-                mNotificationRemoteInputManager,
-                mShadeExpansionStateManager,
-                mStatusBarKeyguardViewManager,
-                mLightBarController,
-                mNotificationStackScrollLayoutController,
-                mLockscreenShadeTransitionController,
-                mNotificationShadeDepthController,
-                mShadeHeaderController,
-                mStatusBarTouchableRegionManager,
-                mKeyguardStateController,
-                mKeyguardBypassController,
-                mKeyguardUpdateMonitor,
-                mScrimController,
-                mMediaDataManager,
-                mMediaHierarchyManager,
-                mAmbientState,
-                mRecordingController,
-                mFalsingManager,
-                mFalsingCollector,
-                mAccessibilityManager,
-                mLockscreenGestureLogger,
-                mMetricsLogger,
-                mFeatureFlags,
-                mInteractionJankMonitor,
-                mShadeLogger,
-                mDumpManager,
-                mock(KeyguardFaceAuthInteractor.class),
-                mock(ShadeRepository.class),
-                mCastController
-        );
-
-        mFragmentListener = mQsController.getQsFragmentListener();
-    }
-
-    @After
-    public void tearDown() {
-        mMainHandler.removeCallbacksAndMessages(null);
-    }
+public class QuickSettingsControllerTest extends QuickSettingsControllerBaseTest {
 
     @Test
     public void testCloseQsSideEffects() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerWithCoroutinesTest.kt
new file mode 100644
index 0000000..cc4a063
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerWithCoroutinesTest.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.shade
+
+import android.app.StatusBarManager
+import androidx.test.filters.SmallTest
+import com.android.systemui.statusbar.disableflags.data.model.DisableFlagsModel
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+
+@SmallTest
+@OptIn(ExperimentalCoroutinesApi::class)
+class QuickSettingsControllerWithCoroutinesTest : QuickSettingsControllerBaseTest() {
+
+    @Test
+    fun isExpansionEnabled_dozing_false() =
+        mTestScope.runTest {
+            mKeyguardRepository.setIsDozing(true)
+            runCurrent()
+
+            assertThat(mQsController.isExpansionEnabled).isFalse()
+        }
+
+    @Test
+    fun isExpansionEnabled_notDozing_true() =
+        mTestScope.runTest {
+            mKeyguardRepository.setIsDozing(false)
+            runCurrent()
+
+            assertThat(mQsController.isExpansionEnabled).isTrue()
+        }
+
+    @Test
+    fun isExpansionEnabled_qsDisabled_false() =
+        mTestScope.runTest {
+            mDisableFlagsRepository.disableFlags.value =
+                DisableFlagsModel(
+                    StatusBarManager.DISABLE_NONE,
+                    StatusBarManager.DISABLE2_QUICK_SETTINGS
+                )
+            runCurrent()
+
+            assertThat(mQsController.isExpansionEnabled).isFalse()
+        }
+
+    @Test
+    fun isExpansionEnabled_shadeDisabled_false() =
+        mTestScope.runTest {
+            mDisableFlagsRepository.disableFlags.value =
+                DisableFlagsModel(
+                    StatusBarManager.DISABLE_NONE,
+                    StatusBarManager.DISABLE2_NOTIFICATION_SHADE
+                )
+            runCurrent()
+
+            assertThat(mQsController.isExpansionEnabled).isFalse()
+        }
+
+    @Test
+    fun isExpansionEnabled_qsAndShadeEnabled_true() =
+        mTestScope.runTest {
+            mDisableFlagsRepository.disableFlags.value =
+                DisableFlagsModel(StatusBarManager.DISABLE_NONE, StatusBarManager.DISABLE2_NONE)
+            runCurrent()
+
+            assertThat(mQsController.isExpansionEnabled).isTrue()
+        }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt
new file mode 100644
index 0000000..52e0c9c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt
@@ -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 com.android.systemui.shade
+
+import android.testing.AndroidTestingRunner
+import android.view.Display
+import android.view.WindowManager
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.assist.AssistManager
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.statusbar.NotificationShadeWindowController
+import com.android.systemui.statusbar.notification.row.NotificationGutsManager
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
+import com.android.systemui.statusbar.policy.DeviceProvisionedController
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.statusbar.window.StatusBarWindowController
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.FakeSystemClock
+import dagger.Lazy
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class ShadeControllerImplTest : SysuiTestCase() {
+    @Mock private lateinit var commandQueue: CommandQueue
+    @Mock private lateinit var keyguardStateController: KeyguardStateController
+    @Mock private lateinit var statusBarStateController: StatusBarStateController
+    @Mock private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager
+    @Mock private lateinit var statusBarWindowController: StatusBarWindowController
+    @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController
+    @Mock private lateinit var notificationShadeWindowController: NotificationShadeWindowController
+    @Mock private lateinit var windowManager: WindowManager
+    @Mock private lateinit var assistManager: AssistManager
+    @Mock private lateinit var gutsManager: NotificationGutsManager
+    @Mock private lateinit var shadeViewController: ShadeViewController
+    @Mock private lateinit var nswvc: NotificationShadeWindowViewController
+    @Mock private lateinit var display: Display
+
+    private lateinit var shadeController: ShadeControllerImpl
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        whenever(windowManager.defaultDisplay).thenReturn(display)
+        whenever(deviceProvisionedController.isCurrentUserSetup).thenReturn(true)
+        shadeController =
+            ShadeControllerImpl(
+                commandQueue,
+                FakeExecutor(FakeSystemClock()),
+                keyguardStateController,
+                statusBarStateController,
+                statusBarKeyguardViewManager,
+                statusBarWindowController,
+                deviceProvisionedController,
+                notificationShadeWindowController,
+                windowManager,
+                Lazy { shadeViewController },
+                Lazy { assistManager },
+                Lazy { gutsManager },
+            )
+        shadeController.setNotificationShadeWindowViewController(nswvc)
+    }
+
+    @Test
+    fun testDisableNotificationShade() {
+        whenever(commandQueue.panelsEnabled()).thenReturn(false)
+
+        // Trying to open it does nothing.
+        shadeController.animateExpandShade()
+        verify(shadeViewController, never()).expandToNotifications()
+        shadeController.animateExpandQs()
+        verify(shadeViewController, never()).expand(ArgumentMatchers.anyBoolean())
+    }
+
+    @Test
+    fun testEnableNotificationShade() {
+        whenever(commandQueue.panelsEnabled()).thenReturn(true)
+
+        // Can now be opened.
+        shadeController.animateExpandShade()
+        verify(shadeViewController).expandToNotifications()
+        shadeController.animateExpandQs()
+        verify(shadeViewController).expandToQs()
+    }
+
+    @Test
+    fun cancelExpansionAndCollapseShade_callsCancelCurrentTouch() {
+        // GIVEN the shade is tracking a touch
+        whenever(shadeViewController.isTracking).thenReturn(true)
+
+        // WHEN cancelExpansionAndCollapseShade is called
+        shadeController.cancelExpansionAndCollapseShade()
+
+        // VERIFY that cancelCurrentTouch is called
+        verify(nswvc).cancelCurrentTouch()
+    }
+
+    @Test
+    fun cancelExpansionAndCollapseShade_doesNotCallAnimateCollapseShade_whenCollapsed() {
+        // GIVEN the shade is tracking a touch
+        whenever(shadeViewController.isTracking).thenReturn(false)
+
+        // WHEN cancelExpansionAndCollapseShade is called
+        shadeController.cancelExpansionAndCollapseShade()
+
+        // VERIFY that cancelCurrentTouch is NOT called
+        verify(nswvc, never()).cancelCurrentTouch()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt
index 2da2e92..bf25f29 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt
@@ -29,6 +29,7 @@
 import android.view.View
 import android.view.ViewPropertyAnimator
 import android.view.WindowInsets
+import android.widget.LinearLayout
 import android.widget.TextView
 import androidx.constraintlayout.motion.widget.MotionLayout
 import androidx.constraintlayout.widget.ConstraintSet
@@ -127,6 +128,7 @@
     var viewVisibility = View.GONE
     var viewAlpha = 1f
 
+    private val systemIcons = LinearLayout(context)
     private lateinit var shadeHeaderController: ShadeHeaderController
     private lateinit var carrierIconSlots: List<String>
     private val configurationController = FakeConfigurationController()
@@ -146,6 +148,7 @@
             .thenReturn(batteryMeterView)
 
         whenever<StatusIconContainer>(view.findViewById(R.id.statusIcons)).thenReturn(statusIcons)
+        whenever<View>(view.findViewById(R.id.shade_header_system_icons)).thenReturn(systemIcons)
 
         viewContext = Mockito.spy(context)
         whenever(view.context).thenReturn(viewContext)
@@ -237,11 +240,22 @@
         whenever(mShadeCarrierGroupController.isSingleCarrier).thenReturn(false)
 
         makeShadeVisible()
+        shadeHeaderController.qsExpandedFraction = 1.0f
 
         verify(statusIcons).addIgnoredSlots(carrierIconSlots)
     }
 
     @Test
+    fun dualCarrier_enablesCarrierIconsInStatusIcons_qsExpanded() {
+        whenever(mShadeCarrierGroupController.isSingleCarrier).thenReturn(false)
+
+        makeShadeVisible()
+        shadeHeaderController.qsExpandedFraction = 0.0f
+
+        verify(statusIcons, times(2)).removeIgnoredSlots(carrierIconSlots)
+    }
+
+    @Test
     fun disableQS_notDisabled_visible() {
         makeShadeVisible()
         shadeHeaderController.disable(0, 0, false)
@@ -440,6 +454,17 @@
     }
 
     @Test
+    fun testLargeScreenActive_collapseActionRun_onSystemIconsClick() {
+        shadeHeaderController.largeScreenActive = true
+        var wasRun = false
+        shadeHeaderController.shadeCollapseAction = Runnable { wasRun = true }
+
+        systemIcons.performClick()
+
+        assertThat(wasRun).isTrue()
+    }
+
+    @Test
     fun testShadeExpandedFraction() {
         // View needs to be visible for this to actually take effect
         shadeHeaderController.qsVisible = true
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/carrier/ShadeCarrierTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/carrier/ShadeCarrierTest.java
index 4461310..dae9c97 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/carrier/ShadeCarrierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/carrier/ShadeCarrierTest.java
@@ -20,8 +20,10 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
+import android.content.Context;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
+import android.view.ContextThemeWrapper;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.widget.TextView;
@@ -48,7 +50,9 @@
     @Before
     public void setUp() throws Exception {
         mTestableLooper = TestableLooper.get(this);
-        LayoutInflater inflater = LayoutInflater.from(mContext);
+        Context themedContext =
+                new ContextThemeWrapper(mContext, R.style.Theme_SystemUI_QuickSettings);
+        LayoutInflater inflater = LayoutInflater.from(themedContext);
         mContext.ensureTestableResources();
         mTestableLooper.runWithLooper(() ->
                 mShadeCarrier = (ShadeCarrier) inflater.inflate(R.layout.shade_carrier, null));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt
new file mode 100644
index 0000000..cdcd1a2
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt
@@ -0,0 +1,356 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.data.repository
+
+import android.app.ActivityManager
+import android.app.StatusBarManager.DISABLE2_NONE
+import android.app.StatusBarManager.DISABLE2_NOTIFICATION_SHADE
+import android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS
+import android.content.pm.UserInfo
+import android.os.UserManager
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.UiEventLogger
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.SysuiTestCase
+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.domain.interactor.KeyguardInteractorFactory
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.statusbar.disableflags.data.model.DisableFlagsModel
+import com.android.systemui.statusbar.disableflags.data.repository.FakeDisableFlagsRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository
+import com.android.systemui.statusbar.policy.DeviceProvisionedController
+import com.android.systemui.telephony.data.repository.FakeTelephonyRepository
+import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
+import com.android.systemui.user.data.model.UserSwitcherSettingsModel
+import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.user.domain.interactor.GuestUserInteractor
+import com.android.systemui.user.domain.interactor.HeadlessSystemUserMode
+import com.android.systemui.user.domain.interactor.RefreshUsersScheduler
+import com.android.systemui.user.domain.interactor.UserInteractor
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@OptIn(ExperimentalCoroutinesApi::class)
+class ShadeInteractorTest : SysuiTestCase() {
+    private lateinit var underTest: ShadeInteractor
+
+    private val testDispatcher = StandardTestDispatcher()
+    private val testScope = TestScope(testDispatcher)
+    private val featureFlags = FakeFeatureFlags()
+    private val userSetupRepository = FakeUserSetupRepository()
+    private val userRepository = FakeUserRepository()
+    private val disableFlagsRepository = FakeDisableFlagsRepository()
+    private val keyguardRepository = FakeKeyguardRepository()
+
+    @Mock private lateinit var manager: UserManager
+    @Mock private lateinit var headlessSystemUserMode: HeadlessSystemUserMode
+    @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController
+    @Mock private lateinit var activityStarter: ActivityStarter
+    @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+    @Mock private lateinit var activityManager: ActivityManager
+    @Mock private lateinit var uiEventLogger: UiEventLogger
+    @Mock private lateinit var guestInteractor: GuestUserInteractor
+
+    private lateinit var userInteractor: UserInteractor
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        featureFlags.set(Flags.FACE_AUTH_REFACTOR, false)
+        featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
+
+        val refreshUsersScheduler =
+            RefreshUsersScheduler(
+                applicationScope = testScope.backgroundScope,
+                mainDispatcher = testDispatcher,
+                repository = userRepository,
+            )
+
+        runBlocking {
+            val userInfos =
+                listOf(
+                    UserInfo(
+                        /* id= */ 0,
+                        /* name= */ "zero",
+                        /* iconPath= */ "",
+                        /* flags= */ UserInfo.FLAG_PRIMARY or
+                            UserInfo.FLAG_ADMIN or
+                            UserInfo.FLAG_FULL,
+                        UserManager.USER_TYPE_FULL_SYSTEM,
+                    ),
+                )
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+        }
+        userInteractor =
+            UserInteractor(
+                applicationContext = context,
+                repository = userRepository,
+                activityStarter = activityStarter,
+                keyguardInteractor =
+                    KeyguardInteractorFactory.create(featureFlags = featureFlags)
+                        .keyguardInteractor,
+                featureFlags = featureFlags,
+                manager = manager,
+                headlessSystemUserMode = headlessSystemUserMode,
+                applicationScope = testScope.backgroundScope,
+                telephonyInteractor =
+                    TelephonyInteractor(
+                        repository = FakeTelephonyRepository(),
+                    ),
+                broadcastDispatcher = fakeBroadcastDispatcher,
+                keyguardUpdateMonitor = keyguardUpdateMonitor,
+                backgroundDispatcher = testDispatcher,
+                activityManager = activityManager,
+                refreshUsersScheduler = refreshUsersScheduler,
+                guestUserInteractor = guestInteractor,
+                uiEventLogger = uiEventLogger,
+            )
+        underTest =
+            ShadeInteractor(
+                testScope.backgroundScope,
+                disableFlagsRepository,
+                keyguardRepository,
+                userSetupRepository,
+                deviceProvisionedController,
+                userInteractor,
+            )
+    }
+
+    @Test
+    fun isShadeEnabled_matchesDisableFlagsRepo() =
+        testScope.runTest {
+            val actual by collectLastValue(underTest.isShadeEnabled)
+
+            disableFlagsRepository.disableFlags.value =
+                DisableFlagsModel(disable2 = DISABLE2_NOTIFICATION_SHADE)
+            assertThat(actual).isFalse()
+
+            disableFlagsRepository.disableFlags.value = DisableFlagsModel(disable2 = DISABLE2_NONE)
+
+            assertThat(actual).isTrue()
+        }
+
+    @Test
+    fun isExpandToQsEnabled_deviceNotProvisioned_false() =
+        testScope.runTest {
+            whenever(deviceProvisionedController.isDeviceProvisioned).thenReturn(false)
+
+            val actual by collectLastValue(underTest.isExpandToQsEnabled)
+
+            assertThat(actual).isFalse()
+        }
+
+    @Test
+    fun isExpandToQsEnabled_userNotSetupAndSimpleUserSwitcher_false() =
+        testScope.runTest {
+            whenever(deviceProvisionedController.isDeviceProvisioned).thenReturn(true)
+
+            userSetupRepository.setUserSetup(false)
+            userRepository.setSettings(UserSwitcherSettingsModel(isSimpleUserSwitcher = true))
+
+            val actual by collectLastValue(underTest.isExpandToQsEnabled)
+
+            assertThat(actual).isFalse()
+        }
+
+    @Test
+    fun isExpandToQsEnabled_shadeNotEnabled_false() =
+        testScope.runTest {
+            whenever(deviceProvisionedController.isDeviceProvisioned).thenReturn(true)
+            userSetupRepository.setUserSetup(true)
+
+            disableFlagsRepository.disableFlags.value =
+                DisableFlagsModel(
+                    disable2 = DISABLE2_NOTIFICATION_SHADE,
+                )
+
+            val actual by collectLastValue(underTest.isExpandToQsEnabled)
+
+            assertThat(actual).isFalse()
+        }
+
+    @Test
+    fun isExpandToQsEnabled_quickSettingsNotEnabled_false() =
+        testScope.runTest {
+            whenever(deviceProvisionedController.isDeviceProvisioned).thenReturn(true)
+            userSetupRepository.setUserSetup(true)
+
+            disableFlagsRepository.disableFlags.value =
+                DisableFlagsModel(
+                    disable2 = DISABLE2_QUICK_SETTINGS,
+                )
+            val actual by collectLastValue(underTest.isExpandToQsEnabled)
+
+            assertThat(actual).isFalse()
+        }
+
+    @Test
+    fun isExpandToQsEnabled_dozing_false() =
+        testScope.runTest {
+            whenever(deviceProvisionedController.isDeviceProvisioned).thenReturn(true)
+            userSetupRepository.setUserSetup(true)
+            disableFlagsRepository.disableFlags.value =
+                DisableFlagsModel(
+                    disable2 = DISABLE2_NONE,
+                )
+
+            keyguardRepository.setIsDozing(true)
+
+            val actual by collectLastValue(underTest.isExpandToQsEnabled)
+
+            assertThat(actual).isFalse()
+        }
+
+    @Test
+    fun isExpandToQsEnabled_userSetup_true() =
+        testScope.runTest {
+            whenever(deviceProvisionedController.isDeviceProvisioned).thenReturn(true)
+            keyguardRepository.setIsDozing(false)
+            disableFlagsRepository.disableFlags.value =
+                DisableFlagsModel(
+                    disable2 = DISABLE2_NONE,
+                )
+
+            userSetupRepository.setUserSetup(true)
+
+            val actual by collectLastValue(underTest.isExpandToQsEnabled)
+
+            assertThat(actual).isTrue()
+        }
+
+    @Test
+    fun isExpandToQsEnabled_notSimpleUserSwitcher_true() =
+        testScope.runTest {
+            whenever(deviceProvisionedController.isDeviceProvisioned).thenReturn(true)
+            keyguardRepository.setIsDozing(false)
+            disableFlagsRepository.disableFlags.value =
+                DisableFlagsModel(
+                    disable2 = DISABLE2_NONE,
+                )
+
+            userRepository.setSettings(UserSwitcherSettingsModel(isSimpleUserSwitcher = false))
+
+            val actual by collectLastValue(underTest.isExpandToQsEnabled)
+
+            assertThat(actual).isTrue()
+        }
+
+    @Test
+    fun isExpandToQsEnabled_respondsToDozingUpdates() =
+        testScope.runTest {
+            whenever(deviceProvisionedController.isDeviceProvisioned).thenReturn(true)
+            keyguardRepository.setIsDozing(false)
+            disableFlagsRepository.disableFlags.value =
+                DisableFlagsModel(
+                    disable2 = DISABLE2_NONE,
+                )
+            userSetupRepository.setUserSetup(true)
+
+            val actual by collectLastValue(underTest.isExpandToQsEnabled)
+
+            assertThat(actual).isTrue()
+
+            // WHEN dozing starts
+            keyguardRepository.setIsDozing(true)
+
+            // THEN expand is disabled
+            assertThat(actual).isFalse()
+
+            // WHEN dozing stops
+            keyguardRepository.setIsDozing(false)
+
+            // THEN expand is enabled
+            assertThat(actual).isTrue()
+        }
+
+    @Test
+    fun isExpandToQsEnabled_respondsToDisableUpdates() =
+        testScope.runTest {
+            whenever(deviceProvisionedController.isDeviceProvisioned).thenReturn(true)
+            keyguardRepository.setIsDozing(false)
+            disableFlagsRepository.disableFlags.value =
+                DisableFlagsModel(
+                    disable2 = DISABLE2_NONE,
+                )
+            userSetupRepository.setUserSetup(true)
+
+            val actual by collectLastValue(underTest.isExpandToQsEnabled)
+
+            assertThat(actual).isTrue()
+
+            // WHEN QS is disabled
+            disableFlagsRepository.disableFlags.value =
+                DisableFlagsModel(
+                    disable2 = DISABLE2_QUICK_SETTINGS,
+                )
+            // THEN expand is disabled
+            assertThat(actual).isFalse()
+
+            // WHEN QS is enabled
+            disableFlagsRepository.disableFlags.value =
+                DisableFlagsModel(
+                    disable2 = DISABLE2_NONE,
+                )
+            // THEN expand is enabled
+            assertThat(actual).isTrue()
+        }
+
+    @Test
+    fun isExpandToQsEnabled_respondsToUserUpdates() =
+        testScope.runTest {
+            whenever(deviceProvisionedController.isDeviceProvisioned).thenReturn(true)
+            keyguardRepository.setIsDozing(false)
+            disableFlagsRepository.disableFlags.value =
+                DisableFlagsModel(
+                    disable2 = DISABLE2_NONE,
+                )
+            userSetupRepository.setUserSetup(true)
+
+            val actual by collectLastValue(underTest.isExpandToQsEnabled)
+
+            assertThat(actual).isTrue()
+
+            // WHEN the user is no longer setup
+            userSetupRepository.setUserSetup(false)
+            userRepository.setSettings(UserSwitcherSettingsModel(isSimpleUserSwitcher = true))
+
+            // THEN expand is disabled
+            assertThat(actual).isFalse()
+
+            // WHEN the user is setup again
+            userSetupRepository.setUserSetup(true)
+
+            // THEN expand is enabled
+            assertThat(actual).isTrue()
+        }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ScrimShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ScrimShadeTransitionControllerTest.kt
index cbf5485..a5bd2ae 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ScrimShadeTransitionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ScrimShadeTransitionControllerTest.kt
@@ -2,26 +2,16 @@
 
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
-import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.DumpManager
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
-import com.android.systemui.shade.STATE_CLOSED
-import com.android.systemui.shade.STATE_OPEN
 import com.android.systemui.shade.STATE_OPENING
 import com.android.systemui.shade.ShadeExpansionChangeEvent
-import com.android.systemui.statusbar.StatusBarState
-import com.android.systemui.statusbar.SysuiStatusBarStateController
 import com.android.systemui.statusbar.phone.ScrimController
-import com.android.systemui.statusbar.policy.FakeConfigurationController
-import com.android.systemui.statusbar.policy.HeadsUpManager
 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.Mockito.`when` as whenever
 import org.mockito.MockitoAnnotations
 
 @RunWith(AndroidTestingRunner::class)
@@ -30,10 +20,6 @@
 
     @Mock private lateinit var scrimController: ScrimController
     @Mock private lateinit var dumpManager: DumpManager
-    @Mock private lateinit var statusBarStateController: SysuiStatusBarStateController
-    @Mock private lateinit var headsUpManager: HeadsUpManager
-    @Mock private lateinit var featureFlags: FeatureFlags
-    private val configurationController = FakeConfigurationController()
 
     private lateinit var controller: ScrimShadeTransitionController
 
@@ -41,131 +27,25 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
         context.ensureTestableResources()
-        controller =
-            ScrimShadeTransitionController(
-                configurationController,
-                dumpManager,
-                scrimController,
-                context.resources,
-                statusBarStateController,
-                headsUpManager,
-                featureFlags)
+        controller = ScrimShadeTransitionController(dumpManager, scrimController)
 
         controller.onPanelStateChanged(STATE_OPENING)
     }
 
     @Test
-    fun onPanelExpansionChanged_inSingleShade_setsFractionEqualToEventFraction() {
-        setSplitShadeEnabled(false)
-
+    fun onPanelExpansionChanged_setsFractionEqualToEventFraction() {
         controller.onPanelExpansionChanged(EXPANSION_EVENT)
 
         verify(scrimController).setRawPanelExpansionFraction(EXPANSION_EVENT.fraction)
     }
 
-    @Test
-    fun onPanelExpansionChanged_inSplitShade_unlockedShade_setsFractionBasedOnDragDownAmount() {
-        whenever(statusBarStateController.currentOrUpcomingState).thenReturn(StatusBarState.SHADE)
-        val scrimShadeTransitionDistance =
-            context.resources.getDimensionPixelSize(R.dimen.split_shade_scrim_transition_distance)
-        setSplitShadeEnabled(true)
-
-        controller.onPanelExpansionChanged(EXPANSION_EVENT)
-
-        val expectedFraction = EXPANSION_EVENT.dragDownPxAmount / scrimShadeTransitionDistance
-        verify(scrimController).setRawPanelExpansionFraction(expectedFraction)
-    }
-
-    @Test
-    fun onPanelExpansionChanged_inSplitShade_largeDragDownAmount_fractionIsNotGreaterThan1() {
-        whenever(statusBarStateController.currentOrUpcomingState).thenReturn(StatusBarState.SHADE)
-        val scrimShadeTransitionDistance =
-            context.resources.getDimensionPixelSize(R.dimen.split_shade_scrim_transition_distance)
-        setSplitShadeEnabled(true)
-
-        controller.onPanelExpansionChanged(
-            EXPANSION_EVENT.copy(dragDownPxAmount = 100f * scrimShadeTransitionDistance))
-
-        verify(scrimController).setRawPanelExpansionFraction(1f)
-    }
-
-    @Test
-    fun onPanelExpansionChanged_inSplitShade_negativeDragDownAmount_fractionIsNotLessThan0() {
-        whenever(statusBarStateController.currentOrUpcomingState).thenReturn(StatusBarState.SHADE)
-        setSplitShadeEnabled(true)
-
-        controller.onPanelExpansionChanged(EXPANSION_EVENT.copy(dragDownPxAmount = -100f))
-
-        verify(scrimController).setRawPanelExpansionFraction(0f)
-    }
-
-    @Test
-    fun onPanelExpansionChanged_inSplitShade_onLockedShade_setsFractionEqualToEventFraction() {
-        whenever(statusBarStateController.currentOrUpcomingState)
-            .thenReturn(StatusBarState.SHADE_LOCKED)
-        setSplitShadeEnabled(true)
-
-        controller.onPanelExpansionChanged(EXPANSION_EVENT)
-
-        verify(scrimController).setRawPanelExpansionFraction(EXPANSION_EVENT.fraction)
-    }
-
-    @Test
-    fun onPanelExpansionChanged_inSplitShade_flagTrue_setsFractionEqualToEventFraction() {
-        whenever(featureFlags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION))
-                .thenReturn(true)
-        whenever(statusBarStateController.currentOrUpcomingState)
-            .thenReturn(StatusBarState.SHADE)
-        setSplitShadeEnabled(true)
-
-        controller.onPanelExpansionChanged(EXPANSION_EVENT)
-
-        verify(scrimController).setRawPanelExpansionFraction(EXPANSION_EVENT.fraction)
-    }
-
-    @Test
-    fun onPanelExpansionChanged_inSplitShade_onKeyguard_setsFractionEqualToEventFraction() {
-        whenever(statusBarStateController.currentOrUpcomingState)
-            .thenReturn(StatusBarState.KEYGUARD)
-        setSplitShadeEnabled(true)
-
-        controller.onPanelExpansionChanged(EXPANSION_EVENT)
-
-        verify(scrimController).setRawPanelExpansionFraction(EXPANSION_EVENT.fraction)
-    }
-
-    @Test
-    fun onPanelExpansionChanged_inSplitShade_panelOpen_setsFractionEqualToEventFraction() {
-        controller.onPanelStateChanged(STATE_OPEN)
-        whenever(statusBarStateController.currentOrUpcomingState)
-            .thenReturn(StatusBarState.KEYGUARD)
-        setSplitShadeEnabled(true)
-
-        controller.onPanelExpansionChanged(EXPANSION_EVENT)
-
-        verify(scrimController).setRawPanelExpansionFraction(EXPANSION_EVENT.fraction)
-    }
-
-    @Test
-    fun onPanelExpansionChanged_inSplitShade_panelClosed_setsFractionEqualToEventFraction() {
-        controller.onPanelStateChanged(STATE_CLOSED)
-        whenever(statusBarStateController.currentOrUpcomingState)
-            .thenReturn(StatusBarState.KEYGUARD)
-        setSplitShadeEnabled(true)
-
-        controller.onPanelExpansionChanged(EXPANSION_EVENT)
-
-        verify(scrimController).setRawPanelExpansionFraction(EXPANSION_EVENT.fraction)
-    }
-
-    private fun setSplitShadeEnabled(enabled: Boolean) {
-        overrideResource(R.bool.config_use_split_notification_shade, enabled)
-        configurationController.notifyConfigurationChanged()
-    }
-
     companion object {
         val EXPANSION_EVENT =
             ShadeExpansionChangeEvent(
-                fraction = 0.5f, expanded = true, tracking = true, dragDownPxAmount = 10f)
+                fraction = 0.5f,
+                expanded = true,
+                tracking = true,
+                dragDownPxAmount = 10f
+            )
     }
 }
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 69d03d9..6e9fba6 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
@@ -22,12 +22,10 @@
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.keyguard.domain.interactor.LockscreenSceneInteractor
 import com.android.systemui.scene.SceneTestUtils
-import com.android.systemui.scene.SceneTestUtils.Companion.CONTAINER_1
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
@@ -39,8 +37,8 @@
 @RunWith(JUnit4::class)
 class ShadeSceneViewModelTest : SysuiTestCase() {
 
-    private val testScope = TestScope()
-    private val utils = SceneTestUtils(this, testScope)
+    private val utils = SceneTestUtils(this)
+    private val testScope = utils.testScope
     private val sceneInteractor = utils.sceneInteractor()
     private val authenticationInteractor =
         utils.authenticationInteractor(
@@ -55,7 +53,6 @@
                     override fun create(containerName: String): LockscreenSceneInteractor {
                         return utils.lockScreenSceneInteractor(
                             authenticationInteractor = authenticationInteractor,
-                            sceneInteractor = sceneInteractor,
                             bouncerInteractor =
                                 utils.bouncerInteractor(
                                     authenticationInteractor = authenticationInteractor,
@@ -71,8 +68,8 @@
     fun upTransitionSceneKey_deviceLocked_lockScreen() =
         testScope.runTest {
             val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
-            authenticationInteractor.lockDevice()
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            utils.authenticationRepository.setUnlocked(false)
 
             assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Lockscreen)
         }
@@ -81,8 +78,8 @@
     fun upTransitionSceneKey_deviceUnlocked_gone() =
         testScope.runTest {
             val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
-            authenticationInteractor.unlockDevice()
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            utils.authenticationRepository.setUnlocked(true)
 
             assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Gone)
         }
@@ -90,9 +87,10 @@
     @Test
     fun onContentClicked_deviceUnlocked_switchesToGone() =
         testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
-            authenticationInteractor.unlockDevice()
+            val currentScene by
+                collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            utils.authenticationRepository.setUnlocked(true)
             runCurrent()
 
             underTest.onContentClicked()
@@ -103,9 +101,10 @@
     @Test
     fun onContentClicked_deviceLockedSecurely_switchesToBouncer() =
         testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
-            authenticationInteractor.lockDevice()
+            val currentScene by
+                collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            utils.authenticationRepository.setUnlocked(false)
             runCurrent()
 
             underTest.onContentClicked()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
index 04c93cb..9495fdd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
@@ -21,6 +21,8 @@
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags.TRANSIT_CLOCK
 import com.android.systemui.plugins.ClockController
 import com.android.systemui.plugins.ClockId
 import com.android.systemui.plugins.ClockMetadata
@@ -68,6 +70,7 @@
     private lateinit var fakeDefaultProvider: FakeClockPlugin
     private lateinit var pluginListener: PluginListener<ClockProviderPlugin>
     private lateinit var registry: ClockRegistry
+    private val featureFlags = FakeFeatureFlags()
 
     companion object {
         private fun failFactory(clockId: ClockId): ClockController {
@@ -448,4 +451,44 @@
         val actual = ClockSettings.serialize(ClockSettings("ID", null))
         assertEquals(expected, actual)
     }
+
+    @Test
+    fun testTransitClockEnabled_hasTransitClock() {
+        testTransitClockFlag(true)
+    }
+
+    @Test
+    fun testTransitClockDisabled_noTransitClock() {
+        testTransitClockFlag(false)
+    }
+
+    private fun testTransitClockFlag(flag: Boolean) {
+        featureFlags.set(TRANSIT_CLOCK, flag)
+        registry.isTransitClockEnabled = featureFlags.isEnabled(TRANSIT_CLOCK)
+        val mockPluginLifecycle = mock<PluginLifecycleManager<ClockProviderPlugin>>()
+        val plugin = FakeClockPlugin()
+                .addClock("clock_1", "clock 1")
+                .addClock("DIGITAL_CLOCK_METRO", "metro clock")
+        pluginListener.onPluginLoaded(plugin, mockContext, mockPluginLifecycle)
+
+        val list = registry.getClocks()
+        if (flag) {
+            assertEquals(
+                    setOf(
+                            ClockMetadata(DEFAULT_CLOCK_ID, DEFAULT_CLOCK_NAME),
+                            ClockMetadata("clock_1", "clock 1"),
+                            ClockMetadata("DIGITAL_CLOCK_METRO", "metro clock")
+                    ),
+                    list.toSet()
+            )
+        } else {
+            assertEquals(
+                    setOf(
+                            ClockMetadata(DEFAULT_CLOCK_ID, DEFAULT_CLOCK_NAME),
+                            ClockMetadata("clock_1", "clock 1")
+                    ),
+                    list.toSet()
+            )
+        }
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/condition/ConditionMonitorTest.java b/packages/SystemUI/tests/src/com/android/systemui/shared/condition/ConditionMonitorTest.java
index b724175..0b1753f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/condition/ConditionMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/condition/ConditionMonitorTest.java
@@ -555,60 +555,6 @@
     }
 
     /**
-     * Ensures a subscription is predicated on its precondition.
-     */
-    @Test
-    public void testPrecondition() {
-        mCondition1.fakeUpdateCondition(false);
-        final Monitor.Callback callback =
-                mock(Monitor.Callback.class);
-
-        mCondition2.fakeUpdateCondition(false);
-
-        // Create a nested condition
-        mConditionMonitor.addSubscription(new Monitor.Subscription.Builder(callback)
-                .addPrecondition(mCondition1)
-                .addCondition(mCondition2)
-                .build());
-
-        mExecutor.runAllReady();
-
-        // Ensure the nested condition callback is not called at all.
-        verify(callback, never()).onActiveChanged(anyBoolean());
-        verify(callback, never()).onConditionsChanged(anyBoolean());
-
-        // Update the condition to true and ensure that the nested condition is not triggered.
-        mCondition2.fakeUpdateCondition(true);
-        verify(callback, never()).onConditionsChanged(anyBoolean());
-        mCondition2.fakeUpdateCondition(false);
-
-        // Set precondition and make sure the inner condition becomes active and reports that
-        // conditions aren't met
-        mCondition1.fakeUpdateCondition(true);
-        mExecutor.runAllReady();
-
-        verify(callback).onActiveChanged(eq(true));
-        verify(callback).onConditionsChanged(eq(false));
-
-        Mockito.clearInvocations(callback);
-
-        // Update the condition and make sure the callback is updated.
-        mCondition2.fakeUpdateCondition(true);
-        mExecutor.runAllReady();
-
-        verify(callback).onConditionsChanged(true);
-
-        Mockito.clearInvocations(callback);
-        // Invalidate precondition and make sure callback is informed, but the last state is
-        // not affected.
-        mCondition1.fakeUpdateCondition(false);
-        mExecutor.runAllReady();
-
-        verify(callback).onActiveChanged(eq(false));
-        verify(callback, never()).onConditionsChanged(anyBoolean());
-    }
-
-    /**
      * Ensure preconditions are applied to every subscription added to a monitor.
      */
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutListSearchTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutListSearchTest.java
index 109f185..22c9e45 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutListSearchTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutListSearchTest.java
@@ -76,5 +76,6 @@
         mKeyboardShortcutListSearch.toggle(mContext, DEVICE_ID);
 
         verify(mWindowManager).requestAppKeyboardShortcuts(any(), anyInt());
+        verify(mWindowManager).requestImeKeyboardShortcuts(any(), anyInt());
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutsTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutsTest.java
index ea822aa..a3ecde0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutsTest.java
@@ -75,5 +75,6 @@
         mKeyboardShortcuts.toggle(mContext, DEVICE_ID);
 
         verify(mWindowManager).requestAppKeyboardShortcuts(any(), anyInt());
+        verify(mWindowManager).requestImeKeyboardShortcuts(any(), anyInt());
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerBaseTest.java
new file mode 100644
index 0000000..d44846e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerBaseTest.java
@@ -0,0 +1,295 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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;
+
+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;
+
+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.Mockito.clearInvocations;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.AlarmManager;
+import android.app.Instrumentation;
+import android.app.admin.DevicePolicyManager;
+import android.app.admin.DevicePolicyResourcesManager;
+import android.app.trust.TrustManager;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.hardware.fingerprint.FingerprintManager;
+import android.os.Looper;
+import android.os.UserManager;
+import android.provider.DeviceConfig;
+import android.testing.TestableLooper;
+import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityManager;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.internal.app.IBatteryStats;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.keyguard.logging.KeyguardLogger;
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.biometrics.AuthController;
+import com.android.systemui.biometrics.FaceHelpMessageDeferral;
+import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
+import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor;
+import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dock.DockManager;
+import com.android.systemui.flags.FakeFeatureFlags;
+import com.android.systemui.keyguard.KeyguardIndication;
+import com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController;
+import com.android.systemui.keyguard.ScreenLifecycle;
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory;
+import com.android.systemui.keyguard.util.IndicationHelper;
+import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.settings.UserTracker;
+import com.android.systemui.statusbar.phone.KeyguardBypassController;
+import com.android.systemui.statusbar.phone.KeyguardIndicationTextView;
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
+import com.android.systemui.util.wakelock.WakeLockFake;
+
+import org.junit.After;
+import org.junit.Before;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+public class KeyguardIndicationControllerBaseTest extends SysuiTestCase {
+
+    protected static final String ORGANIZATION_NAME = "organization";
+
+    protected static final ComponentName DEVICE_OWNER_COMPONENT = new ComponentName(
+            "com.android.foo",
+            "bar");
+
+    protected static final int TEST_STRING_RES = R.string.keyguard_indication_trust_unlocked;
+
+    protected String mDisclosureWithOrganization;
+    protected String mDisclosureGeneric;
+    protected String mFinancedDisclosureWithOrganization;
+
+    @Mock
+    protected DevicePolicyManager mDevicePolicyManager;
+    @Mock
+    protected DevicePolicyResourcesManager mDevicePolicyResourcesManager;
+    @Mock
+    protected ViewGroup mIndicationArea;
+    @Mock
+    protected KeyguardStateController mKeyguardStateController;
+    @Mock
+    protected KeyguardIndicationTextView mIndicationAreaBottom;
+    @Mock
+    protected BroadcastDispatcher mBroadcastDispatcher;
+    @Mock
+    protected StatusBarStateController mStatusBarStateController;
+    @Mock
+    protected KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+    @Mock
+    protected StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+    @Mock
+    protected UserManager mUserManager;
+    @Mock
+    protected IBatteryStats mIBatteryStats;
+    @Mock
+    protected DockManager mDockManager;
+    @Mock
+    protected KeyguardIndicationRotateTextViewController mRotateTextViewController;
+    @Mock
+    protected FalsingManager mFalsingManager;
+    @Mock
+    protected LockPatternUtils mLockPatternUtils;
+    @Mock
+    protected KeyguardBypassController mKeyguardBypassController;
+    @Mock
+    protected AccessibilityManager mAccessibilityManager;
+    @Mock
+    protected FaceHelpMessageDeferral mFaceHelpMessageDeferral;
+    @Mock
+    protected AlternateBouncerInteractor mAlternateBouncerInteractor;
+    @Mock
+    protected ScreenLifecycle mScreenLifecycle;
+    @Mock
+    protected AuthController mAuthController;
+    @Mock
+    protected AlarmManager mAlarmManager;
+    @Mock
+    protected UserTracker mUserTracker;
+    @Captor
+    protected ArgumentCaptor<DockManager.AlignmentStateListener> mAlignmentListener;
+    @Captor
+    protected ArgumentCaptor<StatusBarStateController.StateListener> mStatusBarStateListenerCaptor;
+    @Captor
+    protected ArgumentCaptor<BroadcastReceiver> mBroadcastReceiverCaptor;
+    @Captor
+    protected ArgumentCaptor<KeyguardIndication> mKeyguardIndicationCaptor;
+    @Captor
+    protected ArgumentCaptor<KeyguardUpdateMonitorCallback> mKeyguardUpdateMonitorCallbackCaptor;
+    @Captor
+    protected ArgumentCaptor<KeyguardStateController.Callback>
+            mKeyguardStateControllerCallbackCaptor;
+    @Captor
+    protected ArgumentCaptor<ScreenLifecycle.Observer> mScreenObserverCaptor;
+    protected KeyguardStateController.Callback mKeyguardStateControllerCallback;
+    protected KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback;
+    protected StatusBarStateController.StateListener mStatusBarStateListener;
+    protected ScreenLifecycle.Observer mScreenObserver;
+    protected BroadcastReceiver mBroadcastReceiver;
+    protected IndicationHelper mIndicationHelper;
+    protected FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
+    protected TestableLooper mTestableLooper;
+    protected final int mCurrentUserId = 1;
+
+    protected KeyguardIndicationTextView mTextView; // AOD text
+
+    protected KeyguardIndicationController mController;
+    protected WakeLockFake.Builder mWakeLockBuilder;
+    protected WakeLockFake mWakeLock;
+    protected Instrumentation mInstrumentation;
+    protected FakeFeatureFlags mFlags;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mInstrumentation = InstrumentationRegistry.getInstrumentation();
+        mTestableLooper = TestableLooper.get(this);
+        mTextView = new KeyguardIndicationTextView(mContext);
+        mTextView.setAnimationsEnabled(false);
+
+        // TODO(b/259908270): remove
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_DEVICE_POLICY_MANAGER,
+                DevicePolicyManager.ADD_ISFINANCED_DEVICE_FLAG, "true",
+                /* makeDefault= */ false);
+        mContext.addMockSystemService(Context.DEVICE_POLICY_SERVICE, mDevicePolicyManager);
+        mContext.addMockSystemService(UserManager.class, mUserManager);
+        mContext.addMockSystemService(Context.TRUST_SERVICE, mock(TrustManager.class));
+        mContext.addMockSystemService(Context.FINGERPRINT_SERVICE, mock(FingerprintManager.class));
+        mDisclosureWithOrganization = mContext.getString(R.string.do_disclosure_with_name,
+                ORGANIZATION_NAME);
+        mDisclosureGeneric = mContext.getString(R.string.do_disclosure_generic);
+        mFinancedDisclosureWithOrganization = mContext.getString(
+                R.string.do_financed_disclosure_with_name, ORGANIZATION_NAME);
+
+        when(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
+        when(mScreenLifecycle.getScreenState()).thenReturn(SCREEN_ON);
+        when(mKeyguardUpdateMonitor.isUserUnlocked(anyInt())).thenReturn(true);
+
+        when(mIndicationArea.findViewById(R.id.keyguard_indication_text_bottom))
+                .thenReturn(mIndicationAreaBottom);
+        when(mIndicationArea.findViewById(R.id.keyguard_indication_text)).thenReturn(mTextView);
+
+        when(mDevicePolicyManager.getResources()).thenReturn(mDevicePolicyResourcesManager);
+        when(mDevicePolicyManager.getDeviceOwnerComponentOnAnyUser())
+                .thenReturn(DEVICE_OWNER_COMPONENT);
+        when(mDevicePolicyManager.isFinancedDevice()).thenReturn(false);
+        // TODO(b/259908270): remove
+        when(mDevicePolicyManager.getDeviceOwnerType(DEVICE_OWNER_COMPONENT))
+                .thenReturn(DEVICE_OWNER_TYPE_DEFAULT);
+
+        when(mDevicePolicyResourcesManager.getString(anyString(), any()))
+                .thenReturn(mDisclosureGeneric);
+        when(mDevicePolicyResourcesManager.getString(anyString(), any(), anyString()))
+                .thenReturn(mDisclosureWithOrganization);
+        when(mUserTracker.getUserId()).thenReturn(mCurrentUserId);
+
+        mIndicationHelper = new IndicationHelper(mKeyguardUpdateMonitor);
+
+        mWakeLock = new WakeLockFake();
+        mWakeLockBuilder = new WakeLockFake.Builder(mContext);
+        mWakeLockBuilder.setWakeLock(mWakeLock);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mTextView.setAnimationsEnabled(true);
+        if (mController != null) {
+            mController.destroy();
+            mController = null;
+        }
+    }
+
+    protected void createController() {
+        if (Looper.myLooper() == null) {
+            Looper.prepare();
+        }
+
+        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(),
+                mWakeLockBuilder,
+                mKeyguardStateController, mStatusBarStateController, mKeyguardUpdateMonitor,
+                mDockManager, mBroadcastDispatcher, mDevicePolicyManager, mIBatteryStats,
+                mUserManager, mExecutor, mExecutor, mFalsingManager,
+                mAuthController, mLockPatternUtils, mScreenLifecycle,
+                mKeyguardBypassController, mAccessibilityManager,
+                mFaceHelpMessageDeferral, mock(KeyguardLogger.class),
+                mAlternateBouncerInteractor,
+                mAlarmManager,
+                mUserTracker,
+                mock(BouncerMessageInteractor.class),
+                mFlags,
+                mIndicationHelper,
+                KeyguardInteractorFactory.create(mFlags).getKeyguardInteractor()
+        );
+        mController.init();
+        mController.setIndicationArea(mIndicationArea);
+        verify(mStatusBarStateController).addCallback(mStatusBarStateListenerCaptor.capture());
+        mStatusBarStateListener = mStatusBarStateListenerCaptor.getValue();
+        verify(mBroadcastDispatcher).registerReceiver(mBroadcastReceiverCaptor.capture(), any());
+        mBroadcastReceiver = mBroadcastReceiverCaptor.getValue();
+        mController.mRotateTextViewController = mRotateTextViewController;
+        mController.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
+        clearInvocations(mIBatteryStats);
+
+        verify(mKeyguardStateController).addCallback(
+                mKeyguardStateControllerCallbackCaptor.capture());
+        mKeyguardStateControllerCallback = mKeyguardStateControllerCallbackCaptor.getValue();
+
+        verify(mKeyguardUpdateMonitor).registerCallback(
+                mKeyguardUpdateMonitorCallbackCaptor.capture());
+        mKeyguardUpdateMonitorCallback = mKeyguardUpdateMonitorCallbackCaptor.getValue();
+
+        verify(mScreenLifecycle).addObserver(mScreenObserverCaptor.capture());
+        mScreenObserver = mScreenObserverCaptor.getValue();
+
+        mExecutor.runAllReady();
+        reset(mRotateTextViewController);
+    }
+}
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 cd0c1352..1240f26 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
@@ -11,12 +11,11 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT 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.statusbar;
 
-import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_DEFAULT;
 import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_FINANCED;
 import static android.content.pm.UserInfo.FLAG_MANAGED_PROFILE;
 import static android.hardware.biometrics.BiometricFaceConstants.FACE_ACQUIRED_TOO_DARK;
@@ -26,7 +25,6 @@
 import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_AVAILABLE;
 import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_RECOGNIZED;
 import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED;
-import static com.android.systemui.flags.Flags.KEYGUARD_TALKBACK_FIX;
 import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_ALIGNMENT;
 import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_BATTERY;
 import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_BIOMETRIC_MESSAGE;
@@ -38,7 +36,6 @@
 import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_TRANSIENT;
 import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_TRUST;
 import static com.android.systemui.keyguard.ScreenLifecycle.SCREEN_OFF;
-import static com.android.systemui.keyguard.ScreenLifecycle.SCREEN_ON;
 import static com.android.systemui.keyguard.ScreenLifecycle.SCREEN_TURNING_ON;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -60,70 +57,29 @@
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
-import android.app.AlarmManager;
-import android.app.Instrumentation;
-import android.app.admin.DevicePolicyManager;
-import android.app.admin.DevicePolicyResourcesManager;
-import android.app.trust.TrustManager;
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.Context;
 import android.content.Intent;
 import android.content.pm.UserInfo;
 import android.graphics.Color;
 import android.hardware.biometrics.BiometricFaceConstants;
+import android.hardware.biometrics.BiometricFingerprintConstants;
 import android.hardware.biometrics.BiometricSourceType;
-import android.hardware.fingerprint.FingerprintManager;
 import android.os.BatteryManager;
-import android.os.Looper;
 import android.os.RemoteException;
-import android.os.UserManager;
-import android.provider.DeviceConfig;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
-import android.view.ViewGroup;
-import android.view.accessibility.AccessibilityManager;
 
-import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.FlakyTest;
 import androidx.test.filters.SmallTest;
 
-import com.android.internal.app.IBatteryStats;
-import com.android.internal.widget.LockPatternUtils;
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.keyguard.TrustGrantFlags;
-import com.android.keyguard.logging.KeyguardLogger;
 import com.android.settingslib.fuelgauge.BatteryStatus;
 import com.android.systemui.R;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.biometrics.AuthController;
-import com.android.systemui.biometrics.FaceHelpMessageDeferral;
-import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dock.DockManager;
-import com.android.systemui.flags.FakeFeatureFlags;
 import com.android.systemui.keyguard.KeyguardIndication;
 import com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController;
-import com.android.systemui.keyguard.ScreenLifecycle;
-import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor;
-import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.settings.UserTracker;
-import com.android.systemui.statusbar.phone.KeyguardBypassController;
-import com.android.systemui.statusbar.phone.KeyguardIndicationTextView;
-import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.util.concurrency.FakeExecutor;
-import com.android.systemui.util.time.FakeSystemClock;
-import com.android.systemui.util.wakelock.WakeLockFake;
 
-import org.junit.After;
-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.MockitoAnnotations;
 
 import java.text.NumberFormat;
 import java.util.Collections;
@@ -134,201 +90,7 @@
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
-public class KeyguardIndicationControllerTest extends SysuiTestCase {
-
-    private static final String ORGANIZATION_NAME = "organization";
-
-    private static final ComponentName DEVICE_OWNER_COMPONENT = new ComponentName("com.android.foo",
-            "bar");
-
-    private static final int TEST_STRING_RES = R.string.keyguard_indication_trust_unlocked;
-
-    private String mDisclosureWithOrganization;
-    private String mDisclosureGeneric;
-    private String mFinancedDisclosureWithOrganization;
-
-    @Mock
-    private DevicePolicyManager mDevicePolicyManager;
-    @Mock
-    private DevicePolicyResourcesManager mDevicePolicyResourcesManager;
-    @Mock
-    private ViewGroup mIndicationArea;
-    @Mock
-    private KeyguardStateController mKeyguardStateController;
-    @Mock
-    private KeyguardIndicationTextView mIndicationAreaBottom;
-    @Mock
-    private BroadcastDispatcher mBroadcastDispatcher;
-    @Mock
-    private StatusBarStateController mStatusBarStateController;
-    @Mock
-    private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
-    @Mock
-    private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
-    @Mock
-    private UserManager mUserManager;
-    @Mock
-    private IBatteryStats mIBatteryStats;
-    @Mock
-    private DockManager mDockManager;
-    @Mock
-    private KeyguardIndicationRotateTextViewController mRotateTextViewController;
-    @Mock
-    private FalsingManager mFalsingManager;
-    @Mock
-    private LockPatternUtils mLockPatternUtils;
-    @Mock
-    private KeyguardBypassController mKeyguardBypassController;
-    @Mock
-    private AccessibilityManager mAccessibilityManager;
-    @Mock
-    private FaceHelpMessageDeferral mFaceHelpMessageDeferral;
-    @Mock
-    private AlternateBouncerInteractor mAlternateBouncerInteractor;
-    @Mock
-    private ScreenLifecycle mScreenLifecycle;
-    @Mock
-    private AuthController mAuthController;
-    @Mock
-    private AlarmManager mAlarmManager;
-    @Mock
-    private UserTracker mUserTracker;
-    @Captor
-    private ArgumentCaptor<DockManager.AlignmentStateListener> mAlignmentListener;
-    @Captor
-    private ArgumentCaptor<StatusBarStateController.StateListener> mStatusBarStateListenerCaptor;
-    @Captor
-    private ArgumentCaptor<BroadcastReceiver> mBroadcastReceiverCaptor;
-    @Captor
-    private ArgumentCaptor<KeyguardIndication> mKeyguardIndicationCaptor;
-    @Captor
-    private ArgumentCaptor<KeyguardUpdateMonitorCallback> mKeyguardUpdateMonitorCallbackCaptor;
-    @Captor
-    private ArgumentCaptor<KeyguardStateController.Callback> mKeyguardStateControllerCallbackCaptor;
-    @Captor
-    private ArgumentCaptor<ScreenLifecycle.Observer> mScreenObserverCaptor;
-    private KeyguardStateController.Callback mKeyguardStateControllerCallback;
-    private KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback;
-    private StatusBarStateController.StateListener mStatusBarStateListener;
-    private ScreenLifecycle.Observer mScreenObserver;
-    private BroadcastReceiver mBroadcastReceiver;
-    private FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
-    private TestableLooper mTestableLooper;
-    private final int mCurrentUserId = 1;
-
-    private KeyguardIndicationTextView mTextView; // AOD text
-
-    private KeyguardIndicationController mController;
-    private WakeLockFake.Builder mWakeLockBuilder;
-    private WakeLockFake mWakeLock;
-    private Instrumentation mInstrumentation;
-
-    @Before
-    public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
-        mInstrumentation = InstrumentationRegistry.getInstrumentation();
-        mTestableLooper = TestableLooper.get(this);
-        mTextView = new KeyguardIndicationTextView(mContext);
-        mTextView.setAnimationsEnabled(false);
-
-        // TODO(b/259908270): remove
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_DEVICE_POLICY_MANAGER,
-                DevicePolicyManager.ADD_ISFINANCED_DEVICE_FLAG, "true",
-                /* makeDefault= */ false);
-        mContext.addMockSystemService(Context.DEVICE_POLICY_SERVICE, mDevicePolicyManager);
-        mContext.addMockSystemService(UserManager.class, mUserManager);
-        mContext.addMockSystemService(Context.TRUST_SERVICE, mock(TrustManager.class));
-        mContext.addMockSystemService(Context.FINGERPRINT_SERVICE, mock(FingerprintManager.class));
-        mDisclosureWithOrganization = mContext.getString(R.string.do_disclosure_with_name,
-                ORGANIZATION_NAME);
-        mDisclosureGeneric = mContext.getString(R.string.do_disclosure_generic);
-        mFinancedDisclosureWithOrganization = mContext.getString(
-                R.string.do_financed_disclosure_with_name, ORGANIZATION_NAME);
-
-        when(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
-        when(mScreenLifecycle.getScreenState()).thenReturn(SCREEN_ON);
-        when(mKeyguardUpdateMonitor.isUserUnlocked(anyInt())).thenReturn(true);
-
-        when(mIndicationArea.findViewById(R.id.keyguard_indication_text_bottom))
-                .thenReturn(mIndicationAreaBottom);
-        when(mIndicationArea.findViewById(R.id.keyguard_indication_text)).thenReturn(mTextView);
-
-        when(mDevicePolicyManager.getResources()).thenReturn(mDevicePolicyResourcesManager);
-        when(mDevicePolicyManager.getDeviceOwnerComponentOnAnyUser())
-                .thenReturn(DEVICE_OWNER_COMPONENT);
-        when(mDevicePolicyManager.isFinancedDevice()).thenReturn(false);
-        // TODO(b/259908270): remove
-        when(mDevicePolicyManager.getDeviceOwnerType(DEVICE_OWNER_COMPONENT))
-                .thenReturn(DEVICE_OWNER_TYPE_DEFAULT);
-
-
-        when(mDevicePolicyResourcesManager.getString(anyString(), any()))
-                .thenReturn(mDisclosureGeneric);
-        when(mDevicePolicyResourcesManager.getString(anyString(), any(), anyString()))
-                .thenReturn(mDisclosureWithOrganization);
-        when(mUserTracker.getUserId()).thenReturn(mCurrentUserId);
-
-        mWakeLock = new WakeLockFake();
-        mWakeLockBuilder = new WakeLockFake.Builder(mContext);
-        mWakeLockBuilder.setWakeLock(mWakeLock);
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        mTextView.setAnimationsEnabled(true);
-        if (mController != null) {
-            mController.destroy();
-            mController = null;
-        }
-    }
-
-    private void createController() {
-        if (Looper.myLooper() == null) {
-            Looper.prepare();
-        }
-
-        FakeFeatureFlags flags = new FakeFeatureFlags();
-        flags.set(KEYGUARD_TALKBACK_FIX, true);
-        mController = new KeyguardIndicationController(
-                mContext,
-                mTestableLooper.getLooper(),
-                mWakeLockBuilder,
-                mKeyguardStateController, mStatusBarStateController, mKeyguardUpdateMonitor,
-                mDockManager, mBroadcastDispatcher, mDevicePolicyManager, mIBatteryStats,
-                mUserManager, mExecutor, mExecutor, mFalsingManager,
-                mAuthController, mLockPatternUtils, mScreenLifecycle,
-                mKeyguardBypassController, mAccessibilityManager,
-                mFaceHelpMessageDeferral, mock(KeyguardLogger.class),
-                mAlternateBouncerInteractor,
-                mAlarmManager,
-                mUserTracker,
-                flags
-        );
-        mController.init();
-        mController.setIndicationArea(mIndicationArea);
-        verify(mStatusBarStateController).addCallback(mStatusBarStateListenerCaptor.capture());
-        mStatusBarStateListener = mStatusBarStateListenerCaptor.getValue();
-        verify(mBroadcastDispatcher).registerReceiver(mBroadcastReceiverCaptor.capture(), any());
-        mBroadcastReceiver = mBroadcastReceiverCaptor.getValue();
-        mController.mRotateTextViewController = mRotateTextViewController;
-        mController.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
-        clearInvocations(mIBatteryStats);
-
-        verify(mKeyguardStateController).addCallback(
-                mKeyguardStateControllerCallbackCaptor.capture());
-        mKeyguardStateControllerCallback = mKeyguardStateControllerCallbackCaptor.getValue();
-
-        verify(mKeyguardUpdateMonitor).registerCallback(
-                mKeyguardUpdateMonitorCallbackCaptor.capture());
-        mKeyguardUpdateMonitorCallback = mKeyguardUpdateMonitorCallbackCaptor.getValue();
-
-        verify(mScreenLifecycle).addObserver(mScreenObserverCaptor.capture());
-        mScreenObserver = mScreenObserverCaptor.getValue();
-
-        mExecutor.runAllReady();
-        reset(mRotateTextViewController);
-    }
-
+public class KeyguardIndicationControllerTest extends KeyguardIndicationControllerBaseTest {
     @Test
     public void createController_setIndicationAreaAgain_destroysPreviousRotateTextViewController() {
         // GIVEN a controller with a mocked rotate text view controlller
@@ -388,6 +150,7 @@
                 .isEqualTo(mContext.getColor(R.color.misalignment_text_color));
     }
 
+    @FlakyTest(bugId = 279944472)
     @Test
     public void onAlignmentStateChanged_whileDozing_showsSlowChargingIndication() {
         mInstrumentation.runOnMainSync(() -> {
@@ -803,36 +566,22 @@
     }
 
     @Test
-    public void transientIndication_visibleWhenDozing_ignoresFingerprintCancellation() {
+    public void transientIndication_visibleWhenDozing_ignoresFingerprintErrorMsg() {
         createController();
-
         mController.setVisible(true);
         reset(mRotateTextViewController);
+
+        // WHEN a fingerprint error user cancelled message is received
         mController.getKeyguardCallback().onBiometricError(
-                FingerprintManager.FINGERPRINT_ERROR_USER_CANCELED, "foo",
-                BiometricSourceType.FINGERPRINT);
-        mController.getKeyguardCallback().onBiometricError(
-                FingerprintManager.FINGERPRINT_ERROR_CANCELED, "bar",
+                BiometricFingerprintConstants.FINGERPRINT_ERROR_USER_CANCELED, "foo",
                 BiometricSourceType.FINGERPRINT);
 
+        // THEN no message is shown
         verifyNoMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE);
         verifyNoMessage(INDICATION_TYPE_TRANSIENT);
     }
 
     @Test
-    public void transientIndication_visibleWhenDozing_ignoresPowerPressed() {
-        createController();
-
-        mController.setVisible(true);
-        reset(mRotateTextViewController);
-        mController.getKeyguardCallback().onBiometricError(
-                FingerprintManager.BIOMETRIC_ERROR_POWER_PRESSED, "foo",
-                BiometricSourceType.FINGERPRINT);
-
-        verifyNoMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE);
-    }
-
-    @Test
     public void transientIndication_swipeUpToRetry() {
         createController();
         String message = mContext.getString(R.string.keyguard_retry);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerWithCoroutinesTest.kt
new file mode 100644
index 0000000..cdc7520
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerWithCoroutinesTest.kt
@@ -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.systemui.statusbar
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.view.View
+import androidx.test.filters.SmallTest
+import com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.runBlocking
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+class KeyguardIndicationControllerWithCoroutinesTest : KeyguardIndicationControllerBaseTest() {
+    @Test
+    fun testIndicationAreaVisibility_onLockscreenHostedDreamStateChanged() =
+        runBlocking(IMMEDIATE) {
+            // GIVEN starting state for keyguard indication and wallpaper dream enabled
+            createController()
+            mFlags.set(LOCKSCREEN_WALLPAPER_DREAM_ENABLED, true)
+            mController.setVisible(true)
+
+            // THEN indication area is visible
+            verify(mIndicationArea, times(2)).visibility = View.VISIBLE
+
+            // WHEN the device is dreaming with lockscreen hosted dream
+            mController.mIsActiveDreamLockscreenHostedCallback.accept(
+                true /* isActiveDreamLockscreenHosted */
+            )
+            mExecutor.runAllReady()
+
+            // THEN the indication area is hidden
+            verify(mIndicationArea).visibility = View.GONE
+
+            // WHEN the device stops dreaming with lockscreen hosted dream
+            mController.mIsActiveDreamLockscreenHostedCallback.accept(
+                false /* isActiveDreamLockscreenHosted */
+            )
+            mExecutor.runAllReady()
+
+            // THEN indication area is set visible
+            verify(mIndicationArea, times(3)).visibility = View.VISIBLE
+        }
+
+    companion object {
+        private val IMMEDIATE = Dispatchers.Main.immediate
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
index 2351f760..4a2518a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
@@ -1,5 +1,6 @@
 package com.android.systemui.statusbar
 
+import android.app.StatusBarManager.DISABLE2_NOTIFICATION_SHADE
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import android.testing.TestableLooper.RunWithLooper
@@ -8,14 +9,21 @@
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.classifier.FalsingCollectorFake
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.media.controls.ui.MediaHierarchyManager
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.plugins.qs.QS
+import com.android.systemui.power.data.repository.FakePowerRepository
+import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.shade.ShadeViewController
 import com.android.systemui.shade.data.repository.FakeShadeRepository
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.statusbar.disableflags.data.model.DisableFlagsModel
+import com.android.systemui.statusbar.disableflags.data.repository.FakeDisableFlagsRepository
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
 import com.android.systemui.statusbar.notification.row.NotificationTestHelper
 import com.android.systemui.statusbar.notification.stack.AmbientState
@@ -25,7 +33,13 @@
 import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.statusbar.phone.LSShadeTransitionLogger
 import com.android.systemui.statusbar.phone.ScrimController
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository
 import com.android.systemui.statusbar.policy.FakeConfigurationController
+import com.android.systemui.util.mockito.mock
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
 import org.junit.After
 import org.junit.Assert.assertFalse
 import org.junit.Assert.assertNotNull
@@ -56,8 +70,11 @@
 @SmallTest
 @RunWithLooper(setAsMainLooper = true)
 @RunWith(AndroidTestingRunner::class)
+@OptIn(ExperimentalCoroutinesApi::class)
 class LockscreenShadeTransitionControllerTest : SysuiTestCase() {
 
+    private val testScope = TestScope(StandardTestDispatcher())
+
     lateinit var transitionController: LockscreenShadeTransitionController
     lateinit var row: ExpandableNotificationRow
     @Mock lateinit var statusbarStateController: SysuiStatusBarStateController
@@ -83,12 +100,33 @@
     @Mock lateinit var qsTransitionController: LockscreenShadeQsTransitionController
     @Mock lateinit var activityStarter: ActivityStarter
     @Mock lateinit var transitionControllerCallback: LockscreenShadeTransitionController.Callback
+    private val disableFlagsRepository = FakeDisableFlagsRepository()
+    private val keyguardRepository = FakeKeyguardRepository()
+    private val shadeInteractor = ShadeInteractor(
+        testScope.backgroundScope,
+        disableFlagsRepository,
+        keyguardRepository,
+        userSetupRepository = FakeUserSetupRepository(),
+        deviceProvisionedController = mock(),
+        userInteractor = mock(),
+    )
+    private val powerInteractor = PowerInteractor(
+        FakePowerRepository(),
+        keyguardRepository,
+        FalsingCollectorFake(),
+        screenOffAnimationController = mock(),
+        statusBarStateController = mock(),
+    )
     @JvmField @Rule val mockito = MockitoJUnit.rule()
 
     private val configurationController = FakeConfigurationController()
 
     @Before
     fun setup() {
+        // By default, have the shade enabled
+        disableFlagsRepository.disableFlags.value = DisableFlagsModel()
+        testScope.runCurrent()
+
         val helper = NotificationTestHelper(
                 mContext,
                 mDependency,
@@ -129,6 +167,8 @@
                 qsTransitionControllerFactory = { qsTransitionController },
                 activityStarter = activityStarter,
                 shadeRepository = FakeShadeRepository(),
+                shadeInteractor = shadeInteractor,
+                powerInteractor = powerInteractor,
             )
         transitionController.addCallback(transitionControllerCallback)
         whenever(nsslController.view).thenReturn(stackscroller)
@@ -203,7 +243,10 @@
 
     @Test
     fun testDontGoWhenShadeDisabled() {
-        whenever(mCentralSurfaces.isShadeDisabled).thenReturn(true)
+        disableFlagsRepository.disableFlags.value = DisableFlagsModel(
+            disable2 = DISABLE2_NOTIFICATION_SHADE,
+        )
+        testScope.runCurrent()
         transitionController.goToLockedShade(null)
         verify(statusbarStateController, never()).setState(anyInt())
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java
index ced0734..305f48b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java
@@ -33,25 +33,21 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.power.domain.interactor.PowerInteractor;
 import com.android.systemui.statusbar.notification.NotifPipelineFlags;
 import com.android.systemui.statusbar.notification.RemoteInputControllerLogger;
 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.row.ExpandableNotificationRow;
-import com.android.systemui.statusbar.phone.CentralSurfaces;
 import com.android.systemui.statusbar.policy.RemoteInputUriController;
 
-import dagger.Lazy;
-
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
-import java.util.Optional;
-
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
@@ -69,6 +65,7 @@
     @Mock private RemoteInputUriController mRemoteInputUriController;
     @Mock private NotificationClickNotifier mClickNotifier;
     @Mock private NotificationLockscreenUserManager mLockscreenUserManager;
+    @Mock private PowerInteractor mPowerInteractor;
 
     private TestableNotificationRemoteInputManager mRemoteInputManager;
     private NotificationEntry mEntry;
@@ -82,7 +79,7 @@
                 mLockscreenUserManager,
                 mSmartReplyController,
                 mVisibilityProvider,
-                () -> Optional.of(mock(CentralSurfaces.class)),
+                mPowerInteractor,
                 mStateController,
                 mRemoteInputUriController,
                 mock(RemoteInputControllerLogger.class),
@@ -140,7 +137,7 @@
                 NotificationLockscreenUserManager lockscreenUserManager,
                 SmartReplyController smartReplyController,
                 NotificationVisibilityProvider visibilityProvider,
-                Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy,
+                PowerInteractor powerInteractor,
                 StatusBarStateController statusBarStateController,
                 RemoteInputUriController remoteInputUriController,
                 RemoteInputControllerLogger remoteInputControllerLogger,
@@ -153,7 +150,7 @@
                     lockscreenUserManager,
                     smartReplyController,
                     visibilityProvider,
-                    centralSurfacesOptionalLazy,
+                    powerInteractor,
                     statusBarStateController,
                     remoteInputUriController,
                     remoteInputControllerLogger,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java
index 1b1f4e4..8d016e3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java
@@ -45,6 +45,7 @@
 import android.os.Bundle;
 import android.os.UserHandle;
 import android.service.notification.StatusBarNotification;
+import android.view.ViewGroup;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -65,6 +66,8 @@
 @RunWith(AndroidJUnit4.class)
 public class StatusBarIconViewTest extends SysuiTestCase {
 
+    private static final int TEST_STATUS_BAR_HEIGHT = 150;
+
     @Rule
     public ExpectedException mThrown = ExpectedException.none();
 
@@ -184,4 +187,218 @@
 
         // no crash, good
     }
+
+    @Test
+    public void testUpdateIconScale_constrainedDrawableSizeLessThanDpIconSize() {
+        int dpIconSize = 60;
+        int dpDrawingSize = 30;
+        // the icon view layout size would be 60x150
+        //   (the height is always 150 due to TEST_STATUS_BAR_HEIGHT)
+        setUpIconView(dpIconSize, dpDrawingSize, dpIconSize);
+        mIconView.setNotification(mock(StatusBarNotification.class));
+        // the raw drawable size is 50x50. When put the drawable into iconView whose
+        // layout size is 60x150, the drawable size would not be constrained and thus keep 50x50
+        setIconDrawableWithSize(/* width= */ 50, /* height= */ 50);
+        mIconView.maybeUpdateIconScaleDimens();
+
+        // WHEN both the constrained drawable width/height are less than dpIconSize,
+        // THEN the icon is scaled down from dpIconSize to fit the dpDrawingSize
+        float scaleToFitDrawingSize = (float) dpDrawingSize / dpIconSize;
+        assertEquals(scaleToFitDrawingSize, mIconView.getIconScale(), 0.01f);
+    }
+
+    @Test
+    public void testUpdateIconScale_constrainedDrawableHeightLargerThanDpIconSize() {
+        int dpIconSize = 60;
+        int dpDrawingSize = 30;
+        // the icon view layout size would be 60x150
+        //   (the height is always 150 due to TEST_STATUS_BAR_HEIGHT)
+        setUpIconView(dpIconSize, dpDrawingSize, dpIconSize);
+        mIconView.setNotification(mock(StatusBarNotification.class));
+        // the raw drawable size is 50x100. When put the drawable into iconView whose
+        // layout size is 60x150, the drawable size would not be constrained and thus keep 50x100
+        setIconDrawableWithSize(/* width= */ 50, /* height= */ 100);
+        mIconView.maybeUpdateIconScaleDimens();
+
+        // WHEN constrained drawable larger side length 100 >= dpIconSize
+        // THEN the icon is scaled down from larger side length 100 to ensure both side
+        //      length fit in dpDrawingSize.
+        float scaleToFitDrawingSize = (float) dpDrawingSize / 100;
+        assertEquals(scaleToFitDrawingSize, mIconView.getIconScale(), 0.01f);
+    }
+
+    @Test
+    public void testUpdateIconScale_constrainedDrawableWidthLargerThanDpIconSize() {
+        int dpIconSize = 60;
+        int dpDrawingSize = 30;
+        // the icon view layout size would be 60x150
+        //   (the height is always 150 due to TEST_STATUS_BAR_HEIGHT)
+        setUpIconView(dpIconSize, dpDrawingSize, dpIconSize);
+        mIconView.setNotification(mock(StatusBarNotification.class));
+        // the raw drawable size is 100x50. When put the drawable into iconView whose
+        // layout size is 60x150, the drawable size would be constrained to 60x30
+        setIconDrawableWithSize(/* width= */ 100, /* height= */ 50);
+        mIconView.maybeUpdateIconScaleDimens();
+
+        // WHEN constrained drawable larger side length 60 >= dpIconSize
+        // THEN the icon is scaled down from larger side length 60 to ensure both side
+        //      length fit in dpDrawingSize.
+        float scaleToFitDrawingSize = (float) dpDrawingSize / 60;
+        assertEquals(scaleToFitDrawingSize, mIconView.getIconScale(), 0.01f);
+    }
+
+    @Test
+    public void testUpdateIconScale_smallerFontAndConstrainedDrawableSizeLessThanDpIconSize() {
+        int dpIconSize = 60;
+        int dpDrawingSize = 30;
+        // smaller font scaling causes the spIconSize < dpIconSize
+        int spIconSize = 40;
+        // the icon view layout size would be 40x150
+        //   (the height is always 150 due to TEST_STATUS_BAR_HEIGHT)
+        setUpIconView(dpIconSize, dpDrawingSize, spIconSize);
+        mIconView.setNotification(mock(StatusBarNotification.class));
+        // the raw drawable size is 50x50. When put the drawable into iconView whose
+        // layout size is 40x150, the drawable size would be constrained to 40x40
+        setIconDrawableWithSize(/* width= */ 50, /* height= */ 50);
+        mIconView.maybeUpdateIconScaleDimens();
+
+        // WHEN both the constrained drawable width/height are less than dpIconSize,
+        // THEN the icon is scaled down from dpIconSize to fit the dpDrawingSize
+        float scaleToFitDrawingSize = (float) dpDrawingSize / dpIconSize;
+        // THEN the scaled icon should be scaled down further to fit spIconSize
+        float scaleToFitSpIconSize = (float) spIconSize / dpIconSize;
+        assertEquals(scaleToFitDrawingSize * scaleToFitSpIconSize, mIconView.getIconScale(), 0.01f);
+    }
+
+    @Test
+    public void testUpdateIconScale_smallerFontAndConstrainedDrawableHeightLargerThanDpIconSize() {
+        int dpIconSize = 60;
+        int dpDrawingSize = 30;
+        // smaller font scaling causes the spIconSize < dpIconSize
+        int spIconSize = 40;
+        // the icon view layout size would be 40x150
+        //   (the height is always 150 due to TEST_STATUS_BAR_HEIGHT)
+        setUpIconView(dpIconSize, dpDrawingSize, spIconSize);
+        mIconView.setNotification(mock(StatusBarNotification.class));
+        // the raw drawable size is 50x100. When put the drawable into iconView whose
+        // layout size is 40x150, the drawable size would be constrained to 40x80
+        setIconDrawableWithSize(/* width= */ 50, /* height= */ 100);
+        mIconView.maybeUpdateIconScaleDimens();
+
+        // WHEN constrained drawable larger side length 80 >= dpIconSize
+        // THEN the icon is scaled down from larger side length 80 to ensure both side
+        //      length fit in dpDrawingSize.
+        float scaleToFitDrawingSize = (float) dpDrawingSize / 80;
+        // THEN the scaled icon should be scaled down further to fit spIconSize
+        float scaleToFitSpIconSize = (float) spIconSize / dpIconSize;
+        assertEquals(scaleToFitDrawingSize * scaleToFitSpIconSize, mIconView.getIconScale(), 0.01f);
+    }
+
+    @Test
+    public void testUpdateIconScale_largerFontAndConstrainedDrawableSizeLessThanDpIconSize() {
+        int dpIconSize = 60;
+        int dpDrawingSize = 30;
+        // larger font scaling causes the spIconSize > dpIconSize
+        int spIconSize = 80;
+        // the icon view layout size would be 80x150
+        //   (the height is always 150 due to TEST_STATUS_BAR_HEIGHT)
+        setUpIconView(dpIconSize, dpDrawingSize, spIconSize);
+        mIconView.setNotification(mock(StatusBarNotification.class));
+        // the raw drawable size is 50x50. When put the drawable into iconView whose
+        // layout size is 80x150, the drawable size would not be constrained and thus keep 50x50
+        setIconDrawableWithSize(/* width= */ 50, /* height= */ 50);
+        mIconView.maybeUpdateIconScaleDimens();
+
+        // WHEN both the constrained drawable width/height are less than dpIconSize,
+        // THEN the icon is scaled down from dpIconSize to fit the dpDrawingSize
+        float scaleToFitDrawingSize = (float) dpDrawingSize / dpIconSize;
+        // THEN the scaled icon should be scaled up to fit spIconSize
+        float scaleToFitSpIconSize = (float) spIconSize / dpIconSize;
+        assertEquals(scaleToFitDrawingSize * scaleToFitSpIconSize, mIconView.getIconScale(), 0.01f);
+    }
+
+    @Test
+    public void testUpdateIconScale_largerFontAndConstrainedDrawableHeightLargerThanDpIconSize() {
+        int dpIconSize = 60;
+        int dpDrawingSize = 30;
+        // larger font scaling causes the spIconSize > dpIconSize
+        int spIconSize = 80;
+        // the icon view layout size would be 80x150
+        //   (the height is always 150 due to TEST_STATUS_BAR_HEIGHT)
+        setUpIconView(dpIconSize, dpDrawingSize, spIconSize);
+        mIconView.setNotification(mock(StatusBarNotification.class));
+        // the raw drawable size is 50x100. When put the drawable into iconView whose
+        // layout size is 80x150, the drawable size would not be constrained and thus keep 50x100
+        setIconDrawableWithSize(/* width= */ 50, /* height= */ 100);
+        mIconView.maybeUpdateIconScaleDimens();
+
+        // WHEN constrained drawable larger side length 100 >= dpIconSize
+        // THEN the icon is scaled down from larger side length 100 to ensure both side
+        //      length fit in dpDrawingSize.
+        float scaleToFitDrawingSize = (float) dpDrawingSize / 100;
+        // THEN the scaled icon should be scaled up to fit spIconSize
+        float scaleToFitSpIconSize = (float) spIconSize / dpIconSize;
+        assertEquals(scaleToFitDrawingSize * scaleToFitSpIconSize, mIconView.getIconScale(), 0.01f);
+    }
+
+    @Test
+    public void testUpdateIconScale_largerFontAndConstrainedDrawableWidthLargerThanDpIconSize() {
+        int dpIconSize = 60;
+        int dpDrawingSize = 30;
+        // larger font scaling causes the spIconSize > dpIconSize
+        int spIconSize = 80;
+        // the icon view layout size would be 80x150
+        //   (the height is always 150 due to TEST_STATUS_BAR_HEIGHT)
+        setUpIconView(dpIconSize, dpDrawingSize, spIconSize);
+        mIconView.setNotification(mock(StatusBarNotification.class));
+        // the raw drawable size is 100x50. When put the drawable into iconView whose
+        // layout size is 80x150, the drawable size would not be constrained and thus keep 80x40
+        setIconDrawableWithSize(/* width= */ 100, /* height= */ 50);
+        mIconView.maybeUpdateIconScaleDimens();
+
+        // WHEN constrained drawable larger side length 80 >= dpIconSize
+        // THEN the icon is scaled down from larger side length 80 to ensure both side
+        //      length fit in dpDrawingSize.
+        float scaleToFitDrawingSize = (float) dpDrawingSize / 80;
+        // THEN the scaled icon should be scaled up to fit spIconSize
+        float scaleToFitSpIconSize = (float) spIconSize / dpIconSize;
+        assertEquals(scaleToFitDrawingSize * scaleToFitSpIconSize,
+                mIconView.getIconScale(), 0.01f);
+    }
+
+    /**
+     * Setup iconView dimens for testing. The result icon view layout width would
+     * be spIconSize and height would be 150.
+     *
+     * @param dpIconSize corresponding to status_bar_icon_size
+     * @param dpDrawingSize corresponding to status_bar_icon_drawing_size
+     * @param spIconSize corresponding to status_bar_icon_size_sp under different font scaling
+     */
+    private void setUpIconView(int dpIconSize, int dpDrawingSize, int spIconSize) {
+        mIconView.setIncreasedSize(false);
+        mIconView.mOriginalStatusBarIconSize = dpIconSize;
+        mIconView.mStatusBarIconDrawingSize = dpDrawingSize;
+
+        mIconView.mNewStatusBarIconSize = spIconSize;
+        mIconView.mScaleToFitNewIconSize = (float) spIconSize / dpIconSize;
+
+        // the layout width would be spIconSize + 2 * iconPadding, and we assume iconPadding
+        // is 0 here.
+        ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(spIconSize, TEST_STATUS_BAR_HEIGHT);
+        mIconView.setLayoutParams(lp);
+    }
+
+    private void setIconDrawableWithSize(int width, int height) {
+        Bitmap bitmap = Bitmap.createBitmap(
+                width, height, Bitmap.Config.ARGB_8888);
+        Icon icon = Icon.createWithBitmap(bitmap);
+        mStatusBarIcon = new StatusBarIcon(UserHandle.ALL, "mockPackage",
+                icon, 0, 0, "");
+        // Since we only want to verify icon scale logic here, we directly use
+        // {@link StatusBarIconView#setImageDrawable(Drawable)} to set the image drawable
+        // to iconView instead of call {@link StatusBarIconView#set(StatusBarIcon)}. It's to prevent
+        // the icon drawable size being scaled down when internally calling
+        // {@link StatusBarIconView#getIcon(Context,Context,StatusBarIcon)}.
+        mIconView.setImageDrawable(icon.loadDrawable(mContext));
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/CommandParserTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/CommandParserTest.kt
new file mode 100644
index 0000000..cfbe8e3
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/CommandParserTest.kt
@@ -0,0 +1,212 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.commandline
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import java.io.PrintWriter
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertThrows
+import org.junit.Assert.assertTrue
+import org.junit.Test
+
+@SmallTest
+class CommandParserTest : SysuiTestCase() {
+    private val parser = CommandParser()
+
+    @Test
+    fun registerToken_cannotReuseNames() {
+        parser.flag("-f")
+        assertThrows(IllegalArgumentException::class.java) { parser.flag("-f") }
+    }
+
+    @Test
+    fun unknownToken_throws() {
+        assertThrows(ArgParseError::class.java) { parser.parse(listOf("unknown-token")) }
+    }
+
+    @Test
+    fun parseSingleFlag_present() {
+        val flag by parser.flag("-f")
+        parser.parse(listOf("-f"))
+        assertTrue(flag)
+    }
+
+    @Test
+    fun parseSingleFlag_notPresent() {
+        val flag by parser.flag("-f")
+        parser.parse(listOf())
+        assertFalse(flag)
+    }
+
+    @Test
+    fun parseSingleOptionalParam_present() {
+        val param by parser.param("-p", valueParser = Type.Int)
+        parser.parse(listOf("-p", "123"))
+        assertThat(param).isEqualTo(123)
+    }
+
+    @Test
+    fun parseSingleOptionalParam_notPresent() {
+        val param by parser.param("-p", valueParser = Type.Int)
+        parser.parse(listOf())
+        assertThat(param).isNull()
+    }
+
+    @Test
+    fun parseSingleOptionalParam_missingArg_throws() {
+        val param by parser.param("-p", valueParser = Type.Int)
+        assertThrows(ArgParseError::class.java) { parser.parse(listOf("-p")) }
+    }
+
+    @Test
+    fun parseSingleRequiredParam_present() {
+        val param by parser.require(parser.param("-p", valueParser = Type.Int))
+        parser.parse(listOf("-p", "123"))
+        assertThat(param).isEqualTo(123)
+    }
+
+    @Test
+    fun parseSingleRequiredParam_notPresent_failsValidation() {
+        val param by parser.require(parser.param("-p", valueParser = Type.Int))
+        assertFalse(parser.parse(listOf()))
+    }
+
+    @Test
+    fun parseSingleRequiredParam_missingArg_throws() {
+        val param by parser.require(parser.param("-p", valueParser = Type.Int))
+        assertThrows(ArgParseError::class.java) { parser.parse(listOf("-p")) }
+    }
+
+    @Test
+    fun parseAsSubCommand_singleFlag_present() {
+        val flag by parser.flag("-f")
+        val args = listOf("-f").listIterator()
+        parser.parseAsSubCommand(args)
+
+        assertTrue(flag)
+    }
+
+    @Test
+    fun parseAsSubCommand_singleFlag_notPresent() {
+        val flag by parser.flag("-f")
+        val args = listOf("--other-flag").listIterator()
+        parser.parseAsSubCommand(args)
+
+        assertFalse(flag)
+    }
+
+    @Test
+    fun parseAsSubCommand_singleOptionalParam_present() {
+        val param by parser.param("-p", valueParser = Type.Int)
+        parser.parseAsSubCommand(listOf("-p", "123", "--other-arg", "321").listIterator())
+        assertThat(param).isEqualTo(123)
+    }
+
+    @Test
+    fun parseAsSubCommand_singleOptionalParam_notPresent() {
+        val param by parser.param("-p", valueParser = Type.Int)
+        parser.parseAsSubCommand(listOf("--other-arg", "321").listIterator())
+        assertThat(param).isNull()
+    }
+
+    @Test
+    fun parseAsSubCommand_singleRequiredParam_present() {
+        val param by parser.require(parser.param("-p", valueParser = Type.Int))
+        parser.parseAsSubCommand(listOf("-p", "123", "--other-arg", "321").listIterator())
+        assertThat(param).isEqualTo(123)
+    }
+
+    @Test
+    fun parseAsSubCommand_singleRequiredParam_notPresent() {
+        parser.require(parser.param("-p", valueParser = Type.Int))
+        assertFalse(parser.parseAsSubCommand(listOf("--other-arg", "321").listIterator()))
+    }
+
+    @Test
+    fun parseCommandWithSubCommand_required_provided() {
+        val topLevelFlag by parser.flag("flag", shortName = "-f")
+
+        val cmd =
+            object : ParseableCommand("test") {
+                val flag by flag("flag1")
+                override fun execute(pw: PrintWriter) {}
+            }
+
+        parser.require(parser.subCommand(cmd))
+        parser.parse(listOf("-f", "test", "--flag1"))
+
+        assertTrue(topLevelFlag)
+        assertThat(cmd).isNotNull()
+        assertTrue(cmd.flag)
+    }
+
+    @Test
+    fun parseCommandWithSubCommand_required_notProvided() {
+        val topLevelFlag by parser.flag("-f")
+
+        val cmd =
+            object : ParseableCommand("test") {
+                val flag by parser.flag("flag1")
+                override fun execute(pw: PrintWriter) {}
+            }
+
+        parser.require(parser.subCommand(cmd))
+
+        assertFalse(parser.parse(listOf("-f")))
+    }
+
+    @Test
+    fun flag_requiredParam_optionalParam_allProvided_failsValidation() {
+        val flag by parser.flag("-f")
+        val optionalParam by parser.param("-p", valueParser = Type.Int)
+        val requiredParam by parser.require(parser.param("-p2", valueParser = Type.Boolean))
+
+        parser.parse(
+            listOf(
+                "-f",
+                "-p",
+                "123",
+                "-p2",
+                "false",
+            )
+        )
+
+        assertTrue(flag)
+        assertThat(optionalParam).isEqualTo(123)
+        assertFalse(requiredParam)
+    }
+
+    @Test
+    fun flag_requiredParam_optionalParam_optionalExcluded() {
+        val flag by parser.flag("-f")
+        val optionalParam by parser.param("-p", valueParser = Type.Int)
+        val requiredParam by parser.require(parser.param("-p2", valueParser = Type.Boolean))
+
+        parser.parse(
+            listOf(
+                "-p2",
+                "true",
+            )
+        )
+
+        assertFalse(flag)
+        assertThat(optionalParam).isNull()
+        assertTrue(requiredParam)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/ParametersTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/ParametersTest.kt
new file mode 100644
index 0000000..e391d6b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/ParametersTest.kt
@@ -0,0 +1,55 @@
+package com.android.systemui.statusbar.commandline
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertThrows
+import org.junit.Assert.assertTrue
+import org.junit.Test
+
+@SmallTest
+class ParametersTest : SysuiTestCase() {
+    @Test
+    fun singleArgOptional_returnsNullBeforeParse() {
+        val optional by SingleArgParamOptional(longName = "longName", valueParser = Type.Int)
+        assertThat(optional).isNull()
+    }
+
+    @Test
+    fun singleArgOptional_returnsParsedValue() {
+        val param = SingleArgParamOptional(longName = "longName", valueParser = Type.Int)
+        param.parseArgsFromIter(listOf("3").listIterator())
+        val optional by param
+        assertThat(optional).isEqualTo(3)
+    }
+
+    @Test
+    fun singleArgRequired_throwsBeforeParse() {
+        val req by SingleArgParam(longName = "param", valueParser = Type.Boolean)
+        assertThrows(IllegalStateException::class.java) { req }
+    }
+
+    @Test
+    fun singleArgRequired_returnsParsedValue() {
+        val param = SingleArgParam(longName = "param", valueParser = Type.Boolean)
+        param.parseArgsFromIter(listOf("true").listIterator())
+        val req by param
+        assertTrue(req)
+    }
+
+    @Test
+    fun param_handledAfterParse() {
+        val optParam = SingleArgParamOptional(longName = "string1", valueParser = Type.String)
+        val reqParam = SingleArgParam(longName = "string2", valueParser = Type.Float)
+
+        assertFalse(optParam.handled)
+        assertFalse(reqParam.handled)
+
+        optParam.parseArgsFromIter(listOf("test").listIterator())
+        reqParam.parseArgsFromIter(listOf("1.23").listIterator())
+
+        assertTrue(optParam.handled)
+        assertTrue(reqParam.handled)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/ParseableCommandTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/ParseableCommandTest.kt
new file mode 100644
index 0000000..86548d0
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/ParseableCommandTest.kt
@@ -0,0 +1,317 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.commandline
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import java.io.PrintWriter
+import org.junit.Assert.assertThrows
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+class ParseableCommandTest : SysuiTestCase() {
+    @Mock private lateinit var pw: PrintWriter
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+    }
+
+    /**
+     * A little change-detector-y, but this is just a general assertion that building up a command
+     * parser via its wrapper works as expected.
+     */
+    @Test
+    fun testFactoryMethods() {
+        val mySubCommand =
+            object : ParseableCommand("subCommand") {
+                val flag by flag("flag")
+                override fun execute(pw: PrintWriter) {}
+            }
+
+        val mySubCommand2 =
+            object : ParseableCommand("subCommand2") {
+                val flag by flag("flag")
+                override fun execute(pw: PrintWriter) {}
+            }
+
+        // Verify that the underlying parser contains the correct types
+        val myCommand =
+            object : ParseableCommand("testName") {
+                val flag by flag("flag", shortName = "f")
+                val requiredParam by
+                    param(longName = "required-param", shortName = "r", valueParser = Type.String)
+                        .required()
+                val optionalParam by
+                    param(longName = "optional-param", shortName = "o", valueParser = Type.Boolean)
+                val optionalSubCommand by subCommand(mySubCommand)
+                val requiredSubCommand by subCommand(mySubCommand2).required()
+
+                override fun execute(pw: PrintWriter) {}
+            }
+
+        val flags = myCommand.parser.flags
+        val params = myCommand.parser.params
+        val subCommands = myCommand.parser.subCommands
+
+        assertThat(flags).hasSize(2)
+        assertThat(flags[0]).isInstanceOf(Flag::class.java)
+        assertThat(flags[1]).isInstanceOf(Flag::class.java)
+
+        assertThat(params).hasSize(2)
+        val req = params.filter { it is SingleArgParam<*> }
+        val opt = params.filter { it is SingleArgParamOptional<*> }
+        assertThat(req).hasSize(1)
+        assertThat(opt).hasSize(1)
+
+        val reqSub = subCommands.filter { it is RequiredSubCommand<*> }
+        val optSub = subCommands.filter { it is OptionalSubCommand<*> }
+        assertThat(reqSub).hasSize(1)
+        assertThat(optSub).hasSize(1)
+    }
+
+    @Test
+    fun factoryMethods_enforceShortNameRules() {
+        // Short names MUST be one character long
+        assertThrows(IllegalArgumentException::class.java) {
+            val myCommand =
+                object : ParseableCommand("test-command") {
+                    val flag by flag("longName", "invalidShortName")
+
+                    override fun execute(pw: PrintWriter) {}
+                }
+        }
+
+        assertThrows(IllegalArgumentException::class.java) {
+            val myCommand =
+                object : ParseableCommand("test-command") {
+                    val param by param("longName", "invalidShortName", valueParser = Type.String)
+
+                    override fun execute(pw: PrintWriter) {}
+                }
+        }
+    }
+
+    @Test
+    fun factoryMethods_enforceLongNames_notPrefixed() {
+        // Long names must not start with "-", since they will be added
+        assertThrows(IllegalArgumentException::class.java) {
+            val myCommand =
+                object : ParseableCommand("test-command") {
+                    val flag by flag("--invalid")
+
+                    override fun execute(pw: PrintWriter) {}
+                }
+        }
+
+        assertThrows(IllegalArgumentException::class.java) {
+            val myCommand =
+                object : ParseableCommand("test-command") {
+                    val param by param("-invalid", valueParser = Type.String)
+
+                    override fun execute(pw: PrintWriter) {}
+                }
+        }
+    }
+
+    @Test
+    fun executeDoesNotPropagateExceptions() {
+        val cmd =
+            object : ParseableCommand("test-command") {
+                val flag by flag("flag")
+                override fun execute(pw: PrintWriter) {}
+            }
+
+        val throwingCommand = listOf("unknown-token")
+
+        // Given a command that would cause an ArgParseError
+        assertThrows(ArgParseError::class.java) { cmd.parser.parse(throwingCommand) }
+
+        // The parser consumes that error
+        cmd.execute(pw, throwingCommand)
+    }
+
+    @Test
+    fun executeFailingCommand_callsOnParseFailed() {
+        val cmd =
+            object : ParseableCommand("test-command") {
+                val flag by flag("flag")
+
+                var onParseFailedCalled = false
+
+                override fun execute(pw: PrintWriter) {}
+                override fun onParseFailed(error: ArgParseError) {
+                    onParseFailedCalled = true
+                }
+            }
+
+        val throwingCommand = listOf("unknown-token")
+        cmd.execute(pw, throwingCommand)
+
+        assertTrue(cmd.onParseFailedCalled)
+    }
+
+    @Test
+    fun baseCommand() {
+        val myCommand = MyCommand()
+        myCommand.execute(pw, baseCommand)
+
+        assertThat(myCommand.flag1).isFalse()
+        assertThat(myCommand.singleParam).isNull()
+    }
+
+    @Test
+    fun commandWithFlags() {
+        val command = MyCommand()
+        command.execute(pw, cmdWithFlags)
+
+        assertThat(command.flag1).isTrue()
+        assertThat(command.flag2).isTrue()
+    }
+
+    @Test
+    fun commandWithArgs() {
+        val cmd = MyCommand()
+        cmd.execute(pw, cmdWithSingleArgParam)
+
+        assertThat(cmd.singleParam).isEqualTo("single_param")
+    }
+
+    @Test
+    fun commandWithRequiredParam_provided() {
+        val cmd =
+            object : ParseableCommand(name) {
+                val singleRequiredParam: String by
+                    param(
+                            longName = "param1",
+                            shortName = "p",
+                            valueParser = Type.String,
+                        )
+                        .required()
+
+                override fun execute(pw: PrintWriter) {}
+            }
+
+        val cli = listOf("-p", "value")
+        cmd.execute(pw, cli)
+
+        assertThat(cmd.singleRequiredParam).isEqualTo("value")
+    }
+
+    @Test
+    fun commandWithRequiredParam_not_provided_throws() {
+        val cmd =
+            object : ParseableCommand(name) {
+                val singleRequiredParam by
+                    param(shortName = "p", longName = "param1", valueParser = Type.String)
+                        .required()
+
+                override fun execute(pw: PrintWriter) {}
+
+                override fun execute(pw: PrintWriter, args: List<String>) {
+                    parser.parse(args)
+                    execute(pw)
+                }
+            }
+
+        val cli = listOf("")
+        assertThrows(ArgParseError::class.java) { cmd.execute(pw, cli) }
+    }
+
+    @Test
+    fun commandWithSubCommand() {
+        val subName = "sub-command"
+        val subCmd =
+            object : ParseableCommand(subName) {
+                val singleOptionalParam: String? by param("param", valueParser = Type.String)
+
+                override fun execute(pw: PrintWriter) {}
+            }
+
+        val cmd =
+            object : ParseableCommand(name) {
+                val subCmd by subCommand(subCmd)
+                override fun execute(pw: PrintWriter) {}
+            }
+
+        cmd.execute(pw, listOf("sub-command", "--param", "test"))
+        assertThat(cmd.subCmd?.singleOptionalParam).isEqualTo("test")
+    }
+
+    @Test
+    fun complexCommandWithSubCommands_reusedNames() {
+        val commandLine = "-f --param1 arg1 sub-command1 -f -p arg2 --param2 arg3".split(" ")
+
+        val subName = "sub-command1"
+        val subCmd =
+            object : ParseableCommand(subName) {
+                val flag1 by flag("flag", shortName = "f")
+                val param1: String? by param("param1", shortName = "p", valueParser = Type.String)
+
+                override fun execute(pw: PrintWriter) {}
+            }
+
+        val myCommand =
+            object : ParseableCommand(name) {
+                val flag1 by flag(longName = "flag", shortName = "f")
+                val param1 by param("param1", shortName = "p", valueParser = Type.String).required()
+                val param2: String? by param(longName = "param2", valueParser = Type.String)
+                val subCommand by subCommand(subCmd)
+
+                override fun execute(pw: PrintWriter) {}
+            }
+
+        myCommand.execute(pw, commandLine)
+
+        assertThat(myCommand.flag1).isTrue()
+        assertThat(myCommand.param1).isEqualTo("arg1")
+        assertThat(myCommand.param2).isEqualTo("arg3")
+        assertThat(myCommand.subCommand).isNotNull()
+        assertThat(myCommand.subCommand?.flag1).isTrue()
+        assertThat(myCommand.subCommand?.param1).isEqualTo("arg2")
+    }
+
+    class MyCommand(
+        private val onExecute: ((MyCommand) -> Unit)? = null,
+    ) : ParseableCommand(name) {
+
+        val flag1 by flag(shortName = "f", longName = "flag1", description = "flag 1 for test")
+        val flag2 by flag(shortName = "g", longName = "flag2", description = "flag 2 for test")
+        val singleParam: String? by
+            param(
+                shortName = "a",
+                longName = "arg1",
+                valueParser = Type.String,
+            )
+
+        override fun execute(pw: PrintWriter) {
+            onExecute?.invoke(this)
+        }
+    }
+
+    companion object {
+        const val name = "my_command"
+        val baseCommand = listOf("")
+        val cmdWithFlags = listOf("-f", "--flag2")
+        val cmdWithSingleArgParam = listOf("--arg1", "single_param")
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/ValueParserTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/ValueParserTest.kt
new file mode 100644
index 0000000..759f0bc
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/ValueParserTest.kt
@@ -0,0 +1,61 @@
+package com.android.systemui.statusbar.commandline
+
+import android.graphics.Rect
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Assert.assertTrue
+import org.junit.Test
+
+@SmallTest
+class ValueParserTest : SysuiTestCase() {
+    @Test
+    fun parseString() {
+        assertThat(Type.String.parseValue("test")).isEqualTo(Result.success("test"))
+    }
+
+    @Test
+    fun parseInt() {
+        assertThat(Type.Int.parseValue("123")).isEqualTo(Result.success(123))
+
+        assertTrue(Type.Int.parseValue("not an Int").isFailure)
+    }
+
+    @Test
+    fun parseFloat() {
+        assertThat(Type.Float.parseValue("1.23")).isEqualTo(Result.success(1.23f))
+
+        assertTrue(Type.Int.parseValue("not a Float").isFailure)
+    }
+
+    @Test
+    fun parseBoolean() {
+        assertThat(Type.Boolean.parseValue("true")).isEqualTo(Result.success(true))
+        assertThat(Type.Boolean.parseValue("false")).isEqualTo(Result.success(false))
+
+        assertTrue(Type.Boolean.parseValue("not a Boolean").isFailure)
+    }
+
+    @Test
+    fun mapToComplexType() {
+        val parseSquare = Type.Int.map { Rect(it, it, it, it) }
+
+        assertThat(parseSquare.parseValue("10")).isEqualTo(Result.success(Rect(10, 10, 10, 10)))
+    }
+
+    @Test
+    fun mapToFallibleComplexType() {
+        val fallibleParseSquare =
+            Type.Int.map {
+                if (it > 0) {
+                    Rect(it, it, it, it)
+                } else {
+                    null
+                }
+            }
+
+        assertThat(fallibleParseSquare.parseValue("10"))
+            .isEqualTo(Result.success(Rect(10, 10, 10, 10)))
+        assertTrue(fallibleParseSquare.parseValue("-10").isFailure)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/disableflags/DisableFlagsLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/disableflags/DisableFlagsLoggerTest.kt
index 4b5d0a3..f1c7956 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/disableflags/DisableFlagsLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/disableflags/DisableFlagsLoggerTest.kt
@@ -37,73 +37,8 @@
     private val disableFlagsLogger = DisableFlagsLogger(disable1Flags, disable2Flags)
 
     @Test
-    fun getDisableFlagsString_oldAndNewSame_newAndUnchangedLoggedOldNotLogged() {
-        val state = DisableFlagsLogger.DisableState(
-                0b111, // ABC
-                0b01 // mN
-        )
-
-        val result = disableFlagsLogger.getDisableFlagsString(state, state)
-
-        assertThat(result).doesNotContain("Old")
-        assertThat(result).contains("ABC.mN")
-        assertThat(result).contains("(unchanged)")
-    }
-
-    @Test
-    fun getDisableFlagsString_oldAndNewDifferent_statesAndDiffLogged() {
-        val result = disableFlagsLogger.getDisableFlagsString(
-                DisableFlagsLogger.DisableState(
-                        0b111, // ABC
-                        0b01, // mN
-                ),
-                DisableFlagsLogger.DisableState(
-                        0b001, // abC
-                        0b10 // Mn
-                )
-        )
-
-        assertThat(result).contains("Old: ABC.mN")
-        assertThat(result).contains("New: abC.Mn")
-        assertThat(result).contains("(changed: ab.Mn)")
-    }
-
-    @Test
-    fun getDisableFlagsString_onlyDisable2Different_diffLoggedCorrectly() {
-        val result = disableFlagsLogger.getDisableFlagsString(
-                DisableFlagsLogger.DisableState(
-                        0b001, // abC
-                        0b01, // mN
-                ),
-                DisableFlagsLogger.DisableState(
-                        0b001, // abC
-                        0b00 // mn
-                )
-        )
-
-        assertThat(result).contains("(changed: .n)")
-    }
-
-    @Test
-    fun getDisableFlagsString_nullOld_onlyNewStateLogged() {
-        val result = disableFlagsLogger.getDisableFlagsString(
-            old = null,
-            new = DisableFlagsLogger.DisableState(
-                0b001, // abC
-                0b01, // mN
-            ),
-        )
-
-        assertThat(result).doesNotContain("Old")
-        assertThat(result).contains("abC.mN")
-        assertThat(result).doesNotContain("(")
-        assertThat(result).doesNotContain(")")
-    }
-
-    @Test
     fun getDisableFlagsString_nullLocalModification_localModNotLogged() {
         val result = disableFlagsLogger.getDisableFlagsString(
-                DisableFlagsLogger.DisableState(0, 0),
                 DisableFlagsLogger.DisableState(1, 1),
                 newAfterLocalModification = null
         )
@@ -118,9 +53,7 @@
                 0b10 // mn
         )
 
-        val result = disableFlagsLogger.getDisableFlagsString(
-                DisableFlagsLogger.DisableState(0, 0), newState, newState
-        )
+        val result = disableFlagsLogger.getDisableFlagsString(newState, newState)
 
         assertThat(result).doesNotContain("local modification")
     }
@@ -128,7 +61,6 @@
     @Test
     fun getDisableFlagsString_newAfterLocalModificationDifferent_localModAndDiffLogged() {
         val result = disableFlagsLogger.getDisableFlagsString(
-                old = DisableFlagsLogger.DisableState(0, 0),
                 new = DisableFlagsLogger.DisableState(
                         0b000, // abc
                         0b00 // mn
@@ -143,6 +75,22 @@
     }
 
     @Test
+    fun getDisableFlagsString_onlyDisable2Different_diffLoggedCorrectly() {
+        val result = disableFlagsLogger.getDisableFlagsString(
+            DisableFlagsLogger.DisableState(
+                0b001, // abC
+                0b01, // mN
+            ),
+            DisableFlagsLogger.DisableState(
+                0b001, // abC
+                0b00 // mn
+            )
+        )
+
+        assertThat(result).contains("(changed: .n)")
+    }
+
+    @Test
     fun constructor_defaultDisableFlags_noException() {
         // Just creating the logger with the default params will trigger the exception if there
         // is one.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/disableflags/data/repository/DisableFlagsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/disableflags/data/repository/DisableFlagsRepositoryTest.kt
new file mode 100644
index 0000000..580463a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/disableflags/data/repository/DisableFlagsRepositoryTest.kt
@@ -0,0 +1,274 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.statusbar.disableflags.data.repository
+
+import android.app.StatusBarManager.DISABLE2_NONE
+import android.app.StatusBarManager.DISABLE2_NOTIFICATION_SHADE
+import android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS
+import android.app.StatusBarManager.DISABLE_CLOCK
+import android.app.StatusBarManager.DISABLE_NONE
+import android.app.StatusBarManager.DISABLE_NOTIFICATION_ALERTS
+import android.content.res.Configuration
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.log.LogBufferFactory
+import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.statusbar.disableflags.DisableFlagsLogger
+import com.android.systemui.statusbar.disableflags.data.model.DisableFlagsModel
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.mock
+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
+import org.mockito.Mockito.verify
+
+@SmallTest
+@OptIn(ExperimentalCoroutinesApi::class)
+class DisableFlagsRepositoryTest : SysuiTestCase() {
+
+    private lateinit var underTest: DisableFlagsRepository
+
+    private val testScope = TestScope(UnconfinedTestDispatcher())
+    private val commandQueue: CommandQueue = mock()
+    private val configurationController: ConfigurationController = mock()
+    private val remoteInputQuickSettingsDisabler =
+        RemoteInputQuickSettingsDisabler(
+            context,
+            commandQueue,
+            configurationController,
+        )
+    private val logBuffer = LogBufferFactory(DumpManager(), mock()).create("buffer", 10)
+    private val disableFlagsLogger = DisableFlagsLogger()
+
+    @Before
+    fun setUp() {
+        underTest =
+            DisableFlagsRepositoryImpl(
+                commandQueue,
+                DISPLAY_ID,
+                testScope.backgroundScope,
+                remoteInputQuickSettingsDisabler,
+                logBuffer,
+                disableFlagsLogger,
+            )
+    }
+
+    @Test
+    fun disableFlags_initialValue_none() {
+        assertThat(underTest.disableFlags.value)
+            .isEqualTo(DisableFlagsModel(DISABLE_NONE, DISABLE2_NONE))
+    }
+
+    @Test
+    fun disableFlags_noSubscribers_callbackStillRegistered() =
+        testScope.runTest { verify(commandQueue).addCallback(any()) }
+
+    @Test
+    fun disableFlags_notifAlertsNotDisabled_notifAlertsEnabledTrue() =
+        testScope.runTest {
+            getCommandQueueCallback()
+                .disable(DISPLAY_ID, DISABLE_NONE, DISABLE2_NONE, /* animate= */ false)
+
+            assertThat(underTest.disableFlags.value.areNotificationAlertsEnabled()).isTrue()
+        }
+
+    @Test
+    fun disableFlags_notifAlertsDisabled_notifAlertsEnabledFalse() =
+        testScope.runTest {
+            getCommandQueueCallback()
+                .disable(
+                    DISPLAY_ID,
+                    DISABLE_NOTIFICATION_ALERTS,
+                    DISABLE2_NONE,
+                    /* animate= */ false,
+                )
+
+            assertThat(underTest.disableFlags.value.areNotificationAlertsEnabled()).isFalse()
+        }
+
+    @Test
+    fun disableFlags_notifAlertsDisabled_differentDisplay_notifAlertsEnabledTrue() =
+        testScope.runTest {
+            val wrongDisplayId = DISPLAY_ID + 10
+
+            getCommandQueueCallback()
+                .disable(
+                    wrongDisplayId,
+                    DISABLE_NOTIFICATION_ALERTS,
+                    DISABLE2_NONE,
+                    /* animate= */ false,
+                )
+
+            // THEN our repo reports them as still enabled
+            assertThat(underTest.disableFlags.value.areNotificationAlertsEnabled()).isTrue()
+        }
+
+    @Test
+    fun disableFlags_shadeNotDisabled_shadeEnabledTrue() =
+        testScope.runTest {
+            getCommandQueueCallback()
+                .disable(DISPLAY_ID, DISABLE_NONE, DISABLE2_NONE, /* animate= */ false)
+
+            assertThat(underTest.disableFlags.value.isShadeEnabled()).isTrue()
+        }
+
+    @Test
+    fun disableFlags_shadeDisabled_shadeEnabledFalse() =
+        testScope.runTest {
+            getCommandQueueCallback()
+                .disable(
+                    DISPLAY_ID,
+                    DISABLE_NONE,
+                    DISABLE2_NOTIFICATION_SHADE,
+                    /* animate= */ false,
+                )
+
+            assertThat(underTest.disableFlags.value.isShadeEnabled()).isFalse()
+        }
+
+    @Test
+    fun disableFlags_shadeDisabled_differentDisplay_shadeEnabledTrue() =
+        testScope.runTest {
+            val wrongDisplayId = DISPLAY_ID + 10
+
+            getCommandQueueCallback()
+                .disable(
+                    wrongDisplayId,
+                    DISABLE_NONE,
+                    DISABLE2_NOTIFICATION_SHADE,
+                    /* animate= */ false,
+                )
+
+            // THEN our repo reports them as still enabled
+            assertThat(underTest.disableFlags.value.isShadeEnabled()).isTrue()
+        }
+
+    @Test
+    fun disableFlags_quickSettingsNotDisabled_quickSettingsEnabledTrue() =
+        testScope.runTest {
+            getCommandQueueCallback()
+                .disable(DISPLAY_ID, DISABLE_NONE, DISABLE2_NONE, /* animate= */ false)
+
+            assertThat(underTest.disableFlags.value.isQuickSettingsEnabled()).isTrue()
+        }
+
+    @Test
+    fun disableFlags_quickSettingsDisabled_quickSettingsEnabledFalse() =
+        testScope.runTest {
+            getCommandQueueCallback()
+                .disable(
+                    DISPLAY_ID,
+                    DISABLE_NONE,
+                    DISABLE2_QUICK_SETTINGS,
+                    /* animate= */ false,
+                )
+
+            assertThat(underTest.disableFlags.value.isQuickSettingsEnabled()).isFalse()
+        }
+
+    @Test
+    fun disableFlags_quickSettingsDisabled_differentDisplay_quickSettingsEnabledTrue() =
+        testScope.runTest {
+            val wrongDisplayId = DISPLAY_ID + 10
+
+            getCommandQueueCallback()
+                .disable(
+                    wrongDisplayId,
+                    DISABLE_NONE,
+                    DISABLE2_QUICK_SETTINGS,
+                    /* animate= */ false,
+                )
+
+            // THEN our repo reports them as still enabled
+            assertThat(underTest.disableFlags.value.isQuickSettingsEnabled()).isTrue()
+        }
+
+    @Test
+    fun disableFlags_remoteInputActive_quickSettingsEnabledFalse() =
+        testScope.runTest {
+            // WHEN remote input is set up to be active
+            val configuration = Configuration(mContext.resources.configuration)
+            configuration.orientation = Configuration.ORIENTATION_LANDSCAPE
+            mContext.orCreateTestableResources.addOverride(
+                R.bool.config_use_split_notification_shade,
+                /* value= */ false
+            )
+            remoteInputQuickSettingsDisabler.setRemoteInputActive(true)
+            remoteInputQuickSettingsDisabler.onConfigChanged(configuration)
+
+            getCommandQueueCallback()
+                .disable(
+                    DISPLAY_ID,
+                    DISABLE_NONE,
+                    DISABLE2_NONE,
+                    /* animate= */ false,
+                )
+
+            // THEN quick settings is disabled (even if the disable flags don't say so)
+            assertThat(underTest.disableFlags.value.isQuickSettingsEnabled()).isFalse()
+        }
+
+    @Test
+    fun disableFlags_reactsToChanges() =
+        testScope.runTest {
+            getCommandQueueCallback()
+                .disable(
+                    DISPLAY_ID,
+                    DISABLE_NOTIFICATION_ALERTS,
+                    DISABLE2_NONE,
+                    /* animate= */ false,
+                )
+            assertThat(underTest.disableFlags.value.areNotificationAlertsEnabled()).isFalse()
+
+            getCommandQueueCallback()
+                .disable(
+                    DISPLAY_ID,
+                    DISABLE_CLOCK, // Unrelated to notifications
+                    DISABLE2_NONE,
+                    /* animate= */ false,
+                )
+            assertThat(underTest.disableFlags.value.areNotificationAlertsEnabled()).isTrue()
+
+            getCommandQueueCallback()
+                .disable(
+                    DISPLAY_ID,
+                    DISABLE_NOTIFICATION_ALERTS,
+                    DISABLE2_QUICK_SETTINGS or DISABLE2_NOTIFICATION_SHADE,
+                    /* animate= */ false,
+                )
+            assertThat(underTest.disableFlags.value.areNotificationAlertsEnabled()).isFalse()
+            assertThat(underTest.disableFlags.value.isShadeEnabled()).isFalse()
+            assertThat(underTest.disableFlags.value.isQuickSettingsEnabled()).isFalse()
+        }
+
+    private fun getCommandQueueCallback(): CommandQueue.Callbacks {
+        val callbackCaptor = argumentCaptor<CommandQueue.Callbacks>()
+        verify(commandQueue).addCallback(callbackCaptor.capture())
+        return callbackCaptor.value
+    }
+
+    private companion object {
+        const val DISPLAY_ID = 1
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/disableflags/data/repository/FakeDisableFlagsRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/disableflags/data/repository/FakeDisableFlagsRepository.kt
new file mode 100644
index 0000000..b66231c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/disableflags/data/repository/FakeDisableFlagsRepository.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS 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.disableflags.data.repository
+
+import com.android.systemui.statusbar.disableflags.data.model.DisableFlagsModel
+import kotlinx.coroutines.flow.MutableStateFlow
+
+class FakeDisableFlagsRepository : DisableFlagsRepository {
+    override val disableFlags = MutableStateFlow(DisableFlagsModel())
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventChipAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventChipAnimationControllerTest.kt
new file mode 100644
index 0000000..55b6be9
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventChipAnimationControllerTest.kt
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.events
+
+import android.content.Context
+import android.graphics.Rect
+import android.util.Pair
+import android.view.Gravity
+import android.view.View
+import android.widget.FrameLayout
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.statusbar.phone.StatusBarContentInsetsChangedListener
+import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider
+import com.android.systemui.statusbar.window.StatusBarWindowController
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+class SystemEventChipAnimationControllerTest : SysuiTestCase() {
+    private lateinit var controller: SystemEventChipAnimationController
+
+    @Mock private lateinit var sbWindowController: StatusBarWindowController
+    @Mock private lateinit var insetsProvider: StatusBarContentInsetsProvider
+
+    private var testView = TestView(mContext)
+    private var viewCreator: ViewCreator = { testView }
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+
+        // StatusBarWindowController is mocked. The addViewToWindow function needs to be mocked to
+        // ensure that the chip view is added to a parent view
+        whenever(sbWindowController.addViewToWindow(any(), any())).then {
+            val statusbarFake = FrameLayout(mContext)
+            statusbarFake.layout(
+                portraitArea.left,
+                portraitArea.top,
+                portraitArea.right,
+                portraitArea.bottom,
+            )
+            statusbarFake.addView(
+                it.arguments[0] as View,
+                it.arguments[1] as FrameLayout.LayoutParams
+            )
+        }
+
+        whenever(insetsProvider.getStatusBarContentInsetsForCurrentRotation())
+            .thenReturn(Pair(insets, insets))
+        whenever(insetsProvider.getStatusBarContentAreaForCurrentRotation())
+            .thenReturn(portraitArea)
+
+        controller =
+            SystemEventChipAnimationController(
+                context = mContext,
+                statusBarWindowController = sbWindowController,
+                contentInsetsProvider = insetsProvider,
+                featureFlags = FakeFeatureFlags(),
+            )
+    }
+
+    @Test
+    fun prepareChipAnimation_lazyInitializes() {
+        // Until Dagger can do our initialization, make sure that the first chip animation calls
+        // init()
+        assertFalse(controller.initialized)
+        controller.prepareChipAnimation(viewCreator)
+        assertTrue(controller.initialized)
+    }
+
+    @Test
+    fun prepareChipAnimation_positionsChip() {
+        controller.prepareChipAnimation(viewCreator)
+        val chipRect = controller.chipBounds
+
+        // SB area = 10, 0, 990, 100
+        // chip size = 0, 0, 100, 50
+        assertThat(chipRect).isEqualTo(Rect(890, 25, 990, 75))
+    }
+
+    @Test
+    fun prepareChipAnimation_rotation_repositionsChip() {
+        controller.prepareChipAnimation(viewCreator)
+
+        // Chip has been prepared, and is located at (890, 25, 990, 75)
+        // Rotation should put it into its landscape location:
+        // SB area = 10, 0, 1990, 80
+        // chip size = 0, 0, 100, 50
+
+        whenever(insetsProvider.getStatusBarContentAreaForCurrentRotation())
+            .thenReturn(landscapeArea)
+        getInsetsListener().onStatusBarContentInsetsChanged()
+
+        val chipRect = controller.chipBounds
+        assertThat(chipRect).isEqualTo(Rect(1890, 15, 1990, 65))
+    }
+
+    /** regression test for (b/289378932) */
+    @Test
+    fun fullScreenStatusBar_positionsChipAtTop_withTopGravity() {
+        // In the case of a fullscreen status bar window, the content insets area is still correct
+        // (because it uses the dimens), but the window can be full screen. This seems to happen
+        // when launching an app from the ongoing call chip.
+
+        // GIVEN layout the status bar window fullscreen portrait
+        whenever(sbWindowController.addViewToWindow(any(), any())).then {
+            val statusbarFake = FrameLayout(mContext)
+            statusbarFake.layout(
+                fullScreenSb.left,
+                fullScreenSb.top,
+                fullScreenSb.right,
+                fullScreenSb.bottom,
+            )
+
+            val lp = it.arguments[1] as FrameLayout.LayoutParams
+            assertThat(lp.gravity and Gravity.VERTICAL_GRAVITY_MASK).isEqualTo(Gravity.TOP)
+
+            statusbarFake.addView(
+                it.arguments[0] as View,
+                lp,
+            )
+        }
+
+        // GIVEN insets provider gives the correct content area
+        whenever(insetsProvider.getStatusBarContentAreaForCurrentRotation())
+            .thenReturn(portraitArea)
+
+        // WHEN the controller lays out the chip in a fullscreen window
+        controller.prepareChipAnimation(viewCreator)
+
+        // THEN it still aligns the chip to the content area provided by the insets provider
+        val chipRect = controller.chipBounds
+        assertThat(chipRect).isEqualTo(Rect(890, 25, 990, 75))
+    }
+
+    class TestView(context: Context) : View(context), BackgroundAnimatableView {
+        override val view: View
+            get() = this
+
+        override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+            setMeasuredDimension(100, 50)
+        }
+
+        override fun setBoundsForAnimation(l: Int, t: Int, r: Int, b: Int) {
+            setLeftTopRightBottom(l, t, r, b)
+        }
+    }
+
+    private fun getInsetsListener(): StatusBarContentInsetsChangedListener {
+        val callbackCaptor = argumentCaptor<StatusBarContentInsetsChangedListener>()
+        verify(insetsProvider).addCallback(capture(callbackCaptor))
+        return callbackCaptor.value!!
+    }
+
+    companion object {
+        private val portraitArea = Rect(10, 0, 990, 100)
+        private val landscapeArea = Rect(10, 0, 1990, 80)
+        private val fullScreenSb = Rect(10, 0, 990, 2000)
+
+        // 10px insets on both sides
+        private const val insets = 10
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventCoordinatorTest.kt
new file mode 100644
index 0000000..786856b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventCoordinatorTest.kt
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.events
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor
+import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.State.CONNECTED
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.privacy.PrivacyItemController
+import com.android.systemui.statusbar.policy.BatteryController
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.time.FakeSystemClock
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+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.times
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+@SmallTest
+@OptIn(ExperimentalCoroutinesApi::class)
+class SystemEventCoordinatorTest : SysuiTestCase() {
+
+    private val fakeSystemClock = FakeSystemClock()
+    private val featureFlags = FakeFeatureFlags()
+    private val testScope = TestScope(UnconfinedTestDispatcher())
+    private val connectedDisplayInteractor = FakeConnectedDisplayInteractor()
+
+    @Mock lateinit var batteryController: BatteryController
+    @Mock lateinit var privacyController: PrivacyItemController
+    @Mock lateinit var scheduler: SystemStatusAnimationScheduler
+
+    private lateinit var systemEventCoordinator: SystemEventCoordinator
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        systemEventCoordinator =
+            SystemEventCoordinator(
+                    fakeSystemClock,
+                    batteryController,
+                    privacyController,
+                    context,
+                    featureFlags,
+                    TestScope(UnconfinedTestDispatcher()),
+                    connectedDisplayInteractor
+                )
+                .apply { attachScheduler(scheduler) }
+    }
+
+    @Test
+    fun startObserving_propagatesConnectedDisplayStatusEvents() =
+        testScope.runTest {
+            systemEventCoordinator.startObserving()
+
+            connectedDisplayInteractor.emit(CONNECTED)
+            connectedDisplayInteractor.emit(CONNECTED)
+
+            verify(scheduler, times(2)).onStatusEvent(any<ConnectedDisplayEvent>())
+        }
+
+    @Test
+    fun stopObserving_doesNotPropagateConnectedDisplayStatusEvents() =
+        testScope.runTest {
+            systemEventCoordinator.startObserving()
+
+            connectedDisplayInteractor.emit(CONNECTED)
+
+            verify(scheduler).onStatusEvent(any<ConnectedDisplayEvent>())
+
+            systemEventCoordinator.stopObserving()
+
+            connectedDisplayInteractor.emit(CONNECTED)
+
+            verifyNoMoreInteractions(scheduler)
+        }
+
+    class FakeConnectedDisplayInteractor : ConnectedDisplayInteractor {
+        private val flow = MutableSharedFlow<ConnectedDisplayInteractor.State>()
+        suspend fun emit(value: ConnectedDisplayInteractor.State) = flow.emit(value)
+        override val connectedDisplayState: Flow<ConnectedDisplayInteractor.State>
+            get() = flow
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
index 914301f..2af0ceb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
@@ -69,6 +69,8 @@
 
     @Mock private lateinit var listener: SystemStatusAnimationCallback
 
+    @Mock private lateinit var logger: SystemStatusAnimationSchedulerLogger
+
     private lateinit var systemClock: FakeSystemClock
     private lateinit var chipAnimationController: SystemEventChipAnimationController
     private lateinit var systemStatusAnimationScheduler: SystemStatusAnimationScheduler
@@ -538,7 +540,8 @@
                 statusBarWindowController,
                 dumpManager,
                 systemClock,
-                CoroutineScope(StandardTestDispatcher(testScope.testScheduler))
+                CoroutineScope(StandardTestDispatcher(testScope.testScheduler)),
+                logger
             )
         // add a mock listener
         systemStatusAnimationScheduler.addCallback(listener)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLoggerTest.kt
index a37c386..902dd51 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLoggerTest.kt
@@ -20,8 +20,8 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
 import com.android.systemui.log.LogcatEchoTracker
+import com.android.systemui.log.core.LogLevel
 import com.android.systemui.statusbar.StatusBarState
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/RoundableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/RoundableTest.kt
index 89faa239..a56fb2c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/RoundableTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/RoundableTest.kt
@@ -3,7 +3,11 @@
 import android.view.View
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
 import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
 import org.junit.Assert.assertEquals
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -15,8 +19,9 @@
 @SmallTest
 @RunWith(JUnit4::class)
 class RoundableTest : SysuiTestCase() {
-    val targetView: View = mock()
-    val roundable = FakeRoundable(targetView)
+    private val targetView: View = mock()
+    private val featureFlags = FakeFeatureFlags()
+    private val roundable = FakeRoundable(targetView = targetView, featureFlags = featureFlags)
 
     @Test
     fun defaultConfig_shouldNotHaveRoundedCorner() {
@@ -144,16 +149,62 @@
         assertEquals(0.2f, roundable.roundableState.bottomRoundness)
     }
 
+    @Test
+    fun getCornerRadii_radius_maxed_to_height() {
+        whenever(targetView.height).thenReturn(10)
+        featureFlags.set(Flags.IMPROVED_HUN_ANIMATIONS, true)
+        roundable.requestRoundness(1f, 1f, SOURCE1)
+
+        assertCornerRadiiEquals(5f, 5f)
+    }
+
+    @Test
+    fun getCornerRadii_topRadius_maxed_to_height() {
+        whenever(targetView.height).thenReturn(5)
+        featureFlags.set(Flags.IMPROVED_HUN_ANIMATIONS, true)
+        roundable.requestRoundness(1f, 0f, SOURCE1)
+
+        assertCornerRadiiEquals(5f, 0f)
+    }
+
+    @Test
+    fun getCornerRadii_bottomRadius_maxed_to_height() {
+        whenever(targetView.height).thenReturn(5)
+        featureFlags.set(Flags.IMPROVED_HUN_ANIMATIONS, true)
+        roundable.requestRoundness(0f, 1f, SOURCE1)
+
+        assertCornerRadiiEquals(0f, 5f)
+    }
+
+    @Test
+    fun getCornerRadii_radii_kept() {
+        whenever(targetView.height).thenReturn(100)
+        featureFlags.set(Flags.IMPROVED_HUN_ANIMATIONS, true)
+        roundable.requestRoundness(1f, 1f, SOURCE1)
+
+        assertCornerRadiiEquals(MAX_RADIUS, MAX_RADIUS)
+    }
+
+    private fun assertCornerRadiiEquals(top: Float, bottom: Float) {
+        assertEquals("topCornerRadius", top, roundable.topCornerRadius)
+        assertEquals("bottomCornerRadius", bottom, roundable.bottomCornerRadius)
+    }
+
     class FakeRoundable(
         targetView: View,
         radius: Float = MAX_RADIUS,
+        featureFlags: FeatureFlags
     ) : Roundable {
         override val roundableState =
             RoundableState(
                 targetView = targetView,
                 roundable = this,
                 maxRadius = radius,
+                featureFlags = featureFlags
             )
+
+        override val clipHeight: Int
+            get() = roundableState.targetView.height
     }
 
     companion object {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DreamCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DreamCoordinatorTest.kt
new file mode 100644
index 0000000..a544cad
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DreamCoordinatorTest.kt
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.collection.coordinator
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.SysuiStatusBarStateController
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.withArgCaptor
+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
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class DreamCoordinatorTest : SysuiTestCase() {
+    @Mock private lateinit var statusBarStateController: SysuiStatusBarStateController
+    @Mock private lateinit var notifPipeline: NotifPipeline
+    @Mock private lateinit var filterListener: Pluggable.PluggableListener<NotifFilter>
+
+    private val keyguardRepository = FakeKeyguardRepository()
+    private var fakeEntry: NotificationEntry = NotificationEntryBuilder().build()
+    val testDispatcher = UnconfinedTestDispatcher()
+    val testScope = TestScope(testDispatcher)
+
+    private lateinit var filter: NotifFilter
+    private lateinit var statusBarListener: StatusBarStateController.StateListener
+    private lateinit var dreamCoordinator: DreamCoordinator
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
+
+        // Build the coordinator
+        dreamCoordinator =
+            DreamCoordinator(
+                statusBarStateController,
+                testScope.backgroundScope,
+                keyguardRepository
+            )
+
+        // Attach the pipeline and capture the listeners/filters that it registers
+        dreamCoordinator.attach(notifPipeline)
+
+        filter = withArgCaptor { verify(notifPipeline).addPreGroupFilter(capture()) }
+        filter.setInvalidationListener(filterListener)
+
+        statusBarListener = withArgCaptor {
+            verify(statusBarStateController).addCallback(capture())
+        }
+    }
+
+    @Test
+    fun hideNotifications_whenDreamingAndOnKeyguard() =
+        testScope.runTest {
+            // GIVEN we are on keyguard and not dreaming
+            keyguardRepository.setKeyguardShowing(true)
+            keyguardRepository.setIsActiveDreamLockscreenHosted(false)
+            runCurrent()
+
+            // THEN notifications are not filtered out
+            verifyPipelinesNotInvalidated()
+            assertThat(filter.shouldFilterOut(fakeEntry, 0L)).isFalse()
+
+            // WHEN dreaming starts and the active dream is hosted in lockscreen
+            keyguardRepository.setIsActiveDreamLockscreenHosted(true)
+            runCurrent()
+
+            // THEN pipeline is notified and notifications should all be filtered out
+            verifyPipelinesInvalidated()
+            assertThat(filter.shouldFilterOut(fakeEntry, 0L)).isTrue()
+        }
+
+    @Test
+    fun showNotifications_whenDreamingAndNotOnKeyguard() =
+        testScope.runTest {
+            // GIVEN we are on the keyguard and active dream is hosted in lockscreen
+            keyguardRepository.setKeyguardShowing(true)
+            keyguardRepository.setIsActiveDreamLockscreenHosted(true)
+            runCurrent()
+
+            // THEN pipeline is notified and notifications are all filtered out
+            verifyPipelinesInvalidated()
+            clearPipelineInvocations()
+            assertThat(filter.shouldFilterOut(fakeEntry, 0L)).isTrue()
+
+            // WHEN we are no longer on the keyguard
+            statusBarListener.onStateChanged(StatusBarState.SHADE)
+
+            // THEN pipeline is notified and notifications are not filtered out
+            verifyPipelinesInvalidated()
+            assertThat(filter.shouldFilterOut(fakeEntry, 0L)).isFalse()
+        }
+
+    @Test
+    fun showNotifications_whenOnKeyguardAndNotDreaming() =
+        testScope.runTest {
+            // GIVEN we are on the keyguard and active dream is hosted in lockscreen
+            keyguardRepository.setKeyguardShowing(true)
+            keyguardRepository.setIsActiveDreamLockscreenHosted(true)
+            runCurrent()
+
+            // THEN pipeline is notified and notifications are all filtered out
+            verifyPipelinesInvalidated()
+            clearPipelineInvocations()
+            assertThat(filter.shouldFilterOut(fakeEntry, 0L)).isTrue()
+
+            // WHEN the lockscreen hosted dream stops
+            keyguardRepository.setIsActiveDreamLockscreenHosted(false)
+            runCurrent()
+
+            // THEN pipeline is notified and notifications are not filtered out
+            verifyPipelinesInvalidated()
+            assertThat(filter.shouldFilterOut(fakeEntry, 0L)).isFalse()
+        }
+
+    private fun verifyPipelinesInvalidated() {
+        verify(filterListener).onPluggableInvalidated(eq(filter), any())
+    }
+
+    private fun verifyPipelinesNotInvalidated() {
+        verify(filterListener, never()).onPluggableInvalidated(eq(filter), any())
+    }
+
+    private fun clearPipelineInvocations() {
+        clearInvocations(filterListener)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt
index 2fbe871..ea70e9e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt
@@ -32,7 +32,6 @@
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.StatusBarState
-import com.android.systemui.statusbar.notification.NotifPipelineFlags
 import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
 import com.android.systemui.statusbar.notification.collection.NotifPipeline
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
@@ -46,11 +45,14 @@
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
 import com.android.systemui.statusbar.policy.HeadsUpManager
 import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener
+import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.withArgCaptor
 import com.android.systemui.util.settings.FakeSettings
 import com.google.common.truth.Truth.assertThat
+import java.util.function.Consumer
+import kotlin.time.Duration.Companion.seconds
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.TestCoroutineScheduler
@@ -62,9 +64,8 @@
 import org.mockito.ArgumentMatchers.same
 import org.mockito.Mockito.anyString
 import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
-import java.util.function.Consumer
-import kotlin.time.Duration.Companion.seconds
 import org.mockito.Mockito.`when` as whenever
 
 @SmallTest
@@ -75,7 +76,6 @@
     private val keyguardNotifVisibilityProvider: KeyguardNotificationVisibilityProvider = mock()
     private val keyguardRepository = FakeKeyguardRepository()
     private val keyguardTransitionRepository = FakeKeyguardTransitionRepository()
-    private val notifPipelineFlags: NotifPipelineFlags = mock()
     private val notifPipeline: NotifPipeline = mock()
     private val sectionHeaderVisibilityProvider: SectionHeaderVisibilityProvider = mock()
     private val statusBarStateController: StatusBarStateController = mock()
@@ -136,13 +136,8 @@
             )
             testScheduler.runCurrent()
 
-            // WHEN: The shade is expanded
-            whenever(statusBarStateController.isExpanded).thenReturn(true)
-            statusBarStateListener.onExpandedChanged(true)
-            testScheduler.runCurrent()
-
-            // THEN: The notification is still treated as "unseen" and is not filtered out.
-            assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isFalse()
+            // THEN: We are no longer listening for shade expansions
+            verify(statusBarStateController, never()).addCallback(any())
         }
     }
 
@@ -152,6 +147,10 @@
         keyguardRepository.setKeyguardShowing(false)
         whenever(statusBarStateController.isExpanded).thenReturn(false)
         runKeyguardCoordinatorTest {
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GONE)
+            )
+
             // WHEN: A notification is posted
             val fakeEntry = NotificationEntryBuilder().build()
             collectionListener.onEntryAdded(fakeEntry)
@@ -162,6 +161,9 @@
 
             // WHEN: The keyguard is now showing
             keyguardRepository.setKeyguardShowing(true)
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(from = KeyguardState.GONE, to = KeyguardState.AOD)
+            )
             testScheduler.runCurrent()
 
             // THEN: The notification is recognized as "seen" and is filtered out.
@@ -169,6 +171,9 @@
 
             // WHEN: The keyguard goes away
             keyguardRepository.setKeyguardShowing(false)
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(from = KeyguardState.AOD, to = KeyguardState.GONE)
+            )
             testScheduler.runCurrent()
 
             // THEN: The notification is shown regardless
@@ -182,9 +187,10 @@
         keyguardRepository.setKeyguardShowing(false)
         whenever(statusBarStateController.isExpanded).thenReturn(true)
         runKeyguardCoordinatorTest {
-            val fakeEntry = NotificationEntryBuilder()
+            val fakeEntry =
+                NotificationEntryBuilder()
                     .setNotification(Notification.Builder(mContext, "id").setOngoing(true).build())
-                .build()
+                    .build()
             collectionListener.onEntryAdded(fakeEntry)
 
             // WHEN: The keyguard is now showing
@@ -202,11 +208,13 @@
         keyguardRepository.setKeyguardShowing(false)
         whenever(statusBarStateController.isExpanded).thenReturn(true)
         runKeyguardCoordinatorTest {
-            val fakeEntry = NotificationEntryBuilder().build().apply {
-                row = mock<ExpandableNotificationRow>().apply {
-                    whenever(isMediaRow).thenReturn(true)
+            val fakeEntry =
+                NotificationEntryBuilder().build().apply {
+                    row =
+                        mock<ExpandableNotificationRow>().apply {
+                            whenever(isMediaRow).thenReturn(true)
+                        }
                 }
-            }
             collectionListener.onEntryAdded(fakeEntry)
 
             // WHEN: The keyguard is now showing
@@ -299,14 +307,12 @@
         runKeyguardCoordinatorTest {
             // WHEN: A new notification is posted
             val fakeSummary = NotificationEntryBuilder().build()
-            val fakeChild = NotificationEntryBuilder()
+            val fakeChild =
+                NotificationEntryBuilder()
                     .setGroup(context, "group")
                     .setGroupSummary(context, false)
                     .build()
-            GroupEntryBuilder()
-                    .setSummary(fakeSummary)
-                    .addChild(fakeChild)
-                    .build()
+            GroupEntryBuilder().setSummary(fakeSummary).addChild(fakeChild).build()
 
             collectionListener.onEntryAdded(fakeSummary)
             collectionListener.onEntryAdded(fakeChild)
@@ -331,6 +337,10 @@
         runKeyguardCoordinatorTest {
             val fakeEntry = NotificationEntryBuilder().build()
             collectionListener.onEntryAdded(fakeEntry)
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(from = KeyguardState.AOD, to = KeyguardState.LOCKSCREEN)
+            )
+            testScheduler.runCurrent()
 
             // WHEN: five seconds have passed
             testScheduler.advanceTimeBy(5.seconds)
@@ -338,10 +348,16 @@
 
             // WHEN: Keyguard is no longer showing
             keyguardRepository.setKeyguardShowing(false)
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GONE)
+            )
             testScheduler.runCurrent()
 
             // WHEN: Keyguard is shown again
             keyguardRepository.setKeyguardShowing(true)
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(from = KeyguardState.GONE, to = KeyguardState.AOD)
+            )
             testScheduler.runCurrent()
 
             // THEN: The notification is now recognized as "seen" and is filtered out.
@@ -354,11 +370,17 @@
         // GIVEN: Keyguard is showing, unseen notification is present
         keyguardRepository.setKeyguardShowing(true)
         runKeyguardCoordinatorTest {
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(from = KeyguardState.GONE, to = KeyguardState.LOCKSCREEN)
+            )
             val fakeEntry = NotificationEntryBuilder().build()
             collectionListener.onEntryAdded(fakeEntry)
 
             // WHEN: Keyguard is no longer showing
             keyguardRepository.setKeyguardShowing(false)
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GONE)
+            )
 
             // WHEN: Keyguard is shown again
             keyguardRepository.setKeyguardShowing(true)
@@ -369,14 +391,212 @@
         }
     }
 
+    @Test
+    fun unseenNotificationIsNotMarkedAsSeenIfNotOnKeyguardLongEnough() {
+        // GIVEN: Keyguard is showing, not dozing, unseen notification is present
+        keyguardRepository.setKeyguardShowing(true)
+        keyguardRepository.setIsDozing(false)
+        runKeyguardCoordinatorTest {
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(from = KeyguardState.GONE, to = KeyguardState.LOCKSCREEN)
+            )
+            val firstEntry = NotificationEntryBuilder().setId(1).build()
+            collectionListener.onEntryAdded(firstEntry)
+            testScheduler.runCurrent()
+
+            // WHEN: one second has passed
+            testScheduler.advanceTimeBy(1.seconds)
+            testScheduler.runCurrent()
+
+            // WHEN: another unseen notification is posted
+            val secondEntry = NotificationEntryBuilder().setId(2).build()
+            collectionListener.onEntryAdded(secondEntry)
+            testScheduler.runCurrent()
+
+            // WHEN: four more seconds have passed
+            testScheduler.advanceTimeBy(4.seconds)
+            testScheduler.runCurrent()
+
+            // WHEN: the keyguard is no longer showing
+            keyguardRepository.setKeyguardShowing(false)
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GONE)
+            )
+            testScheduler.runCurrent()
+
+            // WHEN: Keyguard is shown again
+            keyguardRepository.setKeyguardShowing(true)
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(from = KeyguardState.GONE, to = KeyguardState.LOCKSCREEN)
+            )
+            testScheduler.runCurrent()
+
+            // THEN: The first notification is considered seen and is filtered out.
+            assertThat(unseenFilter.shouldFilterOut(firstEntry, 0L)).isTrue()
+
+            // THEN: The second notification is still considered unseen and is not filtered out
+            assertThat(unseenFilter.shouldFilterOut(secondEntry, 0L)).isFalse()
+        }
+    }
+
+    @Test
+    fun unseenNotificationOnKeyguardNotMarkedAsSeenIfRemovedAfterThreshold() {
+        // GIVEN: Keyguard is showing, not dozing
+        keyguardRepository.setKeyguardShowing(true)
+        keyguardRepository.setIsDozing(false)
+        runKeyguardCoordinatorTest {
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(from = KeyguardState.GONE, to = KeyguardState.LOCKSCREEN)
+            )
+            testScheduler.runCurrent()
+
+            // WHEN: a new notification is posted
+            val entry = NotificationEntryBuilder().setId(1).build()
+            collectionListener.onEntryAdded(entry)
+            testScheduler.runCurrent()
+
+            // WHEN: five more seconds have passed
+            testScheduler.advanceTimeBy(5.seconds)
+            testScheduler.runCurrent()
+
+            // WHEN: the notification is removed
+            collectionListener.onEntryRemoved(entry, 0)
+            testScheduler.runCurrent()
+
+            // WHEN: the notification is re-posted
+            collectionListener.onEntryAdded(entry)
+            testScheduler.runCurrent()
+
+            // WHEN: one more second has passed
+            testScheduler.advanceTimeBy(1.seconds)
+            testScheduler.runCurrent()
+
+            // WHEN: the keyguard is no longer showing
+            keyguardRepository.setKeyguardShowing(false)
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GONE)
+            )
+            testScheduler.runCurrent()
+
+            // WHEN: Keyguard is shown again
+            keyguardRepository.setKeyguardShowing(true)
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(from = KeyguardState.GONE, to = KeyguardState.LOCKSCREEN)
+            )
+            testScheduler.runCurrent()
+
+            // THEN: The notification is considered unseen and is not filtered out.
+            assertThat(unseenFilter.shouldFilterOut(entry, 0L)).isFalse()
+        }
+    }
+
+    @Test
+    fun unseenNotificationOnKeyguardNotMarkedAsSeenIfRemovedBeforeThreshold() {
+        // GIVEN: Keyguard is showing, not dozing
+        keyguardRepository.setKeyguardShowing(true)
+        keyguardRepository.setIsDozing(false)
+        runKeyguardCoordinatorTest {
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(from = KeyguardState.GONE, to = KeyguardState.LOCKSCREEN)
+            )
+            testScheduler.runCurrent()
+
+            // WHEN: a new notification is posted
+            val entry = NotificationEntryBuilder().setId(1).build()
+            collectionListener.onEntryAdded(entry)
+            testScheduler.runCurrent()
+
+            // WHEN: one second has passed
+            testScheduler.advanceTimeBy(1.seconds)
+            testScheduler.runCurrent()
+
+            // WHEN: the notification is removed
+            collectionListener.onEntryRemoved(entry, 0)
+            testScheduler.runCurrent()
+
+            // WHEN: the notification is re-posted
+            collectionListener.onEntryAdded(entry)
+            testScheduler.runCurrent()
+
+            // WHEN: one more second has passed
+            testScheduler.advanceTimeBy(1.seconds)
+            testScheduler.runCurrent()
+
+            // WHEN: the keyguard is no longer showing
+            keyguardRepository.setKeyguardShowing(false)
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GONE)
+            )
+            testScheduler.runCurrent()
+
+            // WHEN: Keyguard is shown again
+            keyguardRepository.setKeyguardShowing(true)
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(from = KeyguardState.GONE, to = KeyguardState.LOCKSCREEN)
+            )
+            testScheduler.runCurrent()
+
+            // THEN: The notification is considered unseen and is not filtered out.
+            assertThat(unseenFilter.shouldFilterOut(entry, 0L)).isFalse()
+        }
+    }
+
+    @Test
+    fun unseenNotificationOnKeyguardNotMarkedAsSeenIfUpdatedBeforeThreshold() {
+        // GIVEN: Keyguard is showing, not dozing
+        keyguardRepository.setKeyguardShowing(true)
+        keyguardRepository.setIsDozing(false)
+        runKeyguardCoordinatorTest {
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(from = KeyguardState.GONE, to = KeyguardState.LOCKSCREEN)
+            )
+            testScheduler.runCurrent()
+
+            // WHEN: a new notification is posted
+            val entry = NotificationEntryBuilder().setId(1).build()
+            collectionListener.onEntryAdded(entry)
+            testScheduler.runCurrent()
+
+            // WHEN: one second has passed
+            testScheduler.advanceTimeBy(1.seconds)
+            testScheduler.runCurrent()
+
+            // WHEN: the notification is updated
+            collectionListener.onEntryUpdated(entry)
+            testScheduler.runCurrent()
+
+            // WHEN: four more seconds have passed
+            testScheduler.advanceTimeBy(4.seconds)
+            testScheduler.runCurrent()
+
+            // WHEN: the keyguard is no longer showing
+            keyguardRepository.setKeyguardShowing(false)
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GONE)
+            )
+            testScheduler.runCurrent()
+
+            // WHEN: Keyguard is shown again
+            keyguardRepository.setKeyguardShowing(true)
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(from = KeyguardState.GONE, to = KeyguardState.LOCKSCREEN)
+            )
+            testScheduler.runCurrent()
+
+            // THEN: The notification is considered unseen and is not filtered out.
+            assertThat(unseenFilter.shouldFilterOut(entry, 0L)).isFalse()
+        }
+    }
+
     private fun runKeyguardCoordinatorTest(
         testBlock: suspend KeyguardCoordinatorTestScope.() -> Unit
     ) {
         val testDispatcher = UnconfinedTestDispatcher()
         val testScope = TestScope(testDispatcher)
-        val fakeSettings = FakeSettings().apply {
-            putInt(Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS, 1)
-        }
+        val fakeSettings =
+            FakeSettings().apply {
+                putInt(Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS, 1)
+            }
         val seenNotificationsProvider = SeenNotificationsProviderImpl()
         val keyguardCoordinator =
             KeyguardCoordinator(
@@ -387,7 +607,6 @@
                 keyguardRepository,
                 keyguardTransitionRepository,
                 mock<KeyguardCoordinatorLogger>(),
-                notifPipelineFlags,
                 testScope.backgroundScope,
                 sectionHeaderVisibilityProvider,
                 fakeSettings,
@@ -397,11 +616,12 @@
         keyguardCoordinator.attach(notifPipeline)
         testScope.runTest(dispatchTimeoutMs = 1.seconds.inWholeMilliseconds) {
             KeyguardCoordinatorTestScope(
-                keyguardCoordinator,
-                testScope,
-                seenNotificationsProvider,
-                fakeSettings,
-            ).testBlock()
+                    keyguardCoordinator,
+                    testScope,
+                    seenNotificationsProvider,
+                    fakeSettings,
+                )
+                .testBlock()
         }
     }
 
@@ -414,10 +634,9 @@
         val testScheduler: TestCoroutineScheduler
             get() = scope.testScheduler
 
-        val onStateChangeListener: Consumer<String> =
-            withArgCaptor {
-                verify(keyguardNotifVisibilityProvider).addOnStateChangedListener(capture())
-            }
+        val onStateChangeListener: Consumer<String> = withArgCaptor {
+            verify(keyguardNotifVisibilityProvider).addOnStateChangedListener(capture())
+        }
 
         val unseenFilter: NotifFilter
             get() = keyguardCoordinator.unseenNotifFilter
@@ -426,11 +645,11 @@
             verify(notifPipeline).addCollectionListener(capture())
         }
 
-        val onHeadsUpChangedListener: OnHeadsUpChangedListener get() =
-            withArgCaptor { verify(headsUpManager).addListener(capture()) }
+        val onHeadsUpChangedListener: OnHeadsUpChangedListener
+            get() = withArgCaptor { verify(headsUpManager).addListener(capture()) }
 
-        val statusBarStateListener: StatusBarStateController.StateListener get() =
-            withArgCaptor { verify(statusBarStateController).addCallback(capture()) }
+        val statusBarStateListener: StatusBarStateController.StateListener
+            get() = withArgCaptor { verify(statusBarStateController).addCallback(capture()) }
 
         var showOnlyUnseenNotifsOnKeyguardSetting: Boolean
             get() =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsInteractorTest.kt
new file mode 100644
index 0000000..fe49016
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsInteractorTest.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.systemui.statusbar.notification.domain.interactor
+
+import android.app.StatusBarManager
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.log.LogBufferFactory
+import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.statusbar.disableflags.DisableFlagsLogger
+import com.android.systemui.statusbar.disableflags.data.repository.DisableFlagsRepository
+import com.android.systemui.statusbar.disableflags.data.repository.DisableFlagsRepositoryImpl
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mockito.verify
+
+@SmallTest
+@OptIn(ExperimentalCoroutinesApi::class)
+class NotificationsInteractorTest : SysuiTestCase() {
+
+    private lateinit var underTest: NotificationsInteractor
+
+    private val testScope = TestScope(UnconfinedTestDispatcher())
+    private val commandQueue: CommandQueue = mock()
+    private val logBuffer = LogBufferFactory(DumpManager(), mock()).create("buffer", 10)
+    private val disableFlagsLogger = DisableFlagsLogger()
+    private lateinit var disableFlagsRepository: DisableFlagsRepository
+
+    @Before
+    fun setUp() {
+        disableFlagsRepository =
+            DisableFlagsRepositoryImpl(
+                commandQueue,
+                DISPLAY_ID,
+                testScope.backgroundScope,
+                mock(),
+                logBuffer,
+                disableFlagsLogger,
+            )
+        underTest = NotificationsInteractor(disableFlagsRepository)
+    }
+
+    @Test
+    fun disableFlags_notifAlertsNotDisabled_notifAlertsEnabledTrue() {
+        val callback = getCommandQueueCallback()
+
+        callback.disable(
+            DISPLAY_ID,
+            StatusBarManager.DISABLE_NONE,
+            StatusBarManager.DISABLE2_NONE,
+            /* animate= */ false
+        )
+
+        assertThat(underTest.areNotificationAlertsEnabled()).isTrue()
+    }
+
+    @Test
+    fun disableFlags_notifAlertsDisabled_notifAlertsEnabledFalse() {
+        val callback = getCommandQueueCallback()
+
+        callback.disable(
+            DISPLAY_ID,
+            StatusBarManager.DISABLE_NOTIFICATION_ALERTS,
+            StatusBarManager.DISABLE2_NONE,
+            /* animate= */ false
+        )
+
+        assertThat(underTest.areNotificationAlertsEnabled()).isFalse()
+    }
+
+    private fun getCommandQueueCallback(): CommandQueue.Callbacks {
+        val callbackCaptor = argumentCaptor<CommandQueue.Callbacks>()
+        verify(commandQueue).addCallback(callbackCaptor.capture())
+        return callbackCaptor.value
+    }
+
+    private companion object {
+        const val DISPLAY_ID = 1
+    }
+}
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 d3e5816..daa45db 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
@@ -426,23 +426,8 @@
     }
 
     @Test
-    public void testShouldHeadsUp_oldWhen_flagDisabled() throws Exception {
-        ensureStateForHeadsUpWhenAwake();
-        when(mFlags.isNoHunForOldWhenEnabled()).thenReturn(false);
-
-        NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
-        entry.getSbn().getNotification().when = makeWhenHoursAgo(25);
-
-        assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isTrue();
-
-        verify(mLogger, never()).logNoHeadsUpOldWhen(any(), anyLong(), anyLong());
-        verify(mLogger, never()).logMaybeHeadsUpDespiteOldWhen(any(), anyLong(), anyLong(), any());
-    }
-
-    @Test
     public void testShouldHeadsUp_oldWhen_whenNow() throws Exception {
         ensureStateForHeadsUpWhenAwake();
-        when(mFlags.isNoHunForOldWhenEnabled()).thenReturn(true);
 
         NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
 
@@ -455,7 +440,6 @@
     @Test
     public void testShouldHeadsUp_oldWhen_whenRecent() throws Exception {
         ensureStateForHeadsUpWhenAwake();
-        when(mFlags.isNoHunForOldWhenEnabled()).thenReturn(true);
 
         NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
         entry.getSbn().getNotification().when = makeWhenHoursAgo(13);
@@ -469,7 +453,6 @@
     @Test
     public void testShouldHeadsUp_oldWhen_whenZero() throws Exception {
         ensureStateForHeadsUpWhenAwake();
-        when(mFlags.isNoHunForOldWhenEnabled()).thenReturn(true);
 
         NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
         entry.getSbn().getNotification().when = 0L;
@@ -484,7 +467,6 @@
     @Test
     public void testShouldHeadsUp_oldWhen_whenNegative() throws Exception {
         ensureStateForHeadsUpWhenAwake();
-        when(mFlags.isNoHunForOldWhenEnabled()).thenReturn(true);
 
         NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
         entry.getSbn().getNotification().when = -1L;
@@ -498,7 +480,6 @@
     @Test
     public void testShouldHeadsUp_oldWhen_hasFullScreenIntent() throws Exception {
         ensureStateForHeadsUpWhenAwake();
-        when(mFlags.isNoHunForOldWhenEnabled()).thenReturn(true);
         long when = makeWhenHoursAgo(25);
 
         NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silent= */ false);
@@ -514,7 +495,6 @@
     @Test
     public void testShouldHeadsUp_oldWhen_isForegroundService() throws Exception {
         ensureStateForHeadsUpWhenAwake();
-        when(mFlags.isNoHunForOldWhenEnabled()).thenReturn(true);
         long when = makeWhenHoursAgo(25);
 
         NotificationEntry entry = createFgsNotification(IMPORTANCE_HIGH);
@@ -530,7 +510,6 @@
     @Test
     public void testShouldNotHeadsUp_oldWhen() throws Exception {
         ensureStateForHeadsUpWhenAwake();
-        when(mFlags.isNoHunForOldWhenEnabled()).thenReturn(true);
         long when = makeWhenHoursAgo(25);
 
         NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/StackStateLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/StackStateLoggerTest.kt
new file mode 100644
index 0000000..47c5e5b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/StackStateLoggerTest.kt
@@ -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.systemui.statusbar.notification.logging
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogcatEchoTracker
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.statusbar.notification.stack.StackStateLogger
+import com.google.common.truth.Truth
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class StackStateLoggerTest : SysuiTestCase() {
+    private val logBufferCounter = LogBufferCounter()
+    private lateinit var logger: StackStateLogger
+
+    @Before
+    fun setup() {
+        logger = StackStateLogger(logBufferCounter.logBuffer, logBufferCounter.logBuffer)
+    }
+
+    @Test
+    fun groupChildRemovalEvent() {
+        logger.groupChildRemovalEventProcessed(KEY)
+        verifyDidLog(1)
+        logger.groupChildRemovalAnimationEnded(KEY)
+        verifyDidLog(1)
+    }
+
+    class LogBufferCounter {
+        val recentLogs = mutableListOf<Pair<String, LogLevel>>()
+        val tracker =
+            object : LogcatEchoTracker {
+                override val logInBackgroundThread: Boolean = false
+                override fun isBufferLoggable(bufferName: String, level: LogLevel): Boolean = false
+                override fun isTagLoggable(tagName: String, level: LogLevel): Boolean {
+                    recentLogs.add(tagName to level)
+                    return true
+                }
+            }
+        val logBuffer =
+            LogBuffer(name = "test", maxSize = 1, logcatEchoTracker = tracker, systrace = false)
+
+        fun verifyDidLog(times: Int) {
+            Truth.assertThat(recentLogs).hasSize(times)
+            recentLogs.clear()
+        }
+    }
+
+    private fun verifyDidLog(times: Int) {
+        logBufferCounter.verifyDidLog(times)
+    }
+
+    companion object {
+        private val KEY = "PACKAGE_NAME"
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt
index c2f1f61..2e68cec 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt
@@ -36,6 +36,7 @@
 import com.android.systemui.statusbar.notification.logging.NotificationLogger
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
 import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainer
+import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainerLogger
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer
 import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.statusbar.policy.HeadsUpManager
@@ -107,6 +108,7 @@
                 rivSubComponentFactory,
                 metricsLogger,
                 logBufferLogger,
+                mock<NotificationChildrenContainerLogger>(),
                 listContainer,
                 smartReplyConstants,
                 smartReplyController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java
index 915924f..bdc8135 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java
@@ -108,7 +108,7 @@
 
         mRow.doDragCallback(0, 0);
         verify(controller).startDragAndDrop(mRow);
-        verify(mShadeController).animateCollapsePanels(eq(0), eq(true),
+        verify(mShadeController).animateCollapseShade(eq(0), eq(true),
                 eq(false), anyFloat());
         verify(mNotificationPanelLogger, times(1)).logNotificationDrag(any());
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
index 4c83194..608778e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
@@ -733,6 +733,36 @@
         verify(lowPriVectorDrawable, times(1)).start();
     }
 
+    @Test
+    public void isExpanded_hideSensitive_sensitiveNotExpanded()
+            throws Exception {
+        // GIVEN
+        final ExpandableNotificationRow row = mNotificationTestHelper.createRow();
+        row.setUserExpanded(true);
+        row.setOnKeyguard(false);
+        row.setSensitive(/* sensitive= */true, /* hideSensitive= */false);
+        row.setHideSensitive(/* hideSensitive= */true, /* animated= */false,
+                /* delay= */0L, /* duration= */0L);
+
+        // THEN
+        assertThat(row.isExpanded()).isFalse();
+    }
+
+    @Test
+    public void isExpanded_hideSensitive_nonSensitiveExpanded()
+            throws Exception {
+        // GIVEN
+        final ExpandableNotificationRow row = mNotificationTestHelper.createRow();
+        row.setUserExpanded(true);
+        row.setOnKeyguard(false);
+        row.setSensitive(/* sensitive= */true, /* hideSensitive= */false);
+        row.setHideSensitive(/* hideSensitive= */false, /* animated= */false,
+                /* delay= */0L, /* duration= */0L);
+
+        // THEN
+        assertThat(row.isExpanded()).isTrue();
+    }
+
     private void setDrawableIconsInImageView(CachingIconView icon, Drawable iconDrawable,
             Drawable rightIconDrawable) {
         ImageView iconView = mock(ImageView.class);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactoryTest.kt
new file mode 100644
index 0000000..d5612e8
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactoryTest.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.row
+
+import android.content.Context
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import android.util.AttributeSet
+import android.view.View
+import android.widget.FrameLayout
+import android.widget.LinearLayout
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.DumpManager
+import junit.framework.Assert.assertEquals
+import junit.framework.Assert.assertNotNull
+import junit.framework.Assert.assertNull
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+/** Tests for [NotifLayoutInflaterFactory] */
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+class NotifLayoutInflaterFactoryTest : SysuiTestCase() {
+
+    @Mock private lateinit var attrs: AttributeSet
+
+    @Before
+    fun before() {
+        MockitoAnnotations.initMocks(this)
+    }
+
+    @Test
+    fun onCreateView_notMatchingViews_returnNull() {
+        // GIVEN
+        val layoutInflaterFactory =
+            createNotifLayoutInflaterFactoryImpl(
+                setOf(
+                    createReplacementViewFactory("TextView") { context, attrs ->
+                        FrameLayout(context)
+                    }
+                )
+            )
+
+        // WHEN
+        val createView = layoutInflaterFactory.onCreateView("ImageView", mContext, attrs)
+
+        // THEN
+        assertNull(createView)
+    }
+
+    @Test
+    fun onCreateView_matchingViews_returnReplacementView() {
+        // GIVEN
+        val layoutInflaterFactory =
+            createNotifLayoutInflaterFactoryImpl(
+                setOf(
+                    createReplacementViewFactory("TextView") { context, attrs ->
+                        FrameLayout(context)
+                    }
+                )
+            )
+
+        // WHEN
+        val createView = layoutInflaterFactory.onCreateView("TextView", mContext, attrs)
+
+        // THEN
+        assertNotNull(createView)
+        assertEquals(requireNotNull(createView)::class.java, FrameLayout::class.java)
+    }
+
+    @Test(expected = IllegalStateException::class)
+    fun onCreateView_multipleFactory_throwIllegalStateException() {
+        // GIVEN
+        val layoutInflaterFactory =
+            createNotifLayoutInflaterFactoryImpl(
+                setOf(
+                    createReplacementViewFactory("TextView") { context, attrs ->
+                        FrameLayout(context)
+                    },
+                    createReplacementViewFactory("TextView") { context, attrs ->
+                        LinearLayout(context)
+                    }
+                )
+            )
+
+        // WHEN
+        layoutInflaterFactory.onCreateView("TextView", mContext, attrs)
+    }
+
+    private fun createNotifLayoutInflaterFactoryImpl(
+        replacementViewFactories: Set<@JvmSuppressWildcards NotifRemoteViewsFactory>
+    ) = NotifLayoutInflaterFactory(DumpManager(), replacementViewFactories)
+
+    private fun createReplacementViewFactory(
+        replacementName: String,
+        createView: (context: Context, attrs: AttributeSet) -> View
+    ) =
+        object : NotifRemoteViewsFactory {
+            override fun instantiate(
+                parent: View?,
+                name: String,
+                context: Context,
+                attrs: AttributeSet
+            ): View? =
+                if (replacementName == name) {
+                    createView(context, attrs)
+                } else {
+                    null
+                }
+        }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
index 3face35..f55b0a8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
@@ -92,6 +92,7 @@
     @Mock private ConversationNotificationProcessor mConversationNotificationProcessor;
     @Mock private InflatedSmartReplyState mInflatedSmartReplyState;
     @Mock private InflatedSmartReplyViewHolder mInflatedSmartReplies;
+    @Mock private NotifLayoutInflaterFactory mNotifLayoutInflaterFactory;
 
     private final SmartReplyStateInflater mSmartReplyStateInflater =
             new SmartReplyStateInflater() {
@@ -130,7 +131,8 @@
                 mConversationNotificationProcessor,
                 mock(MediaFeatureFlag.class),
                 mock(Executor.class),
-                mSmartReplyStateInflater);
+                mSmartReplyStateInflater,
+                mNotifLayoutInflaterFactory);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
index 813bae8..1a644d3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
@@ -76,7 +76,7 @@
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.ExpandableNotificationRowLogger;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.OnExpandClickListener;
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
-import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm;
+import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainerLogger;
 import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.policy.InflatedSmartReplyState;
@@ -158,7 +158,8 @@
                 mock(ConversationNotificationProcessor.class),
                 mock(MediaFeatureFlag.class),
                 mock(Executor.class),
-                new MockSmartReplyInflater());
+                new MockSmartReplyInflater(),
+                mock(NotifLayoutInflaterFactory.class));
         contentBinder.setInflateSynchronously(true);
         mBindStage = new RowContentBindStage(contentBinder,
                 mock(NotifInflationErrorManager.class),
@@ -581,6 +582,7 @@
                 mock(NotificationGutsManager.class),
                 mDismissibilityProvider,
                 mock(MetricsLogger.class),
+                mock(NotificationChildrenContainerLogger.class),
                 mock(SmartReplyConstants.class),
                 mock(SmartReplyController.class),
                 mFeatureFlags,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/TextPrecomputerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/TextPrecomputerTest.kt
new file mode 100644
index 0000000..d46763d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/TextPrecomputerTest.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.row
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import android.text.PrecomputedText
+import android.text.TextPaint
+import android.widget.TextView
+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
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+class TextPrecomputerTest : SysuiTestCase() {
+
+    private lateinit var textPrecomputer: TextPrecomputer
+
+    private lateinit var textView: TextView
+
+    @Before
+    fun before() {
+        textPrecomputer = object : TextPrecomputer {}
+        textView = TextView(mContext)
+    }
+
+    @Test
+    fun precompute_returnRunnable() {
+        // WHEN
+        val precomputeResult = textPrecomputer.precompute(textView, TEXT)
+
+        // THEN
+        assertThat(precomputeResult).isInstanceOf(Runnable::class.java)
+    }
+
+    @Test
+    fun precomputeRunnable_anyText_setPrecomputedText() {
+        // WHEN
+        textPrecomputer.precompute(textView, TEXT).run()
+
+        // THEN
+        assertThat(textView.text).isInstanceOf(PrecomputedText::class.java)
+    }
+
+    @Test
+    fun precomputeRunnable_differentPrecomputedTextConfig_notSetPrecomputedText() {
+        // GIVEN
+        val precomputedTextRunnable =
+            textPrecomputer.precompute(textView, TEXT, logException = false)
+
+        // WHEN
+        textView.textMetricsParams = PrecomputedText.Params.Builder(PAINT).build()
+        precomputedTextRunnable.run()
+
+        // THEN
+        assertThat(textView.text).isInstanceOf(String::class.java)
+    }
+
+    @Test
+    fun precomputeRunnable_nullText_setNull() {
+        // GIVEN
+        textView.text = TEXT
+        val precomputedTextRunnable = textPrecomputer.precompute(textView, null)
+
+        // WHEN
+        precomputedTextRunnable.run()
+
+        // THEN
+        assertThat(textView.text).isEqualTo("")
+    }
+
+    private companion object {
+        private val PAINT = TextPaint()
+        private const val TEXT = "Example Notification Test"
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractorTest.kt
index 944eb2d..8881f42 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractorTest.kt
@@ -22,21 +22,23 @@
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.classifier.FalsingCollectorFake
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepository
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.power.data.repository.FakePowerRepository
+import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.statusbar.LockscreenShadeTransitionController
-import com.android.systemui.statusbar.phone.CentralSurfaces
-import com.android.systemui.util.mockito.any
+import com.android.systemui.statusbar.phone.ScreenOffAnimationController
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.time.FakeSystemClock
+import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.ArgumentMatchers.anyLong
 import org.mockito.Mockito.isNull
 import org.mockito.Mockito.verify
 
@@ -46,15 +48,28 @@
 
     private val keyguardRepository = FakeKeyguardRepository()
     private val deviceEntryFaceAuthRepository = FakeDeviceEntryFaceAuthRepository()
-    private val centralSurfaces: CentralSurfaces = mock()
-    private val systemClock = FakeSystemClock()
+
+    private val screenOffAnimationController =
+        mock<ScreenOffAnimationController>().also {
+            whenever(it.allowWakeUpIfDozing()).thenReturn(true)
+        }
+    private val statusBarStateController: StatusBarStateController = mock()
+    private val powerRepository = FakePowerRepository()
+    private val powerInteractor =
+        PowerInteractor(
+            powerRepository,
+            keyguardRepository,
+            FalsingCollectorFake(),
+            screenOffAnimationController,
+            statusBarStateController,
+        )
+
     private val keyguardTransitionController: LockscreenShadeTransitionController = mock()
     private val underTest =
         NotificationShelfInteractor(
             keyguardRepository,
             deviceEntryFaceAuthRepository,
-            centralSurfaces,
-            systemClock,
+            powerInteractor,
             keyguardTransitionController,
         )
 
@@ -107,10 +122,12 @@
 
     @Test
     fun goToLockedShadeFromShelf_wakesUpFromDoze() {
+        whenever(statusBarStateController.isDozing).thenReturn(true)
+
         underTest.goToLockedShadeFromShelf()
 
-        verify(centralSurfaces)
-            .wakeUpIfDozing(anyLong(), any(), eq(PowerManager.WAKE_REASON_GESTURE))
+        assertThat(powerRepository.lastWakeReason).isNotNull()
+        assertThat(powerRepository.lastWakeReason).isEqualTo(PowerManager.WAKE_REASON_GESTURE)
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt
index e9a8f3f..6221f3e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt
@@ -24,23 +24,26 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.accessibility.data.repository.FakeAccessibilityRepository
 import com.android.systemui.accessibility.domain.interactor.AccessibilityInteractor
+import com.android.systemui.classifier.FalsingCollectorFake
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepository
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.power.data.repository.FakePowerRepository
+import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.statusbar.LockscreenShadeTransitionController
 import com.android.systemui.statusbar.notification.row.ui.viewmodel.ActivatableNotificationViewModel
 import com.android.systemui.statusbar.notification.shelf.domain.interactor.NotificationShelfInteractor
-import com.android.systemui.statusbar.phone.CentralSurfaces
-import com.android.systemui.util.mockito.any
+import com.android.systemui.statusbar.phone.ScreenOffAnimationController
 import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.time.FakeSystemClock
+import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 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.ArgumentMatchers
 import org.mockito.Mock
 import org.mockito.Mockito
 import org.mockito.Mockito.verify
@@ -54,14 +57,24 @@
     @Rule @JvmField val mockitoRule: MockitoRule = MockitoJUnit.rule()
 
     // mocks
-    @Mock private lateinit var centralSurfaces: CentralSurfaces
     @Mock private lateinit var keyguardTransitionController: LockscreenShadeTransitionController
+    @Mock private lateinit var screenOffAnimationController: ScreenOffAnimationController
+    @Mock private lateinit var statusBarStateController: StatusBarStateController
 
     // fakes
     private val keyguardRepository = FakeKeyguardRepository()
     private val deviceEntryFaceAuthRepository = FakeDeviceEntryFaceAuthRepository()
-    private val systemClock = FakeSystemClock()
     private val a11yRepo = FakeAccessibilityRepository()
+    private val powerRepository = FakePowerRepository()
+    private val powerInteractor by lazy {
+        PowerInteractor(
+            powerRepository,
+            keyguardRepository,
+            FalsingCollectorFake(),
+            screenOffAnimationController,
+            statusBarStateController,
+        )
+    }
 
     // real impls
     private val a11yInteractor = AccessibilityInteractor(a11yRepo)
@@ -70,13 +83,17 @@
         NotificationShelfInteractor(
             keyguardRepository,
             deviceEntryFaceAuthRepository,
-            centralSurfaces,
-            systemClock,
+            powerInteractor,
             keyguardTransitionController,
         )
     }
     private val underTest by lazy { NotificationShelfViewModel(interactor, activatableViewModel) }
 
+    @Before
+    fun setUp() {
+        whenever(screenOffAnimationController.allowWakeUpIfDozing()).thenReturn(true)
+    }
+
     @Test
     fun canModifyColorOfNotifications_whenKeyguardNotShowing() = runTest {
         val canModifyNotifColor by collectLastValue(underTest.canModifyColorOfNotifications)
@@ -126,10 +143,12 @@
 
     @Test
     fun onClicked_goesToLockedShade() {
+        whenever(statusBarStateController.isDozing).thenReturn(true)
+
         underTest.onShelfClicked()
 
-        verify(centralSurfaces)
-            .wakeUpIfDozing(ArgumentMatchers.anyLong(), any(), eq(PowerManager.WAKE_REASON_GESTURE))
+        assertThat(powerRepository.lastWakeReason).isNotNull()
+        assertThat(powerRepository.lastWakeReason).isEqualTo(PowerManager.WAKE_REASON_GESTURE)
         verify(keyguardTransitionController).goToLockedShade(Mockito.isNull(), eq(true))
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
index 05b70eb..8d751e3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
@@ -1,20 +1,20 @@
 package com.android.systemui.statusbar.notification.stack
 
 import android.testing.AndroidTestingRunner
-import android.testing.TestableLooper
 import android.testing.TestableLooper.RunWithLooper
+import android.view.LayoutInflater
+import android.widget.FrameLayout
 import androidx.test.filters.SmallTest
 import com.android.keyguard.BouncerPanelExpansionCalculator.aboutToShowBouncerProgress
+import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.ShadeInterpolation
 import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
 import com.android.systemui.shade.transition.LargeScreenShadeInterpolator
 import com.android.systemui.statusbar.NotificationShelf
 import com.android.systemui.statusbar.StatusBarIconView
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
 import com.android.systemui.statusbar.notification.row.ExpandableView
-import com.android.systemui.statusbar.notification.row.NotificationTestHelper
 import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm.StackScrollAlgorithmState
 import com.android.systemui.util.mockito.mock
 import junit.framework.Assert.assertEquals
@@ -36,33 +36,32 @@
 @RunWithLooper
 class NotificationShelfTest : SysuiTestCase() {
 
-    @Mock private lateinit var largeScreenShadeInterpolator: LargeScreenShadeInterpolator
-    @Mock private lateinit var flags: FeatureFlags
+    @Mock
+    private lateinit var largeScreenShadeInterpolator: LargeScreenShadeInterpolator
+    @Mock
+    private lateinit var flags: FeatureFlags
+    @Mock
+    private lateinit var ambientState: AmbientState
+    @Mock
+    private lateinit var hostLayoutController: NotificationStackScrollLayoutController
 
-    private val shelf = NotificationShelf(
-            context,
-            /* attrs */ null,
-            /* showNotificationShelf */true
-    )
-    private val shelfState = shelf.viewState as NotificationShelf.ShelfState
-    private val ambientState = mock(AmbientState::class.java)
-    private val hostLayoutController: NotificationStackScrollLayoutController = mock()
-    private val notificationTestHelper by lazy {
-        allowTestableLooperAsMainThread()
-        NotificationTestHelper(
-                mContext,
-                mDependency,
-                TestableLooper.get(this))
-    }
+    private lateinit var shelf: NotificationShelf
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
+        val root = FrameLayout(context)
+        shelf = LayoutInflater.from(root.context)
+                .inflate(/* resource = */ R.layout.status_bar_notification_shelf,
+                    /* root = */root,
+                    /* attachToRoot = */false) as NotificationShelf
+
         whenever(ambientState.largeScreenShadeInterpolator).thenReturn(largeScreenShadeInterpolator)
         whenever(ambientState.featureFlags).thenReturn(flags)
+        whenever(ambientState.isSmallScreen).thenReturn(true)
+
         shelf.bind(ambientState, /* hostLayoutController */ hostLayoutController)
         shelf.layout(/* left */ 0, /* top */ 0, /* right */ 30, /* bottom */5)
-        whenever(ambientState.isSmallScreen).thenReturn(true)
     }
 
     @Test
@@ -311,9 +310,8 @@
     }
 
     @Test
-    fun updateState_flagTrue_largeScreen_expansionChanging_shelfAlphaUpdated_largeScreenValue() {
+    fun updateState_largeScreen_expansionChanging_shelfAlphaUpdated_largeScreenValue() {
         val expansionFraction = 0.6f
-        whenever(flags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION)).thenReturn(true)
         whenever(ambientState.isSmallScreen).thenReturn(false)
         whenever(largeScreenShadeInterpolator.getNotificationContentAlpha(expansionFraction))
             .thenReturn(0.123f)
@@ -325,20 +323,6 @@
     }
 
     @Test
-    fun updateState_flagFalse_largeScreen_expansionChanging_shelfAlphaUpdated_standardValue() {
-        val expansionFraction = 0.6f
-        whenever(flags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION)).thenReturn(false)
-        whenever(ambientState.isSmallScreen).thenReturn(false)
-        whenever(largeScreenShadeInterpolator.getNotificationContentAlpha(expansionFraction))
-            .thenReturn(0.123f)
-
-        updateState_expansionChanging_shelfAlphaUpdated(
-            expansionFraction = expansionFraction,
-            expectedAlpha = ShadeInterpolation.getContentAlpha(expansionFraction)
-        )
-    }
-
-    @Test
     fun updateState_expansionChangingWhileBouncerInTransit_shelfAlphaUpdated() {
         whenever(ambientState.isBouncerInTransit).thenReturn(true)
 
@@ -358,6 +342,127 @@
         )
     }
 
+    @Test
+    fun updateState_withNullLastVisibleBackgroundChild_hideShelf() {
+        // GIVEN
+        shelf.setSensitiveRevealAnimEnabled(true)
+        whenever(ambientState.stackY).thenReturn(100f)
+        whenever(ambientState.stackHeight).thenReturn(100f)
+        val paddingBetweenElements =
+            context.resources.getDimensionPixelSize(R.dimen.notification_divider_height)
+        val endOfStack = 200f + paddingBetweenElements
+        whenever(ambientState.isShadeExpanded).thenReturn(true)
+        val lastVisibleBackgroundChild = mock<ExpandableView>()
+        val expandableViewState = ExpandableViewState()
+        whenever(lastVisibleBackgroundChild.viewState).thenReturn(expandableViewState)
+        val stackScrollAlgorithmState = StackScrollAlgorithmState()
+        stackScrollAlgorithmState.firstViewInShelf = mock()
+
+        whenever(ambientState.lastVisibleBackgroundChild).thenReturn(null)
+
+        // WHEN
+        shelf.updateState(stackScrollAlgorithmState, ambientState)
+
+        // THEN
+        val shelfState = shelf.viewState as NotificationShelf.ShelfState
+        assertEquals(true, shelfState.hidden)
+        assertEquals(endOfStack, shelfState.yTranslation)
+    }
+
+    @Test
+    fun updateState_withNullFirstViewInShelf_hideShelf() {
+        // GIVEN
+        shelf.setSensitiveRevealAnimEnabled(true)
+        whenever(ambientState.stackY).thenReturn(100f)
+        whenever(ambientState.stackHeight).thenReturn(100f)
+        val paddingBetweenElements =
+            context.resources.getDimensionPixelSize(R.dimen.notification_divider_height)
+        val endOfStack = 200f + paddingBetweenElements
+        whenever(ambientState.isShadeExpanded).thenReturn(true)
+        val lastVisibleBackgroundChild = mock<ExpandableView>()
+        val expandableViewState = ExpandableViewState()
+        whenever(lastVisibleBackgroundChild.viewState).thenReturn(expandableViewState)
+        whenever(ambientState.lastVisibleBackgroundChild).thenReturn(lastVisibleBackgroundChild)
+        val stackScrollAlgorithmState = StackScrollAlgorithmState()
+
+        stackScrollAlgorithmState.firstViewInShelf = null
+
+        // WHEN
+        shelf.updateState(stackScrollAlgorithmState, ambientState)
+
+        // THEN
+        val shelfState = shelf.viewState as NotificationShelf.ShelfState
+        assertEquals(true, shelfState.hidden)
+        assertEquals(endOfStack, shelfState.yTranslation)
+    }
+
+    @Test
+    fun updateState_withCollapsedShade_hideShelf() {
+        // GIVEN
+        shelf.setSensitiveRevealAnimEnabled(true)
+        whenever(ambientState.stackY).thenReturn(100f)
+        whenever(ambientState.stackHeight).thenReturn(100f)
+        val paddingBetweenElements =
+            context.resources.getDimensionPixelSize(R.dimen.notification_divider_height)
+        val endOfStack = 200f + paddingBetweenElements
+        val lastVisibleBackgroundChild = mock<ExpandableView>()
+        val expandableViewState = ExpandableViewState()
+        whenever(lastVisibleBackgroundChild.viewState).thenReturn(expandableViewState)
+        whenever(ambientState.lastVisibleBackgroundChild).thenReturn(lastVisibleBackgroundChild)
+        val stackScrollAlgorithmState = StackScrollAlgorithmState()
+        stackScrollAlgorithmState.firstViewInShelf = mock()
+
+        whenever(ambientState.isShadeExpanded).thenReturn(false)
+
+        // WHEN
+        shelf.updateState(stackScrollAlgorithmState, ambientState)
+
+        // THEN
+        val shelfState = shelf.viewState as NotificationShelf.ShelfState
+        assertEquals(true, shelfState.hidden)
+        assertEquals(endOfStack, shelfState.yTranslation)
+    }
+
+    @Test
+    fun updateState_withHiddenSectionBeforeShelf_hideShelf() {
+        // GIVEN
+        shelf.setSensitiveRevealAnimEnabled(true)
+        whenever(ambientState.stackY).thenReturn(100f)
+        whenever(ambientState.stackHeight).thenReturn(100f)
+        val paddingBetweenElements =
+            context.resources.getDimensionPixelSize(R.dimen.notification_divider_height)
+        val endOfStack = 200f + paddingBetweenElements
+        whenever(ambientState.isShadeExpanded).thenReturn(true)
+        val lastVisibleBackgroundChild = mock<ExpandableView>()
+        val expandableViewState = ExpandableViewState()
+        whenever(lastVisibleBackgroundChild.viewState).thenReturn(expandableViewState)
+        val stackScrollAlgorithmState = StackScrollAlgorithmState()
+        whenever(ambientState.lastVisibleBackgroundChild).thenReturn(lastVisibleBackgroundChild)
+
+        val ssaVisibleChild = mock<ExpandableView>()
+        val ssaVisibleChildState = ExpandableViewState()
+        ssaVisibleChildState.hidden = true
+        whenever(ssaVisibleChild.viewState).thenReturn(ssaVisibleChildState)
+
+        val ssaVisibleChild1 = mock<ExpandableView>()
+        val ssaVisibleChildState1 = ExpandableViewState()
+        ssaVisibleChildState1.hidden = true
+        whenever(ssaVisibleChild1.viewState).thenReturn(ssaVisibleChildState1)
+
+        stackScrollAlgorithmState.visibleChildren.add(ssaVisibleChild)
+        stackScrollAlgorithmState.visibleChildren.add(ssaVisibleChild1)
+        whenever(ambientState.isExpansionChanging).thenReturn(true)
+        stackScrollAlgorithmState.firstViewInShelf = ssaVisibleChild1
+
+        // WHEN
+        shelf.updateState(stackScrollAlgorithmState, ambientState)
+
+        // THEN
+        val shelfState = shelf.viewState as NotificationShelf.ShelfState
+        assertEquals(true, shelfState.hidden)
+        assertEquals(endOfStack, shelfState.yTranslation)
+    }
+
     private fun setFractionToShade(fraction: Float) {
         whenever(ambientState.fractionToShade).thenReturn(fraction)
     }
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 cd0550e..ee8325e 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
@@ -24,6 +24,7 @@
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.mock;
@@ -32,10 +33,14 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import static kotlinx.coroutines.flow.FlowKt.emptyFlow;
+
 import android.content.res.Resources;
 import android.metrics.LogMaker;
 import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
 import android.view.View;
+import android.view.ViewTreeObserver;
 
 import androidx.test.filters.SmallTest;
 
@@ -44,10 +49,17 @@
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.logging.nano.MetricsProto;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
 import com.android.systemui.classifier.FalsingCollectorFake;
 import com.android.systemui.classifier.FalsingManagerFake;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FakeFeatureFlags;
 import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
+import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository;
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
+import com.android.systemui.keyguard.shared.model.KeyguardState;
+import com.android.systemui.keyguard.shared.model.TransitionStep;
 import com.android.systemui.media.controls.ui.KeyguardMediaController;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
@@ -71,12 +83,12 @@
 import com.android.systemui.statusbar.notification.collection.render.NotifStats;
 import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
 import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController;
+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.viewmodel.NotificationListViewModel;
-import com.android.systemui.statusbar.phone.CentralSurfaces;
 import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.NotificationIconAreaController;
@@ -103,10 +115,12 @@
  * Tests for {@link NotificationStackScrollLayoutController}.
  */
 @SmallTest
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
 @RunWith(AndroidTestingRunner.class)
 public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
 
     @Mock private NotificationGutsManager mNotificationGutsManager;
+    @Mock private NotificationsController mNotificationsController;
     @Mock private NotificationVisibilityProvider mVisibilityProvider;
     @Mock private HeadsUpManagerPhone mHeadsUpManager;
     @Mock private NotificationRoundnessManager mNotificationRoundnessManager;
@@ -119,6 +133,8 @@
     @Mock private KeyguardMediaController mKeyguardMediaController;
     @Mock private SysuiStatusBarStateController mSysuiStatusBarStateController;
     @Mock private KeyguardBypassController mKeyguardBypassController;
+    @Mock private KeyguardInteractor mKeyguardInteractor;
+    @Mock private PrimaryBouncerInteractor mPrimaryBouncerInteractor;
     @Mock private NotificationLockscreenUserManager mNotificationLockscreenUserManager;
     @Mock private MetricsLogger mMetricsLogger;
     @Mock private DumpManager mDumpManager;
@@ -126,7 +142,6 @@
     @Mock(answer = Answers.RETURNS_SELF)
     private NotificationSwipeHelper.Builder mNotificationSwipeHelperBuilder;
     @Mock private NotificationSwipeHelper mNotificationSwipeHelper;
-    @Mock private CentralSurfaces mCentralSurfaces;
     @Mock private ScrimController mScrimController;
     @Mock private GroupExpansionManager mGroupExpansionManager;
     @Mock private SectionHeaderController mSilentHeaderController;
@@ -142,11 +157,12 @@
     @Mock private StackStateLogger mStackLogger;
     @Mock private NotificationStackScrollLogger mLogger;
     @Mock private NotificationStackSizeCalculator mNotificationStackSizeCalculator;
-    @Mock private FeatureFlags mFeatureFlags;
+    private FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
     @Mock private NotificationTargetsHelper mNotificationTargetsHelper;
     @Mock private SecureSettings mSecureSettings;
     @Mock private NotificationIconAreaController mIconAreaController;
     @Mock private ActivityStarter mActivityStarter;
+    @Mock private KeyguardTransitionRepository mKeyguardTransitionRepo;
 
     @Captor
     private ArgumentCaptor<StatusBarStateController.StateListener> mStateListenerArgumentCaptor;
@@ -158,9 +174,13 @@
 
     @Before
     public void setUp() {
+        allowTestableLooperAsMainThread();
         MockitoAnnotations.initMocks(this);
 
+        mFeatureFlags.set(Flags.USE_REPOS_FOR_BOUNCER_SHOWING, false);
+
         when(mNotificationSwipeHelperBuilder.build()).thenReturn(mNotificationSwipeHelper);
+        when(mKeyguardTransitionRepo.getTransitions()).thenReturn(emptyFlow());
     }
 
     @Test
@@ -259,28 +279,84 @@
     }
 
     @Test
-    public void testUpdateEmptyShadeView_bouncerShowing_hideEmptyView() {
+    public void testUpdateEmptyShadeView_bouncerShowing_flagOff_hideEmptyView() {
         when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(false);
         initController(/* viewIsAttached= */ true);
 
-        when(mCentralSurfaces.isBouncerShowing()).thenReturn(true);
+        // WHEN the flag is off and *only* CentralSurfaces has bouncer as showing
+        mFeatureFlags.set(Flags.USE_REPOS_FOR_BOUNCER_SHOWING, false);
+        mController.setBouncerShowingFromCentralSurfaces(true);
+        when(mPrimaryBouncerInteractor.isBouncerShowing()).thenReturn(false);
+
         setupShowEmptyShadeViewState(true);
         reset(mNotificationStackScrollLayout);
         mController.updateShowEmptyShadeView();
+
+        // THEN the CentralSurfaces value is used. Since the bouncer is showing, we hide the empty
+        // view.
         verify(mNotificationStackScrollLayout).updateEmptyShadeView(
                 /* visible= */ false,
                 /* areNotificationsHiddenInShade= */ false);
     }
 
     @Test
-    public void testUpdateEmptyShadeView_bouncerNotShowing_showEmptyView() {
+    public void testUpdateEmptyShadeView_bouncerShowing_flagOn_hideEmptyView() {
         when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(false);
         initController(/* viewIsAttached= */ true);
 
-        when(mCentralSurfaces.isBouncerShowing()).thenReturn(false);
+        // WHEN the flag is on and *only* PrimaryBouncerInteractor has bouncer as showing
+        mFeatureFlags.set(Flags.USE_REPOS_FOR_BOUNCER_SHOWING, true);
+        when(mPrimaryBouncerInteractor.isBouncerShowing()).thenReturn(true);
+        mController.setBouncerShowingFromCentralSurfaces(false);
+
         setupShowEmptyShadeViewState(true);
         reset(mNotificationStackScrollLayout);
         mController.updateShowEmptyShadeView();
+
+        // THEN the PrimaryBouncerInteractor value is used. Since the bouncer is showing, we
+        // hide the empty view.
+        verify(mNotificationStackScrollLayout).updateEmptyShadeView(
+                /* visible= */ false,
+                /* areNotificationsHiddenInShade= */ false);
+    }
+
+    @Test
+    public void testUpdateEmptyShadeView_bouncerNotShowing_flagOff_showEmptyView() {
+        when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(false);
+        initController(/* viewIsAttached= */ true);
+
+        // WHEN the flag is off and *only* CentralSurfaces has bouncer as not showing
+        mFeatureFlags.set(Flags.USE_REPOS_FOR_BOUNCER_SHOWING, false);
+        mController.setBouncerShowingFromCentralSurfaces(false);
+        when(mPrimaryBouncerInteractor.isBouncerShowing()).thenReturn(true);
+
+        setupShowEmptyShadeViewState(true);
+        reset(mNotificationStackScrollLayout);
+        mController.updateShowEmptyShadeView();
+
+        // THEN the CentralSurfaces value is used. Since the bouncer isn't showing, we can show the
+        // empty view.
+        verify(mNotificationStackScrollLayout).updateEmptyShadeView(
+                /* visible= */ true,
+                /* areNotificationsHiddenInShade= */ false);
+    }
+
+    @Test
+    public void testUpdateEmptyShadeView_bouncerNotShowing_flagOn_showEmptyView() {
+        when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(false);
+        initController(/* viewIsAttached= */ true);
+
+        // WHEN the flag is on and *only* PrimaryBouncerInteractor has bouncer as not showing
+        mFeatureFlags.set(Flags.USE_REPOS_FOR_BOUNCER_SHOWING, true);
+        when(mPrimaryBouncerInteractor.isBouncerShowing()).thenReturn(false);
+        mController.setBouncerShowingFromCentralSurfaces(true);
+
+        setupShowEmptyShadeViewState(true);
+        reset(mNotificationStackScrollLayout);
+        mController.updateShowEmptyShadeView();
+
+        // THEN the PrimaryBouncerInteractor value is used. Since the bouncer isn't showing, we
+        // can show the empty view.
         verify(mNotificationStackScrollLayout).updateEmptyShadeView(
                 /* visible= */ true,
                 /* areNotificationsHiddenInShade= */ false);
@@ -540,17 +616,33 @@
                 .setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
     }
 
+    @Test
+    public void updateEmptyShadeView_onKeyguardTransitionToAod_hidesView() {
+        initController(/* viewIsAttached= */ true);
+        mController.onKeyguardTransitionChanged(
+                new TransitionStep(
+                        /* from= */ KeyguardState.GONE,
+                        /* to= */ KeyguardState.AOD));
+        verify(mNotificationStackScrollLayout).updateEmptyShadeView(eq(false), anyBoolean());
+    }
+
     private LogMaker logMatcher(int category, int type) {
         return argThat(new LogMatcher(category, type));
     }
 
     private void setupShowEmptyShadeViewState(boolean toShow) {
         if (toShow) {
-            when(mSysuiStatusBarStateController.getCurrentOrUpcomingState()).thenReturn(SHADE);
+            mController.onKeyguardTransitionChanged(
+                    new TransitionStep(
+                            /* from= */ KeyguardState.LOCKSCREEN,
+                            /* to= */ KeyguardState.GONE));
             mController.setQsFullScreen(false);
             mController.getView().removeAllViews();
         } else {
-            when(mSysuiStatusBarStateController.getCurrentOrUpcomingState()).thenReturn(KEYGUARD);
+            mController.onKeyguardTransitionChanged(
+                    new TransitionStep(
+                            /* from= */ KeyguardState.GONE,
+                            /* to= */ KeyguardState.AOD));
             mController.setQsFullScreen(true);
             mController.getView().addContainerView(mock(ExpandableNotificationRow.class));
         }
@@ -558,11 +650,14 @@
 
     private void initController(boolean viewIsAttached) {
         when(mNotificationStackScrollLayout.isAttachedToWindow()).thenReturn(viewIsAttached);
-
+        ViewTreeObserver viewTreeObserver = mock(ViewTreeObserver.class);
+        when(mNotificationStackScrollLayout.getViewTreeObserver())
+                .thenReturn(viewTreeObserver);
         mController = new NotificationStackScrollLayoutController(
                 mNotificationStackScrollLayout,
                 true,
                 mNotificationGutsManager,
+                mNotificationsController,
                 mVisibilityProvider,
                 mHeadsUpManager,
                 mNotificationRoundnessManager,
@@ -573,6 +668,9 @@
                 mSysuiStatusBarStateController,
                 mKeyguardMediaController,
                 mKeyguardBypassController,
+                mKeyguardInteractor,
+                mPrimaryBouncerInteractor,
+                mKeyguardTransitionRepo,
                 mZenModeController,
                 mNotificationLockscreenUserManager,
                 Optional.<NotificationListViewModel>empty(),
@@ -582,7 +680,6 @@
                 new FalsingManagerFake(),
                 mResources,
                 mNotificationSwipeHelperBuilder,
-                mCentralSurfaces,
                 mScrimController,
                 mGroupExpansionManager,
                 mSilentHeaderController,
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 40e7e8d..8ad271b 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
@@ -79,9 +79,9 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
 import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
+import com.android.systemui.statusbar.notification.init.NotificationsController;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.FooterView;
-import com.android.systemui.statusbar.phone.CentralSurfaces;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
@@ -111,7 +111,7 @@
     private AmbientState mAmbientState;
     private TestableResources mTestableResources;
     @Rule public MockitoRule mockito = MockitoJUnit.rule();
-    @Mock private CentralSurfaces mCentralSurfaces;
+    @Mock private NotificationsController mNotificationsController;
     @Mock private SysuiStatusBarStateController mBarState;
     @Mock private GroupMembershipManager mGroupMembershipManger;
     @Mock private GroupExpansionManager mGroupExpansionManager;
@@ -177,7 +177,7 @@
                 mNotificationStackSizeCalculator);
         mStackScroller = spy(mStackScrollerInternal);
         mStackScroller.setShelfController(notificationShelfController);
-        mStackScroller.setCentralSurfaces(mCentralSurfaces);
+        mStackScroller.setNotificationsController(mNotificationsController);
         mStackScroller.setEmptyShadeView(mEmptyShadeView);
         when(mStackScrollLayoutController.isHistoryEnabled()).thenReturn(true);
         when(mStackScrollLayoutController.getNotificationRoundnessManager())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
index 88b1eb3..4c97d20 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
@@ -9,7 +9,6 @@
 import com.android.systemui.animation.ShadeInterpolation.getContentAlpha
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
 import com.android.systemui.shade.transition.LargeScreenShadeInterpolator
 import com.android.systemui.statusbar.EmptyShadeView
 import com.android.systemui.statusbar.NotificationShelf
@@ -191,12 +190,10 @@
     }
 
     @Test
-    fun resetViewStates_flagTrue_largeScreen_expansionChanging_alphaUpdated_largeScreenValue() {
+    fun resetViewStates_largeScreen_expansionChanging_alphaUpdated_largeScreenValue() {
         val expansionFraction = 0.6f
         val surfaceAlpha = 123f
         ambientState.isSmallScreen = false
-        whenever(featureFlags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION))
-                .thenReturn(true)
         whenever(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit).thenReturn(false)
         whenever(largeScreenShadeInterpolator.getNotificationContentAlpha(expansionFraction))
             .thenReturn(surfaceAlpha)
@@ -208,23 +205,6 @@
     }
 
     @Test
-    fun resetViewStates_flagFalse_largeScreen_expansionChanging_alphaUpdated_standardValue() {
-        val expansionFraction = 0.6f
-        val surfaceAlpha = 123f
-        ambientState.isSmallScreen = false
-        whenever(featureFlags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION))
-                .thenReturn(false)
-        whenever(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit).thenReturn(false)
-        whenever(largeScreenShadeInterpolator.getNotificationContentAlpha(expansionFraction))
-            .thenReturn(surfaceAlpha)
-
-        resetViewStates_expansionChanging_notificationAlphaUpdated(
-            expansionFraction = expansionFraction,
-            expectedAlpha = getContentAlpha(expansionFraction),
-        )
-    }
-
-    @Test
     fun expansionChanging_largeScreen_bouncerInTransit_alphaUpdated_bouncerValues() {
         ambientState.isSmallScreen = false
         whenever(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit).thenReturn(true)
@@ -835,6 +815,74 @@
         )
     }
 
+    // region shouldPinHunToBottomOfExpandedQs
+    @Test
+    fun shouldHunBeVisibleWhenScrolled_mustStayOnScreenFalse_false() {
+        assertThat(stackScrollAlgorithm.shouldHunBeVisibleWhenScrolled(
+            /* mustStayOnScreen= */false,
+            /* headsUpIsVisible= */false,
+            /* showingPulsing= */false,
+            /* isOnKeyguard=*/false,
+            /*headsUpOnKeyguard=*/false
+        )).isFalse()
+    }
+
+    @Test
+    fun shouldPinHunToBottomOfExpandedQs_headsUpIsVisible_false() {
+        assertThat(stackScrollAlgorithm.shouldHunBeVisibleWhenScrolled(
+            /* mustStayOnScreen= */true,
+            /* headsUpIsVisible= */true,
+            /* showingPulsing= */false,
+            /* isOnKeyguard=*/false,
+            /*headsUpOnKeyguard=*/false
+        )).isFalse()
+    }
+
+    @Test
+    fun shouldHunBeVisibleWhenScrolled_showingPulsing_false() {
+        assertThat(stackScrollAlgorithm.shouldHunBeVisibleWhenScrolled(
+            /* mustStayOnScreen= */true,
+            /* headsUpIsVisible= */false,
+            /* showingPulsing= */true,
+            /* isOnKeyguard=*/false,
+            /* headsUpOnKeyguard= */false
+        )).isFalse()
+    }
+
+    @Test
+    fun shouldHunBeVisibleWhenScrolled_isOnKeyguard_false() {
+        assertThat(stackScrollAlgorithm.shouldHunBeVisibleWhenScrolled(
+            /* mustStayOnScreen= */true,
+            /* headsUpIsVisible= */false,
+            /* showingPulsing= */false,
+            /* isOnKeyguard=*/true,
+            /* headsUpOnKeyguard= */false
+        )).isFalse()
+    }
+
+    @Test
+    fun shouldHunBeVisibleWhenScrolled_isNotOnKeyguard_true() {
+        assertThat(stackScrollAlgorithm.shouldHunBeVisibleWhenScrolled(
+            /* mustStayOnScreen= */true,
+            /* headsUpIsVisible= */false,
+            /* showingPulsing= */false,
+            /* isOnKeyguard=*/false,
+            /* headsUpOnKeyguard= */false
+        )).isTrue()
+    }
+
+    @Test
+    fun shouldHunBeVisibleWhenScrolled_headsUpOnKeyguard_true() {
+        assertThat(stackScrollAlgorithm.shouldHunBeVisibleWhenScrolled(
+            /* mustStayOnScreen= */true,
+            /* headsUpIsVisible= */false,
+            /* showingPulsing= */false,
+            /* isOnKeyguard=*/true,
+            /* headsUpOnKeyguard= */true
+        )).isTrue()
+    }
+    // endregion
+
     private fun createHunViewMock(
             isShadeOpen: Boolean,
             fullyVisible: Boolean,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractorTest.kt
new file mode 100644
index 0000000..7bbb094
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractorTest.kt
@@ -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.systemui.statusbar.notification.stack.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class SharedNotificationContainerInteractorTest : SysuiTestCase() {
+    private lateinit var configurationRepository: FakeConfigurationRepository
+    private lateinit var underTest: SharedNotificationContainerInteractor
+
+    @Before
+    fun setUp() {
+        configurationRepository = FakeConfigurationRepository()
+        underTest =
+            SharedNotificationContainerInteractor(
+                configurationRepository,
+                mContext,
+            )
+    }
+
+    @Test
+    fun validateConfigValues() = runTest {
+        overrideResource(R.bool.config_use_split_notification_shade, true)
+        overrideResource(R.bool.config_use_large_screen_shade_header, false)
+        overrideResource(R.dimen.notification_panel_margin_horizontal, 0)
+        overrideResource(R.dimen.notification_panel_margin_bottom, 10)
+        overrideResource(R.dimen.notification_panel_margin_top, 10)
+        overrideResource(R.dimen.large_screen_shade_header_height, 0)
+
+        val dimens = collectLastValue(underTest.configurationBasedDimensions)
+
+        configurationRepository.onAnyConfigurationChange()
+        runCurrent()
+
+        val lastDimens = dimens()!!
+
+        assertThat(lastDimens.useSplitShade).isTrue()
+        assertThat(lastDimens.useLargeScreenHeader).isFalse()
+        assertThat(lastDimens.marginHorizontal).isEqualTo(0)
+        assertThat(lastDimens.marginBottom).isGreaterThan(0)
+        assertThat(lastDimens.marginTop).isGreaterThan(0)
+        assertThat(lastDimens.marginTopLargeScreen).isEqualTo(0)
+    }
+}
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
new file mode 100644
index 0000000..afd9954
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class SharedNotificationContainerViewModelTest : SysuiTestCase() {
+    private lateinit var configurationRepository: FakeConfigurationRepository
+    private lateinit var sharedNotificationContainerInteractor:
+        SharedNotificationContainerInteractor
+    private lateinit var underTest: SharedNotificationContainerViewModel
+
+    @Before
+    fun setUp() {
+        configurationRepository = FakeConfigurationRepository()
+        sharedNotificationContainerInteractor =
+            SharedNotificationContainerInteractor(
+                configurationRepository,
+                mContext,
+            )
+        underTest = SharedNotificationContainerViewModel(sharedNotificationContainerInteractor)
+    }
+
+    @Test
+    fun validateMarginStartInSplitShade() = runTest {
+        overrideResource(R.bool.config_use_split_notification_shade, true)
+        overrideResource(R.dimen.notification_panel_margin_horizontal, 20)
+
+        val dimens = collectLastValue(underTest.configurationBasedDimensions)
+
+        configurationRepository.onAnyConfigurationChange()
+        runCurrent()
+
+        val lastDimens = dimens()!!
+
+        assertThat(lastDimens.marginStart).isEqualTo(0)
+    }
+
+    @Test
+    fun validateMarginStart() = runTest {
+        overrideResource(R.bool.config_use_split_notification_shade, false)
+        overrideResource(R.dimen.notification_panel_margin_horizontal, 20)
+
+        val dimens = collectLastValue(underTest.configurationBasedDimensions)
+
+        configurationRepository.onAnyConfigurationChange()
+        runCurrent()
+
+        val lastDimens = dimens()!!
+
+        assertThat(lastDimens.marginStart).isEqualTo(20)
+    }
+
+    @Test
+    fun validateMarginEnd() = runTest {
+        overrideResource(R.dimen.notification_panel_margin_horizontal, 50)
+
+        val dimens = collectLastValue(underTest.configurationBasedDimensions)
+
+        configurationRepository.onAnyConfigurationChange()
+        runCurrent()
+
+        val lastDimens = dimens()!!
+
+        assertThat(lastDimens.marginEnd).isEqualTo(50)
+    }
+
+    @Test
+    fun validateMarginBottom() = runTest {
+        overrideResource(R.dimen.notification_panel_margin_bottom, 50)
+
+        val dimens = collectLastValue(underTest.configurationBasedDimensions)
+
+        configurationRepository.onAnyConfigurationChange()
+        runCurrent()
+
+        val lastDimens = dimens()!!
+
+        assertThat(lastDimens.marginBottom).isEqualTo(50)
+    }
+
+    @Test
+    fun validateMarginTopWithLargeScreenHeader() = 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)
+
+        val dimens = collectLastValue(underTest.configurationBasedDimensions)
+
+        configurationRepository.onAnyConfigurationChange()
+        runCurrent()
+
+        val lastDimens = dimens()!!
+
+        assertThat(lastDimens.marginTop).isEqualTo(50)
+    }
+
+    @Test
+    fun validateMarginTop() = 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)
+
+        val dimens = collectLastValue(underTest.configurationBasedDimensions)
+
+        configurationRepository.onAnyConfigurationChange()
+        runCurrent()
+
+        val lastDimens = dimens()!!
+
+        assertThat(lastDimens.marginTop).isEqualTo(0)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt
index 4a30800..5e0e140 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt
@@ -31,6 +31,7 @@
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.shade.ShadeController
 import com.android.systemui.statusbar.NotificationLockscreenUserManager
+import com.android.systemui.statusbar.NotificationShadeWindowController
 import com.android.systemui.statusbar.SysuiStatusBarStateController
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
 import com.android.systemui.statusbar.policy.DeviceProvisionedController
@@ -68,6 +69,7 @@
     @Mock private lateinit var activityLaunchAnimator: ActivityLaunchAnimator
     @Mock private lateinit var lockScreenUserManager: NotificationLockscreenUserManager
     @Mock private lateinit var statusBarWindowController: StatusBarWindowController
+    @Mock private lateinit var notifShadeWindowController: NotificationShadeWindowController
     @Mock private lateinit var wakefulnessLifecycle: WakefulnessLifecycle
     @Mock private lateinit var keyguardStateController: KeyguardStateController
     @Mock private lateinit var statusBarStateController: SysuiStatusBarStateController
@@ -90,8 +92,10 @@
                 Lazy { keyguardViewMediator },
                 Lazy { shadeController },
                 Lazy { statusBarKeyguardViewManager },
+                Lazy { notifShadeWindowController },
                 activityLaunchAnimator,
                 context,
+                DISPLAY_ID,
                 lockScreenUserManager,
                 statusBarWindowController,
                 wakefulnessLifecycle,
@@ -271,4 +275,8 @@
         mainExecutor.runAllReady()
         verify(statusBarStateController).setLeaveOpenOnKeyguardHide(true)
     }
+
+    private companion object {
+        private const val DISPLAY_ID = 0
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
index e680a4e..e4a2236 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
@@ -34,6 +34,7 @@
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
 import android.content.ComponentName;
@@ -51,6 +52,7 @@
 
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dagger.NightDisplayListenerModule;
 import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.qs.AutoAddTracker;
 import com.android.systemui.qs.QSHost;
@@ -111,6 +113,8 @@
     @Mock private DataSaverController mDataSaverController;
     @Mock private ManagedProfileController mManagedProfileController;
     @Mock private NightDisplayListener mNightDisplayListener;
+    @Mock(answer = Answers.RETURNS_SELF)
+    private NightDisplayListenerModule.Builder mNightDisplayListenerBuilder;
     @Mock private ReduceBrightColorsController mReduceBrightColorsController;
     @Mock private DeviceControlsController mDeviceControlsController;
     @Mock private WalletController mWalletController;
@@ -151,6 +155,7 @@
                 .thenReturn(TEST_CUSTOM_SAFETY_PKG);
         Context context = Mockito.spy(mContext);
         when(context.getPackageManager()).thenReturn(mPackageManager);
+        when(mNightDisplayListenerBuilder.build()).thenReturn(mNightDisplayListener);
 
         mAutoTileManager = createAutoTileManager(context);
         mAutoTileManager.init();
@@ -167,7 +172,7 @@
             HotspotController hotspotController,
             DataSaverController dataSaverController,
             ManagedProfileController managedProfileController,
-            NightDisplayListener nightDisplayListener,
+            NightDisplayListenerModule.Builder nightDisplayListenerBuilder,
             CastController castController,
             ReduceBrightColorsController reduceBrightColorsController,
             DeviceControlsController deviceControlsController,
@@ -180,7 +185,7 @@
                 hotspotController,
                 dataSaverController,
                 managedProfileController,
-                nightDisplayListener,
+                mNightDisplayListenerBuilder,
                 castController,
                 reduceBrightColorsController,
                 deviceControlsController,
@@ -191,7 +196,7 @@
 
     private AutoTileManager createAutoTileManager(Context context) {
         return createAutoTileManager(context, mAutoAddTrackerBuilder, mHotspotController,
-                mDataSaverController, mManagedProfileController, mNightDisplayListener,
+                mDataSaverController, mManagedProfileController, mNightDisplayListenerBuilder,
                 mCastController, mReduceBrightColorsController, mDeviceControlsController,
                 mWalletController, mSafetyController, mIsReduceBrightColorsAvailable);
     }
@@ -204,7 +209,7 @@
         HotspotController hC = mock(HotspotController.class);
         DataSaverController dSC = mock(DataSaverController.class);
         ManagedProfileController mPC = mock(ManagedProfileController.class);
-        NightDisplayListener nDS = mock(NightDisplayListener.class);
+        NightDisplayListenerModule.Builder nDSB = mock(NightDisplayListenerModule.Builder.class);
         CastController cC = mock(CastController.class);
         ReduceBrightColorsController rBC = mock(ReduceBrightColorsController.class);
         DeviceControlsController dCC = mock(DeviceControlsController.class);
@@ -212,14 +217,14 @@
         SafetyController sC = mock(SafetyController.class);
 
         AutoTileManager manager =
-                createAutoTileManager(mock(Context.class), builder, hC, dSC, mPC, nDS, cC, rBC,
+                createAutoTileManager(mock(Context.class), builder, hC, dSC, mPC, nDSB, cC, rBC,
                         dCC, wC, sC, true);
 
         verify(tracker, never()).initialize();
         verify(hC, never()).addCallback(any());
         verify(dSC, never()).addCallback(any());
         verify(mPC, never()).addCallback(any());
-        verify(nDS, never()).setCallback(any());
+        verifyNoMoreInteractions(nDSB);
         verify(cC, never()).addCallback(any());
         verify(rBC, never()).addCallback(any());
         verify(dCC, never()).setCallback(any());
@@ -615,6 +620,15 @@
         createAutoTileManager(mContext).destroy();
     }
 
+    @Test
+    public void testUserChange_newNightDisplayListenerCreated() {
+        UserHandle newUser = UserHandle.of(1000);
+        mAutoTileManager.changeUser(newUser);
+        InOrder inOrder = inOrder(mNightDisplayListenerBuilder);
+        inOrder.verify(mNightDisplayListenerBuilder).setUser(newUser.getIdentifier());
+        inOrder.verify(mNightDisplayListenerBuilder).build();
+    }
+
     // Will only notify if it's listening
     private void changeValue(String key, int value) {
         mSecureSettings.putIntForUser(key, value, USER);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
index 3870d99..8545b89 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
@@ -18,9 +18,9 @@
 
 import static android.view.Display.DEFAULT_DISPLAY;
 
-import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
@@ -145,38 +145,33 @@
 
     @Test
     public void testDisableNotificationShade() {
-        when(mCentralSurfaces.getDisabled1()).thenReturn(StatusBarManager.DISABLE_NONE);
-        when(mCentralSurfaces.getDisabled2()).thenReturn(StatusBarManager.DISABLE_NONE);
+        // Start with nothing disabled
+        mSbcqCallbacks.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_NONE,
+                StatusBarManager.DISABLE2_NONE, false);
+
         when(mCommandQueue.panelsEnabled()).thenReturn(false);
+        // WHEN the new disable flags have the shade disabled
         mSbcqCallbacks.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_NONE,
                 StatusBarManager.DISABLE2_NOTIFICATION_SHADE, false);
 
-        verify(mCentralSurfaces).updateQsExpansionEnabled();
+        // THEN the shade is collapsed
         verify(mShadeController).animateCollapseShade();
-
-        // Trying to open it does nothing.
-        mSbcqCallbacks.animateExpandNotificationsPanel();
-        verify(mShadeViewController, never()).expandToNotifications();
-        mSbcqCallbacks.animateExpandSettingsPanel(null);
-        verify(mShadeViewController, never()).expand(anyBoolean());
     }
 
     @Test
     public void testEnableNotificationShade() {
-        when(mCentralSurfaces.getDisabled1()).thenReturn(StatusBarManager.DISABLE_NONE);
-        when(mCentralSurfaces.getDisabled2())
-                .thenReturn(StatusBarManager.DISABLE2_NOTIFICATION_SHADE);
+        // Start with the shade disabled
+        mSbcqCallbacks.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_NONE,
+                StatusBarManager.DISABLE2_NOTIFICATION_SHADE, false);
+        reset(mShadeController);
+
         when(mCommandQueue.panelsEnabled()).thenReturn(true);
+        // WHEN the new disable flags have the shade enabled
         mSbcqCallbacks.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_NONE,
                 StatusBarManager.DISABLE2_NONE, false);
-        verify(mCentralSurfaces).updateQsExpansionEnabled();
-        verify(mShadeController, never()).animateCollapseShade();
 
-        // Can now be opened.
-        mSbcqCallbacks.animateExpandNotificationsPanel();
-        verify(mShadeViewController).expandToNotifications();
-        mSbcqCallbacks.animateExpandSettingsPanel(null);
-        verify(mShadeViewController).expandToQs();
+        // THEN the shade is not collapsed
+        verify(mShadeController, never()).animateCollapseShade();
     }
 
     @Test
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 7702630..5300851 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
@@ -29,7 +29,6 @@
 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.clearInvocations;
@@ -98,7 +97,9 @@
 import com.android.systemui.accessibility.floatingmenu.AccessibilityFloatingMenuController;
 import com.android.systemui.animation.ActivityLaunchAnimator;
 import com.android.systemui.assist.AssistManager;
+import com.android.systemui.back.domain.interactor.BackActionInteractor;
 import com.android.systemui.biometrics.AuthRippleController;
+import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.charging.WiredChargingRippleController;
 import com.android.systemui.classifier.FalsingCollectorFake;
@@ -113,7 +114,6 @@
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.keyguard.ScreenLifecycle;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
-import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor;
 import com.android.systemui.keyguard.ui.viewmodel.LightRevealScrimViewModel;
 import com.android.systemui.navigationbar.NavigationBarController;
 import com.android.systemui.notetask.NoteTaskController;
@@ -122,13 +122,13 @@
 import com.android.systemui.plugins.PluginDependencyProvider;
 import com.android.systemui.plugins.PluginManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.power.domain.interactor.PowerInteractor;
 import com.android.systemui.recents.ScreenPinningRequest;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.settings.brightness.BrightnessSliderController;
 import com.android.systemui.shade.CameraLauncher;
 import com.android.systemui.shade.NotificationPanelView;
 import com.android.systemui.shade.NotificationPanelViewController;
-import com.android.systemui.shade.NotificationShadeWindowView;
 import com.android.systemui.shade.NotificationShadeWindowViewController;
 import com.android.systemui.shade.QuickSettingsController;
 import com.android.systemui.shade.ShadeController;
@@ -145,6 +145,7 @@
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.NotificationShadeDepthController;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
+import com.android.systemui.statusbar.NotificationShelfController;
 import com.android.systemui.statusbar.OperatorNameViewController;
 import com.android.systemui.statusbar.PulseExpansionHandler;
 import com.android.systemui.statusbar.StatusBarState;
@@ -250,7 +251,6 @@
     @Mock private NotificationLogger.ExpansionStateLogger mExpansionStateLogger;
     @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     @Mock private StatusBarSignalPolicy mStatusBarSignalPolicy;
-    @Mock private NotificationShadeWindowView mNotificationShadeWindowView;
     @Mock private BroadcastDispatcher mBroadcastDispatcher;
     @Mock private AssistManager mAssistManager;
     @Mock private NotificationGutsManager mNotificationGutsManager;
@@ -259,6 +259,7 @@
     @Mock private AccessibilityFloatingMenuController mAccessibilityFloatingMenuController;
     @Mock private SysuiColorExtractor mColorExtractor;
     private WakefulnessLifecycle mWakefulnessLifecycle;
+    @Mock private PowerInteractor mPowerInteractor;
     @Mock private ColorExtractor.GradientColors mGradientColors;
     @Mock private PulseExpansionHandler mPulseExpansionHandler;
     @Mock private NotificationWakeUpCoordinator mNotificationWakeUpCoordinator;
@@ -273,10 +274,14 @@
     @Mock private NotificationShadeWindowController mNotificationShadeWindowController;
     @Mock private NotificationIconAreaController mNotificationIconAreaController;
     @Mock private NotificationShadeWindowViewController mNotificationShadeWindowViewController;
+    @Mock private Lazy<NotificationShadeWindowViewController>
+            mNotificationShadeWindowViewControllerLazy;
+    @Mock private NotificationShelfController mNotificationShelfController;
     @Mock private DozeParameters mDozeParameters;
     @Mock private Lazy<LockscreenWallpaper> mLockscreenWallpaperLazy;
     @Mock private LockscreenWallpaper mLockscreenWallpaper;
     @Mock private DozeServiceHost mDozeServiceHost;
+    @Mock private BackActionInteractor mBackActionInteractor;
     @Mock private ViewMediatorCallback mKeyguardVieMediatorCallback;
     @Mock private VolumeComponent mVolumeComponent;
     @Mock private CommandQueue mCommandQueue;
@@ -287,7 +292,6 @@
     @Mock private StatusBarTouchableRegionManager mStatusBarTouchableRegionManager;
     @Mock private ScreenPinningRequest mScreenPinningRequest;
     @Mock private PluginDependencyProvider mPluginDependencyProvider;
-    @Mock private KeyguardDismissUtil mKeyguardDismissUtil;
     @Mock private ExtensionController mExtensionController;
     @Mock private UserInfoControllerImpl mUserInfoControllerImpl;
     @Mock private PhoneStatusBarPolicy mPhoneStatusBarPolicy;
@@ -424,10 +428,10 @@
         when(mLockscreenWallpaperLazy.get()).thenReturn(mLockscreenWallpaper);
         when(mBiometricUnlockControllerLazy.get()).thenReturn(mBiometricUnlockController);
         when(mCameraLauncherLazy.get()).thenReturn(mCameraLauncher);
+        when(mNotificationShadeWindowViewControllerLazy.get())
+                .thenReturn(mNotificationShadeWindowViewController);
 
         when(mStatusBarComponentFactory.create()).thenReturn(mCentralSurfacesComponent);
-        when(mCentralSurfacesComponent.getNotificationShadeWindowViewController()).thenReturn(
-                mNotificationShadeWindowViewController);
         doAnswer(invocation -> {
             ((Runnable) invocation.getArgument(0)).run();
             return null;
@@ -435,16 +439,18 @@
 
         mShadeController = spy(new ShadeControllerImpl(
                 mCommandQueue,
+                mMainExecutor,
                 mKeyguardStateController,
                 mStatusBarStateController,
                 mStatusBarKeyguardViewManager,
                 mStatusBarWindowController,
+                mDeviceProvisionedController,
                 mNotificationShadeWindowController,
                 mContext.getSystemService(WindowManager.class),
+                () -> mNotificationPanelViewController,
                 () -> mAssistManager,
                 () -> mNotificationGutsManager
         ));
-        mShadeController.setNotificationPanelViewController(mNotificationPanelViewController);
         mShadeController.setNotificationShadeWindowViewController(
                 mNotificationShadeWindowViewController);
         mShadeController.setNotificationPresenter(mNotificationPresenter);
@@ -484,14 +490,17 @@
                 mMetricsLogger,
                 mShadeLogger,
                 mUiBgExecutor,
+                mNotificationPanelViewController,
                 mNotificationMediaManager,
                 mLockscreenUserManager,
                 mRemoteInputManager,
+                mQuickSettingsController,
                 mUserSwitcherController,
                 mBatteryController,
                 mColorExtractor,
                 mScreenLifecycle,
                 mWakefulnessLifecycle,
+                mPowerInteractor,
                 mStatusBarStateController,
                 Optional.of(mBubbles),
                 () -> mNoteTaskController,
@@ -501,13 +510,18 @@
                 () -> mAssistManager,
                 configurationController,
                 mNotificationShadeWindowController,
+                mNotificationShadeWindowViewControllerLazy,
+                mNotificationShelfController,
+                mStackScrollerController,
                 mDozeParameters,
                 mScrimController,
                 mLockscreenWallpaperLazy,
                 mBiometricUnlockControllerLazy,
                 mAuthRippleController,
                 mDozeServiceHost,
-                mPowerManager, mScreenPinningRequest,
+                mBackActionInteractor,
+                mPowerManager,
+                mScreenPinningRequest,
                 mDozeScrimController,
                 mVolumeComponent,
                 mCommandQueue,
@@ -519,7 +533,6 @@
                 mInitController,
                 new Handler(TestableLooper.get(this).getLooper()),
                 mPluginDependencyProvider,
-                mKeyguardDismissUtil,
                 mExtensionController,
                 mUserInfoControllerImpl,
                 mPhoneStatusBarPolicy,
@@ -574,16 +587,12 @@
         when(mKeyguardViewMediator.getViewMediatorCallback()).thenReturn(
                 mKeyguardVieMediatorCallback);
 
-        // TODO: we should be able to call mCentralSurfaces.start() and have all the below values
-        // initialized automatically and make NPVC private.
-        mCentralSurfaces.mNotificationShadeWindowView = mNotificationShadeWindowView;
-        mCentralSurfaces.mShadeSurface = mNotificationPanelViewController;
-        mCentralSurfaces.mQsController = mQuickSettingsController;
+        // TODO(b/277764509): we should be able to call mCentralSurfaces.start() and have all the
+        //  below values initialized automatically.
         mCentralSurfaces.mDozeScrimController = mDozeScrimController;
         mCentralSurfaces.mPresenter = mNotificationPresenter;
         mCentralSurfaces.mKeyguardIndicationController = mKeyguardIndicationController;
         mCentralSurfaces.mBarService = mBarService;
-        mCentralSurfaces.mStackScroller = mStackScroller;
         mCentralSurfaces.mGestureWakeLock = mPowerManager.newWakeLock(
                 PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "sysui:GestureWakeLock");
         mCentralSurfaces.startKeyguard();
@@ -814,16 +823,14 @@
      */
     @Test
     public void testPredictiveBackCallback_invocationCollapsesPanel() {
-        mCentralSurfaces.setNotificationShadeWindowViewController(
-                mNotificationShadeWindowViewController);
         mCentralSurfaces.handleVisibleToUserChanged(true);
         verify(mOnBackInvokedDispatcher).registerOnBackInvokedCallback(
                 eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT),
                 mOnBackInvokedCallback.capture());
 
-        when(mNotificationPanelViewController.canBeCollapsed()).thenReturn(true);
+        when(mBackActionInteractor.shouldBackBeHandled()).thenReturn(true);
         mOnBackInvokedCallback.getValue().onBackInvoked();
-        verify(mShadeController).animateCollapseShade();
+        verify(mBackActionInteractor).onBackRequested();
     }
 
     /**
@@ -832,8 +839,6 @@
      */
     @Test
     public void testPredictiveBackAnimation_progressMaxScalesPanel() {
-        mCentralSurfaces.setNotificationShadeWindowViewController(
-                mNotificationShadeWindowViewController);
         mCentralSurfaces.handleVisibleToUserChanged(true);
         verify(mOnBackInvokedDispatcher).registerOnBackInvokedCallback(
                 eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT),
@@ -841,6 +846,7 @@
 
         OnBackAnimationCallback onBackAnimationCallback =
                 (OnBackAnimationCallback) (mOnBackInvokedCallback.getValue());
+        when(mBackActionInteractor.shouldBackBeHandled()).thenReturn(true);
         when(mNotificationPanelViewController.canBeCollapsed()).thenReturn(true);
 
         BackEvent fakeSwipeInFromLeftEdge = new BackEvent(20.0f, 100.0f, 1.0f, BackEvent.EDGE_LEFT);
@@ -854,8 +860,6 @@
      */
     @Test
     public void testPredictiveBackAnimation_progressMinScalesPanel() {
-        mCentralSurfaces.setNotificationShadeWindowViewController(
-                mNotificationShadeWindowViewController);
         mCentralSurfaces.handleVisibleToUserChanged(true);
         verify(mOnBackInvokedDispatcher).registerOnBackInvokedCallback(
                 eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT),
@@ -863,6 +867,7 @@
 
         OnBackAnimationCallback onBackAnimationCallback =
                 (OnBackAnimationCallback) (mOnBackInvokedCallback.getValue());
+        when(mBackActionInteractor.shouldBackBeHandled()).thenReturn(true);
         when(mNotificationPanelViewController.canBeCollapsed()).thenReturn(true);
 
         BackEvent fakeSwipeInFromLeftEdge = new BackEvent(20.0f, 10.0f, 0.0f, BackEvent.EDGE_LEFT);
@@ -1062,7 +1067,7 @@
         // GIVEN device occluded and panel is NOT expanded
         mCentralSurfaces.setBarStateForTest(SHADE); // occluding on LS has StatusBarState = SHADE
         when(mKeyguardStateController.isOccluded()).thenReturn(true);
-        mCentralSurfaces.mPanelExpanded = false;
+        when(mNotificationPanelViewController.isPanelExpanded()).thenReturn(false);
 
         mCentralSurfaces.updateScrimController();
 
@@ -1076,7 +1081,7 @@
         // GIVEN device occluded and qs IS expanded
         mCentralSurfaces.setBarStateForTest(SHADE); // occluding on LS has StatusBarState = SHADE
         when(mKeyguardStateController.isOccluded()).thenReturn(true);
-        mCentralSurfaces.mPanelExpanded = true;
+        when(mNotificationPanelViewController.isPanelExpanded()).thenReturn(true);
 
         mCentralSurfaces.updateScrimController();
 
@@ -1145,32 +1150,6 @@
     }
 
     @Test
-    public void collapseShade_callsanimateCollapseShade_whenExpanded() {
-        // GIVEN the shade is expanded
-        mCentralSurfaces.onShadeExpansionFullyChanged(true);
-        mCentralSurfaces.setBarStateForTest(SHADE);
-
-        // WHEN collapseShade is called
-        mCentralSurfaces.collapseShade();
-
-        // VERIFY that animateCollapseShade is called
-        verify(mShadeController).animateCollapseShade();
-    }
-
-    @Test
-    public void collapseShade_doesNotCallanimateCollapseShade_whenCollapsed() {
-        // GIVEN the shade is collapsed
-        mCentralSurfaces.onShadeExpansionFullyChanged(false);
-        mCentralSurfaces.setBarStateForTest(SHADE);
-
-        // WHEN collapseShade is called
-        mCentralSurfaces.collapseShade();
-
-        // VERIFY that animateCollapseShade is NOT called
-        verify(mShadeController, never()).animateCollapseShade();
-    }
-
-    @Test
     public void deviceStateChange_unfolded_shadeOpen_setsLeaveOpenOnKeyguardHide() {
         setFoldedStates(FOLD_STATE_FOLDED);
         setGoToSleepStates(FOLD_STATE_FOLDED);
@@ -1266,34 +1245,6 @@
     }
 
     @Test
-    public void dozing_wakeUp() throws RemoteException {
-        // GIVEN can wakeup when dozing & is dozing
-        when(mScreenOffAnimationController.allowWakeUpIfDozing()).thenReturn(true);
-        setDozing(true);
-
-        // WHEN wakeup is requested
-        final int wakeReason = PowerManager.WAKE_REASON_TAP;
-        mCentralSurfaces.wakeUpIfDozing(0, "", wakeReason);
-
-        // THEN power manager receives wakeup
-        verify(mPowerManagerService).wakeUp(eq(0L), eq(wakeReason), anyString(), anyString());
-    }
-
-    @Test
-    public void notDozing_noWakeUp() throws RemoteException {
-        // GIVEN can wakeup when dozing and NOT dozing
-        when(mScreenOffAnimationController.allowWakeUpIfDozing()).thenReturn(true);
-        setDozing(false);
-
-        // WHEN wakeup is requested
-        final int wakeReason = PowerManager.WAKE_REASON_TAP;
-        mCentralSurfaces.wakeUpIfDozing(0, "", wakeReason);
-
-        // THEN power manager receives wakeup
-        verify(mPowerManagerService, never()).wakeUp(anyLong(), anyInt(), anyString(), anyString());
-    }
-
-    @Test
     public void frpLockedDevice_shadeDisabled() {
         when(mDeviceProvisionedController.isFrpActive()).thenReturn(true);
         when(mDozeServiceHost.isPulsing()).thenReturn(true);
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 3e90ed9..4aac841 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
@@ -42,6 +42,8 @@
 import org.mockito.ArgumentCaptor
 import org.mockito.Captor
 import org.mockito.Mock
+import org.mockito.Mockito.anyBoolean
+import org.mockito.Mockito.mock
 import org.mockito.Mockito.never
 import org.mockito.Mockito.reset
 import org.mockito.Mockito.verify
@@ -135,6 +137,19 @@
     }
 
     @Test
+    fun onFaceAuthEnabledChanged_notifiesBypassEnabledListeners() {
+        initKeyguardBypassController()
+        val bypassListener = mock(KeyguardBypassController.OnBypassStateChangedListener::class.java)
+        val callback = ArgumentCaptor.forClass(KeyguardStateController.Callback::class.java)
+
+        keyguardBypassController.registerOnBypassStateChangedListener(bypassListener)
+        verify(keyguardStateController).addCallback(callback.capture())
+
+        callback.value.onFaceAuthEnabledChanged()
+        verify(bypassListener).onBypassStateChanged(anyBoolean())
+    }
+
+    @Test
     fun configDevicePostureClosed_matchState_isPostureAllowedForFaceAuth_returnTrue() {
         defaultConfigPostureClosed()
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardDismissUtilTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardDismissUtilTest.java
new file mode 100644
index 0000000..b0aa2d3
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardDismissUtilTest.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
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone;
+
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidTestingRunner.class)
+@SmallTest
+public class KeyguardDismissUtilTest extends SysuiTestCase {
+    @Mock
+    private KeyguardStateController mKeyguardStateController;
+    @Mock
+    private SysuiStatusBarStateController mStatusBarStateController;
+    @Mock
+    private ActivityStarter mActivityStarter;
+    @Mock
+    private ActivityStarter.OnDismissAction mAction;
+
+    private KeyguardDismissUtil mKeyguardDismissUtil;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mKeyguardDismissUtil = new KeyguardDismissUtil(
+                mKeyguardStateController, mStatusBarStateController, mActivityStarter);
+    }
+
+    @Test
+    public void testSetLeaveOpenOnKeyguardHideWhenKeyGuardStateControllerIsShowing() {
+        doReturn(true).when(mKeyguardStateController).isShowing();
+
+        mKeyguardDismissUtil.executeWhenUnlocked(mAction, true /* requiresShadeOpen */,
+                true /* afterKeyguardGone */);
+
+        verify(mStatusBarStateController).setLeaveOpenOnKeyguardHide(true);
+
+        verify(mActivityStarter).dismissKeyguardThenExecute(mAction, null, true);
+
+    }
+
+    @Test
+    public void testSetLeaveOpenOnKeyguardHideWhenKeyGuardStateControllerIsNotShowing() {
+        doReturn(false).when(mKeyguardStateController).isShowing();
+
+        mKeyguardDismissUtil.executeWhenUnlocked(mAction, true /* requiresShadeOpen */,
+                true /* afterKeyguardGone */);
+
+        //no interaction with mStatusBarStateController
+        verify(mStatusBarStateController, times(0)).setLeaveOpenOnKeyguardHide(true);
+
+        verify(mActivityStarter).dismissKeyguardThenExecute(mAction, null, true);
+
+    }
+}
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 6a4b3c5..df3c1e5 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
@@ -22,7 +22,6 @@
 
 import static junit.framework.Assert.assertTrue;
 
-import static org.junit.Assume.assumeTrue;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
@@ -45,8 +44,6 @@
 import com.android.internal.view.AppearanceRegion;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FakeFeatureFlags;
-import com.android.systemui.flags.Flags;
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.settings.FakeDisplayTracker;
 import com.android.systemui.statusbar.policy.BatteryController;
@@ -65,20 +62,13 @@
 
     private static final GradientColors COLORS_LIGHT = makeColors(Color.WHITE);
     private static final GradientColors COLORS_DARK = makeColors(Color.BLACK);
-    private final FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
     private LightBarTransitionsController mLightBarTransitionsController;
     private LightBarTransitionsController mNavBarController;
     private SysuiDarkIconDispatcher mStatusBarIconController;
     private LightBarController mLightBarController;
 
-    /** Allow testing with NEW_LIGHT_BAR_LOGIC flag in different states */
-    protected boolean testNewLightBarLogic() {
-        return false;
-    }
-
     @Before
     public void setup() {
-        mFeatureFlags.set(Flags.NEW_LIGHT_BAR_LOGIC, testNewLightBarLogic());
         mStatusBarIconController = mock(SysuiDarkIconDispatcher.class);
         mNavBarController = mock(LightBarTransitionsController.class);
         when(mNavBarController.supportsIconTintForNavMode(anyInt())).thenReturn(true);
@@ -90,7 +80,6 @@
                 mStatusBarIconController,
                 mock(BatteryController.class),
                 mock(NavigationModeController.class),
-                mFeatureFlags,
                 mock(DumpManager.class),
                 new FakeDisplayTracker(mContext));
     }
@@ -211,8 +200,6 @@
 
     @Test
     public void validateNavBarChangesUpdateIcons() {
-        assumeTrue(testNewLightBarLogic());  // Only run in the new suite
-
         // On the launcher in dark mode buttons are light
         mLightBarController.setScrimState(ScrimState.UNLOCKED, 0f, COLORS_DARK);
         mLightBarController.onNavigationBarAppearanceChanged(
@@ -251,8 +238,6 @@
 
     @Test
     public void navBarHasDarkIconsInLockedShade_lightMode() {
-        assumeTrue(testNewLightBarLogic());  // Only run in the new suite
-
         // On the locked shade QS in light mode buttons are light
         mLightBarController.setScrimState(ScrimState.SHADE_LOCKED, 1f, COLORS_LIGHT);
         mLightBarController.onNavigationBarAppearanceChanged(
@@ -287,8 +272,6 @@
 
     @Test
     public void navBarHasLightIconsInLockedShade_darkMode() {
-        assumeTrue(testNewLightBarLogic());  // Only run in the new suite
-
         // On the locked shade QS in light mode buttons are light
         mLightBarController.setScrimState(ScrimState.SHADE_LOCKED, 1f, COLORS_DARK);
         mLightBarController.onNavigationBarAppearanceChanged(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerWithNewLogicTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerWithNewLogicTest.kt
deleted file mode 100644
index d9c2cfa..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerWithNewLogicTest.kt
+++ /dev/null
@@ -1,28 +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.statusbar.phone
-
-import androidx.test.filters.SmallTest
-import com.android.systemui.flags.Flags.NEW_LIGHT_BAR_LOGIC
-
-/**
- * This file only needs to live as long as [NEW_LIGHT_BAR_LOGIC] does. When we delete that flag, we
- * can roll this back into the old test.
- */
-@SmallTest
-class LightBarControllerWithNewLogicTest : LightBarControllerTest() {
-    override fun testNewLightBarLogic(): Boolean = true
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LockscreenWallpaperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LockscreenWallpaperTest.kt
new file mode 100644
index 0000000..47671fb
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LockscreenWallpaperTest.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.statusbar.phone
+
+import android.app.WallpaperManager
+import android.content.pm.UserInfo
+import android.os.Looper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.user.data.model.SelectionStatus
+import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.util.kotlin.JavaAdapter
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.utils.os.FakeHandler
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mockito.verify
+
+@SmallTest
+@OptIn(ExperimentalCoroutinesApi::class)
+class LockscreenWallpaperTest : SysuiTestCase() {
+
+    private lateinit var underTest: LockscreenWallpaper
+
+    private val testScope = TestScope(StandardTestDispatcher())
+    private val userRepository = FakeUserRepository()
+
+    private val wallpaperManager: WallpaperManager = mock()
+
+    @Before
+    fun setUp() {
+        whenever(wallpaperManager.isLockscreenLiveWallpaperEnabled).thenReturn(false)
+        whenever(wallpaperManager.isWallpaperSupported).thenReturn(true)
+        underTest =
+            LockscreenWallpaper(
+                /* wallpaperManager= */ wallpaperManager,
+                /* iWallpaperManager= */ mock(),
+                /* keyguardUpdateMonitor= */ mock(),
+                /* dumpManager= */ mock(),
+                /* mediaManager= */ mock(),
+                /* mainHandler= */ FakeHandler(Looper.getMainLooper()),
+                /* javaAdapter= */ JavaAdapter(testScope.backgroundScope),
+                /* userRepository= */ userRepository,
+                /* userTracker= */ mock(),
+            )
+        underTest.start()
+    }
+
+    @Test
+    fun getBitmap_matchesUserIdFromUserRepo() =
+        testScope.runTest {
+            val info = UserInfo(/* id= */ 5, /* name= */ "id5", /* flags= */ 0)
+            userRepository.setUserInfos(listOf(info))
+            userRepository.setSelectedUserInfo(info)
+
+            underTest.bitmap
+
+            verify(wallpaperManager).getWallpaperFile(any(), eq(5))
+        }
+
+    @Test
+    fun getBitmap_usesOldUserIfNewUserInProgress() =
+        testScope.runTest {
+            val info5 = UserInfo(/* id= */ 5, /* name= */ "id5", /* flags= */ 0)
+            val info6 = UserInfo(/* id= */ 6, /* name= */ "id6", /* flags= */ 0)
+            userRepository.setUserInfos(listOf(info5, info6))
+            userRepository.setSelectedUserInfo(info5)
+
+            // WHEN the selection of user 6 is only in progress
+            userRepository.setSelectedUserInfo(
+                info6,
+                selectionStatus = SelectionStatus.SELECTION_IN_PROGRESS
+            )
+
+            underTest.bitmap
+
+            // THEN we still use user 5 for wallpaper selection
+            verify(wallpaperManager).getWallpaperFile(any(), eq(5))
+        }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt
index b80b825..c282c1e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt
@@ -21,6 +21,8 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.statusbar.StatusBarIconView
+import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT
+import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
 import junit.framework.Assert.assertEquals
 import junit.framework.Assert.assertFalse
 import junit.framework.Assert.assertTrue
@@ -49,7 +51,7 @@
     fun calculateWidthFor_oneIcon_widthForOneIcon() {
         iconContainer.setActualPaddingStart(10f)
         iconContainer.setActualPaddingEnd(10f)
-        iconContainer.setIconSize(10);
+        iconContainer.setIconSize(10)
 
         assertEquals(/* expected= */ iconContainer.calculateWidthFor(/* numIcons= */ 1f),
                 /* actual= */ 30f)
@@ -59,7 +61,7 @@
     fun calculateWidthFor_fourIcons_widthForFourIcons() {
         iconContainer.setActualPaddingStart(10f)
         iconContainer.setActualPaddingEnd(10f)
-        iconContainer.setIconSize(10);
+        iconContainer.setIconSize(10)
 
         assertEquals(/* expected= */ iconContainer.calculateWidthFor(/* numIcons= */ 4f),
                 /* actual= */ 60f)
@@ -69,7 +71,7 @@
     fun calculateWidthFor_fiveIcons_widthForFourIcons() {
         iconContainer.setActualPaddingStart(10f)
         iconContainer.setActualPaddingEnd(10f)
-        iconContainer.setIconSize(10);
+        iconContainer.setIconSize(10)
         assertEquals(/* expected= */ iconContainer.calculateWidthFor(/* numIcons= */ 5f),
                 /* actual= */ 60f)
     }
@@ -78,7 +80,7 @@
     fun calculateIconXTranslations_shortShelfOneIcon_atCorrectXWithoutOverflowDot() {
         iconContainer.setActualPaddingStart(10f)
         iconContainer.setActualPaddingEnd(10f)
-        iconContainer.setIconSize(10);
+        iconContainer.setIconSize(10)
 
         val icon = mockStatusBarIcon()
         iconContainer.addView(icon)
@@ -99,7 +101,7 @@
     fun calculateIconXTranslations_shortShelfFourIcons_atCorrectXWithoutOverflowDot() {
         iconContainer.setActualPaddingStart(10f)
         iconContainer.setActualPaddingEnd(10f)
-        iconContainer.setIconSize(10);
+        iconContainer.setIconSize(10)
 
         val iconOne = mockStatusBarIcon()
         val iconTwo = mockStatusBarIcon()
@@ -128,7 +130,7 @@
     fun calculateIconXTranslations_shortShelfFiveIcons_atCorrectXWithOverflowDot() {
         iconContainer.setActualPaddingStart(10f)
         iconContainer.setActualPaddingEnd(10f)
-        iconContainer.setIconSize(10);
+        iconContainer.setIconSize(10)
 
         val iconOne = mockStatusBarIcon()
         val iconTwo = mockStatusBarIcon()
@@ -154,6 +156,55 @@
     }
 
     @Test
+    fun calculateIconXTranslations_givenWidthEnoughForThreeIcons_atCorrectXWithoutOverflowDot() {
+        iconContainer.setActualPaddingStart(0f)
+        iconContainer.setActualPaddingEnd(0f)
+        iconContainer.setActualLayoutWidth(30)
+        iconContainer.setIconSize(10)
+
+        val iconOne = mockStatusBarIcon()
+        val iconTwo = mockStatusBarIcon()
+        val iconThree = mockStatusBarIcon()
+
+        iconContainer.addView(iconOne)
+        iconContainer.addView(iconTwo)
+        iconContainer.addView(iconThree)
+        assertEquals(3, iconContainer.childCount)
+
+        iconContainer.calculateIconXTranslations()
+        assertEquals(0f, iconContainer.getIconState(iconOne).xTranslation)
+        assertEquals(10f, iconContainer.getIconState(iconTwo).xTranslation)
+        assertEquals(20f, iconContainer.getIconState(iconThree).xTranslation)
+        assertFalse(iconContainer.areIconsOverflowing())
+    }
+
+    @Test
+    fun calculateIconXTranslations_givenWidthNotEnoughForFourIcons_atCorrectXWithOverflowDot() {
+        iconContainer.setActualPaddingStart(0f)
+        iconContainer.setActualPaddingEnd(0f)
+        iconContainer.setActualLayoutWidth(35)
+        iconContainer.setIconSize(10)
+
+        val iconOne = mockStatusBarIcon()
+        val iconTwo = mockStatusBarIcon()
+        val iconThree = mockStatusBarIcon()
+        val iconFour = mockStatusBarIcon()
+
+        iconContainer.addView(iconOne)
+        iconContainer.addView(iconTwo)
+        iconContainer.addView(iconThree)
+        iconContainer.addView(iconFour)
+        assertEquals(4, iconContainer.childCount)
+
+        iconContainer.calculateIconXTranslations()
+        assertEquals(0f, iconContainer.getIconState(iconOne).xTranslation)
+        assertEquals(10f, iconContainer.getIconState(iconTwo).xTranslation)
+        assertEquals(STATE_DOT, iconContainer.getIconState(iconThree).visibleState)
+        assertEquals(STATE_HIDDEN, iconContainer.getIconState(iconFour).visibleState)
+        assertTrue(iconContainer.areIconsOverflowing())
+    }
+
+    @Test
     fun shouldForceOverflow_appearingAboveSpeedBump_true() {
         val forceOverflow = iconContainer.shouldForceOverflow(
                 /* i= */ 1,
@@ -161,7 +212,7 @@
                 /* iconAppearAmount= */ 1f,
                 /* maxVisibleIcons= */ 5
         )
-        assertTrue(forceOverflow);
+        assertTrue(forceOverflow)
     }
 
     @Test
@@ -172,7 +223,7 @@
                 /* iconAppearAmount= */ 0f,
                 /* maxVisibleIcons= */ 5
         )
-        assertTrue(forceOverflow);
+        assertTrue(forceOverflow)
     }
 
     @Test
@@ -183,7 +234,7 @@
                 /* iconAppearAmount= */ 0f,
                 /* maxVisibleIcons= */ 5
         )
-        assertFalse(forceOverflow);
+        assertFalse(forceOverflow)
     }
 
     @Test
@@ -210,6 +261,17 @@
     }
 
     @Test
+    fun isOverflowing_lastChildXGreaterThanDotX_true() {
+        val isOverflowing = iconContainer.isOverflowing(
+                /* isLastChild= */ true,
+                /* translationX= */ 9f,
+                /* layoutEnd= */ 10f,
+                /* iconSize= */ 2f,
+        )
+        assertTrue(isOverflowing)
+    }
+
+    @Test
     fun isOverflowing_lastChildXGreaterThanLayoutEnd_true() {
         val isOverflowing = iconContainer.isOverflowing(
                 /* isLastChild= */ true,
@@ -253,7 +315,7 @@
         assertTrue(isOverflowing)
     }
 
-    private fun mockStatusBarIcon() : StatusBarIconView {
+    private fun mockStatusBarIcon(): StatusBarIconView {
         val iconView = mock(StatusBarIconView::class.java)
         whenever(iconView.width).thenReturn(10)
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt
index 6b18169..85fbef0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt
@@ -27,6 +27,8 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor
+import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.State
 import com.android.systemui.privacy.PrivacyItemController
 import com.android.systemui.privacy.logging.PrivacyLogger
 import com.android.systemui.screenrecord.RecordingController
@@ -46,9 +48,17 @@
 import com.android.systemui.statusbar.policy.ZenModeController
 import com.android.systemui.util.RingerModeTracker
 import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.kotlin.JavaAdapter
 import com.android.systemui.util.mockito.capture
 import com.android.systemui.util.time.DateFormatUtil
 import com.android.systemui.util.time.FakeSystemClock
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -57,6 +67,8 @@
 import org.mockito.Captor
 import org.mockito.Mock
 import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.inOrder
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when` as whenever
@@ -64,11 +76,13 @@
 
 @RunWith(AndroidTestingRunner::class)
 @RunWithLooper
+@OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 class PhoneStatusBarPolicyTest : SysuiTestCase() {
 
     companion object {
         private const val ALARM_SLOT = "alarm"
+        private const val CONNECTED_DISPLAY_SLOT = "connected_display"
     }
 
     @Mock private lateinit var iconController: StatusBarIconController
@@ -102,6 +116,9 @@
     private lateinit var alarmCallbackCaptor:
         ArgumentCaptor<NextAlarmController.NextAlarmChangeCallback>
 
+    private val testScope = TestScope(UnconfinedTestDispatcher())
+    private val fakeConnectedDisplayStateProvider = FakeConnectedDisplayStateProvider()
+
     private lateinit var executor: FakeExecutor
     private lateinit var statusBarPolicy: PhoneStatusBarPolicy
     private lateinit var testableLooper: TestableLooper
@@ -164,6 +181,57 @@
         verify(iconController).setIconVisibility(ALARM_SLOT, true)
     }
 
+    @Test
+    fun connectedDisplay_connected_iconShown() =
+        testScope.runTest {
+            statusBarPolicy.init()
+            clearInvocations(iconController)
+
+            fakeConnectedDisplayStateProvider.emit(State.CONNECTED)
+            runCurrent()
+
+            verify(iconController).setIconVisibility(CONNECTED_DISPLAY_SLOT, true)
+        }
+
+    @Test
+    fun connectedDisplay_disconnected_iconHidden() =
+        testScope.runTest {
+            statusBarPolicy.init()
+            clearInvocations(iconController)
+
+            fakeConnectedDisplayStateProvider.emit(State.DISCONNECTED)
+
+            verify(iconController).setIconVisibility(CONNECTED_DISPLAY_SLOT, false)
+        }
+
+    @Test
+    fun connectedDisplay_disconnectedThenConnected_iconShown() =
+        testScope.runTest {
+            statusBarPolicy.init()
+            clearInvocations(iconController)
+
+            fakeConnectedDisplayStateProvider.emit(State.CONNECTED)
+            fakeConnectedDisplayStateProvider.emit(State.DISCONNECTED)
+            fakeConnectedDisplayStateProvider.emit(State.CONNECTED)
+
+            inOrder(iconController).apply {
+                verify(iconController).setIconVisibility(CONNECTED_DISPLAY_SLOT, true)
+                verify(iconController).setIconVisibility(CONNECTED_DISPLAY_SLOT, false)
+                verify(iconController).setIconVisibility(CONNECTED_DISPLAY_SLOT, true)
+            }
+        }
+
+    @Test
+    fun connectedDisplay_connectSecureDisplay_iconShown() =
+        testScope.runTest {
+            statusBarPolicy.init()
+            clearInvocations(iconController)
+
+            fakeConnectedDisplayStateProvider.emit(State.CONNECTED_SECURE)
+
+            verify(iconController).setIconVisibility(CONNECTED_DISPLAY_SLOT, true)
+        }
+
     private fun createAlarmInfo(): AlarmManager.AlarmClockInfo {
         return AlarmManager.AlarmClockInfo(10L, null)
     }
@@ -200,7 +268,16 @@
             dateFormatUtil,
             ringerModeTracker,
             privacyItemController,
-            privacyLogger
+            privacyLogger,
+            fakeConnectedDisplayStateProvider,
+            JavaAdapter(testScope.backgroundScope)
         )
     }
+
+    private class FakeConnectedDisplayStateProvider : ConnectedDisplayInteractor {
+        private val flow = MutableSharedFlow<State>()
+        suspend fun emit(value: State) = flow.emit(value)
+        override val connectedDisplayState: Flow<State>
+            get() = flow
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
index 2d96e59..c8ec1bf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
@@ -204,6 +204,7 @@
             userChipViewModel,
             centralSurfacesImpl,
             shadeControllerImpl,
+            shadeViewController,
             shadeLogger,
             viewUtil,
             configurationController
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 a9ed175..0dc1d9a 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
@@ -44,6 +44,9 @@
 
 import android.animation.Animator;
 import android.app.AlarmManager;
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.content.res.TypedArray;
 import android.graphics.Color;
 import android.os.Handler;
 import android.testing.AndroidTestingRunner;
@@ -56,15 +59,14 @@
 import com.android.internal.colorextraction.ColorExtractor.GradientColors;
 import com.android.keyguard.BouncerPanelExpansionCalculator;
 import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.TestScopeProvider;
 import com.android.systemui.DejankUtils;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.animation.ShadeInterpolation;
+import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants;
 import com.android.systemui.dock.DockManager;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
-import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants;
 import com.android.systemui.keyguard.shared.model.KeyguardState;
 import com.android.systemui.keyguard.shared.model.TransitionState;
 import com.android.systemui.keyguard.shared.model.TransitionStep;
@@ -75,9 +77,11 @@
 import com.android.systemui.statusbar.policy.FakeConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.kotlin.JavaAdapter;
 import com.android.systemui.util.time.FakeSystemClock;
 import com.android.systemui.util.wakelock.DelayedWakeLock;
 import com.android.systemui.utils.os.FakeHandler;
+import com.android.systemui.wallpapers.data.repository.FakeWallpaperRepository;
 
 import com.google.common.truth.Expect;
 
@@ -98,6 +102,7 @@
 import java.util.Map;
 
 import kotlinx.coroutines.CoroutineDispatcher;
+import kotlinx.coroutines.test.TestScope;
 
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
@@ -111,6 +116,9 @@
     private final LargeScreenShadeInterpolator
             mLinearLargeScreenShadeInterpolator = new LinearLargeScreenShadeInterpolator();
 
+    private final TestScope mTestScope = TestScopeProvider.getTestScope();
+    private final JavaAdapter mJavaAdapter = new JavaAdapter(mTestScope.getBackgroundScope());
+
     private ScrimController mScrimController;
     private ScrimView mScrimBehind;
     private ScrimView mNotificationsScrim;
@@ -121,6 +129,7 @@
     private int mScrimVisibility;
     private boolean mAlwaysOnEnabled;
     private TestableLooper mLooper;
+    private Context mContext;
     @Mock private AlarmManager mAlarmManager;
     @Mock private DozeParameters mDozeParameters;
     @Mock private LightBarController mLightBarController;
@@ -133,12 +142,13 @@
     @Mock private KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
     @Mock private PrimaryBouncerToGoneTransitionViewModel mPrimaryBouncerToGoneTransitionViewModel;
     @Mock private KeyguardTransitionInteractor mKeyguardTransitionInteractor;
+    private final FakeWallpaperRepository mWallpaperRepository = new FakeWallpaperRepository();
     @Mock private CoroutineDispatcher mMainDispatcher;
+    @Mock private TypedArray mMockTypedArray;
 
     // TODO(b/204991468): Use a real PanelExpansionStateManager object once this bug is fixed. (The
     //   event-dispatch-on-registration pattern caused some of these unit tests to fail.)
     @Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
-    @Mock private FeatureFlags mFeatureFlags;
 
     private static class AnimatorListener implements Animator.AnimatorListener {
         private int mNumStarts;
@@ -182,10 +192,11 @@
             mNumEnds = 0;
             mNumCancels = 0;
         }
-    };
+    }
 
     private AnimatorListener mAnimatorListener = new AnimatorListener();
 
+    private int mSurfaceColor = 0x112233;
 
     private void finishAnimationsImmediately() {
         // Execute code that will trigger animations.
@@ -214,10 +225,17 @@
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
+        mContext = spy(getContext());
+        when(mContext.obtainStyledAttributes(
+                new int[]{com.android.internal.R.attr.materialColorSurface}))
+                .thenReturn(mMockTypedArray);
 
-        mScrimBehind = spy(new ScrimView(getContext()));
-        mScrimInFront = new ScrimView(getContext());
-        mNotificationsScrim = new ScrimView(getContext());
+        when(mMockTypedArray.getColorStateList(anyInt()))
+                .thenAnswer((invocation) -> ColorStateList.valueOf(mSurfaceColor));
+
+        mScrimBehind = spy(new ScrimView(mContext));
+        mScrimInFront = new ScrimView(mContext);
+        mNotificationsScrim = new ScrimView(mContext);
         mAlwaysOnEnabled = true;
         mLooper = TestableLooper.get(this);
         DejankUtils.setImmediate(true);
@@ -262,20 +280,25 @@
                 mDockManager,
                 mConfigurationController,
                 new FakeExecutor(new FakeSystemClock()),
+                mJavaAdapter,
                 mScreenOffAnimationController,
                 mKeyguardUnlockAnimationController,
                 mStatusBarKeyguardViewManager,
                 mPrimaryBouncerToGoneTransitionViewModel,
                 mKeyguardTransitionInteractor,
+                mWallpaperRepository,
                 mMainDispatcher,
-                mLinearLargeScreenShadeInterpolator,
-                mFeatureFlags);
+                mLinearLargeScreenShadeInterpolator);
+        mScrimController.start();
         mScrimController.setScrimVisibleListener(visible -> mScrimVisibility = visible);
         mScrimController.attachViews(mScrimBehind, mNotificationsScrim, mScrimInFront);
         mScrimController.setAnimatorListener(mAnimatorListener);
 
         mScrimController.setHasBackdrop(false);
-        mScrimController.setWallpaperSupportsAmbientMode(false);
+
+        mWallpaperRepository.getWallpaperSupportsAmbientMode().setValue(false);
+        mTestScope.getTestScheduler().runCurrent();
+
         mScrimController.transitionTo(ScrimState.KEYGUARD);
         finishAnimationsImmediately();
     }
@@ -376,7 +399,9 @@
 
     @Test
     public void transitionToAod_withAodWallpaper() {
-        mScrimController.setWallpaperSupportsAmbientMode(true);
+        mWallpaperRepository.getWallpaperSupportsAmbientMode().setValue(true);
+        mTestScope.getTestScheduler().runCurrent();
+
         mScrimController.transitionTo(ScrimState.AOD);
         finishAnimationsImmediately();
 
@@ -398,7 +423,9 @@
     @Test
     public void transitionToAod_withAodWallpaperAndLockScreenWallpaper() {
         mScrimController.setHasBackdrop(true);
-        mScrimController.setWallpaperSupportsAmbientMode(true);
+        mWallpaperRepository.getWallpaperSupportsAmbientMode().setValue(true);
+        mTestScope.getTestScheduler().runCurrent();
+
         mScrimController.transitionTo(ScrimState.AOD);
         finishAnimationsImmediately();
 
@@ -415,7 +442,9 @@
 
     @Test
     public void setHasBackdrop_withAodWallpaperAndAlbumArt() {
-        mScrimController.setWallpaperSupportsAmbientMode(true);
+        mWallpaperRepository.getWallpaperSupportsAmbientMode().setValue(true);
+        mTestScope.getTestScheduler().runCurrent();
+
         mScrimController.transitionTo(ScrimState.AOD);
         finishAnimationsImmediately();
         mScrimController.setHasBackdrop(true);
@@ -528,7 +557,9 @@
         // Pre-condition
         // Need to go to AoD first because PULSING doesn't change
         // the back scrim opacity - otherwise it would hide AoD wallpapers.
-        mScrimController.setWallpaperSupportsAmbientMode(false);
+        mWallpaperRepository.getWallpaperSupportsAmbientMode().setValue(false);
+        mTestScope.getTestScheduler().runCurrent();
+
         mScrimController.transitionTo(ScrimState.AOD);
         finishAnimationsImmediately();
         assertScrimAlpha(Map.of(
@@ -577,7 +608,7 @@
         mScrimController.transitionTo(BOUNCER);
         finishAnimationsImmediately();
         // Front scrim should be transparent
-        // Back scrim should be visible without tint
+        // Back scrim should be visible and tinted to the surface color
         assertScrimAlpha(Map.of(
                 mScrimInFront, TRANSPARENT,
                 mNotificationsScrim, TRANSPARENT,
@@ -585,9 +616,31 @@
 
         assertScrimTinted(Map.of(
                 mScrimInFront, false,
-                mScrimBehind, false,
+                mScrimBehind, true,
                 mNotificationsScrim, false
         ));
+
+        assertScrimTint(mScrimBehind, mSurfaceColor);
+    }
+
+    @Test
+    public void onThemeChange_bouncerBehindTint_isUpdatedToSurfaceColor() {
+        assertEquals(BOUNCER.getBehindTint(), 0x112233);
+        mSurfaceColor = 0x223344;
+        mConfigurationController.notifyThemeChanged();
+        assertEquals(BOUNCER.getBehindTint(), 0x223344);
+    }
+
+    @Test
+    public void onThemeChangeWhileClipQsScrim_bouncerBehindTint_remainsBlack() {
+        mScrimController.setClipsQsScrim(true);
+        mScrimController.transitionTo(BOUNCER);
+        finishAnimationsImmediately();
+
+        assertEquals(BOUNCER.getBehindTint(), Color.BLACK);
+        mSurfaceColor = 0x223344;
+        mConfigurationController.notifyThemeChanged();
+        assertEquals(BOUNCER.getBehindTint(), Color.BLACK);
     }
 
     @Test
@@ -619,16 +672,17 @@
 
         finishAnimationsImmediately();
         // Front scrim should be transparent
-        // Back scrim should be visible without tint
+        // Back scrim should be visible and has a tint of surfaceColor
         assertScrimAlpha(Map.of(
                 mScrimInFront, TRANSPARENT,
                 mNotificationsScrim, TRANSPARENT,
                 mScrimBehind, OPAQUE));
         assertScrimTinted(Map.of(
                 mScrimInFront, false,
-                mScrimBehind, false,
+                mScrimBehind, true,
                 mNotificationsScrim, false
         ));
+        assertScrimTint(mScrimBehind, mSurfaceColor);
     }
 
     @Test
@@ -697,9 +751,7 @@
     }
 
     @Test
-    public void transitionToUnlocked_nonClippedQs_flagTrue_followsLargeScreensInterpolator() {
-        when(mFeatureFlags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION))
-                .thenReturn(true);
+    public void transitionToUnlocked_nonClippedQs_followsLargeScreensInterpolator() {
         mScrimController.setClipsQsScrim(false);
         mScrimController.setRawPanelExpansionFraction(0f);
         mScrimController.transitionTo(ScrimState.UNLOCKED);
@@ -737,48 +789,6 @@
                 mScrimBehind, OPAQUE));
     }
 
-
-    @Test
-    public void transitionToUnlocked_nonClippedQs_flagFalse() {
-        when(mFeatureFlags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION))
-                .thenReturn(false);
-        mScrimController.setClipsQsScrim(false);
-        mScrimController.setRawPanelExpansionFraction(0f);
-        mScrimController.transitionTo(ScrimState.UNLOCKED);
-        finishAnimationsImmediately();
-        assertScrimAlpha(Map.of(
-                mScrimInFront, TRANSPARENT,
-                mNotificationsScrim, TRANSPARENT,
-                mScrimBehind, TRANSPARENT));
-
-        assertScrimTinted(Map.of(
-                mNotificationsScrim, false,
-                mScrimInFront, false,
-                mScrimBehind, true
-        ));
-
-        // Back scrim should be visible after start dragging
-        mScrimController.setRawPanelExpansionFraction(0.29f);
-        assertScrimAlpha(Map.of(
-                mScrimInFront, TRANSPARENT,
-                mNotificationsScrim, TRANSPARENT,
-                mScrimBehind, SEMI_TRANSPARENT));
-
-        // Back scrim should be opaque at 30%
-        mScrimController.setRawPanelExpansionFraction(0.3f);
-        assertScrimAlpha(Map.of(
-                mScrimInFront, TRANSPARENT,
-                mNotificationsScrim, TRANSPARENT,
-                mScrimBehind, OPAQUE));
-
-        // Then, notification scrim should fade in
-        mScrimController.setRawPanelExpansionFraction(0.31f);
-        assertScrimAlpha(Map.of(
-                mScrimInFront, TRANSPARENT,
-                mNotificationsScrim, SEMI_TRANSPARENT,
-                mScrimBehind, OPAQUE));
-    }
-
     @Test
     public void scrimStateCallback() {
         mScrimController.transitionTo(ScrimState.UNLOCKED);
@@ -977,19 +987,22 @@
                 mDockManager,
                 mConfigurationController,
                 new FakeExecutor(new FakeSystemClock()),
+                mJavaAdapter,
                 mScreenOffAnimationController,
                 mKeyguardUnlockAnimationController,
                 mStatusBarKeyguardViewManager,
                 mPrimaryBouncerToGoneTransitionViewModel,
                 mKeyguardTransitionInteractor,
+                mWallpaperRepository,
                 mMainDispatcher,
-                mLinearLargeScreenShadeInterpolator,
-                mFeatureFlags);
+                mLinearLargeScreenShadeInterpolator);
+        mScrimController.start();
         mScrimController.setScrimVisibleListener(visible -> mScrimVisibility = visible);
         mScrimController.attachViews(mScrimBehind, mNotificationsScrim, mScrimInFront);
         mScrimController.setAnimatorListener(mAnimatorListener);
         mScrimController.setHasBackdrop(false);
-        mScrimController.setWallpaperSupportsAmbientMode(false);
+        mWallpaperRepository.getWallpaperSupportsAmbientMode().setValue(false);
+        mTestScope.getTestScheduler().runCurrent();
         mScrimController.transitionTo(ScrimState.KEYGUARD);
         finishAnimationsImmediately();
 
@@ -1114,7 +1127,9 @@
 
     @Test
     public void testWillHideAodWallpaper() {
-        mScrimController.setWallpaperSupportsAmbientMode(true);
+        mWallpaperRepository.getWallpaperSupportsAmbientMode().setValue(true);
+        mTestScope.getTestScheduler().runCurrent();
+
         mScrimController.transitionTo(ScrimState.AOD);
         verify(mAlarmManager).setExact(anyInt(), anyLong(), any(), any(), any());
         mScrimController.transitionTo(ScrimState.KEYGUARD);
@@ -1125,7 +1140,8 @@
     public void testWillHideDockedWallpaper() {
         mAlwaysOnEnabled = false;
         when(mDockManager.isDocked()).thenReturn(true);
-        mScrimController.setWallpaperSupportsAmbientMode(true);
+        mWallpaperRepository.getWallpaperSupportsAmbientMode().setValue(true);
+        mTestScope.getTestScheduler().runCurrent();
 
         mScrimController.transitionTo(ScrimState.AOD);
 
@@ -1174,7 +1190,9 @@
 
     @Test
     public void testHidesShowWhenLockedActivity() {
-        mScrimController.setWallpaperSupportsAmbientMode(true);
+        mWallpaperRepository.getWallpaperSupportsAmbientMode().setValue(true);
+        mTestScope.getTestScheduler().runCurrent();
+
         mScrimController.setKeyguardOccluded(true);
         mScrimController.transitionTo(ScrimState.AOD);
         finishAnimationsImmediately();
@@ -1191,7 +1209,9 @@
 
     @Test
     public void testHidesShowWhenLockedActivity_whenAlreadyInAod() {
-        mScrimController.setWallpaperSupportsAmbientMode(true);
+        mWallpaperRepository.getWallpaperSupportsAmbientMode().setValue(true);
+        mTestScope.getTestScheduler().runCurrent();
+
         mScrimController.transitionTo(ScrimState.AOD);
         finishAnimationsImmediately();
         assertScrimAlpha(Map.of(
@@ -1809,6 +1829,13 @@
         assertEquals(message, hasTint, scrim.getTint() != Color.TRANSPARENT);
     }
 
+    private void assertScrimTint(ScrimView scrim, int expectedTint) {
+        String message = "Tint test failed with expected scrim tint: "
+                + Integer.toHexString(expectedTint) + " and actual tint: "
+                + Integer.toHexString(scrim.getTint()) + " for scrim: " + getScrimName(scrim);
+        assertEquals(message, expectedTint, scrim.getTint(), 0.1);
+    }
+
     private String getScrimName(ScrimView scrim) {
         if (scrim == mScrimInFront) {
             return "front";
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
index 8aaa57f..9157cd9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
@@ -16,7 +16,6 @@
 
 import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_ICON;
 import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_MOBILE;
-import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_WIFI;
 
 import static junit.framework.Assert.assertTrue;
 
@@ -41,13 +40,11 @@
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.StatusBarIconView;
 import com.android.systemui.statusbar.StatusBarMobileView;
-import com.android.systemui.statusbar.StatusBarWifiView;
 import com.android.systemui.statusbar.StatusIconDisplayable;
 import com.android.systemui.statusbar.connectivity.ui.MobileContextProvider;
 import com.android.systemui.statusbar.phone.StatusBarIconController.DarkIconManager;
 import com.android.systemui.statusbar.phone.StatusBarIconController.IconManager;
 import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
-import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
 import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags;
 import com.android.systemui.statusbar.pipeline.mobile.ui.MobileUiAdapter;
 import com.android.systemui.statusbar.pipeline.wifi.ui.WifiUiAdapter;
@@ -156,13 +153,9 @@
         assertTrue("Expected StatusBarIconView",
                 (manager.getViewAt(0) instanceof StatusBarIconView));
 
-        holder = holderForType(TYPE_WIFI);
-        manager.onIconAdded(1, "test_wifi", false, holder);
-        assertTrue(manager.getViewAt(1) instanceof StatusBarWifiView);
-
         holder = holderForType(TYPE_MOBILE);
-        manager.onIconAdded(2, "test_mobile", false, holder);
-        assertTrue(manager.getViewAt(2) instanceof StatusBarMobileView);
+        manager.onIconAdded(1, "test_mobile", false, holder);
+        assertTrue(manager.getViewAt(1) instanceof StatusBarMobileView);
     }
 
     private StatusBarIconHolder holderForType(int type) {
@@ -170,9 +163,6 @@
             case TYPE_MOBILE:
                 return StatusBarIconHolder.fromMobileIconState(mock(MobileIconState.class));
 
-            case TYPE_WIFI:
-                return StatusBarIconHolder.fromWifiIconState(mock(WifiIconState.class));
-
             case TYPE_ICON:
             default:
                 return StatusBarIconHolder.fromIcon(mock(StatusBarIcon.class));
@@ -214,13 +204,6 @@
         }
 
         @Override
-        protected StatusBarWifiView addWifiIcon(int index, String slot, WifiIconState state) {
-            StatusBarWifiView mock = mock(StatusBarWifiView.class);
-            mGroup.addView(mock, index);
-            return mock;
-        }
-
-        @Override
         protected StatusBarMobileView addMobileIcon(int index, String slot, MobileIconState state) {
             StatusBarMobileView mock = mock(StatusBarMobileView.class);
             mGroup.addView(mock, index);
@@ -254,13 +237,6 @@
         }
 
         @Override
-        protected StatusBarWifiView addWifiIcon(int index, String slot, WifiIconState state) {
-            StatusBarWifiView mock = mock(StatusBarWifiView.class);
-            mGroup.addView(mock, index);
-            return mock;
-        }
-
-        @Override
         protected StatusBarMobileView addMobileIcon(int index, String slot, MobileIconState state) {
             StatusBarMobileView mock = mock(StatusBarMobileView.class);
             mGroup.addView(mock, index);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index 131cc07..ed9cf3f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -16,8 +16,8 @@
 
 package com.android.systemui.statusbar.phone;
 
-import static com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN;
-import static com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_VISIBLE;
+import static com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN;
+import static com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.EXPANSION_VISIBLE;
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
@@ -63,16 +63,16 @@
 import com.android.keyguard.ViewMediatorCallback;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor;
+import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor;
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor.PrimaryBouncerExpansionCallback;
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
+import com.android.systemui.bouncer.ui.BouncerView;
+import com.android.systemui.bouncer.ui.BouncerViewDelegate;
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.dreams.DreamOverlayStateController;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
-import com.android.systemui.keyguard.data.BouncerView;
-import com.android.systemui.keyguard.data.BouncerViewDelegate;
-import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor;
-import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor;
-import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor.PrimaryBouncerExpansionCallback;
-import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor;
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.navigationbar.TaskbarDelegate;
 import com.android.systemui.plugins.ActivityStarter;
@@ -137,6 +137,7 @@
     @Mock private BouncerView mBouncerView;
     @Mock private BouncerViewDelegate mBouncerViewDelegate;
     @Mock private OnBackAnimationCallback mBouncerViewDelegateBackCallback;
+    @Mock private NotificationShadeWindowController mNotificationShadeWindowController;
     @Mock private NotificationShadeWindowView mNotificationShadeWindowView;
     @Mock private WindowInsetsController mWindowInsetsController;
     @Mock private TaskbarDelegate mTaskbarDelegate;
@@ -170,7 +171,7 @@
                 .isEnabled(Flags.WM_ENABLE_PREDICTIVE_BACK_BOUNCER_ANIM))
                 .thenReturn(true);
 
-        when(mCentralSurfaces.getNotificationShadeWindowView())
+        when(mNotificationShadeWindowController.getWindowRootView())
                 .thenReturn(mNotificationShadeWindowView);
         when(mNotificationShadeWindowView.getWindowInsetsController())
                 .thenReturn(mWindowInsetsController);
@@ -186,7 +187,7 @@
                         mDreamOverlayStateController,
                         mock(NavigationModeController.class),
                         mock(DockManager.class),
-                        mock(NotificationShadeWindowController.class),
+                        mNotificationShadeWindowController,
                         mKeyguardStateController,
                         mock(NotificationMediaManager.class),
                         mKeyguardMessageAreaFactory,
@@ -648,21 +649,21 @@
     @Test
     public void testReportBouncerOnDreamWhenVisible() {
         mBouncerExpansionCallback.onVisibilityChanged(true);
-        verify(mCentralSurfaces).setBouncerShowingOverDream(false);
+        assertFalse(mStatusBarKeyguardViewManager.isBouncerShowingOverDream());
         Mockito.clearInvocations(mCentralSurfaces);
         when(mDreamOverlayStateController.isOverlayActive()).thenReturn(true);
         mBouncerExpansionCallback.onVisibilityChanged(true);
-        verify(mCentralSurfaces).setBouncerShowingOverDream(true);
+        assertTrue(mStatusBarKeyguardViewManager.isBouncerShowingOverDream());
     }
 
     @Test
     public void testReportBouncerOnDreamWhenNotVisible() {
         mBouncerExpansionCallback.onVisibilityChanged(false);
-        verify(mCentralSurfaces).setBouncerShowingOverDream(false);
+        assertFalse(mStatusBarKeyguardViewManager.isBouncerShowingOverDream());
         Mockito.clearInvocations(mCentralSurfaces);
         when(mDreamOverlayStateController.isOverlayActive()).thenReturn(true);
         mBouncerExpansionCallback.onVisibilityChanged(false);
-        verify(mCentralSurfaces).setBouncerShowingOverDream(false);
+        assertFalse(mStatusBarKeyguardViewManager.isBouncerShowingOverDream());
     }
 
     @Test
@@ -738,6 +739,16 @@
     }
 
     @Test
+    public void testResetBouncerAnimatingAway() {
+        reset(mPrimaryBouncerInteractor);
+        when(mPrimaryBouncerInteractor.isAnimatingAway()).thenReturn(true);
+
+        mStatusBarKeyguardViewManager.reset(true);
+
+        verify(mPrimaryBouncerInteractor, never()).hide();
+    }
+
+    @Test
     public void handleDispatchTouchEvent_alternateBouncerNotVisible() {
         mStatusBarKeyguardViewManager.addCallback(mCallback);
 
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 d6ae2b7..9c7f619 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
@@ -21,6 +21,8 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.mockito.AdditionalAnswers.answerVoid;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -61,10 +63,14 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.animation.ActivityLaunchAnimator;
 import com.android.systemui.assist.AssistManager;
+import com.android.systemui.classifier.FalsingCollectorFake;
 import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.power.data.repository.FakePowerRepository;
+import com.android.systemui.power.domain.interactor.PowerInteractor;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shade.NotificationShadeWindowViewController;
 import com.android.systemui.shade.ShadeControllerImpl;
@@ -73,6 +79,7 @@
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationPresenter;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorControllerProvider;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -109,6 +116,8 @@
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
 public class StatusBarNotificationActivityStarterTest extends SysuiTestCase {
 
+    private static final int DISPLAY_ID = 0;
+
     @Mock
     private AssistManager mAssistManager;
     @Mock
@@ -117,13 +126,12 @@
     private NotificationClickNotifier mClickNotifier;
     @Mock
     private StatusBarStateController mStatusBarStateController;
+    @Mock private ScreenOffAnimationController mScreenOffAnimationController;
     @Mock
     private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
     @Mock
     private NotificationRemoteInputManager mRemoteInputManager;
     @Mock
-    private CentralSurfaces mCentralSurfaces;
-    @Mock
     private KeyguardStateController mKeyguardStateController;
     @Mock
     private NotificationInterruptStateProvider mNotificationInterruptStateProvider;
@@ -149,6 +157,8 @@
     private ActivityLaunchAnimator mActivityLaunchAnimator;
     @Mock
     private InteractionJankMonitor mJankMonitor;
+    private FakePowerRepository mPowerRepository;
+    private PowerInteractor mPowerInteractor;
     @Mock
     private UserTracker mUserTracker;
     private final FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
@@ -198,6 +208,14 @@
         when(mUserTracker.getUserHandle()).thenReturn(
                 UserHandle.of(ActivityManager.getCurrentUser()));
 
+        mPowerRepository = new FakePowerRepository();
+        mPowerInteractor = new PowerInteractor(
+                mPowerRepository,
+                new FakeKeyguardRepository(),
+                new FalsingCollectorFake(),
+                mScreenOffAnimationController,
+                mStatusBarStateController);
+
         HeadsUpManagerPhone headsUpManager = mock(HeadsUpManagerPhone.class);
         NotificationLaunchAnimatorControllerProvider notificationAnimationProvider =
                 new NotificationLaunchAnimatorControllerProvider(
@@ -208,6 +226,7 @@
         mNotificationActivityStarter =
                 new StatusBarNotificationActivityStarter(
                         getContext(),
+                        DISPLAY_ID,
                         mHandler,
                         mUiBgExecutor,
                         mVisibilityProvider,
@@ -230,12 +249,13 @@
                         mock(MetricsLogger.class),
                         mock(StatusBarNotificationActivityStarterLogger.class),
                         mOnUserInteractionCallback,
-                        mCentralSurfaces,
                         mock(NotificationPresenter.class),
                         mock(ShadeViewController.class),
+                        mock(NotificationShadeWindowController.class),
                         mActivityLaunchAnimator,
                         notificationAnimationProvider,
                         mock(LaunchFullScreenIntentProvider.class),
+                        mPowerInteractor,
                         mock(FeatureFlags.class),
                         mUserTracker
                 );
@@ -272,7 +292,7 @@
         notification.flags |= Notification.FLAG_AUTO_CANCEL;
 
         when(mKeyguardStateController.isShowing()).thenReturn(true);
-        when(mCentralSurfaces.isOccluded()).thenReturn(true);
+        when(mKeyguardStateController.isOccluded()).thenReturn(true);
 
         // When
         mNotificationActivityStarter.onNotificationClicked(entry, mNotificationRow);
@@ -338,7 +358,7 @@
         // Given
         sbn.getNotification().contentIntent = null;
         when(mKeyguardStateController.isShowing()).thenReturn(true);
-        when(mCentralSurfaces.isOccluded()).thenReturn(true);
+        when(mKeyguardStateController.isOccluded()).thenReturn(true);
 
         // When
         mNotificationActivityStarter.onNotificationClicked(entry, mBubbleNotificationRow);
@@ -366,7 +386,7 @@
         // Given
         sbn.getNotification().contentIntent = mContentIntent;
         when(mKeyguardStateController.isShowing()).thenReturn(true);
-        when(mCentralSurfaces.isOccluded()).thenReturn(true);
+        when(mKeyguardStateController.isOccluded()).thenReturn(true);
 
         // When
         mNotificationActivityStarter.onNotificationClicked(entry, mBubbleNotificationRow);
@@ -400,11 +420,13 @@
         when(entry.getImportance()).thenReturn(NotificationManager.IMPORTANCE_HIGH);
         when(entry.getSbn()).thenReturn(sbn);
 
-        // WHEN
+        // WHEN the intent is launched while dozing
+        when(mStatusBarStateController.isDozing()).thenReturn(true);
         mNotificationActivityStarter.launchFullScreenIntent(entry);
 
         // THEN display should try wake up for the full screen intent
-        verify(mCentralSurfaces).wakeUpForFullScreenIntent();
+        assertThat(mPowerRepository.getLastWakeReason()).isNotNull();
+        assertThat(mPowerRepository.getLastWakeWhy()).isNotNull();
     }
 
     @Test
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 fdfe028..cd8aaa2 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
@@ -37,14 +37,13 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.power.domain.interactor.PowerInteractor;
 import com.android.systemui.settings.FakeDisplayTracker;
 import com.android.systemui.shade.NotificationShadeWindowView;
 import com.android.systemui.shade.QuickSettingsController;
 import com.android.systemui.shade.ShadeController;
-import com.android.systemui.shade.ShadeNotificationPresenter;
 import com.android.systemui.shade.ShadeViewController;
 import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.KeyguardIndicationController;
 import com.android.systemui.statusbar.LockscreenShadeTransitionController;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationMediaManager;
@@ -52,10 +51,10 @@
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 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.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
 import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource;
+import com.android.systemui.statusbar.notification.domain.interactor.NotificationsInteractor;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptSuppressor;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
@@ -81,16 +80,15 @@
     private FakeMetricsLogger mMetricsLogger;
     private final ShadeController mShadeController = mock(ShadeController.class);
     private final CentralSurfaces mCentralSurfaces = mock(CentralSurfaces.class);
+    private final NotificationsInteractor mNotificationsInteractor =
+            mock(NotificationsInteractor.class);
     private final KeyguardStateController mKeyguardStateController =
             mock(KeyguardStateController.class);
-    private final NotifPipelineFlags mNotifPipelineFlags = mock(NotifPipelineFlags.class);
     private final InitController mInitController = new InitController();
 
     @Before
     public void setup() {
         mMetricsLogger = new FakeMetricsLogger();
-        LockscreenGestureLogger lockscreenGestureLogger = new LockscreenGestureLogger(
-                mMetricsLogger);
         mCommandQueue = new CommandQueue(mContext, new FakeDisplayTracker(mContext));
         mDependency.injectTestDependency(StatusBarStateController.class,
                 mock(SysuiStatusBarStateController.class));
@@ -108,8 +106,6 @@
         when(notificationShadeWindowView.getResources()).thenReturn(mContext.getResources());
 
         ShadeViewController shadeViewController = mock(ShadeViewController.class);
-        when(shadeViewController.getShadeNotificationPresenter())
-                .thenReturn(mock(ShadeNotificationPresenter.class));
         mStatusBarNotificationPresenter = new StatusBarNotificationPresenter(
                 mContext,
                 shadeViewController,
@@ -122,20 +118,19 @@
                 mock(NotificationShadeWindowController.class),
                 mock(DynamicPrivacyController.class),
                 mKeyguardStateController,
-                mock(KeyguardIndicationController.class),
                 mCentralSurfaces,
+                mNotificationsInteractor,
                 mock(LockscreenShadeTransitionController.class),
+                mock(PowerInteractor.class),
                 mCommandQueue,
                 mock(NotificationLockscreenUserManager.class),
                 mock(SysuiStatusBarStateController.class),
                 mock(NotifShadeEventSource.class),
                 mock(NotificationMediaManager.class),
                 mock(NotificationGutsManager.class),
-                lockscreenGestureLogger,
                 mInitController,
                 mNotificationInterruptStateProvider,
                 mock(NotificationRemoteInputManager.class),
-                mNotifPipelineFlags,
                 mock(NotificationRemoteInputManager.Callback.class),
                 mock(NotificationListContainer.class));
         mInitController.executePostInitTasks();
@@ -235,9 +230,9 @@
                 .setTag("a")
                 .setNotification(n)
                 .build();
-        when(mCentralSurfaces.areNotificationAlertsDisabled()).thenReturn(true);
+        when(mNotificationsInteractor.areNotificationAlertsEnabled()).thenReturn(false);
 
-        assertTrue("CentralSurfaces alerts disabled shouldn't allow interruptions",
+        assertTrue("When alerts aren't enabled, interruptions are suppressed",
                 mInterruptSuppressor.suppressInterruptions(entry));
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
index ea534bb..2e9a690 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
@@ -28,6 +28,7 @@
 import com.android.systemui.keyguard.WakefulnessLifecycle
 import com.android.systemui.shade.ShadeViewController
 import com.android.systemui.statusbar.LightRevealScrim
+import com.android.systemui.statusbar.NotificationShadeWindowController
 import com.android.systemui.statusbar.StatusBarStateControllerImpl
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.mockito.eq
@@ -65,6 +66,8 @@
     @Mock
     private lateinit var shadeViewController: ShadeViewController
     @Mock
+    private lateinit var notifShadeWindowController: NotificationShadeWindowController
+    @Mock
     private lateinit var lightRevealScrim: LightRevealScrim
     @Mock
     private lateinit var wakefulnessLifecycle: WakefulnessLifecycle
@@ -89,6 +92,7 @@
                 keyguardStateController,
                 dagger.Lazy<DozeParameters> { dozeParameters },
                 globalSettings,
+                dagger.Lazy<NotificationShadeWindowController> { notifShadeWindowController },
                 interactionJankMonitor,
                 powerManager,
                 handler = handler
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLoggerTest.kt
index 9bc49ae..87d813c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLoggerTest.kt
@@ -50,7 +50,6 @@
         val actualString = stringWriter.toString()
         val expectedLogString =
             disableFlagsLogger.getDisableFlagsString(
-                old = null,
                 new = state,
                 newAfterLocalModification = null,
             )
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 9b1d93b..5dcb901 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
@@ -18,10 +18,6 @@
 
 import static com.android.systemui.shade.ShadeExpansionStateManagerKt.STATE_CLOSED;
 import static com.android.systemui.shade.ShadeExpansionStateManagerKt.STATE_OPEN;
-import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.ANIMATING_IN;
-import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.ANIMATING_OUT;
-import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.IDLE;
-import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.RUNNING_CHIP_ANIM;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -49,7 +45,7 @@
 import android.view.ViewPropertyAnimator;
 import android.widget.FrameLayout;
 
-import androidx.core.animation.Animator;
+import androidx.core.animation.AnimatorTestRule;
 import androidx.test.filters.SmallTest;
 
 import com.android.keyguard.KeyguardUpdateMonitor;
@@ -85,6 +81,7 @@
 import com.android.systemui.util.time.FakeSystemClock;
 
 import org.junit.Before;
+import org.junit.ClassRule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -139,6 +136,8 @@
     private StatusBarWindowStateController mStatusBarWindowStateController;
     @Mock
     private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+    @ClassRule
+    public static AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule();
 
     private List<StatusBarWindowStateListener> mStatusBarWindowStateListeners = new ArrayList<>();
 
@@ -172,7 +171,6 @@
 
     @Test
     public void testDisableSystemInfo_systemAnimationIdle_doesHide() {
-        when(mAnimationScheduler.getAnimationState()).thenReturn(IDLE);
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
         fragment.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_SYSTEM_INFO, 0, false);
@@ -192,24 +190,26 @@
     public void testSystemStatusAnimation_startedDisabled_finishedWithAnimator_showsSystemInfo() {
         // GIVEN the status bar hides the system info via disable flags, while there is no event
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
-        when(mAnimationScheduler.getAnimationState()).thenReturn(IDLE);
         fragment.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_SYSTEM_INFO, 0, false);
         assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility());
 
+        // WHEN the system event animation starts
+        fragment.onSystemEventAnimationBegin().start();
+
+        // THEN the view remains invisible during the animation
+        assertEquals(0f, getEndSideContentView().getAlpha(), 0.01);
+        mAnimatorTestRule.advanceTimeBy(500);
+        assertEquals(0f, getEndSideContentView().getAlpha(), 0.01);
+
         // WHEN the disable flags are cleared during a system event animation
-        when(mAnimationScheduler.getAnimationState()).thenReturn(RUNNING_CHIP_ANIM);
         fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
 
-        // THEN the view is made visible again, but still low alpha
-        assertEquals(View.VISIBLE, getEndSideContentView().getVisibility());
+        // THEN the view remains invisible
         assertEquals(0, getEndSideContentView().getAlpha(), 0.01);
 
         // WHEN the system event animation finishes
-        when(mAnimationScheduler.getAnimationState()).thenReturn(ANIMATING_OUT);
-        Animator anim = fragment.onSystemEventAnimationFinish(false);
-        anim.start();
-        processAllMessages();
-        anim.end();
+        fragment.onSystemEventAnimationFinish(false).start();
+        mAnimatorTestRule.advanceTimeBy(500);
 
         // THEN the system info is full alpha
         assertEquals(1, getEndSideContentView().getAlpha(), 0.01);
@@ -219,20 +219,15 @@
     public void testSystemStatusAnimation_systemInfoDisabled_staysInvisible() {
         // GIVEN the status bar hides the system info via disable flags, while there is no event
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
-        when(mAnimationScheduler.getAnimationState()).thenReturn(IDLE);
         fragment.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_SYSTEM_INFO, 0, false);
         assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility());
 
         // WHEN the system event animation finishes
-        when(mAnimationScheduler.getAnimationState()).thenReturn(ANIMATING_OUT);
-        Animator anim = fragment.onSystemEventAnimationFinish(false);
-        anim.start();
-        processAllMessages();
-        anim.end();
+        fragment.onSystemEventAnimationFinish(false).start();
+        mAnimatorTestRule.advanceTimeBy(500);
 
-        // THEN the system info is at full alpha, but still INVISIBLE (since the disable flag is
-        // still set)
-        assertEquals(1, getEndSideContentView().getAlpha(), 0.01);
+        // THEN the system info remains invisible (since the disable flag is still set)
+        assertEquals(0, getEndSideContentView().getAlpha(), 0.01);
         assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility());
     }
 
@@ -241,15 +236,14 @@
     public void testSystemStatusAnimation_notDisabled_animatesAlphaZero() {
         // GIVEN the status bar is not disabled
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
-        when(mAnimationScheduler.getAnimationState()).thenReturn(ANIMATING_IN);
-        // WHEN the system event animation begins
-        Animator anim = fragment.onSystemEventAnimationBegin();
-        anim.start();
-        processAllMessages();
-        anim.end();
+        assertEquals(1, getEndSideContentView().getAlpha(), 0.01);
 
-        // THEN the system info is visible but alpha 0
-        assertEquals(View.VISIBLE, getEndSideContentView().getVisibility());
+        // WHEN the system event animation begins
+        fragment.onSystemEventAnimationBegin().start();
+        mAnimatorTestRule.advanceTimeBy(500);
+
+        // THEN the system info is invisible
+        assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility());
         assertEquals(0, getEndSideContentView().getAlpha(), 0.01);
     }
 
@@ -257,25 +251,21 @@
     public void testSystemStatusAnimation_notDisabled_animatesBackToAlphaOne() {
         // GIVEN the status bar is not disabled
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
-        when(mAnimationScheduler.getAnimationState()).thenReturn(ANIMATING_IN);
-        // WHEN the system event animation begins
-        Animator anim = fragment.onSystemEventAnimationBegin();
-        anim.start();
-        processAllMessages();
-        anim.end();
+        assertEquals(1, getEndSideContentView().getAlpha(), 0.01);
 
-        // THEN the system info is visible but alpha 0
-        assertEquals(View.VISIBLE, getEndSideContentView().getVisibility());
+        // WHEN the system event animation begins
+        fragment.onSystemEventAnimationBegin().start();
+        mAnimatorTestRule.advanceTimeBy(500);
+
+        // THEN the system info is invisible
+        assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility());
         assertEquals(0, getEndSideContentView().getAlpha(), 0.01);
 
         // WHEN the system event animation finishes
-        when(mAnimationScheduler.getAnimationState()).thenReturn(ANIMATING_OUT);
-        anim = fragment.onSystemEventAnimationFinish(false);
-        anim.start();
-        processAllMessages();
-        anim.end();
+        fragment.onSystemEventAnimationFinish(false).start();
+        mAnimatorTestRule.advanceTimeBy(500);
 
-        // THEN the syste info is full alpha and VISIBLE
+        // THEN the system info is full alpha and VISIBLE
         assertEquals(View.VISIBLE, getEndSideContentView().getVisibility());
         assertEquals(1, getEndSideContentView().getAlpha(), 0.01);
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaControllerTest.kt
new file mode 100644
index 0000000..2617613
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaControllerTest.kt
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.fragment
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.view.View
+import androidx.core.animation.AnimatorTestRule
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import junit.framework.Assert.assertEquals
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+private const val TEST_SOURCE_1 = 1
+private const val TEST_SOURCE_2 = 2
+private const val TEST_ANIMATION_DURATION = 100L
+private const val INITIAL_ALPHA = 1f
+
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+@SmallTest
+class MultiSourceMinAlphaControllerTest : SysuiTestCase() {
+
+    private val view = View(context)
+    private val multiSourceMinAlphaController =
+        MultiSourceMinAlphaController(view, initialAlpha = INITIAL_ALPHA)
+
+    @get:Rule val animatorTestRule = AnimatorTestRule()
+
+    @Before
+    fun setup() {
+        multiSourceMinAlphaController.reset()
+    }
+
+    @Test
+    fun testSetAlpha() {
+        multiSourceMinAlphaController.setAlpha(alpha = 0.5f, sourceId = TEST_SOURCE_1)
+        assertEquals(0.5f, view.alpha)
+    }
+
+    @Test
+    fun testAnimateToAlpha() {
+        multiSourceMinAlphaController.animateToAlpha(
+            alpha = 0.5f,
+            sourceId = TEST_SOURCE_1,
+            duration = TEST_ANIMATION_DURATION
+        )
+        animatorTestRule.advanceTimeBy(TEST_ANIMATION_DURATION)
+        assertEquals(0.5f, view.alpha)
+    }
+
+    @Test
+    fun testReset() {
+        multiSourceMinAlphaController.animateToAlpha(
+            alpha = 0.5f,
+            sourceId = TEST_SOURCE_1,
+            duration = TEST_ANIMATION_DURATION
+        )
+        multiSourceMinAlphaController.setAlpha(alpha = 0.7f, sourceId = TEST_SOURCE_2)
+        multiSourceMinAlphaController.reset()
+        // advance time to ensure that animators are cancelled when the controller is reset
+        animatorTestRule.advanceTimeBy(TEST_ANIMATION_DURATION)
+        assertEquals(INITIAL_ALPHA, view.alpha)
+    }
+
+    @Test
+    fun testMinOfTwoSourcesIsApplied() {
+        multiSourceMinAlphaController.setAlpha(alpha = 0f, sourceId = TEST_SOURCE_1)
+        multiSourceMinAlphaController.setAlpha(alpha = 0.5f, sourceId = TEST_SOURCE_2)
+        assertEquals(0f, view.alpha)
+        multiSourceMinAlphaController.setAlpha(alpha = 1f, sourceId = TEST_SOURCE_1)
+        assertEquals(0.5f, view.alpha)
+    }
+
+    @Test
+    fun testSetAlphaForSameSourceCancelsAnimator() {
+        multiSourceMinAlphaController.animateToAlpha(
+            alpha = 0f,
+            sourceId = TEST_SOURCE_1,
+            duration = TEST_ANIMATION_DURATION
+        )
+        animatorTestRule.advanceTimeBy(TEST_ANIMATION_DURATION / 2)
+        multiSourceMinAlphaController.setAlpha(alpha = 1f, sourceId = TEST_SOURCE_1)
+        animatorTestRule.advanceTimeBy(TEST_ANIMATION_DURATION / 2)
+        // verify that animation was cancelled and the setAlpha call overrides the alpha value of
+        // the animation
+        assertEquals(1f, view.alpha)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepositoryTest.kt
index 6e3af26..932c4a1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepositoryTest.kt
@@ -152,13 +152,11 @@
         }
 
     private fun sendConfig(subId: Int) {
-        fakeBroadcastDispatcher.registeredReceivers.forEach { receiver ->
-            receiver.onReceive(
-                context,
-                Intent(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)
-                    .putExtra(CarrierConfigManager.EXTRA_SUBSCRIPTION_INDEX, subId)
-            )
-        }
+        fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+            context,
+            Intent(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)
+                .putExtra(CarrierConfigManager.EXTRA_SUBSCRIPTION_INDEX, subId),
+        )
     }
 
     companion object {
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 d1df6e3..cf832b4 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
@@ -379,9 +379,10 @@
             var latest: Int? = null
             val job = underTest.carrierId.onEach { latest = it }.launchIn(this)
 
-            fakeBroadcastDispatcher.registeredReceivers.forEach { receiver ->
-                receiver.onReceive(context, carrierIdIntent(carrierId = 4321))
-            }
+            fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+                context,
+                carrierIdIntent(carrierId = 4321),
+            )
 
             assertThat(latest).isEqualTo(4321)
 
@@ -653,10 +654,7 @@
             val job = underTest.networkName.onEach { latest = it }.launchIn(this)
 
             val intent = spnIntent()
-
-            fakeBroadcastDispatcher.registeredReceivers.forEach { receiver ->
-                receiver.onReceive(context, intent)
-            }
+            fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(context, intent)
 
             assertThat(latest).isEqualTo(intent.toNetworkNameModel(SEP))
 
@@ -670,17 +668,13 @@
             val job = underTest.networkName.onEach { latest = it }.launchIn(this)
 
             val intent = spnIntent()
-            fakeBroadcastDispatcher.registeredReceivers.forEach { receiver ->
-                receiver.onReceive(context, intent)
-            }
+            fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(context, intent)
             assertThat(latest).isEqualTo(intent.toNetworkNameModel(SEP))
 
             // WHEN an intent with a different subId is sent
             val wrongSubIntent = spnIntent(subId = 101)
 
-            fakeBroadcastDispatcher.registeredReceivers.forEach { receiver ->
-                receiver.onReceive(context, wrongSubIntent)
-            }
+            fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(context, wrongSubIntent)
 
             // THEN the previous intent's name is still used
             assertThat(latest).isEqualTo(intent.toNetworkNameModel(SEP))
@@ -695,9 +689,7 @@
             val job = underTest.networkName.onEach { latest = it }.launchIn(this)
 
             val intent = spnIntent()
-            fakeBroadcastDispatcher.registeredReceivers.forEach { receiver ->
-                receiver.onReceive(context, intent)
-            }
+            fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(context, intent)
             assertThat(latest).isEqualTo(intent.toNetworkNameModel(SEP))
 
             val intentWithoutInfo =
@@ -706,9 +698,7 @@
                     showPlmn = false,
                 )
 
-            fakeBroadcastDispatcher.registeredReceivers.forEach { receiver ->
-                receiver.onReceive(context, intentWithoutInfo)
-            }
+            fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(context, intentWithoutInfo)
 
             assertThat(latest).isEqualTo(DEFAULT_NAME)
 
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 9aea70f..c8b6f13d 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
@@ -73,7 +73,6 @@
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
-import org.junit.Assert.assertThrows
 import org.junit.Assert.assertTrue
 import org.junit.Before
 import org.junit.Test
@@ -160,6 +159,7 @@
                 mock(),
                 mock(),
                 FakeExecutor(FakeSystemClock()),
+                dispatcher,
                 testScope.backgroundScope,
                 mock(),
             )
@@ -588,11 +588,10 @@
         }
 
     @Test
-    fun testConnectionRepository_invalidSubId_throws() =
+    fun testConnectionRepository_invalidSubId_doesNotThrow() =
         testScope.runTest {
-            assertThrows(IllegalArgumentException::class.java) {
-                underTest.getRepoForSubId(SUB_1_ID)
-            }
+            underTest.getRepoForSubId(SUB_1_ID)
+            // No exception
         }
 
     @Test
@@ -626,23 +625,17 @@
 
             assertThat(latest).isEqualTo(INVALID_SUBSCRIPTION_ID)
 
-            fakeBroadcastDispatcher.registeredReceivers.forEach { receiver ->
-                receiver.onReceive(
-                    context,
-                    Intent(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)
-                        .putExtra(PhoneConstants.SUBSCRIPTION_KEY, SUB_2_ID)
-                )
-            }
+            val intent2 =
+                Intent(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)
+                    .putExtra(PhoneConstants.SUBSCRIPTION_KEY, SUB_2_ID)
+            fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(context, intent2)
 
             assertThat(latest).isEqualTo(SUB_2_ID)
 
-            fakeBroadcastDispatcher.registeredReceivers.forEach { receiver ->
-                receiver.onReceive(
-                    context,
-                    Intent(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)
-                        .putExtra(PhoneConstants.SUBSCRIPTION_KEY, SUB_1_ID)
-                )
-            }
+            val intent1 =
+                Intent(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)
+                    .putExtra(PhoneConstants.SUBSCRIPTION_KEY, SUB_1_ID)
+            fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(context, intent1)
 
             assertThat(latest).isEqualTo(SUB_1_ID)
         }
@@ -1096,12 +1089,10 @@
             assertThat(configFromContext.showAtLeast3G).isTrue()
 
             // WHEN the change event is fired
-            fakeBroadcastDispatcher.registeredReceivers.forEach { receiver ->
-                receiver.onReceive(
-                    context,
-                    Intent(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)
-                )
-            }
+            fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+                context,
+                Intent(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED),
+            )
 
             // THEN the config is updated
             assertThat(latest!!.areEqual(configFromContext)).isTrue()
@@ -1120,12 +1111,10 @@
             assertThat(configFromContext.showAtLeast3G).isTrue()
 
             // WHEN the change event is fired
-            fakeBroadcastDispatcher.registeredReceivers.forEach { receiver ->
-                receiver.onReceive(
-                    context,
-                    Intent(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)
-                )
-            }
+            fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+                context,
+                Intent(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED),
+            )
 
             // WHEN collection starts AFTER the broadcast is sent out
             val latest by collectLastValue(underTest.defaultDataSubRatConfig)
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 6301fa0..842d548 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
@@ -20,7 +20,7 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectValues
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -48,7 +48,11 @@
 
         keyguardTransitionRepository = FakeKeyguardTransitionRepository()
         val interactor =
-            KeyguardTransitionInteractor(keyguardTransitionRepository, testScope.backgroundScope)
+            KeyguardTransitionInteractorFactory.create(
+                    scope = TestScope().backgroundScope,
+                    repository = keyguardTransitionRepository,
+                )
+                .keyguardTransitionInteractor
         underTest = CollapsedStatusBarViewModelImpl(interactor, testScope.backgroundScope)
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcherTest.kt
index 30b95ef..5bc98e0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcherTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcherTest.kt
@@ -83,6 +83,7 @@
                 logger,
                 tableLogger,
                 mainExecutor,
+                testDispatcher,
                 testScope.backgroundScope,
                 wifiManager,
             )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
index dc68180..7007345 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.pipeline.wifi.data.repository.prod
 
+import android.content.Intent
 import android.net.ConnectivityManager
 import android.net.Network
 import android.net.NetworkCapabilities
@@ -33,7 +34,6 @@
 import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlots
 import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
@@ -46,13 +46,10 @@
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.nullable
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import java.util.concurrent.Executor
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.MutableSharedFlow
-import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.test.TestScope
@@ -60,7 +57,6 @@
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
-import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.Mock
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when` as whenever
@@ -73,7 +69,6 @@
 
     private lateinit var underTest: WifiRepositoryImpl
 
-    @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
     @Mock private lateinit var logger: WifiInputLogger
     @Mock private lateinit var tableLogger: TableLogBuffer
     @Mock private lateinit var connectivityManager: ConnectivityManager
@@ -81,20 +76,12 @@
     private lateinit var executor: Executor
     private lateinit var connectivityRepository: ConnectivityRepository
 
-    private val testScope = TestScope(UnconfinedTestDispatcher())
+    private val dispatcher = UnconfinedTestDispatcher()
+    private val testScope = TestScope(dispatcher)
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
-        whenever(
-                broadcastDispatcher.broadcastFlow(
-                    any(),
-                    nullable(),
-                    anyInt(),
-                    nullable(),
-                )
-            )
-            .thenReturn(flowOf(Unit))
         executor = FakeExecutor(FakeSystemClock())
 
         connectivityRepository =
@@ -168,27 +155,23 @@
     @Test
     fun isWifiEnabled_intentsReceived_valueUpdated() =
         testScope.runTest {
-            val intentFlow = MutableSharedFlow<Unit>()
-            whenever(
-                    broadcastDispatcher.broadcastFlow(
-                        any(),
-                        nullable(),
-                        anyInt(),
-                        nullable(),
-                    )
-                )
-                .thenReturn(intentFlow)
             underTest = createRepo()
 
             val job = underTest.isWifiEnabled.launchIn(this)
 
             whenever(wifiManager.isWifiEnabled).thenReturn(true)
-            intentFlow.emit(Unit)
+            fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+                context,
+                Intent(WifiManager.WIFI_STATE_CHANGED_ACTION),
+            )
 
             assertThat(underTest.isWifiEnabled.value).isTrue()
 
             whenever(wifiManager.isWifiEnabled).thenReturn(false)
-            intentFlow.emit(Unit)
+            fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+                context,
+                Intent(WifiManager.WIFI_STATE_CHANGED_ACTION),
+            )
 
             assertThat(underTest.isWifiEnabled.value).isFalse()
 
@@ -198,23 +181,16 @@
     @Test
     fun isWifiEnabled_bothIntentAndNetworkUpdates_valueAlwaysUpdated() =
         testScope.runTest {
-            val intentFlow = MutableSharedFlow<Unit>()
-            whenever(
-                    broadcastDispatcher.broadcastFlow(
-                        any(),
-                        nullable(),
-                        anyInt(),
-                        nullable(),
-                    )
-                )
-                .thenReturn(intentFlow)
             underTest = createRepo()
 
             val networkJob = underTest.wifiNetwork.launchIn(this)
             val enabledJob = underTest.isWifiEnabled.launchIn(this)
 
             whenever(wifiManager.isWifiEnabled).thenReturn(false)
-            intentFlow.emit(Unit)
+            fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+                context,
+                Intent(WifiManager.WIFI_STATE_CHANGED_ACTION),
+            )
             assertThat(underTest.isWifiEnabled.value).isFalse()
 
             whenever(wifiManager.isWifiEnabled).thenReturn(true)
@@ -227,7 +203,10 @@
             assertThat(underTest.isWifiEnabled.value).isFalse()
 
             whenever(wifiManager.isWifiEnabled).thenReturn(true)
-            intentFlow.emit(Unit)
+            fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+                context,
+                Intent(WifiManager.WIFI_STATE_CHANGED_ACTION),
+            )
             assertThat(underTest.isWifiEnabled.value).isTrue()
 
             networkJob.cancel()
@@ -1317,12 +1296,13 @@
 
     private fun createRepo(): WifiRepositoryImpl {
         return WifiRepositoryImpl(
-            broadcastDispatcher,
+            fakeBroadcastDispatcher,
             connectivityManager,
             connectivityRepository,
             logger,
             tableLogger,
             executor,
+            dispatcher,
             testScope.backgroundScope,
             wifiManager,
         )
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 d787ada..5cabcd4 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
@@ -24,6 +24,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.hardware.biometrics.BiometricSourceType;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
@@ -43,6 +44,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
@@ -66,6 +68,9 @@
     @Mock
     private KeyguardUpdateMonitorLogger mLogger;
 
+    @Captor
+    private ArgumentCaptor<KeyguardUpdateMonitorCallback> mUpdateCallbackCaptor;
+
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
@@ -84,6 +89,23 @@
     }
 
     @Test
+    public void testFaceAuthEnabledChanged_calledWhenFaceEnrollmentStateChanges() {
+        KeyguardStateController.Callback callback = mock(KeyguardStateController.Callback.class);
+
+        when(mKeyguardUpdateMonitor.isFaceAuthEnabledForUser(anyInt())).thenReturn(false);
+        verify(mKeyguardUpdateMonitor).registerCallback(mUpdateCallbackCaptor.capture());
+        mKeyguardStateController.addCallback(callback);
+        assertThat(mKeyguardStateController.isFaceAuthEnabled()).isFalse();
+
+        when(mKeyguardUpdateMonitor.isFaceAuthEnabledForUser(anyInt())).thenReturn(true);
+        mUpdateCallbackCaptor.getValue().onBiometricEnrollmentStateChanged(
+                BiometricSourceType.FACE);
+
+        assertThat(mKeyguardStateController.isFaceAuthEnabled()).isTrue();
+        verify(callback).onFaceAuthEnabledChanged();
+    }
+
+    @Test
     public void testIsShowing() {
         assertThat(mKeyguardStateController.isShowing()).isFalse();
         mKeyguardStateController.notifyKeyguardState(true /* showing */, false /* occluded */);
@@ -177,16 +199,14 @@
     @Test
     public void testOnEnabledTrustAgentsChangedCallback() {
         final Random random = new Random();
-        final ArgumentCaptor<KeyguardUpdateMonitorCallback> updateCallbackCaptor =
-                ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback.class);
 
-        verify(mKeyguardUpdateMonitor).registerCallback(updateCallbackCaptor.capture());
+        verify(mKeyguardUpdateMonitor).registerCallback(mUpdateCallbackCaptor.capture());
         final KeyguardStateController.Callback stateCallback =
                 mock(KeyguardStateController.Callback.class);
         mKeyguardStateController.addCallback(stateCallback);
 
         when(mLockPatternUtils.isSecure(anyInt())).thenReturn(true);
-        updateCallbackCaptor.getValue().onEnabledTrustAgentsChanged(random.nextInt());
+        mUpdateCallbackCaptor.getValue().onEnabledTrustAgentsChanged(random.nextInt());
         verify(stateCallback).onUnlockedChanged();
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java
index 3b0d512..ae38958e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java
@@ -51,9 +51,11 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shade.ShadeController;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.SmartReplyController;
+import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
 import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
@@ -107,14 +109,15 @@
 
     private SmartReplyInflaterImpl mSmartReplyInflater;
     private SmartActionInflaterImpl mSmartActionInflater;
+    private KeyguardDismissUtil mKeyguardDismissUtil;
 
     @Mock private SmartReplyConstants mConstants;
     @Mock private ActivityStarter mActivityStarter;
     @Mock private HeadsUpManager mHeadsUpManager;
     @Mock private NotificationRemoteInputManager mNotificationRemoteInputManager;
     @Mock private SmartReplyController mSmartReplyController;
-
-    private final KeyguardDismissUtil mKeyguardDismissUtil = new KeyguardDismissUtil();
+    @Mock private  KeyguardStateController mKeyguardStateController;
+    @Mock private  SysuiStatusBarStateController mStatusBarStateController;
 
     @Before
     public void setUp() {
@@ -122,12 +125,15 @@
         mReceiver = new BlockingQueueIntentReceiver();
         mContext.registerReceiver(mReceiver, new IntentFilter(TEST_ACTION),
                 Context.RECEIVER_EXPORTED_UNAUDITED);
-        mKeyguardDismissUtil.setDismissHandler((action, unused, afterKgGone) -> action.onDismiss());
+
         mDependency.injectMockDependency(KeyguardUpdateMonitor.class);
         mDependency.injectMockDependency(ShadeController.class);
         mDependency.injectMockDependency(NotificationRemoteInputManager.class);
         mDependency.injectTestDependency(ActivityStarter.class, mActivityStarter);
         mDependency.injectTestDependency(SmartReplyConstants.class, mConstants);
+        mDependency.injectTestDependency(KeyguardStateController.class, mKeyguardStateController);
+        mDependency.injectTestDependency(StatusBarStateController.class, mStatusBarStateController);
+
 
         // Any number of replies are fine.
         when(mConstants.getMinNumSystemGeneratedReplies()).thenReturn(0);
@@ -153,6 +159,13 @@
 
         mActionIcon = Icon.createWithResource(mContext, R.drawable.ic_person);
 
+        mKeyguardDismissUtil = new KeyguardDismissUtil(
+                mKeyguardStateController, mStatusBarStateController, mActivityStarter) {
+            public void executeWhenUnlocked(ActivityStarter.OnDismissAction action,
+                    boolean requiresShadeOpen, boolean afterKeyguardGone) {
+                action.onDismiss();
+            }
+        };
         mSmartReplyInflater = new SmartReplyInflaterImpl(
                 mConstants,
                 mKeyguardDismissUtil,
@@ -185,7 +198,17 @@
 
     @Test
     public void testSendSmartReply_keyguardCancelled() throws InterruptedException {
-        mKeyguardDismissUtil.setDismissHandler((action, unused, afterKgGone) -> { });
+        mKeyguardDismissUtil = new KeyguardDismissUtil(
+                mKeyguardStateController, mStatusBarStateController, mActivityStarter) {
+            public void executeWhenUnlocked(ActivityStarter.OnDismissAction action,
+                    boolean requiresShadeOpen, boolean afterKeyguardGone) { }};
+        mSmartReplyInflater = new SmartReplyInflaterImpl(
+                mConstants,
+                mKeyguardDismissUtil,
+                mNotificationRemoteInputManager,
+                mSmartReplyController,
+                mContext);
+
         setSmartReplies(TEST_CHOICES);
 
         mView.getChildAt(2).performClick();
@@ -196,9 +219,20 @@
     @Test
     public void testSendSmartReply_waitsForKeyguard() throws InterruptedException {
         AtomicReference<OnDismissAction> actionRef = new AtomicReference<>();
+        mKeyguardDismissUtil = new KeyguardDismissUtil(
+                mKeyguardStateController, mStatusBarStateController, mActivityStarter) {
+            public void executeWhenUnlocked(ActivityStarter.OnDismissAction action,
+                    boolean requiresShadeOpen, boolean afterKeyguardGone) {
+                actionRef.set(action);
+            }
+        };
+        mSmartReplyInflater = new SmartReplyInflaterImpl(
+                mConstants,
+                mKeyguardDismissUtil,
+                mNotificationRemoteInputManager,
+                mSmartReplyController,
+                mContext);
 
-        mKeyguardDismissUtil.setDismissHandler((action, unused, afterKgGone)
-                -> actionRef.set(action));
         setSmartReplies(TEST_CHOICES);
 
         mView.getChildAt(2).performClick();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerUiTest.kt b/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerUiTest.kt
index 90821bd..d212c02 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerUiTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerUiTest.kt
@@ -126,6 +126,16 @@
     }
 
     @Test
+    fun updateBatteryState_capacitySame_inputDeviceChanges_updatesInputDeviceId() {
+        stylusUsiPowerUi.updateBatteryState(0, FixedCapacityBatteryState(0.1f))
+        stylusUsiPowerUi.updateBatteryState(1, FixedCapacityBatteryState(0.1f))
+
+        assertThat(stylusUsiPowerUi.inputDeviceId).isEqualTo(1)
+        verify(notificationManager, times(1))
+            .notify(eq(R.string.stylus_battery_low_percentage), any())
+    }
+
+    @Test
     fun updateBatteryState_existingNotification_capacityAboveThreshold_cancelsNotification() {
         stylusUsiPowerUi.updateBatteryState(0, FixedCapacityBatteryState(0.1f))
         stylusUsiPowerUi.updateBatteryState(0, FixedCapacityBatteryState(0.8f))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java b/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java
index 27957ed..f299ad4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java
@@ -28,6 +28,7 @@
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
 
 import android.app.Application;
@@ -36,6 +37,7 @@
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
+import android.hardware.display.DisplayManager;
 import android.os.Binder;
 import android.os.Build;
 import android.os.Parcel;
@@ -74,6 +76,8 @@
 import org.mockito.MockitoAnnotations;
 import org.mockito.stubbing.Answer;
 
+import java.util.Arrays;
+
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
@@ -400,6 +404,18 @@
         verify(mToastLogger, never()).logOnHideToast(PACKAGE_NAME_1, TOKEN_1.toString());
     }
 
+    @Test
+    public void testShowToast_invalidDisplayId_logsAndSkipsToast() {
+        int invalidDisplayId = getInvalidDisplayId();
+
+        mToastUI.showToast(UID_1, PACKAGE_NAME_1, TOKEN_1, TEXT, WINDOW_TOKEN_1, Toast.LENGTH_LONG,
+                mCallback, invalidDisplayId);
+
+        verify(mToastLogger).logOnSkipToastForInvalidDisplay(PACKAGE_NAME_1, TOKEN_1.toString(),
+                invalidDisplayId);
+        verifyZeroInteractions(mWindowManager);
+    }
+
     private View verifyWmAddViewAndAttachToParent() {
         ArgumentCaptor<View> viewCaptor = ArgumentCaptor.forClass(View.class);
         verify(mWindowManager).addView(viewCaptor.capture(), any());
@@ -416,4 +432,10 @@
             return null;
         };
     }
+
+    private int getInvalidDisplayId() {
+        return Arrays.stream(
+                mContext.getSystemService(DisplayManager.class).getDisplays())
+                .map(Display::getDisplayId).max(Integer::compare).get() + 1;
+    }
 }
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 d9ee081..813597a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt
@@ -28,12 +28,10 @@
 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.FakeKeyguardBouncerRepository
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
 import com.android.systemui.shade.ShadeFoldAnimator
 import com.android.systemui.shade.ShadeViewController
-import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.statusbar.LightRevealScrim
 import com.android.systemui.statusbar.phone.CentralSurfaces
 import com.android.systemui.unfold.util.FoldableDeviceStates
@@ -80,8 +78,6 @@
 
     @Mock lateinit var viewTreeObserver: ViewTreeObserver
 
-    @Mock private lateinit var commandQueue: CommandQueue
-
     @Mock lateinit var shadeFoldAnimator: ShadeFoldAnimator
 
     @Captor private lateinit var foldStateListenerCaptor: ArgumentCaptor<FoldStateListener>
@@ -111,15 +107,10 @@
             onActionStarted.run()
         }
 
-        keyguardRepository = FakeKeyguardRepository()
         val featureFlags = FakeFeatureFlags().apply { set(FACE_AUTH_REFACTOR, true) }
-        val keyguardInteractor =
-            KeyguardInteractor(
-                repository = keyguardRepository,
-                commandQueue = commandQueue,
-                featureFlags = featureFlags,
-                bouncerRepository = FakeKeyguardBouncerRepository(),
-            )
+        val withDeps = KeyguardInteractorFactory.create(featureFlags = featureFlags)
+        val keyguardInteractor = withDeps.keyguardInteractor
+        keyguardRepository = withDeps.repository
 
         // Needs to be run on the main thread
         runBlocking(IMMEDIATE) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/usb/UsbPermissionActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/usb/UsbPermissionActivityTest.kt
index b30c20d..b04eb01 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/usb/UsbPermissionActivityTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/usb/UsbPermissionActivityTest.kt
@@ -25,8 +25,8 @@
 import android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS
 import androidx.test.filters.SmallTest
 import androidx.test.rule.ActivityTestRule
-import androidx.test.runner.intercepting.SingleActivityFactory
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.activity.SingleActivityFactory
 import com.google.common.truth.Truth.assertThat
 
 import javax.inject.Inject
@@ -49,19 +49,17 @@
 
     open class UsbPermissionActivityTestable @Inject constructor (
         val message: UsbAudioWarningDialogMessage
-    )
-        : UsbPermissionActivity(UsbAudioWarningDialogMessage())
+    ) : UsbPermissionActivity(UsbAudioWarningDialogMessage())
 
     @Rule
     @JvmField
-    var activityRule = ActivityTestRule<UsbPermissionActivityTestable>(
-            object : SingleActivityFactory<UsbPermissionActivityTestable>(
-                    UsbPermissionActivityTestable::class.java
-            ) {
-                    override fun create(intent: Intent?): UsbPermissionActivityTestable {
-                        return UsbPermissionActivityTestable(mMessage)
-                    }
-            }, false, false)
+    var activityRule = ActivityTestRule(
+            /* activityFactory= */ SingleActivityFactory {
+                UsbPermissionActivityTestable(mMessage)
+            },
+            /* initialTouchMode= */ false,
+            /* launchActivity= */ false,
+    )
 
     private val activityIntent = Intent(mContext, UsbPermissionActivityTestable::class.java)
             .apply {
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 079fbcd..0c28cbb 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
@@ -26,6 +26,8 @@
 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
 import com.android.systemui.user.data.model.UserSwitcherSettingsModel
 import com.android.systemui.util.settings.FakeSettings
 import com.google.common.truth.Truth.assertThat
@@ -224,6 +226,40 @@
     }
 
     @Test
+    fun userTrackerCallback_updatesSelectionStatus() = runSelfCancelingTest {
+        underTest = create(this)
+        var selectedUser: SelectedUserModel? = null
+        underTest.selectedUser.onEach { selectedUser = it }.launchIn(this)
+        setUpUsers(count = 2, selectedIndex = 1)
+
+        // WHEN the user is changing
+        tracker.onUserChanging(userId = 1)
+
+        // THEN the selection status is IN_PROGRESS
+        assertThat(selectedUser!!.selectionStatus).isEqualTo(SelectionStatus.SELECTION_IN_PROGRESS)
+
+        // WHEN the user has finished changing
+        tracker.onUserChanged(userId = 1)
+
+        // THEN the selection status is COMPLETE
+        assertThat(selectedUser!!.selectionStatus).isEqualTo(SelectionStatus.SELECTION_COMPLETE)
+
+        tracker.onProfileChanged()
+        assertThat(selectedUser!!.selectionStatus).isEqualTo(SelectionStatus.SELECTION_COMPLETE)
+
+        setUpUsers(count = 2, selectedIndex = 0)
+
+        tracker.onUserChanging(userId = 0)
+        assertThat(selectedUser!!.selectionStatus).isEqualTo(SelectionStatus.SELECTION_IN_PROGRESS)
+
+        // WHEN a profile change occurs while a user is changing
+        tracker.onProfileChanged()
+
+        // THEN the selection status remains as IN_PROGRESS
+        assertThat(selectedUser!!.selectionStatus).isEqualTo(SelectionStatus.SELECTION_IN_PROGRESS)
+    }
+
+    @Test
     fun userSwitchingInProgress_registersUserTrackerCallback() = runSelfCancelingTest {
         underTest = create(this)
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
index ca83d49..5b418ed 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
@@ -19,10 +19,12 @@
 
 import android.app.ActivityManager
 import android.app.admin.DevicePolicyManager
+import android.content.Context
 import android.content.Intent
 import android.content.pm.UserInfo
 import android.graphics.Bitmap
 import android.graphics.drawable.Drawable
+import android.os.Process
 import android.os.UserHandle
 import android.os.UserManager
 import android.provider.Settings
@@ -39,12 +41,10 @@
 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.FakeKeyguardBouncerRepository
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.qs.user.UserSwitchDialogController
-import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.statusbar.policy.DeviceProvisionedController
 import com.android.systemui.telephony.data.repository.FakeTelephonyRepository
 import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
@@ -64,7 +64,9 @@
 import junit.framework.Assert.assertNotNull
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
 import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestDispatcher
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
@@ -78,6 +80,7 @@
 import org.mockito.Mock
 import org.mockito.Mockito.atLeastOnce
 import org.mockito.Mockito.never
+import org.mockito.Mockito.spy
 import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
@@ -97,16 +100,19 @@
     @Mock private lateinit var dialogShower: UserSwitchDialogController.DialogShower
     @Mock private lateinit var resumeSessionReceiver: GuestResumeSessionReceiver
     @Mock private lateinit var resetOrExitSessionReceiver: GuestResetOrExitSessionReceiver
-    @Mock private lateinit var commandQueue: CommandQueue
     @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
 
     private lateinit var underTest: UserInteractor
 
+    private lateinit var spyContext: Context
     private lateinit var testScope: TestScope
     private lateinit var userRepository: FakeUserRepository
+    private lateinit var keyguardReply: KeyguardInteractorFactory.WithDependencies
     private lateinit var keyguardRepository: FakeKeyguardRepository
     private lateinit var telephonyRepository: FakeTelephonyRepository
+    private lateinit var testDispatcher: TestDispatcher
     private lateinit var featureFlags: FakeFeatureFlags
+    private lateinit var refreshUsersScheduler: RefreshUsersScheduler
 
     @Before
     fun setUp() {
@@ -126,63 +132,36 @@
                 set(Flags.FULL_SCREEN_USER_SWITCHER, false)
                 set(Flags.FACE_AUTH_REFACTOR, true)
             }
+        spyContext = spy(context)
+        keyguardReply = KeyguardInteractorFactory.create(featureFlags = featureFlags)
+        keyguardRepository = keyguardReply.repository
         userRepository = FakeUserRepository()
-        keyguardRepository = FakeKeyguardRepository()
         telephonyRepository = FakeTelephonyRepository()
-        val testDispatcher = StandardTestDispatcher()
+        testDispatcher = StandardTestDispatcher()
         testScope = TestScope(testDispatcher)
-        val refreshUsersScheduler =
+        refreshUsersScheduler =
             RefreshUsersScheduler(
                 applicationScope = testScope.backgroundScope,
                 mainDispatcher = testDispatcher,
                 repository = userRepository,
             )
-        underTest =
-            UserInteractor(
-                applicationContext = context,
-                repository = userRepository,
-                activityStarter = activityStarter,
-                keyguardInteractor =
-                    KeyguardInteractor(
-                        repository = keyguardRepository,
-                        commandQueue = commandQueue,
-                        featureFlags = featureFlags,
-                        bouncerRepository = FakeKeyguardBouncerRepository(),
-                    ),
-                manager = manager,
-                headlessSystemUserMode = headlessSystemUserMode,
-                applicationScope = testScope.backgroundScope,
-                telephonyInteractor =
-                    TelephonyInteractor(
-                        repository = telephonyRepository,
-                    ),
-                broadcastDispatcher = fakeBroadcastDispatcher,
-                keyguardUpdateMonitor = keyguardUpdateMonitor,
-                backgroundDispatcher = testDispatcher,
-                activityManager = activityManager,
-                refreshUsersScheduler = refreshUsersScheduler,
-                guestUserInteractor =
-                    GuestUserInteractor(
-                        applicationContext = context,
-                        applicationScope = testScope.backgroundScope,
-                        mainDispatcher = testDispatcher,
-                        backgroundDispatcher = testDispatcher,
-                        manager = manager,
-                        repository = userRepository,
-                        deviceProvisionedController = deviceProvisionedController,
-                        devicePolicyManager = devicePolicyManager,
-                        refreshUsersScheduler = refreshUsersScheduler,
-                        uiEventLogger = uiEventLogger,
-                        resumeSessionReceiver = resumeSessionReceiver,
-                        resetOrExitSessionReceiver = resetOrExitSessionReceiver,
-                    ),
-                uiEventLogger = uiEventLogger,
-                featureFlags = featureFlags,
-            )
     }
 
     @Test
-    fun testKeyguardUpdateMonitor_onKeyguardGoingAway() =
+    fun createUserInteractor_processUser_noSecondaryService() {
+        createUserInteractor()
+        verify(spyContext, never()).startServiceAsUser(any(), any())
+    }
+
+    @Test
+    fun createUserInteractor_nonProcessUser_startsSecondaryService() {
+        createUserInteractor(false /* startAsProcessUser */)
+        verify(spyContext).startServiceAsUser(any(), any())
+    }
+
+    @Test
+    fun testKeyguardUpdateMonitor_onKeyguardGoingAway() {
+        createUserInteractor()
         testScope.runTest {
             val argumentCaptor = ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback::class.java)
             verify(keyguardUpdateMonitor).registerCallback(argumentCaptor.capture())
@@ -192,9 +171,11 @@
             val lastValue = collectLastValue(underTest.dialogDismissRequests)
             assertNotNull(lastValue)
         }
+    }
 
     @Test
-    fun onRecordSelected_user() =
+    fun onRecordSelected_user() {
+        createUserInteractor()
         testScope.runTest {
             val userInfos = createUserInfos(count = 3, includeGuest = false)
             userRepository.setUserInfos(userInfos)
@@ -209,9 +190,11 @@
             verify(activityManager).switchUser(userInfos[1].id)
             Unit
         }
+    }
 
     @Test
-    fun onRecordSelected_switchToGuestUser() =
+    fun onRecordSelected_switchToGuestUser() {
+        createUserInteractor()
         testScope.runTest {
             val userInfos = createUserInfos(count = 3, includeGuest = true)
             userRepository.setUserInfos(userInfos)
@@ -225,9 +208,11 @@
             verify(activityManager).switchUser(userInfos.last().id)
             Unit
         }
+    }
 
     @Test
-    fun onRecordSelected_switchToRestrictedUser() =
+    fun onRecordSelected_switchToRestrictedUser() {
+        createUserInteractor()
         testScope.runTest {
             var userInfos = createUserInfos(count = 2, includeGuest = false).toMutableList()
             userInfos.add(
@@ -250,9 +235,11 @@
             verify(activityManager).switchUser(userInfos.last().id)
             Unit
         }
+    }
 
     @Test
-    fun onRecordSelected_enterGuestMode() =
+    fun onRecordSelected_enterGuestMode() {
+        createUserInteractor()
         testScope.runTest {
             val userInfos = createUserInfos(count = 3, includeGuest = false)
             userRepository.setUserInfos(userInfos)
@@ -270,9 +257,11 @@
             verify(manager).createGuest(any())
             Unit
         }
+    }
 
     @Test
-    fun onRecordSelected_action() =
+    fun onRecordSelected_action() {
+        createUserInteractor()
         testScope.runTest {
             val userInfos = createUserInfos(count = 3, includeGuest = true)
             userRepository.setUserInfos(userInfos)
@@ -286,9 +275,11 @@
             verify(dialogShower, never()).dismiss()
             verify(activityStarter).startActivity(any(), anyBoolean())
         }
+    }
 
     @Test
-    fun users_switcherEnabled() =
+    fun users_switcherEnabled() {
+        createUserInteractor()
         testScope.runTest {
             val userInfos = createUserInfos(count = 3, includeGuest = true)
             userRepository.setUserInfos(userInfos)
@@ -299,9 +290,11 @@
 
             assertUsers(models = value(), count = 3, includeGuest = true)
         }
+    }
 
     @Test
-    fun users_switchesToSecondUser() =
+    fun users_switchesToSecondUser() {
+        createUserInteractor()
         testScope.runTest {
             val userInfos = createUserInfos(count = 2, includeGuest = false)
             userRepository.setUserInfos(userInfos)
@@ -313,9 +306,11 @@
 
             assertUsers(models = value(), count = 2, selectedIndex = 1)
         }
+    }
 
     @Test
-    fun users_switcherNotEnabled() =
+    fun users_switcherNotEnabled() {
+        createUserInteractor()
         testScope.runTest {
             val userInfos = createUserInfos(count = 2, includeGuest = false)
             userRepository.setUserInfos(userInfos)
@@ -325,9 +320,11 @@
             val value = collectLastValue(underTest.users)
             assertUsers(models = value(), count = 1)
         }
+    }
 
     @Test
-    fun selectedUser() =
+    fun selectedUser() {
+        createUserInteractor()
         testScope.runTest {
             val userInfos = createUserInfos(count = 2, includeGuest = false)
             userRepository.setUserInfos(userInfos)
@@ -340,9 +337,11 @@
             userRepository.setSelectedUserInfo(userInfos[1])
             assertUser(value(), id = 1, isSelected = true)
         }
+    }
 
     @Test
-    fun actions_deviceUnlocked() =
+    fun actions_deviceUnlocked() {
+        createUserInteractor()
         testScope.runTest {
             val userInfos = createUserInfos(count = 2, includeGuest = false)
 
@@ -364,9 +363,11 @@
                     )
                 )
         }
+    }
 
     @Test
-    fun actions_deviceUnlocked_fullScreen() =
+    fun actions_deviceUnlocked_fullScreen() {
+        createUserInteractor()
         testScope.runTest {
             featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
             val userInfos = createUserInfos(count = 2, includeGuest = false)
@@ -387,9 +388,11 @@
                     )
                 )
         }
+    }
 
     @Test
-    fun actions_deviceUnlockedUserNotPrimary_emptyList() =
+    fun actions_deviceUnlockedUserNotPrimary_emptyList() {
+        createUserInteractor()
         testScope.runTest {
             val userInfos = createUserInfos(count = 2, includeGuest = false)
             userRepository.setUserInfos(userInfos)
@@ -400,9 +403,11 @@
 
             assertThat(value()).isEqualTo(emptyList<UserActionModel>())
         }
+    }
 
     @Test
-    fun actions_deviceUnlockedUserIsGuest_emptyList() =
+    fun actions_deviceUnlockedUserIsGuest_emptyList() {
+        createUserInteractor()
         testScope.runTest {
             val userInfos = createUserInfos(count = 2, includeGuest = true)
             assertThat(userInfos[1].isGuest).isTrue()
@@ -414,9 +419,11 @@
 
             assertThat(value()).isEqualTo(emptyList<UserActionModel>())
         }
+    }
 
     @Test
-    fun actions_deviceLockedAddFromLockscreenSet_fullList() =
+    fun actions_deviceLockedAddFromLockscreenSet_fullList() {
+        createUserInteractor()
         testScope.runTest {
             val userInfos = createUserInfos(count = 2, includeGuest = false)
             userRepository.setUserInfos(userInfos)
@@ -440,9 +447,11 @@
                     )
                 )
         }
+    }
 
     @Test
-    fun actions_deviceLockedAddFromLockscreenSet_fullList_fullScreen() =
+    fun actions_deviceLockedAddFromLockscreenSet_fullList_fullScreen() {
+        createUserInteractor()
         testScope.runTest {
             featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
             val userInfos = createUserInfos(count = 2, includeGuest = false)
@@ -467,9 +476,11 @@
                     )
                 )
         }
+    }
 
     @Test
-    fun actions_deviceLocked_onlymanageUserIsShown() =
+    fun actions_deviceLocked_onlymanageUserIsShown() {
+        createUserInteractor()
         testScope.runTest {
             val userInfos = createUserInfos(count = 2, includeGuest = false)
             userRepository.setUserInfos(userInfos)
@@ -480,9 +491,11 @@
 
             assertThat(value()).isEqualTo(listOf(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT))
         }
+    }
 
     @Test
-    fun executeAction_addUser_dismissesDialogAndStartsActivity() =
+    fun executeAction_addUser_dismissesDialogAndStartsActivity() {
+        createUserInteractor()
         testScope.runTest {
             val userInfos = createUserInfos(count = 2, includeGuest = false)
             userRepository.setUserInfos(userInfos)
@@ -494,9 +507,11 @@
                 .log(MultiUserActionsEvent.CREATE_USER_FROM_USER_SWITCHER)
             underTest.onDialogShown()
         }
+    }
 
     @Test
-    fun executeAction_addSupervisedUser_dismissesDialogAndStartsActivity() =
+    fun executeAction_addSupervisedUser_dismissesDialogAndStartsActivity() {
+        createUserInteractor()
         testScope.runTest {
             underTest.executeAction(UserActionModel.ADD_SUPERVISED_USER)
 
@@ -508,9 +523,11 @@
                 .isEqualTo(UserManager.ACTION_CREATE_SUPERVISED_USER)
             assertThat(intentCaptor.value.`package`).isEqualTo(SUPERVISED_USER_CREATION_APP_PACKAGE)
         }
+    }
 
     @Test
-    fun executeAction_navigateToManageUsers() =
+    fun executeAction_navigateToManageUsers() {
+        createUserInteractor()
         testScope.runTest {
             underTest.executeAction(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT)
 
@@ -518,9 +535,11 @@
             verify(activityStarter).startActivity(intentCaptor.capture(), eq(true))
             assertThat(intentCaptor.value.action).isEqualTo(Settings.ACTION_USER_SETTINGS)
         }
+    }
 
     @Test
-    fun executeAction_guestMode() =
+    fun executeAction_guestMode() {
+        createUserInteractor()
         testScope.runTest {
             val userInfos = createUserInfos(count = 2, includeGuest = false)
             userRepository.setUserInfos(userInfos)
@@ -556,9 +575,11 @@
                 )
             verify(activityManager).switchUser(guestUserInfo.id)
         }
+    }
 
     @Test
-    fun selectUser_alreadySelectedGuestReSelected_exitGuestDialog() =
+    fun selectUser_alreadySelectedGuestReSelected_exitGuestDialog() {
+        createUserInteractor()
         testScope.runTest {
             val userInfos = createUserInfos(count = 2, includeGuest = true)
             val guestUserInfo = userInfos[1]
@@ -577,9 +598,11 @@
                 .isInstanceOf(ShowDialogRequestModel.ShowExitGuestDialog::class.java)
             verify(dialogShower, never()).dismiss()
         }
+    }
 
     @Test
-    fun selectUser_currentlyGuestNonGuestSelected_exitGuestDialog() =
+    fun selectUser_currentlyGuestNonGuestSelected_exitGuestDialog() {
+        createUserInteractor()
         testScope.runTest {
             val userInfos = createUserInfos(count = 2, includeGuest = true)
             val guestUserInfo = userInfos[1]
@@ -595,9 +618,11 @@
                 .isInstanceOf(ShowDialogRequestModel.ShowExitGuestDialog::class.java)
             verify(dialogShower, never()).dismiss()
         }
+    }
 
     @Test
-    fun selectUser_notCurrentlyGuest_switchesUsers() =
+    fun selectUser_notCurrentlyGuest_switchesUsers() {
+        createUserInteractor()
         testScope.runTest {
             val userInfos = createUserInfos(count = 2, includeGuest = false)
             userRepository.setUserInfos(userInfos)
@@ -611,9 +636,11 @@
             verify(activityManager).switchUser(userInfos[1].id)
             verify(dialogShower).dismiss()
         }
+    }
 
     @Test
-    fun telephonyCallStateChanges_refreshesUsers() =
+    fun telephonyCallStateChanges_refreshesUsers() {
+        createUserInteractor()
         testScope.runTest {
             runCurrent()
 
@@ -624,9 +651,11 @@
 
             assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount + 1)
         }
+    }
 
     @Test
-    fun userSwitchedBroadcast() =
+    fun userSwitchedBroadcast() {
+        createUserInteractor()
         testScope.runTest {
             val userInfos = createUserInfos(count = 2, includeGuest = false)
             userRepository.setUserInfos(userInfos)
@@ -641,81 +670,82 @@
 
             userRepository.setSelectedUserInfo(userInfos[1])
             runCurrent()
-            fakeBroadcastDispatcher.registeredReceivers.forEach {
-                it.onReceive(
-                    context,
-                    Intent(Intent.ACTION_USER_SWITCHED)
-                        .putExtra(Intent.EXTRA_USER_HANDLE, userInfos[1].id),
-                )
-            }
+            fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+                spyContext,
+                Intent(Intent.ACTION_USER_SWITCHED)
+                    .putExtra(Intent.EXTRA_USER_HANDLE, userInfos[1].id),
+            )
             runCurrent()
 
             verify(callback1, atLeastOnce()).onUserStateChanged()
             verify(callback2, atLeastOnce()).onUserStateChanged()
             assertThat(userRepository.secondaryUserId).isEqualTo(userInfos[1].id)
             assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount + 1)
+            verify(spyContext).startServiceAsUser(any(), eq(UserHandle.of(userInfos[1].id)))
         }
+    }
 
     @Test
-    fun userInfoChangedBroadcast() =
+    fun userInfoChangedBroadcast() {
+        createUserInteractor()
         testScope.runTest {
             val userInfos = createUserInfos(count = 2, includeGuest = false)
             userRepository.setUserInfos(userInfos)
             userRepository.setSelectedUserInfo(userInfos[0])
             val refreshUsersCallCount = userRepository.refreshUsersCallCount
 
-            fakeBroadcastDispatcher.registeredReceivers.forEach {
-                it.onReceive(
-                    context,
-                    Intent(Intent.ACTION_USER_INFO_CHANGED),
-                )
-            }
+            fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+                spyContext,
+                Intent(Intent.ACTION_USER_INFO_CHANGED),
+            )
 
             runCurrent()
 
             assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount + 1)
         }
+    }
 
     @Test
-    fun systemUserUnlockedBroadcast_refreshUsers() =
+    fun systemUserUnlockedBroadcast_refreshUsers() {
+        createUserInteractor()
         testScope.runTest {
             val userInfos = createUserInfos(count = 2, includeGuest = false)
             userRepository.setUserInfos(userInfos)
             userRepository.setSelectedUserInfo(userInfos[0])
             val refreshUsersCallCount = userRepository.refreshUsersCallCount
 
-            fakeBroadcastDispatcher.registeredReceivers.forEach {
-                it.onReceive(
-                    context,
-                    Intent(Intent.ACTION_USER_UNLOCKED)
-                        .putExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_SYSTEM),
-                )
-            }
+            fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+                spyContext,
+                Intent(Intent.ACTION_USER_UNLOCKED)
+                    .putExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_SYSTEM),
+            )
             runCurrent()
 
             assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount + 1)
         }
+    }
 
     @Test
-    fun nonSystemUserUnlockedBroadcast_doNotRefreshUsers() =
+    fun nonSystemUserUnlockedBroadcast_doNotRefreshUsers() {
+        createUserInteractor()
         testScope.runTest {
             val userInfos = createUserInfos(count = 2, includeGuest = false)
             userRepository.setUserInfos(userInfos)
             userRepository.setSelectedUserInfo(userInfos[0])
             val refreshUsersCallCount = userRepository.refreshUsersCallCount
 
-            fakeBroadcastDispatcher.registeredReceivers.forEach {
-                it.onReceive(
-                    context,
-                    Intent(Intent.ACTION_USER_UNLOCKED).putExtra(Intent.EXTRA_USER_HANDLE, 1337),
-                )
-            }
+            fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+                spyContext,
+                Intent(Intent.ACTION_USER_UNLOCKED).putExtra(Intent.EXTRA_USER_HANDLE, 1337),
+            )
 
             assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount)
         }
+    }
 
     @Test
-    fun userRecords() =
+    fun userRecords() {
+        createUserInteractor()
         testScope.runTest {
             val userInfos = createUserInfos(count = 3, includeGuest = false)
             userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
@@ -739,9 +769,11 @@
                     ),
             )
         }
+    }
 
     @Test
-    fun userRecordsFullScreen() =
+    fun userRecordsFullScreen() {
+        createUserInteractor()
         testScope.runTest {
             featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
             val userInfos = createUserInfos(count = 3, includeGuest = false)
@@ -766,9 +798,11 @@
                     ),
             )
         }
+    }
 
     @Test
-    fun selectedUserRecord() =
+    fun selectedUserRecord() {
+        createUserInteractor()
         testScope.runTest {
             val userInfos = createUserInfos(count = 3, includeGuest = true)
             userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
@@ -784,9 +818,11 @@
                 isSwitchToEnabled = true,
             )
         }
+    }
 
     @Test
-    fun users_secondaryUser_guestUserCanBeSwitchedTo() =
+    fun users_secondaryUser_guestUserCanBeSwitchedTo() {
+        createUserInteractor()
         testScope.runTest {
             val userInfos = createUserInfos(count = 3, includeGuest = true)
             userRepository.setUserInfos(userInfos)
@@ -797,9 +833,11 @@
             assertThat(res()?.size == 3).isTrue()
             assertThat(res()?.find { it.isGuest }).isNotNull()
         }
+    }
 
     @Test
-    fun users_secondaryUser_noGuestAction() =
+    fun users_secondaryUser_noGuestAction() {
+        createUserInteractor()
         testScope.runTest {
             val userInfos = createUserInfos(count = 3, includeGuest = true)
             userRepository.setUserInfos(userInfos)
@@ -809,9 +847,11 @@
             val res = collectLastValue(underTest.actions)
             assertThat(res()?.find { it == UserActionModel.ENTER_GUEST_MODE }).isNull()
         }
+    }
 
     @Test
-    fun users_secondaryUser_noGuestUserRecord() =
+    fun users_secondaryUser_noGuestUserRecord() {
+        createUserInteractor()
         testScope.runTest {
             val userInfos = createUserInfos(count = 3, includeGuest = true)
             userRepository.setUserInfos(userInfos)
@@ -820,9 +860,11 @@
 
             assertThat(underTest.userRecords.value.find { it.isGuest }).isNull()
         }
+    }
 
     @Test
-    fun showUserSwitcher_fullScreenDisabled_showsDialogSwitcher() =
+    fun showUserSwitcher_fullScreenDisabled_showsDialogSwitcher() {
+        createUserInteractor()
         testScope.runTest {
             val expandable = mock<Expandable>()
             underTest.showUserSwitcher(expandable)
@@ -836,9 +878,11 @@
             underTest.onDialogShown()
             assertThat(dialogRequest()).isNull()
         }
+    }
 
     @Test
-    fun showUserSwitcher_fullScreenEnabled_launchesFullScreenDialog() =
+    fun showUserSwitcher_fullScreenEnabled_launchesFullScreenDialog() {
+        createUserInteractor()
         testScope.runTest {
             featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
 
@@ -854,9 +898,11 @@
             underTest.onDialogShown()
             assertThat(dialogRequest()).isNull()
         }
+    }
 
     @Test
-    fun users_secondaryUser_managedProfileIsNotIncluded() =
+    fun users_secondaryUser_managedProfileIsNotIncluded() {
+        createUserInteractor()
         testScope.runTest {
             val userInfos = createUserInfos(count = 3, includeGuest = false).toMutableList()
             userInfos.add(
@@ -874,9 +920,11 @@
             val res = collectLastValue(underTest.users)
             assertThat(res()?.size == 3).isTrue()
         }
+    }
 
     @Test
-    fun currentUserIsNotPrimaryAndUserSwitcherIsDisabled() =
+    fun currentUserIsNotPrimaryAndUserSwitcherIsDisabled() {
+        createUserInteractor()
         testScope.runTest {
             val userInfos = createUserInfos(count = 2, includeGuest = false)
             userRepository.setUserInfos(userInfos)
@@ -885,9 +933,11 @@
             val selectedUser = collectLastValue(underTest.selectedUser)
             assertThat(selectedUser()).isNotNull()
         }
+    }
 
     @Test
-    fun userRecords_isActionAndNoUsersUnlocked_actionIsDisabled() =
+    fun userRecords_isActionAndNoUsersUnlocked_actionIsDisabled() {
+        createUserInteractor()
         testScope.runTest {
             keyguardRepository.setKeyguardShowing(true)
             whenever(manager.getUserSwitchability(any()))
@@ -907,9 +957,11 @@
                 .filter { it.info == null }
                 .forEach { action -> assertThat(action.isSwitchToEnabled).isFalse() }
         }
+    }
 
     @Test
-    fun userRecords_isActionAndNoUsersUnlocked_actionIsDisabled_HeadlessMode() =
+    fun userRecords_isActionAndNoUsersUnlocked_actionIsDisabled_HeadlessMode() {
+        createUserInteractor()
         testScope.runTest {
             keyguardRepository.setKeyguardShowing(true)
             whenever(headlessSystemUserMode.isHeadlessSystemUserMode()).thenReturn(true)
@@ -929,6 +981,7 @@
                 .filter { it.info == null }
                 .forEach { action -> assertThat(action.isSwitchToEnabled).isFalse() }
         }
+    }
 
     private fun assertUsers(
         models: List<UserModel>?,
@@ -1021,6 +1074,53 @@
             .isEqualTo(type == UserActionModel.ADD_SUPERVISED_USER)
     }
 
+    private fun createUserInteractor(startAsProcessUser: Boolean = true) {
+        val processUserId = Process.myUserHandle().identifier
+        val startUserId = if (startAsProcessUser) processUserId else (processUserId + 1)
+        runBlocking {
+            val userInfo =
+                createUserInfo(id = startUserId, name = "user_$startUserId", isPrimary = true)
+            userRepository.setUserInfos(listOf(userInfo))
+            userRepository.setSelectedUserInfo(userInfo)
+        }
+        underTest =
+            UserInteractor(
+                applicationContext = spyContext,
+                repository = userRepository,
+                activityStarter = activityStarter,
+                keyguardInteractor = keyguardReply.keyguardInteractor,
+                manager = manager,
+                headlessSystemUserMode = headlessSystemUserMode,
+                applicationScope = testScope.backgroundScope,
+                telephonyInteractor =
+                    TelephonyInteractor(
+                        repository = telephonyRepository,
+                    ),
+                broadcastDispatcher = fakeBroadcastDispatcher,
+                keyguardUpdateMonitor = keyguardUpdateMonitor,
+                backgroundDispatcher = testDispatcher,
+                activityManager = activityManager,
+                refreshUsersScheduler = refreshUsersScheduler,
+                guestUserInteractor =
+                    GuestUserInteractor(
+                        applicationContext = spyContext,
+                        applicationScope = testScope.backgroundScope,
+                        mainDispatcher = testDispatcher,
+                        backgroundDispatcher = testDispatcher,
+                        manager = manager,
+                        repository = userRepository,
+                        deviceProvisionedController = deviceProvisionedController,
+                        devicePolicyManager = devicePolicyManager,
+                        refreshUsersScheduler = refreshUsersScheduler,
+                        uiEventLogger = uiEventLogger,
+                        resumeSessionReceiver = resumeSessionReceiver,
+                        resetOrExitSessionReceiver = resetOrExitSessionReceiver,
+                    ),
+                uiEventLogger = uiEventLogger,
+                featureFlags = featureFlags,
+            )
+    }
+
     private fun createUserInfos(
         count: Int,
         includeGuest: Boolean,
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 fd8c6c7..2433e12 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
@@ -32,11 +32,8 @@
 import com.android.systemui.common.shared.model.Text
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags
-import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
 import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.statusbar.policy.DeviceProvisionedController
 import com.android.systemui.telephony.data.repository.FakeTelephonyRepository
 import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
@@ -53,6 +50,7 @@
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.flow.toList
 import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.UnconfinedTestDispatcher
 import kotlinx.coroutines.test.advanceUntilIdle
@@ -80,13 +78,11 @@
     @Mock private lateinit var uiEventLogger: UiEventLogger
     @Mock private lateinit var resumeSessionReceiver: GuestResumeSessionReceiver
     @Mock private lateinit var resetOrExitSessionReceiver: GuestResetOrExitSessionReceiver
-    @Mock private lateinit var commandQueue: CommandQueue
     @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
 
     private lateinit var underTest: StatusBarUserChipViewModel
 
     private val userRepository = FakeUserRepository()
-    private val keyguardRepository = FakeKeyguardRepository()
     private lateinit var guestUserInteractor: GuestUserInteractor
     private lateinit var refreshUsersScheduler: RefreshUsersScheduler
 
@@ -242,6 +238,10 @@
                 set(Flags.FULL_SCREEN_USER_SWITCHER, false)
                 set(Flags.FACE_AUTH_REFACTOR, true)
             }
+        runBlocking {
+            userRepository.setUserInfos(listOf(USER_0))
+            userRepository.setSelectedUserInfo(USER_0)
+        }
         return StatusBarUserChipViewModel(
             context = context,
             interactor =
@@ -250,12 +250,8 @@
                     repository = userRepository,
                     activityStarter = activityStarter,
                     keyguardInteractor =
-                        KeyguardInteractor(
-                            repository = keyguardRepository,
-                            commandQueue = commandQueue,
-                            featureFlags = featureFlags,
-                            bouncerRepository = FakeKeyguardBouncerRepository(),
-                        ),
+                        KeyguardInteractorFactory.create(featureFlags = featureFlags)
+                            .keyguardInteractor,
                     featureFlags = featureFlags,
                     manager = manager,
                     headlessSystemUserMode = headlessSystemUserMode,
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 9155084..8c88f95 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
@@ -30,11 +30,9 @@
 import com.android.systemui.common.shared.model.Text
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags
-import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
 import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.statusbar.policy.DeviceProvisionedController
 import com.android.systemui.telephony.data.repository.FakeTelephonyRepository
 import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
@@ -79,7 +77,6 @@
     @Mock private lateinit var uiEventLogger: UiEventLogger
     @Mock private lateinit var resumeSessionReceiver: GuestResumeSessionReceiver
     @Mock private lateinit var resetOrExitSessionReceiver: GuestResetOrExitSessionReceiver
-    @Mock private lateinit var commandQueue: CommandQueue
     @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
 
     private lateinit var underTest: UserSwitcherViewModel
@@ -105,6 +102,20 @@
         testScope = TestScope(testDispatcher)
         userRepository = FakeUserRepository()
         runBlocking {
+            val userInfos =
+                listOf(
+                    UserInfo(
+                        /* id= */ 0,
+                        /* name= */ "zero",
+                        /* iconPath= */ "",
+                        /* flags= */ UserInfo.FLAG_PRIMARY or
+                            UserInfo.FLAG_ADMIN or
+                            UserInfo.FLAG_FULL,
+                        UserManager.USER_TYPE_FULL_SYSTEM,
+                    ),
+                )
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
             userRepository.setSettings(
                 UserSwitcherSettingsModel(
                     isUserSwitcherEnabled = true,
@@ -112,7 +123,6 @@
             )
         }
 
-        keyguardRepository = FakeKeyguardRepository()
         val refreshUsersScheduler =
             RefreshUsersScheduler(
                 applicationScope = testScope.backgroundScope,
@@ -140,38 +150,35 @@
                 set(Flags.FULL_SCREEN_USER_SWITCHER, false)
                 set(Flags.FACE_AUTH_REFACTOR, true)
             }
+        val reply = KeyguardInteractorFactory.create(featureFlags = featureFlags)
+        keyguardRepository = reply.repository
+
         underTest =
             UserSwitcherViewModel(
-                    userInteractor =
-                        UserInteractor(
-                            applicationContext = context,
-                            repository = userRepository,
-                            activityStarter = activityStarter,
-                            keyguardInteractor =
-                                KeyguardInteractor(
-                                    repository = keyguardRepository,
-                                    commandQueue = commandQueue,
-                                    featureFlags = featureFlags,
-                                    bouncerRepository = FakeKeyguardBouncerRepository(),
-                                ),
-                            featureFlags = featureFlags,
-                            manager = manager,
-                            headlessSystemUserMode = headlessSystemUserMode,
-                            applicationScope = testScope.backgroundScope,
-                            telephonyInteractor =
-                                TelephonyInteractor(
-                                    repository = FakeTelephonyRepository(),
-                                ),
-                            broadcastDispatcher = fakeBroadcastDispatcher,
-                            keyguardUpdateMonitor = keyguardUpdateMonitor,
-                            backgroundDispatcher = testDispatcher,
-                            activityManager = activityManager,
-                            refreshUsersScheduler = refreshUsersScheduler,
-                            guestUserInteractor = guestUserInteractor,
-                            uiEventLogger = uiEventLogger,
-                        ),
-                    guestUserInteractor = guestUserInteractor,
-                )
+                userInteractor =
+                    UserInteractor(
+                        applicationContext = context,
+                        repository = userRepository,
+                        activityStarter = activityStarter,
+                        keyguardInteractor = reply.keyguardInteractor,
+                        featureFlags = featureFlags,
+                        manager = manager,
+                        headlessSystemUserMode = headlessSystemUserMode,
+                        applicationScope = testScope.backgroundScope,
+                        telephonyInteractor =
+                            TelephonyInteractor(
+                                repository = FakeTelephonyRepository(),
+                            ),
+                        broadcastDispatcher = fakeBroadcastDispatcher,
+                        keyguardUpdateMonitor = keyguardUpdateMonitor,
+                        backgroundDispatcher = testDispatcher,
+                        activityManager = activityManager,
+                        refreshUsersScheduler = refreshUsersScheduler,
+                        guestUserInteractor = guestUserInteractor,
+                        uiEventLogger = uiEventLogger,
+                    ),
+                guestUserInteractor = guestUserInteractor,
+            )
     }
 
     @Test
@@ -323,7 +330,7 @@
             setUsers(count = 2)
             val isFinishRequested = mutableListOf<Boolean>()
             val job =
-                    launch(testDispatcher) { underTest.isFinishRequested.toList(isFinishRequested) }
+                launch(testDispatcher) { underTest.isFinishRequested.toList(isFinishRequested) }
             assertThat(isFinishRequested.last()).isFalse()
 
             underTest.onCancelButtonClicked()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/WallpaperControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/WallpaperControllerTest.kt
index d8e418a..b13cb72 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/WallpaperControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/WallpaperControllerTest.kt
@@ -26,6 +26,7 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.util.mockito.eq
+import com.android.systemui.wallpapers.data.repository.FakeWallpaperRepository
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
@@ -56,6 +57,7 @@
     private lateinit var viewRootImpl: ViewRootImpl
     @Mock
     private lateinit var windowToken: IBinder
+    private val wallpaperRepository = FakeWallpaperRepository()
 
     @JvmField
     @Rule
@@ -69,7 +71,7 @@
         `when`(root.windowToken).thenReturn(windowToken)
         `when`(root.isAttachedToWindow).thenReturn(true)
 
-        wallaperController = WallpaperController(wallpaperManager)
+        wallaperController = WallpaperController(wallpaperManager, wallpaperRepository)
 
         wallaperController.rootView = root
     }
@@ -90,9 +92,9 @@
 
     @Test
     fun setUnfoldTransitionZoom_defaultUnfoldTransitionIsDisabled_doesNotUpdateWallpaperZoom() {
-        wallaperController.onWallpaperInfoUpdated(createWallpaperInfo(
+        wallpaperRepository.wallpaperInfo.value = createWallpaperInfo(
             useDefaultTransition = false
-        ))
+        )
 
         wallaperController.setUnfoldTransitionZoom(0.5f)
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
index 8f725be..c819108 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.volume;
 
+import static com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION;
 import static com.android.systemui.volume.Events.DISMISS_REASON_UNKNOWN;
 import static com.android.systemui.volume.Events.SHOW_REASON_UNKNOWN;
 import static com.android.systemui.volume.VolumeDialogControllerImpl.STREAMS;
@@ -51,6 +52,7 @@
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FakeFeatureFlags;
 import com.android.systemui.media.dialog.MediaOutputDialogFactory;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.VolumeDialogController;
@@ -117,6 +119,8 @@
         }
     };
 
+    private FakeFeatureFlags mFeatureFlags;
+
     @Before
     public void setup() throws Exception {
         MockitoAnnotations.initMocks(this);
@@ -132,6 +136,8 @@
 
         mConfigurationController = new FakeConfigurationController();
 
+        mFeatureFlags = new FakeFeatureFlags();
+
         mDialog = new VolumeDialogImpl(
                 getContext(),
                 mVolumeDialogController,
@@ -142,10 +148,12 @@
                 mVolumePanelFactory,
                 mActivityStarter,
                 mInteractionJankMonitor,
+                false,
                 mCsdWarningDialogFactory,
                 mPostureController,
                 mTestableLooper.getLooper(),
-                mDumpManager);
+                mDumpManager,
+                mFeatureFlags);
         mDialog.init(0, null);
         State state = createShellState();
         mDialog.onStateChangedH(state);
@@ -253,6 +261,7 @@
 
     @Test
     public void testVibrateOnRingerChangedToVibrate() {
+        mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false);
         final State initialSilentState = new State();
         initialSilentState.ringerModeInternal = AudioManager.RINGER_MODE_SILENT;
 
@@ -273,7 +282,30 @@
     }
 
     @Test
+    public void testControllerDoesNotVibrateOnRingerChangedToVibrate_OnewayAPI_On() {
+        mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true);
+        final State initialSilentState = new State();
+        initialSilentState.ringerModeInternal = AudioManager.RINGER_MODE_SILENT;
+
+        final State vibrateState = new State();
+        vibrateState.ringerModeInternal = AudioManager.RINGER_MODE_VIBRATE;
+
+        // change ringer to silent
+        mDialog.onStateChangedH(initialSilentState);
+
+        // expected: shouldn't call vibrate yet
+        verify(mVolumeDialogController, never()).vibrate(any());
+
+        // changed ringer to vibrate
+        mDialog.onStateChangedH(vibrateState);
+
+        // expected: vibrate method of controller is not used
+        verify(mVolumeDialogController, never()).vibrate(any());
+    }
+
+    @Test
     public void testNoVibrateOnRingerInitialization() {
+        mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false);
         final State initialUnsetState = new State();
         initialUnsetState.ringerModeInternal = -1;
 
@@ -291,7 +323,42 @@
     }
 
     @Test
+    public void testControllerDoesNotVibrateOnRingerInitialization_OnewayAPI_On() {
+        mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true);
+        final State initialUnsetState = new State();
+        initialUnsetState.ringerModeInternal = -1;
+
+        // ringer not initialized yet:
+        mDialog.onStateChangedH(initialUnsetState);
+
+        final State vibrateState = new State();
+        vibrateState.ringerModeInternal = AudioManager.RINGER_MODE_VIBRATE;
+
+        // changed ringer to vibrate
+        mDialog.onStateChangedH(vibrateState);
+
+        // shouldn't call vibrate on the controller either
+        verify(mVolumeDialogController, never()).vibrate(any());
+    }
+
+    @Test
     public void testSelectVibrateFromDrawer() {
+        mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false);
+        final State initialUnsetState = new State();
+        initialUnsetState.ringerModeInternal = AudioManager.RINGER_MODE_NORMAL;
+        mDialog.onStateChangedH(initialUnsetState);
+
+        mActiveRinger.performClick();
+        mDrawerVibrate.performClick();
+
+        // Make sure we've actually changed the ringer mode.
+        verify(mVolumeDialogController, times(1)).setRingerMode(
+                AudioManager.RINGER_MODE_VIBRATE, false);
+    }
+
+    @Test
+    public void testSelectVibrateFromDrawer_OnewayAPI_On() {
+        mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true);
         final State initialUnsetState = new State();
         initialUnsetState.ringerModeInternal = AudioManager.RINGER_MODE_NORMAL;
         mDialog.onStateChangedH(initialUnsetState);
@@ -306,6 +373,22 @@
 
     @Test
     public void testSelectMuteFromDrawer() {
+        mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false);
+        final State initialUnsetState = new State();
+        initialUnsetState.ringerModeInternal = AudioManager.RINGER_MODE_NORMAL;
+        mDialog.onStateChangedH(initialUnsetState);
+
+        mActiveRinger.performClick();
+        mDrawerMute.performClick();
+
+        // Make sure we've actually changed the ringer mode.
+        verify(mVolumeDialogController, times(1)).setRingerMode(
+                AudioManager.RINGER_MODE_SILENT, false);
+    }
+
+    @Test
+    public void testSelectMuteFromDrawer_OnewayAPI_On() {
+        mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true);
         final State initialUnsetState = new State();
         initialUnsetState.ringerModeInternal = AudioManager.RINGER_MODE_NORMAL;
         mDialog.onStateChangedH(initialUnsetState);
@@ -320,6 +403,22 @@
 
     @Test
     public void testSelectNormalFromDrawer() {
+        mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false);
+        final State initialUnsetState = new State();
+        initialUnsetState.ringerModeInternal = AudioManager.RINGER_MODE_VIBRATE;
+        mDialog.onStateChangedH(initialUnsetState);
+
+        mActiveRinger.performClick();
+        mDrawerNormal.performClick();
+
+        // Make sure we've actually changed the ringer mode.
+        verify(mVolumeDialogController, times(1)).setRingerMode(
+                AudioManager.RINGER_MODE_NORMAL, false);
+    }
+
+    @Test
+    public void testSelectNormalFromDrawer_OnewayAPI_On() {
+        mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true);
         final State initialUnsetState = new State();
         initialUnsetState.ringerModeInternal = AudioManager.RINGER_MODE_VIBRATE;
         mDialog.onStateChangedH(initialUnsetState);
@@ -378,10 +477,12 @@
                 mVolumePanelFactory,
                 mActivityStarter,
                 mInteractionJankMonitor,
+                false,
                 mCsdWarningDialogFactory,
                 devicePostureController,
                 mTestableLooper.getLooper(),
-                mDumpManager
+                mDumpManager,
+                mFeatureFlags
         );
         dialog.init(0 , null);
 
@@ -397,6 +498,8 @@
 
         int gravity = dialog.getWindowGravity();
         assertEquals(Gravity.TOP, gravity & Gravity.VERTICAL_GRAVITY_MASK);
+
+        cleanUp(dialog);
     }
 
     @Test
@@ -415,10 +518,12 @@
                 mVolumePanelFactory,
                 mActivityStarter,
                 mInteractionJankMonitor,
+                false,
                 mCsdWarningDialogFactory,
                 devicePostureController,
                 mTestableLooper.getLooper(),
-                mDumpManager
+                mDumpManager,
+                mFeatureFlags
         );
         dialog.init(0, null);
 
@@ -433,6 +538,8 @@
 
         int gravity = dialog.getWindowGravity();
         assertEquals(Gravity.CENTER_VERTICAL, gravity & Gravity.VERTICAL_GRAVITY_MASK);
+
+        cleanUp(dialog);
     }
 
     @Test
@@ -451,10 +558,12 @@
                 mVolumePanelFactory,
                 mActivityStarter,
                 mInteractionJankMonitor,
+                false,
                 mCsdWarningDialogFactory,
                 devicePostureController,
                 mTestableLooper.getLooper(),
-                mDumpManager
+                mDumpManager,
+                mFeatureFlags
         );
         dialog.init(0, null);
 
@@ -469,6 +578,8 @@
 
         int gravity = dialog.getWindowGravity();
         assertEquals(Gravity.CENTER_VERTICAL, gravity & Gravity.VERTICAL_GRAVITY_MASK);
+
+        cleanUp(dialog);
     }
 
     @Test
@@ -489,18 +600,21 @@
                 mVolumePanelFactory,
                 mActivityStarter,
                 mInteractionJankMonitor,
+                false,
                 mCsdWarningDialogFactory,
                 mPostureController,
                 mTestableLooper.getLooper(),
-                mDumpManager
+                mDumpManager,
+                mFeatureFlags
         );
         dialog.init(0, null);
 
         verify(mPostureController, never()).removeCallback(any());
-
         dialog.destroy();
 
         verify(mPostureController).removeCallback(any());
+
+        cleanUp(dialog);
     }
 
     private void setOrientation(int orientation) {
@@ -513,14 +627,18 @@
 
     @After
     public void teardown() {
-        if (mDialog != null) {
-            mDialog.clearInternalHandlerAfterTest();
-        }
+        cleanUp(mDialog);
         setOrientation(mOriginalOrientation);
         mTestableLooper.processAllMessages();
         reset(mPostureController);
     }
 
+    private void cleanUp(VolumeDialogImpl dialog) {
+        if (dialog != null) {
+            dialog.clearInternalHandlerAfterTest();
+        }
+    }
+
 /*
     @Test
     public void testContentDescriptions() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/data/repository/FakeWallpaperRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/data/repository/FakeWallpaperRepository.kt
new file mode 100644
index 0000000..fe5024f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/data/repository/FakeWallpaperRepository.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.wallpapers.data.repository
+
+import android.app.WallpaperInfo
+import kotlinx.coroutines.flow.MutableStateFlow
+
+/** Fake implementation of the wallpaper repository. */
+class FakeWallpaperRepository : WallpaperRepository {
+    override val wallpaperInfo = MutableStateFlow<WallpaperInfo?>(null)
+    override val wallpaperSupportsAmbientMode = MutableStateFlow(false)
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryImplTest.kt
new file mode 100644
index 0000000..f8b096a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryImplTest.kt
@@ -0,0 +1,425 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.wallpapers.data.repository
+
+import android.app.WallpaperInfo
+import android.app.WallpaperManager
+import android.content.Intent
+import android.content.pm.UserInfo
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.user.data.model.SelectedUserModel
+import com.android.systemui.user.data.model.SelectionStatus
+import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+
+@SmallTest
+@OptIn(ExperimentalCoroutinesApi::class)
+class WallpaperRepositoryImplTest : SysuiTestCase() {
+
+    private val testScope = TestScope(StandardTestDispatcher())
+    private val userRepository = FakeUserRepository()
+    private val wallpaperManager: WallpaperManager = mock()
+
+    private val underTest: WallpaperRepositoryImpl by lazy {
+        WallpaperRepositoryImpl(
+            testScope.backgroundScope,
+            fakeBroadcastDispatcher,
+            userRepository,
+            wallpaperManager,
+            context,
+        )
+    }
+
+    @Before
+    fun setUp() {
+        whenever(wallpaperManager.isWallpaperSupported).thenReturn(true)
+        context.orCreateTestableResources.addOverride(
+            com.android.internal.R.bool.config_dozeSupportsAodWallpaper,
+            true,
+        )
+    }
+
+    @Test
+    fun wallpaperInfo_nullInfo() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.wallpaperInfo)
+
+            whenever(wallpaperManager.getWallpaperInfoForUser(any())).thenReturn(null)
+
+            fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+                context,
+                Intent(Intent.ACTION_WALLPAPER_CHANGED),
+            )
+
+            assertThat(latest).isNull()
+        }
+
+    @Test
+    fun wallpaperInfo_hasInfoFromManager() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.wallpaperInfo)
+
+            whenever(wallpaperManager.getWallpaperInfoForUser(any())).thenReturn(UNSUPPORTED_WP)
+
+            fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+                context,
+                Intent(Intent.ACTION_WALLPAPER_CHANGED),
+            )
+
+            assertThat(latest).isEqualTo(UNSUPPORTED_WP)
+        }
+
+    @Test
+    fun wallpaperInfo_initialValueIsFetched() =
+        testScope.runTest {
+            whenever(wallpaperManager.getWallpaperInfoForUser(USER_WITH_SUPPORTED_WP.id))
+                .thenReturn(SUPPORTED_WP)
+            userRepository.setUserInfos(listOf(USER_WITH_SUPPORTED_WP))
+            userRepository.setSelectedUserInfo(USER_WITH_SUPPORTED_WP)
+
+            // WHEN the repo initially starts up (underTest is lazy), then it fetches the current
+            // value for the wallpaper
+            assertThat(underTest.wallpaperInfo.value).isEqualTo(SUPPORTED_WP)
+        }
+
+    @Test
+    fun wallpaperInfo_updatesOnUserChanged() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.wallpaperInfo)
+
+            val user3 = UserInfo(/* id= */ 3, /* name= */ "user3", /* flags= */ 0)
+            val user3Wp = mock<WallpaperInfo>()
+            whenever(wallpaperManager.getWallpaperInfoForUser(user3.id)).thenReturn(user3Wp)
+
+            val user4 = UserInfo(/* id= */ 4, /* name= */ "user4", /* flags= */ 0)
+            val user4Wp = mock<WallpaperInfo>()
+            whenever(wallpaperManager.getWallpaperInfoForUser(user4.id)).thenReturn(user4Wp)
+
+            userRepository.setUserInfos(listOf(user3, user4))
+
+            // WHEN user3 is selected
+            userRepository.setSelectedUserInfo(user3)
+
+            // THEN user3's wallpaper is used
+            assertThat(latest).isEqualTo(user3Wp)
+
+            // WHEN the user is switched to user4
+            userRepository.setSelectedUserInfo(user4)
+
+            // THEN user4's wallpaper is used
+            assertThat(latest).isEqualTo(user4Wp)
+        }
+
+    @Test
+    fun wallpaperInfo_doesNotUpdateOnUserChanging() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.wallpaperInfo)
+
+            val user3 = UserInfo(/* id= */ 3, /* name= */ "user3", /* flags= */ 0)
+            val user3Wp = mock<WallpaperInfo>()
+            whenever(wallpaperManager.getWallpaperInfoForUser(user3.id)).thenReturn(user3Wp)
+
+            val user4 = UserInfo(/* id= */ 4, /* name= */ "user4", /* flags= */ 0)
+            val user4Wp = mock<WallpaperInfo>()
+            whenever(wallpaperManager.getWallpaperInfoForUser(user4.id)).thenReturn(user4Wp)
+
+            userRepository.setUserInfos(listOf(user3, user4))
+
+            // WHEN user3 is selected
+            userRepository.setSelectedUserInfo(user3)
+
+            // THEN user3's wallpaper is used
+            assertThat(latest).isEqualTo(user3Wp)
+
+            // WHEN the user has started switching to user4 but hasn't finished yet
+            userRepository.selectedUser.value =
+                SelectedUserModel(user4, SelectionStatus.SELECTION_IN_PROGRESS)
+
+            // THEN the wallpaper still matches user3
+            assertThat(latest).isEqualTo(user3Wp)
+        }
+
+    @Test
+    fun wallpaperInfo_updatesOnIntent() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.wallpaperInfo)
+
+            val wp1 = mock<WallpaperInfo>()
+            whenever(wallpaperManager.getWallpaperInfoForUser(any())).thenReturn(wp1)
+
+            assertThat(latest).isEqualTo(wp1)
+
+            // WHEN the info is new and a broadcast is sent
+            val wp2 = mock<WallpaperInfo>()
+            whenever(wallpaperManager.getWallpaperInfoForUser(any())).thenReturn(wp2)
+            fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+                context,
+                Intent(Intent.ACTION_WALLPAPER_CHANGED),
+            )
+
+            // THEN the flow updates
+            assertThat(latest).isEqualTo(wp2)
+        }
+
+    @Test
+    fun wallpaperInfo_wallpaperNotSupported_alwaysNull() =
+        testScope.runTest {
+            whenever(wallpaperManager.isWallpaperSupported).thenReturn(false)
+
+            val latest by collectLastValue(underTest.wallpaperInfo)
+            assertThat(latest).isNull()
+
+            // Even WHEN there *is* current wallpaper
+            val wp1 = mock<WallpaperInfo>()
+            whenever(wallpaperManager.getWallpaperInfoForUser(any())).thenReturn(wp1)
+            fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+                context,
+                Intent(Intent.ACTION_WALLPAPER_CHANGED),
+            )
+
+            // THEN the value is still null because wallpaper isn't supported
+            assertThat(latest).isNull()
+        }
+
+    @Test
+    fun wallpaperInfo_deviceDoesNotSupportAmbientWallpaper_alwaysFalse() =
+        testScope.runTest {
+            context.orCreateTestableResources.addOverride(
+                com.android.internal.R.bool.config_dozeSupportsAodWallpaper,
+                false
+            )
+
+            val latest by collectLastValue(underTest.wallpaperInfo)
+            assertThat(latest).isNull()
+
+            // Even WHEN there *is* current wallpaper
+            val wp1 = mock<WallpaperInfo>()
+            whenever(wallpaperManager.getWallpaperInfoForUser(any())).thenReturn(wp1)
+            fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+                context,
+                Intent(Intent.ACTION_WALLPAPER_CHANGED),
+            )
+
+            // THEN the value is still null because wallpaper isn't supported
+            assertThat(latest).isNull()
+        }
+
+    @Test
+    fun wallpaperSupportsAmbientMode_nullInfo_false() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.wallpaperSupportsAmbientMode)
+
+            whenever(wallpaperManager.getWallpaperInfoForUser(any())).thenReturn(null)
+
+            fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+                context,
+                Intent(Intent.ACTION_WALLPAPER_CHANGED),
+            )
+
+            assertThat(latest).isFalse()
+        }
+
+    @Test
+    fun wallpaperSupportsAmbientMode_infoDoesNotSupport_false() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.wallpaperSupportsAmbientMode)
+
+            whenever(wallpaperManager.getWallpaperInfoForUser(any())).thenReturn(UNSUPPORTED_WP)
+
+            fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+                context,
+                Intent(Intent.ACTION_WALLPAPER_CHANGED),
+            )
+
+            assertThat(latest).isFalse()
+        }
+
+    @Test
+    fun wallpaperSupportsAmbientMode_infoSupports_true() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.wallpaperSupportsAmbientMode)
+
+            whenever(wallpaperManager.getWallpaperInfoForUser(any())).thenReturn(SUPPORTED_WP)
+
+            fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+                context,
+                Intent(Intent.ACTION_WALLPAPER_CHANGED),
+            )
+
+            assertThat(latest).isTrue()
+        }
+
+    @Test
+    fun wallpaperSupportsAmbientMode_initialValueIsFetched_true() =
+        testScope.runTest {
+            whenever(wallpaperManager.getWallpaperInfoForUser(USER_WITH_SUPPORTED_WP.id))
+                .thenReturn(SUPPORTED_WP)
+            userRepository.setUserInfos(listOf(USER_WITH_SUPPORTED_WP))
+            userRepository.setSelectedUserInfo(USER_WITH_SUPPORTED_WP)
+
+            // WHEN the repo initially starts up (underTest is lazy), then it fetches the current
+            // value for the wallpaper
+            assertThat(underTest.wallpaperSupportsAmbientMode.value).isTrue()
+        }
+
+    @Test
+    fun wallpaperSupportsAmbientMode_initialValueIsFetched_false() =
+        testScope.runTest {
+            whenever(wallpaperManager.getWallpaperInfoForUser(USER_WITH_UNSUPPORTED_WP.id))
+                .thenReturn(UNSUPPORTED_WP)
+            userRepository.setUserInfos(listOf(USER_WITH_UNSUPPORTED_WP))
+            userRepository.setSelectedUserInfo(USER_WITH_UNSUPPORTED_WP)
+
+            // WHEN the repo initially starts up (underTest is lazy), then it fetches the current
+            // value for the wallpaper
+            assertThat(underTest.wallpaperSupportsAmbientMode.value).isFalse()
+        }
+
+    @Test
+    fun wallpaperSupportsAmbientMode_updatesOnUserChanged() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.wallpaperSupportsAmbientMode)
+
+            whenever(wallpaperManager.getWallpaperInfoForUser(USER_WITH_SUPPORTED_WP.id))
+                .thenReturn(SUPPORTED_WP)
+            whenever(wallpaperManager.getWallpaperInfoForUser(USER_WITH_UNSUPPORTED_WP.id))
+                .thenReturn(UNSUPPORTED_WP)
+            userRepository.setUserInfos(listOf(USER_WITH_SUPPORTED_WP, USER_WITH_UNSUPPORTED_WP))
+
+            // WHEN a user with supported wallpaper is selected
+            userRepository.setSelectedUserInfo(USER_WITH_SUPPORTED_WP)
+
+            // THEN it's true
+            assertThat(latest).isTrue()
+
+            // WHEN the user is switched to a user with unsupported wallpaper
+            userRepository.setSelectedUserInfo(USER_WITH_UNSUPPORTED_WP)
+
+            // THEN it's false
+            assertThat(latest).isFalse()
+        }
+
+    @Test
+    fun wallpaperSupportsAmbientMode_doesNotUpdateOnUserChanging() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.wallpaperSupportsAmbientMode)
+
+            whenever(wallpaperManager.getWallpaperInfoForUser(USER_WITH_SUPPORTED_WP.id))
+                .thenReturn(SUPPORTED_WP)
+            whenever(wallpaperManager.getWallpaperInfoForUser(USER_WITH_UNSUPPORTED_WP.id))
+                .thenReturn(UNSUPPORTED_WP)
+            userRepository.setUserInfos(listOf(USER_WITH_SUPPORTED_WP, USER_WITH_UNSUPPORTED_WP))
+
+            // WHEN a user with supported wallpaper is selected
+            userRepository.setSelectedUserInfo(USER_WITH_SUPPORTED_WP)
+
+            // THEN it's true
+            assertThat(latest).isTrue()
+
+            // WHEN the user has started switching to a user with unsupported wallpaper but hasn't
+            // finished yet
+            userRepository.selectedUser.value =
+                SelectedUserModel(USER_WITH_UNSUPPORTED_WP, SelectionStatus.SELECTION_IN_PROGRESS)
+
+            // THEN it still matches the old user
+            assertThat(latest).isTrue()
+        }
+
+    @Test
+    fun wallpaperSupportsAmbientMode_updatesOnIntent() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.wallpaperSupportsAmbientMode)
+
+            whenever(wallpaperManager.getWallpaperInfoForUser(any())).thenReturn(UNSUPPORTED_WP)
+
+            assertThat(latest).isFalse()
+
+            // WHEN the info now supports ambient mode and a broadcast is sent
+            whenever(wallpaperManager.getWallpaperInfoForUser(any())).thenReturn(SUPPORTED_WP)
+            fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+                context,
+                Intent(Intent.ACTION_WALLPAPER_CHANGED),
+            )
+
+            // THEN the flow updates
+            assertThat(latest).isTrue()
+        }
+
+    @Test
+    fun wallpaperSupportsAmbientMode_wallpaperNotSupported_alwaysFalse() =
+        testScope.runTest {
+            whenever(wallpaperManager.isWallpaperSupported).thenReturn(false)
+
+            val latest by collectLastValue(underTest.wallpaperSupportsAmbientMode)
+            assertThat(latest).isFalse()
+
+            // Even WHEN the current wallpaper *does* support ambient mode
+            whenever(wallpaperManager.getWallpaperInfoForUser(any())).thenReturn(SUPPORTED_WP)
+
+            fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+                context,
+                Intent(Intent.ACTION_WALLPAPER_CHANGED),
+            )
+
+            // THEN the value is still false because wallpaper isn't supported
+            assertThat(latest).isFalse()
+        }
+
+    @Test
+    fun wallpaperSupportsAmbientMode_deviceDoesNotSupportAmbientWallpaper_alwaysFalse() =
+        testScope.runTest {
+            context.orCreateTestableResources.addOverride(
+                com.android.internal.R.bool.config_dozeSupportsAodWallpaper,
+                false
+            )
+
+            val latest by collectLastValue(underTest.wallpaperSupportsAmbientMode)
+            assertThat(latest).isFalse()
+
+            // Even WHEN the current wallpaper *does* support ambient mode
+            whenever(wallpaperManager.getWallpaperInfoForUser(any())).thenReturn(SUPPORTED_WP)
+
+            fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+                context,
+                Intent(Intent.ACTION_WALLPAPER_CHANGED),
+            )
+
+            // THEN the value is still false because the device doesn't support it
+            assertThat(latest).isFalse()
+        }
+
+    private companion object {
+        val USER_WITH_UNSUPPORTED_WP = UserInfo(/* id= */ 3, /* name= */ "user3", /* flags= */ 0)
+        val UNSUPPORTED_WP =
+            mock<WallpaperInfo>().apply { whenever(this.supportsAmbientMode()).thenReturn(false) }
+
+        val USER_WITH_SUPPORTED_WP = UserInfo(/* id= */ 4, /* name= */ "user4", /* flags= */ 0)
+        val SUPPORTED_WP =
+            mock<WallpaperInfo>().apply { whenever(this.supportsAmbientMode()).thenReturn(true) }
+    }
+}
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 47a86b1..4839eeb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -35,9 +35,11 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyList;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
@@ -133,25 +135,29 @@
 import com.android.wm.shell.bubbles.BubbleDataRepository;
 import com.android.wm.shell.bubbles.BubbleEntry;
 import com.android.wm.shell.bubbles.BubbleLogger;
+import com.android.wm.shell.bubbles.BubbleOverflow;
 import com.android.wm.shell.bubbles.BubbleStackView;
 import com.android.wm.shell.bubbles.BubbleViewInfoTask;
+import com.android.wm.shell.bubbles.BubbleViewProvider;
 import com.android.wm.shell.bubbles.Bubbles;
 import com.android.wm.shell.bubbles.StackEducationViewKt;
+import com.android.wm.shell.bubbles.properties.BubbleProperties;
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.FloatingContentCoordinator;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.TaskStackListenerImpl;
+import com.android.wm.shell.common.bubbles.BubbleBarUpdate;
 import com.android.wm.shell.draganddrop.DragAndDropController;
 import com.android.wm.shell.onehanded.OneHandedController;
 import com.android.wm.shell.sysui.ShellCommandHandler;
 import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.taskview.TaskViewTransitions;
+import com.android.wm.shell.transition.Transitions;
 
 import org.junit.After;
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
@@ -278,7 +284,7 @@
     @Mock
     private ScreenOffAnimationController mScreenOffAnimationController;
     @Mock
-    private TaskViewTransitions mTaskViewTransitions;
+    Transitions mTransitions;
     @Mock
     private Optional<OneHandedController> mOneHandedOptional;
     @Mock
@@ -290,6 +296,8 @@
     @Mock
     private Icon mAppBubbleIcon;
 
+    private TaskViewTransitions mTaskViewTransitions;
+
     private TestableBubblePositioner mPositioner;
 
     private BubbleData mBubbleData;
@@ -300,9 +308,17 @@
 
     private UserHandle mUser0;
 
+    private FakeBubbleProperties mBubbleProperties;
+
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
+
+        if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+            doReturn(true).when(mTransitions).isRegistered();
+        }
+        mTaskViewTransitions = new TaskViewTransitions(mTransitions);
+
         mTestableLooper = TestableLooper.get(this);
 
         // For the purposes of this test, just run everything synchronously
@@ -320,7 +336,7 @@
                 mColorExtractor, mDumpManager, mKeyguardStateController,
                 mScreenOffAnimationController, mAuthController, mShadeExpansionStateManager,
                 mShadeWindowLogger);
-        mNotificationShadeWindowController.setNotificationShadeView(mNotificationShadeWindowView);
+        mNotificationShadeWindowController.setWindowRootView(mNotificationShadeWindowView);
         mNotificationShadeWindowController.attach();
 
         mAppBubbleIntent = new Intent(mContext, BubblesTestActivity.class);
@@ -361,6 +377,7 @@
                         mock(UserTracker.class)
                 );
         when(mShellTaskOrganizer.getExecutor()).thenReturn(syncExecutor);
+        mBubbleProperties = new FakeBubbleProperties();
         mBubbleController = new TestableBubbleController(
                 mContext,
                 mShellInit,
@@ -385,7 +402,8 @@
                 mock(Handler.class),
                 mTaskViewTransitions,
                 mock(SyncTransactionQueue.class),
-                mock(IWindowManager.class));
+                mock(IWindowManager.class),
+                mBubbleProperties);
         mBubbleController.setExpandListener(mBubbleExpandListener);
         spyOn(mBubbleController);
 
@@ -476,7 +494,7 @@
     public void testAddBubble() {
         mBubbleController.updateBubble(mBubbleEntry);
         assertTrue(mBubbleController.hasBubbles());
-        assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
+        assertSysuiStates(false /* stackExpanded */, false /* manageMenuExpanded */);
     }
 
     @Test
@@ -484,7 +502,7 @@
         assertFalse(mBubbleController.hasBubbles());
         mBubbleController.updateBubble(mBubbleEntry);
         assertTrue(mBubbleController.hasBubbles());
-        assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
+        assertSysuiStates(false /* stackExpanded */, false /* manageMenuExpanded */);
     }
 
     @Test
@@ -499,7 +517,7 @@
         assertNull(mBubbleData.getBubbleInStackWithKey(mRow.getKey()));
         verify(mNotifCallback, times(2)).invalidateNotifications(anyString());
 
-        assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
+        assertSysuiStates(false /* stackExpanded */, false /* manageMenuExpanded */);
     }
 
     @Test
@@ -562,7 +580,7 @@
         assertNull(mBubbleData.getBubbleInStackWithKey(mRow.getKey()));
         assertNull(mBubbleData.getBubbleInStackWithKey(mRow2.getKey()));
 
-        assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
+        assertSysuiStates(false /* stackExpanded */, false /* manageMenuExpanded */);
     }
 
     @Test
@@ -581,7 +599,7 @@
         mBubbleData.setExpanded(true);
         assertStackExpanded();
         verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getKey());
-        assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
+        assertSysuiStates(true /* stackExpanded */, false /* manageMenuExpanded */);
 
         // Make sure the notif is suppressed
         assertBubbleNotificationSuppressedFromShade(mBubbleEntry);
@@ -590,11 +608,10 @@
         mBubbleController.collapseStack();
         verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getKey());
         assertStackCollapsed();
-        assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
+        assertSysuiStates(false /* stackExpanded */, false /* manageMenuExpanded */);
     }
 
     @Test
-    @Ignore("Currently broken.")
     public void testCollapseAfterChangingExpandedBubble() {
         // Mark it as a bubble and add it explicitly
         mEntryListener.onEntryAdded(mRow);
@@ -613,7 +630,7 @@
         assertStackExpanded();
         verify(mBubbleExpandListener, atLeastOnce()).onBubbleExpandChanged(
                 true, mRow2.getKey());
-        assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
+        assertSysuiStates(true /* stackExpanded */, false /* manageMenuExpanded */);
 
         // Last added is the one that is expanded
         assertEquals(mRow2.getKey(), mBubbleData.getSelectedBubble().getKey());
@@ -638,7 +655,7 @@
         // Collapse
         mBubbleController.collapseStack();
         assertStackCollapsed();
-        assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
+        assertSysuiStates(false /* stackExpanded */, false /* manageMenuExpanded */);
     }
 
     @Test
@@ -658,7 +675,7 @@
         mBubbleData.setExpanded(true);
         assertStackExpanded();
         verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getKey());
-        assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
+        assertSysuiStates(true /* stackExpanded */, false /* manageMenuExpanded */);
 
         // Notif is suppressed after expansion
         assertBubbleNotificationSuppressedFromShade(mBubbleEntry);
@@ -683,7 +700,7 @@
         mBubbleData.setExpanded(true);
         assertStackExpanded();
         verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getKey());
-        assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
+        assertSysuiStates(true /* stackExpanded */, false /* manageMenuExpanded */);
 
         // Notif is suppressed after expansion
         assertBubbleNotificationSuppressedFromShade(mBubbleEntry);
@@ -712,7 +729,7 @@
         BubbleStackView stackView = mBubbleController.getStackView();
         mBubbleData.setExpanded(true);
 
-        assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
+        assertSysuiStates(true /* stackExpanded */, false /* manageMenuExpanded */);
 
         assertStackExpanded();
         verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow2.getKey());
@@ -743,7 +760,7 @@
         // We should be collapsed
         verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getKey());
         assertFalse(mBubbleController.hasBubbles());
-        assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
+        assertSysuiStates(false /* stackExpanded */, false /* manageMenuExpanded */);
     }
 
     @Test
@@ -756,7 +773,7 @@
         BubbleStackView stackView = mBubbleController.getStackView();
         mBubbleData.setExpanded(true);
 
-        assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
+        assertSysuiStates(true /* stackExpanded */, false /* manageMenuExpanded */);
         assertStackExpanded();
         verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getKey());
 
@@ -771,7 +788,7 @@
         // We should be collapsed
         verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getKey());
         assertFalse(mBubbleController.hasBubbles());
-        assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
+        assertSysuiStates(false /* stackExpanded */, false /* manageMenuExpanded */);
     }
 
 
@@ -789,7 +806,7 @@
         verify(mBubbleExpandListener, never()).onBubbleExpandChanged(false /* expanded */,
                 mRow.getKey());
         assertStackCollapsed();
-        assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
+        assertSysuiStates(false /* stackExpanded */, false /* manageMenuExpanded */);
     }
 
     @Test
@@ -805,7 +822,7 @@
         verify(mBubbleExpandListener).onBubbleExpandChanged(true /* expanded */,
                 mRow.getKey());
         assertStackExpanded();
-        assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
+        assertSysuiStates(true /* stackExpanded */, false /* manageMenuExpanded */);
     }
 
     @Test
@@ -822,7 +839,7 @@
         // Dot + flyout is hidden because notif is suppressed
         assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showDot());
         assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showFlyout());
-        assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
+        assertSysuiStates(false /* stackExpanded */, false /* manageMenuExpanded */);
     }
 
     @Test
@@ -844,7 +861,7 @@
         // Dot + flyout is hidden because notif is suppressed
         assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showDot());
         assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showFlyout());
-        assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
+        assertSysuiStates(false /* stackExpanded */, false /* manageMenuExpanded */);
     }
 
     @Test
@@ -1125,7 +1142,7 @@
         assertThat(mBubbleData.getOverflowBubbleWithKey(mBubbleEntry2User11.getKey())).isNotNull();
 
         // Would have loaded bubbles twice because of user switch
-        verify(mDataRepository, times(2)).loadBubbles(anyInt(), any());
+        verify(mDataRepository, times(2)).loadBubbles(anyInt(), anyList(), any());
     }
 
     @Test
@@ -1181,7 +1198,7 @@
         mEntryListener.onEntryRemoved(mRow2, REASON_APP_CANCEL);
         assertThat(mBubbleData.getOverflowBubbles()).isEmpty();
 
-        verify(mDataRepository, times(1)).loadBubbles(anyInt(), any());
+        verify(mDataRepository, times(1)).loadBubbles(anyInt(), anyList(), any());
     }
 
     /**
@@ -1249,11 +1266,11 @@
         BubbleStackView stackView = mBubbleController.getStackView();
         mBubbleData.setExpanded(true);
         assertStackExpanded();
-        assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
+        assertSysuiStates(true /* stackExpanded */, false /* manageMenuExpanded */);
 
         // Show the menu
         stackView.showManageMenu(true);
-        assertSysuiStates(true /* stackExpanded */, true /* mangeMenuExpanded */);
+        assertSysuiStates(true /* stackExpanded */, true /* manageMenuExpanded */);
         assertTrue(stackView.isManageMenuSettingsVisible());
         assertTrue(stackView.isManageMenuDontBubbleVisible());
     }
@@ -1267,11 +1284,11 @@
         BubbleStackView stackView = mBubbleController.getStackView();
         mBubbleData.setExpanded(true);
         assertStackExpanded();
-        assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
+        assertSysuiStates(true /* stackExpanded */, false /* manageMenuExpanded */);
 
         // Show the menu
         stackView.showManageMenu(true);
-        assertSysuiStates(true /* stackExpanded */, true /* mangeMenuExpanded */);
+        assertSysuiStates(true /* stackExpanded */, true /* manageMenuExpanded */);
         assertFalse(stackView.isManageMenuSettingsVisible());
         assertFalse(stackView.isManageMenuDontBubbleVisible());
     }
@@ -1285,15 +1302,15 @@
         BubbleStackView stackView = mBubbleController.getStackView();
         mBubbleData.setExpanded(true);
         assertStackExpanded();
-        assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
+        assertSysuiStates(true /* stackExpanded */, false /* manageMenuExpanded */);
 
         // Show the menu
         stackView.showManageMenu(true);
-        assertSysuiStates(true /* stackExpanded */, true /* mangeMenuExpanded */);
+        assertSysuiStates(true /* stackExpanded */, true /* manageMenuExpanded */);
 
         // Hide the menu
         stackView.showManageMenu(false);
-        assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
+        assertSysuiStates(true /* stackExpanded */, false /* manageMenuExpanded */);
     }
 
     @Test
@@ -1305,16 +1322,16 @@
         BubbleStackView stackView = mBubbleController.getStackView();
         mBubbleData.setExpanded(true);
         assertStackExpanded();
-        assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
+        assertSysuiStates(true /* stackExpanded */, false /* manageMenuExpanded */);
 
         // Show the menu
         stackView.showManageMenu(true);
-        assertSysuiStates(true /* stackExpanded */, true /* mangeMenuExpanded */);
+        assertSysuiStates(true /* stackExpanded */, true /* manageMenuExpanded */);
 
         // Collapse the stack
         mBubbleData.setExpanded(false);
 
-        assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
+        assertSysuiStates(false /* stackExpanded */, false /* manageMenuExpanded */);
     }
 
     @Test
@@ -1576,7 +1593,7 @@
 
         NotificationListenerService.RankingMap rankingMap =
                 mock(NotificationListenerService.RankingMap.class);
-        when(rankingMap.getOrderedKeys()).thenReturn(new String[] { mBubbleEntry.getKey() });
+        when(rankingMap.getOrderedKeys()).thenReturn(new String[]{mBubbleEntry.getKey()});
         mBubbleController.onRankingUpdated(rankingMap, entryDataByKey);
 
         // Should no longer be in the stack
@@ -1884,7 +1901,111 @@
         );
     }
 
-        /** Creates a bubble using the userId and package. */
+    @Test
+    public void registerBubbleBarListener_barDisabled_largeScreen_shouldBeIgnored() {
+        mBubbleProperties.mIsBubbleBarEnabled = false;
+        mPositioner.setIsLargeScreen(true);
+        mEntryListener.onEntryAdded(mRow);
+        mBubbleController.updateBubble(mBubbleEntry);
+        assertTrue(mBubbleController.hasBubbles());
+
+        assertStackMode();
+
+        FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
+        mBubbleController.registerBubbleStateListener(bubbleStateListener);
+
+        assertStackMode();
+
+        assertThat(mBubbleController.getStackView().getBubbleCount()).isEqualTo(1);
+    }
+
+    @Test
+    public void registerBubbleBarListener_barEnabled_smallScreen_shouldBeIgnored() {
+        mBubbleProperties.mIsBubbleBarEnabled = true;
+        mPositioner.setIsLargeScreen(false);
+        mEntryListener.onEntryAdded(mRow);
+        mBubbleController.updateBubble(mBubbleEntry);
+        assertTrue(mBubbleController.hasBubbles());
+
+        assertStackMode();
+
+        FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
+        mBubbleController.registerBubbleStateListener(bubbleStateListener);
+
+        assertStackMode();
+
+        assertThat(mBubbleController.getStackView().getBubbleCount()).isEqualTo(1);
+    }
+
+    @Test
+    public void registerBubbleBarListener_switchToBarAndBackToStack() {
+        mBubbleProperties.mIsBubbleBarEnabled = true;
+        mPositioner.setIsLargeScreen(true);
+        mEntryListener.onEntryAdded(mRow);
+        mBubbleController.updateBubble(mBubbleEntry);
+        assertTrue(mBubbleController.hasBubbles());
+
+        assertStackMode();
+
+        assertThat(mBubbleData.getBubbles()).hasSize(1);
+        assertBubbleIsInflatedForStack(mBubbleData.getBubbles().get(0));
+        assertBubbleIsInflatedForStack(mBubbleData.getOverflow());
+
+        FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
+        mBubbleController.registerBubbleStateListener(bubbleStateListener);
+
+        assertBarMode();
+
+        assertThat(mBubbleData.getBubbles()).hasSize(1);
+        assertBubbleIsInflatedForBar(mBubbleData.getBubbles().get(0));
+        assertBubbleIsInflatedForBar(mBubbleData.getOverflow());
+
+        mBubbleController.unregisterBubbleStateListener();
+
+        assertStackMode();
+
+        assertThat(mBubbleData.getBubbles()).hasSize(1);
+        assertBubbleIsInflatedForStack(mBubbleData.getBubbles().get(0));
+        assertBubbleIsInflatedForStack(mBubbleData.getOverflow());
+    }
+
+    @Test
+    public void switchBetweenBarAndStack_noBubbles_shouldBeIgnored() {
+        mBubbleProperties.mIsBubbleBarEnabled = false;
+        mPositioner.setIsLargeScreen(true);
+        assertFalse(mBubbleController.hasBubbles());
+
+        assertNoBubbleContainerViews();
+
+        FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
+        mBubbleController.registerBubbleStateListener(bubbleStateListener);
+
+        assertNoBubbleContainerViews();
+
+        mBubbleController.unregisterBubbleStateListener();
+
+        assertNoBubbleContainerViews();
+    }
+
+    @Test
+    public void bubbleBarBubbleExpandedAndCollapsed() {
+        mBubbleProperties.mIsBubbleBarEnabled = true;
+        mPositioner.setIsLargeScreen(true);
+        mEntryListener.onEntryAdded(mRow);
+        mBubbleController.updateBubble(mBubbleEntry);
+
+        FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
+        mBubbleController.registerBubbleStateListener(bubbleStateListener);
+        mBubbleController.expandStackAndSelectBubbleFromLauncher(mBubbleEntry.getKey(), 500, 1000);
+
+        assertThat(mBubbleController.getLayerView().isExpanded()).isTrue();
+
+        mBubbleController.collapseStack();
+
+        assertThat(mBubbleController.getLayerView().isExpanded()).isFalse();
+    }
+
+    /** Creates a bubble using the userId and package. */
     private Bubble createBubble(int userId, String pkg) {
         final UserHandle userHandle = new UserHandle(userId);
         NotificationEntry workEntry = new NotificationEntryBuilder()
@@ -2000,6 +2121,44 @@
         assertFalse(mBubbleController.getImplCachedState().isStackExpanded());
     }
 
+    /** Asserts that both the bubble stack and bar views don't exist. */
+    private void assertNoBubbleContainerViews() {
+        assertThat(mBubbleController.getStackView()).isNull();
+        assertThat(mBubbleController.getLayerView()).isNull();
+    }
+
+    /** Asserts that the stack is created and the bar is null. */
+    private void assertStackMode() {
+        assertThat(mBubbleController.getStackView()).isNotNull();
+        assertThat(mBubbleController.getLayerView()).isNull();
+    }
+
+    /** Asserts that the given bubble has the stack expanded view inflated. */
+    private void assertBubbleIsInflatedForStack(BubbleViewProvider b) {
+        assertThat(b.getIconView()).isNotNull();
+        assertThat(b.getExpandedView()).isNotNull();
+        assertThat(b.getBubbleBarExpandedView()).isNull();
+    }
+
+    /** Asserts that the bar is created and the stack is null. */
+    private void assertBarMode() {
+        assertThat(mBubbleController.getStackView()).isNull();
+        assertThat(mBubbleController.getLayerView()).isNotNull();
+    }
+
+    /** Asserts that the given bubble has the bar expanded view inflated. */
+    private void assertBubbleIsInflatedForBar(BubbleViewProvider b) {
+        // the icon view should be inflated for the overflow but not for other bubbles when showing
+        // in the bar
+        if (b instanceof Bubble) {
+            assertThat(b.getIconView()).isNull();
+        } else if (b instanceof BubbleOverflow) {
+            assertThat(b.getIconView()).isNotNull();
+        }
+        assertThat(b.getExpandedView()).isNull();
+        assertThat(b.getBubbleBarExpandedView()).isNotNull();
+    }
+
     /**
      * Asserts that a bubble notification is suppressed from the shade and also validates the cached
      * state is updated.
@@ -2029,4 +2188,19 @@
         assertThat(mSysUiStateBubblesExpanded).isEqualTo(stackExpanded);
         assertThat(mSysUiStateBubblesManageMenuExpanded).isEqualTo(manageMenuExpanded);
     }
+
+    private static class FakeBubbleStateListener implements Bubbles.BubbleStateListener {
+        @Override
+        public void onBubbleStateChange(BubbleBarUpdate update) {
+        }
+    }
+
+    private static class FakeBubbleProperties implements BubbleProperties {
+        boolean mIsBubbleBarEnabled = false;
+
+        @Override
+        public boolean isBubbleBarEnabled() {
+            return mIsBubbleBarEnabled;
+        }
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java
index 14c3f3c..5855347 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java
@@ -31,6 +31,7 @@
 import com.android.wm.shell.bubbles.BubbleDataRepository;
 import com.android.wm.shell.bubbles.BubbleLogger;
 import com.android.wm.shell.bubbles.BubblePositioner;
+import com.android.wm.shell.bubbles.properties.BubbleProperties;
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.FloatingContentCoordinator;
 import com.android.wm.shell.common.ShellExecutor;
@@ -74,13 +75,14 @@
             Handler shellMainHandler,
             TaskViewTransitions taskViewTransitions,
             SyncTransactionQueue syncQueue,
-            IWindowManager wmService) {
+            IWindowManager wmService,
+            BubbleProperties bubbleProperties) {
         super(context, shellInit, shellCommandHandler, shellController, data, Runnable::run,
                 floatingContentCoordinator, dataRepository, statusBarService, windowManager,
                 windowManagerShellWrapper, userManager, launcherApps, bubbleLogger,
                 taskStackListener, shellTaskOrganizer, positioner, displayController,
                 oneHandedOptional, dragAndDropController, shellMainExecutor, shellMainHandler,
-                new SyncExecutor(), taskViewTransitions, syncQueue, wmService);
+                new SyncExecutor(), taskViewTransitions, syncQueue, wmService, bubbleProperties);
         setInflateSynchronously(true);
         onInit();
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubblePositioner.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubblePositioner.java
index 6edc373..047dc65 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubblePositioner.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubblePositioner.java
@@ -27,6 +27,7 @@
 
 public class TestableBubblePositioner extends BubblePositioner {
     private int mMaxBubbles;
+    private boolean mIsLargeScreen = false;
 
     public TestableBubblePositioner(Context context,
             WindowManager windowManager) {
@@ -46,4 +47,13 @@
     public int getMaxBubbles() {
         return mMaxBubbles;
     }
+
+    public void setIsLargeScreen(boolean isLargeScreen) {
+        mIsLargeScreen = isLargeScreen;
+    }
+
+    @Override
+    public boolean isLargeScreen() {
+        return mIsLargeScreen;
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
index 9de7a87..ef0adbb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
@@ -34,7 +34,6 @@
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.tracing.ProtoTracer;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.desktopmode.DesktopMode;
 import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
@@ -76,7 +75,6 @@
     @Mock SplitScreen mSplitScreen;
     @Mock OneHanded mOneHanded;
     @Mock WakefulnessLifecycle mWakefulnessLifecycle;
-    @Mock ProtoTracer mProtoTracer;
     @Mock UserTracker mUserTracker;
     @Mock ShellExecutor mSysUiMainExecutor;
     @Mock NoteTaskInitializer mNoteTaskInitializer;
@@ -99,7 +97,6 @@
                 mKeyguardUpdateMonitor,
                 mScreenLifecycle,
                 mSysUiState,
-                mProtoTracer,
                 mWakefulnessLifecycle,
                 mUserTracker,
                 displayTracker,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiBaseFragmentTest.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiBaseFragmentTest.java
index 9179efc..e470406 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiBaseFragmentTest.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiBaseFragmentTest.java
@@ -57,8 +57,7 @@
 
     @Before
     public void sysuiSetup() throws ExecutionException, InterruptedException {
-        SystemUIInitializer initializer =
-                SystemUIInitializerFactory.createFromConfigNoAssert(mContext);
+        SystemUIInitializer initializer = new SystemUIInitializerImpl(mContext);
         initializer.init(true);
         mDependency = new TestableDependency(initializer.getSysUIComponent().createDependency());
         Dependency.setInstance(mDependency);
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
index 8bbd58d..de177168 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
@@ -90,8 +90,7 @@
         if (isRobolectricTest()) {
             mContext = mContext.createDefaultDisplayContext();
         }
-        SystemUIInitializer initializer =
-                SystemUIInitializerFactory.createFromConfigNoAssert(mContext);
+        SystemUIInitializer initializer = new SystemUIInitializerImpl(mContext);
         initializer.init(true);
         mDependency = new TestableDependency(initializer.getSysUIComponent().createDependency());
         Dependency.setInstance(mDependency);
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/activity/SingleActivityFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/activity/SingleActivityFactory.kt
new file mode 100644
index 0000000..5a92fb1
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/activity/SingleActivityFactory.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.activity
+
+import android.app.Activity
+import android.content.Intent
+import androidx.test.runner.intercepting.SingleActivityFactory
+
+/**
+ * Builds a new [SingleActivityFactory] which delegating any call of [SingleActivityFactory.create]
+ * to the [instantiate] parameter.
+ *
+ * For more details, see [SingleActivityFactory].
+ */
+inline fun <reified T : Activity> SingleActivityFactory(
+    crossinline instantiate: (intent: Intent?) -> T,
+): SingleActivityFactory<T> {
+    return object : SingleActivityFactory<T>(T::class.java) {
+        override fun create(intent: Intent?): T = instantiate(intent)
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt
new file mode 100644
index 0000000..c2e1ac7
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt
@@ -0,0 +1,216 @@
+/*
+ * 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.systemui.authentication.data.repository
+
+import com.android.internal.widget.LockPatternUtils
+import com.android.internal.widget.LockPatternView
+import com.android.internal.widget.LockscreenCredential
+import com.android.keyguard.KeyguardSecurityModel.SecurityMode
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.authentication.shared.model.AuthenticationResultModel
+import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+class FakeAuthenticationRepository(
+    private val currentTime: () -> Long,
+) : AuthenticationRepository {
+
+    private val _isAutoConfirmEnabled = MutableStateFlow(false)
+    override val isAutoConfirmEnabled: StateFlow<Boolean> = _isAutoConfirmEnabled.asStateFlow()
+
+    private val _isUnlocked = MutableStateFlow(false)
+    override val isUnlocked: StateFlow<Boolean> = _isUnlocked.asStateFlow()
+
+    override val hintedPinLength: Int = HINTING_PIN_LENGTH
+
+    private val _isPatternVisible = MutableStateFlow(true)
+    override val isPatternVisible: StateFlow<Boolean> = _isPatternVisible.asStateFlow()
+
+    private val _throttling = MutableStateFlow(AuthenticationThrottlingModel())
+    override val throttling: StateFlow<AuthenticationThrottlingModel> = _throttling.asStateFlow()
+
+    private val _authenticationMethod =
+        MutableStateFlow<AuthenticationMethodModel>(DEFAULT_AUTHENTICATION_METHOD)
+    val authenticationMethod: StateFlow<AuthenticationMethodModel> =
+        _authenticationMethod.asStateFlow()
+
+    private var isLockscreenEnabled = true
+    private var failedAttemptCount = 0
+    private var throttlingEndTimestamp = 0L
+    private var credentialOverride: List<Any>? = null
+    private var securityMode: SecurityMode = DEFAULT_AUTHENTICATION_METHOD.toSecurityMode()
+
+    override suspend fun getAuthenticationMethod(): AuthenticationMethodModel {
+        return authenticationMethod.value
+    }
+
+    fun setAuthenticationMethod(authenticationMethod: AuthenticationMethodModel) {
+        _authenticationMethod.value = authenticationMethod
+        securityMode = authenticationMethod.toSecurityMode()
+    }
+
+    fun overrideCredential(pin: List<Int>) {
+        credentialOverride = pin
+    }
+
+    override suspend fun isLockscreenEnabled(): Boolean {
+        return isLockscreenEnabled
+    }
+
+    override suspend fun reportAuthenticationAttempt(isSuccessful: Boolean) {
+        failedAttemptCount = if (isSuccessful) 0 else failedAttemptCount + 1
+        _isUnlocked.value = isSuccessful
+    }
+
+    override suspend fun getPinLength(): Int {
+        return (credentialOverride ?: DEFAULT_PIN).size
+    }
+
+    override suspend fun getFailedAuthenticationAttemptCount(): Int {
+        return failedAttemptCount
+    }
+
+    override suspend fun getThrottlingEndTimestamp(): Long {
+        return throttlingEndTimestamp
+    }
+
+    override fun setThrottling(throttlingModel: AuthenticationThrottlingModel) {
+        _throttling.value = throttlingModel
+    }
+
+    fun setUnlocked(isUnlocked: Boolean) {
+        _isUnlocked.value = isUnlocked
+    }
+
+    fun setAutoConfirmEnabled(isEnabled: Boolean) {
+        _isAutoConfirmEnabled.value = isEnabled
+    }
+
+    fun setLockscreenEnabled(isLockscreenEnabled: Boolean) {
+        this.isLockscreenEnabled = isLockscreenEnabled
+    }
+
+    override suspend fun setThrottleDuration(durationMs: Int) {
+        throttlingEndTimestamp = if (durationMs > 0) currentTime() + durationMs else 0
+    }
+
+    override suspend fun checkCredential(
+        credential: LockscreenCredential
+    ): AuthenticationResultModel {
+        val expectedCredential = credentialOverride ?: getExpectedCredential(securityMode)
+        val isSuccessful =
+            when {
+                credential.type != getCurrentCredentialType(securityMode) -> false
+                credential.type == LockPatternUtils.CREDENTIAL_TYPE_PIN ->
+                    credential.isPin && credential.matches(expectedCredential)
+                credential.type == LockPatternUtils.CREDENTIAL_TYPE_PASSWORD ->
+                    credential.isPassword && credential.matches(expectedCredential)
+                credential.type == LockPatternUtils.CREDENTIAL_TYPE_PATTERN ->
+                    credential.isPattern && credential.matches(expectedCredential)
+                else -> error("Unexpected credential type ${credential.type}!")
+            }
+
+        return if (
+            isSuccessful || failedAttemptCount < MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING - 1
+        ) {
+            AuthenticationResultModel(
+                isSuccessful = isSuccessful,
+                throttleDurationMs = 0,
+            )
+        } else {
+            AuthenticationResultModel(
+                isSuccessful = false,
+                throttleDurationMs = THROTTLE_DURATION_MS,
+            )
+        }
+    }
+
+    private fun getExpectedCredential(securityMode: SecurityMode): List<Any> {
+        return when (val credentialType = getCurrentCredentialType(securityMode)) {
+            LockPatternUtils.CREDENTIAL_TYPE_PIN -> credentialOverride ?: DEFAULT_PIN
+            LockPatternUtils.CREDENTIAL_TYPE_PASSWORD -> "password".toList()
+            LockPatternUtils.CREDENTIAL_TYPE_PATTERN -> PATTERN.toCells()
+            else -> error("Unsupported credential type $credentialType!")
+        }
+    }
+
+    companion object {
+        val DEFAULT_AUTHENTICATION_METHOD = AuthenticationMethodModel.Pin
+        val PATTERN =
+            listOf(
+                AuthenticationMethodModel.Pattern.PatternCoordinate(2, 0),
+                AuthenticationMethodModel.Pattern.PatternCoordinate(2, 1),
+                AuthenticationMethodModel.Pattern.PatternCoordinate(2, 2),
+                AuthenticationMethodModel.Pattern.PatternCoordinate(1, 1),
+                AuthenticationMethodModel.Pattern.PatternCoordinate(0, 0),
+                AuthenticationMethodModel.Pattern.PatternCoordinate(0, 1),
+                AuthenticationMethodModel.Pattern.PatternCoordinate(0, 2),
+            )
+        const val MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING = 5
+        const val THROTTLE_DURATION_MS = 30000
+        const val HINTING_PIN_LENGTH = 6
+        val DEFAULT_PIN = buildList { repeat(HINTING_PIN_LENGTH) { add(it + 1) } }
+
+        private fun AuthenticationMethodModel.toSecurityMode(): SecurityMode {
+            return when (this) {
+                is AuthenticationMethodModel.Pin -> SecurityMode.PIN
+                is AuthenticationMethodModel.Password -> SecurityMode.Password
+                is AuthenticationMethodModel.Pattern -> SecurityMode.Pattern
+                is AuthenticationMethodModel.Swipe,
+                is AuthenticationMethodModel.None -> SecurityMode.None
+            }
+        }
+
+        @LockPatternUtils.CredentialType
+        private fun getCurrentCredentialType(
+            securityMode: SecurityMode,
+        ): Int {
+            return when (securityMode) {
+                SecurityMode.PIN,
+                SecurityMode.SimPin,
+                SecurityMode.SimPuk -> LockPatternUtils.CREDENTIAL_TYPE_PIN
+                SecurityMode.Password -> LockPatternUtils.CREDENTIAL_TYPE_PASSWORD
+                SecurityMode.Pattern -> LockPatternUtils.CREDENTIAL_TYPE_PATTERN
+                SecurityMode.None -> LockPatternUtils.CREDENTIAL_TYPE_NONE
+                else -> error("Unsupported SecurityMode $securityMode!")
+            }
+        }
+
+        private fun LockscreenCredential.matches(expectedCredential: List<Any>): Boolean {
+            @Suppress("UNCHECKED_CAST")
+            return when {
+                isPin ->
+                    credential.map { byte -> byte.toInt().toChar() - '0' } == expectedCredential
+                isPassword -> credential.map { byte -> byte.toInt().toChar() } == expectedCredential
+                isPattern ->
+                    credential.contentEquals(
+                        LockPatternUtils.patternToByteArray(
+                            expectedCredential as List<LockPatternView.Cell>
+                        )
+                    )
+                else -> error("Unsupported credential type $type!")
+            }
+        }
+
+        private fun List<AuthenticationMethodModel.Pattern.PatternCoordinate>.toCells():
+            List<LockPatternView.Cell> {
+            return map { coordinate -> LockPatternView.Cell.of(coordinate.y, coordinate.x) }
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFaceSettingsRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFaceSettingsRepository.kt
new file mode 100644
index 0000000..af2706e
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFaceSettingsRepository.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics.data.repository
+
+import kotlinx.coroutines.flow.flowOf
+
+/** Fake settings for tests. */
+class FakeFaceSettingsRepository : FaceSettingsRepository {
+
+    private val userRepositories = mutableMapOf<Int, FaceUserSettingsRepository>()
+
+    /** Add fixed settings for a user. */
+    fun setUserSettings(userId: Int, alwaysRequireConfirmationInApps: Boolean = false) {
+        userRepositories[userId] =
+            object : FaceUserSettingsRepository {
+                override val userId = userId
+                override val alwaysRequireConfirmationInApps =
+                    flowOf(alwaysRequireConfirmationInApps)
+            }
+    }
+
+    override fun forUser(id: Int?) = userRepositories[id] ?: FaceUserSettingsRepositoryImpl.Empty
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFingerprintPropertyRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFingerprintPropertyRepository.kt
index 2362a52..0c5e438 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFingerprintPropertyRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFingerprintPropertyRepository.kt
@@ -20,16 +20,12 @@
 import com.android.systemui.biometrics.shared.model.FingerprintSensorType
 import com.android.systemui.biometrics.shared.model.SensorStrength
 import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
 
 class FakeFingerprintPropertyRepository : FingerprintPropertyRepository {
 
-    private val _isInitialized: MutableStateFlow<Boolean> = MutableStateFlow(false)
-    override val isInitialized = _isInitialized.asStateFlow()
-
     private val _sensorId: MutableStateFlow<Int> = MutableStateFlow(-1)
-    override val sensorId: StateFlow<Int> = _sensorId.asStateFlow()
+    override val sensorId = _sensorId.asStateFlow()
 
     private val _strength: MutableStateFlow<SensorStrength> =
         MutableStateFlow(SensorStrength.CONVENIENCE)
@@ -37,12 +33,11 @@
 
     private val _sensorType: MutableStateFlow<FingerprintSensorType> =
         MutableStateFlow(FingerprintSensorType.UNKNOWN)
-    override val sensorType: StateFlow<FingerprintSensorType> = _sensorType.asStateFlow()
+    override val sensorType = _sensorType.asStateFlow()
 
     private val _sensorLocations: MutableStateFlow<Map<String, SensorLocationInternal>> =
         MutableStateFlow(mapOf("" to SensorLocationInternal.DEFAULT))
-    override val sensorLocations: StateFlow<Map<String, SensorLocationInternal>> =
-        _sensorLocations.asStateFlow()
+    override val sensorLocations = _sensorLocations.asStateFlow()
 
     fun setProperties(
         sensorId: Int,
@@ -54,6 +49,5 @@
         _strength.value = strength
         _sensorType.value = sensorType
         _sensorLocations.value = sensorLocations
-        _isInitialized.value = true
     }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt
index d270700..42ec8fed 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt
@@ -31,13 +31,20 @@
         userId: Int,
         gatekeeperChallenge: Long?,
         kind: PromptKind,
-        requireConfirmation: Boolean,
+    ) = setPrompt(promptInfo, userId, gatekeeperChallenge, kind, forceConfirmation = false)
+
+    fun setPrompt(
+        promptInfo: PromptInfo,
+        userId: Int,
+        gatekeeperChallenge: Long?,
+        kind: PromptKind,
+        forceConfirmation: Boolean = false,
     ) {
         _promptInfo.value = promptInfo
         _userId.value = userId
         _challenge.value = gatekeeperChallenge
         _kind.value = kind
-        _isConfirmationRequired.value = requireConfirmation
+        _isConfirmationRequired.value = promptInfo.isConfirmationRequested || forceConfirmation
     }
 
     override fun unsetPrompt() {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/FakeBouncerMessageRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/FakeBouncerMessageRepository.kt
new file mode 100644
index 0000000..d9b926d
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/FakeBouncerMessageRepository.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.bouncer.data.repository
+
+import com.android.systemui.bouncer.shared.model.BouncerMessageModel
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+
+class FakeBouncerMessageRepository : BouncerMessageRepository {
+    private val _primaryAuthMessage = MutableStateFlow<BouncerMessageModel?>(null)
+    override val primaryAuthMessage: StateFlow<BouncerMessageModel?>
+        get() = _primaryAuthMessage
+
+    private val _faceAcquisitionMessage = MutableStateFlow<BouncerMessageModel?>(null)
+    override val faceAcquisitionMessage: StateFlow<BouncerMessageModel?>
+        get() = _faceAcquisitionMessage
+    private val _fingerprintAcquisitionMessage = MutableStateFlow<BouncerMessageModel?>(null)
+    override val fingerprintAcquisitionMessage: StateFlow<BouncerMessageModel?>
+        get() = _fingerprintAcquisitionMessage
+    private val _customMessage = MutableStateFlow<BouncerMessageModel?>(null)
+    override val customMessage: StateFlow<BouncerMessageModel?>
+        get() = _customMessage
+    private val _biometricAuthMessage = MutableStateFlow<BouncerMessageModel?>(null)
+    override val biometricAuthMessage: StateFlow<BouncerMessageModel?>
+        get() = _biometricAuthMessage
+    private val _authFlagsMessage = MutableStateFlow<BouncerMessageModel?>(null)
+    override val authFlagsMessage: StateFlow<BouncerMessageModel?>
+        get() = _authFlagsMessage
+
+    private val _biometricLockedOutMessage = MutableStateFlow<BouncerMessageModel?>(null)
+    override val biometricLockedOutMessage: Flow<BouncerMessageModel?>
+        get() = _biometricLockedOutMessage
+
+    override fun setPrimaryAuthMessage(value: BouncerMessageModel?) {
+        _primaryAuthMessage.value = value
+    }
+
+    override fun setFaceAcquisitionMessage(value: BouncerMessageModel?) {
+        _faceAcquisitionMessage.value = value
+    }
+
+    override fun setFingerprintAcquisitionMessage(value: BouncerMessageModel?) {
+        _fingerprintAcquisitionMessage.value = value
+    }
+
+    override fun setCustomMessage(value: BouncerMessageModel?) {
+        _customMessage.value = value
+    }
+
+    fun setBiometricAuthMessage(value: BouncerMessageModel?) {
+        _biometricAuthMessage.value = value
+    }
+
+    fun setAuthFlagsMessage(value: BouncerMessageModel?) {
+        _authFlagsMessage.value = value
+    }
+
+    fun setBiometricLockedOutMessage(value: BouncerMessageModel?) {
+        _biometricLockedOutMessage.value = value
+    }
+
+    override fun clearMessage() {
+        _primaryAuthMessage.value = null
+        _faceAcquisitionMessage.value = null
+        _fingerprintAcquisitionMessage.value = null
+        _customMessage.value = null
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/FakeKeyguardBouncerRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/FakeKeyguardBouncerRepository.kt
new file mode 100644
index 0000000..10529e6
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/FakeKeyguardBouncerRepository.kt
@@ -0,0 +1,97 @@
+package com.android.systemui.bouncer.data.repository
+
+import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants
+import com.android.systemui.bouncer.shared.model.BouncerShowMessageModel
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+/** Fake implementation of [KeyguardRepository] */
+class FakeKeyguardBouncerRepository : KeyguardBouncerRepository {
+    private val _primaryBouncerShow = MutableStateFlow(false)
+    override val primaryBouncerShow = _primaryBouncerShow.asStateFlow()
+    private val _primaryBouncerShowingSoon = MutableStateFlow(false)
+    override val primaryBouncerShowingSoon = _primaryBouncerShowingSoon.asStateFlow()
+    private val _primaryBouncerStartingToHide = MutableStateFlow(false)
+    override val primaryBouncerStartingToHide = _primaryBouncerStartingToHide.asStateFlow()
+    private val _primaryBouncerDisappearAnimation = MutableStateFlow<Runnable?>(null)
+    override val primaryBouncerStartingDisappearAnimation =
+        _primaryBouncerDisappearAnimation.asStateFlow()
+    private val _primaryBouncerScrimmed = MutableStateFlow(false)
+    override val primaryBouncerScrimmed = _primaryBouncerScrimmed.asStateFlow()
+    private val _panelExpansionAmount = MutableStateFlow(KeyguardBouncerConstants.EXPANSION_HIDDEN)
+    override val panelExpansionAmount = _panelExpansionAmount.asStateFlow()
+    private val _keyguardPosition = MutableStateFlow(0f)
+    override val keyguardPosition = _keyguardPosition.asStateFlow()
+    private val _isBackButtonEnabled = MutableStateFlow<Boolean?>(null)
+    override val isBackButtonEnabled = _isBackButtonEnabled.asStateFlow()
+    private val _keyguardAuthenticated = MutableStateFlow<Boolean?>(null)
+    override val keyguardAuthenticated = _keyguardAuthenticated.asStateFlow()
+    private val _showMessage = MutableStateFlow<BouncerShowMessageModel?>(null)
+    override val showMessage = _showMessage.asStateFlow()
+    private val _resourceUpdateRequests = MutableStateFlow(false)
+    override val resourceUpdateRequests = _resourceUpdateRequests.asStateFlow()
+    private val _isAlternateBouncerVisible = MutableStateFlow(false)
+    override val alternateBouncerVisible = _isAlternateBouncerVisible.asStateFlow()
+    override var lastAlternateBouncerVisibleTime: Long = 0L
+    private val _isAlternateBouncerUIAvailable = MutableStateFlow<Boolean>(false)
+    override val alternateBouncerUIAvailable = _isAlternateBouncerUIAvailable.asStateFlow()
+    private val _sideFpsShowing: MutableStateFlow<Boolean> = MutableStateFlow(false)
+    override val sideFpsShowing: StateFlow<Boolean> = _sideFpsShowing.asStateFlow()
+
+    override fun setPrimaryScrimmed(isScrimmed: Boolean) {
+        _primaryBouncerScrimmed.value = isScrimmed
+    }
+
+    override fun setAlternateVisible(isVisible: Boolean) {
+        _isAlternateBouncerVisible.value = isVisible
+    }
+
+    override fun setAlternateBouncerUIAvailable(isAvailable: Boolean) {
+        _isAlternateBouncerUIAvailable.value = isAvailable
+    }
+
+    override fun setPrimaryShow(isShowing: Boolean) {
+        _primaryBouncerShow.value = isShowing
+    }
+
+    override fun setPrimaryShowingSoon(showingSoon: Boolean) {
+        _primaryBouncerShowingSoon.value = showingSoon
+    }
+
+    override fun setPrimaryStartingToHide(startingToHide: Boolean) {
+        _primaryBouncerStartingToHide.value = startingToHide
+    }
+
+    override fun setPrimaryStartDisappearAnimation(runnable: Runnable?) {
+        _primaryBouncerDisappearAnimation.value = runnable
+    }
+
+    override fun setPanelExpansion(panelExpansion: Float) {
+        _panelExpansionAmount.value = panelExpansion
+    }
+
+    override fun setKeyguardPosition(keyguardPosition: Float) {
+        _keyguardPosition.value = keyguardPosition
+    }
+
+    override fun setResourceUpdateRequests(willUpdateResources: Boolean) {
+        _resourceUpdateRequests.value = willUpdateResources
+    }
+
+    override fun setShowMessage(bouncerShowMessageModel: BouncerShowMessageModel?) {
+        _showMessage.value = bouncerShowMessageModel
+    }
+
+    override fun setKeyguardAuthenticated(keyguardAuthenticated: Boolean?) {
+        _keyguardAuthenticated.value = keyguardAuthenticated
+    }
+
+    override fun setIsBackButtonEnabled(isBackButtonEnabled: Boolean) {
+        _isBackButtonEnabled.value = isBackButtonEnabled
+    }
+
+    override fun setSideFpsShowing(isShowing: Boolean) {
+        _sideFpsShowing.value = isShowing
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt
index f19e191..21a5eb7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt
@@ -36,9 +36,9 @@
  * A fake instance of [BroadcastDispatcher] for tests.
  *
  * Important: The *real* broadcast dispatcher will only send intents to receivers if the intent
- * matches the [IntentFilter] that the [BroadcastReceiver] was registered with. This fake class does
- * *not* do that matching by default. Use [sendIntentToMatchingReceiversOnly] to get the same
- * matching behavior as the real broadcast dispatcher.
+ * matches the [IntentFilter] that the [BroadcastReceiver] was registered with. This fake class
+ * exposes [sendIntentToMatchingReceiversOnly] to get the same matching behavior as the real
+ * broadcast dispatcher.
  */
 class FakeBroadcastDispatcher(
     context: SysuiTestableContext,
@@ -63,9 +63,6 @@
 
     private val receivers: MutableSet<InternalReceiver> = ConcurrentHashMap.newKeySet()
 
-    val registeredReceivers: Set<BroadcastReceiver>
-        get() = receivers.map { it.receiver }.toSet()
-
     override fun registerReceiverWithHandler(
         receiver: BroadcastReceiver,
         filter: IntentFilter,
@@ -115,11 +112,15 @@
         }
     }
 
+    val numReceiversRegistered: Int
+        get() = receivers.size
+
     fun cleanUpReceivers(testName: String) {
-        registeredReceivers.forEach {
-            Log.i(testName, "Receiver not unregistered from dispatcher: $it")
+        receivers.forEach {
+            val receiver = it.receiver
+            Log.i(testName, "Receiver not unregistered from dispatcher: $receiver")
             if (shouldFailOnLeakedReceiver) {
-                throw IllegalStateException("Receiver not unregistered from dispatcher: $it")
+                throw IllegalStateException("Receiver not unregistered from dispatcher: $receiver")
             }
         }
         receivers.clear()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBouncerMessageRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBouncerMessageRepository.kt
deleted file mode 100644
index b03b4ba..0000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBouncerMessageRepository.kt
+++ /dev/null
@@ -1,84 +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.keyguard.data.repository
-
-import com.android.systemui.keyguard.bouncer.data.repository.BouncerMessageRepository
-import com.android.systemui.keyguard.bouncer.shared.model.BouncerMessageModel
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
-
-class FakeBouncerMessageRepository : BouncerMessageRepository {
-    private val _primaryAuthMessage = MutableStateFlow<BouncerMessageModel?>(null)
-    override val primaryAuthMessage: StateFlow<BouncerMessageModel?>
-        get() = _primaryAuthMessage
-
-    private val _faceAcquisitionMessage = MutableStateFlow<BouncerMessageModel?>(null)
-    override val faceAcquisitionMessage: StateFlow<BouncerMessageModel?>
-        get() = _faceAcquisitionMessage
-    private val _fingerprintAcquisitionMessage = MutableStateFlow<BouncerMessageModel?>(null)
-    override val fingerprintAcquisitionMessage: StateFlow<BouncerMessageModel?>
-        get() = _fingerprintAcquisitionMessage
-    private val _customMessage = MutableStateFlow<BouncerMessageModel?>(null)
-    override val customMessage: StateFlow<BouncerMessageModel?>
-        get() = _customMessage
-    private val _biometricAuthMessage = MutableStateFlow<BouncerMessageModel?>(null)
-    override val biometricAuthMessage: StateFlow<BouncerMessageModel?>
-        get() = _biometricAuthMessage
-    private val _authFlagsMessage = MutableStateFlow<BouncerMessageModel?>(null)
-    override val authFlagsMessage: StateFlow<BouncerMessageModel?>
-        get() = _authFlagsMessage
-
-    private val _biometricLockedOutMessage = MutableStateFlow<BouncerMessageModel?>(null)
-    override val biometricLockedOutMessage: Flow<BouncerMessageModel?>
-        get() = _biometricLockedOutMessage
-
-    override fun setPrimaryAuthMessage(value: BouncerMessageModel?) {
-        _primaryAuthMessage.value = value
-    }
-
-    override fun setFaceAcquisitionMessage(value: BouncerMessageModel?) {
-        _faceAcquisitionMessage.value = value
-    }
-
-    override fun setFingerprintAcquisitionMessage(value: BouncerMessageModel?) {
-        _fingerprintAcquisitionMessage.value = value
-    }
-
-    override fun setCustomMessage(value: BouncerMessageModel?) {
-        _customMessage.value = value
-    }
-
-    fun setBiometricAuthMessage(value: BouncerMessageModel?) {
-        _biometricAuthMessage.value = value
-    }
-
-    fun setAuthFlagsMessage(value: BouncerMessageModel?) {
-        _authFlagsMessage.value = value
-    }
-
-    fun setBiometricLockedOutMessage(value: BouncerMessageModel?) {
-        _biometricLockedOutMessage.value = value
-    }
-
-    override fun clearMessage() {
-        _primaryAuthMessage.value = null
-        _faceAcquisitionMessage.value = null
-        _fingerprintAcquisitionMessage.value = null
-        _customMessage.value = null
-    }
-}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt
index 738f09d..548169e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt
@@ -17,8 +17,8 @@
 package com.android.systemui.keyguard.data.repository
 
 import com.android.keyguard.FaceAuthUiEvent
-import com.android.systemui.keyguard.shared.model.AuthenticationStatus
-import com.android.systemui.keyguard.shared.model.DetectionStatus
+import com.android.systemui.keyguard.shared.model.FaceAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.FaceDetectionStatus
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
@@ -29,19 +29,21 @@
 
     override val isAuthenticated = MutableStateFlow(false)
     override val canRunFaceAuth = MutableStateFlow(false)
-    private val _authenticationStatus = MutableStateFlow<AuthenticationStatus?>(null)
-    override val authenticationStatus: Flow<AuthenticationStatus> =
+    private val _authenticationStatus = MutableStateFlow<FaceAuthenticationStatus?>(null)
+    override val authenticationStatus: Flow<FaceAuthenticationStatus> =
         _authenticationStatus.filterNotNull()
-    fun setAuthenticationStatus(status: AuthenticationStatus) {
+    fun setAuthenticationStatus(status: FaceAuthenticationStatus) {
         _authenticationStatus.value = status
     }
-    private val _detectionStatus = MutableStateFlow<DetectionStatus?>(null)
-    override val detectionStatus: Flow<DetectionStatus>
+    private val _detectionStatus = MutableStateFlow<FaceDetectionStatus?>(null)
+    override val detectionStatus: Flow<FaceDetectionStatus>
         get() = _detectionStatus.filterNotNull()
-    fun setDetectionStatus(status: DetectionStatus) {
+    fun setDetectionStatus(status: FaceDetectionStatus) {
         _detectionStatus.value = status
     }
-    override val isLockedOut = MutableStateFlow(false)
+
+    private val _isLockedOut = MutableStateFlow(false)
+    override val isLockedOut = _isLockedOut
     private val _runningAuthRequest = MutableStateFlow<Pair<FaceAuthUiEvent, Boolean>?>(null)
     val runningAuthRequest: StateFlow<Pair<FaceAuthUiEvent, Boolean>?> =
         _runningAuthRequest.asStateFlow()
@@ -56,6 +58,10 @@
         _isAuthRunning.value = true
     }
 
+    fun setLockedOut(value: Boolean) {
+        _isLockedOut.value = value
+    }
+
     override fun cancel() {
         _isAuthRunning.value = false
         _runningAuthRequest.value = null
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFingerprintAuthRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFingerprintAuthRepository.kt
index 4bfd3d6..38791ca 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFingerprintAuthRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFingerprintAuthRepository.kt
@@ -17,32 +17,38 @@
 
 package com.android.systemui.keyguard.data.repository
 
+import com.android.systemui.keyguard.shared.model.FingerprintAuthenticationStatus
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.filterNotNull
 
 class FakeDeviceEntryFingerprintAuthRepository : DeviceEntryFingerprintAuthRepository {
     private val _isLockedOut = MutableStateFlow(false)
     override val isLockedOut: StateFlow<Boolean> = _isLockedOut.asStateFlow()
-
-    private val _isRunning = MutableStateFlow(false)
-    override val isRunning: Flow<Boolean>
-        get() = _isRunning
-
-    private var fpSensorType = MutableStateFlow<BiometricType?>(null)
-    override val availableFpSensorType: Flow<BiometricType?>
-        get() = fpSensorType
-
     fun setLockedOut(lockedOut: Boolean) {
         _isLockedOut.value = lockedOut
     }
 
+    private val _isRunning = MutableStateFlow(false)
+    override val isRunning: Flow<Boolean>
+        get() = _isRunning
     fun setIsRunning(value: Boolean) {
         _isRunning.value = value
     }
 
+    private var fpSensorType = MutableStateFlow<BiometricType?>(null)
+    override val availableFpSensorType: Flow<BiometricType?>
+        get() = fpSensorType
     fun setAvailableFpSensorType(value: BiometricType?) {
         fpSensorType.value = value
     }
+
+    private var _authenticationStatus = MutableStateFlow<FingerprintAuthenticationStatus?>(null)
+    override val authenticationStatus: Flow<FingerprintAuthenticationStatus>
+        get() = _authenticationStatus.filterNotNull()
+    fun setAuthenticationStatus(status: FingerprintAuthenticationStatus) {
+        _authenticationStatus.value = status
+    }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardBouncerRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardBouncerRepository.kt
deleted file mode 100644
index 8a6d2aa..0000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardBouncerRepository.kt
+++ /dev/null
@@ -1,114 +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.systemui.keyguard.data.repository
-
-import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN
-import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.asStateFlow
-
-/** Fake implementation of [KeyguardRepository] */
-class FakeKeyguardBouncerRepository : KeyguardBouncerRepository {
-    private val _primaryBouncerShow = MutableStateFlow(false)
-    override val primaryBouncerShow = _primaryBouncerShow.asStateFlow()
-    private val _primaryBouncerShowingSoon = MutableStateFlow(false)
-    override val primaryBouncerShowingSoon = _primaryBouncerShowingSoon.asStateFlow()
-    private val _primaryBouncerStartingToHide = MutableStateFlow(false)
-    override val primaryBouncerStartingToHide = _primaryBouncerStartingToHide.asStateFlow()
-    private val _primaryBouncerDisappearAnimation = MutableStateFlow<Runnable?>(null)
-    override val primaryBouncerStartingDisappearAnimation =
-        _primaryBouncerDisappearAnimation.asStateFlow()
-    private val _primaryBouncerScrimmed = MutableStateFlow(false)
-    override val primaryBouncerScrimmed = _primaryBouncerScrimmed.asStateFlow()
-    private val _panelExpansionAmount = MutableStateFlow(EXPANSION_HIDDEN)
-    override val panelExpansionAmount = _panelExpansionAmount.asStateFlow()
-    private val _keyguardPosition = MutableStateFlow(0f)
-    override val keyguardPosition = _keyguardPosition.asStateFlow()
-    private val _isBackButtonEnabled = MutableStateFlow<Boolean?>(null)
-    override val isBackButtonEnabled = _isBackButtonEnabled.asStateFlow()
-    private val _keyguardAuthenticated = MutableStateFlow<Boolean?>(null)
-    override val keyguardAuthenticated = _keyguardAuthenticated.asStateFlow()
-    private val _showMessage = MutableStateFlow<BouncerShowMessageModel?>(null)
-    override val showMessage = _showMessage.asStateFlow()
-    private val _resourceUpdateRequests = MutableStateFlow(false)
-    override val resourceUpdateRequests = _resourceUpdateRequests.asStateFlow()
-    private val _isAlternateBouncerVisible = MutableStateFlow(false)
-    override val alternateBouncerVisible = _isAlternateBouncerVisible.asStateFlow()
-    override var lastAlternateBouncerVisibleTime: Long = 0L
-    private val _isAlternateBouncerUIAvailable = MutableStateFlow<Boolean>(false)
-    override val alternateBouncerUIAvailable = _isAlternateBouncerUIAvailable.asStateFlow()
-    private val _sideFpsShowing: MutableStateFlow<Boolean> = MutableStateFlow(false)
-    override val sideFpsShowing: StateFlow<Boolean> = _sideFpsShowing.asStateFlow()
-
-    override fun setPrimaryScrimmed(isScrimmed: Boolean) {
-        _primaryBouncerScrimmed.value = isScrimmed
-    }
-
-    override fun setAlternateVisible(isVisible: Boolean) {
-        _isAlternateBouncerVisible.value = isVisible
-    }
-
-    override fun setAlternateBouncerUIAvailable(isAvailable: Boolean) {
-        _isAlternateBouncerUIAvailable.value = isAvailable
-    }
-
-    override fun setPrimaryShow(isShowing: Boolean) {
-        _primaryBouncerShow.value = isShowing
-    }
-
-    override fun setPrimaryShowingSoon(showingSoon: Boolean) {
-        _primaryBouncerShowingSoon.value = showingSoon
-    }
-
-    override fun setPrimaryStartingToHide(startingToHide: Boolean) {
-        _primaryBouncerStartingToHide.value = startingToHide
-    }
-
-    override fun setPrimaryStartDisappearAnimation(runnable: Runnable?) {
-        _primaryBouncerDisappearAnimation.value = runnable
-    }
-
-    override fun setPanelExpansion(panelExpansion: Float) {
-        _panelExpansionAmount.value = panelExpansion
-    }
-
-    override fun setKeyguardPosition(keyguardPosition: Float) {
-        _keyguardPosition.value = keyguardPosition
-    }
-
-    override fun setResourceUpdateRequests(willUpdateResources: Boolean) {
-        _resourceUpdateRequests.value = willUpdateResources
-    }
-
-    override fun setShowMessage(bouncerShowMessageModel: BouncerShowMessageModel?) {
-        _showMessage.value = bouncerShowMessageModel
-    }
-
-    override fun setKeyguardAuthenticated(keyguardAuthenticated: Boolean?) {
-        _keyguardAuthenticated.value = keyguardAuthenticated
-    }
-
-    override fun setIsBackButtonEnabled(isBackButtonEnabled: Boolean) {
-        _isBackButtonEnabled.value = isBackButtonEnabled
-    }
-
-    override fun setSideFpsShowing(isShowing: Boolean) {
-        _sideFpsShowing.value = isShowing
-    }
-}
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 b52a768..8428566 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
@@ -22,11 +22,14 @@
 import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
 import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
 import com.android.systemui.keyguard.shared.model.DozeTransitionModel
+import com.android.systemui.keyguard.shared.model.ScreenModel
+import com.android.systemui.keyguard.shared.model.ScreenState
 import com.android.systemui.keyguard.shared.model.StatusBarState
 import com.android.systemui.keyguard.shared.model.WakeSleepReason
 import com.android.systemui.keyguard.shared.model.WakefulnessModel
 import com.android.systemui.keyguard.shared.model.WakefulnessState
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
@@ -48,7 +51,7 @@
     override val isKeyguardShowing: Flow<Boolean> = _isKeyguardShowing
 
     private val _isKeyguardUnlocked = MutableStateFlow(false)
-    override val isKeyguardUnlocked: Flow<Boolean> = _isKeyguardUnlocked
+    override val isKeyguardUnlocked: StateFlow<Boolean> = _isKeyguardUnlocked.asStateFlow()
 
     private val _isKeyguardOccluded = MutableStateFlow(false)
     override val isKeyguardOccluded: Flow<Boolean> = _isKeyguardOccluded
@@ -56,6 +59,9 @@
     private val _isDozing = MutableStateFlow(false)
     override val isDozing: StateFlow<Boolean> = _isDozing
 
+    private val _dozeTimeTick = MutableSharedFlow<Unit>()
+    override val dozeTimeTick = _dozeTimeTick
+
     private val _lastDozeTapToWakePosition = MutableStateFlow<Point?>(null)
     override val lastDozeTapToWakePosition = _lastDozeTapToWakePosition.asStateFlow()
 
@@ -68,6 +74,9 @@
     private val _isDreamingWithOverlay = MutableStateFlow(false)
     override val isDreamingWithOverlay: Flow<Boolean> = _isDreamingWithOverlay
 
+    private val _isActiveDreamLockscreenHosted = MutableStateFlow(false)
+    override val isActiveDreamLockscreenHosted: StateFlow<Boolean> = _isActiveDreamLockscreenHosted
+
     private val _dozeAmount = MutableStateFlow(0f)
     override val linearDozeAmount: Flow<Float> = _dozeAmount
 
@@ -81,7 +90,10 @@
         MutableStateFlow(
             WakefulnessModel(WakefulnessState.ASLEEP, WakeSleepReason.OTHER, WakeSleepReason.OTHER)
         )
-    override val wakefulness: Flow<WakefulnessModel> = _wakefulnessModel
+    override val wakefulness = _wakefulnessModel
+
+    private val _screenModel = MutableStateFlow(ScreenModel(ScreenState.SCREEN_OFF))
+    override val screenModel = _screenModel
 
     private val _isUdfpsSupported = MutableStateFlow(false)
 
@@ -111,6 +123,11 @@
         return _isKeyguardShowing.value
     }
 
+    private var _isBypassEnabled = false
+    override fun isBypassEnabled(): Boolean {
+        return _isBypassEnabled
+    }
+
     override fun setAnimateDozingTransitions(animate: Boolean) {
         _animateBottomAreaDozingTransitions.tryEmit(animate)
     }
@@ -139,6 +156,10 @@
         _isDozing.value = isDozing
     }
 
+    override fun dozeTimeTick() {
+        _dozeTimeTick.tryEmit(Unit)
+    }
+
     override fun setLastDozeTapToWakePosition(position: Point) {
         _lastDozeTapToWakePosition.value = position
     }
@@ -155,6 +176,10 @@
         _isDreamingWithOverlay.value = isDreaming
     }
 
+    override fun setIsActiveDreamLockscreenHosted(isLockscreenHosted: Boolean) {
+        _isActiveDreamLockscreenHosted.value = isLockscreenHosted
+    }
+
     fun setDozeAmount(dozeAmount: Float) {
         _dozeAmount.value = dozeAmount
     }
@@ -187,6 +212,14 @@
         _statusBarState.value = state
     }
 
+    fun setKeyguardUnlocked(isUnlocked: Boolean) {
+        _isKeyguardUnlocked.value = isUnlocked
+    }
+
+    fun setBypassEnabled(isEnabled: Boolean) {
+        _isBypassEnabled = isEnabled
+    }
+
     override fun isUdfpsSupported(): Boolean {
         return _isUdfpsSupported.value
     }
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
new file mode 100644
index 0000000..f13c98d
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import com.android.systemui.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
+
+/**
+ * Simply put, I got tired of adding a constructor argument and then having to tweak dozens of
+ * files. This should alleviate some of the burden by providing defaults for testing.
+ */
+object KeyguardInteractorFactory {
+
+    @JvmOverloads
+    @JvmStatic
+    fun create(
+        featureFlags: FakeFeatureFlags = createFakeFeatureFlags(),
+        repository: FakeKeyguardRepository = FakeKeyguardRepository(),
+        commandQueue: FakeCommandQueue = FakeCommandQueue(),
+        bouncerRepository: FakeKeyguardBouncerRepository = FakeKeyguardBouncerRepository(),
+        configurationRepository: FakeConfigurationRepository = FakeConfigurationRepository(),
+    ): WithDependencies {
+        return WithDependencies(
+            repository = repository,
+            commandQueue = commandQueue,
+            featureFlags = featureFlags,
+            bouncerRepository = bouncerRepository,
+            configurationRepository = configurationRepository,
+            KeyguardInteractor(
+                repository = repository,
+                commandQueue = commandQueue,
+                featureFlags = featureFlags,
+                bouncerRepository = bouncerRepository,
+                configurationRepository = configurationRepository,
+            )
+        )
+    }
+
+    /** Provide defaults, otherwise tests will throw an error */
+    fun createFakeFeatureFlags(): FakeFeatureFlags {
+        return FakeFeatureFlags().apply { set(Flags.FACE_AUTH_REFACTOR, false) }
+    }
+
+    data class WithDependencies(
+        val repository: FakeKeyguardRepository,
+        val commandQueue: FakeCommandQueue,
+        val featureFlags: FakeFeatureFlags,
+        val bouncerRepository: FakeKeyguardBouncerRepository,
+        val configurationRepository: FakeConfigurationRepository,
+        val keyguardInteractor: KeyguardInteractor,
+    )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorFactory.kt
new file mode 100644
index 0000000..312ade5
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorFactory.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.keyguard.domain.interactor
+
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
+import kotlinx.coroutines.CoroutineScope
+
+/**
+ * Helper to create a new KeyguardTransitionInteractor in a way that doesn't require modifying 20+
+ * tests whenever we add a constructor param.
+ */
+object KeyguardTransitionInteractorFactory {
+    @JvmOverloads
+    @JvmStatic
+    fun create(
+        scope: CoroutineScope,
+        repository: KeyguardTransitionRepository = FakeKeyguardTransitionRepository(),
+    ): WithDependencies {
+        return WithDependencies(
+            repository = repository,
+            KeyguardTransitionInteractor(
+                scope = scope,
+                repository = repository,
+            )
+        )
+    }
+
+    data class WithDependencies(
+        val repository: KeyguardTransitionRepository,
+        val keyguardTransitionInteractor: KeyguardTransitionInteractor,
+    )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/power/data/repository/FakePowerRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/power/data/repository/FakePowerRepository.kt
index 15465f4..3334f3e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/power/data/repository/FakePowerRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/power/data/repository/FakePowerRepository.kt
@@ -17,6 +17,7 @@
 
 package com.android.systemui.power.data.repository
 
+import android.os.PowerManager
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.asStateFlow
@@ -28,7 +29,15 @@
     private val _isInteractive = MutableStateFlow(initialInteractive)
     override val isInteractive: Flow<Boolean> = _isInteractive.asStateFlow()
 
+    var lastWakeWhy: String? = null
+    var lastWakeReason: Int? = null
+
     fun setInteractive(value: Boolean) {
         _isInteractive.value = value
     }
+
+    override fun wakeUp(why: String, @PowerManager.WakeReason wakeReason: Int) {
+        lastWakeWhy = why
+        lastWakeReason = wakeReason
+    }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeAutoAddRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeAutoAddRepository.kt
new file mode 100644
index 0000000..9ea079f
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeAutoAddRepository.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.qs.pipeline.data.repository
+
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+
+class FakeAutoAddRepository : AutoAddRepository {
+
+    private val autoAddedTilesPerUser = mutableMapOf<Int, MutableStateFlow<Set<TileSpec>>>()
+
+    override fun autoAddedTiles(userId: Int): Flow<Set<TileSpec>> {
+        return getFlow(userId)
+    }
+
+    override suspend fun markTileAdded(userId: Int, spec: TileSpec) {
+        if (spec == TileSpec.Invalid) return
+        with(getFlow(userId)) { value = value.toMutableSet().apply { add(spec) } }
+    }
+
+    override suspend fun unmarkTileAdded(userId: Int, spec: TileSpec) {
+        with(getFlow(userId)) { value = value.toMutableSet().apply { remove(spec) } }
+    }
+
+    private fun getFlow(userId: Int): MutableStateFlow<Set<TileSpec>> =
+        autoAddedTilesPerUser.getOrPut(userId) { MutableStateFlow(emptySet()) }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/autoaddable/FakeAutoAddable.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/autoaddable/FakeAutoAddable.kt
new file mode 100644
index 0000000..ebdd6fd
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/autoaddable/FakeAutoAddable.kt
@@ -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.systemui.qs.pipeline.domain.autoaddable
+
+import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository.Companion.POSITION_AT_END
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
+import com.android.systemui.qs.pipeline.domain.model.AutoAddable
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.filterNotNull
+
+class FakeAutoAddable(
+    private val spec: TileSpec,
+    override val autoAddTracking: AutoAddTracking,
+) : AutoAddable {
+
+    private val signalsPerUser = mutableMapOf<Int, MutableStateFlow<AutoAddSignal?>>()
+    private fun getFlow(userId: Int): MutableStateFlow<AutoAddSignal?> =
+        signalsPerUser.getOrPut(userId) { MutableStateFlow(null) }
+
+    override fun autoAddSignal(userId: Int): Flow<AutoAddSignal> {
+        return getFlow(userId).asStateFlow().filterNotNull()
+    }
+
+    suspend fun sendRemoveSignal(userId: Int) {
+        getFlow(userId).value = AutoAddSignal.Remove(spec)
+    }
+
+    suspend fun sendAddSignal(userId: Int, position: Int = POSITION_AT_END) {
+        getFlow(userId).value = AutoAddSignal.Add(spec, position)
+    }
+
+    override val description: String
+        get() = "FakeAutoAddable($spec)"
+}
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 be3d54a..9317981 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
@@ -16,21 +16,41 @@
 
 package com.android.systemui.scene
 
+import android.content.pm.UserInfo
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.authentication.data.repository.AuthenticationRepository
-import com.android.systemui.authentication.data.repository.AuthenticationRepositoryImpl
+import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
 import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
-import com.android.systemui.bouncer.data.repo.BouncerRepository
+import com.android.systemui.bouncer.data.repository.BouncerRepository
+import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
 import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
 import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
+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.keyguard.data.repository.KeyguardRepository
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.LockscreenSceneInteractor
-import com.android.systemui.scene.data.model.SceneContainerConfig
+import com.android.systemui.keyguard.shared.model.WakeSleepReason
+import com.android.systemui.keyguard.shared.model.WakefulnessModel
+import com.android.systemui.keyguard.shared.model.WakefulnessState
 import com.android.systemui.scene.data.repository.SceneContainerRepository
 import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.model.SceneContainerConfig
+import com.android.systemui.scene.shared.model.SceneContainerNames
 import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.user.data.repository.UserRepository
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.StandardTestDispatcher
 import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.currentTime
 
 /**
  * Utilities for creating scene container framework related repositories, interactors, and
@@ -39,9 +59,38 @@
 @OptIn(ExperimentalCoroutinesApi::class)
 class SceneTestUtils(
     test: SysuiTestCase,
-    private val testScope: TestScope? = null,
 ) {
+    val testDispatcher = StandardTestDispatcher()
+    val testScope = TestScope(testDispatcher)
+    val featureFlags =
+        FakeFeatureFlags().apply {
+            set(Flags.SCENE_CONTAINER, true)
+            set(Flags.FACE_AUTH_REFACTOR, false)
+        }
+    private val userRepository: UserRepository by lazy {
+        FakeUserRepository().apply {
+            val users = listOf(UserInfo(/* id=  */ 0, "name", /* flags= */ 0))
+            setUserInfos(users)
+            runBlocking { setSelectedUserInfo(users.first()) }
+        }
+    }
 
+    val authenticationRepository: FakeAuthenticationRepository by lazy {
+        FakeAuthenticationRepository(
+            currentTime = { testScope.currentTime },
+        )
+    }
+    val keyguardRepository: FakeKeyguardRepository by lazy {
+        FakeKeyguardRepository().apply {
+            setWakefulnessModel(
+                WakefulnessModel(
+                    WakefulnessState.AWAKE,
+                    WakeSleepReason.OTHER,
+                    WakeSleepReason.OTHER,
+                )
+            )
+        }
+    }
     private val context = test.context
 
     fun fakeSceneContainerRepository(
@@ -51,7 +100,7 @@
                 fakeSceneContainerConfig(CONTAINER_2),
             )
     ): SceneContainerRepository {
-        return SceneContainerRepository(containerConfigurations)
+        return SceneContainerRepository(containerConfigurations.associateBy { it.name })
     }
 
     fun fakeSceneKeys(): List<SceneKey> {
@@ -81,8 +130,8 @@
         )
     }
 
-    fun authenticationRepository(): AuthenticationRepository {
-        return AuthenticationRepositoryImpl()
+    fun authenticationRepository(): FakeAuthenticationRepository {
+        return authenticationRepository
     }
 
     fun authenticationInteractor(
@@ -91,18 +140,25 @@
         return AuthenticationInteractor(
             applicationScope = applicationScope(),
             repository = repository,
+            backgroundDispatcher = testDispatcher,
+            userRepository = userRepository,
+            keyguardRepository = keyguardRepository,
+            clock = mock { whenever(elapsedRealtime()).thenAnswer { testScope.currentTime } }
         )
     }
 
-    private fun applicationScope(): CoroutineScope {
-        return checkNotNull(testScope) {
-                """
-                TestScope not initialized, please create a TestScope and inject it into
-                SceneTestUtils.
-            """
-                    .trimIndent()
-            }
-            .backgroundScope
+    fun keyguardRepository(): FakeKeyguardRepository {
+        return keyguardRepository
+    }
+
+    fun keyguardInteractor(repository: KeyguardRepository): KeyguardInteractor {
+        return KeyguardInteractor(
+            repository = repository,
+            commandQueue = FakeCommandQueue(),
+            featureFlags = featureFlags,
+            bouncerRepository = FakeKeyguardBouncerRepository(),
+            configurationRepository = FakeConfigurationRepository()
+        )
     }
 
     fun bouncerInteractor(
@@ -115,6 +171,7 @@
             repository = BouncerRepository(),
             authenticationInteractor = authenticationInteractor,
             sceneInteractor = sceneInteractor,
+            featureFlags = featureFlags,
             containerName = CONTAINER_1,
         )
     }
@@ -131,13 +188,13 @@
                         return bouncerInteractor
                     }
                 },
+            featureFlags = featureFlags,
             containerName = CONTAINER_1,
         )
     }
 
     fun lockScreenSceneInteractor(
         authenticationInteractor: AuthenticationInteractor,
-        sceneInteractor: SceneInteractor,
         bouncerInteractor: BouncerInteractor,
     ): LockscreenSceneInteractor {
         return LockscreenSceneInteractor(
@@ -149,13 +206,16 @@
                         return bouncerInteractor
                     }
                 },
-            sceneInteractor = sceneInteractor,
             containerName = CONTAINER_1,
         )
     }
 
+    private fun applicationScope(): CoroutineScope {
+        return testScope.backgroundScope
+    }
+
     companion object {
-        const val CONTAINER_1 = "container1"
+        const val CONTAINER_1 = SceneContainerNames.SYSTEM_UI_DEFAULT
         const val CONTAINER_2 = "container2"
     }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
index 61e5b5f..51ee0c0 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
@@ -19,18 +19,28 @@
 
 import android.content.pm.UserInfo
 import android.os.UserHandle
+import com.android.systemui.user.data.model.SelectedUserModel
+import com.android.systemui.user.data.model.SelectionStatus
 import com.android.systemui.user.data.model.UserSwitcherSettingsModel
 import java.util.concurrent.atomic.AtomicBoolean
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.yield
 
 class FakeUserRepository : UserRepository {
     companion object {
         // User id to represent a non system (human) user id. We presume this is the main user.
         private const val MAIN_USER_ID = 10
+
+        private val DEFAULT_SELECTED_USER = 0
+        private val DEFAULT_SELECTED_USER_INFO =
+            UserInfo(
+                /* id= */ DEFAULT_SELECTED_USER,
+                /* name= */ "default selected user",
+                /* flags= */ 0,
+            )
     }
 
     private val _userSwitcherSettings = MutableStateFlow(UserSwitcherSettingsModel())
@@ -40,8 +50,11 @@
     private val _userInfos = MutableStateFlow<List<UserInfo>>(emptyList())
     override val userInfos: Flow<List<UserInfo>> = _userInfos.asStateFlow()
 
-    private val _selectedUserInfo = MutableStateFlow<UserInfo?>(null)
-    override val selectedUserInfo: Flow<UserInfo> = _selectedUserInfo.filterNotNull()
+    override val selectedUser =
+        MutableStateFlow(
+            SelectedUserModel(DEFAULT_SELECTED_USER_INFO, SelectionStatus.SELECTION_COMPLETE)
+        )
+    override val selectedUserInfo: Flow<UserInfo> = selectedUser.map { it.userInfo }
 
     private val _userSwitchingInProgress = MutableStateFlow(false)
     override val userSwitchingInProgress: Flow<Boolean>
@@ -72,7 +85,7 @@
     }
 
     override fun getSelectedUserInfo(): UserInfo {
-        return checkNotNull(_selectedUserInfo.value)
+        return selectedUser.value.userInfo
     }
 
     override fun isSimpleUserSwitcher(): Boolean {
@@ -87,12 +100,15 @@
         _userInfos.value = infos
     }
 
-    suspend fun setSelectedUserInfo(userInfo: UserInfo) {
+    suspend fun setSelectedUserInfo(
+        userInfo: UserInfo,
+        selectionStatus: SelectionStatus = SelectionStatus.SELECTION_COMPLETE,
+    ) {
         check(_userInfos.value.contains(userInfo)) {
             "Cannot select the following user, it is not in the list of user infos: $userInfo!"
         }
 
-        _selectedUserInfo.value = userInfo
+        selectedUser.value = SelectedUserModel(userInfo, selectionStatus)
         yield()
     }
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
index c664c99..56837e8 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
@@ -21,7 +21,6 @@
 import com.android.systemui.statusbar.phone.StatusBarIconController.IconManager;
 import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.CallIndicatorIconState;
 import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
-import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
 
 import java.util.List;
 
@@ -62,10 +61,6 @@
     }
 
     @Override
-    public void setWifiIcon(String slot, WifiIconState state) {
-    }
-
-    @Override
     public void setNewWifiIcon() {
     }
 
diff --git a/packages/VpnDialogs/res/values-fr/strings.xml b/packages/VpnDialogs/res/values-fr/strings.xml
index cdec614..c70fd54 100644
--- a/packages/VpnDialogs/res/values-fr/strings.xml
+++ b/packages/VpnDialogs/res/values-fr/strings.xml
@@ -32,7 +32,7 @@
     <string name="always_on_disconnected_message_settings_link" msgid="6172280302829992412">"Modifier les paramètres VPN"</string>
     <string name="configure" msgid="4905518375574791375">"Configurer"</string>
     <string name="disconnect" msgid="971412338304200056">"Déconnecter"</string>
-    <string name="open_app" msgid="3717639178595958667">"Ouvrir l\'application"</string>
+    <string name="open_app" msgid="3717639178595958667">"Ouvrir l\'appli"</string>
     <string name="dismiss" msgid="6192859333764711227">"Ignorer"</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>
     <string name="sanitized_vpn_label" msgid="1877415015009794766">"<xliff:g id="SANITIZED_VPN_LABEL_0">%1$s</xliff:g> (<xliff:g id="SANITIZED_VPN_LABEL_1">%2$s</xliff:g>)"</string>
diff --git a/packages/VpnDialogs/res/values-zh-rHK/strings.xml b/packages/VpnDialogs/res/values-zh-rHK/strings.xml
index f3abf3c..f4d06e2 100644
--- a/packages/VpnDialogs/res/values-zh-rHK/strings.xml
+++ b/packages/VpnDialogs/res/values-zh-rHK/strings.xml
@@ -17,8 +17,8 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="prompt" msgid="3183836924226407828">"連線要求"</string>
-    <string name="warning" msgid="809658604548412033">"<xliff:g id="APP">%s</xliff:g> 要求設定 VPN 連線以監控網絡流量。除非您信任要求來源,否則請勿隨意接受要求。&lt;br /&gt; &lt;br /&gt;VPN 啟用時,畫面頂端會顯示 &lt;img src=vpn_icon /&gt;。"</string>
-    <string name="warning" product="tv" msgid="5188957997628124947">"「<xliff:g id="APP">%s</xliff:g>」要求設定 VPN 連線以監控網絡流量。除非您信任要求來源,否則請勿隨意接受要求。VPN 啟用時,畫面會顯示 &lt;br /&gt; &lt;br /&gt; &lt;img src=vpn_icon /&gt;。"</string>
+    <string name="warning" msgid="809658604548412033">"<xliff:g id="APP">%s</xliff:g> 要求設定 VPN 連線以監控網絡流量。除非你信任要求來源,否則請勿隨意接受要求。&lt;br /&gt; &lt;br /&gt;VPN 啟用時,畫面頂端會顯示 &lt;img src=vpn_icon /&gt;。"</string>
+    <string name="warning" product="tv" msgid="5188957997628124947">"「<xliff:g id="APP">%s</xliff:g>」要求設定 VPN 連線以監控網絡流量。除非你信任要求來源,否則請勿隨意接受要求。VPN 啟用時,畫面會顯示 &lt;br /&gt; &lt;br /&gt; &lt;img src=vpn_icon /&gt;。"</string>
     <string name="legacy_title" msgid="192936250066580964">"VPN 已連線"</string>
     <string name="session" msgid="6470628549473641030">"時段:"</string>
     <string name="duration" msgid="3584782459928719435">"持續時間︰"</string>
@@ -26,8 +26,8 @@
     <string name="data_received" msgid="4062776929376067820">"已接收:"</string>
     <string name="data_value_format" msgid="2192466557826897580">"<xliff:g id="NUMBER_0">%1$s</xliff:g> 位元組 / <xliff:g id="NUMBER_1">%2$s</xliff:g> 封包"</string>
     <string name="always_on_disconnected_title" msgid="1906740176262776166">"無法連線至保持開啟的 VPN"</string>
-    <string name="always_on_disconnected_message" msgid="555634519845992917">"<xliff:g id="VPN_APP_0">%1$s</xliff:g> 已設定為隨時保持連線,但目前無法連線。在重新連線至 <xliff:g id="VPN_APP_1">%1$s</xliff:g> 前,您的手機將會使用公共網絡。"</string>
-    <string name="always_on_disconnected_message_lockdown" msgid="4232225539869452120">"<xliff:g id="VPN_APP">%1$s</xliff:g> 已設定為隨時保持連線,但目前無法連線。在重新連線至 VPN 前,您將無法連線至網絡。"</string>
+    <string name="always_on_disconnected_message" msgid="555634519845992917">"<xliff:g id="VPN_APP_0">%1$s</xliff:g> 已設定為隨時保持連線,但目前無法連線。在重新連線至 <xliff:g id="VPN_APP_1">%1$s</xliff:g> 前,你的手機將會使用公共網絡。"</string>
+    <string name="always_on_disconnected_message_lockdown" msgid="4232225539869452120">"<xliff:g id="VPN_APP">%1$s</xliff:g> 已設定為隨時保持連線,但目前無法連線。在重新連線至 VPN 前,你將無法連線至網絡。"</string>
     <string name="always_on_disconnected_message_separator" msgid="3310614409322581371">" "</string>
     <string name="always_on_disconnected_message_settings_link" msgid="6172280302829992412">"變更 VPN 設定"</string>
     <string name="configure" msgid="4905518375574791375">"設定"</string>
diff --git a/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java b/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
index 308e2cf..3406102 100644
--- a/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
+++ b/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
@@ -359,11 +359,7 @@
         final File infoStage = new File(filesDir, WALLPAPER_INFO_STAGE);
         final File imageStage = new File(filesDir, SYSTEM_WALLPAPER_STAGE);
         final File lockImageStage = new File(filesDir, LOCK_WALLPAPER_STAGE);
-
-        // If we restored separate lock imagery, the system wallpaper should be
-        // applied as system-only; but if there's no separate lock image, make
-        // sure to apply the restored system wallpaper as both.
-        final int sysWhich = FLAG_SYSTEM | (lockImageStage.exists() ? 0 : FLAG_LOCK);
+        boolean lockImageStageExists = lockImageStage.exists();
 
         try {
             // First parse the live component name so that we know for logging if we care about
@@ -371,14 +367,31 @@
             ComponentName wpService = parseWallpaperComponent(infoStage, "wp");
             mSystemHasLiveComponent = wpService != null;
 
+            ComponentName kwpService = null;
+            boolean lockscreenLiveWallpaper = mWallpaperManager.isLockscreenLiveWallpaperEnabled();
+            if (lockscreenLiveWallpaper) {
+                kwpService = parseWallpaperComponent(infoStage, "kwp");
+            }
+            mLockHasLiveComponent = kwpService != null;
+            boolean separateLockWallpaper = mLockHasLiveComponent || lockImageStage.exists();
+
+            // if there's no separate lock wallpaper, apply the system wallpaper to both screens.
+            final int sysWhich = separateLockWallpaper ? FLAG_SYSTEM : FLAG_SYSTEM | FLAG_LOCK;
+
             // It is valid for the imagery to be absent; it means that we were not permitted
             // to back up the original image on the source device, or there was no user-supplied
             // wallpaper image present.
-            restoreFromStage(imageStage, infoStage, "wp", sysWhich);
-            restoreFromStage(lockImageStage, infoStage, "kwp", FLAG_LOCK);
+            if (!lockscreenLiveWallpaper) restoreFromStage(imageStage, infoStage, "wp", sysWhich);
+            if (lockImageStageExists) {
+                restoreFromStage(lockImageStage, infoStage, "kwp", FLAG_LOCK);
+            }
+            if (lockscreenLiveWallpaper) restoreFromStage(imageStage, infoStage, "wp", sysWhich);
 
             // And reset to the wallpaper service we should be using
-            updateWallpaperComponent(wpService, !lockImageStage.exists());
+            if (lockscreenLiveWallpaper && mLockHasLiveComponent) {
+                updateWallpaperComponent(kwpService, false, FLAG_LOCK);
+            }
+            updateWallpaperComponent(wpService, !lockImageStageExists, sysWhich);
         } catch (Exception e) {
             Slog.e(TAG, "Unable to restore wallpaper: " + e.getMessage());
             mEventLogger.onRestoreException(e);
@@ -397,9 +410,21 @@
     }
 
     @VisibleForTesting
-    void updateWallpaperComponent(ComponentName wpService, boolean applyToLock) throws IOException {
+    void updateWallpaperComponent(ComponentName wpService, boolean applyToLock, int which)
+            throws IOException {
+        boolean lockscreenLiveWallpaper = mWallpaperManager.isLockscreenLiveWallpaperEnabled();
         if (servicePackageExists(wpService)) {
             Slog.i(TAG, "Using wallpaper service " + wpService);
+            if (lockscreenLiveWallpaper) {
+                mWallpaperManager.setWallpaperComponentWithFlags(wpService, which);
+                if ((which & FLAG_LOCK) != 0) {
+                    mEventLogger.onLockLiveWallpaperRestored(wpService);
+                }
+                if ((which & FLAG_SYSTEM) != 0) {
+                    mEventLogger.onSystemLiveWallpaperRestored(wpService);
+                }
+                return;
+            }
             mWallpaperManager.setWallpaperComponent(wpService);
             if (applyToLock) {
                 // We have a live wallpaper and no static lock image,
@@ -414,7 +439,7 @@
             // in reports from users
             if (wpService != null) {
                 // TODO(b/268471749): Handle delayed case
-                applyComponentAtInstall(wpService, applyToLock);
+                applyComponentAtInstall(wpService, applyToLock, which);
                 Slog.w(TAG, "Wallpaper service " + wpService + " isn't available. "
                         + " Will try to apply later");
             }
@@ -437,7 +462,8 @@
                     // And log the success
                     if ((which & FLAG_SYSTEM) > 0) {
                         mEventLogger.onSystemImageWallpaperRestored();
-                    } else {
+                    }
+                    if ((which & FLAG_LOCK) > 0) {
                         mEventLogger.onLockImageWallpaperRestored();
                     }
                 }
@@ -460,7 +486,8 @@
     private void logRestoreError(int which, String error) {
         if ((which & FLAG_SYSTEM) == FLAG_SYSTEM) {
             mEventLogger.onSystemImageWallpaperRestoreFailed(error);
-        } else if ((which & FLAG_LOCK) == FLAG_LOCK) {
+        }
+        if ((which & FLAG_LOCK) == FLAG_LOCK) {
             mEventLogger.onLockImageWallpaperRestoreFailed(error);
         }
     }
@@ -552,16 +579,21 @@
         // Intentionally blank
     }
 
-    private void applyComponentAtInstall(ComponentName componentName, boolean applyToLock) {
-        PackageMonitor packageMonitor = getWallpaperPackageMonitor(componentName, applyToLock);
+    private void applyComponentAtInstall(ComponentName componentName, boolean applyToLock,
+            int which) {
+        PackageMonitor packageMonitor = getWallpaperPackageMonitor(
+                componentName, applyToLock, which);
         packageMonitor.register(getBaseContext(), null, UserHandle.ALL, true);
     }
 
     @VisibleForTesting
-    PackageMonitor getWallpaperPackageMonitor(ComponentName componentName, boolean applyToLock) {
+    PackageMonitor getWallpaperPackageMonitor(ComponentName componentName, boolean applyToLock,
+            int which) {
         return new PackageMonitor() {
             @Override
             public void onPackageAdded(String packageName, int uid) {
+                boolean lockscreenLiveWallpaper =
+                        mWallpaperManager.isLockscreenLiveWallpaperEnabled();
                 if (!isDeviceInRestore()) {
                     // We don't want to reapply the wallpaper outside a restore.
                     unregister();
@@ -582,16 +614,29 @@
 
                 if (componentName.getPackageName().equals(packageName)) {
                     Slog.d(TAG, "Applying component " + componentName);
-                    boolean sysResult = mWallpaperManager.setWallpaperComponent(componentName);
+                    boolean success = lockscreenLiveWallpaper
+                            ? mWallpaperManager.setWallpaperComponentWithFlags(componentName, which)
+                            : mWallpaperManager.setWallpaperComponent(componentName);
                     WallpaperEventLogger logger = new WallpaperEventLogger(
                             mBackupManager.getDelayedRestoreLogger());
-                    if (sysResult) {
-                        logger.onSystemLiveWallpaperRestored(componentName);
+                    if (success) {
+                        if (!lockscreenLiveWallpaper || (which & FLAG_SYSTEM) != 0) {
+                            logger.onSystemLiveWallpaperRestored(componentName);
+                        }
+                        if (lockscreenLiveWallpaper && (which & FLAG_LOCK) != 0) {
+                            logger.onLockLiveWallpaperRestored(componentName);
+                        }
                     } else {
-                        logger.onSystemLiveWallpaperRestoreFailed(
-                                WallpaperEventLogger.ERROR_SET_COMPONENT_EXCEPTION);
+                        if (!lockscreenLiveWallpaper || (which & FLAG_SYSTEM) != 0) {
+                            logger.onSystemLiveWallpaperRestoreFailed(
+                                    WallpaperEventLogger.ERROR_SET_COMPONENT_EXCEPTION);
+                        }
+                        if (lockscreenLiveWallpaper && (which & FLAG_LOCK) != 0) {
+                            logger.onLockLiveWallpaperRestoreFailed(
+                                    WallpaperEventLogger.ERROR_SET_COMPONENT_EXCEPTION);
+                        }
                     }
-                    if (applyToLock) {
+                    if (applyToLock && !lockscreenLiveWallpaper) {
                         try {
                             mWallpaperManager.clear(FLAG_LOCK);
                             logger.onLockLiveWallpaperRestored(componentName);
diff --git a/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java
index 9b07ad4..dc1126e 100644
--- a/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java
+++ b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java
@@ -117,6 +117,7 @@
     public void setUp() {
         MockitoAnnotations.initMocks(this);
 
+        when(mWallpaperManager.isLockscreenLiveWallpaperEnabled()).thenReturn(true);
         when(mWallpaperManager.isWallpaperBackupEligible(eq(FLAG_SYSTEM))).thenReturn(true);
         when(mWallpaperManager.isWallpaperBackupEligible(eq(FLAG_LOCK))).thenReturn(true);
 
@@ -364,14 +365,23 @@
         mWallpaperBackupAgent.mIsDeviceInRestore = true;
 
         mWallpaperBackupAgent.updateWallpaperComponent(mWallpaperComponent,
-                /* applyToLock */ true);
+                /* applyToLock */ true, FLAG_LOCK | FLAG_SYSTEM);
 
         // Imitate wallpaper component installation.
         mWallpaperBackupAgent.mWallpaperPackageMonitor.onPackageAdded(TEST_WALLPAPER_PACKAGE,
                 /* uid */0);
-
-        verify(mWallpaperManager, times(1)).setWallpaperComponent(mWallpaperComponent);
-        verify(mWallpaperManager, times(1)).clear(eq(FLAG_LOCK));
+        if (mWallpaperManager.isLockscreenLiveWallpaperEnabled()) {
+            verify(mWallpaperManager, times(1))
+                    .setWallpaperComponentWithFlags(mWallpaperComponent, FLAG_LOCK | FLAG_SYSTEM);
+            verify(mWallpaperManager, never())
+                    .setWallpaperComponentWithFlags(mWallpaperComponent, FLAG_SYSTEM);
+            verify(mWallpaperManager, never())
+                    .setWallpaperComponentWithFlags(mWallpaperComponent, FLAG_LOCK);
+            verify(mWallpaperManager, never()).clear(anyInt());
+        } else {
+            verify(mWallpaperManager, times(1)).setWallpaperComponent(mWallpaperComponent);
+            verify(mWallpaperManager, times(1)).clear(eq(FLAG_LOCK));
+        }
     }
 
     @Test
@@ -380,14 +390,24 @@
         mWallpaperBackupAgent.mIsDeviceInRestore = true;
 
         mWallpaperBackupAgent.updateWallpaperComponent(mWallpaperComponent,
-                /* applyToLock */ false);
+                /* applyToLock */ false, FLAG_SYSTEM);
 
         // Imitate wallpaper component installation.
         mWallpaperBackupAgent.mWallpaperPackageMonitor.onPackageAdded(TEST_WALLPAPER_PACKAGE,
                 /* uid */0);
 
-        verify(mWallpaperManager, times(1)).setWallpaperComponent(mWallpaperComponent);
-        verify(mWallpaperManager, never()).clear(eq(FLAG_LOCK));
+        if (mWallpaperManager.isLockscreenLiveWallpaperEnabled()) {
+            verify(mWallpaperManager, times(1))
+                    .setWallpaperComponentWithFlags(mWallpaperComponent, FLAG_SYSTEM);
+            verify(mWallpaperManager, never())
+                    .setWallpaperComponentWithFlags(mWallpaperComponent, FLAG_LOCK);
+            verify(mWallpaperManager, never())
+                    .setWallpaperComponentWithFlags(mWallpaperComponent, FLAG_LOCK | FLAG_SYSTEM);
+            verify(mWallpaperManager, never()).clear(anyInt());
+        } else {
+            verify(mWallpaperManager, times(1)).setWallpaperComponent(mWallpaperComponent);
+            verify(mWallpaperManager, never()).clear(eq(FLAG_LOCK));
+        }
     }
 
     @Test
@@ -396,7 +416,7 @@
         mWallpaperBackupAgent.mIsDeviceInRestore = false;
 
         mWallpaperBackupAgent.updateWallpaperComponent(mWallpaperComponent,
-                /* applyToLock */ true);
+                /* applyToLock */ true, FLAG_LOCK | FLAG_SYSTEM);
 
         // Imitate wallpaper component installation.
         mWallpaperBackupAgent.mWallpaperPackageMonitor.onPackageAdded(TEST_WALLPAPER_PACKAGE,
@@ -412,7 +432,7 @@
         mWallpaperBackupAgent.mIsDeviceInRestore = false;
 
         mWallpaperBackupAgent.updateWallpaperComponent(mWallpaperComponent,
-                /* applyToLock */ true);
+                /* applyToLock */ true, FLAG_LOCK | FLAG_SYSTEM);
 
         // Imitate "wrong" wallpaper component installation.
         mWallpaperBackupAgent.mWallpaperPackageMonitor.onPackageAdded(/* packageName */"",
@@ -614,6 +634,13 @@
                 mWallpaperBackupAgent.getBackupRestoreEventLogger().getLoggingResults());
         assertThat(result).isNotNull();
         assertThat(result.getSuccessCount()).isEqualTo(1);
+
+        if (mWallpaperManager.isLockscreenLiveWallpaperEnabled()) {
+            result = getLoggingResult(WALLPAPER_IMG_LOCK,
+                    mWallpaperBackupAgent.getBackupRestoreEventLogger().getLoggingResults());
+            assertThat(result).isNotNull();
+            assertThat(result.getSuccessCount()).isEqualTo(1);
+        }
     }
 
     @Test
@@ -649,19 +676,20 @@
     }
 
     @Test
-    public void testOnRestore_lockWallpaperImgMissingAndNoLive_logsFailure() throws Exception {
+    public void testOnRestore_wallpaperImgMissingAndNoLive_logsFailure() throws Exception {
         mockStagedWallpaperFile(WALLPAPER_INFO_STAGE);
-        mockStagedWallpaperFile(SYSTEM_WALLPAPER_STAGE);
         mWallpaperBackupAgent.onCreate(USER_HANDLE, BackupAnnotations.BackupDestination.CLOUD,
                 BackupAnnotations.OperationType.RESTORE);
 
         mWallpaperBackupAgent.onRestoreFinished();
 
-        DataTypeResult result = getLoggingResult(WALLPAPER_IMG_LOCK,
-                mWallpaperBackupAgent.getBackupRestoreEventLogger().getLoggingResults());
-        assertThat(result).isNotNull();
-        assertThat(result.getFailCount()).isEqualTo(1);
-        assertThat(result.getErrors()).containsKey(ERROR_NO_WALLPAPER);
+        for (String wallpaper: List.of(WALLPAPER_IMG_LOCK, WALLPAPER_IMG_SYSTEM)) {
+            DataTypeResult result = getLoggingResult(wallpaper,
+                    mWallpaperBackupAgent.getBackupRestoreEventLogger().getLoggingResults());
+            assertThat(result).isNotNull();
+            assertThat(result.getFailCount()).isEqualTo(1);
+            assertThat(result.getErrors()).containsKey(ERROR_NO_WALLPAPER);
+        }
     }
 
     @Test
@@ -722,13 +750,15 @@
     public void testUpdateWallpaperComponent_delayedRestore_logsSuccess() throws Exception {
         mWallpaperBackupAgent.mIsDeviceInRestore = true;
         when(mWallpaperManager.setWallpaperComponent(any())).thenReturn(true);
+        when(mWallpaperManager.setWallpaperComponentWithFlags(any(), eq(FLAG_LOCK | FLAG_SYSTEM)))
+                .thenReturn(true);
         BackupRestoreEventLogger logger = new BackupRestoreEventLogger(
                 BackupAnnotations.OperationType.RESTORE);
         when(mBackupManager.getDelayedRestoreLogger()).thenReturn(logger);
         mWallpaperBackupAgent.setBackupManagerForTesting(mBackupManager);
 
         mWallpaperBackupAgent.updateWallpaperComponent(mWallpaperComponent,
-                /* applyToLock */ true);
+                /* applyToLock */ true, FLAG_LOCK | FLAG_SYSTEM);
         // Imitate wallpaper component installation.
         mWallpaperBackupAgent.mWallpaperPackageMonitor.onPackageAdded(TEST_WALLPAPER_PACKAGE,
                 /* uid */0);
@@ -752,7 +782,7 @@
         mWallpaperBackupAgent.setBackupManagerForTesting(mBackupManager);
 
         mWallpaperBackupAgent.updateWallpaperComponent(mWallpaperComponent,
-                /* applyToLock */ true);
+                /* applyToLock */ true, FLAG_LOCK | FLAG_SYSTEM);
         // Imitate wallpaper component installation.
         mWallpaperBackupAgent.mWallpaperPackageMonitor.onPackageAdded(TEST_WALLPAPER_PACKAGE,
                 /* uid */0);
@@ -774,7 +804,7 @@
         mWallpaperBackupAgent.setBackupManagerForTesting(mBackupManager);
 
         mWallpaperBackupAgent.updateWallpaperComponent(mWallpaperComponent,
-                /* applyToLock */ true);
+                /* applyToLock */ true, FLAG_LOCK | FLAG_SYSTEM);
 
         // Imitate wallpaper component installation.
         mWallpaperBackupAgent.mWallpaperPackageMonitor.onPackageAdded(TEST_WALLPAPER_PACKAGE,
@@ -909,8 +939,9 @@
 
         @Override
         PackageMonitor getWallpaperPackageMonitor(ComponentName componentName,
-                boolean applyToLock) {
-            mWallpaperPackageMonitor = super.getWallpaperPackageMonitor(componentName, applyToLock);
+                boolean applyToLock, int which) {
+            mWallpaperPackageMonitor = super.getWallpaperPackageMonitor(
+                    componentName, applyToLock, which);
             return mWallpaperPackageMonitor;
         }
 
diff --git a/packages/overlays/NoCutoutOverlay/res/values-mk/strings.xml b/packages/overlays/NoCutoutOverlay/res/values-mk/strings.xml
index 505c205..0c821f2 100644
--- a/packages/overlays/NoCutoutOverlay/res/values-mk/strings.xml
+++ b/packages/overlays/NoCutoutOverlay/res/values-mk/strings.xml
@@ -17,5 +17,5 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="display_cutout_emulation_overlay" msgid="9031691255599853162">"Сокриј"</string>
+    <string name="display_cutout_emulation_overlay" msgid="9031691255599853162">"Скриј"</string>
 </resources>
diff --git a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
index c2ebddf..502ee4d 100644
--- a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
+++ b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
@@ -75,6 +75,7 @@
 import android.util.Size;
 import android.view.Surface;
 
+import androidx.annotation.NonNull;
 import androidx.camera.extensions.impl.AutoImageCaptureExtenderImpl;
 import androidx.camera.extensions.impl.AutoPreviewExtenderImpl;
 import androidx.camera.extensions.impl.BeautyImageCaptureExtenderImpl;
@@ -204,7 +205,7 @@
      * A per-process global camera extension manager instance, to track and
      * initialize/release extensions depending on client activity.
      */
-    private static final class CameraExtensionManagerGlobal {
+    private static final class CameraExtensionManagerGlobal implements IBinder.DeathRecipient {
         private static final String TAG = "CameraExtensionManagerGlobal";
         private final int EXTENSION_DELAY_MS = 1000;
 
@@ -212,8 +213,9 @@
         private final HandlerThread mHandlerThread;
         private final Object mLock = new Object();
 
-        private long mCurrentClientCount = 0;
-        private ArraySet<Long> mActiveClients = new ArraySet<>();
+        private ArraySet<IBinder> mActiveClients = new ArraySet<>();
+        private HashMap<IBinder, ArraySet<IBinder.DeathRecipient>> mClientDeathRecipient =
+                new HashMap<>();
         private IInitializeSessionCallback mInitializeCb = null;
 
         // Singleton instance
@@ -314,8 +316,20 @@
             return GLOBAL_CAMERA_MANAGER;
         }
 
-        public long registerClient(Context ctx) {
+        public boolean registerClient(Context ctx, IBinder token) {
             synchronized (mLock) {
+                if (mActiveClients.contains(token)) {
+                    Log.e(TAG, "Failed to register existing client!");
+                    return false;
+                }
+
+                try {
+                    token.linkToDeath(this, 0);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "Failed to link to binder token!");
+                    return false;
+                }
+
                 if (INIT_API_SUPPORTED) {
                     if (mActiveClients.isEmpty()) {
                         InitializerFuture status = new InitializerFuture();
@@ -327,43 +341,76 @@
                                     TimeUnit.MILLISECONDS);
                         } catch (TimeoutException e) {
                             Log.e(TAG, "Timed out while initializing camera extensions!");
-                            return -1;
+                            return false;
                         }
                         if (!initSuccess) {
                             Log.e(TAG, "Failed while initializing camera extensions!");
-                            return -1;
+                            return false;
                         }
                     }
                 }
 
-                long ret = mCurrentClientCount;
-                mCurrentClientCount++;
-                if (mCurrentClientCount < 0) {
-                    mCurrentClientCount = 0;
-                }
-                mActiveClients.add(ret);
+                mActiveClients.add(token);
+                mClientDeathRecipient.put(token, new ArraySet<>());
 
-                return ret;
+                return true;
             }
         }
 
-        public void unregisterClient(long clientId) {
+        public void unregisterClient(IBinder token) {
             synchronized (mLock) {
-                if (mActiveClients.remove(clientId) && mActiveClients.isEmpty() &&
-                        INIT_API_SUPPORTED) {
-                    InitializerFuture status = new InitializerFuture();
-                    InitializerImpl.deinit(new ReleaseHandler(status),
-                            new HandlerExecutor(mHandler));
-                    boolean releaseSuccess;
-                    try {
-                        releaseSuccess = status.get(EXTENSION_DELAY_MS, TimeUnit.MILLISECONDS);
-                    } catch (TimeoutException e) {
-                        Log.e(TAG, "Timed out while releasing camera extensions!");
-                        return;
+                if (mActiveClients.remove(token)) {
+                    token.unlinkToDeath(this, 0);
+                    mClientDeathRecipient.remove(token);
+                    if (mActiveClients.isEmpty() && INIT_API_SUPPORTED) {
+                        InitializerFuture status = new InitializerFuture();
+                        InitializerImpl.deinit(new ReleaseHandler(status),
+                                new HandlerExecutor(mHandler));
+                        boolean releaseSuccess;
+                        try {
+                            releaseSuccess = status.get(EXTENSION_DELAY_MS, TimeUnit.MILLISECONDS);
+                        } catch (TimeoutException e) {
+                            Log.e(TAG, "Timed out while releasing camera extensions!");
+                            return;
+                        }
+                        if (!releaseSuccess) {
+                            Log.e(TAG, "Failed while releasing camera extensions!");
+                        }
                     }
-                    if (!releaseSuccess) {
-                        Log.e(TAG, "Failed while releasing camera extensions!");
-                    }
+                }
+            }
+        }
+
+        @Override
+        public void binderDied() {
+            // Do nothing, handled below
+        }
+
+        @Override
+        public void binderDied(@NonNull IBinder who) {
+            synchronized (mLock) {
+                if (mClientDeathRecipient.containsKey(who)) {
+                    mClientDeathRecipient.get(who).stream().forEach(
+                            recipient -> recipient.binderDied(who));
+                }
+                unregisterClient(who);
+            }
+        }
+
+        public void registerDeathRecipient(IBinder token, IBinder.DeathRecipient recipient) {
+            synchronized (mLock) {
+                if (mClientDeathRecipient.containsKey(token)) {
+                    ArraySet<IBinder.DeathRecipient> recipients = mClientDeathRecipient.get(token);
+                    recipients.add(recipient);
+                }
+            }
+        }
+
+        public void unregisterDeathRecipient(IBinder token, IBinder.DeathRecipient recipient) {
+            synchronized (mLock) {
+                if (mClientDeathRecipient.containsKey(token)) {
+                    ArraySet<IBinder.DeathRecipient> recipients = mClientDeathRecipient.get(token);
+                    recipients.remove(recipient);
                 }
             }
         }
@@ -406,21 +453,35 @@
     /**
      * @hide
      */
-    private static long registerClient(Context ctx) {
+    private static boolean registerClient(Context ctx, IBinder token) {
         if (!EXTENSIONS_PRESENT) {
-            return -1;
+            return false;
         }
-        return CameraExtensionManagerGlobal.get().registerClient(ctx);
+        return CameraExtensionManagerGlobal.get().registerClient(ctx, token);
     }
 
     /**
      * @hide
      */
-    public static void unregisterClient(long clientId) {
+    public static void unregisterClient(IBinder token) {
         if (!EXTENSIONS_PRESENT) {
             return;
         }
-        CameraExtensionManagerGlobal.get().unregisterClient(clientId);
+        CameraExtensionManagerGlobal.get().unregisterClient(token);
+    }
+
+    /**
+     * @hide
+     */
+    private static void registerDeathRecipient(IBinder token, IBinder.DeathRecipient recipient) {
+        CameraExtensionManagerGlobal.get().registerDeathRecipient(token, recipient);
+    }
+
+    /**
+     * @hide
+     */
+    private static void unregisterDeathRecipient(IBinder token, IBinder.DeathRecipient recipient) {
+        CameraExtensionManagerGlobal.get().unregisterDeathRecipient(token, recipient);
     }
 
     /**
@@ -649,13 +710,14 @@
 
     private class CameraExtensionsProxyServiceStub extends ICameraExtensionsProxyService.Stub {
         @Override
-        public long registerClient() {
-            return CameraExtensionsProxyService.registerClient(CameraExtensionsProxyService.this);
+        public boolean registerClient(IBinder token) {
+            return CameraExtensionsProxyService.registerClient(CameraExtensionsProxyService.this,
+                    token);
         }
 
         @Override
-        public void unregisterClient(long clientId) {
-            CameraExtensionsProxyService.unregisterClient(clientId);
+        public void unregisterClient(IBinder token) {
+            CameraExtensionsProxyService.unregisterClient(token);
         }
 
         private boolean checkCameraPermission() {
@@ -1192,16 +1254,18 @@
         }
     }
 
-    private class SessionProcessorImplStub extends ISessionProcessorImpl.Stub {
+    private class SessionProcessorImplStub extends ISessionProcessorImpl.Stub implements
+            IBinder.DeathRecipient {
         private final SessionProcessorImpl mSessionProcessor;
         private String mCameraId = null;
+        private IBinder mToken;
 
         public SessionProcessorImplStub(SessionProcessorImpl sessionProcessor) {
             mSessionProcessor = sessionProcessor;
         }
 
         @Override
-        public CameraSessionConfig initSession(String cameraId,
+        public CameraSessionConfig initSession(IBinder token, String cameraId,
                 Map<String, CameraMetadataNative> charsMapNative, OutputSurface previewSurface,
                 OutputSurface imageCaptureSurface, OutputSurface postviewSurface) {
             OutputSurfaceImplStub outputPreviewSurfaceImpl =
@@ -1253,12 +1317,14 @@
             ret.sessionParameter = initializeParcelableMetadata(
                     sessionConfig.getSessionParameters(), cameraId);
             mCameraId = cameraId;
-
+            mToken = token;
+            CameraExtensionsProxyService.registerDeathRecipient(mToken, this);
             return ret;
         }
 
         @Override
-        public void deInitSession() {
+        public void deInitSession(IBinder token) {
+            CameraExtensionsProxyService.unregisterDeathRecipient(mToken, this);
             mSessionProcessor.deInitSession();
         }
 
@@ -1330,6 +1396,11 @@
 
             return null;
         }
+
+        @Override
+        public void binderDied() {
+            mSessionProcessor.deInitSession();
+        }
     }
 
     private class OutputSurfaceConfigurationImplStub implements OutputSurfaceConfigurationImpl {
@@ -1395,24 +1466,31 @@
         }
     }
 
-    private class PreviewExtenderImplStub extends IPreviewExtenderImpl.Stub {
+    private class PreviewExtenderImplStub extends IPreviewExtenderImpl.Stub implements
+            IBinder.DeathRecipient {
         private final PreviewExtenderImpl mPreviewExtender;
         private String mCameraId = null;
+        private boolean mSessionEnabled;
+        private IBinder mToken;
 
         public PreviewExtenderImplStub(PreviewExtenderImpl previewExtender) {
             mPreviewExtender = previewExtender;
         }
 
         @Override
-        public void onInit(String cameraId, CameraMetadataNative cameraCharacteristics) {
+        public void onInit(IBinder token, String cameraId,
+                CameraMetadataNative cameraCharacteristics) {
             mCameraId = cameraId;
             CameraCharacteristics chars = new CameraCharacteristics(cameraCharacteristics);
             mCameraManager.registerDeviceStateListener(chars);
             mPreviewExtender.onInit(cameraId, chars, CameraExtensionsProxyService.this);
+            mToken = token;
+            CameraExtensionsProxyService.registerDeathRecipient(mToken, this);
         }
 
         @Override
-        public void onDeInit() {
+        public void onDeInit(IBinder token) {
+            CameraExtensionsProxyService.unregisterDeathRecipient(mToken, this);
             mPreviewExtender.onDeInit();
         }
 
@@ -1423,11 +1501,13 @@
 
         @Override
         public CaptureStageImpl onEnableSession() {
+            mSessionEnabled = true;
             return initializeParcelable(mPreviewExtender.onEnableSession(), mCameraId);
         }
 
         @Override
         public CaptureStageImpl onDisableSession() {
+            mSessionEnabled = false;
             return initializeParcelable(mPreviewExtender.onDisableSession(), mCameraId);
         }
 
@@ -1516,26 +1596,41 @@
             }
             return null;
         }
+
+        @Override
+        public void binderDied() {
+            if (mSessionEnabled) {
+                mPreviewExtender.onDisableSession();
+            }
+            mPreviewExtender.onDeInit();
+        }
     }
 
-    private class ImageCaptureExtenderImplStub extends IImageCaptureExtenderImpl.Stub {
+    private class ImageCaptureExtenderImplStub extends IImageCaptureExtenderImpl.Stub implements
+            IBinder.DeathRecipient {
         private final ImageCaptureExtenderImpl mImageExtender;
         private String mCameraId = null;
+        private boolean mSessionEnabled;
+        private IBinder mToken;
 
         public ImageCaptureExtenderImplStub(ImageCaptureExtenderImpl imageExtender) {
             mImageExtender = imageExtender;
         }
 
         @Override
-        public void onInit(String cameraId, CameraMetadataNative cameraCharacteristics) {
+        public void onInit(IBinder token, String cameraId,
+                CameraMetadataNative cameraCharacteristics) {
             CameraCharacteristics chars = new CameraCharacteristics(cameraCharacteristics);
             mCameraManager.registerDeviceStateListener(chars);
             mImageExtender.onInit(cameraId, chars, CameraExtensionsProxyService.this);
             mCameraId = cameraId;
+            mToken = token;
+            CameraExtensionsProxyService.registerDeathRecipient(mToken, this);
         }
 
         @Override
-        public void onDeInit() {
+        public void onDeInit(IBinder token) {
+            CameraExtensionsProxyService.unregisterDeathRecipient(mToken, this);
             mImageExtender.onDeInit();
         }
 
@@ -1564,11 +1659,13 @@
 
         @Override
         public CaptureStageImpl onEnableSession() {
+            mSessionEnabled = true;
             return initializeParcelable(mImageExtender.onEnableSession(), mCameraId);
         }
 
         @Override
         public CaptureStageImpl onDisableSession() {
+            mSessionEnabled = false;
             return initializeParcelable(mImageExtender.onDisableSession(), mCameraId);
         }
 
@@ -1737,6 +1834,14 @@
 
             return null;
         }
+
+        @Override
+        public void binderDied() {
+            if (mSessionEnabled) {
+                mImageExtender.onDisableSession();
+            }
+            mImageExtender.onDeInit();
+        }
     }
 
     private class ProcessResultCallback implements ProcessResultImpl {
diff --git a/rs/jni/Android.bp b/rs/jni/Android.bp
index 8a6897c..f732c21 100644
--- a/rs/jni/Android.bp
+++ b/rs/jni/Android.bp
@@ -22,6 +22,7 @@
 cc_library_shared {
     name: "librs_jni",
 
+    cpp_std: "gnu++2b",
     srcs: ["android_renderscript_RenderScript.cpp"],
 
     shared_libs: [
diff --git a/services/Android.bp b/services/Android.bp
index b0a0e5e..53dc068 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -58,7 +58,12 @@
                 optimize: false,
                 shrink: true,
                 ignore_warnings: false,
-                proguard_flags_files: ["proguard.flags"],
+                proguard_flags_files: [
+                    "proguard.flags",
+                    // Ensure classes referenced in the framework-res manifest
+                    // and implemented in system_server are kept.
+                    ":framework-res{.aapt.proguardOptionsFile}",
+                ],
             },
             conditions_default: {
                 optimize: {
@@ -159,6 +164,7 @@
         "services.coverage",
         "services.credentials",
         "services.devicepolicy",
+        "services.flags",
         "services.midi",
         "services.musicsearch",
         "services.net",
diff --git a/services/accessibility/TEST_MAPPING b/services/accessibility/TEST_MAPPING
index 2b8fee3..299d33f 100644
--- a/services/accessibility/TEST_MAPPING
+++ b/services/accessibility/TEST_MAPPING
@@ -4,9 +4,6 @@
       "name": "CtsAccessibilityServiceTestCases",
       "options": [
         {
-          "include-annotation": "android.platform.test.annotations.Presubmit"
-        },
-        {
           "exclude-annotation": "androidx.test.filters.FlakyTest"
         }
       ]
@@ -15,9 +12,6 @@
       "name": "CtsAccessibilityTestCases",
       "options": [
         {
-          "include-annotation": "android.platform.test.annotations.Presubmit"
-        },
-        {
           "exclude-annotation": "androidx.test.filters.FlakyTest"
         }
       ]
@@ -26,9 +20,6 @@
       "name": "CtsUiAutomationTestCases",
       "options": [
         {
-          "include-annotation": "android.platform.test.annotations.Presubmit"
-        },
-        {
           "exclude-annotation": "androidx.test.filters.FlakyTest"
         }
       ]
@@ -48,20 +39,15 @@
       "name": "FrameworksCoreTests",
       "options": [
         {
-          "include-filter": "com.android.internal.accessibility"
+          "include-filter": "android.accessibilityservice"
         },
         {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        }
-      ]
-    },
-    {
-      "name": "FrameworksCoreTests",
-      "options": [
-        {
           "include-filter": "android.view.accessibility"
         },
         {
+          "include-filter": "com.android.internal.accessibility"
+        },
+        {
           "exclude-annotation": "androidx.test.filters.FlakyTest"
         }
       ]
@@ -92,15 +78,13 @@
       "name": "FrameworksCoreTests",
       "options": [
         {
-          "include-filter": "com.android.internal.accessibility"
-        }
-      ]
-    },
-    {
-      "name": "FrameworksCoreTests",
-      "options": [
+          "include-filter": "android.accessibilityservice"
+        },
         {
           "include-filter": "android.view.accessibility"
+        },
+        {
+          "include-filter": "com.android.internal.accessibility"
         }
       ]
     }
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index e89e33f..05b6eb4 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -319,7 +319,11 @@
 
         void unbindImeLocked(AbstractAccessibilityServiceConnection connection);
 
-        void attachAccessibilityOverlayToDisplay(int displayId, SurfaceControl sc);
+        void attachAccessibilityOverlayToDisplay(
+                int interactionId,
+                int displayId,
+                SurfaceControl sc,
+                IAccessibilityInteractionConnectionCallback callback);
 
     }
 
@@ -451,7 +455,12 @@
         if (svcConnTracingEnabled()) {
             logTraceSvcConn("setOnKeyEventResult", "handled=" + handled + ";sequence=" + sequence);
         }
-        mSystemSupport.getKeyEventDispatcher().setOnKeyEventResult(this, handled, sequence);
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            mSystemSupport.getKeyEventDispatcher().setOnKeyEventResult(this, handled, sequence);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
     }
 
     @Override
@@ -478,6 +487,10 @@
         if (svcConnTracingEnabled()) {
             logTraceSvcConn("setServiceInfo", "info=" + info);
         }
+        if (!info.isWithinParcelableSize()) {
+            throw new IllegalStateException(
+                    "Cannot update service info: size is larger than safe parcelable limits.");
+        }
         final long identity = Binder.clearCallingIdentity();
         try {
             synchronized (mLock) {
@@ -543,24 +556,30 @@
             if (!mSecurityPolicy.checkAccessibilityAccess(this)) {
                 return null;
             }
-            final AccessibilityWindowInfo.WindowListSparseArray allWindows =
-                    new AccessibilityWindowInfo.WindowListSparseArray();
-            final ArrayList<Integer> displayList = mA11yWindowManager.getDisplayListLocked(
-                    mDisplayTypes);
-            final int displayListCounts = displayList.size();
-            if (displayListCounts > 0) {
-                for (int i = 0; i < displayListCounts; i++) {
-                    final int displayId = displayList.get(i);
-                    ensureWindowsAvailableTimedLocked(displayId);
 
-                    final List<AccessibilityWindowInfo> windowList = getWindowsByDisplayLocked(
-                            displayId);
-                    if (windowList != null) {
-                        allWindows.put(displayId, windowList);
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                final AccessibilityWindowInfo.WindowListSparseArray allWindows =
+                        new AccessibilityWindowInfo.WindowListSparseArray();
+                final ArrayList<Integer> displayList = mA11yWindowManager.getDisplayListLocked(
+                        mDisplayTypes);
+                final int displayListCounts = displayList.size();
+                if (displayListCounts > 0) {
+                    for (int i = 0; i < displayListCounts; i++) {
+                        final int displayId = displayList.get(i);
+                        ensureWindowsAvailableTimedLocked(displayId);
+
+                        final List<AccessibilityWindowInfo> windowList = getWindowsByDisplayLocked(
+                                displayId);
+                        if (windowList != null) {
+                            allWindows.put(displayId, windowList);
+                        }
                     }
                 }
+                return allWindows;
+            } finally {
+                Binder.restoreCallingIdentity(identity);
             }
-            return allWindows;
         }
     }
 
@@ -592,14 +611,19 @@
             if (!mSecurityPolicy.checkAccessibilityAccess(this)) {
                 return null;
             }
-            AccessibilityWindowInfo window =
-                    mA11yWindowManager.findA11yWindowInfoByIdLocked(windowId);
-            if (window != null) {
-                AccessibilityWindowInfo windowClone = AccessibilityWindowInfo.obtain(window);
-                windowClone.setConnectionId(mId);
-                return windowClone;
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                AccessibilityWindowInfo window =
+                        mA11yWindowManager.findA11yWindowInfoByIdLocked(windowId);
+                if (window != null) {
+                    AccessibilityWindowInfo windowClone = AccessibilityWindowInfo.obtain(window);
+                    windowClone.setConnectionId(mId);
+                    return windowClone;
+                }
+                return null;
+            } finally {
+                Binder.restoreCallingIdentity(identity);
             }
-            return null;
         }
     }
 
@@ -1042,7 +1066,12 @@
                 return false;
             }
         }
-        return mSystemActionPerformer.performSystemAction(action);
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            return mSystemActionPerformer.performSystemAction(action);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
     }
 
     @Override
@@ -1055,7 +1084,12 @@
                 return Collections.emptyList();
             }
         }
-        return mSystemActionPerformer.getSystemActions();
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            return mSystemActionPerformer.getSystemActions();
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
     }
 
     @Override
@@ -1066,12 +1100,17 @@
         if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) {
             return false;
         }
-        if (isCapturingFingerprintGestures()) {
-            FingerprintGestureDispatcher dispatcher =
-                    mSystemSupport.getFingerprintGestureDispatcher();
-            return (dispatcher != null) && dispatcher.isFingerprintGestureDetectionAvailable();
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            if (isCapturingFingerprintGestures()) {
+                FingerprintGestureDispatcher dispatcher =
+                        mSystemSupport.getFingerprintGestureDispatcher();
+                return (dispatcher != null) && dispatcher.isFingerprintGestureDetectionAvailable();
+            }
+            return false;
+        } finally {
+            Binder.restoreCallingIdentity(identity);
         }
-        return false;
     }
 
     @Nullable
@@ -1281,7 +1320,12 @@
             logTraceSvcConn("setMagnificationCallbackEnabled",
                     "displayId=" + displayId + ";enabled=" + enabled);
         }
-        mInvocationHandler.setMagnificationCallbackEnabled(displayId, enabled);
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            mInvocationHandler.setMagnificationCallbackEnabled(displayId, enabled);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
     }
 
     public boolean isMagnificationCallbackEnabled(int displayId) {
@@ -1293,7 +1337,12 @@
         if (svcConnTracingEnabled()) {
             logTraceSvcConn("setSoftKeyboardCallbackEnabled", "enabled=" + enabled);
         }
-        mInvocationHandler.setSoftKeyboardCallbackEnabled(enabled);
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            mInvocationHandler.setSoftKeyboardCallbackEnabled(enabled);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
     }
 
     @Override
@@ -1330,15 +1379,20 @@
             return;
         }
 
-        RemoteAccessibilityConnection connection = mA11yWindowManager.getConnectionLocked(
-                mSystemSupport.getCurrentUserIdLocked(),
-                resolveAccessibilityWindowIdLocked(accessibilityWindowId));
-        if (connection == null) {
-            callback.sendTakeScreenshotOfWindowError(
-                    AccessibilityService.ERROR_TAKE_SCREENSHOT_INVALID_WINDOW, interactionId);
-            return;
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            RemoteAccessibilityConnection connection = mA11yWindowManager.getConnectionLocked(
+                    mSystemSupport.getCurrentUserIdLocked(),
+                    resolveAccessibilityWindowIdLocked(accessibilityWindowId));
+            if (connection == null) {
+                callback.sendTakeScreenshotOfWindowError(
+                        AccessibilityService.ERROR_TAKE_SCREENSHOT_INVALID_WINDOW, interactionId);
+                return;
+            }
+            connection.getRemote().takeScreenshotOfWindow(interactionId, listener, callback);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
         }
-        connection.getRemote().takeScreenshotOfWindow(interactionId, listener, callback);
     }
 
     @Override
@@ -1527,7 +1581,12 @@
             logTraceSvcConn("getOverlayWindowToken", "displayId=" + displayId);
         }
         synchronized (mLock) {
-            return mOverlayWindowTokens.get(displayId);
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                return mOverlayWindowTokens.get(displayId);
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
         }
     }
 
@@ -1543,7 +1602,12 @@
             logTraceSvcConn("getWindowIdForLeashToken", "token=" + token);
         }
         synchronized (mLock) {
-            return mA11yWindowManager.getWindowIdLocked(token);
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                return mA11yWindowManager.getWindowIdLocked(token);
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
         }
     }
 
@@ -2390,7 +2454,12 @@
             logTraceSvcConn("setGestureDetectionPassthroughRegion",
                     "displayId=" + displayId + ";region=" + region);
         }
-        mSystemSupport.setGestureDetectionPassthroughRegion(displayId, region);
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            mSystemSupport.setGestureDetectionPassthroughRegion(displayId, region);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
     }
 
     @Override
@@ -2399,7 +2468,12 @@
             logTraceSvcConn("setTouchExplorationPassthroughRegion",
                     "displayId=" + displayId + ";region=" + region);
         }
-        mSystemSupport.setTouchExplorationPassthroughRegion(displayId, region);
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            mSystemSupport.setTouchExplorationPassthroughRegion(displayId, region);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
     }
 
     @Override
@@ -2428,13 +2502,20 @@
     @Override
     public void logTrace(long timestamp, String where, long loggingTypes, String callingParams,
             int processId, long threadId, int callingUid, Bundle callingStack) {
-        if (mTrace.isA11yTracingEnabledForTypes(loggingTypes)) {
-            ArrayList<StackTraceElement> list =
-                    (ArrayList<StackTraceElement>) callingStack.getSerializable(CALL_STACK, java.util.ArrayList.class);
-            HashSet<String> ignoreList =
-                    (HashSet<String>) callingStack.getSerializable(IGNORE_CALL_STACK, java.util.HashSet.class);
-            mTrace.logTrace(timestamp, where, loggingTypes, callingParams, processId, threadId,
-                    callingUid, list.toArray(new StackTraceElement[list.size()]), ignoreList);
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            if (mTrace.isA11yTracingEnabledForTypes(loggingTypes)) {
+                ArrayList<StackTraceElement> list =
+                        (ArrayList<StackTraceElement>) callingStack.getSerializable(CALL_STACK,
+                                java.util.ArrayList.class);
+                HashSet<String> ignoreList =
+                        (HashSet<String>) callingStack.getSerializable(IGNORE_CALL_STACK,
+                                java.util.HashSet.class);
+                mTrace.logTrace(timestamp, where, loggingTypes, callingParams, processId, threadId,
+                        callingUid, list.toArray(new StackTraceElement[list.size()]), ignoreList);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(identity);
         }
     }
 
@@ -2473,9 +2554,15 @@
         mTrace.logTrace(TRACE_WM + "." + methodName, FLAGS_WINDOW_MANAGER_INTERNAL, params);
     }
 
+    @Override
     public void setServiceDetectsGesturesEnabled(int displayId, boolean mode) {
-        mServiceDetectsGestures.put(displayId, mode);
-        mSystemSupport.setServiceDetectsGesturesEnabled(displayId, mode);
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            mServiceDetectsGestures.put(displayId, mode);
+            mSystemSupport.setServiceDetectsGesturesEnabled(displayId, mode);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
     }
 
     public boolean isServiceDetectsGesturesEnabled(int displayId) {
@@ -2485,29 +2572,60 @@
         return false;
     }
 
+    @Override
     public void requestTouchExploration(int displayId) {
-        mSystemSupport.requestTouchExploration(displayId);
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            mSystemSupport.requestTouchExploration(displayId);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
     }
 
+    @Override
     public void requestDragging(int displayId, int pointerId) {
-        mSystemSupport.requestDragging(displayId, pointerId);
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            mSystemSupport.requestDragging(displayId, pointerId);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
     }
 
+    @Override
     public void requestDelegating(int displayId) {
-        mSystemSupport.requestDelegating(displayId);
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            mSystemSupport.requestDelegating(displayId);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
     }
 
+    @Override
     public void onDoubleTap(int displayId) {
-        mSystemSupport.onDoubleTap(displayId);
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            mSystemSupport.onDoubleTap(displayId);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
     }
 
+    @Override
     public void onDoubleTapAndHold(int displayId) {
-        mSystemSupport.onDoubleTapAndHold(displayId);
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            mSystemSupport.onDoubleTapAndHold(displayId);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
     }
 
     /**
      * Sets the scaling factor for animations.
      */
+    @Override
     public void setAnimationScale(float scale) {
         final long identity = Binder.clearCallingIdentity();
         try {
@@ -2540,26 +2658,49 @@
     }
 
     @Override
-    public void attachAccessibilityOverlayToDisplay(int displayId, SurfaceControl sc) {
-        mSystemSupport.attachAccessibilityOverlayToDisplay(displayId, sc);
+    public void attachAccessibilityOverlayToDisplay(
+            int interactionId,
+            int displayId,
+            SurfaceControl sc,
+            IAccessibilityInteractionConnectionCallback callback) {
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            mSystemSupport.attachAccessibilityOverlayToDisplay(
+                    interactionId, displayId, sc, callback);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
     }
 
     @Override
-    public void attachAccessibilityOverlayToWindow(int accessibilityWindowId, SurfaceControl sc)
+    public void attachAccessibilityOverlayToWindow(
+            int interactionId,
+            int accessibilityWindowId,
+            SurfaceControl sc,
+            IAccessibilityInteractionConnectionCallback callback)
             throws RemoteException {
-        SurfaceControl.Transaction t = new SurfaceControl.Transaction();
-        t.setTrustedOverlay(sc, true).apply();
-        t.close();
-        synchronized (mLock) {
-            RemoteAccessibilityConnection connection =
-                    mA11yWindowManager.getConnectionLocked(
-                            mSystemSupport.getCurrentUserIdLocked(),
-                            resolveAccessibilityWindowIdLocked(accessibilityWindowId));
-            if (connection == null) {
-                Slog.e(LOG_TAG, "unable to get remote accessibility connection.");
-                return;
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+            t.setTrustedOverlay(sc, true).apply();
+            t.close();
+            synchronized (mLock) {
+                RemoteAccessibilityConnection connection =
+                        mA11yWindowManager.getConnectionLocked(
+                                mSystemSupport.getCurrentUserIdLocked(),
+                                resolveAccessibilityWindowIdLocked(accessibilityWindowId));
+                if (connection == null) {
+                    callback.sendAttachOverlayResult(
+                            AccessibilityService.OVERLAY_RESULT_INVALID, interactionId);
+                    return;
+                }
+                connection
+                        .getRemote()
+                        .attachAccessibilityOverlayToWindow(sc, interactionId, callback);
             }
-            connection.getRemote().attachAccessibilityOverlayToWindow(sc);
+
+        } finally {
+            Binder.restoreCallingIdentity(identity);
         }
     }
-}
\ No newline at end of file
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index fdb28ba..cbf4cce 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -74,6 +74,7 @@
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
+import android.content.pm.ParceledListSlice;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
 import android.database.ContentObserver;
@@ -130,6 +131,7 @@
 import android.view.accessibility.AccessibilityWindowAttributes;
 import android.view.accessibility.AccessibilityWindowInfo;
 import android.view.accessibility.IAccessibilityInteractionConnection;
+import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
 import android.view.accessibility.IAccessibilityManager;
 import android.view.accessibility.IAccessibilityManagerClient;
 import android.view.accessibility.IWindowMagnificationConnection;
@@ -686,6 +688,37 @@
         }
     }
 
+    private void onPackageRemovedLocked(String packageName) {
+        final AccessibilityUserState userState = getCurrentUserState();
+        final Predicate<ComponentName> filter =
+                component -> component != null && component.getPackageName().equals(
+                        packageName);
+        userState.mBindingServices.removeIf(filter);
+        userState.mCrashedServices.removeIf(filter);
+        final Iterator<ComponentName> it = userState.mEnabledServices.iterator();
+        boolean anyServiceRemoved = false;
+        while (it.hasNext()) {
+            final ComponentName comp = it.next();
+            final String compPkg = comp.getPackageName();
+            if (compPkg.equals(packageName)) {
+                it.remove();
+                userState.mTouchExplorationGrantedServices.remove(comp);
+                anyServiceRemoved = true;
+            }
+        }
+        if (anyServiceRemoved) {
+            // Update the enabled services setting.
+            persistComponentNamesToSettingLocked(
+                    Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
+                    userState.mEnabledServices, mCurrentUserId);
+            // Update the touch exploration granted services setting.
+            persistComponentNamesToSettingLocked(
+                    Settings.Secure.TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES,
+                    userState.mTouchExplorationGrantedServices, mCurrentUserId);
+            onUserStateChangedLocked(userState);
+        }
+    }
+
     private void registerBroadcastReceivers() {
         PackageMonitor monitor = new PackageMonitor() {
             @Override
@@ -758,34 +791,7 @@
                     if (userId != mCurrentUserId) {
                         return;
                     }
-                    final AccessibilityUserState userState = getUserStateLocked(userId);
-                    final Predicate<ComponentName> filter =
-                            component -> component != null && component.getPackageName().equals(
-                                    packageName);
-                    userState.mBindingServices.removeIf(filter);
-                    userState.mCrashedServices.removeIf(filter);
-                    final Iterator<ComponentName> it = userState.mEnabledServices.iterator();
-                    boolean anyServiceRemoved = false;
-                    while (it.hasNext()) {
-                        final ComponentName comp = it.next();
-                        final String compPkg = comp.getPackageName();
-                        if (compPkg.equals(packageName)) {
-                            it.remove();
-                            userState.mTouchExplorationGrantedServices.remove(comp);
-                            anyServiceRemoved = true;
-                        }
-                    }
-                    if (anyServiceRemoved) {
-                        // Update the enabled services setting.
-                        persistComponentNamesToSettingLocked(
-                                Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
-                                userState.mEnabledServices, userId);
-                        // Update the touch exploration granted services setting.
-                        persistComponentNamesToSettingLocked(
-                                Settings.Secure.TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES,
-                                userState.mTouchExplorationGrantedServices, userId);
-                        onUserStateChangedLocked(userState);
-                    }
+                    onPackageRemovedLocked(packageName);
                 }
             }
 
@@ -848,6 +854,16 @@
                         }
                     }
                 }
+
+                @Override
+                public void onPackageRemoved(String packageName, int uid) {
+                    final int userId = UserHandle.getUserId(uid);
+                    synchronized (mLock) {
+                        if (userId == mCurrentUserId) {
+                            onPackageRemovedLocked(packageName);
+                        }
+                    }
+                }
             });
         }
 
@@ -944,7 +960,13 @@
         final ComponentName menuToMigrate =
                 AccessibilityUtils.getAccessibilityMenuComponentToMigrate(mPackageManager, userId);
         if (menuToMigrate != null) {
-            mPackageManager.setComponentEnabledSetting(
+            // PackageManager#setComponentEnabledSetting disables the component for only the user
+            // linked to PackageManager's context, but mPackageManager is linked to the system user,
+            // so grab a new PackageManager for the current user to support secondary users.
+            final PackageManager userPackageManager =
+                    mContext.createContextAsUser(UserHandle.of(userId), /* flags = */ 0)
+                            .getPackageManager();
+            userPackageManager.setComponentEnabledSetting(
                     menuToMigrate,
                     PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
                     PackageManager.DONT_KILL_APP);
@@ -1230,7 +1252,8 @@
     }
 
     @Override
-    public List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList(int userId) {
+    public ParceledListSlice<AccessibilityServiceInfo> getInstalledAccessibilityServiceList(
+            int userId) {
         if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
             mTraceManager.logTrace(LOG_TAG + ".getInstalledAccessibilityServiceList",
                     FLAGS_ACCESSIBILITY_MANAGER, "userId=" + userId);
@@ -1242,8 +1265,9 @@
             final int deviceId = mProxyManager.getFirstDeviceIdForUidLocked(
                     Binder.getCallingUid());
             if (mProxyManager.isProxyedDeviceId(deviceId)) {
-                return mProxyManager.getInstalledAndEnabledServiceInfosLocked(
-                        AccessibilityServiceInfo.FEEDBACK_ALL_MASK, deviceId);
+                return new ParceledListSlice<>(
+                        mProxyManager.getInstalledAndEnabledServiceInfosLocked(
+                                AccessibilityServiceInfo.FEEDBACK_ALL_MASK, deviceId));
             }
             // We treat calls from a profile as if made by its parent as profiles
             // share the accessibility state of the parent. The call below
@@ -1255,7 +1279,7 @@
         }
 
         if (Binder.getCallingPid() == OWN_PROCESS_ID) {
-            return serviceInfos;
+            return new ParceledListSlice<>(serviceInfos);
         }
         final PackageManagerInternal pm = LocalServices.getService(
                 PackageManagerInternal.class);
@@ -1267,7 +1291,7 @@
                 serviceInfos.remove(i);
             }
         }
-        return serviceInfos;
+        return new ParceledListSlice<>(serviceInfos);
     }
 
     @Override
@@ -1831,6 +1855,9 @@
             // find out a way to detect the device finished the OTA and switch the user.
             migrateAccessibilityButtonSettingsIfNecessaryLocked(userState, null,
                     /* restoreFromSdkInt = */0);
+            // Package components are disabled per user, so secondary users also need their migrated
+            // Accessibility Menu component disabled.
+            disableAccessibilityMenuToMigrateIfNeeded();
 
             if (announceNewUser) {
                 // Schedule announcement of the current user if needed.
@@ -1919,7 +1946,7 @@
 
     private int getClientStateLocked(AccessibilityUserState userState) {
         return userState.getClientStateLocked(
-            mUiAutomationManager.isUiAutomationRunningLocked(),
+            mUiAutomationManager.canIntrospect(),
             mTraceManager.getTraceStateForAccessibilityManagerClientState());
     }
 
@@ -2869,7 +2896,7 @@
     }
 
     private void updateAccessibilityEnabledSettingLocked(AccessibilityUserState userState) {
-        final boolean isA11yEnabled = mUiAutomationManager.isUiAutomationRunningLocked()
+        final boolean isA11yEnabled = mUiAutomationManager.canIntrospect()
                 || userState.isHandlingAccessibilityEventsLocked();
         final long identity = Binder.clearCallingIdentity();
         try {
@@ -5317,16 +5344,27 @@
     }
 
     @Override
-    public void attachAccessibilityOverlayToDisplay(int displayId, SurfaceControl sc) {
+    public void attachAccessibilityOverlayToDisplay(
+            int interactionId,
+            int displayId,
+            SurfaceControl sc,
+            IAccessibilityInteractionConnectionCallback callback) {
         mMainHandler.sendMessage(
                 obtainMessage(
                         AccessibilityManagerService::attachAccessibilityOverlayToDisplayInternal,
                         this,
+                        interactionId,
                         displayId,
-                        sc));
+                        sc,
+                        callback));
     }
 
-    void attachAccessibilityOverlayToDisplayInternal(int displayId, SurfaceControl sc) {
+    void attachAccessibilityOverlayToDisplayInternal(
+            int interactionId,
+            int displayId,
+            SurfaceControl sc,
+            IAccessibilityInteractionConnectionCallback callback) {
+        int result;
         if (!mA11yOverlayLayers.contains(displayId)) {
             mA11yOverlayLayers.put(displayId, mWindowManagerService.getA11yOverlayLayer(displayId));
         }
@@ -5334,10 +5372,19 @@
         if (parent == null) {
             Slog.e(LOG_TAG, "Unable to get accessibility overlay SurfaceControl.");
             mA11yOverlayLayers.remove(displayId);
-            return;
+            result = AccessibilityService.OVERLAY_RESULT_INVALID;
+        } else {
+            SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+            t.reparent(sc, parent).setTrustedOverlay(sc, true).apply();
+            t.close();
+            result = AccessibilityService.OVERLAY_RESULT_SUCCESS;
         }
-        SurfaceControl.Transaction t = new SurfaceControl.Transaction();
-        t.reparent(sc, parent).setTrustedOverlay(sc, true).apply();
-        t.close();
+        // Send the result back to the service.
+        try {
+            callback.sendAttachOverlayResult(result, interactionId);
+        } catch (RemoteException re) {
+            Slog.e(LOG_TAG, "Exception while attaching overlay.", re);
+            // the other side will time out
+        }
     }
 }
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
index 91fe035..9e70073 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
@@ -158,10 +158,11 @@
                     mSystemSupport.persistComponentNamesToSettingLocked(
                             Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
                             userState.getEnabledServicesLocked(), userState.mUserId);
+
+                    mSystemSupport.onClientChangeLocked(false);
                 } finally {
                     Binder.restoreCallingIdentity(identity);
                 }
-                mSystemSupport.onClientChangeLocked(false);
             }
         }
     }
@@ -285,7 +286,13 @@
             }
             final AccessibilityUserState userState = mUserStateWeakReference.get();
             if (userState == null) return false;
-            return userState.setSoftKeyboardModeLocked(showMode, mComponentName);
+
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                return userState.setSoftKeyboardModeLocked(showMode, mComponentName);
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
         }
     }
 
@@ -295,7 +302,12 @@
             logTraceSvcConn("getSoftKeyboardShowMode", "");
         }
         final AccessibilityUserState userState = mUserStateWeakReference.get();
-        return (userState != null) ? userState.getSoftKeyboardShowModeLocked() : 0;
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            return (userState != null) ? userState.getSoftKeyboardShowModeLocked() : 0;
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
     }
 
     @Override
@@ -363,8 +375,14 @@
             if (!hasRightsToCurrentUserLocked()) {
                 return false;
             }
-            AccessibilityUserState userState = mUserStateWeakReference.get();
-            return (userState != null) && isAccessibilityButtonAvailableLocked(userState);
+
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                AccessibilityUserState userState = mUserStateWeakReference.get();
+                return (userState != null) && isAccessibilityButtonAvailableLocked(userState);
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
         }
     }
 
@@ -456,26 +474,32 @@
     public void dispatchGesture(int sequence, ParceledListSlice gestureSteps, int displayId) {
         synchronized (mLock) {
             if (mServiceInterface != null && mSecurityPolicy.canPerformGestures(this)) {
-                MotionEventInjector motionEventInjector =
-                        mSystemSupport.getMotionEventInjectorForDisplayLocked(displayId);
-                if (wmTracingEnabled()) {
-                    logTraceWM("isTouchOrFaketouchDevice", "");
-                }
-                if (motionEventInjector != null
-                        && mWindowManagerService.isTouchOrFaketouchDevice()) {
-                    motionEventInjector.injectEvents(
-                            gestureSteps.getList(), mServiceInterface, sequence, displayId);
-                } else {
-                    try {
-                        if (svcClientTracingEnabled()) {
-                            logTraceSvcClient("onPerformGestureResult", sequence + ", false");
-                        }
-                        mServiceInterface.onPerformGestureResult(sequence, false);
-                    } catch (RemoteException re) {
-                        Slog.e(LOG_TAG, "Error sending motion event injection failure to "
-                                + mServiceInterface, re);
+                final long identity = Binder.clearCallingIdentity();
+                try {
+                    MotionEventInjector motionEventInjector =
+                            mSystemSupport.getMotionEventInjectorForDisplayLocked(displayId);
+                    if (wmTracingEnabled()) {
+                        logTraceWM("isTouchOrFaketouchDevice", "");
                     }
+                    if (motionEventInjector != null
+                            && mWindowManagerService.isTouchOrFaketouchDevice()) {
+                        motionEventInjector.injectEvents(
+                                gestureSteps.getList(), mServiceInterface, sequence, displayId);
+                    } else {
+                        try {
+                            if (svcClientTracingEnabled()) {
+                                logTraceSvcClient("onPerformGestureResult", sequence + ", false");
+                            }
+                            mServiceInterface.onPerformGestureResult(sequence, false);
+                        } catch (RemoteException re) {
+                            Slog.e(LOG_TAG, "Error sending motion event injection failure to "
+                                    + mServiceInterface, re);
+                        }
+                    }
+                } finally {
+                    Binder.restoreCallingIdentity(identity);
                 }
+
             }
         }
     }
@@ -501,10 +525,15 @@
                 return;
             }
 
-            // Sets the appearance data in the A11yUserState.
-            userState.setFocusAppearanceLocked(strokeWidth, color);
-            // Updates the appearance data in the A11yManager.
-            mSystemSupport.onClientChangeLocked(false);
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                // Sets the appearance data in the A11yUserState.
+                userState.setFocusAppearanceLocked(strokeWidth, color);
+                // Updates the appearance data in the A11yManager.
+                mSystemSupport.onClientChangeLocked(false);
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
         }
     }
 
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
index 3b169f8..ab6cc71 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
@@ -410,9 +410,10 @@
         return mBoundServices;
     }
 
-    int getClientStateLocked(boolean isUiAutomationRunning, int traceClientState) {
+    int getClientStateLocked(boolean uiAutomationCanIntrospect,
+            int traceClientState) {
         int clientState = 0;
-        final boolean a11yEnabled = isUiAutomationRunning
+        final boolean a11yEnabled = uiAutomationCanIntrospect
                 || isHandlingAccessibilityEventsLocked();
         if (a11yEnabled) {
             clientState |= AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED;
diff --git a/services/accessibility/java/com/android/server/accessibility/ActionReplacingCallback.java b/services/accessibility/java/com/android/server/accessibility/ActionReplacingCallback.java
index c08b6ab..a525e7c 100644
--- a/services/accessibility/java/com/android/server/accessibility/ActionReplacingCallback.java
+++ b/services/accessibility/java/com/android/server/accessibility/ActionReplacingCallback.java
@@ -16,6 +16,7 @@
 
 package com.android.server.accessibility;
 
+import android.accessibilityservice.AccessibilityService;
 import android.os.Binder;
 import android.os.RemoteException;
 import android.util.Slog;
@@ -281,4 +282,11 @@
             info.setDismissable(mNodeWithReplacementActions.isDismissable());
         }
     }
+
+    @Override
+    public void sendAttachOverlayResult(
+            @AccessibilityService.AttachOverlayResult int result, int interactionId)
+            throws RemoteException {
+        mServiceCallback.sendAttachOverlayResult(result, interactionId);
+    }
 }
diff --git a/services/accessibility/java/com/android/server/accessibility/ProxyManager.java b/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
index 70882c6..119f575 100644
--- a/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
@@ -379,8 +379,8 @@
      */
     public int getStateLocked(int deviceId) {
         int clientState = 0;
-        final boolean automationRunning = mUiAutomationManager.isUiAutomationRunningLocked();
-        if (automationRunning) {
+        final boolean uiAutomationCanIntrospect = mUiAutomationManager.canIntrospect();
+        if (uiAutomationCanIntrospect) {
             clientState |= AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED;
         }
         for (int i = 0; i < mProxyA11yServiceConnections.size(); i++) {
diff --git a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
index 2f3e4c0..208acdf 100644
--- a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
@@ -175,6 +175,10 @@
         return ((mUiAutomationFlags & UiAutomation.FLAG_DONT_USE_ACCESSIBILITY) == 0);
     }
 
+    boolean canIntrospect() {
+        return mUiAutomationService != null;
+    }
+
     boolean isTouchExplorationEnabledLocked() {
         return (mUiAutomationService != null)
                 && mUiAutomationService.mRequestTouchExplorationMode;
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
index b1cdc50..1482d07 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
@@ -110,7 +110,6 @@
     private boolean mAlwaysOnMagnificationEnabled = false;
     private final DisplayManagerInternal mDisplayManagerInternal;
 
-    private final MagnificationThumbnailFeatureFlag mMagnificationThumbnailFeatureFlag;
     @NonNull private final Supplier<MagnificationThumbnail> mThumbnailSupplier;
 
     /**
@@ -319,6 +318,10 @@
                     FullScreenMagnificationController::onUserContextChanged,
                     FullScreenMagnificationController.this, mDisplayId);
             mControllerCtx.getHandler().sendMessage(m);
+
+            synchronized (mLock) {
+                refreshThumbnail();
+            }
         }
 
         @Override
@@ -344,7 +347,7 @@
                     mMagnificationRegion.set(magnified);
                     mMagnificationRegion.getBounds(mMagnificationBounds);
 
-                    refreshThumbnail(getScale(), getCenterX(), getCenterY());
+                    refreshThumbnail();
 
                     // It's possible that our magnification spec is invalid with the new bounds.
                     // Adjust the current spec's offsets if necessary.
@@ -602,13 +605,13 @@
         }
 
         @GuardedBy("mLock")
-        void refreshThumbnail(float scale, float centerX, float centerY) {
+        void refreshThumbnail() {
             if (mMagnificationThumbnail != null) {
                 mMagnificationThumbnail.setThumbnailBounds(
                         mMagnificationBounds,
-                        scale,
-                        centerX,
-                        centerY
+                        getScale(),
+                        getCenterX(),
+                        getCenterY()
                 );
             }
         }
@@ -627,7 +630,7 @@
                 // We call refreshThumbnail when the thumbnail is just created to set current
                 // magnification bounds to thumbnail. It to prevent the thumbnail size has not yet
                 // updated properly and thus shows with huge size. (b/276314641)
-                refreshThumbnail(getScale(), getCenterX(), getCenterY());
+                refreshThumbnail();
             }
         }
 
@@ -639,13 +642,6 @@
             }
         }
 
-        void onThumbnailFeatureFlagChanged() {
-            synchronized (mLock) {
-                destroyThumbnail();
-                createThumbnailIfSupported();
-            }
-        }
-
         /**
          * Updates the current magnification spec.
          *
@@ -806,43 +802,19 @@
         addInfoChangedCallback(magnificationInfoChangedCallback);
         mScaleProvider = scaleProvider;
         mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
-        mMagnificationThumbnailFeatureFlag = new MagnificationThumbnailFeatureFlag();
-        mMagnificationThumbnailFeatureFlag.addOnChangedListener(
-                backgroundExecutor, this::onMagnificationThumbnailFeatureFlagChanged);
         if (thumbnailSupplier != null) {
             mThumbnailSupplier = thumbnailSupplier;
         } else {
             mThumbnailSupplier = () -> {
-                if (mMagnificationThumbnailFeatureFlag.isFeatureFlagEnabled()) {
-                    return new MagnificationThumbnail(
-                            ctx.getContext(),
-                            ctx.getContext().getSystemService(WindowManager.class),
-                            new Handler(ctx.getContext().getMainLooper())
-                    );
-                }
-                return null;
+                return new MagnificationThumbnail(
+                        ctx.getContext(),
+                        ctx.getContext().getSystemService(WindowManager.class),
+                        new Handler(ctx.getContext().getMainLooper())
+                );
             };
         }
     }
 
-    private void onMagnificationThumbnailFeatureFlagChanged() {
-        synchronized (mLock) {
-            for (int i = 0; i < mDisplays.size(); i++) {
-                onMagnificationThumbnailFeatureFlagChanged(mDisplays.keyAt(i));
-            }
-        }
-    }
-
-    private void onMagnificationThumbnailFeatureFlagChanged(int displayId) {
-        synchronized (mLock) {
-            final DisplayMagnification display = mDisplays.get(displayId);
-            if (display == null) {
-                return;
-            }
-            display.onThumbnailFeatureFlagChanged();
-        }
-    }
-
     /**
      * Start tracking the magnification region for services that control magnification and the
      * magnification gesture handler.
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
index 87fbee7..effd873 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
@@ -172,14 +172,18 @@
     }
 
     @Override
-    public void onPerformScaleAction(int displayId, float scale) {
+    public void onPerformScaleAction(int displayId, float scale, boolean updatePersistence) {
         if (getFullScreenMagnificationController().isActivated(displayId)) {
             getFullScreenMagnificationController().setScaleAndCenter(displayId, scale,
                     Float.NaN, Float.NaN, false, MAGNIFICATION_GESTURE_HANDLER_ID);
-            getFullScreenMagnificationController().persistScale(displayId);
+            if (updatePersistence) {
+                getFullScreenMagnificationController().persistScale(displayId);
+            }
         } else if (getWindowMagnificationMgr().isWindowMagnifierEnabled(displayId)) {
             getWindowMagnificationMgr().setScale(displayId, scale);
-            getWindowMagnificationMgr().persistScale(displayId);
+            if (updatePersistence) {
+                getWindowMagnificationMgr().persistScale(displayId);
+            }
         }
     }
 
@@ -502,6 +506,10 @@
     @Override
     public void onSourceBoundsChanged(int displayId, Rect bounds) {
         if (shouldNotifyMagnificationChange(displayId, MAGNIFICATION_MODE_WINDOW)) {
+            // notify sysui the magnification scale changed on window magnifier
+            mWindowMagnificationMgr.onUserMagnificationScaleChanged(
+                    mUserId, displayId, getWindowMagnificationMgr().getScale(displayId));
+
             final MagnificationConfig config = new MagnificationConfig.Builder()
                     .setMode(MAGNIFICATION_MODE_WINDOW)
                     .setActivated(getWindowMagnificationMgr().isWindowMagnifierEnabled(displayId))
@@ -516,6 +524,10 @@
     public void onFullScreenMagnificationChanged(int displayId, @NonNull Region region,
             @NonNull MagnificationConfig config) {
         if (shouldNotifyMagnificationChange(displayId, MAGNIFICATION_MODE_FULLSCREEN)) {
+            // notify sysui the magnification scale changed on fullscreen magnifier
+            mWindowMagnificationMgr.onUserMagnificationScaleChanged(
+                    mUserId, displayId, config.getScale());
+
             mAms.notifyMagnificationChanged(displayId, region, config);
         }
     }
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationScaleProvider.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationScaleProvider.java
index 8e1aa38..41c3dcb 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationScaleProvider.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationScaleProvider.java
@@ -16,6 +16,9 @@
 
 package com.android.server.accessibility.magnification;
 
+import static com.android.internal.accessibility.common.MagnificationConstants.SCALE_MAX_VALUE;
+import static com.android.internal.accessibility.common.MagnificationConstants.SCALE_MIN_VALUE;
+
 import android.content.Context;
 import android.os.UserHandle;
 import android.provider.Settings;
@@ -37,8 +40,9 @@
 
     @VisibleForTesting
     protected static final float DEFAULT_MAGNIFICATION_SCALE = 2.0f;
-    public static final float MIN_SCALE = 1.0f;
-    public static final float MAX_SCALE = 8.0f;
+
+    public static final float MIN_SCALE = SCALE_MIN_VALUE;
+    public static final float MAX_SCALE = SCALE_MAX_VALUE;
 
     private final Context mContext;
     // Stores the scale for non-default displays.
@@ -134,6 +138,6 @@
     }
 
     static float constrainScale(float scale) {
-        return MathUtils.constrain(scale, MIN_SCALE, MAX_SCALE);
+        return MathUtils.constrain(scale, SCALE_MIN_VALUE, SCALE_MAX_VALUE);
     }
 }
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationThumbnail.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationThumbnail.java
index 03fa93d..a7bdd5a 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationThumbnail.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationThumbnail.java
@@ -99,15 +99,17 @@
             Log.d(LOG_TAG, "setThumbnailBounds " + currentBounds);
         }
         mHandler.post(() -> {
-            mWindowBounds = currentBounds;
-            setBackgroundBounds();
+            refreshBackgroundBounds(currentBounds);
             if (mVisible) {
                 updateThumbnailMainThread(scale, centerX, centerY);
             }
         });
     }
 
-    private void setBackgroundBounds() {
+    @MainThread
+    private void refreshBackgroundBounds(Rect currentBounds) {
+        mWindowBounds = currentBounds;
+
         Point magnificationBoundary = getMagnificationThumbnailPadding(mContext);
         mThumbnailWidth = (int) (mWindowBounds.width() / BG_ASPECT_RATIO);
         mThumbnailHeight = (int) (mWindowBounds.height() / BG_ASPECT_RATIO);
@@ -117,6 +119,10 @@
         mBackgroundParams.height = mThumbnailHeight;
         mBackgroundParams.x = initX;
         mBackgroundParams.y = initY;
+
+        if (mVisible) {
+            mWindowManager.updateViewLayout(mThumbnailLayout, mBackgroundParams);
+        }
     }
 
     @MainThread
@@ -264,21 +270,16 @@
             mThumbnailView.setScaleX(scaleDown);
             mThumbnailView.setScaleY(scaleDown);
         }
-        float thumbnailWidth;
-        float thumbnailHeight;
-        if (mThumbnailView.getWidth() == 0 || mThumbnailView.getHeight() == 0) {
-            // if the thumbnail view size is not updated correctly, we just use the cached values.
-            thumbnailWidth = mThumbnailWidth;
-            thumbnailHeight = mThumbnailHeight;
-        } else {
-            thumbnailWidth = mThumbnailView.getWidth();
-            thumbnailHeight = mThumbnailView.getHeight();
-        }
-        if (!Float.isNaN(centerX)) {
+
+        if (!Float.isNaN(centerX)
+                && !Float.isNaN(centerY)
+                && mThumbnailWidth > 0
+                && mThumbnailHeight > 0
+        ) {
             var padding = mThumbnailView.getPaddingTop();
             var ratio = 1f / BG_ASPECT_RATIO;
-            var centerXScaled = centerX * ratio - (thumbnailWidth / 2f + padding);
-            var centerYScaled = centerY * ratio - (thumbnailHeight / 2f + padding);
+            var centerXScaled = centerX * ratio - (mThumbnailWidth / 2f + padding);
+            var centerYScaled = centerY * ratio - (mThumbnailHeight / 2f + padding);
 
             if (DEBUG) {
                 Log.d(
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationThumbnailFeatureFlag.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationThumbnailFeatureFlag.java
deleted file mode 100644
index 519f31b..0000000
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationThumbnailFeatureFlag.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.accessibility.magnification;
-
-import android.provider.DeviceConfig;
-
-/**
- * Encapsulates the feature flags for magnification thumbnail. {@see DeviceConfig}
- *
- * @hide
- */
-public class MagnificationThumbnailFeatureFlag extends MagnificationFeatureFlagBase {
-
-    private static final String NAMESPACE = DeviceConfig.NAMESPACE_ACCESSIBILITY;
-    private static final String FEATURE_NAME_ENABLE_MAGNIFIER_THUMBNAIL =
-            "enable_magnifier_thumbnail";
-
-    @Override
-    String getNamespace() {
-        return NAMESPACE;
-    }
-
-    @Override
-    String getFeatureName() {
-        return FEATURE_NAME_ENABLE_MAGNIFIER_THUMBNAIL;
-    }
-
-    @Override
-    boolean getDefaultValue() {
-        return false;
-    }
-}
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapper.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapper.java
index 1202cfa..6c6394f 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapper.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapper.java
@@ -201,6 +201,22 @@
         return true;
     }
 
+    boolean onUserMagnificationScaleChanged(int userId, int displayId, float scale) {
+        if (mTrace.isA11yTracingEnabledForTypes(FLAGS_WINDOW_MAGNIFICATION_CONNECTION)) {
+            mTrace.logTrace(TAG + ".onMagnificationScaleUpdated",
+                    FLAGS_WINDOW_MAGNIFICATION_CONNECTION, "displayId=" + displayId);
+        }
+        try {
+            mConnection.onUserMagnificationScaleChanged(userId, displayId, scale);
+        } catch (RemoteException e) {
+            if (DBG) {
+                Slog.e(TAG, "Error calling onMagnificationScaleUpdated()", e);
+            }
+            return false;
+        }
+        return true;
+    }
+
     boolean setConnectionCallback(IWindowMagnificationConnectionCallback connectionCallback) {
         if (mTrace.isA11yTracingEnabledForTypes(
                 FLAGS_WINDOW_MAGNIFICATION_CONNECTION
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java
index d07db3f..816f22f 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java
@@ -167,8 +167,9 @@
          *
          * @param displayId The logical display id.
          * @param scale the target scale, or {@link Float#NaN} to leave unchanged
+         * @param updatePersistence whether the scale should be persisted
          */
-        void onPerformScaleAction(int displayId, float scale);
+        void onPerformScaleAction(int displayId, float scale, boolean updatePersistence);
 
         /**
          * Called when the accessibility action is performed.
@@ -827,6 +828,20 @@
     }
 
     /**
+     * Notify System UI the magnification scale on the specified display for userId is changed.
+     *
+     * @param userId the user id.
+     * @param displayId the logical display id.
+     * @param scale magnification scale.
+     */
+    public boolean onUserMagnificationScaleChanged(int userId, int displayId, float scale) {
+        synchronized (mLock) {
+            return mConnectionWrapper != null
+                    && mConnectionWrapper.onUserMagnificationScaleChanged(userId, displayId, scale);
+        }
+    }
+
+    /**
      * Returns the screen-relative X coordinate of the center of the magnified bounds.
      *
      * @param displayId The logical display id
@@ -963,14 +978,15 @@
         }
 
         @Override
-        public void onPerformScaleAction(int displayId, float scale) {
+        public void onPerformScaleAction(int displayId, float scale, boolean updatePersistence) {
             if (mTrace.isA11yTracingEnabledForTypes(
                     FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK)) {
                 mTrace.logTrace(TAG + "ConnectionCallback.onPerformScaleAction",
                         FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK,
-                        "displayId=" + displayId + ";scale=" + scale);
+                        "displayId=" + displayId + ";scale=" + scale
+                                + ";updatePersistence=" + updatePersistence);
             }
-            mCallback.onPerformScaleAction(displayId, scale);
+            mCallback.onPerformScaleAction(displayId, scale, updatePersistence);
         }
 
         @Override
diff --git a/services/accessibility/lint-baseline.xml b/services/accessibility/lint-baseline.xml
new file mode 100644
index 0000000..6bec8cf
--- /dev/null
+++ b/services/accessibility/lint-baseline.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="6" by="lint 8.1.0-dev" type="baseline" client="" dependencies="true" name="" variant="all" version="8.1.0-dev">
+
+    <issue
+        id="SimpleManualPermissionEnforcement"
+        message="IAccessibilityManager permission check should be converted to @EnforcePermission annotation"
+        errorLine1="        mContext.enforceCallingOrSelfPermission("
+        errorLine2="        ^">
+        <location
+            file="frameworks/base/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java"
+            line="3992"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="SimpleManualPermissionEnforcement"
+        message="IAccessibilityManager permission check should be converted to @EnforcePermission annotation"
+        errorLine1="        mContext.enforceCallingOrSelfPermission("
+        errorLine2="        ^">
+        <location
+            file="frameworks/base/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java"
+            line="4007"
+            column="9"/>
+    </issue>
+
+</issues>
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index 2d60716..819f8a1 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -158,7 +158,6 @@
     private static final String TAG = "AppWidgetServiceImpl";
 
     private static final boolean DEBUG = false;
-    static final boolean DEBUG_PROVIDER_INFO_CACHE = true;
 
     private static final String OLD_KEYGUARD_HOST_PACKAGE = "android";
     private static final String NEW_KEYGUARD_HOST_PACKAGE = "com.android.keyguard";
@@ -255,7 +254,6 @@
 
     private boolean mSafeMode;
     private int mMaxWidgetBitmapMemory;
-    private boolean mIsProviderInfoPersisted;
     private boolean mIsCombinedBroadcastEnabled;
 
     // Mark widget lifecycle broadcasts as 'interactive'
@@ -281,14 +279,8 @@
         mCallbackHandler = new CallbackHandler(serviceThread.getLooper());
         mBackupRestoreController = new BackupRestoreController();
         mSecurityPolicy = new SecurityPolicy();
-        mIsProviderInfoPersisted = !ActivityManager.isLowRamDeviceStatic()
-                && DeviceConfig.getBoolean(NAMESPACE_SYSTEMUI,
-                SystemUiDeviceConfigFlags.PERSISTS_WIDGET_PROVIDER_INFO, true);
         mIsCombinedBroadcastEnabled = DeviceConfig.getBoolean(NAMESPACE_SYSTEMUI,
             SystemUiDeviceConfigFlags.COMBINED_BROADCAST_ENABLED, true);
-        if (DEBUG_PROVIDER_INFO_CACHE && !mIsProviderInfoPersisted) {
-            Slog.d(TAG, "App widget provider info will not be persisted on this device");
-        }
 
         BroadcastOptions opts = BroadcastOptions.makeBasic();
         opts.setBackgroundActivityStartsAllowed(false);
@@ -2528,21 +2520,7 @@
         }
     }
 
-    private static void serializeProvider(
-            @NonNull final TypedXmlSerializer out, @NonNull final Provider p) throws IOException {
-        Objects.requireNonNull(out);
-        Objects.requireNonNull(p);
-        serializeProviderInner(out, p, false /* persistsProviderInfo */);
-    }
-
-    private static void serializeProviderWithProviderInfo(
-            @NonNull final TypedXmlSerializer out, @NonNull final Provider p) throws IOException {
-        Objects.requireNonNull(out);
-        Objects.requireNonNull(p);
-        serializeProviderInner(out, p, true /* persistsProviderInfo */);
-    }
-
-    private static void serializeProviderInner(@NonNull final TypedXmlSerializer out,
+    private static void serializeProvider(@NonNull final TypedXmlSerializer out,
             @NonNull final Provider p, final boolean persistsProviderInfo) throws IOException {
         Objects.requireNonNull(out);
         Objects.requireNonNull(p);
@@ -2553,9 +2531,6 @@
         if (!TextUtils.isEmpty(p.infoTag)) {
             out.attribute(null, "info_tag", p.infoTag);
         }
-        if (DEBUG_PROVIDER_INFO_CACHE && persistsProviderInfo && !p.mInfoParsed) {
-            Slog.d(TAG, "Provider info from " + p.id.componentName + " won't be persisted.");
-        }
         if (persistsProviderInfo && p.mInfoParsed) {
             AppWidgetXmlUtil.writeAppWidgetProviderInfoLocked(out, p.info);
         }
@@ -3172,11 +3147,7 @@
                 if (provider.getUserId() != userId) {
                     continue;
                 }
-                if (mIsProviderInfoPersisted) {
-                    serializeProviderWithProviderInfo(out, provider);
-                } else if (provider.shouldBePersisted()) {
-                    serializeProvider(out, provider);
-                }
+                serializeProvider(out, provider, true /* persistsProviderInfo */);
             }
 
             N = mHosts.size();
@@ -3274,10 +3245,10 @@
                             provider.zombie = true;
                             provider.id = providerId;
                             mProviders.add(provider);
-                        } else if (mIsProviderInfoPersisted) {
+                        } else {
                             final AppWidgetProviderInfo info =
                                     AppWidgetXmlUtil.readAppWidgetProviderInfoLocked(parser);
-                            if (DEBUG_PROVIDER_INFO_CACHE && info == null) {
+                            if (DEBUG && info == null) {
                                 Slog.d(TAG, "Unable to load widget provider info from xml for "
                                         + providerId.componentName);
                             }
@@ -4601,7 +4572,7 @@
                                 && (provider.isInPackageForUser(backedupPackage, userId)
                                 || provider.hostedByPackageForUser(backedupPackage, userId))) {
                             provider.tag = index;
-                            serializeProvider(out, provider);
+                            serializeProvider(out, provider, false /* persistsProviderInfo*/);
                             index++;
                         }
                     }
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetXmlUtil.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetXmlUtil.java
index 7d8bb51..69b738a 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetXmlUtil.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetXmlUtil.java
@@ -22,7 +22,6 @@
 import android.content.ComponentName;
 import android.os.Build;
 import android.text.TextUtils;
-import android.util.Slog;
 
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
@@ -84,8 +83,6 @@
         }
         if (info.label != null) {
             out.attribute(null, ATTR_LABEL, info.label);
-        } else if (AppWidgetServiceImpl.DEBUG_PROVIDER_INFO_CACHE) {
-            Slog.e(TAG, "Label is empty in " + info.provider);
         }
         out.attributeInt(null, ATTR_ICON, info.icon);
         out.attributeInt(null, ATTR_PREVIEW_IMAGE, info.previewImage);
diff --git a/services/autofill/OWNERS b/services/autofill/OWNERS
index edfb211..4f170ca 100644
--- a/services/autofill/OWNERS
+++ b/services/autofill/OWNERS
@@ -1 +1,20 @@
+# Bug component: 351486
+
 include /core/java/android/view/autofill/OWNERS
+
+# co-own by both teams
+per-file Session.java = file:/core/java/android/view/autofill/OWNERS
+per-file Session.java = wangqi@google.com
+per-file AutofillManagerService*. = file:/core/java/android/view/autofill/OWNERS
+per-file Session.java = wangqi@google.com
+per-file AutofillManagerService* = file:/core/java/android/view/autofill/OWNERS
+per-file AutofillManagerService* = wangqi@google.com
+per-file *FillUI* = file:/core/java/android/view/autofill/OWNERS
+per-file *FillUI* = wangqi@google.com
+
+# Bug component: 543785 = per-file *Augmented*
+per-file *Augmented* = file:/core/java/android/service/autofill/augmented/OWNERS
+
+# Bug component: 543785 = per-file *Inline*
+per-file *Inline* = file:/core/java/android/widget/inline/OWNERS
+
diff --git a/services/autofill/java/com/android/server/autofill/ClientSuggestionsSession.java b/services/autofill/java/com/android/server/autofill/ClientSuggestionsSession.java
new file mode 100644
index 0000000..715697d
--- /dev/null
+++ b/services/autofill/java/com/android/server/autofill/ClientSuggestionsSession.java
@@ -0,0 +1,293 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.autofill;
+
+import static android.service.autofill.FillRequest.INVALID_REQUEST_ID;
+
+import static com.android.server.autofill.Helper.sVerbose;
+
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.app.AppGlobals;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.os.ICancellationSignal;
+import android.os.RemoteException;
+import android.service.autofill.Dataset;
+import android.service.autofill.FillResponse;
+import android.service.autofill.IFillCallback;
+import android.service.autofill.SaveInfo;
+import android.text.TextUtils;
+import android.text.format.DateUtils;
+import android.util.Slog;
+import android.view.autofill.AutofillId;
+import android.view.autofill.IAutoFillManagerClient;
+import android.view.inputmethod.InlineSuggestionsRequest;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.infra.AndroidFuture;
+
+import java.util.List;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Maintains a client suggestions session with the
+ * {@link android.view.autofill.AutofillRequestCallback} through the {@link IAutoFillManagerClient}.
+ *
+ */
+final class ClientSuggestionsSession {
+
+    private static final String TAG = "ClientSuggestionsSession";
+    private static final long TIMEOUT_REMOTE_REQUEST_MILLIS = 15 * DateUtils.SECOND_IN_MILLIS;
+
+    private final int mSessionId;
+    private final IAutoFillManagerClient mClient;
+    private final Handler mHandler;
+    private final ComponentName mComponentName;
+
+    private final RemoteFillService.FillServiceCallbacks mCallbacks;
+
+    private final Object mLock = new Object();
+    @GuardedBy("mLock")
+    private AndroidFuture<FillResponse> mPendingFillRequest;
+    @GuardedBy("mLock")
+    private int mPendingFillRequestId = INVALID_REQUEST_ID;
+
+    ClientSuggestionsSession(int sessionId, IAutoFillManagerClient client, Handler handler,
+            ComponentName componentName, RemoteFillService.FillServiceCallbacks callbacks) {
+        mSessionId = sessionId;
+        mClient = client;
+        mHandler = handler;
+        mComponentName = componentName;
+        mCallbacks = callbacks;
+    }
+
+    void onFillRequest(int requestId, InlineSuggestionsRequest inlineRequest, int flags) {
+        final AtomicReference<ICancellationSignal> cancellationSink = new AtomicReference<>();
+        final AtomicReference<AndroidFuture<FillResponse>> futureRef = new AtomicReference<>();
+        final AndroidFuture<FillResponse> fillRequest = new AndroidFuture<>();
+
+        mHandler.post(() -> {
+            if (sVerbose) {
+                Slog.v(TAG, "calling onFillRequest() for id=" + requestId);
+            }
+
+            try {
+                mClient.requestFillFromClient(requestId, inlineRequest,
+                        new FillCallbackImpl(fillRequest, futureRef, cancellationSink));
+            } catch (RemoteException e) {
+                fillRequest.completeExceptionally(e);
+            }
+        });
+
+        fillRequest.orTimeout(TIMEOUT_REMOTE_REQUEST_MILLIS, TimeUnit.MILLISECONDS);
+        futureRef.set(fillRequest);
+
+        synchronized (mLock) {
+            mPendingFillRequest = fillRequest;
+            mPendingFillRequestId = requestId;
+        }
+
+        fillRequest.whenComplete((res, err) -> mHandler.post(() -> {
+            synchronized (mLock) {
+                mPendingFillRequest = null;
+                mPendingFillRequestId = INVALID_REQUEST_ID;
+            }
+            if (err == null) {
+                processAutofillId(res);
+                mCallbacks.onFillRequestSuccess(requestId, res,
+                        mComponentName.getPackageName(), flags);
+            } else {
+                Slog.e(TAG, "Error calling on  client fill request", err);
+                if (err instanceof TimeoutException) {
+                    dispatchCancellationSignal(cancellationSink.get());
+                    mCallbacks.onFillRequestTimeout(requestId);
+                } else if (err instanceof CancellationException) {
+                    dispatchCancellationSignal(cancellationSink.get());
+                } else {
+                    mCallbacks.onFillRequestFailure(requestId, err.getMessage());
+                }
+            }
+        }));
+    }
+
+    /**
+     * Gets the application info for the component.
+     */
+    @Nullable
+    static ApplicationInfo getAppInfo(ComponentName comp, @UserIdInt int userId) {
+        try {
+            ApplicationInfo si = AppGlobals.getPackageManager().getApplicationInfo(
+                    comp.getPackageName(),
+                    PackageManager.GET_META_DATA,
+                    userId);
+            if (si != null) {
+                return si;
+            }
+        } catch (RemoteException e) {
+        }
+        return null;
+    }
+
+    /**
+     * Gets the user-visible name of the application.
+     */
+    @Nullable
+    @GuardedBy("mLock")
+    static CharSequence getAppLabelLocked(Context context, ApplicationInfo appInfo) {
+        return appInfo == null ? null : appInfo.loadSafeLabel(
+                context.getPackageManager(), 0 /* do not ellipsize */,
+                TextUtils.SAFE_STRING_FLAG_FIRST_LINE | TextUtils.SAFE_STRING_FLAG_TRIM);
+    }
+
+    /**
+     * Gets the user-visible icon of the application.
+     */
+    @Nullable
+    @GuardedBy("mLock")
+    static Drawable getAppIconLocked(Context context, ApplicationInfo appInfo) {
+        return appInfo == null ? null : appInfo.loadIcon(context.getPackageManager());
+    }
+
+    int cancelCurrentRequest() {
+        synchronized (mLock) {
+            return mPendingFillRequest != null && mPendingFillRequest.cancel(false)
+                    ? mPendingFillRequestId
+                    : INVALID_REQUEST_ID;
+        }
+    }
+
+    /**
+     * The {@link AutofillId} which the client gets from its view is not contain the session id,
+     * but Autofill framework is using the {@link AutofillId} with a session id. So before using
+     * those ids in the Autofill framework, applies the current session id.
+     *
+     * @param res which response need to apply for a session id
+     */
+    private void processAutofillId(FillResponse res) {
+        if (res == null) {
+            return;
+        }
+
+        final List<Dataset> datasets = res.getDatasets();
+        if (datasets != null && !datasets.isEmpty()) {
+            for (int i = 0; i < datasets.size(); i++) {
+                final Dataset dataset = datasets.get(i);
+                if (dataset != null) {
+                    applySessionId(dataset.getFieldIds());
+                }
+            }
+        }
+
+        final SaveInfo saveInfo = res.getSaveInfo();
+        if (saveInfo != null) {
+            applySessionId(saveInfo.getOptionalIds());
+            applySessionId(saveInfo.getRequiredIds());
+            applySessionId(saveInfo.getSanitizerValues());
+            applySessionId(saveInfo.getTriggerId());
+        }
+    }
+
+    private void applySessionId(List<AutofillId> ids) {
+        if (ids == null || ids.isEmpty()) {
+            return;
+        }
+
+        for (int i = 0; i < ids.size(); i++) {
+            applySessionId(ids.get(i));
+        }
+    }
+
+    private void applySessionId(AutofillId[][] ids) {
+        if (ids == null) {
+            return;
+        }
+        for (int i = 0; i < ids.length; i++) {
+            applySessionId(ids[i]);
+        }
+    }
+
+    private void applySessionId(AutofillId[] ids) {
+        if (ids == null) {
+            return;
+        }
+        for (int i = 0; i < ids.length; i++) {
+            applySessionId(ids[i]);
+        }
+    }
+
+    private void applySessionId(AutofillId id) {
+        if (id == null) {
+            return;
+        }
+        id.setSessionId(mSessionId);
+    }
+
+    private void dispatchCancellationSignal(@Nullable ICancellationSignal signal) {
+        if (signal == null) {
+            return;
+        }
+        try {
+            signal.cancel();
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Error requesting a cancellation", e);
+        }
+    }
+
+    private class FillCallbackImpl extends IFillCallback.Stub {
+        final AndroidFuture<FillResponse> mFillRequest;
+        final AtomicReference<AndroidFuture<FillResponse>> mFutureRef;
+        final AtomicReference<ICancellationSignal> mCancellationSink;
+
+        FillCallbackImpl(AndroidFuture<FillResponse> fillRequest,
+                AtomicReference<AndroidFuture<FillResponse>> futureRef,
+                AtomicReference<ICancellationSignal> cancellationSink) {
+            mFillRequest = fillRequest;
+            mFutureRef = futureRef;
+            mCancellationSink = cancellationSink;
+        }
+
+        @Override
+        public void onCancellable(ICancellationSignal cancellation) {
+            AndroidFuture<FillResponse> future = mFutureRef.get();
+            if (future != null && future.isCancelled()) {
+                dispatchCancellationSignal(cancellation);
+            } else {
+                mCancellationSink.set(cancellation);
+            }
+        }
+
+        @Override
+        public void onSuccess(FillResponse response) {
+            mFillRequest.complete(response);
+        }
+
+        @Override
+        public void onFailure(int requestId, CharSequence message) {
+            String errorMessage = message == null ? "" : String.valueOf(message);
+            mFillRequest.completeExceptionally(
+                    new RuntimeException(errorMessage));
+        }
+    }
+}
diff --git a/services/autofill/java/com/android/server/autofill/Helper.java b/services/autofill/java/com/android/server/autofill/Helper.java
index 7557071..ad8f5e1 100644
--- a/services/autofill/java/com/android/server/autofill/Helper.java
+++ b/services/autofill/java/com/android/server/autofill/Helper.java
@@ -95,7 +95,7 @@
         rView.visitUris(uri -> {
             int uriOwnerId = android.content.ContentProvider.getUserIdFromUri(uri);
             boolean allowed = uriOwnerId == userId;
-            permissionsOk.set(allowed && permissionsOk.get());
+            permissionsOk.set(allowed & permissionsOk.get());
         });
 
         return permissionsOk.get();
diff --git a/services/autofill/java/com/android/server/autofill/InlineSuggestionRendorInfoCallbackOnResultListener.java b/services/autofill/java/com/android/server/autofill/InlineSuggestionRendorInfoCallbackOnResultListener.java
deleted file mode 100644
index 7351ef5..0000000
--- a/services/autofill/java/com/android/server/autofill/InlineSuggestionRendorInfoCallbackOnResultListener.java
+++ /dev/null
@@ -1,65 +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.server.autofill;
-
-import android.annotation.Nullable;
-import android.os.Bundle;
-import android.os.RemoteCallback;
-import android.util.Slog;
-import android.view.autofill.AutofillId;
-import android.view.inputmethod.InlineSuggestionsRequest;
-
-import java.lang.ref.WeakReference;
-import java.util.function.Consumer;
-
-final class InlineSuggestionRendorInfoCallbackOnResultListener implements
-        RemoteCallback.OnResultListener{
-    private static final String TAG = "InlineSuggestionRendorInfoCallbackOnResultListener";
-
-    private final int mRequestIdCopy;
-    private final AutofillId mFocusedId;
-    private final WeakReference<Session> mSessionWeakReference;
-    private final Consumer<InlineSuggestionsRequest> mInlineSuggestionsRequestConsumer;
-
-    InlineSuggestionRendorInfoCallbackOnResultListener(WeakReference<Session> sessionWeakReference,
-            int requestIdCopy,
-            Consumer<InlineSuggestionsRequest> inlineSuggestionsRequestConsumer,
-            AutofillId focusedId) {
-        this.mRequestIdCopy = requestIdCopy;
-        this.mInlineSuggestionsRequestConsumer = inlineSuggestionsRequestConsumer;
-        this.mSessionWeakReference = sessionWeakReference;
-        this.mFocusedId = focusedId;
-    }
-    public void onResult(@Nullable Bundle result) {
-        Session session = this.mSessionWeakReference.get();
-        if (session == null) {
-            Slog.wtf(TAG, "Session is null before trying to call onResult");
-            return;
-        }
-        synchronized (session.mLock) {
-            if (session.mDestroyed) {
-                Slog.wtf(TAG, "Session is destroyed before trying to call onResult");
-                return;
-            }
-            session.mInlineSessionController.onCreateInlineSuggestionsRequestLocked(
-                    this.mFocusedId,
-                    session.inlineSuggestionsRequestCacheDecorator(
-                        this.mInlineSuggestionsRequestConsumer, this.mRequestIdCopy),
-                    result);
-        }
-    }
-}
diff --git a/services/autofill/java/com/android/server/autofill/InlineSuggestionRequestConsumer.java b/services/autofill/java/com/android/server/autofill/InlineSuggestionRequestConsumer.java
deleted file mode 100644
index a3efb25..0000000
--- a/services/autofill/java/com/android/server/autofill/InlineSuggestionRequestConsumer.java
+++ /dev/null
@@ -1,55 +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.server.autofill;
-
-import android.util.Slog;
-import android.view.inputmethod.InlineSuggestionsRequest;
-
-import java.lang.ref.WeakReference;
-import java.util.function.Consumer;
-
-class InlineSuggestionRequestConsumer implements Consumer<InlineSuggestionsRequest> {
-
-    static final String TAG = "InlineSuggestionRequestConsumer";
-
-    private final WeakReference<Session.AssistDataReceiverImpl> mAssistDataReceiverWeakReference;
-    private final WeakReference<ViewState>  mViewStateWeakReference;
-
-    InlineSuggestionRequestConsumer(WeakReference<Session.AssistDataReceiverImpl>
-            assistDataReceiverWeakReference,
-            WeakReference<ViewState>  viewStateWeakReference) {
-        mAssistDataReceiverWeakReference = assistDataReceiverWeakReference;
-        mViewStateWeakReference = viewStateWeakReference;
-    }
-
-    @Override
-    public void accept(InlineSuggestionsRequest inlineSuggestionsRequest) {
-        Session.AssistDataReceiverImpl assistDataReceiver = mAssistDataReceiverWeakReference.get();
-        ViewState viewState = mViewStateWeakReference.get();
-        if (assistDataReceiver == null) {
-            Slog.wtf(TAG, "assistDataReceiver is null when accepting new inline suggestion"
-                    + "requests");
-            return;
-        }
-
-        if (viewState == null) {
-            Slog.wtf(TAG, "view state is null when accepting new inline suggestion requests");
-            return;
-        }
-        assistDataReceiver.handleInlineSuggestionRequest(inlineSuggestionsRequest, viewState);
-    }
-}
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index fb26f42..7b459aa 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -44,6 +44,7 @@
 import static android.view.autofill.AutofillManager.ACTION_VIEW_EXITED;
 import static android.view.autofill.AutofillManager.COMMIT_REASON_SESSION_DESTROYED;
 import static android.view.autofill.AutofillManager.COMMIT_REASON_UNKNOWN;
+import static android.view.autofill.AutofillManager.FLAG_ENABLED_CLIENT_SUGGESTIONS;
 import static android.view.autofill.AutofillManager.FLAG_SMART_SUGGESTION_SYSTEM;
 import static android.view.autofill.AutofillManager.getSmartSuggestionModeToString;
 
@@ -323,7 +324,7 @@
      * Id of the View currently being displayed.
      */
     @GuardedBy("mLock")
-    private @Nullable AutofillId mCurrentViewId;
+    @Nullable private AutofillId mCurrentViewId;
 
     @GuardedBy("mLock")
     private IAutoFillManagerClient mClient;
@@ -371,7 +372,7 @@
     private Bundle mClientState;
 
     @GuardedBy("mLock")
-    boolean mDestroyed;
+    private boolean mDestroyed;
 
     /**
      * Helper used to handle state of Save UI when it must be hiding to show a custom description
@@ -450,7 +451,7 @@
     private ArrayList<AutofillId> mAugmentedAutofillableIds;
 
     @NonNull
-    final AutofillInlineSessionController mInlineSessionController;
+    private final AutofillInlineSessionController mInlineSessionController;
 
     /**
      * Receiver of assist data from the app's {@link Activity}.
@@ -462,6 +463,9 @@
      */
     private final PccAssistDataReceiverImpl mPccAssistReceiver = new PccAssistDataReceiverImpl();
 
+    @Nullable
+    private ClientSuggestionsSession mClientSuggestionsSession;
+
     private final ClassificationState mClassificationState = new ClassificationState();
 
     // TODO(b/216576510): Share one BroadcastReceiver between all Sessions instead of creating a
@@ -603,6 +607,9 @@
         /** Whether the current {@link FillResponse} is expired. */
         private boolean mExpiredResponse;
 
+        /** Whether the client is using {@link android.view.autofill.AutofillRequestCallback}. */
+        private boolean mClientSuggestionsEnabled;
+
         /** Whether the fill dialog UI is disabled. */
         private boolean mFillDialogDisabled;
 
@@ -614,7 +621,7 @@
      * TODO(b/151867668): improve how asynchronous data dependencies are handled, without using
      * CountDownLatch.
      */
-    final class AssistDataReceiverImpl extends IAssistDataReceiver.Stub {
+    private final class AssistDataReceiverImpl extends IAssistDataReceiver.Stub {
         @GuardedBy("mLock")
         private boolean mWaitForInlineRequest;
         @GuardedBy("mLock")
@@ -629,32 +636,28 @@
             mPendingFillRequest = null;
             mWaitForInlineRequest = isInlineRequest;
             mPendingInlineSuggestionsRequest = null;
-            if (isInlineRequest) {
-                WeakReference<AssistDataReceiverImpl> assistDataReceiverWeakReference =
-                        new WeakReference<AssistDataReceiverImpl>(this);
-                WeakReference<ViewState> viewStateWeakReference =
-                        new WeakReference<ViewState>(viewState);
-                return new InlineSuggestionRequestConsumer(assistDataReceiverWeakReference,
-                    viewStateWeakReference);
-            }
-            return null;
+            return isInlineRequest ? (inlineSuggestionsRequest) -> {
+                synchronized (mLock) {
+                    if (!mWaitForInlineRequest || mPendingInlineSuggestionsRequest != null) {
+                        return;
+                    }
+                    mWaitForInlineRequest = inlineSuggestionsRequest != null;
+                    mPendingInlineSuggestionsRequest = inlineSuggestionsRequest;
+                    mWaitForInlineRequest = inlineSuggestionsRequest != null;
+                    maybeRequestFillFromServiceLocked();
+                    viewState.resetState(ViewState.STATE_PENDING_CREATE_INLINE_REQUEST);
+                }
+            } : null;
         }
 
-        void handleInlineSuggestionRequest(InlineSuggestionsRequest inlineSuggestionsRequest,
-                ViewState viewState) {
-            synchronized (mLock) {
-                if (!mWaitForInlineRequest || mPendingInlineSuggestionsRequest != null) {
-                    return;
-                }
-                mWaitForInlineRequest = inlineSuggestionsRequest != null;
-                mPendingInlineSuggestionsRequest = inlineSuggestionsRequest;
-                maybeRequestFillLocked();
-                viewState.resetState(ViewState.STATE_PENDING_CREATE_INLINE_REQUEST);
-            }
+        void newAutofillRequestLocked(@Nullable InlineSuggestionsRequest inlineRequest) {
+            mPendingFillRequest = null;
+            mWaitForInlineRequest = inlineRequest != null;
+            mPendingInlineSuggestionsRequest = inlineRequest;
         }
 
         @GuardedBy("mLock")
-        void maybeRequestFillLocked() {
+        void maybeRequestFillFromServiceLocked() {
             if (mPendingFillRequest == null) {
                 return;
             }
@@ -664,13 +667,15 @@
                     return;
                 }
 
-                mPendingFillRequest = new FillRequest(mPendingFillRequest.getId(),
-                        mPendingFillRequest.getFillContexts(),
-                        mPendingFillRequest.getHints(),
-                        mPendingFillRequest.getClientState(),
-                        mPendingFillRequest.getFlags(),
-                        mPendingInlineSuggestionsRequest,
-                        mPendingFillRequest.getDelayedFillIntentSender());
+                if (mPendingInlineSuggestionsRequest.isServiceSupported()) {
+                    mPendingFillRequest = new FillRequest(mPendingFillRequest.getId(),
+                            mPendingFillRequest.getFillContexts(),
+                            mPendingFillRequest.getHints(),
+                            mPendingFillRequest.getClientState(),
+                            mPendingFillRequest.getFlags(),
+                            mPendingInlineSuggestionsRequest,
+                            mPendingFillRequest.getDelayedFillIntentSender());
+                }
             }
             mLastFillRequest = mPendingFillRequest;
 
@@ -792,7 +797,7 @@
                             : mDelayedFillPendingIntent.getIntentSender());
 
                 mPendingFillRequest = request;
-                maybeRequestFillLocked();
+                maybeRequestFillFromServiceLocked();
             }
 
             if (mActivityToken != null) {
@@ -1118,30 +1123,39 @@
     }
 
     /**
-     * Cancels the last request sent to the {@link #mRemoteFillService}.
+     * Cancels the last request sent to the {@link #mRemoteFillService} or the
+     * {@link #mClientSuggestionsSession}.
      */
     @GuardedBy("mLock")
     private void cancelCurrentRequestLocked() {
-        if (mRemoteFillService == null) {
-            wtf(null, "cancelCurrentRequestLocked() called without a remote service. "
-                + "mForAugmentedAutofillOnly: %s", mSessionFlags.mAugmentedAutofillOnly);
+        if (mRemoteFillService == null && mClientSuggestionsSession == null) {
+            wtf(null, "cancelCurrentRequestLocked() called without a remote service or a "
+                    + "client suggestions session.  mForAugmentedAutofillOnly: %s",
+                    mSessionFlags.mAugmentedAutofillOnly);
             return;
         }
-        final int canceledRequest = mRemoteFillService.cancelCurrentRequest();
 
-        // Remove the FillContext as there will never be a response for the service
-        if (canceledRequest != INVALID_REQUEST_ID && mContexts != null) {
-            final int numContexts = mContexts.size();
+        if (mRemoteFillService != null) {
+            final int canceledRequest = mRemoteFillService.cancelCurrentRequest();
 
-            // It is most likely the last context, hence search backwards
-            for (int i = numContexts - 1; i >= 0; i--) {
-                if (mContexts.get(i).getRequestId() == canceledRequest) {
-                    if (sDebug) Slog.d(TAG, "cancelCurrentRequest(): id = " + canceledRequest);
-                    mContexts.remove(i);
-                    break;
+            // Remove the FillContext as there will never be a response for the service
+            if (canceledRequest != INVALID_REQUEST_ID && mContexts != null) {
+                final int numContexts = mContexts.size();
+
+                // It is most likely the last context, hence search backwards
+                for (int i = numContexts - 1; i >= 0; i--) {
+                    if (mContexts.get(i).getRequestId() == canceledRequest) {
+                        if (sDebug) Slog.d(TAG, "cancelCurrentRequest(): id = " + canceledRequest);
+                        mContexts.remove(i);
+                        break;
+                    }
                 }
             }
         }
+
+        if (mClientSuggestionsSession != null) {
+            mClientSuggestionsSession.cancelCurrentRequest();
+        }
     }
 
     private boolean isViewFocusedLocked(int flags) {
@@ -1235,42 +1249,57 @@
             requestAssistStructureForPccLocked(flags | FLAG_PCC_DETECTION);
         }
 
-        // Only ask IME to create inline suggestions request if Autofill provider supports it and
-        // the render service is available except the autofill is triggered manually and the view
-        // is also not focused.
+        // Only ask IME to create inline suggestions request when
+        // 1. Autofill provider supports it or client enabled client suggestions.
+        // 2. The render service is available.
+        // 3. The view is focused. (The view may not be focused if the autofill is triggered
+        //    manually.)
         final RemoteInlineSuggestionRenderService remoteRenderService =
                 mService.getRemoteInlineSuggestionRenderServiceLocked();
-        if (mSessionFlags.mInlineSupportedByService
+        if ((mSessionFlags.mInlineSupportedByService || mSessionFlags.mClientSuggestionsEnabled)
                 && remoteRenderService != null
-                && (isViewFocusedLocked(flags) || isRequestSupportFillDialog(flags))) {
-
-            Consumer<InlineSuggestionsRequest> inlineSuggestionsRequestConsumer =
-                mAssistReceiver.newAutofillRequestLocked(viewState,
-                    /* isInlineRequest= */ true);
-
+                && (isViewFocusedLocked(flags) || (isRequestSupportFillDialog(flags)))) {
+            final Consumer<InlineSuggestionsRequest> inlineSuggestionsRequestConsumer;
+            if (mSessionFlags.mClientSuggestionsEnabled) {
+                final int finalRequestId = requestId;
+                inlineSuggestionsRequestConsumer = (inlineSuggestionsRequest) -> {
+                    // Using client suggestions
+                    synchronized (mLock) {
+                        onClientFillRequestLocked(finalRequestId, inlineSuggestionsRequest);
+                    }
+                    viewState.resetState(ViewState.STATE_PENDING_CREATE_INLINE_REQUEST);
+                };
+            } else {
+                inlineSuggestionsRequestConsumer = mAssistReceiver.newAutofillRequestLocked(
+                        viewState, /* isInlineRequest= */ true);
+            }
             if (inlineSuggestionsRequestConsumer != null) {
-                final int requestIdCopy = requestId;
                 final AutofillId focusedId = mCurrentViewId;
-
-                WeakReference sessionWeakReference = new WeakReference<Session>(this);
-                InlineSuggestionRendorInfoCallbackOnResultListener
-                        inlineSuggestionRendorInfoCallbackOnResultListener =
-                                new InlineSuggestionRendorInfoCallbackOnResultListener(
-                                        sessionWeakReference,
-                                        requestIdCopy,
-                                        inlineSuggestionsRequestConsumer,
-                                        focusedId);
-                RemoteCallback inlineSuggestionRendorInfoCallback = new RemoteCallback(
-                        inlineSuggestionRendorInfoCallbackOnResultListener, mHandler);
-
+                final int requestIdCopy = requestId;
                 remoteRenderService.getInlineSuggestionsRendererInfo(
-                        inlineSuggestionRendorInfoCallback);
+                        new RemoteCallback((extras) -> {
+                            synchronized (mLock) {
+                                mInlineSessionController.onCreateInlineSuggestionsRequestLocked(
+                                        focusedId, inlineSuggestionsRequestCacheDecorator(
+                                                inlineSuggestionsRequestConsumer, requestIdCopy),
+                                        extras);
+                            }
+                        }, mHandler)
+                );
                 viewState.setState(ViewState.STATE_PENDING_CREATE_INLINE_REQUEST);
             }
+        } else if (mSessionFlags.mClientSuggestionsEnabled) {
+            // Request client suggestions for the dropdown mode
+            onClientFillRequestLocked(requestId, null);
         } else {
             mAssistReceiver.newAutofillRequestLocked(viewState, /* isInlineRequest= */ false);
         }
 
+        if (mSessionFlags.mClientSuggestionsEnabled) {
+            // Using client suggestions, unnecessary request AssistStructure
+            return;
+        }
+
         // Now request the assist structure data.
         requestAssistStructureLocked(requestId, flags);
     }
@@ -1377,6 +1406,11 @@
             mSessionFlags = new SessionFlags();
             mSessionFlags.mAugmentedAutofillOnly = forAugmentedAutofillOnly;
             mSessionFlags.mInlineSupportedByService = mService.isInlineSuggestionsEnabledLocked();
+            if (mContext.checkCallingPermission(PROVIDE_OWN_AUTOFILL_SUGGESTIONS)
+                    == PackageManager.PERMISSION_GRANTED) {
+                mSessionFlags.mClientSuggestionsEnabled =
+                        (mFlags & FLAG_ENABLED_CLIENT_SUGGESTIONS) != 0;
+            }
             setClientLocked(client);
         }
 
@@ -1517,14 +1551,15 @@
                 if (requestLog != null) {
                     requestLog.addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_DATASETS, -1);
                 }
-                processNullResponseLocked(requestId, requestFlags);
+                processNullResponseOrFallbackLocked(requestId, requestFlags);
                 return;
             }
 
             // TODO: Check if this is required. We can still present datasets to the user even if
             //  traditional field classification is disabled.
             fieldClassificationIds = response.getFieldClassificationIds();
-            if (fieldClassificationIds != null && !mService.isFieldClassificationEnabledLocked()) {
+            if (!mSessionFlags.mClientSuggestionsEnabled && fieldClassificationIds != null
+                    && !mService.isFieldClassificationEnabledLocked()) {
                 Slog.w(TAG, "Ignoring " + response + " because field detection is disabled");
                 processNullResponseLocked(requestId, requestFlags);
                 return;
@@ -1658,7 +1693,9 @@
                         || (ArrayUtils.isEmpty(saveInfo.getOptionalIds())
                             && ArrayUtils.isEmpty(saveInfo.getRequiredIds())
                             && ((saveInfo.getFlags() & SaveInfo.FLAG_DELAY_SAVE) == 0)))
-                    && (ArrayUtils.isEmpty(response.getFieldClassificationIds())));
+                    && (ArrayUtils.isEmpty(response.getFieldClassificationIds())
+                        || (!mSessionFlags.mClientSuggestionsEnabled
+                        && !mService.isFieldClassificationEnabledLocked())));
         }
     }
 
@@ -2044,6 +2081,40 @@
         }
     }
 
+    @GuardedBy("mLock")
+    private void processNullResponseOrFallbackLocked(int requestId, int flags) {
+        if (!mSessionFlags.mClientSuggestionsEnabled) {
+            processNullResponseLocked(requestId, flags);
+            return;
+        }
+
+        // fallback to the default platform password manager
+        mSessionFlags.mClientSuggestionsEnabled = false;
+        mLastFillDialogTriggerIds = null;
+        // Log the existing FillResponse event.
+        mFillResponseEventLogger.logAndEndEvent();
+
+        final InlineSuggestionsRequest inlineRequest =
+                (mLastInlineSuggestionsRequest != null
+                        && mLastInlineSuggestionsRequest.first == requestId)
+                        ? mLastInlineSuggestionsRequest.second : null;
+
+        // Start a new FillRequest logger for client suggestion fallback.
+        mFillRequestEventLogger.startLogForNewRequest();
+        mRequestCount++;
+        mFillRequestEventLogger.maybeSetAppPackageUid(uid);
+        mFillRequestEventLogger.maybeSetFlags(
+            flags & ~FLAG_ENABLED_CLIENT_SUGGESTIONS);
+        mFillRequestEventLogger.maybeSetRequestTriggerReason(
+            TRIGGER_REASON_NORMAL_TRIGGER);
+        mFillRequestEventLogger.maybeSetIsClientSuggestionFallback(true);
+
+        mAssistReceiver.newAutofillRequestLocked(inlineRequest);
+        requestAssistStructureLocked(requestId,
+                flags & ~FLAG_ENABLED_CLIENT_SUGGESTIONS);
+        return;
+    }
+
     // FillServiceCallbacks
     @Override
     @SuppressWarnings("GuardedBy")
@@ -3186,8 +3257,6 @@
 
         /*
          * Don't show save if the session has credman field
-         *
-         * TODO: add a new enum NO_SAVE_UI_CREDMAN
          */
         if (mSessionFlags.mScreenHasCredmanField) {
             if (sVerbose) {
@@ -4284,13 +4353,22 @@
             filterText = value.getTextValue().toString();
         }
 
-        final CharSequence serviceLabel;
-        final Drawable serviceIcon;
-        synchronized (this.mService.mLock) {
-            serviceLabel = mService.getServiceLabelLocked();
-            serviceIcon = mService.getServiceIconLocked();
+        final CharSequence targetLabel;
+        final Drawable targetIcon;
+        synchronized (mLock) {
+            if (mSessionFlags.mClientSuggestionsEnabled) {
+                final ApplicationInfo appInfo = ClientSuggestionsSession.getAppInfo(mComponentName,
+                        mService.getUserId());
+                targetLabel = ClientSuggestionsSession.getAppLabelLocked(
+                        mService.getMaster().getContext(), appInfo);
+                targetIcon = ClientSuggestionsSession.getAppIconLocked(
+                        mService.getMaster().getContext(), appInfo);
+            } else {
+                targetLabel = mService.getServiceLabelLocked();
+                targetIcon = mService.getServiceIconLocked();
+            }
         }
-        if (serviceLabel == null || serviceIcon == null) {
+        if (targetLabel == null || targetIcon == null) {
             wtf(null, "onFillReady(): no service label or icon");
             return;
         }
@@ -4351,7 +4429,7 @@
 
         getUiForShowing().showFillUi(filledId, response, filterText,
                 mService.getServicePackageName(), mComponentName,
-                serviceLabel, serviceIcon, this, mContext, id, mCompatMode);
+                targetLabel, targetIcon, this, mContext, id, mCompatMode);
 
         synchronized (mLock) {
             mPresentationStatsEventLogger.maybeSetCountShown(
@@ -4553,6 +4631,17 @@
             return false;
         }
 
+        final InlineSuggestionsRequest request = inlineSuggestionsRequest.get();
+        if (mSessionFlags.mClientSuggestionsEnabled && !request.isClientSupported()
+                || !mSessionFlags.mClientSuggestionsEnabled && !request.isServiceSupported()) {
+            if (sDebug) {
+                Slog.d(TAG, "Inline suggestions not supported for "
+                        + (mSessionFlags.mClientSuggestionsEnabled ? "client" : "service")
+                        + ". Falling back to dropdown.");
+            }
+            return false;
+        }
+
         final RemoteInlineSuggestionRenderService remoteRenderService =
                 mService.getRemoteInlineSuggestionRenderServiceLocked();
         if (remoteRenderService == null) {
@@ -4567,8 +4656,8 @@
         }
 
         final InlineFillUi.InlineFillUiInfo inlineFillUiInfo =
-            new InlineFillUi.InlineFillUiInfo(inlineSuggestionsRequest.get(), focusedId,
-                filterText, remoteRenderService, userId, id);
+                new InlineFillUi.InlineFillUiInfo(request, focusedId,
+                        filterText, remoteRenderService, userId, id);
         InlineFillUi inlineFillUi = InlineFillUi.forAutofill(inlineFillUiInfo, response,
                 new InlineFillUi.InlineSuggestionUiCallback() {
                     @Override
@@ -5337,7 +5426,7 @@
     }
 
     @NonNull
-    Consumer<InlineSuggestionsRequest> inlineSuggestionsRequestCacheDecorator(
+    private Consumer<InlineSuggestionsRequest> inlineSuggestionsRequestCacheDecorator(
             @NonNull Consumer<InlineSuggestionsRequest> consumer, int requestId) {
         return inlineSuggestionsRequest -> {
             consumer.accept(inlineSuggestionsRequest);
@@ -5378,6 +5467,26 @@
         }
     }
 
+    @GuardedBy("mLock")
+    private void onClientFillRequestLocked(int requestId,
+            InlineSuggestionsRequest inlineSuggestionsRequest) {
+        if (mClientSuggestionsSession == null) {
+            mClientSuggestionsSession = new ClientSuggestionsSession(id, mClient, mHandler,
+                    mComponentName, this);
+        }
+
+        if (mContexts == null) {
+            mContexts = new ArrayList<>(1);
+        }
+        mContexts.add(new FillContext(requestId, new AssistStructure(), mCurrentViewId));
+
+        if (inlineSuggestionsRequest != null && !inlineSuggestionsRequest.isClientSupported()) {
+            inlineSuggestionsRequest = null;
+        }
+
+        mClientSuggestionsSession.onFillRequest(requestId, inlineSuggestionsRequest, mFlags);
+    }
+
     /**
      * The result of checking whether to show the save dialog, when session can be saved.
      *
diff --git a/services/backup/TEST_MAPPING b/services/backup/TEST_MAPPING
index 62706e74..e153230 100644
--- a/services/backup/TEST_MAPPING
+++ b/services/backup/TEST_MAPPING
@@ -1,6 +1,14 @@
 {
   "presubmit": [
     {
+      "name": "FrameworksMockingServicesTests",
+      "options": [
+        {
+          "include-filter": "com.android.server.backup"
+        }
+      ]
+    },
+    {
       "name": "CtsBackupTestCases",
       "options": [
         {
diff --git a/services/backup/java/com/android/server/backup/FullBackupJob.java b/services/backup/java/com/android/server/backup/FullBackupJob.java
index fe0e1c6..bab65ad 100644
--- a/services/backup/java/com/android/server/backup/FullBackupJob.java
+++ b/services/backup/java/com/android/server/backup/FullBackupJob.java
@@ -24,6 +24,7 @@
 import android.app.job.JobService;
 import android.content.ComponentName;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.os.Bundle;
 import android.util.SparseArray;
 
@@ -52,13 +53,15 @@
         JobInfo.Builder builder = new JobInfo.Builder(getJobIdForUserId(userId), sIdleService);
         final BackupManagerConstants constants = userBackupManagerService.getConstants();
         synchronized (constants) {
-            builder.setRequiresDeviceIdle(true)
-                    .setRequiredNetworkType(constants.getFullBackupRequiredNetworkType())
+            builder.setRequiredNetworkType(constants.getFullBackupRequiredNetworkType())
                     .setRequiresCharging(constants.getFullBackupRequireCharging());
         }
         if (minDelay > 0) {
             builder.setMinimumLatency(minDelay);
         }
+        if (!ctx.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)) {
+            builder.setRequiresDeviceIdle(true);
+        }
 
         Bundle extraInfo = new Bundle();
         extraInfo.putInt(USER_ID_EXTRA_KEY, userId);
@@ -114,7 +117,8 @@
         return false;
     }
 
-    private static int getJobIdForUserId(int userId) {
+    @VisibleForTesting
+    static int getJobIdForUserId(int userId) {
         return JobIdManager.getJobIdForUserId(MIN_JOB_ID, MAX_JOB_ID, userId);
     }
 }
diff --git a/services/backup/java/com/android/server/backup/KeyValueBackupJob.java b/services/backup/java/com/android/server/backup/KeyValueBackupJob.java
index 164bbea..9a788be 100644
--- a/services/backup/java/com/android/server/backup/KeyValueBackupJob.java
+++ b/services/backup/java/com/android/server/backup/KeyValueBackupJob.java
@@ -169,7 +169,8 @@
         sNextScheduledForUserId.delete(userId);
     }
 
-    private static int getJobIdForUserId(int userId) {
+    @VisibleForTesting
+    static int getJobIdForUserId(int userId) {
         return JobIdManager.getJobIdForUserId(MIN_JOB_ID, MAX_JOB_ID, userId);
     }
 }
diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
index 995e557..b6baf7e 100644
--- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
@@ -36,6 +36,7 @@
 import static com.android.server.backup.internal.BackupHandler.MSG_RUN_RESTORE;
 import static com.android.server.backup.internal.BackupHandler.MSG_SCHEDULE_BACKUP_PACKAGE;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
@@ -508,7 +509,8 @@
 
     @VisibleForTesting
     UserBackupManagerService(Context context, PackageManager packageManager,
-            LifecycleOperationStorage operationStorage, TransportManager transportManager) {
+            LifecycleOperationStorage operationStorage, TransportManager transportManager,
+            BackupHandler backupHandler, BackupManagerConstants backupManagerConstants) {
         mContext = context;
 
         mUserId = 0;
@@ -516,6 +518,9 @@
         mPackageManager = packageManager;
         mOperationStorage = operationStorage;
         mTransportManager = transportManager;
+        mFullBackupQueue = new ArrayList<>();
+        mBackupHandler = backupHandler;
+        mConstants = backupManagerConstants;
 
         mBaseStateDir = null;
         mDataDir = null;
@@ -527,9 +532,7 @@
         mAgentTimeoutParameters = null;
         mActivityManagerInternal = null;
         mAlarmManager = null;
-        mConstants = null;
         mWakelock = null;
-        mBackupHandler = null;
         mBackupPreferences = null;
         mBackupPasswordManager = null;
         mPackageManagerBinder = null;
@@ -974,6 +977,7 @@
                 /* scheduler */ null);
     }
 
+    @NonNull
     private ArrayList<FullBackupEntry> readFullBackupSchedule() {
         boolean changed = false;
         ArrayList<FullBackupEntry> schedule = null;
@@ -987,11 +991,11 @@
                  DataInputStream in = new DataInputStream(bufStream)) {
                 int version = in.readInt();
                 if (version != SCHEDULE_FILE_VERSION) {
-                    Slog.e(
-                            TAG,
-                            addUserIdToLogMessage(
-                                    mUserId, "Unknown backup schedule version " + version));
-                    return null;
+                    // The file version doesn't match the expected value.
+                    // Since this is within a "try" block, this exception will be treated like
+                    // any other exception, and caught below.
+                    throw new IllegalArgumentException("Unknown backup schedule version "
+                            + version);
                 }
 
                 final int numPackages = in.readInt();
@@ -2859,8 +2863,7 @@
             if (DEBUG) {
                 Slog.d(
                         TAG,
-                        addUserIdToLogMessage(
-                                mUserId, "Starting backup confirmation UI, token=" + token));
+                        addUserIdToLogMessage(mUserId, "Starting backup confirmation UI"));
             }
             if (!startConfirmationUi(token, FullBackup.FULL_BACKUP_INTENT_ACTION)) {
                 Slog.e(
diff --git a/services/backup/lint-baseline.xml b/services/backup/lint-baseline.xml
index 28bb937..93c9390 100644
--- a/services/backup/lint-baseline.xml
+++ b/services/backup/lint-baseline.xml
@@ -12,4 +12,28 @@
             column="16"/>
     </issue>
 
+    <issue
+        id="SimpleManualPermissionEnforcement"
+        message="IRestoreSession permission check should be converted to @EnforcePermission annotation">
+        <location
+            file="frameworks/base/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java"
+            line="88"/>
+    </issue>
+
+    <issue
+        id="SimpleManualPermissionEnforcement"
+        message="IRestoreSession permission check should be converted to @EnforcePermission annotation">
+        <location
+            file="frameworks/base/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java"
+            line="144"/>
+    </issue>
+
+    <issue
+        id="SimpleManualPermissionEnforcement"
+        message="IRestoreSession permission check should be converted to @EnforcePermission annotation">
+        <location
+            file="frameworks/base/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java"
+            line="207"/>
+    </issue>
+
 </issues>
diff --git a/services/cloudsearch/OWNERS b/services/cloudsearch/OWNERS
index aa4da3b..3a5841e 100644
--- a/services/cloudsearch/OWNERS
+++ b/services/cloudsearch/OWNERS
@@ -1,4 +1,3 @@
 # Bug component: 758286
 
-huiwu@google.com
 srazdan@google.com
diff --git a/services/companion/OWNERS b/services/companion/OWNERS
index cb4cc56..734d8b6 100644
--- a/services/companion/OWNERS
+++ b/services/companion/OWNERS
@@ -1,4 +1 @@
-evanxinchen@google.com
-ewol@google.com
-guojing@google.com
-svetoslavganov@google.com
\ No newline at end of file
+include /core/java/android/companion/OWNERS
\ No newline at end of file
diff --git a/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java b/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
index 77275e0..3e7d759 100644
--- a/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
+++ b/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
@@ -19,7 +19,6 @@
 import static android.app.PendingIntent.FLAG_CANCEL_CURRENT;
 import static android.app.PendingIntent.FLAG_IMMUTABLE;
 import static android.app.PendingIntent.FLAG_ONE_SHOT;
-import static android.companion.CompanionDeviceManager.COMPANION_DEVICE_DISCOVERY_PACKAGE_NAME;
 import static android.companion.CompanionDeviceManager.REASON_INTERNAL_ERROR;
 import static android.companion.CompanionDeviceManager.RESULT_INTERNAL_ERROR;
 import static android.content.ComponentName.createRelative;
@@ -60,6 +59,7 @@
 import android.util.PackageUtils;
 import android.util.Slog;
 
+import com.android.internal.R;
 import com.android.internal.util.ArrayUtils;
 
 import java.util.Arrays;
@@ -114,9 +114,6 @@
 class AssociationRequestsProcessor {
     private static final String TAG = "CDM_AssociationRequestsProcessor";
 
-    private static final ComponentName ASSOCIATION_REQUEST_APPROVAL_ACTIVITY =
-            createRelative(COMPANION_DEVICE_DISCOVERY_PACKAGE_NAME, ".CompanionDeviceActivity");
-
     // AssociationRequestsProcessor <-> UI
     private static final String EXTRA_APPLICATION_CALLBACK = "application_callback";
     private static final String EXTRA_ASSOCIATION_REQUEST = "association_request";
@@ -138,6 +135,8 @@
     private final @NonNull CompanionDeviceManagerService mService;
     private final @NonNull PackageManagerInternal mPackageManager;
     private final @NonNull AssociationStoreImpl mAssociationStore;
+    @NonNull
+    private final ComponentName mCompanionDeviceActivity;
 
     AssociationRequestsProcessor(@NonNull CompanionDeviceManagerService service,
             @NonNull AssociationStoreImpl associationStore) {
@@ -145,6 +144,9 @@
         mService = service;
         mPackageManager = service.mPackageManagerInternal;
         mAssociationStore = associationStore;
+        mCompanionDeviceActivity = createRelative(
+                mContext.getString(R.string.config_companionDeviceManagerPackage),
+                ".CompanionDeviceActivity");
     }
 
     /**
@@ -197,7 +199,7 @@
         extras.putParcelable(EXTRA_RESULT_RECEIVER, prepareForIpc(mOnRequestConfirmationReceiver));
 
         final Intent intent = new Intent();
-        intent.setComponent(ASSOCIATION_REQUEST_APPROVAL_ACTIVITY);
+        intent.setComponent(mCompanionDeviceActivity);
         intent.putExtras(extras);
 
         // 2b.3. Create a PendingIntent.
@@ -224,7 +226,7 @@
         extras.putBoolean(EXTRA_FORCE_CANCEL_CONFIRMATION, true);
 
         final Intent intent = new Intent();
-        intent.setComponent(ASSOCIATION_REQUEST_APPROVAL_ACTIVITY);
+        intent.setComponent(mCompanionDeviceActivity);
         intent.putExtras(extras);
 
         return createPendingIntent(packageUid, intent);
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 611541f..89e8a18 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -17,7 +17,10 @@
 
 package com.android.server.companion;
 
+import static android.Manifest.permission.ASSOCIATE_COMPANION_DEVICES;
+import static android.Manifest.permission.DELIVER_COMPANION_MESSAGES;
 import static android.Manifest.permission.MANAGE_COMPANION_DEVICES;
+import static android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE;
 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE;
 import static android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION;
 import static android.content.pm.PackageManager.CERT_INPUT_SHA256;
@@ -34,7 +37,6 @@
 import static com.android.server.companion.PackageUtils.getPackageInfo;
 import static com.android.server.companion.PermissionsUtils.checkCallerCanManageCompanionDevice;
 import static com.android.server.companion.PermissionsUtils.enforceCallerCanManageAssociationsForPackage;
-import static com.android.server.companion.PermissionsUtils.enforceCallerCanManageCompanionDevice;
 import static com.android.server.companion.PermissionsUtils.enforceCallerIsSystemOr;
 import static com.android.server.companion.PermissionsUtils.enforceCallerIsSystemOrCanInteractWithUserId;
 import static com.android.server.companion.PermissionsUtils.sanitizeWithCallerChecks;
@@ -44,6 +46,7 @@
 import static java.util.concurrent.TimeUnit.DAYS;
 import static java.util.concurrent.TimeUnit.MINUTES;
 
+import android.annotation.EnforcePermission;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SuppressLint;
@@ -604,29 +607,33 @@
         }
 
         @Override
+        @EnforcePermission(MANAGE_COMPANION_DEVICES)
         public List<AssociationInfo> getAllAssociationsForUser(int userId) throws RemoteException {
+            getAllAssociationsForUser_enforcePermission();
+
             enforceCallerIsSystemOrCanInteractWithUserId(getContext(), userId);
-            enforceCallerCanManageCompanionDevice(getContext(), "getAllAssociationsForUser");
 
             return mAssociationStore.getAssociationsForUser(userId);
         }
 
         @Override
+        @EnforcePermission(MANAGE_COMPANION_DEVICES)
         public void addOnAssociationsChangedListener(IOnAssociationsChangedListener listener,
                 int userId) {
+            addOnAssociationsChangedListener_enforcePermission();
+
             enforceCallerIsSystemOrCanInteractWithUserId(getContext(), userId);
-            enforceCallerCanManageCompanionDevice(getContext(),
-                    "addOnAssociationsChangedListener");
 
             mListeners.register(listener, userId);
         }
 
         @Override
+        @EnforcePermission(MANAGE_COMPANION_DEVICES)
         public void removeOnAssociationsChangedListener(IOnAssociationsChangedListener listener,
                 int userId) {
+            removeOnAssociationsChangedListener_enforcePermission();
+
             enforceCallerIsSystemOrCanInteractWithUserId(getContext(), userId);
-            enforceCallerCanManageCompanionDevice(
-                    getContext(), "removeOnAssociationsChangedListener");
 
             mListeners.unregister(listener);
         }
@@ -658,6 +665,10 @@
             mTransportManager.removeListener(messageType, listener);
         }
 
+        /**
+         * @deprecated use {@link #disassociate(int)} instead
+         */
+        @Deprecated
         @Override
         public void legacyDisassociate(String deviceMacAddress, String packageName, int userId) {
             Log.i(TAG, "legacyDisassociate() pkg=u" + userId + "/" + packageName
@@ -714,10 +725,10 @@
         }
 
         @Override
+        @EnforcePermission(MANAGE_COMPANION_DEVICES)
         public boolean isDeviceAssociatedForWifiConnection(String packageName, String macAddress,
                 int userId) {
-            getContext().enforceCallingOrSelfPermission(
-                    MANAGE_COMPANION_DEVICES, "isDeviceAssociated");
+            isDeviceAssociatedForWifiConnection_enforcePermission();
 
             boolean bypassMacPermission = getContext().getPackageManager().checkPermission(
                     android.Manifest.permission.COMPANION_APPROVE_WIFI_CONNECTIONS, packageName)
@@ -731,15 +742,19 @@
         }
 
         @Override
+        @EnforcePermission(REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE)
         public void registerDevicePresenceListenerService(String deviceAddress,
                 String callingPackage, int userId) throws RemoteException {
+            registerDevicePresenceListenerService_enforcePermission();
             // TODO: take the userId into account.
             registerDevicePresenceListenerActive(callingPackage, deviceAddress, true);
         }
 
         @Override
+        @EnforcePermission(REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE)
         public void unregisterDevicePresenceListenerService(String deviceAddress,
                 String callingPackage, int userId) throws RemoteException {
+            unregisterDevicePresenceListenerService_enforcePermission();
             // TODO: take the userId into account.
             registerDevicePresenceListenerActive(callingPackage, deviceAddress, false);
         }
@@ -768,14 +783,20 @@
         }
 
         @Override
+        @EnforcePermission(DELIVER_COMPANION_MESSAGES)
         public void attachSystemDataTransport(String packageName, int userId, int associationId,
                 ParcelFileDescriptor fd) {
+            attachSystemDataTransport_enforcePermission();
+
             getAssociationWithCallerChecks(associationId);
             mTransportManager.attachSystemDataTransport(packageName, userId, associationId, fd);
         }
 
         @Override
+        @EnforcePermission(DELIVER_COMPANION_MESSAGES)
         public void detachSystemDataTransport(String packageName, int userId, int associationId) {
+            detachSystemDataTransport_enforcePermission();
+
             getAssociationWithCallerChecks(associationId);
             mTransportManager.detachSystemDataTransport(packageName, userId, associationId);
         }
@@ -844,9 +865,6 @@
                         + " deviceAddress=" + deviceAddress);
             }
 
-            getContext().enforceCallingOrSelfPermission(
-                    android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE,
-                    "[un]registerDevicePresenceListenerService");
             final int userId = getCallingUserId();
             enforceCallerIsSystemOr(userId, packageName);
 
@@ -889,17 +907,17 @@
         }
 
         @Override
+        @EnforcePermission(ASSOCIATE_COMPANION_DEVICES)
         public void createAssociation(String packageName, String macAddress, int userId,
                 byte[] certificate) {
+            createAssociation_enforcePermission();
+
             if (!getContext().getPackageManager().hasSigningCertificate(
                     packageName, certificate, CERT_INPUT_SHA256)) {
                 Slog.e(TAG, "Given certificate doesn't match the package certificate.");
                 return;
             }
 
-            getContext().enforceCallingOrSelfPermission(
-                    android.Manifest.permission.ASSOCIATE_COMPANION_DEVICES, "createAssociation");
-
             final MacAddress macAddressObj = MacAddress.fromString(macAddress);
             createNewAssociation(userId, packageName, macAddressObj, null, null, false);
         }
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
index 04fbab4..c79466f 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
@@ -341,7 +341,7 @@
         pw.println("  remove-inactive-associations");
         pw.println("      Remove self-managed associations that have not been active ");
         pw.println("      for a long time (90 days or as configured via ");
-        pw.println("      \"debug.cdm.cdmservice.cleanup_time_window\" system property). ");
+        pw.println("      \"debug.cdm.cdmservice.removal_time_window\" system property). ");
         pw.println("      USE FOR DEBUGGING AND/OR TESTING PURPOSES ONLY.");
 
         pw.println("  create-emulated-transport <ASSOCIATION_ID>");
diff --git a/services/companion/java/com/android/server/companion/PermissionsUtils.java b/services/companion/java/com/android/server/companion/PermissionsUtils.java
index 0ff3fb7..f4e14df 100644
--- a/services/companion/java/com/android/server/companion/PermissionsUtils.java
+++ b/services/companion/java/com/android/server/companion/PermissionsUtils.java
@@ -163,13 +163,6 @@
         return context.checkCallingPermission(MANAGE_COMPANION_DEVICES) == PERMISSION_GRANTED;
     }
 
-    static void enforceCallerCanManageCompanionDevice(@NonNull Context context,
-            @Nullable String message) {
-        if (getCallingUid() == SYSTEM_UID) return;
-
-        context.enforceCallingPermission(MANAGE_COMPANION_DEVICES, message);
-    }
-
     static void enforceCallerCanManageAssociationsForPackage(@NonNull Context context,
             @UserIdInt int userId, @NonNull String packageName,
             @Nullable String actionDescription) {
diff --git a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
index dd7d38f..82387a1 100644
--- a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
+++ b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
@@ -19,7 +19,6 @@
 import static android.app.PendingIntent.FLAG_CANCEL_CURRENT;
 import static android.app.PendingIntent.FLAG_IMMUTABLE;
 import static android.app.PendingIntent.FLAG_ONE_SHOT;
-import static android.companion.CompanionDeviceManager.COMPANION_DEVICE_DISCOVERY_PACKAGE_NAME;
 import static android.content.ComponentName.createRelative;
 
 import static com.android.server.companion.Utils.prepareForIpc;
@@ -48,6 +47,7 @@
 import android.permission.PermissionControllerManager;
 import android.util.Slog;
 
+import com.android.internal.R;
 import com.android.server.companion.AssociationStore;
 import com.android.server.companion.CompanionDeviceManagerService;
 import com.android.server.companion.PermissionsUtils;
@@ -75,9 +75,6 @@
     private static final String EXTRA_COMPANION_DEVICE_NAME = "companion_device_name";
     private static final String EXTRA_SYSTEM_DATA_TRANSFER_RESULT_RECEIVER =
             "system_data_transfer_result_receiver";
-    private static final ComponentName SYSTEM_DATA_TRANSFER_REQUEST_APPROVAL_ACTIVITY =
-            createRelative(COMPANION_DEVICE_DISCOVERY_PACKAGE_NAME,
-                    ".CompanionDeviceDataTransferActivity");
 
     private final Context mContext;
     private final AssociationStore mAssociationStore;
@@ -85,6 +82,7 @@
     private final CompanionTransportManager mTransportManager;
     private final PermissionControllerManager mPermissionControllerManager;
     private final ExecutorService mExecutor;
+    private final ComponentName mCompanionDeviceDataTransferActivity;
 
     public SystemDataTransferProcessor(CompanionDeviceManagerService service,
             AssociationStore associationStore,
@@ -108,6 +106,9 @@
         mTransportManager.addListener(MESSAGE_REQUEST_PERMISSION_RESTORE, messageListener);
         mPermissionControllerManager = mContext.getSystemService(PermissionControllerManager.class);
         mExecutor = Executors.newSingleThreadExecutor();
+        mCompanionDeviceDataTransferActivity = createRelative(
+                mContext.getString(R.string.config_companionDeviceManagerPackage),
+                ".CompanionDeviceDataTransferActivity");
     }
 
     /**
@@ -146,7 +147,7 @@
                 prepareForIpc(mOnSystemDataTransferRequestConfirmationReceiver));
 
         final Intent intent = new Intent();
-        intent.setComponent(SYSTEM_DATA_TRANSFER_REQUEST_APPROVAL_ACTIVITY);
+        intent.setComponent(mCompanionDeviceDataTransferActivity);
         intent.putExtras(extras);
 
         // Create a PendingIntent
diff --git a/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java b/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
index a49021a..41867f9 100644
--- a/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
+++ b/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
@@ -16,8 +16,6 @@
 
 package com.android.server.companion.transport;
 
-import static android.Manifest.permission.DELIVER_COMPANION_MESSAGES;
-
 import static com.android.server.companion.transport.Transport.MESSAGE_REQUEST_PERMISSION_RESTORE;
 
 import android.annotation.NonNull;
@@ -140,7 +138,6 @@
 
     public void attachSystemDataTransport(String packageName, int userId, int associationId,
             ParcelFileDescriptor fd) {
-        mContext.enforceCallingOrSelfPermission(DELIVER_COMPANION_MESSAGES, TAG);
         synchronized (mTransports) {
             if (mTransports.contains(associationId)) {
                 detachSystemDataTransport(packageName, userId, associationId);
@@ -154,7 +151,6 @@
     }
 
     public void detachSystemDataTransport(String packageName, int userId, int associationId) {
-        mContext.enforceCallingOrSelfPermission(DELIVER_COMPANION_MESSAGES, TAG);
         synchronized (mTransports) {
             final Transport transport = mTransports.get(associationId);
             if (transport != null) {
diff --git a/services/companion/java/com/android/server/companion/virtual/OWNERS b/services/companion/java/com/android/server/companion/virtual/OWNERS
index 5e8291f..83143a4 100644
--- a/services/companion/java/com/android/server/companion/virtual/OWNERS
+++ b/services/companion/java/com/android/server/companion/virtual/OWNERS
@@ -2,4 +2,5 @@
 
 ogunwale@google.com
 michaelwr@google.com
-vladokom@google.com
\ No newline at end of file
+vladokom@google.com
+marvinramin@google.com
\ No newline at end of file
diff --git a/services/companion/java/com/android/server/companion/virtual/SensorController.java b/services/companion/java/com/android/server/companion/virtual/SensorController.java
index fb99bff..a831401 100644
--- a/services/companion/java/com/android/server/companion/virtual/SensorController.java
+++ b/services/companion/java/com/android/server/companion/virtual/SensorController.java
@@ -18,17 +18,20 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.companion.virtual.IVirtualDevice;
 import android.companion.virtual.sensor.IVirtualSensorCallback;
 import android.companion.virtual.sensor.VirtualSensor;
 import android.companion.virtual.sensor.VirtualSensorConfig;
 import android.companion.virtual.sensor.VirtualSensorEvent;
 import android.hardware.SensorDirectChannel;
+import android.os.Binder;
 import android.os.IBinder;
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
 import android.os.SharedMemory;
 import android.util.ArrayMap;
 import android.util.Slog;
+import android.util.SparseArray;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
@@ -36,6 +39,9 @@
 import com.android.server.sensors.SensorManagerInternal;
 
 import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.concurrent.atomic.AtomicInteger;
@@ -54,46 +60,64 @@
 
     private final Object mLock = new Object();
     private final int mVirtualDeviceId;
+
     @GuardedBy("mLock")
     private final ArrayMap<IBinder, SensorDescriptor> mSensorDescriptors = new ArrayMap<>();
 
+    // This device's sensors, keyed by sensor handle.
+    @GuardedBy("mLock")
+    private SparseArray<VirtualSensor> mVirtualSensors = new SparseArray<>();
+    @GuardedBy("mLock")
+    private List<VirtualSensor> mVirtualSensorList = null;
+
     @NonNull
     private final SensorManagerInternal.RuntimeSensorCallback mRuntimeSensorCallback;
     private final SensorManagerInternal mSensorManagerInternal;
     private final VirtualDeviceManagerInternal mVdmInternal;
 
-    public SensorController(int virtualDeviceId,
-            @Nullable IVirtualSensorCallback virtualSensorCallback) {
+    public SensorController(@NonNull IVirtualDevice virtualDevice, int virtualDeviceId,
+            @Nullable IVirtualSensorCallback virtualSensorCallback,
+            @NonNull List<VirtualSensorConfig> sensors) {
         mVirtualDeviceId = virtualDeviceId;
         mRuntimeSensorCallback = new RuntimeSensorCallbackWrapper(virtualSensorCallback);
         mSensorManagerInternal = LocalServices.getService(SensorManagerInternal.class);
         mVdmInternal = LocalServices.getService(VirtualDeviceManagerInternal.class);
+        createSensors(virtualDevice, sensors);
     }
 
     void close() {
         synchronized (mLock) {
             mSensorDescriptors.values().forEach(
-                    descriptor -> mSensorManagerInternal.removeRuntimeSensor(
-                            descriptor.getHandle()));
+                    descriptor -> mSensorManagerInternal.removeRuntimeSensor(descriptor.mHandle));
             mSensorDescriptors.clear();
+            mVirtualSensors.clear();
+            mVirtualSensorList = null;
         }
     }
 
-    int createSensor(@NonNull IBinder sensorToken, @NonNull VirtualSensorConfig config) {
-        Objects.requireNonNull(sensorToken);
-        Objects.requireNonNull(config);
+    private void createSensors(@NonNull IVirtualDevice virtualDevice,
+            @NonNull List<VirtualSensorConfig> configs) {
+        Objects.requireNonNull(virtualDevice);
+        final long token = Binder.clearCallingIdentity();
         try {
-            return createSensorInternal(sensorToken, config);
+            for (VirtualSensorConfig config : configs) {
+                createSensorInternal(virtualDevice, config);
+            }
         } catch (SensorCreationException e) {
-            throw new RuntimeException(
-                    "Failed to create virtual sensor '" + config.getName() + "'.", e);
+            throw new RuntimeException("Failed to create virtual sensor", e);
+        } finally {
+            Binder.restoreCallingIdentity(token);
         }
     }
 
-    private int createSensorInternal(IBinder sensorToken, VirtualSensorConfig config)
+    private void createSensorInternal(@NonNull IVirtualDevice virtualDevice,
+            @NonNull VirtualSensorConfig config)
             throws SensorCreationException {
+        Objects.requireNonNull(config);
         if (config.getType() <= 0) {
-            throw new SensorCreationException("Received an invalid virtual sensor type.");
+            throw new SensorCreationException(
+                    "Received an invalid virtual sensor type (config name '" + config.getName()
+                            + "').");
         }
         final int handle = mSensorManagerInternal.createRuntimeSensor(mVirtualDeviceId,
                 config.getType(), config.getName(),
@@ -101,15 +125,20 @@
                 config.getResolution(), config.getPower(), config.getMinDelay(),
                 config.getMaxDelay(), config.getFlags(), mRuntimeSensorCallback);
         if (handle <= 0) {
-            throw new SensorCreationException("Received an invalid virtual sensor handle.");
+            throw new SensorCreationException(
+                    "Received an invalid virtual sensor handle '" + config.getName() + "'.");
         }
 
+        SensorDescriptor sensorDescriptor = new SensorDescriptor(
+                handle, config.getType(), config.getName());
+        final IBinder sensorToken =
+                new Binder("android.hardware.sensor.VirtualSensor:" + config.getName());
+        VirtualSensor sensor = new VirtualSensor(handle, config.getType(), config.getName(),
+                virtualDevice, sensorToken);
         synchronized (mLock) {
-            SensorDescriptor sensorDescriptor = new SensorDescriptor(
-                    handle, config.getType(), config.getName());
             mSensorDescriptors.put(sensorToken, sensorDescriptor);
+            mVirtualSensors.put(handle, sensor);
         }
-        return handle;
     }
 
     boolean sendSensorEvent(@NonNull IBinder token, @NonNull VirtualSensorEvent event) {
@@ -126,17 +155,25 @@
         }
     }
 
-    void unregisterSensor(@NonNull IBinder token) {
-        Objects.requireNonNull(token);
+    @Nullable
+    VirtualSensor getSensorByHandle(int handle) {
         synchronized (mLock) {
-            final SensorDescriptor sensorDescriptor = mSensorDescriptors.remove(token);
-            if (sensorDescriptor == null) {
-                throw new IllegalArgumentException("Could not unregister sensor for given token");
-            }
-            mSensorManagerInternal.removeRuntimeSensor(sensorDescriptor.getHandle());
+            return mVirtualSensors.get(handle);
         }
     }
 
+    List<VirtualSensor> getSensorList() {
+        synchronized (mLock) {
+            if (mVirtualSensorList == null) {
+                mVirtualSensorList = new ArrayList<>(mVirtualSensors.size());
+                for (int i = 0; i < mVirtualSensors.size(); ++i) {
+                    mVirtualSensorList.add(mVirtualSensors.valueAt(i));
+                }
+                mVirtualSensorList = Collections.unmodifiableList(mVirtualSensorList);
+            }
+            return mVirtualSensorList;
+        }
+    }
 
     void dump(@NonNull PrintWriter fout) {
         fout.println("    SensorController: ");
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 cd2f844..51acfd3 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -42,13 +42,13 @@
 import android.companion.virtual.audio.IAudioConfigChangedCallback;
 import android.companion.virtual.audio.IAudioRoutingCallback;
 import android.companion.virtual.sensor.VirtualSensor;
-import android.companion.virtual.sensor.VirtualSensorConfig;
 import android.companion.virtual.sensor.VirtualSensorEvent;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
 import android.graphics.PointF;
 import android.hardware.display.DisplayManager;
 import android.hardware.display.DisplayManagerGlobal;
@@ -93,7 +93,6 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -114,6 +113,8 @@
                     | DisplayManager.VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH
                     | DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_FOCUS;
 
+    private static final String PERSISTENT_ID_PREFIX_CDM_ASSOCIATION = "companion:";
+
     /**
      * Timeout until {@link #launchPendingIntent} stops waiting for an activity to be launched.
      */
@@ -127,6 +128,7 @@
     private final PendingTrampolineCallback mPendingTrampolineCallback;
     private final int mOwnerUid;
     private int mDeviceId;
+    private @Nullable String mPersistentDeviceId;
     // Thou shall not hold the mVirtualDeviceLock over the mInputController calls.
     // Holding the lock can lead to lock inversion with GlobalWindowManagerLock.
     // 1. After display is created the window manager calls into VDM during construction
@@ -156,11 +158,6 @@
     @GuardedBy("mVirtualDeviceLock")
     @Nullable
     private LocaleList mLocaleList = null;
-    // This device's sensors, keyed by sensor handle.
-    @GuardedBy("mVirtualDeviceLock")
-    private SparseArray<VirtualSensor> mVirtualSensors = new SparseArray<>();
-    @GuardedBy("mVirtualDeviceLock")
-    private List<VirtualSensor> mVirtualSensorList = null;
 
     private ActivityListener createListenerAdapter() {
         return new ActivityListener() {
@@ -217,7 +214,6 @@
                 ownerUid,
                 deviceId,
                 /* inputController= */ null,
-                /* sensorController= */ null,
                 cameraAccessController,
                 pendingTrampolineCallback,
                 activityListener,
@@ -236,7 +232,6 @@
             int ownerUid,
             int deviceId,
             InputController inputController,
-            SensorController sensorController,
             CameraAccessController cameraAccessController,
             PendingTrampolineCallback pendingTrampolineCallback,
             IVirtualDeviceActivityListener activityListener,
@@ -248,6 +243,7 @@
         UserHandle ownerUserHandle = UserHandle.getUserHandleForUid(ownerUid);
         mContext = context.createContextAsUser(ownerUserHandle, 0);
         mAssociationInfo = associationInfo;
+        mPersistentDeviceId = PERSISTENT_ID_PREFIX_CDM_ASSOCIATION + associationInfo.getId();
         mService = service;
         mPendingTrampolineCallback = pendingTrampolineCallback;
         mActivityListener = activityListener;
@@ -265,15 +261,8 @@
         } else {
             mInputController = inputController;
         }
-        if (sensorController == null) {
-            mSensorController = new SensorController(mDeviceId, mParams.getVirtualSensorCallback());
-        } else {
-            mSensorController = sensorController;
-        }
-        final List<VirtualSensorConfig> virtualSensorConfigs = mParams.getVirtualSensorConfigs();
-        for (int i = 0; i < virtualSensorConfigs.size(); ++i) {
-            createVirtualSensor(virtualSensorConfigs.get(i));
-        }
+        mSensorController = new SensorController(this, mDeviceId,
+                mParams.getVirtualSensorCallback(), mParams.getVirtualSensorConfigs());
         mCameraAccessController = cameraAccessController;
         mCameraAccessController.startObservingIfNeeded();
         try {
@@ -283,6 +272,11 @@
         }
     }
 
+    @VisibleForTesting
+    SensorController getSensorControllerForTest() {
+        return mSensorController;
+    }
+
     /**
      * Returns the flags that should be added to any virtual displays created on this virtual
      * device.
@@ -339,6 +333,12 @@
         return mDeviceId;
     }
 
+    /** Returns the unique device ID of this device. */
+    @Override // Binder call
+    public @Nullable String getPersistentDeviceId() {
+        return mPersistentDeviceId;
+    }
+
     @Override // Binder call
     public int getAssociationId() {
         return mAssociationInfo.getId();
@@ -425,8 +425,6 @@
                     virtualDisplaysToBeReleased[i] = mVirtualDisplays.valueAt(i);
                 }
                 mVirtualDisplays.clear();
-                mVirtualSensorList = null;
-                mVirtualSensors.clear();
             }
             // Destroy the display outside locked section.
             for (VirtualDisplayWrapper virtualDisplayWrapper : virtualDisplaysToBeReleased) {
@@ -504,13 +502,7 @@
     public void createVirtualDpad(VirtualDpadConfig config, @NonNull IBinder deviceToken) {
         super.createVirtualDpad_enforcePermission();
         Objects.requireNonNull(config);
-        synchronized (mVirtualDeviceLock) {
-            if (!mVirtualDisplays.contains(config.getAssociatedDisplayId())) {
-                throw new SecurityException(
-                        "Cannot create a virtual dpad for a display not associated with "
-                                + "this virtual device");
-            }
-        }
+        checkVirtualInputDeviceDisplayIdAssociation(config.getAssociatedDisplayId());
         final long ident = Binder.clearCallingIdentity();
         try {
             mInputController.createDpad(config.getInputDeviceName(), config.getVendorId(),
@@ -525,12 +517,8 @@
     public void createVirtualKeyboard(VirtualKeyboardConfig config, @NonNull IBinder deviceToken) {
         super.createVirtualKeyboard_enforcePermission();
         Objects.requireNonNull(config);
+        checkVirtualInputDeviceDisplayIdAssociation(config.getAssociatedDisplayId());
         synchronized (mVirtualDeviceLock) {
-            if (!mVirtualDisplays.contains(config.getAssociatedDisplayId())) {
-                throw new SecurityException(
-                        "Cannot create a virtual keyboard for a display not associated with "
-                                + "this virtual device");
-            }
             mLocaleList = LocaleList.forLanguageTags(config.getLanguageTag());
         }
         final long ident = Binder.clearCallingIdentity();
@@ -548,13 +536,7 @@
     public void createVirtualMouse(VirtualMouseConfig config, @NonNull IBinder deviceToken) {
         super.createVirtualMouse_enforcePermission();
         Objects.requireNonNull(config);
-        synchronized (mVirtualDeviceLock) {
-            if (!mVirtualDisplays.contains(config.getAssociatedDisplayId())) {
-                throw new SecurityException(
-                        "Cannot create a virtual mouse for a display not associated with this "
-                                + "virtual device");
-            }
-        }
+        checkVirtualInputDeviceDisplayIdAssociation(config.getAssociatedDisplayId());
         final long ident = Binder.clearCallingIdentity();
         try {
             mInputController.createMouse(config.getInputDeviceName(), config.getVendorId(),
@@ -570,13 +552,7 @@
             @NonNull IBinder deviceToken) {
         super.createVirtualTouchscreen_enforcePermission();
         Objects.requireNonNull(config);
-        synchronized (mVirtualDeviceLock) {
-            if (!mVirtualDisplays.contains(config.getAssociatedDisplayId())) {
-                throw new SecurityException(
-                        "Cannot create a virtual touchscreen for a display not associated with "
-                                + "this virtual device");
-            }
-        }
+        checkVirtualInputDeviceDisplayIdAssociation(config.getAssociatedDisplayId());
         int screenHeight = config.getHeight();
         int screenWidth = config.getWidth();
         if (screenHeight <= 0 || screenWidth <= 0) {
@@ -601,13 +577,7 @@
             @NonNull IBinder deviceToken) {
         super.createVirtualNavigationTouchpad_enforcePermission();
         Objects.requireNonNull(config);
-        synchronized (mVirtualDeviceLock) {
-            if (!mVirtualDisplays.contains(config.getAssociatedDisplayId())) {
-                throw new SecurityException(
-                        "Cannot create a virtual navigation touchpad for a display not associated "
-                                + "with this virtual device");
-            }
-        }
+        checkVirtualInputDeviceDisplayIdAssociation(config.getAssociatedDisplayId());
         int touchpadHeight = config.getHeight();
         int touchpadWidth = config.getWidth();
         if (touchpadHeight <= 0 || touchpadWidth <= 0) {
@@ -750,43 +720,17 @@
         }
     }
 
-    private void createVirtualSensor(@NonNull VirtualSensorConfig config) {
-        final IBinder sensorToken =
-                new Binder("android.hardware.sensor.VirtualSensor:" + config.getName());
-        final long ident = Binder.clearCallingIdentity();
-        try {
-            int handle = mSensorController.createSensor(sensorToken, config);
-            VirtualSensor sensor = new VirtualSensor(handle, config.getType(), config.getName(),
-                    this, sensorToken);
-            synchronized (mVirtualDeviceLock) {
-                mVirtualSensors.put(handle, sensor);
-            }
-        } finally {
-            Binder.restoreCallingIdentity(ident);
-        }
-    }
-
     @Override // Binder call
     @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
     @Nullable
     public List<VirtualSensor> getVirtualSensorList() {
         super.getVirtualSensorList_enforcePermission();
-        synchronized (mVirtualDeviceLock) {
-            if (mVirtualSensorList == null) {
-                mVirtualSensorList = new ArrayList<>();
-                for (int i = 0; i < mVirtualSensors.size(); ++i) {
-                    mVirtualSensorList.add(mVirtualSensors.valueAt(i));
-                }
-                mVirtualSensorList = Collections.unmodifiableList(mVirtualSensorList);
-            }
-            return mVirtualSensorList;
-        }
+        return mSensorController.getSensorList();
     }
 
+    @Nullable
     VirtualSensor getVirtualSensorByHandle(int handle) {
-        synchronized (mVirtualDeviceLock) {
-            return mVirtualSensors.get(handle);
-        }
+        return mSensorController.getSensorByHandle(handle);
     }
 
     @Override // Binder call
@@ -995,6 +939,21 @@
         }
     }
 
+    private void checkVirtualInputDeviceDisplayIdAssociation(int displayId) {
+        if (mContext.checkCallingPermission(android.Manifest.permission.INJECT_EVENTS)
+                    == PackageManager.PERMISSION_GRANTED) {
+            // The INJECT_EVENTS permission allows for injecting input to any window / display.
+            return;
+        }
+        synchronized (mVirtualDeviceLock) {
+            if (!mVirtualDisplays.contains(displayId)) {
+                throw new SecurityException(
+                        "Cannot create a virtual input device for display " + displayId
+                                + " which not associated with this virtual device");
+            }
+        }
+    }
+
     /**
      * Release resources tied to virtual display owned by this VirtualDevice instance.
      *
@@ -1094,8 +1053,8 @@
     }
 
     void onEnteringPipBlocked(int uid) {
-        showToastWhereUidIsRunning(uid, com.android.internal.R.string.vdm_pip_blocked,
-                Toast.LENGTH_LONG, mContext.getMainLooper());
+        // Do nothing. ActivityRecord#checkEnterPictureInPictureState logs that the display does not
+        // support PiP.
     }
 
     void playSoundEffect(int effectType) {
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 e9b9980..bbe7289 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -310,6 +310,7 @@
                     }
                 };
 
+        @android.annotation.EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
         @Override // Binder call
         public IVirtualDevice createVirtualDevice(
                 IBinder token,
@@ -318,9 +319,7 @@
                 @NonNull VirtualDeviceParams params,
                 @NonNull IVirtualDeviceActivityListener activityListener,
                 @NonNull IVirtualDeviceSoundEffectListener soundEffectListener) {
-            getContext().enforceCallingOrSelfPermission(
-                    android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
-                    "createVirtualDevice");
+            createVirtualDevice_enforcePermission();
             final int callingUid = getCallingUid();
             if (!PermissionUtils.validateCallingPackageName(getContext(), packageName)) {
                 throw new SecurityException(
@@ -397,7 +396,8 @@
                 for (int i = 0; i < mVirtualDevices.size(); i++) {
                     final VirtualDeviceImpl device = mVirtualDevices.valueAt(i);
                     virtualDevices.add(
-                            new VirtualDevice(device.getDeviceId(), device.getDeviceName()));
+                            new VirtualDevice(device.getDeviceId(), device.getPersistentDeviceId(),
+                                    device.getDeviceName()));
                 }
             }
             return virtualDevices;
@@ -687,6 +687,17 @@
         }
 
         @Override
+        public int getAssociationIdForDevice(int deviceId) {
+            VirtualDeviceImpl virtualDevice;
+            synchronized (mVirtualDeviceManagerLock) {
+                virtualDevice = mVirtualDevices.get(deviceId);
+            }
+            return virtualDevice == null
+                    ? VirtualDeviceManager.ASSOCIATION_ID_INVALID
+                    : virtualDevice.getAssociationId();
+        }
+
+        @Override
         public void registerVirtualDisplayListener(
                 @NonNull VirtualDisplayListener listener) {
             synchronized (mVirtualDeviceManagerLock) {
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
index 61fc32d..ca1ab9b 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
@@ -50,6 +50,7 @@
 import android.content.pm.ActivityPresentationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ParceledListSlice;
 import android.content.pm.UserInfo;
 import android.database.ContentObserver;
 import android.os.Binder;
@@ -69,6 +70,7 @@
 import android.provider.DeviceConfig.Properties;
 import android.provider.Settings;
 import android.service.contentcapture.ActivityEvent.ActivityEventType;
+import android.service.contentcapture.ContentCaptureServiceInfo;
 import android.service.contentcapture.IDataShareCallback;
 import android.service.contentcapture.IDataShareReadAdapter;
 import android.service.voice.VoiceInteractionManagerInternal;
@@ -79,6 +81,7 @@
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
 import android.view.contentcapture.ContentCaptureCondition;
+import android.view.contentcapture.ContentCaptureEvent;
 import android.view.contentcapture.ContentCaptureHelper;
 import android.view.contentcapture.ContentCaptureManager;
 import android.view.contentcapture.DataRemovalRequest;
@@ -88,11 +91,15 @@
 import android.view.contentcapture.IDataShareWriteAdapter;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.infra.AbstractRemoteService;
 import com.android.internal.infra.GlobalWhitelistState;
 import com.android.internal.os.IResultReceiver;
 import com.android.internal.util.DumpUtils;
 import com.android.server.LocalServices;
+import com.android.server.contentprotection.ContentProtectionBlocklistManager;
+import com.android.server.contentprotection.ContentProtectionPackageManager;
+import com.android.server.contentprotection.RemoteContentProtectionService;
 import com.android.server.infra.AbstractMasterSystemService;
 import com.android.server.infra.FrameworkResourcesServiceNameResolver;
 
@@ -117,7 +124,7 @@
  * with other sources to provide contextual data in other areas of the system
  * such as Autofill.
  */
-public final class ContentCaptureManagerService extends
+public class ContentCaptureManagerService extends
         AbstractMasterSystemService<ContentCaptureManagerService, ContentCapturePerUserService> {
 
     private static final String TAG = ContentCaptureManagerService.class.getSimpleName();
@@ -163,13 +170,35 @@
     private boolean mDisabledByDeviceConfig;
 
     // Device-config settings that are cached and passed back to apps
-    @GuardedBy("mLock") int mDevCfgLoggingLevel;
-    @GuardedBy("mLock") int mDevCfgMaxBufferSize;
-    @GuardedBy("mLock") int mDevCfgIdleFlushingFrequencyMs;
-    @GuardedBy("mLock") int mDevCfgTextChangeFlushingFrequencyMs;
-    @GuardedBy("mLock") int mDevCfgLogHistorySize;
-    @GuardedBy("mLock") int mDevCfgIdleUnbindTimeoutMs;
-    @GuardedBy("mLock") boolean mDevCfgDisableFlushForViewTreeAppearing;
+    @GuardedBy("mLock")
+    int mDevCfgLoggingLevel;
+
+    @GuardedBy("mLock")
+    int mDevCfgMaxBufferSize;
+
+    @GuardedBy("mLock")
+    int mDevCfgIdleFlushingFrequencyMs;
+
+    @GuardedBy("mLock")
+    int mDevCfgTextChangeFlushingFrequencyMs;
+
+    @GuardedBy("mLock")
+    int mDevCfgLogHistorySize;
+
+    @GuardedBy("mLock")
+    int mDevCfgIdleUnbindTimeoutMs;
+
+    @GuardedBy("mLock")
+    boolean mDevCfgDisableFlushForViewTreeAppearing;
+
+    @GuardedBy("mLock")
+    boolean mDevCfgEnableContentProtectionReceiver;
+
+    @GuardedBy("mLock")
+    int mDevCfgContentProtectionAppsBlocklistSize;
+
+    @GuardedBy("mLock")
+    int mDevCfgContentProtectionBufferSize;
 
     private final Executor mDataShareExecutor = Executors.newCachedThreadPool();
     private final Handler mHandler = new Handler(Looper.getMainLooper());
@@ -183,6 +212,10 @@
     final GlobalContentCaptureOptions mGlobalContentCaptureOptions =
             new GlobalContentCaptureOptions();
 
+    @Nullable private final ComponentName mContentProtectionServiceComponentName;
+
+    @Nullable private final ContentProtectionBlocklistManager mContentProtectionBlocklistManager;
+
     public ContentCaptureManagerService(@NonNull Context context) {
         super(context, new FrameworkResourcesServiceNameResolver(context,
                 com.android.internal.R.string.config_defaultContentCaptureService),
@@ -220,6 +253,20 @@
                     mServiceNameResolver.getServiceName(userId),
                     mServiceNameResolver.isTemporary(userId));
         }
+
+        if (getEnableContentProtectionReceiverLocked()) {
+            mContentProtectionServiceComponentName = getContentProtectionServiceComponentName();
+            if (mContentProtectionServiceComponentName != null) {
+                mContentProtectionBlocklistManager = createContentProtectionBlocklistManager();
+                mContentProtectionBlocklistManager.updateBlocklist(
+                        mDevCfgContentProtectionAppsBlocklistSize);
+            } else {
+                mContentProtectionBlocklistManager = null;
+            }
+        } else {
+            mContentProtectionServiceComponentName = null;
+            mContentProtectionBlocklistManager = null;
+        }
     }
 
     @Override // from AbstractMasterSystemService
@@ -362,6 +409,11 @@
                 case ContentCaptureManager.DEVICE_CONFIG_PROPERTY_IDLE_UNBIND_TIMEOUT:
                 case ContentCaptureManager
                         .DEVICE_CONFIG_PROPERTY_DISABLE_FLUSH_FOR_VIEW_TREE_APPEARING:
+                case ContentCaptureManager
+                        .DEVICE_CONFIG_PROPERTY_ENABLE_CONTENT_PROTECTION_RECEIVER:
+                case ContentCaptureManager.DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_BUFFER_SIZE:
+                case ContentCaptureManager
+                        .DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_APPS_BLOCKLIST_SIZE:
                     setFineTuneParamsFromDeviceConfig();
                     return;
                 default:
@@ -370,41 +422,84 @@
         }
     }
 
-    private void setFineTuneParamsFromDeviceConfig() {
+    /** @hide */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    protected void setFineTuneParamsFromDeviceConfig() {
         synchronized (mLock) {
-            mDevCfgMaxBufferSize = DeviceConfig.getInt(
-                    DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
-                    ContentCaptureManager.DEVICE_CONFIG_PROPERTY_MAX_BUFFER_SIZE,
-                    ContentCaptureManager.DEFAULT_MAX_BUFFER_SIZE);
-            mDevCfgIdleFlushingFrequencyMs = DeviceConfig.getInt(
-                    DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
-                    ContentCaptureManager.DEVICE_CONFIG_PROPERTY_IDLE_FLUSH_FREQUENCY,
-                    ContentCaptureManager.DEFAULT_IDLE_FLUSHING_FREQUENCY_MS);
-            mDevCfgTextChangeFlushingFrequencyMs = DeviceConfig.getInt(
-                    DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
-                    ContentCaptureManager.DEVICE_CONFIG_PROPERTY_TEXT_CHANGE_FLUSH_FREQUENCY,
-                    ContentCaptureManager.DEFAULT_TEXT_CHANGE_FLUSHING_FREQUENCY_MS);
-            mDevCfgLogHistorySize = DeviceConfig.getInt(
-                    DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
-                    ContentCaptureManager.DEVICE_CONFIG_PROPERTY_LOG_HISTORY_SIZE, 20);
-            mDevCfgIdleUnbindTimeoutMs = DeviceConfig.getInt(
-                    DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
-                    ContentCaptureManager.DEVICE_CONFIG_PROPERTY_IDLE_UNBIND_TIMEOUT,
-                    (int) AbstractRemoteService.PERMANENT_BOUND_TIMEOUT_MS);
-            mDevCfgDisableFlushForViewTreeAppearing = DeviceConfig.getBoolean(
-                    DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
-                    ContentCaptureManager
-                        .DEVICE_CONFIG_PROPERTY_DISABLE_FLUSH_FOR_VIEW_TREE_APPEARING,
-                    false);
+            mDevCfgMaxBufferSize =
+                    DeviceConfig.getInt(
+                            DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
+                            ContentCaptureManager.DEVICE_CONFIG_PROPERTY_MAX_BUFFER_SIZE,
+                            ContentCaptureManager.DEFAULT_MAX_BUFFER_SIZE);
+            mDevCfgIdleFlushingFrequencyMs =
+                    DeviceConfig.getInt(
+                            DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
+                            ContentCaptureManager.DEVICE_CONFIG_PROPERTY_IDLE_FLUSH_FREQUENCY,
+                            ContentCaptureManager.DEFAULT_IDLE_FLUSHING_FREQUENCY_MS);
+            mDevCfgTextChangeFlushingFrequencyMs =
+                    DeviceConfig.getInt(
+                            DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
+                            ContentCaptureManager
+                                    .DEVICE_CONFIG_PROPERTY_TEXT_CHANGE_FLUSH_FREQUENCY,
+                            ContentCaptureManager.DEFAULT_TEXT_CHANGE_FLUSHING_FREQUENCY_MS);
+            mDevCfgLogHistorySize =
+                    DeviceConfig.getInt(
+                            DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
+                            ContentCaptureManager.DEVICE_CONFIG_PROPERTY_LOG_HISTORY_SIZE,
+                            20);
+            mDevCfgIdleUnbindTimeoutMs =
+                    DeviceConfig.getInt(
+                            DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
+                            ContentCaptureManager.DEVICE_CONFIG_PROPERTY_IDLE_UNBIND_TIMEOUT,
+                            (int) AbstractRemoteService.PERMANENT_BOUND_TIMEOUT_MS);
+            mDevCfgDisableFlushForViewTreeAppearing =
+                    DeviceConfig.getBoolean(
+                            DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
+                            ContentCaptureManager
+                                    .DEVICE_CONFIG_PROPERTY_DISABLE_FLUSH_FOR_VIEW_TREE_APPEARING,
+                            false);
+            mDevCfgEnableContentProtectionReceiver =
+                    DeviceConfig.getBoolean(
+                            DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
+                            ContentCaptureManager
+                                    .DEVICE_CONFIG_PROPERTY_ENABLE_CONTENT_PROTECTION_RECEIVER,
+                            ContentCaptureManager.DEFAULT_ENABLE_CONTENT_PROTECTION_RECEIVER);
+            mDevCfgContentProtectionAppsBlocklistSize =
+                    DeviceConfig.getInt(
+                            DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
+                            ContentCaptureManager
+                                    .DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_APPS_BLOCKLIST_SIZE,
+                            ContentCaptureManager.DEFAULT_CONTENT_PROTECTION_APPS_BLOCKLIST_SIZE);
+            // mContentProtectionBlocklistManager.updateBlocklist not called on purpose here to keep
+            // it immutable at this point
+            mDevCfgContentProtectionBufferSize =
+                    DeviceConfig.getInt(
+                            DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
+                            ContentCaptureManager
+                                    .DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_BUFFER_SIZE,
+                            ContentCaptureManager.DEFAULT_CONTENT_PROTECTION_BUFFER_SIZE);
             if (verbose) {
-                Slog.v(TAG, "setFineTuneParamsFromDeviceConfig(): "
-                        + "bufferSize=" + mDevCfgMaxBufferSize
-                        + ", idleFlush=" + mDevCfgIdleFlushingFrequencyMs
-                        + ", textFluxh=" + mDevCfgTextChangeFlushingFrequencyMs
-                        + ", logHistory=" + mDevCfgLogHistorySize
-                        + ", idleUnbindTimeoutMs=" + mDevCfgIdleUnbindTimeoutMs
-                        + ", disableFlushForViewTreeAppearing="
-                        + mDevCfgDisableFlushForViewTreeAppearing);
+                Slog.v(
+                        TAG,
+                        "setFineTuneParamsFromDeviceConfig(): "
+                                + "bufferSize="
+                                + mDevCfgMaxBufferSize
+                                + ", idleFlush="
+                                + mDevCfgIdleFlushingFrequencyMs
+                                + ", textFluxh="
+                                + mDevCfgTextChangeFlushingFrequencyMs
+                                + ", logHistory="
+                                + mDevCfgLogHistorySize
+                                + ", idleUnbindTimeoutMs="
+                                + mDevCfgIdleUnbindTimeoutMs
+                                + ", disableFlushForViewTreeAppearing="
+                                + mDevCfgDisableFlushForViewTreeAppearing
+                                + ", enableContentProtectionReceiver="
+                                + mDevCfgEnableContentProtectionReceiver
+                                + ", contentProtectionAppsBlocklistSize="
+                                + mDevCfgContentProtectionAppsBlocklistSize
+                                + ", contentProtectionBufferSize="
+                                + mDevCfgContentProtectionBufferSize);
             }
         }
     }
@@ -645,24 +740,137 @@
 
         final String prefix2 = prefix + "  ";
 
-        pw.print(prefix); pw.print("Users disabled by Settings: "); pw.println(mDisabledBySettings);
-        pw.print(prefix); pw.println("DeviceConfig Settings: ");
-        pw.print(prefix2); pw.print("disabled: "); pw.println(mDisabledByDeviceConfig);
-        pw.print(prefix2); pw.print("loggingLevel: "); pw.println(mDevCfgLoggingLevel);
-        pw.print(prefix2); pw.print("maxBufferSize: "); pw.println(mDevCfgMaxBufferSize);
-        pw.print(prefix2); pw.print("idleFlushingFrequencyMs: ");
+        pw.print(prefix);
+        pw.print("Users disabled by Settings: ");
+        pw.println(mDisabledBySettings);
+        pw.print(prefix);
+        pw.println("DeviceConfig Settings: ");
+        pw.print(prefix2);
+        pw.print("disabled: ");
+        pw.println(mDisabledByDeviceConfig);
+        pw.print(prefix2);
+        pw.print("loggingLevel: ");
+        pw.println(mDevCfgLoggingLevel);
+        pw.print(prefix2);
+        pw.print("maxBufferSize: ");
+        pw.println(mDevCfgMaxBufferSize);
+        pw.print(prefix2);
+        pw.print("idleFlushingFrequencyMs: ");
         pw.println(mDevCfgIdleFlushingFrequencyMs);
-        pw.print(prefix2); pw.print("textChangeFlushingFrequencyMs: ");
+        pw.print(prefix2);
+        pw.print("textChangeFlushingFrequencyMs: ");
         pw.println(mDevCfgTextChangeFlushingFrequencyMs);
-        pw.print(prefix2); pw.print("logHistorySize: "); pw.println(mDevCfgLogHistorySize);
-        pw.print(prefix2); pw.print("idleUnbindTimeoutMs: ");
+        pw.print(prefix2);
+        pw.print("logHistorySize: ");
+        pw.println(mDevCfgLogHistorySize);
+        pw.print(prefix2);
+        pw.print("idleUnbindTimeoutMs: ");
         pw.println(mDevCfgIdleUnbindTimeoutMs);
-        pw.print(prefix2); pw.print("disableFlushForViewTreeAppearing: ");
+        pw.print(prefix2);
+        pw.print("disableFlushForViewTreeAppearing: ");
         pw.println(mDevCfgDisableFlushForViewTreeAppearing);
-        pw.print(prefix); pw.println("Global Options:");
+        pw.print(prefix2);
+        pw.print("enableContentProtectionReceiver: ");
+        pw.println(mDevCfgEnableContentProtectionReceiver);
+        pw.print(prefix2);
+        pw.print("contentProtectionAppsBlocklistSize: ");
+        pw.println(mDevCfgContentProtectionAppsBlocklistSize);
+        pw.print(prefix2);
+        pw.print("contentProtectionBufferSize: ");
+        pw.println(mDevCfgContentProtectionBufferSize);
+        pw.print(prefix);
+        pw.println("Global Options:");
         mGlobalContentCaptureOptions.dump(prefix2, pw);
     }
 
+    /**
+     * Used by the constructor in order to be able to override the value in the tests.
+     *
+     * @hide
+     */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    @GuardedBy("mLock")
+    protected boolean getEnableContentProtectionReceiverLocked() {
+        return mDevCfgEnableContentProtectionReceiver;
+    }
+
+    /** @hide */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    @NonNull
+    protected ContentProtectionBlocklistManager createContentProtectionBlocklistManager() {
+        return new ContentProtectionBlocklistManager(
+                new ContentProtectionPackageManager(getContext()));
+    }
+
+    @Nullable
+    private ComponentName getContentProtectionServiceComponentName() {
+        String flatComponentName = getContentProtectionServiceFlatComponentName();
+        return ComponentName.unflattenFromString(flatComponentName);
+    }
+
+    /** @hide */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    @Nullable
+    protected String getContentProtectionServiceFlatComponentName() {
+        return getContext()
+                .getString(com.android.internal.R.string.config_defaultContentProtectionService);
+    }
+
+    /**
+     * Can also throw runtime exceptions such as {@link SecurityException}.
+     *
+     * @hide
+     */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    @NonNull
+    protected ContentCaptureServiceInfo createContentProtectionServiceInfo(
+            @NonNull ComponentName componentName) throws PackageManager.NameNotFoundException {
+        return new ContentCaptureServiceInfo(
+                getContext(), componentName, /* isTemp= */ false, UserHandle.getCallingUserId());
+    }
+
+    @Nullable
+    private RemoteContentProtectionService createRemoteContentProtectionService() {
+        if (mContentProtectionServiceComponentName == null) {
+            // This case should not be possible but make sure
+            return null;
+        }
+        synchronized (mLock) {
+            if (!mDevCfgEnableContentProtectionReceiver) {
+                return null;
+            }
+        }
+
+        // Check permissions by trying to construct {@link ContentCaptureServiceInfo}
+        try {
+            createContentProtectionServiceInfo(mContentProtectionServiceComponentName);
+        } catch (Exception ex) {
+            // Swallow, exception was already logged
+            return null;
+        }
+
+        return createRemoteContentProtectionService(mContentProtectionServiceComponentName);
+    }
+
+    /** @hide */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    @NonNull
+    protected RemoteContentProtectionService createRemoteContentProtectionService(
+            @NonNull ComponentName componentName) {
+        return new RemoteContentProtectionService(
+                getContext(),
+                componentName,
+                UserHandle.getCallingUserId(),
+                isBindInstantServiceAllowed());
+    }
+
+    /** @hide */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    @NonNull
+    protected ContentCaptureManagerServiceStub getContentCaptureManagerServiceStub() {
+        return mContentCaptureManagerServiceStub;
+    }
+
     final class ContentCaptureManagerServiceStub extends IContentCaptureManager.Stub {
 
         @Override
@@ -896,6 +1104,19 @@
         public void setDefaultServiceEnabled(@UserIdInt int userId, boolean enabled) {
             ContentCaptureManagerService.this.setDefaultServiceEnabled(userId, enabled);
         }
+
+        @Override
+        public void onLoginDetected(@NonNull ParceledListSlice<ContentCaptureEvent> events) {
+            RemoteContentProtectionService service = createRemoteContentProtectionService();
+            if (service == null) {
+                return;
+            }
+            try {
+                service.onLoginDetected(events);
+            } catch (Exception ex) {
+                Slog.e(TAG, "Failed to call remote service", ex);
+            }
+        }
     }
 
     private final class LocalService extends ContentCaptureManagerInternal {
@@ -984,14 +1205,21 @@
         @GuardedBy("mGlobalWhitelistStateLock")
         public ContentCaptureOptions getOptions(@UserIdInt int userId,
                 @NonNull String packageName) {
-            boolean packageWhitelisted;
+            boolean isContentCaptureReceiverEnabled;
+            boolean isContentProtectionReceiverEnabled;
             ArraySet<ComponentName> whitelistedComponents = null;
+
             synchronized (mGlobalWhitelistStateLock) {
-                packageWhitelisted = isWhitelisted(userId, packageName);
-                if (!packageWhitelisted) {
-                    // Full package is not allowlisted: check individual components first
+                isContentCaptureReceiverEnabled =
+                        isContentCaptureReceiverEnabled(userId, packageName);
+                isContentProtectionReceiverEnabled =
+                        isContentProtectionReceiverEnabled(packageName);
+
+                if (!isContentCaptureReceiverEnabled) {
+                    // Full package is not allowlisted: check individual components next
                     whitelistedComponents = getWhitelistedComponents(userId, packageName);
-                    if (whitelistedComponents == null
+                    if (!isContentProtectionReceiverEnabled
+                            && whitelistedComponents == null
                             && packageName.equals(mServicePackages.get(userId))) {
                         // No components allowlisted either, but let it go because it's the
                         // service's own package
@@ -1010,7 +1238,9 @@
                 }
             }
 
-            if (!packageWhitelisted && whitelistedComponents == null) {
+            if (!isContentCaptureReceiverEnabled
+                    && !isContentProtectionReceiverEnabled
+                    && whitelistedComponents == null) {
                 // No can do!
                 if (verbose) {
                     Slog.v(TAG, "getOptionsForPackage(" + packageName + "): not whitelisted");
@@ -1019,11 +1249,19 @@
             }
 
             synchronized (mLock) {
-                final ContentCaptureOptions options = new ContentCaptureOptions(mDevCfgLoggingLevel,
-                        mDevCfgMaxBufferSize, mDevCfgIdleFlushingFrequencyMs,
-                        mDevCfgTextChangeFlushingFrequencyMs, mDevCfgLogHistorySize,
-                        mDevCfgDisableFlushForViewTreeAppearing,
-                        whitelistedComponents);
+                final ContentCaptureOptions options =
+                        new ContentCaptureOptions(
+                                mDevCfgLoggingLevel,
+                                mDevCfgMaxBufferSize,
+                                mDevCfgIdleFlushingFrequencyMs,
+                                mDevCfgTextChangeFlushingFrequencyMs,
+                                mDevCfgLogHistorySize,
+                                mDevCfgDisableFlushForViewTreeAppearing,
+                                isContentCaptureReceiverEnabled || whitelistedComponents != null,
+                                new ContentCaptureOptions.ContentProtectionOptions(
+                                        isContentProtectionReceiverEnabled,
+                                        mDevCfgContentProtectionBufferSize),
+                                whitelistedComponents);
                 if (verbose) Slog.v(TAG, "getOptionsForPackage(" + packageName + "): " + options);
                 return options;
             }
@@ -1042,6 +1280,36 @@
                 }
             }
         }
+
+        @Override // from GlobalWhitelistState
+        public boolean isWhitelisted(@UserIdInt int userId, @NonNull String packageName) {
+            return isContentCaptureReceiverEnabled(userId, packageName)
+                    || isContentProtectionReceiverEnabled(packageName);
+        }
+
+        @Override // from GlobalWhitelistState
+        public boolean isWhitelisted(@UserIdInt int userId, @NonNull ComponentName componentName) {
+            return super.isWhitelisted(userId, componentName)
+                    || isContentProtectionReceiverEnabled(componentName.getPackageName());
+        }
+
+        private boolean isContentCaptureReceiverEnabled(
+                @UserIdInt int userId, @NonNull String packageName) {
+            return super.isWhitelisted(userId, packageName);
+        }
+
+        private boolean isContentProtectionReceiverEnabled(@NonNull String packageName) {
+            if (mContentProtectionServiceComponentName == null
+                    || mContentProtectionBlocklistManager == null) {
+                return false;
+            }
+            synchronized (mLock) {
+                if (!mDevCfgEnableContentProtectionReceiver) {
+                    return false;
+                }
+            }
+            return mContentProtectionBlocklistManager.isAllowed(packageName);
+        }
     }
 
     private static class DataShareCallbackDelegate extends IDataShareCallback.Stub {
diff --git a/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionBlocklistManager.java b/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionBlocklistManager.java
new file mode 100644
index 0000000..a0fd28b
--- /dev/null
+++ b/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionBlocklistManager.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.contentprotection;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.pm.PackageInfo;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * Manages whether the content protection is enabled for an app using a blocklist.
+ *
+ * @hide
+ */
+public class ContentProtectionBlocklistManager {
+
+    private static final String TAG = "ContentProtectionBlocklistManager";
+
+    private static final String PACKAGE_NAME_BLOCKLIST_FILENAME =
+            "/product/etc/res/raw/content_protection/package_name_blocklist.txt";
+
+    @NonNull private final ContentProtectionPackageManager mContentProtectionPackageManager;
+
+    @Nullable private Set<String> mPackageNameBlocklist;
+
+    public ContentProtectionBlocklistManager(
+            @NonNull ContentProtectionPackageManager contentProtectionPackageManager) {
+        mContentProtectionPackageManager = contentProtectionPackageManager;
+    }
+
+    public boolean isAllowed(@NonNull String packageName) {
+        if (mPackageNameBlocklist == null) {
+            // List not loaded or failed to load, don't run on anything
+            return false;
+        }
+        if (mPackageNameBlocklist.contains(packageName)) {
+            return false;
+        }
+        PackageInfo packageInfo = mContentProtectionPackageManager.getPackageInfo(packageName);
+        if (packageInfo == null) {
+            return false;
+        }
+        if (!mContentProtectionPackageManager.hasRequestedInternetPermissions(packageInfo)) {
+            return false;
+        }
+        if (mContentProtectionPackageManager.isSystemApp(packageInfo)) {
+            return false;
+        }
+        if (mContentProtectionPackageManager.isUpdatedSystemApp(packageInfo)) {
+            return false;
+        }
+        return true;
+    }
+
+    public void updateBlocklist(int blocklistSize) {
+        Slog.i(TAG, "Blocklist size updating to: " + blocklistSize);
+        mPackageNameBlocklist = readPackageNameBlocklist(blocklistSize);
+    }
+
+    @Nullable
+    private Set<String> readPackageNameBlocklist(int blocklistSize) {
+        if (blocklistSize <= 0) {
+            // Explicitly requested an empty blocklist
+            return Collections.emptySet();
+        }
+        List<String> lines = readLinesFromRawFile(PACKAGE_NAME_BLOCKLIST_FILENAME);
+        if (lines == null) {
+            return null;
+        }
+        return lines.stream().limit(blocklistSize).collect(Collectors.toSet());
+    }
+
+    @VisibleForTesting
+    @Nullable
+    protected List<String> readLinesFromRawFile(@NonNull String filename) {
+        try (FileReader fileReader = new FileReader(filename);
+                BufferedReader bufferedReader = new BufferedReader(fileReader)) {
+            return bufferedReader
+                    .lines()
+                    .map(line -> line.trim())
+                    .filter(line -> !line.isBlank())
+                    .collect(Collectors.toList());
+        } catch (Exception ex) {
+            Slog.e(TAG, "Failed to read: " + filename, ex);
+            return null;
+        }
+    }
+}
diff --git a/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionPackageManager.java b/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionPackageManager.java
new file mode 100644
index 0000000..4ebac07
--- /dev/null
+++ b/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionPackageManager.java
@@ -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.server.contentprotection;
+
+import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.PackageManager.PackageInfoFlags;
+import android.util.Slog;
+
+import java.util.Arrays;
+
+/**
+ * Basic package manager for content protection using content capture.
+ *
+ * @hide
+ */
+public class ContentProtectionPackageManager {
+
+    private static final String TAG = "ContentProtectionPackageManager";
+
+    private static final PackageInfoFlags PACKAGE_INFO_FLAGS =
+            PackageInfoFlags.of(PackageManager.GET_PERMISSIONS);
+
+    @NonNull private final PackageManager mPackageManager;
+
+    public ContentProtectionPackageManager(@NonNull Context context) {
+        mPackageManager = context.getPackageManager();
+    }
+
+    @Nullable
+    public PackageInfo getPackageInfo(@NonNull String packageName) {
+        try {
+            return mPackageManager.getPackageInfo(packageName, PACKAGE_INFO_FLAGS);
+        } catch (NameNotFoundException ex) {
+            Slog.w(TAG, "Package info not found for: " + packageName, ex);
+            return null;
+        }
+    }
+
+    public boolean isSystemApp(@NonNull PackageInfo packageInfo) {
+        return packageInfo.applicationInfo != null && isSystemApp(packageInfo.applicationInfo);
+    }
+
+    private boolean isSystemApp(@NonNull ApplicationInfo applicationInfo) {
+        return (applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
+    }
+
+    public boolean isUpdatedSystemApp(@NonNull PackageInfo packageInfo) {
+        return packageInfo.applicationInfo != null
+                && isUpdatedSystemApp(packageInfo.applicationInfo);
+    }
+
+    private boolean isUpdatedSystemApp(@NonNull ApplicationInfo applicationInfo) {
+        return (applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0;
+    }
+
+    public boolean hasRequestedInternetPermissions(@NonNull PackageInfo packageInfo) {
+        return packageInfo.requestedPermissions != null
+                && Arrays.asList(packageInfo.requestedPermissions)
+                        .contains(Manifest.permission.INTERNET);
+    }
+}
diff --git a/services/contentcapture/java/com/android/server/contentprotection/RemoteContentProtectionService.java b/services/contentcapture/java/com/android/server/contentprotection/RemoteContentProtectionService.java
new file mode 100644
index 0000000..dd5545d
--- /dev/null
+++ b/services/contentcapture/java/com/android/server/contentprotection/RemoteContentProtectionService.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.contentprotection;
+
+import android.annotation.NonNull;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ParceledListSlice;
+import android.service.contentcapture.ContentCaptureService;
+import android.service.contentcapture.IContentProtectionService;
+import android.util.Slog;
+import android.view.contentcapture.ContentCaptureEvent;
+
+import com.android.internal.infra.ServiceConnector;
+
+import java.time.Duration;
+
+/**
+ * Connector for the remote content protection service.
+ *
+ * @hide
+ */
+public class RemoteContentProtectionService
+        extends ServiceConnector.Impl<IContentProtectionService> {
+
+    private static final String TAG = RemoteContentProtectionService.class.getSimpleName();
+
+    private static final Duration AUTO_DISCONNECT_TIMEOUT = Duration.ofSeconds(3);
+
+    @NonNull private final ComponentName mComponentName;
+
+    public RemoteContentProtectionService(
+            @NonNull Context context,
+            @NonNull ComponentName componentName,
+            int userId,
+            boolean bindAllowInstant) {
+        super(
+                context,
+                new Intent(ContentCaptureService.PROTECTION_SERVICE_INTERFACE)
+                        .setComponent(componentName),
+                bindAllowInstant ? Context.BIND_ALLOW_INSTANT : 0,
+                userId,
+                IContentProtectionService.Stub::asInterface);
+        mComponentName = componentName;
+    }
+
+    @Override // from ServiceConnector.Impl
+    protected long getAutoDisconnectTimeoutMs() {
+        return AUTO_DISCONNECT_TIMEOUT.toMillis();
+    }
+
+    @Override // from ServiceConnector.Impl
+    protected void onServiceConnectionStatusChanged(
+            @NonNull IContentProtectionService service, boolean isConnected) {
+        Slog.i(
+                TAG,
+                "Connection status for: "
+                        + mComponentName
+                        + " changed to: "
+                        + (isConnected ? "connected" : "disconnected"));
+    }
+
+    public void onLoginDetected(@NonNull ParceledListSlice<ContentCaptureEvent> events) {
+        run(service -> service.onLoginDetected(events));
+    }
+}
diff --git a/services/core/Android.bp b/services/core/Android.bp
index f8d19ec..459e24d 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -74,8 +74,8 @@
         "{ ! (diff $(out) $(location :services.core.protolog.json) | grep -q '^<') || " +
         "{ echo -e '\\n\\n################################################################\\n#\\n" +
         "#  ERROR: ProtoLog viewer config is stale.  To update it, run:\\n#\\n" +
-        "#  cp $(location :generate-protolog.json) " +
-        "$(location :services.core.protolog.json)\\n#\\n" +
+        "#  cp $${ANDROID_BUILD_TOP}/$(location :generate-protolog.json) " +
+        "$${ANDROID_BUILD_TOP}/$(location :services.core.protolog.json)\\n#\\n" +
         "################################################################\\n\\n' >&2 && false; } }",
     out: ["services.core.protolog.json"],
 }
@@ -186,6 +186,7 @@
         "com.android.sysprop.watchdog",
         "ImmutabilityAnnotation",
         "securebox",
+        "apache-commons-math",
     ],
     javac_shard_size: 50,
     javacflags: [
@@ -202,9 +203,9 @@
     srcs: [":services.core.unboosted"],
     tools: ["lockedregioncodeinjection"],
     cmd: "$(location lockedregioncodeinjection) " +
-        "  --targets \"Lcom/android/server/am/ActivityManagerService;,Lcom/android/server/am/ActivityManagerGlobalLock;,Lcom/android/server/wm/WindowManagerGlobalLock;\" " +
-        "  --pre \"com/android/server/am/ActivityManagerService.boostPriorityForLockedSection,com/android/server/am/ActivityManagerService.boostPriorityForProcLockedSection,com/android/server/wm/WindowManagerService.boostPriorityForLockedSection\" " +
-        "  --post \"com/android/server/am/ActivityManagerService.resetPriorityAfterLockedSection,com/android/server/am/ActivityManagerService.resetPriorityAfterProcLockedSection,com/android/server/wm/WindowManagerService.resetPriorityAfterLockedSection\" " +
+        "  --targets \"Lcom/android/server/am/ActivityManagerService;,Lcom/android/server/am/ActivityManagerGlobalLock;,Lcom/android/server/wm/WindowManagerGlobalLock;,Lcom/android/server/pm/PackageManagerTracedLock;\" " +
+        "  --pre \"com/android/server/am/ActivityManagerService.boostPriorityForLockedSection,com/android/server/am/ActivityManagerService.boostPriorityForProcLockedSection,com/android/server/wm/WindowManagerService.boostPriorityForLockedSection,com/android/server/pm/PackageManagerService.boostPriorityForPackageManagerTracedLockedSection\" " +
+        "  --post \"com/android/server/am/ActivityManagerService.resetPriorityAfterLockedSection,com/android/server/am/ActivityManagerService.resetPriorityAfterProcLockedSection,com/android/server/wm/WindowManagerService.resetPriorityAfterLockedSection,com/android/server/pm/PackageManagerService.resetPriorityAfterPackageManagerTracedLockedSection\" " +
         "  -o $(out) " +
         "  -i $(in)",
     out: ["services.core.priorityboosted.jar"],
@@ -229,8 +230,7 @@
     name: "services.core.json.gz",
     srcs: [":checked-protolog.json"],
     out: ["services.core.protolog.json.gz"],
-    cmd: "$(location minigzip) -c < $(in) > $(out)",
-    tools: ["minigzip"],
+    cmd: "gzip -c < $(in) > $(out)",
 }
 
 prebuilt_etc {
diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java
index a305ed3..846283c 100644
--- a/services/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -44,11 +44,13 @@
 import android.util.SparseArray;
 
 import com.android.internal.util.function.pooled.PooledLambda;
+import com.android.permission.persistence.RuntimePermissionsState;
 import com.android.server.pm.Installer.LegacyDexoptDisabledException;
 import com.android.server.pm.KnownPackages;
 import com.android.server.pm.PackageList;
 import com.android.server.pm.PackageSetting;
 import com.android.server.pm.dex.DynamicCodeLogger;
+import com.android.server.pm.permission.LegacyPermissionSettings;
 import com.android.server.pm.pkg.AndroidPackage;
 import com.android.server.pm.pkg.PackageStateInternal;
 import com.android.server.pm.pkg.SharedUserApi;
@@ -1083,6 +1085,23 @@
     public abstract void writePermissionSettings(@NonNull @UserIdInt int[] userIds, boolean async);
 
     /**
+     * Read legacy permission definitions for permissions migration to new permission subsystem.
+     * Note that this api is supposed to be used for permissions migration only.
+     */
+    public abstract LegacyPermissionSettings getLegacyPermissions();
+
+    /**
+     * Read legacy permission states for permissions migration to new permission subsystem.
+     * Note that this api is supposed to be used for permissions state migration only.
+     */
+    public abstract RuntimePermissionsState getLegacyPermissionsState(@UserIdInt int userId);
+
+    /**
+     * @return permissions file version for the given user.
+     */
+    public abstract int getLegacyPermissionsVersion(@UserIdInt int userId);
+
+    /**
      * Returns {@code true} if the caller is the installer of record for the given package.
      * Otherwise, {@code false}.
      */
@@ -1364,4 +1383,17 @@
     @Deprecated
     public abstract void legacyReconcileSecondaryDexFiles(String packageName)
             throws LegacyDexoptDisabledException;
+
+    /**
+     * Gets {@link PackageManager.DistractionRestriction restrictions} of the given
+     * packages of the given user.
+     *
+     * The corresponding element of the resulting array will be -1 if a given package doesn't exist.
+     *
+     * @param packageNames The packages under which to check.
+     * @param userId The user under which to check.
+     * @return an array of distracting restriction state in order of the given packages
+     */
+    public abstract int[] getDistractingPackageRestrictionsAsUser(
+            @NonNull String[] packageNames, int userId);
 }
diff --git a/services/core/java/com/android/server/BootReceiver.java b/services/core/java/com/android/server/BootReceiver.java
index 572e9c2..26421b7 100644
--- a/services/core/java/com/android/server/BootReceiver.java
+++ b/services/core/java/com/android/server/BootReceiver.java
@@ -313,6 +313,11 @@
 
     private static final DropboxRateLimiter sDropboxRateLimiter = new DropboxRateLimiter();
 
+    /** Initialize the rate limiter. */
+    public static void initDropboxRateLimiter() {
+        sDropboxRateLimiter.init();
+    }
+
     /**
      * Reset the dropbox rate limiter.
      */
@@ -362,10 +367,8 @@
                             PosixFilePermissions.fromString("rw-rw----"));
 
                     // Write the new proto container proto with headers.
-                    ParcelFileDescriptor pfd;
-                    try {
-                        pfd = ParcelFileDescriptor.open(tombstoneProtoWithHeaders, MODE_READ_WRITE);
-
+                    try (ParcelFileDescriptor pfd = ParcelFileDescriptor.open(
+                            tombstoneProtoWithHeaders, MODE_READ_WRITE)) {
                         ProtoOutputStream protoStream = new ProtoOutputStream(
                                 pfd.getFileDescriptor());
                         protoStream.write(TombstoneWithHeadersProto.TOMBSTONE, tombstoneBytes);
@@ -379,6 +382,8 @@
                     } catch (FileNotFoundException ex) {
                         Slog.e(TAG, "failed to open for write: " + tombstoneProtoWithHeaders, ex);
                         throw ex;
+                    } catch (IOException ex) {
+                        Slog.e(TAG, "IO exception during write: " + tombstoneProtoWithHeaders, ex);
                     } finally {
                         // Remove the temporary file.
                         if (tombstoneProtoWithHeaders != null) {
diff --git a/services/core/java/com/android/server/DockObserver.java b/services/core/java/com/android/server/DockObserver.java
index fb527c1..9554e63 100644
--- a/services/core/java/com/android/server/DockObserver.java
+++ b/services/core/java/com/android/server/DockObserver.java
@@ -20,9 +20,8 @@
 import android.content.Context;
 import android.content.Intent;
 import android.database.ContentObserver;
-import android.media.AudioManager;
+import android.media.AudioAttributes;
 import android.media.Ringtone;
-import android.media.RingtoneManager;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Handler;
@@ -306,11 +305,16 @@
                     if (soundPath != null) {
                         final Uri soundUri = Uri.parse("file://" + soundPath);
                         if (soundUri != null) {
-                            final Ringtone sfx = RingtoneManager.getRingtone(
-                                    getContext(), soundUri);
+                            AudioAttributes audioAttributes = new AudioAttributes.Builder()
+                                    .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
+                                    .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
+                                    .build();
+                            final Ringtone sfx = new Ringtone.Builder(getContext(),
+                                    Ringtone.MEDIA_SOUND, audioAttributes)
+                                    .setUri(soundUri)
+                                    .setPreferBuiltinDevice()
+                                    .build();
                             if (sfx != null) {
-                                sfx.setStreamType(AudioManager.STREAM_SYSTEM);
-                                sfx.preferBuiltinDevice(true);
                                 sfx.play();
                             }
                         }
diff --git a/services/core/java/com/android/server/DynamicSystemService.java b/services/core/java/com/android/server/DynamicSystemService.java
index 27215b2..cbacee6 100644
--- a/services/core/java/com/android/server/DynamicSystemService.java
+++ b/services/core/java/com/android/server/DynamicSystemService.java
@@ -44,7 +44,7 @@
     private static final String TAG = "DynamicSystemService";
     private static final long MINIMUM_SD_MB = (30L << 10);
     private static final int GSID_ROUGH_TIMEOUT_MS = 8192;
-    private static final String PATH_DEFAULT = "/data/gsi/";
+    private static final String PATH_DEFAULT = "/data/gsi/dsu/";
     private Context mContext;
     private String mInstallPath, mDsuSlot;
     private volatile IGsiService mGsiService;
@@ -226,9 +226,7 @@
         IGsiService gsiService = getGsiService();
         if (enable) {
             try {
-                if (mDsuSlot == null) {
-                    mDsuSlot = gsiService.getActiveDsuSlot();
-                }
+                getActiveDsuSlot();
                 GsiServiceCallback callback = new GsiServiceCallback();
                 synchronized (callback) {
                     gsiService.enableGsiAsync(oneShot, mDsuSlot, callback);
@@ -287,4 +285,15 @@
 
         return getGsiService().suggestScratchSize();
     }
+
+    @Override
+    @EnforcePermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
+    public String getActiveDsuSlot() throws RemoteException {
+        super.getActiveDsuSlot_enforcePermission();
+
+        if (mDsuSlot == null) {
+            mDsuSlot = getGsiService().getActiveDsuSlot();
+        }
+        return mDsuSlot;
+    }
 }
diff --git a/services/core/java/com/android/server/MasterClearReceiver.java b/services/core/java/com/android/server/MasterClearReceiver.java
index 7c3a75f..5a15f17 100644
--- a/services/core/java/com/android/server/MasterClearReceiver.java
+++ b/services/core/java/com/android/server/MasterClearReceiver.java
@@ -49,6 +49,7 @@
     private static final String TAG = "MasterClear";
     private boolean mWipeExternalStorage;
     private boolean mWipeEsims;
+    private boolean mShowWipeProgress = true;
 
     @Override
     public void onReceive(final Context context, final Intent intent) {
@@ -77,8 +78,12 @@
             return;
         }
 
-        final boolean shutdown = intent.getBooleanExtra("shutdown", false);
         final String reason = intent.getStringExtra(Intent.EXTRA_REASON);
+
+        // Factory reset dialog has its own UI for the reset process, so suppress ours if indicated.
+        mShowWipeProgress = intent.getBooleanExtra(Intent.EXTRA_SHOW_WIPE_PROGRESS, true);
+
+        final boolean shutdown = intent.getBooleanExtra("shutdown", false);
         mWipeExternalStorage = intent.getBooleanExtra(Intent.EXTRA_WIPE_EXTERNAL_STORAGE, false);
         mWipeEsims = intent.getBooleanExtra(Intent.EXTRA_WIPE_ESIMS, false);
         final boolean forceWipe = intent.getBooleanExtra(Intent.EXTRA_FORCE_MASTER_CLEAR, false)
@@ -190,15 +195,19 @@
         public WipeDataTask(Context context, Thread chainedTask) {
             mContext = context;
             mChainedTask = chainedTask;
-            mProgressDialog = new ProgressDialog(context, R.style.Theme_DeviceDefault_System);
+            mProgressDialog = mShowWipeProgress
+                    ? new ProgressDialog(context, R.style.Theme_DeviceDefault_System)
+                    : null;
         }
 
         @Override
         protected void onPreExecute() {
-            mProgressDialog.setIndeterminate(true);
-            mProgressDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
-            mProgressDialog.setMessage(mContext.getText(R.string.progress_erasing));
-            mProgressDialog.show();
+            if (mProgressDialog != null) {
+                mProgressDialog.setIndeterminate(true);
+                mProgressDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
+                mProgressDialog.setMessage(mContext.getText(R.string.progress_erasing));
+                mProgressDialog.show();
+            }
         }
 
         @Override
@@ -214,7 +223,9 @@
 
         @Override
         protected void onPostExecute(Void result) {
-            mProgressDialog.dismiss();
+            if (mProgressDialog != null && mProgressDialog.isShowing()) {
+                mProgressDialog.dismiss();
+            }
             mChainedTask.start();
         }
 
diff --git a/services/core/java/com/android/server/OWNERS b/services/core/java/com/android/server/OWNERS
index 123cd328..55130e4 100644
--- a/services/core/java/com/android/server/OWNERS
+++ b/services/core/java/com/android/server/OWNERS
@@ -1,3 +1,6 @@
+# BootReceiver
+per-file BootReceiver.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
 
diff --git a/services/core/java/com/android/server/PendingIntentUtils.java b/services/core/java/com/android/server/PendingIntentUtils.java
index 1600101..a72a4d25 100644
--- a/services/core/java/com/android/server/PendingIntentUtils.java
+++ b/services/core/java/com/android/server/PendingIntentUtils.java
@@ -34,6 +34,7 @@
     public static Bundle createDontSendToRestrictedAppsBundle(@Nullable Bundle bundle) {
         final BroadcastOptions options = BroadcastOptions.makeBasic();
         options.setDontSendToRestrictedApps(true);
+        options.setPendingIntentBackgroundActivityLaunchAllowed(false);
         if (bundle == null) {
             return options.toBundle();
         }
diff --git a/services/core/java/com/android/server/RescueParty.java b/services/core/java/com/android/server/RescueParty.java
index 6e2e5f7..c094c12 100644
--- a/services/core/java/com/android/server/RescueParty.java
+++ b/services/core/java/com/android/server/RescueParty.java
@@ -382,6 +382,14 @@
 
     private static void executeRescueLevelInternal(Context context, int level, @Nullable
             String failedPackage) throws Exception {
+
+        if (level <= LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS) {
+            // Disabling flag resets on master branch for trunk stable launch.
+            // TODO(b/287618292): Re-enable them after the trunk stable is launched and we
+            // figured out a way to reset flags without interfering with trunk development.
+            return;
+        }
+
         FrameworkStatsLog.write(FrameworkStatsLog.RESCUE_PARTY_RESET_REPORTED, level);
         // Try our best to reset all settings possible, and once finished
         // rethrow any exception that we encountered
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 73dbb86a..830d55a 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -1269,6 +1269,21 @@
         }
     }
 
+    /**
+     * This method checks if the volume is public and the volume is visible and the volume it is
+     * trying to mount doesn't have the same mount user id as the current user being maintained by
+     * StorageManagerService and change the mount Id. The checks are same as
+     * {@link StorageManagerService#maybeRemountVolumes(int)}
+     * @param VolumeInfo object to consider for changing the mountId
+     */
+    private void updateVolumeMountIdIfRequired(VolumeInfo vol) {
+        synchronized (mLock) {
+            if (!vol.isPrimary() && vol.isVisible() && vol.getMountUserId() != mCurrentUserId) {
+                vol.mountUserId = mCurrentUserId;
+            }
+        }
+    }
+
     private boolean supportsBlockCheckpoint() throws RemoteException {
         enforcePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS);
         return mVold.supportsBlockCheckpoint();
@@ -1382,13 +1397,14 @@
         }
 
         @Override
-        public void onVolumeStateChanged(String volId, final int newState) {
+        public void onVolumeStateChanged(String volId, final int newState, final int userId) {
             synchronized (mLock) {
                 final VolumeInfo vol = mVolumes.get(volId);
                 if (vol != null) {
                     final int oldState = vol.state;
                     vol.state = newState;
                     final VolumeInfo vInfo = new VolumeInfo(vol);
+                    vInfo.mountUserId = userId;
                     final SomeArgs args = SomeArgs.obtain();
                     args.arg1 = vInfo;
                     args.argi1 = oldState;
@@ -2069,7 +2085,7 @@
                 }
             };
             // TODO(b/149391976): Use different handler?
-            monitor.register(mContext, user, true, mHandler);
+            monitor.register(mContext, user, mHandler);
             mPackageMonitorsForUser.put(userId, monitor);
         } else {
             Slog.w(TAG, "PackageMonitor is already registered for: " + userId);
@@ -2232,7 +2248,7 @@
         if (isMountDisallowed(vol)) {
             throw new SecurityException("Mounting " + volId + " restricted by policy");
         }
-
+        updateVolumeMountIdIfRequired(vol);
         mount(vol);
     }
 
diff --git a/services/core/java/com/android/server/SystemConfig.java b/services/core/java/com/android/server/SystemConfig.java
index 7fae31c..caf1684 100644
--- a/services/core/java/com/android/server/SystemConfig.java
+++ b/services/core/java/com/android/server/SystemConfig.java
@@ -35,7 +35,6 @@
 import android.os.incremental.IncrementalManager;
 import android.os.storage.StorageManager;
 import android.permission.PermissionManager.SplitPermissionInfo;
-import android.sysprop.ApexProperties;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -1208,8 +1207,7 @@
                             boolean systemExt = permFile.toPath().startsWith(
                                     Environment.getSystemExtDirectory().toPath() + "/");
                             boolean apex = permFile.toPath().startsWith(
-                                    Environment.getApexDirectory().toPath() + "/")
-                                    && ApexProperties.updatable().orElse(false);
+                                    Environment.getApexDirectory().toPath() + "/");
                             if (vendor) {
                                 readPrivAppPermissions(parser,
                                         mPermissionAllowlist.getVendorPrivilegedAppAllowlist());
@@ -1827,6 +1825,9 @@
                         soname, soname, new String[0], true);
                 mSharedLibraries.put(entry.name, entry);
             }
+        } catch (FileNotFoundException e) {
+            // Expected for /vendor/etc/public.libraries.txt on some devices
+            Slog.d(TAG, listFile + " does not exist");
         } catch (IOException e) {
             Slog.w(TAG, "Failed to read public libraries file " + listFile, e);
         }
diff --git a/services/core/java/com/android/server/SystemUpdateManagerService.java b/services/core/java/com/android/server/SystemUpdateManagerService.java
index 811a780..d5e7be5 100644
--- a/services/core/java/com/android/server/SystemUpdateManagerService.java
+++ b/services/core/java/com/android/server/SystemUpdateManagerService.java
@@ -86,9 +86,10 @@
         }
     }
 
+    @android.annotation.EnforcePermission(android.Manifest.permission.RECOVERY)
     @Override
     public void updateSystemUpdateInfo(PersistableBundle infoBundle) {
-        mContext.enforceCallingOrSelfPermission(Manifest.permission.RECOVERY, TAG);
+        updateSystemUpdateInfo_enforcePermission();
 
         int status = infoBundle.getInt(KEY_STATUS, STATUS_UNKNOWN);
         if (status == STATUS_UNKNOWN) {
diff --git a/services/core/java/com/android/server/TEST_MAPPING b/services/core/java/com/android/server/TEST_MAPPING
index ae095b5..1e3a5f4 100644
--- a/services/core/java/com/android/server/TEST_MAPPING
+++ b/services/core/java/com/android/server/TEST_MAPPING
@@ -54,28 +54,20 @@
         },
         {
             "name": "BinaryTransparencyHostTest",
-            "file_patterns": [
-                "BinaryTransparencyService\\.java"
-            ]
-        },
-        {
-            "name": "CtsMediaProjectionTestCases",
-            "options": [
-                {
-                    "exclude-annotation": "android.platform.test.annotations.FlakyTest"
-                },
-                {
-                    "exclude-annotation": "androidx.test.filters.FlakyTest"
-                },
-                {
-                    "exclude-annotation": "org.junit.Ignore"
-                }
-            ]
+            "file_patterns": ["BinaryTransparencyService\\.java"]
         },
         {
             // GWP-ASan's CTS test ensures that recoverable tombstones work,
             // which is emitted by the NativeTombstoneManager.
             "name": "CtsGwpAsanTestCases"
+        },
+        {
+            "name": "FrameworksVcnTests",
+            "file_patterns": ["VcnManagementService\\.java"]
+        },
+        {
+            "name": "CtsVcnTestCases",
+            "file_patterns": ["VcnManagementService\\.java"]
         }
     ],
     "presubmit-large": [
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index a641e85..c718d39 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -2093,20 +2093,24 @@
         }
     }
 
-    public void notifyDataActivity(int state) {
-        notifyDataActivityForSubscriber(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, state);
-    }
-
-    public void notifyDataActivityForSubscriber(int subId, int state) {
+    /**
+     * 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()" )) {
             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)) {
diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java
index 55e805a..dc0f901 100644
--- a/services/core/java/com/android/server/UiModeManagerService.java
+++ b/services/core/java/com/android/server/UiModeManagerService.java
@@ -65,6 +65,7 @@
 import android.os.Binder;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.PermissionEnforcer;
 import android.os.PowerManager;
 import android.os.PowerManager.ServiceType;
 import android.os.PowerManagerInternal;
@@ -199,6 +200,8 @@
     private final LocalService mLocalService = new LocalService();
     private PowerManagerInternal mLocalPowerManager;
 
+    private final IUiModeManager.Stub mService;
+
     @GuardedBy("mLock")
     private final SparseArray<RemoteCallbackList<IUiModeManagerCallback>> mUiModeManagerCallbacks =
             new SparseArray<>();
@@ -221,6 +224,7 @@
     protected UiModeManagerService(Context context, boolean setupWizardComplete,
             TwilightManager tm, Injector injector) {
         super(context);
+        mService = new Stub(context);
         mConfiguration.setToDefaults();
         mSetupWizardComplete = setupWizardComplete;
         mTwilightManager = tm;
@@ -663,7 +667,11 @@
         }
     }
 
-    private final IUiModeManager.Stub mService = new IUiModeManager.Stub() {
+    private final class Stub extends IUiModeManager.Stub {
+        Stub(Context context) {
+            super(PermissionEnforcer.fromContext(context));
+        }
+
         @Override
         public void addCallback(IUiModeManagerCallback callback) {
             int userId = getCallingUserId();
@@ -851,25 +859,17 @@
             }
         }
 
+        @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE)
         @Override
         public void setNightModeCustomType(@NightModeCustomType int nightModeCustomType) {
-            if (getContext().checkCallingOrSelfPermission(
-                    android.Manifest.permission.MODIFY_DAY_NIGHT_MODE)
-                    != PackageManager.PERMISSION_GRANTED) {
-                throw new SecurityException(
-                        "setNightModeCustomType requires MODIFY_DAY_NIGHT_MODE permission");
-            }
+            setNightModeCustomType_enforcePermission();
             setNightModeInternal(MODE_NIGHT_CUSTOM, nightModeCustomType);
         }
 
+        @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE)
         @Override
         public  @NightModeCustomReturnType int getNightModeCustomType() {
-            if (getContext().checkCallingOrSelfPermission(
-                    android.Manifest.permission.MODIFY_DAY_NIGHT_MODE)
-                    != PackageManager.PERMISSION_GRANTED) {
-                throw new SecurityException(
-                        "getNightModeCustomType requires MODIFY_DAY_NIGHT_MODE permission");
-            }
+            getNightModeCustomType_enforcePermission();
             synchronized (mLock) {
                 return mNightModeCustomType;
             }
@@ -1098,10 +1098,10 @@
             return releaseProjectionUnchecked(projectionType, callingPackage);
         }
 
+        @android.annotation.EnforcePermission(android.Manifest.permission.READ_PROJECTION_STATE)
         @Override
         public @UiModeManager.ProjectionType int getActiveProjectionTypes() {
-            getContext().enforceCallingOrSelfPermission(
-                    android.Manifest.permission.READ_PROJECTION_STATE, "getActiveProjectionTypes");
+            getActiveProjectionTypes_enforcePermission();
             @UiModeManager.ProjectionType int projectionTypeFlag = PROJECTION_TYPE_NONE;
             synchronized (mLock) {
                 if (mProjectionHolders != null) {
@@ -1115,11 +1115,11 @@
             return projectionTypeFlag;
         }
 
+        @android.annotation.EnforcePermission(android.Manifest.permission.READ_PROJECTION_STATE)
         @Override
         public List<String> getProjectingPackages(
                 @UiModeManager.ProjectionType int projectionType) {
-            getContext().enforceCallingOrSelfPermission(
-                    android.Manifest.permission.READ_PROJECTION_STATE, "getProjectionState");
+            getProjectingPackages_enforcePermission();
             synchronized (mLock) {
                 List<String> packageNames = new ArrayList<>();
                 populateWithRelevantActivePackageNames(projectionType, packageNames);
@@ -1127,11 +1127,10 @@
             }
         }
 
+        @android.annotation.EnforcePermission(android.Manifest.permission.READ_PROJECTION_STATE)
         public void addOnProjectionStateChangedListener(IOnProjectionStateChangedListener listener,
                 @UiModeManager.ProjectionType int projectionType) {
-            getContext().enforceCallingOrSelfPermission(
-                    android.Manifest.permission.READ_PROJECTION_STATE,
-                    "addOnProjectionStateChangedListener");
+            addOnProjectionStateChangedListener_enforcePermission();
             if (projectionType == PROJECTION_TYPE_NONE) {
                 return;
             }
@@ -1161,11 +1160,10 @@
         }
 
 
+        @android.annotation.EnforcePermission(android.Manifest.permission.READ_PROJECTION_STATE)
         public void removeOnProjectionStateChangedListener(
                 IOnProjectionStateChangedListener listener) {
-            getContext().enforceCallingOrSelfPermission(
-                    android.Manifest.permission.READ_PROJECTION_STATE,
-                    "removeOnProjectionStateChangedListener");
+            removeOnProjectionStateChangedListener_enforcePermission();
             synchronized (mLock) {
                 if (mProjectionListeners != null) {
                     for (int i = 0; i < mProjectionListeners.size(); ++i) {
diff --git a/services/core/java/com/android/server/WallpaperUpdateReceiver.java b/services/core/java/com/android/server/WallpaperUpdateReceiver.java
index 9917892..2812233 100644
--- a/services/core/java/com/android/server/WallpaperUpdateReceiver.java
+++ b/services/core/java/com/android/server/WallpaperUpdateReceiver.java
@@ -88,7 +88,7 @@
         } else {
             //live wallpaper
             ComponentName currCN = info.getComponent();
-            ComponentName defaultCN = WallpaperManager.getDefaultWallpaperComponent(context);
+            ComponentName defaultCN = WallpaperManager.getCmfDefaultWallpaperComponent(context);
             if (!currCN.equals(defaultCN)) {
                 return true;
             }
diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java
index 62651dd..6baae4b 100644
--- a/services/core/java/com/android/server/Watchdog.java
+++ b/services/core/java/com/android/server/Watchdog.java
@@ -142,6 +142,7 @@
             "android.hardware.camera.provider@2.4::ICameraProvider",
             "android.hardware.gnss@1.0::IGnss",
             "android.hardware.graphics.allocator@2.0::IAllocator",
+            "android.hardware.graphics.allocator@4.0::IAllocator",
             "android.hardware.graphics.composer@2.1::IComposer",
             "android.hardware.health@2.0::IHealth",
             "android.hardware.light@2.0::ILight",
@@ -452,7 +453,7 @@
         mInterestingJavaPids.add(Process.myPid());
 
         // See the notes on DEFAULT_TIMEOUT.
-        assert DB ||
+        assert DB || Build.IS_USERDEBUG ||
                 DEFAULT_TIMEOUT > ZygoteConnectionConstants.WRAPPED_PID_TIMEOUT_MILLIS;
 
         mTraceErrorLogger = new TraceErrorLogger();
@@ -526,7 +527,8 @@
      */
     void updateWatchdogTimeout(long timeoutMillis) {
         // See the notes on DEFAULT_TIMEOUT.
-        if (!DB && timeoutMillis <= ZygoteConnectionConstants.WRAPPED_PID_TIMEOUT_MILLIS) {
+        if (!DB && !Build.IS_USERDEBUG
+                && timeoutMillis <= ZygoteConnectionConstants.WRAPPED_PID_TIMEOUT_MILLIS) {
             timeoutMillis = ZygoteConnectionConstants.WRAPPED_PID_TIMEOUT_MILLIS + 1;
         }
         mWatchdogTimeoutMillis = timeoutMillis;
@@ -914,7 +916,7 @@
         // The system's been hanging for a whlie, another second or two won't hurt much.
         SystemClock.sleep(5000);
         processCpuTracker.update();
-        report.append(processCpuTracker.printCurrentState(anrTime));
+        report.append(processCpuTracker.printCurrentState(anrTime, 10));
         report.append(tracesFileException.getBuffer());
 
         if (!halfWatchdog) {
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index c30b522..146215b 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -71,6 +71,7 @@
 import android.content.pm.SigningDetails.CertCapabilities;
 import android.content.pm.UserInfo;
 import android.database.Cursor;
+import android.database.sqlite.SQLiteCantOpenDatabaseException;
 import android.database.sqlite.SQLiteFullException;
 import android.database.sqlite.SQLiteStatement;
 import android.os.Binder;
@@ -356,13 +357,25 @@
             @Override
             public void onPackageAdded(String packageName, int uid) {
                 // Called on a handler, and running as the system
-                cancelAccountAccessRequestNotificationIfNeeded(uid, true);
+                try {
+                    UserAccounts accounts = getUserAccounts(UserHandle.getUserId(uid));
+                    cancelAccountAccessRequestNotificationIfNeeded(uid, true, accounts);
+                } catch (SQLiteCantOpenDatabaseException e) {
+                    Log.w(TAG, "Can't read accounts database", e);
+                    return;
+                }
             }
 
             @Override
             public void onPackageUpdateFinished(String packageName, int uid) {
                 // Called on a handler, and running as the system
-                cancelAccountAccessRequestNotificationIfNeeded(uid, true);
+                try {
+                    UserAccounts accounts = getUserAccounts(UserHandle.getUserId(uid));
+                    cancelAccountAccessRequestNotificationIfNeeded(uid, true, accounts);
+                } catch (SQLiteCantOpenDatabaseException e) {
+                    Log.w(TAG, "Can't read accounts database", e);
+                    return;
+                }
             }
         }.register(mContext, mHandler.getLooper(), UserHandle.ALL, true);
 
@@ -379,13 +392,18 @@
                     if (mode == AppOpsManager.MODE_ALLOWED) {
                         final long identity = Binder.clearCallingIdentity();
                         try {
-                            cancelAccountAccessRequestNotificationIfNeeded(packageName, uid, true);
+                            UserAccounts accounts = getUserAccounts(userId);
+                            cancelAccountAccessRequestNotificationIfNeeded(
+                                    packageName, uid, true, accounts);
                         } finally {
                             Binder.restoreCallingIdentity(identity);
                         }
                     }
                 } catch (NameNotFoundException e) {
                     /* ignore */
+                } catch (SQLiteCantOpenDatabaseException e) {
+                    Log.w(TAG, "Can't read accounts database", e);
+                    return;
                 }
             }
         });
@@ -412,15 +430,15 @@
                                 }
 
                         if (accounts == null) {
-                            accounts = getAccountsAsUser(null, userId, "android");
+                            accounts = getAccountsOrEmptyArray(null, userId, "android");
                             if (ArrayUtils.isEmpty(accounts)) {
                                 return;
                             }
                         }
-
+                        UserAccounts userAccounts = getUserAccounts(UserHandle.getUserId(uid));
                         for (Account account : accounts) {
                             cancelAccountAccessRequestNotificationIfNeeded(
-                                    account, uid, packageName, true);
+                                    account, uid, packageName, true, userAccounts);
                         }
                     }
                 } finally {
@@ -440,39 +458,40 @@
     }
 
     private void cancelAccountAccessRequestNotificationIfNeeded(int uid,
-            boolean checkAccess) {
-        Account[] accounts = getAccountsAsUser(null, UserHandle.getUserId(uid), "android");
+            boolean checkAccess, UserAccounts userAccounts) {
+        Account[] accounts = getAccountsOrEmptyArray(null, UserHandle.getUserId(uid), "android");
         for (Account account : accounts) {
-            cancelAccountAccessRequestNotificationIfNeeded(account, uid, checkAccess);
+            cancelAccountAccessRequestNotificationIfNeeded(account, uid, checkAccess, userAccounts);
         }
     }
 
     private void cancelAccountAccessRequestNotificationIfNeeded(String packageName, int uid,
-            boolean checkAccess) {
-        Account[] accounts = getAccountsAsUser(null, UserHandle.getUserId(uid), "android");
+            boolean checkAccess, UserAccounts userAccounts) {
+        Account[] accounts = getAccountsOrEmptyArray(null, UserHandle.getUserId(uid), "android");
         for (Account account : accounts) {
-            cancelAccountAccessRequestNotificationIfNeeded(account, uid, packageName, checkAccess);
+            cancelAccountAccessRequestNotificationIfNeeded(account,
+                    uid, packageName, checkAccess, userAccounts);
         }
     }
 
     private void cancelAccountAccessRequestNotificationIfNeeded(Account account, int uid,
-            boolean checkAccess) {
+            boolean checkAccess, UserAccounts accounts) {
         String[] packageNames = mPackageManager.getPackagesForUid(uid);
         if (packageNames != null) {
             for (String packageName : packageNames) {
                 cancelAccountAccessRequestNotificationIfNeeded(account, uid,
-                        packageName, checkAccess);
+                        packageName, checkAccess, accounts);
             }
         }
     }
 
     private void cancelAccountAccessRequestNotificationIfNeeded(Account account,
-            int uid, String packageName, boolean checkAccess) {
+            int uid, String packageName, boolean checkAccess, UserAccounts accounts) {
         if (!checkAccess || hasAccountAccess(account, packageName,
                 UserHandle.getUserHandleForUid(uid))) {
             cancelNotification(getCredentialPermissionNotificationId(account,
-                    AccountManager.ACCOUNT_ACCESS_TOKEN_TYPE, uid),
-                    UserHandle.getUserHandleForUid(uid));
+                    AccountManager.ACCOUNT_ACCESS_TOKEN_TYPE, uid, accounts),
+                    accounts);
         }
     }
 
@@ -482,14 +501,13 @@
         Bundle.setDefusable(extras, true);
         int callingUid = Binder.getCallingUid();
         int userId = UserHandle.getCallingUserId();
-        if (Log.isLoggable(TAG, Log.VERBOSE)) {
-            Log.v(TAG, "addAccountExplicitly: " + account + ", caller's uid " + callingUid
-                    + ", pid " + Binder.getCallingPid());
-        }
         Objects.requireNonNull(account, "account cannot be null");
+        Log.v(TAG, "addAccountExplicitly: caller's uid=" + callingUid + ", pid="
+                + Binder.getCallingPid() + ", packageName=" + opPackageName + ", accountType="
+                + account.type);
         if (!isAccountManagedByCaller(account.type, callingUid, userId)) {
-            String msg = String.format("uid %s cannot explicitly add accounts of type: %s",
-                    callingUid, account.type);
+            String msg = String.format("uid=%s, package=%s cannot explicitly add "
+                    + "accounts of type: %s", callingUid, opPackageName, account.type);
             throw new SecurityException(msg);
         }
         /*
@@ -1415,7 +1433,13 @@
     private void purgeOldGrants(UserAccounts accounts) {
         synchronized (accounts.dbLock) {
             synchronized (accounts.cacheLock) {
-                List<Integer> uids = accounts.accountsDb.findAllUidGrants();
+                List<Integer> uids;
+                try {
+                    uids = accounts.accountsDb.findAllUidGrants();
+                } catch (SQLiteCantOpenDatabaseException e) {
+                    Log.w(TAG, "Could not delete grants for user = " + accounts.userId);
+                    return;
+                }
                 for (int uid : uids) {
                     final boolean packageExists = mPackageManager.getPackagesForUid(uid) != null;
                     if (packageExists) {
@@ -1441,7 +1465,13 @@
                     mPackageManager.getPackageUidAsUser(packageName, accounts.userId);
                 } catch (NameNotFoundException e) {
                     // package does not exist - remove visibility values
-                    accounts.accountsDb.deleteAccountVisibilityForPackage(packageName);
+                    try {
+                        accounts.accountsDb.deleteAccountVisibilityForPackage(packageName);
+                    } catch (SQLiteCantOpenDatabaseException sqlException) {
+                        Log.w(TAG, "Could not delete account visibility for user = "
+                                + accounts.userId, sqlException);
+                        continue;
+                    }
                     synchronized (accounts.dbLock) {
                         synchronized (accounts.cacheLock) {
                             for (Account account : accounts.visibilityCache.keySet()) {
@@ -2159,13 +2189,13 @@
          */
         cancelNotification(
                 getSigninRequiredNotificationId(accounts, accountToRename),
-                new UserHandle(accounts.userId));
+                accounts);
         synchronized(accounts.credentialsPermissionNotificationIds) {
             for (Pair<Pair<Account, String>, Integer> pair:
                     accounts.credentialsPermissionNotificationIds.keySet()) {
                 if (accountToRename.equals(pair.first.first)) {
                     NotificationId id = accounts.credentialsPermissionNotificationIds.get(pair);
-                    cancelNotification(id, new UserHandle(accounts.userId));
+                    cancelNotification(id, accounts);
                 }
             }
         }
@@ -2311,18 +2341,19 @@
                 response.onError(AccountManager.ERROR_CODE_MANAGEMENT_DISABLED_FOR_ACCOUNT_TYPE,
                         "User cannot modify accounts of this type (policy).");
             } catch (RemoteException re) {
+                Log.w(TAG, "RemoteException while removing account", re);
             }
             return;
         }
         final long identityToken = clearCallingIdentity();
         UserAccounts accounts = getUserAccounts(userId);
-        cancelNotification(getSigninRequiredNotificationId(accounts, account), user);
+        cancelNotification(getSigninRequiredNotificationId(accounts, account), accounts);
         synchronized(accounts.credentialsPermissionNotificationIds) {
             for (Pair<Pair<Account, String>, Integer> pair:
                 accounts.credentialsPermissionNotificationIds.keySet()) {
                 if (account.equals(pair.first.first)) {
                     NotificationId id = accounts.credentialsPermissionNotificationIds.get(pair);
-                    cancelNotification(id, user);
+                    cancelNotification(id, accounts);
                 }
             }
         }
@@ -2516,7 +2547,7 @@
                             && AccountManager.ACCOUNT_ACCESS_TOKEN_TYPE.equals(key.first.second)) {
                         final int uid = (Integer) key.second;
                         mHandler.post(() -> cancelAccountAccessRequestNotificationIfNeeded(
-                                account, uid, false));
+                                account, uid, false, accounts));
                     }
                 }
             }
@@ -2597,8 +2628,7 @@
         if (account == null || tokenType == null || callerPkg == null || callerSigDigest == null) {
             return;
         }
-        cancelNotification(getSigninRequiredNotificationId(accounts, account),
-                UserHandle.of(accounts.userId));
+        cancelNotification(getSigninRequiredNotificationId(accounts, account), accounts);
         synchronized (accounts.cacheLock) {
             accounts.accountTokenCaches.put(
                     account, token, tokenType, callerPkg, callerSigDigest, expiryMillis);
@@ -2610,8 +2640,7 @@
         if (account == null || type == null) {
             return false;
         }
-        cancelNotification(getSigninRequiredNotificationId(accounts, account),
-                UserHandle.of(accounts.userId));
+        cancelNotification(getSigninRequiredNotificationId(accounts, account), accounts);
         synchronized (accounts.dbLock) {
             accounts.accountsDb.beginTransaction();
             boolean updateCache = false;
@@ -3202,7 +3231,8 @@
     }
 
     private void createNoCredentialsPermissionNotification(Account account, Intent intent,
-            String packageName, int userId) {
+            String packageName, UserAccounts accounts) {
+        int userId = accounts.userId;
         int uid = intent.getIntExtra(
                 GrantCredentialsPermissionActivity.EXTRAS_REQUESTING_UID, -1);
         String authTokenType = intent.getStringExtra(
@@ -3232,7 +3262,7 @@
                             null, user))
                     .build();
         installNotification(getCredentialPermissionNotificationId(
-                account, authTokenType, uid), n, "android", user.getIdentifier());
+                account, authTokenType, uid, accounts), n, "android", user.getIdentifier());
     }
 
     private String getApplicationLabel(String packageName, int userId) {
@@ -3256,8 +3286,9 @@
             // the intent from a non-Activity context. This is the default behavior.
             intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         }
+        UserAccounts accounts = getUserAccounts(UserHandle.getUserId(uid));
         intent.addCategory(getCredentialPermissionNotificationId(account,
-                authTokenType, uid).mTag + (packageName != null ? packageName : ""));
+                authTokenType, uid, accounts).mTag + (packageName != null ? packageName : ""));
         intent.putExtra(GrantCredentialsPermissionActivity.EXTRAS_ACCOUNT, account);
         intent.putExtra(GrantCredentialsPermissionActivity.EXTRAS_AUTH_TOKEN_TYPE, authTokenType);
         intent.putExtra(GrantCredentialsPermissionActivity.EXTRAS_RESPONSE, response);
@@ -3267,9 +3298,8 @@
     }
 
     private NotificationId getCredentialPermissionNotificationId(Account account,
-            String authTokenType, int uid) {
+            String authTokenType, int uid, UserAccounts accounts) {
         NotificationId nId;
-        UserAccounts accounts = getUserAccounts(UserHandle.getUserId(uid));
         synchronized (accounts.credentialsPermissionNotificationIds) {
             final Pair<Pair<Account, String>, Integer> key =
                     new Pair<Pair<Account, String>, Integer>(
@@ -4226,9 +4256,9 @@
             }
 
             private void handleAuthenticatorResponse(boolean accessGranted) throws RemoteException {
+                UserAccounts userAccounts = getUserAccounts(UserHandle.getUserId(uid));
                 cancelNotification(getCredentialPermissionNotificationId(account,
-                        AccountManager.ACCOUNT_ACCESS_TOKEN_TYPE, uid),
-                        UserHandle.getUserHandleForUid(uid));
+                        AccountManager.ACCOUNT_ACCESS_TOKEN_TYPE, uid, userAccounts), userAccounts);
                 if (callback != null) {
                     Bundle result = new Bundle();
                     result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, accessGranted);
@@ -4464,6 +4494,16 @@
     }
 
     @NonNull
+    private Account[] getAccountsOrEmptyArray(String type, int userId, String opPackageName) {
+        try {
+            return getAccountsAsUser(type, userId, opPackageName);
+        } catch (SQLiteCantOpenDatabaseException e) {
+            Log.w(TAG, "Could not get accounts for user " + userId, e);
+            return new Account[]{};
+        }
+    }
+
+    @NonNull
     private Account[] getAccountsAsUserForPackage(
             String type,
             int userId,
@@ -4552,7 +4592,7 @@
     public void addSharedAccountsFromParentUser(int parentUserId, int userId,
             String opPackageName) {
         checkManageOrCreateUsersPermission("addSharedAccountsFromParentUser");
-        Account[] accounts = getAccountsAsUser(null, parentUserId, opPackageName);
+        Account[] accounts = getAccountsOrEmptyArray(null, parentUserId, opPackageName);
         for (Account account : accounts) {
             addSharedAccountAsUser(account, userId);
         }
@@ -5056,7 +5096,7 @@
                 Log.v(TAG, "initiating bind to authenticator type " + mAccountType);
             }
             if (!bindToAuthenticator(mAccountType)) {
-                Log.d(TAG, "bind attempt failed for " + toDebugString());
+                Log.w(TAG, "bind attempt failed for " + toDebugString());
                 onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION, "bind failure");
             }
         }
@@ -5169,7 +5209,7 @@
                 if (!TextUtils.isEmpty(accountName) && !TextUtils.isEmpty(accountType)) {
                     Account account = new Account(accountName, accountType);
                     cancelNotification(getSigninRequiredNotificationId(mAccounts, account),
-                            new UserHandle(mAccounts.userId));
+                            mAccounts);
                 }
             }
             IAccountManagerResponse response;
@@ -5251,10 +5291,9 @@
             authenticatorInfo = mAuthenticatorCache.getServiceInfo(
                     AuthenticatorDescription.newKey(authenticatorType), mAccounts.userId);
             if (authenticatorInfo == null) {
-                if (Log.isLoggable(TAG, Log.VERBOSE)) {
-                    Log.v(TAG, "there is no authenticator for " + authenticatorType
-                            + ", bailing out");
-                }
+                Log.w(TAG, "there is no authenticator for " + authenticatorType
+                        + ", bailing out");
+
                 return false;
             }
 
@@ -5276,9 +5315,9 @@
                 flags |= Context.BIND_ALLOW_INSTANT;
             }
             if (!mContext.bindServiceAsUser(intent, this, flags, UserHandle.of(mAccounts.userId))) {
-                if (Log.isLoggable(TAG, Log.VERBOSE)) {
-                    Log.v(TAG, "bindService to " + authenticatorInfo.componentName + " failed");
-                }
+                Log.w(TAG, "bindService to " + authenticatorInfo.componentName + " failed");
+                // Perform unbind as per documentation at Context.bindServiceAsUser
+                mContext.unbindService(this);
                 return false;
             }
 
@@ -5492,7 +5531,7 @@
             if (intent.getComponent() != null &&
                     GrantCredentialsPermissionActivity.class.getName().equals(
                             intent.getComponent().getClassName())) {
-                createNoCredentialsPermissionNotification(account, intent, packageName, userId);
+                createNoCredentialsPermissionNotification(account, intent, packageName, accounts);
             } else {
                 Context contextForUser = getContextForUser(new UserHandle(userId));
                 final NotificationId id = getSigninRequiredNotificationId(accounts, account);
@@ -5538,16 +5577,17 @@
         }
     }
 
-    private void cancelNotification(NotificationId id, UserHandle user) {
-        cancelNotification(id, mContext.getPackageName(), user);
+    private void cancelNotification(NotificationId id, UserAccounts accounts) {
+        cancelNotification(id, mContext.getPackageName(), accounts);
     }
 
-    private void cancelNotification(NotificationId id, String packageName, UserHandle user) {
+    private void cancelNotification(NotificationId id, String packageName, UserAccounts accounts) {
         final long identityToken = clearCallingIdentity();
         try {
             INotificationManager service = mInjector.getNotificationManager();
             service.cancelNotificationWithTag(
-                    packageName, "android", id.mTag, id.mId, user.getIdentifier());
+                    packageName, "android", id.mTag, id.mId,
+                    UserHandle.of(accounts.userId).getIdentifier());
         } catch (RemoteException e) {
             /* ignore - local call */
         } finally {
@@ -6004,10 +6044,11 @@
                     accounts.accountsDb.insertGrant(accountId, authTokenType, uid);
                 }
                 cancelNotification(
-                        getCredentialPermissionNotificationId(account, authTokenType, uid),
-                        UserHandle.of(accounts.userId));
+                        getCredentialPermissionNotificationId(
+                                account, authTokenType, uid, accounts),
+                        accounts);
 
-                cancelAccountAccessRequestNotificationIfNeeded(account, uid, true);
+                cancelAccountAccessRequestNotificationIfNeeded(account, uid, true, accounts);
             }
         }
 
@@ -6047,8 +6088,9 @@
                 }
 
                 cancelNotification(
-                        getCredentialPermissionNotificationId(account, authTokenType, uid),
-                        UserHandle.of(accounts.userId));
+                        getCredentialPermissionNotificationId(
+                                account, authTokenType, uid, accounts),
+                        accounts);
             }
         }
 
diff --git a/services/core/java/com/android/server/am/ActiveInstrumentation.java b/services/core/java/com/android/server/am/ActiveInstrumentation.java
index 61ccf11..49685b95 100644
--- a/services/core/java/com/android/server/am/ActiveInstrumentation.java
+++ b/services/core/java/com/android/server/am/ActiveInstrumentation.java
@@ -40,6 +40,9 @@
     // The application being instrumented
     ApplicationInfo mTargetInfo;
 
+    // Whether the application is instrumented as an sdk running in the sdk_sandbox.
+    boolean mIsSdkInSandbox;
+
     // Where to save profiling
     String mProfileFile;
 
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index d5f41a1..d199006 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -256,7 +256,7 @@
 public final class ActiveServices {
     private static final String TAG = TAG_WITH_CLASS_NAME ? "ActiveServices" : TAG_AM;
     private static final String TAG_MU = TAG + POSTFIX_MU;
-    private static final String TAG_SERVICE = TAG + POSTFIX_SERVICE;
+    static final String TAG_SERVICE = TAG + POSTFIX_SERVICE;
     private static final String TAG_SERVICE_EXECUTING = TAG + POSTFIX_SERVICE_EXECUTING;
 
     private static final boolean DEBUG_DELAYED_SERVICE = DEBUG_SERVICE;
@@ -850,8 +850,7 @@
         // Service.startForeground()), at that point we will consult the BFSL check and the timeout
         // and make the necessary decisions.
         setFgsRestrictionLocked(callingPackage, callingPid, callingUid, service, r, userId,
-                backgroundStartPrivileges, false /* isBindService */,
-                !fgRequired /* isStartService */);
+                backgroundStartPrivileges, false /* isBindService */);
 
         if (!mAm.mUserController.exists(r.userId)) {
             Slog.w(TAG, "Trying to start service with non-existent user! " + r.userId);
@@ -894,7 +893,7 @@
 
         if (fgRequired) {
             logFgsBackgroundStart(r);
-            if (r.mAllowStartForeground == REASON_DENIED && isBgFgsRestrictionEnabled(r)) {
+            if (!r.isFgsAllowedStart() && isBgFgsRestrictionEnabled(r)) {
                 String msg = "startForegroundService() not allowed due to "
                         + "mAllowStartForeground false: service "
                         + r.shortInstanceName;
@@ -1060,7 +1059,7 @@
             // Use that as a shortcut if possible to avoid having to recheck all the conditions.
             final boolean whileInUseAllowsUiJobScheduling =
                     ActivityManagerService.doesReasonCodeAllowSchedulingUserInitiatedJobs(
-                            r.mAllowWhileInUsePermissionInFgsReason);
+                            r.getFgsAllowWIU());
             r.updateAllowUiJobScheduling(whileInUseAllowsUiJobScheduling
                     || mAm.canScheduleUserInitiatedJobs(callingUid, callingPid, callingPackage));
         } else {
@@ -2178,12 +2177,12 @@
                         // on a SHORT_SERVICE FGS.
 
                         // See if the app could start an FGS or not.
-                        r.mAllowStartForeground = REASON_DENIED;
+                        r.clearFgsAllowStart();
                         setFgsRestrictionLocked(r.serviceInfo.packageName, r.app.getPid(),
                                 r.appInfo.uid, r.intent.getIntent(), r, r.userId,
                                 BackgroundStartPrivileges.NONE,
-                                false /* isBindService */, false /* isStartService */);
-                        if (r.mAllowStartForeground == REASON_DENIED) {
+                                false /* isBindService */);
+                        if (!r.isFgsAllowedStart()) {
                             Slog.w(TAG_SERVICE, "FGS type change to/from SHORT_SERVICE: "
                                     + " BFSL DENIED.");
                         } else {
@@ -2191,13 +2190,13 @@
                                 Slog.w(TAG_SERVICE, "FGS type change to/from SHORT_SERVICE: "
                                         + " BFSL Allowed: "
                                         + PowerExemptionManager.reasonCodeToString(
-                                                r.mAllowStartForeground));
+                                                r.getFgsAllowStart()));
                             }
                         }
 
                         final boolean fgsStartAllowed =
                                 !isBgFgsRestrictionEnabledForService
-                                        || (r.mAllowStartForeground != REASON_DENIED);
+                                        || r.isFgsAllowedStart();
 
                         if (fgsStartAllowed) {
                             if (isNewTypeShortFgs) {
@@ -2246,7 +2245,7 @@
                                 setFgsRestrictionLocked(r.serviceInfo.packageName, r.app.getPid(),
                                         r.appInfo.uid, r.intent.getIntent(), r, r.userId,
                                         BackgroundStartPrivileges.NONE,
-                                        false /* isBindService */, false /* isStartService */);
+                                        false /* isBindService */);
                                 final String temp = "startForegroundDelayMs:" + delayMs;
                                 if (r.mInfoAllowStartForeground != null) {
                                     r.mInfoAllowStartForeground += "; " + temp;
@@ -2266,20 +2265,21 @@
                         setFgsRestrictionLocked(r.serviceInfo.packageName, r.app.getPid(),
                                 r.appInfo.uid, r.intent.getIntent(), r, r.userId,
                                 BackgroundStartPrivileges.NONE,
-                                false /* isBindService */, false /* isStartService */);
+                                false /* isBindService */);
                     }
 
                     // If the foreground service is not started from TOP process, do not allow it to
                     // have while-in-use location/camera/microphone access.
-                    if (!r.mAllowWhileInUsePermissionInFgs) {
+                    if (!r.isFgsAllowedWIU()) {
                         Slog.w(TAG,
                                 "Foreground service started from background can not have "
                                         + "location/camera/microphone access: service "
                                         + r.shortInstanceName);
                     }
+                    r.maybeLogFgsLogicChange();
                     if (!bypassBfslCheck) {
                         logFgsBackgroundStart(r);
-                        if (r.mAllowStartForeground == REASON_DENIED
+                        if (!r.isFgsAllowedStart()
                                 && isBgFgsRestrictionEnabledForService) {
                             final String msg = "Service.startForeground() not allowed due to "
                                     + "mAllowStartForeground false: service "
@@ -2378,9 +2378,9 @@
                         // The logging of FOREGROUND_SERVICE_STATE_CHANGED__STATE__ENTER event could
                         // be deferred, make a copy of mAllowStartForeground and
                         // mAllowWhileInUsePermissionInFgs.
-                        r.mAllowStartForegroundAtEntering = r.mAllowStartForeground;
+                        r.mAllowStartForegroundAtEntering = r.getFgsAllowStart();
                         r.mAllowWhileInUsePermissionInFgsAtEntering =
-                                r.mAllowWhileInUsePermissionInFgs;
+                                r.isFgsAllowedWIU();
                         r.mStartForegroundCount++;
                         r.mFgsEnterTime = SystemClock.uptimeMillis();
                         if (!stopProcStatsOp) {
@@ -2558,7 +2558,7 @@
                 policy.getForegroundServiceTypePolicyInfo(type, defaultToType);
         final @ForegroundServicePolicyCheckCode int code = policy.checkForegroundServiceTypePolicy(
                 mAm.mContext, r.packageName, r.app.uid, r.app.getPid(),
-                r.mAllowWhileInUsePermissionInFgs, policyInfo);
+                r.isFgsAllowedWIU(), policyInfo);
         RuntimeException exception = null;
         switch (code) {
             case FGS_TYPE_POLICY_CHECK_DEPRECATED: {
@@ -3701,7 +3701,9 @@
             }
             clientPsr.addConnection(c);
             c.startAssociationIfNeeded();
-            if (c.hasFlag(Context.BIND_ABOVE_CLIENT)) {
+            // Don't set hasAboveClient if binding to self to prevent modifyRawOomAdj() from
+            // dropping the process' adjustment level.
+            if (b.client != s.app && c.hasFlag(Context.BIND_ABOVE_CLIENT)) {
                 clientPsr.setHasAboveClient(true);
             }
             if (c.hasFlag(BIND_ALLOW_WHITELIST_MANAGEMENT)) {
@@ -3742,8 +3744,7 @@
                 }
             }
             setFgsRestrictionLocked(callingPackage, callingPid, callingUid, service, s, userId,
-                    BackgroundStartPrivileges.NONE, true /* isBindService */,
-                    false /* isStartService */);
+                    BackgroundStartPrivileges.NONE, true /* isBindService */);
 
             if (s.app != null) {
                 ProcessServiceRecord servicePsr = s.app.mServices;
@@ -6767,8 +6768,9 @@
                     mAm.mHandler.removeCallbacks(mLastAnrDumpClearer);
                     mAm.mHandler.postDelayed(mLastAnrDumpClearer,
                             LAST_ANR_LIFETIME_DURATION_MSECS);
-                    String anrMessage = "executing service " + timeout.shortInstanceName;
-                    timeoutRecord = TimeoutRecord.forServiceExec(anrMessage);
+                    long waitedMillis = now - timeout.executingStart;
+                    timeoutRecord = TimeoutRecord.forServiceExec(timeout.shortInstanceName,
+                            waitedMillis);
                 } else {
                     Message msg = mAm.mHandler.obtainMessage(
                             ActivityManagerService.SERVICE_TIMEOUT_MSG);
@@ -7441,54 +7443,80 @@
      * @param callingUid caller app's uid.
      * @param intent intent to start/bind service.
      * @param r the service to start.
-     * @param isStartService True if it's called from Context.startService().
-     *                       False if it's called from Context.startForegroundService() or
-     *                       Service.startForeground().
+     * @param isBindService True if it's called from bindService().
      * @return true if allow, false otherwise.
      */
     private void setFgsRestrictionLocked(String callingPackage,
             int callingPid, int callingUid, Intent intent, ServiceRecord r, int userId,
-            BackgroundStartPrivileges backgroundStartPrivileges, boolean isBindService,
-            boolean isStartService) {
-        // Check DeviceConfig flag.
-        if (!mAm.mConstants.mFlagBackgroundFgsStartRestrictionEnabled) {
-            if (!r.mAllowWhileInUsePermissionInFgs) {
-                // BGFGS start restrictions are disabled. We're allowing while-in-use permissions.
-                // Note REASON_OTHER since there's no other suitable reason.
-                r.mAllowWhileInUsePermissionInFgsReason = REASON_OTHER;
-            }
-            r.mAllowWhileInUsePermissionInFgs = true;
+            BackgroundStartPrivileges backgroundStartPrivileges, boolean isBindService) {
+
+        @ReasonCode int allowWIU;
+        @ReasonCode int allowStart;
+
+        // If called from bindService(), do not update the actual fields, but instead
+        // keep it in a separate set of fields.
+        if (isBindService) {
+            allowWIU = r.mAllowWIUInBindService;
+            allowStart = r.mAllowStartInBindService;
+        } else {
+            allowWIU = r.mAllowWhileInUsePermissionInFgsReasonNoBinding;
+            allowStart = r.mAllowStartForegroundNoBinding;
         }
 
-        if (!r.mAllowWhileInUsePermissionInFgs
-                || (r.mAllowStartForeground == REASON_DENIED)) {
+        // Check DeviceConfig flag.
+        if (!mAm.mConstants.mFlagBackgroundFgsStartRestrictionEnabled) {
+            if (allowWIU == REASON_DENIED) {
+                // BGFGS start restrictions are disabled. We're allowing while-in-use permissions.
+                // Note REASON_OTHER since there's no other suitable reason.
+                allowWIU = REASON_OTHER;
+            }
+        }
+
+        if ((allowWIU == REASON_DENIED)
+                || (allowStart == REASON_DENIED)) {
             @ReasonCode final int allowWhileInUse = shouldAllowFgsWhileInUsePermissionLocked(
                     callingPackage, callingPid, callingUid, r.app, backgroundStartPrivileges);
             // We store them to compare the old and new while-in-use logics to each other.
             // (They're not used for any other purposes.)
-            if (!r.mAllowWhileInUsePermissionInFgs) {
-                r.mAllowWhileInUsePermissionInFgs = (allowWhileInUse != REASON_DENIED);
-                r.mAllowWhileInUsePermissionInFgsReason = allowWhileInUse;
+            if (allowWIU == REASON_DENIED) {
+                allowWIU = allowWhileInUse;
             }
-            if (r.mAllowStartForeground == REASON_DENIED) {
-                r.mAllowStartForeground = shouldAllowFgsStartForegroundWithBindingCheckLocked(
+            if (allowStart == REASON_DENIED) {
+                allowStart = shouldAllowFgsStartForegroundWithBindingCheckLocked(
                         allowWhileInUse, callingPackage, callingPid, callingUid, intent, r,
                         backgroundStartPrivileges, isBindService);
             }
         }
+
+        if (isBindService) {
+            r.mAllowWIUInBindService = allowWIU;
+            r.mAllowStartInBindService = allowStart;
+        } else {
+            r.mAllowWhileInUsePermissionInFgsReasonNoBinding = allowWIU;
+            r.mAllowStartForegroundNoBinding = allowStart;
+
+            // Also do a binding client check, unless called from bindService().
+            if (r.mAllowWIUByBindings == REASON_DENIED) {
+                r.mAllowWIUByBindings =
+                        shouldAllowFgsWhileInUsePermissionByBindingsLocked(callingUid);
+            }
+            if (r.mAllowStartByBindings == REASON_DENIED) {
+                r.mAllowStartByBindings = r.mAllowWIUByBindings;
+            }
+        }
     }
 
     /**
      * Reset various while-in-use and BFSL related information.
      */
     void resetFgsRestrictionLocked(ServiceRecord r) {
-        r.mAllowWhileInUsePermissionInFgs = false;
-        r.mAllowWhileInUsePermissionInFgsReason = REASON_DENIED;
-        r.mAllowStartForeground = REASON_DENIED;
+        r.clearFgsAllowWIU();
+        r.clearFgsAllowStart();
+
         r.mInfoAllowStartForeground = null;
         r.mInfoTempFgsAllowListReason = null;
         r.mLoggedInfoAllowStartForeground = false;
-        r.updateAllowUiJobScheduling(r.mAllowWhileInUsePermissionInFgs);
+        r.updateAllowUiJobScheduling(r.isFgsAllowedWIU());
     }
 
     boolean canStartForegroundServiceLocked(int callingPid, int callingUid, String callingPackage) {
@@ -8060,10 +8088,10 @@
         */
         if (!r.mLoggedInfoAllowStartForeground) {
             final String msg = "Background started FGS: "
-                    + ((r.mAllowStartForeground != REASON_DENIED) ? "Allowed " : "Disallowed ")
+                    + (r.isFgsAllowedStart() ? "Allowed " : "Disallowed ")
                     + r.mInfoAllowStartForeground
                     + (r.isShortFgs() ? " (Called on SHORT_SERVICE)" : "");
-            if (r.mAllowStartForeground != REASON_DENIED) {
+            if (r.isFgsAllowedStart()) {
                 if (ActivityManagerUtils.shouldSamplePackageForAtom(r.packageName,
                         mAm.mConstants.mFgsStartAllowedLogSampleRate)) {
                     Slog.wtfQuiet(TAG, msg);
@@ -8103,8 +8131,8 @@
             allowWhileInUsePermissionInFgs = r.mAllowWhileInUsePermissionInFgsAtEntering;
             fgsStartReasonCode = r.mAllowStartForegroundAtEntering;
         } else {
-            allowWhileInUsePermissionInFgs = r.mAllowWhileInUsePermissionInFgs;
-            fgsStartReasonCode = r.mAllowStartForeground;
+            allowWhileInUsePermissionInFgs = r.isFgsAllowedWIU();
+            fgsStartReasonCode = r.getFgsAllowStart();
         }
         final int callerTargetSdkVersion = r.mRecentCallerApplicationInfo != null
                 ? r.mRecentCallerApplicationInfo.targetSdkVersion : 0;
@@ -8293,8 +8321,7 @@
         r.mFgsEnterTime = SystemClock.uptimeMillis();
         r.foregroundServiceType = options.mForegroundServiceTypes;
         setFgsRestrictionLocked(callingPackage, callingPid, callingUid, intent, r, userId,
-                BackgroundStartPrivileges.NONE,  false /* isBindService */,
-                false /* isStartService */);
+                BackgroundStartPrivileges.NONE,  false /* isBindService */);
         final ProcessServiceRecord psr = callerApp.mServices;
         final boolean newService = psr.startService(r);
         // updateOomAdj.
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index 8c31209..03e8691 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -1027,7 +1027,7 @@
     private static final String KEY_ENABLE_WAIT_FOR_FINISH_ATTACH_APPLICATION =
             "enable_wait_for_finish_attach_application";
 
-    private static final boolean DEFAULT_ENABLE_WAIT_FOR_FINISH_ATTACH_APPLICATION = false;
+    private static final boolean DEFAULT_ENABLE_WAIT_FOR_FINISH_ATTACH_APPLICATION = true;
 
     /** @see #KEY_ENABLE_WAIT_FOR_FINISH_ATTACH_APPLICATION */
     public volatile boolean mEnableWaitForFinishAttachApplication =
@@ -1345,9 +1345,11 @@
         // The following read from Settings.
         updateActivityStartsLoggingEnabled();
         updateForegroundServiceStartsLoggingEnabled();
+        // Read DropboxRateLimiter params from flags.
+        mService.initDropboxRateLimiter();
     }
 
-    private void loadDeviceConfigConstants() {
+    void loadDeviceConfigConstants() {
         mOnDeviceConfigChangedListener.onPropertiesChanged(
                 DeviceConfig.getProperties(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER));
         mOnDeviceConfigChangedForComponentAliasListener.onPropertiesChanged(
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 476e1b4..c9769b3 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -559,6 +559,10 @@
     // How long we wait for a launched process to attach to the activity manager
     // before we decide it's never going to come up for real.
     static final int PROC_START_TIMEOUT = 10 * 1000 * Build.HW_TIMEOUT_MULTIPLIER;
+
+    // How long we wait for a launched process to complete its app startup before we ANR.
+    static final int BIND_APPLICATION_TIMEOUT = 10 * 1000 * Build.HW_TIMEOUT_MULTIPLIER;
+
     // How long we wait to kill an application zygote, after the last process using
     // it has gone away.
     static final int KILL_APP_ZYGOTE_DELAY_MS = 5 * 1000;
@@ -1526,7 +1530,8 @@
      */
     int mBootPhase;
 
-    volatile boolean mDeterministicUidIdle = false;
+    @GuardedBy("this")
+    boolean mDeterministicUidIdle = false;
 
     @VisibleForTesting
     public WindowManagerService mWindowManager;
@@ -1624,6 +1629,7 @@
     static final int UPDATE_CACHED_APP_HIGH_WATERMARK = 79;
     static final int ADD_UID_TO_OBSERVER_MSG = 80;
     static final int REMOVE_UID_FROM_OBSERVER_MSG = 81;
+    static final int BIND_APPLICATION_TIMEOUT_MSG = 82;
 
     static final int FIRST_BROADCAST_QUEUE_MSG = 200;
 
@@ -1976,6 +1982,16 @@
                 case UPDATE_CACHED_APP_HIGH_WATERMARK: {
                     mAppProfiler.mCachedAppsWatermarkData.updateCachedAppsSnapshot((long) msg.obj);
                 } break;
+                case BIND_APPLICATION_TIMEOUT_MSG: {
+                    ProcessRecord app = (ProcessRecord) msg.obj;
+
+                    final String anrMessage;
+                    synchronized (app) {
+                        anrMessage = "Process " + app + " failed to complete startup";
+                    }
+
+                    mAnrHelper.appNotResponding(app, TimeoutRecord.forAppStart(anrMessage));
+                } break;
             }
         }
     }
@@ -4579,14 +4595,8 @@
         EventLogTags.writeAmProcBound(app.userId, pid, app.processName);
 
         synchronized (mProcLock) {
-            app.mState.setCurAdj(ProcessList.INVALID_ADJ);
-            app.mState.setSetAdj(ProcessList.INVALID_ADJ);
-            app.mState.setVerifiedAdj(ProcessList.INVALID_ADJ);
-            mOomAdjuster.setAttachingSchedGroupLSP(app);
-            app.mState.setForcingToImportant(null);
+            mOomAdjuster.setAttachingProcessStatesLSP(app);
             clearProcessForegroundLocked(app);
-            app.mState.setHasShownUi(false);
-            app.mState.setCached(false);
             app.setDebugging(false);
             app.setKilledByAm(false);
             app.setKilled(false);
@@ -4714,6 +4724,7 @@
             } else if (instr2 != null) {
                 thread.bindApplication(processName, appInfo,
                         app.sdkSandboxClientAppVolumeUuid, app.sdkSandboxClientAppPackage,
+                        instr2.mIsSdkInSandbox,
                         providerList,
                         instr2.mClass,
                         profilerInfo, instr2.mArguments,
@@ -4730,6 +4741,7 @@
             } else {
                 thread.bindApplication(processName, appInfo,
                         app.sdkSandboxClientAppVolumeUuid, app.sdkSandboxClientAppPackage,
+                        /* isSdkInSandbox= */ false,
                         providerList, null, profilerInfo, null, null, null, testMode,
                         mBinderTransactionTrackingEnabled, enableTrackAllocation,
                         isRestrictedBackupMode || !normalMode, app.isPersistent(),
@@ -4740,6 +4752,12 @@
                         app.getDisabledCompatChanges(), serializedSystemFontMap,
                         app.getStartElapsedTime(), app.getStartUptime());
             }
+
+            Message msg = mHandler.obtainMessage(BIND_APPLICATION_TIMEOUT_MSG);
+            msg.obj = app;
+            mHandler.sendMessageDelayed(msg, BIND_APPLICATION_TIMEOUT);
+            mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app);
+
             if (profilerInfo != null) {
                 profilerInfo.closeFd();
                 profilerInfo = null;
@@ -4753,8 +4771,14 @@
                 app.makeActive(thread, mProcessStats);
                 checkTime(startTime, "attachApplicationLocked: immediately after bindApplication");
             }
+            app.setPendingFinishAttach(true);
+
             updateLruProcessLocked(app, false, null);
             checkTime(startTime, "attachApplicationLocked: after updateLruProcessLocked");
+
+            updateOomAdjLocked(app, OOM_ADJ_REASON_PROCESS_BEGIN);
+            checkTime(startTime, "attachApplicationLocked: after updateOomAdjLocked");
+
             final long now = SystemClock.uptimeMillis();
             synchronized (mAppProfiler.mProfilerLock) {
                 app.mProfile.setLastRequestedGc(now);
@@ -4770,8 +4794,6 @@
 
             if (!mConstants.mEnableWaitForFinishAttachApplication) {
                 finishAttachApplicationInner(startSeq, callingUid, pid);
-            } else {
-                app.setPendingFinishAttach(true);
             }
         } catch (Exception e) {
             // We need kill the process group here. (b/148588589)
@@ -4810,7 +4832,7 @@
         }
 
         if (app != null && app.getStartUid() == uid && app.getStartSeq() == startSeq) {
-            mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app);
+            mHandler.removeMessages(BIND_APPLICATION_TIMEOUT_MSG, app);
         } else {
             Slog.wtf(TAG, "Mismatched or missing ProcessRecord: " + app + ". Pid: " + pid
                     + ". Uid: " + uid);
@@ -5821,12 +5843,25 @@
     /**
      * Allows if {@code pid} is {@link #MY_PID}, then denies if the {@code pid} has been denied
      * provided non-{@code null} {@code permission} before. Otherwise calls into
-     * {@link ActivityManager#checkComponentPermission(String, int, int, boolean)}.
+     * {@link ActivityManager#checkComponentPermission(String, int, int, int, boolean)}.
      */
     @PackageManager.PermissionResult
     @PermissionMethod
     public static int checkComponentPermission(@PermissionName String permission, int pid, int uid,
             int owningUid, boolean exported) {
+        return checkComponentPermission(permission, pid, uid, Context.DEVICE_ID_DEFAULT,
+                owningUid, exported);
+    }
+
+    /**
+     * Allows if {@code pid} is {@link #MY_PID}, then denies if the {@code pid} has been denied
+     * provided non-{@code null} {@code permission} before. Otherwise calls into
+     * {@link ActivityManager#checkComponentPermission(String, int, int, int, boolean)}.
+     */
+    @PackageManager.PermissionResult
+    @PermissionMethod
+    public static int checkComponentPermission(@PermissionName String permission, int pid, int uid,
+            int deviceId, int owningUid, boolean exported) {
         if (pid == MY_PID) {
             return PackageManager.PERMISSION_GRANTED;
         }
@@ -5845,7 +5880,7 @@
                 }
             }
         }
-        return ActivityManager.checkComponentPermission(permission, uid,
+        return ActivityManager.checkComponentPermission(permission, uid, deviceId,
                 owningUid, exported);
     }
 
@@ -5874,10 +5909,27 @@
     @PackageManager.PermissionResult
     @PermissionMethod
     public int checkPermission(@PermissionName String permission, int pid, int uid) {
+        return checkPermissionForDevice(permission, pid, uid, Context.DEVICE_ID_DEFAULT);
+    }
+
+    /**
+     * As the only public entry point for permissions checking, this method
+     * can enforce the semantic that requesting a check on a null global
+     * permission is automatically denied.  (Internally a null permission
+     * string is used when calling {@link #checkComponentPermission} in cases
+     * when only uid-based security is needed.)
+     *
+     * This can be called with or without the global lock held.
+     */
+    @Override
+    @PackageManager.PermissionResult
+    @PermissionMethod
+    public int checkPermissionForDevice(@PermissionName String permission, int pid, int uid,
+            int deviceId) {
         if (permission == null) {
             return PackageManager.PERMISSION_DENIED;
         }
-        return checkComponentPermission(permission, pid, uid, -1, true);
+        return checkComponentPermission(permission, pid, uid, deviceId, -1, true);
     }
 
     /**
@@ -8107,8 +8159,8 @@
         return killed;
     }
 
-    @Override
-    public void killUid(int appId, int userId, String reason) {
+    private void killUid(int appId, int userId, int reason, int subReason,
+            String reasonAsString) {
         enforceCallingPermission(Manifest.permission.KILL_UID, "killUid");
         synchronized (this) {
             final long identity = Binder.clearCallingIdentity();
@@ -8119,9 +8171,9 @@
                             true /* callerWillRestart */, true /* doit */,
                             true /* evenPersistent */, false /* setRemoved */,
                             false /* uninstalling */,
-                            ApplicationExitInfo.REASON_OTHER,
-                            ApplicationExitInfo.SUBREASON_KILL_UID,
-                            reason != null ? reason : "kill uid");
+                            reason,
+                            subReason,
+                            reasonAsString != null ? reasonAsString : "kill uid");
                 }
             } finally {
                 Binder.restoreCallingIdentity(identity);
@@ -8130,6 +8182,12 @@
     }
 
     @Override
+    public void killUid(int appId, int userId, String reason) {
+        killUid(appId, userId, ApplicationExitInfo.REASON_OTHER,
+                ApplicationExitInfo.SUBREASON_KILL_UID, reason);
+    }
+
+    @Override
     public void killUidForPermissionChange(int appId, int userId, String reason) {
         enforceCallingPermission(Manifest.permission.KILL_UID, "killUid");
         synchronized (this) {
@@ -8594,10 +8652,7 @@
                 t.traceEnd();
             }
 
-            t.traceBegin("showSystemReadyErrorDialogs");
-            mAtmInternal.showSystemReadyErrorDialogsIfNeeded();
-            t.traceEnd();
-
+            mHandler.post(mAtmInternal::showSystemReadyErrorDialogsIfNeeded);
 
             if (isBootingSystemUser) {
                 // Need to send the broadcasts for the system user here because
@@ -8647,6 +8702,8 @@
                             Slog.i(TAG, "Skipping kill (uid is SYSTEM)");
                         } else {
                             killUid(UserHandle.getAppId(uid), UserHandle.getUserId(uid),
+                                    ApplicationExitInfo.REASON_EXCESSIVE_RESOURCE_USAGE,
+                                    ApplicationExitInfo.SUBREASON_EXCESSIVE_BINDER_OBJECTS,
                                     "Too many Binders sent to SYSTEM");
                             // We need to run a GC here, because killing the processes involved
                             // actually isn't guaranteed to free up the proxies; in fact, if the
@@ -8660,7 +8717,7 @@
                             // cleaning up the old proxies.
                             VMRuntime.getRuntime().requestConcurrentGC();
                         }
-                    }, mHandler);
+                    }, BackgroundThread.getHandler());
             t.traceEnd(); // setBinderProxies
 
             t.traceEnd(); // ActivityManagerStartApps
@@ -9218,6 +9275,11 @@
 
     private final DropboxRateLimiter mDropboxRateLimiter = new DropboxRateLimiter();
 
+    /** Initializes the Dropbox Rate Limiter parameters from flags. */
+    public void initDropboxRateLimiter() {
+        mDropboxRateLimiter.init();
+    }
+
     /**
      * Write a description of an error (crash, WTF, ANR) to the drop box.
      * @param eventType to include in the drop box tag ("crash", "wtf", etc.)
@@ -9678,6 +9740,11 @@
     public void onShellCommand(FileDescriptor in, FileDescriptor out,
             FileDescriptor err, String[] args, ShellCallback callback,
             ResultReceiver resultReceiver) {
+        final int callingUid = Binder.getCallingUid();
+        if (callingUid != ROOT_UID && callingUid != Process.SHELL_UID) {
+            resultReceiver.send(-1, null);
+            throw new SecurityException("Shell commands are only callable by root or shell");
+        }
         (new ActivityManagerShellCommand(this, false)).exec(
                 this, in, out, err, args, callback, resultReceiver);
     }
@@ -11312,6 +11379,13 @@
                 pw.println("\n\n** Cache info for pid " + pid + " [" + r.processName + "] **");
                 pw.flush();
                 try {
+                    if (pid == Process.myPid()) {
+                        // Directly dump to target fd for local dump to avoid hang.
+                        try (ParcelFileDescriptor pfd = ParcelFileDescriptor.fromFd(fd.getInt$())) {
+                            thread.dumpCacheInfo(pfd, args);
+                        }
+                        continue;
+                    }
                     TransferPipe tp = new TransferPipe();
                     try {
                         thread.dumpCacheInfo(tp.getWriteFd(), args);
@@ -15543,7 +15617,8 @@
                         ai,
                         noRestart,
                         disableHiddenApiChecks,
-                        disableTestApiChecks);
+                        disableTestApiChecks,
+                        (flags & ActivityManager.INSTR_FLAG_INSTRUMENT_SDK_IN_SANDBOX) != 0);
             }
 
             ActiveInstrumentation activeInstr = new ActiveInstrumentation(this);
@@ -15640,7 +15715,8 @@
             ApplicationInfo sdkSandboxClientAppInfo,
             boolean noRestart,
             boolean disableHiddenApiChecks,
-            boolean disableTestApiChecks) {
+            boolean disableTestApiChecks,
+            boolean isSdkInSandbox) {
 
         if (noRestart) {
             reportStartInstrumentationFailureLocked(
@@ -15650,16 +15726,6 @@
             return false;
         }
 
-        final ApplicationInfo sdkSandboxInfo;
-        try {
-            final PackageManager pm = mContext.getPackageManager();
-            sdkSandboxInfo = pm.getApplicationInfoAsUser(pm.getSdkSandboxPackageName(), 0, userId);
-        } catch (NameNotFoundException e) {
-            reportStartInstrumentationFailureLocked(
-                    watcher, className, "Can't find SdkSandbox package");
-            return false;
-        }
-
         final SdkSandboxManagerLocal sandboxManagerLocal =
                 LocalManagerRegistry.getManager(SdkSandboxManagerLocal.class);
         if (sandboxManagerLocal == null) {
@@ -15668,13 +15734,22 @@
             return false;
         }
 
-        final String processName = sandboxManagerLocal.getSdkSandboxProcessNameForInstrumentation(
-                sdkSandboxClientAppInfo);
+        final ApplicationInfo sdkSandboxInfo;
+        try {
+            sdkSandboxInfo =
+                    sandboxManagerLocal.getSdkSandboxApplicationInfoForInstrumentation(
+                            sdkSandboxClientAppInfo, userId, isSdkInSandbox);
+        } catch (NameNotFoundException e) {
+            reportStartInstrumentationFailureLocked(
+                    watcher, className, "Can't find SdkSandbox package");
+            return false;
+        }
 
         ActiveInstrumentation activeInstr = new ActiveInstrumentation(this);
         activeInstr.mClass = className;
-        activeInstr.mTargetProcesses = new String[]{processName};
+        activeInstr.mTargetProcesses = new String[]{sdkSandboxInfo.processName};
         activeInstr.mTargetInfo = sdkSandboxInfo;
+        activeInstr.mIsSdkInSandbox = isSdkInSandbox;
         activeInstr.mProfileFile = profileFile;
         activeInstr.mArguments = arguments;
         activeInstr.mWatcher = watcher;
@@ -15691,7 +15766,6 @@
             sandboxManagerLocal.notifyInstrumentationStarted(
                     sdkSandboxClientAppInfo.packageName, sdkSandboxClientAppInfo.uid);
             synchronized (mProcLock) {
-                int sdkSandboxUid = Process.toSdkSandboxUid(sdkSandboxClientAppInfo.uid);
                 // Kill the package sdk sandbox process belong to. At this point sdk sandbox is
                 // already killed.
                 forceStopPackageLocked(
@@ -15707,10 +15781,10 @@
 
                 ProcessRecord app = addAppLocked(
                         sdkSandboxInfo,
-                        processName,
+                        sdkSandboxInfo.processName,
                         /* isolated= */ false,
                         /* isSdkSandbox= */ true,
-                        sdkSandboxUid,
+                        sdkSandboxInfo.uid,
                         sdkSandboxClientAppInfo.packageName,
                         disableHiddenApiChecks,
                         disableTestApiChecks,
@@ -16537,7 +16611,9 @@
 
     @Override
     public void setDeterministicUidIdle(boolean deterministic) {
-        mDeterministicUidIdle = deterministic;
+        synchronized (this) {
+            mDeterministicUidIdle = deterministic;
+        }
     }
 
     /** Make the currently active UIDs idle after a certain grace period. */
@@ -19603,7 +19679,7 @@
             for (Display display : allDisplays) {
                 int displayId = display.getDisplayId();
                 // TODO(b/247592632): check other properties like isSecure or proper display type
-                if (display.isValid()
+                if (display.isValid() && ((display.getFlags() & Display.FLAG_PRIVATE) == 0)
                         && (allowOnDefaultDisplay || displayId != Display.DEFAULT_DISPLAY)) {
                     displayIds[numberValidDisplays++] = displayId;
                 }
@@ -19839,7 +19915,8 @@
                     return superImpl.apply(code, new AttributionSource(shellUid,
                             Process.INVALID_PID, "com.android.shell",
                             attributionSource.getAttributionTag(), attributionSource.getToken(),
-                            /*renouncedPermissions*/ null, attributionSource.getNext()),
+                            /*renouncedPermissions*/ null, attributionSource.getDeviceId(),
+                            attributionSource.getNext()),
                             shouldCollectAsyncNotedOp, message, shouldCollectMessage,
                             skiProxyOperation);
                 } finally {
@@ -19892,7 +19969,8 @@
                     return superImpl.apply(clientId, code, new AttributionSource(shellUid,
                             Process.INVALID_PID, "com.android.shell",
                             attributionSource.getAttributionTag(), attributionSource.getToken(),
-                            /*renouncedPermissions*/ null, attributionSource.getNext()),
+                            /*renouncedPermissions*/ null, attributionSource.getDeviceId(),
+                            attributionSource.getNext()),
                             startIfModeDefault, shouldCollectAsyncNotedOp, message,
                             shouldCollectMessage, skipProxyOperation, proxyAttributionFlags,
                             proxiedAttributionFlags, attributionChainId);
@@ -19918,7 +19996,8 @@
                     superImpl.apply(clientId, code, new AttributionSource(shellUid,
                             Process.INVALID_PID, "com.android.shell",
                             attributionSource.getAttributionTag(), attributionSource.getToken(),
-                            /*renouncedPermissions*/ null, attributionSource.getNext()),
+                            /*renouncedPermissions*/ null, attributionSource.getDeviceId(),
+                            attributionSource.getNext()),
                             skipProxyOperation);
                 } finally {
                     Binder.restoreCallingIdentity(identity);
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 569f55f..c0ac1f8 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -228,6 +228,9 @@
         final PrintWriter pw = getOutPrintWriter();
         try {
             switch (cmd) {
+                case "help":
+                    onHelp();
+                    return 0;
                 case "start":
                 case "start-activity":
                     return runStartActivity(pw);
@@ -3699,14 +3702,11 @@
             if (foregroundActivities) {
                 try {
                     int prcState = mIam.getUidProcessState(uid, "android");
-                    ProcessRecord topApp = mInternal.getTopApp();
-                    if (topApp == null) {
-                        mPw.println("No top app found");
+
+                    if (prcState == ProcessStateEnum.TOP) {
+                        mPw.println("New foreground process: " + pid);
                     } else {
-                        int topPid = topApp.getPid();
-                        if (prcState == ProcessStateEnum.TOP && topPid == pid) {
-                            mPw.println("New foreground process: " + pid);
-                        }
+                        mPw.println("No top app found");
                     }
                     mPw.flush();
                 } catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/am/AppBatteryTracker.java b/services/core/java/com/android/server/am/AppBatteryTracker.java
index c2326f6..128bbdf 100644
--- a/services/core/java/com/android/server/am/AppBatteryTracker.java
+++ b/services/core/java/com/android/server/am/AppBatteryTracker.java
@@ -58,6 +58,7 @@
 import android.os.BatteryUsageStatsQuery;
 import android.os.PowerExemptionManager;
 import android.os.PowerExemptionManager.ReasonCode;
+import android.os.Process;
 import android.os.SystemClock;
 import android.os.UidBatteryConsumer;
 import android.os.UserHandle;
@@ -1975,15 +1976,15 @@
             if (!mBgCurrentDrainHighThresholdByBgLocation) {
                 return false;
             }
-            final AppRestrictionController controller = mTracker.mAppRestrictionController;
-            if (mInjector.getPermissionManagerServiceInternal().checkUidPermission(
-                    uid, ACCESS_BACKGROUND_LOCATION) == PERMISSION_GRANTED) {
+            if (mTracker.mContext.checkPermission(ACCESS_BACKGROUND_LOCATION,
+                    Process.INVALID_PID, uid) == PERMISSION_GRANTED) {
                 return true;
             }
             if (!mBgCurrentDrainEventDurationBasedThresholdEnabled) {
                 return false;
             }
             final long since = Math.max(0, now - window);
+            final AppRestrictionController controller = mTracker.mAppRestrictionController;
             final long locationDuration = controller.getForegroundServiceTotalDurationsSince(
                     uid, since, now, ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION);
             return locationDuration >= mBgCurrentDrainLocationMinDuration;
diff --git a/services/core/java/com/android/server/am/AppPermissionTracker.java b/services/core/java/com/android/server/am/AppPermissionTracker.java
index 722d0d4..18a9153 100644
--- a/services/core/java/com/android/server/am/AppPermissionTracker.java
+++ b/services/core/java/com/android/server/am/AppPermissionTracker.java
@@ -43,6 +43,7 @@
 import android.content.pm.PackageManagerInternal;
 import android.os.Handler;
 import android.os.Message;
+import android.os.Process;
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.UserHandle;
@@ -292,8 +293,8 @@
                 mPermissionGranted = true;
                 return;
             }
-            mPermissionGranted = mInjector.getPermissionManagerServiceInternal()
-                    .checkUidPermission(mUid, mPermission) == PERMISSION_GRANTED;
+            mPermissionGranted = mContext.checkPermission(mPermission, Process.INVALID_PID, mUid)
+                    == PERMISSION_GRANTED;
         }
 
         void updateAppOps() {
diff --git a/services/core/java/com/android/server/am/BaseAppStateTimeSlotEventsTracker.java b/services/core/java/com/android/server/am/BaseAppStateTimeSlotEventsTracker.java
index ff78355..6268082 100644
--- a/services/core/java/com/android/server/am/BaseAppStateTimeSlotEventsTracker.java
+++ b/services/core/java/com/android/server/am/BaseAppStateTimeSlotEventsTracker.java
@@ -338,7 +338,7 @@
                 pw.print(prefix);
                 pw.print(mKeyNumOfEventsThreshold);
                 pw.print('=');
-                pw.println(mDefaultNumOfEventsThreshold);
+                pw.println(mNumOfEventsThreshold);
             }
             pw.print(prefix);
             pw.print("event_time_slot_size=");
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index dc6f858..9026c20 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -17,7 +17,6 @@
 package com.android.server.am;
 
 import static android.Manifest.permission.BATTERY_STATS;
-import static android.Manifest.permission.BLUETOOTH_CONNECT;
 import static android.Manifest.permission.DEVICE_POWER;
 import static android.Manifest.permission.NETWORK_STACK;
 import static android.Manifest.permission.POWER_SAVER;
@@ -96,6 +95,8 @@
 import com.android.internal.app.IBatteryStats;
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.os.BinderCallsStats;
+import com.android.internal.os.CpuScalingPolicies;
+import com.android.internal.os.CpuScalingPolicyReader;
 import com.android.internal.os.PowerProfile;
 import com.android.internal.os.RailStats;
 import com.android.internal.os.RpmStats;
@@ -152,6 +153,7 @@
     private static IBatteryStats sService;
 
     private final PowerProfile mPowerProfile;
+    private final CpuScalingPolicies mCpuScalingPolicies;
     final BatteryStatsImpl mStats;
     final CpuWakeupStats mCpuWakeupStats;
     private final BatteryUsageStatsStore mBatteryUsageStatsStore;
@@ -368,14 +370,14 @@
         mHandler = new Handler(mHandlerThread.getLooper());
 
         mPowerProfile = new PowerProfile(context);
+        mCpuScalingPolicies = new CpuScalingPolicyReader().read();
 
         mStats = new BatteryStatsImpl(systemDir, handler, this,
-                this, mUserManagerUserInfoProvider);
+                this, mUserManagerUserInfoProvider, mPowerProfile, mCpuScalingPolicies);
         mWorker = new BatteryExternalStatsWorker(context, mStats);
         mStats.setExternalStatsSyncLocked(mWorker);
         mStats.setRadioScanningTimeoutLocked(mContext.getResources().getInteger(
                 com.android.internal.R.integer.config_radioScanningTimeout) * 1000L);
-        mStats.setPowerProfileLocked(mPowerProfile);
 
         final boolean resetOnUnplugHighBatteryLevel = context.getResources().getBoolean(
                 com.android.internal.R.bool.config_batteryStatsResetOnUnplugHighBatteryLevel);
@@ -2916,8 +2918,8 @@
                                 in.setDataPosition(0);
                                 BatteryStatsImpl checkinStats = new BatteryStatsImpl(
                                         null, mStats.mHandler, null, null,
-                                        mUserManagerUserInfoProvider);
-                                checkinStats.setPowerProfileLocked(mPowerProfile);
+                                        mUserManagerUserInfoProvider, mPowerProfile,
+                                        mCpuScalingPolicies);
                                 checkinStats.readSummaryFromParcel(in);
                                 in.recycle();
                                 checkinStats.dumpProtoLocked(
@@ -2957,8 +2959,8 @@
                                 in.setDataPosition(0);
                                 BatteryStatsImpl checkinStats = new BatteryStatsImpl(
                                         null, mStats.mHandler, null, null,
-                                        mUserManagerUserInfoProvider);
-                                checkinStats.setPowerProfileLocked(mPowerProfile);
+                                        mUserManagerUserInfoProvider, mPowerProfile,
+                                        mCpuScalingPolicies);
                                 checkinStats.readSummaryFromParcel(in);
                                 in.recycle();
                                 checkinStats.dumpCheckin(mContext, pw, apps, flags,
diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
index 0fcec6f..fc8175b 100644
--- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
@@ -1144,9 +1144,6 @@
             } else if (mProcessPersistent) {
                 mRunnableAt = runnableAt + constants.DELAY_PERSISTENT_PROC_MILLIS;
                 mRunnableAtReason = REASON_PERSISTENT;
-            } else if (UserHandle.isCore(uid)) {
-                mRunnableAt = runnableAt;
-                mRunnableAtReason = REASON_CORE_UID;
             } else if (mCountOrdered > 0) {
                 mRunnableAt = runnableAt;
                 mRunnableAtReason = REASON_CONTAINS_ORDERED;
@@ -1193,6 +1190,9 @@
                 // is already cached, they'll be deferred on the line above
                 mRunnableAt = runnableAt;
                 mRunnableAtReason = REASON_CONTAINS_RESULT_TO;
+            } else if (UserHandle.isCore(uid)) {
+                mRunnableAt = runnableAt;
+                mRunnableAtReason = REASON_CORE_UID;
             } else {
                 mRunnableAt = runnableAt + constants.DELAY_NORMAL_MILLIS;
                 mRunnableAtReason = REASON_NORMAL;
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index f420619..8d2edaa 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -249,6 +249,7 @@
     private static final int MSG_CHECK_HEALTH = 5;
     private static final int MSG_CHECK_PENDING_COLD_START_VALIDITY = 6;
     private static final int MSG_PROCESS_FREEZABLE_CHANGED = 7;
+    private static final int MSG_UID_STATE_CHANGED = 8;
 
     private void enqueueUpdateRunningList() {
         mLocalHandler.removeMessages(MSG_UPDATE_RUNNING_LIST);
@@ -295,6 +296,19 @@
                 }
                 return true;
             }
+            case MSG_UID_STATE_CHANGED: {
+                final int uid = (int) msg.obj;
+                final int procState = msg.arg1;
+                synchronized (mService) {
+                    if (procState == ActivityManager.PROCESS_STATE_TOP) {
+                        mUidForeground.put(uid, true);
+                    } else {
+                        mUidForeground.delete(uid);
+                    }
+                    refreshProcessQueuesLocked(uid);
+                }
+                return true;
+            }
         }
         return false;
     };
@@ -672,7 +686,7 @@
     @Override
     public void onProcessFreezableChangedLocked(@NonNull ProcessRecord app) {
         mLocalHandler.removeMessages(MSG_PROCESS_FREEZABLE_CHANGED, app);
-        mLocalHandler.sendMessage(mHandler.obtainMessage(MSG_PROCESS_FREEZABLE_CHANGED, app));
+        mLocalHandler.obtainMessage(MSG_PROCESS_FREEZABLE_CHANGED, app).sendToTarget();
     }
 
     @Override
@@ -1601,14 +1615,9 @@
             @Override
             public void onUidStateChanged(int uid, int procState, long procStateSeq,
                     int capability) {
-                synchronized (mService) {
-                    if (procState == ActivityManager.PROCESS_STATE_TOP) {
-                        mUidForeground.put(uid, true);
-                    } else {
-                        mUidForeground.delete(uid);
-                    }
-                    refreshProcessQueuesLocked(uid);
-                }
+                mLocalHandler.removeMessages(MSG_UID_STATE_CHANGED, uid);
+                mLocalHandler.obtainMessage(MSG_UID_STATE_CHANGED, procState, 0, uid)
+                        .sendToTarget();
             }
         }, ActivityManager.UID_OBSERVER_PROCSTATE,
                 ActivityManager.PROCESS_STATE_TOP, "android");
@@ -1716,13 +1725,15 @@
 
     @Override
     public boolean isDelayBehindServices() {
-        // TODO: implement
+        // Modern queue does not alter the broadcasts delivery behavior based on background
+        // services, so ignore.
         return false;
     }
 
     @Override
     public void backgroundServicesFinishedLocked(int userId) {
-        // TODO: implement
+        // Modern queue does not alter the broadcasts delivery behavior based on background
+        // services, so ignore.
     }
 
     private void checkHealth() {
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index e588a9e..cc9628f 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -241,7 +241,8 @@
     private static final int FREEZE_BINDER_TIMEOUT_MS = 100;
     private static final int FREEZE_DEADLOCK_TIMEOUT_MS = 1000;
 
-    @VisibleForTesting static final boolean ENABLE_FILE_COMPACT = false;
+    // If enabled, any compaction issued will apply to code mappings and share memory mappings.
+    @VisibleForTesting static final boolean ENABLE_SHARED_AND_CODE_COMPACT = false;
 
     // Defaults for phenotype flags.
     @VisibleForTesting static final boolean DEFAULT_USE_COMPACTION = true;
@@ -1594,15 +1595,22 @@
 
     void onWakefulnessChanged(int wakefulness) {
         if(wakefulness == PowerManagerInternal.WAKEFULNESS_AWAKE) {
-            // Remove any pending compaction we may have scheduled to happen while screen was off
-            Slog.e(TAG_AM, "Cancel pending or running compactions as system is awake");
-            cancelAllCompactions(CancelCompactReason.SCREEN_ON);
+            if (useCompaction()) {
+                // Remove any pending compaction we may have scheduled to happen while screen was
+                // off
+                cancelAllCompactions(CancelCompactReason.SCREEN_ON);
+            }
         }
     }
 
     void cancelAllCompactions(CancelCompactReason reason) {
         synchronized (mProcLock) {
             while(!mPendingCompactionProcesses.isEmpty()) {
+                if (DEBUG_COMPACTION) {
+                    Slog.e(TAG_AM,
+                            "Cancel pending compaction as system is awake for process="
+                                    + mPendingCompactionProcesses.get(0).processName);
+                }
                 cancelCompactionForProcess(mPendingCompactionProcesses.get(0), reason);
             }
             mPendingCompactionProcesses.clear();
@@ -1679,7 +1687,7 @@
             }
         }
 
-        if (!ENABLE_FILE_COMPACT) {
+        if (!ENABLE_SHARED_AND_CODE_COMPACT) {
             if (profile == CompactProfile.SOME) {
                 profile = CompactProfile.NONE;
             } else if (profile == CompactProfile.FULL) {
diff --git a/services/core/java/com/android/server/am/ComponentAliasResolver.java b/services/core/java/com/android/server/am/ComponentAliasResolver.java
index f9eaf02..6e93ce4 100644
--- a/services/core/java/com/android/server/am/ComponentAliasResolver.java
+++ b/services/core/java/com/android/server/am/ComponentAliasResolver.java
@@ -162,7 +162,7 @@
                     // avoid deadlocks.
                     if (enabled) {
                         mPackageMonitor.register(mAm.mContext, UserHandle.ALL,
-                                /* externalStorage= */ false, BackgroundThread.getHandler());
+                                BackgroundThread.getHandler());
                     } else {
                         mPackageMonitor.unregister();
                     }
diff --git a/services/core/java/com/android/server/am/ContentProviderHelper.java b/services/core/java/com/android/server/am/ContentProviderHelper.java
index 3f7d8ba..e26ee9c 100644
--- a/services/core/java/com/android/server/am/ContentProviderHelper.java
+++ b/services/core/java/com/android/server/am/ContentProviderHelper.java
@@ -96,7 +96,9 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 
 /**
@@ -116,6 +118,8 @@
     private final ProviderMap mProviderMap;
     private boolean mSystemProvidersInstalled;
 
+    private final Map<String, Boolean> mCloneProfileAuthorityRedirectionCache = new HashMap<>();
+
     ContentProviderHelper(ActivityManagerService service, boolean createProviderMap) {
         mService = service;
         mProviderMap = createProviderMap ? new ProviderMap(mService) : null;
@@ -201,9 +205,24 @@
             final UserProperties userProps = umInternal.getUserProperties(userId);
             final boolean isMediaSharedWithParent =
                     userProps != null && userProps.isMediaSharedWithParent();
-            if (!isAuthorityRedirectedForCloneProfile(name) || !isMediaSharedWithParent) {
+            if (!isAuthorityRedirectedForCloneProfileCached(name) || !isMediaSharedWithParent) {
                 // First check if this content provider has been published...
                 cpr = mProviderMap.getProviderByName(name, userId);
+                // In case we are on media authority and callingUid is cloned app asking to access
+                // the contentProvider of user 0 by specifying content as
+                // content://<parent-id>@media/external/file, we skip checkUser.
+                if (isAuthorityRedirectedForCloneProfileCached(name)) {
+                    final int callingUserId = UserHandle.getUserId(callingUid);
+                    final UserProperties userPropsCallingUser =
+                            umInternal.getUserProperties(callingUserId);
+                    final boolean isMediaSharedWithParentForCallingUser =
+                            userPropsCallingUser != null
+                                    && userPropsCallingUser.isMediaSharedWithParent();
+                    if (isMediaSharedWithParentForCallingUser
+                            && umInternal.getProfileParentId(callingUserId) == userId) {
+                        checkCrossUser = false;
+                    }
+                }
             }
             // If that didn't work, check if it exists for user 0 and then
             // verify that it's a singleton provider before using it.
@@ -218,7 +237,7 @@
                                         r == null ? callingUid : r.uid, cpi.applicationInfo.uid)) {
                         userId = UserHandle.USER_SYSTEM;
                         checkCrossUser = false;
-                    } else if (isAuthorityRedirectedForCloneProfile(name)) {
+                    } else if (isAuthorityRedirectedForCloneProfileCached(name)) {
                         if (isMediaSharedWithParent) {
                             userId = umInternal.getProfileParentId(userId);
                             checkCrossUser = false;
@@ -256,7 +275,6 @@
                 if (r != null && cpr.canRunHere(r)) {
                     checkAssociationAndPermissionLocked(r, cpi, callingUid, userId, checkCrossUser,
                             cpr.name.flattenToShortString(), startTime);
-                    enforceContentProviderRestrictionsForSdkSandbox(cpi);
 
                     // This provider has been published or is in the process
                     // of being published...  but it is also allowed to run
@@ -457,7 +475,6 @@
                     // info and allow the caller to instantiate it.  Only do
                     // this if the provider is the same user as the caller's
                     // process, or can run as root (so can be in any process).
-                    enforceContentProviderRestrictionsForSdkSandbox(cpi);
                     return cpr.newHolder(null, true);
                 }
 
@@ -609,8 +626,6 @@
                 // Return a holder instance even if we are waiting for the publishing of the
                 // provider, client will check for the holder.provider to see if it needs to wait
                 // for it.
-                //todo(b/265965249) Need to perform cleanup before calling enforce method here
-                enforceContentProviderRestrictionsForSdkSandbox(cpi);
                 return cpr.newHolder(conn, false);
             }
         }
@@ -672,7 +687,6 @@
                     + " caller=" + callerName + "/" + Binder.getCallingUid());
             return null;
         }
-        enforceContentProviderRestrictionsForSdkSandbox(cpi);
         return cpr.newHolder(conn, false);
     }
 
@@ -1078,7 +1092,7 @@
             return false;
         }
 
-        if (isAuthorityRedirectedForCloneProfile(holder.info.authority)
+        if (isAuthorityRedirectedForCloneProfileCached(holder.info.authority)
                 && resolveParentUserIdForCloneProfile(userId) != userId) {
             // Since clone profile shares certain providers with its parent and the access is
             // re-directed as well, the holder may not actually be installed on the clone profile.
@@ -1113,7 +1127,7 @@
             userId = UserHandle.getCallingUserId();
         }
 
-        if (isAuthorityRedirectedForCloneProfile(authority)) {
+        if (isAuthorityRedirectedForCloneProfileCached(authority)) {
             UserManagerInternal umInternal = LocalServices.getService(UserManagerInternal.class);
             UserInfo userInfo = umInternal.getUserInfo(userId);
 
@@ -1150,7 +1164,6 @@
             appName = r.toString();
         }
 
-        enforceContentProviderRestrictionsForSdkSandbox(cpi);
         return checkContentProviderPermission(cpi, callingPid, Binder.getCallingUid(),
                 userId, checkUser, appName);
     }
@@ -1524,11 +1537,17 @@
 
     /**
      * Check if {@link ProcessRecord} has a possible chance at accessing the
-     * given {@link ProviderInfo}. Final permission checking is always done
+     * given {@link ProviderInfo}. First permission checking is for enforcing
+     * ContentProvider Restrictions from SdkSandboxManager.
+     * Final permission checking is always done
      * in {@link ContentProvider}.
      */
     private String checkContentProviderPermission(ProviderInfo cpi, int callingPid, int callingUid,
             int userId, boolean checkUser, String appName) {
+        if (!canAccessContentProviderFromSdkSandbox(cpi, callingUid)) {
+            return "ContentProvider access not allowed from sdk sandbox UID. "
+                    + "ProviderInfo: " + cpi.toString();
+        }
         boolean checkedGrants = false;
         if (checkUser) {
             // Looking for cross-user grants before enforcing the typical cross-users permissions
@@ -1918,11 +1937,10 @@
         }
     }
 
-    // Binder.clearCallingIdentity() shouldn't be called before this method
-    // as Binder should have its original callingUid for the check
-    private void enforceContentProviderRestrictionsForSdkSandbox(ProviderInfo cpi) {
-        if (!Process.isSdkSandboxUid(Binder.getCallingUid())) {
-            return;
+    private boolean canAccessContentProviderFromSdkSandbox(ProviderInfo cpi,
+                                                                    int callingUid) {
+        if (!Process.isSdkSandboxUid(callingUid)) {
+            return true;
         }
         final SdkSandboxManagerLocal sdkSandboxManagerLocal =
                 LocalManagerRegistry.getManager(SdkSandboxManagerLocal.class);
@@ -1931,11 +1949,7 @@
                     + "when checking whether SDK sandbox uid may "
                     + "access the contentprovider.");
         }
-        if (!sdkSandboxManagerLocal
-                .canAccessContentProviderFromSdkSandbox(cpi)) {
-            throw new SecurityException(
-                    "SDK sandbox uid may not access contentprovider " + cpi.name);
-        }
+        return sdkSandboxManagerLocal.canAccessContentProviderFromSdkSandbox(cpi);
     }
 
     /**
@@ -1959,4 +1973,14 @@
             String[] args) {
         return mProviderMap.dumpProviderProto(fd, pw, name, args);
     }
+
+    private Boolean isAuthorityRedirectedForCloneProfileCached(String auth) {
+        if (mCloneProfileAuthorityRedirectionCache.containsKey(auth)) {
+            return mCloneProfileAuthorityRedirectionCache.get(auth);
+        } else {
+            boolean isAuthRedirected = isAuthorityRedirectedForCloneProfile(auth);
+            mCloneProfileAuthorityRedirectionCache.put(auth, isAuthRedirected);
+            return isAuthRedirected;
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/am/CoreSettingsObserver.java b/services/core/java/com/android/server/am/CoreSettingsObserver.java
index 7482e64..9fdb833 100644
--- a/services/core/java/com/android/server/am/CoreSettingsObserver.java
+++ b/services/core/java/com/android/server/am/CoreSettingsObserver.java
@@ -78,6 +78,8 @@
     static {
         sSecureSettingToTypeMap.put(Settings.Secure.LONG_PRESS_TIMEOUT, int.class);
         sSecureSettingToTypeMap.put(Settings.Secure.MULTI_PRESS_TIMEOUT, int.class);
+        sSecureSettingToTypeMap.put(Settings.Secure.KEY_REPEAT_TIMEOUT_MS, int.class);
+        sSecureSettingToTypeMap.put(Settings.Secure.KEY_REPEAT_DELAY_MS, int.class);
         // add other secure settings here...
 
         sSystemSettingToTypeMap.put(Settings.System.TIME_12_24, String.class);
diff --git a/services/core/java/com/android/server/am/DropboxRateLimiter.java b/services/core/java/com/android/server/am/DropboxRateLimiter.java
index b5c7215..003e614 100644
--- a/services/core/java/com/android/server/am/DropboxRateLimiter.java
+++ b/services/core/java/com/android/server/am/DropboxRateLimiter.java
@@ -17,6 +17,7 @@
 package com.android.server.am;
 
 import android.os.SystemClock;
+import android.provider.DeviceConfig;
 import android.text.format.DateUtils;
 import android.util.ArrayMap;
 import android.util.Slog;
@@ -30,17 +31,26 @@
     // After RATE_LIMIT_ALLOWED_ENTRIES have been collected (for a single breakdown of
     // process/eventType) further entries will be rejected until RATE_LIMIT_BUFFER_DURATION has
     // elapsed, after which the current count for this breakdown will be reset.
-    private static final long RATE_LIMIT_BUFFER_DURATION = 10 * DateUtils.MINUTE_IN_MILLIS;
+    private static final long RATE_LIMIT_BUFFER_DURATION_DEFAULT = 10 * DateUtils.MINUTE_IN_MILLIS;
     // Indicated how many buffer durations to wait before the rate limit buffer will be cleared.
     // E.g. if set to 3 will wait 3xRATE_LIMIT_BUFFER_DURATION before clearing the buffer.
-    private static final long RATE_LIMIT_BUFFER_EXPIRY_FACTOR = 3;
+    private static final long RATE_LIMIT_BUFFER_EXPIRY_FACTOR_DEFAULT = 3;
     // The number of entries to keep per breakdown of process/eventType.
-    private static final int RATE_LIMIT_ALLOWED_ENTRIES = 6;
+    private static final int RATE_LIMIT_ALLOWED_ENTRIES_DEFAULT = 6;
 
     // If a process is rate limited twice in a row we consider it crash-looping and rate limit it
     // more aggressively.
-    private static final int STRICT_RATE_LIMIT_ALLOWED_ENTRIES = 1;
-    private static final long STRICT_RATE_LIMIT_BUFFER_DURATION = 20 * DateUtils.MINUTE_IN_MILLIS;
+    private static final int STRICT_RATE_LIMIT_ALLOWED_ENTRIES_DEFAULT = 1;
+    private static final long STRICT_RATE_LIMIT_BUFFER_DURATION_DEFAULT =
+            20 * DateUtils.MINUTE_IN_MILLIS;
+
+    private static final String FLAG_NAMESPACE = "dropbox";
+
+    private long mRateLimitBufferDuration;
+    private long mRateLimitBufferExpiryFactor;
+    private int mRateLimitAllowedEntries;
+    private int mStrictRatelimitAllowedEntries;
+    private long mStrictRateLimitBufferDuration;
 
     @GuardedBy("mErrorClusterRecords")
     private final ArrayMap<String, ErrorRecord> mErrorClusterRecords = new ArrayMap<>();
@@ -54,6 +64,36 @@
 
     public DropboxRateLimiter(Clock clock) {
         mClock = clock;
+
+        mRateLimitBufferDuration = RATE_LIMIT_BUFFER_DURATION_DEFAULT;
+        mRateLimitBufferExpiryFactor = RATE_LIMIT_BUFFER_EXPIRY_FACTOR_DEFAULT;
+        mRateLimitAllowedEntries = RATE_LIMIT_ALLOWED_ENTRIES_DEFAULT;
+        mStrictRatelimitAllowedEntries = STRICT_RATE_LIMIT_ALLOWED_ENTRIES_DEFAULT;
+        mStrictRateLimitBufferDuration = STRICT_RATE_LIMIT_BUFFER_DURATION_DEFAULT;
+    }
+
+    /** Initializes the rate limiter parameters from flags. */
+    public void init() {
+        mRateLimitBufferDuration = DeviceConfig.getLong(
+            FLAG_NAMESPACE,
+            "DropboxRateLimiter__rate_limit_buffer_duration",
+            RATE_LIMIT_BUFFER_DURATION_DEFAULT);
+        mRateLimitBufferExpiryFactor = DeviceConfig.getLong(
+            FLAG_NAMESPACE,
+            "DropboxRateLimiter__rate_limit_buffer_expiry_factor",
+            RATE_LIMIT_BUFFER_EXPIRY_FACTOR_DEFAULT);
+        mRateLimitAllowedEntries = DeviceConfig.getInt(
+            FLAG_NAMESPACE,
+            "DropboxRateLimiter__rate_limit_allowed_entries",
+            RATE_LIMIT_ALLOWED_ENTRIES_DEFAULT);
+        mStrictRatelimitAllowedEntries = DeviceConfig.getInt(
+            FLAG_NAMESPACE,
+            "DropboxRateLimiter__strict_rate_limit_allowed_entries",
+            STRICT_RATE_LIMIT_ALLOWED_ENTRIES_DEFAULT);
+        mStrictRateLimitBufferDuration = DeviceConfig.getLong(
+            FLAG_NAMESPACE,
+            "DropboxRateLimiter__strict_rate_limit_buffer_duration",
+            STRICT_RATE_LIMIT_BUFFER_DURATION_DEFAULT);
     }
 
     /** The interface clock to use for tracking the time elapsed. */
@@ -116,7 +156,7 @@
 
     private void maybeRemoveExpiredRecords(long currentTime) {
         if (currentTime - mLastMapCleanUp
-                <= RATE_LIMIT_BUFFER_EXPIRY_FACTOR * RATE_LIMIT_BUFFER_DURATION) {
+                <= mRateLimitBufferExpiryFactor * mRateLimitBufferDuration) {
             return;
         }
 
@@ -219,15 +259,15 @@
         }
 
         public int getAllowedEntries() {
-            return isRepeated() ? STRICT_RATE_LIMIT_ALLOWED_ENTRIES : RATE_LIMIT_ALLOWED_ENTRIES;
+            return isRepeated() ? mStrictRatelimitAllowedEntries : mRateLimitAllowedEntries;
         }
 
         public long getBufferDuration() {
-            return isRepeated() ? STRICT_RATE_LIMIT_BUFFER_DURATION : RATE_LIMIT_BUFFER_DURATION;
+            return isRepeated() ? mStrictRateLimitBufferDuration : mRateLimitBufferDuration;
         }
 
         public boolean hasExpired(long currentTime) {
-            long bufferExpiry = RATE_LIMIT_BUFFER_EXPIRY_FACTOR * getBufferDuration();
+            long bufferExpiry = mRateLimitBufferExpiryFactor * getBufferDuration();
             return currentTime - mStartTime > bufferExpiry;
         }
     }
diff --git a/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java b/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java
index 38e7371..4f5b5e1 100644
--- a/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java
+++ b/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java
@@ -479,8 +479,8 @@
                 r.appInfo.uid,
                 r.shortInstanceName,
                 fgsState, // FGS State
-                r.mAllowWhileInUsePermissionInFgs, // allowWhileInUsePermissionInFgs
-                r.mAllowStartForeground, // fgsStartReasonCode
+                r.isFgsAllowedWIU(), // allowWhileInUsePermissionInFgs
+                r.getFgsAllowStart(), // fgsStartReasonCode
                 r.appInfo.targetSdkVersion,
                 r.mRecentCallingUid,
                 0, // callerTargetSdkVersion
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index f21ad22..459c6ff 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -1293,12 +1293,19 @@
         for (int i = numLru - 1; i >= 0; i--) {
             ProcessRecord app = lruList.get(i);
             final ProcessStateRecord state = app.mState;
-            if (!app.isKilledByAm() && app.getThread() != null && !app.isPendingFinishAttach()) {
+            if (!app.isKilledByAm() && app.getThread() != null) {
                 // We don't need to apply the update for the process which didn't get computed
                 if (state.getCompletedAdjSeq() == mAdjSeq) {
                     applyOomAdjLSP(app, true, now, nowElapsed, oomAdjReason);
                 }
 
+                if (app.isPendingFinishAttach()) {
+                    // Avoid trimming processes that are still initializing. If they aren't
+                    // hosting any components yet because they may be unfairly killed.
+                    // We however apply the oom scores set at #setAttachingProcessStatesLSP.
+                    continue;
+                }
+
                 final ProcessServiceRecord psr = app.mServices;
                 // Count the number of process types.
                 switch (state.getCurProcState()) {
@@ -1706,6 +1713,11 @@
         }
     }
 
+    private boolean isScreenOnOrAnimatingLocked(ProcessStateRecord state) {
+        return mService.mWakefulness.get() == PowerManagerInternal.WAKEFULNESS_AWAKE
+                || state.isRunningRemoteAnimation();
+    }
+
     @GuardedBy({"mService", "mProcLock"})
     private boolean computeOomAdjLSP(ProcessRecord app, int cachedAdj,
             ProcessRecord topApp, boolean doingAll, long now, boolean cycleReEval,
@@ -1787,8 +1799,7 @@
                 state.setSystemNoUi(false);
             }
             if (!state.isSystemNoUi()) {
-                if (mService.mWakefulness.get() == PowerManagerInternal.WAKEFULNESS_AWAKE
-                        || state.isRunningRemoteAnimation()) {
+                if (isScreenOnOrAnimatingLocked(state)) {
                     // screen on or animating, promote UI
                     state.setCurProcState(ActivityManager.PROCESS_STATE_PERSISTENT_UI);
                     state.setCurrentSchedulingGroup(SCHED_GROUP_TOP_APP);
@@ -2211,7 +2222,7 @@
 
             if (s.isForeground) {
                 final int fgsType = s.foregroundServiceType;
-                if (s.mAllowWhileInUsePermissionInFgs) {
+                if (s.isFgsAllowedWIU()) {
                     capabilityFromFGS |=
                             (fgsType & FOREGROUND_SERVICE_TYPE_LOCATION)
                                     != 0 ? PROCESS_CAPABILITY_FOREGROUND_LOCATION : 0;
@@ -2806,6 +2817,18 @@
             capability &= ~PROCESS_CAPABILITY_BFSL;
         }
 
+
+        if (app.isPendingFinishAttach()) {
+            // If the app is still starting up. We reset the computations to the
+            // hardcoded values in setAttachingProcessStatesLSP. This ensures that the app keeps
+            // hard-coded default 'startup' oom scores while starting up. When it finishes startup,
+            // we'll recompute oom scores based on it's actual hosted compoenents.
+            setAttachingProcessStatesLSP(app);
+            state.setAdjSeq(mAdjSeq);
+            state.setCompletedAdjSeq(state.getAdjSeq());
+            return false;
+        }
+
         // Do final modification to adj.  Everything we do between here and applying
         // the final setAdj must be done in this function, because we will also use
         // it when computing the final cached adj later.  Note that we don't need to
@@ -3243,8 +3266,11 @@
     }
 
     @GuardedBy({"mService", "mProcLock"})
-    void setAttachingSchedGroupLSP(ProcessRecord app) {
+    void setAttachingProcessStatesLSP(ProcessRecord app) {
         int initialSchedGroup = SCHED_GROUP_DEFAULT;
+        int initialProcState = PROCESS_STATE_CACHED_EMPTY;
+        int initialCapability =  PROCESS_CAPABILITY_NONE;
+        boolean initialCached = true;
         final ProcessStateRecord state = app.mState;
         // If the process has been marked as foreground, it is starting as the top app (with
         // Zygote#START_AS_TOP_APP_ARG), so boost the thread priority of its default UI thread.
@@ -3259,14 +3285,27 @@
                 } else {
                     setThreadPriority(app.getPid(), THREAD_PRIORITY_TOP_APP_BOOST);
                 }
-                initialSchedGroup = SCHED_GROUP_TOP_APP;
+                if (isScreenOnOrAnimatingLocked(state)) {
+                    initialSchedGroup = SCHED_GROUP_TOP_APP;
+                    initialProcState = PROCESS_STATE_TOP;
+                }
+                initialCapability = PROCESS_CAPABILITY_ALL;
+                initialCached = false;
             } catch (Exception e) {
                 Slog.w(TAG, "Failed to pre-set top priority to " + app + " " + e);
             }
         }
 
-        state.setSetSchedGroup(initialSchedGroup);
         state.setCurrentSchedulingGroup(initialSchedGroup);
+        state.setCurProcState(initialProcState);
+        state.setCurRawProcState(initialProcState);
+        state.setCurCapability(initialCapability);
+        state.setCached(initialCached);
+
+        state.setCurAdj(ProcessList.FOREGROUND_APP_ADJ);
+        state.setCurRawAdj(ProcessList.FOREGROUND_APP_ADJ);
+        state.setForcingToImportant(null);
+        state.setHasShownUi(false);
     }
 
     // ONLY used for unit testing in OomAdjusterTests.java
diff --git a/services/core/java/com/android/server/am/OomAdjuster.md b/services/core/java/com/android/server/am/OomAdjuster.md
index febc37b..16091d1 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.md
+++ b/services/core/java/com/android/server/am/OomAdjuster.md
@@ -17,10 +17,10 @@
 
 ## Purpose of Oom Adjuster
 
-The Android OS runs with limited hardware resources, i.e. CPU/RAM/Power. To strive for the better performance, Oom Ajuster is introduced to tweak the following 3 major factors:
+The Android OS runs with limited hardware resources, i.e. CPU/RAM/Power. To strive for the better performance, Oom Adjuster is introduced to tweak the following 3 major factors:
 
  * Process State
-   * Wildly used by the System Server, i.e., determine if it's foreground or not, change the GC behavior, etc.
+   * Widely used by the System Server, i.e., determine if it's foreground or not, change the GC behavior, etc.
    * Defined in `ActivityManager#PROCESS_STATE_*`
  * Oom Adj score
    * Used by the lmkd to determine which process should be expunged on memory pressure.
@@ -31,36 +31,36 @@
 
 ## Process Capabilities
 
-Besides the above 3 major factors, Android R introduced the Process Capabilities `ActivityManager#PROCESS_CAPABILITY_*`.  It's a new attribute to process record, mainly designed for supporting the "while-in-use" permission model - in additional to the traditional Android permissions, wheather or not a process has access to a given API, will be guarded by its current process state as well. The OomAdjuster will compute the process capabilities during updating the oom adj. Meanwhile, the flag `ActivityManager#BIND_INCLUDE_CAPABILITIES` enables to possiblity to "transfer" the capability from a client process to the service process it binds to.
+Besides the above 3 major factors, Android R introduced the Process Capabilities `ActivityManager#PROCESS_CAPABILITY_*`.  It's a new attribute to process record, mainly designed for supporting the "while-in-use" permission model - in addition to the traditional Android permissions, whether or not a process has access to a given API, will be guarded by its current process state as well. The OomAdjuster will compute the process capabilities during updating the oom adj. Meanwhile, the flag `ActivityManager#BIND_INCLUDE_CAPABILITIES` enables the possibility to "transfer" the capability from a client process to the service process it binds to.
 
 ## Rationale of Oom Adjuster
 
-System server keeps a list of recent used app processes. Given the 4 types of entities that an Android processes could have: Activity, Service, Content Provider and Broadcast Receiver, the System Server has to adjust the above 3 factors to give the users the best performance according to the states of the entities. A typical case would be that: foreground app A binds into a background service B in order to serve the user, in the case of memory pressure, the background service B should be avoided from being expunged since it would result user-perceptible interruption of service. The Oom Adjuster is to tweak the aforementioned 3 factors for those app processes.
+System server keeps a list of recent used app processes. Given the 4 types of entities that an Android processes could have: Activity, Service, Content Provider and Broadcast Receiver, the System Server has to adjust the above 3 factors to give the users the best performance according to the states of the entities. A typical case would be that: foreground app A binds into a background service B in order to serve the user, in the case of memory pressure, the background service B should be avoided from being expunged since it would result in user-perceptible interruption of service. The Oom Adjuster is to tweak the aforementioned 3 factors for those app processes.
 
 The timing of updating the Oom Adj score is vital: assume a camera process in background gets launched into foreground, launching camera typically incurs high memory pressure, which could incur low memory kills - if the camera process isn't moved out of the background adj group, it could get killed by lmkd. Therefore the updates have to be called pretty frequently: in case there is an activity start, service binding, etc.
 
 The update procedure basically consists of 3 parts:
   * Find out the process record to be updated
-    * There are two categories of updateOomAdjLocked: one with the target process record to be updated, while the other one is to update all process record.
+    * There are two categories of updateOomAdjLocked: one with the target process record to be updated, while the other one is to update all process records.
     * Besides that, while computing the Oom Aj score, the clients of service connections or content providers of the present process record, which forms a process dependency graph actually, will be evaluated as well.
-    * Starting from Android R, when updating for a specific process record, an optimization is made that, only the reachable process records starting from this process record in the process dependency graph, will be re-evaluated.
+    * Starting from Android R, when updating a specific process record, an optimization is made that only the reachable process records starting from this process record in the process dependency graph will be re-evaluated.
     * The `cached` Oom Adj scores are grouped in `bucket`, which is used in the isolated processes: they could be correlated - assume one isolated Chrome process is at Oom Adj score 920 and another one is 980; the later one could get expunged much earlier than the former one, which doesn't make sense; grouping them would be a big relief for this case.
   * Compute Oom Adj score
     * This procedure returns true if there is a score change, false if there is no.
     * The curAdj field in the process record is used as an intermediate value during the computation.
     * Initialize the Process State to `PROCESS_STATE_CACHED_EMPTY`, which is the lowest importance.
     * Calculate the scores based on various factors:
-      * If it's not allowed to be lower than `ProcessList#FOREGROUND_APP_ADJ`, meaning it's propbably a persistent process, there is no too much to do here.
+      * If it's not allowed to be lower than `ProcessList#FOREGROUND_APP_ADJ`, meaning it's probably a persistent process, there is no too much to do here.
       * Exame if the process is the top app, running remote animation, running instrumentation, receiving broadcast, executing services, running on top but sleeping (screen off), update the intermediate values.
       * Ask Window Manager (yes, ActivityTaskManager is with WindowManager now) to tell each activity's visibility information.
-      * Check if the process has recent tasks, check if it's hosting a foreground service, overlay UI, toast etc. Note for the foreground service, if it was in foreground status, allow it to stay in higher rank in memory for a while: Assuming a camera captureing case, where the camera app is still processing the picture while being switched out of foreground - keep it stay in higher rank in memory would ensure the pictures are persisted correctly.
-      * Check if the process is the heavy weight process, whose launching/exiting would be slow and it's better to keep it in the memory. Note there should be only one heavy weight process across the system.
+      * Check if the process has recent tasks, check if it's hosting a foreground service, overlay UI, toast etc. Note for the foreground service, if it was in foreground status, allow it to stay in higher rank in memory for a while: Assuming a camera capturing case, where the camera app is still processing the picture while being switched out of foreground - keep it stay in higher rank in memory would ensure the pictures are persisted correctly.
+      * Check if the process is the heavyweight process, whose launching/exiting would be slow and it's better to keep it in the memory. Note there should be only one heavyweight process across the system.
       * For sure the Home process shouldn't be expunged frequently as well.
       * The next two factors are either it was the previous process with visible UI to the user, or it's a backup agent.
       * And then it goes to the massive searches against the service connections and the content providers, each of the clients will be evaluated, and the Oom Adj score could get updated according to its clients' scores. However there are a bunch of service binding flags which could impact the result:
         * Below table captures the results with given various service binding states:
 
-        | Conditon #1                     | Condition #2                                               | Condition #3                                 | Condition #4                                      | Result                   |
+        | Condition #1                    | Condition #2                                               | Condition #3                                 | Condition #4                                      | Result                   |
         |---------------------------------|------------------------------------------------------------|----------------------------------------------|---------------------------------------------------|--------------------------|
         | `BIND_WAIVE_PRIORITY` not set   | `BIND_ALLOW_OOM_MANAGEMENT` set                            | Shown UI && Not Home                         |                                                   | Use the app's own Adj    |
         |                                 |                                                            | Inactive for a while                         |                                                   | Use the app's own Adj    |
@@ -85,7 +85,7 @@
         |                                 |                                                            |                                              | `BIND_IMPORTANT` is NOT set                       | Sched = default          |
         * Below table captures the results with given various content provider binding states:
 
-        | Conditon #1                     | Condition #2                                               | Condition #3                                 | Result                   |
+        | Condition #1                    | Condition #2                                               | Condition #3                                 | Result                   |
         |---------------------------------|------------------------------------------------------------|----------------------------------------------|--------------------------|
         | Client's process state >= cached|                                                            |                                              | Client ProcState = empty |
         | Adj > Client Adj                | Not shown UI or is Home, or Client's Adj <= perceptible    | Client's Adj <= foreground Adj               | Try foreground Adj       |
@@ -94,11 +94,11 @@
         |                                 | Client's process state is NOT top                          |                                              | ProcState = bound fg svc |
         | Has external dependencies       | Adj > fg app                                               |                                              | adj = fg app             |
         |                                 | Process state > important foreground                       |                                              | ProcState = important fg |
-        | Still within retain time        | Adj > previous app Adj                                     |                                              | adj = previuos app adj   |
+        | Still within retain time        | Adj > previous app Adj                                     |                                              | adj = previous app adj   |
         |                                 | Process state > last activity                              |                                              | ProcState = last activity|
         * Some additional tweaks after the above ones:
 
-        | Conditon #1                     | Condition #2                                               | Condition #3                                 | Result                             |
+        | Condition #1                    | Condition #2                                               | Condition #3                                 | Result                             |
         |---------------------------------|------------------------------------------------------------|----------------------------------------------|------------------------------------|
         | Process state >= cached empty   | Has client activities                                      |                                              | ProcState = cached activity client |
         |                                 | treat like activity (IME)                                  |                                              | ProcState = cached activity        |
@@ -108,7 +108,7 @@
 
 ## Cycles, Cycles, Cycles
 
-Another interesting aspect of the Oom Adjuster is the cycles of the dependencies. A simple example would be like below illustration, process A is hosting a service which is bound by process B; meanwhile the process B is hosting a service which is bound by process A.
+Another interesting aspect of the Oom Adjuster is the cycles of the dependencies. A simple example would be like the illustration below, process A is hosting a service which is bound by process B; meanwhile process B is hosting a service which is bound by process A.
 <pre>
   +-------------+           +-------------+
   |  Process A  | <-------- |  Process B  |
@@ -116,7 +116,7 @@
   +-------------+           +-------------+
 </pre>
 
-There could be very complicated cases, which could involve multiple cycles, and in the dependency graph, each of the process record node could have different importance.
+There could be very complicated cases, which could involve multiple cycles, and in the dependency graph, each of the process record nodes could have different importance.
 <pre>
   +-------------+           +-------------+           +-------------+           +-------------+           +-------------+
   |  Process D  | --------> |  Process A  | <-------- |  Process B  | <-------- |  Process C  | <-------- |  Process A  |
@@ -124,9 +124,9 @@
   +-------------+           +-------------+           +-------------+           +-------------+           +-------------+
 </pre>
 
-The Oom Adjuster maintains a global sequence ID `mAdjSeq` to track the current Oom Adjuster calling. And each of the process record has a field to track in which sequence the process record is evaluated. If during the Oom Adj computation, a process record with sequence ID as same as the current global sequence ID, this would mean that a cycle is detected; in this case:
+The Oom Adjuster maintains a global sequence ID `mAdjSeq` to track the current Oom Adjuster calling. And each of the process records has a field to track in which sequence the process record is evaluated. If during the Oom Adj computation, a process record with sequence ID as same as the current global sequence ID, this would mean that a cycle is detected; in this case:
   * Decrement the sequence ID of each process if there is a cycle.
-  * Re-evaluate each of the process record within the cycle until nothing was promoted.
+  * Re-evaluate each of the process records within the cycle until nothing was promoted.
   * Iterate the processes from least important to most important ones.
   * A maximum retries of 10 is enforced, while in practice, the maximum retries could reach only 2 to 3.
 
diff --git a/services/core/java/com/android/server/am/PendingIntentController.java b/services/core/java/com/android/server/am/PendingIntentController.java
index 7ee96aa..a20623c 100644
--- a/services/core/java/com/android/server/am/PendingIntentController.java
+++ b/services/core/java/com/android/server/am/PendingIntentController.java
@@ -26,6 +26,7 @@
 import android.annotation.Nullable;
 import android.app.Activity;
 import android.app.ActivityManagerInternal;
+import android.app.ActivityOptions;
 import android.app.AppGlobals;
 import android.app.PendingIntent;
 import android.app.PendingIntentStats;
@@ -126,6 +127,18 @@
                 }
             }
             Bundle.setDefusable(bOptions, true);
+            ActivityOptions opts = ActivityOptions.fromBundle(bOptions);
+            if (opts != null && opts.getPendingIntentBackgroundActivityStartMode()
+                    != ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED) {
+                Slog.wtf(TAG, "Resetting option setPendingIntentBackgroundActivityStartMode("
+                        + opts.getPendingIntentBackgroundActivityStartMode()
+                        + ") to SYSTEM_DEFINED from the options provided by the pending "
+                        + "intent creator ("
+                        + packageName
+                        + ") because this option is meant for the pending intent sender");
+                opts.setPendingIntentBackgroundActivityStartMode(
+                        ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED);
+            }
 
             final boolean noCreate = (flags & PendingIntent.FLAG_NO_CREATE) != 0;
             final boolean cancelCurrent = (flags & PendingIntent.FLAG_CANCEL_CURRENT) != 0;
@@ -135,7 +148,7 @@
 
             PendingIntentRecord.Key key = new PendingIntentRecord.Key(type, packageName, featureId,
                     token, resultWho, requestCode, intents, resolvedTypes, flags,
-                    SafeActivityOptions.fromBundle(bOptions), userId);
+                    new SafeActivityOptions(opts), userId);
             WeakReference<PendingIntentRecord> ref;
             ref = mIntentSenderRecords.get(key);
             PendingIntentRecord rec = ref != null ? ref.get() : null;
diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java
index 202d407..79292fe 100644
--- a/services/core/java/com/android/server/am/PendingIntentRecord.java
+++ b/services/core/java/com/android/server/am/PendingIntentRecord.java
@@ -68,7 +68,6 @@
     @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
     @Overridable
     private static final long DEFAULT_RESCIND_BAL_PRIVILEGES_FROM_PENDING_INTENT_SENDER = 244637991;
-
     public static final int FLAG_ACTIVITY_SENDER = 1 << 0;
     public static final int FLAG_BROADCAST_SENDER = 1 << 1;
     public static final int FLAG_SERVICE_SENDER = 1 << 2;
@@ -457,6 +456,20 @@
             // can specify a consistent launch mode even if the PendingIntent is immutable
             final ActivityOptions opts = ActivityOptions.fromBundle(options);
             if (opts != null) {
+                if (opts.getPendingIntentCreatorBackgroundActivityStartMode()
+                        != ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED) {
+                    Slog.wtf(TAG,
+                            "Resetting option "
+                                    + "setPendingIntentCreatorBackgroundActivityStartMode("
+                                    + opts.getPendingIntentCreatorBackgroundActivityStartMode()
+                                    + ") to SYSTEM_DEFINED from the options provided by the "
+                                    + "pending intent sender ("
+                                    + key.packageName
+                                    + ") because this option is meant for the pending intent "
+                                    + "creator");
+                    opts.setPendingIntentCreatorBackgroundActivityStartMode(
+                            ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED);
+                }
                 finalIntent.addFlags(opts.getPendingIntentLaunchFlags());
             }
 
diff --git a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
index e498384..09df277 100644
--- a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
+++ b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
@@ -51,11 +51,11 @@
 import com.android.internal.annotations.CompositeRWLock;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.modules.expresslog.Counter;
 import com.android.internal.os.ProcessCpuTracker;
 import com.android.internal.os.TimeoutRecord;
 import com.android.internal.os.anr.AnrLatencyTracker;
 import com.android.internal.util.FrameworkStatsLog;
+import com.android.modules.expresslog.Counter;
 import com.android.server.ResourcePressureUtil;
 import com.android.server.criticalevents.CriticalEventLog;
 import com.android.server.stats.pull.ProcfsMemoryUtil.MemorySnapshot;
@@ -321,11 +321,13 @@
         }
 
         final boolean isSilentAnr;
-        final int pid = mApp.getPid();
+        final int pid;
         final UUID errorId;
         latencyTracker.waitingOnAMSLockStarted();
         synchronized (mService) {
             latencyTracker.waitingOnAMSLockEnded();
+            // Get the process's pid after obtaining the global lock.
+            pid = mApp.getPid();
             // Store annotation here as instance above will not be hit on all paths.
             setAnrAnnotation(annotation);
 
@@ -456,6 +458,11 @@
         String currentPsiState = ResourcePressureUtil.currentPsiState();
         latencyTracker.currentPsiStateReturned();
         report.append(currentPsiState);
+        // The 'processCpuTracker' variable is a shared resource that might be initialized and
+        // updated in a different thread. In order to prevent thread visibility issues, which
+        // can occur when one thread does not immediately see the changes made to
+        // 'processCpuTracker' by another thread, it is necessary to use synchronization whenever
+        // 'processCpuTracker' is accessed or modified.
         ProcessCpuTracker processCpuTracker = new ProcessCpuTracker(true);
 
         // We push the native pids collection task to the helper thread through
@@ -517,12 +524,16 @@
             }
             mService.updateCpuStatsNow();
             mService.mAppProfiler.printCurrentCpuState(report, anrTime);
-            info.append(processCpuTracker.printCurrentLoad());
+            synchronized (processCpuTracker) {
+                info.append(processCpuTracker.printCurrentLoad());
+            }
             info.append(report);
         }
         report.append(tracesFileException.getBuffer());
 
-        info.append(processCpuTracker.printCurrentState(anrTime));
+        synchronized (processCpuTracker) {
+            info.append(processCpuTracker.printCurrentState(anrTime));
+        }
 
         Slog.e(TAG, info.toString());
         if (tracesFile == null) {
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index c5776d8..acd9771 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -1827,6 +1827,7 @@
 
             if (debuggableFlag) {
                 runtimeFlags |= Zygote.DEBUG_ENABLE_JDWP;
+                runtimeFlags |= Zygote.DEBUG_ENABLE_PTRACE;
                 runtimeFlags |= Zygote.DEBUG_JAVA_DEBUGGABLE;
                 // Also turn on CheckJNI for debuggable apps. It's quite
                 // awkward to turn on otherwise.
diff --git a/services/core/java/com/android/server/am/ProcessServiceRecord.java b/services/core/java/com/android/server/am/ProcessServiceRecord.java
index 81d0b6a..7ff6d11 100644
--- a/services/core/java/com/android/server/am/ProcessServiceRecord.java
+++ b/services/core/java/com/android/server/am/ProcessServiceRecord.java
@@ -341,7 +341,8 @@
         mHasAboveClient = false;
         for (int i = mConnections.size() - 1; i >= 0; i--) {
             ConnectionRecord cr = mConnections.valueAt(i);
-            if (cr.hasFlag(Context.BIND_ABOVE_CLIENT)) {
+            if (cr.binding.service.app.mServices != this
+                    && cr.hasFlag(Context.BIND_ABOVE_CLIENT)) {
                 mHasAboveClient = true;
                 break;
             }
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index 50fe6d7..aabab61 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -21,9 +21,11 @@
 import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
 import static android.app.ProcessMemoryState.HOSTING_COMPONENT_TYPE_BOUND_SERVICE;
 import static android.os.PowerExemptionManager.REASON_DENIED;
+import static android.os.PowerExemptionManager.reasonCodeToString;
 import static android.os.Process.INVALID_UID;
 
 import static com.android.internal.util.Preconditions.checkArgument;
+import static com.android.server.am.ActiveServices.TAG_SERVICE;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_FOREGROUND_SERVICE;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -172,11 +174,11 @@
     private BackgroundStartPrivileges mBackgroundStartPrivilegesByStartMerged =
             BackgroundStartPrivileges.NONE;
 
-    // allow while-in-use permissions in foreground service or not.
+    // Reason code for allow while-in-use permissions in foreground service.
+    // If it's not DENIED, while-in-use permissions are allowed.
     // while-in-use permissions in FGS started from background might be restricted.
-    boolean mAllowWhileInUsePermissionInFgs;
     @PowerExemptionManager.ReasonCode
-    int mAllowWhileInUsePermissionInFgsReason = REASON_DENIED;
+    int mAllowWhileInUsePermissionInFgsReasonNoBinding = REASON_DENIED;
 
     // A copy of mAllowWhileInUsePermissionInFgs's value when the service is entering FGS state.
     boolean mAllowWhileInUsePermissionInFgsAtEntering;
@@ -205,15 +207,114 @@
 
     // allow the service becomes foreground service? Service started from background may not be
     // allowed to become a foreground service.
-    @PowerExemptionManager.ReasonCode int mAllowStartForeground = REASON_DENIED;
+    @PowerExemptionManager.ReasonCode
+    int mAllowStartForegroundNoBinding = REASON_DENIED;
     // A copy of mAllowStartForeground's value when the service is entering FGS state.
-    @PowerExemptionManager.ReasonCode int mAllowStartForegroundAtEntering = REASON_DENIED;
+    @PowerExemptionManager.ReasonCode
+    int mAllowStartForegroundAtEntering = REASON_DENIED;
     // Debug info why mAllowStartForeground is allowed or denied.
     String mInfoAllowStartForeground;
     // Debug info if mAllowStartForeground is allowed because of a temp-allowlist.
     ActivityManagerService.FgsTempAllowListItem mInfoTempFgsAllowListReason;
     // Is the same mInfoAllowStartForeground string has been logged before? Used for dedup.
     boolean mLoggedInfoAllowStartForeground;
+
+    @PowerExemptionManager.ReasonCode
+    int mAllowWIUInBindService = REASON_DENIED;
+
+    @PowerExemptionManager.ReasonCode
+    int mAllowWIUByBindings = REASON_DENIED;
+
+    @PowerExemptionManager.ReasonCode
+    int mAllowStartInBindService = REASON_DENIED;
+
+    @PowerExemptionManager.ReasonCode
+    int mAllowStartByBindings = REASON_DENIED;
+
+    @PowerExemptionManager.ReasonCode
+    int getFgsAllowWIU() {
+        return mAllowWhileInUsePermissionInFgsReasonNoBinding != REASON_DENIED
+                ? mAllowWhileInUsePermissionInFgsReasonNoBinding
+                : mAllowWIUInBindService;
+    }
+
+    boolean isFgsAllowedWIU() {
+        return getFgsAllowWIU() != REASON_DENIED;
+    }
+
+    @PowerExemptionManager.ReasonCode
+    int getFgsAllowStart() {
+        return mAllowStartForegroundNoBinding != REASON_DENIED
+                ? mAllowStartForegroundNoBinding
+                : mAllowStartInBindService;
+    }
+
+    boolean isFgsAllowedStart() {
+        return getFgsAllowStart() != REASON_DENIED;
+    }
+
+    void clearFgsAllowWIU() {
+        mAllowWhileInUsePermissionInFgsReasonNoBinding = REASON_DENIED;
+        mAllowWIUInBindService = REASON_DENIED;
+        mAllowWIUByBindings = REASON_DENIED;
+    }
+
+    void clearFgsAllowStart() {
+        mAllowStartForegroundNoBinding = REASON_DENIED;
+        mAllowStartInBindService = REASON_DENIED;
+        mAllowStartByBindings = REASON_DENIED;
+    }
+
+    @PowerExemptionManager.ReasonCode
+    int reasonOr(@PowerExemptionManager.ReasonCode int first,
+            @PowerExemptionManager.ReasonCode int second) {
+        return first != REASON_DENIED ? first : second;
+    }
+
+    boolean allowedChanged(@PowerExemptionManager.ReasonCode int first,
+            @PowerExemptionManager.ReasonCode int second) {
+        return (first == REASON_DENIED) != (second == REASON_DENIED);
+    }
+
+    String changeMessage(@PowerExemptionManager.ReasonCode int first,
+            @PowerExemptionManager.ReasonCode int second) {
+        return reasonOr(first, second) == REASON_DENIED ? "DENIED"
+                : ("ALLOWED ("
+                + reasonCodeToString(first)
+                + "+"
+                + reasonCodeToString(second)
+                + ")");
+    }
+
+    void maybeLogFgsLogicChange() {
+        final int origWiu = reasonOr(mAllowWhileInUsePermissionInFgsReasonNoBinding,
+                mAllowWIUInBindService);
+        final int newWiu = reasonOr(mAllowWhileInUsePermissionInFgsReasonNoBinding,
+                mAllowWIUByBindings);
+        final int origStart = reasonOr(mAllowStartForegroundNoBinding, mAllowStartInBindService);
+        final int newStart = reasonOr(mAllowStartForegroundNoBinding, mAllowStartByBindings);
+
+        final boolean wiuChanged = allowedChanged(origWiu, newWiu);
+        final boolean startChanged = allowedChanged(origStart, newStart);
+
+        if (!wiuChanged && !startChanged) {
+            return;
+        }
+        final String message = "FGS logic changed:"
+                + (wiuChanged ? " [WIU changed]" : "")
+                + (startChanged ? " [BFSL changed]" : "")
+                + " OW:" // Orig-WIU
+                + changeMessage(mAllowWhileInUsePermissionInFgsReasonNoBinding,
+                        mAllowWIUInBindService)
+                + " NW:" // New-WIU
+                + changeMessage(mAllowWhileInUsePermissionInFgsReasonNoBinding, mAllowWIUByBindings)
+                + " OS:" // Orig-start
+                + changeMessage(mAllowStartForegroundNoBinding, mAllowStartInBindService)
+                + " NS:" // New-start
+                + changeMessage(mAllowStartForegroundNoBinding, mAllowStartByBindings);
+        Slog.wtf(TAG_SERVICE, message);
+    }
+
     // The number of times Service.startForeground() is called, after this service record is
     // created. (i.e. due to "bound" or "start".) It never decreases, even when stopForeground()
     // is called.
@@ -502,7 +603,7 @@
         ProtoUtils.toDuration(proto, ServiceRecordProto.RESTART_TIME, restartTime, now);
         proto.write(ServiceRecordProto.CREATED_FROM_FG, createdFromFg);
         proto.write(ServiceRecordProto.ALLOW_WHILE_IN_USE_PERMISSION_IN_FGS,
-                mAllowWhileInUsePermissionInFgs);
+                isFgsAllowedWIU());
 
         if (startRequested || delayedStop || lastStartId != 0) {
             long startToken = proto.start(ServiceRecordProto.START);
@@ -618,7 +719,13 @@
             pw.println(mBackgroundStartPrivilegesByStartMerged);
         }
         pw.print(prefix); pw.print("mAllowWhileInUsePermissionInFgsReason=");
-        pw.println(PowerExemptionManager.reasonCodeToString(mAllowWhileInUsePermissionInFgsReason));
+        pw.println(PowerExemptionManager.reasonCodeToString(
+                mAllowWhileInUsePermissionInFgsReasonNoBinding));
+
+        pw.print(prefix); pw.print("mAllowWIUInBindService=");
+        pw.println(PowerExemptionManager.reasonCodeToString(mAllowWIUInBindService));
+        pw.print(prefix); pw.print("mAllowWIUByBindings=");
+        pw.println(PowerExemptionManager.reasonCodeToString(mAllowWIUByBindings));
 
         pw.print(prefix); pw.print("allowUiJobScheduling="); pw.println(mAllowUiJobScheduling);
         pw.print(prefix); pw.print("recentCallingPackage=");
@@ -626,7 +733,12 @@
         pw.print(prefix); pw.print("recentCallingUid=");
         pw.println(mRecentCallingUid);
         pw.print(prefix); pw.print("allowStartForeground=");
-        pw.println(PowerExemptionManager.reasonCodeToString(mAllowStartForeground));
+        pw.println(PowerExemptionManager.reasonCodeToString(mAllowStartForegroundNoBinding));
+        pw.print(prefix); pw.print("mAllowStartInBindService=");
+        pw.println(PowerExemptionManager.reasonCodeToString(mAllowStartInBindService));
+        pw.print(prefix); pw.print("mAllowStartByBindings=");
+        pw.println(PowerExemptionManager.reasonCodeToString(mAllowStartByBindings));
+
         pw.print(prefix); pw.print("startForegroundCount=");
         pw.println(mStartForegroundCount);
         pw.print(prefix); pw.print("infoAllowStartForeground=");
diff --git a/services/core/java/com/android/server/am/StackTracesDumpHelper.java b/services/core/java/com/android/server/am/StackTracesDumpHelper.java
index cf69b53..c1374e1 100644
--- a/services/core/java/com/android/server/am/StackTracesDumpHelper.java
+++ b/services/core/java/com/android/server/am/StackTracesDumpHelper.java
@@ -104,8 +104,8 @@
     public static File dumpStackTraces(ArrayList<Integer> firstPids,
             ProcessCpuTracker processCpuTracker, SparseBooleanArray lastPids,
             Future<ArrayList<Integer>> nativePidsFuture, StringWriter logExceptionCreatingFile,
-            String subject, String criticalEventSection,
-            @NonNull Executor auxiliaryTaskExecutor, AnrLatencyTracker latencyTracker) {
+            String subject, String criticalEventSection, @NonNull Executor auxiliaryTaskExecutor,
+            AnrLatencyTracker latencyTracker) {
         return dumpStackTraces(firstPids, processCpuTracker, lastPids, nativePidsFuture,
                 logExceptionCreatingFile, null, subject, criticalEventSection,
                 /* memoryHeaders= */ null, auxiliaryTaskExecutor, null, latencyTracker);
@@ -120,7 +120,7 @@
             Future<ArrayList<Integer>> nativePidsFuture, StringWriter logExceptionCreatingFile,
             AtomicLong firstPidEndOffset, String subject, String criticalEventSection,
             String memoryHeaders, @NonNull Executor auxiliaryTaskExecutor,
-            Future<File> firstPidFilePromise, AnrLatencyTracker latencyTracker) {
+           Future<File> firstPidFilePromise, AnrLatencyTracker latencyTracker) {
         try {
 
             if (latencyTracker != null) {
@@ -464,28 +464,31 @@
             latencyTracker.processCpuTrackerMethodsCalled();
         }
         ArrayList<Integer> extraPids = new ArrayList<>();
-        processCpuTracker.init();
+        synchronized (processCpuTracker) {
+            processCpuTracker.init();
+        }
         try {
             Thread.sleep(200);
         } catch (InterruptedException ignored) {
         }
 
-        processCpuTracker.update();
+        synchronized (processCpuTracker) {
+            processCpuTracker.update();
+            // We'll take the stack crawls of just the top apps using CPU.
+            final int workingStatsNumber = processCpuTracker.countWorkingStats();
+            for (int i = 0; i < workingStatsNumber && extraPids.size() < 2; i++) {
+                ProcessCpuTracker.Stats stats = processCpuTracker.getWorkingStats(i);
+                if (lastPids.indexOfKey(stats.pid) >= 0) {
+                    if (DEBUG_ANR) {
+                        Slog.d(TAG, "Collecting stacks for extra pid " + stats.pid);
+                    }
 
-        // We'll take the stack crawls of just the top apps using CPU.
-        final int workingStatsNumber = processCpuTracker.countWorkingStats();
-        for (int i = 0; i < workingStatsNumber && extraPids.size() < 2; i++) {
-            ProcessCpuTracker.Stats stats = processCpuTracker.getWorkingStats(i);
-            if (lastPids.indexOfKey(stats.pid) >= 0) {
-                if (DEBUG_ANR) {
-                    Slog.d(TAG, "Collecting stacks for extra pid " + stats.pid);
+                    extraPids.add(stats.pid);
+                } else {
+                    Slog.i(TAG,
+                            "Skipping next CPU consuming process, not a java proc: "
+                            + stats.pid);
                 }
-
-                extraPids.add(stats.pid);
-            } else {
-                Slog.i(TAG,
-                        "Skipping next CPU consuming process, not a java proc: "
-                        + stats.pid);
             }
         }
         if (latencyTracker != null) {
diff --git a/services/core/java/com/android/server/am/TEST_MAPPING b/services/core/java/com/android/server/am/TEST_MAPPING
index 97d4879..0af9b2b 100644
--- a/services/core/java/com/android/server/am/TEST_MAPPING
+++ b/services/core/java/com/android/server/am/TEST_MAPPING
@@ -145,12 +145,7 @@
       ]
     },
     {
-      "name": "CtsAppSecurityHostTestCases",
-      "options": [
-        {
-          "include-filter": "android.appsecurity.cts.AppDataIsolationTests"
-        }
-      ]
+      "name": "CtsAppDataIsolationHostTestCases"
     },
     {
       "name": "CtsAppTestCases",
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 76a994e..d426c79 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -126,6 +126,7 @@
 import com.android.internal.policy.IKeyguardDismissCallback;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.FrameworkStatsLog;
+import com.android.internal.util.ObjectUtils;
 import com.android.internal.util.Preconditions;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.server.FactoryResetter;
@@ -146,6 +147,7 @@
 import com.android.server.wm.WindowManagerService;
 
 import java.io.PrintWriter;
+import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Iterator;
@@ -261,6 +263,10 @@
     // once target user goes into the foreground. Use mLock when updating
     @GuardedBy("mLock")
     private volatile int mTargetUserId = UserHandle.USER_NULL;
+    // If a user switch request comes during an ongoing user switch, it is postponed to the end of
+    // the current switch, and this variable holds those user ids. Use mLock when updating
+    @GuardedBy("mLock")
+    private final ArrayDeque<Integer> mPendingTargetUserIds = new ArrayDeque<>();
 
     /**
      * Which users have been started, so are allowed to run code.
@@ -291,6 +297,7 @@
 
     /**
      * Mapping from each known user ID to the profile group ID it is associated with.
+     * <p>Users not present in this array have a profile group of NO_PROFILE_GROUP_ID.
      */
     @GuardedBy("mLock")
     private final SparseIntArray mUserProfileGroupIds = new SparseIntArray();
@@ -484,9 +491,7 @@
         mHandler.post(() -> {
             finishUserBoot(uss);
             startProfiles();
-            synchronized (mLock) {
-                stopRunningUsersLU(mMaxRunningUsers);
-            }
+            stopExcessRunningUsers();
         });
     }
 
@@ -510,14 +515,31 @@
         return runningUsers;
     }
 
+    private void stopExcessRunningUsers() {
+        final ArraySet<Integer> exemptedUsers = new ArraySet<>();
+        final List<UserInfo> users = mInjector.getUserManager().getUsers(true);
+        for (int i = 0; i < users.size(); i++) {
+            final int userId = users.get(i).id;
+            if (isAlwaysVisibleUser(userId)) {
+                exemptedUsers.add(userId);
+            }
+        }
+
+        synchronized (mLock) {
+            stopExcessRunningUsersLU(mMaxRunningUsers, exemptedUsers);
+        }
+    }
+
     @GuardedBy("mLock")
-    private void stopRunningUsersLU(int maxRunningUsers) {
+    private void stopExcessRunningUsersLU(int maxRunningUsers, ArraySet<Integer> exemptedUsers) {
         List<Integer> currentlyRunning = getRunningUsersLU();
         Iterator<Integer> iterator = currentlyRunning.iterator();
         while (currentlyRunning.size() > maxRunningUsers && iterator.hasNext()) {
             Integer userId = iterator.next();
-            if (userId == UserHandle.USER_SYSTEM || userId == mCurrentUserId) {
-                // Owner/System user and current user can't be stopped
+            if (userId == UserHandle.USER_SYSTEM
+                    || userId == mCurrentUserId
+                    || exemptedUsers.contains(userId)) {
+                // System and current users can't be stopped, and an exempt user shouldn't be
                 continue;
             }
             // allowDelayedLocking set here as stopping user is done without any explicit request
@@ -595,22 +617,18 @@
             }
         }
 
-        // We need to delay unlocking managed profiles until the parent user
-        // is also unlocked.
-        if (mInjector.getUserManager().isProfile(userId)) {
-            final UserInfo parent = mInjector.getUserManager().getProfileParent(userId);
-            if (parent != null
-                    && isUserRunning(parent.id, ActivityManager.FLAG_AND_UNLOCKED)) {
-                Slogf.d(TAG, "User " + userId + " (parent " + parent.id
-                        + "): attempting unlock because parent is unlocked");
-                maybeUnlockUser(userId);
-            } else {
-                String parentId = (parent == null) ? "<null>" : String.valueOf(parent.id);
-                Slogf.d(TAG, "User " + userId + " (parent " + parentId
-                        + "): delaying unlock because parent is locked");
-            }
-        } else {
+        // We need to delay unlocking profiles until the parent user is also unlocked.
+        final UserInfo parent = mInjector.getUserManager().getProfileParent(userId);
+        if (parent == null) {
+            // Not a profile (or is a parentless profile) so no parent for which to wait.
             maybeUnlockUser(userId);
+        } else if (isUserRunning(parent.id, ActivityManager.FLAG_AND_UNLOCKED)) {
+            Slogf.d(TAG, "User " + userId + " (parent " + parent.id
+                    + "): attempting unlock because parent is unlocked");
+            maybeUnlockUser(userId);
+        } else {
+            Slogf.d(TAG, "User " + userId + " (parent " + parent.id
+                    + "): delaying unlock because parent is locked");
         }
     }
 
@@ -1693,7 +1711,6 @@
                 boolean userSwitchUiEnabled;
                 synchronized (mLock) {
                     mCurrentUserId = userId;
-                    mTargetUserId = UserHandle.USER_NULL; // reset, mCurrentUserId has caught up
                     userSwitchUiEnabled = mUserSwitchUiEnabled;
                 }
                 mInjector.updateUserConfiguration();
@@ -1822,8 +1839,7 @@
         boolean success = startUser(targetUserId, USER_START_MODE_FOREGROUND);
         if (!success) {
             mInjector.getWindowManager().setSwitchingUser(false);
-            mTargetUserId = UserHandle.USER_NULL;
-            dismissUserSwitchDialog(null);
+            dismissUserSwitchDialog(this::endUserSwitch);
         }
     }
 
@@ -1900,8 +1916,7 @@
             return false;
         }
 
-        // We just unlocked a user, so let's now attempt to unlock any
-        // managed profiles under that user.
+        // We just unlocked a user, so let's now attempt to unlock any profiles under that user.
 
         // First, get list of userIds. Requires mLock, so we cannot make external calls, e.g. to UMS
         int[] userIds;
@@ -1944,6 +1959,7 @@
             Slogf.w(TAG, "Cannot switch to User #" + targetUserId + ": factory reset in progress");
             return false;
         }
+
         boolean userSwitchUiEnabled;
         synchronized (mLock) {
             if (!mInitialized) {
@@ -1951,6 +1967,12 @@
                         + ": UserController not ready yet");
                 return false;
             }
+            if (mTargetUserId != UserHandle.USER_NULL) {
+                Slogf.w(TAG, "There is already an ongoing user switch to User #" + mTargetUserId
+                        + ". User #" + targetUserId + " will be added to the queue.");
+                mPendingTargetUserIds.offer(targetUserId);
+                return true;
+            }
             mTargetUserId = targetUserId;
             userSwitchUiEnabled = mUserSwitchUiEnabled;
         }
@@ -1972,7 +1994,7 @@
     }
 
     private void dismissUserSwitchDialog(Runnable onDismissed) {
-        mInjector.dismissUserSwitchingDialog(onDismissed);
+        mUiHandler.post(() -> mInjector.dismissUserSwitchingDialog(onDismissed));
     }
 
     private void showUserSwitchDialog(Pair<UserInfo, UserInfo> fromToUserPair) {
@@ -2017,6 +2039,19 @@
         sendUserSwitchBroadcasts(oldUserId, newUserId);
         t.traceEnd();
         t.traceEnd();
+
+        endUserSwitch();
+    }
+
+    private void endUserSwitch() {
+        final int nextUserId;
+        synchronized (mLock) {
+            nextUserId = ObjectUtils.getOrElse(mPendingTargetUserIds.poll(), UserHandle.USER_NULL);
+            mTargetUserId = UserHandle.USER_NULL;
+        }
+        if (nextUserId != UserHandle.USER_NULL) {
+            switchUser(nextUserId);
+        }
     }
 
     private void dispatchLockedBootComplete(@UserIdInt int userId) {
@@ -2784,6 +2819,12 @@
         return userId == getCurrentOrTargetUserIdLU();
     }
 
+    /** Returns whether the user is always-visible (such as a communal profile). */
+    private boolean isAlwaysVisibleUser(@UserIdInt int userId) {
+        final UserProperties properties = getUserProperties(userId);
+        return properties != null && properties.getAlwaysVisible();
+    }
+
     int[] getUsers() {
         UserManagerService ums = mInjector.getUserManager();
         return ums != null ? ums.getUserIds() : new int[] { 0 };
@@ -2853,6 +2894,7 @@
         return mInjector.getUserManager().hasUserRestriction(restriction, userId);
     }
 
+    /** Returns whether the two users are in the same profile group. */
     boolean isSameProfileGroup(int callingUserId, int targetUserId) {
         if (callingUserId == targetUserId) {
             return true;
@@ -2900,7 +2942,9 @@
             if (user.profileGroupId == mCurrentUserId) {
                 mCurrentProfileIds = ArrayUtils.appendInt(mCurrentProfileIds, user.id);
             }
-            mUserProfileGroupIds.put(user.id, user.profileGroupId);
+            if (user.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID) {
+                mUserProfileGroupIds.put(user.id, user.profileGroupId);
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/ambientcontext/AmbientContextManagerService.java b/services/core/java/com/android/server/ambientcontext/AmbientContextManagerService.java
index c6b15b6..c7a560b 100644
--- a/services/core/java/com/android/server/ambientcontext/AmbientContextManagerService.java
+++ b/services/core/java/com/android/server/ambientcontext/AmbientContextManagerService.java
@@ -129,7 +129,7 @@
                 PACKAGE_UPDATE_POLICY_REFRESH_EAGER
                         | /*To avoid high latency*/ PACKAGE_RESTART_POLICY_REFRESH_EAGER);
         mContext = context;
-        mExistingClientRequests = new ArraySet<>();
+        mExistingClientRequests = ConcurrentHashMap.newKeySet();
     }
 
     @Override
@@ -158,18 +158,22 @@
             String callingPackage, IAmbientContextObserver observer) {
         Slog.d(TAG, "New client added: " + callingPackage);
 
-        // Remove any existing ClientRequest for this user and package.
-        mExistingClientRequests.removeAll(
-                findExistingRequests(userId, callingPackage));
+        synchronized (mExistingClientRequests) {
+            // Remove any existing ClientRequest for this user and package.
+            mExistingClientRequests.removeAll(
+                    findExistingRequests(userId, callingPackage));
 
-        // Add to existing ClientRequests
-        mExistingClientRequests.add(
-                new ClientRequest(userId, request, callingPackage, observer));
+            // Add to existing ClientRequests
+            mExistingClientRequests.add(
+                    new ClientRequest(userId, request, callingPackage, observer));
+        }
     }
 
     void clientRemoved(int userId, String packageName) {
         Slog.d(TAG, "Remove client: " + packageName);
-        mExistingClientRequests.removeAll(findExistingRequests(userId, packageName));
+        synchronized (mExistingClientRequests) {
+            mExistingClientRequests.removeAll(findExistingRequests(userId, packageName));
+        }
     }
 
     private Set<ClientRequest> findExistingRequests(int userId, String packageName) {
@@ -184,9 +188,11 @@
 
     @Nullable
     IAmbientContextObserver getClientRequestObserver(int userId, String packageName) {
-        for (ClientRequest clientRequest : mExistingClientRequests) {
-            if (clientRequest.hasUserIdAndPackageName(userId, packageName)) {
-                return clientRequest.getObserver();
+        synchronized (mExistingClientRequests) {
+            for (ClientRequest clientRequest : mExistingClientRequests) {
+                if (clientRequest.hasUserIdAndPackageName(userId, packageName)) {
+                    return clientRequest.getObserver();
+                }
             }
         }
         return null;
@@ -588,10 +594,10 @@
             }
         }
 
+        @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT)
         @Override
         public void unregisterObserver(String callingPackage) {
-            mContext.enforceCallingOrSelfPermission(
-                    Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT, TAG);
+            unregisterObserver_enforcePermission();
             assertCalledByPackageOwner(callingPackage);
 
             synchronized (mLock) {
diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java
index e4a5a3e..c6d6122 100644
--- a/services/core/java/com/android/server/app/GameManagerService.java
+++ b/services/core/java/com/android/server/app/GameManagerService.java
@@ -40,6 +40,7 @@
 import android.app.GameState;
 import android.app.IGameManagerService;
 import android.app.IGameModeListener;
+import android.app.IGameStateListener;
 import android.app.StatsManager;
 import android.app.UidObserver;
 import android.content.BroadcastReceiver;
@@ -148,6 +149,7 @@
     private final Object mLock = new Object();
     private final Object mDeviceConfigLock = new Object();
     private final Object mGameModeListenerLock = new Object();
+    private final Object mGameStateListenerLock = new Object();
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
     final Handler mHandler;
     private final PackageManager mPackageManager;
@@ -164,6 +166,8 @@
     // listener to caller uid map
     @GuardedBy("mGameModeListenerLock")
     private final ArrayMap<IGameModeListener, Integer> mGameModeListeners = new ArrayMap<>();
+    @GuardedBy("mGameStateListenerLock")
+    private final ArrayMap<IGameStateListener, Integer> mGameStateListeners = new ArrayMap<>();
     @Nullable
     private final GameServiceController mGameServiceController;
     private final Object mUidObserverLock = new Object();
@@ -352,6 +356,16 @@
                                     loadingBoostDuration);
                         }
                     }
+                    synchronized (mGameStateListenerLock) {
+                        for (IGameStateListener listener : mGameStateListeners.keySet()) {
+                            try {
+                                listener.onGameStateChanged(packageName, gameState, userId);
+                            } catch (RemoteException ex) {
+                                Slog.w(TAG, "Cannot notify game state change for listener added by "
+                                        + mGameStateListeners.get(listener));
+                            }
+                        }
+                    }
                     break;
                 }
                 case CANCEL_GAME_LOADING_MODE: {
@@ -1474,6 +1488,43 @@
     }
 
     /**
+     * Adds a game state listener.
+     */
+    @Override
+    public void addGameStateListener(@NonNull IGameStateListener listener) {
+        try {
+            final IBinder listenerBinder = listener.asBinder();
+            listenerBinder.linkToDeath(new DeathRecipient() {
+                @Override public void binderDied() {
+                    removeGameStateListenerUnchecked(listener);
+                    listenerBinder.unlinkToDeath(this, 0 /*flags*/);
+                }
+            }, 0 /*flags*/);
+            synchronized (mGameStateListenerLock) {
+                mGameStateListeners.put(listener, Binder.getCallingUid());
+            }
+        } catch (RemoteException ex) {
+            Slog.e(TAG,
+                    "Failed to link death recipient for IGameStateListener from caller "
+                            + Binder.getCallingUid() + ", abandoned its listener registration", ex);
+        }
+    }
+
+    /**
+     * Removes a game state listener.
+     */
+    @Override
+    public void removeGameStateListener(@NonNull IGameStateListener listener) {
+        removeGameStateListenerUnchecked(listener);
+    }
+
+    private void removeGameStateListenerUnchecked(IGameStateListener listener) {
+        synchronized (mGameStateListenerLock) {
+            mGameStateListeners.remove(listener);
+        }
+    }
+
+    /**
      * Notified when boot is completed.
      */
     @VisibleForTesting
@@ -2166,7 +2217,7 @@
         @Override
         public void onUidStateChanged(int uid, int procState, long procStateSeq, int capability) {
             synchronized (mUidObserverLock) {
-                if (ActivityManager.isProcStateBackground(procState)) {
+                if (procState != ActivityManager.PROCESS_STATE_TOP) {
                     disableGameMode(uid);
                     return;
                 }
diff --git a/services/core/java/com/android/server/appop/AppOpMigrationHelper.java b/services/core/java/com/android/server/appop/AppOpMigrationHelper.java
new file mode 100644
index 0000000..7919370
--- /dev/null
+++ b/services/core/java/com/android/server/appop/AppOpMigrationHelper.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.appop;
+
+import android.annotation.NonNull;
+
+import java.util.Map;
+
+/**
+ * In-process api for app-ops migration.
+ *
+ * @hide
+ */
+public interface AppOpMigrationHelper {
+
+    /**
+     * @return a map of app ID to app-op modes (op name -> mode) for a given user.
+     */
+    @NonNull
+    Map<Integer, Map<String, Integer>> getLegacyAppIdAppOpModes(int userId);
+
+    /**
+     * @return a map of package name to app-op modes (op name -> mode) for a given user.
+     */
+    @NonNull
+    Map<String, Map<String, Integer>> getLegacyPackageAppOpModes(int userId);
+
+    /**
+     * @return AppOps file version, the version is same for all the user.
+     */
+    int getLegacyAppOpVersion();
+
+    /**
+     * @return Whether app-op state exists or not.
+     */
+    boolean hasLegacyAppOpState();
+}
diff --git a/services/core/java/com/android/server/appop/AppOpMigrationHelperImpl.java b/services/core/java/com/android/server/appop/AppOpMigrationHelperImpl.java
new file mode 100644
index 0000000..b5288bf
--- /dev/null
+++ b/services/core/java/com/android/server/appop/AppOpMigrationHelperImpl.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.appop;
+
+import android.annotation.NonNull;
+import android.app.AppOpsManager;
+import android.os.UserHandle;
+import android.util.ArrayMap;
+import android.util.AtomicFile;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.SystemServiceManager;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.Map;
+
+/**
+ * Provider of legacy app-ops data for new permission subsystem.
+ *
+ * @hide
+ */
+public class AppOpMigrationHelperImpl implements AppOpMigrationHelper {
+    private SparseArray<Map<Integer, Map<String, Integer>>> mAppIdAppOpModes = null;
+    private SparseArray<Map<String, Map<String, Integer>>> mPackageAppOpModes = null;
+    private int mVersionAtBoot;
+
+    private final Object mLock = new Object();
+
+    @Override
+    @GuardedBy("mLock")
+    @NonNull
+    public Map<Integer, Map<String, Integer>> getLegacyAppIdAppOpModes(int userId) {
+        synchronized (mLock) {
+            if (mAppIdAppOpModes == null) {
+                readLegacyAppOpState();
+            }
+        }
+        return mAppIdAppOpModes.get(userId, Collections.emptyMap());
+    }
+
+    @Override
+    @GuardedBy("mLock")
+    @NonNull
+    public Map<String, Map<String, Integer>> getLegacyPackageAppOpModes(int userId) {
+        synchronized (mLock) {
+            if (mPackageAppOpModes == null) {
+                readLegacyAppOpState();
+            }
+        }
+        return mPackageAppOpModes.get(userId, Collections.emptyMap());
+    }
+
+    @GuardedBy("mLock")
+    private void readLegacyAppOpState() {
+        final File systemDir = SystemServiceManager.ensureSystemDir();
+        AtomicFile appOpFile = new AtomicFile(new File(systemDir, "appops.xml"));
+
+        final SparseArray<SparseIntArray> uidAppOpModes = new SparseArray<>();
+        final SparseArray<ArrayMap<String, SparseIntArray>> packageAppOpModes =
+                new SparseArray<>();
+
+        LegacyAppOpStateParser parser = new LegacyAppOpStateParser();
+        final int version = parser.readState(appOpFile, uidAppOpModes, packageAppOpModes);
+        // -1 No app ops data available
+        // 0 appops.xml exist w/o any version
+        switch (version) {
+            case -2:
+                mVersionAtBoot = -1;
+                break;
+            case -1:
+                mVersionAtBoot = 0;
+                break;
+            default:
+                mVersionAtBoot = version;
+        }
+        mAppIdAppOpModes = getAppIdAppOpModes(uidAppOpModes);
+        mPackageAppOpModes = getPackageAppOpModes(packageAppOpModes);
+    }
+
+    private SparseArray<Map<Integer, Map<String, Integer>>> getAppIdAppOpModes(
+            SparseArray<SparseIntArray> uidAppOpModes) {
+        SparseArray<Map<Integer, Map<String, Integer>>> userAppIdAppOpModes = new SparseArray<>();
+
+        int size = uidAppOpModes.size();
+        for (int uidIndex = 0; uidIndex < size; uidIndex++) {
+            int uid = uidAppOpModes.keyAt(uidIndex);
+            int userId = UserHandle.getUserId(uid);
+            Map<Integer, Map<String, Integer>> appIdAppOpModes = userAppIdAppOpModes.get(userId);
+            if (appIdAppOpModes == null) {
+                appIdAppOpModes = new ArrayMap<>();
+                userAppIdAppOpModes.put(userId, appIdAppOpModes);
+            }
+
+            SparseIntArray appOpModes = uidAppOpModes.valueAt(uidIndex);
+            appIdAppOpModes.put(UserHandle.getAppId(uid), getAppOpModesForOpName(appOpModes));
+        }
+        return userAppIdAppOpModes;
+    }
+
+    private SparseArray<Map<String, Map<String, Integer>>> getPackageAppOpModes(
+            SparseArray<ArrayMap<String, SparseIntArray>> legacyPackageAppOpModes) {
+        SparseArray<Map<String, Map<String, Integer>>> userPackageAppOpModes = new SparseArray<>();
+
+        int usersSize = legacyPackageAppOpModes.size();
+        for (int userIndex = 0; userIndex < usersSize; userIndex++) {
+            int userId = legacyPackageAppOpModes.keyAt(userIndex);
+            Map<String, Map<String, Integer>> packageAppOpModes = userPackageAppOpModes.get(userId);
+            if (packageAppOpModes == null) {
+                packageAppOpModes = new ArrayMap<>();
+                userPackageAppOpModes.put(userId, packageAppOpModes);
+            }
+
+            ArrayMap<String, SparseIntArray> legacyPackagesModes =
+                    legacyPackageAppOpModes.valueAt(userIndex);
+
+            int packagesSize = legacyPackagesModes.size();
+            for (int packageIndex = 0; packageIndex < packagesSize; packageIndex++) {
+                String packageName = legacyPackagesModes.keyAt(packageIndex);
+                SparseIntArray modes = legacyPackagesModes.valueAt(packageIndex);
+                packageAppOpModes.put(packageName, getAppOpModesForOpName(modes));
+            }
+        }
+        return userPackageAppOpModes;
+    }
+
+    /**
+     * Converts the map from op code -> mode to op name -> mode.
+     */
+    private Map<String, Integer> getAppOpModesForOpName(SparseIntArray appOpCodeModes) {
+        int modesSize = appOpCodeModes.size();
+        Map<String, Integer> appOpNameModes = new ArrayMap<>(modesSize);
+
+        for (int modeIndex = 0; modeIndex < modesSize; modeIndex++) {
+            int opCode = appOpCodeModes.keyAt(modeIndex);
+            int opMode = appOpCodeModes.valueAt(modeIndex);
+            appOpNameModes.put(AppOpsManager.opToPublicName(opCode), opMode);
+        }
+        return appOpNameModes;
+    }
+
+    @Override
+    public int getLegacyAppOpVersion() {
+        synchronized (mLock) {
+            if (mAppIdAppOpModes == null || mPackageAppOpModes == null) {
+                readLegacyAppOpState();
+            }
+        }
+        return mVersionAtBoot;
+    }
+
+    @Override
+    public boolean hasLegacyAppOpState() {
+        return getLegacyAppOpVersion() > -1;
+    }
+}
diff --git a/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java b/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java
index 4dfd9b0..108f53f 100644
--- a/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java
+++ b/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java
@@ -17,20 +17,12 @@
 package com.android.server.appop;
 
 import static android.app.AppOpsManager.MODE_ALLOWED;
-import static android.app.AppOpsManager.OP_NONE;
+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.app.AppOpsManager.WATCH_FOREGROUND_CHANGES;
-import static android.app.AppOpsManager.opRestrictsRead;
-import static android.app.AppOpsManager.opToDefaultMode;
 
-import static com.android.server.appop.AppOpsService.ModeCallback.ALL_OPS;
-
-import android.Manifest;
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.annotation.UserIdInt;
-import android.app.AppGlobals;
 import android.app.AppOpsManager;
 import android.app.AppOpsManager.Mode;
 import android.content.Context;
@@ -38,13 +30,10 @@
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.UserPackage;
 import android.os.AsyncTask;
-import android.os.Binder;
 import android.os.Handler;
-import android.os.RemoteException;
 import android.os.UserHandle;
 import android.permission.PermissionManager;
 import android.util.ArrayMap;
-import android.util.ArraySet;
 import android.util.AtomicFile;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -54,30 +43,16 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.XmlUtils;
-import com.android.internal.util.function.pooled.PooledLambda;
-import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
 import com.android.server.LocalServices;
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.pm.permission.PermissionManagerServiceInternal;
 
-import libcore.util.EmptyArray;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
 import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.IOException;
-import java.io.PrintWriter;
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.List;
-import java.util.Objects;
-
 
 /**
  * Legacy implementation for App-ops service's app-op mode (uid and package) storage and access.
@@ -130,9 +105,9 @@
     @GuardedBy("mLock")
     final SparseArray<ArrayMap<String, SparseIntArray>> mUserPackageModes = new SparseArray<>();
 
-    final SparseArray<ArraySet<OnOpModeChangedListener>> mOpModeWatchers = new SparseArray<>();
-    final ArrayMap<String, ArraySet<OnOpModeChangedListener>> mPackageModeWatchers =
-            new ArrayMap<>();
+    private final LegacyAppOpStateParser mAppOpsStateParser = new LegacyAppOpStateParser();
+    @GuardedBy("mLock")
+    private List<AppOpsModeChangedListener> mModeChangedListeners = new ArrayList<>();
 
     final AtomicFile mFile;
     final Runnable mWriteRunner = new Runnable() {
@@ -155,10 +130,6 @@
     boolean mWriteScheduled;
     boolean mFastWriteScheduled;
 
-
-    // Constant meaning that any UID should be matched when dispatching callbacks
-    private static final int UID_ANY = -2;
-
     AppOpsCheckingServiceImpl(File storageFile,
             @NonNull Object lock, Handler handler, Context context,
             SparseArray<int[]> switchedOps) {
@@ -218,31 +189,39 @@
     @Override
     public boolean setUidMode(int uid, int op, int mode) {
         final int defaultMode = AppOpsManager.opToDefaultMode(op);
+        List<AppOpsModeChangedListener> listenersCopy;
         synchronized (mLock) {
             SparseIntArray opModes = mUidModes.get(uid, null);
-            if (opModes == null) {
-                if (mode != defaultMode) {
-                    opModes = new SparseIntArray();
-                    mUidModes.put(uid, opModes);
-                    opModes.put(op, mode);
-                    scheduleWriteLocked();
+
+            int previousMode = defaultMode;
+            if (opModes != null) {
+                previousMode = opModes.get(op, defaultMode);
+            }
+            if (mode == previousMode) {
+                return false;
+            }
+
+            if (mode == defaultMode) {
+                opModes.delete(op);
+                if (opModes.size() == 0) {
+                    mUidModes.remove(uid);
                 }
             } else {
-                if (opModes.indexOfKey(op) >= 0 && opModes.get(op) == mode) {
-                    return false;
+                if (opModes == null) {
+                    opModes = new SparseIntArray();
+                    mUidModes.put(uid, opModes);
                 }
-                if (mode == defaultMode) {
-                    opModes.delete(op);
-                    if (opModes.size() <= 0) {
-                        opModes = null;
-                        mUidModes.delete(uid);
-                    }
-                } else {
-                    opModes.put(op, mode);
-                }
-                scheduleWriteLocked();
+                opModes.put(op, mode);
             }
+
+            scheduleWriteLocked();
+            listenersCopy = new ArrayList<>(mModeChangedListeners);
         }
+
+        for (int i = 0; i < listenersCopy.size(); i++) {
+            listenersCopy.get(i).onUidModeChanged(uid, op, mode);
+        }
+
         return true;
     }
 
@@ -264,35 +243,52 @@
     @Override
     public void setPackageMode(String packageName, int op, @Mode int mode, @UserIdInt int userId) {
         final int defaultMode = AppOpsManager.opToDefaultMode(op);
+        List<AppOpsModeChangedListener> listenersCopy;
         synchronized (mLock) {
             ArrayMap<String, SparseIntArray> packageModes = mUserPackageModes.get(userId, null);
-            if (packageModes == null) {
+            if (packageModes == null && mode != defaultMode) {
                 packageModes = new ArrayMap<>();
                 mUserPackageModes.put(userId, packageModes);
             }
-            SparseIntArray opModes = packageModes.get(packageName);
-            if (opModes == null) {
-                if (mode != defaultMode) {
-                    opModes = new SparseIntArray();
-                    packageModes.put(packageName, opModes);
-                    opModes.put(op, mode);
-                    scheduleWriteLocked();
+            SparseIntArray opModes = null;
+            int previousMode = defaultMode;
+            if (packageModes != null) {
+                opModes = packageModes.get(packageName);
+                if (opModes != null) {
+                    previousMode = opModes.get(op, defaultMode);
+                }
+            }
+
+            if (mode == previousMode) {
+                return;
+            }
+
+            if (mode == defaultMode) {
+                opModes.delete(op);
+                if (opModes.size() == 0) {
+                    packageModes.remove(packageName);
+                    if (packageModes.size() == 0) {
+                        mUserPackageModes.remove(userId);
+                    }
                 }
             } else {
-                if (opModes.indexOfKey(op) >= 0 && opModes.get(op) == mode) {
-                    return;
+                if (packageModes == null) {
+                    packageModes = new ArrayMap<>();
+                    mUserPackageModes.put(userId, packageModes);
                 }
-                if (mode == defaultMode) {
-                    opModes.delete(op);
-                    if (opModes.size() <= 0) {
-                        opModes = null;
-                        packageModes.remove(packageName);
-                    }
-                } else {
-                    opModes.put(op, mode);
+                if (opModes == null) {
+                    opModes = new SparseIntArray();
+                    packageModes.put(packageName, opModes);
                 }
-                scheduleWriteLocked();
+                opModes.put(op, mode);
             }
+
+            scheduleFastWriteLocked();
+            listenersCopy = new ArrayList<>(mModeChangedListeners);
+        }
+
+        for (int i = 0; i < listenersCopy.size(); i++) {
+            listenersCopy.get(i).onPackageModeChanged(packageName, userId, op, mode);
         }
     }
 
@@ -353,348 +349,43 @@
     }
 
     @Override
-    public void startWatchingOpModeChanged(@NonNull OnOpModeChangedListener changedListener,
-            int op) {
-        Objects.requireNonNull(changedListener);
+    public SparseBooleanArray getForegroundOps(int uid) {
+        SparseBooleanArray result = new SparseBooleanArray();
         synchronized (mLock) {
-            ArraySet<OnOpModeChangedListener> modeWatcherSet = mOpModeWatchers.get(op);
-            if (modeWatcherSet == null) {
-                modeWatcherSet = new ArraySet<>();
-                mOpModeWatchers.put(op, modeWatcherSet);
+            SparseIntArray modes = mUidModes.get(uid);
+            if (modes == null) {
+                return result;
             }
-            modeWatcherSet.add(changedListener);
+            for (int i = 0; i < modes.size(); i++) {
+                if (modes.valueAt(i) == MODE_FOREGROUND) {
+                    result.put(modes.keyAt(i), true);
+                }
+            }
         }
+
+        return result;
     }
 
     @Override
-    public void startWatchingPackageModeChanged(@NonNull OnOpModeChangedListener changedListener,
-            @NonNull String packageName) {
-        Objects.requireNonNull(changedListener);
-        Objects.requireNonNull(packageName);
+    public SparseBooleanArray getForegroundOps(String packageName, int userId) {
+        SparseBooleanArray result = new SparseBooleanArray();
         synchronized (mLock) {
-            ArraySet<OnOpModeChangedListener> modeWatcherSet =
-                    mPackageModeWatchers.get(packageName);
-            if (modeWatcherSet == null) {
-                modeWatcherSet = new ArraySet<>();
-                mPackageModeWatchers.put(packageName, modeWatcherSet);
+            ArrayMap<String, SparseIntArray> packageModes = mUserPackageModes.get(userId);
+            if (packageModes == null) {
+                return result;
             }
-            modeWatcherSet.add(changedListener);
-        }
-    }
-
-    @Override
-    public void removeListener(@NonNull OnOpModeChangedListener changedListener) {
-        Objects.requireNonNull(changedListener);
-
-        synchronized (mLock) {
-            for (int i = mOpModeWatchers.size() - 1; i >= 0; i--) {
-                ArraySet<OnOpModeChangedListener> cbs = mOpModeWatchers.valueAt(i);
-                cbs.remove(changedListener);
-                if (cbs.size() <= 0) {
-                    mOpModeWatchers.removeAt(i);
-                }
+            SparseIntArray modes = packageModes.get(packageName);
+            if (modes == null) {
+                return result;
             }
-
-            for (int i = mPackageModeWatchers.size() - 1; i >= 0; i--) {
-                ArraySet<OnOpModeChangedListener> cbs = mPackageModeWatchers.valueAt(i);
-                cbs.remove(changedListener);
-                if (cbs.size() <= 0) {
-                    mPackageModeWatchers.removeAt(i);
-                }
-            }
-        }
-    }
-
-    @Override
-    public ArraySet<OnOpModeChangedListener> getOpModeChangedListeners(int op) {
-        synchronized (mLock) {
-            ArraySet<OnOpModeChangedListener> modeChangedListenersSet = mOpModeWatchers.get(op);
-            if (modeChangedListenersSet == null) {
-                return new ArraySet<>();
-            }
-            return new ArraySet<>(modeChangedListenersSet);
-        }
-    }
-
-    @Override
-    public ArraySet<OnOpModeChangedListener> getPackageModeChangedListeners(
-            @NonNull String packageName) {
-        Objects.requireNonNull(packageName);
-
-        synchronized (mLock) {
-            ArraySet<OnOpModeChangedListener> modeChangedListenersSet =
-                    mPackageModeWatchers.get(packageName);
-            if (modeChangedListenersSet == null) {
-                return new ArraySet<>();
-            }
-            return new ArraySet<>(modeChangedListenersSet);
-        }
-    }
-
-    @Override
-    public void notifyWatchersOfChange(int code, int uid) {
-        ArraySet<OnOpModeChangedListener> listenerSet = getOpModeChangedListeners(code);
-        if (listenerSet == null) {
-            return;
-        }
-        for (int i = 0; i < listenerSet.size(); i++) {
-            final OnOpModeChangedListener listener = listenerSet.valueAt(i);
-            notifyOpChanged(listener, code, uid, null);
-        }
-    }
-
-    @Override
-    public void notifyOpChanged(@NonNull OnOpModeChangedListener onModeChangedListener, int code,
-            int uid, @Nullable String packageName) {
-        Objects.requireNonNull(onModeChangedListener);
-
-        if (uid != UID_ANY && onModeChangedListener.getWatchingUid() >= 0
-                && onModeChangedListener.getWatchingUid() != uid) {
-            return;
-        }
-
-        // See CALL_BACK_ON_CHANGED_LISTENER_WITH_SWITCHED_OP_CHANGE
-        int[] switchedCodes;
-        if (onModeChangedListener.getWatchedOpCode() == ALL_OPS) {
-            switchedCodes = mSwitchedOps.get(code);
-        } else if (onModeChangedListener.getWatchedOpCode() == OP_NONE) {
-            switchedCodes = new int[]{code};
-        } else {
-            switchedCodes = new int[]{onModeChangedListener.getWatchedOpCode()};
-        }
-
-        for (int switchedCode : switchedCodes) {
-            // There are features watching for mode changes such as window manager
-            // and location manager which are in our process. The callbacks in these
-            // features may require permissions our remote caller does not have.
-            final long identity = Binder.clearCallingIdentity();
-            try {
-                if (shouldIgnoreCallback(switchedCode, onModeChangedListener.getCallingPid(),
-                        onModeChangedListener.getCallingUid())) {
-                    continue;
-                }
-                onModeChangedListener.onOpModeChanged(switchedCode, uid, packageName);
-            } catch (RemoteException e) {
-                /* ignore */
-            } finally {
-                Binder.restoreCallingIdentity(identity);
-            }
-        }
-    }
-
-    private boolean shouldIgnoreCallback(int op, int watcherPid, int watcherUid) {
-        // If it's a restricted read op, ignore it if watcher doesn't have manage ops permission,
-        // as watcher should not use this to signal if the value is changed.
-        return opRestrictsRead(op) && mContext.checkPermission(Manifest.permission.MANAGE_APPOPS,
-                watcherPid, watcherUid) != PackageManager.PERMISSION_GRANTED;
-    }
-
-    @Override
-    public void notifyOpChangedForAllPkgsInUid(int code, int uid, boolean onlyForeground,
-            @Nullable OnOpModeChangedListener callbackToIgnore) {
-        String[] uidPackageNames = getPackagesForUid(uid);
-        ArrayMap<OnOpModeChangedListener, ArraySet<String>> callbackSpecs = null;
-
-        synchronized (mLock) {
-            ArraySet<OnOpModeChangedListener> callbacks = mOpModeWatchers.get(code);
-            if (callbacks != null) {
-                final int callbackCount = callbacks.size();
-                for (int i = 0; i < callbackCount; i++) {
-                    OnOpModeChangedListener callback = callbacks.valueAt(i);
-
-                    if (onlyForeground && (callback.getFlags()
-                            & WATCH_FOREGROUND_CHANGES) == 0) {
-                        continue;
-                    }
-
-                    ArraySet<String> changedPackages = new ArraySet<>();
-                    Collections.addAll(changedPackages, uidPackageNames);
-                    if (callbackSpecs == null) {
-                        callbackSpecs = new ArrayMap<>();
-                    }
-                    callbackSpecs.put(callback, changedPackages);
-                }
-            }
-
-            for (String uidPackageName : uidPackageNames) {
-                callbacks = mPackageModeWatchers.get(uidPackageName);
-                if (callbacks != null) {
-                    if (callbackSpecs == null) {
-                        callbackSpecs = new ArrayMap<>();
-                    }
-                    final int callbackCount = callbacks.size();
-                    for (int i = 0; i < callbackCount; i++) {
-                        OnOpModeChangedListener callback = callbacks.valueAt(i);
-
-                        if (onlyForeground && (callback.getFlags()
-                                & WATCH_FOREGROUND_CHANGES) == 0) {
-                            continue;
-                        }
-
-                        ArraySet<String> changedPackages = callbackSpecs.get(callback);
-                        if (changedPackages == null) {
-                            changedPackages = new ArraySet<>();
-                            callbackSpecs.put(callback, changedPackages);
-                        }
-                        changedPackages.add(uidPackageName);
-                    }
-                }
-            }
-
-            if (callbackSpecs != null && callbackToIgnore != null) {
-                callbackSpecs.remove(callbackToIgnore);
-            }
-        }
-
-        if (callbackSpecs == null) {
-            return;
-        }
-
-        for (int i = 0; i < callbackSpecs.size(); i++) {
-            final OnOpModeChangedListener callback = callbackSpecs.keyAt(i);
-            final ArraySet<String> reportedPackageNames = callbackSpecs.valueAt(i);
-            if (reportedPackageNames == null) {
-                mHandler.sendMessage(PooledLambda.obtainMessage(
-                        AppOpsCheckingServiceImpl::notifyOpChanged,
-                        this, callback, code, uid, (String) null));
-
-            } else {
-                final int reportedPackageCount = reportedPackageNames.size();
-                for (int j = 0; j < reportedPackageCount; j++) {
-                    final String reportedPackageName = reportedPackageNames.valueAt(j);
-                    mHandler.sendMessage(PooledLambda.obtainMessage(
-                            AppOpsCheckingServiceImpl::notifyOpChanged,
-                            this, callback, code, uid, reportedPackageName));
-                }
-            }
-        }
-    }
-
-    private static String[] getPackagesForUid(int uid) {
-        String[] packageNames = null;
-
-        // Very early during boot the package manager is not yet or not yet fully started. At this
-        // time there are no packages yet.
-        if (AppGlobals.getPackageManager() != null) {
-            try {
-                packageNames = AppGlobals.getPackageManager().getPackagesForUid(uid);
-            } catch (RemoteException e) {
-                /* ignore - local call */
-            }
-        }
-        if (packageNames == null) {
-            return EmptyArray.STRING;
-        }
-        return packageNames;
-    }
-
-    @Override
-    public SparseBooleanArray evalForegroundUidOps(int uid, SparseBooleanArray foregroundOps) {
-        synchronized (mLock) {
-            return evalForegroundOps(mUidModes.get(uid), foregroundOps);
-        }
-    }
-
-    @Override
-    public SparseBooleanArray evalForegroundPackageOps(String packageName,
-            SparseBooleanArray foregroundOps, @UserIdInt int userId) {
-        synchronized (mLock) {
-            ArrayMap<String, SparseIntArray> packageModes = mUserPackageModes.get(userId, null);
-            return evalForegroundOps(packageModes == null ? null : packageModes.get(packageName),
-                    foregroundOps);
-        }
-    }
-
-    private SparseBooleanArray evalForegroundOps(SparseIntArray opModes,
-            SparseBooleanArray foregroundOps) {
-        SparseBooleanArray tempForegroundOps = foregroundOps;
-        if (opModes != null) {
-            for (int i = opModes.size() - 1; i >= 0; i--) {
-                if (opModes.valueAt(i) == AppOpsManager.MODE_FOREGROUND) {
-                    if (tempForegroundOps == null) {
-                        tempForegroundOps = new SparseBooleanArray();
-                    }
-                    evalForegroundWatchers(opModes.keyAt(i), tempForegroundOps);
-                }
-            }
-        }
-        return tempForegroundOps;
-    }
-
-    private void evalForegroundWatchers(int op, SparseBooleanArray foregroundOps) {
-        boolean curValue = foregroundOps.get(op, false);
-        ArraySet<OnOpModeChangedListener> listenerSet = mOpModeWatchers.get(op);
-        if (listenerSet != null) {
-            for (int cbi = listenerSet.size() - 1; !curValue && cbi >= 0; cbi--) {
-                if ((listenerSet.valueAt(cbi).getFlags()
-                        & AppOpsManager.WATCH_FOREGROUND_CHANGES) != 0) {
-                    curValue = true;
-                }
-            }
-        }
-        foregroundOps.put(op, curValue);
-    }
-
-    @Override
-    public boolean dumpListeners(int dumpOp, int dumpUid, String dumpPackage,
-            PrintWriter printWriter) {
-        boolean needSep = false;
-        if (mOpModeWatchers.size() > 0) {
-            boolean printedHeader = false;
-            for (int i = 0; i < mOpModeWatchers.size(); i++) {
-                if (dumpOp >= 0 && dumpOp != mOpModeWatchers.keyAt(i)) {
-                    continue;
-                }
-                boolean printedOpHeader = false;
-                ArraySet<OnOpModeChangedListener> modeChangedListenerSet =
-                        mOpModeWatchers.valueAt(i);
-                for (int j = 0; j < modeChangedListenerSet.size(); j++) {
-                    final OnOpModeChangedListener listener = modeChangedListenerSet.valueAt(j);
-                    if (dumpPackage != null
-                            && dumpUid != UserHandle.getAppId(listener.getWatchingUid())) {
-                        continue;
-                    }
-                    needSep = true;
-                    if (!printedHeader) {
-                        printWriter.println("  Op mode watchers:");
-                        printedHeader = true;
-                    }
-                    if (!printedOpHeader) {
-                        printWriter.print("    Op ");
-                        printWriter.print(AppOpsManager.opToName(mOpModeWatchers.keyAt(i)));
-                        printWriter.println(":");
-                        printedOpHeader = true;
-                    }
-                    printWriter.print("      #"); printWriter.print(j); printWriter.print(": ");
-                    printWriter.println(listener.toString());
+            for (int i = 0; i < modes.size(); i++) {
+                if (modes.valueAt(i) == MODE_FOREGROUND) {
+                    result.put(modes.keyAt(i), true);
                 }
             }
         }
 
-        if (mPackageModeWatchers.size() > 0 && dumpOp < 0) {
-            boolean printedHeader = false;
-            for (int i = 0; i < mPackageModeWatchers.size(); i++) {
-                if (dumpPackage != null
-                        && !dumpPackage.equals(mPackageModeWatchers.keyAt(i))) {
-                    continue;
-                }
-                needSep = true;
-                if (!printedHeader) {
-                    printWriter.println("  Package mode watchers:");
-                    printedHeader = true;
-                }
-                printWriter.print("    Pkg "); printWriter.print(mPackageModeWatchers.keyAt(i));
-                printWriter.println(":");
-                ArraySet<OnOpModeChangedListener> modeChangedListenerSet =
-                        mPackageModeWatchers.valueAt(i);
-
-                for (int j = 0; j < modeChangedListenerSet.size(); j++) {
-                    printWriter.print("      #"); printWriter.print(j); printWriter.print(": ");
-                    printWriter.println(modeChangedListenerSet.valueAt(j).toString());
-                }
-            }
-        }
-        return needSep;
+        return result;
     }
 
     private void scheduleWriteLocked() {
@@ -834,58 +525,7 @@
     public void readState() {
         synchronized (mFile) {
             synchronized (mLock) {
-                FileInputStream stream;
-                try {
-                    stream = mFile.openRead();
-                } catch (FileNotFoundException e) {
-                    Slog.i(TAG, "No existing app ops " + mFile.getBaseFile() + "; starting empty");
-                    mVersionAtBoot = NO_FILE_VERSION;
-                    return;
-                }
-
-                try {
-                    TypedXmlPullParser parser = Xml.resolvePullParser(stream);
-                    int type;
-                    while ((type = parser.next()) != XmlPullParser.START_TAG
-                            && type != XmlPullParser.END_DOCUMENT) {
-                        // Parse next until we reach the start or end
-                    }
-
-                    if (type != XmlPullParser.START_TAG) {
-                        throw new IllegalStateException("no start tag found");
-                    }
-
-                    mVersionAtBoot = parser.getAttributeInt(null, "v", NO_VERSION);
-
-                    int outerDepth = parser.getDepth();
-                    while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
-                            && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
-                        if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
-                            continue;
-                        }
-
-                        String tagName = parser.getName();
-                        if (tagName.equals("pkg")) {
-                            // version 2 has the structure pkg -> uid -> op ->
-                            // in version 3, since pkg and uid states are kept completely
-                            // independent we switch to user -> pkg -> op
-                            readPackage(parser);
-                        } else if (tagName.equals("uid")) {
-                            readUidOps(parser);
-                        } else if (tagName.equals("user")) {
-                            readUser(parser);
-                        } else {
-                            Slog.w(TAG, "Unknown element under <app-ops>: "
-                                    + parser.getName());
-                            XmlUtils.skipCurrentTag(parser);
-                        }
-                    }
-                    return;
-                } catch (XmlPullParserException e) {
-                    throw new RuntimeException(e);
-                } catch (IOException e) {
-                    throw new RuntimeException(e);
-                }
+                mVersionAtBoot = mAppOpsStateParser.readState(mFile, mUidModes, mUserPackageModes);
             }
         }
     }
@@ -907,162 +547,6 @@
     }
 
     @GuardedBy("mLock")
-    private void readUidOps(TypedXmlPullParser parser) throws NumberFormatException,
-            XmlPullParserException, IOException {
-        final int uid = parser.getAttributeInt(null, "n");
-        SparseIntArray modes = mUidModes.get(uid);
-        if (modes == null) {
-            modes = new SparseIntArray();
-            mUidModes.put(uid, modes);
-        }
-
-        int outerDepth = parser.getDepth();
-        int type;
-        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
-                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
-            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
-                continue;
-            }
-
-            String tagName = parser.getName();
-            if (tagName.equals("op")) {
-                final int code = parser.getAttributeInt(null, "n");
-                final int mode = parser.getAttributeInt(null, "m");
-
-                if (mode != opToDefaultMode(code)) {
-                    modes.put(code, mode);
-                }
-            } else {
-                Slog.w(TAG, "Unknown element under <uid>: "
-                        + parser.getName());
-                XmlUtils.skipCurrentTag(parser);
-            }
-        }
-    }
-
-    /*
-     * Used for migration when pkg is the depth=1 tag
-     */
-    @GuardedBy("mLock")
-    private void readPackage(TypedXmlPullParser parser)
-            throws NumberFormatException, XmlPullParserException, IOException {
-        String pkgName = parser.getAttributeValue(null, "n");
-        int outerDepth = parser.getDepth();
-        int type;
-        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
-                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
-            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
-                continue;
-            }
-
-            String tagName = parser.getName();
-            if (tagName.equals("uid")) {
-                readUid(parser, pkgName);
-            } else {
-                Slog.w(TAG, "Unknown element under <pkg>: "
-                        + parser.getName());
-                XmlUtils.skipCurrentTag(parser);
-            }
-        }
-    }
-
-    /*
-     * Used for migration when uid is the depth=2 tag
-     */
-    @GuardedBy("mLock")
-    private void readUid(TypedXmlPullParser parser, String pkgName)
-            throws NumberFormatException, XmlPullParserException, IOException {
-        int userId = UserHandle.getUserId(parser.getAttributeInt(null, "n"));
-        int outerDepth = parser.getDepth();
-        int type;
-        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
-                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
-            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
-                continue;
-            }
-
-            String tagName = parser.getName();
-            if (tagName.equals("op")) {
-                readOp(parser, userId, pkgName);
-            } else {
-                Slog.w(TAG, "Unknown element under <pkg>: "
-                        + parser.getName());
-                XmlUtils.skipCurrentTag(parser);
-            }
-        }
-    }
-
-    @GuardedBy("mLock")
-    private void readUser(TypedXmlPullParser parser)
-            throws NumberFormatException, XmlPullParserException, IOException {
-        int userId = parser.getAttributeInt(null, "n");
-        int outerDepth = parser.getDepth();
-        int type;
-        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
-                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
-            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
-                continue;
-            }
-
-            String tagName = parser.getName();
-            if (tagName.equals("pkg")) {
-                readPackage(parser, userId);
-            } else {
-                Slog.w(TAG, "Unknown element under <user>: "
-                        + parser.getName());
-                XmlUtils.skipCurrentTag(parser);
-            }
-        }
-    }
-
-    @GuardedBy("mLock")
-    private void readPackage(TypedXmlPullParser parser, int userId)
-            throws NumberFormatException, XmlPullParserException, IOException {
-        String pkgName = parser.getAttributeValue(null, "n");
-        int outerDepth = parser.getDepth();
-        int type;
-        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
-                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
-            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
-                continue;
-            }
-
-            String tagName = parser.getName();
-            if (tagName.equals("op")) {
-                readOp(parser, userId, pkgName);
-            } else {
-                Slog.w(TAG, "Unknown element under <pkg>: "
-                        + parser.getName());
-                XmlUtils.skipCurrentTag(parser);
-            }
-        }
-    }
-
-    @GuardedBy("mLock")
-    private void readOp(TypedXmlPullParser parser, int userId, @NonNull String pkgName)
-            throws NumberFormatException, XmlPullParserException {
-        final int opCode = parser.getAttributeInt(null, "n");
-        final int defaultMode = AppOpsManager.opToDefaultMode(opCode);
-        final int mode = parser.getAttributeInt(null, "m", defaultMode);
-
-        if (mode != defaultMode) {
-            ArrayMap<String, SparseIntArray> packageModes = mUserPackageModes.get(userId);
-            if (packageModes == null) {
-                packageModes = new ArrayMap<>();
-                mUserPackageModes.put(userId, packageModes);
-            }
-
-            SparseIntArray modes = packageModes.get(pkgName);
-            if (modes == null) {
-                modes = new SparseIntArray();
-                packageModes.put(pkgName, modes);
-            }
-
-            modes.put(opCode, mode);
-        }
-    }
-
-    @GuardedBy("mLock")
     private void upgradeLocked(int oldVersion) {
         if (oldVersion == NO_FILE_VERSION || oldVersion >= CURRENT_VERSION) {
             return;
@@ -1217,4 +701,18 @@
 
         return result;
     }
-}
\ No newline at end of file
+
+    @Override
+    public boolean addAppOpsModeChangedListener(AppOpsModeChangedListener listener) {
+        synchronized (mLock) {
+            return mModeChangedListeners.add(listener);
+        }
+    }
+
+    @Override
+    public boolean removeAppOpsModeChangedListener(AppOpsModeChangedListener listener) {
+        synchronized (mLock) {
+            return mModeChangedListeners.remove(listener);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/appop/AppOpsCheckingServiceInterface.java b/services/core/java/com/android/server/appop/AppOpsCheckingServiceInterface.java
index 9096898..60d17cd 100644
--- a/services/core/java/com/android/server/appop/AppOpsCheckingServiceInterface.java
+++ b/services/core/java/com/android/server/appop/AppOpsCheckingServiceInterface.java
@@ -16,17 +16,13 @@
 package com.android.server.appop;
 
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.annotation.UserIdInt;
 import android.app.AppOpsManager.Mode;
-import android.util.ArraySet;
 import android.util.SparseBooleanArray;
 import android.util.SparseIntArray;
 
 import com.android.internal.annotations.VisibleForTesting;
 
-import java.io.PrintWriter;
-
 /**
  * Interface for accessing and modifying modes for app-ops i.e. package and uid modes.
  * This interface also includes functions for added and removing op mode watchers.
@@ -148,99 +144,60 @@
     void clearAllModes();
 
     /**
-     * Registers changedListener to listen to op's mode change.
-     * @param changedListener the listener that must be trigger on the op's mode change.
-     * @param op op representing the app-op whose mode change needs to be listened to.
+     * @param uid UID 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.
      */
-    void startWatchingOpModeChanged(@NonNull OnOpModeChangedListener changedListener, int op);
+    SparseBooleanArray getForegroundOps(int uid);
 
     /**
-     * Registers changedListener to listen to package's app-op's mode change.
-     * @param changedListener the listener that must be trigger on the mode change.
-     * @param packageName of the package whose app-op's mode change needs to be listened to.
+     *
+     * @param packageName Package name to check for.
+     * @param userId User ID to check for.
+     * @return SparseBooleanArray where the keys are the op codes for which their modes are
+     * MODE_FOREGROUND for the passed package name and user ID.
      */
-    void startWatchingPackageModeChanged(@NonNull OnOpModeChangedListener changedListener,
-            @NonNull String packageName);
+    SparseBooleanArray getForegroundOps(String packageName, int userId);
 
     /**
-     * Stop the changedListener from triggering on any mode change.
-     * @param changedListener the listener that needs to be removed.
+     * Adds a listener for changes in appop modes. These callbacks should be dispatched
+     * synchronously.
+     *
+     * @param listener The listener to be added.
+     * @return true if the listener was added.
      */
-    void removeListener(@NonNull OnOpModeChangedListener changedListener);
+    boolean addAppOpsModeChangedListener(@NonNull AppOpsModeChangedListener listener);
 
     /**
-     * Temporary API which will be removed once we can safely untangle the methods that use this.
-     * Returns a set of OnOpModeChangedListener that are listening for op's mode changes.
-     * @param op app-op whose mode change is being listened to.
+     * Removes a listener for changes in appop modes.
+     *
+     * @param listener The listener to be removed.
+     * @return true if the listener was removed.
      */
-    ArraySet<OnOpModeChangedListener> getOpModeChangedListeners(int op);
+    boolean removeAppOpsModeChangedListener(@NonNull AppOpsModeChangedListener listener);
 
     /**
-     * Temporary API which will be removed once we can safely untangle the methods that use this.
-     * Returns a set of OnOpModeChangedListener that are listening for package's op's mode changes.
-     * @param packageName of package whose app-op's mode change is being listened to.
+     * A listener for changes to the AppOps mode.
      */
-    ArraySet<OnOpModeChangedListener> getPackageModeChangedListeners(@NonNull String packageName);
+    interface AppOpsModeChangedListener {
 
-    /**
-     * Temporary API which will be removed once we can safely untangle the methods that use this.
-     * Notify that the app-op's mode is changed by triggering the change listener.
-     * @param op App-op whose mode has changed
-     * @param uid user id associated with the app-op (or, if UID_ANY, notifies all users)
-     */
-    void notifyWatchersOfChange(int op, int uid);
+        /**
+         * Invoked when a UID's appop mode is changed.
+         *
+         * @param uid The UID whose appop mode was changed.
+         * @param code The op code that was changed.
+         * @param mode The new mode.
+         */
+        void onUidModeChanged(int uid, int code, int mode);
 
-    /**
-     * Temporary API which will be removed once we can safely untangle the methods that use this.
-     * Notify that the app-op's mode is changed by triggering the change listener.
-     * @param changedListener the change listener.
-     * @param op App-op whose mode has changed
-     * @param uid user id associated with the app-op
-     * @param packageName package name that is associated with the app-op
-     */
-    void notifyOpChanged(@NonNull OnOpModeChangedListener changedListener, int op, int uid,
-            @Nullable String packageName);
-
-    /**
-     * Temporary API which will be removed once we can safely untangle the methods that use this.
-     * Notify that the app-op's mode is changed to all packages associated with the uid by
-     * triggering the appropriate change listener.
-     * @param op App-op whose mode has changed
-     * @param uid user id associated with the app-op
-     * @param onlyForeground true if only watchers that
-     * @param callbackToIgnore callback that should be ignored.
-     */
-    void notifyOpChangedForAllPkgsInUid(int op, int uid, boolean onlyForeground,
-            @Nullable OnOpModeChangedListener callbackToIgnore);
-
-    /**
-     * TODO: Move hasForegroundWatchers and foregroundOps into this.
-     * Go over the list of app-ops for the uid and mark app-ops with MODE_FOREGROUND in
-     * foregroundOps.
-     * @param uid for which the app-op's mode needs to be marked.
-     * @param foregroundOps boolean array where app-ops that have MODE_FOREGROUND are marked true.
-     * @return  foregroundOps.
-     */
-    SparseBooleanArray evalForegroundUidOps(int uid, SparseBooleanArray foregroundOps);
-
-    /**
-     * Go over the list of app-ops for the package name and mark app-ops with MODE_FOREGROUND in
-     * foregroundOps.
-     * @param packageName for which the app-op's mode needs to be marked.
-     * @param foregroundOps boolean array where app-ops that have MODE_FOREGROUND are marked true.
-     * @param userId user id associated with the package.
-     * @return foregroundOps.
-     */
-    SparseBooleanArray evalForegroundPackageOps(String packageName,
-            SparseBooleanArray foregroundOps, @UserIdInt int userId);
-
-    /**
-     * Dump op mode and package mode listeners and their details.
-     * @param dumpOp if -1 then op mode listeners for all app-ops are dumped. If it's set to an
-     *               app-op, only the watchers for that app-op are dumped.
-     * @param dumpUid uid for which we want to dump op mode watchers.
-     * @param dumpPackage if not null and if dumpOp is -1, dumps watchers for the package name.
-     * @param printWriter writer to dump to.
-     */
-    boolean dumpListeners(int dumpOp, int dumpUid, String dumpPackage, PrintWriter printWriter);
+        /**
+         * Invoked when a package's appop mode is changed.
+         *
+         * @param packageName The package name whose appop mode was changed.
+         * @param userId The user ID for the package.
+         * @param code The op code that was changed.
+         * @param mode The new mode.
+         */
+        void onPackageModeChanged(@NonNull String packageName, int userId, int code, int mode);
+    }
 }
diff --git a/services/core/java/com/android/server/appop/AppOpsCheckingServiceLoggingDecorator.java b/services/core/java/com/android/server/appop/AppOpsCheckingServiceLoggingDecorator.java
index 0094b86..3fee59b 100644
--- a/services/core/java/com/android/server/appop/AppOpsCheckingServiceLoggingDecorator.java
+++ b/services/core/java/com/android/server/appop/AppOpsCheckingServiceLoggingDecorator.java
@@ -17,14 +17,10 @@
 package com.android.server.appop;
 
 import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.util.ArraySet;
 import android.util.Log;
 import android.util.SparseBooleanArray;
 import android.util.SparseIntArray;
 
-import java.io.PrintWriter;
-
 /**
  * Logging decorator for {@link AppOpsCheckingServiceInterface}.
  */
@@ -134,83 +130,27 @@
     }
 
     @Override
-    public void startWatchingOpModeChanged(@NonNull OnOpModeChangedListener changedListener,
-            int op) {
-        Log.i(LOG_TAG, "startWatchingOpModeChanged(changedListener = " + changedListener + ", op = "
-                + op + ")");
-        mService.startWatchingOpModeChanged(changedListener, op);
+    public SparseBooleanArray getForegroundOps(int uid) {
+        Log.i(LOG_TAG, "getForegroundOps(uid = " + uid + ")");
+        return mService.getForegroundOps(uid);
     }
 
     @Override
-    public void startWatchingPackageModeChanged(@NonNull OnOpModeChangedListener changedListener,
-            @NonNull String packageName) {
-        Log.i(LOG_TAG, "startWatchingPackageModeChanged(changedListener = " + changedListener
-                + ", packageName = " + packageName + ")");
-        mService.startWatchingPackageModeChanged(changedListener, packageName);
-    }
-
-    @Override
-    public void removeListener(@NonNull OnOpModeChangedListener changedListener) {
-        Log.i(LOG_TAG, "removeListener(changedListener = " + changedListener + ")");
-        mService.removeListener(changedListener);
-    }
-
-    @Override
-    public ArraySet<OnOpModeChangedListener> getOpModeChangedListeners(int op) {
-        Log.i(LOG_TAG, "getOpModeChangedListeners(op = " + op + ")");
-        return mService.getOpModeChangedListeners(op);
-    }
-
-    @Override
-    public ArraySet<OnOpModeChangedListener> getPackageModeChangedListeners(
-            @NonNull String packageName) {
-        Log.i(LOG_TAG, "getPackageModeChangedListeners(packageName = " + packageName + ")");
-        return mService.getPackageModeChangedListeners(packageName);
-    }
-
-    @Override
-    public void notifyWatchersOfChange(int op, int uid) {
-        Log.i(LOG_TAG, "notifyWatchersOfChange(op = " + op + ", uid = " + uid + ")");
-        mService.notifyWatchersOfChange(op, uid);
-    }
-
-    @Override
-    public void notifyOpChanged(@NonNull OnOpModeChangedListener changedListener, int op, int uid,
-            @Nullable String packageName) {
-        Log.i(LOG_TAG, "notifyOpChanged(changedListener = " + changedListener + ", op = " + op
-                + ", uid = " + uid + ", packageName = " + packageName + ")");
-        mService.notifyOpChanged(changedListener, op, uid, packageName);
-    }
-
-    @Override
-    public void notifyOpChangedForAllPkgsInUid(int op, int uid, boolean onlyForeground,
-            @Nullable OnOpModeChangedListener callbackToIgnore) {
-        Log.i(LOG_TAG, "notifyOpChangedForAllPkgsInUid(op = " + op + ", uid = " + uid
-                + ", onlyForeground = " + onlyForeground + ", callbackToIgnore = "
-                + callbackToIgnore + ")");
-        mService.notifyOpChangedForAllPkgsInUid(op, uid, onlyForeground, callbackToIgnore);
-    }
-
-    @Override
-    public SparseBooleanArray evalForegroundUidOps(int uid, SparseBooleanArray foregroundOps) {
-        Log.i(LOG_TAG, "evalForegroundUidOps(uid = " + uid + ", foregroundOps = " + foregroundOps
+    public SparseBooleanArray getForegroundOps(String packageName, int userId) {
+        Log.i(LOG_TAG, "getForegroundOps(packageName = " + packageName + ", userId = " + userId
                 + ")");
-        return mService.evalForegroundUidOps(uid, foregroundOps);
+        return mService.getForegroundOps(packageName, userId);
     }
 
     @Override
-    public SparseBooleanArray evalForegroundPackageOps(String packageName,
-            SparseBooleanArray foregroundOps, int userId) {
-        Log.i(LOG_TAG, "evalForegroundPackageOps(packageName = " + packageName
-                + ", foregroundOps = " + foregroundOps + ", userId = " + userId + ")");
-        return mService.evalForegroundPackageOps(packageName, foregroundOps, userId);
+    public boolean addAppOpsModeChangedListener(AppOpsModeChangedListener listener) {
+        Log.i(LOG_TAG, "addAppOpsModeChangedListener(listener = " + listener + ")");
+        return mService.addAppOpsModeChangedListener(listener);
     }
 
     @Override
-    public boolean dumpListeners(int dumpOp, int dumpUid, String dumpPackage,
-            PrintWriter printWriter) {
-        Log.i(LOG_TAG, "dumpListeners(dumpOp = " + dumpOp + ", dumpUid = " + dumpUid
-                + ", dumpPackage = " + dumpPackage + ", printWriter = " + printWriter + ")");
-        return mService.dumpListeners(dumpOp, dumpUid, dumpPackage, printWriter);
+    public boolean removeAppOpsModeChangedListener(AppOpsModeChangedListener listener) {
+        Log.i(LOG_TAG, "removeAppOpsModeChangedListener(listener = " + listener + ")");
+        return mService.removeAppOpsModeChangedListener(listener);
     }
 }
diff --git a/services/core/java/com/android/server/appop/AppOpsCheckingServiceTracingDecorator.java b/services/core/java/com/android/server/appop/AppOpsCheckingServiceTracingDecorator.java
index a028ae1..c0cc8b1 100644
--- a/services/core/java/com/android/server/appop/AppOpsCheckingServiceTracingDecorator.java
+++ b/services/core/java/com/android/server/appop/AppOpsCheckingServiceTracingDecorator.java
@@ -17,16 +17,12 @@
 package com.android.server.appop;
 
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.annotation.UserIdInt;
 import android.app.AppOpsManager;
 import android.os.Trace;
-import android.util.ArraySet;
 import android.util.SparseBooleanArray;
 import android.util.SparseIntArray;
 
-import java.io.PrintWriter;
-
 /**
  * Surrounds all AppOpsCheckingServiceInterface method calls with Trace.traceBegin and
  * Trace.traceEnd. These traces are used for performance testing.
@@ -205,128 +201,44 @@
     }
 
     @Override
-    public void startWatchingOpModeChanged(@NonNull OnOpModeChangedListener changedListener,
-            int op) {
+    public SparseBooleanArray getForegroundOps(int uid) {
         Trace.traceBegin(TRACE_TAG,
-                "TaggedTracingAppOpsCheckingServiceInterfaceImpl#startWatchingOpModeChanged");
+                "TaggedTracingAppOpsCheckingServiceInterfaceImpl#getForegroundOps");
         try {
-            mService.startWatchingOpModeChanged(changedListener, op);
+            return mService.getForegroundOps(uid);
         } finally {
             Trace.traceEnd(TRACE_TAG);
         }
     }
 
     @Override
-    public void startWatchingPackageModeChanged(@NonNull OnOpModeChangedListener changedListener,
-            @NonNull String packageName) {
+    public SparseBooleanArray getForegroundOps(String packageName, int userId) {
         Trace.traceBegin(TRACE_TAG,
-                "TaggedTracingAppOpsCheckingServiceInterfaceImpl#startWatchingPackageModeChanged");
+                "TaggedTracingAppOpsCheckingServiceInterfaceImpl#getForegroundOps");
         try {
-            mService.startWatchingPackageModeChanged(changedListener, packageName);
+            return mService.getForegroundOps(packageName, userId);
         } finally {
             Trace.traceEnd(TRACE_TAG);
         }
     }
 
     @Override
-    public void removeListener(@NonNull OnOpModeChangedListener changedListener) {
+    public boolean addAppOpsModeChangedListener(AppOpsModeChangedListener listener) {
         Trace.traceBegin(TRACE_TAG,
-                "TaggedTracingAppOpsCheckingServiceInterfaceImpl#removeListener");
+                "TaggedTracingAppOpsCheckingServiceInterfaceImpl#addAppOpsModeChangedListener");
         try {
-            mService.removeListener(changedListener);
+            return mService.addAppOpsModeChangedListener(listener);
         } finally {
             Trace.traceEnd(TRACE_TAG);
         }
     }
 
     @Override
-    public ArraySet<OnOpModeChangedListener> getOpModeChangedListeners(int op) {
+    public boolean removeAppOpsModeChangedListener(AppOpsModeChangedListener listener) {
         Trace.traceBegin(TRACE_TAG,
-                "TaggedTracingAppOpsCheckingServiceInterfaceImpl#getOpModeChangedListeners");
+                "TaggedTracingAppOpsCheckingServiceInterfaceImpl#removeAppOpsModeChangedListener");
         try {
-            return mService.getOpModeChangedListeners(op);
-        } finally {
-            Trace.traceEnd(TRACE_TAG);
-        }
-    }
-
-    @Override
-    public ArraySet<OnOpModeChangedListener> getPackageModeChangedListeners(
-            @NonNull String packageName) {
-        Trace.traceBegin(TRACE_TAG,
-                "TaggedTracingAppOpsCheckingServiceInterfaceImpl#getPackageModeChangedListeners");
-        try {
-            return mService.getPackageModeChangedListeners(packageName);
-        } finally {
-            Trace.traceEnd(TRACE_TAG);
-        }
-    }
-
-    @Override
-    public void notifyWatchersOfChange(int op, int uid) {
-        Trace.traceBegin(TRACE_TAG,
-                "TaggedTracingAppOpsCheckingServiceInterfaceImpl#notifyWatchersOfChange");
-        try {
-            mService.notifyWatchersOfChange(op, uid);
-        } finally {
-            Trace.traceEnd(TRACE_TAG);
-        }
-    }
-
-    @Override
-    public void notifyOpChanged(@NonNull OnOpModeChangedListener changedListener, int op, int uid,
-            @Nullable String packageName) {
-        Trace.traceBegin(TRACE_TAG,
-                "TaggedTracingAppOpsCheckingServiceInterfaceImpl#notifyOpChanged");
-        try {
-            mService.notifyOpChanged(changedListener, op, uid, packageName);
-        } finally {
-            Trace.traceEnd(TRACE_TAG);
-        }
-    }
-
-    @Override
-    public void notifyOpChangedForAllPkgsInUid(int op, int uid, boolean onlyForeground,
-            @Nullable OnOpModeChangedListener callbackToIgnore) {
-        Trace.traceBegin(TRACE_TAG,
-                "TaggedTracingAppOpsCheckingServiceInterfaceImpl#notifyOpChangedForAllPkgsInUid");
-        try {
-            mService.notifyOpChangedForAllPkgsInUid(op, uid, onlyForeground, callbackToIgnore);
-        } finally {
-            Trace.traceEnd(TRACE_TAG);
-        }
-    }
-
-    @Override
-    public SparseBooleanArray evalForegroundUidOps(int uid, SparseBooleanArray foregroundOps) {
-        Trace.traceBegin(TRACE_TAG,
-                "TaggedTracingAppOpsCheckingServiceInterfaceImpl#evalForegroundUidOps");
-        try {
-            return mService.evalForegroundUidOps(uid, foregroundOps);
-        } finally {
-            Trace.traceEnd(TRACE_TAG);
-        }
-    }
-
-    @Override
-    public SparseBooleanArray evalForegroundPackageOps(String packageName,
-            SparseBooleanArray foregroundOps, @UserIdInt int userId) {
-        Trace.traceBegin(TRACE_TAG,
-                "TaggedTracingAppOpsCheckingServiceInterfaceImpl#evalForegroundPackageOps");
-        try {
-            return mService.evalForegroundPackageOps(packageName, foregroundOps, userId);
-        } finally {
-            Trace.traceEnd(TRACE_TAG);
-        }
-    }
-
-    @Override
-    public boolean dumpListeners(int dumpOp, int dumpUid, String dumpPackage,
-            PrintWriter printWriter) {
-        Trace.traceBegin(TRACE_TAG,
-                "TaggedTracingAppOpsCheckingServiceInterfaceImpl#dumpListeners");
-        try {
-            return mService.dumpListeners(dumpOp, dumpUid, dumpPackage, printWriter);
+            return mService.removeAppOpsModeChangedListener(listener);
         } finally {
             Trace.traceEnd(TRACE_TAG);
         }
diff --git a/services/core/java/com/android/server/appop/AppOpsRestrictions.java b/services/core/java/com/android/server/appop/AppOpsRestrictions.java
index f7ccd34..0241d02 100644
--- a/services/core/java/com/android/server/appop/AppOpsRestrictions.java
+++ b/services/core/java/com/android/server/appop/AppOpsRestrictions.java
@@ -144,4 +144,11 @@
      */
     void dumpRestrictions(PrintWriter printWriter, int dumpOp, String dumpPackage,
             boolean showUserRestrictions);
+
+    /**
+     * Listener for when an appop restriction is removed.
+     */
+    interface AppOpsRestrictionRemovedListener {
+        void onAppOpsRestrictionRemoved(int code);
+    }
 }
diff --git a/services/core/java/com/android/server/appop/AppOpsRestrictionsImpl.java b/services/core/java/com/android/server/appop/AppOpsRestrictionsImpl.java
index f51200f2..ae93991 100644
--- a/services/core/java/com/android/server/appop/AppOpsRestrictionsImpl.java
+++ b/services/core/java/com/android/server/appop/AppOpsRestrictionsImpl.java
@@ -42,7 +42,8 @@
 
     private Context mContext;
     private Handler mHandler;
-    private AppOpsCheckingServiceInterface mAppOpsCheckingServiceInterface;
+
+    private AppOpsRestrictionRemovedListener mAppOpsRestrictionRemovedListener;
 
     // Map from (Object token) to (int code) to (boolean restricted)
     private final ArrayMap<Object, SparseBooleanArray> mGlobalRestrictions = new ArrayMap<>();
@@ -56,10 +57,10 @@
             mUserRestrictionExcludedPackageTags = new ArrayMap<>();
 
     public AppOpsRestrictionsImpl(Context context, Handler handler,
-            AppOpsCheckingServiceInterface appOpsCheckingServiceInterface) {
+            AppOpsRestrictionRemovedListener appOpsRestrictionRemovedListener) {
         mContext = context;
         mHandler = handler;
-        mAppOpsCheckingServiceInterface = appOpsCheckingServiceInterface;
+        mAppOpsRestrictionRemovedListener = appOpsRestrictionRemovedListener;
     }
 
     @Override
@@ -211,15 +212,11 @@
         return allRestrictedCodes;
     }
 
-    // TODO: For clearUserRestrictions, we are calling notifyOpChanged from within the
-    //  LegacyAppOpsServiceInterfaceImpl class. But, for all other changes to restrictions, we're
-    //  calling it from within AppOpsService. This is awkward, and we should probably do it one
-    //  way or the other.
     private void notifyAllUserRestrictions(SparseBooleanArray allUserRestrictedCodes) {
         int restrictedCodesSize = allUserRestrictedCodes.size();
         for (int j = 0; j < restrictedCodesSize; j++) {
             int code = allUserRestrictedCodes.keyAt(j);
-            mHandler.post(() -> mAppOpsCheckingServiceInterface.notifyWatchersOfChange(code, UID_ANY));
+            mHandler.post(() -> mAppOpsRestrictionRemovedListener.onAppOpsRestrictionRemoved(code));
         }
     }
 
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index a110169..4ee4c30 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -54,6 +54,7 @@
 import static android.app.AppOpsManager.SAMPLING_STRATEGY_UNIFORM;
 import static android.app.AppOpsManager.SAMPLING_STRATEGY_UNIFORM_OPS;
 import static android.app.AppOpsManager.SECURITY_EXCEPTION_ON_INVALID_ATTRIBUTION_TAG_CHANGE;
+import static android.app.AppOpsManager.WATCH_FOREGROUND_CHANGES;
 import static android.app.AppOpsManager._NUM_OP;
 import static android.app.AppOpsManager.extractFlagsFromKey;
 import static android.app.AppOpsManager.extractUidStateFromKey;
@@ -232,6 +233,15 @@
     private static final int MAX_UNUSED_POOLED_OBJECTS = 3;
     private static final int RARELY_USED_PACKAGES_INITIALIZATION_DELAY_MILLIS = 300000;
 
+    /* Temporary solution before Uidstate class is removed. These uids get their modes set. */
+    private static final int[] NON_PACKAGE_UIDS = new int[]{
+            Process.ROOT_UID,
+            Process.PHONE_UID,
+            Process.BLUETOOTH_UID,
+            Process.NFC_UID,
+            Process.NETWORK_STACK_UID,
+            Process.SHELL_UID};
+
     final Context mContext;
     final AtomicFile mStorageFile;
     final AtomicFile mRecentAccessesFile;
@@ -286,6 +296,11 @@
     private final ArrayMap<Pair<String, Integer>, ArrayList<AsyncNotedAppOp>>
             mUnforwardedAsyncNotedOps = new ArrayMap<>();
 
+    private final SparseArray<ArraySet<OnOpModeChangedListener>> mOpModeWatchers =
+            new SparseArray<>();
+    private final ArrayMap<String, ArraySet<OnOpModeChangedListener>> mPackageModeWatchers =
+            new ArrayMap<>();
+
     boolean mWriteNoteOpsScheduled;
 
     boolean mWriteScheduled;
@@ -309,6 +324,8 @@
     @GuardedBy("this")
     @VisibleForTesting
     final SparseArray<UidState> mUidStates = new SparseArray<>();
+    @GuardedBy("this")
+    private boolean mUidStatesInitialized;
 
     volatile @NonNull HistoricalRegistry mHistoricalRegistry = new HistoricalRegistry(this);
 
@@ -335,8 +352,6 @@
       */
     private final SparseArray<int[]> mSwitchedOps = new SparseArray<>();
 
-    private ActivityManagerInternal mActivityManagerInternal;
-
     /** Package sampled for message collection in the current session */
     @GuardedBy("this")
     private String mSampledPackage = null;
@@ -384,6 +399,10 @@
 
     private AppOpsUidStateTracker mUidStateTracker;
 
+    /** Callback to skip on next appop update.*/
+    @GuardedBy("this")
+    private IAppOpsCallback mIgnoredCallback = null;
+
     /** Hands the definition of foreground and uid states */
     @GuardedBy("this")
     public AppOpsUidStateTracker getUidStateTracker() {
@@ -499,11 +518,6 @@
         @NonNull
         public final ArrayMap<String, Ops> pkgOps = new ArrayMap<>();
 
-        // true indicates there is an interested observer, false there isn't but it has such an op
-        //TODO: Move foregroundOps and hasForegroundWatchers into the AppOpsServiceInterface.
-        public SparseBooleanArray foregroundOps;
-        public boolean hasForegroundWatchers;
-
         public UidState(int uid) {
             this.uid = uid;
         }
@@ -534,25 +548,6 @@
             return getUidStateTracker().evalMode(uid, op, mode);
         }
 
-        public void evalForegroundOps() {
-            foregroundOps = null;
-            foregroundOps = mAppOpsCheckingService.evalForegroundUidOps(uid, foregroundOps);
-            for (int i = pkgOps.size() - 1; i >= 0; i--) {
-                foregroundOps = mAppOpsCheckingService
-                        .evalForegroundPackageOps(pkgOps.valueAt(i).packageName, foregroundOps,
-                                UserHandle.getUserId(uid));
-            }
-            hasForegroundWatchers = false;
-            if (foregroundOps != null) {
-                for (int i = 0;  i < foregroundOps.size(); i++) {
-                    if (foregroundOps.valueAt(i)) {
-                        hasForegroundWatchers = true;
-                        break;
-                    }
-                }
-            }
-        }
-
         @SuppressWarnings("GuardedBy")
         public int getState() {
             return getUidStateTracker().getUidState(uid);
@@ -929,12 +924,33 @@
             mSwitchedOps.put(switchCode,
                     ArrayUtils.appendInt(mSwitchedOps.get(switchCode), switchedCode));
         }
-        mAppOpsCheckingService = new AppOpsCheckingServiceTracingDecorator(
-                new AppOpsCheckingServiceImpl(
-                        storageFile, this, handler, context,  mSwitchedOps));
-        //mAppOpsCheckingService = new AppOpsCheckingServiceLoggingDecorator(
-        //        LocalServices.getService(AppOpsCheckingServiceInterface.class));
-        mAppOpsRestrictions = new AppOpsRestrictionsImpl(context, handler, mAppOpsCheckingService);
+        if (PermissionManager.USE_ACCESS_CHECKING_SERVICE) {
+            mAppOpsCheckingService = new AppOpsCheckingServiceTracingDecorator(
+                    LocalServices.getService(AppOpsCheckingServiceInterface.class));
+        } else {
+            mAppOpsCheckingService = new AppOpsCheckingServiceTracingDecorator(
+                    new AppOpsCheckingServiceImpl(storageFile, this, handler, context,
+                            mSwitchedOps));
+        }
+        mAppOpsCheckingService.addAppOpsModeChangedListener(
+                new AppOpsCheckingServiceInterface.AppOpsModeChangedListener() {
+                    @Override
+                    public void onUidModeChanged(int uid, int code, int mode) {
+                        mHandler.sendMessage(PooledLambda.obtainMessage(
+                                AppOpsService::notifyOpChangedForAllPkgsInUid, AppOpsService.this,
+                                code, uid, false));
+                    }
+
+                    @Override
+                    public void onPackageModeChanged(String packageName, int userId, int code,
+                            int mode) {
+                        mHandler.sendMessage(PooledLambda.obtainMessage(
+                                AppOpsService::notifyOpChangedForPkg, AppOpsService.this,
+                                packageName, code, mode, userId));
+                    }
+                });
+        mAppOpsRestrictions = new AppOpsRestrictionsImpl(context, handler,
+                code -> notifyWatchersOfChange(code, UID_ANY));
 
         LockGuard.installLock(this, LockGuard.INDEX_APP_OPS);
         mStorageFile = new AtomicFile(storageFile, "appops_legacy");
@@ -1059,7 +1075,7 @@
                 UidState uidState = mUidStates.valueAt(uidNum);
 
                 String[] pkgsInUid = getPackagesForUid(uidState.uid);
-                if (ArrayUtils.isEmpty(pkgsInUid)) {
+                if (ArrayUtils.isEmpty(pkgsInUid) && uid >= Process.FIRST_APPLICATION_UID) {
                     uidState.clear();
                     mUidStates.removeAt(uidNum);
                     scheduleFastWriteLocked();
@@ -1088,6 +1104,64 @@
             }
         }
 
+        prepareInternalCallbacks();
+
+        final IntentFilter packageSuspendFilter = new IntentFilter();
+        packageSuspendFilter.addAction(Intent.ACTION_PACKAGES_UNSUSPENDED);
+        packageSuspendFilter.addAction(Intent.ACTION_PACKAGES_SUSPENDED);
+        mContext.registerReceiverAsUser(new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                final int[] changedUids = intent.getIntArrayExtra(Intent.EXTRA_CHANGED_UID_LIST);
+                final String[] changedPkgs = intent.getStringArrayExtra(
+                        Intent.EXTRA_CHANGED_PACKAGE_LIST);
+                for (int code : OPS_RESTRICTED_ON_SUSPEND) {
+                    ArraySet<OnOpModeChangedListener> onModeChangedListeners;
+                    synchronized (AppOpsService.this) {
+                        onModeChangedListeners = mOpModeWatchers.get(code);
+                        if (onModeChangedListeners == null) {
+                            continue;
+                        }
+                    }
+                    for (int i = 0; i < changedUids.length; i++) {
+                        final int changedUid = changedUids[i];
+                        final String changedPkg = changedPkgs[i];
+                        // We trust packagemanager to insert matching uid and packageNames in the
+                        // extras
+                        notifyOpChanged(onModeChangedListeners, code, changedUid, changedPkg);
+                    }
+                }
+            }
+        }, UserHandle.ALL, packageSuspendFilter, null, null);
+
+        mHandler.postDelayed(new Runnable() {
+            @Override
+            public void run() {
+                List<String> packageNames = getPackageListAndResample();
+                initializeRarelyUsedPackagesList(new ArraySet<>(packageNames));
+            }
+        }, RARELY_USED_PACKAGES_INITIALIZATION_DELAY_MILLIS);
+
+        getPackageManagerInternal().setExternalSourcesPolicy(
+                new PackageManagerInternal.ExternalSourcesPolicy() {
+                    @Override
+                    public int getPackageTrustedToInstallApps(String packageName, int uid) {
+                        int appOpMode = checkOperation(AppOpsManager.OP_REQUEST_INSTALL_PACKAGES,
+                                uid, packageName);
+                        switch (appOpMode) {
+                            case AppOpsManager.MODE_ALLOWED:
+                                return PackageManagerInternal.ExternalSourcesPolicy.USER_TRUSTED;
+                            case AppOpsManager.MODE_ERRORED:
+                                return PackageManagerInternal.ExternalSourcesPolicy.USER_BLOCKED;
+                            default:
+                                return PackageManagerInternal.ExternalSourcesPolicy.USER_DEFAULT;
+                        }
+                    }
+                });
+    }
+
+    @VisibleForTesting
+    void prepareInternalCallbacks() {
         getUserManagerInternal().addUserLifecycleListener(
                 new UserManagerInternal.UserLifecycleListener() {
                     @Override
@@ -1133,62 +1207,6 @@
                         }
                     }
                 });
-
-        final IntentFilter packageSuspendFilter = new IntentFilter();
-        packageSuspendFilter.addAction(Intent.ACTION_PACKAGES_UNSUSPENDED);
-        packageSuspendFilter.addAction(Intent.ACTION_PACKAGES_SUSPENDED);
-        mContext.registerReceiverAsUser(new BroadcastReceiver() {
-            @Override
-            public void onReceive(Context context, Intent intent) {
-                final int[] changedUids = intent.getIntArrayExtra(Intent.EXTRA_CHANGED_UID_LIST);
-                final String[] changedPkgs = intent.getStringArrayExtra(
-                        Intent.EXTRA_CHANGED_PACKAGE_LIST);
-                for (int code : OPS_RESTRICTED_ON_SUSPEND) {
-                    ArraySet<OnOpModeChangedListener> onModeChangedListeners;
-                    synchronized (AppOpsService.this) {
-                        onModeChangedListeners =
-                                mAppOpsCheckingService.getOpModeChangedListeners(code);
-                        if (onModeChangedListeners == null) {
-                            continue;
-                        }
-                    }
-                    for (int i = 0; i < changedUids.length; i++) {
-                        final int changedUid = changedUids[i];
-                        final String changedPkg = changedPkgs[i];
-                        // We trust packagemanager to insert matching uid and packageNames in the
-                        // extras
-                        notifyOpChanged(onModeChangedListeners, code, changedUid, changedPkg);
-                    }
-                }
-            }
-        }, UserHandle.ALL, packageSuspendFilter, null, null);
-
-        mHandler.postDelayed(new Runnable() {
-            @Override
-            public void run() {
-                List<String> packageNames = getPackageListAndResample();
-                initializeRarelyUsedPackagesList(new ArraySet<>(packageNames));
-            }
-        }, RARELY_USED_PACKAGES_INITIALIZATION_DELAY_MILLIS);
-
-        getPackageManagerInternal().setExternalSourcesPolicy(
-                new PackageManagerInternal.ExternalSourcesPolicy() {
-                    @Override
-                    public int getPackageTrustedToInstallApps(String packageName, int uid) {
-                        int appOpMode = checkOperation(AppOpsManager.OP_REQUEST_INSTALL_PACKAGES,
-                                uid, packageName);
-                        switch (appOpMode) {
-                            case AppOpsManager.MODE_ALLOWED:
-                                return PackageManagerInternal.ExternalSourcesPolicy.USER_TRUSTED;
-                            case AppOpsManager.MODE_ERRORED:
-                                return PackageManagerInternal.ExternalSourcesPolicy.USER_BLOCKED;
-                            default:
-                                return PackageManagerInternal.ExternalSourcesPolicy.USER_DEFAULT;
-                        }
-                    }
-                });
-
-        mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
     }
 
     /**
@@ -1207,6 +1225,11 @@
                     initializeUserUidStatesLocked(userId, packageStates);
                 }
             }
+
+            for (int uid : NON_PACKAGE_UIDS) {
+                mUidStates.put(uid, new UidState(uid));
+            }
+            mUidStatesInitialized = true;
         }
     }
 
@@ -1250,8 +1273,6 @@
                 ops.put(code, new Op(uidState, packageName, code, uid));
             }
         }
-
-        uidState.evalForegroundOps();
     }
 
     /**
@@ -1325,23 +1346,51 @@
     // The callback method from AppOpsUidStateTracker
     private void onUidStateChanged(int uid, int state, boolean foregroundModeMayChange) {
         synchronized (this) {
-            UidState uidState = getUidStateLocked(uid, true);
+            UidState uidState = getUidStateLocked(uid, false);
 
-            if (uidState != null && foregroundModeMayChange && uidState.hasForegroundWatchers) {
-                for (int fgi = uidState.foregroundOps.size() - 1; fgi >= 0; fgi--) {
-                    if (!uidState.foregroundOps.valueAt(fgi)) {
+            boolean hasForegroundWatchers = false;
+
+            for (int i = 0; i < mModeWatchers.size(); i++) {
+                ModeCallback cb = mModeWatchers.valueAt(i);
+                if (cb.isWatchingUid(uid) && (cb.getFlags() & WATCH_FOREGROUND_CHANGES) != 0) {
+                    hasForegroundWatchers = true;
+                    break;
+                }
+            }
+
+            if (uidState != null && foregroundModeMayChange && hasForegroundWatchers) {
+
+                SparseBooleanArray foregroundOps = new SparseBooleanArray();
+
+                SparseBooleanArray uidForegroundOps = mAppOpsCheckingService.getForegroundOps(uid);
+                for (int i = 0; i < uidForegroundOps.size(); i++) {
+                    foregroundOps.put(uidForegroundOps.keyAt(i), true);
+                }
+                String[] uidPackageNames = getPackagesForUid(uid);
+
+                int userId = UserHandle.getUserId(uid);
+                for (String packageName : uidPackageNames) {
+                    SparseBooleanArray packageForegroundOps =
+                            mAppOpsCheckingService.getForegroundOps(packageName, userId);
+                    for (int i = 0; i < packageForegroundOps.size(); i++) {
+                        foregroundOps.put(packageForegroundOps.keyAt(i), true);
+                    }
+                }
+
+                for (int fgi = foregroundOps.size() - 1; fgi >= 0; fgi--) {
+                    if (!foregroundOps.valueAt(fgi)) {
                         continue;
                     }
-                    final int code = uidState.foregroundOps.keyAt(fgi);
+                    final int code = foregroundOps.keyAt(fgi);
 
                     if (uidState.getUidMode(code) != AppOpsManager.opToDefaultMode(code)
                             && uidState.getUidMode(code) == AppOpsManager.MODE_FOREGROUND) {
                         mHandler.sendMessage(PooledLambda.obtainMessage(
                                 AppOpsService::notifyOpChangedForAllPkgsInUid,
-                                this, code, uidState.uid, true, null));
+                                this, code, uidState.uid, true));
                     } else if (!uidState.pkgOps.isEmpty()) {
                         final ArraySet<OnOpModeChangedListener> listenerSet =
-                                mAppOpsCheckingService.getOpModeChangedListeners(code);
+                                mOpModeWatchers.get(code);
                         if (listenerSet != null) {
                             for (int cbi = listenerSet.size() - 1; cbi >= 0; cbi--) {
                                 final OnOpModeChangedListener listener = listenerSet.valueAt(cbi);
@@ -1398,12 +1447,6 @@
             @ActivityManager.ProcessCapability int capability) {
         synchronized (this) {
             getUidStateTracker().updateUidProcState(uid, procState, capability);
-            if (!mUidStates.contains(uid)) {
-                UidState uidState = new UidState(uid);
-                mUidStates.put(uid, uidState);
-                onUidStateChanged(uid,
-                        AppOpsUidStateTracker.processStateToUidState(procState), false);
-            }
         }
     }
 
@@ -1538,7 +1581,7 @@
                 return null;
             }
             ArrayList<AppOpsManager.OpEntry> resOps = collectOps(pkgOps, ops);
-            if (resOps == null) {
+            if (resOps == null || resOps.size() == 0) {
                 return null;
             }
             ArrayList<AppOpsManager.PackageOps> res = new ArrayList<AppOpsManager.PackageOps>();
@@ -1795,6 +1838,12 @@
                 if (mode == defaultMode) {
                     return;
                 }
+                if (uid >= Process.FIRST_APPLICATION_UID) {
+                    // TODO change to a throw; no crashing for now.
+                    Slog.e(TAG, "Trying to set mode for unknown uid " + uid + ".");
+                }
+                // I suppose we'll support setting these uids. Shouldn't matter later when UidState
+                // is removed.
                 uidState = new UidState(uid);
                 mUidStates.put(uid, uidState);
             }
@@ -1805,17 +1854,16 @@
                 previousMode = MODE_DEFAULT;
             }
 
+            mIgnoredCallback = permissionPolicyCallback;
             if (!uidState.setUidMode(code, mode)) {
                 return;
             }
-            uidState.evalForegroundOps();
             if (mode != MODE_ERRORED && mode != previousMode) {
                 updateStartedOpModeForUidLocked(code, mode == MODE_IGNORED, uid);
             }
         }
 
-        notifyOpChangedForAllPkgsInUid(code, uid, false, permissionPolicyCallback);
-        notifyOpChangedSync(code, uid, null, mode, previousMode);
+        notifyStorageManagerOpModeChangedSync(code, uid, null, mode, previousMode);
     }
 
     /**
@@ -1825,12 +1873,128 @@
      * @param uid The uid the op was changed for
      * @param onlyForeground Only notify watchers that watch for foreground changes
      */
-    private void notifyOpChangedForAllPkgsInUid(int code, int uid, boolean onlyForeground,
-            @Nullable IAppOpsCallback callbackToIgnore) {
-        ModeCallback listenerToIgnore = callbackToIgnore != null
-                ? mModeWatchers.get(callbackToIgnore.asBinder()) : null;
-        mAppOpsCheckingService.notifyOpChangedForAllPkgsInUid(code, uid, onlyForeground,
-                listenerToIgnore);
+    private void notifyOpChangedForAllPkgsInUid(int code, int uid, boolean onlyForeground) {
+        String[] uidPackageNames = getPackagesForUid(uid);
+        ArrayMap<OnOpModeChangedListener, ArraySet<String>> callbackSpecs = null;
+        synchronized (this) {
+            ArraySet<OnOpModeChangedListener> callbacks = mOpModeWatchers.get(code);
+            if (callbacks != null) {
+                final int callbackCount = callbacks.size();
+                for (int i = 0; i < callbackCount; i++) {
+                    OnOpModeChangedListener callback = callbacks.valueAt(i);
+
+                    if (!callback.isWatchingUid(uid)) {
+                        continue;
+                    }
+
+                    if (onlyForeground && (callback.getFlags()
+                            & WATCH_FOREGROUND_CHANGES) == 0) {
+                        continue;
+                    }
+
+                    ArraySet<String> changedPackages = new ArraySet<>();
+                    Collections.addAll(changedPackages, uidPackageNames);
+                    if (callbackSpecs == null) {
+                        callbackSpecs = new ArrayMap<>();
+                    }
+                    callbackSpecs.put(callback, changedPackages);
+                }
+            }
+
+            for (String uidPackageName : uidPackageNames) {
+                callbacks = mPackageModeWatchers.get(uidPackageName);
+                if (callbacks != null) {
+                    if (callbackSpecs == null) {
+                        callbackSpecs = new ArrayMap<>();
+                    }
+                    final int callbackCount = callbacks.size();
+                    for (int i = 0; i < callbackCount; i++) {
+                        OnOpModeChangedListener callback = callbacks.valueAt(i);
+
+                        if (onlyForeground && (callback.getFlags()
+                                & WATCH_FOREGROUND_CHANGES) == 0) {
+                            continue;
+                        }
+
+                        ArraySet<String> changedPackages = callbackSpecs.get(callback);
+                        if (changedPackages == null) {
+                            changedPackages = new ArraySet<>();
+                            callbackSpecs.put(callback, changedPackages);
+                        }
+                        changedPackages.add(uidPackageName);
+                    }
+                }
+            }
+
+            if (callbackSpecs != null && mIgnoredCallback != null) {
+                callbackSpecs.remove(mModeWatchers.get(mIgnoredCallback.asBinder()));
+            }
+        }
+
+        if (callbackSpecs == null) {
+            return;
+        }
+
+        for (int i = 0; i < callbackSpecs.size(); i++) {
+            final OnOpModeChangedListener callback = callbackSpecs.keyAt(i);
+            final ArraySet<String> reportedPackageNames = callbackSpecs.valueAt(i);
+            if (reportedPackageNames == null) {
+                mHandler.sendMessage(PooledLambda.obtainMessage(
+                        AppOpsService::notifyOpChanged,
+                        this, callback, code, uid, (String) null));
+
+            } else {
+                final int reportedPackageCount = reportedPackageNames.size();
+                for (int j = 0; j < reportedPackageCount; j++) {
+                    final String reportedPackageName = reportedPackageNames.valueAt(j);
+                    mHandler.sendMessage(PooledLambda.obtainMessage(
+                            AppOpsService::notifyOpChanged,
+                            this, callback, code, uid, reportedPackageName));
+                }
+            }
+        }
+    }
+
+    private void notifyOpChangedForPkg(@NonNull String packageName, int code, int mode,
+            @UserIdInt int userId) {
+        ArraySet<OnOpModeChangedListener> repCbs = null;
+        int uid = -1;
+        synchronized (AppOpsService.this) {
+            ArraySet<OnOpModeChangedListener> cbs = mOpModeWatchers.get(code);
+            if (cbs != null) {
+                if (repCbs == null) {
+                    repCbs = new ArraySet<>();
+                }
+                repCbs.addAll(cbs);
+            }
+            cbs = mPackageModeWatchers.get(packageName);
+            if (cbs != null) {
+                if (repCbs == null) {
+                    repCbs = new ArraySet<>();
+                }
+                repCbs.addAll(cbs);
+            }
+            if (repCbs != null && mIgnoredCallback != null) {
+                repCbs.remove(mModeWatchers.get(mIgnoredCallback.asBinder()));
+            }
+            uid = getPackageManagerInternal().getPackageUid(packageName,
+                    PackageManager.MATCH_KNOWN_PACKAGES, userId);
+            Op op = getOpLocked(code, uid, packageName, null, false, null, /* edit */ false);
+            if (op != null && mode == AppOpsManager.opToDefaultMode(op.op)) {
+                // If going into the default mode, prune this op
+                // if there is nothing else interesting in it.
+                pruneOpLocked(op, uid, packageName);
+            }
+            scheduleFastWriteLocked();
+            if (mode != MODE_ERRORED) {
+                updateStartedOpModeForUidLocked(code, mode == MODE_IGNORED, uid);
+            }
+        }
+
+        if (repCbs != null && uid != -1) {
+            mHandler.sendMessage(PooledLambda.obtainMessage(AppOpsService::notifyOpChanged, this,
+                    repCbs, code, uid, packageName));
+        }
     }
 
     private void updatePermissionRevokedCompat(int uid, int switchCode, int mode) {
@@ -1924,8 +2088,8 @@
         }
     }
 
-    private void notifyOpChangedSync(int code, int uid, @NonNull String packageName, int mode,
-            int previousMode) {
+    private void notifyStorageManagerOpModeChangedSync(int code, int uid,
+            @NonNull String packageName, int mode, int previousMode) {
         final StorageManagerInternal storageManagerInternal =
                 LocalServices.getService(StorageManagerInternal.class);
         if (storageManagerInternal != null) {
@@ -1954,70 +2118,29 @@
             return;
         }
 
-        ArraySet<OnOpModeChangedListener> repCbs = null;
         code = AppOpsManager.opToSwitch(code);
 
         PackageVerificationResult pvr;
         try {
             pvr = verifyAndGetBypass(uid, packageName, null);
         } catch (SecurityException e) {
-            if (Process.isIsolated(uid)) {
-                Slog.e(TAG, "Cannot setMode: isolated process");
-            } else {
-                Slog.e(TAG, "Cannot setMode", e);
-            }
+            logVerifyAndGetBypassFailure(uid, e, "setMode");
             return;
         }
 
         int previousMode = MODE_DEFAULT;
         synchronized (this) {
-            UidState uidState = getUidStateLocked(uid, false);
             Op op = getOpLocked(code, uid, packageName, null, false, pvr.bypass, /* edit */ true);
             if (op != null) {
                 if (op.getMode() != mode) {
                     previousMode = op.getMode();
+                    mIgnoredCallback = permissionPolicyCallback;
                     op.setMode(mode);
-
-                    if (uidState != null) {
-                        uidState.evalForegroundOps();
-                    }
-                    ArraySet<OnOpModeChangedListener> cbs =
-                            mAppOpsCheckingService.getOpModeChangedListeners(code);
-                    if (cbs != null) {
-                        if (repCbs == null) {
-                            repCbs = new ArraySet<>();
-                        }
-                        repCbs.addAll(cbs);
-                    }
-                    cbs = mAppOpsCheckingService.getPackageModeChangedListeners(packageName);
-                    if (cbs != null) {
-                        if (repCbs == null) {
-                            repCbs = new ArraySet<>();
-                        }
-                        repCbs.addAll(cbs);
-                    }
-                    if (repCbs != null && permissionPolicyCallback != null) {
-                        repCbs.remove(mModeWatchers.get(permissionPolicyCallback.asBinder()));
-                    }
-                    if (mode == AppOpsManager.opToDefaultMode(op.op)) {
-                        // If going into the default mode, prune this op
-                        // if there is nothing else interesting in it.
-                        pruneOpLocked(op, uid, packageName);
-                    }
-                    scheduleFastWriteLocked();
-                    if (mode != MODE_ERRORED) {
-                        updateStartedOpModeForUidLocked(code, mode == MODE_IGNORED, uid);
-                    }
                 }
             }
         }
-        if (repCbs != null) {
-            mHandler.sendMessage(PooledLambda.obtainMessage(
-                    AppOpsService::notifyOpChanged,
-                    this, repCbs, code, uid, packageName));
-        }
 
-        notifyOpChangedSync(code, uid, packageName, mode, previousMode);
+        notifyStorageManagerOpModeChangedSync(code, uid, packageName, mode, previousMode);
     }
 
     private void notifyOpChanged(ArraySet<OnOpModeChangedListener> callbacks, int code,
@@ -2028,9 +2151,42 @@
         }
     }
 
-    private void notifyOpChanged(OnOpModeChangedListener callback, int code,
+    private void notifyOpChanged(OnOpModeChangedListener onModeChangedListener, int code,
             int uid, String packageName) {
-        mAppOpsCheckingService.notifyOpChanged(callback, code, uid, packageName);
+        Objects.requireNonNull(onModeChangedListener);
+
+        if (uid != UID_ANY && onModeChangedListener.getWatchingUid() >= 0
+                && onModeChangedListener.getWatchingUid() != uid) {
+            return;
+        }
+
+        // See CALL_BACK_ON_CHANGED_LISTENER_WITH_SWITCHED_OP_CHANGE
+        int[] switchedCodes;
+        if (onModeChangedListener.getWatchedOpCode() == ALL_OPS) {
+            switchedCodes = mSwitchedOps.get(code);
+        } else if (onModeChangedListener.getWatchedOpCode() == OP_NONE) {
+            switchedCodes = new int[]{code};
+        } else {
+            switchedCodes = new int[]{onModeChangedListener.getWatchedOpCode()};
+        }
+
+        for (int switchedCode : switchedCodes) {
+            // There are features watching for mode changes such as window manager
+            // and location manager which are in our process. The callbacks in these
+            // features may require permissions our remote caller does not have.
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                if (shouldIgnoreCallback(switchedCode, onModeChangedListener.getCallingPid(),
+                        onModeChangedListener.getCallingUid())) {
+                    continue;
+                }
+                onModeChangedListener.onOpModeChanged(switchedCode, uid, packageName);
+            } catch (RemoteException e) {
+                /* ignore */
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
     }
 
     private static ArrayList<ChangeRec> addChange(ArrayList<ChangeRec> reports,
@@ -2129,11 +2285,9 @@
                             uidState.setUidMode(code, newMode);
                             for (String packageName : getPackagesForUid(uidState.uid)) {
                                 callbacks = addCallbacks(callbacks, code, uidState.uid, packageName,
-                                        previousMode,
-                                        mAppOpsCheckingService.getOpModeChangedListeners(code));
+                                        previousMode, mOpModeWatchers.get(code));
                                 callbacks = addCallbacks(callbacks, code, uidState.uid, packageName,
-                                        previousMode, mAppOpsCheckingService
-                                                .getPackageModeChangedListeners(packageName));
+                                        previousMode, mPackageModeWatchers.get(packageName));
 
                                 allChanges = addChange(allChanges, code, uidState.uid,
                                         packageName, previousMode);
@@ -2182,11 +2336,9 @@
                             uidChanged = true;
                             final int uid = curOp.uidState.uid;
                             callbacks = addCallbacks(callbacks, curOp.op, uid, packageName,
-                                    previousMode,
-                                    mAppOpsCheckingService.getOpModeChangedListeners(curOp.op));
+                                    previousMode, mOpModeWatchers.get(curOp.op));
                             callbacks = addCallbacks(callbacks, curOp.op, uid, packageName,
-                                    previousMode, mAppOpsCheckingService
-                                            .getPackageModeChangedListeners(packageName));
+                                    previousMode, mPackageModeWatchers.get(packageName));
 
                             allChanges = addChange(allChanges, curOp.op, uid, packageName,
                                     previousMode);
@@ -2202,9 +2354,6 @@
                                 UserHandle.getUserId(uidState.uid));
                     }
                 }
-                if (uidChanged) {
-                    uidState.evalForegroundOps();
-                }
             }
 
             if (changed) {
@@ -2228,7 +2377,7 @@
         int numChanges = allChanges.size();
         for (int i = 0; i < numChanges; i++) {
             ChangeRec change = allChanges.get(i);
-            notifyOpChangedSync(change.op, change.uid, change.pkg,
+            notifyStorageManagerOpModeChangedSync(change.op, change.uid, change.pkg,
                     AppOpsManager.opToDefaultMode(change.op), change.previous_mode);
         }
     }
@@ -2281,15 +2430,6 @@
         dpmi.resetOp(op, packageName, userId);
     }
 
-    private void evalAllForegroundOpsLocked() {
-        for (int uidi = mUidStates.size() - 1; uidi >= 0; uidi--) {
-            final UidState uidState = mUidStates.valueAt(uidi);
-            if (uidState.foregroundOps != null) {
-                uidState.evalForegroundOps();
-            }
-        }
-    }
-
     @Override
     public void startWatchingMode(int op, String packageName, IAppOpsCallback callback) {
         startWatchingModeWithFlags(op, packageName, 0, callback);
@@ -2333,12 +2473,21 @@
                 mModeWatchers.put(callback.asBinder(), cb);
             }
             if (switchOp != AppOpsManager.OP_NONE) {
-                mAppOpsCheckingService.startWatchingOpModeChanged(cb, switchOp);
+                ArraySet<OnOpModeChangedListener> cbs = mOpModeWatchers.get(switchOp);
+                if (cbs == null) {
+                    cbs = new ArraySet<>();
+                    mOpModeWatchers.put(switchOp, cbs);
+                }
+                cbs.add(cb);
             }
             if (mayWatchPackageName) {
-                mAppOpsCheckingService.startWatchingPackageModeChanged(cb, packageName);
+                ArraySet<OnOpModeChangedListener> cbs = mPackageModeWatchers.get(packageName);
+                if (cbs == null) {
+                    cbs = new ArraySet<>();
+                    mPackageModeWatchers.put(packageName, cbs);
+                }
+                cbs.add(cb);
             }
-            evalAllForegroundOpsLocked();
         }
     }
 
@@ -2351,10 +2500,21 @@
             ModeCallback cb = mModeWatchers.remove(callback.asBinder());
             if (cb != null) {
                 cb.unlinkToDeath();
-                mAppOpsCheckingService.removeListener(cb);
+                for (int i = mOpModeWatchers.size() - 1; i >= 0; i--) {
+                    ArraySet<OnOpModeChangedListener> cbs = mOpModeWatchers.valueAt(i);
+                    cbs.remove(cb);
+                    if (cbs.size() <= 0) {
+                        mOpModeWatchers.removeAt(i);
+                    }
+                }
+                for (int i = mPackageModeWatchers.size() - 1; i >= 0; i--) {
+                    ArraySet<OnOpModeChangedListener> cbs = mPackageModeWatchers.valueAt(i);
+                    cbs.remove(cb);
+                    if (cbs.size() <= 0) {
+                        mPackageModeWatchers.removeAt(i);
+                    }
+                }
             }
-
-            evalAllForegroundOpsLocked();
         }
     }
 
@@ -2416,11 +2576,7 @@
         try {
             pvr = verifyAndGetBypass(uid, packageName, null);
         } catch (SecurityException e) {
-            if (Process.isIsolated(uid)) {
-                Slog.e(TAG, "Cannot checkOperation: isolated process");
-            } else {
-                Slog.e(TAG, "Cannot checkOperation", e);
-            }
+            logVerifyAndGetBypassFailure(uid, e, "checkOperation");
             return AppOpsManager.opToDefaultMode(code);
         }
 
@@ -2632,11 +2788,7 @@
                 attributionTag = null;
             }
         } catch (SecurityException e) {
-            if (Process.isIsolated(uid)) {
-                Slog.e(TAG, "Cannot noteOperation: isolated process");
-            } else {
-                Slog.e(TAG, "Cannot noteOperation", e);
-            }
+            logVerifyAndGetBypassFailure(uid, e, "noteOperation");
             return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
                     packageName);
         }
@@ -3055,7 +3207,7 @@
         return startOperationUnchecked(clientId, code, uid, packageName, attributionTag,
                 Process.INVALID_UID, null, null, OP_FLAG_SELF, startIfModeDefault,
                 shouldCollectAsyncNotedOp, message, shouldCollectMessage, attributionFlags,
-                attributionChainId, /*dryRun*/ false);
+                attributionChainId);
     }
 
     @Override
@@ -3123,11 +3275,10 @@
 
         if (!skipProxyOperation) {
             // Test if the proxied operation will succeed before starting the proxy operation
-            final SyncNotedAppOp testProxiedOp = startOperationUnchecked(clientId, code,
-                    proxiedUid, resolvedProxiedPackageName, proxiedAttributionTag, proxyUid,
-                    resolvedProxyPackageName, proxyAttributionTag, proxiedFlags, startIfModeDefault,
-                    shouldCollectAsyncNotedOp, message, shouldCollectMessage,
-                    proxiedAttributionFlags, attributionChainId, /*dryRun*/ true);
+            final SyncNotedAppOp testProxiedOp = startOperationDryRun(code,
+                    proxiedUid, resolvedProxiedPackageName, proxiedAttributionTag,
+                    resolvedProxyPackageName, proxiedFlags, startIfModeDefault);
+
             if (!shouldStartForMode(testProxiedOp.getOpMode(), startIfModeDefault)) {
                 return testProxiedOp;
             }
@@ -3138,8 +3289,7 @@
             final SyncNotedAppOp proxyAppOp = startOperationUnchecked(clientId, code, proxyUid,
                     resolvedProxyPackageName, proxyAttributionTag, Process.INVALID_UID, null, null,
                     proxyFlags, startIfModeDefault, !isProxyTrusted, "proxy " + message,
-                    shouldCollectMessage, proxyAttributionFlags, attributionChainId,
-                    /*dryRun*/ false);
+                    shouldCollectMessage, proxyAttributionFlags, attributionChainId);
             if (!shouldStartForMode(proxyAppOp.getOpMode(), startIfModeDefault)) {
                 return proxyAppOp;
             }
@@ -3148,8 +3298,7 @@
         return startOperationUnchecked(clientId, code, proxiedUid, resolvedProxiedPackageName,
                 proxiedAttributionTag, proxyUid, resolvedProxyPackageName, proxyAttributionTag,
                 proxiedFlags, startIfModeDefault, shouldCollectAsyncNotedOp, message,
-                shouldCollectMessage, proxiedAttributionFlags, attributionChainId,
-                /*dryRun*/ false);
+                shouldCollectMessage, proxiedAttributionFlags, attributionChainId);
     }
 
     private boolean shouldStartForMode(int mode, boolean startIfModeDefault) {
@@ -3161,7 +3310,7 @@
             String proxyPackageName, @Nullable String proxyAttributionTag, @OpFlags int flags,
             boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, @Nullable String message,
             boolean shouldCollectMessage, @AttributionFlags int attributionFlags,
-            int attributionChainId, boolean dryRun) {
+            int attributionChainId) {
         PackageVerificationResult pvr;
         try {
             pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyPackageName);
@@ -3169,11 +3318,7 @@
                 attributionTag = null;
             }
         } catch (SecurityException e) {
-            if (Process.isIsolated(uid)) {
-                Slog.e(TAG, "Cannot startOperation: isolated process");
-            } else {
-                Slog.e(TAG, "Cannot startOperation", e);
-            }
+            logVerifyAndGetBypassFailure(uid, e, "startOperation");
             return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
                     packageName);
         }
@@ -3184,11 +3329,9 @@
             final Ops ops = getOpsLocked(uid, packageName, attributionTag,
                     pvr.isAttributionTagValid, pvr.bypass, /* edit */ true);
             if (ops == null) {
-                if (!dryRun) {
-                    scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
-                            flags, AppOpsManager.MODE_IGNORED, startType, attributionFlags,
-                            attributionChainId);
-                }
+                scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
+                        flags, AppOpsManager.MODE_IGNORED, startType, attributionFlags,
+                        attributionChainId);
                 if (DEBUG) Slog.d(TAG, "startOperation: no op for code " + code + " uid " + uid
                         + " package " + packageName + " flags: "
                         + AppOpsManager.flagsToString(flags));
@@ -3211,11 +3354,9 @@
                                 + switchCode + " (" + code + ") uid " + uid + " package "
                                 + packageName + " flags: " + AppOpsManager.flagsToString(flags));
                     }
-                    if (!dryRun) {
-                        attributedOp.rejected(uidState.getState(), flags);
-                        scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
-                                flags, uidMode, startType, attributionFlags, attributionChainId);
-                    }
+                    attributedOp.rejected(uidState.getState(), flags);
+                    scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
+                            flags, uidMode, startType, attributionFlags, attributionChainId);
                     return new SyncNotedAppOp(uidMode, code, attributionTag, packageName);
                 }
             } else {
@@ -3227,39 +3368,35 @@
                     if (DEBUG) Slog.d(TAG, "startOperation: reject #" + mode + " for code "
                             + switchCode + " (" + code + ") uid " + uid + " package "
                             + packageName + " flags: " + AppOpsManager.flagsToString(flags));
-                    if (!dryRun) {
-                        attributedOp.rejected(uidState.getState(), flags);
-                        scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
-                                flags, mode, startType, attributionFlags, attributionChainId);
-                    }
+                    attributedOp.rejected(uidState.getState(), flags);
+                    scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
+                            flags, mode, startType, attributionFlags, attributionChainId);
                     return new SyncNotedAppOp(mode, code, attributionTag, packageName);
                 }
             }
             if (DEBUG) Slog.d(TAG, "startOperation: allowing code " + code + " uid " + uid
                     + " package " + packageName + " restricted: " + isRestricted
                     + " flags: " + AppOpsManager.flagsToString(flags));
-            if (!dryRun) {
-                try {
-                    if (isRestricted) {
-                        attributedOp.createPaused(clientId, proxyUid, proxyPackageName,
-                                proxyAttributionTag, uidState.getState(), flags,
-                                attributionFlags, attributionChainId);
-                    } else {
-                        attributedOp.started(clientId, proxyUid, proxyPackageName,
-                                proxyAttributionTag, uidState.getState(), flags,
-                                attributionFlags, attributionChainId);
-                        startType = START_TYPE_STARTED;
-                    }
-                } catch (RemoteException e) {
-                    throw new RuntimeException(e);
+            try {
+                if (isRestricted) {
+                    attributedOp.createPaused(clientId, proxyUid, proxyPackageName,
+                            proxyAttributionTag, uidState.getState(), flags,
+                            attributionFlags, attributionChainId);
+                } else {
+                    attributedOp.started(clientId, proxyUid, proxyPackageName,
+                            proxyAttributionTag, uidState.getState(), flags,
+                            attributionFlags, attributionChainId);
+                    startType = START_TYPE_STARTED;
                 }
-                scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag, flags,
-                        isRestricted ? MODE_IGNORED : MODE_ALLOWED, startType, attributionFlags,
-                        attributionChainId);
+            } catch (RemoteException e) {
+                throw new RuntimeException(e);
             }
+            scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag, flags,
+                    isRestricted ? MODE_IGNORED : MODE_ALLOWED, startType, attributionFlags,
+                    attributionChainId);
         }
 
-        if (shouldCollectAsyncNotedOp && !dryRun && !isRestricted) {
+        if (shouldCollectAsyncNotedOp && !isRestricted) {
             collectAsyncNotedOp(uid, packageName, code, attributionTag, AppOpsManager.OP_FLAG_SELF,
                     message, shouldCollectMessage);
         }
@@ -3268,6 +3405,88 @@
                 packageName);
     }
 
+    /**
+     * Performs a dry run of the start operation i.e. determines the result of the start operation
+     * without actually updating the op state to be started.
+     *
+     * <p>This is used for proxy operations; before starting the op as the proxy, we must check that
+     * the proxied app can successfully start the operation.
+     */
+    private SyncNotedAppOp startOperationDryRun(int code, int uid,
+            @NonNull String packageName, @Nullable String attributionTag,
+            String proxyPackageName, @OpFlags int flags,
+            boolean startIfModeDefault) {
+        PackageVerificationResult pvr;
+        try {
+            pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyPackageName);
+            if (!pvr.isAttributionTagValid) {
+                attributionTag = null;
+            }
+        } catch (SecurityException e) {
+            if (Process.isIsolated(uid)) {
+                Slog.e(TAG, "Cannot startOperation: isolated process");
+            } else {
+                Slog.e(TAG, "Cannot startOperation", e);
+            }
+            return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
+                    packageName);
+        }
+
+        boolean isRestricted = false;
+        synchronized (this) {
+            final Ops ops = getOpsLocked(uid, packageName, attributionTag,
+                    pvr.isAttributionTagValid, pvr.bypass, /* edit */ true);
+            if (ops == null) {
+                if (DEBUG) {
+                    Slog.d(TAG, "startOperation: no op for code " + code + " uid " + uid
+                            + " package " + packageName + " flags: "
+                            + AppOpsManager.flagsToString(flags));
+                }
+                return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
+                        packageName);
+            }
+            final Op op = getOpLocked(ops, code, uid, true);
+            final UidState uidState = ops.uidState;
+            isRestricted = isOpRestrictedLocked(uid, code, packageName, attributionTag, pvr.bypass,
+                    false);
+            final int switchCode = AppOpsManager.opToSwitch(code);
+            // 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 (uidState.getUidMode(switchCode) != AppOpsManager.opToDefaultMode(switchCode)) {
+                final int uidMode = uidState.evalMode(code, uidState.getUidMode(switchCode));
+                if (!shouldStartForMode(uidMode, startIfModeDefault)) {
+                    if (DEBUG) {
+                        Slog.d(TAG, "startOperation: uid reject #" + uidMode + " for code "
+                                + switchCode + " (" + code + ") uid " + uid + " package "
+                                + packageName + " flags: " + AppOpsManager.flagsToString(flags));
+                    }
+                    return new SyncNotedAppOp(uidMode, code, attributionTag, packageName);
+                }
+            } else {
+                final Op switchOp = switchCode != code ? getOpLocked(ops, switchCode, uid, true)
+                        : op;
+                final int mode = switchOp.uidState.evalMode(switchOp.op, switchOp.getMode());
+                if (mode != AppOpsManager.MODE_ALLOWED
+                        && (!startIfModeDefault || mode != MODE_DEFAULT)) {
+                    if (DEBUG) {
+                        Slog.d(TAG, "startOperation: reject #" + mode + " for code "
+                                + switchCode + " (" + code + ") uid " + uid + " package "
+                                + packageName + " flags: " + AppOpsManager.flagsToString(flags));
+                    }
+                    return new SyncNotedAppOp(mode, code, attributionTag, packageName);
+                }
+            }
+            if (DEBUG) {
+                Slog.d(TAG, "startOperation: allowing code " + code + " uid " + uid
+                        + " package " + packageName + " restricted: " + isRestricted
+                        + " flags: " + AppOpsManager.flagsToString(flags));
+            }
+        }
+
+        return new SyncNotedAppOp(isRestricted ? MODE_IGNORED : MODE_ALLOWED, code, attributionTag,
+                packageName);
+    }
+
     @Override
     public void finishOperation(IBinder clientId, int code, int uid, String packageName,
             String attributionTag) {
@@ -3349,11 +3568,7 @@
                 attributionTag = null;
             }
         } catch (SecurityException e) {
-            if (Process.isIsolated(uid)) {
-                Slog.e(TAG, "Cannot finishOperation: isolated process");
-            } else {
-                Slog.e(TAG, "Cannot finishOperation", e);
-            }
+            logVerifyAndGetBypassFailure(uid, e, "finishOperation");
             return;
         }
 
@@ -3719,7 +3934,7 @@
     /**
      * Create a restriction description matching the properties of the package.
      *
-     * @param pkg The package to create the restriction description for
+     * @param packageState The package to create the restriction description for
      *
      * @return The restriction matching the package
      */
@@ -3909,6 +4124,17 @@
         return false;
     }
 
+    private void logVerifyAndGetBypassFailure(int uid, @NonNull SecurityException e,
+            @NonNull String methodName) {
+        if (Process.isIsolated(uid)) {
+            Slog.e(TAG, "Cannot " + methodName + ": isolated UID");
+        } else if (UserHandle.getAppId(uid) < Process.FIRST_APPLICATION_UID) {
+            Slog.e(TAG, "Cannot " + methodName + ": non-application UID " + uid);
+        } else {
+            Slog.e(TAG, "Cannot " + methodName, e);
+        }
+    }
+
     /**
      * Get (and potentially create) ops.
      *
@@ -3923,7 +4149,7 @@
      */
     private Ops getOpsLocked(int uid, String packageName, @Nullable String attributionTag,
             boolean isAttributionTagValid, @Nullable RestrictionBypass bypass, boolean edit) {
-        UidState uidState = getUidStateLocked(uid, edit);
+        UidState uidState = getUidStateLocked(uid, false);
         if (uidState == null) {
             return null;
         }
@@ -5149,8 +5375,55 @@
                 pw.println();
             }
 
-            if (!dumpHistory) {
-                needSep |= mAppOpsCheckingService.dumpListeners(dumpOp, dumpUid, dumpPackage, pw);
+            if (mOpModeWatchers.size() > 0 && !dumpHistory) {
+                boolean printedHeader = false;
+                for (int i = 0; i < mOpModeWatchers.size(); i++) {
+                    if (dumpOp >= 0 && dumpOp != mOpModeWatchers.keyAt(i)) {
+                        continue;
+                    }
+                    boolean printedOpHeader = false;
+                    ArraySet<OnOpModeChangedListener> callbacks = mOpModeWatchers.valueAt(i);
+                    for (int j = 0; j < callbacks.size(); j++) {
+                        final OnOpModeChangedListener cb = callbacks.valueAt(j);
+                        if (dumpPackage != null
+                                && dumpUid != UserHandle.getAppId(cb.getWatchingUid())) {
+                            continue;
+                        }
+                        needSep = true;
+                        if (!printedHeader) {
+                            pw.println("  Op mode watchers:");
+                            printedHeader = true;
+                        }
+                        if (!printedOpHeader) {
+                            pw.print("    Op ");
+                            pw.print(AppOpsManager.opToName(mOpModeWatchers.keyAt(i)));
+                            pw.println(":");
+                            printedOpHeader = true;
+                        }
+                        pw.print("      #"); pw.print(j); pw.print(": ");
+                        pw.println(cb);
+                    }
+                }
+            }
+            if (mPackageModeWatchers.size() > 0 && dumpOp < 0 && !dumpHistory) {
+                boolean printedHeader = false;
+                for (int i = 0; i < mPackageModeWatchers.size(); i++) {
+                    if (dumpPackage != null && !dumpPackage.equals(mPackageModeWatchers.keyAt(i))) {
+                        continue;
+                    }
+                    needSep = true;
+                    if (!printedHeader) {
+                        pw.println("  Package mode watchers:");
+                        printedHeader = true;
+                    }
+                    pw.print("    Pkg "); pw.print(mPackageModeWatchers.keyAt(i));
+                    pw.println(":");
+                    ArraySet<OnOpModeChangedListener> callbacks = mPackageModeWatchers.valueAt(i);
+                    for (int j = 0; j < callbacks.size(); j++) {
+                        pw.print("      #"); pw.print(j); pw.print(": ");
+                        pw.println(callbacks.valueAt(j));
+                    }
+                }
             }
 
             if (mModeWatchers.size() > 0 && dumpOp < 0 && !dumpHistory) {
@@ -5350,11 +5623,6 @@
                             }
                         }
                     }
-                    if (uidState.foregroundOps != null && !hasOp) {
-                        if (uidState.foregroundOps.indexOfKey(dumpOp) > 0) {
-                            hasOp = true;
-                        }
-                    }
                     if (!hasOp || !hasPackage || !hasMode) {
                         continue;
                     }
@@ -5362,21 +5630,6 @@
 
                 pw.print("  Uid "); UserHandle.formatUid(pw, uidState.uid); pw.println(":");
                 uidState.dump(pw, nowElapsed);
-                if (uidState.foregroundOps != null && (dumpMode < 0
-                        || dumpMode == AppOpsManager.MODE_FOREGROUND)) {
-                    pw.println("    foregroundOps:");
-                    for (int j = 0; j < uidState.foregroundOps.size(); j++) {
-                        if (dumpOp >= 0 && dumpOp != uidState.foregroundOps.keyAt(j)) {
-                            continue;
-                        }
-                        pw.print("      ");
-                        pw.print(AppOpsManager.opToName(uidState.foregroundOps.keyAt(j)));
-                        pw.print(": ");
-                        pw.println(uidState.foregroundOps.valueAt(j) ? "WATCHER" : "SILENT");
-                    }
-                    pw.print("    hasForegroundWatchers=");
-                    pw.println(uidState.hasForegroundWatchers);
-                }
                 needSep = true;
 
                 if (opModes != null) {
@@ -5578,7 +5831,7 @@
     private void notifyWatchersOfChange(int code, int uid) {
         final ArraySet<OnOpModeChangedListener> modeChangedListenerSet;
         synchronized (this) {
-            modeChangedListenerSet = mAppOpsCheckingService.getOpModeChangedListeners(code);
+            modeChangedListenerSet = mOpModeWatchers.get(code);
             if (modeChangedListenerSet == null) {
                 return;
             }
@@ -5665,10 +5918,10 @@
         }
     }
 
+    @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_APPOPS)
     @Override
     public void resetPackageOpsNoHistory(@NonNull String packageName) {
-        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
-                "resetPackageOpsNoHistory");
+        resetPackageOpsNoHistory_enforcePermission();
         synchronized (AppOpsService.this) {
             final int uid = mPackageManagerInternal.getPackageUid(packageName, 0,
                     UserHandle.getCallingUserId());
@@ -5687,52 +5940,52 @@
         }
     }
 
+    @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_APPOPS)
     @Override
     public void setHistoryParameters(@AppOpsManager.HistoricalMode int mode,
             long baseSnapshotInterval, int compressionStep) {
-        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
-                "setHistoryParameters");
+        setHistoryParameters_enforcePermission();
         // Must not hold the appops lock
         mHistoricalRegistry.setHistoryParameters(mode, baseSnapshotInterval, compressionStep);
     }
 
+    @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_APPOPS)
     @Override
     public void offsetHistory(long offsetMillis) {
-        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
-                "offsetHistory");
+        offsetHistory_enforcePermission();
         // Must not hold the appops lock
         mHistoricalRegistry.offsetHistory(offsetMillis);
         mHistoricalRegistry.offsetDiscreteHistory(offsetMillis);
     }
 
+    @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_APPOPS)
     @Override
     public void addHistoricalOps(HistoricalOps ops) {
-        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
-                "addHistoricalOps");
+        addHistoricalOps_enforcePermission();
         // Must not hold the appops lock
         mHistoricalRegistry.addHistoricalOps(ops);
     }
 
+    @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_APPOPS)
     @Override
     public void resetHistoryParameters() {
-        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
-                "resetHistoryParameters");
+        resetHistoryParameters_enforcePermission();
         // Must not hold the appops lock
         mHistoricalRegistry.resetHistoryParameters();
     }
 
+    @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_APPOPS)
     @Override
     public void clearHistory() {
-        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
-                "clearHistory");
+        clearHistory_enforcePermission();
         // Must not hold the appops lock
         mHistoricalRegistry.clearAllHistory();
     }
 
+    @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_APPOPS)
     @Override
     public void rebootHistory(long offlineDurationMillis) {
-        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
-                "rebootHistory");
+        rebootHistory_enforcePermission();
 
         Preconditions.checkArgument(offlineDurationMillis >= 0);
 
diff --git a/services/core/java/com/android/server/appop/AppOpsServiceTestingShim.java b/services/core/java/com/android/server/appop/AppOpsServiceTestingShim.java
new file mode 100644
index 0000000..de73a55
--- /dev/null
+++ b/services/core/java/com/android/server/appop/AppOpsServiceTestingShim.java
@@ -0,0 +1,228 @@
+/*
+ * 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.appop;
+
+import android.util.SparseBooleanArray;
+import android.util.SparseIntArray;
+
+import java.util.Objects;
+
+/**
+ * A testing shim, which supports running two variants of an AppOpsServiceInterface at once,
+ * and checking the results of both.
+ */
+public class AppOpsServiceTestingShim implements AppOpsCheckingServiceInterface {
+
+    private AppOpsCheckingServiceInterface mOldImplementation;
+    private AppOpsCheckingServiceInterface mNewImplementation;
+
+    public AppOpsServiceTestingShim(AppOpsCheckingServiceInterface oldValImpl,
+            AppOpsCheckingServiceInterface newImpl) {
+        mOldImplementation = oldValImpl;
+        mNewImplementation = newImpl;
+    }
+
+    private void signalImplDifference(String message) {
+        //TODO b/252886104 implement
+    }
+
+    @Override
+    public void writeState() {
+        mOldImplementation.writeState();
+        mNewImplementation.writeState();
+    }
+
+    @Override
+    public void readState() {
+        mOldImplementation.readState();
+        mNewImplementation.readState();
+    }
+
+    @Override
+    public void shutdown() {
+        mOldImplementation.shutdown();
+        mNewImplementation.shutdown();
+    }
+
+    @Override
+    public void systemReady() {
+        mOldImplementation.systemReady();
+        mNewImplementation.systemReady();
+    }
+
+    @Override
+    public SparseIntArray getNonDefaultUidModes(int uid) {
+        SparseIntArray oldVal = mOldImplementation.getNonDefaultUidModes(uid);
+        SparseIntArray newVal = mNewImplementation.getNonDefaultUidModes(uid);
+
+        if (!Objects.equals(oldVal, newVal)) {
+            signalImplDifference("getNonDefaultUidModes");
+        }
+
+        return newVal;
+    }
+
+    @Override
+    public SparseIntArray getNonDefaultPackageModes(String packageName, int userId) {
+        SparseIntArray oldVal = mOldImplementation.getNonDefaultPackageModes(packageName, userId);
+        SparseIntArray newVal = mNewImplementation.getNonDefaultPackageModes(packageName, userId);
+
+        if (!Objects.equals(oldVal, newVal)) {
+            signalImplDifference("getNonDefaultPackageModes");
+        }
+
+        return newVal;
+    }
+
+    @Override
+    public int getUidMode(int uid, int op) {
+        int oldVal = mOldImplementation.getUidMode(uid, op);
+        int newVal = mNewImplementation.getUidMode(uid, op);
+
+        if (oldVal != newVal) {
+            signalImplDifference("getUidMode");
+        }
+
+        return newVal;
+    }
+
+    @Override
+    public boolean setUidMode(int uid, int op, int mode) {
+        boolean oldVal = mOldImplementation.setUidMode(uid, op, mode);
+        boolean newVal = mNewImplementation.setUidMode(uid, op, mode);
+
+        if (oldVal != newVal) {
+            signalImplDifference("setUidMode");
+        }
+
+        return newVal;
+    }
+
+    @Override
+    public int getPackageMode(String packageName, int op, int userId) {
+        int oldVal = mOldImplementation.getPackageMode(packageName, op, userId);
+        int newVal = mNewImplementation.getPackageMode(packageName, op, userId);
+
+        if (oldVal != newVal) {
+            signalImplDifference("getPackageMode");
+        }
+
+        return newVal;
+    }
+
+    @Override
+    public void setPackageMode(String packageName, int op, int mode, int userId) {
+        mOldImplementation.setPackageMode(packageName, op, mode, userId);
+        mNewImplementation.setPackageMode(packageName, op, mode, userId);
+    }
+
+    @Override
+    public boolean removePackage(String packageName, int userId) {
+        boolean oldVal = mOldImplementation.removePackage(packageName, userId);
+        boolean newVal = mNewImplementation.removePackage(packageName, userId);
+
+        if (oldVal != newVal) {
+            signalImplDifference("removePackage");
+        }
+
+        return newVal;
+    }
+
+    @Override
+    public void removeUid(int uid) {
+        mOldImplementation.removeUid(uid);
+        mNewImplementation.removeUid(uid);
+    }
+
+    @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);
+
+        if (!Objects.equals(oldVal, newVal)) {
+            signalImplDifference("getForegroundOps");
+        }
+
+        return newVal;
+    }
+
+    @Override
+    public SparseBooleanArray getForegroundOps(String packageName, int userId) {
+        SparseBooleanArray oldVal = mOldImplementation.getForegroundOps(packageName, userId);
+        SparseBooleanArray newVal = mNewImplementation.getForegroundOps(packageName, userId);
+
+        if (!Objects.equals(oldVal, newVal)) {
+            signalImplDifference("getForegroundOps");
+        }
+
+        return newVal;
+    }
+
+    @Override
+    public boolean addAppOpsModeChangedListener(AppOpsModeChangedListener listener) {
+        boolean oldVal = mOldImplementation.addAppOpsModeChangedListener(listener);
+        boolean newVal = mNewImplementation.addAppOpsModeChangedListener(listener);
+
+        if (oldVal != newVal) {
+            signalImplDifference("addAppOpsModeChangedListener");
+        }
+
+        return newVal;
+    }
+
+    @Override
+    public boolean removeAppOpsModeChangedListener(AppOpsModeChangedListener listener) {
+        boolean oldVal = mOldImplementation.removeAppOpsModeChangedListener(listener);
+        boolean newVal = mNewImplementation.removeAppOpsModeChangedListener(listener);
+
+        if (oldVal != newVal) {
+            signalImplDifference("removeAppOpsModeChangedListener");
+        }
+
+        return newVal;
+    }
+}
diff --git a/services/core/java/com/android/server/appop/LegacyAppOpStateParser.java b/services/core/java/com/android/server/appop/LegacyAppOpStateParser.java
new file mode 100644
index 0000000..a6d5050
--- /dev/null
+++ b/services/core/java/com/android/server/appop/LegacyAppOpStateParser.java
@@ -0,0 +1,255 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.appop;
+
+import static android.app.AppOpsManager.opToDefaultMode;
+
+import android.annotation.NonNull;
+import android.app.AppOpsManager;
+import android.os.UserHandle;
+import android.util.ArrayMap;
+import android.util.AtomicFile;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
+import android.util.Xml;
+
+import com.android.internal.util.XmlUtils;
+import com.android.modules.utils.TypedXmlPullParser;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+class LegacyAppOpStateParser {
+    static final String TAG = LegacyAppOpStateParser.class.getSimpleName();
+
+    private static final int NO_FILE_VERSION = -2;
+    private static final int NO_VERSION = -1;
+
+    /**
+     * Reads legacy app-ops data into given maps.
+     */
+    public int readState(AtomicFile file, SparseArray<SparseIntArray> uidModes,
+            SparseArray<ArrayMap<String, SparseIntArray>> userPackageModes) {
+        FileInputStream stream;
+        try {
+            stream = file.openRead();
+        } catch (FileNotFoundException e) {
+            Slog.i(TAG, "No existing app ops " + file.getBaseFile() + "; starting empty");
+            return NO_FILE_VERSION;
+        }
+
+        try {
+            TypedXmlPullParser parser = Xml.resolvePullParser(stream);
+            int type;
+            while ((type = parser.next()) != XmlPullParser.START_TAG
+                    && type != XmlPullParser.END_DOCUMENT) {
+                // Parse next until we reach the start or end
+            }
+
+            if (type != XmlPullParser.START_TAG) {
+                throw new IllegalStateException("no start tag found");
+            }
+
+            int versionAtBoot = parser.getAttributeInt(null, "v", NO_VERSION);
+
+            int outerDepth = parser.getDepth();
+            while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                    && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+                if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                    continue;
+                }
+
+                String tagName = parser.getName();
+                if (tagName.equals("pkg")) {
+                    // version 2 has the structure pkg -> uid -> op ->
+                    // in version 3, since pkg and uid states are kept completely
+                    // independent we switch to user -> pkg -> op
+                    readPackage(parser, userPackageModes);
+                } else if (tagName.equals("uid")) {
+                    readUidOps(parser, uidModes);
+                } else if (tagName.equals("user")) {
+                    readUser(parser, userPackageModes);
+                } else {
+                    Slog.w(TAG, "Unknown element under <app-ops>: "
+                            + parser.getName());
+                    XmlUtils.skipCurrentTag(parser);
+                }
+            }
+            return versionAtBoot;
+        } catch (XmlPullParserException e) {
+            throw new RuntimeException(e);
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private void readPackage(TypedXmlPullParser parser,
+            SparseArray<ArrayMap<String, SparseIntArray>> userPackageModes)
+            throws NumberFormatException, XmlPullParserException, IOException {
+        String pkgName = parser.getAttributeValue(null, "n");
+        int outerDepth = parser.getDepth();
+        int type;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                continue;
+            }
+
+            String tagName = parser.getName();
+            if (tagName.equals("uid")) {
+                readPackageUid(parser, pkgName, userPackageModes);
+            } else {
+                Slog.w(TAG, "Unknown element under <pkg>: "
+                        + parser.getName());
+                XmlUtils.skipCurrentTag(parser);
+            }
+        }
+    }
+
+    private void readPackageUid(TypedXmlPullParser parser, String pkgName,
+            SparseArray<ArrayMap<String, SparseIntArray>> userPackageModes)
+            throws NumberFormatException, XmlPullParserException, IOException {
+        int userId = UserHandle.getUserId(parser.getAttributeInt(null, "n"));
+        int outerDepth = parser.getDepth();
+        int type;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                continue;
+            }
+
+            String tagName = parser.getName();
+            if (tagName.equals("op")) {
+                readOp(parser, userId, pkgName, userPackageModes);
+            } else {
+                Slog.w(TAG, "Unknown element under <pkg>: "
+                        + parser.getName());
+                XmlUtils.skipCurrentTag(parser);
+            }
+        }
+    }
+
+    private void readUidOps(TypedXmlPullParser parser, SparseArray<SparseIntArray> uidModes)
+            throws NumberFormatException,
+            XmlPullParserException, IOException {
+        final int uid = parser.getAttributeInt(null, "n");
+        SparseIntArray modes = uidModes.get(uid);
+        if (modes == null) {
+            modes = new SparseIntArray();
+            uidModes.put(uid, modes);
+        }
+
+        int outerDepth = parser.getDepth();
+        int type;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                continue;
+            }
+
+            String tagName = parser.getName();
+            if (tagName.equals("op")) {
+                final int code = parser.getAttributeInt(null, "n");
+                final int mode = parser.getAttributeInt(null, "m");
+
+                if (mode != opToDefaultMode(code)) {
+                    modes.put(code, mode);
+                }
+            } else {
+                Slog.w(TAG, "Unknown element under <uid>: "
+                        + parser.getName());
+                XmlUtils.skipCurrentTag(parser);
+            }
+        }
+    }
+
+    private void readUser(TypedXmlPullParser parser,
+            SparseArray<ArrayMap<String, SparseIntArray>> userPackageModes)
+            throws NumberFormatException, XmlPullParserException, IOException {
+        int userId = parser.getAttributeInt(null, "n");
+        int outerDepth = parser.getDepth();
+        int type;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                continue;
+            }
+
+            String tagName = parser.getName();
+            if (tagName.equals("pkg")) {
+                readPackageOp(parser, userId, userPackageModes);
+            } else {
+                Slog.w(TAG, "Unknown element under <user>: "
+                        + parser.getName());
+                XmlUtils.skipCurrentTag(parser);
+            }
+        }
+    }
+
+    // read package tag refactored in Android U
+    private void readPackageOp(TypedXmlPullParser parser, int userId,
+            SparseArray<ArrayMap<String, SparseIntArray>> userPackageModes)
+            throws NumberFormatException, XmlPullParserException, IOException {
+        String pkgName = parser.getAttributeValue(null, "n");
+        int outerDepth = parser.getDepth();
+        int type;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                continue;
+            }
+
+            String tagName = parser.getName();
+            if (tagName.equals("op")) {
+                readOp(parser, userId, pkgName, userPackageModes);
+            } else {
+                Slog.w(TAG, "Unknown element under <pkg>: "
+                        + parser.getName());
+                XmlUtils.skipCurrentTag(parser);
+            }
+        }
+    }
+
+    private void readOp(TypedXmlPullParser parser, int userId, @NonNull String pkgName,
+            SparseArray<ArrayMap<String, SparseIntArray>> userPackageModes)
+            throws NumberFormatException, XmlPullParserException {
+        final int opCode = parser.getAttributeInt(null, "n");
+        final int defaultMode = AppOpsManager.opToDefaultMode(opCode);
+        final int mode = parser.getAttributeInt(null, "m", defaultMode);
+
+        if (mode != defaultMode) {
+            ArrayMap<String, SparseIntArray> packageModes = userPackageModes.get(userId);
+            if (packageModes == null) {
+                packageModes = new ArrayMap<>();
+                userPackageModes.put(userId, packageModes);
+            }
+
+            SparseIntArray modes = packageModes.get(pkgName);
+            if (modes == null) {
+                modes = new SparseIntArray();
+                packageModes.put(pkgName, modes);
+            }
+
+            modes.put(opCode, mode);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/appop/OWNERS b/services/core/java/com/android/server/appop/OWNERS
index 999ea0e..2fe78c5 100644
--- a/services/core/java/com/android/server/appop/OWNERS
+++ b/services/core/java/com/android/server/appop/OWNERS
@@ -1 +1,2 @@
+#Bug component: 137825
 include /core/java/android/permission/OWNERS
diff --git a/services/core/java/com/android/server/attention/AttentionManagerService.java b/services/core/java/com/android/server/attention/AttentionManagerService.java
index 658e38b..5edbaa9 100644
--- a/services/core/java/com/android/server/attention/AttentionManagerService.java
+++ b/services/core/java/com/android/server/attention/AttentionManagerService.java
@@ -96,12 +96,15 @@
     @VisibleForTesting
     static final String KEY_SERVICE_ENABLED = "service_enabled";
 
-    /** Default value in absence of {@link DeviceConfig} override. */
+    /** Default service enabled value in absence of {@link DeviceConfig} override. */
     private static final boolean DEFAULT_SERVICE_ENABLED = true;
 
     @VisibleForTesting
     boolean mIsServiceEnabled;
 
+    @VisibleForTesting
+    boolean mIsProximityEnabled;
+
     /**
      * DeviceConfig flag name, describes how much time we consider a result fresh; if the check
      * attention called within that period - cached value will be returned.
@@ -180,6 +183,9 @@
             DeviceConfig.addOnPropertiesChangedListener(NAMESPACE_ATTENTION_MANAGER_SERVICE,
                     ActivityThread.currentApplication().getMainExecutor(),
                     (properties) -> onDeviceConfigChange(properties.getKeyset()));
+            mIsProximityEnabled = mContext.getResources()
+                    .getBoolean(com.android.internal.R.bool.config_enableProximityService);
+            Slog.i(LOG_TAG, "mIsProximityEnabled is: " + mIsProximityEnabled);
         }
     }
 
@@ -351,7 +357,7 @@
     @VisibleForTesting
     boolean onStartProximityUpdates(ProximityUpdateCallbackInternal callbackInternal) {
         Objects.requireNonNull(callbackInternal);
-        if (!mIsServiceEnabled) {
+        if (!mIsProximityEnabled) {
             Slog.w(LOG_TAG, "Trying to call onProximityUpdate() on an unsupported device.");
             return false;
         }
@@ -488,6 +494,7 @@
     private void dumpInternal(IndentingPrintWriter ipw) {
         ipw.println("Attention Manager Service (dumpsys attention) state:\n");
         ipw.println("isServiceEnabled=" + mIsServiceEnabled);
+        ipw.println("mIsProximityEnabled=" + mIsProximityEnabled);
         ipw.println("mStaleAfterMillis=" + mStaleAfterMillis);
         ipw.println("AttentionServicePackageName=" + getServiceConfigPackage(mContext));
         ipw.println("Resolved component:");
@@ -519,6 +526,11 @@
         }
 
         @Override
+        public boolean isProximitySupported() {
+            return AttentionManagerService.this.mIsProximityEnabled;
+        }
+
+        @Override
         public boolean checkAttention(long timeout, AttentionCallbackInternal callbackInternal) {
             return AttentionManagerService.this.checkAttention(timeout, callbackInternal);
         }
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 29a1941..e4dd4a4 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -30,6 +30,8 @@
 import android.media.AudioDeviceAttributes;
 import android.media.AudioDeviceInfo;
 import android.media.AudioManager;
+import android.media.AudioPlaybackConfiguration;
+import android.media.AudioRecordingConfiguration;
 import android.media.AudioRoutesInfo;
 import android.media.AudioSystem;
 import android.media.BluetoothProfileConnectionInfo;
@@ -289,37 +291,38 @@
      * @param on
      * @param eventSource for logging purposes
      */
-    /*package*/ void setSpeakerphoneOn(IBinder cb, int pid, boolean on, String eventSource) {
+    /*package*/ void setSpeakerphoneOn(
+            IBinder cb, int uid, boolean on, boolean isPrivileged, String eventSource) {
 
         if (AudioService.DEBUG_COMM_RTE) {
-            Log.v(TAG, "setSpeakerphoneOn, on: " + on + " pid: " + pid);
+            Log.v(TAG, "setSpeakerphoneOn, on: " + on + " uid: " + uid);
         }
         postSetCommunicationDeviceForClient(new CommunicationDeviceInfo(
-                cb, pid, new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_SPEAKER, ""),
-                on, BtHelper.SCO_MODE_UNDEFINED, eventSource, false));
+                cb, uid, new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_SPEAKER, ""),
+                on, BtHelper.SCO_MODE_UNDEFINED, eventSource, false, isPrivileged));
     }
 
     /**
      * Select device for use for communication use cases.
      * @param cb Client binder for death detection
-     * @param pid Client pid
+     * @param uid Client uid
      * @param device Device selected or null to unselect.
      * @param eventSource for logging purposes
      */
 
     private static final long SET_COMMUNICATION_DEVICE_TIMEOUT_MS = 3000;
 
-    /*package*/ boolean setCommunicationDevice(
-            IBinder cb, int pid, AudioDeviceInfo device, String eventSource) {
+    /*package*/ boolean setCommunicationDevice(IBinder cb, int uid, AudioDeviceInfo device,
+                                               boolean isPrivileged, String eventSource) {
 
         if (AudioService.DEBUG_COMM_RTE) {
-            Log.v(TAG, "setCommunicationDevice, device: " + device + ", pid: " + pid);
+            Log.v(TAG, "setCommunicationDevice, device: " + device + ", uid: " + uid);
         }
 
         AudioDeviceAttributes deviceAttr =
                 (device != null) ? new AudioDeviceAttributes(device) : null;
-        CommunicationDeviceInfo deviceInfo = new CommunicationDeviceInfo(cb, pid, deviceAttr,
-                device != null, BtHelper.SCO_MODE_UNDEFINED, eventSource, true);
+        CommunicationDeviceInfo deviceInfo = new CommunicationDeviceInfo(cb, uid, deviceAttr,
+                device != null, BtHelper.SCO_MODE_UNDEFINED, eventSource, true, isPrivileged);
         postSetCommunicationDeviceForClient(deviceInfo);
         boolean status;
         synchronized (deviceInfo) {
@@ -353,7 +356,7 @@
             Log.v(TAG, "onSetCommunicationDeviceForClient: " + deviceInfo);
         }
         if (!deviceInfo.mOn) {
-            CommunicationRouteClient client = getCommunicationRouteClientForPid(deviceInfo.mPid);
+            CommunicationRouteClient client = getCommunicationRouteClientForUid(deviceInfo.mUid);
             if (client == null || (deviceInfo.mDevice != null
                     && !deviceInfo.mDevice.equals(client.getDevice()))) {
                 return false;
@@ -361,22 +364,23 @@
         }
 
         AudioDeviceAttributes device = deviceInfo.mOn ? deviceInfo.mDevice : null;
-        setCommunicationRouteForClient(deviceInfo.mCb, deviceInfo.mPid, device,
-                deviceInfo.mScoAudioMode, deviceInfo.mEventSource);
+        setCommunicationRouteForClient(deviceInfo.mCb, deviceInfo.mUid, device,
+                deviceInfo.mScoAudioMode, deviceInfo.mIsPrivileged, deviceInfo.mEventSource);
         return true;
     }
 
     @GuardedBy("mDeviceStateLock")
     /*package*/ void setCommunicationRouteForClient(
-                            IBinder cb, int pid, AudioDeviceAttributes device,
-                            int scoAudioMode, String eventSource) {
+                            IBinder cb, int uid, AudioDeviceAttributes device,
+                            int scoAudioMode, boolean isPrivileged, String eventSource) {
 
         if (AudioService.DEBUG_COMM_RTE) {
-            Log.v(TAG, "setCommunicationRouteForClient: device: " + device);
+            Log.v(TAG, "setCommunicationRouteForClient: device: " + device
+                    + ", eventSource: " + eventSource);
         }
         AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent(
-                                        "setCommunicationRouteForClient for pid: " + pid
-                                        + " device: " + device
+                                        "setCommunicationRouteForClient for uid: " + uid
+                                        + " device: " + device + " isPrivileged: " + isPrivileged
                                         + " from API: " + eventSource)).printLog(TAG));
 
         final boolean wasBtScoRequested = isBluetoothScoRequested();
@@ -385,16 +389,18 @@
 
         // Save previous client route in case of failure to start BT SCO audio
         AudioDeviceAttributes prevClientDevice = null;
-        client = getCommunicationRouteClientForPid(pid);
+        boolean prevPrivileged = false;
+        client = getCommunicationRouteClientForUid(uid);
         if (client != null) {
             prevClientDevice = client.getDevice();
+            prevPrivileged = client.isPrivileged();
         }
 
         if (device != null) {
-            client = addCommunicationRouteClient(cb, pid, device);
+            client = addCommunicationRouteClient(cb, uid, device, isPrivileged);
             if (client == null) {
-                Log.w(TAG, "setCommunicationRouteForClient: could not add client for pid: "
-                        + pid + " and device: " + device);
+                Log.w(TAG, "setCommunicationRouteForClient: could not add client for uid: "
+                        + uid + " and device: " + device);
             }
         } else {
             client = removeCommunicationRouteClient(cb, true);
@@ -406,11 +412,11 @@
         boolean isBtScoRequested = isBluetoothScoRequested();
         if (isBtScoRequested && (!wasBtScoRequested || !isBluetoothScoActive())) {
             if (!mBtHelper.startBluetoothSco(scoAudioMode, eventSource)) {
-                Log.w(TAG, "setCommunicationRouteForClient: failure to start BT SCO for pid: "
-                        + pid);
+                Log.w(TAG, "setCommunicationRouteForClient: failure to start BT SCO for uid: "
+                        + uid);
                 // clean up or restore previous client selection
                 if (prevClientDevice != null) {
-                    addCommunicationRouteClient(cb, pid, prevClientDevice);
+                    addCommunicationRouteClient(cb, uid, prevClientDevice, prevPrivileged);
                 } else {
                     removeCommunicationRouteClient(cb, true);
                 }
@@ -447,11 +453,12 @@
     @GuardedBy("mDeviceStateLock")
     private CommunicationRouteClient topCommunicationRouteClient() {
         for (CommunicationRouteClient crc : mCommunicationRouteClients) {
-            if (crc.getPid() == mAudioModeOwner.mPid) {
+            if (crc.getUid() == mAudioModeOwner.mUid) {
                 return crc;
             }
         }
-        if (!mCommunicationRouteClients.isEmpty() && mAudioModeOwner.mPid == 0) {
+        if (!mCommunicationRouteClients.isEmpty() && mAudioModeOwner.mPid == 0
+                && mCommunicationRouteClients.get(0).isActive()) {
             return mCommunicationRouteClients.get(0);
         }
         return null;
@@ -491,14 +498,48 @@
     };
 
     /*package */ static boolean isValidCommunicationDevice(AudioDeviceInfo device) {
+        return isValidCommunicationDeviceType(device.getType());
+    }
+
+    private static boolean isValidCommunicationDeviceType(int deviceType) {
         for (int type : VALID_COMMUNICATION_DEVICE_TYPES) {
-            if (device.getType() == type) {
+            if (deviceType == type) {
                 return true;
             }
         }
         return false;
     }
 
+    /*package */
+    void postCheckCommunicationDeviceRemoval(@NonNull AudioDeviceAttributes device) {
+        if (!isValidCommunicationDeviceType(
+                AudioDeviceInfo.convertInternalDeviceToDeviceType(device.getInternalType()))) {
+            return;
+        }
+        sendLMsgNoDelay(MSG_L_CHECK_COMMUNICATION_DEVICE_REMOVAL, SENDMSG_QUEUE, device);
+    }
+
+    @GuardedBy("mDeviceStateLock")
+    void onCheckCommunicationDeviceRemoval(@NonNull AudioDeviceAttributes device) {
+        if (AudioService.DEBUG_COMM_RTE) {
+            Log.v(TAG, "onCheckCommunicationDeviceRemoval device: " + device.toString());
+        }
+        for (CommunicationRouteClient crc : mCommunicationRouteClients) {
+            if (device.equals(crc.getDevice())) {
+                if (AudioService.DEBUG_COMM_RTE) {
+                    Log.v(TAG, "onCheckCommunicationDeviceRemoval removing client: "
+                            + crc.toString());
+                }
+                // Cancelling the route for this client will remove it from the stack and update
+                // the communication route.
+                CommunicationDeviceInfo deviceInfo = new CommunicationDeviceInfo(
+                        crc.getBinder(), crc.getUid(), device, false,
+                        BtHelper.SCO_MODE_UNDEFINED, "onCheckCommunicationDeviceRemoval",
+                        false, crc.isPrivileged());
+                postSetCommunicationDeviceForClient(deviceInfo);
+            }
+        }
+    }
     /* package */ static List<AudioDeviceInfo> getAvailableCommunicationDevices() {
         ArrayList<AudioDeviceInfo> commDevices = new ArrayList<>();
         AudioDeviceInfo[] allDevices =
@@ -1107,26 +1148,26 @@
         sendLMsgNoDelay(MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE, SENDMSG_QUEUE, info);
     }
 
-    /*package*/ void startBluetoothScoForClient(IBinder cb, int pid, int scoAudioMode,
-                @NonNull String eventSource) {
+    /*package*/ void startBluetoothScoForClient(IBinder cb, int uid, int scoAudioMode,
+                                                boolean isPrivileged, @NonNull String eventSource) {
 
         if (AudioService.DEBUG_COMM_RTE) {
-            Log.v(TAG, "startBluetoothScoForClient, pid: " + pid);
+            Log.v(TAG, "startBluetoothScoForClient, uid: " + uid);
         }
         postSetCommunicationDeviceForClient(new CommunicationDeviceInfo(
-                cb, pid, new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_BLUETOOTH_SCO, ""),
-                true, scoAudioMode, eventSource, false));
+                cb, uid, new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_BLUETOOTH_SCO, ""),
+                true, scoAudioMode, eventSource, false, isPrivileged));
     }
 
     /*package*/ void stopBluetoothScoForClient(
-                        IBinder cb, int pid, @NonNull String eventSource) {
+                        IBinder cb, int uid, boolean isPrivileged, @NonNull String eventSource) {
 
         if (AudioService.DEBUG_COMM_RTE) {
-            Log.v(TAG, "stopBluetoothScoForClient, pid: " + pid);
+            Log.v(TAG, "stopBluetoothScoForClient, uid: " + uid);
         }
         postSetCommunicationDeviceForClient(new CommunicationDeviceInfo(
-                cb, pid, new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_BLUETOOTH_SCO, ""),
-                false, BtHelper.SCO_MODE_UNDEFINED, eventSource, false));
+                cb, uid, new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_BLUETOOTH_SCO, ""),
+                false, BtHelper.SCO_MODE_UNDEFINED, eventSource, false, isPrivileged));
     }
 
     /*package*/ int setPreferredDevicesForStrategySync(int strategy,
@@ -1367,22 +1408,24 @@
 
     /*package*/ static final class CommunicationDeviceInfo {
         final @NonNull IBinder mCb; // Identifies the requesting client for death handler
-        final int mPid; // Requester process ID
+        final int mUid; // Requester UID
         final @Nullable AudioDeviceAttributes mDevice; // Device being set or reset.
         final boolean mOn; // true if setting, false if resetting
         final int mScoAudioMode; // only used for SCO: requested audio mode
+        final boolean mIsPrivileged; // true if the client app has MODIFY_PHONE_STATE permission
         final @NonNull String mEventSource; // caller identifier for logging
         boolean mWaitForStatus; // true if the caller waits for a completion status (API dependent)
         boolean mStatus = false; // completion status only used if mWaitForStatus is true
 
-        CommunicationDeviceInfo(@NonNull IBinder cb, int pid,
+        CommunicationDeviceInfo(@NonNull IBinder cb, int uid,
                 @Nullable AudioDeviceAttributes device, boolean on, int scoAudioMode,
-                @NonNull String eventSource, boolean waitForStatus) {
+                @NonNull String eventSource, boolean waitForStatus, boolean isPrivileged) {
             mCb = cb;
-            mPid = pid;
+            mUid = uid;
             mDevice = device;
             mOn = on;
             mScoAudioMode = scoAudioMode;
+            mIsPrivileged = isPrivileged;
             mEventSource = eventSource;
             mWaitForStatus = waitForStatus;
         }
@@ -1401,16 +1444,17 @@
             }
 
             return mCb.equals(((CommunicationDeviceInfo) o).mCb)
-                    && mPid == ((CommunicationDeviceInfo) o).mPid;
+                    && mUid == ((CommunicationDeviceInfo) o).mUid;
         }
 
         @Override
         public String toString() {
             return "CommunicationDeviceInfo mCb=" + mCb.toString()
-                    + " mPid=" + mPid
+                    + " mUid=" + mUid
                     + " mDevice=[" + (mDevice != null ? mDevice.toString() : "null") + "]"
                     + " mOn=" + mOn
                     + " mScoAudioMode=" + mScoAudioMode
+                    + " mIsPrivileged=" + mIsPrivileged
                     + " mEventSource=" + mEventSource
                     + " mWaitForStatus=" + mWaitForStatus
                     + " mStatus=" + mStatus;
@@ -1440,7 +1484,7 @@
         }
     }
 
-    /*package*/ boolean handleDeviceConnection(AudioDeviceAttributes attributes,
+    /*package*/ boolean handleDeviceConnection(@NonNull AudioDeviceAttributes attributes,
                                 boolean connect, @Nullable BluetoothDevice btDevice) {
         synchronized (mDeviceStateLock) {
             return mDeviceInventory.handleDeviceConnection(
@@ -1507,8 +1551,7 @@
 
         pw.println("\n" + prefix + "Communication route clients:");
         mCommunicationRouteClients.forEach((cl) -> {
-            pw.println("  " + prefix + "pid: " + cl.getPid() + " device: "
-                        + cl.getDevice() + " cb: " + cl.getBinder()); });
+            pw.println("  " + prefix + cl.toString()); });
 
         pw.println("\n" + prefix + "Computed Preferred communication device: "
                 +  preferredCommunicationDevice());
@@ -1850,6 +1893,15 @@
                     final BluetoothDevice btDevice = (BluetoothDevice) msg.obj;
                     BtHelper.onNotifyPreferredAudioProfileApplied(btDevice);
                 } break;
+
+                case MSG_L_CHECK_COMMUNICATION_DEVICE_REMOVAL: {
+                    synchronized (mSetModeLock) {
+                        synchronized (mDeviceStateLock) {
+                            onCheckCommunicationDeviceRemoval((AudioDeviceAttributes) msg.obj);
+                        }
+                    }
+                } break;
+
                 default:
                     Log.wtf(TAG, "Invalid message " + msg.what);
             }
@@ -1926,6 +1978,7 @@
     private static final int MSG_IL_BTLEAUDIO_TIMEOUT = 49;
 
     private static final int MSG_L_NOTIFY_PREFERRED_AUDIOPROFILE_APPLIED = 52;
+    private static final int MSG_L_CHECK_COMMUNICATION_DEVICE_REMOVAL = 53;
 
     private static boolean isMessageHandledUnderWakelock(int msgId) {
         switch(msgId) {
@@ -2101,13 +2154,20 @@
 
     private class CommunicationRouteClient implements IBinder.DeathRecipient {
         private final IBinder mCb;
-        private final int mPid;
+        private final int mUid;
+        private final boolean mIsPrivileged;
         private AudioDeviceAttributes mDevice;
+        private boolean mPlaybackActive;
+        private boolean mRecordingActive;
 
-        CommunicationRouteClient(IBinder cb, int pid, AudioDeviceAttributes device) {
+        CommunicationRouteClient(IBinder cb, int uid, AudioDeviceAttributes device,
+                                 boolean isPrivileged) {
             mCb = cb;
-            mPid = pid;
+            mUid = uid;
             mDevice = device;
+            mIsPrivileged = isPrivileged;
+            mPlaybackActive = mAudioService.isPlaybackActiveForUid(uid);
+            mRecordingActive = mAudioService.isRecordingActiveForUid(uid);
         }
 
         public boolean registerDeathRecipient() {
@@ -2125,7 +2185,7 @@
             try {
                 mCb.unlinkToDeath(this, 0);
             } catch (NoSuchElementException e) {
-                Log.w(TAG, "CommunicationRouteClient could not not unregistered to binder");
+                Log.w(TAG, "CommunicationRouteClient could not unlink to " + mCb + " binder death");
             }
         }
 
@@ -2138,13 +2198,38 @@
             return mCb;
         }
 
-        int getPid() {
-            return mPid;
+        int getUid() {
+            return mUid;
+        }
+
+        boolean isPrivileged() {
+            return mIsPrivileged;
         }
 
         AudioDeviceAttributes getDevice() {
             return mDevice;
         }
+
+        public void setPlaybackActive(boolean active) {
+            mPlaybackActive = active;
+        }
+
+        public void setRecordingActive(boolean active) {
+            mRecordingActive = active;
+        }
+
+        public boolean isActive() {
+            return mIsPrivileged || mRecordingActive || mPlaybackActive;
+        }
+
+        @Override
+        public String toString() {
+            return "[CommunicationRouteClient: mUid: " + mUid
+                    + " mDevice: " + mDevice.toString()
+                    + " mIsPrivileged: " + mIsPrivileged
+                    + " mPlaybackActive: " + mPlaybackActive
+                    + " mRecordingActive: " + mRecordingActive + "]";
+        }
     }
 
     // @GuardedBy("mSetModeLock")
@@ -2154,8 +2239,9 @@
             return;
         }
         Log.w(TAG, "Communication client died");
-        setCommunicationRouteForClient(client.getBinder(), client.getPid(), null,
-                BtHelper.SCO_MODE_UNDEFINED, "onCommunicationRouteClientDied");
+        setCommunicationRouteForClient(client.getBinder(), client.getUid(), null,
+                BtHelper.SCO_MODE_UNDEFINED, client.isPrivileged(),
+                "onCommunicationRouteClientDied");
     }
 
     /**
@@ -2242,8 +2328,8 @@
                     + crc + " eventSource: " + eventSource);
         }
         if (crc != null) {
-            setCommunicationRouteForClient(crc.getBinder(), crc.getPid(), crc.getDevice(),
-                    BtHelper.SCO_MODE_UNDEFINED, eventSource);
+            setCommunicationRouteForClient(crc.getBinder(), crc.getUid(), crc.getDevice(),
+                    BtHelper.SCO_MODE_UNDEFINED, crc.isPrivileged(), eventSource);
         }
     }
 
@@ -2267,6 +2353,7 @@
         dispatchCommunicationDevice();
     }
 
+    @GuardedBy("mDeviceStateLock")
     private CommunicationRouteClient removeCommunicationRouteClient(
                     IBinder cb, boolean unregister) {
         for (CommunicationRouteClient cl : mCommunicationRouteClients) {
@@ -2282,11 +2369,12 @@
     }
 
     @GuardedBy("mDeviceStateLock")
-    private CommunicationRouteClient addCommunicationRouteClient(
-                    IBinder cb, int pid, AudioDeviceAttributes device) {
+    private CommunicationRouteClient addCommunicationRouteClient(IBinder cb, int uid,
+                AudioDeviceAttributes device, boolean isPrivileged) {
         // always insert new request at first position
         removeCommunicationRouteClient(cb, true);
-        CommunicationRouteClient client = new CommunicationRouteClient(cb, pid, device);
+        CommunicationRouteClient client =
+                new CommunicationRouteClient(cb, uid, device, isPrivileged);
         if (client.registerDeathRecipient()) {
             mCommunicationRouteClients.add(0, client);
             return client;
@@ -2295,9 +2383,9 @@
     }
 
     @GuardedBy("mDeviceStateLock")
-    private CommunicationRouteClient getCommunicationRouteClientForPid(int pid) {
+    private CommunicationRouteClient getCommunicationRouteClientForUid(int uid) {
         for (CommunicationRouteClient cl : mCommunicationRouteClients) {
-            if (cl.getPid() == pid) {
+            if (cl.getUid() == uid) {
                 return cl;
             }
         }
@@ -2330,6 +2418,45 @@
         return device;
     }
 
+    void updateCommunicationRouteClientsActivity(
+            List<AudioPlaybackConfiguration> playbackConfigs,
+            List<AudioRecordingConfiguration> recordConfigs) {
+        synchronized (mSetModeLock) {
+            synchronized (mDeviceStateLock) {
+                boolean updateCommunicationRoute = false;
+                for (CommunicationRouteClient crc : mCommunicationRouteClients) {
+                    boolean wasActive = crc.isActive();
+                    if (playbackConfigs != null) {
+                        crc.setPlaybackActive(false);
+                        for (AudioPlaybackConfiguration config : playbackConfigs) {
+                            if (config.getClientUid() == crc.getUid()
+                                    && config.isActive()) {
+                                crc.setPlaybackActive(true);
+                                break;
+                            }
+                        }
+                    }
+                    if (recordConfigs != null) {
+                        crc.setRecordingActive(false);
+                        for (AudioRecordingConfiguration config : recordConfigs) {
+                            if (config.getClientUid() == crc.getUid()
+                                    && !config.isClientSilenced()) {
+                                crc.setRecordingActive(true);
+                                break;
+                            }
+                        }
+                    }
+                    if (wasActive != crc.isActive()) {
+                        updateCommunicationRoute = true;
+                    }
+                }
+                if (updateCommunicationRoute) {
+                    postUpdateCommunicationRouteClient("updateCommunicationRouteClientsActivity");
+                }
+            }
+        }
+    }
+
     @Nullable UUID getDeviceSensorUuid(AudioDeviceAttributes device) {
         synchronized (mDeviceStateLock) {
             return mDeviceInventory.getDeviceSensorUuid(device);
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index 0c7f11f..b70e11d 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -1245,8 +1245,9 @@
      * @param btDevice the corresponding Bluetooth device when relevant.
      * @return false if an error was reported by AudioSystem
      */
-    /*package*/ boolean handleDeviceConnection(AudioDeviceAttributes attributes, boolean connect,
-            boolean isForTesting, @Nullable BluetoothDevice btDevice) {
+    /*package*/ boolean handleDeviceConnection(@NonNull AudioDeviceAttributes attributes,
+                                               boolean connect, boolean isForTesting,
+                                               @Nullable BluetoothDevice btDevice) {
         int device = attributes.getInternalType();
         String address = attributes.getAddress();
         String deviceName = attributes.getName();
@@ -1297,6 +1298,7 @@
                         AudioSystem.DEVICE_STATE_UNAVAILABLE, AudioSystem.AUDIO_FORMAT_DEFAULT);
                 // always remove even if disconnection failed
                 mConnectedDevices.remove(deviceKey);
+                mDeviceBroker.postCheckCommunicationDeviceRemoval(attributes);
                 status = true;
             }
             if (status) {
@@ -1801,8 +1803,9 @@
 
         // device to remove was visible by APM, update APM
         mDeviceBroker.clearAvrcpAbsoluteVolumeSupported();
-        final int res = mAudioSystem.setDeviceConnectionState(new AudioDeviceAttributes(
-                AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address),
+        AudioDeviceAttributes ada = new AudioDeviceAttributes(
+                AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address);
+        final int res = mAudioSystem.setDeviceConnectionState(ada,
                 AudioSystem.DEVICE_STATE_UNAVAILABLE, a2dpCodec);
 
         if (res != AudioSystem.AUDIO_STATUS_OK) {
@@ -1816,11 +1819,13 @@
                     "A2DP device addr=" + address + " made unavailable")).printLog(TAG));
         }
         mApmConnectedDevices.remove(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP);
+
         // Remove A2DP routes as well
         setCurrentAudioRouteNameIfPossible(null, true /*fromA2dp*/);
         mmi.record();
         updateBluetoothPreferredModes_l(null /*connectedDevice*/);
         purgeDevicesRoles_l();
+        mDeviceBroker.postCheckCommunicationDeviceRemoval(ada);
     }
 
     @GuardedBy("mDevicesLock")
@@ -1855,12 +1860,14 @@
 
     @GuardedBy("mDevicesLock")
     private void makeA2dpSrcUnavailable(String address) {
-        mAudioSystem.setDeviceConnectionState(new AudioDeviceAttributes(
-                AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address),
+        AudioDeviceAttributes ada = new AudioDeviceAttributes(
+                AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address);
+        mAudioSystem.setDeviceConnectionState(ada,
                 AudioSystem.DEVICE_STATE_UNAVAILABLE,
                 AudioSystem.AUDIO_FORMAT_DEFAULT);
         mConnectedDevices.remove(
                 DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address));
+        mDeviceBroker.postCheckCommunicationDeviceRemoval(ada);
     }
 
     @GuardedBy("mDevicesLock")
@@ -1893,8 +1900,9 @@
 
     @GuardedBy("mDevicesLock")
     private void makeHearingAidDeviceUnavailable(String address) {
-        mAudioSystem.setDeviceConnectionState(new AudioDeviceAttributes(
-                AudioSystem.DEVICE_OUT_HEARING_AID, address),
+        AudioDeviceAttributes ada = new AudioDeviceAttributes(
+                AudioSystem.DEVICE_OUT_HEARING_AID, address);
+        mAudioSystem.setDeviceConnectionState(ada,
                 AudioSystem.DEVICE_STATE_UNAVAILABLE,
                 AudioSystem.AUDIO_FORMAT_DEFAULT);
         mConnectedDevices.remove(
@@ -1906,6 +1914,7 @@
                 .set(MediaMetrics.Property.DEVICE,
                         AudioSystem.getDeviceName(AudioSystem.DEVICE_OUT_HEARING_AID))
                 .record();
+        mDeviceBroker.postCheckCommunicationDeviceRemoval(ada);
     }
 
     /**
@@ -2002,9 +2011,10 @@
 
     @GuardedBy("mDevicesLock")
     private void makeLeAudioDeviceUnavailableNow(String address, int device) {
+        AudioDeviceAttributes ada = null;
         if (device != AudioSystem.DEVICE_NONE) {
-            final int res = AudioSystem.setDeviceConnectionState(new AudioDeviceAttributes(
-                    device, address),
+            ada = new AudioDeviceAttributes(device, address);
+            final int res = AudioSystem.setDeviceConnectionState(ada,
                     AudioSystem.DEVICE_STATE_UNAVAILABLE,
                     AudioSystem.AUDIO_FORMAT_DEFAULT);
 
@@ -2024,6 +2034,9 @@
         setCurrentAudioRouteNameIfPossible(null, false /*fromA2dp*/);
         updateBluetoothPreferredModes_l(null /*connectedDevice*/);
         purgeDevicesRoles_l();
+        if (ada != null) {
+            mDeviceBroker.postCheckCommunicationDeviceRemoval(ada);
+        }
     }
 
     @GuardedBy("mDevicesLock")
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 53ed38e..7404b19 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -17,7 +17,6 @@
 package com.android.server.audio;
 
 import static android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED;
-import static android.Manifest.permission.REMOTE_AUDIO_PLAYBACK;
 import static android.app.BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT;
 import static android.media.AudioManager.RINGER_MODE_NORMAL;
 import static android.media.AudioManager.RINGER_MODE_SILENT;
@@ -356,7 +355,7 @@
     private static final int MSG_SET_ALL_VOLUMES = 10;
     private static final int MSG_UNLOAD_SOUND_EFFECTS = 15;
     private static final int MSG_SYSTEM_READY = 16;
-    private static final int MSG_UNMUTE_STREAM = 18;
+    private static final int MSG_UNMUTE_STREAM_ON_SINGLE_VOL_DEVICE = 18;
     private static final int MSG_DYN_POLICY_MIX_STATE_UPDATE = 19;
     private static final int MSG_INDICATE_SYSTEM_READY = 20;
     private static final int MSG_ACCESSORY_PLUG_MEDIA_UNMUTE = 21;
@@ -393,6 +392,7 @@
     private static final int MSG_DISPATCH_PREFERRED_MIXER_ATTRIBUTES = 52;
     private static final int MSG_LOWER_VOLUME_TO_RS1 = 53;
     private static final int MSG_CONFIGURATION_CHANGED = 54;
+    private static final int MSG_BROADCAST_MASTER_MUTE = 55;
 
     /** Messages handled by the {@link SoundDoseHelper}. */
     /*package*/ static final int SAFE_MEDIA_VOLUME_MSG_START = 1000;
@@ -975,6 +975,9 @@
     @GuardedBy("mSettingsLock")
     private boolean mRttEnabled = false;
 
+    private AtomicBoolean mMasterMute = new AtomicBoolean(false);
+
+
     ///////////////////////////////////////////////////////////////////////////
     // Construction
     ///////////////////////////////////////////////////////////////////////////
@@ -2455,13 +2458,11 @@
         return true;
     }
 
+    @android.annotation.EnforcePermission(android.Manifest.permission.WRITE_SETTINGS)
     /** @see AudioManager#setEncodedSurroundMode(int) */
     @Override
     public boolean setEncodedSurroundMode(@AudioManager.EncodedSurroundOutputMode int mode) {
-        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.WRITE_SETTINGS)
-                != PackageManager.PERMISSION_GRANTED) {
-            throw new SecurityException("Missing WRITE_SETTINGS permission");
-        }
+        setEncodedSurroundMode_enforcePermission();
 
         final long token = Binder.clearCallingIdentity();
         try {
@@ -2737,21 +2738,18 @@
         }
         final int currentUser = getCurrentUserId();
 
+        if (mUseFixedVolume) {
+            AudioSystem.setMasterVolume(1.0f);
+        }
+
         // Check the current user restriction.
         boolean masterMute =
                 mUserManagerInternal.getUserRestriction(currentUser,
                         UserManager.DISALLOW_UNMUTE_DEVICE)
                         || mUserManagerInternal.getUserRestriction(currentUser,
                         UserManager.DISALLOW_ADJUST_VOLUME);
-        if (mUseFixedVolume) {
-            masterMute = false;
-            AudioSystem.setMasterVolume(1.0f);
-        }
-        if (DEBUG_VOL) {
-            Log.d(TAG, String.format("Master mute %s, user=%d", masterMute, currentUser));
-        }
-        AudioSystem.setMasterMute(masterMute);
-        broadcastMasterMuteStatus(masterMute);
+        setMasterMuteInternalNoCallerCheck(
+                masterMute, /* flags =*/ 0, currentUser, "readUserRestrictions");
 
         mMicMuteFromRestrictions = mUserManagerInternal.getUserRestriction(
                 currentUser, UserManager.DISALLOW_UNMUTE_MICROPHONE);
@@ -3509,7 +3507,7 @@
 
         if (adjustVolume && (direction != AudioManager.ADJUST_SAME)
                 && (keyEventMode != AudioDeviceVolumeManager.ADJUST_MODE_END)) {
-            mAudioHandler.removeMessages(MSG_UNMUTE_STREAM);
+            mAudioHandler.removeMessages(MSG_UNMUTE_STREAM_ON_SINGLE_VOL_DEVICE);
 
             if (isMuteAdjust && !mFullVolumeDevices.contains(device)) {
                 boolean state;
@@ -3536,8 +3534,9 @@
                         muteAliasStreams(streamTypeAlias, false);
                     } else if (direction == AudioManager.ADJUST_LOWER) {
                         if (mIsSingleVolume) {
-                            sendMsg(mAudioHandler, MSG_UNMUTE_STREAM, SENDMSG_QUEUE,
-                                    streamTypeAlias, flags, null, UNMUTE_STREAM_DELAY);
+                            sendMsg(mAudioHandler, MSG_UNMUTE_STREAM_ON_SINGLE_VOL_DEVICE,
+                                    SENDMSG_QUEUE, streamTypeAlias, flags, null,
+                                    UNMUTE_STREAM_DELAY);
                         }
                     }
                 }
@@ -3598,16 +3597,15 @@
             synchronized (mHdmiClientLock) {
                 if (mHdmiManager != null) {
                     // At most one of mHdmiPlaybackClient and mHdmiTvClient should be non-null
-                    HdmiClient fullVolumeHdmiClient = mHdmiPlaybackClient;
+                    HdmiClient hdmiClient = mHdmiPlaybackClient;
                     if (mHdmiTvClient != null) {
-                        fullVolumeHdmiClient = mHdmiTvClient;
+                        hdmiClient = mHdmiTvClient;
                     }
 
-                    if (fullVolumeHdmiClient != null
+                    if (((mHdmiPlaybackClient != null && isFullVolumeDevice(device))
+                            || (mHdmiTvClient != null && mHdmiSystemAudioSupported))
                             && mHdmiCecVolumeControlEnabled
-                            && streamTypeAlias == AudioSystem.STREAM_MUSIC
-                            // vol change on a full volume device
-                            && isFullVolumeDevice(device)) {
+                            && streamTypeAlias == AudioSystem.STREAM_MUSIC) {
                         int keyCode = KeyEvent.KEYCODE_UNKNOWN;
                         switch (direction) {
                             case AudioManager.ADJUST_RAISE:
@@ -3631,14 +3629,14 @@
                             try {
                                 switch (keyEventMode) {
                                     case AudioDeviceVolumeManager.ADJUST_MODE_NORMAL:
-                                        fullVolumeHdmiClient.sendVolumeKeyEvent(keyCode, true);
-                                        fullVolumeHdmiClient.sendVolumeKeyEvent(keyCode, false);
+                                        hdmiClient.sendVolumeKeyEvent(keyCode, true);
+                                        hdmiClient.sendVolumeKeyEvent(keyCode, false);
                                         break;
                                     case AudioDeviceVolumeManager.ADJUST_MODE_START:
-                                        fullVolumeHdmiClient.sendVolumeKeyEvent(keyCode, true);
+                                        hdmiClient.sendVolumeKeyEvent(keyCode, true);
                                         break;
                                     case AudioDeviceVolumeManager.ADJUST_MODE_END:
-                                        fullVolumeHdmiClient.sendVolumeKeyEvent(keyCode, false);
+                                        hdmiClient.sendVolumeKeyEvent(keyCode, false);
                                         break;
                                     default:
                                         Log.e(TAG, "Invalid keyEventMode " + keyEventMode);
@@ -3701,21 +3699,24 @@
     }
 
     // Called after a delay when volume down is pressed while muted
-    private void onUnmuteStream(int stream, int flags) {
+    private void onUnmuteStreamOnSingleVolDevice(int streamAlias, int flags) {
         boolean wasMuted;
         // Locking mSettingsLock to avoid inversion when calling vss.mute -> vss.doMute ->
         // vss.updateVolumeGroupIndex
         synchronized (mSettingsLock) {
             synchronized (VolumeStreamState.class) {
-                final VolumeStreamState streamState = mStreamStates[stream];
+                final VolumeStreamState streamState = mStreamStates[streamAlias];
                 // if unmuting causes a change, it was muted
-                wasMuted = streamState.mute(false, "onUnmuteStream");
-
-                final int device = getDeviceForStream(stream);
+                wasMuted = streamState.mute(false, "onUnmuteStreamOnSingleVolDevice");
+                if (wasMuted) {
+                    // Unmute all aliasted streams
+                    muteAliasStreams(streamAlias, false);
+                }
+                final int device = getDeviceForStream(streamAlias);
                 final int index = streamState.getIndex(device);
-                sendVolumeUpdate(stream, index, index, flags, device);
+                sendVolumeUpdate(streamAlias, index, index, flags, device);
             }
-            if (stream == AudioSystem.STREAM_MUSIC && wasMuted) {
+            if (streamAlias == AudioSystem.STREAM_MUSIC && wasMuted) {
                 synchronized (mHdmiClientLock) {
                     maybeSendSystemAudioStatusCommand(true);
                 }
@@ -4262,22 +4263,41 @@
         // 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
         // and request an audio mode update after a grace period.
+        updateAudioModeHandlers(
+                configs /* playbackConfigs */, null /* recordConfigs */);
+        mDeviceBroker.updateCommunicationRouteClientsActivity(
+                configs /* playbackConfigs */, null /* recordConfigs */);
+    }
+
+    void updateAudioModeHandlers(List<AudioPlaybackConfiguration> playbackConfigs,
+                                 List<AudioRecordingConfiguration> recordConfigs) {
         synchronized (mDeviceBroker.mSetModeLock) {
             boolean updateAudioMode = false;
             int existingMsgPolicy = SENDMSG_QUEUE;
             int delay = CHECK_MODE_FOR_UID_PERIOD_MS;
             for (SetModeDeathHandler h : mSetModeDeathHandlers) {
                 boolean wasActive = h.isActive();
-                h.setPlaybackActive(false);
-                for (AudioPlaybackConfiguration config : configs) {
-                    final int usage = config.getAudioAttributes().getUsage();
-                    if (config.getClientUid() == h.getUid()
-                            && (usage == AudioAttributes.USAGE_VOICE_COMMUNICATION
+                if (playbackConfigs != null) {
+                    h.setPlaybackActive(false);
+                    for (AudioPlaybackConfiguration config : playbackConfigs) {
+                        final int usage = config.getAudioAttributes().getUsage();
+                        if (config.getClientUid() == h.getUid()
+                                && (usage == AudioAttributes.USAGE_VOICE_COMMUNICATION
                                 || usage == AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING)
-                            && config.getPlayerState()
-                                == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
-                        h.setPlaybackActive(true);
-                        break;
+                                && config.isActive()) {
+                            h.setPlaybackActive(true);
+                            break;
+                        }
+                    }
+                }
+                if (recordConfigs != null) {
+                    h.setRecordingActive(false);
+                    for (AudioRecordingConfiguration config : recordConfigs) {
+                        if (config.getClientUid() == h.getUid() && !config.isClientSilenced()
+                                && config.getAudioSource() == AudioSource.VOICE_COMMUNICATION) {
+                            h.setRecordingActive(true);
+                            break;
+                        }
                     }
                 }
                 if (wasActive != h.isActive()) {
@@ -4315,38 +4335,10 @@
         // 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
         // and request an audio mode update after a grace period.
-        synchronized (mDeviceBroker.mSetModeLock) {
-            boolean updateAudioMode = false;
-            int existingMsgPolicy = SENDMSG_QUEUE;
-            int delay = CHECK_MODE_FOR_UID_PERIOD_MS;
-            for (SetModeDeathHandler h : mSetModeDeathHandlers) {
-                boolean wasActive = h.isActive();
-                h.setRecordingActive(false);
-                for (AudioRecordingConfiguration config : configs) {
-                    if (config.getClientUid() == h.getUid()
-                            && config.getAudioSource() == AudioSource.VOICE_COMMUNICATION) {
-                        h.setRecordingActive(true);
-                        break;
-                    }
-                }
-                if (wasActive != h.isActive()) {
-                    updateAudioMode = true;
-                    if (h.isActive() && h == getAudioModeOwnerHandler()) {
-                        existingMsgPolicy = SENDMSG_REPLACE;
-                        delay = 0;
-                    }
-                }
-            }
-            if (updateAudioMode) {
-                sendMsg(mAudioHandler,
-                        MSG_UPDATE_AUDIO_MODE,
-                        existingMsgPolicy,
-                        AudioSystem.MODE_CURRENT,
-                        android.os.Process.myPid(),
-                        mContext.getPackageName(),
-                        delay);
-            }
-        }
+        updateAudioModeHandlers(
+                null /* playbackConfigs */, configs /* recordConfigs */);
+        mDeviceBroker.updateCommunicationRouteClientsActivity(
+                null /* playbackConfigs */, configs /* recordConfigs */);
     }
 
     private void dumpAudioMode(PrintWriter pw) {
@@ -4762,16 +4754,10 @@
     // UI update and Broadcast Intent
     private void sendMasterMuteUpdate(boolean muted, int flags) {
         mVolumeController.postMasterMuteChanged(updateFlagsForTvPlatform(flags));
-        broadcastMasterMuteStatus(muted);
+        sendMsg(mAudioHandler, MSG_BROADCAST_MASTER_MUTE,
+                SENDMSG_QUEUE, muted ? 1 : 0, 0, null, 0);
     }
 
-    private void broadcastMasterMuteStatus(boolean muted) {
-        Intent intent = new Intent(AudioManager.MASTER_MUTE_CHANGED_ACTION);
-        intent.putExtra(AudioManager.EXTRA_MASTER_VOLUME_MUTED, muted);
-        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
-                | Intent.FLAG_RECEIVER_REPLACE_PENDING);
-        sendStickyBroadcastToAll(intent);
-    }
 
     /**
      * Sets the stream state's index, and posts a message to set system volume.
@@ -4938,18 +4924,21 @@
                 != PackageManager.PERMISSION_GRANTED) {
             return;
         }
-        setMasterMuteInternalNoCallerCheck(mute, flags, userId);
+        setMasterMuteInternalNoCallerCheck(mute, flags, userId, "setMasterMute");
     }
 
-    private void setMasterMuteInternalNoCallerCheck(boolean mute, int flags, int userId) {
+    private void setMasterMuteInternalNoCallerCheck(
+            boolean mute, int flags, int userId, String eventSource) {
         if (DEBUG_VOL) {
-            Log.d(TAG, String.format("Master mute %s, %d, user=%d", mute, flags, userId));
+            Log.d(TAG, TextUtils.formatSimple("Master mute %s, %d, user=%d from %s",
+                    mute, flags, userId, eventSource));
         }
+
         if (!isPlatformAutomotive() && mUseFixedVolume) {
             // If using fixed volume, we don't mute.
             // TODO: remove the isPlatformAutomotive check here.
             // The isPlatformAutomotive check is added for safety but may not be necessary.
-            return;
+            mute = false;
         }
         // For automotive,
         // - the car service is always running as system user
@@ -4958,8 +4947,10 @@
         // Therefore, the getCurrentUser() is always different to the foreground user.
         if ((isPlatformAutomotive() && userId == UserHandle.USER_SYSTEM)
                 || (getCurrentUserId() == userId)) {
-            if (mute != AudioSystem.getMasterMute()) {
-                AudioSystem.setMasterMute(mute);
+            if (mute != mMasterMute.getAndSet(mute)) {
+                sVolumeLogger.enqueue(new VolumeEvent(
+                        VolumeEvent.VOL_MASTER_MUTE, mute));
+                mAudioSystem.setMasterMute(mute);
                 sendMasterMuteUpdate(mute, flags);
             }
         }
@@ -4967,7 +4958,7 @@
 
     /** get global mute state. */
     public boolean isMasterMute() {
-        return AudioSystem.getMasterMute();
+        return mMasterMute.get();
     }
 
     @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
@@ -6299,10 +6290,12 @@
                             ? MediaMetrics.Value.CONNECTED : MediaMetrics.Value.DISCONNECTED)
                     .record();
         }
-
+        final boolean isPrivileged = mContext.checkCallingOrSelfPermission(
+                android.Manifest.permission.MODIFY_PHONE_STATE)
+                == PackageManager.PERMISSION_GRANTED;
         final long ident = Binder.clearCallingIdentity();
         try {
-            return mDeviceBroker.setCommunicationDevice(cb, pid, device, eventSource);
+            return mDeviceBroker.setCommunicationDevice(cb, uid, device, isPrivileged, eventSource);
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
@@ -6348,6 +6341,9 @@
         if (!checkAudioSettingsPermission("setSpeakerphoneOn()")) {
             return;
         }
+        final boolean isPrivileged = mContext.checkCallingOrSelfPermission(
+                android.Manifest.permission.MODIFY_PHONE_STATE)
+                == PackageManager.PERMISSION_GRANTED;
 
         // for logging only
         final int uid = Binder.getCallingUid();
@@ -6363,9 +6359,10 @@
                 .set(MediaMetrics.Property.STATE, on
                         ? MediaMetrics.Value.ON : MediaMetrics.Value.OFF)
                 .record();
+
         final long ident = Binder.clearCallingIdentity();
         try {
-            mDeviceBroker.setSpeakerphoneOn(cb, pid, on, eventSource);
+            mDeviceBroker.setSpeakerphoneOn(cb, uid, on, isPrivileged, eventSource);
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
@@ -6490,7 +6487,7 @@
                 .set(MediaMetrics.Property.SCO_AUDIO_MODE,
                         BtHelper.scoAudioModeToString(scoAudioMode))
                 .record();
-        startBluetoothScoInt(cb, pid, scoAudioMode, eventSource);
+        startBluetoothScoInt(cb, uid, scoAudioMode, eventSource);
 
     }
 
@@ -6513,10 +6510,10 @@
                 .set(MediaMetrics.Property.SCO_AUDIO_MODE,
                         BtHelper.scoAudioModeToString(BtHelper.SCO_MODE_VIRTUAL_CALL))
                 .record();
-        startBluetoothScoInt(cb, pid, BtHelper.SCO_MODE_VIRTUAL_CALL, eventSource);
+        startBluetoothScoInt(cb, uid, BtHelper.SCO_MODE_VIRTUAL_CALL, eventSource);
     }
 
-    void startBluetoothScoInt(IBinder cb, int pid, int scoAudioMode, @NonNull String eventSource) {
+    void startBluetoothScoInt(IBinder cb, int uid, int scoAudioMode, @NonNull String eventSource) {
         MediaMetrics.Item mmi = new MediaMetrics.Item(MediaMetrics.Name.AUDIO_BLUETOOTH)
                 .set(MediaMetrics.Property.EVENT, "startBluetoothScoInt")
                 .set(MediaMetrics.Property.SCO_AUDIO_MODE,
@@ -6527,9 +6524,13 @@
             mmi.set(MediaMetrics.Property.EARLY_RETURN, "permission or systemReady").record();
             return;
         }
+        final boolean isPrivileged = mContext.checkCallingOrSelfPermission(
+                android.Manifest.permission.MODIFY_PHONE_STATE)
+                == PackageManager.PERMISSION_GRANTED;
         final long ident = Binder.clearCallingIdentity();
         try {
-            mDeviceBroker.startBluetoothScoForClient(cb, pid, scoAudioMode, eventSource);
+            mDeviceBroker.startBluetoothScoForClient(
+                    cb, uid, scoAudioMode, isPrivileged, eventSource);
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
@@ -6547,9 +6548,12 @@
         final String eventSource =  new StringBuilder("stopBluetoothSco()")
                 .append(") from u/pid:").append(uid).append("/")
                 .append(pid).toString();
+        final boolean isPrivileged = mContext.checkCallingOrSelfPermission(
+                android.Manifest.permission.MODIFY_PHONE_STATE)
+                == PackageManager.PERMISSION_GRANTED;
         final long ident = Binder.clearCallingIdentity();
         try {
-            mDeviceBroker.stopBluetoothScoForClient(cb, pid, eventSource);
+            mDeviceBroker.stopBluetoothScoForClient(cb, uid, isPrivileged, eventSource);
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
@@ -7550,15 +7554,13 @@
     public @interface BtProfile {}
 
 
+    @android.annotation.EnforcePermission(android.Manifest.permission.BLUETOOTH_STACK)
     /**
      * See AudioManager.handleBluetoothActiveDeviceChanged(...)
      */
     public void handleBluetoothActiveDeviceChanged(BluetoothDevice newDevice,
             BluetoothDevice previousDevice, @NonNull BluetoothProfileConnectionInfo info) {
-        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.BLUETOOTH_STACK)
-                != PackageManager.PERMISSION_GRANTED) {
-            throw new SecurityException("Bluetooth is the only caller allowed");
-        }
+        handleBluetoothActiveDeviceChanged_enforcePermission();
         if (info == null) {
             throw new IllegalArgumentException("Illegal null BluetoothProfileConnectionInfo for"
                     + " device " + previousDevice + " -> " + newDevice);
@@ -9229,8 +9231,8 @@
                     onAccessoryPlugMediaUnmute(msg.arg1);
                     break;
 
-                case MSG_UNMUTE_STREAM:
-                    onUnmuteStream(msg.arg1, msg.arg2);
+                case MSG_UNMUTE_STREAM_ON_SINGLE_VOL_DEVICE:
+                    onUnmuteStreamOnSingleVolDevice(msg.arg1, msg.arg2);
                     break;
 
                 case MSG_DYN_POLICY_MIX_STATE_UPDATE:
@@ -9272,6 +9274,10 @@
                     mSystemServer.sendMicrophoneMuteChangedIntent();
                     break;
 
+                case MSG_BROADCAST_MASTER_MUTE:
+                    mSystemServer.broadcastMasterMuteStatus(msg.arg1 == 1);
+                    break;
+
                 case MSG_CHECK_MODE_FOR_UID:
                     synchronized (mDeviceBroker.mSetModeLock) {
                         if (msg.obj == null) {
@@ -9284,8 +9290,8 @@
                             break;
                         }
                         boolean wasActive = h.isActive();
-                        h.setPlaybackActive(mPlaybackMonitor.isPlaybackActiveForUid(h.getUid()));
-                        h.setRecordingActive(mRecordMonitor.isRecordingActiveForUid(h.getUid()));
+                        h.setPlaybackActive(isPlaybackActiveForUid(h.getUid()));
+                        h.setRecordingActive(isRecordingActiveForUid(h.getUid()));
                         if (wasActive != h.isActive()) {
                             onUpdateAudioMode(AudioSystem.MODE_CURRENT, android.os.Process.myPid(),
                                     mContext.getPackageName(), false /*force*/);
@@ -9565,14 +9571,14 @@
                         0,
                         null, 0);
             } else if (action.equals(Intent.ACTION_USER_SWITCHED)) {
-                if (mUserSwitchedReceived) {
+                // the current audio focus owner is likely no longer valid
+                final boolean audioDiscarded = mMediaFocusControl.maybeDiscardAudioFocusOwner();
+                if (audioDiscarded && mUserSwitchedReceived) {
                     // attempt to stop music playback for background user except on first user
                     // switch (i.e. first boot)
                     mDeviceBroker.postBroadcastBecomingNoisy();
                 }
                 mUserSwitchedReceived = true;
-                // the current audio focus owner is no longer valid
-                mMediaFocusControl.discardAudioFocusOwner();
 
                 if (mSupportsMicPrivacyToggle) {
                     mMicMuteFromPrivacyToggle = mSensorPrivacyManagerInternal
@@ -9669,7 +9675,8 @@
                         newRestrictions.getBoolean(UserManager.DISALLOW_ADJUST_VOLUME)
                                 || newRestrictions.getBoolean(UserManager.DISALLOW_UNMUTE_DEVICE);
                 if (wasRestricted != isRestricted) {
-                    setMasterMuteInternalNoCallerCheck(isRestricted, /* flags =*/ 0, userId);
+                    setMasterMuteInternalNoCallerCheck(
+                            isRestricted, /* flags =*/ 0, userId, "onUserRestrictionsChanged");
                 }
             }
         }
@@ -10586,9 +10593,10 @@
         }
     }
 
+    @android.annotation.EnforcePermission(android.Manifest.permission.REMOTE_AUDIO_PLAYBACK)
     @Override
     public void setRingtonePlayer(IRingtonePlayer player) {
-        mContext.enforceCallingOrSelfPermission(REMOTE_AUDIO_PLAYBACK, null);
+        setRingtonePlayer_enforcePermission();
         mRingtonePlayer = player;
     }
 
@@ -10869,6 +10877,10 @@
 
     private void updateA11yVolumeAlias(boolean a11VolEnabled) {
         if (DEBUG_VOL) Log.d(TAG, "Accessibility volume enabled = " + a11VolEnabled);
+        if (mIsSingleVolume) {
+            if (DEBUG_VOL) Log.d(TAG, "Accessibility volume is not set on single volume device");
+            return;
+        }
         if (sIndependentA11yVolume != a11VolEnabled) {
             sIndependentA11yVolume = a11VolEnabled;
             // update the volume mapping scheme
@@ -11057,10 +11069,11 @@
             pw.print("  mHdmiCecVolumeControlEnabled="); pw.println(mHdmiCecVolumeControlEnabled);
         }
         pw.print("  mIsCallScreeningModeSupported="); pw.println(mIsCallScreeningModeSupported);
-        pw.print("  mic mute FromSwitch=" + mMicMuteFromSwitch
+        pw.println("  mic mute FromSwitch=" + mMicMuteFromSwitch
                         + " FromRestrictions=" + mMicMuteFromRestrictions
                         + " FromApi=" + mMicMuteFromApi
                         + " from system=" + mMicMuteFromSystemCached);
+        pw.print("  mMasterMute="); pw.println(mMasterMute.get());
         dumpAccessibilityServiceUids(pw);
         dumpAssistantServicesUids(pw);
 
@@ -12378,6 +12391,16 @@
         }
     }
 
+    /* package */
+    boolean isPlaybackActiveForUid(int uid) {
+        return mPlaybackMonitor.isPlaybackActiveForUid(uid);
+    }
+
+    /* package */
+    boolean isRecordingActiveForUid(int uid) {
+        return mRecordMonitor.isRecordingActiveForUid(uid);
+    }
+
     //======================
     // Audio device management
     //======================
diff --git a/services/core/java/com/android/server/audio/AudioServiceEvents.java b/services/core/java/com/android/server/audio/AudioServiceEvents.java
index 6ebb42e..aac868f 100644
--- a/services/core/java/com/android/server/audio/AudioServiceEvents.java
+++ b/services/core/java/com/android/server/audio/AudioServiceEvents.java
@@ -228,6 +228,7 @@
         static final int VOL_MUTE_STREAM_INT = 9;
         static final int VOL_SET_LE_AUDIO_VOL = 10;
         static final int VOL_ADJUST_GROUP_VOL = 11;
+        static final int VOL_MASTER_MUTE = 12;
 
         final int mOp;
         final int mStream;
@@ -321,6 +322,17 @@
             logMetricEvent();
         }
 
+        /** used for VOL_MASTER_MUTE */
+        VolumeEvent(int op, boolean state) {
+            mOp = op;
+            mStream = -1;
+            mVal1 = state ? 1 : 0;
+            mVal2 = 0;
+            mCaller = null;
+            mGroupName = null;
+            logMetricEvent();
+        }
+
 
         /**
          * Audio Analytics unique Id.
@@ -429,6 +441,9 @@
                 case VOL_MUTE_STREAM_INT:
                     // No value in logging metrics for this internal event
                     return;
+                case VOL_MASTER_MUTE:
+                    // No value in logging metrics for this internal event
+                    return;
                 default:
                     return;
             }
@@ -510,6 +525,10 @@
                             .append(AudioSystem.streamToString(mStream))
                             .append(mVal1 == 1 ? ", muted)" : ", unmuted)")
                             .toString();
+                case VOL_MASTER_MUTE:
+                    return new StringBuilder("Master mute:")
+                            .append(mVal1 == 1 ? " muted)" : " unmuted)")
+                            .toString();
                 default: return new StringBuilder("FIXME invalid op:").append(mOp).toString();
             }
         }
diff --git a/services/core/java/com/android/server/audio/AudioSystemAdapter.java b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
index 4343894..e70b649 100644
--- a/services/core/java/com/android/server/audio/AudioSystemAdapter.java
+++ b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
@@ -685,6 +685,15 @@
     }
 
     /**
+     * Sets master mute state in audio flinger
+     * @param mute the mute state to set
+     * @return operation status
+     */
+    public int setMasterMute(boolean mute) {
+        return AudioSystem.setMasterMute(mute);
+    }
+
+    /**
      * Part of AudioService dump
      * @param pw
      */
diff --git a/services/core/java/com/android/server/audio/FocusRequester.java b/services/core/java/com/android/server/audio/FocusRequester.java
index ab8b795..88a4b05 100644
--- a/services/core/java/com/android/server/audio/FocusRequester.java
+++ b/services/core/java/com/android/server/audio/FocusRequester.java
@@ -18,15 +18,19 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.content.pm.UserProperties;
 import android.media.AudioAttributes;
 import android.media.AudioFocusInfo;
 import android.media.AudioManager;
 import android.media.IAudioFocusDispatcher;
 import android.os.IBinder;
+import android.os.UserHandle;
 import android.util.Log;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.server.LocalServices;
 import com.android.server.audio.MediaFocusControl.AudioFocusDeathHandler;
+import com.android.server.pm.UserManagerInternal;
 
 import java.io.PrintWriter;
 
@@ -166,6 +170,12 @@
         return mCallingUid == uid;
     }
 
+    boolean isAlwaysVisibleUser() {
+        final UserManagerInternal umi = LocalServices.getService(UserManagerInternal.class);
+        final UserProperties properties = umi.getUserProperties(UserHandle.getUserId(mCallingUid));
+        return properties != null && properties.getAlwaysVisible();
+    }
+
     int getClientUid() {
         return mCallingUid;
     }
diff --git a/services/core/java/com/android/server/audio/MediaFocusControl.java b/services/core/java/com/android/server/audio/MediaFocusControl.java
index 27687b2..b218096 100644
--- a/services/core/java/com/android/server/audio/MediaFocusControl.java
+++ b/services/core/java/com/android/server/audio/MediaFocusControl.java
@@ -208,20 +208,29 @@
     }
 
     /**
-     * Discard the current audio focus owner.
+     * Discard the current audio focus owner (unless the user is considered {@link
+     * FocusRequester#isAlwaysVisibleUser() always visible)}.
      * Notify top of audio focus stack that it lost focus (regardless of possibility to reassign
      * focus), remove it from the stack, and clear the remote control display.
+     * @return whether the current audio focus owner was discarded (including if there was none);
+     *         returns false if it was purposefully kept
      */
-    protected void discardAudioFocusOwner() {
+    protected boolean maybeDiscardAudioFocusOwner() {
         synchronized(mAudioFocusLock) {
             if (!mFocusStack.empty()) {
-                // notify the current focus owner it lost focus after removing it from stack
-                final FocusRequester exFocusOwner = mFocusStack.pop();
-                exFocusOwner.handleFocusLoss(AudioManager.AUDIOFOCUS_LOSS, null,
-                        false /*forceDuck*/);
-                exFocusOwner.release();
+                final FocusRequester exFocusOwner = mFocusStack.peek();
+                if (!exFocusOwner.isAlwaysVisibleUser()) {
+                    mFocusStack.pop();
+                    exFocusOwner.handleFocusLoss(AudioManager.AUDIOFOCUS_LOSS, null,
+                            false /*forceDuck*/);
+                    exFocusOwner.release();
+                    return true;
+                } else {
+                    return false;
+                }
             }
         }
+        return true;
     }
 
     /**
diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
index 50ffbcb9..3f4f981 100644
--- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
+++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
@@ -957,6 +957,7 @@
             players = (HashMap<Integer, AudioPlaybackConfiguration>) mPlayers.clone();
         }
         mFadingManager.unfadeOutUid(uid, players);
+        mDuckingManager.unduckUid(uid, players);
     }
 
     //=================================================================
diff --git a/services/core/java/com/android/server/audio/RecordingActivityMonitor.java b/services/core/java/com/android/server/audio/RecordingActivityMonitor.java
index 652ea52..4332fdd 100644
--- a/services/core/java/com/android/server/audio/RecordingActivityMonitor.java
+++ b/services/core/java/com/android/server/audio/RecordingActivityMonitor.java
@@ -227,8 +227,8 @@
         synchronized (mRecordStates) {
             for (RecordingState state : mRecordStates) {
                 // Note: isActiveConfiguration() == true => state.getConfig() != null
-                if (state.isActiveConfiguration()
-                        && state.getConfig().getClientUid() == uid) {
+                if (state.isActiveConfiguration() && state.getConfig().getClientUid() == uid
+                        && !state.getConfig().isClientSilenced()) {
                     return true;
                 }
             }
diff --git a/services/core/java/com/android/server/audio/SystemServerAdapter.java b/services/core/java/com/android/server/audio/SystemServerAdapter.java
index 22456bc..dfcd2e9 100644
--- a/services/core/java/com/android/server/audio/SystemServerAdapter.java
+++ b/services/core/java/com/android/server/audio/SystemServerAdapter.java
@@ -145,4 +145,18 @@
             ActivityManager.broadcastStickyIntent(intent, profileId);
         }
     }
+
+    /*package*/ void broadcastMasterMuteStatus(boolean muted) {
+        Intent intent = new Intent(AudioManager.MASTER_MUTE_CHANGED_ACTION);
+        intent.putExtra(AudioManager.EXTRA_MASTER_VOLUME_MUTED, muted);
+        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
+                | Intent.FLAG_RECEIVER_REPLACE_PENDING
+                | Intent.FLAG_RECEIVER_FOREGROUND);
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/backup/SystemBackupAgent.java b/services/core/java/com/android/server/backup/SystemBackupAgent.java
index 224e34d..b5d5cbe 100644
--- a/services/core/java/com/android/server/backup/SystemBackupAgent.java
+++ b/services/core/java/com/android/server/backup/SystemBackupAgent.java
@@ -25,6 +25,7 @@
 import android.app.backup.FullBackupDataOutput;
 import android.app.backup.WallpaperBackupHelper;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.os.Environment;
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
@@ -124,7 +125,9 @@
         addHelperIfEligibleForUser(USAGE_STATS_HELPER, new UsageStatsBackupHelper(mUserId));
         addHelperIfEligibleForUser(SHORTCUT_MANAGER_HELPER, new ShortcutBackupHelper(mUserId));
         addHelperIfEligibleForUser(ACCOUNT_MANAGER_HELPER, new AccountManagerBackupHelper(mUserId));
-        addHelperIfEligibleForUser(SLICES_HELPER, new SliceBackupHelper(this));
+        if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_SLICES_DISABLED)) {
+            addHelperIfEligibleForUser(SLICES_HELPER, new SliceBackupHelper(this));
+        }
         addHelperIfEligibleForUser(PEOPLE_HELPER, new PeopleBackupHelper(mUserId));
         addHelperIfEligibleForUser(APP_LOCALES_HELPER, new AppSpecificLocalesBackupHelper(mUserId));
         addHelperIfEligibleForUser(APP_GENDER_HELPER,
diff --git a/services/core/java/com/android/server/biometrics/AuthSession.java b/services/core/java/com/android/server/biometrics/AuthSession.java
index 8ef2a1b..cb5e7f1 100644
--- a/services/core/java/com/android/server/biometrics/AuthSession.java
+++ b/services/core/java/com/android/server/biometrics/AuthSession.java
@@ -21,7 +21,10 @@
 import static android.hardware.biometrics.BiometricAuthenticator.TYPE_NONE;
 import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_VENDOR;
 import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_VENDOR_BASE;
+import static android.hardware.biometrics.BiometricManager.Authenticators.BIOMETRIC_CONVENIENCE;
 
+import static com.android.server.biometrics.BiometricSensor.STATE_CANCELING;
+import static com.android.server.biometrics.BiometricSensor.STATE_UNKNOWN;
 import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTHENTICATED_PENDING_SYSUI;
 import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTH_CALLED;
 import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTH_IDLE;
@@ -439,6 +442,13 @@
             return false;
         }
 
+        final boolean errorLockout = error == BiometricConstants.BIOMETRIC_ERROR_LOCKOUT
+                || error == BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT;
+        if (errorLockout) {
+            cancelAllSensors(sensor -> Utils.isAtLeastStrength(sensorIdToStrength(sensorId),
+                    sensor.getCurrentStrength()));
+        }
+
         mErrorEscrow = error;
         mVendorCodeEscrow = vendorCode;
 
@@ -477,8 +487,6 @@
 
             case STATE_AUTH_STARTED:
             case STATE_AUTH_STARTED_UI_SHOWING: {
-                final boolean errorLockout = error == BiometricConstants.BIOMETRIC_ERROR_LOCKOUT
-                        || error == BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT;
                 if (isAllowDeviceCredential() && errorLockout) {
                     // SystemUI handles transition from biometric to device credential.
                     mState = STATE_SHOWING_DEVICE_CREDENTIAL;
@@ -675,7 +683,9 @@
     }
 
     private boolean pauseSensorIfSupported(int sensorId) {
-        if (sensorIdToModality(sensorId) == TYPE_FACE) {
+        boolean isSensorCancelling = sensorIdToState(sensorId) == STATE_CANCELING;
+        // If the sensor is locked out, canceling sensors operation is handled in onErrorReceived()
+        if (sensorIdToModality(sensorId) == TYPE_FACE && !isSensorCancelling) {
             cancelAllSensors(sensor -> sensor.id == sensorId);
             return true;
         }
@@ -948,6 +958,27 @@
         return TYPE_NONE;
     }
 
+    private @BiometricSensor.SensorState int sensorIdToState(int sensorId) {
+        for (BiometricSensor sensor : mPreAuthInfo.eligibleSensors) {
+            if (sensorId == sensor.id) {
+                return sensor.getSensorState();
+            }
+        }
+        Slog.e(TAG, "Unknown sensor: " + sensorId);
+        return STATE_UNKNOWN;
+    }
+
+    @BiometricManager.Authenticators.Types
+    private int sensorIdToStrength(int sensorId) {
+        for (BiometricSensor sensor : mPreAuthInfo.eligibleSensors) {
+            if (sensorId == sensor.id) {
+                return sensor.getCurrentStrength();
+            }
+        }
+        Slog.e(TAG, "Unknown sensor: " + sensorId);
+        return BIOMETRIC_CONVENIENCE;
+    }
+
     private String getAcquiredMessageForSensor(int sensorId, int acquiredInfo, int vendorCode) {
         final @Modality int modality = sensorIdToModality(sensorId);
         switch (modality) {
diff --git a/services/core/java/com/android/server/biometrics/BiometricCameraManager.java b/services/core/java/com/android/server/biometrics/BiometricCameraManager.java
new file mode 100644
index 0000000..058ea6b
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/BiometricCameraManager.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics;
+
+/**
+ * Interface for biometrics to get camera status.
+ */
+public interface BiometricCameraManager {
+    /**
+     * Returns true if any camera is in use.
+     */
+    boolean isAnyCameraUnavailable();
+
+    /**
+     * Returns true if privacy is enabled and camera access is disabled.
+     */
+    boolean isCameraPrivacyEnabled();
+}
diff --git a/services/core/java/com/android/server/biometrics/BiometricCameraManagerImpl.java b/services/core/java/com/android/server/biometrics/BiometricCameraManagerImpl.java
new file mode 100644
index 0000000..000ee54
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/BiometricCameraManagerImpl.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.biometrics;
+
+import static android.hardware.SensorPrivacyManager.Sensors.CAMERA;
+
+import android.annotation.NonNull;
+import android.hardware.SensorPrivacyManager;
+import android.hardware.camera2.CameraManager;
+
+import java.util.concurrent.ConcurrentHashMap;
+
+public class BiometricCameraManagerImpl implements BiometricCameraManager {
+
+    private final CameraManager mCameraManager;
+    private final SensorPrivacyManager mSensorPrivacyManager;
+    private final ConcurrentHashMap<String, Boolean> mIsCameraAvailable = new ConcurrentHashMap<>();
+
+    private final CameraManager.AvailabilityCallback mCameraAvailabilityCallback =
+            new CameraManager.AvailabilityCallback() {
+                @Override
+                public void onCameraAvailable(@NonNull String cameraId) {
+                    mIsCameraAvailable.put(cameraId, true);
+                }
+
+                @Override
+                public void onCameraUnavailable(@NonNull String cameraId) {
+                    mIsCameraAvailable.put(cameraId, false);
+                }
+            };
+
+    public BiometricCameraManagerImpl(@NonNull CameraManager cameraManager,
+            @NonNull SensorPrivacyManager sensorPrivacyManager) {
+        mCameraManager = cameraManager;
+        mSensorPrivacyManager = sensorPrivacyManager;
+        mCameraManager.registerAvailabilityCallback(mCameraAvailabilityCallback, null);
+    }
+
+    @Override
+    public boolean isAnyCameraUnavailable() {
+        for (String cameraId : mIsCameraAvailable.keySet()) {
+            if (!mIsCameraAvailable.get(cameraId)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public boolean isCameraPrivacyEnabled() {
+        return mSensorPrivacyManager != null && mSensorPrivacyManager
+                .isSensorPrivacyEnabled(SensorPrivacyManager.TOGGLE_TYPE_SOFTWARE, CAMERA);
+    }
+}
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index 0942d85..279aaf9 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -17,6 +17,8 @@
 package com.android.server.biometrics;
 
 import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
 import static android.hardware.biometrics.BiometricManager.Authenticators;
 
 import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTH_IDLE;
@@ -33,6 +35,7 @@
 import android.content.pm.PackageManager;
 import android.content.pm.UserInfo;
 import android.database.ContentObserver;
+import android.hardware.SensorPrivacyManager;
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricConstants;
 import android.hardware.biometrics.BiometricPrompt;
@@ -47,6 +50,7 @@
 import android.hardware.biometrics.ITestSessionCallback;
 import android.hardware.biometrics.PromptInfo;
 import android.hardware.biometrics.SensorPropertiesInternal;
+import android.hardware.camera2.CameraManager;
 import android.hardware.fingerprint.FingerprintManager;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.net.Uri;
@@ -124,6 +128,8 @@
     AuthSession mAuthSession;
     private final Handler mHandler = new Handler(Looper.getMainLooper());
 
+    private final BiometricCameraManager mBiometricCameraManager;
+
     /**
      * Tracks authenticatorId invalidation. For more details, see
      * {@link com.android.server.biometrics.sensors.InvalidationRequesterClient}.
@@ -365,7 +371,7 @@
         public boolean getConfirmationAlwaysRequired(@BiometricAuthenticator.Modality int modality,
                 int userId) {
             switch (modality) {
-                case BiometricAuthenticator.TYPE_FACE:
+                case TYPE_FACE:
                     if (!mFaceAlwaysRequireConfirmation.containsKey(userId)) {
                         onChange(true /* selfChange */,
                                 FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION,
@@ -563,22 +569,6 @@
 
             Utils.combineAuthenticatorBundles(promptInfo);
 
-            // Set the default title if necessary.
-            if (promptInfo.isUseDefaultTitle()) {
-                if (TextUtils.isEmpty(promptInfo.getTitle())) {
-                    promptInfo.setTitle(getContext()
-                            .getString(R.string.biometric_dialog_default_title));
-                }
-            }
-
-            // Set the default subtitle if necessary.
-            if (promptInfo.isUseDefaultSubtitle()) {
-                if (TextUtils.isEmpty(promptInfo.getSubtitle())) {
-                    promptInfo.setSubtitle(getContext()
-                            .getString(R.string.biometric_dialog_default_subtitle));
-                }
-            }
-
             final long requestId = mRequestCounter.get();
             mHandler.post(() -> handleAuthenticate(
                     token, requestId, operationId, userId, receiver, opPackageName, promptInfo));
@@ -933,7 +923,7 @@
 
         return PreAuthInfo.create(mTrustManager, mDevicePolicyManager, mSettingObserver, mSensors,
                 userId, promptInfo, opPackageName, false /* checkDevicePolicyManager */,
-                getContext());
+                getContext(), mBiometricCameraManager);
     }
 
     /**
@@ -1026,6 +1016,11 @@
         public UserManager getUserManager(Context context) {
             return context.getSystemService(UserManager.class);
         }
+
+        public BiometricCameraManager getBiometricCameraManager(Context context) {
+            return new BiometricCameraManagerImpl(context.getSystemService(CameraManager.class),
+                    context.getSystemService(SensorPrivacyManager.class));
+        }
     }
 
     /**
@@ -1054,6 +1049,7 @@
         mRequestCounter = mInjector.getRequestGenerator();
         mBiometricContext = injector.getBiometricContext(context);
         mUserManager = injector.getUserManager(context);
+        mBiometricCameraManager = injector.getBiometricCameraManager(context);
 
         try {
             injector.getActivityManagerService().registerUserSwitchObserver(
@@ -1290,7 +1286,34 @@
                 final PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager,
                         mDevicePolicyManager, mSettingObserver, mSensors, userId, promptInfo,
                         opPackageName, promptInfo.isDisallowBiometricsIfPolicyExists(),
-                        getContext());
+                        getContext(), mBiometricCameraManager);
+
+                // Set the default title if necessary.
+                if (promptInfo.isUseDefaultTitle()) {
+                    if (TextUtils.isEmpty(promptInfo.getTitle())) {
+                        promptInfo.setTitle(getContext()
+                                .getString(R.string.biometric_dialog_default_title));
+                    }
+                }
+
+                final int eligible = preAuthInfo.getEligibleModalities();
+                final boolean hasEligibleFingerprintSensor =
+                        (eligible & TYPE_FINGERPRINT) == TYPE_FINGERPRINT;
+                final boolean hasEligibleFaceSensor = (eligible & TYPE_FACE) == TYPE_FACE;
+
+                // Set the subtitle according to the modality.
+                if (promptInfo.isUseDefaultSubtitle()) {
+                    if (hasEligibleFingerprintSensor && hasEligibleFaceSensor) {
+                        promptInfo.setSubtitle(getContext()
+                                .getString(R.string.biometric_dialog_default_subtitle));
+                    } else if (hasEligibleFingerprintSensor) {
+                        promptInfo.setSubtitle(getContext()
+                                .getString(R.string.biometric_dialog_fingerprint_subtitle));
+                    } else if (hasEligibleFaceSensor) {
+                        promptInfo.setSubtitle(getContext()
+                                .getString(R.string.biometric_dialog_face_subtitle));
+                    }
+                }
 
                 final Pair<Integer, Integer> preAuthStatus = preAuthInfo.getPreAuthenticateStatus();
 
@@ -1300,9 +1323,7 @@
                         + promptInfo.isIgnoreEnrollmentState());
                 // BIOMETRIC_ERROR_SENSOR_PRIVACY_ENABLED is added so that BiometricPrompt can
                 // be shown for this case.
-                if (preAuthStatus.second == BiometricConstants.BIOMETRIC_SUCCESS
-                        || preAuthStatus.second
-                        == BiometricConstants.BIOMETRIC_ERROR_SENSOR_PRIVACY_ENABLED) {
+                if (preAuthStatus.second == BiometricConstants.BIOMETRIC_SUCCESS) {
                     // If BIOMETRIC_WEAK or BIOMETRIC_STRONG are allowed, but not enrolled, but
                     // CREDENTIAL is requested and available, set the bundle to only request
                     // CREDENTIAL.
diff --git a/services/core/java/com/android/server/biometrics/PreAuthInfo.java b/services/core/java/com/android/server/biometrics/PreAuthInfo.java
index 3813fd1..b1740a7 100644
--- a/services/core/java/com/android/server/biometrics/PreAuthInfo.java
+++ b/services/core/java/com/android/server/biometrics/PreAuthInfo.java
@@ -27,7 +27,6 @@
 import android.app.admin.DevicePolicyManager;
 import android.app.trust.ITrustManager;
 import android.content.Context;
-import android.hardware.SensorPrivacyManager;
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricManager;
 import android.hardware.biometrics.PromptInfo;
@@ -73,13 +72,16 @@
     final Context context;
     private final boolean mBiometricRequested;
     private final int mBiometricStrengthRequested;
+    private final BiometricCameraManager mBiometricCameraManager;
+
     private PreAuthInfo(boolean biometricRequested, int biometricStrengthRequested,
             boolean credentialRequested, List<BiometricSensor> eligibleSensors,
             List<Pair<BiometricSensor, Integer>> ineligibleSensors, boolean credentialAvailable,
             boolean confirmationRequested, boolean ignoreEnrollmentState, int userId,
-            Context context) {
+            Context context, BiometricCameraManager biometricCameraManager) {
         mBiometricRequested = biometricRequested;
         mBiometricStrengthRequested = biometricStrengthRequested;
+        mBiometricCameraManager = biometricCameraManager;
         this.credentialRequested = credentialRequested;
 
         this.eligibleSensors = eligibleSensors;
@@ -96,7 +98,8 @@
             BiometricService.SettingObserver settingObserver,
             List<BiometricSensor> sensors,
             int userId, PromptInfo promptInfo, String opPackageName,
-            boolean checkDevicePolicyManager, Context context)
+            boolean checkDevicePolicyManager, Context context,
+            BiometricCameraManager biometricCameraManager)
             throws RemoteException {
 
         final boolean confirmationRequested = promptInfo.isConfirmationRequested();
@@ -124,7 +127,7 @@
                         checkDevicePolicyManager, requestedStrength,
                         promptInfo.getAllowedSensorIds(),
                         promptInfo.isIgnoreEnrollmentState(),
-                        context);
+                        biometricCameraManager);
 
                 Slog.d(TAG, "Package: " + opPackageName
                         + " Sensor ID: " + sensor.id
@@ -138,7 +141,7 @@
                 //
                 // Note: if only a certain sensor is required and the privacy is enabled,
                 // canAuthenticate() will return false.
-                if (status == AUTHENTICATOR_OK || status == BIOMETRIC_SENSOR_PRIVACY_ENABLED) {
+                if (status == AUTHENTICATOR_OK) {
                     eligibleSensors.add(sensor);
                 } else {
                     ineligibleSensors.add(new Pair<>(sensor, status));
@@ -148,7 +151,7 @@
 
         return new PreAuthInfo(biometricRequested, requestedStrength, credentialRequested,
                 eligibleSensors, ineligibleSensors, credentialAvailable, confirmationRequested,
-                promptInfo.isIgnoreEnrollmentState(), userId, context);
+                promptInfo.isIgnoreEnrollmentState(), userId, context, biometricCameraManager);
     }
 
     /**
@@ -165,12 +168,16 @@
             BiometricSensor sensor, int userId, String opPackageName,
             boolean checkDevicePolicyManager, int requestedStrength,
             @NonNull List<Integer> requestedSensorIds,
-            boolean ignoreEnrollmentState, Context context) {
+            boolean ignoreEnrollmentState, BiometricCameraManager biometricCameraManager) {
 
         if (!requestedSensorIds.isEmpty() && !requestedSensorIds.contains(sensor.id)) {
             return BIOMETRIC_NO_HARDWARE;
         }
 
+        if (sensor.modality == TYPE_FACE && biometricCameraManager.isAnyCameraUnavailable()) {
+            return BIOMETRIC_HARDWARE_NOT_DETECTED;
+        }
+
         final boolean wasStrongEnough =
                 Utils.isAtLeastStrength(sensor.oemStrength, requestedStrength);
         final boolean isStrongEnough =
@@ -191,12 +198,10 @@
                     && !ignoreEnrollmentState) {
                 return BIOMETRIC_NOT_ENROLLED;
             }
-            final SensorPrivacyManager sensorPrivacyManager = context
-                    .getSystemService(SensorPrivacyManager.class);
 
-            if (sensorPrivacyManager != null && sensor.modality == TYPE_FACE) {
-                if (sensorPrivacyManager
-                        .isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA, userId)) {
+            if (biometricCameraManager != null && sensor.modality == TYPE_FACE) {
+                if (biometricCameraManager.isCameraPrivacyEnabled()) {
+                    //Camera privacy is enabled as the access is disabled
                     return BIOMETRIC_SENSOR_PRIVACY_ENABLED;
                 }
             }
@@ -266,17 +271,30 @@
     }
 
     private Pair<BiometricSensor, Integer> calculateErrorByPriority() {
-        // If the caller requested STRONG, and the device contains both STRONG and non-STRONG
-        // sensors, prioritize BIOMETRIC_NOT_ENROLLED over the weak sensor's
-        // BIOMETRIC_INSUFFICIENT_STRENGTH error. Pretty sure we can always prioritize
-        // BIOMETRIC_NOT_ENROLLED over any other error (unless of course its calculation is
-        // wrong, in which case we should fix that instead).
+        Pair<BiometricSensor, Integer> sensorNotEnrolled = null;
+        Pair<BiometricSensor, Integer> sensorLockout = null;
         for (Pair<BiometricSensor, Integer> pair : ineligibleSensors) {
+            int status = pair.second;
+            if (status == BIOMETRIC_LOCKOUT_TIMED || status == BIOMETRIC_LOCKOUT_PERMANENT) {
+                sensorLockout = pair;
+            }
             if (pair.second == BIOMETRIC_NOT_ENROLLED) {
-                return pair;
+                sensorNotEnrolled = pair;
             }
         }
 
+        // If there is a sensor locked out, prioritize lockout over other sensor's error.
+        // See b/286923477.
+        if (sensorLockout != null) {
+            return sensorLockout;
+        }
+
+        // If the caller requested STRONG, and the device contains both STRONG and non-STRONG
+        // sensors, prioritize BIOMETRIC_NOT_ENROLLED over the weak sensor's
+        // BIOMETRIC_INSUFFICIENT_STRENGTH error.
+        if (sensorNotEnrolled != null) {
+            return sensorNotEnrolled;
+        }
         return ineligibleSensors.get(0);
     }
 
@@ -292,13 +310,9 @@
         @AuthenticatorStatus final int status;
         @BiometricAuthenticator.Modality int modality = TYPE_NONE;
 
-        final SensorPrivacyManager sensorPrivacyManager = context
-                .getSystemService(SensorPrivacyManager.class);
-
         boolean cameraPrivacyEnabled = false;
-        if (sensorPrivacyManager != null) {
-            cameraPrivacyEnabled = sensorPrivacyManager
-                    .isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA, userId);
+        if (mBiometricCameraManager != null) {
+            cameraPrivacyEnabled = mBiometricCameraManager.isCameraPrivacyEnabled();
         }
 
         if (mBiometricRequested && credentialRequested) {
@@ -315,7 +329,7 @@
                     // and the face sensor privacy is enabled then return
                     // BIOMETRIC_SENSOR_PRIVACY_ENABLED.
                     //
-                    // Note: This sensor will still be eligible for calls to authenticate.
+                    // Note: This sensor will not be eligible for calls to authenticate.
                     status = BIOMETRIC_SENSOR_PRIVACY_ENABLED;
                 } else {
                     status = AUTHENTICATOR_OK;
@@ -340,7 +354,7 @@
                     // If the only modality requested is face and the privacy is enabled
                     // then return BIOMETRIC_SENSOR_PRIVACY_ENABLED.
                     //
-                    // Note: This sensor will still be eligible for calls to authenticate.
+                    // Note: This sensor will not be eligible for calls to authenticate.
                     status = BIOMETRIC_SENSOR_PRIVACY_ENABLED;
                 } else {
                     status = AUTHENTICATOR_OK;
diff --git a/services/core/java/com/android/server/biometrics/Utils.java b/services/core/java/com/android/server/biometrics/Utils.java
index 06417d7..f51b62d 100644
--- a/services/core/java/com/android/server/biometrics/Utils.java
+++ b/services/core/java/com/android/server/biometrics/Utils.java
@@ -66,7 +66,6 @@
 import com.android.internal.widget.LockPatternUtils;
 import com.android.server.biometrics.sensors.BaseClientMonitor;
 
-import java.util.ArrayList;
 import java.util.List;
 
 public class Utils {
@@ -98,33 +97,6 @@
     }
 
     /**
-     * Get the enabled HAL instances. If virtual is enabled and available it will be returned as
-     * the only instance, otherwise all other instances will be returned.
-     *
-     * @param context system context
-     * @param declaredInstances known instances
-     * @return filtered list of enabled instances
-     */
-    @NonNull
-    public static List<String> filterAvailableHalInstances(@NonNull Context context,
-            @NonNull List<String> declaredInstances) {
-        if (declaredInstances.size() <= 1) {
-            return declaredInstances;
-        }
-
-        final int virtualAt = declaredInstances.indexOf("virtual");
-        if (isVirtualEnabled(context) && virtualAt != -1) {
-            return List.of(declaredInstances.get(virtualAt));
-        }
-
-        declaredInstances = new ArrayList<>(declaredInstances);
-        if (virtualAt != -1) {
-            declaredInstances.remove(virtualAt);
-        }
-        return declaredInstances;
-    }
-
-    /**
      * Combines {@link PromptInfo#setDeviceCredentialAllowed(boolean)} with
      * {@link PromptInfo#setAuthenticators(int)}, as the former is not flexible enough.
      */
diff --git a/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java b/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java
index 46c77e8..aa6a0f1 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java
@@ -210,4 +210,8 @@
     public boolean isInterruptable() {
         return true;
     }
+
+    public boolean isAlreadyCancelled() {
+        return mAlreadyCancelled;
+    }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
index 05ca6e4..6ac1631 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
@@ -266,8 +266,12 @@
             }
         } else {
             if (isBackgroundAuth) {
-                Slog.e(TAG, "cancelling due to background auth");
-                cancel();
+                Slog.e(TAG, "Sending cancel to client(Due to background auth)");
+                if (mTaskStackListener != null) {
+                    mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener);
+                }
+                sendCancelOnly(getListener());
+                mCallback.onClientFinished(this, false);
             } else {
                 // Allow system-defined limit of number of attempts before giving up
                 if (mShouldUseLockoutTracker) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
index 7fa4d6c..78c3808 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
@@ -268,6 +268,14 @@
             return;
         }
 
+        if (mCurrentOperation.isAcquisitionOperation()) {
+            AcquisitionClient client = (AcquisitionClient) mCurrentOperation.getClientMonitor();
+            if (client.isAlreadyCancelled()) {
+                mCurrentOperation.cancel(mHandler, mInternalCallback);
+                return;
+            }
+        }
+
         if (mGestureAvailabilityDispatcher != null && mCurrentOperation.isAcquisitionOperation()) {
             mGestureAvailabilityDispatcher.markSensorActive(
                     mCurrentOperation.getSensorId(), true /* active */);
diff --git a/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallbackConverter.java b/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallbackConverter.java
index 2e1a363..1a682a9 100644
--- a/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallbackConverter.java
+++ b/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallbackConverter.java
@@ -170,6 +170,12 @@
         }
     }
 
+    public void onUdfpsOverlayShown() throws RemoteException {
+        if (mFingerprintServiceReceiver != null) {
+            mFingerprintServiceReceiver.onUdfpsOverlayShown();
+        }
+    }
+
     // Face-specific callbacks for FaceManager only
 
     /**
diff --git a/services/core/java/com/android/server/biometrics/sensors/SensorOverlays.java b/services/core/java/com/android/server/biometrics/sensors/SensorOverlays.java
index 969a174..aeb6b6e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/SensorOverlays.java
+++ b/services/core/java/com/android/server/biometrics/sensors/SensorOverlays.java
@@ -20,7 +20,6 @@
 import android.annotation.Nullable;
 import android.hardware.biometrics.BiometricOverlayConstants;
 import android.hardware.fingerprint.ISidefpsController;
-import android.hardware.fingerprint.IUdfpsOverlay;
 import android.hardware.fingerprint.IUdfpsOverlayController;
 import android.hardware.fingerprint.IUdfpsOverlayControllerCallback;
 import android.os.RemoteException;
@@ -44,7 +43,6 @@
 
     @NonNull private final Optional<IUdfpsOverlayController> mUdfpsOverlayController;
     @NonNull private final Optional<ISidefpsController> mSidefpsController;
-    @NonNull private final Optional<IUdfpsOverlay> mUdfpsOverlay;
 
     /**
      * Create an overlay controller for each modality.
@@ -54,11 +52,9 @@
      */
     public SensorOverlays(
             @Nullable IUdfpsOverlayController udfpsOverlayController,
-            @Nullable ISidefpsController sidefpsController,
-            @Nullable IUdfpsOverlay udfpsOverlay) {
+            @Nullable ISidefpsController sidefpsController) {
         mUdfpsOverlayController = Optional.ofNullable(udfpsOverlayController);
         mSidefpsController = Optional.ofNullable(sidefpsController);
-        mUdfpsOverlay = Optional.ofNullable(udfpsOverlay);
     }
 
     /**
@@ -94,14 +90,6 @@
                 Slog.e(TAG, "Remote exception when showing the UDFPS overlay", e);
             }
         }
-
-        if (mUdfpsOverlay.isPresent()) {
-            try {
-                mUdfpsOverlay.get().show(client.getRequestId(), sensorId, reason);
-            } catch (RemoteException e) {
-                Slog.e(TAG, "Remote exception when showing the new UDFPS overlay", e);
-            }
-        }
     }
 
     /**
@@ -125,14 +113,6 @@
                 Slog.e(TAG, "Remote exception when hiding the UDFPS overlay", e);
             }
         }
-
-        if (mUdfpsOverlay.isPresent()) {
-            try {
-                mUdfpsOverlay.get().hide(sensorId);
-            } catch (RemoteException e) {
-                Slog.e(TAG, "Remote exception when hiding the new udfps overlay", e);
-            }
-        }
     }
 
     /**
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
index a501647..33ed63c 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
@@ -149,6 +149,17 @@
             @NonNull String halInstanceName,
             @NonNull LockoutResetDispatcher lockoutResetDispatcher,
             @NonNull BiometricContext biometricContext) {
+        this(context, biometricStateCallback, props, halInstanceName, lockoutResetDispatcher,
+                biometricContext, null /* daemon */);
+    }
+
+    @VisibleForTesting FaceProvider(@NonNull Context context,
+            @NonNull BiometricStateCallback biometricStateCallback,
+            @NonNull SensorProps[] props,
+            @NonNull String halInstanceName,
+            @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+            @NonNull BiometricContext biometricContext,
+            IFace daemon) {
         mContext = context;
         mBiometricStateCallback = biometricStateCallback;
         mHalInstanceName = halInstanceName;
@@ -160,6 +171,7 @@
         mTaskStackListener = new BiometricTaskStackListener();
         mBiometricContext = biometricContext;
         mAuthSessionCoordinator = mBiometricContext.getAuthSessionCoordinator();
+        mDaemon = daemon;
 
         for (SensorProps prop : props) {
             final int sensorId = prop.commonProps.sensorId;
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
index ece35c5..7cc6940 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
@@ -54,7 +54,6 @@
 import android.hardware.fingerprint.IFingerprintService;
 import android.hardware.fingerprint.IFingerprintServiceReceiver;
 import android.hardware.fingerprint.ISidefpsController;
-import android.hardware.fingerprint.IUdfpsOverlay;
 import android.hardware.fingerprint.IUdfpsOverlayController;
 import android.os.Binder;
 import android.os.Build;
@@ -872,19 +871,22 @@
             super.registerAuthenticators_enforcePermission();
 
             mRegistry.registerAll(() -> {
-                final List<ServiceProvider> providers = new ArrayList<>();
-                providers.addAll(getHidlProviders(hidlSensors));
                 List<String> aidlSensors = new ArrayList<>();
                 final String[] instances = mAidlInstanceNameSupplier.get();
                 if (instances != null) {
                     aidlSensors.addAll(Lists.newArrayList(instances));
                 }
-                providers.addAll(getAidlProviders(
-                        Utils.filterAvailableHalInstances(getContext(), aidlSensors)));
+
+                final Pair<List<FingerprintSensorPropertiesInternal>, List<String>>
+                        filteredInstances = filterAvailableHalInstances(hidlSensors, aidlSensors);
+
+                final List<ServiceProvider> providers = new ArrayList<>();
+                providers.addAll(getHidlProviders(filteredInstances.first));
+                providers.addAll(getAidlProviders(filteredInstances.second));
+
                 return providers;
             });
         }
-
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override
         public void addAuthenticatorsRegisteredCallback(
@@ -913,7 +915,6 @@
             }
             provider.onPointerDown(requestId, sensorId, pc);
         }
-
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override
 
@@ -929,17 +930,19 @@
 
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override
-        public void onUiReady(long requestId, int sensorId) {
-            super.onUiReady_enforcePermission();
+        public void onUdfpsUiEvent(@FingerprintManager.UdfpsUiEvent int event, long requestId,
+                int sensorId) {
+            super.onUdfpsUiEvent_enforcePermission();
 
             final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
-                Slog.w(TAG, "No matching provider for onUiReady, sensorId: " + sensorId);
+                Slog.w(TAG, "No matching provider for onUdfpsUiEvent, sensorId: " + sensorId);
                 return;
             }
-            provider.onUiReady(requestId, sensorId);
+            provider.onUdfpsUiEvent(event, requestId, sensorId);
         }
 
+
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override
         public void setUdfpsOverlayController(@NonNull IUdfpsOverlayController controller) {
@@ -962,16 +965,6 @@
 
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override
-        public void setUdfpsOverlay(@NonNull IUdfpsOverlay controller) {
-            super.setUdfpsOverlay_enforcePermission();
-
-            for (ServiceProvider provider : mRegistry.getProviders()) {
-                provider.setUdfpsOverlay(controller);
-            }
-        }
-
-        @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
-        @Override
         public void onPowerPressed() {
             super.onPowerPressed_enforcePermission();
 
@@ -1048,6 +1041,33 @@
         });
     }
 
+    private Pair<List<FingerprintSensorPropertiesInternal>, List<String>>
+                filterAvailableHalInstances(
+            @NonNull List<FingerprintSensorPropertiesInternal> hidlInstances,
+            @NonNull List<String> aidlInstances) {
+        if ((hidlInstances.size() + aidlInstances.size()) <= 1) {
+            return new Pair(hidlInstances, aidlInstances);
+        }
+
+        final int virtualAt = aidlInstances.indexOf("virtual");
+        if (Utils.isVirtualEnabled(getContext())) {
+            if (virtualAt != -1) {
+                //only virtual instance should be returned
+                return new Pair(new ArrayList<>(), List.of(aidlInstances.get(virtualAt)));
+            } else {
+                Slog.e(TAG, "Could not find virtual interface while it is enabled");
+                return new Pair(hidlInstances, aidlInstances);
+            }
+        } else {
+            //remove virtual instance
+            aidlInstances = new ArrayList<>(aidlInstances);
+            if (virtualAt != -1) {
+                aidlInstances.remove(virtualAt);
+            }
+            return new Pair(hidlInstances, aidlInstances);
+        }
+    }
+
     @NonNull
     private List<ServiceProvider> getHidlProviders(
             @NonNull List<FingerprintSensorPropertiesInternal> hidlSensors) {
@@ -1182,4 +1202,15 @@
             }
         }
     }
+
+    void simulateVhalFingerDown() {
+        if (Utils.isVirtualEnabled(getContext())) {
+            Slog.i(TAG, "Simulate virtual HAL finger down event");
+            final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
+            if (provider != null) {
+                provider.second.simulateVhalFingerDown(UserHandle.getCallingUserId(),
+                        provider.first);
+            }
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintShellCommand.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintShellCommand.java
index 636413f..dc6a63f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintShellCommand.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintShellCommand.java
@@ -45,6 +45,8 @@
                     return doHelp();
                 case "sync":
                     return doSync();
+                case "fingerdown":
+                    return doSimulateVhalFingerDown();
                 default:
                     getOutPrintWriter().println("Unrecognized command: " + cmd);
             }
@@ -62,6 +64,8 @@
         pw.println("      Print this help text.");
         pw.println("  sync");
         pw.println("      Sync enrollments now (virtualized sensors only).");
+        pw.println("  fingerdown");
+        pw.println("      Simulate finger down event (virtualized sensors only).");
     }
 
     private int doHelp() {
@@ -73,4 +77,9 @@
         mService.syncEnrollmentsNow();
         return 0;
     }
+
+    private int doSimulateVhalFingerDown() {
+        mService.simulateVhalFingerDown();
+        return 0;
+    }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
index 004af2c..a15d1a4 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
@@ -28,7 +28,6 @@
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.hardware.fingerprint.IFingerprintServiceReceiver;
 import android.hardware.fingerprint.ISidefpsController;
-import android.hardware.fingerprint.IUdfpsOverlay;
 import android.hardware.fingerprint.IUdfpsOverlayController;
 import android.os.IBinder;
 
@@ -130,16 +129,10 @@
 
     void onPointerUp(long requestId, int sensorId, PointerContext pc);
 
-    void onUiReady(long requestId, int sensorId);
+    void onUdfpsUiEvent(@FingerprintManager.UdfpsUiEvent int event, long requestId, int sensorId);
 
     void setUdfpsOverlayController(@NonNull IUdfpsOverlayController controller);
 
-    /**
-     * Sets udfps overlay
-     * @param controller udfps overlay
-     */
-    void setUdfpsOverlay(@NonNull IUdfpsOverlay controller);
-
     void onPowerPressed();
 
     /**
@@ -157,4 +150,11 @@
      * @param sensorId sensor ID of the associated operation
      */
     default void scheduleWatchdog(int sensorId) {}
+
+    /**
+     * Simulate fingerprint down touch event for virtual HAL
+     * @param userId user ID
+     * @param sensorId sensor ID
+     */
+    default void simulateVhalFingerDown(int userId, int sensorId) {};
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/Udfps.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/Udfps.java
index da7163a..dce0175 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/Udfps.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/Udfps.java
@@ -17,6 +17,7 @@
 package com.android.server.biometrics.sensors.fingerprint;
 
 import android.hardware.biometrics.fingerprint.PointerContext;
+import android.hardware.fingerprint.FingerprintManager;
 
 import com.android.server.biometrics.sensors.BaseClientMonitor;
 
@@ -28,6 +29,6 @@
 public interface Udfps {
     void onPointerDown(PointerContext pc);
     void onPointerUp(PointerContext pc);
-    void onUiReady();
+    void onUdfpsUiEvent(@FingerprintManager.UdfpsUiEvent int event);
     boolean isPointerDown();
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java
index 135eccf..ec1eeb1 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java
@@ -114,6 +114,11 @@
         public void onUdfpsPointerUp(int sensorId) {
 
         }
+
+        @Override
+        public void onUdfpsOverlayShown() {
+
+        }
     };
 
     BiometricTestSessionImpl(@NonNull Context context, int sensorId,
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
index 2bfc239..54d1faa 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
@@ -27,9 +27,9 @@
 import android.hardware.biometrics.common.ICancellationSignal;
 import android.hardware.biometrics.fingerprint.PointerContext;
 import android.hardware.fingerprint.FingerprintAuthenticateOptions;
+import android.hardware.fingerprint.FingerprintManager;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.hardware.fingerprint.ISidefpsController;
-import android.hardware.fingerprint.IUdfpsOverlay;
 import android.hardware.fingerprint.IUdfpsOverlayController;
 import android.os.Build;
 import android.os.Handler;
@@ -111,7 +111,6 @@
             @NonNull LockoutCache lockoutCache,
             @Nullable IUdfpsOverlayController udfpsOverlayController,
             @Nullable ISidefpsController sidefpsController,
-            @Nullable IUdfpsOverlay udfpsOverlay,
             boolean allowBackgroundAuthentication,
             @NonNull FingerprintSensorPropertiesInternal sensorProps,
             @NonNull Handler handler,
@@ -136,8 +135,7 @@
                 false /* shouldVibrate */,
                 biometricStrength);
         setRequestId(requestId);
-        mSensorOverlays = new SensorOverlays(udfpsOverlayController,
-                sidefpsController, udfpsOverlay);
+        mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController);
         mSensorProps = sensorProps;
         mALSProbeCallback = getLogger().getAmbientLightProbe(false /* startWithClient */);
         mHandler = handler;
@@ -363,9 +361,11 @@
     }
 
     @Override
-    public void onUiReady() {
+    public void onUdfpsUiEvent(@FingerprintManager.UdfpsUiEvent int event) {
         try {
-            getFreshDaemon().getSession().onUiReady();
+            if (event == FingerprintManager.UDFPS_UI_READY) {
+                getFreshDaemon().getSession().onUiReady();
+            }
         } catch (RemoteException e) {
             Slog.e(TAG, "Remote exception", e);
         }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
index 46f62d3..51a9385 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
@@ -22,7 +22,6 @@
 import android.hardware.biometrics.BiometricOverlayConstants;
 import android.hardware.biometrics.common.ICancellationSignal;
 import android.hardware.fingerprint.FingerprintAuthenticateOptions;
-import android.hardware.fingerprint.IUdfpsOverlay;
 import android.hardware.fingerprint.IUdfpsOverlayController;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -59,15 +58,13 @@
             @NonNull FingerprintAuthenticateOptions options,
             @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext,
             @Nullable IUdfpsOverlayController udfpsOverlayController,
-            @Nullable IUdfpsOverlay udfpsOverlay,
             boolean isStrongBiometric) {
         super(context, lazyDaemon, token, listener, options.getUserId(),
                 options.getOpPackageName(), 0 /* cookie */, options.getSensorId(),
                 true /* shouldVibrate */, biometricLogger, biometricContext);
         setRequestId(requestId);
         mIsStrongBiometric = isStrongBiometric;
-        mSensorOverlays = new SensorOverlays(udfpsOverlayController,
-                null /* sideFpsController*/, udfpsOverlay);
+        mSensorOverlays = new SensorOverlays(udfpsOverlayController, null /* sideFpsController*/);
         mOptions = options;
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
index c2ca78e..f9e08d6 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
@@ -29,7 +29,6 @@
 import android.hardware.fingerprint.FingerprintManager;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.hardware.fingerprint.ISidefpsController;
-import android.hardware.fingerprint.IUdfpsOverlay;
 import android.hardware.fingerprint.IUdfpsOverlayController;
 import android.hardware.keymaster.HardwareAuthToken;
 import android.os.IBinder;
@@ -87,7 +86,6 @@
             @NonNull FingerprintSensorPropertiesInternal sensorProps,
             @Nullable IUdfpsOverlayController udfpsOverlayController,
             @Nullable ISidefpsController sidefpsController,
-            @Nullable IUdfpsOverlay udfpsOverlay,
             int maxTemplatesPerUser, @FingerprintManager.EnrollReason int enrollReason) {
         // UDFPS haptics occur when an image is acquired (instead of when the result is known)
         super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils,
@@ -95,8 +93,7 @@
                 biometricContext);
         setRequestId(requestId);
         mSensorProps = sensorProps;
-        mSensorOverlays = new SensorOverlays(udfpsOverlayController,
-                sidefpsController, udfpsOverlay);
+        mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController);
         mMaxTemplatesPerUser = maxTemplatesPerUser;
 
         mALSProbeCallback = getLogger().getAmbientLightProbe(true /* startWithClient */);
@@ -265,11 +262,20 @@
     }
 
     @Override
-    public void onUiReady() {
+    public void onUdfpsUiEvent(@FingerprintManager.UdfpsUiEvent int event) {
         try {
-            getFreshDaemon().getSession().onUiReady();
+            switch (event) {
+                case FingerprintManager.UDFPS_UI_OVERLAY_SHOWN:
+                    getListener().onUdfpsOverlayShown();
+                    break;
+                case FingerprintManager.UDFPS_UI_READY:
+                    getFreshDaemon().getSession().onUiReady();
+                    break;
+                default:
+                    Slog.w(TAG, "No matching event for onUdfpsUiEvent");
+            }
         } catch (RemoteException e) {
-            Slog.e(TAG, "Unable to send UI ready", e);
+            Slog.e(TAG, "Unable to send onUdfpsUiEvent", e);
         }
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
index 58ece89..0421d78 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
@@ -43,7 +43,6 @@
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.hardware.fingerprint.IFingerprintServiceReceiver;
 import android.hardware.fingerprint.ISidefpsController;
-import android.hardware.fingerprint.IUdfpsOverlay;
 import android.hardware.fingerprint.IUdfpsOverlayController;
 import android.os.Binder;
 import android.os.Handler;
@@ -122,7 +121,6 @@
     @Nullable private IFingerprint mDaemon;
     @Nullable private IUdfpsOverlayController mUdfpsOverlayController;
     @Nullable private ISidefpsController mSidefpsController;
-    @Nullable private IUdfpsOverlay mUdfpsOverlay;
     private AuthSessionCoordinator mAuthSessionCoordinator;
 
     private final class BiometricTaskStackListener extends TaskStackListener {
@@ -160,6 +158,17 @@
             @NonNull LockoutResetDispatcher lockoutResetDispatcher,
             @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
             @NonNull BiometricContext biometricContext) {
+        this(context, biometricStateCallback, props, halInstanceName, lockoutResetDispatcher,
+                gestureAvailabilityDispatcher, biometricContext, null /* daemon */);
+    }
+
+    @VisibleForTesting FingerprintProvider(@NonNull Context context,
+            @NonNull BiometricStateCallback biometricStateCallback,
+            @NonNull SensorProps[] props, @NonNull String halInstanceName,
+            @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+            @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
+            @NonNull BiometricContext biometricContext,
+            IFingerprint daemon) {
         mContext = context;
         mBiometricStateCallback = biometricStateCallback;
         mHalInstanceName = halInstanceName;
@@ -170,6 +179,7 @@
         mTaskStackListener = new BiometricTaskStackListener();
         mBiometricContext = biometricContext;
         mAuthSessionCoordinator = mBiometricContext.getAuthSessionCoordinator();
+        mDaemon = daemon;
 
         final List<SensorLocationInternal> workaroundLocations = getWorkaroundSensorProps(context);
 
@@ -408,7 +418,7 @@
                             BiometricsProtoEnums.CLIENT_UNKNOWN),
                     mBiometricContext,
                     mFingerprintSensors.get(sensorId).getSensorProperties(),
-                    mUdfpsOverlayController, mSidefpsController, mUdfpsOverlay,
+                    mUdfpsOverlayController, mSidefpsController,
                     maxTemplatesPerUser, enrollReason);
             scheduleForSensor(sensorId, client, new ClientMonitorCompositeCallback(
                     mBiometricStateCallback, new ClientMonitorCallback() {
@@ -446,8 +456,7 @@
                     mFingerprintSensors.get(sensorId).getLazySession(), token, id, callback,
                     options,
                     createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
-                    mBiometricContext,
-                    mUdfpsOverlayController, mUdfpsOverlay, isStrongBiometric);
+                    mBiometricContext, mUdfpsOverlayController, isStrongBiometric);
             scheduleForSensor(sensorId, client, mBiometricStateCallback);
         });
 
@@ -471,7 +480,7 @@
                     createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
                     mBiometricContext, isStrongBiometric,
                     mTaskStackListener, mFingerprintSensors.get(sensorId).getLockoutCache(),
-                    mUdfpsOverlayController, mSidefpsController, mUdfpsOverlay,
+                    mUdfpsOverlayController, mSidefpsController,
                     allowBackgroundAuthentication,
                     mFingerprintSensors.get(sensorId).getSensorProperties(), mHandler,
                     Utils.getCurrentStrength(sensorId),
@@ -669,14 +678,15 @@
     }
 
     @Override
-    public void onUiReady(long requestId, int sensorId) {
+    public void onUdfpsUiEvent(@FingerprintManager.UdfpsUiEvent int event, long requestId,
+            int sensorId) {
         mFingerprintSensors.get(sensorId).getScheduler().getCurrentClientIfMatches(
                 requestId, (client) -> {
                     if (!(client instanceof Udfps)) {
-                        Slog.e(getTag(), "onUiReady received during client: " + client);
+                        Slog.e(getTag(), "onUdfpsUiEvent received during client: " + client);
                         return;
                     }
-                    ((Udfps) client).onUiReady();
+                    ((Udfps) client).onUdfpsUiEvent(event);
                 });
     }
 
@@ -706,11 +716,6 @@
     }
 
     @Override
-    public void setUdfpsOverlay(@NonNull IUdfpsOverlay controller) {
-        mUdfpsOverlay = controller;
-    }
-
-    @Override
     public void dumpProtoState(int sensorId, @NonNull ProtoOutputStream proto,
             boolean clearSchedulerBuffer) {
         if (mFingerprintSensors.contains(sensorId)) {
@@ -844,4 +849,18 @@
         }
         biometricScheduler.startWatchdog();
     }
+
+    @Override
+    public void simulateVhalFingerDown(int userId, int sensorId) {
+        Slog.d(getTag(), "Simulate virtual HAL finger down event");
+        final AidlSession session = mFingerprintSensors.get(sensorId).getSessionForUser(userId);
+        final PointerContext pc = new PointerContext();
+        try {
+            session.getSession().onPointerDownWithContext(pc);
+            session.getSession().onUiReady();
+            session.getSession().onPointerUpWithContext(pc);
+        } catch (RemoteException e) {
+            Slog.e(getTag(), "failed hal operation ", e);
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java
index 86a9f79..c20a9eb 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java
@@ -115,6 +115,11 @@
         public void onUdfpsPointerUp(int sensorId) {
 
         }
+
+        @Override
+        public void onUdfpsOverlayShown() {
+
+        }
     };
 
     BiometricTestSessionImpl(@NonNull Context context, int sensorId,
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
index 9e6f4e4..92b216d 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
@@ -40,7 +40,6 @@
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.hardware.fingerprint.IFingerprintServiceReceiver;
 import android.hardware.fingerprint.ISidefpsController;
-import android.hardware.fingerprint.IUdfpsOverlay;
 import android.hardware.fingerprint.IUdfpsOverlayController;
 import android.os.Handler;
 import android.os.IBinder;
@@ -123,7 +122,6 @@
     @NonNull private final HalResultController mHalResultController;
     @Nullable private IUdfpsOverlayController mUdfpsOverlayController;
     @Nullable private ISidefpsController mSidefpsController;
-    @Nullable private IUdfpsOverlay mUdfpsOverlay;
     @NonNull private final BiometricContext mBiometricContext;
     // for requests that do not use biometric prompt
     @NonNull private final AtomicLong mRequestCounter = new AtomicLong(0);
@@ -597,9 +595,7 @@
                     mSensorProperties.sensorId,
                     createLogger(BiometricsProtoEnums.ACTION_ENROLL,
                             BiometricsProtoEnums.CLIENT_UNKNOWN),
-                    mBiometricContext,
-                    mUdfpsOverlayController, mSidefpsController, mUdfpsOverlay,
-                    enrollReason);
+                    mBiometricContext, mUdfpsOverlayController, mSidefpsController, enrollReason);
             mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
                 @Override
                 public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
@@ -644,8 +640,7 @@
             final FingerprintDetectClient client = new FingerprintDetectClient(mContext,
                     mLazyDaemon, token, id, listener, options,
                     createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
-                    mBiometricContext, mUdfpsOverlayController, mUdfpsOverlay,
-                    isStrongBiometric);
+                    mBiometricContext, mUdfpsOverlayController, isStrongBiometric);
             mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
         });
 
@@ -668,7 +663,7 @@
                     createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
                     mBiometricContext, isStrongBiometric,
                     mTaskStackListener, mLockoutTracker,
-                    mUdfpsOverlayController, mSidefpsController, mUdfpsOverlay,
+                    mUdfpsOverlayController, mSidefpsController,
                     allowBackgroundAuthentication, mSensorProperties,
                     Utils.getCurrentStrength(mSensorId));
             mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
@@ -829,13 +824,14 @@
     }
 
     @Override
-    public void onUiReady(long requestId, int sensorId) {
+    public void onUdfpsUiEvent(@FingerprintManager.UdfpsUiEvent int event, long requestId,
+            int sensorId) {
         mScheduler.getCurrentClientIfMatches(requestId, (client) -> {
             if (!(client instanceof Udfps)) {
-                Slog.w(TAG, "onUiReady received during client: " + client);
+                Slog.w(TAG, "onUdfpsUiEvent received during client: " + client);
                 return;
             }
-            ((Udfps) client).onUiReady();
+            ((Udfps) client).onUdfpsUiEvent(event);
         });
     }
 
@@ -855,11 +851,6 @@
     }
 
     @Override
-    public void setUdfpsOverlay(@NonNull IUdfpsOverlay controller) {
-        mUdfpsOverlay = controller;
-    }
-
-    @Override
     public void dumpProtoState(int sensorId, @NonNull ProtoOutputStream proto,
             boolean clearSchedulerBuffer) {
         final long sensorToken = proto.start(SensorServiceStateProto.SENSOR_STATES);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
index d22aef8..9966e91 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
@@ -27,9 +27,9 @@
 import android.hardware.biometrics.fingerprint.PointerContext;
 import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
 import android.hardware.fingerprint.FingerprintAuthenticateOptions;
+import android.hardware.fingerprint.FingerprintManager;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.hardware.fingerprint.ISidefpsController;
-import android.hardware.fingerprint.IUdfpsOverlay;
 import android.hardware.fingerprint.IUdfpsOverlayController;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -82,7 +82,6 @@
             @NonNull LockoutFrameworkImpl lockoutTracker,
             @Nullable IUdfpsOverlayController udfpsOverlayController,
             @Nullable ISidefpsController sidefpsController,
-            @Nullable IUdfpsOverlay udfpsOverlay,
             boolean allowBackgroundAuthentication,
             @NonNull FingerprintSensorPropertiesInternal sensorProps,
             @Authenticators.Types int sensorStrength) {
@@ -92,8 +91,7 @@
                 false /* shouldVibrate */, sensorStrength);
         setRequestId(requestId);
         mLockoutFrameworkImpl = lockoutTracker;
-        mSensorOverlays = new SensorOverlays(udfpsOverlayController,
-                sidefpsController, udfpsOverlay);
+        mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController);
         mSensorProps = sensorProps;
         mALSProbeCallback = getLogger().getAmbientLightProbe(false /* startWithClient */);
     }
@@ -273,7 +271,7 @@
     }
 
     @Override
-    public void onUiReady() {
+    public void onUdfpsUiEvent(@FingerprintManager.UdfpsUiEvent int event) {
         // Unsupported in HIDL.
     }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
index 362c820..0d7f9f2 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
@@ -25,7 +25,7 @@
 import android.hardware.biometrics.fingerprint.PointerContext;
 import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
 import android.hardware.fingerprint.FingerprintAuthenticateOptions;
-import android.hardware.fingerprint.IUdfpsOverlay;
+import android.hardware.fingerprint.FingerprintManager;
 import android.hardware.fingerprint.IUdfpsOverlayController;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -66,13 +66,12 @@
             @NonNull FingerprintAuthenticateOptions options,
             @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext,
             @Nullable IUdfpsOverlayController udfpsOverlayController,
-            @Nullable IUdfpsOverlay udfpsOverlay, boolean isStrongBiometric) {
+            boolean isStrongBiometric) {
         super(context, lazyDaemon, token, listener, options.getUserId(),
                 options.getOpPackageName(), 0 /* cookie */, options.getSensorId(),
                 true /* shouldVibrate */, biometricLogger, biometricContext);
         setRequestId(requestId);
-        mSensorOverlays = new SensorOverlays(udfpsOverlayController,
-                null /* sideFpsController */, udfpsOverlay);
+        mSensorOverlays = new SensorOverlays(udfpsOverlayController, null /* sideFpsController */);
         mIsStrongBiometric = isStrongBiometric;
     }
 
@@ -130,7 +129,7 @@
     }
 
     @Override
-    public void onUiReady() {
+    public void onUdfpsUiEvent(@FingerprintManager.UdfpsUiEvent int event) {
         // Unsupported in HIDL.
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
index 78039ef..6fee84a 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
@@ -27,7 +27,6 @@
 import android.hardware.fingerprint.Fingerprint;
 import android.hardware.fingerprint.FingerprintManager;
 import android.hardware.fingerprint.ISidefpsController;
-import android.hardware.fingerprint.IUdfpsOverlay;
 import android.hardware.fingerprint.IUdfpsOverlayController;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -69,14 +68,12 @@
             @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext,
             @Nullable IUdfpsOverlayController udfpsOverlayController,
             @Nullable ISidefpsController sidefpsController,
-            @Nullable IUdfpsOverlay udfpsOverlay,
             @FingerprintManager.EnrollReason int enrollReason) {
         super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils,
                 timeoutSec, sensorId, true /* shouldVibrate */, biometricLogger,
                 biometricContext);
         setRequestId(requestId);
-        mSensorOverlays = new SensorOverlays(udfpsOverlayController,
-                sidefpsController, udfpsOverlay);
+        mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController);
 
         mEnrollReason = enrollReason;
         if (enrollReason == FingerprintManager.ENROLL_FIND_SENSOR) {
@@ -186,7 +183,7 @@
     }
 
     @Override
-    public void onUiReady() {
+    public void onUdfpsUiEvent(@FingerprintManager.UdfpsUiEvent int event) {
         // Unsupported in HIDL.
     }
 }
diff --git a/services/core/java/com/android/server/camera/CameraServiceProxy.java b/services/core/java/com/android/server/camera/CameraServiceProxy.java
index 0b04159..f8f0088 100644
--- a/services/core/java/com/android/server/camera/CameraServiceProxy.java
+++ b/services/core/java/com/android/server/camera/CameraServiceProxy.java
@@ -613,16 +613,26 @@
 
         @Override
         public boolean isCameraDisabled(int userId) {
-            DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
-            if (dpm == null) {
-                Slog.e(TAG, "Failed to get the device policy manager service");
+            if (Binder.getCallingUid() != Process.CAMERASERVER_UID) {
+                Slog.e(TAG, "Calling UID: " + Binder.getCallingUid()
+                        + " doesn't match expected camera service UID!");
                 return false;
             }
+            final long ident = Binder.clearCallingIdentity();
             try {
-                return dpm.getCameraDisabled(null, userId);
-            } catch (Exception e) {
-                e.printStackTrace();
-                return false;
+                DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
+                if (dpm == null) {
+                    Slog.e(TAG, "Failed to get the device policy manager service");
+                    return false;
+                }
+                try {
+                    return dpm.getCameraDisabled(null, userId);
+                } catch (Exception e) {
+                    e.printStackTrace();
+                    return false;
+                }
+            } finally {
+                Binder.restoreCallingIdentity(ident);
             }
         }
     };
diff --git a/services/core/java/com/android/server/clipboard/ClipboardService.java b/services/core/java/com/android/server/clipboard/ClipboardService.java
index 4b8b431..38c6a52 100644
--- a/services/core/java/com/android/server/clipboard/ClipboardService.java
+++ b/services/core/java/com/android/server/clipboard/ClipboardService.java
@@ -193,8 +193,11 @@
         if (IS_EMULATOR) {
             mEmulatorClipboardMonitor = new EmulatorClipboardMonitor((clip) -> {
                 synchronized (mLock) {
-                    setPrimaryClipInternalLocked(getClipboardLocked(0, DEVICE_ID_DEFAULT), clip,
-                            android.os.Process.SYSTEM_UID, null);
+                    Clipboard clipboard = getClipboardLocked(0, DEVICE_ID_DEFAULT);
+                    if (clipboard != null) {
+                        setPrimaryClipInternalLocked(clipboard, clip, android.os.Process.SYSTEM_UID,
+                                null);
+                    }
                 }
             });
         } else {
@@ -470,6 +473,7 @@
                     callingPackage);
         }
 
+        @android.annotation.EnforcePermission(android.Manifest.permission.SET_CLIP_SOURCE)
         @Override
         public void setPrimaryClipAsPackage(
                 ClipData clip,
@@ -478,8 +482,7 @@
                 @UserIdInt int userId,
                 int deviceId,
                 String sourcePackage) {
-            getContext().enforceCallingOrSelfPermission(Manifest.permission.SET_CLIP_SOURCE,
-                    "Requires SET_CLIP_SOURCE permission");
+            setPrimaryClipAsPackage_enforcePermission();
             checkAndSetPrimaryClip(clip, callingPackage, attributionTag, userId, deviceId,
                     sourcePackage);
         }
@@ -637,6 +640,9 @@
                 }
 
                 Clipboard clipboard = getClipboardLocked(intendingUserId, intendingDeviceId);
+                if (clipboard == null) {
+                    return null;
+                }
                 showAccessNotificationLocked(pkg, intendingUid, intendingUserId, clipboard);
                 notifyTextClassifierLocked(clipboard, pkg, intendingUid);
                 if (clipboard.primaryClip != null) {
@@ -665,7 +671,7 @@
             }
             synchronized (mLock) {
                 Clipboard clipboard = getClipboardLocked(intendingUserId, intendingDeviceId);
-                return clipboard.primaryClip != null
+                return (clipboard != null && clipboard.primaryClip != null)
                         ? clipboard.primaryClip.getDescription() : null;
             }
         }
@@ -688,7 +694,8 @@
                 return false;
             }
             synchronized (mLock) {
-                return getClipboardLocked(intendingUserId, intendingDeviceId).primaryClip != null;
+                Clipboard clipboard = getClipboardLocked(intendingUserId, intendingDeviceId);
+                return clipboard != null && clipboard.primaryClip != null;
             }
         }
 
@@ -709,8 +716,11 @@
                 return;
             }
             synchronized (mLock) {
-                getClipboardLocked(intendingUserId, intendingDeviceId)
-                        .primaryClipListeners
+                Clipboard clipboard = getClipboardLocked(intendingUserId, intendingDeviceId);
+                if (clipboard == null) {
+                    return;
+                }
+                clipboard.primaryClipListeners
                         .register(
                                 listener,
                                 new ListenerInfo(intendingUid, callingPackage, attributionTag));
@@ -733,8 +743,11 @@
                 return;
             }
             synchronized (mLock) {
-                getClipboardLocked(intendingUserId,
-                        intendingDeviceId).primaryClipListeners.unregister(listener);
+                Clipboard clipboard = getClipboardLocked(intendingUserId,
+                                intendingDeviceId);
+                if (clipboard != null) {
+                    clipboard.primaryClipListeners.unregister(listener);
+                }
             }
         }
 
@@ -757,7 +770,7 @@
             }
             synchronized (mLock) {
                 Clipboard clipboard = getClipboardLocked(intendingUserId, intendingDeviceId);
-                if (clipboard.primaryClip != null) {
+                if (clipboard != null && clipboard.primaryClip != null) {
                     CharSequence text = clipboard.primaryClip.getItemAt(0).getText();
                     return text != null && text.length() > 0;
                 }
@@ -765,11 +778,11 @@
             }
         }
 
+        @android.annotation.EnforcePermission(android.Manifest.permission.SET_CLIP_SOURCE)
         @Override
         public String getPrimaryClipSource(
                 String callingPackage, String attributionTag, int userId, int deviceId) {
-            getContext().enforceCallingOrSelfPermission(Manifest.permission.SET_CLIP_SOURCE,
-                    "Requires SET_CLIP_SOURCE permission");
+            getPrimaryClipSource_enforcePermission();
             final int intendingUid = getIntendingUid(callingPackage, userId);
             final int intendingUserId = UserHandle.getUserId(intendingUid);
             final int intendingDeviceId = getIntendingDeviceId(deviceId, intendingUid);
@@ -786,7 +799,7 @@
             }
             synchronized (mLock) {
                 Clipboard clipboard = getClipboardLocked(intendingUserId, intendingDeviceId);
-                if (clipboard.primaryClip != null) {
+                if (clipboard != null && clipboard.primaryClip != null) {
                     return clipboard.mPrimaryClipPackage;
                 }
                 return null;
@@ -808,7 +821,8 @@
                         final int intendingUid = msg.arg2;
                         final int intendingDeviceId = ((Pair<Integer, Integer>) msg.obj).second;
                         synchronized (mLock) {
-                            if (getClipboardLocked(userId, intendingDeviceId).primaryClip != null) {
+                            Clipboard clipboard = getClipboardLocked(userId, intendingDeviceId);
+                            if (clipboard != null && clipboard.primaryClip != null) {
                                 FrameworkStatsLog.write(FrameworkStatsLog.CLIPBOARD_CLEARED,
                                         FrameworkStatsLog.CLIPBOARD_CLEARED__SOURCE__AUTO_CLEAR);
                                 setPrimaryClipInternalLocked(
@@ -824,9 +838,23 @@
     };
 
     @GuardedBy("mLock")
-    private Clipboard getClipboardLocked(@UserIdInt int userId, int deviceId) {
+    private @Nullable Clipboard getClipboardLocked(@UserIdInt int userId, int deviceId) {
         Clipboard clipboard = mClipboards.get(userId, deviceId);
         if (clipboard == null) {
+            try {
+                if (!mUm.isUserRunning(userId)) {
+                    Slog.w(TAG, "getClipboardLocked called with not running userId " + userId);
+                    return null;
+                }
+            } catch (RemoteException e) {
+                Slog.e(TAG, "RemoteException calling UserManager: " + e);
+                return null;
+            }
+            if (deviceId != DEVICE_ID_DEFAULT && !mVdm.isValidVirtualDeviceId(deviceId)) {
+                Slog.w(TAG, "getClipboardLocked called with invalid (possibly released) deviceId "
+                        + deviceId);
+                return null;
+            }
             clipboard = new Clipboard(userId, deviceId);
             mClipboards.add(userId, deviceId, clipboard);
         }
@@ -876,8 +904,11 @@
         final int userId = UserHandle.getUserId(uid);
 
         // Update this user
-        setPrimaryClipInternalLocked(getClipboardLocked(userId, deviceId), clip, uid,
-                sourcePackage);
+        Clipboard clipboard = getClipboardLocked(userId, deviceId);
+        if (clipboard == null) {
+            return;
+        }
+        setPrimaryClipInternalLocked(clipboard, clip, uid, sourcePackage);
 
         // Update related users
         List<UserInfo> related = getRelatedProfiles(userId);
@@ -911,8 +942,11 @@
                         final boolean canCopyIntoProfile = !hasRestriction(
                                 UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE, id);
                         if (canCopyIntoProfile) {
-                            setPrimaryClipInternalNoClassifyLocked(
-                                    getClipboardLocked(id, deviceId), clip, uid, sourcePackage);
+                            Clipboard relatedClipboard = getClipboardLocked(id, deviceId);
+                            if (relatedClipboard != null) {
+                                setPrimaryClipInternalNoClassifyLocked(relatedClipboard, clip, uid,
+                                        sourcePackage);
+                            }
                         }
                     }
                 }
@@ -1046,6 +1080,9 @@
 
         synchronized (mLock) {
             Clipboard clipboard = getClipboardLocked(userId, deviceId);
+            if (clipboard == null) {
+                return;
+            }
             if (clipboard.primaryClip == clip) {
                 applyClassificationAndSendBroadcastLocked(
                         clipboard, confidences, links, classifier);
@@ -1061,7 +1098,8 @@
                                     UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE, id);
                             if (canCopyIntoProfile) {
                                 Clipboard relatedClipboard = getClipboardLocked(id, deviceId);
-                                if (hasTextLocked(relatedClipboard, text)) {
+                                if (relatedClipboard != null
+                                        && hasTextLocked(relatedClipboard, text)) {
                                     applyClassificationAndSendBroadcastLocked(
                                             relatedClipboard, confidences, links, classifier);
                                 }
@@ -1184,9 +1222,10 @@
             Binder.restoreCallingIdentity(oldIdentity);
         }
         Clipboard clipboard = getClipboardLocked(UserHandle.getUserId(uid), deviceId);
-        if (clipboard.primaryClip != null && !clipboard.activePermissionOwners.contains(pkg)) {
+        if (clipboard != null && clipboard.primaryClip != null
+                && !clipboard.activePermissionOwners.contains(pkg)) {
             final int N = clipboard.primaryClip.getItemCount();
-            for (int i=0; i<N; i++) {
+            for (int i = 0; i < N; i++) {
                 grantItemPermission(clipboard.primaryClip.getItemAt(i), clipboard.primaryClipUid,
                         pkg, UserHandle.getUserId(uid));
             }
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 4d12574..ee323f9 100644
--- a/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
+++ b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
@@ -156,4 +156,14 @@
      * @return the set of display ids for all VirtualDisplays owned by the device
      */
     public abstract @NonNull ArraySet<Integer> getDisplayIdsForDevice(int deviceId);
+
+    /**
+     * Gets the CDM association ID for the VirtualDevice with the given device ID.
+     *
+     * @param deviceId which device we're asking about
+     * @return the CDM association ID for this device, or
+     *   {@link android.companion.virtual.VirtualDeviceManager#ASSOCIATION_ID_INVALID} if no such
+     *   association exists.
+     */
+    public abstract int getAssociationIdForDevice(int deviceId);
 }
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index e85eee81..cba5039 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -22,7 +22,6 @@
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
-import static android.net.NetworkCapabilities.TRANSPORT_VPN;
 import static android.net.RouteInfo.RTN_THROW;
 import static android.net.RouteInfo.RTN_UNREACHABLE;
 import static android.net.VpnManager.NOTIFICATION_CHANNEL_VPN;
@@ -280,15 +279,21 @@
     private static final int VPN_DEFAULT_SCORE = 101;
 
     /**
-     * The reset session timer for data stall. If a session has not successfully revalidated after
-     * the delay, the session will be torn down and restarted in an attempt to recover. Delay
+     * The recovery timer for data stall. If a session has not successfully revalidated after
+     * the delay, the session will perform MOBIKE or be restarted in an attempt to recover. Delay
      * counter is reset on successful validation only.
      *
+     * <p>The first {@code MOBIKE_RECOVERY_ATTEMPT} timers are used for performing MOBIKE.
+     * System will perform session reset for the remaining timers.
      * <p>If retries have exceeded the length of this array, the last entry in the array will be
      * used as a repeating interval.
      */
-    private static final long[] DATA_STALL_RESET_DELAYS_SEC = {30L, 60L, 120L, 240L, 480L, 960L};
-
+    private static final long[] DATA_STALL_RECOVERY_DELAYS_MS =
+            {1000L, 5000L, 30000L, 60000L, 120000L, 240000L, 480000L, 960000L};
+    /**
+     * Maximum attempts to perform MOBIKE when the network is bad.
+     */
+    private static final int MAX_MOBIKE_RECOVERY_ATTEMPT = 2;
     /**
      * The initial token value of IKE session.
      */
@@ -380,6 +385,7 @@
     private final INetworkManagementService mNms;
     private final INetd mNetd;
     @VisibleForTesting
+    @GuardedBy("this")
     protected VpnConfig mConfig;
     private final NetworkProvider mNetworkProvider;
     @VisibleForTesting
@@ -392,7 +398,6 @@
     private final UserManager mUserManager;
 
     private final VpnProfileStore mVpnProfileStore;
-    protected boolean mDataStallSuspected = false;
 
     @VisibleForTesting
     VpnProfileStore getVpnProfileStore() {
@@ -685,14 +690,14 @@
         }
 
         /**
-         * Get the length of time to wait before resetting the ike session when a data stall is
-         * suspected.
+         * Get the length of time to wait before perform data stall recovery when the validation
+         * result is bad.
          */
-        public long getDataStallResetSessionSeconds(int count) {
-            if (count >= DATA_STALL_RESET_DELAYS_SEC.length) {
-                return DATA_STALL_RESET_DELAYS_SEC[DATA_STALL_RESET_DELAYS_SEC.length - 1];
+        public long getValidationFailRecoveryMs(int count) {
+            if (count >= DATA_STALL_RECOVERY_DELAYS_MS.length) {
+                return DATA_STALL_RECOVERY_DELAYS_MS[DATA_STALL_RECOVERY_DELAYS_MS.length - 1];
             } else {
-                return DATA_STALL_RESET_DELAYS_SEC[count];
+                return DATA_STALL_RECOVERY_DELAYS_MS[count];
             }
         }
 
@@ -1389,7 +1394,7 @@
         }
 
         // Check that the caller is authorized.
-        enforceControlPermission();
+        enforceControlPermissionOrInternalCaller();
 
         // Stop an existing always-on VPN from being dethroned by other apps.
         if (mAlwaysOn && !isCurrentPreparedPackage(newPackage)) {
@@ -1598,6 +1603,8 @@
         return network;
     }
 
+    // TODO : this is not synchronized(this) but reads from mConfig, which is dangerous
+    // This file makes an effort to avoid partly initializing mConfig, but this is still not great
     private LinkProperties makeLinkProperties() {
         // The design of disabling IPv6 is only enabled for IKEv2 VPN because it needs additional
         // logic to handle IPv6 only VPN, and the IPv6 only VPN may be restarted when its MTU
@@ -1679,6 +1686,7 @@
      * registering a new NetworkAgent. This is not always possible if the new VPN configuration
      * has certain changes, in which case this method would just return {@code false}.
      */
+    // TODO : this method is not synchronized(this) but reads from mConfig
     private boolean updateLinkPropertiesInPlaceIfPossible(NetworkAgent agent, VpnConfig oldConfig) {
         // NetworkAgentConfig cannot be updated without registering a new NetworkAgent.
         // Strictly speaking, bypassability is affected by lockdown and therefore it's possible
@@ -2269,7 +2277,12 @@
      */
     public synchronized VpnConfig getVpnConfig() {
         enforceControlPermission();
-        return mConfig;
+        // Constructor of VpnConfig cannot take a null parameter. Return null directly if mConfig is
+        // null
+        if (mConfig == null) return null;
+        // mConfig is guarded by "this" and can be modified by another thread as soon as
+        // this method returns, so this method must return a copy.
+        return new VpnConfig(mConfig);
     }
 
     @Deprecated
@@ -2315,6 +2328,7 @@
         }
     };
 
+    @GuardedBy("this")
     private void cleanupVpnStateLocked() {
         mStatusIntent = null;
         resetNetworkCapabilities();
@@ -2837,9 +2851,7 @@
         }
 
         final boolean isLegacyVpn = mVpnRunner instanceof LegacyVpnRunner;
-
         mVpnRunner.exit();
-        mVpnRunner = null;
 
         // LegacyVpn uses daemons that must be shut down before new ones are brought up.
         // The same limitation does not apply to Platform VPNs.
@@ -3044,7 +3056,6 @@
 
         @Nullable private IkeSessionWrapper mSession;
         @Nullable private IkeSessionConnectionInfo mIkeConnectionInfo;
-        @Nullable private VpnConnectivityDiagnosticsCallback mDiagnosticsCallback;
 
         // mMobikeEnabled can only be updated after IKE AUTH is finished.
         private boolean mMobikeEnabled = false;
@@ -3055,7 +3066,7 @@
          * <p>This variable controls the retry delay, and is reset when the VPN pass network
          * validation.
          */
-        private int mDataStallRetryCount = 0;
+        private int mValidationFailRetryCount = 0;
 
         /**
          * The number of attempts since the last successful connection.
@@ -3084,6 +3095,7 @@
                     }
         };
 
+        // GuardedBy("Vpn.this") (annotation can't be applied to constructor)
         IkeV2VpnRunner(
                 @NonNull Ikev2VpnProfile profile, @NonNull ScheduledThreadPoolExecutor executor) {
             super(TAG);
@@ -3136,15 +3148,6 @@
                 mConnectivityManager.registerSystemDefaultNetworkCallback(mNetworkCallback,
                         new Handler(mLooper));
             }
-
-            // DiagnosticsCallback may return more than one alive VPNs, but VPN will filter based on
-            // Network object.
-            final NetworkRequest diagRequest = new NetworkRequest.Builder()
-                    .addTransportType(TRANSPORT_VPN)
-                    .removeCapability(NET_CAPABILITY_NOT_VPN).build();
-            mDiagnosticsCallback = new VpnConnectivityDiagnosticsCallback();
-            mConnectivityDiagnosticsManager.registerConnectivityDiagnosticsCallback(
-                    diagRequest, mExecutor, mDiagnosticsCallback);
         }
 
         private boolean isActiveNetwork(@Nullable Network network) {
@@ -3343,7 +3346,7 @@
                 // Transforms do not need to be persisted; the IkeSession will keep
                 // them alive for us
                 mIpSecManager.applyTunnelModeTransform(mTunnelIface, direction, transform);
-            } catch (IOException e) {
+            } catch (IOException | IllegalArgumentException e) {
                 Log.d(TAG, "Transform application failed for token " + token, e);
                 onSessionLost(token, e);
             }
@@ -3437,7 +3440,7 @@
                         mTunnelIface, IpSecManager.DIRECTION_IN, inTransform);
                 mIpSecManager.applyTunnelModeTransform(
                         mTunnelIface, IpSecManager.DIRECTION_OUT, outTransform);
-            } catch (IOException e) {
+            } catch (IOException | IllegalArgumentException e) {
                 Log.d(TAG, "Transform application failed for token " + token, e);
                 onSessionLost(token, e);
             }
@@ -3710,11 +3713,14 @@
         }
 
         public void updateVpnTransportInfoAndNetCap(int keepaliveDelaySec) {
-            final VpnTransportInfo info = new VpnTransportInfo(
-                    getActiveVpnType(),
-                    mConfig.session,
-                    mConfig.allowBypass && !mLockdown,
-                    areLongLivedTcpConnectionsExpensive(keepaliveDelaySec));
+            final VpnTransportInfo info;
+            synchronized (Vpn.this) {
+                info = new VpnTransportInfo(
+                        getActiveVpnType(),
+                        mConfig.session,
+                        mConfig.allowBypass && !mLockdown,
+                        areLongLivedTcpConnectionsExpensive(keepaliveDelaySec));
+            }
             final boolean ncUpdateRequired = !info.equals(mNetworkCapabilities.getTransportInfo());
             if (ncUpdateRequired) {
                 mNetworkCapabilities = new NetworkCapabilities.Builder(mNetworkCapabilities)
@@ -3875,39 +3881,12 @@
             }
         }
 
-        class VpnConnectivityDiagnosticsCallback
-                extends ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback {
-            // The callback runs in the executor thread.
-            @Override
-            public void onDataStallSuspected(
-                    ConnectivityDiagnosticsManager.DataStallReport report) {
-                synchronized (Vpn.this) {
-                    // Ignore stale runner.
-                    if (mVpnRunner != Vpn.IkeV2VpnRunner.this) return;
-
-                    // Handle the report only for current VPN network. If data stall is already
-                    // reported, ignoring the other reports. It means that the stall is not
-                    // recovered by MOBIKE and should be on the way to reset the ike session.
-                    if (mNetworkAgent != null
-                            && mNetworkAgent.getNetwork().equals(report.getNetwork())
-                            && !mDataStallSuspected) {
-                        Log.d(TAG, "Data stall suspected");
-
-                        // Trigger MOBIKE.
-                        maybeMigrateIkeSessionAndUpdateVpnTransportInfo(mActiveNetwork);
-                        mDataStallSuspected = true;
-                    }
-                }
-            }
-        }
-
         public void onValidationStatus(int status) {
             mEventChanges.log("[Validation] validation status " + status);
             if (status == NetworkAgent.VALIDATION_STATUS_VALID) {
                 // No data stall now. Reset it.
                 mExecutor.execute(() -> {
-                    mDataStallSuspected = false;
-                    mDataStallRetryCount = 0;
+                    mValidationFailRetryCount = 0;
                     if (mScheduledHandleDataStallFuture != null) {
                         Log.d(TAG, "Recovered from stall. Cancel pending reset action.");
                         mScheduledHandleDataStallFuture.cancel(false /* mayInterruptIfRunning */);
@@ -3918,8 +3897,21 @@
                 // Skip other invalid status if the scheduled recovery exists.
                 if (mScheduledHandleDataStallFuture != null) return;
 
+                if (mValidationFailRetryCount < MAX_MOBIKE_RECOVERY_ATTEMPT) {
+                    Log.d(TAG, "Validation failed");
+
+                    // Trigger MOBIKE to recover first.
+                    mExecutor.schedule(() -> {
+                        maybeMigrateIkeSessionAndUpdateVpnTransportInfo(mActiveNetwork);
+                    }, mDeps.getValidationFailRecoveryMs(mValidationFailRetryCount++),
+                            TimeUnit.MILLISECONDS);
+                    return;
+                }
+
+                // Data stall is not recovered by MOBIKE. Try to reset session to recover it.
                 mScheduledHandleDataStallFuture = mExecutor.schedule(() -> {
-                    if (mDataStallSuspected) {
+                    // Only perform the recovery when the network is still bad.
+                    if (mValidationFailRetryCount > 0) {
                         Log.d(TAG, "Reset session to recover stalled network");
                         // This will reset old state if it exists.
                         startIkeSession(mActiveNetwork);
@@ -3928,7 +3920,9 @@
                     // Reset mScheduledHandleDataStallFuture since it's already run on executor
                     // thread.
                     mScheduledHandleDataStallFuture = null;
-                }, mDeps.getDataStallResetSessionSeconds(mDataStallRetryCount++), TimeUnit.SECONDS);
+                    // TODO: compute the delay based on the last recovery timestamp
+                }, mDeps.getValidationFailRecoveryMs(mValidationFailRetryCount++),
+                        TimeUnit.MILLISECONDS);
             }
         }
 
@@ -4220,7 +4214,7 @@
          * consistency of the Ikev2VpnRunner fields.
          */
         private void disconnectVpnRunner() {
-            mEventChanges.log("[VPNRunner] Disconnect runner, underlying network" + mActiveNetwork);
+            mEventChanges.log("[VPNRunner] Disconnect runner, underlying net " + mActiveNetwork);
             mActiveNetwork = null;
             mUnderlyingNetworkCapabilities = null;
             mUnderlyingLinkProperties = null;
@@ -4231,8 +4225,6 @@
             mCarrierConfigManager.unregisterCarrierConfigChangeListener(
                     mCarrierConfigChangeListener);
             mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
-            mConnectivityDiagnosticsManager.unregisterConnectivityDiagnosticsCallback(
-                    mDiagnosticsCallback);
             clearVpnNetworkPreference(mSessionKey);
 
             mExecutor.shutdown();
@@ -4293,6 +4285,7 @@
             }
         };
 
+        // GuardedBy("Vpn.this") (annotation can't be applied to constructor)
         LegacyVpnRunner(VpnConfig config, String[] racoon, String[] mtpd, VpnProfile profile) {
             super(TAG);
             if (racoon == null && mtpd == null) {
@@ -4500,46 +4493,46 @@
                 }
 
                 // Set the interface and the addresses in the config.
-                mConfig.interfaze = parameters[0].trim();
-
-                mConfig.addLegacyAddresses(parameters[1]);
-                // Set the routes if they are not set in the config.
-                if (mConfig.routes == null || mConfig.routes.isEmpty()) {
-                    mConfig.addLegacyRoutes(parameters[2]);
-                }
-
-                // Set the DNS servers if they are not set in the config.
-                if (mConfig.dnsServers == null || mConfig.dnsServers.size() == 0) {
-                    String dnsServers = parameters[3].trim();
-                    if (!dnsServers.isEmpty()) {
-                        mConfig.dnsServers = Arrays.asList(dnsServers.split(" "));
-                    }
-                }
-
-                // Set the search domains if they are not set in the config.
-                if (mConfig.searchDomains == null || mConfig.searchDomains.size() == 0) {
-                    String searchDomains = parameters[4].trim();
-                    if (!searchDomains.isEmpty()) {
-                        mConfig.searchDomains = Arrays.asList(searchDomains.split(" "));
-                    }
-                }
-
-                // Add a throw route for the VPN server endpoint, if one was specified.
-                if (endpointAddress instanceof Inet4Address) {
-                    mConfig.routes.add(new RouteInfo(
-                            new IpPrefix(endpointAddress, 32), null /*gateway*/,
-                            null /*iface*/, RTN_THROW));
-                } else if (endpointAddress instanceof Inet6Address) {
-                    mConfig.routes.add(new RouteInfo(
-                            new IpPrefix(endpointAddress, 128), null /*gateway*/,
-                            null /*iface*/, RTN_THROW));
-                } else {
-                    Log.e(TAG, "Unknown IP address family for VPN endpoint: "
-                            + endpointAddress);
-                }
-
-                // Here is the last step and it must be done synchronously.
                 synchronized (Vpn.this) {
+                    mConfig.interfaze = parameters[0].trim();
+
+                    mConfig.addLegacyAddresses(parameters[1]);
+                    // Set the routes if they are not set in the config.
+                    if (mConfig.routes == null || mConfig.routes.isEmpty()) {
+                        mConfig.addLegacyRoutes(parameters[2]);
+                    }
+
+                    // Set the DNS servers if they are not set in the config.
+                    if (mConfig.dnsServers == null || mConfig.dnsServers.size() == 0) {
+                        String dnsServers = parameters[3].trim();
+                        if (!dnsServers.isEmpty()) {
+                            mConfig.dnsServers = Arrays.asList(dnsServers.split(" "));
+                        }
+                    }
+
+                    // Set the search domains if they are not set in the config.
+                    if (mConfig.searchDomains == null || mConfig.searchDomains.size() == 0) {
+                        String searchDomains = parameters[4].trim();
+                        if (!searchDomains.isEmpty()) {
+                            mConfig.searchDomains = Arrays.asList(searchDomains.split(" "));
+                        }
+                    }
+
+                    // Add a throw route for the VPN server endpoint, if one was specified.
+                    if (endpointAddress instanceof Inet4Address) {
+                        mConfig.routes.add(new RouteInfo(
+                                new IpPrefix(endpointAddress, 32), null /*gateway*/,
+                                null /*iface*/, RTN_THROW));
+                    } else if (endpointAddress instanceof Inet6Address) {
+                        mConfig.routes.add(new RouteInfo(
+                                new IpPrefix(endpointAddress, 128), null /*gateway*/,
+                                null /*iface*/, RTN_THROW));
+                    } else {
+                        Log.e(TAG, "Unknown IP address family for VPN endpoint: "
+                                + endpointAddress);
+                    }
+
+                    // Here is the last step and it must be done synchronously.
                     // Set the start time
                     mConfig.startTime = SystemClock.elapsedRealtime();
 
@@ -4773,25 +4766,26 @@
 
         try {
             // Build basic config
-            mConfig = new VpnConfig();
+            final VpnConfig config = new VpnConfig();
             if (VpnConfig.LEGACY_VPN.equals(packageName)) {
-                mConfig.legacy = true;
-                mConfig.session = profile.name;
-                mConfig.user = profile.key;
+                config.legacy = true;
+                config.session = profile.name;
+                config.user = profile.key;
 
                 // TODO: Add support for configuring meteredness via Settings. Until then, use a
                 // safe default.
-                mConfig.isMetered = true;
+                config.isMetered = true;
             } else {
-                mConfig.user = packageName;
-                mConfig.isMetered = profile.isMetered;
+                config.user = packageName;
+                config.isMetered = profile.isMetered;
             }
-            mConfig.startTime = SystemClock.elapsedRealtime();
-            mConfig.proxyInfo = profile.proxy;
-            mConfig.requiresInternetValidation = profile.requiresInternetValidation;
-            mConfig.excludeLocalRoutes = profile.excludeLocalRoutes;
-            mConfig.allowBypass = profile.isBypassable;
-            mConfig.disallowedApplications = getAppExclusionList(mPackage);
+            config.startTime = SystemClock.elapsedRealtime();
+            config.proxyInfo = profile.proxy;
+            config.requiresInternetValidation = profile.requiresInternetValidation;
+            config.excludeLocalRoutes = profile.excludeLocalRoutes;
+            config.allowBypass = profile.isBypassable;
+            config.disallowedApplications = getAppExclusionList(mPackage);
+            mConfig = config;
 
             switch (profile.type) {
                 case VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS:
@@ -4805,6 +4799,7 @@
                     mVpnRunner.start();
                     break;
                 default:
+                    mConfig = null;
                     updateState(DetailedState.FAILED, "Invalid platform VPN type");
                     Log.d(TAG, "Unknown VPN profile type: " + profile.type);
                     break;
@@ -5216,7 +5211,7 @@
                 pw.println("MOBIKE " + (runner.mMobikeEnabled ? "enabled" : "disabled"));
                 pw.println("Profile: " + runner.mProfile);
                 pw.println("Token: " + runner.mCurrentToken);
-                if (mDataStallSuspected) pw.println("Data stall suspected");
+                pw.println("Validation failed retry count:" + runner.mValidationFailRetryCount);
                 if (runner.mScheduledHandleDataStallFuture != null) {
                     pw.println("Reset session scheduled");
                 }
diff --git a/services/core/java/com/android/server/content/ContentService.java b/services/core/java/com/android/server/content/ContentService.java
index 781920c..1b48e3c 100644
--- a/services/core/java/com/android/server/content/ContentService.java
+++ b/services/core/java/com/android/server/content/ContentService.java
@@ -1150,10 +1150,10 @@
         }
     }
 
+    @android.annotation.EnforcePermission(android.Manifest.permission.READ_SYNC_STATS)
     @Override
     public boolean isSyncActive(Account account, String authority, ComponentName cname) {
-        mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS,
-                "no permission to read the sync stats");
+        isSyncActive_enforcePermission();
 
         final int callingUid = Binder.getCallingUid();
         final int userId = UserHandle.getCallingUserId();
@@ -1254,11 +1254,11 @@
         return isSyncPendingAsUser(account, authority, cname, UserHandle.getCallingUserId());
     }
 
+    @android.annotation.EnforcePermission(android.Manifest.permission.READ_SYNC_STATS)
     @Override
     public boolean isSyncPendingAsUser(Account account, String authority, ComponentName cname,
                                        int userId) {
-        mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS,
-                "no permission to read the sync stats");
+        isSyncPendingAsUser_enforcePermission();
         enforceCrossUserPermission(userId,
                 "no permission to retrieve the sync settings for user " + userId);
 
diff --git a/services/core/java/com/android/server/cpu/CpuInfoReader.java b/services/core/java/com/android/server/cpu/CpuInfoReader.java
index 70d7bde..984ad1d 100644
--- a/services/core/java/com/android/server/cpu/CpuInfoReader.java
+++ b/services/core/java/com/android/server/cpu/CpuInfoReader.java
@@ -203,15 +203,15 @@
                 continue;
             }
             if (dynamicPolicyInfo.curCpuFreqKHz == CpuInfo.MISSING_FREQUENCY
-                    || staticPolicyInfo.maxCpuFreqKHz == CpuInfo.MISSING_FREQUENCY) {
+                    || dynamicPolicyInfo.maxCpuFreqKHz == CpuInfo.MISSING_FREQUENCY) {
                 Slogf.w(TAG, "Current and maximum CPU frequency information mismatch/missing for"
                         + " policy ID %d", policyId);
                 continue;
             }
-            if (dynamicPolicyInfo.curCpuFreqKHz > staticPolicyInfo.maxCpuFreqKHz) {
+            if (dynamicPolicyInfo.curCpuFreqKHz > dynamicPolicyInfo.maxCpuFreqKHz) {
                 Slogf.w(TAG, "Current CPU frequency (%d) is greater than maximum CPU frequency"
                         + " (%d) for policy ID (%d). Skipping CPU frequency policy",
-                        dynamicPolicyInfo.curCpuFreqKHz,  staticPolicyInfo.maxCpuFreqKHz, policyId);
+                        dynamicPolicyInfo.curCpuFreqKHz, dynamicPolicyInfo.maxCpuFreqKHz, policyId);
                 continue;
             }
             for (int coreIdx = 0; coreIdx < staticPolicyInfo.relatedCpuCores.size(); coreIdx++) {
@@ -234,7 +234,8 @@
                 if (dynamicPolicyInfo.affectedCpuCores.indexOf(relatedCpuCore) < 0) {
                     cpuInfoByCpus.append(relatedCpuCore, new CpuInfo(relatedCpuCore,
                             cpusetCategories, /* isOnline= */false, CpuInfo.MISSING_FREQUENCY,
-                            staticPolicyInfo.maxCpuFreqKHz, CpuInfo.MISSING_FREQUENCY, usageStats));
+                            dynamicPolicyInfo.maxCpuFreqKHz, CpuInfo.MISSING_FREQUENCY,
+                            usageStats));
                     continue;
                 }
                 // If a CPU core is online, it must have the usage stats. When the usage stats is
@@ -245,7 +246,7 @@
                     continue;
                 }
                 CpuInfo cpuInfo = new CpuInfo(relatedCpuCore, cpusetCategories, /* isOnline= */true,
-                        dynamicPolicyInfo.curCpuFreqKHz, staticPolicyInfo.maxCpuFreqKHz,
+                        dynamicPolicyInfo.curCpuFreqKHz, dynamicPolicyInfo.maxCpuFreqKHz,
                         dynamicPolicyInfo.avgTimeInStateCpuFreqKHz, usageStats);
                 cpuInfoByCpus.append(relatedCpuCore, cpuInfo);
                 if (DEBUG) {
@@ -423,12 +424,6 @@
         for (int i = 0; i < mCpuFreqPolicyDirsById.size(); i++) {
             int policyId = mCpuFreqPolicyDirsById.keyAt(i);
             File policyDir = mCpuFreqPolicyDirsById.valueAt(i);
-            long maxCpuFreqKHz = readCpuFreqKHz(new File(policyDir, MAX_SCALING_FREQ_FILE));
-            if (maxCpuFreqKHz == CpuInfo.MISSING_FREQUENCY) {
-                Slogf.w(TAG, "Missing max CPU frequency information at %s",
-                        policyDir.getAbsolutePath());
-                continue;
-            }
             File cpuCoresFile = new File(policyDir, RELATED_CPUS_FILE);
             IntArray relatedCpuCores = readCpuCores(cpuCoresFile);
             if (relatedCpuCores == null || relatedCpuCores.size() == 0) {
@@ -436,8 +431,7 @@
                         cpuCoresFile.getAbsolutePath());
                 continue;
             }
-            StaticPolicyInfo staticPolicyInfo = new StaticPolicyInfo(maxCpuFreqKHz,
-                    relatedCpuCores);
+            StaticPolicyInfo staticPolicyInfo = new StaticPolicyInfo(relatedCpuCores);
             mStaticPolicyInfoById.append(policyId, staticPolicyInfo);
             if (DEBUG) {
                 Slogf.d(TAG, "Added static policy info %s for policy id %d", staticPolicyInfo,
@@ -464,8 +458,14 @@
                 Slogf.e(TAG, "Failed to read CPU cores from %s", cpuCoresFile.getAbsolutePath());
                 continue;
             }
+            long maxCpuFreqKHz = readCpuFreqKHz(new File(policyDir, MAX_SCALING_FREQ_FILE));
+            if (maxCpuFreqKHz == CpuInfo.MISSING_FREQUENCY) {
+                Slogf.w(TAG, "Missing max CPU frequency information at %s",
+                        policyDir.getAbsolutePath());
+                continue;
+            }
             DynamicPolicyInfo dynamicPolicyInfo = new DynamicPolicyInfo(curCpuFreqKHz,
-                    avgTimeInStateCpuFreqKHz, affectedCpuCores);
+                    maxCpuFreqKHz, avgTimeInStateCpuFreqKHz, affectedCpuCores);
             dynamicPolicyInfoById.append(policyId, dynamicPolicyInfo);
             if (DEBUG) {
                 Slogf.d(TAG, "Read dynamic policy info %s for policy id %d", dynamicPolicyInfo,
@@ -593,9 +593,12 @@
             List<String> lines = Files.readAllLines(file.toPath());
             IntArray cpuCores = new IntArray(0);
             for (int i = 0; i < lines.size(); i++) {
-                String line = lines.get(i);
-                String[] pairs = line.contains(",") ? line.trim().split(",")
-                        : line.trim().split(" ");
+                String line = lines.get(i).trim();
+                if (line.isEmpty()) {
+                    continue;
+                }
+                String[] pairs = line.contains(",") ? line.split(",")
+                        : line.split(" ");
                 for (int j = 0; j < pairs.length; j++) {
                     String[] minMaxPairs = pairs[j].split("-");
                     if (minMaxPairs.length >= 2) {
@@ -615,6 +618,9 @@
                 }
             }
             return cpuCores;
+        } catch (NumberFormatException e) {
+            Slogf.e(TAG, e, "Failed to read CPU cores from %s due to incorrect file format",
+                    file.getAbsolutePath());
         } catch (Exception e) {
             Slogf.e(TAG, e, "Failed to read CPU cores from %s", file.getAbsolutePath());
         }
@@ -889,29 +895,28 @@
     }
 
     private static final class StaticPolicyInfo {
-        public final long maxCpuFreqKHz;
         public final IntArray relatedCpuCores;
 
-        StaticPolicyInfo(long maxCpuFreqKHz, IntArray relatedCpuCores) {
-            this.maxCpuFreqKHz = maxCpuFreqKHz;
+        StaticPolicyInfo(IntArray relatedCpuCores) {
             this.relatedCpuCores = relatedCpuCores;
         }
 
         @Override
         public String toString() {
-            return "StaticPolicyInfo{maxCpuFreqKHz = " + maxCpuFreqKHz + ", relatedCpuCores = "
-                    + relatedCpuCores + '}';
+            return "StaticPolicyInfo{relatedCpuCores = " + relatedCpuCores + '}';
         }
     }
 
     private static final class DynamicPolicyInfo {
         public final long curCpuFreqKHz;
+        public final long maxCpuFreqKHz;
         public final long avgTimeInStateCpuFreqKHz;
         public final IntArray affectedCpuCores;
 
-        DynamicPolicyInfo(long curCpuFreqKHz, long avgTimeInStateCpuFreqKHz,
+        DynamicPolicyInfo(long curCpuFreqKHz, long maxCpuFreqKHz, long avgTimeInStateCpuFreqKHz,
                 IntArray affectedCpuCores) {
             this.curCpuFreqKHz = curCpuFreqKHz;
+            this.maxCpuFreqKHz = maxCpuFreqKHz;
             this.avgTimeInStateCpuFreqKHz = avgTimeInStateCpuFreqKHz;
             this.affectedCpuCores = affectedCpuCores;
         }
@@ -919,6 +924,7 @@
         @Override
         public String toString() {
             return "DynamicPolicyInfo{curCpuFreqKHz = " + curCpuFreqKHz
+                    + ", maxCpuFreqKHz = " + maxCpuFreqKHz
                     + ", avgTimeInStateCpuFreqKHz = " + avgTimeInStateCpuFreqKHz
                     + ", affectedCpuCores = " + affectedCpuCores + '}';
         }
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
index add94b1..1012bc1 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
@@ -1234,12 +1234,11 @@
             }
         }
 
+        @android.annotation.EnforcePermission(android.Manifest.permission.CONTROL_DEVICE_STATE)
         @Override // Binder call
         public void onStateRequestOverlayDismissed(boolean shouldCancelRequest) {
 
-            getContext().enforceCallingOrSelfPermission(CONTROL_DEVICE_STATE,
-                    "CONTROL_DEVICE_STATE permission required to control the state request "
-                            + "overlay");
+            onStateRequestOverlayDismissed_enforcePermission();
 
             final long callingIdentity = Binder.clearCallingIdentity();
             try {
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index 75709fbb..d647757 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -223,11 +223,11 @@
     private final ShortTermModel mShortTermModel;
     private final ShortTermModel mPausedShortTermModel;
 
-    // Controls High Brightness Mode.
-    private HighBrightnessModeController mHbmController;
+    // Controls Brightness range (including High Brightness Mode).
+    private final BrightnessRangeController mBrightnessRangeController;
 
     // Throttles (caps) maximum allowed brightness
-    private BrightnessThrottler mBrightnessThrottler;
+    private final BrightnessThrottler mBrightnessThrottler;
     private boolean mIsBrightnessThrottled;
 
     // Context-sensitive brightness configurations require keeping track of the foreground app's
@@ -257,7 +257,8 @@
             HysteresisLevels screenBrightnessThresholds,
             HysteresisLevels ambientBrightnessThresholdsIdle,
             HysteresisLevels screenBrightnessThresholdsIdle, Context context,
-            HighBrightnessModeController hbmController, BrightnessThrottler brightnessThrottler,
+            BrightnessRangeController brightnessModeController,
+            BrightnessThrottler brightnessThrottler,
             BrightnessMappingStrategy idleModeBrightnessMapper, int ambientLightHorizonShort,
             int ambientLightHorizonLong, float userLux, float userBrightness) {
         this(new Injector(), callbacks, looper, sensorManager, lightSensor,
@@ -267,7 +268,7 @@
                 darkeningLightDebounceConfig, resetAmbientLuxAfterWarmUpConfig,
                 ambientBrightnessThresholds, screenBrightnessThresholds,
                 ambientBrightnessThresholdsIdle, screenBrightnessThresholdsIdle, context,
-                hbmController, brightnessThrottler, idleModeBrightnessMapper,
+                brightnessModeController, brightnessThrottler, idleModeBrightnessMapper,
                 ambientLightHorizonShort, ambientLightHorizonLong, userLux, userBrightness
         );
     }
@@ -283,7 +284,8 @@
             HysteresisLevels screenBrightnessThresholds,
             HysteresisLevels ambientBrightnessThresholdsIdle,
             HysteresisLevels screenBrightnessThresholdsIdle, Context context,
-            HighBrightnessModeController hbmController, BrightnessThrottler brightnessThrottler,
+            BrightnessRangeController brightnessModeController,
+            BrightnessThrottler brightnessThrottler,
             BrightnessMappingStrategy idleModeBrightnessMapper, int ambientLightHorizonShort,
             int ambientLightHorizonLong, float userLux, float userBrightness) {
         mInjector = injector;
@@ -326,7 +328,7 @@
         mPendingForegroundAppPackageName = null;
         mForegroundAppCategory = ApplicationInfo.CATEGORY_UNDEFINED;
         mPendingForegroundAppCategory = ApplicationInfo.CATEGORY_UNDEFINED;
-        mHbmController = hbmController;
+        mBrightnessRangeController = brightnessModeController;
         mBrightnessThrottler = brightnessThrottler;
         mInteractiveModeBrightnessMapper = interactiveModeBrightnessMapper;
         mIdleModeBrightnessMapper = idleModeBrightnessMapper;
@@ -607,10 +609,11 @@
 
         pw.println();
         pw.println("  mInteractiveMapper=");
-        mInteractiveModeBrightnessMapper.dump(pw, mHbmController.getNormalBrightnessMax());
+        mInteractiveModeBrightnessMapper.dump(pw,
+                mBrightnessRangeController.getNormalBrightnessMax());
         if (mIdleModeBrightnessMapper != null) {
             pw.println("  mIdleMapper=");
-            mIdleModeBrightnessMapper.dump(pw, mHbmController.getNormalBrightnessMax());
+            mIdleModeBrightnessMapper.dump(pw, mBrightnessRangeController.getNormalBrightnessMax());
         }
 
         pw.println();
@@ -736,7 +739,7 @@
             mAmbientDarkeningThreshold =
                     mAmbientBrightnessThresholds.getDarkeningThreshold(lux);
         }
-        mHbmController.onAmbientLuxChange(mAmbientLux);
+        mBrightnessRangeController.onAmbientLuxChange(mAmbientLux);
 
 
         // If the short term model was invalidated and the change is drastic enough, reset it.
@@ -976,9 +979,9 @@
 
     // Clamps values with float range [0.0-1.0]
     private float clampScreenBrightness(float value) {
-        final float minBrightness = Math.min(mHbmController.getCurrentBrightnessMin(),
+        final float minBrightness = Math.min(mBrightnessRangeController.getCurrentBrightnessMin(),
                 mBrightnessThrottler.getBrightnessCap());
-        final float maxBrightness = Math.min(mHbmController.getCurrentBrightnessMax(),
+        final float maxBrightness = Math.min(mBrightnessRangeController.getCurrentBrightnessMax(),
                 mBrightnessThrottler.getBrightnessCap());
         return MathUtils.constrain(value, minBrightness, maxBrightness);
     }
diff --git a/services/core/java/com/android/server/display/BrightnessRangeController.java b/services/core/java/com/android/server/display/BrightnessRangeController.java
new file mode 100644
index 0000000..5b11cfe
--- /dev/null
+++ b/services/core/java/com/android/server/display/BrightnessRangeController.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.hardware.display.BrightnessInfo;
+import android.os.IBinder;
+import android.provider.DeviceConfigInterface;
+
+import com.android.server.display.feature.DeviceConfigParameterProvider;
+
+import java.io.PrintWriter;
+import java.util.function.BooleanSupplier;
+
+class BrightnessRangeController {
+
+    private final HighBrightnessModeController mHbmController;
+    private final NormalBrightnessModeController mNormalBrightnessModeController =
+            new NormalBrightnessModeController();
+
+    private final Runnable mModeChangeCallback;
+    private final boolean mUseNbmController;
+
+
+    BrightnessRangeController(HighBrightnessModeController hbmController,
+            Runnable modeChangeCallback) {
+        this(hbmController, modeChangeCallback,
+                new DeviceConfigParameterProvider(DeviceConfigInterface.REAL));
+    }
+
+    BrightnessRangeController(HighBrightnessModeController hbmController,
+            Runnable modeChangeCallback, DeviceConfigParameterProvider configParameterProvider) {
+        mHbmController = hbmController;
+        mModeChangeCallback = modeChangeCallback;
+        mUseNbmController = configParameterProvider.isNormalBrightnessControllerFeatureEnabled();
+    }
+
+    void dump(PrintWriter pw) {
+        pw.println("BrightnessRangeController:");
+        pw.println("  mUseNormalBrightnessController=" + mUseNbmController);
+        mHbmController.dump(pw);
+        mNormalBrightnessModeController.dump(pw);
+
+    }
+
+    void onAmbientLuxChange(float ambientLux) {
+        applyChanges(
+                () -> mNormalBrightnessModeController.onAmbientLuxChange(ambientLux),
+                () -> mHbmController.onAmbientLuxChange(ambientLux)
+        );
+    }
+
+    float getNormalBrightnessMax() {
+        return mHbmController.getNormalBrightnessMax();
+    }
+
+    void loadFromConfig(HighBrightnessModeMetadata hbmMetadata, IBinder token,
+            DisplayDeviceInfo info, DisplayDeviceConfig displayDeviceConfig) {
+        applyChanges(
+                () -> mNormalBrightnessModeController.resetNbmData(
+                        displayDeviceConfig.getLuxThrottlingData()),
+                () -> {
+                    mHbmController.setHighBrightnessModeMetadata(hbmMetadata);
+                    mHbmController.resetHbmData(info.width, info.height, token, info.uniqueId,
+                            displayDeviceConfig.getHighBrightnessModeData(),
+                            displayDeviceConfig::getHdrBrightnessFromSdr);
+                }
+        );
+    }
+
+    void stop() {
+        mHbmController.stop();
+    }
+
+    void setAutoBrightnessEnabled(int state) {
+        applyChanges(
+                () -> mNormalBrightnessModeController.setAutoBrightnessState(state),
+                () ->  mHbmController.setAutoBrightnessEnabled(state)
+        );
+    }
+
+    void onBrightnessChanged(float brightness, float unthrottledBrightness,
+            @BrightnessInfo.BrightnessMaxReason int throttlingReason) {
+        mHbmController.onBrightnessChanged(brightness, unthrottledBrightness, throttlingReason);
+    }
+
+    float getCurrentBrightnessMin() {
+        return mHbmController.getCurrentBrightnessMin();
+    }
+
+
+    float getCurrentBrightnessMax() {
+        if (mUseNbmController && mHbmController.getHighBrightnessMode()
+                == BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF) {
+            return Math.min(mHbmController.getCurrentBrightnessMax(),
+                    mNormalBrightnessModeController.getCurrentBrightnessMax());
+        }
+        return mHbmController.getCurrentBrightnessMax();
+    }
+
+    int getHighBrightnessMode() {
+        return mHbmController.getHighBrightnessMode();
+    }
+
+    float getHdrBrightnessValue() {
+        return mHbmController.getHdrBrightnessValue();
+    }
+
+    float getTransitionPoint() {
+        return mHbmController.getTransitionPoint();
+    }
+
+    private void applyChanges(BooleanSupplier nbmChangesFunc, Runnable hbmChangesFunc) {
+        if (mUseNbmController) {
+            boolean nbmTransitionChanged = nbmChangesFunc.getAsBoolean();
+            hbmChangesFunc.run();
+            // if nbm transition changed - trigger callback
+            // HighBrightnessModeController handles sending changes itself
+            if (nbmTransitionChanged) {
+                mModeChangeCallback.run();
+            }
+        } else {
+            hbmChangesFunc.run();
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/display/BrightnessSetting.java b/services/core/java/com/android/server/display/BrightnessSetting.java
index de42370..651828b 100644
--- a/services/core/java/com/android/server/display/BrightnessSetting.java
+++ b/services/core/java/com/android/server/display/BrightnessSetting.java
@@ -40,6 +40,7 @@
 
     private final LogicalDisplay mLogicalDisplay;
 
+    private int mUserSerial;
     private final Handler mHandler = new Handler(Looper.getMainLooper()) {
         @Override
         public void handleMessage(Message msg) {
@@ -56,13 +57,15 @@
     @GuardedBy("mSyncRoot")
     private float mBrightness;
 
-    BrightnessSetting(@NonNull PersistentDataStore persistentDataStore,
+    BrightnessSetting(int userSerial,
+            @NonNull PersistentDataStore persistentDataStore,
             @NonNull LogicalDisplay logicalDisplay,
             DisplayManagerService.SyncRoot syncRoot) {
         mPersistentDataStore = persistentDataStore;
         mLogicalDisplay = logicalDisplay;
+        mUserSerial = userSerial;
         mBrightness = mPersistentDataStore.getBrightness(
-                mLogicalDisplay.getPrimaryDisplayDeviceLocked());
+                mLogicalDisplay.getPrimaryDisplayDeviceLocked(), userSerial);
         mSyncRoot = syncRoot;
     }
 
@@ -96,8 +99,13 @@
         mListeners.remove(l);
     }
 
+    /** Sets the user serial for the brightness setting */
+    public void setUserSerial(int userSerial) {
+        mUserSerial = userSerial;
+    }
+
     /**
-     * Sets the brigtness and broadcasts the change to the listeners.
+     * Sets the brightness and broadcasts the change to the listeners.
      * @param brightness The value to which the brightness is to be set.
      */
     public void setBrightness(float brightness) {
@@ -112,7 +120,8 @@
             // changed.
             if (brightness != mBrightness) {
                 mPersistentDataStore.setBrightness(mLogicalDisplay.getPrimaryDisplayDeviceLocked(),
-                        brightness);
+                        brightness, mUserSerial
+                );
             }
             mBrightness = brightness;
             int toSend = Float.floatToIntBits(mBrightness);
diff --git a/services/core/java/com/android/server/display/BrightnessThrottler.java b/services/core/java/com/android/server/display/BrightnessThrottler.java
index cfdcd63..59844e1 100644
--- a/services/core/java/com/android/server/display/BrightnessThrottler.java
+++ b/services/core/java/com/android/server/display/BrightnessThrottler.java
@@ -22,7 +22,6 @@
 import android.annotation.Nullable;
 import android.content.Context;
 import android.hardware.display.BrightnessInfo;
-import android.hardware.display.DisplayManager;
 import android.os.Handler;
 import android.os.HandlerExecutor;
 import android.os.IThermalEventListener;
@@ -38,17 +37,25 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData;
 import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel;
+import com.android.server.display.feature.DeviceConfigParameterProvider;
+import com.android.server.display.utils.DeviceConfigParsingUtils;
 
 import java.io.PrintWriter;
-import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.concurrent.Executor;
+import java.util.function.BiFunction;
+import java.util.function.Function;
 
 /**
  * This class monitors various conditions, such as skin temperature throttling status, and limits
  * the allowed brightness range accordingly.
+ *
+ * @deprecated will be replaced by
+ * {@link com.android.server.display.brightness.clamper.BrightnessThermalClamper}
  */
+@Deprecated
 class BrightnessThrottler {
     private static final String TAG = "BrightnessThrottler";
     private static final boolean DEBUG = false;
@@ -63,7 +70,7 @@
     private final Runnable mThrottlingChangeCallback;
     private final SkinThermalStatusObserver mSkinThermalStatusObserver;
     private final DeviceConfigListener mDeviceConfigListener;
-    private final DeviceConfigInterface mDeviceConfig;
+    private final DeviceConfigParameterProvider mConfigParameterProvider;
 
     private int mThrottlingStatus;
 
@@ -93,8 +100,21 @@
     // time the underlying display device changes.
     // This map is indexed by uniqueDisplayId, to provide maps for throttlingId -> throttlingData.
     // HashMap< uniqueDisplayId, HashMap< throttlingDataId, ThermalBrightnessThrottlingData >>
-    private final HashMap<String, HashMap<String, ThermalBrightnessThrottlingData>>
-            mThermalBrightnessThrottlingDataOverride = new HashMap<>(1);
+    private final Map<String, Map<String, ThermalBrightnessThrottlingData>>
+            mThermalBrightnessThrottlingDataOverride = new HashMap<>();
+
+    private final BiFunction<String, String, ThrottlingLevel> mDataPointMapper = (key, value) -> {
+        try {
+            int status = DeviceConfigParsingUtils.parseThermalStatus(key);
+            float brightnessPoint = DeviceConfigParsingUtils.parseBrightness(value);
+            return new ThrottlingLevel(status, brightnessPoint);
+        } catch (IllegalArgumentException iae) {
+            return null;
+        }
+    };
+
+    private final Function<List<ThrottlingLevel>, ThermalBrightnessThrottlingData>
+            mDataSetMapper = ThermalBrightnessThrottlingData::create;
 
     BrightnessThrottler(Handler handler, Runnable throttlingChangeCallback, String uniqueDisplayId,
             String throttlingDataId,
@@ -118,7 +138,7 @@
         mSkinThermalStatusObserver = new SkinThermalStatusObserver(mInjector, mHandler);
 
         mUniqueDisplayId = uniqueDisplayId;
-        mDeviceConfig = injector.getDeviceConfig();
+        mConfigParameterProvider = new DeviceConfigParameterProvider(injector.getDeviceConfig());
         mDeviceConfigListener = new DeviceConfigListener();
         mThermalBrightnessThrottlingDataId = throttlingDataId;
         mDdcThermalThrottlingDataMap = thermalBrightnessThrottlingDataMap;
@@ -145,7 +165,7 @@
 
     void stop() {
         mSkinThermalStatusObserver.stopObserving();
-        mDeviceConfig.removeOnPropertiesChangedListener(mDeviceConfigListener);
+        mConfigParameterProvider.removeOnPropertiesChangedListener(mDeviceConfigListener);
         // We're asked to stop throttling, so reset brightness restrictions.
         mBrightnessCap = PowerManager.BRIGHTNESS_MAX;
         mBrightnessMaxReason = BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE;
@@ -248,12 +268,6 @@
         mSkinThermalStatusObserver.dump(pw);
     }
 
-    private String getThermalBrightnessThrottlingDataString() {
-        return mDeviceConfig.getString(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
-                DisplayManager.DeviceConfig.KEY_BRIGHTNESS_THROTTLING_DATA,
-                /* defaultValue= */ null);
-    }
-
     // The brightness throttling data id may or may not be specified in the string that is passed
     // in, if there is none specified, we assume it is for the default case. Each string passed in
     // here must be for one display and one throttling id.
@@ -263,78 +277,15 @@
     // 456,2,moderate,0.9,critical,0.7,id_2
     // displayId, number, <state, val> * number
     // displayId, <number, <state, val> * number>, throttlingId
-    private boolean parseAndAddData(@NonNull String strArray,
-            @NonNull HashMap<String, HashMap<String, ThermalBrightnessThrottlingData>>
-                    displayIdToThrottlingIdToBtd) {
-        boolean validConfig = true;
-        String[] items = strArray.split(",");
-        int i = 0;
-
-        try {
-            String uniqueDisplayId = items[i++];
-
-            // number of throttling points
-            int noOfThrottlingPoints = Integer.parseInt(items[i++]);
-            List<ThrottlingLevel> throttlingLevels = new ArrayList<>(noOfThrottlingPoints);
-
-            // throttling level and point
-            for (int j = 0; j < noOfThrottlingPoints; j++) {
-                String severity = items[i++];
-                int status = parseThermalStatus(severity);
-                float brightnessPoint = parseBrightness(items[i++]);
-                throttlingLevels.add(new ThrottlingLevel(status, brightnessPoint));
-            }
-
-            String throttlingDataId = (i < items.length) ? items[i++] : DEFAULT_ID;
-            ThermalBrightnessThrottlingData throttlingLevelsData =
-                    DisplayDeviceConfig.ThermalBrightnessThrottlingData.create(throttlingLevels);
-
-            // Add throttlingLevelsData to inner map where necessary.
-            HashMap<String, ThermalBrightnessThrottlingData> throttlingMapForDisplay =
-                    displayIdToThrottlingIdToBtd.get(uniqueDisplayId);
-            if (throttlingMapForDisplay == null) {
-                throttlingMapForDisplay = new HashMap<>();
-                throttlingMapForDisplay.put(throttlingDataId, throttlingLevelsData);
-                displayIdToThrottlingIdToBtd.put(uniqueDisplayId, throttlingMapForDisplay);
-            } else if (throttlingMapForDisplay.containsKey(throttlingDataId)) {
-                Slog.e(TAG, "Throttling data for display " + uniqueDisplayId
-                        + "contains duplicate throttling ids: '" + throttlingDataId + "'");
-                return false;
-            } else {
-                throttlingMapForDisplay.put(throttlingDataId, throttlingLevelsData);
-            }
-        } catch (NumberFormatException | IndexOutOfBoundsException
-                | UnknownThermalStatusException e) {
-            Slog.e(TAG, "Throttling data is invalid array: '" + strArray + "'", e);
-            validConfig = false;
-        }
-
-        if (i != items.length) {
-            validConfig = false;
-        }
-        return validConfig;
-    }
-
     private void loadThermalBrightnessThrottlingDataFromDeviceConfig() {
-        HashMap<String, HashMap<String, ThermalBrightnessThrottlingData>> tempThrottlingData =
-                new HashMap<>(1);
-        mThermalBrightnessThrottlingDataString = getThermalBrightnessThrottlingDataString();
-        boolean validConfig = true;
+        mThermalBrightnessThrottlingDataString =
+                mConfigParameterProvider.getBrightnessThrottlingData();
         mThermalBrightnessThrottlingDataOverride.clear();
         if (mThermalBrightnessThrottlingDataString != null) {
-            String[] throttlingDataSplits = mThermalBrightnessThrottlingDataString.split(";");
-            for (String s : throttlingDataSplits) {
-                if (!parseAndAddData(s, tempThrottlingData)) {
-                    validConfig = false;
-                    break;
-                }
-            }
-
-            if (validConfig) {
-                mThermalBrightnessThrottlingDataOverride.putAll(tempThrottlingData);
-                tempThrottlingData.clear();
-            }
-
+            Map<String, Map<String, ThermalBrightnessThrottlingData>> tempThrottlingData =
+                    DeviceConfigParsingUtils.parseDeviceConfigMap(
+                    mThermalBrightnessThrottlingDataString, mDataPointMapper, mDataSetMapper);
+            mThermalBrightnessThrottlingDataOverride.putAll(tempThrottlingData);
         } else {
             Slog.w(TAG, "DeviceConfig ThermalBrightnessThrottlingData is null");
         }
@@ -390,8 +341,7 @@
         public Executor mExecutor = new HandlerExecutor(mDeviceConfigHandler);
 
         public void startListening() {
-            mDeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
-                    mExecutor, this);
+            mConfigParameterProvider.addOnPropertiesChangedListener(mExecutor, this);
         }
 
         @Override
@@ -401,42 +351,6 @@
         }
     }
 
-    private float parseBrightness(String intVal) throws NumberFormatException {
-        float value = Float.parseFloat(intVal);
-        if (value < PowerManager.BRIGHTNESS_MIN || value > PowerManager.BRIGHTNESS_MAX) {
-            throw new NumberFormatException("Brightness constraint value out of bounds.");
-        }
-        return value;
-    }
-
-    @PowerManager.ThermalStatus private int parseThermalStatus(@NonNull String value)
-            throws UnknownThermalStatusException {
-        switch (value) {
-            case "none":
-                return PowerManager.THERMAL_STATUS_NONE;
-            case "light":
-                return PowerManager.THERMAL_STATUS_LIGHT;
-            case "moderate":
-                return PowerManager.THERMAL_STATUS_MODERATE;
-            case "severe":
-                return PowerManager.THERMAL_STATUS_SEVERE;
-            case "critical":
-                return PowerManager.THERMAL_STATUS_CRITICAL;
-            case "emergency":
-                return PowerManager.THERMAL_STATUS_EMERGENCY;
-            case "shutdown":
-                return PowerManager.THERMAL_STATUS_SHUTDOWN;
-            default:
-                throw new UnknownThermalStatusException("Invalid Thermal Status: " + value);
-        }
-    }
-
-    private static class UnknownThermalStatusException extends Exception {
-        UnknownThermalStatusException(String message) {
-            super(message);
-        }
-    }
-
     private final class SkinThermalStatusObserver extends IThermalEventListener.Stub {
         private final Injector mInjector;
         private final Handler mHandler;
diff --git a/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java b/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java
index f84a58c..472c1f5 100644
--- a/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java
+++ b/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java
@@ -22,7 +22,6 @@
 import android.util.IndentingPrintWriter;
 import android.util.Slog;
 import android.util.SparseArray;
-import android.view.Display;
 import android.view.DisplayAddress;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -38,6 +37,7 @@
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.math.BigInteger;
 
 import javax.xml.datatype.DatatypeConfigurationException;
 
@@ -115,13 +115,16 @@
                 Slog.i(TAG, "Display layout config not found: " + configFile);
                 return;
             }
-            int leadDisplayId = Display.DEFAULT_DISPLAY;
             for (com.android.server.display.config.layout.Layout l : layouts.getLayout()) {
                 final int state = l.getState().intValue();
                 final Layout layout = createLayout(state);
                 for (com.android.server.display.config.layout.Display d: l.getDisplay()) {
                     assert layout != null;
                     int position = getPosition(d.getPosition());
+                    BigInteger leadDisplayPhysicalId = d.getLeadDisplayAddress();
+                    DisplayAddress leadDisplayAddress = leadDisplayPhysicalId == null ? null
+                            : DisplayAddress.fromPhysicalDisplayId(
+                                    leadDisplayPhysicalId.longValue());
                     layout.createDisplayLocked(
                             DisplayAddress.fromPhysicalDisplayId(d.getAddress().longValue()),
                             d.isDefaultDisplay(),
@@ -129,11 +132,12 @@
                             d.getDisplayGroup(),
                             mIdProducer,
                             position,
-                            leadDisplayId,
+                            leadDisplayAddress,
                             d.getBrightnessThrottlingMapId(),
                             d.getRefreshRateZoneId(),
                             d.getRefreshRateThermalThrottlingMapId());
                 }
+                layout.postProcessLocked();
             }
         } catch (IOException | DatatypeConfigurationException | XmlPullParserException e) {
             Slog.e(TAG, "Encountered an error while reading/parsing display layout config file: "
diff --git a/services/core/java/com/android/server/display/DisplayBrightnessState.java b/services/core/java/com/android/server/display/DisplayBrightnessState.java
index e27182f..dd5afa2 100644
--- a/services/core/java/com/android/server/display/DisplayBrightnessState.java
+++ b/services/core/java/com/android/server/display/DisplayBrightnessState.java
@@ -16,6 +16,8 @@
 
 package com.android.server.display;
 
+import android.text.TextUtils;
+
 import com.android.server.display.brightness.BrightnessReason;
 
 import java.util.Objects;
@@ -29,12 +31,14 @@
     private final float mSdrBrightness;
     private final BrightnessReason mBrightnessReason;
     private final String mDisplayBrightnessStrategyName;
+    private final boolean mShouldUseAutoBrightness;
 
     private DisplayBrightnessState(Builder builder) {
-        this.mBrightness = builder.getBrightness();
-        this.mSdrBrightness = builder.getSdrBrightness();
-        this.mBrightnessReason = builder.getBrightnessReason();
-        this.mDisplayBrightnessStrategyName = builder.getDisplayBrightnessStrategyName();
+        mBrightness = builder.getBrightness();
+        mSdrBrightness = builder.getSdrBrightness();
+        mBrightnessReason = builder.getBrightnessReason();
+        mDisplayBrightnessStrategyName = builder.getDisplayBrightnessStrategyName();
+        mShouldUseAutoBrightness = builder.getShouldUseAutoBrightness();
     }
 
     /**
@@ -66,6 +70,13 @@
         return mDisplayBrightnessStrategyName;
     }
 
+    /**
+     * @return {@code true} if the device is set up to run auto-brightness.
+     */
+    public boolean getShouldUseAutoBrightness() {
+        return mShouldUseAutoBrightness;
+    }
+
     @Override
     public String toString() {
         StringBuilder stringBuilder = new StringBuilder("DisplayBrightnessState:");
@@ -75,6 +86,8 @@
         stringBuilder.append(getSdrBrightness());
         stringBuilder.append("\n    brightnessReason:");
         stringBuilder.append(getBrightnessReason());
+        stringBuilder.append("\n    shouldUseAutoBrightness:");
+        stringBuilder.append(getShouldUseAutoBrightness());
         return stringBuilder.toString();
     }
 
@@ -91,28 +104,20 @@
             return false;
         }
 
-        DisplayBrightnessState
-                displayBrightnessState = (DisplayBrightnessState) other;
+        DisplayBrightnessState otherState = (DisplayBrightnessState) other;
 
-        if (mBrightness != displayBrightnessState.getBrightness()) {
-            return false;
-        }
-        if (mSdrBrightness != displayBrightnessState.getSdrBrightness()) {
-            return false;
-        }
-        if (!mBrightnessReason.equals(displayBrightnessState.getBrightnessReason())) {
-            return false;
-        }
-        if (!mDisplayBrightnessStrategyName.equals(
-                displayBrightnessState.getDisplayBrightnessStrategyName())) {
-            return false;
-        }
-        return true;
+        return mBrightness == otherState.getBrightness()
+                && mSdrBrightness == otherState.getSdrBrightness()
+                && mBrightnessReason.equals(otherState.getBrightnessReason())
+                && TextUtils.equals(mDisplayBrightnessStrategyName,
+                        otherState.getDisplayBrightnessStrategyName())
+                && mShouldUseAutoBrightness == otherState.getShouldUseAutoBrightness();
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(mBrightness, mSdrBrightness, mBrightnessReason);
+        return Objects.hash(
+                mBrightness, mSdrBrightness, mBrightnessReason, mShouldUseAutoBrightness);
     }
 
     /**
@@ -123,6 +128,23 @@
         private float mSdrBrightness;
         private BrightnessReason mBrightnessReason = new BrightnessReason();
         private String mDisplayBrightnessStrategyName;
+        private boolean mShouldUseAutoBrightness;
+
+        /**
+         * Create a builder starting with the values from the specified {@link
+         * DisplayBrightnessState}.
+         *
+         * @param state The state from which to initialize.
+         */
+        public static Builder from(DisplayBrightnessState state) {
+            Builder builder = new Builder();
+            builder.setBrightness(state.getBrightness());
+            builder.setSdrBrightness(state.getSdrBrightness());
+            builder.setBrightnessReason(state.getBrightnessReason());
+            builder.setDisplayBrightnessStrategyName(state.getDisplayBrightnessStrategyName());
+            builder.setShouldUseAutoBrightness(state.getShouldUseAutoBrightness());
+            return builder;
+        }
 
         /**
          * Gets the brightness
@@ -200,6 +222,21 @@
         }
 
         /**
+         * See {@link DisplayBrightnessState#getShouldUseAutoBrightness}.
+         */
+        public Builder setShouldUseAutoBrightness(boolean shouldUseAutoBrightness) {
+            this.mShouldUseAutoBrightness = shouldUseAutoBrightness;
+            return this;
+        }
+
+        /**
+         * See {@link DisplayBrightnessState#getShouldUseAutoBrightness}.
+         */
+        public boolean getShouldUseAutoBrightness() {
+            return mShouldUseAutoBrightness;
+        }
+
+        /**
          * 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/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index 7a797dd..c25b253 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -41,6 +41,7 @@
 import com.android.internal.display.BrightnessSynchronizer;
 import com.android.server.display.config.AutoBrightness;
 import com.android.server.display.config.BlockingZoneConfig;
+import com.android.server.display.config.BrightnessLimitMap;
 import com.android.server.display.config.BrightnessThresholds;
 import com.android.server.display.config.BrightnessThrottlingMap;
 import com.android.server.display.config.BrightnessThrottlingPoint;
@@ -51,8 +52,11 @@
 import com.android.server.display.config.HbmTiming;
 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.NitsMap;
+import com.android.server.display.config.NonNegativeFloatToFloatPoint;
 import com.android.server.display.config.Point;
+import com.android.server.display.config.PredefinedBrightnessLimitNames;
 import com.android.server.display.config.RefreshRateConfigs;
 import com.android.server.display.config.RefreshRateRange;
 import com.android.server.display.config.RefreshRateThrottlingMap;
@@ -219,6 +223,22 @@
  *        <allowInLowPowerMode>false</allowInLowPowerMode>
  *      </highBrightnessMode>
  *
+ *      <luxThrottling>
+ *        <brightnessLimitMap>
+ *          <type>default</type>
+ *          <map>
+ *            <point>
+ *                <first>5000</first>
+ *                <second>0.3</second>
+ *            </point>
+ *            <point>
+ *               <first>5000</first>
+ *               <second>0.3</second>
+ *            </point>
+ *          </map>
+ *        </brightnessPeakMap>
+ *      </luxThrottling>
+ *
  *      <quirks>
  *       <quirk>canSetBrightnessViaHwc</quirk>
  *      </quirks>
@@ -438,7 +458,7 @@
 
     public static final String QUIRK_CAN_SET_BRIGHTNESS_VIA_HWC = "canSetBrightnessViaHwc";
 
-    static final String DEFAULT_ID = "default";
+    public static final String DEFAULT_ID = "default";
 
     private static final float BRIGHTNESS_DEFAULT = 0.5f;
     private static final String ETC_DIR = "etc";
@@ -495,7 +515,9 @@
     private final SensorData mScreenOffBrightnessSensor = new SensorData();
 
     // The details of the proximity sensor associated with this display.
-    private final SensorData mProximitySensor = new SensorData();
+    // Is null when no sensor should be used for that display
+    @Nullable
+    private SensorData mProximitySensor = new SensorData();
 
     private final List<RefreshRateLimitation> mRefreshRateLimitations =
             new ArrayList<>(2 /*initialCapacity*/);
@@ -693,6 +715,9 @@
     private final Map<String, SparseArray<SurfaceControl.RefreshRateRange>>
             mRefreshRateThrottlingMap = new HashMap<>();
 
+    private final Map<BrightnessLimitMapType, Map<Float, Float>>
+            mLuxThrottlingData = new HashMap<>();
+
     @Nullable
     private HostUsiVersion mHostUsiVersion;
 
@@ -1314,7 +1339,8 @@
         return mScreenOffBrightnessSensor;
     }
 
-    SensorData getProximitySensor() {
+    @Nullable
+    public SensorData getProximitySensor() {
         return mProximitySensor;
     }
 
@@ -1344,6 +1370,11 @@
         return hbmData;
     }
 
+    @NonNull
+    public Map<BrightnessLimitMapType, Map<Float, Float>> getLuxThrottlingData() {
+        return mLuxThrottlingData;
+    }
+
     public List<RefreshRateLimitation> getRefreshRateLimitations() {
         return mRefreshRateLimitations;
     }
@@ -1530,6 +1561,7 @@
                 + ", mBrightnessDefault=" + mBrightnessDefault
                 + ", mQuirks=" + mQuirks
                 + ", isHbmEnabled=" + mIsHighBrightnessModeEnabled
+                + ", mLuxThrottlingData=" + mLuxThrottlingData
                 + ", mHbmData=" + mHbmData
                 + ", mSdrToHdrRatioSpline=" + mSdrToHdrRatioSpline
                 + ", mThermalBrightnessThrottlingDataMapByThrottlingId="
@@ -1676,6 +1708,7 @@
                 loadBrightnessMap(config);
                 loadThermalThrottlingConfig(config);
                 loadHighBrightnessModeData(config);
+                loadLuxThrottling(config);
                 loadQuirks(config);
                 loadBrightnessRamps(config);
                 loadAmbientLightSensorFromDdc(config);
@@ -2428,6 +2461,54 @@
         }
     }
 
+    private void loadLuxThrottling(DisplayConfiguration config) {
+        LuxThrottling cfg = config.getLuxThrottling();
+        if (cfg != null) {
+            HighBrightnessMode hbm = config.getHighBrightnessMode();
+            float hbmTransitionPoint = hbm != null ? hbm.getTransitionPoint_all().floatValue()
+                    : PowerManager.BRIGHTNESS_MAX;
+            List<BrightnessLimitMap> limitMaps = cfg.getBrightnessLimitMap();
+            for (BrightnessLimitMap map : limitMaps) {
+                PredefinedBrightnessLimitNames type = map.getType();
+                BrightnessLimitMapType mappedType = BrightnessLimitMapType.convert(type);
+                if (mappedType == null) {
+                    Slog.wtf(TAG, "Invalid NBM config: unsupported map type=" + type);
+                    continue;
+                }
+                if (mLuxThrottlingData.containsKey(mappedType)) {
+                    Slog.wtf(TAG, "Invalid NBM config: duplicate map type=" + mappedType);
+                    continue;
+                }
+                Map<Float, Float> luxToTransitionPointMap = new HashMap<>();
+
+                List<NonNegativeFloatToFloatPoint> points = map.getMap().getPoint();
+                for (NonNegativeFloatToFloatPoint point : points) {
+                    float lux = point.getFirst().floatValue();
+                    float maxBrightness = point.getSecond().floatValue();
+                    if (maxBrightness > hbmTransitionPoint) {
+                        Slog.wtf(TAG,
+                                "Invalid NBM config: maxBrightness is greater than hbm"
+                                        + ".transitionPoint. type="
+                                        + type + "; lux=" + lux + "; maxBrightness="
+                                        + maxBrightness);
+                        continue;
+                    }
+                    if (luxToTransitionPointMap.containsKey(lux)) {
+                        Slog.wtf(TAG,
+                                "Invalid NBM config: duplicate lux key. type=" + type + "; lux="
+                                        + lux);
+                        continue;
+                    }
+                    luxToTransitionPointMap.put(lux,
+                            mBacklightToBrightnessSpline.interpolate(maxBrightness));
+                }
+                if (!luxToTransitionPointMap.isEmpty()) {
+                    mLuxThrottlingData.put(mappedType, luxToTransitionPointMap);
+                }
+            }
+        }
+    }
+
     private void loadBrightnessRamps(DisplayConfiguration config) {
         // Priority 1: Value in the display device config (float)
         // Priority 2: Value in the config.xml (int)
@@ -2485,46 +2566,51 @@
     private void loadAmbientLightSensorFromDdc(DisplayConfiguration config) {
         final SensorDetails sensorDetails = config.getLightSensor();
         if (sensorDetails != null) {
-            mAmbientLightSensor.type = sensorDetails.getType();
-            mAmbientLightSensor.name = sensorDetails.getName();
-            final RefreshRateRange rr = sensorDetails.getRefreshRate();
-            if (rr != null) {
-                mAmbientLightSensor.minRefreshRate = rr.getMinimum().floatValue();
-                mAmbientLightSensor.maxRefreshRate = rr.getMaximum().floatValue();
-            }
+            loadSensorData(sensorDetails, mAmbientLightSensor);
         } else {
             loadAmbientLightSensorFromConfigXml();
         }
     }
 
     private void setProxSensorUnspecified() {
-        mProximitySensor.name = null;
-        mProximitySensor.type = null;
+        mProximitySensor = new SensorData();
     }
 
     private void loadScreenOffBrightnessSensorFromDdc(DisplayConfiguration config) {
         final SensorDetails sensorDetails = config.getScreenOffBrightnessSensor();
         if (sensorDetails != null) {
-            mScreenOffBrightnessSensor.type = sensorDetails.getType();
-            mScreenOffBrightnessSensor.name = sensorDetails.getName();
+            loadSensorData(sensorDetails, mScreenOffBrightnessSensor);
         }
     }
 
     private void loadProxSensorFromDdc(DisplayConfiguration config) {
         SensorDetails sensorDetails = config.getProxSensor();
         if (sensorDetails != null) {
-            mProximitySensor.name = sensorDetails.getName();
-            mProximitySensor.type = sensorDetails.getType();
-            final RefreshRateRange rr = sensorDetails.getRefreshRate();
-            if (rr != null) {
-                mProximitySensor.minRefreshRate = rr.getMinimum().floatValue();
-                mProximitySensor.maxRefreshRate = rr.getMaximum().floatValue();
+            String name = sensorDetails.getName();
+            String type = sensorDetails.getType();
+            if ("".equals(name) && "".equals(type)) {
+                // <proxSensor> with empty values to the config means no sensor should be used
+                mProximitySensor = null;
+            } else {
+                mProximitySensor = new SensorData();
+                loadSensorData(sensorDetails, mProximitySensor);
             }
         } else {
             setProxSensorUnspecified();
         }
     }
 
+    private void loadSensorData(@NonNull SensorDetails sensorDetails,
+            @NonNull SensorData sensorData) {
+        sensorData.name = sensorDetails.getName();
+        sensorData.type = sensorDetails.getType();
+        final RefreshRateRange rr = sensorDetails.getRefreshRate();
+        if (rr != null) {
+            sensorData.minRefreshRate = rr.getMinimum().floatValue();
+            sensorData.maxRefreshRate = rr.getMaximum().floatValue();
+        }
+    }
+
     private void loadBrightnessChangeThresholdsFromXml() {
         loadBrightnessChangeThresholds(/* config= */ null);
     }
@@ -3041,11 +3127,15 @@
     public static class ThermalBrightnessThrottlingData {
         public List<ThrottlingLevel> throttlingLevels;
 
-        static class ThrottlingLevel {
+        /**
+         * thermal status to brightness cap holder
+         */
+        public static class ThrottlingLevel {
             public @PowerManager.ThermalStatus int thermalStatus;
             public float brightness;
 
-            ThrottlingLevel(@PowerManager.ThermalStatus int thermalStatus, float brightness) {
+            public ThrottlingLevel(
+                    @PowerManager.ThermalStatus int thermalStatus, float brightness) {
                 this.thermalStatus = thermalStatus;
                 this.brightness = brightness;
             }
@@ -3155,4 +3245,19 @@
             }
         }
     }
+
+    public enum BrightnessLimitMapType {
+        DEFAULT, ADAPTIVE;
+
+        @Nullable
+        private static BrightnessLimitMapType convert(PredefinedBrightnessLimitNames type) {
+            switch (type) {
+                case _default:
+                    return DEFAULT;
+                case adaptive:
+                    return ADAPTIVE;
+            }
+            return null;
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index da8eb23..3a0b8b5 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -24,7 +24,6 @@
 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED;
 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE;
 import static android.hardware.display.DisplayManager.EventsMask;
-import static android.hardware.display.DisplayManager.HDR_OUTPUT_CONTROL_FLAG;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD;
@@ -42,7 +41,6 @@
 import static android.hardware.display.HdrConversionMode.HDR_CONVERSION_UNSUPPORTED;
 import static android.os.Process.FIRST_APPLICATION_UID;
 import static android.os.Process.ROOT_UID;
-import static android.provider.DeviceConfig.NAMESPACE_DISPLAY_MANAGER;
 
 import android.Manifest;
 import android.annotation.NonNull;
@@ -116,7 +114,7 @@
 import android.os.Trace;
 import android.os.UserHandle;
 import android.os.UserManager;
-import android.provider.DeviceConfig;
+import android.provider.DeviceConfigInterface;
 import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.ArraySet;
@@ -152,10 +150,12 @@
 import com.android.server.UiThread;
 import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
 import com.android.server.display.DisplayDeviceConfig.SensorData;
+import com.android.server.display.feature.DeviceConfigParameterProvider;
 import com.android.server.display.layout.Layout;
 import com.android.server.display.mode.DisplayModeDirector;
 import com.android.server.display.utils.SensorUtils;
 import com.android.server.input.InputManagerInternal;
+import com.android.server.utils.FoldSettingWrapper;
 import com.android.server.wm.SurfaceAnimationThread;
 import com.android.server.wm.WindowManagerInternal;
 
@@ -506,6 +506,8 @@
 
     private final BrightnessSynchronizer mBrightnessSynchronizer;
 
+    private final DeviceConfigParameterProvider mConfigParameterProvider;
+
     /**
      * Applications use {@link android.view.Display#getRefreshRate} and
      * {@link android.view.Display.Mode#getRefreshRate} to know what is the display refresh rate.
@@ -539,7 +541,8 @@
         mUiHandler = UiThread.getHandler();
         mDisplayDeviceRepo = new DisplayDeviceRepository(mSyncRoot, mPersistentDataStore);
         mLogicalDisplayMapper = new LogicalDisplayMapper(mContext, mDisplayDeviceRepo,
-                new LogicalDisplayListener(), mSyncRoot, mHandler);
+                new LogicalDisplayListener(), mSyncRoot, mHandler,
+                new FoldSettingWrapper(mContext.getContentResolver()));
         mDisplayModeDirector = new DisplayModeDirector(context, mHandler);
         mBrightnessSynchronizer = new BrightnessSynchronizer(mContext);
         Resources resources = mContext.getResources();
@@ -558,6 +561,7 @@
         mWideColorSpace = colorSpaces[1];
         mOverlayProperties = SurfaceControl.getOverlaySupport();
         mSystemReady = false;
+        mConfigParameterProvider = new DeviceConfigParameterProvider(DeviceConfigInterface.REAL);
     }
 
     public void setupSchedulerPolicies() {
@@ -653,6 +657,12 @@
                             logicalDisplay.getPrimaryDisplayDeviceLocked().getUniqueId(),
                             userSerial);
                     dpc.setBrightnessConfiguration(config, /* shouldResetShortTermModel= */ true);
+                    // change the brightness value according to the selected user.
+                    final DisplayDevice device = logicalDisplay.getPrimaryDisplayDeviceLocked();
+                    if (device != null) {
+                        dpc.setBrightness(
+                                mPersistentDataStore.getBrightness(device, userSerial), userSerial);
+                    }
                 }
                 dpc.onSwitchUser(newUserId);
             });
@@ -688,11 +698,11 @@
         synchronized (mSyncRoot) {
             mSafeMode = safeMode;
             mSystemReady = true;
-            mIsHdrOutputControlEnabled = isDeviceConfigHdrOutputControlEnabled();
-            DeviceConfig.addOnPropertiesChangedListener(NAMESPACE_DISPLAY_MANAGER,
-                    BackgroundThread.getExecutor(),
+            mIsHdrOutputControlEnabled =
+                    mConfigParameterProvider.isHdrOutputControlFeatureEnabled();
+            mConfigParameterProvider.addOnPropertiesChangedListener(BackgroundThread.getExecutor(),
                     properties -> mIsHdrOutputControlEnabled =
-                            isDeviceConfigHdrOutputControlEnabled());
+                            mConfigParameterProvider.isHdrOutputControlFeatureEnabled());
             // Just in case the top inset changed before the system was ready. At this point, any
             // relevant configuration should be in place.
             recordTopInsetLocked(mLogicalDisplayMapper.getDisplayLocked(Display.DEFAULT_DISPLAY));
@@ -723,12 +733,6 @@
         mContext.registerReceiver(mIdleModeReceiver, filter);
     }
 
-    private boolean isDeviceConfigHdrOutputControlEnabled() {
-        return DeviceConfig.getBoolean(NAMESPACE_DISPLAY_MANAGER,
-                HDR_OUTPUT_CONTROL_FLAG,
-                true);
-    }
-
     @VisibleForTesting
     Handler getDisplayHandler() {
         return mHandler;
@@ -2004,20 +2008,7 @@
         final Point userPreferredResolution =
                 mPersistentDataStore.getUserPreferredResolution(device);
         final float refreshRate = mPersistentDataStore.getUserPreferredRefreshRate(device);
-        // If value in persistentDataStore is null, preserving the mode from systemPreferredMode.
-        // This is required because in some devices, user-preferred mode was not stored in
-        // persistentDataStore, but was stored in a config which is returned through
-        // systemPreferredMode.
-        if ((userPreferredResolution == null && Float.isNaN(refreshRate))
-                || (userPreferredResolution.equals(0, 0) && refreshRate == 0.0f)) {
-            Display.Mode systemPreferredMode = device.getSystemPreferredDisplayModeLocked();
-            if (systemPreferredMode == null) {
-                return;
-            }
-            storeModeInPersistentDataStoreLocked(
-                    display.getDisplayIdLocked(), systemPreferredMode.getPhysicalWidth(),
-                    systemPreferredMode.getPhysicalHeight(), systemPreferredMode.getRefreshRate());
-            device.setUserPreferredDisplayModeLocked(systemPreferredMode);
+        if (userPreferredResolution == null && Float.isNaN(refreshRate)) {
             return;
         }
         Display.Mode.Builder modeBuilder = new Display.Mode.Builder();
@@ -3135,8 +3126,9 @@
             mBrightnessTracker = new BrightnessTracker(mContext, null);
         }
 
-        final BrightnessSetting brightnessSetting = new BrightnessSetting(mPersistentDataStore,
-                display, mSyncRoot);
+        final int userSerial = getUserManager().getUserSerialNumber(mContext.getUserId());
+        final BrightnessSetting brightnessSetting = new BrightnessSetting(userSerial,
+                mPersistentDataStore, display, mSyncRoot);
         final DisplayPowerControllerInterface displayPowerController;
 
         // If display is internal and has a HighBrightnessModeMetadata mapping, use that.
@@ -3151,8 +3143,7 @@
                     + "display: " + display.getDisplayIdLocked());
             return null;
         }
-        if (DeviceConfig.getBoolean("display_manager",
-                "use_newly_structured_display_power_controller", true)) {
+        if (mConfigParameterProvider.isNewPowerControllerFeatureEnabled()) {
             displayPowerController = new DisplayPowerController2(
                     mContext, /* injector= */ null, mDisplayPowerCallbacks, mPowerHandler,
                     mSensorManager, mDisplayBlanker, display, mBrightnessTracker, brightnessSetting,
@@ -3502,10 +3493,10 @@
             }
         }
 
+        @android.annotation.EnforcePermission(android.Manifest.permission.CONFIGURE_WIFI_DISPLAY)
         @Override // Binder call
         public void startWifiDisplayScan() {
-            mContext.enforceCallingOrSelfPermission(Manifest.permission.CONFIGURE_WIFI_DISPLAY,
-                    "Permission required to start wifi display scans");
+            startWifiDisplayScan_enforcePermission();
 
             final int callingPid = Binder.getCallingPid();
             final long token = Binder.clearCallingIdentity();
@@ -3516,10 +3507,10 @@
             }
         }
 
+        @android.annotation.EnforcePermission(android.Manifest.permission.CONFIGURE_WIFI_DISPLAY)
         @Override // Binder call
         public void stopWifiDisplayScan() {
-            mContext.enforceCallingOrSelfPermission(Manifest.permission.CONFIGURE_WIFI_DISPLAY,
-                    "Permission required to stop wifi display scans");
+            stopWifiDisplayScan_enforcePermission();
 
             final int callingPid = Binder.getCallingPid();
             final long token = Binder.clearCallingIdentity();
@@ -3593,10 +3584,10 @@
             }
         }
 
+        @android.annotation.EnforcePermission(android.Manifest.permission.CONFIGURE_WIFI_DISPLAY)
         @Override // Binder call
         public void pauseWifiDisplay() {
-            mContext.enforceCallingOrSelfPermission(Manifest.permission.CONFIGURE_WIFI_DISPLAY,
-                    "Permission required to pause a wifi display session");
+            pauseWifiDisplay_enforcePermission();
 
             final long token = Binder.clearCallingIdentity();
             try {
@@ -3606,10 +3597,10 @@
             }
         }
 
+        @android.annotation.EnforcePermission(android.Manifest.permission.CONFIGURE_WIFI_DISPLAY)
         @Override // Binder call
         public void resumeWifiDisplay() {
-            mContext.enforceCallingOrSelfPermission(Manifest.permission.CONFIGURE_WIFI_DISPLAY,
-                    "Permission required to resume a wifi display session");
+            resumeWifiDisplay_enforcePermission();
 
             final long token = Binder.clearCallingIdentity();
             try {
@@ -3632,11 +3623,10 @@
             }
         }
 
+        @android.annotation.EnforcePermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
         @Override // Binder call
         public void setUserDisabledHdrTypes(int[] userDisabledFormats) {
-            mContext.enforceCallingOrSelfPermission(
-                    Manifest.permission.WRITE_SECURE_SETTINGS,
-                    "Permission required to write the user settings.");
+            setUserDisabledHdrTypes_enforcePermission();
 
             final long token = Binder.clearCallingIdentity();
             try {
@@ -3659,11 +3649,10 @@
             DisplayControl.overrideHdrTypes(displayToken, modes);
         }
 
+        @android.annotation.EnforcePermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
         @Override // Binder call
         public void setAreUserDisabledHdrTypesAllowed(boolean areUserDisabledHdrTypesAllowed) {
-            mContext.enforceCallingOrSelfPermission(
-                    Manifest.permission.WRITE_SECURE_SETTINGS,
-                    "Permission required to write the user settings.");
+            setAreUserDisabledHdrTypesAllowed_enforcePermission();
             final long token = Binder.clearCallingIdentity();
             try {
                 setAreUserDisabledHdrTypesAllowedInternal(areUserDisabledHdrTypesAllowed);
@@ -3686,11 +3675,10 @@
             }
         }
 
+        @android.annotation.EnforcePermission(android.Manifest.permission.CONFIGURE_DISPLAY_COLOR_MODE)
         @Override // Binder call
         public void requestColorMode(int displayId, int colorMode) {
-            mContext.enforceCallingOrSelfPermission(
-                    Manifest.permission.CONFIGURE_DISPLAY_COLOR_MODE,
-                    "Permission required to change the display color mode");
+            requestColorMode_enforcePermission();
             final long token = Binder.clearCallingIdentity();
             try {
                 requestColorModeInternal(displayId, colorMode);
@@ -3767,11 +3755,10 @@
             }
         }
 
+        @android.annotation.EnforcePermission(android.Manifest.permission.BRIGHTNESS_SLIDER_USAGE)
         @Override // Binder call
         public ParceledListSlice<BrightnessChangeEvent> getBrightnessEvents(String callingPackage) {
-            mContext.enforceCallingOrSelfPermission(
-                    Manifest.permission.BRIGHTNESS_SLIDER_USAGE,
-                    "Permission to read brightness events.");
+            getBrightnessEvents_enforcePermission();
 
             final int callingUid = Binder.getCallingUid();
             AppOpsManager appOpsManager = mContext.getSystemService(AppOpsManager.class);
@@ -3800,11 +3787,10 @@
             }
         }
 
+        @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_AMBIENT_LIGHT_STATS)
         @Override // Binder call
         public ParceledListSlice<AmbientBrightnessDayStats> getAmbientBrightnessStats() {
-            mContext.enforceCallingOrSelfPermission(
-                    Manifest.permission.ACCESS_AMBIENT_LIGHT_STATS,
-                    "Permission required to to access ambient light stats.");
+            getAmbientBrightnessStats_enforcePermission();
             final int callingUid = Binder.getCallingUid();
             final int userId = UserHandle.getUserId(callingUid);
             final long token = Binder.clearCallingIdentity();
@@ -3818,12 +3804,11 @@
             }
         }
 
+        @android.annotation.EnforcePermission(android.Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS)
         @Override // Binder call
         public void setBrightnessConfigurationForUser(
                 BrightnessConfiguration c, @UserIdInt int userId, String packageName) {
-            mContext.enforceCallingOrSelfPermission(
-                    Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS,
-                    "Permission required to change the display's brightness configuration");
+            setBrightnessConfigurationForUser_enforcePermission();
             if (userId != UserHandle.getCallingUserId()) {
                 mContext.enforceCallingOrSelfPermission(
                         Manifest.permission.INTERACT_ACROSS_USERS,
@@ -3848,12 +3833,11 @@
             }
         }
 
+        @android.annotation.EnforcePermission(android.Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS)
         @Override // Binder call
         public void setBrightnessConfigurationForDisplay(BrightnessConfiguration c,
                 String uniqueId, int userId, String packageName) {
-            mContext.enforceCallingOrSelfPermission(
-                    Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS,
-                    "Permission required to change the display's brightness configuration");
+            setBrightnessConfigurationForDisplay_enforcePermission();
             if (userId != UserHandle.getCallingUserId()) {
                 mContext.enforceCallingOrSelfPermission(
                         Manifest.permission.INTERACT_ACROSS_USERS,
@@ -3868,12 +3852,11 @@
             }
         }
 
+        @android.annotation.EnforcePermission(android.Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS)
         @Override // Binder call
         public BrightnessConfiguration getBrightnessConfigurationForDisplay(String uniqueId,
                 int userId) {
-            mContext.enforceCallingOrSelfPermission(
-                    Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS,
-                    "Permission required to read the display's brightness configuration");
+            getBrightnessConfigurationForDisplay_enforcePermission();
             if (userId != UserHandle.getCallingUserId()) {
                 mContext.enforceCallingOrSelfPermission(
                         Manifest.permission.INTERACT_ACROSS_USERS,
@@ -3917,11 +3900,10 @@
 
         }
 
+        @android.annotation.EnforcePermission(android.Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS)
         @Override // Binder call
         public BrightnessConfiguration getDefaultBrightnessConfiguration() {
-            mContext.enforceCallingOrSelfPermission(
-                    Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS,
-                    "Permission required to read the display's default brightness configuration");
+            getDefaultBrightnessConfiguration_enforcePermission();
             final long token = Binder.clearCallingIdentity();
             try {
                 synchronized (mSyncRoot) {
@@ -3933,11 +3915,10 @@
             }
         }
 
+        @android.annotation.EnforcePermission(android.Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS)
         @Override
         public BrightnessInfo getBrightnessInfo(int displayId) {
-            mContext.enforceCallingOrSelfPermission(
-                    Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS,
-                    "Permission required to read the display's brightness info.");
+            getBrightnessInfo_enforcePermission();
             final long token = Binder.clearCallingIdentity();
             try {
                 synchronized (mSyncRoot) {
@@ -3965,11 +3946,10 @@
             }
         }
 
+        @android.annotation.EnforcePermission(android.Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS)
         @Override // Binder call
         public void setTemporaryBrightness(int displayId, float brightness) {
-            mContext.enforceCallingOrSelfPermission(
-                    Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS,
-                    "Permission required to set the display's brightness");
+            setTemporaryBrightness_enforcePermission();
             final long token = Binder.clearCallingIdentity();
             try {
                 synchronized (mSyncRoot) {
@@ -3981,11 +3961,10 @@
             }
         }
 
+        @android.annotation.EnforcePermission(android.Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS)
         @Override // Binder call
         public void setBrightness(int displayId, float brightness) {
-            mContext.enforceCallingOrSelfPermission(
-                    Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS,
-                    "Permission required to set the display's brightness");
+            setBrightness_enforcePermission();
             if (!isValidBrightness(brightness)) {
                 Slog.w(TAG, "Attempted to set invalid brightness" + brightness);
                 return;
@@ -4024,11 +4003,10 @@
             return brightness;
         }
 
+        @android.annotation.EnforcePermission(android.Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS)
         @Override // Binder call
         public void setTemporaryAutoBrightnessAdjustment(float adjustment) {
-            mContext.enforceCallingOrSelfPermission(
-                    Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS,
-                    "Permission required to set the display's auto brightness adjustment");
+            setTemporaryAutoBrightnessAdjustment_enforcePermission();
             final long token = Binder.clearCallingIdentity();
             try {
                 synchronized (mSyncRoot) {
@@ -4068,11 +4046,10 @@
             }
         }
 
+        @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_USER_PREFERRED_DISPLAY_MODE)
         @Override // Binder call
         public void setUserPreferredDisplayMode(int displayId, Display.Mode mode) {
-            mContext.enforceCallingOrSelfPermission(
-                    Manifest.permission.MODIFY_USER_PREFERRED_DISPLAY_MODE,
-                    "Permission required to set the user preferred display mode.");
+            setUserPreferredDisplayMode_enforcePermission();
             final long token = Binder.clearCallingIdentity();
             try {
                 setUserPreferredDisplayModeInternal(displayId, mode);
@@ -4157,11 +4134,10 @@
             }
         }
 
+        @android.annotation.EnforcePermission(android.Manifest.permission.OVERRIDE_DISPLAY_MODE_REQUESTS)
         @Override // Binder call
         public void setShouldAlwaysRespectAppRequestedMode(boolean enabled) {
-            mContext.enforceCallingOrSelfPermission(
-                    Manifest.permission.OVERRIDE_DISPLAY_MODE_REQUESTS,
-                    "Permission required to override display mode requests.");
+            setShouldAlwaysRespectAppRequestedMode_enforcePermission();
             final long token = Binder.clearCallingIdentity();
             try {
                 setShouldAlwaysRespectAppRequestedModeInternal(enabled);
@@ -4170,11 +4146,10 @@
             }
         }
 
+        @android.annotation.EnforcePermission(android.Manifest.permission.OVERRIDE_DISPLAY_MODE_REQUESTS)
         @Override // Binder call
         public boolean shouldAlwaysRespectAppRequestedMode() {
-            mContext.enforceCallingOrSelfPermission(
-                    Manifest.permission.OVERRIDE_DISPLAY_MODE_REQUESTS,
-                    "Permission required to override display mode requests.");
+            shouldAlwaysRespectAppRequestedMode_enforcePermission();
             final long token = Binder.clearCallingIdentity();
             try {
                 return shouldAlwaysRespectAppRequestedModeInternal();
@@ -4183,11 +4158,10 @@
             }
         }
 
+        @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_REFRESH_RATE_SWITCHING_TYPE)
         @Override // Binder call
         public void setRefreshRateSwitchingType(int newValue) {
-            mContext.enforceCallingOrSelfPermission(
-                    Manifest.permission.MODIFY_REFRESH_RATE_SWITCHING_TYPE,
-                    "Permission required to modify refresh rate switching type.");
+            setRefreshRateSwitchingType_enforcePermission();
             final long token = Binder.clearCallingIdentity();
             try {
                 setRefreshRateSwitchingTypeInternal(newValue);
@@ -4253,6 +4227,13 @@
     }
 
     @VisibleForTesting
+    void overrideSensorManager(SensorManager sensorManager) {
+        synchronized (mSyncRoot) {
+            mSensorManager = sensorManager;
+        }
+    }
+
+    @VisibleForTesting
     final class LocalService extends DisplayManagerInternal {
 
         @Override
@@ -4505,7 +4486,7 @@
                 }
                 final DisplayDeviceConfig config = device.getDisplayDeviceConfig();
                 SensorData sensorData = config.getProximitySensor();
-                if (sensorData.matches(sensorName, sensorType)) {
+                if (sensorData != null && sensorData.matches(sensorName, sensorType)) {
                     return new RefreshRateRange(sensorData.minRefreshRate,
                             sensorData.maxRefreshRate);
                 }
@@ -4621,6 +4602,22 @@
         }
 
         @Override
+        public AmbientLightSensorData getAmbientLightSensorData(int displayId) {
+            synchronized (mSyncRoot) {
+                final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(displayId);
+                if (display == null) {
+                    return null;
+                }
+                final DisplayDevice device = display.getPrimaryDisplayDeviceLocked();
+                if (device == null) {
+                    return null;
+                }
+                SensorData data = device.getDisplayDeviceConfig().getAmbientLightSensor();
+                return new AmbientLightSensorData(data.name, data.type);
+            }
+        }
+
+        @Override
         public IntArray getDisplayGroupIds() {
             Set<Integer> visitedIds = new ArraySet<>();
             IntArray displayGroupIds = new IntArray();
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 9d31572..d19e78d 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -141,6 +141,9 @@
     private static final int MSG_STATSD_HBM_BRIGHTNESS = 13;
     private static final int MSG_SWITCH_USER = 14;
     private static final int MSG_BOOT_COMPLETED = 15;
+    private static final int MSG_SET_DWBC_STRONG_MODE = 16;
+    private static final int MSG_SET_DWBC_COLOR_OVERRIDE = 17;
+    private static final int MSG_SET_DWBC_LOGGING_ENABLED = 18;
 
     private static final int PROXIMITY_UNKNOWN = -1;
     private static final int PROXIMITY_NEGATIVE = 0;
@@ -436,6 +439,7 @@
     private final boolean mSkipScreenOnBrightnessRamp;
 
     // Display white balance components.
+    // Critical methods must be called on DPC handler thread.
     @Nullable
     private final DisplayWhiteBalanceSettings mDisplayWhiteBalanceSettings;
     @Nullable
@@ -445,7 +449,7 @@
     private final ColorDisplayServiceInternal mCdsi;
     private float[] mNitsRange;
 
-    private final HighBrightnessModeController mHbmController;
+    private final BrightnessRangeController mBrightnessRangeController;
     private final HighBrightnessModeMetadata mHighBrightnessModeMetadata;
 
     private final BrightnessThrottler mBrightnessThrottler;
@@ -512,6 +516,9 @@
     // of the lead display that this DPC should follow.
     private float mBrightnessToFollow;
 
+    // Indicates whether we should ramp slowly to the brightness value to follow.
+    private boolean mBrightnessToFollowSlowChange;
+
     // The last auto brightness adjustment that was set by the user and not temporary. Set to
     // Float.NaN when an auto-brightness adjustment hasn't been recorded yet.
     private float mAutoBrightnessAdjustment;
@@ -654,8 +661,19 @@
         loadBrightnessRampRates();
         mSkipScreenOnBrightnessRamp = resources.getBoolean(
                 com.android.internal.R.bool.config_skipScreenOnBrightnessRamp);
+        Runnable modeChangeCallback = () -> {
+            sendUpdatePowerState();
+            postBrightnessChangeRunnable();
+            // TODO(b/192258832): Switch the HBMChangeCallback to a listener pattern.
+            if (mAutomaticBrightnessController != null) {
+                mAutomaticBrightnessController.update();
+            }
+        };
 
-        mHbmController = createHbmControllerLocked();
+        HighBrightnessModeController hbmController = createHbmControllerLocked(modeChangeCallback);
+
+        mBrightnessRangeController = new BrightnessRangeController(hbmController,
+                modeChangeCallback);
 
         mBrightnessThrottler = createBrightnessThrottlerLocked();
 
@@ -666,9 +684,9 @@
         DisplayWhiteBalanceController displayWhiteBalanceController = null;
         if (mDisplayId == Display.DEFAULT_DISPLAY) {
             try {
+                displayWhiteBalanceController = injector.getDisplayWhiteBalanceController(
+                        mHandler, mSensorManager, resources);
                 displayWhiteBalanceSettings = new DisplayWhiteBalanceSettings(mContext, mHandler);
-                displayWhiteBalanceController = DisplayWhiteBalanceFactory.create(mHandler,
-                        mSensorManager, resources);
                 displayWhiteBalanceSettings.setCallbacks(this);
                 displayWhiteBalanceController.setCallbacks(this);
             } catch (Exception e) {
@@ -801,8 +819,9 @@
     }
 
     @Override
-    public void setBrightnessToFollow(float leadDisplayBrightness, float nits, float ambientLux) {
-        mHbmController.onAmbientLuxChange(ambientLux);
+    public void setBrightnessToFollow(float leadDisplayBrightness, float nits, float ambientLux,
+            boolean slowChange) {
+        mBrightnessRangeController.onAmbientLuxChange(ambientLux);
         if (nits < 0) {
             mBrightnessToFollow = leadDisplayBrightness;
         } else {
@@ -814,6 +833,7 @@
                 mBrightnessToFollow = leadDisplayBrightness;
             }
         }
+        mBrightnessToFollowSlowChange = slowChange;
         sendUpdatePowerState();
     }
 
@@ -831,7 +851,7 @@
             mDisplayBrightnessFollowers.remove(follower.getDisplayId());
             mHandler.postAtTime(() -> follower.setBrightnessToFollow(
                     PowerManager.BRIGHTNESS_INVALID_FLOAT, /* nits= */ -1,
-                    /* ambientLux= */ 0), mClock.uptimeMillis());
+                    /* ambientLux= */ 0, /* slowChange= */ false), mClock.uptimeMillis());
         }
     }
 
@@ -841,7 +861,7 @@
             DisplayPowerControllerInterface follower = mDisplayBrightnessFollowers.valueAt(i);
             mHandler.postAtTime(() -> follower.setBrightnessToFollow(
                     PowerManager.BRIGHTNESS_INVALID_FLOAT, /* nits= */ -1,
-                    /* ambientLux= */ 0), mClock.uptimeMillis());
+                    /* ambientLux= */ 0, /* slowChange= */ false), mClock.uptimeMillis());
         }
         mDisplayBrightnessFollowers.clear();
     }
@@ -1009,10 +1029,6 @@
             Message msg = mHandler.obtainMessage(MSG_STOP);
             mHandler.sendMessageAtTime(msg, mClock.uptimeMillis());
 
-            if (mDisplayWhiteBalanceController != null) {
-                mDisplayWhiteBalanceController.setEnabled(false);
-            }
-
             if (mAutomaticBrightnessController != null) {
                 mAutomaticBrightnessController.stop();
             }
@@ -1039,17 +1055,7 @@
                     mBrightnessRampIncreaseMaxTimeMillis,
                     mBrightnessRampDecreaseMaxTimeMillis);
         }
-        mHbmController.setHighBrightnessModeMetadata(hbmMetadata);
-        mHbmController.resetHbmData(info.width, info.height, token, info.uniqueId,
-                mDisplayDeviceConfig.getHighBrightnessModeData(),
-                new HighBrightnessModeController.HdrBrightnessDeviceConfig() {
-                    @Override
-                    public float getHdrBrightnessFromSdr(
-                            float sdrBrightness, float maxDesiredHdrSdrRatio) {
-                        return mDisplayDeviceConfig.getHdrBrightnessFromSdr(
-                                sdrBrightness, maxDesiredHdrSdrRatio);
-                    }
-                });
+        mBrightnessRangeController.loadFromConfig(hbmMetadata, token, info, mDisplayDeviceConfig);
         mBrightnessThrottler.loadThermalBrightnessThrottlingDataFromDisplayDeviceConfig(
                 mDisplayDeviceConfig.getThermalBrightnessThrottlingDataMapByThrottlingId(),
                 mThermalBrightnessThrottlingDataId, mUniqueDisplayId);
@@ -1264,7 +1270,7 @@
                     darkeningLightDebounce, autoBrightnessResetAmbientLuxAfterWarmUp,
                     ambientBrightnessThresholds, screenBrightnessThresholds,
                     ambientBrightnessThresholdsIdle, screenBrightnessThresholdsIdle, mContext,
-                    mHbmController, mBrightnessThrottler, mIdleModeBrightnessMapper,
+                    mBrightnessRangeController, mBrightnessThrottler, mIdleModeBrightnessMapper,
                     mDisplayDeviceConfig.getAmbientHorizonShort(),
                     mDisplayDeviceConfig.getAmbientHorizonLong(), userLux, userBrightness);
 
@@ -1328,9 +1334,11 @@
                 mAutomaticBrightnessController.switchToInteractiveScreenBrightnessMode();
             }
         }
-        if (mDisplayWhiteBalanceController != null) {
-            mDisplayWhiteBalanceController.setStrongModeEnabled(isIdle);
-        }
+
+        Message msg = mHandler.obtainMessage();
+        msg.what = MSG_SET_DWBC_STRONG_MODE;
+        msg.arg1 = isIdle ? 1 : 0;
+        mHandler.sendMessageAtTime(msg, mClock.uptimeMillis());
     }
 
     private final Animator.AnimatorListener mAnimatorListener = new Animator.AnimatorListener() {
@@ -1364,7 +1372,7 @@
     /** Clean up all resources that are accessed via the {@link #mHandler} thread. */
     private void cleanupHandlerThreadAfterStop() {
         setProximitySensorEnabled(false);
-        mHbmController.stop();
+        mBrightnessRangeController.stop();
         mBrightnessThrottler.stop();
         mHandler.removeCallbacksAndMessages(null);
 
@@ -1399,8 +1407,13 @@
         if (mScreenOffBrightnessSensorController != null) {
             mScreenOffBrightnessSensorController.stop();
         }
+        if (mDisplayWhiteBalanceController != null) {
+            mDisplayWhiteBalanceController.setEnabled(false);
+        }
+
     }
 
+    // Call from handler thread
     private void updatePowerState() {
         Trace.traceBegin(Trace.TRACE_TAG_POWER,
                 "DisplayPowerController#updatePowerState");
@@ -1558,6 +1571,7 @@
         final int oldState = mPowerState.getScreenState();
         animateScreenStateChange(state, performScreenOffTransition);
         state = mPowerState.getScreenState();
+        boolean slowChange = false;
 
         if (state == Display.STATE_OFF) {
             brightnessState = PowerManager.BRIGHTNESS_OFF_FLOAT;
@@ -1566,6 +1580,7 @@
 
         if (Float.isNaN(brightnessState) && isValidBrightnessValue(mBrightnessToFollow)) {
             brightnessState = mBrightnessToFollow;
+            slowChange = mBrightnessToFollowSlowChange;
             mBrightnessReasonTemp.setReason(BrightnessReason.REASON_FOLLOWER);
         }
 
@@ -1582,12 +1597,12 @@
                 mAllowAutoBrightnessWhileDozingConfig && Display.isDozeState(state);
         final boolean autoBrightnessEnabled = mUseAutoBrightness
                 && (state == Display.STATE_ON || autoBrightnessEnabledInDoze)
-                && Float.isNaN(brightnessState)
-                && mAutomaticBrightnessController != null
-                && mBrightnessReasonTemp.getReason() != BrightnessReason.REASON_FOLLOWER;
+                && mBrightnessReasonTemp.getReason() != BrightnessReason.REASON_OVERRIDE
+                && mAutomaticBrightnessController != null;
         final boolean autoBrightnessDisabledDueToDisplayOff = mUseAutoBrightness
                 && !(state == Display.STATE_ON || autoBrightnessEnabledInDoze);
         final int autoBrightnessState = autoBrightnessEnabled
+                    && mBrightnessReasonTemp.getReason() != BrightnessReason.REASON_FOLLOWER
                 ? AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED
                 : autoBrightnessDisabledDueToDisplayOff
                         ? AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE
@@ -1647,9 +1662,11 @@
                     mShouldResetShortTermModel);
             mShouldResetShortTermModel = false;
         }
-        mHbmController.setAutoBrightnessEnabled(mUseAutoBrightness
+        mBrightnessRangeController.setAutoBrightnessEnabled(autoBrightnessEnabled
                 ? AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED
-                : AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED);
+                : autoBrightnessDisabledDueToDisplayOff
+                        ? AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE
+                        : AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED);
 
         if (mBrightnessTracker != null) {
             mBrightnessTracker.setShouldCollectColorSample(mBrightnessConfiguration != null
@@ -1660,7 +1677,6 @@
         float rawBrightnessState = brightnessState;
 
         // Apply auto-brightness.
-        boolean slowChange = false;
         if (Float.isNaN(brightnessState)) {
             float newAutoBrightnessAdjustment = autoBrightnessAdjustment;
             if (autoBrightnessEnabled) {
@@ -1739,6 +1755,14 @@
             mBrightnessReasonTemp.setReason(BrightnessReason.REASON_MANUAL);
         }
 
+        float ambientLux = mAutomaticBrightnessController == null ? 0
+                : mAutomaticBrightnessController.getAmbientLux();
+        for (int i = 0; i < displayBrightnessFollowers.size(); i++) {
+            DisplayPowerControllerInterface follower = displayBrightnessFollowers.valueAt(i);
+            follower.setBrightnessToFollow(rawBrightnessState, convertToNits(rawBrightnessState),
+                    ambientLux, slowChange);
+        }
+
         // Now that a desired brightness has been calculated, apply brightness throttling. The
         // dimming and low power transformations that follow can only dim brightness further.
         //
@@ -1761,14 +1785,6 @@
             mAppliedThrottling = false;
         }
 
-        float ambientLux = mAutomaticBrightnessController == null ? 0
-                : mAutomaticBrightnessController.getAmbientLux();
-        for (int i = 0; i < displayBrightnessFollowers.size(); i++) {
-            DisplayPowerControllerInterface follower = displayBrightnessFollowers.valueAt(i);
-            follower.setBrightnessToFollow(rawBrightnessState, convertToNits(rawBrightnessState),
-                    ambientLux);
-        }
-
         if (updateScreenBrightnessSetting) {
             // Tell the rest of the system about the new brightness in case we had to change it
             // for things like auto-brightness or high-brightness-mode. Note that we do this
@@ -1820,7 +1836,7 @@
         // here instead of having HbmController listen to the brightness setting because certain
         // brightness sources (such as an app override) are not saved to the setting, but should be
         // reflected in HBM calculations.
-        mHbmController.onBrightnessChanged(brightnessState, unthrottledBrightnessState,
+        mBrightnessRangeController.onBrightnessChanged(brightnessState, unthrottledBrightnessState,
                 mBrightnessThrottler.getBrightnessMaxReason());
 
         // Animate the screen brightness when the screen is on or dozing.
@@ -1874,13 +1890,14 @@
             float sdrAnimateValue = animateValue;
             // TODO(b/216365040): The decision to prevent HBM for HDR in low power mode should be
             // done in HighBrightnessModeController.
-            if (mHbmController.getHighBrightnessMode() == BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR
+            if (mBrightnessRangeController.getHighBrightnessMode()
+                    == BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR
                     && (mBrightnessReasonTemp.getModifier() & BrightnessReason.MODIFIER_DIMMED) == 0
                     && (mBrightnessReasonTemp.getModifier() & BrightnessReason.MODIFIER_LOW_POWER)
                     == 0) {
                 // We want to scale HDR brightness level with the SDR level, we also need to restore
                 // SDR brightness immediately when entering dim or low power mode.
-                animateValue = mHbmController.getHdrBrightnessValue();
+                animateValue = mBrightnessRangeController.getHdrBrightnessValue();
             }
 
             final float currentBrightness = mPowerState.getScreenBrightness();
@@ -1942,8 +1959,8 @@
         mTempBrightnessEvent.setBrightness(brightnessState);
         mTempBrightnessEvent.setPhysicalDisplayId(mUniqueDisplayId);
         mTempBrightnessEvent.setReason(mBrightnessReason);
-        mTempBrightnessEvent.setHbmMax(mHbmController.getCurrentBrightnessMax());
-        mTempBrightnessEvent.setHbmMode(mHbmController.getHighBrightnessMode());
+        mTempBrightnessEvent.setHbmMax(mBrightnessRangeController.getCurrentBrightnessMax());
+        mTempBrightnessEvent.setHbmMode(mBrightnessRangeController.getHighBrightnessMode());
         mTempBrightnessEvent.setFlags(mTempBrightnessEvent.getFlags()
                 | (mIsRbcActive ? BrightnessEvent.FLAG_RBC : 0)
                 | (mPowerRequest.lowPowerMode ? BrightnessEvent.FLAG_LOW_POWER_MODE : 0));
@@ -2048,6 +2065,32 @@
         }
     }
 
+    private void setDwbcOverride(float cct) {
+        if (mDisplayWhiteBalanceController != null) {
+            mDisplayWhiteBalanceController.setAmbientColorTemperatureOverride(cct);
+            // The ambient color temperature override is only applied when the ambient color
+            // temperature changes or is updated, so it doesn't necessarily change the screen color
+            // temperature immediately. So, let's make it!
+            // We can call this directly, since we're already on the handler thread.
+            updatePowerState();
+        }
+    }
+
+    private void setDwbcStrongMode(int arg) {
+        if (mDisplayWhiteBalanceController != null) {
+            final boolean isIdle = (arg == 1);
+            mDisplayWhiteBalanceController.setStrongModeEnabled(isIdle);
+        }
+    }
+
+    private void setDwbcLoggingEnabled(int arg) {
+        if (mDisplayWhiteBalanceController != null) {
+            final boolean shouldEnable = (arg == 1);
+            mDisplayWhiteBalanceController.setLoggingEnabled(shouldEnable);
+            mDisplayWhiteBalanceSettings.setLoggingEnabled(shouldEnable);
+        }
+    }
+
     @Override
     public void updateBrightness() {
         sendUpdatePowerState();
@@ -2104,9 +2147,11 @@
 
     private boolean saveBrightnessInfo(float brightness, float adjustedBrightness) {
         synchronized (mCachedBrightnessInfo) {
-            final float minBrightness = Math.min(mHbmController.getCurrentBrightnessMin(),
+            final float minBrightness = Math.min(
+                    mBrightnessRangeController.getCurrentBrightnessMin(),
                     mBrightnessThrottler.getBrightnessCap());
-            final float maxBrightness = Math.min(mHbmController.getCurrentBrightnessMax(),
+            final float maxBrightness = Math.min(
+                    mBrightnessRangeController.getCurrentBrightnessMax(),
                     mBrightnessThrottler.getBrightnessCap());
             boolean changed = false;
 
@@ -2124,10 +2169,10 @@
                             maxBrightness);
             changed |=
                     mCachedBrightnessInfo.checkAndSetInt(mCachedBrightnessInfo.hbmMode,
-                            mHbmController.getHighBrightnessMode());
+                            mBrightnessRangeController.getHighBrightnessMode());
             changed |=
                     mCachedBrightnessInfo.checkAndSetFloat(mCachedBrightnessInfo.hbmTransitionPoint,
-                            mHbmController.getTransitionPoint());
+                            mBrightnessRangeController.getTransitionPoint());
             changed |=
                     mCachedBrightnessInfo.checkAndSetInt(mCachedBrightnessInfo.brightnessMaxReason,
                             mBrightnessThrottler.getBrightnessMaxReason());
@@ -2137,10 +2182,13 @@
     }
 
     void postBrightnessChangeRunnable() {
-        mHandler.post(mOnBrightnessChangeRunnable);
+        if (!mHandler.hasCallbacks(mOnBrightnessChangeRunnable)) {
+            mHandler.post(mOnBrightnessChangeRunnable);
+        }
     }
 
-    private HighBrightnessModeController createHbmControllerLocked() {
+    private HighBrightnessModeController createHbmControllerLocked(
+            Runnable modeChangeCallback) {
         final DisplayDevice device = mLogicalDisplay.getPrimaryDisplayDeviceLocked();
         final DisplayDeviceConfig ddConfig = device.getDisplayDeviceConfig();
         final IBinder displayToken =
@@ -2150,8 +2198,9 @@
         final DisplayDeviceConfig.HighBrightnessModeData hbmData =
                 ddConfig != null ? ddConfig.getHighBrightnessModeData() : null;
         final DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
-        return new HighBrightnessModeController(mHandler, info.width, info.height, displayToken,
-                displayUniqueId, PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX, hbmData,
+        return mInjector.getHighBrightnessModeController(mHandler, info.width, info.height,
+                displayToken, displayUniqueId, PowerManager.BRIGHTNESS_MIN,
+                PowerManager.BRIGHTNESS_MAX, hbmData,
                 new HighBrightnessModeController.HdrBrightnessDeviceConfig() {
                     @Override
                     public float getHdrBrightnessFromSdr(
@@ -2159,15 +2208,7 @@
                         return mDisplayDeviceConfig.getHdrBrightnessFromSdr(
                                 sdrBrightness, maxDesiredHdrSdrRatio);
                     }
-                },
-                () -> {
-                    sendUpdatePowerState();
-                    postBrightnessChangeRunnable();
-                    // TODO(b/192258832): Switch the HBMChangeCallback to a listener pattern.
-                    if (mAutomaticBrightnessController != null) {
-                        mAutomaticBrightnessController.update();
-                    }
-                }, mHighBrightnessModeMetadata, mContext);
+                }, modeChangeCallback, mHighBrightnessModeMetadata, mContext);
     }
 
     private BrightnessThrottler createBrightnessThrottlerLocked() {
@@ -2295,29 +2336,23 @@
     }
 
     private void loadAmbientLightSensor() {
-        DisplayDeviceConfig.SensorData lightSensor = mDisplayDeviceConfig.getAmbientLightSensor();
         final int fallbackType = mDisplayId == Display.DEFAULT_DISPLAY
                 ? Sensor.TYPE_LIGHT : SensorUtils.NO_FALLBACK;
-        mLightSensor = SensorUtils.findSensor(mSensorManager, lightSensor.type, lightSensor.name,
-                fallbackType);
+        mLightSensor = SensorUtils.findSensor(mSensorManager,
+                mDisplayDeviceConfig.getAmbientLightSensor(), fallbackType);
     }
 
     private void loadScreenOffBrightnessSensor() {
-        DisplayDeviceConfig.SensorData screenOffBrightnessSensor =
-                mDisplayDeviceConfig.getScreenOffBrightnessSensor();
         mScreenOffBrightnessSensor = SensorUtils.findSensor(mSensorManager,
-                screenOffBrightnessSensor.type, screenOffBrightnessSensor.name,
-                SensorUtils.NO_FALLBACK);
+                mDisplayDeviceConfig.getScreenOffBrightnessSensor(), SensorUtils.NO_FALLBACK);
     }
 
     private void loadProximitySensor() {
         if (DEBUG_PRETEND_PROXIMITY_SENSOR_ABSENT || mDisplayId != Display.DEFAULT_DISPLAY) {
             return;
         }
-        final DisplayDeviceConfig.SensorData proxSensor =
-                mDisplayDeviceConfig.getProximitySensor();
-        mProximitySensor = SensorUtils.findSensor(mSensorManager, proxSensor.type, proxSensor.name,
-                Sensor.TYPE_PROXIMITY);
+        mProximitySensor = SensorUtils.findSensor(mSensorManager,
+                mDisplayDeviceConfig.getProximitySensor(), Sensor.TYPE_PROXIMITY);
         if (mProximitySensor != null) {
             mProximityThreshold = Math.min(mProximitySensor.getMaximumRange(),
                     TYPICAL_PROXIMITY_THRESHOLD);
@@ -2328,8 +2363,8 @@
         if (Float.isNaN(value)) {
             value = PowerManager.BRIGHTNESS_MIN;
         }
-        return MathUtils.constrain(value,
-                mHbmController.getCurrentBrightnessMin(), mHbmController.getCurrentBrightnessMax());
+        return MathUtils.constrain(value, mBrightnessRangeController.getCurrentBrightnessMin(),
+                mBrightnessRangeController.getCurrentBrightnessMax());
     }
 
     // Checks whether the brightness is within the valid brightness range, not including off.
@@ -2667,12 +2702,14 @@
     }
 
     @Override
-    public void setBrightness(float brightnessValue) {
+    public void setBrightness(float brightnessValue, int userSerial) {
         // Update the setting, which will eventually call back into DPC to have us actually update
         // the display with the new value.
-        mBrightnessSetting.setBrightness(brightnessValue);
+        float clampedBrightnessValue = clampScreenBrightness(brightnessValue);
+        mBrightnessSetting.setUserSerial(userSerial);
+        mBrightnessSetting.setBrightness(clampedBrightnessValue);
         if (mDisplayId == Display.DEFAULT_DISPLAY && mPersistBrightnessNitsForDefaultDisplay) {
-            float nits = convertToNits(brightnessValue);
+            float nits = convertToNits(clampedBrightnessValue);
             if (nits >= 0) {
                 mBrightnessSetting.setBrightnessNitsForDefaultDisplay(nits);
             }
@@ -2949,6 +2986,7 @@
                 + mPendingScreenBrightnessSetting);
         pw.println("  mTemporaryScreenBrightness=" + mTemporaryScreenBrightness);
         pw.println("  mBrightnessToFollow=" + mBrightnessToFollow);
+        pw.println("  mBrightnessToFollowSlowChange=" + mBrightnessToFollowSlowChange);
         pw.println("  mAutoBrightnessAdjustment=" + mAutoBrightnessAdjustment);
         pw.println("  mBrightnessReason=" + mBrightnessReason);
         pw.println("  mTemporaryAutoBrightnessAdjustment=" + mTemporaryAutoBrightnessAdjustment);
@@ -3003,8 +3041,8 @@
             mScreenOffBrightnessSensorController.dump(pw);
         }
 
-        if (mHbmController != null) {
-            mHbmController.dump(pw);
+        if (mBrightnessRangeController != null) {
+            mBrightnessRangeController.dump(pw);
         }
 
         if (mBrightnessThrottler != null) {
@@ -3327,6 +3365,19 @@
                     mBootCompleted = true;
                     updatePowerState();
                     break;
+
+                case MSG_SET_DWBC_STRONG_MODE:
+                    setDwbcStrongMode(msg.arg1);
+                    break;
+
+                case MSG_SET_DWBC_COLOR_OVERRIDE:
+                    final float cct = Float.intBitsToFloat(msg.arg1);
+                    setDwbcOverride(cct);
+                    break;
+
+                case MSG_SET_DWBC_LOGGING_ENABLED:
+                    setDwbcLoggingEnabled(msg.arg1);
+                    break;
             }
         }
     }
@@ -3394,21 +3445,18 @@
 
     @Override
     public void setDisplayWhiteBalanceLoggingEnabled(boolean enabled) {
-        if (mDisplayWhiteBalanceController != null) {
-            mDisplayWhiteBalanceController.setLoggingEnabled(enabled);
-            mDisplayWhiteBalanceSettings.setLoggingEnabled(enabled);
-        }
+        Message msg = mHandler.obtainMessage();
+        msg.what = MSG_SET_DWBC_LOGGING_ENABLED;
+        msg.arg1 = enabled ? 1 : 0;
+        msg.sendToTarget();
     }
 
     @Override
     public void setAmbientColorTemperatureOverride(float cct) {
-        if (mDisplayWhiteBalanceController != null) {
-            mDisplayWhiteBalanceController.setAmbientColorTemperatureOverride(cct);
-            // The ambient color temperature override is only applied when the ambient color
-            // temperature changes or is updated, so it doesn't necessarily change the screen color
-            // temperature immediately. So, let's make it!
-            sendUpdatePowerState();
-        }
+        Message msg = mHandler.obtainMessage();
+        msg.what = MSG_SET_DWBC_COLOR_OVERRIDE;
+        msg.arg1 = Float.floatToIntBits(cct);
+        msg.sendToTarget();
     }
 
     @VisibleForTesting
@@ -3471,7 +3519,8 @@
                 HysteresisLevels screenBrightnessThresholds,
                 HysteresisLevels ambientBrightnessThresholdsIdle,
                 HysteresisLevels screenBrightnessThresholdsIdle, Context context,
-                HighBrightnessModeController hbmController, BrightnessThrottler brightnessThrottler,
+                BrightnessRangeController brightnessRangeController,
+                BrightnessThrottler brightnessThrottler,
                 BrightnessMappingStrategy idleModeBrightnessMapper, int ambientLightHorizonShort,
                 int ambientLightHorizonLong, float userLux, float userBrightness) {
             return new AutomaticBrightnessController(callbacks, looper, sensorManager, lightSensor,
@@ -3480,9 +3529,9 @@
                     brighteningLightDebounceConfig, darkeningLightDebounceConfig,
                     resetAmbientLuxAfterWarmUpConfig, ambientBrightnessThresholds,
                     screenBrightnessThresholds, ambientBrightnessThresholdsIdle,
-                    screenBrightnessThresholdsIdle, context, hbmController, brightnessThrottler,
-                    idleModeBrightnessMapper, ambientLightHorizonShort, ambientLightHorizonLong,
-                    userLux, userBrightness);
+                    screenBrightnessThresholdsIdle, context, brightnessRangeController,
+                    brightnessThrottler, idleModeBrightnessMapper, ambientLightHorizonShort,
+                    ambientLightHorizonLong, userLux, userBrightness);
         }
 
         BrightnessMappingStrategy getInteractiveModeBrightnessMapper(Resources resources,
@@ -3527,6 +3576,23 @@
                     brightnessMapper
             );
         }
+
+        HighBrightnessModeController getHighBrightnessModeController(Handler handler, int width,
+                int height, IBinder displayToken, String displayUniqueId, float brightnessMin,
+                float brightnessMax, DisplayDeviceConfig.HighBrightnessModeData hbmData,
+                HighBrightnessModeController.HdrBrightnessDeviceConfig hdrBrightnessCfg,
+                Runnable hbmChangeCallback, HighBrightnessModeMetadata hbmMetadata,
+                Context context) {
+            return new HighBrightnessModeController(handler, width, height, displayToken,
+                    displayUniqueId, brightnessMin, brightnessMax, hbmData, hdrBrightnessCfg,
+                    hbmChangeCallback, hbmMetadata, context);
+        }
+
+        DisplayWhiteBalanceController getDisplayWhiteBalanceController(Handler handler,
+                SensorManager sensorManager, Resources resources) {
+            return DisplayWhiteBalanceFactory.create(handler,
+                    sensorManager, resources);
+        }
     }
 
     static class CachedBrightnessInfo {
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
index 41e4671d..2694f55 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController2.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController2.java
@@ -48,6 +48,7 @@
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.util.FloatProperty;
+import android.util.IndentingPrintWriter;
 import android.util.Log;
 import android.util.MathUtils;
 import android.util.MutableFloat;
@@ -64,7 +65,6 @@
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.util.FrameworkStatsLog;
-import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.RingBuffer;
 import com.android.server.LocalServices;
 import com.android.server.am.BatteryStatsService;
@@ -73,6 +73,7 @@
 import com.android.server.display.brightness.BrightnessReason;
 import com.android.server.display.brightness.BrightnessUtils;
 import com.android.server.display.brightness.DisplayBrightnessController;
+import com.android.server.display.brightness.clamper.BrightnessClamperController;
 import com.android.server.display.brightness.strategy.AutomaticBrightnessStrategy;
 import com.android.server.display.color.ColorDisplayService.ColorDisplayServiceInternal;
 import com.android.server.display.color.ColorDisplayService.ReduceBrightColorsListener;
@@ -141,6 +142,11 @@
     private static final int MSG_STATSD_HBM_BRIGHTNESS = 11;
     private static final int MSG_SWITCH_USER = 12;
     private static final int MSG_BOOT_COMPLETED = 13;
+    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 BRIGHTNESS_CHANGE_STATSD_REPORT_INTERVAL_MS = 500;
 
@@ -367,6 +373,7 @@
     private final boolean mSkipScreenOnBrightnessRamp;
 
     // Display white balance components.
+    // Critical methods must be called on DPC2 handler thread.
     @Nullable
     private final DisplayWhiteBalanceSettings mDisplayWhiteBalanceSettings;
     @Nullable
@@ -376,11 +383,12 @@
     private final ColorDisplayServiceInternal mCdsi;
     private float[] mNitsRange;
 
-    private final HighBrightnessModeController mHbmController;
-    private final HighBrightnessModeMetadata mHighBrightnessModeMetadata;
+    private final BrightnessRangeController mBrightnessRangeController;
 
     private final BrightnessThrottler mBrightnessThrottler;
 
+    private final BrightnessClamperController mBrightnessClamperController;
+
     private final Runnable mOnBrightnessChangeRunnable;
 
     private final BrightnessEvent mLastBrightnessEvent;
@@ -436,6 +444,9 @@
     @Nullable
     private BrightnessMappingStrategy mIdleModeBrightnessMapper;
 
+    // Indicates whether we should ramp slowly to the brightness value to follow.
+    private boolean mBrightnessToFollowSlowChange;
+
     private boolean mIsRbcActive;
 
     // Animators.
@@ -489,9 +500,7 @@
         mDisplayPowerProximityStateController = mInjector.getDisplayPowerProximityStateController(
                 mWakelockController, mDisplayDeviceConfig, mHandler.getLooper(),
                 () -> updatePowerState(), mDisplayId, mSensorManager);
-        mHighBrightnessModeMetadata = hbmMetadata;
         mDisplayStateController = new DisplayStateController(mDisplayPowerProximityStateController);
-        mAutomaticBrightnessStrategy = new AutomaticBrightnessStrategy(context, mDisplayId);
         mTag = "DisplayPowerController2[" + mDisplayId + "]";
         mThermalBrightnessThrottlingDataId =
                 logicalDisplay.getDisplayInfoLocked().thermalBrightnessThrottlingDataId;
@@ -532,24 +541,46 @@
         mSkipScreenOnBrightnessRamp = resources.getBoolean(
                 R.bool.config_skipScreenOnBrightnessRamp);
 
-        mHbmController = createHbmControllerLocked();
+        Runnable modeChangeCallback = () -> {
+            sendUpdatePowerState();
+            postBrightnessChangeRunnable();
+            // TODO(b/192258832): Switch the HBMChangeCallback to a listener pattern.
+            if (mAutomaticBrightnessController != null) {
+                mAutomaticBrightnessController.update();
+            }
+        };
 
+        HighBrightnessModeController hbmController = createHbmControllerLocked(hbmMetadata,
+                modeChangeCallback);
         mBrightnessThrottler = createBrightnessThrottlerLocked();
+
+        mBrightnessRangeController = new BrightnessRangeController(hbmController,
+                modeChangeCallback);
+
         mDisplayBrightnessController =
                 new DisplayBrightnessController(context, null,
                         mDisplayId, mLogicalDisplay.getDisplayInfoLocked().brightnessDefault,
                         brightnessSetting, () -> postBrightnessChangeRunnable(),
                         new HandlerExecutor(mHandler));
+
+        mBrightnessClamperController = new BrightnessClamperController(mHandler,
+                modeChangeCallback::run, new BrightnessClamperController.DisplayDeviceData(
+                mUniqueDisplayId,
+                mThermalBrightnessThrottlingDataId,
+                mDisplayDeviceConfig
+        ));
         // Seed the cached brightness
         saveBrightnessInfo(getScreenBrightnessSetting());
+        mAutomaticBrightnessStrategy =
+                mDisplayBrightnessController.getAutomaticBrightnessStrategy();
 
         DisplayWhiteBalanceSettings displayWhiteBalanceSettings = null;
         DisplayWhiteBalanceController displayWhiteBalanceController = null;
         if (mDisplayId == Display.DEFAULT_DISPLAY) {
             try {
+                displayWhiteBalanceController = mInjector.getDisplayWhiteBalanceController(
+                        mHandler, mSensorManager, resources);
                 displayWhiteBalanceSettings = new DisplayWhiteBalanceSettings(mContext, mHandler);
-                displayWhiteBalanceController = DisplayWhiteBalanceFactory.create(mHandler,
-                        mSensorManager, resources);
                 displayWhiteBalanceSettings.setCallbacks(this);
                 displayWhiteBalanceController.setCallbacks(this);
             } catch (Exception e) {
@@ -585,7 +616,7 @@
 
         setUpAutoBrightness(resources, handler);
 
-        mColorFadeEnabled = !ActivityManager.isLowRamDeviceStatic();
+        mColorFadeEnabled = mInjector.isColorFadeEnabled();
         mColorFadeFadesConfig = resources.getBoolean(
                 R.bool.config_animateScreenLights);
 
@@ -768,6 +799,10 @@
         final String thermalBrightnessThrottlingDataId =
                 mLogicalDisplay.getDisplayInfoLocked().thermalBrightnessThrottlingDataId;
 
+        mBrightnessClamperController.onDisplayChanged(
+                new BrightnessClamperController.DisplayDeviceData(mUniqueDisplayId,
+                        mThermalBrightnessThrottlingDataId, config));
+
         mHandler.postAtTime(() -> {
             boolean changed = false;
             if (mDisplayDevice != device) {
@@ -821,10 +856,6 @@
             Message msg = mHandler.obtainMessage(MSG_STOP);
             mHandler.sendMessageAtTime(msg, mClock.uptimeMillis());
 
-            if (mDisplayWhiteBalanceController != null) {
-                mDisplayWhiteBalanceController.setEnabled(false);
-            }
-
             if (mAutomaticBrightnessController != null) {
                 mAutomaticBrightnessController.stop();
             }
@@ -848,17 +879,8 @@
                     mBrightnessRampIncreaseMaxTimeMillis,
                     mBrightnessRampDecreaseMaxTimeMillis);
         }
-        mHbmController.setHighBrightnessModeMetadata(hbmMetadata);
-        mHbmController.resetHbmData(info.width, info.height, token, info.uniqueId,
-                mDisplayDeviceConfig.getHighBrightnessModeData(),
-                new HighBrightnessModeController.HdrBrightnessDeviceConfig() {
-                    @Override
-                    public float getHdrBrightnessFromSdr(
-                            float sdrBrightness, float maxDesiredHdrSdrRatio) {
-                        return mDisplayDeviceConfig.getHdrBrightnessFromSdr(
-                                sdrBrightness, maxDesiredHdrSdrRatio);
-                    }
-                });
+
+        mBrightnessRangeController.loadFromConfig(hbmMetadata, token, info, mDisplayDeviceConfig);
         mBrightnessThrottler.loadThermalBrightnessThrottlingDataFromDisplayDeviceConfig(
                 mDisplayDeviceConfig.getThermalBrightnessThrottlingDataMapByThrottlingId(),
                 mThermalBrightnessThrottlingDataId, mUniqueDisplayId);
@@ -1076,7 +1098,7 @@
                     darkeningLightDebounce, autoBrightnessResetAmbientLuxAfterWarmUp,
                     ambientBrightnessThresholds, screenBrightnessThresholds,
                     ambientBrightnessThresholdsIdle, screenBrightnessThresholdsIdle, mContext,
-                    mHbmController, mBrightnessThrottler, mIdleModeBrightnessMapper,
+                    mBrightnessRangeController, mBrightnessThrottler, mIdleModeBrightnessMapper,
                     mDisplayDeviceConfig.getAmbientHorizonShort(),
                     mDisplayDeviceConfig.getAmbientHorizonLong(), userLux, userBrightness);
             mDisplayBrightnessController.setAutomaticBrightnessController(
@@ -1144,9 +1166,10 @@
                 mAutomaticBrightnessController.switchToInteractiveScreenBrightnessMode();
             }
         }
-        if (mDisplayWhiteBalanceController != null) {
-            mDisplayWhiteBalanceController.setStrongModeEnabled(isIdle);
-        }
+        Message msg = mHandler.obtainMessage();
+        msg.what = MSG_SET_DWBC_STRONG_MODE;
+        msg.arg1 = isIdle ? 1 : 0;
+        mHandler.sendMessageAtTime(msg, mClock.uptimeMillis());
     }
 
     private final Animator.AnimatorListener mAnimatorListener = new Animator.AnimatorListener() {
@@ -1180,8 +1203,9 @@
     /** Clean up all resources that are accessed via the {@link #mHandler} thread. */
     private void cleanupHandlerThreadAfterStop() {
         mDisplayPowerProximityStateController.cleanup();
-        mHbmController.stop();
+        mBrightnessRangeController.stop();
         mBrightnessThrottler.stop();
+        mBrightnessClamperController.stop();
         mHandler.removeCallbacksAndMessages(null);
 
         // Release any outstanding wakelocks we're still holding because of pending messages.
@@ -1200,8 +1224,13 @@
         if (mScreenOffBrightnessSensorController != null) {
             mScreenOffBrightnessSensorController.stop();
         }
+
+        if (mDisplayWhiteBalanceController != null) {
+            mDisplayWhiteBalanceController.setEnabled(false);
+        }
     }
 
+    // Call from handler thread
     private void updatePowerState() {
         Trace.traceBegin(Trace.TRACE_TAG_POWER,
                 "DisplayPowerController#updatePowerState");
@@ -1252,14 +1281,6 @@
         int state = mDisplayStateController
                 .updateDisplayState(mPowerRequest, mIsEnabled, mIsInTransition);
 
-        if (mScreenOffBrightnessSensorController != null) {
-            mScreenOffBrightnessSensorController
-                    .setLightSensorEnabled(mAutomaticBrightnessStrategy.shouldUseAutoBrightness()
-                    && mIsEnabled && (state == Display.STATE_OFF || (state == Display.STATE_DOZE
-                    && !mDisplayBrightnessController.isAllowAutoBrightnessWhileDozingConfig()))
-                    && mLeadDisplayId == Layout.NO_LEAD_DISPLAY);
-        }
-
         // Initialize things the first time the power state is changed.
         if (mustInitialize) {
             initialize(readyToUpdateDisplayState() ? state : Display.STATE_UNKNOWN);
@@ -1270,6 +1291,7 @@
         // actual state instead of the desired one.
         animateScreenStateChange(state, mDisplayStateController.shouldPerformScreenOffTransition());
         state = mPowerState.getScreenState();
+        boolean slowChange = false;
         final boolean userSetBrightnessChanged = mDisplayBrightnessController
                 .updateUserSetScreenBrightness();
 
@@ -1279,13 +1301,29 @@
         float rawBrightnessState = displayBrightnessState.getBrightness();
         mBrightnessReasonTemp.set(displayBrightnessState.getBrightnessReason());
 
+        if (displayBrightnessState.getBrightnessReason().getReason()
+                == BrightnessReason.REASON_FOLLOWER) {
+            slowChange = mBrightnessToFollowSlowChange;
+        }
+
+        // Set up the ScreenOff controller used when coming out of SCREEN_OFF and the ALS sensor
+        // doesn't yet have a valid lux value to use with auto-brightness.
+        if (mScreenOffBrightnessSensorController != null) {
+            mScreenOffBrightnessSensorController
+                    .setLightSensorEnabled(displayBrightnessState.getShouldUseAutoBrightness()
+                    && mIsEnabled && (state == Display.STATE_OFF
+                    || (state == Display.STATE_DOZE
+                    && !mDisplayBrightnessController.isAllowAutoBrightnessWhileDozingConfig()))
+                    && mLeadDisplayId == Layout.NO_LEAD_DISPLAY);
+        }
+
         // Take note if the short term model was already active before applying the current
         // request changes.
         final boolean wasShortTermModelActive =
                 mAutomaticBrightnessStrategy.isShortTermModelActive();
         mAutomaticBrightnessStrategy.setAutoBrightnessState(state,
                 mDisplayBrightnessController.isAllowAutoBrightnessWhileDozingConfig(),
-                brightnessState, mBrightnessReasonTemp.getReason(), mPowerRequest.policy,
+                mBrightnessReasonTemp.getReason(), mPowerRequest.policy,
                 mDisplayBrightnessController.getLastUserSetScreenBrightness(),
                 userSetBrightnessChanged);
 
@@ -1295,15 +1333,16 @@
                 && (mAutomaticBrightnessStrategy.getAutoBrightnessAdjustmentChanged()
                 || userSetBrightnessChanged);
 
-        mHbmController.setAutoBrightnessEnabled(mAutomaticBrightnessStrategy
-                .shouldUseAutoBrightness()
+        mBrightnessRangeController.setAutoBrightnessEnabled(
+                mAutomaticBrightnessStrategy.isAutoBrightnessEnabled()
                 ? AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED
-                : AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED);
+                : mAutomaticBrightnessStrategy.isAutoBrightnessDisabledDueToDisplayOff()
+                        ? AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE
+                        : AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED);
 
         boolean updateScreenBrightnessSetting = false;
         float currentBrightnessSetting = mDisplayBrightnessController.getCurrentBrightness();
         // Apply auto-brightness.
-        boolean slowChange = false;
         int brightnessAdjustmentFlags = 0;
         if (Float.isNaN(brightnessState)) {
             if (mAutomaticBrightnessStrategy.isAutoBrightnessEnabled()) {
@@ -1320,10 +1359,13 @@
                     brightnessAdjustmentFlags =
                             mAutomaticBrightnessStrategy.getAutoBrightnessAdjustmentReasonsFlags();
                     updateScreenBrightnessSetting = currentBrightnessSetting != brightnessState;
+                    mAutomaticBrightnessStrategy.setAutoBrightnessApplied(true);
                     mBrightnessReasonTemp.setReason(BrightnessReason.REASON_AUTOMATIC);
                     if (mScreenOffBrightnessSensorController != null) {
                         mScreenOffBrightnessSensorController.setLightSensorEnabled(false);
                     }
+                } else {
+                    mAutomaticBrightnessStrategy.setAutoBrightnessApplied(false);
                 }
             }
         } else {
@@ -1331,6 +1373,7 @@
             // to clamping so that they don't go beyond the current max as specified by HBM
             // Controller.
             brightnessState = clampScreenBrightness(brightnessState);
+            mAutomaticBrightnessStrategy.setAutoBrightnessApplied(false);
         }
 
         // Use default brightness when dozing unless overridden.
@@ -1370,6 +1413,15 @@
             mBrightnessReasonTemp.setReason(BrightnessReason.REASON_MANUAL);
         }
 
+        float ambientLux = mAutomaticBrightnessController == null ? 0
+                : mAutomaticBrightnessController.getAmbientLux();
+        for (int i = 0; i < displayBrightnessFollowers.size(); i++) {
+            DisplayPowerControllerInterface follower = displayBrightnessFollowers.valueAt(i);
+            follower.setBrightnessToFollow(rawBrightnessState,
+                    mDisplayBrightnessController.convertToNits(rawBrightnessState),
+                    ambientLux, slowChange);
+        }
+
         // Now that a desired brightness has been calculated, apply brightness throttling. The
         // dimming and low power transformations that follow can only dim brightness further.
         //
@@ -1392,15 +1444,6 @@
             mAppliedThrottling = false;
         }
 
-        float ambientLux = mAutomaticBrightnessController == null ? 0
-                : mAutomaticBrightnessController.getAmbientLux();
-        for (int i = 0; i < displayBrightnessFollowers.size(); i++) {
-            DisplayPowerControllerInterface follower = displayBrightnessFollowers.valueAt(i);
-            follower.setBrightnessToFollow(rawBrightnessState,
-                    mDisplayBrightnessController.convertToNits(rawBrightnessState),
-                    ambientLux);
-        }
-
         if (updateScreenBrightnessSetting) {
             // Tell the rest of the system about the new brightness in case we had to change it
             // for things like auto-brightness or high-brightness-mode. Note that we do this
@@ -1452,7 +1495,7 @@
         // here instead of having HbmController listen to the brightness setting because certain
         // brightness sources (such as an app override) are not saved to the setting, but should be
         // reflected in HBM calculations.
-        mHbmController.onBrightnessChanged(brightnessState, unthrottledBrightnessState,
+        mBrightnessRangeController.onBrightnessChanged(brightnessState, unthrottledBrightnessState,
                 mBrightnessThrottler.getBrightnessMaxReason());
 
         // Animate the screen brightness when the screen is on or dozing.
@@ -1503,19 +1546,22 @@
             // allowed range.
             float animateValue = clampScreenBrightness(brightnessState);
 
+            animateValue = mBrightnessClamperController.clamp(animateValue);
+
             // If there are any HDR layers on the screen, we have a special brightness value that we
             // use instead. We still preserve the calculated brightness for Standard Dynamic Range
             // (SDR) layers, but the main brightness value will be the one for HDR.
             float sdrAnimateValue = animateValue;
             // TODO(b/216365040): The decision to prevent HBM for HDR in low power mode should be
             // done in HighBrightnessModeController.
-            if (mHbmController.getHighBrightnessMode() == BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR
+            if (mBrightnessRangeController.getHighBrightnessMode()
+                    == BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR
                     && (mBrightnessReasonTemp.getModifier() & BrightnessReason.MODIFIER_DIMMED) == 0
                     && (mBrightnessReasonTemp.getModifier() & BrightnessReason.MODIFIER_LOW_POWER)
                     == 0) {
                 // We want to scale HDR brightness level with the SDR level, we also need to restore
                 // SDR brightness immediately when entering dim or low power mode.
-                animateValue = mHbmController.getHdrBrightnessValue();
+                animateValue = mBrightnessRangeController.getHdrBrightnessValue();
             }
 
             final float currentBrightness = mPowerState.getScreenBrightness();
@@ -1546,7 +1592,7 @@
 
             notifyBrightnessTrackerChanged(brightnessState, userInitiatedChange,
                     wasShortTermModelActive, mAutomaticBrightnessStrategy.isAutoBrightnessEnabled(),
-                    brightnessIsTemporary);
+                    brightnessIsTemporary, displayBrightnessState.getShouldUseAutoBrightness());
 
             // We save the brightness info *after* the brightness setting has been changed and
             // adjustments made so that the brightness info reflects the latest value.
@@ -1579,8 +1625,8 @@
         mTempBrightnessEvent.setBrightness(brightnessState);
         mTempBrightnessEvent.setPhysicalDisplayId(mUniqueDisplayId);
         mTempBrightnessEvent.setReason(mBrightnessReason);
-        mTempBrightnessEvent.setHbmMax(mHbmController.getCurrentBrightnessMax());
-        mTempBrightnessEvent.setHbmMode(mHbmController.getHighBrightnessMode());
+        mTempBrightnessEvent.setHbmMax(mBrightnessRangeController.getCurrentBrightnessMax());
+        mTempBrightnessEvent.setHbmMode(mBrightnessRangeController.getHighBrightnessMode());
         mTempBrightnessEvent.setFlags(mTempBrightnessEvent.getFlags()
                 | (mIsRbcActive ? BrightnessEvent.FLAG_RBC : 0)
                 | (mPowerRequest.lowPowerMode ? BrightnessEvent.FLAG_LOW_POWER_MODE : 0));
@@ -1590,8 +1636,8 @@
         mTempBrightnessEvent.setWasShortTermModelActive(wasShortTermModelActive);
         mTempBrightnessEvent.setDisplayBrightnessStrategyName(displayBrightnessState
                 .getDisplayBrightnessStrategyName());
-        mTempBrightnessEvent.setAutomaticBrightnessEnabled(mAutomaticBrightnessStrategy
-                .shouldUseAutoBrightness());
+        mTempBrightnessEvent.setAutomaticBrightnessEnabled(
+                displayBrightnessState.getShouldUseAutoBrightness());
         // Temporary is what we use during slider interactions. We avoid logging those so that
         // we don't spam logcat when the slider is being used.
         boolean tempToTempTransition =
@@ -1688,6 +1734,32 @@
         }
     }
 
+    private void setDwbcOverride(float cct) {
+        if (mDisplayWhiteBalanceController != null) {
+            mDisplayWhiteBalanceController.setAmbientColorTemperatureOverride(cct);
+            // The ambient color temperature override is only applied when the ambient color
+            // temperature changes or is updated, so it doesn't necessarily change the screen color
+            // temperature immediately. So, let's make it!
+            // We can call this directly, since we're already on the handler thread.
+            updatePowerState();
+        }
+    }
+
+    private void setDwbcStrongMode(int arg) {
+        if (mDisplayWhiteBalanceController != null) {
+            final boolean isIdle = (arg == 1);
+            mDisplayWhiteBalanceController.setStrongModeEnabled(isIdle);
+        }
+    }
+
+    private void setDwbcLoggingEnabled(int arg) {
+        if (mDisplayWhiteBalanceController != null) {
+            final boolean enabled = (arg == 1);
+            mDisplayWhiteBalanceController.setLoggingEnabled(enabled);
+            mDisplayWhiteBalanceSettings.setLoggingEnabled(enabled);
+        }
+    }
+
     @Override
     public void updateBrightness() {
         sendUpdatePowerState();
@@ -1750,9 +1822,11 @@
 
     private boolean saveBrightnessInfo(float brightness, float adjustedBrightness) {
         synchronized (mCachedBrightnessInfo) {
-            final float minBrightness = Math.min(mHbmController.getCurrentBrightnessMin(),
+            final float minBrightness = Math.min(
+                    mBrightnessRangeController.getCurrentBrightnessMin(),
                     mBrightnessThrottler.getBrightnessCap());
-            final float maxBrightness = Math.min(mHbmController.getCurrentBrightnessMax(),
+            final float maxBrightness = Math.min(
+                    mBrightnessRangeController.getCurrentBrightnessMax(),
                     mBrightnessThrottler.getBrightnessCap());
             boolean changed = false;
 
@@ -1770,10 +1844,10 @@
                             maxBrightness);
             changed |=
                     mCachedBrightnessInfo.checkAndSetInt(mCachedBrightnessInfo.hbmMode,
-                            mHbmController.getHighBrightnessMode());
+                            mBrightnessRangeController.getHighBrightnessMode());
             changed |=
                     mCachedBrightnessInfo.checkAndSetFloat(mCachedBrightnessInfo.hbmTransitionPoint,
-                            mHbmController.getTransitionPoint());
+                            mBrightnessRangeController.getTransitionPoint());
             changed |=
                     mCachedBrightnessInfo.checkAndSetInt(mCachedBrightnessInfo.brightnessMaxReason,
                             mBrightnessThrottler.getBrightnessMaxReason());
@@ -1783,10 +1857,13 @@
     }
 
     void postBrightnessChangeRunnable() {
-        mHandler.post(mOnBrightnessChangeRunnable);
+        if (!mHandler.hasCallbacks(mOnBrightnessChangeRunnable)) {
+            mHandler.post(mOnBrightnessChangeRunnable);
+        }
     }
 
-    private HighBrightnessModeController createHbmControllerLocked() {
+    private HighBrightnessModeController createHbmControllerLocked(
+            HighBrightnessModeMetadata hbmMetadata, Runnable modeChangeCallback) {
         final DisplayDevice device = mLogicalDisplay.getPrimaryDisplayDeviceLocked();
         final DisplayDeviceConfig ddConfig = device.getDisplayDeviceConfig();
         final IBinder displayToken =
@@ -1796,24 +1873,11 @@
         final DisplayDeviceConfig.HighBrightnessModeData hbmData =
                 ddConfig != null ? ddConfig.getHighBrightnessModeData() : null;
         final DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
-        return new HighBrightnessModeController(mHandler, info.width, info.height, displayToken,
-                displayUniqueId, PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX, hbmData,
-                new HighBrightnessModeController.HdrBrightnessDeviceConfig() {
-                    @Override
-                    public float getHdrBrightnessFromSdr(
-                            float sdrBrightness, float maxDesiredHdrSdrRatio) {
-                        return mDisplayDeviceConfig.getHdrBrightnessFromSdr(
-                                sdrBrightness, maxDesiredHdrSdrRatio);
-                    }
-                },
-                () -> {
-                    sendUpdatePowerState();
-                    postBrightnessChangeRunnable();
-                    // TODO(b/192258832): Switch the HBMChangeCallback to a listener pattern.
-                    if (mAutomaticBrightnessController != null) {
-                        mAutomaticBrightnessController.update();
-                    }
-                }, mHighBrightnessModeMetadata, mContext);
+        return mInjector.getHighBrightnessModeController(mHandler, info.width, info.height,
+                displayToken, displayUniqueId, PowerManager.BRIGHTNESS_MIN,
+                PowerManager.BRIGHTNESS_MAX, hbmData, (sdrBrightness, maxDesiredHdrSdrRatio) ->
+                        mDisplayDeviceConfig.getHdrBrightnessFromSdr(sdrBrightness,
+                                maxDesiredHdrSdrRatio), modeChangeCallback, hbmMetadata, mContext);
     }
 
     private BrightnessThrottler createBrightnessThrottlerLocked() {
@@ -1941,27 +2005,23 @@
     }
 
     private void loadAmbientLightSensor() {
-        DisplayDeviceConfig.SensorData lightSensor = mDisplayDeviceConfig.getAmbientLightSensor();
         final int fallbackType = mDisplayId == Display.DEFAULT_DISPLAY
                 ? Sensor.TYPE_LIGHT : SensorUtils.NO_FALLBACK;
-        mLightSensor = SensorUtils.findSensor(mSensorManager, lightSensor.type, lightSensor.name,
-                fallbackType);
+        mLightSensor = SensorUtils.findSensor(mSensorManager,
+                mDisplayDeviceConfig.getAmbientLightSensor(), fallbackType);
     }
 
     private void loadScreenOffBrightnessSensor() {
-        DisplayDeviceConfig.SensorData screenOffBrightnessSensor =
-                mDisplayDeviceConfig.getScreenOffBrightnessSensor();
         mScreenOffBrightnessSensor = SensorUtils.findSensor(mSensorManager,
-                screenOffBrightnessSensor.type, screenOffBrightnessSensor.name,
-                SensorUtils.NO_FALLBACK);
+                mDisplayDeviceConfig.getScreenOffBrightnessSensor(), SensorUtils.NO_FALLBACK);
     }
 
     private float clampScreenBrightness(float value) {
         if (Float.isNaN(value)) {
             value = PowerManager.BRIGHTNESS_MIN;
         }
-        return MathUtils.constrain(value,
-                mHbmController.getCurrentBrightnessMin(), mHbmController.getCurrentBrightnessMax());
+        return MathUtils.constrain(value, mBrightnessRangeController.getCurrentBrightnessMin(),
+                mBrightnessRangeController.getCurrentBrightnessMax());
     }
 
     private void animateScreenBrightness(float target, float sdrTarget, float rate) {
@@ -2179,8 +2239,9 @@
     }
 
     @Override
-    public void setBrightness(float brightnessValue) {
-        mDisplayBrightnessController.setBrightness(brightnessValue);
+    public void setBrightness(float brightnessValue, int userSerial) {
+        mDisplayBrightnessController.setBrightness(clampScreenBrightness(brightnessValue),
+                userSerial);
     }
 
     @Override
@@ -2194,8 +2255,9 @@
     }
 
     @Override
-    public void setBrightnessToFollow(float leadDisplayBrightness, float nits, float ambientLux) {
-        mHbmController.onAmbientLuxChange(ambientLux);
+    public void setBrightnessToFollow(float leadDisplayBrightness, float nits, float ambientLux,
+            boolean slowChange) {
+        mBrightnessRangeController.onAmbientLuxChange(ambientLux);
         if (nits < 0) {
             mDisplayBrightnessController.setBrightnessToFollow(leadDisplayBrightness);
         } else {
@@ -2207,12 +2269,13 @@
                 mDisplayBrightnessController.setBrightnessToFollow(leadDisplayBrightness);
             }
         }
+        mBrightnessToFollowSlowChange = slowChange;
         sendUpdatePowerState();
     }
 
     private void notifyBrightnessTrackerChanged(float brightness, boolean userInitiated,
             boolean wasShortTermModelActive, boolean autobrightnessEnabled,
-            boolean brightnessIsTemporary) {
+            boolean brightnessIsTemporary, boolean shouldUseAutoBrightness) {
 
         final float brightnessInNits =
                 mDisplayBrightnessController.convertToAdjustedNits(brightness);
@@ -2227,7 +2290,7 @@
                 || mAutomaticBrightnessController.isInIdleMode()
                 || !autobrightnessEnabled
                 || mBrightnessTracker == null
-                || !mAutomaticBrightnessStrategy.shouldUseAutoBrightness()
+                || !shouldUseAutoBrightness
                 || brightnessInNits < 0.0f) {
             return;
         }
@@ -2266,7 +2329,7 @@
             mDisplayBrightnessFollowers.remove(follower.getDisplayId());
             mHandler.postAtTime(() -> follower.setBrightnessToFollow(
                     PowerManager.BRIGHTNESS_INVALID_FLOAT, /* nits= */ -1,
-                    /* ambientLux= */ 0), mClock.uptimeMillis());
+                    /* ambientLux= */ 0, /* slowChange= */ false), mClock.uptimeMillis());
         }
     }
 
@@ -2276,7 +2339,7 @@
             DisplayPowerControllerInterface follower = mDisplayBrightnessFollowers.valueAt(i);
             mHandler.postAtTime(() -> follower.setBrightnessToFollow(
                     PowerManager.BRIGHTNESS_INVALID_FLOAT, /* nits= */ -1,
-                    /* ambientLux= */ 0), mClock.uptimeMillis());
+                    /* ambientLux= */ 0, /* slowChange= */ false), mClock.uptimeMillis());
         }
         mDisplayBrightnessFollowers.clear();
     }
@@ -2346,6 +2409,7 @@
         pw.println("  mReportedToPolicy="
                 + reportedToPolicyToString(mReportedScreenStateToPolicy));
         pw.println("  mIsRbcActive=" + mIsRbcActive);
+        pw.println("  mBrightnessToFollowSlowChange=" + mBrightnessToFollowSlowChange);
         IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "    ");
         mAutomaticBrightnessStrategy.dump(ipw);
 
@@ -2374,8 +2438,8 @@
 
         dumpRbcEvents(pw);
 
-        if (mHbmController != null) {
-            mHbmController.dump(pw);
+        if (mBrightnessRangeController != null) {
+            mBrightnessRangeController.dump(pw);
         }
 
         if (mBrightnessThrottler != null) {
@@ -2403,6 +2467,11 @@
         if (mDisplayStateController != null) {
             mDisplayStateController.dumpsys(pw);
         }
+
+        pw.println();
+        if (mBrightnessClamperController != null) {
+            mBrightnessClamperController.dump(ipw);
+        }
     }
 
 
@@ -2721,6 +2790,19 @@
                     mBootCompleted = true;
                     updatePowerState();
                     break;
+
+                case MSG_SET_DWBC_STRONG_MODE:
+                    setDwbcStrongMode(msg.arg1);
+                    break;
+
+                case MSG_SET_DWBC_COLOR_OVERRIDE:
+                    final float cct = Float.intBitsToFloat(msg.arg1);
+                    setDwbcOverride(cct);
+                    break;
+
+                case MSG_SET_DWBC_LOGGING_ENABLED:
+                    setDwbcLoggingEnabled(msg.arg1);
+                    break;
             }
         }
     }
@@ -2771,21 +2853,18 @@
 
     @Override
     public void setDisplayWhiteBalanceLoggingEnabled(boolean enabled) {
-        if (mDisplayWhiteBalanceController != null) {
-            mDisplayWhiteBalanceController.setLoggingEnabled(enabled);
-            mDisplayWhiteBalanceSettings.setLoggingEnabled(enabled);
-        }
+        Message msg = mHandler.obtainMessage();
+        msg.what = MSG_SET_DWBC_LOGGING_ENABLED;
+        msg.arg1 = enabled ? 1 : 0;
+        msg.sendToTarget();
     }
 
     @Override
     public void setAmbientColorTemperatureOverride(float cct) {
-        if (mDisplayWhiteBalanceController != null) {
-            mDisplayWhiteBalanceController.setAmbientColorTemperatureOverride(cct);
-            // The ambient color temperature override is only applied when the ambient color
-            // temperature changes or is updated, so it doesn't necessarily change the screen color
-            // temperature immediately. So, let's make it!
-            sendUpdatePowerState();
-        }
+        Message msg = mHandler.obtainMessage();
+        msg.what = MSG_SET_DWBC_COLOR_OVERRIDE;
+        msg.arg1 = Float.floatToIntBits(cct);
+        msg.sendToTarget();
     }
 
     /** Functional interface for providing time. */
@@ -2840,7 +2919,8 @@
                 HysteresisLevels screenBrightnessThresholds,
                 HysteresisLevels ambientBrightnessThresholdsIdle,
                 HysteresisLevels screenBrightnessThresholdsIdle, Context context,
-                HighBrightnessModeController hbmController, BrightnessThrottler brightnessThrottler,
+                BrightnessRangeController brightnessModeController,
+                BrightnessThrottler brightnessThrottler,
                 BrightnessMappingStrategy idleModeBrightnessMapper, int ambientLightHorizonShort,
                 int ambientLightHorizonLong, float userLux, float userBrightness) {
             return new AutomaticBrightnessController(callbacks, looper, sensorManager, lightSensor,
@@ -2849,9 +2929,9 @@
                     brighteningLightDebounceConfig, darkeningLightDebounceConfig,
                     resetAmbientLuxAfterWarmUpConfig, ambientBrightnessThresholds,
                     screenBrightnessThresholds, ambientBrightnessThresholdsIdle,
-                    screenBrightnessThresholdsIdle, context, hbmController, brightnessThrottler,
-                    idleModeBrightnessMapper, ambientLightHorizonShort, ambientLightHorizonLong,
-                    userLux, userBrightness);
+                    screenBrightnessThresholdsIdle, context, brightnessModeController,
+                    brightnessThrottler, idleModeBrightnessMapper, ambientLightHorizonShort,
+                    ambientLightHorizonLong, userLux, userBrightness);
         }
 
         BrightnessMappingStrategy getInteractiveModeBrightnessMapper(Resources resources,
@@ -2896,6 +2976,27 @@
                     brightnessMapper
             );
         }
+
+        HighBrightnessModeController getHighBrightnessModeController(Handler handler, int width,
+                int height, IBinder displayToken, String displayUniqueId, float brightnessMin,
+                float brightnessMax, DisplayDeviceConfig.HighBrightnessModeData hbmData,
+                HighBrightnessModeController.HdrBrightnessDeviceConfig hdrBrightnessCfg,
+                Runnable hbmChangeCallback, HighBrightnessModeMetadata hbmMetadata,
+                Context context) {
+            return new HighBrightnessModeController(handler, width, height, displayToken,
+                    displayUniqueId, brightnessMin, brightnessMax, hbmData, hdrBrightnessCfg,
+                    hbmChangeCallback, hbmMetadata, context);
+        }
+
+        DisplayWhiteBalanceController getDisplayWhiteBalanceController(Handler handler,
+                SensorManager sensorManager, Resources resources) {
+            return DisplayWhiteBalanceFactory.create(handler,
+                    sensorManager, resources);
+        }
+
+        boolean isColorFadeEnabled() {
+            return !ActivityManager.isLowRamDeviceStatic();
+        }
     }
 
     static class CachedBrightnessInfo {
diff --git a/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java b/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
index 73edb97..e3108c9 100644
--- a/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
+++ b/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
@@ -29,7 +29,7 @@
  * An interface to manage the display's power state and brightness
  */
 public interface DisplayPowerControllerInterface {
-
+    int DEFAULT_USER_SERIAL = -1;
     /**
      * Notified when the display is changed.
      *
@@ -98,7 +98,17 @@
      * Set the screen brightness of the associated display
      * @param brightness The value to which the brightness is to be set
      */
-    void setBrightness(float brightness);
+    default void setBrightness(float brightness) {
+        setBrightness(brightness, DEFAULT_USER_SERIAL);
+    }
+
+    /**
+     * Set the screen brightness of the associated display
+     * @param brightness The value to which the brightness is to be set
+     * @param userSerial The user for which the brightness value is to be set. Use userSerial = -1,
+     * if brightness needs to be updated for the current user.
+     */
+    void setBrightness(float brightness, int userSerial);
 
     /**
      * Checks if the proximity sensor is available
@@ -191,8 +201,10 @@
      * @param nits The brightness value in nits if the device supports nits. Set to a negative
      *             number otherwise.
      * @param ambientLux The lux value that will be passed to {@link HighBrightnessModeController}
+     * @param slowChange Indicates whether we should slowly animate to the given brightness value.
      */
-    void setBrightnessToFollow(float leadDisplayBrightness, float nits, float ambientLux);
+    void setBrightnessToFollow(float leadDisplayBrightness, float nits, float ambientLux,
+            boolean slowChange);
 
     /**
      * Add an additional display that will copy the brightness value from this display. This is used
diff --git a/services/core/java/com/android/server/display/DisplayPowerProximityStateController.java b/services/core/java/com/android/server/display/DisplayPowerProximityStateController.java
index c074786..882c02f 100644
--- a/services/core/java/com/android/server/display/DisplayPowerProximityStateController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerProximityStateController.java
@@ -358,10 +358,8 @@
         if (DEBUG_PRETEND_PROXIMITY_SENSOR_ABSENT || mDisplayId != Display.DEFAULT_DISPLAY) {
             return;
         }
-        final DisplayDeviceConfig.SensorData proxSensor =
-                mDisplayDeviceConfig.getProximitySensor();
-        mProximitySensor = SensorUtils.findSensor(mSensorManager, proxSensor.type, proxSensor.name,
-                Sensor.TYPE_PROXIMITY);
+        mProximitySensor = SensorUtils.findSensor(mSensorManager,
+                mDisplayDeviceConfig.getProximitySensor(), Sensor.TYPE_PROXIMITY);
         if (mProximitySensor != null) {
             mProximityThreshold = Math.min(mProximitySensor.getMaximumRange(),
                     TYPICAL_PROXIMITY_THRESHOLD);
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index c7c0fab..b00b7a1 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -70,7 +70,7 @@
 
     private static final String UNIQUE_ID_PREFIX = "local:";
 
-    private static final String PROPERTY_EMULATOR_CIRCULAR = "ro.emulator.circular";
+    private static final String PROPERTY_EMULATOR_CIRCULAR = "ro.boot.emulator.circular";
 
     private final LongSparseArray<LocalDisplayDevice> mDevices = new LongSparseArray<>();
 
@@ -496,7 +496,7 @@
         private void loadDisplayDeviceConfig() {
             // Load display device config
             final Context context = getOverlayContext();
-            mDisplayDeviceConfig = DisplayDeviceConfig.create(context, mPhysicalDisplayId,
+            mDisplayDeviceConfig = mInjector.createDisplayDeviceConfig(context, mPhysicalDisplayId,
                     mIsFirstDisplay);
 
             // Load brightness HWC quirk
@@ -701,11 +701,15 @@
                         maxDisplayMode == null ? mInfo.width : maxDisplayMode.getPhysicalWidth();
                 final int maxHeight =
                         maxDisplayMode == null ? mInfo.height : maxDisplayMode.getPhysicalHeight();
-                mInfo.displayCutout = DisplayCutout.fromResourcesRectApproximation(res,
-                        mInfo.uniqueId, maxWidth, maxHeight, mInfo.width, mInfo.height);
 
-                mInfo.roundedCorners = RoundedCorners.fromResources(
-                        res, mInfo.uniqueId, maxWidth, maxHeight, mInfo.width, mInfo.height);
+                // We cannot determine cutouts and rounded corners of external displays.
+                if (mStaticDisplayInfo.isInternal) {
+                    mInfo.displayCutout = DisplayCutout.fromResourcesRectApproximation(res,
+                            mInfo.uniqueId, maxWidth, maxHeight, mInfo.width, mInfo.height);
+                    mInfo.roundedCorners = RoundedCorners.fromResources(
+                            res, mInfo.uniqueId, maxWidth, maxHeight, mInfo.width, mInfo.height);
+                }
+
                 mInfo.installOrientation = mStaticDisplayInfo.installOrientation;
 
                 mInfo.displayShape = DisplayShape.fromResources(
@@ -1332,6 +1336,11 @@
         public SurfaceControlProxy getSurfaceControlProxy() {
             return new SurfaceControlProxy();
         }
+
+        public DisplayDeviceConfig createDisplayDeviceConfig(Context context,
+                long physicalDisplayId, boolean isFirstDisplay) {
+            return DisplayDeviceConfig.create(context, physicalDisplayId, isFirstDisplay);
+        }
     }
 
     public interface DisplayEventListener {
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index d01b03f..26f8029 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -42,6 +42,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.display.layout.DisplayIdProducer;
 import com.android.server.display.layout.Layout;
+import com.android.server.utils.FoldSettingWrapper;
 
 import java.io.PrintWriter;
 import java.util.Arrays;
@@ -142,6 +143,7 @@
     private final Listener mListener;
     private final DisplayManagerService.SyncRoot mSyncRoot;
     private final LogicalDisplayMapperHandler mHandler;
+    private final FoldSettingWrapper mFoldSettingWrapper;
     private final PowerManager mPowerManager;
 
     /**
@@ -189,21 +191,23 @@
 
     LogicalDisplayMapper(@NonNull Context context, @NonNull DisplayDeviceRepository repo,
             @NonNull Listener listener, @NonNull DisplayManagerService.SyncRoot syncRoot,
-            @NonNull Handler handler) {
+            @NonNull Handler handler, FoldSettingWrapper foldSettingWrapper) {
         this(context, repo, listener, syncRoot, handler,
                 new DeviceStateToLayoutMap((isDefault) -> isDefault ? DEFAULT_DISPLAY
-                        : sNextNonDefaultDisplayId++));
+                        : sNextNonDefaultDisplayId++), foldSettingWrapper);
     }
 
     LogicalDisplayMapper(@NonNull Context context, @NonNull DisplayDeviceRepository repo,
             @NonNull Listener listener, @NonNull DisplayManagerService.SyncRoot syncRoot,
-            @NonNull Handler handler, @NonNull DeviceStateToLayoutMap deviceStateToLayoutMap) {
+            @NonNull Handler handler, @NonNull DeviceStateToLayoutMap deviceStateToLayoutMap,
+            FoldSettingWrapper foldSettingWrapper) {
         mSyncRoot = syncRoot;
         mPowerManager = context.getSystemService(PowerManager.class);
         mInteractive = mPowerManager.isInteractive();
         mHandler = new LogicalDisplayMapperHandler(handler.getLooper());
         mDisplayDeviceRepo = repo;
         mListener = listener;
+        mFoldSettingWrapper = foldSettingWrapper;
         mSingleDisplayDemoMode = SystemProperties.getBoolean("persist.demo.singledisplay", false);
         mSupportsConcurrentInternalDisplays = context.getResources().getBoolean(
                 com.android.internal.R.bool.config_supportsConcurrentInternalDisplays);
@@ -531,9 +535,10 @@
      * Returns if the device should be put to sleep or not.
      *
      * Includes a check to verify that the device state that we are moving to, {@code pendingState},
-     * is the same as the physical state of the device, {@code baseState}. Different values for
-     * these parameters indicate a device state override is active, and we shouldn't put the device
-     * to sleep to provide a better user experience.
+     * is the same as the physical state of the device, {@code baseState}. Also if the
+     * 'Stay Awake On Fold' is not enabled. Different values for these parameters indicate a device
+     * state override is active, and we shouldn't put the device to sleep to provide a better user
+     * experience.
      *
      * @param pendingState device state we are moving to
      * @param currentState device state we are currently in
@@ -551,7 +556,7 @@
                 && mDeviceStatesOnWhichToSleep.get(pendingState)
                 && !mDeviceStatesOnWhichToSleep.get(currentState)
                 && !isOverrideActive
-                && isInteractive && isBootCompleted;
+                && isInteractive && isBootCompleted && !mFoldSettingWrapper.shouldStayAwakeOnFold();
     }
 
     private boolean areAllTransitioningDisplaysOffLocked() {
diff --git a/services/core/java/com/android/server/display/NormalBrightnessModeController.java b/services/core/java/com/android/server/display/NormalBrightnessModeController.java
new file mode 100644
index 0000000..135ebd8
--- /dev/null
+++ b/services/core/java/com/android/server/display/NormalBrightnessModeController.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 com.android.server.display;
+
+import android.annotation.NonNull;
+import android.os.PowerManager;
+
+import com.android.server.display.DisplayDeviceConfig.BrightnessLimitMapType;
+
+import java.io.PrintWriter;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Limits brightness for normal-brightness mode, based on ambient lux
+ **/
+class NormalBrightnessModeController {
+    @NonNull
+    private Map<BrightnessLimitMapType, Map<Float, Float>> mMaxBrightnessLimits = new HashMap<>();
+    private float mAmbientLux = Float.MAX_VALUE;
+    private boolean mAutoBrightnessEnabled = false;
+
+    // brightness limit in normal brightness mode, based on ambient lux.
+    private float mMaxBrightness = PowerManager.BRIGHTNESS_MAX;
+
+    boolean onAmbientLuxChange(float ambientLux) {
+        mAmbientLux = ambientLux;
+        return recalculateMaxBrightness();
+    }
+
+    boolean setAutoBrightnessState(int state) {
+        boolean isEnabled = state == AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED;
+        if (isEnabled != mAutoBrightnessEnabled) {
+            mAutoBrightnessEnabled = isEnabled;
+            return recalculateMaxBrightness();
+        }
+        return false;
+    }
+
+    float getCurrentBrightnessMax() {
+        return mMaxBrightness;
+    }
+
+    boolean resetNbmData(
+            @NonNull Map<BrightnessLimitMapType, Map<Float, Float>> maxBrightnessLimits) {
+        mMaxBrightnessLimits = maxBrightnessLimits;
+        return recalculateMaxBrightness();
+    }
+
+    void dump(PrintWriter pw) {
+        pw.println("NormalBrightnessModeController:");
+        pw.println("  mAutoBrightnessEnabled=" + mAutoBrightnessEnabled);
+        pw.println("  mAmbientLux=" + mAmbientLux);
+        pw.println("  mMaxBrightness=" + mMaxBrightness);
+        pw.println("  mMaxBrightnessLimits=" + mMaxBrightnessLimits);
+    }
+
+    private boolean recalculateMaxBrightness() {
+        float foundAmbientBoundary = Float.MAX_VALUE;
+        float foundMaxBrightness = PowerManager.BRIGHTNESS_MAX;
+
+        Map<Float, Float> maxBrightnessPoints = null;
+
+        if (mAutoBrightnessEnabled) {
+            maxBrightnessPoints = mMaxBrightnessLimits.get(BrightnessLimitMapType.ADAPTIVE);
+        }
+
+        if (maxBrightnessPoints == null) {
+            maxBrightnessPoints = mMaxBrightnessLimits.get(BrightnessLimitMapType.DEFAULT);
+        }
+
+        if (maxBrightnessPoints != null) {
+            for (Map.Entry<Float, Float> brightnessPoint : maxBrightnessPoints.entrySet()) {
+                float ambientBoundary = brightnessPoint.getKey();
+                // find ambient lux upper boundary closest to current ambient lux
+                if (ambientBoundary > mAmbientLux && ambientBoundary < foundAmbientBoundary) {
+                    foundMaxBrightness = brightnessPoint.getValue();
+                    foundAmbientBoundary = ambientBoundary;
+                }
+            }
+        }
+
+        if (mMaxBrightness != foundMaxBrightness) {
+            mMaxBrightness = foundMaxBrightness;
+            return true;
+        }
+        return false;
+    }
+}
diff --git a/services/core/java/com/android/server/display/PersistentDataStore.java b/services/core/java/com/android/server/display/PersistentDataStore.java
index 6d6ed72..2d7792d 100644
--- a/services/core/java/com/android/server/display/PersistentDataStore.java
+++ b/services/core/java/com/android/server/display/PersistentDataStore.java
@@ -133,6 +133,7 @@
 
     private static final String TAG_BRIGHTNESS_NITS_FOR_DEFAULT_DISPLAY =
             "brightness-nits-for-default-display";
+    public static final int DEFAULT_USER_ID = -1;
 
     // Remembered Wifi display devices.
     private ArrayList<WifiDisplay> mRememberedWifiDisplays = new ArrayList<WifiDisplay>();
@@ -294,7 +295,7 @@
         return false;
     }
 
-    public float getBrightness(DisplayDevice device) {
+    public float getBrightness(DisplayDevice device, int userSerial) {
         if (device == null || !device.hasStableUniqueId()) {
             return Float.NaN;
         }
@@ -302,10 +303,10 @@
         if (state == null) {
             return Float.NaN;
         }
-        return state.getBrightness();
+        return state.getBrightness(userSerial);
     }
 
-    public boolean setBrightness(DisplayDevice displayDevice, float brightness) {
+    public boolean setBrightness(DisplayDevice displayDevice, float brightness, int userSerial) {
         if (displayDevice == null || !displayDevice.hasStableUniqueId()) {
             return false;
         }
@@ -314,7 +315,7 @@
             return false;
         }
         final DisplayState state = getDisplayState(displayDeviceUniqueId, true);
-        if (state.setBrightness(brightness)) {
+        if (state.setBrightness(brightness, userSerial)) {
             setDirty();
             return true;
         }
@@ -611,6 +612,7 @@
             state.saveToXml(serializer);
             serializer.endTag(null, TAG_DISPLAY);
         }
+
         serializer.endTag(null, TAG_DISPLAY_STATES);
         serializer.startTag(null, TAG_STABLE_DEVICE_VALUES);
         mStableDeviceValues.saveToXml(serializer);
@@ -649,7 +651,8 @@
 
     private static final class DisplayState {
         private int mColorMode;
-        private float mBrightness = Float.NaN;
+
+        private SparseArray<Float> mPerUserBrightness = new SparseArray<>();
         private int mWidth;
         private int mHeight;
         private float mRefreshRate;
@@ -670,16 +673,25 @@
             return mColorMode;
         }
 
-        public boolean setBrightness(float brightness) {
-            if (brightness == mBrightness) {
+        public boolean setBrightness(float brightness, int userSerial) {
+            // Remove the default user brightness, before setting a new user-specific value.
+            // This is a one-time operation, required to restructure the config after user-specific
+            // brightness was introduced.
+            mPerUserBrightness.remove(DEFAULT_USER_ID);
+
+            if (getBrightness(userSerial) == brightness) {
                 return false;
             }
-            mBrightness = brightness;
+            mPerUserBrightness.set(userSerial, brightness);
             return true;
         }
 
-        public float getBrightness() {
-            return mBrightness;
+        public float getBrightness(int userSerial) {
+            float brightness = mPerUserBrightness.get(userSerial, Float.NaN);
+            if (Float.isNaN(brightness)) {
+                brightness = mPerUserBrightness.get(DEFAULT_USER_ID, Float.NaN);
+            }
+            return brightness;
         }
 
         public boolean setBrightnessConfiguration(BrightnessConfiguration configuration,
@@ -729,12 +741,7 @@
                         mColorMode = Integer.parseInt(value);
                         break;
                     case TAG_BRIGHTNESS_VALUE:
-                        String brightness = parser.nextText();
-                        try {
-                            mBrightness = Float.parseFloat(brightness);
-                        } catch (NumberFormatException e) {
-                            mBrightness = Float.NaN;
-                        }
+                        loadBrightnessFromXml(parser);
                         break;
                     case TAG_BRIGHTNESS_CONFIGURATIONS:
                         mDisplayBrightnessConfigurations.loadFromXml(parser);
@@ -760,11 +767,12 @@
             serializer.text(Integer.toString(mColorMode));
             serializer.endTag(null, TAG_COLOR_MODE);
 
-            serializer.startTag(null, TAG_BRIGHTNESS_VALUE);
-            if (!Float.isNaN(mBrightness)) {
-                serializer.text(Float.toString(mBrightness));
+            for (int i = 0; i < mPerUserBrightness.size(); i++) {
+                serializer.startTag(null, TAG_BRIGHTNESS_VALUE);
+                serializer.attributeInt(null, ATTR_USER_SERIAL, mPerUserBrightness.keyAt(i));
+                serializer.text(Float.toString(mPerUserBrightness.valueAt(i)));
+                serializer.endTag(null, TAG_BRIGHTNESS_VALUE);
             }
-            serializer.endTag(null, TAG_BRIGHTNESS_VALUE);
 
             serializer.startTag(null, TAG_BRIGHTNESS_CONFIGURATIONS);
             mDisplayBrightnessConfigurations.saveToXml(serializer);
@@ -785,12 +793,33 @@
 
         public void dump(final PrintWriter pw, final String prefix) {
             pw.println(prefix + "ColorMode=" + mColorMode);
-            pw.println(prefix + "BrightnessValue=" + mBrightness);
+            pw.println(prefix + "BrightnessValues: ");
+            for (int i = 0; i < mPerUserBrightness.size(); i++) {
+                pw.println("User: " + mPerUserBrightness.keyAt(i)
+                        + " Value: " + mPerUserBrightness.valueAt(i));
+            }
             pw.println(prefix + "DisplayBrightnessConfigurations: ");
             mDisplayBrightnessConfigurations.dump(pw, prefix);
             pw.println(prefix + "Resolution=" + mWidth + " " + mHeight);
             pw.println(prefix + "RefreshRate=" + mRefreshRate);
         }
+
+        private void loadBrightnessFromXml(TypedXmlPullParser parser)
+                throws IOException, XmlPullParserException {
+            int userSerial;
+            try {
+                userSerial = parser.getAttributeInt(null, ATTR_USER_SERIAL);
+            } catch (NumberFormatException | XmlPullParserException e) {
+                userSerial = DEFAULT_USER_ID;
+                Slog.e(TAG, "Failed to read user serial", e);
+            }
+            String brightness = parser.nextText();
+            try {
+                mPerUserBrightness.set(userSerial, Float.parseFloat(brightness));
+            } catch (NumberFormatException nfe) {
+                Slog.e(TAG, "Failed to read brightness", nfe);
+            }
+        }
     }
 
     private static final class StableDeviceValues {
diff --git a/services/core/java/com/android/server/display/TEST_MAPPING b/services/core/java/com/android/server/display/TEST_MAPPING
index c4a566f..57c2e01 100644
--- a/services/core/java/com/android/server/display/TEST_MAPPING
+++ b/services/core/java/com/android/server/display/TEST_MAPPING
@@ -16,20 +16,6 @@
                 {"exclude-annotation": "androidx.test.filters.FlakyTest"},
                 {"exclude-annotation": "org.junit.Ignore"}
             ]
-        },
-        {
-            "name": "CtsMediaProjectionTestCases",
-            "options": [
-                {
-                    "exclude-annotation": "android.platform.test.annotations.FlakyTest"
-                },
-                {
-                    "exclude-annotation": "androidx.test.filters.FlakyTest"
-                },
-                {
-                    "exclude-annotation": "org.junit.Ignore"
-                }
-            ]
         }
     ]
 }
\ No newline at end of file
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 7574de8..ffd62a3 100644
--- a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
+++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
@@ -29,6 +29,7 @@
 import com.android.server.display.AutomaticBrightnessController;
 import com.android.server.display.BrightnessSetting;
 import com.android.server.display.DisplayBrightnessState;
+import com.android.server.display.brightness.strategy.AutomaticBrightnessStrategy;
 import com.android.server.display.brightness.strategy.DisplayBrightnessStrategy;
 
 import java.io.PrintWriter;
@@ -38,6 +39,8 @@
  * display. Applies the chosen brightness.
  */
 public final class DisplayBrightnessController {
+    private static final int DEFAULT_USER_SERIAL = -1;
+
     // The ID of the display tied to this DisplayBrightnessController
     private final int mDisplayId;
 
@@ -132,11 +135,21 @@
     public DisplayBrightnessState updateBrightness(
             DisplayManagerInternal.DisplayPowerRequest displayPowerRequest,
             int targetDisplayState) {
+
+        DisplayBrightnessState state;
         synchronized (mLock) {
             mDisplayBrightnessStrategy = mDisplayBrightnessStrategySelector.selectStrategy(
                     displayPowerRequest, targetDisplayState);
-            return mDisplayBrightnessStrategy.updateBrightness(displayPowerRequest);
+            state = mDisplayBrightnessStrategy.updateBrightness(displayPowerRequest);
         }
+
+        // This is a temporary measure until AutomaticBrightnessStrategy works as a traditional
+        // strategy.
+        // TODO: Remove when AutomaticBrightnessStrategy is populating the values directly.
+        if (state != null) {
+            state = addAutomaticBrightnessState(state);
+        }
+        return state;
     }
 
     /**
@@ -274,8 +287,16 @@
      * Notifies the brightnessSetting to persist the supplied brightness value.
      */
     public void setBrightness(float brightnessValue) {
+        setBrightness(brightnessValue, DEFAULT_USER_SERIAL);
+    }
+
+    /**
+     * Notifies the brightnessSetting to persist the supplied brightness value for a user.
+     */
+    public void setBrightness(float brightnessValue, int userSerial) {
         // Update the setting, which will eventually call back into DPC to have us actually
         // update the display with the new value.
+        mBrightnessSetting.setUserSerial(userSerial);
         mBrightnessSetting.setBrightness(brightnessValue);
         if (mDisplayId == Display.DEFAULT_DISPLAY && mPersistBrightnessNitsForDefaultDisplay) {
             float nits = convertToNits(brightnessValue);
@@ -312,6 +333,13 @@
     }
 
     /**
+     * TODO(b/253226419): Remove once auto-brightness is a fully-functioning strategy.
+     */
+    public AutomaticBrightnessStrategy getAutomaticBrightnessStrategy() {
+        return mDisplayBrightnessStrategySelector.getAutomaticBrightnessStrategy();
+    }
+
+    /**
      * Convert a brightness float scale value to a nit value. Adjustments, such as RBC, are not
      * applied. This is used when storing the brightness in nits for the default display and when
      * passing the brightness value to follower displays.
@@ -415,6 +443,18 @@
         }
     }
 
+    /**
+     * TODO(b/253226419): Remove once auto-brightness is a fully-functioning strategy.
+     */
+    private DisplayBrightnessState addAutomaticBrightnessState(DisplayBrightnessState state) {
+        AutomaticBrightnessStrategy autoStrat = getAutomaticBrightnessStrategy();
+
+        DisplayBrightnessState.Builder builder = DisplayBrightnessState.Builder.from(state);
+        builder.setShouldUseAutoBrightness(
+                autoStrat != null && autoStrat.shouldUseAutoBrightness());
+        return builder.build();
+    }
+
     @GuardedBy("mLock")
     private void setTemporaryBrightnessLocked(float temporaryBrightness) {
         mDisplayBrightnessStrategySelector.getTemporaryDisplayBrightnessStrategy()
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 02ca2d3..45f1be0 100644
--- a/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java
+++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java
@@ -25,6 +25,7 @@
 
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.display.brightness.strategy.AutomaticBrightnessStrategy;
 import com.android.server.display.brightness.strategy.BoostBrightnessStrategy;
 import com.android.server.display.brightness.strategy.DisplayBrightnessStrategy;
 import com.android.server.display.brightness.strategy.DozeBrightnessStrategy;
@@ -60,6 +61,8 @@
     private final FollowerBrightnessStrategy mFollowerBrightnessStrategy;
     // The brightness strategy used to manage the brightness state when the request is invalid.
     private final InvalidBrightnessStrategy mInvalidBrightnessStrategy;
+    // Controls brightness when automatic (adaptive) brightness is running.
+    private final AutomaticBrightnessStrategy mAutomaticBrightnessStrategy;
 
     // We take note of the old brightness strategy so that we can know when the strategy changes.
     private String mOldBrightnessStrategyName;
@@ -81,6 +84,7 @@
         mBoostBrightnessStrategy = injector.getBoostBrightnessStrategy();
         mFollowerBrightnessStrategy = injector.getFollowerBrightnessStrategy(displayId);
         mInvalidBrightnessStrategy = injector.getInvalidBrightnessStrategy();
+        mAutomaticBrightnessStrategy = injector.getAutomaticBrightnessStrategy(context, displayId);
         mAllowAutoBrightnessWhileDozingConfig = context.getResources().getBoolean(
                 R.bool.config_allowAutoBrightnessWhileDozing);
         mOldBrightnessStrategyName = mInvalidBrightnessStrategy.getName();
@@ -130,6 +134,10 @@
         return mFollowerBrightnessStrategy;
     }
 
+    public AutomaticBrightnessStrategy getAutomaticBrightnessStrategy() {
+        return mAutomaticBrightnessStrategy;
+    }
+
     /**
      * Returns a boolean flag indicating if the light sensor is to be used to decide the screen
      * brightness when dozing
@@ -198,5 +206,9 @@
         InvalidBrightnessStrategy getInvalidBrightnessStrategy() {
             return new InvalidBrightnessStrategy();
         }
+
+        AutomaticBrightnessStrategy getAutomaticBrightnessStrategy(Context context, int displayId) {
+            return new AutomaticBrightnessStrategy(context, displayId);
+        }
     }
 }
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
new file mode 100644
index 0000000..9345a3d
--- /dev/null
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.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
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.os.PowerManager;
+
+import java.io.PrintWriter;
+
+abstract class BrightnessClamper<T> {
+
+    protected float mBrightnessCap = PowerManager.BRIGHTNESS_MAX;
+    protected boolean mIsActive = false;
+
+    float getBrightnessCap() {
+        return mBrightnessCap;
+    }
+
+    boolean isActive() {
+        return mIsActive;
+    }
+
+    void dump(PrintWriter writer) {
+        writer.println("BrightnessClamper:" + getType());
+        writer.println(" mBrightnessCap: " + mBrightnessCap);
+        writer.println(" mIsActive: " + mIsActive);
+    }
+
+    @NonNull
+    abstract Type getType();
+
+    abstract void onDeviceConfigChanged();
+
+    abstract void onDisplayChanged(T displayData);
+
+    abstract void stop();
+
+    enum Type {
+        THERMAL
+    }
+}
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
new file mode 100644
index 0000000..d0f28c3
--- /dev/null
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.BrightnessClamper.Type;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.os.PowerManager;
+import android.provider.DeviceConfig;
+import android.provider.DeviceConfigInterface;
+import android.util.IndentingPrintWriter;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.display.DisplayDeviceConfig;
+import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData;
+import com.android.server.display.feature.DeviceConfigParameterProvider;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Executor;
+
+/**
+ * Clampers controller, all in DisplayControllerHandler
+ */
+public class BrightnessClamperController {
+
+    private static final boolean ENABLED = false;
+
+    private final DeviceConfigParameterProvider mDeviceConfigParameterProvider;
+    private final Handler mHandler;
+    private final ClamperChangeListener mClamperChangeListenerExternal;
+
+    private final Executor mExecutor;
+    private final List<BrightnessClamper<? super DisplayDeviceData>> mClampers = new ArrayList<>();
+    private final DeviceConfig.OnPropertiesChangedListener mOnPropertiesChangedListener =
+            properties -> mClampers.forEach(BrightnessClamper::onDeviceConfigChanged);
+    private float mBrightnessCap = PowerManager.BRIGHTNESS_MAX;
+    @Nullable
+    private Type mClamperType = null;
+
+    public BrightnessClamperController(Handler handler,
+            ClamperChangeListener clamperChangeListener, DisplayDeviceData data) {
+        this(new Injector(), handler, clamperChangeListener, data);
+    }
+
+    @VisibleForTesting
+    BrightnessClamperController(Injector injector, Handler handler,
+            ClamperChangeListener clamperChangeListener, DisplayDeviceData data) {
+        mDeviceConfigParameterProvider = injector.getDeviceConfigParameterProvider();
+        mHandler = handler;
+        mClamperChangeListenerExternal = clamperChangeListener;
+        mExecutor = new HandlerExecutor(handler);
+
+        Runnable clamperChangeRunnableInternal = this::recalculateBrightnessCap;
+
+        ClamperChangeListener clamperChangeListenerInternal = () -> {
+            if (!mHandler.hasCallbacks(clamperChangeRunnableInternal)) {
+                mHandler.post(clamperChangeRunnableInternal);
+            }
+        };
+
+        if (ENABLED) {
+            mClampers.add(
+                    new BrightnessThermalClamper(handler, clamperChangeListenerInternal, data));
+            start();
+        }
+    }
+
+    /**
+     * Should be called when display changed. Forwards the call to individual clampers
+     */
+    public void onDisplayChanged(DisplayDeviceData data) {
+        mClampers.forEach(clamper -> clamper.onDisplayChanged(data));
+    }
+
+    /**
+     * Applies clamping
+     * Called in DisplayControllerHandler
+     */
+    public float clamp(float value) {
+        return Math.min(value, mBrightnessCap);
+    }
+
+    /**
+     * Used to dump ClampersController state.
+     */
+    public void dump(PrintWriter writer) {
+        writer.println("BrightnessClampersController:");
+        writer.println("  mBrightnessCap: " + mBrightnessCap);
+        writer.println("  mClamperType: " + mClamperType);
+        IndentingPrintWriter ipw = new IndentingPrintWriter(writer, "    ");
+        mClampers.forEach(clamper -> clamper.dump(ipw));
+    }
+
+    /**
+     * This method should be called when the ClamperController is no longer in use.
+     * Called in DisplayControllerHandler
+     */
+    public void stop() {
+        mDeviceConfigParameterProvider.removeOnPropertiesChangedListener(
+                mOnPropertiesChangedListener);
+        mClampers.forEach(BrightnessClamper::stop);
+    }
+
+
+    // Called in DisplayControllerHandler
+    private void recalculateBrightnessCap() {
+        float brightnessCap = PowerManager.BRIGHTNESS_MAX;
+        Type clamperType = null;
+
+        BrightnessClamper<?> minClamper = mClampers.stream()
+                .filter(BrightnessClamper::isActive)
+                .min((clamper1, clamper2) -> Float.compare(clamper1.getBrightnessCap(),
+                        clamper2.getBrightnessCap())).orElse(null);
+
+        if (minClamper != null) {
+            brightnessCap = minClamper.getBrightnessCap();
+            clamperType = minClamper.getType();
+        }
+
+        if (mBrightnessCap != brightnessCap || mClamperType != clamperType) {
+            mBrightnessCap = brightnessCap;
+            mClamperType = clamperType;
+            mClamperChangeListenerExternal.onChanged();
+        }
+    }
+
+    private void start() {
+        mDeviceConfigParameterProvider.addOnPropertiesChangedListener(
+                mExecutor, mOnPropertiesChangedListener);
+    }
+
+    /**
+     * Clampers change listener
+     */
+    public interface ClamperChangeListener {
+        /**
+         * Notifies that clamper state changed
+         */
+        void onChanged();
+    }
+
+    @VisibleForTesting
+    static class Injector {
+        DeviceConfigParameterProvider getDeviceConfigParameterProvider() {
+            return new DeviceConfigParameterProvider(DeviceConfigInterface.REAL);
+        }
+    }
+
+    /**
+     * Data for clampers
+     */
+    public static class DisplayDeviceData implements BrightnessThermalClamper.ThermalData {
+        @NonNull
+        private final String mUniqueDisplayId;
+        @NonNull
+        private final String mThermalThrottlingDataId;
+
+        private final DisplayDeviceConfig mDisplayDeviceConfig;
+
+        public DisplayDeviceData(@NonNull String uniqueDisplayId,
+                @NonNull String thermalThrottlingDataId,
+                @NonNull DisplayDeviceConfig displayDeviceConfig) {
+            mUniqueDisplayId = uniqueDisplayId;
+            mThermalThrottlingDataId = thermalThrottlingDataId;
+            mDisplayDeviceConfig = displayDeviceConfig;
+        }
+
+
+        @NonNull
+        @Override
+        public String getUniqueDisplayId() {
+            return mUniqueDisplayId;
+        }
+
+        @NonNull
+        @Override
+        public String getThermalThrottlingDataId() {
+            return mThermalThrottlingDataId;
+        }
+
+        @Nullable
+        @Override
+        public ThermalBrightnessThrottlingData getThermalBrightnessThrottlingData() {
+            return mDisplayDeviceConfig.getThermalBrightnessThrottlingDataMapByThrottlingId().get(
+                    mThermalThrottlingDataId);
+        }
+    }
+}
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
new file mode 100644
index 0000000..8ae962b
--- /dev/null
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessThermalClamper.java
@@ -0,0 +1,272 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.DisplayDeviceConfig.DEFAULT_ID;
+import static com.android.server.display.brightness.clamper.BrightnessClamperController.ClamperChangeListener;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.os.Handler;
+import android.os.IThermalEventListener;
+import android.os.IThermalService;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.Temperature;
+import android.provider.DeviceConfigInterface;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData;
+import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel;
+import com.android.server.display.feature.DeviceConfigParameterProvider;
+import com.android.server.display.utils.DeviceConfigParsingUtils;
+
+import java.io.PrintWriter;
+import java.util.List;
+import java.util.Map;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+
+
+class BrightnessThermalClamper extends
+        BrightnessClamper<BrightnessThermalClamper.ThermalData> {
+
+    private static final String TAG = "BrightnessThermalClamper";
+
+    @Nullable
+    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
+    private Map<String, Map<String, ThermalBrightnessThrottlingData>>
+            mThermalThrottlingDataOverride = Map.of();
+    // data from DisplayDeviceConfig, for particular display+dataSet
+    @Nullable
+    private ThermalBrightnessThrottlingData mThermalThrottlingDataFromDeviceConfig = null;
+    // Active data, if mDataOverride contains data for mUniqueDisplayId, mDataId, then use it,
+    // otherwise mDataFromDeviceConfig
+    @Nullable
+    private ThermalBrightnessThrottlingData mThermalThrottlingDataActive = null;
+    private boolean mStarted = false;
+    @Nullable
+    private String mUniqueDisplayId = null;
+    @Nullable
+    private String mDataId = null;
+    @Temperature.ThrottlingStatus
+    private int mThrottlingStatus = Temperature.THROTTLING_NONE;
+
+    private final IThermalEventListener mThermalEventListener = new IThermalEventListener.Stub() {
+        @Override
+        public void notifyThrottling(Temperature temperature) {
+            @Temperature.ThrottlingStatus int status = temperature.getStatus();
+            mHandler.post(() -> thermalStatusChanged(status));
+        }
+    };
+
+    private final BiFunction<String, String, ThrottlingLevel> mDataPointMapper = (key, value) -> {
+        try {
+            int status = DeviceConfigParsingUtils.parseThermalStatus(key);
+            float brightnessPoint = DeviceConfigParsingUtils.parseBrightness(value);
+            return new ThrottlingLevel(status, brightnessPoint);
+        } catch (IllegalArgumentException iae) {
+            return null;
+        }
+    };
+
+    private final Function<List<ThrottlingLevel>, ThermalBrightnessThrottlingData>
+            mDataSetMapper = ThermalBrightnessThrottlingData::create;
+
+
+    BrightnessThermalClamper(Handler handler, ClamperChangeListener listener,
+            ThermalData thermalData) {
+        this(new Injector(), handler, listener, thermalData);
+    }
+
+    @VisibleForTesting
+    BrightnessThermalClamper(Injector injector, Handler handler,
+            ClamperChangeListener listener, ThermalData thermalData) {
+        mThermalService = injector.getThermalService();
+        mConfigParameterProvider = injector.getDeviceConfigParameterProvider();
+        mHandler = handler;
+        mChangelistener = listener;
+        mHandler.post(() -> {
+            setDisplayData(thermalData);
+            loadOverrideData();
+            start();
+        });
+
+    }
+
+    @Override
+    @NonNull
+    Type getType() {
+        return Type.THERMAL;
+    }
+
+    @Override
+    void onDeviceConfigChanged() {
+        mHandler.post(() -> {
+            loadOverrideData();
+            recalculateActiveData();
+        });
+    }
+
+    @Override
+    void onDisplayChanged(ThermalData data) {
+        mHandler.post(() -> {
+            setDisplayData(data);
+            recalculateActiveData();
+        });
+    }
+
+    @Override
+    void stop() {
+        if (!mStarted) {
+            return;
+        }
+        try {
+            mThermalService.unregisterThermalEventListener(mThermalEventListener);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Failed to unregister thermal status listener", e);
+        }
+        mStarted = false;
+    }
+
+    @Override
+    void dump(PrintWriter writer) {
+        writer.println("BrightnessThermalClamper:");
+        writer.println("  mStarted: " + mStarted);
+        if (mThermalService != null) {
+            writer.println("  ThermalService available");
+        } else {
+            writer.println("  ThermalService not available");
+        }
+        writer.println("  mThrottlingStatus: " + mThrottlingStatus);
+        writer.println("  mUniqueDisplayId: " + mUniqueDisplayId);
+        writer.println("  mDataId: " + mDataId);
+        writer.println("  mDataOverride: " + mThermalThrottlingDataOverride);
+        writer.println("  mDataFromDeviceConfig: " + mThermalThrottlingDataFromDeviceConfig);
+        writer.println("  mDataActive: " + mThermalThrottlingDataActive);
+        super.dump(writer);
+    }
+
+    private void recalculateActiveData() {
+        if (mUniqueDisplayId == null || mDataId == null) {
+            return;
+        }
+        mThermalThrottlingDataActive = mThermalThrottlingDataOverride
+                .getOrDefault(mUniqueDisplayId, Map.of()).getOrDefault(mDataId,
+                        mThermalThrottlingDataFromDeviceConfig);
+
+        recalculateBrightnessCap();
+    }
+
+    private void loadOverrideData() {
+        String throttlingDataOverride = mConfigParameterProvider.getBrightnessThrottlingData();
+        mThermalThrottlingDataOverride = DeviceConfigParsingUtils.parseDeviceConfigMap(
+                throttlingDataOverride, mDataPointMapper, mDataSetMapper);
+    }
+
+    private void setDisplayData(@NonNull ThermalData data) {
+        mUniqueDisplayId = data.getUniqueDisplayId();
+        mDataId = data.getThermalThrottlingDataId();
+        mThermalThrottlingDataFromDeviceConfig = data.getThermalBrightnessThrottlingData();
+        if (mThermalThrottlingDataFromDeviceConfig == null && !DEFAULT_ID.equals(mDataId)) {
+            Slog.wtf(TAG,
+                    "Thermal throttling data is missing for thermalThrottlingDataId=" + mDataId);
+        }
+    }
+
+    private void recalculateBrightnessCap() {
+        float brightnessCap = PowerManager.BRIGHTNESS_MAX;
+        boolean isActive = false;
+
+        if (mThermalThrottlingDataActive != null) {
+            // Throttling levels are sorted by increasing severity
+            for (ThrottlingLevel level : mThermalThrottlingDataActive.throttlingLevels) {
+                if (level.thermalStatus <= mThrottlingStatus) {
+                    brightnessCap = level.brightness;
+                    isActive = true;
+                } else {
+                    // Throttling levels that are greater than the current status are irrelevant
+                    break;
+                }
+            }
+        }
+
+        if (brightnessCap  != mBrightnessCap || mIsActive != isActive) {
+            mBrightnessCap = brightnessCap;
+            mIsActive = isActive;
+            mChangelistener.onChanged();
+        }
+    }
+
+    private void thermalStatusChanged(@Temperature.ThrottlingStatus int status) {
+        if (mThrottlingStatus != status) {
+            mThrottlingStatus = status;
+            recalculateBrightnessCap();
+        }
+    }
+
+    private void start() {
+        if (mThermalService == null) {
+            Slog.e(TAG, "Could not observe thermal status. Service not available");
+            return;
+        }
+        try {
+            // We get a callback immediately upon registering so there's no need to query
+            // for the current value.
+            mThermalService.registerThermalEventListenerWithType(mThermalEventListener,
+                    Temperature.TYPE_SKIN);
+            mStarted = true;
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Failed to register thermal status listener", e);
+        }
+    }
+
+    interface ThermalData {
+        @NonNull
+        String getUniqueDisplayId();
+
+        @NonNull
+        String getThermalThrottlingDataId();
+
+        @Nullable
+        ThermalBrightnessThrottlingData getThermalBrightnessThrottlingData();
+    }
+
+    @VisibleForTesting
+    static class Injector {
+        IThermalService getThermalService() {
+            return IThermalService.Stub.asInterface(
+                    ServiceManager.getService(Context.THERMAL_SERVICE));
+        }
+
+        DeviceConfigParameterProvider getDeviceConfigParameterProvider() {
+            return new DeviceConfigParameterProvider(DeviceConfigInterface.REAL);
+        }
+    }
+}
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 0e885dc..bcd5259 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
@@ -73,6 +73,9 @@
     // the user has enabled the auto-brightness from the settings, it is disabled because the
     // display is off
     private boolean mIsAutoBrightnessEnabled = false;
+    // Indicates if auto-brightness is disabled due to the display being off. Needed for metric
+    // purposes.
+    private boolean mAutoBrightnessDisabledDueToDisplayOff;
     // If the auto-brightness model for the last manual changes done by the user.
     private boolean mIsShortTermModelActive = false;
 
@@ -96,24 +99,21 @@
      * AutomaticBrightnessController accounting for any manual changes made by the user.
      */
     public void setAutoBrightnessState(int targetDisplayState,
-            boolean allowAutoBrightnessWhileDozingConfig,
-            float brightnessState, int brightnessReason, int policy,
+            boolean allowAutoBrightnessWhileDozingConfig, int brightnessReason, int policy,
             float lastUserSetScreenBrightness, boolean userSetBrightnessChanged) {
         final boolean autoBrightnessEnabledInDoze =
                 allowAutoBrightnessWhileDozingConfig
                         && Display.isDozeState(targetDisplayState);
         mIsAutoBrightnessEnabled = shouldUseAutoBrightness()
                 && (targetDisplayState == Display.STATE_ON || autoBrightnessEnabledInDoze)
-                && (Float.isNaN(brightnessState)
-                || brightnessReason == BrightnessReason.REASON_TEMPORARY
-                || brightnessReason == BrightnessReason.REASON_BOOST)
-                && mAutomaticBrightnessController != null
-                && brightnessReason != BrightnessReason.REASON_FOLLOWER;
-        final boolean autoBrightnessDisabledDueToDisplayOff = shouldUseAutoBrightness()
+                && brightnessReason != BrightnessReason.REASON_OVERRIDE
+                && mAutomaticBrightnessController != null;
+        mAutoBrightnessDisabledDueToDisplayOff = shouldUseAutoBrightness()
                 && !(targetDisplayState == Display.STATE_ON || autoBrightnessEnabledInDoze);
         final int autoBrightnessState = mIsAutoBrightnessEnabled
+                    && brightnessReason != BrightnessReason.REASON_FOLLOWER
                 ? AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED
-                : autoBrightnessDisabledDueToDisplayOff
+                : mAutoBrightnessDisabledDueToDisplayOff
                         ? AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE
                         : AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED;
 
@@ -125,6 +125,10 @@
         return mIsAutoBrightnessEnabled;
     }
 
+    public boolean isAutoBrightnessDisabledDueToDisplayOff() {
+        return mAutoBrightnessDisabledDueToDisplayOff;
+    }
+
     /**
      * Updates the {@link BrightnessConfiguration} that is currently being used by the associated
      * display.
@@ -287,8 +291,6 @@
         mAutoBrightnessAdjustmentReasonsFlags = isTemporaryAutoBrightnessAdjustmentApplied()
                 ? BrightnessReason.ADJUSTMENT_AUTO_TEMP
                 : BrightnessReason.ADJUSTMENT_AUTO;
-        mAppliedAutoBrightness = BrightnessUtils.isValidBrightnessValue(brightnessState)
-                || brightnessState == PowerManager.BRIGHTNESS_OFF_FLOAT;
         float newAutoBrightnessAdjustment =
                 (mAutomaticBrightnessController != null)
                         ? mAutomaticBrightnessController.getAutomaticScreenBrightnessAdjustment()
@@ -345,8 +347,7 @@
     /**
      * Sets if the auto-brightness is applied on the latest brightness change.
      */
-    @VisibleForTesting
-    void setAutoBrightnessApplied(boolean autoBrightnessApplied) {
+    public void setAutoBrightnessApplied(boolean autoBrightnessApplied) {
         mAppliedAutoBrightness = autoBrightnessApplied;
     }
 
diff --git a/services/core/java/com/android/server/display/color/ColorDisplayService.java b/services/core/java/com/android/server/display/color/ColorDisplayService.java
index c0ea5fe..d8831fa 100644
--- a/services/core/java/com/android/server/display/color/ColorDisplayService.java
+++ b/services/core/java/com/android/server/display/color/ColorDisplayService.java
@@ -1696,11 +1696,10 @@
     @VisibleForTesting
     final class BinderService extends IColorDisplayManager.Stub {
 
+        @android.annotation.EnforcePermission(android.Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS)
         @Override
         public void setColorMode(int colorMode) {
-            getContext().enforceCallingOrSelfPermission(
-                    Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS,
-                    "Permission required to set display color mode");
+            setColorMode_enforcePermission();
             final long token = Binder.clearCallingIdentity();
             try {
                 setColorModeInternal(colorMode);
@@ -1790,11 +1789,10 @@
             }
         }
 
+        @android.annotation.EnforcePermission(android.Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS)
         @Override
         public boolean setNightDisplayActivated(boolean activated) {
-            getContext().enforceCallingOrSelfPermission(
-                    Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS,
-                    "Permission required to set night display activated");
+            setNightDisplayActivated_enforcePermission();
             final long token = Binder.clearCallingIdentity();
             try {
                 mNightDisplayTintController.setActivated(activated);
@@ -1814,11 +1812,10 @@
             }
         }
 
+        @android.annotation.EnforcePermission(android.Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS)
         @Override
         public boolean setNightDisplayColorTemperature(int temperature) {
-            getContext().enforceCallingOrSelfPermission(
-                    Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS,
-                    "Permission required to set night display temperature");
+            setNightDisplayColorTemperature_enforcePermission();
             final long token = Binder.clearCallingIdentity();
             try {
                 return mNightDisplayTintController.setColorTemperature(temperature);
@@ -1837,11 +1834,10 @@
             }
         }
 
+        @android.annotation.EnforcePermission(android.Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS)
         @Override
         public boolean setNightDisplayAutoMode(int autoMode) {
-            getContext().enforceCallingOrSelfPermission(
-                    Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS,
-                    "Permission required to set night display auto mode");
+            setNightDisplayAutoMode_enforcePermission();
             final long token = Binder.clearCallingIdentity();
             try {
                 return setNightDisplayAutoModeInternal(autoMode);
@@ -1850,11 +1846,10 @@
             }
         }
 
+        @android.annotation.EnforcePermission(android.Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS)
         @Override
         public int getNightDisplayAutoMode() {
-            getContext().enforceCallingOrSelfPermission(
-                    Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS,
-                    "Permission required to get night display auto mode");
+            getNightDisplayAutoMode_enforcePermission();
             final long token = Binder.clearCallingIdentity();
             try {
                 return getNightDisplayAutoModeInternal();
@@ -1873,11 +1868,10 @@
             }
         }
 
+        @android.annotation.EnforcePermission(android.Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS)
         @Override
         public boolean setNightDisplayCustomStartTime(Time startTime) {
-            getContext().enforceCallingOrSelfPermission(
-                    Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS,
-                    "Permission required to set night display custom start time");
+            setNightDisplayCustomStartTime_enforcePermission();
             final long token = Binder.clearCallingIdentity();
             try {
                 return setNightDisplayCustomStartTimeInternal(startTime);
@@ -1896,11 +1890,10 @@
             }
         }
 
+        @android.annotation.EnforcePermission(android.Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS)
         @Override
         public boolean setNightDisplayCustomEndTime(Time endTime) {
-            getContext().enforceCallingOrSelfPermission(
-                    Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS,
-                    "Permission required to set night display custom end time");
+            setNightDisplayCustomEndTime_enforcePermission();
             final long token = Binder.clearCallingIdentity();
             try {
                 return setNightDisplayCustomEndTimeInternal(endTime);
@@ -1919,11 +1912,10 @@
             }
         }
 
+        @android.annotation.EnforcePermission(android.Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS)
         @Override
         public boolean setDisplayWhiteBalanceEnabled(boolean enabled) {
-            getContext().enforceCallingOrSelfPermission(
-                    Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS,
-                    "Permission required to set night display activated");
+            setDisplayWhiteBalanceEnabled_enforcePermission();
             final long token = Binder.clearCallingIdentity();
             try {
                 return setDisplayWhiteBalanceSettingEnabled(enabled);
@@ -1952,11 +1944,10 @@
             }
         }
 
+        @android.annotation.EnforcePermission(android.Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS)
         @Override
         public boolean setReduceBrightColorsActivated(boolean activated) {
-            getContext().enforceCallingOrSelfPermission(
-                    Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS,
-                    "Permission required to set reduce bright colors activation state");
+            setReduceBrightColorsActivated_enforcePermission();
             final long token = Binder.clearCallingIdentity();
             try {
                 return setReduceBrightColorsActivatedInternal(activated);
@@ -1985,11 +1976,10 @@
             }
         }
 
+        @android.annotation.EnforcePermission(android.Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS)
         @Override
         public boolean setReduceBrightColorsStrength(int strength) {
-            getContext().enforceCallingOrSelfPermission(
-                    Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS,
-                    "Permission required to set reduce bright colors strength");
+            setReduceBrightColorsStrength_enforcePermission();
             final long token = Binder.clearCallingIdentity();
             try {
                 return setReduceBrightColorsStrengthInternal(strength);
diff --git a/services/core/java/com/android/server/display/feature/DeviceConfigParameterProvider.java b/services/core/java/com/android/server/display/feature/DeviceConfigParameterProvider.java
new file mode 100644
index 0000000..feebdf1
--- /dev/null
+++ b/services/core/java/com/android/server/display/feature/DeviceConfigParameterProvider.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.feature;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.hardware.display.DisplayManager;
+import android.provider.DeviceConfig;
+import android.provider.DeviceConfigInterface;
+import android.util.Slog;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Helper class to access all DeviceConfig features for display_manager namespace
+ *
+ **/
+public class DeviceConfigParameterProvider {
+
+    private static final String TAG = "DisplayFeatureProvider";
+
+    private final DeviceConfigInterface mDeviceConfig;
+
+    public DeviceConfigParameterProvider(DeviceConfigInterface deviceConfig) {
+        mDeviceConfig = deviceConfig;
+    }
+
+    // feature: revamping_display_power_controller_feature
+    // parameter: use_newly_structured_display_power_controller
+    public boolean isNewPowerControllerFeatureEnabled() {
+        return mDeviceConfig.getBoolean(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+                DisplayManager.DeviceConfig.KEY_NEW_POWER_CONTROLLER, true);
+    }
+
+    // feature: hdr_output_control
+    // parameter: enable_hdr_output_control
+    public boolean isHdrOutputControlFeatureEnabled() {
+        return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+                DisplayManager.HDR_OUTPUT_CONTROL_FLAG, true);
+    }
+
+    // feature: flexible_brightness_range_feature
+    // parameter: normal_brightness_mode_controller_enabled
+    public boolean isNormalBrightnessControllerFeatureEnabled() {
+        return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+                DisplayManager.DeviceConfig.KEY_USE_NORMAL_BRIGHTNESS_MODE_CONTROLLER, false);
+    }
+
+    // feature: smooth_display_feature
+    // parameter: peak_refresh_rate_default
+    public float getPeakRefreshRateDefault() {
+        return mDeviceConfig.getFloat(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+                DisplayManager.DeviceConfig.KEY_PEAK_REFRESH_RATE_DEFAULT, -1);
+    }
+
+    // Test parameters
+    // usage e.g.: adb shell device_config put display_manager refresh_rate_in_hbm_sunlight 90
+
+    // allows to customize brightness throttling data
+    public String getBrightnessThrottlingData() {
+        return mDeviceConfig.getString(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+                DisplayManager.DeviceConfig.KEY_BRIGHTNESS_THROTTLING_DATA, null);
+    }
+
+    public int getRefreshRateInHbmSunlight() {
+        return mDeviceConfig.getInt(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+                DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HBM_SUNLIGHT, -1);
+    }
+
+    public int getRefreshRateInHbmHdr() {
+        return mDeviceConfig.getInt(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+                DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HBM_HDR, -1);
+    }
+
+
+    public int getRefreshRateInHighZone() {
+        return mDeviceConfig.getInt(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+                DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HIGH_ZONE, -1);
+    }
+
+    public int getRefreshRateInLowZone() {
+        return mDeviceConfig.getInt(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+                DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_LOW_ZONE, -1);
+    }
+
+    /** Return null if no such property or wrong format (not comma separated integers). */
+    @Nullable
+    public int[] getHighAmbientBrightnessThresholds() {
+        return getIntArrayProperty(DisplayManager.DeviceConfig
+                .KEY_FIXED_REFRESH_RATE_HIGH_AMBIENT_BRIGHTNESS_THRESHOLDS);
+    }
+
+    /** Return null if no such property or wrong format (not comma separated integers). */
+    @Nullable
+    public int[] getHighDisplayBrightnessThresholds() {
+        return getIntArrayProperty(DisplayManager.DeviceConfig
+                .KEY_FIXED_REFRESH_RATE_HIGH_DISPLAY_BRIGHTNESS_THRESHOLDS);
+    }
+
+    /** Return null if no such property or wrong format (not comma separated integers). */
+    @Nullable
+    public int[] getLowDisplayBrightnessThresholds() {
+        return getIntArrayProperty(DisplayManager.DeviceConfig
+                .KEY_FIXED_REFRESH_RATE_LOW_DISPLAY_BRIGHTNESS_THRESHOLDS);
+    }
+
+    /** Return null if no such property or wrong format (not comma separated integers). */
+    @Nullable
+    public int[] getLowAmbientBrightnessThresholds() {
+        return getIntArrayProperty(DisplayManager.DeviceConfig
+                .KEY_FIXED_REFRESH_RATE_LOW_AMBIENT_BRIGHTNESS_THRESHOLDS);
+    }
+
+    /** add property change listener to DeviceConfig */
+    public void addOnPropertiesChangedListener(Executor executor,
+            DeviceConfig.OnPropertiesChangedListener listener) {
+        mDeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+                executor, listener);
+    }
+
+    /** remove property change listener from DeviceConfig */
+    public void removeOnPropertiesChangedListener(
+            DeviceConfig.OnPropertiesChangedListener listener) {
+        mDeviceConfig.removeOnPropertiesChangedListener(listener);
+    }
+
+    @Nullable
+    private int[] getIntArrayProperty(String prop) {
+        String strArray = mDeviceConfig.getString(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, prop,
+                null);
+
+        if (strArray != null) {
+            return parseIntArray(strArray);
+        }
+        return null;
+    }
+
+    @Nullable
+    private int[] parseIntArray(@NonNull String strArray) {
+        String[] items = strArray.split(",");
+        int[] array = new int[items.length];
+
+        try {
+            for (int i = 0; i < array.length; i++) {
+                array[i] = Integer.parseInt(items[i]);
+            }
+        } catch (NumberFormatException e) {
+            Slog.e(TAG, "Incorrect format for array: '" + strArray + "'", e);
+            array = null;
+        }
+
+        return array;
+    }
+}
diff --git a/services/core/java/com/android/server/display/layout/Layout.java b/services/core/java/com/android/server/display/layout/Layout.java
index b55d7d5..d9ec3de 100644
--- a/services/core/java/com/android/server/display/layout/Layout.java
+++ b/services/core/java/com/android/server/display/layout/Layout.java
@@ -22,6 +22,8 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.text.TextUtils;
+import android.util.ArraySet;
 import android.util.Slog;
 import android.view.DisplayAddress;
 
@@ -77,7 +79,7 @@
             DisplayIdProducer idProducer) {
         createDisplayLocked(address, /* isDefault= */ true, /* isEnabled= */ true,
                 DEFAULT_DISPLAY_GROUP_NAME, idProducer, POSITION_UNKNOWN,
-                NO_LEAD_DISPLAY, /* brightnessThrottlingMapId= */ null,
+                /* leadDisplayAddress= */ null, /* brightnessThrottlingMapId= */ null,
                 /* refreshRateZoneId= */ null, /* refreshRateThermalThrottlingMapId= */ null);
     }
 
@@ -90,19 +92,20 @@
      * @param displayGroupName Name of the display group to which the display is assigned.
      * @param idProducer Produces the logical display id.
      * @param position Indicates the position this display is facing in this layout.
-     * @param leadDisplayId Display that this one follows (-1 if none).
+     * @param leadDisplayAddress Address of a display that this one follows ({@code null} if none).
      * @param brightnessThrottlingMapId Name of which brightness throttling policy should be used.
      * @param refreshRateZoneId Layout limited refresh rate zone name.
      * @param refreshRateThermalThrottlingMapId Name of which refresh rate throttling
      *                                          policy should be used.
-
+     *
      * @exception IllegalArgumentException When a default display owns a display group other than
      *            DEFAULT_DISPLAY_GROUP.
      */
     public void createDisplayLocked(
             @NonNull DisplayAddress address, boolean isDefault, boolean isEnabled,
-            String displayGroupName, DisplayIdProducer idProducer, int position, int leadDisplayId,
-            String brightnessThrottlingMapId, @Nullable String refreshRateZoneId,
+            String displayGroupName, DisplayIdProducer idProducer, int position,
+            @Nullable DisplayAddress leadDisplayAddress, String brightnessThrottlingMapId,
+            @Nullable String refreshRateZoneId,
             @Nullable String refreshRateThermalThrottlingMapId) {
         if (contains(address)) {
             Slog.w(TAG, "Attempting to add second definition for display-device: " + address);
@@ -115,21 +118,27 @@
             return;
         }
 
-        // Assign a logical display ID and create the new display.
-        // Note that the logical display ID is saved into the layout, so when switching between
-        // different layouts, a logical display can be destroyed and later recreated with the
-        // same logical display ID.
         if (displayGroupName == null) {
             displayGroupName = DEFAULT_DISPLAY_GROUP_NAME;
         }
         if (isDefault && !displayGroupName.equals(DEFAULT_DISPLAY_GROUP_NAME)) {
             throw new IllegalArgumentException("Default display should own DEFAULT_DISPLAY_GROUP");
         }
+        if (isDefault && leadDisplayAddress != null) {
+            throw new IllegalArgumentException("Default display cannot have a lead display");
+        }
+        if (address.equals(leadDisplayAddress)) {
+            throw new IllegalArgumentException("Lead display address cannot be the same as display "
+                    + " address");
+        }
+        // Assign a logical display ID and create the new display.
+        // Note that the logical display ID is saved into the layout, so when switching between
+        // different layouts, a logical display can be destroyed and later recreated with the
+        // same logical display ID.
         final int logicalDisplayId = idProducer.getId(isDefault);
-        leadDisplayId = isDefault ? NO_LEAD_DISPLAY : leadDisplayId;
 
         final Display display = new Display(address, logicalDisplayId, isEnabled, displayGroupName,
-                brightnessThrottlingMapId, position, leadDisplayId, refreshRateZoneId,
+                brightnessThrottlingMapId, position, leadDisplayAddress, refreshRateZoneId,
                 refreshRateThermalThrottlingMapId);
 
         mDisplays.add(display);
@@ -146,6 +155,43 @@
     }
 
     /**
+     * Applies post-processing to displays to make sure the information of each display is
+     * up-to-date.
+     *
+     * <p>At creation of a display, lead display is specified by display address. At post
+     * processing, we convert it to logical display ID.
+     */
+    public void postProcessLocked() {
+        for (int i = 0; i < mDisplays.size(); i++) {
+            Display display = mDisplays.get(i);
+            if (display.getLogicalDisplayId() == DEFAULT_DISPLAY) {
+                display.setLeadDisplayId(NO_LEAD_DISPLAY);
+                continue;
+            }
+            DisplayAddress leadDisplayAddress = display.getLeadDisplayAddress();
+            if (leadDisplayAddress == null) {
+                display.setLeadDisplayId(NO_LEAD_DISPLAY);
+                continue;
+            }
+            Display leadDisplay = getByAddress(leadDisplayAddress);
+            if (leadDisplay == null) {
+                throw new IllegalArgumentException("Cannot find a lead display whose address is "
+                        + leadDisplayAddress);
+            }
+            if (!TextUtils.equals(display.getDisplayGroupName(),
+                    leadDisplay.getDisplayGroupName())) {
+                throw new IllegalArgumentException("Lead display(" + leadDisplay + ") should be in "
+                        + "the same display group of the display(" + display + ")");
+            }
+            if (hasCyclicLeadDisplay(display)) {
+                throw new IllegalArgumentException("Display(" + display + ") has a cyclic lead "
+                        + "display");
+            }
+            display.setLeadDisplayId(leadDisplay.getLogicalDisplayId());
+        }
+    }
+
+    /**
      * @param address The address to check.
      *
      * @return True if the specified address is used in this layout.
@@ -208,6 +254,20 @@
         return mDisplays.size();
     }
 
+    private boolean hasCyclicLeadDisplay(Display display) {
+        ArraySet<Display> visited = new ArraySet<>();
+
+        while (display != null) {
+            if (visited.contains(display)) {
+                return true;
+            }
+            visited.add(display);
+            DisplayAddress leadDisplayAddress = display.getLeadDisplayAddress();
+            display = leadDisplayAddress == null ? null : getByAddress(leadDisplayAddress);
+        }
+        return false;
+    }
+
     /**
      * Describes how a {@link LogicalDisplay} is built from {@link DisplayDevice}s.
      */
@@ -240,8 +300,9 @@
         @Nullable
         private final String mThermalBrightnessThrottlingMapId;
 
-        // The ID of the lead display that this display will follow in a layout. -1 means no lead.
-        private final int mLeadDisplayId;
+        // The address of the lead display that is specified in display-layout-configuration.
+        @Nullable
+        private final DisplayAddress mLeadDisplayAddress;
 
         // Refresh rate zone id for specific layout
         @Nullable
@@ -250,9 +311,13 @@
         @Nullable
         private final String mThermalRefreshRateThrottlingMapId;
 
+        // The ID of the lead display that this display will follow in a layout. -1 means no lead.
+        // This is determined using {@code mLeadDisplayAddress}.
+        private int mLeadDisplayId;
+
         private Display(@NonNull DisplayAddress address, int logicalDisplayId, boolean isEnabled,
                 @NonNull String displayGroupName, String brightnessThrottlingMapId, int position,
-                int leadDisplayId, @Nullable String refreshRateZoneId,
+                @Nullable DisplayAddress leadDisplayAddress, @Nullable String refreshRateZoneId,
                 @Nullable String refreshRateThermalThrottlingMapId) {
             mAddress = address;
             mLogicalDisplayId = logicalDisplayId;
@@ -260,9 +325,10 @@
             mDisplayGroupName = displayGroupName;
             mPosition = position;
             mThermalBrightnessThrottlingMapId = brightnessThrottlingMapId;
+            mLeadDisplayAddress = leadDisplayAddress;
             mRefreshRateZoneId = refreshRateZoneId;
             mThermalRefreshRateThrottlingMapId = refreshRateThermalThrottlingMapId;
-            mLeadDisplayId = leadDisplayId;
+            mLeadDisplayId = NO_LEAD_DISPLAY;
         }
 
         @Override
@@ -276,6 +342,7 @@
                     + ", mThermalBrightnessThrottlingMapId: " + mThermalBrightnessThrottlingMapId
                     + ", mRefreshRateZoneId: " + mRefreshRateZoneId
                     + ", mLeadDisplayId: " + mLeadDisplayId
+                    + ", mLeadDisplayAddress: " + mLeadDisplayAddress
                     + ", mThermalRefreshRateThrottlingMapId: " + mThermalRefreshRateThrottlingMapId
                     + "}";
         }
@@ -297,6 +364,7 @@
                     otherDisplay.mThermalBrightnessThrottlingMapId)
                     && Objects.equals(otherDisplay.mRefreshRateZoneId, this.mRefreshRateZoneId)
                     && this.mLeadDisplayId == otherDisplay.mLeadDisplayId
+                    && Objects.equals(mLeadDisplayAddress, otherDisplay.mLeadDisplayAddress)
                     && Objects.equals(mThermalRefreshRateThrottlingMapId,
                     otherDisplay.mThermalRefreshRateThrottlingMapId);
         }
@@ -309,9 +377,10 @@
             result = 31 * result + mLogicalDisplayId;
             result = 31 * result + mDisplayGroupName.hashCode();
             result = 31 * result + mAddress.hashCode();
-            result = 31 * result + mThermalBrightnessThrottlingMapId.hashCode();
+            result = 31 * result + Objects.hashCode(mThermalBrightnessThrottlingMapId);
             result = 31 * result + Objects.hashCode(mRefreshRateZoneId);
             result = 31 * result + mLeadDisplayId;
+            result = 31 * result + Objects.hashCode(mLeadDisplayAddress);
             result = 31 * result + Objects.hashCode(mThermalRefreshRateThrottlingMapId);
             return result;
         }
@@ -360,8 +429,20 @@
             return mLeadDisplayId;
         }
 
+        /**
+         * @return Display address of the display that this one follows.
+         */
+        @Nullable
+        public DisplayAddress getLeadDisplayAddress() {
+            return mLeadDisplayAddress;
+        }
+
         public String getRefreshRateThermalThrottlingMapId() {
             return mThermalRefreshRateThrottlingMapId;
         }
+
+        private void setLeadDisplayId(int id) {
+            mLeadDisplayId = id;
+        }
     }
 }
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 1889578..11e35ce 100644
--- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
@@ -20,6 +20,7 @@
 import static android.hardware.display.DisplayManagerInternal.REFRESH_RATE_LIMIT_HIGH_BRIGHTNESS_MODE;
 import static android.os.PowerManager.BRIGHTNESS_INVALID;
 
+import android.annotation.IntegerRes;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.ContentResolver;
@@ -68,6 +69,7 @@
 import com.android.internal.os.BackgroundThread;
 import com.android.server.LocalServices;
 import com.android.server.display.DisplayDeviceConfig;
+import com.android.server.display.feature.DeviceConfigParameterProvider;
 import com.android.server.display.utils.AmbientFilter;
 import com.android.server.display.utils.AmbientFilterFactory;
 import com.android.server.display.utils.SensorUtils;
@@ -84,6 +86,7 @@
 import java.util.Locale;
 import java.util.Objects;
 import java.util.concurrent.Callable;
+import java.util.function.IntSupplier;
 
 /**
  * The DisplayModeDirector is responsible for determining what modes are allowed to be automatically
@@ -117,7 +120,7 @@
     private final SensorObserver mSensorObserver;
     private final HbmObserver mHbmObserver;
     private final SkinThermalStatusObserver mSkinThermalStatusObserver;
-    private final DeviceConfigInterface mDeviceConfig;
+    private final DeviceConfigParameterProvider mConfigParameterProvider;
     private final DeviceConfigDisplaySettings mDeviceConfigDisplaySettings;
 
     @GuardedBy("mLock")
@@ -157,7 +160,7 @@
         mSupportedModesByDisplay = new SparseArray<>();
         mDefaultModeByDisplay = new SparseArray<>();
         mAppRequestObserver = new AppRequestObserver();
-        mDeviceConfig = injector.getDeviceConfig();
+        mConfigParameterProvider = new DeviceConfigParameterProvider(injector.getDeviceConfig());
         mDeviceConfigDisplaySettings = new DeviceConfigDisplaySettings();
         mSettingsObserver = new SettingsObserver(context, handler);
         mBrightnessObserver = new BrightnessObserver(context, handler, injector);
@@ -681,9 +684,9 @@
         synchronized (mLock) {
             mDefaultDisplayDeviceConfig = displayDeviceConfig;
             mSettingsObserver.setRefreshRates(displayDeviceConfig,
-                /* attemptLoadingFromDeviceConfig= */ true);
+                /* attemptReadFromFeatureParams= */ true);
             mBrightnessObserver.updateBlockingZoneThresholds(displayDeviceConfig,
-                /* attemptLoadingFromDeviceConfig= */ true);
+                /* attemptReadFromFeatureParams= */ true);
             mBrightnessObserver.reloadLightSensor(displayDeviceConfig);
             mHbmObserver.setupHdrRefreshRates(displayDeviceConfig);
         }
@@ -1087,7 +1090,7 @@
             // reading from the DeviceConfig is an intensive IO operation and having it in the
             // startup phase where we thrive to keep the latency very low has significant impact.
             setRefreshRates(/* displayDeviceConfig= */ null,
-                /* attemptLoadingFromDeviceConfig= */ false);
+                /* attemptReadFromFeatureParams= */ false);
         }
 
         /**
@@ -1095,8 +1098,8 @@
          * if missing from DisplayDeviceConfig, and finally fallback to config.xml.
          */
         public void setRefreshRates(DisplayDeviceConfig displayDeviceConfig,
-                boolean attemptLoadingFromDeviceConfig) {
-            setDefaultPeakRefreshRate(displayDeviceConfig, attemptLoadingFromDeviceConfig);
+                boolean attemptReadFromFeatureParams) {
+            setDefaultPeakRefreshRate(displayDeviceConfig, attemptReadFromFeatureParams);
             mDefaultRefreshRate =
                     (displayDeviceConfig == null) ? (float) mContext.getResources().getInteger(
                         R.integer.config_defaultRefreshRate)
@@ -1113,9 +1116,9 @@
             cr.registerContentObserver(mMatchContentFrameRateSetting, false /*notifyDescendants*/,
                     this);
 
-            Float deviceConfigDefaultPeakRefresh =
-                    mDeviceConfigDisplaySettings.getDefaultPeakRefreshRate();
-            if (deviceConfigDefaultPeakRefresh != null) {
+            float deviceConfigDefaultPeakRefresh =
+                    mConfigParameterProvider.getPeakRefreshRateDefault();
+            if (deviceConfigDefaultPeakRefresh != -1) {
                 mDefaultPeakRefreshRate = deviceConfigDefaultPeakRefresh;
             }
 
@@ -1137,7 +1140,7 @@
             synchronized (mLock) {
                 if (defaultPeakRefreshRate == null) {
                     setDefaultPeakRefreshRate(mDefaultDisplayDeviceConfig,
-                            /* attemptLoadingFromDeviceConfig= */ false);
+                            /* attemptReadFromFeatureParams= */ false);
                     updateRefreshRateSettingLocked();
                 } else if (mDefaultPeakRefreshRate != defaultPeakRefreshRate) {
                     mDefaultPeakRefreshRate = defaultPeakRefreshRate;
@@ -1171,18 +1174,17 @@
         }
 
         private void setDefaultPeakRefreshRate(DisplayDeviceConfig displayDeviceConfig,
-                boolean attemptLoadingFromDeviceConfig) {
-            Float defaultPeakRefreshRate = null;
+                boolean attemptReadFromFeatureParams) {
+            float defaultPeakRefreshRate = -1;
 
-            if (attemptLoadingFromDeviceConfig) {
+            if (attemptReadFromFeatureParams) {
                 try {
-                    defaultPeakRefreshRate =
-                        mDeviceConfigDisplaySettings.getDefaultPeakRefreshRate();
+                    defaultPeakRefreshRate = mConfigParameterProvider.getPeakRefreshRateDefault();
                 } catch (Exception exception) {
                     // Do nothing
                 }
             }
-            if (defaultPeakRefreshRate == null) {
+            if (defaultPeakRefreshRate == -1) {
                 defaultPeakRefreshRate =
                         (displayDeviceConfig == null) ? (float) mContext.getResources().getInteger(
                                 R.integer.config_defaultPeakRefreshRate)
@@ -1528,7 +1530,7 @@
             mHandler = handler;
             mInjector = injector;
             updateBlockingZoneThresholds(/* displayDeviceConfig= */ null,
-                /* attemptLoadingFromDeviceConfig= */ false);
+                /* attemptReadFromFeatureParams= */ false);
             mRefreshRateInHighZone = context.getResources().getInteger(
                     R.integer.config_fixedRefreshRateInHighZone);
         }
@@ -1537,10 +1539,10 @@
          * This is used to update the blocking zone thresholds from the DeviceConfig, which
          * if missing from DisplayDeviceConfig, and finally fallback to config.xml.
          */
-        public void updateBlockingZoneThresholds(DisplayDeviceConfig displayDeviceConfig,
-                boolean attemptLoadingFromDeviceConfig) {
-            loadLowBrightnessThresholds(displayDeviceConfig, attemptLoadingFromDeviceConfig);
-            loadHighBrightnessThresholds(displayDeviceConfig, attemptLoadingFromDeviceConfig);
+        public void updateBlockingZoneThresholds(@Nullable DisplayDeviceConfig displayDeviceConfig,
+                boolean attemptReadFromFeatureParams) {
+            loadLowBrightnessThresholds(displayDeviceConfig, attemptReadFromFeatureParams);
+            loadHighBrightnessThresholds(displayDeviceConfig, attemptReadFromFeatureParams);
         }
 
         @VisibleForTesting
@@ -1579,20 +1581,20 @@
             return mRefreshRateInLowZone;
         }
 
-        private void loadLowBrightnessThresholds(DisplayDeviceConfig displayDeviceConfig,
-                boolean attemptLoadingFromDeviceConfig) {
-            loadRefreshRateInHighZone(displayDeviceConfig, attemptLoadingFromDeviceConfig);
-            loadRefreshRateInLowZone(displayDeviceConfig, attemptLoadingFromDeviceConfig);
+        private void loadLowBrightnessThresholds(@Nullable DisplayDeviceConfig displayDeviceConfig,
+                boolean attemptReadFromFeatureParams) {
+            loadRefreshRateInHighZone(displayDeviceConfig, attemptReadFromFeatureParams);
+            loadRefreshRateInLowZone(displayDeviceConfig, attemptReadFromFeatureParams);
             mLowDisplayBrightnessThresholds = loadBrightnessThresholds(
-                    () -> mDeviceConfigDisplaySettings.getLowDisplayBrightnessThresholds(),
+                    () -> mConfigParameterProvider.getLowDisplayBrightnessThresholds(),
                     () -> displayDeviceConfig.getLowDisplayBrightnessThresholds(),
                     R.array.config_brightnessThresholdsOfPeakRefreshRate,
-                    displayDeviceConfig, attemptLoadingFromDeviceConfig);
+                    displayDeviceConfig, attemptReadFromFeatureParams);
             mLowAmbientBrightnessThresholds = loadBrightnessThresholds(
-                    () -> mDeviceConfigDisplaySettings.getLowAmbientBrightnessThresholds(),
+                    () -> mConfigParameterProvider.getLowAmbientBrightnessThresholds(),
                     () -> displayDeviceConfig.getLowAmbientBrightnessThresholds(),
                     R.array.config_ambientThresholdsOfPeakRefreshRate,
-                    displayDeviceConfig, attemptLoadingFromDeviceConfig);
+                    displayDeviceConfig, attemptReadFromFeatureParams);
             if (mLowDisplayBrightnessThresholds.length != mLowAmbientBrightnessThresholds.length) {
                 throw new RuntimeException("display low brightness threshold array and ambient "
                         + "brightness threshold array have different length: "
@@ -1604,55 +1606,55 @@
         }
 
         private void loadRefreshRateInLowZone(DisplayDeviceConfig displayDeviceConfig,
-                boolean attemptLoadingFromDeviceConfig) {
-            int refreshRateInLowZone =
-                    (displayDeviceConfig == null) ? mContext.getResources().getInteger(
-                        R.integer.config_defaultRefreshRateInZone)
-                        : displayDeviceConfig.getDefaultLowBlockingZoneRefreshRate();
-            if (attemptLoadingFromDeviceConfig) {
+                boolean attemptReadFromFeatureParams) {
+            int refreshRateInLowZone = -1;
+            if (attemptReadFromFeatureParams) {
                 try {
-                    refreshRateInLowZone = mDeviceConfig.getInt(
-                        DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
-                        DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_LOW_ZONE,
-                        refreshRateInLowZone);
+                    refreshRateInLowZone = mConfigParameterProvider.getRefreshRateInLowZone();
                 } catch (Exception exception) {
                     // Do nothing
                 }
             }
+            if (refreshRateInLowZone == -1) {
+                refreshRateInLowZone = (displayDeviceConfig == null)
+                        ? mContext.getResources().getInteger(
+                                R.integer.config_defaultRefreshRateInZone)
+                        : displayDeviceConfig.getDefaultLowBlockingZoneRefreshRate();
+            }
             mRefreshRateInLowZone = refreshRateInLowZone;
         }
 
         private void loadRefreshRateInHighZone(DisplayDeviceConfig displayDeviceConfig,
-                boolean attemptLoadingFromDeviceConfig) {
-            int refreshRateInHighZone =
-                    (displayDeviceConfig == null) ? mContext.getResources().getInteger(
-                        R.integer.config_fixedRefreshRateInHighZone) : displayDeviceConfig
-                        .getDefaultHighBlockingZoneRefreshRate();
-            if (attemptLoadingFromDeviceConfig) {
+                boolean attemptReadFromFeatureParams) {
+            int refreshRateInHighZone = -1;
+            if (attemptReadFromFeatureParams) {
                 try {
-                    refreshRateInHighZone = mDeviceConfig.getInt(
-                        DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
-                        DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HIGH_ZONE,
-                        refreshRateInHighZone);
+                    refreshRateInHighZone = mConfigParameterProvider.getRefreshRateInHighZone();
                 } catch (Exception exception) {
                     // Do nothing
                 }
             }
+            if (refreshRateInHighZone == -1) {
+                refreshRateInHighZone = (displayDeviceConfig == null)
+                        ? mContext.getResources().getInteger(
+                                R.integer.config_fixedRefreshRateInHighZone)
+                        : displayDeviceConfig.getDefaultHighBlockingZoneRefreshRate();
+            }
             mRefreshRateInHighZone = refreshRateInHighZone;
         }
 
         private void loadHighBrightnessThresholds(DisplayDeviceConfig displayDeviceConfig,
-                boolean attemptLoadingFromDeviceConfig) {
+                boolean attemptReadFromFeatureParams) {
             mHighDisplayBrightnessThresholds = loadBrightnessThresholds(
-                    () -> mDeviceConfigDisplaySettings.getHighDisplayBrightnessThresholds(),
+                    () -> mConfigParameterProvider.getHighDisplayBrightnessThresholds(),
                     () -> displayDeviceConfig.getHighDisplayBrightnessThresholds(),
                     R.array.config_highDisplayBrightnessThresholdsOfFixedRefreshRate,
-                    displayDeviceConfig, attemptLoadingFromDeviceConfig);
+                    displayDeviceConfig, attemptReadFromFeatureParams);
             mHighAmbientBrightnessThresholds = loadBrightnessThresholds(
-                    () -> mDeviceConfigDisplaySettings.getHighAmbientBrightnessThresholds(),
+                    () -> mConfigParameterProvider.getHighAmbientBrightnessThresholds(),
                     () -> displayDeviceConfig.getHighAmbientBrightnessThresholds(),
                     R.array.config_highAmbientBrightnessThresholdsOfFixedRefreshRate,
-                    displayDeviceConfig, attemptLoadingFromDeviceConfig);
+                    displayDeviceConfig, attemptReadFromFeatureParams);
             if (mHighDisplayBrightnessThresholds.length
                     != mHighAmbientBrightnessThresholds.length) {
                 throw new RuntimeException("display high brightness threshold array and ambient "
@@ -1668,10 +1670,10 @@
                 Callable<int[]> loadFromDeviceConfigDisplaySettingsCallable,
                 Callable<int[]> loadFromDisplayDeviceConfigCallable,
                 int brightnessThresholdOfFixedRefreshRateKey,
-                DisplayDeviceConfig displayDeviceConfig, boolean attemptLoadingFromDeviceConfig) {
+                DisplayDeviceConfig displayDeviceConfig, boolean attemptReadFromFeatureParams) {
             int[] brightnessThresholds = null;
 
-            if (attemptLoadingFromDeviceConfig) {
+            if (attemptReadFromFeatureParams) {
                 try {
                     brightnessThresholds =
                         loadFromDeviceConfigDisplaySettingsCallable.call();
@@ -1715,9 +1717,9 @@
 
             // DeviceConfig is accessible after system ready.
             int[] lowDisplayBrightnessThresholds =
-                    mDeviceConfigDisplaySettings.getLowDisplayBrightnessThresholds();
+                    mConfigParameterProvider.getLowDisplayBrightnessThresholds();
             int[] lowAmbientBrightnessThresholds =
-                    mDeviceConfigDisplaySettings.getLowAmbientBrightnessThresholds();
+                    mConfigParameterProvider.getLowAmbientBrightnessThresholds();
 
             if (lowDisplayBrightnessThresholds != null && lowAmbientBrightnessThresholds != null
                     && lowDisplayBrightnessThresholds.length
@@ -1727,9 +1729,9 @@
             }
 
             int[] highDisplayBrightnessThresholds =
-                    mDeviceConfigDisplaySettings.getHighDisplayBrightnessThresholds();
+                    mConfigParameterProvider.getHighDisplayBrightnessThresholds();
             int[] highAmbientBrightnessThresholds =
-                    mDeviceConfigDisplaySettings.getHighAmbientBrightnessThresholds();
+                    mConfigParameterProvider.getHighAmbientBrightnessThresholds();
 
             if (highDisplayBrightnessThresholds != null && highAmbientBrightnessThresholds != null
                     && highDisplayBrightnessThresholds.length
@@ -1738,14 +1740,12 @@
                 mHighAmbientBrightnessThresholds = highAmbientBrightnessThresholds;
             }
 
-            final int refreshRateInLowZone = mDeviceConfigDisplaySettings
-                    .getRefreshRateInLowZone();
+            final int refreshRateInLowZone = mConfigParameterProvider.getRefreshRateInLowZone();
             if (refreshRateInLowZone != -1) {
                 mRefreshRateInLowZone = refreshRateInLowZone;
             }
 
-            final int refreshRateInHighZone = mDeviceConfigDisplaySettings
-                    .getRefreshRateInHighZone();
+            final int refreshRateInHighZone = mConfigParameterProvider.getRefreshRateInHighZone();
             if (refreshRateInHighZone != -1) {
                 mRefreshRateInHighZone = refreshRateInHighZone;
             }
@@ -1799,15 +1799,15 @@
                     displayDeviceConfig = mDefaultDisplayDeviceConfig;
                 }
                 mLowDisplayBrightnessThresholds = loadBrightnessThresholds(
-                        () -> mDeviceConfigDisplaySettings.getLowDisplayBrightnessThresholds(),
+                        () -> mConfigParameterProvider.getLowDisplayBrightnessThresholds(),
                         () -> displayDeviceConfig.getLowDisplayBrightnessThresholds(),
                         R.array.config_brightnessThresholdsOfPeakRefreshRate,
-                        displayDeviceConfig, /* attemptLoadingFromDeviceConfig= */ false);
+                        displayDeviceConfig, /* attemptReadFromFeatureParams= */ false);
                 mLowAmbientBrightnessThresholds = loadBrightnessThresholds(
-                        () -> mDeviceConfigDisplaySettings.getLowAmbientBrightnessThresholds(),
+                        () -> mConfigParameterProvider.getLowAmbientBrightnessThresholds(),
                         () -> displayDeviceConfig.getLowAmbientBrightnessThresholds(),
                         R.array.config_ambientThresholdsOfPeakRefreshRate,
-                        displayDeviceConfig, /* attemptLoadingFromDeviceConfig= */ false);
+                        displayDeviceConfig, /* attemptReadFromFeatureParams= */ false);
             }
             restartObserver();
         }
@@ -1822,7 +1822,7 @@
                 // from there.
                 synchronized (mLock) {
                     loadRefreshRateInLowZone(mDefaultDisplayDeviceConfig,
-                            /* attemptLoadingFromDeviceConfig= */ false);
+                            /* attemptReadFromFeatureParams= */ false);
                 }
                 restartObserver();
             } else if (refreshRate != mRefreshRateInLowZone) {
@@ -1843,15 +1843,15 @@
                     displayDeviceConfig = mDefaultDisplayDeviceConfig;
                 }
                 mHighDisplayBrightnessThresholds = loadBrightnessThresholds(
-                        () -> mDeviceConfigDisplaySettings.getHighDisplayBrightnessThresholds(),
+                        () -> mConfigParameterProvider.getLowDisplayBrightnessThresholds(),
                         () -> displayDeviceConfig.getHighDisplayBrightnessThresholds(),
                         R.array.config_highDisplayBrightnessThresholdsOfFixedRefreshRate,
-                        displayDeviceConfig, /* attemptLoadingFromDeviceConfig= */ false);
+                        displayDeviceConfig, /* attemptReadFromFeatureParams= */ false);
                 mHighAmbientBrightnessThresholds = loadBrightnessThresholds(
-                        () -> mDeviceConfigDisplaySettings.getHighAmbientBrightnessThresholds(),
+                        () -> mConfigParameterProvider.getHighAmbientBrightnessThresholds(),
                         () -> displayDeviceConfig.getHighAmbientBrightnessThresholds(),
                         R.array.config_highAmbientBrightnessThresholdsOfFixedRefreshRate,
-                        displayDeviceConfig, /* attemptLoadingFromDeviceConfig= */ false);
+                        displayDeviceConfig, /* attemptReadFromFeatureParams= */ false);
             }
             restartObserver();
         }
@@ -1866,7 +1866,7 @@
                 // from there.
                 synchronized (mLock) {
                     loadRefreshRateInHighZone(mDefaultDisplayDeviceConfig,
-                            /* attemptLoadingFromDeviceConfig= */ false);
+                            /* attemptReadFromFeatureParams= */ false);
                 }
                 restartObserver();
             } else if (refreshRate != mRefreshRateInHighZone) {
@@ -2675,113 +2675,55 @@
 
     private class DeviceConfigDisplaySettings implements DeviceConfig.OnPropertiesChangedListener {
         public void startListening() {
-            mDeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+            mConfigParameterProvider.addOnPropertiesChangedListener(
                     BackgroundThread.getExecutor(), this);
         }
 
-        /*
-         * Return null if no such property or wrong format (not comma separated integers).
-         */
-        public int[] getLowDisplayBrightnessThresholds() {
-            return getIntArrayProperty(
-                    DisplayManager.DeviceConfig
-                            .KEY_FIXED_REFRESH_RATE_LOW_DISPLAY_BRIGHTNESS_THRESHOLDS);
+        private int getRefreshRateInHbmHdr(DisplayDeviceConfig displayDeviceConfig) {
+            return getRefreshRate(
+                    () -> mConfigParameterProvider.getRefreshRateInHbmHdr(),
+                    () -> displayDeviceConfig.getDefaultRefreshRateInHbmHdr(),
+                    R.integer.config_defaultRefreshRateInHbmHdr,
+                    displayDeviceConfig
+            );
         }
 
-        /*
-         * Return null if no such property or wrong format (not comma separated integers).
-         */
-        public int[] getLowAmbientBrightnessThresholds() {
-            return getIntArrayProperty(
-                    DisplayManager.DeviceConfig
-                            .KEY_FIXED_REFRESH_RATE_LOW_AMBIENT_BRIGHTNESS_THRESHOLDS);
+        private int getRefreshRateInHbmSunlight(DisplayDeviceConfig displayDeviceConfig) {
+            return getRefreshRate(
+                    () -> mConfigParameterProvider.getRefreshRateInHbmSunlight(),
+                    () -> displayDeviceConfig.getDefaultRefreshRateInHbmSunlight(),
+                    R.integer.config_defaultRefreshRateInHbmSunlight,
+                    displayDeviceConfig
+            );
         }
 
-        public int getRefreshRateInLowZone() {
-            return mDeviceConfig.getInt(
-                    DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
-                    DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_LOW_ZONE, -1);
-
-        }
-
-        /*
-         * Return null if no such property or wrong format (not comma separated integers).
-         */
-        public int[] getHighDisplayBrightnessThresholds() {
-            return getIntArrayProperty(
-                    DisplayManager.DeviceConfig
-                            .KEY_FIXED_REFRESH_RATE_HIGH_DISPLAY_BRIGHTNESS_THRESHOLDS);
-        }
-
-        /*
-         * Return null if no such property or wrong format (not comma separated integers).
-         */
-        public int[] getHighAmbientBrightnessThresholds() {
-            return getIntArrayProperty(
-                    DisplayManager.DeviceConfig
-                            .KEY_FIXED_REFRESH_RATE_HIGH_AMBIENT_BRIGHTNESS_THRESHOLDS);
-        }
-
-        public int getRefreshRateInHighZone() {
-            return mDeviceConfig.getInt(
-                    DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
-                    DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HIGH_ZONE,
-                    -1);
-        }
-
-        public int getRefreshRateInHbmHdr(DisplayDeviceConfig displayDeviceConfig) {
-            int refreshRate =
-                    (displayDeviceConfig == null) ? mContext.getResources().getInteger(
-                            R.integer.config_defaultRefreshRateInHbmHdr)
-                            : displayDeviceConfig.getDefaultRefreshRateInHbmHdr();
+        private int getRefreshRate(IntSupplier fromConfigPram, IntSupplier fromDisplayDeviceConfig,
+                @IntegerRes int configKey, DisplayDeviceConfig displayDeviceConfig) {
+            int refreshRate = -1;
             try {
-                refreshRate = mDeviceConfig.getInt(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
-                    DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HBM_HDR,
-                    refreshRate);
-            } catch (NullPointerException e) {
+                refreshRate = fromConfigPram.getAsInt();
+            } catch (NullPointerException npe) {
                 // Do Nothing
             }
-            return refreshRate;
-        }
-
-        public int getRefreshRateInHbmSunlight(DisplayDeviceConfig displayDeviceConfig) {
-            int refreshRate =
-                    (displayDeviceConfig == null) ? mContext.getResources()
-                        .getInteger(R.integer.config_defaultRefreshRateInHbmSunlight)
-                        : displayDeviceConfig.getDefaultRefreshRateInHbmSunlight();
-            try {
-                refreshRate = mDeviceConfig.getInt(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
-                    DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HBM_SUNLIGHT,
-                    refreshRate);
-            } catch (NullPointerException e) {
-                // Do Nothing
+            if (refreshRate == -1) {
+                refreshRate = (displayDeviceConfig == null)
+                                ? mContext.getResources().getInteger(configKey)
+                                : fromDisplayDeviceConfig.getAsInt();
             }
             return refreshRate;
         }
 
-        /*
-         * Return null if no such property
-         */
-        public Float getDefaultPeakRefreshRate() {
-            float defaultPeakRefreshRate = mDeviceConfig.getFloat(
-                    DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
-                    DisplayManager.DeviceConfig.KEY_PEAK_REFRESH_RATE_DEFAULT, -1);
-
-            if (defaultPeakRefreshRate == -1) {
-                return null;
-            }
-            return defaultPeakRefreshRate;
-        }
-
         @Override
         public void onPropertiesChanged(@NonNull DeviceConfig.Properties properties) {
-            Float defaultPeakRefreshRate = getDefaultPeakRefreshRate();
+            float defaultPeakRefreshRate = mConfigParameterProvider.getPeakRefreshRateDefault();
             mHandler.obtainMessage(MSG_DEFAULT_PEAK_REFRESH_RATE_CHANGED,
-                    defaultPeakRefreshRate).sendToTarget();
+                    defaultPeakRefreshRate == -1 ? null : defaultPeakRefreshRate).sendToTarget();
 
-            int[] lowDisplayBrightnessThresholds = getLowDisplayBrightnessThresholds();
-            int[] lowAmbientBrightnessThresholds = getLowAmbientBrightnessThresholds();
-            final int refreshRateInLowZone = getRefreshRateInLowZone();
+            int[] lowDisplayBrightnessThresholds =
+                    mConfigParameterProvider.getLowDisplayBrightnessThresholds();
+            int[] lowAmbientBrightnessThresholds =
+                    mConfigParameterProvider.getLowAmbientBrightnessThresholds();
+            final int refreshRateInLowZone = mConfigParameterProvider.getRefreshRateInLowZone();
 
             mHandler.obtainMessage(MSG_LOW_BRIGHTNESS_THRESHOLDS_CHANGED,
                     new Pair<>(lowDisplayBrightnessThresholds, lowAmbientBrightnessThresholds))
@@ -2790,9 +2732,11 @@
             mHandler.obtainMessage(MSG_REFRESH_RATE_IN_LOW_ZONE_CHANGED, refreshRateInLowZone,
                     0).sendToTarget();
 
-            int[] highDisplayBrightnessThresholds = getHighDisplayBrightnessThresholds();
-            int[] highAmbientBrightnessThresholds = getHighAmbientBrightnessThresholds();
-            final int refreshRateInHighZone = getRefreshRateInHighZone();
+            int[] highDisplayBrightnessThresholds =
+                    mConfigParameterProvider.getHighDisplayBrightnessThresholds();
+            int[] highAmbientBrightnessThresholds =
+                    mConfigParameterProvider.getHighAmbientBrightnessThresholds();
+            final int refreshRateInHighZone = mConfigParameterProvider.getRefreshRateInHighZone();
 
             mHandler.obtainMessage(MSG_HIGH_BRIGHTNESS_THRESHOLDS_CHANGED,
                     new Pair<>(highDisplayBrightnessThresholds, highAmbientBrightnessThresholds))
@@ -2814,33 +2758,6 @@
                     .sendToTarget();
             }
         }
-
-        private int[] getIntArrayProperty(String prop) {
-            String strArray = mDeviceConfig.getString(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, prop,
-                    null);
-
-            if (strArray != null) {
-                return parseIntArray(strArray);
-            }
-
-            return null;
-        }
-
-        private int[] parseIntArray(@NonNull String strArray) {
-            String[] items = strArray.split(",");
-            int[] array = new int[items.length];
-
-            try {
-                for (int i = 0; i < array.length; i++) {
-                    array[i] = Integer.parseInt(items[i]);
-                }
-            } catch (NumberFormatException e) {
-                Slog.e(TAG, "Incorrect format for array: '" + strArray + "'", e);
-                array = null;
-            }
-
-            return array;
-        }
     }
 
     interface Injector {
diff --git a/services/core/java/com/android/server/display/utils/DeviceConfigParsingUtils.java b/services/core/java/com/android/server/display/utils/DeviceConfigParsingUtils.java
new file mode 100644
index 0000000..a8034c5
--- /dev/null
+++ b/services/core/java/com/android/server/display/utils/DeviceConfigParsingUtils.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.utils;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.PowerManager;
+import android.util.Slog;
+
+import com.android.server.display.DisplayDeviceConfig;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+
+/**
+ * Provides utility methods for DeviceConfig string parsing
+ */
+public class DeviceConfigParsingUtils {
+    private static final String TAG = "DeviceConfigParsingUtils";
+
+    /**
+     * Parses map from device config
+     * Data format:
+     * (displayId:String,numberOfPoints:Int,(state:T,value:Float){numberOfPoints},
+     * dataSetId:String)?;)+
+     * result : mapOf(displayId to mapOf(dataSetId to V))
+     */
+    @NonNull
+    public static <T, V> Map<String, Map<String, V>> parseDeviceConfigMap(
+            @Nullable String data,
+            @NonNull BiFunction<String, String, T> dataPointMapper,
+            @NonNull Function<List<T>, V> dataSetMapper) {
+        if (data == null) {
+            return Map.of();
+        }
+        Map<String, Map<String, V>> result = new HashMap<>();
+        String[] dataSets = data.split(";"); // by displayId + dataSetId
+        for (String dataSet : dataSets) {
+            String[] items = dataSet.split(",");
+            int noOfItems = items.length;
+            // Validate number of items, at least: displayId,1,key1,value1
+            if (noOfItems < 4) {
+                Slog.e(TAG, "Invalid dataSet(not enough items):" + dataSet, new Throwable());
+                return Map.of();
+            }
+            int i = 0;
+            String uniqueDisplayId = items[i++];
+
+            String numberOfPointsString = items[i++];
+            int numberOfPoints;
+            try {
+                numberOfPoints = Integer.parseInt(numberOfPointsString);
+            } catch (NumberFormatException nfe) {
+                Slog.e(TAG, "Invalid dataSet(invalid number of points):" + dataSet, nfe);
+                return Map.of();
+            }
+            // Validate number of itmes based on numberOfPoints:
+            // displayId,numberOfPoints,(key,value) x numberOfPoints,dataSetId(optional)
+            int expectedMinItems = 2 + numberOfPoints * 2;
+            if (noOfItems < expectedMinItems || noOfItems > expectedMinItems + 1) {
+                Slog.e(TAG, "Invalid dataSet(wrong number of points):" + dataSet, new Throwable());
+                return Map.of();
+            }
+            // Construct data points
+            List<T> dataPoints = new ArrayList<>();
+            for (int j = 0; j < numberOfPoints; j++) {
+                String key = items[i++];
+                String value = items[i++];
+                T dataPoint = dataPointMapper.apply(key, value);
+                if (dataPoint == null) {
+                    Slog.e(TAG,
+                            "Invalid dataPoint ,key=" + key + ",value=" + value + ",dataSet="
+                                    + dataSet, new Throwable());
+                    return Map.of();
+                }
+                dataPoints.add(dataPoint);
+            }
+            // Construct dataSet
+            V dataSetMapped = dataSetMapper.apply(dataPoints);
+            if (dataSetMapped == null) {
+                Slog.e(TAG, "Invalid dataSetMapped dataPoints=" + dataPoints + ",dataSet="
+                        + dataSet, new Throwable());
+                return Map.of();
+            }
+            // Get dataSetId and dataSets map for displayId
+            String dataSetId = (i < items.length) ? items[i] : DisplayDeviceConfig.DEFAULT_ID;
+            Map<String, V> byDisplayId = result.computeIfAbsent(uniqueDisplayId,
+                    k -> new HashMap<>());
+
+            // Try to store dataSet in datasets for display
+            if (byDisplayId.put(dataSetId, dataSetMapped) != null) {
+                Slog.e(TAG, "Duplicate dataSetId=" + dataSetId + ",data=" + data, new Throwable());
+                return Map.of();
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Parses thermal string value from device config
+     */
+    @PowerManager.ThermalStatus
+    public static int parseThermalStatus(@NonNull String value) throws IllegalArgumentException {
+        switch (value) {
+            case "none":
+                return PowerManager.THERMAL_STATUS_NONE;
+            case "light":
+                return PowerManager.THERMAL_STATUS_LIGHT;
+            case "moderate":
+                return PowerManager.THERMAL_STATUS_MODERATE;
+            case "severe":
+                return PowerManager.THERMAL_STATUS_SEVERE;
+            case "critical":
+                return PowerManager.THERMAL_STATUS_CRITICAL;
+            case "emergency":
+                return PowerManager.THERMAL_STATUS_EMERGENCY;
+            case "shutdown":
+                return PowerManager.THERMAL_STATUS_SHUTDOWN;
+            default:
+                throw new IllegalArgumentException("Invalid Thermal Status: " + value);
+        }
+    }
+
+    /**
+     * Parses brightness value from device config
+     */
+    public static float parseBrightness(String stringVal) throws IllegalArgumentException {
+        float value = Float.parseFloat(stringVal);
+        if (value < PowerManager.BRIGHTNESS_MIN || value > PowerManager.BRIGHTNESS_MAX) {
+            throw new IllegalArgumentException("Brightness value out of bounds: " + stringVal);
+        }
+        return value;
+    }
+}
diff --git a/services/core/java/com/android/server/display/utils/SensorUtils.java b/services/core/java/com/android/server/display/utils/SensorUtils.java
index 48bc46c..56321cd 100644
--- a/services/core/java/com/android/server/display/utils/SensorUtils.java
+++ b/services/core/java/com/android/server/display/utils/SensorUtils.java
@@ -16,10 +16,13 @@
 
 package com.android.server.display.utils;
 
+import android.annotation.Nullable;
 import android.hardware.Sensor;
 import android.hardware.SensorManager;
 import android.text.TextUtils;
 
+import com.android.server.display.DisplayDeviceConfig;
+
 import java.util.List;
 
 /**
@@ -29,17 +32,26 @@
     public static final int NO_FALLBACK = 0;
 
     /**
+     * Finds the specified sensor for SensorData from DisplayDeviceConfig.
+     */
+    @Nullable
+    public static Sensor findSensor(@Nullable SensorManager sensorManager,
+            @Nullable DisplayDeviceConfig.SensorData sensorData, int fallbackType) {
+        if (sensorData == null) {
+            return null;
+        } else {
+            return findSensor(sensorManager, sensorData.type, sensorData.name, fallbackType);
+        }
+    }
+    /**
      * Finds the specified sensor by type and name using SensorManager.
      */
-    public static Sensor findSensor(SensorManager sensorManager, String sensorType,
-            String sensorName, int fallbackType) {
+    @Nullable
+    public static Sensor findSensor(@Nullable SensorManager sensorManager,
+            @Nullable String sensorType, @Nullable String sensorName, int fallbackType) {
         if (sensorManager == null) {
             return null;
         }
-
-        if ("".equals(sensorName) && "".equals(sensorType)) {
-            return null;
-        }
         final boolean isNameSpecified = !TextUtils.isEmpty(sensorName);
         final boolean isTypeSpecified = !TextUtils.isEmpty(sensorType);
         if (isNameSpecified || isTypeSpecified) {
diff --git a/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceController.java b/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceController.java
index 5b772fc..4ad26c4 100644
--- a/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceController.java
+++ b/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceController.java
@@ -37,8 +37,11 @@
  * - Uses the AmbientColorTemperatureSensor to detect changes in the ambient color temperature;
  * - Uses the AmbientColorTemperatureFilter to average these changes over time, filter out the
  *   noise, and arrive at an estimate of the actual ambient color temperature;
- * - Uses the DisplayWhiteBalanceThrottler to decide whether the display color tempearture should
+ * - Uses the DisplayWhiteBalanceThrottler to decide whether the display color temperature should
  *   be updated, suppressing changes that are too frequent or too minor.
+ *
+ *   Calls to this class must happen on the DisplayPowerController(2) handler, to ensure
+ *   values do not get out of sync.
  */
 public class DisplayWhiteBalanceController implements
         AmbientSensor.AmbientBrightnessSensor.Callbacks,
diff --git a/services/core/java/com/android/server/dreams/DreamController.java b/services/core/java/com/android/server/dreams/DreamController.java
index 6d70d21..633bf731 100644
--- a/services/core/java/com/android/server/dreams/DreamController.java
+++ b/services/core/java/com/android/server/dreams/DreamController.java
@@ -16,11 +16,11 @@
 
 package com.android.server.dreams;
 
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM;
 import static android.content.Intent.FLAG_RECEIVER_FOREGROUND;
 
 import android.app.ActivityTaskManager;
 import android.app.BroadcastOptions;
+import android.app.IAppTask;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -214,6 +214,27 @@
     }
 
     /**
+     * Provides an appTask for the dream with token {@code dreamToken}, so that the dream controller
+     * can stop the dream task when necessary.
+     */
+    void setDreamAppTask(Binder dreamToken, IAppTask appTask) {
+        if (mCurrentDream == null || mCurrentDream.mToken != dreamToken
+                || mCurrentDream.mAppTask != null) {
+            Slog.e(TAG, "Illegal dream activity start. mCurrentDream.mToken = "
+                    + mCurrentDream.mToken + ", illegal dreamToken = " + dreamToken
+                    + ". Ending this dream activity.");
+            try {
+                appTask.finishAndRemoveTask();
+            } catch (RemoteException | RuntimeException e) {
+                Slog.e(TAG, "Unable to stop illegal dream activity.");
+            }
+            return;
+        }
+
+        mCurrentDream.mAppTask = appTask;
+    }
+
+    /**
      * Stops dreaming.
      *
      * The current dream, if any, and any unstopped previous dreams are stopped. The device stops
@@ -303,8 +324,14 @@
                     mSentStartBroadcast = false;
                 }
 
-                mActivityTaskManager.removeRootTasksWithActivityTypes(
-                        new int[] {ACTIVITY_TYPE_DREAM});
+                if (mCurrentDream != null && mCurrentDream.mAppTask != null) {
+                    // Finish the dream task in case it hasn't finished by itself already.
+                    try {
+                        mCurrentDream.mAppTask.finishAndRemoveTask();
+                    } catch (RemoteException | RuntimeException e) {
+                        Slog.e(TAG, "Unable to stop dream activity.");
+                    }
+                }
 
                 mListener.onDreamStopped(dream.mToken);
             }
@@ -364,6 +391,7 @@
         public final boolean mIsPreviewMode;
         public final boolean mCanDoze;
         public final int mUserId;
+        public IAppTask mAppTask;
 
         public PowerManager.WakeLock mWakeLock;
         public boolean mBound;
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
index 41651fd..d88fe8a 100644
--- a/services/core/java/com/android/server/dreams/DreamManagerService.java
+++ b/services/core/java/com/android/server/dreams/DreamManagerService.java
@@ -27,6 +27,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
+import android.app.IAppTask;
 import android.app.TaskInfo;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -37,6 +38,7 @@
 import android.content.pm.ActivityInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.PackageManagerInternal;
 import android.content.pm.ServiceInfo;
 import android.database.ContentObserver;
 import android.hardware.display.AmbientDisplayConfiguration;
@@ -116,6 +118,7 @@
     private final PowerManagerInternal mPowerManagerInternal;
     private final PowerManager.WakeLock mDozeWakeLock;
     private final ActivityTaskManagerInternal mAtmInternal;
+    private final PackageManagerInternal mPmInternal;
     private final UserManager mUserManager;
     private final UiEventLogger mUiEventLogger;
     private final DreamUiEventLogger mDreamUiEventLogger;
@@ -203,9 +206,7 @@
 
         @Override
         public void onChange(boolean selfChange, Uri uri) {
-            synchronized (mLock) {
-                updateWhenToDreamSettings();
-            }
+            updateWhenToDreamSettings();
         }
     }
 
@@ -218,6 +219,7 @@
         mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
         mPowerManagerInternal = getLocalService(PowerManagerInternal.class);
         mAtmInternal = getLocalService(ActivityTaskManagerInternal.class);
+        mPmInternal = getLocalService(PackageManagerInternal.class);
         mUserManager = context.getSystemService(UserManager.class);
         mDozeWakeLock = mPowerManager.newWakeLock(PowerManager.DOZE_WAKE_LOCK, DOZE_WAKE_LOCK_TAG);
         mDozeConfig = new AmbientDisplayConfiguration(mContext);
@@ -257,15 +259,7 @@
             if (Build.IS_DEBUGGABLE) {
                 SystemProperties.addChangeCallback(mSystemPropertiesChanged);
             }
-            mContext.registerReceiver(new BroadcastReceiver() {
-                @Override
-                public void onReceive(Context context, Intent intent) {
-                    writePulseGestureEnabled();
-                    synchronized (mLock) {
-                        stopDreamLocked(false /*immediate*/, "user switched");
-                    }
-                }
-            }, new IntentFilter(Intent.ACTION_USER_SWITCHED), null, mHandler);
+
             mContext.getContentResolver().registerContentObserver(
                     Settings.Secure.getUriFor(Settings.Secure.DOZE_DOUBLE_TAP_GESTURE), false,
                     mDozeEnabledObserver, UserHandle.USER_ALL);
@@ -303,6 +297,18 @@
         }
     }
 
+    @Override
+    public void onUserSwitching(@Nullable TargetUser from, @NonNull TargetUser to) {
+        updateWhenToDreamSettings();
+
+        mHandler.post(() -> {
+            writePulseGestureEnabled();
+            synchronized (mLock) {
+                stopDreamLocked(false /*immediate*/, "user switched");
+            }
+        });
+    }
+
     private void dumpInternal(PrintWriter pw) {
         synchronized (mLock) {
             pw.println("DREAM MANAGER (dumpsys dreams)");
@@ -318,6 +324,7 @@
             pw.println("mWhenToDream=" + mWhenToDream);
             pw.println("mKeepDreamingWhenUnpluggingDefault=" + mKeepDreamingWhenUnpluggingDefault);
             pw.println("getDozeComponent()=" + getDozeComponent());
+            pw.println("mDreamOverlayServiceName=" + mDreamOverlayServiceName.flattenToString());
             pw.println();
 
             DumpUtils.dumpAsync(mHandler, (pw1, prefix) -> mController.dump(pw1), pw, "", 200);
@@ -1061,6 +1068,64 @@
                 Binder.restoreCallingIdentity(ident);
             }
         }
+
+        @Override // Binder call
+        public void startDreamActivity(@NonNull Intent intent) {
+            final int callingUid = Binder.getCallingUid();
+            final int callingPid = Binder.getCallingPid();
+            // We post here, because startDreamActivity and setDreamAppTask have to run
+            // synchronously and DreamController#setDreamAppTask has to run on mHandler.
+            mHandler.post(() -> {
+                final Binder dreamToken;
+                final String dreamPackageName;
+                synchronized (mLock) {
+                    if (mCurrentDream == null) {
+                        Slog.e(TAG, "Attempt to start DreamActivity, but the device is not "
+                                + "dreaming. Aborting without starting the DreamActivity.");
+                        return;
+                    }
+                    dreamToken = mCurrentDream.token;
+                    dreamPackageName = mCurrentDream.name.getPackageName();
+                }
+
+                if (!canLaunchDreamActivity(dreamPackageName, intent.getPackage(),
+                            callingUid)) {
+                    Slog.e(TAG, "The dream activity can be started only when the device is dreaming"
+                            + " and only by the active dream package.");
+                    return;
+                }
+
+                final IAppTask appTask = mAtmInternal.startDreamActivity(intent, callingUid,
+                        callingPid);
+                if (appTask == null) {
+                    Slog.e(TAG, "Could not start dream activity.");
+                    stopDreamInternal(true, "DreamActivity not started");
+                    return;
+                }
+                mController.setDreamAppTask(dreamToken, appTask);
+            });
+        }
+
+        boolean canLaunchDreamActivity(String dreamPackageName, String packageName,
+                int callingUid) {
+            if (dreamPackageName == null || packageName == null) {
+                Slog.e(TAG, "Cannot launch dream activity due to invalid state. dream component= "
+                        + dreamPackageName + ", packageName=" + packageName);
+                return false;
+            }
+            if (!mPmInternal.isSameApp(packageName, callingUid, UserHandle.getUserId(callingUid))) {
+                Slog.e(TAG, "Cannot launch dream activity because package="
+                        + packageName + " does not match callingUid=" + callingUid);
+                return false;
+            }
+            if (packageName.equals(dreamPackageName)) {
+                return true;
+            }
+            Slog.e(TAG, "Dream packageName does not match active dream. Package " + packageName
+                    + " does not match " + dreamPackageName);
+            return false;
+        }
+
     }
 
     private final class LocalService extends DreamManagerInternal {
diff --git a/services/core/java/com/android/server/graphics/fonts/FontManagerService.java b/services/core/java/com/android/server/graphics/fonts/FontManagerService.java
index ad640b1..c6a50ed 100644
--- a/services/core/java/com/android/server/graphics/fonts/FontManagerService.java
+++ b/services/core/java/com/android/server/graphics/fonts/FontManagerService.java
@@ -46,6 +46,7 @@
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.Preconditions;
 import com.android.server.LocalServices;
+import com.android.server.SystemServerInitThreadPool;
 import com.android.server.SystemService;
 
 import java.io.File;
@@ -61,6 +62,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.concurrent.CompletableFuture;
 
 /** A service for managing system fonts. */
 public final class FontManagerService extends IFontManager.Stub {
@@ -136,10 +138,11 @@
     /** Class to manage FontManagerService's lifecycle. */
     public static final class Lifecycle extends SystemService {
         private final FontManagerService mService;
+        private final CompletableFuture<Void> mServiceStarted = new CompletableFuture<>();
 
         public Lifecycle(@NonNull Context context, boolean safeMode) {
             super(context);
-            mService = new FontManagerService(context, safeMode);
+            mService = new FontManagerService(context, safeMode, mServiceStarted);
         }
 
         @Override
@@ -152,11 +155,20 @@
                             if (!Typeface.ENABLE_LAZY_TYPEFACE_INITIALIZATION) {
                                 return null;
                             }
+                            mServiceStarted.join();
                             return mService.getCurrentFontMap();
                         }
                     });
             publishBinderService(Context.FONT_SERVICE, mService);
         }
+
+        @Override
+        public void onBootPhase(int phase) {
+            if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) {
+                // Wait for FontManagerService to start since it will be needed after this point.
+                mServiceStarted.join();
+            }
+        }
     }
 
     private static class FsverityUtilImpl implements UpdatableFontDir.FsverityUtil {
@@ -219,14 +231,30 @@
     @Nullable
     private SharedMemory mSerializedFontMap = null;
 
-    private FontManagerService(Context context, boolean safeMode) {
+    private FontManagerService(
+            Context context, boolean safeMode, CompletableFuture<Void> serviceStarted) {
         if (safeMode) {
             Slog.i(TAG, "Entering safe mode. Deleting all font updates.");
             UpdatableFontDir.deleteAllFiles(new File(FONT_FILES_DIR), new File(CONFIG_XML_FILE));
         }
         mContext = context;
         mIsSafeMode = safeMode;
-        initialize();
+
+        SystemServerInitThreadPool.submit(() -> {
+            initialize();
+
+            // Set system font map only if there is updatable font directory.
+            // If there is no updatable font directory, `initialize` will have already loaded the
+            // system font map, so there's no need to set the system font map again here.
+            if  (mUpdatableFontDir != null) {
+                try {
+                    Typeface.setSystemFontMap(getCurrentFontMap());
+                } catch (IOException | ErrnoException e) {
+                    Slog.w(TAG, "Failed to set system font map of system_server");
+                }
+            }
+            serviceStarted.complete(null);
+        }, "FontManagerService_create");
     }
 
     @Nullable
@@ -263,6 +291,9 @@
         synchronized (mUpdatableFontDirLock) {
             mUpdatableFontDir = createUpdatableFontDir();
             if (mUpdatableFontDir == null) {
+                // If fs-verity is not supported, load preinstalled system font map and use it for
+                // all apps.
+                Typeface.loadPreinstalledSystemFontMap();
                 setSerializedFontMap(serializeSystemServerFontMap());
                 return;
             }
diff --git a/services/core/java/com/android/server/hdmi/Constants.java b/services/core/java/com/android/server/hdmi/Constants.java
index 090d728..77213bf 100644
--- a/services/core/java/com/android/server/hdmi/Constants.java
+++ b/services/core/java/com/android/server/hdmi/Constants.java
@@ -601,6 +601,7 @@
     })
     @interface RcProfileSource {}
 
+    static final int HDMI_EARC_STATUS_UNKNOWN = -1;
     static final int HDMI_EARC_STATUS_IDLE = IEArcStatus.IDLE; // IDLE1
     static final int HDMI_EARC_STATUS_EARC_PENDING =
             IEArcStatus.EARC_PENDING; // DISC1 and DISC2
@@ -609,6 +610,7 @@
             IEArcStatus.EARC_CONNECTED; // eARC connected
 
     @IntDef({
+            HDMI_EARC_STATUS_UNKNOWN,
             HDMI_EARC_STATUS_IDLE,
             HDMI_EARC_STATUS_EARC_PENDING,
             HDMI_EARC_STATUS_ARC_PENDING,
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecAtomWriter.java b/services/core/java/com/android/server/hdmi/HdmiCecAtomWriter.java
index 9437c4f..7ae7d5c 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecAtomWriter.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecAtomWriter.java
@@ -16,6 +16,11 @@
 
 package com.android.server.hdmi;
 
+import static com.android.server.hdmi.Constants.HDMI_EARC_STATUS_ARC_PENDING;
+import static com.android.server.hdmi.Constants.HDMI_EARC_STATUS_EARC_CONNECTED;
+import static com.android.server.hdmi.Constants.HDMI_EARC_STATUS_EARC_PENDING;
+import static com.android.server.hdmi.Constants.HDMI_EARC_STATUS_IDLE;
+
 import android.stats.hdmi.HdmiStatsEnums;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -193,6 +198,46 @@
     }
 
     /**
+     * Writes a HdmiEarcStatusReported atom representing a eARC status change.
+     * @param isSupported         Whether the hardware supports eARC.
+     * @param isEnabled           Whether eARC is enabled.
+     * @param oldConnectionState  If enumLogReason == HdmiStatsEnums.LOG_REASON_EARC_STATUS_CHANGED,
+     *                            the state just before the change. Otherwise, the current state.
+     * @param newConnectionState  If enumLogReason == HdmiStatsEnums.LOG_REASON_EARC_STATUS_CHANGED,
+     *                            the state just after the change. Otherwise, the current state.
+     * @param enumLogReason       The event that triggered the log.
+     */
+    public void earcStatusChanged(boolean isSupported, boolean isEnabled, int oldConnectionState,
+            int newConnectionState, int enumLogReason) {
+        int enumOldConnectionState = earcStateToEnum(oldConnectionState);
+        int enumNewConnectionState = earcStateToEnum(newConnectionState);
+
+        FrameworkStatsLog.write(
+                FrameworkStatsLog.HDMI_EARC_STATUS_REPORTED,
+                isSupported,
+                isEnabled,
+                enumOldConnectionState,
+                enumNewConnectionState,
+                enumLogReason
+        );
+    }
+
+    private int earcStateToEnum(int earcState) {
+        switch (earcState) {
+            case HDMI_EARC_STATUS_IDLE:
+                return HdmiStatsEnums.HDMI_EARC_STATUS_IDLE;
+            case HDMI_EARC_STATUS_EARC_PENDING:
+                return HdmiStatsEnums.HDMI_EARC_STATUS_EARC_PENDING;
+            case HDMI_EARC_STATUS_ARC_PENDING:
+                return HdmiStatsEnums.HDMI_EARC_STATUS_ARC_PENDING;
+            case HDMI_EARC_STATUS_EARC_CONNECTED:
+                return HdmiStatsEnums.HDMI_EARC_STATUS_EARC_CONNECTED;
+            default:
+                return HdmiStatsEnums.HDMI_EARC_STATUS_UNKNOWN;
+        }
+    }
+
+    /**
      * Contains the required arguments for creating any HdmiCecMessageReported atom
      */
     private class MessageReportedGenericArgs {
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecFeatureAction.java b/services/core/java/com/android/server/hdmi/HdmiCecFeatureAction.java
index 2e1ff03..5db114b 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecFeatureAction.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecFeatureAction.java
@@ -191,6 +191,11 @@
         mService.sendCecCommand(cmd, callback);
     }
 
+    protected final void sendCommandWithoutRetries(HdmiCecMessage cmd,
+            HdmiControlService.SendMessageCallback callback) {
+        mService.sendCecCommandWithoutRetries(cmd, callback);
+    }
+
     protected final void addAndStartAction(HdmiCecFeatureAction action) {
         mSource.addAndStartAction(action);
     }
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecKeycode.java b/services/core/java/com/android/server/hdmi/HdmiCecKeycode.java
index 698ee0b..29ed9da 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecKeycode.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecKeycode.java
@@ -213,7 +213,13 @@
         }
 
         private int toAndroidKeycodeIfMatched(byte[] cecKeycodeAndParams) {
-            if (Arrays.equals(mCecKeycodeAndParams, cecKeycodeAndParams)) {
+            if (cecKeycodeAndParams.length < mCecKeycodeAndParams.length) {
+                return UNSUPPORTED_KEYCODE;
+            }
+            byte[] trimmedCecKeycodeAndParams = new byte[mCecKeycodeAndParams.length];
+            System.arraycopy(cecKeycodeAndParams, 0, trimmedCecKeycodeAndParams, 0,
+                    mCecKeycodeAndParams.length);
+            if (Arrays.equals(mCecKeycodeAndParams, trimmedCecKeycodeAndParams)) {
                 return mAndroidKeycode;
             } else {
                 return UNSUPPORTED_KEYCODE;
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
old mode 100755
new mode 100644
index ca1abd6..207d38e
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
@@ -16,6 +16,8 @@
 
 package com.android.server.hdmi;
 
+import static com.android.server.hdmi.HdmiControlService.DEVICE_CLEANUP_TIMEOUT;
+
 import android.annotation.CallSuper;
 import android.hardware.hdmi.DeviceFeatures;
 import android.hardware.hdmi.HdmiControlManager;
@@ -61,9 +63,6 @@
     private static final int MAX_HDMI_ACTIVE_SOURCE_HISTORY = 10;
     private static final int MSG_DISABLE_DEVICE_TIMEOUT = 1;
     private static final int MSG_USER_CONTROL_RELEASE_TIMEOUT = 2;
-    // Timeout in millisecond for device clean up (5s).
-    // Normal actions timeout is 2s but some of them would have several sequence of timeout.
-    private static final int DEVICE_CLEANUP_TIMEOUT = 5000;
     // Within the timer, a received <User Control Pressed> will start "Press and Hold" behavior.
     // When it expires, we can assume <User Control Release> is received.
     private static final int FOLLOWER_SAFETY_TIMEOUT = 550;
@@ -175,6 +174,14 @@
             };
 
     /**
+     * A callback interface used by local devices use to indicate that they have finished their part
+     * of the standby process.
+     */
+    interface StandbyCompletedCallback {
+        void onStandbyCompleted();
+    }
+
+    /**
      * A callback interface to get notified when all pending action is cleared. It can be called
      * when timeout happened.
      */
@@ -1006,6 +1013,12 @@
         assertRunOnServiceThread();
         mActions.add(action);
         if (mService.isPowerStandby() || !mService.isAddressAllocated()) {
+            if (action.getClass() == ResendCecCommandAction.class) {
+                Slog.i(TAG, "Not ready to start ResendCecCommandAction. "
+                        + "This action is cancelled.");
+                removeAction(action);
+                return;
+            }
             Slog.i(TAG, "Not ready to start action. Queued for deferred start:" + action);
             return;
         }
@@ -1126,6 +1139,16 @@
         removeActionExcept(clazz, null);
     }
 
+    // Remove all running actions.
+    @ServiceThreadOnly
+    void removeAllActions() {
+        assertRunOnServiceThread();
+        for (HdmiCecFeatureAction action : mActions) {
+            action.finish(false);
+        }
+        mActions.clear();
+    }
+
     // Remove all actions matched with the given Class type besides |exception|.
     @ServiceThreadOnly
     <T extends HdmiCecFeatureAction> void removeActionExcept(
@@ -1250,8 +1273,14 @@
      *     messages like &lt;Standby&gt;
      * @param standbyAction Intent action that drives the standby process, either {@link
      *     HdmiControlService#STANDBY_SCREEN_OFF} or {@link HdmiControlService#STANDBY_SHUTDOWN}
+     * @param callback callback invoked after the standby process for the local device is completed.
      */
-    protected void onStandby(boolean initiatedByCec, int standbyAction) {}
+    protected void onStandby(boolean initiatedByCec, int standbyAction,
+            StandbyCompletedCallback callback) {}
+
+    protected void onStandby(boolean initiatedByCec, int standbyAction) {
+        onStandby(initiatedByCec, standbyAction, null);
+    }
 
     /**
      * Called when the initialization of local devices is complete.
@@ -1272,6 +1301,7 @@
         removeAction(AbsoluteVolumeAudioStatusAction.class);
         removeAction(SetAudioVolumeLevelDiscoveryAction.class);
         removeAction(ActiveSourceAction.class);
+        removeAction(ResendCecCommandAction.class);
 
         mPendingActionClearedCallback =
                 new PendingActionClearedCallback() {
@@ -1412,6 +1442,16 @@
         }
     }
 
+    @ServiceThreadOnly
+    @VisibleForTesting
+    public void invokeStandbyCompletedCallback(StandbyCompletedCallback callback) {
+        assertRunOnServiceThread();
+        if (callback == null) {
+            return;
+        }
+        callback.onStandbyCompleted();
+    }
+
     void sendUserControlPressedAndReleased(int targetAddress, int cecKeycode) {
         mService.sendCecCommand(
                 HdmiCecMessageBuilder.buildUserControlPressed(
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
index a026c4b..b3aa351 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
@@ -21,6 +21,7 @@
 import static com.android.server.hdmi.Constants.ALWAYS_SYSTEM_AUDIO_CONTROL_ON_POWER_ON;
 import static com.android.server.hdmi.Constants.PROPERTY_SYSTEM_AUDIO_CONTROL_ON_POWER_ON;
 import static com.android.server.hdmi.Constants.USE_LAST_STATE_SYSTEM_AUDIO_CONTROL_ON_POWER_ON;
+import static com.android.server.hdmi.HdmiControlService.SendMessageCallback;
 
 import android.annotation.Nullable;
 import android.content.ActivityNotFoundException;
@@ -231,11 +232,20 @@
         super.disableDevice(initiatedByCec, callback);
         assertRunOnServiceThread();
         mService.unregisterTvInputCallback(mTvInputCallback);
+        // Removing actions and invoking the callback is similar to
+        // HdmiCecLocalDevicePlayback#disableDevice and HdmiCecLocalDeviceTv#disableDevice,
+        // with the difference that in those classes only specific actions are removed and
+        // here we remove all actions. We don't expect any issues with removing all actions
+        // at this time, but we have to pay attention in the future.
+        removeAllActions();
+        // Call the callback instantly or else it will be called 5 seconds later.
+        checkIfPendingActionsCleared();
     }
 
     @Override
     @ServiceThreadOnly
-    protected void onStandby(boolean initiatedByCec, int standbyAction) {
+    protected void onStandby(boolean initiatedByCec, int standbyAction,
+            StandbyCompletedCallback callback) {
         assertRunOnServiceThread();
         // Invalidate the internal active source record when goes to standby
         // This set will also update mIsActiveSource
@@ -248,7 +258,7 @@
                     Constants.PROPERTY_LAST_SYSTEM_AUDIO_CONTROL,
                     isSystemAudioActivated() ? "true" : "false");
         }
-        terminateSystemAudioMode();
+        terminateSystemAudioMode(callback);
     }
 
     @Override
@@ -1084,9 +1094,16 @@
     }
 
     protected void terminateSystemAudioMode() {
+        terminateSystemAudioMode(null);
+    }
+
+    // Since this method is not just called during the standby process, the callback should be
+    // generalized in the future.
+    protected void terminateSystemAudioMode(StandbyCompletedCallback callback) {
         // remove pending initiation actions
         removeAction(SystemAudioInitiationActionFromAvr.class);
         if (!isSystemAudioActivated()) {
+            invokeStandbyCompletedCallback(callback);
             return;
         }
 
@@ -1094,7 +1111,13 @@
             // send <Set System Audio Mode> [“Off”]
             mService.sendCecCommand(
                     HdmiCecMessageBuilder.buildSetSystemAudioMode(
-                            getDeviceInfo().getLogicalAddress(), Constants.ADDR_BROADCAST, false));
+                            getDeviceInfo().getLogicalAddress(), Constants.ADDR_BROADCAST, false),
+                    new SendMessageCallback() {
+                        @Override
+                        public void onSendCompleted(int error) {
+                            invokeStandbyCompletedCallback(callback);
+                        }
+                    });
         }
     }
 
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
index 3ec3f94..dc416b2 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
@@ -28,7 +28,6 @@
 import android.os.Binder;
 import android.os.Handler;
 import android.os.PowerManager;
-import android.os.PowerManager.WakeLock;
 import android.os.SystemProperties;
 import android.sysprop.HdmiProperties;
 import android.util.Slog;
@@ -110,7 +109,8 @@
                     new SendMessageCallback() {
                         @Override
                         public void onSendCompleted(int error) {
-                            if (error != SendMessageResult.SUCCESS) {
+                            // In consideration of occasional transmission failures.
+                            if (error == SendMessageResult.NACK) {
                                 HdmiLogger.debug(
                                         "AVR did not respond to <Give System Audio Mode Status>");
                                 mService.setSystemAudioActivated(false);
@@ -237,9 +237,11 @@
 
     @Override
     @ServiceThreadOnly
-    protected void onStandby(boolean initiatedByCec, int standbyAction) {
+    protected void onStandby(boolean initiatedByCec, int standbyAction,
+            StandbyCompletedCallback callback) {
         assertRunOnServiceThread();
         if (!mService.isCecControlEnabled()) {
+            invokeStandbyCompletedCallback(callback);
             return;
         }
         boolean wasActiveSource = isActiveSource();
@@ -247,12 +249,20 @@
         mService.setActiveSource(Constants.ADDR_INVALID, Constants.INVALID_PHYSICAL_ADDRESS,
                 "HdmiCecLocalDevicePlayback#onStandby()");
         if (!wasActiveSource) {
+            invokeStandbyCompletedCallback(callback);
             return;
         }
+        SendMessageCallback sendMessageCallback = new SendMessageCallback() {
+            @Override
+            public void onSendCompleted(int error) {
+                invokeStandbyCompletedCallback(callback);
+            }
+        };
         if (initiatedByCec) {
             mService.sendCecCommand(
                     HdmiCecMessageBuilder.buildInactiveSource(
-                            getDeviceInfo().getLogicalAddress(), mService.getPhysicalAddress()));
+                            getDeviceInfo().getLogicalAddress(), mService.getPhysicalAddress()),
+                    sendMessageCallback);
             return;
         }
         switch (standbyAction) {
@@ -265,7 +275,8 @@
                     case HdmiControlManager.POWER_CONTROL_MODE_TV:
                         mService.sendCecCommand(
                                 HdmiCecMessageBuilder.buildStandby(
-                                        getDeviceInfo().getLogicalAddress(), Constants.ADDR_TV));
+                                        getDeviceInfo().getLogicalAddress(), Constants.ADDR_TV),
+                                sendMessageCallback);
                         break;
                     case HdmiControlManager.POWER_CONTROL_MODE_TV_AND_AUDIO_SYSTEM:
                         mService.sendCecCommand(
@@ -274,19 +285,19 @@
                         mService.sendCecCommand(
                                 HdmiCecMessageBuilder.buildStandby(
                                         getDeviceInfo().getLogicalAddress(),
-                                        Constants.ADDR_AUDIO_SYSTEM));
+                                        Constants.ADDR_AUDIO_SYSTEM), sendMessageCallback);
                         break;
                     case HdmiControlManager.POWER_CONTROL_MODE_BROADCAST:
                         mService.sendCecCommand(
                                 HdmiCecMessageBuilder.buildStandby(
                                         getDeviceInfo().getLogicalAddress(),
-                                        Constants.ADDR_BROADCAST));
+                                        Constants.ADDR_BROADCAST), sendMessageCallback);
                         break;
                     case HdmiControlManager.POWER_CONTROL_MODE_NONE:
                         mService.sendCecCommand(
                                 HdmiCecMessageBuilder.buildInactiveSource(
                                         getDeviceInfo().getLogicalAddress(),
-                                        mService.getPhysicalAddress()));
+                                        mService.getPhysicalAddress()), sendMessageCallback);
                         break;
                 }
                 break;
@@ -294,7 +305,8 @@
                 // ACTION_SHUTDOWN is taken as a signal to power off all the devices.
                 mService.sendCecCommand(
                         HdmiCecMessageBuilder.buildStandby(
-                                getDeviceInfo().getLogicalAddress(), Constants.ADDR_BROADCAST));
+                                getDeviceInfo().getLogicalAddress(), Constants.ADDR_BROADCAST),
+                        sendMessageCallback);
                 break;
         }
     }
@@ -589,7 +601,7 @@
     }
 
     private class SystemWakeLock implements ActiveWakeLock {
-        private final WakeLock mWakeLock;
+        private final WakeLockWrapper mWakeLock;
         public SystemWakeLock() {
             mWakeLock = mService.getPowerManager().newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
             mWakeLock.setReferenceCounted(false);
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index 5ef06f9..6cca1bd 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -377,6 +377,7 @@
             return;
         }
         int oldPath = getActivePortId() != Constants.INVALID_PORT_ID
+                && getActivePortId() != Constants.CEC_SWITCH_HOME
                 ? mService.portIdToPath(getActivePortId()) : getDeviceInfo().getPhysicalAddress();
         setActivePath(oldPath);
         if (mSkipRoutingControl) {
@@ -1184,7 +1185,11 @@
         if (mService.isSystemAudioActivated()) {
             return Constants.ABORT_NOT_IN_CORRECT_MODE;
         } else {
-            mService.setStreamMusicVolume(message.getAudioVolumeLevel(), 0);
+            int audioVolumeLevel = message.getAudioVolumeLevel();
+            if (audioVolumeLevel >= AudioStatus.MIN_VOLUME
+                    && audioVolumeLevel <= AudioStatus.MAX_VOLUME) {
+                mService.setStreamMusicVolume(audioVolumeLevel, 0);
+            }
             return Constants.HANDLED;
         }
     }
@@ -1395,10 +1400,12 @@
 
     @Override
     @ServiceThreadOnly
-    protected void onStandby(boolean initiatedByCec, int standbyAction) {
+    protected void onStandby(boolean initiatedByCec, int standbyAction,
+            StandbyCompletedCallback callback) {
         assertRunOnServiceThread();
         // Seq #11
         if (!mService.isCecControlEnabled()) {
+            invokeStandbyCompletedCallback(callback);
             return;
         }
         boolean sendStandbyOnSleep =
@@ -1408,7 +1415,15 @@
         if (!initiatedByCec && sendStandbyOnSleep) {
             mService.sendCecCommand(
                     HdmiCecMessageBuilder.buildStandby(
-                            getDeviceInfo().getLogicalAddress(), Constants.ADDR_BROADCAST));
+                            getDeviceInfo().getLogicalAddress(), Constants.ADDR_BROADCAST),
+                    new SendMessageCallback() {
+                        @Override
+                        public void onSendCompleted(int error) {
+                            invokeStandbyCompletedCallback(callback);
+                        }
+                    });
+        } else {
+            invokeStandbyCompletedCallback(callback);
         }
     }
 
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java b/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java
index adcff4c..e9959c2 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java
@@ -287,6 +287,17 @@
     }
 
     /**
+     * Build &lt;Image View On&gt; command.
+     *
+     * @param src source address of command
+     * @param dest destination address of command
+     * @return newly created {@link HdmiCecMessage}
+     */
+    static HdmiCecMessage buildImageViewOn(int src, int dest) {
+        return HdmiCecMessage.build(src, dest, Constants.MESSAGE_IMAGE_VIEW_ON);
+    }
+
+    /**
      * Build &lt;Request Active Source&gt; command.
      *
      * @param src source address of command
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java b/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java
index da38a344..a15cb10 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java
@@ -99,8 +99,7 @@
         // Messages related to the physical address.
         PhysicalAddressValidator physicalAddressValidator = new PhysicalAddressValidator();
         addValidationInfo(Constants.MESSAGE_ACTIVE_SOURCE,
-                physicalAddressValidator, ADDR_ALL ^ (ADDR_RECORDER_1 | ADDR_RECORDER_2
-                        | ADDR_AUDIO_SYSTEM | ADDR_RECORDER_3), ADDR_BROADCAST);
+                physicalAddressValidator, ADDR_ALL ^ ADDR_AUDIO_SYSTEM, ADDR_BROADCAST);
         addValidationInfo(Constants.MESSAGE_INACTIVE_SOURCE,
                 physicalAddressValidator, ADDR_NOT_UNREGISTERED, ADDR_DIRECT);
         addValidationInfo(Constants.MESSAGE_REPORT_PHYSICAL_ADDRESS,
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 3eab4b0..dd45307 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -21,13 +21,16 @@
 import static android.hardware.hdmi.HdmiControlManager.EARC_FEATURE_DISABLED;
 import static android.hardware.hdmi.HdmiControlManager.EARC_FEATURE_ENABLED;
 import static android.hardware.hdmi.HdmiControlManager.HDMI_CEC_CONTROL_ENABLED;
+import static android.hardware.hdmi.HdmiControlManager.POWER_CONTROL_MODE_NONE;
 import static android.hardware.hdmi.HdmiControlManager.SOUNDBAR_MODE_DISABLED;
 import static android.hardware.hdmi.HdmiControlManager.SOUNDBAR_MODE_ENABLED;
+import static android.hardware.hdmi.HdmiControlManager.TV_SEND_STANDBY_ON_SLEEP_ENABLED;
 
 import static com.android.server.hdmi.Constants.ADDR_UNREGISTERED;
 import static com.android.server.hdmi.Constants.DISABLED;
 import static com.android.server.hdmi.Constants.ENABLED;
 import static com.android.server.hdmi.Constants.HDMI_EARC_STATUS_ARC_PENDING;
+import static com.android.server.hdmi.Constants.HDMI_EARC_STATUS_UNKNOWN;
 import static com.android.server.hdmi.Constants.OPTION_MHL_ENABLE;
 import static com.android.server.hdmi.Constants.OPTION_MHL_INPUT_SWITCHING;
 import static com.android.server.hdmi.Constants.OPTION_MHL_POWER_CHARGE;
@@ -91,6 +94,7 @@
 import android.os.UserHandle;
 import android.provider.DeviceConfig;
 import android.provider.Settings.Global;
+import android.stats.hdmi.HdmiStatsEnums;
 import android.sysprop.HdmiProperties;
 import android.text.TextUtils;
 import android.util.ArrayMap;
@@ -108,6 +112,7 @@
 import com.android.server.hdmi.HdmiCecController.AllocateAddressCallback;
 import com.android.server.hdmi.HdmiCecLocalDevice.ActiveSource;
 import com.android.server.hdmi.HdmiCecLocalDevice.PendingActionClearedCallback;
+import com.android.server.hdmi.HdmiCecLocalDevice.StandbyCompletedCallback;
 
 import libcore.util.EmptyArray;
 
@@ -222,6 +227,10 @@
     public @interface WakeReason {
     }
 
+    // Timeout in millisecond for device clean up (5s).
+    // Normal actions timeout is 2s but some of them would have several sequences of timeout.
+    static final int DEVICE_CLEANUP_TIMEOUT = 5000;
+
     @VisibleForTesting
     static final AudioDeviceAttributes AUDIO_OUTPUT_DEVICE_HDMI = new AudioDeviceAttributes(
             AudioDeviceAttributes.ROLE_OUTPUT, AudioDeviceInfo.TYPE_HDMI, "");
@@ -490,6 +499,9 @@
     private DeviceConfigWrapper mDeviceConfig;
 
     @Nullable
+    private WakeLockWrapper mWakeLock;
+
+    @Nullable
     private PowerManagerWrapper mPowerManager;
 
     @Nullable
@@ -1506,6 +1518,51 @@
         }
     }
 
+    @ServiceThreadOnly
+    void sendCecCommand(HdmiCecMessage command) {
+        sendCecCommand(command, null);
+    }
+
+    @ServiceThreadOnly
+    void sendCecCommand(HdmiCecMessage command, @Nullable SendMessageCallback callback) {
+        switch (command.getOpcode()) {
+            case Constants.MESSAGE_ACTIVE_SOURCE:
+            case Constants.MESSAGE_IMAGE_VIEW_ON:
+            case Constants.MESSAGE_INACTIVE_SOURCE:
+            case Constants.MESSAGE_ROUTING_CHANGE:
+            case Constants.MESSAGE_SET_STREAM_PATH:
+            case Constants.MESSAGE_TEXT_VIEW_ON:
+                sendCecCommandWithRetries(command, callback);
+                break;
+            default:
+                sendCecCommandWithoutRetries(command, callback);
+        }
+    }
+
+    /**
+     * Create a {@link ResendCecCommandAction} that will retry sending the CEC message if it fails.
+     * @param command  command to be sent on the CEC bus.
+     * @param callback callback for handling the result of sending the command.
+     */
+    @ServiceThreadOnly
+    private void sendCecCommandWithRetries(HdmiCecMessage command,
+            @Nullable SendMessageCallback callback) {
+        assertRunOnServiceThread();
+        HdmiCecLocalDevice localDevice = getAllCecLocalDevices().get(0);
+        if (localDevice != null) {
+            sendCecCommandWithoutRetries(command, new SendMessageCallback() {
+                @Override
+                public void onSendCompleted(int result) {
+                    if (result != SendMessageResult.SUCCESS) {
+                        localDevice.addAndStartAction(new
+                                ResendCecCommandAction(localDevice, command, callback));
+                    }
+                }
+            });
+        }
+    }
+
+
     /**
      * Transmit a CEC command to CEC bus.
      *
@@ -1513,7 +1570,8 @@
      * @param callback interface used to the result of send command
      */
     @ServiceThreadOnly
-    void sendCecCommand(HdmiCecMessage command, @Nullable SendMessageCallback callback) {
+    void sendCecCommandWithoutRetries(HdmiCecMessage command,
+            @Nullable SendMessageCallback callback) {
         assertRunOnServiceThread();
         if (command.getValidationResult() == HdmiCecMessageValidator.OK
                 && verifyPhysicalAddresses(command)) {
@@ -1526,12 +1584,6 @@
         }
     }
 
-    @ServiceThreadOnly
-    void sendCecCommand(HdmiCecMessage command) {
-        assertRunOnServiceThread();
-        sendCecCommand(command, null);
-    }
-
     /**
      * Send <Feature Abort> command on the given CEC message if possible.
      * If the aborted message is invalid, then it wont send the message.
@@ -3013,7 +3065,7 @@
         }
         String powerControlMode = getHdmiCecConfig().getStringValue(
                 HdmiControlManager.CEC_SETTING_NAME_POWER_CONTROL_MODE);
-        if (powerControlMode.equals(HdmiControlManager.POWER_CONTROL_MODE_NONE)) {
+        if (powerControlMode.equals(POWER_CONTROL_MODE_NONE)) {
             return false;
         }
         int hdmiCecEnabled = getHdmiCecConfig().getIntValue(
@@ -3530,7 +3582,8 @@
         }
     }
 
-    private boolean isEarcSupported() {
+    @VisibleForTesting
+    protected boolean isEarcSupported() {
         synchronized (mLock) {
             return mEarcSupported;
         }
@@ -3648,12 +3701,20 @@
                 setEarcEnabledInHal(false, false);
             }
         }
+        if (isTvDevice()) {
+            int earcStatus = getEarcStatus();
+            getAtomWriter().earcStatusChanged(isEarcSupported(), isEarcEnabled(),
+                    earcStatus, earcStatus, HdmiStatsEnums.LOG_REASON_WAKE);
+        }
         // TODO: Initialize MHL local devices.
     }
 
     @ServiceThreadOnly
     @VisibleForTesting
     protected void onStandby(final int standbyAction) {
+        if (shouldAcquireWakeLockOnStandby()) {
+            acquireWakeLock();
+        }
         mWakeUpMessageReceived = false;
         assertRunOnServiceThread();
         mPowerStatusController.setPowerStatus(HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY,
@@ -3719,7 +3780,8 @@
         return mMenuLanguage;
     }
 
-    private void disableCecLocalDevices(PendingActionClearedCallback callback) {
+    @VisibleForTesting
+    protected void disableCecLocalDevices(PendingActionClearedCallback callback) {
         if (mCecController != null) {
             for (HdmiCecLocalDevice device : mHdmiCecNetwork.getLocalDeviceList()) {
                 device.disableDevice(mStandbyMessageReceived, callback);
@@ -3747,25 +3809,81 @@
      * cleared during standby. In this case, it does not execute the standby flow.
      */
     @ServiceThreadOnly
-    private void onPendingActionsCleared(int standbyAction) {
+    @VisibleForTesting
+    protected void onPendingActionsCleared(int standbyAction) {
         assertRunOnServiceThread();
         Slog.v(TAG, "onPendingActionsCleared");
+        int localDevicesCount = getAllCecLocalDevices().size();
+        final int[] countStandbyCompletedDevices = new int[1];
+        StandbyCompletedCallback callback = new StandbyCompletedCallback() {
+            @Override
+            public void onStandbyCompleted() {
+                if (localDevicesCount < ++countStandbyCompletedDevices[0]) {
+                    return;
+                }
+
+                releaseWakeLock();
+                if (isAudioSystemDevice() || !isPowerStandby()) {
+                    return;
+                }
+                mCecController.enableSystemCecControl(false);
+                mMhlController.setOption(OPTION_MHL_SERVICE_CONTROL, DISABLED);
+            }
+        };
 
         if (mPowerStatusController.isPowerStatusTransientToStandby()) {
             mPowerStatusController.setPowerStatus(HdmiControlManager.POWER_STATUS_STANDBY);
             for (HdmiCecLocalDevice device : mHdmiCecNetwork.getLocalDeviceList()) {
-                device.onStandby(mStandbyMessageReceived, standbyAction);
-            }
-            if (!isAudioSystemDevice()) {
-                mCecController.enableSystemCecControl(false);
-                mMhlController.setOption(OPTION_MHL_SERVICE_CONTROL, DISABLED);
+                device.onStandby(mStandbyMessageReceived, standbyAction, callback);
             }
         }
-
         // Always reset this flag to set up for the next standby
         mStandbyMessageReceived = false;
     }
 
+    private boolean shouldAcquireWakeLockOnStandby() {
+        boolean sendStandbyOnSleep = false;
+        if (tv() != null) {
+            sendStandbyOnSleep = mHdmiCecConfig.getIntValue(
+                    HdmiControlManager.CEC_SETTING_NAME_TV_SEND_STANDBY_ON_SLEEP)
+                    == TV_SEND_STANDBY_ON_SLEEP_ENABLED;
+        } else if (playback() != null) {
+            sendStandbyOnSleep = !mHdmiCecConfig.getStringValue(
+                            HdmiControlManager.CEC_SETTING_NAME_POWER_CONTROL_MODE)
+                    .equals(POWER_CONTROL_MODE_NONE);
+        }
+
+        return isCecControlEnabled() && isPowerOnOrTransient() && sendStandbyOnSleep;
+    }
+
+    /**
+     * Acquire the wake lock used to hold the system awake until the standby process is finished.
+     */
+    @VisibleForTesting
+    protected void acquireWakeLock() {
+        releaseWakeLock();
+        mWakeLock = mPowerManager.newWakeLock(
+                PowerManager.PARTIAL_WAKE_LOCK, TAG);
+        mWakeLock.acquire(DEVICE_CLEANUP_TIMEOUT);
+    }
+
+    /**
+     * Release the wake lock acquired when the standby process started.
+     */
+    @VisibleForTesting
+    protected void releaseWakeLock() {
+        if (mWakeLock != null) {
+            try {
+                if (mWakeLock.isHeld()) {
+                    mWakeLock.release();
+                }
+            } catch (RuntimeException e) {
+                Slog.w(TAG, "Exception when releasing wake lock.");
+            }
+            mWakeLock = null;
+        }
+    }
+
     @VisibleForTesting
     void addVendorCommandListener(IHdmiVendorCommandListener listener, int vendorId) {
         VendorCommandListenerRecord record = new VendorCommandListenerRecord(listener, vendorId);
@@ -3786,6 +3904,7 @@
             if (mVendorCommandListenerRecords.isEmpty()) {
                 return false;
             }
+            boolean notifiedVendorCommandToListeners = false;
             for (VendorCommandListenerRecord record : mVendorCommandListenerRecords) {
                 if (hasVendorId) {
                     int vendorId =
@@ -3798,12 +3917,12 @@
                 }
                 try {
                     record.mListener.onReceived(srcAddress, destAddress, params, hasVendorId);
-                    return true;
+                    notifiedVendorCommandToListeners = true;
                 } catch (RemoteException e) {
                     Slog.e(TAG, "Failed to notify vendor command reception", e);
                 }
             }
-            return false;
+            return notifiedVendorCommandToListeners;
         }
     }
 
@@ -4678,6 +4797,17 @@
     }
 
     @ServiceThreadOnly
+    private int getEarcStatus() {
+        assertRunOnServiceThread();
+        if (mEarcLocalDevice != null) {
+            synchronized (mLock) {
+                return mEarcLocalDevice.mEarcStatus;
+            }
+        }
+        return HDMI_EARC_STATUS_UNKNOWN;
+    }
+
+    @ServiceThreadOnly
     @VisibleForTesting
     HdmiEarcLocalDevice getEarcLocalDevice() {
         assertRunOnServiceThread();
@@ -4725,17 +4855,29 @@
     @ServiceThreadOnly
     void handleEarcStateChange(int status, int portId) {
         assertRunOnServiceThread();
+        int oldEarcStatus = getEarcStatus();
         if (!getPortInfo(portId).isEarcSupported()) {
             Slog.w(TAG, "Tried to update eARC status on a port that doesn't support eARC.");
+            getAtomWriter().earcStatusChanged(isEarcSupported(), isEarcEnabled(), oldEarcStatus,
+                    status, HdmiStatsEnums.LOG_REASON_EARC_STATUS_CHANGED_UNSUPPORTED_PORT);
             return;
         }
         if (mEarcLocalDevice != null) {
             mEarcLocalDevice.handleEarcStateChange(status);
+            getAtomWriter().earcStatusChanged(isEarcSupported(), isEarcEnabled(),
+                    oldEarcStatus, status, HdmiStatsEnums.LOG_REASON_EARC_STATUS_CHANGED);
         } else if (status == HDMI_EARC_STATUS_ARC_PENDING) {
             // If eARC is disabled, the local device is null. This is why we notify
             // AudioService here that the eARC connection is terminated.
+            HdmiLogger.debug("eARC state change [new: HDMI_EARC_STATUS_ARC_PENDING(2)]");
             notifyEarcStatusToAudioService(false, new ArrayList<>());
             startArcAction(true, null);
+            getAtomWriter().earcStatusChanged(isEarcSupported(), isEarcEnabled(),
+                    oldEarcStatus, status, HdmiStatsEnums.LOG_REASON_EARC_STATUS_CHANGED);
+        } else {
+            getAtomWriter().earcStatusChanged(isEarcSupported(), isEarcEnabled(),
+                    oldEarcStatus, status,
+                    HdmiStatsEnums.LOG_REASON_EARC_STATUS_CHANGED_WRONG_STATE);
         }
     }
 
diff --git a/services/core/java/com/android/server/hdmi/PowerManagerWrapper.java b/services/core/java/com/android/server/hdmi/PowerManagerWrapper.java
index f081068..7530b3b 100644
--- a/services/core/java/com/android/server/hdmi/PowerManagerWrapper.java
+++ b/services/core/java/com/android/server/hdmi/PowerManagerWrapper.java
@@ -18,7 +18,6 @@
 
 import android.content.Context;
 import android.os.PowerManager;
-import android.os.PowerManager.WakeLock;
 
 /**
  * Abstraction around {@link PowerManager} to allow faking PowerManager in tests.
@@ -44,7 +43,54 @@
         mPowerManager.goToSleep(time, reason, flags);
     }
 
-    WakeLock newWakeLock(int levelAndFlags, String tag) {
-        return mPowerManager.newWakeLock(levelAndFlags, tag);
+    WakeLockWrapper newWakeLock(int levelAndFlags, String tag) {
+        return new DefaultWakeLockWrapper(mPowerManager.newWakeLock(levelAndFlags, tag));
+    }
+
+    /**
+     * "Default" wrapper for {@link PowerManager.WakeLock}, as opposed to a "Fake" wrapper for
+     * testing - see {@link FakePowerManagerWrapper.FakeWakeLockWrapper}.
+     *
+     * Stores an instance of {@link PowerManager.WakeLock} and directly passes method calls to that
+     * instance.
+     */
+    public static class DefaultWakeLockWrapper implements WakeLockWrapper {
+
+        private static final String TAG = "DefaultWakeLockWrapper";
+
+        private final PowerManager.WakeLock mWakeLock;
+
+        private DefaultWakeLockWrapper(PowerManager.WakeLock wakeLock) {
+            mWakeLock = wakeLock;
+        }
+
+        @Override
+        public void acquire(long timeout) {
+            mWakeLock.acquire(timeout);
+        }
+
+        @Override
+        public void acquire() {
+            mWakeLock.acquire();
+        }
+
+        /**
+         * @throws RuntimeException WakeLock can throw this exception if it is not released
+         * successfully.
+         */
+        @Override
+        public void release() throws RuntimeException {
+            mWakeLock.release();
+        }
+
+        @Override
+        public boolean isHeld() {
+            return mWakeLock.isHeld();
+        }
+
+        @Override
+        public void setReferenceCounted(boolean value) {
+            mWakeLock.setReferenceCounted(value);
+        }
     }
 }
diff --git a/services/core/java/com/android/server/hdmi/ResendCecCommandAction.java b/services/core/java/com/android/server/hdmi/ResendCecCommandAction.java
new file mode 100644
index 0000000..b130c46
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/ResendCecCommandAction.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.hdmi;
+
+import android.hardware.tv.hdmi.cec.SendMessageResult;
+import android.util.Slog;
+
+import com.android.server.hdmi.HdmiControlService.SendMessageCallback;
+
+/**
+ * Action that retries RETRANSMISSION_COUNT times to send a CEC command when the first attempt to
+ * send the message failed.
+ *
+ * This action starts with a delay of SEND_COMMAND_RETRY_MS milliseconds.
+ *
+ * If this action can't be started it will be canceled and not deferred.
+ * See {@link HdmiCecLocalDevice#addAndStartAction}.
+ */
+public class ResendCecCommandAction extends HdmiCecFeatureAction {
+    private static final String TAG = "ResendCecCommandAction";
+    private static final int RETRANSMISSION_COUNT = 1;
+    private static final int STATE_WAIT_FOR_RESEND_COMMAND = 1;
+    static final int SEND_COMMAND_RETRY_MS = 300;
+
+    private final HdmiCecMessage mCommand;
+    private int mRetransmissionCount = 0;
+    private final SendMessageCallback mResultCallback;
+    private final SendMessageCallback mCallback = new SendMessageCallback(){
+        @Override
+        public void onSendCompleted(int result) {
+            if (result != SendMessageResult.SUCCESS
+                    && mRetransmissionCount++ < RETRANSMISSION_COUNT) {
+                mState = STATE_WAIT_FOR_RESEND_COMMAND;
+                addTimer(mState, SEND_COMMAND_RETRY_MS);
+            } else {
+                if (mResultCallback != null) {
+                    mResultCallback.onSendCompleted(result);
+                }
+                finish();
+            }
+        }
+    };
+
+    ResendCecCommandAction(HdmiCecLocalDevice source, HdmiCecMessage command,
+            SendMessageCallback callback) {
+        super(source);
+        mCommand = command;
+        mResultCallback = callback;
+        mState = STATE_WAIT_FOR_RESEND_COMMAND;
+        addTimer(mState, SEND_COMMAND_RETRY_MS);
+    }
+
+    @Override
+    boolean start() {
+        Slog.d(TAG, "ResendCecCommandAction started");
+        return true;
+    }
+
+    @Override
+    void handleTimerEvent(int state) {
+        if (mState != state) {
+            Slog.w(TAG, "Timeout in invalid state:[Expected:" + mState + ", Actual:" + state
+                    + "]");
+            return;
+        }
+        if (mState == STATE_WAIT_FOR_RESEND_COMMAND) {
+            Slog.d(TAG, "sendCommandWithoutRetries failed, retry");
+            sendCommandWithoutRetries(mCommand, mCallback);
+        }
+    }
+
+    @Override
+    boolean processCommand(HdmiCecMessage command) {
+        return false;
+    }
+}
diff --git a/services/core/java/com/android/server/hdmi/WakeLockWrapper.java b/services/core/java/com/android/server/hdmi/WakeLockWrapper.java
new file mode 100644
index 0000000..f689910
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/WakeLockWrapper.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.hdmi;
+
+/**
+ * Interface with the methods from {@link PowerManager.WakeLock} used by the HDMI control framework.
+ * Allows the class to be faked for the tests.
+ *
+ * See implementations {@link DefaultWakeLockWrapper} and
+ * {@link FakePowerManagerWrapper.FakeWakeLockWrapper}.
+ */
+public interface WakeLockWrapper {
+
+    /**
+     * Wraps {@link PowerManager.WakeLock#acquire(long)};
+     */
+    void acquire(long timeout);
+
+    /**
+     * Wraps {@link PowerManager.WakeLock#acquire()};
+     */
+    void acquire();
+
+    /**
+     * Wraps {@link PowerManager.WakeLock#release()};
+     */
+    void release();
+
+    /**
+     * Wraps {@link PowerManager.WakeLock#isHeld()};
+     */
+    boolean isHeld();
+
+    /**
+     * Wraps {@link PowerManager.WakeLock#setReferenceCounted(boolean)};
+     */
+    void setReferenceCounted(boolean value);
+}
diff --git a/services/core/java/com/android/server/input/AmbientKeyboardBacklightController.java b/services/core/java/com/android/server/input/AmbientKeyboardBacklightController.java
new file mode 100644
index 0000000..ce86849
--- /dev/null
+++ b/services/core/java/com/android/server/input/AmbientKeyboardBacklightController.java
@@ -0,0 +1,431 @@
+/*
+ * 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.input;
+
+import android.annotation.MainThread;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.res.Resources;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayManagerInternal;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Log;
+import android.util.Slog;
+import android.util.TypedValue;
+import android.view.Display;
+import android.view.DisplayInfo;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.LocalServices;
+import com.android.server.display.utils.SensorUtils;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * A thread-safe component of {@link InputManagerService} responsible for managing the keyboard
+ * backlight based on ambient light sensor.
+ */
+final class AmbientKeyboardBacklightController implements DisplayManager.DisplayListener,
+        SensorEventListener {
+
+    private static final String TAG = "KbdBacklightController";
+
+    // To enable these logs, run:
+    // 'adb shell setprop log.tag.KbdBacklightController DEBUG' (requires restart)
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+    // Number of light sensor responses required to overcome temporal hysteresis.
+    @VisibleForTesting
+    public static final int HYSTERESIS_THRESHOLD = 2;
+
+    private static final int MSG_BRIGHTNESS_CALLBACK = 0;
+    private static final int MSG_SETUP_DISPLAY_AND_SENSOR = 1;
+
+    private static final Object sAmbientControllerLock = new Object();
+
+    private final Context mContext;
+    private final Handler mHandler;
+
+    @Nullable
+    @GuardedBy("sAmbientControllerLock")
+    private Sensor mLightSensor;
+    @GuardedBy("sAmbientControllerLock")
+    private String mCurrentDefaultDisplayUniqueId;
+
+    // List of currently registered ambient backlight listeners
+    @GuardedBy("sAmbientControllerLock")
+    private final List<AmbientKeyboardBacklightListener> mAmbientKeyboardBacklightListeners =
+            new ArrayList<>();
+
+    private BrightnessStep[] mBrightnessSteps;
+    private int mCurrentBrightnessStepIndex;
+    private HysteresisState mHysteresisState;
+    private int mHysteresisCount = 0;
+    private float mSmoothingConstant;
+    private int mSmoothedLux;
+    private int mSmoothedLuxAtLastAdjustment;
+
+    private enum HysteresisState {
+        // The most-recent mSmoothedLux matched mSmoothedLuxAtLastAdjustment.
+        STABLE,
+        // The most-recent mSmoothedLux was less than mSmoothedLuxAtLastAdjustment.
+        DECREASING,
+        // The most-recent mSmoothedLux was greater than mSmoothedLuxAtLastAdjustment.
+        INCREASING,
+        // The brightness should be adjusted immediately after the next sensor reading.
+        IMMEDIATE,
+    }
+
+    AmbientKeyboardBacklightController(Context context, Looper looper) {
+        mContext = context;
+        mHandler = new Handler(looper, this::handleMessage);
+        initConfiguration();
+    }
+
+    public void systemRunning() {
+        mHandler.sendEmptyMessage(MSG_SETUP_DISPLAY_AND_SENSOR);
+        DisplayManager displayManager = Objects.requireNonNull(
+                mContext.getSystemService(DisplayManager.class));
+        displayManager.registerDisplayListener(this, mHandler);
+    }
+
+    public void registerAmbientBacklightListener(AmbientKeyboardBacklightListener listener) {
+        synchronized (sAmbientControllerLock) {
+            if (mAmbientKeyboardBacklightListeners.contains(listener)) {
+                throw new IllegalStateException(
+                        "AmbientKeyboardBacklightListener was already registered, listener = "
+                                + listener);
+            }
+            if (mAmbientKeyboardBacklightListeners.isEmpty()) {
+                // Add sensor listener when we add the first ambient backlight listener.
+                addSensorListener(mLightSensor);
+            }
+            mAmbientKeyboardBacklightListeners.add(listener);
+        }
+    }
+
+    public void unregisterAmbientBacklightListener(AmbientKeyboardBacklightListener listener) {
+        synchronized (sAmbientControllerLock) {
+            if (!mAmbientKeyboardBacklightListeners.contains(listener)) {
+                throw new IllegalStateException(
+                        "AmbientKeyboardBacklightListener was never registered, listener = "
+                                + listener);
+            }
+            mAmbientKeyboardBacklightListeners.remove(listener);
+            if (mAmbientKeyboardBacklightListeners.isEmpty()) {
+                removeSensorListener(mLightSensor);
+            }
+        }
+    }
+
+    private void sendBrightnessAdjustment(int brightnessValue) {
+        Message msg = Message.obtain(mHandler, MSG_BRIGHTNESS_CALLBACK, brightnessValue);
+        mHandler.sendMessage(msg);
+    }
+
+    @MainThread
+    private void handleBrightnessCallback(int brightnessValue) {
+        synchronized (sAmbientControllerLock) {
+            for (AmbientKeyboardBacklightListener listener : mAmbientKeyboardBacklightListeners) {
+                listener.onKeyboardBacklightValueChanged(brightnessValue);
+            }
+        }
+    }
+
+    @MainThread
+    private void handleAmbientLuxChange(float rawLux) {
+        if (rawLux < 0) {
+            Slog.w(TAG, "Light sensor doesn't have valid value");
+            return;
+        }
+        updateSmoothedLux(rawLux);
+
+        if (mHysteresisState != HysteresisState.IMMEDIATE
+                && mSmoothedLux == mSmoothedLuxAtLastAdjustment) {
+            mHysteresisState = HysteresisState.STABLE;
+            return;
+        }
+
+        int newStepIndex = Math.max(0, mCurrentBrightnessStepIndex);
+        int numSteps = mBrightnessSteps.length;
+
+        if (mSmoothedLux > mSmoothedLuxAtLastAdjustment) {
+            if (mHysteresisState != HysteresisState.IMMEDIATE
+                    && mHysteresisState != HysteresisState.INCREASING) {
+                if (DEBUG) {
+                    Slog.d(TAG, "ALS transitioned to brightness increasing state");
+                }
+                mHysteresisState = HysteresisState.INCREASING;
+                mHysteresisCount = 0;
+            }
+            for (; newStepIndex < numSteps; newStepIndex++) {
+                if (mSmoothedLux < mBrightnessSteps[newStepIndex].mIncreaseLuxThreshold) {
+                    break;
+                }
+            }
+        } else if (mSmoothedLux < mSmoothedLuxAtLastAdjustment) {
+            if (mHysteresisState != HysteresisState.IMMEDIATE
+                    && mHysteresisState != HysteresisState.DECREASING) {
+                if (DEBUG) {
+                    Slog.d(TAG, "ALS transitioned to brightness decreasing state");
+                }
+                mHysteresisState = HysteresisState.DECREASING;
+                mHysteresisCount = 0;
+            }
+            for (; newStepIndex >= 0; newStepIndex--) {
+                if (mSmoothedLux > mBrightnessSteps[newStepIndex].mDecreaseLuxThreshold) {
+                    break;
+                }
+            }
+        }
+
+        if (mHysteresisState == HysteresisState.IMMEDIATE) {
+            mCurrentBrightnessStepIndex = newStepIndex;
+            mSmoothedLuxAtLastAdjustment = mSmoothedLux;
+            mHysteresisState = HysteresisState.STABLE;
+            mHysteresisCount = 0;
+            sendBrightnessAdjustment(mBrightnessSteps[newStepIndex].mBrightnessValue);
+            return;
+        }
+
+        if (newStepIndex == mCurrentBrightnessStepIndex) {
+            return;
+        }
+
+        mHysteresisCount++;
+        if (DEBUG) {
+            Slog.d(TAG, "Incremented hysteresis count to " + mHysteresisCount + " (lux went from "
+                    + mSmoothedLuxAtLastAdjustment + " to " + mSmoothedLux + ")");
+        }
+        if (mHysteresisCount >= HYSTERESIS_THRESHOLD) {
+            mCurrentBrightnessStepIndex = newStepIndex;
+            mSmoothedLuxAtLastAdjustment = mSmoothedLux;
+            mHysteresisCount = 1;
+            sendBrightnessAdjustment(mBrightnessSteps[newStepIndex].mBrightnessValue);
+        }
+    }
+
+    @MainThread
+    private void handleDisplayChange() {
+        DisplayManagerInternal displayManagerInternal = LocalServices.getService(
+                DisplayManagerInternal.class);
+        DisplayInfo displayInfo = displayManagerInternal.getDisplayInfo(Display.DEFAULT_DISPLAY);
+        synchronized (sAmbientControllerLock) {
+            if (Objects.equals(mCurrentDefaultDisplayUniqueId, displayInfo.uniqueId)) {
+                return;
+            }
+            if (DEBUG) {
+                Slog.d(TAG, "Default display changed: resetting the light sensor");
+            }
+            // Keep track of current default display
+            mCurrentDefaultDisplayUniqueId = displayInfo.uniqueId;
+            // Clear all existing sensor listeners
+            if (!mAmbientKeyboardBacklightListeners.isEmpty()) {
+                removeSensorListener(mLightSensor);
+            }
+            mLightSensor = getAmbientLightSensor(
+                    displayManagerInternal.getAmbientLightSensorData(Display.DEFAULT_DISPLAY));
+            // Re-add sensor listeners if required;
+            if (!mAmbientKeyboardBacklightListeners.isEmpty()) {
+                addSensorListener(mLightSensor);
+            }
+        }
+    }
+
+    private Sensor getAmbientLightSensor(
+            DisplayManagerInternal.AmbientLightSensorData ambientSensor) {
+        SensorManager sensorManager = Objects.requireNonNull(
+                mContext.getSystemService(SensorManager.class));
+        if (DEBUG) {
+            Slog.d(TAG, "Ambient Light sensor data: " + ambientSensor);
+        }
+        return SensorUtils.findSensor(sensorManager, ambientSensor.sensorType,
+                ambientSensor.sensorName, Sensor.TYPE_LIGHT);
+    }
+
+    private void updateSmoothedLux(float rawLux) {
+        // For the first sensor reading, use raw lux value directly without smoothing.
+        if (mHysteresisState == HysteresisState.IMMEDIATE) {
+            mSmoothedLux = (int) rawLux;
+        } else {
+            mSmoothedLux =
+                    (int) (mSmoothingConstant * rawLux + (1 - mSmoothingConstant) * mSmoothedLux);
+        }
+        if (DEBUG) {
+            Slog.d(TAG, "Current smoothed lux from ALS = " + mSmoothedLux);
+        }
+    }
+
+    @VisibleForTesting
+    public void addSensorListener(@Nullable Sensor sensor) {
+        SensorManager sensorManager = mContext.getSystemService(SensorManager.class);
+        if (sensorManager == null || sensor == null) {
+            return;
+        }
+        // Reset values before registering listener
+        reset();
+        sensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_NORMAL, mHandler);
+        if (DEBUG) {
+            Slog.d(TAG, "Registering ALS listener");
+        }
+    }
+
+    private void removeSensorListener(@Nullable Sensor sensor) {
+        SensorManager sensorManager = mContext.getSystemService(SensorManager.class);
+        if (sensorManager == null || sensor == null) {
+            return;
+        }
+        sensorManager.unregisterListener(this, sensor);
+        if (DEBUG) {
+            Slog.d(TAG, "Unregistering ALS listener");
+        }
+    }
+
+    private void initConfiguration() {
+        Resources res = mContext.getResources();
+        int[] brightnessValueArray = res.getIntArray(
+                com.android.internal.R.array.config_autoKeyboardBacklightBrightnessValues);
+        int[] decreaseThresholdArray = res.getIntArray(
+                com.android.internal.R.array.config_autoKeyboardBacklightDecreaseLuxThreshold);
+        int[] increaseThresholdArray = res.getIntArray(
+                com.android.internal.R.array.config_autoKeyboardBacklightIncreaseLuxThreshold);
+        if (brightnessValueArray.length != decreaseThresholdArray.length
+                || decreaseThresholdArray.length != increaseThresholdArray.length) {
+            throw new IllegalArgumentException(
+                    "The config files for auto keyboard backlight brightness must contain arrays "
+                            + "of equal lengths");
+        }
+        final int size = brightnessValueArray.length;
+        mBrightnessSteps = new BrightnessStep[size];
+        for (int i = 0; i < size; i++) {
+            int increaseThreshold =
+                    increaseThresholdArray[i] < 0 ? Integer.MAX_VALUE : increaseThresholdArray[i];
+            int decreaseThreshold =
+                    decreaseThresholdArray[i] < 0 ? Integer.MIN_VALUE : decreaseThresholdArray[i];
+            mBrightnessSteps[i] = new BrightnessStep(brightnessValueArray[i], increaseThreshold,
+                    decreaseThreshold);
+        }
+
+        int numSteps = mBrightnessSteps.length;
+        if (numSteps == 0 || mBrightnessSteps[0].mDecreaseLuxThreshold != Integer.MIN_VALUE
+                || mBrightnessSteps[numSteps - 1].mIncreaseLuxThreshold != Integer.MAX_VALUE) {
+            throw new IllegalArgumentException(
+                    "The config files for auto keyboard backlight brightness must contain arrays "
+                            + "of length > 0 and have -1 or Integer.MIN_VALUE as lower bound for "
+                            + "decrease thresholds and -1 or Integer.MAX_VALUE as upper bound for "
+                            + "increase thresholds");
+        }
+
+        final TypedValue smoothingConstantValue = new TypedValue();
+        res.getValue(
+                com.android.internal.R.dimen.config_autoKeyboardBrightnessSmoothingConstant,
+                smoothingConstantValue,
+                true /*resolveRefs*/);
+        mSmoothingConstant = smoothingConstantValue.getFloat();
+        if (mSmoothingConstant <= 0.0 || mSmoothingConstant > 1.0) {
+            throw new IllegalArgumentException(
+                    "The config files for auto keyboard backlight brightness must contain "
+                            + "smoothing constant in range (0.0, 1.0].");
+        }
+
+        if (DEBUG) {
+            Log.d(TAG, "Brightness steps: " + Arrays.toString(mBrightnessSteps)
+                    + " Smoothing constant = " + mSmoothingConstant);
+        }
+    }
+
+    private void reset() {
+        mHysteresisState = HysteresisState.IMMEDIATE;
+        mSmoothedLux = 0;
+        mSmoothedLuxAtLastAdjustment = 0;
+        mCurrentBrightnessStepIndex = -1;
+    }
+
+    private boolean handleMessage(Message msg) {
+        switch (msg.what) {
+            case MSG_BRIGHTNESS_CALLBACK:
+                handleBrightnessCallback((int) msg.obj);
+                return true;
+            case MSG_SETUP_DISPLAY_AND_SENSOR:
+                handleDisplayChange();
+                return true;
+        }
+        return false;
+    }
+
+    @Override
+    public void onSensorChanged(SensorEvent event) {
+        handleAmbientLuxChange(event.values[0]);
+    }
+
+    @Override
+    public void onAccuracyChanged(Sensor sensor, int accuracy) {
+    }
+
+    @Override
+    public void onDisplayAdded(int displayId) {
+        handleDisplayChange();
+    }
+
+    @Override
+    public void onDisplayRemoved(int displayId) {
+        handleDisplayChange();
+    }
+
+    @Override
+    public void onDisplayChanged(int displayId) {
+        handleDisplayChange();
+    }
+
+    public interface AmbientKeyboardBacklightListener {
+        /**
+         * @param value between [0, 255] to which keyboard backlight needs to be set according
+         *              to Ambient light sensor.
+         */
+        void onKeyboardBacklightValueChanged(int value);
+    }
+
+    private static class BrightnessStep {
+        private final int mBrightnessValue;
+        private final int mIncreaseLuxThreshold;
+        private final int mDecreaseLuxThreshold;
+
+        private BrightnessStep(int brightnessValue, int increaseLuxThreshold,
+                int decreaseLuxThreshold) {
+            mBrightnessValue = brightnessValue;
+            mIncreaseLuxThreshold = increaseLuxThreshold;
+            mDecreaseLuxThreshold = decreaseLuxThreshold;
+        }
+
+        @Override
+        public String toString() {
+            return "BrightnessStep{" + "mBrightnessValue=" + mBrightnessValue
+                    + ", mIncreaseThreshold=" + mIncreaseLuxThreshold + ", mDecreaseThreshold="
+                    + mDecreaseLuxThreshold + '}';
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/input/BatteryController.java b/services/core/java/com/android/server/input/BatteryController.java
index ff9ce6f..38a0d37 100644
--- a/services/core/java/com/android/server/input/BatteryController.java
+++ b/services/core/java/com/android/server/input/BatteryController.java
@@ -46,7 +46,6 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.input.BatteryController.UEventManager.UEventBatteryListener;
 
 import java.io.PrintWriter;
 import java.util.Arrays;
@@ -102,8 +101,9 @@
     @GuardedBy("mLock")
     private BluetoothBatteryManager.BluetoothBatteryListener mBluetoothBatteryListener;
 
-    BatteryController(Context context, NativeInputManagerService nativeService, Looper looper) {
-        this(context, nativeService, looper, new UEventManager() {},
+    BatteryController(Context context, NativeInputManagerService nativeService, Looper looper,
+            UEventManager uEventManager) {
+        this(context, nativeService, looper, uEventManager,
                 new LocalBluetoothBatteryManager(context, looper));
     }
 
@@ -567,7 +567,7 @@
         private BluetoothAdapter.OnMetadataChangedListener mBluetoothMetadataListener;
 
         @Nullable
-        private UEventBatteryListener mUEventBatteryListener;
+        private BatteryController.UEventBatteryListener mUEventBatteryListener;
 
         DeviceMonitor(int deviceId) {
             mState = new State(deviceId);
@@ -630,7 +630,7 @@
                 return;
             }
             final int deviceId = mState.deviceId;
-            mUEventBatteryListener = new UEventBatteryListener() {
+            mUEventBatteryListener = new BatteryController.UEventBatteryListener() {
                 @Override
                 public void onBatteryUEvent(long eventTime) {
                     handleUEventNotification(deviceId, eventTime);
@@ -898,40 +898,25 @@
         }
     }
 
-    // An interface used to change the API of UEventObserver to a more test-friendly format.
     @VisibleForTesting
-    interface UEventManager {
-
-        @VisibleForTesting
-        abstract class UEventBatteryListener {
-            private final UEventObserver mObserver = new UEventObserver() {
-                @Override
-                public void onUEvent(UEvent event) {
-                    final long eventTime = SystemClock.uptimeMillis();
-                    if (DEBUG) {
-                        Slog.d(TAG,
-                                "UEventListener: Received UEvent: "
-                                        + event + " eventTime: " + eventTime);
-                    }
-                    if (!"CHANGE".equalsIgnoreCase(event.get("ACTION"))
-                            || !"POWER_SUPPLY".equalsIgnoreCase(event.get("SUBSYSTEM"))) {
-                        // Disregard any UEvents that do not correspond to battery changes.
-                        return;
-                    }
-                    UEventBatteryListener.this.onBatteryUEvent(eventTime);
-                }
-            };
-
-            public abstract void onBatteryUEvent(long eventTime);
+    abstract static class UEventBatteryListener extends UEventManager.UEventListener {
+        @Override
+        public void onUEvent(UEventObserver.UEvent event) {
+            final long eventTime = SystemClock.uptimeMillis();
+            if (DEBUG) {
+                Slog.d(TAG,
+                        "UEventListener: Received UEvent: "
+                                + event + " eventTime: " + eventTime);
+            }
+            if (!"CHANGE".equalsIgnoreCase(event.get("ACTION"))
+                    || !"POWER_SUPPLY".equalsIgnoreCase(event.get("SUBSYSTEM"))) {
+                // Disregard any UEvents that do not correspond to battery changes.
+                return;
+            }
+            UEventBatteryListener.this.onBatteryUEvent(eventTime);
         }
 
-        default void addListener(UEventBatteryListener listener, String match) {
-            listener.mObserver.startObserving(match);
-        }
-
-        default void removeListener(UEventBatteryListener listener) {
-            listener.mObserver.stopObserving();
-        }
+        public abstract void onBatteryUEvent(long eventTime);
     }
 
     // An interface used to change the API of adding a bluetooth battery listener to a more
diff --git a/services/core/java/com/android/server/input/InputFeatureFlagProvider.java b/services/core/java/com/android/server/input/InputFeatureFlagProvider.java
new file mode 100644
index 0000000..a646d1e
--- /dev/null
+++ b/services/core/java/com/android/server/input/InputFeatureFlagProvider.java
@@ -0,0 +1,101 @@
+/*
+ * 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.input;
+
+import android.sysprop.InputProperties;
+
+import java.util.Optional;
+
+/**
+ * A component of {@link InputManagerService} responsible for managing the input sysprop flags
+ *
+ * @hide
+ */
+@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
+public final class InputFeatureFlagProvider {
+
+    // To disable Keyboard backlight control via Framework, run:
+    // 'adb shell setprop persist.input.keyboard_backlight_control.enabled false' (requires restart)
+    private static final boolean KEYBOARD_BACKLIGHT_CONTROL_ENABLED =
+            InputProperties.enable_keyboard_backlight_control().orElse(true);
+
+    // To disable Framework controlled keyboard backlight animation run:
+    // adb shell setprop persist.input.keyboard.backlight_animation.enabled false (requires restart)
+    private static final boolean KEYBOARD_BACKLIGHT_ANIMATION_ENABLED =
+            InputProperties.enable_keyboard_backlight_animation().orElse(false);
+
+    // To disable Custom keyboard backlight levels support via IDC files run:
+    // adb shell setprop persist.input.keyboard.backlight_custom_levels.enabled false (requires
+    // restart)
+    private static final boolean KEYBOARD_BACKLIGHT_CUSTOM_LEVELS_ENABLED =
+            InputProperties.enable_keyboard_backlight_custom_levels().orElse(true);
+
+    // To disable als based ambient keyboard backlight control run:
+    // adb shell setprop persist.input.keyboard.ambient_backlight_control.enabled false (requires
+    // restart)
+    private static final boolean AMBIENT_KEYBOARD_BACKLIGHT_CONTROL_ENABLED =
+            InputProperties.enable_ambient_keyboard_backlight_control().orElse(true);
+
+    private static Optional<Boolean> sKeyboardBacklightControlOverride = Optional.empty();
+    private static Optional<Boolean> sKeyboardBacklightAnimationOverride = Optional.empty();
+    private static Optional<Boolean> sKeyboardBacklightCustomLevelsOverride = Optional.empty();
+    private static Optional<Boolean> sAmbientKeyboardBacklightControlOverride = Optional.empty();
+
+    public static boolean isKeyboardBacklightControlEnabled() {
+        return sKeyboardBacklightControlOverride.orElse(KEYBOARD_BACKLIGHT_CONTROL_ENABLED);
+    }
+
+    public static boolean isKeyboardBacklightAnimationEnabled() {
+        return sKeyboardBacklightAnimationOverride.orElse(KEYBOARD_BACKLIGHT_ANIMATION_ENABLED);
+    }
+
+    public static boolean isKeyboardBacklightCustomLevelsEnabled() {
+        return sKeyboardBacklightCustomLevelsOverride.orElse(
+                KEYBOARD_BACKLIGHT_CUSTOM_LEVELS_ENABLED);
+    }
+
+    public static boolean isAmbientKeyboardBacklightControlEnabled() {
+        return sAmbientKeyboardBacklightControlOverride.orElse(
+                AMBIENT_KEYBOARD_BACKLIGHT_CONTROL_ENABLED);
+    }
+
+    public static void setKeyboardBacklightControlEnabled(boolean enabled) {
+        sKeyboardBacklightControlOverride = Optional.of(enabled);
+    }
+
+    public static void setKeyboardBacklightAnimationEnabled(boolean enabled) {
+        sKeyboardBacklightAnimationOverride = Optional.of(enabled);
+    }
+
+    public static void setKeyboardBacklightCustomLevelsEnabled(boolean enabled) {
+        sKeyboardBacklightCustomLevelsOverride = Optional.of(enabled);
+    }
+
+    public static void setAmbientKeyboardBacklightControlEnabled(boolean enabled) {
+        sAmbientKeyboardBacklightControlOverride = Optional.of(enabled);
+    }
+
+    /**
+     * Clears all input feature flag overrides.
+     */
+    public static void clearOverrides() {
+        sKeyboardBacklightControlOverride = Optional.empty();
+        sKeyboardBacklightAnimationOverride = Optional.empty();
+        sKeyboardBacklightCustomLevelsOverride = Optional.empty();
+        sAmbientKeyboardBacklightControlOverride = Optional.empty();
+    }
+}
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 4cb22db..e78adda 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -74,7 +74,6 @@
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.ShellCallback;
-import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.VibrationEffect;
 import android.os.vibrator.StepSegment;
@@ -160,11 +159,6 @@
     private static final AdditionalDisplayInputProperties
             DEFAULT_ADDITIONAL_DISPLAY_INPUT_PROPERTIES = new AdditionalDisplayInputProperties();
 
-    // To disable Keyboard backlight control via Framework, run:
-    // 'adb shell setprop persist.input.keyboard_backlight_control.enabled false' (requires restart)
-    private static final boolean KEYBOARD_BACKLIGHT_CONTROL_ENABLED = SystemProperties.getBoolean(
-            "persist.input.keyboard.backlight_control.enabled", true);
-
     private final NativeInputManagerService mNative;
 
     private final Context mContext;
@@ -399,10 +393,12 @@
     static class Injector {
         private final Context mContext;
         private final Looper mLooper;
+        private final UEventManager mUEventManager;
 
-        Injector(Context context, Looper looper) {
+        Injector(Context context, Looper looper, UEventManager uEventManager) {
             mContext = context;
             mLooper = looper;
+            mUEventManager = uEventManager;
         }
 
         Context getContext() {
@@ -413,6 +409,10 @@
             return mLooper;
         }
 
+        UEventManager getUEventManager() {
+            return mUEventManager;
+        }
+
         NativeInputManagerService getNativeService(InputManagerService service) {
             return new NativeInputManagerService.NativeImpl(service, mLooper.getQueue());
         }
@@ -423,7 +423,7 @@
     }
 
     public InputManagerService(Context context) {
-        this(new Injector(context, DisplayThread.get().getLooper()));
+        this(new Injector(context, DisplayThread.get().getLooper(), new UEventManager() {}));
     }
 
     @VisibleForTesting
@@ -438,11 +438,12 @@
         mSettingsObserver = new InputSettingsObserver(mContext, mHandler, this, mNative);
         mKeyboardLayoutManager = new KeyboardLayoutManager(mContext, mNative, mDataStore,
                 injector.getLooper());
-        mBatteryController = new BatteryController(mContext, mNative, injector.getLooper());
-        mKeyboardBacklightController =
-                KEYBOARD_BACKLIGHT_CONTROL_ENABLED ? new KeyboardBacklightController(mContext,
-                        mNative, mDataStore, injector.getLooper())
-                        : new KeyboardBacklightControllerInterface() {};
+        mBatteryController = new BatteryController(mContext, mNative, injector.getLooper(),
+                injector.getUEventManager());
+        mKeyboardBacklightController = InputFeatureFlagProvider.isKeyboardBacklightControlEnabled()
+                ? new KeyboardBacklightController(mContext, mNative, mDataStore,
+                        injector.getLooper(), injector.getUEventManager())
+                : new KeyboardBacklightControllerInterface() {};
         mKeyRemapper = new KeyRemapper(mContext, mNative, mDataStore, injector.getLooper());
 
         mUseDevInputEventForAudioJack =
@@ -2560,12 +2561,17 @@
     }
 
     /**
-     * Ports are highly platform-specific, so only allow these to be specified in the vendor
+     * Ports are highly platform-specific, so allow these to be specified in the odm/vendor
      * directory.
      */
     private static Map<String, Integer> loadStaticInputPortAssociations() {
-        final File baseDir = Environment.getVendorDirectory();
-        final File confFile = new File(baseDir, PORT_ASSOCIATIONS_PATH);
+        File baseDir = Environment.getOdmDirectory();
+        File confFile = new File(baseDir, PORT_ASSOCIATIONS_PATH);
+
+        if (!confFile.exists()) {
+            baseDir = Environment.getVendorDirectory();
+            confFile = new File(baseDir, PORT_ASSOCIATIONS_PATH);
+        }
 
         try (final InputStream stream = new FileInputStream(confFile)) {
             return ConfigurationProcessor.processInputPortAssociations(stream);
@@ -2637,18 +2643,6 @@
 
     // Native callback.
     @SuppressWarnings("unused")
-    private int getKeyRepeatTimeout() {
-        return ViewConfiguration.getKeyRepeatTimeout();
-    }
-
-    // Native callback.
-    @SuppressWarnings("unused")
-    private int getKeyRepeatDelay() {
-        return ViewConfiguration.getKeyRepeatDelay();
-    }
-
-    // Native callback.
-    @SuppressWarnings("unused")
     private int getHoverTapTimeout() {
         return ViewConfiguration.getHoverTapTimeout();
     }
@@ -2799,6 +2793,11 @@
         void notifyConfigurationChanged();
 
         /**
+         * This callback is invoked when the pointer location changes.
+         */
+        void notifyPointerLocationChanged(boolean pointerLocationEnabled);
+
+        /**
          * This callback is invoked when the camera lens cover switch changes state.
          * @param whenNanos the time when the change occurred
          * @param lensCovered true is the lens is covered
@@ -3380,6 +3379,10 @@
         }
     }
 
+    void updatePointerLocationEnabled(boolean enabled) {
+        mWindowManagerCallbacks.notifyPointerLocationChanged(enabled);
+    }
+
     void updateFocusEventDebugViewEnabled(boolean enabled) {
         FocusEventDebugView view;
         synchronized (mFocusEventDebugViewLock) {
diff --git a/services/core/java/com/android/server/input/InputSettingsObserver.java b/services/core/java/com/android/server/input/InputSettingsObserver.java
index 651063e..cf7c692 100644
--- a/services/core/java/com/android/server/input/InputSettingsObserver.java
+++ b/services/core/java/com/android/server/input/InputSettingsObserver.java
@@ -47,6 +47,9 @@
     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);
@@ -67,6 +70,8 @@
                         (reason) -> updateTouchpadRightClickZoneEnabled()),
                 Map.entry(Settings.System.getUriFor(Settings.System.SHOW_TOUCHES),
                         (reason) -> updateShowTouches()),
+                Map.entry(Settings.System.getUriFor(Settings.System.POINTER_LOCATION),
+                        (reason) -> updatePointerLocation()),
                 Map.entry(
                         Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_LARGE_POINTER_ICON),
                         (reason) -> updateAccessibilityLargePointer()),
@@ -77,7 +82,11 @@
                                 Settings.Global.MAXIMUM_OBSCURING_OPACITY_FOR_TOUCH),
                         (reason) -> updateMaximumObscuringOpacityForTouch()),
                 Map.entry(Settings.System.getUriFor(Settings.System.SHOW_KEY_PRESSES),
-                        (reason) -> updateShowKeyPresses()));
+                        (reason) -> updateShowKeyPresses()),
+                Map.entry(Settings.Secure.getUriFor(Settings.Secure.KEY_REPEAT_TIMEOUT_MS),
+                        (reason) -> updateKeyRepeatInfo(getLatestLongPressTimeoutValue())),
+                Map.entry(Settings.Secure.getUriFor(Settings.Secure.KEY_REPEAT_DELAY_MS),
+                        (reason) -> updateKeyRepeatInfo(getLatestLongPressTimeoutValue())));
     }
 
     /**
@@ -115,41 +124,45 @@
         return setting != 0;
     }
 
-    private int getPointerSpeedValue(String settingName) {
-        int speed = Settings.System.getIntForUser(mContext.getContentResolver(),
-                settingName, InputSettings.DEFAULT_POINTER_SPEED, UserHandle.USER_CURRENT);
+    private int constrainPointerSpeedValue(int speed) {
         return Math.min(Math.max(speed, InputSettings.MIN_POINTER_SPEED),
                 InputSettings.MAX_POINTER_SPEED);
     }
 
     private void updateMousePointerSpeed() {
-        mNative.setPointerSpeed(getPointerSpeedValue(Settings.System.POINTER_SPEED));
+        int speed = Settings.System.getIntForUser(mContext.getContentResolver(),
+                Settings.System.POINTER_SPEED, InputSettings.DEFAULT_POINTER_SPEED,
+                UserHandle.USER_CURRENT);
+        mNative.setPointerSpeed(constrainPointerSpeedValue(speed));
     }
 
     private void updateTouchpadPointerSpeed() {
         mNative.setTouchpadPointerSpeed(
-                getPointerSpeedValue(Settings.System.TOUCHPAD_POINTER_SPEED));
+                constrainPointerSpeedValue(InputSettings.getTouchpadPointerSpeed(mContext)));
     }
 
     private void updateTouchpadNaturalScrollingEnabled() {
         mNative.setTouchpadNaturalScrollingEnabled(
-                getBoolean(Settings.System.TOUCHPAD_NATURAL_SCROLLING, true));
+                InputSettings.useTouchpadNaturalScrolling(mContext));
     }
 
     private void updateTouchpadTapToClickEnabled() {
-        mNative.setTouchpadTapToClickEnabled(
-                getBoolean(Settings.System.TOUCHPAD_TAP_TO_CLICK, true));
+        mNative.setTouchpadTapToClickEnabled(InputSettings.useTouchpadTapToClick(mContext));
     }
 
     private void updateTouchpadRightClickZoneEnabled() {
-        mNative.setTouchpadRightClickZoneEnabled(
-                getBoolean(Settings.System.TOUCHPAD_RIGHT_CLICK_ZONE, false));
+        mNative.setTouchpadRightClickZoneEnabled(InputSettings.useTouchpadRightClickZone(mContext));
     }
 
     private void updateShowTouches() {
         mNative.setShowTouches(getBoolean(Settings.System.SHOW_TOUCHES, false));
     }
 
+    private void updatePointerLocation() {
+        mService.updatePointerLocationEnabled(
+                getBoolean(Settings.System.POINTER_LOCATION, false));
+    }
+
     private void updateShowKeyPresses() {
         mService.updateFocusEventDebugViewEnabled(
                 getBoolean(Settings.System.SHOW_KEY_PRESSES, false));
@@ -164,27 +177,48 @@
     }
 
     private void updateLongPressTimeout(String reason) {
-        // Some key gesture timeouts are based on the long press timeout, so update key gesture
-        // timeouts when the value changes. See ViewConfiguration#getKeyRepeatTimeout().
-        mNative.notifyKeyGestureTimeoutsChanged();
+        final int longPressTimeoutValue = getLatestLongPressTimeoutValue();
 
-        // Update the deep press status.
-        // Not using ViewConfiguration.getLongPressTimeout here because it may return a stale value.
-        final int timeout = Settings.Secure.getIntForUser(mContext.getContentResolver(),
-                Settings.Secure.LONG_PRESS_TIMEOUT, ViewConfiguration.DEFAULT_LONG_PRESS_TIMEOUT,
-                UserHandle.USER_CURRENT);
+        // 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);
+
         final boolean featureEnabledFlag =
                 DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_INPUT_NATIVE_BOOT,
                         DEEP_PRESS_ENABLED, true /* default */);
         final boolean enabled =
-                featureEnabledFlag && timeout <= ViewConfiguration.DEFAULT_LONG_PRESS_TIMEOUT;
-        Log.i(TAG,
-                (enabled ? "Enabling" : "Disabling") + " motion classifier because " + reason
+                featureEnabledFlag
+                        && longPressTimeoutValue <= ViewConfiguration.DEFAULT_LONG_PRESS_TIMEOUT;
+        Log.i(TAG, (enabled ? "Enabling" : "Disabling") + " motion classifier because " + reason
                 + ": feature " + (featureEnabledFlag ? "enabled" : "disabled")
-                + ", long press timeout = " + timeout);
+                + ", long press timeout = " + longPressTimeoutValue);
         mNative.setMotionClassifierEnabled(enabled);
     }
 
+    private void updateKeyRepeatInfo(int fallbackKeyRepeatTimeoutValue) {
+        // Not using ViewConfiguration.getKeyRepeatTimeout here because it may return a stale value.
+        final int timeoutMs = Settings.Secure.getIntForUser(mContext.getContentResolver(),
+                Settings.Secure.KEY_REPEAT_TIMEOUT_MS, fallbackKeyRepeatTimeoutValue,
+                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);
+    }
+
     private void updateMaximumObscuringOpacityForTouch() {
         final float opacity = InputSettings.getMaximumObscuringOpacityForTouch(mContext);
         if (opacity < 0 || opacity > 1) {
@@ -194,4 +228,19 @@
         }
         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/input/InputShellCommand.java b/services/core/java/com/android/server/input/InputShellCommand.java
index 5132591..16b23ca 100644
--- a/services/core/java/com/android/server/input/InputShellCommand.java
+++ b/services/core/java/com/android/server/input/InputShellCommand.java
@@ -74,6 +74,8 @@
     private static final int DEFAULT_EDGE_FLAGS = 0;
     private static final int DEFAULT_BUTTON_STATE = 0;
     private static final int DEFAULT_FLAGS = 0;
+    private static final boolean INJECT_ASYNC = true;
+    private static final boolean INJECT_SYNC = false;
 
     /** Modifier key to meta state */
     private static final Map<Integer, Integer> MODIFIER;
@@ -109,9 +111,11 @@
         SOURCES = unmodifiableMap(map);
     }
 
-    private void injectKeyEvent(KeyEvent event) {
-        InputManagerGlobal.getInstance().injectInputEvent(event,
-                InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH);
+    private void injectKeyEvent(KeyEvent event, boolean async) {
+        int injectMode = async
+                ? InputManager.INJECT_INPUT_EVENT_MODE_ASYNC
+                : InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH;
+        InputManagerGlobal.getInstance().injectInputEvent(event, injectMode);
     }
 
     private int getInputDeviceId(int inputSource) {
@@ -271,7 +275,9 @@
             out.println();
             out.println("The commands and default sources are:");
             out.println("      text <string> (Default: touchscreen)");
-            out.println("      keyevent [--longpress|--doubletap] <key code number or name> ..."
+            out.println("      keyevent [--longpress|--doubletap|--async"
+                    + "|--delay <duration between keycodes in ms>]"
+                    + " <key code number or name> ..."
                     + " (Default: keyboard)");
             out.println("      tap <x> <y> (Default: touchscreen)");
             out.println("      swipe <x1> <y1> <x2> <y2> [duration(ms)]"
@@ -322,32 +328,45 @@
                 e.setSource(source);
             }
             e.setDisplayId(displayId);
-            injectKeyEvent(e);
+            injectKeyEvent(e, INJECT_SYNC);
         }
     }
 
     private void runKeyEvent(int inputSource, int displayId) {
-        String arg = getNextArgRequired();
-        final boolean longpress = "--longpress".equals(arg);
-        if (longpress) {
-            arg = getNextArgRequired();
-        } else {
-            final boolean doubleTap = "--doubletap".equals(arg);
-            if (doubleTap) {
-                arg = getNextArgRequired();
-                final int keycode = KeyEvent.keyCodeFromString(arg);
-                sendKeyDoubleTap(inputSource, keycode, displayId);
-                return;
-            }
-        }
+        boolean longPress = false;
+        boolean async = false;
+        boolean doubleTap = false;
+        long delayMs = 0;
 
+        String arg = getNextArgRequired();
         do {
-            final int keycode = KeyEvent.keyCodeFromString(arg);
-            sendKeyEvent(inputSource, keycode, longpress, displayId);
+            if (!arg.startsWith("--")) break;
+            longPress = (longPress || arg.equals("--longpress"));
+            async = (async || arg.equals("--async"));
+            doubleTap = (doubleTap || arg.equals("--doubletap"));
+            if (arg.equals("--delay")) {
+                delayMs = Long.parseLong(getNextArgRequired());
+            }
+        } while ((arg = getNextArg()) != null);
+
+        boolean firstInput = true;
+        do {
+            if (!firstInput && delayMs > 0) {
+                sleep(delayMs);
+            }
+            firstInput = false;
+
+            final int keyCode = KeyEvent.keyCodeFromString(arg);
+            sendKeyEvent(inputSource, keyCode, longPress, displayId, async);
+            if (doubleTap) {
+                sleep(ViewConfiguration.getDoubleTapMinTime());
+                sendKeyEvent(inputSource, keyCode, longPress, displayId, async);
+            }
         } while ((arg = getNextArg()) != null);
     }
 
-    private void sendKeyEvent(int inputSource, int keyCode, boolean longpress, int displayId) {
+    private void sendKeyEvent(
+            int inputSource, int keyCode, boolean longPress, int displayId, boolean async) {
         final long now = SystemClock.uptimeMillis();
 
         KeyEvent event = new KeyEvent(now, now, KeyEvent.ACTION_DOWN, keyCode, 0 /* repeatCount */,
@@ -355,21 +374,16 @@
                 inputSource);
         event.setDisplayId(displayId);
 
-        injectKeyEvent(event);
-        if (longpress) {
+        injectKeyEvent(event, async);
+        if (longPress) {
             sleep(ViewConfiguration.getLongPressTimeout());
             // Some long press behavior would check the event time, we set a new event time here.
             final long nextEventTime = now + ViewConfiguration.getLongPressTimeout();
-            injectKeyEvent(KeyEvent.changeTimeRepeat(event, nextEventTime, 1 /* repeatCount */,
-                    KeyEvent.FLAG_LONG_PRESS));
+            KeyEvent longPressEvent = KeyEvent.changeTimeRepeat(
+                    event, nextEventTime, 1 /* repeatCount */, KeyEvent.FLAG_LONG_PRESS);
+            injectKeyEvent(longPressEvent, async);
         }
-        injectKeyEvent(KeyEvent.changeAction(event, KeyEvent.ACTION_UP));
-    }
-
-    private void sendKeyDoubleTap(int inputSource, int keyCode, int displayId) {
-        sendKeyEvent(inputSource, keyCode, false, displayId);
-        sleep(ViewConfiguration.getDoubleTapMinTime());
-        sendKeyEvent(inputSource, keyCode, false, displayId);
+        injectKeyEvent(KeyEvent.changeAction(event, KeyEvent.ACTION_UP), async);
     }
 
     private void runTap(int inputSource, int displayId) {
@@ -530,11 +544,6 @@
         sendKeyCombination(inputSource, keyCodes, displayId, duration);
     }
 
-    private void injectKeyEventAsync(KeyEvent event) {
-        InputManagerGlobal.getInstance().injectInputEvent(event,
-                InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
-    }
-
     private void sendKeyCombination(int inputSource, IntArray keyCodes, int displayId,
             long duration) {
         final long now = SystemClock.uptimeMillis();
@@ -555,7 +564,7 @@
         for (KeyEvent event: events) {
             // Use async inject so interceptKeyBeforeQueueing or interceptKeyBeforeDispatching could
             // handle keys.
-            injectKeyEventAsync(event);
+            injectKeyEvent(event, INJECT_ASYNC);
         }
 
         sleep(duration);
@@ -565,7 +574,7 @@
             final KeyEvent upEvent = new KeyEvent(now, now, KeyEvent.ACTION_UP, keyCode,
                     0, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /*scancode*/, 0 /*flags*/,
                     inputSource);
-            injectKeyEventAsync(upEvent);
+            injectKeyEvent(upEvent, INJECT_ASYNC);
             metaState &= ~MODIFIER.getOrDefault(keyCode, 0);
         }
     }
diff --git a/services/core/java/com/android/server/input/KeyboardBacklightController.java b/services/core/java/com/android/server/input/KeyboardBacklightController.java
index 48c346a..c3205af 100644
--- a/services/core/java/com/android/server/input/KeyboardBacklightController.java
+++ b/services/core/java/com/android/server/input/KeyboardBacklightController.java
@@ -16,7 +16,9 @@
 
 package com.android.server.input;
 
+import android.animation.ValueAnimator;
 import android.annotation.BinderThread;
+import android.annotation.Nullable;
 import android.content.Context;
 import android.graphics.Color;
 import android.hardware.input.IKeyboardBacklightListener;
@@ -45,6 +47,7 @@
 import java.util.Arrays;
 import java.util.Objects;
 import java.util.OptionalInt;
+import java.util.TreeSet;
 
 /**
  * A thread-safe component of {@link InputManagerService} responsible for managing the keyboard
@@ -69,7 +72,11 @@
     private static final int MSG_NOTIFY_USER_INACTIVITY = 5;
     private static final int MSG_INTERACTIVE_STATE_CHANGED = 6;
     private static final int MAX_BRIGHTNESS = 255;
-    private static final int NUM_BRIGHTNESS_CHANGE_STEPS = 10;
+    private static final int DEFAULT_NUM_BRIGHTNESS_CHANGE_STEPS = 10;
+    @VisibleForTesting
+    static final int MAX_BRIGHTNESS_CHANGE_STEPS = 10;
+    private static final long TRANSITION_ANIMATION_DURATION_MILLIS =
+            Duration.ofSeconds(1).toMillis();
 
     private static final String UEVENT_KEYBOARD_BACKLIGHT_TAG = "kbd_backlight";
 
@@ -77,7 +84,8 @@
     static final long USER_INACTIVITY_THRESHOLD_MILLIS = Duration.ofSeconds(30).toMillis();
 
     @VisibleForTesting
-    static final int[] BRIGHTNESS_VALUE_FOR_LEVEL = new int[NUM_BRIGHTNESS_CHANGE_STEPS + 1];
+    static final int[] DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL =
+            new int[DEFAULT_NUM_BRIGHTNESS_CHANGE_STEPS + 1];
 
     private final Context mContext;
     private final NativeInputManagerService mNative;
@@ -85,6 +93,8 @@
     @GuardedBy("mDataStore")
     private final PersistentDataStore mDataStore;
     private final Handler mHandler;
+    private final AnimatorFactory mAnimatorFactory;
+    private final UEventManager mUEventManager;
     // Always access on handler thread or need to lock this for synchronization.
     private final SparseArray<KeyboardBacklightState> mKeyboardBacklights = new SparseArray<>(1);
     // Maintains state if all backlights should be on or turned off
@@ -97,22 +107,38 @@
     private final SparseArray<KeyboardBacklightListenerRecord> mKeyboardBacklightListenerRecords =
             new SparseArray<>();
 
+    private final AmbientKeyboardBacklightController mAmbientController;
+    @Nullable
+    private AmbientKeyboardBacklightController.AmbientKeyboardBacklightListener mAmbientListener;
+
+    private int mAmbientBacklightValue = 0;
+
     static {
         // Fixed brightness levels to avoid issues when converting back and forth from the
         // device brightness range to [0-255]
-        // Levels are: 0, 25, 51, ..., 255
-        for (int i = 0; i <= NUM_BRIGHTNESS_CHANGE_STEPS; i++) {
-            BRIGHTNESS_VALUE_FOR_LEVEL[i] = (int) Math.floor(
-                    ((float) i * MAX_BRIGHTNESS) / NUM_BRIGHTNESS_CHANGE_STEPS);
+        // Levels are: 0, 51, ..., 255
+        for (int i = 0; i <= DEFAULT_NUM_BRIGHTNESS_CHANGE_STEPS; i++) {
+            DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL[i] = (int) Math.floor(
+                    ((float) i * MAX_BRIGHTNESS) / DEFAULT_NUM_BRIGHTNESS_CHANGE_STEPS);
         }
     }
 
     KeyboardBacklightController(Context context, NativeInputManagerService nativeService,
-            PersistentDataStore dataStore, Looper looper) {
+            PersistentDataStore dataStore, Looper looper, UEventManager uEventManager) {
+        this(context, nativeService, dataStore, looper, ValueAnimator::ofInt, uEventManager);
+    }
+
+    @VisibleForTesting
+    KeyboardBacklightController(Context context, NativeInputManagerService nativeService,
+            PersistentDataStore dataStore, Looper looper, AnimatorFactory animatorFactory,
+            UEventManager uEventManager) {
         mContext = context;
         mNative = nativeService;
         mDataStore = dataStore;
         mHandler = new Handler(looper, this::handleMessage);
+        mAnimatorFactory = animatorFactory;
+        mAmbientController = new AmbientKeyboardBacklightController(context, looper);
+        mUEventManager = uEventManager;
     }
 
     @Override
@@ -129,13 +155,17 @@
         // We want to observe creation of such LED nodes since they might be created after device
         // FD created and InputDevice creation logic doesn't initialize LED nodes which leads to
         // backlight not working.
-        UEventObserver observer = new UEventObserver() {
+        mUEventManager.addListener(new UEventManager.UEventListener() {
             @Override
-            public void onUEvent(UEvent event) {
+            public void onUEvent(UEventObserver.UEvent event) {
                 onKeyboardBacklightUEvent(event);
             }
-        };
-        observer.startObserving(UEVENT_KEYBOARD_BACKLIGHT_TAG);
+        }, UEVENT_KEYBOARD_BACKLIGHT_TAG);
+
+        if (InputFeatureFlagProvider.isAmbientKeyboardBacklightControlEnabled()) {
+            // Start ambient backlight controller
+            mAmbientController.systemRunning();
+        }
     }
 
     @Override
@@ -168,43 +198,89 @@
         if (inputDevice == null || state == null) {
             return;
         }
-        Light keyboardBacklight = state.mLight;
         // Follow preset levels of brightness defined in BRIGHTNESS_LEVELS
-        final int currBrightnessLevel = state.mBrightnessLevel;
+        final int currBrightnessLevel;
+        if (state.mUseAmbientController) {
+            int index = Arrays.binarySearch(state.mBrightnessValueForLevel, mAmbientBacklightValue);
+            // Set current level to the lower bound of the ambient value in the brightness array.
+            if (index < 0) {
+                int lowerBound = Math.max(0, -(index + 1) - 1);
+                currBrightnessLevel =
+                        direction == Direction.DIRECTION_UP ? lowerBound : lowerBound + 1;
+            } else {
+                currBrightnessLevel = index;
+            }
+        } else {
+            currBrightnessLevel = state.mBrightnessLevel;
+        }
         final int newBrightnessLevel;
         if (direction == Direction.DIRECTION_UP) {
-            newBrightnessLevel = Math.min(currBrightnessLevel + 1, NUM_BRIGHTNESS_CHANGE_STEPS);
+            newBrightnessLevel = Math.min(currBrightnessLevel + 1,
+                    state.getNumBrightnessChangeSteps());
         } else {
             newBrightnessLevel = Math.max(currBrightnessLevel - 1, 0);
         }
-        updateBacklightState(deviceId, keyboardBacklight, newBrightnessLevel,
-                true /* isTriggeredByKeyPress */);
 
+        state.setBrightnessLevel(newBrightnessLevel);
+
+        // Might need to stop listening to ALS since user has manually selected backlight
+        // level through keyboard up/down button
+        updateAmbientLightListener();
+
+        maybeBackupBacklightBrightness(inputDevice, state.mLight,
+                state.mBrightnessValueForLevel[newBrightnessLevel]);
+
+        if (DEBUG) {
+            Slog.d(TAG,
+                    "Changing state from " + state.mBrightnessLevel + " to " + newBrightnessLevel);
+        }
+
+        synchronized (mKeyboardBacklightListenerRecords) {
+            for (int i = 0; i < mKeyboardBacklightListenerRecords.size(); i++) {
+                IKeyboardBacklightState callbackState = new IKeyboardBacklightState();
+                callbackState.brightnessLevel = newBrightnessLevel;
+                callbackState.maxBrightnessLevel = state.getNumBrightnessChangeSteps();
+                mKeyboardBacklightListenerRecords.valueAt(i).notifyKeyboardBacklightChanged(
+                        deviceId, callbackState, true);
+            }
+        }
+    }
+
+    private void maybeBackupBacklightBrightness(InputDevice inputDevice, Light keyboardBacklight,
+            int brightnessValue) {
+        // Don't back up or restore when ALS based keyboard backlight is enabled
+        if (InputFeatureFlagProvider.isAmbientKeyboardBacklightControlEnabled()) {
+            return;
+        }
         synchronized (mDataStore) {
             try {
                 mDataStore.setKeyboardBacklightBrightness(inputDevice.getDescriptor(),
                         keyboardBacklight.getId(),
-                        BRIGHTNESS_VALUE_FOR_LEVEL[newBrightnessLevel]);
+                        brightnessValue);
             } finally {
                 mDataStore.saveIfNeeded();
             }
         }
     }
 
-    private void restoreBacklightBrightness(InputDevice inputDevice, Light keyboardBacklight) {
+    private void maybeRestoreBacklightBrightness(InputDevice inputDevice, Light keyboardBacklight) {
+        // Don't back up or restore when ALS based keyboard backlight is enabled
+        if (InputFeatureFlagProvider.isAmbientKeyboardBacklightControlEnabled()) {
+            return;
+        }
+        KeyboardBacklightState state = mKeyboardBacklights.get(inputDevice.getId());
         OptionalInt brightness;
         synchronized (mDataStore) {
             brightness = mDataStore.getKeyboardBacklightBrightness(
                     inputDevice.getDescriptor(), keyboardBacklight.getId());
         }
-        if (brightness.isPresent()) {
+        if (state != null && brightness.isPresent()) {
             int brightnessValue = Math.max(0, Math.min(MAX_BRIGHTNESS, brightness.getAsInt()));
-            int index = Arrays.binarySearch(BRIGHTNESS_VALUE_FOR_LEVEL, brightnessValue);
-            if (index < 0) {
-                index = Math.min(NUM_BRIGHTNESS_CHANGE_STEPS, -(index + 1));
+            int newLevel = Arrays.binarySearch(state.mBrightnessValueForLevel, brightnessValue);
+            if (newLevel < 0) {
+                newLevel = Math.min(state.getNumBrightnessChangeSteps(), -(newLevel + 1));
             }
-            updateBacklightState(inputDevice.getId(), keyboardBacklight, index,
-                    false /* isTriggeredByKeyPress */);
+            state.setBrightnessLevel(newLevel);
             if (DEBUG) {
                 Slog.d(TAG, "Restoring brightness level " + brightness.getAsInt());
             }
@@ -217,14 +293,10 @@
         if (!mIsInteractive) {
             return;
         }
-        if (!mIsBacklightOn) {
-            mIsBacklightOn = true;
-            for (int i = 0; i < mKeyboardBacklights.size(); i++) {
-                int deviceId = mKeyboardBacklights.keyAt(i);
-                KeyboardBacklightState state = mKeyboardBacklights.valueAt(i);
-                updateBacklightState(deviceId, state.mLight, state.mBrightnessLevel,
-                        false /* isTriggeredByKeyPress */);
-            }
+        mIsBacklightOn = true;
+        for (int i = 0; i < mKeyboardBacklights.size(); i++) {
+            KeyboardBacklightState state = mKeyboardBacklights.valueAt(i);
+            state.onBacklightStateChanged();
         }
         mHandler.removeMessages(MSG_NOTIFY_USER_INACTIVITY);
         mHandler.sendEmptyMessageAtTime(MSG_NOTIFY_USER_INACTIVITY,
@@ -232,14 +304,10 @@
     }
 
     private void handleUserInactivity() {
-        if (mIsBacklightOn) {
-            mIsBacklightOn = false;
-            for (int i = 0; i < mKeyboardBacklights.size(); i++) {
-                int deviceId = mKeyboardBacklights.keyAt(i);
-                KeyboardBacklightState state = mKeyboardBacklights.valueAt(i);
-                updateBacklightState(deviceId, state.mLight, state.mBrightnessLevel,
-                        false /* isTriggeredByKeyPress */);
-            }
+        mIsBacklightOn = false;
+        for (int i = 0; i < mKeyboardBacklights.size(); i++) {
+            KeyboardBacklightState state = mKeyboardBacklights.valueAt(i);
+            state.onBacklightStateChanged();
         }
     }
 
@@ -253,6 +321,16 @@
         } else {
             handleUserInactivity();
         }
+        updateAmbientLightListener();
+    }
+
+    @VisibleForTesting
+    public void handleAmbientLightValueChanged(int brightnessValue) {
+        mAmbientBacklightValue = brightnessValue;
+        for (int i = 0; i < mKeyboardBacklights.size(); i++) {
+            KeyboardBacklightState state = mKeyboardBacklights.valueAt(i);
+            state.onAmbientBacklightValueChanged();
+        }
     }
 
     private boolean handleMessage(Message msg) {
@@ -285,12 +363,14 @@
     @Override
     public void onInputDeviceAdded(int deviceId) {
         onInputDeviceChanged(deviceId);
+        updateAmbientLightListener();
     }
 
     @VisibleForTesting
     @Override
     public void onInputDeviceRemoved(int deviceId) {
         mKeyboardBacklights.remove(deviceId);
+        updateAmbientLightListener();
     }
 
     @VisibleForTesting
@@ -310,8 +390,8 @@
             return;
         }
         // The keyboard backlight was added or changed.
-        mKeyboardBacklights.put(deviceId, new KeyboardBacklightState(keyboardBacklight));
-        restoreBacklightBrightness(inputDevice, keyboardBacklight);
+        mKeyboardBacklights.put(deviceId, new KeyboardBacklightState(deviceId, keyboardBacklight));
+        maybeRestoreBacklightBrightness(inputDevice, keyboardBacklight);
     }
 
     private InputDevice getInputDevice(int deviceId) {
@@ -372,33 +452,6 @@
         }
     }
 
-    private void updateBacklightState(int deviceId, Light light, int brightnessLevel,
-            boolean isTriggeredByKeyPress) {
-        KeyboardBacklightState state = mKeyboardBacklights.get(deviceId);
-        if (state == null) {
-            return;
-        }
-
-        mNative.setLightColor(deviceId, light.getId(),
-                mIsBacklightOn ? Color.argb(BRIGHTNESS_VALUE_FOR_LEVEL[brightnessLevel], 0, 0, 0)
-                        : 0);
-        if (DEBUG) {
-            Slog.d(TAG, "Changing state from " + state.mBrightnessLevel + " to " + brightnessLevel
-                    + "(isBacklightOn = " + mIsBacklightOn + ")");
-        }
-        state.mBrightnessLevel = brightnessLevel;
-
-        synchronized (mKeyboardBacklightListenerRecords) {
-            for (int i = 0; i < mKeyboardBacklightListenerRecords.size(); i++) {
-                IKeyboardBacklightState callbackState = new IKeyboardBacklightState();
-                callbackState.brightnessLevel = brightnessLevel;
-                callbackState.maxBrightnessLevel = NUM_BRIGHTNESS_CHANGE_STEPS;
-                mKeyboardBacklightListenerRecords.valueAt(i).notifyKeyboardBacklightChanged(
-                        deviceId, callbackState, isTriggeredByKeyPress);
-            }
-        }
-    }
-
     private void onKeyboardBacklightListenerDied(int pid) {
         synchronized (mKeyboardBacklightListenerRecords) {
             mKeyboardBacklightListenerRecords.remove(pid);
@@ -416,6 +469,25 @@
         }
     }
 
+    private void updateAmbientLightListener() {
+        if (!InputFeatureFlagProvider.isAmbientKeyboardBacklightControlEnabled()) {
+            return;
+        }
+        boolean needToListenAmbientLightSensor = false;
+        for (int i = 0; i < mKeyboardBacklights.size(); i++) {
+            needToListenAmbientLightSensor |= mKeyboardBacklights.valueAt(i).mUseAmbientController;
+        }
+        needToListenAmbientLightSensor &= mIsInteractive;
+        if (needToListenAmbientLightSensor && mAmbientListener == null) {
+            mAmbientListener = this::handleAmbientLightValueChanged;
+            mAmbientController.registerAmbientBacklightListener(mAmbientListener);
+        }
+        if (!needToListenAmbientLightSensor && mAmbientListener != null) {
+            mAmbientController.unregisterAmbientBacklightListener(mAmbientListener);
+            mAmbientListener = null;
+        }
+    }
+
     private static boolean isValidBacklightNodePath(String devPath) {
         if (TextUtils.isEmpty(devPath)) {
             return false;
@@ -436,10 +508,7 @@
     @Override
     public void dump(PrintWriter pw) {
         IndentingPrintWriter ipw = new IndentingPrintWriter(pw);
-        ipw.println(
-                TAG + ": " + mKeyboardBacklights.size() + " keyboard backlights, isBacklightOn = "
-                        + mIsBacklightOn);
-
+        ipw.println(TAG + ": " + mKeyboardBacklights.size() + " keyboard backlights");
         ipw.increaseIndent();
         for (int i = 0; i < mKeyboardBacklights.size(); i++) {
             KeyboardBacklightState state = mKeyboardBacklights.valueAt(i);
@@ -478,12 +547,99 @@
         }
     }
 
-    private static class KeyboardBacklightState {
+    private class KeyboardBacklightState {
+        private final int mDeviceId;
         private final Light mLight;
         private int mBrightnessLevel;
+        private ValueAnimator mAnimator;
+        private final int[] mBrightnessValueForLevel;
+        private boolean mUseAmbientController =
+                InputFeatureFlagProvider.isAmbientKeyboardBacklightControlEnabled();
 
-        KeyboardBacklightState(Light light) {
+        KeyboardBacklightState(int deviceId, Light light) {
+            mDeviceId = deviceId;
             mLight = light;
+            mBrightnessValueForLevel = setupBrightnessLevels();
+        }
+
+        private int[] setupBrightnessLevels() {
+            if (!InputFeatureFlagProvider.isKeyboardBacklightCustomLevelsEnabled()) {
+                return DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL;
+            }
+            int[] customLevels = mLight.getPreferredBrightnessLevels();
+            if (customLevels == null || customLevels.length == 0) {
+                return DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL;
+            }
+            TreeSet<Integer> brightnessLevels = new TreeSet<>();
+            brightnessLevels.add(0);
+            for (int level : customLevels) {
+                if (level > 0 && level < MAX_BRIGHTNESS) {
+                    brightnessLevels.add(level);
+                }
+            }
+            brightnessLevels.add(MAX_BRIGHTNESS);
+            int brightnessChangeSteps = brightnessLevels.size() - 1;
+            if (brightnessChangeSteps > MAX_BRIGHTNESS_CHANGE_STEPS) {
+                return DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL;
+            }
+            int[] result = new int[brightnessLevels.size()];
+            int index = 0;
+            for (int val : brightnessLevels) {
+                result[index++] = val;
+            }
+            return result;
+        }
+
+        private int getNumBrightnessChangeSteps() {
+            return mBrightnessValueForLevel.length - 1;
+        }
+
+        private void onBacklightStateChanged() {
+            int toValue = mUseAmbientController ? mAmbientBacklightValue
+                    : mBrightnessValueForLevel[mBrightnessLevel];
+            setBacklightValue(mIsBacklightOn ? toValue : 0);
+        }
+        private void setBrightnessLevel(int brightnessLevel) {
+            // Once we manually set level, disregard ambient light controller
+            mUseAmbientController = false;
+            if (mIsBacklightOn) {
+                setBacklightValue(mBrightnessValueForLevel[brightnessLevel]);
+            }
+            mBrightnessLevel = brightnessLevel;
+        }
+
+        private void onAmbientBacklightValueChanged() {
+            if (mIsBacklightOn && mUseAmbientController) {
+                setBacklightValue(mAmbientBacklightValue);
+            }
+        }
+
+        private void cancelAnimation() {
+            if (mAnimator != null && mAnimator.isRunning()) {
+                mAnimator.cancel();
+            }
+        }
+
+        private void setBacklightValue(int toValue) {
+            int fromValue = Color.alpha(mNative.getLightColor(mDeviceId, mLight.getId()));
+            if (fromValue == toValue) {
+                return;
+            }
+            if (InputFeatureFlagProvider.isKeyboardBacklightAnimationEnabled()) {
+                startAnimation(fromValue, toValue);
+            } else {
+                mNative.setLightColor(mDeviceId, mLight.getId(), Color.argb(toValue, 0, 0, 0));
+            }
+        }
+
+        private void startAnimation(int fromValue, int toValue) {
+            // Cancel any ongoing animation before starting a new one
+            cancelAnimation();
+            mAnimator = mAnimatorFactory.makeIntAnimator(fromValue, toValue);
+            mAnimator.addUpdateListener(
+                    (animation) -> mNative.setLightColor(mDeviceId, mLight.getId(),
+                            Color.argb((int) animation.getAnimatedValue(), 0, 0, 0)));
+            mAnimator.setDuration(TRANSITION_ANIMATION_DURATION_MILLIS).start();
         }
 
         @Override
@@ -493,4 +649,9 @@
                     + "}";
         }
     }
+
+    @VisibleForTesting
+    interface AnimatorFactory {
+        ValueAnimator makeIntAnimator(int from, int to);
+    }
 }
diff --git a/services/core/java/com/android/server/input/KeyboardLayoutManager.java b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
index 6ec4022..7a8de34 100644
--- a/services/core/java/com/android/server/input/KeyboardLayoutManager.java
+++ b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
@@ -16,6 +16,11 @@
 
 package com.android.server.input;
 
+import static com.android.server.input.KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEFAULT;
+import static com.android.server.input.KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEVICE;
+import static com.android.server.input.KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_USER;
+import static com.android.server.input.KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD;
+
 import android.annotation.AnyThread;
 import android.annotation.MainThread;
 import android.annotation.NonNull;
@@ -24,6 +29,7 @@
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
+import android.app.settings.SettingsEnums;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
@@ -67,6 +73,8 @@
 import com.android.internal.messages.nano.SystemMessageProto;
 import com.android.internal.notification.SystemNotificationChannels;
 import com.android.internal.util.XmlUtils;
+import com.android.server.input.KeyboardMetricsCollector.KeyboardConfigurationEvent;
+import com.android.server.input.KeyboardMetricsCollector.LayoutSelectionCriteria;
 import com.android.server.inputmethod.InputMethodManagerInternal;
 
 import libcore.io.Streams;
@@ -118,7 +126,7 @@
     // This cache stores "best-matched" layouts so that we don't need to run the matching
     // algorithm repeatedly.
     @GuardedBy("mKeyboardLayoutCache")
-    private final Map<String, String> mKeyboardLayoutCache = new ArrayMap<>();
+    private final Map<String, KeyboardLayoutInfo> mKeyboardLayoutCache = new ArrayMap<>();
     private final Object mImeInfoLock = new Object();
     @Nullable
     @GuardedBy("mImeInfoLock")
@@ -194,7 +202,8 @@
                         setCurrentKeyboardLayoutForInputDevice(inputDevice.getIdentifier(), layout);
                     }
                 }
-                config.setCurrentLayout(layout);
+                config.setCurrentLayout(
+                        new KeyboardLayoutInfo(layout, LAYOUT_SELECTION_CRITERIA_USER));
                 if (layout == null) {
                     // In old settings show notification always until user manually selects a
                     // layout in the settings.
@@ -205,18 +214,19 @@
             final InputDeviceIdentifier identifier = inputDevice.getIdentifier();
             final String key = getLayoutDescriptor(identifier);
             Set<String> selectedLayouts = new HashSet<>();
-            for (ImeInfo imeInfo : getImeInfoListForLayoutMapping()) {
+            List<ImeInfo> imeInfoList = getImeInfoListForLayoutMapping();
+            List<KeyboardLayoutInfo> layoutInfoList = new ArrayList<>();
+            boolean hasMissingLayout = false;
+            for (ImeInfo imeInfo : imeInfoList) {
                 // Check if the layout has been previously configured
-                String layout = getKeyboardLayoutForInputDeviceInternal(identifier,
-                        new ImeInfo(imeInfo.mUserId, imeInfo.mImeSubtypeHandle,
-                                imeInfo.mImeSubtype));
-                if (layout == null) {
-                    // If even one layout not configured properly, we need to ask user to configure
-                    // the keyboard properly from the Settings.
-                    selectedLayouts.clear();
-                    break;
+                KeyboardLayoutInfo layoutInfo = getKeyboardLayoutForInputDeviceInternal(identifier,
+                        imeInfo);
+                boolean noLayoutFound = layoutInfo == null || layoutInfo.mDescriptor == null;
+                if (!noLayoutFound) {
+                    selectedLayouts.add(layoutInfo.mDescriptor);
                 }
-                selectedLayouts.add(layout);
+                layoutInfoList.add(layoutInfo);
+                hasMissingLayout |= noLayoutFound;
             }
 
             if (DEBUG) {
@@ -225,24 +235,38 @@
                                 + selectedLayouts);
             }
 
+            // If even one layout not configured properly, we need to ask user to configure
+            // the keyboard properly from the Settings.
+            if (hasMissingLayout) {
+                selectedLayouts.clear();
+            }
+
             config.setConfiguredLayouts(selectedLayouts);
 
             // Update current layout: If there is a change then need to reload.
             synchronized (mImeInfoLock) {
-                String layout = getKeyboardLayoutForInputDeviceInternal(
+                KeyboardLayoutInfo layoutInfo = getKeyboardLayoutForInputDeviceInternal(
                         inputDevice.getIdentifier(), mCurrentImeInfo);
-                if (!Objects.equals(layout, config.getCurrentLayout())) {
-                    config.setCurrentLayout(layout);
+                if (!Objects.equals(layoutInfo, config.getCurrentLayout())) {
+                    config.setCurrentLayout(layoutInfo);
                     mHandler.sendEmptyMessage(MSG_RELOAD_KEYBOARD_LAYOUTS);
                 }
             }
 
             synchronized (mDataStore) {
                 try {
+                    boolean isFirstConfiguration = !mDataStore.hasInputDeviceEntry(key);
                     if (mDataStore.setSelectedKeyboardLayouts(key, selectedLayouts)) {
                         // Need to show the notification only if layout selection changed
                         // from the previous configuration
                         needToShowNotification = true;
+
+                        // Logging keyboard configuration data to statsd only if the
+                        // configuration changed from the previous configuration. Currently
+                        // only logging for New Settings UI where we are using IME to decide
+                        // the layout information.
+                        logKeyboardConfigurationEvent(inputDevice, imeInfoList, layoutInfoList,
+                                isFirstConfiguration);
                     }
                 } finally {
                     mDataStore.saveIfNeeded();
@@ -401,7 +425,7 @@
 
     @AnyThread
     @Nullable
-    public KeyboardLayout getKeyboardLayout(String keyboardLayoutDescriptor) {
+    public KeyboardLayout getKeyboardLayout(@NonNull String keyboardLayoutDescriptor) {
         Objects.requireNonNull(keyboardLayoutDescriptor,
                 "keyboardLayoutDescriptor must not be null");
 
@@ -749,8 +773,9 @@
         String keyboardLayoutDescriptor;
         if (useNewSettingsUi()) {
             synchronized (mImeInfoLock) {
-                keyboardLayoutDescriptor = getKeyboardLayoutForInputDeviceInternal(identifier,
+                KeyboardLayoutInfo layoutInfo = getKeyboardLayoutForInputDeviceInternal(identifier,
                         mCurrentImeInfo);
+                keyboardLayoutDescriptor = layoutInfo == null ? null : layoutInfo.mDescriptor;
             }
         } else {
             keyboardLayoutDescriptor = getCurrentKeyboardLayoutForInputDevice(identifier);
@@ -787,13 +812,13 @@
             return null;
         }
         InputMethodSubtypeHandle subtypeHandle = InputMethodSubtypeHandle.of(imeInfo, imeSubtype);
-        String layout = getKeyboardLayoutForInputDeviceInternal(identifier,
+        KeyboardLayoutInfo layoutInfo = getKeyboardLayoutForInputDeviceInternal(identifier,
                 new ImeInfo(userId, subtypeHandle, imeSubtype));
         if (DEBUG) {
             Slog.d(TAG, "getKeyboardLayoutForInputDevice() " + identifier.toString() + ", userId : "
-                    + userId + ", subtypeHandle = " + subtypeHandle + " -> " + layout);
+                    + userId + ", subtypeHandle = " + subtypeHandle + " -> " + layoutInfo);
         }
-        return layout;
+        return layoutInfo != null ? layoutInfo.mDescriptor : null;
     }
 
     @AnyThread
@@ -924,11 +949,11 @@
             for (int i = 0; i < mConfiguredKeyboards.size(); i++) {
                 InputDevice inputDevice = Objects.requireNonNull(
                         getInputDevice(mConfiguredKeyboards.keyAt(i)));
-                String layout = getKeyboardLayoutForInputDeviceInternal(inputDevice.getIdentifier(),
-                        mCurrentImeInfo);
+                KeyboardLayoutInfo layoutInfo = getKeyboardLayoutForInputDeviceInternal(
+                        inputDevice.getIdentifier(), mCurrentImeInfo);
                 KeyboardConfiguration config = mConfiguredKeyboards.valueAt(i);
-                if (!Objects.equals(layout, config.getCurrentLayout())) {
-                    config.setCurrentLayout(layout);
+                if (!Objects.equals(layoutInfo, config.getCurrentLayout())) {
+                    config.setCurrentLayout(layoutInfo);
                     mHandler.sendEmptyMessage(MSG_RELOAD_KEYBOARD_LAYOUTS);
                     return;
                 }
@@ -937,39 +962,40 @@
     }
 
     @Nullable
-    private String getKeyboardLayoutForInputDeviceInternal(InputDeviceIdentifier identifier,
-            @Nullable ImeInfo imeInfo) {
+    private KeyboardLayoutInfo getKeyboardLayoutForInputDeviceInternal(
+            InputDeviceIdentifier identifier, @Nullable ImeInfo imeInfo) {
         InputDevice inputDevice = getInputDevice(identifier);
         if (inputDevice == null || inputDevice.isVirtual() || !inputDevice.isFullKeyboard()) {
             return null;
         }
         String key = createLayoutKey(identifier, imeInfo);
-        String layout;
         synchronized (mDataStore) {
-            layout = mDataStore.getKeyboardLayout(getLayoutDescriptor(identifier), key);
-        }
-        if (layout == null) {
-            synchronized (mKeyboardLayoutCache) {
-                // Check Auto-selected layout cache to see if layout had been previously selected
-                if (mKeyboardLayoutCache.containsKey(key)) {
-                    layout = mKeyboardLayoutCache.get(key);
-                } else {
-                    // NOTE: This list is already filtered based on IME Script code
-                    KeyboardLayout[] layoutList = getKeyboardLayoutListForInputDeviceInternal(
-                            identifier, imeInfo);
-                    // Call auto-matching algorithm to find the best matching layout
-                    layout = getDefaultKeyboardLayoutBasedOnImeInfo(inputDevice, imeInfo,
-                            layoutList);
-                    mKeyboardLayoutCache.put(key, layout);
-                }
+            String layout = mDataStore.getKeyboardLayout(getLayoutDescriptor(identifier), key);
+            if (layout != null) {
+                return new KeyboardLayoutInfo(layout, LAYOUT_SELECTION_CRITERIA_USER);
             }
         }
-        return layout;
+
+        synchronized (mKeyboardLayoutCache) {
+            // Check Auto-selected layout cache to see if layout had been previously selected
+            if (mKeyboardLayoutCache.containsKey(key)) {
+                return mKeyboardLayoutCache.get(key);
+            } else {
+                // NOTE: This list is already filtered based on IME Script code
+                KeyboardLayout[] layoutList = getKeyboardLayoutListForInputDeviceInternal(
+                        identifier, imeInfo);
+                // Call auto-matching algorithm to find the best matching layout
+                KeyboardLayoutInfo layoutInfo =
+                        getDefaultKeyboardLayoutBasedOnImeInfo(inputDevice, imeInfo, layoutList);
+                mKeyboardLayoutCache.put(key, layoutInfo);
+                return layoutInfo;
+            }
+        }
     }
 
     @Nullable
-    private static String getDefaultKeyboardLayoutBasedOnImeInfo(InputDevice inputDevice,
-            @Nullable ImeInfo imeInfo, KeyboardLayout[] layoutList) {
+    private static KeyboardLayoutInfo getDefaultKeyboardLayoutBasedOnImeInfo(
+            InputDevice inputDevice, @Nullable ImeInfo imeInfo, KeyboardLayout[] layoutList) {
         Arrays.sort(layoutList);
 
         // Check <VendorID, ProductID> matching for explicitly declared custom KCM files.
@@ -982,7 +1008,8 @@
                                     + "vendor and product Ids. " + inputDevice.getIdentifier()
                                     + " : " + layout.getDescriptor());
                 }
-                return layout.getDescriptor();
+                return new KeyboardLayoutInfo(layout.getDescriptor(),
+                        LAYOUT_SELECTION_CRITERIA_DEVICE);
             }
         }
 
@@ -999,7 +1026,7 @@
                                     + "HW information (Language tag and Layout type). "
                                     + inputDevice.getIdentifier() + " : " + layoutDesc);
                 }
-                return layoutDesc;
+                return new KeyboardLayoutInfo(layoutDesc, LAYOUT_SELECTION_CRITERIA_DEVICE);
             }
         }
 
@@ -1021,7 +1048,10 @@
                             + "IME locale matching. " + inputDevice.getIdentifier() + " : "
                             + layoutDesc);
         }
-        return layoutDesc;
+        if (layoutDesc != null) {
+            return new KeyboardLayoutInfo(layoutDesc, LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD);
+        }
+        return null;
     }
 
     @Nullable
@@ -1140,6 +1170,8 @@
 
         if (targetDevice != null) {
             intent.putExtra(Settings.EXTRA_INPUT_DEVICE_IDENTIFIER, targetDevice.getIdentifier());
+            intent.putExtra(
+                    Settings.EXTRA_ENTRYPOINT, SettingsEnums.KEYBOARD_CONFIGURED_NOTIFICATION);
         }
 
         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
@@ -1227,6 +1259,26 @@
         }
     }
 
+    private void logKeyboardConfigurationEvent(@NonNull InputDevice inputDevice,
+            @NonNull List<ImeInfo> imeInfoList, @NonNull List<KeyboardLayoutInfo> layoutInfoList,
+            boolean isFirstConfiguration) {
+        if (imeInfoList.isEmpty() || layoutInfoList.isEmpty()) {
+            return;
+        }
+        KeyboardConfigurationEvent.Builder configurationEventBuilder =
+                new KeyboardConfigurationEvent.Builder(inputDevice).setIsFirstTimeConfiguration(
+                        isFirstConfiguration);
+        for (int i = 0; i < imeInfoList.size(); i++) {
+            KeyboardLayoutInfo layoutInfo = layoutInfoList.get(i);
+            boolean noLayoutFound = layoutInfo == null || layoutInfo.mDescriptor == null;
+            configurationEventBuilder.addLayoutSelection(imeInfoList.get(i).mImeSubtype,
+                    noLayoutFound ? null : getKeyboardLayout(layoutInfo.mDescriptor),
+                    noLayoutFound ? LAYOUT_SELECTION_CRITERIA_DEFAULT
+                            : layoutInfo.mSelectionCriteria);
+        }
+        KeyboardMetricsCollector.logKeyboardConfiguredAtom(configurationEventBuilder.build());
+    }
+
     private boolean handleMessage(Message msg) {
         switch (msg.what) {
             case MSG_UPDATE_EXISTING_DEVICES:
@@ -1409,7 +1461,7 @@
 
         // If null, it means no layout is selected for the device.
         @Nullable
-        private String mCurrentLayout;
+        private KeyboardLayoutInfo mCurrentLayout;
 
         private boolean hasConfiguredLayouts() {
             return mConfiguredLayouts != null && !mConfiguredLayouts.isEmpty();
@@ -1425,15 +1477,42 @@
         }
 
         @Nullable
-        private String getCurrentLayout() {
+        private KeyboardLayoutInfo getCurrentLayout() {
             return mCurrentLayout;
         }
 
-        private void setCurrentLayout(String currentLayout) {
+        private void setCurrentLayout(KeyboardLayoutInfo currentLayout) {
             mCurrentLayout = currentLayout;
         }
     }
 
+    private static class KeyboardLayoutInfo {
+        @Nullable
+        private final String mDescriptor;
+        @LayoutSelectionCriteria
+        private final int mSelectionCriteria;
+
+        private KeyboardLayoutInfo(@Nullable String descriptor,
+                @LayoutSelectionCriteria int selectionCriteria) {
+            mDescriptor = descriptor;
+            mSelectionCriteria = selectionCriteria;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (obj instanceof KeyboardLayoutInfo) {
+                return Objects.equals(mDescriptor, ((KeyboardLayoutInfo) obj).mDescriptor)
+                        && mSelectionCriteria == ((KeyboardLayoutInfo) obj).mSelectionCriteria;
+            }
+            return false;
+        }
+
+        @Override
+        public int hashCode() {
+            return 31 * mSelectionCriteria + mDescriptor.hashCode();
+        }
+    }
+
     private interface KeyboardLayoutVisitor {
         void visitKeyboardLayout(Resources resources,
                 int keyboardLayoutResId, KeyboardLayout layout);
diff --git a/services/core/java/com/android/server/input/KeyboardMetricsCollector.java b/services/core/java/com/android/server/input/KeyboardMetricsCollector.java
new file mode 100644
index 0000000..eb2da34
--- /dev/null
+++ b/services/core/java/com/android/server/input/KeyboardMetricsCollector.java
@@ -0,0 +1,331 @@
+/*
+ * 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.input;
+
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.hardware.input.KeyboardLayout;
+import android.icu.util.ULocale;
+import android.util.Log;
+import android.util.Slog;
+import android.util.proto.ProtoOutputStream;
+import android.view.InputDevice;
+import android.view.inputmethod.InputMethodSubtype;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.KeyboardConfiguredProto.KeyboardLayoutConfig;
+import com.android.internal.os.KeyboardConfiguredProto.RepeatedKeyboardLayoutConfig;
+import com.android.internal.util.FrameworkStatsLog;
+
+import java.lang.annotation.Retention;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Collect Keyboard metrics
+ */
+public final class KeyboardMetricsCollector {
+    private static final String TAG = "KeyboardMetricCollector";
+
+    // To enable these logs, run: 'adb shell setprop log.tag.KeyboardMetricCollector DEBUG'
+    // (requires restart)
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+    @Retention(SOURCE)
+    @IntDef(prefix = { "LAYOUT_SELECTION_CRITERIA_" }, value = {
+            LAYOUT_SELECTION_CRITERIA_USER,
+            LAYOUT_SELECTION_CRITERIA_DEVICE,
+            LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD,
+            LAYOUT_SELECTION_CRITERIA_DEFAULT
+    })
+    public @interface LayoutSelectionCriteria {}
+
+    /** Manual selection by user */
+    public static final int LAYOUT_SELECTION_CRITERIA_USER = 0;
+
+    /** Auto-detection based on device provided language tag and layout type */
+    public static final int LAYOUT_SELECTION_CRITERIA_DEVICE = 1;
+
+    /** Auto-detection based on IME provided language tag and layout type */
+    public static final int LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD = 2;
+
+    /** Default selection */
+    public static final int LAYOUT_SELECTION_CRITERIA_DEFAULT = 3;
+
+    @VisibleForTesting
+    static final String DEFAULT_LAYOUT = "Default";
+
+    @VisibleForTesting
+    static final String DEFAULT_LANGUAGE_TAG = "None";
+
+    /**
+     * Log keyboard system shortcuts for the proto
+     * {@link com.android.os.input.KeyboardSystemsEventReported}
+     * defined in "stats/atoms/input/input_extension_atoms.proto"
+     */
+    public static void logKeyboardSystemsEventReportedAtom(InputDevice inputDevice,
+            int keyboardSystemEvent, int[] keyCode, int modifierState) {
+        int vendorId = inputDevice.getVendorId();
+        int productId = inputDevice.getProductId();
+        FrameworkStatsLog.write(FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED,
+                vendorId, productId, keyboardSystemEvent, keyCode, modifierState);
+    }
+
+    /**
+     * Function to log the KeyboardConfigured
+     * {@link com.android.os.input.KeyboardConfigured} atom
+     *
+     * @param event {@link KeyboardConfigurationEvent} contains information about keyboard
+     *               configuration. Use {@link KeyboardConfigurationEvent.Builder} to create the
+     *               configuration event to log.
+     */
+    public static void logKeyboardConfiguredAtom(KeyboardConfigurationEvent event) {
+        // Creating proto to log nested field KeyboardLayoutConfig in atom
+        ProtoOutputStream proto = new ProtoOutputStream();
+
+        for (LayoutConfiguration layoutConfiguration : event.getLayoutConfigurations()) {
+            addKeyboardLayoutConfigurationToProto(proto, layoutConfiguration);
+        }
+        // Push the atom to Statsd
+        FrameworkStatsLog.write(FrameworkStatsLog.KEYBOARD_CONFIGURED,
+                event.isFirstConfiguration(), event.getVendorId(), event.getProductId(),
+                proto.getBytes());
+
+        if (DEBUG) {
+            Slog.d(TAG, "Logging Keyboard configuration event: " + event);
+        }
+    }
+
+    /**
+     * Populate the KeyboardLayoutConfig proto which is a repeated proto
+     * in the RepeatedKeyboardLayoutConfig proto with values from the
+     * {@link LayoutConfiguration} class
+     * The proto definitions can be found at:
+     * "frameworks/proto_logging/stats/atoms/input/input_extension_atoms.proto"
+     *
+     * @param proto Representing the nested proto RepeatedKeyboardLayoutConfig
+     * @param layoutConfiguration Class containing the fields for populating the
+     * KeyboardLayoutConfig proto
+     */
+    private static void addKeyboardLayoutConfigurationToProto(ProtoOutputStream proto,
+            LayoutConfiguration layoutConfiguration) {
+        // Start a new KeyboardLayoutConfig proto.
+        long keyboardLayoutConfigToken = proto.start(
+                RepeatedKeyboardLayoutConfig.KEYBOARD_LAYOUT_CONFIG);
+        proto.write(KeyboardLayoutConfig.KEYBOARD_LANGUAGE_TAG,
+                layoutConfiguration.keyboardLanguageTag);
+        proto.write(KeyboardLayoutConfig.KEYBOARD_LAYOUT_TYPE,
+                layoutConfiguration.keyboardLayoutType);
+        proto.write(KeyboardLayoutConfig.KEYBOARD_LAYOUT_NAME,
+                layoutConfiguration.keyboardLayoutName);
+        proto.write(KeyboardLayoutConfig.LAYOUT_SELECTION_CRITERIA,
+                layoutConfiguration.layoutSelectionCriteria);
+        proto.write(KeyboardLayoutConfig.IME_LANGUAGE_TAG,
+                layoutConfiguration.imeLanguageTag);
+        proto.write(KeyboardLayoutConfig.IME_LAYOUT_TYPE,
+                layoutConfiguration.imeLayoutType);
+        proto.end(keyboardLayoutConfigToken);
+    }
+
+    /**
+     * Class representing the proto KeyboardLayoutConfig defined in
+     * "frameworks/proto_logging/stats/atoms/input/input_extension_atoms.proto
+     *
+     * @see com.android.os.input.KeyboardConfigured
+     */
+    public static class KeyboardConfigurationEvent {
+
+        private final InputDevice mInputDevice;
+        private final boolean mIsFirstConfiguration;
+        private final List<LayoutConfiguration> mLayoutConfigurations;
+
+        private KeyboardConfigurationEvent(InputDevice inputDevice, boolean isFirstConfiguration,
+                List<LayoutConfiguration> layoutConfigurations) {
+            mInputDevice = inputDevice;
+            mIsFirstConfiguration = isFirstConfiguration;
+            mLayoutConfigurations = layoutConfigurations;
+        }
+
+        public int getVendorId() {
+            return mInputDevice.getVendorId();
+        }
+
+        public int getProductId() {
+            return mInputDevice.getProductId();
+        }
+
+        public boolean isFirstConfiguration() {
+            return mIsFirstConfiguration;
+        }
+
+        public List<LayoutConfiguration> getLayoutConfigurations() {
+            return mLayoutConfigurations;
+        }
+
+        @Override
+        public String toString() {
+            return "InputDevice = {VendorId = " + Integer.toHexString(getVendorId())
+                    + ", ProductId = " + Integer.toHexString(getProductId())
+                    + "}, isFirstConfiguration = " + mIsFirstConfiguration
+                    + ", LayoutConfigurations = " + mLayoutConfigurations;
+        }
+
+        /**
+         * Builder class to help create {@link KeyboardConfigurationEvent}.
+         */
+        public static class Builder {
+            @NonNull
+            private final InputDevice mInputDevice;
+            private boolean mIsFirstConfiguration;
+            private final List<InputMethodSubtype> mImeSubtypeList = new ArrayList<>();
+            private final List<KeyboardLayout> mSelectedLayoutList = new ArrayList<>();
+            private final List<Integer> mLayoutSelectionCriteriaList = new ArrayList<>();
+
+            public Builder(@NonNull InputDevice inputDevice) {
+                Objects.requireNonNull(inputDevice, "InputDevice provided should not be null");
+                mInputDevice = inputDevice;
+            }
+
+            /**
+             * Set whether this is the first time this keyboard is configured.
+             */
+            public Builder setIsFirstTimeConfiguration(boolean isFirstTimeConfiguration) {
+                mIsFirstConfiguration = isFirstTimeConfiguration;
+                return this;
+            }
+
+            /**
+             * Adds keyboard layout configuration info for a particular IME subtype language
+             */
+            public Builder addLayoutSelection(@NonNull InputMethodSubtype imeSubtype,
+                    @Nullable KeyboardLayout selectedLayout,
+                    @LayoutSelectionCriteria int layoutSelectionCriteria) {
+                Objects.requireNonNull(imeSubtype, "IME subtype provided should not be null");
+                if (!isValidSelectionCriteria(layoutSelectionCriteria)) {
+                    throw new IllegalStateException("Invalid layout selection criteria");
+                }
+                mImeSubtypeList.add(imeSubtype);
+                mSelectedLayoutList.add(selectedLayout);
+                mLayoutSelectionCriteriaList.add(layoutSelectionCriteria);
+                return this;
+            }
+
+            /**
+             * Creates {@link KeyboardConfigurationEvent} from the provided information
+             */
+            public KeyboardConfigurationEvent build() {
+                int size = mImeSubtypeList.size();
+                if (size == 0) {
+                    throw new IllegalStateException("Should have at least one configuration");
+                }
+                List<LayoutConfiguration> configurationList = new ArrayList<>();
+                for (int i = 0; i < size; i++) {
+                    KeyboardLayout selectedLayout = mSelectedLayoutList.get(i);
+                    @LayoutSelectionCriteria int layoutSelectionCriteria =
+                            mLayoutSelectionCriteriaList.get(i);
+                    InputMethodSubtype imeSubtype =  mImeSubtypeList.get(i);
+                    String keyboardLanguageTag = mInputDevice.getKeyboardLanguageTag();
+                    keyboardLanguageTag = keyboardLanguageTag == null ? DEFAULT_LANGUAGE_TAG
+                            : keyboardLanguageTag;
+                    int keyboardLayoutType = KeyboardLayout.LayoutType.getLayoutTypeEnumValue(
+                            mInputDevice.getKeyboardLayoutType());
+
+                    ULocale pkLocale = imeSubtype.getPhysicalKeyboardHintLanguageTag();
+                    String canonicalizedLanguageTag =
+                            imeSubtype.getCanonicalizedLanguageTag().equals("")
+                            ? DEFAULT_LANGUAGE_TAG : imeSubtype.getCanonicalizedLanguageTag();
+                    String imeLanguageTag = pkLocale != null ? pkLocale.toLanguageTag()
+                            : canonicalizedLanguageTag;
+                    int imeLayoutType = KeyboardLayout.LayoutType.getLayoutTypeEnumValue(
+                            imeSubtype.getPhysicalKeyboardHintLayoutType());
+
+                    // Sanitize null values
+                    String keyboardLayoutName =
+                            selectedLayout == null ? DEFAULT_LAYOUT : selectedLayout.getLabel();
+
+                    configurationList.add(
+                            new LayoutConfiguration(keyboardLayoutType, keyboardLanguageTag,
+                                    keyboardLayoutName, layoutSelectionCriteria,
+                                    imeLayoutType, imeLanguageTag));
+                }
+                return new KeyboardConfigurationEvent(mInputDevice, mIsFirstConfiguration,
+                        configurationList);
+            }
+        }
+    }
+
+    @VisibleForTesting
+    static class LayoutConfiguration {
+        // This should match enum values defined in "frameworks/base/core/res/res/values/attrs.xml"
+        public final int keyboardLayoutType;
+        public final String keyboardLanguageTag;
+        public final String keyboardLayoutName;
+        @LayoutSelectionCriteria
+        public final int layoutSelectionCriteria;
+        public final int imeLayoutType;
+        public final String imeLanguageTag;
+
+        private LayoutConfiguration(int keyboardLayoutType, String keyboardLanguageTag,
+                String keyboardLayoutName, @LayoutSelectionCriteria int layoutSelectionCriteria,
+                int imeLayoutType, String imeLanguageTag) {
+            this.keyboardLayoutType = keyboardLayoutType;
+            this.keyboardLanguageTag = keyboardLanguageTag;
+            this.keyboardLayoutName = keyboardLayoutName;
+            this.layoutSelectionCriteria = layoutSelectionCriteria;
+            this.imeLayoutType = imeLayoutType;
+            this.imeLanguageTag = imeLanguageTag;
+        }
+
+        @Override
+        public String toString() {
+            return "{keyboardLanguageTag = " + keyboardLanguageTag + " keyboardLayoutType = "
+                    + KeyboardLayout.LayoutType.getLayoutNameFromValue(keyboardLayoutType)
+                    + " keyboardLayoutName = " + keyboardLayoutName + " layoutSelectionCriteria = "
+                    + getStringForSelectionCriteria(layoutSelectionCriteria)
+                    + "imeLanguageTag = " + imeLanguageTag + " imeLayoutType = "
+                    + KeyboardLayout.LayoutType.getLayoutNameFromValue(imeLayoutType) + "}";
+        }
+    }
+
+    private static String getStringForSelectionCriteria(
+            @LayoutSelectionCriteria int layoutSelectionCriteria) {
+        switch (layoutSelectionCriteria) {
+            case LAYOUT_SELECTION_CRITERIA_USER:
+                return "LAYOUT_SELECTION_CRITERIA_USER";
+            case LAYOUT_SELECTION_CRITERIA_DEVICE:
+                return "LAYOUT_SELECTION_CRITERIA_DEVICE";
+            case LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD:
+                return "LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD";
+            case LAYOUT_SELECTION_CRITERIA_DEFAULT:
+                return "LAYOUT_SELECTION_CRITERIA_DEFAULT";
+            default:
+                return "INVALID_CRITERIA";
+        }
+    }
+
+    private static boolean isValidSelectionCriteria(int layoutSelectionCriteria) {
+        return layoutSelectionCriteria == LAYOUT_SELECTION_CRITERIA_USER
+                || layoutSelectionCriteria == LAYOUT_SELECTION_CRITERIA_DEVICE
+                || layoutSelectionCriteria == LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD
+                || layoutSelectionCriteria == LAYOUT_SELECTION_CRITERIA_DEFAULT;
+    }
+}
+
diff --git a/services/core/java/com/android/server/input/NativeInputManagerService.java b/services/core/java/com/android/server/input/NativeInputManagerService.java
index 363bc94..f126a89 100644
--- a/services/core/java/com/android/server/input/NativeInputManagerService.java
+++ b/services/core/java/com/android/server/input/NativeInputManagerService.java
@@ -28,7 +28,6 @@
 import android.view.InputEvent;
 import android.view.PointerIcon;
 import android.view.VerifiedInputEvent;
-import android.view.ViewConfiguration;
 
 import java.util.List;
 
@@ -203,6 +202,8 @@
 
     void setMotionClassifierEnabled(boolean enabled);
 
+    void setKeyRepeatConfiguration(int timeoutMs, int delayMs);
+
     InputSensorInfo[] getSensorList(int deviceId);
 
     boolean flushSensor(int deviceId, int sensorType);
@@ -242,15 +243,6 @@
      */
     void sysfsNodeChanged(String sysfsNodePath);
 
-    /**
-     * Notify there is a change in any of the key gesture timeouts, such as the key
-     * repeat timeout or key repeat delay.
-     *
-     * @see ViewConfiguration#getKeyRepeatTimeout()
-     * @see ViewConfiguration#getKeyRepeatDelay()
-     */
-    void notifyKeyGestureTimeoutsChanged();
-
     /** The native implementation of InputManagerService methods. */
     class NativeImpl implements NativeInputManagerService {
         /** Pointer to native input manager service object, used by native code. */
@@ -466,6 +458,9 @@
         public native void setMotionClassifierEnabled(boolean enabled);
 
         @Override
+        public native void setKeyRepeatConfiguration(int timeoutMs, int delayMs);
+
+        @Override
         public native InputSensorInfo[] getSensorList(int deviceId);
 
         @Override
@@ -498,8 +493,5 @@
 
         @Override
         public native void sysfsNodeChanged(String sysfsNodePath);
-
-        @Override
-        public native void notifyKeyGestureTimeoutsChanged();
     }
 }
diff --git a/services/core/java/com/android/server/input/PersistentDataStore.java b/services/core/java/com/android/server/input/PersistentDataStore.java
index bce210d..31083fd 100644
--- a/services/core/java/com/android/server/input/PersistentDataStore.java
+++ b/services/core/java/com/android/server/input/PersistentDataStore.java
@@ -101,6 +101,10 @@
         }
     }
 
+    public boolean hasInputDeviceEntry(String inputDeviceDescriptor) {
+        return getInputDeviceState(inputDeviceDescriptor) != null;
+    }
+
     public TouchCalibration getTouchCalibration(String inputDeviceDescriptor, int surfaceRotation) {
         InputDeviceState state = getInputDeviceState(inputDeviceDescriptor);
         if (state == null) {
diff --git a/services/core/java/com/android/server/input/UEventManager.java b/services/core/java/com/android/server/input/UEventManager.java
new file mode 100644
index 0000000..17d87e4
--- /dev/null
+++ b/services/core/java/com/android/server/input/UEventManager.java
@@ -0,0 +1,42 @@
+/*
+ * 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.input;
+
+import android.os.UEventObserver;
+
+/** An interface used to change the API of UEventObserver to a more test-friendly format. */
+interface UEventManager {
+
+    abstract class UEventListener {
+        private final UEventObserver mObserver = new UEventObserver() {
+            @Override
+            public void onUEvent(UEvent event) {
+                UEventListener.this.onUEvent(event);
+            }
+        };
+
+        public abstract void onUEvent(UEventObserver.UEvent event);
+    }
+
+    default void addListener(UEventListener listener, String match) {
+        listener.mObserver.startObserving(match);
+    }
+
+    default void removeListener(UEventListener listener) {
+        listener.mObserver.stopObserving();
+    }
+}
diff --git a/services/core/java/com/android/server/inputmethod/HandwritingModeController.java b/services/core/java/com/android/server/inputmethod/HandwritingModeController.java
index 6a0550b..26df162 100644
--- a/services/core/java/com/android/server/inputmethod/HandwritingModeController.java
+++ b/services/core/java/com/android/server/inputmethod/HandwritingModeController.java
@@ -16,8 +16,6 @@
 
 package com.android.server.inputmethod;
 
-import static android.view.InputDevice.SOURCE_STYLUS;
-
 import android.Manifest;
 import android.annotation.AnyThread;
 import android.annotation.NonNull;
@@ -94,15 +92,6 @@
         mInkWindowInitRunnable = inkWindowInitRunnable;
     }
 
-    // TODO(b/210039666): Consider moving this to MotionEvent
-    private static boolean isStylusEvent(MotionEvent event) {
-        if (!event.isFromSource(SOURCE_STYLUS)) {
-            return false;
-        }
-        final int tool = event.getToolType(0);
-        return tool == MotionEvent.TOOL_TYPE_STYLUS || tool == MotionEvent.TOOL_TYPE_ERASER;
-    }
-
     /**
      * Initializes the handwriting spy on the given displayId.
      *
@@ -291,6 +280,10 @@
         reset(false /* reinitializing */);
     }
 
+    void setInkWindowInitializer(Runnable inkWindowInitializer) {
+        mInkWindowInitRunnable = inkWindowInitializer;
+    }
+
     private void reset(boolean reinitializing) {
         if (mHandwritingEventReceiver != null) {
             mHandwritingEventReceiver.dispose();
@@ -328,7 +321,7 @@
             return false;
         }
         final MotionEvent event = (MotionEvent) ev;
-        if (!isStylusEvent(event)) {
+        if (!event.isStylusPointer()) {
             return false;
         }
         if (event.getDisplayId() != mCurrentDisplayId) {
@@ -348,6 +341,10 @@
             // Ask IMMS to make ink window ready.
             mInkWindowInitRunnable.run();
             mInkWindowInitRunnable = null;
+            return;
+        } else if (event.isHoverEvent()) {
+            // Hover events need not be recorded to buffer.
+            return;
         }
 
         // If handwriting delegation is ongoing, don't clear the buffer so that multiple strokes
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 57f8d14..7bda2c1 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -48,6 +48,7 @@
 
 import static com.android.server.inputmethod.ImeVisibilityStateComputer.ImeTargetWindowState;
 import static com.android.server.inputmethod.ImeVisibilityStateComputer.ImeVisibilityResult;
+import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_HIDE_IME;
 import static com.android.server.inputmethod.InputMethodBindingController.TIME_TO_RECONNECT;
 import static com.android.server.inputmethod.InputMethodUtils.isSoftInputModeStateVisibleAllowed;
 
@@ -1174,6 +1175,8 @@
                     Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD), false, this, userId);
             resolver.registerContentObserver(Settings.Secure.getUriFor(
                     Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE), false, this, userId);
+            resolver.registerContentObserver(Settings.Secure.getUriFor(
+                    STYLUS_HANDWRITING_ENABLED), false, this);
             mRegistered = true;
         }
 
@@ -1182,6 +1185,8 @@
                     Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD);
             final Uri accessibilityRequestingNoImeUri = Settings.Secure.getUriFor(
                     Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE);
+            final Uri stylusHandwritingEnabledUri = Settings.Secure.getUriFor(
+                    STYLUS_HANDWRITING_ENABLED);
             synchronized (ImfLock.class) {
                 if (showImeUri.equals(uri)) {
                     mMenuController.updateKeyboardFromSettingsLocked();
@@ -1199,6 +1204,8 @@
                         showCurrentInputImplicitLocked(mCurFocusedWindow,
                                 SoftInputShowHideReason.SHOW_SETTINGS_ON_CHANGE);
                     }
+                } else if (stylusHandwritingEnabledUri.equals(uri)) {
+                    InputMethodManager.invalidateLocalStylusHandwritingAvailabilityCaches();
                 } else {
                     boolean enabledChanged = false;
                     String newEnabled = mSettings.getEnabledInputMethodsStr();
@@ -2362,11 +2369,33 @@
             mCurVirtualDisplayToScreenMatrix = null;
             ImeTracker.forLogging().onFailed(mCurStatsToken, ImeTracker.PHASE_SERVER_WAIT_IME);
             mCurStatsToken = null;
-
+            InputMethodManager.invalidateLocalStylusHandwritingAvailabilityCaches();
             mMenuController.hideInputMethodMenuLocked();
         }
     }
 
+    /**
+     * Called when {@link #resetCurrentMethodAndClientLocked(int)} invoked for clean-up states
+     * before unbinding the current method.
+     */
+    @GuardedBy("ImfLock.class")
+    void onUnbindCurrentMethodByReset() {
+        final ImeTargetWindowState winState = mVisibilityStateComputer.getWindowStateOrNull(
+                mCurFocusedWindow);
+        if (winState != null && !winState.isRequestedImeVisible()
+                && !mVisibilityStateComputer.isInputShown()) {
+            // Normally, the focus window will apply the IME visibility state to
+            // WindowManager when the IME has applied it. But it would be too late when
+            // switching IMEs in between different users. (Since the focused IME will
+            // first unbind the service to switch to bind the next user of the IME
+            // service, that wouldn't make the attached IME token validity check in time)
+            // As a result, we have to notify WM to apply IME visibility before clearing the
+            // binding states in the first place.
+            mVisibilityApplier.applyImeVisibility(mCurFocusedWindow, mCurStatsToken,
+                    STATE_HIDE_IME);
+        }
+    }
+
     /** {@code true} when a {@link ClientState} has attached from starting the input connection. */
     @GuardedBy("ImfLock.class")
     boolean hasAttachedClient() {
@@ -2449,6 +2478,9 @@
                 curInputMethodInfo != null && curInputMethodInfo.suppressesSpellChecker();
         final SparseArray<IAccessibilityInputMethodSession> accessibilityInputMethodSessions =
                 createAccessibilityInputMethodSessions(mCurClient.mAccessibilitySessions);
+        if (mBindingController.supportsStylusHandwriting() && hasSupportedStylusLocked()) {
+            mHwController.setInkWindowInitializer(new InkWindowInitializer());
+        }
         return new InputBindResult(InputBindResult.ResultCode.SUCCESS_WITH_IME_SESSION,
                 session.mSession, accessibilityInputMethodSessions,
                 (session.mChannel != null ? session.mChannel.dup() : null),
@@ -2838,6 +2870,8 @@
     @GuardedBy("ImfLock.class")
     void resetCurrentMethodAndClientLocked(@UnbindReason int unbindClientReason) {
         setSelectedMethodIdLocked(null);
+        // Callback before clean-up binding states.
+        onUnbindCurrentMethodByReset();
         mBindingController.unbindCurrentMethod();
         unbindCurrentClientLocked(unbindClientReason);
     }
@@ -3050,7 +3084,12 @@
     @GuardedBy("ImfLock.class")
     private boolean shouldShowImeSwitcherLocked(int visibility) {
         if (!mShowOngoingImeSwitcherForPhones) return false;
+        // When the IME switcher dialog is shown, the IME switcher button should be hidden.
         if (mMenuController.getSwitchingDialogLocked() != null) return false;
+        // When we are switching IMEs, the IME switcher button should be hidden.
+        if (!Objects.equals(getCurIdLocked(), getSelectedMethodIdLocked())) {
+            return false;
+        }
         if (mWindowManagerInternal.isKeyguardShowingAndNotOccluded()
                 && mWindowManagerInternal.isKeyguardSecure(mSettings.getCurrentUserId())) {
             return false;
@@ -3205,7 +3244,12 @@
             } else {
                 vis &= ~InputMethodService.IME_VISIBLE_IMPERCEPTIBLE;
             }
-            // mImeWindowVis should be updated before calling shouldShowImeSwitcherLocked().
+            if (mMenuController.getSwitchingDialogLocked() != null
+                    || !Objects.equals(getCurIdLocked(), getSelectedMethodIdLocked())) {
+                // When the IME switcher dialog is shown, or we are switching IMEs,
+                // the back button should be in the default state (as if the IME is not shown).
+                backDisposition = InputMethodService.BACK_DISPOSITION_ADJUST_NOTHING;
+            }
             final boolean needsToShowImeSwitcher = shouldShowImeSwitcherLocked(vis);
             if (mStatusBarManagerInternal != null) {
                 mStatusBarManagerInternal.setImeWindowStatus(mCurTokenDisplayId,
@@ -5769,7 +5813,7 @@
                 // input target changed, in case seeing the dialog dismiss flickering during
                 // the next focused window starting the input connection.
                 if (mLastImeTargetWindow != mCurFocusedWindow) {
-                    mMenuController.hideInputMethodMenu();
+                    mMenuController.hideInputMethodMenuLocked();
                 }
             }
         }
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
index c212e8e..c2ef83d 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
@@ -175,13 +175,13 @@
                     int subtypeId = mSubtypeIds[which];
                     adapter.mCheckedItem = which;
                     adapter.notifyDataSetChanged();
-                    hideInputMethodMenu();
                     if (im != null) {
                         if (subtypeId < 0 || subtypeId >= im.getSubtypeCount()) {
                             subtypeId = NOT_A_SUBTYPE_ID;
                         }
                         mService.setInputMethodLocked(im.getId(), subtypeId);
                     }
+                    hideInputMethodMenuLocked();
                 }
             };
             mDialogBuilder.setSingleChoiceItems(adapter, checkedItem, choiceListener);
@@ -220,12 +220,18 @@
         }
     }
 
+    /**
+     * Hides the input method switcher menu.
+     */
     void hideInputMethodMenu() {
         synchronized (ImfLock.class) {
             hideInputMethodMenuLocked();
         }
     }
 
+    /**
+     * Hides the input method switcher menu, synchronised version of {@link #hideInputMethodMenu}.
+     */
     @GuardedBy("ImfLock.class")
     void hideInputMethodMenuLocked() {
         if (DEBUG) Slog.v(TAG, "Hide switching menu");
diff --git a/services/core/java/com/android/server/lights/LightsService.java b/services/core/java/com/android/server/lights/LightsService.java
index 91f14de..6124b55 100644
--- a/services/core/java/com/android/server/lights/LightsService.java
+++ b/services/core/java/com/android/server/lights/LightsService.java
@@ -15,7 +15,6 @@
 
 package com.android.server.lights;
 
-import android.Manifest;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.content.Context;
@@ -29,6 +28,7 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
+import android.os.PermissionEnforcer;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.Trace;
@@ -68,6 +68,9 @@
     private Handler mH;
 
     private final class LightsManagerBinderService extends ILightsManager.Stub {
+        LightsManagerBinderService() {
+            super(PermissionEnforcer.fromContext(getContext()));
+        }
 
         private final class Session implements Comparable<Session> {
             final IBinder mToken;
@@ -101,10 +104,10 @@
          * Returns the lights available for apps to control on the device. Only lights that aren't
          * reserved for system use are available to apps.
          */
+        @android.annotation.EnforcePermission(android.Manifest.permission.CONTROL_DEVICE_LIGHTS)
         @Override
         public List<Light> getLights() {
-            getContext().enforceCallingOrSelfPermission(Manifest.permission.CONTROL_DEVICE_LIGHTS,
-                    "getLights requires CONTROL_DEVICE_LIGHTS_PERMISSION");
+            getLights_enforcePermission();
 
             synchronized (LightsService.this) {
                 final List<Light> lights = new ArrayList<Light>();
@@ -125,10 +128,10 @@
          * <p>Null values mean that the request should be removed, and the light turned off if it
          * is not being used by anything else.
          */
+        @android.annotation.EnforcePermission(android.Manifest.permission.CONTROL_DEVICE_LIGHTS)
         @Override
         public void setLightStates(IBinder token, int[] lightIds, LightState[] lightStates) {
-            getContext().enforceCallingOrSelfPermission(Manifest.permission.CONTROL_DEVICE_LIGHTS,
-                    "setLightStates requires CONTROL_DEVICE_LIGHTS permission");
+            setLightStates_enforcePermission();
             Preconditions.checkState(lightIds.length == lightStates.length);
 
             synchronized (LightsService.this) {
@@ -144,10 +147,10 @@
             }
         }
 
+        @android.annotation.EnforcePermission(android.Manifest.permission.CONTROL_DEVICE_LIGHTS)
         @Override
         public @Nullable LightState getLightState(int lightId) {
-            getContext().enforceCallingOrSelfPermission(Manifest.permission.CONTROL_DEVICE_LIGHTS,
-                    "getLightState(@TestApi) requires CONTROL_DEVICE_LIGHTS permission");
+            getLightState_enforcePermission();
 
             synchronized (LightsService.this) {
                 final LightImpl light = mLightsById.get(lightId);
@@ -158,10 +161,10 @@
             }
         }
 
+        @android.annotation.EnforcePermission(android.Manifest.permission.CONTROL_DEVICE_LIGHTS)
         @Override
         public void openSession(IBinder token, int priority) {
-            getContext().enforceCallingOrSelfPermission(Manifest.permission.CONTROL_DEVICE_LIGHTS,
-                    "openSession requires CONTROL_DEVICE_LIGHTS permission");
+            openSession_enforcePermission();
             Preconditions.checkNotNull(token);
 
             synchronized (LightsService.this) {
@@ -177,10 +180,10 @@
             }
         }
 
+        @android.annotation.EnforcePermission(android.Manifest.permission.CONTROL_DEVICE_LIGHTS)
         @Override
         public void closeSession(IBinder token) {
-            getContext().enforceCallingOrSelfPermission(Manifest.permission.CONTROL_DEVICE_LIGHTS,
-                    "closeSession requires CONTROL_DEVICE_LIGHTS permission");
+            closeSession_enforcePermission();
             Preconditions.checkNotNull(token);
             closeSessionInternal(token);
         }
@@ -465,9 +468,10 @@
         }
 
         for (int i = mLightsById.size() - 1; i >= 0; i--) {
-            final int type = mLightsById.keyAt(i);
+            LightImpl light = mLightsById.valueAt(i);
+            final int type = light.mHwLight.type;
             if (0 <= type && type < mLightsByType.length) {
-                mLightsByType[type] = mLightsById.valueAt(i);
+                mLightsByType[type] = light;
             }
         }
     }
diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java
index 115421d..595b2e4 100644
--- a/services/core/java/com/android/server/location/LocationManagerService.java
+++ b/services/core/java/com/android/server/location/LocationManagerService.java
@@ -204,8 +204,9 @@
         public void onUserStarting(TargetUser user) {
             mUserInfoHelper.onUserStarted(user.getUserIdentifier());
 
-            // log location enabled state on start to minimize coverage loss
+            // log location enabled state and emergency state on start to minimize coverage loss
             mService.logLocationEnabledState();
+            mService.logEmergencyState();
         }
 
         @Override
@@ -297,6 +298,8 @@
                 refreshAppOpsRestrictions(userId);
             }
         });
+        mInjector.getEmergencyHelper().addOnEmergencyStateChangedListener(
+                this::onEmergencyStateChanged);
 
         // set up passive provider first since it will be required for all other location providers,
         // which are loaded later once the system is ready.
@@ -361,9 +364,13 @@
             if (realProvider != null) {
                 // custom logic wrapping all non-passive providers
                 if (manager != mPassiveManager) {
+                    int defaultStationaryThrottlingSetting =
+                            mContext.getPackageManager().hasSystemFeature(
+                                PackageManager.FEATURE_WATCH) ? 0 : 1;
                     boolean enableStationaryThrottling = Settings.Global.getInt(
                             mContext.getContentResolver(),
-                            Settings.Global.LOCATION_ENABLE_STATIONARY_THROTTLE, 1) != 0;
+                            Settings.Global.LOCATION_ENABLE_STATIONARY_THROTTLE,
+                            defaultStationaryThrottlingSetting) != 0;
                     if (enableStationaryThrottling) {
                         realProvider = new StationaryThrottlingLocationProvider(manager.getName(),
                                 mInjector, realProvider);
@@ -568,6 +575,15 @@
         refreshAppOpsRestrictions(userId);
     }
 
+    private void onEmergencyStateChanged() {
+        this.logEmergencyState();
+    }
+
+    private void logEmergencyState() {
+        boolean isInEmergency = mInjector.getEmergencyHelper().isInEmergency(Long.MIN_VALUE);
+        mInjector.getLocationUsageLogger().logEmergencyStateChanged(isInEmergency);
+    }
+
     private void logLocationEnabledState() {
         boolean locationEnabled = false;
         // Location setting is considered on if it is enabled for any one user
@@ -598,10 +614,11 @@
         return mGnssManagerService == null ? 0 : mGnssManagerService.getGnssBatchSize();
     }
 
+    @android.annotation.EnforcePermission(android.Manifest.permission.LOCATION_HARDWARE)
     @Override
     public void startGnssBatch(long periodNanos, ILocationListener listener, String packageName,
             @Nullable String attributionTag, String listenerId) {
-        mContext.enforceCallingOrSelfPermission(Manifest.permission.LOCATION_HARDWARE, null);
+        startGnssBatch_enforcePermission();
 
         if (mGnssManagerService == null) {
             return;
@@ -627,9 +644,10 @@
         }
     }
 
+    @android.annotation.EnforcePermission(android.Manifest.permission.LOCATION_HARDWARE)
     @Override
     public void flushGnssBatch() {
-        mContext.enforceCallingOrSelfPermission(Manifest.permission.LOCATION_HARDWARE, null);
+        flushGnssBatch_enforcePermission();
 
         if (mGnssManagerService == null) {
             return;
@@ -642,9 +660,10 @@
         }
     }
 
+    @android.annotation.EnforcePermission(android.Manifest.permission.LOCATION_HARDWARE)
     @Override
     public void stopGnssBatch() {
-        mContext.enforceCallingOrSelfPermission(Manifest.permission.LOCATION_HARDWARE, null);
+        stopGnssBatch_enforcePermission();
 
         if (mGnssManagerService == null) {
             return;
@@ -1110,10 +1129,11 @@
         }
     }
 
+    @android.annotation.EnforcePermission(android.Manifest.permission.INTERACT_ACROSS_USERS)
     @Override
     @RequiresPermission(INTERACT_ACROSS_USERS)
     public void addProviderRequestListener(IProviderRequestListener listener) {
-        mContext.enforceCallingOrSelfPermission(INTERACT_ACROSS_USERS, null);
+        addProviderRequestListener_enforcePermission();
         for (LocationProviderManager manager : mProviderManagers) {
             if (manager.isVisibleToCaller()) {
                 manager.addProviderRequestListener(listener);
@@ -1194,10 +1214,11 @@
         return manager.getProperties();
     }
 
+    @android.annotation.EnforcePermission(android.Manifest.permission.READ_DEVICE_CONFIG)
     @Override
     public boolean isProviderPackage(@Nullable String provider, String packageName,
             @Nullable String attributionTag) {
-        mContext.enforceCallingOrSelfPermission(permission.READ_DEVICE_CONFIG, null);
+        isProviderPackage_enforcePermission();
 
         for (LocationProviderManager manager : mProviderManagers) {
             if (provider != null && !provider.equals(manager.getName())) {
@@ -1216,9 +1237,10 @@
         return false;
     }
 
+    @android.annotation.EnforcePermission(android.Manifest.permission.READ_DEVICE_CONFIG)
     @Override
     public List<String> getProviderPackages(String provider) {
-        mContext.enforceCallingOrSelfPermission(permission.READ_DEVICE_CONFIG, null);
+        getProviderPackages_enforcePermission();
 
         LocationProviderManager manager = getLocationProviderManager(provider);
         if (manager == null) {
@@ -1549,6 +1571,18 @@
         }
         ipw.decreaseIndent();
 
+        ipw.println("Historical Aggregate Gnss Measurement Provider Data:");
+        ipw.increaseIndent();
+        ArrayMap<CallerIdentity, LocationEventLog.GnssMeasurementAggregateStats>
+                gnssAggregateStats = EVENT_LOG.copyGnssMeasurementAggregateStats();
+        for (int i = 0; i < gnssAggregateStats.size(); i++) {
+            ipw.print(gnssAggregateStats.keyAt(i));
+            ipw.print(": ");
+            gnssAggregateStats.valueAt(i).updateTotals();
+            ipw.println(gnssAggregateStats.valueAt(i));
+        }
+        ipw.decreaseIndent();
+
         if (mGnssManagerService != null) {
             ipw.println("GNSS Manager:");
             ipw.increaseIndent();
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubService.java b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
index 5f78374..93c66a1 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
@@ -52,6 +52,7 @@
 import android.os.ShellCallback;
 import android.os.SystemClock;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.provider.Settings;
 import android.util.Log;
 import android.util.Pair;
@@ -169,6 +170,8 @@
 
     private SensorPrivacyManagerInternal mSensorPrivacyManagerInternal;
 
+    private UserManager mUserManager = null;
+
     private final Map<Integer, AtomicLong> mLastRestartTimestampMap = new HashMap<>();
 
     /**
@@ -491,6 +494,14 @@
             return;
         }
 
+        if (mUserManager == null) {
+            mUserManager = mContext.getSystemService(UserManager.class);
+            if (mUserManager == null) {
+                Log.e(TAG, "Unable to get the UserManager service");
+                return;
+            }
+        }
+
         sendMicrophoneDisableSettingUpdateForCurrentUser();
         if (mSensorPrivacyManagerInternal == null) {
             Log.e(TAG, "Unable to add a sensor privacy listener for all users");
@@ -499,8 +510,9 @@
 
         mSensorPrivacyManagerInternal.addSensorPrivacyListenerForAllUsers(
                 SensorPrivacyManager.Sensors.MICROPHONE, (userId, enabled) -> {
-                    if (userId == getCurrentUserId()) {
-                        Log.d(TAG, "User: " + userId + "mic privacy: " + enabled);
+                    // If we are in HSUM mode, any user can change the microphone setting
+                    if (mUserManager.isHeadlessSystemUserMode() || userId == getCurrentUserId()) {
+                        Log.d(TAG, "User: " + userId + " mic privacy: " + enabled);
                         sendMicrophoneDisableSettingUpdate(enabled);
                     }
                 });
diff --git a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
index eb1a0e2..09f373f 100644
--- a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
+++ b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
@@ -421,6 +421,9 @@
                 android.hardware.contexthub.IContextHubCallback.Stub {
             private final int mContextHubId;
             private final ICallback mCallback;
+            // 9a17008d-6bf1-445a-9011-6d21bd985b6c
+            private static final byte[] UUID = {-102, 23, 0, -115, 107, -15, 68, 90,
+                                                -112, 17, 109, 33, -67, -104, 91, 108};
 
             ContextHubAidlCallback(int contextHubId, ICallback callback) {
                 mContextHubId = contextHubId;
@@ -463,6 +466,10 @@
                 // TODO(271471342): Implement
             }
 
+            public byte[] getUuid() {
+                return UUID;
+            }
+
             @Override
             public String getInterfaceHash() {
                 return android.hardware.contexthub.IContextHubCallback.HASH;
diff --git a/services/core/java/com/android/server/location/eventlog/LocationEventLog.java b/services/core/java/com/android/server/location/eventlog/LocationEventLog.java
index cb952ed..87e193f 100644
--- a/services/core/java/com/android/server/location/eventlog/LocationEventLog.java
+++ b/services/core/java/com/android/server/location/eventlog/LocationEventLog.java
@@ -30,6 +30,7 @@
 import static java.util.concurrent.TimeUnit.MILLISECONDS;
 
 import android.annotation.Nullable;
+import android.location.GnssMeasurementRequest;
 import android.location.LocationRequest;
 import android.location.provider.ProviderRequest;
 import android.location.util.identity.CallerIdentity;
@@ -58,21 +59,25 @@
 
     private static int getLocationsLogSize() {
         if (D) {
-            return 200;
+            return 400;
         } else {
-            return 100;
+            return 200;
         }
     }
 
     @GuardedBy("mAggregateStats")
     private final ArrayMap<String, ArrayMap<CallerIdentity, AggregateStats>> mAggregateStats;
 
+    @GuardedBy("mGnssMeasAggregateStats")
+    private final ArrayMap<CallerIdentity, GnssMeasurementAggregateStats> mGnssMeasAggregateStats;
+
     @GuardedBy("this")
     private final LocationsEventLog mLocationsLog;
 
     private LocationEventLog() {
         super(getLogSize(), Object.class);
         mAggregateStats = new ArrayMap<>(4);
+        mGnssMeasAggregateStats = new ArrayMap<>();
         mLocationsLog = new LocationsEventLog(getLocationsLogSize());
     }
 
@@ -105,6 +110,29 @@
         }
     }
 
+    /** Copies out gnss measurement aggregated stats. */
+    public ArrayMap<CallerIdentity, GnssMeasurementAggregateStats>
+            copyGnssMeasurementAggregateStats() {
+        synchronized (mGnssMeasAggregateStats) {
+            ArrayMap<CallerIdentity, GnssMeasurementAggregateStats> copy = new ArrayMap<>(
+                    mGnssMeasAggregateStats);
+            return copy;
+        }
+    }
+
+    private GnssMeasurementAggregateStats getGnssMeasurementAggregateStats(
+            CallerIdentity identity) {
+        synchronized (mGnssMeasAggregateStats) {
+            CallerIdentity aggregate = CallerIdentity.forAggregation(identity);
+            GnssMeasurementAggregateStats stats = mGnssMeasAggregateStats.get(aggregate);
+            if (stats == null) {
+                stats = new GnssMeasurementAggregateStats();
+                mGnssMeasAggregateStats.put(aggregate, stats);
+            }
+            return stats;
+        }
+    }
+
     /** Logs a user switched event. */
     public void logUserSwitched(int userIdFrom, int userIdTo) {
         addLog(new UserSwitchedEvent(userIdFrom, userIdTo));
@@ -221,6 +249,29 @@
         addLog(new LocationPowerSaveModeEvent(locationPowerSaveMode));
     }
 
+    /** Logs a new client registration for a GNSS Measurement. */
+    public void logGnssMeasurementClientRegistered(CallerIdentity identity,
+            GnssMeasurementRequest request) {
+        addLog(new GnssMeasurementClientRegisterEvent(true, identity, request));
+        getGnssMeasurementAggregateStats(identity).markRequestAdded(request.getIntervalMillis(),
+                request.isFullTracking());
+    }
+
+    /** Logs a new client unregistration for a GNSS Measurement. */
+    public void logGnssMeasurementClientUnregistered(CallerIdentity identity) {
+        addLog(new GnssMeasurementClientRegisterEvent(false, identity, null));
+        getGnssMeasurementAggregateStats(identity).markRequestRemoved();
+    }
+
+    /** Logs a GNSS measurement event deliver for a client. */
+    public void logGnssMeasurementsDelivered(int numGnssMeasurements,
+            CallerIdentity identity) {
+        synchronized (this) {
+            mLocationsLog.logDeliveredGnssMeasurements(numGnssMeasurements, identity);
+        }
+        getGnssMeasurementAggregateStats(identity).markGnssMeasurementDelivered();
+    }
+
     private void addLog(Object logEvent) {
         addLog(SystemClock.elapsedRealtime(), logEvent);
     }
@@ -528,6 +579,50 @@
         }
     }
 
+    private static final class GnssMeasurementClientRegisterEvent{
+
+        private final boolean mRegistered;
+        private final CallerIdentity mIdentity;
+        @Nullable
+        private final GnssMeasurementRequest mGnssMeasurementRequest;
+
+        GnssMeasurementClientRegisterEvent(boolean registered,
+                CallerIdentity identity, @Nullable GnssMeasurementRequest measurementRequest) {
+            mRegistered = registered;
+            mIdentity = identity;
+            mGnssMeasurementRequest = measurementRequest;
+        }
+
+        @Override
+        public String toString() {
+            if (mRegistered) {
+                return "gnss measurements +registration " + mIdentity + " -> "
+                        + mGnssMeasurementRequest;
+            } else {
+                return "gnss measurements -registration " + mIdentity;
+            }
+        }
+    }
+
+    private static final class GnssMeasurementDeliverEvent {
+
+        private final int mNumGnssMeasurements;
+        @Nullable
+        private final CallerIdentity mIdentity;
+
+        GnssMeasurementDeliverEvent(int numGnssMeasurements,
+                @Nullable CallerIdentity identity) {
+            mNumGnssMeasurements = numGnssMeasurements;
+            mIdentity = identity;
+        }
+
+        @Override
+        public String toString() {
+            return "gnss measurements delivered GnssMeasurements[" + mNumGnssMeasurements + "]"
+                    + " to " + mIdentity;
+        }
+    }
+
     private static final class LocationsEventLog extends LocalEventLog<Object> {
 
         LocationsEventLog(int size) {
@@ -538,6 +633,11 @@
             addLog(new ProviderReceiveLocationEvent(provider, numLocations));
         }
 
+        public void logDeliveredGnssMeasurements(int numGnssMeasurements,
+                CallerIdentity identity) {
+            addLog(new GnssMeasurementDeliverEvent(numGnssMeasurements, identity));
+        }
+
         public void logProviderDeliveredLocations(String provider, int numLocations,
                 CallerIdentity identity) {
             addLog(new ProviderDeliverLocationEvent(provider, numLocations, identity));
@@ -668,4 +768,89 @@
             }
         }
     }
+
+    /**
+     * Aggregate statistics for GNSS measurements.
+     */
+    public static final class GnssMeasurementAggregateStats {
+        @GuardedBy("this")
+        private int mAddedRequestCount;
+        @GuardedBy("this")
+        private int mReceivedMeasurementEventCount;
+        @GuardedBy("this")
+        private long mAddedTimeTotalMs;
+        @GuardedBy("this")
+        private long mAddedTimeLastUpdateRealtimeMs;
+        @GuardedBy("this")
+        private long mFastestIntervalMs = Long.MAX_VALUE;
+        @GuardedBy("this")
+        private long mSlowestIntervalMs = 0;
+        @GuardedBy("this")
+        private boolean mHasFullTracking;
+        @GuardedBy("this")
+        private boolean mHasDutyCycling;
+
+        GnssMeasurementAggregateStats() {
+        }
+
+        synchronized void markRequestAdded(long intervalMillis, boolean fullTracking) {
+            if (mAddedRequestCount++ == 0) {
+                mAddedTimeLastUpdateRealtimeMs = SystemClock.elapsedRealtime();
+            }
+            if (fullTracking) {
+                mHasFullTracking = true;
+            } else {
+                mHasDutyCycling = true;
+            }
+            mFastestIntervalMs = min(intervalMillis, mFastestIntervalMs);
+            mSlowestIntervalMs = max(intervalMillis, mSlowestIntervalMs);
+        }
+
+        synchronized void markRequestRemoved() {
+            updateTotals();
+            --mAddedRequestCount;
+            Preconditions.checkState(mAddedRequestCount >= 0);
+        }
+
+        synchronized void markGnssMeasurementDelivered() {
+            mReceivedMeasurementEventCount++;
+        }
+
+        public synchronized void updateTotals() {
+            if (mAddedRequestCount > 0) {
+                long realtimeMs = SystemClock.elapsedRealtime();
+                mAddedTimeTotalMs += realtimeMs - mAddedTimeLastUpdateRealtimeMs;
+                mAddedTimeLastUpdateRealtimeMs = realtimeMs;
+            }
+        }
+
+        @Override
+        public synchronized String toString() {
+            return "min/max interval = "
+                    + intervalToString(mFastestIntervalMs) + "/"
+                    + intervalToString(mSlowestIntervalMs)
+                    + ", total duration = " + formatDuration(mAddedTimeTotalMs)
+                    + ", tracking mode = " + trackingModeToString() + ", GNSS measurement events = "
+                    + mReceivedMeasurementEventCount;
+        }
+
+        private static String intervalToString(long intervalMs) {
+            if (intervalMs == GnssMeasurementRequest.PASSIVE_INTERVAL) {
+                return "passive";
+            } else {
+                return MILLISECONDS.toSeconds(intervalMs) + "s";
+            }
+        }
+
+        @GuardedBy("this")
+        private String trackingModeToString() {
+            if (mHasFullTracking && mHasDutyCycling) {
+                return "mixed tracking mode";
+            } else if (mHasFullTracking) {
+                return "always full-tracking";
+            } else {
+                return "always duty-cycling";
+            }
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java b/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java
index 041f11d..d02b6f4 100644
--- a/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java
@@ -18,6 +18,7 @@
 
 import static android.app.AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION;
 
+import static com.android.server.location.eventlog.LocationEventLog.EVENT_LOG;
 import static com.android.server.location.gnss.GnssManagerService.D;
 import static com.android.server.location.gnss.GnssManagerService.TAG;
 
@@ -62,11 +63,17 @@
         @Override
         protected void onRegister() {
             super.onRegister();
-
+            EVENT_LOG.logGnssMeasurementClientRegistered(getIdentity(), getRequest());
             executeOperation(listener -> listener.onStatusChanged(
                     GnssMeasurementsEvent.Callback.STATUS_READY));
         }
 
+        @Override
+        protected void onUnregister() {
+            EVENT_LOG.logGnssMeasurementClientUnregistered(getIdentity());
+            super.onUnregister();
+        }
+
         @Nullable
         @Override
         protected void onActive() {
@@ -250,6 +257,8 @@
         deliverToListeners(registration -> {
             if (mAppOpsHelper.noteOpNoThrow(AppOpsManager.OP_FINE_LOCATION,
                     registration.getIdentity())) {
+                EVENT_LOG.logGnssMeasurementsDelivered(event.getMeasurements().size(),
+                        registration.getIdentity());
                 return listener -> listener.onGnssMeasurementsReceived(event);
             } else {
                 return null;
diff --git a/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java b/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
index c0cce6f..78c8cde 100644
--- a/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
+++ b/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
@@ -458,12 +458,12 @@
         // capabilities.
         capabilities = networkAttributes.mCapabilities;
         Log.i(TAG, String.format(
-                "updateNetworkState, state=%s, connected=%s, network=%s, capabilities=%s"
+                "updateNetworkState, state=%s, connected=%s, network=%s, capabilityFlags=%d"
                         + ", availableNetworkCount: %d",
                 agpsDataConnStateAsString(),
                 isConnected,
                 network,
-                capabilities,
+                NetworkAttributes.getCapabilityFlags(capabilities),
                 mAvailableNetworkAttributes.size()));
 
         if (native_is_agps_ril_supported()) {
diff --git a/services/core/java/com/android/server/location/gnss/NetworkTimeHelper.java b/services/core/java/com/android/server/location/gnss/NetworkTimeHelper.java
index f5114b7..01c108b 100644
--- a/services/core/java/com/android/server/location/gnss/NetworkTimeHelper.java
+++ b/services/core/java/com/android/server/location/gnss/NetworkTimeHelper.java
@@ -37,7 +37,7 @@
      * a platform bug. This switch will be removed in a future release. If there are problems with
      * the new impl we'd like to hear about them.
      */
-    static final boolean USE_TIME_DETECTOR_IMPL = false;
+    static final boolean USE_TIME_DETECTOR_IMPL = true;
 
     /**
      * The callback interface used by {@link NetworkTimeHelper} to report the time to {@link
diff --git a/services/core/java/com/android/server/location/injector/EmergencyHelper.java b/services/core/java/com/android/server/location/injector/EmergencyHelper.java
index be4bf50..10cf714 100644
--- a/services/core/java/com/android/server/location/injector/EmergencyHelper.java
+++ b/services/core/java/com/android/server/location/injector/EmergencyHelper.java
@@ -16,14 +16,55 @@
 
 package com.android.server.location.injector;
 
+import java.util.concurrent.CopyOnWriteArrayList;
+
 /**
  * Provides helpers for emergency sessions.
  */
 public abstract class EmergencyHelper {
 
+    private final CopyOnWriteArrayList<EmergencyStateChangedListener> mListeners;
+
+    protected EmergencyHelper() {
+        mListeners = new CopyOnWriteArrayList<>();
+    }
+
+    /**
+     * Listener for emergency state changes.
+     */
+    public interface EmergencyStateChangedListener {
+        /**
+         * Called when state changes.
+         */
+        void onStateChanged();
+    }
+
     /**
      * Returns true if the device is in an emergency session, or if an emergency session ended
      * within the given extension time.
      */
     public abstract boolean isInEmergency(long extensionTimeMs);
+
+    /**
+     * Add a listener for changes to the emergency location state.
+     */
+    public void addOnEmergencyStateChangedListener(EmergencyStateChangedListener listener) {
+        mListeners.add(listener);
+    }
+
+    /**
+     * Remove a listener for changes to the emergency location state.
+     */
+    public void removeOnEmergencyStateChangedListener(EmergencyStateChangedListener listener) {
+        mListeners.remove(listener);
+    }
+
+    /**
+     * Notify listeners for emergency state of state change
+     */
+    protected final void dispatchEmergencyStateChanged() {
+        for (EmergencyStateChangedListener listener : mListeners) {
+            listener.onStateChanged();
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/location/injector/Injector.java b/services/core/java/com/android/server/location/injector/Injector.java
index b2c8672..4a0c4b2 100644
--- a/services/core/java/com/android/server/location/injector/Injector.java
+++ b/services/core/java/com/android/server/location/injector/Injector.java
@@ -16,13 +16,11 @@
 
 package com.android.server.location.injector;
 
-import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.location.settings.LocationSettings;
 
 /**
  * Injects various location dependencies so that they may be controlled by tests.
  */
-@VisibleForTesting
 public interface Injector {
 
     /** Returns a UserInfoHelper. */
diff --git a/services/core/java/com/android/server/location/injector/LocationUsageLogger.java b/services/core/java/com/android/server/location/injector/LocationUsageLogger.java
index a9701b3..9319e89 100644
--- a/services/core/java/com/android/server/location/injector/LocationUsageLogger.java
+++ b/services/core/java/com/android/server/location/injector/LocationUsageLogger.java
@@ -129,6 +129,13 @@
         FrameworkStatsLog.write(FrameworkStatsLog.LOCATION_ENABLED_STATE_CHANGED, enabled);
     }
 
+    /**
+     * Log emergency location state change event
+     */
+    public synchronized void logEmergencyStateChanged(boolean isInEmergency) {
+        FrameworkStatsLog.write(FrameworkStatsLog.EMERGENCY_STATE_CHANGED, isInEmergency);
+    }
+
     private static int bucketizeProvider(String provider) {
         if (LocationManager.NETWORK_PROVIDER.equals(provider)) {
             return LocationStatsEnums.PROVIDER_NETWORK;
diff --git a/services/core/java/com/android/server/location/injector/SystemEmergencyHelper.java b/services/core/java/com/android/server/location/injector/SystemEmergencyHelper.java
index 1fb00ef..c772e08 100644
--- a/services/core/java/com/android/server/location/injector/SystemEmergencyHelper.java
+++ b/services/core/java/com/android/server/location/injector/SystemEmergencyHelper.java
@@ -27,6 +27,7 @@
 import android.telephony.TelephonyManager;
 import android.util.Log;
 
+import com.android.internal.telephony.TelephonyIntents;
 import com.android.server.FgThread;
 
 import java.util.Objects;
@@ -73,12 +74,25 @@
                     try {
                         mIsInEmergencyCall = mTelephonyManager.isEmergencyNumber(
                                 intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER));
+                        dispatchEmergencyStateChanged();
                     } catch (IllegalStateException e) {
                         Log.w(TAG, "Failed to call TelephonyManager.isEmergencyNumber().", e);
                     }
                 }
             }
         }, new IntentFilter(Intent.ACTION_NEW_OUTGOING_CALL));
+
+        mContext.registerReceiver(new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                if (!TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED.equals(
+                        intent.getAction())) {
+                    return;
+                }
+
+                dispatchEmergencyStateChanged();
+            }
+        }, new IntentFilter(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED));
     }
 
     @Override
@@ -108,6 +122,7 @@
                     if (mIsInEmergencyCall) {
                         mEmergencyCallEndRealtimeMs = SystemClock.elapsedRealtime();
                         mIsInEmergencyCall = false;
+                        dispatchEmergencyStateChanged();
                     }
                 }
             }
diff --git a/services/core/java/com/android/server/location/provider/LocationProviderManager.java b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
index 925ab65..d3eb5a9 100644
--- a/services/core/java/com/android/server/location/provider/LocationProviderManager.java
+++ b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
@@ -64,6 +64,7 @@
 import android.location.LocationManagerInternal.ProviderEnabledListener;
 import android.location.LocationRequest;
 import android.location.LocationResult;
+import android.location.altitude.AltitudeConverter;
 import android.location.provider.IProviderRequestListener;
 import android.location.provider.ProviderProperties;
 import android.location.provider.ProviderRequest;
@@ -81,6 +82,7 @@
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.os.WorkSource;
+import android.provider.DeviceConfig;
 import android.stats.location.LocationStatsEnums;
 import android.text.TextUtils;
 import android.util.ArraySet;
@@ -94,6 +96,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.Preconditions;
 import com.android.server.FgThread;
+import com.android.server.IoThread;
 import com.android.server.LocalServices;
 import com.android.server.location.LocationPermissions;
 import com.android.server.location.LocationPermissions.PermissionLevel;
@@ -122,6 +125,7 @@
 import com.android.server.location.settings.LocationUserSettings;
 
 import java.io.FileDescriptor;
+import java.io.IOException;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
@@ -886,6 +890,15 @@
 
                         @Override
                         public boolean test(Location location) {
+                            if (Double.isNaN(location.getLatitude()) || location.getLatitude() < -90
+                                    || location.getLatitude() > 90
+                                    || Double.isNaN(location.getLongitude())
+                                    || location.getLongitude() < -180
+                                    || location.getLongitude() > 180) {
+                                Log.e(TAG, mName + " provider registration " + getIdentity()
+                                        + " dropped delivery - invalid latitude or longitude.");
+                                return false;
+                            }
                             if (mPreviousLocation != null) {
                                 // check fastest interval
                                 long deltaMs = location.getElapsedRealtimeMillis()
@@ -1441,6 +1454,10 @@
     @GuardedBy("mMultiplexerLock")
     @Nullable private StateChangedListener mStateChangedListener;
 
+    /** Enables missing MSL altitudes to be added on behalf of the provider. */
+    private final AltitudeConverter mAltitudeConverter = new AltitudeConverter();
+    private volatile boolean mIsAltitudeConverterIdle = true;
+
     public LocationProviderManager(Context context, Injector injector,
             String name, @Nullable PassiveLocationProviderManager passiveManager) {
         this(context, injector, name, passiveManager, Collections.emptyList());
@@ -2512,33 +2529,18 @@
     @GuardedBy("mMultiplexerLock")
     @Override
     public void onReportLocation(LocationResult locationResult) {
-        LocationResult filtered;
+        LocationResult processed;
         if (mPassiveManager != null) {
-            filtered = locationResult.filter(location -> {
-                if (!location.isMock()) {
-                    if (location.getLatitude() == 0 && location.getLongitude() == 0) {
-                        Log.e(TAG, "blocking 0,0 location from " + mName + " provider");
-                        return false;
-                    }
-                }
-
-                if (!location.isComplete()) {
-                    Log.e(TAG, "blocking incomplete location from " + mName + " provider");
-                    return false;
-                }
-
-                return true;
-            });
-
-            if (filtered == null) {
+            processed = processReportedLocation(locationResult);
+            if (processed == null) {
                 return;
             }
 
             // don't log location received for passive provider because it's spammy
-            EVENT_LOG.logProviderReceivedLocations(mName, filtered.size());
+            EVENT_LOG.logProviderReceivedLocations(mName, processed.size());
         } else {
-            // passive provider should get already filtered results as input
-            filtered = locationResult;
+            // passive provider should get already processed results as input
+            processed = locationResult;
         }
 
         // check for non-monotonic locations if we're not the passive manager. the passive manager
@@ -2554,20 +2556,78 @@
         }
 
         // update last location
-        setLastLocation(filtered.getLastLocation(), UserHandle.USER_ALL);
+        setLastLocation(processed.getLastLocation(), UserHandle.USER_ALL);
 
         // attempt listener delivery
         deliverToListeners(registration -> {
-            return registration.acceptLocationChange(filtered);
+            return registration.acceptLocationChange(processed);
         });
 
         // notify passive provider
         if (mPassiveManager != null) {
-            mPassiveManager.updateLocation(filtered);
+            mPassiveManager.updateLocation(processed);
         }
     }
 
     @GuardedBy("mMultiplexerLock")
+    @Nullable
+    private LocationResult processReportedLocation(LocationResult locationResult) {
+        LocationResult processed = locationResult.filter(location -> {
+            if (!location.isMock()) {
+                if (location.getLatitude() == 0 && location.getLongitude() == 0) {
+                    Log.e(TAG, "blocking 0,0 location from " + mName + " provider");
+                    return false;
+                }
+            }
+
+            if (!location.isComplete()) {
+                Log.e(TAG, "blocking incomplete location from " + mName + " provider");
+                return false;
+            }
+
+            return true;
+        });
+        if (processed == null) {
+            return null;
+        }
+
+        // Attempt to add a missing MSL altitude on behalf of the provider.
+        if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_LOCATION,
+                "enable_location_provider_manager_msl", true)) {
+            return processed.map(location -> {
+                if (!location.hasMslAltitude() && location.hasAltitude()) {
+                    try {
+                        Location locationCopy = new Location(location);
+                        if (mAltitudeConverter.addMslAltitudeToLocation(locationCopy)) {
+                            return locationCopy;
+                        }
+                        // Only queue up one IO thread runnable.
+                        if (mIsAltitudeConverterIdle) {
+                            mIsAltitudeConverterIdle = false;
+                            IoThread.getExecutor().execute(() -> {
+                                try {
+                                    // Results added to the location copy are essentially discarded.
+                                    // We only rely on the side effect of loading altitude assets
+                                    // into the converter's memory cache.
+                                    mAltitudeConverter.addMslAltitudeToLocation(mContext,
+                                            locationCopy);
+                                } catch (IOException e) {
+                                    Log.e(TAG, "not loading MSL altitude assets: " + e);
+                                }
+                                mIsAltitudeConverterIdle = true;
+                            });
+                        }
+                    } catch (IllegalArgumentException e) {
+                        Log.e(TAG, "not adding MSL altitude to location: " + e);
+                    }
+                }
+                return location;
+            });
+        }
+        return processed;
+    }
+
+    @GuardedBy("mMultiplexerLock")
     private void onUserStarted(int userId) {
         if (userId == UserHandle.USER_NULL) {
             return;
diff --git a/services/core/java/com/android/server/locksettings/BiometricDeferredQueue.java b/services/core/java/com/android/server/locksettings/BiometricDeferredQueue.java
index 46f486d..f572845 100644
--- a/services/core/java/com/android/server/locksettings/BiometricDeferredQueue.java
+++ b/services/core/java/com/android/server/locksettings/BiometricDeferredQueue.java
@@ -311,7 +311,7 @@
 
     @Nullable
     private static synchronized IGateKeeperService getGatekeeperService() {
-        final IBinder service = ServiceManager.getService(Context.GATEKEEPER_SERVICE);
+        final IBinder service = ServiceManager.waitForService(Context.GATEKEEPER_SERVICE);
         if (service == null) {
             Slog.e(TAG, "Unable to acquire GateKeeperService");
             return null;
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 48d46df..b3ef869 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -31,7 +31,6 @@
 import static android.os.UserHandle.USER_SYSTEM;
 
 import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
-import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
 import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD_OR_PIN;
 import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PIN;
 import static com.android.internal.widget.LockPatternUtils.CURRENT_LSKF_BASED_PROTECTOR_ID_KEY;
@@ -40,8 +39,12 @@
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE;
 import static com.android.internal.widget.LockPatternUtils.USER_FRP;
+import static com.android.internal.widget.LockPatternUtils.USER_REPAIR_MODE;
 import static com.android.internal.widget.LockPatternUtils.VERIFY_FLAG_REQUEST_GK_PW_HANDLE;
+import static com.android.internal.widget.LockPatternUtils.VERIFY_FLAG_WRITE_REPAIR_MODE_PW;
 import static com.android.internal.widget.LockPatternUtils.frpCredentialEnabled;
+import static com.android.internal.widget.LockPatternUtils.isSpecialUserId;
+import static com.android.internal.widget.LockPatternUtils.pinOrPasswordQualityToCredentialType;
 import static com.android.internal.widget.LockPatternUtils.userOwnsFrpCredential;
 import static com.android.server.locksettings.SyntheticPasswordManager.TOKEN_TYPE_STRONG;
 import static com.android.server.locksettings.SyntheticPasswordManager.TOKEN_TYPE_WEAK;
@@ -115,7 +118,6 @@
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
-import android.util.EventLog;
 import android.util.Log;
 import android.util.LongSparseArray;
 import android.util.Slog;
@@ -278,8 +280,6 @@
     protected IGateKeeperService mGateKeeperService;
     protected IAuthSecret mAuthSecretService;
 
-    private static final String GSI_RUNNING_PROP = "ro.gsid.image_running";
-
     /**
      * The UIDs that are used for system credential storage in keystore.
      */
@@ -310,6 +310,8 @@
             super.onBootPhase(phase);
             if (phase == PHASE_ACTIVITY_MANAGER_READY) {
                 mLockSettingsService.migrateOldDataAfterSystemReady();
+                mLockSettingsService.deleteRepairModePersistentDataIfNeeded();
+            } else if (phase == PHASE_BOOT_COMPLETED) {
                 mLockSettingsService.loadEscrowData();
             }
         }
@@ -391,11 +393,14 @@
         if (mStorage.hasChildProfileLock(profileUserId)) {
             return;
         }
+        final UserInfo parent = mUserManager.getProfileParent(profileUserId);
+        if (parent == null) {
+            return;
+        }
         // If parent does not have a screen lock, simply clear credential from the profile,
         // to maintain the invariant that unified profile should always have the same secure state
         // as its parent.
-        final int parentId = mUserManager.getProfileParent(profileUserId).id;
-        if (!isUserSecure(parentId) && !profileUserPassword.isNone()) {
+        if (!isUserSecure(parent.id) && !profileUserPassword.isNone()) {
             Slogf.i(TAG, "Clearing password for profile user %d to match parent", profileUserId);
             setLockCredentialInternal(LockscreenCredential.createNone(), profileUserPassword,
                     profileUserId, /* isLockTiedToParent= */ true);
@@ -406,7 +411,7 @@
         // This can only happen during an upgrade path where SID is yet to be
         // generated when the user unlocks for the first time.
         try {
-            parentSid = getGateKeeperService().getSecureUserId(parentId);
+            parentSid = getGateKeeperService().getSecureUserId(parent.id);
             if (parentSid == 0) {
                 return;
             }
@@ -417,7 +422,7 @@
         try (LockscreenCredential unifiedProfilePassword = generateRandomProfilePassword()) {
             setLockCredentialInternal(unifiedProfilePassword, profileUserPassword, profileUserId,
                     /* isLockTiedToParent= */ true);
-            tieProfileLockToParent(profileUserId, parentId, unifiedProfilePassword);
+            tieProfileLockToParent(profileUserId, parent.id, unifiedProfilePassword);
             mManagedProfilePasswordCache.storePassword(profileUserId, unifiedProfilePassword,
                     parentSid);
         }
@@ -541,7 +546,7 @@
         }
 
         public boolean isGsiRunning() {
-            return SystemProperties.getInt(GSI_RUNNING_PROP, 0) > 0;
+            return LockPatternUtils.isGsiRunning();
         }
 
         public FingerprintManager getFingerprintManager() {
@@ -830,15 +835,11 @@
 
     @Override // binder interface
     public void systemReady() {
-        if (mContext.checkCallingOrSelfPermission(PERMISSION) != PERMISSION_GRANTED) {
-            EventLog.writeEvent(0x534e4554, "28251513", getCallingUid(), "");  // SafetyNet
-        }
         checkWritePermission();
 
         mHasSecureLockScreen = mContext.getPackageManager()
                 .hasSystemFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN);
         migrateOldData();
-        getGateKeeperService();
         getAuthSecretHal();
         mDeviceProvisionedObserver.onSystemReady();
 
@@ -953,6 +954,16 @@
         return success;
     }
 
+    @VisibleForTesting
+    void deleteRepairModePersistentDataIfNeeded() {
+        if (!LockPatternUtils.isRepairModeSupported(mContext)
+                || LockPatternUtils.isRepairModeActive(mContext)
+                || mInjector.isGsiRunning()) {
+            return;
+        }
+        mStorage.deleteRepairModePersistentData();
+    }
+
     // This is called when Weaver is guaranteed to be available (if the device supports Weaver).
     // It does any synthetic password related work that was delayed from earlier in the boot.
     private void onThirdPartyAppsStarted() {
@@ -1078,9 +1089,6 @@
     }
 
     private final void checkPasswordHavePermission() {
-        if (mContext.checkCallingOrSelfPermission(PERMISSION) != PERMISSION_GRANTED) {
-            EventLog.writeEvent(0x534e4554, "28251513", getCallingUid(), "");  // SafetyNet
-        }
         mContext.enforceCallingOrSelfPermission(PERMISSION, "LockSettingsHave");
     }
 
@@ -1283,8 +1291,8 @@
      * {@link #CREDENTIAL_TYPE_PASSWORD}
      */
     private int getCredentialTypeInternal(int userId) {
-        if (userId == USER_FRP) {
-            return getFrpCredentialType();
+        if (isSpecialUserId(userId)) {
+            return mSpManager.getSpecialUserCredentialType(userId);
         }
         synchronized (mSpManager) {
             final long protectorId = getCurrentLskfBasedProtectorId(userId);
@@ -1300,29 +1308,6 @@
         }
     }
 
-    private int getFrpCredentialType() {
-        PersistentData data = mStorage.readPersistentDataBlock();
-        if (data.type != PersistentData.TYPE_SP_GATEKEEPER &&
-                data.type != PersistentData.TYPE_SP_WEAVER) {
-            return CREDENTIAL_TYPE_NONE;
-        }
-        int credentialType = SyntheticPasswordManager.getFrpCredentialType(data.payload);
-        if (credentialType != CREDENTIAL_TYPE_PASSWORD_OR_PIN) {
-            return credentialType;
-        }
-        return pinOrPasswordQualityToCredentialType(data.qualityForUi);
-    }
-
-    private static int pinOrPasswordQualityToCredentialType(int quality) {
-        if (LockPatternUtils.isQualityAlphabeticPassword(quality)) {
-            return CREDENTIAL_TYPE_PASSWORD;
-        }
-        if (LockPatternUtils.isQualityNumericPin(quality)) {
-            return CREDENTIAL_TYPE_PIN;
-        }
-        throw new IllegalArgumentException("Quality is neither Pin nor password: " + quality);
-    }
-
     private boolean isUserSecure(int userId) {
         return getCredentialTypeInternal(userId) != CREDENTIAL_TYPE_NONE;
     }
@@ -1564,8 +1549,8 @@
      * unlock operation.
      */
     private void sendCredentialsOnUnlockIfRequired(LockscreenCredential credential, int userId) {
-        // Don't send credentials during the factory reset protection flow.
-        if (userId == USER_FRP) {
+        // Don't send credentials during the special user flow.
+        if (isSpecialUserId(userId)) {
             return;
         }
 
@@ -1646,6 +1631,7 @@
                                 + PERMISSION);
             }
         }
+        credential.validateBasicRequirements();
 
         final long identity = Binder.clearCallingIdentity();
         try {
@@ -1744,10 +1730,6 @@
     }
 
     private void onPostPasswordChanged(LockscreenCredential newCredential, int userHandle) {
-        if (newCredential.isPattern()) {
-            setBoolean(LockPatternUtils.PATTERN_EVER_CHOSEN_KEY, true, userHandle);
-        }
-
         updatePasswordHistory(newCredential, userHandle);
         mContext.getSystemService(TrustManager.class).reportEnabledTrustAgentsChanged(userHandle);
     }
@@ -2189,15 +2171,19 @@
             Slog.e(TAG, "FRP credential can only be verified prior to provisioning.");
             return VerifyCredentialResponse.ERROR;
         }
+        if (userId == USER_REPAIR_MODE && !LockPatternUtils.isRepairModeActive(mContext)) {
+            Slog.e(TAG, "Repair mode is not active on the device.");
+            return VerifyCredentialResponse.ERROR;
+        }
         Slogf.i(TAG, "Verifying lockscreen credential for user %d", userId);
 
         final AuthenticationResult authResult;
         VerifyCredentialResponse response;
 
         synchronized (mSpManager) {
-            if (userId == USER_FRP) {
-                return mSpManager.verifyFrpCredential(getGateKeeperService(), credential,
-                        progressCallback);
+            if (isSpecialUserId(userId)) {
+                return mSpManager.verifySpecialUserCredential(userId, getGateKeeperService(),
+                        credential, progressCallback);
             }
 
             long protectorId = getCurrentLskfBasedProtectorId(userId);
@@ -2206,20 +2192,15 @@
             response = authResult.gkResponse;
 
             if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
+                if ((flags & VERIFY_FLAG_WRITE_REPAIR_MODE_PW) != 0) {
+                    if (!mSpManager.writeRepairModeCredentialLocked(protectorId, userId)) {
+                        Slog.e(TAG, "Failed to write repair mode credential");
+                        return VerifyCredentialResponse.ERROR;
+                    }
+                }
                 // credential has matched
                 mBiometricDeferredQueue.addPendingLockoutResetForUser(userId,
                         authResult.syntheticPassword.deriveGkPassword());
-
-                // perform verifyChallenge with synthetic password which generates the real GK auth
-                // token and response for the current user
-                response = mSpManager.verifyChallenge(getGateKeeperService(),
-                        authResult.syntheticPassword, 0L /* challenge */, userId);
-                if (response.getResponseCode() != VerifyCredentialResponse.RESPONSE_OK) {
-                    // This shouldn't really happen: the unwrapping of SP succeeds, but SP doesn't
-                    // match the recorded GK password handle.
-                    Slog.wtf(TAG, "verifyChallenge with SP failed.");
-                    return VerifyCredentialResponse.ERROR;
-                }
             }
         }
         if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
@@ -2615,7 +2596,7 @@
             return mGateKeeperService;
         }
 
-        final IBinder service = ServiceManager.getService(Context.GATEKEEPER_SERVICE);
+        final IBinder service = ServiceManager.waitForService(Context.GATEKEEPER_SERVICE);
         if (service != null) {
             try {
                 service.linkToDeath(new GateKeeperDiedRecipient(), 0);
@@ -2853,7 +2834,7 @@
      *
      * Also maintains the invariants described in {@link SyntheticPasswordManager} by
      * setting/clearing the protection (by the SP) on the user's auth-bound Keystore keys when the
-     * LSKF is added/removed, respectively.  If the new LSKF is nonempty, then the Gatekeeper auth
+     * LSKF is added/removed, respectively.  If an LSKF is being added, then the Gatekeeper auth
      * token is also refreshed.
      */
     @GuardedBy("mSpManager")
@@ -2870,9 +2851,7 @@
             // not needed by synchronizeUnifiedWorkChallengeForProfiles()
             profilePasswords = null;
 
-            if (mSpManager.hasSidForUser(userId)) {
-                mSpManager.verifyChallenge(getGateKeeperService(), sp, 0L, userId);
-            } else {
+            if (!mSpManager.hasSidForUser(userId)) {
                 mSpManager.newSidForUser(getGateKeeperService(), sp, userId);
                 mSpManager.verifyChallenge(getGateKeeperService(), sp, 0L, userId);
                 setKeystorePassword(sp.deriveKeyStorePassword(), userId);
@@ -3088,6 +3067,7 @@
     private boolean setLockCredentialWithToken(LockscreenCredential credential, long tokenHandle,
             byte[] token, int userId) {
         boolean result;
+        credential.validateBasicRequirements();
         synchronized (mSpManager) {
             if (!mSpManager.hasEscrowData(userId)) {
                 throw new SecurityException("Escrow token is disabled on the current user");
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java b/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java
index f107d0b..df95c69 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java
@@ -16,8 +16,6 @@
 
 package com.android.server.locksettings;
 
-import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
-import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
 
 import android.app.ActivityManager;
@@ -313,16 +311,8 @@
                 mLockPatternUtils.getRequestedPasswordMetrics(mCurrentUserId);
         final int requiredComplexity =
                 mLockPatternUtils.getRequestedPasswordComplexity(mCurrentUserId);
-        final List<PasswordValidationError> errors;
-        if (credential.isPassword() || credential.isPin()) {
-            errors = PasswordMetrics.validatePassword(requiredMetrics, requiredComplexity,
-                    credential.isPin(), credential.getCredential());
-        } else {
-            PasswordMetrics metrics = new PasswordMetrics(
-                    credential.isPattern() ? CREDENTIAL_TYPE_PATTERN : CREDENTIAL_TYPE_NONE);
-            errors = PasswordMetrics.validatePasswordMetrics(
-                    requiredMetrics, requiredComplexity, metrics);
-        }
+        final List<PasswordValidationError> errors =
+                PasswordMetrics.validateCredential(requiredMetrics, requiredComplexity, credential);
         if (!errors.isEmpty()) {
             getOutPrintWriter().println(
                     "New credential doesn't satisfy admin policies: " + errors.get(0));
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
index 2fa637e..1c5ecb7 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
@@ -19,7 +19,7 @@
 import static android.content.Context.USER_SERVICE;
 
 import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
-import static com.android.internal.widget.LockPatternUtils.USER_FRP;
+import static com.android.internal.widget.LockPatternUtils.isSpecialUserId;
 
 import android.annotation.Nullable;
 import android.app.admin.DevicePolicyManager;
@@ -90,6 +90,9 @@
 
     private static final String SYNTHETIC_PASSWORD_DIRECTORY = "spblob/";
 
+    private static final String REPAIR_MODE_DIRECTORY = "repair-mode/";
+    private static final String REPAIR_MODE_PERSISTENT_FILE = "pst";
+
     private static final Object DEFAULT = new Object();
 
     private static final String[] SETTINGS_TO_BACKUP = new String[] {
@@ -390,6 +393,29 @@
         }
     }
 
+    @VisibleForTesting
+    File getRepairModePersistentDataFile() {
+        final File directory = new File(Environment.getMetadataDirectory(), REPAIR_MODE_DIRECTORY);
+        return new File(directory, REPAIR_MODE_PERSISTENT_FILE);
+    }
+
+    public PersistentData readRepairModePersistentData() {
+        final byte[] data = readFile(getRepairModePersistentDataFile());
+        if (data == null) {
+            return PersistentData.NONE;
+        }
+        return PersistentData.fromBytes(data);
+    }
+
+    public void writeRepairModePersistentData(int persistentType, int userId, byte[] payload) {
+        writeFile(getRepairModePersistentDataFile(),
+                PersistentData.toBytes(persistentType, userId, /* qualityForUi= */0, payload));
+    }
+
+    public void deleteRepairModePersistentData() {
+        deleteFile(getRepairModePersistentDataFile());
+    }
+
     /**
      * Writes the synthetic password state file for the given user ID, protector ID, and state name.
      * If the file already exists, then it is atomically replaced.
@@ -510,7 +536,8 @@
     }
 
     public void setString(String key, String value, int userId) {
-        Preconditions.checkArgument(userId != USER_FRP, "cannot store lock settings for FRP user");
+        Preconditions.checkArgument(!isSpecialUserId(userId),
+                "cannot store lock settings for special user: %d", userId);
 
         writeKeyValue(key, value, userId);
         if (ArrayUtils.contains(SETTINGS_TO_BACKUP, key)) {
@@ -535,7 +562,7 @@
     }
 
     public String getString(String key, String defaultValue, int userId) {
-        if (userId == USER_FRP) {
+        if (isSpecialUserId(userId)) {
             return null;
         }
         return readKeyValue(key, defaultValue, userId);
@@ -583,6 +610,17 @@
         }
     }
 
+    /**
+     * Provides a concrete data structure to represent the minimal information from
+     * a user's LSKF-based SP protector that is needed to verify the user's LSKF,
+     * in combination with the corresponding Gatekeeper enrollment or Weaver slot.
+     * It can be stored in {@link com.android.server.PersistentDataBlockService} for
+     * FRP to live across factory resets not initiated via the Settings UI.
+     * Written to {@link #REPAIR_MODE_PERSISTENT_FILE} to support verification for
+     * exiting repair mode, since the device runs with an empty data partition in
+     * repair mode and the same credential be provided to exit repair mode is
+     * required.
+     */
     public static class PersistentData {
         static final byte VERSION_1 = 1;
         static final int VERSION_1_HEADER_SIZE = 1 + 1 + 4 + 4;
@@ -685,6 +723,19 @@
             }
             pw.decreaseIndent();
         }
+        // Dump repair mode file states
+        final File repairModeFile = getRepairModePersistentDataFile();
+        if (repairModeFile.exists()) {
+            pw.println(TextUtils.formatSimple("Repair Mode [%s]:", repairModeFile.getParent()));
+            pw.increaseIndent();
+            pw.println(TextUtils.formatSimple("%6d %s %s", repairModeFile.length(),
+                    LockSettingsService.timestampToString(repairModeFile.lastModified()),
+                    repairModeFile.getName()));
+            final PersistentData data = readRepairModePersistentData();
+            pw.println(TextUtils.formatSimple("type: %d, user id: %d, payload size: %d",
+                    data.type, data.userId, data.payload != null ? data.payload.length : 0));
+            pw.decreaseIndent();
+        }
     }
 
     static class DatabaseHelper extends SQLiteOpenHelper {
diff --git a/services/core/java/com/android/server/locksettings/OWNERS b/services/core/java/com/android/server/locksettings/OWNERS
index 55b0cff..5d49863 100644
--- a/services/core/java/com/android/server/locksettings/OWNERS
+++ b/services/core/java/com/android/server/locksettings/OWNERS
@@ -1,3 +1,4 @@
+# Bug component: 1333694
 ebiggers@google.com
 jaggies@google.com
 rubinxu@google.com
diff --git a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
index 66f862a..d700c6a 100644
--- a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
+++ b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
@@ -16,9 +16,14 @@
 
 package com.android.server.locksettings;
 
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD_OR_PIN;
 import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PIN;
 import static com.android.internal.widget.LockPatternUtils.EscrowTokenStateChangeCallback;
 import static com.android.internal.widget.LockPatternUtils.PIN_LENGTH_UNAVAILABLE;
+import static com.android.internal.widget.LockPatternUtils.USER_FRP;
+import static com.android.internal.widget.LockPatternUtils.USER_REPAIR_MODE;
+import static com.android.internal.widget.LockPatternUtils.pinOrPasswordQualityToCredentialType;
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
@@ -31,6 +36,7 @@
 import android.hardware.weaver.WeaverConfig;
 import android.hardware.weaver.WeaverReadResponse;
 import android.hardware.weaver.WeaverReadStatus;
+import android.os.IBinder;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.os.ServiceManager;
@@ -500,7 +506,7 @@
 
     private final Context mContext;
     private LockSettingsStorage mStorage;
-    private IWeaver mWeaver;
+    private volatile IWeaver mWeaver;
     private WeaverConfig mWeaverConfig;
     private PasswordSlotManager mPasswordSlotManager;
 
@@ -531,13 +537,33 @@
         }
     }
 
-    private IWeaver getWeaverService() {
+    private class WeaverDiedRecipient implements IBinder.DeathRecipient {
+        // Not synchronized on the outer class, since setting the pointer to null is atomic, and we
+        // don't want to have to worry about any sort of deadlock here.
+        @Override
+        public void binderDied() {
+            // Weaver died.  Try to recover by setting mWeaver to null, which makes
+            // getWeaverService() look up the service again.  This is done only as a simple
+            // robustness measure; it should not be relied on.  If this triggers, the root cause is
+            // almost certainly a bug in the device's Weaver implementation, which must be fixed.
+            Slog.wtf(TAG, "Weaver service has died");
+            mWeaver.asBinder().unlinkToDeath(this, 0);
+            mWeaver = null;
+        }
+    }
+
+    private @Nullable IWeaver getWeaverServiceInternal() {
         // Try to get the AIDL service first
         try {
             IWeaver aidlWeaver = IWeaver.Stub.asInterface(
                     ServiceManager.waitForDeclaredService(IWeaver.DESCRIPTOR + "/default"));
             if (aidlWeaver != null) {
                 Slog.i(TAG, "Using AIDL weaver service");
+                try {
+                    aidlWeaver.asBinder().linkToDeath(new WeaverDiedRecipient(), 0);
+                } catch (RemoteException e) {
+                    Slog.w(TAG, "Unable to register Weaver death recipient", e);
+                }
                 return aidlWeaver;
             }
         } catch (SecurityException e) {
@@ -563,15 +589,20 @@
         return LockPatternUtils.isAutoPinConfirmFeatureAvailable();
     }
 
-    private synchronized boolean isWeaverAvailable() {
-        if (mWeaver != null) {
-            return true;
+    /**
+     * Returns a handle to the Weaver service, or null if Weaver is unavailable.  Note that not all
+     * devices support Weaver.
+     */
+    private synchronized @Nullable IWeaver getWeaverService() {
+        IWeaver weaver = mWeaver;
+        if (weaver != null) {
+            return weaver;
         }
 
         // Re-initialize weaver in case there was a transient error preventing access to it.
-        IWeaver weaver = getWeaverService();
+        weaver = getWeaverServiceInternal();
         if (weaver == null) {
-            return false;
+            return null;
         }
 
         final WeaverConfig weaverConfig;
@@ -579,19 +610,18 @@
             weaverConfig = weaver.getConfig();
         } catch (RemoteException | ServiceSpecificException e) {
             Slog.e(TAG, "Failed to get weaver config", e);
-            return false;
+            return null;
         }
         if (weaverConfig == null || weaverConfig.slots <= 0) {
             Slog.e(TAG, "Invalid weaver config");
-            return false;
+            return null;
         }
 
         mWeaver = weaver;
         mWeaverConfig = weaverConfig;
         mPasswordSlotManager.refreshActiveSlots(getUsedWeaverSlots());
         Slog.i(TAG, "Weaver service initialized");
-
-        return true;
+        return weaver;
     }
 
     /**
@@ -601,7 +631,7 @@
      *
      * @return the value stored in the weaver slot, or null if the operation fails
      */
-    private byte[] weaverEnroll(int slot, byte[] key, @Nullable byte[] value) {
+    private byte[] weaverEnroll(IWeaver weaver, int slot, byte[] key, @Nullable byte[] value) {
         if (slot == INVALID_WEAVER_SLOT || slot >= mWeaverConfig.slots) {
             throw new IllegalArgumentException("Invalid slot for weaver");
         }
@@ -614,7 +644,7 @@
             value = SecureRandomUtils.randomBytes(mWeaverConfig.valueSize);
         }
         try {
-            mWeaver.write(slot, key, value);
+            weaver.write(slot, key, value);
         } catch (RemoteException e) {
             Slog.e(TAG, "weaver write binder call failed, slot: " + slot, e);
             return null;
@@ -643,7 +673,7 @@
      * the verification is successful, throttled or failed. If successful, the bound secret
      * is also returned.
      */
-    private VerifyCredentialResponse weaverVerify(int slot, byte[] key) {
+    private VerifyCredentialResponse weaverVerify(IWeaver weaver, int slot, byte[] key) {
         if (slot == INVALID_WEAVER_SLOT || slot >= mWeaverConfig.slots) {
             throw new IllegalArgumentException("Invalid slot for weaver");
         }
@@ -654,7 +684,7 @@
         }
         final WeaverReadResponse readResponse;
         try {
-            readResponse = mWeaver.read(slot, key);
+            readResponse = weaver.read(slot, key);
         } catch (RemoteException e) {
             Slog.e(TAG, "weaver read failed, slot: " + slot, e);
             return VerifyCredentialResponse.ERROR;
@@ -718,11 +748,30 @@
         return PasswordData.fromBytes(passwordData).credentialType;
     }
 
-    static int getFrpCredentialType(byte[] payload) {
-        if (payload == null) {
+    int getSpecialUserCredentialType(int userId) {
+        final PersistentData data = getSpecialUserPersistentData(userId);
+        if (data.type != PersistentData.TYPE_SP_GATEKEEPER
+                && data.type != PersistentData.TYPE_SP_WEAVER) {
+            return CREDENTIAL_TYPE_NONE;
+        }
+        if (data.payload == null) {
             return LockPatternUtils.CREDENTIAL_TYPE_NONE;
         }
-        return PasswordData.fromBytes(payload).credentialType;
+        final int credentialType = PasswordData.fromBytes(data.payload).credentialType;
+        if (credentialType != CREDENTIAL_TYPE_PASSWORD_OR_PIN) {
+            return credentialType;
+        }
+        return pinOrPasswordQualityToCredentialType(data.qualityForUi);
+    }
+
+    private PersistentData getSpecialUserPersistentData(int userId) {
+        if (userId == USER_FRP) {
+            return mStorage.readPersistentDataBlock();
+        }
+        if (userId == USER_REPAIR_MODE) {
+            return mStorage.readRepairModePersistentData();
+        }
+        throw new IllegalArgumentException("Unknown special user id " + userId);
     }
 
     /**
@@ -846,14 +895,15 @@
         int slot = loadWeaverSlot(protectorId, userId);
         destroyState(WEAVER_SLOT_NAME, protectorId, userId);
         if (slot != INVALID_WEAVER_SLOT) {
-            if (!isWeaverAvailable()) {
+            final IWeaver weaver = getWeaverService();
+            if (weaver == null) {
                 Slog.e(TAG, "Cannot erase Weaver slot because Weaver is unavailable");
                 return;
             }
             Set<Integer> usedSlots = getUsedWeaverSlots();
             if (!usedSlots.contains(slot)) {
                 Slogf.i(TAG, "Erasing Weaver slot %d", slot);
-                weaverEnroll(slot, null, null);
+                weaverEnroll(weaver, slot, null, null);
                 mPasswordSlotManager.markSlotDeleted(slot);
             } else {
                 Slogf.i(TAG, "Weaver slot %d was already reused; not erasing it", slot);
@@ -931,13 +981,14 @@
 
         Slogf.i(TAG, "Creating LSKF-based protector %016x for user %d", protectorId, userId);
 
-        if (isWeaverAvailable()) {
+        final IWeaver weaver = getWeaverService();
+        if (weaver != null) {
             // Weaver is available, so make the protector use it to verify the LSKF.  Do this even
             // if the LSKF is empty, as that gives us support for securely deleting the protector.
             int weaverSlot = getNextAvailableWeaverSlot();
             Slogf.i(TAG, "Enrolling LSKF for user %d into Weaver slot %d", userId, weaverSlot);
-            byte[] weaverSecret = weaverEnroll(weaverSlot, stretchedLskfToWeaverKey(stretchedLskf),
-                    null);
+            byte[] weaverSecret = weaverEnroll(weaver, weaverSlot,
+                    stretchedLskfToWeaverKey(stretchedLskf), null);
             if (weaverSecret == null) {
                 throw new IllegalStateException(
                         "Fail to enroll user password under weaver " + userId);
@@ -1005,10 +1056,10 @@
         return sizeOfCredential;
     }
 
-    public VerifyCredentialResponse verifyFrpCredential(IGateKeeperService gatekeeper,
-            LockscreenCredential userCredential,
+    public VerifyCredentialResponse verifySpecialUserCredential(int sourceUserId,
+            IGateKeeperService gatekeeper, LockscreenCredential userCredential,
             ICheckCredentialProgressCallback progressCallback) {
-        PersistentData persistentData = mStorage.readPersistentDataBlock();
+        final PersistentData persistentData = getSpecialUserPersistentData(sourceUserId);
         if (persistentData.type == PersistentData.TYPE_SP_GATEKEEPER) {
             PasswordData pwd = PasswordData.fromBytes(persistentData.payload);
             byte[] stretchedLskf = stretchLskf(userCredential, pwd);
@@ -1019,20 +1070,22 @@
                         0 /* challenge */, pwd.passwordHandle,
                         stretchedLskfToGkPassword(stretchedLskf));
             } catch (RemoteException e) {
-                Slog.e(TAG, "FRP verifyChallenge failed", e);
+                Slog.e(TAG, "Persistent data credential verifyChallenge failed", e);
                 return VerifyCredentialResponse.ERROR;
             }
             return VerifyCredentialResponse.fromGateKeeperResponse(response);
         } else if (persistentData.type == PersistentData.TYPE_SP_WEAVER) {
-            if (!isWeaverAvailable()) {
-                Slog.e(TAG, "No weaver service to verify SP-based FRP credential");
+            final IWeaver weaver = getWeaverService();
+            if (weaver == null) {
+                Slog.e(TAG, "No weaver service to verify SP-based persistent data credential");
                 return VerifyCredentialResponse.ERROR;
             }
             PasswordData pwd = PasswordData.fromBytes(persistentData.payload);
             byte[] stretchedLskf = stretchLskf(userCredential, pwd);
             int weaverSlot = persistentData.userId;
 
-            return weaverVerify(weaverSlot, stretchedLskfToWeaverKey(stretchedLskf)).stripPayload();
+            return weaverVerify(weaver, weaverSlot,
+                    stretchedLskfToWeaverKey(stretchedLskf)).stripPayload();
         } else {
             Slog.e(TAG, "persistentData.type must be TYPE_SP_GATEKEEPER or TYPE_SP_WEAVER, but is "
                     + persistentData.type);
@@ -1114,6 +1167,57 @@
         }
     }
 
+    /**
+     * Writes the user's synthetic password data to the repair mode file.
+     *
+     * @param protectorId current LSKF based protectorId
+     * @param userId user id of the user
+     */
+    public boolean writeRepairModeCredentialLocked(long protectorId, int userId) {
+        if (!shouldWriteRepairModeCredential(userId)) {
+            return false;
+        }
+        final byte[] data = loadState(PASSWORD_DATA_NAME, protectorId, userId);
+        if (data == null) {
+            Slogf.w(TAG, "Password data not found for user %d", userId);
+            return false;
+        }
+        final PasswordData pwd = PasswordData.fromBytes(data);
+        if (isNoneCredential(pwd)) {
+            Slogf.w(TAG, "User %d has NONE credential", userId);
+            return false;
+        }
+        Slogf.d(TAG, "Writing repair mode credential tied to user %d", userId);
+        final int weaverSlot = loadWeaverSlot(protectorId, userId);
+        if (weaverSlot != INVALID_WEAVER_SLOT) {
+            // write weaver password
+            mStorage.writeRepairModePersistentData(
+                    PersistentData.TYPE_SP_WEAVER, weaverSlot, pwd.toBytes());
+        } else {
+            // write gatekeeper password
+            mStorage.writeRepairModePersistentData(
+                    PersistentData.TYPE_SP_GATEKEEPER, userId, pwd.toBytes());
+        }
+        return true;
+    }
+
+    private boolean shouldWriteRepairModeCredential(int userId) {
+        final UserInfo userInfo = mUserManager.getUserInfo(userId);
+        if (!LockPatternUtils.canUserEnterRepairMode(mContext, userInfo)) {
+            Slogf.w(TAG, "User %d can't enter repair mode", userId);
+            return false;
+        }
+        if (LockPatternUtils.isRepairModeActive(mContext)) {
+            Slog.w(TAG, "Can't write repair mode credential while repair mode is already active");
+            return false;
+        }
+        if (LockPatternUtils.isGsiRunning()) {
+            Slog.w(TAG, "Can't write repair mode credential while GSI is running");
+            return false;
+        }
+        return true;
+    }
+
     private ArrayMap<Integer, ArrayMap<Long, TokenData>> tokenMap = new ArrayMap<>();
 
     /**
@@ -1134,7 +1238,7 @@
         TokenData tokenData = new TokenData();
         tokenData.mType = type;
         final byte[] secdiscardable = SecureRandomUtils.randomBytes(SECDISCARDABLE_LENGTH);
-        if (isWeaverAvailable()) {
+        if (getWeaverService() != null) {
             tokenData.weaverSecret = SecureRandomUtils.randomBytes(mWeaverConfig.valueSize);
             tokenData.secdiscardableOnDisk = SyntheticPasswordCrypto.encrypt(tokenData.weaverSecret,
                             PERSONALIZATION_WEAVER_TOKEN, secdiscardable);
@@ -1177,10 +1281,11 @@
             return false;
         }
         Slogf.i(TAG, "Creating token-based protector %016x for user %d", tokenHandle, userId);
-        if (isWeaverAvailable()) {
+        final IWeaver weaver = getWeaverService();
+        if (weaver != null) {
             int slot = getNextAvailableWeaverSlot();
             Slogf.i(TAG, "Using Weaver slot %d for new token-based protector", slot);
-            if (weaverEnroll(slot, null, tokenData.weaverSecret) == null) {
+            if (weaverEnroll(weaver, slot, null, tokenData.weaverSecret) == null) {
                 Slog.e(TAG, "Failed to enroll weaver secret when activating token");
                 return false;
             }
@@ -1269,12 +1374,14 @@
         int weaverSlot = loadWeaverSlot(protectorId, userId);
         if (weaverSlot != INVALID_WEAVER_SLOT) {
             // Protector uses Weaver to verify the LSKF
-            if (!isWeaverAvailable()) {
+            final IWeaver weaver = getWeaverService();
+            if (weaver == null) {
                 Slog.e(TAG, "Protector uses Weaver, but Weaver is unavailable");
                 result.gkResponse = VerifyCredentialResponse.ERROR;
                 return result;
             }
-            result.gkResponse = weaverVerify(weaverSlot, stretchedLskfToWeaverKey(stretchedLskf));
+            result.gkResponse = weaverVerify(weaver, weaverSlot,
+                    stretchedLskfToWeaverKey(stretchedLskf));
             if (result.gkResponse.getResponseCode() != VerifyCredentialResponse.RESPONSE_OK) {
                 return result;
             }
@@ -1442,12 +1549,13 @@
         }
         int slotId = loadWeaverSlot(protectorId, userId);
         if (slotId != INVALID_WEAVER_SLOT) {
-            if (!isWeaverAvailable()) {
+            final IWeaver weaver = getWeaverService();
+            if (weaver == null) {
                 Slog.e(TAG, "Protector uses Weaver, but Weaver is unavailable");
                 result.gkResponse = VerifyCredentialResponse.ERROR;
                 return result;
             }
-            VerifyCredentialResponse response = weaverVerify(slotId, null);
+            VerifyCredentialResponse response = weaverVerify(weaver, slotId, null);
             if (response.getResponseCode() != VerifyCredentialResponse.RESPONSE_OK ||
                     response.getGatekeeperHAT() == null) {
                 Slog.e(TAG,
@@ -1576,8 +1684,10 @@
             }
             return result;
         } else if (responseCode == GateKeeperResponse.RESPONSE_RETRY) {
+            Slog.e(TAG, "Gatekeeper verification of synthetic password failed with RESPONSE_RETRY");
             return VerifyCredentialResponse.fromTimeout(response.getTimeout());
         } else {
+            Slog.e(TAG, "Gatekeeper verification of synthetic password failed with RESPONSE_ERROR");
             return VerifyCredentialResponse.ERROR;
         }
     }
@@ -1600,7 +1710,7 @@
     /** Destroy all weak token-based SP protectors for the given user. */
     public void destroyAllWeakTokenBasedProtectors(int userId) {
         List<Long> protectorIds =
-            mStorage.listSyntheticPasswordProtectorsForUser(SECDISCARDABLE_NAME, userId);
+            mStorage.listSyntheticPasswordProtectorsForUser(SP_BLOB_NAME, userId);
         for (long protectorId : protectorIds) {
             SyntheticPasswordBlob blob = SyntheticPasswordBlob.fromBytes(loadState(SP_BLOB_NAME,
                     protectorId, userId));
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java
index 7009a41..ce3fb85 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java
@@ -166,6 +166,7 @@
      * @param userId The ID of the user to whose lock screen the platform key must be bound.
      * @throws NoSuchAlgorithmException if AES is unavailable - should never happen.
      * @throws KeyStoreException if there is an error in AndroidKeyStore.
+     * @throws InsecureUserException if the user does not have a lock screen set.
      * @throws IOException if there was an issue with local database update.
      * @throws RemoteException if there was an issue communicating with {@link IGateKeeperService}.
      *
@@ -174,7 +175,7 @@
     @VisibleForTesting
     void regenerate(int userId)
             throws NoSuchAlgorithmException, KeyStoreException, IOException,
-                    RemoteException {
+                    RemoteException, InsecureUserException {
         int generationId = getGenerationId(userId);
         int nextId;
         if (generationId == -1) {
@@ -195,13 +196,14 @@
      * @throws UnrecoverableKeyException if the key could not be recovered.
      * @throws NoSuchAlgorithmException if AES is unavailable - should never occur.
      * @throws IOException if there was an issue with local database update.
+     * @throws InsecureUserException if the user does not have a lock screen set.
      * @throws RemoteException if there was an issue communicating with {@link IGateKeeperService}.
      *
      * @hide
      */
     public PlatformEncryptionKey getEncryptKey(int userId)
             throws KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException,
-                    IOException, RemoteException {
+                    IOException, RemoteException, InsecureUserException {
         init(userId);
         try {
             // Try to see if the decryption key is still accessible before using the encryption key.
@@ -254,7 +256,7 @@
      */
     public PlatformDecryptionKey getDecryptKey(int userId)
             throws KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException,
-                    IOException, RemoteException {
+                    IOException, InsecureUserException, RemoteException {
         init(userId);
         try {
             PlatformDecryptionKey decryptionKey = getDecryptKeyInternal(userId);
@@ -328,7 +330,7 @@
      */
     void init(int userId)
             throws KeyStoreException, NoSuchAlgorithmException, IOException,
-                    RemoteException {
+                    RemoteException, InsecureUserException {
         int generationId = getGenerationId(userId);
         if (isKeyLoaded(userId, generationId)) {
             Log.i(TAG, String.format(
@@ -414,7 +416,8 @@
      * @throws RemoteException if there was an issue communicating with {@link IGateKeeperService}.
      */
     private void generateAndLoadKey(int userId, int generationId)
-            throws NoSuchAlgorithmException, KeyStoreException, IOException, RemoteException {
+            throws NoSuchAlgorithmException, KeyStoreException, IOException, RemoteException,
+                InsecureUserException {
         String encryptAlias = getEncryptAlias(userId, generationId);
         String decryptAlias = getDecryptAlias(userId, generationId);
         // SecretKey implementation doesn't provide reliable way to destroy the secret
@@ -427,23 +430,31 @@
                     .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE);
         // Skip UserAuthenticationRequired for main user
         if (userId ==  UserHandle.USER_SYSTEM) {
+            // attempt to store key will fail if screenlock is not set.
             decryptionKeyProtection.setUnlockedDeviceRequired(true);
         } else {
             // Don't set protection params to prevent losing key.
         }
         // Store decryption key first since it is more likely to fail.
-        mKeyStore.setEntry(
-                decryptAlias,
-                new KeyStore.SecretKeyEntry(secretKey),
-                decryptionKeyProtection.build());
-        mKeyStore.setEntry(
-                encryptAlias,
-                new KeyStore.SecretKeyEntry(secretKey),
-                new KeyProtection.Builder(KeyProperties.PURPOSE_ENCRYPT)
-                    .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
-                    .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
-                    .build());
-
+        try {
+            mKeyStore.setEntry(
+                    decryptAlias,
+                    new KeyStore.SecretKeyEntry(secretKey),
+                    decryptionKeyProtection.build());
+            mKeyStore.setEntry(
+                    encryptAlias,
+                    new KeyStore.SecretKeyEntry(secretKey),
+                    new KeyProtection.Builder(KeyProperties.PURPOSE_ENCRYPT)
+                        .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
+                        .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
+                        .build());
+        } catch (KeyStoreException e) {
+            if (!isDeviceSecure(userId)) {
+                throw new InsecureUserException("Screenlock is not set");
+            } else {
+                throw e;
+            }
+        }
         setGenerationId(userId, generationId);
     }
 
@@ -477,4 +488,8 @@
         return keyStore;
     }
 
+    private boolean isDeviceSecure(int userId) {
+        return mContext.getSystemService(KeyguardManager.class).isDeviceSecure(userId);
+    }
+
 }
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
index 24dbce4..0cfdaf2 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
@@ -19,6 +19,7 @@
 import static android.security.keystore.recovery.RecoveryController.ERROR_BAD_CERTIFICATE_FORMAT;
 import static android.security.keystore.recovery.RecoveryController.ERROR_DECRYPTION_FAILED;
 import static android.security.keystore.recovery.RecoveryController.ERROR_DOWNGRADE_CERTIFICATE;
+import static android.security.keystore.recovery.RecoveryController.ERROR_INSECURE_USER;
 import static android.security.keystore.recovery.RecoveryController.ERROR_INVALID_CERTIFICATE;
 import static android.security.keystore.recovery.RecoveryController.ERROR_INVALID_KEY_FORMAT;
 import static android.security.keystore.recovery.RecoveryController.ERROR_NO_SNAPSHOT_PENDING;
@@ -750,6 +751,8 @@
             throw new RuntimeException(e);
         } catch (KeyStoreException | UnrecoverableKeyException | IOException e) {
             throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
+        } catch (InsecureUserException e) {
+            throw new ServiceSpecificException(ERROR_INSECURE_USER, e.getMessage());
         }
 
         try {
@@ -817,6 +820,8 @@
             throw new RuntimeException(e);
         } catch (KeyStoreException | UnrecoverableKeyException | IOException e) {
             throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
+        } catch (InsecureUserException e) {
+            throw new ServiceSpecificException(ERROR_INSECURE_USER, e.getMessage());
         }
 
         try {
diff --git a/services/core/java/com/android/server/logcat/OWNERS b/services/core/java/com/android/server/logcat/OWNERS
index 4ce93aa..33e1873 100644
--- a/services/core/java/com/android/server/logcat/OWNERS
+++ b/services/core/java/com/android/server/logcat/OWNERS
@@ -4,6 +4,5 @@
 eunjeongshin@google.com
 georgechan@google.com
 jsharkey@google.com
-vishwath@google.com
 wenhaowang@google.com
 xiaozhenl@google.com
diff --git a/services/core/java/com/android/server/media/MediaFeatureFlagManager.java b/services/core/java/com/android/server/media/MediaFeatureFlagManager.java
index 70ee38f..f555505 100644
--- a/services/core/java/com/android/server/media/MediaFeatureFlagManager.java
+++ b/services/core/java/com/android/server/media/MediaFeatureFlagManager.java
@@ -17,6 +17,8 @@
 package com.android.server.media;
 
 import android.annotation.StringDef;
+import android.app.ActivityThread;
+import android.app.Application;
 import android.provider.DeviceConfig;
 
 import java.lang.annotation.ElementType;
@@ -31,10 +33,13 @@
      */
     private static final String NAMESPACE_MEDIA_BETTER_TOGETHER = "media_better_together";
 
-    @StringDef(prefix = "FEATURE_", value = {
-            FEATURE_AUDIO_STRATEGIES_IS_USING_LEGACY_CONTROLLER
-    })
-    @Target({ ElementType.TYPE_USE, ElementType.TYPE_PARAMETER })
+    @StringDef(
+            prefix = "FEATURE_",
+            value = {
+                FEATURE_AUDIO_STRATEGIES_IS_USING_LEGACY_CONTROLLER,
+                FEATURE_SCANNING_MINIMUM_PACKAGE_IMPORTANCE
+            })
+    @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
     @Retention(RetentionPolicy.SOURCE)
     /* package */ @interface MediaFeatureFlag {}
 
@@ -46,6 +51,13 @@
             FEATURE_AUDIO_STRATEGIES_IS_USING_LEGACY_CONTROLLER =
             "BluetoothRouteController__enable_legacy_bluetooth_routes_controller";
 
+    /**
+     * Whether to use IMPORTANCE_FOREGROUND (i.e. 100) or IMPORTANCE_FOREGROUND_SERVICE (i.e. 125)
+     * as the minimum package importance for scanning.
+     */
+    /* package */ static final @MediaFeatureFlag String
+            FEATURE_SCANNING_MINIMUM_PACKAGE_IMPORTANCE = "scanning_package_minimum_importance";
+
     private static final MediaFeatureFlagManager sInstance = new MediaFeatureFlagManager();
 
     private MediaFeatureFlagManager() {
@@ -63,4 +75,29 @@
     public boolean getBoolean(@MediaFeatureFlag String key, boolean defaultValue) {
         return DeviceConfig.getBoolean(NAMESPACE_MEDIA_BETTER_TOGETHER, key, defaultValue);
     }
+
+    /**
+     * Returns an int value from {@link DeviceConfig} from the system_time namespace, or {@code
+     * defaultValue} if there is no explicit value set.
+     */
+    public int getInt(@MediaFeatureFlag String key, int defaultValue) {
+        return DeviceConfig.getInt(NAMESPACE_MEDIA_BETTER_TOGETHER, key, defaultValue);
+    }
+
+    /**
+     * Adds a listener to react for changes in media feature flags values. Future calls to this
+     * method with the same listener will replace the old namespace and executor.
+     *
+     * @param onPropertiesChangedListener The listener to add.
+     */
+    public void addOnPropertiesChangedListener(
+            DeviceConfig.OnPropertiesChangedListener onPropertiesChangedListener) {
+        Application currentApplication = ActivityThread.currentApplication();
+        if (currentApplication != null) {
+            DeviceConfig.addOnPropertiesChangedListener(
+                    NAMESPACE_MEDIA_BETTER_TOGETHER,
+                    currentApplication.getMainExecutor(),
+                    onPropertiesChangedListener);
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/media/MediaRoute2Provider.java b/services/core/java/com/android/server/media/MediaRoute2Provider.java
index c59b733..8149847 100644
--- a/services/core/java/com/android/server/media/MediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/MediaRoute2Provider.java
@@ -116,6 +116,7 @@
     public void dump(PrintWriter pw, String prefix) {
         pw.println(prefix + getDebugString());
         prefix += "  ";
+
         if (mProviderInfo == null) {
             pw.println(prefix + "<provider info not received, yet>");
         } else if (mProviderInfo.getRoutes().isEmpty()) {
@@ -125,6 +126,17 @@
                 pw.printf("%s%s | %s\n", prefix, route.getId(), route.getName());
             }
         }
+
+        pw.println(prefix + "Active routing sessions:");
+        synchronized (mLock) {
+            if (mSessionInfos.isEmpty()) {
+                pw.println(prefix + "  <no active routing sessions>");
+            } else {
+                for (RoutingSessionInfo routingSessionInfo : mSessionInfos) {
+                    routingSessionInfo.dump(pw, prefix + "  ");
+                }
+            }
+        }
     }
 
     @Override
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index 309a99e..63dc59c 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -24,13 +24,13 @@
 import static android.media.MediaRouter2Utils.getProviderId;
 
 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
+import static com.android.server.media.MediaFeatureFlagManager.FEATURE_SCANNING_MINIMUM_PACKAGE_IMPORTANCE;
 
 import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.app.ActivityManager;
-import android.app.ActivityThread;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
@@ -95,10 +95,11 @@
     //       in MediaRouter2, remove this constant and replace the usages with the real request IDs.
     private static final long DUMMY_REQUEST_ID = -1;
 
-    private static final String MEDIA_BETTER_TOGETHER_NAMESPACE = "media_better_together";
-
-    private static final String KEY_SCANNING_PACKAGE_MINIMUM_IMPORTANCE =
-            "scanning_package_minimum_importance";
+    private static int sPackageImportanceForScanning =
+            MediaFeatureFlagManager.getInstance()
+                    .getInt(
+                            FEATURE_SCANNING_MINIMUM_PACKAGE_IMPORTANCE,
+                            IMPORTANCE_FOREGROUND_SERVICE);
 
     /**
      * Contains the list of bluetooth permissions that are required to do system routing.
@@ -111,11 +112,6 @@
                 Manifest.permission.BLUETOOTH_CONNECT, Manifest.permission.BLUETOOTH_SCAN
             };
 
-    private static int sPackageImportanceForScanning = DeviceConfig.getInt(
-            MEDIA_BETTER_TOGETHER_NAMESPACE,
-            /* name */ KEY_SCANNING_PACKAGE_MINIMUM_IMPORTANCE,
-            /* defaultValue */ IMPORTANCE_FOREGROUND_SERVICE);
-
     private final Context mContext;
     private final UserManagerInternal mUserManagerInternal;
     private final Object mLock = new Object();
@@ -172,9 +168,8 @@
         mContext.registerReceiver(mScreenOnOffReceiver, screenOnOffIntentFilter);
         mContext.getPackageManager().addOnPermissionsChangeListener(this::onPermissionsChanged);
 
-        DeviceConfig.addOnPropertiesChangedListener(MEDIA_BETTER_TOGETHER_NAMESPACE,
-                ActivityThread.currentApplication().getMainExecutor(),
-                this::onDeviceConfigChange);
+        MediaFeatureFlagManager.getInstance()
+                .addOnPropertiesChangedListener(this::onDeviceConfigChange);
     }
 
     /**
@@ -487,20 +482,20 @@
     }
 
     public void registerManager(@NonNull IMediaRouter2Manager manager,
-            @NonNull String packageName) {
+            @NonNull String callerPackageName) {
         Objects.requireNonNull(manager, "manager must not be null");
-        if (TextUtils.isEmpty(packageName)) {
-            throw new IllegalArgumentException("packageName must not be empty");
+        if (TextUtils.isEmpty(callerPackageName)) {
+            throw new IllegalArgumentException("callerPackageName must not be empty");
         }
 
-        final int uid = Binder.getCallingUid();
-        final int pid = Binder.getCallingPid();
-        final int userId = UserHandle.getUserHandleForUid(uid).getIdentifier();
+        final int callerUid = Binder.getCallingUid();
+        final int callerPid = Binder.getCallingPid();
+        final int userId = UserHandle.getUserHandleForUid(callerUid).getIdentifier();
 
         final long token = Binder.clearCallingIdentity();
         try {
             synchronized (mLock) {
-                registerManagerLocked(manager, uid, pid, packageName, userId);
+                registerManagerLocked(manager, callerUid, callerPid, callerPackageName, userId);
             }
         } finally {
             Binder.restoreCallingIdentity(token);
@@ -678,30 +673,35 @@
 
         final long token = Binder.clearCallingIdentity();
         try {
-            RoutingSessionInfo systemSessionInfo = null;
             synchronized (mLock) {
                 UserRecord userRecord = getOrCreateUserRecordLocked(userId);
                 List<RoutingSessionInfo> sessionInfos;
                 if (hasModifyAudioRoutingPermission) {
                     if (setDeviceRouteSelected) {
-                        systemSessionInfo = userRecord.mHandler.mSystemProvider
+                        // Return a fake system session that shows the device route as selected and
+                        // available bluetooth routes as transferable.
+                        return userRecord.mHandler.mSystemProvider
                                 .generateDeviceRouteSelectedSessionInfo(packageName);
                     } else {
                         sessionInfos = userRecord.mHandler.mSystemProvider.getSessionInfos();
                         if (sessionInfos != null && !sessionInfos.isEmpty()) {
-                            systemSessionInfo = new RoutingSessionInfo.Builder(sessionInfos.get(0))
-                                    .setClientPackageName(packageName).build();
+                            // Return a copy of the current system session with no modification,
+                            // except setting the client package name.
+                            return new RoutingSessionInfo.Builder(sessionInfos.get(0))
+                                    .setClientPackageName(packageName)
+                                    .build();
                         } else {
                             Slog.w(TAG, "System provider does not have any session info.");
                         }
                     }
                 } else {
-                    systemSessionInfo = new RoutingSessionInfo.Builder(
-                            userRecord.mHandler.mSystemProvider.getDefaultSessionInfo())
-                            .setClientPackageName(packageName).build();
+                    return new RoutingSessionInfo.Builder(
+                                    userRecord.mHandler.mSystemProvider.getDefaultSessionInfo())
+                            .setClientPackageName(packageName)
+                            .build();
                 }
             }
-            return systemSessionInfo;
+            return null;
         } finally {
             Binder.restoreCallingIdentity(token);
         }
@@ -925,6 +925,17 @@
             return;
         }
 
+        Slog.i(
+                TAG,
+                TextUtils.formatSimple(
+                        "requestCreateSessionWithRouter2 | router: %s(id: %d), old session id: %s,"
+                            + " new session's route id: %s, request id: %d",
+                        routerRecord.mPackageName,
+                        routerRecord.mRouterId,
+                        oldSession.getId(),
+                        route.getId(),
+                        requestId));
+
         if (managerRequestId != MediaRoute2ProviderService.REQUEST_ID_NONE) {
             ManagerRecord manager = routerRecord.mUserRecord.mHandler.findManagerWithId(
                     toRequesterId(managerRequestId));
@@ -1130,25 +1141,26 @@
 
     @GuardedBy("mLock")
     private void registerManagerLocked(@NonNull IMediaRouter2Manager manager,
-            int uid, int pid, @NonNull String packageName, int userId) {
+            int callerUid, int callerPid, @NonNull String callerPackageName, int userId) {
         final IBinder binder = manager.asBinder();
         ManagerRecord managerRecord = mAllManagerRecords.get(binder);
 
         if (managerRecord != null) {
-            Slog.w(TAG, "registerManagerLocked: Same manager already exists. packageName="
-                    + packageName);
+            Slog.w(TAG, "registerManagerLocked: Same manager already exists. callerPackageName="
+                    + callerPackageName);
             return;
         }
 
         Slog.i(TAG, TextUtils.formatSimple(
-                "registerManager | uid: %d, pid: %d, package: %s, user: %d",
-                uid, pid, packageName, userId));
+                "registerManager | callerUid: %d, callerPid: %d, package: %s, user: %d",
+                callerUid, callerPid, callerPackageName, userId));
 
-        mContext.enforcePermission(Manifest.permission.MEDIA_CONTENT_CONTROL, pid, uid,
+        mContext.enforcePermission(Manifest.permission.MEDIA_CONTENT_CONTROL, callerPid, callerUid,
                 "Must hold MEDIA_CONTENT_CONTROL permission.");
 
         UserRecord userRecord = getOrCreateUserRecordLocked(userId);
-        managerRecord = new ManagerRecord(userRecord, manager, uid, pid, packageName);
+        managerRecord = new ManagerRecord(
+                userRecord, manager, callerUid, callerPid, callerPackageName);
         try {
             binder.linkToDeath(managerRecord, 0);
         } catch (RemoteException ex) {
@@ -1174,8 +1186,11 @@
             // TODO: UserRecord <-> routerRecord, why do they reference each other?
             // How about removing mUserRecord from routerRecord?
             routerRecord.mUserRecord.mHandler.sendMessage(
-                    obtainMessage(UserHandler::notifyDiscoveryPreferenceChangedToManager,
-                        routerRecord.mUserRecord.mHandler, routerRecord, manager));
+                    obtainMessage(
+                            UserHandler::notifyDiscoveryPreferenceChangedToManager,
+                            routerRecord.mUserRecord.mHandler,
+                            routerRecord,
+                            manager));
         }
 
         userRecord.mHandler.sendMessage(
@@ -1192,7 +1207,7 @@
 
         Slog.i(TAG, TextUtils.formatSimple(
                 "unregisterManager | package: %s, user: %d, manager: %d",
-                managerRecord.mPackageName,
+                managerRecord.mOwnerPackageName,
                 userRecord.mUserId,
                 managerRecord.mManagerId));
 
@@ -1455,9 +1470,10 @@
     // End of locked methods that are used by both MediaRouter2 and MediaRouter2Manager.
 
     private void onDeviceConfigChange(@NonNull DeviceConfig.Properties properties) {
-        sPackageImportanceForScanning = properties.getInt(
-                /* name */ KEY_SCANNING_PACKAGE_MINIMUM_IMPORTANCE,
-                /* defaultValue */ IMPORTANCE_FOREGROUND_SERVICE);
+        sPackageImportanceForScanning =
+                properties.getInt(
+                        /* name */ FEATURE_SCANNING_MINIMUM_PACKAGE_IMPORTANCE,
+                        /* defaultValue */ IMPORTANCE_FOREGROUND_SERVICE);
     }
 
     static long toUniqueRequestId(int requesterId, int originalRequestId) {
@@ -1709,20 +1725,20 @@
     final class ManagerRecord implements IBinder.DeathRecipient {
         public final UserRecord mUserRecord;
         public final IMediaRouter2Manager mManager;
-        public final int mUid;
-        public final int mPid;
-        public final String mPackageName;
+        public final int mOwnerUid;
+        public final int mOwnerPid;
+        public final String mOwnerPackageName;
         public final int mManagerId;
         public SessionCreationRequest mLastSessionCreationRequest;
         public boolean mIsScanning;
 
         ManagerRecord(UserRecord userRecord, IMediaRouter2Manager manager,
-                int uid, int pid, String packageName) {
+                int ownerUid, int ownerPid, String ownerPackageName) {
             mUserRecord = userRecord;
             mManager = manager;
-            mUid = uid;
-            mPid = pid;
-            mPackageName = packageName;
+            mOwnerUid = ownerUid;
+            mOwnerPid = ownerPid;
+            mOwnerPackageName = ownerPackageName;
             mManagerId = mNextRouterOrManagerId.getAndIncrement();
         }
 
@@ -1740,10 +1756,10 @@
 
             String indent = prefix + "  ";
 
-            pw.println(indent + "mPackageName=" + mPackageName);
+            pw.println(indent + "mOwnerPackageName=" + mOwnerPackageName);
             pw.println(indent + "mManagerId=" + mManagerId);
-            pw.println(indent + "mUid=" + mUid);
-            pw.println(indent + "mPid=" + mPid);
+            pw.println(indent + "mOwnerUid=" + mOwnerUid);
+            pw.println(indent + "mOwnerPid=" + mOwnerPid);
             pw.println(indent + "mIsScanning=" + mIsScanning);
 
             if (mLastSessionCreationRequest != null) {
@@ -1771,7 +1787,7 @@
 
         @Override
         public String toString() {
-            return "Manager " + mPackageName + " (pid " + mPid + ")";
+            return "Manager " + mOwnerPackageName + " (pid " + mOwnerPid + ")";
         }
     }
 
@@ -1926,10 +1942,10 @@
             }
             boolean isUidRelevant;
             synchronized (service.mLock) {
-                isUidRelevant = mUserRecord.mRouterRecords.stream().anyMatch(
-                        router -> router.mUid == uid)
-                        | mUserRecord.mManagerRecords.stream().anyMatch(
-                            manager -> manager.mUid == uid);
+                isUidRelevant =
+                        mUserRecord.mRouterRecords.stream().anyMatch(router -> router.mUid == uid)
+                                | mUserRecord.mManagerRecords.stream()
+                                        .anyMatch(manager -> manager.mOwnerUid == uid);
             }
             if (isUidRelevant) {
                 sendMessage(PooledLambda.obtainMessage(
@@ -2742,7 +2758,7 @@
             if (service.mPowerManager.isInteractive()) {
                 isManagerScanning = managerRecords.stream().anyMatch(manager ->
                         manager.mIsScanning && service.mActivityManager
-                                .getPackageImportance(manager.mPackageName)
+                                .getPackageImportance(manager.mOwnerPackageName)
                                 <= sPackageImportanceForScanning);
 
                 if (isManagerScanning) {
@@ -2808,6 +2824,7 @@
             return null;
         }
     }
+
     static final class SessionCreationRequest {
         public final RouterRecord mRouterRecord;
         public final long mUniqueRequestId;
diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java
index b440e88..cc261a4 100644
--- a/services/core/java/com/android/server/media/MediaRouterService.java
+++ b/services/core/java/com/android/server/media/MediaRouterService.java
@@ -195,7 +195,8 @@
 
     // Binder call
     @Override
-    public void registerClientAsUser(IMediaRouterClient client, String packageName, int userId) {
+    public void registerClientAsUser(
+            IMediaRouterClient client, @NonNull String packageName, int userId) {
         final int uid = Binder.getCallingUid();
         if (!validatePackageName(uid, packageName)) {
             throw new SecurityException("packageName must match the calling uid");
@@ -537,12 +538,12 @@
 
     // Binder call
     @Override
-    public void registerManager(IMediaRouter2Manager manager, String packageName) {
+    public void registerManager(IMediaRouter2Manager manager, String callerPackageName) {
         final int uid = Binder.getCallingUid();
-        if (!validatePackageName(uid, packageName)) {
-            throw new SecurityException("packageName must match the calling uid");
+        if (!validatePackageName(uid, callerPackageName)) {
+            throw new SecurityException("callerPackageName must match the calling uid");
         }
-        mService2.registerManager(manager, packageName);
+        mService2.registerManager(manager, callerPackageName);
     }
 
     // Binder call
@@ -693,8 +694,13 @@
     }
 
     @GuardedBy("mLock")
-    private void registerClientLocked(IMediaRouterClient client,
-            int uid, int pid, String packageName, int userId, boolean trusted) {
+    private void registerClientLocked(
+            IMediaRouterClient client,
+            int uid,
+            int pid,
+            @NonNull String packageName,
+            int userId,
+            boolean trusted) {
         final IBinder binder = client.asBinder();
         ClientRecord clientRecord = mAllClientRecords.get(binder);
         if (clientRecord == null) {
@@ -926,6 +932,10 @@
         clientRecord.dispose();
     }
 
+    /**
+     * Validates whether the provided package name matches a given uid. Returns false if the package
+     * name is null.
+     */
     private boolean validatePackageName(int uid, String packageName) {
         if (packageName != null) {
             String[] packageNames = mContext.getPackageManager().getPackagesForUid(uid);
@@ -974,8 +984,13 @@
         public String mSelectedRouteId;
         public String mGroupId;
 
-        public ClientRecord(UserRecord userRecord, IMediaRouterClient client,
-                int uid, int pid, String packageName, boolean trusted) {
+        ClientRecord(
+                UserRecord userRecord,
+                IMediaRouterClient client,
+                int uid,
+                int pid,
+                @NonNull String packageName,
+                boolean trusted) {
             mUserRecord = userRecord;
             mClient = client;
             mUid = uid;
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index 9185a00..95ca08c 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -106,6 +106,16 @@
     @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
     static final long THROW_FOR_INVALID_BROADCAST_RECEIVER = 270049379L;
 
+    /**
+     * {@link MediaSession#setMediaButtonReceiver(PendingIntent)} throws an {@link
+     * IllegalArgumentException} if the provided {@link PendingIntent} targets an {@link
+     * android.app.Activity activity} for apps targeting Android V and above. For apps targeting
+     * Android U and below, the request will be ignored.
+     */
+    @ChangeId
+    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
+    static final long THROW_FOR_ACTIVITY_MEDIA_BUTTON_RECEIVER = 272737196L;
+
     private static final String TAG = "MediaSessionRecord";
     private static final String[] ART_URIS = new String[] {
             MediaMetadata.METADATA_KEY_ALBUM_ART_URI,
@@ -1055,13 +1065,26 @@
         }
 
         @Override
-        public void setMediaButtonReceiver(PendingIntent pi) throws RemoteException {
+        public void setMediaButtonReceiver(@Nullable PendingIntent pi) throws RemoteException {
+            final int uid = Binder.getCallingUid();
             final long token = Binder.clearCallingIdentity();
             try {
                 if ((mPolicies & MediaSessionPolicyProvider.SESSION_POLICY_IGNORE_BUTTON_RECEIVER)
                         != 0) {
                     return;
                 }
+
+                if (pi != null && pi.isActivity()) {
+                    if (CompatChanges.isChangeEnabled(
+                            THROW_FOR_ACTIVITY_MEDIA_BUTTON_RECEIVER, uid)) {
+                        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.");
+                        return;
+                    }
+                }
+
                 mMediaButtonReceiverHolder =
                         MediaButtonReceiverHolder.create(mUserId, pi, mPackageName);
                 mService.onMediaButtonReceiverChanged(MediaSessionRecord.this);
diff --git a/services/core/java/com/android/server/media/MediaSessionStack.java b/services/core/java/com/android/server/media/MediaSessionStack.java
index 26e8fe6..321b74c 100644
--- a/services/core/java/com/android/server/media/MediaSessionStack.java
+++ b/services/core/java/com/android/server/media/MediaSessionStack.java
@@ -40,8 +40,6 @@
     private static final boolean DEBUG = MediaSessionService.DEBUG;
     private static final String TAG = "MediaSessionStack";
 
-    private static final int DUMP_EVENTS_MAX_COUNT = 70;
-
     /**
      * Listen the change in the media button session.
      */
diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
index f4e6abd..6c9aa4b 100644
--- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
@@ -17,6 +17,7 @@
 package com.android.server.media;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
@@ -253,6 +254,16 @@
         return mDefaultSessionInfo;
     }
 
+    /**
+     * Builds a system {@link RoutingSessionInfo} with the selected route set to the currently
+     * selected <b>device</b> route (wired or built-in, but not bluetooth) and transferable routes
+     * set to the currently available (connected) bluetooth routes.
+     *
+     * <p>The session's client package name is set to the provided package name.
+     *
+     * <p>Returns {@code null} if there are no registered system sessions.
+     */
+    @Nullable
     public RoutingSessionInfo generateDeviceRouteSelectedSessionInfo(String packageName) {
         synchronized (mLock) {
             if (mSessionInfos.isEmpty()) {
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
index 65e34e6..c649164 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
@@ -147,7 +147,7 @@
         mInjector = injector;
         mClock = injector.createClock();
         mDeathEaters = new ArrayMap<IBinder, IBinder.DeathRecipient>();
-        mCallbackDelegate = new CallbackDelegate();
+        mCallbackDelegate = new CallbackDelegate(injector.createCallbackLooper());
         mAppOps = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
         mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
         mPackageManager = mContext.getPackageManager();
@@ -182,6 +182,11 @@
         Clock createClock() {
             return SystemClock::uptimeMillis;
         }
+
+        /** Creates the {@link Looper} to be used when notifying callbacks. */
+        Looper createCallbackLooper() {
+            return Looper.getMainLooper();
+        }
     }
 
     @Override
@@ -268,7 +273,8 @@
         dispatchStop(projection);
     }
 
-    private void addCallback(final IMediaProjectionWatcherCallback callback) {
+    @VisibleForTesting
+    void addCallback(final IMediaProjectionWatcherCallback callback) {
         IBinder.DeathRecipient deathRecipient = new IBinder.DeathRecipient() {
             @Override
             public void binderDied() {
@@ -315,6 +321,12 @@
         mCallbackDelegate.dispatchStop(projection);
     }
 
+    private void dispatchSessionSet(
+            @NonNull MediaProjectionInfo projectionInfo,
+            @Nullable ContentRecordingSession session) {
+        mCallbackDelegate.dispatchSession(projectionInfo, session);
+    }
+
     /**
      * Returns {@code true} when updating the current mirroring session on WM succeeded, and
      * {@code false} otherwise.
@@ -335,6 +347,7 @@
             if (mProjectionGrant != null) {
                 // Cache the session details.
                 mProjectionGrant.mSession = incomingSession;
+                dispatchSessionSet(mProjectionGrant.getProjectionInfo(), incomingSession);
             }
             return true;
         }
@@ -611,13 +624,10 @@
             return projection;
         }
 
+        @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_MEDIA_PROJECTION)
         @Override // Binder call
         public boolean isCurrentProjection(IMediaProjection projection) {
-            if (mContext.checkCallingOrSelfPermission(MANAGE_MEDIA_PROJECTION)
-                    != PackageManager.PERMISSION_GRANTED) {
-                throw new SecurityException("Requires MANAGE_MEDIA_PROJECTION in order to check "
-                        + "if the given projection is current.");
-            }
+            isCurrentProjection_enforcePermission();
             return MediaProjectionManagerService.this.isCurrentProjection(
                     projection == null ? null : projection.asBinder());
         }
@@ -637,13 +647,10 @@
             }
         }
 
+        @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_MEDIA_PROJECTION)
         @Override // Binder call
         public void stopActiveProjection() {
-            if (mContext.checkCallingOrSelfPermission(MANAGE_MEDIA_PROJECTION)
-                    != PackageManager.PERMISSION_GRANTED) {
-                throw new SecurityException("Requires MANAGE_MEDIA_PROJECTION in order to stop "
-                        + "the active projection");
-            }
+            stopActiveProjection_enforcePermission();
             final long token = Binder.clearCallingIdentity();
             try {
                 synchronized (mLock) {
@@ -656,13 +663,10 @@
             }
         }
 
+        @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_MEDIA_PROJECTION)
         @Override // Binder call
         public void notifyActiveProjectionCapturedContentResized(int width, int height) {
-            if (mContext.checkCallingOrSelfPermission(MANAGE_MEDIA_PROJECTION)
-                    != PackageManager.PERMISSION_GRANTED) {
-                throw new SecurityException("Requires MANAGE_MEDIA_PROJECTION in order to notify "
-                        + "on captured content resize");
-            }
+            notifyActiveProjectionCapturedContentResized_enforcePermission();
             synchronized (mLock) {
                 if (!isCurrentProjection(mProjectionGrant)) {
                     return;
@@ -680,13 +684,10 @@
             }
         }
 
+        @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_MEDIA_PROJECTION)
         @Override
         public void notifyActiveProjectionCapturedContentVisibilityChanged(boolean isVisible) {
-            if (mContext.checkCallingOrSelfPermission(MANAGE_MEDIA_PROJECTION)
-                    != PackageManager.PERMISSION_GRANTED) {
-                throw new SecurityException("Requires MANAGE_MEDIA_PROJECTION in order to notify "
-                        + "on captured content visibility changed");
-            }
+            notifyActiveProjectionCapturedContentVisibilityChanged_enforcePermission();
             synchronized (mLock) {
                 if (!isCurrentProjection(mProjectionGrant)) {
                     return;
@@ -734,14 +735,11 @@
             }
         }
 
+        @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_MEDIA_PROJECTION)
         @Override
         public boolean setContentRecordingSession(@Nullable ContentRecordingSession incomingSession,
                 @NonNull IMediaProjection projection) {
-            if (mContext.checkCallingOrSelfPermission(Manifest.permission.MANAGE_MEDIA_PROJECTION)
-                    != PackageManager.PERMISSION_GRANTED) {
-                throw new SecurityException("Requires MANAGE_MEDIA_PROJECTION to set session "
-                        + "details.");
-            }
+            setContentRecordingSession_enforcePermission();
             synchronized (mLock) {
                 if (!isCurrentProjection(projection)) {
                     throw new SecurityException("Unable to set ContentRecordingSession on "
@@ -757,13 +755,10 @@
             }
         }
 
+        @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_MEDIA_PROJECTION)
         @Override
         public void requestConsentForInvalidProjection(@NonNull IMediaProjection projection) {
-            if (mContext.checkCallingOrSelfPermission(Manifest.permission.MANAGE_MEDIA_PROJECTION)
-                    != PackageManager.PERMISSION_GRANTED) {
-                throw new SecurityException("Requires MANAGE_MEDIA_PROJECTION to check if the given"
-                        + "projection is valid.");
-            }
+            requestConsentForInvalidProjection_enforcePermission();
             synchronized (mLock) {
                 if (!isCurrentProjection(projection)) {
                     Slog.v(TAG, "Reusing token: Won't request consent again for a token that "
@@ -877,13 +872,10 @@
                 || mType == MediaProjectionManager.TYPE_SCREEN_CAPTURE;
         }
 
+        @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_MEDIA_PROJECTION)
         @Override // Binder call
         public int applyVirtualDisplayFlags(int flags) {
-            if (mContext.checkCallingOrSelfPermission(MANAGE_MEDIA_PROJECTION)
-                    != PackageManager.PERMISSION_GRANTED) {
-                throw new SecurityException("Requires MANAGE_MEDIA_PROJECTION to apply virtual "
-                        + "display flags.");
-            }
+            applyVirtualDisplayFlags_enforcePermission();
             if (mType == MediaProjectionManager.TYPE_SCREEN_CAPTURE) {
                 flags &= ~DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
                 flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR
@@ -1035,33 +1027,24 @@
             mCallbackDelegate.remove(callback);
         }
 
+        @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_MEDIA_PROJECTION)
         @Override // Binder call
         public void setLaunchCookie(IBinder launchCookie) {
-            if (mContext.checkCallingOrSelfPermission(MANAGE_MEDIA_PROJECTION)
-                    != PackageManager.PERMISSION_GRANTED) {
-                throw new SecurityException("Requires MANAGE_MEDIA_PROJECTION to set launch "
-                        + "cookie.");
-            }
+            setLaunchCookie_enforcePermission();
             mLaunchCookie = launchCookie;
         }
 
+        @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_MEDIA_PROJECTION)
         @Override // Binder call
         public IBinder getLaunchCookie() {
-            if (mContext.checkCallingOrSelfPermission(MANAGE_MEDIA_PROJECTION)
-                    != PackageManager.PERMISSION_GRANTED) {
-                throw new SecurityException("Requires MANAGE_MEDIA_PROJECTION to get launch "
-                        + "cookie.");
-            }
+            getLaunchCookie_enforcePermission();
             return mLaunchCookie;
         }
 
+        @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_MEDIA_PROJECTION)
         @Override
         public boolean isValid() {
-            if (mContext.checkCallingOrSelfPermission(Manifest.permission.MANAGE_MEDIA_PROJECTION)
-                    != PackageManager.PERMISSION_GRANTED) {
-                throw new SecurityException("Requires MANAGE_MEDIA_PROJECTION to check if this"
-                        + "projection is valid.");
-            }
+            isValid_enforcePermission();
             synchronized (mLock) {
                 final long curMs = mClock.uptimeMillis();
                 final boolean hasTimedOut = curMs - mCreateTimeMs > mTimeoutMs;
@@ -1088,13 +1071,10 @@
             }
         }
 
+        @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_MEDIA_PROJECTION)
         @Override
         public void notifyVirtualDisplayCreated(int displayId) {
-            if (mContext.checkCallingOrSelfPermission(Manifest.permission.MANAGE_MEDIA_PROJECTION)
-                    != PackageManager.PERMISSION_GRANTED) {
-                throw new SecurityException("Requires MANAGE_MEDIA_PROJECTION to notify virtual "
-                        + "display created.");
-            }
+            notifyVirtualDisplayCreated_enforcePermission();
             synchronized (mLock) {
                 mVirtualDisplayId = displayId;
 
@@ -1155,8 +1135,8 @@
         private Handler mHandler;
         private final Object mLock = new Object();
 
-        public CallbackDelegate() {
-            mHandler = new Handler(Looper.getMainLooper(), null, true /*async*/);
+        CallbackDelegate(Looper callbackLooper) {
+            mHandler = new Handler(callbackLooper, null, true /*async*/);
             mClientCallbacks = new ArrayMap<IBinder, IMediaProjectionCallback>();
             mWatcherCallbacks = new ArrayMap<IBinder, IMediaProjectionWatcherCallback>();
         }
@@ -1219,6 +1199,16 @@
             }
         }
 
+        public void dispatchSession(
+                @NonNull MediaProjectionInfo projectionInfo,
+                @Nullable ContentRecordingSession session) {
+            synchronized (mLock) {
+                for (IMediaProjectionWatcherCallback callback : mWatcherCallbacks.values()) {
+                    mHandler.post(new WatcherSessionCallback(callback, projectionInfo, session));
+                }
+            }
+        }
+
         public void dispatchResize(MediaProjection projection, int width, int height) {
             if (projection == null) {
                 Slog.e(TAG,
@@ -1335,6 +1325,29 @@
         }
     }
 
+    private static final class WatcherSessionCallback implements Runnable {
+        private final IMediaProjectionWatcherCallback mCallback;
+        private final MediaProjectionInfo mProjectionInfo;
+        private final ContentRecordingSession mSession;
+
+        WatcherSessionCallback(
+                @NonNull IMediaProjectionWatcherCallback callback,
+                @NonNull MediaProjectionInfo projectionInfo,
+                @Nullable ContentRecordingSession session) {
+            mCallback = callback;
+            mProjectionInfo = projectionInfo;
+            mSession = session;
+        }
+
+        @Override
+        public void run() {
+            try {
+                mCallback.onRecordingSessionSet(mProjectionInfo, mSession);
+            } catch (RemoteException e) {
+                Slog.w(TAG, "Failed to notify content recording session changed", e);
+            }
+        }
+    }
 
     private static String typeToString(int type) {
         switch (type) {
diff --git a/services/core/java/com/android/server/media/projection/TEST_MAPPING b/services/core/java/com/android/server/media/projection/TEST_MAPPING
index 4324930..a792498 100644
--- a/services/core/java/com/android/server/media/projection/TEST_MAPPING
+++ b/services/core/java/com/android/server/media/projection/TEST_MAPPING
@@ -13,20 +13,6 @@
           "exclude-annotation": "org.junit.Ignore"
         }
       ]
-    },
-    {
-      "name": "CtsMediaProjectionTestCases",
-      "options": [
-        {
-          "exclude-annotation": "android.platform.test.annotations.FlakyTest"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        },
-        {
-          "exclude-annotation": "org.junit.Ignore"
-        }
-      ]
     }
   ]
 }
diff --git a/services/core/java/com/android/server/net/NetworkManagementService.java b/services/core/java/com/android/server/net/NetworkManagementService.java
index e5ffa7e..39b8bfd 100644
--- a/services/core/java/com/android/server/net/NetworkManagementService.java
+++ b/services/core/java/com/android/server/net/NetworkManagementService.java
@@ -34,8 +34,6 @@
 import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_STANDBY;
 import static android.net.NetworkPolicyManager.FIREWALL_RULE_DEFAULT;
 
-import static com.android.net.module.util.NetworkStatsUtils.LIMIT_GLOBAL_ALERT;
-
 import android.annotation.NonNull;
 import android.app.ActivityManager;
 import android.content.Context;
@@ -43,15 +41,12 @@
 import android.net.INetd;
 import android.net.INetdUnsolicitedEventListener;
 import android.net.INetworkManagementEventObserver;
-import android.net.ITetheringStatsProvider;
 import android.net.InetAddresses;
 import android.net.InterfaceConfiguration;
 import android.net.InterfaceConfigurationParcel;
 import android.net.IpPrefix;
 import android.net.LinkAddress;
 import android.net.NetworkPolicyManager;
-import android.net.NetworkStack;
-import android.net.NetworkStats;
 import android.net.RouteInfo;
 import android.net.util.NetdService;
 import android.os.BatteryStats;
@@ -78,28 +73,20 @@
 import com.android.internal.app.IBatteryStats;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.HexDump;
-import com.android.internal.util.Preconditions;
 import com.android.net.module.util.NetdUtils;
 import com.android.net.module.util.NetdUtils.ModifyOperation;
+import com.android.net.module.util.PermissionUtils;
 import com.android.server.FgThread;
 import com.android.server.LocalServices;
 
 import com.google.android.collect.Maps;
 
-import java.io.BufferedReader;
-import java.io.DataInputStream;
 import java.io.FileDescriptor;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStreamReader;
 import java.io.PrintWriter;
 import java.net.InetAddress;
-import java.net.InterfaceAddress;
 import java.util.ArrayList;
 import java.util.HashMap;
-import java.util.List;
 import java.util.Map;
-import java.util.Objects;
 
 /**
  * @hide
@@ -129,13 +116,6 @@
     private static final String TAG = "NetworkManagement";
     private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
 
-    private static final int MAX_UID_RANGES_PER_COMMAND = 10;
-
-    static final int DAEMON_MSG_MOBILE_CONN_REAL_TIME_INFO = 1;
-
-    static final boolean MODIFY_OPERATION_ADD = true;
-    static final boolean MODIFY_OPERATION_REMOVE = false;
-
     /**
      * Binder context for this service
      */
@@ -154,10 +134,6 @@
     private final RemoteCallbackList<INetworkManagementEventObserver> mObservers =
             new RemoteCallbackList<>();
 
-    @GuardedBy("mTetheringStatsProviders")
-    private final HashMap<ITetheringStatsProvider, String>
-            mTetheringStatsProviders = Maps.newHashMap();
-
     /**
      * If both locks need to be held, then they should be obtained in the order:
      * first {@link #mQuotaLock} and then {@link #mRulesLock}.
@@ -182,35 +158,35 @@
     private SparseIntArray mUidCleartextPolicy = new SparseIntArray();
     /** Set of UIDs that are to be blocked/allowed by firewall controller. */
     @GuardedBy("mRulesLock")
-    private SparseIntArray mUidFirewallRules = new SparseIntArray();
+    private final SparseIntArray mUidFirewallRules = new SparseIntArray();
     /**
      * Set of UIDs that are to be blocked/allowed by firewall controller.  This set of Ids matches
      * to application idles.
      */
     @GuardedBy("mRulesLock")
-    private SparseIntArray mUidFirewallStandbyRules = new SparseIntArray();
+    private final SparseIntArray mUidFirewallStandbyRules = new SparseIntArray();
     /**
      * Set of UIDs that are to be blocked/allowed by firewall controller.  This set of Ids matches
      * to device idles.
      */
     @GuardedBy("mRulesLock")
-    private SparseIntArray mUidFirewallDozableRules = new SparseIntArray();
+    private final SparseIntArray mUidFirewallDozableRules = new SparseIntArray();
     /**
      * Set of UIDs that are to be blocked/allowed by firewall controller.  This set of Ids matches
      * to device on power-save mode.
      */
     @GuardedBy("mRulesLock")
-    private SparseIntArray mUidFirewallPowerSaveRules = new SparseIntArray();
+    private final SparseIntArray mUidFirewallPowerSaveRules = new SparseIntArray();
     /**
      * Contains the per-UID firewall rules that are used when Restricted Networking Mode is enabled.
      */
     @GuardedBy("mRulesLock")
-    private SparseIntArray mUidFirewallRestrictedRules = new SparseIntArray();
+    private final SparseIntArray mUidFirewallRestrictedRules = new SparseIntArray();
     /**
      * Contains the per-UID firewall rules that are used when Low Power Standby is enabled.
      */
     @GuardedBy("mRulesLock")
-    private SparseIntArray mUidFirewallLowPowerStandbyRules = new SparseIntArray();
+    private final SparseIntArray mUidFirewallLowPowerStandbyRules = new SparseIntArray();
     /** Set of states for the child firewall chains. True if the chain is active. */
     @GuardedBy("mRulesLock")
     final SparseBooleanArray mFirewallChainStates = new SparseBooleanArray();
@@ -237,17 +213,6 @@
         mNetdUnsolicitedEventListener = new NetdUnsolicitedEventListener();
 
         mDeps.registerLocalService(new LocalService());
-
-        synchronized (mTetheringStatsProviders) {
-            mTetheringStatsProviders.put(new NetdTetheringStatsProvider(), "netd");
-        }
-    }
-
-    private NetworkManagementService() {
-        mContext = null;
-        mDaemonHandler = null;
-        mDeps = null;
-        mNetdUnsolicitedEventListener = null;
     }
 
     static NetworkManagementService create(Context context, Dependencies deps)
@@ -290,19 +255,19 @@
 
     @Override
     public void registerObserver(INetworkManagementEventObserver observer) {
-        NetworkStack.checkNetworkStackPermission(mContext);
+        PermissionUtils.enforceNetworkStackPermission(mContext);
         mObservers.register(observer);
     }
 
     @Override
     public void unregisterObserver(INetworkManagementEventObserver observer) {
-        NetworkStack.checkNetworkStackPermission(mContext);
+        PermissionUtils.enforceNetworkStackPermission(mContext);
         mObservers.unregister(observer);
     }
 
     @FunctionalInterface
     private interface NetworkManagementEventCallback {
-        public void sendCallback(INetworkManagementEventObserver o) throws RemoteException;
+        void sendCallback(INetworkManagementEventObserver o) throws RemoteException;
     }
 
     private void invokeForAllObservers(NetworkManagementEventCallback eventCallback) {
@@ -368,35 +333,6 @@
                 type, isActive, tsNanos, uid));
     }
 
-    @Override
-    public void registerTetheringStatsProvider(ITetheringStatsProvider provider, String name) {
-        NetworkStack.checkNetworkStackPermission(mContext);
-        Objects.requireNonNull(provider);
-        synchronized(mTetheringStatsProviders) {
-            mTetheringStatsProviders.put(provider, name);
-        }
-    }
-
-    @Override
-    public void unregisterTetheringStatsProvider(ITetheringStatsProvider provider) {
-        NetworkStack.checkNetworkStackPermission(mContext);
-        synchronized(mTetheringStatsProviders) {
-            mTetheringStatsProviders.remove(provider);
-        }
-    }
-
-    @Override
-    public void tetherLimitReached(ITetheringStatsProvider provider) {
-        NetworkStack.checkNetworkStackPermission(mContext);
-        synchronized(mTetheringStatsProviders) {
-            if (!mTetheringStatsProviders.containsKey(provider)) {
-                return;
-            }
-            // No current code examines the interface parameter in a global alert. Just pass null.
-            mDaemonHandler.post(() -> notifyLimitReached(LIMIT_GLOBAL_ALERT, null));
-        }
-    }
-
     // Sync the state of the given chain with the native daemon.
     private void syncFirewallChainLocked(int chain, String name) {
         SparseIntArray rules;
@@ -666,7 +602,7 @@
     public String[] listInterfaces() {
         // TODO: Remove CONNECTIVITY_INTERNAL after bluetooth tethering has no longer called these
         //  APIs.
-        NetworkStack.checkNetworkStackPermissionOr(mContext, CONNECTIVITY_INTERNAL);
+        PermissionUtils.enforceNetworkStackPermissionOr(mContext, CONNECTIVITY_INTERNAL);
         try {
             return mNetdService.interfaceGetList();
         } catch (RemoteException | ServiceSpecificException e) {
@@ -718,7 +654,7 @@
     public InterfaceConfiguration getInterfaceConfig(String iface) {
         // TODO: Remove CONNECTIVITY_INTERNAL after bluetooth tethering has no longer called these
         //  APIs.
-        NetworkStack.checkNetworkStackPermissionOr(mContext, CONNECTIVITY_INTERNAL);
+        PermissionUtils.enforceNetworkStackPermissionOr(mContext, CONNECTIVITY_INTERNAL);
         final InterfaceConfigurationParcel result;
         try {
             result = mNetdService.interfaceGetCfg(iface);
@@ -738,7 +674,7 @@
     public void setInterfaceConfig(String iface, InterfaceConfiguration cfg) {
         // TODO: Remove CONNECTIVITY_INTERNAL after bluetooth tethering has no longer called these
         //  APIs.
-        NetworkStack.checkNetworkStackPermissionOr(mContext, CONNECTIVITY_INTERNAL);
+        PermissionUtils.enforceNetworkStackPermissionOr(mContext, CONNECTIVITY_INTERNAL);
         LinkAddress linkAddr = cfg.getLinkAddress();
         if (linkAddr == null || linkAddr.getAddress() == null) {
             throw new IllegalStateException("Null LinkAddress given");
@@ -755,7 +691,7 @@
 
     @Override
     public void setInterfaceDown(String iface) {
-        NetworkStack.checkNetworkStackPermission(mContext);
+        PermissionUtils.enforceNetworkStackPermission(mContext);
         final InterfaceConfiguration ifcg = getInterfaceConfig(iface);
         ifcg.setInterfaceDown();
         setInterfaceConfig(iface, ifcg);
@@ -763,7 +699,7 @@
 
     @Override
     public void setInterfaceUp(String iface) {
-        NetworkStack.checkNetworkStackPermission(mContext);
+        PermissionUtils.enforceNetworkStackPermission(mContext);
         final InterfaceConfiguration ifcg = getInterfaceConfig(iface);
         ifcg.setInterfaceUp();
         setInterfaceConfig(iface, ifcg);
@@ -771,7 +707,7 @@
 
     @Override
     public void setInterfaceIpv6PrivacyExtensions(String iface, boolean enable) {
-        NetworkStack.checkNetworkStackPermission(mContext);
+        PermissionUtils.enforceNetworkStackPermission(mContext);
         try {
             mNetdService.interfaceSetIPv6PrivacyExtensions(iface, enable);
         } catch (RemoteException | ServiceSpecificException e) {
@@ -783,7 +719,7 @@
        IPv6 addresses on interface down, but we need to do full clean up here */
     @Override
     public void clearInterfaceAddresses(String iface) {
-        NetworkStack.checkNetworkStackPermission(mContext);
+        PermissionUtils.enforceNetworkStackPermission(mContext);
         try {
             mNetdService.interfaceClearAddrs(iface);
         } catch (RemoteException | ServiceSpecificException e) {
@@ -793,7 +729,7 @@
 
     @Override
     public void enableIpv6(String iface) {
-        NetworkStack.checkNetworkStackPermission(mContext);
+        PermissionUtils.enforceNetworkStackPermission(mContext);
         try {
             mNetdService.interfaceSetEnableIPv6(iface, true);
         } catch (RemoteException | ServiceSpecificException e) {
@@ -803,7 +739,7 @@
 
     @Override
     public void setIPv6AddrGenMode(String iface, int mode) throws ServiceSpecificException {
-        NetworkStack.checkNetworkStackPermission(mContext);
+        PermissionUtils.enforceNetworkStackPermission(mContext);
         try {
             mNetdService.setIPv6AddrGenMode(iface, mode);
         } catch (RemoteException e) {
@@ -813,7 +749,7 @@
 
     @Override
     public void disableIpv6(String iface) {
-        NetworkStack.checkNetworkStackPermission(mContext);
+        PermissionUtils.enforceNetworkStackPermission(mContext);
         try {
             mNetdService.interfaceSetEnableIPv6(iface, false);
         } catch (RemoteException | ServiceSpecificException e) {
@@ -823,44 +759,16 @@
 
     @Override
     public void addRoute(int netId, RouteInfo route) {
-        NetworkStack.checkNetworkStackPermission(mContext);
+        PermissionUtils.enforceNetworkStackPermission(mContext);
         NetdUtils.modifyRoute(mNetdService, ModifyOperation.ADD, netId, route);
     }
 
     @Override
     public void removeRoute(int netId, RouteInfo route) {
-        NetworkStack.checkNetworkStackPermission(mContext);
+        PermissionUtils.enforceNetworkStackPermission(mContext);
         NetdUtils.modifyRoute(mNetdService, ModifyOperation.REMOVE, netId, route);
     }
 
-    private ArrayList<String> readRouteList(String filename) {
-        FileInputStream fstream = null;
-        ArrayList<String> list = new ArrayList<>();
-
-        try {
-            fstream = new FileInputStream(filename);
-            DataInputStream in = new DataInputStream(fstream);
-            BufferedReader br = new BufferedReader(new InputStreamReader(in));
-            String s;
-
-            // throw away the title line
-
-            while (((s = br.readLine()) != null) && (s.length() != 0)) {
-                list.add(s);
-            }
-        } catch (IOException ex) {
-            // return current list, possibly empty
-        } finally {
-            if (fstream != null) {
-                try {
-                    fstream.close();
-                } catch (IOException ex) {}
-            }
-        }
-
-        return list;
-    }
-
     @android.annotation.EnforcePermission(android.Manifest.permission.SHUTDOWN)
     @Override
     public void shutdown() {
@@ -873,11 +781,10 @@
 
     @Override
     public boolean getIpForwardingEnabled() throws IllegalStateException{
-        NetworkStack.checkNetworkStackPermission(mContext);
+        PermissionUtils.enforceNetworkStackPermission(mContext);
 
         try {
-            final boolean isEnabled = mNetdService.ipfwdEnabled();
-            return isEnabled;
+            return mNetdService.ipfwdEnabled();
         } catch (RemoteException | ServiceSpecificException e) {
             throw new IllegalStateException(e);
         }
@@ -885,7 +792,7 @@
 
     @Override
     public void setIpForwardingEnabled(boolean enable) {
-        NetworkStack.checkNetworkStackPermission(mContext);
+        PermissionUtils.enforceNetworkStackPermission(mContext);
         try {
             if (enable) {
                 mNetdService.ipfwdEnableForwarding("tethering");
@@ -899,14 +806,9 @@
 
     @Override
     public void startTethering(String[] dhcpRange) {
-        startTetheringWithConfiguration(true, dhcpRange);
-    }
-
-    @Override
-    public void startTetheringWithConfiguration(boolean usingLegacyDnsProxy, String[] dhcpRange) {
-        NetworkStack.checkNetworkStackPermission(mContext);
+        PermissionUtils.enforceNetworkStackPermission(mContext);
         try {
-            NetdUtils.tetherStart(mNetdService, usingLegacyDnsProxy, dhcpRange);
+            NetdUtils.tetherStart(mNetdService, true /* usingLegacyDnsProxy */, dhcpRange);
         } catch (RemoteException | ServiceSpecificException e) {
             throw new IllegalStateException(e);
         }
@@ -914,7 +816,7 @@
 
     @Override
     public void stopTethering() {
-        NetworkStack.checkNetworkStackPermission(mContext);
+        PermissionUtils.enforceNetworkStackPermission(mContext);
         try {
             mNetdService.tetherStop();
         } catch (RemoteException | ServiceSpecificException e) {
@@ -924,11 +826,9 @@
 
     @Override
     public boolean isTetheringStarted() {
-        NetworkStack.checkNetworkStackPermission(mContext);
-
+        PermissionUtils.enforceNetworkStackPermission(mContext);
         try {
-            final boolean isEnabled = mNetdService.tetherIsEnabled();
-            return isEnabled;
+            return mNetdService.tetherIsEnabled();
         } catch (RemoteException | ServiceSpecificException e) {
             throw new IllegalStateException(e);
         }
@@ -936,7 +836,7 @@
 
     @Override
     public void tetherInterface(String iface) {
-        NetworkStack.checkNetworkStackPermission(mContext);
+        PermissionUtils.enforceNetworkStackPermission(mContext);
         try {
             final LinkAddress addr = getInterfaceConfig(iface).getLinkAddress();
             final IpPrefix dest = new IpPrefix(addr.getAddress(), addr.getPrefixLength());
@@ -948,7 +848,7 @@
 
     @Override
     public void untetherInterface(String iface) {
-        NetworkStack.checkNetworkStackPermission(mContext);
+        PermissionUtils.enforceNetworkStackPermission(mContext);
         try {
             NetdUtils.untetherInterface(mNetdService, iface);
         } catch (RemoteException | ServiceSpecificException e) {
@@ -958,7 +858,7 @@
 
     @Override
     public String[] listTetheredInterfaces() {
-        NetworkStack.checkNetworkStackPermission(mContext);
+        PermissionUtils.enforceNetworkStackPermission(mContext);
         try {
             return mNetdService.tetherInterfaceList();
         } catch (RemoteException | ServiceSpecificException e) {
@@ -967,51 +867,8 @@
     }
 
     @Override
-    public String[] getDnsForwarders() {
-        NetworkStack.checkNetworkStackPermission(mContext);
-        try {
-            return mNetdService.tetherDnsList();
-        } catch (RemoteException | ServiceSpecificException e) {
-            throw new IllegalStateException(e);
-        }
-    }
-
-    private List<InterfaceAddress> excludeLinkLocal(List<InterfaceAddress> addresses) {
-        ArrayList<InterfaceAddress> filtered = new ArrayList<>(addresses.size());
-        for (InterfaceAddress ia : addresses) {
-            if (!ia.getAddress().isLinkLocalAddress())
-                filtered.add(ia);
-        }
-        return filtered;
-    }
-
-    private void modifyInterfaceForward(boolean add, String fromIface, String toIface) {
-        try {
-            if (add) {
-                mNetdService.ipfwdAddInterfaceForward(fromIface, toIface);
-            } else {
-                mNetdService.ipfwdRemoveInterfaceForward(fromIface, toIface);
-            }
-        } catch (RemoteException | ServiceSpecificException e) {
-            throw new IllegalStateException(e);
-        }
-    }
-
-    @Override
-    public void startInterfaceForwarding(String fromIface, String toIface) {
-        NetworkStack.checkNetworkStackPermission(mContext);
-        modifyInterfaceForward(true, fromIface, toIface);
-    }
-
-    @Override
-    public void stopInterfaceForwarding(String fromIface, String toIface) {
-        NetworkStack.checkNetworkStackPermission(mContext);
-        modifyInterfaceForward(false, fromIface, toIface);
-    }
-
-    @Override
     public void enableNat(String internalInterface, String externalInterface) {
-        NetworkStack.checkNetworkStackPermission(mContext);
+        PermissionUtils.enforceNetworkStackPermission(mContext);
         try {
             mNetdService.tetherAddForward(internalInterface, externalInterface);
         } catch (RemoteException | ServiceSpecificException e) {
@@ -1021,7 +878,7 @@
 
     @Override
     public void disableNat(String internalInterface, String externalInterface) {
-        NetworkStack.checkNetworkStackPermission(mContext);
+        PermissionUtils.enforceNetworkStackPermission(mContext);
         try {
             mNetdService.tetherRemoveForward(internalInterface, externalInterface);
         } catch (RemoteException | ServiceSpecificException e) {
@@ -1031,7 +888,7 @@
 
     @Override
     public void setInterfaceQuota(String iface, long quotaBytes) {
-        NetworkStack.checkNetworkStackPermission(mContext);
+        PermissionUtils.enforceNetworkStackPermission(mContext);
 
         synchronized (mQuotaLock) {
             if (mActiveQuotas.containsKey(iface)) {
@@ -1046,23 +903,12 @@
             } catch (RemoteException | ServiceSpecificException e) {
                 throw new IllegalStateException(e);
             }
-
-            synchronized (mTetheringStatsProviders) {
-                for (ITetheringStatsProvider provider : mTetheringStatsProviders.keySet()) {
-                    try {
-                        provider.setInterfaceQuota(iface, quotaBytes);
-                    } catch (RemoteException e) {
-                        Log.e(TAG, "Problem setting tethering data limit on provider " +
-                                mTetheringStatsProviders.get(provider) + ": " + e);
-                    }
-                }
-            }
         }
     }
 
     @Override
     public void removeInterfaceQuota(String iface) {
-        NetworkStack.checkNetworkStackPermission(mContext);
+        PermissionUtils.enforceNetworkStackPermission(mContext);
 
         synchronized (mQuotaLock) {
             if (!mActiveQuotas.containsKey(iface)) {
@@ -1079,23 +925,12 @@
             } catch (RemoteException | ServiceSpecificException e) {
                 throw new IllegalStateException(e);
             }
-
-            synchronized (mTetheringStatsProviders) {
-                for (ITetheringStatsProvider provider : mTetheringStatsProviders.keySet()) {
-                    try {
-                        provider.setInterfaceQuota(iface, ITetheringStatsProvider.QUOTA_UNLIMITED);
-                    } catch (RemoteException e) {
-                        Log.e(TAG, "Problem removing tethering data limit on provider " +
-                                mTetheringStatsProviders.get(provider) + ": " + e);
-                    }
-                }
-            }
         }
     }
 
     @Override
     public void setInterfaceAlert(String iface, long alertBytes) {
-        NetworkStack.checkNetworkStackPermission(mContext);
+        PermissionUtils.enforceNetworkStackPermission(mContext);
 
         // quick validity check
         if (!mActiveQuotas.containsKey(iface)) {
@@ -1119,7 +954,7 @@
 
     @Override
     public void removeInterfaceAlert(String iface) {
-        NetworkStack.checkNetworkStackPermission(mContext);
+        PermissionUtils.enforceNetworkStackPermission(mContext);
 
         synchronized (mQuotaLock) {
             if (!mActiveAlerts.containsKey(iface)) {
@@ -1137,19 +972,8 @@
         }
     }
 
-    @Override
-    public void setGlobalAlert(long alertBytes) {
-        NetworkStack.checkNetworkStackPermission(mContext);
-
-        try {
-            mNetdService.bandwidthSetGlobalAlert(alertBytes);
-        } catch (RemoteException | ServiceSpecificException e) {
-            throw new IllegalStateException(e);
-        }
-    }
-
     private void setUidOnMeteredNetworkList(int uid, boolean allowlist, boolean enable) {
-        NetworkStack.checkNetworkStackPermission(mContext);
+        PermissionUtils.enforceNetworkStackPermission(mContext);
 
         synchronized (mQuotaLock) {
             boolean oldEnable;
@@ -1261,7 +1085,7 @@
     @Override
     public void setUidCleartextNetworkPolicy(int uid, int policy) {
         if (mDeps.getCallingUid() != uid) {
-            NetworkStack.checkNetworkStackPermission(mContext);
+            PermissionUtils.enforceNetworkStackPermission(mContext);
         }
 
         synchronized (mQuotaLock) {
@@ -1298,27 +1122,6 @@
         return true;
     }
 
-    private class NetdTetheringStatsProvider extends ITetheringStatsProvider.Stub {
-        @Override
-        public NetworkStats getTetherStats(int how) {
-            // Remove the implementation of NetdTetheringStatsProvider#getTetherStats
-            // since all callers are migrated to use INetd#tetherGetStats directly.
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        public void setInterfaceQuota(String iface, long quotaBytes) {
-            // Do nothing. netd is already informed of quota changes in setInterfaceQuota.
-        }
-    }
-
-    @Override
-    public NetworkStats getNetworkStatsTethering(int how) {
-        // Remove the implementation of getNetworkStatsTethering since all callers are migrated
-        // to use INetd#tetherGetStats directly.
-        throw new UnsupportedOperationException();
-    }
-
     @Override
     public void setFirewallEnabled(boolean enabled) {
         enforceSystemUid();
@@ -1338,18 +1141,6 @@
     }
 
     @Override
-    public void setFirewallInterfaceRule(String iface, boolean allow) {
-        enforceSystemUid();
-        Preconditions.checkState(mFirewallEnabled);
-        try {
-            mNetdService.firewallSetInterfaceRule(iface,
-                    allow ? INetd.FIREWALL_RULE_ALLOW : INetd.FIREWALL_RULE_DENY);
-        } catch (RemoteException | ServiceSpecificException e) {
-            throw new IllegalStateException(e);
-        }
-    }
-
-    @Override
     public void setFirewallChainEnabled(int chain, boolean enable) {
         enforceSystemUid();
         synchronized (mQuotaLock) {
@@ -1619,22 +1410,9 @@
         pw.println("]");
     }
 
-    private void modifyInterfaceInNetwork(boolean add, int netId, String iface) {
-        NetworkStack.checkNetworkStackPermission(mContext);
-        try {
-            if (add) {
-                mNetdService.networkAddInterface(netId, iface);
-            } else {
-                mNetdService.networkRemoveInterface(netId, iface);
-            }
-        } catch (RemoteException | ServiceSpecificException e) {
-            throw new IllegalStateException(e);
-        }
-    }
-
     @Override
     public void allowProtect(int uid) {
-        NetworkStack.checkNetworkStackPermission(mContext);
+        PermissionUtils.enforceNetworkStackPermission(mContext);
 
         try {
             mNetdService.networkSetProtectAllow(uid);
@@ -1645,7 +1423,7 @@
 
     @Override
     public void denyProtect(int uid) {
-        NetworkStack.checkNetworkStackPermission(mContext);
+        PermissionUtils.enforceNetworkStackPermission(mContext);
 
         try {
             mNetdService.networkSetProtectDeny(uid);
@@ -1654,24 +1432,6 @@
         }
     }
 
-    @Override
-    public void addInterfaceToLocalNetwork(String iface, List<RouteInfo> routes) {
-        modifyInterfaceInNetwork(MODIFY_OPERATION_ADD, INetd.LOCAL_NET_ID, iface);
-        // modifyInterfaceInNetwork already check calling permission.
-        NetdUtils.addRoutesToLocalNetwork(mNetdService, iface, routes);
-    }
-
-    @Override
-    public void removeInterfaceFromLocalNetwork(String iface) {
-        modifyInterfaceInNetwork(MODIFY_OPERATION_REMOVE, INetd.LOCAL_NET_ID, iface);
-    }
-
-    @Override
-    public int removeRoutesFromLocalNetwork(List<RouteInfo> routes) {
-        NetworkStack.checkNetworkStackPermission(mContext);
-        return NetdUtils.removeRoutesFromLocalNetwork(mNetdService, routes);
-    }
-
     @android.annotation.EnforcePermission(android.Manifest.permission.OBSERVE_NETWORK_POLICY)
     @Override
     public boolean isNetworkRestricted(int uid) {
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index b708269..fb9bc01 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -141,6 +141,7 @@
 import static org.xmlpull.v1.XmlPullParser.START_TAG;
 
 import android.Manifest;
+import android.annotation.EnforcePermission;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -222,6 +223,7 @@
 import android.telephony.SubscriptionManager;
 import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
 import android.telephony.SubscriptionPlan;
+import android.telephony.TelephonyCallback;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.text.format.DateUtils;
@@ -803,6 +805,16 @@
             }
             return buckets;
         }
+
+        /** Require IPC call. Don't call when holding a lock. */
+        int getDefaultDataSubId() {
+            return SubscriptionManager.getDefaultDataSubscriptionId();
+        }
+
+        /** Require IPC call. Don't call when holding a lock. */
+        int getActivateDataSubId() {
+            return SubscriptionManager.getActiveDataSubscriptionId();
+        }
     }
 
     @VisibleForTesting
@@ -830,7 +842,7 @@
 
         mSuppressDefaultPolicy = suppressDefaultPolicy;
         mDeps = Objects.requireNonNull(deps, "missing Dependencies");
-
+        mActiveDataSubIdListener = new ActiveDataSubIdListener();
         mPolicyFile = new AtomicFile(new File(systemDir, "netpolicy.xml"), "net-policy");
 
         mAppOps = context.getSystemService(AppOpsManager.class);
@@ -1090,6 +1102,10 @@
                         }
                     });
 
+            // Listen for active data sub Id change, upon which data notifications is shown/hidden.
+            mContext.getSystemService(TelephonyManager.class).registerTelephonyCallback(executor,
+                    mActiveDataSubIdListener);
+
             // tell systemReady() that the service has been initialized
             initCompleteSignal.countDown();
         } finally {
@@ -1261,6 +1277,38 @@
     };
 
     /**
+     * Listener that watches for active data sub Id change, upon which data notifications are
+     * shown/hidden.
+     */
+    private final ActiveDataSubIdListener mActiveDataSubIdListener;
+    private class ActiveDataSubIdListener extends TelephonyCallback implements
+            TelephonyCallback.ActiveDataSubscriptionIdListener {
+        /**
+         * In most cases active data sub is the same as the default data sub, but if user enabled
+         * auto data switch {@link TelephonyManager#MOBILE_DATA_POLICY_AUTO_DATA_SWITCH},
+         * active data sub could be the non-default data sub.
+         *
+         * If the listener is initialized before the phone process is up, the IPC call to the
+         * static method of SubscriptionManager lead to INVALID_SUBSCRIPTION_ID to be returned,
+         * indicating the phone process is unable to determine a valid data sub Id at this point, in
+         * which case no data notifications should be shown anyway. Later on when an active data
+         * sub is known, notifications will be re-evaluated by this callback.
+         */
+        private int mDefaultDataSubId = mDeps.getDefaultDataSubId();
+        private int mActiveDataSubId = mDeps.getActivateDataSubId();
+        // Only listen to active data sub change is sufficient because default data sub change
+        // leads to active data sub change as well.
+        @Override
+        public void onActiveDataSubscriptionIdChanged(int subId) {
+            mActiveDataSubId = subId;
+            mDefaultDataSubId = mDeps.getDefaultDataSubId();
+            synchronized (mNetworkPoliciesSecondLock) {
+                updateNotificationsNL();
+            }
+        }
+    }
+
+    /**
      * Listener that watches for {@link NetworkStatsManager} updates, which
      * NetworkPolicyManagerService uses to check against {@link NetworkPolicy#warningBytes}.
      */
@@ -1449,6 +1497,9 @@
 
             // ignore policies that aren't relevant to user
             if (subId == INVALID_SUBSCRIPTION_ID) continue;
+            // ignore if the data sub is neither default nor active for data at the moment.
+            if (subId != mActiveDataSubIdListener.mDefaultDataSubId
+                    && subId != mActiveDataSubIdListener.mActiveDataSubId) continue;
             if (!policy.hasCycle()) continue;
 
             final Pair<ZonedDateTime, ZonedDateTime> cycle = NetworkPolicyManager
@@ -2842,9 +2893,10 @@
         }
     }
 
+    @EnforcePermission(MANAGE_NETWORK_POLICY)
     @Override
     public void setUidPolicy(int uid, int policy) {
-        mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);
+        setUidPolicy_enforcePermission();
 
         if (!UserHandle.isApp(uid)) {
             throw new IllegalArgumentException("cannot apply policy to UID " + uid);
@@ -2863,9 +2915,10 @@
         }
     }
 
+    @EnforcePermission(MANAGE_NETWORK_POLICY)
     @Override
     public void addUidPolicy(int uid, int policy) {
-        mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);
+        addUidPolicy_enforcePermission();
 
         if (!UserHandle.isApp(uid)) {
             throw new IllegalArgumentException("cannot apply policy to UID " + uid);
@@ -2881,9 +2934,10 @@
         }
     }
 
+    @EnforcePermission(MANAGE_NETWORK_POLICY)
     @Override
     public void removeUidPolicy(int uid, int policy) {
-        mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);
+        removeUidPolicy_enforcePermission();
 
         if (!UserHandle.isApp(uid)) {
             throw new IllegalArgumentException("cannot apply policy to UID " + uid);
@@ -2948,18 +3002,20 @@
         }
     }
 
+    @EnforcePermission(MANAGE_NETWORK_POLICY)
     @Override
     public int getUidPolicy(int uid) {
-        mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);
+        getUidPolicy_enforcePermission();
 
         synchronized (mUidRulesFirstLock) {
             return mUidPolicy.get(uid, POLICY_NONE);
         }
     }
 
+    @EnforcePermission(MANAGE_NETWORK_POLICY)
     @Override
     public int[] getUidsWithPolicy(int policy) {
-        mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);
+        getUidsWithPolicy_enforcePermission();
 
         int[] uids = new int[0];
         synchronized (mUidRulesFirstLock) {
@@ -3055,9 +3111,10 @@
         mListeners.unregister(listener);
     }
 
+    @EnforcePermission(MANAGE_NETWORK_POLICY)
     @Override
     public void setNetworkPolicies(NetworkPolicy[] policies) {
-        mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);
+        setNetworkPolicies_enforcePermission();
 
         final long token = Binder.clearCallingIdentity();
         try {
@@ -3078,9 +3135,10 @@
         setNetworkPolicies(policies);
     }
 
+    @EnforcePermission(MANAGE_NETWORK_POLICY)
     @Override
     public NetworkPolicy[] getNetworkPolicies(String callingPackage) {
-        mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);
+        getNetworkPolicies_enforcePermission();
         try {
             mContext.enforceCallingOrSelfPermission(READ_PRIVILEGED_PHONE_STATE, TAG);
             // SKIP checking run-time OP_READ_PHONE_STATE since caller or self has PRIVILEGED
@@ -3176,9 +3234,10 @@
         return template;
     }
 
+    @EnforcePermission(MANAGE_NETWORK_POLICY)
     @Override
     public void snoozeLimit(NetworkTemplate template) {
-        mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);
+        snoozeLimit_enforcePermission();
 
         final long token = Binder.clearCallingIdentity();
         try {
@@ -3286,9 +3345,10 @@
                 .sendToTarget();
     }
 
+    @EnforcePermission(ACCESS_NETWORK_STATE)
     @Override
     public int getRestrictBackgroundByCaller() {
-        mContext.enforceCallingOrSelfPermission(ACCESS_NETWORK_STATE, TAG);
+        getRestrictBackgroundByCaller_enforcePermission();
         return getRestrictBackgroundStatusInternal(Binder.getCallingUid());
     }
 
@@ -3321,18 +3381,20 @@
         }
     }
 
+    @EnforcePermission(MANAGE_NETWORK_POLICY)
     @Override
     public boolean getRestrictBackground() {
-        mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);
+        getRestrictBackground_enforcePermission();
 
         synchronized (mUidRulesFirstLock) {
             return mRestrictBackground;
         }
     }
 
+    @EnforcePermission(MANAGE_NETWORK_POLICY)
     @Override
     public void setDeviceIdleMode(boolean enabled) {
-        mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);
+        setDeviceIdleMode_enforcePermission();
         Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "setDeviceIdleMode");
         try {
             synchronized (mUidRulesFirstLock) {
@@ -3357,9 +3419,10 @@
         }
     }
 
+    @EnforcePermission(MANAGE_NETWORK_POLICY)
     @Override
     public void setWifiMeteredOverride(String networkId, int meteredOverride) {
-        mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);
+        setWifiMeteredOverride_enforcePermission();
         final long token = Binder.clearCallingIdentity();
         try {
             final WifiManager wm = mContext.getSystemService(WifiManager.class);
@@ -5967,9 +6030,10 @@
         }
     }
 
+    @EnforcePermission(NETWORK_SETTINGS)
     @Override
     public void factoryReset(String subscriber) {
-        mContext.enforceCallingOrSelfPermission(NETWORK_SETTINGS, TAG);
+        factoryReset_enforcePermission();
 
         if (mUserManager.hasUserRestriction(UserManager.DISALLOW_NETWORK_RESET)) {
             return;
@@ -6028,9 +6092,10 @@
         return blockedReasons != BLOCKED_REASON_NONE;
     }
 
+    @EnforcePermission(OBSERVE_NETWORK_POLICY)
     @Override
     public boolean isUidRestrictedOnMeteredNetworks(int uid) {
-        mContext.enforceCallingOrSelfPermission(OBSERVE_NETWORK_POLICY, TAG);
+        isUidRestrictedOnMeteredNetworks_enforcePermission();
         synchronized (mUidBlockedState) {
             final UidBlockedState uidBlockedState = mUidBlockedState.get(uid);
             int blockedReasons = uidBlockedState == null
diff --git a/services/core/java/com/android/server/notification/ConditionProviders.java b/services/core/java/com/android/server/notification/ConditionProviders.java
index 030c96e..bfc4f53 100644
--- a/services/core/java/com/android/server/notification/ConditionProviders.java
+++ b/services/core/java/com/android/server/notification/ConditionProviders.java
@@ -251,6 +251,11 @@
     }
 
     @Override
+    protected boolean allowRebindForParentUser() {
+        return true;
+    }
+
+    @Override
     protected String getRequiredPermission() {
         return null;
     }
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
index 12fc263..c6f6fe2 100644
--- a/services/core/java/com/android/server/notification/ManagedServices.java
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -26,6 +26,7 @@
 
 import android.annotation.NonNull;
 import android.app.ActivityManager;
+import android.app.ActivityOptions;
 import android.app.PendingIntent;
 import android.app.admin.DevicePolicyManager;
 import android.content.ComponentName;
@@ -1371,7 +1372,9 @@
     protected void rebindServices(boolean forceRebind, int userToRebind) {
         if (DEBUG) Slog.d(TAG, "rebindServices " + forceRebind + " " + userToRebind);
         IntArray userIds = mUserProfiles.getCurrentProfileIds();
-        if (userToRebind != USER_ALL) {
+        boolean rebindAllCurrentUsers = mUserProfiles.isProfileUser(userToRebind)
+                && allowRebindForParentUser();
+        if (userToRebind != USER_ALL && !rebindAllCurrentUsers) {
             userIds = new IntArray(1);
             userIds.add(userToRebind);
         }
@@ -1540,8 +1543,11 @@
 
         intent.putExtra(Intent.EXTRA_CLIENT_LABEL, mConfig.clientLabel);
 
+        final ActivityOptions activityOptions = ActivityOptions.makeBasic();
+        activityOptions.setIgnorePendingIntentCreatorForegroundState(true);
         final PendingIntent pendingIntent = PendingIntent.getActivity(
-            mContext, 0, new Intent(mConfig.settingsAction), PendingIntent.FLAG_IMMUTABLE);
+                mContext, 0, new Intent(mConfig.settingsAction), PendingIntent.FLAG_IMMUTABLE,
+                activityOptions.toBundle());
         intent.putExtra(Intent.EXTRA_CLIENT_INTENT, pendingIntent);
 
         ApplicationInfo appInfo = null;
@@ -1758,6 +1764,13 @@
         return true;
     }
 
+    /**
+     * Returns true if services in the parent user should be rebound
+     *  when rebindServices is called with a profile userId.
+     * Must be false for NotificationAssistants.
+     */
+    protected abstract boolean allowRebindForParentUser();
+
     public class ManagedServiceInfo implements IBinder.DeathRecipient {
         public IInterface service;
         public ComponentName component;
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 7369e5ed..2db724f 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -322,7 +322,6 @@
 import com.android.server.pm.PackageManagerService;
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.policy.PermissionPolicyInternal;
-import com.android.server.powerstats.StatsPullAtomCallbackImpl;
 import com.android.server.statusbar.StatusBarManagerInternal;
 import com.android.server.uri.UriGrantsManagerInternal;
 import com.android.server.utils.Slogf;
@@ -1715,7 +1714,6 @@
                 return;
             }
 
-            boolean queryRestart = false;
             boolean queryRemove = false;
             boolean packageChanged = false;
             boolean cancelNotifications = true;
@@ -1727,7 +1725,6 @@
                     || (queryRemove=action.equals(Intent.ACTION_PACKAGE_REMOVED))
                     || action.equals(Intent.ACTION_PACKAGE_RESTARTED)
                     || (packageChanged=action.equals(Intent.ACTION_PACKAGE_CHANGED))
-                    || (queryRestart=action.equals(Intent.ACTION_QUERY_PACKAGE_RESTART))
                     || action.equals(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE)
                     || action.equals(Intent.ACTION_PACKAGES_SUSPENDED)
                     || action.equals(Intent.ACTION_PACKAGES_UNSUSPENDED)
@@ -1768,10 +1765,6 @@
                         cancelNotifications = false;
                         unhideNotifications = true;
                     }
-
-                } else if (queryRestart) {
-                    pkgList = intent.getStringArrayExtra(Intent.EXTRA_PACKAGES);
-                    uidList = new int[] {intent.getIntExtra(Intent.EXTRA_UID, -1)};
                 } else {
                     Uri uri = intent.getData();
                     if (uri == null) {
@@ -1809,7 +1802,7 @@
                     if (cancelNotifications) {
                         for (String pkgName : pkgList) {
                             cancelAllNotificationsInt(MY_UID, MY_PID, pkgName, null, 0, 0,
-                                    !queryRestart, changeUserId, reason, null);
+                                    changeUserId, reason);
                         }
                     } else if (hideNotifications && uidList != null && (uidList.length > 0)) {
                         hideNotificationsForPackages(pkgList, uidList);
@@ -1843,14 +1836,14 @@
             } else if (action.equals(Intent.ACTION_USER_STOPPED)) {
                 int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
                 if (userHandle >= 0) {
-                    cancelAllNotificationsInt(MY_UID, MY_PID, null, null, 0, 0, true, userHandle,
-                            REASON_USER_STOPPED, null);
+                    cancelAllNotificationsInt(MY_UID, MY_PID, null, null, 0, 0, userHandle,
+                            REASON_USER_STOPPED);
                 }
             } else if (action.equals(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE)) {
                 int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
                 if (userHandle >= 0 && !mDpm.isKeepProfilesRunningEnabled()) {
-                    cancelAllNotificationsInt(MY_UID, MY_PID, null, null, 0, 0, true, userHandle,
-                            REASON_PROFILE_TURNED_OFF, null);
+                    cancelAllNotificationsInt(MY_UID, MY_PID, null, null, 0, 0, userHandle,
+                            REASON_PROFILE_TURNED_OFF);
                     mSnoozeHelper.clearData(userHandle);
                 }
             } else if (action.equals(Intent.ACTION_USER_PRESENT)) {
@@ -2344,6 +2337,7 @@
                 mRankingHandler,
                 mZenModeHelper,
                 mPermissionHelper,
+                mPermissionManager,
                 mNotificationChannelLogger,
                 mAppOps,
                 new SysUiStatsEvent.BuilderFactory(),
@@ -2467,7 +2461,6 @@
         pkgFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
         pkgFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
         pkgFilter.addAction(Intent.ACTION_PACKAGE_RESTARTED);
-        pkgFilter.addAction(Intent.ACTION_QUERY_PACKAGE_RESTART);
         pkgFilter.addDataScheme("package");
         getContext().registerReceiverAsUser(mPackageIntentReceiver, UserHandle.ALL, pkgFilter, null,
                 null);
@@ -2498,6 +2491,16 @@
         getContext().registerReceiver(mReviewNotificationPermissionsReceiver,
                 ReviewNotificationPermissionsReceiver.getFilter(),
                 Context.RECEIVER_NOT_EXPORTED);
+
+        mAppOps.startWatchingMode(AppOpsManager.OP_POST_NOTIFICATION, null,
+                new AppOpsManager.OnOpChangedInternalListener() {
+                    @Override
+                    public void onOpChanged(@NonNull String op, @NonNull String packageName,
+                            int userId) {
+                        mHandler.post(
+                                () -> handleNotificationPermissionChange(packageName, userId));
+                    }
+                });
     }
 
     /**
@@ -2854,17 +2857,17 @@
             boolean fromListener) {
         if (channel.getImportance() == NotificationManager.IMPORTANCE_NONE) {
             // cancel
-            cancelAllNotificationsInt(MY_UID, MY_PID, pkg, channel.getId(), 0, 0, true,
-                    UserHandle.getUserId(uid), REASON_CHANNEL_BANNED,
-                    null);
+            cancelAllNotificationsInt(MY_UID, MY_PID, pkg, channel.getId(), 0, 0,
+                    UserHandle.getUserId(uid), REASON_CHANNEL_BANNED
+            );
             if (isUidSystemOrPhone(uid)) {
                 IntArray profileIds = mUserProfiles.getCurrentProfileIds();
                 int N = profileIds.size();
                 for (int i = 0; i < N; i++) {
                     int profileId = profileIds.get(i);
-                    cancelAllNotificationsInt(MY_UID, MY_PID, pkg, channel.getId(), 0, 0, true,
-                            profileId, REASON_CHANNEL_BANNED,
-                            null);
+                    cancelAllNotificationsInt(MY_UID, MY_PID, pkg, channel.getId(), 0, 0,
+                            profileId, REASON_CHANNEL_BANNED
+                    );
                 }
             }
         }
@@ -3538,7 +3541,7 @@
             // Don't allow the app to cancel active FGS or UIJ notifications
             cancelAllNotificationsInt(Binder.getCallingUid(), Binder.getCallingPid(),
                     pkg, null, 0, FLAG_FOREGROUND_SERVICE | FLAG_USER_INITIATED_JOB,
-                    true, userId, REASON_APP_CANCEL_ALL, null);
+                    userId, REASON_APP_CANCEL_ALL);
         }
 
         @Override
@@ -3557,20 +3560,16 @@
             }
             mPermissionHelper.setNotificationPermission(
                     pkg, UserHandle.getUserId(uid), enabled, true);
-            sendAppBlockStateChangedBroadcast(pkg, uid, !enabled);
 
             mMetricsLogger.write(new LogMaker(MetricsEvent.ACTION_BAN_APP_NOTES)
                     .setType(MetricsEvent.TYPE_ACTION)
                     .setPackageName(pkg)
                     .setSubtype(enabled ? 1 : 0));
             mNotificationChannelLogger.logAppNotificationsAllowed(uid, pkg, enabled);
-            // Now, cancel any outstanding notifications that are part of a just-disabled app
-            if (!enabled) {
-                cancelAllNotificationsInt(MY_UID, MY_PID, pkg, null, 0, 0, true,
-                        UserHandle.getUserId(uid), REASON_PACKAGE_BANNED, null);
-            }
 
-            handleSavePolicyFile();
+            // Outstanding notifications from this package will be cancelled, and the package will
+            // be sent the ACTION_APP_BLOCK_STATE_CHANGED broadcast, as soon as we get the
+            // callback from AppOpsManager.
         }
 
         /**
@@ -4029,8 +4028,8 @@
             }
             enforceDeletingChannelHasNoFgService(pkg, callingUser, channelId);
             enforceDeletingChannelHasNoUserInitiatedJob(pkg, callingUser, channelId);
-            cancelAllNotificationsInt(MY_UID, MY_PID, pkg, channelId, 0, 0, true,
-                    callingUser, REASON_CHANNEL_REMOVED, null);
+            cancelAllNotificationsInt(MY_UID, MY_PID, pkg, channelId, 0, 0,
+                    callingUser, REASON_CHANNEL_REMOVED);
             boolean previouslyExisted = mPreferencesHelper.deleteNotificationChannel(
                     pkg, callingUid, channelId, callingUid, isSystemOrSystemUi);
             if (previouslyExisted) {
@@ -4084,9 +4083,8 @@
                 for (int i = 0; i < deletedChannels.size(); i++) {
                     final NotificationChannel deletedChannel = deletedChannels.get(i);
                     cancelAllNotificationsInt(MY_UID, MY_PID, pkg, deletedChannel.getId(), 0, 0,
-                            true,
-                            userId, REASON_CHANNEL_REMOVED,
-                            null);
+                            userId, REASON_CHANNEL_REMOVED
+                    );
                     mListeners.notifyNotificationChannelChanged(pkg,
                             UserHandle.getUserHandleForUid(callingUid),
                             deletedChannel,
@@ -4255,8 +4253,8 @@
             checkCallerIsSystem();
             // Cancel posted notifications
             final int userId = UserHandle.getUserId(uid);
-            cancelAllNotificationsInt(MY_UID, MY_PID, packageName, null, 0, 0, true,
-                    UserHandle.getUserId(Binder.getCallingUid()), REASON_CLEAR_DATA, null);
+            cancelAllNotificationsInt(MY_UID, MY_PID, packageName, null, 0, 0,
+                    UserHandle.getUserId(Binder.getCallingUid()), REASON_CLEAR_DATA);
 
             // Zen
             packagesChanged |=
@@ -4338,6 +4336,7 @@
             return getActiveNotificationsWithAttribution(callingPkg, null);
         }
 
+        @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_NOTIFICATIONS)
         /**
          * System-only API for getting a list of current (i.e. not cleared) notifications.
          *
@@ -4348,9 +4347,7 @@
         public StatusBarNotification[] getActiveNotificationsWithAttribution(String callingPkg,
                 String callingAttributionTag) {
             // enforce() will ensure the calling uid has the correct permission
-            getContext().enforceCallingOrSelfPermission(
-                    android.Manifest.permission.ACCESS_NOTIFICATIONS,
-                    "NotificationManagerService.getActiveNotifications");
+            getActiveNotificationsWithAttribution_enforcePermission();
 
             ArrayList<StatusBarNotification> tmp = new ArrayList<>();
             int uid = Binder.getCallingUid();
@@ -4442,7 +4439,7 @@
                     // Remove background token before returning notification to untrusted app, this
                     // ensures the app isn't able to perform background operations that are
                     // associated with notification interactions.
-                    notification.setAllowlistToken(null);
+                    notification.clearAllowlistToken();
                     return new StatusBarNotification(
                             sbn.getPackageName(),
                             sbn.getOpPkg(),
@@ -4466,6 +4463,7 @@
                     includeSnoozed);
         }
 
+        @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_NOTIFICATIONS)
         /**
          * System-only API for getting a list of recent (cleared, no longer shown) notifications.
          */
@@ -4474,9 +4472,7 @@
         public StatusBarNotification[] getHistoricalNotificationsWithAttribution(String callingPkg,
                 String callingAttributionTag, int count, boolean includeSnoozed) {
             // enforce() will ensure the calling uid has the correct permission
-            getContext().enforceCallingOrSelfPermission(
-                    android.Manifest.permission.ACCESS_NOTIFICATIONS,
-                    "NotificationManagerService.getHistoricalNotifications");
+            getHistoricalNotificationsWithAttribution_enforcePermission();
 
             StatusBarNotification[] tmp = null;
             int uid = Binder.getCallingUid();
@@ -4492,6 +4488,7 @@
             return tmp;
         }
 
+        @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_NOTIFICATIONS)
         /**
          * System-only API for getting a list of historical notifications. May contain multiple days
          * of notifications.
@@ -4502,9 +4499,7 @@
         public NotificationHistory getNotificationHistory(String callingPkg,
                 String callingAttributionTag) {
             // enforce() will ensure the calling uid has the correct permission
-            getContext().enforceCallingOrSelfPermission(
-                    android.Manifest.permission.ACCESS_NOTIFICATIONS,
-                    "NotificationManagerService.getNotificationHistory");
+            getNotificationHistory_enforcePermission();
             int uid = Binder.getCallingUid();
 
             // noteOp will check to make sure the callingPkg matches the uid
@@ -5894,6 +5889,21 @@
         }
     };
 
+    private void handleNotificationPermissionChange(String pkg, @UserIdInt int userId) {
+        int uid = mPackageManagerInternal.getPackageUid(pkg, 0, userId);
+        if (uid == INVALID_UID) {
+            Log.e(TAG, String.format("No uid found for %s, %s!", pkg, userId));
+            return;
+        }
+        boolean hasPermission = mPermissionHelper.hasPermission(uid);
+        sendAppBlockStateChangedBroadcast(pkg, uid, !hasPermission);
+        if (!hasPermission) {
+            cancelAllNotificationsInt(MY_UID, MY_PID, pkg, /* channelId= */ null,
+                    /* mustHaveFlags= */ 0, /* mustNotHaveFlags= */ 0, userId,
+                    REASON_PACKAGE_BANNED);
+        }
+    }
+
     protected void checkNotificationListenerAccess() {
         if (!isCallerSystemOrPhone()) {
             getContext().enforceCallingPermission(
@@ -6777,6 +6787,8 @@
             return false;
         }
 
+        mUsageStats.registerEnqueuedByAppAndAccepted(pkg);
+
         if (info != null) {
             // Cache the shortcut synchronously after the associated notification is posted in case
             // the app unpublishes this shortcut immediately after posting the notification. If the
@@ -6828,9 +6840,9 @@
                 mPreferencesHelper.deleteConversations(pkg, uid, shortcuts,
                         /* callingUid */ Process.SYSTEM_UID, /* is system */ true);
         for (String channelId : deletedChannelIds) {
-            cancelAllNotificationsInt(MY_UID, MY_PID, pkg, channelId, 0, 0, true,
-                    UserHandle.getUserId(uid), REASON_CHANNEL_REMOVED,
-                    null);
+            cancelAllNotificationsInt(MY_UID, MY_PID, pkg, channelId, 0, 0,
+                    UserHandle.getUserId(uid), REASON_CHANNEL_REMOVED
+            );
         }
         handleSavePolicyFile();
     }
@@ -7215,11 +7227,12 @@
                             + " cannot create notifications");
                 }
 
-                // rate limit updates that aren't completed progress notifications
-                if (mNotificationsByKey.get(r.getSbn().getKey()) != null
-                        && !r.getNotification().hasCompletedProgress()
-                        && !isAutogroup) {
-
+                // Rate limit updates that aren't completed progress notifications
+                // Search for the original one in the posted and not-yet-posted (enqueued) lists.
+                boolean isUpdate = mNotificationsByKey.get(r.getSbn().getKey()) != null
+                        || findNotificationByListLocked(mEnqueuedNotifications, r.getSbn().getKey())
+                        != null;
+                if (isUpdate && !r.getNotification().hasCompletedProgress() && !isAutogroup) {
                     final float appEnqueueRate = mUsageStats.getAppEnqueueRate(pkg);
                     if (appEnqueueRate > mMaxPackageEnqueueRate) {
                         mUsageStats.registerOverRateQuota(pkg);
@@ -7844,15 +7857,8 @@
             boolean posted = false;
             synchronized (mNotificationLock) {
                 try {
-                    NotificationRecord r = null;
-                    int N = mEnqueuedNotifications.size();
-                    for (int i = 0; i < N; i++) {
-                        final NotificationRecord enqueued = mEnqueuedNotifications.get(i);
-                        if (Objects.equals(key, enqueued.getKey())) {
-                            r = enqueued;
-                            break;
-                        }
-                    }
+                    NotificationRecord r = findNotificationByListLocked(mEnqueuedNotifications,
+                            key);
                     if (r == null) {
                         Slog.i(TAG, "Cannot find enqueued record for key: " + key);
                         return false;
@@ -9511,14 +9517,19 @@
      * Determine whether the userId applies to the notification in question, either because
      * they match exactly, or one of them is USER_ALL (which is treated as a wildcard).
      */
-    private boolean notificationMatchesUserId(NotificationRecord r, int userId) {
-        return
+    private static boolean notificationMatchesUserId(NotificationRecord r, int userId,
+            boolean isAutogroupSummary) {
+        if (isAutogroupSummary) {
+            return r.getUserId() == userId;
+        } else {
+            return
                 // looking for USER_ALL notifications? match everything
-                   userId == UserHandle.USER_ALL
-                // a notification sent to USER_ALL matches any query
-                || r.getUserId() == UserHandle.USER_ALL
-                // an exact user match
-                || r.getUserId() == userId;
+                userId == UserHandle.USER_ALL
+                        // a notification sent to USER_ALL matches any query
+                        || r.getUserId() == UserHandle.USER_ALL
+                        // an exact user match
+                        || r.getUserId() == userId;
+        }
     }
 
     /**
@@ -9527,31 +9538,24 @@
      * because it matches one of the users profiles.
      */
     private boolean notificationMatchesCurrentProfiles(NotificationRecord r, int userId) {
-        return notificationMatchesUserId(r, userId)
+        return notificationMatchesUserId(r, userId, false)
                 || mUserProfiles.isCurrentProfile(r.getUserId());
     }
 
     /**
      * Cancels all notifications from a given package that have all of the
-     * {@code mustHaveFlags}.
+     * {@code mustHaveFlags} and none of the {@code mustNotHaveFlags}.
      */
-    void cancelAllNotificationsInt(int callingUid, int callingPid, String pkg, String channelId,
-            int mustHaveFlags, int mustNotHaveFlags, boolean doit, int userId, int reason,
-            ManagedServiceInfo listener) {
+    void cancelAllNotificationsInt(int callingUid, int callingPid, String pkg,
+            @Nullable String channelId, int mustHaveFlags, int mustNotHaveFlags, int userId,
+            int reason) {
         final long cancellationElapsedTimeMs = SystemClock.elapsedRealtime();
         mHandler.post(new Runnable() {
             @Override
             public void run() {
-                String listenerName = listener == null ? null : listener.component.toShortString();
                 EventLogTags.writeNotificationCancelAll(callingUid, callingPid,
                         pkg, userId, mustHaveFlags, mustNotHaveFlags, reason,
-                        listenerName);
-
-                // Why does this parameter exist? Do we actually want to execute the above if doit
-                // is false?
-                if (!doit) {
-                    return;
-                }
+                        /* listener= */ null);
 
                 synchronized (mNotificationLock) {
                     FlagChecker flagChecker = (int flags) -> {
@@ -9563,14 +9567,15 @@
                         }
                         return true;
                     };
-                    cancelAllNotificationsByListLocked(mNotificationList, callingUid, callingPid,
-                            pkg, true /*nullPkgIndicatesUserSwitch*/, channelId, flagChecker,
+                    cancelAllNotificationsByListLocked(mNotificationList, pkg,
+                            true /*nullPkgIndicatesUserSwitch*/, channelId, flagChecker,
                             false /*includeCurrentProfiles*/, userId, false /*sendDelete*/, reason,
-                            listenerName, true /* wasPosted */, cancellationElapsedTimeMs);
-                    cancelAllNotificationsByListLocked(mEnqueuedNotifications, callingUid,
-                            callingPid, pkg, true /*nullPkgIndicatesUserSwitch*/, channelId,
-                            flagChecker, false /*includeCurrentProfiles*/, userId,
-                            false /*sendDelete*/, reason, listenerName, false /* wasPosted */,
+                            null /* listenerName */, true /* wasPosted */,
+                            cancellationElapsedTimeMs);
+                    cancelAllNotificationsByListLocked(mEnqueuedNotifications, pkg,
+                            true /*nullPkgIndicatesUserSwitch*/, channelId, flagChecker,
+                            false /*includeCurrentProfiles*/, userId, false /*sendDelete*/, reason,
+                            null /* listenerName */, false /* wasPosted */,
                             cancellationElapsedTimeMs);
                     mSnoozeHelper.cancel(userId, pkg);
                 }
@@ -9585,9 +9590,9 @@
 
     @GuardedBy("mNotificationLock")
     private void cancelAllNotificationsByListLocked(ArrayList<NotificationRecord> notificationList,
-            int callingUid, int callingPid, String pkg, boolean nullPkgIndicatesUserSwitch,
-            String channelId, FlagChecker flagChecker, boolean includeCurrentProfiles, int userId,
-            boolean sendDelete, int reason, String listenerName, boolean wasPosted,
+            @Nullable String pkg, boolean nullPkgIndicatesUserSwitch, @Nullable String channelId,
+            FlagChecker flagChecker, boolean includeCurrentProfiles, int userId, boolean sendDelete,
+            int reason, String listenerName, boolean wasPosted,
             @ElapsedRealtimeLong long cancellationElapsedTimeMs) {
         Set<String> childNotifications = null;
         for (int i = notificationList.size() - 1; i >= 0; --i) {
@@ -9596,7 +9601,7 @@
                 if (!notificationMatchesCurrentProfiles(r, userId)) {
                     continue;
                 }
-            } else if (!notificationMatchesUserId(r, userId)) {
+            } else if (!notificationMatchesUserId(r, userId, false)) {
                 continue;
             }
             // Don't remove notifications to all, if there's no package name specified
@@ -9704,12 +9709,12 @@
                         return true;
                     };
 
-                    cancelAllNotificationsByListLocked(mNotificationList, callingUid, callingPid,
+                    cancelAllNotificationsByListLocked(mNotificationList,
                             null, false /*nullPkgIndicatesUserSwitch*/, null, flagChecker,
                             includeCurrentProfiles, userId, true /*sendDelete*/, reason,
                             listenerName, true, cancellationElapsedTimeMs);
-                    cancelAllNotificationsByListLocked(mEnqueuedNotifications, callingUid,
-                            callingPid, null, false /*nullPkgIndicatesUserSwitch*/, null,
+                    cancelAllNotificationsByListLocked(mEnqueuedNotifications,
+                            null, false /*nullPkgIndicatesUserSwitch*/, null,
                             flagChecker, includeCurrentProfiles, userId, true /*sendDelete*/,
                             reason, listenerName, false, cancellationElapsedTimeMs);
                     mSnoozeHelper.cancel(userId, includeCurrentProfiles);
@@ -9834,7 +9839,7 @@
         final int len = list.size();
         for (int i = 0; i < len; i++) {
             NotificationRecord r = list.get(i);
-            if (notificationMatchesUserId(r, userId) && r.getGroupKey().equals(groupKey)
+            if (notificationMatchesUserId(r, userId, false) && r.getGroupKey().equals(groupKey)
                     && r.getSbn().getPackageName().equals(pkg)) {
                 records.add(r);
             }
@@ -9870,14 +9875,14 @@
         return null;
     }
 
-    @GuardedBy("mNotificationLock")
-    private NotificationRecord findNotificationByListLocked(ArrayList<NotificationRecord> list,
-            String pkg, String tag, int id, int userId) {
+    @Nullable
+    private static NotificationRecord findNotificationByListLocked(
+            ArrayList<NotificationRecord> list, String pkg, String tag, int id, int userId) {
         final int len = list.size();
         for (int i = 0; i < len; i++) {
             NotificationRecord r = list.get(i);
-            if (notificationMatchesUserId(r, userId) && r.getSbn().getId() == id &&
-                    TextUtils.equals(r.getSbn().getTag(), tag)
+            if (notificationMatchesUserId(r, userId, (r.getFlags() & GroupHelper.BASE_FLAGS) != 0)
+                    && r.getSbn().getId() == id && TextUtils.equals(r.getSbn().getTag(), tag)
                     && r.getSbn().getPackageName().equals(pkg)) {
                 return r;
             }
@@ -9885,15 +9890,14 @@
         return null;
     }
 
-    @GuardedBy("mNotificationLock")
-    private List<NotificationRecord> findNotificationsByListLocked(
+    private static List<NotificationRecord> findNotificationsByListLocked(
             ArrayList<NotificationRecord> list, String pkg, String tag, int id, int userId) {
         List<NotificationRecord> matching = new ArrayList<>();
         final int len = list.size();
         for (int i = 0; i < len; i++) {
             NotificationRecord r = list.get(i);
-            if (notificationMatchesUserId(r, userId) && r.getSbn().getId() == id &&
-                    TextUtils.equals(r.getSbn().getTag(), tag)
+            if (notificationMatchesUserId(r, userId, false) && r.getSbn().getId() == id
+                    && TextUtils.equals(r.getSbn().getTag(), tag)
                     && r.getSbn().getPackageName().equals(pkg)) {
                 matching.add(r);
             }
@@ -9901,9 +9905,9 @@
         return matching;
     }
 
-    @GuardedBy("mNotificationLock")
-    private NotificationRecord findNotificationByListLocked(ArrayList<NotificationRecord> list,
-            String key) {
+    @Nullable
+    private static NotificationRecord findNotificationByListLocked(
+            ArrayList<NotificationRecord> list, String key) {
         final int N = list.size();
         for (int i = 0; i < N; i++) {
             if (key.equals(list.get(i).getKey())) {
@@ -9913,29 +9917,6 @@
         return null;
     }
 
-    /**
-     * There may be multiple records that match your criteria. For instance if there have been
-     * multiple notifications posted which are enqueued for the same pkg, tag, id, userId. This
-     * method will find all of them in the given list
-     * @return
-     */
-    @GuardedBy("mNotificationLock")
-    private List<NotificationRecord> findEnqueuedNotificationsForCriteria(
-            String pkg, String tag, int id, int userId) {
-        final ArrayList<NotificationRecord> records = new ArrayList<>();
-        final int n = mEnqueuedNotifications.size();
-        for (int i = 0; i < n; i++) {
-            NotificationRecord r = mEnqueuedNotifications.get(i);
-            if (notificationMatchesUserId(r, userId)
-                    && r.getSbn().getId() == id
-                    && TextUtils.equals(r.getSbn().getTag(), tag)
-                    && r.getSbn().getPackageName().equals(pkg)) {
-                records.add(r);
-            }
-        }
-        return records;
-    }
-
     @GuardedBy("mNotificationLock")
     int indexOfNotificationLocked(String key) {
         final int N = mNotificationList.size();
@@ -10526,6 +10507,11 @@
         }
 
         @Override
+        protected boolean allowRebindForParentUser() {
+            return false;
+        }
+
+        @Override
         protected String getRequiredPermission() {
             // only signature/privileged apps can be bound.
             return android.Manifest.permission.REQUEST_NOTIFICATION_ASSISTANT_SERVICE;
@@ -11070,6 +11056,11 @@
         }
 
         @Override
+        protected boolean allowRebindForParentUser() {
+            return true;
+        }
+
+        @Override
         public void onPackagesChanged(boolean removingPackage, String[] pkgList, int[] uidList) {
             super.onPackagesChanged(removingPackage, pkgList, uidList);
 
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index c9a6c63..b2d3fca 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -27,6 +27,7 @@
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.IActivityManager;
+import android.app.KeyguardManager;
 import android.app.Notification;
 import android.app.NotificationChannel;
 import android.app.Person;
@@ -46,6 +47,7 @@
 import android.os.Build;
 import android.os.Bundle;
 import android.os.IBinder;
+import android.os.PowerManager;
 import android.os.UserHandle;
 import android.os.VibrationEffect;
 import android.provider.Settings;
@@ -101,7 +103,8 @@
     final int mTargetSdkVersion;
     final int mOriginalFlags;
     private final Context mContext;
-
+    private KeyguardManager mKeyguardManager;
+    private final PowerManager mPowerManager;
     NotificationUsageStats.SingleNotificationStats stats;
     boolean isCanceled;
     IBinder permissionOwner;
@@ -228,6 +231,8 @@
         mUpdateTimeMs = mCreationTimeMs;
         mInterruptionTimeMs = mCreationTimeMs;
         mContext = context;
+        mKeyguardManager = mContext.getSystemService(KeyguardManager.class);
+        mPowerManager = mContext.getSystemService(PowerManager.class);
         stats = new NotificationUsageStats.SingleNotificationStats();
         mChannel = channel;
         mPreChannelsNotification = isPreChannelsNotification();
@@ -1619,6 +1624,22 @@
         return mPhoneNumbers;
     }
 
+    boolean isLocked() {
+        return getKeyguardManager().isKeyguardLocked()
+                || !mPowerManager.isInteractive();  // Unlocked AOD
+    }
+
+    /**
+     * For some early {@link NotificationRecord}, {@link KeyguardManager} can be {@code null} in
+     * the constructor. Retrieve it again if it is null.
+     */
+    private KeyguardManager getKeyguardManager() {
+        if (mKeyguardManager == null) {
+            mKeyguardManager = mContext.getSystemService(KeyguardManager.class);
+        }
+        return mKeyguardManager;
+    }
+
     @VisibleForTesting
     static final class Light {
         public final int color;
diff --git a/services/core/java/com/android/server/notification/NotificationRecordLogger.java b/services/core/java/com/android/server/notification/NotificationRecordLogger.java
index 0cc4fc4..b015a72 100644
--- a/services/core/java/com/android/server/notification/NotificationRecordLogger.java
+++ b/services/core/java/com/android/server/notification/NotificationRecordLogger.java
@@ -30,10 +30,14 @@
 import android.os.Bundle;
 import android.service.notification.NotificationListenerService;
 import android.service.notification.NotificationStats;
+import android.util.Log;
 
+import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags;
+import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags;
 import com.android.internal.logging.InstanceId;
 import com.android.internal.logging.UiEvent;
 import com.android.internal.logging.UiEventLogger;
+import com.android.internal.util.FrameworkStatsLog;
 
 import java.util.ArrayList;
 import java.util.Objects;
@@ -45,6 +49,8 @@
  */
 interface NotificationRecordLogger {
 
+    static final String TAG = "NotificationRecordLogger";
+
     // The high-level interface used by clients.
 
     /**
@@ -225,51 +231,40 @@
                 @NotificationStats.DismissalSurface int surface) {
             // Shouldn't be possible to get a non-dismissed notification here.
             if (surface == NotificationStats.DISMISSAL_NOT_DISMISSED) {
-                if (NotificationManagerService.DBG) {
-                    throw new IllegalArgumentException("Unexpected surface " + surface);
-                }
+                Log.wtf(TAG, "Unexpected surface: " + surface + " with reason " + reason);
                 return INVALID;
             }
-            // Most cancel reasons do not have a meaningful surface. Reason codes map directly
-            // to NotificationCancelledEvent codes.
-            if (surface == NotificationStats.DISMISSAL_OTHER) {
+
+            // User cancels have a meaningful surface, which we differentiate by. See b/149038335
+            // for caveats.
+            if (reason == REASON_CANCEL) {
+                switch (surface) {
+                    case NotificationStats.DISMISSAL_PEEK:
+                        return NOTIFICATION_CANCEL_USER_PEEK;
+                    case NotificationStats.DISMISSAL_AOD:
+                        return NOTIFICATION_CANCEL_USER_AOD;
+                    case NotificationStats.DISMISSAL_SHADE:
+                        return NOTIFICATION_CANCEL_USER_SHADE;
+                    case NotificationStats.DISMISSAL_BUBBLE:
+                        return NOTIFICATION_CANCEL_USER_BUBBLE;
+                    case NotificationStats.DISMISSAL_LOCKSCREEN:
+                        return NOTIFICATION_CANCEL_USER_LOCKSCREEN;
+                    case NotificationStats.DISMISSAL_OTHER:
+                        return NOTIFICATION_CANCEL_USER_OTHER;
+                    default:
+                        Log.wtf(TAG, "Unexpected surface: " + surface + " with reason " + reason);
+                        return INVALID;
+                }
+            } else {
                 if ((REASON_CLICK <= reason) && (reason <= REASON_CLEAR_DATA)) {
                     return NotificationCancelledEvent.values()[reason];
                 }
                 if (reason == REASON_ASSISTANT_CANCEL) {
                     return NotificationCancelledEvent.NOTIFICATION_CANCEL_ASSISTANT;
                 }
-                if (NotificationManagerService.DBG) {
-                    throw new IllegalArgumentException("Unexpected cancel reason " + reason);
-                }
+                Log.wtf(TAG, "Unexpected reason: " + reason + " with surface " + surface);
                 return INVALID;
             }
-            // User cancels have a meaningful surface, which we differentiate by. See b/149038335
-            // for caveats.
-            if (reason != REASON_CANCEL) {
-                if (NotificationManagerService.DBG) {
-                    throw new IllegalArgumentException("Unexpected cancel with surface " + reason);
-                }
-                return INVALID;
-            }
-            switch (surface) {
-                case NotificationStats.DISMISSAL_PEEK:
-                    return NOTIFICATION_CANCEL_USER_PEEK;
-                case NotificationStats.DISMISSAL_AOD:
-                    return NOTIFICATION_CANCEL_USER_AOD;
-                case NotificationStats.DISMISSAL_SHADE:
-                    return NOTIFICATION_CANCEL_USER_SHADE;
-                case NotificationStats.DISMISSAL_BUBBLE:
-                    return NOTIFICATION_CANCEL_USER_BUBBLE;
-                case NotificationStats.DISMISSAL_LOCKSCREEN:
-                    return NOTIFICATION_CANCEL_USER_LOCKSCREEN;
-                default:
-                    if (NotificationManagerService.DBG) {
-                        throw new IllegalArgumentException("Unexpected surface for user-dismiss "
-                                + reason);
-                    }
-                    return INVALID;
-            }
         }
     }
 
@@ -500,6 +495,8 @@
         final boolean is_foreground_service;
         final long timeout_millis;
         final boolean is_non_dismissible;
+        final int fsi_state;
+        final boolean is_locked;
         @DurationMillisLong long post_duration_millis; // Not final; calculated at the end.
 
         NotificationReported(NotificationRecordPair p,
@@ -530,6 +527,20 @@
             this.is_foreground_service = NotificationRecordLogger.isForegroundService(p.r);
             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,
+                    hasFullScreenIntent, hasFsiRequestedButDeniedFlag, eventType);
+
+            this.is_locked = p.r.isLocked();
         }
     }
 
@@ -558,7 +569,6 @@
     }
 
     /**
-     * @param r NotificationRecord
      * @return Whether the notification is a non-dismissible notification.
      */
     static boolean isNonDismissible(@NonNull NotificationRecord r) {
@@ -567,4 +577,28 @@
         }
         return (r.getNotification().flags & Notification.FLAG_NO_DISMISS) != 0;
     }
+
+    /**
+     * @return FrameworkStatsLog enum of the state of the full screen intent posted with this
+     * notification.
+     */
+    static int getFsiState(boolean isStickyHunFlagEnabled,
+                           boolean hasFullScreenIntent,
+                           boolean hasFsiRequestedButDeniedFlag,
+                           NotificationReportedEvent eventType) {
+
+        if (!isStickyHunFlagEnabled
+                || 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;
+        }
+        if (hasFullScreenIntent) {
+            return FrameworkStatsLog.NOTIFICATION_REPORTED__FSI_STATE__FSI_ALLOWED;
+        }
+        if (hasFsiRequestedButDeniedFlag) {
+            return FrameworkStatsLog.NOTIFICATION_REPORTED__FSI_STATE__FSI_DENIED;
+        }
+        return FrameworkStatsLog.NOTIFICATION_REPORTED__FSI_STATE__NO_FSI;
+    }
 }
diff --git a/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java b/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java
index feb75ef..9da0e98 100644
--- a/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java
+++ b/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java
@@ -75,7 +75,9 @@
                 notificationReported.is_foreground_service,
                 notificationReported.timeout_millis,
                 notificationReported.is_non_dismissible,
-                notificationReported.post_duration_millis);
+                notificationReported.post_duration_millis,
+                notificationReported.fsi_state,
+                notificationReported.is_locked);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/notification/NotificationUsageStats.java b/services/core/java/com/android/server/notification/NotificationUsageStats.java
index ffe33a8..e960f4b 100644
--- a/services/core/java/com/android/server/notification/NotificationUsageStats.java
+++ b/services/core/java/com/android/server/notification/NotificationUsageStats.java
@@ -16,23 +16,16 @@
 
 package com.android.server.notification;
 
-import static android.app.NotificationManager.IMPORTANCE_HIGH;
-
 import android.app.Notification;
-import android.content.ContentValues;
 import android.content.Context;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteFullException;
-import android.database.sqlite.SQLiteOpenHelper;
 import android.os.Handler;
-import android.os.HandlerThread;
 import android.os.Message;
 import android.os.SystemClock;
 import android.text.TextUtils;
 import android.util.ArraySet;
 import android.util.Log;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.logging.MetricsLogger;
 import com.android.server.notification.NotificationManagerService.DumpFilter;
 
@@ -42,8 +35,6 @@
 
 import java.io.PrintWriter;
 import java.util.ArrayDeque;
-import java.util.Calendar;
-import java.util.GregorianCalendar;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Set;
@@ -63,7 +54,6 @@
     private static final String TAG = "NotificationUsageStats";
 
     private static final boolean ENABLE_AGGREGATED_IN_MEMORY_STATS = true;
-    private static final boolean ENABLE_SQLITE_LOG = true;
     private static final AggregatedStats[] EMPTY_AGGREGATED_STATS = new AggregatedStats[0];
     private static final String DEVICE_GLOBAL_STATS = "__global"; // packages start with letters
     private static final int MSG_EMIT = 1;
@@ -73,12 +63,15 @@
     public static final int FOUR_HOURS = 1000 * 60 * 60 * 4;
     private static final long EMIT_PERIOD = DEBUG ? TEN_SECONDS : FOUR_HOURS;
 
-    // Guarded by synchronized(this).
+    @GuardedBy("this")
     private final Map<String, AggregatedStats> mStats = new HashMap<>();
+    @GuardedBy("this")
     private final ArrayDeque<AggregatedStats[]> mStatsArrays = new ArrayDeque<>();
+    @GuardedBy("this")
     private ArraySet<String> mStatExpiredkeys = new ArraySet<>();
     private final Context mContext;
     private final Handler mHandler;
+    @GuardedBy("this")
     private long mLastEmitTime;
 
     public NotificationUsageStats(Context context) {
@@ -105,11 +98,7 @@
      */
     public synchronized float getAppEnqueueRate(String packageName) {
         AggregatedStats stats = getOrCreateAggregatedStatsLocked(packageName);
-        if (stats != null) {
-            return stats.getEnqueueRate(SystemClock.elapsedRealtime());
-        } else {
-            return 0f;
-        }
+        return stats.getEnqueueRate(SystemClock.elapsedRealtime());
     }
 
     /**
@@ -117,11 +106,7 @@
      */
     public synchronized boolean isAlertRateLimited(String packageName) {
         AggregatedStats stats = getOrCreateAggregatedStatsLocked(packageName);
-        if (stats != null) {
-            return stats.isAlertRateLimited();
-        } else {
-            return false;
-        }
+        return stats.isAlertRateLimited();
     }
 
     /**
@@ -136,16 +121,32 @@
     }
 
     /**
+     * Called when a notification that was enqueued by an app is effectively enqueued to be
+     * posted. This is after rate checking, to update the rate.
+     *
+     * <p>Note that if we updated the arrival estimate <em>before</em> checking it, then an app
+     * enqueueing at slightly above the acceptable rate would never get their notifications
+     * accepted; updating afterwards allows the rate to dip below the threshold and thus lets
+     * through some of them.
+     */
+    public synchronized void registerEnqueuedByAppAndAccepted(String packageName) {
+        final long now = SystemClock.elapsedRealtime();
+        AggregatedStats[] aggregatedStatsArray = getAggregatedStatsLocked(packageName);
+        for (AggregatedStats stats : aggregatedStatsArray) {
+            stats.updateInterarrivalEstimate(now);
+        }
+        releaseAggregatedStatsLocked(aggregatedStatsArray);
+    }
+
+    /**
      * Called when a notification has been posted.
      */
     public synchronized void registerPostedByApp(NotificationRecord notification) {
-        final long now = SystemClock.elapsedRealtime();
-        notification.stats.posttimeElapsedMs = now;
+        notification.stats.posttimeElapsedMs = SystemClock.elapsedRealtime();
 
         AggregatedStats[] aggregatedStatsArray = getAggregatedStatsLocked(notification);
         for (AggregatedStats stats : aggregatedStatsArray) {
             stats.numPostedByApp++;
-            stats.updateInterarrivalEstimate(now);
             stats.countApiUse(notification);
             stats.numUndecoratedRemoteViews += (notification.hasUndecoratedRemoteView() ? 1 : 0);
         }
@@ -161,7 +162,6 @@
         AggregatedStats[] aggregatedStatsArray = getAggregatedStatsLocked(notification);
         for (AggregatedStats stats : aggregatedStatsArray) {
             stats.numUpdatedByApp++;
-            stats.updateInterarrivalEstimate(SystemClock.elapsedRealtime());
             stats.countApiUse(notification);
         }
         releaseAggregatedStatsLocked(aggregatedStatsArray);
@@ -257,12 +257,12 @@
         }
     }
 
-    // Locked by this.
+    @GuardedBy("this")
     private AggregatedStats[] getAggregatedStatsLocked(NotificationRecord record) {
         return getAggregatedStatsLocked(record.getSbn().getPackageName());
     }
 
-    // Locked by this.
+    @GuardedBy("this")
     private AggregatedStats[] getAggregatedStatsLocked(String packageName) {
         if (!ENABLE_AGGREGATED_IN_MEMORY_STATS) {
             return EMPTY_AGGREGATED_STATS;
@@ -277,7 +277,7 @@
         return array;
     }
 
-    // Locked by this.
+    @GuardedBy("this")
     private void releaseAggregatedStatsLocked(AggregatedStats[] array) {
         for(int i = 0; i < array.length; i++) {
             array[i] = null;
@@ -285,7 +285,7 @@
         mStatsArrays.offer(array);
     }
 
-    // Locked by this.
+    @GuardedBy("this")
     private AggregatedStats getOrCreateAggregatedStatsLocked(String key) {
         AggregatedStats result = mStats.get(key);
         if (result == null) {
diff --git a/services/core/java/com/android/server/notification/PermissionHelper.java b/services/core/java/com/android/server/notification/PermissionHelper.java
index 93c83e1..85c140c 100644
--- a/services/core/java/com/android/server/notification/PermissionHelper.java
+++ b/services/core/java/com/android/server/notification/PermissionHelper.java
@@ -196,19 +196,20 @@
             int uid = mPackageManager.getPackageUid(packageName, 0, userId);
             boolean currentlyGranted = hasPermission(uid);
             if (grant && !currentlyGranted) {
-                mPermManager.grantRuntimePermission(packageName, NOTIFICATION_PERMISSION, userId);
+                mPermManager.grantRuntimePermission(packageName, NOTIFICATION_PERMISSION,
+                        Context.DEVICE_ID_DEFAULT, userId);
             } else if (!grant && currentlyGranted) {
                 mPermManager.revokeRuntimePermission(packageName, NOTIFICATION_PERMISSION,
-                        userId, TAG);
+                        Context.DEVICE_ID_DEFAULT, userId, TAG);
             }
             int flagMask = FLAG_PERMISSION_USER_SET | FLAG_PERMISSION_USER_FIXED;
             flagMask = userSet || !grant ? flagMask | FLAG_PERMISSION_GRANTED_BY_DEFAULT : flagMask;
             if (userSet) {
-                mPermManager.updatePermissionFlags(packageName, NOTIFICATION_PERMISSION,
-                        flagMask, FLAG_PERMISSION_USER_SET, true, userId);
+                mPermManager.updatePermissionFlags(packageName, NOTIFICATION_PERMISSION, flagMask,
+                        FLAG_PERMISSION_USER_SET, true, Context.DEVICE_ID_DEFAULT, userId);
             } else {
                 mPermManager.updatePermissionFlags(packageName, NOTIFICATION_PERMISSION,
-                        flagMask, 0, true, userId);
+                        flagMask, 0, true, Context.DEVICE_ID_DEFAULT, userId);
             }
         } catch (RemoteException e) {
             Slog.e(TAG, "Could not reach system server", e);
@@ -236,7 +237,7 @@
         try {
             try {
                 int flags = mPermManager.getPermissionFlags(packageName, NOTIFICATION_PERMISSION,
-                        userId);
+                        Context.DEVICE_ID_DEFAULT, userId);
                 return (flags & PackageManager.FLAG_PERMISSION_SYSTEM_FIXED) != 0
                         || (flags & PackageManager.FLAG_PERMISSION_POLICY_FIXED) != 0;
             } catch (RemoteException e) {
@@ -253,7 +254,7 @@
         try {
             try {
                 int flags = mPermManager.getPermissionFlags(packageName, NOTIFICATION_PERMISSION,
-                        userId);
+                        Context.DEVICE_ID_DEFAULT, userId);
                 return (flags & (PackageManager.FLAG_PERMISSION_USER_SET
                         | PackageManager.FLAG_PERMISSION_USER_FIXED)) != 0;
             } catch (RemoteException e) {
@@ -270,7 +271,7 @@
         try {
             try {
                 int flags = mPermManager.getPermissionFlags(packageName, NOTIFICATION_PERMISSION,
-                        userId);
+                        Context.DEVICE_ID_DEFAULT, userId);
                 return (flags & (PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT
                         | PackageManager.FLAG_PERMISSION_GRANTED_BY_ROLE)) != 0;
             } catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 4399a3c..1818d25 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -30,6 +30,9 @@
 import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_GROUP_PREFERENCES;
 import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES;
 import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES;
+import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__DENIED;
+import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__GRANTED;
+import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__NOT_REQUESTED;
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
@@ -41,6 +44,7 @@
 import android.app.NotificationChannel;
 import android.app.NotificationChannelGroup;
 import android.app.NotificationManager;
+import android.content.AttributionSource;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
@@ -53,6 +57,7 @@
 import android.os.Build;
 import android.os.Process;
 import android.os.UserHandle;
+import android.permission.PermissionManager;
 import android.provider.Settings;
 import android.service.notification.ConversationChannelWrapper;
 import android.service.notification.NotificationListenerService;
@@ -72,6 +77,8 @@
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags;
+import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.util.Preconditions;
 import com.android.internal.util.XmlUtils;
@@ -157,6 +164,9 @@
     static final int DEFAULT_BUBBLE_PREFERENCE = BUBBLE_PREFERENCE_NONE;
     static final boolean DEFAULT_MEDIA_NOTIFICATION_FILTERING = true;
 
+    private static final int NOTIFICATION_UPDATE_LOG_SUBTYPE_FROM_APP = 0;
+    private static final int NOTIFICATION_UPDATE_LOG_SUBTYPE_FROM_USER = 1;
+
     /**
      * Default value for what fields are user locked. See {@link LockableAppFields} for all lockable
      * fields.
@@ -183,6 +193,7 @@
     private final RankingHandler mRankingHandler;
     private final ZenModeHelper mZenModeHelper;
     private final PermissionHelper mPermissionHelper;
+    private final PermissionManager mPermissionManager;
     private final NotificationChannelLogger mNotificationChannelLogger;
     private final AppOpsManager mAppOps;
 
@@ -198,7 +209,7 @@
     private boolean mAllowInvalidShortcuts = false;
 
     public PreferencesHelper(Context context, PackageManager pm, RankingHandler rankingHandler,
-            ZenModeHelper zenHelper, PermissionHelper permHelper,
+            ZenModeHelper zenHelper, PermissionHelper permHelper, PermissionManager permManager,
             NotificationChannelLogger notificationChannelLogger,
             AppOpsManager appOpsManager,
             SysUiStatsEvent.BuilderFactory statsEventBuilderFactory,
@@ -207,6 +218,7 @@
         mZenModeHelper = zenHelper;
         mRankingHandler = rankingHandler;
         mPermissionHelper = permHelper;
+        mPermissionManager = permManager;
         mPm = pm;
         mNotificationChannelLogger = notificationChannelLogger;
         mAppOps = appOpsManager;
@@ -1101,12 +1113,20 @@
             if (!channel.equals(updatedChannel)) {
                 // only log if there are real changes
                 MetricsLogger.action(getChannelLog(updatedChannel, pkg)
-                        .setSubtype(fromUser ? 1 : 0));
+                        .setSubtype(fromUser ? NOTIFICATION_UPDATE_LOG_SUBTYPE_FROM_USER
+                                : NOTIFICATION_UPDATE_LOG_SUBTYPE_FROM_APP));
                 mNotificationChannelLogger.logNotificationChannelModified(updatedChannel, uid, pkg,
                         NotificationChannelLogger.getLoggingImportance(channel), fromUser);
                 changed = true;
             }
 
+            if (fromUser && SystemUiSystemPropertiesFlags.getResolver().isEnabled(
+                    NotificationFlags.PROPAGATE_CHANNEL_UPDATES_TO_CONVERSATIONS)) {
+                updateChildrenConversationChannels(r, channel, updatedChannel);
+                // No need to update changed or needsDndChanged as the child channel(s) cannot be
+                // relevantly affected without the parent channel already having been.
+            }
+
             if (updatedChannel.canBypassDnd() != mAreChannelsBypassingDnd
                     || channel.getImportance() != updatedChannel.getImportance()) {
                 needsDndChange = true;
@@ -1121,6 +1141,101 @@
         }
     }
 
+    /**
+     * Updates conversation channels after user changes to their parent channel. See
+     * {@link #maybeUpdateChildConversationChannel}.
+     */
+    @GuardedBy("mPackagePreferences")
+    private void updateChildrenConversationChannels(@NonNull PackagePreferences packagePreferences,
+            @NonNull NotificationChannel oldParent, @NonNull NotificationChannel updatedParent) {
+        if (oldParent.equals(updatedParent)) {
+            return;
+        }
+        if (oldParent.isConversation()) {
+            return; // Can't have children.
+        }
+        for (NotificationChannel channel : packagePreferences.channels.values()) {
+            // Include deleted -- otherwise they will have old settings if later resurrected.
+            // Include demoted -- still attached to their parents.
+            if (channel.isConversation()
+                    && oldParent.getId().equals(channel.getParentChannelId())) {
+                maybeUpdateChildConversationChannel(packagePreferences.pkg, packagePreferences.uid,
+                        channel, oldParent, updatedParent);
+            }
+        }
+    }
+
+    /**
+     * Apply the diff between {@code oldParent} and {@code updatedParent} to the child
+     * {@code conversation} channel. Only fields that are not locked on the conversation channel
+     * (see {@link NotificationChannel#LOCKABLE_FIELDS }) will be updated (so that we don't override
+     * previous explicit user choices).
+     *
+     * <p>This will also log the change as if it was {@code fromUser=true}.
+     */
+    @GuardedBy("mPackagePreferences")
+    private void maybeUpdateChildConversationChannel(String pkg, int uid,
+            @NonNull NotificationChannel conversation, @NonNull NotificationChannel oldParent,
+            @NonNull NotificationChannel updatedParent) {
+        boolean changed = false;
+        int oldLoggingImportance = NotificationChannelLogger.getLoggingImportance(conversation);
+
+        if ((conversation.getUserLockedFields() & NotificationChannel.USER_LOCKED_PRIORITY) == 0
+                && oldParent.canBypassDnd() != updatedParent.canBypassDnd()) {
+            conversation.setBypassDnd(updatedParent.canBypassDnd());
+            changed = true;
+        }
+        if ((conversation.getUserLockedFields() & NotificationChannel.USER_LOCKED_VISIBILITY) == 0
+                && oldParent.getLockscreenVisibility()
+                != updatedParent.getLockscreenVisibility()) {
+            conversation.setLockscreenVisibility(updatedParent.getLockscreenVisibility());
+            changed = true;
+        }
+        if ((conversation.getUserLockedFields() & NotificationChannel.USER_LOCKED_IMPORTANCE) == 0
+                && oldParent.getImportance() != updatedParent.getImportance()) {
+            conversation.setImportance(updatedParent.getImportance());
+            changed = true;
+        }
+        if ((conversation.getUserLockedFields() & NotificationChannel.USER_LOCKED_LIGHTS) == 0
+                && (oldParent.shouldShowLights() != updatedParent.shouldShowLights()
+                || oldParent.getLightColor() != updatedParent.getLightColor())) {
+            conversation.enableLights(updatedParent.shouldShowLights());
+            conversation.setLightColor(updatedParent.getLightColor());
+            changed = true;
+        }
+        if ((conversation.getUserLockedFields() & NotificationChannel.USER_LOCKED_SOUND) == 0
+                && !Objects.equals(oldParent.getSound(), updatedParent.getSound())) {
+            conversation.setSound(updatedParent.getSound(), updatedParent.getAudioAttributes());
+            changed = true;
+        }
+        if ((conversation.getUserLockedFields() & NotificationChannel.USER_LOCKED_VIBRATION) == 0
+                && (!Arrays.equals(oldParent.getVibrationPattern(),
+                updatedParent.getVibrationPattern())
+                || oldParent.shouldVibrate() != updatedParent.shouldVibrate())) {
+            // enableVibration must be 2nd because setVibrationPattern may toggle it.
+            conversation.setVibrationPattern(updatedParent.getVibrationPattern());
+            conversation.enableVibration(updatedParent.shouldVibrate());
+            changed = true;
+        }
+        if ((conversation.getUserLockedFields() & NotificationChannel.USER_LOCKED_SHOW_BADGE) == 0
+                && oldParent.canShowBadge() != updatedParent.canShowBadge()) {
+            conversation.setShowBadge(updatedParent.canShowBadge());
+            changed = true;
+        }
+        if ((conversation.getUserLockedFields() & NotificationChannel.USER_LOCKED_ALLOW_BUBBLE) == 0
+                && oldParent.getAllowBubbles() != updatedParent.getAllowBubbles()) {
+            conversation.setAllowBubbles(updatedParent.getAllowBubbles());
+            changed = true;
+        }
+
+        if (changed) {
+            MetricsLogger.action(getChannelLog(conversation, pkg).setSubtype(
+                    NOTIFICATION_UPDATE_LOG_SUBTYPE_FROM_USER));
+            mNotificationChannelLogger.logNotificationChannelModified(conversation, uid, pkg,
+                    oldLoggingImportance, /* fromUser= */ true);
+        }
+    }
+
     @Override
     public NotificationChannel getNotificationChannel(String pkg, int uid, String channelId,
             boolean includeDeleted) {
@@ -1829,8 +1944,8 @@
         }
     }
 
-    @VisibleForTesting
-    void lockFieldsForUpdateLocked(NotificationChannel original, NotificationChannel update) {
+    private void lockFieldsForUpdateLocked(NotificationChannel original,
+            NotificationChannel update) {
         if (original.canBypassDnd() != update.canBypassDnd()) {
             update.lockFields(NotificationChannel.USER_LOCKED_PRIORITY);
         }
@@ -2027,6 +2142,43 @@
     }
 
     /**
+     * @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;
+        }
+        if (!requestedFSIPermission) {
+            return PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__NOT_REQUESTED;
+        }
+        final AttributionSource attributionSource =
+                new AttributionSource.Builder(uid).setPackageName(pkg).build();
+
+        final int result = mPermissionManager.checkPermissionForPreflight(
+                android.Manifest.permission.USE_FULL_SCREEN_INTENT, attributionSource);
+
+        if (result == PermissionManager.PERMISSION_GRANTED) {
+            return PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__GRANTED;
+        }
+        return PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__DENIED;
+    }
+
+    /**
+     * @return True if the current full screen intent permission state for this package was set by
+     * 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) {
+            return false;
+        }
+        return (currentPermissionFlags & PackageManager.FLAG_PERMISSION_USER_SET) != 0;
+    }
+
+    /**
      * Fills out {@link PackageNotificationPreferences} proto and wraps it in a {@link StatsEvent}.
      */
     public void pullPackagePreferencesStats(List<StatsEvent> events,
@@ -2070,7 +2222,33 @@
 
                 event.writeInt(r.visibility);
                 event.writeInt(r.lockedAppFields);
-                event.writeBoolean(importanceIsUserSet);  // optional bool user_set_importance = 5;
+
+                // optional bool user_set_importance = 5;
+                event.writeBoolean(importanceIsUserSet);
+
+                // optional FsiState fsi_state = 6;
+                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);
+
+                event.writeInt(fsiState);
+
+                // optional bool is_fsi_permission_user_set = 7;
+                final int currentPermissionFlags = mPm.getPermissionFlags(
+                        android.Manifest.permission.USE_FULL_SCREEN_INTENT, r.pkg,
+                        UserHandle.getUserHandleForUid(r.uid));
+
+                final boolean isUserSet =
+                        isFsiPermissionUserSet(r.pkg, r.uid, fsiState, currentPermissionFlags,
+                                isStickyHunFlagEnabled);
+
+                event.writeBoolean(isUserSet);
+
                 events.add(event.build());
             }
         }
diff --git a/services/core/java/com/android/server/notification/RateEstimator.java b/services/core/java/com/android/server/notification/RateEstimator.java
index a2f93dc..eda96ac 100644
--- a/services/core/java/com/android/server/notification/RateEstimator.java
+++ b/services/core/java/com/android/server/notification/RateEstimator.java
@@ -22,9 +22,10 @@
  *
  * {@hide}
  */
-public class RateEstimator {
-    private static final double RATE_ALPHA = 0.8;
+class RateEstimator {
+    private static final double RATE_ALPHA = 0.7;
     private static final double MINIMUM_DT = 0.0005;
+
     private Long mLastEventTime;
     private double mInterarrivalTime;
 
@@ -34,18 +35,12 @@
     }
 
     /** Update the estimate to account for an event that just happened. */
-    public float update(long now) {
-        float rate;
-        if (mLastEventTime == null) {
-            // No last event time, rate is zero.
-            rate = 0f;
-        } else {
+    public void update(long now) {
+        if (mLastEventTime != null) {
             // Calculate the new inter-arrival time based on last event time.
             mInterarrivalTime = getInterarrivalEstimate(now);
-            rate = (float) (1.0 / mInterarrivalTime);
         }
         mLastEventTime = now;
-        return rate;
     }
 
     /** @return the estimated rate if there were a new event right now. */
diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java
index d471c8a..7736e2b 100644
--- a/services/core/java/com/android/server/om/OverlayManagerService.java
+++ b/services/core/java/com/android/server/om/OverlayManagerService.java
@@ -1120,6 +1120,21 @@
             int callingUid = Binder.getCallingUid();
             mActorEnforcer.enforceActor(overlayInfo, methodName, callingUid, realUserId);
         }
+
+        /**
+         * @hide
+         */
+        public String getPartitionOrder() {
+            return mImpl.getOverlayConfig().getPartitionOrder();
+        }
+
+        /**
+         * @hide
+         */
+        public boolean isDefaultPartitionOrder() {
+            return mImpl.getOverlayConfig().isDefaultPartitionOrder();
+        }
+
     };
 
     private static final class PackageManagerHelperImpl implements PackageManagerHelper {
@@ -1301,7 +1316,8 @@
 
             ApkAssets apkAssets = null;
             try {
-                apkAssets = ApkAssets.loadFromPath(pkg.getSplits().get(0).getPath());
+                apkAssets = ApkAssets.loadFromPath(pkg.getSplits().get(0).getPath(),
+                        ApkAssets.PROPERTY_ONLY_OVERLAYABLES);
                 return apkAssets.getOverlayableInfo(targetOverlayableName);
             } finally {
                 if (apkAssets != null) {
diff --git a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
index 1beba9f..972c78d 100644
--- a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
+++ b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
@@ -925,4 +925,8 @@
             super(message, cause);
         }
     }
+
+    OverlayConfig getOverlayConfig() {
+        return mOverlayConfig;
+    }
 }
diff --git a/services/core/java/com/android/server/om/OverlayManagerShellCommand.java b/services/core/java/com/android/server/om/OverlayManagerShellCommand.java
index 978e436..f77d7898 100644
--- a/services/core/java/com/android/server/om/OverlayManagerShellCommand.java
+++ b/services/core/java/com/android/server/om/OverlayManagerShellCommand.java
@@ -16,6 +16,8 @@
 
 package com.android.server.om;
 
+import static com.android.internal.content.om.OverlayConfig.PARTITION_ORDER_FILE_PATH;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
@@ -95,6 +97,8 @@
                     return runLookup();
                 case "fabricate":
                     return runFabricate();
+                case "partition-order":
+                    return runPartitionOrder();
                 default:
                     return handleDefaultCommands(cmd);
             }
@@ -147,6 +151,9 @@
         out.println("    Create an overlay from a single resource. Caller must be root. Example:");
         out.println("      fabricate --target android --name LighterGray \\");
         out.println("                android:color/lighter_gray 0x1c 0xffeeeeee");
+        out.println("  partition-order");
+        out.println("    Print the partition order from overlay config and how this order");
+        out.println("    got established, by default or by " + PARTITION_ORDER_FILE_PATH);
     }
 
     private int runList() throws RemoteException {
@@ -247,6 +254,14 @@
         return 0;
     }
 
+    private int runPartitionOrder() throws RemoteException {
+        final PrintWriter out = getOutPrintWriter();
+        out.println("Partition order (low to high priority): " + mInterface.getPartitionOrder());
+        out.println("Established by " + (mInterface.isDefaultPartitionOrder() ? "default"
+                : PARTITION_ORDER_FILE_PATH));
+        return 0;
+    }
+
     private int runFabricate() throws RemoteException {
         final PrintWriter err = getErrPrintWriter();
         if (Binder.getCallingUid() != Process.ROOT_UID) {
diff --git a/services/core/java/com/android/server/om/TEST_MAPPING b/services/core/java/com/android/server/om/TEST_MAPPING
index e8a2a02..82e7817 100644
--- a/services/core/java/com/android/server/om/TEST_MAPPING
+++ b/services/core/java/com/android/server/om/TEST_MAPPING
@@ -15,12 +15,7 @@
       "name": "OverlayHostTests"
     },
     {
-      "name": "CtsAppSecurityHostTestCases",
-      "options": [
-        {
-          "include-filter": "android.appsecurity.cts.OverlayHostTest"
-        }
-      ]
+      "name": "CtsOverlayHostTestCases"
     }
   ],
   "presubmit-large": [
diff --git a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
index 0605345..3ba307b 100644
--- a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
+++ b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
@@ -22,6 +22,7 @@
 import android.app.ActivityManager;
 import android.app.AppOpsManager;
 import android.app.admin.DevicePolicyManager;
+import android.app.role.RoleManager;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.content.pm.UserInfo;
@@ -64,6 +65,8 @@
     private static final int LOCAL_LOG_SIZE = 20;
     private static final String TAG = "BugreportManagerService";
     private static final boolean DEBUG = false;
+    private static final String ROLE_SYSTEM_AUTOMOTIVE_PROJECTION =
+            "android.app.role.SYSTEM_AUTOMOTIVE_PROJECTION";
 
     private static final String BUGREPORT_SERVICE = "bugreportd";
     private static final long DEFAULT_BUGREPORT_SERVICE_TIMEOUT_MILLIS = 30 * 1000;
@@ -326,11 +329,22 @@
 
         // To gain access through the DUMP permission, the OEM has to allow this package explicitly
         // via sysconfig and privileged permissions.
-        if (mBugreportAllowlistedPackages.contains(callingPackage)
-                && mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
-                        == PackageManager.PERMISSION_GRANTED) {
+        boolean allowlisted = mBugreportAllowlistedPackages.contains(callingPackage);
+        if (!allowlisted) {
+            final long token = Binder.clearCallingIdentity();
+            try {
+                allowlisted = mContext.getSystemService(RoleManager.class).getRoleHolders(
+                        ROLE_SYSTEM_AUTOMOTIVE_PROJECTION).contains(callingPackage);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        if (allowlisted && mContext.checkCallingOrSelfPermission(
+                android.Manifest.permission.DUMP) == PackageManager.PERMISSION_GRANTED) {
             return;
         }
+
         // For carrier privileges, this can include user-installed apps. This is essentially a
         // function of the current active SIM(s) in the device to let carrier apps through.
         final long token = Binder.clearCallingIdentity();
@@ -346,7 +360,8 @@
 
         String message =
                 callingPackage
-                        + " does not hold the DUMP permission or is not bugreport-whitelisted "
+                        + " does not hold the DUMP permission or is not bugreport-whitelisted or "
+                        + "does not have an allowed role "
                         + (checkCarrierPrivileges ? "and does not have carrier privileges " : "")
                         + "to request a bugreport";
         Slog.w(TAG, message);
diff --git a/services/core/java/com/android/server/os/NativeTombstoneManager.java b/services/core/java/com/android/server/os/NativeTombstoneManager.java
index d3d1cc5..c7c8136 100644
--- a/services/core/java/com/android/server/os/NativeTombstoneManager.java
+++ b/services/core/java/com/android/server/os/NativeTombstoneManager.java
@@ -96,6 +96,8 @@
         registerForUserRemoval();
         registerForPackageRemoval();
 
+        BootReceiver.initDropboxRateLimiter();
+
         // Scan existing tombstones.
         mHandler.post(() -> {
             final File[] tombstoneFiles = TOMBSTONE_DIR.listFiles();
@@ -370,7 +372,7 @@
                 return false;
             }
 
-            if (Math.abs(exitInfo.getTimestamp() - mTimestampMs) > 5000) {
+            if (Math.abs(exitInfo.getTimestamp() - mTimestampMs) > 10000) {
                 return false;
             }
 
diff --git a/services/core/java/com/android/server/os/OWNERS b/services/core/java/com/android/server/os/OWNERS
index 1957332..70be161 100644
--- a/services/core/java/com/android/server/os/OWNERS
+++ b/services/core/java/com/android/server/os/OWNERS
@@ -1,2 +1,5 @@
 # Bugreporting
 per-file Bugreport* = file:/platform/frameworks/native:/cmds/dumpstate/OWNERS
+
+# NativeTombstone
+per-file NativeTombstone* = gaillard@google.com
diff --git a/services/core/java/com/android/server/pm/ApexManager.java b/services/core/java/com/android/server/pm/ApexManager.java
index 5e62b56..2206eac 100644
--- a/services/core/java/com/android/server/pm/ApexManager.java
+++ b/services/core/java/com/android/server/pm/ApexManager.java
@@ -35,7 +35,6 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.Trace;
-import android.sysprop.ApexProperties;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -82,18 +81,12 @@
             new Singleton<ApexManager>() {
                 @Override
                 protected ApexManager create() {
-                    if (ApexProperties.updatable().orElse(false)) {
-                        return new ApexManagerImpl();
-                    } else {
-                        return new ApexManagerFlattenedApex();
-                    }
+                    return new ApexManagerImpl();
                 }
             };
 
     /**
-     * Returns an instance of either {@link ApexManagerImpl} or {@link ApexManagerFlattenedApex}
-     * depending on whether this device supports APEX, i.e. {@link ApexProperties#updatable()}
-     * evaluates to {@code true}.
+     * Returns an instance of {@link ApexManagerImpl}
      * @hide
      */
     public static ApexManager getInstance() {
@@ -991,203 +984,4 @@
             }
         }
     }
-
-    /**
-     * An implementation of {@link ApexManager} that should be used in case device does not support
-     * updating APEX packages.
-     */
-    @VisibleForTesting
-    static final class ApexManagerFlattenedApex extends ApexManager {
-        @Override
-        ApexInfo[] getAllApexInfos() {
-            return null;
-        }
-
-        @Override
-        void notifyScanResult(List<ScanResult> scanResults) {
-            // No-op
-        }
-
-        @Override
-        public List<ActiveApexInfo> getActiveApexInfos() {
-            // There is no apexd running in case of flattened apex
-            // We look up the /apex directory and identify the active APEX modules from there.
-            // As "preinstalled" path, we just report /system since in the case of flattened APEX
-            // the /apex directory is just a symlink to /system/apex.
-            List<ActiveApexInfo> result = new ArrayList<>();
-            File apexDir = Environment.getApexDirectory();
-            if (apexDir.isDirectory()) {
-                File[] files = apexDir.listFiles();
-                // listFiles might be null if system server doesn't have permission to read
-                // a directory.
-                if (files != null) {
-                    for (File file : files) {
-                        if (file.isDirectory() && !file.getName().contains("@")
-                                // In flattened configuration, init special-cases the art directory
-                                // and bind-mounts com.android.art.debug to com.android.art.
-                                && !file.getName().equals("com.android.art.debug")) {
-                            result.add(
-                                    new ActiveApexInfo(file, Environment.getRootDirectory(), file));
-                        }
-                    }
-                }
-            }
-            return result;
-        }
-
-        @Override
-        @Nullable
-        public String getActiveApexPackageNameContainingPackage(
-                @NonNull String containedPackageName) {
-            Objects.requireNonNull(containedPackageName);
-
-            return null;
-        }
-
-        @Override
-        ApexSessionInfo getStagedSessionInfo(int sessionId) {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        SparseArray<ApexSessionInfo> getSessions() {
-            return new SparseArray<>(0);
-        }
-
-        @Override
-        ApexInfoList submitStagedSession(ApexSessionParams params)
-                throws PackageManagerException {
-            throw new PackageManagerException(PackageManager.INSTALL_FAILED_INTERNAL_ERROR,
-                    "Device doesn't support updating APEX");
-        }
-
-        @Override
-        ApexInfo[] getStagedApexInfos(ApexSessionParams params) {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        void markStagedSessionReady(int sessionId) {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        void markStagedSessionSuccessful(int sessionId) {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        boolean isApexSupported() {
-            return false;
-        }
-
-        @Override
-        boolean revertActiveSessions() {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        boolean abortStagedSession(int sessionId) {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        boolean uninstallApex(String apexPackagePath) {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        void registerApkInApex(AndroidPackage pkg) {
-            // No-op
-        }
-
-        @Override
-        void reportErrorWithApkInApex(String scanDirPath, String errorMsg) {
-            // No-op
-        }
-
-        @Override
-        @Nullable
-        String getApkInApexInstallError(String apexPackageName) {
-            return null;
-        }
-
-        @Override
-        List<String> getApksInApex(String apexPackageName) {
-            return Collections.emptyList();
-        }
-
-        @Override
-        @Nullable
-        public String getApexModuleNameForPackageName(String apexPackageName) {
-            return null;
-        }
-
-        @Override
-        @Nullable
-        public String getActivePackageNameForApexModuleName(String apexModuleName) {
-            return null;
-        }
-
-        @Override
-        public boolean snapshotCeData(int userId, int rollbackId, String apexPackageName) {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        public boolean restoreCeData(int userId, int rollbackId, String apexPackageName) {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        public boolean destroyDeSnapshots(int rollbackId) {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        public boolean destroyCeSnapshots(int userId, int rollbackId) {
-            return true;
-        }
-
-        @Override
-        public boolean destroyCeSnapshotsNotSpecified(int userId, int[] retainRollbackIds) {
-            return true;
-        }
-
-        @Override
-        public void markBootCompleted() {
-            // No-op
-        }
-
-        @Override
-        public long calculateSizeForCompressedApex(CompressedApexInfoList infoList) {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        public void reserveSpaceForCompressedApex(CompressedApexInfoList infoList) {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        ApexInfo installPackage(File apexFile) {
-            throw new UnsupportedOperationException("APEX updates are not supported");
-        }
-
-        @Override
-        public List<ApexSystemServiceInfo> getApexSystemServices() {
-            // TODO(satayev): we can't really support flattened apex use case, and need to migrate
-            // the manifest entries into system's manifest asap.
-            return Collections.emptyList();
-        }
-
-        @Override
-        void dump(PrintWriter pw) {
-        }
-
-        @Override
-        public File getBackingApexFile(File file) {
-            return null;
-        }
-    }
 }
diff --git a/services/core/java/com/android/server/pm/ApkChecksums.java b/services/core/java/com/android/server/pm/ApkChecksums.java
index 2e1c72e..5b93244 100644
--- a/services/core/java/com/android/server/pm/ApkChecksums.java
+++ b/services/core/java/com/android/server/pm/ApkChecksums.java
@@ -40,8 +40,6 @@
 import android.content.pm.parsing.ApkLiteParseUtils;
 import android.content.pm.parsing.result.ParseResult;
 import android.content.pm.parsing.result.ParseTypeImpl;
-import android.os.Environment;
-import android.os.FileUtils;
 import android.os.Handler;
 import android.os.RemoteException;
 import android.os.SystemClock;
@@ -637,18 +635,9 @@
         return null;
     }
 
-    private static boolean containsFile(File dir, String filePath) {
-        if (dir == null) {
-            return false;
-        }
-        return FileUtils.contains(dir.getAbsolutePath(), filePath);
-    }
-
     private static ApkChecksum extractHashFromFS(String split, String filePath) {
         // verity first
-        // Skip /product folder.
-        // TODO(b/231354111): remove this hack once we are allowed to change SELinux rules.
-        if (!containsFile(Environment.getProductDirectory(), filePath)) {
+        if (VerityUtils.hasFsverity(filePath)) {
             byte[] verityHash = VerityUtils.getFsverityDigest(filePath);
             if (verityHash != null) {
                 return new ApkChecksum(split, TYPE_WHOLE_MERKLE_ROOT_4K_SHA256, verityHash);
diff --git a/services/core/java/com/android/server/pm/AppsFilterBase.java b/services/core/java/com/android/server/pm/AppsFilterBase.java
index 12f6a18..5b32a94 100644
--- a/services/core/java/com/android/server/pm/AppsFilterBase.java
+++ b/services/core/java/com/android/server/pm/AppsFilterBase.java
@@ -197,6 +197,7 @@
 
     protected volatile boolean mCacheReady = false;
     protected volatile boolean mCacheEnabled = true;
+    protected volatile boolean mNeedToUpdateCacheForImplicitAccess = false;
 
     protected static final boolean CACHE_VALID = true;
     protected static final boolean CACHE_INVALID = false;
diff --git a/services/core/java/com/android/server/pm/AppsFilterImpl.java b/services/core/java/com/android/server/pm/AppsFilterImpl.java
index ba5907c..21610c9 100644
--- a/services/core/java/com/android/server/pm/AppsFilterImpl.java
+++ b/services/core/java/com/android/server/pm/AppsFilterImpl.java
@@ -465,6 +465,9 @@
             changed = retainOnUpdate
                     ? mRetainedImplicitlyQueryable.add(recipientUid, visibleUid)
                     : mImplicitlyQueryable.add(recipientUid, visibleUid);
+            if (!mCacheReady && changed) {
+                mNeedToUpdateCacheForImplicitAccess = true;
+            }
         }
         if (changed && DEBUG_LOGGING) {
             Slog.i(TAG, (retainOnUpdate ? "retained " : "") + "implicit access granted: "
@@ -476,8 +479,6 @@
                 // Update the cache in a one-off manner since we've got all the information we need.
                 mShouldFilterCache.put(recipientUid, visibleUid, false);
             }
-        } else if (changed) {
-            invalidateCache("grantImplicitAccess: " + recipientUid + " -> " + visibleUid);
         }
         if (changed) {
             onChanged();
@@ -833,7 +834,6 @@
             }
 
             updateEntireShouldFilterCacheInner(snapshot, settings, usersRef[0], USER_ALL);
-            onChanged();
             logCacheRebuilt(reason, SystemClock.currentTimeMicro() - currentTimeUs,
                     users.length, settings.size());
 
@@ -844,7 +844,15 @@
                 return;
             }
 
-            mCacheReady = true;
+            synchronized (mImplicitlyQueryableLock) {
+                if (mNeedToUpdateCacheForImplicitAccess) {
+                    updateShouldFilterCacheForImplicitAccess();
+                    mNeedToUpdateCacheForImplicitAccess = false;
+                }
+                mCacheReady = true;
+            }
+
+            onChanged();
         }, delayMs);
     }
 
@@ -875,6 +883,25 @@
                 snapshot.getPackageStates().size());
     }
 
+    @GuardedBy("mImplicitlyQueryableLock")
+    private void updateShouldFilterCacheForImplicitAccess() {
+        updateShouldFilterCacheForImplicitAccess(mRetainedImplicitlyQueryable);
+        updateShouldFilterCacheForImplicitAccess(mImplicitlyQueryable);
+    }
+
+    private void updateShouldFilterCacheForImplicitAccess(
+            WatchedSparseSetArray<Integer> queriesMap) {
+        synchronized (mCacheLock) {
+            for (int i = 0; i < queriesMap.size(); i++) {
+                Integer callingUid = queriesMap.keyAt(i);
+                ArraySet<Integer> targetUids = queriesMap.get(callingUid);
+                for (Integer targetUid : targetUids) {
+                    mShouldFilterCache.put(callingUid, targetUid, false);
+                }
+            }
+        }
+    }
+
     private void updateShouldFilterCacheForPackage(Computer snapshot,
             String packageName) {
         if (!mCacheReady) {
@@ -1198,6 +1225,7 @@
                                 setting.getPackageName(), siblingSetting, settings,
                                 users, USER_ALL, settings.size());
                     }
+                    break;
                 }
             }
 
diff --git a/services/core/java/com/android/server/pm/AppsFilterLocked.java b/services/core/java/com/android/server/pm/AppsFilterLocked.java
index 29bb14e..e29f2b9 100644
--- a/services/core/java/com/android/server/pm/AppsFilterLocked.java
+++ b/services/core/java/com/android/server/pm/AppsFilterLocked.java
@@ -28,21 +28,28 @@
     /**
      * The following locks guard the accesses for the list/set class members
      */
-    protected final Object mForceQueryableLock = new Object();
-    protected final Object mQueriesViaPackageLock = new Object();
-    protected final Object mQueriesViaComponentLock = new Object();
+    protected final PackageManagerTracedLock mForceQueryableLock =
+            new PackageManagerTracedLock();
+    protected final PackageManagerTracedLock mQueriesViaPackageLock =
+            new PackageManagerTracedLock();
+    protected final PackageManagerTracedLock mQueriesViaComponentLock =
+            new PackageManagerTracedLock();
     /**
      * This lock covers both {@link #mImplicitlyQueryable} and {@link #mRetainedImplicitlyQueryable}
      */
-    protected final Object mImplicitlyQueryableLock = new Object();
-    protected final Object mQueryableViaUsesLibraryLock = new Object();
-    protected final Object mProtectedBroadcastsLock = new Object();
-    protected final Object mQueryableViaUsesPermissionLock = new Object();
+    protected final PackageManagerTracedLock mImplicitlyQueryableLock =
+        new PackageManagerTracedLock();
+    protected final PackageManagerTracedLock mQueryableViaUsesLibraryLock =
+        new PackageManagerTracedLock();
+    protected final PackageManagerTracedLock mProtectedBroadcastsLock =
+        new PackageManagerTracedLock();
+    protected final PackageManagerTracedLock mQueryableViaUsesPermissionLock =
+        new PackageManagerTracedLock();
 
     /**
      * Guards the access for {@link AppsFilterBase#mShouldFilterCache};
      */
-    protected final Object mCacheLock = new Object();
+    protected final PackageManagerTracedLock mCacheLock = new PackageManagerTracedLock();
 
     @Override
     protected boolean isForceQueryable(int appId) {
diff --git a/services/core/java/com/android/server/pm/ArchiveManager.java b/services/core/java/com/android/server/pm/ArchiveManager.java
new file mode 100644
index 0000000..99479f0
--- /dev/null
+++ b/services/core/java/com/android/server/pm/ArchiveManager.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import static android.content.pm.PackageManager.DELETE_KEEP_DATA;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.IntentSender;
+import android.content.pm.LauncherActivityInfo;
+import android.content.pm.LauncherApps;
+import android.content.pm.PackageManager;
+import android.content.pm.VersionedPackage;
+import android.os.Binder;
+import android.os.UserHandle;
+import android.text.TextUtils;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.pm.pkg.ArchiveState;
+import com.android.server.pm.pkg.ArchiveState.ArchiveActivityInfo;
+import com.android.server.pm.pkg.PackageStateInternal;
+
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Responsible archiving apps and returning information about archived apps.
+ *
+ * <p> An archived app is in a state where the app is not fully on the device. APKs are removed
+ * while the data directory is kept. Archived apps are included in the list of launcher apps where
+ * tapping them re-installs the full app.
+ */
+final class ArchiveManager {
+
+    private final Context mContext;
+    private final PackageManagerService mPm;
+
+    @Nullable
+    private LauncherApps mLauncherApps;
+
+    ArchiveManager(Context context, PackageManagerService mPm) {
+        this.mContext = context;
+        this.mPm = mPm;
+    }
+
+    void archiveApp(
+            @NonNull String packageName,
+            @NonNull String callerPackageName,
+            @NonNull UserHandle user,
+            @NonNull IntentSender intentSender) throws PackageManager.NameNotFoundException {
+        Objects.requireNonNull(packageName);
+        Objects.requireNonNull(callerPackageName);
+        Objects.requireNonNull(user);
+        Objects.requireNonNull(intentSender);
+
+        Computer snapshot = mPm.snapshotComputer();
+        int callingUid = Binder.getCallingUid();
+        int userId = user.getIdentifier();
+        String callingPackageName = snapshot.getNameForUid(callingUid);
+        snapshot.enforceCrossUserPermission(callingUid, userId, true, true,
+                "archiveApp");
+        verifyCaller(callerPackageName, callingPackageName);
+
+        PackageStateInternal ps = getPackageState(packageName, snapshot, callingUid, user);
+        verifyInstallOwnership(packageName, callingPackageName, ps.getInstallSource());
+
+        List<LauncherActivityInfo> mainActivities = getLauncherApps().getActivityList(
+                ps.getPackageName(),
+                new UserHandle(userId));
+        // TODO(b/291569242) Verify that this list is not empty and return failure with intentsender
+
+        storeArchiveState(ps, mainActivities, userId);
+
+        // TODO(b/278553670) Add special strings for the delete dialog
+        mPm.mInstallerService.uninstall(
+                new VersionedPackage(packageName, PackageManager.VERSION_CODE_HIGHEST),
+                callerPackageName, DELETE_KEEP_DATA, intentSender, userId);
+    }
+
+    @NonNull
+    private static PackageStateInternal getPackageState(String packageName,
+            Computer snapshot, int callingUid, UserHandle user)
+            throws PackageManager.NameNotFoundException {
+        PackageStateInternal ps = snapshot.getPackageStateFiltered(packageName, callingUid,
+                user.getIdentifier());
+        if (ps == null) {
+            throw new PackageManager.NameNotFoundException(
+                    TextUtils.formatSimple("Package %s not found.", packageName));
+        }
+        return ps;
+    }
+
+    private LauncherApps getLauncherApps() {
+        if (mLauncherApps == null) {
+            mLauncherApps = mContext.getSystemService(LauncherApps.class);
+        }
+        return mLauncherApps;
+    }
+
+    private void storeArchiveState(PackageStateInternal ps,
+            List<LauncherActivityInfo> mainActivities, int userId)
+            throws PackageManager.NameNotFoundException {
+        List<ArchiveActivityInfo> activityInfos = new ArrayList<>();
+        for (int i = 0; i < mainActivities.size(); i++) {
+            // TODO(b/278553670) Extract and store launcher icons
+            ArchiveActivityInfo activityInfo = new ArchiveActivityInfo(
+                    mainActivities.get(i).getLabel().toString(),
+                    Path.of("/TODO"), null);
+            activityInfos.add(activityInfo);
+        }
+        // TODO(b/278553670) Adapt installer check verifyInstallOwnership and check for null there
+        InstallSource installSource = ps.getInstallSource();
+        String installerPackageName = installSource.mUpdateOwnerPackageName != null
+                ? installSource.mUpdateOwnerPackageName : installSource.mInstallerPackageName;
+
+        synchronized (mPm.mLock) {
+            getPackageSetting(ps.getPackageName(), userId).modifyUserState(userId).setArchiveState(
+                    new ArchiveState(activityInfos, installerPackageName));
+        }
+    }
+
+    @NonNull
+    @GuardedBy("mPm.mLock")
+    private PackageSetting getPackageSetting(String packageName, int userId)
+            throws PackageManager.NameNotFoundException {
+        PackageSetting ps = mPm.mSettings.getPackageLPr(packageName);
+        if (ps == null || !ps.getUserStateOrDefault(userId).isInstalled()) {
+            throw new PackageManager.NameNotFoundException(
+                    TextUtils.formatSimple("Package %s not found.", packageName));
+        }
+        return ps;
+    }
+
+    private static void verifyCaller(String callerPackageName, String callingPackageName) {
+        if (!TextUtils.equals(callingPackageName, callerPackageName)) {
+            throw new SecurityException(
+                    TextUtils.formatSimple(
+                            "The callerPackageName %s set by the caller doesn't match the "
+                                    + "caller's own package name %s.",
+                            callerPackageName,
+                            callingPackageName));
+        }
+    }
+
+    private static void verifyInstallOwnership(String packageName, String callingPackageName,
+            InstallSource installSource) {
+        if (!TextUtils.equals(installSource.mInstallerPackageName,
+                callingPackageName)) {
+            throw new SecurityException(
+                    TextUtils.formatSimple("Caller is not the installer of record for %s.",
+                            packageName));
+        }
+        String updateOwnerPackageName = installSource.mUpdateOwnerPackageName;
+        if (updateOwnerPackageName != null
+                && !TextUtils.equals(updateOwnerPackageName, callingPackageName)) {
+            throw new SecurityException(
+                    TextUtils.formatSimple("Caller is not the update owner for %s.", packageName));
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/pm/BackgroundInstallControlService.java b/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
index 1b34c70..7f0aadc 100644
--- a/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
+++ b/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
@@ -234,7 +234,7 @@
         // the installers without INSTALL_PACKAGES perm can't perform
         // the installation in background. So we can just filter out them.
         if (mPermissionManager.checkPermission(installerPackageName,
-                android.Manifest.permission.INSTALL_PACKAGES,
+                android.Manifest.permission.INSTALL_PACKAGES, Context.DEVICE_ID_DEFAULT,
                 userId) != PackageManager.PERMISSION_GRANTED) {
             return;
         }
@@ -433,7 +433,7 @@
             return true;
         }
         return mPermissionManager.checkPermission(pkgName,
-                android.Manifest.permission.INSTALL_PACKAGES,
+                android.Manifest.permission.INSTALL_PACKAGES, Context.DEVICE_ID_DEFAULT,
                 userId) == PackageManager.PERMISSION_GRANTED;
     }
 
diff --git a/services/core/java/com/android/server/pm/Computer.java b/services/core/java/com/android/server/pm/Computer.java
index 9748aba..6e75605 100644
--- a/services/core/java/com/android/server/pm/Computer.java
+++ b/services/core/java/com/android/server/pm/Computer.java
@@ -60,7 +60,6 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
-import java.util.Collection;
 import java.util.List;
 import java.util.Set;
 
@@ -406,8 +405,7 @@
     ProviderInfo getProviderInfo(@NonNull ComponentName component,
             @PackageManager.ComponentInfoFlagsBits long flags, @UserIdInt int userId);
 
-    @Nullable
-    String[] getSystemSharedLibraryNames();
+    ArrayMap<String, String> getSystemSharedLibraryNamesAndPaths();
 
     /**
      * @return the state if the given package is installed and isn't filtered by visibility.
@@ -679,5 +677,5 @@
     UserInfo[] getUserInfos();
 
     @NonNull
-    Collection<SharedUserSetting> getAllSharedUsers();
+    ArrayMap<String, ? extends SharedUserApi> getSharedUsers();
 }
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index 78f1fa6..1b52725 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -301,8 +301,8 @@
         }
 
         @NonNull
-        public Collection<SharedUserSetting> getAllSharedUsers() {
-            return mSettings.getAllSharedUsersLPw();
+        ArrayMap<String, ? extends SharedUserApi> getSharedUsers() {
+            return mSettings.getSharedUsersLocked().untrackedStorage();
         }
 
         @Nullable
@@ -2583,7 +2583,7 @@
 
     // NOTE: Can't remove without a major refactor. Keep around for now.
     public final int checkUidPermission(String permName, int uid) {
-        return mPermissionManager.checkUidPermission(uid, permName);
+        return mPermissionManager.checkUidPermission(uid, permName, Context.DEVICE_ID_DEFAULT);
     }
 
     public int getPackageUidInternal(String packageName,
@@ -4053,13 +4053,12 @@
         return null;
     }
 
-    @Nullable
     @Override
-    public String[] getSystemSharedLibraryNames() {
+    public ArrayMap<String, String> getSystemSharedLibraryNamesAndPaths() {
         // allow instant applications
         final WatchedArrayMap<String, WatchedLongSparseArray<SharedLibraryInfo>> sharedLibraries =
                 getSharedLibraries();
-        Set<String> libs = null;
+        final ArrayMap<String, String> libs = new ArrayMap<>();
         final int libCount = sharedLibraries.size();
         for (int i = 0; i < libCount; i++) {
             WatchedLongSparseArray<SharedLibraryInfo> versionedLib = sharedLibraries.valueAt(i);
@@ -4070,10 +4069,7 @@
             for (int j = 0; j < versionCount; j++) {
                 SharedLibraryInfo libraryInfo = versionedLib.valueAt(j);
                 if (!libraryInfo.isStatic()) {
-                    if (libs == null) {
-                        libs = new ArraySet<>();
-                    }
-                    libs.add(libraryInfo.getName());
+                    libs.put(libraryInfo.getName(), libraryInfo.getPath());
                     break;
                 }
                 final PackageStateInternal ps =
@@ -4081,22 +4077,12 @@
                 if (ps != null && !filterSharedLibPackage(ps, Binder.getCallingUid(),
                         UserHandle.getUserId(Binder.getCallingUid()),
                         PackageManager.MATCH_STATIC_SHARED_AND_SDK_LIBRARIES)) {
-                    if (libs == null) {
-                        libs = new ArraySet<>();
-                    }
-                    libs.add(libraryInfo.getName());
+                    libs.put(libraryInfo.getName(), libraryInfo.getPath());
                     break;
                 }
             }
         }
-
-        if (libs != null) {
-            String[] libsArray = new String[libs.size()];
-            libs.toArray(libsArray);
-            return libsArray;
-        }
-
-        return null;
+        return libs;
     }
 
     @Override
@@ -4542,8 +4528,8 @@
         int numMatch = 0;
         for (int i=0; i<permissions.length; i++) {
             final String permission = permissions[i];
-            if (mPermissionManager.checkPermission(ps.getPackageName(), permission, userId)
-                    == PERMISSION_GRANTED) {
+            if (mPermissionManager.checkPermission(ps.getPackageName(), permission,
+                    Context.DEVICE_ID_DEFAULT, userId) == PERMISSION_GRANTED) {
                 tmp[i] = true;
                 numMatch++;
             } else {
@@ -5519,8 +5505,8 @@
     @Override
     public SparseArray<String> getAppsWithSharedUserIds() {
         final SparseArray<String> sharedUserIds = new SparseArray<>();
-        for (SharedUserSetting setting : mSettings.getAllSharedUsers()) {
-            sharedUserIds.put(UserHandle.getAppId(setting.mAppId), setting.name);
+        for (SharedUserApi sharedUser : mSettings.getSharedUsers().values()) {
+            sharedUserIds.put(UserHandle.getAppId(sharedUser.getAppId()), sharedUser.getName());
         }
         return sharedUserIds;
     }
@@ -5644,7 +5630,12 @@
             return sus.getPackages();
         } else if (settingBase instanceof PackageSetting) {
             final PackageSetting ps = (PackageSetting) settingBase;
-            return List.of(ps.getPkg());
+            final AndroidPackage pkg = ps.getPkg();
+            if (pkg != null) {
+                return Collections.singletonList(pkg);
+            } else {
+                return Collections.emptyList();
+            }
         } else {
             return Collections.emptyList();
         }
@@ -5820,8 +5811,8 @@
 
     @Override
     @NonNull
-    public Collection<SharedUserSetting> getAllSharedUsers() {
-        return mSettings.getAllSharedUsers();
+    public ArrayMap<String, ? extends SharedUserApi> getSharedUsers() {
+        return mSettings.getSharedUsers();
     }
 
     @Override
diff --git a/services/core/java/com/android/server/pm/DefaultAppProvider.java b/services/core/java/com/android/server/pm/DefaultAppProvider.java
index c18d0e9..fc61451 100644
--- a/services/core/java/com/android/server/pm/DefaultAppProvider.java
+++ b/services/core/java/com/android/server/pm/DefaultAppProvider.java
@@ -24,14 +24,10 @@
 import android.os.UserHandle;
 import android.util.Slog;
 
-import com.android.internal.infra.AndroidFuture;
 import com.android.internal.util.CollectionUtils;
 import com.android.server.FgThread;
 
-import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Executor;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
 import java.util.function.Consumer;
 import java.util.function.Supplier;
 
@@ -70,27 +66,19 @@
      * Set the package name of the default browser.
      *
      * @param packageName package name of the default browser, or {@code null} to unset
-     * @param async whether the operation should be asynchronous
      * @param userId the user ID
-     * @return whether the default browser was successfully set.
      */
-    public boolean setDefaultBrowser(@Nullable String packageName, boolean async,
-            @UserIdInt int userId) {
-        if (userId == UserHandle.USER_ALL) {
-            return false;
-        }
+    public void setDefaultBrowser(@Nullable String packageName, @UserIdInt int userId) {
         final RoleManager roleManager = mRoleManagerSupplier.get();
         if (roleManager == null) {
-            return false;
+            return;
         }
         final UserHandle user = UserHandle.of(userId);
         final Executor executor = FgThread.getExecutor();
-        final AndroidFuture<Void> future = new AndroidFuture<>();
         final Consumer<Boolean> callback = successful -> {
-            if (successful) {
-                future.complete(null);
-            } else {
-                future.completeExceptionally(new RuntimeException());
+            if (!successful) {
+                Slog.e(PackageManagerService.TAG, "Failed to set default browser to "
+                        + packageName);
             }
         };
         final long identity = Binder.clearCallingIdentity();
@@ -102,19 +90,9 @@
                 roleManager.clearRoleHoldersAsUser(RoleManager.ROLE_BROWSER, 0, user, executor,
                         callback);
             }
-            if (!async) {
-                try {
-                    future.get(5, TimeUnit.SECONDS);
-                } catch (InterruptedException | ExecutionException | TimeoutException e) {
-                    Slog.e(PackageManagerService.TAG, "Exception while setting default browser: "
-                            + packageName, e);
-                    return false;
-                }
-            }
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
-        return true;
     }
 
     /**
diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java
index 5f52c16..a5a4594 100644
--- a/services/core/java/com/android/server/pm/DeletePackageHelper.java
+++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java
@@ -16,6 +16,7 @@
 
 package com.android.server.pm;
 
+import static android.Manifest.permission.CONTROL_KEYGUARD;
 import static android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS;
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
@@ -134,10 +135,6 @@
         final int removeUser = (deleteFlags & PackageManager.DELETE_ALL_USERS) != 0
                 ? UserHandle.USER_ALL : userId;
 
-        if (mPm.isPackageDeviceAdmin(packageName, removeUser)) {
-            Slog.w(TAG, "Not removing package " + packageName + ": has active device admin");
-            return PackageManager.DELETE_FAILED_DEVICE_POLICY_MANAGER;
-        }
 
         final PackageSetting uninstalledPs;
         final PackageSetting disabledSystemPs;
@@ -343,6 +340,20 @@
         return res ? DELETE_SUCCEEDED : PackageManager.DELETE_FAILED_INTERNAL_ERROR;
     }
 
+    /** Deletes dexopt artifacts for the given package*/
+    private void deleteArtDexoptArtifacts(String packageName) {
+        try (PackageManagerLocal.FilteredSnapshot filteredSnapshot =
+                     PackageManagerServiceUtils.getPackageManagerLocal()
+                             .withFilteredSnapshot()) {
+            try {
+                DexOptHelper.getArtManagerLocal().deleteDexoptArtifacts(
+                        filteredSnapshot, packageName);
+            } catch (IllegalArgumentException | IllegalStateException e) {
+                Slog.w(TAG, e.toString());
+            }
+        }
+    }
+
     /*
      * This method handles package deletion in general
      */
@@ -354,6 +365,12 @@
         synchronized (mPm.mLock) {
             final PackageSetting ps = mPm.mSettings.getPackageLPr(packageName);
             final PackageSetting disabledPs = mPm.mSettings.getDisabledSystemPkgLPr(ps);
+            if (PackageManagerServiceUtils.isSystemApp(ps)
+                    && mPm.checkPermission(CONTROL_KEYGUARD, packageName, UserHandle.USER_SYSTEM)
+                    == PERMISSION_GRANTED) {
+                Slog.w(TAG, "Attempt to delete keyguard system package " + packageName);
+                return false;
+            }
             action = mayDeletePackageLocked(outInfo, ps, disabledPs, flags, user);
         }
         if (DEBUG_REMOVE) Slog.d(TAG, "deletePackageLI: " + packageName + " user " + user);
@@ -484,6 +501,11 @@
                     action, allUserHandles, writeSettings);
         } else {
             if (DEBUG_REMOVE) Slog.d(TAG, "Removing non-system package: " + ps.getPackageName());
+            if (ps.isIncremental()) {
+                // Explicitly delete dexopt artifacts for incremental app because the
+                // artifacts are not stored in the same directory as the APKs
+                deleteArtDexoptArtifacts(packageName);
+            }
             deleteInstalledPackageLIF(ps, deleteCodeAndResources, flags, allUserHandles,
                     outInfo, writeSettings);
         }
@@ -610,7 +632,9 @@
                     PackageManager.UNINSTALL_REASON_UNKNOWN,
                     null /*harmfulAppWarning*/,
                     null /*splashScreenTheme*/,
-                    0 /*firstInstallTime*/);
+                    0 /*firstInstallTime*/,
+                    PackageManager.USER_MIN_ASPECT_RATIO_UNSET,
+                    null /*archiveState*/);
         }
         mPm.mSettings.writeKernelMappingLPr(ps);
     }
@@ -680,18 +704,6 @@
         final String packageName = versionedPackage.getPackageName();
         final long versionCode = versionedPackage.getLongVersionCode();
 
-        if (mPm.mProtectedPackages.isPackageDataProtected(userId, packageName)) {
-            mPm.mHandler.post(() -> {
-                try {
-                    Slog.w(TAG, "Attempted to delete protected package: " + packageName);
-                    observer.onPackageDeleted(packageName,
-                            PackageManager.DELETE_FAILED_INTERNAL_ERROR, null);
-                } catch (RemoteException re) {
-                }
-            });
-            return;
-        }
-
         try {
             if (mPm.mInjector.getLocalService(ActivityTaskManagerInternal.class)
                     .isBaseOfLockedTask(packageName)) {
@@ -732,6 +744,41 @@
                     "deletePackage for user " + userId);
         }
 
+        final long token = Binder.clearCallingIdentity();
+        try {
+            // If a package is device admin, or is data protected for any user, it should not be
+            // uninstalled from that user, or from any users if DELETE_ALL_USERS flag is passed.
+            for (int user : users) {
+                if (mPm.isPackageDeviceAdmin(packageName, user)) {
+                    mPm.mHandler.post(() -> {
+                        try {
+                            Slog.w(TAG, "Not removing package " + packageName
+                                    + ": has active device admin");
+                            observer.onPackageDeleted(packageName,
+                                    PackageManager.DELETE_FAILED_DEVICE_POLICY_MANAGER, null);
+                        } catch (RemoteException e) {
+                            // no-op
+                        }
+                    });
+                    return;
+                }
+                if (mPm.mProtectedPackages.isPackageDataProtected(user, packageName)) {
+                    mPm.mHandler.post(() -> {
+                        try {
+                            Slog.w(TAG, "Attempted to delete protected package: " + packageName);
+                            observer.onPackageDeleted(packageName,
+                                    PackageManager.DELETE_FAILED_INTERNAL_ERROR, null);
+                        } catch (RemoteException re) {
+                            // no-op
+                        }
+                    });
+                    return;
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+
         if (mPm.isUserRestricted(userId, UserManager.DISALLOW_UNINSTALL_APPS)) {
             mPm.mHandler.post(() -> {
                 try {
diff --git a/services/core/java/com/android/server/pm/DistractingPackageHelper.java b/services/core/java/com/android/server/pm/DistractingPackageHelper.java
index dbf0d29..8ebb6ea 100644
--- a/services/core/java/com/android/server/pm/DistractingPackageHelper.java
+++ b/services/core/java/com/android/server/pm/DistractingPackageHelper.java
@@ -32,6 +32,7 @@
 import com.android.server.pm.pkg.PackageStateInternal;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 
 /**
@@ -133,6 +134,39 @@
         return unactionedPackages.toArray(new String[0]);
     }
 
+
+    /**
+     * Gets {@link DistractionRestriction restrictions} of the given packages of the given user.
+     *
+     * The corresponding element of the resulting array will be -1 if a given package doesn't exist.
+     *
+     * @param packageNames The packages under which to check.
+     * @param userId The user under which to check.
+     * @return an array of distracting restriction state in order of the given packages
+     */
+    int[] getDistractingPackageRestrictionsAsUser(@NonNull Computer snapshot,
+            @NonNull String[] packageNames, int userId, int callingUid) {
+        int[] res = new int[packageNames.length];
+        Arrays.fill(res, -1);
+
+        if (ArrayUtils.isEmpty(packageNames)) {
+            return res;
+        }
+
+        for (int i = 0; i < packageNames.length; i++) {
+            final String packageName = packageNames[i];
+            final PackageStateInternal packageState =
+                    snapshot.getPackageStateForInstalledAndFiltered(
+                            packageName, callingUid, userId);
+            if (packageState == null) {
+                continue;
+            }
+
+            res[i] = packageState.getUserStateOrDefault(userId).getDistractionFlags();
+        }
+        return res;
+    }
+
     /**
      * Removes any {@link DistractionRestriction restrictions} set on given packages.
      *
diff --git a/services/core/java/com/android/server/pm/DumpHelper.java b/services/core/java/com/android/server/pm/DumpHelper.java
index fcaaa90..f3ea42e 100644
--- a/services/core/java/com/android/server/pm/DumpHelper.java
+++ b/services/core/java/com/android/server/pm/DumpHelper.java
@@ -23,6 +23,7 @@
 
 import android.annotation.NonNull;
 import android.content.ComponentName;
+import android.content.Context;
 import android.content.pm.FeatureInfo;
 import android.content.pm.PackageManager;
 import android.os.Binder;
@@ -160,7 +161,8 @@
                 pkg = snapshot.resolveInternalPackageName(pkg,
                         PackageManager.VERSION_CODE_HIGHEST);
 
-                pw.println(mPermissionManager.checkPermission(perm, pkg, user));
+                pw.println(mPermissionManager.checkPermission(
+                        pkg, perm, Context.DEVICE_ID_DEFAULT, user));
                 return;
             } else if ("l".equals(cmd) || "libraries".equals(cmd)) {
                 dumpState.setDump(DumpState.DUMP_LIBS);
@@ -617,7 +619,9 @@
         pw.println("    --checkin: dump for a checkin");
         pw.println("    -f: print details of intent filters");
         pw.println("    -h: print this help");
+        pw.println("    ---proto: dump data to proto");
         pw.println("    --all-components: include all component names in package dump");
+        pw.println("    --include-apex: includes the apex packages in package dump");
         pw.println("  cmd may be one of:");
         pw.println("    apex: list active APEXes and APEX session state");
         pw.println("    l[ibraries]: list known shared libraries");
@@ -631,7 +635,7 @@
         pw.println("    prov[iders]: dump content providers");
         pw.println("    p[ackages]: dump installed packages");
         pw.println("    q[ueries]: dump app queryability calculations");
-        pw.println("    s[hared-users]: dump shared user IDs");
+        pw.println("    s[hared-users] [noperm]: dump shared user IDs");
         pw.println("    m[essages]: print collected runtime messages");
         pw.println("    v[erifiers]: print package verifier info");
         pw.println("    d[omain-preferred-apps]: print domains preferred apps");
@@ -644,9 +648,12 @@
         pw.println("    dexopt: dump dexopt state");
         pw.println("    compiler-stats: dump compiler statistics");
         pw.println("    service-permissions: dump permissions required by services");
-        pw.println("    snapshot: dump snapshot statistics");
+        pw.println("    snapshot [--full|--brief]: dump snapshot statistics");
         pw.println("    protected-broadcasts: print list of protected broadcast actions");
         pw.println("    known-packages: dump known packages");
+        pw.println("    changes: dump the packages that have been changed");
+        pw.println("    frozen: dump the frozen packages");
+        pw.println("    volumes: dump the loaded volumes");
         pw.println("    <package.name>: info about given package");
     }
 
diff --git a/services/core/java/com/android/server/pm/FreeStorageHelper.java b/services/core/java/com/android/server/pm/FreeStorageHelper.java
new file mode 100644
index 0000000..66ec80f
--- /dev/null
+++ b/services/core/java/com/android/server/pm/FreeStorageHelper.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 com.android.server.pm;
+
+import static com.android.server.pm.PackageManagerService.TAG;
+
+import android.content.Context;
+import android.content.pm.PackageInfoLite;
+import android.content.pm.parsing.PackageLite;
+import android.os.Environment;
+import android.os.FileUtils;
+import android.os.RemoteException;
+import android.os.SystemProperties;
+import android.os.storage.IStorageManager;
+import android.os.storage.StorageManager;
+import android.os.storage.StorageManagerInternal;
+import android.provider.Settings;
+import android.text.format.DateUtils;
+import android.util.Slog;
+
+import com.android.internal.content.InstallLocationUtils;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Objects;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A helper to clear various types of cached data across the system.
+ */
+final class FreeStorageHelper {
+    private static final long FREE_STORAGE_UNUSED_STATIC_SHARED_LIB_MIN_CACHE_PERIOD =
+            TimeUnit.HOURS.toMillis(2); /* two hours */
+
+    /**
+     * Wall-clock timeout (in milliseconds) after which we *require* that an fstrim
+     * be run on this device.  We use the value in the Settings.Global.MANDATORY_FSTRIM_INTERVAL
+     * settings entry if available, otherwise we use the hardcoded default.  If it's been
+     * more than this long since the last fstrim, we force one during the boot sequence.
+     *
+     * This backstops other fstrim scheduling:  if the device is alive at midnight+idle,
+     * one gets run at the next available charging+idle time.  This final mandatory
+     * no-fstrim check kicks in only of the other scheduling criteria is never met.
+     */
+    private static final long DEFAULT_MANDATORY_FSTRIM_INTERVAL = 3 * DateUtils.DAY_IN_MILLIS;
+
+    private final PackageManagerService mPm;
+    private final Context mContext;
+    private final PackageManagerServiceInjector mInjector;
+    private final boolean mEnableFreeCacheV2;
+
+    // TODO(b/198166813): remove PMS dependency
+    FreeStorageHelper(PackageManagerService pm, PackageManagerServiceInjector injector,
+            Context context, boolean enableFreeCacheV2) {
+        mPm = pm;
+        mInjector = injector;
+        mContext = context;
+        mEnableFreeCacheV2 = enableFreeCacheV2;
+    }
+
+    FreeStorageHelper(PackageManagerService pm) {
+        this(pm, pm.mInjector, pm.mContext,
+                SystemProperties.getBoolean("fw.free_cache_v2", true));
+    }
+
+    /**
+     * Blocking call to clear various types of cached data across the system
+     * until the requested bytes are available.
+     */
+    void freeStorage(String volumeUuid, long bytes,
+            @StorageManager.AllocateFlags int flags) throws IOException {
+        final StorageManager storage = mInjector.getSystemService(StorageManager.class);
+        final File file = storage.findPathForUuid(volumeUuid);
+        if (file.getUsableSpace() >= bytes) return;
+
+        if (mEnableFreeCacheV2) {
+            final boolean internalVolume = Objects.equals(StorageManager.UUID_PRIVATE_INTERNAL,
+                    volumeUuid);
+            final boolean aggressive = (flags & StorageManager.FLAG_ALLOCATE_AGGRESSIVE) != 0;
+
+            // 1. Pre-flight to determine if we have any chance to succeed
+            // 2. Consider preloaded data (after 1w honeymoon, unless aggressive)
+            if (internalVolume && (aggressive || SystemProperties
+                    .getBoolean("persist.sys.preloads.file_cache_expired", false))) {
+                mPm.deletePreloadsFileCache();
+                if (file.getUsableSpace() >= bytes) return;
+            }
+
+            // 3. Consider parsed APK data (aggressive only)
+            if (internalVolume && aggressive) {
+                FileUtils.deleteContents(mPm.getCacheDir());
+                if (file.getUsableSpace() >= bytes) return;
+            }
+
+            // 4. Consider cached app data (above quotas)
+            synchronized (mPm.mInstallLock) {
+                try {
+                    mPm.mInstaller.freeCache(volumeUuid, bytes, Installer.FLAG_FREE_CACHE_V2);
+                } catch (Installer.InstallerException ignored) {
+                }
+            }
+            if (file.getUsableSpace() >= bytes) return;
+
+            final Computer computer = mPm.snapshotComputer();
+            final SharedLibrariesImpl sharedLibraries = mPm.mInjector.getSharedLibrariesImpl();
+            // 5. Consider shared libraries with refcount=0 and age>min cache period
+            if (internalVolume && sharedLibraries.pruneUnusedStaticSharedLibraries(computer, bytes,
+                    android.provider.Settings.Global.getLong(mContext.getContentResolver(),
+                            Settings.Global.UNUSED_STATIC_SHARED_LIB_MIN_CACHE_PERIOD,
+                            FREE_STORAGE_UNUSED_STATIC_SHARED_LIB_MIN_CACHE_PERIOD))) {
+                return;
+            }
+
+            // 6. Consider dexopt output (aggressive only)
+            // TODO: Implement
+
+            // 7. Consider installed instant apps unused longer than min cache period
+            if (internalVolume) {
+                if (mPm.mInstantAppRegistry.pruneInstalledInstantApps(computer, bytes,
+                        android.provider.Settings.Global.getLong(
+                                mContext.getContentResolver(),
+                                Settings.Global.INSTALLED_INSTANT_APP_MIN_CACHE_PERIOD,
+                                InstantAppRegistry
+                                        .DEFAULT_INSTALLED_INSTANT_APP_MIN_CACHE_PERIOD))) {
+                    return;
+                }
+            }
+
+            // 8. Consider cached app data (below quotas)
+            synchronized (mPm.mInstallLock) {
+                try {
+                    mPm.mInstaller.freeCache(volumeUuid, bytes,
+                            Installer.FLAG_FREE_CACHE_V2 | Installer.FLAG_FREE_CACHE_V2_DEFY_QUOTA);
+                } catch (Installer.InstallerException ignored) {
+                }
+            }
+            if (file.getUsableSpace() >= bytes) return;
+
+            // 9. Consider DropBox entries
+            // TODO: Implement
+
+            // 10. Consider instant meta-data (uninstalled apps) older that min cache period
+            if (internalVolume) {
+                if (mPm.mInstantAppRegistry.pruneUninstalledInstantApps(computer, bytes,
+                        android.provider.Settings.Global.getLong(
+                                mContext.getContentResolver(),
+                                Settings.Global.UNINSTALLED_INSTANT_APP_MIN_CACHE_PERIOD,
+                                InstantAppRegistry
+                                        .DEFAULT_UNINSTALLED_INSTANT_APP_MIN_CACHE_PERIOD))) {
+                    return;
+                }
+            }
+
+            // 11. Free storage service cache
+            StorageManagerInternal smInternal =
+                    mInjector.getLocalService(StorageManagerInternal.class);
+            long freeBytesRequired = bytes - file.getUsableSpace();
+            if (freeBytesRequired > 0) {
+                smInternal.freeCache(volumeUuid, freeBytesRequired);
+            }
+
+            // 12. Clear temp install session files
+            mPm.mInstallerService.freeStageDirs(volumeUuid);
+        } else {
+            synchronized (mPm.mInstallLock) {
+                try {
+                    mPm.mInstaller.freeCache(volumeUuid, bytes, 0);
+                } catch (Installer.InstallerException ignored) {
+                }
+            }
+        }
+        if (file.getUsableSpace() >= bytes) return;
+
+        throw new IOException("Failed to free " + bytes + " on storage device at " + file);
+    }
+
+    int freeCacheForInstallation(int recommendedInstallLocation, PackageLite pkgLite,
+            String resolvedPath, String mPackageAbiOverride, int installFlags) {
+        // TODO: focus freeing disk space on the target device
+        final StorageManager storage = StorageManager.from(mContext);
+        final long lowThreshold = storage.getStorageLowBytes(Environment.getDataDirectory());
+
+        final long sizeBytes = PackageManagerServiceUtils.calculateInstalledSize(resolvedPath,
+                mPackageAbiOverride);
+        if (sizeBytes >= 0) {
+            synchronized (mPm.mInstallLock) {
+                try {
+                    mPm.mInstaller.freeCache(null, sizeBytes + lowThreshold, 0);
+                    PackageInfoLite pkgInfoLite = PackageManagerServiceUtils.getMinimalPackageInfo(
+                            mContext, pkgLite, resolvedPath, installFlags,
+                            mPackageAbiOverride);
+                    // The cache free must have deleted the file we downloaded to install.
+                    if (pkgInfoLite.recommendedInstallLocation
+                            == InstallLocationUtils.RECOMMEND_FAILED_INVALID_URI) {
+                        pkgInfoLite.recommendedInstallLocation =
+                                InstallLocationUtils.RECOMMEND_FAILED_INSUFFICIENT_STORAGE;
+                    }
+                    return pkgInfoLite.recommendedInstallLocation;
+                } catch (Installer.InstallerException e) {
+                    Slog.w(TAG, "Failed to free cache", e);
+                }
+            }
+        }
+        return recommendedInstallLocation;
+    }
+
+    void performFstrimIfNeeded() {
+        PackageManagerServiceUtils.enforceSystemOrRoot("Only the system can request fstrim");
+
+        // Before everything else, see whether we need to fstrim.
+        try {
+            IStorageManager sm = InstallLocationUtils.getStorageManager();
+            if (sm != null) {
+                boolean doTrim = false;
+                final long interval = android.provider.Settings.Global.getLong(
+                        mContext.getContentResolver(),
+                        android.provider.Settings.Global.FSTRIM_MANDATORY_INTERVAL,
+                        DEFAULT_MANDATORY_FSTRIM_INTERVAL);
+                if (interval > 0) {
+                    final long timeSinceLast = System.currentTimeMillis() - sm.lastMaintenance();
+                    if (timeSinceLast > interval) {
+                        doTrim = true;
+                        Slog.w(TAG, "No disk maintenance in " + timeSinceLast
+                                + "; running immediately");
+                    }
+                }
+                if (doTrim) {
+                    sm.runMaintenance();
+                }
+            } else {
+                Slog.e(TAG, "storageManager service unavailable!");
+            }
+        } catch (RemoteException e) {
+            // Can't happen; StorageManagerService is local
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/pm/IPackageManagerBase.java b/services/core/java/com/android/server/pm/IPackageManagerBase.java
index 52fdbda..fd47846 100644
--- a/services/core/java/com/android/server/pm/IPackageManagerBase.java
+++ b/services/core/java/com/android/server/pm/IPackageManagerBase.java
@@ -57,6 +57,7 @@
 import android.os.Trace;
 import android.os.UserHandle;
 import android.permission.PermissionManager;
+import android.util.ArrayMap;
 import android.util.Log;
 
 import com.android.internal.R;
@@ -67,6 +68,7 @@
 import com.android.server.pm.verify.domain.proxy.DomainVerificationProxyV1;
 
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 
 /**
@@ -810,7 +812,22 @@
     @Override
     @Deprecated
     public final String[] getSystemSharedLibraryNames() {
-        return snapshot().getSystemSharedLibraryNames();
+        ArrayMap<String, String> namesAndPaths = snapshot().getSystemSharedLibraryNamesAndPaths();
+        if (namesAndPaths.isEmpty()) {
+            return null;
+        }
+        final int size = namesAndPaths.size();
+        final String[] libs = new String[size];
+        for (int i = 0; i < size; i++) {
+            libs[i] = namesAndPaths.keyAt(i);
+        }
+        return libs;
+    }
+
+    @Override
+    @Deprecated
+    public final Map<String, String> getSystemSharedLibraryNamesAndPaths() {
+        return snapshot().getSystemSharedLibraryNamesAndPaths();
     }
 
     @Override
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 50f1673..ead26cc 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -110,6 +110,7 @@
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.IntentSender;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.DataLoaderType;
@@ -119,7 +120,6 @@
 import android.content.pm.PackageManager;
 import android.content.pm.PermissionGroupInfo;
 import android.content.pm.PermissionInfo;
-import android.content.pm.ResolveInfo;
 import android.content.pm.SharedLibraryInfo;
 import android.content.pm.Signature;
 import android.content.pm.SigningDetails;
@@ -184,7 +184,9 @@
 import com.android.server.pm.pkg.PackageStateInternal;
 import com.android.server.pm.pkg.SharedLibraryWrapper;
 import com.android.server.pm.pkg.component.ComponentMutateUtils;
+import com.android.server.pm.pkg.component.ParsedActivity;
 import com.android.server.pm.pkg.component.ParsedInstrumentation;
+import com.android.server.pm.pkg.component.ParsedIntentInfo;
 import com.android.server.pm.pkg.component.ParsedPermission;
 import com.android.server.pm.pkg.component.ParsedPermissionGroup;
 import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
@@ -207,6 +209,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -692,7 +695,7 @@
                     if ((installFlags & PackageManager.INSTALL_ALL_WHITELIST_RESTRICTED_PERMISSIONS)
                             != 0) {
                         permissionParamsBuilder.setAllowlistedRestrictedPermissions(
-                                pkgSetting.getPkg().getRequestedPermissions());
+                                new ArrayList<>(pkgSetting.getPkg().getRequestedPermissions()));
                     }
                     mPm.mPermissionManager.onPackageInstalled(pkgSetting.getPkg(),
                             Process.INVALID_UID /* previousAppId */,
@@ -1505,29 +1508,34 @@
         } else {
             // Enable SCAN_NO_DEX flag to skip dexopt at a later stage
             scanFlags |= SCAN_NO_DEX;
+            // The native libs of Apex is located in apex_payload.img, don't need to parse it from
+            // the original apex file
+            if (!isApex) {
+                try {
+                    PackageSetting pkgSetting;
+                    synchronized (mPm.mLock) {
+                        pkgSetting = mPm.mSettings.getPackageLPr(pkgName);
+                    }
+                    boolean isUpdatedSystemAppFromExistingSetting = pkgSetting != null
+                            && pkgSetting.isUpdatedSystemApp();
+                    final String abiOverride = deriveAbiOverride(request.getAbiOverride());
 
-            try {
-                PackageSetting pkgSetting;
-                synchronized (mPm.mLock) {
-                    pkgSetting = mPm.mSettings.getPackageLPr(pkgName);
+                    // TODO: Are these system flags actually set properly at this stage?
+                    boolean isUpdatedSystemAppInferred =
+                            pkgSetting != null && pkgSetting.isSystem();
+                    final Pair<PackageAbiHelper.Abis, PackageAbiHelper.NativeLibraryPaths>
+                            derivedAbi = mPackageAbiHelper.derivePackageAbi(parsedPackage,
+                            systemApp, (isUpdatedSystemAppFromExistingSetting
+                                    || isUpdatedSystemAppInferred), abiOverride,
+                            ScanPackageUtils.getAppLib32InstallDir());
+                    derivedAbi.first.applyTo(parsedPackage);
+                    derivedAbi.second.applyTo(parsedPackage);
+                } catch (PackageManagerException pme) {
+                    Slog.e(TAG, "Error deriving application ABI", pme);
+                    throw PrepareFailure.ofInternalError(
+                            "Error deriving application ABI: " + pme.getMessage(),
+                            PackageManagerException.INTERNAL_ERROR_DERIVING_ABI);
                 }
-                boolean isUpdatedSystemAppFromExistingSetting = pkgSetting != null
-                        && pkgSetting.isUpdatedSystemApp();
-                final String abiOverride = deriveAbiOverride(request.getAbiOverride());
-
-                // TODO: Are these system flags actually set properly at this stage?
-                boolean isUpdatedSystemAppInferred = pkgSetting != null && pkgSetting.isSystem();
-                final Pair<PackageAbiHelper.Abis, PackageAbiHelper.NativeLibraryPaths>
-                        derivedAbi = mPackageAbiHelper.derivePackageAbi(parsedPackage, systemApp,
-                        isUpdatedSystemAppFromExistingSetting || isUpdatedSystemAppInferred,
-                        abiOverride, ScanPackageUtils.getAppLib32InstallDir());
-                derivedAbi.first.applyTo(parsedPackage);
-                derivedAbi.second.applyTo(parsedPackage);
-            } catch (PackageManagerException pme) {
-                Slog.e(TAG, "Error deriving application ABI", pme);
-                throw PrepareFailure.ofInternalError(
-                        "Error deriving application ABI: " + pme.getMessage(),
-                        PackageManagerException.INTERNAL_ERROR_DERIVING_ABI);
             }
         }
 
@@ -2140,7 +2148,7 @@
 
     private void updateSettingsInternalLI(AndroidPackage pkg,
             int[] allUsers, InstallRequest installRequest) {
-        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "updateSettings");
+        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "updateSettingsInternal");
 
         final String pkgName = pkg.getPackageName();
         final int[] installedForUsers = installRequest.getOriginUsers();
@@ -2314,9 +2322,7 @@
                         & PackageManager.INSTALL_GRANT_ALL_REQUESTED_PERMISSIONS) != 0;
                 if (grantRequestedPermissions) {
                     var permissionStates = new ArrayMap<String, Integer>();
-                    var requestedPermissions = pkg.getRequestedPermissions();
-                    for (int index = 0; index < requestedPermissions.size(); index++) {
-                        var permissionName = requestedPermissions.get(index);
+                    for (var permissionName : pkg.getRequestedPermissions()) {
                         permissionStates.put(permissionName,
                                 PackageInstaller.SessionParams.PERMISSION_STATE_GRANTED);
                     }
@@ -2332,7 +2338,8 @@
                         (installRequest.getInstallFlags()
                                 & PackageManager.INSTALL_ALL_WHITELIST_RESTRICTED_PERMISSIONS) != 0;
                 final List<String> allowlistedRestrictedPermissions =
-                        allowlistAllRestrictedPermissions ? pkg.getRequestedPermissions()
+                        allowlistAllRestrictedPermissions
+                                ? new ArrayList<>(pkg.getRequestedPermissions())
                                 : installRequest.getAllowlistedRestrictedPermissions();
                 if (allowlistedRestrictedPermissions != null) {
                     permissionParamsBuilder.setAllowlistedRestrictedPermissions(
@@ -2791,6 +2798,8 @@
                     final String[] pkgNames = new String[]{
                             request.getRemovedInfo().mRemovedPackage};
                     final int[] uids = new int[]{request.getRemovedInfo().mUid};
+                    mPm.notifyResourcesChanged(false /* mediaStatus */,
+                            true /* replacing */, pkgNames, uids);
                     mBroadcastHelper.sendResourcesChangedBroadcast(mPm::snapshotComputer,
                             false /* mediaStatus */, true /* replacing */, pkgNames, uids);
                 }
@@ -2996,6 +3005,8 @@
                     final int[] uids = new int[]{request.getPkg().getUid()};
                     mBroadcastHelper.sendResourcesChangedBroadcast(mPm::snapshotComputer,
                             true /* mediaStatus */, true /* replacing */, pkgNames, uids);
+                    mPm.notifyResourcesChanged(true /* mediaStatus */, true /* replacing */,
+                            pkgNames, uids);
                 }
             } else if (!ArrayUtils.isEmpty(request.getLibraryConsumers())) { // if static shared lib
                 // No need to kill consumers if it's installation of new version static shared lib.
@@ -3713,12 +3724,15 @@
 
             if (throwable == null) {
                 try {
+                    Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "addForInitLI");
                     addForInitLI(parseResult.parsedPackage, parseFlags, scanFlags,
                             new UserHandle(UserHandle.USER_SYSTEM), apexInfo);
                 } catch (PackageManagerException e) {
                     errorCode = e.error;
                     errorMsg = "Failed to scan " + parseResult.scanFile + ": " + e.getMessage();
                     Slog.w(TAG, errorMsg);
+                } finally {
+                    Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
                 }
             } else if (throwable instanceof PackageManagerException) {
                 PackageManagerException e = (PackageManagerException) throwable;
@@ -3925,23 +3939,6 @@
             }
         }
 
-        // If this is a system app we hadn't seen before, and this is a first boot or OTA,
-        // we need to unstop it if it doesn't have a launcher entry.
-        if (mPm.mShouldStopSystemPackagesByDefault && scanResult.mRequest.mPkgSetting == null
-                && ((scanFlags & SCAN_FIRST_BOOT_OR_UPGRADE) != 0)
-                && ((scanFlags & SCAN_AS_SYSTEM) != 0)) {
-            final Intent launcherIntent = new Intent(Intent.ACTION_MAIN);
-            launcherIntent.addCategory(Intent.CATEGORY_LAUNCHER);
-            launcherIntent.setPackage(parsedPackage.getPackageName());
-            final List<ResolveInfo> launcherActivities =
-                    mPm.snapshotComputer().queryIntentActivitiesInternal(launcherIntent, null,
-                            PackageManager.MATCH_DIRECT_BOOT_AWARE
-                            | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, 0);
-            if (launcherActivities.isEmpty()) {
-                scanResult.mPkgSetting.setStopped(false, 0);
-            }
-        }
-
         if (mIncrementalManager != null && isIncrementalPath(parsedPackage.getPath())) {
             if (scanResult.mPkgSetting != null && scanResult.mPkgSetting.isLoading()) {
                 // Continue monitoring loading progress of active incremental packages
@@ -4314,6 +4311,7 @@
         //   - It's an APEX or overlay package since stopped state does not affect them.
         //   - It is enumerated with a <initial-package-state> tag having the stopped attribute
         //     set to false
+        //   - It doesn't have a launcher entry which means the user doesn't have a way to unstop it
         final boolean isApexPkg = (scanFlags & SCAN_AS_APEX) != 0;
         if (mPm.mShouldStopSystemPackagesByDefault
                 && scanSystemPartition
@@ -4322,8 +4320,9 @@
                 && !parsedPackage.isOverlayIsStatic()
         ) {
             String packageName = parsedPackage.getPackageName();
-            if (!mPm.mInitialNonStoppedSystemPackages.contains(packageName)
-                    && !"android".contentEquals(packageName)) {
+            if (!"android".contentEquals(packageName)
+                    && !mPm.mInitialNonStoppedSystemPackages.contains(packageName)
+                    && hasLauncherEntry(parsedPackage)) {
                 scanFlags |= SCAN_AS_STOPPED_SYSTEM_APP;
             }
         }
@@ -4333,6 +4332,22 @@
         return new Pair<>(scanResult, shouldHideSystemApp);
     }
 
+    private static boolean hasLauncherEntry(ParsedPackage parsedPackage) {
+        final HashSet<String> categories = new HashSet<>();
+        categories.add(Intent.CATEGORY_LAUNCHER);
+        final List<ParsedActivity> activities = parsedPackage.getActivities();
+        for (int indexActivity = 0; indexActivity < activities.size(); indexActivity++) {
+            final List<ParsedIntentInfo> intents = activities.get(indexActivity).getIntents();
+            for (int indexIntent = 0; indexIntent < intents.size(); indexIntent++) {
+                final IntentFilter intentFilter = intents.get(indexIntent).getIntentFilter();
+                if (intentFilter != null && intentFilter.matchCategories(categories) == null) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
     /**
      * Returns if forced apk verification can be skipped for the whole package, including splits.
      */
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index 68c8abf..9ac983d 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -399,7 +399,7 @@
                     args[j] = mArgs.get(i + j);
                 }
                 final CreateAppDataResult[] results = installer.createAppDataBatched(args);
-                for (int j = 0; j < args.length; j++) {
+                for (int j = 0; j < results.length; j++) {
                     final CreateAppDataResult result = results[j];
                     final CompletableFuture<Long> future = mFutures.get(i + j);
                     if (result.exceptionCode == 0) {
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index ae169318..12f3aa9 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -372,7 +372,7 @@
                 filter.addDataScheme("package");
                 mContext.registerReceiverAsUser(mPackageRemovedListener, UserHandle.ALL, filter,
                         /* broadcastPermission= */ null, mCallbackHandler);
-                mPackageMonitor.register(mContext, UserHandle.ALL, true, mCallbackHandler);
+                mPackageMonitor.register(mContext, UserHandle.ALL, mCallbackHandler);
                 mIsWatchingPackageBroadcasts = true;
             }
         }
diff --git a/services/core/java/com/android/server/pm/MovePackageHelper.java b/services/core/java/com/android/server/pm/MovePackageHelper.java
index bec5a9a..15c10aa 100644
--- a/services/core/java/com/android/server/pm/MovePackageHelper.java
+++ b/services/core/java/com/android/server/pm/MovePackageHelper.java
@@ -35,6 +35,7 @@
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageStats;
+import android.content.pm.UserInfo;
 import android.content.pm.parsing.ApkLiteParseUtils;
 import android.content.pm.parsing.PackageLite;
 import android.content.pm.parsing.result.ParseResult;
@@ -49,6 +50,7 @@
 import android.os.UserHandle;
 import android.os.storage.StorageManager;
 import android.os.storage.VolumeInfo;
+import android.text.TextUtils;
 import android.util.MathUtils;
 import android.util.Slog;
 import android.util.SparseIntArray;
@@ -100,16 +102,13 @@
                     "3rd party apps are not allowed on internal storage");
         }
 
-
-        final String currentVolumeUuid = packageState.getVolumeUuid();
-
         final File probe = new File(pkg.getPath());
-        final File probeOat = new File(probe, "oat");
-        if (!probe.isDirectory() || !probeOat.isDirectory()) {
+        if (!probe.isDirectory()) {
             throw new PackageManagerException(MOVE_FAILED_INTERNAL_ERROR,
                     "Move only supported for modern cluster style installs");
         }
 
+        final String currentVolumeUuid = packageState.getVolumeUuid();
         if (Objects.equals(currentVolumeUuid, volumeUuid)) {
             throw new PackageManagerException(MOVE_FAILED_INTERNAL_ERROR,
                     "Package already moved to " + volumeUuid);
@@ -220,6 +219,16 @@
                     "Not enough free space to move");
         }
 
+        try {
+            for (int index = 0; index < installedUserIds.length; index++) {
+                prepareUserDataForVolumeIfRequired(volumeUuid, installedUserIds[index], storage);
+            }
+        } catch (RuntimeException e) {
+            freezer.close();
+            throw new PackageManagerException(MOVE_FAILED_INTERNAL_ERROR,
+                    "Failed to prepare user storage while moving app");
+        }
+
         mPm.mMoveCallbacks.notifyStatusChanged(moveId, 10);
 
         final CountDownLatch installedLatch = new CountDownLatch(1);
@@ -366,6 +375,29 @@
         return true;
     }
 
+    private void prepareUserDataForVolumeIfRequired(String volumeUuid, int userId,
+            StorageManager storageManager) {
+        if (TextUtils.isEmpty(volumeUuid)
+                || doesDataDirectoryExistForUser(volumeUuid, userId)) {
+            return;
+        }
+        if (DEBUG_INSTALL) {
+            Slog.d(TAG, "Preparing user directories for user u" + userId + " for UUID "
+                    + volumeUuid);
+        }
+        final UserInfo user = mPm.mUserManager.getUserInfo(userId);
+        if (user == null) return;
+        // This call is same as StorageEventHelper#loadPrivatePackagesInner which prepares
+        // the storage before reconciling apps
+        storageManager.prepareUserStorage(volumeUuid, user.id, user.serialNumber,
+                StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE);
+    }
+
+    private boolean doesDataDirectoryExistForUser(String uuid, int userId) {
+        final File userDirectoryFile = Environment.getDataUserCeDirectory(uuid, userId);
+        return userDirectoryFile != null && userDirectoryFile.exists();
+    }
+
     public static class MoveCallbacks extends Handler {
         private static final int MSG_CREATED = 1;
         private static final int MSG_STATUS_CHANGED = 2;
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index a911537..2c0e74d 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -1304,9 +1304,10 @@
         }
     }
 
+    @android.annotation.EnforcePermission(android.Manifest.permission.INSTALL_PACKAGES)
     @Override
     public void setPermissionsResult(int sessionId, boolean accepted) {
-        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.INSTALL_PACKAGES, TAG);
+        setPermissionsResult_enforcePermission();
 
         synchronized (mSessions) {
             PackageInstallerSession session = mSessions.get(sessionId);
@@ -1317,10 +1318,16 @@
     }
 
     private boolean isValidForInstallConstraints(PackageStateInternal ps,
-            String installerPackageName) {
+            String installerPackageName, int installerUid, String packageName) {
+        final var snapshot = mPm.snapshotComputer();
+        final var isSelfUpdatePermissionGranted =
+                (snapshot.checkUidPermission(android.Manifest.permission.INSTALL_SELF_UPDATES,
+                        installerUid) == PackageManager.PERMISSION_GRANTED);
+        final var isSelfUpdateAllowed = isSelfUpdatePermissionGranted && TextUtils.equals(
+                packageName, installerPackageName);
         return TextUtils.equals(ps.getInstallSource().mInstallerPackageName, installerPackageName)
                 || TextUtils.equals(ps.getInstallSource().mUpdateOwnerPackageName,
-                installerPackageName);
+                installerPackageName) || isSelfUpdateAllowed;
     }
 
     private CompletableFuture<InstallConstraintsResult> checkInstallConstraintsInternal(
@@ -1339,7 +1346,8 @@
         if (!PackageManagerServiceUtils.isSystemOrRootOrShell(callingUid)) {
             for (var packageName : packageNames) {
                 var ps = snapshot.getPackageStateInternal(packageName);
-                if (ps == null || !isValidForInstallConstraints(ps, installerPackageName)) {
+                if (ps == null || !isValidForInstallConstraints(ps, installerPackageName,
+                        callingUid, packageName)) {
                     throw new SecurityException("Caller has no access to package " + packageName);
                 }
             }
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index f0e3895..6136197 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -66,6 +66,7 @@
 import android.app.admin.DevicePolicyEventLogger;
 import android.app.admin.DevicePolicyManager;
 import android.app.admin.DevicePolicyManagerInternal;
+import android.app.compat.CompatChanges;
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.EnabledSince;
 import android.content.ComponentName;
@@ -333,6 +334,15 @@
 
     private static final int APP_METADATA_FILE_ACCESS_MODE = 0640;
 
+    /**
+     * Throws IllegalArgumentException if the {@link IntentSender} from an immutable
+     * {@link android.app.PendingIntent} when caller has a target SDK of API
+     * {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM} or above.
+     */
+    @ChangeId
+    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
+    private static final long THROW_EXCEPTION_COMMIT_WITH_IMMUTABLE_PENDING_INTENT = 240618202L;
+
     // TODO: enforce INSTALL_ALLOW_TEST
     // TODO: enforce INSTALL_ALLOW_DOWNGRADE
 
@@ -451,6 +461,9 @@
     private IntentSender mRemoteStatusReceiver;
 
     @GuardedBy("mLock")
+    private IntentSender mPreapprovalRemoteStatusReceiver;
+
+    @GuardedBy("mLock")
     private PreapprovalDetails mPreapprovalDetails;
 
     /** Fields derived from commit parsing */
@@ -695,9 +708,30 @@
         @Override
         public void verifySession() {
             assertCallerIsOwnerOrRootOrSystem();
-            Preconditions.checkArgument(isCommitted());
-            Preconditions.checkArgument(!isInTerminalState());
-            verify();
+            if (isCommittedAndNotInTerminalState()) {
+                verify();
+            }
+        }
+
+        private boolean isCommittedAndNotInTerminalState() {
+            String errorMsg = null;
+            if (!isCommitted()) {
+                errorMsg = TextUtils.formatSimple("The session %d should be committed", sessionId);
+            } else if (isSessionApplied()) {
+                errorMsg = TextUtils.formatSimple("The session %d has applied", sessionId);
+            } else if (isSessionFailed()) {
+                synchronized (PackageInstallerSession.this.mLock) {
+                    errorMsg = TextUtils.formatSimple("The session %d has failed with error: %s",
+                            sessionId, PackageInstallerSession.this.mSessionErrorMessage);
+                }
+            }
+            if (errorMsg != null) {
+                Slog.e(TAG, "verifySession error: " + errorMsg);
+                setSessionFailed(INSTALL_FAILED_INTERNAL_ERROR, errorMsg);
+                onSessionVerificationFailure(INSTALL_FAILED_INTERNAL_ERROR, errorMsg);
+                return false;
+            }
+            return true;
         }
     }
 
@@ -1864,6 +1898,12 @@
     @Override
     public void commit(@NonNull IntentSender statusReceiver, boolean forTransfer) {
         assertNotChild("commit");
+        boolean throwsExceptionCommitImmutableCheck = CompatChanges.isChangeEnabled(
+                THROW_EXCEPTION_COMMIT_WITH_IMMUTABLE_PENDING_INTENT, Binder.getCallingUid());
+        if (throwsExceptionCommitImmutableCheck && statusReceiver.isImmutable()) {
+            throw new IllegalArgumentException(
+                "The commit() status receiver should come from a mutable PendingIntent");
+        }
 
         if (!markAsSealed(statusReceiver, forTransfer)) {
             return;
@@ -2003,11 +2043,11 @@
          * {@link #setPermissionsResult(boolean)} is called and then
          * {@link #MSG_PRE_APPROVAL_REQUEST} is handled to come back here to check again.
          */
-        if (sendPendingUserActionIntentIfNeeded()) {
+        if (sendPendingUserActionIntentIfNeeded(/* forPreapproval= */true)) {
             return;
         }
 
-        dispatchSessionPreappoved();
+        dispatchSessionPreapproved();
     }
 
     private final class FileSystemConnector extends
@@ -2462,14 +2502,16 @@
      * @return true if the session set requires user action for the installation, otherwise false.
      */
     @WorkerThread
-    private boolean sendPendingUserActionIntentIfNeeded() {
+    private boolean sendPendingUserActionIntentIfNeeded(boolean forPreapproval) {
         // To support pre-approval request of atomic install, we allow child session to handle
         // the result by itself since it has the status receiver.
         if (isCommitted()) {
             assertNotChild("PackageInstallerSession#sendPendingUserActionIntentIfNeeded");
         }
-
-        final IntentSender statusReceiver = getRemoteStatusReceiver();
+        // Since there are separate status receivers for session preapproval and commit,
+        // check whether user action is requested for session preapproval or commit
+        final IntentSender statusReceiver = forPreapproval ? getPreapprovalRemoteStatusReceiver()
+                                            : getRemoteStatusReceiver();
         return sessionContains(s -> checkUserActionRequirement(s, statusReceiver));
     }
 
@@ -2492,7 +2534,8 @@
          * install. Since control may come back here more than 1 time, we must ensure that it's
          * value is not overwritten.
          */
-        boolean wasUserActionIntentSent = sendPendingUserActionIntentIfNeeded();
+        boolean wasUserActionIntentSent =
+                sendPendingUserActionIntentIfNeeded(/* forPreapproval= */false);
         if (mUserActionRequired == null) {
             mUserActionRequired = wasUserActionIntentSent;
         }
@@ -2554,6 +2597,18 @@
         }
     }
 
+    private IntentSender getPreapprovalRemoteStatusReceiver() {
+        synchronized (mLock) {
+            return mPreapprovalRemoteStatusReceiver;
+        }
+    }
+
+    private void setPreapprovalRemoteStatusReceiver(IntentSender remoteStatusReceiver) {
+        synchronized (mLock) {
+            mPreapprovalRemoteStatusReceiver = remoteStatusReceiver;
+        }
+    }
+
     /**
      * Prepares staged directory with any inherited APKs.
      */
@@ -2791,7 +2846,8 @@
     private void onVerificationComplete() {
         if (isStaged()) {
             mStagingManager.commitSession(mStagedSession);
-            sendUpdateToRemoteStatusReceiver(INSTALL_SUCCEEDED, "Session staged", null);
+            sendUpdateToRemoteStatusReceiver(INSTALL_SUCCEEDED, "Session staged",
+                    /* extras= */ null, /* forPreapproval= */ false);
             return;
         }
         install();
@@ -4109,16 +4165,18 @@
         return params.installFlags;
     }
 
+    @android.annotation.EnforcePermission(android.Manifest.permission.USE_INSTALLER_V2)
     @Override
     public DataLoaderParamsParcel getDataLoaderParams() {
-        mContext.enforceCallingOrSelfPermission(Manifest.permission.USE_INSTALLER_V2, null);
+        getDataLoaderParams_enforcePermission();
         return params.dataLoaderParams != null ? params.dataLoaderParams.getData() : null;
     }
 
+    @android.annotation.EnforcePermission(android.Manifest.permission.USE_INSTALLER_V2)
     @Override
     public void addFile(int location, String name, long lengthBytes, byte[] metadata,
             byte[] signature) {
-        mContext.enforceCallingOrSelfPermission(Manifest.permission.USE_INSTALLER_V2, null);
+        addFile_enforcePermission();
         if (!isDataLoaderInstallation()) {
             throw new IllegalStateException(
                     "Cannot add files to non-data loader installation session.");
@@ -4149,9 +4207,10 @@
         }
     }
 
+    @android.annotation.EnforcePermission(android.Manifest.permission.USE_INSTALLER_V2)
     @Override
     public void removeFile(int location, String name) {
-        mContext.enforceCallingOrSelfPermission(Manifest.permission.USE_INSTALLER_V2, null);
+        removeFile_enforcePermission();
         if (!isDataLoaderInstallation()) {
             throw new IllegalStateException(
                     "Cannot add files to non-data loader installation session.");
@@ -4545,7 +4604,11 @@
     }
 
     private void dispatchSessionFinished(int returnCode, String msg, Bundle extras) {
-        sendUpdateToRemoteStatusReceiver(returnCode, msg, extras);
+        // Session can be marked as finished due to user rejecting pre approval or commit request,
+        // any internal error or after successful completion. As such, check whether
+        // the session is in the preapproval stage or the commit stage.
+        sendUpdateToRemoteStatusReceiver(returnCode, msg, extras,
+                /* forPreapproval= */ isPreapprovalRequested() && !isCommitted());
 
         synchronized (mLock) {
             mFinalStatus = returnCode;
@@ -4567,8 +4630,10 @@
         }
     }
 
-    private void sendUpdateToRemoteStatusReceiver(int returnCode, String msg, Bundle extras) {
-        final IntentSender statusReceiver = getRemoteStatusReceiver();
+    private void sendUpdateToRemoteStatusReceiver(int returnCode, String msg, Bundle extras,
+            boolean forPreapproval) {
+        final IntentSender statusReceiver = forPreapproval ? getPreapprovalRemoteStatusReceiver()
+                                            : getRemoteStatusReceiver();
         if (statusReceiver != null) {
             // Execute observer.onPackageInstalled on different thread as we don't want callers
             // inside the system server have to worry about catching the callbacks while they are
@@ -4584,8 +4649,8 @@
         }
     }
 
-    private void dispatchSessionPreappoved() {
-        final IntentSender target = getRemoteStatusReceiver();
+    private void dispatchSessionPreapproved() {
+        final IntentSender target = getPreapprovalRemoteStatusReceiver();
         final Intent intent = new Intent();
         intent.putExtra(PackageInstaller.EXTRA_SESSION_ID, sessionId);
         intent.putExtra(PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_SUCCESS);
@@ -4606,7 +4671,8 @@
 
         if (!mPm.isPreapprovalRequestAvailable()) {
             sendUpdateToRemoteStatusReceiver(INSTALL_FAILED_PRE_APPROVAL_NOT_AVAILABLE,
-                    "Request user pre-approval is currently not available.", null /* extras */);
+                    "Request user pre-approval is currently not available.", /* extras= */null,
+                    /* preapproval= */true);
             return;
         }
 
@@ -4628,7 +4694,7 @@
         synchronized (mLock) {
             assertPreparedAndNotSealedLocked("request of session " + sessionId);
             mPreapprovalDetails = details;
-            setRemoteStatusReceiver(statusReceiver);
+            setPreapprovalRemoteStatusReceiver(statusReceiver);
         }
     }
 
diff --git a/services/core/java/com/android/server/pm/PackageManagerLocal.java b/services/core/java/com/android/server/pm/PackageManagerLocal.java
index 935c4dd..6266ef3 100644
--- a/services/core/java/com/android/server/pm/PackageManagerLocal.java
+++ b/services/core/java/com/android/server/pm/PackageManagerLocal.java
@@ -24,6 +24,7 @@
 import android.os.UserHandle;
 
 import com.android.server.pm.pkg.PackageState;
+import com.android.server.pm.pkg.SharedUserApi;
 
 import java.io.IOException;
 import java.lang.annotation.Retention;
@@ -150,6 +151,16 @@
         Map<String, PackageState> getPackageStates();
 
         /**
+         * Returns a map of all {@link SharedUserApi SharedUsers} on the device.
+         *
+         * @return Mapping of shared user name to {@link SharedUserApi}.
+         *
+         * @hide Pending API
+         */
+        @NonNull
+        Map<String, SharedUserApi> getSharedUsers();
+
+        /**
          * Returns a map of all disabled system {@link PackageState PackageStates} on the device.
          *
          * @return Mapping of package name to disabled system {@link PackageState}.
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index db47306..9137d44 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -15,7 +15,6 @@
 
 package com.android.server.pm;
 
-import static android.Manifest.permission.GET_APP_METADATA;
 import static android.Manifest.permission.MANAGE_DEVICE_ADMINS;
 import static android.Manifest.permission.SET_HARMFUL_APP_WARNINGS;
 import static android.app.AppOpsManager.MODE_IGNORED;
@@ -30,6 +29,7 @@
 import static android.content.pm.PackageManager.MATCH_FACTORY_ONLY;
 import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_UNSET;
 import static android.os.Process.INVALID_UID;
 import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
 import static android.os.storage.StorageManager.FLAG_STORAGE_CE;
@@ -96,7 +96,6 @@
 import android.content.pm.InstantAppRequest;
 import android.content.pm.ModuleInfo;
 import android.content.pm.PackageInfo;
-import android.content.pm.PackageInfoLite;
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.ComponentEnabledSetting;
@@ -130,6 +129,7 @@
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.IBinder;
+import android.os.IRemoteCallback;
 import android.os.Message;
 import android.os.Parcel;
 import android.os.ParcelFileDescriptor;
@@ -148,7 +148,6 @@
 import android.os.UserManager;
 import android.os.incremental.IncrementalManager;
 import android.os.incremental.PerUidReadTimeouts;
-import android.os.storage.IStorageManager;
 import android.os.storage.StorageManager;
 import android.os.storage.StorageManagerInternal;
 import android.os.storage.VolumeRecord;
@@ -157,7 +156,6 @@
 import android.provider.Settings.Global;
 import android.provider.Settings.Secure;
 import android.text.TextUtils;
-import android.text.format.DateUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.DisplayMetrics;
@@ -189,6 +187,7 @@
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
 import com.android.permission.persistence.RuntimePermissionsPersistence;
+import com.android.permission.persistence.RuntimePermissionsState;
 import com.android.server.EventLogTags;
 import com.android.server.FgThread;
 import com.android.server.LocalManagerRegistry;
@@ -197,6 +196,7 @@
 import com.android.server.PackageWatchdog;
 import com.android.server.ServiceThread;
 import com.android.server.SystemConfig;
+import com.android.server.ThreadPriorityBooster;
 import com.android.server.Watchdog;
 import com.android.server.apphibernation.AppHibernationManagerInternal;
 import com.android.server.art.DexUseManagerLocal;
@@ -219,6 +219,7 @@
 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;
 import com.android.server.pm.permission.PermissionManagerService;
 import com.android.server.pm.permission.PermissionManagerServiceInternal;
 import com.android.server.pm.pkg.AndroidPackage;
@@ -482,18 +483,6 @@
     static final long WATCHDOG_TIMEOUT = 1000*60*10;     // ten minutes
 
     /**
-     * Wall-clock timeout (in milliseconds) after which we *require* that an fstrim
-     * be run on this device.  We use the value in the Settings.Global.MANDATORY_FSTRIM_INTERVAL
-     * settings entry if available, otherwise we use the hardcoded default.  If it's been
-     * more than this long since the last fstrim, we force one during the boot sequence.
-     *
-     * This backstops other fstrim scheduling:  if the device is alive at midnight+idle,
-     * one gets run at the next available charging+idle time.  This final mandatory
-     * no-fstrim check kicks in only of the other scheduling criteria is never met.
-     */
-    private static final long DEFAULT_MANDATORY_FSTRIM_INTERVAL = 3 * DateUtils.DAY_IN_MILLIS;
-
-    /**
      * Default IncFs timeouts. Maximum values in IncFs is 1hr.
      *
      * <p>If flag value is empty, the default value will be assigned.
@@ -599,8 +588,6 @@
 
     final ProcessLoggingHandler mProcessLoggingHandler;
 
-    private final boolean mEnableFreeCacheV2;
-
     private final int mSdkVersion;
     final Context mContext;
     final boolean mFactoryTest;
@@ -725,6 +712,9 @@
     @NonNull
     private final PackageObserverHelper mPackageObserverHelper = new PackageObserverHelper();
 
+    @NonNull
+    private final PackageMonitorCallbackHelper mPackageMonitorCallbackHelper;
+
     private final ModuleInfoProvider mModuleInfoProvider;
 
     final ApexManager mApexManager;
@@ -947,8 +937,6 @@
     // When the service constructor finished plus a delay (used for broadcast delay computation)
     private long mServiceStartWithDelay;
 
-    private static final long FREE_STORAGE_UNUSED_STATIC_SHARED_LIB_MIN_CACHE_PERIOD =
-            TimeUnit.HOURS.toMillis(2); /* two hours */
     static final long DEFAULT_UNUSED_STATIC_SHARED_LIB_MIN_CACHE_PERIOD =
             TimeUnit.DAYS.toMillis(7); /* 7 days */
 
@@ -1002,6 +990,34 @@
     private final SuspendPackageHelper mSuspendPackageHelper;
     private final DistractingPackageHelper mDistractingPackageHelper;
     private final StorageEventHelper mStorageEventHelper;
+    private final FreeStorageHelper mFreeStorageHelper;
+
+
+    private static final boolean ENABLE_BOOST = false;
+
+    private static ThreadPriorityBooster sThreadPriorityBooster = new ThreadPriorityBooster(
+            Process.THREAD_PRIORITY_FOREGROUND, LockGuard.INDEX_PACKAGES);
+
+    /**
+     * Boost the priority of the thread before holding PM traced lock.
+     * @hide
+     */
+    public static void boostPriorityForPackageManagerTracedLockedSection() {
+        if (ENABLE_BOOST) {
+            sThreadPriorityBooster.boost();
+        }
+    }
+
+
+    /**
+     * Restore the priority of the thread after release the PM traced lock.
+     * @hide
+     */
+    public static void resetPriorityAfterPackageManagerTracedLockedSection() {
+        if (ENABLE_BOOST) {
+            sThreadPriorityBooster.reset();
+        }
+    }
 
     /**
      * Invalidate the package info cache, which includes updating the cached computer.
@@ -1798,7 +1814,7 @@
         mSnapshotStatistics = null;
 
         mPackages.putAll(testParams.packages);
-        mEnableFreeCacheV2 = testParams.enableFreeCacheV2;
+        mFreeStorageHelper = testParams.freeStorageHelper;
         mSdkVersion = testParams.sdkVersion;
         mAppInstallDir = testParams.appInstallDir;
         mIsEngBuild = testParams.isEngBuild;
@@ -1821,6 +1837,7 @@
         mSharedLibraries.setDeletePackageHelper(mDeletePackageHelper);
 
         mStorageEventHelper = testParams.storageEventHelper;
+        mPackageMonitorCallbackHelper = testParams.packageMonitorCallbackHelper;
 
         registerObservers(false);
         invalidatePackageInfoCache();
@@ -1852,7 +1869,7 @@
         mFactoryTest = factoryTest;
         mMetrics = injector.getDisplayMetrics();
         mInstaller = injector.getInstaller();
-        mEnableFreeCacheV2 = SystemProperties.getBoolean("fw.free_cache_v2", true);
+        mFreeStorageHelper = new FreeStorageHelper(this);
 
         // Create sub-components that provide services / data. Order here is important.
         t.traceBegin("createSubComponents");
@@ -1961,6 +1978,7 @@
         mDomainVerificationManager.setConnection(mDomainVerificationConnection);
 
         mBroadcastHelper = new BroadcastHelper(mInjector);
+        mPackageMonitorCallbackHelper = new PackageMonitorCallbackHelper(mInjector);
         mAppDataHelper = new AppDataHelper(this);
         mInstallPackageHelper = new InstallPackageHelper(this, mAppDataHelper);
         mRemovePackageHelper = new RemovePackageHelper(this, mAppDataHelper);
@@ -2402,13 +2420,6 @@
 
         mInjector.getSystemWrapper().enablePackageCaches();
 
-        // Now after opening every single application zip, make sure they
-        // are all flushed.  Not really needed, but keeps things nice and
-        // tidy.
-        t.traceBegin("GC");
-        VMRuntime.getRuntime().requestConcurrentGC();
-        t.traceEnd();
-
         // The initial scanning above does many calls into installd while
         // holding the mPackages lock, but we're mostly interested in yelling
         // once we have a booted system.
@@ -2489,13 +2500,22 @@
 
     @NonNull
     private String getRequiredServicesExtensionPackageLPr(@NonNull Computer computer) {
-        String servicesExtensionPackage =
-                ensureSystemPackageName(computer,
-                        mContext.getString(R.string.config_servicesExtensionPackage));
+        String configServicesExtensionPackage = mContext.getString(
+                R.string.config_servicesExtensionPackage);
+        if (TextUtils.isEmpty(configServicesExtensionPackage)) {
+            throw new RuntimeException(
+                    "Required services extension package failed due to "
+                            + "config_servicesExtensionPackage is empty.");
+        }
+        String servicesExtensionPackage = ensureSystemPackageName(computer,
+                configServicesExtensionPackage);
         if (TextUtils.isEmpty(servicesExtensionPackage)) {
             throw new RuntimeException(
-                    "Required services extension package is missing, check "
-                            + "config_servicesExtensionPackage.");
+                    "Required services extension package is missing, "
+                            + "config_servicesExtensionPackage had defined with "
+                            + configServicesExtensionPackage
+                            + ", but can not find the package info on the system image, check if "
+                            + "the package has a problem.");
         }
         return servicesExtensionPackage;
     }
@@ -2774,138 +2794,13 @@
      */
     public void freeStorage(String volumeUuid, long bytes,
             @StorageManager.AllocateFlags int flags) throws IOException {
-        final StorageManager storage = mInjector.getSystemService(StorageManager.class);
-        final File file = storage.findPathForUuid(volumeUuid);
-        if (file.getUsableSpace() >= bytes) return;
-
-        if (mEnableFreeCacheV2) {
-            final boolean internalVolume = Objects.equals(StorageManager.UUID_PRIVATE_INTERNAL,
-                    volumeUuid);
-            final boolean aggressive = (flags & StorageManager.FLAG_ALLOCATE_AGGRESSIVE) != 0;
-
-            // 1. Pre-flight to determine if we have any chance to succeed
-            // 2. Consider preloaded data (after 1w honeymoon, unless aggressive)
-            if (internalVolume && (aggressive || SystemProperties
-                    .getBoolean("persist.sys.preloads.file_cache_expired", false))) {
-                deletePreloadsFileCache();
-                if (file.getUsableSpace() >= bytes) return;
-            }
-
-            // 3. Consider parsed APK data (aggressive only)
-            if (internalVolume && aggressive) {
-                FileUtils.deleteContents(mCacheDir);
-                if (file.getUsableSpace() >= bytes) return;
-            }
-
-            // 4. Consider cached app data (above quotas)
-            synchronized (mInstallLock) {
-                try {
-                    mInstaller.freeCache(volumeUuid, bytes, Installer.FLAG_FREE_CACHE_V2);
-                } catch (InstallerException ignored) {
-                }
-            }
-            if (file.getUsableSpace() >= bytes) return;
-
-            Computer computer = snapshotComputer();
-            // 5. Consider shared libraries with refcount=0 and age>min cache period
-            if (internalVolume && mSharedLibraries.pruneUnusedStaticSharedLibraries(computer, bytes,
-                    android.provider.Settings.Global.getLong(mContext.getContentResolver(),
-                            Global.UNUSED_STATIC_SHARED_LIB_MIN_CACHE_PERIOD,
-                            FREE_STORAGE_UNUSED_STATIC_SHARED_LIB_MIN_CACHE_PERIOD))) {
-                return;
-            }
-
-            // 6. Consider dexopt output (aggressive only)
-            // TODO: Implement
-
-            // 7. Consider installed instant apps unused longer than min cache period
-            if (internalVolume) {
-                if (mInstantAppRegistry.pruneInstalledInstantApps(computer, bytes,
-                        android.provider.Settings.Global.getLong(
-                                mContext.getContentResolver(),
-                                Global.INSTALLED_INSTANT_APP_MIN_CACHE_PERIOD,
-                                InstantAppRegistry
-                                        .DEFAULT_INSTALLED_INSTANT_APP_MIN_CACHE_PERIOD))) {
-                    return;
-                }
-            }
-
-            // 8. Consider cached app data (below quotas)
-            synchronized (mInstallLock) {
-                try {
-                    mInstaller.freeCache(volumeUuid, bytes,
-                            Installer.FLAG_FREE_CACHE_V2 | Installer.FLAG_FREE_CACHE_V2_DEFY_QUOTA);
-                } catch (InstallerException ignored) {
-                }
-            }
-            if (file.getUsableSpace() >= bytes) return;
-
-            // 9. Consider DropBox entries
-            // TODO: Implement
-
-            // 10. Consider instant meta-data (uninstalled apps) older that min cache period
-            if (internalVolume) {
-                if (mInstantAppRegistry.pruneUninstalledInstantApps(computer, bytes,
-                        android.provider.Settings.Global.getLong(
-                                mContext.getContentResolver(),
-                                Global.UNINSTALLED_INSTANT_APP_MIN_CACHE_PERIOD,
-                                InstantAppRegistry
-                                        .DEFAULT_UNINSTALLED_INSTANT_APP_MIN_CACHE_PERIOD))) {
-                    return;
-                }
-            }
-
-            // 11. Free storage service cache
-            StorageManagerInternal smInternal =
-                    mInjector.getLocalService(StorageManagerInternal.class);
-            long freeBytesRequired = bytes - file.getUsableSpace();
-            if (freeBytesRequired > 0) {
-                smInternal.freeCache(volumeUuid, freeBytesRequired);
-            }
-
-            // 12. Clear temp install session files
-            mInstallerService.freeStageDirs(volumeUuid);
-        } else {
-            synchronized (mInstallLock) {
-                try {
-                    mInstaller.freeCache(volumeUuid, bytes, 0);
-                } catch (InstallerException ignored) {
-                }
-            }
-        }
-        if (file.getUsableSpace() >= bytes) return;
-
-        throw new IOException("Failed to free " + bytes + " on storage device at " + file);
+        mFreeStorageHelper.freeStorage(volumeUuid, bytes, flags);
     }
 
     int freeCacheForInstallation(int recommendedInstallLocation, PackageLite pkgLite,
             String resolvedPath, String mPackageAbiOverride, int installFlags) {
-        // TODO: focus freeing disk space on the target device
-        final StorageManager storage = StorageManager.from(mContext);
-        final long lowThreshold = storage.getStorageLowBytes(Environment.getDataDirectory());
-
-        final long sizeBytes = PackageManagerServiceUtils.calculateInstalledSize(resolvedPath,
-                mPackageAbiOverride);
-        if (sizeBytes >= 0) {
-            synchronized (mInstallLock) {
-                try {
-                    mInstaller.freeCache(null, sizeBytes + lowThreshold, 0);
-                    PackageInfoLite pkgInfoLite = PackageManagerServiceUtils.getMinimalPackageInfo(
-                            mContext, pkgLite, resolvedPath, installFlags,
-                            mPackageAbiOverride);
-                    // The cache free must have deleted the file we downloaded to install.
-                    if (pkgInfoLite.recommendedInstallLocation
-                            == InstallLocationUtils.RECOMMEND_FAILED_INVALID_URI) {
-                        pkgInfoLite.recommendedInstallLocation =
-                                InstallLocationUtils.RECOMMEND_FAILED_INSUFFICIENT_STORAGE;
-                    }
-                    return pkgInfoLite.recommendedInstallLocation;
-                } catch (Installer.InstallerException e) {
-                    Slog.w(TAG, "Failed to free cache", e);
-                }
-            }
-        }
-        return recommendedInstallLocation;
+        return mFreeStorageHelper.freeCacheForInstallation(recommendedInstallLocation, pkgLite,
+                resolvedPath, mPackageAbiOverride, installFlags);
     }
 
     public ModuleInfo getModuleInfo(String packageName, @PackageManager.ModuleInfoFlags int flags) {
@@ -2930,7 +2825,8 @@
 
     // NOTE: Can't remove due to unsupported app usage
     public int checkPermission(String permName, String pkgName, int userId) {
-        return mPermissionManager.checkPermission(pkgName, permName, userId);
+        return mPermissionManager.checkPermission(pkgName, permName, Context.DEVICE_ID_DEFAULT,
+                userId);
     }
 
     public String getSdkSandboxPackageName() {
@@ -2984,34 +2880,7 @@
     }
 
     public void performFstrimIfNeeded() {
-        PackageManagerServiceUtils.enforceSystemOrRoot("Only the system can request fstrim");
-
-        // Before everything else, see whether we need to fstrim.
-        try {
-            IStorageManager sm = InstallLocationUtils.getStorageManager();
-            if (sm != null) {
-                boolean doTrim = false;
-                final long interval = android.provider.Settings.Global.getLong(
-                        mContext.getContentResolver(),
-                        android.provider.Settings.Global.FSTRIM_MANDATORY_INTERVAL,
-                        DEFAULT_MANDATORY_FSTRIM_INTERVAL);
-                if (interval > 0) {
-                    final long timeSinceLast = System.currentTimeMillis() - sm.lastMaintenance();
-                    if (timeSinceLast > interval) {
-                        doTrim = true;
-                        Slog.w(TAG, "No disk maintenance in " + timeSinceLast
-                                + "; running immediately");
-                    }
-                }
-                if (doTrim) {
-                    sm.runMaintenance();
-                }
-            } else {
-                Slog.e(TAG, "storageManager service unavailable!");
-            }
-        } catch (RemoteException e) {
-            // Can't happen; StorageManagerService is local
-        }
+        mFreeStorageHelper.performFstrimIfNeeded();
     }
 
     public void updatePackagesIfNeeded() {
@@ -3123,6 +2992,27 @@
         mHandler.post(() -> mBroadcastHelper.sendPackageBroadcast(action, pkg, extras, flags,
                 targetPkg, finishedReceiver, userIds, instantUserIds, broadcastAllowList,
                 null /* filterExtrasForReceiver */, bOptions));
+        if (targetPkg == null) {
+            // For some broadcast action, e.g. ACTION_PACKAGE_ADDED, this method will be called
+            // many times to different targets, e.g. installer app, permission controller, other
+            // registered apps. We should filter it to avoid calling back many times for the same
+            // action. When the targetPkg is set, it sends the broadcast to specific app, e.g.
+            // installer app or null for registered apps. The callback only need to send back to the
+            // registered apps so we check the null condition here.
+            notifyPackageMonitor(action, pkg, extras, userIds, instantUserIds, broadcastAllowList);
+        }
+    }
+
+    void notifyPackageMonitor(String action, String pkg, Bundle extras, int[] userIds,
+            int[] instantUserIds, SparseArray<int[]> broadcastAllowList) {
+        mPackageMonitorCallbackHelper.notifyPackageMonitor(action, pkg, extras, userIds,
+                instantUserIds, broadcastAllowList);
+    }
+
+    void notifyResourcesChanged(boolean mediaStatus, boolean replacing,
+            @NonNull String[] pkgNames, @NonNull int[] uids) {
+        mPackageMonitorCallbackHelper.notifyResourcesChanged(mediaStatus, replacing, pkgNames,
+                uids);
     }
 
     @Override
@@ -3171,6 +3061,8 @@
                 userIds, snapshot.getPackageStates());
         mHandler.post(() -> mBroadcastHelper.sendPackageAddedForNewUsers(
                 packageName, appId, userIds, instantUserIds, dataLoaderType, broadcastAllowList));
+        mPackageMonitorCallbackHelper.notifyPackageAddedForNewUsers(packageName, appId, userIds,
+                instantUserIds, dataLoaderType, broadcastAllowList);
         if (sendBootCompleted && !ArrayUtils.isEmpty(userIds)) {
             mHandler.post(() -> {
                         for (int userId : userIds) {
@@ -3534,6 +3426,18 @@
         // within these users.
         mPermissionManager.restoreDelayedRuntimePermissions(packageName, userId);
 
+        // Restore default browser setting if it is now installed.
+        String defaultBrowser;
+        synchronized (mLock) {
+            defaultBrowser = mSettings.getPendingDefaultBrowserLPr(userId);
+        }
+        if (Objects.equals(packageName, defaultBrowser)) {
+            mDefaultAppProvider.setDefaultBrowser(packageName, userId);
+            synchronized (mLock) {
+                mSettings.removePendingDefaultBrowserLPw(userId);
+            }
+        }
+
         // Persistent preferred activity might have came into effect due to this
         // install.
         mPreferredActivityHelper.updateDefaultHomeNotLocked(snapshotComputer(), userId);
@@ -4163,6 +4067,8 @@
         mHandler.post(() -> mBroadcastHelper.sendPackageChangedBroadcast(
                 packageName, dontKillApp, componentNames, packageUid, reason, userIds,
                 instantUserIds, broadcastAllowList));
+        mPackageMonitorCallbackHelper.notifyPackageChanged(packageName, dontKillApp, componentNames,
+                packageUid, reason, userIds, instantUserIds, broadcastAllowList);
     }
 
     /**
@@ -4258,7 +4164,10 @@
         final int livingUserCount = livingUsers.size();
         for (int i = 0; i < livingUserCount; i++) {
             final int userId = livingUsers.get(i).id;
-            if (mSettings.isPermissionUpgradeNeeded(userId)) {
+            final boolean isPermissionUpgradeNeeded = !Objects.equals(
+                    mPermissionManager.getDefaultPermissionGrantFingerprint(userId),
+                    Build.FINGERPRINT);
+            if (isPermissionUpgradeNeeded) {
                 grantPermissionsUserIds = ArrayUtils.appendInt(
                         grantPermissionsUserIds, userId);
             }
@@ -4266,6 +4175,7 @@
         // If we upgraded grant all default permissions before kicking off.
         for (int userId : grantPermissionsUserIds) {
             mLegacyPermissionManager.grantDefaultPermissions(userId);
+            mPermissionManager.setDefaultPermissionGrantFingerprint(Build.FINGERPRINT, userId);
         }
         if (grantPermissionsUserIds == EMPTY_INT_ARRAY) {
             // If we did not grant default permissions, we preload from this the
@@ -4402,6 +4312,7 @@
             mAppsFilter.onUserDeleted(snapshotComputer(), userId);
         }
         mInstantAppRegistry.onUserRemoved(userId);
+        mPackageMonitorCallbackHelper.onUserRemoved(userId);
     }
 
     /**
@@ -4434,6 +4345,7 @@
         if (!convertedFromPreCreated || !readPermissionStateForUser(userId)) {
             mPermissionManager.onUserCreated(userId);
             mLegacyPermissionManager.grantDefaultPermissions(userId);
+            mPermissionManager.setDefaultPermissionGrantFingerprint(Build.FINGERPRINT, userId);
             mDomainVerificationManager.clearUser(userId);
         }
     }
@@ -4443,7 +4355,10 @@
             mPermissionManager.writeLegacyPermissionStateTEMP();
             mSettings.readPermissionStateForUserSyncLPr(userId);
             mPermissionManager.readLegacyPermissionStateTEMP();
-            return mSettings.isPermissionUpgradeNeeded(userId);
+            final boolean isPermissionUpgradeNeeded = !Objects.equals(
+                    mPermissionManager.getDefaultPermissionGrantFingerprint(userId),
+                    Build.FINGERPRINT);
+            return isPermissionUpgradeNeeded;
         }
     }
 
@@ -4697,11 +4612,11 @@
             }
         }
 
+        @android.annotation.EnforcePermission(android.Manifest.permission.CLEAR_APP_USER_DATA)
         @Override
         public void clearApplicationUserData(final String packageName,
                 final IPackageDataObserver observer, final int userId) {
-            mContext.enforceCallingOrSelfPermission(
-                    android.Manifest.permission.CLEAR_APP_USER_DATA, null);
+            clearApplicationUserData_enforcePermission();
 
             final int callingUid = Binder.getCallingUid();
             final Computer snapshot = snapshotComputer();
@@ -4773,10 +4688,10 @@
             });
         }
 
+        @android.annotation.EnforcePermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
         @Override
         public void clearCrossProfileIntentFilters(int sourceUserId, String ownerPackage) {
-            mContext.enforceCallingOrSelfPermission(
-                    android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
+            clearCrossProfileIntentFilters_enforcePermission();
             final int callingUid = Binder.getCallingUid();
             final Computer snapshot = snapshotComputer();
             enforceOwnerRights(snapshot, ownerPackage, callingUid);
@@ -4788,13 +4703,13 @@
             scheduleWritePackageRestrictions(sourceUserId);
         }
 
+        @android.annotation.EnforcePermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
         @Override
         public boolean removeCrossProfileIntentFilter(IntentFilter intentFilter,
                 String ownerPackage,
                 int sourceUserId,
                 int targetUserId, int flags) {
-            mContext.enforceCallingOrSelfPermission(
-                    android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
+            removeCrossProfileIntentFilter_enforcePermission();
             final int callingUid = Binder.getCallingUid();
             enforceOwnerRights(snapshotComputer(), ownerPackage, callingUid);
             mUserManager.enforceCrossProfileIntentFilterAccess(sourceUserId, targetUserId,
@@ -4962,11 +4877,11 @@
         }
 
 
+        @android.annotation.EnforcePermission(android.Manifest.permission.CLEAR_APP_CACHE)
         @Override
         public void freeStorage(final String volumeUuid, final long freeStorageSize,
                 final @StorageManager.AllocateFlags int flags, final IntentSender pi) {
-            mContext.enforceCallingOrSelfPermission(
-                    android.Manifest.permission.CLEAR_APP_CACHE, TAG);
+            freeStorage_enforcePermission();
             mHandler.post(() -> {
                 boolean success = false;
                 try {
@@ -4989,11 +4904,11 @@
             });
         }
 
+        @android.annotation.EnforcePermission(android.Manifest.permission.CLEAR_APP_CACHE)
         @Override
         public void freeStorageAndNotify(final String volumeUuid, final long freeStorageSize,
                 final @StorageManager.AllocateFlags int flags, final IPackageDataObserver observer) {
-            mContext.enforceCallingOrSelfPermission(
-                    android.Manifest.permission.CLEAR_APP_CACHE, null);
+            freeStorageAndNotify_enforcePermission();
             mHandler.post(() -> {
                 boolean success = false;
                 try {
@@ -5078,10 +4993,10 @@
             return token;
         }
 
+        @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_INSTANT_APPS)
         @Override
         public String getInstantAppAndroidId(String packageName, int userId) {
-            mContext.enforceCallingOrSelfPermission(
-                    android.Manifest.permission.ACCESS_INSTANT_APPS, "getInstantAppAndroidId");
+            getInstantAppAndroidId_enforcePermission();
             final Computer snapshot = snapshotComputer();
             snapshot.enforceCrossUserPermission(Binder.getCallingUid(), userId,
                     true /* requireFullPermission */, false /* checkShell */,
@@ -5173,16 +5088,17 @@
             return getMimeGroupInternal(snapshot, packageName, mimeGroup);
         }
 
+        @android.annotation.EnforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS)
         @Override
         public int getMoveStatus(int moveId) {
-            mContext.enforceCallingOrSelfPermission(
-                    Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS, null);
+            getMoveStatus_enforcePermission();
             return mMoveCallbacks.mLastStatus.get(moveId);
         }
 
+        @android.annotation.EnforcePermission(android.Manifest.permission.GET_APP_METADATA)
         @Override
         public ParcelFileDescriptor getAppMetadataFd(String packageName, int userId) {
-            mContext.enforceCallingOrSelfPermission(GET_APP_METADATA, "getAppMetadataFd");
+            getAppMetadataFd_enforcePermission();
             final int callingUid = Binder.getCallingUid();
             final Computer snapshot = snapshotComputer();
             final PackageStateInternal ps = snapshot.getPackageStateForInstalledAndFiltered(
@@ -5239,6 +5155,20 @@
         }
 
         @Override
+        @PackageManager.UserMinAspectRatio
+        public int getUserMinAspectRatio(@NonNull String packageName, int userId) {
+            final Computer snapshot = snapshotComputer();
+            final int callingUid = Binder.getCallingUid();
+            snapshot.enforceCrossUserPermission(
+                    callingUid, userId, false /* requireFullPermission */,
+                    false /* checkShell */, "getUserMinAspectRatio");
+            final PackageStateInternal packageState = snapshot
+                    .getPackageStateForInstalledAndFiltered(packageName, callingUid, userId);
+            return packageState == null ? USER_MIN_ASPECT_RATIO_UNSET
+                    : packageState.getUserStateOrDefault(userId).getMinAspectRatio();
+        }
+
+        @Override
         public Bundle getSuspendedPackageAppExtras(String packageName, int userId) {
             final int callingUid = Binder.getCallingUid();
             final Computer snapshot = snapshot();
@@ -5289,11 +5219,10 @@
                     packageNames, userId, callingUid);
         }
 
+        @android.annotation.EnforcePermission(android.Manifest.permission.PACKAGE_VERIFICATION_AGENT)
         @Override
         public VerifierDeviceIdentity getVerifierDeviceIdentity() throws RemoteException {
-            mContext.enforceCallingOrSelfPermission(
-                    Manifest.permission.PACKAGE_VERIFICATION_AGENT,
-                    "Only package verification agents can read the verifier device identity");
+            getVerifierDeviceIdentity_enforcePermission();
 
             synchronized (mLock) {
                 return mSettings.getVerifierDeviceIdentityLPw(mLiveComputer);
@@ -5315,10 +5244,10 @@
                     false /*direct*/, false /* retainOnUpdate */);
         }
 
+        @android.annotation.EnforcePermission(android.Manifest.permission.MAKE_UID_VISIBLE)
         @Override
         public void makeUidVisible(int recipientUid, int visibleUid) {
-            mContext.enforceCallingOrSelfPermission(
-                    android.Manifest.permission.MAKE_UID_VISIBLE, "makeUidVisible");
+            makeUidVisible_enforcePermission();
             final int callingUid = Binder.getCallingUid();
             final int recipientUserId = UserHandle.getUserId(recipientUid);
             final int visibleUserId = UserHandle.getUserId(visibleUid);
@@ -5417,9 +5346,10 @@
                     processName, uid, seinfo, pid);
         }
 
+        @android.annotation.EnforcePermission(android.Manifest.permission.MOVE_PACKAGE)
         @Override
         public int movePackage(final String packageName, final String volumeUuid) {
-            mContext.enforceCallingOrSelfPermission(Manifest.permission.MOVE_PACKAGE, null);
+            movePackage_enforcePermission();
 
             final int callingUid = Binder.getCallingUid();
             final UserHandle user = new UserHandle(UserHandle.getUserId(callingUid));
@@ -5438,9 +5368,10 @@
             return moveId;
         }
 
+        @android.annotation.EnforcePermission(android.Manifest.permission.MOVE_PACKAGE)
         @Override
         public int movePrimaryStorage(String volumeUuid) throws RemoteException {
-            mContext.enforceCallingOrSelfPermission(Manifest.permission.MOVE_PACKAGE, null);
+            movePrimaryStorage_enforcePermission();
 
             final int realMoveId = mNextMoveId.getAndIncrement();
             final Bundle extras = new Bundle();
@@ -5628,10 +5559,10 @@
             }
         }
 
+        @android.annotation.EnforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS)
         @Override
         public void registerMoveCallback(IPackageMoveObserver callback) {
-            mContext.enforceCallingOrSelfPermission(
-                    Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS, null);
+            registerMoveCallback_enforcePermission();
             mMoveCallbacks.register(callback);
         }
 
@@ -5733,10 +5664,11 @@
                     userId, callingPackage);
         }
 
+        @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_USERS)
         @Override
         public boolean setApplicationHiddenSettingAsUser(String packageName, boolean hidden,
                 int userId) {
-            mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USERS, null);
+            setApplicationHiddenSettingAsUser_enforcePermission();
             final int callingUid = Binder.getCallingUid();
             final Computer snapshot = snapshotComputer();
             snapshot.enforceCrossUserPermission(callingUid, userId, true /* requireFullPermission */,
@@ -5820,11 +5752,11 @@
             }
         }
 
+        @android.annotation.EnforcePermission(android.Manifest.permission.DELETE_PACKAGES)
         @Override
         public boolean setBlockUninstallForUser(String packageName, boolean blockUninstall,
                 int userId) {
-            mContext.enforceCallingOrSelfPermission(
-                    Manifest.permission.DELETE_PACKAGES, null);
+            setBlockUninstallForUser_enforcePermission();
             final Computer snapshot = snapshotComputer();
             PackageStateInternal packageState = snapshot.getPackageStateInternal(packageName);
             if (packageState != null && packageState.getPkg() != null) {
@@ -5916,10 +5848,10 @@
             scheduleWritePackageRestrictions(userId);
         }
 
+        @android.annotation.EnforcePermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
         @Override
         public boolean setInstallLocation(int loc) {
-            mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SECURE_SETTINGS,
-                    null);
+            setInstallLocation_enforcePermission();
             if (getInstallLocation() == loc) {
                 return true;
             }
@@ -6201,6 +6133,32 @@
             return true;
         }
 
+        @android.annotation.EnforcePermission(android.Manifest.permission.INSTALL_PACKAGES)
+        @Override
+        public void setUserMinAspectRatio(@NonNull String packageName, int userId,
+                @PackageManager.UserMinAspectRatio int aspectRatio) {
+            setUserMinAspectRatio_enforcePermission();
+            final int callingUid = Binder.getCallingUid();
+            final Computer snapshot = snapshotComputer();
+            snapshot.enforceCrossUserPermission(callingUid, userId,
+                    false /* requireFullPermission */, false /* checkShell */,
+                    "setUserMinAspectRatio");
+            enforceOwnerRights(snapshot, packageName, callingUid);
+
+            final PackageStateInternal packageState = snapshot
+                    .getPackageStateForInstalledAndFiltered(packageName, callingUid, userId);
+            if (packageState == null) {
+                return;
+            }
+
+            if (packageState.getUserStateOrDefault(userId).getMinAspectRatio() == aspectRatio) {
+                return;
+            }
+
+            commitPackageStateMutation(null, packageName, state ->
+                    state.userState(userId).setMinAspectRatio(aspectRatio));
+        }
+
         @Override
         @SuppressWarnings("GuardedBy")
         public void setRuntimePermissionsVersion(int version, @UserIdInt int userId) {
@@ -6230,17 +6188,18 @@
                     state.userState(userId).setSplashScreenTheme(themeId));
         }
 
+        @android.annotation.EnforcePermission(android.Manifest.permission.INSTALL_PACKAGES)
         @Override
         public void setUpdateAvailable(String packageName, boolean updateAvailable) {
-            mContext.enforceCallingOrSelfPermission(Manifest.permission.INSTALL_PACKAGES, null);
+            setUpdateAvailable_enforcePermission();
             commitPackageStateMutation(null, packageName, state ->
                     state.setUpdateAvailable(updateAvailable));
         }
 
+        @android.annotation.EnforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS)
         @Override
         public void unregisterMoveCallback(IPackageMoveObserver callback) {
-            mContext.enforceCallingOrSelfPermission(
-                    Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS, null);
+            unregisterMoveCallback_enforcePermission();
             mMoveCallbacks.unregister(callback);
         }
 
@@ -6278,6 +6237,17 @@
         }
 
         @Override
+        public void registerPackageMonitorCallback(@NonNull IRemoteCallback callback, int userId) {
+            int uid = Binder.getCallingUid();
+            mPackageMonitorCallbackHelper.registerPackageMonitorCallback(callback, userId, uid);
+        }
+
+        @Override
+        public void unregisterPackageMonitorCallback(@NonNull IRemoteCallback callback) {
+            mPackageMonitorCallbackHelper.unregisterPackageMonitorCallback(callback);
+        }
+
+        @Override
         public void requestPackageChecksums(@NonNull String packageName, boolean includeSplits,
                 @Checksum.TypeMask int optional, @Checksum.TypeMask int required,
                 @Nullable List trustedInstallers,
@@ -6691,7 +6661,7 @@
         @Override
         public String removeLegacyDefaultBrowserPackageName(int userId) {
             synchronized (mLock) {
-                return mSettings.removeDefaultBrowserPackageNameLPw(userId);
+                return mSettings.removePendingDefaultBrowserLPw(userId);
             }
         }
 
@@ -6811,6 +6781,30 @@
         }
 
         @Override
+        public LegacyPermissionSettings getLegacyPermissions() {
+            synchronized (mLock) {
+                return mSettings.mPermissions;
+            }
+        }
+
+        /**
+         * Read legacy permission states for permissions migration to new permission subsystem.
+         */
+        @Override
+        public RuntimePermissionsState getLegacyPermissionsState(int userId) {
+            synchronized (mLock) {
+                return mSettings.getLegacyPermissionsState(userId);
+            }
+        }
+
+        @Override
+        public int getLegacyPermissionsVersion(@UserIdInt int userId) {
+            synchronized (mLock) {
+                return mSettings.getDefaultRuntimePermissionsVersion(userId);
+            }
+        }
+
+        @Override
         @SuppressWarnings("GuardedBy")
         public boolean isPermissionUpgradeNeeded(int userId) {
             return mSettings.isPermissionUpgradeNeeded(userId);
@@ -6905,6 +6899,16 @@
             mHandler.post(() -> PackageManagerService.this.notifyInstallObserver(packageName,
                     true /* killApp */));
         }
+
+        @Override
+        public int[] getDistractingPackageRestrictionsAsUser(
+                @NonNull String[] packageNames, int userId) {
+            final int callingUid = Binder.getCallingUid();
+            final Computer snapshot = snapshotComputer();
+            Objects.requireNonNull(packageNames, "packageNames cannot be null");
+            return mDistractingPackageHelper.getDistractingPackageRestrictionsAsUser(snapshot,
+                    packageNames, userId, callingUid);
+        }
     }
 
     private void setEnabledOverlayPackages(@UserIdInt int userId,
@@ -7528,8 +7532,13 @@
                 callback);
     }
 
-    void setDefaultBrowser(@Nullable String packageName, boolean async, @UserIdInt int userId) {
-        mDefaultAppProvider.setDefaultBrowser(packageName, async, userId);
+    @Nullable
+    String getDefaultBrowser(@UserIdInt int userId) {
+        return mDefaultAppProvider.getDefaultBrowser(userId);
+    }
+
+    void setDefaultBrowser(@Nullable String packageName, @UserIdInt int userId) {
+        mDefaultAppProvider.setDefaultBrowser(packageName, userId);
     }
 
     PackageUsage getPackageUsage() {
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
index 0ed90e4..ca57209 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
@@ -100,7 +100,6 @@
     public @Nullable String wearableSensingPackage;
     public ComponentName resolveComponentName;
     public ArrayMap<String, AndroidPackage> packages;
-    public boolean enableFreeCacheV2;
     public int sdkVersion;
     public File appInstallDir;
     public File appLib32InstallDir;
@@ -123,4 +122,6 @@
     public StorageEventHelper storageEventHelper;
     public Set<String> initialNonStoppedSystemPackages = new ArraySet<>();
     public boolean shouldStopSystemPackagesByDefault;
+    public FreeStorageHelper freeStorageHelper;
+    public PackageMonitorCallbackHelper packageMonitorCallbackHelper;
 }
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index e1f010f..8d64bd9 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -23,6 +23,9 @@
 import static android.content.pm.PackageManager.FLAG_PERMISSION_REVOKE_WHEN_REQUESTED;
 import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_FIXED;
 import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET;
+import static android.content.pm.PackageManager.RESTRICTION_HIDE_FROM_SUGGESTIONS;
+import static android.content.pm.PackageManager.RESTRICTION_HIDE_NOTIFICATIONS;
+import static android.content.pm.PackageManager.RESTRICTION_NONE;
 
 import static com.android.server.LocalManagerRegistry.ManagerNotFoundException;
 import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
@@ -216,6 +219,9 @@
         final PrintWriter pw = getOutPrintWriter();
         try {
             switch (cmd) {
+                case "help":
+                    onHelp();
+                    return 0;
                 case "path":
                     return runPath();
                 case "dump":
@@ -287,6 +293,8 @@
                     return runSuspend(false);
                 case "set-distracting-restriction":
                     return runSetDistractingRestriction();
+                case "get-distracting-restriction":
+                    return runGetDistractingRestriction();
                 case "grant":
                     return runGrantRevokePermission(true);
                 case "revoke":
@@ -311,6 +319,8 @@
                     return runCreateUser();
                 case "remove-user":
                     return runRemoveUser();
+                case "mark-guest-for-deletion":
+                    return runMarkGuestForDeletion();
                 case "rename-user":
                     return runRenameUser();
                 case "set-user-restriction":
@@ -354,6 +364,8 @@
                     return runSetSilentUpdatesPolicy();
                 case "get-app-metadata":
                     return runGetAppMetadata();
+                case "clear-package-preferred-activities":
+                    return runClearPackagePreferredActivities();
                 case "wait-for-handler":
                     return runWaitForHandler(/* forBackgroundHandler= */ false);
                 case "wait-for-background-handler":
@@ -907,27 +919,42 @@
 
     private int runListLibraries() throws RemoteException {
         final PrintWriter pw = getOutPrintWriter();
-        final List<String> list = new ArrayList<String>();
-        final String[] rawList = mInterface.getSystemSharedLibraryNames();
-        for (int i = 0; i < rawList.length; i++) {
-            list.add(rawList[i]);
+        boolean verbose = false;
+        String opt;
+        while ((opt = getNextArg()) != null) {
+            switch (opt) {
+                case "-v":
+                    verbose = true;
+                    break;
+                default:
+                    pw.println("Error: Unknown option: " + opt);
+                    return -1;
+            }
+        }
+
+        final Map<String, String> namesAndPaths = mInterface.getSystemSharedLibraryNamesAndPaths();
+        if (namesAndPaths.isEmpty()) {
+            return 0;
         }
 
         // sort by name
-        Collections.sort(list, new Comparator<String>() {
-            public int compare(String o1, String o2) {
+        final List<String> libs = new ArrayList<>(namesAndPaths.keySet());
+        Collections.sort(libs, (o1, o2) -> {
                 if (o1 == o2) return 0;
                 if (o1 == null) return -1;
                 if (o2 == null) return 1;
                 return o1.compareTo(o2);
-            }
         });
 
-        final int count = (list != null) ? list.size() : 0;
-        for (int p = 0; p < count; p++) {
-            String lib = list.get(p);
+        for (int i = 0; i < libs.size(); i++) {
+            String lib = libs.get(i);
             pw.print("library:");
-            pw.println(lib);
+            pw.print(lib);
+            if (verbose) {
+                pw.print(" path:");
+                pw.print(namesAndPaths.get(lib));
+            }
+            pw.println();
         }
         return 0;
     }
@@ -2565,6 +2592,58 @@
         }
     }
 
+    private int runGetDistractingRestriction() {
+        final PrintWriter pw = getOutPrintWriter();
+        int userId = UserHandle.USER_SYSTEM;
+        String opt;
+        while ((opt = getNextOption()) != null) {
+            switch (opt) {
+                case "--user":
+                    userId = UserHandle.parseUserArg(getNextArgRequired());
+                    break;
+                default:
+                    pw.println("Error: Unknown option: " + opt);
+                    return 1;
+            }
+        }
+
+        final List<String> packageNames = getRemainingArgs();
+        if (packageNames.isEmpty()) {
+            pw.println("Error: package name not specified");
+            return 1;
+        }
+        pw.println("Distracting restrictions state for user " + userId);
+
+        final int translatedUserId = translateUserId(userId, UserHandle.USER_NULL,
+                "get-distracting");
+        final String[] packages = packageNames.toArray(new String[]{});
+        int[] res = mPm.getDistractingPackageRestrictionsAsUser(packages, translatedUserId);
+
+        for (int i = 0; i < res.length; i++) {
+            final int state = res[i];
+            if (state == -1) {
+                pw.println(packages[i] + " not found ...");
+            } else {
+                pw.println(packages[i] + "  state: " + stateToString(state));
+            }
+        }
+
+        return 0;
+    }
+
+    private static String stateToString(@PackageManager.DistractionRestriction int flag) {
+        switch (flag) {
+            case RESTRICTION_NONE:
+                return "NONE";
+            case RESTRICTION_HIDE_FROM_SUGGESTIONS:
+                return "HIDE_FROM_SUGGESTIONS";
+            case RESTRICTION_HIDE_NOTIFICATIONS:
+                return "HIDE_NOTIFICATIONS";
+            default:
+                return "UNKNOWN";
+        }
+    }
+
     private int runSuspend(boolean suspendedState) {
         final PrintWriter pw = getOutPrintWriter();
         int userId = UserHandle.USER_SYSTEM;
@@ -3126,6 +3205,24 @@
         }
     }
 
+    private int runMarkGuestForDeletion() throws RemoteException {
+        String arg = getNextArg();
+        if (arg == null) {
+            getErrPrintWriter().println("Error: no user id specified.");
+            return 1;
+        }
+        int userId = resolveUserId(UserHandle.parseUserArg(arg));
+
+        IUserManager um = IUserManager.Stub.asInterface(
+                ServiceManager.getService(Context.USER_SERVICE));
+        if (!um.markGuestForDeletion(userId)) {
+            getErrPrintWriter().println("Error: could not mark guest for deletion");
+            return 1;
+        }
+
+        return 0;
+    }
+
     private int runRenameUser() throws RemoteException {
         String arg = getNextArg();
         if (arg == null) {
@@ -4138,6 +4235,22 @@
         return userId == UserHandle.USER_CURRENT ? ActivityManager.getCurrentUser() : userId;
     }
 
+    private int runClearPackagePreferredActivities() {
+        final PrintWriter pw = getErrPrintWriter();
+        final String packageName = getNextArg();
+        if (packageName == null) {
+            pw.println("Error: package name not specified");
+            return 1;
+        }
+        try {
+            mContext.getPackageManager().clearPackagePreferredActivities(packageName);
+            return 0;
+        } catch (Exception e) {
+            pw.println(e.toString());
+            return 1;
+        }
+    }
+
     @Override
     public void onHelp() {
         final PrintWriter pw = getOutPrintWriter();
@@ -4163,8 +4276,10 @@
         pw.println("    Options:");
         pw.println("      -f: dump the name of the .apk file containing the test package");
         pw.println("");
-        pw.println("  list libraries");
+        pw.println("  list libraries [-v]");
         pw.println("    Prints all system libraries.");
+        pw.println("    Options:");
+        pw.println("      -v: shows the location of the library in the device's filesystem");
         pw.println("");
         pw.println("  list packages [-f] [-d] [-e] [-s] [-3] [-i] [-l] [-u] [-U] ");
         pw.println("      [--show-versioncode] [--apex-only] [--factory-only]");
@@ -4231,7 +4346,7 @@
         pw.println("       [--preload] [--instant] [--full] [--dont-kill]");
         pw.println("       [--enable-rollback]");
         pw.println("       [--force-uuid internal|UUID] [--pkg PACKAGE] [-S BYTES]");
-        pw.println("       [--apex] [--staged-ready-timeout TIMEOUT]");
+        pw.println("       [--apex] [--force-non-staged] [--staged-ready-timeout TIMEOUT]");
         pw.println("       [PATH [SPLIT...]|-]");
         pw.println("    Install an application.  Must provide the apk data to install, either as");
         pw.println("    file path(s) or '-' to read from stdin.  Options are:");
@@ -4260,6 +4375,8 @@
         pw.println("      --update-ownership: request the update ownership enforcement");
         pw.println("      --force-uuid: force install on to disk volume with given UUID");
         pw.println("      --apex: install an .apex file, not an .apk");
+        pw.println("      --force-non-staged: force the installation to run under a non-staged");
+        pw.println("          session, which may complete without requiring a reboot");
         pw.println("      --staged-ready-timeout: By default, staged sessions wait "
                 + DEFAULT_STAGED_READY_TIMEOUT_MS);
         pw.println("          milliseconds for pre-reboot verification to complete when");
@@ -4358,6 +4475,9 @@
         pw.println("    Any existing flags are overwritten, which also means that if no flags are");
         pw.println("    specified then all existing flags will be cleared.");
         pw.println("");
+        pw.println("  get-distracting-restriction [--user USER_ID] PACKAGE [PACKAGE...]");
+        pw.println("    Gets the specified restriction flags of given package(s) (of the user).");
+        pw.println("");
         pw.println("  grant [--user USER_ID] PACKAGE PERMISSION");
         pw.println("  revoke [--user USER_ID] PACKAGE PERMISSION");
         pw.println("    These commands either grant or revoke permissions to apps.  The permissions");
@@ -4411,6 +4531,11 @@
         pw.println("        switch or reboot)");
         pw.println("      --wait: Wait until user is removed. Ignored if set-ephemeral-if-in-use");
         pw.println("");
+        pw.println("  mark-guest-for-deletion USER_ID");
+        pw.println("    Mark the guest user for deletion. After this, it is possible to create a");
+        pw.println("    new guest user and switch to it. This allows resetting the guest user");
+        pw.println("    without switching to another user.");
+        pw.println("");
         pw.println("  rename-user USER_ID [USER_NAME]");
         pw.println("    Rename USER_ID with USER_NAME (or null when [USER_NAME] is not set)");
         pw.println("");
@@ -4465,6 +4590,8 @@
         pw.println("      --reset: restore the installer and throttle time to the default, and");
         pw.println("        clear tracks of silent updates in the system.");
         pw.println("");
+        pw.println("  clear-package-preferred-activities <PACKAGE>");
+        pw.println("    Remove the preferred activity mappings for the given package.");
         pw.println("  wait-for-handler --timeout <MILLIS>");
         pw.println("    Wait for a given amount of time till the package manager handler finishes");
         pw.println("    handling all pending messages.");
diff --git a/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java b/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java
new file mode 100644
index 0000000..a771502
--- /dev/null
+++ b/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.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 com.android.server.pm;
+
+import static com.android.server.pm.PackageManagerService.PACKAGE_SCHEME;
+
+import android.annotation.AppIdInt;
+import android.annotation.NonNull;
+import android.app.ActivityManager;
+import android.app.IActivityManager;
+import android.content.Intent;
+import android.content.pm.PackageInstaller;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IRemoteCallback;
+import android.os.Process;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.ArrayUtils;
+
+import java.util.ArrayList;
+
+/** Helper class to handle PackageMonitorCallback and notify the registered client. This is mainly
+ * used by PackageMonitor to improve the broadcast latency. */
+class PackageMonitorCallbackHelper {
+
+    private static final boolean DEBUG = false;
+
+    @NonNull
+    private final Object mLock = new Object();
+    final IActivityManager mActivityManager = ActivityManager.getService();
+
+    final Handler mHandler;
+
+    PackageMonitorCallbackHelper(PackageManagerServiceInjector injector) {
+        mHandler = injector.getHandler();
+    }
+
+    @NonNull
+    @GuardedBy("mLock")
+    private final RemoteCallbackList<IRemoteCallback> mCallbacks = new RemoteCallbackList<>();
+
+    public void registerPackageMonitorCallback(IRemoteCallback callback, int userId, int uid) {
+        synchronized (mLock) {
+            mCallbacks.register(callback, new RegisterUser(userId, uid));
+        }
+    }
+
+    public void unregisterPackageMonitorCallback(IRemoteCallback callback) {
+        synchronized (mLock) {
+            mCallbacks.unregister(callback);
+        }
+    }
+
+    public void onUserRemoved(int userId) {
+        ArrayList<IRemoteCallback> targetUnRegisteredCallbacks = null;
+        synchronized (mLock) {
+            int registerCount = mCallbacks.getRegisteredCallbackCount();
+            for (int i = 0; i < registerCount; i++) {
+                RegisterUser registerUser =
+                        (RegisterUser) mCallbacks.getRegisteredCallbackCookie(i);
+                if (registerUser.getUserId() == userId) {
+                    IRemoteCallback callback = mCallbacks.getRegisteredCallbackItem(i);
+                    if (targetUnRegisteredCallbacks == null) {
+                        targetUnRegisteredCallbacks = new ArrayList<>();
+                    }
+                    targetUnRegisteredCallbacks.add(callback);
+                }
+            }
+        }
+        if (targetUnRegisteredCallbacks != null && targetUnRegisteredCallbacks.size() > 0) {
+            int count = targetUnRegisteredCallbacks.size();
+            for (int i = 0; i < count; i++) {
+                unregisterPackageMonitorCallback(targetUnRegisteredCallbacks.get(i));
+            }
+        }
+    }
+
+    public void notifyPackageAddedForNewUsers(String packageName,
+            @AppIdInt int appId, @NonNull int[] userIds, @NonNull int[] instantUserIds,
+            int dataLoaderType, SparseArray<int[]> broadcastAllowList) {
+        Bundle extras = new Bundle(2);
+        // Set to UID of the first user, EXTRA_UID is automatically updated in sendPackageBroadcast
+        final int uid = UserHandle.getUid(
+                (ArrayUtils.isEmpty(userIds) ? instantUserIds[0] : userIds[0]), appId);
+        extras.putInt(Intent.EXTRA_UID, uid);
+        extras.putInt(PackageInstaller.EXTRA_DATA_LOADER_TYPE, dataLoaderType);
+        notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED, packageName, extras ,
+                userIds /* userIds */, instantUserIds, broadcastAllowList);
+    }
+
+    public void notifyResourcesChanged(boolean mediaStatus, boolean replacing,
+            @NonNull String[] pkgNames, @NonNull int[] uids) {
+        Bundle extras = new Bundle();
+        extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, pkgNames);
+        extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, uids);
+        if (replacing) {
+            extras.putBoolean(Intent.EXTRA_REPLACING, replacing);
+        }
+        String action = mediaStatus ? Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE
+                : Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE;
+        notifyPackageMonitor(action, null /* pkg */, extras, null /* userIds */,
+                null /* instantUserIds */, null /* broadcastAllowList */);
+    }
+
+    public void notifyPackageChanged(String packageName, boolean dontKillApp,
+            ArrayList<String> componentNames, int packageUid, String reason, int[] userIds,
+            int[] instantUserIds, SparseArray<int[]> broadcastAllowList) {
+        Bundle extras = new Bundle(4);
+        extras.putString(Intent.EXTRA_CHANGED_COMPONENT_NAME, componentNames.get(0));
+        String[] nameList = new String[componentNames.size()];
+        componentNames.toArray(nameList);
+        extras.putStringArray(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST, nameList);
+        extras.putBoolean(Intent.EXTRA_DONT_KILL_APP, dontKillApp);
+        extras.putInt(Intent.EXTRA_UID, packageUid);
+        if (reason != null) {
+            extras.putString(Intent.EXTRA_REASON, reason);
+        }
+        notifyPackageMonitor(Intent.ACTION_PACKAGE_CHANGED, packageName, extras, userIds,
+                instantUserIds, broadcastAllowList);
+    }
+
+    public void notifyPackageMonitor(String action, String pkg, Bundle extras,
+            int[] userIds, int[] instantUserIds, SparseArray<int[]> broadcastAllowList) {
+        if (!isAllowedCallbackAction(action)) {
+            return;
+        }
+        try {
+            final int[] resolvedUserIds;
+            if (userIds == null) {
+                if (mActivityManager == null) return;
+                resolvedUserIds = mActivityManager.getRunningUserIds();
+            } else {
+                resolvedUserIds = userIds;
+            }
+
+            if (ArrayUtils.isEmpty(instantUserIds)) {
+                doNotifyCallbacks(action, pkg, extras, resolvedUserIds, broadcastAllowList);
+            } else {
+                doNotifyCallbacks(action, pkg, extras, instantUserIds, broadcastAllowList);
+            }
+        } catch (RemoteException e) {
+            // do nothing
+        }
+    }
+
+    private static boolean isAllowedCallbackAction(String action) {
+        return TextUtils.equals(action, Intent.ACTION_PACKAGE_ADDED)
+                || TextUtils.equals(action, Intent.ACTION_PACKAGE_REMOVED)
+                || TextUtils.equals(action, Intent.ACTION_PACKAGE_CHANGED)
+                || TextUtils.equals(action, Intent.ACTION_UID_REMOVED)
+                || TextUtils.equals(action, Intent.ACTION_PACKAGES_SUSPENDED)
+                || TextUtils.equals(action, Intent.ACTION_PACKAGES_UNSUSPENDED)
+                || TextUtils.equals(action, Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE)
+                || TextUtils.equals(action, Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
+    }
+
+    private void doNotifyCallbacks(String action, String pkg, Bundle extras, int[] userIds,
+            SparseArray<int[]> broadcastAllowList) {
+        RemoteCallbackList<IRemoteCallback> callbacks;
+        synchronized (mLock) {
+            callbacks = mCallbacks;
+        }
+        for (int userId : userIds) {
+            final Intent intent = new Intent(action,
+                    pkg != null ? Uri.fromParts(PACKAGE_SCHEME, pkg, null) : null);
+            if (extras != null) {
+                intent.putExtras(extras);
+            }
+            int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
+            if (uid >= 0 && UserHandle.getUserId(uid) != userId) {
+                uid = UserHandle.getUid(userId, UserHandle.getAppId(uid));
+                intent.putExtra(Intent.EXTRA_UID, uid);
+            }
+            intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
+
+            final int[] allowUids =
+                    broadcastAllowList != null ? broadcastAllowList.get(userId) : new int[]{};
+
+            mHandler.post(() -> callbacks.broadcast((callback, user) -> {
+                RegisterUser registerUser = (RegisterUser) user;
+                if ((registerUser.getUserId() != UserHandle.USER_ALL) && (registerUser.getUserId()
+                        != userId)) {
+                    return;
+                }
+                int registerUid = registerUser.getUid();
+                if (broadcastAllowList != null && registerUid != Process.SYSTEM_UID
+                        && !ArrayUtils.contains(allowUids, registerUid)) {
+                    if (DEBUG) {
+                        Slog.w("PackageMonitorCallbackHelper",
+                                "Skip invoke PackageMonitorCallback for " + action + ", uid "
+                                        + registerUid);
+                    }
+                    return;
+                }
+                invokeCallback(callback, intent);
+            }));
+        }
+    }
+
+    private void invokeCallback(IRemoteCallback callback, Intent intent) {
+        try {
+            Bundle bundle = new Bundle();
+            bundle.putParcelable(
+                    PackageManager.EXTRA_PACKAGE_MONITOR_CALLBACK_RESULT, intent);
+            callback.sendResult(bundle);
+        } catch (RemoteException e) {
+            // do nothing
+        }
+    }
+
+    private final class RegisterUser {
+        int mUserId;
+        int mUid;
+
+        RegisterUser(int userId, int uid) {
+            mUid = uid;
+            mUserId = userId;
+        }
+
+        public int getUid() {
+            return mUid;
+        }
+
+        public int getUserId() {
+            return mUserId;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index 7fda092..494709c 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -48,6 +48,7 @@
 import com.android.server.pm.permission.LegacyPermissionDataProvider;
 import com.android.server.pm.permission.LegacyPermissionState;
 import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.ArchiveState;
 import com.android.server.pm.pkg.PackageState;
 import com.android.server.pm.pkg.PackageStateInternal;
 import com.android.server.pm.pkg.PackageStateUnserialized;
@@ -171,7 +172,7 @@
     @NonNull
     private InstallSource installSource;
 
-    /** @see PackageState#getVolumeUuid()  */
+    /** @see PackageState#getVolumeUuid() */
     @Nullable
     private String volumeUuid;
 
@@ -337,7 +338,7 @@
             final long previousFirstInstallTime =
                     replacedPkgSetting.getUserStateOrDefault(userId).getFirstInstallTimeMillis();
             if (previousFirstInstallTime != 0) {
-                modifyUserState(userId).setFirstInstallTime(previousFirstInstallTime);
+                modifyUserState(userId).setFirstInstallTimeMillis(previousFirstInstallTime);
             }
         }
         onChanged();
@@ -352,10 +353,10 @@
         if (userId == UserHandle.USER_ALL) {
             int userStateCount = mUserStates.size();
             for (int i = 0; i < userStateCount; i++) {
-                mUserStates.valueAt(i).setFirstInstallTime(firstInstallTime);
+                mUserStates.valueAt(i).setFirstInstallTimeMillis(firstInstallTime);
             }
         } else {
-            modifyUserState(userId).setFirstInstallTime(firstInstallTime);
+            modifyUserState(userId).setFirstInstallTimeMillis(firstInstallTime);
         }
         onChanged();
         return this;
@@ -448,6 +449,7 @@
     /**
      * Notify {@link #onChanged()}  if the parameter {@code usesLibraryFiles} is different from
      * {@link #getUsesLibraryFiles()}.
+     *
      * @param usesLibraryFiles the new uses library files
      * @return {@code this}
      */
@@ -875,7 +877,7 @@
             ArraySet<String> enabledComponents, ArraySet<String> disabledComponents,
             int installReason, int uninstallReason,
             String harmfulAppWarning, String splashScreenTheme,
-            long firstInstallTime) {
+            long firstInstallTime, int aspectRatio, ArchiveState archiveState) {
         modifyUserState(userId)
                 .setSuspendParams(suspendParams)
                 .setCeDataInode(ceDataInode)
@@ -894,7 +896,9 @@
                 .setVirtualPreload(virtualPreload)
                 .setHarmfulAppWarning(harmfulAppWarning)
                 .setSplashScreenTheme(splashScreenTheme)
-                .setFirstInstallTime(firstInstallTime);
+                .setFirstInstallTimeMillis(firstInstallTime)
+                .setMinAspectRatio(aspectRatio)
+                .setArchiveState(archiveState);
         onChanged();
     }
 
@@ -912,7 +916,8 @@
                         ? null : otherState.getDisabledComponentsNoCopy().untrackedStorage(),
                 otherState.getInstallReason(), otherState.getUninstallReason(),
                 otherState.getHarmfulAppWarning(), otherState.getSplashScreenTheme(),
-                otherState.getFirstInstallTimeMillis());
+                otherState.getFirstInstallTimeMillis(), otherState.getMinAspectRatio(),
+                otherState.getArchiveState());
     }
 
     WatchedArraySet<String> getEnabledComponents(int userId) {
@@ -1107,10 +1112,36 @@
                     state.getLastDisableAppCaller());
             proto.write(PackageProto.UserInfoProto.FIRST_INSTALL_TIME_MS,
                     state.getFirstInstallTimeMillis());
+            writeArchiveState(proto, state.getArchiveState());
             proto.end(userToken);
         }
     }
 
+    private static void writeArchiveState(ProtoOutputStream proto, ArchiveState archiveState) {
+        long archiveStateToken = proto.start(PackageProto.UserInfoProto.ARCHIVE_STATE);
+        for (ArchiveState.ArchiveActivityInfo activityInfo : archiveState.getActivityInfos()) {
+            long activityInfoToken = proto.start(
+                    PackageProto.UserInfoProto.ArchiveState.ACTIVITY_INFOS);
+            proto.write(PackageProto.UserInfoProto.ArchiveState.ArchiveActivityInfo.TITLE,
+                    activityInfo.getTitle());
+            proto.write(
+                    PackageProto.UserInfoProto.ArchiveState.ArchiveActivityInfo.ICON_BITMAP_PATH,
+                    activityInfo.getIconBitmap().toAbsolutePath().toString());
+            proto.write(
+                    PackageProto
+                            .UserInfoProto
+                            .ArchiveState
+                            .ArchiveActivityInfo
+                            .MONOCHROME_ICON_BITMAP_PATH,
+                    activityInfo.getMonochromeIconBitmap().toAbsolutePath().toString());
+            proto.end(activityInfoToken);
+        }
+
+        proto.write(PackageProto.UserInfoProto.ArchiveState.INSTALLER_TITLE,
+                archiveState.getInstallerTitle());
+        proto.end(archiveStateToken);
+    }
+
     /**
      * @see #mPath
      */
@@ -1164,8 +1195,11 @@
     }
 
     public PackageSetting setLoadingProgress(float progress) {
-        mLoadingProgress = progress;
-        onChanged();
+        // To prevent race conditions, we don't allow progress to ever go down
+        if (mLoadingProgress < progress) {
+            mLoadingProgress = progress;
+            onChanged();
+        }
         return this;
     }
 
@@ -1598,10 +1632,10 @@
     }
 
     @DataClass.Generated(
-            time = 1680917079522L,
+            time = 1688743336932L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/services/core/java/com/android/server/pm/PackageSetting.java",
-            inputSignatures = "private  int mSharedUserAppId\nprivate @android.annotation.Nullable java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mimeGroups\nprivate @java.lang.Deprecated @android.annotation.Nullable java.util.Set<java.lang.String> mOldCodePaths\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  boolean installPermissionsFixed\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  boolean updateAvailable\nprivate  boolean forceQueryableOverride\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 final @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> mSnapshot\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  void setSharedUserAppId(int)\npublic @java.lang.Override int getSharedUserAppId()\npublic @java.lang.Override boolean hasSharedUser()\npublic @java.lang.Override java.lang.String toString()\nprotected  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  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 isAnyInstalled(int[])\n  int[] queryInstalledUsers(int[],boolean)\n  long getCeDataInode(int)\n  void setCeDataInode(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,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)\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)\n  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 setOldCodePaths(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()\nclass PackageSetting extends com.android.server.pm.SettingBase implements [com.android.server.pm.pkg.PackageStateInternal]\n@com.android.internal.util.DataClass(genGetters=true, genConstructor=false, genSetters=false, genBuilder=false)")
+            inputSignatures = "private  int mSharedUserAppId\nprivate @android.annotation.Nullable java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mimeGroups\nprivate @java.lang.Deprecated @android.annotation.Nullable java.util.Set<java.lang.String> mOldCodePaths\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  boolean installPermissionsFixed\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  boolean updateAvailable\nprivate  boolean forceQueryableOverride\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 final @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> mSnapshot\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  void setSharedUserAppId(int)\npublic @java.lang.Override int getSharedUserAppId()\npublic @java.lang.Override boolean hasSharedUser()\npublic @java.lang.Override java.lang.String toString()\nprotected  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  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 isAnyInstalled(int[])\n  int[] queryInstalledUsers(int[],boolean)\n  long getCeDataInode(int)\n  void setCeDataInode(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,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,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)\n  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 setOldCodePaths(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()\nclass PackageSetting extends com.android.server.pm.SettingBase implements [com.android.server.pm.pkg.PackageStateInternal]\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/PreferredActivityHelper.java b/services/core/java/com/android/server/pm/PreferredActivityHelper.java
index 214a8b8..571aab4 100644
--- a/services/core/java/com/android/server/pm/PreferredActivityHelper.java
+++ b/services/core/java/com/android/server/pm/PreferredActivityHelper.java
@@ -557,9 +557,8 @@
             serializer.startDocument(null, true);
             serializer.startTag(null, TAG_DEFAULT_APPS);
 
-            synchronized (mPm.mLock) {
-                mPm.mSettings.writeDefaultAppsLPr(serializer, userId);
-            }
+            final String defaultBrowser = mPm.getDefaultBrowser(userId);
+            Settings.writeDefaultApps(serializer, defaultBrowser);
 
             serializer.endTag(null, TAG_DEFAULT_APPS);
             serializer.endDocument();
@@ -584,14 +583,19 @@
             parser.setInput(new ByteArrayInputStream(backup), StandardCharsets.UTF_8.name());
             restoreFromXml(parser, userId, TAG_DEFAULT_APPS,
                     (parser1, userId1) -> {
-                        final String defaultBrowser;
-                        synchronized (mPm.mLock) {
-                            mPm.mSettings.readDefaultAppsLPw(parser1, userId1);
-                            defaultBrowser = mPm.mSettings.removeDefaultBrowserPackageNameLPw(
-                                    userId1);
-                        }
+                        final String defaultBrowser = Settings.readDefaultApps(parser1);
                         if (defaultBrowser != null) {
-                            mPm.setDefaultBrowser(defaultBrowser, false, userId1);
+                            final PackageStateInternal packageState = mPm.snapshotComputer()
+                                    .getPackageStateInternal(defaultBrowser);
+                            if (packageState != null
+                                    && packageState.getUserStateOrDefault(userId1).isInstalled()) {
+                                mPm.setDefaultBrowser(defaultBrowser, userId1);
+                            } else {
+                                synchronized (mPm.mLock) {
+                                    mPm.mSettings.setPendingDefaultBrowserLPw(defaultBrowser,
+                                            userId1);
+                                }
+                            }
                         }
                     });
         } catch (Exception e) {
@@ -661,6 +665,7 @@
             final Iterator<PreferredActivity> it = pir.filterIterator();
             while (it.hasNext()) {
                 final PreferredActivity pa = it.next();
+                if (pa == null) continue;
                 final String prefPackageName = pa.mPref.mComponent.getPackageName();
                 if (packageName == null
                         || (prefPackageName.equals(packageName) && pa.mPref.mAlways)) {
diff --git a/services/core/java/com/android/server/pm/ResilientAtomicFile.java b/services/core/java/com/android/server/pm/ResilientAtomicFile.java
index 54ca426..3aefc5a 100644
--- a/services/core/java/com/android/server/pm/ResilientAtomicFile.java
+++ b/services/core/java/com/android/server/pm/ResilientAtomicFile.java
@@ -107,10 +107,15 @@
 
         // In case of MT access, it's possible the files get overwritten during write.
         // Let's open all FDs we need now.
-        mMainOutStream = new FileOutputStream(mFile);
-        mMainInStream = new FileInputStream(mFile);
-        mReserveOutStream = new FileOutputStream(mReserveCopy);
-        mReserveInStream = new FileInputStream(mReserveCopy);
+        try {
+            mMainOutStream = new FileOutputStream(mFile);
+            mMainInStream = new FileInputStream(mFile);
+            mReserveOutStream = new FileOutputStream(mReserveCopy);
+            mReserveInStream = new FileInputStream(mReserveCopy);
+        } catch (IOException e) {
+            close();
+            throw e;
+        }
 
         return mMainOutStream;
     }
diff --git a/services/core/java/com/android/server/pm/SELinuxMMAC.java b/services/core/java/com/android/server/pm/SELinuxMMAC.java
index 2596006..a8cdef4 100644
--- a/services/core/java/com/android/server/pm/SELinuxMMAC.java
+++ b/services/core/java/com/android/server/pm/SELinuxMMAC.java
@@ -83,6 +83,8 @@
     // Append targetSdkVersion=n to existing seinfo label where n is the app's targetSdkVersion
     private static final String TARGETSDKVERSION_STR = ":targetSdkVersion=";
 
+    private static final String PARTITION_STR = ":partition=";
+
     /**
      * Allows opt-in to the latest targetSdkVersion enforced changes without changing target SDK.
      * Turning this change on for an app targeting the latest SDK or higher is a no-op.
@@ -373,15 +375,33 @@
         return pkg.getTargetSdkVersion();
     }
 
+    private static String getPartition(PackageState state) {
+        if (state.isSystemExt()) {
+            return "system_ext";
+        } else if (state.isProduct()) {
+            return "product";
+        } else if (state.isVendor()) {
+            return "vendor";
+        } else if (state.isOem()) {
+            return "oem";
+        } else if (state.isOdm()) {
+            return "odm";
+        } else if (state.isSystem()) {
+            return "system";
+        }
+        return "";
+    }
+
     /**
      * Selects a security label to a package based on input parameters and the seinfo tag taken
      * from a matched policy. All signature based policy stanzas are consulted and, if no match
      * is found, the default seinfo label of 'default' is used. The security label is attached to
      * the ApplicationInfo instance of the package.
      *
-     * @param pkg               object representing the package to be labeled.
-     * @param sharedUser if the app shares a sharedUserId, then this has the shared setting.
-     * @param compatibility     the PlatformCompat service to ask about state of compat changes.
+     * @param packageState  {@link PackageState} object representing the package to be labeled.
+     * @param pkg           {@link AndroidPackage} object representing the package to be labeled.
+     * @param sharedUser    if the app shares a sharedUserId, then this has the shared setting.
+     * @param compatibility the PlatformCompat service to ask about state of compat changes.
      * @return String representing the resulting seinfo.
      */
     public static String getSeInfo(@NonNull PackageState packageState, @NonNull AndroidPackage pkg,
@@ -393,7 +413,7 @@
         final boolean isPrivileged =
                 (sharedUser != null) ? sharedUser.isPrivileged() | packageState.isPrivileged()
                         : packageState.isPrivileged();
-        return getSeInfo(pkg, isPrivileged, targetSdkVersion);
+        return getSeInfo(packageState, pkg, isPrivileged, targetSdkVersion);
     }
 
     /**
@@ -402,15 +422,16 @@
      * is found, the default seinfo label of 'default' is used. The security label is attached to
      * the ApplicationInfo instance of the package.
      *
-     * @param pkg object representing the package to be labeled.
-     * @param isPrivileged boolean.
+     * @param packageState     {@link PackageState} object representing the package to be labeled.
+     * @param pkg              {@link AndroidPackage} object representing the package to be labeled.
+     * @param isPrivileged     boolean.
      * @param targetSdkVersion int. If this pkg runs as a sharedUser, targetSdkVersion is the
      *        greater of: lowest targetSdk for all pkgs in the sharedUser, or
      *        MINIMUM_TARGETSDKVERSION.
      * @return String representing the resulting seinfo.
      */
-    public static String getSeInfo(AndroidPackage pkg, boolean isPrivileged,
-            int targetSdkVersion) {
+    public static String getSeInfo(PackageState packageState, AndroidPackage pkg,
+            boolean isPrivileged, int targetSdkVersion) {
         String seInfo = null;
         synchronized (sPolicies) {
             if (!sPolicyRead) {
@@ -437,8 +458,13 @@
 
         seInfo += TARGETSDKVERSION_STR + targetSdkVersion;
 
+        String partition = getPartition(packageState);
+        if (!partition.isEmpty()) {
+            seInfo += PARTITION_STR + partition;
+        }
+
         if (DEBUG_POLICY_INSTALL) {
-            Slog.i(TAG, "package (" + pkg.getPackageName() + ") labeled with "
+            Slog.i(TAG, "package (" + packageState.getPackageName() + ") labeled with "
                     + "seinfo=" + seInfo);
         }
         return seInfo;
diff --git a/services/core/java/com/android/server/pm/ScanPackageUtils.java b/services/core/java/com/android/server/pm/ScanPackageUtils.java
index c3106a8..1cd44e6 100644
--- a/services/core/java/com/android/server/pm/ScanPackageUtils.java
+++ b/services/core/java/com/android/server/pm/ScanPackageUtils.java
@@ -149,6 +149,8 @@
         String primaryCpuAbiFromSettings = null;
         String secondaryCpuAbiFromSettings = null;
         boolean needToDeriveAbi = (scanFlags & SCAN_FIRST_BOOT_OR_UPGRADE) != 0;
+        boolean isApex = (scanFlags & SCAN_AS_APEX) != 0;
+
         if (!needToDeriveAbi) {
             if (pkgSetting != null) {
                 // TODO(b/154610922): if it is not first boot or upgrade, we should directly use
@@ -240,6 +242,7 @@
                     usesStaticLibraries, parsedPackage.getUsesStaticLibrariesVersions(),
                     parsedPackage.getMimeGroups(), newDomainSetId);
         }
+
         if (createNewPackage && originalPkgSetting != null) {
             // This is the initial transition from the original package, so,
             // fix up the new package's name now. We must do this after looking
@@ -279,77 +282,91 @@
         final boolean isUpdatedSystemApp = pkgSetting.isUpdatedSystemApp();
 
         final File appLib32InstallDir = getAppLib32InstallDir();
-        if ((scanFlags & SCAN_NEW_INSTALL) == 0) {
-            if (needToDeriveAbi) {
-                Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "derivePackageAbi");
-                final Pair<PackageAbiHelper.Abis, PackageAbiHelper.NativeLibraryPaths> derivedAbi =
-                        packageAbiHelper.derivePackageAbi(parsedPackage, isSystemApp,
-                                isUpdatedSystemApp, cpuAbiOverride, appLib32InstallDir);
-                derivedAbi.first.applyTo(parsedPackage);
-                derivedAbi.second.applyTo(parsedPackage);
-                Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+        // The native libs of Apex is located in apex_payload.img, don't need to parse it from
+        // the original apex file
+        if (!isApex) {
+            if ((scanFlags & SCAN_NEW_INSTALL) == 0) {
+                if (needToDeriveAbi) {
+                    Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "derivePackageAbi");
+                    try {
+                        final Pair<PackageAbiHelper.Abis, PackageAbiHelper.NativeLibraryPaths>
+                                derivedAbi =
+                                packageAbiHelper.derivePackageAbi(
+                                        parsedPackage,
+                                        isSystemApp,
+                                        isUpdatedSystemApp,
+                                        cpuAbiOverride,
+                                        appLib32InstallDir);
+                        derivedAbi.first.applyTo(parsedPackage);
+                        derivedAbi.second.applyTo(parsedPackage);
+                    } finally {
+                        Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+                    }
 
-                // Some system apps still use directory structure for native libraries
-                // in which case we might end up not detecting abi solely based on apk
-                // structure. Try to detect abi based on directory structure.
+                    // Some system apps still use directory structure for native libraries
+                    // in which case we might end up not detecting abi solely based on apk
+                    // structure. Try to detect abi based on directory structure.
 
-                String pkgRawPrimaryCpuAbi = AndroidPackageUtils.getRawPrimaryCpuAbi(parsedPackage);
-                if (isSystemApp && !isUpdatedSystemApp && pkgRawPrimaryCpuAbi == null) {
-                    final PackageAbiHelper.Abis abis = packageAbiHelper.getBundledAppAbis(
+                    String pkgRawPrimaryCpuAbi = AndroidPackageUtils.getRawPrimaryCpuAbi(
                             parsedPackage);
-                    abis.applyTo(parsedPackage);
-                    abis.applyTo(pkgSetting);
+                    if (isSystemApp && !isUpdatedSystemApp && pkgRawPrimaryCpuAbi == null) {
+                        final PackageAbiHelper.Abis abis = packageAbiHelper.getBundledAppAbis(
+                                parsedPackage);
+                        abis.applyTo(parsedPackage);
+                        abis.applyTo(pkgSetting);
+                        final PackageAbiHelper.NativeLibraryPaths nativeLibraryPaths =
+                                packageAbiHelper.deriveNativeLibraryPaths(parsedPackage,
+                                        isSystemApp, isUpdatedSystemApp, appLib32InstallDir);
+                        nativeLibraryPaths.applyTo(parsedPackage);
+                    }
+                } else {
+                    // This is not a first boot or an upgrade, don't bother deriving the
+                    // ABI during the scan. Instead, trust the value that was stored in the
+                    // package setting.
+                    parsedPackage.setPrimaryCpuAbi(primaryCpuAbiFromSettings)
+                            .setSecondaryCpuAbi(secondaryCpuAbiFromSettings);
+
                     final PackageAbiHelper.NativeLibraryPaths nativeLibraryPaths =
                             packageAbiHelper.deriveNativeLibraryPaths(parsedPackage, isSystemApp,
                                     isUpdatedSystemApp, appLib32InstallDir);
                     nativeLibraryPaths.applyTo(parsedPackage);
+
+                    if (DEBUG_ABI_SELECTION) {
+                        Slog.i(TAG, "Using ABIS and native lib paths from settings : "
+                                + parsedPackage.getPackageName() + " "
+                                + AndroidPackageUtils.getRawPrimaryCpuAbi(parsedPackage)
+                                + ", "
+                                + AndroidPackageUtils.getRawSecondaryCpuAbi(parsedPackage));
+                    }
                 }
             } else {
-                // This is not a first boot or an upgrade, don't bother deriving the
-                // ABI during the scan. Instead, trust the value that was stored in the
-                // package setting.
-                parsedPackage.setPrimaryCpuAbi(primaryCpuAbiFromSettings)
-                        .setSecondaryCpuAbi(secondaryCpuAbiFromSettings);
+                if ((scanFlags & SCAN_MOVE) != 0) {
+                    // We haven't run dex-opt for this move (since we've moved the compiled output
+                    // too) but we already have this packages package info in the PackageSetting.
+                    // We just use that and derive the native library path based on the new code
+                    // path.
+                    parsedPackage.setPrimaryCpuAbi(pkgSetting.getPrimaryCpuAbiLegacy())
+                            .setSecondaryCpuAbi(pkgSetting.getSecondaryCpuAbiLegacy());
+                }
 
+                // Set native library paths again. For moves, the path will be updated based on the
+                // ABIs we've determined above. For non-moves, the path will be updated based on the
+                // ABIs we determined during compilation, but the path will depend on the final
+                // package path (after the rename away from the stage path).
                 final PackageAbiHelper.NativeLibraryPaths nativeLibraryPaths =
                         packageAbiHelper.deriveNativeLibraryPaths(parsedPackage, isSystemApp,
                                 isUpdatedSystemApp, appLib32InstallDir);
                 nativeLibraryPaths.applyTo(parsedPackage);
-
-                if (DEBUG_ABI_SELECTION) {
-                    Slog.i(TAG, "Using ABIS and native lib paths from settings : "
-                            + parsedPackage.getPackageName() + " "
-                            + AndroidPackageUtils.getRawPrimaryCpuAbi(parsedPackage)
-                            + ", "
-                            + AndroidPackageUtils.getRawSecondaryCpuAbi(parsedPackage));
-                }
-            }
-        } else {
-            if ((scanFlags & SCAN_MOVE) != 0) {
-                // We haven't run dex-opt for this move (since we've moved the compiled output too)
-                // but we already have this packages package info in the PackageSetting. We just
-                // use that and derive the native library path based on the new code path.
-                parsedPackage.setPrimaryCpuAbi(pkgSetting.getPrimaryCpuAbiLegacy())
-                        .setSecondaryCpuAbi(pkgSetting.getSecondaryCpuAbiLegacy());
             }
 
-            // Set native library paths again. For moves, the path will be updated based on the
-            // ABIs we've determined above. For non-moves, the path will be updated based on the
-            // ABIs we determined during compilation, but the path will depend on the final
-            // package path (after the rename away from the stage path).
-            final PackageAbiHelper.NativeLibraryPaths nativeLibraryPaths =
-                    packageAbiHelper.deriveNativeLibraryPaths(parsedPackage, isSystemApp,
-                            isUpdatedSystemApp, appLib32InstallDir);
-            nativeLibraryPaths.applyTo(parsedPackage);
-        }
-
-        // This is a special case for the "system" package, where the ABI is
-        // dictated by the zygote configuration (and init.rc). We should keep track
-        // of this ABI so that we can deal with "normal" applications that run under
-        // the same UID correctly.
-        if (isPlatformPackage) {
-            parsedPackage.setPrimaryCpuAbi(VMRuntime.getRuntime().is64Bit()
-                    ? Build.SUPPORTED_64_BIT_ABIS[0] : Build.SUPPORTED_32_BIT_ABIS[0]);
+            // This is a special case for the "system" package, where the ABI is
+            // dictated by the zygote configuration (and init.rc). We should keep track
+            // of this ABI so that we can deal with "normal" applications that run under
+            // the same UID correctly.
+            if (isPlatformPackage) {
+                parsedPackage.setPrimaryCpuAbi(VMRuntime.getRuntime().is64Bit()
+                        ? Build.SUPPORTED_64_BIT_ABIS[0] : Build.SUPPORTED_32_BIT_ABIS[0]);
+            }
         }
 
         // If there's a mismatch between the abi-override in the package setting
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index aaf13eb..e5ad01f 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -107,9 +107,11 @@
 import com.android.server.pm.permission.LegacyPermissionState;
 import com.android.server.pm.permission.LegacyPermissionState.PermissionState;
 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.PackageUserState;
 import com.android.server.pm.pkg.PackageUserStateInternal;
+import com.android.server.pm.pkg.SharedUserApi;
 import com.android.server.pm.pkg.SuspendParams;
 import com.android.server.pm.pkg.component.ParsedComponent;
 import com.android.server.pm.pkg.component.ParsedIntentInfo;
@@ -152,6 +154,7 @@
 import java.io.PrintWriter;
 import java.nio.charset.Charset;
 import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -315,6 +318,8 @@
     private static final String TAG_SUSPEND_PARAMS = "suspend-params";
     private static final String TAG_MIME_GROUP = "mime-group";
     private static final String TAG_MIME_TYPE = "mime-type";
+    private static final String TAG_ARCHIVE_STATE = "archive-state";
+    private static final String TAG_ARCHIVE_ACTIVITY_INFO = "archive-activity-info";
 
     public static final String ATTR_NAME = "name";
     public static final String ATTR_PACKAGE = "package";
@@ -351,6 +356,7 @@
     private static final String ATTR_VIRTUAL_PRELOAD = "virtual-preload";
     private static final String ATTR_HARMFUL_APP_WARNING = "harmful-app-warning";
     private static final String ATTR_SPLASH_SCREEN_THEME = "splash-screen-theme";
+    private static final String ATTR_MIN_ASPECT_RATIO = "min-aspect-ratio";
 
     private static final String ATTR_PACKAGE_NAME = "packageName";
     private static final String ATTR_BUILD_FINGERPRINT = "buildFingerprint";
@@ -360,6 +366,10 @@
     private static final String ATTR_DATABASE_VERSION = "databaseVersion";
     private static final String ATTR_VALUE = "value";
     private static final String ATTR_FIRST_INSTALL_TIME = "first-install-time";
+    private static final String ATTR_ARCHIVE_ACTIVITY_TITLE = "activity-title";
+    private static final String ATTR_ARCHIVE_INSTALLER_TITLE = "installer-title";
+    private static final String ATTR_ARCHIVE_ICON_PATH = "icon-path";
+    private static final String ATTR_ARCHIVE_MONOCHROME_ICON_PATH = "monochrome-icon-path";
 
     private final Handler mHandler;
 
@@ -516,9 +526,11 @@
     private final WatchedArrayMap<String, String> mRenamedPackages =
             new WatchedArrayMap<String, String>();
 
-    // For every user, it is used to find the package name of the default Browser App.
+    // For every user, it is used to find the package name of the default browser app pending to be
+    // applied, either on first boot after upgrade, or after backup & restore but before app is
+    // installed.
     @Watched
-    final WatchedSparseArray<String> mDefaultBrowserApp = new WatchedSparseArray<String>();
+    final WatchedSparseArray<String> mPendingDefaultBrowser = new WatchedSparseArray<>();
 
     // TODO(b/161161364): This seems unused, and is probably not relevant in the new API, but should
     //  verify.
@@ -591,7 +603,7 @@
         mAppIds.registerObserver(mObserver);
         mRenamedPackages.registerObserver(mObserver);
         mNextAppLinkGeneration.registerObserver(mObserver);
-        mDefaultBrowserApp.registerObserver(mObserver);
+        mPendingDefaultBrowser.registerObserver(mObserver);
         mPendingPackages.registerObserver(mObserver);
         mPastSignatures.registerObserver(mObserver);
         mKeySetRefs.registerObserver(mObserver);
@@ -696,7 +708,7 @@
         mHandler = handler;
         mLock = lock;
         mAppIds = new AppIdSettingMap();
-        mPermissions = new LegacyPermissionSettings(lock);
+        mPermissions = new LegacyPermissionSettings();
         mRuntimePermissionsPersistence = new RuntimePermissionPersistence(
                 runtimePermissionsPersistence, new Consumer<Integer>() {
             @Override
@@ -786,7 +798,7 @@
 
         mRenamedPackages.snapshot(r.mRenamedPackages);
         mNextAppLinkGeneration.snapshot(r.mNextAppLinkGeneration);
-        mDefaultBrowserApp.snapshot(r.mDefaultBrowserApp);
+        mPendingDefaultBrowser.snapshot(r.mPendingDefaultBrowser);
         // mReadMessages
         mPendingPackages = r.mPendingPackagesSnapshot.snapshot();
         mPendingPackagesSnapshot = new SnapshotCache.Sealed<>();
@@ -867,6 +879,10 @@
         return s;
     }
 
+    WatchedArrayMap<String, ? extends SharedUserApi> getSharedUsersLocked() {
+        return mSharedUsers;
+    }
+
     Collection<SharedUserSetting> getAllSharedUsersLPw() {
         return mSharedUsers.values();
     }
@@ -1122,7 +1138,9 @@
                                 PackageManager.UNINSTALL_REASON_UNKNOWN,
                                 null /*harmfulAppWarning*/,
                                 null /*splashscreenTheme*/,
-                                0 /*firstInstallTime*/
+                                0 /*firstInstallTime*/,
+                                PackageManager.USER_MIN_ASPECT_RATIO_UNSET,
+                                null /*archiveState*/
                         );
                     }
                 }
@@ -1502,8 +1520,16 @@
         return cpir;
     }
 
-    String removeDefaultBrowserPackageNameLPw(int userId) {
-        return (userId == UserHandle.USER_ALL) ? null : mDefaultBrowserApp.removeReturnOld(userId);
+    String getPendingDefaultBrowserLPr(int userId) {
+        return mPendingDefaultBrowser.get(userId);
+    }
+
+    void setPendingDefaultBrowserLPw(String defaultBrowser, int userId) {
+        mPendingDefaultBrowser.put(userId, defaultBrowser);
+    }
+
+    String removePendingDefaultBrowserLPw(int userId) {
+        return mPendingDefaultBrowser.removeReturnOld(userId);
     }
 
     private File getUserSystemDirectory(int userId) {
@@ -1686,6 +1712,19 @@
 
     void readDefaultAppsLPw(XmlPullParser parser, int userId)
             throws XmlPullParserException, IOException {
+        String defaultBrowser = readDefaultApps(parser);
+        if (defaultBrowser != null) {
+            mPendingDefaultBrowser.put(userId, defaultBrowser);
+        }
+    }
+
+    /**
+     * @return the package name for the default browser app, or {@code null} if none.
+     */
+    @Nullable
+    static String readDefaultApps(@NonNull XmlPullParser parser)
+            throws XmlPullParserException, IOException {
+        String defaultBrowser = null;
         int outerDepth = parser.getDepth();
         int type;
         while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
@@ -1695,8 +1734,7 @@
             }
             String tagName = parser.getName();
             if (tagName.equals(TAG_DEFAULT_BROWSER)) {
-                String packageName = parser.getAttributeValue(null, ATTR_PACKAGE_NAME);
-                mDefaultBrowserApp.put(userId, packageName);
+                defaultBrowser = parser.getAttributeValue(null, ATTR_PACKAGE_NAME);
             } else if (tagName.equals(TAG_DEFAULT_DIALER)) {
                 // Ignored.
             } else {
@@ -1706,6 +1744,7 @@
                 XmlUtils.skipCurrentTag(parser);
             }
         }
+        return defaultBrowser;
     }
 
     void readBlockUninstallPackagesLPw(TypedXmlPullParser parser, int userId)
@@ -1776,7 +1815,9 @@
                                     PackageManager.UNINSTALL_REASON_UNKNOWN,
                                     null /*harmfulAppWarning*/,
                                     null /* splashScreenTheme*/,
-                                    0 /*firstInstallTime*/
+                                    0 /*firstInstallTime*/,
+                                    PackageManager.USER_MIN_ASPECT_RATIO_UNSET,
+                                    null /*archiveState*/
                             );
                         }
                         return;
@@ -1871,12 +1912,16 @@
                                 ATTR_SPLASH_SCREEN_THEME);
                         final long firstInstallTime = parser.getAttributeLongHex(null,
                                 ATTR_FIRST_INSTALL_TIME, 0);
+                        final int minAspectRatio = parser.getAttributeInt(null,
+                                ATTR_MIN_ASPECT_RATIO,
+                                PackageManager.USER_MIN_ASPECT_RATIO_UNSET);
 
                         ArraySet<String> enabledComponents = null;
                         ArraySet<String> disabledComponents = null;
                         PersistableBundle suspendedAppExtras = null;
                         PersistableBundle suspendedLauncherExtras = null;
                         SuspendDialogInfo oldSuspendDialogInfo = null;
+                        ArchiveState archiveState = null;
 
                         int packageDepth = parser.getDepth();
                         ArrayMap<String, SuspendParams> suspendParamsMap = null;
@@ -1918,6 +1963,9 @@
                                     suspendParamsMap.put(suspendingPackage,
                                             SuspendParams.restoreFromXml(parser));
                                     break;
+                                case TAG_ARCHIVE_STATE:
+                                    archiveState = parseArchiveState(parser);
+                                    break;
                                 default:
                                     Slog.wtf(TAG, "Unknown tag " + parser.getName() + " under tag "
                                             + TAG_PACKAGE);
@@ -1947,7 +1995,8 @@
                                 enabledCaller, enabledComponents, disabledComponents, installReason,
                                 uninstallReason, harmfulAppWarning, splashScreenTheme,
                                 firstInstallTime != 0 ? firstInstallTime :
-                                        origFirstInstallTimes.getOrDefault(name, 0L));
+                                        origFirstInstallTimes.getOrDefault(name, 0L),
+                                minAspectRatio, archiveState);
 
                         mDomainVerificationManager.setLegacyUserState(name, userId, verifState);
                     } else if (tagName.equals("preferred-activities")) {
@@ -1976,6 +2025,64 @@
         }
     }
 
+    private static ArchiveState parseArchiveState(TypedXmlPullParser parser)
+            throws XmlPullParserException, IOException {
+        String installerTitle = parser.getAttributeValue(null,
+                ATTR_ARCHIVE_INSTALLER_TITLE);
+        List<ArchiveState.ArchiveActivityInfo> activityInfos =
+                parseArchiveActivityInfos(parser);
+
+        if (installerTitle == null) {
+            Slog.wtf(TAG, "parseArchiveState: installerTitle is null");
+            return null;
+        }
+
+        if (activityInfos.size() < 1) {
+            Slog.wtf(TAG, "parseArchiveState: activityInfos is empty");
+            return null;
+        }
+
+        return new ArchiveState(activityInfos, installerTitle);
+    }
+
+    private static List<ArchiveState.ArchiveActivityInfo> parseArchiveActivityInfos(
+            TypedXmlPullParser parser) throws XmlPullParserException, IOException {
+        List<ArchiveState.ArchiveActivityInfo> activityInfos = new ArrayList<>();
+        int type;
+        int outerDepth = parser.getDepth();
+        String tagName;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && (type != XmlPullParser.END_TAG
+                || parser.getDepth() > outerDepth)) {
+            if (type == XmlPullParser.END_TAG
+                    || type == XmlPullParser.TEXT) {
+                continue;
+            }
+            tagName = parser.getName();
+            if (tagName.equals(TAG_ARCHIVE_ACTIVITY_INFO)) {
+                String title = parser.getAttributeValue(null,
+                        ATTR_ARCHIVE_ACTIVITY_TITLE);
+                Path iconPath = Path.of(parser.getAttributeValue(null,
+                        ATTR_ARCHIVE_ICON_PATH));
+                Path monochromeIconPath = Path.of(parser.getAttributeValue(null,
+                        ATTR_ARCHIVE_MONOCHROME_ICON_PATH));
+
+                if (title == null || iconPath == null) {
+                    Slog.wtf(TAG,
+                            TextUtils.formatSimple("Missing attributes in tag %s. %s: %s, %s: %s",
+                                    TAG_ARCHIVE_ACTIVITY_INFO, ATTR_ARCHIVE_ACTIVITY_TITLE, title,
+                                    ATTR_ARCHIVE_ICON_PATH,
+                                    iconPath));
+                    continue;
+                }
+
+                activityInfos.add(
+                        new ArchiveState.ArchiveActivityInfo(title, iconPath, monochromeIconPath));
+            }
+        }
+        return activityInfos;
+    }
+
     void setBlockUninstallLPw(int userId, String packageName, boolean blockUninstall) {
         ArraySet<String> packages = mBlockUninstallPackages.get(userId);
         if (blockUninstall) {
@@ -2080,8 +2187,13 @@
 
     void writeDefaultAppsLPr(XmlSerializer serializer, int userId)
             throws IllegalArgumentException, IllegalStateException, IOException {
+        String defaultBrowser = mPendingDefaultBrowser.get(userId);
+        writeDefaultApps(serializer, defaultBrowser);
+    }
+
+    static void writeDefaultApps(@NonNull XmlSerializer serializer, @Nullable String defaultBrowser)
+            throws IllegalArgumentException, IllegalStateException, IOException {
         serializer.startTag(null, TAG_DEFAULT_APPS);
-        String defaultBrowser = mDefaultBrowserApp.get(userId);
         if (!TextUtils.isEmpty(defaultBrowser)) {
             serializer.startTag(null, TAG_DEFAULT_BROWSER);
             serializer.attribute(null, ATTR_PACKAGE_NAME, defaultBrowser);
@@ -2242,6 +2354,11 @@
                             serializer.attribute(null, ATTR_SPLASH_SCREEN_THEME,
                                     ustate.getSplashScreenTheme());
                         }
+                        if (ustate.getMinAspectRatio()
+                                != PackageManager.USER_MIN_ASPECT_RATIO_UNSET) {
+                            serializer.attributeInt(null, ATTR_MIN_ASPECT_RATIO,
+                                    ustate.getMinAspectRatio());
+                        }
                         if (ustate.isSuspended()) {
                             for (int i = 0; i < ustate.getSuspendParams().size(); i++) {
                                 final String suspendingPackage = ustate.getSuspendParams().keyAt(i);
@@ -2278,6 +2395,7 @@
                             }
                             serializer.endTag(null, TAG_DISABLED_COMPONENTS);
                         }
+                        writeArchiveStateLPr(serializer, ustate.getArchiveState());
 
                         serializer.endTag(null, TAG_PACKAGE);
                     }
@@ -2316,6 +2434,28 @@
         }
     }
 
+    private void writeArchiveStateLPr(TypedXmlSerializer serializer, ArchiveState archiveState)
+            throws IOException {
+        if (archiveState == null) {
+            return;
+        }
+
+        serializer.startTag(null, TAG_ARCHIVE_STATE);
+        serializer.attribute(null, ATTR_ARCHIVE_INSTALLER_TITLE, archiveState.getInstallerTitle());
+        for (ArchiveState.ArchiveActivityInfo activityInfo : archiveState.getActivityInfos()) {
+            serializer.startTag(null, TAG_ARCHIVE_ACTIVITY_INFO);
+            serializer.attribute(null, ATTR_ARCHIVE_ACTIVITY_TITLE, activityInfo.getTitle());
+            serializer.attribute(null, ATTR_ARCHIVE_ICON_PATH,
+                    activityInfo.getIconBitmap().toAbsolutePath().toString());
+            if (activityInfo.getMonochromeIconBitmap() != null) {
+                serializer.attribute(null, ATTR_ARCHIVE_MONOCHROME_ICON_PATH,
+                        activityInfo.getMonochromeIconBitmap().toAbsolutePath().toString());
+            }
+            serializer.endTag(null, TAG_ARCHIVE_ACTIVITY_INFO);
+        }
+        serializer.endTag(null, TAG_ARCHIVE_STATE);
+    }
+
     void readInstallPermissionsLPr(TypedXmlPullParser parser,
             LegacyPermissionState permissionsState, List<UserInfo> users)
             throws IOException, XmlPullParserException {
@@ -3297,6 +3437,11 @@
                 mPackages, mSharedUsers, getUserRuntimePermissionsFile(userId));
     }
 
+    RuntimePermissionsState getLegacyPermissionsState(@UserIdInt int userId) {
+        return mRuntimePermissionsPersistence.getLegacyPermissionsState(
+                userId, mPackages, mSharedUsers);
+    }
+
     void applyDefaultPreferredAppsLPw(int userId) {
         // First pull data from any pre-installed apps.
         final PackageManagerInternal pmInternal =
@@ -4993,10 +5138,9 @@
         if ((permissionNames != null || dumpAll) && pkg != null
                 && pkg.getRequestedPermissions() != null
                 && pkg.getRequestedPermissions().size() > 0) {
-            final List<String> perms = pkg.getRequestedPermissions();
+            final Set<String> perms = pkg.getRequestedPermissions();
             pw.print(prefix); pw.println("  requested permissions:");
-            for (int i=0; i<perms.size(); i++) {
-                String perm = perms.get(i);
+            for (String perm : perms) {
                 if (permissionNames != null
                         && !permissionNames.contains(perm)) {
                     continue;
@@ -5154,6 +5298,10 @@
                     }
                 }
             }
+            ArchiveState archiveState = userState.getArchiveState();
+            if (archiveState != null) {
+                pw.print(archiveState.toString());
+            }
         }
     }
 
@@ -5747,7 +5895,7 @@
                 legacyPermissionDataProvider,
                 @NonNull WatchedArrayMap<String, ? extends PackageStateInternal> packageStates,
                 @NonNull WatchedArrayMap<String, SharedUserSetting> sharedUsers,
-                @Nullable Handler pmHandler, @NonNull Object pmLock,
+                @Nullable Handler pmHandler, @NonNull PackageManagerTracedLock pmLock,
                 boolean sync) {
             synchronized (mLock) {
                 mAsyncHandler.removeMessages(userId);
@@ -5757,44 +5905,16 @@
             Runnable writer = () -> {
                 boolean isLegacyPermissionStateStale = mIsLegacyPermissionStateStale.getAndSet(
                         false);
+                Map<String, List<RuntimePermissionsState.PermissionState>> packagePermissions;
+                Map<String, List<RuntimePermissionsState.PermissionState>> sharedUserPermissions;
 
-                final Map<String, List<RuntimePermissionsState.PermissionState>>
-                        packagePermissions = new ArrayMap<>();
-                final Map<String, List<RuntimePermissionsState.PermissionState>>
-                        sharedUserPermissions = new ArrayMap<>();
                 synchronized (pmLock) {
                     if (sync || isLegacyPermissionStateStale) {
                         legacyPermissionDataProvider.writeLegacyPermissionStateTEMP();
                     }
 
-                    int packagesSize = packageStates.size();
-                    for (int i = 0; i < packagesSize; i++) {
-                        String packageName = packageStates.keyAt(i);
-                        PackageStateInternal packageState = packageStates.valueAt(i);
-                        if (!packageState.hasSharedUser()) {
-                            List<RuntimePermissionsState.PermissionState> permissions =
-                                    getPermissionsFromPermissionsState(
-                                            packageState.getLegacyPermissionState(), userId);
-                            if (permissions.isEmpty()
-                                    && !packageState.isInstallPermissionsFixed()) {
-                                // Storing an empty state means the package is known to the
-                                // system and its install permissions have been granted and fixed.
-                                // If this is not the case, we should not store anything.
-                                continue;
-                            }
-                            packagePermissions.put(packageName, permissions);
-                        }
-                    }
-
-                    final int sharedUsersSize = sharedUsers.size();
-                    for (int i = 0; i < sharedUsersSize; i++) {
-                        String sharedUserName = sharedUsers.keyAt(i);
-                        SharedUserSetting sharedUserSetting = sharedUsers.valueAt(i);
-                        List<RuntimePermissionsState.PermissionState> permissions =
-                                getPermissionsFromPermissionsState(
-                                        sharedUserSetting.getLegacyPermissionState(), userId);
-                        sharedUserPermissions.put(sharedUserName, permissions);
-                    }
+                    packagePermissions = getPackagePermissions(userId, packageStates);
+                    sharedUserPermissions = getShareUsersPermissions(userId, sharedUsers);
                 }
                 synchronized (mLock) {
                     int version = mVersions.get(userId, INITIAL_VERSION);
@@ -5822,6 +5942,68 @@
             }
         }
 
+        @NonNull
+        RuntimePermissionsState getLegacyPermissionsState(int userId,
+                @NonNull WatchedArrayMap<String, ? extends PackageStateInternal> packageStates,
+                @NonNull WatchedArrayMap<String, SharedUserSetting> sharedUsers) {
+            int version;
+            String fingerprint;
+            synchronized (mLock) {
+                version = mVersions.get(userId, INITIAL_VERSION);
+                fingerprint = mFingerprints.get(userId);
+            }
+
+            return new RuntimePermissionsState(
+                    version, fingerprint, getPackagePermissions(userId, packageStates),
+                    getShareUsersPermissions(userId, sharedUsers));
+        }
+
+        @NonNull
+        private Map<String, List<RuntimePermissionsState.PermissionState>> getPackagePermissions(
+                int userId,
+                @NonNull WatchedArrayMap<String, ? extends PackageStateInternal> packageStates) {
+            final Map<String, List<RuntimePermissionsState.PermissionState>>
+                    packagePermissions = new ArrayMap<>();
+
+            final int packagesSize = packageStates.size();
+            for (int i = 0; i < packagesSize; i++) {
+                String packageName = packageStates.keyAt(i);
+                PackageStateInternal packageState = packageStates.valueAt(i);
+                if (!packageState.hasSharedUser()) {
+                    List<RuntimePermissionsState.PermissionState> permissions =
+                            getPermissionsFromPermissionsState(
+                                    packageState.getLegacyPermissionState(), userId);
+                    if (permissions.isEmpty()
+                            && !packageState.isInstallPermissionsFixed()) {
+                        // Storing an empty state means the package is known to the
+                        // system and its install permissions have been granted and fixed.
+                        // If this is not the case, we should not store anything.
+                        continue;
+                    }
+                    packagePermissions.put(packageName, permissions);
+                }
+            }
+            return packagePermissions;
+        }
+
+        @NonNull
+        private Map<String, List<RuntimePermissionsState.PermissionState>> getShareUsersPermissions(
+                int userId, @NonNull WatchedArrayMap<String, SharedUserSetting> sharedUsers) {
+            final Map<String, List<RuntimePermissionsState.PermissionState>>
+                    sharedUserPermissions = new ArrayMap<>();
+
+            final int sharedUsersSize = sharedUsers.size();
+            for (int i = 0; i < sharedUsersSize; i++) {
+                String sharedUserName = sharedUsers.keyAt(i);
+                SharedUserSetting sharedUserSetting = sharedUsers.valueAt(i);
+                List<RuntimePermissionsState.PermissionState> permissions =
+                        getPermissionsFromPermissionsState(
+                                sharedUserSetting.getLegacyPermissionState(), userId);
+                sharedUserPermissions.put(sharedUserName, permissions);
+            }
+            return sharedUserPermissions;
+        }
+
         private void writePendingStates() {
             while (true) {
                 final RuntimePermissionsState runtimePermissions;
diff --git a/services/core/java/com/android/server/pm/SharedUserSetting.java b/services/core/java/com/android/server/pm/SharedUserSetting.java
index a037ae8..9376259 100644
--- a/services/core/java/com/android/server/pm/SharedUserSetting.java
+++ b/services/core/java/com/android/server/pm/SharedUserSetting.java
@@ -285,7 +285,7 @@
                 continue;
             }
             final boolean isPrivileged = isPrivileged() | ps.isPrivileged();
-            ps.getPkgState().setOverrideSeInfo(SELinuxMMAC.getSeInfo(ps.getPkg(), isPrivileged,
+            ps.getPkgState().setOverrideSeInfo(SELinuxMMAC.getSeInfo(ps, ps.getPkg(), isPrivileged,
                     seInfoTargetSdkVersion));
             onChanged();
         }
diff --git a/services/core/java/com/android/server/pm/ShortcutLauncher.java b/services/core/java/com/android/server/pm/ShortcutLauncher.java
index 00f7dc4..bac300d 100644
--- a/services/core/java/com/android/server/pm/ShortcutLauncher.java
+++ b/services/core/java/com/android/server/pm/ShortcutLauncher.java
@@ -23,7 +23,6 @@
 import android.content.pm.UserPackage;
 import android.util.ArrayMap;
 import android.util.ArraySet;
-import android.util.AtomicFile;
 import android.util.Slog;
 import android.util.Xml;
 
@@ -32,8 +31,6 @@
 import com.android.modules.utils.TypedXmlSerializer;
 import com.android.server.pm.ShortcutService.DumpFilter;
 
-import libcore.io.IoUtils;
-
 import org.json.JSONException;
 import org.json.JSONObject;
 import org.xmlpull.v1.XmlPullParser;
@@ -41,7 +38,6 @@
 
 import java.io.File;
 import java.io.FileInputStream;
-import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -264,45 +260,41 @@
 
     public static ShortcutLauncher loadFromFile(File path, ShortcutUser shortcutUser,
             int ownerUserId, boolean fromBackup) {
+        try (ResilientAtomicFile file = getResilientFile(path)) {
+            FileInputStream in = null;
+            try {
+                in = file.openRead();
+                if (in == null) {
+                    Slog.d(TAG, "Not found " + path);
+                    return null;
+                }
 
-        final AtomicFile file = new AtomicFile(path);
-        final FileInputStream in;
-        try {
-            in = file.openRead();
-        } catch (FileNotFoundException e) {
-            if (ShortcutService.DEBUG) {
-                Slog.d(TAG, "Not found " + path);
+                ShortcutLauncher ret = null;
+                TypedXmlPullParser parser = Xml.resolvePullParser(in);
+
+                int type;
+                while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
+                    if (type != XmlPullParser.START_TAG) {
+                        continue;
+                    }
+                    final int depth = parser.getDepth();
+
+                    final String tag = parser.getName();
+                    if (ShortcutService.DEBUG_LOAD) {
+                        Slog.d(TAG, String.format("depth=%d type=%d name=%s", depth, type, tag));
+                    }
+                    if ((depth == 1) && TAG_ROOT.equals(tag)) {
+                        ret = loadFromXml(parser, shortcutUser, ownerUserId, fromBackup);
+                        continue;
+                    }
+                    ShortcutService.throwForInvalidTag(depth, tag);
+                }
+                return ret;
+            } catch (Exception e) {
+                Slog.e(TAG, "Failed to read file " + file.getBaseFile(), e);
+                file.failRead(in, e);
+                return loadFromFile(path, shortcutUser, ownerUserId, fromBackup);
             }
-            return null;
-        }
-
-        try {
-            ShortcutLauncher ret = null;
-            TypedXmlPullParser parser = Xml.resolvePullParser(in);
-
-            int type;
-            while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
-                if (type != XmlPullParser.START_TAG) {
-                    continue;
-                }
-                final int depth = parser.getDepth();
-
-                final String tag = parser.getName();
-                if (ShortcutService.DEBUG_LOAD) {
-                    Slog.d(TAG, String.format("depth=%d type=%d name=%s", depth, type, tag));
-                }
-                if ((depth == 1) && TAG_ROOT.equals(tag)) {
-                    ret = loadFromXml(parser, shortcutUser, ownerUserId, fromBackup);
-                    continue;
-                }
-                ShortcutService.throwForInvalidTag(depth, tag);
-            }
-            return ret;
-        } catch (IOException | XmlPullParserException e) {
-            Slog.e(TAG, "Failed to read file " + file.getBaseFile(), e);
-            return null;
-        } finally {
-            IoUtils.closeQuietly(in);
         }
     }
 
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index 28cb7f0..0ea45c4 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -50,7 +50,6 @@
 import android.text.format.Formatter;
 import android.util.ArrayMap;
 import android.util.ArraySet;
-import android.util.AtomicFile;
 import android.util.Log;
 import android.util.Slog;
 import android.util.Xml;
@@ -69,8 +68,6 @@
 import com.android.server.pm.ShortcutService.ShortcutOperation;
 import com.android.server.pm.ShortcutService.Stats;
 
-import libcore.io.IoUtils;
-
 import org.json.JSONException;
 import org.json.JSONObject;
 import org.xmlpull.v1.XmlPullParser;
@@ -78,7 +75,6 @@
 
 import java.io.File;
 import java.io.FileInputStream;
-import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -1969,45 +1965,41 @@
 
     public static ShortcutPackage loadFromFile(ShortcutService s, ShortcutUser shortcutUser,
             File path, boolean fromBackup) {
+        try (ResilientAtomicFile file = getResilientFile(path)) {
+            FileInputStream in = null;
+            try {
+                in = file.openRead();
+                if (in == null) {
+                    Slog.d(TAG, "Not found " + path);
+                    return null;
+                }
 
-        final AtomicFile file = new AtomicFile(path);
-        final FileInputStream in;
-        try {
-            in = file.openRead();
-        } catch (FileNotFoundException e) {
-            if (ShortcutService.DEBUG) {
-                Slog.d(TAG, "Not found " + path);
+                ShortcutPackage ret = null;
+                TypedXmlPullParser parser = Xml.resolvePullParser(in);
+
+                int type;
+                while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
+                    if (type != XmlPullParser.START_TAG) {
+                        continue;
+                    }
+                    final int depth = parser.getDepth();
+
+                    final String tag = parser.getName();
+                    if (ShortcutService.DEBUG_LOAD || ShortcutService.DEBUG_REBOOT) {
+                        Slog.d(TAG, String.format("depth=%d type=%d name=%s", depth, type, tag));
+                    }
+                    if ((depth == 1) && TAG_ROOT.equals(tag)) {
+                        ret = loadFromXml(s, shortcutUser, parser, fromBackup);
+                        continue;
+                    }
+                    ShortcutService.throwForInvalidTag(depth, tag);
+                }
+                return ret;
+            } catch (Exception e) {
+                Slog.e(TAG, "Failed to read file " + file.getBaseFile(), e);
+                file.failRead(in, e);
+                return loadFromFile(s, shortcutUser, path, fromBackup);
             }
-            return null;
-        }
-
-        try {
-            ShortcutPackage ret = null;
-            TypedXmlPullParser parser = Xml.resolvePullParser(in);
-
-            int type;
-            while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
-                if (type != XmlPullParser.START_TAG) {
-                    continue;
-                }
-                final int depth = parser.getDepth();
-
-                final String tag = parser.getName();
-                if (ShortcutService.DEBUG_LOAD || ShortcutService.DEBUG_REBOOT) {
-                    Slog.d(TAG, String.format("depth=%d type=%d name=%s", depth, type, tag));
-                }
-                if ((depth == 1) && TAG_ROOT.equals(tag)) {
-                    ret = loadFromXml(s, shortcutUser, parser, fromBackup);
-                    continue;
-                }
-                ShortcutService.throwForInvalidTag(depth, tag);
-            }
-            return ret;
-        } catch (IOException | XmlPullParserException e) {
-            Slog.e(TAG, "Failed to read file " + file.getBaseFile(), e);
-            return null;
-        } finally {
-            IoUtils.closeQuietly(in);
         }
     }
 
diff --git a/services/core/java/com/android/server/pm/ShortcutPackageItem.java b/services/core/java/com/android/server/pm/ShortcutPackageItem.java
index 8b118da..8667888 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackageItem.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackageItem.java
@@ -20,14 +20,13 @@
 import android.content.pm.PackageInfo;
 import android.content.pm.ShortcutInfo;
 import android.graphics.Bitmap;
-import android.util.AtomicFile;
+import android.os.FileUtils;
 import android.util.Slog;
 import android.util.Xml;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.Preconditions;
 import com.android.modules.utils.TypedXmlSerializer;
-import com.android.server.security.FileIntegrity;
 
 import org.json.JSONException;
 import org.json.JSONObject;
@@ -160,36 +159,31 @@
 
     @GuardedBy("mLock")
     public void saveToFileLocked(File path, boolean forBackup) {
-        final AtomicFile file = new AtomicFile(path);
-        FileOutputStream os = null;
-        try {
-            os = file.startWrite();
-
-            // Write to XML
-            final TypedXmlSerializer itemOut;
-            if (forBackup) {
-                itemOut = Xml.newFastSerializer();
-                itemOut.setOutput(os, StandardCharsets.UTF_8.name());
-            } else {
-                itemOut = Xml.resolveSerializer(os);
-            }
-            itemOut.startDocument(null, true);
-
-            saveToXml(itemOut, forBackup);
-
-            itemOut.endDocument();
-
-            os.flush();
-            file.finishWrite(os);
-
+        try (ResilientAtomicFile file = getResilientFile(path)) {
+            FileOutputStream os = null;
             try {
-                FileIntegrity.setUpFsVerity(path);
-            } catch (IOException e) {
-                Slog.e(TAG, "Failed to verity-protect " + path, e);
+                os = file.startWrite();
+
+                // Write to XML
+                final TypedXmlSerializer itemOut;
+                if (forBackup) {
+                    itemOut = Xml.newFastSerializer();
+                    itemOut.setOutput(os, StandardCharsets.UTF_8.name());
+                } else {
+                    itemOut = Xml.resolveSerializer(os);
+                }
+                itemOut.startDocument(null, true);
+
+                saveToXml(itemOut, forBackup);
+
+                itemOut.endDocument();
+
+                os.flush();
+                file.finishWrite(os);
+            } catch (XmlPullParserException | IOException e) {
+                Slog.e(TAG, "Failed to write to file " + file.getBaseFile(), e);
+                file.failWrite(os);
             }
-        } catch (XmlPullParserException | IOException e) {
-            Slog.e(TAG, "Failed to write to file " + file.getBaseFile(), e);
-            file.failWrite(os);
         }
     }
 
@@ -265,9 +259,18 @@
 
     void removeShortcutPackageItem() {
         synchronized (mLock) {
-            getShortcutPackageItemFile().delete();
+            getResilientFile(getShortcutPackageItemFile()).delete();
         }
     }
 
     protected abstract File getShortcutPackageItemFile();
+
+    protected static ResilientAtomicFile getResilientFile(File file) {
+        String path = file.getPath();
+        File temporaryBackup = new File(path + ".backup");
+        File reserveCopy = new File(path + ".reservecopy");
+        int fileMode = FileUtils.S_IRWXU | FileUtils.S_IRWXG | FileUtils.S_IXOTH;
+        return new ResilientAtomicFile(file, temporaryBackup, reserveCopy, fileMode,
+                "shortcut package item", null);
+    }
 }
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 5b3514c..710e0b7 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -15,6 +15,7 @@
  */
 package com.android.server.pm;
 
+import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED;
 import static android.provider.DeviceConfig.NAMESPACE_SYSTEMUI;
 
 import android.Manifest.permission;
@@ -24,6 +25,7 @@
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
+import android.app.ActivityOptions;
 import android.app.AppGlobals;
 import android.app.IUidObserver;
 import android.app.IUriGrantsManager;
@@ -4407,8 +4409,11 @@
             return;
         }
         try {
+            ActivityOptions options = ActivityOptions.makeBasic()
+                    .setPendingIntentBackgroundActivityStartMode(
+                            MODE_BACKGROUND_ACTIVITY_START_DENIED);
             intentSender.sendIntent(mContext, /* code= */ 0, extras,
-                    /* onFinished=*/ null, /* handler= */ null);
+                    /* onFinished=*/ null, /* handler= */ null, null, options.toBundle());
         } catch (SendIntentException e) {
             Slog.w(TAG, "sendIntent failed().", e);
         }
diff --git a/services/core/java/com/android/server/pm/ShortcutUser.java b/services/core/java/com/android/server/pm/ShortcutUser.java
index 94eb6bb..d32eb22 100644
--- a/services/core/java/com/android/server/pm/ShortcutUser.java
+++ b/services/core/java/com/android/server/pm/ShortcutUser.java
@@ -437,14 +437,14 @@
         } else {
             final File root = s.injectUserDataPath(userId);
 
-            forAllFilesIn(new File(root, DIRECTORY_PACKAGES), (File f) -> {
+            forMainFilesIn(new File(root, DIRECTORY_PACKAGES), (File f) -> {
                 final ShortcutPackage sp = ShortcutPackage.loadFromFile(s, ret, f, fromBackup);
                 if (sp != null) {
                     ret.mPackages.put(sp.getPackageName(), sp);
                 }
             });
 
-            forAllFilesIn(new File(root, DIRECTORY_LUANCHERS), (File f) -> {
+            forMainFilesIn(new File(root, DIRECTORY_LUANCHERS), (File f) -> {
                 final ShortcutLauncher sl =
                         ShortcutLauncher.loadFromFile(f, ret, userId, fromBackup);
                 if (sl != null) {
@@ -456,13 +456,15 @@
         return ret;
     }
 
-    private static void forAllFilesIn(File path, Consumer<File> callback) {
+    private static void forMainFilesIn(File path, Consumer<File> callback) {
         if (!path.exists()) {
             return;
         }
         File[] list = path.listFiles();
         for (File f : list) {
-            callback.accept(f);
+            if (!f.getName().endsWith(".reservecopy") && !f.getName().endsWith(".backup")) {
+                callback.accept(f);
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/pm/StorageEventHelper.java b/services/core/java/com/android/server/pm/StorageEventHelper.java
index 8f8f437..6f0fe63 100644
--- a/services/core/java/com/android/server/pm/StorageEventHelper.java
+++ b/services/core/java/com/android/server/pm/StorageEventHelper.java
@@ -306,6 +306,7 @@
         }
         mBroadcastHelper.sendResourcesChangedBroadcast(mPm::snapshotComputer, mediaStatus,
                 replacing, packageNames, packageUids);
+        mPm.notifyResourcesChanged(mediaStatus, replacing, packageNames, packageUids);
     }
 
     /**
diff --git a/services/core/java/com/android/server/pm/SuspendPackageHelper.java b/services/core/java/com/android/server/pm/SuspendPackageHelper.java
index 08934c6..94e09f1 100644
--- a/services/core/java/com/android/server/pm/SuspendPackageHelper.java
+++ b/services/core/java/com/android/server/pm/SuspendPackageHelper.java
@@ -633,6 +633,8 @@
                 (callingUid, intentExtras) -> BroadcastHelper.filterExtrasChangedPackageList(
                         mPm.snapshotComputer(), callingUid, intentExtras),
                 options));
+        mPm.notifyPackageMonitor(intent, null /* pkg */, extras, new int[]{userId},
+                null /* instantUserIds */, null /* broadcastAllowList */);
     }
 
     /**
diff --git a/services/core/java/com/android/server/pm/TEST_MAPPING b/services/core/java/com/android/server/pm/TEST_MAPPING
index a622d07..3c846da 100644
--- a/services/core/java/com/android/server/pm/TEST_MAPPING
+++ b/services/core/java/com/android/server/pm/TEST_MAPPING
@@ -88,6 +88,15 @@
           "include-filter": "android.uidmigration.cts"
         }
       ]
+    },
+    {
+      "name": "CtsInstantAppsHostTestCases",
+      "file_patterns": ["(/|^)PackageMonitorCallbackHelper\\.java"],
+      "options": [
+        {
+          "include-filter": "android.appsecurity.cts.EphemeralTest#testGetSearchableInfo"
+        }
+      ]
     }
   ],
   "presubmit-large": [
@@ -116,7 +125,7 @@
       ]
     },
     {
-      "name": "CtsAppSecurityHostTestCases"
+      "name": "CtsPackageManagerHostTestCases"
     },
     {
       "name": "PackageManagerServiceHostTests"
diff --git a/services/core/java/com/android/server/pm/UserManagerInternal.java b/services/core/java/com/android/server/pm/UserManagerInternal.java
index 194f237..04cd183 100644
--- a/services/core/java/com/android/server/pm/UserManagerInternal.java
+++ b/services/core/java/com/android/server/pm/UserManagerInternal.java
@@ -588,4 +588,10 @@
      */
     public abstract @UserIdInt int getBootUser(boolean waitUntilSet)
             throws UserManager.CheckedUserOperationException;
+
+    /**
+     * Returns the user id of the communal profile, or {@link android.os.UserHandle#USER_NULL}
+     * if there is no such user.
+     */
+    public abstract @UserIdInt int getCommunalProfileId();
 }
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 7e88e13..e9c6511 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1037,7 +1037,7 @@
                 final UserData userData = mUsers.valueAt(i);
                 final int userId = userData.info.id;
                 if (userId != currentUser && userData.info.isFull() && !userData.info.partial
-                        && !mRemovingUserIds.get(userId)) {
+                        && userData.info.isEnabled() && !mRemovingUserIds.get(userId)) {
                     final long userEnteredTime = userData.mLastEnteredForegroundTimeMillis;
                     if (userEnteredTime > latestEnteredTime) {
                         latestEnteredTime = userEnteredTime;
@@ -1049,6 +1049,26 @@
         return previousUser;
     }
 
+    @Override
+    public @UserIdInt int getCommunalProfileId() {
+        checkQueryOrCreateUsersPermission("get communal profile user id");
+        return getCommunalProfileIdUnchecked();
+    }
+
+    /** Returns the currently-designated communal profile, or USER_NULL if not present. */
+    private @UserIdInt int getCommunalProfileIdUnchecked() {
+        synchronized (mUsersLock) {
+            final int userSize = mUsers.size();
+            for (int i = 0; i < userSize; i++) {
+                final UserInfo user = mUsers.valueAt(i).info;
+                if (user.isCommunalProfile() && !mRemovingUserIds.get(user.id)) {
+                    return user.id;
+                }
+            }
+        }
+        return UserHandle.USER_NULL;
+    }
+
     public @NonNull List<UserInfo> getUsers(boolean excludeDying) {
         return getUsers(/*excludePartial= */ true, excludeDying, /* excludePreCreated= */
                 true);
@@ -1209,6 +1229,10 @@
         return isSameProfileGroupNoChecks(userId, otherUserId);
     }
 
+    /**
+     * Returns whether users are in the same non-empty profile group.
+     * Currently, false if empty profile group, even if they are the same user, for whatever reason.
+     */
     private boolean isSameProfileGroupNoChecks(@UserIdInt int userId, int otherUserId) {
         synchronized (mUsersLock) {
             UserInfo userInfo = getUserInfoLU(userId);
@@ -1224,6 +1248,16 @@
         }
     }
 
+    /**
+     * Returns whether users are in the same profile group, or if the target is a communal profile.
+     */
+    private boolean isSameUserOrProfileGroupOrTargetIsCommunal(UserInfo asker, UserInfo target) {
+        if (asker.id == target.id) return true;
+        if (target.isCommunalProfile()) return true;
+        return (asker.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID
+                && asker.profileGroupId == target.profileGroupId);
+    }
+
     @Override
     public UserInfo getProfileParent(@UserIdInt int userId) {
         if (!hasManageUsersOrPermission(android.Manifest.permission.INTERACT_ACROSS_USERS)) {
@@ -1875,6 +1909,18 @@
         return userTypeDetails.getBadgeNoBackground();
     }
 
+    @Override
+    public @DrawableRes int getUserStatusBarIconResId(@UserIdInt int userId) {
+        checkManageOrInteractPermissionIfCallerInOtherProfileGroup(userId,
+                "getUserStatusBarIconResId");
+        final UserTypeDetails userTypeDetails = getUserTypeDetailsNoChecks(userId);
+        if (userTypeDetails == null || !userTypeDetails.hasBadge()) {
+            Slog.w(LOG_TAG, "Requested status bar icon for non-badged user " + userId);
+            return Resources.ID_NULL;
+        }
+        return userTypeDetails.getStatusBarIcon();
+    }
+
     public boolean isProfile(@UserIdInt int userId) {
         checkQueryOrInteractPermissionIfCallerInOtherProfileGroup(userId, "isProfile");
         return isProfileUnchecked(userId);
@@ -2286,7 +2332,7 @@
     @Override
     public boolean isRestricted(@UserIdInt int userId) {
         if (userId != UserHandle.getCallingUserId()) {
-            checkCreateUsersPermission("query isRestricted for user " + userId);
+            checkQueryOrCreateUsersPermission("query isRestricted for user " + userId);
         }
         synchronized (mUsersLock) {
             final UserInfo userInfo = getUserInfoLU(userId);
@@ -2507,41 +2553,58 @@
     @Override
     public boolean setUserEphemeral(@UserIdInt int userId, boolean enableEphemeral) {
         checkCreateUsersPermission("update ephemeral user flag");
-        UserData userToUpdate = null;
+        return enableEphemeral
+                ? UserManager.isRemoveResultSuccessful(setUserEphemeralUnchecked(userId))
+                : setUserNonEphemeralUnchecked(userId);
+    }
+
+    private boolean setUserNonEphemeralUnchecked(@UserIdInt int userId) {
         synchronized (mPackagesLock) {
+            final UserData userData;
             synchronized (mUsersLock) {
-                final UserData userData = mUsers.get(userId);
+                userData = mUsers.get(userId);
                 if (userData == null) {
-                    Slog.e(LOG_TAG, "User not found for setting ephemeral mode: u" + userId);
+                    Slog.e(LOG_TAG, TextUtils.formatSimple(
+                            "Cannot set user %d non-ephemeral, invalid user id provided.", userId));
                     return false;
                 }
-                boolean isEphemeralUser = (userData.info.flags & UserInfo.FLAG_EPHEMERAL) != 0;
-                boolean isEphemeralOnCreateUser =
-                        (userData.info.flags & UserInfo.FLAG_EPHEMERAL_ON_CREATE) != 0;
-                // when user is created in ephemeral mode via FLAG_EPHEMERAL
-                // its state cannot be changed to non ephemeral.
-                // FLAG_EPHEMERAL_ON_CREATE is used to keep track of this state
-                if (isEphemeralOnCreateUser && !enableEphemeral) {
-                    Slog.e(LOG_TAG, "Failed to change user state to non-ephemeral for user "
-                            + userId);
+                if (!userData.info.isEphemeral()) {
+                    return true;
+                }
+
+                if ((userData.info.flags & UserInfo.FLAG_EPHEMERAL_ON_CREATE) != 0) {
+                    // when user is created in ephemeral mode via FLAG_EPHEMERAL
+                    // its state cannot be changed to non-ephemeral.
+                    // FLAG_EPHEMERAL_ON_CREATE is used to keep track of this state
+                    Slog.e(LOG_TAG, TextUtils.formatSimple("User %d can not be changed to "
+                            + "non-ephemeral because it was set ephemeral on create.", userId));
                     return false;
                 }
-                if (isEphemeralUser != enableEphemeral) {
-                    if (enableEphemeral) {
-                        userData.info.flags |= UserInfo.FLAG_EPHEMERAL;
-                    } else {
-                        userData.info.flags &= ~UserInfo.FLAG_EPHEMERAL;
-                    }
-                    userToUpdate = userData;
-                }
             }
-            if (userToUpdate != null) {
-                writeUserLP(userToUpdate);
-            }
+            userData.info.flags &= ~UserInfo.FLAG_EPHEMERAL;
+            writeUserLP(userData);
         }
         return true;
     }
 
+    private @UserManager.RemoveResult int setUserEphemeralUnchecked(@UserIdInt int userId) {
+        synchronized (mPackagesLock) {
+            final UserData userData;
+            synchronized (mUsersLock) {
+                final int userRemovability = getUserRemovabilityLocked(userId, "set as ephemeral");
+                if (userRemovability != UserManager.REMOVE_RESULT_USER_IS_REMOVABLE) {
+                    return userRemovability;
+                }
+                userData = mUsers.get(userId);
+            }
+            userData.info.flags |= UserInfo.FLAG_EPHEMERAL;
+            writeUserLP(userData);
+        }
+        Slog.i(LOG_TAG, TextUtils.formatSimple(
+                "User %d is set ephemeral and will be removed on user switch or reboot.", userId));
+        return UserManager.REMOVE_RESULT_DEFERRED;
+    }
+
     @Override
     public void setUserIcon(@UserIdInt int userId, Bitmap bitmap) {
         try {
@@ -2578,11 +2641,8 @@
             }
 
             final int callingUserId = UserHandle.getCallingUserId();
-            final int callingGroupId = getUserInfoNoChecks(callingUserId).profileGroupId;
-            final int targetGroupId = targetUserInfo.profileGroupId;
-            final boolean sameGroup = (callingGroupId != UserInfo.NO_PROFILE_GROUP_ID
-                    && callingGroupId == targetGroupId);
-            if ((callingUserId != targetUserId) && !sameGroup) {
+            final UserInfo callingUserInfo = getUserInfoNoChecks(callingUserId);
+            if (!isSameUserOrProfileGroupOrTargetIsCommunal(callingUserInfo, targetUserInfo)) {
                 checkManageUsersPermission("get the icon of a user who is not related");
             }
 
@@ -2927,14 +2987,14 @@
             Preconditions.checkState(mCachedEffectiveUserRestrictions.getRestrictions(userId)
                     != newBaseRestrictions);
 
-            if (mBaseUserRestrictions.updateRestrictions(userId, newBaseRestrictions)) {
+            if (mBaseUserRestrictions.updateRestrictions(userId, new Bundle(newBaseRestrictions))) {
                 scheduleWriteUser(userId);
             }
         }
 
         final Bundle effective = computeEffectiveUserRestrictionsLR(userId);
 
-        mCachedEffectiveUserRestrictions.updateRestrictions(userId, effective);
+        mCachedEffectiveUserRestrictions.updateRestrictions(userId, new Bundle(effective));
 
         // Apply the new restrictions.
         if (DBG) {
@@ -4803,6 +4863,7 @@
         final boolean isRestricted = UserManager.isUserTypeRestricted(userType);
         final boolean isDemo = UserManager.isUserTypeDemo(userType);
         final boolean isManagedProfile = UserManager.isUserTypeManagedProfile(userType);
+        final boolean isCommunalProfile = UserManager.isUserTypeCommunalProfile(userType);
 
         final long ident = Binder.clearCallingIdentity();
         UserInfo userInfo;
@@ -4837,7 +4898,8 @@
                             UserManager.USER_OPERATION_ERROR_MAX_USERS);
                 }
                 // TODO(b/142482943): Perhaps let the following code apply to restricted users too.
-                if (isProfile && !canAddMoreProfilesToUser(userType, parentId, false)) {
+                if (isProfile && !isCommunalProfile &&
+                        !canAddMoreProfilesToUser(userType, parentId, false)) {
                     throwCheckedUserOperationException(
                             "Cannot add more profiles of type " + userType
                                     + " for user " + parentId,
@@ -5422,23 +5484,37 @@
     }
 
     private boolean removeUserWithProfilesUnchecked(@UserIdInt int userId) {
-        UserInfo userInfo = getUserInfoNoChecks(userId);
-
-        if (userInfo == null) {
-            Slog.e(LOG_TAG, TextUtils.formatSimple(
-                    "Cannot remove user %d, invalid user id provided.", userId));
-            return false;
+        final UserData userData;
+        final boolean isProfile;
+        final IntArray profileIds;
+        synchronized (mUsersLock) {
+            final int userRemovability = getUserRemovabilityLocked(userId, "removed");
+            if (userRemovability != UserManager.REMOVE_RESULT_USER_IS_REMOVABLE) {
+                return UserManager.isRemoveResultSuccessful(userRemovability);
+            }
+            userData = mUsers.get(userId);
+            isProfile = userData.info.isProfile();
+            profileIds = isProfile ? null : getProfileIdsLU(userId, null, false);
         }
 
-        if (!userInfo.isProfile()) {
-            int[] profileIds = getProfileIds(userId, false);
-            for (int profileId : profileIds) {
+        if (!isProfile) {
+            Pair<Integer, Integer> currentAndTargetUserIds = getCurrentAndTargetUserIds();
+            if (userId == currentAndTargetUserIds.first) {
+                Slog.w(LOG_TAG, "Current user cannot be removed.");
+                return false;
+            }
+            if (userId == currentAndTargetUserIds.second) {
+                Slog.w(LOG_TAG, "Target user of an ongoing user switch cannot be removed.");
+                return false;
+            }
+            for (int i = profileIds.size() - 1; i >= 0; i--) {
+                int profileId = profileIds.get(i);
                 if (profileId == userId) {
                     //Remove the associated profiles first and then remove the user
                     continue;
                 }
                 Slog.i(LOG_TAG, "removing profile:" + profileId
-                        + "associated with user:" + userId);
+                        + " associated with user:" + userId);
                 if (!removeUserUnchecked(profileId)) {
                     // If the profile was not immediately removed, make sure it is marked as
                     // ephemeral. Don't mark as disabled since, per UserInfo.FLAG_DISABLED
@@ -5485,45 +5561,16 @@
         final long ident = Binder.clearCallingIdentity();
         try {
             final UserData userData;
-            Pair<Integer, Integer> currentAndTargetUserIds = getCurrentAndTargetUserIds();
-            if (userId == currentAndTargetUserIds.first) {
-                Slog.w(LOG_TAG, "Current user cannot be removed.");
-                return false;
-            }
-            if (userId == currentAndTargetUserIds.second) {
-                Slog.w(LOG_TAG, "Target user of an ongoing user switch cannot be removed.");
-                return false;
-            }
             synchronized (mPackagesLock) {
                 synchronized (mUsersLock) {
+                    final int userRemovability = getUserRemovabilityLocked(userId, "removed");
+                    if (userRemovability != UserManager.REMOVE_RESULT_USER_IS_REMOVABLE) {
+                        return UserManager.isRemoveResultSuccessful(userRemovability);
+                    }
                     userData = mUsers.get(userId);
-                    if (userId == UserHandle.USER_SYSTEM) {
-                        Slog.e(LOG_TAG, "System user cannot be removed.");
-                        return false;
-                    }
-
-                    if (userData == null) {
-                        Slog.e(LOG_TAG, TextUtils.formatSimple(
-                                "Cannot remove user %d, invalid user id provided.", userId));
-                        return false;
-                    }
-
-                    if (isNonRemovableMainUser(userData.info)) {
-                        Slog.e(LOG_TAG, "Main user cannot be removed when "
-                                + "it's a permanent admin user.");
-                        return false;
-                    }
-
-                    if (mRemovingUserIds.get(userId)) {
-                        Slog.e(LOG_TAG, TextUtils.formatSimple(
-                                "User %d is already scheduled for removal.", userId));
-                        return false;
-                    }
-
                     Slog.i(LOG_TAG, "Removing user " + userId);
                     addRemovingUserIdLocked(userId);
                 }
-
                 // Set this to a partially created user, so that the user will be purged
                 // on next startup, in case the runtime stops now before stopping and
                 // removing the user completely.
@@ -5589,8 +5636,14 @@
         }
     }
 
-    @GuardedBy("mUsersLock")
     @VisibleForTesting
+    void addRemovingUserId(@UserIdInt int userId) {
+        synchronized (mUsersLock) {
+            addRemovingUserIdLocked(userId);
+        }
+    }
+
+    @GuardedBy("mUsersLock")
     void addRemovingUserIdLocked(@UserIdInt int userId) {
         // We remember deleted user IDs to prevent them from being
         // reused during the current boot; they can still be reused
@@ -5606,6 +5659,7 @@
     @Override
     public @UserManager.RemoveResult int removeUserWhenPossible(@UserIdInt int userId,
             boolean overrideDevicePolicy) {
+        Slog.i(LOG_TAG, "removeUserWhenPossible u" + userId);
         checkCreateUsersPermission("Only the system can remove users");
 
         if (!overrideDevicePolicy) {
@@ -5615,65 +5669,47 @@
                 return UserManager.REMOVE_RESULT_ERROR_USER_RESTRICTION;
             }
         }
+        Slog.i(LOG_TAG, "Attempting to immediately remove user " + userId);
+        if (removeUserWithProfilesUnchecked(userId)) {
+            return UserManager.REMOVE_RESULT_REMOVED;
+        }
+        Slog.i(LOG_TAG, TextUtils.formatSimple(
+                "Unable to immediately remove user %d. Now trying to set it ephemeral.", userId));
+        return setUserEphemeralUnchecked(userId);
+    }
+
+    /**
+     * Returns the user's removability status.
+     * User is removable if the return value is {@link UserManager#REMOVE_RESULT_USER_IS_REMOVABLE}.
+     * If the user is not removable this method also prints the reason.
+     * See also {@link UserManager#isRemoveResultSuccessful}.
+     */
+    @GuardedBy("mUsersLock")
+    private @UserManager.RemoveResult int getUserRemovabilityLocked(@UserIdInt int userId,
+            String msg) {
+        String prefix = TextUtils.formatSimple("User %d can not be %s, ", userId, msg);
         if (userId == UserHandle.USER_SYSTEM) {
-            Slog.e(LOG_TAG, "System user cannot be removed.");
+            Slog.e(LOG_TAG, prefix + "system user cannot be removed.");
             return UserManager.REMOVE_RESULT_ERROR_SYSTEM_USER;
         }
-
-        final long ident = Binder.clearCallingIdentity();
-        try {
-            final UserData userData;
-            synchronized (mPackagesLock) {
-                synchronized (mUsersLock) {
-                    userData = mUsers.get(userId);
-                    if (userData == null) {
-                        Slog.e(LOG_TAG,
-                                "Cannot remove user " + userId + ", invalid user id provided.");
-                        return UserManager.REMOVE_RESULT_ERROR_USER_NOT_FOUND;
-                    }
-
-                    if (isNonRemovableMainUser(userData.info)) {
-                        Slog.e(LOG_TAG, "Main user cannot be removed when "
-                                + "it's a permanent admin user.");
-                        return UserManager.REMOVE_RESULT_ERROR_MAIN_USER_PERMANENT_ADMIN;
-                    }
-
-                    if (mRemovingUserIds.get(userId)) {
-                        Slog.e(LOG_TAG, "User " + userId + " is already scheduled for removal.");
-                        return UserManager.REMOVE_RESULT_ALREADY_BEING_REMOVED;
-                    }
-                }
-
-                // Attempt to immediately remove a non-current and non-target user
-                Pair<Integer, Integer> currentAndTargetUserIds = getCurrentAndTargetUserIds();
-                if (userId != currentAndTargetUserIds.first
-                        && userId != currentAndTargetUserIds.second) {
-                    // Attempt to remove the user. This will fail if the user is the current user
-                    if (removeUserWithProfilesUnchecked(userId)) {
-                        return UserManager.REMOVE_RESULT_REMOVED;
-                    }
-                }
-                // If the user was not immediately removed, make sure it is marked as ephemeral.
-                // Don't mark as disabled since, per UserInfo.FLAG_DISABLED documentation, an
-                // ephemeral user should only be marked as disabled when its removal is in progress.
-                Slog.i(LOG_TAG, TextUtils.formatSimple("Unable to immediately remove user %d "
-                                + "(%s is %d). User is set as ephemeral and will be removed on "
-                                + "user switch or reboot.",
-                        userId,
-                        userId == currentAndTargetUserIds.first
-                                ? "current user"
-                                : "target user of an ongoing user switch",
-                        userId));
-                userData.info.flags |= UserInfo.FLAG_EPHEMERAL;
-                writeUserLP(userData);
-
-                return UserManager.REMOVE_RESULT_DEFERRED;
-            }
-        } finally {
-            Binder.restoreCallingIdentity(ident);
+        final UserData userData = mUsers.get(userId);
+        if (userData == null) {
+            Slog.e(LOG_TAG, prefix + "invalid user id provided.");
+            return UserManager.REMOVE_RESULT_ERROR_USER_NOT_FOUND;
         }
+        if (isNonRemovableMainUser(userData.info)) {
+            Slog.e(LOG_TAG, prefix
+                    + "main user cannot be removed when it's a permanent admin user.");
+            return UserManager.REMOVE_RESULT_ERROR_MAIN_USER_PERMANENT_ADMIN;
+        }
+        if (mRemovingUserIds.get(userId)) {
+            Slog.w(LOG_TAG, prefix + "it is already scheduled for removal.");
+            return UserManager.REMOVE_RESULT_ALREADY_BEING_REMOVED;
+        }
+        return UserManager.REMOVE_RESULT_USER_IS_REMOVABLE;
     }
 
+
     private void finishRemoveUser(final @UserIdInt int userId) {
         Slog.i(LOG_TAG, "finishRemoveUser " + userId);
 
@@ -7057,6 +7093,7 @@
                     return false;
                 }
 
+                // TODO(b/276473320): Probably use isSameUserOrProfileGroupOrTargetIsCommunal.
                 if (targetUserInfo.profileGroupId == UserInfo.NO_PROFILE_GROUP_ID ||
                         targetUserInfo.profileGroupId != callingUserInfo.profileGroupId) {
                     if (throwSecurityException) {
@@ -7141,8 +7178,12 @@
         @UserAssignmentResult
         public int assignUserToDisplayOnStart(@UserIdInt int userId,
                 @UserIdInt int profileGroupId, @UserStartMode int userStartMode, int displayId) {
+
+            final UserProperties properties = getUserProperties(userId);
+            final boolean isAlwaysVisible =  properties != null && properties.getAlwaysVisible();
+
             return mUserVisibilityMediator.assignUserToDisplayOnStart(userId, profileGroupId,
-                    userStartMode, displayId);
+                    userStartMode, displayId, isAlwaysVisible);
         }
 
         @Override
@@ -7249,6 +7290,11 @@
             return getBootUserUnchecked();
         }
 
+        @Override
+        public @UserIdInt int getCommunalProfileId() {
+            return getCommunalProfileIdUnchecked();
+        }
+
     } // class LocalService
 
 
diff --git a/services/core/java/com/android/server/pm/UserTypeDetails.java b/services/core/java/com/android/server/pm/UserTypeDetails.java
index 6065372..7bdcd68 100644
--- a/services/core/java/com/android/server/pm/UserTypeDetails.java
+++ b/services/core/java/com/android/server/pm/UserTypeDetails.java
@@ -116,6 +116,9 @@
     /** Resource ID of the badge without a background. Should be set if mIconBadge is set. */
     private @DrawableRes final int mBadgeNoBackground;
 
+    /** Resource ID of the status bar icon. */
+    private @DrawableRes final int mStatusBarIcon;
+
     /**
      * Resource ID ({@link StringRes}) of the of the labels to describe badged apps; should be the
      * same format as com.android.internal.R.color.profile_badge_1. These are used for accessibility
@@ -160,6 +163,7 @@
             @UserInfoFlag int baseType, @UserInfoFlag int defaultUserInfoPropertyFlags, int label,
             int maxAllowedPerParent,
             int iconBadge, int badgePlain, int badgeNoBackground,
+            int statusBarIcon,
             @Nullable int[] badgeLabels, @Nullable int[] badgeColors,
             @Nullable int[] darkThemeBadgeColors,
             @Nullable Bundle defaultRestrictions,
@@ -181,6 +185,7 @@
         this.mIconBadge = iconBadge;
         this.mBadgePlain = badgePlain;
         this.mBadgeNoBackground = badgeNoBackground;
+        this.mStatusBarIcon = statusBarIcon;
         this.mLabel = label;
         this.mBadgeLabels = badgeLabels;
         this.mBadgeColors = badgeColors;
@@ -254,6 +259,11 @@
         return mBadgeNoBackground;
     }
 
+    /** Resource ID of the status bar icon. */
+    public @DrawableRes int getStatusBarIcon() {
+        return mStatusBarIcon;
+    }
+
     /**
      * Returns the Resource ID of the badgeIndexth badge label, where the badgeIndex is expected
      * to be the {@link UserInfo#profileBadge} of the user.
@@ -375,6 +385,7 @@
         pw.print(prefix); pw.print("mIconBadge: "); pw.println(mIconBadge);
         pw.print(prefix); pw.print("mBadgePlain: "); pw.println(mBadgePlain);
         pw.print(prefix); pw.print("mBadgeNoBackground: "); pw.println(mBadgeNoBackground);
+        pw.print(prefix); pw.print("mStatusBarIcon: "); pw.println(mStatusBarIcon);
         pw.print(prefix); pw.print("mBadgeLabels.length: ");
         pw.println(mBadgeLabels != null ? mBadgeLabels.length : "0(null)");
         pw.print(prefix); pw.print("mBadgeColors.length: ");
@@ -404,6 +415,7 @@
         private @DrawableRes int mIconBadge = Resources.ID_NULL;
         private @DrawableRes int mBadgePlain = Resources.ID_NULL;
         private @DrawableRes int mBadgeNoBackground = Resources.ID_NULL;
+        private @DrawableRes int mStatusBarIcon = Resources.ID_NULL;
         // Default UserProperties cannot be null but for efficiency we don't initialize it now.
         // If it isn't set explicitly, {@link UserProperties.Builder#build()} will be used.
         private @Nullable UserProperties mDefaultUserProperties = null;
@@ -471,6 +483,11 @@
             return this;
         }
 
+        public Builder setStatusBarIcon(@DrawableRes int statusBarIcon) {
+            mStatusBarIcon = statusBarIcon;
+            return this;
+        }
+
         public Builder setLabel(int label) {
             mLabel = label;
             return this;
@@ -550,6 +567,7 @@
                     mIconBadge,
                     mBadgePlain,
                     mBadgeNoBackground,
+                    mStatusBarIcon,
                     mBadgeLabels,
                     mBadgeColors,
                     mDarkThemeBadgeColors == null ? mBadgeColors : mDarkThemeBadgeColors,
@@ -607,4 +625,20 @@
     public boolean isManagedProfile() {
         return UserManager.isUserTypeManagedProfile(mName);
     }
+
+    /**
+     * Returns whether the user type is a communal profile
+     * (i.e. {@link UserManager#USER_TYPE_PROFILE_COMMUNAL}).
+     */
+    public boolean isCommunalProfile() {
+        return UserManager.isUserTypeCommunalProfile(mName);
+    }
+
+    /**
+     * Returns whether the user type is a private profile
+     * (i.e. {@link UserManager#USER_TYPE_PROFILE_PRIVATE}).
+     */
+    public boolean isPrivateProfile() {
+        return UserManager.isUserTypePrivateProfile(mName);
+    }
 }
diff --git a/services/core/java/com/android/server/pm/UserTypeFactory.java b/services/core/java/com/android/server/pm/UserTypeFactory.java
index a814ca4..f7967c0 100644
--- a/services/core/java/com/android/server/pm/UserTypeFactory.java
+++ b/services/core/java/com/android/server/pm/UserTypeFactory.java
@@ -33,7 +33,9 @@
 import static android.os.UserManager.USER_TYPE_FULL_SECONDARY;
 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_COMMUNAL;
 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_PROFILE_TEST;
 import static android.os.UserManager.USER_TYPE_SYSTEM_HEADLESS;
 
@@ -106,6 +108,8 @@
         builders.put(USER_TYPE_FULL_RESTRICTED, getDefaultTypeFullRestricted());
         builders.put(USER_TYPE_SYSTEM_HEADLESS, getDefaultTypeSystemHeadless());
         builders.put(USER_TYPE_PROFILE_CLONE, getDefaultTypeProfileClone());
+        builders.put(USER_TYPE_PROFILE_COMMUNAL, getDefaultTypeProfileCommunal());
+        builders.put(USER_TYPE_PROFILE_PRIVATE, getDefaultTypeProfilePrivate());
         if (Build.IS_DEBUGGABLE) {
             builders.put(USER_TYPE_PROFILE_TEST, getDefaultTypeProfileTest());
         }
@@ -128,6 +132,7 @@
                 .setBadgePlain(com.android.internal.R.drawable.ic_clone_badge)
                 // Clone doesn't use BadgeNoBackground, so just set to BadgePlain as a placeholder.
                 .setBadgeNoBackground(com.android.internal.R.drawable.ic_clone_badge)
+                .setStatusBarIcon(Resources.ID_NULL)
                 .setBadgeLabels(
                         com.android.internal.R.string.clone_profile_label_badge)
                 .setBadgeColors(
@@ -167,6 +172,7 @@
                 .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)
+                .setStatusBarIcon(com.android.internal.R.drawable.stat_sys_managed_profile_status)
                 .setBadgeLabels(
                         com.android.internal.R.string.managed_profile_label_badge,
                         com.android.internal.R.string.managed_profile_label_badge_2,
@@ -179,7 +185,7 @@
                         com.android.internal.R.color.profile_badge_1_dark,
                         com.android.internal.R.color.profile_badge_2_dark,
                         com.android.internal.R.color.profile_badge_3_dark)
-                .setDefaultRestrictions(getDefaultManagedProfileRestrictions())
+                .setDefaultRestrictions(getDefaultProfileRestrictions())
                 .setDefaultSecureSettings(getDefaultManagedProfileSecureSettings())
                 .setDefaultCrossProfileIntentFilters(getDefaultManagedCrossProfileIntentFilter())
                 .setDefaultUserProperties(new UserProperties.Builder()
@@ -194,7 +200,7 @@
      * configuration (for userdebug builds). For now it just uses managed profile badges.
      */
     private static UserTypeDetails.Builder getDefaultTypeProfileTest() {
-        final Bundle restrictions = new Bundle();
+        final Bundle restrictions = getDefaultProfileRestrictions();
         restrictions.putBoolean(UserManager.DISALLOW_FUN, true);
 
         return new UserTypeDetails.Builder()
@@ -205,6 +211,7 @@
                 .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)
+                .setStatusBarIcon(com.android.internal.R.drawable.ic_test_badge_experiment)
                 .setBadgeLabels(
                         com.android.internal.R.string.managed_profile_label_badge,
                         com.android.internal.R.string.managed_profile_label_badge_2,
@@ -222,6 +229,76 @@
     }
 
     /**
+     * Returns the Builder for the default {@link UserManager#USER_TYPE_PROFILE_COMMUNAL}
+     * configuration. For now it just uses managed profile badges.
+     */
+    private static UserTypeDetails.Builder getDefaultTypeProfileCommunal() {
+        return new UserTypeDetails.Builder()
+                .setName(USER_TYPE_PROFILE_COMMUNAL)
+                .setBaseType(FLAG_PROFILE)
+                .setMaxAllowed(1)
+                .setEnabled(UserManager.isCommunalProfileEnabled() ? 1 : 0)
+                .setLabel(0)
+                .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)
+                .setStatusBarIcon(com.android.internal.R.drawable.ic_test_badge_experiment)
+                .setBadgeLabels(
+                        com.android.internal.R.string.managed_profile_label_badge,
+                        com.android.internal.R.string.managed_profile_label_badge_2,
+                        com.android.internal.R.string.managed_profile_label_badge_3)
+                .setBadgeColors(
+                        com.android.internal.R.color.profile_badge_1,
+                        com.android.internal.R.color.profile_badge_2,
+                        com.android.internal.R.color.profile_badge_3)
+                .setDarkThemeBadgeColors(
+                        com.android.internal.R.color.profile_badge_1_dark,
+                        com.android.internal.R.color.profile_badge_2_dark,
+                        com.android.internal.R.color.profile_badge_3_dark)
+                .setDefaultRestrictions(getDefaultProfileRestrictions())
+                .setDefaultSecureSettings(getDefaultNonManagedProfileSecureSettings())
+                .setDefaultUserProperties(new UserProperties.Builder()
+                        .setStartWithParent(false)
+                        .setShowInLauncher(UserProperties.SHOW_IN_LAUNCHER_SEPARATE)
+                        .setShowInSettings(UserProperties.SHOW_IN_SETTINGS_SEPARATE)
+                        .setCredentialShareableWithParent(false)
+                        .setAlwaysVisible(true));
+    }
+
+    /**
+     * Returns the Builder for the default {@link UserManager#USER_TYPE_PROFILE_PRIVATE}
+     * configuration.
+     */
+    private static UserTypeDetails.Builder getDefaultTypeProfilePrivate() {
+        return new UserTypeDetails.Builder()
+                .setName(USER_TYPE_PROFILE_PRIVATE)
+                .setBaseType(FLAG_PROFILE)
+                .setMaxAllowedPerParent(1)
+                .setLabel(0)
+                .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)
+                .setStatusBarIcon(com.android.internal.R.drawable.ic_test_badge_experiment)
+                .setBadgeLabels(
+                        com.android.internal.R.string.managed_profile_label_badge,
+                        com.android.internal.R.string.managed_profile_label_badge_2,
+                        com.android.internal.R.string.managed_profile_label_badge_3)
+                .setBadgeColors(
+                        com.android.internal.R.color.profile_badge_2)
+                .setDarkThemeBadgeColors(
+                        com.android.internal.R.color.profile_badge_2_dark)
+                .setDefaultRestrictions(getDefaultProfileRestrictions())
+                .setDefaultSecureSettings(getDefaultNonManagedProfileSecureSettings())
+                .setDefaultUserProperties(new UserProperties.Builder()
+                        .setStartWithParent(true)
+                        .setCredentialShareableWithParent(false)
+                        .setMediaSharedWithParent(false)
+                        .setShowInLauncher(UserProperties.SHOW_IN_LAUNCHER_SEPARATE)
+                        .setCrossProfileIntentFilterAccessControl(
+                                UserProperties.CROSS_PROFILE_INTENT_FILTER_ACCESS_LEVEL_SYSTEM));
+    }
+
+    /**
      * Returns the Builder for the default {@link UserManager#USER_TYPE_FULL_SECONDARY}
      * configuration.
      */
@@ -314,7 +391,7 @@
         return restrictions;
     }
 
-    private static Bundle getDefaultManagedProfileRestrictions() {
+    private static Bundle getDefaultProfileRestrictions() {
         final Bundle restrictions = new Bundle();
         restrictions.putBoolean(UserManager.DISALLOW_WALLPAPER, true);
         return restrictions;
@@ -426,6 +503,7 @@
                     setResAttribute(parser, "icon-badge", builder::setIconBadge);
                     setResAttribute(parser, "badge-plain", builder::setBadgePlain);
                     setResAttribute(parser, "badge-no-background", builder::setBadgeNoBackground);
+                    setResAttribute(parser, "status-bar-icon", builder::setStatusBarIcon);
                 }
 
                 setIntAttribute(parser, "enabled", builder::setEnabled);
diff --git a/services/core/java/com/android/server/pm/UserVisibilityMediator.java b/services/core/java/com/android/server/pm/UserVisibilityMediator.java
index cf82536..613ebd3 100644
--- a/services/core/java/com/android/server/pm/UserVisibilityMediator.java
+++ b/services/core/java/com/android/server/pm/UserVisibilityMediator.java
@@ -121,6 +121,13 @@
     })
     public @interface SecondaryDisplayMappingStatus {}
 
+    /**
+     * ProfileGroupId representing always-visible users (e.g. a communal profile).
+     * This is an implementation detail of this class only; it may have nothing to do with the
+     * actual user's profile group id in UserManagerService.
+     */
+    public static final int ALWAYS_VISIBLE_PROFILE_GROUP_ID = UserHandle.USER_ALL;
+
     // TODO(b/266158156): might need to change this if boot logic is refactored for HSUM devices
     @VisibleForTesting
     static final int INITIAL_CURRENT_USER_ID = USER_SYSTEM;
@@ -159,6 +166,10 @@
      *
      * <p>It's used to determine not just if the user is visible, but also
      * {@link #isProfile(int, int) if it's a profile}.
+     *
+     * <p>Note that these profile group ids might not be identical to those used in
+     * UserManagerService, as different conventions are used (such as how to treat users with no
+     * profiles, or how to treat the communal profile).
      */
     @GuardedBy("mLock")
     private final SparseIntArray mStartedVisibleProfileGroupIds = new SparseIntArray();
@@ -221,7 +232,7 @@
      */
     public @UserAssignmentResult int assignUserToDisplayOnStart(@UserIdInt int userId,
             @UserIdInt int unResolvedProfileGroupId, @UserStartMode int userStartMode,
-            int displayId) {
+            int displayId, boolean isAlwaysVisible) {
         Preconditions.checkArgument(!isSpecialUserId(userId), "user id cannot be generic: %d",
                 userId);
         validateUserStartMode(userStartMode);
@@ -238,9 +249,8 @@
         // getUserVisibilityOnStartLocked() respectively).
 
 
-        int profileGroupId = unResolvedProfileGroupId == NO_PROFILE_GROUP_ID
-                ? userId
-                : unResolvedProfileGroupId;
+        int profileGroupId
+                = resolveProfileGroupId(userId, unResolvedProfileGroupId, isAlwaysVisible);
         if (DBG) {
             Slogf.d(TAG, "assignUserToDisplayOnStart(%d, %d, %s, %d): actualProfileGroupId=%d",
                     userId, unResolvedProfileGroupId, userStartModeToString(userStartMode),
@@ -330,6 +340,18 @@
         return result;
     }
 
+    private int resolveProfileGroupId(
+            @UserIdInt int userId, @UserIdInt int unResolvedProfileGroupId,
+            boolean isAlwaysVisible) {
+
+        if (isAlwaysVisible) {
+            return ALWAYS_VISIBLE_PROFILE_GROUP_ID;
+        }
+        return unResolvedProfileGroupId == NO_PROFILE_GROUP_ID
+                ? userId
+                : unResolvedProfileGroupId;
+    }
+
     @GuardedBy("mLock")
     @UserAssignmentResult
     private int getUserVisibilityOnStartLocked(@UserIdInt int userId, @UserIdInt int profileGroupId,
@@ -388,8 +410,7 @@
                             userStartModeToString(userStartMode), displayId);
                     return USER_ASSIGNMENT_RESULT_FAILURE;
                 case USER_START_MODE_BACKGROUND_VISIBLE:
-                    boolean isParentVisibleOnDisplay = isUserVisible(profileGroupId, displayId);
-                    if (!isParentVisibleOnDisplay) {
+                    if (!isParentVisibleOnDisplay(profileGroupId, displayId)) {
                         Slogf.w(TAG, "getUserVisibilityOnStartLocked(%d, %d, %s, %d) failed: cannot"
                                 + " start profile user visible when its parent is not visible in "
                                 + "that display", userId, profileGroupId,
@@ -721,6 +742,19 @@
     }
 
     /**
+     * Returns whether the given profileGroupId - i.e. the user (for a non-profile), or its parent
+     * (for a profile) - is visible on the given display.
+     */
+    private boolean isParentVisibleOnDisplay(@UserIdInt int profileGroupId, int displayId) {
+        if (profileGroupId == ALWAYS_VISIBLE_PROFILE_GROUP_ID) {
+            return true;
+        }
+        // The profileGroupId is the user (for a non-profile) or its parent (for a profile),
+        // so query whether it is visible.
+        return isUserVisible(profileGroupId, displayId);
+    }
+
+    /**
      * See {@link UserManagerInternal#isUserVisible(int, int)}.
      */
     public boolean isUserVisible(@UserIdInt int userId, int displayId) {
@@ -1133,8 +1167,9 @@
             if (mCurrentUserId == userId) {
                 return true;
             }
-            return mStartedVisibleProfileGroupIds.get(userId, NO_PROFILE_GROUP_ID)
-                    == mCurrentUserId;
+            int profileGroupId = mStartedVisibleProfileGroupIds.get(userId, NO_PROFILE_GROUP_ID);
+            return profileGroupId ==
+                    mCurrentUserId || profileGroupId == ALWAYS_VISIBLE_PROFILE_GROUP_ID;
         }
     }
 
diff --git a/services/core/java/com/android/server/pm/VerifyingSession.java b/services/core/java/com/android/server/pm/VerifyingSession.java
index 7198de2..3a1fd7c 100644
--- a/services/core/java/com/android/server/pm/VerifyingSession.java
+++ b/services/core/java/com/android/server/pm/VerifyingSession.java
@@ -589,27 +589,14 @@
             final PackageVerificationResponse response = new PackageVerificationResponse(
                     verificationCodeAtTimeout, requiredUid);
 
-            if (streaming) {
-                // For streaming installations, count verification timeout from the broadcast.
-                startVerificationTimeoutCountdown(verificationId, streaming, response,
-                        verificationTimeout);
-            }
+            startVerificationTimeoutCountdown(verificationId, streaming, response,
+                    verificationTimeout);
 
             // Send the intent to the required verification agent, but only start the
             // verification timeout after the target BroadcastReceivers have run.
             mPm.mContext.sendOrderedBroadcastAsUser(requiredIntent, verifierUser,
                     receiverPermission, AppOpsManager.OP_NONE, options.toBundle(),
-                    new BroadcastReceiver() {
-                        @Override
-                        public void onReceive(Context context, Intent intent) {
-                            if (!streaming) {
-                                // For NON-streaming installations, count verification timeout from
-                                // the broadcast was processed by all receivers.
-                                startVerificationTimeoutCountdown(verificationId, streaming,
-                                        response, verificationTimeout);
-                            }
-                        }
-                    }, null, 0, null, null);
+                    null, null, 0, null, null);
         }
 
         Trace.asyncTraceBegin(TRACE_TAG_PACKAGE_MANAGER, "verification", verificationId);
diff --git a/services/core/java/com/android/server/pm/local/PackageManagerLocalImpl.java b/services/core/java/com/android/server/pm/local/PackageManagerLocalImpl.java
index 4e0a11d..8d05450 100644
--- a/services/core/java/com/android/server/pm/local/PackageManagerLocalImpl.java
+++ b/services/core/java/com/android/server/pm/local/PackageManagerLocalImpl.java
@@ -28,6 +28,7 @@
 import com.android.server.pm.PackageManagerLocal;
 import com.android.server.pm.PackageManagerService;
 import com.android.server.pm.pkg.PackageState;
+import com.android.server.pm.pkg.SharedUserApi;
 import com.android.server.pm.snapshot.PackageDataSnapshot;
 
 import java.io.IOException;
@@ -105,6 +106,9 @@
         private Map<String, PackageState> mCachedUnmodifiablePackageStates;
 
         @Nullable
+        private Map<String, SharedUserApi> mCachedUnmodifiableSharedUsers;
+
+        @Nullable
         private Map<String, PackageState> mCachedUnmodifiableDisabledSystemPackageStates;
 
         private UnfilteredSnapshotImpl(@NonNull PackageDataSnapshot snapshot) {
@@ -132,6 +136,19 @@
         @SuppressWarnings("RedundantSuppression")
         @NonNull
         @Override
+        public Map<String, SharedUserApi> getSharedUsers() {
+            checkClosed();
+
+            if (mCachedUnmodifiableSharedUsers == null) {
+                mCachedUnmodifiableSharedUsers =
+                        Collections.unmodifiableMap(mSnapshot.getSharedUsers());
+            }
+            return mCachedUnmodifiableSharedUsers;
+        }
+
+        @SuppressWarnings("RedundantSuppression")
+        @NonNull
+        @Override
         public Map<String, PackageState> getDisabledSystemPackageStates() {
             checkClosed();
 
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 f036835..056aae4 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
@@ -160,8 +160,8 @@
      */
     @NonNull
     @Deprecated
-    @DataClass.ParcelWith(Parcelling.BuiltIn.ForInternedStringList.class)
-    protected List<String> requestedPermissions = emptyList();
+    @DataClass.ParcelWith(Parcelling.BuiltIn.ForInternedStringSet.class)
+    protected Set<String> requestedPermissions = emptySet();
     @NonNull
     @DataClass.ParcelWith(Parcelling.BuiltIn.ForInternedStringList.class)
     protected List<String> protectedBroadcasts = emptyList();
@@ -277,8 +277,8 @@
     @NonNull
     private List<ParsedUsesPermission> usesPermissions = emptyList();
     @NonNull
-    @DataClass.ParcelWith(Parcelling.BuiltIn.ForInternedStringList.class)
-    private List<String> implicitPermissions = emptyList();
+    @DataClass.ParcelWith(Parcelling.BuiltIn.ForInternedStringSet.class)
+    private Set<String> implicitPermissions = emptySet();
     @NonNull
     private Set<String> upgradeKeySets = emptySet();
     @NonNull
@@ -664,29 +664,39 @@
 
     @Override
     public PackageImpl addUsesLibrary(String libraryName) {
-        this.usesLibraries = CollectionUtils.add(this.usesLibraries,
-                TextUtils.safeIntern(libraryName));
+        libraryName = TextUtils.safeIntern(libraryName);
+        if (!ArrayUtils.contains(this.usesLibraries, libraryName)) {
+            this.usesLibraries = CollectionUtils.add(this.usesLibraries, libraryName);
+        }
         return this;
     }
 
     @Override
     public final PackageImpl addUsesNativeLibrary(String libraryName) {
-        this.usesNativeLibraries = CollectionUtils.add(this.usesNativeLibraries,
-                TextUtils.safeIntern(libraryName));
+        libraryName = TextUtils.safeIntern(libraryName);
+        if (!ArrayUtils.contains(this.usesNativeLibraries, libraryName)) {
+            this.usesNativeLibraries = CollectionUtils.add(this.usesNativeLibraries, libraryName);
+        }
         return this;
     }
 
     @Override
     public PackageImpl addUsesOptionalLibrary(String libraryName) {
-        this.usesOptionalLibraries = CollectionUtils.add(this.usesOptionalLibraries,
-                TextUtils.safeIntern(libraryName));
+        libraryName = TextUtils.safeIntern(libraryName);
+        if (!ArrayUtils.contains(this.usesOptionalLibraries, libraryName)) {
+            this.usesOptionalLibraries = CollectionUtils.add(this.usesOptionalLibraries,
+                    libraryName);
+        }
         return this;
     }
 
     @Override
     public final PackageImpl addUsesOptionalNativeLibrary(String libraryName) {
-        this.usesOptionalNativeLibraries = CollectionUtils.add(this.usesOptionalNativeLibraries,
-                TextUtils.safeIntern(libraryName));
+        libraryName = TextUtils.safeIntern(libraryName);
+        if (!ArrayUtils.contains(this.usesOptionalNativeLibraries, libraryName)) {
+            this.usesOptionalNativeLibraries = CollectionUtils.add(this.usesOptionalNativeLibraries,
+                    libraryName);
+        }
         return this;
     }
 
@@ -967,7 +977,7 @@
 
     @NonNull
     @Override
-    public List<String> getImplicitPermissions() {
+    public Set<String> getImplicitPermissions() {
         return implicitPermissions;
     }
 
@@ -1225,7 +1235,7 @@
     @NonNull
     @Override
     @Deprecated
-    public List<String> getRequestedPermissions() {
+    public Set<String> getRequestedPermissions() {
         return requestedPermissions;
     }
 
@@ -2729,7 +2739,7 @@
         usesOptionalNativeLibraries = Collections.unmodifiableList(usesOptionalNativeLibraries);
         originalPackages = Collections.unmodifiableList(originalPackages);
         adoptPermissions = Collections.unmodifiableList(adoptPermissions);
-        requestedPermissions = Collections.unmodifiableList(requestedPermissions);
+        requestedPermissions = Collections.unmodifiableSet(requestedPermissions);
         protectedBroadcasts = Collections.unmodifiableList(protectedBroadcasts);
         apexSystemServices = Collections.unmodifiableList(apexSystemServices);
 
@@ -2750,7 +2760,7 @@
         featureGroups = Collections.unmodifiableList(featureGroups);
         usesPermissions = Collections.unmodifiableList(usesPermissions);
         usesSdkLibraries = Collections.unmodifiableList(usesSdkLibraries);
-        implicitPermissions = Collections.unmodifiableList(implicitPermissions);
+        implicitPermissions = Collections.unmodifiableSet(implicitPermissions);
         upgradeKeySets = Collections.unmodifiableSet(upgradeKeySets);
         keySetMapping = Collections.unmodifiableMap(keySetMapping);
         attributions = Collections.unmodifiableList(attributions);
@@ -3121,9 +3131,9 @@
         dest.writeByteArray(this.restrictUpdateHash);
         dest.writeStringList(this.originalPackages);
         sForInternedStringList.parcel(this.adoptPermissions, dest, flags);
-        sForInternedStringList.parcel(this.requestedPermissions, dest, flags);
+        sForInternedStringSet.parcel(this.requestedPermissions, dest, flags);
         ParsingUtils.writeParcelableList(dest, this.usesPermissions);
-        sForInternedStringList.parcel(this.implicitPermissions, dest, flags);
+        sForInternedStringSet.parcel(this.implicitPermissions, dest, flags);
         sForStringSet.parcel(this.upgradeKeySets, dest, flags);
         ParsingPackageUtils.writeKeySetMapping(dest, this.keySetMapping);
         sForInternedStringList.parcel(this.protectedBroadcasts, dest, flags);
@@ -3273,10 +3283,10 @@
         this.restrictUpdateHash = in.createByteArray();
         this.originalPackages = in.createStringArrayList();
         this.adoptPermissions = sForInternedStringList.unparcel(in);
-        this.requestedPermissions = sForInternedStringList.unparcel(in);
+        this.requestedPermissions = sForInternedStringSet.unparcel(in);
         this.usesPermissions = ParsingUtils.createTypedInterfaceList(in,
                 ParsedUsesPermissionImpl.CREATOR);
-        this.implicitPermissions = sForInternedStringList.unparcel(in);
+        this.implicitPermissions = sForInternedStringSet.unparcel(in);
         this.upgradeKeySets = sForStringSet.unparcel(in);
         this.keySetMapping = ParsingPackageUtils.readKeySetMapping(in);
         this.protectedBroadcasts = sForInternedStringList.unparcel(in);
diff --git a/services/core/java/com/android/server/pm/permission/AccessTestingShimFactory.java b/services/core/java/com/android/server/pm/permission/AccessTestingShimFactory.java
new file mode 100644
index 0000000..0682e92
--- /dev/null
+++ b/services/core/java/com/android/server/pm/permission/AccessTestingShimFactory.java
@@ -0,0 +1,75 @@
+/*
+ * 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.pm.permission;
+
+import static android.provider.DeviceConfig.NAMESPACE_PRIVACY;
+
+import android.content.Context;
+import android.provider.DeviceConfig;
+
+import com.android.server.appop.AppOpsCheckingServiceInterface;
+import com.android.server.appop.AppOpsServiceTestingShim;
+
+import java.util.function.Supplier;
+
+/**
+ * A factory which will select one or both implementations of a PermissionManagerServiceInterface or
+ * AppOpsCheckingServiceInterface, based upon either a DeviceConfig value, or a hard coded config.
+ */
+public class AccessTestingShimFactory {
+
+    private static final int RUN_OLD_SUBSYSTEM = 0;
+    private static final int RUN_NEW_SUBSYSTEM = 1;
+    private static final int RUN_BOTH_SUBSYSTEMS = 2;
+    public static final String DEVICE_CONFIG_SETTING = "selected_access_subsystem";
+
+    /**
+     * Get the PermissionManagerServiceInterface, based upon the current config state.
+     */
+    public static PermissionManagerServiceInterface getPms(Context context,
+            Supplier<PermissionManagerServiceInterface> oldImpl,
+            Supplier<PermissionManagerServiceInterface> newImpl) {
+        int selectedSystem = DeviceConfig.getInt(NAMESPACE_PRIVACY,
+                DEVICE_CONFIG_SETTING, RUN_OLD_SUBSYSTEM);
+        switch (selectedSystem) {
+            case RUN_BOTH_SUBSYSTEMS:
+                return new PermissionManagerServiceTestingShim(oldImpl.get(), newImpl.get());
+            case RUN_NEW_SUBSYSTEM:
+                return newImpl.get();
+            default:
+                return oldImpl.get();
+        }
+    }
+
+    /**
+     * Get the AppOpsCheckingServiceInterface, based upon the current config state.
+     */
+    public static AppOpsCheckingServiceInterface getAos(Context context,
+            Supplier<AppOpsCheckingServiceInterface> oldImpl,
+            Supplier<AppOpsCheckingServiceInterface> newImpl) {
+        int selectedSystem = DeviceConfig.getInt(NAMESPACE_PRIVACY,
+                DEVICE_CONFIG_SETTING, RUN_OLD_SUBSYSTEM);
+        switch (selectedSystem) {
+            case RUN_BOTH_SUBSYSTEMS:
+                return new AppOpsServiceTestingShim(oldImpl.get(), newImpl.get());
+            case RUN_NEW_SUBSYSTEM:
+                return newImpl.get();
+            default:
+                return oldImpl.get();
+        }
+    }
+}
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 9510529..c44b8852 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -26,7 +26,6 @@
 import android.app.DownloadManager;
 import android.app.SearchManager;
 import android.app.admin.DevicePolicyManager;
-import android.companion.CompanionDeviceManager;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
@@ -74,6 +73,8 @@
 import com.android.server.pm.permission.LegacyPermissionManagerInternal.PackagesProvider;
 import com.android.server.pm.permission.LegacyPermissionManagerInternal.SyncAdapterPackagesProvider;
 
+import libcore.util.HexEncoding;
+
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
@@ -126,6 +127,7 @@
     private static final String ATTR_NAME = "name";
     private static final String ATTR_FIXED = "fixed";
     private static final String ATTR_WHITELISTED = "whitelisted";
+    private static final String ATTR_CERT = "cert";
 
     private static final Set<String> PHONE_PERMISSIONS = new ArraySet<>();
 
@@ -216,7 +218,6 @@
         STORAGE_PERMISSIONS.add(Manifest.permission.READ_MEDIA_AUDIO);
         STORAGE_PERMISSIONS.add(Manifest.permission.READ_MEDIA_VIDEO);
         STORAGE_PERMISSIONS.add(Manifest.permission.READ_MEDIA_IMAGES);
-        STORAGE_PERMISSIONS.add(Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED);
     }
 
     private static final Set<String> NEARBY_DEVICES_PERMISSIONS = new ArraySet<>();
@@ -900,7 +901,7 @@
 
         // Companion devices
         grantSystemFixedPermissionsToSystemPackage(pm,
-                CompanionDeviceManager.COMPANION_DEVICE_DISCOVERY_PACKAGE_NAME, userId,
+                getDefaultCompanionDeviceManagerPackage(), userId,
                 ALWAYS_LOCATION_PERMISSIONS, NEARBY_DEVICES_PERMISSIONS);
 
         // Ringtone Picker
@@ -950,6 +951,10 @@
         return mContext.getString(R.string.config_defaultDockManagerPackageName);
     }
 
+    private String getDefaultCompanionDeviceManagerPackage() {
+        return mContext.getString(R.string.config_companionDeviceManagerPackage);
+    }
+
     @SafeVarargs
     private final void grantPermissionToEachSystemPackage(PackageManagerWrapper pm,
             ArrayList<String> packages, int userId, Set<String>... permissions) {
@@ -1438,7 +1443,7 @@
         final int exceptionCount = mGrantExceptions.size();
         for (int i = 0; i < exceptionCount; i++) {
             String packageName = mGrantExceptions.keyAt(i);
-            PackageInfo pkg = pm.getSystemPackageInfo(packageName);
+            PackageInfo pkg = pm.getPackageInfo(packageName);
             List<DefaultPermissionGrant> permissionGrants = mGrantExceptions.valueAt(i);
             final int permissionGrantCount = permissionGrants.size();
             for (int j = 0; j < permissionGrantCount; j++) {
@@ -1556,12 +1561,12 @@
             }
             if (TAG_EXCEPTION.equals(parser.getName())) {
                 String packageName = parser.getAttributeValue(null, ATTR_PACKAGE);
+                String cert = parser.getAttributeValue(null, ATTR_CERT);
 
                 List<DefaultPermissionGrant> packageExceptions =
                         outGrantExceptions.get(packageName);
                 if (packageExceptions == null) {
-                    // The package must be on the system image
-                    PackageInfo packageInfo = pm.getSystemPackageInfo(packageName);
+                    PackageInfo packageInfo = pm.getPackageInfo(packageName);
 
                     if (packageInfo == null) {
                         Log.w(TAG, "No such package:" + packageName);
@@ -1569,8 +1574,8 @@
                         continue;
                     }
 
-                    if (!pm.isSystemPackage(packageInfo)) {
-                        Log.w(TAG, "Unknown system package:" + packageName);
+                    if (!isSystemOrCertificateMatchingPackage(packageInfo, cert)) {
+                        Log.w(TAG, "Not system or certificate-matching package: " + packageName);
                         XmlUtils.skipCurrentTag(parser);
                         continue;
                     }
@@ -1625,6 +1630,15 @@
         }
     }
 
+    private boolean isSystemOrCertificateMatchingPackage(PackageInfo pi, String cert) {
+        if (cert == null) {
+            return pi.applicationInfo.isSystemApp();
+        }
+
+        return mContext.getPackageManager().hasSigningCertificate(pi.packageName, HexEncoding.
+                decode(cert.replace(":", "")), PackageManager.CERT_INPUT_SHA256);
+    }
+
     private static boolean doesPackageSupportRuntimePermissions(PackageInfo pkg) {
         return pkg.applicationInfo != null
                 && pkg.applicationInfo.targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1;
diff --git a/services/core/java/com/android/server/pm/permission/LegacyPermissionSettings.java b/services/core/java/com/android/server/pm/permission/LegacyPermissionSettings.java
index fc6d202..fe6cd4d 100644
--- a/services/core/java/com/android/server/pm/permission/LegacyPermissionSettings.java
+++ b/services/core/java/com/android/server/pm/permission/LegacyPermissionSettings.java
@@ -28,10 +28,10 @@
 import com.android.modules.utils.TypedXmlSerializer;
 import com.android.server.pm.DumpState;
 import com.android.server.pm.PackageManagerService;
+import com.android.server.pm.PackageManagerTracedLock;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
-import org.xmlpull.v1.XmlSerializer;
 
 import java.io.IOException;
 import java.io.PrintWriter;
@@ -59,11 +59,7 @@
     private final ArrayMap<String, LegacyPermission> mPermissionTrees = new ArrayMap<>();
 
     @NonNull
-    private final Object mLock;
-
-    public LegacyPermissionSettings(@NonNull Object lock) {
-        mLock = lock;
-    }
+    private final PackageManagerTracedLock mLock = new PackageManagerTracedLock();
 
     @NonNull
     public List<LegacyPermission> getPermissions() {
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index 297ad73..b01a89e 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -70,6 +70,7 @@
 import android.util.SparseArray;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.Preconditions;
 import com.android.internal.util.function.TriFunction;
 import com.android.server.LocalServices;
@@ -79,6 +80,8 @@
 import com.android.server.pm.pkg.AndroidPackage;
 import com.android.server.pm.pkg.PackageState;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -153,10 +156,13 @@
         LocalServices.addService(PermissionManagerServiceInternal.class, localService);
         LocalServices.addService(PermissionManagerInternal.class, localService);
 
-        mPermissionManagerServiceImpl = new PermissionManagerServiceImpl(context,
-                availableFeatures);
-        //mPermissionManagerServiceImpl = new PermissionManagerServiceLoggingDecorator(
-        //        LocalServices.getService(PermissionManagerServiceInterface.class));
+        if (PermissionManager.USE_ACCESS_CHECKING_SERVICE) {
+            mPermissionManagerServiceImpl = LocalServices.getService(
+                    PermissionManagerServiceInterface.class);
+        } else {
+            mPermissionManagerServiceImpl = new PermissionManagerServiceImpl(context,
+                    availableFeatures);
+        }
     }
 
     /**
@@ -212,9 +218,12 @@
         }
     }
 
-    private int checkPermission(String pkgName, String permName, @UserIdInt int userId) {
+    @Override
+    @PackageManager.PermissionResult
+    public int checkPermission(String packageName, String permissionName, int deviceId,
+            @UserIdInt int userId) {
         // Not using Objects.requireNonNull() here for compatibility reasons.
-        if (pkgName == null || permName == null) {
+        if (packageName == null || permissionName == null) {
             return PackageManager.PERMISSION_DENIED;
         }
 
@@ -224,15 +233,18 @@
         }
 
         if (checkPermissionDelegate == null) {
-            return mPermissionManagerServiceImpl.checkPermission(pkgName, permName, userId);
+            return mPermissionManagerServiceImpl.checkPermission(
+                    packageName, permissionName, userId);
         }
-        return checkPermissionDelegate.checkPermission(pkgName, permName, userId,
+        return checkPermissionDelegate.checkPermission(packageName, permissionName, userId,
                 mPermissionManagerServiceImpl::checkPermission);
     }
 
-    private int checkUidPermission(int uid, String permName) {
+    @Override
+    @PackageManager.PermissionResult
+    public int checkUidPermission(int uid, String permissionName, int deviceId) {
         // Not using Objects.requireNonNull() here for compatibility reasons.
-        if (permName == null) {
+        if (permissionName == null) {
             return PackageManager.PERMISSION_DENIED;
         }
 
@@ -242,9 +254,9 @@
         }
 
         if (checkPermissionDelegate == null)  {
-            return mPermissionManagerServiceImpl.checkUidPermission(uid, permName);
+            return mPermissionManagerServiceImpl.checkUidPermission(uid, permissionName);
         }
-        return checkPermissionDelegate.checkUidPermission(uid, permName,
+        return checkPermissionDelegate.checkUidPermission(uid, permissionName,
                 mPermissionManagerServiceImpl::checkUidPermission);
     }
 
@@ -390,13 +402,11 @@
         return oneTimePermissionUserManager;
     }
 
+    @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_ONE_TIME_PERMISSION_SESSIONS)
     @Override
     public void startOneTimePermissionSession(String packageName, @UserIdInt int userId,
             long timeoutMillis, long revokeAfterKilledDelayMillis) {
-        mContext.enforceCallingOrSelfPermission(
-                Manifest.permission.MANAGE_ONE_TIME_PERMISSION_SESSIONS,
-                "Must hold " + Manifest.permission.MANAGE_ONE_TIME_PERMISSION_SESSIONS
-                        + " to register permissions as one time.");
+        startOneTimePermissionSession_enforcePermission();
         Objects.requireNonNull(packageName);
 
         final long token = Binder.clearCallingIdentity();
@@ -498,14 +508,15 @@
     }
 
     @Override
-    public int getPermissionFlags(String packageName, String permissionName, int userId) {
+    public int getPermissionFlags(String packageName, String permissionName, int deviceId,
+            int userId) {
         return mPermissionManagerServiceImpl
                 .getPermissionFlags(packageName, permissionName, userId);
     }
 
     @Override
     public void updatePermissionFlags(String packageName, String permissionName, int flagMask,
-            int flagValues, boolean checkAdjustPolicyFlagPermission, int userId) {
+            int flagValues, boolean checkAdjustPolicyFlagPermission, int deviceId, int userId) {
         mPermissionManagerServiceImpl.updatePermissionFlags(packageName, permissionName, flagMask,
                 flagValues, checkAdjustPolicyFlagPermission, userId);
     }
@@ -547,15 +558,16 @@
     }
 
     @Override
-    public void grantRuntimePermission(String packageName, String permissionName, int userId) {
+    public void grantRuntimePermission(String packageName, String permissionName, int deviceId,
+            int userId) {
         mPermissionManagerServiceImpl.grantRuntimePermission(packageName, permissionName, userId);
     }
 
     @Override
-    public void revokeRuntimePermission(String packageName, String permissionName, int userId,
-            String reason) {
-        mPermissionManagerServiceImpl.revokeRuntimePermission(packageName, permissionName, userId,
-                reason);
+    public void revokeRuntimePermission(String packageName, String permissionName, int deviceId,
+            int userId, String reason) {
+        mPermissionManagerServiceImpl.revokeRuntimePermission(packageName, permissionName,
+                userId, reason);
     }
 
     @Override
@@ -566,14 +578,14 @@
 
     @Override
     public boolean shouldShowRequestPermissionRationale(String packageName, String permissionName,
-            int userId) {
+            int deviceId, int userId) {
         return mPermissionManagerServiceImpl.shouldShowRequestPermissionRationale(packageName,
                 permissionName, userId);
     }
 
     @Override
     public boolean isPermissionRevokedByPolicy(String packageName, String permissionName,
-            int userId) {
+            int deviceId, int userId) {
         return mPermissionManagerServiceImpl
                 .isPermissionRevokedByPolicy(packageName, permissionName, userId);
     }
@@ -588,14 +600,14 @@
     private class PermissionManagerServiceInternalImpl implements PermissionManagerServiceInternal {
         @Override
         public int checkPermission(@NonNull String packageName, @NonNull String permissionName,
-                @UserIdInt int userId) {
+                int deviceId, @UserIdInt int userId) {
             return PermissionManagerService.this.checkPermission(packageName, permissionName,
-                    userId);
+                    deviceId, userId);
         }
 
         @Override
-        public int checkUidPermission(int uid, @NonNull String permissionName) {
-            return PermissionManagerService.this.checkUidPermission(uid, permissionName);
+        public int checkUidPermission(int uid, @NonNull String permissionName, int deviceId) {
+            return PermissionManagerService.this.checkUidPermission(uid, permissionName, deviceId);
         }
 
         @Override
@@ -686,6 +698,18 @@
             mPermissionManagerServiceImpl.writeLegacyPermissionsTEMP(legacyPermissionSettings);
         }
 
+        @Nullable
+        @Override
+        public String getDefaultPermissionGrantFingerprint(@UserIdInt int userId) {
+            return mPermissionManagerServiceImpl.getDefaultPermissionGrantFingerprint(userId);
+        }
+
+        @Override
+        public void setDefaultPermissionGrantFingerprint(@NonNull String fingerprint,
+                @UserIdInt int userId) {
+            mPermissionManagerServiceImpl.setDefaultPermissionGrantFingerprint(fingerprint, userId);
+        }
+
         @Override
         public void onPackageAdded(@NonNull PackageState packageState, boolean isInstantApp,
                 @Nullable AndroidPackage oldPkg) {
@@ -722,23 +746,21 @@
         public void onPackageUninstalled(@NonNull String packageName, int appId,
                 @NonNull PackageState packageState, @Nullable AndroidPackage pkg,
                 @NonNull List<AndroidPackage> sharedUserPkgs, @UserIdInt int userId) {
+            if (userId != UserHandle.USER_ALL) {
+                final int[] userIds = getAllUserIds();
+                if (!ArrayUtils.contains(userIds, userId)) {
+                    // This may happen due to DeletePackageHelper.removeUnusedPackagesLPw() calling
+                    // deletePackageX() asynchronously.
+                    Slog.w(LOG_TAG, "Skipping onPackageUninstalled() for non-existent user "
+                            + userId);
+                    return;
+                }
+            }
             mPermissionManagerServiceImpl.onPackageUninstalled(packageName, appId, packageState,
                     pkg, sharedUserPkgs, userId);
         }
 
         @Override
-        public void addOnRuntimePermissionStateChangedListener(
-                OnRuntimePermissionStateChangedListener listener) {
-            mPermissionManagerServiceImpl.addOnRuntimePermissionStateChangedListener(listener);
-        }
-
-        @Override
-        public void removeOnRuntimePermissionStateChangedListener(
-                OnRuntimePermissionStateChangedListener listener) {
-            mPermissionManagerServiceImpl.removeOnRuntimePermissionStateChangedListener(listener);
-        }
-
-        @Override
         public void onSystemReady() {
             mPermissionManagerServiceImpl.onSystemReady();
         }
@@ -791,6 +813,7 @@
         public void resetRuntimePermissions(@NonNull AndroidPackage pkg, @UserIdInt int userId) {
             mPermissionManagerServiceImpl.resetRuntimePermissions(pkg, userId);
         }
+
         @Override
         public void resetRuntimePermissionsForUser(@UserIdInt int userId) {
             mPermissionManagerServiceImpl.resetRuntimePermissionsForUser(userId);
@@ -1207,8 +1230,7 @@
             }
 
             if (!fromDatasource && !checkPermission(context, permissionManagerServiceInt,
-                    permission, attributionSource.getUid(),
-                    attributionSource.getRenouncedPermissions())) {
+                    permission, attributionSource)) {
                 return PermissionChecker.PERMISSION_HARD_DENIED;
             }
 
@@ -1269,12 +1291,11 @@
                     }
                     case AppOpsManager.MODE_DEFAULT: {
                         if (!skipCurrentChecks && !checkPermission(context,
-                                permissionManagerServiceInt, permission, attributionSource.getUid(),
-                                attributionSource.getRenouncedPermissions())) {
+                                permissionManagerServiceInt, permission, attributionSource)) {
                             return PermissionChecker.PERMISSION_HARD_DENIED;
                         }
                         if (next != null && !checkPermission(context, permissionManagerServiceInt,
-                                permission, next.getUid(), next.getRenouncedPermissions())) {
+                                permission, next)) {
                             return PermissionChecker.PERMISSION_HARD_DENIED;
                         }
                     }
@@ -1303,8 +1324,7 @@
             // We consider the chain trusted if the start node has UPDATE_APP_OPS_STATS, and
             // every attributionSource in the chain is registered with the system.
             final boolean isChainStartTrusted = !hasChain || checkPermission(context,
-                    permissionManagerServiceInt, UPDATE_APP_OPS_STATS, current.getUid(),
-                    current.getRenouncedPermissions());
+                    permissionManagerServiceInt, UPDATE_APP_OPS_STATS, current);
 
             while (true) {
                 final boolean skipCurrentChecks = (fromDatasource || next != null);
@@ -1319,12 +1339,12 @@
 
                 // If we already checked the permission for this one, skip the work
                 if (!skipCurrentChecks && !checkPermission(context, permissionManagerServiceInt,
-                        permission, current.getUid(), current.getRenouncedPermissions())) {
+                        permission, current)) {
                     return PermissionChecker.PERMISSION_HARD_DENIED;
                 }
 
                 if (next != null && !checkPermission(context, permissionManagerServiceInt,
-                        permission, next.getUid(), next.getRenouncedPermissions())) {
+                        permission, next)) {
                     return PermissionChecker.PERMISSION_HARD_DENIED;
                 }
 
@@ -1392,9 +1412,13 @@
 
         private static boolean checkPermission(@NonNull Context context,
                 @NonNull PermissionManagerServiceInternal permissionManagerServiceInt,
-                @NonNull String permission, int uid, @NonNull Set<String> renouncedPermissions) {
-            boolean permissionGranted = context.checkPermission(permission, /*pid*/ -1,
-                    uid) == PackageManager.PERMISSION_GRANTED;
+                @NonNull String permission, AttributionSource attributionSource) {
+            int uid = attributionSource.getUid();
+            int deviceId = attributionSource.getDeviceId();
+            final Context deviceContext = context.getDeviceId() == deviceId ? context
+                    : context.createDeviceContext(deviceId);
+            boolean permissionGranted = deviceContext.checkPermission(permission,
+                    Process.INVALID_PID, uid) == PackageManager.PERMISSION_GRANTED;
 
             // Override certain permissions checks for the shared isolated process for both
             // HotwordDetectionService and VisualQueryDetectionService, which ordinarily cannot hold
@@ -1410,10 +1434,10 @@
                 permissionGranted = hotwordServiceProvider != null
                         && uid == hotwordServiceProvider.getUid();
             }
-
+            Set<String> renouncedPermissions = attributionSource.getRenouncedPermissions();
             if (permissionGranted && renouncedPermissions.contains(permission)
-                    && context.checkPermission(Manifest.permission.RENOUNCE_PERMISSIONS,
-                    /*pid*/ -1, uid) == PackageManager.PERMISSION_GRANTED) {
+                    && deviceContext.checkPermission(Manifest.permission.RENOUNCE_PERMISSIONS,
+                        Process.INVALID_PID, uid) == PackageManager.PERMISSION_GRANTED) {
                 return false;
             }
             return permissionGranted;
@@ -1484,8 +1508,7 @@
             // We consider the chain trusted if the start node has UPDATE_APP_OPS_STATS, and
             // every attributionSource in the chain is registered with the system.
             final boolean isChainStartTrusted = !hasChain || checkPermission(context,
-                    permissionManagerServiceInt, UPDATE_APP_OPS_STATS, current.getUid(),
-                    current.getRenouncedPermissions());
+                    permissionManagerServiceInt, UPDATE_APP_OPS_STATS, current);
 
             while (true) {
                 final boolean skipCurrentChecks = (next != null);
@@ -1777,4 +1800,10 @@
             return false;
         }
     }
+
+    @Override
+    protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter writer,
+            @Nullable String[] args) {
+        mPermissionManagerServiceImpl.dump(fd, writer, args);
+    }
 }
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 2499529..de7b687 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
@@ -121,10 +121,8 @@
 import com.android.internal.os.RoSystemProperties;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.CollectionUtils;
-import com.android.internal.util.DumpUtils;
 import com.android.internal.util.IntPair;
 import com.android.internal.util.Preconditions;
-import com.android.internal.util.function.pooled.PooledLambda;
 import com.android.server.FgThread;
 import com.android.server.LocalServices;
 import com.android.server.PermissionThread;
@@ -134,6 +132,7 @@
 import com.android.server.pm.ApexManager;
 import com.android.server.pm.KnownPackages;
 import com.android.server.pm.PackageInstallerService;
+import com.android.server.pm.PackageManagerTracedLock;
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.pm.UserManagerService;
 import com.android.server.pm.parsing.PackageInfoUtils;
@@ -253,7 +252,7 @@
             new ArraySet<>();
 
     /** Lock to protect internal data access */
-    private final Object mLock = new Object();
+    private final PackageManagerTracedLock mLock = new PackageManagerTracedLock();
 
     /** Internal connection to the package manager */
     private final PackageManagerInternal mPackageManagerInt;
@@ -310,12 +309,6 @@
     @GuardedBy("mLock")
     private final SparseBooleanArray mHasNoDelayedPermBackup = new SparseBooleanArray();
 
-    /** Listeners for permission state (granting and flags) changes */
-    @GuardedBy("mLock")
-    private final ArrayList<PermissionManagerServiceInternal
-            .OnRuntimePermissionStateChangedListener>
-            mRuntimePermissionStateChangedListeners = new ArrayList<>();
-
     private final boolean mIsLeanback;
 
     @NonNull
@@ -394,7 +387,11 @@
             mPackageManagerInt.writeSettings(true);
         }
         @Override
-        public void onPermissionUpdated(int[] userIds, boolean sync) {
+        public void onPermissionUpdated(int[] userIds, boolean sync, int appId) {
+            for (int i = 0; i < userIds.length; i++) {
+                int uid = UserHandle.getUid(userIds[i], appId);
+                mOnPermissionChangeListeners.onPermissionsChanged(uid);
+            }
             mPackageManagerInt.writePermissionSettings(userIds, !sync);
         }
         @Override
@@ -405,18 +402,6 @@
         public void onPermissionRemoved() {
             mPackageManagerInt.writeSettings(false);
         }
-        public void onPermissionUpdatedNotifyListener(@UserIdInt int[] updatedUserIds, boolean sync,
-                int uid) {
-            onPermissionUpdated(updatedUserIds, sync);
-            for (int i = 0; i < updatedUserIds.length; i++) {
-                int userUid = UserHandle.getUid(updatedUserIds[i], UserHandle.getAppId(uid));
-                mOnPermissionChangeListeners.onPermissionsChanged(userUid);
-            }
-        }
-        public void onInstallPermissionUpdatedNotifyListener(int uid) {
-            onInstallPermissionUpdated();
-            mOnPermissionChangeListeners.onPermissionsChanged(uid);
-        }
     };
 
     public PermissionManagerServiceImpl(@NonNull Context context,
@@ -475,13 +460,7 @@
     }
 
     @Override
-    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) {
-            return;
-        }
-
-        mContext.getSystemService(PermissionControllerManager.class).dump(fd, args);
-    }
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {}
 
     /**
      * This method should typically only be used when granting or revoking
@@ -816,12 +795,6 @@
             flagValues &= ~FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT;
             flagValues &= ~FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT;
             flagValues &= ~PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION;
-            // REVIEW_REQUIRED can be set on any permission by the shell or the root uid, or by
-            // any app for the POST_NOTIFICATIONS permission specifically.
-            if (!POST_NOTIFICATIONS.equals(permName) && callingUid != Process.SHELL_UID
-                    && callingUid != Process.ROOT_UID) {
-                flagValues &= ~PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED;
-            }
         }
 
         final AndroidPackage pkg = mPackageManagerInt.getPackage(packageName);
@@ -878,17 +851,13 @@
             permissionUpdated = uidState.updatePermissionFlags(bp, flagMask, flagValues);
         }
 
-        if (permissionUpdated && isRuntimePermission) {
-            notifyRuntimePermissionStateChanged(packageName, userId);
-        }
         if (permissionUpdated && callback != null) {
             // Install and runtime permissions are stored in different places,
             // so figure out what permission changed and persist the change.
             if (!isRuntimePermission) {
-                int userUid = UserHandle.getUid(userId, pkg.getUid());
-                callback.onInstallPermissionUpdatedNotifyListener(userUid);
+                callback.onInstallPermissionUpdated();
             } else {
-                callback.onPermissionUpdatedNotifyListener(new int[]{userId}, false, pkg.getUid());
+                callback.onPermissionUpdated(new int[]{ userId }, false, pkg.getUid());
             }
         }
     }
@@ -1150,9 +1119,7 @@
 
             ArrayList<String> allowlistedPermissions = null;
 
-            final int permissionCount = ArrayUtils.size(pkg.getRequestedPermissions());
-            for (int i = 0; i < permissionCount; i++) {
-                final String permissionName = pkg.getRequestedPermissions().get(i);
+            for (final String permissionName : pkg.getRequestedPermissions()) {
                 final int currentFlags =
                         uidState.getPermissionFlags(permissionName);
                 if ((currentFlags & queryFlags) != 0) {
@@ -1498,10 +1465,6 @@
                 callback.onGidsChanged(UserHandle.getAppId(pkg.getUid()), userId);
             }
         }
-
-        if (isRuntimePermission) {
-            notifyRuntimePermissionStateChanged(packageName, userId);
-        }
     }
 
     @Override
@@ -1656,10 +1619,6 @@
                 mDefaultPermissionCallback.onInstallPermissionRevoked();
             }
         }
-
-        if (isRuntimePermission) {
-            notifyRuntimePermissionStateChanged(packageName, userId);
-        }
     }
 
     private boolean mayManageRolePermission(int uid) {
@@ -1715,8 +1674,9 @@
                 mDefaultPermissionCallback.onInstallPermissionRevoked();
             }
 
-            public void onPermissionUpdated(int[] updatedUserIds, boolean sync) {
-                for (int userId : updatedUserIds) {
+            public void onPermissionUpdated(int[] userIds, boolean sync, int appId) {
+                mOnPermissionChangeListeners.onPermissionsChanged(appId);
+                for (int userId : userIds) {
                     if (sync) {
                         syncUpdatedUsers.add(userId);
                         asyncUpdatedUsers.remove(userId);
@@ -1736,16 +1696,6 @@
             public void onInstallPermissionUpdated() {
                 mDefaultPermissionCallback.onInstallPermissionUpdated();
             }
-
-            public void onPermissionUpdatedNotifyListener(@UserIdInt int[] updatedUserIds,
-                    boolean sync, int uid) {
-                onPermissionUpdated(updatedUserIds, sync);
-                mOnPermissionChangeListeners.onPermissionsChanged(uid);
-            }
-
-            public void onInstallPermissionUpdatedNotifyListener(int uid) {
-                mDefaultPermissionCallback.onInstallPermissionUpdatedNotifyListener(uid);
-            }
         };
 
         if (filterPkg != null) {
@@ -1796,10 +1746,11 @@
                 | FLAG_PERMISSION_POLICY_FIXED;
 
         final String packageName = pkg.getPackageName();
-        final int permissionCount = ArrayUtils.size(pkg.getRequestedPermissions());
-        for (int i = 0; i < permissionCount; i++) {
-            final String permName = pkg.getRequestedPermissions().get(i);
-
+        for (final String permName : pkg.getRequestedPermissions()) {
+            if (mIsLeanback && NOTIFICATION_PERMISSIONS.contains(permName)) {
+                // Do not reset the Notification permissions on TV
+                continue;
+            }
             final boolean isRuntimePermission;
             synchronized (mLock) {
                 final Permission permission = mRegistry.getPermission(permName);
@@ -2073,45 +2024,6 @@
                 });
     }
 
-    @Override
-    public void addOnRuntimePermissionStateChangedListener(
-            PermissionManagerServiceInternal.OnRuntimePermissionStateChangedListener listener) {
-        synchronized (mLock) {
-            mRuntimePermissionStateChangedListeners.add(listener);
-        }
-    }
-
-    @Override
-    public void removeOnRuntimePermissionStateChangedListener(
-            PermissionManagerServiceInternal.OnRuntimePermissionStateChangedListener listener) {
-        synchronized (mLock) {
-            mRuntimePermissionStateChangedListeners.remove(listener);
-        }
-    }
-
-    private void notifyRuntimePermissionStateChanged(@NonNull String packageName,
-            @UserIdInt int userId) {
-        FgThread.getHandler().sendMessage(PooledLambda.obtainMessage(
-                PermissionManagerServiceImpl::doNotifyRuntimePermissionStateChanged,
-                PermissionManagerServiceImpl.this, packageName, userId));
-    }
-
-    private void doNotifyRuntimePermissionStateChanged(@NonNull String packageName,
-            @UserIdInt int userId) {
-        final ArrayList<PermissionManagerServiceInternal.OnRuntimePermissionStateChangedListener>
-                listeners;
-        synchronized (mLock) {
-            if (mRuntimePermissionStateChangedListeners.isEmpty()) {
-                return;
-            }
-            listeners = new ArrayList<>(mRuntimePermissionStateChangedListeners);
-        }
-        final int listenerCount = listeners.size();
-        for (int i = 0; i < listenerCount; i++) {
-            listeners.get(i).onRuntimePermissionStateChanged(packageName, userId);
-        }
-    }
-
     /**
      * If the app is updated, and has scoped storage permissions, then it is possible that the
      * app updated in an attempt to get unscoped storage. If so, revoke all storage permissions.
@@ -2134,11 +2046,9 @@
 
         final int callingUid = Binder.getCallingUid();
         for (int userId: getAllUserIds()) {
-            int numRequestedPermissions = newPackage.getRequestedPermissions().size();
-            for (int i = 0; i < numRequestedPermissions; i++) {
-                PermissionInfo permInfo = getPermissionInfo(
-                        newPackage.getRequestedPermissions().get(i),
-                        0, newPackage.getPackageName());
+            for (final String permName : newPackage.getRequestedPermissions()) {
+                PermissionInfo permInfo = getPermissionInfo(permName, 0,
+                        newPackage.getPackageName());
                 if (permInfo == null) {
                     continue;
                 }
@@ -2470,10 +2380,8 @@
                 if (DEBUG_REMOVE) Log.d(TAG, "  Permissions: " + r);
             }
 
-            n = pkg.getRequestedPermissions().size();
             r = null;
-            for (int i = 0; i < n; i++) {
-                final String permissionName = pkg.getRequestedPermissions().get(i);
+            for (final String permissionName : pkg.getRequestedPermissions()) {
                 final Permission permission = mRegistry.getPermission(permissionName);
                 if (permission != null && permission.isAppOp()) {
                     mRegistry.removeAppOpPermissionPackage(permissionName,
@@ -2591,11 +2499,8 @@
         ArraySet<String> shouldGrantSignaturePermission = null;
         ArraySet<String> shouldGrantInternalPermission = null;
         ArraySet<String> shouldGrantPrivilegedPermissionIfWasGranted = new ArraySet<>();
-        final List<String> requestedPermissions = pkg.getRequestedPermissions();
-        final int requestedPermissionsSize = requestedPermissions.size();
-        for (int i = 0; i < requestedPermissionsSize; i++) {
-            final String permissionName = pkg.getRequestedPermissions().get(i);
-
+        final Set<String> requestedPermissions = pkg.getRequestedPermissions();
+        for (final String permissionName : pkg.getRequestedPermissions()) {
             final Permission permission;
             synchronized (mLock) {
                 permission = mRegistry.getPermission(permissionName);
@@ -2726,9 +2631,7 @@
                 ArraySet<String> newImplicitPermissions = new ArraySet<>();
                 final String friendlyName = pkg.getPackageName() + "(" + pkg.getUid() + ")";
 
-                for (int i = 0; i < requestedPermissionsSize; i++) {
-                    final String permName = requestedPermissions.get(i);
-
+                for (final String permName : requestedPermissions) {
                     final Permission bp = mRegistry.getPermission(permName);
                     final boolean appSupportsRuntimePermissions =
                             pkg.getTargetSdkVersion() >= Build.VERSION_CODES.M;
@@ -3025,11 +2928,7 @@
         if (callback != null) {
             callback.onPermissionUpdated(updatedUserIds,
                     (changingPackageName != null && replace && installPermissionsChanged)
-                            || runtimePermissionsRevoked);
-        }
-
-        for (int userId : updatedUserIds) {
-            notifyRuntimePermissionStateChanged(pkg.getPackageName(), userId);
+                            || runtimePermissionsRevoked, pkg.getUid());
         }
     }
 
@@ -3728,12 +3627,9 @@
             @UserIdInt int userId) {
         ArraySet<String> oldGrantedRestrictedPermissions = null;
         boolean updatePermissions = false;
-        final int permissionCount = pkg.getRequestedPermissions().size();
         final int myUid = Process.myUid();
 
-        for (int j = 0; j < permissionCount; j++) {
-            final String permissionName = pkg.getRequestedPermissions().get(j);
-
+        for (final String permissionName : pkg.getRequestedPermissions()) {
             final boolean isGranted;
             synchronized (mLock) {
                 final Permission bp = mRegistry.getPermission(permissionName);
@@ -3856,7 +3752,8 @@
                     isGranted = uidState.isPermissionGranted(permissionName);
                 }
                 if (!isGranted) {
-                    mDefaultPermissionCallback.onPermissionRevoked(pkg.getUid(), userId, null);
+                    mDefaultPermissionCallback.onPermissionRevoked(
+                            UserHandle.getUid(userId, pkg.getUid()), userId, null);
                     break;
                 }
             }
@@ -4700,6 +4597,19 @@
         }
     }
 
+    @Nullable
+    @Override
+    public String getDefaultPermissionGrantFingerprint(@UserIdInt int userId) {
+        return mPackageManagerInt.isPermissionUpgradeNeeded(userId) ? null : Build.FINGERPRINT;
+    }
+
+    @Override
+    public void setDefaultPermissionGrantFingerprint(@NonNull String fingerprint,
+            @UserIdInt int userId) {
+        // Ignored - default permission grant here shares the same version with runtime permission
+        // upgrade, and the new version is set by that later.
+    }
+
     private void onPackageAddedInternal(@NonNull PackageState packageState,
             @NonNull AndroidPackage pkg, boolean isInstantApp, @Nullable AndroidPackage oldPkg) {
         if (!pkg.getAdoptPermissions().isEmpty()) {
@@ -5350,25 +5260,14 @@
         public void onPermissionGranted(int uid, @UserIdInt int userId) {}
         public void onInstallPermissionGranted() {}
         public void onPermissionRevoked(int uid, @UserIdInt int userId, String reason) {
-            onPermissionRevoked(uid, userId, reason, false);
-        }
-        public void onPermissionRevoked(int uid, @UserIdInt int userId, String reason,
-                boolean overrideKill) {
             onPermissionRevoked(uid, userId, reason, false, null);
         }
         public void onPermissionRevoked(int uid, @UserIdInt int userId, String reason,
                 boolean overrideKill, @Nullable String permissionName) {}
         public void onInstallPermissionRevoked() {}
-        public void onPermissionUpdated(@UserIdInt int[] updatedUserIds, boolean sync) {}
-        public void onPermissionUpdatedNotifyListener(@UserIdInt int[] updatedUserIds, boolean sync,
-                int uid) {
-            onPermissionUpdated(updatedUserIds, sync);
-        }
+        public void onPermissionUpdated(@UserIdInt int[] userIds, boolean sync, int appId) {}
         public void onPermissionRemoved() {}
         public void onInstallPermissionUpdated() {}
-        public void onInstallPermissionUpdatedNotifyListener(int uid) {
-            onInstallPermissionUpdated();
-        }
     }
 
     private static final class OnPermissionChangeListeners extends Handler {
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java
index b62c64b..128f847 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java
@@ -383,24 +383,6 @@
     int checkUidPermission(int uid, String permName);
 
     /**
-     * Adds a listener for runtime permission state (permissions or flags) changes.
-     *
-     * @param listener The listener.
-     */
-    void addOnRuntimePermissionStateChangedListener(
-            @NonNull PermissionManagerServiceInternal
-                    .OnRuntimePermissionStateChangedListener listener);
-
-    /**
-     * Removes a listener for runtime permission state (permissions or flags) changes.
-     *
-     * @param listener The listener.
-     */
-    void removeOnRuntimePermissionStateChangedListener(
-            @NonNull PermissionManagerServiceInternal
-                    .OnRuntimePermissionStateChangedListener listener);
-
-    /**
      * Get all the package names requesting app op permissions.
      *
      * @return a map of app op permission names to package names requesting them
@@ -541,6 +523,17 @@
     void writeLegacyPermissionsTEMP(@NonNull LegacyPermissionSettings legacyPermissionSettings);
 
     /**
+     * Get the fingerprint for default permission grants.
+     */
+    @Nullable
+    String getDefaultPermissionGrantFingerprint(@UserIdInt int userId);
+
+    /**
+     * Set the fingerprint for default permission grants.
+     */
+    void setDefaultPermissionGrantFingerprint(@NonNull String fingerprint, @UserIdInt int userId);
+
+    /**
      * Callback when the system is ready.
      */
     void onSystemReady();
@@ -621,6 +614,6 @@
      * @param userId the user ID the package is uninstalled for
      */
     void onPackageUninstalled(@NonNull String packageName, int appId,
-            @NonNull PackageState packageState, @NonNull AndroidPackage pkg,
+            @NonNull PackageState packageState, @Nullable AndroidPackage pkg,
             @NonNull List<AndroidPackage> sharedUserPkgs, @UserIdInt int userId);
 }
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
index 4dd6966..98adeb6 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
@@ -46,12 +46,13 @@
      *
      * @param packageName the name of the package you are checking against
      * @param permissionName the name of the permission you are checking for
+     * @param deviceId the device ID
      * @param userId the user ID
      * @return {@code PERMISSION_GRANTED} if the permission is granted, or {@code PERMISSION_DENIED}
      *         otherwise
      */
     //@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
-    int checkPermission(@NonNull String packageName, @NonNull String permissionName,
+    int checkPermission(@NonNull String packageName, @NonNull String permissionName, int deviceId,
             @UserIdInt int userId);
 
     /**
@@ -59,27 +60,12 @@
      *
      * @param uid the UID
      * @param permissionName the name of the permission you are checking for
+     * @param deviceId the device for which you are checking the permission
      * @return {@code PERMISSION_GRANTED} if the permission is granted, or {@code PERMISSION_DENIED}
      *         otherwise
      */
     //@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
-    int checkUidPermission(int uid, @NonNull String permissionName);
-
-    /**
-     * Adds a listener for runtime permission state (permissions or flags) changes.
-     *
-     * @param listener The listener.
-     */
-    void addOnRuntimePermissionStateChangedListener(
-            @NonNull OnRuntimePermissionStateChangedListener listener);
-
-    /**
-     * Removes a listener for runtime permission state (permissions or flags) changes.
-     *
-     * @param listener The listener.
-     */
-    void removeOnRuntimePermissionStateChangedListener(
-            @NonNull OnRuntimePermissionStateChangedListener listener);
+    int checkUidPermission(int uid, @NonNull String permissionName, int deviceId);
 
     /**
      * Get whether permission review is required for a package.
@@ -89,8 +75,7 @@
      * @return whether permission review is required
      */
     //@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
-    boolean isPermissionsReviewRequired(@NonNull String packageName,
-            @UserIdInt int userId);
+    boolean isPermissionsReviewRequired(@NonNull String packageName, @UserIdInt int userId);
 
     /**
      * Reset the runtime permission state changes for a package.
@@ -101,8 +86,7 @@
      * @param userId the user ID
      */
     //@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
-    void resetRuntimePermissions(@NonNull AndroidPackage pkg,
-            @UserIdInt int userId);
+    void resetRuntimePermissions(@NonNull AndroidPackage pkg, @UserIdInt int userId);
 
     /**
      * Reset the runtime permission state changes for all packages in a user.
@@ -231,6 +215,17 @@
     void writeLegacyPermissionsTEMP(@NonNull LegacyPermissionSettings legacyPermissionSettings);
 
     /**
+     * Get the fingerprint for default permission grants.
+     */
+    @Nullable
+    String getDefaultPermissionGrantFingerprint(@UserIdInt int userId);
+
+    /**
+     * Set the fingerprint for default permission grants.
+     */
+    void setDefaultPermissionGrantFingerprint(@NonNull String fingerprint, @UserIdInt int userId);
+
+    /**
      * Callback when the system is ready.
      */
     //@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
@@ -313,22 +308,6 @@
             @NonNull List<AndroidPackage> sharedUserPkgs, @UserIdInt int userId);
 
     /**
-     * Listener for package permission state (permissions or flags) changes.
-     */
-    interface OnRuntimePermissionStateChangedListener {
-
-        /**
-         * Called when the runtime permission state (permissions or flags) changed.
-         *
-         * @param packageName The package for which the change happened.
-         * @param userId the user id for which the change happened.
-         */
-        @Nullable
-        void onRuntimePermissionStateChanged(@NonNull String packageName,
-                @UserIdInt int userId);
-    }
-
-    /**
      * The permission-related parameters passed in for package installation.
      *
      * @see SessionParams
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceLoggingDecorator.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceLoggingDecorator.java
index a6fa304..7f98e21 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceLoggingDecorator.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceLoggingDecorator.java
@@ -238,23 +238,6 @@
     }
 
     @Override
-    public void addOnRuntimePermissionStateChangedListener(
-            @NonNull PermissionManagerServiceInternal
-                    .OnRuntimePermissionStateChangedListener listener) {
-        Log.i(LOG_TAG, "addOnRuntimePermissionStateChangedListener(listener = " + listener + ")");
-        mService.addOnRuntimePermissionStateChangedListener(listener);
-    }
-
-    @Override
-    public void removeOnRuntimePermissionStateChangedListener(
-            @NonNull PermissionManagerServiceInternal
-                    .OnRuntimePermissionStateChangedListener listener) {
-        Log.i(LOG_TAG, "removeOnRuntimePermissionStateChangedListener(listener = " + listener
-                + ")");
-        mService.removeOnRuntimePermissionStateChangedListener(listener);
-    }
-
-    @Override
     public Map<String, Set<String>> getAllAppOpPermissionPackages() {
         Log.i(LOG_TAG, "getAllAppOpPermissionPackages()");
         return mService.getAllAppOpPermissionPackages();
@@ -373,6 +356,20 @@
         mService.writeLegacyPermissionsTEMP(legacyPermissionSettings);
     }
 
+    @Nullable
+    @Override
+    public String getDefaultPermissionGrantFingerprint(int userId) {
+        Log.i(LOG_TAG, "getDefaultPermissionGrantFingerprint(userId = " + userId + ")");
+        return mService.getDefaultPermissionGrantFingerprint(userId);
+    }
+
+    @Override
+    public void setDefaultPermissionGrantFingerprint(@NonNull String fingerprint, int userId) {
+        Log.i(LOG_TAG, "setDefaultPermissionGrantFingerprint(fingerprint = " + fingerprint
+                + ", userId = " + userId + ")");
+        mService.setDefaultPermissionGrantFingerprint(fingerprint, userId);
+    }
+
     @Override
     public void onSystemReady() {
         Log.i(LOG_TAG, "onSystemReady()");
@@ -429,7 +426,7 @@
 
     @Override
     public void onPackageUninstalled(@NonNull String packageName, int appId,
-            @NonNull PackageState packageState, @NonNull AndroidPackage pkg,
+            @NonNull PackageState packageState, @Nullable AndroidPackage pkg,
             @NonNull List<AndroidPackage> sharedUserPkgs, int userId) {
         Log.i(LOG_TAG, "onPackageUninstalled(packageName = " + packageName + ", appId = " + appId
                 + ", packageState = " + packageState + ", pkg = " + pkg + ", sharedUserPkgs = "
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceTestingShim.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceTestingShim.java
new file mode 100644
index 0000000..d4c6d42
--- /dev/null
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceTestingShim.java
@@ -0,0 +1,572 @@
+/*
+ * 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.pm.permission;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.content.pm.PermissionGroupInfo;
+import android.content.pm.PermissionInfo;
+import android.content.pm.permission.SplitPermissionInfoParcelable;
+import android.os.Build;
+import android.permission.IOnPermissionsChangeListener;
+
+import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.PackageState;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * A testing shim, which supports running two variants of a PermissionManagerServiceInterface at
+ * once, and checking the results of both.
+ */
+public class PermissionManagerServiceTestingShim implements PermissionManagerServiceInterface {
+
+    private PermissionManagerServiceInterface mOldImplementation;
+    private PermissionManagerServiceInterface mNewImplementation;
+
+    public PermissionManagerServiceTestingShim(PermissionManagerServiceInterface oldImpl,
+            PermissionManagerServiceInterface newImpl) {
+        mOldImplementation = oldImpl;
+        mNewImplementation = newImpl;
+    }
+
+    private void signalImplDifference(String message) {
+        //TODO b/252886104 implement
+    }
+
+
+    @Nullable
+    @Override
+    public byte[] backupRuntimePermissions(int userId) {
+        byte[] oldVal = mOldImplementation.backupRuntimePermissions(userId);
+        byte[] newVal = mNewImplementation.backupRuntimePermissions(userId);
+        if (!Arrays.equals(oldVal, newVal)) {
+            signalImplDifference("backupRuntimePermissions");
+        }
+
+        return newVal;
+    }
+
+    @Override
+    public void restoreRuntimePermissions(@NonNull byte[] backup, int userId) {
+        mOldImplementation.backupRuntimePermissions(userId);
+        mNewImplementation.backupRuntimePermissions(userId);
+    }
+
+    @Override
+    public void restoreDelayedRuntimePermissions(@NonNull String packageName, int userId) {
+        mOldImplementation.restoreDelayedRuntimePermissions(packageName, userId);
+        mNewImplementation.restoreDelayedRuntimePermissions(packageName, userId);
+
+    }
+
+    @Override
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        mOldImplementation.dump(fd, pw, args);
+        mNewImplementation.dump(fd, pw, args);
+    }
+
+    @Override
+    public List<PermissionGroupInfo> getAllPermissionGroups(int flags) {
+        List<PermissionGroupInfo> oldVal = mOldImplementation.getAllPermissionGroups(flags);
+        List<PermissionGroupInfo> newVal = mNewImplementation.getAllPermissionGroups(flags);
+
+        if (!Objects.equals(oldVal, newVal)) {
+            signalImplDifference("getAllPermissionGroups");
+        }
+        return newVal;
+    }
+
+    @Override
+    public PermissionGroupInfo getPermissionGroupInfo(String groupName, int flags) {
+        PermissionGroupInfo oldVal = mOldImplementation.getPermissionGroupInfo(groupName, flags);
+        PermissionGroupInfo newVal = mNewImplementation.getPermissionGroupInfo(groupName, flags);
+
+        if (!Objects.equals(oldVal, newVal)) {
+            signalImplDifference("getPermissionGroupInfo");
+        }
+        return newVal;
+    }
+
+    @Override
+    public PermissionInfo getPermissionInfo(@NonNull String permName, int flags,
+            @NonNull String opPackageName) {
+        PermissionInfo oldVal = mOldImplementation.getPermissionInfo(permName, flags,
+                opPackageName);
+        PermissionInfo newVal = mNewImplementation.getPermissionInfo(permName, flags,
+                opPackageName);
+
+        if (!Objects.equals(oldVal, newVal)) {
+            signalImplDifference("getPermissionInfo");
+        }
+        return newVal;
+    }
+
+    @Override
+    public List<PermissionInfo> queryPermissionsByGroup(String groupName, int flags) {
+        List<PermissionInfo> oldVal = mOldImplementation.queryPermissionsByGroup(groupName,
+                flags);
+        List<PermissionInfo> newVal = mNewImplementation.queryPermissionsByGroup(groupName, flags);
+
+        if (!Objects.equals(oldVal, newVal)) {
+            signalImplDifference("queryPermissionsByGroup");
+        }
+        return newVal;
+    }
+
+    @Override
+    public boolean addPermission(PermissionInfo info, boolean async) {
+        boolean oldVal = mOldImplementation.addPermission(info, async);
+        boolean newVal = mNewImplementation.addPermission(info, async);
+
+        if (!Objects.equals(oldVal, newVal)) {
+            signalImplDifference("addPermission");
+        }
+        return newVal;
+    }
+
+    @Override
+    public void removePermission(String permName) {
+        mOldImplementation.removePermission(permName);
+        mNewImplementation.removePermission(permName);
+    }
+
+    @Override
+    public int getPermissionFlags(String packageName, String permName, int userId) {
+        int oldVal = mOldImplementation.getPermissionFlags(packageName, permName, userId);
+        int newVal = mNewImplementation.getPermissionFlags(packageName, permName, userId);
+
+        if (!Objects.equals(oldVal, newVal)) {
+            signalImplDifference("getPermissionFlags");
+        }
+        return newVal;
+    }
+
+    @Override
+    public void updatePermissionFlags(String packageName, String permName, int flagMask,
+            int flagValues, boolean checkAdjustPolicyFlagPermission, int userId) {
+        mOldImplementation.updatePermissionFlags(packageName, permName, flagMask, flagValues,
+                checkAdjustPolicyFlagPermission, userId);
+        mNewImplementation.updatePermissionFlags(packageName, permName, flagMask, flagValues,
+                checkAdjustPolicyFlagPermission, userId);
+    }
+
+    @Override
+    public void updatePermissionFlagsForAllApps(int flagMask, int flagValues, int userId) {
+        mOldImplementation.updatePermissionFlagsForAllApps(flagMask, flagValues, userId);
+        mNewImplementation.updatePermissionFlagsForAllApps(flagMask, flagValues, userId);
+    }
+
+    @Override
+    public void addOnPermissionsChangeListener(IOnPermissionsChangeListener listener) {
+        mOldImplementation.addOnPermissionsChangeListener(listener);
+        mNewImplementation.addOnPermissionsChangeListener(listener);
+    }
+
+    @Override
+    public void removeOnPermissionsChangeListener(IOnPermissionsChangeListener listener) {
+        mOldImplementation.removeOnPermissionsChangeListener(listener);
+        mNewImplementation.removeOnPermissionsChangeListener(listener);
+    }
+
+    @Override
+    public boolean addAllowlistedRestrictedPermission(@NonNull String packageName,
+            @NonNull String permName, int flags, int userId) {
+        boolean oldVal = mOldImplementation.addAllowlistedRestrictedPermission(packageName,
+                permName,
+                flags, userId);
+        boolean newVal = mNewImplementation.addAllowlistedRestrictedPermission(packageName,
+                permName, flags, userId);
+
+        if (!Objects.equals(oldVal, newVal)) {
+            signalImplDifference("addAllowlistedRestrictedPermission");
+        }
+        return newVal;
+    }
+
+    @Override
+    public List<String> getAllowlistedRestrictedPermissions(@NonNull String packageName, int flags,
+            int userId) {
+        List<String> oldVal = mOldImplementation.getAllowlistedRestrictedPermissions(packageName,
+                flags, userId);
+        List<String> newVal = mNewImplementation.getAllowlistedRestrictedPermissions(packageName,
+                flags, userId);
+
+        if (!Objects.equals(oldVal, newVal)) {
+            signalImplDifference("getAllowlistedRestrictedPermissions");
+        }
+        return newVal;
+    }
+
+    @Override
+    public boolean removeAllowlistedRestrictedPermission(@NonNull String packageName,
+            @NonNull String permName, int flags, int userId) {
+        boolean oldVal = mOldImplementation.removeAllowlistedRestrictedPermission(packageName,
+                permName, flags, userId);
+        boolean newVal = mNewImplementation.removeAllowlistedRestrictedPermission(packageName,
+                permName, flags, userId);
+
+        if (!Objects.equals(oldVal, newVal)) {
+            signalImplDifference("removeAllowlistedRestrictedPermission");
+        }
+        return newVal;
+    }
+
+    @Override
+    public void grantRuntimePermission(String packageName, String permName, int userId) {
+        mOldImplementation.grantRuntimePermission(packageName, permName, userId);
+        mNewImplementation.grantRuntimePermission(packageName, permName, userId);
+    }
+
+    @Override
+    public void revokeRuntimePermission(String packageName, String permName, int userId,
+            String reason) {
+        mOldImplementation.grantRuntimePermission(packageName, permName, userId);
+        mNewImplementation.grantRuntimePermission(packageName, permName, userId);
+    }
+
+    @Override
+    public void revokePostNotificationPermissionWithoutKillForTest(String packageName, int userId) {
+        mOldImplementation.revokePostNotificationPermissionWithoutKillForTest(packageName,
+                userId);
+        mNewImplementation.revokePostNotificationPermissionWithoutKillForTest(packageName, userId);
+    }
+
+    @Override
+    public boolean shouldShowRequestPermissionRationale(String packageName, String permName,
+            int userId) {
+        boolean oldVal = mOldImplementation
+                .shouldShowRequestPermissionRationale(packageName, permName, userId);
+        boolean newVal = mNewImplementation
+                .shouldShowRequestPermissionRationale(packageName, permName, userId);
+
+        if (!Objects.equals(oldVal, newVal)) {
+            signalImplDifference("shouldShowRequestPermissionRationale");
+        }
+        return newVal;
+    }
+
+    @Override
+    public boolean isPermissionRevokedByPolicy(String packageName, String permName, int userId) {
+        boolean oldVal = mOldImplementation
+                .isPermissionRevokedByPolicy(packageName, permName, userId);
+        boolean newVal = mNewImplementation.isPermissionRevokedByPolicy(packageName, permName,
+                userId);
+
+        if (!Objects.equals(oldVal, newVal)) {
+            signalImplDifference("isPermissionRevokedByPolicy");
+        }
+        return newVal;
+    }
+
+    @Override
+    public List<SplitPermissionInfoParcelable> getSplitPermissions() {
+        List<SplitPermissionInfoParcelable> oldVal = mOldImplementation.getSplitPermissions();
+        List<SplitPermissionInfoParcelable> newVal = mNewImplementation.getSplitPermissions();
+
+        if (!Objects.equals(oldVal, newVal)) {
+            signalImplDifference("getSplitPermissions");
+        }
+        return newVal;
+    }
+
+    @Override
+    public int checkPermission(String pkgName, String permName, int userId) {
+        int oldVal = mOldImplementation.checkPermission(pkgName, permName, userId);
+        int newVal = mNewImplementation.checkPermission(pkgName, permName, userId);
+
+        if (!Objects.equals(oldVal, newVal)) {
+            signalImplDifference("checkPermission");
+        }
+        return newVal;
+    }
+
+    @Override
+    public int checkUidPermission(int uid, String permName) {
+        int oldVal = mOldImplementation.checkUidPermission(uid, permName);
+        int newVal = mNewImplementation.checkUidPermission(uid, permName);
+
+        if (!Objects.equals(oldVal, newVal)) {
+            signalImplDifference("checkUidPermission");
+        }
+        return newVal;
+    }
+
+    @Override
+    public Map<String, Set<String>> getAllAppOpPermissionPackages() {
+        Map<String, Set<String>> oldVal = mOldImplementation.getAllAppOpPermissionPackages();
+        Map<String, Set<String>> newVal = mNewImplementation.getAllAppOpPermissionPackages();
+
+        if (!Objects.equals(oldVal, newVal)) {
+            signalImplDifference("getAllAppOpPermissionPackages");
+        }
+        return newVal;
+    }
+
+    @Override
+    public boolean isPermissionsReviewRequired(@NonNull String packageName, int userId) {
+        boolean oldVal = mOldImplementation.isPermissionsReviewRequired(packageName, userId);
+        boolean newVal = mNewImplementation.isPermissionsReviewRequired(packageName, userId);
+
+        if (!Objects.equals(oldVal, newVal)) {
+            signalImplDifference("isPermissionsReviewRequired");
+        }
+        return newVal;
+    }
+
+    @Override
+    public void resetRuntimePermissions(@NonNull AndroidPackage pkg, @UserIdInt int userId) {
+        mOldImplementation.resetRuntimePermissions(pkg, userId);
+        mNewImplementation.resetRuntimePermissions(pkg, userId);
+    }
+
+    @Override
+    public void resetRuntimePermissionsForUser(int userId) {
+        mOldImplementation.resetRuntimePermissionsForUser(userId);
+        mNewImplementation.resetRuntimePermissionsForUser(userId);
+    }
+
+    @Override
+    public void readLegacyPermissionStateTEMP() {
+        mOldImplementation.readLegacyPermissionStateTEMP();
+        mNewImplementation.readLegacyPermissionStateTEMP();
+    }
+
+    @Override
+    public void writeLegacyPermissionStateTEMP() {
+        mOldImplementation.writeLegacyPermissionStateTEMP();
+        mNewImplementation.writeLegacyPermissionStateTEMP();
+    }
+
+    @Override
+    public Set<String> getInstalledPermissions(String packageName) {
+        Set<String> oldVal = mOldImplementation.getInstalledPermissions(packageName);
+        Set<String> newVal = mNewImplementation.getInstalledPermissions(packageName);
+
+        if (!Objects.equals(oldVal, newVal)) {
+            signalImplDifference("getInstalledPermissions");
+        }
+        return newVal;
+    }
+
+    @NonNull
+    @Override
+    public Set<String> getGrantedPermissions(@NonNull String packageName, int userId) {
+        Set<String> oldVal = mOldImplementation.getGrantedPermissions(packageName, userId);
+        Set<String> newVal = mNewImplementation.getGrantedPermissions(packageName, userId);
+
+        if (!Objects.equals(oldVal, newVal)) {
+            signalImplDifference("getGrantedPermissions");
+        }
+        return newVal;
+    }
+
+    @NonNull
+    @Override
+    public int[] getPermissionGids(@NonNull String permissionName, int userId) {
+        int[] oldVal = mOldImplementation.getPermissionGids(permissionName, userId);
+        int[] newVal = mNewImplementation.getPermissionGids(permissionName, userId);
+
+        if (!Arrays.equals(oldVal, newVal)) {
+            signalImplDifference("getPermissionGids");
+        }
+        return newVal;
+    }
+
+    @NonNull
+    @Override
+    public String[] getAppOpPermissionPackages(@NonNull String permissionName) {
+        String[] oldVal = mOldImplementation.getAppOpPermissionPackages(permissionName);
+        String[] newVal = mNewImplementation.getAppOpPermissionPackages(permissionName);
+
+        if (!Arrays.equals(oldVal, newVal)) {
+            signalImplDifference("getAppOpPermissionPackages");
+        }
+        return newVal;
+    }
+
+    @Nullable
+    @Override
+    public Permission getPermissionTEMP(@NonNull String permName) {
+        Permission oldVal = mOldImplementation.getPermissionTEMP(permName);
+        Permission newVal = mNewImplementation.getPermissionTEMP(permName);
+
+        if (!Objects.equals(oldVal, newVal)) {
+            signalImplDifference("getPermissionTEMP");
+        }
+        return newVal;
+    }
+
+    @NonNull
+    @Override
+    public List<PermissionInfo> getAllPermissionsWithProtection(int protection) {
+        List<PermissionInfo> oldVal = mOldImplementation.getAllPermissionsWithProtection(
+                protection);
+        List<PermissionInfo> newVal = mNewImplementation.getAllPermissionsWithProtection(
+                protection);
+
+        if (!Objects.equals(oldVal, newVal)) {
+            signalImplDifference("getAllPermissionsWithProtection");
+        }
+        return newVal;
+    }
+
+    @NonNull
+    @Override
+    public List<PermissionInfo> getAllPermissionsWithProtectionFlags(int protectionFlags) {
+        List<PermissionInfo> oldVal = mOldImplementation
+                .getAllPermissionsWithProtectionFlags(protectionFlags);
+        List<PermissionInfo> newVal = mNewImplementation.getAllPermissionsWithProtectionFlags(
+                protectionFlags);
+
+        if (!Objects.equals(oldVal, newVal)) {
+            signalImplDifference("getAllPermissionsWithProtectionFlags");
+        }
+        return newVal;
+    }
+
+    @NonNull
+    @Override
+    public List<LegacyPermission> getLegacyPermissions() {
+        List<LegacyPermission> oldVal = mOldImplementation.getLegacyPermissions();
+        List<LegacyPermission> newVal = mNewImplementation.getLegacyPermissions();
+
+        if (!Objects.equals(oldVal, newVal)) {
+            signalImplDifference("getLegacyPermissions");
+        }
+        return newVal;
+    }
+
+    @NonNull
+    @Override
+    public LegacyPermissionState getLegacyPermissionState(int appId) {
+        LegacyPermissionState oldVal = mOldImplementation.getLegacyPermissionState(appId);
+        LegacyPermissionState newVal = mNewImplementation.getLegacyPermissionState(appId);
+
+        if (!Objects.equals(oldVal, newVal)) {
+            signalImplDifference("getLegacyPermissionState");
+        }
+        return newVal;
+    }
+
+    @Override
+    public void readLegacyPermissionsTEMP(
+            @NonNull LegacyPermissionSettings legacyPermissionSettings) {
+        mOldImplementation.readLegacyPermissionsTEMP(legacyPermissionSettings);
+        mNewImplementation.readLegacyPermissionsTEMP(legacyPermissionSettings);
+    }
+
+    @Override
+    public void writeLegacyPermissionsTEMP(
+            @NonNull LegacyPermissionSettings legacyPermissionSettings) {
+        mOldImplementation.writeLegacyPermissionsTEMP(legacyPermissionSettings);
+        mNewImplementation.writeLegacyPermissionsTEMP(legacyPermissionSettings);
+    }
+
+    @Nullable
+    @Override
+    public String getDefaultPermissionGrantFingerprint(@UserIdInt int userId) {
+        String oldVal = mOldImplementation.getDefaultPermissionGrantFingerprint(userId);
+        String newVal = mNewImplementation.getDefaultPermissionGrantFingerprint(userId);
+
+        if (Objects.equals(oldVal, Build.FINGERPRINT)
+                != Objects.equals(newVal, Build.FINGERPRINT)) {
+            signalImplDifference("getDefaultPermissionGrantFingerprint");
+        }
+        return newVal;
+    }
+
+    @Override
+    public void setDefaultPermissionGrantFingerprint(@NonNull String fingerprint,
+            @UserIdInt int userId) {
+        mOldImplementation.setDefaultPermissionGrantFingerprint(fingerprint, userId);
+        mNewImplementation.setDefaultPermissionGrantFingerprint(fingerprint, userId);
+    }
+
+    @Override
+    public void onSystemReady() {
+        mOldImplementation.onSystemReady();
+        mNewImplementation.onSystemReady();
+    }
+
+    @Override
+    public void onStorageVolumeMounted(@NonNull String volumeUuid, boolean fingerprintChanged) {
+        mOldImplementation.onStorageVolumeMounted(volumeUuid, fingerprintChanged);
+        mNewImplementation.onStorageVolumeMounted(volumeUuid, fingerprintChanged);
+    }
+
+    @NonNull
+    @Override
+    public int[] getGidsForUid(int uid) {
+        int[] oldVal = mOldImplementation.getGidsForUid(uid);
+        int[] newVal = mNewImplementation.getGidsForUid(uid);
+
+        if (!Arrays.equals(oldVal, newVal)) {
+            signalImplDifference("getGidsForUid");
+        }
+        return newVal;
+    }
+
+    @Override
+    public void onUserCreated(int userId) {
+        mOldImplementation.onUserCreated(userId);
+        mNewImplementation.onUserCreated(userId);
+    }
+
+    @Override
+    public void onUserRemoved(int userId) {
+        mOldImplementation.onUserRemoved(userId);
+        mNewImplementation.onUserRemoved(userId);
+    }
+
+    @Override
+    public void onPackageAdded(@NonNull PackageState pkg, boolean isInstantApp,
+            @Nullable AndroidPackage oldPkg) {
+        mOldImplementation.onPackageAdded(pkg, isInstantApp, oldPkg);
+        mNewImplementation.onPackageAdded(pkg, isInstantApp, oldPkg);
+    }
+
+    @Override
+    public void onPackageInstalled(@NonNull AndroidPackage pkg, int previousAppId,
+            @NonNull PermissionManagerServiceInternal.PackageInstalledParams params, int userId) {
+        mOldImplementation.onPackageInstalled(pkg, previousAppId, params, userId);
+        mNewImplementation.onPackageInstalled(pkg, previousAppId, params, userId);
+    }
+
+    @Override
+    public void onPackageRemoved(@NonNull AndroidPackage pkg) {
+        mOldImplementation.onPackageRemoved(pkg);
+        mNewImplementation.onPackageRemoved(pkg);
+    }
+
+    @Override
+    public void onPackageUninstalled(@NonNull String packageName, int appId,
+            @NonNull PackageState packageState, @Nullable AndroidPackage pkg,
+            @NonNull List<AndroidPackage> sharedUserPkgs, int userId) {
+        mOldImplementation.onPackageUninstalled(packageName, appId, packageState, pkg,
+                sharedUserPkgs, userId);
+        mNewImplementation.onPackageUninstalled(packageName, appId, packageState, pkg,
+                sharedUserPkgs, userId);
+    }
+}
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceTracingDecorator.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceTracingDecorator.java
new file mode 100644
index 0000000..4e72fae
--- /dev/null
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceTracingDecorator.java
@@ -0,0 +1,659 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm.permission;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.pm.PermissionGroupInfo;
+import android.content.pm.PermissionInfo;
+import android.content.pm.permission.SplitPermissionInfoParcelable;
+import android.os.Trace;
+import android.permission.IOnPermissionsChangeListener;
+
+import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.PackageState;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Surrounds all PermissionManagerServiceInterface method calls with Trace.traceBegin and
+ * Trace.traceEnd. These traces are used for identifying permission issues and testing.
+ */
+public class PermissionManagerServiceTracingDecorator implements PermissionManagerServiceInterface {
+    private static final long TRACE_TAG = Trace.TRACE_TAG_PACKAGE_MANAGER;
+
+    @NonNull
+    private final PermissionManagerServiceInterface mService;
+
+    public PermissionManagerServiceTracingDecorator(
+            @NonNull PermissionManagerServiceInterface service
+    ) {
+        mService = service;
+    }
+
+    @Nullable
+    @Override
+    public byte[] backupRuntimePermissions(int userId) {
+        Trace.traceBegin(TRACE_TAG,
+                "TaggedTracingPermissionManagerServiceImpl#backupRuntimePermissions");
+        try {
+            return mService.backupRuntimePermissions(userId);
+        } finally {
+            Trace.traceEnd(TRACE_TAG);
+        }
+    }
+
+    @Override
+    public void restoreRuntimePermissions(@NonNull byte[] backup, int userId) {
+        Trace.traceBegin(TRACE_TAG,
+                "TaggedTracingPermissionManagerServiceImpl#restoreRuntimePermissions");
+        try {
+            mService.restoreRuntimePermissions(backup, userId);
+        } finally {
+            Trace.traceEnd(TRACE_TAG);
+        }
+    }
+
+    @Override
+    public void restoreDelayedRuntimePermissions(@NonNull String packageName, int userId) {
+        Trace.traceBegin(TRACE_TAG,
+                "TaggedTracingPermissionManagerServiceImpl#restoreDelayedRuntimePermissions");
+        try {
+            mService.restoreDelayedRuntimePermissions(packageName, userId);
+        } finally {
+            Trace.traceEnd(TRACE_TAG);
+        }
+    }
+
+    @Override
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        Trace.traceBegin(TRACE_TAG, "TaggedTracingPermissionManagerServiceImpl#dump");
+        try {
+            mService.dump(fd, pw, args);
+        } finally {
+            Trace.traceEnd(TRACE_TAG);
+        }
+    }
+
+    @Override
+    public List<PermissionGroupInfo> getAllPermissionGroups(int flags) {
+        Trace.traceBegin(TRACE_TAG,
+                "TaggedTracingPermissionManagerServiceImpl#getAllPermissionGroups");
+        try {
+            return mService.getAllPermissionGroups(flags);
+        } finally {
+            Trace.traceEnd(TRACE_TAG);
+        }
+    }
+
+    @Override
+    public PermissionGroupInfo getPermissionGroupInfo(String groupName, int flags) {
+        Trace.traceBegin(TRACE_TAG,
+                "TaggedTracingPermissionManagerServiceImpl#getPermissionGroupInfo");
+        try {
+            return mService.getPermissionGroupInfo(groupName, flags);
+        } finally {
+            Trace.traceEnd(TRACE_TAG);
+        }
+    }
+
+    @Override
+    public PermissionInfo getPermissionInfo(@NonNull String permName, int flags,
+            @NonNull String opPackageName) {
+        Trace.traceBegin(TRACE_TAG, "TaggedTracingPermissionManagerServiceImpl#getPermissionInfo");
+        try {
+            return mService.getPermissionInfo(permName, flags, opPackageName);
+        } finally {
+            Trace.traceEnd(TRACE_TAG);
+        }
+    }
+
+    @Override
+    public List<PermissionInfo> queryPermissionsByGroup(String groupName, int flags) {
+        Trace.traceBegin(TRACE_TAG,
+                "TaggedTracingPermissionManagerServiceImpl#queryPermissionsByGroup");
+        try {
+            return mService.queryPermissionsByGroup(groupName, flags);
+        } finally {
+            Trace.traceEnd(TRACE_TAG);
+        }
+    }
+
+    @Override
+    public boolean addPermission(PermissionInfo info, boolean async) {
+        Trace.traceBegin(TRACE_TAG, "TaggedTracingPermissionManagerServiceImpl#addPermission");
+        try {
+            return mService.addPermission(info, async);
+        } finally {
+            Trace.traceEnd(TRACE_TAG);
+        }
+    }
+
+    @Override
+    public void removePermission(String permName) {
+        Trace.traceBegin(TRACE_TAG, "TaggedTracingPermissionManagerServiceImpl#removePermission");
+        try {
+            mService.removePermission(permName);
+        } finally {
+            Trace.traceEnd(TRACE_TAG);
+        }
+    }
+
+    @Override
+    public int getPermissionFlags(String packageName, String permName, int userId) {
+        Trace.traceBegin(TRACE_TAG, "TaggedTracingPermissionManagerServiceImpl#getPermissionFlags");
+        try {
+            return mService.getPermissionFlags(packageName, permName, userId);
+        } finally {
+            Trace.traceEnd(TRACE_TAG);
+        }
+    }
+
+    @Override
+    public void updatePermissionFlags(String packageName, String permName, int flagMask,
+            int flagValues, boolean checkAdjustPolicyFlagPermission, int userId) {
+        Trace.traceBegin(TRACE_TAG,
+                "TaggedTracingPermissionManagerServiceImpl#updatePermissionFlags");
+        try {
+            mService.updatePermissionFlags(packageName, permName, flagMask, flagValues,
+                    checkAdjustPolicyFlagPermission, userId);
+        } finally {
+            Trace.traceEnd(TRACE_TAG);
+        }
+    }
+
+    @Override
+    public void updatePermissionFlagsForAllApps(int flagMask, int flagValues, int userId) {
+        Trace.traceBegin(TRACE_TAG,
+                "TaggedTracingPermissionManagerServiceImpl#updatePermissionFlagsForAllApps");
+        try {
+            mService.updatePermissionFlagsForAllApps(flagMask, flagValues, userId);
+        } finally {
+            Trace.traceEnd(TRACE_TAG);
+        }
+    }
+
+    @Override
+    public void addOnPermissionsChangeListener(IOnPermissionsChangeListener listener) {
+        Trace.traceBegin(TRACE_TAG,
+                "TaggedTracingPermissionManagerServiceImpl#addOnPermissionsChangeListener");
+        try {
+            mService.addOnPermissionsChangeListener(listener);
+        } finally {
+            Trace.traceEnd(TRACE_TAG);
+        }
+    }
+
+    @Override
+    public void removeOnPermissionsChangeListener(
+            IOnPermissionsChangeListener listener) {
+        Trace.traceBegin(TRACE_TAG,
+                "TaggedTracingPermissionManagerServiceImpl#removeOnPermissionsChangeListener");
+        try {
+            mService.removeOnPermissionsChangeListener(listener);
+        } finally {
+            Trace.traceEnd(TRACE_TAG);
+        }
+    }
+
+    @Override
+    public boolean addAllowlistedRestrictedPermission(@NonNull String packageName,
+            @NonNull String permName, int flags, int userId) {
+        Trace.traceBegin(TRACE_TAG,
+                "TaggedTracingPermissionManagerServiceImpl#addAllowlistedRestrictedPermission");
+        try {
+            return mService.addAllowlistedRestrictedPermission(packageName, permName, flags,
+                    userId);
+        } finally {
+            Trace.traceEnd(TRACE_TAG);
+        }
+    }
+
+    @Override
+    public List<String> getAllowlistedRestrictedPermissions(@NonNull String packageName, int flags,
+            int userId) {
+        Trace.traceBegin(TRACE_TAG,
+                "TaggedTracingPermissionManagerServiceImpl#getAllowlistedRestrictedPermissions");
+        try {
+            return mService.getAllowlistedRestrictedPermissions(packageName, flags, userId);
+        } finally {
+            Trace.traceEnd(TRACE_TAG);
+        }
+    }
+
+    @Override
+    public boolean removeAllowlistedRestrictedPermission(@NonNull String packageName,
+            @NonNull String permName, int flags, int userId) {
+        Trace.traceBegin(TRACE_TAG,
+                "TaggedTracingPermissionManagerServiceImpl#removeAllowlistedRestrictedPermission");
+        try {
+            return mService.removeAllowlistedRestrictedPermission(packageName, permName, flags,
+                    userId);
+        } finally {
+            Trace.traceEnd(TRACE_TAG);
+        }
+    }
+
+    @Override
+    public void grantRuntimePermission(String packageName, String permName, int userId) {
+        Trace.traceBegin(TRACE_TAG,
+                "TaggedTracingPermissionManagerServiceImpl#grantRuntimePermission");
+        try {
+            mService.grantRuntimePermission(packageName, permName, userId);
+        } finally {
+            Trace.traceEnd(TRACE_TAG);
+        }
+    }
+
+    @Override
+    public void revokeRuntimePermission(String packageName, String permName, int userId,
+            String reason) {
+        Trace.traceBegin(TRACE_TAG,
+                "TaggedTracingPermissionManagerServiceImpl#revokeRuntimePermission");
+        try {
+            mService.revokeRuntimePermission(packageName, permName, userId, reason);
+        } finally {
+            Trace.traceEnd(TRACE_TAG);
+        }
+    }
+
+    @Override
+    public void revokePostNotificationPermissionWithoutKillForTest(String packageName, int userId) {
+        Trace.traceBegin(TRACE_TAG, "TaggedTracingPermissionManagerServiceImpl"
+                + "#revokePostNotificationPermissionWithoutKillForTest");
+        try {
+            mService.revokePostNotificationPermissionWithoutKillForTest(packageName, userId);
+        } finally {
+            Trace.traceEnd(TRACE_TAG);
+        }
+    }
+
+    @Override
+    public boolean shouldShowRequestPermissionRationale(String packageName, String permName,
+            int userId) {
+        Trace.traceBegin(TRACE_TAG,
+                "TaggedTracingPermissionManagerServiceImpl#shouldShowRequestPermissionRationale");
+        try {
+            return mService.shouldShowRequestPermissionRationale(packageName, permName, userId);
+        } finally {
+            Trace.traceEnd(TRACE_TAG);
+        }
+    }
+
+    @Override
+    public boolean isPermissionRevokedByPolicy(String packageName, String permName, int userId) {
+        Trace.traceBegin(TRACE_TAG,
+                "TaggedTracingPermissionManagerServiceImpl#isPermissionRevokedByPolicy");
+        try {
+            return mService.isPermissionRevokedByPolicy(packageName, permName, userId);
+        } finally {
+            Trace.traceEnd(TRACE_TAG);
+        }
+    }
+
+    @Override
+    public List<SplitPermissionInfoParcelable> getSplitPermissions() {
+        Trace.traceBegin(TRACE_TAG,
+                "TaggedTracingPermissionManagerServiceImpl#getSplitPermissions");
+        try {
+            return mService.getSplitPermissions();
+        } finally {
+            Trace.traceEnd(TRACE_TAG);
+        }
+    }
+
+    @Override
+    public int checkPermission(String pkgName, String permName, int userId) {
+        Trace.traceBegin(TRACE_TAG, "TaggedTracingPermissionManagerServiceImpl#checkPermission");
+        try {
+            return mService.checkPermission(pkgName, permName, userId);
+        } finally {
+            Trace.traceEnd(TRACE_TAG);
+        }
+    }
+
+    @Override
+    public int checkUidPermission(int uid, String permName) {
+        Trace.traceBegin(TRACE_TAG, "TaggedTracingPermissionManagerServiceImpl#checkUidPermission");
+        try {
+            return mService.checkUidPermission(uid, permName);
+        } finally {
+            Trace.traceEnd(TRACE_TAG);
+        }
+    }
+
+    @Override
+    public Map<String, Set<String>> getAllAppOpPermissionPackages() {
+        Trace.traceBegin(TRACE_TAG,
+                "TaggedTracingPermissionManagerServiceImpl#getAllAppOpPermissionPackages");
+        try {
+            return mService.getAllAppOpPermissionPackages();
+        } finally {
+            Trace.traceEnd(TRACE_TAG);
+        }
+    }
+
+    @Override
+    public boolean isPermissionsReviewRequired(@NonNull String packageName, int userId) {
+        Trace.traceBegin(TRACE_TAG,
+                "TaggedTracingPermissionManagerServiceImpl#isPermissionsReviewRequired");
+        try {
+            return mService.isPermissionsReviewRequired(packageName, userId);
+        } finally {
+            Trace.traceEnd(TRACE_TAG);
+        }
+    }
+
+    @Override
+    public void resetRuntimePermissions(@NonNull AndroidPackage pkg, int userId) {
+        Trace.traceBegin(TRACE_TAG,
+                "TaggedTracingPermissionManagerServiceImpl#resetRuntimePermissions");
+        try {
+            mService.resetRuntimePermissions(pkg, userId);
+        } finally {
+            Trace.traceEnd(TRACE_TAG);
+        }
+    }
+
+    @Override
+    public void resetRuntimePermissionsForUser(int userId) {
+        Trace.traceBegin(TRACE_TAG,
+                "TaggedTracingPermissionManagerServiceImpl#resetRuntimePermissionsForUser");
+        try {
+            mService.resetRuntimePermissionsForUser(userId);
+        } finally {
+            Trace.traceEnd(TRACE_TAG);
+        }
+    }
+
+    @Override
+    public void readLegacyPermissionStateTEMP() {
+        Trace.traceBegin(TRACE_TAG,
+                "TaggedTracingPermissionManagerServiceImpl#readLegacyPermissionStateTEMP");
+        try {
+            mService.readLegacyPermissionStateTEMP();
+        } finally {
+            Trace.traceEnd(TRACE_TAG);
+        }
+    }
+
+    @Override
+    public void writeLegacyPermissionStateTEMP() {
+        Trace.traceBegin(TRACE_TAG,
+                "TaggedTracingPermissionManagerServiceImpl#writeLegacyPermissionStateTEMP");
+        try {
+            mService.writeLegacyPermissionStateTEMP();
+        } finally {
+            Trace.traceEnd(TRACE_TAG);
+        }
+    }
+
+    @NonNull
+    @Override
+    public Set<String> getInstalledPermissions(@NonNull String packageName) {
+        Trace.traceBegin(TRACE_TAG,
+                "TaggedTracingPermissionManagerServiceImpl#getInstalledPermissions");
+        try {
+            return mService.getInstalledPermissions(packageName);
+        } finally {
+            Trace.traceEnd(TRACE_TAG);
+        }
+    }
+
+    @NonNull
+    @Override
+    public Set<String> getGrantedPermissions(@NonNull String packageName, int userId) {
+        Trace.traceBegin(TRACE_TAG,
+                "TaggedTracingPermissionManagerServiceImpl#getGrantedPermissions");
+        try {
+            return mService.getGrantedPermissions(packageName, userId);
+        } finally {
+            Trace.traceEnd(TRACE_TAG);
+        }
+    }
+
+    @NonNull
+    @Override
+    public int[] getPermissionGids(@NonNull String permissionName, int userId) {
+        Trace.traceBegin(TRACE_TAG, "TaggedTracingPermissionManagerServiceImpl#getPermissionGids");
+        try {
+            return mService.getPermissionGids(permissionName, userId);
+        } finally {
+            Trace.traceEnd(TRACE_TAG);
+        }
+    }
+
+    @NonNull
+    @Override
+    public String[] getAppOpPermissionPackages(@NonNull String permissionName) {
+        Trace.traceBegin(TRACE_TAG,
+                "TaggedTracingPermissionManagerServiceImpl#getAppOpPermissionPackages");
+        try {
+            return mService.getAppOpPermissionPackages(permissionName);
+        } finally {
+            Trace.traceEnd(TRACE_TAG);
+        }
+    }
+
+    @Nullable
+    @Override
+    public Permission getPermissionTEMP(@NonNull String permName) {
+        Trace.traceBegin(TRACE_TAG, "TaggedTracingPermissionManagerServiceImpl#getPermissionTEMP");
+        try {
+            return mService.getPermissionTEMP(permName);
+        } finally {
+            Trace.traceEnd(TRACE_TAG);
+        }
+    }
+
+    @NonNull
+    @Override
+    public List<PermissionInfo> getAllPermissionsWithProtection(int protection) {
+        Trace.traceBegin(TRACE_TAG,
+                "TaggedTracingPermissionManagerServiceImpl#getAllPermissionsWithProtection");
+        try {
+            return mService.getAllPermissionsWithProtection(protection);
+        } finally {
+            Trace.traceEnd(TRACE_TAG);
+        }
+    }
+
+    @NonNull
+    @Override
+    public List<PermissionInfo> getAllPermissionsWithProtectionFlags(int protectionFlags) {
+        Trace.traceBegin(TRACE_TAG,
+                "TaggedTracingPermissionManagerServiceImpl#getAllPermissionsWithProtectionFlags");
+        try {
+            return mService.getAllPermissionsWithProtectionFlags(protectionFlags);
+        } finally {
+            Trace.traceEnd(TRACE_TAG);
+        }
+    }
+
+    @NonNull
+    @Override
+    public List<LegacyPermission> getLegacyPermissions() {
+        Trace.traceBegin(TRACE_TAG,
+                "TaggedTracingPermissionManagerServiceImpl#getLegacyPermissions");
+        try {
+            return mService.getLegacyPermissions();
+        } finally {
+            Trace.traceEnd(TRACE_TAG);
+        }
+    }
+
+    @NonNull
+    @Override
+    public LegacyPermissionState getLegacyPermissionState(int appId) {
+        Trace.traceBegin(TRACE_TAG,
+                "TaggedTracingPermissionManagerServiceImpl#getLegacyPermissionState");
+        try {
+            return mService.getLegacyPermissionState(appId);
+        } finally {
+            Trace.traceEnd(TRACE_TAG);
+        }
+    }
+
+    @Override
+    public void readLegacyPermissionsTEMP(
+            @NonNull LegacyPermissionSettings legacyPermissionSettings) {
+        Trace.traceBegin(TRACE_TAG,
+                "TaggedTracingPermissionManagerServiceImpl#readLegacyPermissionsTEMP");
+        try {
+            mService.readLegacyPermissionsTEMP(legacyPermissionSettings);
+        } finally {
+            Trace.traceEnd(TRACE_TAG);
+        }
+    }
+
+    @Override
+    public void writeLegacyPermissionsTEMP(
+            @NonNull LegacyPermissionSettings legacyPermissionSettings) {
+        Trace.traceBegin(TRACE_TAG,
+                "TaggedTracingPermissionManagerServiceImpl#writeLegacyPermissionsTEMP");
+        try {
+            mService.writeLegacyPermissionsTEMP(legacyPermissionSettings);
+        } finally {
+            Trace.traceEnd(TRACE_TAG);
+        }
+    }
+
+    @Nullable
+    @Override
+    public String getDefaultPermissionGrantFingerprint(int userId) {
+        Trace.traceBegin(TRACE_TAG,
+                "TaggedTracingPermissionManagerServiceImpl#getDefaultPermissionGrantFingerprint");
+        try {
+            return mService.getDefaultPermissionGrantFingerprint(userId);
+        } finally {
+            Trace.traceEnd(TRACE_TAG);
+        }
+    }
+
+    @Override
+    public void setDefaultPermissionGrantFingerprint(@NonNull String fingerprint, int userId) {
+        Trace.traceBegin(TRACE_TAG,
+                "TaggedTracingPermissionManagerServiceImpl#setDefaultPermissionGrantFingerprint");
+        try {
+            mService.setDefaultPermissionGrantFingerprint(fingerprint, userId);
+        } finally {
+            Trace.traceEnd(TRACE_TAG);
+        }
+    }
+
+    @Override
+    public void onSystemReady() {
+        Trace.traceBegin(TRACE_TAG, "TaggedTracingPermissionManagerServiceImpl#onSystemReady");
+        try {
+            mService.onSystemReady();
+        } finally {
+            Trace.traceEnd(TRACE_TAG);
+        }
+    }
+
+    @Override
+    public void onStorageVolumeMounted(@NonNull String volumeUuid, boolean fingerprintChanged) {
+        Trace.traceBegin(TRACE_TAG,
+                "TaggedTracingPermissionManagerServiceImpl#onStorageVolumeMounted");
+        try {
+            mService.onStorageVolumeMounted(volumeUuid, fingerprintChanged);
+        } finally {
+            Trace.traceEnd(TRACE_TAG);
+        }
+    }
+
+    @NonNull
+    @Override
+    public int[] getGidsForUid(int uid) {
+        Trace.traceBegin(TRACE_TAG, "TaggedTracingPermissionManagerServiceImpl#getGidsForUid");
+        try {
+            return mService.getGidsForUid(uid);
+        } finally {
+            Trace.traceEnd(TRACE_TAG);
+        }
+    }
+
+    @Override
+    public void onUserCreated(int userId) {
+        Trace.traceBegin(TRACE_TAG, "TaggedTracingPermissionManagerServiceImpl#onUserCreated");
+        try {
+            mService.onUserCreated(userId);
+        } finally {
+            Trace.traceEnd(TRACE_TAG);
+        }
+    }
+
+    @Override
+    public void onUserRemoved(int userId) {
+        Trace.traceBegin(TRACE_TAG, "TaggedTracingPermissionManagerServiceImpl#onUserRemoved");
+        try {
+            mService.onUserRemoved(userId);
+        } finally {
+            Trace.traceEnd(TRACE_TAG);
+        }
+    }
+
+    @Override
+    public void onPackageAdded(@NonNull PackageState packageState, boolean isInstantApp,
+            @Nullable AndroidPackage oldPkg) {
+        Trace.traceBegin(TRACE_TAG, "TaggedTracingPermissionManagerServiceImpl#onPackageAdded");
+        try {
+            mService.onPackageAdded(packageState, isInstantApp, oldPkg);
+        } finally {
+            Trace.traceEnd(TRACE_TAG);
+        }
+    }
+
+    @Override
+    public void onPackageInstalled(@NonNull AndroidPackage pkg, int previousAppId,
+            @NonNull PermissionManagerServiceInternal.PackageInstalledParams params, int userId) {
+        Trace.traceBegin(TRACE_TAG, "TaggedTracingPermissionManagerServiceImpl#onPackageInstalled");
+        try {
+            mService.onPackageInstalled(pkg, previousAppId, params, userId);
+        } finally {
+            Trace.traceEnd(TRACE_TAG);
+        }
+    }
+
+    @Override
+    public void onPackageRemoved(@NonNull AndroidPackage pkg) {
+        Trace.traceBegin(TRACE_TAG, "TaggedTracingPermissionManagerServiceImpl#onPackageRemoved");
+        try {
+            mService.onPackageRemoved(pkg);
+        } finally {
+            Trace.traceEnd(TRACE_TAG);
+        }
+    }
+
+    @Override
+    public void onPackageUninstalled(@NonNull String packageName, int appId,
+            @NonNull PackageState packageState, @Nullable AndroidPackage pkg,
+            @NonNull List<AndroidPackage> sharedUserPkgs, int userId) {
+        Trace.traceBegin(TRACE_TAG,
+                "TaggedTracingPermissionManagerServiceImpl#onPackageUninstalled");
+        try {
+            mService.onPackageUninstalled(packageName, appId, packageState, pkg, sharedUserPkgs,
+                    userId);
+        } finally {
+            Trace.traceEnd(TRACE_TAG);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/pm/permission/PermissionMigrationHelper.java b/services/core/java/com/android/server/pm/permission/PermissionMigrationHelper.java
new file mode 100644
index 0000000..9eb6fde
--- /dev/null
+++ b/services/core/java/com/android/server/pm/permission/PermissionMigrationHelper.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm.permission;
+
+import android.annotation.NonNull;
+import android.content.pm.PermissionInfo;
+
+import java.util.Map;
+
+/**
+ * In-process api for permissions migration.
+ *
+ * @hide
+ */
+public interface PermissionMigrationHelper {
+    /**
+     * Whether legacy permission definitions/trees exist or not.
+     */
+    boolean hasLegacyPermission();
+
+    /**
+     * @return legacy permission definitions.
+     */
+    @NonNull
+    Map<String, LegacyPermission> getLegacyPermissions();
+
+    /**
+     * @return legacy permission trees.
+     */
+    @NonNull
+    Map<String, LegacyPermission> getLegacyPermissionTrees();
+
+    /**
+     * @return legacy permissions state for a user.
+     */
+    @NonNull
+    Map<Integer, Map<String, LegacyPermissionState>> getLegacyPermissionStates(int userId);
+
+    /**
+     * @return permissions file version for the given user.
+     */
+    int getLegacyPermissionStateVersion(int userId);
+
+    /**
+     * @return true if permissions state exists or not.
+     */
+    boolean hasLegacyPermissionState(int userId);
+
+    /**
+     * Legacy permission definition.
+     */
+    final class LegacyPermission {
+        private final PermissionInfo mPermissionInfo;
+        private final int mType;
+
+        LegacyPermission(PermissionInfo permissionInfo, int type) {
+            mPermissionInfo = permissionInfo;
+            mType = type;
+        }
+
+        @NonNull
+        public PermissionInfo getPermissionInfo() {
+            return mPermissionInfo;
+        }
+
+        public int getType() {
+            return mType;
+        }
+    }
+
+    /**
+     * State of a legacy permission.
+     */
+    final class LegacyPermissionState {
+        private final boolean mGranted;
+        private final int mFlags;
+
+        LegacyPermissionState(boolean granted, int flags) {
+            mGranted = granted;
+            mFlags = flags;
+        }
+
+        /**
+         * @return Whether the permission is granted or not.
+         */
+        public boolean isGranted() {
+            return mGranted;
+        }
+
+        /**
+         * @return Permission flags.
+         */
+        public int getFlags() {
+            return mFlags;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/pm/permission/PermissionMigrationHelperImpl.java b/services/core/java/com/android/server/pm/permission/PermissionMigrationHelperImpl.java
new file mode 100644
index 0000000..dbf4047
--- /dev/null
+++ b/services/core/java/com/android/server/pm/permission/PermissionMigrationHelperImpl.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm.permission;
+
+import android.annotation.NonNull;
+import android.content.pm.PackageManagerInternal;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import com.android.permission.persistence.RuntimePermissionsState;
+import com.android.server.LocalManagerRegistry;
+import com.android.server.LocalServices;
+import com.android.server.pm.PackageManagerLocal;
+import com.android.server.pm.pkg.PackageState;
+import com.android.server.pm.pkg.SharedUserApi;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Provider of legacy permissions data for new permission subsystem.
+ *
+ * @hide
+ */
+public class PermissionMigrationHelperImpl implements PermissionMigrationHelper {
+    private static final String LOG_TAG = PermissionMigrationHelperImpl.class.getSimpleName();
+
+    @Override
+    public boolean hasLegacyPermission() {
+        PackageManagerInternal packageManagerInternal =
+                LocalServices.getService(PackageManagerInternal.class);
+        LegacyPermissionSettings legacySettings = packageManagerInternal.getLegacyPermissions();
+        return !(legacySettings.getPermissions().isEmpty()
+                && legacySettings.getPermissionTrees().isEmpty());
+    }
+
+    /**
+     * @return legacy permission definitions.
+     */
+    @NonNull
+    public Map<String, LegacyPermission> getLegacyPermissions() {
+        PackageManagerInternal mPackageManagerInternal =
+                LocalServices.getService(PackageManagerInternal.class);
+        return toLegacyPermissions(
+                mPackageManagerInternal.getLegacyPermissions().getPermissions());
+    }
+
+    /**
+     * @return legacy permission trees.
+     */
+    @NonNull
+    public Map<String, LegacyPermission> getLegacyPermissionTrees() {
+        PackageManagerInternal mPackageManagerInternal =
+                LocalServices.getService(PackageManagerInternal.class);
+        return toLegacyPermissions(
+                mPackageManagerInternal.getLegacyPermissions().getPermissionTrees());
+    }
+
+    @NonNull
+    private Map<String, LegacyPermission> toLegacyPermissions(
+            List<com.android.server.pm.permission.LegacyPermission> legacyPermissions) {
+        Map<String, LegacyPermission> permissions = new ArrayMap<>();
+        legacyPermissions.forEach(legacyPermission -> {
+            LegacyPermission permission = new LegacyPermission(legacyPermission.getPermissionInfo(),
+                    legacyPermission.getType());
+            permissions.put(legacyPermission.getPermissionInfo().name, permission);
+        });
+
+        return permissions;
+    }
+
+    /**
+     * @return permissions state for a user, i.e. map of appId to map of permission name and state.
+     */
+    @NonNull
+    public Map<Integer, Map<String, LegacyPermissionState>> getLegacyPermissionStates(int userId) {
+        PackageManagerInternal mPackageManagerInternal =
+                LocalServices.getService(PackageManagerInternal.class);
+        Map<Integer, Map<String, LegacyPermissionState>> appIdPermissionStates = new ArrayMap<>();
+
+        RuntimePermissionsState legacyState =
+                mPackageManagerInternal.getLegacyPermissionsState(userId);
+        PackageManagerLocal packageManagerLocal =
+                LocalManagerRegistry.getManager(PackageManagerLocal.class);
+
+        try (PackageManagerLocal.UnfilteredSnapshot snapshot =
+                     packageManagerLocal.withUnfilteredSnapshot()) {
+            Map<String, PackageState> packageStates = snapshot.getPackageStates();
+            legacyState.getPackagePermissions().forEach((packageName, permissionStates) -> {
+                if (!permissionStates.isEmpty()) {
+                    PackageState packageState = packageStates.get(packageName);
+                    if (packageState != null) {
+                        int appId = packageState.getAppId();
+                        appIdPermissionStates.put(appId,
+                                toLegacyPermissionStates(permissionStates));
+                    } else {
+                        Log.w(LOG_TAG, "Package " + packageName + " not found.");
+                    }
+                }
+            });
+
+            Map<String, SharedUserApi> sharedUsers = snapshot.getSharedUsers();
+            legacyState.getSharedUserPermissions().forEach((sharedUserName, permissionStates) -> {
+                if (!permissionStates.isEmpty()) {
+                    SharedUserApi sharedUser = sharedUsers.get(sharedUserName);
+                    if (sharedUser != null) {
+                        int appId = sharedUser.getAppId();
+                        appIdPermissionStates.put(appId,
+                                toLegacyPermissionStates(permissionStates));
+                    } else {
+                        Log.w(LOG_TAG, "Shared user " + sharedUserName + " not found.");
+                    }
+                }
+            });
+        }
+        return appIdPermissionStates;
+    }
+
+    @Override
+    public int getLegacyPermissionStateVersion(int userId) {
+        PackageManagerInternal packageManagerInternal =
+                LocalServices.getService(PackageManagerInternal.class);
+        int version = packageManagerInternal.getLegacyPermissionsVersion(userId);
+        // -1 No permission data available
+        // 0 runtime-permissions.xml exist w/o any version
+        switch (version) {
+            case -1:
+                return 0;
+            case 0:
+                return -1;
+            default:
+                return version;
+        }
+    }
+
+    @Override
+    public boolean hasLegacyPermissionState(int userId) {
+        return getLegacyPermissionStateVersion(userId) > -1;
+    }
+
+    @NonNull
+    private Map<String, LegacyPermissionState> toLegacyPermissionStates(
+            List<RuntimePermissionsState.PermissionState> permissions) {
+        Map<String, LegacyPermissionState> legacyPermissions = new ArrayMap<>();
+
+        final int size = permissions.size();
+        for (int i = 0; i < size; i++) {
+            RuntimePermissionsState.PermissionState permState = permissions.get(i);
+            legacyPermissions.put(permState.getName(), new LegacyPermissionState(
+                    permState.isGranted(), permState.getFlags()));
+        }
+
+        return legacyPermissions;
+    }
+}
diff --git a/services/core/java/com/android/server/pm/pkg/AndroidPackage.java b/services/core/java/com/android/server/pm/pkg/AndroidPackage.java
index e54f34d..91854fd 100644
--- a/services/core/java/com/android/server/pm/pkg/AndroidPackage.java
+++ b/services/core/java/com/android/server/pm/pkg/AndroidPackage.java
@@ -775,7 +775,7 @@
      * @hide
      */
     @NonNull
-    List<String> getImplicitPermissions();
+    Set<String> getImplicitPermissions();
 
     /**
      * @see ApplicationInfo#installLocation
@@ -1123,7 +1123,7 @@
      * @hide
      */
     @NonNull
-    List<String> getRequestedPermissions();
+    Set<String> getRequestedPermissions();
 
     /**
      * Whether or not the app requested explicitly resizeable Activities. Null value means nothing
diff --git a/services/core/java/com/android/server/pm/pkg/ArchiveState.java b/services/core/java/com/android/server/pm/pkg/ArchiveState.java
new file mode 100644
index 0000000..d44ae16
--- /dev/null
+++ b/services/core/java/com/android/server/pm/pkg/ArchiveState.java
@@ -0,0 +1,306 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm.pkg;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import com.android.internal.util.DataClass;
+
+import java.nio.file.Path;
+import java.util.List;
+
+/**
+ * Information about the state of an archived app. All fields are gathered at the time of
+ * archival.
+ *
+ * @hide
+ */
+@DataClass(genEqualsHashCode = true, genToString = true)
+public class ArchiveState {
+
+    /**
+     * Information about main activities.
+     *
+     * <p> This list has at least one entry. In the vast majority of cases, this list has only one
+     * entry.
+     */
+    @NonNull
+    private final List<ArchiveActivityInfo> mActivityInfos;
+
+    /**
+     * Corresponds to android:label of the installer responsible for the unarchival of the app.
+     * Stored in the installer's locale .
+     */
+    @NonNull
+    private final String mInstallerTitle;
+
+    /** Information about a main activity of an archived app. */
+    @DataClass(genEqualsHashCode = true, genToString = true)
+    public static class ArchiveActivityInfo {
+        /** Corresponds to the activity's android:label in the app's locale. */
+        @NonNull
+        private final String mTitle;
+
+        /** The path to the stored icon of the activity in the app's locale. */
+        @NonNull
+        private final Path mIconBitmap;
+
+        /** See {@link #mIconBitmap}. Only set if the app defined a monochrome icon. */
+        @Nullable
+        private final Path mMonochromeIconBitmap;
+
+
+
+        // Code below generated by codegen v1.0.23.
+        //
+        // DO NOT MODIFY!
+        // CHECKSTYLE:OFF Generated code
+        //
+        // To regenerate run:
+        // $ codegen $ANDROID_BUILD_TOP/frameworks/base/services/core/java/com/android/server/pm/pkg/ArchiveState.java
+        //
+        // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+        //   Settings > Editor > Code Style > Formatter Control
+        //@formatter:off
+
+
+        /**
+         * Creates a new ArchiveActivityInfo.
+         *
+         * @param title
+         *   Corresponds to the activity's android:label in the app's locale.
+         * @param iconBitmap
+         *   The path to the stored icon of the activity in the app's locale.
+         * @param monochromeIconBitmap
+         *   See {@link #mIconBitmap}. Only set if the app defined a monochrome icon.
+         */
+        @DataClass.Generated.Member
+        public ArchiveActivityInfo(
+                @NonNull String title,
+                @NonNull Path iconBitmap,
+                @Nullable Path monochromeIconBitmap) {
+            this.mTitle = title;
+            com.android.internal.util.AnnotationValidations.validate(
+                    NonNull.class, null, mTitle);
+            this.mIconBitmap = iconBitmap;
+            com.android.internal.util.AnnotationValidations.validate(
+                    NonNull.class, null, mIconBitmap);
+            this.mMonochromeIconBitmap = monochromeIconBitmap;
+
+            // onConstructed(); // You can define this method to get a callback
+        }
+
+        /**
+         * Corresponds to the activity's android:label in the app's locale.
+         */
+        @DataClass.Generated.Member
+        public @NonNull String getTitle() {
+            return mTitle;
+        }
+
+        /**
+         * The path to the stored icon of the activity in the app's locale.
+         */
+        @DataClass.Generated.Member
+        public @NonNull Path getIconBitmap() {
+            return mIconBitmap;
+        }
+
+        /**
+         * See {@link #mIconBitmap}. Only set if the app defined a monochrome icon.
+         */
+        @DataClass.Generated.Member
+        public @Nullable Path getMonochromeIconBitmap() {
+            return mMonochromeIconBitmap;
+        }
+
+        @Override
+        @DataClass.Generated.Member
+        public String toString() {
+            // You can override field toString logic by defining methods like:
+            // String fieldNameToString() { ... }
+
+            return "ArchiveActivityInfo { " +
+                    "title = " + mTitle + ", " +
+                    "iconBitmap = " + mIconBitmap + ", " +
+                    "monochromeIconBitmap = " + mMonochromeIconBitmap +
+            " }";
+        }
+
+        @Override
+        @DataClass.Generated.Member
+        public boolean equals(@Nullable Object o) {
+            // You can override field equality logic by defining either of the methods like:
+            // boolean fieldNameEquals(ArchiveActivityInfo other) { ... }
+            // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+            @SuppressWarnings("unchecked")
+            ArchiveActivityInfo that = (ArchiveActivityInfo) o;
+            //noinspection PointlessBooleanExpression
+            return true
+                    && java.util.Objects.equals(mTitle, that.mTitle)
+                    && java.util.Objects.equals(mIconBitmap, that.mIconBitmap)
+                    && java.util.Objects.equals(mMonochromeIconBitmap, that.mMonochromeIconBitmap);
+        }
+
+        @Override
+        @DataClass.Generated.Member
+        public int hashCode() {
+            // You can override field hashCode logic by defining methods like:
+            // int fieldNameHashCode() { ... }
+
+            int _hash = 1;
+            _hash = 31 * _hash + java.util.Objects.hashCode(mTitle);
+            _hash = 31 * _hash + java.util.Objects.hashCode(mIconBitmap);
+            _hash = 31 * _hash + java.util.Objects.hashCode(mMonochromeIconBitmap);
+            return _hash;
+        }
+
+        @DataClass.Generated(
+                time = 1689169065133L,
+                codegenVersion = "1.0.23",
+                sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/ArchiveState.java",
+                inputSignatures = "private final @android.annotation.NonNull java.lang.String mTitle\nprivate final @android.annotation.NonNull java.nio.file.Path mIconBitmap\nprivate final @android.annotation.Nullable java.nio.file.Path mMonochromeIconBitmap\nclass ArchiveActivityInfo extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true)")
+        @Deprecated
+        private void __metadata() {}
+
+
+        //@formatter:on
+        // End of generated code
+
+    }
+
+
+
+
+
+    // Code below generated by codegen v1.0.23.
+    //
+    // DO NOT MODIFY!
+    // CHECKSTYLE:OFF Generated code
+    //
+    // To regenerate run:
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/services/core/java/com/android/server/pm/pkg/ArchiveState.java
+    //
+    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+    //   Settings > Editor > Code Style > Formatter Control
+    //@formatter:off
+
+
+    /**
+     * Creates a new ArchiveState.
+     *
+     * @param activityInfos
+     *   Information about main activities.
+     *
+     *   <p> This list has at least one entry. In the vast majority of cases, this list has only one
+     *   entry.
+     * @param installerTitle
+     *   Corresponds to android:label of the installer responsible for the unarchival of the app.
+     *   Stored in the installer's locale .
+     */
+    @DataClass.Generated.Member
+    public ArchiveState(
+            @NonNull List<ArchiveActivityInfo> activityInfos,
+            @NonNull String installerTitle) {
+        this.mActivityInfos = activityInfos;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mActivityInfos);
+        this.mInstallerTitle = installerTitle;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mInstallerTitle);
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    /**
+     * Information about main activities.
+     *
+     * <p> This list has at least one entry. In the vast majority of cases, this list has only one
+     * entry.
+     */
+    @DataClass.Generated.Member
+    public @NonNull List<ArchiveActivityInfo> getActivityInfos() {
+        return mActivityInfos;
+    }
+
+    /**
+     * Corresponds to android:label of the installer responsible for the unarchival of the app.
+     * Stored in the installer's locale .
+     */
+    @DataClass.Generated.Member
+    public @NonNull String getInstallerTitle() {
+        return mInstallerTitle;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public String toString() {
+        // You can override field toString logic by defining methods like:
+        // String fieldNameToString() { ... }
+
+        return "ArchiveState { " +
+                "activityInfos = " + mActivityInfos + ", " +
+                "installerTitle = " + mInstallerTitle +
+        " }";
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public boolean equals(@Nullable Object o) {
+        // You can override field equality logic by defining either of the methods like:
+        // boolean fieldNameEquals(ArchiveState other) { ... }
+        // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        @SuppressWarnings("unchecked")
+        ArchiveState that = (ArchiveState) o;
+        //noinspection PointlessBooleanExpression
+        return true
+                && java.util.Objects.equals(mActivityInfos, that.mActivityInfos)
+                && java.util.Objects.equals(mInstallerTitle, that.mInstallerTitle);
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int hashCode() {
+        // You can override field hashCode logic by defining methods like:
+        // int fieldNameHashCode() { ... }
+
+        int _hash = 1;
+        _hash = 31 * _hash + java.util.Objects.hashCode(mActivityInfos);
+        _hash = 31 * _hash + java.util.Objects.hashCode(mInstallerTitle);
+        return _hash;
+    }
+
+    @DataClass.Generated(
+            time = 1689169065144L,
+            codegenVersion = "1.0.23",
+            sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/ArchiveState.java",
+            inputSignatures = "private final @android.annotation.NonNull java.util.List<com.android.server.pm.pkg.ArchiveActivityInfo> mActivityInfos\nprivate final @android.annotation.NonNull java.lang.String mInstallerTitle\nclass ArchiveState extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true)")
+    @Deprecated
+    private void __metadata() {}
+
+
+    //@formatter:on
+    // End of generated code
+
+}
diff --git a/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java b/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java
index 91a25db3..ba274e0 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java
@@ -377,7 +377,11 @@
         private final int mUninstallReason;
         @Nullable
         private final String mSplashScreenTheme;
+        @PackageManager.UserMinAspectRatio
+        private final int mMinAspectRatio;
         private final long mFirstInstallTimeMillis;
+        @Nullable
+        private final ArchiveState mArchiveState;
 
         private UserStateImpl(@NonNull PackageUserState userState) {
             mCeDataInode = userState.getCeDataInode();
@@ -392,6 +396,7 @@
             mSharedLibraryOverlayPaths = userState.getSharedLibraryOverlayPaths();
             mUninstallReason = userState.getUninstallReason();
             mSplashScreenTheme = userState.getSplashScreenTheme();
+            mMinAspectRatio = userState.getMinAspectRatio();
             setBoolean(Booleans.HIDDEN, userState.isHidden());
             setBoolean(Booleans.INSTALLED, userState.isInstalled());
             setBoolean(Booleans.INSTANT_APP, userState.isInstantApp());
@@ -400,6 +405,7 @@
             setBoolean(Booleans.SUSPENDED, userState.isSuspended());
             setBoolean(Booleans.VIRTUAL_PRELOAD, userState.isVirtualPreload());
             mFirstInstallTimeMillis = userState.getFirstInstallTimeMillis();
+            mArchiveState = userState.getArchiveState();
         }
 
         @Override
@@ -543,21 +549,31 @@
         }
 
         @DataClass.Generated.Member
+        public @PackageManager.UserMinAspectRatio int getMinAspectRatio() {
+            return mMinAspectRatio;
+        }
+
+        @DataClass.Generated.Member
         public long getFirstInstallTimeMillis() {
             return mFirstInstallTimeMillis;
         }
 
         @DataClass.Generated.Member
+        public @Nullable ArchiveState getArchiveState() {
+            return mArchiveState;
+        }
+
+        @DataClass.Generated.Member
         public @NonNull UserStateImpl setBooleans( int value) {
             mBooleans = value;
             return this;
         }
 
         @DataClass.Generated(
-                time = 1671671043891L,
+                time = 1689171425723L,
                 codegenVersion = "1.0.23",
                 sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java",
-                inputSignatures = "private  int mBooleans\nprivate final  long mCeDataInode\nprivate final @android.annotation.NonNull android.util.ArraySet<java.lang.String> mDisabledComponents\nprivate final @android.content.pm.PackageManager.DistractionRestriction int mDistractionFlags\nprivate final @android.annotation.NonNull android.util.ArraySet<java.lang.String> mEnabledComponents\nprivate final  int mEnabledState\nprivate final @android.annotation.Nullable java.lang.String mHarmfulAppWarning\nprivate final @android.content.pm.PackageManager.InstallReason int mInstallReason\nprivate final @android.annotation.Nullable java.lang.String mLastDisableAppCaller\nprivate final @android.annotation.NonNull android.content.pm.overlay.OverlayPaths mOverlayPaths\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,android.content.pm.overlay.OverlayPaths> mSharedLibraryOverlayPaths\nprivate final @android.content.pm.PackageManager.UninstallReason int mUninstallReason\nprivate final @android.annotation.Nullable java.lang.String mSplashScreenTheme\nprivate final  long mFirstInstallTimeMillis\npublic static  com.android.server.pm.pkg.PackageUserState copy(com.android.server.pm.pkg.PackageUserState)\nprivate  void setBoolean(int,boolean)\nprivate  boolean getBoolean(int)\npublic @java.lang.Override boolean isHidden()\npublic @java.lang.Override boolean isInstalled()\npublic @java.lang.Override boolean isInstantApp()\npublic @java.lang.Override boolean isNotLaunched()\npublic @java.lang.Override boolean isStopped()\npublic @java.lang.Override boolean isSuspended()\npublic @java.lang.Override boolean isVirtualPreload()\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()\nclass UserStateImpl extends java.lang.Object implements [com.android.server.pm.pkg.PackageUserState]\nprivate static final  int HIDDEN\nprivate static final  int INSTALLED\nprivate static final  int INSTANT_APP\nprivate static final  int NOT_LAUNCHED\nprivate static final  int STOPPED\nprivate static final  int SUSPENDED\nprivate static final  int VIRTUAL_PRELOAD\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false)")
+                inputSignatures = "private  int mBooleans\nprivate final  long mCeDataInode\nprivate final @android.annotation.NonNull android.util.ArraySet<java.lang.String> mDisabledComponents\nprivate final @android.content.pm.PackageManager.DistractionRestriction int mDistractionFlags\nprivate final @android.annotation.NonNull android.util.ArraySet<java.lang.String> mEnabledComponents\nprivate final  int mEnabledState\nprivate final @android.annotation.Nullable java.lang.String mHarmfulAppWarning\nprivate final @android.content.pm.PackageManager.InstallReason int mInstallReason\nprivate final @android.annotation.Nullable java.lang.String mLastDisableAppCaller\nprivate final @android.annotation.NonNull android.content.pm.overlay.OverlayPaths mOverlayPaths\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,android.content.pm.overlay.OverlayPaths> mSharedLibraryOverlayPaths\nprivate final @android.content.pm.PackageManager.UninstallReason int mUninstallReason\nprivate final @android.annotation.Nullable java.lang.String mSplashScreenTheme\nprivate final @android.content.pm.PackageManager.UserMinAspectRatio int mMinAspectRatio\nprivate final  long mFirstInstallTimeMillis\nprivate final @android.annotation.Nullable com.android.server.pm.pkg.ArchiveState mArchiveState\npublic static  com.android.server.pm.pkg.PackageUserState copy(com.android.server.pm.pkg.PackageUserState)\nprivate  void setBoolean(int,boolean)\nprivate  boolean getBoolean(int)\npublic @java.lang.Override boolean isHidden()\npublic @java.lang.Override boolean isInstalled()\npublic @java.lang.Override boolean isInstantApp()\npublic @java.lang.Override boolean isNotLaunched()\npublic @java.lang.Override boolean isStopped()\npublic @java.lang.Override boolean isSuspended()\npublic @java.lang.Override boolean isVirtualPreload()\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()\nclass UserStateImpl extends java.lang.Object implements [com.android.server.pm.pkg.PackageUserState]\nprivate static final  int HIDDEN\nprivate static final  int INSTALLED\nprivate static final  int INSTANT_APP\nprivate static final  int NOT_LAUNCHED\nprivate static final  int STOPPED\nprivate static final  int SUSPENDED\nprivate static final  int VIRTUAL_PRELOAD\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false)")
         @Deprecated
         private void __metadata() {}
 
@@ -729,7 +745,7 @@
     }
 
     @DataClass.Generated(
-            time = 1671671043929L,
+            time = 1689171425753L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java",
             inputSignatures = "private  int mBooleans\nprivate final @android.annotation.Nullable com.android.server.pm.pkg.AndroidPackage mAndroidPackage\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.Nullable java.lang.String mVolumeUuid\nprivate final  int mAppId\nprivate final  int mCategoryOverride\nprivate final @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate final @android.content.pm.ApplicationInfo.HiddenApiEnforcementPolicy int mHiddenApiEnforcementPolicy\nprivate final  long mLastModifiedTime\nprivate final  long mLastUpdateTime\nprivate final  long mLongVersionCode\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mMimeGroups\nprivate final @android.annotation.NonNull java.io.File mPath\nprivate final @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate final @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate final @android.annotation.Nullable java.lang.String mSeInfo\nprivate final  boolean mHasSharedUser\nprivate final  int mSharedUserAppId\nprivate final @android.annotation.NonNull java.lang.String[] mUsesSdkLibraries\nprivate final @android.annotation.NonNull long[] mUsesSdkLibrariesVersionsMajor\nprivate final @android.annotation.NonNull java.lang.String[] mUsesStaticLibraries\nprivate final @android.annotation.NonNull long[] mUsesStaticLibrariesVersions\nprivate final @android.annotation.NonNull java.util.List<com.android.server.pm.pkg.SharedLibrary> mUsesLibraries\nprivate final @android.annotation.NonNull java.util.List<java.lang.String> mUsesLibraryFiles\nprivate final @android.annotation.NonNull long[] mLastPackageUsageTime\nprivate final @android.annotation.NonNull android.content.pm.SigningInfo mSigningInfo\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.pkg.PackageUserState> mUserStates\nprivate final @android.annotation.Nullable java.lang.String mApexModuleName\npublic static  com.android.server.pm.pkg.PackageState copy(com.android.server.pm.pkg.PackageStateInternal)\nprivate  void setBoolean(int,boolean)\nprivate  boolean getBoolean(int)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserState getStateForUser(android.os.UserHandle)\npublic @java.lang.Override boolean isExternalStorage()\npublic @java.lang.Override boolean isForceQueryableOverride()\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @java.lang.Override boolean isInstallPermissionsFixed()\npublic @java.lang.Override boolean isOdm()\npublic @java.lang.Override boolean isOem()\npublic @java.lang.Override boolean isPrivileged()\npublic @java.lang.Override boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic @java.lang.Override boolean isSystem()\npublic @java.lang.Override boolean isSystemExt()\npublic @java.lang.Override boolean isUpdateAvailable()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic @java.lang.Override boolean isApkInUpdatedApex()\npublic @java.lang.Override boolean isVendor()\npublic @java.lang.Override long getVersionCode()\npublic @java.lang.Override boolean hasSharedUser()\npublic @java.lang.Override boolean isApex()\nclass PackageStateImpl extends java.lang.Object implements [com.android.server.pm.pkg.PackageState]\nprivate static final  int SYSTEM\nprivate static final  int EXTERNAL_STORAGE\nprivate static final  int PRIVILEGED\nprivate static final  int OEM\nprivate static final  int VENDOR\nprivate static final  int PRODUCT\nprivate static final  int SYSTEM_EXT\nprivate static final  int REQUIRED_FOR_SYSTEM_USER\nprivate static final  int ODM\nprivate static final  int FORCE_QUERYABLE_OVERRIDE\nprivate static final  int HIDDEN_UNTIL_INSTALLED\nprivate static final  int INSTALL_PERMISSIONS_FIXED\nprivate static final  int UPDATE_AVAILABLE\nprivate static final  int UPDATED_SYSTEM_APP\nprivate static final  int APK_IN_UPDATED_APEX\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false)")
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 f839648..fc74a195 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageStateInternal.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageStateInternal.java
@@ -35,7 +35,7 @@
  */
 public interface PackageStateInternal extends PackageState {
 
-    @NonNull
+    @Nullable
     AndroidPackageInternal getPkg();
 
     // TODO: Remove in favor of exposing APIs directly?
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 2048d65..81915b4 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageUserState.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageUserState.java
@@ -217,4 +217,20 @@
      */
     @Nullable
     String getSplashScreenTheme();
+
+    /**
+     * @return the min aspect ratio setting of the package which by default is unset
+     * unless it has been set by the user
+     * @hide
+     */
+    @PackageManager.UserMinAspectRatio
+    int getMinAspectRatio();
+    /**
+     * Information about the archived state of an app. Set only if an app is archived.
+     *
+     * @hide
+     */
+    @Immutable.Ignore
+    @Nullable
+    ArchiveState getArchiveState();
 }
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 73fb672..cce18a8 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageUserStateDefault.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageUserStateDefault.java
@@ -136,6 +136,11 @@
     }
 
     @Override
+    public @PackageManager.UserMinAspectRatio int getMinAspectRatio() {
+        return PackageManager.USER_MIN_ASPECT_RATIO_UNSET;
+    }
+
+    @Override
     public long getFirstInstallTimeMillis() {
         return 0;
     }
@@ -180,4 +185,10 @@
             @NonNull ComponentName componentName) {
         return null;
     }
+
+    @Nullable
+    @Override
+    public ArchiveState getArchiveState() {
+        return null;
+    }
 }
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 ed4aab9..6ac7c34 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java
@@ -16,6 +16,7 @@
 
 package com.android.server.pm.pkg;
 
+import android.annotation.CurrentTimeMillisLong;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.ComponentName;
@@ -61,6 +62,7 @@
     private int mDistractionFlags;
     private boolean mInstantApp;
     private boolean mVirtualPreload;
+    @PackageManager.EnabledState
     private int mEnabledState = PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
     @PackageManager.InstallReason
     private int mInstallReason = PackageManager.INSTALL_REASON_UNKNOWN;
@@ -81,6 +83,9 @@
     @Nullable
     private String mSplashScreenTheme;
 
+    @PackageManager.UserMinAspectRatio
+    private int mMinAspectRatio = PackageManager.USER_MIN_ASPECT_RATIO_UNSET;
+
     /**
      * Suspending package to suspend params
      */
@@ -90,12 +95,15 @@
     @Nullable
     private WatchedArrayMap<ComponentName, Pair<String, Integer>> mComponentLabelIconOverrideMap;
 
-    private long mFirstInstallTime;
+    private @CurrentTimeMillisLong long mFirstInstallTimeMillis;
 
     // TODO(b/239050028): Remove, enforce notifying parent through PMS commit method
     @Nullable
     private Watchable mWatchable;
 
+    @Nullable
+    private ArchiveState mArchiveState;
+
     @NonNull
     final SnapshotCache<PackageUserStateImpl> mSnapshot;
 
@@ -144,10 +152,12 @@
         mHarmfulAppWarning = other.mHarmfulAppWarning;
         mLastDisableAppCaller = other.mLastDisableAppCaller;
         mSplashScreenTheme = other.mSplashScreenTheme;
+        mMinAspectRatio = other.mMinAspectRatio;
         mSuspendParams = other.mSuspendParams == null ? null : other.mSuspendParams.snapshot();
         mComponentLabelIconOverrideMap = other.mComponentLabelIconOverrideMap == null
                 ? null : other.mComponentLabelIconOverrideMap.snapshot();
-        mFirstInstallTime = other.mFirstInstallTime;
+        mFirstInstallTimeMillis = other.mFirstInstallTimeMillis;
+        mArchiveState = other.mArchiveState;
         mSnapshot = new SnapshotCache.Sealed<>();
     }
 
@@ -506,6 +516,19 @@
     }
 
     /**
+     * Sets user min aspect ratio override value
+     * @see PackageManager.UserMinAspectRatio
+     */
+    public @NonNull PackageUserStateImpl setMinAspectRatio(
+            @PackageManager.UserMinAspectRatio int value) {
+        mMinAspectRatio = value;
+        com.android.internal.util.AnnotationValidations.validate(
+                PackageManager.UserMinAspectRatio.class, null, mMinAspectRatio);
+        onChanged();
+        return this;
+    }
+
+    /**
      * Suspending package to suspend params
      */
     public @NonNull PackageUserStateImpl setSuspendParams(
@@ -538,8 +561,20 @@
         return this;
     }
 
-    public @NonNull PackageUserStateImpl setFirstInstallTime(long value) {
-        mFirstInstallTime = value;
+    public @NonNull PackageUserStateImpl setFirstInstallTimeMillis(long value) {
+        mFirstInstallTimeMillis = value;
+        onChanged();
+        return this;
+    }
+
+    /**
+     * Sets the value for {@link #getArchiveState()}.
+     *
+     * @hide
+     */
+    @NonNull
+    public PackageUserStateImpl setArchiveState(@NonNull ArchiveState archiveState) {
+        mArchiveState = archiveState;
         onChanged();
         return this;
     }
@@ -643,7 +678,7 @@
     }
 
     @DataClass.Generated.Member
-    public int getEnabledState() {
+    public @PackageManager.EnabledState int getEnabledState() {
         return mEnabledState;
     }
 
@@ -677,6 +712,11 @@
         return mSplashScreenTheme;
     }
 
+    @DataClass.Generated.Member
+    public @PackageManager.UserMinAspectRatio int getMinAspectRatio() {
+        return mMinAspectRatio;
+    }
+
     /**
      * Suspending package to suspend params
      */
@@ -691,8 +731,13 @@
     }
 
     @DataClass.Generated.Member
-    public long getFirstInstallTimeMillis() {
-        return mFirstInstallTime;
+    public @CurrentTimeMillisLong long getFirstInstallTimeMillis() {
+        return mFirstInstallTimeMillis;
+    }
+
+    @DataClass.Generated.Member
+    public @Nullable ArchiveState getArchiveState() {
+        return mArchiveState;
     }
 
     @DataClass.Generated.Member
@@ -764,10 +809,12 @@
                 && Objects.equals(mOverlayPaths, that.mOverlayPaths)
                 && Objects.equals(mSharedLibraryOverlayPaths, that.mSharedLibraryOverlayPaths)
                 && Objects.equals(mSplashScreenTheme, that.mSplashScreenTheme)
+                && mMinAspectRatio == that.mMinAspectRatio
                 && Objects.equals(mSuspendParams, that.mSuspendParams)
                 && Objects.equals(mComponentLabelIconOverrideMap, that.mComponentLabelIconOverrideMap)
-                && mFirstInstallTime == that.mFirstInstallTime
+                && mFirstInstallTimeMillis == that.mFirstInstallTimeMillis
                 && watchableEquals(that.mWatchable)
+                && Objects.equals(mArchiveState, that.mArchiveState)
                 && snapshotEquals(that.mSnapshot);
     }
 
@@ -796,19 +843,21 @@
         _hash = 31 * _hash + Objects.hashCode(mOverlayPaths);
         _hash = 31 * _hash + Objects.hashCode(mSharedLibraryOverlayPaths);
         _hash = 31 * _hash + Objects.hashCode(mSplashScreenTheme);
+        _hash = 31 * _hash + mMinAspectRatio;
         _hash = 31 * _hash + Objects.hashCode(mSuspendParams);
         _hash = 31 * _hash + Objects.hashCode(mComponentLabelIconOverrideMap);
-        _hash = 31 * _hash + Long.hashCode(mFirstInstallTime);
+        _hash = 31 * _hash + Long.hashCode(mFirstInstallTimeMillis);
         _hash = 31 * _hash + watchableHashCode();
+        _hash = 31 * _hash + Objects.hashCode(mArchiveState);
         _hash = 31 * _hash + snapshotHashCode();
         return _hash;
     }
 
     @DataClass.Generated(
-            time = 1668033772891L,
+            time = 1689171513404L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java",
-            inputSignatures = "protected @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  boolean mInstalled\nprivate  boolean mStopped\nprivate  boolean mNotLaunched\nprivate  boolean mHidden\nprivate  int mDistractionFlags\nprivate  boolean mInstantApp\nprivate  boolean mVirtualPreload\nprivate  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.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  long mFirstInstallTime\nprivate @android.annotation.Nullable com.android.server.utils.Watchable mWatchable\nfinal @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl> mSnapshot\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 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 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 setFirstInstallTime(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()\nclass PackageUserStateImpl extends com.android.server.utils.WatchableImpl implements [com.android.server.pm.pkg.PackageUserStateInternal, com.android.server.utils.Snappable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=false, genEqualsHashCode=true)")
+            inputSignatures = "protected @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  boolean mInstalled\nprivate  boolean mStopped\nprivate  boolean mNotLaunched\nprivate  boolean mHidden\nprivate  int mDistractionFlags\nprivate  boolean mInstantApp\nprivate  boolean mVirtualPreload\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  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 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()\nclass PackageUserStateImpl extends com.android.server.utils.WatchableImpl implements [com.android.server.pm.pkg.PackageUserStateInternal, com.android.server.utils.Snappable]\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/mutate/PackageStateMutator.java b/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java
index 8125b0f..8430cf7 100644
--- a/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java
+++ b/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java
@@ -439,6 +439,16 @@
                 }
                 return null;
             }
+
+            @NonNull
+            @Override
+            public PackageUserStateWrite setMinAspectRatio(
+                    @PackageManager.UserMinAspectRatio int aspectRatio) {
+                if (mUserState != null) {
+                    mUserState.setMinAspectRatio(aspectRatio);
+                }
+                return this;
+            }
         }
     }
 }
diff --git a/services/core/java/com/android/server/pm/pkg/mutate/PackageUserStateWrite.java b/services/core/java/com/android/server/pm/pkg/mutate/PackageUserStateWrite.java
index 11d6d97..0c6c672 100644
--- a/services/core/java/com/android/server/pm/pkg/mutate/PackageUserStateWrite.java
+++ b/services/core/java/com/android/server/pm/pkg/mutate/PackageUserStateWrite.java
@@ -22,6 +22,7 @@
 import android.content.pm.PackageManager;
 import android.content.pm.overlay.OverlayPaths;
 
+import com.android.server.pm.pkg.PackageUserStateImpl;
 import com.android.server.pm.pkg.SuspendParams;
 
 public interface PackageUserStateWrite {
@@ -68,4 +69,8 @@
     @NonNull
     PackageUserStateWrite setComponentLabelIcon(@NonNull ComponentName componentName,
             @Nullable String nonLocalizedLabel, @Nullable Integer icon);
+
+    /** @see PackageUserStateImpl#setMinAspectRatio(int) */
+    @NonNull
+    PackageUserStateWrite setMinAspectRatio(@PackageManager.UserMinAspectRatio int aspectRatio);
 }
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java
index 7fc3356..f1f0fa3 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java
@@ -456,7 +456,7 @@
     List<ParsedActivity> getReceivers();
 
     @NonNull
-    List<String> getRequestedPermissions();
+    Set<String> getRequestedPermissions();
 
     @Nullable
     Boolean getResizeableActivity();
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 1567af0..e2cb87e 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
@@ -2958,7 +2958,7 @@
         final int listSize = mSplitPermissionInfos.size();
         for (int is = 0; is < listSize; is++) {
             final PermissionManager.SplitPermissionInfo spi = mSplitPermissionInfos.get(is);
-            List<String> requestedPermissions = pkg.getRequestedPermissions();
+            Set<String> requestedPermissions = pkg.getRequestedPermissions();
             if (pkg.getTargetSdkVersion() >= spi.getTargetSdk()
                     || !requestedPermissions.contains(spi.getSplitPermission())) {
                 continue;
diff --git a/services/core/java/com/android/server/policy/ModifierShortcutManager.java b/services/core/java/com/android/server/policy/ModifierShortcutManager.java
index 784e177..44cc3e7 100644
--- a/services/core/java/com/android/server/policy/ModifierShortcutManager.java
+++ b/services/core/java/com/android/server/policy/ModifierShortcutManager.java
@@ -20,7 +20,6 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
-import android.content.pm.ActivityInfo;
 import android.content.pm.PackageManager;
 import android.content.res.XmlResourceParser;
 import android.os.RemoteException;
@@ -59,8 +58,8 @@
     private static final String ATTRIBUTE_CATEGORY = "category";
     private static final String ATTRIBUTE_SHIFT = "shift";
 
-    private final SparseArray<ShortcutInfo> mIntentShortcuts = new SparseArray<>();
-    private final SparseArray<ShortcutInfo> mShiftShortcuts = new SparseArray<>();
+    private final SparseArray<Intent> mIntentShortcuts = new SparseArray<>();
+    private final SparseArray<Intent> mShiftShortcuts = new SparseArray<>();
 
     private LongSparseArray<IShortcutService> mShortcutKeyServices = new LongSparseArray<>();
 
@@ -118,26 +117,26 @@
             return null;
         }
 
-        ShortcutInfo shortcut = null;
+        Intent shortcutIntent = null;
 
         // If the Shift key is pressed, then search for the shift shortcuts.
-        SparseArray<ShortcutInfo> shortcutMap = isShiftOn ? mShiftShortcuts : mIntentShortcuts;
+        SparseArray<Intent> shortcutMap = isShiftOn ? mShiftShortcuts : mIntentShortcuts;
 
         // First try the exact keycode (with modifiers).
         int shortcutChar = kcm.get(keyCode, metaState);
         if (shortcutChar != 0) {
-            shortcut = shortcutMap.get(shortcutChar);
+            shortcutIntent = shortcutMap.get(shortcutChar);
         }
 
         // Next try the primary character on that key.
-        if (shortcut == null) {
+        if (shortcutIntent == null) {
             shortcutChar = Character.toLowerCase(kcm.getDisplayLabel(keyCode));
             if (shortcutChar != 0) {
-                shortcut = shortcutMap.get(shortcutChar);
+                shortcutIntent = shortcutMap.get(shortcutChar);
             }
         }
 
-        return (shortcut != null) ? shortcut.intent : null;
+        return shortcutIntent;
     }
 
     private void loadShortcuts() {
@@ -173,12 +172,10 @@
                 final boolean isShiftShortcut = (shiftName != null && shiftName.equals("true"));
 
                 final Intent intent;
-                final String title;
                 if (packageName != null && className != null) {
-                    ActivityInfo info = null;
                     ComponentName componentName = new ComponentName(packageName, className);
                     try {
-                        info = packageManager.getActivityInfo(componentName,
+                        packageManager.getActivityInfo(componentName,
                                 PackageManager.MATCH_DIRECT_BOOT_AWARE
                                         | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
                                         | PackageManager.MATCH_UNINSTALLED_PACKAGES);
@@ -187,7 +184,7 @@
                                 new String[] { packageName });
                         componentName = new ComponentName(packages[0], className);
                         try {
-                            info = packageManager.getActivityInfo(componentName,
+                            packageManager.getActivityInfo(componentName,
                                     PackageManager.MATCH_DIRECT_BOOT_AWARE
                                             | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
                                             | PackageManager.MATCH_UNINSTALLED_PACKAGES);
@@ -201,21 +198,18 @@
                     intent = new Intent(Intent.ACTION_MAIN);
                     intent.addCategory(Intent.CATEGORY_LAUNCHER);
                     intent.setComponent(componentName);
-                    title = info.loadLabel(packageManager).toString();
                 } else if (categoryName != null) {
                     intent = Intent.makeMainSelectorActivity(Intent.ACTION_MAIN, categoryName);
-                    title = "";
                 } else {
                     Log.w(TAG, "Unable to add bookmark for shortcut " + shortcutName
                             + ": missing package/class or category attributes");
                     continue;
                 }
 
-                ShortcutInfo shortcut = new ShortcutInfo(title, intent);
                 if (isShiftShortcut) {
-                    mShiftShortcuts.put(shortcutChar, shortcut);
+                    mShiftShortcuts.put(shortcutChar, intent);
                 } else {
-                    mIntentShortcuts.put(shortcutChar, shortcut);
+                    mIntentShortcuts.put(shortcutChar, intent);
                 }
             }
         } catch (XmlPullParserException | IOException e) {
@@ -370,14 +364,4 @@
 
         return false;
     }
-
-    private static final class ShortcutInfo {
-        public final String title;
-        public final Intent intent;
-
-        ShortcutInfo(String title, Intent intent) {
-            this.title = title;
-            this.intent = intent;
-        }
-    }
 }
diff --git a/services/core/java/com/android/server/policy/OWNERS b/services/core/java/com/android/server/policy/OWNERS
index 8887e40..f20ea4b 100644
--- a/services/core/java/com/android/server/policy/OWNERS
+++ b/services/core/java/com/android/server/policy/OWNERS
@@ -1,3 +1,6 @@
 include /services/core/java/com/android/server/wm/OWNERS
 include /services/core/java/com/android/server/input/OWNERS
-include /services/core/java/com/android/server/pm/permission/OWNERS
+per-file *AppOp* = file:/services/core/java/com/android/server/pm/permission/OWNERS
+per-file *Permission* = file:/services/core/java/com/android/server/pm/permission/OWNERS
+per-file OWNERS = file:/services/core/java/com/android/server/pm/permission/OWNERS
+per-file TEST_MAPPING = file:/services/core/java/com/android/server/pm/permission/OWNERS
diff --git a/services/core/java/com/android/server/policy/PermissionPolicyService.java b/services/core/java/com/android/server/policy/PermissionPolicyService.java
index 0e99e7e..d6e35e8 100644
--- a/services/core/java/com/android/server/policy/PermissionPolicyService.java
+++ b/services/core/java/com/android/server/policy/PermissionPolicyService.java
@@ -58,7 +58,6 @@
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.PackageManagerInternal.PackageListObserver;
 import android.content.pm.PermissionInfo;
-import android.content.pm.UserPackage;
 import android.content.res.Resources;
 import android.os.Build;
 import android.os.Bundle;
@@ -142,18 +141,18 @@
     private OnInitializedCallback mOnInitializedCallback;
 
     /**
-     * Whether an async {@link #synchronizePackagePermissionsAndAppOpsForUser} is currently
-     * scheduled for a package/user.
+     * Whether an async {@link #synchronizeUidPermissionsAndAppOps} is currently
+     * scheduled for a UID.
      */
     @GuardedBy("mLock")
-    private final ArraySet<UserPackage> mIsPackageSyncsScheduled = new ArraySet<>();
+    private final SparseBooleanArray mIsUidSyncScheduled = new SparseBooleanArray();
 
     /**
      * Whether an async {@link #resetAppOpPermissionsIfNotRequestedForUid} is currently
      * scheduled for a uid.
      */
     @GuardedBy("mLock")
-    private final SparseBooleanArray mIsUidSyncScheduled = new SparseBooleanArray();
+    private final SparseBooleanArray mIsUidResetScheduled = new SparseBooleanArray();
 
     /**
      * This change reflects the presence of the new Notification Permission
@@ -199,7 +198,8 @@
                         .getUserIds();
                 for (final int userId : userIds) {
                     if (isStarted(userId)) {
-                        synchronizePackagePermissionsAndAppOpsForUser(packageName, userId);
+                        final int uid = UserHandle.getUid(userId, appId);
+                        synchronizeUidPermissionsAndAppOps(uid);
                     }
                 }
             }
@@ -210,8 +210,8 @@
                         .getUserIds();
                 for (final int userId : userIds) {
                     if (isStarted(userId)) {
-                        synchronizePackagePermissionsAndAppOpsForUser(packageName, userId);
                         final int uid = UserHandle.getUid(userId, appId);
+                        synchronizeUidPermissionsAndAppOps(uid);
                         resetAppOpPermissionsIfNotRequestedForUid(uid);
                     }
                 }
@@ -230,14 +230,13 @@
             }
         });
 
-        mPermissionManagerInternal.addOnRuntimePermissionStateChangedListener(
-                this::synchronizePackagePermissionsAndAppOpsAsyncForUser);
+        mPackageManager.addOnPermissionsChangeListener(
+                this::synchronizeUidPermissionsAndAppOpsAsync);
 
         mAppOpsCallback = new IAppOpsCallback.Stub() {
             public void opChanged(int op, int uid, @Nullable String packageName) {
                 if (packageName != null) {
-                    synchronizePackagePermissionsAndAppOpsAsyncForUser(packageName,
-                            UserHandle.getUserId(uid));
+                    synchronizeUidPermissionsAndAppOpsAsync(uid);
                 }
                 resetAppOpPermissionsIfNotRequestedForUidAsync(uid);
             }
@@ -381,24 +380,23 @@
         return AppOpsManager.opToSwitch(op);
     }
 
-    private void synchronizePackagePermissionsAndAppOpsAsyncForUser(@NonNull String packageName,
-            @UserIdInt int changedUserId) {
-        if (isStarted(changedUserId)) {
+    private void synchronizeUidPermissionsAndAppOpsAsync(int uid) {
+        final int userId = UserHandle.getUserId(uid);
+        if (isStarted(userId)) {
             synchronized (mLock) {
-                if (mIsPackageSyncsScheduled.add(UserPackage.of(changedUserId, packageName))) {
+                if (!mIsUidSyncScheduled.get(uid)) {
                     // TODO(b/165030092): migrate this to PermissionThread.getHandler().
-                    // synchronizePackagePermissionsAndAppOpsForUser is a heavy operation.
+                    // synchronizeUidPermissionsAndAppOps is a heavy operation.
                     // Dispatched on a PermissionThread, it interferes with user switch.
                     // FgThread is busy and schedules it after most of the switch is done.
                     // A possible solution is to delay the callback.
                     FgThread.getHandler().sendMessage(PooledLambda.obtainMessage(
-                            PermissionPolicyService
-                                    ::synchronizePackagePermissionsAndAppOpsForUser,
-                            this, packageName, changedUserId));
+                            PermissionPolicyService::synchronizeUidPermissionsAndAppOps, this,
+                            uid));
+                    mIsUidSyncScheduled.put(uid, true);
                 } else {
                     if (DEBUG) {
-                        Slog.v(LOG_TAG, "sync for " + packageName + "/" + changedUserId
-                                + " already scheduled");
+                        Slog.v(LOG_TAG, "sync for UID " + uid + " already scheduled");
                     }
                 }
             }
@@ -585,6 +583,10 @@
     }
 
     private void grantOrUpgradeDefaultRuntimePermissionsIfNeeded(@UserIdInt int userId) {
+        if (PermissionManager.USE_ACCESS_CHECKING_SERVICE) {
+            return;
+        }
+
         if (DEBUG) Slog.i(LOG_TAG, "grantOrUpgradeDefaultPermsIfNeeded(" + userId + ")");
         final TimingsTraceAndSlog t = new TimingsTraceAndSlog();
 
@@ -648,39 +650,27 @@
     }
 
     /**
-     * Synchronize a single package.
+     * Synchronize a single UID.
      */
-    private void synchronizePackagePermissionsAndAppOpsForUser(@NonNull String packageName,
-            @UserIdInt int userId) {
+    private void synchronizeUidPermissionsAndAppOps(int uid) {
         synchronized (mLock) {
-            mIsPackageSyncsScheduled.remove(UserPackage.of(userId, packageName));
+            mIsUidSyncScheduled.delete(uid);
         }
 
         if (DEBUG) {
             Slog.v(LOG_TAG,
-                    "synchronizePackagePermissionsAndAppOpsForUser(" + packageName + ", "
-                            + userId + ")");
+                    "synchronizePackagePermissionsAndAppOpsForUser(" + uid + ")");
         }
 
-        final PackageManagerInternal packageManagerInternal = LocalServices.getService(
-                PackageManagerInternal.class);
-        final PackageInfo pkg = packageManagerInternal.getPackageInfo(packageName, 0,
-                Process.SYSTEM_UID, userId);
-        if (pkg == null) {
-            return;
-        }
+        final UserHandle user = UserHandle.getUserHandleForUid(uid);
         final PermissionToOpSynchroniser synchroniser = new PermissionToOpSynchroniser(
-                getUserContext(getContext(), UserHandle.of(userId)));
-        synchroniser.addPackage(pkg.packageName);
-        final String[] sharedPkgNames = packageManagerInternal.getSharedUserPackagesForPackage(
-                pkg.packageName, userId);
-
-        for (String sharedPkgName : sharedPkgNames) {
-            final AndroidPackage sharedPkg = packageManagerInternal
-                    .getPackage(sharedPkgName);
-            if (sharedPkg != null) {
-                synchroniser.addPackage(sharedPkg.getPackageName());
-            }
+                getUserContext(getContext(), user));
+        final int appId = UserHandle.getAppId(uid);
+        final List<AndroidPackage> pkgs = mPackageManagerInternal.getPackagesForAppId(appId);
+        final int pkgsSize = pkgs.size();
+        for (int i = 0; i < pkgsSize; i++) {
+            final AndroidPackage pkg = pkgs.get(i);
+            synchroniser.addPackage(pkg.getPackageName());
         }
         synchroniser.syncPackages();
     }
@@ -708,8 +698,8 @@
     private void resetAppOpPermissionsIfNotRequestedForUidAsync(int uid) {
         if (isStarted(UserHandle.getUserId(uid))) {
             synchronized (mLock) {
-                if (!mIsUidSyncScheduled.get(uid)) {
-                    mIsUidSyncScheduled.put(uid, true);
+                if (!mIsUidResetScheduled.get(uid)) {
+                    mIsUidResetScheduled.put(uid, true);
                     PermissionThread.getHandler().sendMessage(PooledLambda.obtainMessage(
                             PermissionPolicyService::resetAppOpPermissionsIfNotRequestedForUid,
                             this, uid));
@@ -720,7 +710,7 @@
 
     private void resetAppOpPermissionsIfNotRequestedForUid(int uid) {
         synchronized (mLock) {
-            mIsUidSyncScheduled.delete(uid);
+            mIsUidResetScheduled.delete(uid);
         }
 
         final Context context = getContext();
@@ -858,7 +848,7 @@
         /**
          * Set app ops that were added in {@link #addPackage}.
          *
-         * <p>This processes ops previously added by {@link #addAppOps(PackageInfo, String)}
+         * <p>This processes ops previously added by {@link #addPackage(String)})}
          */
         private void syncPackages() {
             // Remember which ops were already set. This makes sure that we always set the most
@@ -1431,8 +1421,8 @@
             }
             boolean hasCreatedNotificationChannels = mNotificationManager
                     .getNumNotificationChannelsForPackage(pkgName, uid, true) > 0;
-            boolean granted = mPermissionManagerInternal.checkUidPermission(uid, POST_NOTIFICATIONS)
-                    == PackageManager.PERMISSION_GRANTED;
+            boolean granted = mPermissionManagerInternal.checkUidPermission(uid, POST_NOTIFICATIONS,
+                    Context.DEVICE_ID_DEFAULT) == PackageManager.PERMISSION_GRANTED;
             int flags = mPackageManager.getPermissionFlags(POST_NOTIFICATIONS, pkgName, user);
             boolean explicitlySet = (flags & PermissionManager.EXPLICIT_SET_FLAGS) != 0;
             return !granted && hasCreatedNotificationChannels && !explicitlySet;
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index a88e2b0..7bbe878 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -68,6 +68,7 @@
 import static android.view.WindowManagerGlobal.ADD_PERMISSION_DENIED;
 
 import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.SCREENSHOT_KEYCHORD_DELAY;
+import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_WEAR_TRIPLE_PRESS_GESTURE;
 import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_COVERED;
 import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_COVER_ABSENT;
 import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_UNCOVERED;
@@ -91,6 +92,7 @@
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.app.ActivityManager;
 import android.app.ActivityManager.RecentTaskInfo;
 import android.app.ActivityManagerInternal;
@@ -124,6 +126,7 @@
 import android.hardware.hdmi.HdmiControlManager;
 import android.hardware.hdmi.HdmiPlaybackClient;
 import android.hardware.hdmi.HdmiPlaybackClient.OneTouchPlayCallback;
+import android.hardware.input.InputManager;
 import android.media.AudioManager;
 import android.media.AudioManagerInternal;
 import android.media.AudioSystem;
@@ -190,6 +193,7 @@
 
 import com.android.internal.R;
 import com.android.internal.accessibility.AccessibilityShortcutController;
+import com.android.internal.accessibility.util.AccessibilityStatsLogUtils;
 import com.android.internal.accessibility.util.AccessibilityUtils;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.AssistUtils;
@@ -205,6 +209,7 @@
 import com.android.internal.policy.TransitionAnimation;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.FrameworkStatsLog;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.server.AccessibilityManagerInternal;
 import com.android.server.ExtconStateObserver;
@@ -215,6 +220,7 @@
 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.inputmethod.InputMethodManagerInternal;
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.policy.KeyCombinationManager.TwoKeysCombinationRule;
@@ -261,7 +267,13 @@
     // Whether to allow devices placed in vr headset viewers to have an alternative Home intent.
     static final boolean ENABLE_VR_HEADSET_HOME_CAPTURE = true;
 
+    // --------- Key behavior definitions below. ---------
+    // NOTE: When updating the valid range of any of these behaviors, and if that behavior has a
+    // Settings key associated to it for dynamic update, update its valid Settings value range in
+    // android.provider.settings.validators.GlobalSettingsValidators.
+
     // must match: config_shortPressOnPowerBehavior in config.xml
+    // The config value can be overridden using Settings.Global.POWER_BUTTON_SHORT_PRESS
     static final int SHORT_PRESS_POWER_NOTHING = 0;
     static final int SHORT_PRESS_POWER_GO_TO_SLEEP = 1;
     static final int SHORT_PRESS_POWER_REALLY_GO_TO_SLEEP = 2;
@@ -272,6 +284,7 @@
     static final int SHORT_PRESS_POWER_DREAM_OR_SLEEP = 7;
 
     // must match: config_LongPressOnPowerBehavior in config.xml
+    // The config value can be overridden using Settings.Global.POWER_BUTTON_LONG_PRESS
     static final int LONG_PRESS_POWER_NOTHING = 0;
     static final int LONG_PRESS_POWER_GLOBAL_ACTIONS = 1;
     static final int LONG_PRESS_POWER_SHUT_OFF = 2;
@@ -280,6 +293,7 @@
     static final int LONG_PRESS_POWER_ASSISTANT = 5; // Settings.Secure.ASSISTANT
 
     // must match: config_veryLongPresOnPowerBehavior in config.xml
+    // The config value can be overridden using Settings.Global.POWER_BUTTON_VERY_LONG_PRESS
     static final int VERY_LONG_PRESS_POWER_NOTHING = 0;
     static final int VERY_LONG_PRESS_POWER_GLOBAL_ACTIONS = 1;
 
@@ -289,6 +303,8 @@
     static final int POWER_VOLUME_UP_BEHAVIOR_GLOBAL_ACTIONS = 2;
 
     // must match: config_doublePressOnPowerBehavior in config.xml
+    // The config value can be overridden using Settings.Global.POWER_BUTTON_DOUBLE_PRESS and/or
+    // Settings.Global.POWER_BUTTON_TRIPLE_PRESS
     static final int MULTI_PRESS_POWER_NOTHING = 0;
     static final int MULTI_PRESS_POWER_THEATER_MODE = 1;
     static final int MULTI_PRESS_POWER_BRIGHTNESS_BOOST = 2;
@@ -319,18 +335,22 @@
     static final int PENDING_KEY_NULL = -1;
 
     // Must match: config_shortPressOnStemPrimaryBehavior in config.xml
+    // The config value can be overridden using Settings.Global.STEM_PRIMARY_BUTTON_SHORT_PRESS
     static final int SHORT_PRESS_PRIMARY_NOTHING = 0;
     static final int SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS = 1;
 
     // Must match: config_longPressOnStemPrimaryBehavior in config.xml
+    // The config value can be overridden using Settings.Global.STEM_PRIMARY_BUTTON_LONG_PRESS
     static final int LONG_PRESS_PRIMARY_NOTHING = 0;
     static final int LONG_PRESS_PRIMARY_LAUNCH_VOICE_ASSISTANT = 1;
 
     // Must match: config_doublePressOnStemPrimaryBehavior in config.xml
+    //The config value can be overridden using Settings.Global.STEM_PRIMARY_BUTTON_DOUBLE_PRESS
     static final int DOUBLE_PRESS_PRIMARY_NOTHING = 0;
     static final int DOUBLE_PRESS_PRIMARY_SWITCH_RECENT_APP = 1;
 
     // Must match: config_triplePressOnStemPrimaryBehavior in config.xml
+    // The config value can be overridden using Settings.Global.STEM_PRIMARY_BUTTON_TRIPLE_PRESS
     static final int TRIPLE_PRESS_PRIMARY_NOTHING = 0;
     static final int TRIPLE_PRESS_PRIMARY_TOGGLE_ACCESSIBILITY = 1;
 
@@ -536,10 +556,6 @@
     int mShortPressOnSleepBehavior;
     int mShortPressOnWindowBehavior;
     int mPowerVolUpBehavior;
-    int mShortPressOnStemPrimaryBehavior;
-    int mDoublePressOnStemPrimaryBehavior;
-    int mTriplePressOnStemPrimaryBehavior;
-    int mLongPressOnStemPrimaryBehavior;
     boolean mStylusButtonsEnabled = true;
     boolean mHasSoftInput = false;
     boolean mHapticTextHandleEnabled;
@@ -553,6 +569,12 @@
     int mSearchKeyBehavior;
     ComponentName mSearchKeyTargetActivity;
 
+    // Key Behavior - Stem Primary
+    private int mShortPressOnStemPrimaryBehavior;
+    private int mDoublePressOnStemPrimaryBehavior;
+    private int mTriplePressOnStemPrimaryBehavior;
+    private int mLongPressOnStemPrimaryBehavior;
+
     private boolean mHandleVolumeKeysInWM;
 
     private boolean mPendingKeyguardOccluded;
@@ -595,8 +617,8 @@
     // What we do when the user double-taps on home
     private int mDoubleTapOnHomeBehavior;
 
-    // Whether to lock the device after the next app transition has finished.
-    boolean mLockAfterAppTransitionFinished;
+    // Whether to lock the device after the next dreaming transition has finished.
+    private boolean mLockAfterDreamingTransitionFinished;
 
     // Allowed theater mode wake actions
     private boolean mAllowTheaterModeWakeFromKey;
@@ -627,6 +649,8 @@
 
     SettingsObserver mSettingsObserver;
     ModifierShortcutManager mModifierShortcutManager;
+    /** Currently fully consumed key codes per device */
+    private final SparseArray<Set<Integer>> mConsumedKeysForDevice = new SparseArray<>();
     PowerManager.WakeLock mBroadcastWakeLock;
     PowerManager.WakeLock mPowerKeyWakeLock;
     boolean mHavePendingMediaKeyRepeatWithWakeLock;
@@ -676,6 +700,7 @@
     private static final int MSG_LAUNCH_ASSIST = 23;
     private static final int MSG_RINGER_TOGGLE_CHORD = 24;
     private static final int MSG_SWITCH_KEYBOARD_LAYOUT = 25;
+    private static final int MSG_LOG_KEYBOARD_SYSTEM_EVENT = 26;
 
     private class PolicyHandler extends Handler {
         @Override
@@ -752,6 +777,9 @@
                 case MSG_SWITCH_KEYBOARD_LAYOUT:
                     handleSwitchKeyboardLayout(msg.arg1, msg.arg2);
                     break;
+                case MSG_LOG_KEYBOARD_SYSTEM_EVENT:
+                    handleKeyboardSystemEvent(msg.arg2, (KeyEvent) msg.obj);
+                    break;
             }
         }
     }
@@ -796,6 +824,15 @@
                     Settings.Secure.SYSTEM_NAVIGATION_KEYS_ENABLED), false, this,
                     UserHandle.USER_ALL);
             resolver.registerContentObserver(Settings.Global.getUriFor(
+                    Settings.Global.POWER_BUTTON_SHORT_PRESS), false, this,
+                    UserHandle.USER_ALL);
+            resolver.registerContentObserver(Settings.Global.getUriFor(
+                    Settings.Global.POWER_BUTTON_DOUBLE_PRESS), false, this,
+                    UserHandle.USER_ALL);
+            resolver.registerContentObserver(Settings.Global.getUriFor(
+                    Settings.Global.POWER_BUTTON_TRIPLE_PRESS), false, this,
+                    UserHandle.USER_ALL);
+            resolver.registerContentObserver(Settings.Global.getUriFor(
                     Settings.Global.POWER_BUTTON_LONG_PRESS), false, this,
                     UserHandle.USER_ALL);
             resolver.registerContentObserver(Settings.Global.getUriFor(
@@ -805,6 +842,18 @@
                     Settings.Global.POWER_BUTTON_VERY_LONG_PRESS), false, this,
                     UserHandle.USER_ALL);
             resolver.registerContentObserver(Settings.Global.getUriFor(
+                    Settings.Global.STEM_PRIMARY_BUTTON_SHORT_PRESS), false, this,
+                    UserHandle.USER_ALL);
+            resolver.registerContentObserver(Settings.Global.getUriFor(
+                    Settings.Global.STEM_PRIMARY_BUTTON_DOUBLE_PRESS), false, this,
+                    UserHandle.USER_ALL);
+            resolver.registerContentObserver(Settings.Global.getUriFor(
+                    Settings.Global.STEM_PRIMARY_BUTTON_TRIPLE_PRESS), false, this,
+                    UserHandle.USER_ALL);
+            resolver.registerContentObserver(Settings.Global.getUriFor(
+                    Settings.Global.STEM_PRIMARY_BUTTON_LONG_PRESS), false, this,
+                    UserHandle.USER_ALL);
+            resolver.registerContentObserver(Settings.Global.getUriFor(
                     Settings.Global.KEY_CHORD_POWER_VOLUME_UP), false, this,
                     UserHandle.USER_ALL);
             resolver.registerContentObserver(Settings.Global.getUriFor(
@@ -818,7 +867,6 @@
 
         @Override public void onChange(boolean selfChange) {
             updateSettings();
-            updateRotation(false);
         }
     }
 
@@ -978,6 +1026,9 @@
     }
 
     private void interceptPowerKeyUp(KeyEvent event, boolean canceled) {
+        // Inform the StatusBar; but do not allow it to consume the event.
+        sendSystemKeyToStatusBarAsync(event);
+
         final boolean handled = canceled || mPowerKeyHandled;
 
         if (!handled) {
@@ -1098,7 +1149,7 @@
         synchronized (mLock) {
             // If the setting to lock instantly on power button press is true, then set the flag to
             // lock after the dream transition has finished.
-            mLockAfterAppTransitionFinished =
+            mLockAfterDreamingTransitionFinished =
                     mLockPatternUtils.getPowerButtonInstantlyLocks(mCurrentUserId);
         }
 
@@ -1419,7 +1470,20 @@
                 if (DEBUG_INPUT) {
                     Slog.d(TAG, "Executing stem primary triple press action behavior.");
                 }
-                toggleTalkBack();
+
+                if (Settings.System.getIntForUser(mContext.getContentResolver(),
+                        Settings.System.WEAR_ACCESSIBILITY_GESTURE_ENABLED,
+                        /* def= */ 0, UserHandle.USER_CURRENT) == 1) {
+                    /** Toggle talkback begin */
+                    ComponentName componentName = getTalkbackComponent();
+                    if (componentName != null && toggleTalkBack(componentName)) {
+                        /** log stem triple press telemetry if it's a talkback enabled event */
+                        logStemTriplePressAccessibilityTelemetry(componentName);
+                    }
+                    performHapticFeedback(HapticFeedbackConstants.CONFIRM, /* always = */ false,
+                        /* reason = */ "Stem primary - Triple Press - Toggle Accessibility");
+                    /** Toggle talkback end */
+                }
                 break;
         }
     }
@@ -1438,17 +1502,39 @@
         }
     }
 
-    private void toggleTalkBack() {
-        final ComponentName componentName = getTalkbackComponent();
-        if (componentName == null) {
-            return;
-        }
-
+    /**
+     * A function that toggles talkback service
+     *
+     * @return {@code true} if talkback is enabled, {@code false} if talkback is disabled
+     */
+    private boolean toggleTalkBack(ComponentName componentName) {
         final Set<ComponentName> enabledServices =
                 AccessibilityUtils.getEnabledServicesFromSettings(mContext, mCurrentUserId);
 
+        boolean isTalkbackAlreadyEnabled = enabledServices.contains(componentName);
         AccessibilityUtils.setAccessibilityServiceState(mContext, componentName,
-                !enabledServices.contains(componentName));
+                !isTalkbackAlreadyEnabled);
+        /** if isTalkbackAlreadyEnabled is true, then it's a disabled event so return false
+         * and if isTalkbackAlreadyEnabled is false, return true as it's an enabled event */
+        return !isTalkbackAlreadyEnabled;
+    }
+
+    /**
+     * A function that logs stem triple press accessibility telemetry
+     * If the user setup (Oobe) is not completed, set the
+     * WEAR_ACCESSIBILITY_GESTURE_ENABLED_DURING_OOBE
+     * setting which will be later logged via Settings Snapshot
+     * else, log ACCESSIBILITY_SHORTCUT_REPORTED atom
+     */
+    private void logStemTriplePressAccessibilityTelemetry(ComponentName componentName) {
+        if (!AccessibilityUtils.isUserSetupCompleted(mContext)) {
+            Settings.Secure.putInt(mContext.getContentResolver(),
+                    Settings.System.WEAR_ACCESSIBILITY_GESTURE_ENABLED_DURING_OOBE, 1);
+        } else {
+            AccessibilityStatsLogUtils.logAccessibilityShortcutActivated(mContext, componentName,
+                    ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_WEAR_TRIPLE_PRESS_GESTURE,
+                    /* serviceEnabled= */ true);
+        }
     }
 
     private ComponentName getTalkbackComponent() {
@@ -1809,7 +1895,7 @@
             mDisplayId = displayId;
         }
 
-        int handleHomeButton(IBinder focusedToken, KeyEvent event) {
+        boolean handleHomeButton(IBinder focusedToken, KeyEvent event) {
             final boolean keyguardOn = keyguardOn();
             final int repeatCount = event.getRepeatCount();
             final boolean down = event.getAction() == KeyEvent.ACTION_DOWN;
@@ -1830,12 +1916,12 @@
                 mHomePressed = false;
                 if (mHomeConsumed) {
                     mHomeConsumed = false;
-                    return -1;
+                    return true;
                 }
 
                 if (canceled) {
                     Log.i(TAG, "Ignoring HOME; event canceled.");
-                    return -1;
+                    return true;
                 }
 
                 // Delay handling home if a double-tap is possible.
@@ -1847,13 +1933,13 @@
                         mHomeDoubleTapPending = true;
                         mHandler.postDelayed(mHomeDoubleTapTimeoutRunnable,
                                 ViewConfiguration.getDoubleTapTimeout());
-                        return -1;
+                        return true;
                     }
                 }
 
                 // Post to main thread to avoid blocking input pipeline.
                 mHandler.post(() -> handleShortPressOnHome(mDisplayId));
-                return -1;
+                return true;
             }
 
             final KeyInterceptionInfo info =
@@ -1865,12 +1951,12 @@
                         || (info.layoutParamsType == TYPE_NOTIFICATION_SHADE
                         && isKeyguardShowing())) {
                     // the "app" is keyguard, so give it the key
-                    return 0;
+                    return false;
                 }
                 for (int t : WINDOW_TYPES_WHERE_HOME_DOESNT_WORK) {
                     if (info.layoutParamsType == t) {
                         // don't do anything, but also don't pass it to the app
-                        return -1;
+                        return true;
                     }
                 }
             }
@@ -1895,7 +1981,7 @@
                             event.getEventTime()));
                 }
             }
-            return -1;
+            return true;
         }
 
         private void handleDoubleTapOnHome() {
@@ -1994,6 +2080,21 @@
         Supplier<GlobalActions> getGlobalActionsFactory() {
             return () -> new GlobalActions(mContext, mWindowManagerFuncs);
         }
+
+        KeyguardServiceDelegate getKeyguardServiceDelegate() {
+            return new KeyguardServiceDelegate(mContext,
+                    new StateCallback() {
+                        @Override
+                        public void onTrustedChanged() {
+                            mWindowManagerFuncs.notifyKeyguardTrustedChanged();
+                        }
+
+                        @Override
+                        public void onShowingChanged() {
+                            mWindowManagerFuncs.onKeyguardShowingAndNotOccludedChanged();
+                        }
+                    });
+        }
     }
 
     /** {@inheritDoc} */
@@ -2136,21 +2237,15 @@
         mLongPressOnBackBehavior = mContext.getResources().getInteger(
                 com.android.internal.R.integer.config_longPressOnBackBehavior);
 
-        mShortPressOnPowerBehavior = mContext.getResources().getInteger(
-                com.android.internal.R.integer.config_shortPressOnPowerBehavior);
         mLongPressOnPowerBehavior = mContext.getResources().getInteger(
                 com.android.internal.R.integer.config_longPressOnPowerBehavior);
         mLongPressOnPowerAssistantTimeoutMs = mContext.getResources().getInteger(
                 com.android.internal.R.integer.config_longPressOnPowerDurationMs);
         mVeryLongPressOnPowerBehavior = mContext.getResources().getInteger(
                 com.android.internal.R.integer.config_veryLongPressOnPowerBehavior);
-        mDoublePressOnPowerBehavior = mContext.getResources().getInteger(
-                com.android.internal.R.integer.config_doublePressOnPowerBehavior);
         mPowerDoublePressTargetActivity = ComponentName.unflattenFromString(
             mContext.getResources().getString(
                 com.android.internal.R.string.config_doublePressOnPowerTargetActivity));
-        mTriplePressOnPowerBehavior = mContext.getResources().getInteger(
-                com.android.internal.R.integer.config_triplePressOnPowerBehavior);
         mShortPressOnSleepBehavior = mContext.getResources().getInteger(
                 com.android.internal.R.integer.config_shortPressOnSleepBehavior);
         mAllowStartActivityForLongPressOnPowerDuringSetup = mContext.getResources().getBoolean(
@@ -2232,37 +2327,28 @@
                         true /* notifyOccluded */);
 
                 synchronized (mLock) {
-                    mLockAfterAppTransitionFinished = false;
+                    mLockAfterDreamingTransitionFinished = false;
                 }
             }
 
             @Override
             public void onAppTransitionFinishedLocked(IBinder token) {
                 synchronized (mLock) {
-                    if (!mLockAfterAppTransitionFinished) {
-                        return;
+                    final DreamManagerInternal dreamManagerInternal = getDreamManagerInternal();
+                    // check both isDreaming and mLockAfterDreamingTransitionFinished before lockNow
+                    // so it won't relock after dreaming has stopped
+                    if (dreamManagerInternal != null && dreamManagerInternal.isDreaming()
+                            && mLockAfterDreamingTransitionFinished) {
+                        lockNow(null);
                     }
-                    mLockAfterAppTransitionFinished = false;
+                    mLockAfterDreamingTransitionFinished = false;
                 }
-
-                lockNow(null);
             }
         });
 
         mKeyguardDrawnTimeout = mContext.getResources().getInteger(
                 com.android.internal.R.integer.config_keyguardDrawnTimeout);
-        mKeyguardDelegate = new KeyguardServiceDelegate(mContext,
-                new StateCallback() {
-                    @Override
-                    public void onTrustedChanged() {
-                        mWindowManagerFuncs.notifyKeyguardTrustedChanged();
-                    }
-
-                    @Override
-                    public void onShowingChanged() {
-                        mWindowManagerFuncs.onKeyguardShowingAndNotOccludedChanged();
-                    }
-                });
+        mKeyguardDelegate = injector.getKeyguardServiceDelegate();
         initKeyCombinationRules();
         initSingleKeyGestureRules();
         mSideFpsEventHandler = new SideFpsEventHandler(mContext, mHandler, mPowerManager);
@@ -2287,6 +2373,7 @@
                             cancelPendingScreenshotChordAction();
                         }
                     });
+
             if (mHasFeatureWatch) {
                 mKeyCombinationManager.addRule(
                         new TwoKeysCombinationRule(KEYCODE_POWER, KEYCODE_STEM_PRIMARY) {
@@ -2572,14 +2659,6 @@
         if (mPackageManager.hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)) {
             mShortPressOnWindowBehavior = SHORT_PRESS_WINDOW_PICTURE_IN_PICTURE;
         }
-        mShortPressOnStemPrimaryBehavior = mContext.getResources().getInteger(
-                com.android.internal.R.integer.config_shortPressOnStemPrimaryBehavior);
-        mLongPressOnStemPrimaryBehavior = mContext.getResources().getInteger(
-                com.android.internal.R.integer.config_longPressOnStemPrimaryBehavior);
-        mDoublePressOnStemPrimaryBehavior = mContext.getResources().getInteger(
-                com.android.internal.R.integer.config_doublePressOnStemPrimaryBehavior);
-        mTriplePressOnStemPrimaryBehavior = mContext.getResources().getInteger(
-                com.android.internal.R.integer.config_triplePressOnStemPrimaryBehavior);
     }
 
     public void updateSettings() {
@@ -2632,6 +2711,19 @@
                 updateRotation = true;
             }
 
+            mShortPressOnPowerBehavior = Settings.Global.getInt(resolver,
+                    Settings.Global.POWER_BUTTON_SHORT_PRESS,
+                    mContext.getResources().getInteger(
+                            com.android.internal.R.integer.config_shortPressOnPowerBehavior));
+            mDoublePressOnPowerBehavior = Settings.Global.getInt(resolver,
+                    Settings.Global.POWER_BUTTON_DOUBLE_PRESS,
+                    mContext.getResources().getInteger(
+                            com.android.internal.R.integer.config_doublePressOnPowerBehavior));
+            mTriplePressOnPowerBehavior = Settings.Global.getInt(resolver,
+                    Settings.Global.POWER_BUTTON_TRIPLE_PRESS,
+                    mContext.getResources().getInteger(
+                            com.android.internal.R.integer.config_triplePressOnPowerBehavior));
+
             final int longPressOnPowerBehavior = Settings.Global.getInt(resolver,
                     Settings.Global.POWER_BUTTON_LONG_PRESS,
                     mContext.getResources().getInteger(
@@ -2657,6 +2749,25 @@
                     mContext.getResources().getInteger(
                             com.android.internal.R.integer.config_keyChordPowerVolumeUp));
 
+            mShortPressOnStemPrimaryBehavior = Settings.Global.getInt(resolver,
+                    Settings.Global.STEM_PRIMARY_BUTTON_SHORT_PRESS,
+                    mContext.getResources().getInteger(
+                            com.android.internal.R.integer.config_shortPressOnStemPrimaryBehavior));
+            mDoublePressOnStemPrimaryBehavior = Settings.Global.getInt(resolver,
+                    Settings.Global.STEM_PRIMARY_BUTTON_DOUBLE_PRESS,
+                    mContext.getResources().getInteger(
+                            com.android.internal.R.integer
+                                    .config_doublePressOnStemPrimaryBehavior));
+            mTriplePressOnStemPrimaryBehavior = Settings.Global.getInt(resolver,
+                    Settings.Global.STEM_PRIMARY_BUTTON_TRIPLE_PRESS,
+                    mContext.getResources().getInteger(
+                            com.android.internal.R.integer
+                                    .config_triplePressOnStemPrimaryBehavior));
+            mLongPressOnStemPrimaryBehavior = Settings.Global.getInt(resolver,
+                    Settings.Global.STEM_PRIMARY_BUTTON_LONG_PRESS,
+                    mContext.getResources().getInteger(
+                            com.android.internal.R.integer.config_longPressOnStemPrimaryBehavior));
+
             mStylusButtonsEnabled = Settings.Secure.getIntForUser(resolver,
                     Secure.STYLUS_BUTTONS_ENABLED, 1, UserHandle.USER_CURRENT) == 1;
             mInputManagerInternal.setStylusButtonMotionEventsEnabled(mStylusButtonsEnabled);
@@ -2906,29 +3017,49 @@
             WindowManager.LayoutParams.TYPE_SYSTEM_ERROR,
         };
 
+    /**
+     * Log the keyboard shortcuts without blocking the current thread.
+     *
+     * We won't log keyboard events when the input device is null
+     * or when it is virtual.
+     */
+    private void handleKeyboardSystemEvent(int keyboardSystemEvent, KeyEvent event) {
+        final InputManager inputManager = mContext.getSystemService(InputManager.class);
+        final InputDevice inputDevice = inputManager != null
+                ? inputManager.getInputDevice(event.getDeviceId()) : null;
+        if (inputDevice != null && !inputDevice.isVirtual()) {
+            KeyboardMetricsCollector.logKeyboardSystemsEventReportedAtom(
+                    inputDevice, keyboardSystemEvent,
+                    new int[]{event.getKeyCode()}, event.getMetaState());
+        }
+    }
+
+    private void logKeyboardSystemsEvent(KeyEvent event, int keyboardSystemEvent) {
+        mHandler.obtainMessage(MSG_LOG_KEYBOARD_SYSTEM_EVENT, 0, keyboardSystemEvent, event)
+                .sendToTarget();
+    }
+
     // TODO(b/117479243): handle it in InputPolicy
+    // TODO (b/283241997): Add the remaining keyboard shortcut logging after refactoring
     /** {@inheritDoc} */
     @Override
     public long interceptKeyBeforeDispatching(IBinder focusedToken, KeyEvent event,
             int policyFlags) {
-        final boolean keyguardOn = keyguardOn();
         final int keyCode = event.getKeyCode();
-        final int repeatCount = event.getRepeatCount();
-        final int metaState = event.getMetaState();
         final int flags = event.getFlags();
-        final boolean down = event.getAction() == KeyEvent.ACTION_DOWN;
-        final boolean canceled = event.isCanceled();
-        final int displayId = event.getDisplayId();
-        final long key_consumed = -1;
-        final long key_not_consumed = 0;
+        final long keyConsumed = -1;
+        final long keyNotConsumed = 0;
+        final int deviceId = event.getDeviceId();
 
         if (DEBUG_INPUT) {
-            Log.d(TAG, "interceptKeyTi keyCode=" + keyCode + " down=" + down + " repeatCount="
-                    + repeatCount + " keyguardOn=" + keyguardOn + " canceled=" + canceled);
+            Log.d(TAG,
+                    "interceptKeyTi keyCode=" + keyCode + " action=" + event.getAction()
+                            + " repeatCount=" + event.getRepeatCount() + " keyguardOn="
+                            + keyguardOn() + " canceled=" + event.isCanceled());
         }
 
         if (mKeyCombinationManager.isKeyConsumed(event)) {
-            return key_consumed;
+            return keyConsumed;
         }
 
         if ((flags & KeyEvent.FLAG_FALLBACK) == 0) {
@@ -2939,8 +3070,54 @@
             }
         }
 
-        // Cancel any pending meta actions if we see any other keys being pressed between the down
-        // of the meta key and its corresponding up.
+        Set<Integer> consumedKeys = mConsumedKeysForDevice.get(deviceId);
+        if (consumedKeys == null) {
+            consumedKeys = new HashSet<>();
+            mConsumedKeysForDevice.put(deviceId, consumedKeys);
+        }
+
+        if (interceptSystemKeysAndShortcuts(focusedToken, event)
+                && event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
+            consumedKeys.add(keyCode);
+            return keyConsumed;
+        }
+
+        boolean needToConsumeKey = consumedKeys.contains(keyCode);
+        if (event.getAction() == KeyEvent.ACTION_UP || event.isCanceled()) {
+            consumedKeys.remove(keyCode);
+            if (consumedKeys.isEmpty()) {
+                mConsumedKeysForDevice.remove(deviceId);
+            }
+        }
+
+        return needToConsumeKey ? keyConsumed : keyNotConsumed;
+    }
+
+    // You can only start consuming the key gesture if ACTION_DOWN and repeat count
+    // is 0. If you start intercepting the key halfway, then key will not be consumed
+    // and will be sent to apps for processing too.
+    // e.g. If a certain combination is only handled on ACTION_UP (i.e.
+    // interceptShortcut() returns true only for ACTION_UP), then since we already
+    // sent the ACTION_DOWN events to the application, we MUST also send the
+    // ACTION_UP to the application.
+    // So, to ensure that your intercept logic works properly, and we don't send any
+    // conflicting events to application, make sure to consume the event on
+    // ACTION_DOWN even if you want to do something on ACTION_UP. This is essential
+    // to maintain event parity and to not have incomplete key gestures.
+    @SuppressLint("MissingPermission")
+    private boolean interceptSystemKeysAndShortcuts(IBinder focusedToken, KeyEvent event) {
+        final boolean keyguardOn = keyguardOn();
+        final int keyCode = event.getKeyCode();
+        final int repeatCount = event.getRepeatCount();
+        final int metaState = event.getMetaState();
+        final boolean down = event.getAction() == KeyEvent.ACTION_DOWN;
+        final boolean canceled = event.isCanceled();
+        final int displayId = event.getDisplayId();
+        final int deviceId = event.getDeviceId();
+        final boolean firstDown = down && repeatCount == 0;
+
+        // Cancel any pending meta actions if we see any other keys being pressed between the
+        // down of the meta key and its corresponding up.
         if (mPendingMetaAction && !KeyEvent.isMetaKey(keyCode)) {
             mPendingMetaAction = false;
         }
@@ -2954,46 +3131,49 @@
                 dismissKeyboardShortcutsMenu();
                 mPendingMetaAction = false;
                 mPendingCapsLockToggle = false;
-                return key_consumed;
+                return true;
             }
         }
 
-        switch(keyCode) {
+        switch (keyCode) {
             case KeyEvent.KEYCODE_HOME:
+                logKeyboardSystemsEvent(event,
+                        FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__HOME);
                 return handleHomeShortcuts(displayId, focusedToken, event);
             case KeyEvent.KEYCODE_MENU:
                 // Hijack modified menu keys for debugging features
                 final int chordBug = KeyEvent.META_SHIFT_ON;
 
-                if (down && repeatCount == 0) {
-                    if (mEnableShiftMenuBugReports && (metaState & chordBug) == chordBug) {
-                        Intent intent = new Intent(Intent.ACTION_BUG_REPORT);
-                        mContext.sendOrderedBroadcastAsUser(intent, UserHandle.CURRENT,
-                                null, null, null, 0, null, null);
-                        return key_consumed;
-                    }
+                if (mEnableShiftMenuBugReports && firstDown
+                        && (metaState & chordBug) == chordBug) {
+                    Intent intent = new Intent(Intent.ACTION_BUG_REPORT);
+                    mContext.sendOrderedBroadcastAsUser(intent, UserHandle.CURRENT,
+                            null, null, null, 0, null, null);
+                    return true;
                 }
                 break;
             case KeyEvent.KEYCODE_RECENT_APPS:
-                if (down && repeatCount == 0) {
+                if (firstDown) {
                     showRecentApps(false /* triggeredFromAltTab */);
+                    logKeyboardSystemsEvent(event,
+                            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__RECENT_APPS);
                 }
-                return key_consumed;
+                return true;
             case KeyEvent.KEYCODE_APP_SWITCH:
                 if (!keyguardOn) {
-                    if (down && repeatCount == 0) {
+                    if (firstDown) {
                         preloadRecentApps();
                     } else if (!down) {
                         toggleRecentApps();
                     }
                 }
-                return key_consumed;
+                return true;
             case KeyEvent.KEYCODE_A:
-                if (down && event.isMetaPressed()) {
+                if (firstDown && event.isMetaPressed()) {
                     launchAssistAction(Intent.EXTRA_ASSIST_INPUT_HINT_KEYBOARD,
-                            event.getDeviceId(),
-                            event.getEventTime(), AssistUtils.INVOCATION_TYPE_UNKNOWN);
-                    return key_consumed;
+                            deviceId, event.getEventTime(),
+                            AssistUtils.INVOCATION_TYPE_UNKNOWN);
+                    return true;
                 }
                 break;
             case KeyEvent.KEYCODE_H:
@@ -3003,67 +3183,73 @@
                 }
                 break;
             case KeyEvent.KEYCODE_I:
-                if (down && event.isMetaPressed()) {
+                if (firstDown && event.isMetaPressed()) {
                     showSystemSettings();
-                    return key_consumed;
+                    return true;
+                }
+                break;
+            case KeyEvent.KEYCODE_L:
+                if (firstDown && event.isMetaPressed()) {
+                    lockNow(null /* options */);
+                    return true;
                 }
                 break;
             case KeyEvent.KEYCODE_N:
-                if (down && event.isMetaPressed()) {
+                if (firstDown && event.isMetaPressed()) {
                     if (event.isCtrlPressed()) {
                         sendSystemKeyToStatusBarAsync(event);
                     } else {
                         toggleNotificationPanel();
                     }
-                    return key_consumed;
+                    return true;
                 }
                 break;
             case KeyEvent.KEYCODE_S:
-                if (down && event.isMetaPressed() && event.isCtrlPressed() && repeatCount == 0) {
+                if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) {
                     interceptScreenshotChord(SCREENSHOT_KEY_OTHER, 0 /*pressDelay*/);
-                    return key_consumed;
+                    return true;
                 }
                 break;
             case KeyEvent.KEYCODE_T:
-                if (down && event.isMetaPressed()) {
+                if (firstDown && event.isMetaPressed()) {
                     toggleTaskbar();
-                    return key_consumed;
+                    return true;
                 }
                 break;
             case KeyEvent.KEYCODE_DPAD_UP:
-                if (down && event.isMetaPressed() && event.isCtrlPressed() && repeatCount == 0) {
+                if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) {
                     StatusBarManagerInternal statusbar = getStatusBarManagerInternal();
                     if (statusbar != null) {
                         statusbar.goToFullscreenFromSplit();
+                        return true;
                     }
-                    return key_consumed;
                 }
                 break;
             case KeyEvent.KEYCODE_DPAD_LEFT:
-                if (down && event.isMetaPressed() && event.isCtrlPressed() && repeatCount == 0) {
+                if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) {
                     enterStageSplitFromRunningApp(true /* leftOrTop */);
-                    return key_consumed;
+                    return true;
                 }
                 break;
             case KeyEvent.KEYCODE_DPAD_RIGHT:
-                if (down && event.isMetaPressed() && event.isCtrlPressed() && repeatCount == 0) {
+                if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) {
                     enterStageSplitFromRunningApp(false /* leftOrTop */);
-                    return key_consumed;
+                    return true;
                 }
                 break;
             case KeyEvent.KEYCODE_SLASH:
-                if (down && repeatCount == 0 && event.isMetaPressed() && !keyguardOn) {
+                if (firstDown && event.isMetaPressed() && !keyguardOn) {
                     toggleKeyboardShortcutsMenu(event.getDeviceId());
-                    return key_consumed;
+                    return true;
                 }
                 break;
             case KeyEvent.KEYCODE_ASSIST:
                 Slog.wtf(TAG, "KEYCODE_ASSIST should be handled in interceptKeyBeforeQueueing");
-                return key_consumed;
+                return true;
             case KeyEvent.KEYCODE_VOICE_ASSIST:
                 Slog.wtf(TAG, "KEYCODE_VOICE_ASSIST should be handled in"
                         + " interceptKeyBeforeQueueing");
-                return key_consumed;
+                return true;
             case KeyEvent.KEYCODE_VIDEO_APP_1:
             case KeyEvent.KEYCODE_VIDEO_APP_2:
             case KeyEvent.KEYCODE_VIDEO_APP_3:
@@ -3081,7 +3267,7 @@
             case KeyEvent.KEYCODE_DEMO_APP_3:
             case KeyEvent.KEYCODE_DEMO_APP_4:
                 Slog.wtf(TAG, "KEYCODE_APP_X should be handled in interceptKeyBeforeQueueing");
-                return key_consumed;
+                return true;
             case KeyEvent.KEYCODE_BRIGHTNESS_UP:
             case KeyEvent.KEYCODE_BRIGHTNESS_DOWN:
                 if (down) {
@@ -3118,23 +3304,25 @@
                             minLinearBrightness, maxLinearBrightness);
                     mDisplayManager.setBrightness(screenDisplayId, adjustedLinearBrightness);
 
-                    startActivityAsUser(new Intent(Intent.ACTION_SHOW_BRIGHTNESS_DIALOG),
-                            UserHandle.CURRENT_OR_SELF);
+                    Intent intent = new Intent(Intent.ACTION_SHOW_BRIGHTNESS_DIALOG);
+                    intent.addFlags(Intent.FLAG_ACTIVITY_NO_USER_ACTION);
+                    intent.putExtra(EXTRA_FROM_BRIGHTNESS_KEY, true);
+                    startActivityAsUser(intent, UserHandle.CURRENT_OR_SELF);
                 }
-                return key_consumed;
+                return true;
             case KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_DOWN:
                 if (down) {
                     mInputManagerInternal.decrementKeyboardBacklight(event.getDeviceId());
                 }
-                return key_consumed;
+                return true;
             case KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_UP:
                 if (down) {
                     mInputManagerInternal.incrementKeyboardBacklight(event.getDeviceId());
                 }
-                return key_consumed;
+                return true;
             case KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_TOGGLE:
                 // TODO: Add logic
-                return key_consumed;
+                return true;
             case KeyEvent.KEYCODE_VOLUME_UP:
             case KeyEvent.KEYCODE_VOLUME_DOWN:
             case KeyEvent.KEYCODE_VOLUME_MUTE:
@@ -3142,7 +3330,7 @@
                     // On TVs or when the configuration is enabled, volume keys never
                     // go to the foreground app.
                     dispatchDirectAudioEvent(event);
-                    return key_consumed;
+                    return true;
                 }
 
                 // If the device is in VR mode and keys are "internal" (e.g. on the side of the
@@ -3151,26 +3339,23 @@
                 if (mDefaultDisplayPolicy.isPersistentVrModeEnabled()) {
                     final InputDevice d = event.getDevice();
                     if (d != null && !d.isExternal()) {
-                        return key_consumed;
+                        return true;
                     }
                 }
                 break;
             case KeyEvent.KEYCODE_TAB:
-                if (down && event.isMetaPressed()) {
-                    if (!keyguardOn && isUserSetupComplete()) {
+                if (firstDown && !keyguardOn && isUserSetupComplete()) {
+                    if (event.isMetaPressed()) {
                         showRecentApps(false);
-                        return key_consumed;
-                    }
-                } else if (down && repeatCount == 0) {
-                    // Display task switcher for ALT-TAB.
-                    if (mRecentAppsHeldModifiers == 0 && !keyguardOn && isUserSetupComplete()) {
+                        return true;
+                    } else if (mRecentAppsHeldModifiers == 0) {
                         final int shiftlessModifiers =
                                 event.getModifiers() & ~KeyEvent.META_SHIFT_MASK;
                         if (KeyEvent.metaStateHasModifiers(
                                 shiftlessModifiers, KeyEvent.META_ALT_ON)) {
                             mRecentAppsHeldModifiers = shiftlessModifiers;
                             showRecentApps(true);
-                            return key_consumed;
+                            return true;
                         }
                     }
                 }
@@ -3182,18 +3367,18 @@
                     msg.setAsynchronous(true);
                     msg.sendToTarget();
                 }
-                return key_consumed;
+                return true;
             case KeyEvent.KEYCODE_NOTIFICATION:
                 if (!down) {
                     toggleNotificationPanel();
                 }
-                return key_consumed;
+                return true;
             case KeyEvent.KEYCODE_SEARCH:
-                if (down && repeatCount == 0 && !keyguardOn()) {
-                    switch(mSearchKeyBehavior) {
+                if (firstDown && !keyguardOn) {
+                    switch (mSearchKeyBehavior) {
                         case SEARCH_BEHAVIOR_TARGET_ACTIVITY: {
                             launchTargetSearchActivity();
-                            return key_consumed;
+                            return true;
                         }
                         case SEARCH_BEHAVIOR_DEFAULT_SEARCH:
                         default:
@@ -3202,21 +3387,18 @@
                 }
                 break;
             case KeyEvent.KEYCODE_LANGUAGE_SWITCH:
-                if (down && repeatCount == 0) {
+                if (firstDown) {
                     int direction = (metaState & KeyEvent.META_SHIFT_MASK) != 0 ? -1 : 1;
                     sendSwitchKeyboardLayout(event, direction);
-                    return key_consumed;
+                    return true;
                 }
                 break;
             case KeyEvent.KEYCODE_SPACE:
                 // Handle keyboard layout switching. (META + SPACE)
-                if ((metaState & KeyEvent.META_META_MASK) == 0) {
-                    return key_not_consumed;
-                }
-                if (down && repeatCount == 0) {
+                if (firstDown && event.isMetaPressed()) {
                     int direction = (metaState & KeyEvent.META_SHIFT_MASK) != 0 ? -1 : 1;
                     sendSwitchKeyboardLayout(event, direction);
-                    return key_consumed;
+                    return true;
                 }
                 break;
             case KeyEvent.KEYCODE_META_LEFT:
@@ -3241,7 +3423,7 @@
                         mPendingMetaAction = false;
                     }
                 }
-                return key_consumed;
+                return true;
             case KeyEvent.KEYCODE_ALT_LEFT:
             case KeyEvent.KEYCODE_ALT_RIGHT:
                 if (down) {
@@ -3257,14 +3439,14 @@
                             && (metaState & mRecentAppsHeldModifiers) == 0) {
                         mRecentAppsHeldModifiers = 0;
                         hideRecentApps(true, false);
-                        return key_consumed;
+                        return true;
                     }
 
                     // Toggle Caps Lock on META-ALT.
                     if (mPendingCapsLockToggle) {
                         mInputManagerInternal.toggleCapsLock(event.getDeviceId());
                         mPendingCapsLockToggle = false;
-                        return key_consumed;
+                        return true;
                     }
                 }
                 break;
@@ -3274,24 +3456,18 @@
             case KeyEvent.KEYCODE_STYLUS_BUTTON_TAIL:
                 Slog.wtf(TAG, "KEYCODE_STYLUS_BUTTON_* should be handled in"
                         + " interceptKeyBeforeQueueing");
-                return key_consumed;
+                return true;
         }
-
         if (isValidGlobalKey(keyCode)
                 && mGlobalKeyManager.handleGlobalKey(mContext, keyCode, event)) {
-            return key_consumed;
+            return true;
         }
 
         // Reserve all the META modifier combos for system behavior
-        if ((metaState & KeyEvent.META_META_ON) != 0) {
-            return key_consumed;
-        }
-
-        // Let the application handle the key.
-        return key_not_consumed;
+        return (metaState & KeyEvent.META_META_ON) != 0;
     }
 
-    private int handleHomeShortcuts(int displayId, IBinder focusedToken, KeyEvent event) {
+    private boolean handleHomeShortcuts(int displayId, IBinder focusedToken, KeyEvent event) {
         // First we always handle the home key here, so applications
         // can never break it, although if keyguard is on, we do let
         // it handle it, because that gives us the correct 5 second
@@ -3361,16 +3537,12 @@
     }
 
     private void requestBugreportForTv() {
-        if ("1".equals(SystemProperties.get("ro.debuggable"))
-                || Settings.Global.getInt(mContext.getContentResolver(),
-                        Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0) == 1) {
-            try {
-                if (!ActivityManager.getService().launchBugReportHandlerApp()) {
-                    ActivityManager.getService().requestInteractiveBugReport();
-                }
-            } catch (RemoteException e) {
-                Slog.e(TAG, "Error taking bugreport", e);
+        try {
+            if (!ActivityManager.getService().launchBugReportHandlerApp()) {
+                ActivityManager.getService().requestInteractiveBugReport();
             }
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Error taking bugreport", e);
         }
     }
 
@@ -4424,6 +4596,9 @@
             case KeyEvent.KEYCODE_MACRO_4:
                 result &= ~ACTION_PASS_TO_USER;
                 break;
+            case KeyEvent.KEYCODE_STEM_PRIMARY:
+                sendSystemKeyToStatusBarAsync(event);
+                break;
         }
 
         if (useHapticFeedback) {
@@ -4594,7 +4769,8 @@
         if ((policyFlags & FLAG_WAKE) != 0) {
             if (wakeUp(whenNanos / 1000000, mAllowTheaterModeWakeFromMotion,
                     PowerManager.WAKE_REASON_WAKE_MOTION, "android.policy:MOTION")) {
-                return 0;
+                // Woke up. Pass motion events to user.
+                return ACTION_PASS_TO_USER;
             }
         }
 
@@ -4606,8 +4782,11 @@
         // there will be no dream to intercept the touch and wake into ambient.  The device should
         // wake up in this case.
         if (isTheaterModeEnabled() && (policyFlags & FLAG_WAKE) != 0) {
-            wakeUp(whenNanos / 1000000, mAllowTheaterModeWakeFromMotionWhenNotDreaming,
-                    PowerManager.WAKE_REASON_WAKE_MOTION, "android.policy:MOTION");
+            if (wakeUp(whenNanos / 1000000, mAllowTheaterModeWakeFromMotionWhenNotDreaming,
+                    PowerManager.WAKE_REASON_WAKE_MOTION, "android.policy:MOTION")) {
+                // Woke up. Pass motion events to user.
+                return ACTION_PASS_TO_USER;
+            }
         }
 
         return 0;
@@ -4623,28 +4802,19 @@
         final boolean displayOff = (display == null
                 || display.getState() == STATE_OFF);
 
-        if (displayOff && !mHasFeatureWatch) {
+        if (displayOff) {
             return false;
         }
 
         // Send events to keyguard while the screen is on and it's showing.
-        if (isKeyguardShowingAndNotOccluded() && !displayOff) {
+        if (isKeyguardShowingAndNotOccluded()) {
             return true;
         }
 
-        // Watches handle BACK and hardware buttons specially
-        if (mHasFeatureWatch && (keyCode == KeyEvent.KEYCODE_BACK
-                || keyCode == KeyEvent.KEYCODE_STEM_PRIMARY
-                || keyCode == KeyEvent.KEYCODE_STEM_1
-                || keyCode == KeyEvent.KEYCODE_STEM_2
-                || keyCode == KeyEvent.KEYCODE_STEM_3)) {
-            return false;
-        }
-
         // TODO(b/123372519): Refine when dream can support multi display.
         if (isDefaultDisplay) {
-            // Send events to a dozing dream even if the screen is off since the dream
-            // is in control of the state of the screen.
+            // Send events to a dozing dream since the dream is in control of the state of the
+            // screen.
             IDreamManager dreamManager = getDreamManager();
 
             try {
@@ -5839,7 +6009,7 @@
             case HapticFeedbackConstants.CONTEXT_CLICK:
             case HapticFeedbackConstants.GESTURE_END:
             case HapticFeedbackConstants.GESTURE_THRESHOLD_ACTIVATE:
-            case HapticFeedbackConstants.ROTARY_SCROLL_TICK:
+            case HapticFeedbackConstants.SCROLL_TICK:
             case HapticFeedbackConstants.SEGMENT_TICK:
                 return VibrationEffect.get(VibrationEffect.EFFECT_TICK);
 
@@ -5864,8 +6034,8 @@
             case HapticFeedbackConstants.CALENDAR_DATE:
             case HapticFeedbackConstants.CONFIRM:
             case HapticFeedbackConstants.GESTURE_START:
-            case HapticFeedbackConstants.ROTARY_SCROLL_ITEM_FOCUS:
-            case HapticFeedbackConstants.ROTARY_SCROLL_LIMIT:
+            case HapticFeedbackConstants.SCROLL_ITEM_FOCUS:
+            case HapticFeedbackConstants.SCROLL_LIMIT:
                 return VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
 
             case HapticFeedbackConstants.LONG_PRESS:
@@ -5943,9 +6113,9 @@
                 return PHYSICAL_EMULATION_VIBRATION_ATTRIBUTES;
             case HapticFeedbackConstants.ASSISTANT_BUTTON:
             case HapticFeedbackConstants.LONG_PRESS_POWER_BUTTON:
-            case HapticFeedbackConstants.ROTARY_SCROLL_TICK:
-            case HapticFeedbackConstants.ROTARY_SCROLL_ITEM_FOCUS:
-            case HapticFeedbackConstants.ROTARY_SCROLL_LIMIT:
+            case HapticFeedbackConstants.SCROLL_TICK:
+            case HapticFeedbackConstants.SCROLL_ITEM_FOCUS:
+            case HapticFeedbackConstants.SCROLL_LIMIT:
                 return HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES;
             default:
                 return TOUCH_VIBRATION_ATTRIBUTES;
@@ -6101,10 +6271,10 @@
                 pw.println(endcallBehaviorToString(mEndcallBehavior));
         pw.print(prefix);
         // TODO(b/117479243): handle it in InputPolicy
-        pw.print("mDisplayHomeButtonHandlers=");
+        pw.println("mDisplayHomeButtonHandlers=");
         for (int i = 0; i < mDisplayHomeButtonHandlers.size(); i++) {
             final int key = mDisplayHomeButtonHandlers.keyAt(i);
-            pw.println(mDisplayHomeButtonHandlers.get(key));
+            pw.print(prefix); pw.print("  "); pw.println(mDisplayHomeButtonHandlers.get(key));
         }
         pw.print(prefix); pw.print("mKeyguardOccluded="); pw.print(isKeyguardOccluded());
                 pw.print(" mKeyguardOccludedChanged="); pw.print(mKeyguardOccludedChanged);
@@ -6375,6 +6545,7 @@
 
     private class HdmiVideoExtconUEventObserver extends ExtconStateObserver<Boolean> {
         private static final String HDMI_EXIST = "HDMI=1";
+        private static final String DP_EXIST = "DP=1";
         private static final String NAME = "hdmi";
 
         private boolean init(ExtconInfo hdmi) {
@@ -6405,7 +6576,8 @@
         public Boolean parseState(ExtconInfo extconIfno, String state) {
             // extcon event state changes from kernel4.9
             // new state will be like STATE=HDMI=1
-            return state.contains(HDMI_EXIST);
+            // or like STATE=DP=1 for newer kernel
+            return state.contains(HDMI_EXIST) || state.contains(DP_EXIST);
         }
     }
 
diff --git a/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java b/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java
index 9c3b38a..b999bbb3 100644
--- a/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java
+++ b/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java
@@ -314,7 +314,9 @@
             if (eventTime < mLastDownTime + mActiveRule.getVeryLongPressTimeoutMs()) {
                 mHandler.removeMessages(MSG_KEY_VERY_LONG_PRESS);
             } else {
-                mHandledByLongPress = mActiveRule.supportVeryLongPress();
+                // If long press or very long press (~3.5s) had been handled, we should skip the
+                // short press behavior.
+                mHandledByLongPress |= mActiveRule.supportVeryLongPress();
             }
         }
 
diff --git a/services/core/java/com/android/server/policy/role/OWNERS b/services/core/java/com/android/server/policy/role/OWNERS
new file mode 100644
index 0000000..a462da8
--- /dev/null
+++ b/services/core/java/com/android/server/policy/role/OWNERS
@@ -0,0 +1 @@
+include /services/core/java/com/android/server/pm/permission/OWNERS
diff --git a/services/core/java/com/android/server/policy/role/RoleServicePlatformHelperImpl.java b/services/core/java/com/android/server/policy/role/RoleServicePlatformHelperImpl.java
index f45bda7..5e8b4de 100644
--- a/services/core/java/com/android/server/policy/role/RoleServicePlatformHelperImpl.java
+++ b/services/core/java/com/android/server/policy/role/RoleServicePlatformHelperImpl.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
+import android.app.admin.DevicePolicyManagerInternal;
 import android.app.role.RoleManager;
 import android.content.ComponentName;
 import android.content.ContentResolver;
@@ -57,7 +58,6 @@
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
 import java.util.Collections;
-import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
@@ -304,6 +304,8 @@
     public String computePackageStateHash(@UserIdInt int userId) {
         PackageManagerInternal packageManagerInternal = LocalServices.getService(
                 PackageManagerInternal.class);
+        DevicePolicyManagerInternal devicePolicyManagerInternal = LocalServices.getService(
+                DevicePolicyManagerInternal.class);
         final MessageDigestOutputStream mdos = new MessageDigestOutputStream();
 
         DataOutputStream dataOutputStream = new DataOutputStream(new BufferedOutputStream(mdos));
@@ -314,11 +316,10 @@
                 dataOutputStream.writeInt(packageManagerInternal.getApplicationEnabledState(
                         pkg.getPackageName(), userId));
 
-                final List<String> requestedPermissions = pkg.getRequestedPermissions();
-                final int requestedPermissionsSize = requestedPermissions.size();
-                dataOutputStream.writeInt(requestedPermissionsSize);
-                for (int i = 0; i < requestedPermissionsSize; i++) {
-                    dataOutputStream.writeUTF(requestedPermissions.get(i));
+                final Set<String> requestedPermissions = pkg.getRequestedPermissions();
+                dataOutputStream.writeInt(requestedPermissions.size());
+                for (String permissionName : requestedPermissions) {
+                    dataOutputStream.writeUTF(permissionName);
                 }
 
                 final ArraySet<String> enabledComponents =
@@ -344,6 +345,34 @@
                 throw new AssertionError(e);
             }
         }, userId);
+        try {
+            String deviceOwner= "";
+            if (devicePolicyManagerInternal != null) {
+                if (devicePolicyManagerInternal.getDeviceOwnerUserId() == userId) {
+                    ComponentName deviceOwnerComponent =
+                        devicePolicyManagerInternal.getDeviceOwnerComponent(false);
+                    if (deviceOwnerComponent != null) {
+                        deviceOwner = deviceOwnerComponent.getPackageName();
+                    }
+                }
+            }
+            dataOutputStream.writeUTF(deviceOwner);
+            String profileOwner = "";
+            if (devicePolicyManagerInternal != null) {
+                ComponentName profileOwnerComponent =
+                    devicePolicyManagerInternal.getProfileOwnerAsUser(userId);
+                if (profileOwnerComponent != null) {
+                    profileOwner = profileOwnerComponent.getPackageName();
+                }
+            }
+            dataOutputStream.writeUTF(profileOwner);
+            dataOutputStream.writeInt(Settings.Global.getInt(mContext.getContentResolver(),
+                    Settings.Global.DEVICE_DEMO_MODE, 0));
+            dataOutputStream.flush();
+        } catch (IOException e) {
+            // Never happens for MessageDigestOutputStream and DataOutputStream.
+            throw new AssertionError(e);
+        }
         return mdos.getDigestAsString();
     }
 
diff --git a/services/core/java/com/android/server/power/LowPowerStandbyController.java b/services/core/java/com/android/server/power/LowPowerStandbyController.java
index 8e78041..3b4b20f 100644
--- a/services/core/java/com/android/server/power/LowPowerStandbyController.java
+++ b/services/core/java/com/android/server/power/LowPowerStandbyController.java
@@ -1369,7 +1369,7 @@
          * Otherwise, returns false, and the default policy will be used.
          */
         public boolean enableCustomPolicy() {
-            return DeviceConfig.getBoolean(NAMESPACE, FEATURE_FLAG_ENABLE_POLICY, false);
+            return DeviceConfig.getBoolean(NAMESPACE, FEATURE_FLAG_ENABLE_POLICY, true);
         }
 
         /**
@@ -1377,7 +1377,7 @@
          * Otherwise, returns false, and {@link #getActiveStandbyPorts()} will always be empty.
          */
         public boolean enableStandbyPorts() {
-            return DeviceConfig.getBoolean(NAMESPACE, FEATURE_FLAG_ENABLE_STANDBY_PORTS, false);
+            return DeviceConfig.getBoolean(NAMESPACE, FEATURE_FLAG_ENABLE_STANDBY_PORTS, true);
         }
 
         /**
diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java
index a694e31..3ecc985 100644
--- a/services/core/java/com/android/server/power/Notifier.java
+++ b/services/core/java/com/android/server/power/Notifier.java
@@ -222,7 +222,7 @@
         mShowWirelessChargingAnimationConfig = context.getResources().getBoolean(
                 com.android.internal.R.bool.config_showBuiltinWirelessChargingAnim);
 
-        mWakeLockLog = new WakeLockLog();
+        mWakeLockLog = new WakeLockLog(context);
 
         // Initialize interactive state for battery stats.
         try {
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index a53b831..fc971e4 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -6765,6 +6765,13 @@
         }
     }
 
+    @VisibleForTesting
+    int getPowerGroupSize() {
+        synchronized (mLock) {
+            return mPowerGroups.size();
+        }
+    }
+
     @GoToSleepReason
     private int getLastSleepReasonInternal() {
         synchronized (mLock) {
diff --git a/services/core/java/com/android/server/power/ShutdownCheckPoints.java b/services/core/java/com/android/server/power/ShutdownCheckPoints.java
index 546dc81..dafaa7d 100644
--- a/services/core/java/com/android/server/power/ShutdownCheckPoints.java
+++ b/services/core/java/com/android/server/power/ShutdownCheckPoints.java
@@ -121,23 +121,25 @@
 
     @VisibleForTesting
     void recordCheckPointInternal(@Nullable String reason) {
-        recordCheckPointInternal(new SystemServerCheckPoint(mInjector, reason));
+        recordCheckPointInternal(new SystemServerCheckPoint(mInjector.currentTimeMillis(), reason));
         Slog.v(TAG, "System server shutdown checkpoint recorded");
     }
 
     @VisibleForTesting
     void recordCheckPointInternal(int callerProcessId, @Nullable String reason) {
+        long timestamp = mInjector.currentTimeMillis();
         recordCheckPointInternal(callerProcessId == Process.myPid()
-                ? new SystemServerCheckPoint(mInjector, reason)
-                : new BinderCheckPoint(mInjector, callerProcessId, reason));
+                ? new SystemServerCheckPoint(timestamp, reason)
+                : new BinderCheckPoint(timestamp, callerProcessId, reason));
         Slog.v(TAG, "Binder shutdown checkpoint recorded with pid=" + callerProcessId);
     }
 
     @VisibleForTesting
     void recordCheckPointInternal(String intentName, String packageName, @Nullable String reason) {
+        long timestamp = mInjector.currentTimeMillis();
         recordCheckPointInternal("android".equals(packageName)
-                ? new SystemServerCheckPoint(mInjector, reason)
-                : new IntentCheckPoint(mInjector, intentName, packageName, reason));
+                ? new SystemServerCheckPoint(timestamp, reason)
+                : new IntentCheckPoint(timestamp, intentName, packageName, reason));
         Slog.v(TAG, String.format("Shutdown intent checkpoint recorded intent=%s from package=%s",
                 intentName, packageName));
     }
@@ -156,7 +158,7 @@
             records = new ArrayList<>(mCheckPoints);
         }
         for (CheckPoint record : records) {
-            record.dump(printWriter);
+            record.dump(mInjector, printWriter);
             printWriter.println();
         }
     }
@@ -185,12 +187,12 @@
         private final long mTimestamp;
         @Nullable private final String mReason;
 
-        CheckPoint(Injector injector, @Nullable String reason) {
-            mTimestamp = injector.currentTimeMillis();
+        CheckPoint(long timestamp, @Nullable String reason) {
+            mTimestamp = timestamp;
             mReason = reason;
         }
 
-        final void dump(PrintWriter printWriter) {
+        final void dump(Injector injector, PrintWriter printWriter) {
             printWriter.print("Shutdown request from ");
             printWriter.print(getOrigin());
             if (mReason != null) {
@@ -200,12 +202,12 @@
             printWriter.print(" at ");
             printWriter.print(DATE_FORMAT.format(new Date(mTimestamp)));
             printWriter.println(" (epoch=" + mTimestamp + ")");
-            dumpDetails(printWriter);
+            dumpDetails(injector, printWriter);
         }
 
         abstract String getOrigin();
 
-        abstract void dumpDetails(PrintWriter printWriter);
+        abstract void dumpDetails(Injector injector, PrintWriter printWriter);
     }
 
     /** Representation of a shutdown call from the system server, with stack trace. */
@@ -213,8 +215,8 @@
 
         private final StackTraceElement[] mStackTraceElements;
 
-        SystemServerCheckPoint(Injector injector, @Nullable String reason) {
-            super(injector, reason);
+        SystemServerCheckPoint(long timestamp, @Nullable String reason) {
+            super(timestamp, reason);
             mStackTraceElements = Thread.currentThread().getStackTrace();
         }
 
@@ -224,14 +226,14 @@
         }
 
         @Override
-        void dumpDetails(PrintWriter printWriter) {
-            String methodName = getMethodName();
+        void dumpDetails(Injector injector, PrintWriter printWriter) {
+            String methodName = findMethodName();
             printWriter.println(methodName == null ? "Failed to get method name" : methodName);
             printStackTrace(printWriter);
         }
 
         @Nullable
-        String getMethodName() {
+        String findMethodName() {
             int idx = findCallSiteIndex();
             if (idx < mStackTraceElements.length) {
                 StackTraceElement element = mStackTraceElements[idx];
@@ -241,7 +243,7 @@
         }
 
         void printStackTrace(PrintWriter printWriter) {
-            // Skip the call site line, as it's already considered with getMethodName.
+            // Skip the call site line, as it's already considered with findMethodName.
             for (int i = findCallSiteIndex() + 1; i < mStackTraceElements.length; i++) {
                 printWriter.print(" at ");
                 printWriter.println(mStackTraceElements[i]);
@@ -268,12 +270,10 @@
     /** Representation of a shutdown call to {@link android.os.Binder}, with caller process id. */
     private static class BinderCheckPoint extends SystemServerCheckPoint {
         private final int mCallerProcessId;
-        private final IActivityManager mActivityManager;
 
-        BinderCheckPoint(Injector injector, int callerProcessId, @Nullable String reason) {
-            super(injector, reason);
+        BinderCheckPoint(long timestamp, int callerProcessId, @Nullable String reason) {
+            super(timestamp, reason);
             mCallerProcessId = callerProcessId;
-            mActivityManager = injector.activityManager();
         }
 
         @Override
@@ -282,25 +282,25 @@
         }
 
         @Override
-        void dumpDetails(PrintWriter printWriter) {
-            String methodName = getMethodName();
+        void dumpDetails(Injector injector, PrintWriter printWriter) {
+            String methodName = findMethodName();
             printWriter.println(methodName == null ? "Failed to get method name" : methodName);
 
-            String processName = getProcessName();
+            String processName = findProcessName(injector.activityManager());
             printWriter.print("From process ");
             printWriter.print(processName == null ? "?" : processName);
             printWriter.println(" (pid=" + mCallerProcessId + ")");
         }
 
         @Nullable
-        String getProcessName() {
+        private String findProcessName(@Nullable IActivityManager activityManager) {
             try {
                 List<ActivityManager.RunningAppProcessInfo> runningProcesses = null;
-                if (mActivityManager != null) {
-                    runningProcesses = mActivityManager.getRunningAppProcesses();
+                if (activityManager != null) {
+                    runningProcesses = activityManager.getRunningAppProcesses();
                 } else {
-                    Slog.v(TAG, "No ActivityManager available to find process name with pid="
-                            + mCallerProcessId);
+                    Slog.v(TAG, "No ActivityManager to find name of process with pid="
+                        + mCallerProcessId);
                 }
                 if (runningProcesses != null) {
                     for (ActivityManager.RunningAppProcessInfo processInfo : runningProcesses) {
@@ -322,8 +322,8 @@
         private final String mPackageName;
 
         IntentCheckPoint(
-                Injector injector, String intentName, String packageName, @Nullable String reason) {
-            super(injector, reason);
+                long timestamp, String intentName, String packageName, @Nullable String reason) {
+            super(timestamp, reason);
             mIntentName = intentName;
             mPackageName = packageName;
         }
@@ -334,7 +334,7 @@
         }
 
         @Override
-        void dumpDetails(PrintWriter printWriter) {
+        void dumpDetails(Injector injector, PrintWriter printWriter) {
             printWriter.print("Intent: ");
             printWriter.println(mIntentName);
             printWriter.print("Package: ");
diff --git a/services/core/java/com/android/server/power/ShutdownThread.java b/services/core/java/com/android/server/power/ShutdownThread.java
index b1430e7..862948e 100644
--- a/services/core/java/com/android/server/power/ShutdownThread.java
+++ b/services/core/java/com/android/server/power/ShutdownThread.java
@@ -30,7 +30,6 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.PackageManagerInternal;
-import android.media.AudioAttributes;
 import android.os.Bundle;
 import android.os.FileUtils;
 import android.os.Handler;
@@ -44,20 +43,26 @@
 import android.os.Trace;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.os.VibrationAttributes;
+import android.os.VibrationEffect;
 import android.os.Vibrator;
+import android.os.vibrator.persistence.VibrationXmlParser;
 import android.telephony.TelephonyManager;
+import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.Log;
 import android.util.Slog;
 import android.util.TimingsTraceLog;
 import android.view.WindowManager;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.LocalServices;
 import com.android.server.RescueParty;
 import com.android.server.statusbar.StatusBarManagerInternal;
 
 import java.io.File;
 import java.io.FileOutputStream;
+import java.io.FileReader;
 import java.io.IOException;
 import java.nio.charset.StandardCharsets;
 
@@ -80,7 +85,7 @@
     private static final int MOUNT_SERVICE_STOP_PERCENT = 20;
 
     // length of vibration before shutting down
-    private static final int SHUTDOWN_VIBRATE_MS = 500;
+    @VisibleForTesting static final int DEFAULT_SHUTDOWN_VIBRATE_MS = 500;
 
     // state tracking
     private static final Object sIsStartedGuard = new Object();
@@ -101,11 +106,6 @@
     // static instance of this thread
     private static final ShutdownThread sInstance = new ShutdownThread();
 
-    private static final AudioAttributes VIBRATION_ATTRIBUTES = new AudioAttributes.Builder()
-            .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
-            .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
-            .build();
-
     // Metrics that will be reported to tron after reboot
     private static final ArrayMap<String, Long> TRON_METRICS = new ArrayMap<>();
 
@@ -124,6 +124,8 @@
     private static String METRIC_RADIO = "shutdown_radio";
     private static String METRIC_SHUTDOWN_TIME_START = "begin_shutdown";
 
+    private final Injector mInjector;
+
     private final Object mActionDoneSync = new Object();
     private boolean mActionDone;
     private Context mContext;
@@ -136,6 +138,12 @@
     private ProgressDialog mProgressDialog;
 
     private ShutdownThread() {
+        this(new Injector());
+    }
+
+    @VisibleForTesting
+    ShutdownThread(Injector injector) {
+        mInjector = injector;
     }
 
     /**
@@ -699,19 +707,10 @@
             PowerManagerService.lowLevelReboot(reason);
             Log.e(TAG, "Reboot failed, will attempt shutdown instead");
             reason = null;
-        } else if (SHUTDOWN_VIBRATE_MS > 0 && context != null) {
+        } else if (context != null) {
             // vibrate before shutting down
-            Vibrator vibrator = new SystemVibrator(context);
             try {
-                if (vibrator.hasVibrator()) {
-                    vibrator.vibrate(SHUTDOWN_VIBRATE_MS, VIBRATION_ATTRIBUTES);
-                    // vibrator is asynchronous so we need to wait to avoid shutting down too soon.
-                    try {
-                        Thread.sleep(SHUTDOWN_VIBRATE_MS);
-                    } catch (InterruptedException unused) {
-                        // this is not critical and does not require logging
-                    }
-                }
+                sInstance.playShutdownVibration(context);
             } catch (Exception e) {
                 // Failure to vibrate shouldn't interrupt shutdown.  Just log it.
                 Log.w(TAG, "Failed to vibrate during shutdown.", e);
@@ -723,6 +722,31 @@
         PowerManagerService.lowLevelShutdown(reason);
     }
 
+    /**
+     * Plays a vibration for shutdown. Along with playing a shutdown vibration, this method also
+     * sleeps the current Thread for some time, to allow the vibration to finish before the device
+     * shuts down.
+     */
+    @VisibleForTesting // For testing vibrations without shutting down device
+    void playShutdownVibration(Context context) {
+        Vibrator vibrator = mInjector.getVibrator(context);
+        if (!vibrator.hasVibrator()) {
+            return;
+        }
+
+        VibrationEffect vibrationEffect = getValidShutdownVibration(context, vibrator);
+        vibrator.vibrate(
+                vibrationEffect,
+                VibrationAttributes.createForUsage(VibrationAttributes.USAGE_TOUCH));
+
+        // vibrator is asynchronous so we have to wait to avoid shutting down too soon.
+        long vibrationDuration = vibrationEffect.getDuration();
+        // A negative vibration duration may indicate a vibration effect whose duration is not
+        // known by the system (e.g. pre-baked effects). In that case, use the default shutdown
+        // vibration duration.
+        mInjector.sleep(vibrationDuration < 0 ? DEFAULT_SHUTDOWN_VIBRATE_MS : vibrationDuration);
+    }
+
     private static void saveMetrics(boolean reboot, String reason) {
         StringBuilder metricValue = new StringBuilder();
         metricValue.append("reboot:");
@@ -810,4 +834,75 @@
             }
         }
     }
-}
+
+    /**
+     * Provides a {@link VibrationEffect} to be used for shutdown.
+     *
+     * <p>The vibration to be played is derived from the shutdown vibration file (which the device
+     * should specify at `com.android.internal.R.string.config_defaultShutdownVibrationFile`). A
+     * fallback vibration maybe used in one of these conditions:
+     *      <ul>
+     *          <li>A vibration file has not been specified, or if the specified file does not exist
+     *          <li>If the content of the file does not represent a valid serialization of a
+     *              {@link VibrationEffect}
+     *          <li>If the {@link VibrationEffect} specified in the file is not suitable for
+     *              a shutdown vibration (such as indefinite vibrations)
+     *      </ul>
+     */
+    private VibrationEffect getValidShutdownVibration(Context context, Vibrator vibrator) {
+        VibrationEffect parsedEffect = parseVibrationEffectFromFile(
+                mInjector.getDefaultShutdownVibrationEffectFilePath(context));
+
+        if (parsedEffect == null || !vibrator.areVibrationFeaturesSupported(parsedEffect)) {
+            return createDefaultVibrationEffect();
+        }
+
+        long parsedEffectDuration = parsedEffect.getDuration();
+        if (parsedEffectDuration == Long.MAX_VALUE) {
+            // This means that the effect does not have a defined end.
+            // Since we don't want to vibrate forever while trying to shutdown, we ignore this
+            // parsed effect and use the default one instead.
+            Log.w(TAG, "The parsed shutdown vibration is indefinite.");
+            return createDefaultVibrationEffect();
+        }
+
+        return parsedEffect;
+    }
+
+    private static VibrationEffect parseVibrationEffectFromFile(String filePath) {
+        if (!TextUtils.isEmpty(filePath)) {
+            try {
+                return VibrationXmlParser.parse(new FileReader(filePath));
+            } catch (IOException e) {
+                Log.e(TAG, "Error parsing default shutdown vibration effect.", e);
+            }
+        }
+        return null;
+    }
+
+    private static VibrationEffect createDefaultVibrationEffect() {
+        return VibrationEffect.createOneShot(
+                DEFAULT_SHUTDOWN_VIBRATE_MS, VibrationEffect.DEFAULT_AMPLITUDE);
+    }
+
+    /** Utility class to inject instances, for easy testing. */
+    @VisibleForTesting
+    static class Injector {
+        public Vibrator getVibrator(Context context) {
+            return new SystemVibrator(context);
+        }
+
+        public void sleep(long durationMs) {
+            try {
+                Thread.sleep(durationMs);
+            } catch (InterruptedException unused) {
+                // this is not critical and does not require logging.
+            }
+        }
+
+        public String getDefaultShutdownVibrationEffectFilePath(Context context) {
+            return context.getResources().getString(
+                    com.android.internal.R.string.config_defaultShutdownVibrationFile);
+        }
+    }
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/power/TEST_MAPPING b/services/core/java/com/android/server/power/TEST_MAPPING
index cf1bfc3..fbfe291 100644
--- a/services/core/java/com/android/server/power/TEST_MAPPING
+++ b/services/core/java/com/android/server/power/TEST_MAPPING
@@ -20,6 +20,7 @@
       "name": "FrameworksServicesTests",
       "options": [
         {"include-filter": "com.android.server.power"},
+        {"exclude-filter": "com.android.server.power.BatteryStatsTests"},
         {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
         {"exclude-annotation": "androidx.test.filters.FlakyTest"}
       ]
@@ -38,7 +39,8 @@
     {
       "name": "FrameworksServicesTests",
       "options": [
-        {"include-filter": "com.android.server.power"}
+        {"include-filter": "com.android.server.power"},
+        {"exclude-filter": "com.android.server.power.BatteryStatsTests"}
       ]
     }
   ]
diff --git a/services/core/java/com/android/server/power/ThermalManagerService.java b/services/core/java/com/android/server/power/ThermalManagerService.java
index 43f96e7..99064bc 100644
--- a/services/core/java/com/android/server/power/ThermalManagerService.java
+++ b/services/core/java/com/android/server/power/ThermalManagerService.java
@@ -589,6 +589,8 @@
         @Override
         public int onCommand(String cmd) {
             switch(cmd != null ? cmd : "") {
+                case "inject-temperature":
+                    return runInjectTemperature();
                 case "override-status":
                     return runOverrideStatus();
                 case "reset":
@@ -611,6 +613,95 @@
             }
         }
 
+
+        private int runInjectTemperature() {
+            final long token = Binder.clearCallingIdentity();
+            try {
+                final PrintWriter pw = getOutPrintWriter();
+                int type;
+                String typeName = getNextArgRequired();
+                switch (typeName.toUpperCase()) {
+                    case "UNKNOWN":
+                        type = Temperature.TYPE_UNKNOWN;
+                        break;
+                    case "CPU":
+                        type = Temperature.TYPE_CPU;
+                        break;
+                    case "GPU":
+                        type = Temperature.TYPE_GPU;
+                        break;
+                    case "BATTERY":
+                        type = Temperature.TYPE_BATTERY;
+                        break;
+                    case "SKIN":
+                        type = Temperature.TYPE_SKIN;
+                        break;
+                    case "USB_PORT":
+                        type = Temperature.TYPE_USB_PORT;
+                        break;
+                    case "POWER_AMPLIFIER":
+                        type = Temperature.TYPE_POWER_AMPLIFIER;
+                        break;
+                    case "BCL_VOLTAGE":
+                        type = Temperature.TYPE_BCL_VOLTAGE;
+                        break;
+                    case "BCL_CURRENT":
+                        type = Temperature.TYPE_BCL_CURRENT;
+                        break;
+                    case "BCL_PERCENTAGE":
+                        type = Temperature.TYPE_BCL_PERCENTAGE;
+                        break;
+                    case "NPU":
+                        type = Temperature.TYPE_NPU;
+                        break;
+                    default:
+                        pw.println("Invalid temperature type: " + typeName);
+                        return -1;
+                }
+                int throttle;
+                String throttleName = getNextArgRequired();
+                switch (throttleName.toUpperCase()) {
+                    case "NONE":
+                        throttle = Temperature.THROTTLING_NONE;
+                        break;
+                    case "LIGHT":
+                        throttle = Temperature.THROTTLING_LIGHT;
+                        break;
+                    case "MODERATE":
+                        throttle = Temperature.THROTTLING_MODERATE;
+                        break;
+                    case "SEVERE":
+                        throttle = Temperature.THROTTLING_SEVERE;
+                        break;
+                    case "CRITICAL":
+                        throttle = Temperature.THROTTLING_CRITICAL;
+                        break;
+                    case "EMERGENCY":
+                        throttle = Temperature.THROTTLING_EMERGENCY;
+                        break;
+                    case "SHUTDOWN":
+                        throttle = Temperature.THROTTLING_SHUTDOWN;
+                        break;
+                    default:
+                        pw.println("Invalid throttle status: " + throttleName);
+                        return -1;
+                }
+                String name = getNextArgRequired();
+                float value = 28.0f;
+                try {
+                    String valueStr = getNextArg();
+                    if (valueStr != null) value = Float.parseFloat(valueStr);
+                } catch (RuntimeException ex) {
+                    pw.println("Error: " + ex.toString());
+                    return -1;
+                }
+                onTemperatureChanged(new Temperature(value, type, name, throttle), true);
+                return 0;
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
         private int runOverrideStatus() {
             final long token = Binder.clearCallingIdentity();
             try {
@@ -643,6 +734,9 @@
             pw.println("  help");
             pw.println("    Print this help text.");
             pw.println("");
+            pw.println("  inject-temperature TYPE STATUS NAME [VALUE]");
+            pw.println("    injects a new temperature sample for the specified device.");
+            pw.println("    type and status strings follow the names in android.os.Temperature.");
             pw.println("  override-status STATUS");
             pw.println("    sets and locks the thermal status of the device to STATUS.");
             pw.println("    status code is defined in android.os.Temperature.");
diff --git a/services/core/java/com/android/server/power/WakeLockLog.java b/services/core/java/com/android/server/power/WakeLockLog.java
index d20c7f1..d3486a4 100644
--- a/services/core/java/com/android/server/power/WakeLockLog.java
+++ b/services/core/java/com/android/server/power/WakeLockLog.java
@@ -16,20 +16,26 @@
 
 package com.android.server.power;
 
+import android.content.Context;
+import android.content.pm.PackageManager;
 import android.os.PowerManager;
 import android.text.TextUtils;
 import android.util.Slog;
+import android.util.SparseArray;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.BackgroundThread;
 
 import java.io.PrintWriter;
 import java.text.SimpleDateFormat;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.ConcurrentModificationException;
 import java.util.Date;
 import java.util.Iterator;
+import java.util.List;
 import java.util.NoSuchElementException;
+import java.util.Objects;
 
 /**
  * Simple Log for wake lock events. Optimized to reduce memory usage.
@@ -117,7 +123,7 @@
     private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("MM-dd HH:mm:ss.SSS");
 
     /**
-     * Lock protects WakeLockLock.dump (binder thread) from conflicting with changes to the log
+     * Lock protects WakeLockLog.dump (binder thread) from conflicting with changes to the log
      * happening on the background thread.
      */
     private final Object mLock = new Object();
@@ -126,18 +132,20 @@
     private final TheLog mLog;
     private final TagDatabase mTagDatabase;
     private final SimpleDateFormat mDumpsysDateFormat;
+    private final Context mContext;
 
-    WakeLockLog() {
-        this(new Injector());
+    WakeLockLog(Context context) {
+        this(new Injector(), context);
     }
 
     @VisibleForTesting
-    WakeLockLog(Injector injector) {
+    WakeLockLog(Injector injector, Context context) {
         mInjector = injector;
         mTagDatabase = new TagDatabase(injector);
         EntryByteTranslator translator = new EntryByteTranslator(mTagDatabase);
         mLog = new TheLog(injector, translator, mTagDatabase);
         mDumpsysDateFormat = injector.getDateFormat();
+        mContext = context;
     }
 
     /**
@@ -176,10 +184,24 @@
         try {
             synchronized (mLock) {
                 pw.println("Wake Lock Log");
-                LogEntry tempEntry = new LogEntry();  // Temporary entry for the iterator to reuse.
-                final Iterator<LogEntry> iterator = mLog.getAllItems(tempEntry);
                 int numEvents = 0;
                 int numResets = 0;
+                SparseArray<String[]> uidToPackagesCache = new SparseArray();
+
+                for (int i = 0; i < mLog.mSavedAcquisitions.size(); i++) {
+                    numEvents++;
+                    LogEntry entry = mLog.mSavedAcquisitions.get(i);
+
+                    entry.updatePackageName(uidToPackagesCache, mContext.getPackageManager());
+
+                    if (DEBUG) {
+                        pw.print("Saved acquisition no. " + i);
+                    }
+                    entry.dump(pw, mDumpsysDateFormat);
+                }
+
+                LogEntry tempEntry = new LogEntry();  // Temporary entry for the iterator to reuse.
+                final Iterator<LogEntry> iterator = mLog.getAllItems(tempEntry);
                 while (iterator.hasNext()) {
                     String address = null;
                     if (DEBUG) {
@@ -192,6 +214,8 @@
                             numResets++;
                         } else {
                             numEvents++;
+                            entry.updatePackageName(uidToPackagesCache,
+                                    mContext.getPackageManager());
                             if (DEBUG) {
                                 pw.print(address);
                             }
@@ -381,6 +405,11 @@
          */
         public int flags;
 
+        /**
+         * The name of the package that acquired the wake lock
+         */
+        public String packageName;
+
         LogEntry() {}
 
         LogEntry(long time, int type, TagData tag, int flags) {
@@ -438,8 +467,13 @@
             }
             sb.append(dateFormat.format(new Date(time)))
                     .append(" - ")
-                    .append(tag == null ? "---" : tag.ownerUid)
-                    .append(" - ")
+                    .append(tag == null ? "---" : tag.ownerUid);
+            if (packageName != null) {
+                sb.append(" (");
+                sb.append(packageName);
+                sb.append(")");
+            }
+            sb.append(" - ")
                     .append(type == TYPE_ACQUIRE ? "ACQ" : "REL")
                     .append(" ")
                     .append(tag == null ? "UNKNOWN" : tag.tag);
@@ -463,6 +497,36 @@
                 sb.append(",system-wakelock");
             }
         }
+
+        /**
+         * Update the package name using the cache if available or the package manager.
+         * @param uidToPackagesCache The cache of package names
+         * @param packageManager The package manager
+         */
+        public void updatePackageName(SparseArray<String[]> uidToPackagesCache,
+                PackageManager packageManager) {
+            if (tag == null) {
+                return;
+            }
+
+            String[] packages;
+            if (uidToPackagesCache.contains(tag.ownerUid)) {
+                packages = uidToPackagesCache.get(tag.ownerUid);
+            } else {
+                packages = packageManager.getPackagesForUid(tag.ownerUid);
+                uidToPackagesCache.put(tag.ownerUid, packages);
+            }
+
+            if (packages != null && packages.length > 0) {
+                packageName = packages[0];
+                if (packages.length > 1) {
+                    StringBuilder sb = new StringBuilder();
+                    sb.append(packageName)
+                            .append(",...");
+                    packageName = sb.toString();
+                }
+            }
+        }
     }
 
     /**
@@ -744,6 +808,12 @@
 
         private final TagDatabase mTagDatabase;
 
+        /**
+         * Wake lock acquisition events should continue to be printed until their corresponding
+         * release event is removed from the log.
+         */
+        private final List<LogEntry> mSavedAcquisitions;
+
         TheLog(Injector injector, EntryByteTranslator translator, TagDatabase tagDatabase) {
             final int logSize = Math.max(injector.getLogSize(), LOG_SIZE_MIN);
             mBuffer = new byte[logSize];
@@ -758,6 +828,8 @@
                     removeTagIndex(index);
                 }
             });
+
+            mSavedAcquisitions = new ArrayList();
         }
 
         /**
@@ -976,6 +1048,19 @@
 
             // Copy the contents of the start of the buffer to our temporary buffer.
             LogEntry entry = readEntryAt(mStart, mStartTime, null);
+            if (entry.type == TYPE_ACQUIRE) {
+                // We'll continue to print the event until the corresponding release event is also
+                // removed from the log.
+                mSavedAcquisitions.add(entry);
+            } else if (entry.type == TYPE_RELEASE) {
+                // We no longer need to print the corresponding acquire event.
+                for (int i = 0; i < mSavedAcquisitions.size(); i++) {
+                    if (Objects.equals(mSavedAcquisitions.get(i).tag, entry.tag)) {
+                        mSavedAcquisitions.remove(i);
+                        break;
+                    }
+                }
+            }
             if (DEBUG) {
                 Slog.d(TAG, "Removing oldest item at @ " + mStart + ", found: " + entry);
             }
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 b4613a7..07d1844 100644
--- a/services/core/java/com/android/server/power/batterysaver/BatterySaverPolicy.java
+++ b/services/core/java/com/android/server/power/batterysaver/BatterySaverPolicy.java
@@ -19,6 +19,7 @@
 import android.app.UiModeManager;
 import android.content.ContentResolver;
 import android.content.Context;
+import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.net.Uri;
 import android.os.BatterySaverPolicyConfig;
@@ -162,26 +163,8 @@
 
     private static final Policy DEFAULT_ADAPTIVE_POLICY = OFF_POLICY;
 
-    private static final Policy DEFAULT_FULL_POLICY = new Policy(
-            0.5f,  /* adjustBrightnessFactor */
-            true,  /* advertiseIsEnabled */
-            true,  /* deferFullBackup */
-            true,  /* deferKeyValueBackup */
-            false, /* disableAnimation */
-            true,  /* disableAod */
-            true,  /* disableLaunchBoost */
-            true,  /* disableOptionalSensors */
-            true,  /* disableVibration */
-            false, /* enableAdjustBrightness */
-            false, /* enableDataSaver */
-            true,  /* enableFirewall */
-            true, /* enableNightMode */
-            true, /* enableQuickDoze */
-            true, /* forceAllAppsStandby */
-            true, /* forceBackgroundCheck */
-            PowerManager.LOCATION_MODE_FOREGROUND_ONLY, /* locationMode */
-            PowerManager.SOUND_TRIGGER_MODE_CRITICAL_ONLY /* soundTriggerMode */
-    );
+    /** The base default full policy for the device. */
+    private final Policy DEFAULT_FULL_POLICY;
 
     private final Object mLock;
     private final Handler mHandler;
@@ -222,13 +205,13 @@
     @GuardedBy("mLock")
     private Policy mAdaptivePolicy = DEFAULT_ADAPTIVE_POLICY;
 
-    /** The current default full policy. */
+    /** The current default full policy. This may be modified by Settings or DeviceConfig flags. */
     @GuardedBy("mLock")
-    private Policy mDefaultFullPolicy = DEFAULT_FULL_POLICY;
+    private Policy mDefaultFullPolicy;
 
     /** The policy to be used for full battery saver. */
     @GuardedBy("mLock")
-    private Policy mFullPolicy = DEFAULT_FULL_POLICY;
+    private Policy mFullPolicy;
 
     /**
      * The current effective policy. This is based on the current policy level's policy, with any
@@ -273,6 +256,30 @@
         mContext = context;
         mContentResolver = context.getContentResolver();
         mBatterySavingStats = batterySavingStats;
+
+        final Resources res = context.getResources();
+        DEFAULT_FULL_POLICY = new Policy(
+                res.getFloat(R.dimen.config_batterySaver_full_adjustBrightnessFactor),
+                true,  /* advertiseIsEnabled */
+                res.getBoolean(R.bool.config_batterySaver_full_deferFullBackup),
+                res.getBoolean(R.bool.config_batterySaver_full_deferKeyValueBackup),
+                res.getBoolean(R.bool.config_batterySaver_full_disableAnimation),
+                res.getBoolean(R.bool.config_batterySaver_full_disableAod),
+                res.getBoolean(R.bool.config_batterySaver_full_disableLaunchBoost),
+                res.getBoolean(R.bool.config_batterySaver_full_disableOptionalSensors),
+                res.getBoolean(R.bool.config_batterySaver_full_disableVibration),
+                res.getBoolean(R.bool.config_batterySaver_full_enableAdjustBrightness),
+                res.getBoolean(R.bool.config_batterySaver_full_enableDataSaver),
+                res.getBoolean(R.bool.config_batterySaver_full_enableFirewall),
+                res.getBoolean(R.bool.config_batterySaver_full_enableNightMode),
+                res.getBoolean(R.bool.config_batterySaver_full_enableQuickDoze),
+                res.getBoolean(R.bool.config_batterySaver_full_forceAllAppsStandby),
+                res.getBoolean(R.bool.config_batterySaver_full_forceBackgroundCheck),
+                res.getInteger(R.integer.config_batterySaver_full_locationMode),
+                res.getInteger(R.integer.config_batterySaver_full_soundTriggerMode)
+        );
+        mDefaultFullPolicy = DEFAULT_FULL_POLICY;
+        mFullPolicy = DEFAULT_FULL_POLICY;
     }
 
     /**
diff --git a/services/core/java/com/android/server/power/batterysaver/OWNERS b/services/core/java/com/android/server/power/batterysaver/OWNERS
index 09136dc..cf23bea 100644
--- a/services/core/java/com/android/server/power/batterysaver/OWNERS
+++ b/services/core/java/com/android/server/power/batterysaver/OWNERS
@@ -1 +1,3 @@
+kwekua@google.com
 omakoto@google.com
+yamasani@google.com
\ No newline at end of file
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 751f535..d5fd017 100644
--- a/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java
+++ b/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java
@@ -54,6 +54,7 @@
 import java.util.concurrent.Executor;
 import java.util.concurrent.Executors;
 import java.util.concurrent.Future;
+import java.util.concurrent.RejectedExecutionException;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.ThreadFactory;
 import java.util.concurrent.TimeUnit;
@@ -404,8 +405,10 @@
      * within the task, never wait on the resulting Future. This will result in a deadlock.
      */
     public synchronized void scheduleRunnable(Runnable runnable) {
-        if (!mExecutorService.isShutdown()) {
+        try {
             mExecutorService.submit(runnable);
+        } catch (RejectedExecutionException e) {
+            Slog.e(TAG, "Couldn't schedule " + runnable, e);
         }
     }
 
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 27329e2..80c21f4 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -113,6 +113,7 @@
 import com.android.internal.os.BinderCallsStats;
 import com.android.internal.os.BinderTransactionNameResolver;
 import com.android.internal.os.Clock;
+import com.android.internal.os.CpuScalingPolicies;
 import com.android.internal.os.KernelCpuSpeedReader;
 import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidActiveTimeReader;
 import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidClusterTimeReader;
@@ -251,10 +252,14 @@
     private static final LongCounter[] ZERO_LONG_COUNTER_ARRAY =
             new LongCounter[]{ZERO_LONG_COUNTER};
 
-    private final KernelWakelockReader mKernelWakelockReader = new KernelWakelockReader();
+    @VisibleForTesting
+    protected CpuScalingPolicies mCpuScalingPolicies;
+
     private final KernelWakelockStats mTmpWakelockStats = new KernelWakelockStats();
 
     @VisibleForTesting
+    protected KernelWakelockReader mKernelWakelockReader;
+    @VisibleForTesting
     protected KernelCpuUidUserSysTimeReader mCpuUidUserSysTimeReader;
     @VisibleForTesting
     protected KernelCpuSpeedReader[] mKernelCpuSpeedReaders;
@@ -323,6 +328,7 @@
      * ModemActivityInfo must be available.
      */
     public static final int PER_UID_MODEM_POWER_MODEL_MODEM_ACTIVITY_INFO_RX_TX = 2;
+
     @IntDef(flag = true, prefix = "PER_UID_MODEM_MODEL_", value = {
             PER_UID_MODEM_POWER_MODEL_MOBILE_RADIO_ACTIVE_TIME,
             PER_UID_MODEM_POWER_MODEL_MODEM_ACTIVITY_INFO_RX_TX,
@@ -577,9 +583,7 @@
     @SuppressWarnings("GuardedBy")    // errorprone false positive on getProcStateTimeCounter
     @VisibleForTesting
     public void updateProcStateCpuTimesLocked(int uid, long elapsedRealtimeMs, long uptimeMs) {
-        if (!initKernelSingleUidTimeReaderLocked()) {
-            return;
-        }
+        ensureKernelSingleUidTimeReaderLocked();
 
         final Uid u = getUidStatsLocked(uid);
 
@@ -656,9 +660,7 @@
                 return;
             }
 
-            if(!initKernelSingleUidTimeReaderLocked()) {
-                return;
-            }
+            ensureKernelSingleUidTimeReaderLocked();
 
             // TODO(b/197162116): just get a list of UIDs
             final SparseArray<long[]> allUidCpuFreqTimesMs =
@@ -718,24 +720,15 @@
     }
 
     @GuardedBy("this")
-    private boolean initKernelSingleUidTimeReaderLocked() {
-        if (mKernelSingleUidTimeReader == null) {
-            if (mPowerProfile == null) {
-                return false;
-            }
-            if (mCpuFreqs == null) {
-                mCpuFreqs = mCpuUidFreqTimeReader.readFreqs(mPowerProfile);
-            }
-            if (mCpuFreqs != null) {
-                mKernelSingleUidTimeReader = new KernelSingleUidTimeReader(mCpuFreqs.length);
-            } else {
-                mPerProcStateCpuTimesAvailable = mCpuUidFreqTimeReader.allUidTimesAvailable();
-                return false;
-            }
+    private void ensureKernelSingleUidTimeReaderLocked() {
+        if (mKernelSingleUidTimeReader != null) {
+            return;
         }
-        mPerProcStateCpuTimesAvailable = mCpuUidFreqTimeReader.allUidTimesAvailable()
+
+        mKernelSingleUidTimeReader = new KernelSingleUidTimeReader(
+                mCpuScalingPolicies.getScalingStepCount());
+        mPerProcStateCpuTimesAvailable = mCpuUidFreqTimeReader.perClusterTimesAvailable()
                 && mKernelSingleUidTimeReader.singleUidCpuTimesAvailable();
-        return true;
     }
 
     public interface ExternalStatsSync {
@@ -1032,6 +1025,7 @@
     private static final int USB_DATA_CONNECTED = 2;
     int mUsbDataState = USB_DATA_UNKNOWN;
 
+    private static final int GPS_SIGNAL_QUALITY_NONE = 2;
     int mGpsSignalQualityBin = -1;
     final StopwatchTimer[] mGpsSignalQualityTimer =
         new StopwatchTimer[GnssSignalQuality.NUM_GNSS_SIGNAL_QUALITY_LEVELS];
@@ -1542,8 +1536,6 @@
 
     private long mBatteryTimeToFullSeconds = -1;
 
-    private boolean mCpuFreqsInitialized;
-    private long[] mCpuFreqs;
     private LongArrayMultiStateCounter.LongArrayContainer mTmpCpuTimeInFreq;
 
     /**
@@ -1763,6 +1755,7 @@
         mCpuUidFreqTimeReader = new KernelCpuUidFreqTimeReader(true, clock);
         mCpuUidActiveTimeReader = new KernelCpuUidActiveTimeReader(true, clock);
         mCpuUidClusterTimeReader = new KernelCpuUidClusterTimeReader(true, clock);
+        mKernelWakelockReader = new KernelWakelockReader();
     }
 
     /**
@@ -2675,19 +2668,18 @@
          * The reported count from /proc/wakelocks when unplug() was last
          * called.
          */
-        int mUnpluggedReportedCount;
+        int mBaseReportedCount;
 
         /**
          * The most recent reported total_time from /proc/wakelocks.
          */
         long mCurrentReportedTotalTimeUs;
 
-
         /**
          * The reported total_time from /proc/wakelocks when unplug() was last
          * called.
          */
-        long mUnpluggedReportedTotalTimeUs;
+        long mBaseReportedTotalTimeUs;
 
         /**
          * Whether we are currently in a discharge cycle.
@@ -2708,9 +2700,9 @@
         public SamplingTimer(Clock clock, TimeBase timeBase, Parcel in) {
             super(clock, 0, timeBase, in);
             mCurrentReportedCount = in.readInt();
-            mUnpluggedReportedCount = in.readInt();
+            mBaseReportedCount = in.readInt();
             mCurrentReportedTotalTimeUs = in.readLong();
-            mUnpluggedReportedTotalTimeUs = in.readLong();
+            mBaseReportedTotalTimeUs = in.readLong();
             mTrackingReportedValues = in.readInt() == 1;
             mTimeBaseRunning = timeBase.isRunning();
         }
@@ -2736,8 +2728,8 @@
         public void endSample(long elapsedRealtimeUs) {
             mTotalTimeUs = computeRunTimeLocked(0 /* unused by us */, elapsedRealtimeUs);
             mCount = computeCurrentCountLocked();
-            mUnpluggedReportedTotalTimeUs = mCurrentReportedTotalTimeUs = 0;
-            mUnpluggedReportedCount = mCurrentReportedCount = 0;
+            mBaseReportedTotalTimeUs = mCurrentReportedTotalTimeUs = 0;
+            mBaseReportedCount = mCurrentReportedCount = 0;
             mTrackingReportedValues = false;
         }
 
@@ -2762,10 +2754,21 @@
          * @param count total number of times the event being sampled occurred.
          */
         public void update(long totalTimeUs, int count, long elapsedRealtimeUs) {
+            update(totalTimeUs, 0, count, elapsedRealtimeUs);
+        }
+
+        /**
+         * Updates the current recorded values. See {@link #update(long, int, long)}
+         *
+         * @param activeTimeUs Time that the currently active wake lock has been held.
+         */
+        public void update(long totalTimeUs, long activeTimeUs, int count,
+                long elapsedRealtimeUs) {
             if (mTimeBaseRunning && !mTrackingReportedValues) {
-                // Updating the reported value for the first time.
-                mUnpluggedReportedTotalTimeUs = totalTimeUs;
-                mUnpluggedReportedCount = count;
+                // Updating the reported value for the first time. If the wake lock is currently
+                // active, mark the time it was acquired as the base timestamp.
+                mBaseReportedTotalTimeUs = totalTimeUs - activeTimeUs;
+                mBaseReportedCount = activeTimeUs == 0 ? count : count - 1;
             }
 
             mTrackingReportedValues = true;
@@ -2800,8 +2803,8 @@
         public void onTimeStarted(long elapsedRealtimeUs, long baseUptimeUs, long baseRealtimeUs) {
             super.onTimeStarted(elapsedRealtimeUs, baseUptimeUs, baseRealtimeUs);
             if (mTrackingReportedValues) {
-                mUnpluggedReportedTotalTimeUs = mCurrentReportedTotalTimeUs;
-                mUnpluggedReportedCount = mCurrentReportedCount;
+                mBaseReportedTotalTimeUs = mCurrentReportedTotalTimeUs;
+                mBaseReportedCount = mCurrentReportedCount;
             }
             mTimeBaseRunning = true;
         }
@@ -2816,30 +2819,30 @@
         public void logState(Printer pw, String prefix) {
             super.logState(pw, prefix);
             pw.println(prefix + "mCurrentReportedCount=" + mCurrentReportedCount
-                    + " mUnpluggedReportedCount=" + mUnpluggedReportedCount
+                    + " mBaseReportedCount=" + mBaseReportedCount
                     + " mCurrentReportedTotalTime=" + mCurrentReportedTotalTimeUs
-                    + " mUnpluggedReportedTotalTime=" + mUnpluggedReportedTotalTimeUs);
+                    + " mBaseReportedTotalTimeUs=" + mBaseReportedTotalTimeUs);
         }
 
         @Override
         protected long computeRunTimeLocked(long curBatteryRealtime, long elapsedRealtimeUs) {
             return mTotalTimeUs + (mTimeBaseRunning && mTrackingReportedValues
-                    ? mCurrentReportedTotalTimeUs - mUnpluggedReportedTotalTimeUs : 0);
+                    ? mCurrentReportedTotalTimeUs - mBaseReportedTotalTimeUs : 0);
         }
 
         @Override
         protected int computeCurrentCountLocked() {
             return mCount + (mTimeBaseRunning && mTrackingReportedValues
-                    ? mCurrentReportedCount - mUnpluggedReportedCount : 0);
+                    ? mCurrentReportedCount - mBaseReportedCount : 0);
         }
 
         @Override
         public void writeToParcel(Parcel out, long elapsedRealtimeUs) {
             super.writeToParcel(out, elapsedRealtimeUs);
             out.writeInt(mCurrentReportedCount);
-            out.writeInt(mUnpluggedReportedCount);
+            out.writeInt(mBaseReportedCount);
             out.writeLong(mCurrentReportedTotalTimeUs);
-            out.writeLong(mUnpluggedReportedTotalTimeUs);
+            out.writeLong(mBaseReportedTotalTimeUs);
             out.writeInt(mTrackingReportedValues ? 1 : 0);
         }
 
@@ -2847,8 +2850,8 @@
         public boolean reset(boolean detachIfReset, long elapsedRealtimeUs) {
             super.reset(detachIfReset, elapsedRealtimeUs);
             mTrackingReportedValues = false;
-            mUnpluggedReportedTotalTimeUs = 0;
-            mUnpluggedReportedCount = 0;
+            mBaseReportedTotalTimeUs = 0;
+            mBaseReportedCount = 0;
             return true;
         }
     }
@@ -5210,6 +5213,8 @@
         if (mGpsNesting == 0) {
             mHistory.recordStateStopEvent(elapsedRealtimeMs, uptimeMs,
                     HistoryItem.STATE_GPS_ON_FLAG);
+            mHistory.recordGpsSignalQualityEvent(elapsedRealtimeMs, uptimeMs,
+                    GPS_SIGNAL_QUALITY_NONE);
             stopAllGpsSignalQualityTimersLocked(-1, elapsedRealtimeMs);
             mGpsSignalQualityBin = -1;
         }
@@ -8209,11 +8214,13 @@
 
             mProcStateTimeMs =
                     new TimeInFreqMultiStateCounter(mBsi.mOnBatteryTimeBase,
-                            PROC_STATE_TIME_COUNTER_STATE_COUNT, mBsi.getCpuFreqCount(),
+                            PROC_STATE_TIME_COUNTER_STATE_COUNT,
+                            mBsi.mCpuScalingPolicies.getScalingStepCount(),
                             timestampMs);
             mProcStateScreenOffTimeMs =
                     new TimeInFreqMultiStateCounter(mBsi.mOnBatteryScreenOffTimeBase,
-                            PROC_STATE_TIME_COUNTER_STATE_COUNT, mBsi.getCpuFreqCount(),
+                            PROC_STATE_TIME_COUNTER_STATE_COUNT,
+                            mBsi.mCpuScalingPolicies.getScalingStepCount(),
                             timestampMs);
         }
 
@@ -9214,6 +9221,7 @@
         }
 
         @Override
+        @Deprecated
         public long getTimeAtCpuSpeed(int cluster, int step, int which) {
             if (mCpuClusterSpeedTimesUs != null) {
                 if (cluster >= 0 && cluster < mCpuClusterSpeedTimesUs.length) {
@@ -10445,7 +10453,7 @@
                 cpuActiveCounter.setState(0, timestampMs);
 
                 if (mBsi.trackPerProcStateCpuTimes()) {
-                    final int cpuFreqCount = mBsi.getCpuFreqCount();
+                    final int cpuFreqCount = mBsi.mCpuScalingPolicies.getScalingStepCount();
 
                     cpuTimeInFreqCounter = new LongArrayMultiStateCounter(1, cpuFreqCount);
 
@@ -10848,44 +10856,43 @@
     }
 
     @GuardedBy("this")
-    @Override
-    public long[] getCpuFreqs() {
-        if (!mCpuFreqsInitialized) {
-            mCpuFreqs = mCpuUidFreqTimeReader.readFreqs(mPowerProfile);
-            mCpuFreqsInitialized = true;
-        }
-        return mCpuFreqs;
-    }
-
-    @GuardedBy("this")
-    @Override
-    public int getCpuFreqCount() {
-        final long[] cpuFreqs = getCpuFreqs();
-        return cpuFreqs != null ? cpuFreqs.length : 0;
+    public CpuScalingPolicies getCpuScalingPolicies() {
+        return mCpuScalingPolicies;
     }
 
     @GuardedBy("this")
     private LongArrayMultiStateCounter.LongArrayContainer getCpuTimeInFreqContainer() {
         if (mTmpCpuTimeInFreq == null) {
             mTmpCpuTimeInFreq =
-                    new LongArrayMultiStateCounter.LongArrayContainer(getCpuFreqCount());
+                    new LongArrayMultiStateCounter.LongArrayContainer(
+                            mCpuScalingPolicies.getScalingStepCount());
         }
         return mTmpCpuTimeInFreq;
     }
 
-    public BatteryStatsImpl(File systemDir, Handler handler, PlatformIdleStateCallback cb,
-            EnergyStatsRetriever energyStatsCb, UserInfoProvider userInfoProvider) {
-        this(Clock.SYSTEM_CLOCK, systemDir, handler, cb, energyStatsCb, userInfoProvider);
+    public BatteryStatsImpl(@Nullable File systemDir, @NonNull Handler handler,
+            @Nullable PlatformIdleStateCallback cb, @Nullable EnergyStatsRetriever energyStatsCb,
+            @NonNull UserInfoProvider userInfoProvider, @NonNull PowerProfile powerProfile,
+            @NonNull CpuScalingPolicies cpuScalingPolicies) {
+        this(Clock.SYSTEM_CLOCK, systemDir, handler, cb, energyStatsCb, userInfoProvider,
+                powerProfile, cpuScalingPolicies);
     }
 
-    private BatteryStatsImpl(Clock clock, File systemDir, Handler handler,
-            PlatformIdleStateCallback cb, EnergyStatsRetriever energyStatsCb,
-            UserInfoProvider userInfoProvider) {
+    private BatteryStatsImpl(@NonNull Clock clock, @Nullable File systemDir,
+            @NonNull Handler handler, @Nullable PlatformIdleStateCallback cb,
+            @Nullable EnergyStatsRetriever energyStatsCb,
+            @NonNull UserInfoProvider userInfoProvider, @NonNull PowerProfile powerProfile,
+            @NonNull CpuScalingPolicies cpuScalingPolicies) {
         init(clock);
 
         mHandler = new MyHandler(handler.getLooper());
         mConstants = new Constants(mHandler);
 
+        mPowerProfile = powerProfile;
+        mCpuScalingPolicies = cpuScalingPolicies;
+
+        initPowerProfile();
+
         if (systemDir == null) {
             mStatsFile = null;
             mCheckinFile = null;
@@ -11001,39 +11008,27 @@
         mBatteryLevel = 0;
     }
 
-    /**
-     * Injects a power profile.
-     */
-    @GuardedBy("this")
-    public void setPowerProfileLocked(PowerProfile profile) {
-        mPowerProfile = profile;
-
-        int totalSpeedStepCount = 0;
-
-        // We need to initialize the KernelCpuSpeedReaders to read from
-        // the first cpu of each core. Once we have the PowerProfile, we have access to this
-        // information.
-        final int numClusters = mPowerProfile.getNumCpuClusters();
-        mKernelCpuSpeedReaders = new KernelCpuSpeedReader[numClusters];
-        int firstCpuOfCluster = 0;
-        for (int i = 0; i < numClusters; i++) {
-            final int numSpeedSteps = mPowerProfile.getNumSpeedStepsInCpuCluster(i);
-            mKernelCpuSpeedReaders[i] = new KernelCpuSpeedReader(firstCpuOfCluster,
-                    numSpeedSteps);
-            firstCpuOfCluster += mPowerProfile.getNumCoresInCpuCluster(i);
-            totalSpeedStepCount += numSpeedSteps;
+    private void initPowerProfile() {
+        int[] policies = mCpuScalingPolicies.getPolicies();
+        mKernelCpuSpeedReaders = new KernelCpuSpeedReader[policies.length];
+        for (int i = 0; i < policies.length; i++) {
+            int[] cpus = mCpuScalingPolicies.getRelatedCpus(policies[i]);
+            int[] freqs = mCpuScalingPolicies.getFrequencies(policies[i]);
+            // We need to initialize the KernelCpuSpeedReaders to read from
+            // the first cpu of each core. Once we have the CpuScalingPolicy, we have access to this
+            // information.
+            mKernelCpuSpeedReaders[i] = new KernelCpuSpeedReader(cpus[0], freqs.length);
         }
 
         // Initialize CPU power bracket map, which combines CPU states (cluster/freq pairs)
         // into a small number of brackets
-        mCpuPowerBracketMap = new int[totalSpeedStepCount];
+        mCpuPowerBracketMap = new int[mCpuScalingPolicies.getScalingStepCount()];
         int index = 0;
-        int numCpuClusters = mPowerProfile.getNumCpuClusters();
-        for (int cluster = 0; cluster < numCpuClusters; cluster++) {
-            int steps = mPowerProfile.getNumSpeedStepsInCpuCluster(cluster);
+        for (int policy : policies) {
+            int steps = mCpuScalingPolicies.getFrequencies(policy).length;
             for (int step = 0; step < steps; step++) {
                 mCpuPowerBracketMap[index++] =
-                        mPowerProfile.getPowerBracketForCpuCore(cluster, step);
+                        mPowerProfile.getCpuPowerBracketForScalingStep(policy, step);
             }
         }
 
@@ -11042,7 +11037,7 @@
         mCpuUsageDetails.cpuUsageMs = new long[cpuPowerBracketCount];
         for (int i = 0; i < cpuPowerBracketCount; i++) {
             mCpuUsageDetails.cpuBracketDescriptions[i] =
-                    mPowerProfile.getCpuPowerBracketDescription(i);
+                    mPowerProfile.getCpuPowerBracketDescription(mCpuScalingPolicies, i);
         }
 
         if (mEstimatedBatteryCapacityMah == -1) {
@@ -12164,7 +12159,8 @@
         if (DEBUG_ENERGY) {
             Slog.d(TAG, "Updating mobile radio stats with " + activityInfo);
         }
-        ModemActivityInfo deltaInfo = mLastModemActivityInfo == null ? activityInfo
+        ModemActivityInfo deltaInfo = mLastModemActivityInfo == null
+                ? (activityInfo == null ? null : activityInfo.getDelta(activityInfo))
                 : mLastModemActivityInfo.getDelta(activityInfo);
         mLastModemActivityInfo = activityInfo;
 
@@ -13398,6 +13394,10 @@
      * Read and distribute kernel wake lock use across apps.
      */
     public void updateKernelWakelocksLocked(long elapsedRealtimeUs) {
+        if (mKernelWakelockReader == null) {
+            return;
+        }
+
         final KernelWakelockStats wakelockStats = mKernelWakelockReader.readKernelWakelockStats(
                 mTmpWakelockStats);
         if (wakelockStats == null) {
@@ -13416,8 +13416,8 @@
                 mKernelWakelockStats.put(name, kwlt);
             }
 
-            kwlt.update(kws.mTotalTime, kws.mCount, elapsedRealtimeUs);
-            kwlt.setUpdateVersion(kws.mVersion);
+            kwlt.update(kws.totalTimeUs, kws.activeTimeUs, kws.count, elapsedRealtimeUs);
+            kwlt.setUpdateVersion(kws.version);
         }
 
         int numWakelocksSetStale = 0;
@@ -13471,7 +13471,7 @@
                 Slog.d(TAG, String.format("Added entry %d and updated timer to: "
                         + "mUnpluggedReportedTotalTimeUs %d size %d", bandwidthEntries.keyAt(i),
                         mKernelMemoryStats.get(
-                                bandwidthEntries.keyAt(i)).mUnpluggedReportedTotalTimeUs,
+                                bandwidthEntries.keyAt(i)).mBaseReportedTotalTimeUs,
                         mKernelMemoryStats.size()));
             }
         }
@@ -13554,18 +13554,10 @@
     @GuardedBy("this")
     public void updateCpuTimeLocked(boolean onBattery, boolean onBatteryScreenOff,
             long[] cpuClusterChargeUC) {
-        if (mPowerProfile == null) {
-            return;
-        }
-
         if (DEBUG_ENERGY_CPU) {
             Slog.d(TAG, "!Cpu updating!");
         }
 
-        if (mCpuFreqs == null) {
-            mCpuFreqs = mCpuUidFreqTimeReader.readFreqs(mPowerProfile);
-        }
-
         // Calculate the wakelocks we have to distribute amongst. The system is excluded as it is
         // usually holding the wakelock on behalf of an app.
         // And Only distribute cpu power to wakelocks if the screen is off and we're on battery.
@@ -13597,31 +13589,37 @@
                 mCpuUidClusterTimeReader.readDelta(false, null);
                 mNumAllUidCpuTimeReads += 2;
             }
-            for (int cluster = mKernelCpuSpeedReaders.length - 1; cluster >= 0; --cluster) {
-                mKernelCpuSpeedReaders[cluster].readDelta();
+            for (int i = mKernelCpuSpeedReaders.length - 1; i >= 0; --i) {
+                if (mKernelCpuSpeedReaders[i] != null) {
+                    mKernelCpuSpeedReaders[i].readDelta();
+                }
             }
             mSystemServerCpuThreadReader.readDelta();
             return;
         }
 
         mUserInfoProvider.refreshUserIds();
-        final SparseLongArray updatedUids = mCpuUidFreqTimeReader.perClusterTimesAvailable()
+        final SparseLongArray updatedUids = mCpuUidFreqTimeReader.allUidTimesAvailable()
                 ? null : new SparseLongArray();
 
         final CpuDeltaPowerAccumulator powerAccumulator;
         if (mGlobalEnergyConsumerStats != null
                 && mGlobalEnergyConsumerStats.isStandardBucketSupported(
-                EnergyConsumerStats.POWER_BUCKET_CPU) && mCpuPowerCalculator != null) {
+                EnergyConsumerStats.POWER_BUCKET_CPU)) {
             if (cpuClusterChargeUC == null) {
                 Slog.wtf(TAG,
                         "POWER_BUCKET_CPU supported but no EnergyConsumer Cpu Cluster charge "
                                 + "reported on updateCpuTimeLocked!");
                 powerAccumulator = null;
             } else {
+                if (mCpuPowerCalculator == null) {
+                    mCpuPowerCalculator = new CpuPowerCalculator(mCpuScalingPolicies,
+                            mPowerProfile);
+                }
                 // Cpu EnergyConsumer is supported, create an object to accumulate the estimated
                 // charge consumption since the last cpu update
-                final int numClusters = mPowerProfile.getNumCpuClusters();
-                powerAccumulator = new CpuDeltaPowerAccumulator(mCpuPowerCalculator, numClusters);
+                powerAccumulator = new CpuDeltaPowerAccumulator(mCpuPowerCalculator,
+                        mCpuScalingPolicies.getPolicies().length);
             }
         } else {
             powerAccumulator = null;
@@ -13679,15 +13677,14 @@
         if (DEBUG_BINDER_STATS) {
             Slog.d(TAG, "System server threads per CPU cluster (incoming binder threads)");
             long binderThreadTimeMs = 0;
-            int cpuIndex = 0;
             final long[] binderThreadCpuTimesUs = mBinderThreadCpuTimesUs.getCountsLocked(
                     BatteryStats.STATS_SINCE_CHARGED);
             int index = 0;
-            int numCpuClusters = mPowerProfile.getNumCpuClusters();
-            for (int cluster = 0; cluster < numCpuClusters; cluster++) {
+            int[] policies = mCpuScalingPolicies.getPolicies();
+            for (int policy : policies) {
                 StringBuilder sb = new StringBuilder();
-                sb.append("cpu").append(cpuIndex).append(": [");
-                int numSpeeds = mPowerProfile.getNumSpeedStepsInCpuCluster(cluster);
+                sb.append("policy").append(policy).append(": [");
+                int numSpeeds = mCpuScalingPolicies.getFrequencies(policy).length;
                 for (int speed = 0; speed < numSpeeds; speed++) {
                     if (speed != 0) {
                         sb.append(", ");
@@ -13698,7 +13695,6 @@
                     binderThreadTimeMs += binderCountMs;
                     index++;
                 }
-                cpuIndex += mPowerProfile.getNumCoresInCpuCluster(cluster);
                 Slog.d(TAG, sb.toString());
             }
         }
@@ -13749,10 +13745,12 @@
         // Read the time spent for each cluster at various cpu frequencies.
         final long[][] clusterSpeedTimesMs = new long[mKernelCpuSpeedReaders.length][];
         for (int cluster = 0; cluster < mKernelCpuSpeedReaders.length; cluster++) {
-            clusterSpeedTimesMs[cluster] = mKernelCpuSpeedReaders[cluster].readDelta();
-            if (clusterSpeedTimesMs[cluster] != null) {
-                for (int speed = clusterSpeedTimesMs[cluster].length - 1; speed >= 0; --speed) {
-                    totalCpuClustersTimeMs += clusterSpeedTimesMs[cluster][speed];
+            if (mKernelCpuSpeedReaders[cluster] != null) {
+                clusterSpeedTimesMs[cluster] = mKernelCpuSpeedReaders[cluster].readDelta();
+                if (clusterSpeedTimesMs[cluster] != null) {
+                    for (int speed = clusterSpeedTimesMs[cluster].length - 1; speed >= 0; --speed) {
+                        totalCpuClustersTimeMs += clusterSpeedTimesMs[cluster][speed];
+                    }
                 }
             }
         }
@@ -13767,13 +13765,13 @@
                 final Uid u = getUidStatsLocked(updatedUids.keyAt(i), elapsedRealtimeMs, uptimeMs);
                 final long appCpuTimeUs = updatedUids.valueAt(i);
                 // Add the cpu speeds to this UID.
-                final int numClusters = mPowerProfile.getNumCpuClusters();
+                int[] policies = mCpuScalingPolicies.getPolicies();
                 if (u.mCpuClusterSpeedTimesUs == null ||
-                        u.mCpuClusterSpeedTimesUs.length != numClusters) {
-                    u.mCpuClusterSpeedTimesUs = new LongSamplingCounter[numClusters][];
+                        u.mCpuClusterSpeedTimesUs.length != policies.length) {
+                    u.mCpuClusterSpeedTimesUs = new LongSamplingCounter[policies.length][];
                 }
 
-                for (int cluster = 0; cluster < clusterSpeedTimesMs.length; cluster++) {
+                for (int cluster = 0; cluster < policies.length; cluster++) {
                     final int speedsInCluster = clusterSpeedTimesMs[cluster].length;
                     if (u.mCpuClusterSpeedTimesUs[cluster] == null || speedsInCluster !=
                             u.mCpuClusterSpeedTimesUs[cluster].length) {
@@ -13929,7 +13927,8 @@
         final boolean perClusterTimesAvailable =
                 mCpuUidFreqTimeReader.perClusterTimesAvailable();
         final int numWakelocks = partialTimers == null ? 0 : partialTimers.size();
-        final int numClusters = mPowerProfile.getNumCpuClusters();
+        final int[] policies = mCpuScalingPolicies.getPolicies();
+        final int numClusters = policies.length;
         mWakeLockAllocationsUs = null;
         final long startTimeMs = mClock.uptimeMillis();
         final long elapsedRealtimeMs = mClock.elapsedRealtime();
@@ -13970,19 +13969,18 @@
                 }
 
                 int freqIndex = 0;
-                for (int cluster = 0; cluster < numClusters; ++cluster) {
-                    final int speedsInCluster = mPowerProfile.getNumSpeedStepsInCpuCluster(cluster);
+                for (int cluster = 0; cluster < numClusters; cluster++) {
+                    final int[] freqs = mCpuScalingPolicies.getFrequencies(policies[cluster]);
                     if (u.mCpuClusterSpeedTimesUs[cluster] == null ||
-                            u.mCpuClusterSpeedTimesUs[cluster].length != speedsInCluster) {
+                            u.mCpuClusterSpeedTimesUs[cluster].length != freqs.length) {
                         detachIfNotNull(u.mCpuClusterSpeedTimesUs[cluster]);
-                        u.mCpuClusterSpeedTimesUs[cluster]
-                                = new LongSamplingCounter[speedsInCluster];
+                        u.mCpuClusterSpeedTimesUs[cluster] = new LongSamplingCounter[freqs.length];
                     }
                     if (numWakelocks > 0 && mWakeLockAllocationsUs[cluster] == null) {
-                        mWakeLockAllocationsUs[cluster] = new long[speedsInCluster];
+                        mWakeLockAllocationsUs[cluster] = new long[freqs.length];
                     }
                     final LongSamplingCounter[] cpuTimesUs = u.mCpuClusterSpeedTimesUs[cluster];
-                    for (int speed = 0; speed < speedsInCluster; ++speed) {
+                    for (int speed = 0; speed < freqs.length; ++speed) {
                         if (cpuTimesUs[speed] == null) {
                             cpuTimesUs[speed] = new LongSamplingCounter(mOnBatteryTimeBase);
                         }
@@ -14022,7 +14020,8 @@
                 }
 
                 for (int cluster = 0; cluster < numClusters; ++cluster) {
-                    final int speedsInCluster = mPowerProfile.getNumSpeedStepsInCpuCluster(cluster);
+                    final int speedsInCluster =
+                            mCpuScalingPolicies.getFrequencies(policies[cluster]).length;
                     if (u.mCpuClusterSpeedTimesUs[cluster] == null ||
                             u.mCpuClusterSpeedTimesUs[cluster].length != speedsInCluster) {
                         detachIfNotNull(u.mCpuClusterSpeedTimesUs[cluster]);
@@ -14152,6 +14151,9 @@
      * Notifies BatteryStatsImpl that the system server is ready.
      */
     public void onSystemReady() {
+        if (mCpuUidFreqTimeReader != null) {
+            mCpuUidFreqTimeReader.onSystemReady();
+        }
         mSystemReady = true;
     }
 
@@ -15222,9 +15224,6 @@
             if (supportedStandardBuckets[EnergyConsumerStats.POWER_BUCKET_BLUETOOTH]) {
                 mBluetoothPowerCalculator = new BluetoothPowerCalculator(mPowerProfile);
             }
-            if (supportedStandardBuckets[EnergyConsumerStats.POWER_BUCKET_CPU]) {
-                mCpuPowerCalculator = new CpuPowerCalculator(mPowerProfile);
-            }
             if (supportedStandardBuckets[EnergyConsumerStats.POWER_BUCKET_MOBILE_RADIO]) {
                 mMobileRadioPowerCalculator = new MobileRadioPowerCalculator(mPowerProfile);
             }
@@ -15612,7 +15611,7 @@
             pw.print("    ");
             pw.print(bracket);
             pw.print(": ");
-            pw.println(mPowerProfile.getCpuPowerBracketDescription(bracket));
+            pw.println(mPowerProfile.getCpuPowerBracketDescription(mCpuScalingPolicies, bracket));
         }
     }
 
@@ -16103,7 +16102,9 @@
 
             if (in.readInt() != 0) {
                 final int numClusters = in.readInt();
-                if (mPowerProfile != null && mPowerProfile.getNumCpuClusters() != numClusters) {
+                int[] policies =
+                        mCpuScalingPolicies != null ? mCpuScalingPolicies.getPolicies() : null;
+                if (policies != null && policies.length != numClusters) {
                     throw new ParcelFormatException("Incompatible cpu cluster arrangement");
                 }
                 detachIfNotNull(u.mCpuClusterSpeedTimesUs);
@@ -16111,8 +16112,9 @@
                 for (int cluster = 0; cluster < numClusters; cluster++) {
                     if (in.readInt() != 0) {
                         final int NSB = in.readInt();
-                        if (mPowerProfile != null &&
-                                mPowerProfile.getNumSpeedStepsInCpuCluster(cluster) != NSB) {
+                        if (policies != null
+                                && mCpuScalingPolicies.getFrequencies(policies[cluster]).length
+                                != NSB) {
                             throw new ParcelFormatException("File corrupt: too many speed bins " +
                                     NSB);
                         }
@@ -16157,7 +16159,7 @@
                 detachIfNotNull(u.mProcStateTimeMs);
                 u.mProcStateTimeMs = TimeInFreqMultiStateCounter.readFromParcel(in,
                         mOnBatteryTimeBase, PROC_STATE_TIME_COUNTER_STATE_COUNT,
-                        getCpuFreqCount(), mClock.elapsedRealtime());
+                        mCpuScalingPolicies.getScalingStepCount(), mClock.elapsedRealtime());
             }
 
             detachIfNotNull(u.mProcStateScreenOffTimeMs);
@@ -16168,7 +16170,7 @@
                 detachIfNotNull(u.mProcStateScreenOffTimeMs);
                 u.mProcStateScreenOffTimeMs = TimeInFreqMultiStateCounter.readFromParcel(in,
                         mOnBatteryScreenOffTimeBase, PROC_STATE_TIME_COUNTER_STATE_COUNT,
-                        getCpuFreqCount(), mClock.elapsedRealtime());
+                        mCpuScalingPolicies.getScalingStepCount(), mClock.elapsedRealtime());
             }
 
             if (in.readInt() != 0) {
@@ -16244,7 +16246,7 @@
             }
 
             NP = in.readInt();
-            if (NP > 1000) {
+            if (NP > 10000) {
                 throw new ParcelFormatException("File corrupt: too many processes " + NP);
             }
             for (int ip = 0; ip < NP; ip++) {
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 ebd4aec..3a32733 100644
--- a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
+++ b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
@@ -31,6 +31,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.CpuScalingPolicies;
 import com.android.internal.os.PowerProfile;
 
 import java.util.ArrayList;
@@ -47,6 +48,7 @@
     private final BatteryStats mStats;
     private final BatteryUsageStatsStore mBatteryUsageStatsStore;
     private final PowerProfile mPowerProfile;
+    private final CpuScalingPolicies mCpuScalingPolicies;
     private final Object mLock = new Object();
     private List<PowerCalculator> mPowerCalculators;
 
@@ -63,6 +65,7 @@
         mPowerProfile = stats instanceof BatteryStatsImpl
                 ? ((BatteryStatsImpl) stats).getPowerProfile()
                 : new PowerProfile(context);
+        mCpuScalingPolicies = stats.getCpuScalingPolicies();
     }
 
     private List<PowerCalculator> getPowerCalculators() {
@@ -72,7 +75,7 @@
 
                 // Power calculators are applied in the order of registration
                 mPowerCalculators.add(new BatteryChargeCalculator());
-                mPowerCalculators.add(new CpuPowerCalculator(mPowerProfile));
+                mPowerCalculators.add(new CpuPowerCalculator(mCpuScalingPolicies, mPowerProfile));
                 mPowerCalculators.add(new MemoryPowerCalculator(mPowerProfile));
                 mPowerCalculators.add(new WakelockPowerCalculator(mPowerProfile));
                 if (!BatteryStats.checkWifiOnly(mContext)) {
@@ -97,7 +100,8 @@
                 // It is important that SystemServicePowerCalculator be applied last,
                 // because it re-attributes some of the power estimated by the other
                 // calculators.
-                mPowerCalculators.add(new SystemServicePowerCalculator(mPowerProfile));
+                mPowerCalculators.add(
+                        new SystemServicePowerCalculator(mCpuScalingPolicies, mPowerProfile));
             }
         }
         return mPowerCalculators;
diff --git a/services/core/java/com/android/server/power/stats/CpuPowerCalculator.java b/services/core/java/com/android/server/power/stats/CpuPowerCalculator.java
index 5074838..ce8680f 100644
--- a/services/core/java/com/android/server/power/stats/CpuPowerCalculator.java
+++ b/services/core/java/com/android/server/power/stats/CpuPowerCalculator.java
@@ -24,6 +24,7 @@
 import android.util.Log;
 import android.util.SparseArray;
 
+import com.android.internal.os.CpuScalingPolicies;
 import com.android.internal.os.PowerProfile;
 
 import java.util.Arrays;
@@ -32,6 +33,8 @@
     private static final String TAG = "CpuPowerCalculator";
     private static final boolean DEBUG = PowerCalculator.DEBUG;
     private static final BatteryConsumer.Key[] UNINITIALIZED_KEYS = new BatteryConsumer.Key[0];
+
+    private final CpuScalingPolicies mCpuScalingPolicies;
     private final int mNumCpuClusters;
 
     // Time-in-state based CPU power estimation model computes the estimated power
@@ -59,34 +62,33 @@
         public long[] cpuFreqTimes;
     }
 
-    public CpuPowerCalculator(PowerProfile profile) {
-        mNumCpuClusters = profile.getNumCpuClusters();
+    public CpuPowerCalculator(CpuScalingPolicies cpuScalingPolicies, PowerProfile profile) {
+        mCpuScalingPolicies = cpuScalingPolicies;
+        int[] policies = mCpuScalingPolicies.getPolicies();
+        mNumCpuClusters = policies.length;
 
         mCpuActivePowerEstimator = new UsageBasedPowerEstimator(
                 profile.getAveragePower(PowerProfile.POWER_CPU_ACTIVE));
 
-        mPerClusterPowerEstimators = new UsageBasedPowerEstimator[mNumCpuClusters];
-        for (int cluster = 0; cluster < mNumCpuClusters; cluster++) {
-            mPerClusterPowerEstimators[cluster] = new UsageBasedPowerEstimator(
-                    profile.getAveragePowerForCpuCluster(cluster));
+        mPerClusterPowerEstimators = new UsageBasedPowerEstimator[policies.length];
+        for (int i = 0; i < policies.length; i++) {
+            mPerClusterPowerEstimators[i] = new UsageBasedPowerEstimator(
+                    profile.getAveragePowerForCpuScalingPolicy(policies[i]));
         }
 
-        int freqCount = 0;
-        for (int cluster = 0; cluster < mNumCpuClusters; cluster++) {
-            freqCount += profile.getNumSpeedStepsInCpuCluster(cluster);
-        }
-
+        mPerCpuFreqPowerEstimators =
+                new UsageBasedPowerEstimator[cpuScalingPolicies.getScalingStepCount()];
         mPerCpuFreqPowerEstimatorsByCluster = new UsageBasedPowerEstimator[mNumCpuClusters][];
-        mPerCpuFreqPowerEstimators = new UsageBasedPowerEstimator[freqCount];
         int index = 0;
-        for (int cluster = 0; cluster < mNumCpuClusters; cluster++) {
-            final int speedsForCluster = profile.getNumSpeedStepsInCpuCluster(cluster);
+        for (int cluster = 0; cluster < policies.length; cluster++) {
+            int policy = policies[cluster];
+            int[] freqs = cpuScalingPolicies.getFrequencies(policy);
             mPerCpuFreqPowerEstimatorsByCluster[cluster] =
-                    new UsageBasedPowerEstimator[speedsForCluster];
-            for (int speed = 0; speed < speedsForCluster; speed++) {
+                    new UsageBasedPowerEstimator[freqs.length];
+            for (int step = 0; step < freqs.length; step++) {
                 final UsageBasedPowerEstimator estimator = new UsageBasedPowerEstimator(
-                        profile.getAveragePowerForCpuCore(cluster, speed));
-                mPerCpuFreqPowerEstimatorsByCluster[cluster][speed] = estimator;
+                        profile.getAveragePowerForCpuScalingStep(policy, step));
+                mPerCpuFreqPowerEstimatorsByCluster[cluster][step] = estimator;
                 mPerCpuFreqPowerEstimators[index++] = estimator;
             }
         }
@@ -105,7 +107,7 @@
         BatteryConsumer.Key[] keys = UNINITIALIZED_KEYS;
         Result result = new Result();
         if (query.isProcessStateDataNeeded()) {
-            result.cpuFreqTimes = new long[batteryStats.getCpuFreqCount()];
+            result.cpuFreqTimes = new long[mCpuScalingPolicies.getScalingStepCount()];
         }
         final SparseArray<UidBatteryConsumer.Builder> uidBatteryConsumerBuilders =
                 builder.getUidBatteryConsumerBuilders();
@@ -333,7 +335,7 @@
      * @param durationsMs duration of CPU usage.
      * @return a double in milliamp-hours of estimated active CPU power consumption.
      */
-    public double calculateActiveCpuPowerMah(long durationsMs) {
+    private double calculateActiveCpuPowerMah(long durationsMs) {
         return mCpuActivePowerEstimator.calculatePower(durationsMs);
     }
 
diff --git a/services/core/java/com/android/server/power/stats/KernelWakelockReader.java b/services/core/java/com/android/server/power/stats/KernelWakelockReader.java
index 0377d8a..3fffcf7 100644
--- a/services/core/java/com/android/server/power/stats/KernelWakelockReader.java
+++ b/services/core/java/com/android/server/power/stats/KernelWakelockReader.java
@@ -42,30 +42,31 @@
     private static final String sWakeupSourceFile = "/d/wakeup_sources";
     private static final String sSysClassWakeupDir = "/sys/class/wakeup";
 
-    private static final int[] PROC_WAKELOCKS_FORMAT = new int[] {
-        Process.PROC_TAB_TERM|Process.PROC_OUT_STRING|                // 0: name
-                              Process.PROC_QUOTES,
-        Process.PROC_TAB_TERM|Process.PROC_OUT_LONG,                  // 1: count
-        Process.PROC_TAB_TERM,
-        Process.PROC_TAB_TERM,
-        Process.PROC_TAB_TERM,
-        Process.PROC_TAB_TERM|Process.PROC_OUT_LONG,                  // 5: totalTime
+    private static final int[] PROC_WAKELOCKS_FORMAT = new int[]{
+            Process.PROC_TAB_TERM | Process.PROC_OUT_STRING                 // 0: name
+                    | Process.PROC_QUOTES,
+            Process.PROC_TAB_TERM | Process.PROC_OUT_LONG,                  // 1: count
+            Process.PROC_TAB_TERM,
+            Process.PROC_TAB_TERM | Process.PROC_OUT_LONG,                  // 3: activeSince
+            Process.PROC_TAB_TERM,
+            Process.PROC_TAB_TERM | Process.PROC_OUT_LONG,                  // 5: totalTime
     };
 
-    private static final int[] WAKEUP_SOURCES_FORMAT = new int[] {
-        Process.PROC_TAB_TERM|Process.PROC_OUT_STRING,                // 0: name
-        Process.PROC_TAB_TERM|Process.PROC_COMBINE|
-                              Process.PROC_OUT_LONG,                  // 1: count
-        Process.PROC_TAB_TERM|Process.PROC_COMBINE,
-        Process.PROC_TAB_TERM|Process.PROC_COMBINE,
-        Process.PROC_TAB_TERM|Process.PROC_COMBINE,
-        Process.PROC_TAB_TERM|Process.PROC_COMBINE,
-        Process.PROC_TAB_TERM|Process.PROC_COMBINE
-                             |Process.PROC_OUT_LONG,                  // 6: totalTime
+    private static final int[] WAKEUP_SOURCES_FORMAT = new int[]{
+            Process.PROC_TAB_TERM | Process.PROC_OUT_STRING,                // 0: name
+            Process.PROC_TAB_TERM | Process.PROC_COMBINE
+                    | Process.PROC_OUT_LONG,                                // 1: count
+            Process.PROC_TAB_TERM | Process.PROC_COMBINE,
+            Process.PROC_TAB_TERM | Process.PROC_COMBINE,
+            Process.PROC_TAB_TERM | Process.PROC_COMBINE,
+            Process.PROC_TAB_TERM | Process.PROC_COMBINE
+                    | Process.PROC_OUT_LONG,                                // 5: activeSince
+            Process.PROC_TAB_TERM | Process.PROC_COMBINE
+                    | Process.PROC_OUT_LONG,                                // 6: totalTime
     };
 
     private final String[] mProcWakelocksName = new String[3];
-    private final long[] mProcWakelocksData = new long[3];
+    private final long[] mProcWakelocksData = new long[4];
     private ISuspendControlServiceInternal mSuspendControlService = null;
     private byte[] mKernelWakelockBuffer = new byte[32 * 1024];
 
@@ -74,7 +75,7 @@
      * @param staleStats Existing object to update.
      * @return the updated data.
      */
-    public final KernelWakelockStats readKernelWakelockStats(KernelWakelockStats staleStats) {
+    public KernelWakelockStats readKernelWakelockStats(KernelWakelockStats staleStats) {
         boolean useSystemSuspend = (new File(sSysClassWakeupDir)).exists();
 
         if (useSystemSuspend) {
@@ -180,14 +181,16 @@
      */
     private KernelWakelockStats getWakelockStatsFromSystemSuspend(
             final KernelWakelockStats staleStats) {
-        WakeLockInfo[] wlStats = null;
-        try {
-            mSuspendControlService = waitForSuspendControlService();
-        } catch (ServiceNotFoundException e) {
-            Slog.wtf(TAG, "Required service suspend_control not available", e);
-            return null;
+        if (mSuspendControlService == null) {
+            try {
+                mSuspendControlService = waitForSuspendControlService();
+            } catch (ServiceNotFoundException e) {
+                Slog.wtf(TAG, "Required service suspend_control not available", e);
+                return null;
+            }
         }
 
+        WakeLockInfo[] wlStats;
         try {
             wlStats = mSuspendControlService.getWakeLockStats();
             updateWakelockStats(wlStats, staleStats);
@@ -210,13 +213,16 @@
         for (WakeLockInfo info : wlStats) {
             if (!staleStats.containsKey(info.name)) {
                 staleStats.put(info.name, new KernelWakelockStats.Entry((int) info.activeCount,
-                        info.totalTime * 1000 /* ms to us */, sKernelWakelockUpdateVersion));
+                        info.totalTime * 1000 /* ms to us */,
+                        info.isActive ? info.activeTime * 1000 : 0,
+                        sKernelWakelockUpdateVersion));
             } else {
                 KernelWakelockStats.Entry kwlStats = staleStats.get(info.name);
-                kwlStats.mCount = (int) info.activeCount;
+                kwlStats.count = (int) info.activeCount;
                 // Convert milliseconds to microseconds
-                kwlStats.mTotalTime = info.totalTime * 1000;
-                kwlStats.mVersion = sKernelWakelockUpdateVersion;
+                kwlStats.totalTimeUs = info.totalTime * 1000;
+                kwlStats.activeTimeUs = info.isActive ? info.activeTime * 1000 : 0;
+                kwlStats.version = sKernelWakelockUpdateVersion;
             }
         }
 
@@ -232,6 +238,7 @@
         String name;
         int count;
         long totalTime;
+        long activeTime;
         int startIndex;
         int endIndex;
 
@@ -268,26 +275,30 @@
                 count = (int) wlData[1];
 
                 if (wakeup_sources) {
-                        // convert milliseconds to microseconds
-                        totalTime = wlData[2] * 1000;
+                    // convert milliseconds to microseconds
+                    activeTime = wlData[2] * 1000;
+                    totalTime = wlData[3] * 1000;
                 } else {
-                        // convert nanoseconds to microseconds with rounding.
-                        totalTime = (wlData[2] + 500) / 1000;
+                    // convert nanoseconds to microseconds with rounding.
+                    activeTime = (wlData[2] + 500) / 1000;
+                    totalTime = (wlData[3] + 500) / 1000;
                 }
 
                 if (parsed && name.length() > 0) {
                     if (!staleStats.containsKey(name)) {
                         staleStats.put(name, new KernelWakelockStats.Entry(count, totalTime,
-                                sKernelWakelockUpdateVersion));
+                                activeTime, sKernelWakelockUpdateVersion));
                     } else {
                         KernelWakelockStats.Entry kwlStats = staleStats.get(name);
-                        if (kwlStats.mVersion == sKernelWakelockUpdateVersion) {
-                            kwlStats.mCount += count;
-                            kwlStats.mTotalTime += totalTime;
+                        if (kwlStats.version == sKernelWakelockUpdateVersion) {
+                            kwlStats.count += count;
+                            kwlStats.totalTimeUs += totalTime;
+                            kwlStats.activeTimeUs = activeTime;
                         } else {
-                            kwlStats.mCount = count;
-                            kwlStats.mTotalTime = totalTime;
-                            kwlStats.mVersion = sKernelWakelockUpdateVersion;
+                            kwlStats.count = count;
+                            kwlStats.totalTimeUs = totalTime;
+                            kwlStats.activeTimeUs = activeTime;
+                            kwlStats.version = sKernelWakelockUpdateVersion;
                         }
                     }
                 } else if (!parsed) {
@@ -326,7 +337,7 @@
     public KernelWakelockStats removeOldStats(final KernelWakelockStats staleStats) {
         Iterator<KernelWakelockStats.Entry> itr = staleStats.values().iterator();
         while (itr.hasNext()) {
-            if (itr.next().mVersion != sKernelWakelockUpdateVersion) {
+            if (itr.next().version != sKernelWakelockUpdateVersion) {
                 itr.remove();
             }
         }
diff --git a/services/core/java/com/android/server/power/stats/KernelWakelockStats.java b/services/core/java/com/android/server/power/stats/KernelWakelockStats.java
index 502b7a0..3ed3803 100644
--- a/services/core/java/com/android/server/power/stats/KernelWakelockStats.java
+++ b/services/core/java/com/android/server/power/stats/KernelWakelockStats.java
@@ -22,14 +22,16 @@
  */
 public class KernelWakelockStats extends HashMap<String, KernelWakelockStats.Entry> {
     public static class Entry {
-        public int mCount;
-        public long mTotalTime;
-        public int mVersion;
+        public int count;
+        public long totalTimeUs;
+        public long activeTimeUs;
+        public int version;
 
-        Entry(int count, long totalTime, int version) {
-            mCount = count;
-            mTotalTime = totalTime;
-            mVersion = version;
+        Entry(int count, long totalTimeUs, long activeTimeUs, int version) {
+            this.count = count;
+            this.totalTimeUs = totalTimeUs;
+            this.activeTimeUs = activeTimeUs;
+            this.version = version;
         }
     }
 
diff --git a/services/core/java/com/android/server/power/stats/SystemServicePowerCalculator.java b/services/core/java/com/android/server/power/stats/SystemServicePowerCalculator.java
index aa17d4fb..1d5d93e 100644
--- a/services/core/java/com/android/server/power/stats/SystemServicePowerCalculator.java
+++ b/services/core/java/com/android/server/power/stats/SystemServicePowerCalculator.java
@@ -25,6 +25,7 @@
 import android.util.Log;
 import android.util.SparseArray;
 
+import com.android.internal.os.CpuScalingPolicies;
 import com.android.internal.os.PowerProfile;
 
 /**
@@ -39,23 +40,19 @@
     // to this layout:
     // {cluster1-speed1, cluster1-speed2, ..., cluster2-speed1, cluster2-speed2, ...}
     private final UsageBasedPowerEstimator[] mPowerEstimators;
-    private final com.android.server.power.stats.CpuPowerCalculator mCpuPowerCalculator;
+    private final CpuPowerCalculator mCpuPowerCalculator;
 
-    public SystemServicePowerCalculator(PowerProfile powerProfile) {
-        mCpuPowerCalculator = new CpuPowerCalculator(powerProfile);
-        int numFreqs = 0;
-        final int numCpuClusters = powerProfile.getNumCpuClusters();
-        for (int cluster = 0; cluster < numCpuClusters; cluster++) {
-            numFreqs += powerProfile.getNumSpeedStepsInCpuCluster(cluster);
-        }
-
-        mPowerEstimators = new UsageBasedPowerEstimator[numFreqs];
+    public SystemServicePowerCalculator(CpuScalingPolicies cpuScalingPolicies,
+            PowerProfile powerProfile) {
+        mCpuPowerCalculator = new CpuPowerCalculator(cpuScalingPolicies, powerProfile);
+        mPowerEstimators = new UsageBasedPowerEstimator[cpuScalingPolicies.getScalingStepCount()];
         int index = 0;
-        for (int cluster = 0; cluster < numCpuClusters; cluster++) {
-            final int numSpeeds = powerProfile.getNumSpeedStepsInCpuCluster(cluster);
+        int[] policies = cpuScalingPolicies.getPolicies();
+        for (int policy : policies) {
+            final int numSpeeds = cpuScalingPolicies.getFrequencies(policy).length;
             for (int speed = 0; speed < numSpeeds; speed++) {
                 mPowerEstimators[index++] = new UsageBasedPowerEstimator(
-                        powerProfile.getAveragePowerForCpuCore(cluster, speed));
+                        powerProfile.getAveragePowerForCpuScalingStep(policy, speed));
             }
         }
     }
diff --git a/services/core/java/com/android/server/power/stats/wakeups/CpuWakeupStats.java b/services/core/java/com/android/server/power/stats/wakeups/CpuWakeupStats.java
index 712a696..f047f56 100644
--- a/services/core/java/com/android/server/power/stats/wakeups/CpuWakeupStats.java
+++ b/services/core/java/com/android/server/power/stats/wakeups/CpuWakeupStats.java
@@ -32,12 +32,12 @@
 import android.os.UserHandle;
 import android.provider.DeviceConfig;
 import android.util.IndentingPrintWriter;
+import android.util.LongSparseArray;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
 import android.util.SparseIntArray;
 import android.util.SparseLongArray;
-import android.util.TimeSparseArray;
 import android.util.TimeUtils;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -74,12 +74,12 @@
     private final WakingActivityHistory mRecentWakingActivity;
 
     @VisibleForTesting
-    final TimeSparseArray<Wakeup> mWakeupEvents = new TimeSparseArray<>();
+    final LongSparseArray<Wakeup> mWakeupEvents = new LongSparseArray<>();
 
     /* Maps timestamp -> {subsystem  -> {uid -> procState}} */
     @VisibleForTesting
-    final TimeSparseArray<SparseArray<SparseIntArray>> mWakeupAttribution =
-            new TimeSparseArray<>();
+    final LongSparseArray<SparseArray<SparseIntArray>> mWakeupAttribution =
+            new LongSparseArray<>();
 
     final SparseIntArray mUidProcStates = new SparseIntArray();
     private final SparseIntArray mReusableUidProcStates = new SparseIntArray(4);
@@ -218,11 +218,11 @@
         // the last wakeup and its attribution (if computed) is always stored, even if that wakeup
         // had occurred before retentionDuration.
         final long retentionDuration = mConfig.WAKEUP_STATS_RETENTION_MS;
-        int lastIdx = mWakeupEvents.closestIndexOnOrBefore(elapsedRealtime - retentionDuration);
+        int lastIdx = mWakeupEvents.lastIndexOnOrBefore(elapsedRealtime - retentionDuration);
         for (int i = lastIdx; i >= 0; i--) {
             mWakeupEvents.removeAt(i);
         }
-        lastIdx = mWakeupAttribution.closestIndexOnOrBefore(elapsedRealtime - retentionDuration);
+        lastIdx = mWakeupAttribution.lastIndexOnOrBefore(elapsedRealtime - retentionDuration);
         for (int i = lastIdx; i >= 0; i--) {
             mWakeupAttribution.removeAt(i);
         }
@@ -273,9 +273,9 @@
             SparseIntArray uidProcStates) {
         final long matchingWindowMillis = mConfig.WAKEUP_MATCHING_WINDOW_MS;
 
-        final int startIdx = mWakeupEvents.closestIndexOnOrAfter(
+        final int startIdx = mWakeupEvents.firstIndexOnOrAfter(
                 activityElapsed - matchingWindowMillis);
-        final int endIdx = mWakeupEvents.closestIndexOnOrBefore(
+        final int endIdx = mWakeupEvents.lastIndexOnOrBefore(
                 activityElapsed + matchingWindowMillis);
 
         for (int wakeupIdx = startIdx; wakeupIdx <= endIdx; wakeupIdx++) {
@@ -404,7 +404,7 @@
     static final class WakingActivityHistory {
         private LongSupplier mRetentionSupplier;
         @VisibleForTesting
-        final SparseArray<TimeSparseArray<SparseIntArray>> mWakingActivity = new SparseArray<>();
+        final SparseArray<LongSparseArray<SparseIntArray>> mWakingActivity = new SparseArray<>();
 
         WakingActivityHistory(LongSupplier retentionSupplier) {
             mRetentionSupplier = retentionSupplier;
@@ -414,9 +414,9 @@
             if (uidProcStates == null) {
                 return;
             }
-            TimeSparseArray<SparseIntArray> wakingActivity = mWakingActivity.get(subsystem);
+            LongSparseArray<SparseIntArray> wakingActivity = mWakingActivity.get(subsystem);
             if (wakingActivity == null) {
-                wakingActivity = new TimeSparseArray<>();
+                wakingActivity = new LongSparseArray<>();
                 mWakingActivity.put(subsystem, wakingActivity);
             }
             final SparseIntArray uidsToBlame = wakingActivity.get(elapsedRealtime);
@@ -435,7 +435,7 @@
             // Limit activity history per subsystem to the last retention period as supplied by
             // mRetentionSupplier. Note that the last activity is always present, even if it
             // occurred before the retention period.
-            final int endIdx = wakingActivity.closestIndexOnOrBefore(
+            final int endIdx = wakingActivity.lastIndexOnOrBefore(
                     elapsedRealtime - mRetentionSupplier.getAsLong());
             for (int i = endIdx; i >= 0; i--) {
                 wakingActivity.removeAt(i);
@@ -445,11 +445,11 @@
         SparseIntArray removeBetween(int subsystem, long startElapsed, long endElapsed) {
             final SparseIntArray uidsToReturn = new SparseIntArray();
 
-            final TimeSparseArray<SparseIntArray> activityForSubsystem =
+            final LongSparseArray<SparseIntArray> activityForSubsystem =
                     mWakingActivity.get(subsystem);
             if (activityForSubsystem != null) {
-                final int startIdx = activityForSubsystem.closestIndexOnOrAfter(startElapsed);
-                final int endIdx = activityForSubsystem.closestIndexOnOrBefore(endElapsed);
+                final int startIdx = activityForSubsystem.firstIndexOnOrAfter(startElapsed);
+                final int endIdx = activityForSubsystem.lastIndexOnOrBefore(endElapsed);
                 for (int i = endIdx; i >= startIdx; i--) {
                     final SparseIntArray uidsForTime = activityForSubsystem.valueAt(i);
                     for (int j = 0; j < uidsForTime.size(); j++) {
@@ -463,8 +463,8 @@
                     activityForSubsystem.removeAt(i);
                 }
                 // Generally waking activity is a high frequency occurrence for any subsystem, so we
-                // don't delete the TimeSparseArray even if it is now empty, to avoid object churn.
-                // This will leave one TimeSparseArray per subsystem, which are few right now.
+                // don't delete the LongSparseArray even if it is now empty, to avoid object churn.
+                // This will leave one LongSparseArray per subsystem, which are few right now.
             }
             return uidsToReturn.size() > 0 ? uidsToReturn : null;
         }
@@ -474,7 +474,7 @@
             pw.increaseIndent();
             for (int i = 0; i < mWakingActivity.size(); i++) {
                 pw.println("Subsystem " + subsystemToString(mWakingActivity.keyAt(i)) + ":");
-                final TimeSparseArray<SparseIntArray> wakingActivity = mWakingActivity.valueAt(i);
+                final LongSparseArray<SparseIntArray> wakingActivity = mWakingActivity.valueAt(i);
                 if (wakingActivity == null) {
                     continue;
                 }
diff --git a/services/core/java/com/android/server/powerstats/IntervalRandomNoiseGenerator.java b/services/core/java/com/android/server/powerstats/IntervalRandomNoiseGenerator.java
new file mode 100644
index 0000000..444f885
--- /dev/null
+++ b/services/core/java/com/android/server/powerstats/IntervalRandomNoiseGenerator.java
@@ -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.server.powerstats;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import org.apache.commons.math.MathException;
+import org.apache.commons.math.distribution.AbstractContinuousDistribution;
+import org.apache.commons.math.distribution.BetaDistributionImpl;
+
+import java.util.Arrays;
+
+/**
+ * Adds random noise to provided value, keeping it within the limits of a specified range.
+ * @hide
+ */
+public class IntervalRandomNoiseGenerator {
+    private static final int DISTRIBUTION_SAMPLE_SIZE = 17;
+
+    private final AbstractContinuousDistribution mDistribution;
+    private final double[] mSamples = new double[DISTRIBUTION_SAMPLE_SIZE];
+
+    private static final double UNINITIALIZED = -1;
+
+    /**
+     * Higher alpha makes the distribution more asymmetrical, tightening it
+     * closer to the high bound.  A value of alpha should be &gt; 1 to ensure
+     * that the samples closer to 1 appear more frequently t those closer
+     * to 0.
+     */
+    IntervalRandomNoiseGenerator(double alpha) {
+        if (alpha <= 1) {
+            throw new IllegalArgumentException("alpha should be > 1");
+        }
+        mDistribution = new BetaDistributionImpl(alpha, 1 /* beta */);
+        refresh();
+    }
+
+    @VisibleForTesting
+    void reseed(long seed) {
+        mDistribution.reseedRandomGenerator(seed);
+    }
+
+    /**
+     * Returns a random value between the specified bounds, statistically closer to the
+     * highProbabilityBound.
+     *
+     * The same value is returned for a given stickyKey until {@link #refresh()} is called.
+     */
+    long addNoise(long lowProbabilityBound, long highProbabilityBound, int stickyKey) {
+        double sample = mSamples[stickyKey % DISTRIBUTION_SAMPLE_SIZE];
+        if (sample < 0) {   // UNINITIALIZED
+            try {
+                sample = mDistribution.sample();
+            } catch (MathException e) {
+                throw new IllegalStateException(e);
+            }
+            mSamples[stickyKey % DISTRIBUTION_SAMPLE_SIZE] = sample;
+        }
+        return lowProbabilityBound + (long) ((highProbabilityBound - lowProbabilityBound) * sample);
+    }
+
+    /**
+     * Resets the cache of random samples.
+     */
+    void refresh() {
+        Arrays.fill(mSamples, UNINITIALIZED);
+    }
+}
diff --git a/services/core/java/com/android/server/powerstats/PowerStatsService.java b/services/core/java/com/android/server/powerstats/PowerStatsService.java
index 2638f34..9832c49 100644
--- a/services/core/java/com/android/server/powerstats/PowerStatsService.java
+++ b/services/core/java/com/android/server/powerstats/PowerStatsService.java
@@ -16,27 +16,39 @@
 
 package com.android.server.powerstats;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
 import android.hardware.power.stats.Channel;
 import android.hardware.power.stats.EnergyConsumer;
 import android.hardware.power.stats.EnergyConsumerResult;
+import android.hardware.power.stats.EnergyConsumerType;
 import android.hardware.power.stats.EnergyMeasurement;
 import android.hardware.power.stats.PowerEntity;
 import android.hardware.power.stats.StateResidencyResult;
 import android.os.Binder;
+import android.os.Bundle;
 import android.os.Environment;
 import android.os.Handler;
+import android.os.HandlerExecutor;
 import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.IPowerStatsService;
 import android.os.Looper;
+import android.os.PowerMonitor;
+import android.os.PowerMonitorReadings;
+import android.os.ResultReceiver;
 import android.os.UserHandle;
 import android.power.PowerStatsInternal;
+import android.provider.DeviceConfig;
+import android.provider.DeviceConfigInterface;
+import android.util.Log;
 import android.util.Slog;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.Clock;
 import com.android.internal.util.DumpUtils;
-import com.android.internal.util.function.pooled.PooledLambda;
 import com.android.server.SystemService;
 import com.android.server.powerstats.PowerStatsHALWrapper.IPowerStatsHALWrapper;
 import com.android.server.powerstats.ProtoStreamUtils.ChannelUtils;
@@ -46,7 +58,11 @@
 import java.io.File;
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
 import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Executor;
 
 /**
  * This class provides a system service that estimates system power usage
@@ -65,8 +81,19 @@
     private static final String METER_CACHE_FILENAME = "meterCache";
     private static final String MODEL_CACHE_FILENAME = "modelCache";
     private static final String RESIDENCY_CACHE_FILENAME = "residencyCache";
+    private static final long MAX_POWER_MONITOR_AGE_MILLIS = 30_000;
+
+    static final String KEY_POWER_MONITOR_API_ENABLED = "power_monitor_api_enabled";
+
+    // The alpha parameter of the Beta distribution used by the random noise generator.
+    // The higher this value, the smaller the amount of added noise.
+    private static final double INTERVAL_RANDOM_NOISE_GENERATION_ALPHA = 50;
+    private static final long MAX_RANDOM_NOISE_UWS = 10_000_000;
 
     private final Injector mInjector;
+    private final Clock mClock;
+    private final DeviceConfigInterface mDeviceConfig;
+    private final DeviceConfigListener mDeviceConfigListener = new DeviceConfigListener();
     private File mDataStoragePath;
 
     private Context mContext;
@@ -83,15 +110,23 @@
     @Nullable
     @GuardedBy("this")
     private Looper mLooper;
+    private Handler mHandler;
     @Nullable
     @GuardedBy("this")
     private EnergyConsumer[] mEnergyConsumers = null;
+    @Nullable
+    @GuardedBy("this")
+    private Channel[] mEnergyMeters = null;
 
     @VisibleForTesting
     static class Injector {
         @GuardedBy("this")
         private IPowerStatsHALWrapper mPowerStatsHALWrapper;
 
+        Clock getClock() {
+            return Clock.SYSTEM_CLOCK;
+        }
+
         File createDataStoragePath() {
             return new File(Environment.getDataSystemDeDirectory(UserHandle.USER_SYSTEM),
                 DATA_STORAGE_SUBDIR);
@@ -158,9 +193,30 @@
                 PowerStatsInternal powerStatsInternal) {
             return new StatsPullAtomCallbackImpl(context, powerStatsInternal);
         }
+
+        DeviceConfigInterface getDeviceConfig() {
+            return DeviceConfigInterface.REAL;
+        }
+
+        IntervalRandomNoiseGenerator createIntervalRandomNoiseGenerator() {
+            return new IntervalRandomNoiseGenerator(INTERVAL_RANDOM_NOISE_GENERATION_ALPHA);
+        }
     }
 
-    private final class BinderService extends Binder {
+    private final IBinder mService = new IPowerStatsService.Stub() {
+
+        @Override
+        public void getSupportedPowerMonitors(ResultReceiver resultReceiver) {
+            getHandler().post(() -> getSupportedPowerMonitorsImpl(resultReceiver));
+        }
+
+        @Override
+        public void getPowerMonitorReadings(int[] powerMonitorIds, ResultReceiver resultReceiver) {
+            int callingUid = Binder.getCallingUid();
+            getHandler().post(() ->
+                    getPowerMonitorReadingsImpl(powerMonitorIds, resultReceiver, callingUid));
+        }
+
         @Override
         protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
             if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
@@ -191,6 +247,20 @@
                 }
             }
         }
+    };
+
+    private class DeviceConfigListener implements DeviceConfig.OnPropertiesChangedListener {
+        public Executor mExecutor = new HandlerExecutor(getHandler());
+
+        void startListening() {
+            mDeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_BATTERY_STATS,
+                    mExecutor, this);
+        }
+
+        @Override
+        public void onPropertiesChanged(DeviceConfig.Properties properties) {
+            refreshFlags();
+        }
     }
 
     @Override
@@ -208,11 +278,13 @@
             mPowerStatsInternal = new LocalService();
             publishLocalService(PowerStatsInternal.class, mPowerStatsInternal);
         }
-        publishBinderService(Context.POWER_STATS_SERVICE, new BinderService());
+        publishBinderService(Context.POWER_STATS_SERVICE, mService);
     }
 
     private void onSystemServicesReady() {
         mPullAtomCallback = mInjector.createStatsPullerImpl(mContext, mPowerStatsInternal);
+        mDeviceConfigListener.startListening();
+        refreshFlags();
     }
 
     @VisibleForTesting
@@ -263,6 +335,15 @@
         }
     }
 
+    private Handler getHandler() {
+        synchronized (this) {
+            if (mHandler == null) {
+                mHandler = new Handler(getLooper());
+            }
+            return mHandler;
+        }
+    }
+
     private EnergyConsumer[] getEnergyConsumerInfo() {
         synchronized (this) {
             if (mEnergyConsumers == null) {
@@ -272,6 +353,15 @@
         }
     }
 
+    private Channel[] getEnergyMeterInfo() {
+        synchronized (this) {
+            if (mEnergyMeters == null) {
+                mEnergyMeters = getPowerStatsHal().getEnergyMeterInfo();
+            }
+            return mEnergyMeters;
+        }
+    }
+
     public PowerStatsService(Context context) {
         this(context, new Injector());
     }
@@ -281,15 +371,16 @@
         super(context);
         mContext = context;
         mInjector = injector;
+        mClock = injector.getClock();
+        mDeviceConfig = injector.getDeviceConfig();
+    }
+
+    void refreshFlags() {
+        setPowerMonitorApiEnabled(mDeviceConfig.getBoolean(DeviceConfig.NAMESPACE_BATTERY_STATS,
+                KEY_POWER_MONITOR_API_ENABLED, true));
     }
 
     private final class LocalService extends PowerStatsInternal {
-        private final Handler mHandler;
-
-        LocalService() {
-            mHandler = new Handler(getLooper());
-        }
-
 
         @Override
         public EnergyConsumer[] getEnergyConsumerInfo() {
@@ -300,9 +391,8 @@
         public CompletableFuture<EnergyConsumerResult[]> getEnergyConsumedAsync(
                 int[] energyConsumerIds) {
             final CompletableFuture<EnergyConsumerResult[]> future = new CompletableFuture<>();
-            mHandler.sendMessage(
-                    PooledLambda.obtainMessage(PowerStatsService.this::getEnergyConsumedAsync,
-                            future, energyConsumerIds));
+            getHandler().post(
+                    () -> PowerStatsService.this.getEnergyConsumedAsync(future, energyConsumerIds));
             return future;
         }
 
@@ -315,9 +405,8 @@
         public CompletableFuture<StateResidencyResult[]> getStateResidencyAsync(
                 int[] powerEntityIds) {
             final CompletableFuture<StateResidencyResult[]> future = new CompletableFuture<>();
-            mHandler.sendMessage(
-                    PooledLambda.obtainMessage(PowerStatsService.this::getStateResidencyAsync,
-                            future, powerEntityIds));
+            getHandler().post(
+                    () -> PowerStatsService.this.getStateResidencyAsync(future, powerEntityIds));
             return future;
         }
 
@@ -330,9 +419,8 @@
         public CompletableFuture<EnergyMeasurement[]> readEnergyMeterAsync(
                 int[] channelIds) {
             final CompletableFuture<EnergyMeasurement[]> future = new CompletableFuture<>();
-            mHandler.sendMessage(
-                    PooledLambda.obtainMessage(PowerStatsService.this::readEnergyMeterAsync,
-                            future, channelIds));
+            getHandler().post(
+                    () -> PowerStatsService.this.readEnergyMeterAsync(future, channelIds));
             return future;
         }
     }
@@ -413,4 +501,280 @@
             int[] channelIds) {
         future.complete(getPowerStatsHal().readEnergyMeter(channelIds));
     }
+
+    private static class PowerMonitorState {
+        public final PowerMonitor powerMonitor;
+        public final int id;
+        public long timestampMs;
+        public long energyUws = PowerMonitorReadings.ENERGY_UNAVAILABLE;
+        public long prevEnergyUws;
+
+        private PowerMonitorState(PowerMonitor powerMonitor, int id) {
+            this.powerMonitor = powerMonitor;
+            this.id = id;
+        }
+    }
+
+    private boolean mPowerMonitorApiEnabled = true;
+    private volatile PowerMonitor[] mPowerMonitors;
+    private PowerMonitorState[] mPowerMonitorStates;
+    private IntervalRandomNoiseGenerator mIntervalRandomNoiseGenerator;
+
+    private void setPowerMonitorApiEnabled(boolean powerMonitorApiEnabled) {
+        if (powerMonitorApiEnabled != mPowerMonitorApiEnabled) {
+            mPowerMonitorApiEnabled = powerMonitorApiEnabled;
+            mPowerMonitors = null;
+            mPowerMonitorStates = null;
+        }
+    }
+
+    private void ensurePowerMonitors() {
+        if (mPowerMonitors != null) {
+            return;
+        }
+
+        synchronized (this) {
+            if (mPowerMonitors != null) {
+                return;
+            }
+
+            if (mIntervalRandomNoiseGenerator == null) {
+                mIntervalRandomNoiseGenerator = mInjector.createIntervalRandomNoiseGenerator();
+            }
+
+            if (!mPowerMonitorApiEnabled) {
+                mPowerMonitors = new PowerMonitor[0];
+                mPowerMonitorStates = new PowerMonitorState[0];
+                return;
+            }
+
+            List<PowerMonitor> monitors = new ArrayList<>();
+            List<PowerMonitorState> states = new ArrayList<>();
+
+            int index = 0;
+
+            Channel[] channels = getEnergyMeterInfo();
+            for (Channel channel : channels) {
+                PowerMonitor monitor = new PowerMonitor(index++,
+                        PowerMonitor.POWER_MONITOR_TYPE_MEASUREMENT,
+                        getChannelName(channel));
+                monitors.add(monitor);
+                states.add(new PowerMonitorState(monitor, channel.id));
+            }
+
+            EnergyConsumer[] energyConsumers = getEnergyConsumerInfo();
+            for (EnergyConsumer consumer : energyConsumers) {
+                PowerMonitor monitor = new PowerMonitor(index++,
+                        PowerMonitor.POWER_MONITOR_TYPE_CONSUMER,
+                        getEnergyConsumerName(consumer, energyConsumers));
+                monitors.add(monitor);
+                states.add(new PowerMonitorState(monitor, consumer.id));
+            }
+
+            mPowerMonitors = monitors.toArray(new PowerMonitor[monitors.size()]);
+            mPowerMonitorStates = states.toArray(new PowerMonitorState[monitors.size()]);
+        }
+    }
+
+    @NonNull
+    private String getChannelName(Channel c) {
+        StringBuilder sb = new StringBuilder();
+        sb.append('[').append(c.name).append("]:");
+        if (c.subsystem != null) {
+            sb.append(c.subsystem);
+        }
+        return sb.toString();
+    }
+
+    @NonNull
+    private String getEnergyConsumerName(EnergyConsumer consumer,
+            EnergyConsumer[] energyConsumers) {
+        if (consumer.type != EnergyConsumerType.OTHER) {
+            StringBuilder sb = new StringBuilder();
+            sb.append(energyConsumerTypeToString(consumer.type));
+            boolean hasOrdinal = consumer.ordinal != 0;
+            if (!hasOrdinal) {
+                // See if any other EnergyConsumer of the same type has an ordinal
+                for (EnergyConsumer aConsumer : energyConsumers) {
+                    if (aConsumer.type == consumer.type && aConsumer.ordinal != 0) {
+                        hasOrdinal = true;
+                        break;
+                    }
+                }
+            }
+            if (hasOrdinal) {
+                sb.append('/').append(consumer.ordinal);
+            }
+            return sb.toString();
+        } else {
+            return consumer.name;
+        }
+    }
+
+    private static String energyConsumerTypeToString(int type) {
+        switch(type) {
+            case EnergyConsumerType.BLUETOOTH: return "BLUETOOTH";
+            case EnergyConsumerType.CPU_CLUSTER: return "CPU";
+            case EnergyConsumerType.DISPLAY: return "DISPLAY";
+            case EnergyConsumerType.GNSS: return "GNSS";
+            case EnergyConsumerType.MOBILE_RADIO: return "MOBILE_RADIO";
+            case EnergyConsumerType.WIFI: return "WIFI";
+            case EnergyConsumerType.OTHER: return "";
+            default:
+                throw new IllegalStateException("Unrecognized EnergyConsumerType: " + type);
+        }
+    }
+
+    /**
+     * Returns names of supported power monitors, including Channels and EnergyConsumers.
+     */
+    @VisibleForTesting
+    public void getSupportedPowerMonitorsImpl(ResultReceiver resultReceiver) {
+        ensurePowerMonitors();
+        Bundle result = new Bundle();
+        result.putParcelableArray(IPowerStatsService.KEY_MONITORS, mPowerMonitors);
+        resultReceiver.send(IPowerStatsService.RESULT_SUCCESS, result);
+    }
+
+    /**
+     * Returns the latest readings for the specified power monitors.
+     */
+    @VisibleForTesting
+    public void getPowerMonitorReadingsImpl(@NonNull int[] powerMonitorIndices,
+            ResultReceiver resultReceiver, int callingUid) {
+        ensurePowerMonitors();
+
+        long earliestTimestamp = Long.MAX_VALUE;
+        PowerMonitorState[] powerMonitorStates = new PowerMonitorState[powerMonitorIndices.length];
+        for (int i = 0; i < powerMonitorIndices.length; i++) {
+            int index = powerMonitorIndices[i];
+            if (index < 0 || index >= mPowerMonitorStates.length) {
+                resultReceiver.send(IPowerStatsService.RESULT_UNSUPPORTED_POWER_MONITOR, null);
+                return;
+            }
+
+            powerMonitorStates[i] = mPowerMonitorStates[index];
+            if (mPowerMonitorStates[index] != null
+                    && mPowerMonitorStates[index].timestampMs < earliestTimestamp) {
+                earliestTimestamp = mPowerMonitorStates[index].timestampMs;
+            }
+        }
+
+        if (earliestTimestamp == 0
+                || mClock.elapsedRealtime() - earliestTimestamp > MAX_POWER_MONITOR_AGE_MILLIS) {
+            updateEnergyConsumers(powerMonitorStates);
+            updateEnergyMeasurements(powerMonitorStates);
+            mIntervalRandomNoiseGenerator.refresh();
+        }
+
+        long[] energy = new long[powerMonitorStates.length];
+        long[] timestamps = new long[powerMonitorStates.length];
+        for (int i = 0; i < powerMonitorStates.length; i++) {
+            PowerMonitorState state = powerMonitorStates[i];
+            if (state.energyUws != PowerMonitorReadings.ENERGY_UNAVAILABLE
+                    && state.prevEnergyUws != PowerMonitorReadings.ENERGY_UNAVAILABLE) {
+                energy[i] = mIntervalRandomNoiseGenerator.addNoise(
+                        Math.max(state.prevEnergyUws, state.energyUws - MAX_RANDOM_NOISE_UWS),
+                        state.energyUws, callingUid);
+                if (DEBUG) {
+                    Log.d(TAG, String.format(Locale.ENGLISH,
+                            "Monitor=%s timestamp=%d energy=%d"
+                                    + " uid=%d noise=%.1f%% returned=%d",
+                            state.powerMonitor.name,
+                            state.timestampMs,
+                            state.energyUws,
+                            callingUid,
+                            state.energyUws != state.prevEnergyUws
+                                    ? (state.energyUws - energy[i]) * 100.0
+                                            / (state.energyUws - state.prevEnergyUws)
+                                    : 0,
+                            energy[i]));
+                }
+            } else {
+                energy[i] = state.energyUws;
+            }
+            timestamps[i] = state.timestampMs;
+        }
+
+        Bundle result = new Bundle();
+        result.putLongArray(IPowerStatsService.KEY_ENERGY, energy);
+        result.putLongArray(IPowerStatsService.KEY_TIMESTAMPS, timestamps);
+        resultReceiver.send(IPowerStatsService.RESULT_SUCCESS, result);
+    }
+
+    private void updateEnergyConsumers(PowerMonitorState[] powerMonitorStates) {
+        int[] ids = collectIds(powerMonitorStates, PowerMonitor.POWER_MONITOR_TYPE_CONSUMER);
+        if (ids == null) {
+            return;
+        }
+
+        EnergyConsumerResult[] energyConsumerResults = getPowerStatsHal().getEnergyConsumed(ids);
+        if (energyConsumerResults == null) {
+            return;
+        }
+
+        for (PowerMonitorState powerMonitorState : powerMonitorStates) {
+            if (powerMonitorState.powerMonitor.type
+                    == PowerMonitor.POWER_MONITOR_TYPE_CONSUMER) {
+                for (EnergyConsumerResult energyConsumerResult : energyConsumerResults) {
+                    if (energyConsumerResult.id == powerMonitorState.id) {
+                        powerMonitorState.prevEnergyUws = powerMonitorState.energyUws;
+                        powerMonitorState.energyUws = energyConsumerResult.energyUWs;
+                        powerMonitorState.timestampMs = energyConsumerResult.timestampMs;
+                        break;
+                    }
+                }
+            }
+        }
+    }
+
+    private void updateEnergyMeasurements(PowerMonitorState[] powerMonitorStates) {
+        int[] ids = collectIds(powerMonitorStates, PowerMonitor.POWER_MONITOR_TYPE_MEASUREMENT);
+        if (ids == null) {
+            return;
+        }
+
+        EnergyMeasurement[] energyMeasurements = getPowerStatsHal().readEnergyMeter(ids);
+        if (energyMeasurements == null) {
+            return;
+        }
+
+        for (PowerMonitorState powerMonitorState : powerMonitorStates) {
+            if (powerMonitorState.powerMonitor.type
+                    == PowerMonitor.POWER_MONITOR_TYPE_MEASUREMENT) {
+                for (EnergyMeasurement energyMeasurement : energyMeasurements) {
+                    if (energyMeasurement.id == powerMonitorState.id) {
+                        powerMonitorState.prevEnergyUws = powerMonitorState.energyUws;
+                        powerMonitorState.energyUws = energyMeasurement.energyUWs;
+                        powerMonitorState.timestampMs = energyMeasurement.timestampMs;
+                        break;
+                    }
+                }
+            }
+        }
+    }
+
+    @Nullable
+    private int[] collectIds(PowerMonitorState[] powerMonitorStates,
+            @PowerMonitor.PowerMonitorType int type) {
+        int count = 0;
+        for (PowerMonitorState monitorState : powerMonitorStates) {
+            if (monitorState.powerMonitor.type == type) {
+                count++;
+            }
+        }
+
+        if (count == 0) {
+            return null;
+        }
+
+        int[] ids = new int[count];
+        int index = 0;
+        for (PowerMonitorState monitorState : powerMonitorStates) {
+            if (monitorState.powerMonitor.type == type) {
+                ids[index++] = monitorState.id;
+            }
+        }
+        return ids;
+    }
 }
diff --git a/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java b/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java
index 9d5173a..375ef61 100644
--- a/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java
+++ b/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java
@@ -52,7 +52,6 @@
 import android.os.ShellCallback;
 import android.os.SystemProperties;
 import android.provider.DeviceConfig;
-import android.sysprop.ApexProperties;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.FastImmutableArraySet;
@@ -906,10 +905,11 @@
         return RESUME_ON_REBOOT_REBOOT_ERROR_UNSPECIFIED;
     }
 
+    @android.annotation.EnforcePermission(android.Manifest.permission.RECOVERY)
     @Override // Binder call for the legacy rebootWithLskf
     public @ResumeOnRebootRebootErrorCode int rebootWithLskfAssumeSlotSwitch(String packageName,
             String reason) {
-        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.RECOVERY, null);
+        rebootWithLskfAssumeSlotSwitch_enforcePermission();
         return rebootWithLskfImpl(packageName, reason, true);
     }
 
@@ -920,10 +920,6 @@
         return rebootWithLskfImpl(packageName, reason, slotSwitch);
     }
 
-    public static boolean isUpdatableApexSupported() {
-        return ApexProperties.updatable().orElse(false);
-    }
-
     // Metadata should be no more than few MB, if it's larger than 100MB something is wrong.
     private static final long APEX_INFO_SIZE_LIMIT = 24 * 1024 * 100;
 
@@ -970,14 +966,10 @@
         }
     }
 
+    @android.annotation.EnforcePermission(android.Manifest.permission.RECOVERY)
     @Override
     public boolean allocateSpaceForUpdate(String packageFile) {
-        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.RECOVERY, null);
-        if (!isUpdatableApexSupported()) {
-            Log.i(TAG, "Updatable Apex not supported, "
-                    + "allocateSpaceForUpdate does nothing.");
-            return true;
-        }
+        allocateSpaceForUpdate_enforcePermission();
         final long token = Binder.clearCallingIdentity();
         try {
             CompressedApexInfoList apexInfoList = getCompressedApexInfoList(packageFile);
diff --git a/services/core/java/com/android/server/security/FileIntegrity.java b/services/core/java/com/android/server/security/FileIntegrity.java
index 7b87d99..b8f187e 100644
--- a/services/core/java/com/android/server/security/FileIntegrity.java
+++ b/services/core/java/com/android/server/security/FileIntegrity.java
@@ -16,6 +16,8 @@
 
 package com.android.server.security;
 
+import static android.os.ParcelFileDescriptor.MODE_READ_ONLY;
+
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.os.ParcelFileDescriptor;
@@ -36,18 +38,26 @@
     private FileIntegrity() {}
 
     /**
-     * Enables fs-verity, if supported by the filesystem.
+     * Enables fs-verity, if supported by the filesystem. This operation is atomic, i.e. it's either
+     * enabled or not, even in case of power failure during or after the call.
      * @see <a href="https://www.kernel.org/doc/html/latest/filesystems/fsverity.html">
+     *
      * @hide
      */
     @SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
     public static void setUpFsVerity(@NonNull File file) throws IOException {
-        VerityUtils.setUpFsverity(file.getAbsolutePath());
+        try (ParcelFileDescriptor pfd = ParcelFileDescriptor.open(file, MODE_READ_ONLY)) {
+            setUpFsVerity(pfd);
+        }
     }
 
     /**
-     * Enables fs-verity, if supported by the filesystem.
+     * Enables fs-verity, if supported by the filesystem. This operation is atomic, i.e. it's either
+     * enabled or not, even in case of power failure during or after the call.
      * @see <a href="https://www.kernel.org/doc/html/latest/filesystems/fsverity.html">
+     *
+     * @param parcelFileDescriptor an FD opened in {@link ParcelFileDescriptor#MODE_READ_ONLY}.
+     *
      * @hide
      */
     @SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
diff --git a/services/core/java/com/android/server/security/FileIntegrityService.java b/services/core/java/com/android/server/security/FileIntegrityService.java
index 5ae6973..9529621 100644
--- a/services/core/java/com/android/server/security/FileIntegrityService.java
+++ b/services/core/java/com/android/server/security/FileIntegrityService.java
@@ -184,13 +184,7 @@
     }
 
     private void loadAllCertificates() {
-        // A better alternative to load certificates would be to read from .fs-verity kernel
-        // keyring, which fsverity_init loads to during earlier boot time from the same sources
-        // below. But since the read operation from keyring is not provided in kernel, we need to
-        // duplicate the same loading logic here.
-
         // Load certificates trusted by the device manufacturer.
-        // NB: Directories need to be synced with system/security/fsverity_init/fsverity_init.cpp.
         final String relativeDir = "etc/security/fsverity";
         loadCertificatesFromDirectory(Environment.getRootDirectory().toPath()
                 .resolve(relativeDir));
diff --git a/services/core/java/com/android/server/security/rkp/RemoteProvisioningShellCommand.java b/services/core/java/com/android/server/security/rkp/RemoteProvisioningShellCommand.java
index 71eca69..bc39084 100644
--- a/services/core/java/com/android/server/security/rkp/RemoteProvisioningShellCommand.java
+++ b/services/core/java/com/android/server/security/rkp/RemoteProvisioningShellCommand.java
@@ -134,7 +134,9 @@
             pw.println("supportedEekCurve=" + info.supportedEekCurve);
         }
         pw.println("uniqueId=" + info.uniqueId);
-        pw.println("supportedNumKeysInCsr=" + info.supportedNumKeysInCsr);
+        if (info.versionNumber >= 3) {
+            pw.println("supportedNumKeysInCsr=" + info.supportedNumKeysInCsr);
+        }
     }
 
     private int list() throws RemoteException {
@@ -153,7 +155,7 @@
                     challenge = Base64.getDecoder().decode(getNextArgRequired());
                     break;
                 default:
-                    getErrPrintWriter().println("error: unknown option");
+                    getErrPrintWriter().println("error: unknown option " + opt);
                     return ERROR;
             }
         }
diff --git a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
index ecd5bd2..76126df 100644
--- a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
+++ b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
@@ -1487,6 +1487,7 @@
         @Override
         public void binderDied() {
             mSensorPrivacyServiceImpl.removeSensorPrivacyListener(mListener);
+            mSensorPrivacyServiceImpl.removeToggleSensorPrivacyListener(mListener);
         }
 
         public void destroy() {
diff --git a/services/core/java/com/android/server/servicewatcher/CurrentUserServiceSupplier.java b/services/core/java/com/android/server/servicewatcher/CurrentUserServiceSupplier.java
index 00bedac..6677e7e 100644
--- a/services/core/java/com/android/server/servicewatcher/CurrentUserServiceSupplier.java
+++ b/services/core/java/com/android/server/servicewatcher/CurrentUserServiceSupplier.java
@@ -37,8 +37,8 @@
 import android.content.pm.ServiceInfo;
 import android.content.res.Resources;
 import android.os.Bundle;
+import android.os.Process;
 import android.os.UserHandle;
-import android.permission.PermissionManager;
 import android.util.Log;
 
 import com.android.internal.util.Preconditions;
@@ -294,8 +294,8 @@
             BoundServiceInfo serviceInfo = new BoundServiceInfo(mIntent.getAction(), resolveInfo);
 
             if (mServicePermission != null) {
-                if (PermissionManager.checkPackageNamePermission(mServicePermission,
-                        service.packageName, serviceInfo.getUserId()) != PERMISSION_GRANTED) {
+                if (mContext.checkPermission(mServicePermission, Process.INVALID_PID,
+                        serviceInfo.mUid) != PERMISSION_GRANTED) {
                     Log.d(TAG, serviceInfo.getComponentName().flattenToShortString()
                             + " disqualified due to not holding " + mCallerPermission);
                     continue;
diff --git a/services/core/java/com/android/server/servicewatcher/ServiceWatcherImpl.java b/services/core/java/com/android/server/servicewatcher/ServiceWatcherImpl.java
index ac97038..b178269 100644
--- a/services/core/java/com/android/server/servicewatcher/ServiceWatcherImpl.java
+++ b/services/core/java/com/android/server/servicewatcher/ServiceWatcherImpl.java
@@ -98,7 +98,7 @@
         Preconditions.checkState(!mRegistered);
 
         mRegistered = true;
-        mPackageMonitor.register(mContext, UserHandle.ALL, /*externalStorage=*/ true, mHandler);
+        mPackageMonitor.register(mContext, UserHandle.ALL, mHandler);
         mServiceSupplier.register(this);
 
         onServiceChanged(false);
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 9128974..5d04e5d 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -1734,7 +1734,7 @@
             String name = ent.getKey();
             KernelWakelockStats.Entry kws = ent.getValue();
             pulledData.add(FrameworkStatsLog.buildStatsEvent(
-                    atomTag, name, kws.mCount, kws.mVersion, kws.mTotalTime));
+                    atomTag, name, kws.count, kws.version, kws.totalTimeUs));
         }
         return StatsManager.PULL_SUCCESS;
     }
@@ -3098,6 +3098,7 @@
     }
 
     // read high watermark for section
+    @GuardedBy("mProcStatsLock")
     private long readProcStatsHighWaterMark(int atomTag) {
         try {
             File[] files =
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 044d30b..cc849b6 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -346,9 +346,10 @@
 
         @Override
         public void showScreenPinningRequest(int taskId) {
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.showScreenPinningRequest(taskId);
+                    bar.showScreenPinningRequest(taskId);
                 } catch (RemoteException e) {
                 }
             }
@@ -356,9 +357,10 @@
 
         @Override
         public void showAssistDisclosure() {
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.showAssistDisclosure();
+                    bar.showAssistDisclosure();
                 } catch (RemoteException e) {
                 }
             }
@@ -366,9 +368,10 @@
 
         @Override
         public void startAssist(Bundle args) {
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.startAssist(args);
+                    bar.startAssist(args);
                 } catch (RemoteException e) {
                 }
             }
@@ -376,9 +379,10 @@
 
         @Override
         public void onCameraLaunchGestureDetected(int source) {
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.onCameraLaunchGestureDetected(source);
+                    bar.onCameraLaunchGestureDetected(source);
                 } catch (RemoteException e) {
                 }
             }
@@ -392,9 +396,10 @@
         @Override
         public void onEmergencyActionLaunchGestureDetected() {
             if (SPEW) Slog.d(TAG, "Launching emergency action");
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.onEmergencyActionLaunchGestureDetected();
+                    bar.onEmergencyActionLaunchGestureDetected();
                 } catch (RemoteException e) {
                     if (SPEW) Slog.d(TAG, "Failed to launch emergency action");
                 }
@@ -409,9 +414,10 @@
         @Override
         public void toggleSplitScreen() {
             enforceStatusBarService();
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.toggleSplitScreen();
+                    bar.toggleSplitScreen();
                 } catch (RemoteException ex) {}
             }
         }
@@ -419,27 +425,30 @@
         @Override
         public void appTransitionFinished(int displayId) {
             enforceStatusBarService();
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.appTransitionFinished(displayId);
+                    bar.appTransitionFinished(displayId);
                 } catch (RemoteException ex) {}
             }
         }
 
         @Override
         public void toggleTaskbar() {
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.toggleTaskbar();
+                    bar.toggleTaskbar();
                 } catch (RemoteException ex) {}
             }
         }
 
         @Override
         public void toggleRecentApps() {
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.toggleRecentApps();
+                    bar.toggleRecentApps();
                 } catch (RemoteException ex) {}
             }
         }
@@ -453,45 +462,50 @@
 
         @Override
         public void preloadRecentApps() {
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.preloadRecentApps();
+                    bar.preloadRecentApps();
                 } catch (RemoteException ex) {}
             }
         }
 
         @Override
         public void cancelPreloadRecentApps() {
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.cancelPreloadRecentApps();
+                    bar.cancelPreloadRecentApps();
                 } catch (RemoteException ex) {}
             }
         }
 
         @Override
         public void showRecentApps(boolean triggeredFromAltTab) {
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.showRecentApps(triggeredFromAltTab);
+                    bar.showRecentApps(triggeredFromAltTab);
                 } catch (RemoteException ex) {}
             }
         }
 
         @Override
         public void hideRecentApps(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.hideRecentApps(triggeredFromAltTab, triggeredFromHomeKey);
+                    bar.hideRecentApps(triggeredFromAltTab, triggeredFromHomeKey);
                 } catch (RemoteException ex) {}
             }
         }
 
         @Override
         public void collapsePanels() {
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.animateCollapsePanels();
+                    bar.animateCollapsePanels();
                 } catch (RemoteException ex) {
                 }
             }
@@ -499,18 +513,20 @@
 
         @Override
         public void dismissKeyboardShortcutsMenu() {
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.dismissKeyboardShortcutsMenu();
+                    bar.dismissKeyboardShortcutsMenu();
                 } catch (RemoteException ex) {}
             }
         }
 
         @Override
         public void toggleKeyboardShortcutsMenu(int deviceId) {
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.toggleKeyboardShortcutsMenu(deviceId);
+                    bar.toggleKeyboardShortcutsMenu(deviceId);
                 } catch (RemoteException ex) {}
             }
         }
@@ -536,9 +552,10 @@
 
         @Override
         public void showChargingAnimation(int batteryLevel) {
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.showWirelessChargingAnimation(batteryLevel);
+                    bar.showWirelessChargingAnimation(batteryLevel);
                 } catch (RemoteException ex){
                 }
             }
@@ -546,7 +563,8 @@
 
         @Override
         public void showPictureInPictureMenu() {
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
                     mBar.showPictureInPictureMenu();
                 } catch (RemoteException ex) {}
@@ -555,27 +573,30 @@
 
         @Override
         public void setWindowState(int displayId, int window, int state) {
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.setWindowState(displayId, window, state);
+                    bar.setWindowState(displayId, window, state);
                 } catch (RemoteException ex) {}
             }
         }
 
         @Override
         public void appTransitionPending(int displayId) {
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.appTransitionPending(displayId);
+                    bar.appTransitionPending(displayId);
                 } catch (RemoteException ex) {}
             }
         }
 
         @Override
         public void appTransitionCancelled(int displayId) {
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.appTransitionCancelled(displayId);
+                    bar.appTransitionCancelled(displayId);
                 } catch (RemoteException ex) {}
             }
         }
@@ -583,9 +604,10 @@
         @Override
         public void appTransitionStarting(int displayId, long statusBarAnimationsStartTime,
                 long statusBarAnimationsDuration) {
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.appTransitionStarting(
+                    bar.appTransitionStarting(
                             displayId, statusBarAnimationsStartTime, statusBarAnimationsDuration);
                 } catch (RemoteException ex) {}
             }
@@ -593,9 +615,10 @@
 
         @Override
         public void setTopAppHidesStatusBar(boolean hidesStatusBar) {
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.setTopAppHidesStatusBar(hidesStatusBar);
+                    bar.setTopAppHidesStatusBar(hidesStatusBar);
                 } catch (RemoteException ex) {}
             }
         }
@@ -605,9 +628,10 @@
             if (!mContext.getResources().getBoolean(R.bool.config_showSysuiShutdown)) {
                 return false;
             }
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.showShutdownUi(isReboot, reason);
+                    bar.showShutdownUi(isReboot, reason);
                     return true;
                 } catch (RemoteException ex) {}
             }
@@ -626,18 +650,20 @@
 
         @Override
         public void onDisplayReady(int displayId) {
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.onDisplayReady(displayId);
+                    bar.onDisplayReady(displayId);
                 } catch (RemoteException ex) {}
             }
         }
 
         @Override
         public void onRecentsAnimationStateChanged(boolean running) {
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.onRecentsAnimationStateChanged(running);
+                    bar.onRecentsAnimationStateChanged(running);
                 } catch (RemoteException ex) {}
             }
 
@@ -651,9 +677,10 @@
             getUiState(displayId).setBarAttributes(appearance, appearanceRegions,
                     navbarColorManagedByIme, behavior, requestedVisibleTypes, packageName,
                     letterboxDetails);
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.onSystemBarAttributesChanged(displayId, appearance, appearanceRegions,
+                    bar.onSystemBarAttributesChanged(displayId, appearance, appearanceRegions,
                             navbarColorManagedByIme, behavior, requestedVisibleTypes, packageName,
                             letterboxDetails);
                 } catch (RemoteException ex) { }
@@ -664,9 +691,10 @@
         public void showTransient(int displayId, @InsetsType int types,
                 boolean isGestureOnSystemBar) {
             getUiState(displayId).showTransient(types);
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.showTransient(displayId, types, isGestureOnSystemBar);
+                    bar.showTransient(displayId, types, isGestureOnSystemBar);
                 } catch (RemoteException ex) { }
             }
         }
@@ -674,9 +702,10 @@
         @Override
         public void abortTransient(int displayId, @InsetsType int types) {
             getUiState(displayId).clearTransient(types);
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.abortTransient(displayId, types);
+                    bar.abortTransient(displayId, types);
                 } catch (RemoteException ex) { }
             }
         }
@@ -685,9 +714,10 @@
         public void showToast(int uid, String packageName, IBinder token, CharSequence text,
                 IBinder windowToken, int duration,
                 @Nullable ITransientNotificationCallback callback, int displayId) {
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.showToast(uid, packageName, token, text, windowToken, duration, callback,
+                    bar.showToast(uid, packageName, token, text, windowToken, duration, callback,
                             displayId);
                 } catch (RemoteException ex) { }
             }
@@ -695,18 +725,20 @@
 
         @Override
         public void hideToast(String packageName, IBinder token) {
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.hideToast(packageName, token);
+                    bar.hideToast(packageName, token);
                 } catch (RemoteException ex) { }
             }
         }
 
         @Override
         public boolean requestWindowMagnificationConnection(boolean request) {
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.requestWindowMagnificationConnection(request);
+                    bar.requestWindowMagnificationConnection(request);
                     return true;
                 } catch (RemoteException ex) { }
             }
@@ -715,9 +747,10 @@
 
         @Override
         public void setNavigationBarLumaSamplingEnabled(int displayId, boolean enable) {
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.setNavigationBarLumaSamplingEnabled(displayId, enable);
+                    bar.setNavigationBarLumaSamplingEnabled(displayId, enable);
                 } catch (RemoteException ex) { }
             }
         }
@@ -727,45 +760,50 @@
             synchronized (mLock) {
                 mUdfpsRefreshRateRequestCallback = callback;
             }
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.setUdfpsRefreshRateCallback(callback);
+                    bar.setUdfpsRefreshRateCallback(callback);
                 } catch (RemoteException ex) { }
             }
         }
 
         @Override
         public void showRearDisplayDialog(int currentBaseState) {
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.showRearDisplayDialog(currentBaseState);
+                    bar.showRearDisplayDialog(currentBaseState);
                 } catch (RemoteException ex) { }
             }
         }
 
         @Override
         public void goToFullscreenFromSplit() {
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.goToFullscreenFromSplit();
+                    bar.goToFullscreenFromSplit();
                 } catch (RemoteException ex) { }
             }
         }
 
         @Override
         public void enterStageSplitFromRunningApp(boolean leftOrTop) {
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.enterStageSplitFromRunningApp(leftOrTop);
+                    bar.enterStageSplitFromRunningApp(leftOrTop);
                 } catch (RemoteException ex) { }
             }
         }
 
         @Override
         public void showMediaOutputSwitcher(String packageName) {
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.showMediaOutputSwitcher(packageName);
+                    bar.showMediaOutputSwitcher(packageName);
                 } catch (RemoteException ex) {
                 }
             }
@@ -788,9 +826,10 @@
 
         @Override
         public void showGlobalActions() {
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.showGlobalActionsMenu();
+                    bar.showGlobalActionsMenu();
                 } catch (RemoteException ex) {}
             }
         }
@@ -1113,9 +1152,10 @@
         if (!state.disableEquals(net1, net2)) {
             state.setDisabled(net1, net2);
             mHandler.post(() -> mNotificationDelegate.onSetDisabled(net1));
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.disable(displayId, net1, net2);
+                    bar.disable(displayId, net1, net2);
                 } catch (RemoteException ex) {
                 }
             }
@@ -1173,9 +1213,10 @@
             //Slog.d(TAG, "setIcon slot=" + slot + " index=" + index + " icon=" + icon);
             mIcons.put(slot, icon);
 
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.setIcon(slot, icon);
+                    bar.setIcon(slot, icon);
                 } catch (RemoteException ex) {
                 }
             }
@@ -1194,9 +1235,10 @@
             if (icon.visible != visibility) {
                 icon.visible = visibility;
 
-                if (mBar != null) {
+                IStatusBar bar = mBar;
+                if (bar != null) {
                     try {
-                        mBar.setIcon(slot, icon);
+                        bar.setIcon(slot, icon);
                     } catch (RemoteException ex) {
                     }
                 }
@@ -1211,9 +1253,10 @@
         synchronized (mIcons) {
             mIcons.remove(slot);
 
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.removeIcon(slot);
+                    bar.removeIcon(slot);
                 } catch (RemoteException ex) {
                 }
             }
@@ -1815,9 +1858,10 @@
     @Override
     public void showInattentiveSleepWarning() {
         enforceStatusBarService();
-        if (mBar != null) {
+        IStatusBar bar = mBar;
+        if (bar != null) {
             try {
-                mBar.showInattentiveSleepWarning();
+                bar.showInattentiveSleepWarning();
             } catch (RemoteException ex) {
             }
         }
@@ -1826,9 +1870,10 @@
     @Override
     public void dismissInattentiveSleepWarning(boolean animated) {
         enforceStatusBarService();
-        if (mBar != null) {
+        IStatusBar bar = mBar;
+        if (bar != null) {
             try {
-                mBar.dismissInattentiveSleepWarning(animated);
+                bar.dismissInattentiveSleepWarning(animated);
             } catch (RemoteException ex) {
             }
         }
@@ -1837,9 +1882,10 @@
     @Override
     public void suppressAmbientDisplay(boolean suppress) {
         enforceStatusBarService();
-        if (mBar != null) {
+        IStatusBar bar = mBar;
+        if (bar != null) {
             try {
-                mBar.suppressAmbientDisplay(suppress);
+                bar.suppressAmbientDisplay(suppress);
             } catch (RemoteException ex) {
             }
         }
@@ -1913,9 +1959,10 @@
                 }
             }
         }
-        if (mBar != null) {
+        IStatusBar bar = mBar;
+        if (bar != null) {
             try {
-                mBar.requestTileServiceListeningState(componentName);
+                bar.requestTileServiceListeningState(componentName);
             } catch (RemoteException e) {
                 Slog.e(TAG, "requestTileServiceListeningState", e);
             }
@@ -2028,9 +2075,10 @@
 
         CharSequence appName = r.serviceInfo.applicationInfo
                 .loadLabel(mContext.getPackageManager());
-        if (mBar != null) {
+        IStatusBar bar = mBar;
+        if (bar != null) {
             try {
-                mBar.requestAddTile(componentName, appName, label, icon, proxyCallback);
+                bar.requestAddTile(componentName, appName, label, icon, proxyCallback);
                 return;
             } catch (RemoteException e) {
                 Slog.e(TAG, "requestAddTile", e);
@@ -2052,9 +2100,10 @@
 
     private void cancelRequestAddTileInternal(String packageName) {
         clearTileAddRequest(packageName);
-        if (mBar != null) {
+        IStatusBar bar = mBar;
+        if (bar != null) {
             try {
-                mBar.cancelRequestAddTile(packageName);
+                bar.cancelRequestAddTile(packageName);
             } catch (RemoteException e) {
                 Slog.e(TAG, "requestAddTile", e);
             }
@@ -2183,9 +2232,10 @@
             @Nullable IUndoMediaTransferCallback undoCallback
     ) {
         enforceMediaContentControl();
-        if (mBar != null) {
+        IStatusBar bar = mBar;
+        if (bar != null) {
             try {
-                mBar.updateMediaTapToTransferSenderDisplay(displayState, routeInfo, undoCallback);
+                bar.updateMediaTapToTransferSenderDisplay(displayState, routeInfo, undoCallback);
             } catch (RemoteException e) {
                 Slog.e(TAG, "updateMediaTapToTransferSenderDisplay", e);
             }
@@ -2206,9 +2256,10 @@
             @Nullable Icon appIcon,
             @Nullable CharSequence appName) {
         enforceMediaContentControl();
-        if (mBar != null) {
+        IStatusBar bar = mBar;
+        if (bar != null) {
             try {
-                mBar.updateMediaTapToTransferReceiverDisplay(
+                bar.updateMediaTapToTransferReceiverDisplay(
                         displayState, routeInfo, appIcon, appName);
             } catch (RemoteException e) {
                 Slog.e(TAG, "updateMediaTapToTransferReceiverDisplay", e);
@@ -2232,9 +2283,10 @@
             @NonNull INearbyMediaDevicesProvider provider
     ) {
         enforceMediaContentControl();
-        if (mBar != null) {
+        IStatusBar bar = mBar;
+        if (bar != null) {
             try {
-                mBar.registerNearbyMediaDevicesProvider(provider);
+                bar.registerNearbyMediaDevicesProvider(provider);
             } catch (RemoteException e) {
                 Slog.e(TAG, "registerNearbyMediaDevicesProvider", e);
             }
@@ -2257,9 +2309,10 @@
             @NonNull INearbyMediaDevicesProvider provider
     ) {
         enforceMediaContentControl();
-        if (mBar != null) {
+        IStatusBar bar = mBar;
+        if (bar != null) {
             try {
-                mBar.unregisterNearbyMediaDevicesProvider(provider);
+                bar.unregisterNearbyMediaDevicesProvider(provider);
             } catch (RemoteException e) {
                 Slog.e(TAG, "unregisterNearbyMediaDevicesProvider", e);
             }
@@ -2270,9 +2323,10 @@
     @Override
     public void showRearDisplayDialog(int currentState) {
         enforceControlDeviceStatePermission();
-        if (mBar != null) {
+        IStatusBar bar = mBar;
+        if (bar != null) {
             try {
-                mBar.showRearDisplayDialog(currentState);
+                bar.showRearDisplayDialog(currentState);
             } catch (RemoteException e) {
                 Slog.e(TAG, "showRearDisplayDialog", e);
             }
diff --git a/services/core/java/com/android/server/timedetector/ConfigurationInternal.java b/services/core/java/com/android/server/timedetector/ConfigurationInternal.java
index 4f221b5..9718194 100644
--- a/services/core/java/com/android/server/timedetector/ConfigurationInternal.java
+++ b/services/core/java/com/android/server/timedetector/ConfigurationInternal.java
@@ -244,6 +244,8 @@
                 && mAutoDetectionEnabledSetting == that.mAutoDetectionEnabledSetting
                 && mUserId == that.mUserId && mUserConfigAllowed == that.mUserConfigAllowed
                 && mSystemClockUpdateThresholdMillis == that.mSystemClockUpdateThresholdMillis
+                && mSystemClockConfidenceThresholdMillis
+                == that.mSystemClockConfidenceThresholdMillis
                 && mAutoSuggestionLowerBound.equals(that.mAutoSuggestionLowerBound)
                 && mManualSuggestionLowerBound.equals(that.mManualSuggestionLowerBound)
                 && mSuggestionUpperBound.equals(that.mSuggestionUpperBound)
@@ -253,7 +255,8 @@
     @Override
     public int hashCode() {
         int result = Objects.hash(mAutoDetectionSupported, mAutoDetectionEnabledSetting, mUserId,
-                mUserConfigAllowed, mSystemClockUpdateThresholdMillis, mAutoSuggestionLowerBound,
+                mUserConfigAllowed, mSystemClockUpdateThresholdMillis,
+                mSystemClockConfidenceThresholdMillis, mAutoSuggestionLowerBound,
                 mManualSuggestionLowerBound, mSuggestionUpperBound);
         result = 31 * result + Arrays.hashCode(mOriginPriorities);
         return result;
diff --git a/services/core/java/com/android/server/timedetector/EnvironmentImpl.java b/services/core/java/com/android/server/timedetector/EnvironmentImpl.java
index fc960d8..c52f8f8 100644
--- a/services/core/java/com/android/server/timedetector/EnvironmentImpl.java
+++ b/services/core/java/com/android/server/timedetector/EnvironmentImpl.java
@@ -22,15 +22,16 @@
 import android.os.Handler;
 import android.os.PowerManager;
 import android.os.SystemClock;
+import android.util.IndentingPrintWriter;
 import android.util.Slog;
 
 import com.android.server.AlarmManagerInternal;
 import com.android.server.LocalServices;
 import com.android.server.SystemClockTime;
 import com.android.server.SystemClockTime.TimeConfidence;
-import com.android.server.timezonedetector.StateChangeListener;
 
-import java.io.PrintWriter;
+import java.time.Duration;
+import java.time.Instant;
 import java.util.Objects;
 
 /**
@@ -41,14 +42,11 @@
     private static final String LOG_TAG = TimeDetectorService.TAG;
 
     @NonNull private final Handler mHandler;
-    @NonNull private final ServiceConfigAccessor mServiceConfigAccessor;
     @NonNull private final PowerManager.WakeLock mWakeLock;
     @NonNull private final AlarmManagerInternal mAlarmManagerInternal;
 
-    EnvironmentImpl(@NonNull Context context, @NonNull Handler handler,
-            @NonNull ServiceConfigAccessor serviceConfigAccessor) {
+    EnvironmentImpl(@NonNull Context context, @NonNull Handler handler) {
         mHandler = Objects.requireNonNull(handler);
-        mServiceConfigAccessor = Objects.requireNonNull(serviceConfigAccessor);
 
         PowerManager powerManager = context.getSystemService(PowerManager.class);
         mWakeLock = Objects.requireNonNull(
@@ -59,19 +57,6 @@
     }
 
     @Override
-    public void setConfigurationInternalChangeListener(
-            @NonNull StateChangeListener listener) {
-        StateChangeListener stateChangeListener =
-                () -> mHandler.post(listener::onChange);
-        mServiceConfigAccessor.addConfigurationInternalChangeListener(stateChangeListener);
-    }
-
-    @Override
-    public ConfigurationInternal getCurrentUserConfigurationInternal() {
-        return mServiceConfigAccessor.getCurrentUserConfigurationInternal();
-    }
-
-    @Override
     public void acquireWakeLock() {
         if (mWakeLock.isHeld()) {
             Slog.wtf(LOG_TAG, "WakeLock " + mWakeLock + " already held");
@@ -126,8 +111,19 @@
     }
 
     @Override
-    public void dumpDebugLog(@NonNull PrintWriter printWriter) {
-        SystemClockTime.dump(printWriter);
+    public void dumpDebugLog(@NonNull IndentingPrintWriter pw) {
+        long elapsedRealtimeMillis = elapsedRealtimeMillis();
+        pw.printf("elapsedRealtimeMillis()=%s (%s)\n",
+                Duration.ofMillis(elapsedRealtimeMillis), elapsedRealtimeMillis);
+        long systemClockMillis = systemClockMillis();
+        pw.printf("systemClockMillis()=%s (%s)\n",
+                Instant.ofEpochMilli(systemClockMillis), systemClockMillis);
+        pw.println("systemClockConfidence()=" + systemClockConfidence());
+
+        pw.println("SystemClockTime debug log:");
+        pw.increaseIndent();
+        SystemClockTime.dump(pw);
+        pw.decreaseIndent();
     }
 
     @Override
diff --git a/services/core/java/com/android/server/timedetector/NetworkTimeUpdateService.java b/services/core/java/com/android/server/timedetector/NetworkTimeUpdateService.java
index e7c073c..49dec05 100644
--- a/services/core/java/com/android/server/timedetector/NetworkTimeUpdateService.java
+++ b/services/core/java/com/android/server/timedetector/NetworkTimeUpdateService.java
@@ -21,13 +21,9 @@
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.app.AlarmManager;
-import android.app.PendingIntent;
 import android.app.time.UnixEpochTime;
-import android.content.BroadcastReceiver;
 import android.content.ContentResolver;
 import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
 import android.database.ContentObserver;
 import android.net.ConnectivityManager;
 import android.net.Network;
@@ -73,10 +69,6 @@
     private static final String TAG = "NetworkTimeUpdateService";
     private static final boolean DBG = false;
 
-    private static final String ACTION_POLL =
-            "com.android.server.timedetector.NetworkTimeUpdateService.action.POLL";
-    private static final int POLL_REQUEST = 0;
-
     private final Object mLock = new Object();
     private final Context mContext;
     private final ConnectivityManager mCM;
@@ -113,16 +105,19 @@
         AlarmManager alarmManager = mContext.getSystemService(AlarmManager.class);
         TimeDetectorInternal timeDetectorInternal =
                 LocalServices.getService(TimeDetectorInternal.class);
-        // Broadcast alarms sent by system are immutable
-        Intent pollIntent = new Intent(ACTION_POLL, null).setPackage("android");
-        PendingIntent pendingPollIntent = PendingIntent.getBroadcast(mContext, POLL_REQUEST,
-                pollIntent, PendingIntent.FLAG_IMMUTABLE);
         mRefreshCallbacks = new Engine.RefreshCallbacks() {
+            private final AlarmManager.OnAlarmListener mOnAlarmListener =
+                    new ScheduledRefreshAlarmListener();
+
             @Override
             public void scheduleNextRefresh(@ElapsedRealtimeLong long elapsedRealtimeMillis) {
-                alarmManager.cancel(pendingPollIntent);
+                alarmManager.cancel(mOnAlarmListener);
+
+                String alarmTag = "NetworkTimeUpdateService.POLL";
+                Handler handler = null; // Use the main thread
                 alarmManager.set(
-                        AlarmManager.ELAPSED_REALTIME, elapsedRealtimeMillis, pendingPollIntent);
+                        AlarmManager.ELAPSED_REALTIME, elapsedRealtimeMillis, alarmTag,
+                        mOnAlarmListener, handler);
             }
 
             @Override
@@ -138,10 +133,6 @@
 
     /** Initialize the receivers and initiate the first NTP request */
     public void systemRunning() {
-        // Listen for scheduled refreshes.
-        ScheduledRefreshBroadcastReceiver receiver = new ScheduledRefreshBroadcastReceiver();
-        mContext.registerReceiver(receiver, new IntentFilter(ACTION_POLL));
-
         // Listen for network connectivity changes.
         NetworkConnectivityCallback networkConnectivityCallback = new NetworkConnectivityCallback();
         mCM.registerDefaultNetworkCallback(networkConnectivityCallback, mHandler);
@@ -214,13 +205,13 @@
         }
     }
 
-    private class ScheduledRefreshBroadcastReceiver extends BroadcastReceiver implements Runnable {
+    private class ScheduledRefreshAlarmListener implements AlarmManager.OnAlarmListener, Runnable {
 
         @Override
-        public void onReceive(Context context, Intent intent) {
-            // The BroadcastReceiver has to complete quickly or an ANR will be triggered by the
+        public void onAlarm() {
+            // The OnAlarmListener has to complete quickly or an ANR will be triggered by the
             // platform regardless of the receiver thread used. Instead of blocking the receiver
-            // thread, the long-running / blocking work is posted to mHandler to allow onReceive()
+            // thread, the long-running / blocking work is posted to mHandler to allow onAlarm()
             // to return immediately.
             mHandler.post(this);
         }
@@ -424,8 +415,14 @@
             logToDebugAndDumpsys("forceRefreshForTests: refreshSuccessful=" + refreshSuccessful);
 
             if (refreshSuccessful) {
-                makeNetworkTimeSuggestion(mNtpTrustedTime.getCachedTimeResult(),
-                        "EngineImpl.forceRefreshForTests()", refreshCallbacks);
+                TimeResult cachedTimeResult = mNtpTrustedTime.getCachedTimeResult();
+                if (cachedTimeResult == null) {
+                    logToDebugAndDumpsys(
+                            "forceRefreshForTests: cachedTimeResult unexpectedly null");
+                } else {
+                    makeNetworkTimeSuggestion(cachedTimeResult,
+                            "EngineImpl.forceRefreshForTests()", refreshCallbacks);
+                }
             }
             return refreshSuccessful;
         }
diff --git a/services/core/java/com/android/server/timedetector/ServiceConfigAccessor.java b/services/core/java/com/android/server/timedetector/ServiceConfigAccessor.java
index ff180eb..7bc0af3 100644
--- a/services/core/java/com/android/server/timedetector/ServiceConfigAccessor.java
+++ b/services/core/java/com/android/server/timedetector/ServiceConfigAccessor.java
@@ -64,7 +64,7 @@
             boolean bypassUserPolicyChecks);
 
     /**
-     * Returns a snapshot of the configuration that controls time zone detector behavior for the
+     * Returns a snapshot of the configuration that controls time detector behavior for the
      * specified user.
      */
     @NonNull
diff --git a/services/core/java/com/android/server/timedetector/ServiceConfigAccessorImpl.java b/services/core/java/com/android/server/timedetector/ServiceConfigAccessorImpl.java
index dc2a974..58c31d5 100644
--- a/services/core/java/com/android/server/timedetector/ServiceConfigAccessorImpl.java
+++ b/services/core/java/com/android/server/timedetector/ServiceConfigAccessorImpl.java
@@ -39,7 +39,6 @@
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
 import android.database.ContentObserver;
-import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
@@ -65,8 +64,6 @@
  */
 final class ServiceConfigAccessorImpl implements ServiceConfigAccessor {
 
-    private static final int SYSTEM_CLOCK_UPDATE_THRESHOLD_MILLIS_DEFAULT = 2 * 1000;
-
     /**
      * An absolute threshold at/below which the system clock confidence can be upgraded. i.e. if the
      * detector receives a high-confidence time and the current system clock is +/- this value from
@@ -122,9 +119,8 @@
         mConfigOriginPrioritiesSupplier = new ConfigOriginPrioritiesSupplier(context);
         mServerFlagsOriginPrioritiesSupplier =
                 new ServerFlagsOriginPrioritiesSupplier(mServerFlags);
-        mSystemClockUpdateThresholdMillis =
-                SystemProperties.getInt("ro.sys.time_detector_update_diff",
-                        SYSTEM_CLOCK_UPDATE_THRESHOLD_MILLIS_DEFAULT);
+        mSystemClockUpdateThresholdMillis = context.getResources().getInteger(
+                R.integer.config_timeDetectorAutoUpdateDiffMillis);
 
         // Wire up the config change listeners for anything that could affect ConfigurationInternal.
         // Use the main thread for event delivery, listeners can post to their chosen thread.
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorService.java b/services/core/java/com/android/server/timedetector/TimeDetectorService.java
index 22f096b..d88f426 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorService.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorService.java
@@ -96,8 +96,8 @@
 
             CallerIdentityInjector callerIdentityInjector = CallerIdentityInjector.REAL;
             TimeDetectorService service = new TimeDetectorService(
-                    context, handler, callerIdentityInjector, serviceConfigAccessor,
-                    timeDetectorStrategy, NtpTrustedTime.getInstance(context));
+                    context, handler, callerIdentityInjector, timeDetectorStrategy,
+                    NtpTrustedTime.getInstance(context));
 
             // Publish the binder service so it can be accessed from other (appropriately
             // permissioned) processes.
@@ -108,7 +108,6 @@
     @NonNull private final Handler mHandler;
     @NonNull private final Context mContext;
     @NonNull private final CallerIdentityInjector mCallerIdentityInjector;
-    @NonNull private final ServiceConfigAccessor mServiceConfigAccessor;
     @NonNull private final TimeDetectorStrategy mTimeDetectorStrategy;
     @NonNull private final NtpTrustedTime mNtpTrustedTime;
 
@@ -123,20 +122,18 @@
     @VisibleForTesting
     public TimeDetectorService(@NonNull Context context, @NonNull Handler handler,
             @NonNull CallerIdentityInjector callerIdentityInjector,
-            @NonNull ServiceConfigAccessor serviceConfigAccessor,
             @NonNull TimeDetectorStrategy timeDetectorStrategy,
             @NonNull NtpTrustedTime ntpTrustedTime) {
         mContext = Objects.requireNonNull(context);
         mHandler = Objects.requireNonNull(handler);
         mCallerIdentityInjector = Objects.requireNonNull(callerIdentityInjector);
-        mServiceConfigAccessor = Objects.requireNonNull(serviceConfigAccessor);
         mTimeDetectorStrategy = Objects.requireNonNull(timeDetectorStrategy);
         mNtpTrustedTime = Objects.requireNonNull(ntpTrustedTime);
 
-        // Wire up a change listener so that ITimeZoneDetectorListeners can be notified when
-        // the configuration changes for any reason.
-        mServiceConfigAccessor.addConfigurationInternalChangeListener(
-                () -> mHandler.post(this::handleConfigurationInternalChangedOnHandlerThread));
+        // Wire up a change listener so that ITimeDetectorListeners can be notified when the
+        // detector state changes for any reason.
+        mTimeDetectorStrategy.addChangeListener(
+                () -> mHandler.post(this::handleChangeOnHandlerThread));
     }
 
     @Override
@@ -151,10 +148,8 @@
 
         final long token = mCallerIdentityInjector.clearCallingIdentity();
         try {
-            ConfigurationInternal configurationInternal =
-                    mServiceConfigAccessor.getConfigurationInternal(userId);
-            final boolean bypassUserPolicyCheck = false;
-            return configurationInternal.createCapabilitiesAndConfig(bypassUserPolicyCheck);
+            final boolean bypassUserPolicyChecks = false;
+            return mTimeDetectorStrategy.getCapabilitiesAndConfig(userId, bypassUserPolicyChecks);
         } finally {
             mCallerIdentityInjector.restoreCallingIdentity(token);
         }
@@ -180,9 +175,9 @@
 
         final long token = mCallerIdentityInjector.clearCallingIdentity();
         try {
-            final boolean bypassUserPolicyCheck = false;
-            return mServiceConfigAccessor.updateConfiguration(
-                    resolvedUserId, configuration, bypassUserPolicyCheck);
+            final boolean bypassUserPolicyChecks = false;
+            return mTimeDetectorStrategy.updateConfiguration(
+                    resolvedUserId, configuration, bypassUserPolicyChecks);
         } finally {
             mCallerIdentityInjector.restoreCallingIdentity(token);
         }
@@ -262,7 +257,7 @@
         }
     }
 
-    private void handleConfigurationInternalChangedOnHandlerThread() {
+    private void handleChangeOnHandlerThread() {
         // Configuration has changed, but each user may have a different view of the configuration.
         // It's possible that this will cause unnecessary notifications but that shouldn't be a
         // problem.
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java
index 11cec66..15c0a80 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java
@@ -21,6 +21,8 @@
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
 import android.app.time.ExternalTimeSuggestion;
+import android.app.time.TimeCapabilitiesAndConfig;
+import android.app.time.TimeConfiguration;
 import android.app.time.TimeState;
 import android.app.time.UnixEpochTime;
 import android.app.timedetector.ManualTimeSuggestion;
@@ -87,6 +89,48 @@
      */
     boolean confirmTime(@NonNull UnixEpochTime confirmationTime);
 
+    /**
+     * Adds a listener that will be triggered when something changes that could affect the result
+     * of the {@link #getCapabilitiesAndConfig} call for the <em>current user only</em>. This
+     * includes the current user changing. This is exposed so that (indirect) users like SettingsUI
+     * can monitor for changes to data derived from {@link TimeCapabilitiesAndConfig} and update
+     * the UI accordingly.
+     */
+    void addChangeListener(@NonNull StateChangeListener listener);
+
+    /**
+     * Returns a {@link TimeCapabilitiesAndConfig} object for the specified user.
+     *
+     * <p>The strategy is dependent on device state like current user, settings and device config.
+     * These updates are usually handled asynchronously, so callers should expect some delay between
+     * a change being made directly to services like settings and the strategy becoming aware of
+     * them. Changes made via {@link #updateConfiguration} will be visible immediately.
+     *
+     * @param userId the user ID to retrieve the information for
+     * @param bypassUserPolicyChecks {@code true} for device policy manager use cases where device
+     *   policy restrictions that should apply to actual users can be ignored
+     */
+    TimeCapabilitiesAndConfig getCapabilitiesAndConfig(
+            @UserIdInt int userId, boolean bypassUserPolicyChecks);
+
+    /**
+     * Updates the configuration properties that control a device's time behavior.
+     *
+     * <p>This method returns {@code true} if the configuration was changed, {@code false}
+     * otherwise.
+     *
+     * <p>See {@link #getCapabilitiesAndConfig} for guarantees about visibility of updates to
+     * subsequent calls.
+     *
+     * @param userId the current user ID, supplied to make sure that the asynchronous process
+     *   that happens when users switch is completed when the call is made
+     * @param configuration the configuration changes
+     * @param bypassUserPolicyChecks {@code true} for device policy manager use cases where device
+     *   policy restrictions that should apply to actual users can be ignored
+     */
+    boolean updateConfiguration(@UserIdInt int userId,
+            @NonNull TimeConfiguration configuration, boolean bypassUserPolicyChecks);
+
     /** Processes the suggested time from telephony sources. */
     void suggestTelephonyTime(@NonNull TelephonyTimeSuggestion suggestion);
 
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
index b293bac..374dd89 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
@@ -30,6 +30,7 @@
 import android.app.time.ExternalTimeSuggestion;
 import android.app.time.TimeCapabilities;
 import android.app.time.TimeCapabilitiesAndConfig;
+import android.app.time.TimeConfiguration;
 import android.app.time.TimeState;
 import android.app.time.UnixEpochTime;
 import android.app.timedetector.ManualTimeSuggestion;
@@ -48,10 +49,10 @@
 import com.android.server.timezonedetector.ReferenceWithHistory;
 import com.android.server.timezonedetector.StateChangeListener;
 
-import java.io.PrintWriter;
-import java.time.Duration;
 import java.time.Instant;
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.List;
 import java.util.Objects;
 
 /**
@@ -94,6 +95,12 @@
     @NonNull
     private final Environment mEnvironment;
 
+    @NonNull
+    private final ServiceConfigAccessor mServiceConfigAccessor;
+
+    @GuardedBy("this")
+    @NonNull private final List<StateChangeListener> mStateChangeListeners = new ArrayList<>();
+
     @GuardedBy("this")
     @NonNull
     private ConfigurationInternal mCurrentConfigurationInternal;
@@ -139,16 +146,6 @@
      */
     public interface Environment {
 
-        /**
-         * Sets a {@link StateChangeListener} that will be invoked when there are any changes that
-         * could affect the content of {@link ConfigurationInternal}.
-         * This is invoked during system server setup.
-         */
-        void setConfigurationInternalChangeListener(@NonNull StateChangeListener listener);
-
-        /** Returns the {@link ConfigurationInternal} for the current user. */
-        @NonNull ConfigurationInternal getCurrentUserConfigurationInternal();
-
         /** Acquire a suitable wake lock. Must be followed by {@link #releaseWakeLock()} */
         void acquireWakeLock();
 
@@ -174,16 +171,15 @@
         /** Release the wake lock acquired by a call to {@link #acquireWakeLock()}. */
         void releaseWakeLock();
 
-
         /**
          * Adds a standalone entry to the time debug log.
          */
         void addDebugLogEntry(@NonNull String logMsg);
 
         /**
-         * Dumps the time debug log to the supplied {@link PrintWriter}.
+         * Dumps the time debug log to the supplied {@link IndentingPrintWriter}.
          */
-        void dumpDebugLog(PrintWriter printWriter);
+        void dumpDebugLog(IndentingPrintWriter ipw);
 
         /**
          * Requests that the supplied runnable is invoked asynchronously.
@@ -195,19 +191,23 @@
             @NonNull Context context, @NonNull Handler handler,
             @NonNull ServiceConfigAccessor serviceConfigAccessor) {
 
-        TimeDetectorStrategyImpl.Environment environment =
-                new EnvironmentImpl(context, handler, serviceConfigAccessor);
-        return new TimeDetectorStrategyImpl(environment);
+        TimeDetectorStrategyImpl.Environment environment = new EnvironmentImpl(context, handler);
+        return new TimeDetectorStrategyImpl(environment, serviceConfigAccessor);
     }
 
     @VisibleForTesting
-    TimeDetectorStrategyImpl(@NonNull Environment environment) {
+    TimeDetectorStrategyImpl(@NonNull Environment environment,
+            @NonNull ServiceConfigAccessor serviceConfigAccessor) {
         mEnvironment = Objects.requireNonNull(environment);
+        mServiceConfigAccessor = Objects.requireNonNull(serviceConfigAccessor);
 
         synchronized (this) {
-            mEnvironment.setConfigurationInternalChangeListener(
-                    this::handleConfigurationInternalChanged);
-            mCurrentConfigurationInternal = mEnvironment.getCurrentUserConfigurationInternal();
+            // Listen for config and user changes and get an initial snapshot of configuration.
+            StateChangeListener stateChangeListener = this::handleConfigurationInternalMaybeChanged;
+            mServiceConfigAccessor.addConfigurationInternalChangeListener(stateChangeListener);
+
+            // Initialize mCurrentConfigurationInternal with a starting value.
+            updateCurrentConfigurationInternalIfRequired("TimeDetectorStrategyImpl:");
         }
     }
 
@@ -378,7 +378,7 @@
             @Origin int origin = ORIGIN_MANUAL;
             UnixEpochTime unixEpochTime = timeState.getUnixEpochTime();
             setSystemClockAndConfidenceUnderWakeLock(
-                    origin, unixEpochTime, confidence, "setTimeZoneState()");
+                    origin, unixEpochTime, confidence, "setTimeState()");
         } finally {
             mEnvironment.releaseWakeLock();
         }
@@ -421,6 +421,57 @@
         }
     }
 
+    @GuardedBy("this")
+    private void notifyStateChangeListenersAsynchronously() {
+        for (StateChangeListener listener : mStateChangeListeners) {
+            // This is queuing asynchronous notification, so no need to surrender the "this" lock.
+            mEnvironment.runAsync(listener::onChange);
+        }
+    }
+
+    @Override
+    public synchronized void addChangeListener(@NonNull StateChangeListener listener) {
+        mStateChangeListeners.add(listener);
+    }
+
+    @Override
+    public synchronized TimeCapabilitiesAndConfig getCapabilitiesAndConfig(@UserIdInt int userId,
+            boolean bypassUserPolicyChecks) {
+        ConfigurationInternal configurationInternal;
+        if (mCurrentConfigurationInternal.getUserId() == userId) {
+            // Use the cached snapshot we have.
+            configurationInternal = mCurrentConfigurationInternal;
+        } else {
+            // This is not a common case: It would be unusual to want the configuration for a user
+            // other than the "current" user, but it is supported because it is trivial to do so.
+            // Unlike the current user config, there's no cached copy to worry about so read it
+            // directly from mServiceConfigAccessor.
+            configurationInternal = mServiceConfigAccessor.getConfigurationInternal(userId);
+        }
+        return configurationInternal.createCapabilitiesAndConfig(bypassUserPolicyChecks);
+    }
+
+    @Override
+    public synchronized boolean updateConfiguration(@UserIdInt int userId,
+            @NonNull TimeConfiguration configuration, boolean bypassUserPolicyChecks) {
+        // Write-through
+        boolean updateSuccessful = mServiceConfigAccessor.updateConfiguration(
+                userId, configuration, bypassUserPolicyChecks);
+
+        // The update above will trigger config update listeners asynchronously if they are needed,
+        // but that could mean an immediate call to getCapabilitiesAndConfig() for the current user
+        // wouldn't see the update. So, handle the cache update and notifications here. When the
+        // async update listener triggers it will find everything already up to date and do nothing.
+        if (updateSuccessful) {
+            String logMsg = "updateConfiguration:"
+                    + " userId=" + userId
+                    + ", configuration=" + configuration
+                    + ", bypassUserPolicyChecks=" + bypassUserPolicyChecks;
+            updateCurrentConfigurationInternalIfRequired(logMsg);
+        }
+        return updateSuccessful;
+    }
+
     @Override
     public synchronized void suggestTelephonyTime(@NonNull TelephonyTimeSuggestion suggestion) {
         // Empty time suggestion means that telephony network connectivity has been lost.
@@ -448,26 +499,49 @@
         doAutoTimeDetection(reason);
     }
 
-    private synchronized void handleConfigurationInternalChanged() {
-        ConfigurationInternal currentUserConfig =
-                mEnvironment.getCurrentUserConfigurationInternal();
-        String logMsg = "handleConfigurationInternalChanged:"
-                + " oldConfiguration=" + mCurrentConfigurationInternal
-                + ", newConfiguration=" + currentUserConfig;
-        addDebugLogEntry(logMsg);
-        mCurrentConfigurationInternal = currentUserConfig;
+    /**
+     * Handles a configuration change notification.
+     */
+    private synchronized void handleConfigurationInternalMaybeChanged() {
+        String logMsg = "handleConfigurationInternalMaybeChanged:";
+        updateCurrentConfigurationInternalIfRequired(logMsg);
+    }
 
-        boolean autoDetectionEnabled =
-                mCurrentConfigurationInternal.getAutoDetectionEnabledBehavior();
-        // When automatic time detection is enabled we update the system clock instantly if we can.
-        // Conversely, when automatic time detection is disabled we leave the clock as it is.
-        if (autoDetectionEnabled) {
-            String reason = "Auto time zone detection config changed.";
-            doAutoTimeDetection(reason);
-        } else {
-            // CLOCK_PARANOIA: We are losing "control" of the system clock so we cannot predict what
-            // it should be in future.
-            mLastAutoSystemClockTimeSet = null;
+    @GuardedBy("this")
+    private void updateCurrentConfigurationInternalIfRequired(@NonNull String logMsg) {
+        ConfigurationInternal newCurrentConfigurationInternal =
+                mServiceConfigAccessor.getCurrentUserConfigurationInternal();
+        // mCurrentConfigurationInternal is null the first time this method is called.
+        ConfigurationInternal oldCurrentConfigurationInternal = mCurrentConfigurationInternal;
+
+        // If the configuration actually changed, update the cached copy synchronously and do
+        // other necessary house-keeping / (async) listener notifications.
+        if (!newCurrentConfigurationInternal.equals(oldCurrentConfigurationInternal)) {
+            mCurrentConfigurationInternal = newCurrentConfigurationInternal;
+
+            logMsg = new StringBuilder(logMsg)
+                    .append(" [oldConfiguration=").append(oldCurrentConfigurationInternal)
+                    .append(", newConfiguration=").append(newCurrentConfigurationInternal)
+                    .append("]")
+                    .toString();
+            addDebugLogEntry(logMsg);
+
+            // The configuration and maybe the status changed so notify listeners.
+            notifyStateChangeListenersAsynchronously();
+
+            boolean autoDetectionEnabled =
+                    mCurrentConfigurationInternal.getAutoDetectionEnabledBehavior();
+            // When automatic time detection is enabled we update the system clock instantly if we
+            // can. Conversely, when automatic time detection is disabled we leave the clock as it
+            // is.
+            if (autoDetectionEnabled) {
+                String reason = "Auto time detection config changed.";
+                doAutoTimeDetection(reason);
+            } else {
+                // CLOCK_PARANOIA: We are losing "control" of the system clock so we cannot predict
+                // what it should be in future.
+                mLastAutoSystemClockTimeSet = null;
+            }
         }
     }
 
@@ -489,13 +563,11 @@
         ipw.println("[Capabilities="
                 + mCurrentConfigurationInternal.createCapabilitiesAndConfig(bypassUserPolicyChecks)
                 + "]");
-        long elapsedRealtimeMillis = mEnvironment.elapsedRealtimeMillis();
-        ipw.printf("mEnvironment.elapsedRealtimeMillis()=%s (%s)\n",
-                Duration.ofMillis(elapsedRealtimeMillis), elapsedRealtimeMillis);
-        long systemClockMillis = mEnvironment.systemClockMillis();
-        ipw.printf("mEnvironment.systemClockMillis()=%s (%s)\n",
-                Instant.ofEpochMilli(systemClockMillis), systemClockMillis);
-        ipw.println("mEnvironment.systemClockConfidence()=" + mEnvironment.systemClockConfidence());
+
+        ipw.println("mEnvironment:");
+        ipw.increaseIndent();
+        mEnvironment.dumpDebugLog(ipw);
+        ipw.decreaseIndent();
 
         ipw.println("Time change log:");
         ipw.increaseIndent(); // level 2
@@ -525,6 +597,11 @@
         ipw.decreaseIndent(); // level 1
     }
 
+    @VisibleForTesting
+    public synchronized ConfigurationInternal getCachedCapabilitiesAndConfigForTests() {
+        return mCurrentConfigurationInternal;
+    }
+
     @GuardedBy("this")
     private boolean storeTelephonySuggestion(@NonNull TelephonyTimeSuggestion suggestion) {
         UnixEpochTime newUnixEpochTime = suggestion.getUnixEpochTime();
diff --git a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderController.java b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderController.java
index ed7ea00..36658b2 100644
--- a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderController.java
+++ b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderController.java
@@ -303,8 +303,7 @@
     private void reportSuggestionEvent(
             @NonNull GeolocationTimeZoneSuggestion suggestion, @NonNull String reason) {
         LocationTimeZoneAlgorithmStatus algorithmStatus = generateCurrentAlgorithmStatus();
-        LocationAlgorithmEvent event = new LocationAlgorithmEvent(
-                algorithmStatus, suggestion);
+        LocationAlgorithmEvent event = new LocationAlgorithmEvent(algorithmStatus, suggestion);
         event.addDebugInfo(reason);
         reportEvent(event);
     }
@@ -728,20 +727,35 @@
         // Start the uncertainty timeout if needed to ensure the controller will eventually make an
         // uncertain suggestion if no success event arrives in time to counteract it.
         if (!mUncertaintyTimeoutQueue.hasQueued()) {
-            debugLog("Starting uncertainty timeout: reason=" + reason);
+            if (STATE_UNCERTAIN.equals(mState.get())) {
+                // If the controller is already uncertain, there's no reason to start a timeout;
+                // just forward the suggestion immediately to make it obvious in the logs what has
+                // happened. Making a new suggestion potentially captures new LTZP status info.
+                GeolocationTimeZoneSuggestion suggestion =
+                        GeolocationTimeZoneSuggestion.createUncertainSuggestion(
+                                uncertaintyStartedElapsedMillis);
+                String debugInfo = "Uncertainty received from " + provider.getName() + ":"
+                        + " primary=" + mPrimaryProvider
+                        + ", secondary=" + mSecondaryProvider
+                        + ", uncertaintyStarted="
+                        + Duration.ofMillis(uncertaintyStartedElapsedMillis);
+                reportSuggestionEvent(suggestion, debugInfo);
+            } else {
+                debugLog("Starting uncertainty timeout: reason=" + reason);
 
-            Duration uncertaintyDelay = mEnvironment.getUncertaintyDelay();
-            mUncertaintyTimeoutQueue.runDelayed(
-                    () -> onProviderUncertaintyTimeout(
-                            provider, uncertaintyStartedElapsedMillis, uncertaintyDelay),
-                    uncertaintyDelay.toMillis());
+                Duration uncertaintyDelay = mEnvironment.getUncertaintyDelay();
+                mUncertaintyTimeoutQueue.runDelayed(
+                        () -> onProviderUncertaintyTimeout(
+                                provider, uncertaintyStartedElapsedMillis, uncertaintyDelay),
+                        uncertaintyDelay.toMillis());
+            }
         }
 
         if (provider == mPrimaryProvider) {
             // (Try to) start the secondary. It could already be started, or enabling might not
             // succeed if the provider has previously reported it is perm failed. The uncertainty
-            // timeout (set above) is used to ensure that an uncertain suggestion will be made if
-            // the secondary cannot generate a success event in time.
+            // timeout (may be set above) is used to ensure that an uncertain suggestion will be
+            // made if the secondary cannot generate a success event in time.
             tryStartProvider(mSecondaryProvider, mCurrentUserConfiguration);
         }
     }
diff --git a/services/core/java/com/android/server/tv/TvInputHardwareManager.java b/services/core/java/com/android/server/tv/TvInputHardwareManager.java
index d63a908..0ab6d57 100755
--- a/services/core/java/com/android/server/tv/TvInputHardwareManager.java
+++ b/services/core/java/com/android/server/tv/TvInputHardwareManager.java
@@ -396,6 +396,16 @@
         }
     }
 
+    public void updateInputInfo(TvInputInfo info) {
+        synchronized (mLock) {
+            if (!mInputMap.containsKey(info.getId())) {
+                return;
+            }
+            Slog.w(TAG, "update inputInfo for input id " + info.getId());
+            mInputMap.put(info.getId(), info);
+        }
+    }
+
     /**
      * Create a TvInputHardware object with a specific deviceId. One service at a time can access
      * the object, and if more than one process attempts to create hardware with the same deviceId,
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index a05bd2d..19ee554 100644
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -1067,6 +1067,11 @@
         }
         inputState.info = inputInfo;
         inputState.uid = getInputUid(inputInfo);
+        ServiceState serviceState = userState.serviceStateMap.get(inputInfo.getComponent());
+        if (serviceState != null && serviceState.isHardware) {
+            serviceState.hardwareInputMap.put(inputInfo.getId(), inputInfo);
+            mTvInputHardwareManager.updateInputInfo(inputInfo);
+        }
 
         int n = userState.mCallbacks.beginBroadcast();
         for (int i = 0; i < n; ++i) {
diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java b/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java
index 301e612..8e37527 100644
--- a/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java
+++ b/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java
@@ -82,6 +82,11 @@
     private Set<Integer> mUsingDemuxHandles = new HashSet<>();
 
     /**
+     * Client id sharee that has shared frontend with the current client.
+     */
+    private Integer mShareeFeClientId = INVALID_RESOURCE_ID;
+
+    /**
      * List of the Lnb handles that are used by the current client.
      */
     private Set<Integer> mUsingLnbHandles = new HashSet<>();
@@ -225,12 +230,21 @@
         return mShareFeClientIds;
     }
 
+    public Integer getShareeFeClientId() {
+        return mShareeFeClientId;
+    }
+
+    public void setShareeFeClientId(Integer shareeFeClientId) {
+        mShareeFeClientId = shareeFeClientId;
+    }
+
     /**
      * Called when the client released a frontend.
      */
     public void releaseFrontend() {
         mUsingFrontendHandles.clear();
         mShareFeClientIds.clear();
+        mShareeFeClientId = INVALID_RESOURCE_ID;
         mPrimaryUsingFrontendHandle = TunerResourceManager.INVALID_RESOURCE_HANDLE;
     }
 
diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
index 8f41608..c526016 100644
--- a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
+++ b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
@@ -1087,9 +1087,15 @@
         if (DEBUG) {
             Slog.d(TAG, "shareFrontend from " + selfClientId + " with " + targetClientId);
         }
+        Integer shareeFeClientId = getClientProfile(selfClientId).getShareeFeClientId();
+        if (shareeFeClientId != ClientProfile.INVALID_RESOURCE_ID) {
+            getClientProfile(shareeFeClientId).stopSharingFrontend(selfClientId);
+            getClientProfile(selfClientId).releaseFrontend();
+        }
         for (int feId : getClientProfile(targetClientId).getInUseFrontendHandles()) {
             getClientProfile(selfClientId).useFrontend(feId);
         }
+        getClientProfile(selfClientId).setShareeFeClientId(targetClientId);
         getClientProfile(targetClientId).shareFrontend(selfClientId);
     }
 
@@ -1099,6 +1105,8 @@
         // change the owner of all the inUse frontend
         newOwnerProfile.shareFrontend(currentOwnerId);
         currentOwnerProfile.stopSharingFrontend(newOwnerId);
+        newOwnerProfile.setShareeFeClientId(ClientProfile.INVALID_RESOURCE_ID);
+        currentOwnerProfile.setShareeFeClientId(newOwnerId);
         for (int inUseHandle : newOwnerProfile.getInUseFrontendHandles()) {
             getFrontendResource(inUseHandle).setOwner(newOwnerId);
         }
diff --git a/services/core/java/com/android/server/uri/TEST_MAPPING b/services/core/java/com/android/server/uri/TEST_MAPPING
index a9525f4a..6c36af5 100644
--- a/services/core/java/com/android/server/uri/TEST_MAPPING
+++ b/services/core/java/com/android/server/uri/TEST_MAPPING
@@ -9,7 +9,7 @@
             ]
         },
         {
-            "name": "CtsAppSecurityHostTestCases",
+            "name": "CtsStorageHostTestCases",
             "options": [
                 {
                     "include-filter": "android.appsecurity.cts.ExternalStorageHostTest#testGrantUriPermission"
diff --git a/services/core/java/com/android/server/utils/AlarmQueue.java b/services/core/java/com/android/server/utils/AlarmQueue.java
index 09ba195..83605ae 100644
--- a/services/core/java/com/android/server/utils/AlarmQueue.java
+++ b/services/core/java/com/android/server/utils/AlarmQueue.java
@@ -151,6 +151,10 @@
     @GuardedBy("mLock")
     @ElapsedRealtimeLong
     private long mTriggerTimeElapsed = NOT_SCHEDULED;
+    /** The last time an alarm went off (ie. the last time {@link #onAlarm()} was called}). */
+    @GuardedBy("mLock")
+    @ElapsedRealtimeLong
+    private long mLastFireTimeElapsed;
 
     /**
      * @param alarmTag               The tag to use when scheduling the alarm with AlarmManager.
@@ -284,7 +288,7 @@
     /** Sets an alarm with {@link AlarmManager} for the earliest alarm in the queue after now. */
     @GuardedBy("mLock")
     private void setNextAlarmLocked() {
-        setNextAlarmLocked(mInjector.getElapsedRealtime());
+        setNextAlarmLocked(mLastFireTimeElapsed + mMinTimeBetweenAlarmsMs);
     }
 
     /**
@@ -334,6 +338,7 @@
         final ArraySet<K> expired = new ArraySet<>();
         synchronized (mLock) {
             final long nowElapsed = mInjector.getElapsedRealtime();
+            mLastFireTimeElapsed = nowElapsed;
             while (mAlarmPriorityQueue.size() > 0) {
                 final Pair<K, Long> alarm = mAlarmPriorityQueue.peek();
                 if (alarm.second <= nowElapsed) {
diff --git a/services/core/java/com/android/server/utils/FoldSettingWrapper.java b/services/core/java/com/android/server/utils/FoldSettingWrapper.java
new file mode 100644
index 0000000..97a1ac0
--- /dev/null
+++ b/services/core/java/com/android/server/utils/FoldSettingWrapper.java
@@ -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.server.utils;
+
+import android.content.ContentResolver;
+import android.provider.Settings;
+
+/**
+ * A wrapper class for the {@link Settings.System#STAY_AWAKE_ON_FOLD} setting.
+ *
+ * This class provides a convenient way to access the {@link Settings.System#STAY_AWAKE_ON_FOLD}
+ * setting for testing.
+ */
+public class FoldSettingWrapper {
+    private final ContentResolver mContentResolver;
+
+    public FoldSettingWrapper(ContentResolver contentResolver) {
+        mContentResolver = contentResolver;
+    }
+
+    /**
+     * Returns whether the device should remain awake after folding.
+     */
+    public boolean shouldStayAwakeOnFold() {
+        try {
+            return (Settings.System.getIntForUser(
+                    mContentResolver,
+                    Settings.System.STAY_AWAKE_ON_FOLD,
+                    0) == 1);
+        } catch (Settings.SettingNotFoundException e) {
+            return false;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
index a1ef537..d480ddb 100644
--- a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
+++ b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
@@ -1889,7 +1889,7 @@
                     mIpSecManager.applyTunnelModeTransform(
                             tunnelIface, IpSecManager.DIRECTION_FWD, transform);
                 }
-            } catch (IOException e) {
+            } catch (IOException | IllegalArgumentException e) {
                 logInfo("Transform application failed for network " + token, e);
                 sessionLost(token, e);
             }
diff --git a/services/core/java/com/android/server/vibrator/ClippingAmplitudeAndFrequencyAdapter.java b/services/core/java/com/android/server/vibrator/ClippingAmplitudeAndFrequencyAdapter.java
index 160f4f9..48d477c 100644
--- a/services/core/java/com/android/server/vibrator/ClippingAmplitudeAndFrequencyAdapter.java
+++ b/services/core/java/com/android/server/vibrator/ClippingAmplitudeAndFrequencyAdapter.java
@@ -18,7 +18,6 @@
 
 import android.os.VibratorInfo;
 import android.os.vibrator.RampSegment;
-import android.os.vibrator.StepSegment;
 import android.os.vibrator.VibrationEffectSegment;
 import android.util.MathUtils;
 import android.util.Range;
@@ -26,37 +25,28 @@
 import java.util.List;
 
 /**
- * Adapter that clips frequency values to the ones specified by the
- * {@link VibratorInfo.FrequencyProfile}.
+ * Adapter that clips frequency values to the supported range specified by
+ * {@link VibratorInfo.FrequencyProfile}, then clips amplitude values to the max supported one at
+ * each frequency.
  *
- * <p>Devices with no frequency control will collapse all frequencies to the resonant frequency and
- * leave amplitudes unchanged.
+ * <p>The {@link VibratorInfo.FrequencyProfile} is only applicable to PWLE compositions. This
+ * adapter is only applied to {@link RampSegment} and leaves all other segments unchanged.
  */
-final class ClippingAmplitudeAndFrequencyAdapter
-        implements VibrationEffectAdapters.SegmentsAdapter<VibratorInfo> {
+final class ClippingAmplitudeAndFrequencyAdapter implements VibrationSegmentsAdapter {
 
     @Override
-    public int apply(List<VibrationEffectSegment> segments, int repeatIndex, VibratorInfo info) {
+    public int adaptToVibrator(VibratorInfo info, List<VibrationEffectSegment> segments,
+            int repeatIndex) {
         int segmentCount = segments.size();
         for (int i = 0; i < segmentCount; i++) {
             VibrationEffectSegment segment = segments.get(i);
-            if (segment instanceof StepSegment) {
-                segments.set(i, apply((StepSegment) segment, info));
-            } else if (segment instanceof RampSegment) {
+            if (segment instanceof RampSegment) {
                 segments.set(i, apply((RampSegment) segment, info));
             }
         }
         return repeatIndex;
     }
 
-    private StepSegment apply(StepSegment segment, VibratorInfo info) {
-        float clampedFrequency = clampFrequency(info, segment.getFrequencyHz());
-        return new StepSegment(
-                clampAmplitude(info, clampedFrequency, segment.getAmplitude()),
-                clampedFrequency,
-                (int) segment.getDuration());
-    }
-
     private RampSegment apply(RampSegment segment, VibratorInfo info) {
         float clampedStartFrequency = clampFrequency(info, segment.getStartFrequencyHz());
         float clampedEndFrequency = clampFrequency(info, segment.getEndFrequencyHz());
@@ -71,7 +61,7 @@
     private float clampFrequency(VibratorInfo info, float frequencyHz) {
         Range<Float> frequencyRangeHz = info.getFrequencyProfile().getFrequencyRangeHz();
         if (frequencyHz == 0 || frequencyRangeHz == null)  {
-            return info.getResonantFrequencyHz();
+            return Float.isNaN(info.getResonantFrequencyHz()) ? 0 : info.getResonantFrequencyHz();
         }
         return frequencyRangeHz.clamp(frequencyHz);
     }
diff --git a/services/core/java/com/android/server/vibrator/ComposePwleVibratorStep.java b/services/core/java/com/android/server/vibrator/ComposePwleVibratorStep.java
index 7100ffd..5d572be6 100644
--- a/services/core/java/com/android/server/vibrator/ComposePwleVibratorStep.java
+++ b/services/core/java/com/android/server/vibrator/ComposePwleVibratorStep.java
@@ -19,7 +19,6 @@
 import android.os.Trace;
 import android.os.VibrationEffect;
 import android.os.vibrator.RampSegment;
-import android.os.vibrator.StepSegment;
 import android.os.vibrator.VibrationEffectSegment;
 import android.util.Slog;
 
@@ -30,7 +29,7 @@
  * Represents a step to turn the vibrator on using a composition of PWLE segments.
  *
  * <p>This step will use the maximum supported number of consecutive segments of type
- * {@link StepSegment} or {@link RampSegment} starting at the current index.
+ * {@link RampSegment}, starting at the current index.
  */
 final class ComposePwleVibratorStep extends AbstractVibratorStep {
     /**
diff --git a/services/core/java/com/android/server/vibrator/DeviceAdapter.java b/services/core/java/com/android/server/vibrator/DeviceAdapter.java
new file mode 100644
index 0000000..41649fa
--- /dev/null
+++ b/services/core/java/com/android/server/vibrator/DeviceAdapter.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.vibrator;
+
+import android.annotation.NonNull;
+import android.os.CombinedVibration;
+import android.os.VibrationEffect;
+import android.os.VibratorInfo;
+import android.os.vibrator.VibrationEffectSegment;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Adapts a {@link CombinedVibration} to a device by transforming each {@link VibrationEffect} to
+ * the available device vibrator capabilities defined by {@link VibratorInfo}.
+ */
+final class DeviceAdapter implements CombinedVibration.VibratorAdapter {
+    private static final String TAG = "DeviceAdapter";
+
+    /**
+     * The VibratorController.getInfo might trigger HAL method calls, so just keep a reference to
+     * the system controllers until the adaptor is triggered by the VibrationThread.
+     */
+    private final SparseArray<VibratorController> mAvailableVibrators;
+    private final int[] mAvailableVibratorIds;
+
+    /**
+     * The actual adapters that can replace VibrationEffectSegment entries from a list based on the
+     * VibratorInfo. They can be applied in a chain to a mutable list before a new VibrationEffect
+     * instance is created with the final segment list.
+     */
+    private final List<VibrationSegmentsAdapter> mSegmentAdapters;
+
+    DeviceAdapter(VibrationSettings settings, SparseArray<VibratorController> vibrators) {
+        mSegmentAdapters = Arrays.asList(
+                // TODO(b/167947076): add filter that removes unsupported primitives
+                // TODO(b/167947076): add filter that replaces unsupported prebaked with fallback
+                new RampToStepAdapter(settings.getRampStepDuration()),
+                new StepToRampAdapter(),
+                new RampDownAdapter(settings.getRampDownDuration(), settings.getRampStepDuration()),
+                new ClippingAmplitudeAndFrequencyAdapter()
+        );
+        mAvailableVibrators = vibrators;
+        mAvailableVibratorIds = new int[vibrators.size()];
+        for (int i = 0; i < vibrators.size(); i++) {
+            mAvailableVibratorIds[i] = vibrators.keyAt(i);
+        }
+    }
+
+    SparseArray<VibratorController> getAvailableVibrators() {
+        return mAvailableVibrators;
+    }
+
+    @Override
+    public int[] getAvailableVibratorIds() {
+        return mAvailableVibratorIds;
+    }
+
+    @NonNull
+    @Override
+    public VibrationEffect adaptToVibrator(int vibratorId, @NonNull VibrationEffect effect) {
+        if (!(effect instanceof VibrationEffect.Composed)) {
+            // Segments adapters can only apply to Composed effects.
+            Slog.wtf(TAG, "Error adapting unsupported vibration effect: " + effect);
+            return effect;
+        }
+
+        VibratorController controller = mAvailableVibrators.get(vibratorId);
+        if (controller == null) {
+            // Effect mapped to nonexistent vibrator, skip adapter.
+            return effect;
+        }
+
+        VibratorInfo info = controller.getVibratorInfo();
+        VibrationEffect.Composed composed = (VibrationEffect.Composed) effect;
+        List<VibrationEffectSegment> newSegments = new ArrayList<>(composed.getSegments());
+        int newRepeatIndex = composed.getRepeatIndex();
+
+        int adapterCount = mSegmentAdapters.size();
+        for (int i = 0; i < adapterCount; i++) {
+            newRepeatIndex =
+                    mSegmentAdapters.get(i).adaptToVibrator(info, newSegments, newRepeatIndex);
+        }
+
+        return new VibrationEffect.Composed(newSegments, newRepeatIndex);
+    }
+}
diff --git a/services/core/java/com/android/server/vibrator/DeviceVibrationEffectAdapter.java b/services/core/java/com/android/server/vibrator/DeviceVibrationEffectAdapter.java
deleted file mode 100644
index 24da261..0000000
--- a/services/core/java/com/android/server/vibrator/DeviceVibrationEffectAdapter.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.vibrator;
-
-import android.os.VibrationEffect;
-import android.os.VibratorInfo;
-
-import java.util.Arrays;
-import java.util.List;
-
-/** Adapts a {@link VibrationEffect} to a specific device, taking into account its capabilities. */
-final class DeviceVibrationEffectAdapter
-        implements VibrationEffectAdapters.EffectAdapter<VibratorInfo> {
-
-    private final List<VibrationEffectAdapters.SegmentsAdapter<VibratorInfo>> mSegmentAdapters;
-
-    DeviceVibrationEffectAdapter(VibrationSettings settings) {
-        mSegmentAdapters = Arrays.asList(
-                // TODO(b/167947076): add filter that removes unsupported primitives
-                // TODO(b/167947076): add filter that replaces unsupported prebaked with fallback
-                new RampToStepAdapter(settings.getRampStepDuration()),
-                new StepToRampAdapter(),
-                new RampDownAdapter(settings.getRampDownDuration(), settings.getRampStepDuration()),
-                new ClippingAmplitudeAndFrequencyAdapter()
-        );
-    }
-
-    @Override
-    public VibrationEffect apply(VibrationEffect effect, VibratorInfo info) {
-        return VibrationEffectAdapters.apply(effect, mSegmentAdapters, info);
-    }
-}
diff --git a/services/core/java/com/android/server/vibrator/HalVibration.java b/services/core/java/com/android/server/vibrator/HalVibration.java
index b803273..743d02d 100644
--- a/services/core/java/com/android/server/vibrator/HalVibration.java
+++ b/services/core/java/com/android/server/vibrator/HalVibration.java
@@ -26,9 +26,8 @@
 
 import com.android.internal.util.FrameworkStatsLog;
 
-import java.util.List;
+import java.util.Objects;
 import java.util.concurrent.CountDownLatch;
-import java.util.function.Function;
 
 /**
  * Represents a vibration defined by a {@link CombinedVibration} that will be performed by
@@ -38,26 +37,28 @@
 
     public final SparseArray<VibrationEffect> mFallbacks = new SparseArray<>();
 
-    /** The actual effect to be played. */
-    @Nullable
-    private CombinedVibration mEffect;
+    /** A {@link CountDownLatch} to enable waiting for completion. */
+    private final CountDownLatch mCompletionLatch = new CountDownLatch(1);
+
+    /** The original effect that was requested, for debugging purposes. */
+    @NonNull
+    private final CombinedVibration mOriginalEffect;
 
     /**
-     * The original effect that was requested. Typically these two things differ because the effect
-     * was scaled based on the users vibration intensity settings.
+     * The scaled and adapted effect to be played. This should only be updated from a single thread,
+     * but can be read from different ones for debugging purposes.
      */
-    @Nullable
-    private CombinedVibration mOriginalEffect;
+    @NonNull
+    private volatile CombinedVibration mEffectToPlay;
 
     /** Vibration status. */
     private Vibration.Status mStatus;
 
-    /** A {@link CountDownLatch} to enable waiting for completion. */
-    private final CountDownLatch mCompletionLatch = new CountDownLatch(1);
-
-    HalVibration(@NonNull IBinder token, CombinedVibration effect, @NonNull CallerInfo callerInfo) {
+    HalVibration(@NonNull IBinder token, @NonNull CombinedVibration effect,
+            @NonNull CallerInfo callerInfo) {
         super(token, callerInfo);
-        this.mEffect = effect;
+        mOriginalEffect = effect;
+        mEffectToPlay = effect;
         mStatus = Vibration.Status.RUNNING;
     }
 
@@ -101,53 +102,35 @@
     }
 
     /**
-     * Applied update function to the current effect held by this vibration, and to each fallback
-     * effect added.
+     * Scales the {@link #getEffectToPlay()} and each fallback effect with a scaling transformation.
+     *
+     * @param scaler A {@link VibrationEffect.Transformation<Integer>} that takes one of the
+     *               {@code VibrationAttributes.USAGE_*} as the modifier to scale the effect
+     *               based on the user settings.
      */
-    public void updateEffects(Function<VibrationEffect, VibrationEffect> updateFn) {
-        CombinedVibration newEffect = transformCombinedEffect(mEffect, updateFn);
-        if (!newEffect.equals(mEffect)) {
-            if (mOriginalEffect == null) {
-                mOriginalEffect = mEffect;
-            }
-            mEffect = newEffect;
+    public void scaleEffects(VibrationEffect.Transformation<Integer> scaler) {
+        int vibrationUsage = callerInfo.attrs.getUsage();
+        CombinedVibration newEffect = mEffectToPlay.transform(scaler, vibrationUsage);
+        if (!Objects.equals(mEffectToPlay, newEffect)) {
+            mEffectToPlay = newEffect;
         }
         for (int i = 0; i < mFallbacks.size(); i++) {
-            mFallbacks.setValueAt(i, updateFn.apply(mFallbacks.valueAt(i)));
+            mFallbacks.setValueAt(i, scaler.transform(mFallbacks.valueAt(i), vibrationUsage));
         }
     }
 
     /**
-     * Creates a new {@link CombinedVibration} by applying the given transformation function
-     * to each {@link VibrationEffect}.
+     * Adapts the {@link #getEffectToPlay()} to the device using given vibrator adapter.
+     *
+     * @param deviceAdapter A {@link CombinedVibration.VibratorAdapter} that transforms vibration
+     *                      effects to device vibrators based on its capabilities.
      */
-    private static CombinedVibration transformCombinedEffect(
-            CombinedVibration combinedEffect, Function<VibrationEffect, VibrationEffect> fn) {
-        if (combinedEffect instanceof CombinedVibration.Mono) {
-            VibrationEffect effect = ((CombinedVibration.Mono) combinedEffect).getEffect();
-            return CombinedVibration.createParallel(fn.apply(effect));
-        } else if (combinedEffect instanceof CombinedVibration.Stereo) {
-            SparseArray<VibrationEffect> effects =
-                    ((CombinedVibration.Stereo) combinedEffect).getEffects();
-            CombinedVibration.ParallelCombination combination =
-                    CombinedVibration.startParallel();
-            for (int i = 0; i < effects.size(); i++) {
-                combination.addVibrator(effects.keyAt(i), fn.apply(effects.valueAt(i)));
-            }
-            return combination.combine();
-        } else if (combinedEffect instanceof CombinedVibration.Sequential) {
-            List<CombinedVibration> effects =
-                    ((CombinedVibration.Sequential) combinedEffect).getEffects();
-            CombinedVibration.SequentialCombination combination =
-                    CombinedVibration.startSequential();
-            for (CombinedVibration effect : effects) {
-                combination.addNext(transformCombinedEffect(effect, fn));
-            }
-            return combination.combine();
-        } else {
-            // Unknown combination, return same effect.
-            return combinedEffect;
+    public void adaptToDevice(CombinedVibration.VibratorAdapter deviceAdapter) {
+        CombinedVibration newEffect = mEffectToPlay.adapt(deviceAdapter);
+        if (!Objects.equals(mEffectToPlay, newEffect)) {
+            mEffectToPlay = newEffect;
         }
+        // No need to update fallback effects, they are already configured per device.
     }
 
     /** Return true is current status is different from {@link Status#RUNNING}. */
@@ -157,21 +140,21 @@
 
     @Override
     public boolean isRepeating() {
-        return mEffect.getDuration() == Long.MAX_VALUE;
+        return mOriginalEffect.getDuration() == Long.MAX_VALUE;
     }
 
     /** Return the effect that should be played by this vibration. */
-    @Nullable
-    public CombinedVibration getEffect() {
-        return mEffect;
+    public CombinedVibration getEffectToPlay() {
+        return mEffectToPlay;
     }
 
-    /**
-     * Return {@link Vibration.DebugInfo} with read-only debug information about this vibration.
-     */
+    /** Return {@link Vibration.DebugInfo} with read-only debug information about this vibration. */
     public Vibration.DebugInfo getDebugInfo() {
-        return new Vibration.DebugInfo(mStatus, stats, mEffect, mOriginalEffect, /* scale= */ 0,
-                callerInfo);
+        // Clear the original effect if it's the same as the effect that was played, for simplicity
+        CombinedVibration originalEffect =
+                Objects.equals(mOriginalEffect, mEffectToPlay) ? null : mOriginalEffect;
+        return new Vibration.DebugInfo(mStatus, stats, mEffectToPlay, originalEffect,
+                /* scale= */ 0, callerInfo);
     }
 
     /** Return {@link VibrationStats.StatsInfo} with read-only metrics about this vibration. */
diff --git a/services/core/java/com/android/server/vibrator/OWNERS b/services/core/java/com/android/server/vibrator/OWNERS
index 08f0a90..9afa682 100644
--- a/services/core/java/com/android/server/vibrator/OWNERS
+++ b/services/core/java/com/android/server/vibrator/OWNERS
@@ -1,3 +1,6 @@
+# Bug component: 345036
+
 lsandrade@google.com
 michaelwr@google.com
-sbowden@google.com
\ No newline at end of file
+sbowden@google.com
+khalilahmad@google.com
\ No newline at end of file
diff --git a/services/core/java/com/android/server/vibrator/RampDownAdapter.java b/services/core/java/com/android/server/vibrator/RampDownAdapter.java
index 8fec162..3d8d717 100644
--- a/services/core/java/com/android/server/vibrator/RampDownAdapter.java
+++ b/services/core/java/com/android/server/vibrator/RampDownAdapter.java
@@ -42,7 +42,7 @@
  * be used to bring the amplitude down to zero. This ensures that the transition from the last
  * amplitude to zero will be handled by the same vibrate method.
  */
-final class RampDownAdapter implements VibrationEffectAdapters.SegmentsAdapter<VibratorInfo> {
+final class RampDownAdapter implements VibrationSegmentsAdapter {
     private final int mRampDownDuration;
     private final int mStepDuration;
 
@@ -52,8 +52,8 @@
     }
 
     @Override
-    public int apply(List<VibrationEffectSegment> segments, int repeatIndex,
-            VibratorInfo info) {
+    public int adaptToVibrator(VibratorInfo info, List<VibrationEffectSegment> segments,
+            int repeatIndex) {
         if (mRampDownDuration <= 0) {
             // Nothing to do, no ramp down duration configured.
             return repeatIndex;
diff --git a/services/core/java/com/android/server/vibrator/RampToStepAdapter.java b/services/core/java/com/android/server/vibrator/RampToStepAdapter.java
index c943bb2..9e248cd 100644
--- a/services/core/java/com/android/server/vibrator/RampToStepAdapter.java
+++ b/services/core/java/com/android/server/vibrator/RampToStepAdapter.java
@@ -32,7 +32,7 @@
  *
  * <p>This leaves the list unchanged if the device has compose PWLE capability.
  */
-final class RampToStepAdapter implements VibrationEffectAdapters.SegmentsAdapter<VibratorInfo> {
+final class RampToStepAdapter implements VibrationSegmentsAdapter {
 
     private final int mStepDuration;
 
@@ -41,8 +41,8 @@
     }
 
     @Override
-    public int apply(List<VibrationEffectSegment> segments, int repeatIndex,
-            VibratorInfo info) {
+    public int adaptToVibrator(VibratorInfo info, List<VibrationEffectSegment> segments,
+            int repeatIndex) {
         if (info.hasCapability(IVibrator.CAP_COMPOSE_PWLE_EFFECTS)) {
             // The vibrator have PWLE capability, so keep the segments unchanged.
             return repeatIndex;
@@ -94,6 +94,9 @@
     }
 
     private static float fillEmptyFrequency(VibratorInfo info, float frequencyHz) {
+        if (Float.isNaN(info.getResonantFrequencyHz())) {
+            return 0;
+        }
         return frequencyHz == 0 ? info.getResonantFrequencyHz() : frequencyHz;
     }
 }
diff --git a/services/core/java/com/android/server/vibrator/SetAmplitudeVibratorStep.java b/services/core/java/com/android/server/vibrator/SetAmplitudeVibratorStep.java
index cce1ef4..6d01123 100644
--- a/services/core/java/com/android/server/vibrator/SetAmplitudeVibratorStep.java
+++ b/services/core/java/com/android/server/vibrator/SetAmplitudeVibratorStep.java
@@ -179,7 +179,9 @@
         while (i < segmentCount) {
             VibrationEffectSegment segment = segments.get(i);
             if (!(segment instanceof StepSegment)
-                    || ((StepSegment) segment).getAmplitude() == 0) {
+                    // play() will ignore segments with zero duration, so it's important that
+                    // zero-duration segments don't affect this method.
+                    || (segment.getDuration() > 0 && ((StepSegment) segment).getAmplitude() == 0)) {
                 break;
             }
             timing += segment.getDuration();
diff --git a/services/core/java/com/android/server/vibrator/StartSequentialEffectStep.java b/services/core/java/com/android/server/vibrator/StartSequentialEffectStep.java
index 15c60a3..c197271 100644
--- a/services/core/java/com/android/server/vibrator/StartSequentialEffectStep.java
+++ b/services/core/java/com/android/server/vibrator/StartSequentialEffectStep.java
@@ -22,7 +22,6 @@
 import android.os.SystemClock;
 import android.os.Trace;
 import android.os.VibrationEffect;
-import android.os.VibratorInfo;
 import android.os.vibrator.PrebakedSegment;
 import android.os.vibrator.PrimitiveSegment;
 import android.os.vibrator.StepSegment;
@@ -162,8 +161,7 @@
      * waveforms return {@link Long#MAX_VALUE}. Zero or negative values indicate the vibrators
      * have ignored all effects.
      */
-    private long startVibrating(
-            DeviceEffectMap effectMapping, List<Step> nextSteps) {
+    private long startVibrating(DeviceEffectMap effectMapping, List<Step> nextSteps) {
         int vibratorCount = effectMapping.size();
         if (vibratorCount == 0) {
             // No effect was mapped to any available vibrator.
@@ -257,17 +255,22 @@
 
         DeviceEffectMap(CombinedVibration.Mono mono) {
             SparseArray<VibratorController> vibrators = conductor.getVibrators();
-            mVibratorEffects = new SparseArray<>(vibrators.size());
-            mVibratorIds = new int[vibrators.size()];
-            for (int i = 0; i < vibrators.size(); i++) {
-                int vibratorId = vibrators.keyAt(i);
-                VibratorInfo vibratorInfo = vibrators.valueAt(i).getVibratorInfo();
-                VibrationEffect effect = conductor.deviceEffectAdapter.apply(
-                        mono.getEffect(), vibratorInfo);
-                if (effect instanceof VibrationEffect.Composed) {
-                    mVibratorEffects.put(vibratorId, (VibrationEffect.Composed) effect);
+            VibrationEffect effect = mono.getEffect();
+            if (effect instanceof VibrationEffect.Composed) {
+                mVibratorEffects = new SparseArray<>(vibrators.size());
+                mVibratorIds = new int[vibrators.size()];
+
+                VibrationEffect.Composed composedEffect = (VibrationEffect.Composed) effect;
+                for (int i = 0; i < vibrators.size(); i++) {
+                    int vibratorId = vibrators.keyAt(i);
+                    mVibratorEffects.put(vibratorId, composedEffect);
                     mVibratorIds[i] = vibratorId;
                 }
+            } else {
+                Slog.wtf(VibrationThread.TAG,
+                        "Unable to map device vibrators to unexpected effect: " + effect);
+                mVibratorEffects = new SparseArray<>();
+                mVibratorIds = new int[0];
             }
             mRequiredSyncCapabilities = calculateRequiredSyncCapabilities(mVibratorEffects);
         }
@@ -279,11 +282,12 @@
             for (int i = 0; i < stereoEffects.size(); i++) {
                 int vibratorId = stereoEffects.keyAt(i);
                 if (vibrators.contains(vibratorId)) {
-                    VibratorInfo vibratorInfo = vibrators.valueAt(i).getVibratorInfo();
-                    VibrationEffect effect = conductor.deviceEffectAdapter.apply(
-                            stereoEffects.valueAt(i), vibratorInfo);
+                    VibrationEffect effect = stereoEffects.valueAt(i);
                     if (effect instanceof VibrationEffect.Composed) {
                         mVibratorEffects.put(vibratorId, (VibrationEffect.Composed) effect);
+                    } else {
+                        Slog.wtf(VibrationThread.TAG,
+                                "Unable to map device vibrators to unexpected effect: " + effect);
                     }
                 }
             }
diff --git a/services/core/java/com/android/server/vibrator/StepToRampAdapter.java b/services/core/java/com/android/server/vibrator/StepToRampAdapter.java
index 86fc642..d86ee78 100644
--- a/services/core/java/com/android/server/vibrator/StepToRampAdapter.java
+++ b/services/core/java/com/android/server/vibrator/StepToRampAdapter.java
@@ -33,11 +33,11 @@
  * start and end amplitudes/frequencies, which can then be converted to PWLE compositions. This
  * adapter leaves the segments unchanged if the device doesn't have the PWLE composition capability.
  */
-final class StepToRampAdapter implements VibrationEffectAdapters.SegmentsAdapter<VibratorInfo> {
+final class StepToRampAdapter implements VibrationSegmentsAdapter {
 
     @Override
-    public int apply(List<VibrationEffectSegment> segments, int repeatIndex,
-            VibratorInfo info) {
+    public int adaptToVibrator(VibratorInfo info, List<VibrationEffectSegment> segments,
+            int repeatIndex) {
         if (!info.hasCapability(IVibrator.CAP_COMPOSE_PWLE_EFFECTS)) {
             // The vibrator does not have PWLE capability, so keep the segments unchanged.
             return repeatIndex;
@@ -148,6 +148,9 @@
     }
 
     private static float fillEmptyFrequency(VibratorInfo info, float frequencyHz) {
+        if (Float.isNaN(info.getResonantFrequencyHz())) {
+            return frequencyHz;
+        }
         return frequencyHz == 0 ? info.getResonantFrequencyHz() : frequencyHz;
     }
 }
diff --git a/services/core/java/com/android/server/vibrator/Vibration.java b/services/core/java/com/android/server/vibrator/Vibration.java
index c6bd80f..4f7f13e 100644
--- a/services/core/java/com/android/server/vibrator/Vibration.java
+++ b/services/core/java/com/android/server/vibrator/Vibration.java
@@ -31,6 +31,7 @@
 
 import java.text.SimpleDateFormat;
 import java.util.Date;
+import java.util.Locale;
 import java.util.Objects;
 import java.util.concurrent.atomic.AtomicInteger;
 
@@ -76,7 +77,8 @@
         IGNORED_FOR_RINGER_MODE(VibrationProto.IGNORED_FOR_RINGER_MODE),
         IGNORED_FOR_SETTINGS(VibrationProto.IGNORED_FOR_SETTINGS),
         IGNORED_SUPERSEDED(VibrationProto.IGNORED_SUPERSEDED),
-        IGNORED_FROM_VIRTUAL_DEVICE(VibrationProto.IGNORED_FROM_VIRTUAL_DEVICE);
+        IGNORED_FROM_VIRTUAL_DEVICE(VibrationProto.IGNORED_FROM_VIRTUAL_DEVICE),
+        IGNORED_ON_WIRELESS_CHARGER(VibrationProto.IGNORED_ON_WIRELESS_CHARGER);
 
         private final int mProtoEnumValue;
 
@@ -205,13 +207,13 @@
         private final long mStartTime;
         private final long mEndTime;
         private final long mDurationMs;
-        private final CombinedVibration mEffect;
-        private final CombinedVibration mOriginalEffect;
+        @Nullable private final CombinedVibration mOriginalEffect;
+        @Nullable private final CombinedVibration mPlayedEffect;
         private final float mScale;
         private final CallerInfo mCallerInfo;
         private final Status mStatus;
 
-        DebugInfo(Status status, VibrationStats stats, @Nullable CombinedVibration effect,
+        DebugInfo(Status status, VibrationStats stats, @Nullable CombinedVibration playedEffect,
                 @Nullable CombinedVibration originalEffect, float scale,
                 @NonNull CallerInfo callerInfo) {
             Objects.requireNonNull(callerInfo);
@@ -219,7 +221,7 @@
             mStartTime = stats.getStartTimeDebug();
             mEndTime = stats.getEndTimeDebug();
             mDurationMs = stats.getDurationDebug();
-            mEffect = effect;
+            mPlayedEffect = playedEffect;
             mOriginalEffect = originalEffect;
             mScale = scale;
             mCallerInfo = callerInfo;
@@ -228,27 +230,16 @@
 
         @Override
         public String toString() {
-            return new StringBuilder()
-                    .append("createTime: ")
-                    .append(DEBUG_DATE_FORMAT.format(new Date(mCreateTime)))
-                    .append(", startTime: ")
-                    .append(DEBUG_DATE_FORMAT.format(new Date(mStartTime)))
-                    .append(", endTime: ")
-                    .append(mEndTime == 0 ? null
-                            : DEBUG_DATE_FORMAT.format(new Date(mEndTime)))
-                    .append(", durationMs: ")
-                    .append(mDurationMs)
-                    .append(", status: ")
-                    .append(mStatus.name().toLowerCase())
-                    .append(", effect: ")
-                    .append(mEffect)
-                    .append(", originalEffect: ")
-                    .append(mOriginalEffect)
-                    .append(", scale: ")
-                    .append(String.format("%.2f", mScale))
-                    .append(", callerInfo: ")
-                    .append(mCallerInfo)
-                    .toString();
+            return "createTime: " + DEBUG_DATE_FORMAT.format(new Date(mCreateTime))
+                    + ", startTime: " + DEBUG_DATE_FORMAT.format(new Date(mStartTime))
+                    + ", endTime: "
+                    + (mEndTime == 0 ? null : DEBUG_DATE_FORMAT.format(new Date(mEndTime)))
+                    + ", durationMs: " + mDurationMs
+                    + ", status: " + mStatus.name().toLowerCase(Locale.ROOT)
+                    + ", playedEffect: " + mPlayedEffect
+                    + ", originalEffect: " + mOriginalEffect
+                    + ", scale: " + String.format(Locale.ROOT, "%.2f", mScale)
+                    + ", callerInfo: " + mCallerInfo;
         }
 
         /** Write this info into given {@code fieldId} on {@link ProtoOutputStream}. */
@@ -266,8 +257,8 @@
             proto.write(VibrationAttributesProto.FLAGS, attrs.getFlags());
             proto.end(attrsToken);
 
-            if (mEffect != null) {
-                dumpEffect(proto, VibrationProto.EFFECT, mEffect);
+            if (mPlayedEffect != null) {
+                dumpEffect(proto, VibrationProto.PLAYED_EFFECT, mPlayedEffect);
             }
             if (mOriginalEffect != null) {
                 dumpEffect(proto, VibrationProto.ORIGINAL_EFFECT, mOriginalEffect);
diff --git a/services/core/java/com/android/server/vibrator/VibrationEffectAdapters.java b/services/core/java/com/android/server/vibrator/VibrationEffectAdapters.java
deleted file mode 100644
index 446d981..0000000
--- a/services/core/java/com/android/server/vibrator/VibrationEffectAdapters.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.vibrator;
-
-import android.os.VibrationEffect;
-import android.os.vibrator.VibrationEffectSegment;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Helpers to adapt a {@link VibrationEffect} to generic modifiers (e.g. device capabilities,
- * user settings, etc).
- */
-public final class VibrationEffectAdapters {
-
-    /**
-     * Function that applies a generic modifier to a sequence of {@link VibrationEffectSegment}.
-     *
-     * @param <T> The type of modifiers this adapter accepts.
-     */
-    public interface SegmentsAdapter<T> {
-
-        /**
-         * Add and/or remove segments to the given {@link VibrationEffectSegment} list based on the
-         * given modifier.
-         *
-         * <p>This returns the new {@code repeatIndex} to be used together with the updated list to
-         * specify an equivalent {@link VibrationEffect}.
-         *
-         * @param segments    List of {@link VibrationEffectSegment} to be modified.
-         * @param repeatIndex Repeat index on the current segment list.
-         * @param modifier    The modifier to be applied to the sequence of segments.
-         * @return The new repeat index on the modifies list.
-         */
-        int apply(List<VibrationEffectSegment> segments, int repeatIndex, T modifier);
-    }
-
-    /**
-     * Function that applies a generic modifier to a {@link VibrationEffect}.
-     *
-     * @param <T> The type of modifiers this adapter accepts.
-     */
-    public interface EffectAdapter<T> {
-
-        /** Applies the modifier to given {@link VibrationEffect}, returning the new effect. */
-        VibrationEffect apply(VibrationEffect effect, T modifier);
-    }
-
-    /**
-     * Applies a sequence of {@link SegmentsAdapter} to the segments of a given
-     * {@link VibrationEffect}, in order.
-     *
-     * @param effect   The effect to be adapted to given modifier.
-     * @param adapters The sequence of adapters to be applied to given {@link VibrationEffect}.
-     * @param modifier The modifier to be passed to each adapter that describes the conditions the
-     *                 {@link VibrationEffect} needs to be adapted to (e.g. device capabilities,
-     *                 user settings, etc).
-     */
-    public static <T> VibrationEffect apply(VibrationEffect effect,
-            List<SegmentsAdapter<T>> adapters, T modifier) {
-        if (!(effect instanceof VibrationEffect.Composed)) {
-            // Segments adapters can only be applied to Composed effects.
-            return effect;
-        }
-
-        VibrationEffect.Composed composed = (VibrationEffect.Composed) effect;
-        List<VibrationEffectSegment> newSegments = new ArrayList<>(composed.getSegments());
-        int newRepeatIndex = composed.getRepeatIndex();
-
-        int adapterCount = adapters.size();
-        for (int i = 0; i < adapterCount; i++) {
-            newRepeatIndex = adapters.get(i).apply(newSegments, newRepeatIndex, modifier);
-        }
-
-        return new VibrationEffect.Composed(newSegments, newRepeatIndex);
-    }
-}
diff --git a/services/core/java/com/android/server/vibrator/VibrationScaler.java b/services/core/java/com/android/server/vibrator/VibrationScaler.java
index a528f06..59b55bf7 100644
--- a/services/core/java/com/android/server/vibrator/VibrationScaler.java
+++ b/services/core/java/com/android/server/vibrator/VibrationScaler.java
@@ -16,15 +16,19 @@
 
 package com.android.server.vibrator;
 
+import android.annotation.NonNull;
 import android.content.Context;
 import android.hardware.vibrator.V1_0.EffectStrength;
 import android.os.IExternalVibratorService;
 import android.os.VibrationEffect;
 import android.os.Vibrator;
 import android.os.vibrator.PrebakedSegment;
+import android.os.vibrator.VibrationEffectSegment;
 import android.util.Slog;
 import android.util.SparseArray;
 
+import java.util.ArrayList;
+
 /** Controls vibration scaling. */
 final class VibrationScaler {
     private static final String TAG = "VibrationScaler";
@@ -100,7 +104,14 @@
      * @return The same given effect, if no changes were made, or a new {@link VibrationEffect} with
      * resolved and scaled amplitude
      */
-    public <T extends VibrationEffect> T scale(VibrationEffect effect, int usageHint) {
+    @NonNull
+    public VibrationEffect scale(@NonNull VibrationEffect effect, int usageHint) {
+        if (!(effect instanceof VibrationEffect.Composed)) {
+            // This only scales composed vibration effects.
+            Slog.wtf(TAG, "Error scaling unsupported vibration effect: " + effect);
+            return effect;
+        }
+
         int defaultIntensity = mSettingsController.getDefaultIntensity(usageHint);
         int currentIntensity = mSettingsController.getCurrentIntensity(usageHint);
 
@@ -110,17 +121,36 @@
         }
 
         int newEffectStrength = intensityToEffectStrength(currentIntensity);
-        effect = effect.applyEffectStrength(newEffectStrength).resolve(mDefaultVibrationAmplitude);
-        ScaleLevel scale = mScaleLevels.get(currentIntensity - defaultIntensity);
+        ScaleLevel scaleLevel = mScaleLevels.get(currentIntensity - defaultIntensity);
 
-        if (scale == null) {
+        if (scaleLevel == null) {
             // Something about our scaling has gone wrong, so just play with no scaling.
             Slog.e(TAG, "No configured scaling level!"
                     + " (current=" + currentIntensity + ", default= " + defaultIntensity + ")");
-            return (T) effect;
         }
 
-        return (T) effect.scale(scale.factor);
+        VibrationEffect.Composed composedEffect = (VibrationEffect.Composed) effect;
+        ArrayList<VibrationEffectSegment> segments =
+                new ArrayList<>(composedEffect.getSegments());
+        int segmentCount = segments.size();
+        for (int i = 0; i < segmentCount; i++) {
+            VibrationEffectSegment segment = segments.get(i);
+            segment = segment.resolve(mDefaultVibrationAmplitude)
+                    .applyEffectStrength(newEffectStrength);
+            if (scaleLevel != null) {
+                segment = segment.scale(scaleLevel.factor);
+            }
+            segments.set(i, segment);
+        }
+        if (segments.equals(composedEffect.getSegments())) {
+            // No segment was updated, return original effect.
+            return effect;
+        }
+        VibrationEffect.Composed scaled =
+                new VibrationEffect.Composed(segments, composedEffect.getRepeatIndex());
+        // Make sure we validate what was scaled, since we're using the constructor directly
+        scaled.validate();
+        return scaled;
     }
 
     /**
diff --git a/services/core/java/com/android/server/vibrator/VibrationSegmentsAdapter.java b/services/core/java/com/android/server/vibrator/VibrationSegmentsAdapter.java
new file mode 100644
index 0000000..b0fc0bf
--- /dev/null
+++ b/services/core/java/com/android/server/vibrator/VibrationSegmentsAdapter.java
@@ -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.server.vibrator;
+
+import android.os.VibrationEffect;
+import android.os.VibratorInfo;
+import android.os.vibrator.VibrationEffectSegment;
+
+import java.util.List;
+
+/** Adapts a sequence of {@link VibrationEffectSegment} to a vibrator. */
+interface VibrationSegmentsAdapter {
+
+    /**
+     * Add and/or remove segments to the given {@link VibrationEffectSegment} list based on the
+     * given {@link VibratorInfo}.
+     *
+     * <p>This returns the new {@code repeatIndex} to be used together with the updated list to
+     * specify an equivalent {@link VibrationEffect}.
+     *
+     * @param info        The vibrator info to be applied to the sequence of segments.
+     * @param segments    List of {@link VibrationEffectSegment} to be modified.
+     * @param repeatIndex Repeat index on the current segment list.
+     * @return The new repeat index on the modifies list.
+     */
+    int adaptToVibrator(VibratorInfo info, List<VibrationEffectSegment> segments, int repeatIndex);
+}
diff --git a/services/core/java/com/android/server/vibrator/VibrationSettings.java b/services/core/java/com/android/server/vibrator/VibrationSettings.java
index 9cf0834..4ae7c77 100644
--- a/services/core/java/com/android/server/vibrator/VibrationSettings.java
+++ b/services/core/java/com/android/server/vibrator/VibrationSettings.java
@@ -40,6 +40,7 @@
 import android.database.ContentObserver;
 import android.media.AudioManager;
 import android.net.Uri;
+import android.os.BatteryManager;
 import android.os.Handler;
 import android.os.PowerManager;
 import android.os.PowerManagerInternal;
@@ -186,6 +187,8 @@
     private boolean mVibrateOn;
     @GuardedBy("mLock")
     private int mRingerMode;
+    @GuardedBy("mLock")
+    private boolean mOnWirelessCharger;
 
     VibrationSettings(Context context, Handler handler) {
         this(context, handler, new VibrationConfig(context.getResources()));
@@ -291,6 +294,25 @@
         registerSettingsObserver(
                 Settings.System.getUriFor(Settings.System.RING_VIBRATION_INTENSITY));
 
+        if (mVibrationConfig.ignoreVibrationsOnWirelessCharger()) {
+            Intent batteryStatus = mContext.registerReceiver(
+                    new BroadcastReceiver() {
+                        @Override
+                        public void onReceive(Context context, Intent intent) {
+                            updateBatteryInfo(intent);
+                        }
+                    },
+                    new IntentFilter(Intent.ACTION_BATTERY_CHANGED),
+                    Context.RECEIVER_NOT_EXPORTED);
+            // After registering the receiver for battery status, process the sticky broadcast that
+            // may have been returned upon registration of the receiver. This helps to capture the
+            // current charging state, and subsequent charging states can be listened to via the
+            // receiver registered.
+            if (batteryStatus != null) {
+                updateBatteryInfo(batteryStatus);
+            }
+        }
+
         // Update with newly loaded services.
         update();
     }
@@ -409,6 +431,10 @@
                     return Vibration.Status.IGNORED_FOR_RINGER_MODE;
                 }
             }
+
+            if (mVibrationConfig.ignoreVibrationsOnWirelessCharger() && mOnWirelessCharger) {
+                return Vibration.Status.IGNORED_ON_WIRELESS_CHARGER;
+            }
         }
         return null;
     }
@@ -544,6 +570,13 @@
         }
     }
 
+    private void updateBatteryInfo(Intent intent) {
+        int pluggedInfo = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0);
+        synchronized (mLock) {
+            mOnWirelessCharger = pluggedInfo == BatteryManager.BATTERY_PLUGGED_WIRELESS;
+        }
+    }
+
     @Override
     public String toString() {
         synchronized (mLock) {
diff --git a/services/core/java/com/android/server/vibrator/VibrationStepConductor.java b/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
index 4202afb..624da80 100644
--- a/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
+++ b/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
@@ -62,14 +62,14 @@
 
     // Used within steps.
     public final VibrationSettings vibrationSettings;
-    public final DeviceVibrationEffectAdapter deviceEffectAdapter;
     public final VibrationThread.VibratorManagerHooks vibratorManagerHooks;
 
-    // Not guarded by lock because they're not modified by this conductor, it's used here only to
-    // check immutable attributes. The status and other mutable states are changed by the service or
-    // by the vibrator steps.
+    private final DeviceAdapter mDeviceAdapter;
+
+    // Not guarded by lock because it's mostly used to read immutable fields by this conductor.
+    // This is only modified here at the prepareToStart method which always runs at the vibration
+    // thread, to update the adapted effect and report start time.
     private final HalVibration mVibration;
-    private final SparseArray<VibratorController> mVibrators = new SparseArray<>();
 
     private final PriorityQueue<Step> mNextSteps = new PriorityQueue<>();
     private final Queue<Step> mPendingOnVibratorCompleteSteps = new LinkedList<>();
@@ -96,21 +96,14 @@
     private int mSuccessfulVibratorOnSteps;
 
     VibrationStepConductor(HalVibration vib, VibrationSettings vibrationSettings,
-            DeviceVibrationEffectAdapter effectAdapter,
-            SparseArray<VibratorController> availableVibrators,
+            DeviceAdapter deviceAdapter,
             VibrationThread.VibratorManagerHooks vibratorManagerHooks) {
         this.mVibration = vib;
         this.vibrationSettings = vibrationSettings;
-        this.deviceEffectAdapter = effectAdapter;
+        this.mDeviceAdapter = deviceAdapter;
         this.vibratorManagerHooks = vibratorManagerHooks;
-
-        CombinedVibration effect = vib.getEffect();
-        for (int i = 0; i < availableVibrators.size(); i++) {
-            if (effect.hasVibrator(availableVibrators.keyAt(i))) {
-                mVibrators.put(availableVibrators.keyAt(i), availableVibrators.valueAt(i));
-            }
-        }
-        this.mSignalVibratorsComplete = new IntArray(mVibrators.size());
+        this.mSignalVibratorsComplete =
+                new IntArray(mDeviceAdapter.getAvailableVibratorIds().length);
     }
 
     @Nullable
@@ -150,7 +143,9 @@
         if (Build.IS_DEBUGGABLE) {
             expectIsVibrationThread(true);
         }
-        CombinedVibration.Sequential sequentialEffect = toSequential(mVibration.getEffect());
+        // Scaling happened before the effect was dispatched to this conductor (or to input devices)
+        mVibration.adaptToDevice(mDeviceAdapter);
+        CombinedVibration.Sequential sequentialEffect = toSequential(mVibration.getEffectToPlay());
         mPendingVibrateSteps++;
         // This count is decremented at the completion of the step, so we don't subtract one.
         mRemainingStartSequentialEffectSteps = sequentialEffect.getEffects().size();
@@ -167,7 +162,7 @@
 
     SparseArray<VibratorController> getVibrators() {
         // No thread assertion: immutable
-        return mVibrators;
+        return mDeviceAdapter.getAvailableVibrators();
     }
 
     public boolean isFinished() {
@@ -408,8 +403,8 @@
         }
 
         synchronized (mLock) {
-            for (int i = 0; i < mVibrators.size(); i++) {
-                mSignalVibratorsComplete.add(mVibrators.keyAt(i));
+            for (int vibratorId : mDeviceAdapter.getAvailableVibratorIds()) {
+                mSignalVibratorsComplete.add(vibratorId);
             }
             mLock.notify();
         }
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index cb7e54d..6fdb1db 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -54,6 +54,7 @@
 import android.os.VibratorInfo;
 import android.os.vibrator.PrebakedSegment;
 import android.os.vibrator.VibrationEffectSegment;
+import android.os.vibrator.persistence.VibrationXmlParser;
 import android.text.TextUtils;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -70,7 +71,9 @@
 import libcore.util.NativeAllocationRegistry;
 
 import java.io.FileDescriptor;
+import java.io.IOException;
 import java.io.PrintWriter;
+import java.io.StringReader;
 import java.lang.ref.WeakReference;
 import java.time.Duration;
 import java.util.ArrayList;
@@ -151,7 +154,7 @@
     private final VibrationSettings mVibrationSettings;
     private final VibrationScaler mVibrationScaler;
     private final InputDeviceDelegate mInputDeviceDelegate;
-    private final DeviceVibrationEffectAdapter mDeviceVibrationEffectAdapter;
+    private final DeviceAdapter mDeviceAdapter;
 
     private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
         @Override
@@ -196,7 +199,6 @@
         mVibrationSettings = new VibrationSettings(mContext, mHandler);
         mVibrationScaler = new VibrationScaler(mContext, mVibrationSettings);
         mInputDeviceDelegate = new InputDeviceDelegate(mContext, mHandler);
-        mDeviceVibrationEffectAdapter = new DeviceVibrationEffectAdapter(mVibrationSettings);
 
         VibrationCompleteListener listener = new VibrationCompleteListener(this);
         mNativeWrapper = injector.getNativeWrapper();
@@ -234,6 +236,9 @@
             }
         }
 
+        // Load vibrator adapter, that depends on hardware info.
+        mDeviceAdapter = new DeviceAdapter(mVibrationSettings, mVibrators);
+
         // Reset the hardware to a default state, in case this is a runtime restart instead of a
         // fresh boot.
         mNativeWrapper.cancelSynced();
@@ -298,20 +303,18 @@
         return controller.isVibratorInfoLoadSuccessful() ? info : null;
     }
 
+    @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_VIBRATOR_STATE)
     @Override // Binder call
     public boolean isVibrating(int vibratorId) {
-        mContext.enforceCallingOrSelfPermission(
-                android.Manifest.permission.ACCESS_VIBRATOR_STATE,
-                "isVibrating");
+        isVibrating_enforcePermission();
         VibratorController controller = mVibrators.get(vibratorId);
         return controller != null && controller.isVibrating();
     }
 
+    @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_VIBRATOR_STATE)
     @Override // Binder call
     public boolean registerVibratorStateListener(int vibratorId, IVibratorStateListener listener) {
-        mContext.enforceCallingOrSelfPermission(
-                android.Manifest.permission.ACCESS_VIBRATOR_STATE,
-                "registerVibratorStateListener");
+        registerVibratorStateListener_enforcePermission();
         VibratorController controller = mVibrators.get(vibratorId);
         if (controller == null) {
             return false;
@@ -319,12 +322,11 @@
         return controller.registerVibratorStateListener(listener);
     }
 
+    @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_VIBRATOR_STATE)
     @Override // Binder call
     public boolean unregisterVibratorStateListener(int vibratorId,
             IVibratorStateListener listener) {
-        mContext.enforceCallingOrSelfPermission(
-                android.Manifest.permission.ACCESS_VIBRATOR_STATE,
-                "unregisterVibratorStateListener");
+        unregisterVibratorStateListener_enforcePermission();
         VibratorController controller = mVibrators.get(vibratorId);
         if (controller == null) {
             return false;
@@ -412,7 +414,7 @@
             if (attrs.isFlagSet(VibrationAttributes.FLAG_INVALIDATE_SETTINGS_CACHE)) {
                 // Force update of user settings before checking if this vibration effect should
                 // be ignored or scaled.
-                mVibrationSettings.mSettingObserver.onChange(false);
+                mVibrationSettings.update();
             }
 
             synchronized (mLock) {
@@ -674,16 +676,16 @@
     private Vibration.EndInfo startVibrationLocked(HalVibration vib) {
         Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "startVibrationLocked");
         try {
-            vib.updateEffects(
-                    effect -> mVibrationScaler.scale(effect, vib.callerInfo.attrs.getUsage()));
+            // Scale effect before dispatching it to the input devices or the vibration thread.
+            vib.scaleEffects(mVibrationScaler::scale);
             boolean inputDevicesAvailable = mInputDeviceDelegate.vibrateIfAvailable(
-                    vib.callerInfo, vib.getEffect());
+                    vib.callerInfo, vib.getEffectToPlay());
             if (inputDevicesAvailable) {
                 return new Vibration.EndInfo(Vibration.Status.FORWARDED_TO_INPUT_DEVICES);
             }
 
             VibrationStepConductor conductor = new VibrationStepConductor(vib, mVibrationSettings,
-                    mDeviceVibrationEffectAdapter, mVibrators, mVibrationThreadCallbacks);
+                    mDeviceAdapter, mVibrationThreadCallbacks);
             if (mCurrentVibration == null) {
                 return startVibrationOnThreadLocked(conductor);
             }
@@ -1509,7 +1511,7 @@
         }
 
         public Vibration.DebugInfo getDebugInfo() {
-            return new Vibration.DebugInfo(mStatus, stats, /* effect= */ null,
+            return new Vibration.DebugInfo(mStatus, stats, /* playedEffect= */ null,
                     /* originalEffect= */ null, scale, callerInfo);
         }
 
@@ -1899,6 +1901,9 @@
                 if ("sequential".equals(cmd)) {
                     return runSequential();
                 }
+                if ("xml".equals(cmd)) {
+                    return runXml();
+                }
                 if ("cancel".equals(cmd)) {
                     return runCancel();
                 }
@@ -1973,6 +1978,14 @@
             return 0;
         }
 
+        private int runXml() {
+            CommonOptions commonOptions = new CommonOptions();
+            String xml = getNextArgRequired();
+            CombinedVibration vibration = parseXml(xml);
+            runVibrate(commonOptions, vibration);
+            return 0;
+        }
+
         private int runCancel() {
             // Cancel is only needed if the vibration was run in the background, otherwise it's
             // terminated by the shell command ending. In these cases, the token was that of the
@@ -2166,6 +2179,18 @@
                     .build();
         }
 
+        private CombinedVibration parseXml(String xml) {
+            try {
+                VibrationEffect effect = VibrationXmlParser.parse(new StringReader(xml));
+                if (effect == null) {
+                    throw new IllegalArgumentException("Error parsing vibration XML " + xml);
+                }
+                return CombinedVibration.createParallel(effect);
+            } catch (IOException e) {
+                throw new RuntimeException("Error parsing vibration XML " + xml, e);
+            }
+        }
+
         @Override
         public void onHelp() {
             try (PrintWriter pw = getOutPrintWriter();) {
@@ -2182,6 +2207,9 @@
                 pw.println("    Vibrates different effects on each vibrator in sync.");
                 pw.println("  sequential [options] (-v <vibrator-id> <effect>...)...");
                 pw.println("    Vibrates different effects on each vibrator in sequence.");
+                pw.println("  xml [options] <xml>");
+                pw.println("    Vibrates using combined vibration described in given XML string.");
+                pw.println("    XML containing a single effect it runs on all vibrators in sync.");
                 pw.println("  cancel");
                 pw.println("    Cancels any active vibration");
                 pw.println("");
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperCropper.java b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
index 49b125c..b773ade 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
@@ -81,7 +81,7 @@
         if (DEBUG) {
             Slog.v(TAG, "Generating crop for new wallpaper(s): 0x"
                     + Integer.toHexString(wallpaper.mWhich)
-                    + " to " + wallpaper.cropFile.getName()
+                    + " to " + wallpaper.getCropFile().getName()
                     + " crop=(" + cropHint.width() + 'x' + cropHint.height()
                     + ") dim=(" + wpData.mWidth + 'x' + wpData.mHeight + ')');
         }
@@ -89,7 +89,7 @@
         // Analyse the source; needed in multiple cases
         BitmapFactory.Options options = new BitmapFactory.Options();
         options.inJustDecodeBounds = true;
-        BitmapFactory.decodeFile(wallpaper.wallpaperFile.getAbsolutePath(), options);
+        BitmapFactory.decodeFile(wallpaper.getWallpaperFile().getAbsolutePath(), options);
         if (options.outWidth <= 0 || options.outHeight <= 0) {
             Slog.w(TAG, "Invalid wallpaper data");
             success = false;
@@ -154,11 +154,10 @@
                 //  may be we can try to remove this optimized way in the future,
                 //  that means, we will always go into the 'else' block.
 
-                success = FileUtils.copyFile(wallpaper.wallpaperFile, wallpaper.cropFile);
+                success = FileUtils.copyFile(wallpaper.getWallpaperFile(), wallpaper.getCropFile());
 
                 if (!success) {
-                    wallpaper.cropFile.delete();
-                    // TODO: fall back to default wallpaper in this case
+                    wallpaper.getCropFile().delete();
                 }
 
                 if (DEBUG) {
@@ -226,7 +225,7 @@
 
                     //Create a record file and will delete if ImageDecoder work well.
                     final String recordName =
-                            (wallpaper.wallpaperFile.getName().equals(WALLPAPER)
+                            (wallpaper.getWallpaperFile().getName().equals(WALLPAPER)
                                     ? RECORD_FILE : RECORD_LOCK_FILE);
                     final File record = new File(getWallpaperDir(wallpaper.userId), recordName);
                     record.createNewFile();
@@ -234,7 +233,7 @@
                             + ", record name =" + record.getName());
 
                     final ImageDecoder.Source srcData =
-                            ImageDecoder.createSource(wallpaper.wallpaperFile);
+                            ImageDecoder.createSource(wallpaper.getWallpaperFile());
                     final int sampleSize = scale;
                     Bitmap cropped = ImageDecoder.decodeBitmap(srcData, (decoder, info, src) -> {
                         decoder.setTargetSampleSize(sampleSize);
@@ -257,7 +256,7 @@
                                     + " h=" + finalCrop.getHeight());
                         }
 
-                        f = new FileOutputStream(wallpaper.cropFile);
+                        f = new FileOutputStream(wallpaper.getCropFile());
                         bos = new BufferedOutputStream(f, 32 * 1024);
                         finalCrop.compress(Bitmap.CompressFormat.PNG, 100, bos);
                         // don't rely on the implicit flush-at-close when noting success
@@ -277,11 +276,11 @@
 
         if (!success) {
             Slog.e(TAG, "Unable to apply new wallpaper");
-            wallpaper.cropFile.delete();
+            wallpaper.getCropFile().delete();
         }
 
-        if (wallpaper.cropFile.exists()) {
-            boolean didRestorecon = SELinux.restorecon(wallpaper.cropFile.getAbsoluteFile());
+        if (wallpaper.getCropFile().exists()) {
+            boolean didRestorecon = SELinux.restorecon(wallpaper.getCropFile().getAbsoluteFile());
             if (DEBUG) {
                 Slog.v(TAG, "restorecon() of crop file returned " + didRestorecon);
             }
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperData.java b/services/core/java/com/android/server/wallpaper/WallpaperData.java
index d87fca4..b0b66cf 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperData.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperData.java
@@ -40,10 +40,7 @@
  */
 class WallpaperData {
 
-    int userId;
-
-    final File wallpaperFile;   // source image
-    final File cropFile;        // eventual destination
+    final int userId;
 
     /**
      * True while the client is writing a new wallpaper
@@ -133,14 +130,13 @@
      */
     final Rect cropHint = new Rect(0, 0, 0, 0);
 
+    // map of which -> File
+    private final SparseArray<File> mWallpaperFiles = new SparseArray<>();
+    private final SparseArray<File> mCropFiles = new SparseArray<>();
+
     WallpaperData(int userId, @SetWallpaperFlags int wallpaperType) {
         this.userId = userId;
         this.mWhich = wallpaperType;
-        File wallpaperDir = getWallpaperDir(userId);
-        String wallpaperFileName = (wallpaperType == FLAG_LOCK) ? WALLPAPER_LOCK_ORIG : WALLPAPER;
-        String cropFileName = (wallpaperType == FLAG_LOCK) ? WALLPAPER_LOCK_CROP : WALLPAPER_CROP;
-        this.wallpaperFile = new File(wallpaperDir, wallpaperFileName);
-        this.cropFile = new File(wallpaperDir, cropFileName);
     }
 
     /**
@@ -154,8 +150,6 @@
      */
     WallpaperData(WallpaperData source) {
         this.userId = source.userId;
-        this.wallpaperFile = source.wallpaperFile;
-        this.cropFile = source.cropFile;
         this.wallpaperComponent = source.wallpaperComponent;
         this.mWhich = source.mWhich;
         this.wallpaperId = source.wallpaperId;
@@ -169,6 +163,25 @@
         }
     }
 
+    File getWallpaperFile() {
+        String fileName = mWhich == FLAG_LOCK ? WALLPAPER_LOCK_ORIG : WALLPAPER;
+        return getFile(mWallpaperFiles, fileName);
+    }
+
+    File getCropFile() {
+        String fileName = mWhich == FLAG_LOCK ? WALLPAPER_LOCK_CROP : WALLPAPER_CROP;
+        return getFile(mCropFiles, fileName);
+    }
+
+    private File getFile(SparseArray<File> map, String fileName) {
+        File result = map.get(mWhich);
+        if (result == null) {
+            result = new File(getWallpaperDir(userId), fileName);
+            map.put(userId, result);
+        }
+        return result;
+    }
+
     @Override
     public String toString() {
         StringBuilder out = new StringBuilder(defaultString(this));
@@ -177,7 +190,7 @@
         out.append(", which: ");
         out.append(mWhich);
         out.append(", file mod: ");
-        out.append(wallpaperFile != null ? wallpaperFile.lastModified() : "null");
+        out.append(getWallpaperFile() != null ? getWallpaperFile().lastModified() : "null");
         if (connection == null) {
             out.append(", no connection");
         } else {
@@ -202,10 +215,10 @@
 
     // Called during initialization of a given user's wallpaper bookkeeping
     boolean cropExists() {
-        return cropFile.exists();
+        return getCropFile().exists();
     }
 
     boolean sourceExists() {
-        return wallpaperFile.exists();
+        return getWallpaperFile().exists();
     }
 }
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java b/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
index 3e498d7..c54e3bd 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
@@ -87,7 +87,7 @@
         mImageWallpaper = ComponentName.unflattenFromString(
                 context.getResources().getString(R.string.image_wallpaper_component));
         mIsLockscreenLiveWallpaperEnabled =
-                SystemProperties.getBoolean("persist.wm.debug.lockscreen_live_wallpaper", false);
+                SystemProperties.getBoolean("persist.wm.debug.lockscreen_live_wallpaper", true);
     }
 
     private JournaledFile makeJournaledFile(int userId) {
@@ -542,12 +542,12 @@
                     }
 
                     res = r.openRawResource(resId);
-                    if (wallpaper.wallpaperFile.exists()) {
-                        wallpaper.wallpaperFile.delete();
-                        wallpaper.cropFile.delete();
+                    if (wallpaper.getWallpaperFile().exists()) {
+                        wallpaper.getWallpaperFile().delete();
+                        wallpaper.getCropFile().delete();
                     }
-                    fos = new FileOutputStream(wallpaper.wallpaperFile);
-                    cos = new FileOutputStream(wallpaper.cropFile);
+                    fos = new FileOutputStream(wallpaper.getWallpaperFile());
+                    cos = new FileOutputStream(wallpaper.getCropFile());
 
                     byte[] buffer = new byte[32768];
                     int amt;
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index cd3d603..7996434 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -45,6 +45,7 @@
 import android.app.ActivityOptions;
 import android.app.AppGlobals;
 import android.app.AppOpsManager;
+import android.app.ApplicationExitInfo;
 import android.app.ILocalWallpaperColorConsumer;
 import android.app.IWallpaperManager;
 import android.app.IWallpaperManagerCallback;
@@ -187,6 +188,10 @@
     /** Tracks wallpaper being migrated from system+lock to lock when setting static wp. */
     WallpaperDestinationChangeHandler mPendingMigrationViaStatic;
 
+    private static final double LMK_LOW_THRESHOLD_MEMORY_PERCENTAGE = 10;
+    private static final int LMK_RECONNECT_REBIND_RETRIES = 3;
+    private static final long LMK_RECONNECT_DELAY_MS = 5000;
+
     /**
      * Minimum time between crashes of a wallpaper service for us to consider
      * restarting it vs. just reverting to the static wallpaper.
@@ -366,6 +371,7 @@
                     if (lockedWallpaper != null) {
                         detachWallpaperLocked(lockedWallpaper);
                     }
+                    clearWallpaperBitmaps(mWallpaper.userId, FLAG_LOCK);
                     mLockWallpaperMap.remove(wallpaper.userId);
                     notifyColorsWhich |= FLAG_LOCK;
                 }
@@ -669,8 +675,8 @@
             // Not having a wallpaperComponent means it's a lock screen wallpaper.
             final boolean imageWallpaper = mImageWallpaper.equals(wallpaper.wallpaperComponent)
                     || wallpaper.wallpaperComponent == null;
-            if (imageWallpaper && wallpaper.cropFile != null && wallpaper.cropFile.exists()) {
-                cropFile = wallpaper.cropFile.getAbsolutePath();
+            if (imageWallpaper && wallpaper.getCropFile().exists()) {
+                cropFile = wallpaper.getCropFile().getAbsolutePath();
             } else if (imageWallpaper && !wallpaper.cropExists() && !wallpaper.sourceExists()) {
                 defaultImageWallpaper = true;
             }
@@ -949,7 +955,7 @@
             try {
                 connection.mService.attach(connection, mToken, TYPE_WALLPAPER, false,
                         wpdData.mWidth, wpdData.mHeight,
-                        wpdData.mPadding, mDisplayId, wallpaper.mWhich);
+                        wpdData.mPadding, mDisplayId, wallpaper.mWhich, connection.mInfo);
             } catch (RemoteException e) {
                 Slog.w(TAG, "Failed attaching wallpaper on display", e);
                 if (wallpaper != null && !wallpaper.wallpaperUpdating
@@ -988,6 +994,7 @@
         /** Time in milliseconds until we expect the wallpaper to reconnect (unless we're in the
          *  middle of an update). If exceeded, the wallpaper gets reset to the system default. */
         private static final long WALLPAPER_RECONNECT_TIMEOUT_MS = 10000;
+        private int mLmkLimitRebindRetries = LMK_RECONNECT_REBIND_RETRIES;
 
         final WallpaperInfo mInfo;
         IWallpaperService mService;
@@ -1209,20 +1216,51 @@
                             && mWallpaper.userId == mCurrentUserId
                             && !Objects.equals(mDefaultWallpaperComponent, wpService)
                             && !Objects.equals(mImageWallpaper, wpService)) {
-                        // There is a race condition which causes
-                        // {@link #mWallpaper.wallpaperUpdating} to be false even if it is
-                        // currently updating since the broadcast notifying us is async.
-                        // This race is overcome by the general rule that we only reset the
-                        // wallpaper if its service was shut down twice
-                        // during {@link #MIN_WALLPAPER_CRASH_TIME} millis.
-                        if (mWallpaper.lastDiedTime != 0
-                                && mWallpaper.lastDiedTime + MIN_WALLPAPER_CRASH_TIME
-                                > SystemClock.uptimeMillis()) {
-                            Slog.w(TAG, "Reverting to built-in wallpaper!");
-                            clearWallpaperLocked(true, FLAG_SYSTEM, mWallpaper.userId, null);
+                        List<ApplicationExitInfo> reasonList =
+                                mActivityManager.getHistoricalProcessExitReasons(
+                                wpService.getPackageName(), 0, 1);
+                        int exitReason = ApplicationExitInfo.REASON_UNKNOWN;
+                        if (reasonList != null && !reasonList.isEmpty()) {
+                            ApplicationExitInfo info = reasonList.get(0);
+                            exitReason = info.getReason();
+                        }
+                        Slog.d(TAG, "exitReason: " + exitReason);
+                        // If exit reason is LOW_MEMORY_KILLER
+                        // delay the mTryToRebindRunnable for 10s
+                        if (exitReason == ApplicationExitInfo.REASON_LOW_MEMORY) {
+                            if (isRunningOnLowMemory()) {
+                                Slog.i(TAG, "Rebind is delayed due to lmk");
+                                mContext.getMainThreadHandler().postDelayed(mTryToRebindRunnable,
+                                        LMK_RECONNECT_DELAY_MS);
+                                mLmkLimitRebindRetries = LMK_RECONNECT_REBIND_RETRIES;
+                            } else {
+                                if (mLmkLimitRebindRetries <= 0) {
+                                    Slog.w(TAG, "Reverting to built-in wallpaper due to lmk!");
+                                    clearWallpaperLocked(true, FLAG_SYSTEM, mWallpaper.userId,
+                                            null);
+                                    mLmkLimitRebindRetries = LMK_RECONNECT_REBIND_RETRIES;
+                                    return;
+                                }
+                                mLmkLimitRebindRetries--;
+                                mContext.getMainThreadHandler().postDelayed(mTryToRebindRunnable,
+                                        LMK_RECONNECT_DELAY_MS);
+                            }
                         } else {
-                            mWallpaper.lastDiedTime = SystemClock.uptimeMillis();
-                            tryToRebind();
+                            // There is a race condition which causes
+                            // {@link #mWallpaper.wallpaperUpdating} to be false even if it is
+                            // currently updating since the broadcast notifying us is async.
+                            // This race is overcome by the general rule that we only reset the
+                            // wallpaper if its service was shut down twice
+                            // during {@link #MIN_WALLPAPER_CRASH_TIME} millis.
+                            if (mWallpaper.lastDiedTime != 0
+                                    && mWallpaper.lastDiedTime + MIN_WALLPAPER_CRASH_TIME
+                                    > SystemClock.uptimeMillis()) {
+                                Slog.w(TAG, "Reverting to built-in wallpaper!");
+                                clearWallpaperLocked(true, FLAG_SYSTEM, mWallpaper.userId, null);
+                            } else {
+                                mWallpaper.lastDiedTime = SystemClock.uptimeMillis();
+                                tryToRebind();
+                            }
                         }
                     }
                 } else {
@@ -1233,6 +1271,14 @@
             }
         };
 
+        private boolean isRunningOnLowMemory() {
+            ActivityManager.MemoryInfo memoryInfo = new ActivityManager.MemoryInfo();
+            mActivityManager.getMemoryInfo(memoryInfo);
+            double availableMBsInPercentage = memoryInfo.availMem / (double)memoryInfo.totalMem *
+                    100.0;
+            return availableMBsInPercentage < LMK_LOW_THRESHOLD_MEMORY_PERCENTAGE;
+        }
+
         /**
          * Called by a live wallpaper if its colors have changed.
          * @param primaryColors representation of wallpaper primary colors
@@ -1417,6 +1463,10 @@
                 }
             }
 
+            synchronized (mLock) {
+                saveSettingsLocked(mNewWallpaper.userId);
+            }
+
             if (DEBUG) {
                 Slog.v(TAG, "--- wallpaper changed --");
                 Slog.v(TAG, "new sysWp: " + mWallpaperMap.get(mCurrentUserId));
@@ -1588,7 +1638,7 @@
         mShuttingDown = false;
         mImageWallpaper = ComponentName.unflattenFromString(
                 context.getResources().getString(R.string.image_wallpaper_component));
-        mDefaultWallpaperComponent = WallpaperManager.getDefaultWallpaperComponent(context);
+        mDefaultWallpaperComponent = WallpaperManager.getCmfDefaultWallpaperComponent(context);
         mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class);
         mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
         mIPackageManager = AppGlobals.getPackageManager();
@@ -1604,7 +1654,7 @@
                 mWallpaperCropper);
 
         mIsLockscreenLiveWallpaperEnabled =
-                SystemProperties.getBoolean("persist.wm.debug.lockscreen_live_wallpaper", false);
+                SystemProperties.getBoolean("persist.wm.debug.lockscreen_live_wallpaper", true);
         mIsMultiCropEnabled =
                 SystemProperties.getBoolean("persist.wm.debug.wallpaper_multi_crop", false);
         LocalServices.addService(WallpaperManagerInternal.class, new LocalService());
@@ -1775,21 +1825,23 @@
             if (record.exists()) {
                 Slog.w(TAG, "User:" + userID + ", wallpaper tyep = " + type
                         + ", wallpaper fail detect!! reset to default wallpaper");
-                clearWallpaperData(userID, type);
+                clearWallpaperBitmaps(userID, type);
                 record.delete();
             }
         });
     }
 
-    private void clearWallpaperData(int userID, int wallpaperType) {
+    private void clearWallpaperBitmaps(int userID, int wallpaperType) {
         final WallpaperData wallpaper = new WallpaperData(userID, wallpaperType);
-        if (wallpaper.sourceExists()) {
-            wallpaper.wallpaperFile.delete();
-        }
-        if (wallpaper.cropExists()) {
-            wallpaper.cropFile.delete();
-        }
+        clearWallpaperBitmaps(wallpaper);
+    }
 
+    private boolean clearWallpaperBitmaps(WallpaperData wallpaper) {
+        boolean sourceExists = wallpaper.sourceExists();
+        boolean cropExists = wallpaper.cropExists();
+        if (sourceExists) wallpaper.getWallpaperFile().delete();
+        if (cropExists) wallpaper.getCropFile().delete();
+        return sourceExists || cropExists;
     }
 
     @Override
@@ -1966,10 +2018,7 @@
 
         // files from the previous static wallpaper may still be stored in memory.
         // delete them in order to show the default wallpaper.
-        if (wallpaper.wallpaperFile.exists()) {
-            wallpaper.wallpaperFile.delete();
-            wallpaper.cropFile.delete();
-        }
+        clearWallpaperBitmaps(wallpaper);
 
         bindWallpaperComponentLocked(mImageWallpaper, true, false, fallback, reply);
         if ((wallpaper.mWhich & FLAG_SYSTEM) != 0) mHomeWallpaperWaitingForUnlock = true;
@@ -1988,7 +2037,11 @@
 
         WallpaperData data = null;
         synchronized (mLock) {
-            clearWallpaperLocked(false, which, userId, null);
+            if (mIsLockscreenLiveWallpaperEnabled) {
+                clearWallpaperLocked(callingPackage, false, which, userId);
+            } else {
+                clearWallpaperLocked(false, which, userId, null);
+            }
 
             if (which == FLAG_LOCK) {
                 data = mLockWallpaperMap.get(userId);
@@ -2005,7 +2058,64 @@
         }
     }
 
-    void clearWallpaperLocked(boolean defaultFailed, int which, int userId, IRemoteCallback reply) {
+    private void clearWallpaperLocked(String callingPackage, boolean defaultFailed,
+            int which, int userId) {
+
+        // Might need to bring it in the first time to establish our rewrite
+        if (!mWallpaperMap.contains(userId)) {
+            loadSettingsLocked(userId, false, FLAG_LOCK | FLAG_SYSTEM);
+        }
+        final WallpaperData wallpaper = mWallpaperMap.get(userId);
+        final WallpaperData lockWallpaper = mLockWallpaperMap.get(userId);
+        if (which == FLAG_LOCK && lockWallpaper == null) {
+            // It's already gone; we're done.
+            if (DEBUG) {
+                Slog.i(TAG, "Lock wallpaper already cleared");
+            }
+            return;
+        }
+
+        RuntimeException e = null;
+        try {
+            if (userId != mCurrentUserId && !hasCrossUserPermission()) return;
+
+            final ComponentName component;
+            final int finalWhich;
+
+            if ((which & FLAG_LOCK) > 0 && lockWallpaper != null) {
+                clearWallpaperBitmaps(lockWallpaper);
+            }
+            if ((which & FLAG_SYSTEM) > 0) {
+                clearWallpaperBitmaps(wallpaper);
+            }
+
+            // lock only case: set the system wallpaper component to both screens
+            if (which == FLAG_LOCK) {
+                component = wallpaper.wallpaperComponent;
+                finalWhich = FLAG_LOCK | FLAG_SYSTEM;
+            } else {
+                component = defaultFailed ? mImageWallpaper : null;
+                finalWhich = which;
+            }
+
+            boolean success = withCleanCallingIdentity(() -> setWallpaperComponent(
+                    component, callingPackage, finalWhich, userId));
+            if (success) return;
+        } catch (IllegalArgumentException e1) {
+            e = e1;
+        }
+
+        // This can happen if the default wallpaper component doesn't
+        // exist. This should be a system configuration problem, but
+        // let's not let it crash the system and just live with no
+        // wallpaper.
+        Slog.e(TAG, "Default wallpaper component not found!", e);
+        withCleanCallingIdentity(() -> clearWallpaperComponentLocked(wallpaper));
+    }
+
+    // TODO(b/266818039) remove this version of the method
+    private void clearWallpaperLocked(boolean defaultFailed, int which, int userId,
+            IRemoteCallback reply) {
         if (which != FLAG_SYSTEM && which != FLAG_LOCK) {
             throw new IllegalArgumentException("Must specify exactly one kind of wallpaper to clear");
         }
@@ -2034,9 +2144,7 @@
 
         final long ident = Binder.clearCallingIdentity();
         try {
-            if (wallpaper.wallpaperFile.exists()) {
-                wallpaper.wallpaperFile.delete();
-                wallpaper.cropFile.delete();
+            if (clearWallpaperBitmaps(wallpaper)) {
                 if (which == FLAG_LOCK) {
                     mLockWallpaperMap.remove(userId);
                     final IWallpaperManagerCallback cb = mKeyguardListener;
@@ -2116,8 +2224,8 @@
                     continue;
                 }
 
-                // ignore managed profiles
-                if (user.isManagedProfile()) {
+                // ignore profiles
+                if (user.isProfile()) {
                     continue;
                 }
                 WallpaperData wd = mWallpaperMap.get(user.id);
@@ -2331,13 +2439,13 @@
                     wallpaper.callbacks.register(cb);
                 }
 
-                File fileToReturn = getCropped ? wallpaper.cropFile : wallpaper.wallpaperFile;
+                File result = getCropped ? wallpaper.getCropFile() : wallpaper.getWallpaperFile();
 
-                if (!fileToReturn.exists()) {
+                if (!result.exists()) {
                     return null;
                 }
 
-                return ParcelFileDescriptor.open(fileToReturn, MODE_READ_ONLY);
+                return ParcelFileDescriptor.open(result, MODE_READ_ONLY);
             } catch (FileNotFoundException e) {
                 /* Shouldn't happen as we check to see if the file exists */
                 Slog.w(TAG, "Error getting wallpaper", e);
@@ -3101,19 +3209,24 @@
 
         // Migrate the bitmap files outright; no need to copy
         try {
-            Os.rename(sysWP.wallpaperFile.getAbsolutePath(), lockWP.wallpaperFile.getAbsolutePath());
-            Os.rename(sysWP.cropFile.getAbsolutePath(), lockWP.cropFile.getAbsolutePath());
+            if (!mIsLockscreenLiveWallpaperEnabled || sysWP.getWallpaperFile().exists()) {
+                Os.rename(sysWP.getWallpaperFile().getAbsolutePath(),
+                        lockWP.getWallpaperFile().getAbsolutePath());
+            }
+            if (!mIsLockscreenLiveWallpaperEnabled || sysWP.getCropFile().exists()) {
+                Os.rename(sysWP.getCropFile().getAbsolutePath(),
+                        lockWP.getCropFile().getAbsolutePath());
+            }
             mLockWallpaperMap.put(userId, lockWP);
             if (mIsLockscreenLiveWallpaperEnabled) {
-                SELinux.restorecon(lockWP.wallpaperFile);
+                SELinux.restorecon(lockWP.getWallpaperFile());
                 mLastLockWallpaper = lockWP;
             }
             return true;
         } catch (ErrnoException e) {
             // can happen when migrating default wallpaper (which is not stored in wallpaperFile)
             Slog.w(TAG, "Couldn't migrate system wallpaper: " + e.getMessage());
-            lockWP.wallpaperFile.delete();
-            lockWP.cropFile.delete();
+            clearWallpaperBitmaps(lockWP);
             return false;
         }
     }
@@ -3130,11 +3243,11 @@
                         FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH,
                         -1, -1);
             }
-            ParcelFileDescriptor fd = ParcelFileDescriptor.open(wallpaper.wallpaperFile,
+            ParcelFileDescriptor fd = ParcelFileDescriptor.open(wallpaper.getWallpaperFile(),
                     MODE_CREATE|MODE_READ_WRITE|MODE_TRUNCATE);
-            if (!SELinux.restorecon(wallpaper.wallpaperFile)) {
+            if (!SELinux.restorecon(wallpaper.getWallpaperFile())) {
                 Slog.w(TAG, "restorecon failed for wallpaper file: " +
-                        wallpaper.wallpaperFile.getPath());
+                        wallpaper.getWallpaperFile().getPath());
                 return null;
             }
             wallpaper.name = name;
@@ -3145,7 +3258,7 @@
             // Nullify field to require new computation
             wallpaper.primaryColors = null;
             Slog.v(TAG, "updateWallpaperBitmapLocked() : id=" + wallpaper.wallpaperId
-                    + " name=" + name + " file=" + wallpaper.wallpaperFile.getName());
+                    + " name=" + name + " file=" + wallpaper.getWallpaperFile().getName());
             return fd;
         } catch (FileNotFoundException e) {
             Slog.w(TAG, "Error setting wallpaper", e);
@@ -3169,16 +3282,17 @@
     }
 
     @VisibleForTesting
-    void setWallpaperComponent(ComponentName name, String callingPackage,
+    boolean setWallpaperComponent(ComponentName name, String callingPackage,
             @SetWallpaperFlags int which, int userId) {
         if (mIsLockscreenLiveWallpaperEnabled) {
-            setWallpaperComponentInternal(name, callingPackage, which, userId);
+            return setWallpaperComponentInternal(name, callingPackage, which, userId);
         } else {
             setWallpaperComponentInternalLegacy(name, callingPackage, which, userId);
+            return true;
         }
     }
 
-    private void setWallpaperComponentInternal(ComponentName name, String callingPackage,
+    private boolean setWallpaperComponentInternal(ComponentName name, String callingPackage,
             @SetWallpaperFlags int which, int userIdIn) {
         if (DEBUG) {
             Slog.v(TAG, "Setting new live wallpaper: which=" + which + ", component: " + name);
@@ -3189,6 +3303,7 @@
         checkPermission(android.Manifest.permission.SET_WALLPAPER_COMPONENT);
 
         boolean shouldNotifyColors = false;
+        boolean bindSuccess;
         final WallpaperData newWallpaper;
 
         synchronized (mLock) {
@@ -3237,7 +3352,7 @@
                  */
                 boolean forceRebind = same && systemIsBoth && which == FLAG_SYSTEM;
 
-                boolean bindSuccess = bindWallpaperComponentLocked(name, /* force */
+                bindSuccess = bindWallpaperComponentLocked(name, /* force */
                         forceRebind, /* fromUser */ true, newWallpaper, callback);
                 if (bindSuccess) {
                     if (!same) {
@@ -3256,6 +3371,11 @@
                             });
                         }
                     }
+                    boolean lockBitmapCleared = false;
+                    if (!mImageWallpaper.equals(newWallpaper.wallpaperComponent)) {
+                        clearWallpaperBitmaps(newWallpaper);
+                        lockBitmapCleared = newWallpaper.mWhich == FLAG_LOCK;
+                    }
                     newWallpaper.wallpaperId = makeWallpaperIdLocked();
                     notifyCallbacksLocked(newWallpaper);
                     shouldNotifyColors = true;
@@ -3272,6 +3392,9 @@
                                 updateEngineFlags(newWallpaper);
                             }
                         }
+                        if (!lockBitmapCleared) {
+                            clearWallpaperBitmaps(newWallpaper.userId, FLAG_LOCK);
+                        }
                         mLockWallpaperMap.remove(newWallpaper.userId);
                     }
                 }
@@ -3284,6 +3407,7 @@
             notifyWallpaperColorsChanged(newWallpaper, which);
             notifyWallpaperColorsChanged(mFallbackWallpaper, FLAG_SYSTEM);
         }
+        return bindSuccess;
     }
 
     // TODO(b/266818039) Remove this method
@@ -3472,7 +3596,9 @@
                 final int hasPrivilege = mIPackageManager.checkPermission(
                         android.Manifest.permission.AMBIENT_WALLPAPER, wi.getPackageName(),
                         serviceUserId);
-                if (hasPrivilege != PERMISSION_GRANTED) {
+                // All watch wallpapers support ambient mode by default.
+                if (hasPrivilege != PERMISSION_GRANTED
+                        && !mIPackageManager.hasSystemFeature(PackageManager.FEATURE_WATCH, 0)) {
                     String msg = "Selected service does not have "
                             + android.Manifest.permission.AMBIENT_WALLPAPER
                             + ": " + componentName;
@@ -3503,10 +3629,15 @@
             intent.putExtra(Intent.EXTRA_CLIENT_LABEL,
                     com.android.internal.R.string.wallpaper_binding_label);
             intent.putExtra(Intent.EXTRA_CLIENT_INTENT, clientIntent);
-            boolean bindSuccess = mContext.bindServiceAsUser(intent, newConn,
-                    Context.BIND_AUTO_CREATE | Context.BIND_SHOWING_UI
+            int bindFlags = Context.BIND_AUTO_CREATE | Context.BIND_SHOWING_UI
                             | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE
-                            | Context.BIND_INCLUDE_CAPABILITIES,
+                            | Context.BIND_INCLUDE_CAPABILITIES;
+
+            if (mContext.getResources().getBoolean(
+                    com.android.internal.R.bool.config_wallpaperTopApp)) {
+                bindFlags |= Context.BIND_SCHEDULE_LIKE_TOP_APP;
+            }
+            boolean bindSuccess = mContext.bindServiceAsUser(intent, newConn, bindFlags,
                     new UserHandle(serviceUserId));
             if (!bindSuccess) {
                 String msg = "Unable to bind service: " + componentName;
diff --git a/services/core/java/com/android/server/webkit/OWNERS b/services/core/java/com/android/server/webkit/OWNERS
index 00e540a..e7fd7a5 100644
--- a/services/core/java/com/android/server/webkit/OWNERS
+++ b/services/core/java/com/android/server/webkit/OWNERS
@@ -1,3 +1,3 @@
-changwan@google.com
-tobiasjs@google.com
+# Bug component: 76427
+ntfschr@google.com
 torne@google.com
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index f300113..b3ae2ee 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -1280,45 +1280,53 @@
                 }
 
                 void drawIfNeeded(SurfaceControl.Transaction t) {
+                    // Drawing variables (alpha, dirty rect, and bounds) access is synchronized
+                    // using WindowManagerGlobalLock. Grab copies of these values before
+                    // drawing on the canvas so that drawing can be performed outside of the lock.
+                    int alpha;
+                    Rect drawingRect = null;
+                    Region drawingBounds = null;
                     synchronized (mService.mGlobalLock) {
                         if (!mInvalidated) {
                             return;
                         }
                         mInvalidated = false;
-                        if (mAlpha > 0) {
-                            Canvas canvas = null;
-                            try {
-                                // Empty dirty rectangle means unspecified.
-                                if (mDirtyRect.isEmpty()) {
-                                    mBounds.getBounds(mDirtyRect);
-                                }
-                                mDirtyRect.inset(-mHalfBorderWidth, -mHalfBorderWidth);
-                                canvas = mSurface.lockCanvas(mDirtyRect);
-                                if (DEBUG_VIEWPORT_WINDOW) {
-                                    Slog.i(LOG_TAG, "Dirty rect: " + mDirtyRect);
-                                }
-                            } catch (IllegalArgumentException iae) {
-                                /* ignore */
-                            } catch (Surface.OutOfResourcesException oore) {
-                                /* ignore */
-                            }
-                            if (canvas == null) {
-                                return;
-                            }
-                            if (DEBUG_VIEWPORT_WINDOW) {
-                                Slog.i(LOG_TAG, "Bounds: " + mBounds);
-                            }
-                            canvas.drawColor(Color.TRANSPARENT, Mode.CLEAR);
-                            mPaint.setAlpha(mAlpha);
-                            Path path = mBounds.getBoundaryPath();
-                            canvas.drawPath(path, mPaint);
 
-                            mSurface.unlockCanvasAndPost(canvas);
-                            t.show(mSurfaceControl);
-                        } else {
-                            t.hide(mSurfaceControl);
+                        alpha = mAlpha;
+                        if (alpha > 0) {
+                            drawingBounds = new Region(mBounds);
+                            // Empty dirty rectangle means unspecified.
+                            if (mDirtyRect.isEmpty()) {
+                                mBounds.getBounds(mDirtyRect);
+                            }
+                            mDirtyRect.inset(-mHalfBorderWidth, -mHalfBorderWidth);
+                            drawingRect = new Rect(mDirtyRect);
+                            if (DEBUG_VIEWPORT_WINDOW) {
+                                Slog.i(LOG_TAG, "ViewportWindow bounds: " + mBounds);
+                                Slog.i(LOG_TAG, "ViewportWindow dirty rect: " + mDirtyRect);
+                            }
                         }
                     }
+
+                    // Draw without holding WindowManagerGlobalLock.
+                    if (alpha > 0) {
+                        Canvas canvas = null;
+                        try {
+                            canvas = mSurface.lockCanvas(drawingRect);
+                        } catch (IllegalArgumentException | OutOfResourcesException e) {
+                            /* ignore */
+                        }
+                        if (canvas == null) {
+                            return;
+                        }
+                        canvas.drawColor(Color.TRANSPARENT, Mode.CLEAR);
+                        mPaint.setAlpha(alpha);
+                        canvas.drawPath(drawingBounds.getBoundaryPath(), mPaint);
+                        mSurface.unlockCanvasAndPost(canvas);
+                        t.show(mSurfaceControl);
+                    } else {
+                        t.hide(mSurfaceControl);
+                    }
                 }
 
                 void releaseSurface() {
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 64b37d4..904d480 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -291,6 +291,7 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.ConstrainDisplayApisConfig;
 import android.content.pm.PackageManager;
+import android.content.pm.UserProperties;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
@@ -367,6 +368,7 @@
 import com.android.server.am.PendingIntentRecord;
 import com.android.server.contentcapture.ContentCaptureManagerInternal;
 import com.android.server.display.color.ColorDisplayService;
+import com.android.server.pm.UserManagerInternal;
 import com.android.server.uri.NeededUriGrants;
 import com.android.server.uri.UriPermissionOwner;
 import com.android.server.wm.ActivityMetricsLogger.TransitionInfoSnapshot;
@@ -722,6 +724,13 @@
     private boolean mInheritShownWhenLocked;
     private boolean mTurnScreenOn;
 
+    /**
+     * Whether the user is always-visible (e.g. a communal profile). Activities for such users
+     * are treated as visible regardless of what user is in the foreground, and can appear
+     * over the lockscreen.
+     */
+    private final boolean mIsUserAlwaysVisible;
+
     /** Allow activity launches which would otherwise be blocked by
      * {@link ActivityTransitionSecurityController#checkActivityAllowedToStart}
      */
@@ -2033,7 +2042,12 @@
                 .getBoolean(R.bool.config_isCameraCompatControlForStretchedIssuesEnabled);
 
         mTargetSdk = info.applicationInfo.targetSdkVersion;
-        mShowForAllUsers = (info.flags & FLAG_SHOW_FOR_ALL_USERS) != 0;
+
+        final UserManagerInternal umi = LocalServices.getService(UserManagerInternal.class);
+        final UserProperties properties = umi.getUserProperties(mUserId);
+        mIsUserAlwaysVisible =  properties != null && properties.getAlwaysVisible();
+
+        mShowForAllUsers = (info.flags & FLAG_SHOW_FOR_ALL_USERS) != 0 || mIsUserAlwaysVisible;
         setOrientation(info.screenOrientation);
         mRotationAnimationHint = info.rotationAnimation;
 
@@ -2115,8 +2129,7 @@
         hasBeenLaunched = false;
         mTaskSupervisor = supervisor;
 
-        info.taskAffinity = computeTaskAffinity(info.taskAffinity, info.applicationInfo.uid,
-                info.launchMode, mActivityComponent);
+        info.taskAffinity = computeTaskAffinity(info.taskAffinity, info.applicationInfo.uid);
         taskAffinity = info.taskAffinity;
         final String uid = Integer.toString(info.applicationInfo.uid);
         if (info.windowLayout != null && info.windowLayout.windowLayoutAffinity != null
@@ -2216,19 +2229,12 @@
      *
      * @param affinity The affinity of the activity.
      * @param uid The user-ID that has been assigned to this application.
-     * @param launchMode The activity launch mode
-     * @param componentName The activity component name. This is only useful when the given
-     *                      launchMode is {@link ActivityInfo#LAUNCH_SINGLE_INSTANCE}
      * @return The task affinity
      */
-    static String computeTaskAffinity(String affinity, int uid, int launchMode,
-            ComponentName componentName) {
+    static String computeTaskAffinity(String affinity, int uid) {
         final String uidStr = Integer.toString(uid);
         if (affinity != null && !affinity.startsWith(uidStr)) {
             affinity = uidStr + ":" + affinity;
-            if (launchMode == LAUNCH_SINGLE_INSTANCE && componentName != null) {
-                affinity += ":" + componentName.hashCode();
-            }
         }
         return affinity;
     }
@@ -4516,7 +4522,7 @@
                     mTransitionChangeFlags |= FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
                 }
                 // Post cleanup after the visibility and animation are transferred.
-                fromActivity.postWindowRemoveStartingWindowCleanup(tStartingWindow);
+                fromActivity.postWindowRemoveStartingWindowCleanup();
                 fromActivity.mVisibleSetFromTransferredStartingWindow = false;
 
                 mWmService.updateFocusedWindowLocked(
@@ -4560,6 +4566,26 @@
         }
         task.forAllActivities(fromActivity -> {
             if (fromActivity == this) return true;
+            // The snapshot starting window could remove itself when receive resized request without
+            // redraw, so transfer it to a different size activity could only cause flicker.
+            // By schedule remove snapshot starting window, the remove process will happen when
+            // transition ready, transition ready means the app window is drawn.
+            final StartingData tmpStartingData = fromActivity.mStartingData;
+            if (tmpStartingData != null && tmpStartingData.mAssociatedTask == null
+                    && mTransitionController.isCollecting(fromActivity)
+                    && tmpStartingData instanceof SnapshotStartingData) {
+                final Rect fromBounds = fromActivity.getBounds();
+                final Rect myBounds = getBounds();
+                if (!fromBounds.equals(myBounds)) {
+                    // Mark as no animation, so these changes won't merge into playing transition.
+                    if (mTransitionController.inPlayingTransition(fromActivity)) {
+                        mTransitionController.setNoAnimation(this);
+                        mTransitionController.setNoAnimation(fromActivity);
+                    }
+                    fromActivity.removeStartingWindow();
+                    return true;
+                }
+            }
             return !fromActivity.isVisibleRequested() && transferStartingWindow(fromActivity);
         });
     }
@@ -4629,7 +4655,8 @@
      * @return {@code true} if the activity windowing mode is not in
      *         {@link android.app.WindowConfiguration#WINDOWING_MODE_PINNED} and a) activity
      *         contains windows that have {@link LayoutParams#FLAG_SHOW_WHEN_LOCKED} set or if the
-     *         activity has set {@link #mShowWhenLocked}, or b) if the activity has set
+     *         activity has set {@link #mShowWhenLocked}, or if its user
+     *         is {@link #mIsUserAlwaysVisible always-visible} or b) if the activity has set
      *         {@link #mInheritShownWhenLocked} and the activity behind this satisfies the
      *         conditions a) above.
      *         Multi-windowing mode will be exited if {@code true} is returned.
@@ -4638,17 +4665,22 @@
         if (r == null || r.getTaskFragment() == null) {
             return false;
         }
-        if (!r.inPinnedWindowingMode() && (r.mShowWhenLocked || r.containsShowWhenLockedWindow())) {
+        if (canShowWhenLockedInner(r)) {
             return true;
         } else if (r.mInheritShownWhenLocked) {
             final ActivityRecord activity = r.getTaskFragment().getActivityBelow(r);
-            return activity != null && !activity.inPinnedWindowingMode()
-                    && (activity.mShowWhenLocked || activity.containsShowWhenLockedWindow());
+            return activity != null && canShowWhenLockedInner(activity);
         } else {
             return false;
         }
     }
 
+    /** @see #canShowWhenLocked(ActivityRecord) */
+    private static boolean canShowWhenLockedInner(@NonNull ActivityRecord r) {
+        return !r.inPinnedWindowingMode() &&
+                (r.mShowWhenLocked || r.containsShowWhenLockedWindow() || r.mIsUserAlwaysVisible);
+    }
+
     /**
      *  Determines if the activity can show while lock-screen is displayed. System displays
      *  activities while lock-screen is displayed only if all activities
@@ -5640,13 +5672,6 @@
         final DisplayContent displayContent = getDisplayContent();
         if (!visible) {
             mImeInsetsFrozenUntilStartInput = true;
-            if (usingShellTransitions) {
-                final WindowState wallpaperTarget =
-                        displayContent.mWallpaperController.getWallpaperTarget();
-                if (wallpaperTarget != null && wallpaperTarget.mActivityRecord == this) {
-                    displayContent.mWallpaperController.hideWallpapers(wallpaperTarget);
-                }
-            }
         }
 
         if (!displayContent.mClosingApps.contains(this)
@@ -7430,27 +7455,12 @@
         }
     }
 
-    void postWindowRemoveStartingWindowCleanup(WindowState win) {
-        // TODO: Something smells about the code below...Is there a better way?
-        if (mStartingWindow == win) {
-            ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Notify removed startingWindow %s", win);
-            removeStartingWindow();
-        } else if (mChildren.size() == 0) {
-            // If this is the last window and we had requested a starting transition window,
-            // well there is no point now.
-            ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Nulling last startingData");
-            mStartingData = null;
-            if (mVisibleSetFromTransferredStartingWindow) {
-                // We set the visible state to true for the token from a transferred starting
-                // window. We now reset it back to false since the starting window was the last
-                // window in the token.
-                setVisible(false);
-            }
-        } else if (mChildren.size() == 1 && mStartingSurface != null && !isRelaunching()) {
-            // If this is the last window except for a starting transition window,
-            // we need to get rid of the starting transition.
-            ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Last window, removing starting window %s", win);
-            removeStartingWindow();
+    void postWindowRemoveStartingWindowCleanup() {
+        if (mChildren.size() == 0 && mVisibleSetFromTransferredStartingWindow) {
+            // We set the visible state to true for the token from a transferred starting
+            // window. We now reset it back to false since the starting window was the last
+            // window in the token.
+            setVisible(false);
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index a0a45e6..0a62c88 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -104,7 +104,6 @@
 import android.app.WaitResult;
 import android.app.WindowConfiguration;
 import android.compat.annotation.ChangeId;
-import android.compat.annotation.Disabled;
 import android.compat.annotation.EnabledSince;
 import android.content.IIntentSender;
 import android.content.Intent;
@@ -189,7 +188,7 @@
      * Feature flag for go/activity-security rules
      */
     @ChangeId
-    @Disabled
+    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
     static final long ASM_RESTRICTIONS = 230590090L;
 
     private final ActivityTaskManagerService mService;
@@ -699,6 +698,10 @@
                         mRequest.intent, caller, callingUid);
             }
 
+            if (mRequest.intent != null) {
+                mRequest.componentSpecified |= mRequest.intent.getComponent() != null;
+            }
+
             // If the caller hasn't already resolved the activity, we're willing
             // to do so here. If the caller is already holding the WM lock here,
             // and we need to check dynamic Uri permissions, then we're forced
@@ -898,6 +901,11 @@
         }
         mLastStartReason = request.reason;
         mLastStartActivityTimeMs = System.currentTimeMillis();
+        // Reset the ActivityRecord#mCurrentLaunchCanTurnScreenOn state of last start activity in
+        // case the state is not yet consumed during rapid activity launch.
+        if (mLastStartActivityRecord != null) {
+            mLastStartActivityRecord.setCurrentLaunchCanTurnScreenOn(false);
+        }
         mLastStartActivityRecord = null;
 
         final IApplicationThread caller = request.caller;
@@ -1583,21 +1591,17 @@
                 newTransition = null;
             }
         }
-        if (isTransientLaunch) {
-            if (forceTransientTransition) {
-                transitionController.collect(mLastStartActivityRecord);
-                transitionController.collect(mPriorAboveTask);
-            }
-            // `started` isn't guaranteed to be the actual relevant activity, so we must wait
-            // until after we launched to identify the relevant activity.
+        if (forceTransientTransition) {
+            transitionController.collect(mLastStartActivityRecord);
+            transitionController.collect(mPriorAboveTask);
+            // If keyguard is active and occluded, the transient target won't be moved to front
+            // to be collected, so set transient again after it is collected.
             transitionController.setTransientLaunch(mLastStartActivityRecord, mPriorAboveTask);
-            if (forceTransientTransition) {
-                final DisplayContent dc = mLastStartActivityRecord.getDisplayContent();
-                // update wallpaper target to TransientHide
-                dc.mWallpaperController.adjustWallpaperWindows();
-                // execute transition because there is no change
-                transitionController.setReady(dc, true /* ready */);
-            }
+            final DisplayContent dc = mLastStartActivityRecord.getDisplayContent();
+            // update wallpaper target to TransientHide
+            dc.mWallpaperController.adjustWallpaperWindows();
+            // execute transition because there is no change
+            transitionController.setReady(dc, true /* ready */);
         }
         if (!userLeaving) {
             // no-user-leaving implies not entering PiP.
@@ -1686,7 +1690,9 @@
             }
             // When running transient transition, the transient launch target should keep on top.
             // So disallow the transient hide activity to move itself to front, e.g. trampoline.
-            if (!mAvoidMoveToFront && r.mTransitionController.isTransientHide(targetTask)) {
+            if (!mAvoidMoveToFront && (mService.mHomeProcess == null
+                    || mService.mHomeProcess.mUid != realCallingUid)
+                    && r.mTransitionController.isTransientHide(targetTask)) {
                 mAvoidMoveToFront = true;
             }
             mPriorAboveTask = TaskDisplayArea.getRootTaskAbove(targetTask.getRootTask());
@@ -1705,6 +1711,7 @@
                     activity.destroyIfPossible("Removes redundant singleInstance");
                 }
             }
+            recordTransientLaunchIfNeeded(targetTaskTop);
             // Recycle the target task for this launch.
             startResult = recycleTask(targetTask, targetTaskTop, reusedTask, intentGrants);
             if (startResult != START_SUCCESS) {
@@ -1736,6 +1743,9 @@
             addOrReparentStartingActivity(targetTask, "adding to task");
         }
 
+        // After activity is attached to task, but before actual start
+        recordTransientLaunchIfNeeded(mLastStartActivityRecord);
+
         if (!mAvoidMoveToFront && mDoResume) {
             mTargetRootTask.getRootTask().moveToFront("reuseOrNewTask", targetTask);
             if (!mTargetRootTask.isTopRootTaskInDisplayArea() && mService.isDreaming()
@@ -1832,6 +1842,14 @@
         return START_SUCCESS;
     }
 
+    private void recordTransientLaunchIfNeeded(ActivityRecord r) {
+        if (r == null || !mTransientLaunch) return;
+        final TransitionController controller = r.mTransitionController;
+        if (controller.isCollecting() && !controller.isTransientCollect(r)) {
+            controller.setTransientLaunch(r, mPriorAboveTask);
+        }
+    }
+
     /** Returns the leaf task where the target activity may be placed. */
     private Task computeTargetTask() {
         if (mStartActivity.resultTo == null && mInTask == null && !mAddingToTask
@@ -1982,11 +2000,7 @@
             }
         }
 
-        boolean shouldBlockActivityStart = true;
-        // Used for logging/toasts. Would we block the start if target sdk was U and feature was
-        // enabled?
-        boolean wouldBlockActivityStartIgnoringFlags = true;
-
+        Pair<Boolean, Boolean> pair = null;
         if (mSourceRecord != null) {
             boolean passesAsmChecks = true;
             Task sourceTask = mSourceRecord.getTask();
@@ -2002,14 +2016,25 @@
 
             if (passesAsmChecks) {
                 Task taskToCheck = taskToFront ? sourceTask : targetTask;
-                // first == false means Should Block
-                // second == false means Would Block disregarding flags
-                Pair<Boolean, Boolean> pair = ActivityTaskSupervisor
+                pair = ActivityTaskSupervisor
                         .doesTopActivityMatchingUidExistForAsm(taskToCheck, mSourceRecord.getUid(),
                                 mSourceRecord);
-                shouldBlockActivityStart = !pair.first;
-                wouldBlockActivityStartIgnoringFlags = !pair.second;
             }
+        } else if (!taskToFront) {
+            // We don't have a sourceRecord, and we're launching into an existing task.
+            // Allow if callingUid is top of stack.
+            pair = ActivityTaskSupervisor
+                    .doesTopActivityMatchingUidExistForAsm(targetTask, mCallingUid,
+                            /*sourceRecord*/null);
+        }
+
+        boolean shouldBlockActivityStart = true;
+        if (pair != null) {
+            // We block if feature flag is enabled
+            shouldBlockActivityStart = !pair.first;
+            // Used for logging/toasts. Would we block if target sdk was U and feature was
+            // enabled? If so, we can't return here but we also might not block at the end
+            boolean wouldBlockActivityStartIgnoringFlags = !pair.second;
 
             if (!wouldBlockActivityStartIgnoringFlags) {
                 return true;
@@ -2928,15 +2953,6 @@
             }
         }
 
-        // If the matching task is already in the adjacent task of the launch target. Adjust to use
-        // the adjacent task as its launch target. So the existing task will be launched into the
-        // closer one and won't be reparent redundantly.
-        final Task adjacentTargetTask = mTargetRootTask.getAdjacentTask();
-        if (adjacentTargetTask != null && intentActivity.isDescendantOf(adjacentTargetTask)
-                && intentTask.isOnTop()) {
-            mTargetRootTask = adjacentTargetTask;
-        }
-
         // If the target task is not in the front, then we need to bring it to the front...
         // except...  well, with SINGLE_TASK_LAUNCH it's not entirely clear. We'd like to have
         // the same behavior as if a new instance was being started, which means not bringing it
@@ -2977,7 +2993,9 @@
                     // should be START_DELIVERED_TO_TOP instead of START_TASK_TO_FRONT.
                     final boolean wasTopOfVisibleRootTask = intentActivity.isVisibleRequested()
                             && intentActivity.inMultiWindowMode()
-                            && intentActivity == mTargetRootTask.topRunningActivity();
+                            && intentActivity == mTargetRootTask.topRunningActivity()
+                            && !intentActivity.mTransitionController.isTransientHide(
+                                    mTargetRootTask);
                     // We only want to move to the front, if we aren't going to launch on a
                     // different root task. If we launch on a different root task, we will put the
                     // task on top there.
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
index 32f1f42..a2547fd 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
@@ -23,6 +23,7 @@
 import android.app.AppProtoEnums;
 import android.app.BackgroundStartPrivileges;
 import android.app.IActivityManager;
+import android.app.IAppTask;
 import android.app.IApplicationThread;
 import android.app.ITaskStackListener;
 import android.app.ProfilerInfo;
@@ -308,6 +309,12 @@
     public abstract void notifyActiveDreamChanged(@Nullable ComponentName activeDreamComponent);
 
     /**
+     * Starts a dream activity in the DreamService's process.
+     */
+    public abstract IAppTask startDreamActivity(@NonNull Intent intent, int callingUid,
+            int callingPid);
+
+    /**
      * Set a uid that is allowed to bypass stopped app switches, launching an app
      * whenever it wants.
      *
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 47b51ac..aeaf783 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -546,6 +546,7 @@
      */
     private volatile long mLastStopAppSwitchesTime;
 
+    @GuardedBy("itself")
     private final List<AnrController> mAnrController = new ArrayList<>();
     IActivityController mController = null;
     boolean mControllerIsAMonkey = false;
@@ -733,7 +734,7 @@
     private boolean mShowDialogs = true;
 
     /** Set if we are shutting down the system, similar to sleeping. */
-    boolean mShuttingDown = false;
+    volatile boolean mShuttingDown;
 
     /**
      * We want to hold a wake lock while running a voice interaction session, since
@@ -1411,29 +1412,39 @@
 
             final long origId = Binder.clearCallingIdentity();
             // TODO(b/64750076): Check if calling pid should really be -1.
-            final int res = getActivityStartController()
-                    .obtainStarter(intent, "startNextMatchingActivity")
-                    .setCaller(r.app.getThread())
-                    .setResolvedType(r.resolvedType)
-                    .setActivityInfo(aInfo)
-                    .setResultTo(resultTo != null ? resultTo.token : null)
-                    .setResultWho(resultWho)
-                    .setRequestCode(requestCode)
-                    .setCallingPid(-1)
-                    .setCallingUid(r.launchedFromUid)
-                    .setCallingPackage(r.launchedFromPackage)
-                    .setCallingFeatureId(r.launchedFromFeatureId)
-                    .setRealCallingPid(-1)
-                    .setRealCallingUid(r.launchedFromUid)
-                    .setActivityOptions(options)
-                    .execute();
-            Binder.restoreCallingIdentity(origId);
+            try {
+                if (options == null) {
+                    options = new SafeActivityOptions(ActivityOptions.makeBasic());
+                }
 
-            r.finishing = wasFinishing;
-            if (res != ActivityManager.START_SUCCESS) {
-                return false;
+                // Fixes b/230492947
+                // Prevents background activity launch through #startNextMatchingActivity
+                // An activity going into the background could still go back to the foreground
+                // if the intent used matches both:
+                // - the activity in the background
+                // - a second activity.
+                options.getOptions(r).setAvoidMoveToFront();
+                final int res = getActivityStartController()
+                        .obtainStarter(intent, "startNextMatchingActivity")
+                        .setCaller(r.app.getThread())
+                        .setResolvedType(r.resolvedType)
+                        .setActivityInfo(aInfo)
+                        .setResultTo(resultTo != null ? resultTo.token : null)
+                        .setResultWho(resultWho)
+                        .setRequestCode(requestCode)
+                        .setCallingPid(-1)
+                        .setCallingUid(r.launchedFromUid)
+                        .setCallingPackage(r.launchedFromPackage)
+                        .setCallingFeatureId(r.launchedFromFeatureId)
+                        .setRealCallingPid(-1)
+                        .setRealCallingUid(r.launchedFromUid)
+                        .setActivityOptions(options)
+                        .execute();
+                r.finishing = wasFinishing;
+                return res == ActivityManager.START_SUCCESS;
+            } finally {
+                Binder.restoreCallingIdentity(origId);
             }
-            return true;
         }
     }
 
@@ -1456,23 +1467,8 @@
         return false;
     }
 
-    private void enforceCallerIsDream(String callerPackageName) {
-        final long origId = Binder.clearCallingIdentity();
-        try {
-            if (!canLaunchDreamActivity(callerPackageName)) {
-                throw new SecurityException("The dream activity can be started only when the device"
-                        + " is dreaming and only by the active dream package.");
-            }
-        } finally {
-            Binder.restoreCallingIdentity(origId);
-        }
-    }
-
-    @Override
-    public boolean startDreamActivity(@NonNull Intent intent) {
-        assertPackageMatchesCallingUid(intent.getPackage());
-        enforceCallerIsDream(intent.getPackage());
-
+    private IAppTask startDreamActivityInternal(@NonNull Intent intent, int callingUid,
+            int callingPid) {
         final ActivityInfo a = new ActivityInfo();
         a.theme = com.android.internal.R.style.Theme_Dream;
         a.exported = true;
@@ -1490,7 +1486,7 @@
         options.setLaunchActivityType(ACTIVITY_TYPE_DREAM);
 
         synchronized (mGlobalLock) {
-            final WindowProcessController process = mProcessMap.getProcess(Binder.getCallingPid());
+            final WindowProcessController process = mProcessMap.getProcess(callingPid);
 
             a.packageName = process.mInfo.packageName;
             a.applicationInfo = process.mInfo;
@@ -1498,26 +1494,25 @@
             a.uiOptions = process.mInfo.uiOptions;
             a.taskAffinity = "android:" + a.packageName + "/dream";
 
-            final int callingUid = Binder.getCallingUid();
-            final int callingPid = Binder.getCallingPid();
 
-            final long origId = Binder.clearCallingIdentity();
-            try {
-                getActivityStartController().obtainStarter(intent, "dream")
-                        .setCallingUid(callingUid)
-                        .setCallingPid(callingPid)
-                        .setCallingPackage(intent.getPackage())
-                        .setActivityInfo(a)
-                        .setActivityOptions(createSafeActivityOptionsWithBalAllowed(options))
-                        // To start the dream from background, we need to start it from a persistent
-                        // system process. Here we set the real calling uid to the system server uid
-                        .setRealCallingUid(Binder.getCallingUid())
-                        .setBackgroundStartPrivileges(BackgroundStartPrivileges.ALLOW_BAL)
-                        .execute();
-                return true;
-            } finally {
-                Binder.restoreCallingIdentity(origId);
-            }
+            final ActivityRecord[] outActivity = new ActivityRecord[1];
+            getActivityStartController().obtainStarter(intent, "dream")
+                    .setCallingUid(callingUid)
+                    .setCallingPid(callingPid)
+                    .setCallingPackage(intent.getPackage())
+                    .setActivityInfo(a)
+                    .setActivityOptions(createSafeActivityOptionsWithBalAllowed(options))
+                    .setOutActivity(outActivity)
+                    // To start the dream from background, we need to start it from a persistent
+                    // system process. Here we set the real calling uid to the system server uid
+                    .setRealCallingUid(Binder.getCallingUid())
+                    .setBackgroundStartPrivileges(BackgroundStartPrivileges.ALLOW_BAL)
+                    .execute();
+
+            final ActivityRecord started = outActivity[0];
+            final IAppTask appTask = started == null ? null :
+                    new AppTaskImpl(this, started.getTask().mTaskId, callingUid);
+            return appTask;
         }
     }
 
@@ -2288,14 +2283,14 @@
 
     /** Register an {@link AnrController} to control the ANR dialog behavior */
     public void registerAnrController(AnrController controller) {
-        synchronized (mGlobalLock) {
+        synchronized (mAnrController) {
             mAnrController.add(controller);
         }
     }
 
     /** Unregister an {@link AnrController} */
     public void unregisterAnrController(AnrController controller) {
-        synchronized (mGlobalLock) {
+        synchronized (mAnrController) {
             mAnrController.remove(controller);
         }
     }
@@ -2311,7 +2306,7 @@
         }
 
         final ArrayList<AnrController> controllers;
-        synchronized (mGlobalLock) {
+        synchronized (mAnrController) {
             controllers = new ArrayList<>(mAnrController);
         }
 
@@ -5915,6 +5910,11 @@
         }
 
         @Override
+        public IAppTask startDreamActivity(@NonNull Intent intent, int callingUid, int callingPid) {
+            return startDreamActivityInternal(intent, callingUid, callingPid);
+        }
+
+        @Override
         public void setAllowAppSwitches(@NonNull String type, int uid, int userId) {
             if (!mAmInternal.isUserRunning(userId, ActivityManager.FLAG_OR_STOPPED)) {
                 return;
@@ -6024,15 +6024,13 @@
 
         @Override
         public boolean isShuttingDown() {
-            synchronized (mGlobalLock) {
-                return mShuttingDown;
-            }
+            return mShuttingDown;
         }
 
         @Override
         public boolean shuttingDown(boolean booted, int timeout) {
+            mShuttingDown = true;
             synchronized (mGlobalLock) {
-                mShuttingDown = true;
                 mRootWindowContainer.prepareForShutdown();
                 updateEventDispatchingLocked(booted);
                 notifyTaskPersisterLocked(null, true);
@@ -6058,6 +6056,12 @@
 
         @Override
         public void showSystemReadyErrorDialogsIfNeeded() {
+            if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) {
+                Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "showSystemReadyErrorDialogs");
+            }
+            // Pull the check for build consistency outside the lock, to avoid holding the lock for
+            // too long, given that `Build.isBuildConsistent()` takes relatively long.
+            boolean isBuildConsistent = Build.isBuildConsistent();
             synchronized (mGlobalLock) {
                 try {
                     if (AppGlobals.getPackageManager().hasSystemUidErrors()) {
@@ -6080,7 +6084,7 @@
                 } catch (RemoteException e) {
                 }
 
-                if (!Build.isBuildConsistent()) {
+                if (!isBuildConsistent) {
                     Slog.e(TAG, "Build fingerprint is not consistent, warning user");
                     mUiHandler.post(() -> {
                         if (mShowDialogs) {
@@ -6097,6 +6101,7 @@
                     });
                 }
             }
+            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
         }
 
         @Override
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index b34ae19..50948e1 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -1891,7 +1891,7 @@
         // DestroyActivityItem may be called first.
         final ActivityRecord top = task.getTopMostActivity();
         if (top != null && top.finishing && !top.mAppStopped && top.lastVisibleTime > 0
-                && !task.mKillProcessesOnDestroyed) {
+                && !task.mKillProcessesOnDestroyed && top.hasProcess()) {
             task.mKillProcessesOnDestroyed = true;
             mHandler.sendMessageDelayed(
                     mHandler.obtainMessage(KILL_TASK_PROCESSES_TIMEOUT_MSG, task),
@@ -2370,6 +2370,10 @@
         final ActivityRecord prevTopActivity = mTopResumedActivity;
         final Task topRootTask = mRootWindowContainer.getTopDisplayFocusedRootTask();
         if (topRootTask == null || topRootTask.getTopResumedActivity() == prevTopActivity) {
+            if (topRootTask == null) {
+                // There's no focused task and there won't have any resumed activity either.
+                scheduleTopResumedActivityStateLossIfNeeded();
+            }
             if (mService.isSleepingLocked()) {
                 // There won't be a next resumed activity. The top process should still be updated
                 // according to the current top focused activity.
@@ -2379,16 +2383,7 @@
         }
 
         // Ask previous activity to release the top state.
-        final boolean prevActivityReceivedTopState =
-                prevTopActivity != null && !mTopResumedActivityWaitingForPrev;
-        // mTopResumedActivityWaitingForPrev == true at this point would mean that an activity
-        // before the prevTopActivity one hasn't reported back yet. So server never sent the top
-        // resumed state change message to prevTopActivity.
-        if (prevActivityReceivedTopState
-                && prevTopActivity.scheduleTopResumedActivityChanged(false /* onTop */)) {
-            scheduleTopResumedStateLossTimeout(prevTopActivity);
-            mTopResumedActivityWaitingForPrev = true;
-        }
+        scheduleTopResumedActivityStateLossIfNeeded();
 
         // Update the current top activity.
         mTopResumedActivity = topRootTask.getTopResumedActivity();
@@ -2413,6 +2408,23 @@
         mService.updateTopApp(mTopResumedActivity);
     }
 
+    /** Schedule current top resumed activity state loss */
+    private void scheduleTopResumedActivityStateLossIfNeeded() {
+        if (mTopResumedActivity == null) {
+            return;
+        }
+
+        // mTopResumedActivityWaitingForPrev == true at this point would mean that an activity
+        // before the prevTopActivity one hasn't reported back yet. So server never sent the top
+        // resumed state change message to prevTopActivity.
+        if (!mTopResumedActivityWaitingForPrev
+                && mTopResumedActivity.scheduleTopResumedActivityChanged(false /* onTop */)) {
+            scheduleTopResumedStateLossTimeout(mTopResumedActivity);
+            mTopResumedActivityWaitingForPrev = true;
+            mTopResumedActivity = null;
+        }
+    }
+
     /** Schedule top resumed state change if previous top activity already reported back. */
     private void scheduleTopResumedActivityStateIfNeeded() {
         if (mTopResumedActivity != null && !mTopResumedActivityWaitingForPrev) {
diff --git a/services/core/java/com/android/server/wm/AppWarnings.java b/services/core/java/com/android/server/wm/AppWarnings.java
index f7ccc0d..ad5f442 100644
--- a/services/core/java/com/android/server/wm/AppWarnings.java
+++ b/services/core/java/com/android/server/wm/AppWarnings.java
@@ -16,7 +16,9 @@
 
 package com.android.server.wm;
 
+import android.annotation.NonNull;
 import android.annotation.UiThread;
+import android.annotation.WorkerThread;
 import android.app.AlertDialog;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -30,14 +32,18 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.SystemProperties;
+import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.AtomicFile;
 import android.util.DisplayMetrics;
 import android.util.Slog;
 import android.util.Xml;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.ArrayUtils;
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
+import com.android.server.IoThread;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -45,9 +51,7 @@
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
+import java.util.concurrent.atomic.AtomicReference;
 
 /**
  * Manages warning dialogs shown during application lifecycle.
@@ -61,11 +65,12 @@
     public static final int FLAG_HIDE_DEPRECATED_SDK = 0x04;
     public static final int FLAG_HIDE_DEPRECATED_ABI = 0x08;
 
-    private final HashMap<String, Integer> mPackageFlags = new HashMap<>();
+    @GuardedBy("mPackageFlags")
+    private final ArrayMap<String, Integer> mPackageFlags = new ArrayMap<>();
 
     private final ActivityTaskManagerService mAtm;
     private final Context mUiContext;
-    private final ConfigHandler mHandler;
+    private final WriteConfigTask mWriteConfigTask;
     private final UiHandler mUiHandler;
     private final AtomicFile mConfigFile;
 
@@ -75,30 +80,20 @@
     private DeprecatedAbiDialog mDeprecatedAbiDialog;
 
     /** @see android.app.ActivityManager#alwaysShowUnsupportedCompileSdkWarning */
-    private HashSet<ComponentName> mAlwaysShowUnsupportedCompileSdkWarningActivities =
-            new HashSet<>();
+    private final ArraySet<ComponentName> mAlwaysShowUnsupportedCompileSdkWarningActivities =
+            new ArraySet<>();
 
     /** @see android.app.ActivityManager#alwaysShowUnsupportedCompileSdkWarning */
     void alwaysShowUnsupportedCompileSdkWarning(ComponentName activity) {
         mAlwaysShowUnsupportedCompileSdkWarningActivities.add(activity);
     }
 
-    /**
-     * Creates a new warning dialog manager.
-     * <p>
-     * <strong>Note:</strong> Must be called from the ActivityManagerService thread.
-     *
-     * @param atm
-     * @param uiContext
-     * @param handler
-     * @param uiHandler
-     * @param systemDir
-     */
+    /** Creates a new warning dialog manager. */
     public AppWarnings(ActivityTaskManagerService atm, Context uiContext, Handler handler,
             Handler uiHandler, File systemDir) {
         mAtm = atm;
         mUiContext = uiContext;
-        mHandler = new ConfigHandler(handler.getLooper());
+        mWriteConfigTask = new WriteConfigTask();
         mUiHandler = new UiHandler(uiHandler.getLooper());
         mConfigFile = new AtomicFile(new File(systemDir, CONFIG_FILE_NAME), "warnings-config");
 
@@ -165,7 +160,11 @@
      * @param r activity record for which the warning may be displayed
      */
     public void showDeprecatedTargetDialogIfNeeded(ActivityRecord r) {
-        if (r.info.applicationInfo.targetSdkVersion < Build.VERSION.MIN_SUPPORTED_TARGET_SDK_INT) {
+        // The warning dialog can be disabled for debugging or testing purposes
+        final boolean disableDeprecatedTargetSdkDialog = SystemProperties.getBoolean(
+                "debug.wm.disable_deprecated_target_sdk_dialog", false);
+        if (r.info.applicationInfo.targetSdkVersion < Build.VERSION.MIN_SUPPORTED_TARGET_SDK_INT
+                && !disableDeprecatedTargetSdkDialog) {
             mUiHandler.showDeprecatedTargetDialog(r);
         }
     }
@@ -256,8 +255,9 @@
         mUiHandler.hideDialogsForPackage(name);
 
         synchronized (mPackageFlags) {
-            mPackageFlags.remove(name);
-            mHandler.scheduleWrite();
+            if (mPackageFlags.remove(name) != null) {
+                mWriteConfigTask.schedule();
+            }
         }
     }
 
@@ -425,7 +425,7 @@
                 } else {
                     mPackageFlags.remove(name);
                 }
-                mHandler.scheduleWrite();
+                mWriteConfigTask.schedule();
             }
         }
     }
@@ -556,46 +556,30 @@
         }
     }
 
-    /**
-     * Handles messages on the ActivityTaskManagerService thread.
-     */
-    private final class ConfigHandler extends Handler {
-        private static final int MSG_WRITE = 1;
-
-        private static final int DELAY_MSG_WRITE = 10000;
-
-        public ConfigHandler(Looper looper) {
-            super(looper, null, true);
-        }
+    private final class WriteConfigTask implements Runnable {
+        private static final long WRITE_CONFIG_DELAY_MS = 10000;
+        final AtomicReference<ArrayMap<String, Integer>> mPendingPackageFlags =
+                new AtomicReference<>();
 
         @Override
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case MSG_WRITE:
-                    writeConfigToFileAmsThread();
-                    break;
+        public void run() {
+            final ArrayMap<String, Integer> packageFlags = mPendingPackageFlags.getAndSet(null);
+            if (packageFlags != null) {
+                writeConfigToFile(packageFlags);
             }
         }
 
-        public void scheduleWrite() {
-            removeMessages(MSG_WRITE);
-            sendEmptyMessageDelayed(MSG_WRITE, DELAY_MSG_WRITE);
+        @GuardedBy("mPackageFlags")
+        void schedule() {
+            if (mPendingPackageFlags.getAndSet(new ArrayMap<>(mPackageFlags)) == null) {
+                IoThread.getHandler().postDelayed(this, WRITE_CONFIG_DELAY_MS);
+            }
         }
     }
 
-    /**
-     * Writes the configuration file.
-     * <p>
-     * <strong>Note:</strong> Should be called from the ActivityManagerService thread unless you
-     * don't care where you're doing I/O operations. But you <i>do</i> care, don't you?
-     */
-    private void writeConfigToFileAmsThread() {
-        // Create a shallow copy so that we don't have to synchronize on config.
-        final HashMap<String, Integer> packageFlags;
-        synchronized (mPackageFlags) {
-            packageFlags = new HashMap<>(mPackageFlags);
-        }
-
+    /** Writes the configuration file. */
+    @WorkerThread
+    private void writeConfigToFile(@NonNull ArrayMap<String, Integer> packageFlags) {
         FileOutputStream fos = null;
         try {
             fos = mConfigFile.startWrite();
@@ -605,9 +589,9 @@
             out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
             out.startTag(null, "packages");
 
-            for (Map.Entry<String, Integer> entry : packageFlags.entrySet()) {
-                String pkg = entry.getKey();
-                int mode = entry.getValue();
+            for (int i = 0; i < packageFlags.size(); i++) {
+                final String pkg = packageFlags.keyAt(i);
+                final int mode = packageFlags.valueAt(i);
                 if (mode == 0) {
                     continue;
                 }
diff --git a/services/core/java/com/android/server/wm/AsyncRotationController.java b/services/core/java/com/android/server/wm/AsyncRotationController.java
index 0115877..4ce21bd 100644
--- a/services/core/java/com/android/server/wm/AsyncRotationController.java
+++ b/services/core/java/com/android/server/wm/AsyncRotationController.java
@@ -93,15 +93,12 @@
     /** Whether the start transaction of the transition is committed (by shell). */
     private boolean mIsStartTransactionCommitted;
 
-    /** Whether all windows should wait for the start transaction. */
-    private boolean mAlwaysWaitForStartTransaction;
-
     /** Whether the target windows have been requested to sync their draw transactions. */
     private boolean mIsSyncDrawRequested;
 
     private SeamlessRotator mRotator;
 
-    private final int mOriginalRotation;
+    private int mOriginalRotation;
     private final boolean mHasScreenRotationAnimation;
 
     AsyncRotationController(DisplayContent displayContent) {
@@ -147,15 +144,6 @@
         if (mTransitionOp == OP_LEGACY) {
             mIsStartTransactionCommitted = true;
         } else if (displayContent.mTransitionController.isCollecting(displayContent)) {
-            final Transition transition =
-                    mDisplayContent.mTransitionController.getCollectingTransition();
-            if (transition != null) {
-                final BLASTSyncEngine.SyncGroup syncGroup =
-                        mDisplayContent.mWmService.mSyncEngine.getSyncSet(transition.getSyncId());
-                if (syncGroup != null && syncGroup.mSyncMethod == BLASTSyncEngine.METHOD_BLAST) {
-                    mAlwaysWaitForStartTransaction = true;
-                }
-            }
             keepAppearanceInPreviousRotation();
         }
     }
@@ -279,10 +267,12 @@
             // The previous animation leash will be dropped when preparing fade-in animation, so
             // simply apply new animation without restoring the transformation.
             fadeWindowToken(true /* show */, windowToken, ANIMATION_TYPE_TOKEN_TRANSFORM);
-        } else if (op.mAction == Operation.ACTION_SEAMLESS && mRotator != null
+        } else if (op.mAction == Operation.ACTION_SEAMLESS
                 && op.mLeash != null && op.mLeash.isValid()) {
             if (DEBUG) Slog.d(TAG, "finishOp undo seamless " + windowToken.getTopChild());
-            mRotator.setIdentityMatrix(windowToken.getSyncTransaction(), op.mLeash);
+            final SurfaceControl.Transaction t = windowToken.getSyncTransaction();
+            t.setMatrix(op.mLeash, 1, 0, 0, 1);
+            t.setPosition(op.mLeash, 0, 0);
         }
     }
 
@@ -365,6 +355,32 @@
         }
     }
 
+    /**
+     * Re-initialize the states if the current display rotation has changed to a different rotation.
+     * This is mainly for seamless rotation to update the transform based on new rotation.
+     */
+    void updateRotation() {
+        if (mRotator == null) return;
+        final int currentRotation = mDisplayContent.getWindowConfiguration().getRotation();
+        if (mOriginalRotation == currentRotation) {
+            return;
+        }
+        Slog.d(TAG, "Update original rotation " + currentRotation);
+        mOriginalRotation = currentRotation;
+        mDisplayContent.forAllWindows(w -> {
+            if (w.mForceSeamlesslyRotate && w.mHasSurface
+                    && !mTargetWindowTokens.containsKey(w.mToken)) {
+                final Operation op = new Operation(Operation.ACTION_SEAMLESS);
+                op.mLeash = w.mToken.mSurfaceControl;
+                mTargetWindowTokens.put(w.mToken, op);
+            }
+        }, true /* traverseTopToBottom */);
+        mRotator = null;
+        mIsStartTransactionCommitted = false;
+        mIsSyncDrawRequested = false;
+        keepAppearanceInPreviousRotation();
+    }
+
     private void scheduleTimeout() {
         if (mTimeoutRunnable == null) {
             mTimeoutRunnable = () -> {
@@ -589,7 +605,7 @@
      * start transaction of rotation transition is applied.
      */
     private boolean canDrawBeforeStartTransaction(Operation op) {
-        return !mAlwaysWaitForStartTransaction && op.mAction != Operation.ACTION_SEAMLESS;
+        return op.mAction != Operation.ACTION_SEAMLESS;
     }
 
     /** The operation to control the rotation appearance associated with window token. */
diff --git a/services/core/java/com/android/server/wm/ContentRecorder.java b/services/core/java/com/android/server/wm/ContentRecorder.java
index 6a7e764..2ecbf8a 100644
--- a/services/core/java/com/android/server/wm/ContentRecorder.java
+++ b/services/core/java/com/android/server/wm/ContentRecorder.java
@@ -166,7 +166,7 @@
                                     + "%d to new bounds %s and/or orientation %d.",
                             mDisplayContent.getDisplayId(), recordedContentBounds,
                             recordedContentOrientation);
-                    updateMirroredSurface(mDisplayContent.mWmService.mTransactionFactory.get(),
+                    updateMirroredSurface(mRecordedWindowContainer.getSyncTransaction(),
                             recordedContentBounds, surfaceSize);
                 } else {
                     // If the surface removed, do nothing. We will handle this via onDisplayChanged
@@ -325,6 +325,7 @@
                         .reparent(mDisplayContent.getOverlayLayer(), null);
         // Retrieve the size of the DisplayArea to mirror.
         updateMirroredSurface(transaction, mRecordedWindowContainer.getBounds(), surfaceSize);
+        transaction.apply();
 
         // Notify the client about the visibility of the mirrored region, now that we have begun
         // capture.
@@ -481,8 +482,7 @@
                 .setMatrix(mRecordedSurface, scale, 0 /* dtdx */, 0 /* dtdy */, scale)
                 // Position needs to be updated when the mirrored DisplayArea has changed, since
                 // the content will no longer be centered in the output surface.
-                .setPosition(mRecordedSurface, shiftedX /* x */, shiftedY /* y */)
-                .apply();
+                .setPosition(mRecordedSurface, shiftedX /* x */, shiftedY /* y */);
         mLastRecordedBounds = new Rect(recordedContentBounds);
         // Request to notify the client about the resize.
         mMediaProjectionManager.notifyActiveProjectionCapturedContentResized(
diff --git a/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java b/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java
index e91c9d4..4180cd2 100644
--- a/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java
+++ b/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java
@@ -39,10 +39,11 @@
             TAG_WITH_CLASS_NAME ? "DesktopModeLaunchParamsModifier" : TAG_ATM;
     private static final boolean DEBUG = false;
 
-    // Desktop mode feature flag.
-    static final boolean DESKTOP_MODE_SUPPORTED = SystemProperties.getBoolean(
-            "persist.wm.debug.desktop_mode", false) || SystemProperties.getBoolean(
-            "persist.wm.debug.desktop_mode_2", false);
+    // Desktop mode feature flags.
+    private static final boolean DESKTOP_MODE_PROTO1_SUPPORTED =
+            SystemProperties.getBoolean("persist.wm.debug.desktop_mode", false);
+    private static final boolean DESKTOP_MODE_PROTO2_SUPPORTED =
+            SystemProperties.getBoolean("persist.wm.debug.desktop_mode_2", false);
     // Override default freeform task width when desktop mode is enabled. In dips.
     private static final int DESKTOP_MODE_DEFAULT_WIDTH_DP = SystemProperties.getInt(
             "persist.wm.debug.desktop_mode.default_width", 840);
@@ -76,22 +77,37 @@
             appendLog("task null, skipping");
             return RESULT_SKIP;
         }
-        if (phase != PHASE_BOUNDS) {
-            appendLog("not in bounds phase, skipping");
+        if (!task.isActivityTypeStandardOrUndefined()) {
+            appendLog("not standard or undefined activity type, skipping");
             return RESULT_SKIP;
         }
-        if (!task.isActivityTypeStandard()) {
-            appendLog("not standard activity type, skipping");
-            return RESULT_SKIP;
-        }
-        if (!currentParams.mBounds.isEmpty()) {
-            appendLog("currentParams has bounds set, not overriding");
+        if (phase < PHASE_WINDOWING_MODE) {
+            appendLog("not in windowing mode or bounds phase, skipping");
             return RESULT_SKIP;
         }
 
         // Copy over any values
         outParams.set(currentParams);
 
+        // In Proto2, trampoline task launches of an existing background task can result in the
+        // previous windowing mode to be restored even if the desktop mode state has changed.
+        // Let task launches inherit the windowing mode from the source task if available, which
+        // should have the desired windowing mode set by WM Shell. See b/286929122.
+        if (DESKTOP_MODE_PROTO2_SUPPORTED && source != null && source.getTask() != null) {
+            final Task sourceTask = source.getTask();
+            outParams.mWindowingMode = sourceTask.getWindowingMode();
+            appendLog("inherit-from-source=" + outParams.mWindowingMode);
+        }
+
+        if (phase == PHASE_WINDOWING_MODE) {
+            return RESULT_DONE;
+        }
+
+        if (!currentParams.mBounds.isEmpty()) {
+            appendLog("currentParams has bounds set, not overriding");
+            return RESULT_SKIP;
+        }
+
         // Update width and height with default desktop mode values
         float density = (float) task.getConfiguration().densityDpi / DENSITY_DEFAULT;
         final int width = (int) (DESKTOP_MODE_DEFAULT_WIDTH_DP * density + 0.5f);
@@ -123,4 +139,9 @@
     private void outputLog() {
         if (DEBUG) Slog.d(TAG, mLogBuilder.toString());
     }
+
+    /** Whether desktop mode is supported. */
+    static boolean isDesktopModeSupported() {
+        return DESKTOP_MODE_PROTO1_SUPPORTED || DESKTOP_MODE_PROTO2_SUPPORTED;
+    }
 }
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index ef19eef..349d115 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -522,7 +522,6 @@
     boolean mWaitingForConfig;
 
     // TODO(multi-display): remove some of the usages.
-    @VisibleForTesting
     boolean isDefaultDisplay;
 
     /** Detect user tapping outside of current focused task bounds .*/
@@ -1065,7 +1064,7 @@
         if (obscuredChanged && w.isVisible() && mWallpaperController.isWallpaperTarget(w)) {
             // This is the wallpaper target and its obscured state changed... make sure the
             // current wallpaper's visibility has been updated accordingly.
-            mWallpaperController.updateWallpaperVisibility();
+            mWallpaperController.updateWallpaperTokens(mDisplayContent.isKeyguardLocked());
         }
 
         w.handleWindowMovedIfNeeded();
@@ -1214,8 +1213,7 @@
         mDisplayRotationCompatPolicy =
                 // Not checking DeviceConfig value here to allow enabling via DeviceConfig
                 // without the need to restart the device.
-                mWmService.mLetterboxConfiguration.isCameraCompatTreatmentEnabled(
-                            /* checkDeviceConfig */ false)
+                mWmService.mLetterboxConfiguration.isCameraCompatTreatmentEnabledAtBuildTime()
                         ? new DisplayRotationCompatPolicy(this) : null;
         mRotationReversionController = new DisplayRotationReversionController(this);
 
@@ -1714,9 +1712,7 @@
     }
 
     private int getMinimalTaskSizeDp() {
-        final Context displayConfigurationContext =
-                mAtmService.mContext.createConfigurationContext(getConfiguration());
-        final Resources res = displayConfigurationContext.getResources();
+        final Resources res = getDisplayUiContext().getResources();
         final TypedValue value = new TypedValue();
         res.getValue(R.dimen.default_minimal_size_resizable_task, value, true /* resolveRefs */);
         final int valueUnit = ((value.data >> COMPLEX_UNIT_SHIFT) & COMPLEX_UNIT_MASK);
@@ -2717,6 +2713,7 @@
         if (mDisplayPolicy != null) {
             mDisplayPolicy.onConfigurationChanged();
             mPinnedTaskController.onPostDisplayConfigurationChanged();
+            mMinSizeOfResizeableTaskDp = getMinimalTaskSizeDp();
         }
         // Update IME parent if needed.
         updateImeParent();
@@ -2858,7 +2855,6 @@
 
     void onDisplayInfoChanged() {
         updateDisplayFrames(false /* notifyInsetsChange */);
-        mMinSizeOfResizeableTaskDp = getMinimalTaskSizeDp();
         mInputMonitor.layoutInputConsumers(mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight);
         mDisplayPolicy.onDisplayInfoChanged(mDisplayInfo);
     }
@@ -3361,6 +3357,7 @@
             mRootWindowContainer.mTaskSupervisor
                     .getKeyguardController().onDisplayRemoved(mDisplayId);
             mWallpaperController.resetLargestDisplay(mDisplay);
+            mWmService.mDisplayWindowSettings.onDisplayRemoved(this);
         } finally {
             mDisplayReady = false;
         }
@@ -3460,6 +3457,11 @@
                 this, this, null /* remoteTransition */, displayChange);
         if (t != null) {
             mAtmService.startLaunchPowerMode(POWER_MODE_REASON_CHANGE_DISPLAY);
+            if (mAsyncRotationController != null) {
+                // Give a chance to update the transform if the current rotation is changed when
+                // some windows haven't finished previous rotation.
+                mAsyncRotationController.updateRotation();
+            }
             if (mFixedRotationLaunchingApp != null) {
                 // A fixed-rotation transition is done, then continue to start a seamless display
                 // transition.
@@ -3704,6 +3706,7 @@
         mInputMonitor.dump(pw, "  ");
         pw.println();
         mInsetsStateController.dump(prefix, pw);
+        mInsetsPolicy.dump(prefix, pw);
         mDwpcHelper.dump(prefix, pw);
         pw.println();
     }
@@ -6401,9 +6404,9 @@
             // Don't do recursive work.
             return;
         }
-        mInEnsureActivitiesVisible = true;
         mAtmService.mTaskSupervisor.beginActivityVisibilityUpdate();
         try {
+            mInEnsureActivitiesVisible = true;
             forAllRootTasks(rootTask -> {
                 rootTask.ensureActivitiesVisible(starting, configChanges, preserveWindows,
                         notifyClients);
@@ -7034,4 +7037,12 @@
         // Display is the root, so it's not rotated relative to anything.
         return Surface.ROTATION_0;
     }
+
+    public void replaceContent(SurfaceControl sc) {
+        new Transaction().reparent(sc, getSurfaceControl())
+                .reparent(mWindowingLayer, null)
+                .reparent(mOverlayLayer, null)
+                .reparent(mA11yOverlayLayer, null)
+                .apply();
+    }
 }
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 95c953a..ab89b87 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -176,7 +176,7 @@
 
     // TODO(b/266197298): Remove this by a more general protocol from the insets providers.
     private static final boolean USE_CACHED_INSETS_FOR_DISPLAY_SWITCH =
-            SystemProperties.getBoolean("persist.wm.debug.cached_insets_switch", false);
+            SystemProperties.getBoolean("persist.wm.debug.cached_insets_switch", true);
 
     private final WindowManagerService mService;
     private final Context mContext;
@@ -271,13 +271,13 @@
     private WindowState mSystemUiControllingWindow;
 
     // Candidate window to determine the color of navigation bar. The window needs to be top
-    // fullscreen-app windows or dim layers that are intersecting with the window frame of status
-    // bar.
+    // fullscreen-app windows or dim layers that are intersecting with the window frame of
+    // navigation bar.
     private WindowState mNavBarColorWindowCandidate;
 
-    // The window to determine opacity and background of translucent navigation bar. The window
-    // needs to be opaque.
-    private WindowState mNavBarBackgroundWindow;
+    // Candidate window to determine opacity and background of translucent navigation bar.
+    // The window frame must intersect the frame of navigation bar.
+    private WindowState mNavBarBackgroundWindowCandidate;
 
     /**
      * A collection of {@link AppearanceRegion} to indicate that which region of status bar applies
@@ -327,8 +327,6 @@
     private WindowState mTopFullscreenOpaqueWindowState;
     private boolean mTopIsFullscreen;
     private int mNavBarOpacityMode = NAV_BAR_OPAQUE_WHEN_FREEFORM_OR_DOCKED;
-    private boolean mForceConsumeSystemBars;
-    private boolean mForceShowSystemBars;
 
     /**
      * Windows that provides gesture insets. If multiple windows provide gesture insets at the same
@@ -942,7 +940,7 @@
             float maxOpacity = mService.mMaximumObscuringOpacityForTouch;
             if (attrs.alpha > maxOpacity
                     && (attrs.flags & FLAG_NOT_TOUCHABLE) != 0
-                    && (attrs.privateFlags & PRIVATE_FLAG_TRUSTED_OVERLAY) == 0) {
+                    && !win.isTrustedOverlay()) {
                 // The app is posting a SAW with the intent of letting touches pass through, but
                 // they are going to be deemed untrusted and will be blocked. Try to honor the
                 // intent of letting touches pass through at the cost of 0.2 opacity for app
@@ -961,12 +959,6 @@
         if (!win.mSession.mCanSetUnrestrictedGestureExclusion) {
             attrs.privateFlags &= ~PRIVATE_FLAG_UNRESTRICTED_GESTURE_EXCLUSION;
         }
-
-        final InsetsSourceProvider provider = win.getControllableInsetProvider();
-        if (provider != null && provider.getSource().insetsRoundedCornerFrame()
-                != attrs.insetsRoundedCornerFrame) {
-            provider.getSource().setInsetsRoundedCornerFrame(attrs.insetsRoundedCornerFrame);
-        }
     }
 
     /**
@@ -1104,9 +1096,11 @@
                 } else {
                     overrideProviders = null;
                 }
-                mDisplayContent.getInsetsStateController().getOrCreateSourceProvider(
-                        provider.getId(), provider.getType()).setWindowContainer(
-                                win, frameProvider, overrideProviders);
+                final InsetsSourceProvider sourceProvider = mDisplayContent
+                        .getInsetsStateController().getOrCreateSourceProvider(provider.getId(),
+                                provider.getType());
+                sourceProvider.getSource().setFlags(provider.getFlags());
+                sourceProvider.setWindowContainer(win, frameProvider, overrideProviders);
                 mInsetsSourceWindowsExceptIme.add(win);
             }
         }
@@ -1290,18 +1284,10 @@
         return ANIMATION_STYLEABLE;
     }
 
-    /**
-     * @return true if the system bars are forced to be consumed
-     */
+    // TODO (b/277891341): Remove this and related usages. This has been replaced by
+    //                     InsetsSource#FLAG_FORCE_CONSUMING.
     public boolean areSystemBarsForcedConsumedLw() {
-        return mForceConsumeSystemBars;
-    }
-
-    /**
-     * @return true if the system bars are forced to stay visible
-     */
-    public boolean areSystemBarsForcedShownLw() {
-        return mForceShowSystemBars;
+        return false;
     }
 
     /**
@@ -1387,7 +1373,7 @@
         mBottomGestureHost = null;
         mTopFullscreenOpaqueWindowState = null;
         mNavBarColorWindowCandidate = null;
-        mNavBarBackgroundWindow = null;
+        mNavBarBackgroundWindowCandidate = null;
         mStatusBarAppearanceRegionList.clear();
         mLetterboxDetails.clear();
         mStatusBarBackgroundWindows.clear();
@@ -1514,8 +1500,8 @@
                     mNavBarColorWindowCandidate = win;
                     addSystemBarColorApp(win);
                 }
-                if (mNavBarBackgroundWindow == null) {
-                    mNavBarBackgroundWindow = win;
+                if (mNavBarBackgroundWindowCandidate == null) {
+                    mNavBarBackgroundWindowCandidate = win;
                 }
             }
 
@@ -1531,20 +1517,32 @@
             }
         } else if (win.isDimming()) {
             if (mStatusBar != null) {
-                if (addStatusBarAppearanceRegionsForDimmingWindow(
-                        win.mAttrs.insetsFlags.appearance & APPEARANCE_LIGHT_STATUS_BARS,
-                        mStatusBar.getFrame(), win.getBounds(), win.getFrame())) {
+                // If the dim window is below status bar window, we should update the appearance
+                // region if needed. Otherwise, leave it as it is.
+                final int statusBarLayer = mStatusBar.mToken.getWindowLayerFromType();
+                final int targetWindowLayer = win.mToken.getWindowLayerFromType();
+                if (targetWindowLayer < statusBarLayer
+                        && addStatusBarAppearanceRegionsForDimmingWindow(
+                                win.mAttrs.insetsFlags.appearance & APPEARANCE_LIGHT_STATUS_BARS,
+                                mStatusBar.getFrame(), win.getBounds(), win.getFrame())) {
                     addSystemBarColorApp(win);
                 }
             }
             if (isOverlappingWithNavBar(win) && mNavBarColorWindowCandidate == null) {
                 mNavBarColorWindowCandidate = win;
+                addSystemBarColorApp(win);
             }
-        } else if (appWindow && attached == null && mNavBarColorWindowCandidate == null
+        } else if (appWindow && attached == null
+                && (mNavBarColorWindowCandidate == null || mNavBarBackgroundWindowCandidate == null)
                 && win.getFrame().contains(
                         getBarContentFrameForWindow(win, Type.navigationBars()))) {
-            mNavBarColorWindowCandidate = win;
-            addSystemBarColorApp(win);
+            if (mNavBarColorWindowCandidate == null) {
+                mNavBarColorWindowCandidate = win;
+                addSystemBarColorApp(win);
+            }
+            if (mNavBarBackgroundWindowCandidate == null) {
+                mNavBarBackgroundWindowCandidate = win;
+            }
         }
     }
 
@@ -1686,7 +1684,8 @@
      * @return Whether the top fullscreen app hides the given type of system bar.
      */
     boolean topAppHidesSystemBar(@InsetsType int type) {
-        if (mTopFullscreenOpaqueWindowState == null || mForceShowSystemBars) {
+        if (mTopFullscreenOpaqueWindowState == null
+                || getInsetsPolicy().areTypesForciblyShowing(type)) {
             return false;
         }
         return !mTopFullscreenOpaqueWindowState.isRequestedVisible(type);
@@ -2282,8 +2281,8 @@
                 && Arrays.equals(mLastLetterboxDetails, letterboxDetails)) {
             return;
         }
-        if (mDisplayContent.isDefaultDisplay && mLastFocusIsFullscreen != isFullscreen
-                && ((mLastAppearance ^ appearance) & APPEARANCE_LOW_PROFILE_BARS) != 0) {
+        if (mDisplayContent.isDefaultDisplay && (mLastFocusIsFullscreen != isFullscreen
+                || ((mLastAppearance ^ appearance) & APPEARANCE_LOW_PROFILE_BARS) != 0)) {
             mService.mInputManager.setSystemUiLightsOut(
                     isFullscreen || (appearance & APPEARANCE_LOW_PROFILE_BARS) != 0);
         }
@@ -2363,14 +2362,7 @@
         final boolean freeformRootTaskVisible =
                 defaultTaskDisplayArea.isRootTaskVisible(WINDOWING_MODE_FREEFORM);
 
-        // We need to force showing system bars when adjacent tasks or freeform roots visible.
-        mForceShowSystemBars = adjacentTasksVisible || freeformRootTaskVisible;
-        // We need to force the consumption of the system bars if they are force shown or if they
-        // are controlled by a remote insets controller.
-        mForceConsumeSystemBars = mForceShowSystemBars
-                || getInsetsPolicy().remoteInsetsControllerControlsSystemBars(win)
-                || getInsetsPolicy().forcesShowingNavigationBars(win);
-        mDisplayContent.getInsetsPolicy().updateBarControlTarget(win);
+        getInsetsPolicy().updateSystemBars(win, adjacentTasksVisible, freeformRootTaskVisible);
 
         final boolean topAppHidesStatusBar = topAppHidesSystemBar(Type.statusBars());
         if (getStatusBar() != null) {
@@ -2484,7 +2476,7 @@
         return win.isFullyTransparentBarAllowed(getBarContentFrameForWindow(win, type));
     }
 
-    private boolean drawsBarBackground(WindowState win) {
+    private static boolean drawsBarBackground(WindowState win) {
         if (win == null) {
             return true;
         }
@@ -2524,7 +2516,14 @@
      */
     private int configureNavBarOpacity(int appearance, boolean multiWindowTaskVisible,
             boolean freeformRootTaskVisible) {
-        final boolean drawBackground = drawsBarBackground(mNavBarBackgroundWindow);
+        final WindowState navBackgroundWin = chooseNavigationBackgroundWindow(
+                mNavBarBackgroundWindowCandidate,
+                mDisplayContent.mInputMethodWindow,
+                mNavigationBarPosition);
+        final boolean drawBackground = navBackgroundWin != null
+                // There is no app window showing underneath nav bar. (e.g., The screen is locked.)
+                // Let system windows (ex: notification shade) draw nav bar background.
+                || mNavBarBackgroundWindowCandidate == null;
 
         if (mNavBarOpacityMode == NAV_BAR_FORCE_TRANSPARENT) {
             if (drawBackground) {
@@ -2544,7 +2543,7 @@
             }
         }
 
-        if (!isFullyTransparentAllowed(mNavBarBackgroundWindow, Type.navigationBars())) {
+        if (!isFullyTransparentAllowed(navBackgroundWin, Type.navigationBars())) {
             appearance |= APPEARANCE_SEMI_TRANSPARENT_NAVIGATION_BARS;
         }
 
@@ -2555,11 +2554,26 @@
         return appearance & ~APPEARANCE_OPAQUE_NAVIGATION_BARS;
     }
 
+    @VisibleForTesting
+    @Nullable
+    static WindowState chooseNavigationBackgroundWindow(WindowState candidate,
+            WindowState imeWindow, @NavigationBarPosition int navBarPosition) {
+        if (imeWindow != null && imeWindow.isVisible() && navBarPosition == NAV_BAR_BOTTOM
+                && drawsBarBackground(imeWindow)) {
+            return imeWindow;
+        }
+        if (drawsBarBackground(candidate)) {
+            return candidate;
+        }
+        return null;
+    }
+
     private boolean isImmersiveMode(WindowState win) {
         if (win == null) {
             return false;
         }
-        if (win == getNotificationShade() || win.isActivityTypeDream()) {
+        if (win.mPolicy.getWindowLayerLw(win) > win.mPolicy.getWindowLayerFromTypeLw(
+                WindowManager.LayoutParams.TYPE_STATUS_BAR) || win.isActivityTypeDream()) {
             return false;
         }
         return getInsetsPolicy().hasHiddenSources(Type.navigationBars());
@@ -2727,9 +2741,9 @@
             pw.print(prefix); pw.print("mNavBarColorWindowCandidate=");
             pw.println(mNavBarColorWindowCandidate);
         }
-        if (mNavBarBackgroundWindow != null) {
-            pw.print(prefix); pw.print("mNavBarBackgroundWindow=");
-            pw.println(mNavBarBackgroundWindow);
+        if (mNavBarBackgroundWindowCandidate != null) {
+            pw.print(prefix); pw.print("mNavBarBackgroundWindowCandidate=");
+            pw.println(mNavBarBackgroundWindowCandidate);
         }
         if (mLastStatusBarAppearanceRegions != null) {
             pw.print(prefix); pw.println("mLastStatusBarAppearanceRegions=");
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index b681c19..99cbdde 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -977,6 +977,8 @@
                 return false;
             case IWindowManager.FIXED_TO_USER_ROTATION_ENABLED:
                 return true;
+            case IWindowManager.FIXED_TO_USER_ROTATION_IF_NO_AUTO_ROTATION:
+                return false;
             default:
                 return mDefaultFixedToUserRotation;
         }
@@ -1290,9 +1292,13 @@
             // Application just wants to remain locked in the last rotation.
             preferredRotation = lastRotation;
         } else if (!mSupportAutoRotation) {
-            // If we don't support auto-rotation then bail out here and ignore
-            // the sensor and any rotation lock settings.
-            preferredRotation = -1;
+            if (mFixedToUserRotation == IWindowManager.FIXED_TO_USER_ROTATION_IF_NO_AUTO_ROTATION) {
+                preferredRotation = mUserRotation;
+            } else {
+                // If we don't support auto-rotation then bail out here and ignore
+                // the sensor and any rotation lock settings.
+                preferredRotation = -1;
+            }
         } else if (((mUserRotationMode == WindowManagerPolicy.USER_ROTATION_FREE
                             || isTabletopAutoRotateOverrideEnabled())
                         && (orientation == ActivityInfo.SCREEN_ORIENTATION_USER
diff --git a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
index 2b34bb2..f96f99d 100644
--- a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
@@ -336,8 +336,7 @@
      * </ul>
      */
     private boolean isTreatmentEnabledForDisplay() {
-        return mWmService.mLetterboxConfiguration.isCameraCompatTreatmentEnabled(
-                    /* checkDeviceConfig */ true)
+        return mWmService.mLetterboxConfiguration.isCameraCompatTreatmentEnabled()
                 && mDisplayContent.getIgnoreOrientationRequest()
                 // TODO(b/225928882): Support camera compat rotation for external displays
                 && mDisplayContent.getDisplay().getType() == TYPE_INTERNAL;
diff --git a/services/core/java/com/android/server/wm/DisplayRotationImmersiveAppCompatPolicy.java b/services/core/java/com/android/server/wm/DisplayRotationImmersiveAppCompatPolicy.java
index 74494dd..de70c4d 100644
--- a/services/core/java/com/android/server/wm/DisplayRotationImmersiveAppCompatPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayRotationImmersiveAppCompatPolicy.java
@@ -44,7 +44,7 @@
             @NonNull final DisplayRotation displayRotation,
             @NonNull final DisplayContent displayContent) {
         if (!letterboxConfiguration
-                .isDisplayRotationImmersiveAppCompatPolicyEnabled(/* checkDeviceConfig */ false)) {
+                .isDisplayRotationImmersiveAppCompatPolicyEnabledAtBuildTime()) {
             return null;
         }
 
@@ -87,8 +87,7 @@
      * @return {@code true}, if there is a need to lock screen rotation, {@code false} otherwise.
      */
     boolean isRotationLockEnforced(@Surface.Rotation final int proposedRotation) {
-        if (!mLetterboxConfiguration.isDisplayRotationImmersiveAppCompatPolicyEnabled(
-                /* checkDeviceConfig */ true)) {
+        if (!mLetterboxConfiguration.isDisplayRotationImmersiveAppCompatPolicyEnabled()) {
             return false;
         }
         synchronized (mDisplayContent.mWmService.mGlobalLock) {
diff --git a/services/core/java/com/android/server/wm/DisplayWindowSettings.java b/services/core/java/com/android/server/wm/DisplayWindowSettings.java
index 4c0435e..e1753d7 100644
--- a/services/core/java/com/android/server/wm/DisplayWindowSettings.java
+++ b/services/core/java/com/android/server/wm/DisplayWindowSettings.java
@@ -33,6 +33,7 @@
 import android.view.DisplayInfo;
 import android.view.IWindowManager;
 import android.view.Surface;
+import android.view.WindowManager;
 import android.view.WindowManager.DisplayImePolicy;
 
 import com.android.server.policy.WindowManagerPolicy;
@@ -45,15 +46,20 @@
  * delegates the persistence and lookup of settings values to the supplied {@link SettingsProvider}.
  */
 class DisplayWindowSettings {
+    @NonNull
     private final WindowManagerService mService;
+    @NonNull
     private final SettingsProvider mSettingsProvider;
 
-    DisplayWindowSettings(WindowManagerService service, SettingsProvider settingsProvider) {
+    DisplayWindowSettings(@NonNull WindowManagerService service,
+            @NonNull SettingsProvider settingsProvider) {
         mService = service;
         mSettingsProvider = settingsProvider;
     }
 
-    void setUserRotation(DisplayContent displayContent, int rotationMode, int rotation) {
+    void setUserRotation(@NonNull DisplayContent displayContent,
+            @WindowManagerPolicy.UserRotationMode int rotationMode,
+            @Surface.Rotation int rotation) {
         final DisplayInfo displayInfo = displayContent.getDisplayInfo();
         final SettingsProvider.SettingsEntry overrideSettings =
                 mSettingsProvider.getOverrideSettings(displayInfo);
@@ -62,7 +68,7 @@
         mSettingsProvider.updateOverrideSettings(displayInfo, overrideSettings);
     }
 
-    void setForcedSize(DisplayContent displayContent, int width, int height) {
+    void setForcedSize(@NonNull DisplayContent displayContent, int width, int height) {
         if (displayContent.isDefaultDisplay) {
             final String sizeString = (width == 0 || height == 0) ? "" : (width + "," + height);
             Settings.Global.putString(mService.mContext.getContentResolver(),
@@ -77,21 +83,20 @@
         mSettingsProvider.updateOverrideSettings(displayInfo, overrideSettings);
     }
 
-    void setForcedDensity(DisplayInfo info, int density, int userId) {
+    void setForcedDensity(@NonNull DisplayInfo info, int density, int userId) {
         if (info.displayId == Display.DEFAULT_DISPLAY) {
             final String densityString = density == 0 ? "" : Integer.toString(density);
             Settings.Secure.putStringForUser(mService.mContext.getContentResolver(),
                     Settings.Secure.DISPLAY_DENSITY_FORCED, densityString, userId);
         }
 
-        final DisplayInfo displayInfo = info;
         final SettingsProvider.SettingsEntry overrideSettings =
-                mSettingsProvider.getOverrideSettings(displayInfo);
+                mSettingsProvider.getOverrideSettings(info);
         overrideSettings.mForcedDensity = density;
-        mSettingsProvider.updateOverrideSettings(displayInfo, overrideSettings);
+        mSettingsProvider.updateOverrideSettings(info, overrideSettings);
     }
 
-    void setForcedScalingMode(DisplayContent displayContent, @ForceScalingMode int mode) {
+    void setForcedScalingMode(@NonNull DisplayContent displayContent, @ForceScalingMode int mode) {
         if (displayContent.isDefaultDisplay) {
             Settings.Global.putInt(mService.mContext.getContentResolver(),
                     Settings.Global.DISPLAY_SCALING_FORCE, mode);
@@ -104,7 +109,7 @@
         mSettingsProvider.updateOverrideSettings(displayInfo, overrideSettings);
     }
 
-    void setFixedToUserRotation(DisplayContent displayContent, int fixedToUserRotation) {
+    void setFixedToUserRotation(@NonNull DisplayContent displayContent, int fixedToUserRotation) {
         final DisplayInfo displayInfo = displayContent.getDisplayInfo();
         final SettingsProvider.SettingsEntry overrideSettings =
                 mSettingsProvider.getOverrideSettings(displayInfo);
@@ -112,8 +117,8 @@
         mSettingsProvider.updateOverrideSettings(displayInfo, overrideSettings);
     }
 
-    void setIgnoreOrientationRequest(
-            DisplayContent displayContent, boolean ignoreOrientationRequest) {
+    void setIgnoreOrientationRequest(@NonNull DisplayContent displayContent,
+            boolean ignoreOrientationRequest) {
         final DisplayInfo displayInfo = displayContent.getDisplayInfo();
         final SettingsProvider.SettingsEntry overrideSettings =
                 mSettingsProvider.getOverrideSettings(displayInfo);
@@ -121,7 +126,9 @@
         mSettingsProvider.updateOverrideSettings(displayInfo, overrideSettings);
     }
 
-    private int getWindowingModeLocked(SettingsProvider.SettingsEntry settings, DisplayContent dc) {
+    @WindowConfiguration.WindowingMode
+    private int getWindowingModeLocked(@NonNull SettingsProvider.SettingsEntry settings,
+            @NonNull DisplayContent dc) {
         int windowingMode = settings.mWindowingMode;
         // This display used to be in freeform, but we don't support freeform anymore, so fall
         // back to fullscreen.
@@ -139,13 +146,15 @@
         return windowingMode;
     }
 
-    int getWindowingModeLocked(DisplayContent dc) {
+    @WindowConfiguration.WindowingMode
+    int getWindowingModeLocked(@NonNull DisplayContent dc) {
         final DisplayInfo displayInfo = dc.getDisplayInfo();
         final SettingsProvider.SettingsEntry settings = mSettingsProvider.getSettings(displayInfo);
         return getWindowingModeLocked(settings, dc);
     }
 
-    void setWindowingModeLocked(DisplayContent dc, int mode) {
+    void setWindowingModeLocked(@NonNull DisplayContent dc,
+            @WindowConfiguration.WindowingMode int mode) {
         final DisplayInfo displayInfo = dc.getDisplayInfo();
         final SettingsProvider.SettingsEntry overrideSettings =
                 mSettingsProvider.getOverrideSettings(displayInfo);
@@ -157,7 +166,8 @@
         mSettingsProvider.updateOverrideSettings(displayInfo, overrideSettings);
     }
 
-    int getRemoveContentModeLocked(DisplayContent dc) {
+    @WindowManager.RemoveContentMode
+    int getRemoveContentModeLocked(@NonNull DisplayContent dc) {
         final DisplayInfo displayInfo = dc.getDisplayInfo();
         final SettingsProvider.SettingsEntry settings = mSettingsProvider.getSettings(displayInfo);
         if (settings.mRemoveContentMode == REMOVE_CONTENT_MODE_UNDEFINED) {
@@ -171,7 +181,8 @@
         return settings.mRemoveContentMode;
     }
 
-    void setRemoveContentModeLocked(DisplayContent dc, int mode) {
+    void setRemoveContentModeLocked(@NonNull DisplayContent dc,
+            @WindowManager.RemoveContentMode int mode) {
         final DisplayInfo displayInfo = dc.getDisplayInfo();
         final SettingsProvider.SettingsEntry overrideSettings =
                 mSettingsProvider.getOverrideSettings(displayInfo);
@@ -179,14 +190,14 @@
         mSettingsProvider.updateOverrideSettings(displayInfo, overrideSettings);
     }
 
-    boolean shouldShowWithInsecureKeyguardLocked(DisplayContent dc) {
+    boolean shouldShowWithInsecureKeyguardLocked(@NonNull DisplayContent dc) {
         final DisplayInfo displayInfo = dc.getDisplayInfo();
         final SettingsProvider.SettingsEntry settings = mSettingsProvider.getSettings(displayInfo);
         return settings.mShouldShowWithInsecureKeyguard != null
                 ? settings.mShouldShowWithInsecureKeyguard : false;
     }
 
-    void setShouldShowWithInsecureKeyguardLocked(DisplayContent dc, boolean shouldShow) {
+    void setShouldShowWithInsecureKeyguardLocked(@NonNull DisplayContent dc, boolean shouldShow) {
         if (!dc.isPrivate() && shouldShow) {
             throw new IllegalArgumentException("Public display can't be allowed to show content"
                     + " when locked");
@@ -199,7 +210,7 @@
         mSettingsProvider.updateOverrideSettings(displayInfo, overrideSettings);
     }
 
-    void setDontMoveToTop(DisplayContent dc, boolean dontMoveToTop) {
+    void setDontMoveToTop(@NonNull DisplayContent dc, boolean dontMoveToTop) {
         DisplayInfo displayInfo = dc.getDisplayInfo();
         SettingsProvider.SettingsEntry overrideSettings =
                 mSettingsProvider.getSettings(displayInfo);
@@ -207,7 +218,7 @@
         mSettingsProvider.updateOverrideSettings(displayInfo, overrideSettings);
     }
 
-    boolean shouldShowSystemDecorsLocked(DisplayContent dc) {
+    boolean shouldShowSystemDecorsLocked(@NonNull DisplayContent dc) {
         if (dc.getDisplayId() == Display.DEFAULT_DISPLAY) {
             // Default display should show system decors.
             return true;
@@ -218,7 +229,7 @@
         return settings.mShouldShowSystemDecors != null ? settings.mShouldShowSystemDecors : false;
     }
 
-    void setShouldShowSystemDecorsLocked(DisplayContent dc, boolean shouldShow) {
+    void setShouldShowSystemDecorsLocked(@NonNull DisplayContent dc, boolean shouldShow) {
         final DisplayInfo displayInfo = dc.getDisplayInfo();
         final SettingsProvider.SettingsEntry overrideSettings =
                 mSettingsProvider.getOverrideSettings(displayInfo);
@@ -226,7 +237,8 @@
         mSettingsProvider.updateOverrideSettings(displayInfo, overrideSettings);
     }
 
-    @DisplayImePolicy int getImePolicyLocked(DisplayContent dc) {
+    @DisplayImePolicy
+    int getImePolicyLocked(@NonNull DisplayContent dc) {
         if (dc.getDisplayId() == Display.DEFAULT_DISPLAY) {
             // Default display should show IME.
             return DISPLAY_IME_POLICY_LOCAL;
@@ -238,7 +250,7 @@
                 : DISPLAY_IME_POLICY_FALLBACK_DISPLAY;
     }
 
-    void setDisplayImePolicy(DisplayContent dc, @DisplayImePolicy int imePolicy) {
+    void setDisplayImePolicy(@NonNull DisplayContent dc, @DisplayImePolicy int imePolicy) {
         final DisplayInfo displayInfo = dc.getDisplayInfo();
         final SettingsProvider.SettingsEntry overrideSettings =
                 mSettingsProvider.getOverrideSettings(displayInfo);
@@ -246,11 +258,11 @@
         mSettingsProvider.updateOverrideSettings(displayInfo, overrideSettings);
     }
 
-    void applySettingsToDisplayLocked(DisplayContent dc) {
+    void applySettingsToDisplayLocked(@NonNull DisplayContent dc) {
         applySettingsToDisplayLocked(dc, /* includeRotationSettings */ true);
     }
 
-    void applySettingsToDisplayLocked(DisplayContent dc, boolean includeRotationSettings) {
+    void applySettingsToDisplayLocked(@NonNull DisplayContent dc, boolean includeRotationSettings) {
         final DisplayInfo displayInfo = dc.getDisplayInfo();
         final SettingsProvider.SettingsEntry settings = mSettingsProvider.getSettings(displayInfo);
 
@@ -274,9 +286,8 @@
         dc.mIsDensityForced = hasDensityOverride;
         dc.mIsSizeForced = hasSizeOverride;
 
-        final boolean ignoreDisplayCutout = settings.mIgnoreDisplayCutout != null
+        dc.mIgnoreDisplayCutout = settings.mIgnoreDisplayCutout != null
                 ? settings.mIgnoreDisplayCutout : false;
-        dc.mIgnoreDisplayCutout = ignoreDisplayCutout;
 
         final int width = hasSizeOverride ? settings.mForcedWidth : dc.mInitialDisplayWidth;
         final int height = hasSizeOverride ? settings.mForcedHeight : dc.mInitialDisplayHeight;
@@ -296,7 +307,7 @@
         if (includeRotationSettings) applyRotationSettingsToDisplayLocked(dc);
     }
 
-    void applyRotationSettingsToDisplayLocked(DisplayContent dc) {
+    void applyRotationSettingsToDisplayLocked(@NonNull DisplayContent dc) {
         final DisplayInfo displayInfo = dc.getDisplayInfo();
         final SettingsProvider.SettingsEntry settings = mSettingsProvider.getSettings(displayInfo);
 
@@ -315,7 +326,7 @@
      * @return {@code true} if any settings for this display has changed; {@code false} if nothing
      * changed.
      */
-    boolean updateSettingsForDisplay(DisplayContent dc) {
+    boolean updateSettingsForDisplay(@NonNull DisplayContent dc) {
         final TaskDisplayArea defaultTda = dc.getDefaultTaskDisplayArea();
         if (defaultTda != null && defaultTda.getWindowingMode() != getWindowingModeLocked(dc)) {
             // For the time being the only thing that may change is windowing mode, so just update
@@ -327,6 +338,13 @@
     }
 
     /**
+     * Called when the given {@link DisplayContent} is removed to cleanup.
+     */
+    void onDisplayRemoved(@NonNull DisplayContent dc) {
+        mSettingsProvider.onDisplayRemoved(dc.getDisplayInfo());
+    }
+
+    /**
      * Provides the functionality to lookup the {@link SettingsEntry settings} for a given
      * {@link DisplayInfo}.
      * <p>
@@ -364,19 +382,29 @@
         void updateOverrideSettings(@NonNull DisplayInfo info, @NonNull SettingsEntry overrides);
 
         /**
+         * Called when a display is removed to cleanup.
+         */
+        void onDisplayRemoved(@NonNull DisplayInfo info);
+
+        /**
          * Settings for a display.
          */
         class SettingsEntry {
+            @WindowConfiguration.WindowingMode
             int mWindowingMode = WindowConfiguration.WINDOWING_MODE_UNDEFINED;
             @Nullable
+            @WindowManagerPolicy.UserRotationMode
             Integer mUserRotationMode;
             @Nullable
+            @Surface.Rotation
             Integer mUserRotation;
             int mForcedWidth;
             int mForcedHeight;
             int mForcedDensity;
             @Nullable
+            @ForceScalingMode
             Integer mForcedScalingMode;
+            @WindowManager.RemoveContentMode
             int mRemoveContentMode = REMOVE_CONTENT_MODE_UNDEFINED;
             @Nullable
             Boolean mShouldShowWithInsecureKeyguard;
@@ -395,7 +423,7 @@
 
             SettingsEntry() {}
 
-            SettingsEntry(SettingsEntry copyFrom) {
+            SettingsEntry(@NonNull SettingsEntry copyFrom) {
                 setTo(copyFrom);
             }
 
diff --git a/services/core/java/com/android/server/wm/DisplayWindowSettingsProvider.java b/services/core/java/com/android/server/wm/DisplayWindowSettingsProvider.java
index 1abb0a1..ea668fa 100644
--- a/services/core/java/com/android/server/wm/DisplayWindowSettingsProvider.java
+++ b/services/core/java/com/android/server/wm/DisplayWindowSettingsProvider.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wm;
 
+import static android.view.Display.TYPE_VIRTUAL;
 import static android.view.WindowManager.DISPLAY_IME_POLICY_FALLBACK_DISPLAY;
 import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL;
 import static android.view.WindowManager.REMOVE_CONTENT_MODE_UNDEFINED;
@@ -28,6 +29,8 @@
 import android.annotation.Nullable;
 import android.app.WindowConfiguration;
 import android.os.Environment;
+import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.AtomicFile;
 import android.util.Slog;
 import android.util.Xml;
@@ -49,7 +52,6 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
-import java.util.HashMap;
 import java.util.Map;
 
 /**
@@ -86,7 +88,9 @@
         void finishWrite(OutputStream os, boolean success);
     }
 
+    @NonNull
     private ReadableSettings mBaseSettings;
+    @NonNull
     private final WritableSettings mOverrideSettings;
 
     DisplayWindowSettingsProvider() {
@@ -155,6 +159,16 @@
         mOverrideSettings.updateSettingsEntry(info, overrides);
     }
 
+    @Override
+    public void onDisplayRemoved(@NonNull DisplayInfo info) {
+        mOverrideSettings.onDisplayRemoved(info);
+    }
+
+    @VisibleForTesting
+    int getOverrideSettingsSize() {
+        return mOverrideSettings.mSettings.size();
+    }
+
     /**
      * Class that allows reading {@link SettingsEntry entries} from a
      * {@link ReadableSettingsStorage}.
@@ -168,14 +182,15 @@
          */
         @DisplayIdentifierType
         protected int mIdentifierType;
-        protected final Map<String, SettingsEntry> mSettings = new HashMap<>();
+        @NonNull
+        protected final ArrayMap<String, SettingsEntry> mSettings = new ArrayMap<>();
 
-        ReadableSettings(ReadableSettingsStorage settingsStorage) {
+        ReadableSettings(@NonNull ReadableSettingsStorage settingsStorage) {
             loadSettings(settingsStorage);
         }
 
         @Nullable
-        final SettingsEntry getSettingsEntry(DisplayInfo info) {
+        final SettingsEntry getSettingsEntry(@NonNull DisplayInfo info) {
             final String identifier = getIdentifier(info);
             SettingsEntry settings;
             // Try to get corresponding settings using preferred identifier for the current config.
@@ -193,7 +208,8 @@
         }
 
         /** Gets the identifier of choice for the current config. */
-        protected final String getIdentifier(DisplayInfo displayInfo) {
+        @NonNull
+        protected final String getIdentifier(@NonNull DisplayInfo displayInfo) {
             if (mIdentifierType == IDENTIFIER_PORT && displayInfo.address != null) {
                 // Config suggests using port as identifier for physical displays.
                 if (displayInfo.address instanceof DisplayAddress.Physical) {
@@ -203,7 +219,7 @@
             return displayInfo.uniqueId;
         }
 
-        private void loadSettings(ReadableSettingsStorage settingsStorage) {
+        private void loadSettings(@NonNull ReadableSettingsStorage settingsStorage) {
             FileData fileData = readSettings(settingsStorage);
             if (fileData != null) {
                 mIdentifierType = fileData.mIdentifierType;
@@ -217,15 +233,18 @@
      * {@link WritableSettingsStorage}.
      */
     private static final class WritableSettings extends ReadableSettings {
+        @NonNull
         private final WritableSettingsStorage mSettingsStorage;
+        @NonNull
+        private final ArraySet<String> mVirtualDisplayIdentifiers = new ArraySet<>();
 
-        WritableSettings(WritableSettingsStorage settingsStorage) {
+        WritableSettings(@NonNull WritableSettingsStorage settingsStorage) {
             super(settingsStorage);
             mSettingsStorage = settingsStorage;
         }
 
         @NonNull
-        SettingsEntry getOrCreateSettingsEntry(DisplayInfo info) {
+        SettingsEntry getOrCreateSettingsEntry(@NonNull DisplayInfo info) {
             final String identifier = getIdentifier(info);
             SettingsEntry settings;
             // Try to get corresponding settings using preferred identifier for the current config.
@@ -243,21 +262,47 @@
 
             settings = new SettingsEntry();
             mSettings.put(identifier, settings);
+            if (info.type == TYPE_VIRTUAL) {
+                // Keep track of virtual display. We don't want to write virtual display settings to
+                // file.
+                mVirtualDisplayIdentifiers.add(identifier);
+            }
             return settings;
         }
 
-        void updateSettingsEntry(DisplayInfo info, SettingsEntry settings) {
+        void updateSettingsEntry(@NonNull DisplayInfo info, @NonNull SettingsEntry settings) {
             final SettingsEntry overrideSettings = getOrCreateSettingsEntry(info);
             final boolean changed = overrideSettings.setTo(settings);
-            if (changed) {
+            if (changed && info.type != TYPE_VIRTUAL) {
                 writeSettings();
             }
         }
 
+        void onDisplayRemoved(@NonNull DisplayInfo info) {
+            final String identifier = getIdentifier(info);
+            if (!mSettings.containsKey(identifier)) {
+                return;
+            }
+            if (mVirtualDisplayIdentifiers.remove(identifier)
+                    || mSettings.get(identifier).isEmpty()) {
+                // Don't keep track of virtual display or empty settings to avoid growing the cached
+                // map.
+                mSettings.remove(identifier);
+            }
+        }
+
         private void writeSettings() {
-            FileData fileData = new FileData();
+            final FileData fileData = new FileData();
             fileData.mIdentifierType = mIdentifierType;
-            fileData.mSettings.putAll(mSettings);
+            final int size = mSettings.size();
+            for (int i = 0; i < size; i++) {
+                final String identifier = mSettings.keyAt(i);
+                if (mVirtualDisplayIdentifiers.contains(identifier)) {
+                    // Do not write virtual display settings to file.
+                    continue;
+                }
+                fileData.mSettings.put(identifier, mSettings.get(identifier));
+            }
             DisplayWindowSettingsProvider.writeSettings(mSettingsStorage, fileData);
         }
     }
@@ -283,7 +328,7 @@
     }
 
     @Nullable
-    private static FileData readSettings(ReadableSettingsStorage storage) {
+    private static FileData readSettings(@NonNull ReadableSettingsStorage storage) {
         InputStream stream;
         try {
             stream = storage.openRead();
@@ -348,13 +393,14 @@
         return fileData;
     }
 
-    private static int getIntAttribute(TypedXmlPullParser parser, String name, int defaultValue) {
+    private static int getIntAttribute(@NonNull TypedXmlPullParser parser, @NonNull String name,
+            int defaultValue) {
         return parser.getAttributeInt(null, name, defaultValue);
     }
 
     @Nullable
-    private static Integer getIntegerAttribute(TypedXmlPullParser parser, String name,
-            @Nullable Integer defaultValue) {
+    private static Integer getIntegerAttribute(@NonNull TypedXmlPullParser parser,
+            @NonNull String name, @Nullable Integer defaultValue) {
         try {
             return parser.getAttributeInt(null, name);
         } catch (Exception ignored) {
@@ -363,8 +409,8 @@
     }
 
     @Nullable
-    private static Boolean getBooleanAttribute(TypedXmlPullParser parser, String name,
-            @Nullable Boolean defaultValue) {
+    private static Boolean getBooleanAttribute(@NonNull TypedXmlPullParser parser,
+            @NonNull String name, @Nullable Boolean defaultValue) {
         try {
             return parser.getAttributeBoolean(null, name);
         } catch (Exception ignored) {
@@ -372,7 +418,7 @@
         }
     }
 
-    private static void readDisplay(TypedXmlPullParser parser, FileData fileData)
+    private static void readDisplay(@NonNull TypedXmlPullParser parser, @NonNull FileData fileData)
             throws NumberFormatException, XmlPullParserException, IOException {
         String name = parser.getAttributeValue(null, "name");
         if (name != null) {
@@ -420,7 +466,7 @@
         XmlUtils.skipCurrentTag(parser);
     }
 
-    private static void readConfig(TypedXmlPullParser parser, FileData fileData)
+    private static void readConfig(@NonNull TypedXmlPullParser parser, @NonNull FileData fileData)
             throws NumberFormatException,
             XmlPullParserException, IOException {
         fileData.mIdentifierType = getIntAttribute(parser, "identifier",
@@ -428,7 +474,8 @@
         XmlUtils.skipCurrentTag(parser);
     }
 
-    private static void writeSettings(WritableSettingsStorage storage, FileData data) {
+    private static void writeSettings(@NonNull WritableSettingsStorage storage,
+            @NonNull FileData data) {
         OutputStream stream;
         try {
             stream = storage.startWrite();
@@ -525,7 +572,8 @@
 
     private static final class FileData {
         int mIdentifierType;
-        final Map<String, SettingsEntry> mSettings = new HashMap<>();
+        @NonNull
+        final Map<String, SettingsEntry> mSettings = new ArrayMap<>();
 
         @Override
         public String toString() {
@@ -537,6 +585,7 @@
     }
 
     private static final class AtomicFileStorage implements WritableSettingsStorage {
+        @NonNull
         private final AtomicFile mAtomicFile;
 
         AtomicFileStorage(@NonNull AtomicFile atomicFile) {
diff --git a/services/core/java/com/android/server/wm/DragDropController.java b/services/core/java/com/android/server/wm/DragDropController.java
index 2583514..4a3d0c1 100644
--- a/services/core/java/com/android/server/wm/DragDropController.java
+++ b/services/core/java/com/android/server/wm/DragDropController.java
@@ -181,7 +181,11 @@
                     }
                 } finally {
                     if (surface != null) {
-                        surface.release();
+                        try (final SurfaceControl.Transaction transaction =
+                                mService.mTransactionFactory.get()) {
+                            transaction.remove(surface);
+                            transaction.apply();
+                        }
                     }
                 }
             }
diff --git a/services/core/java/com/android/server/wm/EmbeddedWindowController.java b/services/core/java/com/android/server/wm/EmbeddedWindowController.java
index 44d6768..98027bb 100644
--- a/services/core/java/com/android/server/wm/EmbeddedWindowController.java
+++ b/services/core/java/com/android/server/wm/EmbeddedWindowController.java
@@ -23,6 +23,7 @@
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 import static com.android.server.wm.WindowStateProto.IDENTIFIER;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -217,10 +218,10 @@
                     mHostWindowState.mInputWindowHandle.getInputApplicationHandle());
         }
 
-        InputChannel openInputChannel() {
+        void openInputChannel(@NonNull InputChannel outInputChannel) {
             final String name = toString();
             mInputChannel = mWmService.mInputManager.createInputChannel(name);
-            return mInputChannel;
+            mInputChannel.copyTo(outInputChannel);
         }
 
         void onRemoved() {
diff --git a/services/core/java/com/android/server/wm/InputManagerCallback.java b/services/core/java/com/android/server/wm/InputManagerCallback.java
index 0a47fe0..20595ea 100644
--- a/services/core/java/com/android/server/wm/InputManagerCallback.java
+++ b/services/core/java/com/android/server/wm/InputManagerCallback.java
@@ -128,6 +128,21 @@
         }
     }
 
+    /** Notifies that the pointer location configuration has changed. */
+    @Override
+    public void notifyPointerLocationChanged(boolean pointerLocationEnabled) {
+        if (mService.mPointerLocationEnabled == pointerLocationEnabled) {
+            return;
+        }
+
+        synchronized (mService.mGlobalLock) {
+            mService.mPointerLocationEnabled = pointerLocationEnabled;
+            mService.mRoot.forAllDisplayPolicies(
+                    p -> p.setPointerLocationEnabled(mService.mPointerLocationEnabled)
+            );
+        }
+    }
+
     /** Notifies that the lid switch changed state. */
     @Override
     public void notifyLidSwitchChanged(long whenNanos, boolean lidOpen) {
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index daa823c..835c92d 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -21,12 +21,7 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-import static android.view.InsetsController.ANIMATION_TYPE_HIDE;
-import static android.view.InsetsController.ANIMATION_TYPE_SHOW;
-import static android.view.InsetsController.LAYOUT_INSETS_DURING_ANIMATION_HIDDEN;
-import static android.view.InsetsController.LAYOUT_INSETS_DURING_ANIMATION_SHOWN;
 import static android.view.InsetsSource.ID_IME;
-import static android.view.SyncRtSurfaceTransactionApplier.applyParams;
 import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION;
@@ -39,16 +34,13 @@
 import android.app.WindowConfiguration;
 import android.content.ComponentName;
 import android.content.res.Resources;
+import android.os.Handler;
+import android.os.IBinder;
 import android.util.SparseArray;
-import android.view.InsetsAnimationControlCallbacks;
-import android.view.InsetsAnimationControlImpl;
-import android.view.InsetsAnimationControlRunner;
 import android.view.InsetsController;
 import android.view.InsetsFrameProvider;
 import android.view.InsetsSource;
-import android.view.InsetsSourceControl;
 import android.view.InsetsState;
-import android.view.InternalInsetsAnimationController;
 import android.view.SurfaceControl;
 import android.view.SyncRtSurfaceTransactionApplier;
 import android.view.WindowInsets;
@@ -56,14 +48,16 @@
 import android.view.WindowInsets.Type.InsetsType;
 import android.view.WindowInsetsAnimation;
 import android.view.WindowInsetsAnimation.Bounds;
-import android.view.WindowInsetsAnimationControlListener;
 import android.view.WindowManager;
+import android.view.inputmethod.InputMethodManager;
 
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.DisplayThread;
 import com.android.server.statusbar.StatusBarManagerInternal;
 
+import java.io.PrintWriter;
+import java.util.List;
+
 /**
  * Policy that implements who gets control over the windows generating insets.
  */
@@ -77,47 +71,19 @@
     private final DisplayContent mDisplayContent;
     private final DisplayPolicy mPolicy;
 
-    /** For resetting visibilities of insets sources. */
-    private final InsetsControlTarget mDummyControlTarget = new InsetsControlTarget() {
+    /** Used to show system bars transiently. This won't affect the layout. */
+    private final InsetsControlTarget mTransientControlTarget;
 
-        @Override
-        public void notifyInsetsControlChanged() {
-            boolean hasLeash = false;
-            final InsetsSourceControl[] controls =
-                    mStateController.getControlsForDispatch(this);
-            if (controls == null) {
-                return;
-            }
-            for (InsetsSourceControl control : controls) {
-                if (isTransient(control.getType())) {
-                    // The visibilities of transient bars will be handled with animations.
-                    continue;
-                }
-                final SurfaceControl leash = control.getLeash();
-                if (leash != null) {
-                    hasLeash = true;
-
-                    // We use alpha to control the visibility here which aligns the logic at
-                    // SurfaceAnimator.createAnimationLeash
-                    final boolean visible =
-                            (control.getType() & WindowInsets.Type.defaultVisible()) != 0;
-                    mDisplayContent.getPendingTransaction().setAlpha(leash, visible ? 1f : 0f);
-                }
-            }
-            if (hasLeash) {
-                mDisplayContent.scheduleAnimation();
-            }
-        }
-    };
+    /** Used to show system bars permanently. This will affect the layout. */
+    private final InsetsControlTarget mPermanentControlTarget;
 
     private WindowState mFocusedWin;
     private final BarWindow mStatusBar = new BarWindow(StatusBarManager.WINDOW_STATUS_BAR);
     private final BarWindow mNavBar = new BarWindow(StatusBarManager.WINDOW_NAVIGATION_BAR);
     private @InsetsType int mShowingTransientTypes;
-    private boolean mAnimatingShown;
+    private @InsetsType int mForcedShowingTypes;
 
     private final boolean mHideNavBarForKeyboard;
-    private final float[] mTmpFloat9 = new float[9];
 
     InsetsPolicy(InsetsStateController stateController, DisplayContent displayContent) {
         mStateController = stateController;
@@ -125,9 +91,12 @@
         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");
     }
 
-
     /** Updates the target which can control system bars. */
     void updateBarControlTarget(@Nullable WindowState focusedWin) {
         if (mFocusedWin != focusedWin) {
@@ -142,13 +111,13 @@
         final WindowState topApp = mPolicy.getTopFullscreenOpaqueWindow();
         mStateController.onBarControlTargetChanged(
                 statusControlTarget,
-                statusControlTarget == mDummyControlTarget
+                statusControlTarget == mTransientControlTarget
                         ? getStatusControlTarget(focusedWin, true /* fake */)
                         : statusControlTarget == notificationShade
                                 ? getStatusControlTarget(topApp, true /* fake */)
                                 : null,
                 navControlTarget,
-                navControlTarget == mDummyControlTarget
+                navControlTarget == mTransientControlTarget
                         ? getNavControlTarget(focusedWin, true /* fake */)
                         : navControlTarget == notificationShade
                                 ? getNavControlTarget(topApp, true /* fake */)
@@ -198,19 +167,19 @@
                     mFocusedWin,
                     (showingTransientTypes & (Type.statusBars() | Type.navigationBars())) != 0,
                     isGestureOnSystemBar);
-
-            // The leashes can be created while updating bar control target. The surface transaction
-            // of the new leashes might not be applied yet. The callback posted here ensures we can
-            // get the valid leashes because the surface transaction will be applied in the next
-            // animation frame which will be triggered if a new leash is created.
-            mDisplayContent.mWmService.mAnimator.getChoreographer().postFrameCallback(time -> {
-                synchronized (mDisplayContent.mWmService.mGlobalLock) {
-                    startAnimation(true /* show */, null /* callback */);
-                }
-            });
         }
     }
 
+    @VisibleForTesting
+    InsetsControlTarget getTransientControlTarget() {
+        return  mTransientControlTarget;
+    }
+
+    @VisibleForTesting
+    InsetsControlTarget getPermanentControlTarget() {
+        return  mPermanentControlTarget;
+    }
+
     void hideTransient() {
         if (mShowingTransientTypes == 0) {
             return;
@@ -221,23 +190,8 @@
                 /* areVisible= */ false,
                 /* wereRevealedFromSwipeOnSystemBar= */ false);
 
-        startAnimation(false /* show */, () -> {
-            synchronized (mDisplayContent.mWmService.mGlobalLock) {
-                final SparseArray<InsetsSourceProvider> providers =
-                        mStateController.getSourceProviders();
-                for (int i = providers.size() - 1; i >= 0; i--) {
-                    final InsetsSourceProvider provider = providers.valueAt(i);
-                    if (!isTransient(provider.getSource().getType())) {
-                        continue;
-                    }
-                    // We are about to clear mShowingTransientTypes, we don't want the transient bar
-                    // can cause insets on the client. Restore the client visibility.
-                    provider.setClientVisible(false);
-                }
-                mShowingTransientTypes = 0;
-                updateBarControlTarget(mFocusedWin);
-            }
-        });
+        mShowingTransientTypes = 0;
+        updateBarControlTarget(mFocusedWin);
     }
 
     boolean isTransient(@InsetsType int type) {
@@ -440,8 +394,8 @@
         return originalState;
     }
 
-    void onInsetsModified(InsetsControlTarget caller) {
-        mStateController.onInsetsModified(caller);
+    void onRequestedVisibleTypesChanged(InsetsControlTarget caller) {
+        mStateController.onRequestedVisibleTypesChanged(caller);
         checkAbortTransient(caller);
         updateBarControlTarget(mFocusedWin);
     }
@@ -500,7 +454,7 @@
     private @Nullable InsetsControlTarget getStatusControlTarget(@Nullable WindowState focusedWin,
             boolean fake) {
         if (!fake && isTransient(Type.statusBars())) {
-            return mDummyControlTarget;
+            return mTransientControlTarget;
         }
         final WindowState notificationShade = mPolicy.getNotificationShade();
         if (focusedWin == notificationShade) {
@@ -514,16 +468,16 @@
                     component, focusedWin.getRequestedVisibleTypes());
             return mDisplayContent.mRemoteInsetsControlTarget;
         }
-        if (mPolicy.areSystemBarsForcedShownLw()) {
+        if (areTypesForciblyShowing(Type.statusBars())) {
             // Status bar is forcibly shown. We don't want the client to control the status bar, and
             // we will dispatch the real visibility of status bar to the client.
-            return null;
+            return mPermanentControlTarget;
         }
         if (forceShowsStatusBarTransiently() && !fake) {
             // Status bar is forcibly shown transiently, and its new visibility won't be
             // dispatched to the client so that we can keep the layout stable. We will dispatch the
             // fake control to the client, so that it can re-show the bar during this scenario.
-            return mDummyControlTarget;
+            return mTransientControlTarget;
         }
         if (!canBeTopFullscreenOpaqueWindow(focusedWin)
                 && mPolicy.topAppHidesSystemBar(Type.statusBars())
@@ -554,7 +508,7 @@
             return null;
         }
         if (!fake && isTransient(Type.navigationBars())) {
-            return mDummyControlTarget;
+            return mTransientControlTarget;
         }
         if (focusedWin == mPolicy.getNotificationShade()) {
             // Notification shade has control anyways, no reason to force anything.
@@ -567,13 +521,6 @@
                 return focusedWin;
             }
         }
-        if (forcesShowingNavigationBars(focusedWin)) {
-            // When "force show navigation bar" is enabled, it means both force visible is true, and
-            // we are in 3-button navigation. In this mode, the navigation bar is forcibly shown
-            // when activity type is ACTIVITY_TYPE_STANDARD which means Launcher or Recent could
-            // still control the navigation bar in this mode.
-            return null;
-        }
         if (remoteInsetsControllerControlsSystemBars(focusedWin)) {
             ComponentName component = focusedWin.mActivityRecord != null
                     ? focusedWin.mActivityRecord.mActivityComponent : null;
@@ -581,16 +528,16 @@
                     component, focusedWin.getRequestedVisibleTypes());
             return mDisplayContent.mRemoteInsetsControlTarget;
         }
-        if (mPolicy.areSystemBarsForcedShownLw()) {
+        if (areTypesForciblyShowing(Type.navigationBars())) {
             // Navigation bar is forcibly shown. We don't want the client to control the navigation
             // bar, and we will dispatch the real visibility of navigation bar to the client.
-            return null;
+            return mPermanentControlTarget;
         }
         if (forceShowsNavigationBarTransiently() && !fake) {
             // Navigation bar is forcibly shown transiently, and its new visibility won't be
             // dispatched to the client so that we can keep the layout stable. We will dispatch the
             // fake control to the client, so that it can re-show the bar during this scenario.
-            return mDummyControlTarget;
+            return mTransientControlTarget;
         }
         final WindowState notificationShade = mPolicy.getNotificationShade();
         if (!canBeTopFullscreenOpaqueWindow(focusedWin)
@@ -603,7 +550,32 @@
         return focusedWin;
     }
 
-    boolean forcesShowingNavigationBars(WindowState win) {
+    boolean areTypesForciblyShowing(@InsetsType int types) {
+        return (mForcedShowingTypes & types) == types;
+    }
+
+    void updateSystemBars(WindowState win, boolean inSplitScreenMode, boolean inFreeformMode) {
+        mForcedShowingTypes = (inSplitScreenMode || inFreeformMode)
+                ? (Type.statusBars() | Type.navigationBars())
+                : forceShowingNavigationBars(win)
+                        ? Type.navigationBars()
+                        : 0;
+
+        // The client app won't be able to control these types of system bars. Here makes the client
+        // forcibly consume these types to prevent the app content from getting obscured.
+        mStateController.setForcedConsumingTypes(
+                mForcedShowingTypes | (remoteInsetsControllerControlsSystemBars(win)
+                        ? (Type.statusBars() | Type.navigationBars())
+                        : 0));
+
+        updateBarControlTarget(win);
+    }
+
+    private boolean forceShowingNavigationBars(WindowState win) {
+        // When "force show navigation bar" is enabled, it means both force visible is true, and
+        // we are in 3-button navigation. In this mode, the navigation bar is forcibly shown
+        // when activity type is ACTIVITY_TYPE_STANDARD which means Launcher or Recent could
+        // still control the navigation bar in this mode.
         return mPolicy.isForceShowNavigationBarEnabled() && win != null
                 && win.getActivityType() == ACTIVITY_TYPE_STANDARD;
     }
@@ -642,34 +614,6 @@
                 && (win.mAttrs.privateFlags & PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION) != 0;
     }
 
-    @VisibleForTesting
-    void startAnimation(boolean show, Runnable callback) {
-        @InsetsType int typesReady = 0;
-        final SparseArray<InsetsSourceControl> controlsReady = new SparseArray<>();
-        final InsetsSourceControl[] controls =
-                mStateController.getControlsForDispatch(mDummyControlTarget);
-        if (controls == null) {
-            if (callback != null) {
-                DisplayThread.getHandler().post(callback);
-            }
-            return;
-        }
-        for (InsetsSourceControl control : controls) {
-            if (isTransient(control.getType()) && control.getLeash() != null) {
-                typesReady |= control.getType();
-                controlsReady.put(control.getId(), new InsetsSourceControl(control));
-            }
-        }
-        controlAnimationUnchecked(typesReady, controlsReady, show, callback);
-    }
-
-    private void controlAnimationUnchecked(int typesReady,
-            SparseArray<InsetsSourceControl> controls, boolean show, Runnable callback) {
-        InsetsPolicyAnimationControlListener listener =
-                new InsetsPolicyAnimationControlListener(show, callback, typesReady);
-        listener.mControlCallbacks.controlAnimationUnchecked(typesReady, controls, show);
-    }
-
     private void dispatchTransientSystemBarsVisibilityChanged(
             @Nullable WindowState focusedWindow,
             boolean areVisible,
@@ -696,6 +640,21 @@
                         wereRevealedFromSwipeOnSystemBar);
     }
 
+    void dump(String prefix, PrintWriter pw) {
+        pw.println(prefix + "InsetsPolicy");
+        prefix = prefix + "  ";
+        pw.println(prefix + "status: " + StatusBarManager.windowStateToString(mStatusBar.mState));
+        pw.println(prefix + "nav: " + StatusBarManager.windowStateToString(mNavBar.mState));
+        if (mShowingTransientTypes != 0) {
+            pw.println(prefix + "mShowingTransientTypes="
+                    + WindowInsets.Type.toString(mShowingTransientTypes));
+        }
+        if (mForcedShowingTypes != 0) {
+            pw.println(prefix + "mForcedShowingTypes="
+                    + WindowInsets.Type.toString(mForcedShowingTypes));
+        }
+    }
+
     private class BarWindow {
 
         private final int mId;
@@ -725,105 +684,138 @@
         }
     }
 
-    private class InsetsPolicyAnimationControlListener extends
-            InsetsController.InternalAnimationControlListener {
-        Runnable mFinishCallback;
-        InsetsPolicyAnimationControlCallbacks mControlCallbacks;
+    private static class ControlTarget implements InsetsControlTarget {
 
-        InsetsPolicyAnimationControlListener(boolean show, Runnable finishCallback, int types) {
-            super(show, false /* hasCallbacks */, types, BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE,
-                    false /* disable */, 0 /* floatingImeBottomInsets */,
-                    null /* loggingListener */, null /* jankContext */);
-            mFinishCallback = finishCallback;
-            mControlCallbacks = new InsetsPolicyAnimationControlCallbacks(this);
+        private final InsetsState mState = new InsetsState();
+        private final InsetsController mInsetsController;
+        private final InsetsStateController mStateController;
+        private final String mName;
+
+        ControlTarget(InsetsStateController stateController, Handler handler, String name) {
+            mStateController = stateController;
+            mInsetsController = new InsetsController(new Host(handler, name));
+            mName = name;
         }
 
         @Override
-        protected void onAnimationFinish() {
-            super.onAnimationFinish();
-            if (mFinishCallback != null) {
-                DisplayThread.getHandler().post(mFinishCallback);
-            }
+        public void notifyInsetsControlChanged() {
+            mState.set(mStateController.getRawInsetsState(), true /* copySources */);
+            mInsetsController.onStateChanged(mState);
+            mInsetsController.onControlsChanged(mStateController.getControlsForDispatch(this));
         }
 
-        private class InsetsPolicyAnimationControlCallbacks implements
-                InsetsAnimationControlCallbacks {
-            private InsetsAnimationControlImpl mAnimationControl = null;
-            private InsetsPolicyAnimationControlListener mListener;
+        @Override
+        public String toString() {
+            return mName;
+        }
+    }
 
-            InsetsPolicyAnimationControlCallbacks(InsetsPolicyAnimationControlListener listener) {
-                mListener = listener;
+    private static class Host implements InsetsController.Host {
+
+        private final float[] mTmpFloat9 = new float[9];
+        private final Handler mHandler;
+        private final String mName;
+
+        Host(Handler handler, String name) {
+            mHandler = handler;
+            mName = name;
+        }
+
+        @Override
+        public Handler getHandler() {
+            return mHandler;
+        }
+
+        @Override
+        public void notifyInsetsChanged() { }
+
+        @Override
+        public void dispatchWindowInsetsAnimationPrepare(
+                @NonNull WindowInsetsAnimation animation) { }
+
+        @Override
+        public Bounds dispatchWindowInsetsAnimationStart(
+                @NonNull WindowInsetsAnimation animation,
+                @NonNull Bounds bounds) {
+            return bounds;
+        }
+
+        @Override
+        public WindowInsets dispatchWindowInsetsAnimationProgress(
+                @NonNull WindowInsets insets,
+                @NonNull List<WindowInsetsAnimation> runningAnimations) {
+            return insets;
+        }
+
+        @Override
+        public void dispatchWindowInsetsAnimationEnd(
+                @NonNull WindowInsetsAnimation animation) { }
+
+        @Override
+        public void applySurfaceParams(SyncRtSurfaceTransactionApplier.SurfaceParams... p) {
+            final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+            for (int i = p.length - 1; i >= 0; i--) {
+                SyncRtSurfaceTransactionApplier.applyParams(t, p[i], mTmpFloat9);
             }
+            t.apply();
+            t.close();
+        }
 
-            private void controlAnimationUnchecked(int typesReady,
-                    SparseArray<InsetsSourceControl> controls, boolean show) {
-                if (typesReady == 0) {
-                    // nothing to animate.
-                    return;
-                }
-                mAnimatingShown = show;
+        @Override
+        public void updateRequestedVisibleTypes(int types) { }
 
-                final InsetsState state = mFocusedWin.getInsetsState();
+        @Override
+        public boolean hasAnimationCallbacks() {
+            return false;
+        }
 
-                // We are about to playing the default animation. Passing a null frame indicates
-                // the controlled types should be animated regardless of the frame.
-                mAnimationControl = new InsetsAnimationControlImpl(controls,
-                        null /* frame */, state, mListener, typesReady, this,
-                        mListener.getDurationMs(), getInsetsInterpolator(),
-                        show ? ANIMATION_TYPE_SHOW : ANIMATION_TYPE_HIDE, show
-                                ? LAYOUT_INSETS_DURING_ANIMATION_SHOWN
-                                : LAYOUT_INSETS_DURING_ANIMATION_HIDDEN,
-                        null /* translator */, null /* statsToken */);
-                SurfaceAnimationThread.getHandler().post(
-                        () -> mListener.onReady(mAnimationControl, typesReady));
-            }
+        @Override
+        public void setSystemBarsAppearance(int appearance, int mask) { }
 
-            /** Called on SurfaceAnimationThread without global WM lock held. */
-            @Override
-            public void scheduleApplyChangeInsets(InsetsAnimationControlRunner runner) {
-                if (mAnimationControl.applyChangeInsets(null /* outState */)) {
-                    mAnimationControl.finish(mAnimatingShown);
-                }
-            }
+        @Override
+        public int getSystemBarsAppearance() {
+            return 0;
+        }
 
-            @Override
-            public void notifyFinished(InsetsAnimationControlRunner runner, boolean shown) {
-                // Nothing's needed here. Finish steps is handled in the listener
-                // onAnimationFinished callback.
-            }
+        @Override
+        public void setSystemBarsBehavior(int behavior) { }
 
-            /** Called on SurfaceAnimationThread without global WM lock held. */
-            @Override
-            public void applySurfaceParams(
-                    final SyncRtSurfaceTransactionApplier.SurfaceParams... params) {
-                SurfaceControl.Transaction t = new SurfaceControl.Transaction();
-                for (int i = params.length - 1; i >= 0; i--) {
-                    SyncRtSurfaceTransactionApplier.SurfaceParams surfaceParams = params[i];
-                    applyParams(t, surfaceParams, mTmpFloat9);
-                }
-                t.apply();
-                t.close();
-            }
+        @Override
+        public int getSystemBarsBehavior() {
+            return BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
+        }
 
-            // Since we don't push applySurfaceParams to a Handler-queue we don't need
-            // to push release in this case.
-            @Override
-            public void releaseSurfaceControlFromRt(SurfaceControl sc) {
-                sc.release();
-            }
+        @Override
+        public void releaseSurfaceControlFromRt(SurfaceControl surfaceControl) {
+            surfaceControl.release();
+        }
 
-            @Override
-            public <T extends InsetsAnimationControlRunner & InternalInsetsAnimationController>
-            void startAnimation(T runner, WindowInsetsAnimationControlListener listener, int types,
-                    WindowInsetsAnimation animation,
-                    Bounds bounds) {
-            }
+        @Override
+        public void addOnPreDrawRunnable(Runnable r) { }
 
-            @Override
-            public void reportPerceptible(int types, boolean perceptible) {
-                // No-op for now - only client windows report perceptibility for now, with policy
-                // controllers assumed to always be perceptible.
-            }
+        @Override
+        public void postInsetsAnimationCallback(Runnable r) { }
+
+        @Override
+        public InputMethodManager getInputMethodManager() {
+            return null;
+        }
+
+        @Nullable
+        @Override
+        public String getRootViewTitle() {
+            return mName;
+        }
+
+        @Override
+        public int dipToPx(int dips) {
+            return 0;
+        }
+
+        @Nullable
+        @Override
+        public IBinder getWindowToken() {
+            return null;
         }
     }
 }
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index 5e2618b..2b8312c 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -40,6 +40,7 @@
 import android.util.SparseArray;
 import android.util.proto.ProtoOutputStream;
 import android.view.InsetsSource;
+import android.view.InsetsSource.Flags;
 import android.view.InsetsSourceControl;
 import android.view.SurfaceControl;
 import android.view.SurfaceControl.Transaction;
@@ -81,6 +82,8 @@
     private final Rect mSourceFrame = new Rect();
     private final Rect mLastSourceFrame = new Rect();
     private @NonNull Insets mInsetsHint = Insets.NONE;
+    private @Flags int mFlagsFromFrameProvider;
+    private @Flags int mFlagsFromServer;
 
     private final Consumer<Transaction> mSetLeashPositionConsumer = t -> {
         if (mControl != null) {
@@ -175,7 +178,7 @@
         if (windowContainer == null) {
             setServerVisible(false);
             mSource.setVisibleFrame(null);
-            mSource.setInsetsRoundedCornerFrame(false);
+            mSource.setFlags(0, 0xffffffff);
             mSourceFrame.setEmpty();
         } else {
             mWindowContainer.getInsetsSourceProviders().put(mSource.getId(), this);
@@ -189,6 +192,16 @@
         }
     }
 
+    boolean setFlags(@Flags int flags, @Flags int mask) {
+        mFlagsFromServer = (mFlagsFromServer & ~mask) | (flags & mask);
+        final @Flags int mergedFlags = mFlagsFromFrameProvider | mFlagsFromServer;
+        if (mSource.getFlags() != mergedFlags) {
+            mSource.setFlags(mergedFlags);
+            return true;
+        }
+        return false;
+    }
+
     /**
      * The source frame can affect the layout of other windows, so this should be called once the
      * window container gets laid out.
@@ -217,11 +230,11 @@
 
         mSourceFrame.set(frame);
         if (mFrameProvider != null) {
-            final int flags = mFrameProvider.apply(
+            mFlagsFromFrameProvider = mFrameProvider.apply(
                     mWindowContainer.getDisplayContent().mDisplayFrames,
                     mWindowContainer,
                     mSourceFrame);
-            mSource.setFlags(flags);
+            mSource.setFlags(mFlagsFromFrameProvider | mFlagsFromServer);
         }
         updateSourceFrameForServerVisibility();
 
diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java
index 249ead0..081ebe0 100644
--- a/services/core/java/com/android/server/wm/InsetsStateController.java
+++ b/services/core/java/com/android/server/wm/InsetsStateController.java
@@ -17,6 +17,7 @@
 package com.android.server.wm;
 
 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
+import static android.view.InsetsSource.FLAG_FORCE_CONSUMING;
 import static android.view.InsetsSource.ID_IME;
 import static android.view.WindowInsets.Type.displayCutout;
 import static android.view.WindowInsets.Type.ime;
@@ -85,6 +86,8 @@
         }
     };
 
+    private @InsetsType int mForcedConsumingTypes;
+
     InsetsStateController(DisplayContent displayContent) {
         mDisplayContent = displayContent;
     }
@@ -122,6 +125,11 @@
         provider = id == ID_IME
                 ? new ImeInsetsSourceProvider(source, this, mDisplayContent)
                 : new InsetsSourceProvider(source, this, mDisplayContent);
+        provider.setFlags(
+                (mForcedConsumingTypes & type) != 0
+                        ? FLAG_FORCE_CONSUMING
+                        : 0,
+                FLAG_FORCE_CONSUMING);
         mProviders.put(id, provider);
         return provider;
     }
@@ -137,6 +145,24 @@
         }
     }
 
+    void setForcedConsumingTypes(@InsetsType int types) {
+        if (mForcedConsumingTypes != types) {
+            mForcedConsumingTypes = types;
+            boolean changed = false;
+            for (int i = mProviders.size() - 1; i >= 0; i--) {
+                final InsetsSourceProvider provider = mProviders.valueAt(i);
+                changed |= provider.setFlags(
+                        (types & provider.getSource().getType()) != 0
+                                ? FLAG_FORCE_CONSUMING
+                                : 0,
+                        FLAG_FORCE_CONSUMING);
+            }
+            if (changed) {
+                notifyInsetsChanged();
+            }
+        }
+    }
+
     /**
      * Called when a layout pass has occurred.
      */
@@ -190,7 +216,7 @@
         }
     }
 
-    void onInsetsModified(InsetsControlTarget caller) {
+    void onRequestedVisibleTypesChanged(InsetsControlTarget caller) {
         boolean changed = false;
         for (int i = mProviders.size() - 1; i >= 0; i--) {
             changed |= mProviders.valueAt(i).updateClientVisibility(caller);
@@ -352,7 +378,7 @@
             // to the clients, so that the clients can change the current visibilities to the
             // requested visibilities with animations.
             for (int i = newControlTargets.size() - 1; i >= 0; i--) {
-                onInsetsModified(newControlTargets.valueAt(i));
+                onRequestedVisibleTypesChanged(newControlTargets.valueAt(i));
             }
             newControlTargets.clear();
         });
@@ -391,6 +417,10 @@
         for (int i = mProviders.size() - 1; i >= 0; i--) {
             mProviders.valueAt(i).dump(pw, prefix + "  ");
         }
+        if (mForcedConsumingTypes != 0) {
+            pw.println(prefix + "mForcedConsumingTypes="
+                    + WindowInsets.Type.toString(mForcedConsumingTypes));
+        }
     }
 
     void dumpDebug(ProtoOutputStream proto, @WindowTraceLogLevel int logLevel) {
diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java
index 83fd725..d03388a 100644
--- a/services/core/java/com/android/server/wm/KeyguardController.java
+++ b/services/core/java/com/android/server/wm/KeyguardController.java
@@ -204,7 +204,8 @@
         //   handle the snapshot.
         // - The display state is ON. Because if AOD is not on or pulsing, the display state will
         //   be OFF or DOZE (the path of screen off may have handled it).
-        if (((aodShowing ^ keyguardShowing) || (aodShowing && aodChanged && keyguardChanged))
+        if (displayId == DEFAULT_DISPLAY
+                && ((aodShowing ^ keyguardShowing) || (aodShowing && aodChanged && keyguardChanged))
                 && !state.mKeyguardGoingAway && Display.isOnState(
                         mRootWindowContainer.getDefaultDisplay().getDisplayInfo().state)) {
             mWindowManager.mTaskSnapshotController.snapshotForSleeping(DEFAULT_DISPLAY);
@@ -686,8 +687,7 @@
                 display.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
             }
 
-            if (mTopTurnScreenOnActivity != lastTurnScreenOnActivity
-                    && mTopTurnScreenOnActivity != null
+            if (mTopTurnScreenOnActivity != null
                     && !mService.mWindowManager.mPowerManager.isInteractive()
                     && (mRequestDismissKeyguard || occludedByActivity)) {
                 controller.mTaskSupervisor.wakeUp("handleTurnScreenOn");
diff --git a/services/core/java/com/android/server/wm/LaunchParamsController.java b/services/core/java/com/android/server/wm/LaunchParamsController.java
index e74e5787..91bb8d0 100644
--- a/services/core/java/com/android/server/wm/LaunchParamsController.java
+++ b/services/core/java/com/android/server/wm/LaunchParamsController.java
@@ -64,7 +64,7 @@
     void registerDefaultModifiers(ActivityTaskSupervisor supervisor) {
         // {@link TaskLaunchParamsModifier} handles window layout preferences.
         registerModifier(new TaskLaunchParamsModifier(supervisor));
-        if (DesktopModeLaunchParamsModifier.DESKTOP_MODE_SUPPORTED) {
+        if (DesktopModeLaunchParamsModifier.isDesktopModeSupported()) {
             // {@link DesktopModeLaunchParamsModifier} handles default task size changes
             registerModifier(new DesktopModeLaunchParamsModifier());
         }
diff --git a/services/core/java/com/android/server/wm/LaunchParamsPersister.java b/services/core/java/com/android/server/wm/LaunchParamsPersister.java
index bf511adf0..2394da9 100644
--- a/services/core/java/com/android/server/wm/LaunchParamsPersister.java
+++ b/services/core/java/com/android/server/wm/LaunchParamsPersister.java
@@ -433,7 +433,7 @@
             final byte[] data = saveParamsToXml();
 
             final File launchParamFolder = getLaunchParamFolder(mUserId);
-            if (!launchParamFolder.isDirectory() && !launchParamFolder.mkdirs()) {
+            if (!launchParamFolder.isDirectory() && !launchParamFolder.mkdir()) {
                 Slog.w(TAG, "Failed to create folder for " + mUserId);
                 return;
             }
diff --git a/services/core/java/com/android/server/wm/LetterboxConfiguration.java b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
index 067a18c..e945bc1 100644
--- a/services/core/java/com/android/server/wm/LetterboxConfiguration.java
+++ b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
@@ -18,11 +18,6 @@
 
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
-import static com.android.server.wm.LetterboxConfigurationDeviceConfig.KEY_ALLOW_IGNORE_ORIENTATION_REQUEST;
-import static com.android.server.wm.LetterboxConfigurationDeviceConfig.KEY_ENABLE_CAMERA_COMPAT_TREATMENT;
-import static com.android.server.wm.LetterboxConfigurationDeviceConfig.KEY_ENABLE_COMPAT_FAKE_FOCUS;
-import static com.android.server.wm.LetterboxConfigurationDeviceConfig.KEY_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY;
-import static com.android.server.wm.LetterboxConfigurationDeviceConfig.KEY_ENABLE_LETTERBOX_TRANSLUCENT_ACTIVITY;
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
@@ -44,6 +39,59 @@
 
     private static final String TAG = TAG_WITH_CLASS_NAME ? "LetterboxConfiguration" : TAG_ATM;
 
+    // Whether camera compatibility treatment is enabled.
+    // See DisplayRotationCompatPolicy for context.
+    private static final String KEY_ENABLE_CAMERA_COMPAT_TREATMENT =
+            "enable_compat_camera_treatment";
+
+    private static final boolean DEFAULT_VALUE_ENABLE_CAMERA_COMPAT_TREATMENT = true;
+
+    // Whether enabling rotation compat policy for immersive apps that prevents auto
+    // rotation into non-optimal screen orientation while in fullscreen. This is needed
+    // because immersive apps, such as games, are often not optimized for all
+    // orientations and can have a poor UX when rotated. Additionally, some games rely
+    // on sensors for the gameplay so  users can trigger such rotations accidentally
+    // when auto rotation is on.
+    private static final String KEY_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY =
+            "enable_display_rotation_immersive_app_compat_policy";
+
+    private static final boolean DEFAULT_VALUE_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY =
+            true;
+
+    // Whether ignore orientation request is allowed
+    private static final String KEY_ALLOW_IGNORE_ORIENTATION_REQUEST =
+            "allow_ignore_orientation_request";
+
+    private static final boolean DEFAULT_VALUE_ALLOW_IGNORE_ORIENTATION_REQUEST = true;
+
+    // Whether sending compat fake focus is enabled for unfocused apps in splitscreen.
+    // Some game engines wait to get focus before drawing the content of the app so
+    // this needs  to be used otherwise the apps get blacked out when they are resumed
+    // and do not have focus yet.
+    private static final String KEY_ENABLE_COMPAT_FAKE_FOCUS = "enable_compat_fake_focus";
+
+    private static final boolean DEFAULT_VALUE_ENABLE_COMPAT_FAKE_FOCUS = true;
+
+    // Whether translucent activities policy is enabled
+    private static final String KEY_ENABLE_LETTERBOX_TRANSLUCENT_ACTIVITY =
+            "enable_letterbox_translucent_activity";
+
+    private static final boolean DEFAULT_VALUE_ENABLE_LETTERBOX_TRANSLUCENT_ACTIVITY = true;
+
+    // Whether per-app user aspect ratio override settings is enabled
+    private static final String KEY_ENABLE_USER_ASPECT_RATIO_SETTINGS =
+            "enable_app_compat_user_aspect_ratio_settings";
+
+    // TODO(b/288142656): Enable user aspect ratio settings by default.
+    private static final boolean DEFAULT_VALUE_ENABLE_USER_ASPECT_RATIO_SETTINGS = false;
+
+    // Whether the letterbox wallpaper style is enabled by default
+    private static final String KEY_ENABLE_LETTERBOX_BACKGROUND_WALLPAPER =
+            "enable_letterbox_background_wallpaper";
+
+    // TODO(b/290048978): Enable wallpaper as default letterbox background.
+    private static final boolean DEFAULT_VALUE_ENABLE_LETTERBOX_BACKGROUND_WALLPAPER = false;
+
     /**
      * Override of aspect ratio for fixed orientation letterboxing that is set via ADB with
      * set-fixed-orientation-letterbox-aspect-ratio or via {@link
@@ -60,9 +108,16 @@
 
     /** Enum for Letterbox background type. */
     @Retention(RetentionPolicy.SOURCE)
-    @IntDef({LETTERBOX_BACKGROUND_SOLID_COLOR, LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND,
-            LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING, LETTERBOX_BACKGROUND_WALLPAPER})
+    @IntDef({LETTERBOX_BACKGROUND_OVERRIDE_UNSET,
+            LETTERBOX_BACKGROUND_SOLID_COLOR,
+            LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND,
+            LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING,
+            LETTERBOX_BACKGROUND_WALLPAPER})
     @interface LetterboxBackgroundType {};
+
+    /** No letterbox background style set. Using the one defined by DeviceConfig. */
+    static final int LETTERBOX_BACKGROUND_OVERRIDE_UNSET = -1;
+
     /** Solid background using color specified in R.color.config_letterboxBackgroundColor. */
     static final int LETTERBOX_BACKGROUND_SOLID_COLOR = 0;
 
@@ -142,14 +197,14 @@
     @Nullable private Integer mLetterboxBackgroundColorResourceIdOverride;
 
     @LetterboxBackgroundType
-    private int mLetterboxBackgroundType;
+    private final int mLetterboxBackgroundType;
 
-    // Blur radius for LETTERBOX_BACKGROUND_WALLPAPER option in mLetterboxBackgroundType.
+    // Blur radius for LETTERBOX_BACKGROUND_WALLPAPER option from getLetterboxBackgroundType().
     // Values <= 0 are ignored and 0 is used instead.
-    private int mLetterboxBackgroundWallpaperBlurRadius;
+    private int mLetterboxBackgroundWallpaperBlurRadiusPx;
 
     // Alpha of a black scrim shown over wallpaper letterbox background when
-    // LETTERBOX_BACKGROUND_WALLPAPER option is selected for mLetterboxBackgroundType.
+    // LETTERBOX_BACKGROUND_WALLPAPER option is returned from getLetterboxBackgroundType().
     // Values < 0 or >= 1 are ignored and 0.0 (transparent) is used instead.
     private float mLetterboxBackgroundWallpaperDarkScrimAlpha;
 
@@ -205,26 +260,21 @@
     // unresizable apps
     private boolean mIsDisplayAspectRatioEnabledForFixedOrientationLetterbox;
 
-    // Whether letterboxing strategy is enabled for translucent activities. If {@value false}
-    // all the feature is disabled
-    private boolean mTranslucentLetterboxingEnabled;
-
     // Allows to enable letterboxing strategy for translucent activities ignoring flags.
     private boolean mTranslucentLetterboxingOverrideEnabled;
 
-    // Whether sending compat fake focus is enabled for unfocused apps in splitscreen. Some game
-    // engines wait to get focus before drawing the content of the app so this needs to be used
-    // otherwise the apps get blacked out when they are resumed and do not have focus yet.
-    private boolean mIsCompatFakeFocusEnabled;
+    // Allows to enable user aspect ratio settings ignoring flags.
+    private boolean mUserAppAspectRatioSettingsOverrideEnabled;
+
+    // The override for letterbox background type in case it's different from
+    // LETTERBOX_BACKGROUND_OVERRIDE_UNSET
+    @LetterboxBackgroundType
+    private int mLetterboxBackgroundTypeOverride = LETTERBOX_BACKGROUND_OVERRIDE_UNSET;
 
     // Whether we should use split screen aspect ratio for the activity when camera compat treatment
     // is enabled and activity is connected to the camera in fullscreen.
     private final boolean mIsCameraCompatSplitScreenAspectRatioEnabled;
 
-    // Whether camera compatibility treatment is enabled.
-    // See DisplayRotationCompatPolicy for context.
-    private final boolean mIsCameraCompatTreatmentEnabled;
-
     // Whether activity "refresh" in camera compatibility treatment is enabled.
     // See RefreshCallbackItem for context.
     private boolean mIsCameraCompatTreatmentRefreshEnabled = true;
@@ -240,41 +290,32 @@
     // LetterboxUiController#shouldIgnoreRequestedOrientation for details.
     private final boolean mIsPolicyForIgnoringRequestedOrientationEnabled;
 
-    // Whether enabling rotation compat policy for immersive apps that prevents auto rotation
-    // into non-optimal screen orientation while in fullscreen. This is needed because immersive
-    // apps, such as games, are often not optimized for all orientations and can have a poor UX
-    // when rotated. Additionally, some games rely on sensors for the gameplay so users can trigger
-    // such rotations accidentally when auto rotation is on.
-    private final boolean mIsDisplayRotationImmersiveAppCompatPolicyEnabled;
-
     // Flags dynamically updated with {@link android.provider.DeviceConfig}.
-    @NonNull private final LetterboxConfigurationDeviceConfig mDeviceConfig;
+    @NonNull private final SynchedDeviceConfig mDeviceConfig;
 
     LetterboxConfiguration(@NonNull final Context systemUiContext) {
-        this(systemUiContext,
-                new LetterboxConfigurationPersister(systemUiContext,
-                        () -> readLetterboxHorizontalReachabilityPositionFromConfig(
-                                systemUiContext, /* forBookMode */ false),
-                        () -> readLetterboxVerticalReachabilityPositionFromConfig(
-                                systemUiContext, /* forTabletopMode */ false),
-                        () -> readLetterboxHorizontalReachabilityPositionFromConfig(
-                                systemUiContext, /* forBookMode */ true),
-                        () -> readLetterboxVerticalReachabilityPositionFromConfig(
-                                systemUiContext, /* forTabletopMode */ true)));
+        this(systemUiContext, new LetterboxConfigurationPersister(
+                () -> readLetterboxHorizontalReachabilityPositionFromConfig(
+                        systemUiContext, /* forBookMode */ false),
+                () -> readLetterboxVerticalReachabilityPositionFromConfig(
+                        systemUiContext, /* forTabletopMode */ false),
+                () -> readLetterboxHorizontalReachabilityPositionFromConfig(
+                        systemUiContext, /* forBookMode */ true),
+                () -> readLetterboxVerticalReachabilityPositionFromConfig(
+                        systemUiContext, /* forTabletopMode */ true)));
     }
 
     @VisibleForTesting
     LetterboxConfiguration(@NonNull final Context systemUiContext,
             @NonNull final LetterboxConfigurationPersister letterboxConfigurationPersister) {
         mContext = systemUiContext;
-        mDeviceConfig = new LetterboxConfigurationDeviceConfig(systemUiContext.getMainExecutor());
 
         mFixedOrientationLetterboxAspectRatio = mContext.getResources().getFloat(
                 R.dimen.config_fixedOrientationLetterboxAspectRatio);
+        mLetterboxBackgroundType = readLetterboxBackgroundTypeFromConfig(mContext);
         mLetterboxActivityCornersRadius = mContext.getResources().getInteger(
                 R.integer.config_letterboxActivityCornersRadius);
-        mLetterboxBackgroundType = readLetterboxBackgroundTypeFromConfig(mContext);
-        mLetterboxBackgroundWallpaperBlurRadius = mContext.getResources().getDimensionPixelSize(
+        mLetterboxBackgroundWallpaperBlurRadiusPx = mContext.getResources().getDimensionPixelSize(
                 R.dimen.config_letterboxBackgroundWallpaperBlurRadius);
         mLetterboxBackgroundWallpaperDarkScrimAlpha = mContext.getResources().getFloat(
                 R.dimen.config_letterboxBackgroundWallaperDarkScrimAlpha);
@@ -305,36 +346,40 @@
         mIsDisplayAspectRatioEnabledForFixedOrientationLetterbox = mContext.getResources()
                 .getBoolean(R.bool
                         .config_letterboxIsDisplayAspectRatioForFixedOrientationLetterboxEnabled);
-        mTranslucentLetterboxingEnabled = mContext.getResources().getBoolean(
-                R.bool.config_letterboxIsEnabledForTranslucentActivities);
-        mIsCameraCompatTreatmentEnabled = mContext.getResources().getBoolean(
-                R.bool.config_isWindowManagerCameraCompatTreatmentEnabled);
         mIsCameraCompatSplitScreenAspectRatioEnabled = mContext.getResources().getBoolean(
                 R.bool.config_isWindowManagerCameraCompatSplitScreenAspectRatioEnabled);
-        mIsCompatFakeFocusEnabled = mContext.getResources().getBoolean(
-                R.bool.config_isCompatFakeFocusEnabled);
         mIsPolicyForIgnoringRequestedOrientationEnabled = mContext.getResources().getBoolean(
                 R.bool.config_letterboxIsPolicyForIgnoringRequestedOrientationEnabled);
-        mIsDisplayRotationImmersiveAppCompatPolicyEnabled = mContext.getResources().getBoolean(
-                R.bool.config_letterboxIsDisplayRotationImmersiveAppCompatPolicyEnabled);
-        mDeviceConfig.updateFlagActiveStatus(
-                /* isActive */ mIsCameraCompatTreatmentEnabled,
-                /* key */ KEY_ENABLE_CAMERA_COMPAT_TREATMENT);
-        mDeviceConfig.updateFlagActiveStatus(
-                /* isActive */ mIsDisplayRotationImmersiveAppCompatPolicyEnabled,
-                /* key */ KEY_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY);
-        mDeviceConfig.updateFlagActiveStatus(
-                /* isActive */ true,
-                /* key */ KEY_ALLOW_IGNORE_ORIENTATION_REQUEST);
-        mDeviceConfig.updateFlagActiveStatus(
-                /* isActive */ mIsCompatFakeFocusEnabled,
-                /* key */ KEY_ENABLE_COMPAT_FAKE_FOCUS);
-        mDeviceConfig.updateFlagActiveStatus(
-                /* isActive */ mTranslucentLetterboxingEnabled,
-                /* key */ KEY_ENABLE_LETTERBOX_TRANSLUCENT_ACTIVITY);
 
         mLetterboxConfigurationPersister = letterboxConfigurationPersister;
         mLetterboxConfigurationPersister.start();
+
+        mDeviceConfig = SynchedDeviceConfig.builder(DeviceConfig.NAMESPACE_WINDOW_MANAGER,
+                        systemUiContext.getMainExecutor())
+                .addDeviceConfigEntry(KEY_ENABLE_CAMERA_COMPAT_TREATMENT,
+                        DEFAULT_VALUE_ENABLE_CAMERA_COMPAT_TREATMENT,
+                        mContext.getResources().getBoolean(
+                                R.bool.config_isWindowManagerCameraCompatTreatmentEnabled))
+                .addDeviceConfigEntry(KEY_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY,
+                        DEFAULT_VALUE_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY,
+                        mContext.getResources().getBoolean(R.bool
+                                .config_letterboxIsDisplayRotationImmersiveAppCompatPolicyEnabled))
+                .addDeviceConfigEntry(KEY_ALLOW_IGNORE_ORIENTATION_REQUEST,
+                        DEFAULT_VALUE_ALLOW_IGNORE_ORIENTATION_REQUEST, /* enabled */ true)
+                .addDeviceConfigEntry(KEY_ENABLE_COMPAT_FAKE_FOCUS,
+                        DEFAULT_VALUE_ENABLE_COMPAT_FAKE_FOCUS,
+                        mContext.getResources().getBoolean(R.bool.config_isCompatFakeFocusEnabled))
+                .addDeviceConfigEntry(KEY_ENABLE_LETTERBOX_TRANSLUCENT_ACTIVITY,
+                        DEFAULT_VALUE_ENABLE_LETTERBOX_TRANSLUCENT_ACTIVITY,
+                        mContext.getResources().getBoolean(
+                                R.bool.config_letterboxIsEnabledForTranslucentActivities))
+                .addDeviceConfigEntry(KEY_ENABLE_USER_ASPECT_RATIO_SETTINGS,
+                        DEFAULT_VALUE_ENABLE_USER_ASPECT_RATIO_SETTINGS,
+                        mContext.getResources().getBoolean(
+                                R.bool.config_appCompatUserAppAspectRatioSettingsIsEnabled))
+                .addDeviceConfigEntry(KEY_ENABLE_LETTERBOX_BACKGROUND_WALLPAPER,
+                        DEFAULT_VALUE_ENABLE_LETTERBOX_BACKGROUND_WALLPAPER, /* enabled */ true)
+                .build();
     }
 
     /**
@@ -342,7 +387,7 @@
      * via {@link android.provider.DeviceConfig}.
      */
     boolean isIgnoreOrientationRequestAllowed() {
-        return mDeviceConfig.getFlag(KEY_ALLOW_IGNORE_ORIENTATION_REQUEST);
+        return mDeviceConfig.getFlagValue(KEY_ALLOW_IGNORE_ORIENTATION_REQUEST);
     }
 
     /**
@@ -472,25 +517,39 @@
     }
 
     /**
-     * Gets {@link LetterboxBackgroundType} specified in {@link
-     * com.android.internal.R.integer.config_letterboxBackgroundType} or over via ADB command.
+     * Gets {@link LetterboxBackgroundType} specified via ADB command or the default one.
      */
     @LetterboxBackgroundType
     int getLetterboxBackgroundType() {
-        return mLetterboxBackgroundType;
+        return mLetterboxBackgroundTypeOverride != LETTERBOX_BACKGROUND_OVERRIDE_UNSET
+                ? mLetterboxBackgroundTypeOverride
+                : getDefaultLetterboxBackgroundType();
     }
 
-    /** Sets letterbox background type. */
-    void setLetterboxBackgroundType(@LetterboxBackgroundType int backgroundType) {
-        mLetterboxBackgroundType = backgroundType;
+    /** Overrides the letterbox background type. */
+    void setLetterboxBackgroundTypeOverride(@LetterboxBackgroundType int backgroundType) {
+        mLetterboxBackgroundTypeOverride = backgroundType;
     }
 
     /**
-     * Resets cletterbox background type to {@link
-     * com.android.internal.R.integer.config_letterboxBackgroundType}.
+     * Resets letterbox background type value depending on the
+     * {@link #KEY_ENABLE_LETTERBOX_BACKGROUND_WALLPAPER} built time and runtime flags.
+     *
+     * <p>If enabled, the letterbox background type value is set toZ
+     * {@link #LETTERBOX_BACKGROUND_WALLPAPER}. When disabled the letterbox background type value
+     * comes from {@link R.integer.config_letterboxBackgroundType}.
      */
     void resetLetterboxBackgroundType() {
-        mLetterboxBackgroundType = readLetterboxBackgroundTypeFromConfig(mContext);
+        mLetterboxBackgroundTypeOverride = LETTERBOX_BACKGROUND_OVERRIDE_UNSET;
+    }
+
+    // Returns KEY_ENABLE_LETTERBOX_BACKGROUND_WALLPAPER if the DeviceConfig flag is enabled
+    // or the value in com.android.internal.R.integer.config_letterboxBackgroundType if the flag
+    // is disabled.
+    @LetterboxBackgroundType
+    private int getDefaultLetterboxBackgroundType() {
+        return mDeviceConfig.getFlagValue(KEY_ENABLE_LETTERBOX_BACKGROUND_WALLPAPER)
+                ? LETTERBOX_BACKGROUND_WALLPAPER : mLetterboxBackgroundType;
     }
 
     /** Returns a string representing the given {@link LetterboxBackgroundType}. */
@@ -523,7 +582,7 @@
 
     /**
      * Overrides alpha of a black scrim shown over wallpaper for {@link
-     * #LETTERBOX_BACKGROUND_WALLPAPER} option in {@link mLetterboxBackgroundType}.
+     * #LETTERBOX_BACKGROUND_WALLPAPER} option returned from {@link getLetterboxBackgroundType()}.
      *
      * <p>If given value is < 0 or >= 1, both it and a value of {@link
      * com.android.internal.R.dimen.config_letterboxBackgroundWallaperDarkScrimAlpha} are ignored
@@ -550,33 +609,33 @@
     }
 
     /**
-     * Overrides blur radius for {@link #LETTERBOX_BACKGROUND_WALLPAPER} option in
-     * {@link mLetterboxBackgroundType}.
+     * Overrides blur radius for {@link #LETTERBOX_BACKGROUND_WALLPAPER} option from
+     * {@link getLetterboxBackgroundType()}.
      *
      * <p> If given value <= 0, both it and a value of {@link
      * com.android.internal.R.dimen.config_letterboxBackgroundWallpaperBlurRadius} are ignored
      * and 0 is used instead.
      */
-    void setLetterboxBackgroundWallpaperBlurRadius(int radius) {
-        mLetterboxBackgroundWallpaperBlurRadius = radius;
+    void setLetterboxBackgroundWallpaperBlurRadiusPx(int radius) {
+        mLetterboxBackgroundWallpaperBlurRadiusPx = radius;
     }
 
     /**
-     * Resets blur raidus for {@link #LETTERBOX_BACKGROUND_WALLPAPER} option in {@link
-     * mLetterboxBackgroundType} to {@link
+     * Resets blur raidus for {@link #LETTERBOX_BACKGROUND_WALLPAPER} option returned by {@link
+     * getLetterboxBackgroundType()} to {@link
      * com.android.internal.R.dimen.config_letterboxBackgroundWallpaperBlurRadius}.
      */
-    void resetLetterboxBackgroundWallpaperBlurRadius() {
-        mLetterboxBackgroundWallpaperBlurRadius = mContext.getResources().getDimensionPixelSize(
+    void resetLetterboxBackgroundWallpaperBlurRadiusPx() {
+        mLetterboxBackgroundWallpaperBlurRadiusPx = mContext.getResources().getDimensionPixelSize(
                 com.android.internal.R.dimen.config_letterboxBackgroundWallpaperBlurRadius);
     }
 
     /**
-     * Gets blur raidus for {@link #LETTERBOX_BACKGROUND_WALLPAPER} option in {@link
-     * mLetterboxBackgroundType}.
+     * Gets blur raidus for {@link #LETTERBOX_BACKGROUND_WALLPAPER} option returned by {@link
+     * getLetterboxBackgroundType()}.
      */
-    int getLetterboxBackgroundWallpaperBlurRadius() {
-        return mLetterboxBackgroundWallpaperBlurRadius;
+    int getLetterboxBackgroundWallpaperBlurRadiusPx() {
+        return mLetterboxBackgroundWallpaperBlurRadiusPx;
     }
 
     /*
@@ -1049,28 +1108,21 @@
     }
 
     boolean isTranslucentLetterboxingEnabled() {
-        return mTranslucentLetterboxingOverrideEnabled || (mTranslucentLetterboxingEnabled
-                && mDeviceConfig.getFlag(KEY_ENABLE_LETTERBOX_TRANSLUCENT_ACTIVITY));
-    }
-
-    void setTranslucentLetterboxingEnabled(boolean translucentLetterboxingEnabled) {
-        mTranslucentLetterboxingEnabled = translucentLetterboxingEnabled;
+        return mTranslucentLetterboxingOverrideEnabled
+                || mDeviceConfig.getFlagValue(KEY_ENABLE_LETTERBOX_TRANSLUCENT_ACTIVITY);
     }
 
     void setTranslucentLetterboxingOverrideEnabled(
             boolean translucentLetterboxingOverrideEnabled) {
         mTranslucentLetterboxingOverrideEnabled = translucentLetterboxingOverrideEnabled;
-        setTranslucentLetterboxingEnabled(translucentLetterboxingOverrideEnabled);
     }
 
     /**
      * Resets whether we use the constraints override strategy for letterboxing when dealing
-     * with translucent activities {@link R.bool.config_letterboxIsEnabledForTranslucentActivities}.
+     * with translucent activities
+     * {@link mDeviceConfig.getFlagValue(KEY_ENABLE_LETTERBOX_TRANSLUCENT_ACTIVITY)}.
      */
     void resetTranslucentLetterboxingEnabled() {
-        final boolean newValue = mContext.getResources().getBoolean(
-                R.bool.config_letterboxIsEnabledForTranslucentActivities);
-        setTranslucentLetterboxingEnabled(newValue);
         setTranslucentLetterboxingOverrideEnabled(false);
     }
 
@@ -1100,15 +1152,7 @@
 
     /** Whether fake sending focus is enabled for unfocused apps in splitscreen */
     boolean isCompatFakeFocusEnabled() {
-        return mIsCompatFakeFocusEnabled && mDeviceConfig.getFlag(KEY_ENABLE_COMPAT_FAKE_FOCUS);
-    }
-
-    /**
-     * Overrides whether fake sending focus is enabled for unfocused apps in splitscreen
-     */
-    @VisibleForTesting
-    void setIsCompatFakeFocusEnabled(boolean enabled) {
-        mIsCompatFakeFocusEnabled = enabled;
+        return mDeviceConfig.getFlagValue(KEY_ENABLE_COMPAT_FAKE_FOCUS);
     }
 
     /**
@@ -1128,10 +1172,20 @@
         return mIsCameraCompatSplitScreenAspectRatioEnabled;
     }
 
-    /** Whether camera compatibility treatment is enabled. */
-    boolean isCameraCompatTreatmentEnabled(boolean checkDeviceConfig) {
-        return mIsCameraCompatTreatmentEnabled && (!checkDeviceConfig
-                || mDeviceConfig.getFlag(KEY_ENABLE_CAMERA_COMPAT_TREATMENT));
+    /**
+     * @return Whether camera compatibility treatment is currently enabled.
+     */
+    boolean isCameraCompatTreatmentEnabled() {
+        return mDeviceConfig.getFlagValue(KEY_ENABLE_CAMERA_COMPAT_TREATMENT);
+    }
+
+    /**
+     * @return Whether camera compatibility treatment is enabled at build time. This is used when
+     * we need to safely initialize a component before the {@link DeviceConfig} flag value is
+     * available.
+     */
+    boolean isCameraCompatTreatmentEnabledAtBuildTime() {
+        return mDeviceConfig.isBuildTimeFlagEnabled(KEY_ENABLE_CAMERA_COMPAT_TREATMENT);
     }
 
     /** Whether camera compatibility refresh is enabled. */
@@ -1177,18 +1231,48 @@
 
     /**
      * Checks whether rotation compat policy for immersive apps that prevents auto rotation
-     * into non-optimal screen orientation while in fullscreen is enabled.
+     * into non-optimal screen orientation while in fullscreen is enabled at build time. This is
+     * used when we need to safely initialize a component before the {@link DeviceConfig} flag
+     * value is available.
      *
      * <p>This is needed because immersive apps, such as games, are often not optimized for all
      * orientations and can have a poor UX when rotated. Additionally, some games rely on sensors
      * for the gameplay so users can trigger such rotations accidentally when auto rotation is on.
-     *
-     * @param checkDeviceConfig whether should check both static config and a dynamic property
-     *        from {@link DeviceConfig} or only static value.
      */
-    boolean isDisplayRotationImmersiveAppCompatPolicyEnabled(final boolean checkDeviceConfig) {
-        return mIsDisplayRotationImmersiveAppCompatPolicyEnabled && (!checkDeviceConfig
-                || mDeviceConfig.getFlag(KEY_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY));
+    boolean isDisplayRotationImmersiveAppCompatPolicyEnabledAtBuildTime() {
+        return mDeviceConfig.isBuildTimeFlagEnabled(
+                KEY_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY);
     }
 
+    /**
+     * Checks whether rotation compat policy for immersive apps that prevents auto rotation
+     * into non-optimal screen orientation while in fullscreen is currently enabled.
+     *
+     * <p>This is needed because immersive apps, such as games, are often not optimized for all
+     * orientations and can have a poor UX when rotated. Additionally, some games rely on sensors
+     * for the gameplay so users can trigger such rotations accidentally when auto rotation is on.
+     */
+    boolean isDisplayRotationImmersiveAppCompatPolicyEnabled() {
+        return mDeviceConfig.getFlagValue(KEY_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY);
+    }
+
+    /**
+     * Whether per-app user aspect ratio override settings is enabled
+     */
+    boolean isUserAppAspectRatioSettingsEnabled() {
+        return mUserAppAspectRatioSettingsOverrideEnabled
+                || mDeviceConfig.getFlagValue(KEY_ENABLE_USER_ASPECT_RATIO_SETTINGS);
+    }
+
+    void setUserAppAspectRatioSettingsOverrideEnabled(boolean enabled) {
+        mUserAppAspectRatioSettingsOverrideEnabled = enabled;
+    }
+
+    /**
+     * Resets whether per-app user aspect ratio override settings is enabled
+     * {@code mDeviceConfig.getFlagValue(KEY_ENABLE_USER_ASPECT_RATIO_SETTINGS)}.
+     */
+    void resetUserAppAspectRatioSettingsEnabled() {
+        setUserAppAspectRatioSettingsOverrideEnabled(false);
+    }
 }
diff --git a/services/core/java/com/android/server/wm/LetterboxConfigurationDeviceConfig.java b/services/core/java/com/android/server/wm/LetterboxConfigurationDeviceConfig.java
deleted file mode 100644
index a739e42..0000000
--- a/services/core/java/com/android/server/wm/LetterboxConfigurationDeviceConfig.java
+++ /dev/null
@@ -1,180 +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.server.wm;
-
-import android.annotation.NonNull;
-import android.provider.DeviceConfig;
-import android.util.ArraySet;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-import java.util.Map;
-import java.util.concurrent.Executor;
-
-/**
- * Utility class that caches {@link DeviceConfig} flags for app compat features and listens
- * to updates by implementing {@link DeviceConfig.OnPropertiesChangedListener}.
- */
-final class LetterboxConfigurationDeviceConfig
-        implements DeviceConfig.OnPropertiesChangedListener {
-
-    static final String KEY_ENABLE_CAMERA_COMPAT_TREATMENT = "enable_compat_camera_treatment";
-    private static final boolean DEFAULT_VALUE_ENABLE_CAMERA_COMPAT_TREATMENT = true;
-
-    static final String KEY_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY =
-            "enable_display_rotation_immersive_app_compat_policy";
-    private static final boolean DEFAULT_VALUE_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY =
-            true;
-
-    static final String KEY_ALLOW_IGNORE_ORIENTATION_REQUEST =
-            "allow_ignore_orientation_request";
-    private static final boolean DEFAULT_VALUE_ALLOW_IGNORE_ORIENTATION_REQUEST = true;
-
-    static final String KEY_ENABLE_COMPAT_FAKE_FOCUS = "enable_compat_fake_focus";
-    private static final boolean DEFAULT_VALUE_ENABLE_COMPAT_FAKE_FOCUS = true;
-
-    static final String KEY_ENABLE_LETTERBOX_TRANSLUCENT_ACTIVITY =
-            "enable_letterbox_translucent_activity";
-
-    private static final boolean DEFAULT_VALUE_ENABLE_LETTERBOX_TRANSLUCENT_ACTIVITY = true;
-
-    @VisibleForTesting
-    static final Map<String, Boolean> sKeyToDefaultValueMap = Map.of(
-            KEY_ENABLE_CAMERA_COMPAT_TREATMENT,
-            DEFAULT_VALUE_ENABLE_CAMERA_COMPAT_TREATMENT,
-            KEY_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY,
-            DEFAULT_VALUE_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY,
-            KEY_ALLOW_IGNORE_ORIENTATION_REQUEST,
-            DEFAULT_VALUE_ALLOW_IGNORE_ORIENTATION_REQUEST,
-            KEY_ENABLE_COMPAT_FAKE_FOCUS,
-            DEFAULT_VALUE_ENABLE_COMPAT_FAKE_FOCUS,
-            KEY_ENABLE_LETTERBOX_TRANSLUCENT_ACTIVITY,
-            DEFAULT_VALUE_ENABLE_LETTERBOX_TRANSLUCENT_ACTIVITY
-    );
-
-    // Whether camera compatibility treatment is enabled.
-    // See DisplayRotationCompatPolicy for context.
-    private boolean mIsCameraCompatTreatmentEnabled =
-            DEFAULT_VALUE_ENABLE_CAMERA_COMPAT_TREATMENT;
-
-    // Whether enabling rotation compat policy for immersive apps that prevents auto rotation
-    // into non-optimal screen orientation while in fullscreen. This is needed because immersive
-    // apps, such as games, are often not optimized for all orientations and can have a poor UX
-    // when rotated. Additionally, some games rely on sensors for the gameplay so users can trigger
-    // such rotations accidentally when auto rotation is on.
-    private boolean mIsDisplayRotationImmersiveAppCompatPolicyEnabled =
-            DEFAULT_VALUE_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY;
-
-    // Whether enabling ignoreOrientationRequest is allowed on the device.
-    private boolean mIsAllowIgnoreOrientationRequest =
-            DEFAULT_VALUE_ALLOW_IGNORE_ORIENTATION_REQUEST;
-
-    // Whether sending compat fake focus for split screen resumed activities is enabled. This is
-    // needed because some game engines wait to get focus before drawing the content of the app
-    // which isn't guaranteed by default in multi-window modes.
-    private boolean mIsCompatFakeFocusAllowed = DEFAULT_VALUE_ENABLE_COMPAT_FAKE_FOCUS;
-
-    // Whether the letterbox strategy for transparent activities is allowed
-    private boolean mIsTranslucentLetterboxingAllowed =
-            DEFAULT_VALUE_ENABLE_LETTERBOX_TRANSLUCENT_ACTIVITY;
-
-    // Set of active device configs that need to be updated in
-    // DeviceConfig.OnPropertiesChangedListener#onPropertiesChanged.
-    private final ArraySet<String> mActiveDeviceConfigsSet = new ArraySet<>();
-
-    LetterboxConfigurationDeviceConfig(@NonNull final Executor executor) {
-        DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_WINDOW_MANAGER,
-                executor, /* onPropertiesChangedListener */ this);
-    }
-
-    @Override
-    public void onPropertiesChanged(@NonNull final DeviceConfig.Properties properties) {
-        for (int i = mActiveDeviceConfigsSet.size() - 1; i >= 0; i--) {
-            String key = mActiveDeviceConfigsSet.valueAt(i);
-            // Reads the new configuration, if the device config properties contain the key.
-            if (properties.getKeyset().contains(key)) {
-                readAndSaveValueFromDeviceConfig(key);
-            }
-        }
-    }
-
-    /**
-     * Adds {@code key} to a set of flags that can be updated from the server if
-     * {@code isActive} is {@code true} and read it's current value from {@link DeviceConfig}.
-     */
-    void updateFlagActiveStatus(boolean isActive, String key) {
-        if (!isActive) {
-            return;
-        }
-        mActiveDeviceConfigsSet.add(key);
-        readAndSaveValueFromDeviceConfig(key);
-    }
-
-    /**
-     * Returns values of the {@code key} flag.
-     *
-     * @throws AssertionError {@code key} isn't recognised.
-     */
-    boolean getFlag(String key) {
-        switch (key) {
-            case KEY_ENABLE_CAMERA_COMPAT_TREATMENT:
-                return mIsCameraCompatTreatmentEnabled;
-            case KEY_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY:
-                return mIsDisplayRotationImmersiveAppCompatPolicyEnabled;
-            case KEY_ALLOW_IGNORE_ORIENTATION_REQUEST:
-                return mIsAllowIgnoreOrientationRequest;
-            case KEY_ENABLE_COMPAT_FAKE_FOCUS:
-                return mIsCompatFakeFocusAllowed;
-            case KEY_ENABLE_LETTERBOX_TRANSLUCENT_ACTIVITY:
-                return mIsTranslucentLetterboxingAllowed;
-            default:
-                throw new AssertionError("Unexpected flag name: " + key);
-        }
-    }
-
-    private void readAndSaveValueFromDeviceConfig(String key) {
-        Boolean defaultValue = sKeyToDefaultValueMap.get(key);
-        if (defaultValue == null) {
-            throw new AssertionError("Haven't found default value for flag: " + key);
-        }
-        switch (key) {
-            case KEY_ENABLE_CAMERA_COMPAT_TREATMENT:
-                mIsCameraCompatTreatmentEnabled = getDeviceConfig(key, defaultValue);
-                break;
-            case KEY_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY:
-                mIsDisplayRotationImmersiveAppCompatPolicyEnabled =
-                        getDeviceConfig(key, defaultValue);
-                break;
-            case KEY_ALLOW_IGNORE_ORIENTATION_REQUEST:
-                mIsAllowIgnoreOrientationRequest = getDeviceConfig(key, defaultValue);
-                break;
-            case KEY_ENABLE_COMPAT_FAKE_FOCUS:
-                mIsCompatFakeFocusAllowed = getDeviceConfig(key, defaultValue);
-                break;
-            case KEY_ENABLE_LETTERBOX_TRANSLUCENT_ACTIVITY:
-                mIsTranslucentLetterboxingAllowed = getDeviceConfig(key, defaultValue);
-                break;
-            default:
-                throw new AssertionError("Unexpected flag name: " + key);
-        }
-    }
-
-    private boolean getDeviceConfig(String key, boolean defaultValue) {
-        return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_WINDOW_MANAGER,
-                key, defaultValue);
-    }
-}
diff --git a/services/core/java/com/android/server/wm/LetterboxConfigurationPersister.java b/services/core/java/com/android/server/wm/LetterboxConfigurationPersister.java
index 7563397..38aa903 100644
--- a/services/core/java/com/android/server/wm/LetterboxConfigurationPersister.java
+++ b/services/core/java/com/android/server/wm/LetterboxConfigurationPersister.java
@@ -23,7 +23,6 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.content.Context;
 import android.os.Environment;
 import android.os.StrictMode;
 import android.os.StrictMode.ThreadPolicy;
@@ -53,10 +52,8 @@
     private static final String TAG =
             TAG_WITH_CLASS_NAME ? "LetterboxConfigurationPersister" : TAG_WM;
 
-    @VisibleForTesting
-    static final String LETTERBOX_CONFIGURATION_FILENAME = "letterbox_config";
+    private static final String LETTERBOX_CONFIGURATION_FILENAME = "letterbox_config";
 
-    private final Context mContext;
     private final Supplier<Integer> mDefaultHorizontalReachabilitySupplier;
     private final Supplier<Integer> mDefaultVerticalReachabilitySupplier;
     private final Supplier<Integer> mDefaultBookModeReachabilitySupplier;
@@ -97,36 +94,32 @@
     @NonNull
     private final PersisterQueue mPersisterQueue;
 
-    LetterboxConfigurationPersister(Context systemUiContext,
-            Supplier<Integer> defaultHorizontalReachabilitySupplier,
-            Supplier<Integer> defaultVerticalReachabilitySupplier,
-            Supplier<Integer> defaultBookModeReachabilitySupplier,
-            Supplier<Integer> defaultTabletopModeReachabilitySupplier) {
-        this(systemUiContext, defaultHorizontalReachabilitySupplier,
-                defaultVerticalReachabilitySupplier,
-                defaultBookModeReachabilitySupplier,
-                defaultTabletopModeReachabilitySupplier,
+    LetterboxConfigurationPersister(
+            @NonNull Supplier<Integer> defaultHorizontalReachabilitySupplier,
+            @NonNull Supplier<Integer> defaultVerticalReachabilitySupplier,
+            @NonNull Supplier<Integer> defaultBookModeReachabilitySupplier,
+            @NonNull Supplier<Integer> defaultTabletopModeReachabilitySupplier) {
+        this(defaultHorizontalReachabilitySupplier, defaultVerticalReachabilitySupplier,
+                defaultBookModeReachabilitySupplier, defaultTabletopModeReachabilitySupplier,
                 Environment.getDataSystemDirectory(), new PersisterQueue(),
-                /* completionCallback */ null);
+                /* completionCallback */ null, LETTERBOX_CONFIGURATION_FILENAME);
     }
 
     @VisibleForTesting
-    LetterboxConfigurationPersister(Context systemUiContext,
-            Supplier<Integer> defaultHorizontalReachabilitySupplier,
-            Supplier<Integer> defaultVerticalReachabilitySupplier,
-            Supplier<Integer> defaultBookModeReachabilitySupplier,
-            Supplier<Integer> defaultTabletopModeReachabilitySupplier,
-            File configFolder,
-            PersisterQueue persisterQueue, @Nullable Consumer<String> completionCallback) {
-        mContext = systemUiContext.createDeviceProtectedStorageContext();
+    LetterboxConfigurationPersister(
+            @NonNull Supplier<Integer> defaultHorizontalReachabilitySupplier,
+            @NonNull Supplier<Integer> defaultVerticalReachabilitySupplier,
+            @NonNull Supplier<Integer> defaultBookModeReachabilitySupplier,
+            @NonNull Supplier<Integer> defaultTabletopModeReachabilitySupplier,
+            @NonNull File configFolder, @NonNull PersisterQueue persisterQueue,
+            @Nullable Consumer<String> completionCallback,
+            @NonNull String letterboxConfigurationFileName) {
         mDefaultHorizontalReachabilitySupplier = defaultHorizontalReachabilitySupplier;
         mDefaultVerticalReachabilitySupplier = defaultVerticalReachabilitySupplier;
-        mDefaultBookModeReachabilitySupplier =
-                defaultBookModeReachabilitySupplier;
-        mDefaultTabletopModeReachabilitySupplier =
-                defaultTabletopModeReachabilitySupplier;
+        mDefaultBookModeReachabilitySupplier = defaultBookModeReachabilitySupplier;
+        mDefaultTabletopModeReachabilitySupplier = defaultTabletopModeReachabilitySupplier;
         mCompletionCallback = completionCallback;
-        final File prefFiles = new File(configFolder, LETTERBOX_CONFIGURATION_FILENAME);
+        final File prefFiles = new File(configFolder, letterboxConfigurationFileName);
         mConfigurationFile = new AtomicFile(prefFiles);
         mPersisterQueue = persisterQueue;
         runWithDiskReadsThreadPolicy(this::readCurrentConfiguration);
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index d83c861..39f7570 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -294,18 +294,15 @@
                         PROPERTY_COMPAT_ENABLE_FAKE_FOCUS);
         mBooleanPropertyCameraCompatAllowForceRotation =
                 readComponentProperty(packageManager, mActivityRecord.packageName,
-                        () -> mLetterboxConfiguration.isCameraCompatTreatmentEnabled(
-                                /* checkDeviceConfig */ true),
+                        () -> mLetterboxConfiguration.isCameraCompatTreatmentEnabled(),
                         PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION);
         mBooleanPropertyCameraCompatAllowRefresh =
                 readComponentProperty(packageManager, mActivityRecord.packageName,
-                        () -> mLetterboxConfiguration.isCameraCompatTreatmentEnabled(
-                                /* checkDeviceConfig */ true),
+                        () -> mLetterboxConfiguration.isCameraCompatTreatmentEnabled(),
                         PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH);
         mBooleanPropertyCameraCompatEnableRefreshViaPause =
                 readComponentProperty(packageManager, mActivityRecord.packageName,
-                        () -> mLetterboxConfiguration.isCameraCompatTreatmentEnabled(
-                                /* checkDeviceConfig */ true),
+                        () -> mLetterboxConfiguration.isCameraCompatTreatmentEnabled(),
                         PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE);
 
         mBooleanPropertyAllowOrientationOverride =
@@ -697,7 +694,7 @@
     boolean shouldRefreshActivityForCameraCompat() {
         return shouldEnableWithOptOutOverrideAndProperty(
                 /* gatingCondition */ () -> mLetterboxConfiguration
-                        .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true),
+                        .isCameraCompatTreatmentEnabled(),
                 mIsOverrideCameraCompatDisableRefreshEnabled,
                 mBooleanPropertyCameraCompatAllowRefresh);
     }
@@ -719,7 +716,7 @@
     boolean shouldRefreshActivityViaPauseForCameraCompat() {
         return shouldEnableWithOverrideAndProperty(
                 /* gatingCondition */ () -> mLetterboxConfiguration
-                        .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true),
+                        .isCameraCompatTreatmentEnabled(),
                 mIsOverrideCameraCompatEnableRefreshViaPauseEnabled,
                 mBooleanPropertyCameraCompatEnableRefreshViaPause);
     }
@@ -738,7 +735,7 @@
     boolean shouldForceRotateForCameraCompat() {
         return shouldEnableWithOptOutOverrideAndProperty(
                 /* gatingCondition */ () -> mLetterboxConfiguration
-                        .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true),
+                        .isCameraCompatTreatmentEnabled(),
                 mIsOverrideCameraCompatDisableForceRotationEnabled,
                 mBooleanPropertyCameraCompatAllowForceRotation);
     }
@@ -897,7 +894,7 @@
                         this::shouldLetterboxHaveRoundedCorners,
                         this::getLetterboxBackgroundColor,
                         this::hasWallpaperBackgroundForLetterbox,
-                        this::getLetterboxWallpaperBlurRadius,
+                        this::getLetterboxWallpaperBlurRadiusPx,
                         this::getLetterboxWallpaperDarkScrimAlpha,
                         this::handleHorizontalDoubleTap,
                         this::handleVerticalDoubleTap,
@@ -1318,7 +1315,7 @@
             case LETTERBOX_BACKGROUND_WALLPAPER:
                 if (hasWallpaperBackgroundForLetterbox()) {
                     // Color is used for translucent scrim that dims wallpaper.
-                    return Color.valueOf(Color.BLACK);
+                    return mLetterboxConfiguration.getLetterboxBackgroundColor();
                 }
                 Slog.w(TAG, "Wallpaper option is selected for letterbox background but "
                         + "blur is not supported by a device or not supported in the current "
@@ -1428,7 +1425,8 @@
         for (int i = state.sourceSize() - 1; i >= 0; i--) {
             final InsetsSource source = state.sourceAt(i);
             if (source.getType() == WindowInsets.Type.navigationBars()
-                    && source.insetsRoundedCornerFrame() && source.isVisible()) {
+                    && source.hasFlags(InsetsSource.FLAG_INSETS_ROUNDED_CORNER)
+                    && source.isVisible()) {
                 return source;
             }
         }
@@ -1474,10 +1472,10 @@
                         // Don't use wallpaper as a background if letterboxed for display cutout.
                         && isLetterboxedNotForDisplayCutout(mainWindow)
                         // Check that dark scrim alpha or blur radius are provided
-                        && (getLetterboxWallpaperBlurRadius() > 0
+                        && (getLetterboxWallpaperBlurRadiusPx() > 0
                                 || getLetterboxWallpaperDarkScrimAlpha() > 0)
                         // Check that blur is supported by a device if blur radius is provided.
-                        && (getLetterboxWallpaperBlurRadius() <= 0
+                        && (getLetterboxWallpaperBlurRadiusPx() <= 0
                                 || isLetterboxWallpaperBlurSupported());
         if (mShowWallpaperForLetterboxBackground != wallpaperShouldBeShown) {
             mShowWallpaperForLetterboxBackground = wallpaperShouldBeShown;
@@ -1485,9 +1483,9 @@
         }
     }
 
-    private int getLetterboxWallpaperBlurRadius() {
-        int blurRadius = mLetterboxConfiguration.getLetterboxBackgroundWallpaperBlurRadius();
-        return blurRadius < 0 ? 0 : blurRadius;
+    private int getLetterboxWallpaperBlurRadiusPx() {
+        int blurRadius = mLetterboxConfiguration.getLetterboxBackgroundWallpaperBlurRadiusPx();
+        return Math.max(blurRadius, 0);
     }
 
     private float getLetterboxWallpaperDarkScrimAlpha() {
@@ -1537,7 +1535,7 @@
             pw.println(prefix + "  letterboxBackgroundWallpaperDarkScrimAlpha="
                     + getLetterboxWallpaperDarkScrimAlpha());
             pw.println(prefix + "  letterboxBackgroundWallpaperBlurRadius="
-                    + getLetterboxWallpaperBlurRadius());
+                    + getLetterboxWallpaperBlurRadiusPx());
         }
 
         pw.println(prefix + "  isHorizontalReachabilityEnabled="
diff --git a/services/core/java/com/android/server/wm/OWNERS b/services/core/java/com/android/server/wm/OWNERS
index 07f3bc6..0af9fe9 100644
--- a/services/core/java/com/android/server/wm/OWNERS
+++ b/services/core/java/com/android/server/wm/OWNERS
@@ -15,6 +15,7 @@
 lihongyu@google.com
 mariiasand@google.com
 rgl@google.com
+yunfanc@google.com
 
 per-file BackgroundActivityStartController.java = set noparent
 per-file BackgroundActivityStartController.java = brufino@google.com, ogunwale@google.com, louischang@google.com, lus@google.com, rickywai@google.com
diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java
index e47787e..f33ecaa 100644
--- a/services/core/java/com/android/server/wm/RecentTasks.java
+++ b/services/core/java/com/android/server/wm/RecentTasks.java
@@ -183,6 +183,8 @@
 
     /** The non-empty tasks that are removed from recent tasks (see {@link #removeForAddTask}). */
     private final ArrayList<Task> mHiddenTasks = new ArrayList<>();
+    /** The maximum size that the hidden tasks are cached. */
+    private static final int MAX_HIDDEN_TASK_SIZE = 10;
 
     /** Whether to trim inactive tasks when activities are idle. */
     private boolean mCheckTrimmableTasksOnIdle;
@@ -682,6 +684,26 @@
         }
     }
 
+    /**
+     * Removes the oldest recent task that is compatible with the given one. This is possible if
+     * the task windowing mode changed after being added to the Recents.
+     */
+    void removeCompatibleRecentTask(Task task) {
+        final int taskIndex = mTasks.indexOf(task);
+        if (taskIndex < 0) {
+            return;
+        }
+
+        final int candidateIndex = findRemoveIndexForTask(task, false /* includingSelf */);
+        if (candidateIndex == -1) {
+            // Nothing to trim
+            return;
+        }
+
+        final Task taskToRemove = taskIndex > candidateIndex ? task : mTasks.get(candidateIndex);
+        remove(taskToRemove);
+    }
+
     void removeTasksByPackageName(String packageName, int userId) {
         for (int i = mTasks.size() - 1; i >= 0; --i) {
             final Task task = mTasks.get(i);
@@ -1104,6 +1126,10 @@
                         // front unless overridden by the provided activity options
                         mTasks.remove(taskIndex);
                         mTasks.add(0, task);
+                        if (taskIndex != 0) {
+                            // Only notify when position changes
+                            mTaskNotificationController.notifyTaskListUpdated();
+                        }
 
                         if (DEBUG_RECENTS) {
                             Slog.d(TAG_RECENTS, "addRecent: moving to top " + task
@@ -1473,9 +1499,13 @@
         return task.compareTo(rootHomeTask) < 0;
     }
 
-    /** Remove the tasks that user may not be able to return. */
+    /** Remove the tasks that user may not be able to return when exceeds the cache limit. */
     private void removeUnreachableHiddenTasks(int windowingMode) {
-        for (int i = mHiddenTasks.size() - 1; i >= 0; i--) {
+        final int size = mHiddenTasks.size();
+        if (size <= MAX_HIDDEN_TASK_SIZE) {
+            return;
+        }
+        for (int i = size - 1; i >= MAX_HIDDEN_TASK_SIZE; i--) {
             final Task hiddenTask = mHiddenTasks.get(i);
             if (!hiddenTask.hasChild() || hiddenTask.inRecents) {
                 // The task was removed by other path or it became reachable (added to recents).
@@ -1519,7 +1549,7 @@
                 // A non-empty task is replaced by a new task. Because the removed task is no longer
                 // managed by the recent tasks list, add it to the hidden list to prevent the task
                 // from becoming dangling.
-                mHiddenTasks.add(removedTask);
+                mHiddenTasks.add(0, removedTask);
             }
             notifyTaskRemoved(removedTask, false /* wasTrimmed */, false /* killProcess */);
             if (DEBUG_RECENTS_TRIM_TASKS) {
@@ -1536,6 +1566,10 @@
      * list (if any).
      */
     private int findRemoveIndexForAddTask(Task task) {
+        return findRemoveIndexForTask(task, true /* includingSelf */);
+    }
+
+    private int findRemoveIndexForTask(Task task, boolean includingSelf) {
         final int recentsCount = mTasks.size();
         final Intent intent = task.intent;
         final boolean document = intent != null && intent.isDocument();
@@ -1552,7 +1586,7 @@
                         task.affinity != null && task.affinity.equals(t.affinity);
                 final boolean sameIntent = intent != null && intent.filterEquals(trIntent);
                 boolean multiTasksAllowed = false;
-                final int flags = intent.getFlags();
+                final int flags = intent != null ? intent.getFlags() : 0;
                 if ((flags & (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_NEW_DOCUMENT)) != 0
                         && (flags & FLAG_ACTIVITY_MULTIPLE_TASK) != 0) {
                     multiTasksAllowed = true;
@@ -1591,6 +1625,8 @@
                     // existing task
                     continue;
                 }
+            } else if (!includingSelf) {
+                continue;
             }
             return i;
         }
diff --git a/services/core/java/com/android/server/wm/RemoteDisplayChangeController.java b/services/core/java/com/android/server/wm/RemoteDisplayChangeController.java
index 43baebc..e646f14 100644
--- a/services/core/java/com/android/server/wm/RemoteDisplayChangeController.java
+++ b/services/core/java/com/android/server/wm/RemoteDisplayChangeController.java
@@ -114,9 +114,15 @@
         // timed-out, so run all continue callbacks and clear the list
         synchronized (mService.mGlobalLock) {
             for (int i = 0; i < mCallbacks.size(); ++i) {
-                mCallbacks.get(i).onContinueRemoteDisplayChange(null /* transaction */);
+                final ContinueRemoteDisplayChangeCallback callback = mCallbacks.get(i);
+                if (i == mCallbacks.size() - 1) {
+                    // Clear all callbacks before calling the last one, so that if the callback
+                    // itself calls {@link #isWaitingForRemoteDisplayChange()}, it will get
+                    // {@code false}. After all, there is nothing pending after this one.
+                    mCallbacks.clear();
+                }
+                callback.onContinueRemoteDisplayChange(null /* transaction */);
             }
-            mCallbacks.clear();
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 05f95f81..2f0c303 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -102,6 +102,7 @@
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.ResolveInfo;
+import android.content.pm.UserProperties;
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.hardware.display.DisplayManager;
@@ -148,6 +149,7 @@
 import com.android.server.am.ActivityManagerService;
 import com.android.server.am.AppTimeTracker;
 import com.android.server.am.UserState;
+import com.android.server.pm.UserManagerInternal;
 import com.android.server.policy.PermissionPolicyInternal;
 import com.android.server.policy.WindowManagerPolicy;
 import com.android.server.utils.Slogf;
@@ -1023,31 +1025,28 @@
      * manager may choose to mirror or blank the display.
      */
     boolean handleNotObscuredLocked(WindowState w, boolean obscured, boolean syswin) {
-        final WindowManager.LayoutParams attrs = w.mAttrs;
-        final int attrFlags = attrs.flags;
         final boolean onScreen = w.isOnScreen();
-        final boolean canBeSeen = w.isDisplayed();
-        final int privateflags = attrs.privateFlags;
         boolean displayHasContent = false;
 
         ProtoLog.d(WM_DEBUG_KEEP_SCREEN_ON,
                 "handleNotObscuredLocked w: %s, w.mHasSurface: %b, w.isOnScreen(): %b, w"
                         + ".isDisplayedLw(): %b, w.mAttrs.userActivityTimeout: %d",
                 w, w.mHasSurface, onScreen, w.isDisplayed(), w.mAttrs.userActivityTimeout);
-        if (w.mHasSurface && onScreen) {
-            if (!syswin && w.mAttrs.userActivityTimeout >= 0 && mUserActivityTimeout < 0) {
-                mUserActivityTimeout = w.mAttrs.userActivityTimeout;
-                ProtoLog.d(WM_DEBUG_KEEP_SCREEN_ON, "mUserActivityTimeout set to %d",
-                        mUserActivityTimeout);
-            }
+        if (!onScreen) {
+            return false;
         }
-        if (w.mHasSurface && canBeSeen) {
+        if (!syswin && w.mAttrs.userActivityTimeout >= 0 && mUserActivityTimeout < 0) {
+            mUserActivityTimeout = w.mAttrs.userActivityTimeout;
+            ProtoLog.d(WM_DEBUG_KEEP_SCREEN_ON, "mUserActivityTimeout set to %d",
+                    mUserActivityTimeout);
+        }
+        if (w.isDrawn() || (w.mActivityRecord != null && w.mActivityRecord.firstWindowDrawn
+                && w.mActivityRecord.isVisibleRequested())) {
             if (!syswin && w.mAttrs.screenBrightness >= 0
                     && Float.isNaN(mScreenBrightnessOverride)) {
                 mScreenBrightnessOverride = w.mAttrs.screenBrightness;
             }
 
-            final int type = attrs.type;
             // This function assumes that the contents of the default display are processed first
             // before secondary displays.
             final DisplayContent displayContent = w.getDisplayContent();
@@ -1062,12 +1061,12 @@
             } else if (displayContent != null &&
                     (!mObscureApplicationContentOnSecondaryDisplays
                             || displayContent.isKeyguardAlwaysUnlocked()
-                            || (obscured && type == TYPE_KEYGUARD_DIALOG))) {
+                            || (obscured && w.mAttrs.type == TYPE_KEYGUARD_DIALOG))) {
                 // Allow full screen keyguard presentation dialogs to be seen, or simply ignore the
                 // keyguard if this display is always unlocked.
                 displayHasContent = true;
             }
-            if ((privateflags & PRIVATE_FLAG_SUSTAINED_PERFORMANCE_MODE) != 0) {
+            if ((w.mAttrs.privateFlags & PRIVATE_FLAG_SUSTAINED_PERFORMANCE_MODE) != 0) {
                 mSustainedPerformanceModeCurrent = true;
             }
         }
@@ -1850,9 +1849,8 @@
             // Don't do recursive work.
             return;
         }
-
+        mTaskSupervisor.beginActivityVisibilityUpdate();
         try {
-            mTaskSupervisor.beginActivityVisibilityUpdate();
             // First the front root tasks. In case any are not fullscreen and are in front of home.
             for (int displayNdx = getChildCount() - 1; displayNdx >= 0; --displayNdx) {
                 final DisplayContent display = getChildAt(displayNdx);
@@ -1881,6 +1879,14 @@
             rootTask.switchUser(userId);
         });
 
+
+        if (topFocusedRootTask != null && isAlwaysVisibleUser(topFocusedRootTask.mUserId)) {
+            Slog.i(TAG, "Persisting top task because it belongs to an always-visible user");
+            // For a normal user-switch, we will restore the new user's task. But if the pre-switch
+            // top task is an always-visible (Communal) one, keep it even after the switch.
+            mUserRootTaskInFront.put(mCurrentUser, focusRootTaskId);
+        }
+
         final int restoreRootTaskId = mUserRootTaskInFront.get(userId);
         Task rootTask = getRootTask(restoreRootTaskId);
         if (rootTask == null) {
@@ -1896,6 +1902,13 @@
         return homeInFront;
     }
 
+    /** Returns whether the given user is to be always-visible (e.g. a communal profile). */
+    private boolean isAlwaysVisibleUser(@UserIdInt int userId) {
+        final UserManagerInternal umi = LocalServices.getService(UserManagerInternal.class);
+        final UserProperties properties = umi.getUserProperties(userId);
+        return properties != null && properties.getAlwaysVisible();
+    }
+
     void removeUser(int userId) {
         mUserRootTaskInFront.delete(userId);
     }
diff --git a/services/core/java/com/android/server/wm/SafeActivityOptions.java b/services/core/java/com/android/server/wm/SafeActivityOptions.java
index c914fa1..fe3094e 100644
--- a/services/core/java/com/android/server/wm/SafeActivityOptions.java
+++ b/services/core/java/com/android/server/wm/SafeActivityOptions.java
@@ -293,26 +293,7 @@
             throw new SecurityException(msg);
         }
         // Check if the caller is allowed to launch on the specified display area.
-        final WindowContainerToken daToken = options.getLaunchTaskDisplayArea();
-        TaskDisplayArea taskDisplayArea = daToken != null
-                ? (TaskDisplayArea) WindowContainer.fromBinder(daToken.asBinder()) : null;
-
-        // If we do not have a task display area token, check if the launch task display area
-        // feature id is specified.
-        if (taskDisplayArea == null) {
-            final int launchTaskDisplayAreaFeatureId = options.getLaunchTaskDisplayAreaFeatureId();
-            if (launchTaskDisplayAreaFeatureId != FEATURE_UNDEFINED) {
-                final int launchDisplayId = options.getLaunchDisplayId() == INVALID_DISPLAY
-                        ? DEFAULT_DISPLAY : options.getLaunchDisplayId();
-                final DisplayContent dc = supervisor.mRootWindowContainer
-                        .getDisplayContent(launchDisplayId);
-                if (dc != null) {
-                    taskDisplayArea = dc.getItemFromTaskDisplayAreas(tda ->
-                            tda.mFeatureId == launchTaskDisplayAreaFeatureId ? tda : null);
-                }
-            }
-        }
-
+        final TaskDisplayArea taskDisplayArea = getLaunchTaskDisplayArea(options, supervisor);
         if (aInfo != null && taskDisplayArea != null
                 && !supervisor.isCallerAllowedToLaunchOnTaskDisplayArea(callingPid, callingUid,
                 taskDisplayArea, aInfo)) {
@@ -428,6 +409,32 @@
         }
     }
 
+    @VisibleForTesting
+    TaskDisplayArea getLaunchTaskDisplayArea(ActivityOptions options,
+            ActivityTaskSupervisor supervisor) {
+        final WindowContainerToken daToken = options.getLaunchTaskDisplayArea();
+        TaskDisplayArea taskDisplayArea = daToken != null
+                ? (TaskDisplayArea) WindowContainer.fromBinder(daToken.asBinder()) : null;
+        if (taskDisplayArea != null) {
+            return taskDisplayArea;
+        }
+
+        // If we do not have a task display area token, check if the launch task display area
+        // feature id is specified.
+        final int launchTaskDisplayAreaFeatureId = options.getLaunchTaskDisplayAreaFeatureId();
+        if (launchTaskDisplayAreaFeatureId != FEATURE_UNDEFINED) {
+            final int launchDisplayId = options.getLaunchDisplayId() == INVALID_DISPLAY
+                    ? DEFAULT_DISPLAY : options.getLaunchDisplayId();
+            final DisplayContent dc = supervisor.mRootWindowContainer
+                    .getDisplayContent(launchDisplayId);
+            if (dc != null) {
+                taskDisplayArea = dc.getItemFromTaskDisplayAreas(tda ->
+                        tda.mFeatureId == launchTaskDisplayAreaFeatureId ? tda : null);
+            }
+        }
+        return taskDisplayArea;
+    }
+
     private boolean isAssistant(ActivityTaskManagerService atmService, int callingUid) {
         if (atmService.mActiveVoiceInteractionServiceComponent == null) {
             return false;
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 7b10c63..b49c5fb 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -687,11 +687,11 @@
     @Override
     public void updateRequestedVisibleTypes(IWindow window, @InsetsType int requestedVisibleTypes) {
         synchronized (mService.mGlobalLock) {
-            final WindowState windowState = mService.windowForClientLocked(this, window,
+            final WindowState win = mService.windowForClientLocked(this, window,
                     false /* throwOnError */);
-            if (windowState != null) {
-                windowState.setRequestedVisibleTypes(requestedVisibleTypes);
-                windowState.getDisplayContent().getInsetsPolicy().onInsetsModified(windowState);
+            if (win != null) {
+                win.setRequestedVisibleTypes(requestedVisibleTypes);
+                win.getDisplayContent().getInsetsPolicy().onRequestedVisibleTypesChanged(win);
             }
         }
     }
diff --git a/services/core/java/com/android/server/wm/SynchedDeviceConfig.java b/services/core/java/com/android/server/wm/SynchedDeviceConfig.java
new file mode 100644
index 0000000..c2e819e
--- /dev/null
+++ b/services/core/java/com/android/server/wm/SynchedDeviceConfig.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import android.annotation.NonNull;
+import android.provider.DeviceConfig;
+
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Executor;
+
+/**
+ * Utility class that caches {@link DeviceConfig} flags and listens to updates by implementing
+ * {@link DeviceConfig.OnPropertiesChangedListener}.
+ */
+final class SynchedDeviceConfig implements DeviceConfig.OnPropertiesChangedListener {
+
+    private final String mNamespace;
+    private final Executor mExecutor;
+
+    private final Map<String, SynchedDeviceConfigEntry> mDeviceConfigEntries;
+
+    /**
+     * @param namespace The namespace for the {@link DeviceConfig}
+     * @param executor  The {@link Executor} implementation to use when receiving updates
+     * @return the Builder implementation for the SynchedDeviceConfig
+     */
+    @NonNull
+    static SynchedDeviceConfigBuilder builder(@NonNull String namespace,
+            @NonNull Executor executor) {
+        return new SynchedDeviceConfigBuilder(namespace, executor);
+    }
+
+    private SynchedDeviceConfig(@NonNull String namespace, @NonNull Executor executor,
+            @NonNull Map<String, SynchedDeviceConfigEntry> deviceConfigEntries) {
+        mNamespace = namespace;
+        mExecutor = executor;
+        mDeviceConfigEntries = deviceConfigEntries;
+    }
+
+    @Override
+    public void onPropertiesChanged(@NonNull final DeviceConfig.Properties properties) {
+        for (SynchedDeviceConfigEntry entry : mDeviceConfigEntries.values()) {
+            if (properties.getKeyset().contains(entry.mFlagKey)) {
+                entry.updateValue(properties.getBoolean(entry.mFlagKey, entry.mDefaultValue));
+            }
+        }
+    }
+
+    /**
+     * Builds the {@link SynchedDeviceConfig} and start listening to the {@link DeviceConfig}
+     * updates.
+     *
+     * @return The {@link SynchedDeviceConfig}
+     */
+    @NonNull
+    private SynchedDeviceConfig start() {
+        DeviceConfig.addOnPropertiesChangedListener(mNamespace,
+                mExecutor, /* onPropertiesChangedListener */ this);
+        return this;
+    }
+
+    /**
+     * Requests a {@link DeviceConfig} update for all the flags
+     */
+    @NonNull
+    private SynchedDeviceConfig updateFlags() {
+        mDeviceConfigEntries.forEach((key, entry) -> entry.updateValue(
+                isDeviceConfigFlagEnabled(key, entry.mDefaultValue)));
+        return this;
+    }
+
+    /**
+     * Returns values of the {@code key} flag with the following criteria:
+     *
+     * <ul>
+     *     <li>{@code false} if the build time flag is disabled.
+     *     <li>{@code defaultValue} if the build time flag is enabled and no {@link DeviceConfig}
+     *          updates happened
+     *     <li>Last value from {@link DeviceConfig} in case of updates.
+     * </ul>
+     *
+     * @throws IllegalArgumentException {@code key} isn't recognised.
+     */
+    boolean getFlagValue(@NonNull String key) {
+        return findEntry(key).map(SynchedDeviceConfigEntry::getValue)
+                .orElseThrow(() -> new IllegalArgumentException("Unexpected flag name: " + key));
+    }
+
+    /**
+     * @return {@code true} if the flag for the given {@code key} was enabled at build time.
+     */
+    boolean isBuildTimeFlagEnabled(@NonNull String key) {
+        return findEntry(key).map(SynchedDeviceConfigEntry::isBuildTimeFlagEnabled)
+                .orElseThrow(() -> new IllegalArgumentException("Unexpected flag name: " + key));
+    }
+
+    private boolean isDeviceConfigFlagEnabled(@NonNull String key, boolean defaultValue) {
+        return DeviceConfig.getBoolean(mNamespace, key, defaultValue);
+    }
+
+    @NonNull
+    private Optional<SynchedDeviceConfigEntry> findEntry(@NonNull String key) {
+        return Optional.ofNullable(mDeviceConfigEntries.get(key));
+    }
+
+    static class SynchedDeviceConfigBuilder {
+
+        private final String mNamespace;
+        private final Executor mExecutor;
+
+        private final Map<String, SynchedDeviceConfigEntry> mDeviceConfigEntries =
+                new ConcurrentHashMap<>();
+
+        private SynchedDeviceConfigBuilder(@NonNull String namespace, @NonNull Executor executor) {
+            mNamespace = namespace;
+            mExecutor = executor;
+        }
+
+        @NonNull
+        SynchedDeviceConfigBuilder addDeviceConfigEntry(@NonNull String key,
+                boolean defaultValue, boolean enabled) {
+            if (mDeviceConfigEntries.containsKey(key)) {
+                throw new AssertionError("Key already present: " + key);
+            }
+            mDeviceConfigEntries.put(key,
+                    new SynchedDeviceConfigEntry(key, defaultValue, enabled));
+            return this;
+        }
+
+        @NonNull
+        SynchedDeviceConfig build() {
+            return new SynchedDeviceConfig(mNamespace, mExecutor,
+                    mDeviceConfigEntries).updateFlags().start();
+        }
+    }
+
+    /**
+     * Contains all the information related to an entry to be managed by DeviceConfig
+     */
+    private static class SynchedDeviceConfigEntry {
+
+        // The key of the specific configuration flag
+        private final String mFlagKey;
+
+        // The value of the flag at build time.
+        private final boolean mBuildTimeFlagEnabled;
+
+        // The initial value of the flag when mBuildTimeFlagEnabled is true.
+        private final boolean mDefaultValue;
+
+        // The current value of the flag when mBuildTimeFlagEnabled is true.
+        private volatile boolean mOverrideValue;
+
+        private SynchedDeviceConfigEntry(@NonNull String flagKey, boolean defaultValue,
+                boolean enabled) {
+            mFlagKey = flagKey;
+            mOverrideValue = mDefaultValue = defaultValue;
+            mBuildTimeFlagEnabled = enabled;
+        }
+
+        @NonNull
+        private void updateValue(boolean newValue) {
+            mOverrideValue = newValue;
+        }
+
+        private boolean getValue() {
+            return mBuildTimeFlagEnabled && mOverrideValue;
+        }
+
+        private boolean isBuildTimeFlagEnabled() {
+            return mBuildTimeFlagEnabled;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/wm/SystemGesturesPointerEventListener.java b/services/core/java/com/android/server/wm/SystemGesturesPointerEventListener.java
index 878b33f..1007357 100644
--- a/services/core/java/com/android/server/wm/SystemGesturesPointerEventListener.java
+++ b/services/core/java/com/android/server/wm/SystemGesturesPointerEventListener.java
@@ -20,6 +20,8 @@
 import static android.view.DisplayCutout.BOUNDS_POSITION_LEFT;
 import static android.view.DisplayCutout.BOUNDS_POSITION_RIGHT;
 import static android.view.DisplayCutout.BOUNDS_POSITION_TOP;
+import static android.view.MotionEvent.AXIS_GESTURE_SWIPE_FINGER_COUNT;
+import static android.view.MotionEvent.CLASSIFICATION_MULTI_FINGER_SWIPE;
 
 import android.annotation.NonNull;
 import android.content.Context;
@@ -59,6 +61,12 @@
     private static final int SWIPE_FROM_RIGHT = 3;
     private static final int SWIPE_FROM_LEFT = 4;
 
+    private static final int TRACKPAD_SWIPE_NONE = 0;
+    private static final int TRACKPAD_SWIPE_FROM_TOP = 1;
+    private static final int TRACKPAD_SWIPE_FROM_BOTTOM = 2;
+    private static final int TRACKPAD_SWIPE_FROM_RIGHT = 3;
+    private static final int TRACKPAD_SWIPE_FROM_LEFT = 4;
+
     private final Context mContext;
     private final Handler mHandler;
     private int mDisplayCutoutTouchableRegionSize;
@@ -207,6 +215,25 @@
                 break;
             case MotionEvent.ACTION_MOVE:
                 if (mSwipeFireable) {
+                    int trackpadSwipe = detectTrackpadThreeFingerSwipe(event);
+                    mSwipeFireable = trackpadSwipe == TRACKPAD_SWIPE_NONE;
+                    if (!mSwipeFireable) {
+                        if (trackpadSwipe == TRACKPAD_SWIPE_FROM_TOP) {
+                            if (DEBUG) Slog.d(TAG, "Firing onSwipeFromTop from trackpad");
+                            mCallbacks.onSwipeFromTop();
+                        } else if (trackpadSwipe == TRACKPAD_SWIPE_FROM_BOTTOM) {
+                            if (DEBUG) Slog.d(TAG, "Firing onSwipeFromBottom from trackpad");
+                            mCallbacks.onSwipeFromBottom();
+                        } else if (trackpadSwipe == TRACKPAD_SWIPE_FROM_RIGHT) {
+                            if (DEBUG) Slog.d(TAG, "Firing onSwipeFromRight from trackpad");
+                            mCallbacks.onSwipeFromRight();
+                        } else if (trackpadSwipe == TRACKPAD_SWIPE_FROM_LEFT) {
+                            if (DEBUG) Slog.d(TAG, "Firing onSwipeFromLeft from trackpad");
+                            mCallbacks.onSwipeFromLeft();
+                        }
+                        break;
+                    }
+
                     final int swipe = detectSwipe(event);
                     mSwipeFireable = swipe == SWIPE_NONE;
                     if (swipe == SWIPE_FROM_TOP) {
@@ -300,6 +327,31 @@
         return mDownPointers - 1;
     }
 
+    private int detectTrackpadThreeFingerSwipe(MotionEvent move) {
+        if (!isTrackpadThreeFingerSwipe(move)) {
+            return TRACKPAD_SWIPE_NONE;
+        }
+
+        float dx = move.getX() - mDownX[0];
+        float dy = move.getY() - mDownY[0];
+        if (Math.abs(dx) < Math.abs(dy)) {
+            if (Math.abs(dy) > mSwipeDistanceThreshold) {
+                return dy > 0 ? TRACKPAD_SWIPE_FROM_TOP : TRACKPAD_SWIPE_FROM_BOTTOM;
+            }
+        } else {
+            if (Math.abs(dx) > mSwipeDistanceThreshold) {
+                return dx > 0 ? TRACKPAD_SWIPE_FROM_LEFT : TRACKPAD_SWIPE_FROM_RIGHT;
+            }
+        }
+
+        return TRACKPAD_SWIPE_NONE;
+    }
+
+    private static boolean isTrackpadThreeFingerSwipe(MotionEvent event) {
+        return event.getClassification() == CLASSIFICATION_MULTI_FINGER_SWIPE
+                && event.getAxisValue(AXIS_GESTURE_SWIPE_FINGER_COUNT) == 3;
+    }
+
     private int detectSwipe(MotionEvent move) {
         final int historySize = move.getHistorySize();
         final int pointerCount = move.getPointerCount();
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index ceb80d6..92e90ae 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -49,6 +49,7 @@
 import static android.provider.Settings.Secure.USER_SETUP_COMPLETE;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.INVALID_DISPLAY;
+import static android.view.InsetsSource.FLAG_INSETS_ROUNDED_CORNER;
 import static android.view.SurfaceControl.METADATA_TASK_ID;
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
@@ -1271,6 +1272,10 @@
         if (isPersistable) {
             mLastTimeMoved = System.currentTimeMillis();
         }
+        if (toTop && inRecents) {
+            // If task is in recents, ensure it is at the top
+            mTaskSupervisor.mRecentTasks.add(this);
+        }
     }
 
     // Close up recents linked list.
@@ -1842,6 +1847,9 @@
                 td.setEnsureStatusBarContrastWhenTransparent(
                         atd.getEnsureStatusBarContrastWhenTransparent());
             }
+            if (td.getStatusBarAppearance() == 0) {
+                td.setStatusBarAppearance(atd.getStatusBarAppearance());
+            }
             if (td.getNavigationBarColor() == 0) {
                 td.setNavigationBarColor(atd.getNavigationBarColor());
                 td.setEnsureNavigationBarContrastWhenTransparent(
@@ -2858,7 +2866,7 @@
                     getDisplayContent().getInsetsStateController().getRawInsetsState();
             for (int i = state.sourceSize() - 1; i >= 0; i--) {
                 final InsetsSource source = state.sourceAt(i);
-                if (source.insetsRoundedCornerFrame()) {
+                if (source.hasFlags(FLAG_INSETS_ROUNDED_CORNER)) {
                     animationBounds.inset(source.calculateVisibleInsets(animationBounds));
                 }
             }
@@ -3465,6 +3473,11 @@
                         top.mLetterboxUiController.getLetterboxPositionForVerticalReachability();
             }
         }
+        // User Aspect Ratio Settings is enabled if the app is not in SCM
+        info.topActivityEligibleForUserAspectRatioButton =
+                mWmService.mLetterboxConfiguration.isUserAppAspectRatioSettingsEnabled()
+                        && top != null && !info.topActivityInSizeCompat;
+        info.topActivityBoundsLetterboxed = top != null && top.areBoundsLetterboxed();
     }
 
     /**
@@ -5393,8 +5406,7 @@
         // Basic case: for simple app-centric recents, we need to recreate
         // the task if the affinity has changed.
 
-        final String affinity = ActivityRecord.computeTaskAffinity(destAffinity, srec.getUid(),
-                srec.launchMode, srec.mActivityComponent);
+        final String affinity = ActivityRecord.computeTaskAffinity(destAffinity, srec.getUid());
         if (srec == null || srec.getTask().affinity == null
                 || !srec.getTask().affinity.equals(affinity)) {
             return true;
diff --git a/services/core/java/com/android/server/wm/TaskPersister.java b/services/core/java/com/android/server/wm/TaskPersister.java
index 29c192c..5ec0119 100644
--- a/services/core/java/com/android/server/wm/TaskPersister.java
+++ b/services/core/java/com/android/server/wm/TaskPersister.java
@@ -509,7 +509,7 @@
 
     private static boolean createParentDirectory(String filePath) {
         File parentDir = new File(filePath).getParentFile();
-        return parentDir.exists() || parentDir.mkdirs();
+        return parentDir.isDirectory() || parentDir.mkdir();
     }
 
     private static class TaskWriteQueueItem implements PersisterQueue.WriteQueueItem {
@@ -549,8 +549,8 @@
             // Write out one task.
             byte[] data = null;
             Task task = mTask;
-            if (DEBUG) Slog.d(TAG, "Writing task=" + task);
             synchronized (mService.mGlobalLock) {
+                if (DEBUG) Slog.d(TAG, "Writing task=" + task);
                 if (task.inRecents) {
                     // Still there.
                     try {
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 65c5c9b..a143540 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -78,6 +78,7 @@
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.IRemoteCallback;
+import android.os.Looper;
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.Trace;
@@ -359,22 +360,30 @@
         mTransientLaunches.put(activity, restoreBelow);
         setTransientLaunchToChanges(activity);
 
-        if (restoreBelow != null) {
-            final Task transientRootTask = activity.getRootTask();
+        final Task transientRootTask = activity.getRootTask();
+        final WindowContainer<?> parent = restoreBelow != null ? restoreBelow.getParent()
+                : (transientRootTask != null ? transientRootTask.getParent() : null);
+        if (parent != null) {
             // Collect all visible tasks which can be occluded by the transient activity to
             // make sure they are in the participants so their visibilities can be updated when
             // finishing transition.
-            ((WindowContainer<?>) restoreBelow.getParent()).forAllTasks(t -> {
+            parent.forAllTasks(t -> {
+                // Skip transient-launch task
+                if (t == transientRootTask) return false;
                 if (t.isVisibleRequested() && !t.isAlwaysOnTop()
                         && !t.getWindowConfiguration().tasksAreFloating()) {
-                    if (t.isRootTask() && t != transientRootTask) {
+                    if (t.isRootTask()) {
                         mTransientHideTasks.add(t);
                     }
                     if (t.isLeafTask()) {
                         collect(t);
                     }
                 }
-                return t == restoreBelow;
+                return restoreBelow != null
+                        // Stop at the restoreBelow task
+                        ? t == restoreBelow
+                        // Or stop at the last visible task if no restore-below (new task)
+                        : (t.isRootTask() && t.fillsParent());
             });
             // Add FLAG_ABOVE_TRANSIENT_LAUNCH to the tree of transient-hide tasks,
             // so ChangeInfo#hasChanged() can return true to report the transition info.
@@ -606,14 +615,16 @@
             }
         }
         if (mParticipants.contains(wc)) return;
-        // Wallpaper is like in a static drawn state unless display may have changes, so exclude
-        // the case to reduce transition latency waiting for the unchanged wallpaper to redraw.
-        final boolean needSync = (!isWallpaper(wc) || mParticipants.contains(wc.mDisplayContent))
-                // Transient-hide may be hidden later, so no need to request redraw.
-                && !isInTransientHide(wc);
-        if (needSync) {
+        // Transient-hide may be hidden later, so no need to request redraw.
+        if (!isInTransientHide(wc)) {
             mSyncEngine.addToSyncSet(mSyncId, wc);
         }
+        if (wc.asWindowToken() != null && wc.asWindowToken().mRoundedCornerOverlay) {
+            // Only need to sync the transaction (SyncSet) without ChangeInfo because cutout and
+            // rounded corner overlay never need animations. Especially their surfaces may be put
+            // in root (null, see WindowToken#makeSurface()) that cannot reparent.
+            return;
+        }
         ChangeInfo info = mChanges.get(wc);
         if (info == null) {
             info = new ChangeInfo(wc);
@@ -1180,16 +1191,6 @@
                 hasParticipatedDisplay = true;
                 continue;
             }
-            final WallpaperWindowToken wt = participant.asWallpaperToken();
-            if (wt != null) {
-                final boolean visibleAtTransitionEnd = mVisibleAtTransitionEndTokens.contains(wt);
-                if (!visibleAtTransitionEnd && !wt.isVisibleRequested()) {
-                    ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
-                            "  Commit wallpaper becoming invisible: %s", wt);
-                    wt.commitVisibility(false /* visible */);
-                }
-                continue;
-            }
             final Task tr = participant.asTask();
             if (tr != null && tr.isVisibleRequested() && tr.inPinnedWindowingMode()) {
                 final ActivityRecord top = tr.getTopNonFinishingActivity();
@@ -1209,6 +1210,20 @@
                 }
             }
         }
+        // Commit wallpaper visibility after activity, because usually the wallpaper target token is
+        // an activity, and wallpaper's visibility is depends on activity's visibility.
+        for (int i = mParticipants.size() - 1; i >= 0; --i) {
+            final WallpaperWindowToken wt = mParticipants.valueAt(i).asWallpaperToken();
+            if (wt == null) continue;
+            final WindowState target = wt.mDisplayContent.mWallpaperController.getWallpaperTarget();
+            final boolean isTargetInvisible = target == null || !target.mToken.isVisible();
+            if (isTargetInvisible || (!wt.isVisibleRequested()
+                    && !mVisibleAtTransitionEndTokens.contains(wt))) {
+                ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+                        "  Commit wallpaper becoming invisible: %s", wt);
+                wt.commitVisibility(false /* visible */);
+            }
+        }
         if (committedSomeInvisible) {
             mController.onCommittedInvisibles();
         }
@@ -1606,7 +1621,7 @@
         // Since we created root-leash but no longer reference it from core, release it now
         info.releaseAnimSurfaces();
 
-        mController.mLoggerHandler.post(mLogger::logOnSend);
+        mLogger.logOnSendAsync(mController.mLoggerHandler);
         if (mLogger.mInfo != null) {
             mController.mTransitionTracer.logSentTransition(this, mTargets);
         }
@@ -2697,6 +2712,26 @@
         });
     }
 
+    /**
+     * Returns {@code true} if the transition and the corresponding transaction should be applied
+     * on display thread. Currently, this only checks for display rotation change because the order
+     * of dispatching the new display info will be after requesting the windows to sync drawing.
+     * That avoids potential flickering of screen overlays (e.g. cutout, rounded corner). Also,
+     * because the display thread has a higher priority, it is faster to perform the configuration
+     * changes and window hierarchy traversal.
+     */
+    boolean shouldApplyOnDisplayThread() {
+        for (int i = mParticipants.size() - 1; i >= 0; --i) {
+            final DisplayContent dc = mParticipants.valueAt(i).asDisplayContent();
+            if (dc == null) continue;
+            final ChangeInfo changeInfo = mChanges.get(dc);
+            if (changeInfo != null && changeInfo.mRotation != dc.getRotation()) {
+                return Looper.myLooper() != mController.mAtm.mWindowManager.mH.getLooper();
+            }
+        }
+        return false;
+    }
+
     /** Applies the new configuration for the changed displays. */
     void applyDisplayChangeIfNeeded() {
         for (int i = mParticipants.size() - 1; i >= 0; --i) {
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index b05c6b4..bfaf6fc 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -597,15 +597,6 @@
     /** Sets the sync method for the display change. */
     private void setDisplaySyncMethod(@NonNull TransitionRequestInfo.DisplayChange displayChange,
             @NonNull Transition displayTransition, @NonNull DisplayContent displayContent) {
-        final int startRotation = displayChange.getStartRotation();
-        final int endRotation = displayChange.getEndRotation();
-        if (startRotation != endRotation && (startRotation + endRotation) % 2 == 0) {
-            // 180 degrees rotation change may not change screen size. So the clients may draw
-            // some frames before and after the display projection transaction is applied by the
-            // remote player. That may cause some buffers to show in different rotation. So use
-            // sync method to pause clients drawing until the projection transaction is applied.
-            mSyncEngine.setSyncMethod(displayTransition.getSyncId(), BLASTSyncEngine.METHOD_BLAST);
-        }
         final Rect startBounds = displayChange.getStartAbsBounds();
         final Rect endBounds = displayChange.getEndAbsBounds();
         if (startBounds == null || endBounds == null) return;
@@ -1460,7 +1451,7 @@
      * Beside `mCreateWallTimeMs`, all times are elapsed times and will all be reported relative
      * to when the transition was created.
      */
-    static class Logger {
+    static class Logger implements Runnable {
         long mCreateWallTimeMs;
         long mCreateTimeNs;
         long mRequestTimeNs;
@@ -1484,6 +1475,20 @@
             return sb.toString();
         }
 
+        void logOnSendAsync(Handler handler) {
+            handler.post(this);
+        }
+
+        @Override
+        public void run() {
+            try {
+                logOnSend();
+            } catch (Exception e) {
+                // In case TransitionRequestInfo#toString() accesses window container with race.
+                Slog.w(TAG, "Failed to log transition", e);
+            }
+        }
+
         void logOnSend() {
             ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS_MIN, "%s", buildOnSendLog());
             ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS_MIN, "    startWCT=%s", mStartWCT);
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index eb7b8c2..00bedcd 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -247,7 +247,7 @@
                 resources.getBoolean(
                         com.android.internal.R.bool.config_offsetWallpaperToCenterOfLargestDisplay);
         mIsLockscreenLiveWallpaperEnabled =
-                SystemProperties.getBoolean("persist.wm.debug.lockscreen_live_wallpaper", false);
+                SystemProperties.getBoolean("persist.wm.debug.lockscreen_live_wallpaper", true);
     }
 
     void resetLargestDisplay(Display display) {
@@ -308,29 +308,12 @@
         }
     }
 
-    private boolean shouldWallpaperBeVisible(WindowState wallpaperTarget) {
-        if (DEBUG_WALLPAPER) {
-            Slog.v(TAG, "Wallpaper vis: target " + wallpaperTarget + " prev="
-                    + mPrevWallpaperTarget);
-        }
-        return wallpaperTarget != null || mPrevWallpaperTarget != null;
-    }
-
     boolean isWallpaperTargetAnimating() {
         return mWallpaperTarget != null && mWallpaperTarget.isAnimating(TRANSITION | PARENTS)
                 && (mWallpaperTarget.mActivityRecord == null
                         || !mWallpaperTarget.mActivityRecord.isWaitingForTransitionStart());
     }
 
-    void updateWallpaperVisibility() {
-        final boolean visible = shouldWallpaperBeVisible(mWallpaperTarget);
-
-        for (int curTokenNdx = mWallpaperTokens.size() - 1; curTokenNdx >= 0; curTokenNdx--) {
-            final WallpaperWindowToken token = mWallpaperTokens.get(curTokenNdx);
-            token.setVisibility(visible);
-        }
-    }
-
     /**
      * Make one wallpaper visible, according to {@attr showHome}.
      * This is called during the keyguard unlocking transition
@@ -801,11 +784,20 @@
         result.setWallpaperTarget(wallpaperTarget);
     }
 
+    public void updateWallpaperTokens(boolean keyguardLocked) {
+        if (DEBUG_WALLPAPER) {
+            Slog.v(TAG, "Wallpaper vis: target " + mWallpaperTarget + " prev="
+                    + mPrevWallpaperTarget);
+        }
+        updateWallpaperTokens(mWallpaperTarget != null || mPrevWallpaperTarget != null,
+                keyguardLocked);
+    }
+
     /**
      * Change the visibility of the top wallpaper to {@param visibility} and hide all the others.
      */
-    private void updateWallpaperTokens(boolean visibility, boolean locked) {
-        WindowState topWallpaper = mFindResults.getTopWallpaper(locked);
+    private void updateWallpaperTokens(boolean visibility, boolean keyguardLocked) {
+        WindowState topWallpaper = mFindResults.getTopWallpaper(keyguardLocked);
         WallpaperWindowToken topWallpaperToken =
                 topWallpaper == null ? null : topWallpaper.mToken.asWallpaperToken();
         for (int curTokenNdx = mWallpaperTokens.size() - 1; curTokenNdx >= 0; curTokenNdx--) {
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 0152666..457a555 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -81,7 +81,9 @@
 import android.graphics.Rect;
 import android.os.Debug;
 import android.os.IBinder;
+import android.os.RemoteException;
 import android.os.Trace;
+import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Pair;
 import android.util.Pools;
@@ -174,6 +176,9 @@
      */
     protected SparseArray<InsetsSourceProvider> mInsetsSourceProviders = null;
 
+    @Nullable
+    private ArrayMap<IBinder, DeathRecipient> mInsetsOwnerDeathRecipientMap;
+
     // List of children for this window container. List is in z-order as the children appear on
     // screen with the top-most window container at the tail of the list.
     protected final WindowList<E> mChildren = new WindowList<E>();
@@ -419,11 +424,12 @@
      * Adds an {@link InsetsFrameProvider} which describes what insets should be provided to
      * this {@link WindowContainer} and its children.
      *
-     * @param provider describes the insets types and the frames.
+     * @param provider describes the insets type and the frame.
+     * @param owner owns the insets source which only exists when the owner is alive.
      */
-    void addLocalInsetsFrameProvider(InsetsFrameProvider provider) {
-        if (provider == null) {
-            throw new IllegalArgumentException("Insets type not specified.");
+    void addLocalInsetsFrameProvider(InsetsFrameProvider provider, IBinder owner) {
+        if (provider == null || owner == null) {
+            throw new IllegalArgumentException("Insets provider or owner not specified.");
         }
         if (mDisplayContent == null) {
             // This is possible this container is detached when WM shell is responding to a previous
@@ -432,10 +438,26 @@
             Slog.w(TAG, "Can't add insets frame provider when detached. " + this);
             return;
         }
+
+        if (mInsetsOwnerDeathRecipientMap == null) {
+            mInsetsOwnerDeathRecipientMap = new ArrayMap<>();
+        }
+        DeathRecipient deathRecipient = mInsetsOwnerDeathRecipientMap.get(owner);
+        if (deathRecipient == null) {
+            deathRecipient = new DeathRecipient(owner);
+            try {
+                owner.linkToDeath(deathRecipient, 0);
+            } catch (RemoteException e) {
+                Slog.w(TAG, "Failed to add source for " + provider + " since the owner has died.");
+                return;
+            }
+            mInsetsOwnerDeathRecipientMap.put(owner, deathRecipient);
+        }
+        final int id = provider.getId();
+        deathRecipient.addSourceId(id);
         if (mLocalInsetsSources == null) {
             mLocalInsetsSources = new SparseArray<>();
         }
-        final int id = provider.getId();
         if (mLocalInsetsSources.get(id) != null) {
             if (DEBUG) {
                 Slog.d(TAG, "The local insets source for this " + provider
@@ -448,27 +470,77 @@
         mDisplayContent.getInsetsStateController().updateAboveInsetsState(true);
     }
 
-    void removeLocalInsetsFrameProvider(InsetsFrameProvider provider) {
-        if (provider == null) {
-            throw new IllegalArgumentException("Insets type not specified.");
-        }
-        if (mLocalInsetsSources == null) {
-            return;
+    private class DeathRecipient implements IBinder.DeathRecipient {
+
+        private final IBinder mOwner;
+        private final ArraySet<Integer> mSourceIds = new ArraySet<>();
+
+        DeathRecipient(IBinder owner) {
+            mOwner = owner;
         }
 
-        final int id = provider.getId();
-        if (mLocalInsetsSources.get(id) == null) {
-            if (DEBUG) {
-                Slog.d(TAG, "Given " + provider + " doesn't have a local insets source.");
+        void addSourceId(int id) {
+            mSourceIds.add(id);
+        }
+
+        void removeSourceId(int id) {
+            mSourceIds.remove(id);
+        }
+
+        boolean hasSource() {
+            return !mSourceIds.isEmpty();
+        }
+
+        @Override
+        public void binderDied() {
+            synchronized (mWmService.mGlobalLock) {
+                boolean changed = false;
+                for (int i = mSourceIds.size() - 1; i >= 0; i--) {
+                    changed |= removeLocalInsetsSource(mSourceIds.valueAt(i));
+                }
+                mSourceIds.clear();
+                mOwner.unlinkToDeath(this, 0);
+                mInsetsOwnerDeathRecipientMap.remove(mOwner);
+                if (changed && mDisplayContent != null) {
+                    mDisplayContent.getInsetsStateController().updateAboveInsetsState(true);
+                }
             }
-            return;
         }
-        mLocalInsetsSources.remove(id);
+    }
 
-        // Update insets if this window is attached.
-        if (mDisplayContent != null) {
+    void removeLocalInsetsFrameProvider(InsetsFrameProvider provider, IBinder owner) {
+        if (provider == null || owner == null) {
+            throw new IllegalArgumentException("Insets provider or owner not specified.");
+        }
+        final int id = provider.getId();
+        if (removeLocalInsetsSource(id) && mDisplayContent != null) {
             mDisplayContent.getInsetsStateController().updateAboveInsetsState(true);
         }
+        if (mInsetsOwnerDeathRecipientMap == null) {
+            return;
+        }
+        final DeathRecipient deathRecipient = mInsetsOwnerDeathRecipientMap.get(owner);
+        if (deathRecipient == null) {
+            return;
+        }
+        deathRecipient.removeSourceId(id);
+        if (!deathRecipient.hasSource()) {
+            owner.unlinkToDeath(deathRecipient, 0);
+            mInsetsOwnerDeathRecipientMap.remove(owner);
+        }
+    }
+
+    private boolean removeLocalInsetsSource(int id) {
+        if (mLocalInsetsSources == null) {
+            return false;
+        }
+        if (mLocalInsetsSources.removeReturnOld(id) == null) {
+            if (DEBUG) {
+                Slog.d(TAG, "Given id " + Integer.toHexString(id) + " doesn't exist.");
+            }
+            return false;
+        }
+        return true;
     }
 
     /**
@@ -4141,7 +4213,7 @@
                     getDisplayContent().getInsetsStateController().getSourceProviders();
             for (int i = providers.size(); i >= 0; i--) {
                 final InsetsSourceProvider insetProvider = providers.valueAt(i);
-                if (!insetProvider.getSource().insetsRoundedCornerFrame()) {
+                if (!insetProvider.getSource().hasFlags(InsetsSource.FLAG_INSETS_ROUNDED_CORNER)) {
                     return;
                 }
 
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index d9ff91f..db92191 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wm;
 
+import static android.Manifest.permission.ACCESS_SURFACE_FLINGER;
 import static android.Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS;
 import static android.Manifest.permission.INPUT_CONSUMER;
 import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;
@@ -24,7 +25,6 @@
 import static android.Manifest.permission.MODIFY_TOUCH_MODE_STATE;
 import static android.Manifest.permission.READ_FRAME_BUFFER;
 import static android.Manifest.permission.REGISTER_WINDOW_MANAGER_LISTENERS;
-import static android.Manifest.permission.RESTRICTED_VR_ACCESS;
 import static android.Manifest.permission.START_TASKS_FROM_RECENTS;
 import static android.Manifest.permission.STATUS_BAR_SERVICE;
 import static android.Manifest.permission.WRITE_SECURE_SETTINGS;
@@ -415,7 +415,7 @@
     private static final String DENSITY_OVERRIDE = "ro.config.density_override";
     private static final String SIZE_OVERRIDE = "ro.config.size_override";
 
-    private static final String PROPERTY_EMULATOR_CIRCULAR = "ro.emulator.circular";
+    private static final String PROPERTY_EMULATOR_CIRCULAR = "ro.boot.emulator.circular";
 
     static final int MY_PID = myPid();
     static final int MY_UID = myUid();
@@ -552,7 +552,9 @@
     final PackageManagerInternal mPmInternal;
     private final TestUtilityService mTestUtilityService;
 
+    @NonNull
     final DisplayWindowSettingsProvider mDisplayWindowSettingsProvider;
+    @NonNull
     final DisplayWindowSettings mDisplayWindowSettings;
 
     /** If the system should display notifications for apps displaying an alert window. */
@@ -763,8 +765,6 @@
                 Settings.Secure.getUriFor(Settings.Secure.IMMERSIVE_MODE_CONFIRMATIONS);
         private final Uri mPolicyControlUri =
                 Settings.Global.getUriFor(Settings.Global.POLICY_CONTROL);
-        private final Uri mPointerLocationUri =
-                Settings.System.getUriFor(Settings.System.POINTER_LOCATION);
         private final Uri mForceDesktopModeOnExternalDisplaysUri = Settings.Global.getUriFor(
                         Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS);
         private final Uri mFreeformWindowUri = Settings.Global.getUriFor(
@@ -792,7 +792,6 @@
             resolver.registerContentObserver(mImmersiveModeConfirmationsUri, false, this,
                     UserHandle.USER_ALL);
             resolver.registerContentObserver(mPolicyControlUri, false, this, UserHandle.USER_ALL);
-            resolver.registerContentObserver(mPointerLocationUri, false, this, UserHandle.USER_ALL);
             resolver.registerContentObserver(mForceDesktopModeOnExternalDisplaysUri, false, this,
                     UserHandle.USER_ALL);
             resolver.registerContentObserver(mFreeformWindowUri, false, this, UserHandle.USER_ALL);
@@ -816,11 +815,6 @@
                 return;
             }
 
-            if (mPointerLocationUri.equals(uri)) {
-                updatePointerLocation();
-                return;
-            }
-
             if (mForceDesktopModeOnExternalDisplaysUri.equals(uri)) {
                 updateForceDesktopModeOnExternalDisplays();
                 return;
@@ -869,7 +863,6 @@
 
         void loadSettings() {
             updateSystemUiSettings(false /* handleChange */);
-            updatePointerLocation();
             updateMaximumObscuringOpacityForTouch();
         }
 
@@ -900,21 +893,6 @@
             }
         }
 
-        void updatePointerLocation() {
-            ContentResolver resolver = mContext.getContentResolver();
-            final boolean enablePointerLocation = Settings.System.getIntForUser(resolver,
-                    Settings.System.POINTER_LOCATION, 0, UserHandle.USER_CURRENT) != 0;
-
-            if (mPointerLocationEnabled == enablePointerLocation) {
-                return;
-            }
-            mPointerLocationEnabled = enablePointerLocation;
-            synchronized (mGlobalLock) {
-                mRoot.forAllDisplayPolicies(
-                        p -> p.setPointerLocationEnabled(mPointerLocationEnabled));
-            }
-        }
-
         void updateForceDesktopModeOnExternalDisplays() {
             ContentResolver resolver = mContext.getContentResolver();
             final boolean enableForceDesktopMode = Settings.Global.getInt(resolver,
@@ -2020,7 +1998,7 @@
         }
 
         if (win.mActivityRecord != null) {
-            win.mActivityRecord.postWindowRemoveStartingWindowCleanup(win);
+            win.mActivityRecord.postWindowRemoveStartingWindowCleanup();
         }
 
         if (win.mAttrs.type == TYPE_WALLPAPER) {
@@ -3249,15 +3227,13 @@
         }
     }
 
+    @android.annotation.EnforcePermission(android.Manifest.permission.DISABLE_KEYGUARD)
     /**
      * @see android.app.KeyguardManager#exitKeyguardSecurely
      */
     @Override
     public void exitKeyguardSecurely(final IOnKeyguardExitResult callback) {
-        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DISABLE_KEYGUARD)
-            != PackageManager.PERMISSION_GRANTED) {
-            throw new SecurityException("Requires DISABLE_KEYGUARD permission");
-        }
+        exitKeyguardSecurely_enforcePermission();
 
         if (callback == null) {
             throw new IllegalArgumentException("callback == null");
@@ -4457,13 +4433,11 @@
         }
     }
 
+    @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_APP_TOKENS)
     @Override
     public SurfaceControl addShellRoot(int displayId, IWindow client,
             @WindowManager.ShellRootLayer int shellRootLayer) {
-        if (mContext.checkCallingOrSelfPermission(MANAGE_APP_TOKENS)
-                != PackageManager.PERMISSION_GRANTED) {
-            throw new SecurityException("Must hold permission " + MANAGE_APP_TOKENS);
-        }
+        addShellRoot_enforcePermission();
         final long origId = Binder.clearCallingIdentity();
         try {
             synchronized (mGlobalLock) {
@@ -4478,13 +4452,11 @@
         }
     }
 
+    @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_APP_TOKENS)
     @Override
     public void setShellRootAccessibilityWindow(int displayId,
             @WindowManager.ShellRootLayer int shellRootLayer, IWindow target) {
-        if (mContext.checkCallingOrSelfPermission(MANAGE_APP_TOKENS)
-                != PackageManager.PERMISSION_GRANTED) {
-            throw new SecurityException("Must hold permission " + MANAGE_APP_TOKENS);
-        }
+        setShellRootAccessibilityWindow_enforcePermission();
         final long origId = Binder.clearCallingIdentity();
         try {
             synchronized (mGlobalLock) {
@@ -4503,13 +4475,11 @@
         }
     }
 
+    @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_APP_TOKENS)
     @Override
     public void setDisplayWindowInsetsController(
             int displayId, IDisplayWindowInsetsController insetsController) {
-        if (mContext.checkCallingOrSelfPermission(MANAGE_APP_TOKENS)
-                != PackageManager.PERMISSION_GRANTED) {
-            throw new SecurityException("Must hold permission " + MANAGE_APP_TOKENS);
-        }
+        setDisplayWindowInsetsController_enforcePermission();
         final long origId = Binder.clearCallingIdentity();
         try {
             synchronized (mGlobalLock) {
@@ -4524,13 +4494,11 @@
         }
     }
 
+    @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_APP_TOKENS)
     @Override
     public void updateDisplayWindowRequestedVisibleTypes(
             int displayId, @InsetsType int requestedVisibleTypes) {
-        if (mContext.checkCallingOrSelfPermission(MANAGE_APP_TOKENS)
-                != PackageManager.PERMISSION_GRANTED) {
-            throw new SecurityException("Must hold permission " + MANAGE_APP_TOKENS);
-        }
+        updateDisplayWindowRequestedVisibleTypes_enforcePermission();
         final long origId = Binder.clearCallingIdentity();
         try {
             synchronized (mGlobalLock) {
@@ -4539,7 +4507,8 @@
                     return;
                 }
                 dc.mRemoteInsetsControlTarget.setRequestedVisibleTypes(requestedVisibleTypes);
-                dc.getInsetsStateController().onInsetsModified(dc.mRemoteInsetsControlTarget);
+                dc.getInsetsStateController().onRequestedVisibleTypesChanged(
+                        dc.mRemoteInsetsControlTarget);
             }
         } finally {
             Binder.restoreCallingIdentity(origId);
@@ -5726,12 +5695,10 @@
         }
     }
 
+    @android.annotation.EnforcePermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
     @Override
     public void setForcedDisplaySize(int displayId, int width, int height) {
-        if (mContext.checkCallingOrSelfPermission(WRITE_SECURE_SETTINGS)
-                != PackageManager.PERMISSION_GRANTED) {
-            throw new SecurityException("Must hold permission " + WRITE_SECURE_SETTINGS);
-        }
+        setForcedDisplaySize_enforcePermission();
 
         final long ident = Binder.clearCallingIdentity();
         try {
@@ -5746,12 +5713,10 @@
         }
     }
 
+    @android.annotation.EnforcePermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
     @Override
     public void setForcedDisplayScalingMode(int displayId, int mode) {
-        if (mContext.checkCallingOrSelfPermission(WRITE_SECURE_SETTINGS)
-                != PackageManager.PERMISSION_GRANTED) {
-            throw new SecurityException("Must hold permission " + WRITE_SECURE_SETTINGS);
-        }
+        setForcedDisplayScalingMode_enforcePermission();
 
         final long ident = Binder.clearCallingIdentity();
         try {
@@ -5836,12 +5801,10 @@
         return changed;
     }
 
+    @android.annotation.EnforcePermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
     @Override
     public void clearForcedDisplaySize(int displayId) {
-        if (mContext.checkCallingOrSelfPermission(WRITE_SECURE_SETTINGS)
-                != PackageManager.PERMISSION_GRANTED) {
-            throw new SecurityException("Must hold permission " + WRITE_SECURE_SETTINGS);
-        }
+        clearForcedDisplaySize_enforcePermission();
 
         final long ident = Binder.clearCallingIdentity();
         try {
@@ -5901,12 +5864,10 @@
         return -1;
     }
 
+    @android.annotation.EnforcePermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
     @Override
     public void setForcedDisplayDensityForUser(int displayId, int density, int userId) {
-        if (mContext.checkCallingOrSelfPermission(WRITE_SECURE_SETTINGS)
-                != PackageManager.PERMISSION_GRANTED) {
-            throw new SecurityException("Must hold permission " + WRITE_SECURE_SETTINGS);
-        }
+        setForcedDisplayDensityForUser_enforcePermission();
 
         final int targetUserId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
                 Binder.getCallingUid(), userId, false, true, "setForcedDisplayDensityForUser",
@@ -5929,12 +5890,10 @@
         }
     }
 
+    @android.annotation.EnforcePermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
     @Override
     public void clearForcedDisplayDensityForUser(int displayId, int userId) {
-        if (mContext.checkCallingOrSelfPermission(WRITE_SECURE_SETTINGS)
-                != PackageManager.PERMISSION_GRANTED) {
-            throw new SecurityException("Must hold permission " + WRITE_SECURE_SETTINGS);
-        }
+        clearForcedDisplayDensityForUser_enforcePermission();
 
         final int callingUserId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
                 Binder.getCallingUid(), userId, false, true, "clearForcedDisplayDensityForUser",
@@ -6430,12 +6389,9 @@
         }
     }
 
+    @android.annotation.EnforcePermission(android.Manifest.permission.STATUS_BAR)
     public void setNavBarVirtualKeyHapticFeedbackEnabled(boolean enabled) {
-        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR)
-                != PackageManager.PERMISSION_GRANTED) {
-            throw new SecurityException("Caller does not hold permission "
-                    + android.Manifest.permission.STATUS_BAR);
-        }
+        setNavBarVirtualKeyHapticFeedbackEnabled_enforcePermission();
 
         synchronized (mGlobalLock) {
             mPolicy.setNavBarVirtualKeyHapticFeedbackEnabledLw(enabled);
@@ -6475,11 +6431,10 @@
         }
     }
 
+    @android.annotation.EnforcePermission(android.Manifest.permission.RESTRICTED_VR_ACCESS)
     @Override
     public Region getCurrentImeTouchRegion() {
-        if (mContext.checkCallingOrSelfPermission(RESTRICTED_VR_ACCESS) != PERMISSION_GRANTED) {
-            throw new SecurityException("getCurrentImeTouchRegion is restricted to VR services");
-        }
+        getCurrentImeTouchRegion_enforcePermission();
         synchronized (mGlobalLock) {
             final Region r = new Region();
             // TODO(b/111080190): this method is only return the recent focused IME touch region,
@@ -7174,15 +7129,45 @@
 
     @Override
     public void requestAppKeyboardShortcuts(IResultReceiver receiver, int deviceId) {
-        mContext.enforceCallingOrSelfPermission(REGISTER_WINDOW_MANAGER_LISTENERS,
-                "requestAppKeyboardShortcuts");
+        enforceRegisterWindowManagerListenersPermission("requestAppKeyboardShortcuts");
 
+        WindowState focusedWindow = getFocusedWindow();
+        if (focusedWindow == null || focusedWindow.mClient == null) {
+            notifyReceiverWithEmptyBundle(receiver);
+            return;
+        }
         try {
-            WindowState focusedWindow = getFocusedWindow();
-            if (focusedWindow != null && focusedWindow.mClient != null) {
-                getFocusedWindow().mClient.requestAppKeyboardShortcuts(receiver, deviceId);
-            }
+            focusedWindow.mClient.requestAppKeyboardShortcuts(receiver, deviceId);
         } catch (RemoteException e) {
+            notifyReceiverWithEmptyBundle(receiver);
+        }
+    }
+
+    @Override
+    public void requestImeKeyboardShortcuts(IResultReceiver receiver, int deviceId) {
+        enforceRegisterWindowManagerListenersPermission("requestImeKeyboardShortcuts");
+
+        WindowState imeWindow = mRoot.getCurrentInputMethodWindow();
+        if (imeWindow == null || imeWindow.mClient == null) {
+            notifyReceiverWithEmptyBundle(receiver);
+            return;
+        }
+        try {
+            imeWindow.mClient.requestAppKeyboardShortcuts(receiver, deviceId);
+        } catch (RemoteException e) {
+            notifyReceiverWithEmptyBundle(receiver);
+        }
+    }
+
+    private void enforceRegisterWindowManagerListenersPermission(String message) {
+        mContext.enforceCallingOrSelfPermission(REGISTER_WINDOW_MANAGER_LISTENERS, message);
+    }
+
+    private static void notifyReceiverWithEmptyBundle(IResultReceiver receiver) {
+        try {
+            receiver.send(0, Bundle.EMPTY);
+        } catch (RemoteException e) {
+            ProtoLog.e(WM_ERROR, "unable to call receiver for empty keyboard shortcuts");
         }
     }
 
@@ -8260,6 +8245,13 @@
         @Override
         public void addTrustedTaskOverlay(int taskId,
                 SurfaceControlViewHost.SurfacePackage overlay) {
+            if (overlay == null) {
+                throw new IllegalArgumentException("Invalid overlay passed in for task=" + taskId);
+            } else if (overlay.getSurfaceControl() == null
+                    || !overlay.getSurfaceControl().isValid()) {
+                throw new IllegalArgumentException(
+                        "Invalid overlay surfacecontrol passed in for task=" + taskId);
+            }
             synchronized (mGlobalLock) {
                 final Task task = mRoot.getRootTask(taskId);
                 if (task == null) {
@@ -8272,6 +8264,13 @@
         @Override
         public void removeTrustedTaskOverlay(int taskId,
                 SurfaceControlViewHost.SurfacePackage overlay) {
+            if (overlay == null) {
+                throw new IllegalArgumentException("Invalid overlay passed in for task=" + taskId);
+            } else if (overlay.getSurfaceControl() == null
+                    || !overlay.getSurfaceControl().isValid()) {
+                throw new IllegalArgumentException(
+                        "Invalid overlay surfacecontrol passed in for task=" + taskId);
+            }
             synchronized (mGlobalLock) {
                 final Task task = mRoot.getRootTask(taskId);
                 if (task == null) {
@@ -8737,24 +8736,22 @@
         final int sanitizedType = sanitizeWindowType(session, displayId, windowToken, type);
         final InputApplicationHandle applicationHandle;
         final String name;
-        final InputChannel clientChannel;
+        Objects.requireNonNull(outInputChannel);
         synchronized (mGlobalLock) {
             EmbeddedWindowController.EmbeddedWindow win =
                     new EmbeddedWindowController.EmbeddedWindow(session, this, window,
                             mInputToWindowMap.get(hostInputToken), callingUid, callingPid,
                             sanitizedType, displayId, focusGrantToken, inputHandleName,
                             (flags & FLAG_NOT_FOCUSABLE) == 0);
-            clientChannel = win.openInputChannel();
-            mEmbeddedWindowController.add(clientChannel.getToken(), win);
+            win.openInputChannel(outInputChannel);
+            mEmbeddedWindowController.add(outInputChannel.getToken(), win);
             applicationHandle = win.getApplicationHandle();
             name = win.toString();
         }
 
-        updateInputChannel(clientChannel.getToken(), callingUid, callingPid, displayId, surface,
+        updateInputChannel(outInputChannel.getToken(), callingUid, callingPid, displayId, surface,
                 name, applicationHandle, flags, privateFlags, inputFeatures, sanitizedType,
                 null /* region */, window);
-
-        clientChannel.copyTo(outInputChannel);
     }
 
     boolean transferEmbeddedTouchFocusToHost(IWindow embeddedWindow) {
@@ -9555,4 +9552,23 @@
             return List.copyOf(notifiedApps);
         }
     }
+
+    @RequiresPermission(ACCESS_SURFACE_FLINGER)
+    @Override
+    public boolean replaceContentOnDisplay(int displayId, SurfaceControl sc) {
+        if (!checkCallingPermission(ACCESS_SURFACE_FLINGER,
+                "replaceDisplayContent()")) {
+            throw new SecurityException("Requires ACCESS_SURFACE_FLINGER permission");
+        }
+
+        DisplayContent dc;
+        synchronized (mGlobalLock) {
+            dc = mRoot.getDisplayContentOrCreate(displayId);
+            if (dc == null) {
+                return false;
+            }
+            dc.replaceContent(sc);
+            return true;
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
index a153708..f4781f9 100644
--- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
+++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
@@ -41,6 +41,7 @@
 import android.provider.Settings;
 import android.util.DisplayMetrics;
 import android.util.Pair;
+import android.util.TypedValue;
 import android.view.Display;
 import android.view.IWindow;
 import android.view.IWindowManager;
@@ -728,7 +729,7 @@
             return -1;
         }
         synchronized (mInternal.mGlobalLock) {
-            mLetterboxConfiguration.setLetterboxBackgroundType(backgroundType);
+            mLetterboxConfiguration.setLetterboxBackgroundTypeOverride(backgroundType);
         }
         return 0;
     }
@@ -770,10 +771,10 @@
 
     private int runSetLetterboxBackgroundWallpaperBlurRadius(PrintWriter pw)
             throws RemoteException {
-        final int radius;
+        final int radiusDp;
         try {
             String arg = getNextArgRequired();
-            radius = Integer.parseInt(arg);
+            radiusDp = Integer.parseInt(arg);
         } catch (NumberFormatException  e) {
             getErrPrintWriter().println("Error: blur radius format " + e);
             return -1;
@@ -783,7 +784,9 @@
             return -1;
         }
         synchronized (mInternal.mGlobalLock) {
-            mLetterboxConfiguration.setLetterboxBackgroundWallpaperBlurRadius(radius);
+            final int radiusPx = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+                    radiusDp, mInternal.mContext.getResources().getDisplayMetrics());
+            mLetterboxConfiguration.setLetterboxBackgroundWallpaperBlurRadiusPx(radiusPx);
         }
         return 0;
     }
@@ -1006,13 +1009,16 @@
                     runSetBooleanFlag(pw, mLetterboxConfiguration
                             ::setTranslucentLetterboxingOverrideEnabled);
                     break;
+                case "--isUserAppAspectRatioSettingsEnabled":
+                    runSetBooleanFlag(pw, mLetterboxConfiguration
+                            ::setUserAppAspectRatioSettingsOverrideEnabled);
+                    break;
                 case "--isCameraCompatRefreshEnabled":
-                    runSetBooleanFlag(pw, enabled -> mLetterboxConfiguration
-                            .setCameraCompatRefreshEnabled(enabled));
+                    runSetBooleanFlag(pw, mLetterboxConfiguration::setCameraCompatRefreshEnabled);
                     break;
                 case "--isCameraCompatRefreshCycleThroughStopEnabled":
-                    runSetBooleanFlag(pw, enabled -> mLetterboxConfiguration
-                            .setCameraCompatRefreshCycleThroughStopEnabled(enabled));
+                    runSetBooleanFlag(pw,
+                            mLetterboxConfiguration::setCameraCompatRefreshCycleThroughStopEnabled);
                     break;
                 default:
                     getErrPrintWriter().println(
@@ -1047,7 +1053,7 @@
                         mLetterboxConfiguration.resetLetterboxBackgroundColor();
                         break;
                     case "wallpaperBlurRadius":
-                        mLetterboxConfiguration.resetLetterboxBackgroundWallpaperBlurRadius();
+                        mLetterboxConfiguration.resetLetterboxBackgroundWallpaperBlurRadiusPx();
                         break;
                     case "wallpaperDarkScrimAlpha":
                         mLetterboxConfiguration.resetLetterboxBackgroundWallpaperDarkScrimAlpha();
@@ -1084,6 +1090,9 @@
                     case "isTranslucentLetterboxingEnabled":
                         mLetterboxConfiguration.resetTranslucentLetterboxingEnabled();
                         break;
+                    case "isUserAppAspectRatioSettingsEnabled":
+                        mLetterboxConfiguration.resetUserAppAspectRatioSettingsEnabled();
+                        break;
                     case "isCameraCompatRefreshEnabled":
                         mLetterboxConfiguration.resetCameraCompatRefreshEnabled();
                         break;
@@ -1182,7 +1191,7 @@
             mLetterboxConfiguration.resetLetterboxActivityCornersRadius();
             mLetterboxConfiguration.resetLetterboxBackgroundType();
             mLetterboxConfiguration.resetLetterboxBackgroundColor();
-            mLetterboxConfiguration.resetLetterboxBackgroundWallpaperBlurRadius();
+            mLetterboxConfiguration.resetLetterboxBackgroundWallpaperBlurRadiusPx();
             mLetterboxConfiguration.resetLetterboxBackgroundWallpaperDarkScrimAlpha();
             mLetterboxConfiguration.resetLetterboxHorizontalPositionMultiplier();
             mLetterboxConfiguration.resetIsHorizontalReachabilityEnabled();
@@ -1194,6 +1203,7 @@
             mLetterboxConfiguration.resetIsSplitScreenAspectRatioForUnresizableAppsEnabled();
             mLetterboxConfiguration.resetIsDisplayAspectRatioEnabledForFixedOrientationLetterbox();
             mLetterboxConfiguration.resetTranslucentLetterboxingEnabled();
+            mLetterboxConfiguration.resetUserAppAspectRatioSettingsEnabled();
             mLetterboxConfiguration.resetCameraCompatRefreshEnabled();
             mLetterboxConfiguration.resetCameraCompatRefreshCycleThroughStopEnabled();
         }
@@ -1249,22 +1259,19 @@
                     + mLetterboxConfiguration.isCameraCompatRefreshEnabled());
             pw.println("    Refresh using \"stopped -> resumed\" cycle: "
                     + mLetterboxConfiguration.isCameraCompatRefreshCycleThroughStopEnabled());
-
             pw.println("Background type: "
                     + LetterboxConfiguration.letterboxBackgroundTypeToString(
                             mLetterboxConfiguration.getLetterboxBackgroundType()));
             pw.println("    Background color: " + Integer.toHexString(
                     mLetterboxConfiguration.getLetterboxBackgroundColor().toArgb()));
             pw.println("    Wallpaper blur radius: "
-                    + mLetterboxConfiguration.getLetterboxBackgroundWallpaperBlurRadius());
+                    + mLetterboxConfiguration.getLetterboxBackgroundWallpaperBlurRadiusPx());
             pw.println("    Wallpaper dark scrim alpha: "
                     + mLetterboxConfiguration.getLetterboxBackgroundWallpaperDarkScrimAlpha());
-
-            if (mLetterboxConfiguration.isTranslucentLetterboxingEnabled()) {
-                pw.println("Letterboxing for translucent activities: enabled");
-            } else {
-                pw.println("Letterboxing for translucent activities: disabled");
-            }
+            pw.println("Is letterboxing for translucent activities enabled: "
+                    + mLetterboxConfiguration.isTranslucentLetterboxingEnabled());
+            pw.println("Is the user aspect ratio settings enabled: "
+                    + mLetterboxConfiguration.isUserAppAspectRatioSettingsEnabled());
         }
         return 0;
     }
@@ -1462,6 +1469,8 @@
         pw.println("        unresizable apps.");
         pw.println("      --isTranslucentLetterboxingEnabled [true|1|false|0]");
         pw.println("        Whether letterboxing for translucent activities is enabled.");
+        pw.println("      --isUserAppAspectRatioSettingsEnabled [true|1|false|0]");
+        pw.println("        Whether user aspect ratio settings are enabled.");
         pw.println("      --isCameraCompatRefreshEnabled [true|1|false|0]");
         pw.println("        Whether camera compatibility refresh is enabled.");
         pw.println("      --isCameraCompatRefreshCycleThroughStopEnabled [true|1|false|0]");
@@ -1473,7 +1482,7 @@
         pw.println("      |horizontalPositionMultiplier|verticalPositionMultiplier");
         pw.println("      |isHorizontalReachabilityEnabled|isVerticalReachabilityEnabled");
         pw.println("      |isEducationEnabled||defaultPositionMultiplierForHorizontalReachability");
-        pw.println("      |isTranslucentLetterboxingEnabled");
+        pw.println("      |isTranslucentLetterboxingEnabled|isUserAppAspectRatioSettingsEnabled");
         pw.println("      |defaultPositionMultiplierForVerticalReachability]");
         pw.println("    Resets overrides to default values for specified properties separated");
         pw.println("    by space, e.g. 'reset-letterbox-style aspectRatio cornerRadius'.");
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 027ab97..164c8b0 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -18,12 +18,13 @@
 
 import static android.Manifest.permission.START_TASKS_FROM_RECENTS;
 import static android.app.ActivityManager.isStartResultSuccessful;
-import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.WINDOW_CONFIG_BOUNDS;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.window.TaskFragmentOperation.OP_TYPE_CLEAR_ADJACENT_TASK_FRAGMENTS;
 import static android.window.TaskFragmentOperation.OP_TYPE_CREATE_TASK_FRAGMENT;
 import static android.window.TaskFragmentOperation.OP_TYPE_DELETE_TASK_FRAGMENT;
+import static android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_FRONT;
 import static android.window.TaskFragmentOperation.OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT;
 import static android.window.TaskFragmentOperation.OP_TYPE_REQUEST_FOCUS_ON_TASK_FRAGMENT;
 import static android.window.TaskFragmentOperation.OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS;
@@ -321,9 +322,18 @@
                     applyTransaction(wct, -1 /*syncId*/, null /*transition*/, caller);
                     return transition.getToken();
                 }
-                transition.start();
                 transition.mLogger.mStartWCT = wct;
-                applyTransaction(wct, -1 /*syncId*/, transition, caller);
+                if (transition.shouldApplyOnDisplayThread()) {
+                    mService.mH.post(() -> {
+                        synchronized (mService.mGlobalLock) {
+                            transition.start();
+                            applyTransaction(wct, -1 /* syncId */, transition, caller);
+                        }
+                    });
+                } else {
+                    transition.start();
+                    applyTransaction(wct, -1 /* syncId */, transition, caller);
+                }
                 // Since the transition is already provided, it means WMCore is determining the
                 // "readiness lifecycle" outside the provided transaction, so don't set ready here.
                 return transition.getToken();
@@ -545,9 +555,9 @@
                 // setWindowingMode call in force-hidden.
                 boolean forceHiddenForPip = false;
                 if (wc.asTask() != null && wc.inPinnedWindowingMode()
-                        && entry.getValue().getWindowingMode() == WINDOWING_MODE_UNDEFINED) {
-                    // We are in pip and going to undefined. Now search hierarchy ops to determine
-                    // whether we are removing pip or expanding pip.
+                        && entry.getValue().getWindowingMode() != WINDOWING_MODE_PINNED) {
+                    // We are going out of pip. Now search hierarchy ops to determine whether we
+                    // are removing pip or expanding pip.
                     for (int i = 0; i < hopSize; ++i) {
                         final WindowContainerTransaction.HierarchyOp hop = hops.get(i);
                         if (hop.getType() != HIERARCHY_OP_TYPE_REORDER) continue;
@@ -691,7 +701,7 @@
                 return effects;
             }
 
-            if (windowingMode == WindowConfiguration.WINDOWING_MODE_PINNED) {
+            if (windowingMode == WINDOWING_MODE_PINNED) {
                 // Do not directly put the container into PINNED mode as it may not support it or
                 // the app may not want to enter it. Instead, send a signal to request PIP
                 // mode to the app if they wish to support it below in #applyTaskChanges.
@@ -744,7 +754,7 @@
             tr.mDisplayContent.mPinnedTaskController.setEnterPipBounds(enterPipBounds);
         }
 
-        if (c.getWindowingMode() == WindowConfiguration.WINDOWING_MODE_PINNED
+        if (c.getWindowingMode() == WINDOWING_MODE_PINNED
                 && !tr.inPinnedWindowingMode()) {
             final ActivityRecord activity = tr.getTopNonFinishingActivity();
             if (activity != null) {
@@ -1088,7 +1098,8 @@
                             + container);
                     break;
                 }
-                container.addLocalInsetsFrameProvider(hop.getInsetsFrameProvider());
+                container.addLocalInsetsFrameProvider(
+                        hop.getInsetsFrameProvider(), hop.getInsetsFrameOwner());
                 break;
             }
             case HIERARCHY_OP_TYPE_REMOVE_INSETS_FRAME_PROVIDER: {
@@ -1098,7 +1109,8 @@
                                     + container);
                     break;
                 }
-                container.removeLocalInsetsFrameProvider(hop.getInsetsFrameProvider());
+                container.removeLocalInsetsFrameProvider(
+                        hop.getInsetsFrameProvider(), hop.getInsetsFrameOwner());
                 break;
             }
             case HIERARCHY_OP_TYPE_SET_ALWAYS_ON_TOP: {
@@ -1331,6 +1343,19 @@
                 taskFragment.setAnimationParams(animationParams);
                 break;
             }
+            case OP_TYPE_REORDER_TO_FRONT: {
+                final Task task = taskFragment.getTask();
+                if (task != null) {
+                    final TaskFragment topTaskFragment = task.getTaskFragment(
+                            tf -> tf.asTask() == null);
+                    if (topTaskFragment != null && topTaskFragment != taskFragment) {
+                        final int index = task.mChildren.indexOf(topTaskFragment);
+                        task.mChildren.remove(taskFragment);
+                        task.mChildren.add(index, taskFragment);
+                    }
+                }
+                break;
+            }
         }
         return effects;
     }
@@ -1590,6 +1615,7 @@
         final int count = tasksToReparent.size();
         for (int i = 0; i < count; ++i) {
             final Task task = tasksToReparent.get(i);
+            final int prevWindowingMode = task.getWindowingMode();
             if (syncId >= 0) {
                 addToSyncSet(syncId, task);
             }
@@ -1603,6 +1629,12 @@
                         hop.getToTop() ? POSITION_TOP : POSITION_BOTTOM,
                         false /*moveParents*/, "processChildrenTaskReparentHierarchyOp");
             }
+            // Trim the compatible Recent task (if any) after the Task is reparented and now has
+            // a different windowing mode, in order to prevent redundant Recent tasks after
+            // reparenting.
+            if (prevWindowingMode != task.getWindowingMode()) {
+                mService.mTaskSupervisor.mRecentTasks.removeCompatibleRecentTask(task);
+            }
         }
 
         if (transition != null) transition.collect(newParent);
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 5de5bab..9f0c78f 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1445,6 +1445,8 @@
             Slog.v(TAG_WM, "Win " + this + " config changed: " + getConfiguration());
         }
 
+        final boolean dragResizingChanged = !mDragResizingChangeReported && isDragResizeChanged();
+
         final boolean attachedFrameChanged = LOCAL_LAYOUT
                 && mLayoutAttached && getParentWindow().frameChanged();
 
@@ -1458,6 +1460,7 @@
         if (didFrameInsetsChange
                 || configChanged
                 || insetsChanged
+                || dragResizingChanged
                 || shouldSendRedrawForSync()
                 || attachedFrameChanged) {
             ProtoLog.v(WM_DEBUG_RESIZE,
@@ -1478,7 +1481,8 @@
 
             // Reset the drawn state if the window need to redraw for the change, so the transition
             // can wait until it has finished drawing to start.
-            if ((configChanged || getOrientationChanging()) && isVisibleRequested()) {
+            if ((configChanged || getOrientationChanging() || dragResizingChanged)
+                    && isVisibleRequested()) {
                 winAnimator.mDrawState = DRAW_PENDING;
                 if (mActivityRecord != null) {
                     mActivityRecord.clearAllDrawn();
@@ -2466,6 +2470,11 @@
                     if (allowExitAnimation && mWinAnimator.applyAnimationLocked(transit, false)) {
                         ProtoLog.v(WM_DEBUG_ANIM,
                                 "Set animatingExit: reason=remove/applyAnimation win=%s", this);
+                        if (startingWindow && mSurfaceAnimator.hasLeash()) {
+                            // Keep starting window on top during fade-out animation.
+                            getPendingTransaction().setLayer(mSurfaceAnimator.mLeash,
+                                    Integer.MAX_VALUE);
+                        }
                         mAnimatingExit = true;
 
                         // mAnimatingExit affects canAffectSystemUiFlags(). Run layout such that
@@ -5661,7 +5670,7 @@
         }
         if (mIsWallpaper) {
             // TODO(b/233286785): Add sync support to wallpaper.
-            return false;
+            return true;
         }
         // In the WindowContainer implementation we immediately mark ready
         // since a generic WindowContainer only needs to wait for its
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index d5217c8..101af4d 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -177,7 +177,7 @@
         "android.hardware.power@1.1",
         "android.hardware.power@1.2",
         "android.hardware.power@1.3",
-        "android.hardware.power-V4-cpp",
+        "android.hardware.power-V4-ndk",
         "android.hardware.power.stats@1.0",
         "android.hardware.power.stats-V1-ndk",
         "android.hardware.thermal@1.0",
diff --git a/services/core/jni/com_android_server_UsbDescriptorParser.cpp b/services/core/jni/com_android_server_UsbDescriptorParser.cpp
index 9917bcb..a4a44c9 100644
--- a/services/core/jni/com_android_server_UsbDescriptorParser.cpp
+++ b/services/core/jni/com_android_server_UsbDescriptorParser.cpp
@@ -63,19 +63,13 @@
         return NULL;
     }
 
-    // Get Raw UCS2 Bytes
-    jbyte* byteBuffer = NULL;
-    size_t numUSC2Bytes = 0;
-    int retVal =
-            usb_device_get_string_ucs2(device, stringId,
-                                       USB_CONTROL_TRANSFER_TIMEOUT_MS,
-                                       (void**)&byteBuffer, &numUSC2Bytes);
+    char* data = usb_device_get_string(device, stringId, USB_CONTROL_TRANSFER_TIMEOUT_MS);
 
     jstring j_str = NULL;
 
-    if (retVal == 0) {
-        j_str = env->NewString((jchar*)byteBuffer, numUSC2Bytes/2);
-        free(byteBuffer);
+    if (data != NULL) {
+        j_str = env->NewStringUTF(data);
+        free(data);
     }
 
     usb_device_close(device);
diff --git a/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
index 0488247..4343edd 100644
--- a/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
+++ b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
@@ -333,7 +333,10 @@
     return -1;
 }
 static int getAnonPageAdvice(const Vma& vma) {
-    if (vma.inode == 0 && !vma.is_shared) {
+    bool hasReadFlag = (vma.flags & PROT_READ) > 0;
+    bool hasWriteFlag = (vma.flags & PROT_WRITE) > 0;
+    bool hasExecuteFlag = (vma.flags & PROT_EXEC) > 0;
+    if ((hasReadFlag || hasWriteFlag) && !hasExecuteFlag && !vma.is_shared) {
         return MADV_PAGEOUT;
     }
     return -1;
@@ -376,6 +379,9 @@
                 ++coldVmaIndex;
                 break;
             case MADV_PAGEOUT:
+#ifdef DEBUG_COMPACTION
+                ALOGE("Adding to compact vma=%s", vma.name.c_str());
+#endif
                 if (pageoutVmaIndex < pageoutVmas.size()) {
                     pageoutVmas[pageoutVmaIndex] = vma;
                 } else {
diff --git a/services/core/jni/com_android_server_hint_HintManagerService.cpp b/services/core/jni/com_android_server_hint_HintManagerService.cpp
index e322fa2..e148b94 100644
--- a/services/core/jni/com_android_server_hint_HintManagerService.cpp
+++ b/services/core/jni/com_android_server_hint_HintManagerService.cpp
@@ -18,77 +18,78 @@
 
 //#define LOG_NDEBUG 0
 
+#include <aidl/android/hardware/power/IPower.h>
 #include <android-base/stringprintf.h>
-#include <android/hardware/power/IPower.h>
-#include <android_runtime/AndroidRuntime.h>
 #include <nativehelper/JNIHelp.h>
 #include <nativehelper/ScopedPrimitiveArray.h>
 #include <powermanager/PowerHalController.h>
 #include <utils/Log.h>
 
-#include <unistd.h>
-#include <cinttypes>
-
-#include <sys/types.h>
+#include <unordered_map>
 
 #include "jni.h"
 
-using android::hardware::power::IPowerHintSession;
-using android::hardware::power::SessionHint;
-using android::hardware::power::WorkDuration;
+using aidl::android::hardware::power::IPowerHintSession;
+using aidl::android::hardware::power::SessionHint;
+using aidl::android::hardware::power::WorkDuration;
 
 using android::base::StringPrintf;
 
 namespace android {
 
 static power::PowerHalController gPowerHalController;
+static std::unordered_map<jlong, std::shared_ptr<IPowerHintSession>> gSessionMap;
+static std::mutex gSessionMapLock;
 
 static jlong createHintSession(JNIEnv* env, int32_t tgid, int32_t uid,
                                std::vector<int32_t> threadIds, int64_t durationNanos) {
-    auto result =
-            gPowerHalController.createHintSession(tgid, uid, std::move(threadIds), durationNanos);
+    auto result = gPowerHalController.createHintSession(tgid, uid, threadIds, durationNanos);
     if (result.isOk()) {
-        sp<IPowerHintSession> appSession = result.value();
-        if (appSession) appSession->incStrong(env);
-        return reinterpret_cast<jlong>(appSession.get());
+        auto session_ptr = reinterpret_cast<jlong>(result.value().get());
+        {
+            std::unique_lock<std::mutex> sessionLock(gSessionMapLock);
+            auto res = gSessionMap.insert({session_ptr, result.value()});
+            return res.second ? session_ptr : 0;
+        }
     }
     return 0;
 }
 
 static void pauseHintSession(JNIEnv* env, int64_t session_ptr) {
-    sp<IPowerHintSession> appSession = reinterpret_cast<IPowerHintSession*>(session_ptr);
+    auto appSession = reinterpret_cast<IPowerHintSession*>(session_ptr);
     appSession->pause();
 }
 
 static void resumeHintSession(JNIEnv* env, int64_t session_ptr) {
-    sp<IPowerHintSession> appSession = reinterpret_cast<IPowerHintSession*>(session_ptr);
+    auto appSession = reinterpret_cast<IPowerHintSession*>(session_ptr);
     appSession->resume();
 }
 
 static void closeHintSession(JNIEnv* env, int64_t session_ptr) {
-    sp<IPowerHintSession> appSession = reinterpret_cast<IPowerHintSession*>(session_ptr);
+    auto appSession = reinterpret_cast<IPowerHintSession*>(session_ptr);
     appSession->close();
-    appSession->decStrong(env);
+    std::unique_lock<std::mutex> sessionLock(gSessionMapLock);
+    gSessionMap.erase(session_ptr);
 }
 
 static void updateTargetWorkDuration(int64_t session_ptr, int64_t targetDurationNanos) {
-    sp<IPowerHintSession> appSession = reinterpret_cast<IPowerHintSession*>(session_ptr);
+    auto appSession = reinterpret_cast<IPowerHintSession*>(session_ptr);
     appSession->updateTargetWorkDuration(targetDurationNanos);
 }
 
 static void reportActualWorkDuration(int64_t session_ptr,
                                      const std::vector<WorkDuration>& actualDurations) {
-    sp<IPowerHintSession> appSession = reinterpret_cast<IPowerHintSession*>(session_ptr);
+    auto appSession = reinterpret_cast<IPowerHintSession*>(session_ptr);
     appSession->reportActualWorkDuration(actualDurations);
 }
 
 static void sendHint(int64_t session_ptr, SessionHint hint) {
-    sp<IPowerHintSession> appSession = reinterpret_cast<IPowerHintSession*>(session_ptr);
+    auto appSession = reinterpret_cast<IPowerHintSession*>(session_ptr);
     appSession->sendHint(hint);
 }
 
 static void setThreads(int64_t session_ptr, const std::vector<int32_t>& threadIds) {
-    sp<IPowerHintSession> appSession = reinterpret_cast<IPowerHintSession*>(session_ptr);
+    auto appSession = reinterpret_cast<IPowerHintSession*>(session_ptr);
     appSession->setThreads(threadIds);
 }
 
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index f0d718a..cb0b9c9 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -122,8 +122,6 @@
     jmethodID getInputUniqueIdAssociations;
     jmethodID getDeviceTypeAssociations;
     jmethodID getKeyboardLayoutAssociations;
-    jmethodID getKeyRepeatTimeout;
-    jmethodID getKeyRepeatDelay;
     jmethodID getHoverTapTimeout;
     jmethodID getHoverTapSlop;
     jmethodID getDoubleTapTimeout;
@@ -279,7 +277,7 @@
     base::Result<std::unique_ptr<InputChannel>> createInputChannel(const std::string& name);
     base::Result<std::unique_ptr<InputChannel>> createInputMonitor(int32_t displayId,
                                                                    const std::string& name,
-                                                                   int32_t pid);
+                                                                   gui::Pid pid);
     status_t removeInputChannel(const sp<IBinder>& connectionToken);
     status_t pilferPointers(const sp<IBinder>& token);
 
@@ -330,9 +328,9 @@
     void notifyConfigurationChanged(nsecs_t when) override;
     // ANR-related callbacks -- start
     void notifyNoFocusedWindowAnr(const std::shared_ptr<InputApplicationHandle>& handle) override;
-    void notifyWindowUnresponsive(const sp<IBinder>& token, std::optional<int32_t> pid,
+    void notifyWindowUnresponsive(const sp<IBinder>& token, std::optional<gui::Pid> pid,
                                   const std::string& reason) override;
-    void notifyWindowResponsive(const sp<IBinder>& token, std::optional<int32_t> pid) override;
+    void notifyWindowResponsive(const sp<IBinder>& token, std::optional<gui::Pid> pid) override;
     // ANR-related callbacks -- end
     void notifyInputChannelBroken(const sp<IBinder>& token) override;
     void notifyFocusChanged(const sp<IBinder>& oldToken, const sp<IBinder>& newToken) override;
@@ -343,7 +341,6 @@
                               InputDeviceSensorAccuracy accuracy) override;
     void notifyVibratorState(int32_t deviceId, bool isOn) override;
     bool filterInputEvent(const InputEvent& inputEvent, uint32_t policyFlags) override;
-    InputDispatcherConfiguration getDispatcherConfiguration() override;
     void interceptKeyBeforeQueueing(const KeyEvent& keyEvent, uint32_t& policyFlags) override;
     void interceptMotionBeforeQueueing(int32_t displayId, nsecs_t when,
                                        uint32_t& policyFlags) override;
@@ -355,6 +352,8 @@
     void onPointerDownOutsideFocus(const sp<IBinder>& touchedToken) override;
     void setPointerCapture(const PointerCaptureRequest& request) override;
     void notifyDropWindow(const sp<IBinder>& token, float x, float y) override;
+    void notifyDeviceInteraction(int32_t deviceId, nsecs_t timestamp,
+                                 const std::set<gui::Uid>& uids) override;
 
     /* --- PointerControllerPolicyInterface implementation --- */
 
@@ -539,7 +538,7 @@
 }
 
 base::Result<std::unique_ptr<InputChannel>> NativeInputManager::createInputMonitor(
-        int32_t displayId, const std::string& name, int32_t pid) {
+        int32_t displayId, const std::string& name, gui::Pid pid) {
     ATRACE_CALL();
     return mInputManager->getDispatcher().createInputMonitor(displayId, name, pid);
 }
@@ -883,7 +882,7 @@
 }
 
 void NativeInputManager::notifyWindowUnresponsive(const sp<IBinder>& token,
-                                                  std::optional<int32_t> pid,
+                                                  std::optional<gui::Pid> pid,
                                                   const std::string& reason) {
 #if DEBUG_INPUT_DISPATCHER_POLICY
     ALOGD("notifyWindowUnresponsive");
@@ -897,12 +896,12 @@
     ScopedLocalRef<jstring> reasonObj(env, env->NewStringUTF(reason.c_str()));
 
     env->CallVoidMethod(mServiceObj, gServiceClassInfo.notifyWindowUnresponsive, tokenObj,
-                        pid.value_or(0), pid.has_value(), reasonObj.get());
+                        pid.value_or(gui::Pid{0}).val(), pid.has_value(), reasonObj.get());
     checkAndClearExceptionFromCallback(env, "notifyWindowUnresponsive");
 }
 
 void NativeInputManager::notifyWindowResponsive(const sp<IBinder>& token,
-                                                std::optional<int32_t> pid) {
+                                                std::optional<gui::Pid> pid) {
 #if DEBUG_INPUT_DISPATCHER_POLICY
     ALOGD("notifyWindowResponsive");
 #endif
@@ -914,7 +913,7 @@
     jobject tokenObj = javaObjectForIBinder(env, token);
 
     env->CallVoidMethod(mServiceObj, gServiceClassInfo.notifyWindowResponsive, tokenObj,
-                        pid.value_or(0), pid.has_value());
+                        pid.value_or(gui::Pid{0}).val(), pid.has_value());
     checkAndClearExceptionFromCallback(env, "notifyWindowResponsive");
 }
 
@@ -966,6 +965,15 @@
     checkAndClearExceptionFromCallback(env, "notifyDropWindow");
 }
 
+void NativeInputManager::notifyDeviceInteraction(int32_t deviceId, nsecs_t timestamp,
+                                                 const std::set<gui::Uid>& uids) {
+    static const bool ENABLE_INPUT_DEVICE_USAGE_METRICS =
+            sysprop::InputProperties::enable_input_device_usage_metrics().value_or(true);
+    if (!ENABLE_INPUT_DEVICE_USAGE_METRICS) return;
+
+    mInputManager->getMetricsCollector().notifyDeviceInteraction(deviceId, timestamp, uids);
+}
+
 void NativeInputManager::notifySensorEvent(int32_t deviceId, InputDeviceSensorType sensorType,
                                            InputDeviceSensorAccuracy accuracy, nsecs_t timestamp,
                                            const std::vector<float>& values) {
@@ -1007,24 +1015,6 @@
     checkAndClearExceptionFromCallback(env, "notifyVibratorState");
 }
 
-InputDispatcherConfiguration NativeInputManager::getDispatcherConfiguration() {
-    ATRACE_CALL();
-    InputDispatcherConfiguration config;
-    JNIEnv* env = jniEnv();
-
-    jint keyRepeatTimeout = env->CallIntMethod(mServiceObj, gServiceClassInfo.getKeyRepeatTimeout);
-    if (!checkAndClearExceptionFromCallback(env, "getKeyRepeatTimeout")) {
-        config.keyRepeatTimeout = milliseconds_to_nanoseconds(keyRepeatTimeout);
-    }
-
-    jint keyRepeatDelay = env->CallIntMethod(mServiceObj, gServiceClassInfo.getKeyRepeatDelay);
-    if (!checkAndClearExceptionFromCallback(env, "getKeyRepeatDelay")) {
-        config.keyRepeatDelay = milliseconds_to_nanoseconds(keyRepeatDelay);
-    }
-
-    return config;
-}
-
 void NativeInputManager::displayRemoved(JNIEnv* env, int32_t displayId) {
     mInputManager->getDispatcher().displayRemoved(displayId);
 }
@@ -1180,6 +1170,8 @@
 }
 
 void NativeInputManager::setInputDeviceEnabled(uint32_t deviceId, bool enabled) {
+    bool refresh = false;
+
     { // acquire lock
         std::scoped_lock _l(mLock);
 
@@ -1187,14 +1179,18 @@
         bool currentlyEnabled = it == mLocked.disabledInputDevices.end();
         if (!enabled && currentlyEnabled) {
             mLocked.disabledInputDevices.insert(deviceId);
+            refresh = true;
         }
         if (enabled && !currentlyEnabled) {
             mLocked.disabledInputDevices.erase(deviceId);
+            refresh = true;
         }
     } // release lock
 
-    mInputManager->getReader().requestRefreshConfiguration(
-            InputReaderConfiguration::Change::ENABLED_STATE);
+    if (refresh) {
+        mInputManager->getReader().requestRefreshConfiguration(
+                InputReaderConfiguration::Change::ENABLED_STATE);
+    }
 }
 
 void NativeInputManager::setShowTouches(bool enabled) {
@@ -1596,8 +1592,8 @@
 }
 
 PointerIconStyle NativeInputManager::getDefaultStylusIconId() {
-    // TODO: add resource for default stylus icon and change this
-    return PointerIconStyle::TYPE_CROSSHAIR;
+    // Use the empty icon as the default pointer icon for a hovering stylus.
+    return PointerIconStyle::TYPE_NULL;
 }
 
 PointerIconStyle NativeInputManager::getCustomPointerIconId() {
@@ -1808,7 +1804,7 @@
     std::string name = nameChars.c_str();
 
     base::Result<std::unique_ptr<InputChannel>> inputChannel =
-            im->createInputMonitor(displayId, name, pid);
+            im->createInputMonitor(displayId, name, gui::Pid{pid});
 
     if (!inputChannel.ok()) {
         std::string message = inputChannel.error().message();
@@ -1853,7 +1849,8 @@
                                      jint pid, jint uid, jboolean hasPermission, jint displayId) {
     NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
 
-    return im->getInputManager()->getDispatcher().setInTouchMode(inTouchMode, pid, uid,
+    return im->getInputManager()->getDispatcher().setInTouchMode(inTouchMode, gui::Pid{pid},
+                                                                 gui::Uid{static_cast<uid_t>(uid)},
                                                                  hasPermission, displayId);
 }
 
@@ -1869,7 +1866,7 @@
                                    jint timeoutMillis, jint policyFlags) {
     NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
 
-    const std::optional<int32_t> targetUid = injectIntoUid ? std::make_optional(uid) : std::nullopt;
+    const auto targetUid = injectIntoUid ? std::make_optional<gui::Uid>(uid) : std::nullopt;
     // static_cast is safe because the value was already checked at the Java layer
     InputEventInjectionSync mode = static_cast<InputEventInjectionSync>(syncMode);
 
@@ -2214,13 +2211,25 @@
             jCapability |= env->GetStaticIntField(gLightClassInfo.clazz,
                                                   gLightClassInfo.lightCapabilityColorRgb);
         }
+
+        ScopedLocalRef<jintArray> jPreferredBrightnessLevels{env};
+        if (!lightInfo.preferredBrightnessLevels.empty()) {
+            std::vector<int32_t> vec;
+            for (auto it : lightInfo.preferredBrightnessLevels) {
+              vec.push_back(ftl::to_underlying(it));
+            }
+            jPreferredBrightnessLevels.reset(env->NewIntArray(vec.size()));
+            env->SetIntArrayRegion(jPreferredBrightnessLevels.get(), 0, vec.size(), vec.data());
+        }
+
         ScopedLocalRef<jobject> lightObj(env,
                                          env->NewObject(gLightClassInfo.clazz,
                                                         gLightClassInfo.constructor,
                                                         static_cast<jint>(lightInfo.id),
                                                         env->NewStringUTF(lightInfo.name.c_str()),
                                                         static_cast<jint>(lightInfo.ordinal),
-                                                        jTypeId, jCapability));
+                                                        jTypeId, jCapability,
+                                                        jPreferredBrightnessLevels.get()));
         // Add light object to list
         env->CallBooleanMethod(jLights, gArrayListClassInfo.add, lightObj.get());
     }
@@ -2423,6 +2432,16 @@
     im->setMotionClassifierEnabled(enabled);
 }
 
+static void nativeSetKeyRepeatConfiguration(JNIEnv* env, jobject nativeImplObj, jint timeoutMs,
+                                            jint delayMs) {
+    NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
+    im->getInputManager()->getDispatcher().setKeyRepeatConfiguration(static_cast<nsecs_t>(
+                                                                             timeoutMs) *
+                                                                             1000000,
+                                                                     static_cast<nsecs_t>(delayMs) *
+                                                                             1000000);
+}
+
 static jobject createInputSensorInfo(JNIEnv* env, jstring name, jstring vendor, jint version,
                                      jint handle, jint type, jfloat maxRange, jfloat resolution,
                                      jfloat power, jfloat minDelay, jint fifoReservedEventCount,
@@ -2553,11 +2572,6 @@
     im->setStylusPointerIconEnabled(enabled);
 }
 
-static void nativeNotifyKeyGestureTimeoutsChanged(JNIEnv* env, jobject nativeImplObj) {
-    NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
-    im->getInputManager()->getDispatcher().requestRefreshConfiguration();
-}
-
 // ----------------------------------------------------------------------------
 
 static const JNINativeMethod gInputManagerMethods[] = {
@@ -2642,6 +2656,7 @@
         {"setDisplayEligibilityForPointerCapture", "(IZ)V",
          (void*)nativeSetDisplayEligibilityForPointerCapture},
         {"setMotionClassifierEnabled", "(Z)V", (void*)nativeSetMotionClassifierEnabled},
+        {"setKeyRepeatConfiguration", "(II)V", (void*)nativeSetKeyRepeatConfiguration},
         {"getSensorList", "(I)[Landroid/hardware/input/InputSensorInfo;",
          (void*)nativeGetSensorList},
         {"enableSensor", "(IIII)Z", (void*)nativeEnableSensor},
@@ -2654,7 +2669,6 @@
          (void*)nativeSetStylusButtonMotionEventsEnabled},
         {"getMouseCursorPosition", "()[F", (void*)nativeGetMouseCursorPosition},
         {"setStylusPointerIconEnabled", "(Z)V", (void*)nativeSetStylusPointerIconEnabled},
-        {"notifyKeyGestureTimeoutsChanged", "()V", (void*)nativeNotifyKeyGestureTimeoutsChanged},
 };
 
 #define FIND_CLASS(var, className) \
@@ -2771,12 +2785,6 @@
     GET_METHOD_ID(gServiceClassInfo.getKeyboardLayoutAssociations, clazz,
                   "getKeyboardLayoutAssociations", "()[Ljava/lang/String;");
 
-    GET_METHOD_ID(gServiceClassInfo.getKeyRepeatTimeout, clazz,
-            "getKeyRepeatTimeout", "()I");
-
-    GET_METHOD_ID(gServiceClassInfo.getKeyRepeatDelay, clazz,
-            "getKeyRepeatDelay", "()I");
-
     GET_METHOD_ID(gServiceClassInfo.getHoverTapTimeout, clazz,
             "getHoverTapTimeout", "()I");
 
@@ -2846,7 +2854,7 @@
     FIND_CLASS(gLightClassInfo.clazz, "android/hardware/lights/Light");
     gLightClassInfo.clazz = jclass(env->NewGlobalRef(gLightClassInfo.clazz));
     GET_METHOD_ID(gLightClassInfo.constructor, gLightClassInfo.clazz, "<init>",
-                  "(ILjava/lang/String;III)V");
+                  "(ILjava/lang/String;III[I)V");
 
     gLightClassInfo.clazz = jclass(env->NewGlobalRef(gLightClassInfo.clazz));
     gLightClassInfo.lightTypeInput =
diff --git a/services/core/jni/com_android_server_power_PowerManagerService.cpp b/services/core/jni/com_android_server_power_PowerManagerService.cpp
index fe86ff1..6ab98fe 100644
--- a/services/core/jni/com_android_server_power_PowerManagerService.cpp
+++ b/services/core/jni/com_android_server_power_PowerManagerService.cpp
@@ -18,48 +18,40 @@
 
 //#define LOG_NDEBUG 0
 
+#include "com_android_server_power_PowerManagerService.h"
+
+#include <aidl/android/hardware/power/Boost.h>
+#include <aidl/android/hardware/power/Mode.h>
 #include <aidl/android/system/suspend/ISystemSuspend.h>
 #include <aidl/android/system/suspend/IWakeLock.h>
-#include <android/hardware/power/1.1/IPower.h>
-#include <android/hardware/power/Boost.h>
-#include <android/hardware/power/IPower.h>
-#include <android/hardware/power/Mode.h>
-#include <android/system/suspend/ISuspendControlService.h>
-#include <android/system/suspend/internal/ISuspendControlServiceInternal.h>
-#include <nativehelper/JNIHelp.h>
-#include "jni.h"
-
-#include <nativehelper/ScopedUtfChars.h>
-#include <powermanager/PowerHalController.h>
-
-#include <limits.h>
-
 #include <android-base/chrono_utils.h>
 #include <android/binder_manager.h>
+#include <android/system/suspend/ISuspendControlService.h>
+#include <android/system/suspend/internal/ISuspendControlServiceInternal.h>
 #include <android_runtime/AndroidRuntime.h>
 #include <android_runtime/Log.h>
 #include <binder/IServiceManager.h>
 #include <gui/SurfaceComposerClient.h>
-#include <hardware/power.h>
 #include <hardware_legacy/power.h>
 #include <hidl/ServiceManagement.h>
+#include <limits.h>
+#include <nativehelper/JNIHelp.h>
+#include <nativehelper/ScopedUtfChars.h>
+#include <powermanager/PowerHalController.h>
 #include <utils/Log.h>
 #include <utils/String8.h>
 #include <utils/Timers.h>
 #include <utils/misc.h>
 
-#include "com_android_server_power_PowerManagerService.h"
+#include "jni.h"
 
+using aidl::android::hardware::power::Boost;
+using aidl::android::hardware::power::Mode;
 using aidl::android::system::suspend::ISystemSuspend;
 using aidl::android::system::suspend::IWakeLock;
 using aidl::android::system::suspend::WakeLockType;
 using android::String8;
-using android::hardware::power::Boost;
-using android::hardware::power::Mode;
 using android::system::suspend::ISuspendControlService;
-using IPowerV1_1 = android::hardware::power::V1_1::IPower;
-using IPowerV1_0 = android::hardware::power::V1_0::IPower;
-using IPowerAidl = android::hardware::power::IPower;
 
 namespace android {
 
diff --git a/services/core/jni/com_android_server_power_PowerManagerService.h b/services/core/jni/com_android_server_power_PowerManagerService.h
index a2f335c..36aaceb 100644
--- a/services/core/jni/com_android_server_power_PowerManagerService.h
+++ b/services/core/jni/com_android_server_power_PowerManagerService.h
@@ -18,9 +18,10 @@
 #define _ANDROID_SERVER_POWER_MANAGER_SERVICE_H
 
 #include <nativehelper/JNIHelp.h>
-#include "jni.h"
-
 #include <powermanager/PowerManager.h>
+#include <utils/Timers.h>
+
+#include "jni.h"
 
 namespace android {
 
diff --git a/services/core/lint-baseline.xml b/services/core/lint-baseline.xml
index 69e13b3..070bd4b 100644
--- a/services/core/lint-baseline.xml
+++ b/services/core/lint-baseline.xml
@@ -137,4 +137,12 @@
             line="1448"/>
     </issue>
 
+    <issue
+        id="SimpleManualPermissionEnforcement"
+        message="IWindowManager permission check should be converted to @EnforcePermission annotation">
+        <location
+            file="out/.intermediates/frameworks/base/services/core/services.core.protologsrc/gen/services.core.protolog.srcjar!/frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java"
+            line="7158"/>
+    </issue>
+
 </issues>
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 f96ca58..7104a80 100644
--- a/services/core/xsd/display-device-config/display-device-config.xsd
+++ b/services/core/xsd/display-device-config/display-device-config.xsd
@@ -46,6 +46,8 @@
                     <xs:annotation name="nonnull"/>
                     <xs:annotation name="final"/>
                 </xs:element>
+                <xs:element type="luxThrottling" name="luxThrottling" minOccurs="0"
+                            maxOccurs="1"/>
                 <xs:element type="highBrightnessMode" name="highBrightnessMode" minOccurs="0"
                             maxOccurs="1"/>
                 <xs:element type="displayQuirks" name="quirks" minOccurs="0" maxOccurs="1"/>
@@ -137,6 +139,39 @@
         </xs:sequence>
     </xs:complexType>
 
+    <xs:complexType name="luxThrottling">
+        <xs:sequence>
+            <xs:element name="brightnessLimitMap" type="brightnessLimitMap"
+                        maxOccurs="unbounded">
+                <xs:annotation name="nonnull"/>
+                <xs:annotation name="final"/>
+            </xs:element>
+        </xs:sequence>
+    </xs:complexType>
+
+    <xs:complexType name="brightnessLimitMap">
+        <xs:sequence>
+            <xs:element name="type" type="PredefinedBrightnessLimitNames">
+                <xs:annotation name="nonnull"/>
+                <xs:annotation name="final"/>
+            </xs:element>
+            <!-- lux level from light sensor to screen brightness recommended max value map.
+            Screen brightness recommended max value is to highBrightnessMode.transitionPoint and must be below that -->
+            <xs:element name="map" type="nonNegativeFloatToFloatMap">
+                <xs:annotation name="nonnull"/>
+                <xs:annotation name="final"/>
+            </xs:element>
+        </xs:sequence>
+    </xs:complexType>
+
+    <!-- Predefined type names as defined by DisplayDeviceConfig.BrightnessLimitMapType -->
+    <xs:simpleType  name="PredefinedBrightnessLimitNames">
+        <xs:restriction base="xs:string">
+            <xs:enumeration value="default"/>
+            <xs:enumeration value="adaptive"/>
+        </xs:restriction>
+    </xs:simpleType>
+
     <xs:complexType name="highBrightnessMode">
         <xs:all>
             <xs:element name="transitionPoint" type="nonNegativeDecimal" minOccurs="1"
@@ -575,4 +610,27 @@
             <xs:annotation name="final"/>
         </xs:element>
     </xs:complexType>
+
+    <!-- generic types -->
+    <xs:complexType name="nonNegativeFloatToFloatPoint">
+        <xs:sequence>
+            <xs:element name="first" type="nonNegativeDecimal">
+                <xs:annotation name="nonnull"/>
+                <xs:annotation name="final"/>
+            </xs:element>
+            <xs:element name="second" type="nonNegativeDecimal">
+                <xs:annotation name="nonnull"/>
+                <xs:annotation name="final"/>
+            </xs:element>
+        </xs:sequence>
+    </xs:complexType>
+
+    <xs:complexType name="nonNegativeFloatToFloatMap">
+        <xs:sequence>
+            <xs:element name="point" type="nonNegativeFloatToFloatPoint" maxOccurs="unbounded">
+                <xs:annotation name="nonnull"/>
+                <xs:annotation name="final"/>
+            </xs:element>
+        </xs:sequence>
+    </xs:complexType>
 </xs:schema>
diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt
index ad6434e..507c9dc 100644
--- a/services/core/xsd/display-device-config/schema/current.txt
+++ b/services/core/xsd/display-device-config/schema/current.txt
@@ -26,6 +26,14 @@
     method public final java.util.List<com.android.server.display.config.DisplayBrightnessPoint> getDisplayBrightnessPoint();
   }
 
+  public class BrightnessLimitMap {
+    ctor public BrightnessLimitMap();
+    method @NonNull public final com.android.server.display.config.NonNegativeFloatToFloatMap getMap();
+    method @NonNull public final com.android.server.display.config.PredefinedBrightnessLimitNames getType();
+    method public final void setMap(@NonNull com.android.server.display.config.NonNegativeFloatToFloatMap);
+    method public final void setType(@NonNull com.android.server.display.config.PredefinedBrightnessLimitNames);
+  }
+
   public class BrightnessThresholds {
     ctor public BrightnessThresholds();
     method public final com.android.server.display.config.ThresholdPoints getBrightnessThresholdPoints();
@@ -89,6 +97,7 @@
     method public final com.android.server.display.config.Thresholds getDisplayBrightnessChangeThresholdsIdle();
     method public com.android.server.display.config.HighBrightnessMode getHighBrightnessMode();
     method public final com.android.server.display.config.SensorDetails getLightSensor();
+    method public com.android.server.display.config.LuxThrottling getLuxThrottling();
     method @Nullable public final String getName();
     method public final com.android.server.display.config.SensorDetails getProxSensor();
     method public com.android.server.display.config.DisplayQuirks getQuirks();
@@ -115,6 +124,7 @@
     method public final void setDisplayBrightnessChangeThresholdsIdle(com.android.server.display.config.Thresholds);
     method public void setHighBrightnessMode(com.android.server.display.config.HighBrightnessMode);
     method public final void setLightSensor(com.android.server.display.config.SensorDetails);
+    method public void setLuxThrottling(com.android.server.display.config.LuxThrottling);
     method public final void setName(@Nullable String);
     method public final void setProxSensor(com.android.server.display.config.SensorDetails);
     method public void setQuirks(com.android.server.display.config.DisplayQuirks);
@@ -173,6 +183,11 @@
     method public java.util.List<java.math.BigInteger> getItem();
   }
 
+  public class LuxThrottling {
+    ctor public LuxThrottling();
+    method @NonNull public final java.util.List<com.android.server.display.config.BrightnessLimitMap> getBrightnessLimitMap();
+  }
+
   public class NitsMap {
     ctor public NitsMap();
     method public String getInterpolation();
@@ -180,6 +195,19 @@
     method public void setInterpolation(String);
   }
 
+  public class NonNegativeFloatToFloatMap {
+    ctor public NonNegativeFloatToFloatMap();
+    method @NonNull public final java.util.List<com.android.server.display.config.NonNegativeFloatToFloatPoint> getPoint();
+  }
+
+  public class NonNegativeFloatToFloatPoint {
+    ctor public NonNegativeFloatToFloatPoint();
+    method @NonNull public final java.math.BigDecimal getFirst();
+    method @NonNull public final java.math.BigDecimal getSecond();
+    method public final void setFirst(@NonNull java.math.BigDecimal);
+    method public final void setSecond(@NonNull java.math.BigDecimal);
+  }
+
   public class Point {
     ctor public Point();
     method @NonNull public final java.math.BigDecimal getNits();
@@ -188,6 +216,12 @@
     method public final void setValue(@NonNull java.math.BigDecimal);
   }
 
+  public enum PredefinedBrightnessLimitNames {
+    method public String getRawName();
+    enum_constant public static final com.android.server.display.config.PredefinedBrightnessLimitNames _default;
+    enum_constant public static final com.android.server.display.config.PredefinedBrightnessLimitNames adaptive;
+  }
+
   public class RefreshRateConfigs {
     ctor public RefreshRateConfigs();
     method public final java.math.BigInteger getDefaultPeakRefreshRate();
diff --git a/services/core/xsd/display-layout-config/display-layout-config.xsd b/services/core/xsd/display-layout-config/display-layout-config.xsd
index ce022e9..57b5d00 100644
--- a/services/core/xsd/display-layout-config/display-layout-config.xsd
+++ b/services/core/xsd/display-layout-config/display-layout-config.xsd
@@ -53,6 +53,7 @@
             <xs:element name="position" type="xs:string" minOccurs="0" maxOccurs="1" />
             <xs:element name="brightnessThrottlingMapId" type="xs:string" minOccurs="0" maxOccurs="1" />
             <xs:element name="refreshRateThermalThrottlingMapId" type="xs:string" minOccurs="0" />
+            <xs:element name="leadDisplayAddress" type="xs:nonNegativeInteger" minOccurs="0" maxOccurs="1" />
         </xs:sequence>
         <xs:attribute name="enabled" type="xs:boolean" use="optional" />
         <xs:attribute name="defaultDisplay" type="xs:boolean" use="optional" />
diff --git a/services/core/xsd/display-layout-config/schema/current.txt b/services/core/xsd/display-layout-config/schema/current.txt
index 42a800d..2d4f7a4 100644
--- a/services/core/xsd/display-layout-config/schema/current.txt
+++ b/services/core/xsd/display-layout-config/schema/current.txt
@@ -6,6 +6,7 @@
     method public java.math.BigInteger getAddress();
     method public String getBrightnessThrottlingMapId();
     method public String getDisplayGroup();
+    method public java.math.BigInteger getLeadDisplayAddress();
     method public String getPosition();
     method public String getRefreshRateThermalThrottlingMapId();
     method public String getRefreshRateZoneId();
@@ -16,6 +17,7 @@
     method public void setDefaultDisplay(boolean);
     method public void setDisplayGroup(String);
     method public void setEnabled(boolean);
+    method public void setLeadDisplayAddress(java.math.BigInteger);
     method public void setPosition(String);
     method public void setRefreshRateThermalThrottlingMapId(String);
     method public void setRefreshRateZoneId(String);
diff --git a/services/core/xsd/vts/OWNERS b/services/core/xsd/vts/OWNERS
new file mode 100644
index 0000000..9af2eba
--- /dev/null
+++ b/services/core/xsd/vts/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 151862
+sundongahn@google.com
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index e44b8cd..77f8de5 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -1413,7 +1413,6 @@
                             policy.mAdminList.remove(i);
                             policy.mAdminMap.remove(aa.info.getComponent());
                             pushActiveAdminPackagesLocked(userHandle);
-                            pushMeteredDisabledPackages(userHandle);
                         }
                     }
                 } catch (RemoteException re) {
@@ -1454,6 +1453,7 @@
         if (removedAdmin) {
             // The removed admin might have disabled camera, so update user restrictions.
             pushUserRestrictions(userHandle);
+            pushMeteredDisabledPackages(userHandle);
         }
     }
 
@@ -3186,6 +3186,10 @@
         final ActiveAdmin admin = getActiveAdminUncheckedLocked(adminReceiver, userHandle);
         DevicePolicyData policy = getUserData(userHandle);
         if (admin != null && !policy.mRemovingAdmins.contains(adminReceiver)) {
+            Slogf.d(LOG_TAG, "Adding " + adminReceiver + " for user " + userHandle
+                    + " to list of removing admins.");
+            logStackTrace("removeActiveAdminLocked");
+
             policy.mRemovingAdmins.add(adminReceiver);
             sendAdminCommandLocked(admin,
                     DeviceAdminReceiver.ACTION_DEVICE_ADMIN_DISABLED,
@@ -3465,8 +3469,10 @@
             }
 
             revertTransferOwnershipIfNecessaryLocked();
+            if (!isPolicyEngineForFinanceFlagEnabled()) {
+                updateUsbDataSignal(mContext, isUsbDataSignalingEnabledInternalLocked());
+            }
         }
-        updateUsbDataSignal();
 
         // In case flag value has changed, we apply it during boot to avoid doing it concurrently
         // with user toggling quiet mode.
@@ -5126,8 +5132,11 @@
             boolean deviceWideOnly) {
         final CallerIdentity caller = getCallerIdentity();
         Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(caller, userHandle)
-                && (isSystemUid(caller) || hasCallingOrSelfPermission(
-                permission.SET_INITIAL_LOCK)));
+                && (isSystemUid(caller)
+                    // Accept any permission that ILockSettings#setLockCredential() accepts.
+                    || hasCallingOrSelfPermission(permission.SET_INITIAL_LOCK)
+                    || hasCallingOrSelfPermission(permission.SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS)
+                    || hasCallingOrSelfPermission(permission.ACCESS_KEYGUARD_SECURE_STORAGE)));
         return getPasswordMinimumMetricsUnchecked(userHandle, deviceWideOnly);
     }
 
@@ -5724,20 +5733,17 @@
         final int callingUid = caller.getUid();
         final int userHandle = UserHandle.getUserId(callingUid);
         final boolean isPin = PasswordMetrics.isNumericOnly(password);
+        final LockscreenCredential newCredential;
+        if (isPin) {
+            newCredential = LockscreenCredential.createPin(password);
+        } else {
+            newCredential = LockscreenCredential.createPasswordOrNone(password);
+        }
         synchronized (getLockObject()) {
             final PasswordMetrics minMetrics = getPasswordMinimumMetricsUnchecked(userHandle);
-            final List<PasswordValidationError> validationErrors;
             final int complexity = getAggregatedPasswordComplexityLocked(userHandle);
-            // TODO: Consider changing validation API to take LockscreenCredential.
-            if (password.isEmpty()) {
-                validationErrors = PasswordMetrics.validatePasswordMetrics(
-                        minMetrics, complexity, new PasswordMetrics(CREDENTIAL_TYPE_NONE));
-            } else {
-                // TODO(b/120484642): remove getBytes() below
-                validationErrors = PasswordMetrics.validatePassword(
-                        minMetrics, complexity, isPin, password.getBytes());
-            }
-
+            final List<PasswordValidationError> validationErrors =
+                    PasswordMetrics.validateCredential(minMetrics, complexity, newCredential);
             if (!validationErrors.isEmpty()) {
                 Slogf.w(LOG_TAG, "Failed to reset password due to constraint violation: %s",
                         validationErrors.get(0));
@@ -5761,12 +5767,6 @@
         // Don't do this with the lock held, because it is going to call
         // back in to the service.
         final long ident = mInjector.binderClearCallingIdentity();
-        final LockscreenCredential newCredential;
-        if (isPin) {
-            newCredential = LockscreenCredential.createPin(password);
-        } else {
-            newCredential = LockscreenCredential.createPasswordOrNone(password);
-        }
         try {
             if (tokenHandle == 0 || token == null) {
                 if (!mLockPatternUtils.setLockCredential(newCredential,
@@ -13598,6 +13598,9 @@
                     : SecurityLog.TAG_USER_RESTRICTION_REMOVED;
             SecurityLog.writeEvent(eventTag, caller.getPackageName(), caller.getUserId(), key);
         }
+
+        Slogf.i(LOG_TAG, "Changing user restriction %s to: %b caller: %s",
+                key, enabled, caller.toString());
     }
 
     private void saveUserRestrictionsLocked(int userId) {
@@ -14842,17 +14845,23 @@
                 "Caller is not managed profile owner or device owner;"
                         + " only managed profile owner or device owner may control the preferential"
                         + " network service");
-        synchronized (getLockObject()) {
-            final ActiveAdmin requiredAdmin = getDeviceOrProfileOwnerAdminLocked(
-                    caller.getUserId());
-            if (!requiredAdmin.mPreferentialNetworkServiceConfigs.equals(
-                    preferentialNetworkServiceConfigs)) {
-                requiredAdmin.mPreferentialNetworkServiceConfigs =
-                        new ArrayList<>(preferentialNetworkServiceConfigs);
-                saveSettingsLocked(caller.getUserId());
+
+        try {
+            updateNetworkPreferenceForUser(caller.getUserId(), preferentialNetworkServiceConfigs);
+            synchronized (getLockObject()) {
+                final ActiveAdmin requiredAdmin = getDeviceOrProfileOwnerAdminLocked(
+                        caller.getUserId());
+                if (!requiredAdmin.mPreferentialNetworkServiceConfigs.equals(
+                        preferentialNetworkServiceConfigs)) {
+                    requiredAdmin.mPreferentialNetworkServiceConfigs =
+                            new ArrayList<>(preferentialNetworkServiceConfigs);
+                    saveSettingsLocked(caller.getUserId());
+                }
             }
+        } catch (Exception e) {
+            Slogf.e(LOG_TAG, "Failed to set preferential network service configs");
+            throw e;
         }
-        updateNetworkPreferenceForUser(caller.getUserId(), preferentialNetworkServiceConfigs);
         DevicePolicyEventLogger
                 .createEvent(DevicePolicyEnums.SET_PREFERENTIAL_NETWORK_SERVICE_ENABLED)
                 .setBoolean(preferentialNetworkServiceConfigs
@@ -16220,6 +16229,11 @@
         }
 
         @Override
+        public ComponentName getDeviceOwnerComponent(boolean callingUserOnly) {
+            return DevicePolicyManagerService.this.getDeviceOwnerComponent(callingUserOnly);
+        }
+
+        @Override
         public int getDeviceOwnerUserId() {
             return DevicePolicyManagerService.this.getDeviceOwnerUserId();
         }
@@ -17306,9 +17320,6 @@
         if (!mUserManager.isUserRunning(new UserHandle(deviceOwnerUserId))) {
             return STATUS_USER_NOT_RUNNING;
         }
-        if (mIsWatch && hasPaired(UserHandle.USER_SYSTEM)) {
-            return STATUS_HAS_PAIRED;
-        }
 
         boolean isHeadlessSystemUserMode = mInjector.userManagerIsHeadlessSystemUserMode();
 
@@ -17332,7 +17343,7 @@
 
         if (isAdb) {
             // If shell command runs after user setup completed check device status. Otherwise, OK.
-            if (mIsWatch || hasUserSetupCompleted(UserHandle.USER_SYSTEM)) {
+            if (hasUserSetupCompleted(UserHandle.USER_SYSTEM)) {
                 // DO can be setup only if there are no users which are neither created by default
                 // nor marked as FOR_TESTING
 
@@ -17880,41 +17891,44 @@
         if (!mHasFeature) {
             return packageNames;
         }
-        synchronized (getLockObject()) {
-            final ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId());
-            return mInjector.binderWithCleanCallingIdentity(() -> {
-                final List<String> excludedPkgs = removeInvalidPkgsForMeteredDataRestriction(
-                        caller.getUserId(), packageNames);
+        return mInjector.binderWithCleanCallingIdentity(() -> {
+            final List<String> excludedPkgs = removeInvalidPkgsForMeteredDataRestriction(
+                    caller.getUserId(), packageNames);
+
+            synchronized (getLockObject()) {
+                final ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId());
                 admin.meteredDisabledPackages = packageNames;
-                pushMeteredDisabledPackages(caller.getUserId());
                 saveSettingsLocked(caller.getUserId());
-                return excludedPkgs;
-            });
-        }
+            }
+            pushMeteredDisabledPackages(caller.getUserId());
+            return excludedPkgs;
+        });
     }
 
     private List<String> removeInvalidPkgsForMeteredDataRestriction(
             int userId, List<String> pkgNames) {
-        final Set<String> activeAdmins = getActiveAdminPackagesLocked(userId);
-        final List<String> excludedPkgs = new ArrayList<>();
-        for (int i = pkgNames.size() - 1; i >= 0; --i) {
-            final String pkgName = pkgNames.get(i);
-            // If the package is an active admin, don't restrict it.
-            if (activeAdmins.contains(pkgName)) {
-                excludedPkgs.add(pkgName);
-                continue;
-            }
-            // If the package doesn't exist, don't restrict it.
-            try {
-                if (!mInjector.getIPackageManager().isPackageAvailable(pkgName, userId)) {
+        synchronized (getLockObject()) {
+            final Set<String> activeAdmins = getActiveAdminPackagesLocked(userId);
+            final List<String> excludedPkgs = new ArrayList<>();
+            for (int i = pkgNames.size() - 1; i >= 0; --i) {
+                final String pkgName = pkgNames.get(i);
+                // If the package is an active admin, don't restrict it.
+                if (activeAdmins.contains(pkgName)) {
                     excludedPkgs.add(pkgName);
+                    continue;
                 }
-            } catch (RemoteException e) {
-                // Should not happen
+                // If the package doesn't exist, don't restrict it.
+                try {
+                    if (!mInjector.getIPackageManager().isPackageAvailable(pkgName, userId)) {
+                        excludedPkgs.add(pkgName);
+                    }
+                } catch (RemoteException e) {
+                    // Should not happen
+                }
             }
+            pkgNames.removeAll(excludedPkgs);
+            return excludedPkgs;
         }
-        pkgNames.removeAll(excludedPkgs);
-        return excludedPkgs;
     }
 
     @Override
@@ -18540,7 +18554,15 @@
             pushActiveAdminPackagesLocked(userHandle);
             saveSettingsLocked(userHandle);
             updateMaximumTimeToLockLocked(userHandle);
+
+            Slogf.d(LOG_TAG,
+                    "Removing device admin " + adminReceiver + " from user " + userHandle);
+            logStackTrace("removeAdminArtifacts");
+
             policy.mRemovingAdmins.remove(adminReceiver);
+            Slogf.d(LOG_TAG, "Current state of DevicePolicyData#mRemovingAdmins for user "
+                    + userHandle + ": " + policy.mRemovingAdmins);
+
             pushScreenCapturePolicy(userHandle);
 
             Slogf.i(LOG_TAG, "Device admin " + adminReceiver + " removed from user " + userHandle);
@@ -19488,8 +19510,9 @@
     public boolean isCurrentInputMethodSetByOwner() {
         final CallerIdentity caller = getCallerIdentity();
         Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)
-                || isProfileOwner(caller) || isSystemUid(caller),
-                "Only profile owner, device owner and system may call this method.");
+                || isProfileOwner(caller) || canQueryAdminPolicy(caller) || isSystemUid(caller),
+                "Only profile owner, device owner, a caller with QUERY_ADMIN_POLICY "
+                        + "permission or system may call this method.");
         return getUserData(caller.getUserId()).mCurrentInputMethodSet;
     }
 
@@ -22343,7 +22366,7 @@
     public void setUsbDataSignalingEnabled(String packageName, boolean enabled) {
         Objects.requireNonNull(packageName, "Admin package name must be provided");
         final CallerIdentity caller = getCallerIdentity(packageName);
-        if (!isPermissionCheckFlagEnabled()) {
+        if (!isPolicyEngineForFinanceFlagEnabled()) {
             Preconditions.checkCallAuthorization(
                     isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
                     "USB data signaling can only be controlled by a device owner or "
@@ -22352,22 +22375,25 @@
                     "USB data signaling cannot be disabled.");
         }
 
-
         synchronized (getLockObject()) {
-            ActiveAdmin admin;
-            if (isPermissionCheckFlagEnabled()) {
-                admin = enforcePermissionAndGetEnforcingAdmin(
+            if (isPolicyEngineForFinanceFlagEnabled()) {
+                EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
                         /* admin= */ null, MANAGE_DEVICE_POLICY_USB_DATA_SIGNALLING,
                         caller.getPackageName(),
-                        caller.getUserId()).getActiveAdmin();
+                        caller.getUserId());
+                Preconditions.checkState(canUsbDataSignalingBeDisabled(),
+                        "USB data signaling cannot be disabled.");
+                mDevicePolicyEngine.setGlobalPolicy(
+                        PolicyDefinition.USB_DATA_SIGNALING,
+                        enforcingAdmin,
+                        new BooleanPolicyValue(enabled));
             } else {
-                admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId());
-            }
-
-            if (admin.mUsbDataSignalingEnabled != enabled) {
-                admin.mUsbDataSignalingEnabled = enabled;
-                saveSettingsLocked(caller.getUserId());
-                updateUsbDataSignal();
+                ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId());
+                if (admin.mUsbDataSignalingEnabled != enabled) {
+                    admin.mUsbDataSignalingEnabled = enabled;
+                    saveSettingsLocked(caller.getUserId());
+                    updateUsbDataSignal(mContext, isUsbDataSignalingEnabledInternalLocked());
+                }
             }
         }
         DevicePolicyEventLogger
@@ -22377,16 +22403,12 @@
                 .write();
     }
 
-    private void updateUsbDataSignal() {
-        if (!canUsbDataSignalingBeDisabled()) {
+    static void updateUsbDataSignal(Context context, boolean value) {
+        if (!canUsbDataSignalingBeDisabledInternal(context)) {
             return;
         }
-        final boolean usbEnabled;
-        synchronized (getLockObject()) {
-            usbEnabled = isUsbDataSignalingEnabledInternalLocked();
-        }
-        if (!mInjector.binderWithCleanCallingIdentity(
-                () -> mInjector.getUsbManager().enableUsbDataSignal(usbEnabled))) {
+        if (!Binder.withCleanCallingIdentity(
+                () -> context.getSystemService(UsbManager.class).enableUsbDataSignal(value))) {
             Slogf.w(LOG_TAG, "Failed to set usb data signaling state");
         }
     }
@@ -22394,28 +22416,26 @@
     @Override
     public boolean isUsbDataSignalingEnabled(String packageName) {
         final CallerIdentity caller = getCallerIdentity(packageName);
-        synchronized (getLockObject()) {
-            // If the caller is an admin, return the policy set by itself. Otherwise
-            // return the device-wide policy.
-            if (isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller)) {
-                return getProfileOwnerOrDeviceOwnerLocked(
-                        caller.getUserId()).mUsbDataSignalingEnabled;
-            } else {
-                return isUsbDataSignalingEnabledInternalLocked();
+        if (isPolicyEngineForFinanceFlagEnabled()) {
+            Boolean enabled = mDevicePolicyEngine.getResolvedPolicy(
+                    PolicyDefinition.USB_DATA_SIGNALING,
+                    caller.getUserId());
+            return enabled == null || enabled;
+        } else {
+            synchronized (getLockObject()) {
+                // If the caller is an admin, return the policy set by itself. Otherwise
+                // return the device-wide policy.
+                if (isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(
+                        caller)) {
+                    return getProfileOwnerOrDeviceOwnerLocked(
+                            caller.getUserId()).mUsbDataSignalingEnabled;
+                } else {
+                    return isUsbDataSignalingEnabledInternalLocked();
+                }
             }
         }
     }
 
-    @Override
-    public boolean isUsbDataSignalingEnabledForUser(int userId) {
-        final CallerIdentity caller = getCallerIdentity();
-        Preconditions.checkCallAuthorization(isSystemUid(caller));
-
-        synchronized (getLockObject()) {
-            return isUsbDataSignalingEnabledInternalLocked();
-        }
-    }
-
     private boolean isUsbDataSignalingEnabledInternalLocked() {
         // TODO(b/261999445): remove
         ActiveAdmin admin;
@@ -22430,9 +22450,14 @@
 
     @Override
     public boolean canUsbDataSignalingBeDisabled() {
-        return mInjector.binderWithCleanCallingIdentity(() ->
-                mInjector.getUsbManager() != null
-                        && mInjector.getUsbManager().getUsbHalVersion() >= UsbManager.USB_HAL_V1_3
+        return canUsbDataSignalingBeDisabledInternal(mContext);
+    }
+
+    private static boolean canUsbDataSignalingBeDisabledInternal(Context context) {
+        return Binder.withCleanCallingIdentity(() ->
+            context.getSystemService(UsbManager.class) != null
+                  && context.getSystemService(UsbManager.class).getUsbHalVersion()
+                  >= UsbManager.USB_HAL_V1_3
         );
     }
 
@@ -24107,7 +24132,7 @@
                 // When listener is added onSubscriptionsChanged gets called immediately for once
                 // (even if subscriptions are not changed) and later on when subscriptions changes.
                 subscriptionManager.addOnSubscriptionsChangedListener(
-                        mSubscriptionsChangedListener.getHandlerExecutor(),
+                        mHandler::post,
                         mSubscriptionsChangedListener);
             } finally {
                 mInjector.binderRestoreCallingIdentity(id);
@@ -24627,4 +24652,28 @@
         return getRoleHolderPackageNameOnUser(
                 RoleManager.ROLE_FINANCED_DEVICE_KIOSK, UserHandle.USER_ALL);
     }
+
+    /**
+     * TODO (b/278924166): this method is added for debugging the specified bug.
+     * Remove once fixed.
+     **/
+    private void logStackTrace(String methodName) {
+        try {
+            StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();
+            String stackMethod;
+            StringBuilder stackTrace = new StringBuilder();
+            for (StackTraceElement s : stackTraceElements) {
+                stackMethod = s.getMethodName();
+                if (stackMethod == null || stackMethod.equals("getThreadStackTrace")
+                        || stackMethod.equals("getStackTrace")
+                        || stackMethod.equals("logStackTrace")) {
+                    continue;
+                }
+                stackTrace.append(s.getMethodName() + ":" + s.getLineNumber() + "\n");
+            }
+            Slogf.d(LOG_TAG, "StackTrace for " + methodName + ": \n" + stackTrace);
+        } catch (Exception e) {
+            Slogf.d(LOG_TAG, "Unable to get stacktrace");
+        }
+    }
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
index 7e48407..7a877b9 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
@@ -332,6 +332,14 @@
             PolicyEnforcerCallbacks::setPersonalAppsSuspended,
             new BooleanPolicySerializer());
 
+    static PolicyDefinition<Boolean> USB_DATA_SIGNALING = new PolicyDefinition<>(
+            new NoArgsPolicyKey(DevicePolicyIdentifiers.USB_DATA_SIGNALING_POLICY),
+            // usb data signaling is enabled by default, hence disabling it is more restrictive.
+            FALSE_MORE_RESTRICTIVE,
+            POLICY_FLAG_GLOBAL_ONLY_POLICY,
+            (Boolean value, Context context, Integer userId, PolicyKey policyKey) ->
+                PolicyEnforcerCallbacks.setUsbDataSignalingEnabled(value, context),
+            new BooleanPolicySerializer());
 
     private static final Map<String, PolicyDefinition<?>> POLICY_DEFINITIONS = new HashMap<>();
     private static Map<String, Integer> USER_RESTRICTION_FLAGS = new HashMap<>();
@@ -364,6 +372,8 @@
                 SCREEN_CAPTURE_DISABLED);
         POLICY_DEFINITIONS.put(DevicePolicyIdentifiers.PERSONAL_APPS_SUSPENDED_POLICY,
                 PERSONAL_APPS_SUSPENDED);
+        POLICY_DEFINITIONS.put(DevicePolicyIdentifiers.USB_DATA_SIGNALING_POLICY,
+                USB_DATA_SIGNALING);
 
         // User Restriction Policies
         USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_MODIFY_ACCOUNTS, /* flags= */ 0);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
index 3b048b2..6570ce1 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
@@ -302,4 +302,14 @@
             Slogf.wtf(LOG_TAG, "Failed to suspend apps: " + String.join(",", failedApps));
         }
     }
+
+    static boolean setUsbDataSignalingEnabled(@Nullable Boolean value, @NonNull Context context) {
+        return Binder.withCleanCallingIdentity(() -> {
+            Objects.requireNonNull(context);
+
+            boolean enabled = value == null || value;
+            DevicePolicyManagerService.updateUsbDataSignal(context, enabled);
+            return true;
+        });
+    }
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyVersionUpgrader.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyVersionUpgrader.java
index 06f11be..733b1d9 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyVersionUpgrader.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyVersionUpgrader.java
@@ -29,6 +29,7 @@
 import java.io.IOException;
 import java.nio.charset.Charset;
 import java.nio.file.Files;
+import java.nio.file.NoSuchFileException;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -307,6 +308,8 @@
             String versionString = Files.readAllLines(
                     file.toPath(), Charset.defaultCharset()).get(0);
             return Integer.parseInt(versionString);
+        } catch (NoSuchFileException e) {
+            return 0; // expected on first boot
         } catch (IOException | NumberFormatException | IndexOutOfBoundsException e) {
             Slog.e(LOG_TAG, "Error reading version", e);
             return 0;
diff --git a/services/flags/Android.bp b/services/flags/Android.bp
new file mode 100644
index 0000000..2d0337d
--- /dev/null
+++ b/services/flags/Android.bp
@@ -0,0 +1,17 @@
+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"],
+}
+
+java_library_static {
+    name: "services.flags",
+    defaults: ["platform_service_defaults"],
+    srcs: [
+        "java/**/*.java",
+    ],
+    libs: ["services.core"],
+}
diff --git a/services/flags/OWNERS b/services/flags/OWNERS
new file mode 100644
index 0000000..3925b5c
--- /dev/null
+++ b/services/flags/OWNERS
@@ -0,0 +1,6 @@
+# Bug component: 1306523
+
+mankoff@google.com
+
+pixel@google.com
+dsandler@android.com
diff --git a/services/flags/java/com/android/server/flags/DynamicFlagBinderDelegate.java b/services/flags/java/com/android/server/flags/DynamicFlagBinderDelegate.java
new file mode 100644
index 0000000..0db3287
--- /dev/null
+++ b/services/flags/java/com/android/server/flags/DynamicFlagBinderDelegate.java
@@ -0,0 +1,280 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.flags;
+
+import android.annotation.NonNull;
+import android.flags.IFeatureFlagsCallback;
+import android.flags.SyncableFlag;
+import android.os.Build;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.provider.DeviceConfig;
+import android.util.Slog;
+
+import com.android.internal.os.BackgroundThread;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Function;
+
+/**
+ * Handles DynamicFlags for {@link FeatureFlagsBinder}.
+ *
+ * Dynamic flags are simultaneously simpler and more complicated than process stable flags. We can
+ * return whatever value is last known for a flag is, without too much worry about the flags
+ * changing (they are dynamic after all). However, we have to alert all the relevant clients
+ * about those flag changes, and need to be able to restore to a default value if the flag gets
+ * reset/erased during runtime.
+ */
+class DynamicFlagBinderDelegate {
+
+    private final FlagOverrideStore mFlagStore;
+    private final FlagCache<DynamicFlagData> mDynamicFlags = new FlagCache<>();
+    private final Map<Integer, Set<IFeatureFlagsCallback>> mCallbacks = new HashMap<>();
+    private static final Function<Integer, Set<IFeatureFlagsCallback>> NEW_CALLBACK_SET =
+            k -> new HashSet<>();
+
+    private final DeviceConfig.OnPropertiesChangedListener mDeviceConfigListener =
+            new DeviceConfig.OnPropertiesChangedListener() {
+                @Override
+                public void onPropertiesChanged(@NonNull DeviceConfig.Properties properties) {
+                    String ns = properties.getNamespace();
+                    for (String name : properties.getKeyset()) {
+                        // Don't alert for flags we don't care about.
+                        // Don't alert for flags that have been overridden locally.
+                        if (!mDynamicFlags.contains(ns, name) || mFlagStore.contains(ns, name)) {
+                            continue;
+                        }
+                        mFlagChangeCallback.onFlagChanged(
+                                ns, name, properties.getString(name, null));
+                    }
+                }
+            };
+
+    private final FlagOverrideStore.FlagChangeCallback mFlagChangeCallback =
+            (namespace, name, value) -> {
+                // Don't bother with callbacks for non-dynamic flags.
+                if (!mDynamicFlags.contains(namespace, name)) {
+                    return;
+                }
+
+                // Don't bother with callbacks if nothing changed.
+                // Handling erasure (null) is special, as we may be restoring back to a value
+                // we were already at.
+                DynamicFlagData data = mDynamicFlags.getOrNull(namespace, name);
+                if (data == null) {
+                    return;  // shouldn't happen, but better safe than sorry.
+                }
+                if (value == null) {
+                    if (data.getValue().equals(data.getDefaultValue())) {
+                        return;
+                    }
+                    value = data.getDefaultValue();
+                } else if (data.getValue().equals(value)) {
+                    return;
+                }
+                data.setValue(value);
+
+                final Set<IFeatureFlagsCallback> cbCopy;
+                synchronized (mCallbacks) {
+                    cbCopy = new HashSet<>();
+
+                    for (Integer pid : mCallbacks.keySet()) {
+                        if (data.containsPid(pid)) {
+                            cbCopy.addAll(mCallbacks.get(pid));
+                        }
+                    }
+                }
+                SyncableFlag sFlag = new SyncableFlag(namespace, name, value, true);
+                cbCopy.forEach(cb -> {
+                    try {
+                        cb.onFlagChange(sFlag);
+                    } catch (RemoteException e) {
+                        Slog.w(
+                                FeatureFlagsService.TAG,
+                                "Failed to communicate flag change to client.");
+                    }
+                });
+            };
+
+    DynamicFlagBinderDelegate(FlagOverrideStore flagStore) {
+        mFlagStore = flagStore;
+        mFlagStore.setChangeCallback(mFlagChangeCallback);
+    }
+
+    SyncableFlag syncDynamicFlag(int pid, SyncableFlag sf) {
+        if (!sf.isDynamic()) {
+            return sf;
+        }
+
+        String ns = sf.getNamespace();
+        String name = sf.getName();
+
+        // Dynamic flags don't need any special threading or synchronization considerations.
+        // We simply give them whatever the current value is.
+        // However, we do need to keep track of dynamic flags, so that we can alert
+        // about changes coming in from adb, DeviceConfig, or other sources.
+        // And also so that we can keep flags relatively consistent across processes.
+
+        DynamicFlagData data = mDynamicFlags.getOrNull(ns, name);
+        String value = getFlagValue(ns, name, sf.getValue());
+        // DeviceConfig listeners are per-namespace.
+        if (!mDynamicFlags.containsNamespace(ns)) {
+            DeviceConfig.addOnPropertiesChangedListener(
+                    ns, BackgroundThread.getExecutor(), mDeviceConfigListener);
+        }
+        data.addClientPid(pid);
+        data.setValue(value);
+        // Store the default value so that if an override gets erased, we can restore
+        // to something.
+        data.setDefaultValue(sf.getValue());
+
+        return new SyncableFlag(sf.getNamespace(), sf.getName(), value, true);
+    }
+
+
+    void registerCallback(int pid, IFeatureFlagsCallback callback) {
+        // Always add callback so that we don't end up with a possible race/leak.
+        // We remove the callback directly if we fail to call #linkToDeath.
+        // If we tried to add the callback after we linked, then we could end up in a
+        // scenario where we link, then the binder dies, firing our BinderGriever which tries
+        // to remove the callback (which has not yet been added), then finally we add the
+        // callback, creating a leak.
+        Set<IFeatureFlagsCallback> callbacks;
+        synchronized (mCallbacks) {
+            callbacks = mCallbacks.computeIfAbsent(pid, NEW_CALLBACK_SET);
+            callbacks.add(callback);
+        }
+        try {
+            callback.asBinder().linkToDeath(new BinderGriever(pid), 0);
+        } catch (RemoteException e) {
+            Slog.e(
+                    FeatureFlagsService.TAG,
+                    "Failed to link to binder death. Callback not registered.");
+            synchronized (mCallbacks) {
+                callbacks.remove(callback);
+            }
+        }
+    }
+
+    void unregisterCallback(int pid, IFeatureFlagsCallback callback) {
+        // No need to unlink, since the BinderGriever will essentially be a no-op.
+        // We would have to track our BinderGriever's in a map otherwise.
+        synchronized (mCallbacks) {
+            Set<IFeatureFlagsCallback> callbacks =
+                    mCallbacks.computeIfAbsent(pid, NEW_CALLBACK_SET);
+            callbacks.remove(callback);
+        }
+    }
+
+    String getFlagValue(String namespace, String name, String defaultValue) {
+        // If we already have a value cached, just use that.
+        String value = null;
+        DynamicFlagData data = mDynamicFlags.getOrNull(namespace, name);
+        if (data != null) {
+            value = data.getValue();
+        } else {
+            // Put the value in the cache for future reference.
+            data = new DynamicFlagData(namespace, name);
+            mDynamicFlags.setIfChanged(namespace, name, data);
+        }
+        // If we're not in a release build, flags can be overridden locally on device.
+        if (!Build.IS_USER && value == null) {
+            value = mFlagStore.get(namespace, name);
+        }
+        // If we still don't have a value, maybe DeviceConfig does?
+        // Fallback to sf.getValue() here as well.
+        if (value == null) {
+            value = DeviceConfig.getString(namespace, name, defaultValue);
+        }
+
+        return value;
+    }
+
+    private static class DynamicFlagData {
+        private final String mNamespace;
+        private final String mName;
+        private final Set<Integer> mPids = new HashSet<>();
+        private String mValue;
+        private String mDefaultValue;
+
+        private DynamicFlagData(String namespace, String name) {
+            mNamespace = namespace;
+            mName = name;
+        }
+
+        String getValue() {
+            return mValue;
+        }
+
+        void setValue(String value) {
+            mValue = value;
+        }
+
+        String getDefaultValue() {
+            return mDefaultValue;
+        }
+
+        void setDefaultValue(String value) {
+            mDefaultValue = value;
+        }
+
+        void addClientPid(int pid) {
+            mPids.add(pid);
+        }
+
+        boolean containsPid(int pid) {
+            return mPids.contains(pid);
+        }
+
+        @Override
+        public boolean equals(Object other) {
+            if (other == null || !(other instanceof DynamicFlagData)) {
+                return false;
+            }
+
+            DynamicFlagData o = (DynamicFlagData) other;
+
+            return mName.equals(o.mName) && mNamespace.equals(o.mNamespace)
+                    && mValue.equals(o.mValue) && mDefaultValue.equals(o.mDefaultValue);
+        }
+
+        @Override
+        public int hashCode() {
+            return mName.hashCode() + mNamespace.hashCode()
+              + mValue.hashCode() + mDefaultValue.hashCode();
+        }
+    }
+
+
+    private class BinderGriever implements IBinder.DeathRecipient {
+        private final int mPid;
+
+        private BinderGriever(int pid) {
+            mPid = pid;
+        }
+
+        @Override
+        public void binderDied() {
+            synchronized (mCallbacks) {
+                mCallbacks.remove(mPid);
+            }
+        }
+    }
+}
diff --git a/services/flags/java/com/android/server/flags/FeatureFlagsBinder.java b/services/flags/java/com/android/server/flags/FeatureFlagsBinder.java
new file mode 100644
index 0000000..1fa8532
--- /dev/null
+++ b/services/flags/java/com/android/server/flags/FeatureFlagsBinder.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.flags;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.flags.IFeatureFlags;
+import android.flags.IFeatureFlagsCallback;
+import android.flags.SyncableFlag;
+import android.os.Build;
+import android.os.ParcelFileDescriptor;
+
+import com.android.internal.flags.CoreFlags;
+import com.android.server.flags.FeatureFlagsService.PermissionsChecker;
+
+import java.io.FileOutputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+class FeatureFlagsBinder extends IFeatureFlags.Stub {
+    private final FlagOverrideStore mFlagStore;
+    private final FlagsShellCommand mShellCommand;
+    private final FlagCache<String> mFlagCache = new FlagCache<>();
+    private final DynamicFlagBinderDelegate mDynamicFlagDelegate;
+    private final PermissionsChecker mPermissionsChecker;
+
+    FeatureFlagsBinder(
+            FlagOverrideStore flagStore,
+            FlagsShellCommand shellCommand,
+            PermissionsChecker permissionsChecker) {
+        mFlagStore = flagStore;
+        mShellCommand = shellCommand;
+        mDynamicFlagDelegate = new DynamicFlagBinderDelegate(flagStore);
+        mPermissionsChecker = permissionsChecker;
+    }
+
+    @Override
+    public void registerCallback(IFeatureFlagsCallback callback) {
+        mDynamicFlagDelegate.registerCallback(getCallingPid(), callback);
+    }
+
+    @Override
+    public void unregisterCallback(IFeatureFlagsCallback callback) {
+        mDynamicFlagDelegate.unregisterCallback(getCallingPid(), callback);
+    }
+
+    // Note: The internals of this method should be kept in sync with queryFlags
+    // as they both should return identical results. The difference is that this method
+    // caches any values it receives and/or reads, whereas queryFlags does not.
+
+    @Override
+    public List<SyncableFlag> syncFlags(List<SyncableFlag> incomingFlags) {
+        int pid = getCallingPid();
+        List<SyncableFlag> outputFlags = new ArrayList<>();
+
+        boolean hasFullSyncPrivileges = false;
+        SecurityException permissionFailureException = null;
+        try {
+            assertSyncPermission();
+            hasFullSyncPrivileges = true;
+        } catch (SecurityException e) {
+            permissionFailureException = e;
+        }
+
+        for (SyncableFlag sf : incomingFlags) {
+            if (!hasFullSyncPrivileges && !CoreFlags.isCoreFlag(sf)) {
+                throw permissionFailureException;
+            }
+
+            String ns = sf.getNamespace();
+            String name = sf.getName();
+            SyncableFlag outFlag;
+            if (sf.isDynamic()) {
+                outFlag = mDynamicFlagDelegate.syncDynamicFlag(pid, sf);
+            } else {
+                synchronized (mFlagCache) {
+                    String value = mFlagCache.getOrNull(ns, name);
+                    if (value == null) {
+                        String overrideValue = Build.IS_USER ? null : mFlagStore.get(ns, name);
+                        value = overrideValue != null ? overrideValue : sf.getValue();
+                        mFlagCache.setIfChanged(ns, name, value);
+                    }
+                    outFlag = new SyncableFlag(sf.getNamespace(), sf.getName(), value, false);
+                }
+            }
+            outputFlags.add(outFlag);
+        }
+        return outputFlags;
+    }
+
+    @Override
+    public void overrideFlag(SyncableFlag flag) {
+        assertWritePermission();
+        mFlagStore.set(flag.getNamespace(), flag.getName(), flag.getValue());
+    }
+
+    @Override
+    public void resetFlag(SyncableFlag flag) {
+        assertWritePermission();
+        mFlagStore.erase(flag.getNamespace(), flag.getName());
+    }
+
+    @Override
+    public List<SyncableFlag> queryFlags(List<SyncableFlag> incomingFlags) {
+        assertSyncPermission();
+        List<SyncableFlag> outputFlags = new ArrayList<>();
+        for (SyncableFlag sf : incomingFlags) {
+            String ns = sf.getNamespace();
+            String name = sf.getName();
+            String value;
+            String storeValue = mFlagStore.get(ns, name);
+            boolean overridden  = storeValue != null;
+
+            if (sf.isDynamic()) {
+                value = mDynamicFlagDelegate.getFlagValue(ns, name, sf.getValue());
+            } else {
+                value = mFlagCache.getOrNull(ns, name);
+                if (value == null) {
+                    value = Build.IS_USER ? null : storeValue;
+                    if (value == null) {
+                        value = sf.getValue();
+                    }
+                }
+            }
+            outputFlags.add(new SyncableFlag(
+                    sf.getNamespace(), sf.getName(), value, sf.isDynamic(), overridden));
+        }
+
+        return outputFlags;
+    }
+
+    private void assertSyncPermission() {
+        mPermissionsChecker.assertSyncPermission();
+        clearCallingIdentity();
+    }
+
+    private void assertWritePermission() {
+        mPermissionsChecker.assertWritePermission();
+        clearCallingIdentity();
+    }
+
+
+    @SystemApi
+    public int handleShellCommand(
+            @NonNull ParcelFileDescriptor in,
+            @NonNull ParcelFileDescriptor out,
+            @NonNull ParcelFileDescriptor err,
+            @NonNull String[] args) {
+        FileOutputStream fout = new FileOutputStream(out.getFileDescriptor());
+        FileOutputStream ferr = new FileOutputStream(err.getFileDescriptor());
+
+        return mShellCommand.process(args, fout, ferr);
+    }
+}
diff --git a/services/flags/java/com/android/server/flags/FeatureFlagsService.java b/services/flags/java/com/android/server/flags/FeatureFlagsService.java
new file mode 100644
index 0000000..93b9e9e
--- /dev/null
+++ b/services/flags/java/com/android/server/flags/FeatureFlagsService.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.flags;
+
+import static android.Manifest.permission.SYNC_FLAGS;
+import static android.Manifest.permission.WRITE_FLAGS;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.flags.FeatureFlags;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.SystemService;
+
+/**
+ * A service that manages syncing {@link android.flags.FeatureFlags} across processes.
+ *
+ * This service holds flags stable for at least the lifetime of a process, meaning that if
+ * a process comes online with a flag set to true, any other process that connects here and
+ * tries to read the same flag will also receive the flag as true. The flag will remain stable
+ * until either all of the interested processes have died, or the device restarts.
+ *
+ * TODO(279054964): Add to dumpsys
+ * @hide
+ */
+public class FeatureFlagsService extends SystemService {
+
+    static final String TAG = "FeatureFlagsService";
+    private final FlagOverrideStore mFlagStore;
+    private final FlagsShellCommand mShellCommand;
+
+    /**
+     * Initializes the system service.
+     *
+     * @param context The system server context.
+     */
+    public FeatureFlagsService(Context context) {
+        super(context);
+        mFlagStore = new FlagOverrideStore(
+                new GlobalSettingsProxy(context.getContentResolver()));
+        mShellCommand = new FlagsShellCommand(mFlagStore);
+    }
+
+    @Override
+    public void onStart() {
+        Slog.d(TAG, "Started Feature Flag Service");
+        FeatureFlagsBinder service = new FeatureFlagsBinder(
+                mFlagStore, mShellCommand, new PermissionsChecker(getContext()));
+        publishBinderService(
+                Context.FEATURE_FLAGS_SERVICE, service);
+        publishLocalService(FeatureFlags.class, new FeatureFlags(service));
+    }
+
+    @Override
+    public void onBootPhase(int phase) {
+        super.onBootPhase(phase);
+
+        if (phase == PHASE_SYSTEM_SERVICES_READY) {
+            // Immediately sync our core flags so that they get locked in. We don't want third-party
+            // apps to override them, and syncing immediately is the easiest way to prevent that.
+            FeatureFlags.getInstance().sync();
+        }
+    }
+
+    /**
+     * Delegate for checking flag permissions.
+     */
+    @VisibleForTesting
+    public static class PermissionsChecker {
+        private final Context mContext;
+
+        @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+        public PermissionsChecker(Context context) {
+            mContext = context;
+        }
+
+        /**
+         * Ensures that the caller has {@link SYNC_FLAGS} permission.
+         */
+        @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+        public void assertSyncPermission() {
+            if (mContext.checkCallingOrSelfPermission(SYNC_FLAGS)
+                    != PackageManager.PERMISSION_GRANTED) {
+                throw new SecurityException(
+                        "Non-core flag queried. Requires SYNC_FLAGS permission!");
+            }
+        }
+
+        /**
+         * Ensures that the caller has {@link WRITE_FLAGS} permission.
+         */
+        @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+        public void assertWritePermission() {
+            if (mContext.checkCallingPermission(WRITE_FLAGS) != PackageManager.PERMISSION_GRANTED) {
+                throw new SecurityException("Requires WRITE_FLAGS permission!");
+            }
+        }
+    }
+}
diff --git a/services/flags/java/com/android/server/flags/FlagCache.java b/services/flags/java/com/android/server/flags/FlagCache.java
new file mode 100644
index 0000000..cee1578
--- /dev/null
+++ b/services/flags/java/com/android/server/flags/FlagCache.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 com.android.server.flags;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Function;
+
+/**
+ * Threadsafe cache of values that stores the supplied default on cache miss.
+ *
+ * @param <V> The type of value to store.
+ */
+public class FlagCache<V> {
+    private final Function<String, HashMap<String, V>> mNewHashMap = k -> new HashMap<>();
+
+    // Cache is organized first by namespace, then by name. All values are stored as strings.
+    final Map<String, Map<String, V>> mCache = new HashMap<>();
+
+    FlagCache() {
+    }
+
+    /**
+     * Returns true if the namespace exists in the cache already.
+     */
+    boolean containsNamespace(String namespace) {
+        synchronized (mCache) {
+            return mCache.containsKey(namespace);
+        }
+    }
+
+    /**
+     * Returns true if the value is stored in the cache.
+     */
+    boolean contains(String namespace, String name) {
+        synchronized (mCache) {
+            Map<String, V> nsCache = mCache.get(namespace);
+            return nsCache != null && nsCache.containsKey(name);
+        }
+    }
+
+    /**
+     * Sets the value if it is different from what is currently stored.
+     *
+     * If the value is not set, or the current value is null, it will store the value and
+     * return true.
+     *
+     * @return True if the value was set. False if the value is the same.
+     */
+    boolean setIfChanged(String namespace, String name, V value) {
+        synchronized (mCache) {
+            Map<String, V> nsCache = mCache.computeIfAbsent(namespace, mNewHashMap);
+            V curValue = nsCache.get(name);
+            if (curValue == null || !curValue.equals(value)) {
+                nsCache.put(name, value);
+                return true;
+            }
+            return false;
+        }
+    }
+
+    /**
+     * Gets the current value from the cache, setting it if it is currently absent.
+     *
+     * @return The value that is now in the cache after the call to the method.
+     */
+    V getOrSet(String namespace, String name, V defaultValue) {
+        synchronized (mCache) {
+            Map<String, V> nsCache = mCache.computeIfAbsent(namespace, mNewHashMap);
+            V value = nsCache.putIfAbsent(name, defaultValue);
+            return value == null ? defaultValue : value;
+        }
+    }
+
+    /**
+     * Gets the current value from the cache, returning null if not present.
+     *
+     * @return The value that is now in the cache if there is one.
+     */
+    V getOrNull(String namespace, String name) {
+        synchronized (mCache) {
+            Map<String, V> nsCache = mCache.get(namespace);
+            if (nsCache == null) {
+                return null;
+            }
+            return nsCache.get(name);
+        }
+    }
+}
diff --git a/services/flags/java/com/android/server/flags/FlagOverrideStore.java b/services/flags/java/com/android/server/flags/FlagOverrideStore.java
new file mode 100644
index 0000000..b1ddc7e6
--- /dev/null
+++ b/services/flags/java/com/android/server/flags/FlagOverrideStore.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.flags;
+
+import android.database.Cursor;
+import android.provider.Settings;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Persistent storage for the {@link FeatureFlagsService}.
+ *
+ * The implementation stores data in Settings.<store> (generally {@link Settings.Global}
+ * is expected).
+ */
+@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+public class FlagOverrideStore {
+    private static final String KEYNAME_PREFIX = "flag|";
+    private static final String NAMESPACE_NAME_SEPARATOR = ".";
+
+    private final SettingsProxy mSettingsProxy;
+
+    private FlagChangeCallback mCallback;
+
+    FlagOverrideStore(SettingsProxy settingsProxy) {
+        mSettingsProxy = settingsProxy;
+    }
+
+    void setChangeCallback(FlagChangeCallback callback) {
+        mCallback = callback;
+    }
+
+    /** Returns true if a non-null value is in the store. */
+    boolean contains(String namespace, String name) {
+        return get(namespace, name) != null;
+    }
+
+    /** Put a value in the store. */
+    @VisibleForTesting
+    public void set(String namespace, String name, String value) {
+        mSettingsProxy.putString(getPropName(namespace, name), value);
+        mCallback.onFlagChanged(namespace, name, value);
+    }
+
+    /** Read a value out of the store. */
+    @VisibleForTesting
+    public String get(String namespace, String name) {
+        return mSettingsProxy.getString(getPropName(namespace, name));
+    }
+
+    /** Erase a value from the store. */
+    @VisibleForTesting
+    public void erase(String namespace, String name) {
+        set(namespace, name, null);
+    }
+
+    Map<String, Map<String, String>> getFlags() {
+        return getFlagsForNamespace(null);
+    }
+
+    Map<String, Map<String, String>> getFlagsForNamespace(String namespace) {
+        Cursor c = mSettingsProxy.getContentResolver().query(
+                Settings.Global.CONTENT_URI,
+                new String[]{Settings.NameValueTable.NAME, Settings.NameValueTable.VALUE},
+                null, // Doesn't support a "LIKE" query
+                null,
+                null
+        );
+
+        if (c == null) {
+            return Map.of();
+        }
+        int keynamePrefixLength = KEYNAME_PREFIX.length();
+        Map<String, Map<String, String>> results = new HashMap<>();
+        while (c.moveToNext()) {
+            String key = c.getString(0);
+            if (!key.startsWith(KEYNAME_PREFIX)
+                    || key.indexOf(NAMESPACE_NAME_SEPARATOR, keynamePrefixLength) < 0) {
+                continue;
+            }
+            String value = c.getString(1);
+            if (value == null || value.isEmpty()) {
+                continue;
+            }
+            String ns = key.substring(keynamePrefixLength, key.indexOf(NAMESPACE_NAME_SEPARATOR));
+            if (namespace != null && !namespace.equals(ns)) {
+                continue;
+            }
+            String name = key.substring(key.indexOf(NAMESPACE_NAME_SEPARATOR) + 1);
+            results.putIfAbsent(ns, new HashMap<>());
+            results.get(ns).put(name, value);
+        }
+        c.close();
+        return results;
+    }
+
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    static String getPropName(String namespace, String name) {
+        return KEYNAME_PREFIX + namespace + NAMESPACE_NAME_SEPARATOR + name;
+    }
+
+    interface FlagChangeCallback {
+        void onFlagChanged(String namespace, String name, String value);
+    }
+}
diff --git a/services/flags/java/com/android/server/flags/FlagsShellCommand.java b/services/flags/java/com/android/server/flags/FlagsShellCommand.java
new file mode 100644
index 0000000..b7896ee
--- /dev/null
+++ b/services/flags/java/com/android/server/flags/FlagsShellCommand.java
@@ -0,0 +1,214 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.flags;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.FastPrintWriter;
+
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.util.Locale;
+import java.util.Map;
+
+/**
+ * Process command line input for the flags service.
+ */
+@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+public class FlagsShellCommand {
+    private final FlagOverrideStore mFlagStore;
+
+    FlagsShellCommand(FlagOverrideStore flagStore) {
+        mFlagStore = flagStore;
+    }
+
+    /**
+     * Interpret the command supplied in the constructor.
+     *
+     * @return Zero on success or non-zero on error.
+     */
+    public int process(
+            String[] args,
+            OutputStream out,
+            OutputStream err) {
+        PrintWriter outPw = new FastPrintWriter(out);
+        PrintWriter errPw = new FastPrintWriter(err);
+
+        if (args.length == 0) {
+            return printHelp(outPw);
+        }
+        switch (args[0].toLowerCase(Locale.ROOT)) {
+            case "help":
+                return printHelp(outPw);
+            case "list":
+                return listCmd(args, outPw, errPw);
+            case "set":
+                return setCmd(args, outPw, errPw);
+            case "get":
+                return getCmd(args, outPw, errPw);
+            case "erase":
+                return eraseCmd(args, outPw, errPw);
+            default:
+                return unknownCmd(outPw);
+        }
+    }
+
+    private int printHelp(PrintWriter outPw) {
+        outPw.println("Feature Flags command, allowing listing, setting, getting, and erasing of");
+        outPw.println("local flag overrides on a device.");
+        outPw.println();
+        outPw.println("Commands:");
+        outPw.println("  list [namespace]");
+        outPw.println("    List all flag overrides. Namespace is optional.");
+        outPw.println();
+        outPw.println("  get <namespace> <name>");
+        outPw.println("    Return the string value of a specific flag, or <unset>");
+        outPw.println();
+        outPw.println("  set <namespace> <name> <value>");
+        outPw.println("    Set a specific flag");
+        outPw.println();
+        outPw.println("  erase <namespace> <name>");
+        outPw.println("    Unset a specific flag");
+        outPw.flush();
+        return 0;
+    }
+
+    private int listCmd(String[] args, PrintWriter outPw, PrintWriter errPw) {
+        if (!validateNumArguments(args, 0, 1, args[0], errPw)) {
+            errPw.println("Expected `" + args[0] + " [namespace]`");
+            errPw.flush();
+            return -1;
+        }
+        Map<String, Map<String, String>> overrides;
+        if (args.length == 2) {
+            overrides = mFlagStore.getFlagsForNamespace(args[1]);
+        } else {
+            overrides = mFlagStore.getFlags();
+        }
+        if (overrides.isEmpty()) {
+            outPw.println("No overrides set");
+        } else {
+            int longestNamespaceLen = "namespace".length();
+            int longestFlagLen = "flag".length();
+            int longestValLen = "value".length();
+            for (Map.Entry<String, Map<String, String>> namespace : overrides.entrySet()) {
+                longestNamespaceLen = Math.max(longestNamespaceLen, namespace.getKey().length());
+                for (Map.Entry<String, String> flag : namespace.getValue().entrySet()) {
+                    longestFlagLen = Math.max(longestFlagLen, flag.getKey().length());
+                    longestValLen = Math.max(longestValLen, flag.getValue().length());
+                }
+            }
+            outPw.print(String.format("%-" + longestNamespaceLen + "s", "namespace"));
+            outPw.print(' ');
+            outPw.print(String.format("%-" + longestFlagLen + "s", "flag"));
+            outPw.print(' ');
+            outPw.println("value");
+            for (int i = 0; i < longestNamespaceLen; i++) {
+                outPw.print('=');
+            }
+            outPw.print(' ');
+            for (int i = 0; i < longestFlagLen; i++) {
+                outPw.print('=');
+            }
+            outPw.print(' ');
+            for (int i = 0; i < longestValLen; i++) {
+                outPw.print('=');
+            }
+            outPw.println();
+            for (Map.Entry<String, Map<String, String>> namespace : overrides.entrySet()) {
+                for (Map.Entry<String, String> flag : namespace.getValue().entrySet()) {
+                    outPw.print(
+                            String.format("%-" + longestNamespaceLen + "s", namespace.getKey()));
+                    outPw.print(' ');
+                    outPw.print(String.format("%-" + longestFlagLen + "s", flag.getKey()));
+                    outPw.print(' ');
+                    outPw.println(flag.getValue());
+                }
+            }
+        }
+        outPw.flush();
+        return 0;
+    }
+
+    private int setCmd(String[] args, PrintWriter outPw, PrintWriter errPw) {
+        if (!validateNumArguments(args, 3, args[0], errPw)) {
+            errPw.println("Expected `" + args[0] + " <namespace> <name> <value>`");
+            errPw.flush();
+            return -1;
+        }
+        mFlagStore.set(args[1], args[2], args[3]);
+        outPw.println("Flag " + args[1] + "." + args[2] + " is now " + args[3]);
+        outPw.flush();
+        return 0;
+    }
+
+    private int getCmd(String[] args, PrintWriter outPw, PrintWriter errPw) {
+        if (!validateNumArguments(args, 2, args[0], errPw)) {
+            errPw.println("Expected `" + args[0] + " <namespace> <name>`");
+            errPw.flush();
+            return -1;
+        }
+
+        String value = mFlagStore.get(args[1], args[2]);
+        outPw.print(args[1] + "." + args[2] + " is ");
+        if (value == null || value.isEmpty()) {
+            outPw.println("<unset>");
+        } else {
+            outPw.println("\"" + value.translateEscapes() + "\"");
+        }
+        outPw.flush();
+        return 0;
+    }
+
+    private int eraseCmd(String[] args, PrintWriter outPw, PrintWriter errPw) {
+        if (!validateNumArguments(args, 2, args[0], errPw)) {
+            errPw.println("Expected `" + args[0] + " <namespace> <name>`");
+            errPw.flush();
+            return -1;
+        }
+        mFlagStore.erase(args[1], args[2]);
+        outPw.println("Erased " + args[1] + "." + args[2]);
+        return 0;
+    }
+
+    private int unknownCmd(PrintWriter outPw) {
+        outPw.println("This command is unknown.");
+        printHelp(outPw);
+        outPw.flush();
+        return -1;
+    }
+
+    private boolean validateNumArguments(
+            String[] args, int exactly, String cmdName, PrintWriter errPw) {
+        return validateNumArguments(args, exactly, exactly, cmdName, errPw);
+    }
+
+    private boolean validateNumArguments(
+            String[] args, int min, int max, String cmdName, PrintWriter errPw) {
+        int len = args.length - 1; // Discount the command itself.
+        if (len < min) {
+            errPw.println(
+                    "Less than " + min + " arguments provided for \"" + cmdName + "\" command.");
+            return false;
+        } else if (len > max) {
+            errPw.println(
+                    "More than " + max + " arguments provided for \"" + cmdName + "\" command.");
+            return false;
+        }
+
+        return true;
+    }
+}
diff --git a/services/flags/java/com/android/server/flags/GlobalSettingsProxy.java b/services/flags/java/com/android/server/flags/GlobalSettingsProxy.java
new file mode 100644
index 0000000..acb7bb5
--- /dev/null
+++ b/services/flags/java/com/android/server/flags/GlobalSettingsProxy.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.flags;
+
+import android.content.ContentResolver;
+import android.net.Uri;
+import android.provider.Settings;
+
+class GlobalSettingsProxy implements SettingsProxy {
+    private final ContentResolver mContentResolver;
+
+    GlobalSettingsProxy(ContentResolver contentResolver) {
+        mContentResolver = contentResolver;
+    }
+
+    @Override
+    public ContentResolver getContentResolver() {
+        return mContentResolver;
+    }
+
+    @Override
+    public Uri getUriFor(String name) {
+        return Settings.Global.getUriFor(name);
+    }
+
+    @Override
+    public String getStringForUser(String name, int userHandle) {
+        return Settings.Global.getStringForUser(mContentResolver, name, userHandle);
+    }
+
+    @Override
+    public boolean putString(String name, String value, boolean overrideableByRestore) {
+        throw new UnsupportedOperationException(
+                "This method only exists publicly for Settings.System and Settings.Secure");
+    }
+
+    @Override
+    public boolean putStringForUser(String name, String value, int userHandle) {
+        return Settings.Global.putStringForUser(mContentResolver, name, value, userHandle);
+    }
+
+    @Override
+    public boolean putStringForUser(String name, String value, String tag, boolean makeDefault,
+            int userHandle, boolean overrideableByRestore) {
+        return Settings.Global.putStringForUser(
+                mContentResolver, name, value, tag, makeDefault, userHandle,
+                overrideableByRestore);
+    }
+
+    @Override
+    public boolean putString(String name, String value, String tag, boolean makeDefault) {
+        return Settings.Global.putString(mContentResolver, name, value, tag, makeDefault);
+    }
+}
diff --git a/services/flags/java/com/android/server/flags/SettingsProxy.java b/services/flags/java/com/android/server/flags/SettingsProxy.java
new file mode 100644
index 0000000..c6e85d5
--- /dev/null
+++ b/services/flags/java/com/android/server/flags/SettingsProxy.java
@@ -0,0 +1,381 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.flags;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.content.ContentResolver;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.provider.Settings;
+
+/**
+ * Wrapper class meant to enable hermetic testing of {@link Settings}.
+ *
+ * Implementations of this class are expected to be constructed with a {@link ContentResolver} or,
+ * otherwise have access to an implicit one. All the proxy methods in this class exclude
+ * {@link ContentResolver} from their signature and rely on an internally defined one instead.
+ *
+ * Most methods in the {@link Settings} classes have default implementations defined.
+ * Implementations of this interfac need only concern themselves with getting and putting Strings.
+ * They should also override any methods for a class they are proxying that _are not_ defined, and
+ * throw an appropriate {@link UnsupportedOperationException}. For instance, {@link Settings.Global}
+ * does not define {@link #putString(String, String, boolean)}, so an implementation of this
+ * interface that proxies through to it should throw an exception when that method is called.
+ *
+ * This class adds in the following helpers as well:
+ *  - {@link #getBool(String)}
+ *  - {@link #putBool(String, boolean)}
+ *  - {@link #registerContentObserver(Uri, ContentObserver)}
+ *
+ * ... and similar variations for all of those.
+ */
+public interface SettingsProxy {
+
+    /**
+     * Returns the {@link ContentResolver} this instance uses.
+     */
+    ContentResolver getContentResolver();
+
+    /**
+     * Construct the content URI for a particular name/value pair,
+     * useful for monitoring changes with a ContentObserver.
+     * @param name to look up in the table
+     * @return the corresponding content URI, or null if not present
+     */
+    Uri getUriFor(String name);
+
+    /**See {@link Settings.Secure#getString(ContentResolver, String)} */
+    String getStringForUser(String name, int userHandle);
+
+    /**See {@link Settings.Secure#putString(ContentResolver, String, String, boolean)} */
+    boolean putString(String name, String value, boolean overrideableByRestore);
+
+    /** See {@link Settings.Secure#putStringForUser(ContentResolver, String, String, int)} */
+    boolean putStringForUser(String name, String value, int userHandle);
+
+    /**
+     * See {@link Settings.Secure#putStringForUser(ContentResolver, String, String, String, boolean,
+     * int, boolean)}
+     */
+    boolean putStringForUser(@NonNull String name, @Nullable String value, @Nullable String tag,
+            boolean makeDefault, @UserIdInt int userHandle, boolean overrideableByRestore);
+
+    /** See {@link Settings.Secure#putString(ContentResolver, String, String, String, boolean)} */
+    boolean putString(@NonNull String name, @Nullable String value, @Nullable String tag,
+            boolean makeDefault);
+
+    /**
+     * Returns the user id for the associated {@link ContentResolver}.
+     */
+    default int getUserId() {
+        return getContentResolver().getUserId();
+    }
+
+    /** See {@link Settings.Secure#getString(ContentResolver, String)} */
+    default String getString(String name) {
+        return getStringForUser(name, getUserId());
+    }
+
+    /** See {@link Settings.Secure#putString(ContentResolver, String, String)} */
+    default boolean putString(String name, String value) {
+        return putStringForUser(name, value, getUserId());
+    }
+    /** See {@link Settings.Secure#getIntForUser(ContentResolver, String, int, int)} */
+    default int getIntForUser(String name, int def, int userHandle) {
+        String v = getStringForUser(name, userHandle);
+        try {
+            return v != null ? Integer.parseInt(v) : def;
+        } catch (NumberFormatException e) {
+            return def;
+        }
+    }
+
+    /** See {@link Settings.Secure#getInt(ContentResolver, String)}  */
+    default int getInt(String name) throws Settings.SettingNotFoundException {
+        return getIntForUser(name, getUserId());
+    }
+
+    /** See {@link Settings.Secure#getIntForUser(ContentResolver, String, int)} */
+    default int getIntForUser(String name, int userHandle)
+            throws Settings.SettingNotFoundException {
+        String v = getStringForUser(name, userHandle);
+        try {
+            return Integer.parseInt(v);
+        } catch (NumberFormatException e) {
+            throw new Settings.SettingNotFoundException(name);
+        }
+    }
+
+    /** See {@link Settings.Secure#putInt(ContentResolver, String, int)} */
+    default boolean putInt(String name, int value) {
+        return putIntForUser(name, value, getUserId());
+    }
+
+    /** See {@link Settings.Secure#putIntForUser(ContentResolver, String, int, int)} */
+    default boolean putIntForUser(String name, int value, int userHandle) {
+        return putStringForUser(name, Integer.toString(value), userHandle);
+    }
+
+    /**
+     * Convenience function for retrieving a single settings value
+     * as a boolean.  Note that internally setting values are always
+     * stored as strings; this function converts the string to a boolean
+     * for you. The default value will be returned if the setting is
+     * not defined or not a boolean.
+     *
+     * @param name The name of the setting to retrieve.
+     * @param def Value to return if the setting is not defined.
+     *
+     * @return The setting's current value, or 'def' if it is not defined
+     * or not a valid boolean.
+     */
+    default boolean getBool(String name, boolean def) {
+        return getBoolForUser(name, def, getUserId());
+    }
+
+    /** See {@link #getBool(String, boolean)}. */
+    default boolean getBoolForUser(String name, boolean def, int userHandle) {
+        return getIntForUser(name, def ? 1 : 0, userHandle) != 0;
+    }
+
+    /**
+     * Convenience function for retrieving a single settings value
+     * as a boolean.  Note that internally setting values are always
+     * stored as strings; this function converts the string to a boolean
+     * for you.
+     * <p>
+     * This version does not take a default value.  If the setting has not
+     * been set, or the string value is not a number,
+     * it throws {@link Settings.SettingNotFoundException}.
+     *
+     * @param name The name of the setting to retrieve.
+     *
+     * @throws Settings.SettingNotFoundException Thrown if a setting by the given
+     * name can't be found or the setting value is not a boolean.
+     *
+     * @return The setting's current value.
+     */
+    default boolean getBool(String name) throws Settings.SettingNotFoundException {
+        return getBoolForUser(name, getUserId());
+    }
+
+    /** See {@link #getBool(String)}. */
+    default boolean getBoolForUser(String name, int userHandle)
+            throws Settings.SettingNotFoundException {
+        return getIntForUser(name, userHandle) != 0;
+    }
+
+    /**
+     * Convenience function for updating a single settings value as a
+     * boolean. This will either create a new entry in the table if the
+     * given name does not exist, or modify the value of the existing row
+     * with that name.  Note that internally setting values are always
+     * stored as strings, so this function converts the given value to a
+     * string before storing it.
+     *
+     * @param name The name of the setting to modify.
+     * @param value The new value for the setting.
+     * @return true if the value was set, false on database errors
+     */
+    default boolean putBool(String name, boolean value) {
+        return putBoolForUser(name, value, getUserId());
+    }
+
+    /** See {@link #putBool(String, boolean)}. */
+    default boolean putBoolForUser(String name, boolean value, int userHandle) {
+        return putIntForUser(name, value ? 1 : 0, userHandle);
+    }
+
+    /** See {@link Settings.Secure#getLong(ContentResolver, String, long)}  */
+    default long getLong(String name, long def) {
+        return getLongForUser(name, def, getUserId());
+    }
+
+    /** See {@link Settings.Secure#getLongForUser(ContentResolver, String, long, int)}  */
+    default long getLongForUser(String name, long def, int userHandle) {
+        String valString = getStringForUser(name, userHandle);
+        long value;
+        try {
+            value = valString != null ? Long.parseLong(valString) : def;
+        } catch (NumberFormatException e) {
+            value = def;
+        }
+        return value;
+    }
+
+    /** See {@link Settings.Secure#getLong(ContentResolver, String)}  */
+    default long getLong(String name) throws Settings.SettingNotFoundException {
+        return getLongForUser(name, getUserId());
+    }
+
+    /** See {@link Settings.Secure#getLongForUser(ContentResolver, String, int)}  */
+    default long getLongForUser(String name, int userHandle)
+            throws Settings.SettingNotFoundException {
+        String valString = getStringForUser(name, userHandle);
+        try {
+            return Long.parseLong(valString);
+        } catch (NumberFormatException e) {
+            throw new Settings.SettingNotFoundException(name);
+        }
+    }
+
+    /** See {@link Settings.Secure#putLong(ContentResolver, String, long)} */
+    default boolean putLong(String name, long value) {
+        return putLongForUser(name, value, getUserId());
+    }
+
+    /** See {@link Settings.Secure#putLongForUser(ContentResolver, String, long, int)}  */
+    default boolean putLongForUser(String name, long value, int userHandle) {
+        return putStringForUser(name, Long.toString(value), userHandle);
+    }
+
+    /** See {@link Settings.Secure#getFloat(ContentResolver, String, float)} */
+    default float getFloat(String name, float def) {
+        return getFloatForUser(name, def, getUserId());
+    }
+
+    /** See {@link Settings.Secure#getFloatForUser(ContentResolver, String, int)} */
+    default float getFloatForUser(String name, float def, int userHandle) {
+        String v = getStringForUser(name, userHandle);
+        try {
+            return v != null ? Float.parseFloat(v) : def;
+        } catch (NumberFormatException e) {
+            return def;
+        }
+    }
+
+
+    /** See {@link Settings.Secure#getFloat(ContentResolver, String)}  */
+    default float getFloat(String name) throws Settings.SettingNotFoundException {
+        return getFloatForUser(name, getUserId());
+    }
+
+    /** See {@link Settings.Secure#getFloatForUser(ContentResolver, String, int)}   */
+    default float getFloatForUser(String name, int userHandle)
+            throws Settings.SettingNotFoundException {
+        String v = getStringForUser(name, userHandle);
+        if (v == null) {
+            throw new Settings.SettingNotFoundException(name);
+        }
+        try {
+            return Float.parseFloat(v);
+        } catch (NumberFormatException e) {
+            throw new Settings.SettingNotFoundException(name);
+        }
+    }
+
+    /** See {@link Settings.Secure#putFloat(ContentResolver, String, float)} */
+    default boolean putFloat(String name, float value) {
+        return putFloatForUser(name, value, getUserId());
+    }
+
+    /** See {@link Settings.Secure#putFloatForUser(ContentResolver, String, float, int)} */
+    default boolean putFloatForUser(String name, float value, int userHandle) {
+        return putStringForUser(name, Float.toString(value), userHandle);
+    }
+
+    /**
+     * Convenience wrapper around
+     * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver)}.'
+     *
+     * Implicitly calls {@link #getUriFor(String)} on the passed in name.
+     */
+    default void registerContentObserver(String name, ContentObserver settingsObserver) {
+        registerContentObserver(getUriFor(name), settingsObserver);
+    }
+
+    /**
+     * Convenience wrapper around
+     * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver)}.'
+     */
+    default void registerContentObserver(Uri uri, ContentObserver settingsObserver) {
+        registerContentObserverForUser(uri, settingsObserver, getUserId());
+    }
+
+    /**
+     * Convenience wrapper around
+     * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver)}.
+     *
+     * Implicitly calls {@link #getUriFor(String)} on the passed in name.
+     */
+    default void registerContentObserver(String name, boolean notifyForDescendants,
+            ContentObserver settingsObserver) {
+        registerContentObserver(getUriFor(name), notifyForDescendants, settingsObserver);
+    }
+
+    /**
+     * Convenience wrapper around
+     * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver)}.'
+     */
+    default void registerContentObserver(Uri uri, boolean notifyForDescendants,
+            ContentObserver settingsObserver) {
+        registerContentObserverForUser(uri, notifyForDescendants, settingsObserver, getUserId());
+    }
+
+    /**
+     * Convenience wrapper around
+     * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver, int)}
+     *
+     * Implicitly calls {@link #getUriFor(String)} on the passed in name.
+     */
+    default void registerContentObserverForUser(
+            String name, ContentObserver settingsObserver, int userHandle) {
+        registerContentObserverForUser(
+                getUriFor(name), settingsObserver, userHandle);
+    }
+
+    /**
+     * Convenience wrapper around
+     * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver, int)}
+     */
+    default void registerContentObserverForUser(
+            Uri uri, ContentObserver settingsObserver, int userHandle) {
+        registerContentObserverForUser(
+                uri, false, settingsObserver, userHandle);
+    }
+
+    /**
+     * Convenience wrapper around
+     * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver, int)}
+     *
+     * Implicitly calls {@link #getUriFor(String)} on the passed in name.
+     */
+    default void registerContentObserverForUser(
+            String name, boolean notifyForDescendants, ContentObserver settingsObserver,
+            int userHandle) {
+        registerContentObserverForUser(
+                getUriFor(name), notifyForDescendants, settingsObserver, userHandle);
+    }
+
+    /**
+     * Convenience wrapper around
+     * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver, int)}
+     */
+    default void registerContentObserverForUser(
+            Uri uri, boolean notifyForDescendants, ContentObserver settingsObserver,
+            int userHandle) {
+        getContentResolver().registerContentObserver(
+                uri, notifyForDescendants, settingsObserver, userHandle);
+    }
+
+    /** See {@link ContentResolver#unregisterContentObserver(ContentObserver)}. */
+    default void unregisterContentObserver(ContentObserver settingsObserver) {
+        getContentResolver().unregisterContentObserver(settingsObserver);
+    }
+}
diff --git a/services/java/com/android/server/HsumBootUserInitializer.java b/services/java/com/android/server/HsumBootUserInitializer.java
index b895812..00396e2 100644
--- a/services/java/com/android/server/HsumBootUserInitializer.java
+++ b/services/java/com/android/server/HsumBootUserInitializer.java
@@ -63,28 +63,32 @@
 
     /** Whether this device should always have a non-removable MainUser, including at first boot. */
     private final boolean mShouldAlwaysHaveMainUser;
+    /** Whether this device should have a communal profile created at first boot. */
+    private final boolean mShouldAlwaysHaveCommunalProfile;
 
     /** Static factory method for creating a {@link HsumBootUserInitializer} instance. */
     public static @Nullable HsumBootUserInitializer createInstance(ActivityManagerService am,
             PackageManagerService pms, ContentResolver contentResolver,
-            boolean shouldAlwaysHaveMainUser) {
+            boolean shouldAlwaysHaveMainUser, boolean shouldAlwaysHaveCommunalProfile) {
 
         if (!UserManager.isHeadlessSystemUserMode()) {
             return null;
         }
         return new HsumBootUserInitializer(
                 LocalServices.getService(UserManagerInternal.class),
-                am, pms, contentResolver, shouldAlwaysHaveMainUser);
+                am, pms, contentResolver,
+                shouldAlwaysHaveMainUser, shouldAlwaysHaveCommunalProfile);
     }
 
     private HsumBootUserInitializer(UserManagerInternal umi, ActivityManagerService am,
             PackageManagerService pms, ContentResolver contentResolver,
-            boolean shouldAlwaysHaveMainUser) {
+            boolean shouldAlwaysHaveMainUser, boolean shouldAlwaysHaveCommunalProfile) {
         mUmi = umi;
         mAms = am;
         mPms = pms;
         mContentResolver = contentResolver;
         mShouldAlwaysHaveMainUser = shouldAlwaysHaveMainUser;
+        mShouldAlwaysHaveCommunalProfile = shouldAlwaysHaveCommunalProfile;
     }
 
     /**
@@ -102,6 +106,11 @@
             createMainUserIfNeeded();
             t.traceEnd();
         }
+        if (mShouldAlwaysHaveCommunalProfile) {
+            t.traceBegin("createCommunalProfileIfNeeded");
+            createCommunalProfileIfNeeded();
+            t.traceEnd();
+        }
     }
 
     private void createMainUserIfNeeded() {
@@ -125,6 +134,25 @@
         }
     }
 
+    private void createCommunalProfileIfNeeded() {
+        final int communalProfile = mUmi.getCommunalProfileId();
+        if (communalProfile != UserHandle.USER_NULL) {
+            Slogf.d(TAG, "Found existing Communal Profile, userId=%d", communalProfile);
+            return;
+        }
+
+        Slogf.d(TAG, "Creating a new Communal Profile");
+        try {
+            final UserInfo newProfile = mUmi.createUserEvenWhenDisallowed(
+                    /* name= */ null,  // TODO: Create Communal Profile string name
+                    UserManager.USER_TYPE_PROFILE_COMMUNAL,
+                    /* flags= */ 0, /* disallowedPackages= */ null, /* token= */ null);
+            Slogf.i(TAG, "Successfully created Communal Profile, userId=%d", newProfile.id);
+        } catch (UserManager.CheckedUserOperationException e) {
+            Slogf.wtf(TAG, "Communal Profile creation failed", e);
+        }
+    }
+
     /**
      * Put the device into the correct user state: unlock the system and switch to the boot user.
      *
@@ -148,6 +176,48 @@
         }
     }
 
+    /**
+     * Handles any final initialization once the system is already ready.
+     *
+     * <p>Should only call after {@link ActivityManagerService#systemReady} is completed.
+     */
+    public void postSystemReady(TimingsTraceAndSlog t) {
+        if (mShouldAlwaysHaveCommunalProfile) {
+            startCommunalProfile(t);
+        } else {
+            // As a safeguard, disabling the Communal Profile configuration (or SystemProperty) will
+            // purposefully trigger the removal of the Communal Profile at boot time.
+            removeCommunalProfileIfNeeded();
+        }
+    }
+
+    private void startCommunalProfile(TimingsTraceAndSlog t) {
+        final int communalProfileId = mUmi.getCommunalProfileId();
+        if (communalProfileId != UserHandle.USER_NULL) {
+            Slogf.d(TAG, "Starting the Communal Profile");
+            t.traceBegin("startCommunalProfile-" + communalProfileId);
+            final boolean started = mAms.startProfile(communalProfileId);
+            if (!started) {
+                Slogf.wtf(TAG, "Failed to start communal profile userId=%d", communalProfileId);
+            }
+            t.traceEnd();
+        } else {
+            Slogf.w(TAG, "Cannot start Communal Profile because there isn't one");
+        }
+    }
+
+    private void removeCommunalProfileIfNeeded() {
+        final int communalProfile = mUmi.getCommunalProfileId();
+        if (communalProfile == UserHandle.USER_NULL) {
+            return;
+        }
+        Slogf.d(TAG, "Removing existing Communal Profile, userId=%d", communalProfile);
+        final boolean removeSucceeded = mUmi.removeUserEvenWhenDisallowed(communalProfile);
+        if (!removeSucceeded) {
+            Slogf.e(TAG, "Failed to Communal Profile, userId=%d", communalProfile);
+        }
+    }
+
     private void observeDeviceProvisioning() {
         if (isDeviceProvisioned()) {
             return;
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index af84180..ee4bc12 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -51,7 +51,6 @@
 import android.database.sqlite.SQLiteCompatibilityWalFlags;
 import android.database.sqlite.SQLiteGlobal;
 import android.graphics.GraphicsStatsService;
-import android.graphics.Typeface;
 import android.hardware.display.DisplayManagerInternal;
 import android.net.ConnectivityManager;
 import android.net.ConnectivityModuleConnector;
@@ -76,6 +75,7 @@
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.os.storage.IStorageManager;
 import android.provider.DeviceConfig;
 import android.provider.Settings;
@@ -106,8 +106,9 @@
 import com.android.internal.widget.ILockSettings;
 import com.android.internal.widget.LockSettingsInternal;
 import com.android.server.am.ActivityManagerService;
-import com.android.server.ambientcontext.AmbientContextManagerService;
 import com.android.server.appbinding.AppBindingService;
+import com.android.server.appop.AppOpMigrationHelper;
+import com.android.server.appop.AppOpMigrationHelperImpl;
 import com.android.server.art.ArtModuleServiceInitializer;
 import com.android.server.art.DexUseManagerLocal;
 import com.android.server.attention.AttentionManagerService;
@@ -132,6 +133,7 @@
 import com.android.server.display.color.ColorDisplayService;
 import com.android.server.dreams.DreamManagerService;
 import com.android.server.emergency.EmergencyAffordanceService;
+import com.android.server.flags.FeatureFlagsService;
 import com.android.server.gpu.GpuService;
 import com.android.server.grammaticalinflection.GrammaticalInflectionService;
 import com.android.server.graphics.fonts.FontManagerService;
@@ -174,6 +176,8 @@
 import com.android.server.pm.ShortcutService;
 import com.android.server.pm.UserManagerService;
 import com.android.server.pm.dex.OdsignStatsLogger;
+import com.android.server.pm.permission.PermissionMigrationHelper;
+import com.android.server.pm.permission.PermissionMigrationHelperImpl;
 import com.android.server.pm.verify.domain.DomainVerificationService;
 import com.android.server.policy.AppOpsPolicy;
 import com.android.server.policy.PermissionPolicyService;
@@ -322,16 +326,20 @@
             "com.android.clockwork.power.WearPowerService";
     private static final String HEALTH_SERVICE_CLASS =
             "com.android.clockwork.healthservices.HealthService";
-    private static final String WEAR_SIDEKICK_SERVICE_CLASS =
-            "com.google.android.clockwork.sidekick.SidekickService";
+    private static final String SYSTEM_STATE_DISPLAY_SERVICE_CLASS =
+            "com.android.clockwork.systemstatedisplay.SystemStateDisplayService";
     private static final String WEAR_DISPLAYOFFLOAD_SERVICE_CLASS =
             "com.android.clockwork.displayoffload.DisplayOffloadService";
+    private static final String WEAR_MODE_SERVICE_CLASS =
+            "com.android.clockwork.modes.ModeManagerService";
     private static final String WEAR_DISPLAY_SERVICE_CLASS =
             "com.android.clockwork.display.WearDisplayService";
     private static final String WEAR_TIME_SERVICE_CLASS =
             "com.android.clockwork.time.WearTimeService";
-    private static final String WEAR_GLOBAL_ACTIONS_SERVICE_CLASS =
-            "com.android.clockwork.globalactions.GlobalActionsService";
+    private static final String WEAR_SETTINGS_SERVICE_CLASS =
+            "com.android.clockwork.settings.WearSettingsService";
+    private static final String WRIST_ORIENTATION_SERVICE_CLASS =
+            "com.android.clockwork.wristorientation.WristOrientationService";
     private static final String ACCOUNT_SERVICE_CLASS =
             "com.android.server.accounts.AccountManagerService$Lifecycle";
     private static final String CONTENT_SERVICE_CLASS =
@@ -350,6 +358,8 @@
             "com.android.server.selectiontoolbar.SelectionToolbarManagerService";
     private static final String MUSIC_RECOGNITION_MANAGER_SERVICE_CLASS =
             "com.android.server.musicrecognition.MusicRecognitionManagerService";
+    private static final String AMBIENT_CONTEXT_MANAGER_SERVICE_CLASS =
+            "com.android.server.ambientcontext.AmbientContextManagerService";
     private static final String SYSTEM_CAPTIONS_MANAGER_SERVICE_CLASS =
             "com.android.server.systemcaptions.SystemCaptionsManagerService";
     private static final String TEXT_TO_SPEECH_MANAGER_SERVICE_CLASS =
@@ -434,6 +444,10 @@
                     + "OnDevicePersonalizationSystemService$Lifecycle";
     private static final String UPDATABLE_DEVICE_CONFIG_SERVICE_CLASS =
             "com.android.server.deviceconfig.DeviceConfigInit$Lifecycle";
+    private static final String DEVICE_LOCK_SERVICE_CLASS =
+            "com.android.server.devicelock.DeviceLockService";
+    private static final String DEVICE_LOCK_APEX_PATH =
+            "/apex/com.android.devicelock/javalib/service-devicelock.jar";
 
     private static final String TETHERING_CONNECTOR_CLASS = "android.net.ITetheringConnector";
 
@@ -902,13 +916,6 @@
             SystemServerInitThreadPool tp = SystemServerInitThreadPool.start();
             mDumper.addDumpable(tp);
 
-            // Load preinstalled system fonts for system server, so that WindowManagerService, etc
-            // can start using Typeface. Note that fonts are required not only for text rendering,
-            // but also for some text operations (e.g. TextUtils.makeSafeForPresentation()).
-            if (Typeface.ENABLE_LAZY_TYPEFACE_INITIALIZATION) {
-                Typeface.loadPreinstalledSystemFontMap();
-            }
-
             // Attach JVMTI agent if this is a debuggable build and the system property is set.
             if (Build.IS_DEBUGGABLE) {
                 // Property is of the form "library_path=parameters".
@@ -1111,6 +1118,12 @@
         mSystemServiceManager.startService(DeviceIdentifiersPolicyService.class);
         t.traceEnd();
 
+        // Starts a service for reading runtime flag overrides, and keeping processes
+        // in sync with one another.
+        t.traceBegin("StartFeatureFlagsService");
+        mSystemServiceManager.startService(FeatureFlagsService.class);
+        t.traceEnd();
+
         // Uri Grants Manager.
         t.traceBegin("UriGrantsManagerService");
         mSystemServiceManager.startService(UriGrantsManagerService.Lifecycle.class);
@@ -1133,6 +1146,10 @@
 
         // Start AccessCheckingService which provides new implementation for permission and app op.
         t.traceBegin("StartAccessCheckingService");
+        LocalServices.addService(PermissionMigrationHelper.class,
+                new PermissionMigrationHelperImpl());
+        LocalServices.addService(AppOpMigrationHelper.class,
+                new AppOpMigrationHelperImpl());
         mSystemServiceManager.startService(AccessCheckingService.class);
         t.traceEnd();
 
@@ -1204,13 +1221,6 @@
         }
         t.traceEnd();
 
-        t.traceBegin("StartSidekickService");
-        // Package manager isn't started yet; need to use SysProp not hardware feature
-        if (SystemProperties.getBoolean("config.enable_sidekick_graphics", false)) {
-            mSystemServiceManager.startService(WEAR_SIDEKICK_SERVICE_CLASS);
-        }
-        t.traceEnd();
-
         // Display manager is needed to provide display metrics before package manager
         // starts up.
         t.traceBegin("StartDisplayManager");
@@ -1560,9 +1570,11 @@
             mSystemServiceManager.startService(ROLE_SERVICE_CLASS);
             t.traceEnd();
 
-            t.traceBegin("StartVibratorManagerService");
-            mSystemServiceManager.startService(VibratorManagerService.Lifecycle.class);
-            t.traceEnd();
+            if (!isTv) {
+                t.traceBegin("StartVibratorManagerService");
+                mSystemServiceManager.startService(VibratorManagerService.Lifecycle.class);
+                t.traceEnd();
+            }
 
             t.traceBegin("StartDynamicSystemService");
             dynamicSystem = new DynamicSystemService(context);
@@ -1875,9 +1887,7 @@
             t.traceBegin("StartStatusBarManagerService");
             try {
                 statusBar = new StatusBarManagerService(context);
-                if (!isWatch) {
-                    statusBar.publishGlobalActionsProvider();
-                }
+                statusBar.publishGlobalActionsProvider();
                 ServiceManager.addService(Context.STATUS_BAR_SERVICE, statusBar, false,
                         DUMP_FLAG_PRIORITY_NORMAL | DUMP_FLAG_PROTO);
             } catch (Throwable e) {
@@ -1900,9 +1910,17 @@
             startRotationResolverService(context, t);
             startSystemCaptionsManagerService(context, t);
             startTextToSpeechManagerService(context, t);
-            startAmbientContextService(t);
             startWearableSensingService(t);
 
+            if (deviceHasConfigString(
+                    context, R.string.config_defaultAmbientContextDetectionService)) {
+                t.traceBegin("StartAmbientContextService");
+                mSystemServiceManager.startService(AMBIENT_CONTEXT_MANAGER_SERVICE_CLASS);
+                t.traceEnd();
+            } else {
+                Slog.d(TAG, "AmbientContextManagerService not defined by OEM or disabled by flag");
+            }
+
             // System Speech Recognition Service
             t.traceBegin("StartSpeechRecognitionManagerService");
             mSystemServiceManager.startService(SPEECH_RECOGNITION_MANAGER_SERVICE_CLASS);
@@ -1927,16 +1945,20 @@
             }
 
             // Search UI manager service
-            // TODO: add deviceHasConfigString(context, R.string.config_defaultSearchUiService)
-            t.traceBegin("StartSearchUiService");
-            mSystemServiceManager.startService(SEARCH_UI_MANAGER_SERVICE_CLASS);
-            t.traceEnd();
+            if (deviceHasConfigString(context, R.string.config_defaultSearchUiService)) {
+                t.traceBegin("StartSearchUiService");
+                mSystemServiceManager.startService(SEARCH_UI_MANAGER_SERVICE_CLASS);
+                t.traceEnd();
+            }
 
             // Smartspace manager service
-            // TODO: add deviceHasConfigString(context, R.string.config_defaultSmartspaceService)
-            t.traceBegin("StartSmartspaceService");
-            mSystemServiceManager.startService(SMARTSPACE_MANAGER_SERVICE_CLASS);
-            t.traceEnd();
+            if (deviceHasConfigString(context, R.string.config_defaultSmartspaceService)) {
+                t.traceBegin("StartSmartspaceService");
+                mSystemServiceManager.startService(SMARTSPACE_MANAGER_SERVICE_CLASS);
+                t.traceEnd();
+            } else {
+                Slog.d(TAG, "SmartspaceManagerService not defined by OEM or disabled by flag");
+            }
 
             t.traceBegin("InitConnectivityModuleConnector");
             try {
@@ -2043,14 +2065,17 @@
                 t.traceEnd();
             }
 
-            t.traceBegin("StartPacProxyService");
-            try {
-                pacProxyService = new PacProxyService(context);
-                ServiceManager.addService(Context.PAC_PROXY_SERVICE, pacProxyService);
-            } catch (Throwable e) {
-                reportWtf("starting PacProxyService", e);
+            // Devices without WebView/JavaScript cannot support PAC proxies.
+            if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WEBVIEW)) {
+                t.traceBegin("StartPacProxyService");
+                try {
+                    pacProxyService = new PacProxyService(context);
+                    ServiceManager.addService(Context.PAC_PROXY_SERVICE, pacProxyService);
+                } catch (Throwable e) {
+                    reportWtf("starting PacProxyService", e);
+                }
+                t.traceEnd();
             }
-            t.traceEnd();
 
             t.traceBegin("StartConnectivityService");
             // This has to be called after NetworkManagementService, NetworkStatsService
@@ -2183,12 +2208,13 @@
             }
 
             // WallpaperEffectsGeneration manager service
-            // TODO (b/135218095): Use deviceHasConfigString(context,
-            //  R.string.config_defaultWallpaperEffectsGenerationService)
-            t.traceBegin("StartWallpaperEffectsGenerationService");
-            mSystemServiceManager.startService(
+            if (deviceHasConfigString(context,
+                R.string.config_defaultWallpaperEffectsGenerationService)) {
+                t.traceBegin("StartWallpaperEffectsGenerationService");
+                mSystemServiceManager.startService(
                     WALLPAPER_EFFECTS_GENERATION_MANAGER_SERVICE_CLASS);
-            t.traceEnd();
+                t.traceEnd();
+            }
 
             t.traceBegin("StartAudioService");
             if (!isArc) {
@@ -2571,7 +2597,7 @@
         mSystemServiceManager.startService(MediaProjectionManagerService.class);
         t.traceEnd();
 
-       if (isWatch) {
+        if (isWatch) {
             // Must be started before services that depend it, e.g. WearConnectivityService
             t.traceBegin("StartWearPowerService");
             mSystemServiceManager.startService(WEAR_POWER_SERVICE_CLASS);
@@ -2581,6 +2607,10 @@
             mSystemServiceManager.startService(HEALTH_SERVICE_CLASS);
             t.traceEnd();
 
+            t.traceBegin("StartSystemStateDisplayService");
+            mSystemServiceManager.startService(SYSTEM_STATE_DISPLAY_SERVICE_CLASS);
+            t.traceEnd();
+
             t.traceBegin("StartWearConnectivityService");
             mSystemServiceManager.startService(WEAR_CONNECTIVITY_SERVICE_CLASS);
             t.traceEnd();
@@ -2593,9 +2623,21 @@
             mSystemServiceManager.startService(WEAR_TIME_SERVICE_CLASS);
             t.traceEnd();
 
-            t.traceBegin("StartWearGlobalActionsService");
-            mSystemServiceManager.startService(WEAR_GLOBAL_ACTIONS_SERVICE_CLASS);
+            t.traceBegin("StartWearSettingsService");
+            mSystemServiceManager.startService(WEAR_SETTINGS_SERVICE_CLASS);
             t.traceEnd();
+
+            t.traceBegin("StartWearModeService");
+            mSystemServiceManager.startService(WEAR_MODE_SERVICE_CLASS);
+            t.traceEnd();
+
+            boolean enableWristOrientationService = SystemProperties.getBoolean(
+                    "config.enable_wristorientation", false);
+            if (enableWristOrientationService) {
+                t.traceBegin("StartWristOrientationService");
+                mSystemServiceManager.startService(WRIST_ORIENTATION_SERVICE_CLASS);
+                t.traceEnd();
+            }
         }
 
         if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_SLICES_DISABLED)) {
@@ -2695,10 +2737,12 @@
             Slog.d(TAG, "TranslationService not defined by OEM");
         }
 
-        // Selection toolbar service
-        t.traceBegin("StartSelectionToolbarManagerService");
-        mSystemServiceManager.startService(SELECTION_TOOLBAR_MANAGER_SERVICE_CLASS);
-        t.traceEnd();
+        if (!isTv) {
+            // Selection toolbar service
+            t.traceBegin("StartSelectionToolbarManagerService");
+            mSystemServiceManager.startService(SELECTION_TOOLBAR_MANAGER_SERVICE_CLASS);
+            t.traceEnd();
+        }
 
         // NOTE: ClipboardService depends on ContentCapture and Autofill
         t.traceBegin("StartClipboardService");
@@ -2736,7 +2780,8 @@
         final HsumBootUserInitializer hsumBootUserInitializer =
                 HsumBootUserInitializer.createInstance(
                         mActivityManagerService, mPackageManagerService, mContentResolver,
-                        context.getResources().getBoolean(R.bool.config_isMainUserPermanentAdmin));
+                        context.getResources().getBoolean(R.bool.config_isMainUserPermanentAdmin),
+                        UserManager.isCommunalProfileEnabled());
         if (hsumBootUserInitializer != null) {
             t.traceBegin("HsumBootUserInitializer.init");
             hsumBootUserInitializer.init(t);
@@ -2864,6 +2909,13 @@
         mSystemServiceManager.startService(HEALTHCONNECT_MANAGER_SERVICE_CLASS);
         t.traceEnd();
 
+        if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_DEVICE_LOCK)) {
+            t.traceBegin("DeviceLockService");
+            mSystemServiceManager.startServiceFromJar(DEVICE_LOCK_SERVICE_CLASS,
+                    DEVICE_LOCK_APEX_PATH);
+            t.traceEnd();
+        }
+
         // These are needed to propagate to the runnable below.
         final NetworkManagementService networkManagementF = networkManagement;
         final NetworkPolicyManagerService networkPolicyF = networkPolicy;
@@ -3154,6 +3206,12 @@
             t.traceEnd();
         }, t);
 
+        if (hsumBootUserInitializer != null) {
+            t.traceBegin("HsumBootUserInitializer.postSystemReady");
+            hsumBootUserInitializer.postSystemReady(t);
+            t.traceEnd();
+        }
+
         t.traceBegin("LockSettingsThirdPartyAppsStarted");
         LockSettingsInternal lockSettingsInternal =
             LocalServices.getService(LockSettingsInternal.class);
@@ -3255,6 +3313,12 @@
                 Slog.d(TAG, "ContentCaptureService disabled because resource is not overlaid");
                 return;
             }
+            if (!deviceHasConfigString(context, R.string.config_defaultContentProtectionService)) {
+                Slog.d(
+                        TAG,
+                        "ContentProtectionService disabled because resource is not overlaid,"
+                            + " ContentCaptureService still enabled");
+            }
         }
 
         t.traceBegin("StartContentCaptureService");
@@ -3293,12 +3357,6 @@
 
     }
 
-    private void startAmbientContextService(@NonNull TimingsTraceAndSlog t) {
-        t.traceBegin("StartAmbientContextService");
-        mSystemServiceManager.startService(AmbientContextManagerService.class);
-        t.traceEnd();
-    }
-
     private void startWearableSensingService(@NonNull TimingsTraceAndSlog t) {
         t.traceBegin("startWearableSensingService");
         mSystemServiceManager.startService(WearableSensingManagerService.class);
diff --git a/services/lint-baseline.xml b/services/lint-baseline.xml
new file mode 100644
index 0000000..8489c17
--- /dev/null
+++ b/services/lint-baseline.xml
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="6" by="lint 8.1.0-dev" type="baseline" client="" dependencies="true" name="" variant="all" version="8.1.0-dev">
+
+    <issue
+        id="SimpleManualPermissionEnforcement"
+        message="ISystemConfig permission check should be converted to @EnforcePermission annotation"
+        errorLine1="            mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_CARRIER_APP_INFO,"
+        errorLine2="            ^">
+        <location
+            file="frameworks/base/services/java/com/android/server/SystemConfigService.java"
+            line="46"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="SimpleManualPermissionEnforcement"
+        message="ISystemConfig permission check should be converted to @EnforcePermission annotation"
+        errorLine1="            mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_CARRIER_APP_INFO,"
+        errorLine2="            ^">
+        <location
+            file="frameworks/base/services/java/com/android/server/SystemConfigService.java"
+            line="54"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="SimpleManualPermissionEnforcement"
+        message="ISystemConfig permission check should be converted to @EnforcePermission annotation"
+        errorLine1="            mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_CARRIER_APP_INFO,"
+        errorLine2="            ^">
+        <location
+            file="frameworks/base/services/java/com/android/server/SystemConfigService.java"
+            line="67"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="SimpleManualPermissionEnforcement"
+        message="ISystemConfig permission check should be converted to @EnforcePermission annotation"
+        errorLine1="            mContext.enforceCallingOrSelfPermission(Manifest.permission.GET_RUNTIME_PERMISSIONS,"
+        errorLine2="            ^">
+        <location
+            file="frameworks/base/services/java/com/android/server/SystemConfigService.java"
+            line="76"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="SimpleManualPermissionEnforcement"
+        message="ISystemConfig permission check should be converted to @EnforcePermission annotation"
+        errorLine1="            getContext().enforceCallingOrSelfPermission(Manifest.permission.QUERY_ALL_PACKAGES,"
+        errorLine2="            ^">
+        <location
+            file="frameworks/base/services/java/com/android/server/SystemConfigService.java"
+            line="107"
+            column="13"/>
+    </issue>
+
+</issues>
diff --git a/services/midi/java/com/android/server/midi/MidiService.java b/services/midi/java/com/android/server/midi/MidiService.java
index 4aba30a..5e7dd59 100644
--- a/services/midi/java/com/android/server/midi/MidiService.java
+++ b/services/midi/java/com/android/server/midi/MidiService.java
@@ -16,7 +16,9 @@
 
 package com.android.server.midi;
 
+import android.Manifest;
 import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothUuid;
 import android.content.BroadcastReceiver;
@@ -48,6 +50,7 @@
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.util.EventLog;
 import android.util.Log;
 
@@ -81,6 +84,11 @@
 //   2. synchronized (mDeviceConnections)
 //TODO Introduce a single lock object to lock the whole state and avoid the requirement above.
 
+// All users should be able to connect to USB and Bluetooth MIDI devices.
+// All users can create can install an app that provides a Virtual MIDI Device Service.
+// Users can not open virtual MIDI devices created by other users.
+// getDevices() surfaces devices that can be opened by that user.
+// openDevice() rejects devices that are cannot be opened by that user.
 public class MidiService extends IMidiManager.Stub {
 
     public static class Lifecycle extends SystemService {
@@ -97,10 +105,21 @@
         }
 
         @Override
+        @RequiresPermission(allOf = {Manifest.permission.INTERACT_ACROSS_USERS},
+                anyOf = {Manifest.permission.QUERY_USERS,
+                Manifest.permission.CREATE_USERS,
+                Manifest.permission.MANAGE_USERS})
+        public void onUserStarting(@NonNull TargetUser user) {
+            mMidiService.onStartOrUnlockUser(user, false /* matchDirectBootUnaware */);
+        }
+
+        @Override
+        @RequiresPermission(allOf = {Manifest.permission.INTERACT_ACROSS_USERS},
+                anyOf = {Manifest.permission.QUERY_USERS,
+                Manifest.permission.CREATE_USERS,
+                Manifest.permission.MANAGE_USERS})
         public void onUserUnlocking(@NonNull TargetUser user) {
-            if (user.getUserIdentifier()  == UserHandle.USER_SYSTEM) {
-                mMidiService.onUnlockUser();
-            }
+            mMidiService.onStartOrUnlockUser(user, true /* matchDirectBootUnaware */);
         }
     }
 
@@ -134,6 +153,7 @@
     private int mNextDeviceId = 1;
 
     private final PackageManager mPackageManager;
+    private final UserManager mUserManager;
 
     private static final String MIDI_LEGACY_STRING = "MIDI 1.0";
     private static final String MIDI_UNIVERSAL_STRING = "MIDI 2.0";
@@ -159,21 +179,24 @@
     private final HashSet<ParcelUuid> mNonMidiUUIDs = new HashSet<ParcelUuid>();
 
     // PackageMonitor for listening to package changes
+    // uid is the uid of the package so use getChangingUserId() to fetch the userId.
     private final PackageMonitor mPackageMonitor = new PackageMonitor() {
         @Override
+        @RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS)
         public void onPackageAdded(String packageName, int uid) {
-            addPackageDeviceServers(packageName);
+            addPackageDeviceServers(packageName, getChangingUserId());
         }
 
         @Override
+        @RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS)
         public void onPackageModified(String packageName) {
-            removePackageDeviceServers(packageName);
-            addPackageDeviceServers(packageName);
+            removePackageDeviceServers(packageName, getChangingUserId());
+            addPackageDeviceServers(packageName, getChangingUserId());
         }
 
         @Override
         public void onPackageRemoved(String packageName, int uid) {
-            removePackageDeviceServers(packageName);
+            removePackageDeviceServers(packageName, getChangingUserId());
         }
     };
 
@@ -202,6 +225,10 @@
             return mUid;
         }
 
+        private int getUserId() {
+            return UserHandle.getUserId(mUid);
+        }
+
         public void addListener(IMidiDeviceListener listener) {
             if (mListeners.size() >= MAX_LISTENERS_PER_CLIENT) {
                 throw new SecurityException(
@@ -219,8 +246,12 @@
             }
         }
 
-        public void addDeviceConnection(Device device, IMidiDeviceOpenCallback callback) {
-            Log.d(TAG, "addDeviceConnection() device:" + device);
+        @RequiresPermission(anyOf = {Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+                Manifest.permission.INTERACT_ACROSS_USERS,
+                Manifest.permission.INTERACT_ACROSS_PROFILES})
+        public void addDeviceConnection(Device device, IMidiDeviceOpenCallback callback,
+                int userId) {
+            Log.d(TAG, "addDeviceConnection() device:" + device + " userId:" + userId);
             if (mDeviceConnections.size() >= MAX_CONNECTIONS_PER_CLIENT) {
                 Log.i(TAG, "too many MIDI connections for UID = " + mUid);
                 throw new SecurityException(
@@ -228,7 +259,7 @@
             }
             DeviceConnection connection = new DeviceConnection(device, this, callback);
             mDeviceConnections.put(connection.getToken(), connection);
-            device.addDeviceConnection(connection);
+            device.addDeviceConnection(connection, userId);
         }
 
         // called from MidiService.closeDevice()
@@ -251,8 +282,8 @@
         }
 
         public void deviceAdded(Device device) {
-            // ignore private devices that our client cannot access
-            if (!device.isUidAllowed(mUid)) return;
+            // ignore devices that our client cannot access
+            if (!device.isUidAllowed(mUid) || !device.isUserIdAllowed(getUserId())) return;
 
             MidiDeviceInfo deviceInfo = device.getDeviceInfo();
             try {
@@ -265,8 +296,8 @@
         }
 
         public void deviceRemoved(Device device) {
-            // ignore private devices that our client cannot access
-            if (!device.isUidAllowed(mUid)) return;
+            // ignore devices that our client cannot access
+            if (!device.isUidAllowed(mUid) || !device.isUserIdAllowed(getUserId())) return;
 
             MidiDeviceInfo deviceInfo = device.getDeviceInfo();
             try {
@@ -279,8 +310,8 @@
         }
 
         public void deviceStatusChanged(Device device, MidiDeviceStatus status) {
-            // ignore private devices that our client cannot access
-            if (!device.isUidAllowed(mUid)) return;
+            // ignore devices that our client cannot access
+            if (!device.isUidAllowed(mUid) || !device.isUserIdAllowed(getUserId())) return;
 
             try {
                 for (IMidiDeviceListener listener : mListeners.values()) {
@@ -354,6 +385,8 @@
         private final ServiceInfo mServiceInfo;
         // UID of device implementation
         private final int mUid;
+        // User Id of the app. Only used for virtual devices
+        private final int mUserId;
 
         // ServiceConnection for implementing Service (virtual devices only)
         // mServiceConnection is non-null when connected or attempting to connect to the service
@@ -375,19 +408,24 @@
         private AtomicInteger mTotalOutputBytes = new AtomicInteger();
 
         public Device(IMidiDeviceServer server, MidiDeviceInfo deviceInfo,
-                ServiceInfo serviceInfo, int uid) {
+                ServiceInfo serviceInfo, int uid, int userId) {
             mDeviceInfo = deviceInfo;
             mServiceInfo = serviceInfo;
             mUid = uid;
+            mUserId = userId;
             mBluetoothDevice = (BluetoothDevice)deviceInfo.getProperties().getParcelable(
                     MidiDeviceInfo.PROPERTY_BLUETOOTH_DEVICE, android.bluetooth.BluetoothDevice.class);;
             setDeviceServer(server);
         }
 
+        @RequiresPermission(anyOf = {Manifest.permission.QUERY_USERS,
+                Manifest.permission.CREATE_USERS,
+                Manifest.permission.MANAGE_USERS})
         public Device(BluetoothDevice bluetoothDevice) {
             mBluetoothDevice = bluetoothDevice;
             mServiceInfo = null;
             mUid = mBluetoothServiceUid;
+            mUserId = mUserManager.getMainUser().getIdentifier();
         }
 
         private void setDeviceServer(IMidiDeviceServer server) {
@@ -468,11 +506,22 @@
             return mUid;
         }
 
+        public int getUserId() {
+            return mUserId;
+        }
+
         public boolean isUidAllowed(int uid) {
             return (!mDeviceInfo.isPrivate() || mUid == uid);
         }
 
-        public void addDeviceConnection(DeviceConnection connection) {
+        public boolean isUserIdAllowed(int userId) {
+            return (mDeviceInfo.getType() != MidiDeviceInfo.TYPE_VIRTUAL || mUserId == userId);
+        }
+
+        @RequiresPermission(anyOf = {Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+                Manifest.permission.INTERACT_ACROSS_USERS,
+                Manifest.permission.INTERACT_ACROSS_PROFILES})
+        public void addDeviceConnection(DeviceConnection connection, int userId) {
             Log.d(TAG, "addDeviceConnection() [A] connection:" + connection);
             synchronized (mDeviceConnections) {
                 mDeviceConnectionsAdded.incrementAndGet();
@@ -537,8 +586,8 @@
                                 new ComponentName(mServiceInfo.packageName, mServiceInfo.name));
                     }
 
-                    if (!mContext.bindService(intent, mServiceConnection,
-                            Context.BIND_AUTO_CREATE)) {
+                    if (!mContext.bindServiceAsUser(intent, mServiceConnection,
+                            Context.BIND_AUTO_CREATE, UserHandle.of(mUserId))) {
                         Log.e(TAG, "Unable to bind service: " + intent);
                         setDeviceServer(null);
                         mServiceConnection = null;
@@ -886,6 +935,8 @@
     public MidiService(Context context) {
         mContext = context;
         mPackageManager = context.getPackageManager();
+        mUserManager = mContext.getSystemService(UserManager.class);
+        mPackageMonitor.register(mContext, null, UserHandle.ALL, true);
 
         // TEMPORARY - Disable BTL-MIDI
         //FIXME - b/25689266
@@ -913,32 +964,41 @@
         // mNonMidiUUIDs.add(BluetoothUuid.BATTERY);
     }
 
-    private void onUnlockUser() {
-        mPackageMonitor.register(mContext, null, true);
-
+    @RequiresPermission(allOf = {Manifest.permission.INTERACT_ACROSS_USERS},
+            anyOf = {Manifest.permission.QUERY_USERS,
+            Manifest.permission.CREATE_USERS,
+            Manifest.permission.MANAGE_USERS})
+    private void onStartOrUnlockUser(TargetUser user, boolean matchDirectBootUnaware) {
+        Log.d(TAG, "onStartOrUnlockUser " + user.getUserIdentifier() + " matchDirectBootUnaware: "
+                + matchDirectBootUnaware);
         Intent intent = new Intent(MidiDeviceService.SERVICE_INTERFACE);
-        List<ResolveInfo> resolveInfos = mPackageManager.queryIntentServices(intent,
-                PackageManager.GET_META_DATA);
+        int resolveFlags = PackageManager.GET_META_DATA;
+        if (matchDirectBootUnaware) {
+            resolveFlags |= PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
+        }
+        List<ResolveInfo> resolveInfos = mPackageManager.queryIntentServicesAsUser(intent,
+                resolveFlags, user.getUserIdentifier());
         if (resolveInfos != null) {
             int count = resolveInfos.size();
             for (int i = 0; i < count; i++) {
                 ServiceInfo serviceInfo = resolveInfos.get(i).serviceInfo;
                 if (serviceInfo != null) {
-                    addPackageDeviceServer(serviceInfo);
+                    addPackageDeviceServer(serviceInfo, user.getUserIdentifier());
                 }
             }
         }
 
-        PackageInfo info;
-        try {
-            info = mPackageManager.getPackageInfo(MidiManager.BLUETOOTH_MIDI_SERVICE_PACKAGE, 0);
-        } catch (PackageManager.NameNotFoundException e) {
-            info = null;
-        }
-        if (info != null && info.applicationInfo != null) {
-            mBluetoothServiceUid = info.applicationInfo.uid;
-        } else {
-            mBluetoothServiceUid = -1;
+        if (user.getUserIdentifier() == mUserManager.getMainUser().getIdentifier()) {
+            PackageInfo info;
+            try {
+                info = mPackageManager.getPackageInfoAsUser(
+                        MidiManager.BLUETOOTH_MIDI_SERVICE_PACKAGE, 0, user.getUserIdentifier());
+            } catch (PackageManager.NameNotFoundException e) {
+                info = null;
+            }
+            if (info != null && info.applicationInfo != null) {
+                mBluetoothServiceUid = info.applicationInfo.uid;
+            }
         }
     }
 
@@ -960,10 +1020,11 @@
 
     // Inform listener of the status of all known devices.
     private void updateStickyDeviceStatus(int uid, IMidiDeviceListener listener) {
+        int userId = UserHandle.getUserId(uid);
         synchronized (mDevicesByInfo) {
             for (Device device : mDevicesByInfo.values()) {
-                // ignore private devices that our client cannot access
-                if (device.isUidAllowed(uid)) {
+                // ignore devices that our client cannot access
+                if (device.isUidAllowed(uid) && device.isUserIdAllowed(userId)) {
                     try {
                         MidiDeviceStatus status = device.getDeviceStatus();
                         if (status != null) {
@@ -989,10 +1050,11 @@
     public MidiDeviceInfo[] getDevicesForTransport(int transport) {
         ArrayList<MidiDeviceInfo> deviceInfos = new ArrayList<MidiDeviceInfo>();
         int uid = Binder.getCallingUid();
+        int userId = getCallingUserId();
 
         synchronized (mDevicesByInfo) {
             for (Device device : mDevicesByInfo.values()) {
-                if (device.isUidAllowed(uid)) {
+                if (device.isUidAllowed(uid) && device.isUserIdAllowed(userId)) {
                     // UMP devices have protocols that are not PROTOCOL_UNKNOWN
                     if (transport == MidiManager.TRANSPORT_UNIVERSAL_MIDI_PACKETS) {
                         if (device.getDeviceInfo().getDefaultProtocol()
@@ -1029,6 +1091,9 @@
             if (!device.isUidAllowed(Binder.getCallingUid())) {
                 throw new SecurityException("Attempt to open private device with wrong UID");
             }
+            if (!device.isUserIdAllowed(getCallingUserId())) {
+                throw new SecurityException("Attempt to open virtual device with wrong user id");
+            }
         }
 
         if (deviceInfo.getType() == MidiDeviceInfo.TYPE_USB) {
@@ -1044,7 +1109,7 @@
         final long identity = Binder.clearCallingIdentity();
         try {
             Log.i(TAG, "addDeviceConnection() [B] device:" + device);
-            client.addDeviceConnection(device, callback);
+            client.addDeviceConnection(device, callback, getCallingUserId());
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
@@ -1106,7 +1171,7 @@
         final long identity = Binder.clearCallingIdentity();
         try {
             Log.i(TAG, "addDeviceConnection() [C] device:" + device);
-            client.addDeviceConnection(device, callback);
+            client.addDeviceConnection(device, callback, getCallingUserId());
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
@@ -1124,6 +1189,7 @@
             int numOutputPorts, String[] inputPortNames, String[] outputPortNames,
             Bundle properties, int type, int defaultProtocol) {
         int uid = Binder.getCallingUid();
+        int userId = getCallingUserId();
         if (type == MidiDeviceInfo.TYPE_USB && uid != Process.SYSTEM_UID) {
             throw new SecurityException("only system can create USB devices");
         } else if (type == MidiDeviceInfo.TYPE_BLUETOOTH && uid != mBluetoothServiceUid) {
@@ -1133,7 +1199,7 @@
         synchronized (mDevicesByInfo) {
             return addDeviceLocked(type, numInputPorts, numOutputPorts, inputPortNames,
                     outputPortNames, properties, server, null, false, uid,
-                    defaultProtocol);
+                    defaultProtocol, userId);
         }
     }
 
@@ -1210,7 +1276,8 @@
     private MidiDeviceInfo addDeviceLocked(int type, int numInputPorts, int numOutputPorts,
             String[] inputPortNames, String[] outputPortNames, Bundle properties,
             IMidiDeviceServer server, ServiceInfo serviceInfo,
-            boolean isPrivate, int uid, int defaultProtocol) {
+            boolean isPrivate, int uid, int defaultProtocol, int userId) {
+        Log.d(TAG, "addDeviceLocked()" + uid + " type:" + type);
 
         // Limit the number of devices per app.
         int deviceCountForApp = 0;
@@ -1250,7 +1317,7 @@
             }
         }
         if (device == null) {
-            device = new Device(server, deviceInfo, serviceInfo, uid);
+            device = new Device(server, deviceInfo, serviceInfo, uid, userId);
         }
         mDevicesByInfo.put(deviceInfo, device);
         if (bluetoothDevice != null) {
@@ -1281,12 +1348,14 @@
         }
     }
 
-    private void addPackageDeviceServers(String packageName) {
+    @RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS)
+    private void addPackageDeviceServers(String packageName, int userId) {
         PackageInfo info;
 
         try {
-            info = mPackageManager.getPackageInfo(packageName,
-                    PackageManager.GET_SERVICES | PackageManager.GET_META_DATA);
+            info = mPackageManager.getPackageInfoAsUser(packageName,
+                    PackageManager.GET_SERVICES | PackageManager.GET_META_DATA
+                    | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userId);
         } catch (PackageManager.NameNotFoundException e) {
             Log.e(TAG, "handlePackageUpdate could not find package " + packageName, e);
             return;
@@ -1295,13 +1364,14 @@
         ServiceInfo[] services = info.services;
         if (services == null) return;
         for (int i = 0; i < services.length; i++) {
-            addPackageDeviceServer(services[i]);
+            addPackageDeviceServer(services[i], userId);
         }
     }
 
     private static final String[] EMPTY_STRING_ARRAY = new String[0];
 
-    private void addPackageDeviceServer(ServiceInfo serviceInfo) {
+    private void addPackageDeviceServer(ServiceInfo serviceInfo, int userId) {
+        Log.d(TAG, "addPackageDeviceServer()" + userId);
         XmlResourceParser parser = null;
 
         try {
@@ -1404,8 +1474,8 @@
 
                             int uid;
                             try {
-                                ApplicationInfo appInfo = mPackageManager.getApplicationInfo(
-                                        serviceInfo.packageName, 0);
+                                ApplicationInfo appInfo = mPackageManager.getApplicationInfoAsUser(
+                                        serviceInfo.packageName, 0, userId);
                                 uid = appInfo.uid;
                             } catch (PackageManager.NameNotFoundException e) {
                                 Log.e(TAG, "could not fetch ApplicationInfo for "
@@ -1419,7 +1489,7 @@
                                         inputPortNames.toArray(EMPTY_STRING_ARRAY),
                                         outputPortNames.toArray(EMPTY_STRING_ARRAY),
                                         properties, null, serviceInfo, isPrivate, uid,
-                                        MidiDeviceInfo.PROTOCOL_UNKNOWN);
+                                        MidiDeviceInfo.PROTOCOL_UNKNOWN, userId);
                             }
                             // setting properties to null signals that we are no longer
                             // processing a <device>
@@ -1437,12 +1507,13 @@
         }
     }
 
-    private void removePackageDeviceServers(String packageName) {
+    private void removePackageDeviceServers(String packageName, int userId) {
         synchronized (mDevicesByInfo) {
             Iterator<Device> iterator = mDevicesByInfo.values().iterator();
             while (iterator.hasNext()) {
                 Device device = iterator.next();
-                if (packageName.equals(device.getPackageName())) {
+                if (packageName.equals(device.getPackageName())
+                        && (device.getUserId() == userId)) {
                     iterator.remove();
                     removeDeviceLocked(device);
                 }
@@ -1571,4 +1642,11 @@
     String extractUsbDeviceTag(String propertyName) {
         return propertyName.substring(propertyName.length() - MIDI_LEGACY_STRING.length());
     }
+
+    /**
+     * @return the user id of the calling user.
+     */
+    private int getCallingUserId() {
+        return UserHandle.getUserId(Binder.getCallingUid());
+    }
 }
diff --git a/services/musicrecognition/OWNERS b/services/musicrecognition/OWNERS
index 58f5d40..037b048 100644
--- a/services/musicrecognition/OWNERS
+++ b/services/musicrecognition/OWNERS
@@ -1,6 +1,5 @@
 # Bug component: 830636
 
-joannechung@google.com
 oni@google.com
 volnov@google.com
 
diff --git a/services/people/java/com/android/server/people/PeopleService.java b/services/people/java/com/android/server/people/PeopleService.java
index 292320e..885ed35 100644
--- a/services/people/java/com/android/server/people/PeopleService.java
+++ b/services/people/java/com/android/server/people/PeopleService.java
@@ -63,9 +63,8 @@
 
     private static final String TAG = "PeopleService";
 
-    private DataManager mDataManager;
-    @VisibleForTesting
-    ConversationListenerHelper mConversationListenerHelper;
+    private DataManager mLazyDataManager;
+    private ConversationListenerHelper mLazyConversationListenerHelper;
 
     private PackageManagerInternal mPackageManagerInternal;
 
@@ -76,17 +75,30 @@
      */
     public PeopleService(Context context) {
         super(context);
-
-        mDataManager = new DataManager(context);
-        mConversationListenerHelper = new ConversationListenerHelper();
-        mDataManager.addConversationsListener(mConversationListenerHelper);
     }
 
-    @Override
-    public void onBootPhase(int phase) {
-        if (phase == PHASE_SYSTEM_SERVICES_READY) {
-            mDataManager.initialize();
+    @VisibleForTesting
+    ConversationListenerHelper getConversationListenerHelper() {
+        if (mLazyConversationListenerHelper == null) {
+            initLazyStuff();
         }
+        return mLazyConversationListenerHelper;
+    }
+
+    private synchronized void initLazyStuff() {
+        if (mLazyDataManager == null) {
+            mLazyDataManager = new DataManager(getContext());
+            mLazyDataManager.initialize();
+            mLazyConversationListenerHelper = new ConversationListenerHelper();
+            mLazyDataManager.addConversationsListener(mLazyConversationListenerHelper);
+        }
+    }
+
+    private DataManager getDataManager() {
+        if (mLazyDataManager == null) {
+            initLazyStuff();
+        }
+        return mLazyDataManager;
     }
 
     @Override
@@ -105,12 +117,12 @@
 
     @Override
     public void onUserUnlocked(@NonNull TargetUser user) {
-        mDataManager.onUserUnlocked(user.getUserIdentifier());
+        getDataManager().onUserUnlocked(user.getUserIdentifier());
     }
 
     @Override
     public void onUserStopping(@NonNull TargetUser user) {
-        mDataManager.onUserStopping(user.getUserIdentifier());
+        getDataManager().onUserStopping(user.getUserIdentifier());
     }
 
     /**
@@ -171,28 +183,28 @@
         public ConversationChannel getConversation(
                 String packageName, int userId, String shortcutId) {
             enforceSystemRootOrSystemUI(getContext(), "get conversation");
-            return mDataManager.getConversation(packageName, userId, shortcutId);
+            return getDataManager().getConversation(packageName, userId, shortcutId);
         }
 
         @Override
         public ParceledListSlice<ConversationChannel> getRecentConversations() {
             enforceSystemRootOrSystemUI(getContext(), "get recent conversations");
             return new ParceledListSlice<>(
-                    mDataManager.getRecentConversations(
+                    getDataManager().getRecentConversations(
                             Binder.getCallingUserHandle().getIdentifier()));
         }
 
         @Override
         public void removeRecentConversation(String packageName, int userId, String shortcutId) {
             enforceSystemOrRoot("remove a recent conversation");
-            mDataManager.removeRecentConversation(packageName, userId, shortcutId,
+            getDataManager().removeRecentConversation(packageName, userId, shortcutId,
                     Binder.getCallingUserHandle().getIdentifier());
         }
 
         @Override
         public void removeAllRecentConversations() {
             enforceSystemOrRoot("remove all recent conversations");
-            mDataManager.removeAllRecentConversations(
+            getDataManager().removeAllRecentConversations(
                     Binder.getCallingUserHandle().getIdentifier());
         }
 
@@ -200,7 +212,7 @@
         public boolean isConversation(String packageName, int userId, String shortcutId) {
             enforceHasReadPeopleDataPermission();
             handleIncomingUser(userId);
-            return mDataManager.isConversation(packageName, userId, shortcutId);
+            return getDataManager().isConversation(packageName, userId, shortcutId);
         }
 
         private void enforceHasReadPeopleDataPermission() throws SecurityException {
@@ -213,7 +225,7 @@
         @Override
         public long getLastInteraction(String packageName, int userId, String shortcutId) {
             enforceSystemRootOrSystemUI(getContext(), "get last interaction");
-            return mDataManager.getLastInteraction(packageName, userId, shortcutId);
+            return getDataManager().getLastInteraction(packageName, userId, shortcutId);
         }
 
         @Override
@@ -224,7 +236,7 @@
             if (status.getStartTimeMillis() > System.currentTimeMillis()) {
                 throw new IllegalArgumentException("Start time must be in the past");
             }
-            mDataManager.addOrUpdateStatus(packageName, userId, conversationId, status);
+            getDataManager().addOrUpdateStatus(packageName, userId, conversationId, status);
         }
 
         @Override
@@ -232,14 +244,14 @@
                 String statusId) {
             handleIncomingUser(userId);
             checkCallerIsSameApp(packageName);
-            mDataManager.clearStatus(packageName, userId, conversationId, statusId);
+            getDataManager().clearStatus(packageName, userId, conversationId, statusId);
         }
 
         @Override
         public void clearStatuses(String packageName, int userId, String conversationId) {
             handleIncomingUser(userId);
             checkCallerIsSameApp(packageName);
-            mDataManager.clearStatuses(packageName, userId, conversationId);
+            getDataManager().clearStatuses(packageName, userId, conversationId);
         }
 
         @Override
@@ -250,21 +262,21 @@
                 checkCallerIsSameApp(packageName);
             }
             return new ParceledListSlice<>(
-                    mDataManager.getStatuses(packageName, userId, conversationId));
+                    getDataManager().getStatuses(packageName, userId, conversationId));
         }
 
         @Override
         public void registerConversationListener(
                 String packageName, int userId, String shortcutId, IConversationListener listener) {
             enforceSystemRootOrSystemUI(getContext(), "register conversation listener");
-            mConversationListenerHelper.addConversationListener(
+            getConversationListenerHelper().addConversationListener(
                     new ListenerKey(packageName, userId, shortcutId), listener);
         }
 
         @Override
         public void unregisterConversationListener(IConversationListener listener) {
             enforceSystemRootOrSystemUI(getContext(), "unregister conversation listener");
-            mConversationListenerHelper.removeConversationListener(listener);
+            getConversationListenerHelper().removeConversationListener(listener);
         }
     };
 
@@ -393,7 +405,7 @@
         public void onCreatePredictionSession(AppPredictionContext appPredictionContext,
                 AppPredictionSessionId sessionId) {
             mSessions.put(sessionId,
-                    new SessionInfo(appPredictionContext, mDataManager, sessionId.getUserId(),
+                    new SessionInfo(appPredictionContext, getDataManager(), sessionId.getUserId(),
                             getContext()));
         }
 
@@ -448,18 +460,18 @@
 
         @Override
         public void pruneDataForUser(@UserIdInt int userId, @NonNull CancellationSignal signal) {
-            mDataManager.pruneDataForUser(userId, signal);
+            getDataManager().pruneDataForUser(userId, signal);
         }
 
         @Nullable
         @Override
         public byte[] getBackupPayload(@UserIdInt int userId) {
-            return mDataManager.getBackupPayload(userId);
+            return getDataManager().getBackupPayload(userId);
         }
 
         @Override
         public void restore(@UserIdInt int userId, @NonNull byte[] payload) {
-            mDataManager.restore(userId, payload);
+            getDataManager().restore(userId, payload);
         }
 
         @VisibleForTesting
diff --git a/services/permission/OWNERS b/services/permission/OWNERS
index 6c6c9fc..e464038 100644
--- a/services/permission/OWNERS
+++ b/services/permission/OWNERS
@@ -1,4 +1,5 @@
-ashfall@google.com
+#Bug component: 137825
+
 joecastro@google.com
 ntmyren@google.com
 zhanghai@google.com
diff --git a/services/permission/TEST_MAPPING b/services/permission/TEST_MAPPING
new file mode 100644
index 0000000..579d4e3
--- /dev/null
+++ b/services/permission/TEST_MAPPING
@@ -0,0 +1,71 @@
+{
+    "presubmit": [
+        {
+            "name": "CtsPermissionTestCases",
+            "options": [
+                {
+                    "include-filter": "android.permission.cts.BackgroundPermissionsTest"
+                },
+                {
+                    "include-filter": "android.permission.cts.SplitPermissionTest"
+                },
+                {
+                    "include-filter": "android.permission.cts.PermissionFlagsTest"
+                },
+                {
+                    "include-filter": "android.permission.cts.SharedUidPermissionsTest"
+                }
+            ]
+        },
+        {
+            "name": "CtsAppSecurityHostTestCases",
+            "options": [
+                {
+                    "include-filter": "android.appsecurity.cts.AppSecurityTests#rebootWithDuplicatePermission"
+                }
+            ]
+        },
+        {
+            "name": "CtsPermissionPolicyTestCases",
+            "options": [
+                {
+                    "include-filter": "android.permissionpolicy.cts.RestrictedPermissionsTest"
+                },
+                {
+                    "include-filter": "android.permission.cts.PermissionMaxSdkVersionTest"
+                }
+            ]
+        },
+        {
+            "name": "CtsStatsdHostTestCases",
+            "options": [
+                {
+                    "include-filter": "android.cts.statsd.atom.UidAtomTests#testDangerousPermissionState"
+                }
+            ]
+        }
+    ],
+    "postsubmit": [
+        {
+            "name": "CtsAppSecurityHostTestCases",
+            "options": [
+                {
+                    "include-filter": "android.appsecurity.cts.AppSecurityTests#testPermissionDiffCert"
+                }
+            ]
+        },
+        {
+            "name": "CtsPermissionTestCases",
+            "options": [
+                {
+                    "include-filter": "android.permission.cts.PermissionUpdateListenerTest"
+                }
+            ]
+        }
+    ],
+    "imports": [
+        {
+            "path": "vendor/xts/gts-tests/tests/permission"
+        }
+    ]
+}
diff --git a/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt b/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt
index e416718..c1d137f 100644
--- a/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt
+++ b/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt
@@ -16,6 +16,7 @@
 
 package com.android.server.permission.access
 
+import android.app.admin.DevicePolicyManagerInternal
 import android.content.Context
 import android.content.pm.PackageManager
 import android.content.pm.PackageManagerInternal
@@ -29,6 +30,7 @@
 import com.android.server.appop.AppOpsCheckingServiceInterface
 import com.android.server.permission.access.appop.AppOpService
 import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
+import com.android.server.permission.access.immutable.* // ktlint-disable no-wildcard-imports
 import com.android.server.permission.access.permission.PermissionService
 import com.android.server.pm.KnownPackages
 import com.android.server.pm.PackageManagerLocal
@@ -72,9 +74,9 @@
         userManagerService = UserManagerService.getInstance()
         systemConfig = SystemConfig.getInstance()
 
-        val userIds = IntSet(userManagerService.userIdsIncludingPreCreated)
+        val userIds = MutableIntSet(userManagerService.userIdsIncludingPreCreated)
         val (packageStates, disabledSystemPackageStates) = packageManagerLocal.allPackageStates
-        val knownPackages = packageManagerInternal.knownPackages
+        val knownPackages = packageManagerInternal.getKnownPackages(packageStates)
         val isLeanback = systemConfig.isLeanback
         val configPermissions = systemConfig.permissions
         val privilegedPermissionAllowlistPackages =
@@ -82,7 +84,7 @@
         val permissionAllowlist = systemConfig.permissionAllowlist
         val implicitToSourcePermissions = systemConfig.implicitToSourcePermissions
 
-        val state = AccessState()
+        val state = MutableAccessState()
         policy.initialize(
             state, userIds, packageStates, disabledSystemPackageStates, knownPackages, isLeanback,
             configPermissions, privilegedPermissionAllowlistPackages, permissionAllowlist,
@@ -104,7 +106,7 @@
         get() = PackageManager.FEATURE_LEANBACK in availableFeatures
 
     private val SystemConfig.privilegedPermissionAllowlistPackages: IndexedListSet<String>
-        get() = IndexedListSet<String>().apply {
+        get() = MutableIndexedListSet<String>().apply {
             this += "android"
             if (PackageManager.FEATURE_AUTOMOTIVE in availableFeatures) {
                 // Note that SystemProperties.get(String, String) forces returning an empty string
@@ -117,25 +119,16 @@
         }
 
     private val SystemConfig.implicitToSourcePermissions: IndexedMap<String, IndexedListSet<String>>
-        get() = IndexedMap<String, IndexedListSet<String>>().apply {
+        @Suppress("UNCHECKED_CAST")
+        get() = MutableIndexedMap<String, MutableIndexedListSet<String>>().apply {
             splitPermissions.forEach { splitPermissionInfo ->
                 val sourcePermissionName = splitPermissionInfo.splitPermission
                 splitPermissionInfo.newPermissions.forEach { implicitPermissionName ->
-                    getOrPut(implicitPermissionName) { IndexedListSet() } += sourcePermissionName
+                    getOrPut(implicitPermissionName) { MutableIndexedListSet() } +=
+                        sourcePermissionName
                 }
             }
-        }
-
-    fun getDecision(subject: AccessUri, `object`: AccessUri): Int =
-        getState {
-            with(policy) { getDecision(subject, `object`) }
-        }
-
-    fun setDecision(subject: AccessUri, `object`: AccessUri, decision: Int) {
-        mutateState {
-            with(policy) { setDecision(subject, `object`, decision) }
-        }
-    }
+        } as IndexedMap<String, IndexedListSet<String>>
 
     internal fun onUserAdded(userId: Int) {
         mutateState {
@@ -149,14 +142,18 @@
         }
     }
 
-    internal fun onStorageVolumeMounted(volumeUuid: String?, isSystemUpdated: Boolean) {
+    internal fun onStorageVolumeMounted(
+        volumeUuid: String?,
+        packageNames: List<String>,
+        isSystemUpdated: Boolean
+    ) {
         val (packageStates, disabledSystemPackageStates) = packageManagerLocal.allPackageStates
-        val knownPackages = packageManagerInternal.knownPackages
+        val knownPackages = packageManagerInternal.getKnownPackages(packageStates)
         mutateState {
             with(policy) {
                 onStorageVolumeMounted(
                     packageStates, disabledSystemPackageStates, knownPackages, volumeUuid,
-                    isSystemUpdated
+                    packageNames, isSystemUpdated
                 )
             }
         }
@@ -164,7 +161,7 @@
 
     internal fun onPackageAdded(packageName: String) {
         val (packageStates, disabledSystemPackageStates) = packageManagerLocal.allPackageStates
-        val knownPackages = packageManagerInternal.knownPackages
+        val knownPackages = packageManagerInternal.getKnownPackages(packageStates)
         mutateState {
             with(policy) {
                 onPackageAdded(
@@ -176,7 +173,7 @@
 
     internal fun onPackageRemoved(packageName: String, appId: Int) {
         val (packageStates, disabledSystemPackageStates) = packageManagerLocal.allPackageStates
-        val knownPackages = packageManagerInternal.knownPackages
+        val knownPackages = packageManagerInternal.getKnownPackages(packageStates)
         mutateState {
             with(policy) {
                 onPackageRemoved(
@@ -188,7 +185,7 @@
 
     internal fun onPackageInstalled(packageName: String, userId: Int) {
         val (packageStates, disabledSystemPackageStates) = packageManagerLocal.allPackageStates
-        val knownPackages = packageManagerInternal.knownPackages
+        val knownPackages = packageManagerInternal.getKnownPackages(packageStates)
         mutateState {
             with(policy) {
                 onPackageInstalled(
@@ -200,7 +197,7 @@
 
     internal fun onPackageUninstalled(packageName: String, appId: Int, userId: Int) {
         val (packageStates, disabledSystemPackageStates) = packageManagerLocal.allPackageStates
-        val knownPackages = packageManagerInternal.knownPackages
+        val knownPackages = packageManagerInternal.getKnownPackages(packageStates)
         mutateState {
             with(policy) {
                 onPackageUninstalled(
@@ -212,8 +209,12 @@
     }
 
     internal fun onSystemReady() {
+        val (packageStates, disabledSystemPackageStates) = packageManagerLocal.allPackageStates
+        val knownPackages = packageManagerInternal.getKnownPackages(packageStates)
         mutateState {
-            with(policy) { onSystemReady() }
+            with(policy) {
+                onSystemReady(packageStates, disabledSystemPackageStates, knownPackages)
+            }
         }
     }
 
@@ -221,42 +222,48 @@
         Pair<Map<String, PackageState>, Map<String, PackageState>>
         get() = withUnfilteredSnapshot().use { it.packageStates to it.disabledSystemPackageStates }
 
-    private val PackageManagerInternal.knownPackages: IntMap<Array<String>>
-        get() = IntMap<Array<String>>().apply {
-            this[KnownPackages.PACKAGE_INSTALLER] = getKnownPackageNames(
-                KnownPackages.PACKAGE_INSTALLER, UserHandle.USER_SYSTEM
-            )
-            this[KnownPackages.PACKAGE_PERMISSION_CONTROLLER] = getKnownPackageNames(
-                KnownPackages.PACKAGE_PERMISSION_CONTROLLER, UserHandle.USER_SYSTEM
-            )
-            this[KnownPackages.PACKAGE_VERIFIER] = getKnownPackageNames(
-                KnownPackages.PACKAGE_VERIFIER, UserHandle.USER_SYSTEM
-            )
-            this[KnownPackages.PACKAGE_SETUP_WIZARD] = getKnownPackageNames(
-                KnownPackages.PACKAGE_SETUP_WIZARD, UserHandle.USER_SYSTEM
-            )
-            this[KnownPackages.PACKAGE_SYSTEM_TEXT_CLASSIFIER] = getKnownPackageNames(
-                KnownPackages.PACKAGE_SYSTEM_TEXT_CLASSIFIER, UserHandle.USER_SYSTEM
-            )
-            this[KnownPackages.PACKAGE_CONFIGURATOR] = getKnownPackageNames(
-                KnownPackages.PACKAGE_CONFIGURATOR, UserHandle.USER_SYSTEM
-            )
-            this[KnownPackages.PACKAGE_INCIDENT_REPORT_APPROVER] = getKnownPackageNames(
-                KnownPackages.PACKAGE_INCIDENT_REPORT_APPROVER, UserHandle.USER_SYSTEM
-            )
-            this[KnownPackages.PACKAGE_APP_PREDICTOR] = getKnownPackageNames(
-                KnownPackages.PACKAGE_APP_PREDICTOR, UserHandle.USER_SYSTEM
-            )
-            this[KnownPackages.PACKAGE_COMPANION] = getKnownPackageNames(
-                KnownPackages.PACKAGE_COMPANION, UserHandle.USER_SYSTEM
-            )
-            this[KnownPackages.PACKAGE_RETAIL_DEMO] = getKnownPackageNames(
-                KnownPackages.PACKAGE_RETAIL_DEMO, UserHandle.USER_SYSTEM
-            )
-            this[KnownPackages.PACKAGE_RECENTS] = getKnownPackageNames(
-                KnownPackages.PACKAGE_RECENTS, UserHandle.USER_SYSTEM
-            )
-        }
+    private fun PackageManagerInternal.getKnownPackages(
+        packageStates: Map<String, PackageState>
+    ): IntMap<Array<String>> = MutableIntMap<Array<String>>().apply {
+        this[KnownPackages.PACKAGE_INSTALLER] =
+            getKnownPackageNames(KnownPackages.PACKAGE_INSTALLER, UserHandle.USER_SYSTEM)
+        this[KnownPackages.PACKAGE_PERMISSION_CONTROLLER] = getKnownPackageNames(
+            KnownPackages.PACKAGE_PERMISSION_CONTROLLER, UserHandle.USER_SYSTEM
+        )
+        this[KnownPackages.PACKAGE_VERIFIER] =
+            getKnownPackageNames(KnownPackages.PACKAGE_VERIFIER, UserHandle.USER_SYSTEM)
+        this[KnownPackages.PACKAGE_SETUP_WIZARD] =
+            getKnownPackageNames(KnownPackages.PACKAGE_SETUP_WIZARD, UserHandle.USER_SYSTEM)
+        this[KnownPackages.PACKAGE_SYSTEM_TEXT_CLASSIFIER] = getKnownPackageNames(
+            KnownPackages.PACKAGE_SYSTEM_TEXT_CLASSIFIER, UserHandle.USER_SYSTEM
+        )
+        this[KnownPackages.PACKAGE_CONFIGURATOR] =
+            getKnownPackageNames(KnownPackages.PACKAGE_CONFIGURATOR, UserHandle.USER_SYSTEM)
+        this[KnownPackages.PACKAGE_INCIDENT_REPORT_APPROVER] = getKnownPackageNames(
+            KnownPackages.PACKAGE_INCIDENT_REPORT_APPROVER, UserHandle.USER_SYSTEM
+        )
+        this[KnownPackages.PACKAGE_APP_PREDICTOR] =
+            getKnownPackageNames(KnownPackages.PACKAGE_APP_PREDICTOR, UserHandle.USER_SYSTEM)
+        this[KnownPackages.PACKAGE_COMPANION] =
+            getKnownPackageNames(KnownPackages.PACKAGE_COMPANION, UserHandle.USER_SYSTEM)
+        this[KnownPackages.PACKAGE_RETAIL_DEMO] =
+            getKnownPackageNames(KnownPackages.PACKAGE_RETAIL_DEMO, UserHandle.USER_SYSTEM)
+                .filter { isProfileOwner(it, packageStates) }.toTypedArray()
+        this[KnownPackages.PACKAGE_RECENTS] =
+            getKnownPackageNames(KnownPackages.PACKAGE_RECENTS, UserHandle.USER_SYSTEM)
+    }
+
+    private fun isProfileOwner(
+        packageName: String,
+        packageStates: Map<String, PackageState>
+    ): Boolean {
+        val appId = packageStates[packageName]?.appId ?: return false
+        val devicePolicyManagerInternal =
+            LocalServices.getService(DevicePolicyManagerInternal::class.java) ?: return false
+        // TODO(b/169395065): Figure out if this flow makes sense in Device Owner mode.
+        return devicePolicyManagerInternal.isActiveProfileOwner(appId) ||
+            devicePolicyManagerInternal.isActiveDeviceOwner(appId)
+    }
 
     @OptIn(ExperimentalContracts::class)
     internal inline fun <T> getState(action: GetStateScope.() -> T): T {
@@ -269,7 +276,7 @@
         contract { callsInPlace(action, InvocationKind.EXACTLY_ONCE) }
         synchronized(stateLock) {
             val oldState = state
-            val newState = oldState.copy()
+            val newState = oldState.toMutable()
             MutateStateScope(oldState, newState).action()
             persistence.write(newState)
             state = newState
diff --git a/services/permission/java/com/android/server/permission/access/AccessPersistence.kt b/services/permission/java/com/android/server/permission/access/AccessPersistence.kt
index a25b720..a3f65af 100644
--- a/services/permission/java/com/android/server/permission/access/AccessPersistence.kt
+++ b/services/permission/java/com/android/server/permission/access/AccessPersistence.kt
@@ -22,17 +22,19 @@
 import android.os.SystemClock
 import android.os.UserHandle
 import android.util.AtomicFile
-import android.util.Log
+import android.util.Slog
+import android.util.SparseLongArray
 import com.android.internal.annotations.GuardedBy
 import com.android.internal.os.BackgroundThread
 import com.android.modules.utils.BinaryXmlPullParser
 import com.android.modules.utils.BinaryXmlSerializer
 import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
+import com.android.server.permission.access.immutable.* // ktlint-disable no-wildcard-imports
 import com.android.server.permission.access.util.PermissionApex
 import com.android.server.permission.access.util.parseBinaryXml
-import com.android.server.permission.access.util.read
+import com.android.server.permission.access.util.readWithReserveCopy
 import com.android.server.permission.access.util.serializeBinaryXml
-import com.android.server.permission.access.util.writeInlined
+import com.android.server.permission.access.util.writeWithReserveCopy
 import java.io.File
 import java.io.FileNotFoundException
 
@@ -41,9 +43,9 @@
 ) {
     private val scheduleLock = Any()
     @GuardedBy("scheduleLock")
-    private val pendingMutationTimesMillis = IntLongMap()
+    private val pendingMutationTimesMillis = SparseLongArray()
     @GuardedBy("scheduleLock")
-    private val pendingStates = IntMap<AccessState>()
+    private val pendingStates = MutableIntMap<AccessState>()
     @GuardedBy("scheduleLock")
     private lateinit var writeHandler: WriteHandler
 
@@ -53,36 +55,54 @@
         writeHandler = WriteHandler(BackgroundThread.getHandler().looper)
     }
 
-    fun read(state: AccessState) {
+    /**
+     * Reads the state either from the disk or migrate legacy data when the data files are missing.
+     */
+    fun read(state: MutableAccessState) {
         readSystemState(state)
-        state.systemState.userIds.forEachIndexed { _, userId ->
+        state.externalState.userIds.forEachIndexed { _, userId ->
             readUserState(state, userId)
         }
     }
 
-    private fun readSystemState(state: AccessState) {
-        systemFile.parse {
+    private fun readSystemState(state: MutableAccessState) {
+        val fileExists = systemFile.parse {
             // This is the canonical way to call an extension function in a different class.
             // TODO(b/259469752): Use context receiver for this when it becomes stable.
             with(policy) { parseSystemState(state) }
         }
-    }
 
-    private fun readUserState(state: AccessState, userId: Int) {
-        getUserFile(userId).parse {
-            with(policy) { parseUserState(state, userId) }
+        if (!fileExists) {
+            policy.migrateSystemState(state)
+            state.systemState.write(state, UserHandle.USER_ALL)
         }
     }
 
-    private inline fun File.parse(block: BinaryXmlPullParser.() -> Unit) {
+    private fun readUserState(state: MutableAccessState, userId: Int) {
+        val fileExists = getUserFile(userId).parse {
+            with(policy) { parseUserState(state, userId) }
+        }
+
+        if (!fileExists) {
+            policy.migrateUserState(state, userId)
+            state.userStates[userId]!!.write(state, userId)
+        }
+    }
+
+    /**
+     * @return {@code true} if the file is successfully read from the disk; {@code false} if
+     * the file doesn't exist yet.
+     */
+    private inline fun File.parse(block: BinaryXmlPullParser.() -> Unit): Boolean =
         try {
-            AtomicFile(this).read { it.parseBinaryXml(block) }
+            AtomicFile(this).readWithReserveCopy { it.parseBinaryXml(block) }
+            true
         } catch (e: FileNotFoundException) {
-            Log.i(LOG_TAG, "$this not found")
+            Slog.i(LOG_TAG, "$this not found")
+            false
         } catch (e: Exception) {
             throw IllegalStateException("Failed to read $this", e)
         }
-    }
 
     fun write(state: AccessState) {
         state.systemState.write(state, UserHandle.USER_ALL)
@@ -94,11 +114,7 @@
     private fun WritableState.write(state: AccessState, userId: Int) {
         when (val writeMode = writeMode) {
             WriteMode.NONE -> {}
-            WriteMode.SYNC -> {
-                synchronized(scheduleLock) { pendingStates[userId] = state }
-                writePendingState(userId)
-            }
-            WriteMode.ASYNC -> {
+            WriteMode.ASYNCHRONOUS -> {
                 synchronized(scheduleLock) {
                     writeHandler.removeMessages(userId)
                     pendingStates[userId] = state
@@ -117,6 +133,10 @@
                     }
                 }
             }
+            WriteMode.SYNCHRONOUS -> {
+                synchronized(scheduleLock) { pendingStates[userId] = state }
+                writePendingState(userId)
+            }
             else -> error(writeMode)
         }
     }
@@ -126,7 +146,7 @@
             val state: AccessState?
             synchronized(scheduleLock) {
                 pendingMutationTimesMillis -= userId
-                state = pendingStates.removeReturnOld(userId)
+                state = pendingStates.remove(userId)
                 writeHandler.removeMessages(userId)
             }
             if (state == null) {
@@ -154,9 +174,9 @@
 
     private inline fun File.serialize(block: BinaryXmlSerializer.() -> Unit) {
         try {
-            AtomicFile(this).writeInlined { it.serializeBinaryXml(block) }
+            AtomicFile(this).writeWithReserveCopy { it.serializeBinaryXml(block) }
         } catch (e: Exception) {
-            Log.e(LOG_TAG, "Failed to serialize $this", e)
+            Slog.e(LOG_TAG, "Failed to serialize $this", e)
         }
     }
 
@@ -176,16 +196,6 @@
     }
 
     private inner class WriteHandler(looper: Looper) : Handler(looper) {
-        fun writeAtTime(userId: Int, timeMillis: Long) {
-            removeMessages(userId)
-            val message = obtainMessage(userId)
-            sendMessageDelayed(message, timeMillis)
-        }
-
-        fun cancelWrite(userId: Int) {
-            removeMessages(userId)
-        }
-
         override fun handleMessage(message: Message) {
             val userId = message.what
             writePendingState(userId)
diff --git a/services/permission/java/com/android/server/permission/access/AccessPolicy.kt b/services/permission/java/com/android/server/permission/access/AccessPolicy.kt
index 07a5e72..17474fb 100644
--- a/services/permission/java/com/android/server/permission/access/AccessPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/AccessPolicy.kt
@@ -16,15 +16,21 @@
 
 package com.android.server.permission.access
 
-import android.util.Log
+import android.util.Slog
 import com.android.modules.utils.BinaryXmlPullParser
 import com.android.modules.utils.BinaryXmlSerializer
 import com.android.server.SystemConfig
+import com.android.server.permission.access.appop.AppIdAppOpPolicy
 import com.android.server.permission.access.appop.PackageAppOpPolicy
-import com.android.server.permission.access.appop.UidAppOpPolicy
 import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
-import com.android.server.permission.access.permission.UidPermissionPolicy
+import com.android.server.permission.access.immutable.* // ktlint-disable no-wildcard-imports
+import com.android.server.permission.access.immutable.IndexedMap
+import com.android.server.permission.access.permission.AppIdPermissionPolicy
+import com.android.server.permission.access.util.attributeInt
+import com.android.server.permission.access.util.attributeInterned
 import com.android.server.permission.access.util.forEachTag
+import com.android.server.permission.access.util.getAttributeIntOrThrow
+import com.android.server.permission.access.util.getAttributeValueOrThrow
 import com.android.server.permission.access.util.tag
 import com.android.server.permission.access.util.tagName
 import com.android.server.pm.permission.PermissionAllowlist
@@ -33,14 +39,16 @@
 class AccessPolicy private constructor(
     private val schemePolicies: IndexedMap<String, IndexedMap<String, SchemePolicy>>
 ) {
+    @Suppress("UNCHECKED_CAST")
     constructor() : this(
-        IndexedMap<String, IndexedMap<String, SchemePolicy>>().apply {
-            fun addPolicy(policy: SchemePolicy) =
-                getOrPut(policy.subjectScheme) { IndexedMap() }.put(policy.objectScheme, policy)
-            addPolicy(UidPermissionPolicy())
-            addPolicy(UidAppOpPolicy())
+        MutableIndexedMap<String, MutableIndexedMap<String, SchemePolicy>>().apply {
+            fun addPolicy(policy: SchemePolicy) {
+                getOrPut(policy.subjectScheme) { MutableIndexedMap() }[policy.objectScheme] = policy
+            }
+            addPolicy(AppIdPermissionPolicy())
+            addPolicy(AppIdAppOpPolicy())
             addPolicy(PackageAppOpPolicy())
-        }
+        } as IndexedMap<String, IndexedMap<String, SchemePolicy>>
     )
 
     fun getSchemePolicy(subjectScheme: String, objectScheme: String): SchemePolicy =
@@ -48,15 +56,8 @@
             "Scheme policy for $subjectScheme and $objectScheme does not exist"
         }
 
-    fun GetStateScope.getDecision(subject: AccessUri, `object`: AccessUri): Int =
-        with(getSchemePolicy(subject, `object`)){ getDecision(subject, `object`) }
-
-    fun MutateStateScope.setDecision(subject: AccessUri, `object`: AccessUri, decision: Int) {
-        with(getSchemePolicy(subject, `object`)) { setDecision(subject, `object`, decision) }
-    }
-
     fun initialize(
-        state: AccessState,
+        state: MutableAccessState,
         userIds: IntSet,
         packageStates: Map<String, PackageState>,
         disabledSystemPackageStates: Map<String, PackageState>,
@@ -67,25 +68,24 @@
         permissionAllowlist: PermissionAllowlist,
         implicitToSourcePermissions: IndexedMap<String, IndexedListSet<String>>
     ) {
-        state.systemState.apply {
-            this.userIds += userIds
-            this.packageStates = packageStates
-            this.disabledSystemPackageStates = disabledSystemPackageStates
+        state.mutateExternalState().apply {
+            mutateUserIds() += userIds
+            setPackageStates(packageStates)
+            setDisabledSystemPackageStates(disabledSystemPackageStates)
             packageStates.forEach { (_, packageState) ->
-                appIds.getOrPut(packageState.appId) { IndexedListSet() }
+                mutateAppIdPackageNames()
+                    .mutateOrPut(packageState.appId) { MutableIndexedListSet() }
                     .add(packageState.packageName)
             }
-            this.knownPackages = knownPackages
-            this.isLeanback = isLeanback
-            this.configPermissions = configPermissions
-            this.privilegedPermissionAllowlistPackages = privilegedPermissionAllowlistPackages
-            this.permissionAllowlist = permissionAllowlist
-            this.implicitToSourcePermissions = implicitToSourcePermissions
+            setKnownPackages(knownPackages)
+            setLeanback(isLeanback)
+            setConfigPermissions(configPermissions)
+            setPrivilegedPermissionAllowlistPackages(privilegedPermissionAllowlistPackages)
+            setPermissionAllowlist(permissionAllowlist)
+            setImplicitToSourcePermissions(implicitToSourcePermissions)
         }
-        state.userStates.apply {
-            userIds.forEachIndexed { _, userId ->
-                this[userId] = UserState()
-            }
+        state.mutateUserStatesNoWrite().apply {
+            userIds.forEachIndexed { _, userId -> this[userId] = MutableUserState() }
         }
     }
 
@@ -102,16 +102,19 @@
     }
 
     fun MutateStateScope.onUserAdded(userId: Int) {
-        newState.systemState.userIds += userId
-        newState.userStates[userId] = UserState()
+        newState.mutateExternalState().mutateUserIds() += userId
+        newState.mutateUserStatesNoWrite()[userId] = MutableUserState()
         forEachSchemePolicy {
             with(it) { onUserAdded(userId) }
         }
+        newState.externalState.packageStates.forEach { (_, packageState) ->
+            upgradePackageVersion(packageState, userId)
+        }
     }
 
     fun MutateStateScope.onUserRemoved(userId: Int) {
-        newState.systemState.userIds -= userId
-        newState.userStates -= userId
+        newState.mutateExternalState().mutateUserIds() -= userId
+        newState.mutateUserStatesNoWrite() -= userId
         forEachSchemePolicy {
             with(it) { onUserRemoved(userId) }
         }
@@ -122,22 +125,31 @@
         disabledSystemPackageStates: Map<String, PackageState>,
         knownPackages: IntMap<Array<String>>,
         volumeUuid: String?,
+        packageNames: List<String>,
         isSystemUpdated: Boolean
     ) {
-        val addedAppIds = IntSet()
-        newState.systemState.apply {
-            this.packageStates = packageStates
-            this.disabledSystemPackageStates = disabledSystemPackageStates
+        val addedAppIds = MutableIntSet()
+        newState.mutateExternalState().apply {
+            setPackageStates(packageStates)
+            setDisabledSystemPackageStates(disabledSystemPackageStates)
             packageStates.forEach { (packageName, packageState) ->
                 if (packageState.volumeUuid == volumeUuid) {
+                    // The APK for a package on a mounted storage volume may still be unavailable
+                    // due to APK being deleted, e.g. after an OTA.
+                    check(
+                        packageState.androidPackage == null || packageNames.contains(packageName)
+                    ) {
+                        "Package $packageName on storage volume $volumeUuid didn't receive" +
+                            " onPackageAdded() before onStorageVolumeMounted()"
+                    }
                     val appId = packageState.appId
-                    appIds.getOrPut(appId) {
+                    mutateAppIdPackageNames().mutateOrPut(appId) {
                         addedAppIds += appId
-                        IndexedListSet()
+                        MutableIndexedListSet()
                     } += packageName
                 }
             }
-            this.knownPackages = knownPackages
+            setKnownPackages(knownPackages)
         }
         addedAppIds.forEachIndexed { _, appId ->
             forEachSchemePolicy {
@@ -145,7 +157,14 @@
             }
         }
         forEachSchemePolicy {
-            with(it) { onStorageVolumeMounted(volumeUuid, isSystemUpdated) }
+            with(it) { onStorageVolumeMounted(volumeUuid, packageNames, isSystemUpdated) }
+        }
+        packageStates.forEach { (_, packageState) ->
+            if (packageState.volumeUuid == volumeUuid) {
+                newState.userStates.forEachIndexed { _, userId, _ ->
+                    upgradePackageVersion(packageState, userId)
+                }
+            }
         }
     }
 
@@ -156,20 +175,19 @@
         packageName: String
     ) {
         val packageState = packageStates[packageName]
-        // TODO(zhanghai): STOPSHIP: Remove check before feature enable.
         checkNotNull(packageState) {
             "Added package $packageName isn't found in packageStates in onPackageAdded()"
         }
         val appId = packageState.appId
         var isAppIdAdded = false
-        newState.systemState.apply {
-            this.packageStates = packageStates
-            this.disabledSystemPackageStates = disabledSystemPackageStates
-            appIds.getOrPut(appId) {
+        newState.mutateExternalState().apply {
+            setPackageStates(packageStates)
+            setDisabledSystemPackageStates(disabledSystemPackageStates)
+            mutateAppIdPackageNames().mutateOrPut(appId) {
                 isAppIdAdded = true
-                IndexedListSet()
+                MutableIndexedListSet()
             } += packageName
-            this.knownPackages = knownPackages
+            setKnownPackages(knownPackages)
         }
         if (isAppIdAdded) {
             forEachSchemePolicy {
@@ -179,6 +197,9 @@
         forEachSchemePolicy {
             with(it) { onPackageAdded(packageState) }
         }
+        newState.userStates.forEachIndexed { _, userId, _ ->
+            upgradePackageVersion(packageState, userId)
+        }
     }
 
     fun MutateStateScope.onPackageRemoved(
@@ -188,22 +209,21 @@
         packageName: String,
         appId: Int
     ) {
-        // TODO(zhanghai): STOPSHIP: Remove check before feature enable.
         check(packageName !in packageStates) {
             "Removed package $packageName is still in packageStates in onPackageRemoved()"
         }
         var isAppIdRemoved = false
-        newState.systemState.apply {
-            this.packageStates = packageStates
-            this.disabledSystemPackageStates = disabledSystemPackageStates
-            appIds[appId]?.apply {
+        newState.mutateExternalState().apply {
+            setPackageStates(packageStates)
+            setDisabledSystemPackageStates(disabledSystemPackageStates)
+            mutateAppIdPackageNames().mutate(appId)?.apply {
                 this -= packageName
                 if (isEmpty()) {
-                    appIds -= appId
+                    mutateAppIdPackageNames() -= appId
                     isAppIdRemoved = true
                 }
             }
-            this.knownPackages = knownPackages
+            setKnownPackages(knownPackages)
         }
         forEachSchemePolicy {
             with(it) { onPackageRemoved(packageName, appId) }
@@ -213,6 +233,11 @@
                 with(it) { onAppIdRemoved(appId) }
             }
         }
+        newState.userStates.forEachIndexed { userStateIndex, _, userState ->
+            if (packageName in userState.packageVersions) {
+                newState.mutateUserStateAt(userStateIndex).mutatePackageVersions() -= packageName
+            }
+        }
     }
 
     fun MutateStateScope.onPackageInstalled(
@@ -222,13 +247,12 @@
         packageName: String,
         userId: Int
     ) {
-        newState.systemState.apply {
-            this.packageStates = packageStates
-            this.disabledSystemPackageStates = disabledSystemPackageStates
-            this.knownPackages = knownPackages
+        newState.mutateExternalState().apply {
+            setPackageStates(packageStates)
+            setDisabledSystemPackageStates(disabledSystemPackageStates)
+            setKnownPackages(knownPackages)
         }
         val packageState = packageStates[packageName]
-        // TODO(zhanghai): STOPSHIP: Remove check before feature enable.
         checkNotNull(packageState) {
             "Installed package $packageName isn't found in packageStates in onPackageInstalled()"
         }
@@ -245,24 +269,73 @@
         appId: Int,
         userId: Int
     ) {
-        newState.systemState.apply {
-            this.packageStates = packageStates
-            this.disabledSystemPackageStates = disabledSystemPackageStates
-            this.knownPackages = knownPackages
+        newState.mutateExternalState().apply {
+            setPackageStates(packageStates)
+            setDisabledSystemPackageStates(disabledSystemPackageStates)
+            setKnownPackages(knownPackages)
         }
         forEachSchemePolicy {
             with(it) { onPackageUninstalled(packageName, appId, userId) }
         }
     }
 
-    fun MutateStateScope.onSystemReady() {
-        newState.systemState.isSystemReady = true
+    fun MutateStateScope.onSystemReady(
+        packageStates: Map<String, PackageState>,
+        disabledSystemPackageStates: Map<String, PackageState>,
+        knownPackages: IntMap<Array<String>>
+    ) {
+        newState.mutateExternalState().apply {
+            setPackageStates(packageStates)
+            setDisabledSystemPackageStates(disabledSystemPackageStates)
+            setKnownPackages(knownPackages)
+            setSystemReady(true)
+        }
         forEachSchemePolicy {
             with(it) { onSystemReady() }
         }
     }
 
-    fun BinaryXmlPullParser.parseSystemState(state: AccessState) {
+    fun migrateSystemState(state: MutableAccessState) {
+        forEachSchemePolicy {
+            with(it) { migrateSystemState(state) }
+        }
+    }
+
+    fun migrateUserState(state: MutableAccessState, userId: Int) {
+        forEachSchemePolicy {
+            with(it) { migrateUserState(state, userId) }
+        }
+    }
+
+    private fun MutateStateScope.upgradePackageVersion(packageState: PackageState, userId: Int) {
+        if (packageState.androidPackage == null) {
+            return
+        }
+
+        val packageName = packageState.packageName
+        // The version would be latest when the package is new to the system, e.g. newly
+        // installed, first boot, or system apps added via OTA.
+        val version = newState.userStates[userId]!!.packageVersions[packageName]
+        when {
+            version == null ->
+                newState.mutateUserState(userId)!!.mutatePackageVersions()[packageName] =
+                    VERSION_LATEST
+            version < VERSION_LATEST -> {
+                forEachSchemePolicy {
+                    with(it) { upgradePackageState(packageState, userId, version) }
+                }
+                newState.mutateUserState(userId)!!.mutatePackageVersions()[packageName] =
+                    VERSION_LATEST
+            }
+            version == VERSION_LATEST -> {}
+            else -> Slog.w(
+                LOG_TAG, "Unexpected version $version for package $packageName," +
+                    "latest version is $VERSION_LATEST"
+            )
+        }
+    }
+
+    fun BinaryXmlPullParser.parseSystemState(state: MutableAccessState) {
         forEachTag {
             when (tagName) {
                 TAG_ACCESS -> {
@@ -272,7 +345,7 @@
                         }
                     }
                 }
-                else -> Log.w(LOG_TAG, "Ignoring unknown tag $tagName when parsing system state")
+                else -> Slog.w(LOG_TAG, "Ignoring unknown tag $tagName when parsing system state")
             }
         }
     }
@@ -285,18 +358,25 @@
         }
     }
 
-    fun BinaryXmlPullParser.parseUserState(state: AccessState, userId: Int) {
+    fun BinaryXmlPullParser.parseUserState(state: MutableAccessState, userId: Int) {
         forEachTag {
             when (tagName) {
                 TAG_ACCESS -> {
                     forEachTag {
-                        forEachSchemePolicy {
-                            with(it) { parseUserState(state, userId) }
+                        when (tagName) {
+                            TAG_PACKAGE_VERSIONS -> parsePackageVersions(state, userId)
+                            TAG_DEFAULT_PERMISSION_GRANT ->
+                                parseDefaultPermissionGrant(state, userId)
+                            else -> {
+                                forEachSchemePolicy {
+                                    with(it) { parseUserState(state, userId) }
+                                }
+                            }
                         }
                     }
                 }
                 else -> {
-                    Log.w(
+                    Slog.w(
                         LOG_TAG,
                         "Ignoring unknown tag $tagName when parsing user state for user $userId"
                     )
@@ -305,20 +385,82 @@
         }
     }
 
+    private fun BinaryXmlPullParser.parsePackageVersions(state: MutableAccessState, userId: Int) {
+        val userState = state.mutateUserState(userId, WriteMode.NONE)!!
+        val packageVersions = userState.mutatePackageVersions()
+        forEachTag {
+            when (tagName) {
+                TAG_PACKAGE -> parsePackageVersion(packageVersions)
+                else -> Slog.w(LOG_TAG, "Ignoring unknown tag $name when parsing package versions")
+            }
+        }
+        packageVersions.forEachReversedIndexed { packageVersionIndex, packageName, _ ->
+            if (packageName !in state.externalState.packageStates) {
+                Slog.w(LOG_TAG, "Dropping unknown $packageName when parsing package versions")
+                packageVersions.removeAt(packageVersionIndex)
+                userState.requestWriteMode(WriteMode.ASYNCHRONOUS)
+            }
+        }
+    }
+
+    private fun BinaryXmlPullParser.parsePackageVersion(
+        packageVersions: MutableIndexedMap<String, Int>
+    ) {
+        val packageName = getAttributeValueOrThrow(ATTR_NAME).intern()
+        val version = getAttributeIntOrThrow(ATTR_VERSION)
+        packageVersions[packageName] = version
+    }
+
+    private fun BinaryXmlPullParser.parseDefaultPermissionGrant(
+        state: MutableAccessState,
+        userId: Int
+    ) {
+        val userState = state.mutateUserState(userId, WriteMode.NONE)!!
+        val fingerprint = getAttributeValueOrThrow(ATTR_FINGERPRINT).intern()
+        userState.setDefaultPermissionGrantFingerprint(fingerprint)
+    }
+
     fun BinaryXmlSerializer.serializeUserState(state: AccessState, userId: Int) {
         tag(TAG_ACCESS) {
+            serializePackageVersions(state.userStates[userId]!!.packageVersions)
+            serializeDefaultPermissionGrantFingerprint(
+                state.userStates[userId]!!.defaultPermissionGrantFingerprint
+            )
             forEachSchemePolicy {
                 with(it) { serializeUserState(state, userId) }
             }
         }
     }
 
+    private fun BinaryXmlSerializer.serializePackageVersions(
+        packageVersions: IndexedMap<String, Int>
+    ) {
+        tag(TAG_PACKAGE_VERSIONS) {
+            packageVersions.forEachIndexed { _, packageName, version ->
+                tag(TAG_PACKAGE) {
+                    attributeInterned(ATTR_NAME, packageName)
+                    attributeInt(ATTR_VERSION, version)
+                }
+            }
+        }
+    }
+
+    private fun BinaryXmlSerializer.serializeDefaultPermissionGrantFingerprint(
+        fingerprint: String?
+    ) {
+        if (fingerprint != null) {
+            tag(TAG_DEFAULT_PERMISSION_GRANT) {
+                attributeInterned(ATTR_FINGERPRINT, fingerprint)
+            }
+        }
+    }
+
     private fun getSchemePolicy(subject: AccessUri, `object`: AccessUri): SchemePolicy =
         getSchemePolicy(subject.scheme, `object`.scheme)
 
     private inline fun forEachSchemePolicy(action: (SchemePolicy) -> Unit) {
-        schemePolicies.forEachValueIndexed { _, objectSchemePolicies ->
-            objectSchemePolicies.forEachValueIndexed { _, schemePolicy ->
+        schemePolicies.forEachIndexed { _, _, objectSchemePolicies ->
+            objectSchemePolicies.forEachIndexed { _, _, schemePolicy ->
                 action(schemePolicy)
             }
         }
@@ -327,7 +469,16 @@
     companion object {
         private val LOG_TAG = AccessPolicy::class.java.simpleName
 
+        internal const val VERSION_LATEST = 15
+
         private const val TAG_ACCESS = "access"
+        private const val TAG_DEFAULT_PERMISSION_GRANT = "default-permission-grant"
+        private const val TAG_PACKAGE_VERSIONS = "package-versions"
+        private const val TAG_PACKAGE = "package"
+
+        private const val ATTR_FINGERPRINT = "fingerprint"
+        private const val ATTR_NAME = "name"
+        private const val ATTR_VERSION = "version"
     }
 }
 
@@ -336,14 +487,6 @@
 
     abstract val objectScheme: String
 
-    abstract fun GetStateScope.getDecision(subject: AccessUri, `object`: AccessUri): Int
-
-    abstract fun MutateStateScope.setDecision(
-        subject: AccessUri,
-        `object`: AccessUri,
-        decision: Int
-    )
-
     open fun GetStateScope.onStateMutated() {}
 
     open fun MutateStateScope.onInitialized() {}
@@ -358,7 +501,8 @@
 
     open fun MutateStateScope.onStorageVolumeMounted(
         volumeUuid: String?,
-        isSystemUpdated: Boolean
+        packageNames: List<String>,
+        isSystemUpdated: Boolean,
     ) {}
 
     open fun MutateStateScope.onPackageAdded(packageState: PackageState) {}
@@ -371,11 +515,21 @@
 
     open fun MutateStateScope.onSystemReady() {}
 
-    open fun BinaryXmlPullParser.parseSystemState(state: AccessState) {}
+    open fun migrateSystemState(state: MutableAccessState) {}
+
+    open fun migrateUserState(state: MutableAccessState, userId: Int) {}
+
+    open fun MutateStateScope.upgradePackageState(
+        packageState: PackageState,
+        userId: Int,
+        version: Int
+    ) {}
+
+    open fun BinaryXmlPullParser.parseSystemState(state: MutableAccessState) {}
 
     open fun BinaryXmlSerializer.serializeSystemState(state: AccessState) {}
 
-    open fun BinaryXmlPullParser.parseUserState(state: AccessState, userId: Int) {}
+    open fun BinaryXmlPullParser.parseUserState(state: MutableAccessState, userId: Int) {}
 
     open fun BinaryXmlSerializer.serializeUserState(state: AccessState, userId: Int) {}
 }
diff --git a/services/permission/java/com/android/server/permission/access/AccessState.kt b/services/permission/java/com/android/server/permission/access/AccessState.kt
index 5532311..4ec32ea 100644
--- a/services/permission/java/com/android/server/permission/access/AccessState.kt
+++ b/services/permission/java/com/android/server/permission/access/AccessState.kt
@@ -18,124 +18,424 @@
 
 import android.content.pm.PermissionGroupInfo
 import com.android.server.SystemConfig
-import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
+import com.android.server.permission.access.immutable.* // ktlint-disable no-wildcard-imports
 import com.android.server.permission.access.permission.Permission
 import com.android.server.pm.permission.PermissionAllowlist
 import com.android.server.pm.pkg.PackageState
 
-class AccessState private constructor(
-    val systemState: SystemState,
-    val userStates: IntMap<UserState>
+private typealias ExternalStateReference = MutableReference<ExternalState, MutableExternalState>
+
+private typealias SystemStateReference = MutableReference<SystemState, MutableSystemState>
+
+typealias UserStates = IntReferenceMap<UserState, MutableUserState>
+typealias MutableUserStates = MutableIntReferenceMap<UserState, MutableUserState>
+private typealias UserStatesReference = MutableReference<UserStates, MutableUserStates>
+
+sealed class AccessState(
+    internal val externalStateReference: ExternalStateReference,
+    internal val systemStateReference: SystemStateReference,
+    internal val userStatesReference: UserStatesReference
+) : Immutable<MutableAccessState> {
+    val externalState: ExternalState
+        get() = externalStateReference.get()
+
+    val systemState: SystemState
+        get() = systemStateReference.get()
+
+    val userStates: UserStates
+        get() = userStatesReference.get()
+
+    override fun toMutable(): MutableAccessState = MutableAccessState(this)
+}
+
+class MutableAccessState private constructor(
+    externalStateReference: ExternalStateReference,
+    systemStateReference: SystemStateReference,
+    userStatesReference: UserStatesReference
+) : AccessState(
+    externalStateReference,
+    systemStateReference,
+    userStatesReference
 ) {
     constructor() : this(
-        SystemState(),
-        IntMap()
+        ExternalStateReference(MutableExternalState()),
+        SystemStateReference(MutableSystemState()),
+        UserStatesReference(MutableUserStates())
     )
 
-    fun copy(): AccessState = AccessState(
-        systemState.copy(),
-        userStates.copy { it.copy() }
+    internal constructor(accessState: AccessState) : this(
+        accessState.externalStateReference.toImmutable(),
+        accessState.systemStateReference.toImmutable(),
+        accessState.userStatesReference.toImmutable()
     )
+
+    fun mutateExternalState(): MutableExternalState = externalStateReference.mutate()
+
+    fun mutateSystemState(writeMode: Int = WriteMode.ASYNCHRONOUS): MutableSystemState =
+        systemStateReference.mutate().apply { requestWriteMode(writeMode) }
+
+    fun mutateUserStatesNoWrite(): MutableUserStates = userStatesReference.mutate()
+
+    fun mutateUserState(userId: Int, writeMode: Int = WriteMode.ASYNCHRONOUS): MutableUserState? =
+        mutateUserStatesNoWrite().mutate(userId)?.apply { requestWriteMode(writeMode) }
+
+    fun mutateUserStateAt(index: Int, writeMode: Int = WriteMode.ASYNCHRONOUS): MutableUserState =
+        mutateUserStatesNoWrite().mutateAt(index).apply { requestWriteMode(writeMode) }
 }
 
-class SystemState private constructor(
-    val userIds: IntSet,
-    var packageStates: Map<String, PackageState>,
-    var disabledSystemPackageStates: Map<String, PackageState>,
-    val appIds: IntMap<IndexedListSet<String>>,
-    // Mapping from KnownPackages keys to package names.
-    var knownPackages: IntMap<Array<String>>,
-    var isLeanback: Boolean,
-    var configPermissions: Map<String, SystemConfig.PermissionEntry>,
-    var privilegedPermissionAllowlistPackages: IndexedListSet<String>,
-    var permissionAllowlist: PermissionAllowlist,
-    var implicitToSourcePermissions: IndexedMap<String, IndexedListSet<String>>,
-    var isSystemReady: Boolean,
-    // TODO: Get and watch the state for deviceAndProfileOwners
-    // Mapping from user ID to package name.
-    var deviceAndProfileOwners: IntMap<String>,
-    val permissionGroups: IndexedMap<String, PermissionGroupInfo>,
-    val permissionTrees: IndexedMap<String, Permission>,
-    val permissions: IndexedMap<String, Permission>
-) : WritableState() {
+private typealias UserIdsReference = MutableReference<IntSet, MutableIntSet>
+
+typealias AppIdPackageNames = IntReferenceMap<IndexedListSet<String>, MutableIndexedListSet<String>>
+typealias MutableAppIdPackageNames =
+    MutableIntReferenceMap<IndexedListSet<String>, MutableIndexedListSet<String>>
+private typealias AppIdPackageNamesReference =
+    MutableReference<AppIdPackageNames, MutableAppIdPackageNames>
+
+sealed class ExternalState(
+    val userIdsReference: UserIdsReference,
+    packageStates: Map<String, PackageState>,
+    disabledSystemPackageStates: Map<String, PackageState>,
+    val appIdPackageNamesReference: AppIdPackageNamesReference,
+    knownPackages: IntMap<Array<String>>,
+    isLeanback: Boolean,
+    configPermissions: Map<String, SystemConfig.PermissionEntry>,
+    privilegedPermissionAllowlistPackages: IndexedListSet<String>,
+    permissionAllowlist: PermissionAllowlist,
+    implicitToSourcePermissions: IndexedMap<String, IndexedListSet<String>>,
+    isSystemReady: Boolean
+) : Immutable<MutableExternalState> {
+    val userIds: IntSet
+        get() = userIdsReference.get()
+
+    var packageStates: Map<String, PackageState> = packageStates
+        protected set
+
+    var disabledSystemPackageStates: Map<String, PackageState> = disabledSystemPackageStates
+        protected set
+
+    val appIdPackageNames: AppIdPackageNames
+        get() = appIdPackageNamesReference.get()
+
+    var knownPackages: IntMap<Array<String>> = knownPackages
+        protected set
+
+    var isLeanback: Boolean = isLeanback
+        protected set
+
+    var configPermissions: Map<String, SystemConfig.PermissionEntry> = configPermissions
+        protected set
+
+    var privilegedPermissionAllowlistPackages: IndexedListSet<String> =
+        privilegedPermissionAllowlistPackages
+        protected set
+
+    var permissionAllowlist: PermissionAllowlist = permissionAllowlist
+        protected set
+
+    var implicitToSourcePermissions: IndexedMap<String, IndexedListSet<String>> =
+        implicitToSourcePermissions
+        protected set
+
+    var isSystemReady: Boolean = isSystemReady
+        protected set
+
+    override fun toMutable(): MutableExternalState = MutableExternalState(this)
+}
+
+class MutableExternalState private constructor(
+    userIdsReference: UserIdsReference,
+    packageStates: Map<String, PackageState>,
+    disabledSystemPackageStates: Map<String, PackageState>,
+    appIdPackageNamesReference: AppIdPackageNamesReference,
+    knownPackages: IntMap<Array<String>>,
+    isLeanback: Boolean,
+    configPermissions: Map<String, SystemConfig.PermissionEntry>,
+    privilegedPermissionAllowlistPackages: IndexedListSet<String>,
+    permissionAllowlist: PermissionAllowlist,
+    implicitToSourcePermissions: IndexedMap<String, IndexedListSet<String>>,
+    isSystemReady: Boolean
+) : ExternalState(
+    userIdsReference,
+    packageStates,
+    disabledSystemPackageStates,
+    appIdPackageNamesReference,
+    knownPackages,
+    isLeanback,
+    configPermissions,
+    privilegedPermissionAllowlistPackages,
+    permissionAllowlist,
+    implicitToSourcePermissions,
+    isSystemReady
+) {
     constructor() : this(
-        IntSet(),
+        UserIdsReference(MutableIntSet()),
         emptyMap(),
         emptyMap(),
-        IntMap(),
-        IntMap(),
+        AppIdPackageNamesReference(MutableAppIdPackageNames()),
+        MutableIntMap(),
         false,
         emptyMap(),
-        IndexedListSet(),
+        MutableIndexedListSet(),
         PermissionAllowlist(),
-        IndexedMap(),
-        false,
-        IntMap(),
-        IndexedMap(),
-        IndexedMap(),
-        IndexedMap()
+        MutableIndexedMap(),
+        false
     )
 
-    fun copy(): SystemState =
-        SystemState(
-            userIds.copy(),
-            packageStates,
-            disabledSystemPackageStates,
-            appIds.copy { it.copy() },
-            knownPackages,
-            isLeanback,
-            configPermissions,
-            privilegedPermissionAllowlistPackages,
-            permissionAllowlist,
-            implicitToSourcePermissions,
-            isSystemReady,
-            deviceAndProfileOwners,
-            permissionGroups.copy { it },
-            permissionTrees.copy { it },
-            permissions.copy { it }
-        )
+    internal constructor(externalState: ExternalState) : this(
+        externalState.userIdsReference.toImmutable(),
+        externalState.packageStates,
+        externalState.disabledSystemPackageStates,
+        externalState.appIdPackageNamesReference.toImmutable(),
+        externalState.knownPackages,
+        externalState.isLeanback,
+        externalState.configPermissions,
+        externalState.privilegedPermissionAllowlistPackages,
+        externalState.permissionAllowlist,
+        externalState.implicitToSourcePermissions,
+        externalState.isSystemReady
+    )
+
+    fun mutateUserIds(): MutableIntSet = userIdsReference.mutate()
+
+    @JvmName("setPackageStatesPublic")
+    fun setPackageStates(packageStates: Map<String, PackageState>) {
+        this.packageStates = packageStates
+    }
+
+    @JvmName("setDisabledSystemPackageStatesPublic")
+    fun setDisabledSystemPackageStates(disabledSystemPackageStates: Map<String, PackageState>) {
+        this.disabledSystemPackageStates = disabledSystemPackageStates
+    }
+
+    fun mutateAppIdPackageNames(): MutableAppIdPackageNames = appIdPackageNamesReference.mutate()
+
+    @JvmName("setKnownPackagesPublic")
+    fun setKnownPackages(knownPackages: IntMap<Array<String>>) {
+        this.knownPackages = knownPackages
+    }
+
+    @JvmName("setLeanbackPublic")
+    fun setLeanback(isLeanback: Boolean) {
+        this.isLeanback = isLeanback
+    }
+
+    @JvmName("setConfigPermissionsPublic")
+    fun setConfigPermissions(configPermissions: Map<String, SystemConfig.PermissionEntry>) {
+        this.configPermissions = configPermissions
+    }
+
+    @JvmName("setPrivilegedPermissionAllowlistPackagesPublic")
+    fun setPrivilegedPermissionAllowlistPackages(
+        privilegedPermissionAllowlistPackages: IndexedListSet<String>
+    ) {
+        this.privilegedPermissionAllowlistPackages = privilegedPermissionAllowlistPackages
+    }
+
+    @JvmName("setPermissionAllowlistPublic")
+    fun setPermissionAllowlist(permissionAllowlist: PermissionAllowlist) {
+        this.permissionAllowlist = permissionAllowlist
+    }
+
+    @JvmName("setImplicitToSourcePermissionsPublic")
+    fun setImplicitToSourcePermissions(
+        implicitToSourcePermissions: IndexedMap<String, IndexedListSet<String>>
+    ) {
+        this.implicitToSourcePermissions = implicitToSourcePermissions
+    }
+
+    @JvmName("setSystemReadyPublic")
+    fun setSystemReady(isSystemReady: Boolean) {
+        this.isSystemReady = isSystemReady
+    }
 }
 
-class UserState private constructor(
-    // A map of (appId to a map of (permissionName to permissionFlags))
-    val uidPermissionFlags: IntMap<IndexedMap<String, Int>>,
-    // appId -> opName -> opCode
-    val uidAppOpModes: IntMap<IndexedMap<String, Int>>,
-    // packageName -> opName -> opCode
-    val packageAppOpModes: IndexedMap<String, IndexedMap<String, Int>>
-) : WritableState() {
+private typealias PermissionGroupsReference = MutableReference<
+    IndexedMap<String, PermissionGroupInfo>, MutableIndexedMap<String, PermissionGroupInfo>
+>
+
+private typealias PermissionTreesReference =
+    MutableReference<IndexedMap<String, Permission>, MutableIndexedMap<String, Permission>>
+
+private typealias PermissionsReference =
+    MutableReference<IndexedMap<String, Permission>, MutableIndexedMap<String, Permission>>
+
+sealed class SystemState(
+    val permissionGroupsReference: PermissionGroupsReference,
+    val permissionTreesReference: PermissionTreesReference,
+    val permissionsReference: PermissionsReference,
+    writeMode: Int
+) : WritableState, Immutable<MutableSystemState> {
+    val permissionGroups: IndexedMap<String, PermissionGroupInfo>
+        get() = permissionGroupsReference.get()
+
+    val permissionTrees: IndexedMap<String, Permission>
+        get() = permissionTreesReference.get()
+
+    val permissions: IndexedMap<String, Permission>
+        get() = permissionsReference.get()
+
+    override var writeMode: Int = writeMode
+        protected set
+
+    override fun toMutable(): MutableSystemState = MutableSystemState(this)
+}
+
+class MutableSystemState private constructor(
+    permissionGroupsReference: PermissionGroupsReference,
+    permissionTreesReference: PermissionTreesReference,
+    permissionsReference: PermissionsReference,
+    writeMode: Int
+) : SystemState(
+    permissionGroupsReference,
+    permissionTreesReference,
+    permissionsReference,
+    writeMode
+), MutableWritableState {
     constructor() : this(
-        IntMap(),
-        IntMap(),
-        IndexedMap()
+        PermissionGroupsReference(MutableIndexedMap()),
+        PermissionTreesReference(MutableIndexedMap()),
+        PermissionsReference(MutableIndexedMap()),
+        WriteMode.NONE
     )
 
-    fun copy(): UserState = UserState(
-        uidPermissionFlags.copy { it.copy { it } },
-        uidAppOpModes.copy { it.copy { it } },
-        packageAppOpModes.copy { it.copy { it } }
+    internal constructor(systemState: SystemState) : this(
+        systemState.permissionGroupsReference.toImmutable(),
+        systemState.permissionTreesReference.toImmutable(),
+        systemState.permissionsReference.toImmutable(),
+        WriteMode.NONE
     )
+
+    fun mutatePermissionGroups(): MutableIndexedMap<String, PermissionGroupInfo> =
+        permissionGroupsReference.mutate()
+
+    fun mutatePermissionTrees(): MutableIndexedMap<String, Permission> =
+        permissionTreesReference.mutate()
+
+    fun mutatePermissions(): MutableIndexedMap<String, Permission> =
+        permissionsReference.mutate()
+
+    override fun requestWriteMode(writeMode: Int) {
+        this.writeMode = maxOf(this.writeMode, writeMode)
+    }
+}
+
+private typealias PackageVersionsReference =
+    MutableReference<IndexedMap<String, Int>, MutableIndexedMap<String, Int>>
+
+typealias AppIdPermissionFlags =
+    IntReferenceMap<IndexedMap<String, Int>, MutableIndexedMap<String, Int>>
+typealias MutableAppIdPermissionFlags =
+    MutableIntReferenceMap<IndexedMap<String, Int>, MutableIndexedMap<String, Int>>
+private typealias AppIdPermissionFlagsReference =
+    MutableReference<AppIdPermissionFlags, MutableAppIdPermissionFlags>
+
+typealias AppIdAppOpModes =
+    IntReferenceMap<IndexedMap<String, Int>, MutableIndexedMap<String, Int>>
+typealias MutableAppIdAppOpModes =
+    MutableIntReferenceMap<IndexedMap<String, Int>, MutableIndexedMap<String, Int>>
+private typealias AppIdAppOpModesReference =
+    MutableReference<AppIdAppOpModes, MutableAppIdAppOpModes>
+
+typealias PackageAppOpModes =
+    IndexedReferenceMap<String, IndexedMap<String, Int>, MutableIndexedMap<String, Int>>
+typealias MutablePackageAppOpModes =
+    MutableIndexedReferenceMap<String, IndexedMap<String, Int>, MutableIndexedMap<String, Int>>
+private typealias PackageAppOpModesReference =
+    MutableReference<PackageAppOpModes, MutablePackageAppOpModes>
+
+sealed class UserState(
+    internal val packageVersionsReference: PackageVersionsReference,
+    internal val appIdPermissionFlagsReference: AppIdPermissionFlagsReference,
+    internal val appIdAppOpModesReference: AppIdAppOpModesReference,
+    internal val packageAppOpModesReference: PackageAppOpModesReference,
+    defaultPermissionGrantFingerprint: String?,
+    writeMode: Int
+) : WritableState, Immutable<MutableUserState> {
+    val packageVersions: IndexedMap<String, Int>
+        get() = packageVersionsReference.get()
+
+    val appIdPermissionFlags: AppIdPermissionFlags
+        get() = appIdPermissionFlagsReference.get()
+
+    val appIdAppOpModes: AppIdAppOpModes
+        get() = appIdAppOpModesReference.get()
+
+    val packageAppOpModes: PackageAppOpModes
+        get() = packageAppOpModesReference.get()
+
+    var defaultPermissionGrantFingerprint: String? = defaultPermissionGrantFingerprint
+        protected set
+
+    override var writeMode: Int = writeMode
+        protected set
+
+    override fun toMutable(): MutableUserState = MutableUserState(this)
+}
+
+class MutableUserState private constructor(
+    packageVersionsReference: PackageVersionsReference,
+    appIdPermissionFlagsReference: AppIdPermissionFlagsReference,
+    appIdAppOpModesReference: AppIdAppOpModesReference,
+    packageAppOpModesReference: PackageAppOpModesReference,
+    defaultPermissionGrantFingerprint: String?,
+    writeMode: Int
+) : UserState(
+    packageVersionsReference,
+    appIdPermissionFlagsReference,
+    appIdAppOpModesReference,
+    packageAppOpModesReference,
+    defaultPermissionGrantFingerprint,
+    writeMode
+), MutableWritableState {
+    constructor() : this(
+        PackageVersionsReference(MutableIndexedMap<String, Int>()),
+        AppIdPermissionFlagsReference(MutableAppIdPermissionFlags()),
+        AppIdAppOpModesReference(MutableAppIdAppOpModes()),
+        PackageAppOpModesReference(MutablePackageAppOpModes()),
+        null,
+        WriteMode.NONE
+    )
+
+    internal constructor(userState: UserState) : this(
+        userState.packageVersionsReference.toImmutable(),
+        userState.appIdPermissionFlagsReference.toImmutable(),
+        userState.appIdAppOpModesReference.toImmutable(),
+        userState.packageAppOpModesReference.toImmutable(),
+        userState.defaultPermissionGrantFingerprint,
+        WriteMode.NONE
+    )
+
+    fun mutatePackageVersions(): MutableIndexedMap<String, Int> = packageVersionsReference.mutate()
+
+    fun mutateAppIdPermissionFlags(): MutableAppIdPermissionFlags =
+        appIdPermissionFlagsReference.mutate()
+
+    fun mutateAppIdAppOpModes(): MutableAppIdAppOpModes = appIdAppOpModesReference.mutate()
+
+    fun mutatePackageAppOpModes(): MutablePackageAppOpModes = packageAppOpModesReference.mutate()
+
+    @JvmName("setDefaultPermissionGrantFingerprintPublic")
+    fun setDefaultPermissionGrantFingerprint(defaultPermissionGrantFingerprint: String?) {
+        this.defaultPermissionGrantFingerprint = defaultPermissionGrantFingerprint
+    }
+
+    override fun requestWriteMode(writeMode: Int) {
+        this.writeMode = maxOf(this.writeMode, writeMode)
+    }
 }
 
 object WriteMode {
     const val NONE = 0
-    const val SYNC = 1
-    const val ASYNC = 2
+    const val ASYNCHRONOUS = 1
+    const val SYNCHRONOUS = 2
 }
 
-abstract class WritableState {
-    var writeMode: Int = WriteMode.NONE
-        private set
+interface WritableState {
+    val writeMode: Int
+}
 
-    fun requestWrite(sync: Boolean = false) {
-        if (sync) {
-            writeMode = WriteMode.SYNC
-        } else {
-            if (writeMode != WriteMode.SYNC) {
-                writeMode = WriteMode.ASYNC
-            }
-        }
-    }
+interface MutableWritableState : WritableState {
+    fun requestWriteMode(writeMode: Int)
 }
 
 open class GetStateScope(
@@ -144,5 +444,5 @@
 
 class MutateStateScope(
     val oldState: AccessState,
-    val newState: AccessState
+    val newState: MutableAccessState
 ) : GetStateScope(newState)
diff --git a/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpMigration.kt b/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpMigration.kt
new file mode 100644
index 0000000..96d315e
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpMigration.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.server.permission.access.appop
+
+import android.os.Process
+import android.util.Slog
+import com.android.server.LocalServices
+import com.android.server.appop.AppOpMigrationHelper
+import com.android.server.permission.access.MutableAccessState
+import com.android.server.permission.access.immutable.* // ktlint-disable no-wildcard-imports
+import com.android.server.permission.access.util.PackageVersionMigration
+
+class AppIdAppOpMigration {
+    fun migrateUserState(state: MutableAccessState, userId: Int) {
+        val legacyAppOpsManager = LocalServices.getService(AppOpMigrationHelper::class.java)!!
+        if (!legacyAppOpsManager.hasLegacyAppOpState()) {
+            return
+        }
+
+        val legacyAppIdAppOpModes = legacyAppOpsManager.getLegacyAppIdAppOpModes(userId)
+        val version = PackageVersionMigration.getVersion(userId)
+
+        val userState = state.mutateUserState(userId)!!
+        val appIdAppOpModes = userState.mutateAppIdAppOpModes()
+        legacyAppIdAppOpModes.forEach { (appId, legacyAppOpModes) ->
+            val packageNames = state.externalState.appIdPackageNames[appId]
+            // Non-application UIDs may not have an Android package but may still have app op state.
+            if (packageNames == null && appId >= Process.FIRST_APPLICATION_UID) {
+                Slog.w(LOG_TAG, "Dropping unknown app ID $appId when migrating app op state")
+                return@forEach
+            }
+
+            val appOpModes = MutableIndexedMap<String, Int>()
+            appIdAppOpModes[appId] = appOpModes
+            legacyAppOpModes.forEach { (appOpName, appOpMode) ->
+                appOpModes[appOpName] = appOpMode
+            }
+
+            if (packageNames != null) {
+                val packageVersions = userState.mutatePackageVersions()
+                packageNames.forEachIndexed { _, packageName ->
+                    packageVersions[packageName] = version
+                }
+            }
+        }
+    }
+
+    companion object {
+        private val LOG_TAG = AppIdAppOpMigration::class.java.simpleName
+    }
+}
diff --git a/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPersistence.kt b/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPersistence.kt
new file mode 100644
index 0000000..4c7e946
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPersistence.kt
@@ -0,0 +1,100 @@
+/*
+ * 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.permission.access.appop
+
+import android.os.Process
+import android.util.Slog
+import com.android.modules.utils.BinaryXmlPullParser
+import com.android.modules.utils.BinaryXmlSerializer
+import com.android.server.permission.access.AccessState
+import com.android.server.permission.access.AppIdAppOpModes
+import com.android.server.permission.access.MutableAccessState
+import com.android.server.permission.access.MutableAppIdAppOpModes
+import com.android.server.permission.access.WriteMode
+import com.android.server.permission.access.immutable.* // ktlint-disable no-wildcard-imports
+import com.android.server.permission.access.util.attributeInt
+import com.android.server.permission.access.util.forEachTag
+import com.android.server.permission.access.util.getAttributeIntOrThrow
+import com.android.server.permission.access.util.tag
+import com.android.server.permission.access.util.tagName
+
+class AppIdAppOpPersistence : BaseAppOpPersistence() {
+    override fun BinaryXmlPullParser.parseUserState(state: MutableAccessState, userId: Int) {
+        when (tagName) {
+            TAG_APP_ID_APP_OPS -> parseAppIdAppOps(state, userId)
+            else -> {}
+        }
+    }
+
+    private fun BinaryXmlPullParser.parseAppIdAppOps(state: MutableAccessState, userId: Int) {
+        val userState = state.mutateUserState(userId, WriteMode.NONE)!!
+        val appIdAppOpModes = userState.mutateAppIdAppOpModes()
+        forEachTag {
+            when (tagName) {
+                TAG_APP_ID -> parseAppId(appIdAppOpModes)
+                else -> Slog.w(LOG_TAG, "Ignoring unknown tag $name when parsing app-op state")
+            }
+        }
+        userState.appIdAppOpModes.forEachReversedIndexed { appIdIndex, appId, _ ->
+            // Non-application UIDs may not have an Android package but may still have app op state.
+            if (appId !in state.externalState.appIdPackageNames &&
+                appId >= Process.FIRST_APPLICATION_UID) {
+                Slog.w(LOG_TAG, "Dropping unknown app ID $appId when parsing app-op state")
+                appIdAppOpModes.removeAt(appIdIndex)
+                userState.requestWriteMode(WriteMode.ASYNCHRONOUS)
+            }
+        }
+    }
+
+    private fun BinaryXmlPullParser.parseAppId(appIdAppOpModes: MutableAppIdAppOpModes) {
+        val appId = getAttributeIntOrThrow(ATTR_ID)
+        val appOpModes = MutableIndexedMap<String, Int>()
+        appIdAppOpModes[appId] = appOpModes
+        parseAppOps(appOpModes)
+    }
+
+    override fun BinaryXmlSerializer.serializeUserState(state: AccessState, userId: Int) {
+        serializeAppIdAppOps(state.userStates[userId]!!.appIdAppOpModes)
+    }
+
+    private fun BinaryXmlSerializer.serializeAppIdAppOps(appIdAppOpModes: AppIdAppOpModes) {
+        tag(TAG_APP_ID_APP_OPS) {
+            appIdAppOpModes.forEachIndexed { _, appId, appOpModes ->
+                serializeAppId(appId, appOpModes)
+            }
+        }
+    }
+
+    private fun BinaryXmlSerializer.serializeAppId(
+        appId: Int,
+        appOpModes: IndexedMap<String, Int>
+    ) {
+        tag(TAG_APP_ID) {
+            attributeInt(ATTR_ID, appId)
+            serializeAppOps(appOpModes)
+        }
+    }
+
+    companion object {
+        private val LOG_TAG = AppIdAppOpPersistence::class.java.simpleName
+
+        private const val TAG_APP_ID = "app-id"
+        private const val TAG_APP_ID_APP_OPS = "app-id-app-ops"
+
+        private const val ATTR_ID = "id"
+    }
+}
diff --git a/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPolicy.kt b/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPolicy.kt
new file mode 100644
index 0000000..c02fe4d
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPolicy.kt
@@ -0,0 +1,151 @@
+/*
+ * 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.permission.access.appop
+
+import android.app.AppOpsManager
+import com.android.server.permission.access.GetStateScope
+import com.android.server.permission.access.MutableAccessState
+import com.android.server.permission.access.MutateStateScope
+import com.android.server.permission.access.UidUri
+import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
+import com.android.server.permission.access.immutable.* // ktlint-disable no-wildcard-imports
+import com.android.server.pm.pkg.PackageState
+
+class AppIdAppOpPolicy : BaseAppOpPolicy(AppIdAppOpPersistence()) {
+    private val migration = AppIdAppOpMigration()
+
+    private val upgrade = AppIdAppOpUpgrade(this)
+
+    @Volatile
+    private var onAppOpModeChangedListeners: IndexedListSet<OnAppOpModeChangedListener> =
+        MutableIndexedListSet()
+    private val onAppOpModeChangedListenersLock = Any()
+
+    override val subjectScheme: String
+        get() = UidUri.SCHEME
+
+    override fun GetStateScope.onStateMutated() {
+        onAppOpModeChangedListeners.forEachIndexed { _, it -> it.onStateMutated() }
+    }
+
+    override fun MutateStateScope.onAppIdRemoved(appId: Int) {
+        newState.userStates.forEachIndexed { userStateIndex, _, userState ->
+            val appIdIndex = userState.appIdAppOpModes.indexOfKey(appId)
+            if (appIdIndex >= 0) {
+                newState.mutateUserStateAt(userStateIndex).mutateAppIdAppOpModes()
+                    .removeAt(appIdIndex)
+                // Skip notifying the change listeners since the app ID no longer exists.
+            }
+        }
+    }
+
+    fun GetStateScope.getAppOpModes(appId: Int, userId: Int): IndexedMap<String, Int>? =
+        state.userStates[userId]?.appIdAppOpModes?.get(appId)
+
+    fun MutateStateScope.removeAppOpModes(appId: Int, userId: Int): Boolean {
+        val userStateIndex = newState.userStates.indexOfKey(userId)
+        if (userStateIndex < 0) {
+            return false
+        }
+        val appIdIndex = newState.userStates.valueAt(userStateIndex).appIdAppOpModes
+            .indexOfKey(appId)
+        if (appIdIndex < 0) {
+            return false
+        }
+        newState.mutateUserStateAt(userStateIndex).mutateAppIdAppOpModes().removeAt(appIdIndex)
+        return true
+    }
+
+    fun GetStateScope.getAppOpMode(appId: Int, userId: Int, appOpName: String): Int =
+        state.userStates[userId]?.appIdAppOpModes?.get(appId)
+            .getWithDefault(appOpName, AppOpsManager.opToDefaultMode(appOpName))
+
+    fun MutateStateScope.setAppOpMode(
+        appId: Int,
+        userId: Int,
+        appOpName: String,
+        mode: Int
+    ): Boolean {
+        val defaultMode = AppOpsManager.opToDefaultMode(appOpName)
+        val oldMode = newState.userStates[userId]!!.appIdAppOpModes[appId]
+            .getWithDefault(appOpName, defaultMode)
+        if (oldMode == mode) {
+            return false
+        }
+        val appIdAppOpModes = newState.mutateUserState(userId)!!.mutateAppIdAppOpModes()
+        val appOpModes = appIdAppOpModes.mutateOrPut(appId) { MutableIndexedMap() }
+        appOpModes.putWithDefault(appOpName, mode, defaultMode)
+        if (appOpModes.isEmpty()) {
+            appIdAppOpModes -= appId
+        }
+        onAppOpModeChangedListeners.forEachIndexed { _, it ->
+            it.onAppOpModeChanged(appId, userId, appOpName, oldMode, mode)
+        }
+        return true
+    }
+
+    fun addOnAppOpModeChangedListener(listener: OnAppOpModeChangedListener) {
+        synchronized(onAppOpModeChangedListenersLock) {
+            onAppOpModeChangedListeners = onAppOpModeChangedListeners + listener
+        }
+    }
+
+    fun removeOnAppOpModeChangedListener(listener: OnAppOpModeChangedListener) {
+        synchronized(onAppOpModeChangedListenersLock) {
+            onAppOpModeChangedListeners = onAppOpModeChangedListeners - listener
+        }
+    }
+
+    override fun migrateUserState(state: MutableAccessState, userId: Int) {
+        with(migration) { migrateUserState(state, userId) }
+    }
+
+    override fun MutateStateScope.upgradePackageState(
+        packageState: PackageState,
+        userId: Int,
+        version: Int,
+    ) {
+        with(upgrade) { upgradePackageState(packageState, userId, version) }
+    }
+
+    /**
+     * Listener for app op mode changes.
+     */
+    abstract class OnAppOpModeChangedListener {
+        /**
+         * Called when an app op mode change has been made to the upcoming new state.
+         *
+         * Implementations should keep this method fast to avoid stalling the locked state mutation,
+         * and only call external code after [onStateMutated] when the new state has actually become
+         * the current state visible to external code.
+         */
+        abstract fun onAppOpModeChanged(
+            appId: Int,
+            userId: Int,
+            appOpName: String,
+            oldMode: Int,
+            newMode: Int
+        )
+
+        /**
+         * Called when the upcoming new state has become the current state.
+         *
+         * Implementations should keep this method fast to avoid stalling the locked state mutation.
+         */
+        abstract fun onStateMutated()
+    }
+}
diff --git a/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpUpgrade.kt b/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpUpgrade.kt
new file mode 100644
index 0000000..12df95e
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpUpgrade.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.server.permission.access.appop
+
+import android.app.AppOpsManager
+import com.android.server.permission.access.MutateStateScope
+import com.android.server.pm.pkg.PackageState
+
+class AppIdAppOpUpgrade(private val policy: AppIdAppOpPolicy) {
+    fun MutateStateScope.upgradePackageState(
+        packageState: PackageState,
+        userId: Int,
+        version: Int,
+    ) {
+        if (version <= 2) {
+            with(policy) {
+                val appOpMode = getAppOpMode(
+                    packageState.appId, userId, AppOpsManager.OPSTR_RUN_IN_BACKGROUND
+                )
+                setAppOpMode(
+                    packageState.appId, userId, AppOpsManager.OPSTR_RUN_ANY_IN_BACKGROUND, appOpMode
+                )
+            }
+        }
+        if (version <= 13) {
+            val permissionName = AppOpsManager.opToPermission(AppOpsManager.OP_SCHEDULE_EXACT_ALARM)
+            if (permissionName in packageState.androidPackage!!.requestedPermissions) {
+                with(policy) {
+                    val appOpMode = getAppOpMode(
+                        packageState.appId, userId, AppOpsManager.OPSTR_SCHEDULE_EXACT_ALARM
+                    )
+                    val defaultAppOpMode =
+                        AppOpsManager.opToDefaultMode(AppOpsManager.OP_SCHEDULE_EXACT_ALARM)
+                    if (appOpMode == defaultAppOpMode) {
+                        setAppOpMode(
+                            packageState.appId, userId, AppOpsManager.OPSTR_SCHEDULE_EXACT_ALARM,
+                            AppOpsManager.MODE_ALLOWED
+                        )
+                    }
+                }
+            }
+        }
+    }
+}
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 af85eba..5b91ad9 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
@@ -16,82 +16,70 @@
 
 package com.android.server.permission.access.appop
 
-import android.Manifest
-import android.annotation.UserIdInt
-import android.app.AppGlobals
 import android.app.AppOpsManager
-import android.content.pm.PackageManager
-import android.os.Binder
 import android.os.Handler
-import android.os.RemoteException
 import android.os.UserHandle
+import android.util.ArrayMap
+import android.util.ArraySet
 import android.util.SparseBooleanArray
 import android.util.SparseIntArray
 import com.android.internal.annotations.VisibleForTesting
-import com.android.internal.util.ArrayUtils
-import com.android.internal.util.function.pooled.PooledLambda
 import com.android.server.appop.AppOpsCheckingServiceInterface
-import com.android.server.appop.OnOpModeChangedListener
+import com.android.server.appop.AppOpsCheckingServiceInterface.AppOpsModeChangedListener
 import com.android.server.permission.access.AccessCheckingService
 import com.android.server.permission.access.AppOpUri
 import com.android.server.permission.access.PackageUri
 import com.android.server.permission.access.UidUri
-import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
-import com.android.server.permission.access.util.hasBits
-import libcore.util.EmptyArray
-import java.io.PrintWriter
+import com.android.server.permission.access.collection.forEachIndexed
+import com.android.server.permission.access.collection.set
 
 class AppOpService(
     private val service: AccessCheckingService
 ) : AppOpsCheckingServiceInterface {
     private val packagePolicy = service.getSchemePolicy(PackageUri.SCHEME, AppOpUri.SCHEME)
         as PackageAppOpPolicy
-    private val uidPolicy = service.getSchemePolicy(UidUri.SCHEME, AppOpUri.SCHEME)
-        as UidAppOpPolicy
+    private val appIdPolicy = service.getSchemePolicy(UidUri.SCHEME, AppOpUri.SCHEME)
+        as AppIdAppOpPolicy
 
     private val context = service.context
     private lateinit var handler: Handler
-    private lateinit var lock: Any
-    private lateinit var switchedOps: IntMap<IntArray>
+
+    @Volatile
+    private var listeners = ArraySet<AppOpsModeChangedListener>()
+    private val listenersLock = Any()
 
     fun initialize() {
         // TODO(b/252883039): Wrong handler. Inject main thread handler here.
         handler = Handler(context.mainLooper)
-        // TODO(b/252883039): Wrong lock object. Inject AppOpsService here.
-        lock = Any()
 
-        switchedOps = IntMap()
-        for (switchedCode in 0 until AppOpsManager._NUM_OP) {
-            val switchCode = AppOpsManager.opToSwitch(switchedCode)
-            switchedOps.put(switchCode,
-                ArrayUtils.appendInt(switchedOps.get(switchCode), switchedCode))
-        }
+        appIdPolicy.addOnAppOpModeChangedListener(OnAppIdAppOpModeChangedListener())
+        packagePolicy.addOnAppOpModeChangedListener(OnPackageAppOpModeChangedListener())
     }
 
     @VisibleForTesting
     override fun writeState() {
-        // TODO Not yet implemented
+        // Not implemented because writes are handled automatically.
     }
 
     override fun readState() {
-        // TODO Not yet implemented
+        // Not implemented because reads are handled automatically.
     }
 
     @VisibleForTesting
     override fun shutdown() {
-        // TODO Not yet implemented
+        // Not implemented because writes are handled automatically.
     }
 
     override fun systemReady() {
-        // TODO Not yet implemented
+        // Not implemented because upgrades are handled automatically.
     }
 
     override fun getNonDefaultUidModes(uid: Int): SparseIntArray {
-        return opNameMapToOpIntMap(getUidModes(uid))
+        return opNameMapToOpSparseArray(getUidModes(uid))
     }
 
     override fun getNonDefaultPackageModes(packageName: String, userId: Int): SparseIntArray {
-        return opNameMapToOpIntMap(getPackageModes(packageName, userId))
+        return opNameMapToOpSparseArray(getPackageModes(packageName, userId))
     }
 
     override fun getUidMode(uid: Int, op: Int): Int {
@@ -99,16 +87,14 @@
         val userId = UserHandle.getUserId(uid)
         val opName = AppOpsManager.opToPublicName(op)
         return service.getState {
-            with(uidPolicy) { getAppOpMode(appId, userId, opName) }
+            with(appIdPolicy) { getAppOpMode(appId, userId, opName) }
         }
     }
 
-    private fun getUidModes(uid: Int): IndexedMap<String, Int>? {
+    private fun getUidModes(uid: Int): ArrayMap<String, Int>? {
         val appId = UserHandle.getAppId(uid)
         val userId = UserHandle.getUserId(uid)
-        return service.getState {
-            with(uidPolicy) { getAppOpModes(appId, userId) }
-        }
+        return service.getState { with(appIdPolicy) { getAppOpModes(appId, userId) } }?.map
     }
 
     override fun setUidMode(uid: Int, op: Int, mode: Int): Boolean {
@@ -117,7 +103,7 @@
         val opName = AppOpsManager.opToPublicName(op)
         var wasChanged = false
         service.mutateState {
-            wasChanged = with(uidPolicy) { setAppOpMode(appId, userId, opName, mode) }
+            wasChanged = with(appIdPolicy) { setAppOpMode(appId, userId, opName, mode) }
         }
         return wasChanged
     }
@@ -132,8 +118,8 @@
     private fun getPackageModes(
         packageName: String,
         userId: Int
-    ): IndexedMap<String, Int>? =
-        service.getState { with(packagePolicy) { getAppOpModes(packageName, userId) } }
+    ): ArrayMap<String, Int>? =
+        service.getState { with(packagePolicy) { getAppOpModes(packageName, userId) } }?.map
 
     override fun setPackageMode(packageName: String, op: Int, mode: Int, userId: Int) {
         val opName = AppOpsManager.opToPublicName(op)
@@ -146,7 +132,7 @@
         val appId = UserHandle.getAppId(uid)
         val userId = UserHandle.getUserId(uid)
         service.mutateState {
-            with(uidPolicy) { removeAppOpModes(appId, userId) }
+            with(appIdPolicy) { removeAppOpModes(appId, userId) }
         }
     }
 
@@ -158,15 +144,15 @@
         return wasChanged
     }
 
-    private fun opNameMapToOpIntMap(modes: IndexedMap<String, Int>?): SparseIntArray =
+    private fun opNameMapToOpSparseArray(modes: ArrayMap<String, Int>?): SparseIntArray =
         if (modes == null) {
             SparseIntArray()
         } else {
-            val opIntMap = SparseIntArray(modes.size)
+            val opSparseArray = SparseIntArray(modes.size)
             modes.forEachIndexed { _, opName, opMode ->
-                opIntMap.put(AppOpsManager.strOpToOp(opName), opMode)
+                opSparseArray.put(AppOpsManager.strOpToOp(opName), opMode)
             }
-            opIntMap
+            opSparseArray
         }
 
     override fun areUidModesDefault(uid: Int): Boolean {
@@ -184,308 +170,108 @@
         // and we have our own persistence.
     }
 
-    // code -> listeners
-    private val opModeWatchers = IntMap<IndexedSet<OnOpModeChangedListener>>()
-
-    // packageName -> listeners
-    private val packageModeWatchers = IndexedMap<String, IndexedSet<OnOpModeChangedListener>>()
-
-    override fun startWatchingOpModeChanged(changedListener: OnOpModeChangedListener, op: Int) {
-        synchronized(lock) {
-            opModeWatchers.getOrPut(op) { IndexedSet() } += changedListener
-        }
-    }
-
-    override fun startWatchingPackageModeChanged(
-        changedListener: OnOpModeChangedListener,
-        packageName: String
-    ) {
-        synchronized(lock) {
-            packageModeWatchers.getOrPut(packageName) { IndexedSet() } += changedListener
-        }
-    }
-
-    override fun removeListener(changedListener: OnOpModeChangedListener) {
-        synchronized(lock) {
-            opModeWatchers.removeAllIndexed { _, _, listeners ->
-                listeners -= changedListener
-                listeners.isEmpty()
-            }
-            packageModeWatchers.removeAllIndexed { _, _, listeners ->
-                listeners -= changedListener
-                listeners.isEmpty()
+    override fun getForegroundOps(uid: Int): SparseBooleanArray {
+        return SparseBooleanArray().apply {
+            getUidModes(uid)?.forEachIndexed { _, op, mode ->
+                if (mode == AppOpsManager.MODE_FOREGROUND) {
+                    this[AppOpsManager.strOpToOp(op)] = true
+                }
             }
         }
     }
 
-    override fun getOpModeChangedListeners(op: Int): IndexedSet<OnOpModeChangedListener> {
-        synchronized(lock) {
-            val listeners = opModeWatchers[op]
-            return if (listeners == null) {
-                IndexedSet()
-            } else {
-                IndexedSet(listeners)
+    override fun getForegroundOps(packageName: String, userId: Int): SparseBooleanArray {
+        return SparseBooleanArray().apply {
+            getPackageModes(packageName, userId)?.forEachIndexed { _, op, mode ->
+                if (mode == AppOpsManager.MODE_FOREGROUND) {
+                    this[AppOpsManager.strOpToOp(op)] = true
+                }
             }
         }
     }
 
-    override fun getPackageModeChangedListeners(
-        packageName: String
-    ): IndexedSet<OnOpModeChangedListener> {
-        synchronized(lock) {
-            val listeners = packageModeWatchers[packageName]
-            return if (listeners == null) {
-                IndexedSet()
-            } else {
-                IndexedSet(listeners)
-            }
+    override fun addAppOpsModeChangedListener(listener: AppOpsModeChangedListener): Boolean {
+        synchronized(listenersLock) {
+            val newListeners = ArraySet(listeners)
+            val result = newListeners.add(listener)
+            listeners = newListeners
+            return result
         }
     }
 
-    override fun notifyWatchersOfChange(op: Int, uid: Int) {
-        val listeners = getOpModeChangedListeners(op)
-        listeners.forEachIndexed { _, listener ->
-            notifyOpChanged(listener, op, uid, null)
+    override fun removeAppOpsModeChangedListener(listener: AppOpsModeChangedListener): Boolean {
+        synchronized(listenersLock) {
+            val newListeners = ArraySet(listeners)
+            val result = newListeners.remove(listener)
+            listeners = newListeners
+            return result
         }
     }
 
-    override fun notifyOpChanged(
-        changedListener: OnOpModeChangedListener,
-        op: Int,
-        uid: Int,
-        packageName: String?
-    ) {
-        if (uid != UID_ANY &&
-            changedListener.watchingUid >= 0 &&
-            changedListener.watchingUid != uid
+    inner class OnAppIdAppOpModeChangedListener : AppIdAppOpPolicy.OnAppOpModeChangedListener() {
+        // (uid, appOpCode) -> newMode
+        val pendingChanges = ArrayMap<Pair<Int, Int>, Int>()
+
+        override fun onAppOpModeChanged(
+            appId: Int,
+            userId: Int,
+            appOpName: String,
+            oldMode: Int,
+            newMode: Int
         ) {
-            return
+            val uid = UserHandle.getUid(userId, appId)
+            val appOpCode = AppOpsManager.strOpToOp(appOpName)
+            val key = Pair(uid, appOpCode)
+
+            pendingChanges[key] = newMode
         }
 
-        // See CALL_BACK_ON_CHANGED_LISTENER_WITH_SWITCHED_OP_CHANGE
-        val switchedCodes = when (changedListener.watchedOpCode) {
-            ALL_OPS -> switchedOps.get(op)
-            AppOpsManager.OP_NONE -> intArrayOf(op)
-            else -> intArrayOf(changedListener.watchedOpCode)
-        }
+        override fun onStateMutated() {
+            val listenersLocal = listeners
+            pendingChanges.forEachIndexed { _, key, mode ->
+                listenersLocal.forEachIndexed { _, listener ->
+                    val uid = key.first
+                    val appOpCode = key.second
 
-        for (switchedCode in switchedCodes) {
-            // There are features watching for mode changes such as window manager
-            // and location manager which are in our process. The callbacks in these
-            // features may require permissions our remote caller does not have.
-            val identity = Binder.clearCallingIdentity()
-            try {
-                if (!shouldIgnoreCallback(switchedCode, changedListener)) {
-                    changedListener.onOpModeChanged(switchedCode, uid, packageName)
+                    listener.onUidModeChanged(uid, appOpCode, mode)
                 }
-            } catch (e: RemoteException) {
-                /* ignore */
-            } finally {
-                Binder.restoreCallingIdentity(identity)
             }
+
+            pendingChanges.clear()
         }
     }
 
-    private fun shouldIgnoreCallback(op: Int, listener: OnOpModeChangedListener): Boolean {
-        // If it's a restricted read op, ignore it if watcher doesn't have manage ops permission,
-        // as watcher should not use this to signal if the value is changed.
-        return AppOpsManager.opRestrictsRead(op) && context.checkPermission(
-            Manifest.permission.MANAGE_APPOPS,
-            listener.callingPid,
-            listener.callingUid
-        ) != PackageManager.PERMISSION_GRANTED
-    }
+    private inner class OnPackageAppOpModeChangedListener :
+        PackageAppOpPolicy.OnAppOpModeChangedListener() {
+        // (packageName, userId, appOpCode) -> newMode
+        val pendingChanges = ArrayMap<Triple<String, Int, Int>, Int>()
 
-    /**
-     * Construct a map from each listener (listening to the given op, uid) to all of its associated
-     * packageNames (by reverse-indexing opModeWatchers and packageModeWatchers), then invoke
-     * notifyOpChanged for each listener.
-     */
-    override fun notifyOpChangedForAllPkgsInUid(
-        op: Int,
-        uid: Int,
-        onlyForeground: Boolean,
-        callbackToIgnore: OnOpModeChangedListener?
-    ) {
-        val uidPackageNames = getPackagesForUid(uid)
-        val callbackSpecs = IndexedMap<OnOpModeChangedListener, IndexedSet<String>>()
-
-        fun associateListenerWithPackageNames(
-            listener: OnOpModeChangedListener,
-            packageNames: Array<String>
+        override fun onAppOpModeChanged(
+            packageName: String,
+            userId: Int,
+            appOpName: String,
+            oldMode: Int,
+            newMode: Int
         ) {
-            val listenerIsForeground =
-                listener.flags.hasBits(AppOpsManager.WATCH_FOREGROUND_CHANGES)
-            if (onlyForeground && !listenerIsForeground) {
-                return
-            }
-            val changedPackages = callbackSpecs.getOrPut(listener) { IndexedSet() }
-            changedPackages.addAll(packageNames)
+            val appOpCode = AppOpsManager.strOpToOp(appOpName)
+            val key = Triple(packageName, userId, appOpCode)
+
+            pendingChanges[key] = newMode
         }
 
-        synchronized(lock) {
-            // Collect all listeners from opModeWatchers and pckageModeWatchers
-            val listeners = opModeWatchers[op]
-            listeners?.forEachIndexed { _, listener ->
-                associateListenerWithPackageNames(listener, uidPackageNames)
-            }
-            uidPackageNames.forEachIndexed { _, uidPackageName ->
-                val packageListeners = packageModeWatchers[uidPackageName]
-                packageListeners?.forEachIndexed { _, listener ->
-                    associateListenerWithPackageNames(listener, arrayOf(uidPackageName))
+        override fun onStateMutated() {
+            val listenersLocal = listeners
+            pendingChanges.forEachIndexed { _, key, mode ->
+                listenersLocal.forEachIndexed { _, listener ->
+                    val packageName = key.first
+                    val userId = key.second
+                    val appOpCode = key.third
+
+                    listener.onPackageModeChanged(packageName, userId, appOpCode, mode)
                 }
             }
-            // Remove ignored listeners
-            if (callbackToIgnore != null) {
-                callbackSpecs.remove(callbackToIgnore)
-            }
+
+            pendingChanges.clear()
         }
-
-        // For each (listener, packageName) pair, invoke notifyOpChanged
-        callbackSpecs.forEachIndexed { _, listener, reportedPackageNames ->
-            reportedPackageNames.forEachIndexed { _, reportedPackageName ->
-                handler.sendMessage(
-                    PooledLambda.obtainMessage(
-                        AppOpService::notifyOpChanged, this, listener,
-                        op, uid, reportedPackageName
-                    )
-                )
-            }
-        }
-    }
-
-    private fun getPackagesForUid(uid: Int): Array<String> {
-        // Very early during boot the package manager is not yet or not yet fully started. At this
-        // time there are no packages yet.
-        return try {
-            AppGlobals.getPackageManager()?.getPackagesForUid(uid) ?: EmptyArray.STRING
-        } catch (e: RemoteException) {
-            EmptyArray.STRING
-        }
-    }
-
-    override fun evalForegroundUidOps(
-        uid: Int,
-        foregroundOps: SparseBooleanArray?
-    ): SparseBooleanArray? {
-        synchronized(lock) {
-            val uidModes = getUidModes(uid)
-            return evalForegroundOps(uidModes, foregroundOps)
-        }
-    }
-
-    override fun evalForegroundPackageOps(
-        packageName: String,
-        foregroundOps: SparseBooleanArray?,
-        @UserIdInt userId: Int
-    ): SparseBooleanArray? {
-        synchronized(lock) {
-            val ops = service.getState { getPackageModes(packageName, userId) }
-            return evalForegroundOps(ops, foregroundOps)
-        }
-    }
-
-    private fun evalForegroundOps(
-        ops: IndexedMap<String, Int>?,
-        foregroundOps: SparseBooleanArray?
-    ): SparseBooleanArray? {
-        var foregroundOps = foregroundOps
-        ops?.forEachIndexed { _, opName, opMode ->
-            if (opMode == AppOpsManager.MODE_FOREGROUND) {
-                if (foregroundOps == null) {
-                    foregroundOps = SparseBooleanArray()
-                }
-                evalForegroundWatchers(opName, foregroundOps!!)
-            }
-        }
-        return foregroundOps
-    }
-
-    private fun evalForegroundWatchers(opName: String, foregroundOps: SparseBooleanArray) {
-        val opCode = AppOpsManager.strOpToOp(opName)
-        val listeners = opModeWatchers[opCode]
-        val hasForegroundListeners = foregroundOps[opCode] || listeners?.anyIndexed { _, listener ->
-            listener.flags.hasBits(AppOpsManager.WATCH_FOREGROUND_CHANGES)
-        } ?: false
-        foregroundOps.put(opCode, hasForegroundListeners)
-    }
-
-    override fun dumpListeners(
-        dumpOp: Int,
-        dumpUid: Int,
-        dumpPackage: String?,
-        printWriter: PrintWriter
-    ): Boolean {
-        var needSep = false
-        if (opModeWatchers.size() > 0) {
-            var printedHeader = false
-            opModeWatchers.forEachIndexed { _, op, modeChangedListenerSet ->
-                if (dumpOp >= 0 && dumpOp != op) {
-                    return@forEachIndexed // continue
-                }
-                val opName = AppOpsManager.opToName(op)
-                var printedOpHeader = false
-                modeChangedListenerSet.forEachIndexed listenerLoop@ { listenerIndex, listener ->
-                    with(printWriter) {
-                        if (dumpPackage != null &&
-                            dumpUid != UserHandle.getAppId(listener.watchingUid)) {
-                            return@listenerLoop // continue
-                        }
-                        needSep = true
-                        if (!printedHeader) {
-                            println("  Op mode watchers:")
-                            printedHeader = true
-                        }
-                        if (!printedOpHeader) {
-                            print("    Op ")
-                            print(opName)
-                            println(":")
-                            printedOpHeader = true
-                        }
-                        print("      #")
-                        print(listenerIndex)
-                        print(opName)
-                        print(": ")
-                        println(listener.toString())
-                    }
-                }
-            }
-        }
-
-        if (packageModeWatchers.size > 0 && dumpOp < 0) {
-            var printedHeader = false
-            packageModeWatchers.forEachIndexed { _, packageName, listeners ->
-                with(printWriter) {
-                    if (dumpPackage != null && dumpPackage != packageName) {
-                        return@forEachIndexed // continue
-                    }
-                    needSep = true
-                    if (!printedHeader) {
-                        println("  Package mode watchers:")
-                        printedHeader = true
-                    }
-                    print("    Pkg ")
-                    print(packageName)
-                    println(":")
-                    listeners.forEachIndexed { listenerIndex, listener ->
-                        print("      #")
-                        print(listenerIndex)
-                        print(": ")
-                        println(listener.toString())
-                    }
-                }
-            }
-        }
-        return needSep
-    }
-
-    companion object {
-        private val LOG_TAG = AppOpService::class.java.simpleName
-
-        // Constant meaning that any UID should be matched when dispatching callbacks
-        private const val UID_ANY = -2
-
-        // If watchedOpCode==ALL_OPS, notify for ops affected by the switch-op
-        private const val ALL_OPS = -2
     }
 }
diff --git a/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPersistence.kt b/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPersistence.kt
index 5faf96f..a267637 100644
--- a/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPersistence.kt
+++ b/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPersistence.kt
@@ -16,11 +16,13 @@
 
 package com.android.server.permission.access.appop
 
-import android.util.Log
+import android.util.Slog
 import com.android.modules.utils.BinaryXmlPullParser
 import com.android.modules.utils.BinaryXmlSerializer
 import com.android.server.permission.access.AccessState
+import com.android.server.permission.access.MutableAccessState
 import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
+import com.android.server.permission.access.immutable.* // ktlint-disable no-wildcard-imports
 import com.android.server.permission.access.util.attributeInt
 import com.android.server.permission.access.util.attributeInterned
 import com.android.server.permission.access.util.forEachTag
@@ -30,20 +32,20 @@
 import com.android.server.permission.access.util.tagName
 
 abstract class BaseAppOpPersistence {
-    abstract fun BinaryXmlPullParser.parseUserState(state: AccessState, userId: Int)
+    abstract fun BinaryXmlPullParser.parseUserState(state: MutableAccessState, userId: Int)
 
     abstract fun BinaryXmlSerializer.serializeUserState(state: AccessState, userId: Int)
 
-    protected fun BinaryXmlPullParser.parseAppOps(appOpModes: IndexedMap<String, Int>) {
+    protected fun BinaryXmlPullParser.parseAppOps(appOpModes: MutableIndexedMap<String, Int>) {
         forEachTag {
             when (tagName) {
                 TAG_APP_OP -> parseAppOp(appOpModes)
-                else -> Log.w(LOG_TAG, "Ignoring unknown tag $name when parsing app-op state")
+                else -> Slog.w(LOG_TAG, "Ignoring unknown tag $name when parsing app-op state")
             }
         }
     }
 
-    private fun BinaryXmlPullParser.parseAppOp(appOpModes: IndexedMap<String, Int>) {
+    private fun BinaryXmlPullParser.parseAppOp(appOpModes: MutableIndexedMap<String, Int>) {
         val name = getAttributeValueOrThrow(ATTR_NAME).intern()
         val mode = getAttributeIntOrThrow(ATTR_MODE)
         appOpModes[name] = mode
diff --git a/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPolicy.kt b/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPolicy.kt
index 9c8c0ce..c0a85f8 100644
--- a/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPolicy.kt
@@ -20,6 +20,7 @@
 import com.android.modules.utils.BinaryXmlSerializer
 import com.android.server.permission.access.AccessState
 import com.android.server.permission.access.AppOpUri
+import com.android.server.permission.access.MutableAccessState
 import com.android.server.permission.access.SchemePolicy
 
 abstract class BaseAppOpPolicy(
@@ -28,7 +29,7 @@
     override val objectScheme: String
         get() = AppOpUri.SCHEME
 
-    override fun BinaryXmlPullParser.parseUserState(state: AccessState, userId: Int) {
+    override fun BinaryXmlPullParser.parseUserState(state: MutableAccessState, userId: Int) {
         with(persistence) { this@parseUserState.parseUserState(state, userId) }
     }
 
diff --git a/services/permission/java/com/android/server/permission/access/appop/PackageAppOpMigration.kt b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpMigration.kt
new file mode 100644
index 0000000..03311a2
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpMigration.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.server.permission.access.appop
+
+import android.util.Slog
+import com.android.server.LocalServices
+import com.android.server.appop.AppOpMigrationHelper
+import com.android.server.permission.access.MutableAccessState
+import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
+import com.android.server.permission.access.immutable.* // ktlint-disable no-wildcard-imports
+import com.android.server.permission.access.util.PackageVersionMigration
+
+class PackageAppOpMigration {
+    fun migrateUserState(state: MutableAccessState, userId: Int) {
+        val legacyAppOpsManager = LocalServices.getService(AppOpMigrationHelper::class.java)!!
+        if (!legacyAppOpsManager.hasLegacyAppOpState()) {
+            return
+        }
+
+        val legacyPackageAppOpModes = legacyAppOpsManager.getLegacyPackageAppOpModes(userId)
+        val version = PackageVersionMigration.getVersion(userId)
+
+        val userState = state.mutateUserState(userId)!!
+        val packageAppOpModes = userState.mutatePackageAppOpModes()
+        legacyPackageAppOpModes.forEach { (packageName, legacyAppOpModes) ->
+            if (packageName !in state.externalState.packageStates) {
+                Slog.w(LOG_TAG, "Dropping unknown package $packageName when migrating app op state")
+                return@forEach
+            }
+
+            val appOpModes = MutableIndexedMap<String, Int>()
+            packageAppOpModes[packageName] = appOpModes
+            legacyAppOpModes.forEach { (appOpName, appOpMode) ->
+                appOpModes[appOpName] = appOpMode
+            }
+
+            userState.mutatePackageVersions()[packageName] = version
+        }
+    }
+
+    companion object {
+        private val LOG_TAG = PackageAppOpMigration::class.java.simpleName
+    }
+}
diff --git a/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPersistence.kt b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPersistence.kt
index 6ef117a..169bd69 100644
--- a/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPersistence.kt
+++ b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPersistence.kt
@@ -16,12 +16,16 @@
 
 package com.android.server.permission.access.appop
 
-import android.util.Log
+import android.util.Slog
 import com.android.modules.utils.BinaryXmlPullParser
 import com.android.modules.utils.BinaryXmlSerializer
 import com.android.server.permission.access.AccessState
-import com.android.server.permission.access.UserState
+import com.android.server.permission.access.MutableAccessState
+import com.android.server.permission.access.MutablePackageAppOpModes
+import com.android.server.permission.access.PackageAppOpModes
+import com.android.server.permission.access.WriteMode
 import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
+import com.android.server.permission.access.immutable.* // ktlint-disable no-wildcard-imports
 import com.android.server.permission.access.util.attributeInterned
 import com.android.server.permission.access.util.forEachTag
 import com.android.server.permission.access.util.getAttributeValueOrThrow
@@ -29,44 +33,45 @@
 import com.android.server.permission.access.util.tagName
 
 class PackageAppOpPersistence : BaseAppOpPersistence() {
-    override fun BinaryXmlPullParser.parseUserState(state: AccessState, userId: Int) {
+    override fun BinaryXmlPullParser.parseUserState(state: MutableAccessState, userId: Int) {
         when (tagName) {
             TAG_PACKAGE_APP_OPS -> parsePackageAppOps(state, userId)
             else -> {}
         }
     }
 
-    private fun BinaryXmlPullParser.parsePackageAppOps(state: AccessState, userId: Int) {
-        val userState = state.userStates[userId]
+    private fun BinaryXmlPullParser.parsePackageAppOps(state: MutableAccessState, userId: Int) {
+        val userState = state.mutateUserState(userId, WriteMode.NONE)!!
+        val packageAppOpModes = userState.mutatePackageAppOpModes()
         forEachTag {
             when (tagName) {
-                TAG_PACKAGE -> parsePackage(userState)
-                else -> Log.w(LOG_TAG, "Ignoring unknown tag $name when parsing app-op state")
+                TAG_PACKAGE -> parsePackage(packageAppOpModes)
+                else -> Slog.w(LOG_TAG, "Ignoring unknown tag $name when parsing app-op state")
             }
         }
-        userState.packageAppOpModes.retainAllIndexed { _, packageName, _ ->
-            val hasPackage = packageName in state.systemState.packageStates
-            if (!hasPackage) {
-                Log.w(LOG_TAG, "Dropping unknown package $packageName when parsing app-op state")
+        packageAppOpModes.forEachReversedIndexed { packageNameIndex, packageName, _ ->
+            if (packageName !in state.externalState.packageStates) {
+                Slog.w(LOG_TAG, "Dropping unknown package $packageName when parsing app-op state")
+                packageAppOpModes.removeAt(packageNameIndex)
+                userState.requestWriteMode(WriteMode.ASYNCHRONOUS)
             }
-            hasPackage
         }
     }
 
-    private fun BinaryXmlPullParser.parsePackage(userState: UserState) {
+    private fun BinaryXmlPullParser.parsePackage(packageAppOpModes: MutablePackageAppOpModes) {
         val packageName = getAttributeValueOrThrow(ATTR_NAME).intern()
-        val appOpModes = IndexedMap<String, Int>()
-        userState.packageAppOpModes[packageName] = appOpModes
+        val appOpModes = MutableIndexedMap<String, Int>()
+        packageAppOpModes[packageName] = appOpModes
         parseAppOps(appOpModes)
     }
 
     override fun BinaryXmlSerializer.serializeUserState(state: AccessState, userId: Int) {
-        serializePackageAppOps(state.userStates[userId])
+        serializePackageAppOps(state.userStates[userId]!!.packageAppOpModes)
     }
 
-    private fun BinaryXmlSerializer.serializePackageAppOps(userState: UserState) {
+    private fun BinaryXmlSerializer.serializePackageAppOps(packageAppOpModes: PackageAppOpModes) {
         tag(TAG_PACKAGE_APP_OPS) {
-            userState.packageAppOpModes.forEachIndexed { _, packageName, appOpModes ->
+            packageAppOpModes.forEachIndexed { _, packageName, appOpModes ->
                 serializePackage(packageName, appOpModes)
             }
         }
diff --git a/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt
index 7d3578d..5398a57 100644
--- a/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt
@@ -17,63 +17,62 @@
 package com.android.server.permission.access.appop
 
 import android.app.AppOpsManager
-import com.android.server.permission.access.AccessUri
-import com.android.server.permission.access.AppOpUri
 import com.android.server.permission.access.GetStateScope
+import com.android.server.permission.access.MutableAccessState
 import com.android.server.permission.access.MutateStateScope
 import com.android.server.permission.access.PackageUri
 import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
+import com.android.server.permission.access.immutable.* // ktlint-disable no-wildcard-imports
+import com.android.server.pm.pkg.PackageState
 
 class PackageAppOpPolicy : BaseAppOpPolicy(PackageAppOpPersistence()) {
+    private val migration = PackageAppOpMigration()
+
+    private val upgrade = PackageAppOpUpgrade(this)
+
     @Volatile
-    private var onAppOpModeChangedListeners = IndexedListSet<OnAppOpModeChangedListener>()
+    private var onAppOpModeChangedListeners: IndexedListSet<OnAppOpModeChangedListener> =
+        MutableIndexedListSet()
     private val onAppOpModeChangedListenersLock = Any()
 
     override val subjectScheme: String
         get() = PackageUri.SCHEME
 
-    override fun GetStateScope.getDecision(subject: AccessUri, `object`: AccessUri): Int {
-        subject as PackageUri
-        `object` as AppOpUri
-        return getAppOpMode(subject.packageName, subject.userId, `object`.appOpName)
-    }
-
-    override fun MutateStateScope.setDecision(
-        subject: AccessUri,
-        `object`: AccessUri,
-        decision: Int
-    ) {
-        subject as PackageUri
-        `object` as AppOpUri
-        setAppOpMode(subject.packageName, subject.userId, `object`.appOpName, decision)
-    }
-
     override fun GetStateScope.onStateMutated() {
         onAppOpModeChangedListeners.forEachIndexed { _, it -> it.onStateMutated() }
     }
 
     override fun MutateStateScope.onPackageRemoved(packageName: String, appId: Int) {
-        newState.userStates.forEachIndexed { _, _, userState ->
-            userState.packageAppOpModes -= packageName
-            userState.requestWrite()
-            // Skip notifying the change listeners since the package no longer exists.
+        newState.userStates.forEachIndexed { userStateIndex, _, userState ->
+            val packageNameIndex = userState.packageAppOpModes.indexOfKey(packageName)
+            if (packageNameIndex >= 0) {
+                newState.mutateUserStateAt(userStateIndex).mutatePackageAppOpModes()
+                    .removeAt(packageNameIndex)
+                // Skip notifying the change listeners since the package no longer exists.
+            }
         }
     }
 
     fun GetStateScope.getAppOpModes(packageName: String, userId: Int): IndexedMap<String, Int>? =
-        state.userStates[userId].packageAppOpModes[packageName]
+        state.userStates[userId]?.packageAppOpModes?.get(packageName)
 
     fun MutateStateScope.removeAppOpModes(packageName: String, userId: Int): Boolean {
-        val userState = newState.userStates[userId]
-        val isChanged = userState.packageAppOpModes.remove(packageName) != null
-        if (isChanged) {
-            userState.requestWrite()
+        val userStateIndex = newState.userStates.indexOfKey(userId)
+        if (userStateIndex < 0) {
+            return false
         }
-        return isChanged
+        val packageNameIndex = newState.userStates.valueAt(userStateIndex).packageAppOpModes
+            .indexOfKey(packageName)
+        if (packageNameIndex < 0) {
+            return false
+        }
+        newState.mutateUserStateAt(userStateIndex).mutatePackageAppOpModes()
+            .removeAt(packageNameIndex)
+        return true
     }
 
     fun GetStateScope.getAppOpMode(packageName: String, userId: Int, appOpName: String): Int =
-        state.userStates[userId].packageAppOpModes[packageName]
+        state.userStates[userId]?.packageAppOpModes?.get(packageName)
             .getWithDefault(appOpName, AppOpsManager.opToDefaultMode(appOpName))
 
     fun MutateStateScope.setAppOpMode(
@@ -82,23 +81,18 @@
         appOpName: String,
         mode: Int
     ): Boolean {
-        val userState = newState.userStates[userId]
-        val packageAppOpModes = userState.packageAppOpModes
-        var appOpModes = packageAppOpModes[packageName]
         val defaultMode = AppOpsManager.opToDefaultMode(appOpName)
-        val oldMode = appOpModes.getWithDefault(appOpName, defaultMode)
+        val oldMode = newState.userStates[userId]!!.packageAppOpModes[packageName]
+            .getWithDefault(appOpName, defaultMode)
         if (oldMode == mode) {
             return false
         }
-        if (appOpModes == null) {
-            appOpModes = IndexedMap()
-            packageAppOpModes[packageName] = appOpModes
-        }
+        val packageAppOpModes = newState.mutateUserState(userId)!!.mutatePackageAppOpModes()
+        val appOpModes = packageAppOpModes.mutateOrPut(packageName) { MutableIndexedMap() }
         appOpModes.putWithDefault(appOpName, mode, defaultMode)
         if (appOpModes.isEmpty()) {
             packageAppOpModes -= packageName
         }
-        userState.requestWrite()
         onAppOpModeChangedListeners.forEachIndexed { _, it ->
             it.onAppOpModeChanged(packageName, userId, appOpName, oldMode, mode)
         }
@@ -117,6 +111,18 @@
         }
     }
 
+    override fun migrateUserState(state: MutableAccessState, userId: Int) {
+        with(migration) { migrateUserState(state, userId) }
+    }
+
+    override fun MutateStateScope.upgradePackageState(
+        packageState: PackageState,
+        userId: Int,
+        version: Int,
+    ) {
+        with(upgrade) { upgradePackageState(packageState, userId, version) }
+    }
+
     /**
      * Listener for app op mode changes.
      */
diff --git a/services/permission/java/com/android/server/permission/access/appop/PackageAppOpUpgrade.kt b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpUpgrade.kt
new file mode 100644
index 0000000..8e37093
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpUpgrade.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.server.permission.access.appop
+
+import android.app.AppOpsManager
+import com.android.server.permission.access.MutateStateScope
+import com.android.server.pm.pkg.PackageState
+
+class PackageAppOpUpgrade(private val policy: PackageAppOpPolicy) {
+    fun MutateStateScope.upgradePackageState(
+        packageState: PackageState,
+        userId: Int,
+        version: Int,
+    ) {
+        if (version <= 2) {
+            with(policy) {
+                val appOpMode = getAppOpMode(
+                    packageState.packageName, userId, AppOpsManager.OPSTR_RUN_IN_BACKGROUND
+                )
+                setAppOpMode(
+                    packageState.packageName, userId, AppOpsManager.OPSTR_RUN_ANY_IN_BACKGROUND,
+                    appOpMode
+                )
+            }
+        }
+    }
+}
diff --git a/services/permission/java/com/android/server/permission/access/appop/UidAppOpPersistence.kt b/services/permission/java/com/android/server/permission/access/appop/UidAppOpPersistence.kt
deleted file mode 100644
index 7a965d4..0000000
--- a/services/permission/java/com/android/server/permission/access/appop/UidAppOpPersistence.kt
+++ /dev/null
@@ -1,93 +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.server.permission.access.appop
-
-import android.util.Log
-import com.android.modules.utils.BinaryXmlPullParser
-import com.android.modules.utils.BinaryXmlSerializer
-import com.android.server.permission.access.AccessState
-import com.android.server.permission.access.UserState
-import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
-import com.android.server.permission.access.util.attributeInt
-import com.android.server.permission.access.util.forEachTag
-import com.android.server.permission.access.util.getAttributeIntOrThrow
-import com.android.server.permission.access.util.tag
-import com.android.server.permission.access.util.tagName
-
-class UidAppOpPersistence : BaseAppOpPersistence() {
-    override fun BinaryXmlPullParser.parseUserState(state: AccessState, userId: Int) {
-        when (tagName) {
-            TAG_UID_APP_OPS -> parseUidAppOps(state, userId)
-            else -> {}
-        }
-    }
-
-    private fun BinaryXmlPullParser.parseUidAppOps(state: AccessState, userId: Int) {
-        val userState = state.userStates[userId]
-        forEachTag {
-            when (tagName) {
-                TAG_APP_ID -> parseAppId(userState)
-                else -> Log.w(LOG_TAG, "Ignoring unknown tag $name when parsing app-op state")
-            }
-        }
-        userState.uidAppOpModes.retainAllIndexed { _, appId, _ ->
-            val hasAppId = appId in state.systemState.appIds
-            if (!hasAppId) {
-                Log.w(LOG_TAG, "Dropping unknown app ID $appId when parsing app-op state")
-            }
-            hasAppId
-        }
-    }
-
-    private fun BinaryXmlPullParser.parseAppId(userState: UserState) {
-        val appId = getAttributeIntOrThrow(ATTR_ID)
-        val appOpModes = IndexedMap<String, Int>()
-        userState.uidAppOpModes[appId] = appOpModes
-        parseAppOps(appOpModes)
-    }
-
-    override fun BinaryXmlSerializer.serializeUserState(state: AccessState, userId: Int) {
-        serializeUidAppOps(state.userStates[userId])
-    }
-
-    private fun BinaryXmlSerializer.serializeUidAppOps(userState: UserState) {
-        tag(TAG_UID_APP_OPS) {
-            userState.uidAppOpModes.forEachIndexed { _, appId, appOpModes ->
-                serializeAppId(appId, appOpModes)
-            }
-        }
-    }
-
-    private fun BinaryXmlSerializer.serializeAppId(
-        appId: Int,
-        appOpModes: IndexedMap<String, Int>
-    ) {
-        tag(TAG_APP_ID) {
-            attributeInt(ATTR_ID, appId)
-            serializeAppOps(appOpModes)
-        }
-    }
-
-    companion object {
-        private val LOG_TAG = UidAppOpPersistence::class.java.simpleName
-
-        private const val TAG_APP_ID = "app-id"
-        private const val TAG_UID_APP_OPS = "uid-app-ops"
-
-        private const val ATTR_ID = "id"
-    }
-}
diff --git a/services/permission/java/com/android/server/permission/access/appop/UidAppOpPolicy.kt b/services/permission/java/com/android/server/permission/access/appop/UidAppOpPolicy.kt
deleted file mode 100644
index 0ba9a1e..0000000
--- a/services/permission/java/com/android/server/permission/access/appop/UidAppOpPolicy.kt
+++ /dev/null
@@ -1,146 +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.server.permission.access.appop
-
-import android.app.AppOpsManager
-import com.android.server.permission.access.AccessUri
-import com.android.server.permission.access.AppOpUri
-import com.android.server.permission.access.GetStateScope
-import com.android.server.permission.access.MutateStateScope
-import com.android.server.permission.access.UidUri
-import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
-
-class UidAppOpPolicy : BaseAppOpPolicy(UidAppOpPersistence()) {
-    @Volatile
-    private var onAppOpModeChangedListeners = IndexedListSet<OnAppOpModeChangedListener>()
-    private val onAppOpModeChangedListenersLock = Any()
-
-    override val subjectScheme: String
-        get() = UidUri.SCHEME
-
-    override fun GetStateScope.getDecision(subject: AccessUri, `object`: AccessUri): Int {
-        subject as UidUri
-        `object` as AppOpUri
-        return getAppOpMode(subject.appId, subject.userId, `object`.appOpName)
-    }
-
-    override fun MutateStateScope.setDecision(
-        subject: AccessUri,
-        `object`: AccessUri,
-        decision: Int
-    ) {
-        subject as UidUri
-        `object` as AppOpUri
-        setAppOpMode(subject.appId, subject.userId, `object`.appOpName, decision)
-    }
-
-    override fun GetStateScope.onStateMutated() {
-        onAppOpModeChangedListeners.forEachIndexed { _, it -> it.onStateMutated() }
-    }
-
-    override fun MutateStateScope.onAppIdRemoved(appId: Int) {
-        newState.userStates.forEachIndexed { _, _, userState ->
-            userState.uidAppOpModes -= appId
-            userState.requestWrite()
-            // Skip notifying the change listeners since the app ID no longer exists.
-        }
-    }
-
-    fun GetStateScope.getAppOpModes(appId: Int, userId: Int): IndexedMap<String, Int>? =
-        state.userStates[userId].uidAppOpModes[appId]
-
-    fun MutateStateScope.removeAppOpModes(appId: Int, userId: Int): Boolean {
-        val userState = newState.userStates[userId]
-        val isChanged = userState.uidAppOpModes.removeReturnOld(appId) != null
-        if (isChanged) {
-            userState.requestWrite()
-        }
-        return isChanged
-    }
-
-    fun GetStateScope.getAppOpMode(appId: Int, userId: Int, appOpName: String): Int =
-        state.userStates[userId].uidAppOpModes[appId]
-            .getWithDefault(appOpName, AppOpsManager.opToDefaultMode(appOpName))
-
-    fun MutateStateScope.setAppOpMode(
-        appId: Int,
-        userId: Int,
-        appOpName: String,
-        mode: Int
-    ): Boolean {
-        val userState = newState.userStates[userId]
-        val uidAppOpModes = userState.uidAppOpModes
-        var appOpModes = uidAppOpModes[appId]
-        val defaultMode = AppOpsManager.opToDefaultMode(appOpName)
-        val oldMode = appOpModes.getWithDefault(appOpName, defaultMode)
-        if (oldMode == mode) {
-            return false
-        }
-        if (appOpModes == null) {
-            appOpModes = IndexedMap()
-            uidAppOpModes[appId] = appOpModes
-        }
-        appOpModes.putWithDefault(appOpName, mode, defaultMode)
-        if (appOpModes.isEmpty()) {
-            uidAppOpModes -= appId
-        }
-        userState.requestWrite()
-        onAppOpModeChangedListeners.forEachIndexed { _, it ->
-            it.onAppOpModeChanged(appId, userId, appOpName, oldMode, mode)
-        }
-        return true
-    }
-
-    fun addOnAppOpModeChangedListener(listener: OnAppOpModeChangedListener) {
-        synchronized(onAppOpModeChangedListenersLock) {
-            onAppOpModeChangedListeners = onAppOpModeChangedListeners + listener
-        }
-    }
-
-    fun removeOnAppOpModeChangedListener(listener: OnAppOpModeChangedListener) {
-        synchronized(onAppOpModeChangedListenersLock) {
-            onAppOpModeChangedListeners = onAppOpModeChangedListeners - listener
-        }
-    }
-
-    /**
-     * Listener for app op mode changes.
-     */
-    abstract class OnAppOpModeChangedListener {
-        /**
-         * Called when an app op mode change has been made to the upcoming new state.
-         *
-         * Implementations should keep this method fast to avoid stalling the locked state mutation,
-         * and only call external code after [onStateMutated] when the new state has actually become
-         * the current state visible to external code.
-         */
-        abstract fun onAppOpModeChanged(
-            appId: Int,
-            userId: Int,
-            appOpName: String,
-            oldMode: Int,
-            newMode: Int
-        )
-
-        /**
-         * Called when the upcoming new state has become the current state.
-         *
-         * Implementations should keep this method fast to avoid stalling the locked state mutation.
-         */
-        abstract fun onStateMutated()
-    }
-}
diff --git a/services/permission/java/com/android/server/permission/access/collection/ArrayMapExtensions.kt b/services/permission/java/com/android/server/permission/access/collection/ArrayMapExtensions.kt
new file mode 100644
index 0000000..686db42
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/collection/ArrayMapExtensions.kt
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.permission.access.collection
+
+import android.util.ArrayMap
+
+inline fun <K, V> ArrayMap<K, V>.allIndexed(predicate: (Int, K, V) -> Boolean): Boolean {
+    forEachIndexed { index, key, value ->
+        if (!predicate(index, key, value)) {
+            return false
+        }
+    }
+    return true
+}
+
+inline fun <K, V> ArrayMap<K, V>.anyIndexed(predicate: (Int, K, V) -> Boolean): Boolean {
+    forEachIndexed { index, key, value ->
+        if (predicate(index, key, value)) {
+            return true
+        }
+    }
+    return false
+}
+
+inline fun <K, V> ArrayMap<K, V>.forEachIndexed(action: (Int, K, V) -> Unit) {
+    for (index in 0 until size) {
+        action(index, keyAt(index), valueAt(index))
+    }
+}
+
+inline fun <K, V> ArrayMap<K, V>.forEachReversedIndexed(action: (Int, K, V) -> Unit) {
+    for (index in lastIndex downTo 0) {
+        action(index, keyAt(index), valueAt(index))
+    }
+}
+
+inline fun <K, V> ArrayMap<K, V>.getOrPut(key: K, defaultValue: () -> V): V {
+    get(key)?.let { return it }
+    return defaultValue().also { put(key, it) }
+}
+
+inline val <K, V> ArrayMap<K, V>.lastIndex: Int
+    get() = size - 1
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun <K, V> ArrayMap<K, V>.minusAssign(key: K) {
+    remove(key)
+}
+
+inline fun <K, V> ArrayMap<K, V>.noneIndexed(predicate: (Int, K, V) -> Boolean): Boolean {
+    forEachIndexed { index, key, value ->
+        if (predicate(index, key, value)) {
+            return false
+        }
+    }
+    return true
+}
+
+inline fun <K, V> ArrayMap<K, V>.removeAllIndexed(predicate: (Int, K, V) -> Boolean): Boolean {
+    var isChanged = false
+    forEachReversedIndexed { index, key, value ->
+        if (predicate(index, key, value)) {
+            removeAt(index)
+            isChanged = true
+        }
+    }
+    return isChanged
+}
+
+inline fun <K, V> ArrayMap<K, V>.retainAllIndexed(predicate: (Int, K, V) -> Boolean): Boolean {
+    var isChanged = false
+    forEachReversedIndexed { index, key, value ->
+        if (!predicate(index, key, value)) {
+            removeAt(index)
+            isChanged = true
+        }
+    }
+    return isChanged
+}
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun <K, V> ArrayMap<K, V>.set(key: K, value: V) {
+    put(key, value)
+}
diff --git a/services/permission/java/com/android/server/permission/access/collection/ArraySetExtensions.kt b/services/permission/java/com/android/server/permission/access/collection/ArraySetExtensions.kt
new file mode 100644
index 0000000..4710103
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/collection/ArraySetExtensions.kt
@@ -0,0 +1,95 @@
+/*
+ * 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.permission.access.collection
+
+import android.util.ArraySet
+
+fun <T> arraySetOf(vararg elements: T): ArraySet<T> = ArraySet(elements.asList())
+
+inline fun <T> ArraySet<T>.allIndexed(predicate: (Int, T) -> Boolean): Boolean {
+    forEachIndexed { index, value ->
+        if (!predicate(index, value)) {
+            return false
+        }
+    }
+    return true
+}
+
+inline fun <T> ArraySet<T>.anyIndexed(predicate: (Int, T) -> Boolean): Boolean {
+    forEachIndexed { index, value ->
+        if (predicate(index, value)) {
+            return true
+        }
+    }
+    return false
+}
+
+inline fun <T> ArraySet<T>.forEachIndexed(action: (Int, T) -> Unit) {
+    for (index in 0 until size) {
+        action(index, valueAt(index))
+    }
+}
+
+inline fun <T> ArraySet<T>.forEachReversedIndexed(action: (Int, T) -> Unit) {
+    for (index in lastIndex downTo 0) {
+        action(index, valueAt(index))
+    }
+}
+
+inline val <T> ArraySet<T>.lastIndex: Int
+    get() = size - 1
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun <T> ArraySet<T>.minusAssign(value: T) {
+    remove(value)
+}
+
+inline fun <T> ArraySet<T>.noneIndexed(predicate: (Int, T) -> Boolean): Boolean {
+    forEachIndexed { index, value ->
+        if (predicate(index, value)) {
+            return false
+        }
+    }
+    return true
+}
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun <T> ArraySet<T>.plusAssign(value: T) {
+    add(value)
+}
+
+inline fun <T> ArraySet<T>.removeAllIndexed(predicate: (Int, T) -> Boolean): Boolean {
+    var isChanged = false
+    forEachReversedIndexed { index, value ->
+        if (predicate(index, value)) {
+            removeAt(index)
+            isChanged = true
+        }
+    }
+    return isChanged
+}
+
+inline fun <T> ArraySet<T>.retainAllIndexed(predicate: (Int, T) -> Boolean): Boolean {
+    var isChanged = false
+    forEachReversedIndexed { index, value ->
+        if (!predicate(index, value)) {
+            removeAt(index)
+            isChanged = true
+        }
+    }
+    return isChanged
+}
diff --git a/services/permission/java/com/android/server/permission/access/collection/IndexedList.kt b/services/permission/java/com/android/server/permission/access/collection/IndexedList.kt
deleted file mode 100644
index f4ecceb..0000000
--- a/services/permission/java/com/android/server/permission/access/collection/IndexedList.kt
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.permission.access.collection
-
-typealias IndexedList<T> = ArrayList<T>
-
-inline fun <T> IndexedList<T>.allIndexed(predicate: (Int, T) -> Boolean): Boolean {
-    forEachIndexed { index, element ->
-        if (!predicate(index, element)) {
-            return false
-        }
-    }
-    return true
-}
-
-inline fun <T> IndexedList<T>.anyIndexed(predicate: (Int, T) -> Boolean): Boolean {
-    forEachIndexed { index, element ->
-        if (predicate(index, element)) {
-            return true
-        }
-    }
-    return false
-}
-
-@Suppress("NOTHING_TO_INLINE")
-inline fun <T> IndexedList<T>.copy(): IndexedList<T> = IndexedList(this)
-
-inline fun <T> IndexedList<T>.forEachIndexed(action: (Int, T) -> Unit) {
-    for (index in indices) {
-        action(index, this[index])
-    }
-}
-
-inline fun <T> IndexedList<T>.forEachReversedIndexed(action: (Int, T) -> Unit) {
-    for (index in lastIndex downTo 0) {
-        action(index, this[index])
-    }
-}
-
-@Suppress("NOTHING_TO_INLINE")
-inline operator fun <T> IndexedList<T>.minus(element: T): IndexedList<T> =
-    copy().apply { this -= element }
-
-@Suppress("NOTHING_TO_INLINE")
-inline operator fun <T> IndexedList<T>.minusAssign(element: T) {
-    remove(element)
-}
-
-inline fun <T> IndexedList<T>.noneIndexed(predicate: (Int, T) -> Boolean): Boolean {
-    forEachIndexed { index, element ->
-        if (predicate(index, element)) {
-            return false
-        }
-    }
-    return true
-}
-
-@Suppress("NOTHING_TO_INLINE")
-inline operator fun <T> IndexedList<T>.plus(element: T): IndexedList<T> =
-    copy().apply { this += element }
-
-@Suppress("NOTHING_TO_INLINE")
-inline operator fun <T> IndexedList<T>.plusAssign(element: T) {
-    add(element)
-}
-
-inline fun <T> IndexedList<T>.removeAllIndexed(predicate: (Int, T) -> Boolean): Boolean {
-    var isChanged = false
-    forEachReversedIndexed { index, element ->
-        if (predicate(index, element)) {
-            removeAt(index)
-            isChanged = true
-        }
-    }
-    return isChanged
-}
-
-inline fun <T> IndexedList<T>.retainAllIndexed(predicate: (Int, T) -> Boolean): Boolean {
-    var isChanged = false
-    forEachReversedIndexed { index, element ->
-        if (!predicate(index, element)) {
-            removeAt(index)
-            isChanged = true
-        }
-    }
-    return isChanged
-}
-
-inline fun <T, R> IndexedList<T>.mapNotNullIndexed(transform: (T) -> R?): IndexedList<R> =
-    IndexedList<R>().also { destination ->
-        forEachIndexed { _, element ->
-            transform(element)?.let { destination += it }
-        }
-    }
diff --git a/services/permission/java/com/android/server/permission/access/collection/IndexedListSet.kt b/services/permission/java/com/android/server/permission/access/collection/IndexedListSet.kt
deleted file mode 100644
index c40f7ee..0000000
--- a/services/permission/java/com/android/server/permission/access/collection/IndexedListSet.kt
+++ /dev/null
@@ -1,152 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.permission.access.collection
-
-class IndexedListSet<T> private constructor(
-    private val list: ArrayList<T>
-) : MutableSet<T> {
-    constructor() : this(ArrayList())
-
-    override val size: Int
-        get() = list.size
-
-    override fun contains(element: T): Boolean = list.contains(element)
-
-    override fun isEmpty(): Boolean = list.isEmpty()
-
-    override fun iterator(): MutableIterator<T> = list.iterator()
-
-    override fun containsAll(elements: Collection<T>): Boolean {
-        throw NotImplementedError()
-    }
-
-    fun elementAt(index: Int): T = list[index]
-
-    fun indexOf(element: T): Int = list.indexOf(element)
-
-    override fun add(element: T): Boolean =
-        if (list.contains(element)) {
-            false
-        } else {
-            list.add(element)
-            true
-        }
-
-    override fun remove(element: T): Boolean = list.remove(element)
-
-    override fun clear() {
-        list.clear()
-    }
-
-    override fun addAll(elements: Collection<T>): Boolean {
-        throw NotImplementedError()
-    }
-
-    override fun removeAll(elements: Collection<T>): Boolean {
-        throw NotImplementedError()
-    }
-
-    override fun retainAll(elements: Collection<T>): Boolean {
-        throw NotImplementedError()
-    }
-
-    fun removeAt(index: Int): T? = list.removeAt(index)
-
-    fun copy(): IndexedListSet<T> = IndexedListSet(ArrayList(list))
-}
-
-inline fun <T> IndexedListSet<T>.allIndexed(predicate: (Int, T) -> Boolean): Boolean {
-    forEachIndexed { index, element ->
-        if (!predicate(index, element)) {
-            return false
-        }
-    }
-    return true
-}
-
-inline fun <T> IndexedListSet<T>.anyIndexed(predicate: (Int, T) -> Boolean): Boolean {
-    forEachIndexed { index, element ->
-        if (predicate(index, element)) {
-            return true
-        }
-    }
-    return false
-}
-
-inline fun <T> IndexedListSet<T>.forEachIndexed(action: (Int, T) -> Unit) {
-    for (index in indices) {
-        action(index, elementAt(index))
-    }
-}
-
-inline fun <T> IndexedListSet<T>.forEachReversedIndexed(action: (Int, T) -> Unit) {
-    for (index in lastIndex downTo 0) {
-        action(index, elementAt(index))
-    }
-}
-
-inline val <T> IndexedListSet<T>.lastIndex: Int
-    get() = size - 1
-
-@Suppress("NOTHING_TO_INLINE")
-inline operator fun <T> IndexedListSet<T>.minus(element: T): IndexedListSet<T> =
-    copy().apply { this -= element }
-
-@Suppress("NOTHING_TO_INLINE")
-inline operator fun <T> IndexedListSet<T>.minusAssign(element: T) {
-    remove(element)
-}
-
-inline fun <T> IndexedListSet<T>.noneIndexed(predicate: (Int, T) -> Boolean): Boolean {
-    forEachIndexed { index, element ->
-        if (predicate(index, element)) {
-            return false
-        }
-    }
-    return true
-}
-
-@Suppress("NOTHING_TO_INLINE")
-inline operator fun <T> IndexedListSet<T>.plus(element: T): IndexedListSet<T> =
-    copy().apply { this += element }
-
-@Suppress("NOTHING_TO_INLINE")
-inline operator fun <T> IndexedListSet<T>.plusAssign(element: T) {
-    add(element)
-}
-
-inline fun <T> IndexedListSet<T>.removeAllIndexed(predicate: (Int, T) -> Boolean): Boolean {
-    var isChanged = false
-    forEachReversedIndexed { index, element ->
-        if (predicate(index, element)) {
-            removeAt(index)
-            isChanged = true
-        }
-    }
-    return isChanged
-}
-
-inline fun <T> IndexedListSet<T>.retainAllIndexed(predicate: (Int, T) -> Boolean): Boolean {
-    var isChanged = false
-    forEachReversedIndexed { index, element ->
-        if (!predicate(index, element)) {
-            removeAt(index)
-            isChanged = true
-        }
-    }
-    return isChanged
-}
diff --git a/services/permission/java/com/android/server/permission/access/collection/IndexedMap.kt b/services/permission/java/com/android/server/permission/access/collection/IndexedMap.kt
deleted file mode 100644
index 998d206..0000000
--- a/services/permission/java/com/android/server/permission/access/collection/IndexedMap.kt
+++ /dev/null
@@ -1,179 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.permission.access.collection
-
-import android.util.ArrayMap
-
-typealias IndexedMap<K, V> = ArrayMap<K, V>
-
-inline fun <K, V> IndexedMap<K, V>.allIndexed(predicate: (Int, K, V) -> Boolean): Boolean {
-    forEachIndexed { index, key, value ->
-        if (!predicate(index, key, value)) {
-            return false
-        }
-    }
-    return true
-}
-
-inline fun <K, V> IndexedMap<K, V>.anyIndexed(predicate: (Int, K, V) -> Boolean): Boolean {
-    forEachIndexed { index, key, value ->
-        if (predicate(index, key, value)) {
-            return true
-        }
-    }
-    return false
-}
-
-inline fun <K, V> IndexedMap<K, V>.copy(copyValue: (V) -> V): IndexedMap<K, V> =
-    IndexedMap(this).apply {
-        forEachValueIndexed { index, value ->
-            setValueAt(index, copyValue(value))
-        }
-    }
-
-inline fun <K, V, R> IndexedMap<K, V>.firstNotNullOfOrNullIndexed(transform: (Int, K, V) -> R): R? {
-    forEachIndexed { index, key, value ->
-        transform(index, key, value)?.let { return it }
-    }
-    return null
-}
-
-inline fun <K, V> IndexedMap<K, V>.forEachIndexed(action: (Int, K, V) -> Unit) {
-    for (index in 0 until size) {
-        action(index, keyAt(index), valueAt(index))
-    }
-}
-
-inline fun <K, V> IndexedMap<K, V>.forEachKeyIndexed(action: (Int, K) -> Unit) {
-    for (index in 0 until size) {
-        action(index, keyAt(index))
-    }
-}
-
-inline fun <K, V> IndexedMap<K, V>.forEachReversedIndexed(action: (Int, K, V) -> Unit) {
-    for (index in lastIndex downTo 0) {
-        action(index, keyAt(index), valueAt(index))
-    }
-}
-
-inline fun <K, V> IndexedMap<K, V>.forEachValueIndexed(action: (Int, V) -> Unit) {
-    for (index in 0 until size) {
-        action(index, valueAt(index))
-    }
-}
-
-inline fun <K, V> IndexedMap<K, V>.getOrPut(key: K, defaultValue: () -> V): V {
-    get(key)?.let { return it }
-    return defaultValue().also { put(key, it) }
-}
-
-@Suppress("NOTHING_TO_INLINE")
-inline fun <K, V> IndexedMap<K, V>?.getWithDefault(key: K, defaultValue: V): V {
-    this ?: return defaultValue
-    val index = indexOfKey(key)
-    return if (index >= 0) valueAt(index) else defaultValue
-}
-
-inline val <K, V> IndexedMap<K, V>.lastIndex: Int
-    get() = size - 1
-
-@Suppress("NOTHING_TO_INLINE")
-inline operator fun <K, V> IndexedMap<K, V>.minusAssign(key: K) {
-    remove(key)
-}
-
-inline fun <K, V> IndexedMap<K, V>.noneIndexed(predicate: (Int, K, V) -> Boolean): Boolean {
-    forEachIndexed { index, key, value ->
-        if (predicate(index, key, value)) {
-            return false
-        }
-    }
-    return true
-}
-
-@Suppress("NOTHING_TO_INLINE")
-inline fun <K, V> IndexedMap<K, V>.putWithDefault(key: K, value: V, defaultValue: V): V {
-    val index = indexOfKey(key)
-    if (index >= 0) {
-        val oldValue = valueAt(index)
-        if (value != oldValue) {
-            if (value == defaultValue) {
-                removeAt(index)
-            } else {
-                setValueAt(index, value)
-            }
-        }
-        return oldValue
-    } else {
-        if (value != defaultValue) {
-            put(key, value)
-        }
-        return defaultValue
-    }
-}
-
-inline fun <K, V> IndexedMap<K, V>.removeAllIndexed(predicate: (Int, K, V) -> Boolean): Boolean {
-    var isChanged = false
-    forEachReversedIndexed { index, key, value ->
-        if (predicate(index, key, value)) {
-            removeAt(index)
-            isChanged = true
-        }
-    }
-    return isChanged
-}
-
-inline fun <K, V> IndexedMap<K, V>.retainAllIndexed(predicate: (Int, K, V) -> Boolean): Boolean {
-    var isChanged = false
-    forEachReversedIndexed { index, key, value ->
-        if (!predicate(index, key, value)) {
-            removeAt(index)
-            isChanged = true
-        }
-    }
-    return isChanged
-}
-
-inline fun <K, V, R> IndexedMap<K, V>.mapIndexed(transform: (Int, K, V) -> R): IndexedList<R> =
-    IndexedList<R>().also { destination ->
-        forEachIndexed { index, key, value ->
-            transform(index, key, value).let { destination += it }
-        }
-    }
-
-inline fun <K, V, R> IndexedMap<K, V>.mapNotNullIndexed(
-    transform: (Int, K, V) -> R?
-): IndexedList<R> =
-    IndexedList<R>().also { destination ->
-        forEachIndexed { index, key, value ->
-            transform(index, key, value)?.let { destination += it }
-        }
-    }
-
-inline fun <K, V, R> IndexedMap<K, V>.mapNotNullIndexedToSet(
-    transform: (Int, K, V) -> R?
-): IndexedSet<R> =
-    IndexedSet<R>().also { destination ->
-        forEachIndexed { index, key, value ->
-            transform(index, key, value)?.let { destination += it }
-        }
-    }
-
-@Suppress("NOTHING_TO_INLINE")
-inline operator fun <K, V> IndexedMap<K, V>.set(key: K, value: V) {
-    put(key, value)
-}
diff --git a/services/permission/java/com/android/server/permission/access/collection/IndexedSet.kt b/services/permission/java/com/android/server/permission/access/collection/IndexedSet.kt
deleted file mode 100644
index 13fa31f..0000000
--- a/services/permission/java/com/android/server/permission/access/collection/IndexedSet.kt
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.permission.access.collection
-
-import android.util.ArraySet
-
-typealias IndexedSet<T> = ArraySet<T>
-
-inline fun <T> IndexedSet<T>.allIndexed(predicate: (Int, T) -> Boolean): Boolean {
-    forEachIndexed { index, element ->
-        if (!predicate(index, element)) {
-            return false
-        }
-    }
-    return true
-}
-
-inline fun <T> IndexedSet<T>.anyIndexed(predicate: (Int, T) -> Boolean): Boolean {
-    forEachIndexed { index, element ->
-        if (predicate(index, element)) {
-            return true
-        }
-    }
-    return false
-}
-
-@Suppress("NOTHING_TO_INLINE")
-inline fun <T> IndexedSet<T>.copy(): IndexedSet<T> = IndexedSet(this)
-
-@Suppress("NOTHING_TO_INLINE")
-inline fun <T> IndexedSet<T>.elementAt(index: Int): T = valueAt(index)
-
-inline fun <T> IndexedSet<T>.forEachIndexed(action: (Int, T) -> Unit) {
-    for (index in indices) {
-        action(index, elementAt(index))
-    }
-}
-
-inline fun <T> IndexedSet<T>.forEachReversedIndexed(action: (Int, T) -> Unit) {
-    for (index in lastIndex downTo 0) {
-        action(index, elementAt(index))
-    }
-}
-
-inline val <T> IndexedSet<T>.lastIndex: Int
-    get() = size - 1
-
-@Suppress("NOTHING_TO_INLINE")
-inline operator fun <T> IndexedSet<T>.minus(element: T): IndexedSet<T> =
-    copy().apply { this -= element }
-
-@Suppress("NOTHING_TO_INLINE")
-inline operator fun <T> IndexedSet<T>.minusAssign(element: T) {
-    remove(element)
-}
-
-inline fun <T> IndexedSet<T>.noneIndexed(predicate: (Int, T) -> Boolean): Boolean {
-    forEachIndexed { index, element ->
-        if (predicate(index, element)) {
-            return false
-        }
-    }
-    return true
-}
-
-@Suppress("NOTHING_TO_INLINE")
-inline operator fun <T> IndexedSet<T>.plus(element: T): IndexedSet<T> =
-    copy().apply { this += element }
-
-@Suppress("NOTHING_TO_INLINE")
-inline operator fun <T> IndexedSet<T>.plusAssign(element: T) {
-    add(element)
-}
-
-inline fun <T> IndexedSet<T>.removeAllIndexed(predicate: (Int, T) -> Boolean): Boolean {
-    var isChanged = false
-    forEachReversedIndexed { index, element ->
-        if (predicate(index, element)) {
-            removeAt(index)
-            isChanged = true
-        }
-    }
-    return isChanged
-}
-
-inline fun <T> IndexedSet<T>.retainAllIndexed(predicate: (Int, T) -> Boolean): Boolean {
-    var isChanged = false
-    forEachReversedIndexed { index, element ->
-        if (!predicate(index, element)) {
-            removeAt(index)
-            isChanged = true
-        }
-    }
-    return isChanged
-}
-
-@Suppress("NOTHING_TO_INLINE")
-inline fun <T> indexedSetOf(vararg elements: T): IndexedSet<T> = IndexedSet(elements.asList())
diff --git a/services/permission/java/com/android/server/permission/access/collection/IntBooleanMap.kt b/services/permission/java/com/android/server/permission/access/collection/IntBooleanMap.kt
deleted file mode 100644
index 2f7b9bf..0000000
--- a/services/permission/java/com/android/server/permission/access/collection/IntBooleanMap.kt
+++ /dev/null
@@ -1,171 +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.server.permission.access.collection
-
-import android.util.SparseBooleanArray
-
-typealias IntBooleanMap = SparseBooleanArray
-
-inline fun IntBooleanMap.allIndexed(predicate: (Int, Int, Boolean) -> Boolean): Boolean {
-    forEachIndexed { index, key, value ->
-        if (!predicate(index, key, value)) {
-            return false
-        }
-    }
-    return true
-}
-
-inline fun IntBooleanMap.anyIndexed(predicate: (Int, Int, Boolean) -> Boolean): Boolean {
-    forEachIndexed { index, key, value ->
-        if (predicate(index, key, value)) {
-            return true
-        }
-    }
-    return false
-}
-
-@Suppress("NOTHING_TO_INLINE")
-inline fun IntBooleanMap.copy(): IntBooleanMap = clone()
-
-inline fun <R> IntBooleanMap.firstNotNullOfOrNullIndexed(transform: (Int, Int, Boolean) -> R): R? {
-    forEachIndexed { index, key, value ->
-        transform(index, key, value)?.let { return it }
-    }
-    return null
-}
-
-inline fun IntBooleanMap.forEachIndexed(action: (Int, Int, Boolean) -> Unit) {
-    for (index in 0 until size) {
-        action(index, keyAt(index), valueAt(index))
-    }
-}
-
-inline fun IntBooleanMap.forEachKeyIndexed(action: (Int, Int) -> Unit) {
-    for (index in 0 until size) {
-        action(index, keyAt(index))
-    }
-}
-
-inline fun IntBooleanMap.forEachReversedIndexed(action: (Int, Int, Boolean) -> Unit) {
-    for (index in lastIndex downTo 0) {
-        action(index, keyAt(index), valueAt(index))
-    }
-}
-
-inline fun IntBooleanMap.forEachValueIndexed(action: (Int, Boolean) -> Unit) {
-    for (index in 0 until size) {
-        action(index, valueAt(index))
-    }
-}
-
-inline fun IntBooleanMap.getOrPut(key: Int, defaultValue: () -> Boolean): Boolean {
-    val index = indexOfKey(key)
-    return if (index >= 0) {
-        valueAt(index)
-    } else {
-        defaultValue().also { put(key, it) }
-    }
-}
-
-@Suppress("NOTHING_TO_INLINE")
-inline fun IntBooleanMap?.getWithDefault(key: Int, defaultValue: Boolean): Boolean {
-    this ?: return defaultValue
-    return get(key, defaultValue)
-}
-
-inline val IntBooleanMap.lastIndex: Int
-    get() = size - 1
-
-@Suppress("NOTHING_TO_INLINE")
-inline operator fun IntBooleanMap.minusAssign(key: Int) {
-    delete(key)
-}
-
-inline fun IntBooleanMap.noneIndexed(predicate: (Int, Int, Boolean) -> Boolean): Boolean {
-    forEachIndexed { index, key, value ->
-        if (predicate(index, key, value)) {
-            return false
-        }
-    }
-    return true
-}
-
-@Suppress("NOTHING_TO_INLINE")
-inline fun IntBooleanMap.putWithDefault(key: Int, value: Boolean, defaultValue: Boolean): Boolean {
-    val index = indexOfKey(key)
-    if (index >= 0) {
-        val oldValue = valueAt(index)
-        if (value != oldValue) {
-            if (value == defaultValue) {
-                removeAt(index)
-            } else {
-                setValueAt(index, value)
-            }
-        }
-        return oldValue
-    } else {
-        if (value != defaultValue) {
-            put(key, value)
-        }
-        return defaultValue
-    }
-}
-
-fun IntBooleanMap.remove(key: Int) {
-    delete(key)
-}
-
-fun IntBooleanMap.remove(key: Int, defaultValue: Boolean): Boolean {
-    val index = indexOfKey(key)
-    return if (index >= 0) {
-        val oldValue = valueAt(index)
-        removeAt(index)
-        oldValue
-    } else {
-        defaultValue
-    }
-}
-
-inline fun IntBooleanMap.removeAllIndexed(predicate: (Int, Int, Boolean) -> Boolean): Boolean {
-    var isChanged = false
-    forEachReversedIndexed { index, key, value ->
-        if (predicate(index, key, value)) {
-            removeAt(index)
-            isChanged = true
-        }
-    }
-    return isChanged
-}
-
-inline fun IntBooleanMap.retainAllIndexed(predicate: (Int, Int, Boolean) -> Boolean): Boolean {
-    var isChanged = false
-    forEachReversedIndexed { index, key, value ->
-        if (!predicate(index, key, value)) {
-            removeAt(index)
-            isChanged = true
-        }
-    }
-    return isChanged
-}
-
-@Suppress("NOTHING_TO_INLINE")
-inline operator fun IntBooleanMap.set(key: Int, value: Boolean) {
-    put(key, value)
-}
-
-inline val IntBooleanMap.size: Int
-    get() = size()
diff --git a/services/permission/java/com/android/server/permission/access/collection/IntLongMap.kt b/services/permission/java/com/android/server/permission/access/collection/IntLongMap.kt
deleted file mode 100644
index 692bbd6..0000000
--- a/services/permission/java/com/android/server/permission/access/collection/IntLongMap.kt
+++ /dev/null
@@ -1,171 +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.server.permission.access.collection
-
-import android.util.SparseLongArray
-
-typealias IntLongMap = SparseLongArray
-
-inline fun IntLongMap.allIndexed(predicate: (Int, Int, Long) -> Boolean): Boolean {
-    forEachIndexed { index, key, value ->
-        if (!predicate(index, key, value)) {
-            return false
-        }
-    }
-    return true
-}
-
-inline fun IntLongMap.anyIndexed(predicate: (Int, Int, Long) -> Boolean): Boolean {
-    forEachIndexed { index, key, value ->
-        if (predicate(index, key, value)) {
-            return true
-        }
-    }
-    return false
-}
-
-@Suppress("NOTHING_TO_INLINE")
-inline fun IntLongMap.copy(): IntLongMap = clone()
-
-inline fun <R> IntLongMap.firstNotNullOfOrNullIndexed(transform: (Int, Int, Long) -> R): R? {
-    forEachIndexed { index, key, value ->
-        transform(index, key, value)?.let { return it }
-    }
-    return null
-}
-
-inline fun IntLongMap.forEachIndexed(action: (Int, Int, Long) -> Unit) {
-    for (index in 0 until size) {
-        action(index, keyAt(index), valueAt(index))
-    }
-}
-
-inline fun IntLongMap.forEachKeyIndexed(action: (Int, Int) -> Unit) {
-    for (index in 0 until size) {
-        action(index, keyAt(index))
-    }
-}
-
-inline fun IntLongMap.forEachReversedIndexed(action: (Int, Int, Long) -> Unit) {
-    for (index in lastIndex downTo 0) {
-        action(index, keyAt(index), valueAt(index))
-    }
-}
-
-inline fun IntLongMap.forEachValueIndexed(action: (Int, Long) -> Unit) {
-    for (index in 0 until size) {
-        action(index, valueAt(index))
-    }
-}
-
-inline fun IntLongMap.getOrPut(key: Int, defaultValue: () -> Long): Long {
-    val index = indexOfKey(key)
-    return if (index >= 0) {
-        valueAt(index)
-    } else {
-        defaultValue().also { put(key, it) }
-    }
-}
-
-@Suppress("NOTHING_TO_INLINE")
-inline fun IntLongMap?.getWithDefault(key: Int, defaultValue: Long): Long {
-    this ?: return defaultValue
-    return get(key, defaultValue)
-}
-
-inline val IntLongMap.lastIndex: Int
-    get() = size - 1
-
-@Suppress("NOTHING_TO_INLINE")
-inline operator fun IntLongMap.minusAssign(key: Int) {
-    delete(key)
-}
-
-inline fun IntLongMap.noneIndexed(predicate: (Int, Int, Long) -> Boolean): Boolean {
-    forEachIndexed { index, key, value ->
-        if (predicate(index, key, value)) {
-            return false
-        }
-    }
-    return true
-}
-
-@Suppress("NOTHING_TO_INLINE")
-inline fun IntLongMap.putWithDefault(key: Int, value: Long, defaultValue: Long): Long {
-    val index = indexOfKey(key)
-    if (index >= 0) {
-        val oldValue = valueAt(index)
-        if (value != oldValue) {
-            if (value == defaultValue) {
-                removeAt(index)
-            } else {
-                setValueAt(index, value)
-            }
-        }
-        return oldValue
-    } else {
-        if (value != defaultValue) {
-            put(key, value)
-        }
-        return defaultValue
-    }
-}
-
-fun IntLongMap.remove(key: Int) {
-    delete(key)
-}
-
-fun IntLongMap.remove(key: Int, defaultValue: Long): Long {
-    val index = indexOfKey(key)
-    return if (index >= 0) {
-        val oldValue = valueAt(index)
-        removeAt(index)
-        oldValue
-    } else {
-        defaultValue
-    }
-}
-
-inline fun IntLongMap.removeAllIndexed(predicate: (Int, Int, Long) -> Boolean): Boolean {
-    var isChanged = false
-    forEachReversedIndexed { index, key, value ->
-        if (predicate(index, key, value)) {
-            removeAt(index)
-            isChanged = true
-        }
-    }
-    return isChanged
-}
-
-inline fun IntLongMap.retainAllIndexed(predicate: (Int, Int, Long) -> Boolean): Boolean {
-    var isChanged = false
-    forEachReversedIndexed { index, key, value ->
-        if (!predicate(index, key, value)) {
-            removeAt(index)
-            isChanged = true
-        }
-    }
-    return isChanged
-}
-
-@Suppress("NOTHING_TO_INLINE")
-inline operator fun IntLongMap.set(key: Int, value: Long) {
-    put(key, value)
-}
-
-inline val IntLongMap.size: Int
-    get() = size()
diff --git a/services/permission/java/com/android/server/permission/access/collection/IntMap.kt b/services/permission/java/com/android/server/permission/access/collection/IntMap.kt
deleted file mode 100644
index e905567..0000000
--- a/services/permission/java/com/android/server/permission/access/collection/IntMap.kt
+++ /dev/null
@@ -1,164 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.permission.access.collection
-
-import android.util.SparseArray
-
-typealias IntMap<T> = SparseArray<T>
-
-inline fun <T> IntMap<T>.allIndexed(predicate: (Int, Int, T) -> Boolean): Boolean {
-    forEachIndexed { index, key, value ->
-        if (!predicate(index, key, value)) {
-            return false
-        }
-    }
-    return true
-}
-
-inline fun <T> IntMap<T>.anyIndexed(predicate: (Int, Int, T) -> Boolean): Boolean {
-    forEachIndexed { index, key, value ->
-        if (predicate(index, key, value)) {
-            return true
-        }
-    }
-    return false
-}
-
-inline fun <T> IntMap<T>.copy(copyValue: (T) -> T): IntMap<T> =
-    this.clone().apply {
-        forEachValueIndexed { index, value ->
-            setValueAt(index, copyValue(value))
-        }
-    }
-
-inline fun <T, R> IntMap<T>.firstNotNullOfOrNullIndexed(transform: (Int, Int, T) -> R): R? {
-    forEachIndexed { index, key, value ->
-        transform(index, key, value)?.let { return it }
-    }
-    return null
-}
-
-inline fun <T> IntMap<T>.forEachIndexed(action: (Int, Int, T) -> Unit) {
-    for (index in 0 until size) {
-        action(index, keyAt(index), valueAt(index))
-    }
-}
-
-inline fun <T> IntMap<T>.forEachKeyIndexed(action: (Int, Int) -> Unit) {
-    for (index in 0 until size) {
-        action(index, keyAt(index))
-    }
-}
-
-inline fun <T> IntMap<T>.forEachReversedIndexed(action: (Int, Int, T) -> Unit) {
-    for (index in lastIndex downTo 0) {
-        action(index, keyAt(index), valueAt(index))
-    }
-}
-
-inline fun <T> IntMap<T>.forEachValueIndexed(action: (Int, T) -> Unit) {
-    for (index in 0 until size) {
-        action(index, valueAt(index))
-    }
-}
-
-inline fun <T> IntMap<T>.getOrPut(key: Int, defaultValue: () -> T): T {
-    get(key)?.let { return it }
-    return defaultValue().also { put(key, it) }
-}
-
-@Suppress("NOTHING_TO_INLINE")
-inline fun <T> IntMap<T>?.getWithDefault(key: Int, defaultValue: T): T {
-    this ?: return defaultValue
-    val index = indexOfKey(key)
-    return if (index >= 0) valueAt(index) else defaultValue
-}
-
-inline val <T> IntMap<T>.lastIndex: Int
-    get() = size - 1
-
-@Suppress("NOTHING_TO_INLINE")
-inline operator fun <T> IntMap<T>.minusAssign(key: Int) {
-    remove(key)
-}
-
-inline fun <T> IntMap<T>.noneIndexed(predicate: (Int, Int, T) -> Boolean): Boolean {
-    forEachIndexed { index, key, value ->
-        if (predicate(index, key, value)) {
-            return false
-        }
-    }
-    return true
-}
-
-@Suppress("NOTHING_TO_INLINE")
-inline fun <T> IntMap<T>.putWithDefault(key: Int, value: T, defaultValue: T): T {
-    val index = indexOfKey(key)
-    if (index >= 0) {
-        val oldValue = valueAt(index)
-        if (value != oldValue) {
-            if (value == defaultValue) {
-                removeAt(index)
-            } else {
-                setValueAt(index, value)
-            }
-        }
-        return oldValue
-    } else {
-        if (value != defaultValue) {
-            put(key, value)
-        }
-        return defaultValue
-    }
-}
-
-// SparseArray.removeReturnOld() is @hide, so a backup once we move to APIs.
-fun <T> IntMap<T>.removeReturnOld(key: Int): T? {
-    val index = indexOfKey(key)
-    return if (index >= 0) {
-        val oldValue = valueAt(index)
-        removeAt(index)
-        oldValue
-    } else {
-        null
-    }
-}
-
-inline fun <T> IntMap<T>.removeAllIndexed(predicate: (Int, Int, T) -> Boolean): Boolean {
-    var isChanged = false
-    forEachReversedIndexed { index, key, value ->
-        if (predicate(index, key, value)) {
-            removeAt(index)
-            isChanged = true
-        }
-    }
-    return isChanged
-}
-
-inline fun <T> IntMap<T>.retainAllIndexed(predicate: (Int, Int, T) -> Boolean): Boolean {
-    var isChanged = false
-    forEachReversedIndexed { index, key, value ->
-        if (!predicate(index, key, value)) {
-            removeAt(index)
-            isChanged = true
-        }
-    }
-    return isChanged
-}
-
-inline val <T> IntMap<T>.size: Int
-    get() = size()
diff --git a/services/permission/java/com/android/server/permission/access/collection/IntSet.kt b/services/permission/java/com/android/server/permission/access/collection/IntSet.kt
deleted file mode 100644
index 4717251..0000000
--- a/services/permission/java/com/android/server/permission/access/collection/IntSet.kt
+++ /dev/null
@@ -1,142 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.permission.access.collection
-
-import android.util.SparseBooleanArray
-
-class IntSet private constructor(
-    private val array: SparseBooleanArray
-) {
-    constructor() : this(SparseBooleanArray())
-
-    val size: Int
-        get() = array.size()
-
-    operator fun contains(element: Int): Boolean = array[element]
-
-    fun elementAt(index: Int): Int = array.keyAt(index)
-
-    fun indexOf(element: Int): Int = array.indexOfKey(element)
-
-    fun add(element: Int) {
-        array.put(element, true)
-    }
-
-    fun remove(element: Int) {
-        array.delete(element)
-    }
-
-    fun clear() {
-        array.clear()
-    }
-
-    fun removeAt(index: Int) {
-        array.removeAt(index)
-    }
-
-    fun copy(): IntSet = IntSet(array.clone())
-}
-
-fun IntSet(values: IntArray): IntSet = IntSet().apply{ this += values }
-
-inline fun IntSet.allIndexed(predicate: (Int, Int) -> Boolean): Boolean {
-    forEachIndexed { index, element ->
-        if (!predicate(index, element)) {
-            return false
-        }
-    }
-    return true
-}
-
-inline fun IntSet.anyIndexed(predicate: (Int, Int) -> Boolean): Boolean {
-    forEachIndexed { index, element ->
-        if (predicate(index, element)) {
-            return true
-        }
-    }
-    return false
-}
-
-inline fun IntSet.forEachIndexed(action: (Int, Int) -> Unit) {
-    for (index in 0 until size) {
-        action(index, elementAt(index))
-    }
-}
-
-inline fun IntSet.forEachReversedIndexed(action: (Int, Int) -> Unit) {
-    for (index in lastIndex downTo 0) {
-        action(index, elementAt(index))
-    }
-}
-
-inline val IntSet.lastIndex: Int
-    get() = size - 1
-
-@Suppress("NOTHING_TO_INLINE")
-inline operator fun IntSet.minus(element: Int): IntSet = copy().apply { this -= element }
-
-@Suppress("NOTHING_TO_INLINE")
-inline operator fun IntSet.minusAssign(element: Int) {
-    remove(element)
-}
-
-inline fun IntSet.noneIndexed(predicate: (Int, Int) -> Boolean): Boolean {
-    forEachIndexed { index, element ->
-        if (predicate(index, element)) {
-            return false
-        }
-    }
-    return true
-}
-
-@Suppress("NOTHING_TO_INLINE")
-inline operator fun IntSet.plus(element: Int): IntSet = copy().apply { this += element }
-
-@Suppress("NOTHING_TO_INLINE")
-inline operator fun IntSet.plusAssign(element: Int) {
-    add(element)
-}
-
-operator fun IntSet.plusAssign(set: IntSet) {
-    set.forEachIndexed { _, it -> this += it }
-}
-
-operator fun IntSet.plusAssign(array: IntArray) {
-    array.forEach { this += it }
-}
-
-inline fun IntSet.removeAllIndexed(predicate: (Int, Int) -> Boolean): Boolean {
-    var isChanged = false
-    forEachReversedIndexed { index, element ->
-        if (predicate(index, element)) {
-            removeAt(index)
-            isChanged = true
-        }
-    }
-    return isChanged
-}
-
-inline fun IntSet.retainAllIndexed(predicate: (Int, Int) -> Boolean): Boolean {
-    var isChanged = false
-    forEachReversedIndexed { index, element ->
-        if (!predicate(index, element)) {
-            removeAt(index)
-            isChanged = true
-        }
-    }
-    return isChanged
-}
diff --git a/services/permission/java/com/android/server/permission/access/collection/List.kt b/services/permission/java/com/android/server/permission/access/collection/ListExtensions.kt
similarity index 100%
rename from services/permission/java/com/android/server/permission/access/collection/List.kt
rename to services/permission/java/com/android/server/permission/access/collection/ListExtensions.kt
diff --git a/services/permission/java/com/android/server/permission/access/collection/SparseArrayExtensions.kt b/services/permission/java/com/android/server/permission/access/collection/SparseArrayExtensions.kt
new file mode 100644
index 0000000..8b7f3de
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/collection/SparseArrayExtensions.kt
@@ -0,0 +1,100 @@
+/*
+ * 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.permission.access.collection
+
+import android.util.SparseArray
+
+inline fun <T> SparseArray<T>.allIndexed(predicate: (Int, Int, T) -> Boolean): Boolean {
+    forEachIndexed { index, key, value ->
+        if (!predicate(index, key, value)) {
+            return false
+        }
+    }
+    return true
+}
+
+inline fun <T> SparseArray<T>.anyIndexed(predicate: (Int, Int, T) -> Boolean): Boolean {
+    forEachIndexed { index, key, value ->
+        if (predicate(index, key, value)) {
+            return true
+        }
+    }
+    return false
+}
+
+inline fun <T> SparseArray<T>.forEachIndexed(action: (Int, Int, T) -> Unit) {
+    for (index in 0 until size) {
+        action(index, keyAt(index), valueAt(index))
+    }
+}
+
+inline fun <T> SparseArray<T>.forEachReversedIndexed(action: (Int, Int, T) -> Unit) {
+    for (index in lastIndex downTo 0) {
+        action(index, keyAt(index), valueAt(index))
+    }
+}
+
+inline fun <T> SparseArray<T>.getOrPut(key: Int, defaultValue: () -> T): T {
+    val index = indexOfKey(key)
+    return if (index >= 0) {
+        valueAt(index)
+    } else {
+        defaultValue().also { put(key, it) }
+    }
+}
+
+inline val <T> SparseArray<T>.lastIndex: Int
+    get() = size - 1
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun <T> SparseArray<T>.minusAssign(key: Int) {
+    delete(key)
+}
+
+inline fun <T> SparseArray<T>.noneIndexed(predicate: (Int, Int, T) -> Boolean): Boolean {
+    forEachIndexed { index, key, value ->
+        if (predicate(index, key, value)) {
+            return false
+        }
+    }
+    return true
+}
+
+inline fun <T> SparseArray<T>.removeAllIndexed(predicate: (Int, Int, T) -> Boolean): Boolean {
+    var isChanged = false
+    forEachReversedIndexed { index, key, value ->
+        if (predicate(index, key, value)) {
+            removeAt(index)
+            isChanged = true
+        }
+    }
+    return isChanged
+}
+
+inline fun <T> SparseArray<T>.retainAllIndexed(predicate: (Int, Int, T) -> Boolean): Boolean {
+    var isChanged = false
+    forEachReversedIndexed { index, key, value ->
+        if (!predicate(index, key, value)) {
+            removeAt(index)
+            isChanged = true
+        }
+    }
+    return isChanged
+}
+
+inline val <T> SparseArray<T>.size: Int
+    get() = size()
diff --git a/services/permission/java/com/android/server/permission/access/collection/SparseBooleanArrayExtensions.kt b/services/permission/java/com/android/server/permission/access/collection/SparseBooleanArrayExtensions.kt
new file mode 100644
index 0000000..0a4c52b
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/collection/SparseBooleanArrayExtensions.kt
@@ -0,0 +1,120 @@
+/*
+ * 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.permission.access.collection
+
+import android.util.SparseBooleanArray
+
+inline fun SparseBooleanArray.allIndexed(predicate: (Int, Int, Boolean) -> Boolean): Boolean {
+    forEachIndexed { index, key, value ->
+        if (!predicate(index, key, value)) {
+            return false
+        }
+    }
+    return true
+}
+
+inline fun SparseBooleanArray.anyIndexed(predicate: (Int, Int, Boolean) -> Boolean): Boolean {
+    forEachIndexed { index, key, value ->
+        if (predicate(index, key, value)) {
+            return true
+        }
+    }
+    return false
+}
+
+inline fun SparseBooleanArray.forEachIndexed(action: (Int, Int, Boolean) -> Unit) {
+    for (index in 0 until size) {
+        action(index, keyAt(index), valueAt(index))
+    }
+}
+
+inline fun SparseBooleanArray.forEachReversedIndexed(action: (Int, Int, Boolean) -> Unit) {
+    for (index in lastIndex downTo 0) {
+        action(index, keyAt(index), valueAt(index))
+    }
+}
+
+inline fun SparseBooleanArray.getOrPut(key: Int, defaultValue: () -> Boolean): Boolean {
+    val index = indexOfKey(key)
+    return if (index >= 0) {
+        valueAt(index)
+    } else {
+        defaultValue().also { put(key, it) }
+    }
+}
+
+inline val SparseBooleanArray.lastIndex: Int
+    get() = size - 1
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun SparseBooleanArray.minusAssign(key: Int) {
+    delete(key)
+}
+
+inline fun SparseBooleanArray.noneIndexed(predicate: (Int, Int, Boolean) -> Boolean): Boolean {
+    forEachIndexed { index, key, value ->
+        if (predicate(index, key, value)) {
+            return false
+        }
+    }
+    return true
+}
+
+fun SparseBooleanArray.remove(key: Int) {
+    delete(key)
+}
+
+fun SparseBooleanArray.remove(key: Int, defaultValue: Boolean): Boolean {
+    val index = indexOfKey(key)
+    return if (index >= 0) {
+        val oldValue = valueAt(index)
+        removeAt(index)
+        oldValue
+    } else {
+        defaultValue
+    }
+}
+
+inline fun SparseBooleanArray.removeAllIndexed(predicate: (Int, Int, Boolean) -> Boolean): Boolean {
+    var isChanged = false
+    forEachReversedIndexed { index, key, value ->
+        if (predicate(index, key, value)) {
+            removeAt(index)
+            isChanged = true
+        }
+    }
+    return isChanged
+}
+
+inline fun SparseBooleanArray.retainAllIndexed(predicate: (Int, Int, Boolean) -> Boolean): Boolean {
+    var isChanged = false
+    forEachReversedIndexed { index, key, value ->
+        if (!predicate(index, key, value)) {
+            removeAt(index)
+            isChanged = true
+        }
+    }
+    return isChanged
+}
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun SparseBooleanArray.set(key: Int, value: Boolean) {
+    put(key, value)
+}
+
+inline val SparseBooleanArray.size: Int
+    get() = size()
diff --git a/services/permission/java/com/android/server/permission/access/collection/SparseLongArrayExtensions.kt b/services/permission/java/com/android/server/permission/access/collection/SparseLongArrayExtensions.kt
new file mode 100644
index 0000000..1149c52
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/collection/SparseLongArrayExtensions.kt
@@ -0,0 +1,120 @@
+/*
+ * 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.permission.access.collection
+
+import android.util.SparseLongArray
+
+inline fun SparseLongArray.allIndexed(predicate: (Int, Int, Long) -> Boolean): Boolean {
+    forEachIndexed { index, key, value ->
+        if (!predicate(index, key, value)) {
+            return false
+        }
+    }
+    return true
+}
+
+inline fun SparseLongArray.anyIndexed(predicate: (Int, Int, Long) -> Boolean): Boolean {
+    forEachIndexed { index, key, value ->
+        if (predicate(index, key, value)) {
+            return true
+        }
+    }
+    return false
+}
+
+inline fun SparseLongArray.forEachIndexed(action: (Int, Int, Long) -> Unit) {
+    for (index in 0 until size) {
+        action(index, keyAt(index), valueAt(index))
+    }
+}
+
+inline fun SparseLongArray.forEachReversedIndexed(action: (Int, Int, Long) -> Unit) {
+    for (index in lastIndex downTo 0) {
+        action(index, keyAt(index), valueAt(index))
+    }
+}
+
+inline fun SparseLongArray.getOrPut(key: Int, defaultValue: () -> Long): Long {
+    val index = indexOfKey(key)
+    return if (index >= 0) {
+        valueAt(index)
+    } else {
+        defaultValue().also { put(key, it) }
+    }
+}
+
+inline val SparseLongArray.lastIndex: Int
+    get() = size - 1
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun SparseLongArray.minusAssign(key: Int) {
+    delete(key)
+}
+
+inline fun SparseLongArray.noneIndexed(predicate: (Int, Int, Long) -> Boolean): Boolean {
+    forEachIndexed { index, key, value ->
+        if (predicate(index, key, value)) {
+            return false
+        }
+    }
+    return true
+}
+
+fun SparseLongArray.remove(key: Int) {
+    delete(key)
+}
+
+fun SparseLongArray.remove(key: Int, defaultValue: Long): Long {
+    val index = indexOfKey(key)
+    return if (index >= 0) {
+        val oldValue = valueAt(index)
+        removeAt(index)
+        oldValue
+    } else {
+        defaultValue
+    }
+}
+
+inline fun SparseLongArray.removeAllIndexed(predicate: (Int, Int, Long) -> Boolean): Boolean {
+    var isChanged = false
+    forEachReversedIndexed { index, key, value ->
+        if (predicate(index, key, value)) {
+            removeAt(index)
+            isChanged = true
+        }
+    }
+    return isChanged
+}
+
+inline fun SparseLongArray.retainAllIndexed(predicate: (Int, Int, Long) -> Boolean): Boolean {
+    var isChanged = false
+    forEachReversedIndexed { index, key, value ->
+        if (!predicate(index, key, value)) {
+            removeAt(index)
+            isChanged = true
+        }
+    }
+    return isChanged
+}
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun SparseLongArray.set(key: Int, value: Long) {
+    put(key, value)
+}
+
+inline val SparseLongArray.size: Int
+    get() = size()
diff --git a/services/permission/java/com/android/server/permission/access/immutable/Immutable.kt b/services/permission/java/com/android/server/permission/access/immutable/Immutable.kt
new file mode 100644
index 0000000..64e6d4d
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/immutable/Immutable.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.permission.access.immutable
+
+interface Immutable<M> {
+    fun toMutable(): M
+}
diff --git a/services/permission/java/com/android/server/permission/access/immutable/Immutable.md b/services/permission/java/com/android/server/permission/access/immutable/Immutable.md
new file mode 100644
index 0000000..dcf30d2
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/immutable/Immutable.md
@@ -0,0 +1,214 @@
+# Immutable Data Structures
+
+## Introduction
+
+The classes inside this package implements a way to manipulate data in an immutable way, which
+allows achieving lock-free reads for performance-critical code paths, and organizing the
+implementation of complex state transitions in a readable and maintainable way.
+
+## Features
+
+This implementation provides the following features:
+
+- Immutability is implemented leveraging the Java/Kotlin type system.
+
+    Each data structure has both an immutable and a mutable variant, so that the type system will be
+    enforcing proper operations on the data during compilation and preventing any accidental
+    mutations.
+
+- Unmodified portion of the data is shared between mutations.
+
+    Making a full copy of the entire state for any modification is often an overkill and bad for
+    performance, so a path-copy approach is taken when mutating part of the data, which is also
+    enforced by the type system.
+
+- Consecutive modifications can be batched.
+
+    This implementation keeps track of the mutation status of each object and reuses objects that
+    are already copied to perform further mutations, so that temporary copies won't be unnecessarily
+    created.
+
+- No manual `freeze()` calls needed at the end of modifications.
+
+    Thanks to the type system enforced immutability, a mutated data structure can simply be upcasted
+    back to its immutable variant at the end of mutations, so that any future modification will
+    require a new call to `toMutable()` which ensures a new copy is created. This eliminates a whole
+    class of potential issues with a required manual `freeze()` call, which may either be forgotten
+    for (part of) the data and result in hard-to-catch bugs, or require correct boilerplate code
+    that properly propagates this information across the entire tree of objects.
+
+- Android-specific data structures are included.
+
+    Android has its own collection classes (e.g. `ArrayMap` and `SparseArray`) that are preferred
+    (for typical amount of data) for performance reasons, and this implementation provides
+    immutability for them via wrapper classes so that the same underlying implementation is used and
+    the same performance goals are achieved.
+
+- Android Runtime performance is considered.
+
+    Both the immutable and mutable variants are defined as classes and their member methods are
+    final (default in Kotlin), so that the method invocations will be `invoke-direct` and allow
+    better AOT compilation.
+
+    The data structure classes here also deliberately chose to not implement the standard
+    Java/Kotlin collection interfaces, so that we can enforce that a number of standard Java/Kotlin
+    utilities that may be bad for performance or generate interface calls (e.g. Java 8 streams,
+    methods taking non-inlined lambdas and kotlin-stdlib extensions taking interfaces) won't be
+    accidentally used. We will only add utility methods when necessary and with proper performance
+    considerations (e.g. indexed iteration, taking class instead of interface).
+
+## Implementation
+
+### Immutable and mutable classes
+
+In order to leverage the type system to enforce immutability, the core idea is to have both an
+immutable and a mutable class for any data structure, where the latter extends the former
+(important for `MutableReference` later).
+
+### How mutation works
+
+The primary difficulty in design comes when data structures are composed together in a tree-like
+fashion, via map or custom data structures. Specifically, the mutation and copy-on-write would
+first happen on the immediate data structure that is being mutated, which would produce a new
+instance that contains the mutation, however it is the parent data structure that also needs to know
+about this new instance and mutate itself to update its reference to the new child. This problem is
+also referred to as "path copying" in persistent data structures.
+
+This design difficulty is solved by the following convention in this implementation. Normally, the
+immutable class is good for any read-only access. But when any mutations are needed, it can be
+started by calling a `toMutable()` method on the root data structure, which would return a mutable
+class over a shallow copy of the existing data. In order to perform the actual mutation deeper in
+the tree, a chain of `mutateFoo()` calls will be needed to obtain mutable classes of child data
+structures, while these `mutateFoo()` calls are also only available on mutable classes. This way,
+proper chain of mutation is also enforced by the type system, and unmodified data is unchanged and
+reused.
+
+Here is an example of how this convention would work in the real-world. A read access would just
+work as if this implementation isn't involved:
+
+```kotlin
+val permission = state.systemState.permissions[permissionName]
+```
+
+Whereas the write access would remain similar, which is natural and easy-to-use with safety
+guaranteed by the type system:
+
+```kotlin
+val newState = state.toMutable()
+newState.mutateSystemState().mutatePermissions().put(permission.name, permission)
+state = newState
+```
+
+### The magic: `MutableReference`
+
+The magic of the implementation for this convention comes from the `MutableReference` class, and
+below is a simplified version of it.
+
+```kotlin
+class MutableReference<I : Immutable<M>, M : I>(
+    private var immutable: I,
+    private var mutable: M?
+) {
+    fun get(): I = immutable
+
+    fun mutate(): M {
+        mutable?.let { return it }
+        return immutable.toMutable().also {
+            immutable = it
+            mutable = it
+        }
+    }
+
+    fun toImmutable(): MutableReference<I, M> = MutableReference(immutable, null)
+}
+
+interface Immutable<M> {
+    fun toMutable(): M
+}
+```
+
+Reference to any mutable data structure should be wrapped by this `MutableReference`, which
+encapsulates the logic to mutate/copy a child data structure and update the reference to the new
+child instance. It also remembers the mutated child instance so that it can be reused during further
+mutations. These `MutableReference` objects should be kept private within a data structure, with the
+`get()` method exposed on the immutable interface of the data structure as `getFoo()`, and the
+`mutate()` method exposed on the mutable interface of the data structure as `mutateFoo()`. When the
+parent data structure is mutated/copied, a new `MutableReference` object should be obtained with
+`MutableReference.toImmutable()`, which creates a new reference with the state only being immutable
+and prevents modifications to an object accessed with an immutable interface.
+
+Here is how the usage of `MutableReference` would be like in an actual class:
+
+```kotlin
+private typealias PermissionsReference =
+    MutableReference<IndexedMap<String, Permission>, MutableIndexedMap<String, Permission>>
+
+sealed class SystemState(
+    protected val permissionsReference: PermissionsReference
+) {
+    val permissions: IndexedMap<String, Permission>
+        get() = permissionsReference.get()
+}
+
+class MutableSystemState(
+    permissionsReference: PermissionsReference
+) : SystemState(permissionsRef), Immutable<MutableSystemState> {
+    fun mutatePermissions(): MutableIndexedMap<String, Permission> = permissionsReference.mutate()
+
+    override fun toMutable(): MutableSystemState =
+        MutableSystemState(permissionsReference.toImmutable())
+}
+```
+
+For collection classes like `IndexedMap`, there are also classes like `IndexedReferenceMap` where
+the values are held by `MutableReference`s, and a `mutate(key: K): V` method would help obtain a
+mutable instance of map values.
+
+## Comparison with similar solutions
+
+### Persistent data structure
+
+[Persistent data structure](https://www.wikiwand.com/en/Persistent_data_structure) is a special type
+of data structure implementation that are designed to always preserve the previous version of itself
+when it's modified. Copy-on-write data structure is a common example of it.
+
+Theoretically, persistent data structure can help eliminate the need for locking even upon
+mutations. However, in reality a lot of mutation operations may be updating multiple places in the
+tree of states, and without locking the reader might see an inconsistent state that's right in the
+middle of a mutation operation and make a wrong decision. As a result, we will still need locking
+upon mutations.
+
+Persistent data structure is also much more complex than a plain mutable data structure, both in
+terms of complexity and in terms of performance, and vastly different from the Android-specific
+collection classes that are recommended. Whereas this implementation is just a lightweight wrapper
+around the Android-specific collection classes, which allows reusing them and following the
+guidance for platform code.
+
+### `Snappable` and `Watchable` in `PackageManagerService`
+
+`Snappable` and `Watchable` is an alternative solution for lock contention and immutability.
+Basically, all the mutable state classes will need to implement a way to snapshot themselves, and a
+cache is used for each level of snapshot to reuse copies; the classes will also need to correctly
+implement change notification, so that listeners can be registered to both invalidate snapshot cache
+upon change and detect illegal mutations at run time.
+
+Here are the pros and cons of this implementation, when compared with the snapshot solution:
+
+|                        | Snapshot                                                                                                                                                                      | Immutable                                                                                                                                       |
+|------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------|
+| Locking for reads      | Locked reads when no cached snapshot, lockless when cached                                                                                                                    | Always lockless reads                                                                                                                           |
+| Memory footprint       | Doubled memory usage for mutable data because a copy is kept in snapshot cache if ever read                                                                                   | Potentially more than necessary transient memory usage due to immutability instead of on-demand snapshot (may be mitigated for in-process code) |
+| Immutability for reads | Enforced during run time by `seal()` and `Watchable`                                                                                                                          | Enforced during compile time by type system                                                                                                     |
+| Integration complexity | A `SnapshotCache` field for every existing field, and a correctly implemented `snapshot()` method, keeps Java collection interfaces                                           | Two classes with straightforward accessors for `MutableReference` fields, less room for incorrect code, ditches Java collection interfaces      |
+| ART performance        | Non-final methods (may be made final), potential interface calls for Java collection interfaces, `Snappable` and `Watchable` interface and `instanceof` check for `Snappable` | Final methods, can't have interface call for Java/Kotlin collection interfaces, `Immutable` interface but no `instanceof` check                 |
+
+Unlike package state, permission state is far more frequently queried than mutated - mutations
+mostly happen upon first boot, or when user changes their permission decision which is rare in terms
+of the entire uptime of the system. So reads being always lockless is generally a more suitable
+design in terms of performance, and it also allows flexibility in code that have to obtain external
+state. This fact has a similar impact on the memory footprint, since most of the time the state will
+be unchanged and only read, and we should avoid having to keep another copy of it. Compile time
+enforcement of immutability for reads is safer than run time enforcement, and less room for
+incorrect integration is also an upside when both require some form of code and permission code is
+new. So all in all, the immutable data structure proposed in this document is more suitable for the
+new permission implementation.
diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedList.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedList.kt
new file mode 100644
index 0000000..6108ad2
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedList.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.server.permission.access.immutable
+
+sealed class IndexedList<T>(
+    internal val list: ArrayList<T>
+) : Immutable<MutableIndexedList<T>> {
+    val size: Int
+        get() = list.size
+
+    fun isEmpty(): Boolean = list.isEmpty()
+
+    operator fun contains(element: T): Boolean = list.contains(element)
+
+    @Suppress("ReplaceGetOrSet")
+    operator fun get(index: Int): T = list.get(index)
+
+    override fun toMutable(): MutableIndexedList<T> = MutableIndexedList(this)
+
+    override fun toString(): String = list.toString()
+}
+
+class MutableIndexedList<T>(
+    list: ArrayList<T> = ArrayList()
+) : IndexedList<T>(list) {
+    constructor(indexedList: IndexedList<T>) : this(ArrayList(indexedList.list))
+
+    @Suppress("ReplaceGetOrSet")
+    operator fun set(index: Int, element: T): T = list.set(index, element)
+
+    fun add(element: T) {
+        list.add(element)
+    }
+
+    fun add(index: Int, element: T) {
+        list.add(index, element)
+    }
+
+    fun remove(element: T) {
+        list.remove(element)
+    }
+
+    fun clear() {
+        list.clear()
+    }
+
+    fun removeAt(index: Int): T = list.removeAt(index)
+}
diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedListExtensions.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedListExtensions.kt
new file mode 100644
index 0000000..dc9bae3
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedListExtensions.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.server.permission.access.immutable
+
+inline fun <T> IndexedList<T>.allIndexed(predicate: (Int, T) -> Boolean): Boolean {
+    forEachIndexed { index, element ->
+        if (!predicate(index, element)) {
+            return false
+        }
+    }
+    return true
+}
+
+inline fun <T> IndexedList<T>.anyIndexed(predicate: (Int, T) -> Boolean): Boolean {
+    forEachIndexed { index, element ->
+        if (predicate(index, element)) {
+            return true
+        }
+    }
+    return false
+}
+
+inline fun <T> IndexedList<T>.forEachIndexed(action: (Int, T) -> Unit) {
+    for (index in 0 until size) {
+        action(index, this[index])
+    }
+}
+
+inline fun <T> IndexedList<T>.forEachReversedIndexed(action: (Int, T) -> Unit) {
+    for (index in lastIndex downTo 0) {
+        action(index, this[index])
+    }
+}
+
+inline val <T> IndexedList<T>.lastIndex: Int
+    get() = size - 1
+
+operator fun <T> IndexedList<T>.minus(element: T): MutableIndexedList<T> =
+    toMutable().apply { this -= element }
+
+inline fun <T> IndexedList<T>.noneIndexed(predicate: (Int, T) -> Boolean): Boolean {
+    forEachIndexed { index, element ->
+        if (predicate(index, element)) {
+            return false
+        }
+    }
+    return true
+}
+
+operator fun <T> IndexedList<T>.plus(element: T): MutableIndexedList<T> =
+    toMutable().apply { this += element }
+
+// Using Int instead of <R> to avoid autoboxing, since we only have the use case for Int.
+inline fun <T> IndexedList<T>.reduceIndexed(
+    initialValue: Int,
+    accumulator: (Int, Int, T) -> Int
+): Int {
+    var value = initialValue
+    forEachIndexed { index, element ->
+        value = accumulator(value, index, element)
+    }
+    return value
+}
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun <T> MutableIndexedList<T>.minusAssign(element: T) {
+    remove(element)
+}
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun <T> MutableIndexedList<T>.plusAssign(element: T) {
+    add(element)
+}
diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedListSet.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedListSet.kt
new file mode 100644
index 0000000..1202c81
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedListSet.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.server.permission.access.immutable
+
+sealed class IndexedListSet<T>(
+    internal val list: ArrayList<T>
+) : Immutable<MutableIndexedListSet<T>> {
+    val size: Int
+        get() = list.size
+
+    fun isEmpty(): Boolean = list.isEmpty()
+
+    operator fun contains(element: T): Boolean = list.contains(element)
+
+    fun indexOf(element: T): Int = list.indexOf(element)
+
+    @Suppress("ReplaceGetOrSet")
+    fun elementAt(index: Int): T = list.get(index)
+
+    override fun toMutable(): MutableIndexedListSet<T> = MutableIndexedListSet(this)
+
+    override fun toString(): String = list.toString()
+}
+
+class MutableIndexedListSet<T>(
+    list: ArrayList<T> = ArrayList()
+) : IndexedListSet<T>(list) {
+    constructor(indexedListSet: IndexedListSet<T>) : this(ArrayList(indexedListSet.list))
+
+    fun add(element: T): Boolean =
+        if (list.contains(element)) {
+            false
+        } else {
+            list.add(element)
+            true
+        }
+
+    fun remove(element: T): Boolean = list.remove(element)
+
+    fun clear() {
+        list.clear()
+    }
+
+    fun removeAt(index: Int): T = list.removeAt(index)
+}
diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedListSetExtensions.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedListSetExtensions.kt
new file mode 100644
index 0000000..13fc141
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedListSetExtensions.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.server.permission.access.immutable
+
+inline fun <T> IndexedListSet<T>.allIndexed(predicate: (Int, T) -> Boolean): Boolean {
+    forEachIndexed { index, element ->
+        if (!predicate(index, element)) {
+            return false
+        }
+    }
+    return true
+}
+
+inline fun <T> IndexedListSet<T>.anyIndexed(predicate: (Int, T) -> Boolean): Boolean {
+    forEachIndexed { index, element ->
+        if (predicate(index, element)) {
+            return true
+        }
+    }
+    return false
+}
+
+inline fun <T> IndexedListSet<T>.forEachIndexed(action: (Int, T) -> Unit) {
+    for (index in 0 until size) {
+        action(index, elementAt(index))
+    }
+}
+
+inline fun <T> IndexedListSet<T>.forEachReversedIndexed(action: (Int, T) -> Unit) {
+    for (index in lastIndex downTo 0) {
+        action(index, elementAt(index))
+    }
+}
+
+inline val <T> IndexedListSet<T>.lastIndex: Int
+    get() = size - 1
+
+operator fun <T> IndexedListSet<T>.minus(element: T): MutableIndexedListSet<T> =
+    toMutable().apply { this -= element }
+
+inline fun <T> IndexedListSet<T>.noneIndexed(predicate: (Int, T) -> Boolean): Boolean {
+    forEachIndexed { index, element ->
+        if (predicate(index, element)) {
+            return false
+        }
+    }
+    return true
+}
+
+operator fun <T> IndexedListSet<T>.plus(element: T): MutableIndexedListSet<T> =
+    toMutable().apply { this += element }
+
+// Using Int instead of <R> to avoid autoboxing, since we only have the use case for Int.
+inline fun <T> IndexedListSet<T>.reduceIndexed(
+    initialValue: Int,
+    accumulator: (Int, Int, T) -> Int
+): Int {
+    var value = initialValue
+    forEachIndexed { index, element ->
+        value = accumulator(value, index, element)
+    }
+    return value
+}
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun <T> MutableIndexedListSet<T>.minusAssign(element: T) {
+    remove(element)
+}
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun <T> MutableIndexedListSet<T>.plusAssign(element: T) {
+    add(element)
+}
diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedMap.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedMap.kt
new file mode 100644
index 0000000..5c75de8
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedMap.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.server.permission.access.immutable
+
+import android.util.ArrayMap
+
+sealed class IndexedMap<K, V>(
+    internal val map: ArrayMap<K, V>
+) : Immutable<MutableIndexedMap<K, V>> {
+    val size: Int
+        get() = map.size
+
+    fun isEmpty(): Boolean = map.isEmpty()
+
+    operator fun contains(key: K): Boolean = map.containsKey(key)
+
+    @Suppress("ReplaceGetOrSet")
+    operator fun get(key: K): V? = map.get(key)
+
+    fun indexOfKey(key: K): Int = map.indexOfKey(key)
+
+    fun keyAt(index: Int): K = map.keyAt(index)
+
+    fun valueAt(index: Int): V = map.valueAt(index)
+
+    override fun toMutable(): MutableIndexedMap<K, V> = MutableIndexedMap(this)
+
+    override fun toString(): String = map.toString()
+}
+
+class MutableIndexedMap<K, V>(
+    map: ArrayMap<K, V> = ArrayMap()
+) : IndexedMap<K, V>(map) {
+    constructor(indexedMap: IndexedMap<K, V>) : this(ArrayMap(indexedMap.map))
+
+    fun put(key: K, value: V): V? = map.put(key, value)
+
+    fun remove(key: K): V? = map.remove(key)
+
+    fun clear() {
+        map.clear()
+    }
+
+    fun putAt(index: Int, value: V): V = map.setValueAt(index, value)
+
+    fun removeAt(index: Int): V = map.removeAt(index)
+}
diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedMapExtensions.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedMapExtensions.kt
new file mode 100644
index 0000000..69f1779c
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedMapExtensions.kt
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.permission.access.immutable
+
+inline fun <K, V> IndexedMap<K, V>.allIndexed(predicate: (Int, K, V) -> Boolean): Boolean {
+    forEachIndexed { index, key, value ->
+        if (!predicate(index, key, value)) {
+            return false
+        }
+    }
+    return true
+}
+
+inline fun <K, V> IndexedMap<K, V>.anyIndexed(predicate: (Int, K, V) -> Boolean): Boolean {
+    forEachIndexed { index, key, value ->
+        if (predicate(index, key, value)) {
+            return true
+        }
+    }
+    return false
+}
+
+inline fun <K, V, R> IndexedMap<K, V>.firstNotNullOfOrNullIndexed(transform: (Int, K, V) -> R): R? {
+    forEachIndexed { index, key, value ->
+        transform(index, key, value)?.let { return it }
+    }
+    return null
+}
+
+inline fun <K, V> IndexedMap<K, V>.forEachIndexed(action: (Int, K, V) -> Unit) {
+    for (index in 0 until size) {
+        action(index, keyAt(index), valueAt(index))
+    }
+}
+
+inline fun <K, V> IndexedMap<K, V>.forEachReversedIndexed(action: (Int, K, V) -> Unit) {
+    for (index in lastIndex downTo 0) {
+        action(index, keyAt(index), valueAt(index))
+    }
+}
+
+fun <K, V> IndexedMap<K, V>?.getWithDefault(key: K, defaultValue: V): V {
+    this ?: return defaultValue
+    val index = indexOfKey(key)
+    return if (index >= 0) valueAt(index) else defaultValue
+}
+
+inline val <K, V> IndexedMap<K, V>.lastIndex: Int
+    get() = size - 1
+
+inline fun <K, V> IndexedMap<K, V>.noneIndexed(predicate: (Int, K, V) -> Boolean): Boolean {
+    forEachIndexed { index, key, value ->
+        if (predicate(index, key, value)) {
+            return false
+        }
+    }
+    return true
+}
+
+inline fun <K, V, R, C : MutableCollection<R>> IndexedMap<K, V>.mapIndexedTo(
+    destination: C,
+    transform: (Int, K, V) -> R,
+): C {
+    forEachIndexed { index, key, value ->
+        transform(index, key, value).let { destination += it }
+    }
+    return destination
+}
+
+inline fun <K, V, R, C : MutableCollection<R>> IndexedMap<K, V>.mapNotNullIndexedTo(
+    destination: C,
+    transform: (Int, K, V) -> R?
+): C {
+    forEachIndexed { index, key, value ->
+        transform(index, key, value)?.let { destination += it }
+    }
+    return destination
+}
+
+inline fun <K, V> MutableIndexedMap<K, V>.getOrPut(key: K, defaultValue: () -> V): V {
+    get(key)?.let { return it }
+    return defaultValue().also { put(key, it) }
+}
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun <K, V> MutableIndexedMap<K, V>.minusAssign(key: K) {
+    remove(key)
+}
+
+fun <K, V> MutableIndexedMap<K, V>.putWithDefault(key: K, value: V, defaultValue: V): V {
+    val index = indexOfKey(key)
+    if (index >= 0) {
+        val oldValue = valueAt(index)
+        if (value != oldValue) {
+            if (value == defaultValue) {
+                removeAt(index)
+            } else {
+                putAt(index, value)
+            }
+        }
+        return oldValue
+    } else {
+        if (value != defaultValue) {
+            put(key, value)
+        }
+        return defaultValue
+    }
+}
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun <K, V> MutableIndexedMap<K, V>.set(key: K, value: V) {
+    put(key, value)
+}
diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedReferenceMap.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedReferenceMap.kt
new file mode 100644
index 0000000..8c963aa
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedReferenceMap.kt
@@ -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.server.permission.access.immutable
+
+import android.util.ArrayMap
+
+sealed class IndexedReferenceMap<K, I : Immutable<M>, M : I>(
+    internal val map: ArrayMap<K, MutableReference<I, M>>
+) : Immutable<MutableIndexedReferenceMap<K, I, M>> {
+    val size: Int
+        get() = map.size
+
+    fun isEmpty(): Boolean = map.isEmpty()
+
+    operator fun contains(key: K): Boolean = map.containsKey(key)
+
+    @Suppress("ReplaceGetOrSet")
+    operator fun get(key: K): I? = map.get(key)?.get()
+
+    fun indexOfKey(key: K): Int = map.indexOfKey(key)
+
+    fun keyAt(index: Int): K = map.keyAt(index)
+
+    fun valueAt(index: Int): I = map.valueAt(index).get()
+
+    override fun toMutable(): MutableIndexedReferenceMap<K, I, M> = MutableIndexedReferenceMap(this)
+
+    override fun toString(): String = map.toString()
+}
+
+class MutableIndexedReferenceMap<K, I : Immutable<M>, M : I>(
+    map: ArrayMap<K, MutableReference<I, M>> = ArrayMap()
+) : IndexedReferenceMap<K, I, M>(map) {
+    constructor(indexedReferenceMap: IndexedReferenceMap<K, I, M>) : this(
+        ArrayMap(indexedReferenceMap.map).apply {
+            for (i in 0 until size) {
+                setValueAt(i, valueAt(i).toImmutable())
+            }
+        }
+    )
+
+    @Suppress("ReplaceGetOrSet")
+    fun mutate(key: K): M? = map.get(key)?.mutate()
+
+    fun put(key: K, value: M): I? = map.put(key, MutableReference(value))?.get()
+
+    fun remove(key: K): I? = map.remove(key)?.get()
+
+    fun clear() {
+        map.clear()
+    }
+
+    fun mutateAt(index: Int): M = map.valueAt(index).mutate()
+
+    fun putAt(index: Int, value: M): I = map.setValueAt(index, MutableReference(value)).get()
+
+    fun removeAt(index: Int): I = map.removeAt(index).get()
+}
diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedReferenceMapExtensions.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedReferenceMapExtensions.kt
new file mode 100644
index 0000000..22b4d52
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedReferenceMapExtensions.kt
@@ -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.permission.access.immutable
+
+inline fun <K, I : Immutable<M>, M : I> IndexedReferenceMap<K, I, M>.allIndexed(
+    predicate: (Int, K, I) -> Boolean
+): Boolean {
+    forEachIndexed { index, key, value ->
+        if (!predicate(index, key, value)) {
+            return false
+        }
+    }
+    return true
+}
+
+inline fun <K, I : Immutable<M>, M : I> IndexedReferenceMap<K, I, M>.anyIndexed(
+    predicate: (Int, K, I) -> Boolean
+): Boolean {
+    forEachIndexed { index, key, value ->
+        if (predicate(index, key, value)) {
+            return true
+        }
+    }
+    return false
+}
+
+inline fun <K, I : Immutable<M>, M : I> IndexedReferenceMap<K, I, M>.forEachIndexed(
+    action: (Int, K, I) -> Unit
+) {
+    for (index in 0 until size) {
+        action(index, keyAt(index), valueAt(index))
+    }
+}
+
+inline fun <K, I : Immutable<M>, M : I> IndexedReferenceMap<K, I, M>.forEachReversedIndexed(
+    action: (Int, K, I) -> Unit
+) {
+    for (index in lastIndex downTo 0) {
+        action(index, keyAt(index), valueAt(index))
+    }
+}
+
+inline val <K, I : Immutable<M>, M : I> IndexedReferenceMap<K, I, M>.lastIndex: Int
+    get() = size - 1
+
+inline fun <K, I : Immutable<M>, M : I> IndexedReferenceMap<K, I, M>.noneIndexed(
+    predicate: (Int, K, I) -> Boolean
+): Boolean {
+    forEachIndexed { index, key, value ->
+        if (predicate(index, key, value)) {
+            return false
+        }
+    }
+    return true
+}
+
+inline fun <K, I : Immutable<M>, M : I> MutableIndexedReferenceMap<K, I, M>.mutateOrPut(
+    key: K,
+    defaultValue: () -> M
+): M {
+    mutate(key)?.let { return it }
+    return defaultValue().also { put(key, it) }
+}
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun <K, I : Immutable<M>, M : I> MutableIndexedReferenceMap<K, I, M>.minusAssign(
+    key: K
+) {
+    remove(key)
+}
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun <K, I : Immutable<M>, M : I> MutableIndexedReferenceMap<K, I, M>.set(
+    key: K,
+    value: M
+) {
+    put(key, value)
+}
diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedSet.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedSet.kt
new file mode 100644
index 0000000..9868616
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedSet.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.permission.access.immutable
+
+import android.util.ArraySet
+
+sealed class IndexedSet<T>(
+    internal val set: ArraySet<T>
+) : Immutable<MutableIndexedSet<T>> {
+    val size: Int
+        get() = set.size
+
+    fun isEmpty(): Boolean = set.isEmpty()
+
+    operator fun contains(element: T): Boolean = set.contains(element)
+
+    fun indexOf(element: T): Int = set.indexOf(element)
+
+    fun elementAt(index: Int): T = set.elementAt(index)
+
+    override fun toMutable(): MutableIndexedSet<T> = MutableIndexedSet(this)
+
+    override fun toString(): String = set.toString()
+}
+
+class MutableIndexedSet<T>(
+    set: ArraySet<T> = ArraySet()
+) : IndexedSet<T>(set) {
+    constructor(indexedSet: IndexedSet<T>) : this(ArraySet(indexedSet.set))
+
+    fun add(element: T): Boolean = set.add(element)
+
+    fun remove(element: T): Boolean = set.remove(element)
+
+    fun clear() {
+        set.clear()
+    }
+
+    fun removeAt(index: Int): T = set.removeAt(index)
+}
diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedSetExtensions.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedSetExtensions.kt
new file mode 100644
index 0000000..5959ec6
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedSetExtensions.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.server.permission.access.immutable
+
+import android.util.ArraySet
+import com.android.server.permission.access.collection.forEachIndexed
+
+fun <T> indexedSetOf(vararg elements: T): IndexedSet<T> =
+    MutableIndexedSet(ArraySet(elements.asList()))
+
+inline fun <T> IndexedSet<T>.allIndexed(predicate: (Int, T) -> Boolean): Boolean {
+    forEachIndexed { index, element ->
+        if (!predicate(index, element)) {
+            return false
+        }
+    }
+    return true
+}
+
+inline fun <T> IndexedSet<T>.anyIndexed(predicate: (Int, T) -> Boolean): Boolean {
+    forEachIndexed { index, element ->
+        if (predicate(index, element)) {
+            return true
+        }
+    }
+    return false
+}
+
+inline fun <T> IndexedSet<T>.forEachIndexed(action: (Int, T) -> Unit) {
+    for (index in 0 until size) {
+        action(index, elementAt(index))
+    }
+}
+
+inline fun <T> IndexedSet<T>.forEachReversedIndexed(action: (Int, T) -> Unit) {
+    for (index in lastIndex downTo 0) {
+        action(index, elementAt(index))
+    }
+}
+
+inline val <T> IndexedSet<T>.lastIndex: Int
+    get() = size - 1
+
+operator fun <T> IndexedSet<T>.minus(element: T): MutableIndexedSet<T> =
+    toMutable().apply { this -= element }
+
+inline fun <T> IndexedSet<T>.noneIndexed(predicate: (Int, T) -> Boolean): Boolean {
+    forEachIndexed { index, element ->
+        if (predicate(index, element)) {
+            return false
+        }
+    }
+    return true
+}
+
+operator fun <T> IndexedSet<T>.plus(element: T): MutableIndexedSet<T> =
+    toMutable().apply { this += element }
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun <T> MutableIndexedSet<T>.minusAssign(element: T) {
+    remove(element)
+}
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun <T> MutableIndexedSet<T>.plusAssign(element: T) {
+    add(element)
+}
+
+operator fun <T> MutableIndexedSet<T>.plusAssign(collection: Collection<T>) {
+    collection.forEach { this += it }
+}
diff --git a/services/permission/java/com/android/server/permission/access/immutable/IntMap.kt b/services/permission/java/com/android/server/permission/access/immutable/IntMap.kt
new file mode 100644
index 0000000..b7d8b4c
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/immutable/IntMap.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.server.permission.access.immutable
+
+import android.util.SparseArray
+
+sealed class IntMap<T>(
+    internal val array: SparseArray<T>
+) : Immutable<MutableIntMap<T>> {
+    val size: Int
+        get() = array.size()
+
+    fun isEmpty(): Boolean = array.size() == 0
+
+    operator fun contains(key: Int): Boolean = array.contains(key)
+
+    operator fun get(key: Int): T? = array.get(key)
+
+    fun indexOfKey(key: Int): Int = array.indexOfKey(key)
+
+    fun keyAt(index: Int): Int = array.keyAt(index)
+
+    fun valueAt(index: Int): T = array.valueAt(index)
+
+    override fun toMutable(): MutableIntMap<T> = MutableIntMap(this)
+
+    override fun toString(): String = array.toString()
+}
+
+class MutableIntMap<T>(
+    array: SparseArray<T> = SparseArray()
+) : IntMap<T>(array) {
+    constructor(intMap: IntMap<T>) : this(intMap.array.clone())
+
+    fun put(key: Int, value: T): T? = array.putReturnOld(key, value)
+
+    fun remove(key: Int): T? = array.removeReturnOld(key).also { array.gc() }
+
+    fun clear() {
+        array.clear()
+    }
+
+    fun putAt(index: Int, value: T): T = array.setValueAtReturnOld(index, value)
+
+    fun removeAt(index: Int): T = array.removeAtReturnOld(index).also { array.gc() }
+}
+
+internal fun <T> SparseArray<T>.putReturnOld(key: Int, value: T): T? {
+    val index = indexOfKey(key)
+    return if (index >= 0) {
+        val oldValue = valueAt(index)
+        setValueAt(index, value)
+        oldValue
+    } else {
+        put(key, value)
+        null
+    }
+}
+
+// SparseArray.removeReturnOld() is @hide, so a backup once we move to APIs.
+@Suppress("EXTENSION_SHADOWED_BY_MEMBER")
+internal fun <T> SparseArray<T>.removeReturnOld(key: Int): T? {
+    val index = indexOfKey(key)
+    return if (index >= 0) {
+        val oldValue = valueAt(index)
+        removeAt(index)
+        oldValue
+    } else {
+        null
+    }
+}
+
+internal fun <T> SparseArray<T>.setValueAtReturnOld(index: Int, value: T): T {
+    val oldValue = valueAt(index)
+    setValueAt(index, value)
+    return oldValue
+}
+
+internal fun <T> SparseArray<T>.removeAtReturnOld(index: Int): T {
+    val oldValue = valueAt(index)
+    removeAt(index)
+    return oldValue
+}
+
+internal fun <T> SparseArray<T>.gc() {
+    size()
+}
diff --git a/services/permission/java/com/android/server/permission/access/immutable/IntMapExtensions.kt b/services/permission/java/com/android/server/permission/access/immutable/IntMapExtensions.kt
new file mode 100644
index 0000000..ed7f0af
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/immutable/IntMapExtensions.kt
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.permission.access.immutable
+
+inline fun <T> IntMap<T>.allIndexed(predicate: (Int, Int, T) -> Boolean): Boolean {
+    forEachIndexed { index, key, value ->
+        if (!predicate(index, key, value)) {
+            return false
+        }
+    }
+    return true
+}
+
+inline fun <T> IntMap<T>.anyIndexed(predicate: (Int, Int, T) -> Boolean): Boolean {
+    forEachIndexed { index, key, value ->
+        if (predicate(index, key, value)) {
+            return true
+        }
+    }
+    return false
+}
+
+inline fun <T, R> IntMap<T>.firstNotNullOfOrNullIndexed(transform: (Int, Int, T) -> R): R? {
+    forEachIndexed { index, key, value ->
+        transform(index, key, value)?.let { return it }
+    }
+    return null
+}
+
+inline fun <T> IntMap<T>.forEachIndexed(action: (Int, Int, T) -> Unit) {
+    for (index in 0 until size) {
+        action(index, keyAt(index), valueAt(index))
+    }
+}
+
+inline fun <T> IntMap<T>.forEachReversedIndexed(action: (Int, Int, T) -> Unit) {
+    for (index in lastIndex downTo 0) {
+        action(index, keyAt(index), valueAt(index))
+    }
+}
+
+fun <T> IntMap<T>?.getWithDefault(key: Int, defaultValue: T): T {
+    this ?: return defaultValue
+    val index = indexOfKey(key)
+    return if (index >= 0) valueAt(index) else defaultValue
+}
+
+inline val <T> IntMap<T>.lastIndex: Int
+    get() = size - 1
+
+inline fun <T> IntMap<T>.noneIndexed(predicate: (Int, Int, T) -> Boolean): Boolean {
+    forEachIndexed { index, key, value ->
+        if (predicate(index, key, value)) {
+            return false
+        }
+    }
+    return true
+}
+
+inline fun <T> MutableIntMap<T>.getOrPut(key: Int, defaultValue: () -> T): T {
+    get(key)?.let { return it }
+    return defaultValue().also { put(key, it) }
+}
+
+operator fun <T> MutableIntMap<T>.minusAssign(key: Int) {
+    array.remove(key)
+}
+
+fun <T> MutableIntMap<T>.putWithDefault(key: Int, value: T, defaultValue: T): T {
+    val index = indexOfKey(key)
+    if (index >= 0) {
+        val oldValue = valueAt(index)
+        if (value != oldValue) {
+            if (value == defaultValue) {
+                removeAt(index)
+            } else {
+                putAt(index, value)
+            }
+        }
+        return oldValue
+    } else {
+        if (value != defaultValue) {
+            put(key, value)
+        }
+        return defaultValue
+    }
+}
+
+operator fun <T> MutableIntMap<T>.set(key: Int, value: T) {
+    array.put(key, value)
+}
diff --git a/services/permission/java/com/android/server/permission/access/immutable/IntReferenceMap.kt b/services/permission/java/com/android/server/permission/access/immutable/IntReferenceMap.kt
new file mode 100644
index 0000000..22fa8f2
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/immutable/IntReferenceMap.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.permission.access.immutable
+
+import android.util.SparseArray
+
+sealed class IntReferenceMap<I : Immutable<M>, M : I>(
+    internal val array: SparseArray<MutableReference<I, M>>
+) : Immutable<MutableIntReferenceMap<I, M>> {
+    val size: Int
+        get() = array.size()
+
+    fun isEmpty(): Boolean = array.size() == 0
+
+    operator fun contains(key: Int): Boolean = array.contains(key)
+
+    @Suppress("ReplaceGetOrSet")
+    operator fun get(key: Int): I? = array.get(key)?.get()
+
+    fun indexOfKey(key: Int): Int = array.indexOfKey(key)
+
+    fun keyAt(index: Int): Int = array.keyAt(index)
+
+    fun valueAt(index: Int): I = array.valueAt(index).get()
+
+    override fun toMutable(): MutableIntReferenceMap<I, M> = MutableIntReferenceMap(this)
+
+    override fun toString(): String = array.toString()
+}
+
+class MutableIntReferenceMap<I : Immutable<M>, M : I>(
+    array: SparseArray<MutableReference<I, M>> = SparseArray()
+) : IntReferenceMap<I, M>(array) {
+    constructor(intReferenceMap: IntReferenceMap<I, M>) : this(
+        intReferenceMap.array.clone().apply {
+            for (i in 0 until size()) {
+                setValueAt(i, valueAt(i).toImmutable())
+            }
+        }
+    )
+
+    @Suppress("ReplaceGetOrSet")
+    fun mutate(key: Int): M? = array.get(key)?.mutate()
+
+    fun put(key: Int, value: M): I? = array.putReturnOld(key, MutableReference(value))?.get()
+
+    fun remove(key: Int): I? = array.removeReturnOld(key).also { array.gc() }?.get()
+
+    fun clear() {
+        array.clear()
+    }
+
+    fun mutateAt(index: Int): M = array.valueAt(index).mutate()
+
+    fun putAt(index: Int, value: M): I =
+        array.setValueAtReturnOld(index, MutableReference(value)).get()
+
+    fun removeAt(index: Int): I = array.removeAtReturnOld(index).also { array.gc() }.get()
+}
diff --git a/services/permission/java/com/android/server/permission/access/immutable/IntReferenceMapExtensions.kt b/services/permission/java/com/android/server/permission/access/immutable/IntReferenceMapExtensions.kt
new file mode 100644
index 0000000..b4de5d1
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/immutable/IntReferenceMapExtensions.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.server.permission.access.immutable
+
+inline fun <I : Immutable<M>, M : I> IntReferenceMap<I, M>.allIndexed(
+    predicate: (Int, Int, I) -> Boolean
+): Boolean {
+    forEachIndexed { index, key, value ->
+        if (!predicate(index, key, value)) {
+            return false
+        }
+    }
+    return true
+}
+
+inline fun <I : Immutable<M>, M : I> IntReferenceMap<I, M>.anyIndexed(
+    predicate: (Int, Int, I) -> Boolean
+): Boolean {
+    forEachIndexed { index, key, value ->
+        if (predicate(index, key, value)) {
+            return true
+        }
+    }
+    return false
+}
+
+inline fun <I : Immutable<M>, M : I> IntReferenceMap<I, M>.forEachIndexed(
+    action: (Int, Int, I) -> Unit
+) {
+    for (index in 0 until size) {
+        action(index, keyAt(index), valueAt(index))
+    }
+}
+
+inline fun <I : Immutable<M>, M : I> IntReferenceMap<I, M>.forEachReversedIndexed(
+    action: (Int, Int, I) -> Unit
+) {
+    for (index in lastIndex downTo 0) {
+        action(index, keyAt(index), valueAt(index))
+    }
+}
+
+inline val <I : Immutable<M>, M : I> IntReferenceMap<I, M>.lastIndex: Int
+    get() = size - 1
+
+inline fun <I : Immutable<M>, M : I> IntReferenceMap<I, M>.noneIndexed(
+    predicate: (Int, Int, I) -> Boolean
+): Boolean {
+    forEachIndexed { index, key, value ->
+        if (predicate(index, key, value)) {
+            return false
+        }
+    }
+    return true
+}
+
+inline fun <I : Immutable<M>, M : I> MutableIntReferenceMap<I, M>.mutateOrPut(
+    key: Int,
+    defaultValue: () -> M
+): M {
+    mutate(key)?.let { return it }
+    return defaultValue().also { put(key, it) }
+}
+
+operator fun <I : Immutable<M>, M : I> MutableIntReferenceMap<I, M>.minusAssign(key: Int) {
+    array.remove(key)
+}
+
+operator fun <I : Immutable<M>, M : I> MutableIntReferenceMap<I, M>.set(key: Int, value: M) {
+    array.put(key, MutableReference(value))
+}
diff --git a/services/permission/java/com/android/server/permission/access/immutable/IntSet.kt b/services/permission/java/com/android/server/permission/access/immutable/IntSet.kt
new file mode 100644
index 0000000..9da3671
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/immutable/IntSet.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.permission.access.immutable
+
+import android.util.SparseBooleanArray
+
+sealed class IntSet(
+    internal val array: SparseBooleanArray
+) : Immutable<MutableIntSet> {
+    val size: Int
+        get() = array.size()
+
+    fun isEmpty(): Boolean = array.size() == 0
+
+    operator fun contains(element: Int): Boolean = array.contains(element)
+
+    fun indexOf(element: Int): Int = array.indexOfKey(element)
+
+    fun elementAt(index: Int): Int = array.keyAt(index)
+
+    override fun toMutable(): MutableIntSet = MutableIntSet(this)
+
+    override fun toString(): String = array.toString()
+}
+
+class MutableIntSet(
+    array: SparseBooleanArray = SparseBooleanArray()
+) : IntSet(array) {
+    constructor(intSet: IntSet) : this(intSet.array.clone())
+
+    fun add(element: Int): Boolean =
+        if (array.contains(element)) {
+            false
+        } else {
+            array.put(element, true)
+            true
+        }
+
+    fun remove(element: Int): Boolean {
+        val index = array.indexOfKey(element)
+        return if (index >= 0) {
+            array.removeAt(index)
+            true
+        } else {
+            false
+        }
+    }
+
+    fun clear() {
+        array.clear()
+    }
+
+    fun removeAt(index: Int) {
+        array.removeAt(index)
+    }
+}
+
+// Unlike SparseArray, SparseBooleanArray is missing this method.
+private fun SparseBooleanArray.contains(key: Int): Boolean = indexOfKey(key) >= 0
diff --git a/services/permission/java/com/android/server/permission/access/immutable/IntSetExtensions.kt b/services/permission/java/com/android/server/permission/access/immutable/IntSetExtensions.kt
new file mode 100644
index 0000000..163ebbf
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/immutable/IntSetExtensions.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.permission.access.immutable
+
+inline fun IntSet.allIndexed(predicate: (Int, Int) -> Boolean): Boolean {
+    forEachIndexed { index, element ->
+        if (!predicate(index, element)) {
+            return false
+        }
+    }
+    return true
+}
+
+inline fun IntSet.anyIndexed(predicate: (Int, Int) -> Boolean): Boolean {
+    forEachIndexed { index, element ->
+        if (predicate(index, element)) {
+            return true
+        }
+    }
+    return false
+}
+
+inline fun IntSet.forEachIndexed(action: (Int, Int) -> Unit) {
+    for (index in 0 until size) {
+        action(index, elementAt(index))
+    }
+}
+
+inline fun IntSet.forEachReversedIndexed(action: (Int, Int) -> Unit) {
+    for (index in lastIndex downTo 0) {
+        action(index, elementAt(index))
+    }
+}
+
+inline val IntSet.lastIndex: Int
+    get() = size - 1
+
+operator fun IntSet.minus(element: Int): MutableIntSet = toMutable().apply { this -= element }
+
+operator fun IntSet.minusAssign(element: Int) {
+    array.delete(element)
+}
+
+inline fun IntSet.noneIndexed(predicate: (Int, Int) -> Boolean): Boolean {
+    forEachIndexed { index, element ->
+        if (predicate(index, element)) {
+            return false
+        }
+    }
+    return true
+}
+
+operator fun IntSet.plus(element: Int): MutableIntSet = toMutable().apply { this += element }
+
+fun MutableIntSet(values: IntArray): MutableIntSet = MutableIntSet().apply{ this += values }
+
+operator fun MutableIntSet.plusAssign(element: Int) {
+    array.put(element, true)
+}
+
+operator fun MutableIntSet.plusAssign(set: IntSet) {
+    set.forEachIndexed { _, it -> this += it }
+}
+
+operator fun MutableIntSet.plusAssign(array: IntArray) {
+    array.forEach { this += it }
+}
diff --git a/services/permission/java/com/android/server/permission/access/immutable/MutableReference.kt b/services/permission/java/com/android/server/permission/access/immutable/MutableReference.kt
new file mode 100644
index 0000000..e39a3bb
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/immutable/MutableReference.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.permission.access.immutable
+
+class MutableReference<I : Immutable<M>, M : I> private constructor(
+    private var immutable: I,
+    private var mutable: M?
+) {
+    constructor(mutable: M) : this(mutable, mutable)
+
+    fun get(): I = immutable
+
+    fun mutate(): M {
+        mutable?.let { return it }
+        return immutable.toMutable().also {
+            immutable = it
+            mutable = it
+        }
+    }
+
+    fun toImmutable(): MutableReference<I, M> = MutableReference(immutable, null)
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) {
+            return true
+        }
+        if (javaClass != other?.javaClass) {
+            return false
+        }
+        other as MutableReference<*, *>
+        return immutable == other.immutable
+    }
+
+    override fun hashCode(): Int = immutable.hashCode()
+}
diff --git a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionMigration.kt b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionMigration.kt
new file mode 100644
index 0000000..691ed8f
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionMigration.kt
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.permission.access.permission
+
+import android.util.Slog
+import com.android.server.LocalServices
+import com.android.server.permission.access.MutableAccessState
+import com.android.server.permission.access.immutable.* // ktlint-disable no-wildcard-imports
+import com.android.server.permission.access.util.PackageVersionMigration
+import com.android.server.pm.permission.PermissionMigrationHelper
+
+/**
+ * This class migrate legacy permissions to unified permission subsystem
+ */
+class AppIdPermissionMigration {
+    internal fun migrateSystemState(state: MutableAccessState) {
+        val legacyPermissionsManager =
+            LocalServices.getService(PermissionMigrationHelper::class.java)!!
+        if (!legacyPermissionsManager.hasLegacyPermission()) {
+            return
+        }
+
+        migratePermissions(state.mutateSystemState().mutatePermissions(),
+            legacyPermissionsManager.legacyPermissions)
+        migratePermissions(state.mutateSystemState().mutatePermissionTrees(),
+            legacyPermissionsManager.legacyPermissionTrees, true)
+    }
+
+    private fun migratePermissions(
+        permissions: MutableIndexedMap<String, Permission>,
+        legacyPermissions: Map<String, PermissionMigrationHelper.LegacyPermission>,
+        isPermissionTree: Boolean = false
+    ) {
+        legacyPermissions.forEach { (_, legacyPermission) ->
+            val permission = Permission(
+                legacyPermission.permissionInfo, false, legacyPermission.type, 0
+            )
+            permissions[permission.name] = permission
+            if (DEBUG_MIGRATION) {
+                Slog.v(LOG_TAG, "Migrated permission: ${permission.name}, type: " +
+                    "${permission.type}, appId: ${permission.appId}, protectionLevel: " +
+                    "${permission.protectionLevel}, tree: $isPermissionTree"
+                )
+            }
+        }
+    }
+
+    internal fun migrateUserState(state: MutableAccessState, userId: Int) {
+        val permissionMigrationHelper =
+            LocalServices.getService(PermissionMigrationHelper::class.java)!!
+        if (!permissionMigrationHelper.hasLegacyPermissionState(userId)) {
+            return
+        }
+
+        val legacyAppIdPermissionStates =
+            permissionMigrationHelper.getLegacyPermissionStates(userId)
+        val version = PackageVersionMigration.getVersion(userId)
+
+        val userState = state.mutateUserState(userId)!!
+        val appIdPermissionFlags = userState.mutateAppIdPermissionFlags()
+        legacyAppIdPermissionStates.forEach { (appId, legacyPermissionStates) ->
+            val packageNames = state.externalState.appIdPackageNames[appId]
+            if (packageNames == null) {
+                Slog.w(LOG_TAG, "Dropping unknown app ID $appId when migrating permission state")
+                return@forEach
+            }
+
+            val permissionFlags = MutableIndexedMap<String, Int>()
+            appIdPermissionFlags[appId] = permissionFlags
+            legacyPermissionStates.forEach forEachPermission@ {
+                (permissionName, legacyPermissionState) ->
+                val permission = state.systemState.permissions[permissionName]
+                if (permission == null) {
+                    Slog.w(
+                        LOG_TAG, "Dropping unknown permission $permissionName for app ID $appId" +
+                            " when migrating permission state"
+                    )
+                    return@forEachPermission
+                }
+                permissionFlags[permissionName] = migratePermissionFlags(
+                    permission, legacyPermissionState, appId, userId
+                )
+            }
+
+            val packageVersions = userState.mutatePackageVersions()
+            packageNames.forEachIndexed { _, packageName ->
+                packageVersions[packageName] = version
+            }
+        }
+    }
+
+    private fun migratePermissionFlags(
+        permission: Permission,
+        legacyPermissionState: PermissionMigrationHelper.LegacyPermissionState,
+        appId: Int,
+        userId: Int
+    ): Int {
+        var flags = when {
+            permission.isNormal -> if (legacyPermissionState.isGranted) {
+                PermissionFlags.INSTALL_GRANTED
+            } else {
+                PermissionFlags.INSTALL_REVOKED
+            }
+            permission.isSignature || permission.isInternal ->
+                if (legacyPermissionState.isGranted) {
+                    if (permission.isDevelopment || permission.isRole) {
+                        PermissionFlags.PROTECTION_GRANTED or PermissionFlags.RUNTIME_GRANTED
+                    } else {
+                        PermissionFlags.PROTECTION_GRANTED
+                    }
+                } else {
+                    0
+                }
+            permission.isRuntime ->
+                if (legacyPermissionState.isGranted) PermissionFlags.RUNTIME_GRANTED else 0
+            else -> 0
+        }
+        flags = PermissionFlags.updateFlags(
+            permission, flags, legacyPermissionState.flags, legacyPermissionState.flags
+        )
+        if (DEBUG_MIGRATION) {
+            val oldFlagString = PermissionFlags.apiFlagsToString(legacyPermissionState.flags)
+            val newFlagString = PermissionFlags.toString(flags)
+            val oldGrantState = legacyPermissionState.isGranted
+            val newGrantState = PermissionFlags.isPermissionGranted(flags)
+            val flagsMismatch = legacyPermissionState.flags != PermissionFlags.toApiFlags(flags)
+            Slog.v(
+                LOG_TAG, "Migrated appId: $appId, permission: " +
+                    "${permission.name}, user: $userId, oldGrantState: $oldGrantState" +
+                    ", oldFlags: $oldFlagString, newFlags: $newFlagString, grantMismatch: " +
+                    "${oldGrantState != newGrantState}, flagsMismatch: $flagsMismatch"
+            )
+        }
+        return flags
+    }
+
+    companion object {
+        private val LOG_TAG = AppIdPermissionMigration::class.java.simpleName
+
+        private const val DEBUG_MIGRATION = false
+    }
+}
diff --git a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPersistence.kt b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPersistence.kt
new file mode 100644
index 0000000..2c8175b
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPersistence.kt
@@ -0,0 +1,258 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.permission.access.permission
+
+import android.content.pm.PermissionInfo
+import android.util.Slog
+import com.android.modules.utils.BinaryXmlPullParser
+import com.android.modules.utils.BinaryXmlSerializer
+import com.android.server.permission.access.AccessState
+import com.android.server.permission.access.AppIdPermissionFlags
+import com.android.server.permission.access.MutableAccessState
+import com.android.server.permission.access.MutableAppIdPermissionFlags
+import com.android.server.permission.access.WriteMode
+import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
+import com.android.server.permission.access.immutable.* // ktlint-disable no-wildcard-imports
+import com.android.server.permission.access.util.andInv
+import com.android.server.permission.access.util.attribute
+import com.android.server.permission.access.util.attributeInt
+import com.android.server.permission.access.util.attributeIntHex
+import com.android.server.permission.access.util.attributeIntHexWithDefault
+import com.android.server.permission.access.util.attributeInterned
+import com.android.server.permission.access.util.forEachTag
+import com.android.server.permission.access.util.getAttributeIntHexOrDefault
+import com.android.server.permission.access.util.getAttributeIntHexOrThrow
+import com.android.server.permission.access.util.getAttributeIntOrThrow
+import com.android.server.permission.access.util.getAttributeValue
+import com.android.server.permission.access.util.getAttributeValueOrThrow
+import com.android.server.permission.access.util.hasBits
+import com.android.server.permission.access.util.tag
+import com.android.server.permission.access.util.tagName
+
+class AppIdPermissionPersistence {
+    fun BinaryXmlPullParser.parseSystemState(state: MutableAccessState) {
+        when (tagName) {
+            TAG_PERMISSION_TREES -> parsePermissions(state, true)
+            TAG_PERMISSIONS -> parsePermissions(state, false)
+            else -> {}
+        }
+    }
+
+    private fun BinaryXmlPullParser.parsePermissions(
+        state: MutableAccessState,
+        isPermissionTree: Boolean
+    ) {
+        val systemState = state.mutateSystemState(WriteMode.NONE)
+        val permissions = if (isPermissionTree) {
+            systemState.mutatePermissionTrees()
+        } else {
+            systemState.mutatePermissions()
+        }
+        forEachTag {
+            when (val tagName = tagName) {
+                TAG_PERMISSION -> parsePermission(permissions)
+                else -> Slog.w(LOG_TAG, "Ignoring unknown tag $tagName when parsing permissions")
+            }
+        }
+        permissions.forEachReversedIndexed { permissionIndex, _, permission ->
+            val packageName = permission.packageName
+            val externalState = state.externalState
+            if (packageName !in externalState.packageStates &&
+                packageName !in externalState.disabledSystemPackageStates) {
+                Slog.w(
+                    LOG_TAG, "Dropping permission ${permission.name} from unknown package" +
+                        " $packageName when parsing permissions"
+                )
+                permissions.removeAt(permissionIndex)
+                systemState.requestWriteMode(WriteMode.ASYNCHRONOUS)
+            }
+        }
+    }
+
+    private fun BinaryXmlPullParser.parsePermission(
+        permissions: MutableIndexedMap<String, Permission>
+    ) {
+        val name = getAttributeValueOrThrow(ATTR_NAME).intern()
+        @Suppress("DEPRECATION")
+        val permissionInfo = PermissionInfo().apply {
+            this.name = name
+            packageName = getAttributeValueOrThrow(ATTR_PACKAGE_NAME).intern()
+            protectionLevel = getAttributeIntHexOrThrow(ATTR_PROTECTION_LEVEL)
+        }
+        val type = getAttributeIntOrThrow(ATTR_TYPE)
+        when (type) {
+            Permission.TYPE_MANIFEST -> {}
+            Permission.TYPE_CONFIG -> {
+                Slog.w(LOG_TAG, "Ignoring unexpected config permission $name")
+                return
+            }
+            Permission.TYPE_DYNAMIC -> {
+                permissionInfo.apply {
+                    icon = getAttributeIntHexOrDefault(ATTR_ICON, 0)
+                    nonLocalizedLabel = getAttributeValue(ATTR_LABEL)
+                }
+            }
+            else -> {
+                Slog.w(LOG_TAG, "Ignoring permission $name with unknown type $type")
+                return
+            }
+        }
+        val permission = Permission(permissionInfo, false, type, 0)
+        permissions[name] = permission
+    }
+
+    fun BinaryXmlSerializer.serializeSystemState(state: AccessState) {
+        val systemState = state.systemState
+        serializePermissions(TAG_PERMISSION_TREES, systemState.permissionTrees)
+        serializePermissions(TAG_PERMISSIONS, systemState.permissions)
+    }
+
+    private fun BinaryXmlSerializer.serializePermissions(
+        tagName: String,
+        permissions: IndexedMap<String, Permission>
+    ) {
+        tag(tagName) {
+            permissions.forEachIndexed { _, _, it -> serializePermission(it) }
+        }
+    }
+
+    private fun BinaryXmlSerializer.serializePermission(permission: Permission) {
+        val type = permission.type
+        when (type) {
+            Permission.TYPE_MANIFEST, Permission.TYPE_DYNAMIC -> {}
+            Permission.TYPE_CONFIG -> return
+            else -> {
+                Slog.w(LOG_TAG, "Skipping serializing permission $name with unknown type $type")
+                return
+            }
+        }
+        tag(TAG_PERMISSION) {
+            attributeInterned(ATTR_NAME, permission.name)
+            attributeInterned(ATTR_PACKAGE_NAME, permission.packageName)
+            attributeIntHex(ATTR_PROTECTION_LEVEL, permission.protectionLevel)
+            attributeInt(ATTR_TYPE, type)
+            if (type == Permission.TYPE_DYNAMIC) {
+                val permissionInfo = permission.permissionInfo
+                attributeIntHexWithDefault(ATTR_ICON, permissionInfo.icon, 0)
+                permissionInfo.nonLocalizedLabel?.toString()?.let { attribute(ATTR_LABEL, it) }
+            }
+        }
+    }
+
+    fun BinaryXmlPullParser.parseUserState(state: MutableAccessState, userId: Int) {
+        when (tagName) {
+            TAG_APP_ID_PERMISSIONS -> parseAppIdPermissions(state, userId)
+            else -> {}
+        }
+    }
+
+    private fun BinaryXmlPullParser.parseAppIdPermissions(state: MutableAccessState, userId: Int) {
+        val userState = state.mutateUserState(userId, WriteMode.NONE)!!
+        val appIdPermissionFlags = userState.mutateAppIdPermissionFlags()
+        forEachTag {
+            when (tagName) {
+                TAG_APP_ID -> parseAppId(appIdPermissionFlags)
+                else -> Slog.w(LOG_TAG, "Ignoring unknown tag $name when parsing permission state")
+            }
+        }
+        appIdPermissionFlags.forEachReversedIndexed { appIdIndex, appId, _ ->
+            if (appId !in state.externalState.appIdPackageNames) {
+                Slog.w(LOG_TAG, "Dropping unknown app ID $appId when parsing permission state")
+                appIdPermissionFlags.removeAt(appIdIndex)
+                userState.requestWriteMode(WriteMode.ASYNCHRONOUS)
+            }
+        }
+    }
+
+    private fun BinaryXmlPullParser.parseAppId(appIdPermissionFlags: MutableAppIdPermissionFlags) {
+        val appId = getAttributeIntOrThrow(ATTR_ID)
+        val permissionFlags = MutableIndexedMap<String, Int>()
+        appIdPermissionFlags[appId] = permissionFlags
+        forEachTag {
+            when (tagName) {
+                TAG_PERMISSION -> parseAppIdPermission(permissionFlags)
+                else -> Slog.w(LOG_TAG, "Ignoring unknown tag $name when parsing permission state")
+            }
+        }
+    }
+
+    private fun BinaryXmlPullParser.parseAppIdPermission(
+        permissionFlags: MutableIndexedMap<String, Int>
+    ) {
+        val name = getAttributeValueOrThrow(ATTR_NAME).intern()
+        val flags = getAttributeIntOrThrow(ATTR_FLAGS)
+        permissionFlags[name] = flags
+    }
+
+    fun BinaryXmlSerializer.serializeUserState(state: AccessState, userId: Int) {
+        serializeAppIdPermissions(state.userStates[userId]!!.appIdPermissionFlags)
+    }
+
+    private fun BinaryXmlSerializer.serializeAppIdPermissions(
+        appIdPermissionFlags: AppIdPermissionFlags
+    ) {
+        tag(TAG_APP_ID_PERMISSIONS) {
+            appIdPermissionFlags.forEachIndexed { _, appId, permissionFlags ->
+                serializeAppId(appId, permissionFlags)
+            }
+        }
+    }
+
+    private fun BinaryXmlSerializer.serializeAppId(
+        appId: Int,
+        permissionFlags: IndexedMap<String, Int>
+    ) {
+        tag(TAG_APP_ID) {
+            attributeInt(ATTR_ID, appId)
+            permissionFlags.forEachIndexed { _, name, flags ->
+                serializeAppIdPermission(name, flags)
+            }
+        }
+    }
+
+    private fun BinaryXmlSerializer.serializeAppIdPermission(name: String, flags: Int) {
+        tag(TAG_PERMISSION) {
+            attributeInterned(ATTR_NAME, name)
+            // Never serialize one-time permissions as granted.
+            val serializedFlags = if (flags.hasBits(PermissionFlags.ONE_TIME)) {
+                flags andInv PermissionFlags.RUNTIME_GRANTED
+            } else {
+                flags
+            }
+            attributeInt(ATTR_FLAGS, serializedFlags)
+        }
+    }
+
+    companion object {
+        private val LOG_TAG = AppIdPermissionPersistence::class.java.simpleName
+
+        private const val TAG_APP_ID = "app-id"
+        private const val TAG_APP_ID_PERMISSIONS = "app-id-permissions"
+        private const val TAG_PERMISSION = "permission"
+        private const val TAG_PERMISSIONS = "permissions"
+        private const val TAG_PERMISSION_TREES = "permission-trees"
+
+        private const val ATTR_FLAGS = "flags"
+        private const val ATTR_ICON = "icon"
+        private const val ATTR_ID = "id"
+        private const val ATTR_LABEL = "label"
+        private const val ATTR_NAME = "name"
+        private const val ATTR_PACKAGE_NAME = "packageName"
+        private const val ATTR_PROTECTION_LEVEL = "protectionLevel"
+        private const val ATTR_TYPE = "type"
+    }
+}
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
new file mode 100644
index 0000000..aa86cd6
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
@@ -0,0 +1,1591 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.permission.access.permission
+
+import android.Manifest
+import android.content.pm.PackageManager
+import android.content.pm.PermissionGroupInfo
+import android.content.pm.PermissionInfo
+import android.content.pm.SigningDetails
+import android.os.Build
+import android.util.Slog
+import com.android.internal.os.RoSystemProperties
+import com.android.modules.utils.BinaryXmlPullParser
+import com.android.modules.utils.BinaryXmlSerializer
+import com.android.server.permission.access.AccessState
+import com.android.server.permission.access.GetStateScope
+import com.android.server.permission.access.MutableAccessState
+import com.android.server.permission.access.MutateStateScope
+import com.android.server.permission.access.PermissionUri
+import com.android.server.permission.access.SchemePolicy
+import com.android.server.permission.access.UidUri
+import com.android.server.permission.access.WriteMode
+import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
+import com.android.server.permission.access.immutable.* // ktlint-disable no-wildcard-imports
+import com.android.server.permission.access.util.andInv
+import com.android.server.permission.access.util.hasAnyBit
+import com.android.server.permission.access.util.hasBits
+import com.android.server.permission.access.util.isInternal
+import com.android.server.pm.KnownPackages
+import com.android.server.pm.parsing.PackageInfoUtils
+import com.android.server.pm.permission.CompatibilityPermissionInfo
+import com.android.server.pm.pkg.AndroidPackage
+import com.android.server.pm.pkg.PackageState
+
+class AppIdPermissionPolicy : SchemePolicy() {
+    private val persistence = AppIdPermissionPersistence()
+
+    private val migration = AppIdPermissionMigration()
+
+    private val upgrade = AppIdPermissionUpgrade(this)
+
+    @Volatile
+    private var onPermissionFlagsChangedListeners:
+        IndexedListSet<OnPermissionFlagsChangedListener> = MutableIndexedListSet()
+    private val onPermissionFlagsChangedListenersLock = Any()
+
+    private val privilegedPermissionAllowlistViolations = MutableIndexedSet<String>()
+
+    override val subjectScheme: String
+        get() = UidUri.SCHEME
+
+    override val objectScheme: String
+        get() = PermissionUri.SCHEME
+
+    override fun GetStateScope.onStateMutated() {
+        onPermissionFlagsChangedListeners.forEachIndexed { _, it -> it.onStateMutated() }
+    }
+
+    override fun MutateStateScope.onInitialized() {
+        newState.externalState.configPermissions.forEach { (permissionName, permissionEntry) ->
+            val oldPermission = newState.systemState.permissions[permissionName]
+            val newPermission = if (oldPermission != null) {
+                if (permissionEntry.gids != null) {
+                    oldPermission.copy(
+                        gids = permissionEntry.gids, areGidsPerUser = permissionEntry.perUser
+                    )
+                } else {
+                    return@forEach
+                }
+            } else {
+                @Suppress("DEPRECATION")
+                val permissionInfo = PermissionInfo().apply {
+                    name = permissionName
+                    packageName = PLATFORM_PACKAGE_NAME
+                    protectionLevel = PermissionInfo.PROTECTION_SIGNATURE
+                }
+                if (permissionEntry.gids != null) {
+                    Permission(
+                        permissionInfo, false, Permission.TYPE_CONFIG, 0, permissionEntry.gids,
+                        permissionEntry.perUser
+                    )
+                } else {
+                    Permission(permissionInfo, false, Permission.TYPE_CONFIG, 0)
+                }
+            }
+            newState.mutateSystemState().mutatePermissions()[permissionName] = newPermission
+        }
+    }
+
+    override fun MutateStateScope.onUserAdded(userId: Int) {
+        newState.externalState.packageStates.forEach { (_, packageState) ->
+            evaluateAllPermissionStatesForPackageAndUser(packageState, userId, null)
+        }
+        newState.externalState.appIdPackageNames.forEachIndexed { _, appId, _ ->
+            inheritImplicitPermissionStates(appId, userId)
+        }
+    }
+
+    override fun MutateStateScope.onAppIdRemoved(appId: Int) {
+        newState.userStates.forEachIndexed { userStateIndex, _, userState ->
+            if (appId in userState.appIdPermissionFlags) {
+                newState.mutateUserStateAt(userStateIndex).mutateAppIdPermissionFlags() -= appId
+                // Skip notifying the change listeners since the app ID no longer exists.
+            }
+        }
+    }
+
+    override fun MutateStateScope.onStorageVolumeMounted(
+        volumeUuid: String?,
+        packageNames: List<String>,
+        isSystemUpdated: Boolean
+    ) {
+        val changedPermissionNames = MutableIndexedSet<String>()
+        packageNames.forEachIndexed { _, packageName ->
+            val packageState = newState.externalState.packageStates[packageName]!!
+            adoptPermissions(packageState, changedPermissionNames)
+            addPermissionGroups(packageState)
+            addPermissions(packageState, changedPermissionNames)
+            trimPermissions(packageState.packageName, changedPermissionNames)
+            trimPermissionStates(packageState.appId)
+            revokePermissionsOnPackageUpdate(packageState.appId)
+        }
+        changedPermissionNames.forEachIndexed { _, permissionName ->
+            evaluatePermissionStateForAllPackages(permissionName, null)
+        }
+
+        packageNames.forEachIndexed { _, packageName ->
+            val packageState = newState.externalState.packageStates[packageName]!!
+            val installedPackageState = if (isSystemUpdated) packageState else null
+            evaluateAllPermissionStatesForPackage(packageState, installedPackageState)
+        }
+        packageNames.forEachIndexed { _, packageName ->
+            val packageState = newState.externalState.packageStates[packageName]!!
+            newState.externalState.userIds.forEachIndexed { _, userId ->
+                inheritImplicitPermissionStates(packageState.appId, userId)
+            }
+        }
+    }
+
+    override fun MutateStateScope.onPackageAdded(packageState: PackageState) {
+        val changedPermissionNames = MutableIndexedSet<String>()
+        adoptPermissions(packageState, changedPermissionNames)
+        addPermissionGroups(packageState)
+        addPermissions(packageState, changedPermissionNames)
+        trimPermissions(packageState.packageName, changedPermissionNames)
+        trimPermissionStates(packageState.appId)
+        revokePermissionsOnPackageUpdate(packageState.appId)
+        changedPermissionNames.forEachIndexed { _, permissionName ->
+            evaluatePermissionStateForAllPackages(permissionName, null)
+        }
+        evaluateAllPermissionStatesForPackage(packageState, packageState)
+        newState.externalState.userIds.forEachIndexed { _, userId ->
+            inheritImplicitPermissionStates(packageState.appId, userId)
+        }
+    }
+
+    override fun MutateStateScope.onPackageRemoved(packageName: String, appId: Int) {
+        check(packageName !in newState.externalState.disabledSystemPackageStates) {
+            "Package $packageName reported as removed before disabled system package is enabled"
+        }
+
+        val changedPermissionNames = MutableIndexedSet<String>()
+        trimPermissions(packageName, changedPermissionNames)
+        if (appId in newState.externalState.appIdPackageNames) {
+            trimPermissionStates(appId)
+        }
+        changedPermissionNames.forEachIndexed { _, permissionName ->
+            evaluatePermissionStateForAllPackages(permissionName, null)
+        }
+    }
+
+    override fun MutateStateScope.onPackageInstalled(packageState: PackageState, userId: Int) {
+        // Clear UPGRADE_EXEMPT for all permissions requested by this package since there's
+        // an installer and the installer has made a decision.
+        clearRestrictedPermissionImplicitExemption(packageState, userId)
+    }
+
+    private fun MutateStateScope.clearRestrictedPermissionImplicitExemption(
+        packageState: PackageState,
+        userId: Int
+    ) {
+        // System apps can always retain their UPGRADE_EXEMPT.
+        if (packageState.isSystem) {
+            return
+        }
+        val androidPackage = packageState.androidPackage ?: return
+        val appId = packageState.appId
+        androidPackage.requestedPermissions.forEach { permissionName ->
+            val permission = newState.systemState.permissions[permissionName]
+                ?: return@forEach
+            if (!permission.isHardOrSoftRestricted) {
+                return@forEach
+            }
+            val isRequestedBySystemPackage = anyPackageInAppId(appId) {
+                it.isSystem && permissionName in it.androidPackage!!.requestedPermissions
+            }
+            if (isRequestedBySystemPackage) {
+                return@forEach
+            }
+            val oldFlags = getPermissionFlags(appId, userId, permissionName)
+            var newFlags = oldFlags andInv PermissionFlags.UPGRADE_EXEMPT
+            val isExempt = newFlags.hasAnyBit(PermissionFlags.MASK_EXEMPT)
+            newFlags = if (permission.isHardRestricted && !isExempt) {
+                newFlags or PermissionFlags.RESTRICTION_REVOKED
+            } else {
+                newFlags andInv PermissionFlags.RESTRICTION_REVOKED
+            }
+            newFlags = if (permission.isSoftRestricted && !isExempt) {
+                newFlags or PermissionFlags.SOFT_RESTRICTED
+            } else {
+                newFlags andInv PermissionFlags.SOFT_RESTRICTED
+            }
+            setPermissionFlags(appId, userId, permissionName, newFlags)
+        }
+    }
+
+    override fun MutateStateScope.onPackageUninstalled(
+        packageName: String,
+        appId: Int,
+        userId: Int
+    ) {
+        resetRuntimePermissions(packageName, userId)
+    }
+
+    fun MutateStateScope.resetRuntimePermissions(packageName: String, userId: Int) {
+        // It's okay to skip resetting permissions for packages that are removed,
+        // because their states will be trimmed in onPackageRemoved()/onAppIdRemoved()
+        val packageState = newState.externalState.packageStates[packageName] ?: return
+        val androidPackage = packageState.androidPackage ?: return
+        val appId = packageState.appId
+        androidPackage.requestedPermissions.forEach { permissionName ->
+            val permission = newState.systemState.permissions[permissionName]
+                ?: return@forEach
+            if (!permission.isRuntime || permission.isRemoved) {
+                return@forEach
+            }
+            val isRequestedByOtherPackages = anyPackageInAppId(appId) {
+                it.packageName != packageName &&
+                    permissionName in it.androidPackage!!.requestedPermissions
+            }
+            if (isRequestedByOtherPackages) {
+                return@forEach
+            }
+            val oldFlags = getPermissionFlags(appId, userId, permissionName)
+            if (oldFlags.hasAnyBit(SYSTEM_OR_POLICY_FIXED_MASK)) {
+                return@forEach
+            }
+            var newFlags = oldFlags
+            newFlags = if (
+                newFlags.hasBits(PermissionFlags.ROLE) || newFlags.hasBits(PermissionFlags.PREGRANT)
+            ) {
+                newFlags or PermissionFlags.RUNTIME_GRANTED
+            } else {
+                newFlags andInv PermissionFlags.RUNTIME_GRANTED
+            }
+            newFlags = newFlags andInv USER_SETTABLE_MASK
+            if (newFlags.hasBits(PermissionFlags.LEGACY_GRANTED)) {
+                newFlags = newFlags or PermissionFlags.IMPLICIT
+            }
+            setPermissionFlags(appId, userId, permissionName, newFlags)
+        }
+    }
+
+    private fun MutateStateScope.adoptPermissions(
+        packageState: PackageState,
+        changedPermissionNames: MutableIndexedSet<String>
+    ) {
+        val `package` = packageState.androidPackage!!
+        `package`.adoptPermissions.forEachIndexed { _, originalPackageName ->
+            val packageName = `package`.packageName
+            if (!canAdoptPermissions(packageName, originalPackageName)) {
+                return@forEachIndexed
+            }
+            newState.systemState.permissions.forEachIndexed permissions@ {
+                permissionIndex, permissionName, oldPermission ->
+                if (oldPermission.packageName != originalPackageName) {
+                    return@permissions
+                }
+                @Suppress("DEPRECATION")
+                val newPermissionInfo = PermissionInfo().apply {
+                    name = oldPermission.permissionInfo.name
+                    this.packageName = packageName
+                    protectionLevel = oldPermission.permissionInfo.protectionLevel
+                }
+                // Different from the old implementation, which removes the GIDs upon permission
+                // adoption, but adds them back on the next boot, we now just consistently keep the
+                // GIDs.
+                val newPermission = oldPermission.copy(
+                    permissionInfo = newPermissionInfo, isReconciled = false, appId = 0
+                )
+                newState.mutateSystemState().mutatePermissions()
+                    .putAt(permissionIndex, newPermission)
+                changedPermissionNames += permissionName
+            }
+        }
+    }
+
+    private fun MutateStateScope.canAdoptPermissions(
+        packageName: String,
+        originalPackageName: String
+    ): Boolean {
+        val originalPackageState = newState.externalState.packageStates[originalPackageName]
+            ?: return false
+        if (!originalPackageState.isSystem) {
+            Slog.w(
+                LOG_TAG, "Unable to adopt permissions from $originalPackageName to $packageName:" +
+                    " original package not in system partition"
+            )
+            return false
+        }
+        if (originalPackageState.androidPackage != null) {
+            Slog.w(
+                LOG_TAG, "Unable to adopt permissions from $originalPackageName to $packageName:" +
+                    " original package still exists"
+            )
+            return false
+        }
+        return true
+    }
+
+    private fun MutateStateScope.addPermissionGroups(packageState: PackageState) {
+        // Different from the old implementation, which decides whether the app is an instant app by
+        // the install flags, now for consistent behavior we allow adding permission groups if the
+        // app is non-instant in at least one user.
+        val isInstantApp = packageState.userStates.allIndexed { _, _, it -> it.isInstantApp }
+        if (isInstantApp) {
+            Slog.w(
+                LOG_TAG, "Ignoring permission groups declared in package" +
+                    " ${packageState.packageName}: instant apps cannot declare permission groups"
+            )
+            return
+        }
+        packageState.androidPackage!!.permissionGroups.forEachIndexed { _, parsedPermissionGroup ->
+            val newPermissionGroup = PackageInfoUtils.generatePermissionGroupInfo(
+                parsedPermissionGroup, PackageManager.GET_META_DATA.toLong()
+            )!!
+            // TODO: Clear permission state on group take-over?
+            val permissionGroupName = newPermissionGroup.name
+            val oldPermissionGroup = newState.systemState.permissionGroups[permissionGroupName]
+            if (oldPermissionGroup != null &&
+                newPermissionGroup.packageName != oldPermissionGroup.packageName) {
+                val newPackageName = newPermissionGroup.packageName
+                val oldPackageName = oldPermissionGroup.packageName
+                // Different from the old implementation, which defines permission group on
+                // a first-come-first-serve basis, and relies on system apps being scanned before
+                // non-system apps, we now allow system apps to override permission groups similar
+                // to permissions so that we no longer need to rely on the scan order.
+                if (!packageState.isSystem) {
+                    Slog.w(
+                        LOG_TAG, "Ignoring permission group $permissionGroupName declared in" +
+                            " package $newPackageName: already declared in another" +
+                            " package $oldPackageName"
+                    )
+                    return@forEachIndexed
+                }
+                if (newState.externalState.packageStates[oldPackageName]?.isSystem == true) {
+                    Slog.w(
+                        LOG_TAG, "Ignoring permission group $permissionGroupName declared in" +
+                            " system package $newPackageName: already declared in another" +
+                            " system package $oldPackageName"
+                    )
+                    return@forEachIndexed
+                }
+                Slog.w(
+                    LOG_TAG, "Overriding permission group $permissionGroupName with" +
+                        " new declaration in system package $newPackageName: originally" +
+                        " declared in another package $oldPackageName"
+                )
+            }
+            newState.mutateSystemState().mutatePermissionGroups()[permissionGroupName] =
+                newPermissionGroup
+        }
+    }
+
+    private fun MutateStateScope.addPermissions(
+        packageState: PackageState,
+        changedPermissionNames: MutableIndexedSet<String>
+    ) {
+        val androidPackage = packageState.androidPackage!!
+        // This may not be the same package as the old permission because the old permission owner
+        // can be different, hence using this somewhat strange name to prevent misuse.
+        val oldNewPackage = oldState.externalState.packageStates[packageState.packageName]
+            ?.androidPackage
+        val isPackageSigningChanged = oldNewPackage != null &&
+                androidPackage.signingDetails != oldNewPackage.signingDetails
+        androidPackage.permissions.forEachIndexed { _, parsedPermission ->
+            val newPermissionInfo = PackageInfoUtils.generatePermissionInfo(
+                parsedPermission, PackageManager.GET_META_DATA.toLong()
+            )!!
+            val permissionName = newPermissionInfo.name
+            val oldPermission = if (parsedPermission.isTree) {
+                newState.systemState.permissionTrees[permissionName]
+            } else {
+                newState.systemState.permissions[permissionName]
+            }
+            // Different from the old implementation, which may add an (incomplete) signature
+            // permission inside another package's permission tree, we now consistently ignore such
+            // permissions.
+            val permissionTree = findPermissionTree(permissionName)
+            val newPackageName = newPermissionInfo.packageName
+            if (permissionTree != null && newPackageName != permissionTree.packageName) {
+                Slog.w(
+                    LOG_TAG, "Ignoring permission $permissionName declared in package" +
+                        " $newPackageName: base permission tree ${permissionTree.name} is" +
+                        " declared in another package ${permissionTree.packageName}"
+                )
+                return@forEachIndexed
+            }
+            val newPermission = if (oldPermission != null &&
+                newPackageName != oldPermission.packageName) {
+                val oldPackageName = oldPermission.packageName
+                // Only allow system apps to redefine non-system permissions.
+                if (!packageState.isSystem) {
+                    Slog.w(
+                        LOG_TAG, "Ignoring permission $permissionName declared in package" +
+                            " $newPackageName: already declared in another package" +
+                            " $oldPackageName"
+                    )
+                    return@forEachIndexed
+                }
+                if (oldPermission.type == Permission.TYPE_CONFIG && !oldPermission.isReconciled) {
+                    // It's a config permission and has no owner, take ownership now.
+                    oldPermission.copy(
+                        permissionInfo = newPermissionInfo, isReconciled = true,
+                        type = Permission.TYPE_MANIFEST, appId = packageState.appId
+                    )
+                } else if (newState.externalState.packageStates[oldPackageName]?.isSystem != true) {
+                    Slog.w(
+                        LOG_TAG, "Overriding permission $permissionName with new declaration in" +
+                            " system package $newPackageName: originally declared in another" +
+                            " package $oldPackageName"
+                    )
+                    // Remove permission state on owner change.
+                    newState.externalState.userIds.forEachIndexed { _, userId ->
+                        newState.externalState.appIdPackageNames.forEachIndexed { _, appId, _ ->
+                            setPermissionFlags(appId, userId, permissionName, 0)
+                        }
+                    }
+                    // Different from the old implementation, which removes the GIDs upon permission
+                    // override, but adds them back on the next boot, we now just consistently keep
+                    // the GIDs.
+                    Permission(
+                        newPermissionInfo, true, Permission.TYPE_MANIFEST, packageState.appId,
+                        oldPermission.gids, oldPermission.areGidsPerUser
+                    )
+                } else {
+                    Slog.w(
+                        LOG_TAG, "Ignoring permission $permissionName declared in system package" +
+                            " $newPackageName: already declared in another system package" +
+                            " $oldPackageName"
+                    )
+                    return@forEachIndexed
+                }
+            } else {
+                if (oldPermission != null && oldPermission.isReconciled) {
+                    val isPermissionGroupChanged = newPermissionInfo.isRuntime &&
+                        newPermissionInfo.group != null &&
+                        newPermissionInfo.group != oldPermission.groupName
+                    val isPermissionProtectionChanged =
+                        oldPermission.type != Permission.TYPE_CONFIG && (
+                            (newPermissionInfo.isRuntime && !oldPermission.isRuntime) ||
+                                (newPermissionInfo.isInternal && !oldPermission.isInternal)
+                        )
+                    if (isPermissionGroupChanged || isPermissionProtectionChanged) {
+                        newState.externalState.userIds.forEachIndexed { _, userId ->
+                            newState.externalState.appIdPackageNames.forEachIndexed { _, appId, _ ->
+                                if (isPermissionGroupChanged) {
+                                    // We might auto-grant permissions if any permission of
+                                    // the group is already granted. Hence if the group of
+                                    // a granted permission changes we need to revoke it to
+                                    // avoid having permissions of the new group auto-granted.
+                                    Slog.w(
+                                        LOG_TAG, "Revoking runtime permission $permissionName for" +
+                                            " appId $appId and userId $userId as the permission" +
+                                            " group changed from ${oldPermission.groupName}" +
+                                            " to ${newPermissionInfo.group}"
+                                    )
+                                }
+                                if (isPermissionProtectionChanged) {
+                                    Slog.w(
+                                        LOG_TAG, "Revoking permission $permissionName for" +
+                                            " appId $appId and userId $userId as the permission" +
+                                            " protection changed."
+                                    )
+                                }
+                                setPermissionFlags(appId, userId, permissionName, 0)
+                            }
+                        }
+                    }
+                }
+
+                // Different from the old implementation, which doesn't update the permission
+                // definition upon app update, but does update it on the next boot, we now
+                // consistently update the permission definition upon app update.
+                @Suppress("IfThenToElvis")
+                if (oldPermission != null) {
+                    oldPermission.copy(
+                        permissionInfo = newPermissionInfo, isReconciled = true,
+                        type = Permission.TYPE_MANIFEST, appId = packageState.appId
+                    )
+                } else {
+                    Permission(
+                        newPermissionInfo, true, Permission.TYPE_MANIFEST, packageState.appId
+                    )
+                }
+            }
+
+            if (parsedPermission.isTree) {
+                newState.mutateSystemState().mutatePermissionTrees()[permissionName] = newPermission
+            } else {
+                newState.mutateSystemState().mutatePermissions()[permissionName] = newPermission
+                val isPermissionChanged = oldPermission == null ||
+                    newPackageName != oldPermission.packageName ||
+                    newPermission.protectionLevel != oldPermission.protectionLevel || (
+                        oldPermission.isReconciled && (
+                            (newPermission.isSignature && isPackageSigningChanged) || (
+                                newPermission.isKnownSigner &&
+                                    newPermission.knownCerts != oldPermission.knownCerts
+                            ) || (
+                                newPermission.isRuntime && newPermission.groupName != null &&
+                                    newPermission.groupName != oldPermission.groupName
+                            )
+                        )
+                    )
+                if (isPermissionChanged) {
+                    changedPermissionNames += permissionName
+                }
+            }
+        }
+    }
+
+    private fun MutateStateScope.trimPermissions(
+        packageName: String,
+        changedPermissionNames: MutableIndexedSet<String>
+    ) {
+        val packageState = newState.externalState.packageStates[packageName]
+        val androidPackage = packageState?.androidPackage
+        if (packageState != null && androidPackage == null) {
+            return
+        }
+        val disabledSystemPackage = newState.externalState.disabledSystemPackageStates[packageName]
+            ?.androidPackage
+        // Unlike in the previous implementation, we now also retain permission trees defined by
+        // disabled system packages for consistency with permissions.
+        newState.systemState.permissionTrees.forEachReversedIndexed {
+            permissionTreeIndex, permissionTreeName, permissionTree ->
+            if (permissionTree.packageName == packageName && (
+                packageState == null || androidPackage!!.permissions.noneIndexed { _, it ->
+                    it.isTree && it.name == permissionTreeName
+                }
+            ) && (
+                disabledSystemPackage?.permissions?.anyIndexed { _, it ->
+                    it.isTree && it.name == permissionTreeName
+                } != true
+            )) {
+                newState.mutateSystemState().mutatePermissionTrees().removeAt(permissionTreeIndex)
+            }
+        }
+
+        newState.systemState.permissions.forEachReversedIndexed {
+            permissionIndex, permissionName, permission ->
+            val updatedPermission = updatePermissionIfDynamic(permission)
+            newState.mutateSystemState().mutatePermissions()
+                .putAt(permissionIndex, updatedPermission)
+            if (updatedPermission.packageName == packageName && (
+                packageState == null || androidPackage!!.permissions.noneIndexed { _, it ->
+                    !it.isTree && it.name == permissionName
+                }
+            ) && (
+                disabledSystemPackage?.permissions?.anyIndexed { _, it ->
+                    !it.isTree && it.name == permissionName
+                } != true
+            )) {
+                // Different from the old implementation where we keep the permission state if the
+                // permission is declared by a disabled system package (ag/15189282), we now
+                // shouldn't be notified when the updated system package is removed but the disabled
+                // system package isn't re-enabled yet, so we don't need to maintain that brittle
+                // special case either.
+                newState.externalState.userIds.forEachIndexed { _, userId ->
+                    newState.externalState.appIdPackageNames.forEachIndexed { _, appId, _ ->
+                        setPermissionFlags(appId, userId, permissionName, 0)
+                    }
+                }
+                newState.mutateSystemState().mutatePermissions().removeAt(permissionIndex)
+                changedPermissionNames += permissionName
+            }
+        }
+    }
+
+    private fun MutateStateScope.updatePermissionIfDynamic(permission: Permission): Permission {
+        if (!permission.isDynamic) {
+            return permission
+        }
+        val permissionTree = findPermissionTree(permission.name) ?: return permission
+        @Suppress("DEPRECATION")
+        return permission.copy(
+            permissionInfo = PermissionInfo(permission.permissionInfo).apply {
+                packageName = permissionTree.packageName
+            }, appId = permissionTree.appId, isReconciled = true
+        )
+    }
+
+    private fun MutateStateScope.trimPermissionStates(appId: Int) {
+        val requestedPermissions = MutableIndexedSet<String>()
+        forEachPackageInAppId(appId) {
+            // Note that we still trim the permission states requested by disabled system packages.
+            // Because in the previous implementation:
+            // despite revokeSharedUserPermissionsForLeavingPackageInternal() retains permissions
+            // requested by disabled system packages, revokeUnusedSharedUserPermissionsLocked(),
+            // which is call upon app update installation, didn't do such preservation.
+            // Hence, permissions only requested by disabled system packages were still trimmed in
+            // the previous implementation.
+            requestedPermissions += it.androidPackage!!.requestedPermissions
+        }
+        newState.userStates.forEachIndexed { _, userId, userState ->
+            userState.appIdPermissionFlags[appId]?.forEachReversedIndexed { _, permissionName, _ ->
+                if (permissionName !in requestedPermissions) {
+                    setPermissionFlags(appId, userId, permissionName, 0)
+                }
+            }
+        }
+    }
+
+    private fun MutateStateScope.revokePermissionsOnPackageUpdate(appId: Int) {
+        val hasOldPackage = appId in oldState.externalState.appIdPackageNames &&
+            anyPackageInAppId(appId, oldState) { true }
+        if (!hasOldPackage) {
+            // Don't revoke anything if this isn't a package update, i.e. if information about the
+            // old package isn't available. Notably, this also means skipping packages changed via
+            // OTA, but the revocation here is also mostly for normal apps and there's no way to get
+            // information about the package before OTA anyway.
+            return
+        }
+
+        // If the app is updated, and has scoped storage permissions, then it is possible that the
+        // app updated in an attempt to get unscoped storage. If so, revoke all storage permissions.
+        val oldTargetSdkVersion =
+            reducePackageInAppId(appId, Build.VERSION_CODES.CUR_DEVELOPMENT, oldState) {
+                targetSdkVersion, packageState ->
+                targetSdkVersion.coerceAtMost(packageState.androidPackage!!.targetSdkVersion)
+            }
+        val newTargetSdkVersion =
+            reducePackageInAppId(appId, Build.VERSION_CODES.CUR_DEVELOPMENT, newState) {
+                targetSdkVersion, packageState ->
+                targetSdkVersion.coerceAtMost(packageState.androidPackage!!.targetSdkVersion)
+            }
+        @Suppress("ConvertTwoComparisonsToRangeCheck")
+        val isTargetSdkVersionDowngraded = oldTargetSdkVersion >= Build.VERSION_CODES.Q &&
+            newTargetSdkVersion < Build.VERSION_CODES.Q
+        @Suppress("ConvertTwoComparisonsToRangeCheck")
+        val isTargetSdkVersionUpgraded = oldTargetSdkVersion < Build.VERSION_CODES.Q &&
+            newTargetSdkVersion >= Build.VERSION_CODES.Q
+        val oldIsRequestLegacyExternalStorage = anyPackageInAppId(appId, oldState) {
+            it.androidPackage!!.isRequestLegacyExternalStorage
+        }
+        val newIsRequestLegacyExternalStorage = anyPackageInAppId(appId, newState) {
+            it.androidPackage!!.isRequestLegacyExternalStorage
+        }
+        val isNewlyRequestingLegacyExternalStorage = !isTargetSdkVersionUpgraded &&
+            !oldIsRequestLegacyExternalStorage && newIsRequestLegacyExternalStorage
+        val shouldRevokeStorageAndMediaPermissions = isNewlyRequestingLegacyExternalStorage ||
+            isTargetSdkVersionDowngraded
+        if (shouldRevokeStorageAndMediaPermissions) {
+            newState.userStates.forEachIndexed { _, userId, userState ->
+                userState.appIdPermissionFlags[appId]?.forEachReversedIndexed {
+                    _, permissionName, oldFlags ->
+                    if (permissionName in STORAGE_AND_MEDIA_PERMISSIONS &&
+                        oldFlags.hasBits(PermissionFlags.RUNTIME_GRANTED)) {
+                        Slog.v(
+                            LOG_TAG, "Revoking storage permission: $permissionName for appId: " +
+                                " $appId and user: $userId"
+                        )
+                        val newFlags = oldFlags andInv (
+                            PermissionFlags.RUNTIME_GRANTED or USER_SETTABLE_MASK
+                        )
+                        setPermissionFlags(appId, userId, permissionName, newFlags)
+                    }
+                }
+            }
+        }
+    }
+
+    private fun MutateStateScope.evaluatePermissionStateForAllPackages(
+        permissionName: String,
+        installedPackageState: PackageState?
+    ) {
+        val externalState = newState.externalState
+        externalState.userIds.forEachIndexed { _, userId ->
+            externalState.appIdPackageNames.forEachIndexed { _, appId, _ ->
+                val isPermissionRequested = anyPackageInAppId(appId) {
+                    permissionName in it.androidPackage!!.requestedPermissions
+                }
+                if (isPermissionRequested) {
+                    evaluatePermissionState(appId, userId, permissionName, installedPackageState)
+                }
+            }
+        }
+    }
+
+    private fun MutateStateScope.evaluateAllPermissionStatesForPackage(
+        packageState: PackageState,
+        installedPackageState: PackageState?
+    ) {
+        newState.externalState.userIds.forEachIndexed { _, userId ->
+            evaluateAllPermissionStatesForPackageAndUser(
+                packageState, userId, installedPackageState
+            )
+        }
+    }
+
+    private fun MutateStateScope.evaluateAllPermissionStatesForPackageAndUser(
+        packageState: PackageState,
+        userId: Int,
+        installedPackageState: PackageState?
+    ) {
+        packageState.androidPackage?.requestedPermissions?.forEach { permissionName ->
+            evaluatePermissionState(
+                packageState.appId, userId, permissionName, installedPackageState
+            )
+        }
+    }
+
+    private fun MutateStateScope.evaluatePermissionState(
+        appId: Int,
+        userId: Int,
+        permissionName: String,
+        installedPackageState: PackageState?
+    ) {
+        val packageNames = newState.externalState.appIdPackageNames[appId]!!
+        // Repeatedly checking whether a permission is requested can actually be costly, so we cache
+        // the result for this method which is frequently called during boot, instead of calling
+        // anyPackageInAppId() and checking requested permissions multiple times.
+        val requestingPackageStates = MutableIndexedList<PackageState>()
+        var hasMissingPackage = false
+        packageNames.forEachIndexed { _, packageName ->
+            val packageState = newState.externalState.packageStates[packageName]!!
+            val androidPackage = packageState.androidPackage
+            if (androidPackage != null) {
+                if (permissionName in androidPackage.requestedPermissions) {
+                    requestingPackageStates += packageState
+                }
+            } else {
+                hasMissingPackage = true
+            }
+        }
+        if (packageNames.size == 1 && hasMissingPackage) {
+            // For non-shared-user packages with missing androidPackage, skip evaluation.
+            return
+        }
+        val permission = newState.systemState.permissions[permissionName]
+        val oldFlags = getPermissionFlags(appId, userId, permissionName)
+        if (permission == null) {
+            if (oldFlags == 0) {
+                // If the permission definition is missing and we don't have any permission states
+                // for this permission, add the INSTALL_REVOKED flag to ensure that we don't
+                // automatically grant the permission when it's defined
+                setPermissionFlags(appId, userId, permissionName, PermissionFlags.INSTALL_REVOKED)
+            }
+            return
+        }
+        if (permission.isNormal) {
+            val wasGranted = oldFlags.hasBits(PermissionFlags.INSTALL_GRANTED)
+            if (!wasGranted) {
+                val wasRevoked = oldFlags.hasBits(PermissionFlags.INSTALL_REVOKED)
+                val isRequestedByInstalledPackage = installedPackageState != null &&
+                    permissionName in installedPackageState.androidPackage!!.requestedPermissions
+                val isRequestedBySystemPackage =
+                    requestingPackageStates.anyIndexed { _, it -> it.isSystem }
+                val isCompatibilityPermission = requestingPackageStates.anyIndexed { _, it ->
+                    isCompatibilityPermissionForPackage(it.androidPackage!!, permissionName)
+                }
+                // If this is an existing, non-system package,
+                // then we can't add any new permissions to it.
+                // Except if this is a permission that was added to the platform
+                var newFlags = if (!wasRevoked || isRequestedByInstalledPackage ||
+                    isRequestedBySystemPackage || isCompatibilityPermission) {
+                    PermissionFlags.INSTALL_GRANTED
+                } else {
+                    PermissionFlags.INSTALL_REVOKED
+                }
+                if (permission.isAppOp) {
+                    newFlags = newFlags or (
+                        oldFlags and (PermissionFlags.ROLE or PermissionFlags.USER_SET)
+                    )
+                }
+                setPermissionFlags(appId, userId, permissionName, newFlags)
+            }
+        } else if (permission.isSignature || permission.isInternal) {
+            val wasProtectionGranted = oldFlags.hasBits(PermissionFlags.PROTECTION_GRANTED)
+            var newFlags = if (hasMissingPackage && wasProtectionGranted) {
+                // Keep the non-runtime permission grants for shared UID with missing androidPackage
+                PermissionFlags.PROTECTION_GRANTED
+            } else {
+                val mayGrantByPrivileged = !permission.isPrivileged ||
+                    requestingPackageStates.anyIndexed { _, it ->
+                        checkPrivilegedPermissionAllowlist(it, permission)
+                    }
+                val shouldGrantBySignature = permission.isSignature &&
+                    requestingPackageStates.anyIndexed { _, it ->
+                        shouldGrantPermissionBySignature(it, permission)
+                    }
+                val shouldGrantByProtectionFlags = requestingPackageStates.anyIndexed { _, it ->
+                    shouldGrantPermissionByProtectionFlags(it, permission)
+                }
+                if (mayGrantByPrivileged &&
+                    (shouldGrantBySignature || shouldGrantByProtectionFlags)) {
+                    PermissionFlags.PROTECTION_GRANTED
+                } else {
+                    0
+                }
+            }
+            if (permission.isAppOp) {
+                newFlags = newFlags or (
+                    oldFlags and (PermissionFlags.ROLE or PermissionFlags.USER_SET)
+                )
+            }
+            // Different from the old implementation, which seemingly allows granting an
+            // unallowlisted privileged permission via development or role but revokes it upon next
+            // reconciliation, we now properly allows that because the privileged protection flag
+            // should only affect the other static flags, but not dynamic flags like development or
+            // role. This may be useful in the case of an updated system app.
+            if (permission.isDevelopment) {
+                newFlags = newFlags or (oldFlags and PermissionFlags.RUNTIME_GRANTED)
+            }
+            if (permission.isRole) {
+                newFlags = newFlags or (
+                    oldFlags and (PermissionFlags.ROLE or PermissionFlags.RUNTIME_GRANTED)
+                )
+            }
+            setPermissionFlags(appId, userId, permissionName, newFlags)
+        } else if (permission.isRuntime) {
+            var newFlags = oldFlags and PermissionFlags.MASK_RUNTIME
+            val wasRevoked = newFlags != 0 && !PermissionFlags.isPermissionGranted(newFlags)
+            val targetSdkVersion =
+                requestingPackageStates.reduceIndexed(Build.VERSION_CODES.CUR_DEVELOPMENT) {
+                    targetSdkVersion, _, packageState ->
+                    targetSdkVersion.coerceAtMost(packageState.androidPackage!!.targetSdkVersion)
+                }
+            if (targetSdkVersion < Build.VERSION_CODES.M) {
+                if (permission.isRuntimeOnly) {
+                    // Different from the old implementation, which simply skips a runtime-only
+                    // permission, we now only allow holding on to the restriction related flags,
+                    // since such flags may only be set one-time in some cases, and disallow all
+                    // other flags thus keeping it revoked.
+                    newFlags = newFlags and PermissionFlags.MASK_EXEMPT
+                } else {
+                    newFlags = newFlags or PermissionFlags.LEGACY_GRANTED
+                    if (wasRevoked) {
+                        newFlags = newFlags or PermissionFlags.APP_OP_REVOKED
+                    }
+                    // Explicitly check against the old state to determine if this permission is
+                    // new.
+                    val isNewPermission =
+                        getOldStatePermissionFlags(appId, userId, permissionName) == 0
+                    if (isNewPermission) {
+                        newFlags = newFlags or PermissionFlags.IMPLICIT
+                    }
+                }
+            } else {
+                val wasGrantedByLegacy = newFlags.hasBits(PermissionFlags.LEGACY_GRANTED)
+                val hasImplicitFlag = newFlags.hasBits(PermissionFlags.IMPLICIT)
+                if (wasGrantedByLegacy) {
+                    newFlags = newFlags andInv PermissionFlags.LEGACY_GRANTED
+                    if (!hasImplicitFlag) {
+                        newFlags = newFlags or PermissionFlags.RUNTIME_GRANTED
+                    }
+                }
+                val wasGrantedByImplicit = newFlags.hasBits(PermissionFlags.IMPLICIT_GRANTED)
+                val isLeanbackNotificationsPermission = newState.externalState.isLeanback &&
+                    permissionName in NOTIFICATIONS_PERMISSIONS
+                val isImplicitPermission = requestingPackageStates.anyIndexed { _, it ->
+                    permissionName in it.androidPackage!!.implicitPermissions
+                }
+                val sourcePermissions = newState.externalState
+                    .implicitToSourcePermissions[permissionName]
+                val isAnySourcePermissionNonRuntime = sourcePermissions?.anyIndexed {
+                    _, sourcePermissionName ->
+                    val sourcePermission = newState.systemState.permissions[sourcePermissionName]
+                    checkNotNull(sourcePermission) {
+                        "Unknown source permission $sourcePermissionName in split permissions"
+                    }
+                    !sourcePermission.isRuntime
+                } ?: false
+                val shouldGrantByImplicit = isLeanbackNotificationsPermission ||
+                    (isImplicitPermission && isAnySourcePermissionNonRuntime)
+                if (shouldGrantByImplicit) {
+                    newFlags = newFlags or PermissionFlags.IMPLICIT_GRANTED
+                    if (wasRevoked) {
+                        newFlags = newFlags or PermissionFlags.APP_OP_REVOKED
+                    }
+                } else {
+                    newFlags = newFlags andInv PermissionFlags.IMPLICIT_GRANTED
+                    if ((wasGrantedByLegacy || wasGrantedByImplicit) &&
+                        newFlags.hasBits(PermissionFlags.APP_OP_REVOKED)) {
+                        // The permission was granted from a compatibility grant or an implicit
+                        // grant, however this flag might still be set if the user denied this
+                        // permission in the settings. Hence upon app upgrade and when this
+                        // permission is no longer LEGACY_GRANTED or IMPLICIT_GRANTED and we revoke
+                        // the permission, we want to remove this flag so that the app can request
+                        // the permission again.
+                        newFlags = newFlags andInv (
+                            PermissionFlags.RUNTIME_GRANTED or PermissionFlags.APP_OP_REVOKED
+                        )
+                    }
+                }
+                if (!isImplicitPermission && hasImplicitFlag) {
+                    newFlags = newFlags andInv PermissionFlags.IMPLICIT
+                    var shouldRetainAsNearbyDevices = false
+                    if (permissionName in NEARBY_DEVICES_PERMISSIONS) {
+                        val accessBackgroundLocationFlags = getPermissionFlags(
+                            appId, userId, Manifest.permission.ACCESS_BACKGROUND_LOCATION
+                        )
+                        shouldRetainAsNearbyDevices =
+                            PermissionFlags.isAppOpGranted(accessBackgroundLocationFlags) &&
+                                !accessBackgroundLocationFlags.hasBits(PermissionFlags.IMPLICIT)
+                    }
+                    val shouldRetainByMask = newFlags.hasAnyBit(SYSTEM_OR_POLICY_FIXED_MASK)
+                    if (shouldRetainAsNearbyDevices || shouldRetainByMask) {
+                        if (wasGrantedByImplicit) {
+                            newFlags = newFlags or PermissionFlags.RUNTIME_GRANTED
+                        }
+                    } else {
+                        newFlags = newFlags andInv (
+                            PermissionFlags.RUNTIME_GRANTED or PermissionFlags.USER_SET or
+                                PermissionFlags.USER_FIXED
+                        )
+                    }
+                }
+            }
+
+            val wasExempt = newFlags.hasAnyBit(PermissionFlags.MASK_EXEMPT)
+            val wasRestricted = newFlags.hasAnyBit(PermissionFlags.MASK_RESTRICTED)
+            val isExempt = if (permission.isHardOrSoftRestricted && !wasExempt && !wasRestricted) {
+                // All restricted permissions start as exempt. If there's an installer for the
+                // package, we will drop this UPGRADE_EXEMPT flag when we receive the
+                // onPackageInstalled() callback and set up the INSTALLER_EXEMPT flags.
+                // UPGRADE_EXEMPT is chosen instead of other flags because it is the same flag that
+                // was assigned to pre-installed apps in RuntimePermissionsUpgradeController, and to
+                // apps with missing permission state.
+                // This way we make sure both pre-installed apps, and apps updated/installed after
+                // a rollback snapshot is taken, can get the allowlist for permissions that won't be
+                // allowlisted otherwise.
+                newFlags = newFlags or PermissionFlags.UPGRADE_EXEMPT
+                true
+            } else {
+                wasExempt
+            }
+            newFlags = if (permission.isHardRestricted && !isExempt) {
+                newFlags or PermissionFlags.RESTRICTION_REVOKED
+            } else {
+                newFlags andInv PermissionFlags.RESTRICTION_REVOKED
+            }
+            newFlags = if (permission.isSoftRestricted && !isExempt) {
+                newFlags or PermissionFlags.SOFT_RESTRICTED
+            } else {
+                newFlags andInv PermissionFlags.SOFT_RESTRICTED
+            }
+            setPermissionFlags(appId, userId, permissionName, newFlags)
+        } else {
+            Slog.e(LOG_TAG, "Unknown protection level ${permission.protectionLevel}" +
+                "for permission ${permission.name} while evaluating permission state" +
+                "for appId $appId and userId $userId")
+        }
+    }
+
+    private fun MutateStateScope.inheritImplicitPermissionStates(appId: Int, userId: Int) {
+        val implicitPermissions = MutableIndexedSet<String>()
+        forEachPackageInAppId(appId) {
+            implicitPermissions += it.androidPackage!!.implicitPermissions
+        }
+        implicitPermissions.forEachIndexed implicitPermissions@ { _, implicitPermissionName ->
+            val implicitPermission = newState.systemState.permissions[implicitPermissionName]
+            checkNotNull(implicitPermission) {
+                "Unknown implicit permission $implicitPermissionName in split permissions"
+            }
+            if (!implicitPermission.isRuntime) {
+                return@implicitPermissions
+            }
+            // Explicitly check against the old state to determine if this permission is new.
+            val isNewPermission =
+                getOldStatePermissionFlags(appId, userId, implicitPermissionName) == 0
+            if (!isNewPermission) {
+                return@implicitPermissions
+            }
+            val sourcePermissions = newState.externalState
+                .implicitToSourcePermissions[implicitPermissionName] ?: return@implicitPermissions
+            var newFlags = getPermissionFlags(appId, userId, implicitPermissionName)
+            sourcePermissions.forEachIndexed sourcePermissions@ { _, sourcePermissionName ->
+                val sourcePermission = newState.systemState.permissions[sourcePermissionName]
+                checkNotNull(sourcePermission) {
+                    "Unknown source permission $sourcePermissionName in split permissions"
+                }
+                val sourceFlags = getPermissionFlags(appId, userId, sourcePermissionName)
+                val isSourceGranted = PermissionFlags.isPermissionGranted(sourceFlags)
+                val isNewGranted = PermissionFlags.isPermissionGranted(newFlags)
+                val isGrantingNewFromRevoke = isSourceGranted && !isNewGranted
+                if (isSourceGranted == isNewGranted || isGrantingNewFromRevoke) {
+                    if (isGrantingNewFromRevoke) {
+                        newFlags = 0
+                    }
+                    newFlags = newFlags or (sourceFlags and PermissionFlags.MASK_RUNTIME)
+                }
+            }
+            if (implicitPermissionName in RETAIN_IMPLICIT_FLAGS_PERMISSIONS) {
+                newFlags = newFlags andInv PermissionFlags.IMPLICIT
+            } else {
+                newFlags = newFlags or PermissionFlags.IMPLICIT
+            }
+            setPermissionFlags(appId, userId, implicitPermissionName, newFlags)
+        }
+    }
+
+    private fun isCompatibilityPermissionForPackage(
+        androidPackage: AndroidPackage,
+        permissionName: String
+    ): Boolean {
+        for (compatibilityPermission in CompatibilityPermissionInfo.COMPAT_PERMS) {
+            if (compatibilityPermission.name == permissionName &&
+                androidPackage.targetSdkVersion < compatibilityPermission.sdkVersion) {
+                Slog.i(
+                    LOG_TAG, "Auto-granting $permissionName to old package" +
+                    " ${androidPackage.packageName}"
+                )
+                return true
+            }
+        }
+        return false
+    }
+
+    private fun MutateStateScope.shouldGrantPermissionBySignature(
+        packageState: PackageState,
+        permission: Permission
+    ): Boolean {
+        // Check if the package is allowed to use this signature permission.  A package is allowed
+        // to use a signature permission if:
+        // - it has the same set of signing certificates as the source package
+        // - or its signing certificate was rotated from the source package's certificate
+        // - or its signing certificate is a previous signing certificate of the defining
+        //     package, and the defining package still trusts the old certificate for permissions
+        // - or it shares a common signing certificate in its lineage with the defining package,
+        //     and the defining package still trusts the old certificate for permissions
+        // - or it shares the above relationships with the system package
+        val packageSigningDetails = packageState.androidPackage!!.signingDetails
+        val sourceSigningDetails = newState.externalState
+            .packageStates[permission.packageName]?.androidPackage?.signingDetails
+        val platformSigningDetails = newState.externalState
+            .packageStates[PLATFORM_PACKAGE_NAME]!!.androidPackage!!.signingDetails
+        return sourceSigningDetails?.hasCommonSignerWithCapability(packageSigningDetails,
+            SigningDetails.CertCapabilities.PERMISSION) == true ||
+            packageSigningDetails.hasAncestorOrSelf(platformSigningDetails) ||
+            platformSigningDetails.checkCapability(packageSigningDetails,
+                    SigningDetails.CertCapabilities.PERMISSION)
+    }
+
+    private fun MutateStateScope.checkPrivilegedPermissionAllowlist(
+        packageState: PackageState,
+        permission: Permission
+    ): Boolean {
+        if (RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_DISABLE) {
+            return true
+        }
+        if (packageState.packageName == PLATFORM_PACKAGE_NAME) {
+            return true
+        }
+        if (!(packageState.isSystem && packageState.isPrivileged)) {
+            return true
+        }
+        if (permission.packageName !in
+            newState.externalState.privilegedPermissionAllowlistPackages) {
+            return true
+        }
+        val allowlistState = getPrivilegedPermissionAllowlistState(packageState, permission.name)
+        if (allowlistState != null) {
+            return allowlistState
+        }
+        // Updated system apps do not need to be allowlisted
+        if (packageState.isUpdatedSystemApp) {
+            return true
+        }
+        // Only enforce the privileged permission allowlist on boot
+        if (!newState.externalState.isSystemReady) {
+            // Apps that are in updated apex's do not need to be allowlisted
+            if (!packageState.isApkInUpdatedApex) {
+                Slog.w(
+                    LOG_TAG, "Privileged permission ${permission.name} for package" +
+                    " ${packageState.packageName} (${packageState.path}) not in" +
+                    " privileged permission allowlist"
+                )
+                if (RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_ENFORCE) {
+                    privilegedPermissionAllowlistViolations += "${packageState.packageName}" +
+                        " (${packageState.path}): ${permission.name}"
+                }
+            }
+        }
+        return !RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_ENFORCE
+    }
+
+    /**
+     * Get the whether a privileged permission is explicitly allowed or denied for a package in the
+     * allowlist, or `null` if it's not in the allowlist.
+     */
+    private fun MutateStateScope.getPrivilegedPermissionAllowlistState(
+        packageState: PackageState,
+        permissionName: String
+    ): Boolean? {
+        val permissionAllowlist = newState.externalState.permissionAllowlist
+        val apexModuleName = packageState.apexModuleName
+        val packageName = packageState.packageName
+        return when {
+            packageState.isVendor -> permissionAllowlist.getVendorPrivilegedAppAllowlistState(
+                packageName, permissionName
+            )
+            packageState.isProduct -> permissionAllowlist.getProductPrivilegedAppAllowlistState(
+                packageName, permissionName
+            )
+            packageState.isSystemExt ->
+                permissionAllowlist.getSystemExtPrivilegedAppAllowlistState(
+                    packageName, permissionName
+                )
+            apexModuleName != null -> {
+                val nonApexAllowlistState = permissionAllowlist.getPrivilegedAppAllowlistState(
+                    packageName, permissionName
+                )
+                if (nonApexAllowlistState != null) {
+                    // TODO(andreionea): Remove check as soon as all apk-in-apex
+                    // permission allowlists are migrated.
+                    Slog.w(
+                        LOG_TAG, "Package $packageName is an APK in APEX but has permission" +
+                            " allowlist on the system image, please bundle the allowlist in the" +
+                            " $apexModuleName APEX instead"
+                    )
+                }
+                val apexAllowlistState = permissionAllowlist.getApexPrivilegedAppAllowlistState(
+                    apexModuleName, packageName, permissionName
+                )
+                apexAllowlistState ?: nonApexAllowlistState
+            }
+            else -> permissionAllowlist.getPrivilegedAppAllowlistState(packageName, permissionName)
+        }
+    }
+
+    private inline fun MutateStateScope.anyPackageInAppId(
+        appId: Int,
+        state: AccessState = newState,
+        predicate: (PackageState) -> Boolean
+    ): Boolean {
+        val packageNames = state.externalState.appIdPackageNames[appId]!!
+        return packageNames.anyIndexed { _, packageName ->
+            val packageState = state.externalState.packageStates[packageName]!!
+            packageState.androidPackage != null && predicate(packageState)
+        }
+    }
+
+    private inline fun MutateStateScope.forEachPackageInAppId(
+        appId: Int,
+        state: AccessState = newState,
+        action: (PackageState) -> Unit
+    ) {
+        val packageNames = state.externalState.appIdPackageNames[appId]!!
+        packageNames.forEachIndexed { _, packageName ->
+            val packageState = state.externalState.packageStates[packageName]!!
+            if (packageState.androidPackage != null) {
+                action(packageState)
+            }
+        }
+    }
+
+    // Using Int instead of <T> to avoid autoboxing, since we only have the use case for Int.
+    private inline fun MutateStateScope.reducePackageInAppId(
+        appId: Int,
+        initialValue: Int,
+        state: AccessState = newState,
+        accumulator: (Int, PackageState) -> Int
+    ): Int {
+        val packageNames = state.externalState.appIdPackageNames[appId]!!
+        return packageNames.reduceIndexed(initialValue) { value, _, packageName ->
+            val packageState = state.externalState.packageStates[packageName]!!
+            if (packageState.androidPackage != null) {
+                accumulator(value, packageState)
+            } else {
+                value
+            }
+        }
+    }
+
+    private fun MutateStateScope.shouldGrantPermissionByProtectionFlags(
+        packageState: PackageState,
+        permission: Permission
+    ): Boolean {
+        val androidPackage = packageState.androidPackage!!
+        val knownPackages = newState.externalState.knownPackages
+        val packageName = packageState.packageName
+        if ((permission.isPrivileged || permission.isOem) && packageState.isSystem) {
+            val shouldGrant = if (packageState.isUpdatedSystemApp) {
+                // For updated system applications, a privileged/oem permission
+                // is granted only if it had been defined by the original application.
+                val disabledSystemPackageState = newState.externalState
+                    .disabledSystemPackageStates[packageState.packageName]
+                val disabledSystemPackage = disabledSystemPackageState?.androidPackage
+                disabledSystemPackage != null &&
+                    permission.name in disabledSystemPackage.requestedPermissions &&
+                    shouldGrantPrivilegedOrOemPermission(disabledSystemPackageState, permission)
+            } else {
+                shouldGrantPrivilegedOrOemPermission(packageState, permission)
+            }
+            if (shouldGrant) {
+                return true
+            }
+        }
+        if (permission.isPre23 && androidPackage.targetSdkVersion < Build.VERSION_CODES.M) {
+            // If this was a previously normal/dangerous permission that got moved
+            // to a system permission as part of the runtime permission redesign, then
+            // we still want to blindly grant it to old apps.
+            return true
+        }
+        if (permission.isInstaller && (
+            packageName in knownPackages[KnownPackages.PACKAGE_INSTALLER]!! ||
+                packageName in knownPackages[KnownPackages.PACKAGE_PERMISSION_CONTROLLER]!!
+        )) {
+            // If this permission is to be granted to the system installer and
+            // this app is an installer or permission controller, then it gets the permission.
+            return true
+        }
+        if (permission.isVerifier &&
+            packageName in knownPackages[KnownPackages.PACKAGE_VERIFIER]!!) {
+            // If this permission is to be granted to the system verifier and
+            // this app is a verifier, then it gets the permission.
+            return true
+        }
+        if (permission.isPreInstalled && packageState.isSystem) {
+            // Any pre-installed system app is allowed to get this permission.
+            return true
+        }
+        if (permission.isKnownSigner &&
+            androidPackage.signingDetails.hasAncestorOrSelfWithDigest(permission.knownCerts)) {
+            // If the permission is to be granted to a known signer then check if any of this
+            // app's signing certificates are in the trusted certificate digest Set.
+            return true
+        }
+        if (permission.isSetup &&
+            packageName in knownPackages[KnownPackages.PACKAGE_SETUP_WIZARD]!!) {
+            // If this permission is to be granted to the system setup wizard and
+            // this app is a setup wizard, then it gets the permission.
+            return true
+        }
+        if (permission.isSystemTextClassifier &&
+            packageName in knownPackages[KnownPackages.PACKAGE_SYSTEM_TEXT_CLASSIFIER]!!) {
+            // Special permissions for the system default text classifier.
+            return true
+        }
+        if (permission.isConfigurator &&
+            packageName in knownPackages[KnownPackages.PACKAGE_CONFIGURATOR]!!) {
+            // Special permissions for the device configurator.
+            return true
+        }
+        if (permission.isIncidentReportApprover &&
+            packageName in knownPackages[KnownPackages.PACKAGE_INCIDENT_REPORT_APPROVER]!!) {
+            // If this permission is to be granted to the incident report approver and
+            // this app is the incident report approver, then it gets the permission.
+            return true
+        }
+        if (permission.isAppPredictor &&
+            packageName in knownPackages[KnownPackages.PACKAGE_APP_PREDICTOR]!!) {
+            // Special permissions for the system app predictor.
+            return true
+        }
+        if (permission.isCompanion &&
+            packageName in knownPackages[KnownPackages.PACKAGE_COMPANION]!!) {
+            // Special permissions for the system companion device manager.
+            return true
+        }
+        if (permission.isRetailDemo &&
+            packageName in knownPackages[KnownPackages.PACKAGE_RETAIL_DEMO]!!) {
+            // Special permission granted only to the OEM specified retail demo app.
+            // Note that the original code was passing app ID as UID, so this behavior is kept
+            // unchanged.
+            return true
+        }
+        if (permission.isRecents &&
+            packageName in knownPackages[KnownPackages.PACKAGE_RECENTS]!!) {
+            // Special permission for the recents app.
+            return true
+        }
+        if (permission.isModule && packageState.apexModuleName != null) {
+            // Special permission granted for APKs inside APEX modules.
+            return true
+        }
+        return false
+    }
+
+    private fun MutateStateScope.shouldGrantPrivilegedOrOemPermission(
+        packageState: PackageState,
+        permission: Permission
+    ): Boolean {
+        val permissionName = permission.name
+        val packageName = packageState.packageName
+        when {
+            permission.isPrivileged -> {
+                if (packageState.isPrivileged) {
+                    // In any case, don't grant a privileged permission to privileged vendor apps,
+                    // if the permission's protectionLevel does not have the extra vendorPrivileged
+                    // flag.
+                    if (packageState.isVendor && !permission.isVendorPrivileged) {
+                        Slog.w(
+                            LOG_TAG, "Permission $permissionName cannot be granted to privileged" +
+                            " vendor app $packageName because it isn't a vendorPrivileged" +
+                            " permission"
+                        )
+                        return false
+                    }
+                    return true
+                }
+            }
+            permission.isOem -> {
+                if (packageState.isOem) {
+                    val allowlistState = newState.externalState.permissionAllowlist
+                        .getOemAppAllowlistState(packageName, permissionName)
+                    checkNotNull(allowlistState) {
+                        "OEM permission $permissionName requested by package" +
+                            " $packageName must be explicitly declared granted or not"
+                    }
+                    return allowlistState
+                }
+            }
+        }
+        return false
+    }
+
+    override fun MutateStateScope.onSystemReady() {
+        // HACK: PACKAGE_USAGE_STATS is the only permission with the retailDemo protection flag,
+        // and we have to wait until DevicePolicyManagerService is started to know whether the
+        // retail demo package is a profile owner so that it can have the permission.
+        // Since there's no simple callback for profile owner change, and we are deprecating and
+        // removing the retailDemo protection flag in favor of a proper role soon, we can just
+        // re-evaluate the permission here, which is also how the old implementation has been
+        // working.
+        // TODO: Partially revert ag/22690114 once we can remove support for the retailDemo
+        //  protection flag.
+        val externalState = newState.externalState
+        for (packageName in externalState.knownPackages[KnownPackages.PACKAGE_RETAIL_DEMO]!!) {
+            val appId = externalState.packageStates[packageName]?.appId ?: continue
+            newState.userStates.forEachIndexed { _, userId, _ ->
+                evaluatePermissionState(
+                    appId, userId, Manifest.permission.PACKAGE_USAGE_STATS, null
+                )
+            }
+        }
+        if (!privilegedPermissionAllowlistViolations.isEmpty()) {
+            throw IllegalStateException("Signature|privileged permissions not in privileged" +
+                " permission allowlist: $privilegedPermissionAllowlistViolations")
+        }
+    }
+
+    override fun BinaryXmlPullParser.parseSystemState(state: MutableAccessState) {
+        with(persistence) { this@parseSystemState.parseSystemState(state) }
+    }
+
+    override fun BinaryXmlSerializer.serializeSystemState(state: AccessState) {
+        with(persistence) { this@serializeSystemState.serializeSystemState(state) }
+    }
+
+    override fun BinaryXmlPullParser.parseUserState(state: MutableAccessState, userId: Int) {
+        with(persistence) { this@parseUserState.parseUserState(state, userId) }
+    }
+
+    override fun BinaryXmlSerializer.serializeUserState(state: AccessState, userId: Int) {
+        with(persistence) { this@serializeUserState.serializeUserState(state, userId) }
+    }
+
+    fun GetStateScope.getPermissionTrees(): IndexedMap<String, Permission> =
+        state.systemState.permissionTrees
+
+    fun GetStateScope.findPermissionTree(permissionName: String): Permission? =
+        state.systemState.permissionTrees.firstNotNullOfOrNullIndexed {
+                _, permissionTreeName, permissionTree ->
+            if (permissionName.startsWith(permissionTreeName) &&
+                permissionName.length > permissionTreeName.length &&
+                permissionName[permissionTreeName.length] == '.') {
+                permissionTree
+            } else {
+                null
+            }
+        }
+
+    fun MutateStateScope.addPermissionTree(permission: Permission) {
+        newState.mutateSystemState().mutatePermissionTrees()[permission.name] = permission
+    }
+
+    /**
+     * returns all permission group definitions available in the system
+     */
+    fun GetStateScope.getPermissionGroups(): IndexedMap<String, PermissionGroupInfo> =
+        state.systemState.permissionGroups
+
+    /**
+     * returns all permission definitions available in the system
+     */
+    fun GetStateScope.getPermissions(): IndexedMap<String, Permission> =
+        state.systemState.permissions
+
+    fun MutateStateScope.addPermission(
+        permission: Permission,
+        isSynchronousWrite: Boolean = false
+    ) {
+        val writeMode = if (isSynchronousWrite) WriteMode.SYNCHRONOUS else WriteMode.ASYNCHRONOUS
+        newState.mutateSystemState(writeMode).mutatePermissions()[permission.name] = permission
+    }
+
+    fun MutateStateScope.removePermission(permission: Permission) {
+        newState.mutateSystemState().mutatePermissions() -= permission.name
+    }
+
+    fun GetStateScope.getUidPermissionFlags(appId: Int, userId: Int): IndexedMap<String, Int>? =
+        state.userStates[userId]?.appIdPermissionFlags?.get(appId)
+
+    fun GetStateScope.getPermissionFlags(
+        appId: Int,
+        userId: Int,
+        permissionName: String
+    ): Int = getPermissionFlags(state, appId, userId, permissionName)
+
+    private fun MutateStateScope.getOldStatePermissionFlags(
+        appId: Int,
+        userId: Int,
+        permissionName: String
+    ): Int = getPermissionFlags(oldState, appId, userId, permissionName)
+
+    private fun getPermissionFlags(
+        state: AccessState,
+        appId: Int,
+        userId: Int,
+        permissionName: String
+    ): Int =
+        state.userStates[userId]?.appIdPermissionFlags?.get(appId).getWithDefault(permissionName, 0)
+
+    fun MutateStateScope.setPermissionFlags(
+        appId: Int,
+        userId: Int,
+        permissionName: String,
+        flags: Int
+    ): Boolean =
+        updatePermissionFlags(appId, userId, permissionName, PermissionFlags.MASK_ALL, flags)
+
+    fun MutateStateScope.updatePermissionFlags(
+        appId: Int,
+        userId: Int,
+        permissionName: String,
+        flagMask: Int,
+        flagValues: Int
+    ): Boolean {
+        val oldFlags = newState.userStates[userId]!!.appIdPermissionFlags[appId]
+            .getWithDefault(permissionName, 0)
+        val newFlags = (oldFlags andInv flagMask) or (flagValues and flagMask)
+        if (oldFlags == newFlags) {
+            return false
+        }
+        val appIdPermissionFlags = newState.mutateUserState(userId)!!.mutateAppIdPermissionFlags()
+        val permissionFlags = appIdPermissionFlags.mutateOrPut(appId) { MutableIndexedMap() }
+        permissionFlags.putWithDefault(permissionName, newFlags, 0)
+        if (permissionFlags.isEmpty()) {
+            appIdPermissionFlags -= appId
+        }
+        onPermissionFlagsChangedListeners.forEachIndexed { _, it ->
+            it.onPermissionFlagsChanged(appId, userId, permissionName, oldFlags, newFlags)
+        }
+        return true
+    }
+
+    fun addOnPermissionFlagsChangedListener(listener: OnPermissionFlagsChangedListener) {
+        synchronized(onPermissionFlagsChangedListenersLock) {
+            onPermissionFlagsChangedListeners = onPermissionFlagsChangedListeners + listener
+        }
+    }
+
+    fun removeOnPermissionFlagsChangedListener(listener: OnPermissionFlagsChangedListener) {
+        synchronized(onPermissionFlagsChangedListenersLock) {
+            onPermissionFlagsChangedListeners = onPermissionFlagsChangedListeners - listener
+        }
+    }
+
+    override fun migrateSystemState(state: MutableAccessState) {
+        migration.migrateSystemState(state)
+    }
+
+    override fun migrateUserState(state: MutableAccessState, userId: Int) {
+        migration.migrateUserState(state, userId)
+    }
+
+    override fun MutateStateScope.upgradePackageState(
+        packageState: PackageState,
+        userId: Int,
+        version: Int
+    ) {
+        with(upgrade) { upgradePackageState(packageState, userId, version) }
+    }
+
+    companion object {
+        private val LOG_TAG = AppIdPermissionPolicy::class.java.simpleName
+
+        private const val PLATFORM_PACKAGE_NAME = "android"
+
+        // A set of permissions that we don't want to revoke when they are no longer implicit.
+        private val RETAIN_IMPLICIT_FLAGS_PERMISSIONS = indexedSetOf(
+            Manifest.permission.ACCESS_MEDIA_LOCATION,
+            Manifest.permission.ACTIVITY_RECOGNITION,
+            Manifest.permission.READ_MEDIA_AUDIO,
+            Manifest.permission.READ_MEDIA_IMAGES,
+            Manifest.permission.READ_MEDIA_VIDEO,
+        )
+
+        private val NEARBY_DEVICES_PERMISSIONS = indexedSetOf(
+            Manifest.permission.BLUETOOTH_ADVERTISE,
+            Manifest.permission.BLUETOOTH_CONNECT,
+            Manifest.permission.BLUETOOTH_SCAN,
+            Manifest.permission.NEARBY_WIFI_DEVICES
+        )
+
+        private val NOTIFICATIONS_PERMISSIONS = indexedSetOf(
+            Manifest.permission.POST_NOTIFICATIONS
+        )
+
+        private val STORAGE_AND_MEDIA_PERMISSIONS = indexedSetOf(
+            Manifest.permission.READ_EXTERNAL_STORAGE,
+            Manifest.permission.WRITE_EXTERNAL_STORAGE,
+            Manifest.permission.READ_MEDIA_AUDIO,
+            Manifest.permission.READ_MEDIA_VIDEO,
+            Manifest.permission.READ_MEDIA_IMAGES,
+            Manifest.permission.ACCESS_MEDIA_LOCATION,
+            Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED
+        )
+
+        /**
+         * Mask for all permission flags that can be set by the user
+         */
+        private const val USER_SETTABLE_MASK =
+            PermissionFlags.USER_SET or
+                PermissionFlags.USER_FIXED or
+                PermissionFlags.APP_OP_REVOKED or
+                PermissionFlags.ONE_TIME or
+                PermissionFlags.HIBERNATION or
+                PermissionFlags.USER_SELECTED
+
+        /**
+         * Mask for all permission flags that imply we shouldn't automatically modify the
+         * permission grant state.
+         */
+        private const val SYSTEM_OR_POLICY_FIXED_MASK =
+            PermissionFlags.SYSTEM_FIXED or PermissionFlags.POLICY_FIXED
+    }
+
+    /**
+     * Listener for permission flags changes.
+     */
+    abstract class OnPermissionFlagsChangedListener {
+        /**
+         * Called when a permission flags change has been made to the upcoming new state.
+         *
+         * Implementations should keep this method fast to avoid stalling the locked state mutation,
+         * and only call external code after [onStateMutated] when the new state has actually become
+         * the current state visible to external code.
+         */
+        abstract fun onPermissionFlagsChanged(
+            appId: Int,
+            userId: Int,
+            permissionName: String,
+            oldFlags: Int,
+            newFlags: Int
+        )
+
+        /**
+         * Called when the upcoming new state has become the current state.
+         *
+         * Implementations should keep this method fast to avoid stalling the locked state mutation.
+         */
+        abstract fun onStateMutated()
+    }
+}
diff --git a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionUpgrade.kt b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionUpgrade.kt
new file mode 100644
index 0000000..b644d8f
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionUpgrade.kt
@@ -0,0 +1,272 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.permission.access.permission
+
+import android.Manifest
+import android.os.Build
+import android.util.Slog
+import com.android.server.permission.access.MutateStateScope
+import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
+import com.android.server.permission.access.immutable.* // ktlint-disable no-wildcard-imports
+import com.android.server.permission.access.util.andInv
+import com.android.server.permission.access.util.hasAnyBit
+import com.android.server.permission.access.util.hasBits
+import com.android.server.pm.pkg.PackageState
+
+class AppIdPermissionUpgrade(private val policy: AppIdPermissionPolicy) {
+    /**
+     * Upgrade the package permissions, if needed.
+     *
+     * @param version package version
+     *
+     * @see [com.android.server.permission.access.util.PackageVersionMigration.getVersion]
+     */
+    fun MutateStateScope.upgradePackageState(
+        packageState: PackageState,
+        userId: Int,
+        version: Int
+    ) {
+        val packageName = packageState.packageName
+        if (version <= 3) {
+            Slog.v(
+                LOG_TAG, "Allowlisting and upgrading background location permission for " +
+                    "package: $packageName, version: $version, user:$userId"
+            )
+            allowlistRestrictedPermissions(packageState, userId)
+            upgradeBackgroundLocationPermission(packageState, userId)
+        }
+        if (version <= 10) {
+            Slog.v(
+                LOG_TAG, "Upgrading access media location permission for package: $packageName" +
+                    ", version: $version, user: $userId"
+            )
+            upgradeAccessMediaLocationPermission(packageState, userId)
+        }
+        // TODO Enable isAtLeastT check, when moving subsystem to mainline.
+        if (version <= 12 /*&& SdkLevel.isAtLeastT()*/) {
+            Slog.v(
+                LOG_TAG, "Upgrading scoped permissions for package: $packageName" +
+                    ", version: $version, user: $userId"
+            )
+            upgradeAuralVisualMediaPermissions(packageState, userId)
+        }
+        // TODO Enable isAtLeastU check, when moving subsystem to mainline.
+        if (version <= 14 /*&& SdkLevel.isAtLeastU()*/) {
+            Slog.v(
+                LOG_TAG, "Upgrading visual media permission for package: $packageName" +
+                    ", version: $version, user: $userId"
+            )
+            upgradeUserSelectedVisualMediaPermission(packageState, userId)
+        }
+        // Add a new upgrade step: if (packageVersion <= LATEST_VERSION) { .... }
+        // Also increase LATEST_VERSION
+    }
+
+    private fun MutateStateScope.allowlistRestrictedPermissions(
+        packageState: PackageState,
+        userId: Int
+    ) {
+        packageState.androidPackage!!.requestedPermissions.forEach { permissionName ->
+            if (permissionName in LEGACY_RESTRICTED_PERMISSIONS) {
+                with(policy) {
+                    updatePermissionFlags(
+                        packageState.appId, userId, permissionName,
+                        PermissionFlags.UPGRADE_EXEMPT, PermissionFlags.UPGRADE_EXEMPT
+                    )
+                }
+            }
+        }
+    }
+
+    private fun MutateStateScope.upgradeBackgroundLocationPermission(
+        packageState: PackageState,
+        userId: Int
+    ) {
+        if (Manifest.permission.ACCESS_BACKGROUND_LOCATION in
+            packageState.androidPackage!!.requestedPermissions) {
+            val appId = packageState.appId
+            val accessFineLocationFlags = with(policy) {
+                getPermissionFlags(appId, userId, Manifest.permission.ACCESS_FINE_LOCATION)
+            }
+            val accessCoarseLocationFlags = with(policy) {
+                getPermissionFlags(appId, userId, Manifest.permission.ACCESS_COARSE_LOCATION)
+            }
+            val isForegroundLocationGranted =
+                PermissionFlags.isAppOpGranted(accessFineLocationFlags) ||
+                    PermissionFlags.isAppOpGranted(accessCoarseLocationFlags)
+            if (isForegroundLocationGranted) {
+                grantRuntimePermission(
+                    packageState, userId, Manifest.permission.ACCESS_BACKGROUND_LOCATION
+                )
+            }
+        }
+    }
+
+    private fun MutateStateScope.upgradeAccessMediaLocationPermission(
+        packageState: PackageState,
+        userId: Int
+    ) {
+        if (Manifest.permission.ACCESS_MEDIA_LOCATION in
+            packageState.androidPackage!!.requestedPermissions) {
+            val flags = with(policy) {
+                getPermissionFlags(
+                    packageState.appId, userId, Manifest.permission.READ_EXTERNAL_STORAGE
+                )
+            }
+            if (PermissionFlags.isAppOpGranted(flags)) {
+                grantRuntimePermission(
+                    packageState, userId, Manifest.permission.ACCESS_MEDIA_LOCATION
+                )
+            }
+        }
+    }
+
+    /**
+     * Upgrade permissions based on storage permissions grant
+     */
+    private fun MutateStateScope.upgradeAuralVisualMediaPermissions(
+        packageState: PackageState,
+        userId: Int
+    ) {
+        val androidPackage = packageState.androidPackage!!
+        if (androidPackage.targetSdkVersion < Build.VERSION_CODES.TIRAMISU) {
+            return
+        }
+        val requestedPermissionNames = androidPackage.requestedPermissions
+        val isStorageUserGranted = STORAGE_PERMISSIONS.anyIndexed { _, permissionName ->
+            if (permissionName !in requestedPermissionNames) {
+                return@anyIndexed false
+            }
+            val flags = with(policy) {
+                getPermissionFlags(packageState.appId, userId, permissionName)
+            }
+            PermissionFlags.isAppOpGranted(flags) && flags.hasBits(PermissionFlags.USER_SET)
+        }
+        if (isStorageUserGranted) {
+            AURAL_VISUAL_MEDIA_PERMISSIONS.forEachIndexed { _, permissionName ->
+                if (permissionName in requestedPermissionNames) {
+                    grantRuntimePermission(packageState, userId, permissionName)
+                }
+            }
+        }
+    }
+
+    /**
+     * Upgrade permission based on the grant in [Manifest.permission_group.READ_MEDIA_VISUAL]
+     */
+    private fun MutateStateScope.upgradeUserSelectedVisualMediaPermission(
+        packageState: PackageState,
+        userId: Int
+    ) {
+        val androidPackage = packageState.androidPackage!!
+        if (androidPackage.targetSdkVersion < Build.VERSION_CODES.TIRAMISU) {
+            return
+        }
+        val requestedPermissionNames = androidPackage.requestedPermissions
+        val isVisualMediaUserGranted = VISUAL_MEDIA_PERMISSIONS.anyIndexed { _, permissionName ->
+            if (permissionName !in requestedPermissionNames) {
+                return@anyIndexed false
+            }
+            val flags = with(policy) {
+                getPermissionFlags(packageState.appId, userId, permissionName)
+            }
+            PermissionFlags.isAppOpGranted(flags) && flags.hasBits(PermissionFlags.USER_SET)
+        }
+        if (isVisualMediaUserGranted) {
+            if (Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED in requestedPermissionNames) {
+                grantRuntimePermission(
+                    packageState, userId, Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED
+                )
+            }
+        }
+    }
+
+    private fun MutateStateScope.grantRuntimePermission(
+        packageState: PackageState,
+        userId: Int,
+        permissionName: String
+    ) {
+        Slog.v(
+            LOG_TAG, "Granting runtime permission for package: ${packageState.packageName}, " +
+                "permission: $permissionName, userId: $userId"
+        )
+        val permission = newState.systemState.permissions[permissionName]!!
+        if (packageState.getUserStateOrDefault(userId).isInstantApp && !permission.isInstant) {
+            return
+        }
+
+        val appId = packageState.appId
+        var flags = with(policy) { getPermissionFlags(appId, userId, permissionName) }
+        if (flags.hasAnyBit(MASK_ANY_FIXED)) {
+            Slog.v(
+                LOG_TAG,
+                "Not allowed to grant $permissionName to package ${packageState.packageName}"
+            )
+            return
+        }
+
+        flags = flags or PermissionFlags.RUNTIME_GRANTED
+        flags = flags andInv (
+            PermissionFlags.APP_OP_REVOKED or
+            PermissionFlags.IMPLICIT or
+            PermissionFlags.LEGACY_GRANTED or
+            PermissionFlags.HIBERNATION or
+            PermissionFlags.ONE_TIME
+        )
+        with(policy) { setPermissionFlags(appId, userId, permissionName, flags) }
+    }
+
+    companion object {
+        private val LOG_TAG = AppIdPermissionUpgrade::class.java.simpleName
+
+        private const val MASK_ANY_FIXED =
+            PermissionFlags.USER_SET or PermissionFlags.USER_FIXED or
+            PermissionFlags.POLICY_FIXED or PermissionFlags.SYSTEM_FIXED
+
+        private val LEGACY_RESTRICTED_PERMISSIONS = indexedSetOf(
+            Manifest.permission.ACCESS_BACKGROUND_LOCATION,
+            Manifest.permission.READ_EXTERNAL_STORAGE,
+            Manifest.permission.WRITE_EXTERNAL_STORAGE,
+            Manifest.permission.SEND_SMS,
+            Manifest.permission.RECEIVE_SMS,
+            Manifest.permission.RECEIVE_WAP_PUSH,
+            Manifest.permission.RECEIVE_MMS,
+            Manifest.permission.READ_CELL_BROADCASTS,
+            Manifest.permission.READ_CALL_LOG,
+            Manifest.permission.WRITE_CALL_LOG,
+            Manifest.permission.PROCESS_OUTGOING_CALLS
+        )
+
+        private val STORAGE_PERMISSIONS = indexedSetOf(
+            Manifest.permission.READ_EXTERNAL_STORAGE,
+            Manifest.permission.WRITE_EXTERNAL_STORAGE
+        )
+        private val AURAL_VISUAL_MEDIA_PERMISSIONS = indexedSetOf(
+            Manifest.permission.READ_MEDIA_AUDIO,
+            Manifest.permission.READ_MEDIA_IMAGES,
+            Manifest.permission.READ_MEDIA_VIDEO,
+            Manifest.permission.ACCESS_MEDIA_LOCATION,
+            Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED
+        )
+        // Visual media permissions in T
+        private val VISUAL_MEDIA_PERMISSIONS = indexedSetOf(
+            Manifest.permission.READ_MEDIA_IMAGES,
+            Manifest.permission.READ_MEDIA_VIDEO,
+            Manifest.permission.ACCESS_MEDIA_LOCATION
+        )
+    }
+}
diff --git a/services/permission/java/com/android/server/permission/access/permission/Permission.kt b/services/permission/java/com/android/server/permission/access/permission/Permission.kt
index 714480c..c7fe1a9 100644
--- a/services/permission/java/com/android/server/permission/access/permission/Permission.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/Permission.kt
@@ -140,9 +140,7 @@
         get() = permissionInfo.flags.hasBits(PermissionInfo.FLAG_SOFT_RESTRICTED)
 
     inline val isHardOrSoftRestricted: Boolean
-        get() = permissionInfo.flags.hasBits(
-            PermissionInfo.FLAG_HARD_RESTRICTED or PermissionInfo.FLAG_SOFT_RESTRICTED
-        )
+        get() = isHardRestricted || isSoftRestricted
 
     inline val isImmutablyRestricted: Boolean
         get() = permissionInfo.flags.hasBits(PermissionInfo.FLAG_IMMUTABLY_RESTRICTED)
@@ -170,5 +168,13 @@
         const val TYPE_CONFIG = 1
         // The permission is defined dynamically.
         const val TYPE_DYNAMIC = 2
+
+        fun typeToString(type: Int): String =
+            when (type) {
+                TYPE_MANIFEST -> "TYPE_MANIFEST"
+                TYPE_CONFIG -> "TYPE_CONFIG"
+                TYPE_DYNAMIC -> "TYPE_DYNAMIC"
+                else -> type.toString()
+            }
     }
 }
diff --git a/services/permission/java/com/android/server/permission/access/permission/PermissionFlags.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionFlags.kt
index 48658ff..550d148 100644
--- a/services/permission/java/com/android/server/permission/access/permission/PermissionFlags.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/PermissionFlags.kt
@@ -22,6 +22,7 @@
 import android.os.Build
 import android.permission.PermissionManager
 import com.android.server.permission.access.util.andInv
+import com.android.server.permission.access.util.flagsToString
 import com.android.server.permission.access.util.hasAnyBit
 import com.android.server.permission.access.util.hasBits
 
@@ -137,7 +138,7 @@
      * For example, this flag may be set in
      * [com.android.server.pm.permission.DefaultPermissionGrantPolicy].
      *
-     * @see PackageManager.FLAG_PERMISSION_SYSTEM_FIXED
+     * @see PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT
      */
     const val PREGRANT = 1 shl 9
 
@@ -317,6 +318,11 @@
      */
     const val MASK_EXEMPT = INSTALLER_EXEMPT or SYSTEM_EXEMPT or UPGRADE_EXEMPT
 
+    /**
+     * Mask for all permission flags about permission restriction.
+     */
+    const val MASK_RESTRICTED = RESTRICTION_REVOKED or SOFT_RESTRICTED
+
     fun isPermissionGranted(flags: Int): Boolean {
         if (flags.hasBits(INSTALL_GRANTED)) {
             return true
@@ -477,4 +483,38 @@
         }
         return flags
     }
+
+    fun flagToString(flag: Int): String =
+        when (flag) {
+            INSTALL_GRANTED -> "INSTALL_GRANTED"
+            INSTALL_REVOKED -> "INSTALL_REVOKED"
+            PROTECTION_GRANTED -> "PROTECTION_GRANTED"
+            ROLE -> "ROLE"
+            RUNTIME_GRANTED -> "RUNTIME_GRANTED"
+            USER_SET -> "USER_SET"
+            USER_FIXED -> "USER_FIXED"
+            POLICY_FIXED -> "POLICY_FIXED"
+            SYSTEM_FIXED -> "SYSTEM_FIXED"
+            PREGRANT -> "PREGRANT"
+            LEGACY_GRANTED -> "LEGACY_GRANTED"
+            IMPLICIT_GRANTED -> "IMPLICIT_GRANTED"
+            IMPLICIT -> "IMPLICIT"
+            USER_SENSITIVE_WHEN_GRANTED -> "USER_SENSITIVE_WHEN_GRANTED"
+            USER_SENSITIVE_WHEN_REVOKED -> "USER_SENSITIVE_WHEN_REVOKED"
+            INSTALLER_EXEMPT -> "INSTALLER_EXEMPT"
+            SYSTEM_EXEMPT -> "SYSTEM_EXEMPT"
+            UPGRADE_EXEMPT -> "UPGRADE_EXEMPT"
+            RESTRICTION_REVOKED -> "RESTRICTION_REVOKED"
+            SOFT_RESTRICTED -> "SOFT_RESTRICTED"
+            APP_OP_REVOKED -> "APP_OP_REVOKED"
+            ONE_TIME -> "ONE_TIME"
+            HIBERNATION -> "HIBERNATION"
+            USER_SELECTED -> "USER_SELECTED"
+            else -> "0x${flag.toUInt().toString(16).uppercase()}"
+        }
+
+    fun toString(flags: Int): String = flags.flagsToString { flagToString(it) }
+
+    fun apiFlagsToString(apiFlags: Int): String =
+        apiFlags.flagsToString { PackageManager.permissionFlagToString(it) }
 }
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 de7dc3b..edacf18 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
@@ -45,9 +45,14 @@
 import android.permission.PermissionControllerManager
 import android.permission.PermissionManager
 import android.provider.Settings
+import android.util.ArrayMap
+import android.util.ArraySet
 import android.util.DebugUtils
+import android.util.IndentingPrintWriter
 import android.util.IntArray as GrowingIntArray
-import android.util.Log
+import android.util.Slog
+import android.util.SparseBooleanArray
+import com.android.internal.annotations.GuardedBy
 import com.android.internal.compat.IPlatformCompat
 import com.android.internal.logging.MetricsLogger
 import com.android.internal.logging.nano.MetricsProto
@@ -60,13 +65,15 @@
 import com.android.server.ServiceThread
 import com.android.server.SystemConfig
 import com.android.server.permission.access.AccessCheckingService
+import com.android.server.permission.access.AccessState
 import com.android.server.permission.access.AppOpUri
 import com.android.server.permission.access.GetStateScope
 import com.android.server.permission.access.MutateStateScope
 import com.android.server.permission.access.PermissionUri
 import com.android.server.permission.access.UidUri
-import com.android.server.permission.access.appop.UidAppOpPolicy
+import com.android.server.permission.access.appop.AppIdAppOpPolicy
 import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
+import com.android.server.permission.access.immutable.* // ktlint-disable no-wildcard-imports
 import com.android.server.permission.access.util.andInv
 import com.android.server.permission.access.util.hasAnyBit
 import com.android.server.permission.access.util.hasBits
@@ -101,7 +108,7 @@
     private val service: AccessCheckingService
 ) : PermissionManagerServiceInterface {
     private val policy =
-        service.getSchemePolicy(UidUri.SCHEME, PermissionUri.SCHEME) as UidPermissionPolicy
+        service.getSchemePolicy(UidUri.SCHEME, PermissionUri.SCHEME) as AppIdPermissionPolicy
 
     private val context = service.context
     private lateinit var metricsLogger: MetricsLogger
@@ -117,7 +124,11 @@
     private lateinit var onPermissionsChangeListeners: OnPermissionsChangeListeners
     private lateinit var onPermissionFlagsChangedListener: OnPermissionFlagsChangedListener
 
-    private val mountedStorageVolumes = IndexedSet<String?>()
+    private val storageVolumeLock = Any()
+    @GuardedBy("storageVolumeLock")
+    private val mountedStorageVolumes = ArraySet<String?>()
+    @GuardedBy("storageVolumeLock")
+    private val storageVolumePackageNames = ArrayMap<String?, MutableList<String>>()
 
     private lateinit var permissionControllerManager: PermissionControllerManager
 
@@ -128,7 +139,7 @@
      * This array (`userId -> noDelayedBackupLeft`) is `true` for all the users where
      * there is **no more** delayed backup left.
      */
-    private val isDelayedPermissionBackupFinished = IntBooleanMap()
+    private val isDelayedPermissionBackupFinished = SparseBooleanArray()
 
     fun initialize() {
         metricsLogger = MetricsLogger()
@@ -142,6 +153,12 @@
         userManagerInternal = LocalServices.getService(UserManagerInternal::class.java)
         userManagerService = UserManagerService.getInstance()
 
+        // The package info cache is the cache for package and permission information.
+        // Disable the package info and package permission caches locally but leave the
+        // checkPermission cache active.
+        PackageManager.invalidatePackageInfoCache()
+        PermissionManager.disablePackageNamePermissionCache()
+
         handlerThread = ServiceThread(LOG_TAG, Process.THREAD_PRIORITY_BACKGROUND, true)
             .apply { start() }
         handler = Handler(handlerThread.looper)
@@ -161,7 +178,7 @@
                 with(policy) { getPermissionGroups() }
             }
 
-            return permissionGroups.mapNotNullIndexed { _, _, permissionGroup ->
+            return permissionGroups.mapNotNullIndexedTo(ArrayList()) { _, _, permissionGroup ->
                 if (snapshot.isPackageVisibleToUid(permissionGroup.packageName, callingUid)) {
                     permissionGroup.generatePermissionGroupInfo(flags)
                 } else {
@@ -229,7 +246,7 @@
             val opPackage = snapshot.getPackageState(opPackageName)?.androidPackage
             targetSdkVersion = when {
                 // System sees all flags.
-                isRootOrSystemOrShell(callingUid) -> Build.VERSION_CODES.CUR_DEVELOPMENT
+                isRootOrSystemOrShellUid(callingUid) -> Build.VERSION_CODES.CUR_DEVELOPMENT
                 opPackage != null -> opPackage.targetSdkVersion
                 else -> Build.VERSION_CODES.CUR_DEVELOPMENT
             }
@@ -272,8 +289,7 @@
                 return null
             }
 
-            val permissions: IndexedMap<String, Permission>
-            service.getState {
+            val permissions = service.getState {
                 if (permissionGroupName != null) {
                     val permissionGroup =
                         with(policy) { getPermissionGroups()[permissionGroupName] } ?: return null
@@ -283,10 +299,10 @@
                     }
                 }
 
-                permissions = with(policy) { getPermissions() }
+                with(policy) { getPermissions() }
             }
 
-            return permissions.mapNotNullIndexed { _, _, permission ->
+            return permissions.mapNotNullIndexedTo(ArrayList()) { _, _, permission ->
                 if (permission.groupName == permissionGroupName &&
                     snapshot.isPackageVisibleToUid(permission.packageName, callingUid)
                 ) {
@@ -311,15 +327,15 @@
     private inline fun getPermissionsWithProtectionOrProtectionFlags(
         predicate: (Permission) -> Boolean
     ): List<PermissionInfo> {
-        service.getState {
-            with(policy) {
-                return getPermissions().mapNotNullIndexed { _, _, permission ->
-                    if (predicate(permission)) {
-                        permission.generatePermissionInfo(0)
-                    } else {
-                        null
-                    }
-                }
+        val permissions = service.getState {
+            with(policy) { getPermissions() }
+        }
+
+        return permissions.mapNotNullIndexedTo(ArrayList()) { _, _, permission ->
+            if (predicate(permission)) {
+                permission.generatePermissionInfo(0)
+            } else {
+                null
             }
         }
     }
@@ -337,7 +353,8 @@
         val permissions = service.getState {
             with(policy) { getPermissions() }
         }
-        return permissions.mapNotNullIndexedToSet { _, _, permission ->
+
+        return permissions.mapNotNullIndexedTo(ArraySet()) { _, _, permission ->
             if (permission.packageName == packageName) {
                 permission.name
             } else {
@@ -434,7 +451,7 @@
     private fun GetStateScope.calculatePermissionTreeFootprint(permissionTree: Permission): Int {
         var size = 0
         with(policy) {
-            getPermissions().forEachValueIndexed { _, permission ->
+            getPermissions().forEachIndexed { _, _, permission ->
                 if (permissionTree.appId == permission.appId) {
                     size += permission.footprint
                 }
@@ -458,7 +475,7 @@
             val packageState =
                 packageManagerInternal.getPackageStateInternal(androidPackage.packageName)
             if (packageState == null) {
-                Log.e(
+                Slog.e(
                     LOG_TAG, "checkUidPermission: PackageState not found for AndroidPackage" +
                         " $androidPackage"
                 )
@@ -575,7 +592,7 @@
         val packageState = packageManagerLocal.withUnfilteredSnapshot()
             .use { it.getPackageState(packageName) }
         if (packageState == null) {
-            Log.w(LOG_TAG, "getGrantedPermissions: Unknown package $packageName")
+            Slog.w(LOG_TAG, "getGrantedPermissions: Unknown package $packageName")
             return emptySet()
         }
 
@@ -583,7 +600,7 @@
             val permissionFlags = with(policy) { getUidPermissionFlags(packageState.appId, userId) }
                 ?: return emptySet()
 
-            return permissionFlags.mapNotNullIndexedToSet { _, permissionName, _ ->
+            return permissionFlags.mapNotNullIndexedTo(ArraySet()) { _, permissionName, _ ->
                 if (isPermissionGranted(packageState, userId, permissionName)) {
                     permissionName
                 } else {
@@ -670,7 +687,7 @@
         if (isDebugEnabled &&
             PermissionManager.shouldTraceGrant(packageName, permissionName, userId)) {
             val callingUidName = packageManagerInternal.getNameForUid(callingUid)
-            Log.i(
+            Slog.i(
                 LOG_TAG, "$methodName(packageName = $packageName," +
                     " permissionName = $permissionName" +
                     (if (isGranted) "" else "skipKillUid = $skipKillUid, reason = $revokeReason") +
@@ -680,7 +697,7 @@
         }
 
         if (!userManagerInternal.exists(userId)) {
-            Log.w(LOG_TAG, "$methodName: Unknown user $userId")
+            Slog.w(LOG_TAG, "$methodName: Unknown user $userId")
             return
         }
 
@@ -710,11 +727,11 @@
         // throws when package exists but isn't visible, we now return in both cases to avoid
         // leaking the package existence.
         if (androidPackage == null) {
-            Log.w(LOG_TAG, "$methodName: Unknown package $packageName")
+            Slog.w(LOG_TAG, "$methodName: Unknown package $packageName")
             return
         }
 
-        val canManageRolePermission = isRootOrSystem(callingUid) ||
+        val canManageRolePermission = isRootOrSystemUid(callingUid) ||
             UserHandle.getAppId(callingUid) == permissionControllerPackageState!!.appId
         val overridePolicyFixed = context.checkCallingOrSelfPermission(
             Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY
@@ -740,7 +757,7 @@
     private fun setRequestedPermissionStates(
         packageState: PackageState,
         userId: Int,
-        permissionStates: IndexedMap<String, Int>
+        permissionStates: ArrayMap<String, Int>
     ) {
         service.mutateState {
             permissionStates.forEachIndexed { _, permissionName, permissionState ->
@@ -748,7 +765,7 @@
                     PackageInstaller.SessionParams.PERMISSION_STATE_GRANTED,
                     PackageInstaller.SessionParams.PERMISSION_STATE_DENIED -> {}
                     else -> {
-                        Log.w(
+                        Slog.w(
                             LOG_TAG, "setRequestedPermissionStates: Unknown permission state" +
                             " $permissionState for permission $permissionName"
                         )
@@ -769,6 +786,15 @@
                                 canManageRolePermission = false, overridePolicyFixed = false,
                                 reportError = false, "setRequestedPermissionStates"
                             )
+                            updatePermissionFlags(
+                                packageState.appId, userId, permissionName,
+                                PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED or
+                                    PackageManager.FLAG_PERMISSION_REVOKED_COMPAT, 0,
+                                canUpdateSystemFlags = false,
+                                reportErrorForUnknownPermission = false,
+                                isPermissionRequested = true, "setRequestedPermissionStates",
+                                packageState.packageName
+                            )
                         }
                     }
                     permission.isAppOp && permissionName in
@@ -858,7 +884,7 @@
 
         if (oldFlags.hasBits(PermissionFlags.SYSTEM_FIXED)) {
             if (reportError) {
-                Log.e(
+                Slog.e(
                     LOG_TAG, "$methodName: Cannot change system fixed permission $permissionName" +
                         " for package $packageName"
                 )
@@ -868,7 +894,7 @@
 
         if (oldFlags.hasBits(PermissionFlags.POLICY_FIXED) && !overridePolicyFixed) {
             if (reportError) {
-                Log.e(
+                Slog.e(
                     LOG_TAG, "$methodName: Cannot change policy fixed permission $permissionName" +
                         " for package $packageName"
                 )
@@ -878,7 +904,7 @@
 
         if (isGranted && oldFlags.hasBits(PermissionFlags.RESTRICTION_REVOKED)) {
             if (reportError) {
-                Log.e(
+                Slog.e(
                     LOG_TAG, "$methodName: Cannot grant hard-restricted non-exempt permission" +
                         " $permissionName to package $packageName"
                 )
@@ -894,7 +920,7 @@
             )
             if (!softRestrictedPermissionPolicy.mayGrantPermission()) {
                 if (reportError) {
-                    Log.e(
+                    Slog.e(
                         LOG_TAG, "$methodName: Cannot grant soft-restricted non-exempt permission" +
                             " $permissionName to package $packageName"
                     )
@@ -930,7 +956,8 @@
         permissionName: String,
         isGranted: Boolean
     ) {
-        val appOpPolicy = service.getSchemePolicy(UidUri.SCHEME, AppOpUri.SCHEME) as UidAppOpPolicy
+        val appOpPolicy = service.getSchemePolicy(UidUri.SCHEME, AppOpUri.SCHEME) as
+            AppIdAppOpPolicy
         val appOpName = AppOpsManager.permissionToOp(permissionName)
         val mode = if (isGranted) AppOpsManager.MODE_ALLOWED else AppOpsManager.MODE_ERRORED
         with(appOpPolicy) { setAppOpMode(packageState.appId, userId, appOpName, mode) }
@@ -938,7 +965,7 @@
 
     override fun getPermissionFlags(packageName: String, permissionName: String, userId: Int): Int {
         if (!userManagerInternal.exists(userId)) {
-            Log.w(LOG_TAG, "getPermissionFlags: Unknown user $userId")
+            Slog.w(LOG_TAG, "getPermissionFlags: Unknown user $userId")
             return 0
         }
 
@@ -955,14 +982,14 @@
         val packageState = packageManagerLocal.withFilteredSnapshot()
             .use { it.getPackageState(packageName) }
         if (packageState == null) {
-            Log.w(LOG_TAG, "getPermissionFlags: Unknown package $packageName")
+            Slog.w(LOG_TAG, "getPermissionFlags: Unknown package $packageName")
             return 0
         }
 
         service.getState {
             val permission = with(policy) { getPermissions()[permissionName] }
             if (permission == null) {
-                Log.w(LOG_TAG, "getPermissionFlags: Unknown permission $permissionName")
+                Slog.w(LOG_TAG, "getPermissionFlags: Unknown permission $permissionName")
                 return 0
             }
 
@@ -978,7 +1005,7 @@
         userId: Int
     ): Boolean {
         if (!userManagerInternal.exists(userId)) {
-            Log.w(LOG_TAG, "isPermissionRevokedByPolicy: Unknown user $userId")
+            Slog.w(LOG_TAG, "isPermissionRevokedByPolicy: Unknown user $userId")
             return false
         }
 
@@ -1022,7 +1049,7 @@
         userId: Int
     ): Boolean {
         if (!userManagerInternal.exists(userId)) {
-            Log.w(LOG_TAG, "shouldShowRequestPermissionRationale: Unknown user $userId")
+            Slog.w(LOG_TAG, "shouldShowRequestPermissionRationale: Unknown user $userId")
             return false
         }
 
@@ -1058,7 +1085,7 @@
                         BACKGROUND_RATIONALE_CHANGE_ID, packageName, userId
                     )
                 } catch (e: RemoteException) {
-                    Log.e(LOG_TAG, "shouldShowRequestPermissionRationale: Unable to check if" +
+                    Slog.e(LOG_TAG, "shouldShowRequestPermissionRationale: Unable to check if" +
                         " compatibility change is enabled", e)
                     false
                 }
@@ -1089,7 +1116,7 @@
                 PackageManager::class.java, "FLAG_PERMISSION_", flagValues.toLong()
             )
             val callingUidName = packageManagerInternal.getNameForUid(callingUid)
-            Log.i(
+            Slog.i(
                 LOG_TAG, "updatePermissionFlags(packageName = $packageName," +
                     " permissionName = $permissionName, flagMask = $flagMaskString," +
                     " flagValues = $flagValuesString, userId = $userId," +
@@ -1098,7 +1125,7 @@
         }
 
         if (!userManagerInternal.exists(userId)) {
-            Log.w(LOG_TAG, "updatePermissionFlags: Unknown user $userId")
+            Slog.w(LOG_TAG, "updatePermissionFlags: Unknown user $userId")
             return
         }
 
@@ -1115,7 +1142,7 @@
         // POLICY_FIXED flag if the caller is system or root UID, now we do allow that since system
         // and root UIDs are supposed to have all permissions including
         // ADJUST_RUNTIME_PERMISSIONS_POLICY.
-        if (!isRootOrSystem(callingUid)) {
+        if (!isRootOrSystemUid(callingUid)) {
             if (flagMask.hasBits(PackageManager.FLAG_PERMISSION_POLICY_FIXED)) {
                 if (enforceAdjustPolicyPermission) {
                     context.enforceCallingOrSelfPermission(
@@ -1146,10 +1173,15 @@
         // leaking the package existence.
         if (androidPackage == null ||
             packageManagerInternal.filterAppAccess(packageName, callingUid, userId, false)) {
-            Log.w(LOG_TAG, "updatePermissionFlags: Unknown package $packageName")
+            Slog.w(LOG_TAG, "updatePermissionFlags: Unknown package $packageName")
             return
         }
 
+        // Different from the old implementation, which only allowed the system UID to modify the
+        // following flags, we now allow the root UID as well since both should have all
+        // permissions.
+        val canUpdateSystemFlags = isRootOrSystemUid(callingUid)
+
         val isPermissionRequested = if (permissionName in androidPackage.requestedPermissions) {
             // Fast path, the current package has requested the permission.
             true
@@ -1167,7 +1199,7 @@
         val appId = packageState.appId
         service.mutateState {
             updatePermissionFlags(
-                appId, userId, permissionName, flagMask, flagValues,
+                appId, userId, permissionName, flagMask, flagValues, canUpdateSystemFlags,
                 reportErrorForUnknownPermission = true, isPermissionRequested,
                 "updatePermissionFlags", packageName
             )
@@ -1184,7 +1216,7 @@
                 PackageManager::class.java, "FLAG_PERMISSION_", flagValues.toLong()
             )
             val callingUidName = packageManagerInternal.getNameForUid(callingUid)
-            Log.i(
+            Slog.i(
                 LOG_TAG, "updatePermissionFlagsForAllApps(flagMask = $flagMaskString," +
                     " flagValues = $flagValuesString, userId = $userId," +
                     " callingUid = $callingUidName ($callingUid))", RuntimeException()
@@ -1192,7 +1224,7 @@
         }
 
         if (!userManagerInternal.exists(userId)) {
-            Log.w(LOG_TAG, "updatePermissionFlagsForAllApps: Unknown user $userId")
+            Slog.w(LOG_TAG, "updatePermissionFlagsForAllApps: Unknown user $userId")
             return
         }
 
@@ -1205,18 +1237,20 @@
             Manifest.permission.REVOKE_RUNTIME_PERMISSIONS
         )
 
+        // Different from the old implementation, which only sanitized the SYSTEM_FIXED
+        // flag, we now properly sanitize all flags as in updatePermissionFlags().
+        val canUpdateSystemFlags = isRootOrSystemUid(callingUid)
+
         val packageStates = packageManagerLocal.withUnfilteredSnapshot()
             .use { it.packageStates }
         service.mutateState {
             packageStates.forEach { (packageName, packageState) ->
                 val androidPackage = packageState.androidPackage ?: return@forEach
                 androidPackage.requestedPermissions.forEach { permissionName ->
-                    // Different from the old implementation, which only sanitized the SYSTEM_FIXED
-                    // flag, we now properly sanitize all flags as in updatePermissionFlags().
                     updatePermissionFlags(
                         packageState.appId, userId, permissionName, flagMask, flagValues,
-                        reportErrorForUnknownPermission = false, isPermissionRequested = true,
-                        "updatePermissionFlagsForAllApps", packageName
+                        canUpdateSystemFlags, reportErrorForUnknownPermission = false,
+                        isPermissionRequested = true, "updatePermissionFlagsForAllApps", packageName
                     )
                 }
             }
@@ -1224,8 +1258,7 @@
     }
 
     /**
-     * Shared internal implementation that should only be called by [updatePermissionFlags] and
-     * [updatePermissionFlagsForAllApps].
+     * Update flags for a permission, without any validation on caller.
      */
     private fun MutateStateScope.updatePermissionFlags(
         appId: Int,
@@ -1233,32 +1266,23 @@
         permissionName: String,
         flagMask: Int,
         flagValues: Int,
+        canUpdateSystemFlags: Boolean,
         reportErrorForUnknownPermission: Boolean,
         isPermissionRequested: Boolean,
         methodName: String,
         packageName: String
     ) {
-        // Different from the old implementation, which only allowed the system UID to modify the
-        // following flags, we now allow the root UID as well since both should have all
-        // permissions.
-        // Only the system can change these flags and nothing else.
-        val callingUid = Binder.getCallingUid()
         @Suppress("NAME_SHADOWING")
         var flagMask = flagMask
         @Suppress("NAME_SHADOWING")
         var flagValues = flagValues
-        if (!isRootOrSystem(callingUid)) {
+        // Only the system can change these flags and nothing else.
+        if (!canUpdateSystemFlags) {
             // Different from the old implementation, which allowed non-system UIDs to remove (but
             // not add) permission restriction flags, we now consistently ignore them altogether.
             val ignoredMask = PackageManager.FLAG_PERMISSION_SYSTEM_FIXED or
                 PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT or
-                // REVIEW_REQUIRED can be set on any permission by the shell, or by any app for the
-                // NOTIFICATIONS permissions specifically.
-                if (isShell(callingUid) || permissionName in NOTIFICATIONS_PERMISSIONS) {
-                    0
-                } else {
-                    PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED
-                } or PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT or
+                PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT or
                 PackageManager.FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT or
                 PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT or
                 PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION
@@ -1276,7 +1300,7 @@
 
         val oldFlags = with(policy) { getPermissionFlags(appId, userId, permissionName) }
         if (!isPermissionRequested && oldFlags == 0) {
-            Log.w(
+            Slog.w(
                 LOG_TAG, "$methodName: Permission $permissionName isn't requested by package" +
                     " $packageName"
             )
@@ -1291,13 +1315,13 @@
         packageName: String,
         allowlistedFlags: Int,
         userId: Int
-    ): IndexedList<String>? {
+    ): ArrayList<String>? {
         requireNotNull(packageName) { "packageName cannot be null" }
         Preconditions.checkFlagsArgument(allowlistedFlags, PERMISSION_ALLOWLIST_MASK)
         Preconditions.checkArgumentNonnegative(userId, "userId cannot be null")
 
         if (!userManagerInternal.exists(userId)) {
-            Log.w(LOG_TAG, "AllowlistedRestrictedPermission api: Unknown user $userId")
+            Slog.w(LOG_TAG, "AllowlistedRestrictedPermission api: Unknown user $userId")
             return null
         }
 
@@ -1349,7 +1373,7 @@
         appId: Int,
         allowlistedFlags: Int,
         userId: Int
-    ): IndexedList<String>? {
+    ): ArrayList<String>? {
         val permissionFlags = service.getState {
             with(policy) { getUidPermissionFlags(appId, userId) }
         } ?: return null
@@ -1365,7 +1389,7 @@
             queryFlags = queryFlags or PermissionFlags.INSTALLER_EXEMPT
         }
 
-        return permissionFlags.mapNotNullIndexed { _, permissionName, flags ->
+        return permissionFlags.mapNotNullIndexedTo(ArrayList()) { _, permissionName, flags ->
             if (flags.hasAnyBit(queryFlags)) permissionName else null
         }
     }
@@ -1383,7 +1407,7 @@
 
         val permissionNames = getAllowlistedRestrictedPermissions(
             packageName, allowlistedFlags, userId
-        ) ?: IndexedList(1)
+        ) ?: ArrayList(1)
 
         if (permissionName !in permissionNames) {
             permissionNames += permissionName
@@ -1403,7 +1427,7 @@
         val newPermissionNames = getAllowlistedRestrictedPermissionsUnchecked(appId,
             PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER, userId
         )?.let {
-            IndexedSet(permissionNames).apply { this += it }.toList()
+            ArraySet(permissionNames).apply { this += it }.toList()
         } ?: permissionNames
 
         setAllowlistedRestrictedPermissionsUnchecked(androidPackage, appId, newPermissionNames,
@@ -1437,7 +1461,7 @@
     private fun enforceRestrictedPermission(permissionName: String): Boolean {
         val permission = service.getState { with(policy) { getPermissions()[permissionName] } }
         if (permission == null) {
-            Log.w(LOG_TAG, "permission definition for $permissionName does not exist")
+            Slog.w(LOG_TAG, "permission definition for $permissionName does not exist")
             return false
         }
 
@@ -1569,27 +1593,31 @@
                         return@forEachIndexed
                     }
 
-                    val wasAllowlisted = oldFlags.hasAnyBit(PermissionFlags.MASK_EXEMPT)
-                    val isAllowlisted = newFlags.hasAnyBit(PermissionFlags.MASK_EXEMPT)
+                    val isExempt = newFlags.hasAnyBit(PermissionFlags.MASK_EXEMPT)
 
                     // If the permission is policy fixed as granted but it is no longer
                     // on any of the allowlists we need to clear the policy fixed flag
                     // as allowlisting trumps policy i.e. policy cannot grant a non
                     // grantable permission.
                     if (oldFlags.hasBits(PermissionFlags.POLICY_FIXED)) {
-                        if (!isAllowlisted && wasGranted) {
+                        if (!isExempt && wasGranted) {
                             mask = mask or PermissionFlags.POLICY_FIXED
                             newFlags = newFlags andInv PermissionFlags.POLICY_FIXED
                         }
                     }
 
-                    // If we are allowlisting an app that does not support runtime permissions
-                    // we need to make sure it goes through the permission review UI at launch.
-                    if (androidPackage.targetSdkVersion < Build.VERSION_CODES.M &&
-                        !wasAllowlisted && isAllowlisted) {
-                        mask = mask or PermissionFlags.IMPLICIT
-                        newFlags = newFlags or PermissionFlags.IMPLICIT
+                    newFlags = if (permission.isHardRestricted && !isExempt) {
+                        newFlags or PermissionFlags.RESTRICTION_REVOKED
+                    } else {
+                        newFlags andInv PermissionFlags.RESTRICTION_REVOKED
                     }
+                    newFlags = if (permission.isSoftRestricted && !isExempt) {
+                        newFlags or PermissionFlags.SOFT_RESTRICTED
+                    } else {
+                        newFlags andInv PermissionFlags.SOFT_RESTRICTED
+                    }
+                    mask = mask or PermissionFlags.RESTRICTION_REVOKED or
+                        PermissionFlags.SOFT_RESTRICTED
 
                     updatePermissionFlags(
                         appId, userId, requestedPermission, mask, newFlags
@@ -1600,11 +1628,23 @@
     }
 
     override fun resetRuntimePermissions(androidPackage: AndroidPackage, userId: Int) {
-        // TODO("Not yet implemented")
+        service.mutateState {
+            with(policy) {
+                resetRuntimePermissions(androidPackage.packageName, userId)
+            }
+        }
     }
 
     override fun resetRuntimePermissionsForUser(userId: Int) {
-        // TODO("Not yet implemented")
+        packageManagerLocal.withUnfilteredSnapshot().use { snapshot ->
+            service.mutateState {
+                snapshot.packageStates.forEach { (_, packageState) ->
+                    with(policy) {
+                        resetRuntimePermissions(packageState.packageName, userId)
+                    }
+                }
+            }
+        }
     }
 
     override fun addOnPermissionsChangeListener(listener: IOnPermissionsChangeListener) {
@@ -1615,18 +1655,6 @@
         onPermissionsChangeListeners.removeListener(listener)
     }
 
-    override fun addOnRuntimePermissionStateChangedListener(
-        listener: PermissionManagerServiceInternal.OnRuntimePermissionStateChangedListener
-    ) {
-        // TODO: Should be removed once we remove PermissionPolicyService.
-    }
-
-    override fun removeOnRuntimePermissionStateChangedListener(
-        listener: PermissionManagerServiceInternal.OnRuntimePermissionStateChangedListener
-    ) {
-        // TODO: Should be removed once we remove PermissionPolicyService.
-    }
-
     override fun getSplitPermissions(): List<SplitPermissionInfoParcelable> {
         return PermissionManager.splitPermissionInfoListToParcelableList(
             systemConfig.splitPermissions
@@ -1637,7 +1665,7 @@
 
     override fun getAppOpPermissionPackages(permissionName: String): Array<String> {
         requireNotNull(permissionName) { "permissionName cannot be null" }
-        val packageNames = IndexedSet<String>()
+        val packageNames = ArraySet<String>()
 
         val permission = service.getState {
             with(policy) { getPermissions()[permissionName] }
@@ -1659,7 +1687,7 @@
     }
 
     override fun getAllAppOpPermissionPackages(): Map<String, Set<String>> {
-        val appOpPermissionPackageNames = IndexedMap<String, IndexedSet<String>>()
+        val appOpPermissionPackageNames = ArrayMap<String, ArraySet<String>>()
         val permissions = service.getState { with(policy) { getPermissions() } }
         packageManagerLocal.withUnfilteredSnapshot().use { snapshot ->
             snapshot.packageStates.forEach packageStates@{ (_, packageState) ->
@@ -1668,7 +1696,7 @@
                     val permission = permissions[permissionName] ?: return@requestedPermissions
                     if (permission.isAppOp) {
                         val packageNames = appOpPermissionPackageNames
-                            .getOrPut(permissionName) { IndexedSet() }
+                            .getOrPut(permissionName) { ArraySet() }
                         packageNames += androidPackage.packageName
                     }
                 }
@@ -1689,7 +1717,7 @@
         } catch (e: Exception) {
             when (e) {
                 is TimeoutException, is InterruptedException, is ExecutionException -> {
-                    Log.e(LOG_TAG, "Cannot create permission backup for user $userId", e)
+                    Slog.e(LOG_TAG, "Cannot create permission backup for user $userId", e)
                     null
                 }
                 else -> throw e
@@ -1734,7 +1762,162 @@
         if (!DumpUtils.checkDumpPermission(context, LOG_TAG, pw)) {
             return
         }
-        context.getSystemService(PermissionControllerManager::class.java)!!.dump(fd, args)
+
+        val writer = IndentingPrintWriter(pw, "  ")
+
+        if (args.isNullOrEmpty()) {
+            service.getState {
+                writer.dumpSystemState(state)
+                getAllAppIdPackageNames(state).forEachIndexed { _, appId, packageNames ->
+                    if (appId != Process.INVALID_UID) {
+                        writer.dumpAppIdState(appId, state, packageNames)
+                    }
+                }
+            }
+        } else if (args[0] == "--app-id" && args.size == 2) {
+            val appId = args[1].toInt()
+            service.getState {
+                val appIdPackageNames = getAllAppIdPackageNames(state)
+                if (appId in appIdPackageNames) {
+                    writer.dumpAppIdState(appId, state, appIdPackageNames[appId])
+                } else {
+                    writer.println("Unknown app ID $appId.")
+                }
+            }
+        } else {
+            writer.println("Usage: dumpsys permission [--app-id APP_ID]")
+        }
+    }
+
+    private fun getAllAppIdPackageNames(
+        state: AccessState
+    ): IndexedMap<Int, MutableIndexedSet<String>> {
+        val appIds = MutableIndexedSet<Int>()
+
+        val packageStates = packageManagerLocal.withUnfilteredSnapshot().use {
+            it.packageStates
+        }
+        state.userStates.forEachIndexed { _, _, userState ->
+            userState.appIdPermissionFlags.forEachIndexed { _, appId, _ ->
+                appIds.add(appId)
+            }
+            userState.appIdAppOpModes.forEachIndexed { _, appId, _ ->
+                appIds.add(appId)
+            }
+            userState.packageVersions.forEachIndexed packageVersions@ { _, packageName, _ ->
+                val appId = packageStates[packageName]?.appId ?: return@packageVersions
+                appIds.add(appId)
+            }
+            userState.packageAppOpModes.forEachIndexed packageAppOpModes@ { _, packageName, _ ->
+                val appId = packageStates[packageName]?.appId ?: return@packageAppOpModes
+                appIds.add(appId)
+            }
+        }
+
+        val appIdPackageNames = MutableIndexedMap<Int, MutableIndexedSet<String>>()
+        packageStates.forEach { (_, packageState) ->
+            appIdPackageNames.getOrPut(packageState.appId) { MutableIndexedSet() }
+                .add(packageState.packageName)
+        }
+        // add non-package app IDs which might not be reported by package manager.
+        appIds.forEachIndexed { _, appId ->
+            appIdPackageNames.getOrPut(appId) { MutableIndexedSet() }
+        }
+
+        return appIdPackageNames
+    }
+
+    private fun IndentingPrintWriter.dumpSystemState(state: AccessState) {
+        println("Permissions:")
+        withIndent {
+            state.systemState.permissions.forEachIndexed { _, _, permission ->
+                val protectionLevel = PermissionInfo.protectionToString(permission.protectionLevel)
+                println(
+                    "${permission.name}: " +
+                        "type=${Permission.typeToString(permission.type)}, " +
+                        "packageName=${permission.packageName}, " +
+                        "appId=${permission.appId}, " +
+                        "gids=${permission.gids.contentToString()}, " +
+                        "protectionLevel=[$protectionLevel], " +
+                        "flags=${PermissionInfo.flagsToString(permission.permissionInfo.flags)}"
+                )
+            }
+        }
+
+        println("Permission groups:")
+        withIndent {
+            state.systemState.permissionGroups.forEachIndexed { _, _, permissionGroup ->
+                println(
+                    "${permissionGroup.name}: " +
+                        "packageName=${permissionGroup.packageName}"
+                )
+            }
+        }
+
+        println("Permission trees:")
+        withIndent {
+            state.systemState.permissionTrees.forEachIndexed { _, _, permissionTree ->
+                println(
+                    "${permissionTree.name}: " +
+                        "packageName=${permissionTree.packageName}, " +
+                        "appId=${permissionTree.appId}"
+                )
+            }
+        }
+    }
+
+    private fun IndentingPrintWriter.dumpAppIdState(
+        appId: Int,
+        state: AccessState,
+        packageNames: IndexedSet<String>?
+    ) {
+        println("App ID: $appId")
+        withIndent {
+            state.userStates.forEachIndexed { _, userId, userState ->
+                println("User: $userId")
+                withIndent {
+                    println("Permissions:")
+                    withIndent {
+                        userState.appIdPermissionFlags[appId]?.forEachIndexed {
+                                _, permissionName, flags ->
+                            val isGranted = PermissionFlags.isPermissionGranted(flags)
+                            println(
+                                "$permissionName: granted=$isGranted, flags=" +
+                                    PermissionFlags.toString(flags)
+                            )
+                        }
+                    }
+
+                    println("App ops:")
+                    withIndent {
+                        userState.appIdAppOpModes[appId]?.forEachIndexed {_, appOpName, appOpMode ->
+                            println("$appOpName: mode=${AppOpsManager.modeToName(appOpMode)}")
+                        }
+                    }
+
+                    packageNames?.forEachIndexed { _, packageName ->
+                        println("Package: $packageName")
+                        withIndent {
+                            println("version=${userState.packageVersions[packageName]}")
+                            println("App ops:")
+                            withIndent {
+                                userState.packageAppOpModes[packageName]?.forEachIndexed {
+                                        _, appOpName, appOpMode ->
+                                    val modeName = AppOpsManager.modeToName(appOpMode)
+                                    println("$appOpName: mode=$modeName")
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    private inline fun IndentingPrintWriter.withIndent(block: IndentingPrintWriter.() -> Unit) {
+        increaseIndent()
+        block()
+        decreaseIndent()
     }
 
     override fun getPermissionTEMP(permissionName: String): LegacyPermission2? {
@@ -1751,7 +1934,7 @@
     override fun getLegacyPermissions(): List<LegacyPermission> =
         service.getState {
             with(policy) { getPermissions() }
-        }.mapIndexed { _, _, permission ->
+        }.mapIndexedTo(ArrayList()) { _, _, permission ->
             LegacyPermission(
                 permission.permissionInfo, permission.type, permission.appId, permission.gids
             )
@@ -1774,7 +1957,7 @@
     private fun toLegacyPermissions(
         permissions: IndexedMap<String, Permission>
     ): List<LegacyPermission> =
-        permissions.mapIndexed { _, _, permission ->
+        permissions.mapIndexedTo(ArrayList()) { _, _, permission ->
             // We don't need to provide UID and GIDs, which are only retrieved when dumping.
             LegacyPermission(
                 permission.permissionInfo, permission.type, 0, EmptyArray.INT
@@ -1809,6 +1992,15 @@
 
     override fun writeLegacyPermissionStateTEMP() {}
 
+    override fun getDefaultPermissionGrantFingerprint(userId: Int): String? =
+        service.getState { state.userStates[userId]!!.defaultPermissionGrantFingerprint }
+
+    override fun setDefaultPermissionGrantFingerprint(fingerprint: String, userId: Int) {
+        service.mutateState {
+            newState.mutateUserState(userId)!!.setDefaultPermissionGrantFingerprint(fingerprint)
+        }
+    }
+
     override fun onSystemReady() {
         service.onSystemReady()
         permissionControllerManager = PermissionControllerManager(
@@ -1817,7 +2009,9 @@
     }
 
     override fun onUserCreated(userId: Int) {
-        service.onUserAdded(userId)
+        withCorkedPackageInfoCache {
+            service.onUserAdded(userId)
+        }
     }
 
     override fun onUserRemoved(userId: Int) {
@@ -1825,10 +2019,16 @@
     }
 
     override fun onStorageVolumeMounted(volumeUuid: String, fingerprintChanged: Boolean) {
-        service.onStorageVolumeMounted(volumeUuid, fingerprintChanged)
-        synchronized(mountedStorageVolumes) {
+        val packageNames: List<String>
+        synchronized(storageVolumeLock) {
+            // Removing the storageVolumePackageNames entry because we expect onPackageAdded()
+            // to always be called before onStorageVolumeMounted().
+            packageNames = storageVolumePackageNames.remove(volumeUuid) ?: emptyList()
             mountedStorageVolumes += volumeUuid
         }
+        withCorkedPackageInfoCache {
+            service.onStorageVolumeMounted(volumeUuid, packageNames, fingerprintChanged)
+        }
     }
 
     override fun onPackageAdded(
@@ -1836,7 +2036,14 @@
         isInstantApp: Boolean,
         oldPackage: AndroidPackage?
     ) {
-        synchronized(mountedStorageVolumes) {
+        synchronized(storageVolumeLock) {
+            // Accumulating the package names here because we want to maintain the same call order
+            // of onPackageAdded() and reuse this order in onStorageVolumeAdded(). We need the
+            // packages to be iterated in onStorageVolumeAdded() in the same order so that the
+            // ownership of permissions is consistent.
+            storageVolumePackageNames.getOrPut(packageState.volumeUuid) {
+                mutableListOf()
+            } += packageState.packageName
             if (packageState.volumeUuid !in mountedStorageVolumes) {
                 // Wait for the storage volume to be mounted and batch the state mutation there.
                 return
@@ -1856,7 +2063,19 @@
         params: PermissionManagerServiceInternal.PackageInstalledParams,
         userId: Int
     ) {
-        synchronized(mountedStorageVolumes) {
+        if (params === PermissionManagerServiceInternal.PackageInstalledParams.DEFAULT) {
+            // TODO: We should actually stop calling onPackageInstalled() when we are passing
+            //  PackageInstalledParams.DEFAULT in InstallPackageHelper, because there's actually no
+            //  installer in those cases of system app installs, and the default params won't
+            //  allowlist any permissions which means the original UPGRADE_EXEMPT will be dropped
+            //  without any INSTALLER_EXEMPT added. However, we can't do that right now because the
+            //  old permission subsystem still depends on this method being called to set up the
+            //  permission state for the first time (which we are doing in onPackageAdded() or
+            //  onStorageVolumeMounted() now).
+            return
+        }
+
+        synchronized(storageVolumeLock) {
             if (androidPackage.volumeUuid !in mountedStorageVolumes) {
                 // Wait for the storage volume to be mounted and batch the state mutation there.
                 // PackageInstalledParams won't exist when packages are being scanned instead of
@@ -1905,24 +2124,33 @@
         }
     }
 
+    private inline fun <T> withCorkedPackageInfoCache(block: () -> T): T {
+        PackageManager.corkPackageInfoCache()
+        try {
+            return block()
+        } finally {
+            PackageManager.uncorkPackageInfoCache()
+        }
+    }
+
     /**
-     * Check whether a UID is root or system.
+     * Check whether a UID is root or system UID.
      */
-    private fun isRootOrSystem(uid: Int) =
+    private fun isRootOrSystemUid(uid: Int) =
         when (UserHandle.getAppId(uid)) {
             Process.ROOT_UID, Process.SYSTEM_UID -> true
             else -> false
         }
 
     /**
-     * Check whether a UID is shell.
+     * Check whether a UID is shell UID.
      */
-    private fun isShell(uid: Int) = UserHandle.getAppId(uid) == Process.SHELL_UID
+    private fun isShellUid(uid: Int) = UserHandle.getAppId(uid) == Process.SHELL_UID
 
     /**
-     * Check whether a UID is root, system or shell.
+     * Check whether a UID is root, system or shell UID.
      */
-    private fun isRootOrSystemOrShell(uid: Int) = isRootOrSystem(uid) || isShell(uid)
+    private fun isRootOrSystemOrShellUid(uid: Int) = isRootOrSystemUid(uid) || isShellUid(uid)
 
     /**
      * This method should typically only be used when granting or revoking permissions, since the
@@ -2030,7 +2258,7 @@
                         append(": ")
                     }
                     append("Neither user ")
-                    append(Binder.getCallingUid())
+                    append(callingUid)
                     append(" nor current process has ")
                     append(permissionName)
                     append(" to access user ")
@@ -2039,7 +2267,7 @@
                 throw SecurityException(exceptionMessage)
             }
         }
-        if (enforceShellRestriction && isShell(callingUid)) {
+        if (enforceShellRestriction && isShellUid(callingUid)) {
             val isShellRestricted = userManagerInternal.hasUserRestriction(
                 UserManager.DISALLOW_DEBUGGING_FEATURES, userId
             )
@@ -2090,16 +2318,16 @@
      * Callback invoked when interesting actions have been taken on a permission.
      */
     private inner class OnPermissionFlagsChangedListener :
-        UidPermissionPolicy.OnPermissionFlagsChangedListener() {
+        AppIdPermissionPolicy.OnPermissionFlagsChangedListener() {
         private var isPermissionFlagsChanged = false
 
-        private val runtimePermissionChangedUids = IntSet()
+        private val runtimePermissionChangedUids = MutableIntSet()
         // Mapping from UID to whether only notifications permissions are revoked.
-        private val runtimePermissionRevokedUids = IntBooleanMap()
-        private val gidsChangedUids = IntSet()
+        private val runtimePermissionRevokedUids = SparseBooleanArray()
+        private val gidsChangedUids = MutableIntSet()
 
         private var isKillRuntimePermissionRevokedUidsSkipped = false
-        private val killRuntimePermissionRevokedUidsReasons = IndexedSet<String>()
+        private val killRuntimePermissionRevokedUidsReasons = ArraySet<String>()
 
         fun MutateStateScope.skipKillRuntimePermissionRevokedUids() {
             isKillRuntimePermissionRevokedUidsSkipped = true
@@ -2134,7 +2362,7 @@
                 if (wasPermissionGranted && !isPermissionGranted) {
                     runtimePermissionRevokedUids[uid] =
                         permissionName in NOTIFICATIONS_PERMISSIONS &&
-                            runtimePermissionRevokedUids.getWithDefault(uid, true)
+                            runtimePermissionRevokedUids.get(uid, true)
                 }
             }
 
@@ -2199,7 +2427,7 @@
                 ) == Settings.Secure.USER_SETUP_PERSONALIZATION_STARTED
                 isInSetup || isInDeferredSetup
             } catch (e: Settings.SettingNotFoundException) {
-                Log.w(LOG_TAG, "Failed to check if the user is in restore: $e")
+                Slog.w(LOG_TAG, "Failed to check if the user is in restore: $e")
                 false
             }
         }
@@ -2222,7 +2450,7 @@
                 try {
                     listener.onPermissionsChanged(uid)
                 } catch (e: RemoteException) {
-                    Log.e(LOG_TAG, "Error when calling OnPermissionsChangeListener", e)
+                    Slog.e(LOG_TAG, "Error when calling OnPermissionsChangeListener", e)
                 }
             }
         }
@@ -2257,14 +2485,14 @@
         @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
         private val BACKGROUND_RATIONALE_CHANGE_ID = 147316723L
 
-        private val FULLER_PERMISSIONS = IndexedMap<String, String>().apply {
+        private val FULLER_PERMISSIONS = ArrayMap<String, String>().apply {
             this[Manifest.permission.ACCESS_COARSE_LOCATION] =
                 Manifest.permission.ACCESS_FINE_LOCATION
             this[Manifest.permission.INTERACT_ACROSS_USERS] =
                 Manifest.permission.INTERACT_ACROSS_USERS_FULL
         }
 
-        private val NOTIFICATIONS_PERMISSIONS = indexedSetOf(
+        private val NOTIFICATIONS_PERMISSIONS = arraySetOf(
             Manifest.permission.POST_NOTIFICATIONS
         )
 
diff --git a/services/permission/java/com/android/server/permission/access/permission/UidPermissionPersistence.kt b/services/permission/java/com/android/server/permission/access/permission/UidPermissionPersistence.kt
deleted file mode 100644
index 35cdbce..0000000
--- a/services/permission/java/com/android/server/permission/access/permission/UidPermissionPersistence.kt
+++ /dev/null
@@ -1,229 +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.server.permission.access.permission
-
-import android.content.pm.PermissionInfo
-import android.util.Log
-import com.android.modules.utils.BinaryXmlPullParser
-import com.android.modules.utils.BinaryXmlSerializer
-import com.android.server.permission.access.AccessState
-import com.android.server.permission.access.UserState
-import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
-import com.android.server.permission.access.util.attribute
-import com.android.server.permission.access.util.attributeInt
-import com.android.server.permission.access.util.attributeIntHex
-import com.android.server.permission.access.util.attributeIntHexWithDefault
-import com.android.server.permission.access.util.attributeInterned
-import com.android.server.permission.access.util.forEachTag
-import com.android.server.permission.access.util.getAttributeIntHexOrDefault
-import com.android.server.permission.access.util.getAttributeIntHexOrThrow
-import com.android.server.permission.access.util.getAttributeIntOrThrow
-import com.android.server.permission.access.util.getAttributeValue
-import com.android.server.permission.access.util.getAttributeValueOrThrow
-import com.android.server.permission.access.util.tag
-import com.android.server.permission.access.util.tagName
-
-class UidPermissionPersistence {
-    fun BinaryXmlPullParser.parseSystemState(state: AccessState) {
-        val systemState = state.systemState
-        when (tagName) {
-            TAG_PERMISSION_TREES -> parsePermissions(systemState.permissionTrees)
-            TAG_PERMISSIONS -> parsePermissions(systemState.permissions)
-            else -> {}
-        }
-    }
-
-    private fun BinaryXmlPullParser.parsePermissions(permissions: IndexedMap<String, Permission>) {
-        forEachTag {
-            when (val tagName = tagName) {
-                TAG_PERMISSION -> parsePermission(permissions)
-                else -> Log.w(LOG_TAG, "Ignoring unknown tag $tagName when parsing permissions")
-            }
-        }
-    }
-
-    private fun BinaryXmlPullParser.parsePermission(permissions: IndexedMap<String, Permission>) {
-        val name = getAttributeValueOrThrow(ATTR_NAME).intern()
-        @Suppress("DEPRECATION")
-        val permissionInfo = PermissionInfo().apply {
-            this.name = name
-            packageName = getAttributeValueOrThrow(ATTR_PACKAGE_NAME).intern()
-            protectionLevel = getAttributeIntHexOrThrow(ATTR_PROTECTION_LEVEL)
-        }
-        val type = getAttributeIntOrThrow(ATTR_TYPE)
-        when (type) {
-            Permission.TYPE_MANIFEST -> {}
-            Permission.TYPE_CONFIG -> {
-                Log.w(LOG_TAG, "Ignoring unexpected config permission $name")
-                return
-            }
-            Permission.TYPE_DYNAMIC -> {
-                permissionInfo.apply {
-                    icon = getAttributeIntHexOrDefault(ATTR_ICON, 0)
-                    nonLocalizedLabel = getAttributeValue(ATTR_LABEL)
-                }
-            }
-            else -> {
-                Log.w(LOG_TAG, "Ignoring permission $name with unknown type $type")
-                return
-            }
-        }
-        val permission = Permission(permissionInfo, false, type, 0)
-        permissions[name] = permission
-    }
-
-    fun BinaryXmlSerializer.serializeSystemState(state: AccessState) {
-        val systemState = state.systemState
-        serializePermissions(TAG_PERMISSION_TREES, systemState.permissionTrees)
-        serializePermissions(TAG_PERMISSIONS, systemState.permissions)
-    }
-
-    private fun BinaryXmlSerializer.serializePermissions(
-        tagName: String,
-        permissions: IndexedMap<String, Permission>
-    ) {
-        tag(tagName) {
-            permissions.forEachValueIndexed { _, it -> serializePermission(it) }
-        }
-    }
-
-    private fun BinaryXmlSerializer.serializePermission(permission: Permission) {
-        val type = permission.type
-        when (type) {
-            Permission.TYPE_MANIFEST, Permission.TYPE_DYNAMIC -> {}
-            Permission.TYPE_CONFIG -> return
-            else -> {
-                Log.w(LOG_TAG, "Skipping serializing permission $name with unknown type $type")
-                return
-            }
-        }
-        tag(TAG_PERMISSION) {
-            attributeInterned(ATTR_NAME, permission.name)
-            attributeInterned(ATTR_PACKAGE_NAME, permission.packageName)
-            attributeIntHex(ATTR_PROTECTION_LEVEL, permission.protectionLevel)
-            attributeInt(ATTR_TYPE, type)
-            if (type == Permission.TYPE_DYNAMIC) {
-                val permissionInfo = permission.permissionInfo
-                attributeIntHexWithDefault(ATTR_ICON, permissionInfo.icon, 0)
-                permissionInfo.nonLocalizedLabel?.toString()?.let { attribute(ATTR_LABEL, it) }
-            }
-        }
-    }
-
-    fun BinaryXmlPullParser.parseUserState(state: AccessState, userId: Int) {
-        when (tagName) {
-            TAG_PERMISSIONS -> parsePermissionFlags(state, userId)
-            else -> {}
-        }
-    }
-
-    private fun BinaryXmlPullParser.parsePermissionFlags(state: AccessState, userId: Int) {
-        val userState = state.userStates[userId]
-        forEachTag {
-            when (tagName) {
-                TAG_APP_ID -> parseAppId(userState)
-                else -> Log.w(LOG_TAG, "Ignoring unknown tag $name when parsing permission state")
-            }
-        }
-        userState.uidPermissionFlags.retainAllIndexed { _, appId, _ ->
-            val hasAppId = appId in state.systemState.appIds
-            if (!hasAppId) {
-                Log.w(LOG_TAG, "Dropping unknown app ID $appId when parsing permission state")
-            }
-            hasAppId
-        }
-    }
-
-    private fun BinaryXmlPullParser.parseAppId(userState: UserState) {
-        val appId = getAttributeIntOrThrow(ATTR_ID)
-        val permissionFlags = IndexedMap<String, Int>()
-        userState.uidPermissionFlags[appId] = permissionFlags
-        parseAppIdPermissions(permissionFlags)
-    }
-
-    private fun BinaryXmlPullParser.parseAppIdPermissions(
-        permissionFlags: IndexedMap<String, Int>
-    ) {
-        forEachTag {
-            when (tagName) {
-                TAG_PERMISSION -> parseAppIdPermission(permissionFlags)
-                else -> Log.w(LOG_TAG, "Ignoring unknown tag $name when parsing permission state")
-            }
-        }
-    }
-
-    private fun BinaryXmlPullParser.parseAppIdPermission(permissionFlags: IndexedMap<String, Int>) {
-        val name = getAttributeValueOrThrow(ATTR_NAME).intern()
-        val flags = getAttributeIntOrThrow(ATTR_FLAGS)
-        permissionFlags[name] = flags
-    }
-
-    fun BinaryXmlSerializer.serializeUserState(state: AccessState, userId: Int) {
-        serializePermissionFlags(state.userStates[userId])
-    }
-
-    private fun BinaryXmlSerializer.serializePermissionFlags(userState: UserState) {
-        tag(TAG_PERMISSIONS) {
-            userState.uidPermissionFlags.forEachIndexed { _, appId, permissionFlags ->
-                serializeAppId(appId, permissionFlags)
-            }
-        }
-    }
-
-    private fun BinaryXmlSerializer.serializeAppId(
-        appId: Int,
-        permissionFlags: IndexedMap<String, Int>
-    ) {
-        tag(TAG_APP_ID) {
-            attributeInt(ATTR_ID, appId)
-            serializeAppIdPermissions(permissionFlags)
-        }
-    }
-
-    private fun BinaryXmlSerializer.serializeAppIdPermissions(
-        permissionFlags: IndexedMap<String, Int>
-    ) {
-        permissionFlags.forEachIndexed { _, name, flags ->
-            serializeAppIdPermission(name, flags)
-        }
-    }
-
-    private fun BinaryXmlSerializer.serializeAppIdPermission(name: String, flags: Int) {
-        tag(TAG_PERMISSION) {
-            attributeInterned(ATTR_NAME, name)
-            attributeInt(ATTR_FLAGS, flags)
-        }
-    }
-
-    companion object {
-        private val LOG_TAG = UidPermissionPersistence::class.java.simpleName
-
-        private const val TAG_APP_ID = "app-id"
-        private const val TAG_PERMISSION = "permission"
-        private const val TAG_PERMISSIONS = "permissions"
-        private const val TAG_PERMISSION_TREES = "permission-trees"
-
-        private const val ATTR_FLAGS = "flags"
-        private const val ATTR_ICON = "icon"
-        private const val ATTR_ID = "id"
-        private const val ATTR_LABEL = "label"
-        private const val ATTR_NAME = "name"
-        private const val ATTR_PACKAGE_NAME = "packageName"
-        private const val ATTR_PROTECTION_LEVEL = "protectionLevel"
-        private const val ATTR_TYPE = "type"
-    }
-}
diff --git a/services/permission/java/com/android/server/permission/access/permission/UidPermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/UidPermissionPolicy.kt
deleted file mode 100644
index 5a7b37a..0000000
--- a/services/permission/java/com/android/server/permission/access/permission/UidPermissionPolicy.kt
+++ /dev/null
@@ -1,1468 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.permission.access.permission
-
-import android.Manifest
-import android.content.pm.PackageManager
-import android.content.pm.PermissionGroupInfo
-import android.content.pm.PermissionInfo
-import android.content.pm.SigningDetails
-import android.os.Build
-import android.os.UserHandle
-import android.util.Log
-import com.android.internal.os.RoSystemProperties
-import com.android.modules.utils.BinaryXmlPullParser
-import com.android.modules.utils.BinaryXmlSerializer
-import com.android.server.permission.access.AccessState
-import com.android.server.permission.access.AccessUri
-import com.android.server.permission.access.GetStateScope
-import com.android.server.permission.access.MutateStateScope
-import com.android.server.permission.access.PermissionUri
-import com.android.server.permission.access.SchemePolicy
-import com.android.server.permission.access.UidUri
-import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
-import com.android.server.permission.access.util.andInv
-import com.android.server.permission.access.util.hasAnyBit
-import com.android.server.permission.access.util.hasBits
-import com.android.server.permission.access.util.isInternal
-import com.android.server.pm.KnownPackages
-import com.android.server.pm.parsing.PackageInfoUtils
-import com.android.server.pm.permission.CompatibilityPermissionInfo
-import com.android.server.pm.pkg.AndroidPackage
-import com.android.server.pm.pkg.PackageState
-
-class UidPermissionPolicy : SchemePolicy() {
-    private val persistence = UidPermissionPersistence()
-
-    @Volatile
-    private var onPermissionFlagsChangedListeners =
-        IndexedListSet<OnPermissionFlagsChangedListener>()
-    private val onPermissionFlagsChangedListenersLock = Any()
-
-    private val privilegedPermissionAllowlistViolations = IndexedSet<String>()
-
-    override val subjectScheme: String
-        get() = UidUri.SCHEME
-
-    override val objectScheme: String
-        get() = PermissionUri.SCHEME
-
-    override fun GetStateScope.getDecision(subject: AccessUri, `object`: AccessUri): Int {
-        subject as UidUri
-        `object` as PermissionUri
-        return getPermissionFlags(subject.appId, subject.userId, `object`.permissionName)
-    }
-
-    override fun MutateStateScope.setDecision(
-        subject: AccessUri,
-        `object`: AccessUri,
-        decision: Int
-    ) {
-        subject as UidUri
-        `object` as PermissionUri
-        setPermissionFlags(subject.appId, subject.userId, `object`.permissionName, decision)
-    }
-
-    override fun GetStateScope.onStateMutated() {
-        onPermissionFlagsChangedListeners.forEachIndexed { _, it -> it.onStateMutated() }
-    }
-
-    override fun MutateStateScope.onInitialized() {
-        newState.systemState.configPermissions.forEach { (permissionName, permissionEntry) ->
-            val permissions = newState.systemState.permissions
-            val oldPermission = permissions[permissionName]
-            val newPermission = if (oldPermission != null) {
-                if (permissionEntry.gids != null) {
-                    oldPermission.copy(
-                        gids = permissionEntry.gids, areGidsPerUser = permissionEntry.perUser
-                    )
-                } else {
-                    return@forEach
-                }
-            } else {
-                @Suppress("DEPRECATION")
-                val permissionInfo = PermissionInfo().apply {
-                    name = permissionName
-                    packageName = PLATFORM_PACKAGE_NAME
-                    protectionLevel = PermissionInfo.PROTECTION_SIGNATURE
-                }
-                if (permissionEntry.gids != null) {
-                    Permission(
-                        permissionInfo, false, Permission.TYPE_CONFIG, 0, permissionEntry.gids,
-                        permissionEntry.perUser
-                    )
-                } else {
-                    Permission(permissionInfo, false, Permission.TYPE_CONFIG, 0)
-                }
-            }
-            permissions[permissionName] = newPermission
-        }
-    }
-
-    override fun MutateStateScope.onUserAdded(userId: Int) {
-        newState.systemState.packageStates.forEach { (_, packageState) ->
-            evaluateAllPermissionStatesForPackageAndUser(packageState, userId, null)
-        }
-        newState.systemState.appIds.forEachKeyIndexed { _, appId ->
-            inheritImplicitPermissionStates(appId, userId)
-        }
-    }
-
-    override fun MutateStateScope.onAppIdRemoved(appId: Int) {
-        newState.userStates.forEachValueIndexed { _, userState ->
-            userState.uidPermissionFlags -= appId
-            userState.requestWrite()
-            // Skip notifying the change listeners since the app ID no longer exists.
-        }
-    }
-
-    override fun MutateStateScope.onStorageVolumeMounted(
-        volumeUuid: String?,
-        isSystemUpdated: Boolean
-    ) {
-        val changedPermissionNames = IndexedSet<String>()
-        newState.systemState.packageStates.forEach { (_, packageState) ->
-            val androidPackage = packageState.androidPackage
-            if (androidPackage == null || androidPackage.volumeUuid != volumeUuid) {
-                return@forEach
-            }
-            adoptPermissions(packageState, changedPermissionNames)
-            addPermissionGroups(packageState)
-            addPermissions(packageState, changedPermissionNames)
-            trimPermissions(packageState.packageName, changedPermissionNames)
-            trimPermissionStates(packageState.appId)
-            revokePermissionsOnPackageUpdate(packageState.appId)
-        }
-        changedPermissionNames.forEachIndexed { _, permissionName ->
-            evaluatePermissionStateForAllPackages(permissionName, null)
-        }
-
-        newState.systemState.packageStates.forEach { (_, packageState) ->
-            val androidPackage = packageState.androidPackage
-            if (androidPackage == null || androidPackage.volumeUuid != volumeUuid) {
-                return@forEach
-            }
-            val installedPackageState = if (isSystemUpdated) packageState else null
-            evaluateAllPermissionStatesForPackage(packageState, installedPackageState)
-        }
-        newState.systemState.packageStates.forEach { (_, packageState) ->
-            val androidPackage = packageState.androidPackage
-            if (androidPackage == null || androidPackage.volumeUuid != volumeUuid) {
-                return@forEach
-            }
-            newState.systemState.userIds.forEachIndexed { _, userId ->
-                inheritImplicitPermissionStates(packageState.appId, userId)
-            }
-        }
-    }
-
-    override fun MutateStateScope.onPackageAdded(packageState: PackageState) {
-        val changedPermissionNames = IndexedSet<String>()
-        adoptPermissions(packageState, changedPermissionNames)
-        addPermissionGroups(packageState)
-        addPermissions(packageState, changedPermissionNames)
-        // TODO: revokeSystemAlertWindowIfUpgradedPast23()
-        trimPermissions(packageState.packageName, changedPermissionNames)
-        trimPermissionStates(packageState.appId)
-        revokePermissionsOnPackageUpdate(packageState.appId)
-        changedPermissionNames.forEachIndexed { _, permissionName ->
-            evaluatePermissionStateForAllPackages(permissionName, null)
-        }
-        evaluateAllPermissionStatesForPackage(packageState, packageState)
-        newState.systemState.userIds.forEachIndexed { _, userId ->
-            inheritImplicitPermissionStates(packageState.appId, userId)
-        }
-    }
-
-    override fun MutateStateScope.onPackageRemoved(packageName: String, appId: Int) {
-        // TODO: STOPSHIP: Remove this check or at least turn into logging.
-        check(packageName !in newState.systemState.disabledSystemPackageStates) {
-            "Package $packageName reported as removed before disabled system package is enabled"
-        }
-
-        val changedPermissionNames = IndexedSet<String>()
-        trimPermissions(packageName, changedPermissionNames)
-        if (appId in newState.systemState.appIds) {
-            trimPermissionStates(appId)
-        }
-        changedPermissionNames.forEachIndexed { _, permissionName ->
-            evaluatePermissionStateForAllPackages(permissionName, null)
-        }
-    }
-
-    override fun MutateStateScope.onPackageUninstalled(
-        packageName: String,
-        appId: Int,
-        userId: Int
-    ) {
-        resetRuntimePermissions(packageName, appId, userId)
-    }
-
-    fun MutateStateScope.resetRuntimePermissions(
-        packageName: String,
-        appId: Int,
-        userId: Int
-    ) {
-        val androidPackage = newState.systemState.packageStates[packageName]?.androidPackage
-            ?: return
-        androidPackage.requestedPermissions.forEachIndexed { _, permissionName ->
-            val permission = newState.systemState.permissions[permissionName]
-                ?: return@forEachIndexed
-            if (permission.isRemoved) {
-                return@forEachIndexed
-            }
-            val isRequestedByOtherPackages = anyRequestingPackageInAppId(appId, permissionName) {
-                it.packageName != packageName
-            }
-            if (isRequestedByOtherPackages) {
-                return@forEachIndexed
-            }
-            val oldFlags = getPermissionFlags(appId, userId, permissionName)
-            if (oldFlags.hasAnyBit(SYSTEM_OR_POLICY_FIXED_MASK)) {
-                return@forEachIndexed
-            }
-            var newFlags = oldFlags
-            newFlags = if (
-                newFlags.hasBits(PermissionFlags.ROLE) || newFlags.hasBits(PermissionFlags.PREGRANT)
-            ) {
-                newFlags or PermissionFlags.RUNTIME_GRANTED
-            } else {
-                newFlags andInv PermissionFlags.RUNTIME_GRANTED
-            }
-            newFlags = newFlags andInv USER_SETTABLE_MASK
-            if (newFlags.hasBits(PermissionFlags.LEGACY_GRANTED)) {
-                newFlags = newFlags or PermissionFlags.IMPLICIT
-            }
-            setPermissionFlags(appId, userId, permissionName, newFlags)
-        }
-    }
-
-    private fun MutateStateScope.adoptPermissions(
-        packageState: PackageState,
-        changedPermissionNames: IndexedSet<String>
-    ) {
-        val `package` = packageState.androidPackage!!
-        `package`.adoptPermissions.forEachIndexed { _, originalPackageName ->
-            val packageName = `package`.packageName
-            if (!canAdoptPermissions(packageName, originalPackageName)) {
-                return@forEachIndexed
-            }
-            val systemState = newState.systemState
-            val permissions = systemState.permissions
-            permissions.forEachIndexed permissions@ {
-                permissionIndex, permissionName, oldPermission ->
-                if (oldPermission.packageName != originalPackageName) {
-                    return@permissions
-                }
-                @Suppress("DEPRECATION")
-                val newPermissionInfo = PermissionInfo().apply {
-                    name = oldPermission.permissionInfo.name
-                    this.packageName = packageName
-                    protectionLevel = oldPermission.permissionInfo.protectionLevel
-                }
-                // Different from the old implementation, which removes the GIDs upon permission
-                // adoption, but adds them back on the next boot, we now just consistently keep the
-                // GIDs.
-                val newPermission = oldPermission.copy(
-                    permissionInfo = newPermissionInfo, isReconciled = false, appId = 0
-                )
-                permissions.setValueAt(permissionIndex, newPermission)
-                systemState.requestWrite()
-                changedPermissionNames += permissionName
-            }
-        }
-    }
-
-    private fun MutateStateScope.canAdoptPermissions(
-        packageName: String,
-        originalPackageName: String
-    ): Boolean {
-        val originalPackageState = newState.systemState.packageStates[originalPackageName]
-            ?: return false
-        if (!originalPackageState.isSystem) {
-            Log.w(
-                LOG_TAG, "Unable to adopt permissions from $originalPackageName to $packageName:" +
-                    " original package not in system partition"
-            )
-            return false
-        }
-        if (originalPackageState.androidPackage != null) {
-            Log.w(
-                LOG_TAG, "Unable to adopt permissions from $originalPackageName to $packageName:" +
-                    " original package still exists"
-            )
-            return false
-        }
-        return true
-    }
-
-    private fun MutateStateScope.addPermissionGroups(packageState: PackageState) {
-        // Different from the old implementation, which decides whether the app is an instant app by
-        // the install flags, now for consistent behavior we allow adding permission groups if the
-        // app is non-instant in at least one user.
-        val isInstantApp = packageState.userStates.allIndexed { _, _, it -> it.isInstantApp }
-        if (isInstantApp) {
-            Log.w(
-                LOG_TAG, "Ignoring permission groups declared in package" +
-                    " ${packageState.packageName}: instant apps cannot declare permission groups"
-            )
-            return
-        }
-        packageState.androidPackage!!.permissionGroups.forEachIndexed { _, parsedPermissionGroup ->
-            val newPermissionGroup = PackageInfoUtils.generatePermissionGroupInfo(
-                parsedPermissionGroup, PackageManager.GET_META_DATA.toLong()
-            )!!
-            // TODO: Clear permission state on group take-over?
-            val permissionGroupName = newPermissionGroup.name
-            val oldPermissionGroup = newState.systemState.permissionGroups[permissionGroupName]
-            if (oldPermissionGroup != null &&
-                newPermissionGroup.packageName != oldPermissionGroup.packageName) {
-                val newPackageName = newPermissionGroup.packageName
-                val oldPackageName = oldPermissionGroup.packageName
-                // Different from the old implementation, which defines permission group on
-                // a first-come-first-serve basis, and relies on system apps being scanned before
-                // non-system apps, we now allow system apps to override permission groups similar
-                // to permissions so that we no longer need to rely on the scan order.
-                if (!packageState.isSystem) {
-                    Log.w(
-                        LOG_TAG, "Ignoring permission group $permissionGroupName declared in" +
-                            " package $newPackageName: already declared in another" +
-                            " package $oldPackageName"
-                    )
-                    return@forEachIndexed
-                }
-                if (newState.systemState.packageStates[oldPackageName]?.isSystem == true) {
-                    Log.w(
-                        LOG_TAG, "Ignoring permission group $permissionGroupName declared in" +
-                            " system package $newPackageName: already declared in another" +
-                            " system package $oldPackageName"
-                    )
-                    return@forEachIndexed
-                }
-                Log.w(
-                    LOG_TAG, "Overriding permission group $permissionGroupName with" +
-                        " new declaration in system package $newPackageName: originally" +
-                        " declared in another package $oldPackageName"
-                )
-            }
-            newState.systemState.permissionGroups[permissionGroupName] = newPermissionGroup
-        }
-    }
-
-    private fun MutateStateScope.addPermissions(
-        packageState: PackageState,
-        changedPermissionNames: IndexedSet<String>
-    ) {
-        packageState.androidPackage!!.permissions.forEachIndexed { _, parsedPermission ->
-            // TODO:
-            // parsedPermission.flags = parsedPermission.flags andInv PermissionInfo.FLAG_INSTALLED
-            // TODO: This seems actually unused.
-            // if (packageState.androidPackage.targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1) {
-            //    parsedPermission.setParsedPermissionGroup(
-            //        newState.systemState.permissionGroup[parsedPermission.group]
-            //    )
-            // }
-            val newPermissionInfo = PackageInfoUtils.generatePermissionInfo(
-                parsedPermission, PackageManager.GET_META_DATA.toLong()
-            )!!
-            // TODO: newPermissionInfo.flags |= PermissionInfo.FLAG_INSTALLED
-            val systemState = newState.systemState
-            val permissionName = newPermissionInfo.name
-            val oldPermission = if (parsedPermission.isTree) {
-                systemState.permissionTrees[permissionName]
-            } else {
-                systemState.permissions[permissionName]
-            }
-            // Different from the old implementation, which may add an (incomplete) signature
-            // permission inside another package's permission tree, we now consistently ignore such
-            // permissions.
-            val permissionTree = findPermissionTree(permissionName)
-            val newPackageName = newPermissionInfo.packageName
-            if (permissionTree != null && newPackageName != permissionTree.packageName) {
-                Log.w(
-                    LOG_TAG, "Ignoring permission $permissionName declared in package" +
-                        " $newPackageName: base permission tree ${permissionTree.name} is" +
-                        " declared in another package ${permissionTree.packageName}"
-                )
-                return@forEachIndexed
-            }
-            val newPermission = if (oldPermission != null &&
-                newPackageName != oldPermission.packageName) {
-                val oldPackageName = oldPermission.packageName
-                // Only allow system apps to redefine non-system permissions.
-                if (!packageState.isSystem) {
-                    Log.w(
-                        LOG_TAG, "Ignoring permission $permissionName declared in package" +
-                            " $newPackageName: already declared in another package" +
-                            " $oldPackageName"
-                    )
-                    return@forEachIndexed
-                }
-                if (oldPermission.type == Permission.TYPE_CONFIG && !oldPermission.isReconciled) {
-                    // It's a config permission and has no owner, take ownership now.
-                    oldPermission.copy(
-                        permissionInfo = newPermissionInfo, isReconciled = true,
-                        appId = packageState.appId
-                    )
-                } else if (systemState.packageStates[oldPackageName]?.isSystem != true) {
-                    Log.w(
-                        LOG_TAG, "Overriding permission $permissionName with new declaration in" +
-                            " system package $newPackageName: originally declared in another" +
-                            " package $oldPackageName"
-                    )
-                    // Remove permission state on owner change.
-                    systemState.userIds.forEachIndexed { _, userId ->
-                        systemState.appIds.forEachKeyIndexed { _, appId ->
-                            setPermissionFlags(appId, userId, permissionName, 0)
-                        }
-                    }
-                    // Different from the old implementation, which removes the GIDs upon permission
-                    // override, but adds them back on the next boot, we now just consistently keep
-                    // the GIDs.
-                    Permission(
-                        newPermissionInfo, true, Permission.TYPE_MANIFEST, packageState.appId,
-                        oldPermission.gids, oldPermission.areGidsPerUser
-                    )
-                } else {
-                    Log.w(
-                        LOG_TAG, "Ignoring permission $permissionName declared in system package" +
-                            " $newPackageName: already declared in another system package" +
-                            " $oldPackageName"
-                    )
-                    return@forEachIndexed
-                }
-            } else {
-                if (oldPermission != null) {
-                    val isPermissionGroupChanged = newPermissionInfo.isRuntime &&
-                        newPermissionInfo.group != null &&
-                        newPermissionInfo.group != oldPermission.groupName
-                    val isPermissionTypeChanged = oldPermission.type != Permission.TYPE_CONFIG && (
-                        (newPermissionInfo.isRuntime && !oldPermission.isRuntime) ||
-                            (newPermissionInfo.isInternal && !oldPermission.isInternal)
-                    )
-                    if (isPermissionGroupChanged || isPermissionTypeChanged) {
-                        systemState.userIds.forEachIndexed { _, userId ->
-                            systemState.appIds.forEachKeyIndexed { _, appId ->
-                                if (isPermissionGroupChanged) {
-                                    // We might auto-grant permissions if any permission of
-                                    // the group is already granted. Hence if the group of
-                                    // a granted permission changes we need to revoke it to
-                                    // avoid having permissions of the new group auto-granted.
-                                    Log.w(
-                                        LOG_TAG, "Revoking runtime permission $permissionName for" +
-                                            " appId $appId and userId $userId as the permission" +
-                                            " group changed from ${oldPermission.groupName}" +
-                                            " to ${newPermissionInfo.group}"
-                                    )
-                                }
-                                if (isPermissionTypeChanged) {
-                                    Log.w(
-                                        LOG_TAG, "Revoking permission $permissionName for" +
-                                            " appId $appId and userId $userId as the permission" +
-                                            " type changed."
-                                    )
-                                }
-                                setPermissionFlags(appId, userId, permissionName, 0)
-                            }
-                        }
-                    }
-                }
-
-                // Different from the old implementation, which doesn't update the permission
-                // definition upon app update, but does update it on the next boot, we now
-                // consistently update the permission definition upon app update.
-                @Suppress("IfThenToElvis")
-                if (oldPermission != null) {
-                    oldPermission.copy(
-                        permissionInfo = newPermissionInfo, isReconciled = true,
-                        appId = packageState.appId
-                    )
-                } else {
-                    Permission(
-                        newPermissionInfo, true, Permission.TYPE_MANIFEST, packageState.appId
-                    )
-                }
-            }
-
-            if (parsedPermission.isTree) {
-                systemState.permissionTrees[permissionName] = newPermission
-            } else {
-                systemState.permissions[permissionName] = newPermission
-            }
-            systemState.requestWrite()
-            changedPermissionNames += permissionName
-        }
-    }
-
-    private fun MutateStateScope.trimPermissions(
-        packageName: String,
-        changedPermissionNames: IndexedSet<String>
-    ) {
-        val systemState = newState.systemState
-        val packageState = systemState.packageStates[packageName]
-        val androidPackage = packageState?.androidPackage
-        if (packageState != null && androidPackage == null) {
-            return
-        }
-        val disabledSystemPackage = systemState.disabledSystemPackageStates[packageName]
-            ?.androidPackage
-        // Unlike in the previous implementation, we now also retain permission trees defined by
-        // disabled system packages for consistency with permissions.
-        val isPermissionTreeRemoved = systemState.permissionTrees.removeAllIndexed {
-            _, permissionTreeName, permissionTree ->
-            permissionTree.packageName == packageName && (
-                packageState == null || androidPackage!!.permissions.noneIndexed { _, it ->
-                    it.isTree && it.name == permissionTreeName
-                }
-            ) && (
-                disabledSystemPackage?.permissions?.anyIndexed { _, it ->
-                    it.isTree && it.name == permissionTreeName
-                } != true
-            )
-        }
-        if (isPermissionTreeRemoved) {
-            systemState.requestWrite()
-        }
-
-        systemState.permissions.removeAllIndexed { permissionIndex, permissionName, permission ->
-            val updatedPermission = updatePermissionIfDynamic(permission)
-            newState.systemState.permissions.setValueAt(permissionIndex, updatedPermission)
-            if (updatedPermission.packageName == packageName && (
-                packageState == null || androidPackage!!.permissions.noneIndexed { _, it ->
-                    !it.isTree && it.name == permissionName
-                }
-            ) && (
-                disabledSystemPackage?.permissions?.anyIndexed { _, it ->
-                    !it.isTree && it.name == permissionName
-                } != true
-            )) {
-                // Different from the old implementation where we keep the permission state if the
-                // permission is declared by a disabled system package (ag/15189282), we now
-                // shouldn't be notified when the updated system package is removed but the disabled
-                // system package isn't re-enabled yet, so we don't need to maintain that brittle
-                // special case either.
-                systemState.userIds.forEachIndexed { _, userId ->
-                    systemState.appIds.forEachKeyIndexed { _, appId ->
-                        setPermissionFlags(appId, userId, permissionName, 0)
-                    }
-                }
-                changedPermissionNames += permissionName
-                systemState.requestWrite()
-                true
-            } else {
-                false
-            }
-        }
-    }
-
-    private fun MutateStateScope.updatePermissionIfDynamic(permission: Permission): Permission {
-        if (!permission.isDynamic) {
-            return permission
-        }
-        val permissionTree = findPermissionTree(permission.name) ?: return permission
-        @Suppress("DEPRECATION")
-        return permission.copy(
-            permissionInfo = PermissionInfo(permission.permissionInfo).apply {
-                packageName = permissionTree.packageName
-            }, appId = permissionTree.appId, isReconciled = true
-        )
-    }
-
-    private fun MutateStateScope.trimPermissionStates(appId: Int) {
-        val requestedPermissions = IndexedSet<String>()
-        forEachPackageInAppId(appId) {
-            // Note that we still trim the permission states requested by disabled system packages.
-            // Because in the previous implementation:
-            // despite revokeSharedUserPermissionsForLeavingPackageInternal() retains permissions
-            // requested by disabled system packages, revokeUnusedSharedUserPermissionsLocked(),
-            // which is call upon app update installation, didn't do such preservation.
-            // Hence, permissions only requested by disabled system packages were still trimmed in
-            // the previous implementation.
-            requestedPermissions += it.androidPackage!!.requestedPermissions
-        }
-        newState.userStates.forEachIndexed { _, userId, userState ->
-            userState.uidPermissionFlags[appId]?.forEachReversedIndexed { _, permissionName, _ ->
-                if (permissionName !in requestedPermissions) {
-                    setPermissionFlags(appId, userId, permissionName, 0)
-                }
-            }
-        }
-    }
-
-    private fun MutateStateScope.revokePermissionsOnPackageUpdate(appId: Int) {
-        // If the app is updated, and has scoped storage permissions, then it is possible that the
-        // app updated in an attempt to get unscoped storage. If so, revoke all storage permissions.
-        newState.userStates.forEachIndexed { _, userId, userState ->
-            userState.uidPermissionFlags[appId]?.forEachReversedIndexed {
-                _, permissionName, oldFlags ->
-                if (permissionName !in STORAGE_AND_MEDIA_PERMISSIONS || oldFlags == 0) {
-                    return@forEachReversedIndexed
-                }
-                val oldTargetSdkVersion = getAppIdTargetSdkVersion(appId, permissionName, oldState)
-                val newTargetSdkVersion = getAppIdTargetSdkVersion(appId, permissionName, newState)
-                val isTargetSdkVersionDowngraded = oldTargetSdkVersion >= Build.VERSION_CODES.Q &&
-                    newTargetSdkVersion < Build.VERSION_CODES.Q
-                val isTargetSdkVersionUpgraded = oldTargetSdkVersion < Build.VERSION_CODES.Q &&
-                    newTargetSdkVersion >= Build.VERSION_CODES.Q
-                val oldIsRequestLegacyExternalStorage = anyRequestingPackageInAppId(
-                    appId, permissionName, oldState
-                ) { it.androidPackage!!.isRequestLegacyExternalStorage }
-                val newIsRequestLegacyExternalStorage = anyRequestingPackageInAppId(
-                    appId, permissionName, newState
-                ) { it.androidPackage!!.isRequestLegacyExternalStorage }
-                val isNewlyRequestingLegacyExternalStorage = !isTargetSdkVersionUpgraded &&
-                    !oldIsRequestLegacyExternalStorage && newIsRequestLegacyExternalStorage
-                if ((isNewlyRequestingLegacyExternalStorage || isTargetSdkVersionDowngraded) &&
-                    oldFlags.hasBits(PermissionFlags.RUNTIME_GRANTED)) {
-                    val newFlags = oldFlags andInv (
-                        PermissionFlags.RUNTIME_GRANTED or USER_SETTABLE_MASK
-                    )
-                    setPermissionFlags(appId, userId, permissionName, newFlags)
-                }
-            }
-        }
-    }
-
-    private fun MutateStateScope.evaluatePermissionStateForAllPackages(
-        permissionName: String,
-        installedPackageState: PackageState?
-    ) {
-        val systemState = newState.systemState
-        systemState.userIds.forEachIndexed { _, userId ->
-            systemState.appIds.forEachKeyIndexed { _, appId ->
-                val isPermissionRequested =
-                    anyRequestingPackageInAppId(appId, permissionName) { true }
-                if (isPermissionRequested) {
-                    evaluatePermissionState(appId, userId, permissionName, installedPackageState)
-                }
-            }
-        }
-    }
-
-    private fun MutateStateScope.evaluateAllPermissionStatesForPackage(
-        packageState: PackageState,
-        installedPackageState: PackageState?
-    ) {
-        newState.systemState.userIds.forEachIndexed { _, userId ->
-            evaluateAllPermissionStatesForPackageAndUser(
-                packageState, userId, installedPackageState
-            )
-        }
-    }
-
-    private fun MutateStateScope.evaluateAllPermissionStatesForPackageAndUser(
-        packageState: PackageState,
-        userId: Int,
-        installedPackageState: PackageState?
-    ) {
-        packageState.androidPackage?.requestedPermissions?.forEachIndexed { _, permissionName ->
-            evaluatePermissionState(
-                packageState.appId, userId, permissionName, installedPackageState
-            )
-        }
-    }
-
-    private fun MutateStateScope.evaluatePermissionState(
-        appId: Int,
-        userId: Int,
-        permissionName: String,
-        installedPackageState: PackageState?
-    ) {
-        val packageNames = newState.systemState.appIds[appId]
-        val hasMissingPackage = packageNames.anyIndexed { _, packageName ->
-            newState.systemState.packageStates[packageName]!!.androidPackage == null
-        }
-        if (packageNames.size == 1 && hasMissingPackage) {
-            // For non-shared-user packages with missing androidPackage, skip evaluation.
-            return
-        }
-        val permission = newState.systemState.permissions[permissionName]
-        val oldFlags = getPermissionFlags(appId, userId, permissionName)
-        if (permission == null) {
-            if (oldFlags == 0) {
-                // If the permission definition is missing and we don't have any permission states
-                // for this permission, add the INSTALL_REVOKED flag to ensure that we don't
-                // automatically grant the permission when it's defined
-                setPermissionFlags(appId, userId, permissionName, PermissionFlags.INSTALL_REVOKED)
-            }
-            return
-        }
-        if (permission.isNormal) {
-            val wasGranted = oldFlags.hasBits(PermissionFlags.INSTALL_GRANTED)
-            if (!wasGranted) {
-                val wasRevoked = oldFlags.hasBits(PermissionFlags.INSTALL_REVOKED)
-                val isRequestedByInstalledPackage = installedPackageState != null &&
-                    permissionName in installedPackageState.androidPackage!!.requestedPermissions
-                val isRequestedBySystemPackage =
-                    anyRequestingPackageInAppId(appId, permissionName) { it.isSystem }
-                val isCompatibilityPermission = anyRequestingPackageInAppId(appId, permissionName) {
-                    isCompatibilityPermissionForPackage(it.androidPackage!!, permissionName)
-                }
-                // If this is an existing, non-system package,
-                // then we can't add any new permissions to it.
-                // Except if this is a permission that was added to the platform
-                val newFlags = if (!wasRevoked || isRequestedByInstalledPackage ||
-                    isRequestedBySystemPackage || isCompatibilityPermission) {
-                    PermissionFlags.INSTALL_GRANTED
-                } else {
-                    PermissionFlags.INSTALL_REVOKED
-                }
-                setPermissionFlags(appId, userId, permissionName, newFlags)
-            }
-        } else if (permission.isSignature || permission.isInternal) {
-            val wasProtectionGranted = oldFlags.hasBits(PermissionFlags.PROTECTION_GRANTED)
-            var newFlags = if (hasMissingPackage && wasProtectionGranted) {
-                // Keep the non-runtime permission grants for shared UID with missing androidPackage
-                PermissionFlags.PROTECTION_GRANTED
-            } else {
-                val mayGrantByPrivileged = !permission.isPrivileged || (
-                    anyRequestingPackageInAppId(appId, permissionName) {
-                        checkPrivilegedPermissionAllowlist(it, permission)
-                    }
-                )
-                val shouldGrantBySignature = permission.isSignature && (
-                    anyRequestingPackageInAppId(appId, permissionName) {
-                        shouldGrantPermissionBySignature(it, permission)
-                    }
-                )
-                val shouldGrantByProtectionFlags =
-                    anyRequestingPackageInAppId(appId, permissionName) {
-                        shouldGrantPermissionByProtectionFlags(it, permission)
-                    }
-                if (mayGrantByPrivileged &&
-                    (shouldGrantBySignature || shouldGrantByProtectionFlags)) {
-                    PermissionFlags.PROTECTION_GRANTED
-                } else {
-                    0
-                }
-            }
-            // Different from the old implementation, which seemingly allows granting an
-            // unallowlisted privileged permission via development or role but revokes it upon next
-            // reconciliation, we now properly allows that because the privileged protection flag
-            // should only affect the other static flags, but not dynamic flags like development or
-            // role. This may be useful in the case of an updated system app.
-            if (permission.isDevelopment) {
-                newFlags = newFlags or (oldFlags and PermissionFlags.RUNTIME_GRANTED)
-            }
-            if (permission.isRole) {
-                newFlags = newFlags or (
-                    oldFlags and (PermissionFlags.ROLE or PermissionFlags.RUNTIME_GRANTED)
-                )
-            }
-            setPermissionFlags(appId, userId, permissionName, newFlags)
-        } else if (permission.isRuntime) {
-            var newFlags = oldFlags and PermissionFlags.MASK_RUNTIME
-            if (getAppIdTargetSdkVersion(appId, permissionName) < Build.VERSION_CODES.M) {
-                if (permission.isRuntimeOnly) {
-                    // Different from the old implementation, which simply skips a runtime-only
-                    // permission, we now only allow holding on to the restriction related flags,
-                    // since such flags may only be set one-time in some cases, and disallow all
-                    // other flags thus keeping it revoked.
-                    newFlags = newFlags and PermissionFlags.MASK_EXEMPT
-                } else {
-                    newFlags = newFlags or PermissionFlags.LEGACY_GRANTED
-                    // Explicitly check against the old state to determine if this permission is
-                    // new.
-                    val isNewPermission =
-                        getOldStatePermissionFlags(appId, userId, permissionName) == 0
-                    if (isNewPermission) {
-                        newFlags = newFlags or PermissionFlags.IMPLICIT
-                    }
-                }
-            } else {
-                val wasGrantedByLegacy = newFlags.hasBits(PermissionFlags.LEGACY_GRANTED)
-                newFlags = newFlags andInv PermissionFlags.LEGACY_GRANTED
-                val wasGrantedByImplicit = newFlags.hasBits(PermissionFlags.IMPLICIT_GRANTED)
-                val isLeanbackNotificationsPermission = newState.systemState.isLeanback &&
-                    permissionName in NOTIFICATIONS_PERMISSIONS
-                val isImplicitPermission = anyRequestingPackageInAppId(appId, permissionName) {
-                    permissionName in it.androidPackage!!.implicitPermissions
-                }
-                val sourcePermissions = newState.systemState
-                    .implicitToSourcePermissions[permissionName]
-                val isAnySourcePermissionNonRuntime = sourcePermissions?.any {
-                    val sourcePermission = newState.systemState.permissions[it]
-                    checkNotNull(sourcePermission) {
-                        "Unknown source permission $it in split permissions"
-                    }
-                    !sourcePermission.isRuntime
-                } ?: false
-                val shouldGrantByImplicit = isLeanbackNotificationsPermission ||
-                    (isImplicitPermission && isAnySourcePermissionNonRuntime)
-                if (shouldGrantByImplicit) {
-                    newFlags = newFlags or PermissionFlags.IMPLICIT_GRANTED
-                } else {
-                    newFlags = newFlags andInv PermissionFlags.IMPLICIT_GRANTED
-                }
-                if ((wasGrantedByLegacy || wasGrantedByImplicit) && !shouldGrantByImplicit) {
-                    // The permission was granted from a compatibility grant or an implicit grant,
-                    // however this flag might still be set if the user denied this permission in
-                    // the settings. Hence upon app upgrade and when this permission is no longer
-                    // LEGACY_GRANTED or IMPLICIT_GRANTED and we revoke the permission, we want to
-                    // remove this flag so that the app can request the permission again.
-                    newFlags = newFlags andInv PermissionFlags.APP_OP_REVOKED
-                }
-                val hasImplicitFlag = newFlags.hasBits(PermissionFlags.IMPLICIT)
-                if (!isImplicitPermission && hasImplicitFlag) {
-                    newFlags = newFlags andInv PermissionFlags.IMPLICIT
-                    var shouldRetainAsNearbyDevices = false
-                    if (permissionName in NEARBY_DEVICES_PERMISSIONS) {
-                        val accessBackgroundLocationFlags = getPermissionFlags(
-                            appId, userId, Manifest.permission.ACCESS_BACKGROUND_LOCATION
-                        )
-                        shouldRetainAsNearbyDevices =
-                            PermissionFlags.isAppOpGranted(accessBackgroundLocationFlags) &&
-                                !accessBackgroundLocationFlags.hasBits(PermissionFlags.IMPLICIT)
-                    }
-                    val shouldRetainByMask = newFlags.hasAnyBit(SYSTEM_OR_POLICY_FIXED_MASK)
-                    if (shouldRetainAsNearbyDevices || shouldRetainByMask) {
-                        if (wasGrantedByImplicit) {
-                            newFlags = newFlags or PermissionFlags.RUNTIME_GRANTED
-                        }
-                    } else {
-                        newFlags = newFlags andInv (
-                            PermissionFlags.RUNTIME_GRANTED or PermissionFlags.USER_SET or
-                                PermissionFlags.USER_FIXED
-                        )
-                    }
-                }
-            }
-
-            val isExempt = newFlags.hasAnyBit(PermissionFlags.MASK_EXEMPT)
-            val isHardRestricted = permission.isHardRestricted && !isExempt
-            newFlags = if (isHardRestricted) {
-                newFlags or PermissionFlags.RESTRICTION_REVOKED
-            } else {
-                newFlags andInv PermissionFlags.RESTRICTION_REVOKED
-            }
-            val isSoftRestricted = permission.isSoftRestricted && !isExempt
-            newFlags = if (isSoftRestricted) {
-                newFlags or PermissionFlags.SOFT_RESTRICTED
-            } else {
-                newFlags andInv PermissionFlags.SOFT_RESTRICTED
-            }
-            setPermissionFlags(appId, userId, permissionName, newFlags)
-        } else {
-            Log.e(LOG_TAG, "Unknown protection level ${permission.protectionLevel}" +
-                "for permission ${permission.name} while evaluating permission state" +
-                "for appId $appId and userId $userId")
-        }
-    }
-
-    private fun MutateStateScope.inheritImplicitPermissionStates(appId: Int, userId: Int) {
-        val implicitPermissions = IndexedSet<String>()
-        forEachPackageInAppId(appId) {
-            implicitPermissions += it.androidPackage!!.implicitPermissions
-        }
-        implicitPermissions.forEachIndexed implicitPermissions@ { _, implicitPermissionName ->
-            val implicitPermission = newState.systemState.permissions[implicitPermissionName]
-            checkNotNull(implicitPermission) {
-                "Unknown implicit permission $implicitPermissionName in split permissions"
-            }
-            if (!implicitPermission.isRuntime) {
-                return@implicitPermissions
-            }
-            // Explicitly check against the old state to determine if this permission is new.
-            val isNewPermission =
-                getOldStatePermissionFlags(appId, userId, implicitPermissionName) == 0
-            if (!isNewPermission) {
-                return@implicitPermissions
-            }
-            val sourcePermissions = newState.systemState
-                .implicitToSourcePermissions[implicitPermissionName] ?: return@implicitPermissions
-            var newFlags = getPermissionFlags(appId, userId, implicitPermissionName)
-            sourcePermissions.forEachIndexed sourcePermissions@ { _, sourcePermissionName ->
-                val sourcePermission = newState.systemState.permissions[sourcePermissionName]
-                checkNotNull(sourcePermission) {
-                    "Unknown source permission $sourcePermissionName in split permissions"
-                }
-                val sourceFlags = getPermissionFlags(appId, userId, sourcePermissionName)
-                val isSourceGranted = PermissionFlags.isPermissionGranted(sourceFlags)
-                val isNewGranted = PermissionFlags.isPermissionGranted(newFlags)
-                val isGrantingNewFromRevoke = isSourceGranted && !isNewGranted
-                if (isSourceGranted == isNewGranted || isGrantingNewFromRevoke) {
-                    if (isGrantingNewFromRevoke) {
-                        newFlags = 0
-                    }
-                    newFlags = newFlags or (sourceFlags and PermissionFlags.MASK_RUNTIME)
-                }
-            }
-            if (implicitPermissionName in RETAIN_IMPLICIT_FLAGS_PERMISSIONS) {
-                newFlags = newFlags andInv PermissionFlags.IMPLICIT
-            } else {
-                newFlags = newFlags or PermissionFlags.IMPLICIT
-            }
-            setPermissionFlags(appId, userId, implicitPermissionName, newFlags)
-        }
-    }
-
-    private fun isCompatibilityPermissionForPackage(
-        androidPackage: AndroidPackage,
-        permissionName: String
-    ): Boolean {
-        for (compatibilityPermission in CompatibilityPermissionInfo.COMPAT_PERMS) {
-            if (compatibilityPermission.name == permissionName &&
-                androidPackage.targetSdkVersion < compatibilityPermission.sdkVersion) {
-                Log.i(
-                    LOG_TAG, "Auto-granting $permissionName to old package" +
-                    " ${androidPackage.packageName}"
-                )
-                return true
-            }
-        }
-        return false
-    }
-
-    private fun MutateStateScope.shouldGrantPermissionBySignature(
-        packageState: PackageState,
-        permission: Permission
-    ): Boolean {
-        // Check if the package is allowed to use this signature permission.  A package is allowed
-        // to use a signature permission if:
-        // - it has the same set of signing certificates as the source package
-        // - or its signing certificate was rotated from the source package's certificate
-        // - or its signing certificate is a previous signing certificate of the defining
-        //     package, and the defining package still trusts the old certificate for permissions
-        // - or it shares a common signing certificate in its lineage with the defining package,
-        //     and the defining package still trusts the old certificate for permissions
-        // - or it shares the above relationships with the system package
-        val packageSigningDetails = packageState.androidPackage!!.signingDetails
-        val sourceSigningDetails = newState.systemState
-            .packageStates[permission.packageName]?.androidPackage?.signingDetails
-        val platformSigningDetails = newState.systemState
-            .packageStates[PLATFORM_PACKAGE_NAME]!!.androidPackage!!.signingDetails
-        return sourceSigningDetails?.hasCommonSignerWithCapability(packageSigningDetails,
-            SigningDetails.CertCapabilities.PERMISSION) == true ||
-            packageSigningDetails.hasAncestorOrSelf(platformSigningDetails) ||
-            platformSigningDetails.checkCapability(packageSigningDetails,
-                    SigningDetails.CertCapabilities.PERMISSION)
-    }
-
-    private fun MutateStateScope.checkPrivilegedPermissionAllowlist(
-        packageState: PackageState,
-        permission: Permission
-    ): Boolean {
-        if (RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_DISABLE) {
-            return true
-        }
-        if (packageState.packageName == PLATFORM_PACKAGE_NAME) {
-            return true
-        }
-        if (!(packageState.isSystem && packageState.isPrivileged)) {
-            return true
-        }
-        if (permission.packageName !in newState.systemState.privilegedPermissionAllowlistPackages) {
-            return true
-        }
-        val allowlistState = getPrivilegedPermissionAllowlistState(packageState, permission.name)
-        if (allowlistState != null) {
-            return allowlistState
-        }
-        // Updated system apps do not need to be allowlisted
-        if (packageState.isUpdatedSystemApp) {
-            return true
-        }
-        // Only enforce the privileged permission allowlist on boot
-        if (!newState.systemState.isSystemReady) {
-            // Apps that are in updated apex's do not need to be allowlisted
-            if (!packageState.isApkInUpdatedApex) {
-                Log.w(
-                    LOG_TAG, "Privileged permission ${permission.name} for package" +
-                    " ${packageState.packageName} (${packageState.path}) not in" +
-                    " privileged permission allowlist"
-                )
-                if (RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_ENFORCE) {
-                    privilegedPermissionAllowlistViolations += "${packageState.packageName}" +
-                        " (${packageState.path}): ${permission.name}"
-                }
-            }
-        }
-        return !RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_ENFORCE
-    }
-
-    /**
-     * Get the whether a privileged permission is explicitly allowed or denied for a package in the
-     * allowlist, or `null` if it's not in the allowlist.
-     */
-    private fun MutateStateScope.getPrivilegedPermissionAllowlistState(
-        packageState: PackageState,
-        permissionName: String
-    ): Boolean? {
-        val permissionAllowlist = newState.systemState.permissionAllowlist
-        val apexModuleName = packageState.apexModuleName
-        val packageName = packageState.packageName
-        return when {
-            packageState.isVendor -> permissionAllowlist.getVendorPrivilegedAppAllowlistState(
-                packageName, permissionName
-            )
-            packageState.isProduct -> permissionAllowlist.getProductPrivilegedAppAllowlistState(
-                packageName, permissionName
-            )
-            packageState.isSystemExt ->
-                permissionAllowlist.getSystemExtPrivilegedAppAllowlistState(
-                    packageName, permissionName
-                )
-            apexModuleName != null -> {
-                val nonApexAllowlistState = permissionAllowlist.getPrivilegedAppAllowlistState(
-                    packageName, permissionName
-                )
-                if (nonApexAllowlistState != null) {
-                    // TODO(andreionea): Remove check as soon as all apk-in-apex
-                    // permission allowlists are migrated.
-                    Log.w(
-                        LOG_TAG, "Package $packageName is an APK in APEX but has permission" +
-                            " allowlist on the system image, please bundle the allowlist in the" +
-                            " $apexModuleName APEX instead"
-                    )
-                }
-                val apexAllowlistState = permissionAllowlist.getApexPrivilegedAppAllowlistState(
-                    apexModuleName, packageName, permissionName
-                )
-                apexAllowlistState ?: nonApexAllowlistState
-            }
-            else -> permissionAllowlist.getPrivilegedAppAllowlistState(packageName, permissionName)
-        }
-    }
-
-    private fun MutateStateScope.getAppIdTargetSdkVersion(
-        appId: Int,
-        permissionName: String,
-        state: AccessState = newState
-    ): Int {
-        var targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT
-        forEachRequestingPackageInAppId(appId, permissionName, state) {
-            targetSdkVersion = targetSdkVersion.coerceAtMost(it.androidPackage!!.targetSdkVersion)
-        }
-        return targetSdkVersion
-    }
-
-    private inline fun MutateStateScope.anyRequestingPackageInAppId(
-        appId: Int,
-        permissionName: String,
-        state: AccessState = newState,
-        predicate: (PackageState) -> Boolean
-    ): Boolean {
-        val packageNames = state.systemState.appIds[appId]
-        return packageNames.anyIndexed { _, packageName ->
-            val packageState = state.systemState.packageStates[packageName]!!
-            val androidPackage = packageState.androidPackage
-            androidPackage != null && permissionName in androidPackage.requestedPermissions &&
-                predicate(packageState)
-        }
-    }
-
-    private inline fun MutateStateScope.forEachPackageInAppId(
-        appId: Int,
-        state: AccessState = newState,
-        action: (PackageState) -> Unit
-    ) {
-        val packageNames = state.systemState.appIds[appId]!!
-        packageNames.forEachIndexed { _, packageName ->
-            val packageState = state.systemState.packageStates[packageName]!!
-            if (packageState.androidPackage != null) {
-                action(packageState)
-            }
-        }
-    }
-
-    private inline fun MutateStateScope.forEachRequestingPackageInAppId(
-        appId: Int,
-        permissionName: String,
-        state: AccessState = newState,
-        action: (PackageState) -> Unit
-    ) {
-        val packageNames = state.systemState.appIds[appId]
-        packageNames.forEachIndexed { _, packageName ->
-            val packageState = state.systemState.packageStates[packageName]!!
-            val androidPackage = packageState.androidPackage
-            if (androidPackage != null && permissionName in androidPackage.requestedPermissions) {
-                action(packageState)
-            }
-        }
-    }
-
-    private fun MutateStateScope.shouldGrantPermissionByProtectionFlags(
-        packageState: PackageState,
-        permission: Permission
-    ): Boolean {
-        val androidPackage = packageState.androidPackage!!
-        val knownPackages = newState.systemState.knownPackages
-        val packageName = packageState.packageName
-        if ((permission.isPrivileged || permission.isOem) && packageState.isSystem) {
-            val shouldGrant = if (packageState.isUpdatedSystemApp) {
-                // For updated system applications, a privileged/oem permission
-                // is granted only if it had been defined by the original application.
-                val disabledSystemPackageState = newState.systemState
-                    .disabledSystemPackageStates[packageState.packageName]
-                val disabledSystemPackage = disabledSystemPackageState?.androidPackage
-                disabledSystemPackage != null &&
-                    permission.name in disabledSystemPackage.requestedPermissions &&
-                    shouldGrantPrivilegedOrOemPermission(disabledSystemPackageState, permission)
-            } else {
-                shouldGrantPrivilegedOrOemPermission(packageState, permission)
-            }
-            if (shouldGrant) {
-                return true
-            }
-        }
-        if (permission.isPre23 && androidPackage.targetSdkVersion < Build.VERSION_CODES.M) {
-            // If this was a previously normal/dangerous permission that got moved
-            // to a system permission as part of the runtime permission redesign, then
-            // we still want to blindly grant it to old apps.
-            return true
-        }
-        if (permission.isInstaller && (
-            packageName in knownPackages[KnownPackages.PACKAGE_INSTALLER] ||
-                packageName in knownPackages[KnownPackages.PACKAGE_PERMISSION_CONTROLLER]
-        )) {
-            // If this permission is to be granted to the system installer and
-            // this app is an installer or permission controller, then it gets the permission.
-            return true
-        }
-        if (permission.isVerifier &&
-            packageName in knownPackages[KnownPackages.PACKAGE_VERIFIER]) {
-            // If this permission is to be granted to the system verifier and
-            // this app is a verifier, then it gets the permission.
-            return true
-        }
-        if (permission.isPreInstalled && packageState.isSystem) {
-            // Any pre-installed system app is allowed to get this permission.
-            return true
-        }
-        if (permission.isKnownSigner &&
-            androidPackage.signingDetails.hasAncestorOrSelfWithDigest(permission.knownCerts)) {
-            // If the permission is to be granted to a known signer then check if any of this
-            // app's signing certificates are in the trusted certificate digest Set.
-            return true
-        }
-        if (permission.isSetup &&
-            packageName in knownPackages[KnownPackages.PACKAGE_SETUP_WIZARD]) {
-            // If this permission is to be granted to the system setup wizard and
-            // this app is a setup wizard, then it gets the permission.
-            return true
-        }
-        if (permission.isSystemTextClassifier &&
-            packageName in knownPackages[KnownPackages.PACKAGE_SYSTEM_TEXT_CLASSIFIER]) {
-            // Special permissions for the system default text classifier.
-            return true
-        }
-        if (permission.isConfigurator &&
-            packageName in knownPackages[KnownPackages.PACKAGE_CONFIGURATOR]) {
-            // Special permissions for the device configurator.
-            return true
-        }
-        if (permission.isIncidentReportApprover &&
-            packageName in knownPackages[KnownPackages.PACKAGE_INCIDENT_REPORT_APPROVER]) {
-            // If this permission is to be granted to the incident report approver and
-            // this app is the incident report approver, then it gets the permission.
-            return true
-        }
-        if (permission.isAppPredictor &&
-            packageName in knownPackages[KnownPackages.PACKAGE_APP_PREDICTOR]) {
-            // Special permissions for the system app predictor.
-            return true
-        }
-        if (permission.isCompanion &&
-            packageName in knownPackages[KnownPackages.PACKAGE_COMPANION]) {
-            // Special permissions for the system companion device manager.
-            return true
-        }
-        if (permission.isRetailDemo &&
-            packageName in knownPackages[KnownPackages.PACKAGE_RETAIL_DEMO] &&
-            isDeviceOrProfileOwnerUid(packageState.appId)) {
-            // Special permission granted only to the OEM specified retail demo app.
-            // Note that the original code was passing app ID as UID, so this behavior is kept
-            // unchanged.
-            return true
-        }
-        if (permission.isRecents &&
-            packageName in knownPackages[KnownPackages.PACKAGE_RECENTS]) {
-            // Special permission for the recents app.
-            return true
-        }
-        if (permission.isModule && packageState.apexModuleName != null) {
-            // Special permission granted for APKs inside APEX modules.
-            return true
-        }
-        return false
-    }
-
-    private fun MutateStateScope.shouldGrantPrivilegedOrOemPermission(
-        packageState: PackageState,
-        permission: Permission
-    ): Boolean {
-        val permissionName = permission.name
-        val packageName = packageState.packageName
-        when {
-            permission.isPrivileged -> {
-                if (packageState.isPrivileged) {
-                    // In any case, don't grant a privileged permission to privileged vendor apps,
-                    // if the permission's protectionLevel does not have the extra vendorPrivileged
-                    // flag.
-                    if (packageState.isVendor && !permission.isVendorPrivileged) {
-                        Log.w(
-                            LOG_TAG, "Permission $permissionName cannot be granted to privileged" +
-                            " vendor app $packageName because it isn't a vendorPrivileged" +
-                            " permission"
-                        )
-                        return false
-                    }
-                    return true
-                }
-            }
-            permission.isOem -> {
-                if (packageState.isOem) {
-                    val allowlistState = newState.systemState.permissionAllowlist
-                        .getOemAppAllowlistState(packageName, permissionName)
-                    checkNotNull(allowlistState) {
-                        "OEM permission $permissionName requested by package" +
-                            " $packageName must be explicitly declared granted or not"
-                    }
-                    return allowlistState
-                }
-            }
-        }
-        return false
-    }
-
-    private fun MutateStateScope.isDeviceOrProfileOwnerUid(uid: Int): Boolean {
-        val userId = UserHandle.getUserId(uid)
-        val ownerPackageName = newState.systemState.deviceAndProfileOwners[userId] ?: return false
-        val ownerPackageState = newState.systemState.packageStates[ownerPackageName] ?: return false
-        val ownerUid = UserHandle.getUid(userId, ownerPackageState.appId)
-        return uid == ownerUid
-    }
-
-    override fun MutateStateScope.onSystemReady() {
-        if (!privilegedPermissionAllowlistViolations.isEmpty()) {
-            throw IllegalStateException("Signature|privileged permissions not in privileged" +
-                " permission allowlist: $privilegedPermissionAllowlistViolations")
-        }
-    }
-
-    override fun BinaryXmlPullParser.parseSystemState(state: AccessState) {
-        with(persistence) { this@parseSystemState.parseSystemState(state) }
-    }
-
-    override fun BinaryXmlSerializer.serializeSystemState(state: AccessState) {
-        with(persistence) { this@serializeSystemState.serializeSystemState(state) }
-    }
-
-    override fun BinaryXmlPullParser.parseUserState(state: AccessState, userId: Int) {
-        with(persistence) { this@parseUserState.parseUserState(state, userId) }
-    }
-
-    override fun BinaryXmlSerializer.serializeUserState(state: AccessState, userId: Int) {
-        with(persistence) { this@serializeUserState.serializeUserState(state, userId) }
-    }
-
-    fun GetStateScope.getPermissionTrees(): IndexedMap<String, Permission> =
-        state.systemState.permissionTrees
-
-    fun GetStateScope.findPermissionTree(permissionName: String): Permission? =
-        state.systemState.permissionTrees.firstNotNullOfOrNullIndexed {
-                _, permissionTreeName, permissionTree ->
-            if (permissionName.startsWith(permissionTreeName) &&
-                permissionName.length > permissionTreeName.length &&
-                permissionName[permissionTreeName.length] == '.') {
-                permissionTree
-            } else {
-                null
-            }
-        }
-
-    fun MutateStateScope.addPermissionTree(permission: Permission) {
-        newState.systemState.permissionTrees[permission.name] = permission
-        newState.systemState.requestWrite()
-    }
-
-    /**
-     * returns all permission group definitions available in the system
-     */
-    fun GetStateScope.getPermissionGroups(): IndexedMap<String, PermissionGroupInfo> =
-        state.systemState.permissionGroups
-
-    /**
-     * returns all permission definitions available in the system
-     */
-    fun GetStateScope.getPermissions(): IndexedMap<String, Permission> =
-        state.systemState.permissions
-
-    fun MutateStateScope.addPermission(permission: Permission, sync: Boolean = false) {
-        newState.systemState.permissions[permission.name] = permission
-        newState.systemState.requestWrite(sync)
-    }
-
-    fun MutateStateScope.removePermission(permission: Permission) {
-        newState.systemState.permissions -= permission.name
-        newState.systemState.requestWrite()
-    }
-
-    fun GetStateScope.getUidPermissionFlags(appId: Int, userId: Int): IndexedMap<String, Int>? =
-        state.userStates[userId]?.uidPermissionFlags?.get(appId)
-
-    fun GetStateScope.getPermissionFlags(
-        appId: Int,
-        userId: Int,
-        permissionName: String
-    ): Int = getPermissionFlags(state, appId, userId, permissionName)
-
-    private fun MutateStateScope.getOldStatePermissionFlags(
-        appId: Int,
-        userId: Int,
-        permissionName: String
-    ): Int = getPermissionFlags(oldState, appId, userId, permissionName)
-
-    private fun getPermissionFlags(
-        state: AccessState,
-        appId: Int,
-        userId: Int,
-        permissionName: String
-    ): Int =
-        state.userStates[userId]?.uidPermissionFlags?.get(appId).getWithDefault(permissionName, 0)
-
-    fun MutateStateScope.setPermissionFlags(
-        appId: Int,
-        userId: Int,
-        permissionName: String,
-        flags: Int
-    ): Boolean =
-        updatePermissionFlags(appId, userId, permissionName, PermissionFlags.MASK_ALL, flags)
-
-    fun MutateStateScope.updatePermissionFlags(
-        appId: Int,
-        userId: Int,
-        permissionName: String,
-        flagMask: Int,
-        flagValues: Int
-    ): Boolean {
-        val userState = newState.userStates[userId]
-        val uidPermissionFlags = userState.uidPermissionFlags
-        var permissionFlags = uidPermissionFlags[appId]
-        val oldFlags = permissionFlags.getWithDefault(permissionName, 0)
-        val newFlags = (oldFlags andInv flagMask) or (flagValues and flagMask)
-        if (oldFlags == newFlags) {
-            return false
-        }
-        if (permissionFlags == null) {
-            permissionFlags = IndexedMap()
-            uidPermissionFlags[appId] = permissionFlags
-        }
-        permissionFlags.putWithDefault(permissionName, newFlags, 0)
-        if (permissionFlags.isEmpty()) {
-            uidPermissionFlags -= appId
-        }
-        userState.requestWrite()
-        onPermissionFlagsChangedListeners.forEachIndexed { _, it ->
-            it.onPermissionFlagsChanged(appId, userId, permissionName, oldFlags, newFlags)
-        }
-        return true
-    }
-
-    fun addOnPermissionFlagsChangedListener(listener: OnPermissionFlagsChangedListener) {
-        synchronized(onPermissionFlagsChangedListenersLock) {
-            onPermissionFlagsChangedListeners = onPermissionFlagsChangedListeners + listener
-        }
-    }
-
-    fun removeOnPermissionFlagsChangedListener(listener: OnPermissionFlagsChangedListener) {
-        synchronized(onPermissionFlagsChangedListenersLock) {
-            onPermissionFlagsChangedListeners = onPermissionFlagsChangedListeners - listener
-        }
-    }
-
-    companion object {
-        private val LOG_TAG = UidPermissionPolicy::class.java.simpleName
-
-        private const val PLATFORM_PACKAGE_NAME = "android"
-
-        // A set of permissions that we don't want to revoke when they are no longer implicit.
-        private val RETAIN_IMPLICIT_FLAGS_PERMISSIONS = indexedSetOf(
-            Manifest.permission.ACCESS_MEDIA_LOCATION,
-            Manifest.permission.ACTIVITY_RECOGNITION,
-            Manifest.permission.READ_MEDIA_AUDIO,
-            Manifest.permission.READ_MEDIA_IMAGES,
-            Manifest.permission.READ_MEDIA_VIDEO,
-        )
-
-        private val NEARBY_DEVICES_PERMISSIONS = indexedSetOf(
-            Manifest.permission.BLUETOOTH_ADVERTISE,
-            Manifest.permission.BLUETOOTH_CONNECT,
-            Manifest.permission.BLUETOOTH_SCAN,
-            Manifest.permission.NEARBY_WIFI_DEVICES
-        )
-
-        private val NOTIFICATIONS_PERMISSIONS = indexedSetOf(
-            Manifest.permission.POST_NOTIFICATIONS
-        )
-
-        private val STORAGE_AND_MEDIA_PERMISSIONS = indexedSetOf(
-            Manifest.permission.READ_EXTERNAL_STORAGE,
-            Manifest.permission.WRITE_EXTERNAL_STORAGE,
-            Manifest.permission.READ_MEDIA_AUDIO,
-            Manifest.permission.READ_MEDIA_VIDEO,
-            Manifest.permission.READ_MEDIA_IMAGES,
-            Manifest.permission.ACCESS_MEDIA_LOCATION,
-            Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED
-        )
-
-        /**
-         * Mask for all permission flags that can be set by the user
-         */
-        private const val USER_SETTABLE_MASK =
-            PermissionFlags.USER_SET or
-                PermissionFlags.USER_FIXED or
-                PermissionFlags.APP_OP_REVOKED or
-                PermissionFlags.ONE_TIME or
-                PermissionFlags.HIBERNATION or
-                PermissionFlags.USER_SELECTED
-
-        /**
-         * Mask for all permission flags that imply we shouldn't automatically modify the
-         * permission grant state.
-         */
-        private const val SYSTEM_OR_POLICY_FIXED_MASK =
-            PermissionFlags.SYSTEM_FIXED or PermissionFlags.POLICY_FIXED
-    }
-
-    /**
-     * Listener for permission flags changes.
-     */
-    abstract class OnPermissionFlagsChangedListener {
-        /**
-         * Called when a permission flags change has been made to the upcoming new state.
-         *
-         * Implementations should keep this method fast to avoid stalling the locked state mutation,
-         * and only call external code after [onStateMutated] when the new state has actually become
-         * the current state visible to external code.
-         */
-        abstract fun onPermissionFlagsChanged(
-            appId: Int,
-            userId: Int,
-            permissionName: String,
-            oldFlags: Int,
-            newFlags: Int
-        )
-
-        /**
-         * Called when the upcoming new state has become the current state.
-         *
-         * Implementations should keep this method fast to avoid stalling the locked state mutation.
-         */
-        abstract fun onStateMutated()
-    }
-}
diff --git a/services/permission/java/com/android/server/permission/access/util/AtomicFileExtensions.kt b/services/permission/java/com/android/server/permission/access/util/AtomicFileExtensions.kt
index 984dfb5..bd82935 100644
--- a/services/permission/java/com/android/server/permission/access/util/AtomicFileExtensions.kt
+++ b/services/permission/java/com/android/server/permission/access/util/AtomicFileExtensions.kt
@@ -16,17 +16,54 @@
 
 package com.android.server.permission.access.util
 
+import android.os.FileUtils
 import android.util.AtomicFile
+import android.util.Slog
+import java.io.File
 import java.io.FileInputStream
+import java.io.FileNotFoundException
 import java.io.FileOutputStream
 import java.io.IOException
 
 /**
- * Read from an [AtomicFile] and close everything safely when done.
+ * Read from an [AtomicFile], fallback to reserve file to read the data.
+ */
+@Throws(Exception::class)
+inline fun AtomicFile.readWithReserveCopy(block: (FileInputStream) -> Unit) {
+    try {
+        openRead().use(block)
+    } catch (e: FileNotFoundException) {
+        throw e
+    } catch (e: Exception) {
+        Slog.wtf("AccessPersistence", "Failed to read $this", e)
+        val reserveFile = File(baseFile.parentFile, baseFile.name + ".reservecopy")
+        try {
+            AtomicFile(reserveFile).openRead().use(block)
+        } catch (e2: Exception) {
+            Slog.e("AccessPersistence", "Failed to read $reserveFile", e2)
+            throw e
+        }
+    }
+}
+
+/**
+ * Write to actual file and reserve file.
  */
 @Throws(IOException::class)
-inline fun AtomicFile.read(block: (FileInputStream) -> Unit) {
-    openRead().use(block)
+inline fun AtomicFile.writeWithReserveCopy(block: (FileOutputStream) -> Unit) {
+    val reserveFile = File(baseFile.parentFile, baseFile.name + ".reservecopy")
+    reserveFile.delete()
+    writeInlined(block)
+    try {
+        FileInputStream(baseFile).use { inputStream ->
+            FileOutputStream(reserveFile).use { outputStream ->
+                FileUtils.copy(inputStream, outputStream)
+                outputStream.fd.sync()
+            }
+        }
+    } catch (e: Exception) {
+        Slog.e("AccessPersistence", "Failed to write $reserveFile", e)
+    }
 }
 
 /**
diff --git a/services/permission/java/com/android/server/permission/access/util/IntExtensions.kt b/services/permission/java/com/android/server/permission/access/util/IntExtensions.kt
index e71d7a1..bc3328c 100644
--- a/services/permission/java/com/android/server/permission/access/util/IntExtensions.kt
+++ b/services/permission/java/com/android/server/permission/access/util/IntExtensions.kt
@@ -21,3 +21,19 @@
 fun Int.hasBits(bits: Int): Boolean = this and bits == bits
 
 infix fun Int.andInv(other: Int): Int = this and other.inv()
+
+inline fun Int.flagsToString(flagToString: (Int) -> String): String {
+    var flags = this
+    return buildString {
+        append("[")
+        while (flags != 0) {
+            val flag = 1 shl flags.countTrailingZeroBits()
+            flags = flags andInv flag
+            append(flagToString(flag))
+            if (flags != 0) {
+                append('|')
+            }
+        }
+        append("]")
+    }
+}
diff --git a/services/permission/java/com/android/server/permission/access/util/PackageVersionMigration.kt b/services/permission/java/com/android/server/permission/access/util/PackageVersionMigration.kt
new file mode 100644
index 0000000..a61489c
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/util/PackageVersionMigration.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.permission.access.util
+
+import com.android.server.LocalServices
+import com.android.server.appop.AppOpMigrationHelper
+import com.android.server.pm.permission.PermissionMigrationHelper
+
+object PackageVersionMigration {
+    /**
+     * Maps existing permission and app-op version to a unified version during OTA upgrade. The
+     * new unified version is used in determining the upgrade steps for a package (for both
+     * permission and app-ops).
+     *
+     * @return unified permission/app-op version
+     * @throws IllegalStateException if the method is called when there is nothing to migrate i.e.
+     * permission and app-op file does not exist.
+     */
+    internal fun getVersion(userId: Int): Int {
+        val permissionMigrationHelper =
+            LocalServices.getService(PermissionMigrationHelper::class.java)
+        val permissionVersion = permissionMigrationHelper.getLegacyPermissionStateVersion(userId)
+
+        val appOpMigrationHelper = LocalServices.getService(AppOpMigrationHelper::class.java)
+        val appOpVersion = appOpMigrationHelper.legacyAppOpVersion
+
+        return when {
+            // Both files don't exist.
+            permissionVersion == -1 && appOpVersion == -1 ->
+                error("getVersion() called when there are no legacy files")
+            // merging combination of versions based on released android version
+            // permissions version 1-8 were released in Q, 9 in S, 10 in T and 11 in U
+            // app ops version 1 was released in P, 3 in U.
+            permissionVersion >= 11 && appOpVersion >= 3 -> 15
+            permissionVersion >= 10 && appOpVersion >= 3 -> 14
+            permissionVersion >= 10 && appOpVersion >= 1 -> 13
+            permissionVersion >= 9 && appOpVersion >= 1 -> 12
+            permissionVersion >= 8 && appOpVersion >= 1 -> 11
+            permissionVersion >= 7 && appOpVersion >= 1 -> 10
+            permissionVersion >= 6 && appOpVersion >= 1 -> 9
+            permissionVersion >= 5 && appOpVersion >= 1 -> 8
+            permissionVersion >= 4 && appOpVersion >= 1 -> 7
+            permissionVersion >= 3 && appOpVersion >= 1 -> 6
+            permissionVersion >= 2 && appOpVersion >= 1 -> 5
+            permissionVersion >= 1 && appOpVersion >= 1 -> 4
+            // Permission file exist w/o version, app op file has version as 1.
+            permissionVersion >= 0 && appOpVersion >= 1 -> 3
+            // Both file exist but w/o any version.
+            permissionVersion >= 0 && appOpVersion >= 0 -> 2
+            // Permission file doesn't exit, app op file exist w/o version.
+            permissionVersion >= -1 && appOpVersion >= 0 -> 1
+            // Re-run all upgrades to be safe.
+            else -> 0
+        }
+    }
+}
diff --git a/services/print/lint-baseline.xml b/services/print/lint-baseline.xml
new file mode 100644
index 0000000..1bf031a
--- /dev/null
+++ b/services/print/lint-baseline.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="6" by="lint 8.1.0-dev" type="baseline" client="" dependencies="true" name="" variant="all" version="8.1.0-dev">
+
+    <issue
+        id="SimpleManualPermissionEnforcement"
+        message="IPrintManager permission check should be converted to @EnforcePermission annotation"
+        errorLine1="            mContext.enforceCallingOrSelfPermission("
+        errorLine2="            ^">
+        <location
+            file="frameworks/base/services/print/java/com/android/server/print/PrintManagerService.java"
+            line="401"
+            column="13"/>
+    </issue>
+
+</issues>
diff --git a/services/proguard.flags b/services/proguard.flags
index c31abbb..e11e613 100644
--- a/services/proguard.flags
+++ b/services/proguard.flags
@@ -42,19 +42,6 @@
 -keep,allowoptimization,allowaccessmodification class * extends android.os.IInterface
 -keep,allowoptimization,allowaccessmodification class * extends android.os.IHwInterface
 
-# Global entities normally kept through explicit Manifest entries
-# TODO(b/210510433): Revisit and consider generating from frameworks/base/core/res/AndroidManifest.xml,
-# by including that manifest with the library rule that triggers optimization.
--keep,allowoptimization,allowaccessmodification class com.android.server.** extends android.app.Activity
--keep,allowoptimization,allowaccessmodification class com.android.server.** extends android.app.Service
--keep,allowoptimization,allowaccessmodification class com.android.server.** extends android.app.backup.BackupAgent
--keep,allowoptimization,allowaccessmodification class com.android.server.** extends android.content.BroadcastReceiver
--keep,allowoptimization,allowaccessmodification class com.android.server.** extends android.content.ContentProvider
--keep,allowoptimization,allowaccessmodification class com.android.server.** extends android.preference.Preference
--keep,allowoptimization,allowaccessmodification class com.android.server.** extends android.view.View {
-  public <init>(...);
-}
-
 # Various classes subclassed in or referenced via JNI in ethernet-service
 -keep public class android.net.** { *; }
 -keep,allowoptimization,allowaccessmodification class com.android.net.module.util.* { *; }
diff --git a/services/robotests/backup/Android.bp b/services/robotests/backup/Android.bp
index 506e156..e04dd68 100644
--- a/services/robotests/backup/Android.bp
+++ b/services/robotests/backup/Android.bp
@@ -36,6 +36,7 @@
         "services.backup",
         "services.core",
         "services.net",
+        "service-permission.stubs.system_server",
     ],
 
     libs: ["android.net.ipsec.ike.stubs.system"],
diff --git a/services/robotests/backup/src/com/android/server/backup/FullBackupJobTest.java b/services/robotests/backup/src/com/android/server/backup/FullBackupJobTest.java
index c8797e2..cd53cf4 100644
--- a/services/robotests/backup/src/com/android/server/backup/FullBackupJobTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/FullBackupJobTest.java
@@ -16,13 +16,18 @@
 
 package com.android.server.backup;
 
+import static com.android.server.backup.FullBackupJob.getJobIdForUserId;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.Mockito.when;
+import static org.robolectric.Shadows.shadowOf;
 
 import android.annotation.UserIdInt;
+import android.app.job.JobInfo;
 import android.app.job.JobScheduler;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.os.Handler;
 import android.os.UserHandle;
 import android.platform.test.annotations.Presubmit;
@@ -87,6 +92,25 @@
     }
 
     @Test
+    public void testSchedule_notWatch_requiresDeviceIdle() {
+        shadowOf(mContext.getPackageManager())
+                .setSystemFeature(PackageManager.FEATURE_WATCH, false);
+        FullBackupJob.schedule(mUserOneId, mContext, 0, mUserBackupManagerService);
+
+        JobInfo pendingJob = mShadowJobScheduler.getPendingJob(getJobIdForUserId(mUserOneId));
+        assertThat(pendingJob.isRequireDeviceIdle()).isTrue();
+    }
+
+    @Test
+    public void testSchedule_isWatch_doesNotRequireDeviceIdle() {
+        shadowOf(mContext.getPackageManager()).setSystemFeature(PackageManager.FEATURE_WATCH, true);
+        FullBackupJob.schedule(mUserOneId, mContext, 0, mUserBackupManagerService);
+
+        JobInfo pendingJob = mShadowJobScheduler.getPendingJob(getJobIdForUserId(mUserOneId));
+        assertThat(pendingJob.isRequireDeviceIdle()).isFalse();
+    }
+
+    @Test
     public void testCancel_afterCancelling_jobDoesntExist() {
         FullBackupJob.schedule(mUserOneId, mContext, 0, mUserBackupManagerService);
         FullBackupJob.schedule(mUserTwoId, mContext, 0, mUserBackupManagerService);
@@ -130,9 +154,4 @@
         assertThat(mShadowJobScheduler.getPendingJob(getJobIdForUserId(mUserOneId))).isNull();
         assertThat(mShadowJobScheduler.getPendingJob(getJobIdForUserId(mUserTwoId))).isNotNull();
     }
-
-    private static int getJobIdForUserId(int userId) {
-        return JobIdManager.getJobIdForUserId(FullBackupJob.MIN_JOB_ID, FullBackupJob.MAX_JOB_ID,
-                userId);
-    }
 }
diff --git a/services/tests/InputMethodSystemServerTests/Android.bp b/services/tests/InputMethodSystemServerTests/Android.bp
index 07ddda3..36446f6 100644
--- a/services/tests/InputMethodSystemServerTests/Android.bp
+++ b/services/tests/InputMethodSystemServerTests/Android.bp
@@ -41,6 +41,7 @@
         "mockito-target-extended-minus-junit4",
         "platform-test-annotations",
         "services.core",
+        "service-permission.stubs.system_server",
         "servicestests-core-utils",
         "servicestests-utils-mockito-extended",
         "truth-prebuilt",
@@ -88,6 +89,7 @@
         "mockito-target-extended-minus-junit4",
         "platform-test-annotations",
         "services.core",
+        "service-permission.stubs.system_server",
         "servicestests-core-utils",
         "servicestests-utils-mockito-extended",
         "truth-prebuilt",
diff --git a/services/tests/InputMethodSystemServerTests/OWNERS b/services/tests/InputMethodSystemServerTests/OWNERS
index 1f2c036..eb4fe3c 100644
--- a/services/tests/InputMethodSystemServerTests/OWNERS
+++ b/services/tests/InputMethodSystemServerTests/OWNERS
@@ -1 +1,2 @@
+# Bug component: 34867
 include /services/core/java/com/android/server/inputmethod/OWNERS
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
index a6ada4d..869497c 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
@@ -20,6 +20,7 @@
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE;
 
 import static com.android.internal.inputmethod.SoftInputShowHideReason.HIDE_SOFT_INPUT;
+import static com.android.internal.inputmethod.SoftInputShowHideReason.HIDE_SWITCH_USER;
 import static com.android.internal.inputmethod.SoftInputShowHideReason.SHOW_SOFT_INPUT;
 import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_HIDE_IME;
 import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_HIDE_IME_EXPLICIT;
@@ -43,6 +44,7 @@
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
 import com.android.internal.inputmethod.InputBindResult;
 import com.android.internal.inputmethod.StartInputFlags;
 import com.android.internal.inputmethod.StartInputReason;
@@ -165,6 +167,29 @@
         verify(mMockImeTargetVisibilityPolicy).removeImeScreenshot(eq(Display.DEFAULT_DISPLAY));
     }
 
+    @Test
+    public void testApplyImeVisibility_hideImeWhenUnbinding() {
+        mInputMethodManagerService.setAttachedClientForTesting(null);
+        startInputOrWindowGainedFocus(mWindowToken, SOFT_INPUT_STATE_ALWAYS_VISIBLE);
+        ExtendedMockito.spyOn(mVisibilityApplier);
+
+        synchronized (ImfLock.class) {
+            // Simulate the system hides the IME when switching IME services in different users.
+            // (e.g. unbinding the IME from the current user to the profile user)
+            final int displayIdToShowIme = mInputMethodManagerService.getDisplayIdToShowImeLocked();
+            mInputMethodManagerService.hideCurrentInputLocked(mWindowToken, null, 0, null,
+                    HIDE_SWITCH_USER);
+            mInputMethodManagerService.onUnbindCurrentMethodByReset();
+
+            // Expects applyImeVisibility() -> hideIme() will be called to notify WM for syncing
+            // the IME hidden state.
+            verify(mVisibilityApplier).applyImeVisibility(eq(mWindowToken), any(),
+                    eq(STATE_HIDE_IME));
+            verify(mInputMethodManagerService.mWindowManagerInternal).hideIme(
+                    eq(mWindowToken), eq(displayIdToShowIme), eq(null));
+        }
+    }
+
     private InputBindResult startInputOrWindowGainedFocus(IBinder windowToken, int softInputMode) {
         return mInputMethodManagerService.startInputOrWindowGainedFocus(
                 StartInputReason.WINDOW_FOCUS_GAIN /* startInputReason */,
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
index 1a8e00c..ffd7332 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
@@ -19,6 +19,7 @@
 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.mockitoSession;
+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.any;
@@ -125,6 +126,7 @@
     protected InputMethodManagerService mInputMethodManagerService;
     protected ServiceThread mServiceThread;
     protected boolean mIsLargeScreen;
+    private InputManagerGlobal.TestSession mInputManagerGlobalSession;
 
     @BeforeClass
     public static void setupClass() {
@@ -145,8 +147,7 @@
                         .mockStatic(SystemServerInitThreadPool.class)
                         .startMocking();
 
-        mContext = InstrumentationRegistry.getInstrumentation().getContext();
-        spyOn(mContext);
+        mContext = spy(InstrumentationRegistry.getInstrumentation().getContext());
 
         mTargetSdkVersion = mContext.getApplicationInfo().targetSdkVersion;
         mIsLargeScreen = mContext.getResources().getConfiguration()
@@ -186,7 +187,7 @@
 
         // Injecting and mocked InputMethodBindingController and InputMethod.
         mMockInputMethodInvoker = IInputMethodInvoker.create(mMockInputMethod);
-        InputManagerGlobal.resetInstance(mMockIInputManager);
+        mInputManagerGlobalSession = InputManagerGlobal.createTestSession(mMockIInputManager);
         synchronized (ImfLock.class) {
             when(mMockInputMethodBindingController.getCurMethod())
                     .thenReturn(mMockInputMethodInvoker);
@@ -262,6 +263,10 @@
         if (mMockingSession != null) {
             mMockingSession.finishMocking();
         }
+
+        if (mInputManagerGlobalSession != null) {
+            mInputManagerGlobalSession.close();
+        }
         LocalServices.removeServiceForTest(InputMethodManagerInternal.class);
     }
 
diff --git a/services/tests/PackageManagerComponentOverrideTests/Android.bp b/services/tests/PackageManagerComponentOverrideTests/Android.bp
index 19fdf60..bc36970 100644
--- a/services/tests/PackageManagerComponentOverrideTests/Android.bp
+++ b/services/tests/PackageManagerComponentOverrideTests/Android.bp
@@ -29,12 +29,13 @@
 android_test {
     name: "PackageManagerComponentOverrideTests",
     srcs: [
-        "src/**/*.kt"
+        "src/**/*.kt",
     ],
     static_libs: [
         "androidx.test.runner",
         "mockito-target-extended-minus-junit4",
         "services.core",
+        "service-permission.stubs.system_server",
         "servicestests-utils-mockito-extended",
         "testng", // TODO: remove once Android migrates to JUnit 4.12, which provides assertThrows
         "truth-prebuilt",
diff --git a/services/tests/PackageManagerComponentOverrideTests/OWNERS b/services/tests/PackageManagerComponentOverrideTests/OWNERS
index d825dfd..ecdd028 100644
--- a/services/tests/PackageManagerComponentOverrideTests/OWNERS
+++ b/services/tests/PackageManagerComponentOverrideTests/OWNERS
@@ -1 +1,2 @@
+# Bug component: 36137
 include /services/core/java/com/android/server/pm/OWNERS
diff --git a/services/tests/PackageManagerServiceTests/OWNERS b/services/tests/PackageManagerServiceTests/OWNERS
index 86ae581..95f146d 100644
--- a/services/tests/PackageManagerServiceTests/OWNERS
+++ b/services/tests/PackageManagerServiceTests/OWNERS
@@ -1 +1,2 @@
+# Bug component: 36137
 include /PACKAGE_MANAGER_OWNERS
diff --git a/services/tests/PackageManagerServiceTests/appenumeration/src/com/android/server/pm/test/appenumeration/CrossUserPackageVisibilityTests.java b/services/tests/PackageManagerServiceTests/appenumeration/src/com/android/server/pm/test/appenumeration/CrossUserPackageVisibilityTests.java
index e33ca77..b7a0cf3 100644
--- a/services/tests/PackageManagerServiceTests/appenumeration/src/com/android/server/pm/test/appenumeration/CrossUserPackageVisibilityTests.java
+++ b/services/tests/PackageManagerServiceTests/appenumeration/src/com/android/server/pm/test/appenumeration/CrossUserPackageVisibilityTests.java
@@ -138,6 +138,14 @@
     }
 
     @Test
+    public void testGetUserMinAspectRatio_withCrossUserId() {
+        final int crossUserId = UserHandle.myUserId() + 1;
+        assertThrows(SecurityException.class,
+                () -> mIPackageManager.getUserMinAspectRatio(
+                        mInstrumentation.getContext().getPackageName(), crossUserId));
+    }
+
+    @Test
     public void testIsPackageSignedByKeySet_cannotDetectCrossUserPkg() throws Exception {
         final KeySet keySet = mIPackageManager.getSigningKeySet(mContext.getPackageName());
         assertThrows(IllegalArgumentException.class,
diff --git a/services/tests/PackageManagerServiceTests/server/Android.bp b/services/tests/PackageManagerServiceTests/server/Android.bp
index 3176f06..a1d846e 100644
--- a/services/tests/PackageManagerServiceTests/server/Android.bp
+++ b/services/tests/PackageManagerServiceTests/server/Android.bp
@@ -43,7 +43,6 @@
         "ShortcutManagerTestUtils",
         "truth-prebuilt",
         "testables",
-        "ub-uiautomator",
         "platformprotosnano",
         "framework-protos",
         "hamcrest-library",
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 7909ba4..d5cd6ef9 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
@@ -224,7 +224,6 @@
 
         MockitoAnnotations.initMocks(this);
         when(mSnapshot.getPackageStates()).thenAnswer(x -> mExisting);
-        when(mSnapshot.getAllSharedUsers()).thenReturn(mSharedUserSettings);
         when(mSnapshot.getUserInfos()).thenReturn(USER_INFO_LIST);
         when(mSnapshot.getSharedUser(anyInt())).thenAnswer(invocation -> {
             final int sharedUserAppId = invocation.getArgument(0);
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerServiceTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerServiceTest.java
index 435f0d7..a805e5c 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerServiceTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerServiceTest.java
@@ -20,6 +20,7 @@
 
 import static com.google.common.truth.Truth.assertWithMessage;
 
+import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.fail;
 
 import static java.lang.reflect.Modifier.isFinal;
@@ -138,7 +139,7 @@
                 .setSecondaryCpuAbiString("secondaryCpuAbiString")
                 .setCpuAbiOverrideString("cpuAbiOverrideString")
                 .build();
-        pri.populateUsers(new int[] {
+        pri.populateUsers(new int[]{
                 1, 2, 3, 4, 5
         }, setting);
         Assert.assertNotNull(pri.mBroadcastUsers);
@@ -150,7 +151,7 @@
         pri.mBroadcastUsers = null;
         final int EXCLUDED_USER_ID = 4;
         setting.setInstantApp(true, EXCLUDED_USER_ID);
-        pri.populateUsers(new int[] {
+        pri.populateUsers(new int[]{
                 1, 2, 3, EXCLUDED_USER_ID, 5
         }, setting);
         Assert.assertNotNull(pri.mBroadcastUsers);
@@ -164,8 +165,8 @@
 
     @Test
     public void testPartitions() {
-        String[] partitions = { "system", "vendor", "odm", "oem", "product", "system_ext" };
-        String[] appdir = { "app", "priv-app" };
+        String[] partitions = {"system", "vendor", "odm", "oem", "product", "system_ext"};
+        String[] appdir = {"app", "priv-app"};
         for (int i = 0; i < partitions.length; i++) {
             final ScanPartition scanPartition =
                     PackageManagerService.SYSTEM_PARTITIONS.get(i);
@@ -425,10 +426,10 @@
     private String displayName(Method m) {
         String r = m.getName();
         String p = Arrays.toString(m.getGenericParameterTypes())
-                   .replaceAll("([a-zA-Z0-9]+\\.)+", "")
-                   .replace("class ", "")
-                   .replaceAll("^\\[", "(")
-                   .replaceAll("\\]$", ")");
+                .replaceAll("([a-zA-Z0-9]+\\.)+", "")
+                .replace("class ", "")
+                .replaceAll("^\\[", "(")
+                .replaceAll("\\]$", ")");
         return r + p;
     }
 
@@ -612,4 +613,22 @@
             runShellCommand("pm uninstall " + TEST_PKG_NAME);
         }
     }
+
+    @Test
+    public void testSetUserMinAspectRatio_samePackage_succeeds() throws Exception {
+        mIPackageManager.setUserMinAspectRatio(PACKAGE_NAME, UserHandle.myUserId(),
+                PackageManager.USER_MIN_ASPECT_RATIO_UNSET);
+        // Invoking setUserMinAspectRatio on the same package shouldn't get any exception.
+    }
+
+    @Test
+    public void testSetUserMinAspectRatio_differentPackage_fails() {
+        final File testApk = new File(TEST_DATA_PATH, TEST_APP_APK);
+        runShellCommand("pm install " + testApk);
+        assertThrows(SecurityException.class, () -> {
+            mIPackageManager.setUserMinAspectRatio(TEST_PKG_NAME, UserHandle.myUserId(),
+                    PackageManager.USER_MIN_ASPECT_RATIO_UNSET);
+        });
+        runShellCommand("pm uninstall " + TEST_PKG_NAME);
+    }
 }
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 836f858..dc2fbfc 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
@@ -68,6 +68,7 @@
 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;
 import com.android.server.pm.pkg.PackageUserState;
 import com.android.server.pm.pkg.PackageUserStateInternal;
 import com.android.server.pm.pkg.SuspendParams;
@@ -864,6 +865,34 @@
     }
 
     @Test
+    public void testWriteReadArchiveState() {
+        Settings settings = makeSettings();
+        PackageSetting packageSetting = createPackageSetting(PACKAGE_NAME_1);
+        packageSetting.setAppId(Process.FIRST_APPLICATION_UID);
+        packageSetting.setPkg(PackageImpl.forTesting(PACKAGE_NAME_1).hideAsParsed()
+                .setUid(packageSetting.getAppId())
+                .hideAsFinal());
+
+        ArchiveState archiveState = new ArchiveState(
+                List.of(new ArchiveState.ArchiveActivityInfo("title1", Path.of("/path1"),
+                                Path.of("/monochromePath1")),
+                        new ArchiveState.ArchiveActivityInfo("title2", Path.of("/path2"),
+                                Path.of("/monochromePath2"))),
+                "installerTitle");
+        packageSetting.modifyUserState(UserHandle.SYSTEM.getIdentifier()).setArchiveState(
+                archiveState);
+        settings.mPackages.put(PACKAGE_NAME_1, packageSetting);
+
+        settings.writeLPr(computer, /*sync=*/true);
+        settings.mPackages.clear();
+
+        assertThat(settings.readLPw(computer, createFakeUsers()), is(true));
+
+        Truth.assertThat(settings.getPackageLPr(PACKAGE_NAME_1).getStateForUser(
+                UserHandle.SYSTEM).getArchiveState()).isEqualTo(archiveState);
+    }
+
+    @Test
     public void testPackageRestrictionsDistractionFlagsDefault() {
         final PackageSetting defaultSetting = createPackageSetting(PACKAGE_NAME_1);
         assertThat(defaultSetting.getDistractionFlags(0), is(PackageManager.RESTRICTION_NONE));
@@ -971,7 +1000,7 @@
         origPkgSetting01.setUserState(0, 100, 1, true, false, false, false, 0, null, false,
                 false, "lastDisabledCaller", new ArraySet<>(new String[]{"enabledComponent1"}),
                 new ArraySet<>(new String[]{"disabledComponent1"}), 0, 0, "harmfulAppWarning",
-                "splashScreenTheme", 1000L);
+                "splashScreenTheme", 1000L, PackageManager.USER_MIN_ASPECT_RATIO_UNSET, null);
         final PersistableBundle appExtras1 = createPersistableBundle(
                 PACKAGE_NAME_1, 1L, 0.01, true, "appString1");
         final PersistableBundle launcherExtras1 = createPersistableBundle(
@@ -1638,7 +1667,8 @@
                 : oldUserState.getSharedLibraryOverlayPaths() == null)
                 && userState.getSplashScreenTheme().equals(
                         oldUserState.getSplashScreenTheme())
-                && userState.getUninstallReason() == oldUserState.getUninstallReason();
+                && userState.getUninstallReason() == oldUserState.getUninstallReason()
+                && userState.getMinAspectRatio() == oldUserState.getMinAspectRatio();
     }
 
     private SharedUserSetting createSharedUserSetting(Settings settings, String userName,
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 c7fb32c..0eac4e6 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
@@ -231,8 +231,8 @@
         assertSame(deserialized.getPackageName(), deserialized2.getPackageName());
         assertSame(deserialized.getPermission(),
                 deserialized2.getPermission());
-        assertSame(deserialized.getRequestedPermissions().get(0),
-                deserialized2.getRequestedPermissions().get(0));
+        assertSame(deserialized.getRequestedPermissions().iterator().next(),
+                deserialized2.getRequestedPermissions().iterator().next());
 
         List<String> protectedBroadcastsOne = new ArrayList<>(1);
         protectedBroadcastsOne.addAll(deserialized.getProtectedBroadcasts());
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageUserStateTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageUserStateTest.java
index 9ad503c..58ae740 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageUserStateTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageUserStateTest.java
@@ -35,6 +35,7 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.server.pm.pkg.ArchiveState;
 import com.android.server.pm.pkg.PackageStateUnserialized;
 import com.android.server.pm.pkg.PackageUserStateImpl;
 import com.android.server.pm.pkg.SuspendParams;
@@ -44,6 +45,9 @@
 import org.junit.runner.RunWith;
 import org.mockito.Mockito;
 
+import java.nio.file.Path;
+import java.util.List;
+
 @Presubmit
 @RunWith(AndroidJUnit4.class)
 @SmallTest
@@ -394,4 +398,14 @@
         assertFalse(testState.setSharedLibraryOverlayPaths(LIB_ONE, null));
     }
 
+    @Test
+    public void archiveState() {
+        PackageUserStateImpl packageUserState = new PackageUserStateImpl();
+        ArchiveState.ArchiveActivityInfo archiveActivityInfo = new ArchiveState.ArchiveActivityInfo(
+                "appTitle", Path.of("/path1"), Path.of("/path2"));
+        ArchiveState archiveState = new ArchiveState(List.of(archiveActivityInfo),
+                "installerTitle");
+        packageUserState.setArchiveState(archiveState);
+        assertEquals(archiveState, packageUserState.getArchiveState());
+    }
 }
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 b5bd869..e2939c1 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
@@ -21,6 +21,7 @@
 import static android.content.pm.SharedLibraryInfo.TYPE_STATIC;
 import static android.content.pm.SharedLibraryInfo.VERSION_UNDEFINED;
 
+import static com.android.server.pm.PackageManagerService.SCAN_AS_APEX;
 import static com.android.server.pm.PackageManagerService.SCAN_AS_FULL_APP;
 import static com.android.server.pm.PackageManagerService.SCAN_AS_INSTANT_APP;
 import static com.android.server.pm.PackageManagerService.SCAN_FIRST_BOOT_OR_UPGRADE;
@@ -365,6 +366,46 @@
     }
 
     @Test
+    public void scanFirstBoot_apexDontDeriveAbis() throws Exception {
+        final PackageSetting pkgSetting =
+                createBasicPackageSettingBuilder(DUMMY_PACKAGE_NAME)
+                        .setPkgFlags(ApplicationInfo.FLAG_SYSTEM).build();
+
+        final String codePath = "/data/apex/" + DUMMY_PACKAGE_NAME + ".apex";
+
+        // Create the ParsedPackage for the apex
+        final ParsedPackage basicPackage =
+                ((ParsedPackage) new PackageImpl(DUMMY_PACKAGE_NAME, codePath, codePath,
+                        mock(TypedArray.class), false)
+                        .setVolumeUuid(UUID_ONE.toString())
+                        .hideAsParsed())
+                        .setVersionCodeMajor(1)
+                        .setVersionCode(2345)
+                        .setSystem(true);
+
+        when(mMockInjector.getAbiHelper()).thenReturn(new PackageAbiHelperImpl());
+
+        // prepare the scan request with the scanflag (SCAN_FIRST_BOOT_OR_UPGRADE | SCAN_AS_APEX)
+        final ScanResult scanResult = executeScan(new ScanRequestBuilder(basicPackage)
+                .setPkgSetting(pkgSetting)
+                .addScanFlag(SCAN_FIRST_BOOT_OR_UPGRADE | SCAN_AS_APEX)
+                .build());
+
+        final PackageSetting resultSetting = scanResult.mPkgSetting;
+        final ApplicationInfo applicationInfo = PackageInfoUtils.generateApplicationInfo(
+                resultSetting.getPkg(), 0, pkgSetting.getUserStateOrDefault(0), 0, resultSetting);
+        assertThat(applicationInfo.primaryCpuAbi, nullValue());
+        assertThat(applicationInfo.primaryCpuAbi, nullValue());
+        assertThat(applicationInfo.secondaryCpuAbi, nullValue());
+
+        assertThat(applicationInfo.nativeLibraryRootDir, nullValue());
+        assertThat(pkgSetting.getLegacyNativeLibraryPath(), nullValue());
+        assertThat(applicationInfo.nativeLibraryRootRequiresIsa, is(false));
+        assertThat(applicationInfo.nativeLibraryDir, nullValue());
+        assertThat(applicationInfo.secondaryNativeLibraryDir, nullValue());
+    }
+
+    @Test
     public void scanFirstBoot_derivesAbis() throws Exception {
         final PackageSetting pkgSetting =
                 createBasicPackageSettingBuilder(DUMMY_PACKAGE_NAME).build();
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 3200871..edab1d6 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
@@ -623,7 +623,6 @@
         expect.that(after.longVersionCode).isEqualTo(38654705667)
         expect.that(after.requestedPermissions)
             .containsExactlyElementsIn(after.usesPermissions.map { it.name })
-            .inOrder()
 
         expect.that(after.mimeGroups).containsExactly(
             "TestActivityName/mimeGroup",
diff --git a/services/tests/RemoteProvisioningServiceTests/Android.bp b/services/tests/RemoteProvisioningServiceTests/Android.bp
index 075680a..fc2c085 100644
--- a/services/tests/RemoteProvisioningServiceTests/Android.bp
+++ b/services/tests/RemoteProvisioningServiceTests/Android.bp
@@ -31,6 +31,7 @@
         "service-rkp.impl",
         "services.core",
         "truth-prebuilt",
+        "truth-java8-extension-jar",
     ],
     test_suites: [
         "device-tests",
diff --git a/services/tests/RemoteProvisioningServiceTests/src/com/android/server/security/rkp/RemoteProvisioningShellCommandTest.java b/services/tests/RemoteProvisioningServiceTests/src/com/android/server/security/rkp/RemoteProvisioningShellCommandTest.java
index 77c3396..2d93120 100644
--- a/services/tests/RemoteProvisioningServiceTests/src/com/android/server/security/rkp/RemoteProvisioningShellCommandTest.java
+++ b/services/tests/RemoteProvisioningServiceTests/src/com/android/server/security/rkp/RemoteProvisioningShellCommandTest.java
@@ -17,6 +17,7 @@
 package com.android.server.security.rkp;
 
 import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth8.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -42,7 +43,6 @@
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
-import java.util.Arrays;
 import java.util.Base64;
 import java.util.Map;
 
@@ -119,6 +119,7 @@
         assertThat(res.getErr()).isEmpty();
         assertThat(res.getCode()).isEqualTo(0);
         assertThat(res.getOut()).isEmpty();
+        assertThat(res.getOut().lines()).isEmpty();
     }
 
     @Test
@@ -128,7 +129,7 @@
         CommandResult res = exec(cmd, new String[] {"list"});
         assertThat(res.getErr()).isEmpty();
         assertThat(res.getCode()).isEqualTo(0);
-        assertThat(Arrays.asList(res.getOut().split("\n"))).containsExactly("default");
+        assertThat(res.getOut().lines()).containsExactly("default");
     }
 
     @Test
@@ -140,7 +141,7 @@
         CommandResult res = exec(cmd, new String[] {"list"});
         assertThat(res.getErr()).isEmpty();
         assertThat(res.getCode()).isEqualTo(0);
-        assertThat(Arrays.asList(res.getOut().split("\n"))).containsExactly("default", "strongbox");
+        assertThat(res.getOut().lines()).containsExactly("default", "strongbox");
     }
 
     @Test
diff --git a/services/tests/displayservicetests/Android.bp b/services/tests/displayservicetests/Android.bp
new file mode 100644
index 0000000..4f555d9
--- /dev/null
+++ b/services/tests/displayservicetests/Android.bp
@@ -0,0 +1,59 @@
+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"],
+}
+
+// Include all test java files.
+filegroup {
+    name: "displayservicetests-sources",
+    srcs: [
+        "src/**/*.java",
+    ],
+}
+
+android_test {
+    name: "DisplayServiceTests",
+
+    srcs: [
+        "src/**/*.java",
+    ],
+
+    libs: [
+        "android.test.mock",
+    ],
+
+    static_libs: [
+        "androidx.test.ext.junit",
+        "frameworks-base-testutils",
+        "junit",
+        "junit-params",
+        "mockingservicestests-utils-mockito",
+        "platform-compat-test-rules",
+        "platform-test-annotations",
+        "services.core",
+        "servicestests-utils",
+        "testables",
+    ],
+
+    defaults: [
+        "modules-utils-testable-device-config-defaults",
+    ],
+
+    platform_apis: true,
+    test_suites: [
+        "device-tests",
+        "automotive-tests",
+    ],
+
+    certificate: "platform",
+
+    dxflags: ["--multi-dex"],
+
+    optimize: {
+        enabled: false,
+    },
+}
diff --git a/services/tests/displayservicetests/AndroidManifest.xml b/services/tests/displayservicetests/AndroidManifest.xml
new file mode 100644
index 0000000..55fde00
--- /dev/null
+++ b/services/tests/displayservicetests/AndroidManifest.xml
@@ -0,0 +1,46 @@
+<?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.frameworks.displayservicetests">
+
+    <!-- Permissions -->
+    <uses-permission android:name="android.permission.CONTROL_DISPLAY_BRIGHTNESS" />
+    <uses-permission android:name="android.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS" />
+    <uses-permission android:name="android.permission.DEVICE_POWER" />
+    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
+    <uses-permission android:name="android.permission.LOG_COMPAT_CHANGE" />
+    <uses-permission android:name="android.permission.MANAGE_ACTIVITY_TASKS" />
+    <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
+    <uses-permission android:name="android.permission.READ_COMPAT_CHANGE_CONFIG" />
+    <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
+    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
+
+    <!-- Permissions needed for DisplayTransformManagerTest -->
+    <uses-permission android:name="android.permission.CHANGE_CONFIGURATION" />
+    <uses-permission android:name="android.permission.HARDWARE_TEST"/>
+
+    <application android:debuggable="true"
+                 android:testOnly="true">
+        <uses-library android:name="android.test.mock" android:required="true" />
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:label="Display Service Tests"
+        android:targetPackage="com.android.frameworks.displayservicetests" />
+</manifest>
diff --git a/services/tests/displayservicetests/AndroidTest.xml b/services/tests/displayservicetests/AndroidTest.xml
new file mode 100644
index 0000000..2985f98
--- /dev/null
+++ b/services/tests/displayservicetests/AndroidTest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<configuration description="Runs Display Service Tests.">
+    <option name="test-suite-tag" value="apct" />
+    <option name="test-suite-tag" value="apct-instrumentation" />
+    <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="DisplayServiceTests.apk" />
+    </target_preparer>
+
+    <option name="test-tag" value="DisplayServiceTests" />
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+        <option name="package" value="com.android.frameworks.displayservicetests" />
+        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+        <option name="hidden-api-checks" value="false" />
+    </test>
+</configuration>
diff --git a/services/tests/servicestests/src/com/android/server/display/OWNERS b/services/tests/displayservicetests/OWNERS
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/display/OWNERS
rename to services/tests/displayservicetests/OWNERS
diff --git a/services/tests/displayservicetests/TEST_MAPPING b/services/tests/displayservicetests/TEST_MAPPING
new file mode 100644
index 0000000..d865519
--- /dev/null
+++ b/services/tests/displayservicetests/TEST_MAPPING
@@ -0,0 +1,13 @@
+{
+  "presubmit": [
+    {
+      "name": "DisplayServiceTests",
+      "options": [
+        {"include-filter": "com.android.server.display"},
+        {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
+        {"exclude-annotation": "androidx.test.filters.FlakyTest"},
+        {"exclude-annotation": "org.junit.Ignore"}
+      ]
+    }
+  ]
+}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/AmbientBrightnessStatsTrackerTest.java b/services/tests/displayservicetests/src/com/android/server/display/AmbientBrightnessStatsTrackerTest.java
new file mode 100644
index 0000000..7333bc7
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/AmbientBrightnessStatsTrackerTest.java
@@ -0,0 +1,444 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.hardware.display.AmbientBrightnessDayStats;
+import android.os.SystemClock;
+import android.os.UserManager;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.time.LocalDate;
+import java.util.ArrayList;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class AmbientBrightnessStatsTrackerTest {
+
+    private TestInjector mTestInjector;
+
+    @Before
+    public void setUp() {
+        mTestInjector = new TestInjector();
+    }
+
+    @Test
+    public void testBrightnessStatsTrackerOverSingleDay() {
+        AmbientBrightnessStatsTracker statsTracker = getTestStatsTracker();
+        ArrayList<AmbientBrightnessDayStats> userStats;
+        float[] expectedStats;
+        // Test case where no user data
+        userStats = statsTracker.getUserStats(0);
+        assertNull(userStats);
+        // Test after adding some user data
+        statsTracker.start();
+        statsTracker.add(0, 0);
+        mTestInjector.incrementTime(1000);
+        statsTracker.stop();
+        userStats = statsTracker.getUserStats(0);
+        assertEquals(1, userStats.size());
+        assertEquals(mTestInjector.getLocalDate(), userStats.get(0).getLocalDate());
+        expectedStats = getEmptyStatsArray();
+        expectedStats[0] = 1;
+        assertArrayEquals(expectedStats, userStats.get(0).getStats(), 0);
+        // Test after adding some more user data
+        statsTracker.start();
+        statsTracker.add(0, 0.05f);
+        mTestInjector.incrementTime(1000);
+        statsTracker.add(0, 0.2f);
+        mTestInjector.incrementTime(1500);
+        statsTracker.add(0, 50000);
+        mTestInjector.incrementTime(2500);
+        statsTracker.stop();
+        userStats = statsTracker.getUserStats(0);
+        assertEquals(1, userStats.size());
+        assertEquals(mTestInjector.getLocalDate(), userStats.get(0).getLocalDate());
+        expectedStats = getEmptyStatsArray();
+        expectedStats[0] = 2;
+        expectedStats[1] = 1.5f;
+        expectedStats[11] = 2.5f;
+        assertArrayEquals(expectedStats, userStats.get(0).getStats(), 0);
+    }
+
+    @Test
+    public void testBrightnessStatsTrackerOverMultipleDays() {
+        AmbientBrightnessStatsTracker statsTracker = getTestStatsTracker();
+        ArrayList<AmbientBrightnessDayStats> userStats;
+        float[] expectedStats;
+        // Add data for day 1
+        statsTracker.start();
+        statsTracker.add(0, 0.05f);
+        mTestInjector.incrementTime(1000);
+        statsTracker.add(0, 0.2f);
+        mTestInjector.incrementTime(1500);
+        statsTracker.add(0, 1);
+        mTestInjector.incrementTime(2500);
+        statsTracker.stop();
+        // Add data for day 2
+        mTestInjector.incrementDate(1);
+        statsTracker.start();
+        statsTracker.add(0, 0);
+        mTestInjector.incrementTime(3500);
+        statsTracker.add(0, 5);
+        mTestInjector.incrementTime(5000);
+        statsTracker.stop();
+        // Test that the data is tracked as expected
+        userStats = statsTracker.getUserStats(0);
+        assertEquals(2, userStats.size());
+        assertEquals(mTestInjector.getLocalDate().minusDays(1), userStats.get(0).getLocalDate());
+        expectedStats = getEmptyStatsArray();
+        expectedStats[0] = 1;
+        expectedStats[1] = 1.5f;
+        expectedStats[3] = 2.5f;
+        assertArrayEquals(expectedStats, userStats.get(0).getStats(), 0);
+        assertEquals(mTestInjector.getLocalDate(), userStats.get(1).getLocalDate());
+        expectedStats = getEmptyStatsArray();
+        expectedStats[0] = 3.5f;
+        expectedStats[4] = 5;
+        assertArrayEquals(expectedStats, userStats.get(1).getStats(), 0);
+    }
+
+    @Test
+    public void testBrightnessStatsTrackerOverMultipleUsers() {
+        AmbientBrightnessStatsTracker statsTracker = getTestStatsTracker();
+        ArrayList<AmbientBrightnessDayStats> userStats;
+        float[] expectedStats;
+        // Add data for user 1
+        statsTracker.start();
+        statsTracker.add(0, 0.05f);
+        mTestInjector.incrementTime(1000);
+        statsTracker.add(0, 0.2f);
+        mTestInjector.incrementTime(1500);
+        statsTracker.add(0, 1);
+        mTestInjector.incrementTime(2500);
+        statsTracker.stop();
+        // Add data for user 2
+        mTestInjector.incrementDate(1);
+        statsTracker.start();
+        statsTracker.add(1, 0);
+        mTestInjector.incrementTime(3500);
+        statsTracker.add(1, 5);
+        mTestInjector.incrementTime(5000);
+        statsTracker.stop();
+        // Test that the data is tracked as expected
+        userStats = statsTracker.getUserStats(0);
+        assertEquals(1, userStats.size());
+        assertEquals(mTestInjector.getLocalDate().minusDays(1), userStats.get(0).getLocalDate());
+        expectedStats = getEmptyStatsArray();
+        expectedStats[0] = 1;
+        expectedStats[1] = 1.5f;
+        expectedStats[3] = 2.5f;
+        assertArrayEquals(expectedStats, userStats.get(0).getStats(), 0);
+        userStats = statsTracker.getUserStats(1);
+        assertEquals(1, userStats.size());
+        assertEquals(mTestInjector.getLocalDate(), userStats.get(0).getLocalDate());
+        expectedStats = getEmptyStatsArray();
+        expectedStats[0] = 3.5f;
+        expectedStats[4] = 5;
+        assertArrayEquals(expectedStats, userStats.get(0).getStats(), 0);
+    }
+
+    @Test
+    public void testBrightnessStatsTrackerOverMaxDays() {
+        AmbientBrightnessStatsTracker statsTracker = getTestStatsTracker();
+        ArrayList<AmbientBrightnessDayStats> userStats;
+        // Add 10 extra days of data over the buffer limit
+        for (int i = 0; i < AmbientBrightnessStatsTracker.MAX_DAYS_TO_TRACK + 10; i++) {
+            mTestInjector.incrementDate(1);
+            statsTracker.start();
+            statsTracker.add(0, 10);
+            mTestInjector.incrementTime(1000);
+            statsTracker.add(0, 20);
+            mTestInjector.incrementTime(1000);
+            statsTracker.stop();
+        }
+        // Assert that we are only tracking last "MAX_DAYS_TO_TRACK"
+        userStats = statsTracker.getUserStats(0);
+        assertEquals(AmbientBrightnessStatsTracker.MAX_DAYS_TO_TRACK, userStats.size());
+        LocalDate runningDate = mTestInjector.getLocalDate();
+        for (int i = AmbientBrightnessStatsTracker.MAX_DAYS_TO_TRACK - 1; i >= 0; i--) {
+            assertEquals(runningDate, userStats.get(i).getLocalDate());
+            runningDate = runningDate.minusDays(1);
+        }
+    }
+
+    @Test
+    public void testReadAmbientBrightnessStats() throws IOException {
+        AmbientBrightnessStatsTracker statsTracker = getTestStatsTracker();
+        LocalDate date = mTestInjector.getLocalDate();
+        ArrayList<AmbientBrightnessDayStats> userStats;
+        String statsFile =
+                "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\r\n"
+                        + "<ambient-brightness-stats>\r\n"
+                        // Old stats that shouldn't be read
+                        + "<ambient-brightness-day-stats user=\"10\" local-date=\""
+                        + date.minusDays(AmbientBrightnessStatsTracker.MAX_DAYS_TO_TRACK)
+                        + "\" bucket-boundaries=\"0.0,1.0,3.0,10.0,30.0,100.0,300.0,1000.0,"
+                        + "3000.0,10000.0\" bucket-stats=\"1.088,0.0,0.726,0.0,25.868,0.0,0.0,"
+                        + "0.0,0.0,0.0\" />\r\n"
+                        // Valid stats that should get read
+                        + "<ambient-brightness-day-stats user=\"10\" local-date=\""
+                        + date.minusDays(1)
+                        + "\" bucket-boundaries=\"0.0,1.0,3.0,10.0,30.0,100.0,300.0,1000.0,"
+                        + "3000.0,10000.0\" bucket-stats=\"1.088,0.0,0.726,0.0,25.868,0.0,0.0,"
+                        + "0.0,0.0,0.0\" />\r\n"
+                        // Valid stats that should get read
+                        + "<ambient-brightness-day-stats user=\"10\" local-date=\"" + date
+                        + "\" bucket-boundaries=\"0.0,1.0,3.0,10.0,30.0,100.0,300.0,1000.0,"
+                        + "3000.0,10000.0\" bucket-stats=\"0.0,0.0,0.0,0.0,4.482,0.0,0.0,0.0,0.0,"
+                        + "0.0\" />\r\n"
+                        + "</ambient-brightness-stats>";
+        statsTracker.readStats(getInputStream(statsFile));
+        userStats = statsTracker.getUserStats(0);
+        assertEquals(2, userStats.size());
+        assertEquals(new AmbientBrightnessDayStats(date.minusDays(1),
+                new float[]{0, 1, 3, 10, 30, 100, 300, 1000, 3000, 10000},
+                new float[]{1.088f, 0, 0.726f, 0, 25.868f, 0, 0, 0, 0, 0}), userStats.get(0));
+        assertEquals(new AmbientBrightnessDayStats(date,
+                new float[]{0, 1, 3, 10, 30, 100, 300, 1000, 3000, 10000},
+                new float[]{0, 0, 0, 0, 4.482f, 0, 0, 0, 0, 0}), userStats.get(1));
+    }
+
+    @Test
+    public void testFailedReadAmbientBrightnessStatsWithException() {
+        AmbientBrightnessStatsTracker statsTracker = getTestStatsTracker();
+        LocalDate date = mTestInjector.getLocalDate();
+        String statsFile;
+        // Test with parse error
+        statsFile =
+                "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\r\n"
+                        + "<ambient-brightness-stats>\r\n"
+                        // Incorrect since bucket boundaries not parsable
+                        + "<ambient-brightness-day-stats user=\"10\" local-date=\"" + date
+                        + "\" bucket-boundaries=\"asdf,1.0,3.0,10.0,30.0,100.0,300.0,1000.0,"
+                        + "3000.0,10000.0\" bucket-stats=\"1.088,0.0,0.726,0.0,25.868,0.0,0.0,"
+                        + "0.0,0.0,0.0\" />\r\n"
+                        + "</ambient-brightness-stats>";
+        try {
+            statsTracker.readStats(getInputStream(statsFile));
+        } catch (IOException e) {
+            // Expected
+        }
+        assertNull(statsTracker.getUserStats(0));
+        // Test with incorrect data (bucket boundaries length not equal to stats length)
+        statsFile =
+                "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\r\n"
+                        + "<ambient-brightness-stats>\r\n"
+                        // Correct data
+                        + "<ambient-brightness-day-stats user=\"10\" local-date=\""
+                        + date.minusDays(1)
+                        + "\" bucket-boundaries=\"0.0,1.0,3.0,10.0,30.0,100.0,300.0,1000.0,"
+                        + "3000.0,10000.0\" bucket-stats=\"0.0,0.0,0.0,0.0,4.482,0.0,0.0,0.0,0.0,"
+                        + "0.0\" />\r\n"
+                        // Incorrect data
+                        + "<ambient-brightness-day-stats user=\"10\" local-date=\"" + date
+                        + "\" bucket-boundaries=\"0.0,1.0,3.0,10.0,30.0,100.0,1000.0,"
+                        + "3000.0,10000.0\" bucket-stats=\"1.088,0.0,0.726,0.0,25.868,0.0,0.0,"
+                        + "0.0,0.0,0.0\" />\r\n"
+                        + "</ambient-brightness-stats>";
+        try {
+            statsTracker.readStats(getInputStream(statsFile));
+        } catch (Exception e) {
+            // Expected
+        }
+        assertNull(statsTracker.getUserStats(0));
+        // Test with missing attribute
+        statsFile =
+                "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\r\n"
+                        + "<ambient-brightness-stats>\r\n"
+                        + "<ambientBrightnessDayStats user=\"10\" local-date=\"" + date
+                        + "\" bucket-boundaries=\"0.0,1.0,3.0,10.0,30.0,100.0,300.0,1000.0,"
+                        + "3000.0,10000.0\" />\r\n"
+                        + "</ambient-brightness-stats>";
+        try {
+            statsTracker.readStats(getInputStream(statsFile));
+        } catch (Exception e) {
+            // Expected
+        }
+        assertNull(statsTracker.getUserStats(0));
+    }
+
+    @Test
+    public void testWriteThenReadAmbientBrightnessStats() throws IOException {
+        AmbientBrightnessStatsTracker statsTracker = getTestStatsTracker();
+        ArrayList<AmbientBrightnessDayStats> userStats;
+        float[] expectedStats;
+        // Generate some placeholder data
+        // Data: very old which should not be read
+        statsTracker.start();
+        statsTracker.add(0, 0.05f);
+        mTestInjector.incrementTime(1000);
+        statsTracker.add(0, 0.2f);
+        mTestInjector.incrementTime(1500);
+        statsTracker.add(0, 1);
+        mTestInjector.incrementTime(2500);
+        statsTracker.stop();
+        // Data: day 1 user 1
+        mTestInjector.incrementDate(AmbientBrightnessStatsTracker.MAX_DAYS_TO_TRACK - 1);
+        statsTracker.start();
+        statsTracker.add(0, 0.05f);
+        mTestInjector.incrementTime(1000);
+        statsTracker.add(0, 0.2f);
+        mTestInjector.incrementTime(1500);
+        statsTracker.add(0, 1);
+        mTestInjector.incrementTime(2500);
+        statsTracker.stop();
+        // Data: day 1 user 2
+        statsTracker.start();
+        statsTracker.add(1, 0);
+        mTestInjector.incrementTime(3500);
+        statsTracker.add(1, 5);
+        mTestInjector.incrementTime(5000);
+        statsTracker.stop();
+        // Data: day 2 user 1
+        mTestInjector.incrementDate(1);
+        statsTracker.start();
+        statsTracker.add(0, 0);
+        mTestInjector.incrementTime(3500);
+        statsTracker.add(0, 50000);
+        mTestInjector.incrementTime(5000);
+        statsTracker.stop();
+        // Write them
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        statsTracker.writeStats(baos);
+        baos.flush();
+        // Read them back and assert that it's the same
+        ByteArrayInputStream input = new ByteArrayInputStream(baos.toByteArray());
+        AmbientBrightnessStatsTracker newStatsTracker = getTestStatsTracker();
+        newStatsTracker.readStats(input);
+        userStats = newStatsTracker.getUserStats(0);
+        assertEquals(2, userStats.size());
+        // Check day 1 user 1
+        assertEquals(mTestInjector.getLocalDate().minusDays(1), userStats.get(0).getLocalDate());
+        expectedStats = getEmptyStatsArray();
+        expectedStats[0] = 1;
+        expectedStats[1] = 1.5f;
+        expectedStats[3] = 2.5f;
+        assertArrayEquals(expectedStats, userStats.get(0).getStats(), 0);
+        // Check day 2 user 1
+        assertEquals(mTestInjector.getLocalDate(), userStats.get(1).getLocalDate());
+        expectedStats = getEmptyStatsArray();
+        expectedStats[0] = 3.5f;
+        expectedStats[11] = 5;
+        assertArrayEquals(expectedStats, userStats.get(1).getStats(), 0);
+        userStats = newStatsTracker.getUserStats(1);
+        assertEquals(1, userStats.size());
+        // Check day 1 user 2
+        assertEquals(mTestInjector.getLocalDate().minusDays(1), userStats.get(0).getLocalDate());
+        expectedStats = getEmptyStatsArray();
+        expectedStats[0] = 3.5f;
+        expectedStats[4] = 5;
+        assertArrayEquals(expectedStats, userStats.get(0).getStats(), 0);
+    }
+
+    @Test
+    public void testTimer() {
+        AmbientBrightnessStatsTracker.Timer timer = new AmbientBrightnessStatsTracker.Timer(
+                () -> mTestInjector.elapsedRealtimeMillis());
+        assertEquals(0, timer.totalDurationSec(), 0);
+        mTestInjector.incrementTime(1000);
+        assertEquals(0, timer.totalDurationSec(), 0);
+        assertFalse(timer.isRunning());
+        // Start timer
+        timer.start();
+        assertTrue(timer.isRunning());
+        assertEquals(0, timer.totalDurationSec(), 0);
+        mTestInjector.incrementTime(1000);
+        assertTrue(timer.isRunning());
+        assertEquals(1, timer.totalDurationSec(), 0);
+        // Reset timer
+        timer.reset();
+        assertEquals(0, timer.totalDurationSec(), 0);
+        assertFalse(timer.isRunning());
+        // Start again
+        timer.start();
+        assertTrue(timer.isRunning());
+        assertEquals(0, timer.totalDurationSec(), 0);
+        mTestInjector.incrementTime(2000);
+        assertTrue(timer.isRunning());
+        assertEquals(2, timer.totalDurationSec(), 0);
+        // Reset again
+        timer.reset();
+        assertEquals(0, timer.totalDurationSec(), 0);
+        assertFalse(timer.isRunning());
+    }
+
+    private class TestInjector extends AmbientBrightnessStatsTracker.Injector {
+
+        private long mElapsedRealtimeMillis = SystemClock.elapsedRealtime();
+        private LocalDate mLocalDate = LocalDate.now();
+
+        public void incrementTime(long timeMillis) {
+            mElapsedRealtimeMillis += timeMillis;
+        }
+
+        public void incrementDate(int numDays) {
+            mLocalDate = mLocalDate.plusDays(numDays);
+        }
+
+        @Override
+        public long elapsedRealtimeMillis() {
+            return mElapsedRealtimeMillis;
+        }
+
+        @Override
+        public int getUserSerialNumber(UserManager userManager, int userId) {
+            return userId + 10;
+        }
+
+        @Override
+        public int getUserId(UserManager userManager, int userSerialNumber) {
+            return userSerialNumber - 10;
+        }
+
+        @Override
+        public LocalDate getLocalDate() {
+            return mLocalDate;
+        }
+    }
+
+    private AmbientBrightnessStatsTracker getTestStatsTracker() {
+        return new AmbientBrightnessStatsTracker(
+                InstrumentationRegistry.getContext().getSystemService(UserManager.class),
+                mTestInjector);
+    }
+
+    private float[] getEmptyStatsArray() {
+        return new float[AmbientBrightnessStatsTracker.BUCKET_BOUNDARIES_FOR_NEW_STATS.length];
+    }
+
+    private InputStream getInputStream(String data) {
+        return new ByteArrayInputStream(data.getBytes(StandardCharsets.UTF_8));
+    }
+}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
new file mode 100644
index 0000000..a6acd60
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
@@ -0,0 +1,844 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyFloat;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.hardware.Sensor;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
+import android.os.Handler;
+import android.os.test.TestLooper;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.testutils.OffsettableClock;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class AutomaticBrightnessControllerTest {
+    private static final float BRIGHTNESS_MIN_FLOAT = 0.0f;
+    private static final float BRIGHTNESS_MAX_FLOAT = 1.0f;
+    private static final int LIGHT_SENSOR_RATE = 20;
+    private static final int INITIAL_LIGHT_SENSOR_RATE = 20;
+    private static final int BRIGHTENING_LIGHT_DEBOUNCE_CONFIG = 0;
+    private static final int DARKENING_LIGHT_DEBOUNCE_CONFIG = 0;
+    private static final float DOZE_SCALE_FACTOR = 0.0f;
+    private static final boolean RESET_AMBIENT_LUX_AFTER_WARMUP_CONFIG = false;
+    private static final int LIGHT_SENSOR_WARMUP_TIME = 0;
+    private static final int AMBIENT_LIGHT_HORIZON_SHORT = 1000;
+    private static final int AMBIENT_LIGHT_HORIZON_LONG = 2000;
+    private static final float EPSILON = 0.001f;
+    private OffsettableClock mClock = new OffsettableClock();
+    private TestLooper mTestLooper;
+    private Context mContext;
+    private AutomaticBrightnessController mController;
+    private Sensor mLightSensor;
+
+    @Mock SensorManager mSensorManager;
+    @Mock BrightnessMappingStrategy mBrightnessMappingStrategy;
+    @Mock BrightnessMappingStrategy mIdleBrightnessMappingStrategy;
+    @Mock HysteresisLevels mAmbientBrightnessThresholds;
+    @Mock HysteresisLevels mScreenBrightnessThresholds;
+    @Mock HysteresisLevels mAmbientBrightnessThresholdsIdle;
+    @Mock HysteresisLevels mScreenBrightnessThresholdsIdle;
+    @Mock Handler mNoOpHandler;
+    @Mock BrightnessRangeController mBrightnessRangeController;
+    @Mock BrightnessThrottler mBrightnessThrottler;
+
+    @Before
+    public void setUp() throws Exception {
+        // Share classloader to allow package private access.
+        System.setProperty("dexmaker.share_classloader", "true");
+        MockitoAnnotations.initMocks(this);
+
+        mLightSensor = TestUtils.createSensor(Sensor.TYPE_LIGHT, "Light Sensor");
+        mContext = InstrumentationRegistry.getContext();
+        mController = setupController(mLightSensor, BrightnessMappingStrategy.NO_USER_LUX,
+                BrightnessMappingStrategy.NO_USER_BRIGHTNESS);
+    }
+
+    @After
+    public void tearDown() {
+        if (mController != null) {
+            // Stop the update Brightness loop.
+            mController.stop();
+            mController = null;
+        }
+    }
+
+    private AutomaticBrightnessController setupController(Sensor lightSensor, float userLux,
+            float userBrightness) {
+        mClock = new OffsettableClock.Stopped();
+        mTestLooper = new TestLooper(mClock::now);
+
+        AutomaticBrightnessController controller = new AutomaticBrightnessController(
+                new AutomaticBrightnessController.Injector() {
+                    @Override
+                    public Handler getBackgroundThreadHandler() {
+                        return mNoOpHandler;
+                    }
+
+                    @Override
+                    AutomaticBrightnessController.Clock createClock() {
+                        return mClock::now;
+                    }
+
+                }, // pass in test looper instead, pass in offsettable clock
+                () -> { }, mTestLooper.getLooper(), mSensorManager, lightSensor,
+                mBrightnessMappingStrategy, LIGHT_SENSOR_WARMUP_TIME, BRIGHTNESS_MIN_FLOAT,
+                BRIGHTNESS_MAX_FLOAT, DOZE_SCALE_FACTOR, LIGHT_SENSOR_RATE,
+                INITIAL_LIGHT_SENSOR_RATE, BRIGHTENING_LIGHT_DEBOUNCE_CONFIG,
+                DARKENING_LIGHT_DEBOUNCE_CONFIG, RESET_AMBIENT_LUX_AFTER_WARMUP_CONFIG,
+                mAmbientBrightnessThresholds, mScreenBrightnessThresholds,
+                mAmbientBrightnessThresholdsIdle, mScreenBrightnessThresholdsIdle,
+                mContext, mBrightnessRangeController, mBrightnessThrottler,
+                mIdleBrightnessMappingStrategy, AMBIENT_LIGHT_HORIZON_SHORT,
+                AMBIENT_LIGHT_HORIZON_LONG, userLux, userBrightness
+        );
+
+        when(mBrightnessRangeController.getCurrentBrightnessMax()).thenReturn(
+                BRIGHTNESS_MAX_FLOAT);
+        when(mBrightnessRangeController.getCurrentBrightnessMin()).thenReturn(
+                BRIGHTNESS_MIN_FLOAT);
+        // Disable brightness throttling by default. Individual tests can enable it as needed.
+        when(mBrightnessThrottler.getBrightnessCap()).thenReturn(BRIGHTNESS_MAX_FLOAT);
+        when(mBrightnessThrottler.isThrottled()).thenReturn(false);
+
+        // Configure the brightness controller and grab an instance of the sensor listener,
+        // through which we can deliver fake (for test) sensor values.
+        controller.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration= */,
+                0 /* brightness= */, false /* userChangedBrightness= */, 0 /* adjustment= */,
+                false /* userChanged= */, DisplayPowerRequest.POLICY_BRIGHT,
+                /* shouldResetShortTermModel= */ true);
+
+        return controller;
+    }
+
+    @Test
+    public void testNoHysteresisAtMinBrightness() throws Exception {
+        ArgumentCaptor<SensorEventListener> listenerCaptor =
+                ArgumentCaptor.forClass(SensorEventListener.class);
+        verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
+                eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
+        SensorEventListener listener = listenerCaptor.getValue();
+
+        // Set up system to return 0.02f as a brightness value
+        float lux1 = 100.0f;
+        // Brightness as float (from 0.0f to 1.0f)
+        float normalizedBrightness1 = 0.02f;
+        when(mAmbientBrightnessThresholds.getBrighteningThreshold(lux1))
+                .thenReturn(lux1);
+        when(mAmbientBrightnessThresholds.getDarkeningThreshold(lux1))
+                .thenReturn(lux1);
+        when(mBrightnessMappingStrategy.getBrightness(eq(lux1), eq(null), anyInt()))
+                .thenReturn(normalizedBrightness1);
+
+        // This is the important bit: When the new brightness is set, make sure the new
+        // brightening threshold is beyond the maximum brightness value...so that we can test that
+        // our threshold clamping works.
+        when(mScreenBrightnessThresholds.getBrighteningThreshold(normalizedBrightness1))
+                .thenReturn(1.0f);
+
+        // Send new sensor value and verify
+        listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, (int) lux1));
+        assertEquals(normalizedBrightness1, mController.getAutomaticScreenBrightness(), EPSILON);
+
+        // Set up system to return 0.0f (minimum possible brightness) as a brightness value
+        float lux2 = 10.0f;
+        float normalizedBrightness2 = 0.0f;
+        when(mAmbientBrightnessThresholds.getBrighteningThreshold(lux2))
+                .thenReturn(lux2);
+        when(mAmbientBrightnessThresholds.getDarkeningThreshold(lux2))
+                .thenReturn(lux2);
+        when(mBrightnessMappingStrategy.getBrightness(anyFloat(), eq(null), anyInt()))
+                .thenReturn(normalizedBrightness2);
+
+        // Send new sensor value and verify
+        listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, (int) lux2));
+        assertEquals(normalizedBrightness2, mController.getAutomaticScreenBrightness(), EPSILON);
+    }
+
+    @Test
+    public void testNoHysteresisAtMaxBrightness() throws Exception {
+        ArgumentCaptor<SensorEventListener> listenerCaptor =
+                ArgumentCaptor.forClass(SensorEventListener.class);
+        verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
+                eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
+        SensorEventListener listener = listenerCaptor.getValue();
+
+        // Set up system to return 0.98f as a brightness value
+        float lux1 = 100.0f;
+        float normalizedBrightness1 = 0.98f;
+        when(mAmbientBrightnessThresholds.getBrighteningThreshold(lux1))
+                .thenReturn(lux1);
+        when(mAmbientBrightnessThresholds.getDarkeningThreshold(lux1))
+                .thenReturn(lux1);
+        when(mBrightnessMappingStrategy.getBrightness(eq(lux1), eq(null), anyInt()))
+                .thenReturn(normalizedBrightness1);
+
+        // This is the important bit: When the new brightness is set, make sure the new
+        // brightening threshold is beyond the maximum brightness value...so that we can test that
+        // our threshold clamping works.
+        when(mScreenBrightnessThresholds.getBrighteningThreshold(normalizedBrightness1))
+                .thenReturn(1.1f);
+
+        // Send new sensor value and verify
+        listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, (int) lux1));
+        assertEquals(normalizedBrightness1, mController.getAutomaticScreenBrightness(), EPSILON);
+
+
+        // Set up system to return 1.0f as a brightness value (brightness_max)
+        float lux2 = 110.0f;
+        float normalizedBrightness2 = 1.0f;
+        when(mAmbientBrightnessThresholds.getBrighteningThreshold(lux2))
+                .thenReturn(lux2);
+        when(mAmbientBrightnessThresholds.getDarkeningThreshold(lux2))
+                .thenReturn(lux2);
+        when(mBrightnessMappingStrategy.getBrightness(anyFloat(), eq(null), anyInt()))
+                .thenReturn(normalizedBrightness2);
+
+        // Send new sensor value and verify
+        listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, (int) lux2));
+        assertEquals(normalizedBrightness2, mController.getAutomaticScreenBrightness(), EPSILON);
+    }
+
+    @Test
+    public void testUserAddUserDataPoint() throws Exception {
+        ArgumentCaptor<SensorEventListener> listenerCaptor =
+                ArgumentCaptor.forClass(SensorEventListener.class);
+        verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
+                eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
+        SensorEventListener listener = listenerCaptor.getValue();
+
+        // Sensor reads 1000 lux,
+        listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1000));
+
+        // User sets brightness to 100
+        mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration= */,
+                0.5f /* brightness= */, true /* userChangedBrightness= */, 0 /* adjustment= */,
+                false /* userChanged= */, DisplayPowerRequest.POLICY_BRIGHT,
+                /* shouldResetShortTermModel= */ true);
+
+        // There should be a user data point added to the mapper.
+        verify(mBrightnessMappingStrategy).addUserDataPoint(1000f, 0.5f);
+    }
+
+    @Test
+    public void testRecalculateSplines() throws Exception {
+        // Enabling the light sensor, and setting the ambient lux to 1000
+        int currentLux = 1000;
+        ArgumentCaptor<SensorEventListener> listenerCaptor =
+                ArgumentCaptor.forClass(SensorEventListener.class);
+        verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
+                eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
+        SensorEventListener listener = listenerCaptor.getValue();
+        listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, currentLux));
+
+        // User sets brightness to 0.5f
+        when(mBrightnessMappingStrategy.getBrightness(currentLux,
+                null, ApplicationInfo.CATEGORY_UNDEFINED)).thenReturn(0.5f);
+        mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration= */,
+                0.5f /* brightness= */, true /* userChangedBrightness= */, 0 /* adjustment= */,
+                false /* userChanged= */, DisplayPowerRequest.POLICY_BRIGHT,
+                /* shouldResetShortTermModel= */ true);
+
+        //Recalculating the spline with RBC enabled, verifying that the short term model is reset,
+        //and the interaction is learnt in short term model
+        float[] adjustments = new float[]{0.2f, 0.6f};
+        mController.recalculateSplines(true, adjustments);
+        verify(mBrightnessMappingStrategy).clearUserDataPoints();
+        verify(mBrightnessMappingStrategy).recalculateSplines(true, adjustments);
+        verify(mBrightnessMappingStrategy, times(2)).addUserDataPoint(currentLux, 0.5f);
+
+        clearInvocations(mBrightnessMappingStrategy);
+
+        // Verify short term model is not learnt when RBC is disabled
+        mController.recalculateSplines(false, adjustments);
+        verify(mBrightnessMappingStrategy).clearUserDataPoints();
+        verify(mBrightnessMappingStrategy).recalculateSplines(false, adjustments);
+        verifyNoMoreInteractions(mBrightnessMappingStrategy);
+    }
+
+    @Test
+    public void testShortTermModelTimesOut() throws Exception {
+        ArgumentCaptor<SensorEventListener> listenerCaptor =
+                ArgumentCaptor.forClass(SensorEventListener.class);
+        verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
+                eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
+        SensorEventListener listener = listenerCaptor.getValue();
+
+        // Sensor reads 123 lux,
+        listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 123));
+        // User sets brightness to 100
+        mController.configure(AUTO_BRIGHTNESS_ENABLED, /* configuration= */ null,
+                /* brightness= */ 0.5f, /* userChangedBrightness= */ true, /* adjustment= */ 0,
+                /* userChanged= */ false, DisplayPowerRequest.POLICY_BRIGHT,
+                /* shouldResetShortTermModel= */ true);
+
+        when(mBrightnessMappingStrategy.getShortTermModelTimeout()).thenReturn(2000L);
+
+        mController.switchToIdleMode();
+        when(mIdleBrightnessMappingStrategy.isForIdleMode()).thenReturn(true);
+        when(mBrightnessMappingStrategy.shouldResetShortTermModel(
+                123f, 0.5f)).thenReturn(true);
+
+        // Sensor reads 1000 lux,
+        listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1000));
+        mTestLooper.moveTimeForward(
+                mBrightnessMappingStrategy.getShortTermModelTimeout() + 1000);
+        mTestLooper.dispatchAll();
+
+        mController.switchToInteractiveScreenBrightnessMode();
+        mTestLooper.moveTimeForward(4000);
+        mTestLooper.dispatchAll();
+
+        // Verify only happens on the first configure. (i.e. not again when switching back)
+        // Intentionally using any() to ensure it's not called whatsoever.
+        verify(mBrightnessMappingStrategy, times(1))
+                .addUserDataPoint(123.0f, 0.5f);
+        verify(mBrightnessMappingStrategy, times(1))
+                .addUserDataPoint(anyFloat(), anyFloat());
+    }
+
+    @Test
+    public void testShortTermModelDoesntTimeOut() throws Exception {
+        ArgumentCaptor<SensorEventListener> listenerCaptor =
+                ArgumentCaptor.forClass(SensorEventListener.class);
+        verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
+                eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
+        SensorEventListener listener = listenerCaptor.getValue();
+
+        // Sensor reads 123 lux,
+        listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 123));
+        // User sets brightness to 100
+        mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration= */,
+                0.51f /* brightness= */, true /* userChangedBrightness= */, 0 /* adjustment= */,
+                false /* userChanged= */, DisplayPowerRequest.POLICY_BRIGHT,
+                /* shouldResetShortTermModel= */ true);
+
+        when(mBrightnessMappingStrategy.shouldResetShortTermModel(
+                anyFloat(), anyFloat())).thenReturn(true);
+        when(mBrightnessMappingStrategy.getShortTermModelTimeout()).thenReturn(2000L);
+        when(mBrightnessMappingStrategy.getUserBrightness()).thenReturn(0.51f);
+        when(mBrightnessMappingStrategy.getUserLux()).thenReturn(123.0f);
+
+        mController.switchToIdleMode();
+        when(mIdleBrightnessMappingStrategy.isForIdleMode()).thenReturn(true);
+
+        // Time does not move forward, since clock is doesn't increment naturally.
+        mTestLooper.dispatchAll();
+
+        // Sensor reads 100000 lux,
+        listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 678910));
+        mController.switchToInteractiveScreenBrightnessMode();
+
+        // Verify short term model is not reset.
+        verify(mBrightnessMappingStrategy, never()).clearUserDataPoints();
+
+        // Verify that we add the data point once when the user sets it, and again when we return
+        // interactive mode.
+        verify(mBrightnessMappingStrategy, times(2))
+                .addUserDataPoint(123.0f, 0.51f);
+    }
+
+    @Test
+    public void testShortTermModelIsRestoredWhenSwitchingWithinTimeout() throws Exception {
+        ArgumentCaptor<SensorEventListener> listenerCaptor =
+                ArgumentCaptor.forClass(SensorEventListener.class);
+        verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
+                eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
+        SensorEventListener listener = listenerCaptor.getValue();
+
+        // Sensor reads 123 lux,
+        listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 123));
+        // User sets brightness to 100
+        mController.configure(AUTO_BRIGHTNESS_ENABLED, /* configuration= */ null,
+                /* brightness= */ 0.5f, /* userChangedBrightness= */ true, /* adjustment= */ 0,
+                /* userChanged= */ false, DisplayPowerRequest.POLICY_BRIGHT,
+                /* shouldResetShortTermModel= */ true);
+
+        when(mBrightnessMappingStrategy.getShortTermModelTimeout()).thenReturn(2000L);
+        when(mBrightnessMappingStrategy.getUserBrightness()).thenReturn(0.5f);
+        when(mBrightnessMappingStrategy.getUserLux()).thenReturn(123f);
+
+        mController.switchToIdleMode();
+        when(mIdleBrightnessMappingStrategy.isForIdleMode()).thenReturn(true);
+        when(mIdleBrightnessMappingStrategy.getUserBrightness()).thenReturn(-1f);
+        when(mIdleBrightnessMappingStrategy.getUserLux()).thenReturn(-1f);
+        when(mBrightnessMappingStrategy.shouldResetShortTermModel(
+                123f, 0.5f)).thenReturn(true);
+
+        // Sensor reads 1000 lux,
+        listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1000));
+        mTestLooper.moveTimeForward(
+                mBrightnessMappingStrategy.getShortTermModelTimeout() + 1000);
+        mTestLooper.dispatchAll();
+
+        mController.switchToInteractiveScreenBrightnessMode();
+        mTestLooper.moveTimeForward(4000);
+        mTestLooper.dispatchAll();
+
+        // Verify only happens on the first configure. (i.e. not again when switching back)
+        // Intentionally using any() to ensure it's not called whatsoever.
+        verify(mBrightnessMappingStrategy, times(1))
+                .addUserDataPoint(123.0f, 0.5f);
+        verify(mBrightnessMappingStrategy, times(1))
+                .addUserDataPoint(anyFloat(), anyFloat());
+    }
+
+    @Test
+    public void testShortTermModelNotRestoredAfterTimeout() throws Exception {
+        ArgumentCaptor<SensorEventListener> listenerCaptor =
+                ArgumentCaptor.forClass(SensorEventListener.class);
+        verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
+                eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
+        SensorEventListener listener = listenerCaptor.getValue();
+
+        // Sensor reads 123 lux,
+        listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 123));
+        // User sets brightness to 100
+        mController.configure(AUTO_BRIGHTNESS_ENABLED, /* configuration= */ null,
+                /* brightness= */ 0.5f, /* userChangedBrightness= */ true, /* adjustment= */ 0,
+                /* userChanged= */ false, DisplayPowerRequest.POLICY_BRIGHT,
+                /* shouldResetShortTermModel= */ true);
+
+        when(mBrightnessMappingStrategy.getShortTermModelTimeout()).thenReturn(2000L);
+
+        when(mBrightnessMappingStrategy.getUserBrightness()).thenReturn(0.5f);
+        when(mBrightnessMappingStrategy.getUserLux()).thenReturn(123f);
+
+        mController.switchToIdleMode();
+        when(mIdleBrightnessMappingStrategy.isForIdleMode()).thenReturn(true);
+        when(mIdleBrightnessMappingStrategy.getUserBrightness()).thenReturn(-1f);
+        when(mIdleBrightnessMappingStrategy.getUserLux()).thenReturn(-1f);
+
+        when(mBrightnessMappingStrategy.shouldResetShortTermModel(
+                123f, 0.5f)).thenReturn(true);
+
+        // Sensor reads 1000 lux,
+        listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1000));
+        // Do not fast-forward time.
+        mTestLooper.dispatchAll();
+
+        mController.switchToInteractiveScreenBrightnessMode();
+        // Do not fast-forward time
+        mTestLooper.dispatchAll();
+
+        // Verify this happens on the first configure and again when switching back
+        // Intentionally using any() to ensure it's not called any other times whatsoever.
+        verify(mBrightnessMappingStrategy, times(2))
+                .addUserDataPoint(123.0f, 0.5f);
+        verify(mBrightnessMappingStrategy, times(2))
+                .addUserDataPoint(anyFloat(), anyFloat());
+    }
+
+    @Test
+    public void testSwitchBetweenModesNoUserInteractions() throws Exception {
+        ArgumentCaptor<SensorEventListener> listenerCaptor =
+                ArgumentCaptor.forClass(SensorEventListener.class);
+        verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
+                eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
+        SensorEventListener listener = listenerCaptor.getValue();
+
+        // Sensor reads 123 lux,
+        listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 123));
+        when(mBrightnessMappingStrategy.getShortTermModelTimeout()).thenReturn(2000L);
+        when(mBrightnessMappingStrategy.getUserBrightness()).thenReturn(-1.0f);
+        when(mBrightnessMappingStrategy.getUserLux()).thenReturn(-1.0f);
+
+        // No user brightness interaction.
+
+        mController.switchToIdleMode();
+        when(mIdleBrightnessMappingStrategy.isForIdleMode()).thenReturn(true);
+        when(mIdleBrightnessMappingStrategy.getUserBrightness()).thenReturn(-1.0f);
+        when(mIdleBrightnessMappingStrategy.getUserLux()).thenReturn(-1.0f);
+
+        // Sensor reads 1000 lux,
+        listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1000));
+        // Do not fast-forward time.
+        mTestLooper.dispatchAll();
+
+        mController.switchToInteractiveScreenBrightnessMode();
+        // Do not fast-forward time
+        mTestLooper.dispatchAll();
+
+        // Ensure that there are no data points added, since the user has never adjusted the
+        // brightness
+        verify(mBrightnessMappingStrategy, times(0))
+                .addUserDataPoint(anyFloat(), anyFloat());
+    }
+
+    @Test
+    public void testSwitchToIdleMappingStrategy() throws Exception {
+        ArgumentCaptor<SensorEventListener> listenerCaptor =
+                ArgumentCaptor.forClass(SensorEventListener.class);
+        verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
+                eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
+        SensorEventListener listener = listenerCaptor.getValue();
+
+        // Sensor reads 1000 lux,
+        listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1000));
+
+        // User sets brightness to 100
+        mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration= */,
+                0.5f /* brightness= */, true /* userChangedBrightness= */, 0 /* adjustment= */,
+                false /* userChanged= */, DisplayPowerRequest.POLICY_BRIGHT,
+                /* shouldResetShortTermModel= */ true);
+
+        // There should be a user data point added to the mapper.
+        verify(mBrightnessMappingStrategy, times(1)).addUserDataPoint(1000f, 0.5f);
+        verify(mBrightnessMappingStrategy, times(2)).setBrightnessConfiguration(any());
+        verify(mBrightnessMappingStrategy, times(3)).getBrightness(anyFloat(), any(), anyInt());
+
+        // Now let's do the same for idle mode
+        mController.switchToIdleMode();
+        // Called once for init, and once when switching,
+        // setAmbientLux() is called twice and once in updateAutoBrightness()
+        verify(mBrightnessMappingStrategy, times(5)).isForIdleMode();
+        // Called when switching.
+        verify(mBrightnessMappingStrategy, times(1)).getShortTermModelTimeout();
+        verify(mBrightnessMappingStrategy, times(1)).getUserBrightness();
+        verify(mBrightnessMappingStrategy, times(1)).getUserLux();
+
+        // Ensure, after switching, original BMS is not used anymore
+        verifyNoMoreInteractions(mBrightnessMappingStrategy);
+
+        // User sets idle brightness to 0.5
+        mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration= */,
+                0.5f /* brightness= */, true /* userChangedBrightness= */, 0 /* adjustment= */,
+                false /* userChanged= */, DisplayPowerRequest.POLICY_BRIGHT,
+                /* shouldResetShortTermModel= */ true);
+
+        // Ensure we use the correct mapping strategy
+        verify(mIdleBrightnessMappingStrategy, times(1)).addUserDataPoint(1000f, 0.5f);
+    }
+
+    @Test
+    public void testAmbientLightHorizon() throws Exception {
+        ArgumentCaptor<SensorEventListener> listenerCaptor =
+                ArgumentCaptor.forClass(SensorEventListener.class);
+        verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
+                eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
+        SensorEventListener listener = listenerCaptor.getValue();
+
+        long increment = 500;
+        // set autobrightness to low
+        // t = 0
+        listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0));
+
+        // t = 500
+        mClock.fastForward(increment);
+        listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0));
+
+        // t = 1000
+        mClock.fastForward(increment);
+        listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0));
+        assertEquals(0.0f, mController.getAmbientLux(), EPSILON);
+
+        // t = 1500
+        mClock.fastForward(increment);
+        listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0));
+        assertEquals(0.0f, mController.getAmbientLux(), EPSILON);
+
+        // t = 2000
+        // ensure that our reading is at 0.
+        mClock.fastForward(increment);
+        listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0));
+        assertEquals(0.0f, mController.getAmbientLux(), EPSILON);
+
+        // t = 2500
+        // first 10000 lux sensor event reading
+        mClock.fastForward(increment);
+        listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 10000));
+        assertTrue(mController.getAmbientLux() > 0.0f);
+        assertTrue(mController.getAmbientLux() < 10000.0f);
+
+        // t = 3000
+        // lux reading should still not yet be 10000.
+        mClock.fastForward(increment);
+        listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 10000));
+        assertTrue(mController.getAmbientLux() > 0.0f);
+        assertTrue(mController.getAmbientLux() < 10000.0f);
+
+        // t = 3500
+        mClock.fastForward(increment);
+        // lux has been high (10000) for 1000ms.
+        // lux reading should be 10000
+        // short horizon (ambient lux) is high, long horizon is still not high
+        listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 10000));
+        assertEquals(10000.0f, mController.getAmbientLux(), EPSILON);
+
+        // t = 4000
+        // stay high
+        mClock.fastForward(increment);
+        listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 10000));
+        assertEquals(10000.0f, mController.getAmbientLux(), EPSILON);
+
+        // t = 4500
+        Mockito.clearInvocations(mBrightnessMappingStrategy);
+        mClock.fastForward(increment);
+        // short horizon is high, long horizon is high too
+        listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 10000));
+        verify(mBrightnessMappingStrategy, times(1)).getBrightness(10000, null, -1);
+        assertEquals(10000.0f, mController.getAmbientLux(), EPSILON);
+
+        // t = 5000
+        mClock.fastForward(increment);
+        listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0));
+        assertTrue(mController.getAmbientLux() > 0.0f);
+        assertTrue(mController.getAmbientLux() < 10000.0f);
+
+        // t = 5500
+        mClock.fastForward(increment);
+        listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0));
+        assertTrue(mController.getAmbientLux() > 0.0f);
+        assertTrue(mController.getAmbientLux() < 10000.0f);
+
+        // t = 6000
+        mClock.fastForward(increment);
+        // ambient lux goes to 0
+        listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0));
+        assertEquals(0.0f, mController.getAmbientLux(), EPSILON);
+
+        // only the values within the horizon should be kept
+        assertArrayEquals(new float[] {10000, 10000, 0, 0, 0}, mController.getLastSensorValues(),
+                EPSILON);
+        assertArrayEquals(new long[] {4000, 4500, 5000, 5500, 6000},
+                mController.getLastSensorTimestamps());
+    }
+
+    @Test
+    public void testHysteresisLevels() {
+        float[] ambientBrighteningThresholds = {50, 100};
+        float[] ambientDarkeningThresholds = {10, 20};
+        float[] ambientThresholdLevels = {0, 500};
+        float ambientDarkeningMinChangeThreshold = 3.0f;
+        float ambientBrighteningMinChangeThreshold = 1.5f;
+        HysteresisLevels hysteresisLevels = new HysteresisLevels(ambientBrighteningThresholds,
+                ambientDarkeningThresholds, ambientThresholdLevels, ambientThresholdLevels,
+                ambientDarkeningMinChangeThreshold, ambientBrighteningMinChangeThreshold);
+
+        // test low, activate minimum change thresholds.
+        assertEquals(1.5f, hysteresisLevels.getBrighteningThreshold(0.0f), EPSILON);
+        assertEquals(0f, hysteresisLevels.getDarkeningThreshold(0.0f), EPSILON);
+        assertEquals(1f, hysteresisLevels.getDarkeningThreshold(4.0f), EPSILON);
+
+        // test max
+        // epsilon is x2 here, since the next floating point value about 20,000 is 0.0019531 greater
+        assertEquals(20000f, hysteresisLevels.getBrighteningThreshold(10000.0f), EPSILON * 2);
+        assertEquals(8000f, hysteresisLevels.getDarkeningThreshold(10000.0f), EPSILON);
+
+        // test just below threshold
+        assertEquals(748.5f, hysteresisLevels.getBrighteningThreshold(499f), EPSILON);
+        assertEquals(449.1f, hysteresisLevels.getDarkeningThreshold(499f), EPSILON);
+
+        // test at (considered above) threshold
+        assertEquals(1000f, hysteresisLevels.getBrighteningThreshold(500f), EPSILON);
+        assertEquals(400f, hysteresisLevels.getDarkeningThreshold(500f), EPSILON);
+    }
+
+    @Test
+    public void testBrightnessGetsThrottled() throws Exception {
+        ArgumentCaptor<SensorEventListener> listenerCaptor =
+                ArgumentCaptor.forClass(SensorEventListener.class);
+        verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
+                eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
+        SensorEventListener listener = listenerCaptor.getValue();
+
+        // Set up system to return max brightness at 100 lux
+        final float normalizedBrightness = BRIGHTNESS_MAX_FLOAT;
+        final float lux = 100.0f;
+        when(mAmbientBrightnessThresholds.getBrighteningThreshold(lux))
+                .thenReturn(lux);
+        when(mAmbientBrightnessThresholds.getDarkeningThreshold(lux))
+                .thenReturn(lux);
+        when(mBrightnessMappingStrategy.getBrightness(eq(lux), eq(null), anyInt()))
+                .thenReturn(normalizedBrightness);
+
+        // Sensor reads 100 lux. We should get max brightness.
+        listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, (int) lux));
+        assertEquals(BRIGHTNESS_MAX_FLOAT, mController.getAutomaticScreenBrightness(), 0.0f);
+        assertEquals(BRIGHTNESS_MAX_FLOAT, mController.getRawAutomaticScreenBrightness(), 0.0f);
+
+        // Apply throttling and notify ABC (simulates DisplayPowerController#updatePowerState())
+        final float throttledBrightness = 0.123f;
+        when(mBrightnessThrottler.getBrightnessCap()).thenReturn(throttledBrightness);
+        when(mBrightnessThrottler.isThrottled()).thenReturn(true);
+        mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration= */,
+                BRIGHTNESS_MAX_FLOAT /* brightness= */, false /* userChangedBrightness= */,
+                0 /* adjustment= */, false /* userChanged= */, DisplayPowerRequest.POLICY_BRIGHT,
+                /* shouldResetShortTermModel= */ true);
+        assertEquals(throttledBrightness, mController.getAutomaticScreenBrightness(), 0.0f);
+        // The raw brightness value should not have throttling applied
+        assertEquals(BRIGHTNESS_MAX_FLOAT, mController.getRawAutomaticScreenBrightness(), 0.0f);
+
+        // Remove throttling and notify ABC again
+        when(mBrightnessThrottler.getBrightnessCap()).thenReturn(BRIGHTNESS_MAX_FLOAT);
+        when(mBrightnessThrottler.isThrottled()).thenReturn(false);
+        mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration= */,
+                BRIGHTNESS_MAX_FLOAT /* brightness= */, false /* userChangedBrightness= */,
+                0 /* adjustment= */, false /* userChanged= */, DisplayPowerRequest.POLICY_BRIGHT,
+                /* shouldResetShortTermModel= */ true);
+        assertEquals(BRIGHTNESS_MAX_FLOAT, mController.getAutomaticScreenBrightness(), 0.0f);
+        assertEquals(BRIGHTNESS_MAX_FLOAT, mController.getRawAutomaticScreenBrightness(), 0.0f);
+    }
+
+    @Test
+    public void testGetSensorReadings() throws Exception {
+        ArgumentCaptor<SensorEventListener> listenerCaptor =
+                ArgumentCaptor.forClass(SensorEventListener.class);
+        verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
+                eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
+        SensorEventListener listener = listenerCaptor.getValue();
+
+        // Choose values such that the ring buffer's capacity is extended and the buffer is pruned
+        int increment = 11;
+        int lux = 5000;
+        for (int i = 0; i < 1000; i++) {
+            lux += increment;
+            mClock.fastForward(increment);
+            listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, lux));
+        }
+
+        int valuesCount = (int) Math.ceil((double) AMBIENT_LIGHT_HORIZON_LONG / increment + 1);
+        float[] sensorValues = mController.getLastSensorValues();
+        long[] sensorTimestamps = mController.getLastSensorTimestamps();
+
+        // Only the values within the horizon should be kept
+        assertEquals(valuesCount, sensorValues.length);
+        assertEquals(valuesCount, sensorTimestamps.length);
+
+        long sensorTimestamp = mClock.now();
+        for (int i = valuesCount - 1; i >= 1; i--) {
+            assertEquals(lux, sensorValues[i], EPSILON);
+            assertEquals(sensorTimestamp, sensorTimestamps[i]);
+            lux -= increment;
+            sensorTimestamp -= increment;
+        }
+        assertEquals(lux, sensorValues[0], EPSILON);
+        assertEquals(mClock.now() - AMBIENT_LIGHT_HORIZON_LONG, sensorTimestamps[0]);
+    }
+
+    @Test
+    public void testGetSensorReadingsFullBuffer() throws Exception {
+        ArgumentCaptor<SensorEventListener> listenerCaptor =
+                ArgumentCaptor.forClass(SensorEventListener.class);
+        verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
+                eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
+        SensorEventListener listener = listenerCaptor.getValue();
+        int initialCapacity = 150;
+
+        // Choose values such that the ring buffer is pruned
+        int increment1 = 200;
+        int lux = 5000;
+        for (int i = 0; i < 20; i++) {
+            lux += increment1;
+            mClock.fastForward(increment1);
+            listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, lux));
+        }
+
+        int valuesCount = (int) Math.ceil((double) AMBIENT_LIGHT_HORIZON_LONG / increment1 + 1);
+
+        // Choose values such that the buffer becomes full
+        int increment2 = 1;
+        for (int i = 0; i < initialCapacity - valuesCount; i++) {
+            lux += increment2;
+            mClock.fastForward(increment2);
+            listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, lux));
+        }
+
+        float[] sensorValues = mController.getLastSensorValues();
+        long[] sensorTimestamps = mController.getLastSensorTimestamps();
+
+        // The buffer should be full
+        assertEquals(initialCapacity, sensorValues.length);
+        assertEquals(initialCapacity, sensorTimestamps.length);
+
+        long sensorTimestamp = mClock.now();
+        for (int i = initialCapacity - 1; i >= 1; i--) {
+            assertEquals(lux, sensorValues[i], EPSILON);
+            assertEquals(sensorTimestamp, sensorTimestamps[i]);
+
+            if (i >= valuesCount) {
+                lux -= increment2;
+                sensorTimestamp -= increment2;
+            } else {
+                lux -= increment1;
+                sensorTimestamp -= increment1;
+            }
+        }
+        assertEquals(lux, sensorValues[0], EPSILON);
+        assertEquals(mClock.now() - AMBIENT_LIGHT_HORIZON_LONG, sensorTimestamps[0]);
+    }
+
+    @Test
+    public void testResetShortTermModelWhenConfigChanges() {
+        when(mBrightnessMappingStrategy.isForIdleMode()).thenReturn(false);
+        when(mBrightnessMappingStrategy.setBrightnessConfiguration(any())).thenReturn(true);
+
+        mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration= */,
+                BRIGHTNESS_MAX_FLOAT /* brightness= */, false /* userChangedBrightness= */,
+                0 /* adjustment= */, false /* userChanged= */, DisplayPowerRequest.POLICY_BRIGHT,
+                /* shouldResetShortTermModel= */ false);
+        verify(mBrightnessMappingStrategy, never()).clearUserDataPoints();
+
+        mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration= */,
+                BRIGHTNESS_MAX_FLOAT /* brightness= */, false /* userChangedBrightness= */,
+                0 /* adjustment= */, false /* userChanged= */, DisplayPowerRequest.POLICY_BRIGHT,
+                /* shouldResetShortTermModel= */ true);
+        verify(mBrightnessMappingStrategy).clearUserDataPoints();
+    }
+
+    @Test
+    public void testUseProvidedShortTermModel() {
+        verify(mBrightnessMappingStrategy, never()).addUserDataPoint(anyFloat(), anyFloat());
+
+        float userLux = 1000;
+        float userBrightness = 0.3f;
+        setupController(mLightSensor, userLux, userBrightness);
+        verify(mBrightnessMappingStrategy).addUserDataPoint(userLux, userBrightness);
+    }
+}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/BrightnessMappingStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/BrightnessMappingStrategyTest.java
new file mode 100644
index 0000000..ee7826f
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/BrightnessMappingStrategyTest.java
@@ -0,0 +1,710 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.hardware.display.BrightnessConfiguration;
+import android.os.PowerManager;
+import android.util.MathUtils;
+import android.util.Spline;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.display.whitebalance.DisplayWhiteBalanceController;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+
+import java.util.Arrays;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class BrightnessMappingStrategyTest {
+
+    private static final float[] LUX_LEVELS = {
+        0,
+        5,
+        20,
+        40,
+        100,
+        325,
+        600,
+        1250,
+        2200,
+        4000,
+        5000
+    };
+
+    private static final int[] LUX_LEVELS_IDLE = {
+        0,
+        10,
+        40,
+        80,
+        200,
+        655,
+        1200,
+        2500,
+        4400,
+        8000,
+        10000
+    };
+
+    private static final float[] DISPLAY_LEVELS_NITS = {
+        13.25f,
+        54.0f,
+        78.85f,
+        105.02f,
+        132.7f,
+        170.12f,
+        212.1f,
+        265.2f,
+        335.8f,
+        415.2f,
+        478.5f,
+    };
+
+    private static final float[] DISPLAY_LEVELS_NITS_IDLE = {
+        23.25f,
+        64.0f,
+        88.85f,
+        115.02f,
+        142.7f,
+        180.12f,
+        222.1f,
+        275.2f,
+        345.8f,
+        425.2f,
+        468.5f,
+    };
+
+    private static final int[] DISPLAY_LEVELS_BACKLIGHT = {
+        9,
+        30,
+        45,
+        62,
+        78,
+        96,
+        119,
+        146,
+        178,
+        221,
+        255
+    };
+
+    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 };
+
+    private static final float[] EMPTY_FLOAT_ARRAY = new float[0];
+    private static final int[] EMPTY_INT_ARRAY = new int[0];
+
+    private static final float MAXIMUM_GAMMA = 3.0f;
+
+    private static final float[] GAMMA_CORRECTION_LUX = {
+        0,
+        100,
+        1000,
+        2500,
+        4000,
+        4900,
+        5000
+    };
+    private static final float[] GAMMA_CORRECTION_NITS = {
+        1.0f,
+        10.55f,
+        96.5f,
+        239.75f,
+        383.0f,
+        468.95f,
+        478.5f,
+    };
+    private static final Spline GAMMA_CORRECTION_SPLINE = Spline.createSpline(
+            new float[] { 0.0f, 100.0f, 1000.0f, 2500.0f, 4000.0f, 4900.0f, 5000.0f },
+            new float[] { 0.0475f, 0.0475f, 0.2225f, 0.5140f, 0.8056f, 0.9805f, 1.0f });
+
+    private static final float TOLERANCE = 0.0001f;
+
+    @Mock
+    DisplayWhiteBalanceController mMockDwbc;
+
+    @Test
+    public void testSimpleStrategyMappingAtControlPoints() {
+        Resources res = createResources(DISPLAY_LEVELS_BACKLIGHT);
+        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]);
+            assertEquals(expectedLevel,
+                    simple.getBrightness(LUX_LEVELS[i]), 0.0001f /*tolerance*/);
+        }
+    }
+
+    @Test
+    public void testSimpleStrategyMappingBetweenControlPoints() {
+        Resources res = createResources(DISPLAY_LEVELS_BACKLIGHT);
+        DisplayDeviceConfig ddc = createDdc();
+        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 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]);
+        }
+    }
+
+    @Test
+    public void testSimpleStrategyIgnoresNewConfiguration() {
+        Resources res = createResources(DISPLAY_LEVELS_BACKLIGHT);
+        DisplayDeviceConfig ddc = createDdc();
+        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
+
+        final float[] lux = { 0f, 1f };
+        final float[] nits = { 0, PowerManager.BRIGHTNESS_ON };
+
+        BrightnessConfiguration config = new BrightnessConfiguration.Builder(lux, nits)
+                .build();
+        strategy.setBrightnessConfiguration(config);
+        assertNotEquals(1.0f, strategy.getBrightness(1f), 0.0001f /*tolerance*/);
+    }
+
+    @Test
+    public void testSimpleStrategyIgnoresNullConfiguration() {
+        Resources res = createResources(DISPLAY_LEVELS_BACKLIGHT);
+        DisplayDeviceConfig ddc = createDdc();
+        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
+
+        strategy.setBrightnessConfiguration(null);
+        final int n = DISPLAY_LEVELS_BACKLIGHT.length;
+        final float expectedBrightness =
+                (float) DISPLAY_LEVELS_BACKLIGHT[n - 1] / PowerManager.BRIGHTNESS_ON;
+        assertEquals(expectedBrightness,
+                strategy.getBrightness(LUX_LEVELS[n - 1]), 0.0001f /*tolerance*/);
+    }
+
+    @Test
+    public void testPhysicalStrategyMappingAtControlPoints() {
+        Resources res = createResources(EMPTY_INT_ARRAY);
+        DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS,
+                DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT,
+                LUX_LEVELS, DISPLAY_LEVELS_NITS);
+        BrightnessMappingStrategy physical = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
+        assertNotNull("BrightnessMappingStrategy should not be null", physical);
+        for (int i = 0; i < LUX_LEVELS.length; i++) {
+            final float expectedLevel = MathUtils.map(DISPLAY_RANGE_NITS[0], DISPLAY_RANGE_NITS[1],
+                    DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT[0],
+                    DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT[1],
+                    DISPLAY_LEVELS_NITS[i]);
+            assertEquals(expectedLevel,
+                    physical.getBrightness(LUX_LEVELS[i]),
+                    0.0001f /*tolerance*/);
+        }
+    }
+
+    @Test
+    public void testPhysicalStrategyMappingBetweenControlPoints() {
+        Resources res = createResources(EMPTY_INT_ARRAY);
+        DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS, BACKLIGHT_RANGE_ZERO_TO_ONE,
+                LUX_LEVELS, DISPLAY_LEVELS_NITS);
+        BrightnessMappingStrategy physical = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
+        assertNotNull("BrightnessMappingStrategy should not be null", physical);
+        Spline brightnessToNits =
+                Spline.createSpline(BACKLIGHT_RANGE_ZERO_TO_ONE, DISPLAY_RANGE_NITS);
+        for (int i = 1; i < LUX_LEVELS.length; i++) {
+            final float lux = (LUX_LEVELS[i - 1] + LUX_LEVELS[i]) / 2.0f;
+            final float brightness = physical.getBrightness(lux);
+            final float nits = brightnessToNits.interpolate(brightness);
+            assertTrue("Desired brightness should be between adjacent control points: " + nits,
+                    nits > DISPLAY_LEVELS_NITS[i - 1] && nits < DISPLAY_LEVELS_NITS[i]);
+        }
+    }
+
+    @Test
+    public void testPhysicalStrategyUsesNewConfigurations() {
+        Resources res = createResources(EMPTY_INT_ARRAY);
+        DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS,
+                DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, LUX_LEVELS, DISPLAY_LEVELS_NITS);
+        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
+
+        final float[] lux = {0f, 1f};
+        final float[] nits = {
+                DISPLAY_RANGE_NITS[0],
+                DISPLAY_RANGE_NITS[DISPLAY_RANGE_NITS.length - 1]
+        };
+
+        BrightnessConfiguration config = new BrightnessConfiguration.Builder(lux, nits)
+                .build();
+        strategy.setBrightnessConfiguration(config);
+        assertEquals(1.0f, strategy.getBrightness(1f), 0.0001f /*tolerance*/);
+
+        // Check that null returns us to the default configuration.
+        strategy.setBrightnessConfiguration(null);
+        final int n = DISPLAY_LEVELS_NITS.length;
+        final float expectedBrightness = DISPLAY_LEVELS_NITS[n - 1] / DISPLAY_RANGE_NITS[1];
+        assertEquals(expectedBrightness,
+                strategy.getBrightness(LUX_LEVELS[n - 1]), 0.0001f /*tolerance*/);
+    }
+
+    @Test
+    public void testPhysicalStrategyRecalculateSplines() {
+        Resources res = createResources(EMPTY_INT_ARRAY);
+        DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS,
+                DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, LUX_LEVELS, DISPLAY_LEVELS_NITS);
+        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
+        float[] adjustedNits50p = new float[DISPLAY_RANGE_NITS.length];
+        for (int i = 0; i < DISPLAY_RANGE_NITS.length; i++) {
+            adjustedNits50p[i] = DISPLAY_RANGE_NITS[i] * 0.5f;
+        }
+
+        // Default
+        assertEquals(DISPLAY_RANGE_NITS[0], strategy.convertToNits(BACKLIGHT_RANGE_ZERO_TO_ONE[0]),
+                TOLERANCE);
+        assertEquals(DISPLAY_RANGE_NITS[1], strategy.convertToNits(BACKLIGHT_RANGE_ZERO_TO_ONE[1]),
+                TOLERANCE);
+        assertEquals(DISPLAY_RANGE_NITS[0],
+                strategy.convertToAdjustedNits(BACKLIGHT_RANGE_ZERO_TO_ONE[0]), TOLERANCE);
+        assertEquals(DISPLAY_RANGE_NITS[1],
+                strategy.convertToAdjustedNits(BACKLIGHT_RANGE_ZERO_TO_ONE[1]), TOLERANCE);
+
+        // Adjustment is turned on
+        strategy.recalculateSplines(true, adjustedNits50p);
+        assertEquals(DISPLAY_RANGE_NITS[0], strategy.convertToNits(BACKLIGHT_RANGE_ZERO_TO_ONE[0]),
+                TOLERANCE);
+        assertEquals(DISPLAY_RANGE_NITS[1], strategy.convertToNits(BACKLIGHT_RANGE_ZERO_TO_ONE[1]),
+                TOLERANCE);
+        assertEquals(DISPLAY_RANGE_NITS[0] / 2,
+                strategy.convertToAdjustedNits(BACKLIGHT_RANGE_ZERO_TO_ONE[0]), TOLERANCE);
+        assertEquals(DISPLAY_RANGE_NITS[1] / 2,
+                strategy.convertToAdjustedNits(BACKLIGHT_RANGE_ZERO_TO_ONE[1]), TOLERANCE);
+
+        // Adjustment is turned off
+        strategy.recalculateSplines(false, adjustedNits50p);
+        assertEquals(DISPLAY_RANGE_NITS[0], strategy.convertToNits(BACKLIGHT_RANGE_ZERO_TO_ONE[0]),
+                TOLERANCE);
+        assertEquals(DISPLAY_RANGE_NITS[1], strategy.convertToNits(BACKLIGHT_RANGE_ZERO_TO_ONE[1]),
+                TOLERANCE);
+        assertEquals(DISPLAY_RANGE_NITS[0],
+                strategy.convertToAdjustedNits(BACKLIGHT_RANGE_ZERO_TO_ONE[0]), TOLERANCE);
+        assertEquals(DISPLAY_RANGE_NITS[1],
+                strategy.convertToAdjustedNits(BACKLIGHT_RANGE_ZERO_TO_ONE[1]), TOLERANCE);
+    }
+
+    @Test
+    public void testDefaultStrategyIsPhysical() {
+        Resources res = createResources(DISPLAY_LEVELS_BACKLIGHT);
+        DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS,
+                DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, LUX_LEVELS, DISPLAY_LEVELS_NITS);
+        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
+        assertTrue(strategy instanceof BrightnessMappingStrategy.PhysicalMappingStrategy);
+    }
+
+    @Test
+    public void testNonStrictlyIncreasingLuxLevelsFails() {
+        final float[] lux = Arrays.copyOf(LUX_LEVELS, LUX_LEVELS.length);
+        final int idx = lux.length / 2;
+        float tmp = lux[idx];
+        lux[idx] = lux[idx + 1];
+        lux[idx + 1] = tmp;
+        Resources res = createResources(EMPTY_INT_ARRAY);
+        DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS,
+                DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, lux, DISPLAY_LEVELS_NITS);
+        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
+        assertNull(strategy);
+
+        // And make sure we get the same result even if it's monotone but not increasing.
+        lux[idx] = lux[idx + 1];
+        ddc = createDdc(DISPLAY_RANGE_NITS, DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, lux,
+                DISPLAY_LEVELS_NITS);
+        strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
+        assertNull(strategy);
+    }
+
+    @Test
+    public void testDifferentNumberOfControlPointValuesFails() {
+        //Extra lux level
+        final float[] lux = Arrays.copyOf(LUX_LEVELS, LUX_LEVELS.length + 1);
+        // Make sure it's strictly increasing so that the only failure is the differing array
+        // lengths
+        lux[lux.length - 1] = lux[lux.length - 2] + 1;
+        Resources res = createResources(EMPTY_INT_ARRAY);
+        DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS,
+                DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, lux, DISPLAY_LEVELS_NITS);
+        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
+        assertNull(strategy);
+
+        res = createResources(DISPLAY_LEVELS_BACKLIGHT);
+        strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
+        assertNull(strategy);
+
+        // Extra backlight level
+        final int[] backlight = Arrays.copyOf(
+                DISPLAY_LEVELS_BACKLIGHT, DISPLAY_LEVELS_BACKLIGHT.length + 1);
+        backlight[backlight.length - 1] = backlight[backlight.length - 2] + 1;
+        res = createResources(backlight);
+        ddc = createDdc(DISPLAY_RANGE_NITS,
+                DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, LUX_LEVELS, EMPTY_FLOAT_ARRAY);
+        strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
+        assertNull(strategy);
+
+        // Extra nits level
+        final float[] nits = Arrays.copyOf(DISPLAY_RANGE_NITS, DISPLAY_LEVELS_NITS.length + 1);
+        nits[nits.length - 1] = nits[nits.length - 2] + 1;
+        res = createResources(EMPTY_INT_ARRAY);
+        ddc = createDdc(DISPLAY_RANGE_NITS,
+                DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, LUX_LEVELS, nits);
+        strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
+        assertNull(strategy);
+    }
+
+    @Test
+    public void testPhysicalStrategyRequiresNitsMapping() {
+        Resources res = createResources(EMPTY_INT_ARRAY /*brightnessLevelsBacklight*/);
+        DisplayDeviceConfig ddc = createDdc(EMPTY_FLOAT_ARRAY /*nitsRange*/);
+        BrightnessMappingStrategy physical = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
+        assertNull(physical);
+
+        res = createResources(EMPTY_INT_ARRAY /*brightnessLevelsBacklight*/);
+        physical = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
+        assertNull(physical);
+
+        res = createResources(EMPTY_INT_ARRAY /*brightnessLevelsBacklight*/);
+        physical = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
+        assertNull(physical);
+    }
+
+    @Test
+    public void testStrategiesAdaptToUserDataPoint() {
+        Resources res = createResources(EMPTY_INT_ARRAY /*brightnessLevelsBacklight*/);
+        DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS, BACKLIGHT_RANGE_ZERO_TO_ONE,
+                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);
+        assertStrategyAdaptsToUserDataPoints(BrightnessMappingStrategy.create(res, ddc, mMockDwbc));
+    }
+
+    @Test
+    public void testIdleModeConfigLoadsCorrectly() {
+        Resources res = createResourcesIdle(LUX_LEVELS_IDLE, DISPLAY_LEVELS_NITS_IDLE);
+        DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS, BACKLIGHT_RANGE_ZERO_TO_ONE);
+
+        // Create an idle mode bms
+        // This will fail if it tries to fetch the wrong configuration.
+        BrightnessMappingStrategy bms = BrightnessMappingStrategy.createForIdleMode(res, ddc,
+                mMockDwbc);
+        assertNotNull("BrightnessMappingStrategy should not be null", bms);
+
+        // Ensure that the config is the one we set
+        // Ensure that the lux -> brightness -> nits path works. ()
+        for (int i = 0; i < DISPLAY_LEVELS_NITS_IDLE.length; i++) {
+            assertEquals(LUX_LEVELS_IDLE[i], bms.getDefaultConfig().getCurve().first[i], TOLERANCE);
+            assertEquals(DISPLAY_LEVELS_NITS_IDLE[i], bms.getDefaultConfig().getCurve().second[i],
+                    TOLERANCE);
+            assertEquals(bms.convertToNits(bms.getBrightness(LUX_LEVELS_IDLE[i])),
+                    DISPLAY_LEVELS_NITS_IDLE[i], TOLERANCE);
+        }
+    }
+
+    private static void assertStrategyAdaptsToUserDataPoints(BrightnessMappingStrategy strategy) {
+        // Save out all of the initial brightness data for comparison after reset.
+        float[] initialBrightnessLevels = new float[LUX_LEVELS.length];
+        for (int i = 0; i < LUX_LEVELS.length; i++) {
+            initialBrightnessLevels[i] = strategy.getBrightness(LUX_LEVELS[i]);
+        }
+
+        // Add a data point in the middle of the curve where the user has set the brightness max
+        final int idx = LUX_LEVELS.length / 2;
+        strategy.addUserDataPoint(LUX_LEVELS[idx], 1.0f);
+
+        // Then make sure that all control points after the middle lux level are also set to max...
+        for (int i = idx; i < LUX_LEVELS.length; i++) {
+            assertEquals(strategy.getBrightness(LUX_LEVELS[idx]), 1.0, 0.0001f /*tolerance*/);
+        }
+
+        // ...and that all control points before the middle lux level are strictly less than the
+        // previous one still.
+        float prevBrightness = strategy.getBrightness(LUX_LEVELS[idx]);
+        for (int i = idx - 1; i >= 0; i--) {
+            float brightness = strategy.getBrightness(LUX_LEVELS[i]);
+            assertTrue("Brightness levels must be monotonic after adapting to user data",
+                    prevBrightness >= brightness);
+            prevBrightness = brightness;
+        }
+
+        // Now reset the curve and make sure we go back to the initial brightness levels recorded
+        // before adding the user data point.
+        strategy.clearUserDataPoints();
+        for (int i = 0; i < LUX_LEVELS.length; i++) {
+            assertEquals(initialBrightnessLevels[i], strategy.getBrightness(LUX_LEVELS[i]),
+                    0.0001f /*tolerance*/);
+        }
+
+        // Now set the middle of the lux range to something just above the minimum.
+        float minBrightness = strategy.getBrightness(LUX_LEVELS[0]);
+        strategy.addUserDataPoint(LUX_LEVELS[idx], minBrightness + 0.0001f);
+
+        // Then make sure the curve is still monotonic.
+        prevBrightness = 0f;
+        for (float lux : LUX_LEVELS) {
+            float brightness = strategy.getBrightness(lux);
+            assertTrue("Brightness levels must be monotonic after adapting to user data",
+                    prevBrightness <= brightness);
+            prevBrightness = brightness;
+        }
+
+        // And that the lowest lux level still gives the absolute minimum brightness. This should
+        // be true assuming that there are more than two lux levels in the curve since we picked a
+        // brightness just barely above the minimum for the middle of the curve.
+        minBrightness = (float) MathUtils.pow(minBrightness, MAXIMUM_GAMMA); // Gamma correction.
+        assertEquals(minBrightness, strategy.getBrightness(LUX_LEVELS[0]), 0.0001f /*tolerance*/);
+    }
+
+    private Resources createResources(int[] brightnessLevelsBacklight) {
+        return createResources(brightnessLevelsBacklight, EMPTY_INT_ARRAY, EMPTY_FLOAT_ARRAY);
+    }
+
+    private Resources createResourcesIdle(int[] luxLevelsIdle, float[] brightnessLevelsNitsIdle) {
+        return createResources(EMPTY_INT_ARRAY,
+                luxLevelsIdle, brightnessLevelsNitsIdle);
+    }
+
+    private Resources createResources(int[] brightnessLevelsBacklight, int[] luxLevelsIdle,
+            float[] brightnessLevelsNitsIdle) {
+
+        Resources mockResources = mock(Resources.class);
+        if (luxLevelsIdle.length > 0) {
+            int[] luxLevelsIdleResource = Arrays.copyOfRange(luxLevelsIdle, 1,
+                    luxLevelsIdle.length);
+            when(mockResources.getIntArray(
+                    com.android.internal.R.array.config_autoBrightnessLevelsIdle))
+                    .thenReturn(luxLevelsIdleResource);
+        }
+
+        when(mockResources.getIntArray(
+                com.android.internal.R.array.config_autoBrightnessLcdBacklightValues))
+                .thenReturn(brightnessLevelsBacklight);
+
+        TypedArray mockBrightnessLevelNitsIdle = createFloatTypedArray(brightnessLevelsNitsIdle);
+        when(mockResources.obtainTypedArray(
+                com.android.internal.R.array.config_autoBrightnessDisplayValuesNitsIdle))
+                .thenReturn(mockBrightnessLevelNitsIdle);
+
+        when(mockResources.getInteger(
+                com.android.internal.R.integer.config_screenBrightnessSettingMinimum))
+                .thenReturn(1);
+        when(mockResources.getInteger(
+                com.android.internal.R.integer.config_screenBrightnessSettingMaximum))
+                .thenReturn(255);
+        when(mockResources.getFraction(
+                com.android.internal.R.fraction.config_autoBrightnessAdjustmentMaxGamma, 1, 1))
+                .thenReturn(MAXIMUM_GAMMA);
+        return mockResources;
+    }
+
+    private DisplayDeviceConfig createDdc() {
+        return createDdc(DISPLAY_RANGE_NITS);
+    }
+
+    private DisplayDeviceConfig createDdc(float[] nitsArray) {
+        return createDdc(nitsArray, DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT);
+    }
+
+    private DisplayDeviceConfig createDdc(float[] nitsArray, float[] backlightArray) {
+        DisplayDeviceConfig mockDdc = mock(DisplayDeviceConfig.class);
+        when(mockDdc.getNits()).thenReturn(nitsArray);
+        when(mockDdc.getBrightness()).thenReturn(backlightArray);
+        when(mockDdc.getAutoBrightnessBrighteningLevelsLux()).thenReturn(LUX_LEVELS);
+        when(mockDdc.getAutoBrightnessBrighteningLevelsNits()).thenReturn(EMPTY_FLOAT_ARRAY);
+        return mockDdc;
+    }
+
+    private DisplayDeviceConfig createDdc(float[] nitsArray, float[] backlightArray,
+            float[] luxLevelsFloat, float[] brightnessLevelsNits) {
+        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);
+        return mockDdc;
+    }
+
+    private TypedArray createFloatTypedArray(float[] vals) {
+        TypedArray mockArray = mock(TypedArray.class);
+        when(mockArray.length()).thenAnswer(invocation -> {
+            return vals.length;
+        });
+        when(mockArray.getFloat(anyInt(), anyFloat())).thenAnswer(invocation -> {
+            final float def = (float) invocation.getArguments()[1];
+            if (vals == null) {
+                return def;
+            }
+            int idx = (int) invocation.getArguments()[0];
+            if (idx >= 0 && idx < vals.length) {
+                return vals[idx];
+            } else {
+                return def;
+            }
+        });
+        return mockArray;
+    }
+
+    // Gamma correction tests.
+    // x0 = 100   y0 = ~0.01
+    // x1 = 1000  y1 = ~0.20
+    // x2 = 2500  y2 = ~0.50
+    // x3 = 4000  y3 = ~0.80
+    // x4 = 4900  y4 = ~0.99
+
+    @Test
+    public void testGammaCorrectionLowChangeAtCenter() {
+        // If we set a user data point at (x2, y2^0.5), i.e. gamma = 0.5, it should bump the rest
+        // of the spline accordingly.
+        final int x1 = 1000;
+        final int x2 = 2500;
+        final int x3 = 4000;
+        final float y1 = GAMMA_CORRECTION_SPLINE.interpolate(x1);
+        final float y2 = GAMMA_CORRECTION_SPLINE.interpolate(x2);
+        final float y3 = GAMMA_CORRECTION_SPLINE.interpolate(x3);
+
+        Resources resources = createResources(EMPTY_INT_ARRAY);
+        DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS,
+                DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, GAMMA_CORRECTION_LUX,
+                GAMMA_CORRECTION_NITS);
+        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(resources, ddc,
+                mMockDwbc);
+        // Let's start with a validity check:
+        assertEquals(y1, strategy.getBrightness(x1), 0.0001f /* tolerance */);
+        assertEquals(y2, strategy.getBrightness(x2), 0.0001f /* tolerance */);
+        assertEquals(y3, strategy.getBrightness(x3), 0.0001f /* tolerance */);
+        // OK, let's roll:
+        float gamma = 0.5f;
+        strategy.addUserDataPoint(x2, (float) MathUtils.pow(y2, gamma));
+        assertEquals(MathUtils.pow(y1, gamma), strategy.getBrightness(x1), 0.0001f /* tolerance */);
+        assertEquals(MathUtils.pow(y2, gamma), strategy.getBrightness(x2), 0.0001f /* tolerance */);
+        assertEquals(MathUtils.pow(y3, gamma), strategy.getBrightness(x3), 0.0001f /* tolerance */);
+        // The adjustment should be +0.6308 (manual calculation).
+        assertEquals(+0.6308f, strategy.getAutoBrightnessAdjustment(), 0.0001f /* tolerance */);
+    }
+
+    @Test
+    public void testGammaCorrectionHighChangeAtCenter() {
+        // This time we set a user data point at (x2, y2^0.25), i.e. gamma = 0.3 (the minimum),
+        // which should bump the rest of the spline accordingly, and further correct x2 to hit
+        // y2^0.25 (not y2^0.3).
+        final int x1 = 1000;
+        final int x2 = 2500;
+        final int x3 = 4000;
+        final float y1 = GAMMA_CORRECTION_SPLINE.interpolate(x1);
+        final float y2 = GAMMA_CORRECTION_SPLINE.interpolate(x2);
+        final float y3 = GAMMA_CORRECTION_SPLINE.interpolate(x3);
+        Resources resources = createResources(EMPTY_INT_ARRAY);
+        DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS,
+                DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, GAMMA_CORRECTION_LUX,
+                GAMMA_CORRECTION_NITS);
+        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(resources, ddc,
+                mMockDwbc);
+        // Validity check:
+        assertEquals(y1, strategy.getBrightness(x1), 0.0001f /* tolerance */);
+        assertEquals(y2, strategy.getBrightness(x2), 0.0001f /* tolerance */);
+        assertEquals(y3, strategy.getBrightness(x3), 0.0001f /* tolerance */);
+        // Let's roll:
+        float gamma = 0.25f;
+        final float minGamma = 1.0f / MAXIMUM_GAMMA;
+        strategy.addUserDataPoint(x2, (float) MathUtils.pow(y2, gamma));
+        assertEquals(MathUtils.pow(y1, minGamma),
+                strategy.getBrightness(x1), 0.0001f /* tolerance */);
+        assertEquals(MathUtils.pow(y2, gamma),
+                strategy.getBrightness(x2), 0.0001f /* tolerance */);
+        assertEquals(MathUtils.pow(y3, minGamma),
+                strategy.getBrightness(x3), 0.0001f /* tolerance */);
+        // The adjustment should be +1.0 (maximum adjustment).
+        assertEquals(+1.0f, strategy.getAutoBrightnessAdjustment(), 0.0001f /* tolerance */);
+    }
+
+    @Test
+    public void testGammaCorrectionExtremeChangeAtCenter() {
+        // Extreme changes (e.g. setting brightness to 0.0 or 1.0) can't be gamma corrected, so we
+        // just make sure the adjustment reflects the change.
+        Resources resources = createResources(EMPTY_INT_ARRAY);
+        DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS,
+                DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, GAMMA_CORRECTION_LUX,
+                GAMMA_CORRECTION_NITS);
+        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(resources, ddc,
+                mMockDwbc);
+        assertEquals(0.0f, strategy.getAutoBrightnessAdjustment(), 0.0001f /* tolerance */);
+        strategy.addUserDataPoint(2500, 1.0f);
+        assertEquals(+1.0f, strategy.getAutoBrightnessAdjustment(), 0.0001f /* tolerance */);
+        strategy.addUserDataPoint(2500, 0.0f);
+        assertEquals(-1.0f, strategy.getAutoBrightnessAdjustment(), 0.0001f /* tolerance */);
+    }
+
+    @Test
+    public void testGammaCorrectionChangeAtEdges() {
+        // The algorithm behaves differently at the edges, because gamma correction there tends to
+        // be extreme. If we add a user data point at (x0, y0+0.3), the adjustment should be 0.3,
+        // resulting in a gamma of 3**-0.6 = ~0.52.
+        final int x0 = 100;
+        final int x2 = 2500;
+        final int x4 = 4900;
+        final float y0 = GAMMA_CORRECTION_SPLINE.interpolate(x0);
+        final float y2 = GAMMA_CORRECTION_SPLINE.interpolate(x2);
+        final float y4 = GAMMA_CORRECTION_SPLINE.interpolate(x4);
+        Resources resources = createResources(EMPTY_INT_ARRAY);
+        DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS,
+                DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, GAMMA_CORRECTION_LUX,
+                GAMMA_CORRECTION_NITS);
+        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(resources, ddc,
+                mMockDwbc);
+        // Validity, as per tradition:
+        assertEquals(y0, strategy.getBrightness(x0), 0.0001f /* tolerance */);
+        assertEquals(y2, strategy.getBrightness(x2), 0.0001f /* tolerance */);
+        assertEquals(y4, strategy.getBrightness(x4), 0.0001f /* tolerance */);
+        // Rollin':
+        float adjustment = 0.3f;
+        float gamma = (float) MathUtils.pow(MAXIMUM_GAMMA, -adjustment);
+        strategy.addUserDataPoint(x0, y0 + adjustment);
+        assertEquals(y0 + adjustment, strategy.getBrightness(x0), 0.0001f /* tolerance */);
+        assertEquals(MathUtils.pow(y2, gamma), strategy.getBrightness(x2), 0.0001f /* tolerance */);
+        assertEquals(MathUtils.pow(y4, gamma), strategy.getBrightness(x4), 0.0001f /* tolerance */);
+        assertEquals(adjustment, strategy.getAutoBrightnessAdjustment(), 0.0001f /* tolerance */);
+        // Similarly, if we set a user data point at (x4, 1.0), the adjustment should be 1 - y4.
+        adjustment = 1.0f - y4;
+        gamma = (float) MathUtils.pow(MAXIMUM_GAMMA, -adjustment);
+        strategy.addUserDataPoint(x4, 1.0f);
+        assertEquals(MathUtils.pow(y0, gamma), strategy.getBrightness(x0), 0.0001f /* tolerance */);
+        assertEquals(MathUtils.pow(y2, gamma), strategy.getBrightness(x2), 0.0001f /* tolerance */);
+        assertEquals(1.0f, strategy.getBrightness(x4), 0.0001f /* tolerance */);
+        assertEquals(adjustment, strategy.getAutoBrightnessAdjustment(), 0.0001f /* tolerance */);
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/BrightnessSynchronizerTest.java b/services/tests/displayservicetests/src/com/android/server/display/BrightnessSynchronizerTest.java
similarity index 100%
rename from services/tests/mockingservicestests/src/com/android/server/display/BrightnessSynchronizerTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/BrightnessSynchronizerTest.java
diff --git a/services/tests/displayservicetests/src/com/android/server/display/BrightnessThrottlerTest.java b/services/tests/displayservicetests/src/com/android/server/display/BrightnessThrottlerTest.java
new file mode 100644
index 0000000..8faaf59
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/BrightnessThrottlerTest.java
@@ -0,0 +1,493 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.display.BrightnessInfo;
+import android.os.Handler;
+import android.os.IThermalEventListener;
+import android.os.IThermalService;
+import android.os.Message;
+import android.os.PowerManager;
+import android.os.Temperature;
+import android.os.Temperature.ThrottlingStatus;
+import android.os.test.TestLooper;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.os.BackgroundThread;
+import com.android.server.display.BrightnessThrottler.Injector;
+import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData;
+import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel;
+import com.android.server.display.mode.DisplayModeDirectorTest;
+
+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.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class BrightnessThrottlerTest {
+    private static final float EPSILON = 0.000001f;
+
+    private Handler mHandler;
+    private TestLooper mTestLooper;
+
+    @Mock IThermalService mThermalServiceMock;
+    @Mock Injector mInjectorMock;
+
+    DisplayModeDirectorTest.FakeDeviceConfig mDeviceConfigFake;
+
+    @Captor ArgumentCaptor<IThermalEventListener> mThermalEventListenerCaptor;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        when(mInjectorMock.getThermalService()).thenReturn(mThermalServiceMock);
+        mTestLooper = new TestLooper();
+        mHandler = new Handler(mTestLooper.getLooper(), new Handler.Callback() {
+            @Override
+            public boolean handleMessage(Message msg) {
+                return true;
+            }
+        });
+        mDeviceConfigFake = new DisplayModeDirectorTest.FakeDeviceConfig();
+        when(mInjectorMock.getDeviceConfig()).thenReturn(mDeviceConfigFake);
+
+    }
+
+    /////////////////
+    // Test Methods
+    /////////////////
+
+    @Test
+    public void testThermalBrightnessThrottlingData() {
+        List<ThrottlingLevel> singleLevel = new ArrayList<>();
+        singleLevel.add(new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 0.25f));
+
+        List<ThrottlingLevel> validLevels = new ArrayList<>();
+        validLevels.add(new ThrottlingLevel(PowerManager.THERMAL_STATUS_MODERATE, 0.62f));
+        validLevels.add(new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 0.25f));
+
+        List<ThrottlingLevel> unsortedThermalLevels = new ArrayList<>();
+        unsortedThermalLevels.add(new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 0.62f));
+        unsortedThermalLevels.add(new ThrottlingLevel(PowerManager.THERMAL_STATUS_MODERATE, 0.25f));
+
+        List<ThrottlingLevel> unsortedBrightnessLevels = new ArrayList<>();
+        unsortedBrightnessLevels.add(
+                new ThrottlingLevel(PowerManager.THERMAL_STATUS_MODERATE, 0.25f));
+        unsortedBrightnessLevels.add(
+                new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 0.62f));
+
+        List<ThrottlingLevel> unsortedLevels = new ArrayList<>();
+        unsortedLevels.add(new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 0.25f));
+        unsortedLevels.add(new ThrottlingLevel(PowerManager.THERMAL_STATUS_MODERATE, 0.62f));
+
+        List<ThrottlingLevel> invalidLevel = new ArrayList<>();
+        invalidLevel.add(new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL,
+                PowerManager.BRIGHTNESS_MAX + EPSILON));
+
+        // Test invalid data
+        ThermalBrightnessThrottlingData data;
+        data = ThermalBrightnessThrottlingData.create((List<ThrottlingLevel>) null);
+        assertEquals(data, null);
+        data = ThermalBrightnessThrottlingData.create(new ArrayList<ThrottlingLevel>());
+        assertEquals(data, null);
+        data = ThermalBrightnessThrottlingData.create(unsortedThermalLevels);
+        assertEquals(data, null);
+        data = ThermalBrightnessThrottlingData.create(unsortedBrightnessLevels);
+        assertEquals(data, null);
+        data = ThermalBrightnessThrottlingData.create(unsortedLevels);
+        assertEquals(data, null);
+        data = ThermalBrightnessThrottlingData.create(invalidLevel);
+        assertEquals(data, null);
+
+        // Test valid data
+        data = ThermalBrightnessThrottlingData.create(singleLevel);
+        assertNotEquals(data, null);
+        assertThrottlingLevelsEquals(singleLevel, data.throttlingLevels);
+
+        data = ThermalBrightnessThrottlingData.create(validLevels);
+        assertNotEquals(data, null);
+        assertThrottlingLevelsEquals(validLevels, data.throttlingLevels);
+    }
+
+    @Test
+    public void testThermalThrottlingUnsupported() {
+        final BrightnessThrottler throttler = createThrottlerUnsupported();
+        assertFalse(throttler.deviceSupportsThrottling());
+
+        // Thermal listener shouldn't be registered if throttling is unsupported
+        verify(mInjectorMock, never()).getThermalService();
+
+        // Ensure that brightness is uncapped when the device doesn't support throttling
+        assertEquals(PowerManager.BRIGHTNESS_MAX, throttler.getBrightnessCap(), 0f);
+    }
+
+    @Test
+    public void testThermalThrottlingSingleLevel() throws Exception {
+        final ThrottlingLevel level = new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL,
+                0.25f);
+
+        List<ThrottlingLevel> levels = new ArrayList<>();
+        levels.add(level);
+        final ThermalBrightnessThrottlingData data = ThermalBrightnessThrottlingData.create(levels);
+        final BrightnessThrottler throttler = createThrottlerSupported(data);
+        assertTrue(throttler.deviceSupportsThrottling());
+
+        verify(mThermalServiceMock).registerThermalEventListenerWithType(
+                mThermalEventListenerCaptor.capture(), eq(Temperature.TYPE_SKIN));
+        final IThermalEventListener listener = mThermalEventListenerCaptor.getValue();
+
+        // Set status too low to trigger throttling
+        listener.notifyThrottling(getSkinTemp(level.thermalStatus - 1));
+        mTestLooper.dispatchAll();
+        assertEquals(PowerManager.BRIGHTNESS_MAX, throttler.getBrightnessCap(), 0f);
+        assertFalse(throttler.isThrottled());
+        assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE, throttler.getBrightnessMaxReason());
+
+        // Set status just high enough to trigger throttling
+        listener.notifyThrottling(getSkinTemp(level.thermalStatus));
+        mTestLooper.dispatchAll();
+        assertEquals(level.brightness, throttler.getBrightnessCap(), 0f);
+        assertTrue(throttler.isThrottled());
+        assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL,
+                throttler.getBrightnessMaxReason());
+
+        // Set status more than high enough to trigger throttling
+        listener.notifyThrottling(getSkinTemp(level.thermalStatus + 1));
+        mTestLooper.dispatchAll();
+        assertEquals(level.brightness, throttler.getBrightnessCap(), 0f);
+        assertTrue(throttler.isThrottled());
+        assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL,
+                throttler.getBrightnessMaxReason());
+
+        // Return to the lower throttling level
+        listener.notifyThrottling(getSkinTemp(level.thermalStatus));
+        mTestLooper.dispatchAll();
+        assertEquals(level.brightness, throttler.getBrightnessCap(), 0f);
+        assertTrue(throttler.isThrottled());
+        assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL,
+                throttler.getBrightnessMaxReason());
+
+        // Cool down
+        listener.notifyThrottling(getSkinTemp(level.thermalStatus - 1));
+        mTestLooper.dispatchAll();
+        assertEquals(PowerManager.BRIGHTNESS_MAX, throttler.getBrightnessCap(), 0f);
+        assertFalse(throttler.isThrottled());
+        assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE,
+                throttler.getBrightnessMaxReason());
+    }
+
+    @Test
+    public void testThermalThrottlingMultiLevel() throws Exception {
+        final ThrottlingLevel levelLo = new ThrottlingLevel(PowerManager.THERMAL_STATUS_MODERATE,
+                0.62f);
+        final ThrottlingLevel levelHi = new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL,
+                0.25f);
+
+        List<ThrottlingLevel> levels = new ArrayList<>();
+        levels.add(levelLo);
+        levels.add(levelHi);
+        final ThermalBrightnessThrottlingData data = ThermalBrightnessThrottlingData.create(levels);
+        final BrightnessThrottler throttler = createThrottlerSupported(data);
+        assertTrue(throttler.deviceSupportsThrottling());
+
+        verify(mThermalServiceMock).registerThermalEventListenerWithType(
+                mThermalEventListenerCaptor.capture(), eq(Temperature.TYPE_SKIN));
+        final IThermalEventListener listener = mThermalEventListenerCaptor.getValue();
+
+        // Set status too low to trigger throttling
+        listener.notifyThrottling(getSkinTemp(levelLo.thermalStatus - 1));
+        mTestLooper.dispatchAll();
+        assertEquals(PowerManager.BRIGHTNESS_MAX, throttler.getBrightnessCap(), 0f);
+        assertFalse(throttler.isThrottled());
+        assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE, throttler.getBrightnessMaxReason());
+
+        // Set status just high enough to trigger throttling
+        listener.notifyThrottling(getSkinTemp(levelLo.thermalStatus));
+        mTestLooper.dispatchAll();
+        assertEquals(levelLo.brightness, throttler.getBrightnessCap(), 0f);
+        assertTrue(throttler.isThrottled());
+        assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL,
+                throttler.getBrightnessMaxReason());
+
+        // Set status to an intermediate throttling level
+        listener.notifyThrottling(getSkinTemp(levelLo.thermalStatus + 1));
+        mTestLooper.dispatchAll();
+        assertEquals(levelLo.brightness, throttler.getBrightnessCap(), 0f);
+        assertTrue(throttler.isThrottled());
+        assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL,
+                throttler.getBrightnessMaxReason());
+
+        // Set status to the highest configured throttling level
+        listener.notifyThrottling(getSkinTemp(levelHi.thermalStatus));
+        mTestLooper.dispatchAll();
+        assertEquals(levelHi.brightness, throttler.getBrightnessCap(), 0f);
+        assertTrue(throttler.isThrottled());
+        assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL,
+                throttler.getBrightnessMaxReason());
+
+        // Set status to exceed the highest configured throttling level
+        listener.notifyThrottling(getSkinTemp(levelHi.thermalStatus + 1));
+        mTestLooper.dispatchAll();
+        assertEquals(levelHi.brightness, throttler.getBrightnessCap(), 0f);
+        assertTrue(throttler.isThrottled());
+        assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL,
+                throttler.getBrightnessMaxReason());
+
+        // Return to an intermediate throttling level
+        listener.notifyThrottling(getSkinTemp(levelLo.thermalStatus + 1));
+        mTestLooper.dispatchAll();
+        assertEquals(levelLo.brightness, throttler.getBrightnessCap(), 0f);
+        assertTrue(throttler.isThrottled());
+        assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL,
+                throttler.getBrightnessMaxReason());
+
+        // Return to the lowest configured throttling level
+        listener.notifyThrottling(getSkinTemp(levelLo.thermalStatus));
+        mTestLooper.dispatchAll();
+        assertEquals(levelLo.brightness, throttler.getBrightnessCap(), 0f);
+        assertTrue(throttler.isThrottled());
+        assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL,
+                throttler.getBrightnessMaxReason());
+
+        // Cool down
+        listener.notifyThrottling(getSkinTemp(levelLo.thermalStatus - 1));
+        mTestLooper.dispatchAll();
+        assertEquals(PowerManager.BRIGHTNESS_MAX, throttler.getBrightnessCap(), 0f);
+        assertFalse(throttler.isThrottled());
+        assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE, throttler.getBrightnessMaxReason());
+    }
+
+    @Test public void testUpdateThermalThrottlingData() throws Exception {
+        // Initialise brightness throttling levels
+        // Ensure that they are overridden by setting the data through device config.
+        final ThrottlingLevel level = new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL,
+                0.25f);
+        List<ThrottlingLevel> levels = new ArrayList<>();
+        levels.add(level);
+        final ThermalBrightnessThrottlingData data = ThermalBrightnessThrottlingData.create(levels);
+        mDeviceConfigFake.setThermalBrightnessThrottlingData("123,1,critical,0.4");
+        final BrightnessThrottler throttler = createThrottlerSupported(data);
+
+        verify(mThermalServiceMock).registerThermalEventListenerWithType(
+                mThermalEventListenerCaptor.capture(), eq(Temperature.TYPE_SKIN));
+        final IThermalEventListener listener = mThermalEventListenerCaptor.getValue();
+        testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.4f);
+
+        // Set new (valid) data from device config
+        mDeviceConfigFake.setThermalBrightnessThrottlingData("123,1,critical,0.8");
+        testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.8f);
+
+        mDeviceConfigFake.setThermalBrightnessThrottlingData(
+                "123,1,critical,0.75;123,1,critical,0.99,id_2");
+        testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.75f);
+        mDeviceConfigFake.setThermalBrightnessThrottlingData(
+                "123,1,critical,0.8,default;123,1,critical,0.99,id_2");
+        testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.8f);
+    }
+
+    @Test public void testInvalidThrottlingStrings() throws Exception {
+        // Initialise brightness throttling levels
+        // Ensure that they are not overridden by invalid data through device config.
+        final ThrottlingLevel level = new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL,
+                0.25f);
+        List<ThrottlingLevel> levels = new ArrayList<>();
+        levels.add(level);
+        final ThermalBrightnessThrottlingData data = ThermalBrightnessThrottlingData.create(levels);
+        final BrightnessThrottler throttler = createThrottlerSupported(data);
+        verify(mThermalServiceMock).registerThermalEventListenerWithType(
+                mThermalEventListenerCaptor.capture(), eq(Temperature.TYPE_SKIN));
+        final IThermalEventListener listener = mThermalEventListenerCaptor.getValue();
+
+        // None of these are valid so shouldn't override the original data
+
+        // Not the current id
+        mDeviceConfigFake.setThermalBrightnessThrottlingData("321,1,critical,0.4");
+        testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
+        // Incorrect number
+        mDeviceConfigFake.setThermalBrightnessThrottlingData("123,0,critical,0.4");
+        testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
+        // Incorrect number
+        mDeviceConfigFake.setThermalBrightnessThrottlingData("123,2,critical,0.4");
+        testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
+        // Invalid level
+        mDeviceConfigFake.setThermalBrightnessThrottlingData("123,1,invalid,0.4");
+        testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
+        // Invalid brightness
+        mDeviceConfigFake.setThermalBrightnessThrottlingData("123,1,critical,none");
+        testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
+        // Invalid brightness
+        mDeviceConfigFake.setThermalBrightnessThrottlingData("123,1,critical,-3");
+        testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
+        // Invalid format
+        mDeviceConfigFake.setThermalBrightnessThrottlingData("invalid string");
+        testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
+        // Invalid format
+        mDeviceConfigFake.setThermalBrightnessThrottlingData("");
+        testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
+        // Invalid string format
+        mDeviceConfigFake.setThermalBrightnessThrottlingData(
+                "123,default,1,critical,0.75,1,critical,0.99");
+        testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
+        // Invalid level string and number string
+        mDeviceConfigFake.setThermalBrightnessThrottlingData(
+                "123,1,1,critical,0.75,id_2,1,critical,0.99");
+        testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
+        // Invalid format - (two default ids for same display)
+        mDeviceConfigFake.setThermalBrightnessThrottlingData(
+                "123,1,critical,0.75,default;123,1,critical,0.99");
+        testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
+    }
+
+    private void testThermalThrottling(BrightnessThrottler throttler,
+            IThermalEventListener listener, float tooLowCap, float tooHighCap) throws Exception {
+        final ThrottlingLevel level = new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL,
+                tooHighCap);
+
+        // Set status too low to trigger throttling
+        listener.notifyThrottling(getSkinTemp(level.thermalStatus - 1));
+        mTestLooper.dispatchAll();
+        assertEquals(tooLowCap, throttler.getBrightnessCap(), 0f);
+        assertFalse(throttler.isThrottled());
+
+        // Set status high enough to trigger throttling
+        listener.notifyThrottling(getSkinTemp(level.thermalStatus));
+        mTestLooper.dispatchAll();
+        assertEquals(tooHighCap, throttler.getBrightnessCap(), 0f);
+        assertTrue(throttler.isThrottled());
+    }
+
+    @Test public void testMultipleConfigPoints() throws Exception {
+        // Initialise brightness throttling levels
+        final ThrottlingLevel level = new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL,
+                0.25f);
+        List<ThrottlingLevel> levels = new ArrayList<>();
+        levels.add(level);
+        final ThermalBrightnessThrottlingData data = ThermalBrightnessThrottlingData.create(levels);
+
+        // These are identical to the string set below
+        final ThrottlingLevel levelSevere = new ThrottlingLevel(PowerManager.THERMAL_STATUS_SEVERE,
+                0.9f);
+        final ThrottlingLevel levelCritical = new ThrottlingLevel(
+                PowerManager.THERMAL_STATUS_CRITICAL, 0.5f);
+        final ThrottlingLevel levelEmergency = new ThrottlingLevel(
+                PowerManager.THERMAL_STATUS_EMERGENCY, 0.1f);
+
+        mDeviceConfigFake.setThermalBrightnessThrottlingData(
+                "123,3,severe,0.9,critical,0.5,emergency,0.1");
+        final BrightnessThrottler throttler = createThrottlerSupported(data);
+
+        verify(mThermalServiceMock).registerThermalEventListenerWithType(
+                mThermalEventListenerCaptor.capture(), eq(Temperature.TYPE_SKIN));
+        final IThermalEventListener listener = mThermalEventListenerCaptor.getValue();
+
+        // Ensure that the multiple levels set via the string through the device config correctly
+        // override the original display device config ones.
+
+        // levelSevere
+        // Set status too low to trigger throttling
+        listener.notifyThrottling(getSkinTemp(levelSevere.thermalStatus - 1));
+        mTestLooper.dispatchAll();
+        assertEquals(PowerManager.BRIGHTNESS_MAX, throttler.getBrightnessCap(), 0f);
+        assertFalse(throttler.isThrottled());
+
+        // Set status high enough to trigger throttling
+        listener.notifyThrottling(getSkinTemp(levelSevere.thermalStatus));
+        mTestLooper.dispatchAll();
+        assertEquals(0.9f, throttler.getBrightnessCap(), 0f);
+        assertTrue(throttler.isThrottled());
+
+        // levelCritical
+        // Set status too low to trigger throttling
+        listener.notifyThrottling(getSkinTemp(levelCritical.thermalStatus - 1));
+        mTestLooper.dispatchAll();
+        assertEquals(0.9f, throttler.getBrightnessCap(), 0f);
+        assertTrue(throttler.isThrottled());
+
+        // Set status high enough to trigger throttling
+        listener.notifyThrottling(getSkinTemp(levelCritical.thermalStatus));
+        mTestLooper.dispatchAll();
+        assertEquals(0.5f, throttler.getBrightnessCap(), 0f);
+        assertTrue(throttler.isThrottled());
+
+        //levelEmergency
+        // Set status too low to trigger throttling
+        listener.notifyThrottling(getSkinTemp(levelEmergency.thermalStatus - 1));
+        mTestLooper.dispatchAll();
+        assertEquals(0.5f, throttler.getBrightnessCap(), 0f);
+        assertTrue(throttler.isThrottled());
+
+        // Set status high enough to trigger throttling
+        listener.notifyThrottling(getSkinTemp(levelEmergency.thermalStatus));
+        mTestLooper.dispatchAll();
+        assertEquals(0.1f, throttler.getBrightnessCap(), 0f);
+        assertTrue(throttler.isThrottled());
+    }
+
+    private void assertThrottlingLevelsEquals(
+            List<ThrottlingLevel> expected,
+            List<ThrottlingLevel> actual) {
+        assertEquals(expected.size(), actual.size());
+
+        for (int i = 0; i < expected.size(); i++) {
+            ThrottlingLevel expectedLevel = expected.get(i);
+            ThrottlingLevel actualLevel = actual.get(i);
+
+            assertEquals(expectedLevel.thermalStatus, actualLevel.thermalStatus);
+            assertEquals(expectedLevel.brightness, actualLevel.brightness, 0.0f);
+        }
+    }
+
+    private BrightnessThrottler createThrottlerUnsupported() {
+        return new BrightnessThrottler(mInjectorMock, mHandler, mHandler,
+                /* throttlingChangeCallback= */ () -> {}, /* uniqueDisplayId= */ null,
+                /* thermalThrottlingDataId= */ null,
+                /* thermalThrottlingDataMap= */ new HashMap<>(1));
+    }
+
+    private BrightnessThrottler createThrottlerSupported(ThermalBrightnessThrottlingData data) {
+        assertNotNull(data);
+        HashMap<String, ThermalBrightnessThrottlingData> throttlingDataMap = new HashMap<>(1);
+        throttlingDataMap.put("default", data);
+        return new BrightnessThrottler(mInjectorMock, mHandler, BackgroundThread.getHandler(),
+                () -> {}, "123", "default", throttlingDataMap);
+    }
+
+    private Temperature getSkinTemp(@ThrottlingStatus int status) {
+        return new Temperature(30.0f, Temperature.TYPE_SKIN, "test_skin_temp", status);
+    }
+}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/BrightnessTrackerTest.java b/services/tests/displayservicetests/src/com/android/server/display/BrightnessTrackerTest.java
new file mode 100644
index 0000000..44c7dec
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/BrightnessTrackerTest.java
@@ -0,0 +1,1247 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display;
+
+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.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.app.ActivityTaskManager.RootTaskInfo;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ParceledListSlice;
+import android.database.ContentObserver;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.display.AmbientBrightnessDayStats;
+import android.hardware.display.BrightnessChangeEvent;
+import android.hardware.display.BrightnessConfiguration;
+import android.hardware.display.ColorDisplayManager;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayedContentSample;
+import android.hardware.display.DisplayedContentSamplingAttributes;
+import android.hardware.input.InputSensorInfo;
+import android.os.BatteryManager;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.MessageQueue;
+import android.os.Parcel;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.UserManager;
+import android.provider.Settings;
+import android.util.AtomicFile;
+import android.view.Display;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.R;
+
+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;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.Constructor;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class BrightnessTrackerTest {
+    private static final float DEFAULT_INITIAL_BRIGHTNESS = 2.5f;
+    private static final boolean DEFAULT_COLOR_SAMPLING_ENABLED = true;
+    private static final String DEFAULT_DISPLAY_ID = "123";
+    private static final float FLOAT_DELTA = 0.01f;
+
+    @Mock private InputSensorInfo mInputSensorInfoMock;
+
+    private BrightnessTracker mTracker;
+    private TestInjector mInjector;
+    private Sensor mLightSensorFake;
+
+    private static Object sHandlerLock = new Object();
+    private static Handler sHandler;
+    private static HandlerThread sThread =
+            new HandlerThread("brightness.test", android.os.Process.THREAD_PRIORITY_BACKGROUND);
+
+    private int mDefaultNightModeColorTemperature;
+    private float mRbcOffsetFactor;
+
+    private static Handler ensureHandler() {
+        synchronized (sHandlerLock) {
+            if (sHandler == null) {
+                sThread.start();
+                sHandler = new Handler(sThread.getLooper());
+            }
+            return sHandler;
+        }
+    }
+
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mInjector = new TestInjector(ensureHandler());
+        mLightSensorFake = new Sensor(mInputSensorInfoMock);
+
+        mTracker = new BrightnessTracker(InstrumentationRegistry.getContext(), mInjector);
+        mTracker.setLightSensor(mLightSensorFake);
+        mDefaultNightModeColorTemperature =
+                InstrumentationRegistry.getContext().getResources().getInteger(
+                R.integer.config_nightDisplayColorTemperatureDefault);
+        mRbcOffsetFactor = InstrumentationRegistry.getContext()
+                .getSystemService(ColorDisplayManager.class).getReduceBrightColorsOffsetFactor();
+    }
+
+    @Test
+    public void testStartStopTrackerScreenOnOff() {
+        mInjector.mInteractive = false;
+        startTracker(mTracker);
+        assertNull(mInjector.mSensorListener);
+        assertNotNull(mInjector.mBroadcastReceiver);
+        assertTrue(mInjector.mIdleScheduled);
+        mInjector.sendScreenChange(/* screenOn= */ true);
+        assertNotNull(mInjector.mSensorListener);
+        assertTrue(mInjector.mColorSamplingEnabled);
+
+        mInjector.sendScreenChange(/* screenOn= */ false);
+        assertNull(mInjector.mSensorListener);
+        assertFalse(mInjector.mColorSamplingEnabled);
+
+        // Turn screen on while brightness mode is manual
+        mInjector.setBrightnessMode(/* isBrightnessModeAutomatic= */ false);
+        mInjector.sendScreenChange(/* screenOn= */ true);
+        assertNull(mInjector.mSensorListener);
+        assertFalse(mInjector.mColorSamplingEnabled);
+
+        // Set brightness mode to automatic while screen is off.
+        mInjector.sendScreenChange(/* screenOn= */ false);
+        mInjector.setBrightnessMode(/* isBrightnessModeAutomatic= */ true);
+        assertNull(mInjector.mSensorListener);
+        assertFalse(mInjector.mColorSamplingEnabled);
+
+        // Turn on screen while brightness mode is automatic.
+        mInjector.sendScreenChange(/* screenOn= */ true);
+        assertNotNull(mInjector.mSensorListener);
+        assertTrue(mInjector.mColorSamplingEnabled);
+
+        mTracker.stop();
+        assertNull(mInjector.mSensorListener);
+        assertNull(mInjector.mBroadcastReceiver);
+        assertFalse(mInjector.mIdleScheduled);
+        assertFalse(mInjector.mColorSamplingEnabled);
+    }
+
+    @Test
+    public void testModifyBrightnessConfiguration() {
+        mInjector.mInteractive = true;
+        // Start with tracker not listening for color samples.
+        startTracker(mTracker, DEFAULT_INITIAL_BRIGHTNESS, /* collectColorSamples= */ false);
+        assertFalse(mInjector.mColorSamplingEnabled);
+
+        // Update brightness config to enabled color sampling.
+        mTracker.setShouldCollectColorSample(/* collectColorSamples= */ true);
+        mInjector.waitForHandler();
+        assertTrue(mInjector.mColorSamplingEnabled);
+
+        // Update brightness config to disable color sampling.
+        mTracker.setShouldCollectColorSample(/* collectColorSamples= */ false);
+        mInjector.waitForHandler();
+        assertFalse(mInjector.mColorSamplingEnabled);
+
+        // Pretend screen is off, update config to turn on color sampling.
+        mInjector.sendScreenChange(/* screenOn= */ false);
+        mTracker.setShouldCollectColorSample(/* collectColorSamples= */ true);
+        mInjector.waitForHandler();
+        assertFalse(mInjector.mColorSamplingEnabled);
+
+        // Pretend screen is on.
+        mInjector.sendScreenChange(/* screenOn= */ true);
+        assertTrue(mInjector.mColorSamplingEnabled);
+
+        mTracker.stop();
+        assertFalse(mInjector.mColorSamplingEnabled);
+    }
+
+    @Test
+    public void testNoColorSampling_WrongPixelFormat() {
+        mInjector.mDefaultSamplingAttributes =
+                new DisplayedContentSamplingAttributes(
+                        0x23,
+                        mInjector.mDefaultSamplingAttributes.getDataspace(),
+                        mInjector.mDefaultSamplingAttributes.getComponentMask());
+        startTracker(mTracker);
+        assertFalse(mInjector.mColorSamplingEnabled);
+        assertNull(mInjector.mDisplayListener);
+    }
+
+    @Test
+    public void testNoColorSampling_MissingComponent() {
+        mInjector.mDefaultSamplingAttributes =
+                new DisplayedContentSamplingAttributes(
+                        mInjector.mDefaultSamplingAttributes.getPixelFormat(),
+                        mInjector.mDefaultSamplingAttributes.getDataspace(),
+                        0x2);
+        startTracker(mTracker);
+        assertFalse(mInjector.mColorSamplingEnabled);
+        assertNull(mInjector.mDisplayListener);
+    }
+
+    @Test
+    public void testNoColorSampling_NoSupport() {
+        mInjector.mDefaultSamplingAttributes = null;
+        startTracker(mTracker);
+        assertFalse(mInjector.mColorSamplingEnabled);
+        assertNull(mInjector.mDisplayListener);
+    }
+
+    @Test
+    public void testColorSampling_FrameRateChange() {
+        startTracker(mTracker);
+        assertTrue(mInjector.mColorSamplingEnabled);
+        assertNotNull(mInjector.mDisplayListener);
+        int noFramesSampled = mInjector.mNoColorSamplingFrames;
+        mInjector.mFrameRate = 120.0f;
+        // Wrong display
+        mInjector.mDisplayListener.onDisplayChanged(Display.DEFAULT_DISPLAY + 10);
+        assertEquals(noFramesSampled, mInjector.mNoColorSamplingFrames);
+        // Correct display
+        mInjector.mDisplayListener.onDisplayChanged(Display.DEFAULT_DISPLAY);
+        assertEquals(noFramesSampled * 2, mInjector.mNoColorSamplingFrames);
+    }
+
+    @Test
+    public void testAdaptiveOnOff() {
+        mInjector.mInteractive = true;
+        mInjector.mIsBrightnessModeAutomatic = false;
+        startTracker(mTracker);
+        assertNull(mInjector.mSensorListener);
+        assertNotNull(mInjector.mBroadcastReceiver);
+        assertNotNull(mInjector.mContentObserver);
+        assertTrue(mInjector.mIdleScheduled);
+        assertFalse(mInjector.mColorSamplingEnabled);
+        assertNull(mInjector.mDisplayListener);
+
+        mInjector.setBrightnessMode(/* isBrightnessModeAutomatic= */ true);
+        assertNotNull(mInjector.mSensorListener);
+        assertTrue(mInjector.mColorSamplingEnabled);
+        assertNotNull(mInjector.mDisplayListener);
+
+        SensorEventListener listener = mInjector.mSensorListener;
+        DisplayManager.DisplayListener displayListener = mInjector.mDisplayListener;
+        mInjector.mSensorListener = null;
+        mInjector.mColorSamplingEnabled = false;
+        mInjector.mDisplayListener = null;
+        // Duplicate notification
+        mInjector.setBrightnessMode(/* isBrightnessModeAutomatic= */ true);
+        // Sensor shouldn't have been registered as it was already registered.
+        assertNull(mInjector.mSensorListener);
+        assertFalse(mInjector.mColorSamplingEnabled);
+        assertNull(mInjector.mDisplayListener);
+        mInjector.mDisplayListener = displayListener;
+        mInjector.mColorSamplingEnabled = true;
+
+        mInjector.setBrightnessMode(/* isBrightnessModeAutomatic= */ false);
+        assertNull(mInjector.mSensorListener);
+        assertFalse(mInjector.mColorSamplingEnabled);
+        assertNull(mInjector.mDisplayListener);
+
+        mTracker.stop();
+        assertNull(mInjector.mSensorListener);
+        assertNull(mInjector.mBroadcastReceiver);
+        assertNull(mInjector.mContentObserver);
+        assertFalse(mInjector.mIdleScheduled);
+        assertFalse(mInjector.mColorSamplingEnabled);
+        assertNull(mInjector.mDisplayListener);
+    }
+
+    @Test
+    public void testBrightnessEvent() {
+        final float brightness = 0.5f;
+        final String displayId = "1234";
+
+        startTracker(mTracker);
+        final long sensorTime = TimeUnit.NANOSECONDS.toMillis(mInjector.elapsedRealtimeNanos());
+        mInjector.incrementTime(TimeUnit.SECONDS.toMillis(2));
+        final long currentTime = mInjector.currentTimeMillis();
+        notifyBrightnessChanged(mTracker, brightness, displayId, new float[] {1.0f},
+                new long[] {sensorTime});
+        List<BrightnessChangeEvent> events = mTracker.getEvents(0, true).getList();
+        mTracker.stop();
+
+        assertEquals(1, events.size());
+        BrightnessChangeEvent event = events.get(0);
+        assertEquals(currentTime, event.timeStamp);
+        assertEquals(displayId, event.uniqueDisplayId);
+        assertEquals(1, event.luxValues.length);
+        assertEquals(1.0f, event.luxValues[0], FLOAT_DELTA);
+        assertEquals(currentTime - TimeUnit.SECONDS.toMillis(2),
+                event.luxTimestamps[0]);
+        assertEquals(brightness, event.brightness, FLOAT_DELTA);
+        assertEquals(DEFAULT_INITIAL_BRIGHTNESS, event.lastBrightness, FLOAT_DELTA);
+
+        // System had no data so these should all be at defaults.
+        assertEquals(Float.NaN, event.batteryLevel, 0.0);
+        assertFalse(event.nightMode);
+        assertEquals(mDefaultNightModeColorTemperature, event.colorTemperature);
+    }
+
+    @Test
+    public void testMultipleBrightnessEvents() {
+        final float brightnessOne = 0.2f;
+        final float brightnessTwo = 0.4f;
+        final float brightnessThree = 0.6f;
+        final float brightnessFour = 0.3f;
+        final String displayId = "1234";
+        final float[] luxValues = new float[]{1.0f};
+
+        startTracker(mTracker);
+        final long sensorTime = TimeUnit.NANOSECONDS.toMillis(mInjector.elapsedRealtimeNanos());
+        final long sensorTime2 = sensorTime + TimeUnit.SECONDS.toMillis(20);
+        final long sensorTime3 = sensorTime2 + TimeUnit.SECONDS.toMillis(30);
+        final long sensorTime4 = sensorTime3 + TimeUnit.SECONDS.toMillis(40);
+        final long originalTime = mInjector.currentTimeMillis();
+
+        mInjector.incrementTime(TimeUnit.SECONDS.toMillis(2));
+        notifyBrightnessChanged(mTracker, brightnessOne, displayId, luxValues,
+                new long[] {sensorTime});
+
+        mInjector.incrementTime(TimeUnit.SECONDS.toMillis(20));
+        notifyBrightnessChanged(mTracker, brightnessTwo, displayId, luxValues,
+                new long[] {sensorTime2});
+
+        mInjector.incrementTime(TimeUnit.SECONDS.toMillis(30));
+        notifyBrightnessChanged(mTracker, brightnessThree, displayId, luxValues,
+                new long[] {sensorTime3});
+
+        mInjector.incrementTime(TimeUnit.SECONDS.toMillis(40));
+        notifyBrightnessChanged(mTracker, brightnessFour, displayId, luxValues,
+                new long[] {sensorTime4});
+        mTracker.stop();
+        List<BrightnessChangeEvent> events = mTracker.getEvents(0, true).getList();
+        assertEquals(4, events.size());
+        BrightnessChangeEvent eventOne = events.get(0);
+        assertEquals(brightnessOne, eventOne.brightness, FLOAT_DELTA);
+        assertEquals(originalTime,
+                eventOne.luxTimestamps[0]);
+
+        BrightnessChangeEvent eventTwo = events.get(1);
+        assertEquals(brightnessTwo, eventTwo.brightness, FLOAT_DELTA);
+        assertEquals(originalTime + TimeUnit.SECONDS.toMillis(20),
+                eventTwo.luxTimestamps[0]);
+
+        BrightnessChangeEvent eventThree = events.get(2);
+        assertEquals(brightnessThree, eventThree.brightness, FLOAT_DELTA);
+        assertEquals(originalTime + TimeUnit.SECONDS.toMillis(50),
+                eventThree.luxTimestamps[0]);
+
+        BrightnessChangeEvent eventFour = events.get(3);
+        assertEquals(brightnessFour, eventFour.brightness, FLOAT_DELTA);
+        assertEquals(originalTime + TimeUnit.SECONDS.toMillis(90),
+                eventFour.luxTimestamps[0]);
+    }
+
+    @Test
+    public void testBrightnessFullPopulatedEvent() {
+        final int initialBrightness = 230;
+        final int brightness = 130;
+        final String displayId = "1234";
+
+        mInjector.mSecureIntSettings.put(Settings.Secure.NIGHT_DISPLAY_ACTIVATED, 1);
+        mInjector.mSecureIntSettings.put(Settings.Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE, 3333);
+
+        mInjector.mSecureIntSettings.put(Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED, 1);
+        mInjector.mSecureIntSettings.put(Settings.Secure.REDUCE_BRIGHT_COLORS_LEVEL, 40);
+
+        startTracker(mTracker, initialBrightness, DEFAULT_COLOR_SAMPLING_ENABLED);
+        mInjector.mBroadcastReceiver.onReceive(InstrumentationRegistry.getContext(),
+                batteryChangeEvent(30, 60));
+        final long currentTime = mInjector.currentTimeMillis();
+        notifyBrightnessChanged(mTracker, brightness, displayId, new float[] {1000.0f},
+                new long[] {TimeUnit.NANOSECONDS.toMillis(mInjector.elapsedRealtimeNanos())});
+        List<BrightnessChangeEvent> eventsNoPackage =
+                mTracker.getEvents(0, false).getList();
+        List<BrightnessChangeEvent> events = mTracker.getEvents(0, true).getList();
+        mTracker.stop();
+
+        assertEquals(1, events.size());
+        BrightnessChangeEvent event = events.get(0);
+        assertEquals(event.timeStamp, currentTime);
+        assertEquals(displayId, event.uniqueDisplayId);
+        assertArrayEquals(new float[] {1000.0f}, event.luxValues, FLOAT_DELTA);
+        assertArrayEquals(new long[] {currentTime}, event.luxTimestamps);
+        assertEquals(brightness, event.brightness, FLOAT_DELTA);
+        assertEquals(initialBrightness, event.lastBrightness, FLOAT_DELTA);
+        assertEquals(0.5, event.batteryLevel, FLOAT_DELTA);
+        assertTrue(event.nightMode);
+        assertEquals(3333, event.colorTemperature);
+        assertTrue(event.reduceBrightColors);
+        assertEquals(40, event.reduceBrightColorsStrength);
+        assertEquals(brightness * mRbcOffsetFactor, event.reduceBrightColorsOffset, FLOAT_DELTA);
+        assertEquals("a.package", event.packageName);
+        assertEquals(0, event.userId);
+        assertArrayEquals(new long[] {1, 10, 100, 1000, 300, 30, 10, 1}, event.colorValueBuckets);
+        assertEquals(10000, event.colorSampleDuration);
+
+        assertEquals(1, eventsNoPackage.size());
+        assertNull(eventsNoPackage.get(0).packageName);
+    }
+
+    @Test
+    public void testIgnoreAutomaticBrightnessChange() {
+        final int initialBrightness = 30;
+        startTracker(mTracker, initialBrightness, DEFAULT_COLOR_SAMPLING_ENABLED);
+        mInjector.incrementTime(TimeUnit.SECONDS.toMillis(1));
+
+        final int systemUpdatedBrightness = 20;
+        notifyBrightnessChanged(mTracker, systemUpdatedBrightness, /* userInitiated= */ false,
+                /* powerBrightnessFactor= */ 0.5f, /* isUserSetBrightness= */ false,
+                /* isDefaultBrightnessConfig= */ false, DEFAULT_DISPLAY_ID);
+        List<BrightnessChangeEvent> events = mTracker.getEvents(0, true).getList();
+        // No events because we filtered out our change.
+        assertEquals(0, events.size());
+
+        final int firstUserUpdateBrightness = 20;
+        // Then change comes from somewhere else so we shouldn't filter.
+        notifyBrightnessChanged(mTracker, firstUserUpdateBrightness);
+
+        // and with a different brightness value.
+        final int secondUserUpdateBrightness = 34;
+        notifyBrightnessChanged(mTracker, secondUserUpdateBrightness);
+        events = mTracker.getEvents(0, true).getList();
+
+        assertEquals(2, events.size());
+        // First event is change from system update (20) to first user update (20)
+        assertEquals(systemUpdatedBrightness, events.get(0).lastBrightness, FLOAT_DELTA);
+        assertEquals(firstUserUpdateBrightness, events.get(0).brightness, FLOAT_DELTA);
+        // Second event is from first to second user update.
+        assertEquals(firstUserUpdateBrightness, events.get(1).lastBrightness, FLOAT_DELTA);
+        assertEquals(secondUserUpdateBrightness, events.get(1).brightness, FLOAT_DELTA);
+
+        mTracker.stop();
+    }
+
+    @Test
+    public void testLimitedBufferSize() {
+        startTracker(mTracker);
+
+        for (int brightness = 0; brightness <= 255; ++brightness) {
+            mInjector.incrementTime(TimeUnit.SECONDS.toNanos(1));
+            notifyBrightnessChanged(mTracker, brightness);
+        }
+        List<BrightnessChangeEvent> events = mTracker.getEvents(0, true).getList();
+        mTracker.stop();
+
+        // Should be capped at 100 events, and they should be the most recent 100.
+        assertEquals(100, events.size());
+        for (int i = 0; i < events.size(); i++) {
+            BrightnessChangeEvent event = events.get(i);
+            assertEquals(156 + i, event.brightness, FLOAT_DELTA);
+        }
+    }
+
+    @Test
+    public void testReadEvents() throws Exception {
+        BrightnessTracker tracker = new BrightnessTracker(InstrumentationRegistry.getContext(),
+                mInjector);
+        mInjector.mCurrentTimeMillis = System.currentTimeMillis();
+        long someTimeAgo = System.currentTimeMillis() - TimeUnit.HOURS.toMillis(12);
+        long twoMonthsAgo = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(60);
+        // 3 Events in the file but one too old to read.
+        String eventFile =
+                "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
+                + "<events>\n"
+                + "<event nits=\"194.2\" timestamp=\""
+                + Long.toString(someTimeAgo) + "\" packageName=\""
+                + "com.example.app\" user=\"10\" "
+                + "lastNits=\"32.333\" "
+                + "batteryLevel=\"1.0\" nightMode=\"false\" colorTemperature=\"0\" "
+                + "reduceBrightColors=\"false\" reduceBrightColorsStrength=\"40\" "
+                + "reduceBrightColorsOffset=\"0\"\n"
+                + "uniqueDisplayId=\"123\""
+                + "lux=\"32.2,31.1\" luxTimestamps=\""
+                + Long.toString(someTimeAgo) + "," + Long.toString(someTimeAgo) + "\""
+                + "defaultConfig=\"true\" powerSaveFactor=\"0.5\" userPoint=\"true\" />"
+                + "<event nits=\"71\" timestamp=\""
+                + Long.toString(someTimeAgo) + "\" packageName=\""
+                + "com.android.anapp\" user=\"11\" "
+                + "lastNits=\"32\" "
+                + "batteryLevel=\"0.5\" nightMode=\"true\" colorTemperature=\"3235\" "
+                + "reduceBrightColors=\"true\" reduceBrightColorsStrength=\"40\" "
+                + "reduceBrightColorsOffset=\"0\"\n"
+                + "uniqueDisplayId=\"456\""
+                + "lux=\"132.2,131.1\" luxTimestamps=\""
+                + Long.toString(someTimeAgo) + "," + Long.toString(someTimeAgo) + "\""
+                + "colorSampleDuration=\"3456\" colorValueBuckets=\"123,598,23,19\"/>"
+                // Event that is too old so shouldn't show up.
+                + "<event nits=\"142\" timestamp=\""
+                + Long.toString(twoMonthsAgo) + "\" packageName=\""
+                + "com.example.app\" user=\"10\" "
+                + "lastNits=\"32\" "
+                + "batteryLevel=\"1.0\" nightMode=\"false\" colorTemperature=\"0\" "
+                + "reduceBrightColors=\"false\" reduceBrightColorsStrength=\"40\" "
+                + "reduceBrightColorsOffset=\"0\"\n"
+                + "uniqueDisplayId=\"789\""
+                + "lux=\"32.2,31.1\" luxTimestamps=\""
+                + Long.toString(twoMonthsAgo) + "," + Long.toString(twoMonthsAgo) + "\"/>"
+                + "</events>";
+        tracker.readEventsLocked(getInputStream(eventFile));
+        List<BrightnessChangeEvent> events = tracker.getEvents(0, true).getList();
+        assertEquals(1, events.size());
+        BrightnessChangeEvent event = events.get(0);
+        assertEquals(someTimeAgo, event.timeStamp);
+        assertEquals(194.2, event.brightness, FLOAT_DELTA);
+        assertEquals("123", event.uniqueDisplayId);
+        assertArrayEquals(new float[] {32.2f, 31.1f}, event.luxValues, FLOAT_DELTA);
+        assertArrayEquals(new long[] {someTimeAgo, someTimeAgo}, event.luxTimestamps);
+        assertEquals(32.333, event.lastBrightness, FLOAT_DELTA);
+        assertEquals(0, event.userId);
+        assertFalse(event.nightMode);
+        assertFalse(event.reduceBrightColors);
+        assertEquals(1.0f, event.batteryLevel, FLOAT_DELTA);
+        assertEquals("com.example.app", event.packageName);
+        assertTrue(event.isDefaultBrightnessConfig);
+        assertEquals(0.5f, event.powerBrightnessFactor, FLOAT_DELTA);
+        assertTrue(event.isUserSetBrightness);
+        assertNull(event.colorValueBuckets);
+
+        events = tracker.getEvents(1, true).getList();
+        assertEquals(1, events.size());
+        event = events.get(0);
+        assertEquals(someTimeAgo, event.timeStamp);
+        assertEquals(71, event.brightness, FLOAT_DELTA);
+        assertEquals("456", event.uniqueDisplayId);
+        assertArrayEquals(new float[] {132.2f, 131.1f}, event.luxValues, FLOAT_DELTA);
+        assertArrayEquals(new long[] {someTimeAgo, someTimeAgo}, event.luxTimestamps);
+        assertEquals(32, event.lastBrightness, FLOAT_DELTA);
+        assertEquals(1, event.userId);
+        assertTrue(event.nightMode);
+        assertEquals(3235, event.colorTemperature);
+        assertTrue(event.reduceBrightColors);
+        assertEquals(0.5f, event.batteryLevel, FLOAT_DELTA);
+        assertEquals("com.android.anapp", event.packageName);
+        // Not present in the event so default to false.
+        assertFalse(event.isDefaultBrightnessConfig);
+        assertEquals(1.0, event.powerBrightnessFactor, FLOAT_DELTA);
+        assertFalse(event.isUserSetBrightness);
+        assertEquals(3456L, event.colorSampleDuration);
+        assertArrayEquals(new long[] {123L, 598L, 23L, 19L}, event.colorValueBuckets);
+
+        // Pretend user 1 is a profile of user 0.
+        mInjector.mProfiles = new int[]{0, 1};
+        events = tracker.getEvents(0, true).getList();
+        // Both events should now be returned.
+        assertEquals(2, events.size());
+        BrightnessChangeEvent userZeroEvent;
+        BrightnessChangeEvent userOneEvent;
+        if (events.get(0).userId == 0) {
+            userZeroEvent = events.get(0);
+            userOneEvent = events.get(1);
+        } else {
+            userZeroEvent = events.get(1);
+            userOneEvent = events.get(0);
+        }
+        assertEquals(0, userZeroEvent.userId);
+        assertEquals("com.example.app", userZeroEvent.packageName);
+        assertEquals(1, userOneEvent.userId);
+        // Events from user 1 should have the package name redacted
+        assertNull(userOneEvent.packageName);
+    }
+
+    @Test
+    public void testFailedRead() {
+        String someTimeAgo =
+                Long.toString(System.currentTimeMillis() - TimeUnit.HOURS.toMillis(12));
+        mInjector.mCurrentTimeMillis = System.currentTimeMillis();
+
+        BrightnessTracker tracker = new BrightnessTracker(InstrumentationRegistry.getContext(),
+                mInjector);
+        String eventFile = "junk in the file";
+        try {
+            tracker.readEventsLocked(getInputStream(eventFile));
+        } catch (IOException e) {
+            // Expected;
+        }
+        assertEquals(0, tracker.getEvents(0, true).getList().size());
+
+        // Missing lux value.
+        eventFile =
+                "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
+                        + "<events>\n"
+                        + "<event nits=\"194\" timestamp=\"" + someTimeAgo + "\" packageName=\""
+                        + "com.example.app\" user=\"10\" "
+                        + "batteryLevel=\"0.7\" nightMode=\"false\" colorTemperature=\"0\" />\n"
+                        + "</events>";
+        try {
+            tracker.readEventsLocked(getInputStream(eventFile));
+        } catch (IOException e) {
+            // Expected;
+        }
+        assertEquals(0, tracker.getEvents(0, true).getList().size());
+    }
+
+    @Test
+    public void testWriteThenRead() throws Exception {
+        final int brightness = 20;
+        final String displayId = "1234";
+
+        mInjector.mSecureIntSettings.put(Settings.Secure.NIGHT_DISPLAY_ACTIVATED, 1);
+        mInjector.mSecureIntSettings.put(Settings.Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE, 3339);
+
+        mInjector.mSecureIntSettings.put(Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED, 1);
+        mInjector.mSecureIntSettings.put(Settings.Secure.REDUCE_BRIGHT_COLORS_LEVEL, 40);
+
+        startTracker(mTracker);
+        mInjector.mBroadcastReceiver.onReceive(InstrumentationRegistry.getContext(),
+                batteryChangeEvent(30, 100));
+        final long elapsedTime1 = TimeUnit.NANOSECONDS.toMillis(mInjector.elapsedRealtimeNanos());
+        final long currentTime1 = mInjector.currentTimeMillis();
+        mInjector.incrementTime(TimeUnit.SECONDS.toMillis(2));
+        final long elapsedTime2 = TimeUnit.NANOSECONDS.toMillis(mInjector.elapsedRealtimeNanos());
+        final long currentTime2 = mInjector.currentTimeMillis();
+        mInjector.incrementTime(TimeUnit.SECONDS.toMillis(3));
+        notifyBrightnessChanged(mTracker, brightness, /* userInitiated= */ true,
+                /* powerBrightnessFactor= */ 0.5f, /* isUserSetBrightness= */ true,
+                /* isDefaultBrightnessConfig= */ false, displayId, new float[] {2000.0f, 3000.0f},
+                new long[] {elapsedTime1, elapsedTime2});
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        mTracker.writeEventsLocked(baos);
+        mTracker.stop();
+
+        baos.flush();
+        ByteArrayInputStream input = new ByteArrayInputStream(baos.toByteArray());
+        BrightnessTracker tracker = new BrightnessTracker(InstrumentationRegistry.getContext(),
+                mInjector);
+        tracker.readEventsLocked(input);
+        List<BrightnessChangeEvent> events = tracker.getEvents(0, true).getList();
+
+        assertEquals(1, events.size());
+        BrightnessChangeEvent event = events.get(0);
+        assertEquals(displayId, event.uniqueDisplayId);
+        assertArrayEquals(new float[] {2000.0f, 3000.0f}, event.luxValues, FLOAT_DELTA);
+        assertArrayEquals(new long[] {currentTime1, currentTime2}, event.luxTimestamps);
+        assertEquals(brightness, event.brightness, FLOAT_DELTA);
+        assertEquals(0.3, event.batteryLevel, FLOAT_DELTA);
+        assertTrue(event.nightMode);
+        assertEquals(3339, event.colorTemperature);
+        assertTrue(event.reduceBrightColors);
+        assertEquals(40, event.reduceBrightColorsStrength);
+        assertEquals(brightness * mRbcOffsetFactor, event.reduceBrightColorsOffset, FLOAT_DELTA);
+        assertEquals(0.5f, event.powerBrightnessFactor, FLOAT_DELTA);
+        assertTrue(event.isUserSetBrightness);
+        assertFalse(event.isDefaultBrightnessConfig);
+        assertArrayEquals(new long[] {1, 10, 100, 1000, 300, 30, 10, 1}, event.colorValueBuckets);
+        assertEquals(10000, event.colorSampleDuration);
+    }
+
+    @Test
+    public void testParcelUnParcel() {
+        Parcel parcel = Parcel.obtain();
+        BrightnessChangeEvent.Builder builder = new BrightnessChangeEvent.Builder();
+        builder.setBrightness(23f);
+        builder.setTimeStamp(345L);
+        builder.setPackageName("com.example");
+        builder.setUserId(12);
+        builder.setUniqueDisplayId("9876");
+        float[] luxValues = new float[2];
+        luxValues[0] = 3000.0f;
+        luxValues[1] = 4000.0f;
+        builder.setLuxValues(luxValues);
+        long[] luxTimestamps = new long[2];
+        luxTimestamps[0] = 325L;
+        luxTimestamps[1] = 315L;
+        builder.setLuxTimestamps(luxTimestamps);
+        builder.setBatteryLevel(0.7f);
+        builder.setNightMode(false);
+        builder.setColorTemperature(345);
+        builder.setReduceBrightColors(false);
+        builder.setReduceBrightColorsStrength(40);
+        builder.setReduceBrightColorsOffset(20f);
+        builder.setLastBrightness(50f);
+        builder.setColorValues(new long[] {23, 34, 45}, 1000L);
+        BrightnessChangeEvent event = builder.build();
+
+        event.writeToParcel(parcel, 0);
+        byte[] parceled = parcel.marshall();
+        parcel.recycle();
+
+        parcel = Parcel.obtain();
+        parcel.unmarshall(parceled, 0, parceled.length);
+        parcel.setDataPosition(0);
+
+        BrightnessChangeEvent event2 = BrightnessChangeEvent.CREATOR.createFromParcel(parcel);
+        parcel.recycle();
+        assertEquals(event.brightness, event2.brightness, FLOAT_DELTA);
+        assertEquals(event.timeStamp, event2.timeStamp);
+        assertEquals(event.packageName, event2.packageName);
+        assertEquals(event.userId, event2.userId);
+        assertEquals(event.uniqueDisplayId, event2.uniqueDisplayId);
+        assertArrayEquals(event.luxValues, event2.luxValues, FLOAT_DELTA);
+        assertArrayEquals(event.luxTimestamps, event2.luxTimestamps);
+        assertEquals(event.batteryLevel, event2.batteryLevel, FLOAT_DELTA);
+        assertEquals(event.nightMode, event2.nightMode);
+        assertEquals(event.colorTemperature, event2.colorTemperature);
+        assertEquals(event.reduceBrightColors, event2.reduceBrightColors);
+        assertEquals(event.reduceBrightColorsStrength, event2.reduceBrightColorsStrength);
+        assertEquals(event.reduceBrightColorsOffset, event2.reduceBrightColorsOffset, FLOAT_DELTA);
+        assertEquals(event.lastBrightness, event2.lastBrightness, FLOAT_DELTA);
+        assertArrayEquals(event.colorValueBuckets, event2.colorValueBuckets);
+        assertEquals(event.colorSampleDuration, event2.colorSampleDuration);
+
+        parcel = Parcel.obtain();
+        builder.setBatteryLevel(Float.NaN);
+        event = builder.build();
+        event.writeToParcel(parcel, 0);
+        parceled = parcel.marshall();
+        parcel.recycle();
+
+        parcel = Parcel.obtain();
+        parcel.unmarshall(parceled, 0, parceled.length);
+        parcel.setDataPosition(0);
+        event2 = BrightnessChangeEvent.CREATOR.createFromParcel(parcel);
+        assertEquals(event.batteryLevel, event2.batteryLevel, FLOAT_DELTA);
+    }
+
+    @Test
+    public void testNonNullAmbientStats() {
+        // getAmbientBrightnessStats should return an empty list rather than null when
+        // tracker isn't started or hasn't collected any data.
+        ParceledListSlice<AmbientBrightnessDayStats> slice = mTracker.getAmbientBrightnessStats(0);
+        assertNotNull(slice);
+        assertTrue(slice.getList().isEmpty());
+        startTracker(mTracker);
+        slice = mTracker.getAmbientBrightnessStats(0);
+        assertNotNull(slice);
+        assertTrue(slice.getList().isEmpty());
+    }
+
+    @Test
+    public void testBackgroundHandlerDelay() {
+        final int brightness = 20;
+
+        // Setup tracker.
+        startTracker(mTracker);
+        mInjector.mSensorListener.onSensorChanged(createSensorEvent(1.0f));
+        mInjector.incrementTime(TimeUnit.SECONDS.toMillis(2));
+
+        // Block handler from running.
+        final CountDownLatch latch = new CountDownLatch(1);
+        mInjector.mHandler.post(
+                () -> {
+                    try {
+                        latch.await();
+                    } catch (InterruptedException e) {
+                        fail(e.getMessage());
+                    }
+                });
+
+        // Send an event.
+        long eventTime = mInjector.currentTimeMillis();
+        mTracker.notifyBrightnessChanged(brightness, /* userInitiated= */ true,
+                /* powerBrightnessFactor= */ 1.0f, /* isUserSetBrightness= */ false,
+                /* isDefaultBrightnessConfig= */ false, DEFAULT_DISPLAY_ID, new float[10],
+                new long[10]);
+
+        // Time passes before handler can run.
+        mInjector.incrementTime(TimeUnit.SECONDS.toMillis(2));
+
+        // Let the handler run.
+        latch.countDown();
+        mInjector.waitForHandler();
+
+        List<BrightnessChangeEvent> events = mTracker.getEvents(0, true).getList();
+        mTracker.stop();
+
+        // Check event was recorded with time it was sent rather than handler ran.
+        assertEquals(1, events.size());
+        BrightnessChangeEvent event = events.get(0);
+        assertEquals(eventTime, event.timeStamp);
+    }
+
+    @Test
+    public void testDisplayIdChange() {
+        float firstBrightness = 0.5f;
+        float secondBrightness = 0.75f;
+        String firstDisplayId = "123";
+        String secondDisplayId = "456";
+
+        startTracker(mTracker);
+        mInjector.mSensorListener.onSensorChanged(createSensorEvent(1000.0f));
+
+        notifyBrightnessChanged(mTracker, firstBrightness, firstDisplayId);
+        mInjector.incrementTime(TimeUnit.SECONDS.toMillis(2));
+        List<BrightnessChangeEvent> events = mTracker.getEvents(0, true).getList();
+        assertEquals(1, events.size());
+        BrightnessChangeEvent firstEvent = events.get(0);
+        assertEquals(firstDisplayId, firstEvent.uniqueDisplayId);
+        assertEquals(firstBrightness, firstEvent.brightness, 0.001f);
+
+        notifyBrightnessChanged(mTracker, secondBrightness, secondDisplayId);
+        mInjector.incrementTime(TimeUnit.SECONDS.toMillis(2));
+        events = mTracker.getEvents(0, true).getList();
+        assertEquals(2, events.size());
+        BrightnessChangeEvent secondEvent = events.get(1);
+        assertEquals(secondDisplayId, secondEvent.uniqueDisplayId);
+        assertEquals(secondBrightness, secondEvent.brightness, 0.001f);
+
+        mTracker.stop();
+    }
+
+    @Test
+    public void testLightSensorChange() {
+        // verify the tracker started correctly and a listener registered
+        startTracker(mTracker);
+        assertNotNull(mInjector.mSensorListener);
+        assertEquals(mInjector.mLightSensor, mLightSensorFake);
+
+        // Setting the sensor to null should stop the registered listener.
+        mTracker.setLightSensor(null);
+        mInjector.waitForHandler();
+        assertNull(mInjector.mSensorListener);
+        assertNull(mInjector.mLightSensor);
+
+        // Resetting sensor should start listener again
+        mTracker.setLightSensor(mLightSensorFake);
+        mInjector.waitForHandler();
+        assertNotNull(mInjector.mSensorListener);
+        assertEquals(mInjector.mLightSensor, mLightSensorFake);
+
+        Sensor secondSensor = new Sensor(mInputSensorInfoMock);
+        // Setting a different listener should keep things working
+        mTracker.setLightSensor(secondSensor);
+        mInjector.waitForHandler();
+        assertNotNull(mInjector.mSensorListener);
+        assertEquals(mInjector.mLightSensor, secondSensor);
+    }
+
+    @Test
+    public void testSetLightSensorDoesntStartListener() {
+        mTracker.setLightSensor(mLightSensorFake);
+        assertNull(mInjector.mSensorListener);
+    }
+
+    @Test
+    public void testNullLightSensorWontRegister() {
+        mTracker.setLightSensor(null);
+        startTracker(mTracker);
+        assertNull(mInjector.mSensorListener);
+        assertNull(mInjector.mLightSensor);
+    }
+
+    @Test
+    public void testOnlyOneReceiverRegistered() {
+        assertNull(mInjector.mLightSensor);
+        assertNull(mInjector.mSensorListener);
+        assertNull(mInjector.mContentObserver);
+        assertNull(mInjector.mBroadcastReceiver);
+        assertFalse(mInjector.mIdleScheduled);
+        startTracker(mTracker, 0.3f, false);
+
+        assertNotNull(mInjector.mLightSensor);
+        assertNotNull(mInjector.mSensorListener);
+        assertNotNull(mInjector.mContentObserver);
+        assertNotNull(mInjector.mBroadcastReceiver);
+        assertTrue(mInjector.mIdleScheduled);
+        Sensor registeredLightSensor = mInjector.mLightSensor;
+        SensorEventListener registeredSensorListener = mInjector.mSensorListener;
+        ContentObserver registeredContentObserver = mInjector.mContentObserver;
+        BroadcastReceiver registeredBroadcastReceiver = mInjector.mBroadcastReceiver;
+
+        mTracker.start(0.3f);
+        assertSame(registeredLightSensor, mInjector.mLightSensor);
+        assertSame(registeredSensorListener, mInjector.mSensorListener);
+        assertSame(registeredContentObserver, mInjector.mContentObserver);
+        assertSame(registeredBroadcastReceiver, mInjector.mBroadcastReceiver);
+
+        mTracker.stop();
+        assertNull(mInjector.mLightSensor);
+        assertNull(mInjector.mSensorListener);
+        assertNull(mInjector.mContentObserver);
+        assertNull(mInjector.mBroadcastReceiver);
+        assertFalse(mInjector.mIdleScheduled);
+
+        // mInjector asserts that we aren't removing a null receiver
+        mTracker.stop();
+    }
+
+    private InputStream getInputStream(String data) {
+        return new ByteArrayInputStream(data.getBytes(StandardCharsets.UTF_8));
+    }
+
+    private Intent batteryChangeEvent(int level, int scale) {
+        Intent intent = new Intent();
+        intent.setAction(Intent.ACTION_BATTERY_CHANGED);
+        intent.putExtra(BatteryManager.EXTRA_LEVEL, level);
+        intent.putExtra(BatteryManager.EXTRA_SCALE, scale);
+        return intent;
+    }
+
+    private SensorEvent createSensorEvent(float lux) {
+        SensorEvent event;
+        try {
+            Constructor<SensorEvent> constr =
+                    SensorEvent.class.getDeclaredConstructor(Integer.TYPE);
+            constr.setAccessible(true);
+            event = constr.newInstance(1);
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+        event.values[0] = lux;
+        event.timestamp = mInjector.mElapsedRealtimeNanos;
+
+        return event;
+    }
+
+    private void startTracker(BrightnessTracker tracker) {
+        startTracker(tracker, DEFAULT_INITIAL_BRIGHTNESS,  DEFAULT_COLOR_SAMPLING_ENABLED);
+    }
+
+    private void startTracker(BrightnessTracker tracker, float initialBrightness,
+            boolean collectColorSamples) {
+        tracker.start(initialBrightness);
+        tracker.setShouldCollectColorSample(collectColorSamples);
+        mInjector.waitForHandler();
+    }
+
+    private void notifyBrightnessChanged(BrightnessTracker tracker, float brightness) {
+        notifyBrightnessChanged(tracker, brightness, DEFAULT_DISPLAY_ID);
+    }
+
+    private void notifyBrightnessChanged(BrightnessTracker tracker, float brightness,
+            String displayId) {
+        notifyBrightnessChanged(tracker, brightness, /* userInitiated= */ true,
+                /* powerBrightnessFactor= */ 1.0f, /* isUserSetBrightness= */ false,
+                /* isDefaultBrightnessConfig= */ false, displayId, new float[10], new long[10]);
+    }
+
+    private void notifyBrightnessChanged(BrightnessTracker tracker, float brightness,
+            String displayId, float[] luxValues, long[] luxTimestamps) {
+        notifyBrightnessChanged(tracker, brightness, /* userInitiated= */ true,
+                /* powerBrightnessFactor= */ 1.0f, /* isUserSetBrightness= */ false,
+                /* isDefaultBrightnessConfig= */ false, displayId, luxValues, luxTimestamps);
+    }
+
+    private void notifyBrightnessChanged(BrightnessTracker tracker, float brightness,
+            boolean userInitiated, float powerBrightnessFactor, boolean isUserSetBrightness,
+            boolean isDefaultBrightnessConfig, String displayId) {
+        tracker.notifyBrightnessChanged(brightness, userInitiated, powerBrightnessFactor,
+                isUserSetBrightness, isDefaultBrightnessConfig, displayId, new float[10],
+                new long[10]);
+        mInjector.waitForHandler();
+    }
+
+    private void notifyBrightnessChanged(BrightnessTracker tracker, float brightness,
+            boolean userInitiated, float powerBrightnessFactor, boolean isUserSetBrightness,
+            boolean isDefaultBrightnessConfig, String displayId, float[] luxValues,
+            long[] luxTimestamps) {
+        tracker.notifyBrightnessChanged(brightness, userInitiated, powerBrightnessFactor,
+                isUserSetBrightness, isDefaultBrightnessConfig, displayId, luxValues,
+                luxTimestamps);
+        mInjector.waitForHandler();
+    }
+
+    private BrightnessConfiguration buildBrightnessConfiguration(boolean collectColorSamples) {
+        BrightnessConfiguration.Builder builder = new BrightnessConfiguration.Builder(
+                /* lux= */ new float[] {0f, 10f, 100f},
+                /* nits= */ new float[] {1f, 90f, 100f});
+        builder.setShouldCollectColorSamples(collectColorSamples);
+        return builder.build();
+    }
+
+    private static final class Idle implements MessageQueue.IdleHandler {
+        private boolean mIdle;
+
+        @Override
+        public boolean queueIdle() {
+            synchronized (this) {
+                mIdle = true;
+                notifyAll();
+            }
+            return false;
+        }
+
+        public synchronized void waitForIdle() {
+            while (!mIdle) {
+                try {
+                    wait();
+                } catch (InterruptedException e) {
+                }
+            }
+        }
+    }
+
+    private class TestInjector extends BrightnessTracker.Injector {
+        SensorEventListener mSensorListener;
+        Sensor mLightSensor;
+        BroadcastReceiver mBroadcastReceiver;
+        DisplayManager.DisplayListener mDisplayListener;
+        Map<String, Integer> mSecureIntSettings = new HashMap<>();
+        long mCurrentTimeMillis = System.currentTimeMillis();
+        long mElapsedRealtimeNanos = SystemClock.elapsedRealtimeNanos();
+        Handler mHandler;
+        boolean mIdleScheduled;
+        boolean mInteractive = true;
+        int[] mProfiles;
+        ContentObserver mContentObserver;
+        boolean mIsBrightnessModeAutomatic = true;
+        boolean mColorSamplingEnabled = false;
+        DisplayedContentSamplingAttributes mDefaultSamplingAttributes =
+                new DisplayedContentSamplingAttributes(0x37, 0, 0x4);
+        float mFrameRate = 60.0f;
+        int mNoColorSamplingFrames;
+
+
+        public TestInjector(Handler handler) {
+            mHandler = handler;
+        }
+
+        void incrementTime(long timeMillis) {
+            mCurrentTimeMillis += timeMillis;
+            mElapsedRealtimeNanos += TimeUnit.MILLISECONDS.toNanos(timeMillis);
+        }
+
+        void setBrightnessMode(boolean isBrightnessModeAutomatic) {
+            mIsBrightnessModeAutomatic = isBrightnessModeAutomatic;
+            mContentObserver.dispatchChange(false, null);
+            waitForHandler();
+        }
+
+        void sendScreenChange(boolean screenOn) {
+            mInteractive = screenOn;
+            Intent intent = new Intent();
+            intent.setAction(screenOn ? Intent.ACTION_SCREEN_ON : Intent.ACTION_SCREEN_OFF);
+            mBroadcastReceiver.onReceive(InstrumentationRegistry.getContext(), intent);
+            waitForHandler();
+        }
+
+        void waitForHandler() {
+            Idle idle = new Idle();
+            mHandler.getLooper().getQueue().addIdleHandler(idle);
+            mHandler.post(() -> {});
+            idle.waitForIdle();
+        }
+
+        @Override
+        public void registerSensorListener(Context context,
+                SensorEventListener sensorListener, Sensor lightSensor, Handler handler) {
+            mSensorListener = sensorListener;
+            mLightSensor = lightSensor;
+        }
+
+        @Override
+        public void unregisterSensorListener(Context context,
+                SensorEventListener sensorListener) {
+            mSensorListener = null;
+            mLightSensor = null;
+        }
+
+        @Override
+        public void registerBrightnessModeObserver(ContentResolver resolver,
+                ContentObserver settingsObserver) {
+            mContentObserver = settingsObserver;
+        }
+
+        @Override
+        public void unregisterBrightnessModeObserver(Context context,
+                ContentObserver settingsObserver) {
+            mContentObserver = null;
+        }
+
+        @Override
+        public void registerReceiver(Context context,
+                BroadcastReceiver shutdownReceiver, IntentFilter shutdownFilter) {
+            mBroadcastReceiver = shutdownReceiver;
+        }
+
+        @Override
+        public void unregisterReceiver(Context context,
+                BroadcastReceiver broadcastReceiver) {
+            assertEquals(mBroadcastReceiver, broadcastReceiver);
+            mBroadcastReceiver = null;
+        }
+
+        @Override
+        public Handler getBackgroundHandler() {
+            return mHandler;
+        }
+
+        @Override
+        public boolean isBrightnessModeAutomatic(ContentResolver resolver) {
+            return mIsBrightnessModeAutomatic;
+        }
+
+        @Override
+        public int getSecureIntForUser(ContentResolver resolver, String setting, int defaultValue,
+                int userId) {
+            Integer value = mSecureIntSettings.get(setting);
+            if (value == null) {
+                return defaultValue;
+            } else {
+                return value;
+            }
+        }
+
+        @Override
+        public AtomicFile getFile(String filename) {
+            // Don't have the test write / read from anywhere.
+            return null;
+        }
+
+        @Override
+        public AtomicFile getLegacyFile(String filename) {
+            // Don't have the test write / read from anywhere.
+            return null;
+        }
+
+        @Override
+        public long currentTimeMillis() {
+            return mCurrentTimeMillis;
+        }
+
+        @Override
+        public long elapsedRealtimeNanos() {
+            return mElapsedRealtimeNanos;
+        }
+
+        @Override
+        public int getUserSerialNumber(UserManager userManager, int userId) {
+            return userId + 10;
+        }
+
+        @Override
+        public int getUserId(UserManager userManager, int userSerialNumber) {
+            return userSerialNumber - 10;
+        }
+
+        @Override
+        public int[] getProfileIds(UserManager userManager, int userId) {
+            if (mProfiles != null) {
+                return mProfiles;
+            } else {
+                return new int[]{userId};
+            }
+        }
+
+        @Override
+        public RootTaskInfo getFocusedStack() throws RemoteException {
+            RootTaskInfo focusedStack = new RootTaskInfo();
+            focusedStack.userId = 0;
+            focusedStack.topActivity = new ComponentName("a.package", "a.class");
+            return focusedStack;
+        }
+
+        @Override
+        public void scheduleIdleJob(Context context) {
+            // Don't actually schedule jobs during unit tests.
+            mIdleScheduled = true;
+        }
+
+        @Override
+        public void cancelIdleJob(Context context) {
+            mIdleScheduled = false;
+        }
+
+        @Override
+        public boolean isInteractive(Context context) {
+            return mInteractive;
+        }
+
+        @Override
+        public int getNightDisplayColorTemperature(Context context) {
+            return mSecureIntSettings.getOrDefault(Settings.Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE,
+                    mDefaultNightModeColorTemperature);
+        }
+
+        @Override
+        public boolean isNightDisplayActivated(Context context) {
+            return mSecureIntSettings.getOrDefault(Settings.Secure.NIGHT_DISPLAY_ACTIVATED,
+                    0) == 1;
+        }
+
+        @Override
+        public int getReduceBrightColorsStrength(Context context) {
+            return mSecureIntSettings.getOrDefault(Settings.Secure.REDUCE_BRIGHT_COLORS_LEVEL,
+                    0);
+        }
+
+        @Override
+        public boolean isReduceBrightColorsActivated(Context context) {
+            return mSecureIntSettings.getOrDefault(Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED,
+                    0) == 1;
+        }
+
+        @Override
+        public DisplayedContentSample sampleColor(int noFramesToSample) {
+            return new DisplayedContentSample(600L,
+                    null,
+                    null,
+                     new long[] {1, 10, 100, 1000, 300, 30, 10, 1},
+                    null);
+        }
+
+        @Override
+        public float getFrameRate(Context context) {
+            return mFrameRate;
+        }
+
+        @Override
+        public DisplayedContentSamplingAttributes getSamplingAttributes() {
+            return mDefaultSamplingAttributes;
+        }
+
+        @Override
+        public boolean enableColorSampling(boolean enable, int noFrames) {
+            mColorSamplingEnabled = enable;
+            mNoColorSamplingFrames = noFrames;
+            return true;
+        }
+
+        @Override
+        public void registerDisplayListener(Context context,
+                DisplayManager.DisplayListener listener, Handler handler) {
+            mDisplayListener = listener;
+        }
+
+        @Override
+        public void unRegisterDisplayListener(Context context,
+                DisplayManager.DisplayListener listener) {
+            mDisplayListener = null;
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/display/ColorFadeTest.java b/services/tests/displayservicetests/src/com/android/server/display/ColorFadeTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/display/ColorFadeTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/ColorFadeTest.java
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DensityMappingTest.java b/services/tests/displayservicetests/src/com/android/server/display/DensityMappingTest.java
similarity index 100%
rename from services/tests/mockingservicestests/src/com/android/server/display/DensityMappingTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/DensityMappingTest.java
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DeviceStateToLayoutMapTest.java b/services/tests/displayservicetests/src/com/android/server/display/DeviceStateToLayoutMapTest.java
new file mode 100644
index 0000000..4b124ca
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/DeviceStateToLayoutMapTest.java
@@ -0,0 +1,377 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display;
+
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThrows;
+
+import android.view.DisplayAddress;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.display.layout.DisplayIdProducer;
+import com.android.server.display.layout.Layout;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+
+@SmallTest
+public class DeviceStateToLayoutMapTest {
+    private DeviceStateToLayoutMap mDeviceStateToLayoutMap;
+
+    @Mock DisplayIdProducer mDisplayIdProducerMock;
+
+    @Before
+    public void setUp() throws IOException {
+        MockitoAnnotations.initMocks(this);
+
+        Mockito.when(mDisplayIdProducerMock.getId(false)).thenReturn(1);
+
+        setupDeviceStateToLayoutMap();
+    }
+
+    //////////////////
+    // Test Methods //
+    //////////////////
+
+    @Test
+    public void testInitialState() {
+        Layout configLayout = mDeviceStateToLayoutMap.get(0);
+
+        Layout testLayout = new Layout();
+        createDefaultDisplay(testLayout, 123456L);
+        createNonDefaultDisplay(testLayout, 78910L, /* enabled= */ false, /* group= */ null,
+                /* leadDisplayAddress= */ null);
+        createNonDefaultDisplay(testLayout, 98765L, /* enabled= */ true, /* group= */ "group1",
+                /* leadDisplayAddress= */ null);
+        createNonDefaultDisplay(testLayout, 786L, /* enabled= */ false, /* group= */ "group2",
+                /* leadDisplayAddress= */ null);
+        createNonDefaultDisplay(testLayout, 1092L, /* enabled= */ true, /* group= */ null,
+                DisplayAddress.fromPhysicalDisplayId(78910L));
+        testLayout.postProcessLocked();
+
+        assertEquals(testLayout, configLayout);
+    }
+
+    @Test
+    public void testSwitchedState() {
+        Layout configLayout = mDeviceStateToLayoutMap.get(1);
+
+        Layout testLayout = new Layout();
+        createDefaultDisplay(testLayout, 78910L);
+        createNonDefaultDisplay(testLayout, 123456L, /* enabled= */ false, /* group= */ null,
+                /* leadDisplayAddress= */ null);
+        testLayout.postProcessLocked();
+
+        assertEquals(testLayout, configLayout);
+    }
+
+    @Test
+    public void testThermalBrightnessThrottlingMapId() {
+        Layout configLayout = mDeviceStateToLayoutMap.get(2);
+
+        assertEquals("concurrent1", configLayout.getAt(0).getThermalBrightnessThrottlingMapId());
+        assertEquals("concurrent2", configLayout.getAt(1).getThermalBrightnessThrottlingMapId());
+    }
+
+    @Test
+    public void testRearDisplayLayout() {
+        Layout configLayout = mDeviceStateToLayoutMap.get(2);
+
+        assertEquals(Layout.Display.POSITION_FRONT, configLayout.getAt(0).getPosition());
+        assertEquals(Layout.Display.POSITION_REAR, configLayout.getAt(1).getPosition());
+    }
+
+    @Test
+    public void testRefreshRateZoneId() {
+        Layout configLayout = mDeviceStateToLayoutMap.get(3);
+
+        assertEquals("test1", configLayout.getAt(0).getRefreshRateZoneId());
+        assertNull(configLayout.getAt(1).getRefreshRateZoneId());
+    }
+
+    @Test
+    public void testThermalRefreshRateThrottlingMapId() {
+        Layout configLayout = mDeviceStateToLayoutMap.get(4);
+
+        assertEquals("test2", configLayout.getAt(0).getRefreshRateThermalThrottlingMapId());
+        assertNull(configLayout.getAt(1).getRefreshRateThermalThrottlingMapId());
+    }
+
+    @Test
+    public void testWholeStateConfig() {
+        Layout configLayout = mDeviceStateToLayoutMap.get(99);
+
+        Layout testLayout = new Layout();
+        testLayout.createDisplayLocked(DisplayAddress.fromPhysicalDisplayId(345L),
+                /* isDefault= */ true, /* isEnabled= */ true, /* displayGroupName= */ null,
+                mDisplayIdProducerMock,  Layout.Display.POSITION_FRONT,
+                /* leadDisplayAddress= */ null, /* brightnessThrottlingMapId= */ "brightness1",
+                /* refreshRateZoneId= */ "zone1",
+                /* refreshRateThermalThrottlingMapId= */ "rr1");
+        testLayout.createDisplayLocked(DisplayAddress.fromPhysicalDisplayId(678L),
+                /* isDefault= */ false, /* isEnabled= */ false, /* displayGroupName= */ "group1",
+                mDisplayIdProducerMock, Layout.Display.POSITION_REAR,
+                /* leadDisplayAddress= */ null, /* brightnessThrottlingMapId= */ "brightness2",
+                /* refreshRateZoneId= */ "zone2",
+                /* refreshRateThermalThrottlingMapId= */ "rr2");
+        testLayout.postProcessLocked();
+
+        assertEquals(testLayout, configLayout);
+    }
+
+    @Test
+    public void testLeadDisplayAddress() {
+        Layout layout = new Layout();
+        createNonDefaultDisplay(layout, 111L, /* enabled= */ true, /* group= */ null,
+                /* leadDisplayAddress= */ null);
+        createNonDefaultDisplay(layout, 222L, /* enabled= */ true, /* group= */ null,
+                DisplayAddress.fromPhysicalDisplayId(111L));
+
+        layout.postProcessLocked();
+
+        com.android.server.display.layout.Layout.Display display111 =
+                layout.getByAddress(DisplayAddress.fromPhysicalDisplayId(111));
+        com.android.server.display.layout.Layout.Display display222 =
+                layout.getByAddress(DisplayAddress.fromPhysicalDisplayId(222));
+        assertEquals(display111.getLeadDisplayId(), layout.NO_LEAD_DISPLAY);
+        assertEquals(display222.getLeadDisplayId(), display111.getLogicalDisplayId());
+    }
+
+    @Test
+    public void testLeadDisplayAddress_defaultDisplay() {
+        Layout layout = new Layout();
+        createDefaultDisplay(layout, 123456L);
+
+        layout.postProcessLocked();
+
+        com.android.server.display.layout.Layout.Display display =
+                layout.getByAddress(DisplayAddress.fromPhysicalDisplayId(123456));
+        assertEquals(display.getLeadDisplayId(), layout.NO_LEAD_DISPLAY);
+    }
+
+    @Test
+    public void testLeadDisplayAddress_noLeadDisplay() {
+        Layout layout = new Layout();
+        createNonDefaultDisplay(layout, 222L, /* enabled= */ true, /* group= */ null,
+                /* leadDisplayAddress= */ null);
+
+        layout.postProcessLocked();
+
+        com.android.server.display.layout.Layout.Display display =
+                layout.getByAddress(DisplayAddress.fromPhysicalDisplayId(222));
+        assertEquals(display.getLeadDisplayId(), layout.NO_LEAD_DISPLAY);
+    }
+
+    @Test
+    public void testLeadDisplayAddress_selfLeadDisplayForNonDefaultDisplay() {
+        Layout layout = new Layout();
+
+        assertThrows("Expected Layout to throw IllegalArgumentException when the display points out"
+                + " itself as a lead display",
+                IllegalArgumentException.class,
+                () -> layout.createDisplayLocked(DisplayAddress.fromPhysicalDisplayId(123L),
+                    /* isDefault= */ true, /* isEnabled= */ true, /* displayGroupName= */ null,
+                    mDisplayIdProducerMock,  Layout.Display.POSITION_FRONT,
+                    DisplayAddress.fromPhysicalDisplayId(123L),
+                    /* brightnessThrottlingMapId= */ null, /* refreshRateZoneId= */ null,
+                    /* refreshRateThermalThrottlingMapId= */ null));
+    }
+
+    @Test
+    public void testLeadDisplayAddress_wrongLeadDisplayForDefaultDisplay() {
+        Layout layout = new Layout();
+
+        assertThrows("Expected Layout to throw IllegalArgumentException when the default display "
+                + "has a lead display",
+                IllegalArgumentException.class,
+                () -> layout.createDisplayLocked(DisplayAddress.fromPhysicalDisplayId(123L),
+                    /* isDefault= */ true, /* isEnabled= */ true, /* displayGroupName= */ null,
+                    mDisplayIdProducerMock,  Layout.Display.POSITION_FRONT,
+                    DisplayAddress.fromPhysicalDisplayId(987L),
+                    /* brightnessThrottlingMapId= */ null, /* refreshRateZoneId= */ null,
+                    /* refreshRateThermalThrottlingMapId= */ null));
+    }
+
+    @Test
+    public void testLeadDisplayAddress_notExistingLeadDisplayForNonDefaultDisplay() {
+        Layout layout = new Layout();
+        createNonDefaultDisplay(layout, 222L, /* enabled= */ true, /* group= */ null,
+                DisplayAddress.fromPhysicalDisplayId(111L));
+
+        assertThrows("Expected Layout to throw IllegalArgumentException when a lead display doesn't"
+                + " exist", IllegalArgumentException.class, () -> layout.postProcessLocked());
+    }
+
+    @Test
+    public void testLeadDisplayAddress_leadDisplayInDifferentDisplayGroup() {
+        Layout layout = new Layout();
+        createNonDefaultDisplay(layout, 111, /* enabled= */ true, /* group= */ "group1",
+                /* leadDisplayAddress= */ null);
+        createNonDefaultDisplay(layout, 222L, /* enabled= */ true, /* group= */ "group2",
+                DisplayAddress.fromPhysicalDisplayId(111L));
+
+        assertThrows("Expected Layout to throw IllegalArgumentException when pointing to a lead "
+                + "display in the different group",
+                IllegalArgumentException.class, () -> layout.postProcessLocked());
+    }
+
+    @Test
+    public void testLeadDisplayAddress_cyclicLeadDisplay() {
+        Layout layout = new Layout();
+        createNonDefaultDisplay(layout, 111, /* enabled= */ true, /* group= */ null,
+                DisplayAddress.fromPhysicalDisplayId(222L));
+        createNonDefaultDisplay(layout, 222L, /* enabled= */ true, /* group= */ null,
+                DisplayAddress.fromPhysicalDisplayId(333L));
+        createNonDefaultDisplay(layout, 333L, /* enabled= */ true, /* group= */ null,
+                DisplayAddress.fromPhysicalDisplayId(222L));
+
+        assertThrows("Expected Layout to throw IllegalArgumentException when pointing to a lead "
+                + "display in the different group",
+                IllegalArgumentException.class, () -> layout.postProcessLocked());
+    }
+
+    ////////////////////
+    // Helper Methods //
+    ////////////////////
+
+    private void createDefaultDisplay(Layout layout, long id) {
+        layout.createDefaultDisplayLocked(DisplayAddress.fromPhysicalDisplayId(id),
+                mDisplayIdProducerMock);
+    }
+
+    private void createNonDefaultDisplay(Layout layout, long id, boolean enabled, String group,
+            DisplayAddress leadDisplayAddress) {
+        layout.createDisplayLocked(DisplayAddress.fromPhysicalDisplayId(id), /* isDefault= */ false,
+                enabled, group, mDisplayIdProducerMock, Layout.Display.POSITION_UNKNOWN,
+                leadDisplayAddress, /* brightnessThrottlingMapId= */ null,
+                /* refreshRateZoneId= */ null,
+                /* refreshRateThermalThrottlingMapId= */ null);
+    }
+
+    private void setupDeviceStateToLayoutMap() throws IOException {
+        Path tempFile = Files.createTempFile("device_state_layout_map", ".tmp");
+        Files.write(tempFile, getContent().getBytes(StandardCharsets.UTF_8));
+        mDeviceStateToLayoutMap = new DeviceStateToLayoutMap(mDisplayIdProducerMock,
+                tempFile.toFile());
+    }
+
+    private String getContent() {
+        return "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
+                +  "<layouts>\n"
+                +    "<layout>\n"
+                +      "<state>0</state> \n"
+                +      "<display enabled=\"true\" defaultDisplay=\"true\">\n"
+                +        "<address>123456</address>\n"
+                +      "</display>\n"
+                +      "<display enabled=\"false\">\n"
+                +        "<address>78910</address>\n"
+                +      "</display>\n"
+                +      "<display enabled=\"true\" displayGroup=\"group1\">\n"
+                +        "<address>98765</address>\n"
+                +      "</display>\n"
+                +      "<display enabled=\"false\" displayGroup=\"group2\">\n"
+                +        "<address>786</address>\n"
+                +      "</display>\n"
+                +      "<display enabled=\"true\">\n"
+                +        "<address>1092</address>\n"
+                +        "<leadDisplayAddress>78910</leadDisplayAddress>\n"
+                +      "</display>\n"
+                +    "</layout>\n"
+
+                +    "<layout>\n"
+                +      "<state>1</state> \n"
+                +      "<display enabled=\"true\" defaultDisplay=\"true\">\n"
+                +        "<address>78910</address>\n"
+                +      "</display>\n"
+                +      "<display enabled=\"false\">\n"
+                +        "<address>123456</address>\n"
+                +      "</display>\n"
+                +    "</layout>\n"
+
+                +    "<layout>\n"
+                +      "<state>2</state> \n"
+                +      "<display enabled=\"true\" defaultDisplay=\"true\">\n"
+                +        "<address>345</address>\n"
+                +        "<position>front</position>\n"
+                +        "<brightnessThrottlingMapId>concurrent1</brightnessThrottlingMapId>\n"
+                +      "</display>\n"
+                +      "<display enabled=\"true\">\n"
+                +        "<address>678</address>\n"
+                +        "<position>rear</position>\n"
+                +        "<brightnessThrottlingMapId>concurrent2</brightnessThrottlingMapId>\n"
+                +      "</display>\n"
+                +    "</layout>\n"
+
+                +    "<layout>\n"
+                +      "<state>3</state> \n"
+                +      "<display enabled=\"true\" defaultDisplay=\"true\" "
+                +                                               "refreshRateZoneId=\"test1\">\n"
+                +        "<address>345</address>\n"
+                +      "</display>\n"
+                +      "<display enabled=\"true\">\n"
+                +        "<address>678</address>\n"
+                +      "</display>\n"
+                +    "</layout>\n"
+
+                +    "<layout>\n"
+                +      "<state>4</state> \n"
+                +      "<display enabled=\"true\" defaultDisplay=\"true\" >\n"
+                +        "<address>345</address>\n"
+                +        "<refreshRateThermalThrottlingMapId>"
+                +          "test2"
+                +        "</refreshRateThermalThrottlingMapId>"
+                +      "</display>\n"
+                +      "<display enabled=\"true\">\n"
+                +        "<address>678</address>\n"
+                +      "</display>\n"
+                +    "</layout>\n"
+                +    "<layout>\n"
+                +      "<state>99</state> \n"
+                +      "<display enabled=\"true\" defaultDisplay=\"true\" "
+                +                                          "refreshRateZoneId=\"zone1\">\n"
+                +         "<address>345</address>\n"
+                +         "<position>front</position>\n"
+                +         "<brightnessThrottlingMapId>brightness1</brightnessThrottlingMapId>\n"
+                +         "<refreshRateThermalThrottlingMapId>"
+                +           "rr1"
+                +         "</refreshRateThermalThrottlingMapId>"
+                +       "</display>\n"
+                +       "<display enabled=\"false\" displayGroup=\"group1\" "
+                +                                           "refreshRateZoneId=\"zone2\">\n"
+                +         "<address>678</address>\n"
+                +         "<position>rear</position>\n"
+                +         "<brightnessThrottlingMapId>brightness2</brightnessThrottlingMapId>\n"
+                +         "<refreshRateThermalThrottlingMapId>"
+                +           "rr2"
+                +         "</refreshRateThermalThrottlingMapId>"
+                +       "</display>\n"
+                +     "</layout>\n"
+                +   "</layouts>\n";
+    }
+}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java
new file mode 100644
index 0000000..95c62ae
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display;
+
+import static org.junit.Assert.assertEquals;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.display.brightness.BrightnessReason;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class DisplayBrightnessStateTest {
+    private static final float FLOAT_DELTA = 0.001f;
+
+    private DisplayBrightnessState.Builder mDisplayBrightnessStateBuilder;
+
+    @Before
+    public void before() {
+        mDisplayBrightnessStateBuilder = new DisplayBrightnessState.Builder();
+    }
+
+    @Test
+    public void validateAllDisplayBrightnessStateFieldsAreSetAsExpected() {
+        float brightness = 0.3f;
+        float sdrBrightness = 0.2f;
+        boolean shouldUseAutoBrightness = true;
+        BrightnessReason brightnessReason = new BrightnessReason();
+        brightnessReason.setReason(BrightnessReason.REASON_AUTOMATIC);
+        brightnessReason.setModifier(BrightnessReason.MODIFIER_DIMMED);
+        DisplayBrightnessState displayBrightnessState = mDisplayBrightnessStateBuilder
+                .setBrightness(brightness)
+                .setSdrBrightness(sdrBrightness)
+                .setBrightnessReason(brightnessReason)
+                .setShouldUseAutoBrightness(shouldUseAutoBrightness)
+                .build();
+
+        assertEquals(displayBrightnessState.getBrightness(), brightness, FLOAT_DELTA);
+        assertEquals(displayBrightnessState.getSdrBrightness(), sdrBrightness, FLOAT_DELTA);
+        assertEquals(displayBrightnessState.getBrightnessReason(), brightnessReason);
+        assertEquals(displayBrightnessState.getShouldUseAutoBrightness(), shouldUseAutoBrightness);
+        assertEquals(displayBrightnessState.toString(), getString(displayBrightnessState));
+    }
+
+    @Test
+    public void testFrom() {
+        BrightnessReason reason = new BrightnessReason();
+        reason.setReason(BrightnessReason.REASON_MANUAL);
+        reason.setModifier(BrightnessReason.MODIFIER_DIMMED);
+        DisplayBrightnessState state1 = new DisplayBrightnessState.Builder()
+                .setBrightnessReason(reason)
+                .setBrightness(0.26f)
+                .setSdrBrightness(0.23f)
+                .setShouldUseAutoBrightness(false)
+                .build();
+        DisplayBrightnessState state2 = DisplayBrightnessState.Builder.from(state1).build();
+        assertEquals(state1, state2);
+    }
+
+    private String getString(DisplayBrightnessState displayBrightnessState) {
+        StringBuilder sb = new StringBuilder();
+        sb.append("DisplayBrightnessState:")
+                .append("\n    brightness:")
+                .append(displayBrightnessState.getBrightness())
+                .append("\n    sdrBrightness:")
+                .append(displayBrightnessState.getSdrBrightness())
+                .append("\n    brightnessReason:")
+                .append(displayBrightnessState.getBrightnessReason())
+                .append("\n    shouldUseAutoBrightness:")
+                .append(displayBrightnessState.getShouldUseAutoBrightness());
+        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
new file mode 100644
index 0000000..8b04eca
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
@@ -0,0 +1,953 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display;
+
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.os.Temperature;
+import android.util.SparseArray;
+import android.view.SurfaceControl;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.R;
+import com.android.server.display.config.ThermalStatus;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public final class DisplayDeviceConfigTest {
+    private static final int DEFAULT_PEAK_REFRESH_RATE = 75;
+    private static final int DEFAULT_REFRESH_RATE = 120;
+    private static final int DEFAULT_HIGH_BLOCKING_ZONE_REFRESH_RATE = 55;
+    private static final int DEFAULT_LOW_BLOCKING_ZONE_REFRESH_RATE = 95;
+    private static final int DEFAULT_REFRESH_RATE_IN_HBM_HDR = 90;
+    private static final int DEFAULT_REFRESH_RATE_IN_HBM_SUNLIGHT = 100;
+    private static final int[] LOW_BRIGHTNESS_THRESHOLD_OF_PEAK_REFRESH_RATE = new int[]{10, 30};
+    private static final int[] LOW_AMBIENT_THRESHOLD_OF_PEAK_REFRESH_RATE = new int[]{1, 21};
+    private static final int[] HIGH_BRIGHTNESS_THRESHOLD_OF_PEAK_REFRESH_RATE = new int[]{160};
+    private static final int[] HIGH_AMBIENT_THRESHOLD_OF_PEAK_REFRESH_RATE = new int[]{30000};
+
+    private DisplayDeviceConfig mDisplayDeviceConfig;
+    private static final float ZERO_DELTA = 0.0f;
+    private static final float SMALL_DELTA = 0.0001f;
+    @Mock
+    private Context mContext;
+
+    @Mock
+    private Resources mResources;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        when(mContext.getResources()).thenReturn(mResources);
+        mockDeviceConfigs();
+    }
+
+    @Test
+    public void testConfigValuesFromDisplayConfig() throws IOException {
+        setupDisplayDeviceConfigFromDisplayConfigFile();
+
+        assertEquals(mDisplayDeviceConfig.getName(), "Example Display");
+        assertEquals(mDisplayDeviceConfig.getAmbientHorizonLong(), 5000);
+        assertEquals(mDisplayDeviceConfig.getAmbientHorizonShort(), 50);
+        assertEquals(mDisplayDeviceConfig.getBrightnessRampDecreaseMaxMillis(), 3000);
+        assertEquals(mDisplayDeviceConfig.getBrightnessRampIncreaseMaxMillis(), 2000);
+        assertEquals(mDisplayDeviceConfig.getBrightnessRampFastDecrease(), 0.01f, ZERO_DELTA);
+        assertEquals(mDisplayDeviceConfig.getBrightnessRampFastIncrease(), 0.02f, ZERO_DELTA);
+        assertEquals(mDisplayDeviceConfig.getBrightnessRampSlowIncrease(), 0.04f, ZERO_DELTA);
+        assertEquals(mDisplayDeviceConfig.getBrightnessRampSlowDecrease(), 0.03f, ZERO_DELTA);
+        assertEquals(mDisplayDeviceConfig.getBrightnessDefault(), 0.5f, ZERO_DELTA);
+        assertArrayEquals(mDisplayDeviceConfig.getBrightness(), new float[]{0.0f, 0.62f, 1.0f},
+                ZERO_DELTA);
+        assertArrayEquals(mDisplayDeviceConfig.getNits(), new float[]{2.0f, 500.0f, 800.0f},
+                ZERO_DELTA);
+        assertArrayEquals(mDisplayDeviceConfig.getBacklight(), new float[]{0.0f, 0.62f, 1.0f},
+                ZERO_DELTA);
+        assertEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLightDebounce(), 2000);
+        assertEquals(mDisplayDeviceConfig.getAutoBrightnessDarkeningLightDebounce(), 1000);
+        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(),
+                ZERO_DELTA);
+        assertEquals(20, mDisplayDeviceConfig.getAmbientLuxBrighteningMinThresholdIdle(),
+                ZERO_DELTA);
+        assertEquals(30, mDisplayDeviceConfig.getAmbientLuxDarkeningMinThreshold(), ZERO_DELTA);
+        assertEquals(40, mDisplayDeviceConfig.getAmbientLuxDarkeningMinThresholdIdle(), ZERO_DELTA);
+
+        assertEquals(0.1f, mDisplayDeviceConfig.getScreenBrighteningMinThreshold(), ZERO_DELTA);
+        assertEquals(0.2f, mDisplayDeviceConfig.getScreenBrighteningMinThresholdIdle(), ZERO_DELTA);
+        assertEquals(0.3f, mDisplayDeviceConfig.getScreenDarkeningMinThreshold(), ZERO_DELTA);
+        assertEquals(0.4f, mDisplayDeviceConfig.getScreenDarkeningMinThresholdIdle(), ZERO_DELTA);
+
+        assertArrayEquals(new float[]{0, 0.10f, 0.20f},
+                mDisplayDeviceConfig.getScreenBrighteningLevels(), ZERO_DELTA);
+        assertArrayEquals(new float[]{9, 10, 11},
+                mDisplayDeviceConfig.getScreenBrighteningPercentages(), ZERO_DELTA);
+        assertArrayEquals(new float[]{0, 0.11f, 0.21f},
+                mDisplayDeviceConfig.getScreenDarkeningLevels(), ZERO_DELTA);
+        assertArrayEquals(new float[]{11, 12, 13},
+                mDisplayDeviceConfig.getScreenDarkeningPercentages(), ZERO_DELTA);
+
+        assertArrayEquals(new float[]{0, 100, 200},
+                mDisplayDeviceConfig.getAmbientBrighteningLevels(), ZERO_DELTA);
+        assertArrayEquals(new float[]{13, 14, 15},
+                mDisplayDeviceConfig.getAmbientBrighteningPercentages(), ZERO_DELTA);
+        assertArrayEquals(new float[]{0, 300, 400},
+                mDisplayDeviceConfig.getAmbientDarkeningLevels(), ZERO_DELTA);
+        assertArrayEquals(new float[]{15, 16, 17},
+                mDisplayDeviceConfig.getAmbientDarkeningPercentages(), ZERO_DELTA);
+
+        assertArrayEquals(new float[]{0, 0.12f, 0.22f},
+                mDisplayDeviceConfig.getScreenBrighteningLevelsIdle(), ZERO_DELTA);
+        assertArrayEquals(new float[]{17, 18, 19},
+                mDisplayDeviceConfig.getScreenBrighteningPercentagesIdle(), ZERO_DELTA);
+        assertArrayEquals(new float[]{0, 0.13f, 0.23f},
+                mDisplayDeviceConfig.getScreenDarkeningLevelsIdle(), ZERO_DELTA);
+        assertArrayEquals(new float[]{19, 20, 21},
+                mDisplayDeviceConfig.getScreenDarkeningPercentagesIdle(), ZERO_DELTA);
+
+        assertArrayEquals(new float[]{0, 500, 600},
+                mDisplayDeviceConfig.getAmbientBrighteningLevelsIdle(), ZERO_DELTA);
+        assertArrayEquals(new float[]{21, 22, 23},
+                mDisplayDeviceConfig.getAmbientBrighteningPercentagesIdle(), ZERO_DELTA);
+        assertArrayEquals(new float[]{0, 700, 800},
+                mDisplayDeviceConfig.getAmbientDarkeningLevelsIdle(), ZERO_DELTA);
+        assertArrayEquals(new float[]{23, 24, 25},
+                mDisplayDeviceConfig.getAmbientDarkeningPercentagesIdle(), ZERO_DELTA);
+
+        assertEquals(75, mDisplayDeviceConfig.getDefaultLowBlockingZoneRefreshRate());
+        assertEquals(90, mDisplayDeviceConfig.getDefaultHighBlockingZoneRefreshRate());
+        assertEquals(85, mDisplayDeviceConfig.getDefaultPeakRefreshRate());
+        assertEquals(45, mDisplayDeviceConfig.getDefaultRefreshRate());
+        assertEquals(2, mDisplayDeviceConfig.getRefreshRangeProfiles().size());
+        assertEquals(60, mDisplayDeviceConfig.getRefreshRange("test1").min, SMALL_DELTA);
+        assertEquals(60, mDisplayDeviceConfig.getRefreshRange("test1").max, SMALL_DELTA);
+        assertEquals(80, mDisplayDeviceConfig.getRefreshRange("test2").min, SMALL_DELTA);
+        assertEquals(90, mDisplayDeviceConfig.getRefreshRange("test2").max, SMALL_DELTA);
+        assertEquals(82, mDisplayDeviceConfig.getDefaultRefreshRateInHbmHdr());
+        assertEquals(83, mDisplayDeviceConfig.getDefaultRefreshRateInHbmSunlight());
+        assertArrayEquals(new int[]{45, 55},
+                mDisplayDeviceConfig.getLowDisplayBrightnessThresholds());
+        assertArrayEquals(new int[]{50, 60},
+                mDisplayDeviceConfig.getLowAmbientBrightnessThresholds());
+        assertArrayEquals(new int[]{65, 75},
+                mDisplayDeviceConfig.getHighDisplayBrightnessThresholds());
+        assertArrayEquals(new int[]{70, 80},
+                mDisplayDeviceConfig.getHighAmbientBrightnessThresholds());
+
+        assertEquals("sensor_12345",
+                mDisplayDeviceConfig.getScreenOffBrightnessSensor().type);
+        assertEquals("Sensor 12345",
+                mDisplayDeviceConfig.getScreenOffBrightnessSensor().name);
+
+        assertArrayEquals(new int[]{-1, 10, 20, 30, 40},
+                mDisplayDeviceConfig.getScreenOffBrightnessSensorValueToLux());
+
+        List<DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel>
+                defaultThrottlingLevels = new ArrayList<>();
+        defaultThrottlingLevels.add(
+                new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel(
+                DisplayDeviceConfig.convertThermalStatus(ThermalStatus.light), 0.4f
+        ));
+        defaultThrottlingLevels.add(
+                new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel(
+                DisplayDeviceConfig.convertThermalStatus(ThermalStatus.moderate), 0.3f
+        ));
+        defaultThrottlingLevels.add(
+                new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel(
+                DisplayDeviceConfig.convertThermalStatus(ThermalStatus.severe), 0.2f
+        ));
+        defaultThrottlingLevels.add(
+                new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel(
+                DisplayDeviceConfig.convertThermalStatus(ThermalStatus.critical), 0.1f
+        ));
+        defaultThrottlingLevels.add(
+                new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel(
+                DisplayDeviceConfig.convertThermalStatus(ThermalStatus.emergency), 0.05f
+        ));
+        defaultThrottlingLevels.add(
+                new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel(
+                DisplayDeviceConfig.convertThermalStatus(ThermalStatus.shutdown), 0.025f
+        ));
+
+        DisplayDeviceConfig.ThermalBrightnessThrottlingData defaultThrottlingData =
+                new DisplayDeviceConfig.ThermalBrightnessThrottlingData(defaultThrottlingLevels);
+
+        List<DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel>
+                concurrentThrottlingLevels = new ArrayList<>();
+        concurrentThrottlingLevels.add(
+                new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel(
+                DisplayDeviceConfig.convertThermalStatus(ThermalStatus.light), 0.2f
+        ));
+        concurrentThrottlingLevels.add(
+                new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel(
+                DisplayDeviceConfig.convertThermalStatus(ThermalStatus.moderate), 0.15f
+        ));
+        concurrentThrottlingLevels.add(
+                new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel(
+                DisplayDeviceConfig.convertThermalStatus(ThermalStatus.severe), 0.1f
+        ));
+        concurrentThrottlingLevels.add(
+                new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel(
+                DisplayDeviceConfig.convertThermalStatus(ThermalStatus.critical), 0.05f
+        ));
+        concurrentThrottlingLevels.add(
+                new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel(
+                DisplayDeviceConfig.convertThermalStatus(ThermalStatus.emergency), 0.025f
+        ));
+        concurrentThrottlingLevels.add(
+                new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel(
+                DisplayDeviceConfig.convertThermalStatus(ThermalStatus.shutdown), 0.0125f
+        ));
+        DisplayDeviceConfig.ThermalBrightnessThrottlingData concurrentThrottlingData =
+                new DisplayDeviceConfig.ThermalBrightnessThrottlingData(concurrentThrottlingLevels);
+
+        HashMap<String, DisplayDeviceConfig.ThermalBrightnessThrottlingData> throttlingDataMap =
+                new HashMap<>(2);
+        throttlingDataMap.put("default", defaultThrottlingData);
+        throttlingDataMap.put("concurrent", concurrentThrottlingData);
+
+        assertEquals(throttlingDataMap,
+                mDisplayDeviceConfig.getThermalBrightnessThrottlingDataMapByThrottlingId());
+
+        assertNotNull(mDisplayDeviceConfig.getHostUsiVersion());
+        assertEquals(mDisplayDeviceConfig.getHostUsiVersion().getMajorVersion(), 2);
+        assertEquals(mDisplayDeviceConfig.getHostUsiVersion().getMinorVersion(), 0);
+
+        // Max desired Hdr/SDR ratio upper-bounds the HDR brightness.
+        assertEquals(1.0f,
+                mDisplayDeviceConfig.getHdrBrightnessFromSdr(0.62f, Float.POSITIVE_INFINITY),
+                ZERO_DELTA);
+        assertEquals(0.62f,
+                mDisplayDeviceConfig.getHdrBrightnessFromSdr(0.62f, 1.0f),
+                ZERO_DELTA);
+        assertEquals(0.77787f,
+                mDisplayDeviceConfig.getHdrBrightnessFromSdr(0.62f, 1.25f),
+                SMALL_DELTA);
+
+        // Todo: Add asserts for DensityMapping,
+        // HighBrightnessModeData AmbientLightSensor, RefreshRateLimitations and ProximitySensor.
+    }
+
+    @Test
+    public void testConfigValuesFromConfigResource() {
+        setupDisplayDeviceConfigFromConfigResourceFile();
+        assertNull(mDisplayDeviceConfig.getName());
+        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);
+
+        // Test thresholds
+        assertEquals(0, mDisplayDeviceConfig.getAmbientLuxBrighteningMinThreshold(), ZERO_DELTA);
+        assertEquals(0, mDisplayDeviceConfig.getAmbientLuxBrighteningMinThresholdIdle(),
+                ZERO_DELTA);
+        assertEquals(0, mDisplayDeviceConfig.getAmbientLuxDarkeningMinThreshold(), ZERO_DELTA);
+        assertEquals(0, mDisplayDeviceConfig.getAmbientLuxDarkeningMinThresholdIdle(), ZERO_DELTA);
+
+        assertEquals(0, mDisplayDeviceConfig.getScreenBrighteningMinThreshold(), ZERO_DELTA);
+        assertEquals(0, mDisplayDeviceConfig.getScreenBrighteningMinThresholdIdle(), ZERO_DELTA);
+        assertEquals(0, mDisplayDeviceConfig.getScreenDarkeningMinThreshold(), ZERO_DELTA);
+        assertEquals(0, mDisplayDeviceConfig.getScreenDarkeningMinThresholdIdle(), ZERO_DELTA);
+
+        // screen levels will be considered "old screen brightness scale"
+        // and therefore will divide by 255
+        assertArrayEquals(new float[]{0, 42 / 255f, 43 / 255f},
+                mDisplayDeviceConfig.getScreenBrighteningLevels(), SMALL_DELTA);
+        assertArrayEquals(new float[]{35, 36, 37},
+                mDisplayDeviceConfig.getScreenBrighteningPercentages(), ZERO_DELTA);
+        assertArrayEquals(new float[]{0, 42 / 255f, 43 / 255f},
+                mDisplayDeviceConfig.getScreenDarkeningLevels(), SMALL_DELTA);
+        assertArrayEquals(new float[]{37, 38, 39},
+                mDisplayDeviceConfig.getScreenDarkeningPercentages(), ZERO_DELTA);
+
+        assertArrayEquals(new float[]{0, 30, 31},
+                mDisplayDeviceConfig.getAmbientBrighteningLevels(), ZERO_DELTA);
+        assertArrayEquals(new float[]{27, 28, 29},
+                mDisplayDeviceConfig.getAmbientBrighteningPercentages(), ZERO_DELTA);
+        assertArrayEquals(new float[]{0, 30, 31},
+                mDisplayDeviceConfig.getAmbientDarkeningLevels(), ZERO_DELTA);
+        assertArrayEquals(new float[]{29, 30, 31},
+                mDisplayDeviceConfig.getAmbientDarkeningPercentages(), ZERO_DELTA);
+
+        assertArrayEquals(new float[]{0, 42 / 255f, 43 / 255f},
+                mDisplayDeviceConfig.getScreenBrighteningLevelsIdle(), SMALL_DELTA);
+        assertArrayEquals(new float[]{35, 36, 37},
+                mDisplayDeviceConfig.getScreenBrighteningPercentagesIdle(), ZERO_DELTA);
+        assertArrayEquals(new float[]{0, 42 / 255f, 43 / 255f},
+                mDisplayDeviceConfig.getScreenDarkeningLevelsIdle(), SMALL_DELTA);
+        assertArrayEquals(new float[]{37, 38, 39},
+                mDisplayDeviceConfig.getScreenDarkeningPercentagesIdle(), ZERO_DELTA);
+
+        assertArrayEquals(new float[]{0, 30, 31},
+                mDisplayDeviceConfig.getAmbientBrighteningLevelsIdle(), ZERO_DELTA);
+        assertArrayEquals(new float[]{27, 28, 29},
+                mDisplayDeviceConfig.getAmbientBrighteningPercentagesIdle(), ZERO_DELTA);
+        assertArrayEquals(new float[]{0, 30, 31},
+                mDisplayDeviceConfig.getAmbientDarkeningLevelsIdle(), ZERO_DELTA);
+        assertArrayEquals(new float[]{29, 30, 31},
+                mDisplayDeviceConfig.getAmbientDarkeningPercentagesIdle(), ZERO_DELTA);
+        assertEquals(mDisplayDeviceConfig.getDefaultLowBlockingZoneRefreshRate(),
+                DEFAULT_LOW_BLOCKING_ZONE_REFRESH_RATE);
+        assertEquals(mDisplayDeviceConfig.getDefaultHighBlockingZoneRefreshRate(),
+                DEFAULT_HIGH_BLOCKING_ZONE_REFRESH_RATE);
+        assertEquals(mDisplayDeviceConfig.getDefaultPeakRefreshRate(), DEFAULT_PEAK_REFRESH_RATE);
+        assertEquals(mDisplayDeviceConfig.getDefaultRefreshRate(), DEFAULT_REFRESH_RATE);
+        assertEquals(0, mDisplayDeviceConfig.getRefreshRangeProfiles().size());
+        assertEquals(mDisplayDeviceConfig.getDefaultRefreshRateInHbmSunlight(),
+                DEFAULT_REFRESH_RATE_IN_HBM_SUNLIGHT);
+        assertEquals(mDisplayDeviceConfig.getDefaultRefreshRateInHbmHdr(),
+                DEFAULT_REFRESH_RATE_IN_HBM_HDR);
+        assertArrayEquals(mDisplayDeviceConfig.getLowDisplayBrightnessThresholds(),
+                LOW_BRIGHTNESS_THRESHOLD_OF_PEAK_REFRESH_RATE);
+        assertArrayEquals(mDisplayDeviceConfig.getLowAmbientBrightnessThresholds(),
+                LOW_AMBIENT_THRESHOLD_OF_PEAK_REFRESH_RATE);
+        assertArrayEquals(mDisplayDeviceConfig.getHighDisplayBrightnessThresholds(),
+                HIGH_BRIGHTNESS_THRESHOLD_OF_PEAK_REFRESH_RATE);
+        assertArrayEquals(mDisplayDeviceConfig.getHighAmbientBrightnessThresholds(),
+                HIGH_AMBIENT_THRESHOLD_OF_PEAK_REFRESH_RATE);
+
+        // Todo: Add asserts for ThermalBrightnessThrottlingData, DensityMapping,
+        // HighBrightnessModeData AmbientLightSensor, RefreshRateLimitations and ProximitySensor.
+    }
+
+    @Test
+    public void testThermalRefreshRateThrottlingFromDisplayConfig() throws IOException {
+        setupDisplayDeviceConfigFromDisplayConfigFile();
+
+        SparseArray<SurfaceControl.RefreshRateRange> defaultMap =
+                mDisplayDeviceConfig.getThermalRefreshRateThrottlingData(null);
+        assertNotNull(defaultMap);
+        assertEquals(2, defaultMap.size());
+        assertEquals(30, defaultMap.get(Temperature.THROTTLING_CRITICAL).min, SMALL_DELTA);
+        assertEquals(60, defaultMap.get(Temperature.THROTTLING_CRITICAL).max, SMALL_DELTA);
+        assertEquals(0, defaultMap.get(Temperature.THROTTLING_SHUTDOWN).min, SMALL_DELTA);
+        assertEquals(30, defaultMap.get(Temperature.THROTTLING_SHUTDOWN).max, SMALL_DELTA);
+
+        SparseArray<SurfaceControl.RefreshRateRange> testMap =
+                mDisplayDeviceConfig.getThermalRefreshRateThrottlingData("test");
+        assertNotNull(testMap);
+        assertEquals(1, testMap.size());
+        assertEquals(60, testMap.get(Temperature.THROTTLING_EMERGENCY).min, SMALL_DELTA);
+        assertEquals(90, testMap.get(Temperature.THROTTLING_EMERGENCY).max, SMALL_DELTA);
+    }
+
+    @Test
+    public void testValidLuxThrottling() throws Exception {
+        setupDisplayDeviceConfigFromDisplayConfigFile();
+
+        Map<DisplayDeviceConfig.BrightnessLimitMapType, Map<Float, Float>> luxThrottlingData =
+                mDisplayDeviceConfig.getLuxThrottlingData();
+        assertEquals(2, luxThrottlingData.size());
+
+        Map<Float, Float> adaptiveOnBrightnessPoints = luxThrottlingData.get(
+                DisplayDeviceConfig.BrightnessLimitMapType.ADAPTIVE);
+        assertEquals(2, adaptiveOnBrightnessPoints.size());
+        assertEquals(0.3f, adaptiveOnBrightnessPoints.get(1000f), SMALL_DELTA);
+        assertEquals(0.5f, adaptiveOnBrightnessPoints.get(5000f), SMALL_DELTA);
+
+        Map<Float, Float> adaptiveOffBrightnessPoints = luxThrottlingData.get(
+                DisplayDeviceConfig.BrightnessLimitMapType.DEFAULT);
+        assertEquals(2, adaptiveOffBrightnessPoints.size());
+        assertEquals(0.35f, adaptiveOffBrightnessPoints.get(1500f), SMALL_DELTA);
+        assertEquals(0.55f, adaptiveOffBrightnessPoints.get(5500f), SMALL_DELTA);
+    }
+
+    @Test
+    public void testInvalidLuxThrottling() throws Exception {
+        setupDisplayDeviceConfigFromDisplayConfigFile(getContent(getInvalidLuxThrottling()));
+
+        Map<DisplayDeviceConfig.BrightnessLimitMapType, Map<Float, Float>> luxThrottlingData =
+                mDisplayDeviceConfig.getLuxThrottlingData();
+        assertEquals(1, luxThrottlingData.size());
+
+        Map<Float, Float> adaptiveOnBrightnessPoints = luxThrottlingData.get(
+                DisplayDeviceConfig.BrightnessLimitMapType.ADAPTIVE);
+        assertEquals(1, adaptiveOnBrightnessPoints.size());
+        assertEquals(0.3f, adaptiveOnBrightnessPoints.get(1000f), SMALL_DELTA);
+    }
+
+    private String getValidLuxThrottling() {
+        return "<luxThrottling>\n"
+                + "    <brightnessLimitMap>\n"
+                + "        <type>adaptive</type>\n"
+                + "        <map>\n"
+                + "            <point>"
+                + "                <first>1000</first>\n"
+                + "                <second>0.3</second>\n"
+                + "            </point>"
+                + "            <point>"
+                + "                <first>5000</first>\n"
+                + "                <second>0.5</second>\n"
+                + "            </point>"
+                + "        </map>\n"
+                + "    </brightnessLimitMap>\n"
+                + "    <brightnessLimitMap>\n"
+                + "        <type>default</type>\n"
+                + "        <map>\n"
+                + "            <point>"
+                + "                <first>1500</first>\n"
+                + "                <second>0.35</second>\n"
+                + "            </point>"
+                + "            <point>"
+                + "                <first>5500</first>\n"
+                + "                <second>0.55</second>\n"
+                + "            </point>"
+                + "        </map>\n"
+                + "    </brightnessLimitMap>\n"
+                + "</luxThrottling>";
+    }
+
+    private String getInvalidLuxThrottling() {
+        return "<luxThrottling>\n"
+                + "    <brightnessLimitMap>\n"
+                + "        <type>adaptive</type>\n"
+                + "        <map>\n"
+                + "            <point>"
+                + "                <first>1000</first>\n"
+                + "                <second>0.3</second>\n"
+                + "            </point>"
+                + "            <point>" // second > hbm.transitionPoint, skipped
+                + "                <first>1500</first>\n"
+                + "                <second>0.9</second>\n"
+                + "            </point>"
+                + "            <point>" // same lux value, skipped
+                + "                <first>1000</first>\n"
+                + "                <second>0.5</second>\n"
+                + "            </point>"
+                + "        </map>\n"
+                + "    </brightnessLimitMap>\n"
+                + "    <brightnessLimitMap>\n" // Same type, skipped
+                + "        <type>adaptive</type>\n"
+                + "        <map>\n"
+                + "            <point>"
+                + "                <first>2000</first>\n"
+                + "                <second>0.35</second>\n"
+                + "            </point>"
+                + "            <point>"
+                + "                <first>6000</first>\n"
+                + "                <second>0.55</second>\n"
+                + "            </point>"
+                + "        </map>\n"
+                + "    </brightnessLimitMap>\n"
+                + "    <brightnessLimitMap>\n" // Invalid points only, skipped
+                + "        <type>default</type>\n"
+                + "        <map>\n"
+                + "            <point>"
+                + "                <first>2500</first>\n"
+                + "                <second>0.99</second>\n"
+                + "            </point>"
+                + "        </map>\n"
+                + "    </brightnessLimitMap>\n"
+                + "</luxThrottling>";
+    }
+
+    private String getRefreshThermalThrottlingMaps() {
+        return "<refreshRateThrottlingMap>\n"
+               + "    <refreshRateThrottlingPoint>\n"
+               + "        <thermalStatus>critical</thermalStatus>\n"
+               + "        <refreshRateRange>\n"
+               + "            <minimum>30</minimum>\n"
+               + "            <maximum>60</maximum>\n"
+               + "        </refreshRateRange>\n"
+               + "    </refreshRateThrottlingPoint>\n"
+               + "    <refreshRateThrottlingPoint>\n"
+               + "        <thermalStatus>shutdown</thermalStatus>\n"
+               + "        <refreshRateRange>\n"
+               + "            <minimum>0</minimum>\n"
+               + "            <maximum>30</maximum>\n"
+               + "        </refreshRateRange>\n"
+               + "    </refreshRateThrottlingPoint>\n"
+               + "</refreshRateThrottlingMap>\n"
+               + "<refreshRateThrottlingMap id=\"test\">\n"
+               + "    <refreshRateThrottlingPoint>\n"
+               + "        <thermalStatus>emergency</thermalStatus>\n"
+               + "        <refreshRateRange>\n"
+               + "            <minimum>60</minimum>\n"
+               + "            <maximum>90</maximum>\n"
+               + "        </refreshRateRange>\n"
+               + "    </refreshRateThrottlingPoint>\n"
+               + "</refreshRateThrottlingMap>\n";
+    }
+
+    private String getContent() {
+        return getContent(getValidLuxThrottling());
+    }
+
+    private String getContent(String brightnessCapConfig) {
+        return "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
+                + "<displayConfiguration>\n"
+                +   "<name>Example Display</name>"
+                +   "<screenBrightnessMap>\n"
+                +       "<point>\n"
+                +           "<value>0.0</value>\n"
+                +           "<nits>2.0</nits>\n"
+                +       "</point>\n"
+                +       "<point>\n"
+                +           "<value>0.62</value>\n"
+                +           "<nits>500.0</nits>\n"
+                +       "</point>\n"
+                +       "<point>\n"
+                +           "<value>1.0</value>\n"
+                +           "<nits>800.0</nits>\n"
+                +       "</point>\n"
+                +   "</screenBrightnessMap>\n"
+                +   "<autoBrightness>\n"
+                +       "<brighteningLightDebounceMillis>2000</brighteningLightDebounceMillis>\n"
+                +       "<darkeningLightDebounceMillis>1000</darkeningLightDebounceMillis>\n"
+                +       "<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"
+                +   "</autoBrightness>\n"
+                +   "<highBrightnessMode enabled=\"true\">\n"
+                +       "<transitionPoint>0.62</transitionPoint>\n"
+                +       "<minimumLux>10000</minimumLux>\n"
+                +       "<timing>\n"
+                +           "<!-- allow for 5 minutes out of every 30 minutes -->\n"
+                +           "<timeWindowSecs>1800</timeWindowSecs>\n"
+                +           "<timeMaxSecs>300</timeMaxSecs>\n"
+                +           "<timeMinSecs>60</timeMinSecs>\n"
+                +       "</timing>\n"
+                +       "<refreshRate>\n"
+                +           "<minimum>120</minimum>\n"
+                +           "<maximum>120</maximum>\n"
+                +       "</refreshRate>\n"
+                +       "<thermalStatusLimit>light</thermalStatusLimit>\n"
+                +       "<allowInLowPowerMode>false</allowInLowPowerMode>\n"
+                +       "<sdrHdrRatioMap>\n"
+                +            "<point>\n"
+                +                "<sdrNits>2.000</sdrNits>\n"
+                +                "<hdrRatio>4.000</hdrRatio>\n"
+                +            "</point>\n"
+                +            "<point>\n"
+                +                "<sdrNits>500.0</sdrNits>\n"
+                +                "<hdrRatio>1.6</hdrRatio>\n"
+                +            "</point>\n"
+                +       "</sdrHdrRatioMap>\n"
+                +   "</highBrightnessMode>\n"
+                + brightnessCapConfig
+                +   "<screenOffBrightnessSensor>\n"
+                +       "<type>sensor_12345</type>\n"
+                +       "<name>Sensor 12345</name>\n"
+                +   "</screenOffBrightnessSensor>\n"
+                +   "<ambientBrightnessChangeThresholds>\n"
+                +       "<brighteningThresholds>\n"
+                +           "<minimum>10</minimum>\n"
+                +           "<brightnessThresholdPoints>\n"
+                +               "<brightnessThresholdPoint>\n"
+                +                   "<threshold>0</threshold><percentage>13</percentage>\n"
+                +               "</brightnessThresholdPoint>\n"
+                +               "<brightnessThresholdPoint>\n"
+                +                   "<threshold>100</threshold><percentage>14</percentage>\n"
+                +               "</brightnessThresholdPoint>\n"
+                +               "<brightnessThresholdPoint>\n"
+                +                   "<threshold>200</threshold><percentage>15</percentage>\n"
+                +               "</brightnessThresholdPoint>\n"
+                +           "</brightnessThresholdPoints>\n"
+                +       "</brighteningThresholds>\n"
+                +       "<darkeningThresholds>\n"
+                +           "<minimum>30</minimum>\n"
+                +           "<brightnessThresholdPoints>\n"
+                +               "<brightnessThresholdPoint>\n"
+                +                   "<threshold>0</threshold><percentage>15</percentage>\n"
+                +               "</brightnessThresholdPoint>\n"
+                +               "<brightnessThresholdPoint>\n"
+                +                   "<threshold>300</threshold><percentage>16</percentage>\n"
+                +               "</brightnessThresholdPoint>\n"
+                +               "<brightnessThresholdPoint>\n"
+                +                   "<threshold>400</threshold><percentage>17</percentage>\n"
+                +               "</brightnessThresholdPoint>\n"
+                +           "</brightnessThresholdPoints>\n"
+                +       "</darkeningThresholds>\n"
+                +   "</ambientBrightnessChangeThresholds>\n"
+                +   "<displayBrightnessChangeThresholds>\n"
+                +       "<brighteningThresholds>\n"
+                +           "<minimum>0.1</minimum>\n"
+                +           "<brightnessThresholdPoints>\n"
+                +               "<brightnessThresholdPoint>\n"
+                +                   "<threshold>0</threshold>\n"
+                +                   "<percentage>9</percentage>\n"
+                +               "</brightnessThresholdPoint>\n"
+                +               "<brightnessThresholdPoint>\n"
+                +                   "<threshold>0.10</threshold>\n"
+                +                   "<percentage>10</percentage>\n"
+                +               "</brightnessThresholdPoint>\n"
+                +               "<brightnessThresholdPoint>\n"
+                +                   "<threshold>0.20</threshold>\n"
+                +                   "<percentage>11</percentage>\n"
+                +               "</brightnessThresholdPoint>\n"
+                +           "</brightnessThresholdPoints>\n"
+                +       "</brighteningThresholds>\n"
+                +       "<darkeningThresholds>\n"
+                +           "<minimum>0.3</minimum>\n"
+                +           "<brightnessThresholdPoints>\n"
+                +               "<brightnessThresholdPoint>\n"
+                +                   "<threshold>0</threshold><percentage>11</percentage>\n"
+                +               "</brightnessThresholdPoint>\n"
+                +               "<brightnessThresholdPoint>\n"
+                +                   "<threshold>0.11</threshold><percentage>12</percentage>\n"
+                +               "</brightnessThresholdPoint>\n"
+                +               "<brightnessThresholdPoint>\n"
+                +                   "<threshold>0.21</threshold><percentage>13</percentage>\n"
+                +               "</brightnessThresholdPoint>\n"
+                +           "</brightnessThresholdPoints>\n"
+                +       "</darkeningThresholds>\n"
+                +   "</displayBrightnessChangeThresholds>\n"
+                +   "<ambientBrightnessChangeThresholdsIdle>\n"
+                +       "<brighteningThresholds>\n"
+                +           "<minimum>20</minimum>\n"
+                +           "<brightnessThresholdPoints>\n"
+                +               "<brightnessThresholdPoint>\n"
+                +                   "<threshold>0</threshold><percentage>21</percentage>\n"
+                +               "</brightnessThresholdPoint>\n"
+                +               "<brightnessThresholdPoint>\n"
+                +                   "<threshold>500</threshold><percentage>22</percentage>\n"
+                +               "</brightnessThresholdPoint>\n"
+                +               "<brightnessThresholdPoint>\n"
+                +                   "<threshold>600</threshold><percentage>23</percentage>\n"
+                +               "</brightnessThresholdPoint>\n"
+                +           "</brightnessThresholdPoints>\n"
+                +       "</brighteningThresholds>\n"
+                +       "<darkeningThresholds>\n"
+                +           "<minimum>40</minimum>\n"
+                +           "<brightnessThresholdPoints>\n"
+                +               "<brightnessThresholdPoint>\n"
+                +                   "<threshold>0</threshold><percentage>23</percentage>\n"
+                +               "</brightnessThresholdPoint>\n"
+                +               "<brightnessThresholdPoint>\n"
+                +                   "<threshold>700</threshold><percentage>24</percentage>\n"
+                +               "</brightnessThresholdPoint>\n"
+                +               "<brightnessThresholdPoint>\n"
+                +                   "<threshold>800</threshold><percentage>25</percentage>\n"
+                +               "</brightnessThresholdPoint>\n"
+                +           "</brightnessThresholdPoints>\n"
+                +       "</darkeningThresholds>\n"
+                +   "</ambientBrightnessChangeThresholdsIdle>\n"
+                +   "<displayBrightnessChangeThresholdsIdle>\n"
+                +       "<brighteningThresholds>\n"
+                +           "<minimum>0.2</minimum>\n"
+                +           "<brightnessThresholdPoints>\n"
+                +               "<brightnessThresholdPoint>\n"
+                +                   "<threshold>0</threshold><percentage>17</percentage>\n"
+                +               "</brightnessThresholdPoint>\n"
+                +               "<brightnessThresholdPoint>\n"
+                +                   "<threshold>0.12</threshold><percentage>18</percentage>\n"
+                +               "</brightnessThresholdPoint>\n"
+                +               "<brightnessThresholdPoint>\n"
+                +                   "<threshold>0.22</threshold><percentage>19</percentage>\n"
+                +               "</brightnessThresholdPoint>\n"
+                +           "</brightnessThresholdPoints>\n"
+                +       "</brighteningThresholds>\n"
+                +       "<darkeningThresholds>\n"
+                +           "<minimum>0.4</minimum>\n"
+                +           "<brightnessThresholdPoints>\n"
+                +               "<brightnessThresholdPoint>\n"
+                +                   "<threshold>0</threshold><percentage>19</percentage>\n"
+                +               "</brightnessThresholdPoint>\n"
+                +               "<brightnessThresholdPoint>\n"
+                +                   "<threshold>0.13</threshold><percentage>20</percentage>\n"
+                +               "</brightnessThresholdPoint>\n"
+                +               "<brightnessThresholdPoint>\n"
+                +                   "<threshold>0.23</threshold><percentage>21</percentage>\n"
+                +               "</brightnessThresholdPoint>\n"
+                +           "</brightnessThresholdPoints>\n"
+                +       "</darkeningThresholds>\n"
+                +   "</displayBrightnessChangeThresholdsIdle>\n"
+                +   "<screenBrightnessRampFastDecrease>0.01</screenBrightnessRampFastDecrease> "
+                +   "<screenBrightnessRampFastIncrease>0.02</screenBrightnessRampFastIncrease>  "
+                +   "<screenBrightnessRampSlowDecrease>0.03</screenBrightnessRampSlowDecrease>"
+                +   "<screenBrightnessRampSlowIncrease>0.04</screenBrightnessRampSlowIncrease>"
+                +   "<screenBrightnessRampIncreaseMaxMillis>"
+                +       "2000"
+                +   "</screenBrightnessRampIncreaseMaxMillis>"
+                +   "<screenBrightnessRampDecreaseMaxMillis>"
+                +       "3000"
+                +   "</screenBrightnessRampDecreaseMaxMillis>"
+                +   "<ambientLightHorizonLong>5000</ambientLightHorizonLong>\n"
+                +   "<ambientLightHorizonShort>50</ambientLightHorizonShort>\n"
+                +   "<screenBrightnessRampIncreaseMaxMillis>"
+                +       "2000"
+                +   "</screenBrightnessRampIncreaseMaxMillis>\n"
+                +   "<thermalThrottling>\n"
+                +       "<brightnessThrottlingMap>\n"
+                +           "<brightnessThrottlingPoint>\n"
+                +               "<thermalStatus>light</thermalStatus>\n"
+                +               "<brightness>0.4</brightness>\n"
+                +           "</brightnessThrottlingPoint>\n"
+                +           "<brightnessThrottlingPoint>\n"
+                +               "<thermalStatus>moderate</thermalStatus>\n"
+                +               "<brightness>0.3</brightness>\n"
+                +           "</brightnessThrottlingPoint>\n"
+                +           "<brightnessThrottlingPoint>\n"
+                +               "<thermalStatus>severe</thermalStatus>\n"
+                +               "<brightness>0.2</brightness>\n"
+                +           "</brightnessThrottlingPoint>\n"
+                +           "<brightnessThrottlingPoint>\n"
+                +               "<thermalStatus>critical</thermalStatus>\n"
+                +               "<brightness>0.1</brightness>\n"
+                +           "</brightnessThrottlingPoint>\n"
+                +           "<brightnessThrottlingPoint>\n"
+                +               "<thermalStatus>emergency</thermalStatus>\n"
+                +               "<brightness>0.05</brightness>\n"
+                +           "</brightnessThrottlingPoint>\n"
+                +           "<brightnessThrottlingPoint>\n"
+                +               "<thermalStatus>shutdown</thermalStatus>\n"
+                +               "<brightness>0.025</brightness>\n"
+                +           "</brightnessThrottlingPoint>\n"
+                +       "</brightnessThrottlingMap>\n"
+                +       "<brightnessThrottlingMap id=\"concurrent\">\n"
+                +           "<brightnessThrottlingPoint>\n"
+                +               "<thermalStatus>light</thermalStatus>\n"
+                +               "<brightness>0.2</brightness>\n"
+                +           "</brightnessThrottlingPoint>\n"
+                +           "<brightnessThrottlingPoint>\n"
+                +               "<thermalStatus>moderate</thermalStatus>\n"
+                +               "<brightness>0.15</brightness>\n"
+                +           "</brightnessThrottlingPoint>\n"
+                +           "<brightnessThrottlingPoint>\n"
+                +               "<thermalStatus>severe</thermalStatus>\n"
+                +               "<brightness>0.1</brightness>\n"
+                +           "</brightnessThrottlingPoint>\n"
+                +           "<brightnessThrottlingPoint>\n"
+                +               "<thermalStatus>critical</thermalStatus>\n"
+                +               "<brightness>0.05</brightness>\n"
+                +           "</brightnessThrottlingPoint>\n"
+                +           "<brightnessThrottlingPoint>\n"
+                +               "<thermalStatus>emergency</thermalStatus>\n"
+                +               "<brightness>0.025</brightness>\n"
+                +           "</brightnessThrottlingPoint>\n"
+                +           "<brightnessThrottlingPoint>\n"
+                +               "<thermalStatus>shutdown</thermalStatus>\n"
+                +               "<brightness>0.0125</brightness>\n"
+                +           "</brightnessThrottlingPoint>\n"
+                +       "</brightnessThrottlingMap>\n"
+                +  getRefreshThermalThrottlingMaps()
+                +   "</thermalThrottling>\n"
+                +   "<refreshRate>\n"
+                +       "<defaultRefreshRate>45</defaultRefreshRate>\n"
+                +       "<defaultPeakRefreshRate>85</defaultPeakRefreshRate>\n"
+                +       "<refreshRateZoneProfiles>"
+                +           "<refreshRateZoneProfile id=\"test1\">"
+                +               "<refreshRateRange>\n"
+                +                   "<minimum>60</minimum>\n"
+                +                   "<maximum>60</maximum>\n"
+                +               "</refreshRateRange>\n"
+                +           "</refreshRateZoneProfile>\n"
+                +           "<refreshRateZoneProfile id=\"test2\">"
+                +               "<refreshRateRange>\n"
+                +                   "<minimum>80</minimum>\n"
+                +                   "<maximum>90</maximum>\n"
+                +               "</refreshRateRange>\n"
+                +           "</refreshRateZoneProfile>\n"
+                +       "</refreshRateZoneProfiles>"
+                +       "<defaultRefreshRateInHbmHdr>82</defaultRefreshRateInHbmHdr>\n"
+                +       "<defaultRefreshRateInHbmSunlight>83</defaultRefreshRateInHbmSunlight>\n"
+                +       "<lowerBlockingZoneConfigs>\n"
+                +           "<defaultRefreshRate>75</defaultRefreshRate>\n"
+                +           "<blockingZoneThreshold>\n"
+                +               "<displayBrightnessPoint>\n"
+                +                   "<lux>50</lux>\n"
+                // This number will be rounded to integer when read by the system
+                +                   "<nits>45.3</nits>\n"
+                +               "</displayBrightnessPoint>\n"
+                +               "<displayBrightnessPoint>\n"
+                +                   "<lux>60</lux>\n"
+                // This number will be rounded to integer when read by the system
+                +                   "<nits>55.2</nits>\n"
+                +               "</displayBrightnessPoint>\n"
+                +           "</blockingZoneThreshold>\n"
+                +       "</lowerBlockingZoneConfigs>\n"
+                +       "<higherBlockingZoneConfigs>\n"
+                +           "<defaultRefreshRate>90</defaultRefreshRate>\n"
+                +           "<blockingZoneThreshold>\n"
+                +               "<displayBrightnessPoint>\n"
+                +                   "<lux>70</lux>\n"
+                // This number will be rounded to integer when read by the system
+                +                   "<nits>65.6</nits>\n"
+                +               "</displayBrightnessPoint>\n"
+                +               "<displayBrightnessPoint>\n"
+                +                   "<lux>80</lux>\n"
+                // This number will be rounded to integer when read by the system
+                +                   "<nits>75</nits>\n"
+                +               "</displayBrightnessPoint>\n"
+                +           "</blockingZoneThreshold>\n"
+                +       "</higherBlockingZoneConfigs>\n"
+                +   "</refreshRate>\n"
+                +   "<screenOffBrightnessSensorValueToLux>\n"
+                +       "<item>-1</item>\n"
+                +       "<item>10</item>\n"
+                +       "<item>20</item>\n"
+                +       "<item>30</item>\n"
+                +       "<item>40</item>\n"
+                +   "</screenOffBrightnessSensorValueToLux>\n"
+                +   "<usiVersion>\n"
+                +       "<majorVersion>2</majorVersion>\n"
+                +       "<minorVersion>0</minorVersion>\n"
+                +   "</usiVersion>\n"
+                + "</displayConfiguration>\n";
+    }
+
+    private void mockDeviceConfigs() {
+        when(mResources.getFloat(com.android.internal.R.dimen
+                .config_screenBrightnessSettingDefaultFloat)).thenReturn(0.5f);
+        when(mResources.getFloat(com.android.internal.R.dimen
+                .config_screenBrightnessSettingMaximumFloat)).thenReturn(1.0f);
+    }
+
+    private void setupDisplayDeviceConfigFromDisplayConfigFile() throws IOException {
+        setupDisplayDeviceConfigFromDisplayConfigFile(getContent());
+    }
+
+    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.initFromFile(tempFile.toFile());
+    }
+
+    private void setupDisplayDeviceConfigFromConfigResourceFile() {
+        TypedArray screenBrightnessNits = createFloatTypedArray(new float[]{2.0f, 250.0f, 650.0f});
+        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};
+        when(mResources.getIntArray(
+                com.android.internal.R.array.config_autoBrightnessLevels))
+                .thenReturn(screenBrightnessLevelLux);
+
+        // Thresholds
+        // Config.xml requires the levels arrays to be of length N and the thresholds arrays to be
+        // of length N+1
+        when(mResources.getIntArray(com.android.internal.R.array.config_ambientThresholdLevels))
+                .thenReturn(new int[]{30, 31});
+        when(mResources.getIntArray(com.android.internal.R.array.config_screenThresholdLevels))
+                .thenReturn(new int[]{42, 43});
+        when(mResources.getIntArray(
+                com.android.internal.R.array.config_ambientBrighteningThresholds))
+                .thenReturn(new int[]{270, 280, 290});
+        when(mResources.getIntArray(com.android.internal.R.array.config_ambientDarkeningThresholds))
+                .thenReturn(new int[]{290, 300, 310});
+        when(mResources.getIntArray(R.array.config_screenBrighteningThresholds))
+                .thenReturn(new int[]{350, 360, 370});
+        when(mResources.getIntArray(R.array.config_screenDarkeningThresholds))
+                .thenReturn(new int[]{370, 380, 390});
+
+        // Configs related to refresh rates and blocking zones
+        when(mResources.getInteger(R.integer.config_defaultPeakRefreshRate))
+                .thenReturn(DEFAULT_PEAK_REFRESH_RATE);
+        when(mResources.getInteger(R.integer.config_defaultRefreshRate))
+                .thenReturn(DEFAULT_REFRESH_RATE);
+        when(mResources.getInteger(R.integer.config_fixedRefreshRateInHighZone))
+            .thenReturn(DEFAULT_HIGH_BLOCKING_ZONE_REFRESH_RATE);
+        when(mResources.getInteger(R.integer.config_defaultRefreshRateInZone))
+            .thenReturn(DEFAULT_LOW_BLOCKING_ZONE_REFRESH_RATE);
+        when(mResources.getIntArray(R.array.config_brightnessThresholdsOfPeakRefreshRate))
+                .thenReturn(LOW_BRIGHTNESS_THRESHOLD_OF_PEAK_REFRESH_RATE);
+        when(mResources.getIntArray(R.array.config_ambientThresholdsOfPeakRefreshRate))
+                .thenReturn(LOW_AMBIENT_THRESHOLD_OF_PEAK_REFRESH_RATE);
+        when(mResources.getIntArray(
+                R.array.config_highDisplayBrightnessThresholdsOfFixedRefreshRate))
+                .thenReturn(HIGH_BRIGHTNESS_THRESHOLD_OF_PEAK_REFRESH_RATE);
+        when(mResources.getIntArray(
+                R.array.config_highAmbientBrightnessThresholdsOfFixedRefreshRate))
+                .thenReturn(HIGH_AMBIENT_THRESHOLD_OF_PEAK_REFRESH_RATE);
+        when(mResources.getInteger(
+            R.integer.config_defaultRefreshRateInHbmHdr))
+            .thenReturn(DEFAULT_REFRESH_RATE_IN_HBM_HDR);
+        when(mResources.getInteger(
+            R.integer.config_defaultRefreshRateInHbmSunlight))
+            .thenReturn(DEFAULT_REFRESH_RATE_IN_HBM_SUNLIGHT);
+
+        mDisplayDeviceConfig = DisplayDeviceConfig.create(mContext, true);
+    }
+
+    private TypedArray createFloatTypedArray(float[] vals) {
+        TypedArray mockArray = mock(TypedArray.class);
+        when(mockArray.length()).thenAnswer(invocation -> {
+            return vals.length;
+        });
+        when(mockArray.getFloat(anyInt(), anyFloat())).thenAnswer(invocation -> {
+            final float def = (float) invocation.getArguments()[1];
+            if (vals == null) {
+                return def;
+            }
+            int idx = (int) invocation.getArguments()[0];
+            if (idx >= 0 && idx < vals.length) {
+                return vals[idx];
+            } else {
+                return def;
+            }
+        });
+        return mockArray;
+    }
+}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
new file mode 100644
index 0000000..d16c9c5
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -0,0 +1,2319 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display;
+
+import static android.Manifest.permission.ADD_ALWAYS_UNLOCKED_DISPLAY;
+import static android.Manifest.permission.ADD_TRUSTED_DISPLAY;
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED;
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP;
+import static android.view.ContentRecordingSession.RECORD_CONTENT_DISPLAY;
+import static android.view.ContentRecordingSession.RECORD_CONTENT_TASK;
+
+import static com.android.server.display.VirtualDisplayAdapter.UNIQUE_ID_PREFIX;
+
+import static com.google.common.truth.Truth.assertThat;
+
+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.assertNull;
+import static org.junit.Assert.assertThrows;
+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.eq;
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.PropertyInvalidatedCache;
+import android.companion.virtual.IVirtualDevice;
+import android.companion.virtual.IVirtualDeviceManager;
+import android.companion.virtual.VirtualDeviceManager;
+import android.compat.testing.PlatformCompatChangeRule;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.graphics.Insets;
+import android.graphics.Rect;
+import android.hardware.Sensor;
+import android.hardware.SensorManager;
+import android.hardware.display.BrightnessConfiguration;
+import android.hardware.display.Curve;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayManagerGlobal;
+import android.hardware.display.DisplayManagerInternal;
+import android.hardware.display.DisplayViewport;
+import android.hardware.display.DisplayedContentSample;
+import android.hardware.display.DisplayedContentSamplingAttributes;
+import android.hardware.display.HdrConversionMode;
+import android.hardware.display.IDisplayManagerCallback;
+import android.hardware.display.IVirtualDisplayCallback;
+import android.hardware.display.VirtualDisplayConfig;
+import android.media.projection.IMediaProjection;
+import android.media.projection.IMediaProjectionManager;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.MessageQueue;
+import android.os.Process;
+import android.os.RemoteException;
+import android.view.ContentRecordingSession;
+import android.view.Display;
+import android.view.DisplayCutout;
+import android.view.DisplayEventReceiver;
+import android.view.DisplayInfo;
+import android.view.Surface;
+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;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.R;
+import com.android.server.LocalServices;
+import com.android.server.SystemService;
+import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
+import com.android.server.display.DisplayManagerService.DeviceStateListener;
+import com.android.server.display.DisplayManagerService.SyncRoot;
+import com.android.server.input.InputManagerInternal;
+import com.android.server.lights.LightsManager;
+import com.android.server.sensors.SensorManagerInternal;
+import com.android.server.wm.WindowManagerInternal;
+
+import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
+import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.time.Duration;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.LongStream;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class DisplayManagerServiceTest {
+    private static final int MSG_REGISTER_DEFAULT_DISPLAY_ADAPTERS = 1;
+    private static final long SHORT_DEFAULT_DISPLAY_TIMEOUT_MILLIS = 10;
+
+    private static final float FLOAT_TOLERANCE = 0.01f;
+
+    private static final String VIRTUAL_DISPLAY_NAME = "Test Virtual Display";
+    private static final String PACKAGE_NAME = "com.android.frameworks.displayservicetests";
+    private static final long STANDARD_DISPLAY_EVENTS = DisplayManager.EVENT_FLAG_DISPLAY_ADDED
+                    | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
+                    | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED;
+
+    @Rule
+    public TestRule compatChangeRule = new PlatformCompatChangeRule();
+
+    private Context mContext;
+
+    private int mHdrConversionMode;
+
+    private int mPreferredHdrOutputType;
+
+    private final DisplayManagerService.Injector mShortMockedInjector =
+            new DisplayManagerService.Injector() {
+                @Override
+                VirtualDisplayAdapter getVirtualDisplayAdapter(SyncRoot syncRoot,
+                        Context context, Handler handler, DisplayAdapter.Listener listener) {
+                    return mMockVirtualDisplayAdapter;
+                }
+
+                @Override
+                LocalDisplayAdapter getLocalDisplayAdapter(SyncRoot syncRoot, Context context,
+                        Handler handler, DisplayAdapter.Listener displayAdapterListener) {
+                    return new LocalDisplayAdapter(syncRoot, context, handler,
+                            displayAdapterListener, new LocalDisplayAdapter.Injector() {
+                        @Override
+                        public LocalDisplayAdapter.SurfaceControlProxy getSurfaceControlProxy() {
+                            return mSurfaceControlProxy;
+                        }
+                    });
+                }
+
+                @Override
+                long getDefaultDisplayDelayTimeout() {
+                    return SHORT_DEFAULT_DISPLAY_TIMEOUT_MILLIS;
+                }
+            };
+
+    class BasicInjector extends DisplayManagerService.Injector {
+        @Override
+        IMediaProjectionManager getProjectionService() {
+            return mMockProjectionService;
+        }
+
+        @Override
+        VirtualDisplayAdapter getVirtualDisplayAdapter(SyncRoot syncRoot, Context context,
+                Handler handler, DisplayAdapter.Listener displayAdapterListener) {
+            return new VirtualDisplayAdapter(syncRoot, context, handler, displayAdapterListener,
+                    new VirtualDisplayAdapter.SurfaceControlDisplayFactory() {
+                        @Override
+                        public IBinder createDisplay(String name, boolean secure,
+                                float requestedRefreshRate) {
+                            return mMockDisplayToken;
+                        }
+
+                        @Override
+                        public void destroyDisplay(IBinder displayToken) {
+                        }
+                    });
+        }
+
+        @Override
+        LocalDisplayAdapter getLocalDisplayAdapter(SyncRoot syncRoot, Context context,
+                Handler handler, DisplayAdapter.Listener displayAdapterListener) {
+            return new LocalDisplayAdapter(syncRoot, context, handler,
+                    displayAdapterListener, new LocalDisplayAdapter.Injector() {
+                        @Override
+                        public LocalDisplayAdapter.SurfaceControlProxy getSurfaceControlProxy() {
+                            return mSurfaceControlProxy;
+                        }
+                    });
+        }
+
+        @Override
+        int setHdrConversionMode(int conversionMode, int preferredHdrOutputType,
+                int[] autoHdrTypes) {
+            mHdrConversionMode = conversionMode;
+            mPreferredHdrOutputType = preferredHdrOutputType;
+            return Display.HdrCapabilities.HDR_TYPE_INVALID;
+        }
+
+        @Override
+        int[] getSupportedHdrOutputTypes() {
+            return new int[]{};
+        }
+
+        @Override
+        int[] getHdrOutputTypesWithLatency() {
+            return new int[]{Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION};
+        }
+
+        boolean getHdrOutputConversionSupport() {
+            return true;
+        }
+    }
+
+    private final DisplayManagerService.Injector mBasicInjector = new BasicInjector();
+
+    @Mock IMediaProjectionManager mMockProjectionService;
+    @Mock IVirtualDeviceManager mIVirtualDeviceManager;
+    @Mock InputManagerInternal mMockInputManagerInternal;
+    @Mock VirtualDeviceManagerInternal mMockVirtualDeviceManagerInternal;
+    @Mock IVirtualDisplayCallback.Stub mMockAppToken;
+    @Mock IVirtualDisplayCallback.Stub mMockAppToken2;
+    @Mock WindowManagerInternal mMockWindowManagerInternal;
+    @Mock LightsManager mMockLightsManager;
+    @Mock VirtualDisplayAdapter mMockVirtualDisplayAdapter;
+    @Mock LocalDisplayAdapter.SurfaceControlProxy mSurfaceControlProxy;
+    @Mock IBinder mMockDisplayToken;
+    @Mock SensorManagerInternal mMockSensorManagerInternal;
+
+    @Mock SensorManager mSensorManager;
+
+    @Mock DisplayDeviceConfig mMockDisplayDeviceConfig;
+
+    @Captor ArgumentCaptor<ContentRecordingSession> mContentRecordingSessionCaptor;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        LocalServices.removeServiceForTest(InputManagerInternal.class);
+        LocalServices.addService(InputManagerInternal.class, mMockInputManagerInternal);
+        LocalServices.removeServiceForTest(WindowManagerInternal.class);
+        LocalServices.addService(WindowManagerInternal.class, mMockWindowManagerInternal);
+        LocalServices.removeServiceForTest(LightsManager.class);
+        LocalServices.addService(LightsManager.class, mMockLightsManager);
+        LocalServices.removeServiceForTest(SensorManagerInternal.class);
+        LocalServices.addService(SensorManagerInternal.class, mMockSensorManagerInternal);
+        LocalServices.removeServiceForTest(VirtualDeviceManagerInternal.class);
+        LocalServices.addService(
+                VirtualDeviceManagerInternal.class, mMockVirtualDeviceManagerInternal);
+        // TODO: b/287945043
+        mContext = spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
+
+        VirtualDeviceManager vdm = new VirtualDeviceManager(mIVirtualDeviceManager, mContext);
+        when(mContext.getSystemService(VirtualDeviceManager.class)).thenReturn(vdm);
+        // Disable binder caches in this process.
+        PropertyInvalidatedCache.disableForTestMode();
+        setUpDisplay();
+    }
+
+    private void setUpDisplay() {
+        long[] ids = new long[] {100};
+        when(mSurfaceControlProxy.getPhysicalDisplayIds()).thenReturn(ids);
+        when(mSurfaceControlProxy.getPhysicalDisplayToken(anyLong()))
+                .thenReturn(mMockDisplayToken);
+        SurfaceControl.StaticDisplayInfo staticDisplayInfo = new SurfaceControl.StaticDisplayInfo();
+        staticDisplayInfo.isInternal = true;
+        when(mSurfaceControlProxy.getStaticDisplayInfo(anyLong()))
+                .thenReturn(staticDisplayInfo);
+        SurfaceControl.DynamicDisplayInfo dynamicDisplayMode =
+                new SurfaceControl.DynamicDisplayInfo();
+        SurfaceControl.DisplayMode displayMode = new SurfaceControl.DisplayMode();
+        displayMode.width = 100;
+        displayMode.height = 200;
+        displayMode.supportedHdrTypes = new int[]{1, 2};
+        dynamicDisplayMode.supportedDisplayModes = new SurfaceControl.DisplayMode[] {displayMode};
+        when(mSurfaceControlProxy.getDynamicDisplayInfo(anyLong()))
+                .thenReturn(dynamicDisplayMode);
+        when(mSurfaceControlProxy.getDesiredDisplayModeSpecs(mMockDisplayToken))
+                .thenReturn(new SurfaceControl.DesiredDisplayModeSpecs());
+    }
+
+    @Test
+    public void testCreateVirtualDisplay_sentToInputManager() throws RemoteException {
+        // This is to update the display device config such that DisplayManagerService can ignore
+        // the usage of SensorManager, which is available only after the PowerManagerService
+        // is ready.
+        resetConfigToIgnoreSensorManager(mContext);
+        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+        registerDefaultDisplays(displayManager);
+        displayManager.systemReady(false /* safeMode */);
+        displayManager.windowManagerAndInputReady();
+
+        // This is effectively the DisplayManager service published to ServiceManager.
+        DisplayManagerService.BinderService bs = displayManager.new BinderService();
+
+        String uniqueId = "uniqueId --- Test";
+        String uniqueIdPrefix = UNIQUE_ID_PREFIX + mContext.getPackageName() + ":";
+        int width = 600;
+        int height = 800;
+        int dpi = 320;
+        int flags = DisplayManager.VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH;
+
+        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
+        final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
+                VIRTUAL_DISPLAY_NAME, width, height, dpi);
+        builder.setUniqueId(uniqueId);
+        builder.setFlags(flags);
+        int displayId = bs.createVirtualDisplay(builder.build(), mMockAppToken /* callback */,
+                null /* projection */, PACKAGE_NAME);
+        verify(mMockProjectionService, never()).setContentRecordingSession(any(),
+                nullable(IMediaProjection.class));
+
+        displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
+
+        // flush the handler
+        displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */);
+
+        ArgumentCaptor<List<DisplayViewport>> viewportCaptor = ArgumentCaptor.forClass(List.class);
+        verify(mMockInputManagerInternal).setDisplayViewports(viewportCaptor.capture());
+        List<DisplayViewport> viewports = viewportCaptor.getValue();
+
+        // Expect to receive at least 2 viewports: at least 1 internal, and 1 virtual
+        assertTrue(viewports.size() >= 2);
+
+        DisplayViewport virtualViewport = null;
+        DisplayViewport internalViewport = null;
+        for (int i = 0; i < viewports.size(); i++) {
+            DisplayViewport v = viewports.get(i);
+            switch (v.type) {
+                case DisplayViewport.VIEWPORT_INTERNAL: {
+                    // If more than one internal viewport, this will get overwritten several times,
+                    // which for the purposes of this test is fine.
+                    internalViewport = v;
+                    assertTrue(internalViewport.valid);
+                    break;
+                }
+                case DisplayViewport.VIEWPORT_EXTERNAL: {
+                    // External view port is present for auto devices in the form of instrument
+                    // cluster.
+                    break;
+                }
+                case DisplayViewport.VIEWPORT_VIRTUAL: {
+                    virtualViewport = v;
+                    break;
+                }
+            }
+        }
+        // INTERNAL viewport gets created upon access.
+        assertNotNull(internalViewport);
+        assertNotNull(virtualViewport);
+
+        // VIRTUAL
+        assertEquals(height, virtualViewport.deviceHeight);
+        assertEquals(width, virtualViewport.deviceWidth);
+        assertEquals(uniqueIdPrefix + uniqueId, virtualViewport.uniqueId);
+        assertEquals(displayId, virtualViewport.displayId);
+    }
+
+    @Test
+    public void testPhysicalViewports() {
+        // This is to update the display device config such that DisplayManagerService can ignore
+        // the usage of SensorManager, which is available only after the PowerManagerService
+        // is ready.
+        resetConfigToIgnoreSensorManager(mContext);
+        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+        registerDefaultDisplays(displayManager);
+        displayManager.systemReady(false /* safeMode */);
+        displayManager.windowManagerAndInputReady();
+
+        // This is effectively the DisplayManager service published to ServiceManager.
+        DisplayManagerService.BinderService bs = displayManager.new BinderService();
+
+        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
+
+        final int[] displayIds = bs.getDisplayIds(/* includeDisabled= */ true);
+        final int size = displayIds.length;
+        assertTrue(size > 0);
+
+        Map<Integer, Integer> expectedDisplayTypeToViewPortTypeMapping = Map.of(
+                Display.TYPE_INTERNAL, DisplayViewport.VIEWPORT_INTERNAL,
+                Display.TYPE_EXTERNAL, DisplayViewport.VIEWPORT_EXTERNAL
+        );
+        for (int i = 0; i < size; i++) {
+            DisplayInfo info = bs.getDisplayInfo(displayIds[i]);
+            assertTrue(expectedDisplayTypeToViewPortTypeMapping.keySet().contains(info.type));
+        }
+
+        displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
+
+        // flush the handler
+        displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */);
+
+        ArgumentCaptor<List<DisplayViewport>> viewportCaptor = ArgumentCaptor.forClass(List.class);
+        verify(mMockInputManagerInternal).setDisplayViewports(viewportCaptor.capture());
+        List<DisplayViewport> viewports = viewportCaptor.getValue();
+
+        // Due to the nature of foldables, we may have a different number of viewports than
+        // displays, just verify there's at least one.
+        final int viewportSize = viewports.size();
+        assertTrue(viewportSize > 0);
+
+        // Now verify that each viewport's displayId is valid.
+        Arrays.sort(displayIds);
+        for (int i = 0; i < viewportSize; i++) {
+            DisplayViewport viewport = viewports.get(i);
+            assertNotNull(viewport);
+            DisplayInfo displayInfo = bs.getDisplayInfo(viewport.displayId);
+            assertTrue(expectedDisplayTypeToViewPortTypeMapping.get(displayInfo.type)
+                    == viewport.type);
+            assertTrue(viewport.valid);
+            assertTrue(Arrays.binarySearch(displayIds, viewport.displayId) >= 0);
+        }
+    }
+
+    @Test
+    public void testCreateVirtualDisplayRotatesWithContent() throws Exception {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mBasicInjector);
+        registerDefaultDisplays(displayManager);
+
+        // This is effectively the DisplayManager service published to ServiceManager.
+        DisplayManagerService.BinderService bs = displayManager.new BinderService();
+
+        String uniqueId = "uniqueId --- Rotates With Content Test";
+        int width = 600;
+        int height = 800;
+        int dpi = 320;
+        int flags = DisplayManager.VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT;
+
+        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
+        final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
+                VIRTUAL_DISPLAY_NAME, width, height, dpi);
+        builder.setFlags(flags);
+        builder.setUniqueId(uniqueId);
+        int displayId = bs.createVirtualDisplay(builder.build(), mMockAppToken /* callback */,
+                null /* projection */, PACKAGE_NAME);
+        verify(mMockProjectionService, never()).setContentRecordingSession(any(),
+                nullable(IMediaProjection.class));
+
+        displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
+
+        // flush the handler
+        displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */);
+
+        DisplayDeviceInfo ddi = displayManager.getDisplayDeviceInfoInternal(displayId);
+        assertNotNull(ddi);
+        assertTrue((ddi.flags & DisplayDeviceInfo.FLAG_ROTATES_WITH_CONTENT) != 0);
+    }
+
+    @Test
+    public void testCreateVirtualDisplayOwnFocus() throws RemoteException {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mBasicInjector);
+        registerDefaultDisplays(displayManager);
+
+        // This is effectively the DisplayManager service published to ServiceManager.
+        DisplayManagerService.BinderService bs = displayManager.new BinderService();
+
+        String uniqueId = "uniqueId --- Own Focus Test";
+        int width = 600;
+        int height = 800;
+        int dpi = 320;
+        int flags = DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_FOCUS
+                | DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED;
+
+        when(mContext.checkCallingPermission(ADD_TRUSTED_DISPLAY)).thenReturn(
+                PackageManager.PERMISSION_GRANTED);
+        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
+        final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
+                VIRTUAL_DISPLAY_NAME, width, height, dpi);
+        builder.setFlags(flags);
+        builder.setUniqueId(uniqueId);
+        int displayId = bs.createVirtualDisplay(builder.build(), /* callback= */ mMockAppToken,
+                /* projection= */ null, PACKAGE_NAME);
+        verify(mMockProjectionService, never()).setContentRecordingSession(any(),
+                nullable(IMediaProjection.class));
+
+        displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
+
+        // flush the handler
+        displayManager.getDisplayHandler().runWithScissors(() -> {}, /* now= */ 0);
+
+        DisplayDeviceInfo ddi = displayManager.getDisplayDeviceInfoInternal(displayId);
+        assertNotNull(ddi);
+        assertTrue((ddi.flags & DisplayDeviceInfo.FLAG_OWN_FOCUS) != 0);
+    }
+
+    @Test
+    public void testCreateVirtualDisplayOwnFocus_nonTrustedDisplay() throws RemoteException {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mBasicInjector);
+        registerDefaultDisplays(displayManager);
+
+        // This is effectively the DisplayManager service published to ServiceManager.
+        DisplayManagerService.BinderService bs = displayManager.new BinderService();
+
+        String uniqueId = "uniqueId --- Own Focus Test -- nonTrustedDisplay";
+        int width = 600;
+        int height = 800;
+        int dpi = 320;
+        int flags = DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_FOCUS;
+
+        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
+        final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
+                VIRTUAL_DISPLAY_NAME, width, height, dpi);
+        builder.setFlags(flags);
+        builder.setUniqueId(uniqueId);
+        int displayId = bs.createVirtualDisplay(builder.build(), /* callback= */ mMockAppToken,
+                /* projection= */ null, PACKAGE_NAME);
+        verify(mMockProjectionService, never()).setContentRecordingSession(any(),
+                nullable(IMediaProjection.class));
+
+        displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
+
+        // flush the handler
+        displayManager.getDisplayHandler().runWithScissors(() -> {}, /* now= */ 0);
+
+        DisplayDeviceInfo ddi = displayManager.getDisplayDeviceInfoInternal(displayId);
+        assertNotNull(ddi);
+        assertTrue((ddi.flags & DisplayDeviceInfo.FLAG_OWN_FOCUS) == 0);
+    }
+
+    /**
+     * Tests that the virtual display is created along-side the default display.
+     */
+    @Test
+    public void testStartVirtualDisplayWithDefaultDisplay_Succeeds() throws Exception {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mShortMockedInjector);
+        registerDefaultDisplays(displayManager);
+        displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
+    }
+
+    /**
+     * Tests that we send the device state to window manager
+     */
+    @Test
+    public void testOnStateChanged_sendsStateChangedEventToWm() throws Exception {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mShortMockedInjector);
+        registerDefaultDisplays(displayManager);
+        displayManager.windowManagerAndInputReady();
+        displayManager.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
+        DeviceStateListener listener = displayManager.new DeviceStateListener();
+        Handler handler = displayManager.getDisplayHandler();
+        IDisplayManagerCallback displayChangesCallback = registerDisplayChangeCallback(
+                displayManager);
+
+        listener.onStateChanged(123);
+        waitForIdleHandler(handler);
+
+        InOrder inOrder = inOrder(mMockWindowManagerInternal, displayChangesCallback);
+        // Verify there are no display events before WM call
+        inOrder.verify(displayChangesCallback, never()).onDisplayEvent(anyInt(), anyInt());
+        inOrder.verify(mMockWindowManagerInternal).onDisplayManagerReceivedDeviceState(123);
+    }
+
+    /**
+     * Tests that there should be a display change notification to WindowManager to update its own
+     * internal state for things like display cutout when nonOverrideDisplayInfo is changed.
+     */
+    @Test
+    public void testShouldNotifyChangeWhenNonOverrideDisplayInfoChanged() throws Exception {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mShortMockedInjector);
+        registerDefaultDisplays(displayManager);
+        displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
+
+        // Add the FakeDisplayDevice
+        FakeDisplayDevice displayDevice = new FakeDisplayDevice();
+        DisplayDeviceInfo displayDeviceInfo = new DisplayDeviceInfo();
+        displayDeviceInfo.width = 100;
+        displayDeviceInfo.height = 200;
+        displayDeviceInfo.supportedModes = new Display.Mode[1];
+        displayDeviceInfo.supportedModes[0] = new Display.Mode(1, 100, 200, 60f);
+        displayDeviceInfo.modeId = 1;
+        final Rect zeroRect = new Rect();
+        displayDeviceInfo.displayCutout = new DisplayCutout(
+                Insets.of(0, 10, 0, 0),
+                zeroRect, new Rect(0, 0, 10, 10), zeroRect, zeroRect);
+        displayDeviceInfo.flags = DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY;
+        displayDeviceInfo.address = new TestUtils.TestDisplayAddress();
+        displayDevice.setDisplayDeviceInfo(displayDeviceInfo);
+        displayManager.getDisplayDeviceRepository()
+                .onDisplayDeviceEvent(displayDevice, DisplayAdapter.DISPLAY_DEVICE_EVENT_ADDED);
+
+        // Find the display id of the added FakeDisplayDevice
+        DisplayManagerService.BinderService bs = displayManager.new BinderService();
+        int displayId = getDisplayIdForDisplayDevice(displayManager, bs, displayDevice);
+        // Setup override DisplayInfo
+        DisplayInfo overrideInfo = bs.getDisplayInfo(displayId);
+        displayManager.setDisplayInfoOverrideFromWindowManagerInternal(displayId, overrideInfo);
+
+        FakeDisplayManagerCallback callback = registerDisplayListenerCallback(
+                displayManager, bs, displayDevice);
+
+        // Simulate DisplayDevice change
+        DisplayDeviceInfo displayDeviceInfo2 = new DisplayDeviceInfo();
+        displayDeviceInfo2.copyFrom(displayDeviceInfo);
+        displayDeviceInfo2.displayCutout = null;
+        displayDevice.setDisplayDeviceInfo(displayDeviceInfo2);
+        displayManager.getDisplayDeviceRepository()
+                .onDisplayDeviceEvent(displayDevice, DisplayAdapter.DISPLAY_DEVICE_EVENT_CHANGED);
+
+        Handler handler = displayManager.getDisplayHandler();
+        waitForIdleHandler(handler);
+        assertTrue(callback.mDisplayChangedCalled);
+    }
+
+    /**
+     * Tests that we get a Runtime exception when we cannot initialize the default display.
+     */
+    @Test
+    public void testStartVirtualDisplayWithDefDisplay_NoDefaultDisplay() throws Exception {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mShortMockedInjector);
+        Handler handler = displayManager.getDisplayHandler();
+        waitForIdleHandler(handler);
+
+        try {
+            displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
+        } catch (RuntimeException e) {
+            return;
+        }
+        fail("Expected DisplayManager to throw RuntimeException when it cannot initialize the"
+                + " default display");
+    }
+
+    /**
+     * Tests that we get a Runtime exception when we cannot initialize the virtual display.
+     */
+    @Test
+    public void testStartVirtualDisplayWithDefDisplay_NoVirtualDisplayAdapter() throws Exception {
+        DisplayManagerService displayManager = new DisplayManagerService(mContext,
+                new DisplayManagerService.Injector() {
+                    @Override
+                    VirtualDisplayAdapter getVirtualDisplayAdapter(SyncRoot syncRoot,
+                            Context context, Handler handler, DisplayAdapter.Listener listener) {
+                        return null;  // return null for the adapter.  This should cause a failure.
+                    }
+
+                    @Override
+                    long getDefaultDisplayDelayTimeout() {
+                        return SHORT_DEFAULT_DISPLAY_TIMEOUT_MILLIS;
+                    }
+                });
+        try {
+            displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
+        } catch (RuntimeException e) {
+            return;
+        }
+        fail("Expected DisplayManager to throw RuntimeException when it cannot initialize the"
+                + " virtual display adapter");
+    }
+
+    /**
+     * Tests that an exception is raised for too dark a brightness configuration.
+     */
+    @Test
+    public void testTooDarkBrightnessConfigurationThrowException() {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mShortMockedInjector);
+        Curve minimumBrightnessCurve = displayManager.getMinimumBrightnessCurveInternal();
+        float[] lux = minimumBrightnessCurve.getX();
+        float[] minimumNits = minimumBrightnessCurve.getY();
+        float[] nits = new float[minimumNits.length];
+        // For every control point, assert that making it slightly lower than the minimum throws an
+        // exception.
+        for (int i = 0; i < nits.length; i++) {
+            for (int j = 0; j < nits.length; j++) {
+                nits[j] = minimumNits[j];
+                if (j == i) {
+                    nits[j] -= 0.1f;
+                }
+                if (nits[j] < 0) {
+                    nits[j] = 0;
+                }
+            }
+            BrightnessConfiguration config =
+                    new BrightnessConfiguration.Builder(lux, nits).build();
+            Exception thrown = null;
+            try {
+                displayManager.validateBrightnessConfiguration(config);
+            } catch (IllegalArgumentException e) {
+                thrown = e;
+            }
+            assertNotNull("Building too dark a brightness configuration must throw an exception");
+        }
+    }
+
+    /**
+     * Tests that no exception is raised for not too dark a brightness configuration.
+     */
+    @Test
+    public void testBrightEnoughBrightnessConfigurationDoesNotThrowException() {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mShortMockedInjector);
+        Curve minimumBrightnessCurve = displayManager.getMinimumBrightnessCurveInternal();
+        float[] lux = minimumBrightnessCurve.getX();
+        float[] nits = minimumBrightnessCurve.getY();
+        BrightnessConfiguration config = new BrightnessConfiguration.Builder(lux, nits).build();
+        displayManager.validateBrightnessConfiguration(config);
+    }
+
+    /**
+     * Tests that null brightness configurations are alright.
+     */
+    @Test
+    public void testNullBrightnessConfiguration() {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mShortMockedInjector);
+        displayManager.validateBrightnessConfiguration(null);
+    }
+
+    /**
+     * Tests that collection of display color sampling results are sensible.
+     */
+    @Test
+    public void testDisplayedContentSampling() {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mShortMockedInjector);
+        registerDefaultDisplays(displayManager);
+
+        DisplayDeviceInfo ddi = displayManager.getDisplayDeviceInfoInternal(0);
+        assertNotNull(ddi);
+
+        DisplayedContentSamplingAttributes attr =
+                displayManager.getDisplayedContentSamplingAttributesInternal(0);
+        if (attr == null) return; //sampling not supported on device, skip remainder of test.
+
+        boolean enabled = displayManager.setDisplayedContentSamplingEnabledInternal(0, true, 0, 0);
+        assertTrue(enabled);
+
+        displayManager.setDisplayedContentSamplingEnabledInternal(0, false, 0, 0);
+        DisplayedContentSample sample = displayManager.getDisplayedContentSampleInternal(0, 0, 0);
+        assertNotNull(sample);
+
+        long numPixels = ddi.width * ddi.height * sample.getNumFrames();
+        long[] samples = sample.getSampleComponent(DisplayedContentSample.ColorComponent.CHANNEL0);
+        assertTrue(samples.length == 0 || LongStream.of(samples).sum() == numPixels);
+
+        samples = sample.getSampleComponent(DisplayedContentSample.ColorComponent.CHANNEL1);
+        assertTrue(samples.length == 0 || LongStream.of(samples).sum() == numPixels);
+
+        samples = sample.getSampleComponent(DisplayedContentSample.ColorComponent.CHANNEL2);
+        assertTrue(samples.length == 0 || LongStream.of(samples).sum() == numPixels);
+
+        samples = sample.getSampleComponent(DisplayedContentSample.ColorComponent.CHANNEL3);
+        assertTrue(samples.length == 0 || LongStream.of(samples).sum() == numPixels);
+    }
+
+    /**
+     * Tests that the virtual display is created with
+     * {@link VirtualDisplayConfig.Builder#setDisplayIdToMirror(int)}
+     */
+    @Test
+    @FlakyTest(bugId = 127687569)
+    public void testCreateVirtualDisplay_displayIdToMirror() throws Exception {
+        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+        registerDefaultDisplays(displayManager);
+
+        // This is effectively the DisplayManager service published to ServiceManager.
+        DisplayManagerService.BinderService binderService = displayManager.new BinderService();
+        DisplayManagerService.LocalService localDisplayManager = displayManager.new LocalService();
+
+        final String uniqueId = "uniqueId --- displayIdToMirrorTest";
+        final int width = 600;
+        final int height = 800;
+        final int dpi = 320;
+
+        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
+        final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
+                VIRTUAL_DISPLAY_NAME, width, height, dpi);
+        builder.setUniqueId(uniqueId);
+        final int firstDisplayId = binderService.createVirtualDisplay(builder.build(),
+                mMockAppToken /* callback */, null /* projection */, PACKAGE_NAME);
+        verify(mMockProjectionService, never()).setContentRecordingSession(any(),
+                nullable(IMediaProjection.class));
+
+        // The second virtual display requests to mirror the first virtual display.
+        final String uniqueId2 = "uniqueId --- displayIdToMirrorTest #2";
+        when(mMockAppToken2.asBinder()).thenReturn(mMockAppToken2);
+        final VirtualDisplayConfig.Builder builder2 = new VirtualDisplayConfig.Builder(
+                VIRTUAL_DISPLAY_NAME, width, height, dpi).setUniqueId(uniqueId2);
+        builder2.setUniqueId(uniqueId2);
+        builder2.setDisplayIdToMirror(firstDisplayId);
+        final int secondDisplayId = binderService.createVirtualDisplay(builder2.build(),
+                mMockAppToken2 /* callback */, null /* projection */,
+                PACKAGE_NAME);
+        verify(mMockProjectionService, never()).setContentRecordingSession(any(),
+                nullable(IMediaProjection.class));
+        displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
+
+        // flush the handler
+        displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */);
+
+        // The displayId to mirror should be a default display if there is none initially.
+        assertEquals(localDisplayManager.getDisplayIdToMirror(firstDisplayId),
+                Display.DEFAULT_DISPLAY);
+        assertEquals(localDisplayManager.getDisplayIdToMirror(secondDisplayId),
+                firstDisplayId);
+    }
+
+    /** Tests that the virtual device is created in a device display group. */
+    @Test
+    public void createVirtualDisplay_addsDisplaysToDeviceDisplayGroups() throws Exception {
+        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+        DisplayManagerInternal localService = displayManager.new LocalService();
+
+        registerDefaultDisplays(displayManager);
+        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
+
+        IVirtualDevice virtualDevice = mock(IVirtualDevice.class);
+        when(virtualDevice.getDeviceId()).thenReturn(1);
+        when(mIVirtualDeviceManager.isValidVirtualDeviceId(1)).thenReturn(true);
+        // Create a first virtual display. A display group should be created for this display on the
+        // virtual device.
+        final VirtualDisplayConfig.Builder builder1 =
+                new VirtualDisplayConfig.Builder(VIRTUAL_DISPLAY_NAME, 600, 800, 320)
+                        .setUniqueId("uniqueId --- device display group 1");
+
+        int displayId1 =
+                localService.createVirtualDisplay(
+                        builder1.build(),
+                        mMockAppToken /* callback */,
+                        virtualDevice /* virtualDeviceToken */,
+                        mock(DisplayWindowPolicyController.class),
+                        PACKAGE_NAME);
+        verify(mMockProjectionService, never()).setContentRecordingSession(any(),
+                nullable(IMediaProjection.class));
+        int displayGroupId1 = localService.getDisplayInfo(displayId1).displayGroupId;
+
+        // Create a second virtual display. This should be added to the previously created display
+        // group.
+        final VirtualDisplayConfig.Builder builder2 =
+                new VirtualDisplayConfig.Builder(VIRTUAL_DISPLAY_NAME, 600, 800, 320)
+                        .setUniqueId("uniqueId --- device display group 1");
+
+        int displayId2 =
+                localService.createVirtualDisplay(
+                        builder2.build(),
+                        mMockAppToken /* callback */,
+                        virtualDevice /* virtualDeviceToken */,
+                        mock(DisplayWindowPolicyController.class),
+                        PACKAGE_NAME);
+        verify(mMockProjectionService, never()).setContentRecordingSession(any(),
+                nullable(IMediaProjection.class));
+        int displayGroupId2 = localService.getDisplayInfo(displayId2).displayGroupId;
+
+        assertEquals(
+                "Both displays should be added to the same displayGroup.",
+                displayGroupId1,
+                displayGroupId2);
+    }
+
+    /**
+     * Tests that the virtual display is not added to the device display group when
+     * VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP is set.
+     */
+    @Test
+    public void createVirtualDisplay_addsDisplaysToOwnDisplayGroups() throws Exception {
+        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+        DisplayManagerInternal localService = displayManager.new LocalService();
+
+        registerDefaultDisplays(displayManager);
+        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
+
+        IVirtualDevice virtualDevice = mock(IVirtualDevice.class);
+        when(virtualDevice.getDeviceId()).thenReturn(1);
+        when(mIVirtualDeviceManager.isValidVirtualDeviceId(1)).thenReturn(true);
+
+        // Create a first virtual display. A display group should be created for this display on the
+        // virtual device.
+        final VirtualDisplayConfig.Builder builder1 =
+                new VirtualDisplayConfig.Builder(VIRTUAL_DISPLAY_NAME, 600, 800, 320)
+                        .setUniqueId("uniqueId --- device display group");
+
+        int displayId1 =
+                localService.createVirtualDisplay(
+                        builder1.build(),
+                        mMockAppToken /* callback */,
+                        virtualDevice /* virtualDeviceToken */,
+                        mock(DisplayWindowPolicyController.class),
+                        PACKAGE_NAME);
+        verify(mMockProjectionService, never()).setContentRecordingSession(any(),
+                nullable(IMediaProjection.class));
+        int displayGroupId1 = localService.getDisplayInfo(displayId1).displayGroupId;
+
+        // Create a second virtual display. With the flag VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP,
+        // the display should not be added to the previously created display group.
+        final VirtualDisplayConfig.Builder builder2 =
+                new VirtualDisplayConfig.Builder(VIRTUAL_DISPLAY_NAME, 600, 800, 320)
+                        .setFlags(VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP)
+                        .setUniqueId("uniqueId --- own display group");
+
+        when(mIVirtualDeviceManager.isValidVirtualDeviceId(1)).thenReturn(true);
+
+        int displayId2 =
+                localService.createVirtualDisplay(
+                        builder2.build(),
+                        mMockAppToken /* callback */,
+                        virtualDevice /* virtualDeviceToken */,
+                        mock(DisplayWindowPolicyController.class),
+                        PACKAGE_NAME);
+        verify(mMockProjectionService, never()).setContentRecordingSession(any(),
+                nullable(IMediaProjection.class));
+        int displayGroupId2 = localService.getDisplayInfo(displayId2).displayGroupId;
+
+        assertNotEquals(
+                "Display 1 should be in the device display group and display 2 in its own display"
+                        + " group.",
+                displayGroupId1,
+                displayGroupId2);
+    }
+
+    @Test
+    public void displaysInDeviceOrOwnDisplayGroupShouldPreserveAlwaysUnlockedFlag()
+            throws Exception {
+        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+        DisplayManagerInternal localService = displayManager.new LocalService();
+
+        registerDefaultDisplays(displayManager);
+        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
+
+        IVirtualDevice virtualDevice = mock(IVirtualDevice.class);
+        when(virtualDevice.getDeviceId()).thenReturn(1);
+        when(mIVirtualDeviceManager.isValidVirtualDeviceId(1)).thenReturn(true);
+
+        // Allow an ALWAYS_UNLOCKED display to be created.
+        when(mContext.checkCallingPermission(ADD_TRUSTED_DISPLAY))
+                .thenReturn(PackageManager.PERMISSION_GRANTED);
+
+        when(mContext.checkCallingPermission(ADD_ALWAYS_UNLOCKED_DISPLAY))
+                .thenReturn(PackageManager.PERMISSION_GRANTED);
+
+        // Create a virtual display in a device display group.
+        final VirtualDisplayConfig deviceDisplayGroupDisplayConfig =
+                new VirtualDisplayConfig.Builder(VIRTUAL_DISPLAY_NAME, 600, 800, 320)
+                        .setUniqueId("uniqueId --- device display group 1")
+                        .setFlags(VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED)
+                        .build();
+
+        int deviceDisplayGroupDisplayId =
+                localService.createVirtualDisplay(
+                        deviceDisplayGroupDisplayConfig,
+                        mMockAppToken /* callback */,
+                        virtualDevice /* virtualDeviceToken */,
+                        mock(DisplayWindowPolicyController.class),
+                        PACKAGE_NAME);
+        verify(mMockProjectionService, never()).setContentRecordingSession(any(),
+                nullable(IMediaProjection.class));
+
+        // Check that FLAG_ALWAYS_UNLOCKED is set.
+        assertNotEquals(
+                "FLAG_ALWAYS_UNLOCKED should be set for displays created in a device display"
+                        + " group.",
+                (displayManager.getDisplayDeviceInfoInternal(deviceDisplayGroupDisplayId).flags
+                        & DisplayDeviceInfo.FLAG_ALWAYS_UNLOCKED),
+                0);
+
+        // Create a virtual display in its own display group.
+        final VirtualDisplayConfig ownDisplayGroupConfig =
+                new VirtualDisplayConfig.Builder(VIRTUAL_DISPLAY_NAME, 600, 800, 320)
+                        .setUniqueId("uniqueId --- own display group 1")
+                        .setFlags(
+                                VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED
+                                        | VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP)
+                        .build();
+
+        int ownDisplayGroupDisplayId =
+                localService.createVirtualDisplay(
+                        ownDisplayGroupConfig,
+                        mMockAppToken /* callback */,
+                        virtualDevice /* virtualDeviceToken */,
+                        mock(DisplayWindowPolicyController.class),
+                        PACKAGE_NAME);
+        verify(mMockProjectionService, never()).setContentRecordingSession(any(),
+                nullable(IMediaProjection.class));
+
+        // Check that FLAG_ALWAYS_UNLOCKED is set.
+        assertNotEquals(
+                "FLAG_ALWAYS_UNLOCKED should be set for displays created in their own display"
+                        + " group.",
+                (displayManager.getDisplayDeviceInfoInternal(ownDisplayGroupDisplayId).flags
+                        & DisplayDeviceInfo.FLAG_ALWAYS_UNLOCKED),
+                0);
+
+        // Create a virtual display in a device display group.
+        final VirtualDisplayConfig defaultDisplayGroupConfig =
+                new VirtualDisplayConfig.Builder(VIRTUAL_DISPLAY_NAME, 600, 800, 320)
+                        .setUniqueId("uniqueId --- default display group 1")
+                        .setFlags(VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED)
+                        .build();
+
+        int defaultDisplayGroupDisplayId =
+                localService.createVirtualDisplay(
+                        defaultDisplayGroupConfig,
+                        mMockAppToken /* callback */,
+                        null /* virtualDeviceToken */,
+                        mock(DisplayWindowPolicyController.class),
+                        PACKAGE_NAME);
+        verify(mMockProjectionService, never()).setContentRecordingSession(any(),
+                nullable(IMediaProjection.class));
+
+        // Check that FLAG_ALWAYS_UNLOCKED is not set.
+        assertEquals(
+                "FLAG_ALWAYS_UNLOCKED should not be set for displays created in the default"
+                        + " display group.",
+                (displayManager.getDisplayDeviceInfoInternal(defaultDisplayGroupDisplayId).flags
+                        & DisplayDeviceInfo.FLAG_ALWAYS_UNLOCKED),
+                0);
+    }
+
+    @Test
+    public void testGetDisplayIdToMirror() throws Exception {
+        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+        registerDefaultDisplays(displayManager);
+
+        // This is effectively the DisplayManager service published to ServiceManager.
+        DisplayManagerService.BinderService binderService = displayManager.new BinderService();
+        DisplayManagerService.LocalService localDisplayManager = displayManager.new LocalService();
+
+        final String uniqueId = "uniqueId --- displayIdToMirrorTest";
+        final int width = 600;
+        final int height = 800;
+        final int dpi = 320;
+
+        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
+        final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
+                VIRTUAL_DISPLAY_NAME, width, height, dpi)
+                .setUniqueId(uniqueId)
+                .setFlags(VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY);
+        final int firstDisplayId = binderService.createVirtualDisplay(builder.build(),
+                mMockAppToken /* callback */, null /* projection */, PACKAGE_NAME);
+        verify(mMockProjectionService, never()).setContentRecordingSession(any(),
+                nullable(IMediaProjection.class));
+
+        // The second virtual display requests to mirror the first virtual display.
+        final String uniqueId2 = "uniqueId --- displayIdToMirrorTest #2";
+        when(mMockAppToken2.asBinder()).thenReturn(mMockAppToken2);
+        final VirtualDisplayConfig.Builder builder2 = new VirtualDisplayConfig.Builder(
+                VIRTUAL_DISPLAY_NAME, width, height, dpi)
+                .setUniqueId(uniqueId2)
+                .setWindowManagerMirroringEnabled(true);
+        final int secondDisplayId = binderService.createVirtualDisplay(builder2.build(),
+                mMockAppToken2 /* callback */, null /* projection */,
+                PACKAGE_NAME);
+        verify(mMockProjectionService, never()).setContentRecordingSession(any(),
+                nullable(IMediaProjection.class));
+        displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
+
+        // flush the handler
+        displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */);
+
+        // The displayId to mirror should be a invalid since the display had flag OWN_CONTENT_ONLY
+        assertEquals(localDisplayManager.getDisplayIdToMirror(firstDisplayId),
+                Display.INVALID_DISPLAY);
+        // The second display has mirroring managed by WindowManager so the mirror displayId should
+        // be invalid.
+        assertEquals(localDisplayManager.getDisplayIdToMirror(secondDisplayId),
+                Display.INVALID_DISPLAY);
+    }
+
+    @Test
+    public void testCreateVirtualDisplay_isValidProjection_notValid()
+            throws RemoteException {
+        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
+        IMediaProjection projection = mock(IMediaProjection.class);
+        doReturn(false).when(projection).isValid();
+        when(mMockProjectionService
+                .setContentRecordingSession(any(ContentRecordingSession.class), eq(projection)))
+                .thenReturn(true);
+        doReturn(true).when(mMockProjectionService).isCurrentProjection(eq(projection));
+
+        final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
+                VIRTUAL_DISPLAY_NAME, 600, 800, 320);
+        builder.setUniqueId("uniqueId --- isValid false");
+
+        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+        registerDefaultDisplays(displayManager);
+        displayManager.windowManagerAndInputReady();
+
+        // Pass in a non-null projection.
+        DisplayManagerService.BinderService binderService = displayManager.new BinderService();
+        final int displayId = binderService.createVirtualDisplay(builder.build(),
+                mMockAppToken /* callback */, projection, PACKAGE_NAME);
+
+        // VirtualDisplay is created for mirroring.
+        assertThat(displayId).isNotEqualTo(Display.INVALID_DISPLAY);
+        verify(mMockProjectionService, atLeastOnce()).setContentRecordingSession(
+                any(ContentRecordingSession.class), nullable(IMediaProjection.class));
+        // But mirroring doesn't begin.
+        verify(mMockProjectionService, atLeastOnce()).setContentRecordingSession(
+                mContentRecordingSessionCaptor.capture(), nullable(IMediaProjection.class));
+        ContentRecordingSession session = mContentRecordingSessionCaptor.getValue();
+        assertThat(session.isWaitingForConsent()).isTrue();
+    }
+
+    @Test
+    public void testCreateVirtualDisplay_setContentRecordingSessionSuccess()
+            throws RemoteException {
+        final int displayToRecord = 50;
+        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
+        IMediaProjection projection = mock(IMediaProjection.class);
+        doReturn(true).when(projection).isValid();
+        when(mMockProjectionService
+                .setContentRecordingSession(any(ContentRecordingSession.class), eq(projection)))
+                .thenReturn(true);
+        doReturn(true).when(mMockProjectionService).isCurrentProjection(eq(projection));
+
+        final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
+                VIRTUAL_DISPLAY_NAME, 600, 800, 320);
+        builder.setUniqueId("uniqueId --- setContentRecordingSession true");
+        builder.setDisplayIdToMirror(displayToRecord);
+
+        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+        registerDefaultDisplays(displayManager);
+        displayManager.windowManagerAndInputReady();
+
+        DisplayManagerService.BinderService binderService = displayManager.new BinderService();
+        final int displayId = binderService.createVirtualDisplay(builder.build(),
+                mMockAppToken /* callback */, projection, PACKAGE_NAME);
+
+        assertThat(displayId).isNotEqualTo(Display.INVALID_DISPLAY);
+        verify(mMockProjectionService, atLeastOnce()).setContentRecordingSession(
+                mContentRecordingSessionCaptor.capture(), nullable(IMediaProjection.class));
+        ContentRecordingSession session = mContentRecordingSessionCaptor.getValue();
+        assertThat(session.getContentToRecord()).isEqualTo(RECORD_CONTENT_DISPLAY);
+        assertThat(session.getVirtualDisplayId()).isEqualTo(displayId);
+        assertThat(session.getDisplayToRecord()).isEqualTo(displayToRecord);
+        assertThat(session.isWaitingForConsent()).isFalse();
+    }
+
+    @Test
+    public void testCreateVirtualDisplay_setContentRecordingSessionFail() throws RemoteException {
+        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
+        IMediaProjection projection = mock(IMediaProjection.class);
+        doReturn(true).when(projection).isValid();
+        when(mMockProjectionService
+                .setContentRecordingSession(any(ContentRecordingSession.class), eq(projection)))
+                .thenReturn(false);
+        doReturn(true).when(mMockProjectionService).isCurrentProjection(eq(projection));
+
+        final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
+                VIRTUAL_DISPLAY_NAME, 600, 800, 320);
+        builder.setUniqueId("uniqueId --- setContentRecordingSession false");
+
+        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+        registerDefaultDisplays(displayManager);
+        displayManager.windowManagerAndInputReady();
+
+        DisplayManagerService.BinderService binderService = displayManager.new BinderService();
+        final int displayId = binderService.createVirtualDisplay(builder.build(),
+                mMockAppToken /* callback */, projection, PACKAGE_NAME);
+
+        assertThat(displayId).isEqualTo(Display.INVALID_DISPLAY);
+    }
+
+    @Test
+    public void testCreateVirtualDisplay_setContentRecordingSession_taskSession()
+            throws RemoteException {
+        final int displayToRecord = 50;
+        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
+        IMediaProjection projection = mock(IMediaProjection.class);
+        doReturn(true).when(projection).isValid();
+        when(mMockProjectionService
+                .setContentRecordingSession(any(ContentRecordingSession.class), eq(projection)))
+                .thenReturn(true);
+        doReturn(mock(IBinder.class)).when(projection).getLaunchCookie();
+        doReturn(true).when(mMockProjectionService).isCurrentProjection(eq(projection));
+
+        final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
+                VIRTUAL_DISPLAY_NAME, 600, 800, 320);
+        builder.setUniqueId("uniqueId --- setContentRecordingSession false");
+        builder.setDisplayIdToMirror(displayToRecord);
+
+        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+        registerDefaultDisplays(displayManager);
+        displayManager.windowManagerAndInputReady();
+
+        DisplayManagerService.BinderService binderService = displayManager.new BinderService();
+        final int displayId = binderService.createVirtualDisplay(builder.build(),
+                mMockAppToken /* callback */, projection, PACKAGE_NAME);
+
+        assertThat(displayId).isNotEqualTo(Display.INVALID_DISPLAY);
+        verify(mMockProjectionService, atLeastOnce()).setContentRecordingSession(
+                mContentRecordingSessionCaptor.capture(), nullable(IMediaProjection.class));
+        ContentRecordingSession session = mContentRecordingSessionCaptor.getValue();
+        assertThat(session.getContentToRecord()).isEqualTo(RECORD_CONTENT_TASK);
+        assertThat(session.getVirtualDisplayId()).isEqualTo(displayId);
+        assertThat(session.getTokenToRecord()).isNotNull();
+    }
+
+    @Test
+    public void testCreateVirtualDisplay_setContentRecordingSession_noProjection_noFlags()
+            throws RemoteException {
+        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
+
+        // Set no flags for the VirtualDisplay.
+        final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
+                VIRTUAL_DISPLAY_NAME, 600, 800, 320);
+        builder.setUniqueId("uniqueId --- setContentRecordingSession false");
+
+        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+        registerDefaultDisplays(displayManager);
+        displayManager.windowManagerAndInputReady();
+
+        // Pass in a null projection.
+        DisplayManagerService.BinderService binderService = displayManager.new BinderService();
+        final int displayId = binderService.createVirtualDisplay(builder.build(),
+                mMockAppToken /* callback */, null /* projection */, PACKAGE_NAME);
+
+        // VirtualDisplay is created but not for mirroring.
+        assertThat(displayId).isNotEqualTo(Display.INVALID_DISPLAY);
+        verify(mMockProjectionService, never()).setContentRecordingSession(
+                any(ContentRecordingSession.class), nullable(IMediaProjection.class));
+    }
+
+    @Test
+    public void testCreateVirtualDisplay_setContentRecordingSession_noProjection_noMirroringFlag()
+            throws RemoteException {
+        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
+
+        // Set a non-mirroring flag for the VirtualDisplay.
+        final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
+                VIRTUAL_DISPLAY_NAME, 600, 800, 320);
+        builder.setUniqueId("uniqueId --- setContentRecordingSession false");
+        builder.setFlags(VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY);
+
+        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+        registerDefaultDisplays(displayManager);
+        displayManager.windowManagerAndInputReady();
+
+        // Pass in a null projection.
+        DisplayManagerService.BinderService binderService = displayManager.new BinderService();
+        final int displayId = binderService.createVirtualDisplay(builder.build(),
+                mMockAppToken /* callback */, null /* projection */, PACKAGE_NAME);
+
+        // VirtualDisplay is created but not for mirroring.
+        assertThat(displayId).isNotEqualTo(Display.INVALID_DISPLAY);
+        verify(mMockProjectionService, never()).setContentRecordingSession(
+                any(ContentRecordingSession.class), nullable(IMediaProjection.class));
+    }
+
+    @Test
+    public void testCreateVirtualDisplay_setContentRecordingSession_projection_noMirroringFlag()
+            throws RemoteException {
+        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
+        IMediaProjection projection = mock(IMediaProjection.class);
+        doReturn(true).when(projection).isValid();
+        when(mMockProjectionService
+                .setContentRecordingSession(any(ContentRecordingSession.class), eq(projection)))
+                .thenReturn(true);
+        doReturn(true).when(mMockProjectionService).isCurrentProjection(eq(projection));
+
+        // Set no flags for the VirtualDisplay.
+        final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
+                VIRTUAL_DISPLAY_NAME, 600, 800, 320);
+        builder.setUniqueId("uniqueId --- setContentRecordingSession false");
+
+        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+        registerDefaultDisplays(displayManager);
+        displayManager.windowManagerAndInputReady();
+
+        // Pass in a non-null projection.
+        DisplayManagerService.BinderService binderService = displayManager.new BinderService();
+        final int displayId = binderService.createVirtualDisplay(builder.build(),
+                mMockAppToken /* callback */, projection, PACKAGE_NAME);
+
+        // VirtualDisplay is created for mirroring.
+        assertThat(displayId).isNotEqualTo(Display.INVALID_DISPLAY);
+        verify(mMockProjectionService, atLeastOnce()).setContentRecordingSession(
+                any(ContentRecordingSession.class), nullable(IMediaProjection.class));
+    }
+
+    /**
+     * Tests that the virtual display is created with
+     * {@link VirtualDisplayConfig.Builder#setSurface(Surface)}
+     */
+    @Test
+    @FlakyTest(bugId = 127687569)
+    public void testCreateVirtualDisplay_setSurface() throws Exception {
+        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+        registerDefaultDisplays(displayManager);
+
+        // This is effectively the DisplayManager service published to ServiceManager.
+        DisplayManagerService.BinderService binderService = displayManager.new BinderService();
+
+        final String uniqueId = "uniqueId --- setSurface";
+        final int width = 600;
+        final int height = 800;
+        final int dpi = 320;
+        final Surface surface = new Surface();
+
+        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
+        final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
+                VIRTUAL_DISPLAY_NAME, width, height, dpi);
+        builder.setSurface(surface);
+        builder.setUniqueId(uniqueId);
+        final int displayId = binderService.createVirtualDisplay(builder.build(),
+                mMockAppToken /* callback */, null /* projection */, PACKAGE_NAME);
+        verify(mMockProjectionService, never()).setContentRecordingSession(any(),
+                nullable(IMediaProjection.class));
+
+        displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
+
+        // flush the handler
+        displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */);
+
+        assertEquals(displayManager.getVirtualDisplaySurfaceInternal(mMockAppToken), surface);
+    }
+
+    /**
+     * Tests that specifying VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP is allowed when the permission
+     * ADD_TRUSTED_DISPLAY is granted.
+     */
+    @Test
+    public void testOwnDisplayGroup_allowCreationWithAddTrustedDisplayPermission()
+            throws RemoteException {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mBasicInjector);
+        registerDefaultDisplays(displayManager);
+
+        DisplayManagerService.BinderService bs = displayManager.new BinderService();
+        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
+
+        when(mContext.checkCallingPermission(ADD_TRUSTED_DISPLAY)).thenReturn(
+                PackageManager.PERMISSION_GRANTED);
+
+        final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
+                VIRTUAL_DISPLAY_NAME, 600, 800, 320);
+        builder.setFlags(DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP);
+        builder.setUniqueId("uniqueId --- OWN_DISPLAY_GROUP");
+
+        int displayId = bs.createVirtualDisplay(builder.build(), mMockAppToken /* callback */,
+                null /* projection */, PACKAGE_NAME);
+        verify(mMockProjectionService, never()).setContentRecordingSession(any(),
+                nullable(IMediaProjection.class));
+        displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
+        displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */);
+        DisplayDeviceInfo ddi = displayManager.getDisplayDeviceInfoInternal(displayId);
+        assertNotNull(ddi);
+        assertNotEquals(0, ddi.flags & DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP);
+    }
+
+    /**
+     * Tests that specifying VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP is blocked when the permission
+     * ADD_TRUSTED_DISPLAY is denied.
+     */
+    @Test
+    public void testOwnDisplayGroup_withoutAddTrustedDisplayPermission_throwsSecurityException() {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mBasicInjector);
+        registerDefaultDisplays(displayManager);
+
+        DisplayManagerService.BinderService bs = displayManager.new BinderService();
+        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
+
+        when(mContext.checkCallingPermission(ADD_TRUSTED_DISPLAY)).thenReturn(
+                PackageManager.PERMISSION_DENIED);
+
+        final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
+                VIRTUAL_DISPLAY_NAME, 600, 800, 320);
+        builder.setFlags(DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP);
+        builder.setUniqueId("uniqueId --- OWN_DISPLAY_GROUP");
+
+        try {
+            bs.createVirtualDisplay(builder.build(), mMockAppToken /* callback */,
+                    null /* projection */, PACKAGE_NAME);
+            fail("Creating virtual display with VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP without "
+                    + "ADD_TRUSTED_DISPLAY permission should throw SecurityException.");
+        } catch (SecurityException e) {
+            // SecurityException is expected
+        }
+    }
+
+    /**
+     * Tests that specifying VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP is allowed when called with
+     * a virtual device, even if ADD_TRUSTED_DISPLAY is not granted.
+     */
+    @Test
+    public void testOwnDisplayGroup_allowCreationWithVirtualDevice()  throws Exception {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mBasicInjector);
+        DisplayManagerInternal localService = displayManager.new LocalService();
+
+        registerDefaultDisplays(displayManager);
+
+        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
+
+        when(mContext.checkCallingPermission(ADD_TRUSTED_DISPLAY)).thenReturn(
+                PackageManager.PERMISSION_DENIED);
+
+        final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
+                VIRTUAL_DISPLAY_NAME, 600, 800, 320);
+        builder.setFlags(DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP);
+        builder.setUniqueId("uniqueId --- OWN_DISPLAY_GROUP");
+
+        IVirtualDevice virtualDevice = mock(IVirtualDevice.class);
+        when(virtualDevice.getDeviceId()).thenReturn(1);
+        when(mIVirtualDeviceManager.isValidVirtualDeviceId(1)).thenReturn(true);
+
+        int displayId = localService.createVirtualDisplay(builder.build(),
+                mMockAppToken /* callback */, virtualDevice /* virtualDeviceToken */,
+                mock(DisplayWindowPolicyController.class), PACKAGE_NAME);
+        verify(mMockProjectionService, never()).setContentRecordingSession(any(),
+                nullable(IMediaProjection.class));
+        displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
+        displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */);
+        DisplayDeviceInfo ddi = displayManager.getDisplayDeviceInfoInternal(displayId);
+        assertNotNull(ddi);
+        assertNotEquals(0, ddi.flags & DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP);
+    }
+
+    /**
+     * Tests that there is a display change notification if the frame rate override
+     * list is updated.
+     */
+    @Test
+    public void testShouldNotifyChangeWhenDisplayInfoFrameRateOverrideChanged() throws Exception {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mShortMockedInjector);
+        DisplayManagerService.BinderService displayManagerBinderService =
+                displayManager.new BinderService();
+        registerDefaultDisplays(displayManager);
+        displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
+
+        FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager, new float[]{60f});
+        FakeDisplayManagerCallback callback = registerDisplayListenerCallback(displayManager,
+                displayManagerBinderService, displayDevice);
+
+        int myUid = Process.myUid();
+        updateFrameRateOverride(displayManager, displayDevice,
+                new DisplayEventReceiver.FrameRateOverride[]{
+                        new DisplayEventReceiver.FrameRateOverride(myUid, 30f),
+                });
+        assertTrue(callback.mDisplayChangedCalled);
+        callback.clear();
+
+        updateFrameRateOverride(displayManager, displayDevice,
+                new DisplayEventReceiver.FrameRateOverride[]{
+                        new DisplayEventReceiver.FrameRateOverride(myUid, 30f),
+                        new DisplayEventReceiver.FrameRateOverride(1234, 30f),
+                });
+        assertFalse(callback.mDisplayChangedCalled);
+
+        updateFrameRateOverride(displayManager, displayDevice,
+                new DisplayEventReceiver.FrameRateOverride[]{
+                        new DisplayEventReceiver.FrameRateOverride(myUid, 20f),
+                        new DisplayEventReceiver.FrameRateOverride(1234, 30f),
+                        new DisplayEventReceiver.FrameRateOverride(5678, 30f),
+                });
+        assertTrue(callback.mDisplayChangedCalled);
+        callback.clear();
+
+        updateFrameRateOverride(displayManager, displayDevice,
+                new DisplayEventReceiver.FrameRateOverride[]{
+                        new DisplayEventReceiver.FrameRateOverride(1234, 30f),
+                        new DisplayEventReceiver.FrameRateOverride(5678, 30f),
+                });
+        assertTrue(callback.mDisplayChangedCalled);
+        callback.clear();
+
+        updateFrameRateOverride(displayManager, displayDevice,
+                new DisplayEventReceiver.FrameRateOverride[]{
+                        new DisplayEventReceiver.FrameRateOverride(5678, 30f),
+                });
+        assertFalse(callback.mDisplayChangedCalled);
+    }
+
+    /**
+     * Tests that the DisplayInfo is updated correctly with a frame rate override
+     */
+    @Test
+    public void testDisplayInfoFrameRateOverride() throws Exception {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mShortMockedInjector);
+        DisplayManagerService.BinderService displayManagerBinderService =
+                displayManager.new BinderService();
+        registerDefaultDisplays(displayManager);
+        displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
+
+        FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager,
+                new float[]{60f, 30f, 20f});
+        int displayId = getDisplayIdForDisplayDevice(displayManager, displayManagerBinderService,
+                displayDevice);
+        DisplayInfo displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
+        assertEquals(60f, displayInfo.getRefreshRate(), 0.01f);
+
+        updateFrameRateOverride(displayManager, displayDevice,
+                new DisplayEventReceiver.FrameRateOverride[]{
+                        new DisplayEventReceiver.FrameRateOverride(
+                                Process.myUid(), 20f),
+                        new DisplayEventReceiver.FrameRateOverride(
+                                Process.myUid() + 1, 30f)
+                });
+        displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
+        assertEquals(20f, displayInfo.getRefreshRate(), 0.01f);
+
+        // Changing the mode to 30Hz should not override the refresh rate to 20Hz anymore
+        // as 20 is not a divider of 30.
+        updateModeId(displayManager, displayDevice, 2);
+        displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
+        assertEquals(30f, displayInfo.getRefreshRate(), 0.01f);
+    }
+
+    /**
+     * Tests that the frame rate override is returning the correct value from
+     * DisplayInfo#getRefreshRate
+     */
+    @Test
+    public void testDisplayInfoNonNativeFrameRateOverride() throws Exception {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mBasicInjector);
+        DisplayManagerService.BinderService displayManagerBinderService =
+                displayManager.new BinderService();
+        registerDefaultDisplays(displayManager);
+        displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
+
+        FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager,
+                new float[]{60f});
+        int displayId = getDisplayIdForDisplayDevice(displayManager, displayManagerBinderService,
+                displayDevice);
+        DisplayInfo displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
+        assertEquals(60f, displayInfo.getRefreshRate(), 0.01f);
+
+        updateFrameRateOverride(displayManager, displayDevice,
+                new DisplayEventReceiver.FrameRateOverride[]{
+                        new DisplayEventReceiver.FrameRateOverride(
+                                Process.myUid(), 20f)
+                });
+        displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
+        assertEquals(20f, displayInfo.getRefreshRate(), 0.01f);
+    }
+
+    /**
+     * Tests that the mode reflects the frame rate override is in compat mode
+     */
+    @Test
+    @DisableCompatChanges({DisplayManagerService.DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE})
+    public  void testDisplayInfoFrameRateOverrideModeCompat() throws Exception {
+        testDisplayInfoFrameRateOverrideModeCompat(/*compatChangeEnabled*/ false);
+    }
+
+    /**
+     * Tests that the mode reflects the physical display refresh rate when not in compat mode.
+     */
+    @Test
+    @EnableCompatChanges({DisplayManagerService.DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE})
+    public  void testDisplayInfoFrameRateOverrideMode() throws Exception {
+        testDisplayInfoFrameRateOverrideModeCompat(/*compatChangeEnabled*/ true);
+    }
+
+    /**
+     * Tests that the mode reflects the frame rate override is in compat mode and accordingly to the
+     * allowNonNativeRefreshRateOverride policy.
+     */
+    @Test
+    @DisableCompatChanges({DisplayManagerService.DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE})
+    public void testDisplayInfoNonNativeFrameRateOverrideModeCompat() throws Exception {
+        testDisplayInfoNonNativeFrameRateOverrideMode(/*compatChangeEnabled*/ false);
+    }
+
+    /**
+     * Tests that the mode reflects the physical display refresh rate when not in compat mode.
+     */
+    @Test
+    @EnableCompatChanges({DisplayManagerService.DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE})
+    public void testDisplayInfoNonNativeFrameRateOverrideMode() throws Exception {
+        testDisplayInfoNonNativeFrameRateOverrideMode(/*compatChangeEnabled*/ true);
+    }
+
+    /**
+     * Tests that there is a display change notification if the render frame rate is updated
+     */
+    @Test
+    public void testShouldNotifyChangeWhenDisplayInfoRenderFrameRateChanged() throws Exception {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mShortMockedInjector);
+        DisplayManagerService.BinderService displayManagerBinderService =
+                displayManager.new BinderService();
+        registerDefaultDisplays(displayManager);
+        displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
+
+        FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager, new float[]{60f});
+        FakeDisplayManagerCallback callback = registerDisplayListenerCallback(displayManager,
+                displayManagerBinderService, displayDevice);
+
+        updateRenderFrameRate(displayManager, displayDevice, 30f);
+        assertTrue(callback.mDisplayChangedCalled);
+        callback.clear();
+
+        updateRenderFrameRate(displayManager, displayDevice, 30f);
+        assertFalse(callback.mDisplayChangedCalled);
+
+        updateRenderFrameRate(displayManager, displayDevice, 20f);
+        assertTrue(callback.mDisplayChangedCalled);
+        callback.clear();
+    }
+
+    /**
+     * Tests that the DisplayInfo is updated correctly with a render frame rate
+     */
+    @Test
+    public void testDisplayInfoRenderFrameRate() throws Exception {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mShortMockedInjector);
+        DisplayManagerService.BinderService displayManagerBinderService =
+                displayManager.new BinderService();
+        registerDefaultDisplays(displayManager);
+        displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
+
+        FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager,
+                new float[]{60f, 30f, 20f});
+        int displayId = getDisplayIdForDisplayDevice(displayManager, displayManagerBinderService,
+                displayDevice);
+        DisplayInfo displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
+        assertEquals(60f, displayInfo.getRefreshRate(), 0.01f);
+
+        updateRenderFrameRate(displayManager, displayDevice, 20f);
+        displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
+        assertEquals(20f, displayInfo.getRefreshRate(), 0.01f);
+    }
+
+    /**
+     * Tests that the mode reflects the render frame rate is in compat mode
+     */
+    @Test
+    @DisableCompatChanges({DisplayManagerService.DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE})
+    public  void testDisplayInfoRenderFrameRateModeCompat() throws Exception {
+        testDisplayInfoRenderFrameRateModeCompat(/*compatChangeEnabled*/ false);
+    }
+
+    /**
+     * Tests that the mode reflects the physical display refresh rate when not in compat mode.
+     */
+    @Test
+    @EnableCompatChanges({DisplayManagerService.DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE})
+    public  void testDisplayInfoRenderFrameRateMode() throws Exception {
+        testDisplayInfoRenderFrameRateModeCompat(/*compatChangeEnabled*/ true);
+    }
+
+    /**
+     * Tests that EVENT_DISPLAY_ADDED is sent when a display is added.
+     */
+    @Test
+    public void testShouldNotifyDisplayAdded_WhenNewDisplayDeviceIsAdded() {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mShortMockedInjector);
+        DisplayManagerService.BinderService displayManagerBinderService =
+                displayManager.new BinderService();
+
+        Handler handler = displayManager.getDisplayHandler();
+        waitForIdleHandler(handler);
+
+        // register display listener callback
+        FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback();
+        displayManagerBinderService.registerCallbackWithEventMask(
+                callback, STANDARD_DISPLAY_EVENTS);
+
+        waitForIdleHandler(handler);
+
+        createFakeDisplayDevice(displayManager, new float[]{60f});
+
+        waitForIdleHandler(handler);
+
+        assertFalse(callback.mDisplayChangedCalled);
+        assertFalse(callback.mDisplayRemovedCalled);
+        assertTrue(callback.mDisplayAddedCalled);
+    }
+
+    /**
+     * Tests that EVENT_DISPLAY_ADDED is not sent when a display is added and the
+     * client has a callback which is not subscribed to this event type.
+     */
+    @Test
+    public void testShouldNotNotifyDisplayAdded_WhenClientIsNotSubscribed() {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mShortMockedInjector);
+        DisplayManagerService.BinderService displayManagerBinderService =
+                displayManager.new BinderService();
+
+        Handler handler = displayManager.getDisplayHandler();
+        waitForIdleHandler(handler);
+
+        // register display listener callback
+        FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback();
+        long allEventsExceptDisplayAdded = STANDARD_DISPLAY_EVENTS
+                & ~DisplayManager.EVENT_FLAG_DISPLAY_ADDED;
+        displayManagerBinderService.registerCallbackWithEventMask(callback,
+                allEventsExceptDisplayAdded);
+
+        waitForIdleHandler(handler);
+
+        createFakeDisplayDevice(displayManager, new float[]{60f});
+
+        waitForIdleHandler(handler);
+
+        assertFalse(callback.mDisplayChangedCalled);
+        assertFalse(callback.mDisplayRemovedCalled);
+        assertFalse(callback.mDisplayAddedCalled);
+    }
+
+    /**
+     * Tests that EVENT_DISPLAY_REMOVED is sent when a display is removed.
+     */
+    @Test
+    public void testShouldNotifyDisplayRemoved_WhenDisplayDeviceIsRemoved() {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mShortMockedInjector);
+        DisplayManagerService.BinderService displayManagerBinderService =
+                displayManager.new BinderService();
+
+        Handler handler = displayManager.getDisplayHandler();
+        waitForIdleHandler(handler);
+
+        FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager,
+                new float[]{60f});
+
+        waitForIdleHandler(handler);
+
+        FakeDisplayManagerCallback callback = registerDisplayListenerCallback(
+                displayManager, displayManagerBinderService, displayDevice);
+
+        waitForIdleHandler(handler);
+
+        displayManager.getDisplayDeviceRepository()
+                .onDisplayDeviceEvent(displayDevice, DisplayAdapter.DISPLAY_DEVICE_EVENT_REMOVED);
+
+        waitForIdleHandler(handler);
+
+        assertFalse(callback.mDisplayChangedCalled);
+        assertTrue(callback.mDisplayRemovedCalled);
+        assertFalse(callback.mDisplayAddedCalled);
+    }
+
+    /**
+     * Tests that EVENT_DISPLAY_REMOVED is not sent when a display is added and the
+     * client has a callback which is not subscribed to this event type.
+     */
+    @Test
+    public void testShouldNotNotifyDisplayRemoved_WhenClientIsNotSubscribed() {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mShortMockedInjector);
+        DisplayManagerService.BinderService displayManagerBinderService =
+                displayManager.new BinderService();
+
+        Handler handler = displayManager.getDisplayHandler();
+        waitForIdleHandler(handler);
+
+        FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager,
+                new float[]{60f});
+
+        waitForIdleHandler(handler);
+
+        FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback();
+        long allEventsExceptDisplayRemoved = STANDARD_DISPLAY_EVENTS
+                & ~DisplayManager.EVENT_FLAG_DISPLAY_REMOVED;
+        displayManagerBinderService.registerCallbackWithEventMask(callback,
+                allEventsExceptDisplayRemoved);
+
+        waitForIdleHandler(handler);
+
+        displayManager.getDisplayDeviceRepository()
+                .onDisplayDeviceEvent(displayDevice, DisplayAdapter.DISPLAY_DEVICE_EVENT_REMOVED);
+
+        waitForIdleHandler(handler);
+
+        assertFalse(callback.mDisplayChangedCalled);
+        assertFalse(callback.mDisplayRemovedCalled);
+        assertFalse(callback.mDisplayAddedCalled);
+    }
+
+
+
+    @Test
+    public void testSettingTwoBrightnessConfigurationsOnMultiDisplay() {
+        Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
+        DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
+
+        // get the first two internal displays
+        Display[] displays = displayManager.getDisplays();
+        Display internalDisplayOne = null;
+        Display internalDisplayTwo = null;
+        for (Display display : displays) {
+            if (display.getType() == Display.TYPE_INTERNAL) {
+                if (internalDisplayOne == null) {
+                    internalDisplayOne = display;
+                } else {
+                    internalDisplayTwo = display;
+                    break;
+                }
+            }
+        }
+
+        // return if there are fewer than 2 displays on this device
+        if (internalDisplayOne == null || internalDisplayTwo == null) {
+            return;
+        }
+
+        final String uniqueDisplayIdOne = internalDisplayOne.getUniqueId();
+        final String uniqueDisplayIdTwo = internalDisplayTwo.getUniqueId();
+
+        BrightnessConfiguration configOne =
+                new BrightnessConfiguration.Builder(
+                        new float[]{0.0f, 12345.0f}, new float[]{15.0f, 400.0f})
+                        .setDescription("model:1").build();
+        BrightnessConfiguration configTwo =
+                new BrightnessConfiguration.Builder(
+                        new float[]{0.0f, 6789.0f}, new float[]{12.0f, 300.0f})
+                        .setDescription("model:2").build();
+
+        displayManager.setBrightnessConfigurationForDisplay(configOne,
+                uniqueDisplayIdOne);
+        displayManager.setBrightnessConfigurationForDisplay(configTwo,
+                uniqueDisplayIdTwo);
+
+        BrightnessConfiguration configFromOne =
+                displayManager.getBrightnessConfigurationForDisplay(uniqueDisplayIdOne);
+        BrightnessConfiguration configFromTwo =
+                displayManager.getBrightnessConfigurationForDisplay(uniqueDisplayIdTwo);
+
+        assertNotNull(configFromOne);
+        assertEquals(configOne, configFromOne);
+        assertEquals(configTwo, configFromTwo);
+
+    }
+
+    @Test
+    public void testHdrConversionModeEquals() {
+        assertEquals(
+                new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_FORCE, 2),
+                new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_FORCE, 2));
+        assertNotEquals(
+                new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_FORCE, 2),
+                new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_FORCE, 3));
+        assertEquals(
+                new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_SYSTEM),
+                new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_SYSTEM));
+        assertNotEquals(
+                new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_FORCE, 2),
+                new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_SYSTEM));
+    }
+
+    @Test
+    public void testCreateHdrConversionMode_withInvalidArguments_throwsException() {
+        assertThrows(
+                "preferredHdrOutputType must not be set if the conversion mode is "
+                        + "HDR_CONVERSION_PASSTHROUGH",
+                IllegalArgumentException.class,
+                () -> new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_PASSTHROUGH,
+                        Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION));
+    }
+
+    @Test
+    public void testSetHdrConversionModeInternal_withInvalidArguments_throwsException() {
+        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+        assertThrows("Expected DisplayManager to throw IllegalArgumentException when "
+                        + "preferredHdrOutputType is set and the conversion mode is "
+                        + "HDR_CONVERSION_SYSTEM",
+                IllegalArgumentException.class,
+                () -> displayManager.setHdrConversionModeInternal(new HdrConversionMode(
+                        HdrConversionMode.HDR_CONVERSION_SYSTEM,
+                        Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION)));
+    }
+
+    @Test
+    public void testSetAndGetHdrConversionModeInternal() {
+        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+        final HdrConversionMode mode = new HdrConversionMode(
+                HdrConversionMode.HDR_CONVERSION_FORCE,
+                Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION);
+        displayManager.setHdrConversionModeInternal(mode);
+        assertEquals(mode, displayManager.getHdrConversionModeSettingInternal());
+        assertEquals(mode.getConversionMode(), mHdrConversionMode);
+        assertEquals(mode.getPreferredHdrOutputType(), mPreferredHdrOutputType);
+    }
+
+    @Test
+    public void testHdrConversionMode_withMinimalPostProcessing() {
+        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+        DisplayManagerService.BinderService displayManagerBinderService =
+                displayManager.new BinderService();
+        registerDefaultDisplays(displayManager);
+        displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
+        FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager,
+                new float[]{60f, 30f, 20f});
+        int displayId = getDisplayIdForDisplayDevice(displayManager, displayManagerBinderService,
+                displayDevice);
+
+        final HdrConversionMode mode = new HdrConversionMode(
+                HdrConversionMode.HDR_CONVERSION_FORCE,
+                Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION);
+        displayManager.setHdrConversionModeInternal(mode);
+        assertEquals(mode, displayManager.getHdrConversionModeSettingInternal());
+
+        displayManager.setMinimalPostProcessingAllowed(true);
+        displayManager.setDisplayPropertiesInternal(displayId, false /* hasContent */,
+                30.0f /* requestedRefreshRate */,
+                displayDevice.getDisplayDeviceInfoLocked().modeId /* requestedModeId */,
+                30.0f /* requestedMinRefreshRate */, 120.0f /* requestedMaxRefreshRate */,
+                true /* preferMinimalPostProcessing */, false /* disableHdrConversion */,
+                true /* inTraversal */);
+
+        assertEquals(new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_PASSTHROUGH),
+                displayManager.getHdrConversionModeInternal());
+
+        displayManager.setDisplayPropertiesInternal(displayId, false /* hasContent */,
+                30.0f /* requestedRefreshRate */,
+                displayDevice.getDisplayDeviceInfoLocked().modeId /* requestedModeId */,
+                30.0f /* requestedMinRefreshRate */, 120.0f /* requestedMaxRefreshRate */,
+                false /* preferMinimalPostProcessing */, false /* disableHdrConversion */,
+                true /* inTraversal */);
+        assertEquals(mode, displayManager.getHdrConversionModeInternal());
+    }
+
+    @Test
+    public void testReturnsRefreshRateForDisplayAndSensor_proximitySensorSet() {
+        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+        DisplayManagerInternal localService = displayManager.new LocalService();
+        DisplayManagerService.BinderService displayManagerBinderService =
+                displayManager.new BinderService();
+        displayManager.overrideSensorManager(mSensorManager);
+
+        FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager, new float[]{60f});
+        displayDevice.mDisplayDeviceConfig = mMockDisplayDeviceConfig;
+        int displayId = getDisplayIdForDisplayDevice(displayManager, displayManagerBinderService,
+                displayDevice);
+
+        String testSensorName = "testName";
+        String testSensorType = "testType";
+        Sensor testSensor = TestUtils.createSensor(testSensorType, testSensorName);
+
+        DisplayDeviceConfig.SensorData sensorData = new DisplayDeviceConfig.SensorData();
+        sensorData.type = testSensorType;
+        sensorData.name = testSensorName;
+        sensorData.minRefreshRate = 10f;
+        sensorData.maxRefreshRate = 100f;
+
+        when(mMockDisplayDeviceConfig.getProximitySensor()).thenReturn(sensorData);
+        when(mSensorManager.getSensorList(Sensor.TYPE_ALL)).thenReturn(Collections.singletonList(
+                testSensor));
+
+        SurfaceControl.RefreshRateRange result = localService.getRefreshRateForDisplayAndSensor(
+                displayId, testSensorName, testSensorType);
+
+        assertNotNull(result);
+        assertEquals(result.min, sensorData.minRefreshRate, FLOAT_TOLERANCE);
+        assertEquals(result.max, sensorData.maxRefreshRate, FLOAT_TOLERANCE);
+    }
+
+    @Test
+    public void testReturnsRefreshRateForDisplayAndSensor_proximitySensorNotSet() {
+        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+        DisplayManagerInternal localService = displayManager.new LocalService();
+        DisplayManagerService.BinderService displayManagerBinderService =
+                displayManager.new BinderService();
+        displayManager.overrideSensorManager(mSensorManager);
+
+        FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager, new float[]{60f});
+        displayDevice.mDisplayDeviceConfig = mMockDisplayDeviceConfig;
+        int displayId = getDisplayIdForDisplayDevice(displayManager, displayManagerBinderService,
+                displayDevice);
+
+        String testSensorName = "testName";
+        String testSensorType = "testType";
+        Sensor testSensor = TestUtils.createSensor(testSensorType, testSensorName);
+
+        DisplayDeviceConfig.SensorData sensorData = new DisplayDeviceConfig.SensorData();
+        sensorData.type = testSensorType;
+        sensorData.name = testSensorName;
+        sensorData.minRefreshRate = 10f;
+        sensorData.maxRefreshRate = 100f;
+
+        when(mMockDisplayDeviceConfig.getProximitySensor()).thenReturn(null);
+        when(mSensorManager.getSensorList(Sensor.TYPE_ALL)).thenReturn(Collections.singletonList(
+                testSensor));
+
+        SurfaceControl.RefreshRateRange result = localService.getRefreshRateForDisplayAndSensor(
+                displayId, testSensorName, testSensorType);
+
+        assertNull(result);
+    }
+
+    private void testDisplayInfoFrameRateOverrideModeCompat(boolean compatChangeEnabled)
+            throws Exception {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mShortMockedInjector);
+        DisplayManagerService.BinderService displayManagerBinderService =
+                displayManager.new BinderService();
+        registerDefaultDisplays(displayManager);
+        displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
+
+        FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager,
+                new float[]{60f, 30f, 20f});
+        int displayId = getDisplayIdForDisplayDevice(displayManager, displayManagerBinderService,
+                displayDevice);
+        DisplayInfo displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
+        assertEquals(60f, displayInfo.getRefreshRate(), 0.01f);
+
+        updateFrameRateOverride(displayManager, displayDevice,
+                new DisplayEventReceiver.FrameRateOverride[]{
+                        new DisplayEventReceiver.FrameRateOverride(
+                                Process.myUid(), 20f),
+                        new DisplayEventReceiver.FrameRateOverride(
+                                Process.myUid() + 1, 30f)
+                });
+        displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
+        assertEquals(20f, displayInfo.getRefreshRate(), 0.01f);
+        Display.Mode expectedMode;
+        if (compatChangeEnabled) {
+            expectedMode = new Display.Mode(1, 100, 200, 60f);
+        } else {
+            expectedMode = new Display.Mode(3, 100, 200, 20f);
+        }
+        assertEquals(expectedMode, displayInfo.getMode());
+    }
+
+    private void testDisplayInfoRenderFrameRateModeCompat(boolean compatChangeEnabled)
+            throws Exception {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mShortMockedInjector);
+        DisplayManagerService.BinderService displayManagerBinderService =
+                displayManager.new BinderService();
+        registerDefaultDisplays(displayManager);
+        displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
+
+        FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager,
+                new float[]{60f, 30f, 20f});
+        int displayId = getDisplayIdForDisplayDevice(displayManager, displayManagerBinderService,
+                displayDevice);
+        DisplayInfo displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
+        assertEquals(60f, displayInfo.getRefreshRate(), 0.01f);
+
+        updateRenderFrameRate(displayManager, displayDevice, 20f);
+        displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
+        assertEquals(20f, displayInfo.getRefreshRate(), 0.01f);
+        Display.Mode expectedMode;
+        if (compatChangeEnabled) {
+            expectedMode = new Display.Mode(1, 100, 200, 60f);
+        } else {
+            expectedMode = new Display.Mode(3, 100, 200, 20f);
+        }
+        assertEquals(expectedMode, displayInfo.getMode());
+    }
+
+    private void testDisplayInfoNonNativeFrameRateOverrideMode(boolean compatChangeEnabled) {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mBasicInjector);
+        DisplayManagerService.BinderService displayManagerBinderService =
+                displayManager.new BinderService();
+        registerDefaultDisplays(displayManager);
+        displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
+
+        FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager,
+                new float[]{60f});
+        int displayId = getDisplayIdForDisplayDevice(displayManager, displayManagerBinderService,
+                displayDevice);
+        DisplayInfo displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
+        assertEquals(60f, displayInfo.getRefreshRate(), 0.01f);
+
+        updateFrameRateOverride(displayManager, displayDevice,
+                new DisplayEventReceiver.FrameRateOverride[]{
+                        new DisplayEventReceiver.FrameRateOverride(
+                                Process.myUid(), 20f)
+                });
+        displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
+        Display.Mode expectedMode;
+        if (compatChangeEnabled) {
+            expectedMode = new Display.Mode(1, 100, 200, 60f);
+        } else {
+            expectedMode = new Display.Mode(255, 100, 200, 20f);
+        }
+        assertEquals(expectedMode, displayInfo.getMode());
+    }
+
+    private int getDisplayIdForDisplayDevice(
+            DisplayManagerService displayManager,
+            DisplayManagerService.BinderService displayManagerBinderService,
+            FakeDisplayDevice displayDevice) {
+
+        final int[] displayIds = displayManagerBinderService.getDisplayIds(
+                /* includeDisabled= */ true);
+        assertTrue(displayIds.length > 0);
+        int displayId = Display.INVALID_DISPLAY;
+        for (int i = 0; i < displayIds.length; i++) {
+            DisplayDeviceInfo ddi = displayManager.getDisplayDeviceInfoInternal(displayIds[i]);
+            if (displayDevice.getDisplayDeviceInfoLocked().equals(ddi)) {
+                displayId = displayIds[i];
+                break;
+            }
+        }
+        assertFalse(displayId == Display.INVALID_DISPLAY);
+        return displayId;
+    }
+
+    private void updateDisplayDeviceInfo(DisplayManagerService displayManager,
+            FakeDisplayDevice displayDevice,
+            DisplayDeviceInfo displayDeviceInfo) {
+        displayDevice.setDisplayDeviceInfo(displayDeviceInfo);
+        displayManager.getDisplayDeviceRepository()
+                .onDisplayDeviceEvent(displayDevice, DisplayAdapter.DISPLAY_DEVICE_EVENT_CHANGED);
+        Handler handler = displayManager.getDisplayHandler();
+        waitForIdleHandler(handler);
+    }
+
+    private void updateFrameRateOverride(DisplayManagerService displayManager,
+            FakeDisplayDevice displayDevice,
+            DisplayEventReceiver.FrameRateOverride[] frameRateOverrides) {
+        DisplayDeviceInfo displayDeviceInfo = new DisplayDeviceInfo();
+        displayDeviceInfo.copyFrom(displayDevice.getDisplayDeviceInfoLocked());
+        displayDeviceInfo.frameRateOverrides = frameRateOverrides;
+        updateDisplayDeviceInfo(displayManager, displayDevice, displayDeviceInfo);
+    }
+
+    private void updateRenderFrameRate(DisplayManagerService displayManager,
+            FakeDisplayDevice displayDevice,
+            float renderFrameRate) {
+        DisplayDeviceInfo displayDeviceInfo = new DisplayDeviceInfo();
+        displayDeviceInfo.copyFrom(displayDevice.getDisplayDeviceInfoLocked());
+        displayDeviceInfo.renderFrameRate = renderFrameRate;
+        updateDisplayDeviceInfo(displayManager, displayDevice, displayDeviceInfo);
+    }
+
+    private void updateModeId(DisplayManagerService displayManager,
+            FakeDisplayDevice displayDevice,
+            int modeId) {
+        DisplayDeviceInfo displayDeviceInfo = new DisplayDeviceInfo();
+        displayDeviceInfo.copyFrom(displayDevice.getDisplayDeviceInfoLocked());
+        displayDeviceInfo.modeId = modeId;
+        updateDisplayDeviceInfo(displayManager, displayDevice, displayDeviceInfo);
+    }
+
+    private IDisplayManagerCallback registerDisplayChangeCallback(
+            DisplayManagerService displayManager) {
+        IDisplayManagerCallback displayChangesCallback = mock(IDisplayManagerCallback.class);
+        when(displayChangesCallback.asBinder()).thenReturn(new Binder());
+        DisplayManagerService.BinderService binderService = displayManager.new BinderService();
+        binderService.registerCallback(displayChangesCallback);
+        return displayChangesCallback;
+    }
+
+    private FakeDisplayManagerCallback registerDisplayListenerCallback(
+            DisplayManagerService displayManager,
+            DisplayManagerService.BinderService displayManagerBinderService,
+            FakeDisplayDevice displayDevice) {
+        // Find the display id of the added FakeDisplayDevice
+        int displayId = getDisplayIdForDisplayDevice(displayManager, displayManagerBinderService,
+                displayDevice);
+
+        Handler handler = displayManager.getDisplayHandler();
+        waitForIdleHandler(handler);
+
+        // register display listener callback
+        FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback(displayId);
+        displayManagerBinderService.registerCallbackWithEventMask(
+                callback, STANDARD_DISPLAY_EVENTS);
+        return callback;
+    }
+
+    private FakeDisplayDevice createFakeDisplayDevice(DisplayManagerService displayManager,
+            float[] refreshRates) {
+        FakeDisplayDevice displayDevice = new FakeDisplayDevice();
+        DisplayDeviceInfo displayDeviceInfo = new DisplayDeviceInfo();
+        int width = 100;
+        int height = 200;
+        displayDeviceInfo.supportedModes = new Display.Mode[refreshRates.length];
+        for (int i = 0; i < refreshRates.length; i++) {
+            displayDeviceInfo.supportedModes[i] =
+                    new Display.Mode(i + 1, width, height, refreshRates[i]);
+        }
+        displayDeviceInfo.modeId = 1;
+        displayDeviceInfo.renderFrameRate = displayDeviceInfo.supportedModes[0].getRefreshRate();
+        displayDeviceInfo.width = width;
+        displayDeviceInfo.height = height;
+        final Rect zeroRect = new Rect();
+        displayDeviceInfo.displayCutout = new DisplayCutout(
+                Insets.of(0, 10, 0, 0),
+                zeroRect, new Rect(0, 0, 10, 10), zeroRect, zeroRect);
+        displayDeviceInfo.flags = DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY;
+        displayDeviceInfo.address = new TestUtils.TestDisplayAddress();
+        displayDevice.setDisplayDeviceInfo(displayDeviceInfo);
+        displayManager.getDisplayDeviceRepository()
+                .onDisplayDeviceEvent(displayDevice, DisplayAdapter.DISPLAY_DEVICE_EVENT_ADDED);
+        return displayDevice;
+    }
+
+    private void registerDefaultDisplays(DisplayManagerService displayManager) {
+        Handler handler = displayManager.getDisplayHandler();
+        // Would prefer to call displayManager.onStart() directly here but it performs binderService
+        // registration which triggers security exceptions when running from a test.
+        handler.sendEmptyMessage(MSG_REGISTER_DEFAULT_DISPLAY_ADAPTERS);
+        waitForIdleHandler(handler);
+    }
+
+    private void waitForIdleHandler(Handler handler) {
+        waitForIdleHandler(handler, Duration.ofSeconds(1));
+    }
+
+    private void waitForIdleHandler(Handler handler, Duration timeout) {
+        final MessageQueue queue = handler.getLooper().getQueue();
+        final CountDownLatch latch = new CountDownLatch(1);
+        queue.addIdleHandler(() -> {
+            latch.countDown();
+            // Remove idle handler
+            return false;
+        });
+        try {
+            latch.await(timeout.toMillis(), TimeUnit.MILLISECONDS);
+        } catch (InterruptedException e) {
+            fail("Interrupted unexpectedly: " + e);
+        }
+    }
+
+    private void resetConfigToIgnoreSensorManager(Context context) {
+        final Resources res = Mockito.spy(context.getResources());
+        doReturn(new int[]{-1}).when(res).getIntArray(R.array
+                .config_ambientThresholdsOfPeakRefreshRate);
+        doReturn(new int[]{-1}).when(res).getIntArray(R.array
+                .config_brightnessThresholdsOfPeakRefreshRate);
+        doReturn(new int[]{-1}).when(res).getIntArray(R.array
+                .config_highDisplayBrightnessThresholdsOfFixedRefreshRate);
+        doReturn(new int[]{-1}).when(res).getIntArray(R.array
+                .config_highAmbientBrightnessThresholdsOfFixedRefreshRate);
+
+        when(context.getResources()).thenReturn(res);
+    }
+
+    private class FakeDisplayManagerCallback extends IDisplayManagerCallback.Stub {
+        int mDisplayId;
+        boolean mDisplayAddedCalled = false;
+        boolean mDisplayChangedCalled = false;
+        boolean mDisplayRemovedCalled = false;
+
+        FakeDisplayManagerCallback(int displayId) {
+            mDisplayId = displayId;
+        }
+
+        FakeDisplayManagerCallback() {
+            mDisplayId = -1;
+        }
+
+        @Override
+        public void onDisplayEvent(int displayId, int event) {
+            if (mDisplayId != -1 && displayId != mDisplayId) {
+                return;
+            }
+
+            if (event == DisplayManagerGlobal.EVENT_DISPLAY_ADDED) {
+                mDisplayAddedCalled = true;
+            }
+
+            if (event == DisplayManagerGlobal.EVENT_DISPLAY_CHANGED) {
+                mDisplayChangedCalled = true;
+            }
+
+            if (event == DisplayManagerGlobal.EVENT_DISPLAY_REMOVED) {
+                mDisplayRemovedCalled = true;
+            }
+        }
+
+        public void clear() {
+            mDisplayAddedCalled = false;
+            mDisplayChangedCalled = false;
+            mDisplayRemovedCalled = false;
+        }
+    }
+
+    private class FakeDisplayDevice extends DisplayDevice {
+        private DisplayDeviceInfo mDisplayDeviceInfo;
+
+        FakeDisplayDevice() {
+            super(null, null, "", mContext);
+        }
+
+        public void setDisplayDeviceInfo(DisplayDeviceInfo displayDeviceInfo) {
+            mDisplayDeviceInfo = displayDeviceInfo;
+        }
+
+        @Override
+        public boolean hasStableUniqueId() {
+            return false;
+        }
+
+        @Override
+        public DisplayDeviceInfo getDisplayDeviceInfoLocked() {
+            return mDisplayDeviceInfo;
+        }
+    }
+}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java
new file mode 100644
index 0000000..8a9a851
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java
@@ -0,0 +1,1411 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+
+import static org.junit.Assert.assertNotNull;
+import static org.mockito.ArgumentMatchers.any;
+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.isA;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.hardware.Sensor;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.hardware.display.DisplayManagerInternal.DisplayPowerCallbacks;
+import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.PowerManager;
+import android.os.SystemProperties;
+import android.os.test.TestLooper;
+import android.provider.Settings;
+import android.testing.TestableContext;
+import android.util.FloatProperty;
+import android.view.Display;
+import android.view.DisplayInfo;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.modules.utils.testing.ExtendedMockitoRule;
+import com.android.server.LocalServices;
+import com.android.server.am.BatteryStatsService;
+import com.android.server.display.RampAnimator.DualRampAnimator;
+import com.android.server.display.brightness.BrightnessEvent;
+import com.android.server.display.color.ColorDisplayService;
+import com.android.server.display.layout.Layout;
+import com.android.server.display.whitebalance.DisplayWhiteBalanceController;
+import com.android.server.policy.WindowManagerPolicy;
+import com.android.server.testutils.OffsettableClock;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.quality.Strictness;
+import org.mockito.stubbing.Answer;
+
+import java.util.List;
+
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public final class DisplayPowerController2Test {
+    private static final int DISPLAY_ID = Display.DEFAULT_DISPLAY;
+    private static final String UNIQUE_ID = "unique_id_test123";
+    private static final int FOLLOWER_DISPLAY_ID = DISPLAY_ID + 1;
+    private static final String FOLLOWER_UNIQUE_ID = "unique_id_456";
+    private static final int SECOND_FOLLOWER_DISPLAY_ID = FOLLOWER_DISPLAY_ID + 1;
+    private static final String SECOND_FOLLOWER_UNIQUE_DISPLAY_ID = "unique_id_789";
+    private static final float PROX_SENSOR_MAX_RANGE = 5;
+    private static final float BRIGHTNESS_RAMP_RATE_FAST_DECREASE = 0.3f;
+    private static final float BRIGHTNESS_RAMP_RATE_FAST_INCREASE = 0.4f;
+    private static final float BRIGHTNESS_RAMP_RATE_SLOW_DECREASE = 0.1f;
+    private static final float BRIGHTNESS_RAMP_RATE_SLOW_INCREASE = 0.2f;
+
+    private OffsettableClock mClock;
+    private TestLooper mTestLooper;
+    private Handler mHandler;
+    private DisplayPowerControllerHolder mHolder;
+    private Sensor mProxSensor;
+
+    @Mock
+    private DisplayPowerCallbacks mDisplayPowerCallbacksMock;
+    @Mock
+    private SensorManager mSensorManagerMock;
+    @Mock
+    private DisplayBlanker mDisplayBlankerMock;
+    @Mock
+    private BrightnessTracker mBrightnessTrackerMock;
+    @Mock
+    private WindowManagerPolicy mWindowManagerPolicyMock;
+    @Mock
+    private PowerManager mPowerManagerMock;
+    @Mock
+    private ColorDisplayService.ColorDisplayServiceInternal mCdsiMock;
+    @Mock
+    private DisplayWhiteBalanceController mDisplayWhiteBalanceControllerMock;
+    @Captor
+    private ArgumentCaptor<SensorEventListener> mSensorEventListenerCaptor;
+
+    @Rule
+    public final TestableContext mContext = new TestableContext(
+            InstrumentationRegistry.getInstrumentation().getContext());
+
+    @Rule
+    public final ExtendedMockitoRule mExtendedMockitoRule =
+            new ExtendedMockitoRule.Builder(this)
+                    .setStrictness(Strictness.LENIENT)
+                    .spyStatic(SystemProperties.class)
+                    .spyStatic(BatteryStatsService.class)
+                    .build();
+
+    @Before
+    public void setUp() throws Exception {
+        mClock = new OffsettableClock.Stopped();
+        mTestLooper = new TestLooper(mClock::now);
+        mHandler = new Handler(mTestLooper.getLooper());
+
+        // Put the system into manual brightness by default, just to minimize unexpected events and
+        // have a consistent starting state
+        Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+                Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL);
+
+        addLocalServiceMock(WindowManagerPolicy.class, mWindowManagerPolicyMock);
+        addLocalServiceMock(ColorDisplayService.ColorDisplayServiceInternal.class,
+                mCdsiMock);
+
+        mContext.addMockSystemService(PowerManager.class, mPowerManagerMock);
+
+        doAnswer((Answer<Void>) invocationOnMock -> null).when(() ->
+                SystemProperties.set(anyString(), any()));
+        doAnswer((Answer<Void>) invocationOnMock -> null).when(BatteryStatsService::getService);
+
+        setUpSensors();
+        mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
+    }
+
+    @After
+    public void tearDown() {
+        LocalServices.removeServiceForTest(WindowManagerPolicy.class);
+        LocalServices.removeServiceForTest(ColorDisplayService.ColorDisplayServiceInternal.class);
+    }
+
+    @Test
+    public void testReleaseProxSuspendBlockersOnExit() throws Exception {
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+        // Send a display power request
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
+        dpr.useProximitySensor = true;
+        mHolder.dpc.requestPowerState(dpr, false /* waitForNegativeProximity */);
+
+        // Run updatePowerState to start listener for the prox sensor
+        advanceTime(1);
+
+        SensorEventListener listener = getSensorEventListener(mProxSensor);
+        assertNotNull(listener);
+
+        listener.onSensorChanged(TestUtils.createSensorEvent(mProxSensor, /* value= */ 5));
+        advanceTime(1);
+
+        // two times, one for unfinished business and one for proximity
+        verify(mHolder.wakelockController, times(2)).acquireWakelock(
+                WakelockController.WAKE_LOCK_UNFINISHED_BUSINESS);
+        verify(mHolder.wakelockController).acquireWakelock(
+                WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE);
+
+        mHolder.dpc.stop();
+        advanceTime(1);
+        // two times, one for unfinished business and one for proximity
+        verify(mHolder.wakelockController, times(2)).acquireWakelock(
+                WakelockController.WAKE_LOCK_UNFINISHED_BUSINESS);
+        verify(mHolder.wakelockController).acquireWakelock(
+                WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE);
+    }
+
+    @Test
+    public void testScreenOffBecauseOfProximity() throws Exception {
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+        // Send a display power request
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
+        dpr.useProximitySensor = true;
+        mHolder.dpc.requestPowerState(dpr, false /* waitForNegativeProximity */);
+
+        // Run updatePowerState to start listener for the prox sensor
+        advanceTime(1);
+
+        SensorEventListener listener = getSensorEventListener(mProxSensor);
+        assertNotNull(listener);
+
+        // Send a positive proximity event
+        listener.onSensorChanged(TestUtils.createSensorEvent(mProxSensor, /* value= */ 1));
+        advanceTime(1);
+
+        // The display should have been turned off
+        verify(mHolder.displayPowerState).setScreenState(Display.STATE_OFF);
+
+        clearInvocations(mHolder.displayPowerState);
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_OFF);
+        // Send a negative proximity event
+        listener.onSensorChanged(TestUtils.createSensorEvent(mProxSensor,
+                (int) PROX_SENSOR_MAX_RANGE + 1));
+        // Advance time by less than PROXIMITY_SENSOR_NEGATIVE_DEBOUNCE_DELAY
+        advanceTime(1);
+
+        // The prox sensor is debounced so the display should not have been turned back on yet
+        verify(mHolder.displayPowerState, never()).setScreenState(Display.STATE_ON);
+
+        // Advance time by more than PROXIMITY_SENSOR_NEGATIVE_DEBOUNCE_DELAY
+        advanceTime(1000);
+
+        // The display should have been turned back on
+        verify(mHolder.displayPowerState).setScreenState(Display.STATE_ON);
+    }
+
+    @Test
+    public void testScreenOffBecauseOfProximity_ProxSensorGone() throws Exception {
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+        // Send a display power request
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
+        dpr.useProximitySensor = true;
+        mHolder.dpc.requestPowerState(dpr, false /* waitForNegativeProximity */);
+
+        // Run updatePowerState to start listener for the prox sensor
+        advanceTime(1);
+
+        SensorEventListener listener = getSensorEventListener(mProxSensor);
+        assertNotNull(listener);
+
+        // Send a positive proximity event
+        listener.onSensorChanged(TestUtils.createSensorEvent(mProxSensor, /* value= */ 1));
+        advanceTime(1);
+
+        // The display should have been turned off
+        verify(mHolder.displayPowerState).setScreenState(Display.STATE_OFF);
+
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_OFF);
+        // The display device changes and we no longer have a prox sensor
+        reset(mSensorManagerMock);
+        setUpDisplay(DISPLAY_ID, "new_unique_id", mHolder.display, mock(DisplayDevice.class),
+                mock(DisplayDeviceConfig.class), /* isEnabled= */ true);
+        mHolder.dpc.onDisplayChanged(mHolder.hbmMetadata, Layout.NO_LEAD_DISPLAY);
+
+        advanceTime(1); // Run updatePowerState
+
+        // The display should have been turned back on and the listener should have been
+        // unregistered
+        verify(mHolder.displayPowerState).setScreenState(Display.STATE_ON);
+        verify(mSensorManagerMock).unregisterListener(listener);
+    }
+
+    @Test
+    public void testProximitySensorListenerNotRegisteredForNonDefaultDisplay() {
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+        // send a display power request
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
+        dpr.useProximitySensor = true;
+        final DisplayPowerControllerHolder followerDpc =
+                createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
+        followerDpc.dpc.requestPowerState(dpr, false /* waitForNegativeProximity */);
+
+        // Run updatePowerState
+        advanceTime(1);
+
+        verify(mSensorManagerMock, never()).registerListener(any(SensorEventListener.class),
+                eq(mProxSensor), anyInt(), any(Handler.class));
+    }
+
+    @Test
+    public void testDisplayBrightnessFollowers_BothDpcsSupportNits() {
+        DisplayPowerControllerHolder followerDpc =
+                createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
+        when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+        when(followerDpc.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        followerDpc.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
+                ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
+        verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture());
+        BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
+
+        mHolder.dpc.addDisplayBrightnessFollower(followerDpc.dpc);
+
+        // Test different float scale values
+        float leadBrightness = 0.3f;
+        float followerBrightness = 0.4f;
+        float nits = 300;
+        when(mHolder.automaticBrightnessController.convertToNits(leadBrightness)).thenReturn(nits);
+        when(followerDpc.automaticBrightnessController.convertToFloatScale(nits))
+                .thenReturn(followerBrightness);
+        when(mHolder.brightnessSetting.getBrightness()).thenReturn(leadBrightness);
+        listener.onBrightnessChanged(leadBrightness);
+        advanceTime(1); // Send messages, run updatePowerState
+        verify(mHolder.animator).animateTo(eq(leadBrightness), anyFloat(), anyFloat());
+        verify(followerDpc.animator).animateTo(eq(followerBrightness), anyFloat(),
+                anyFloat());
+
+        clearInvocations(mHolder.animator, followerDpc.animator);
+
+        // Test the same float scale value
+        float brightness = 0.6f;
+        nits = 600;
+        when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
+        when(followerDpc.automaticBrightnessController.convertToFloatScale(nits))
+                .thenReturn(brightness);
+        when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
+        listener.onBrightnessChanged(brightness);
+        advanceTime(1); // Send messages, run updatePowerState
+        verify(mHolder.animator).animateTo(eq(brightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+        verify(followerDpc.animator).animateTo(eq(brightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+    }
+
+    @Test
+    public void testDisplayBrightnessFollowers_FollowerDoesNotSupportNits() {
+        DisplayPowerControllerHolder followerDpc =
+                createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
+        when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+        when(followerDpc.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        followerDpc.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
+                ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
+        verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture());
+        BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
+
+        mHolder.dpc.addDisplayBrightnessFollower(followerDpc.dpc);
+
+        float brightness = 0.3f;
+        when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(300f);
+        when(followerDpc.automaticBrightnessController.convertToFloatScale(anyFloat()))
+                .thenReturn(PowerManager.BRIGHTNESS_INVALID_FLOAT);
+        when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
+        listener.onBrightnessChanged(brightness);
+        advanceTime(1); // Send messages, run updatePowerState
+        verify(mHolder.animator).animateTo(eq(brightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+        verify(followerDpc.animator).animateTo(eq(brightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+    }
+
+    @Test
+    public void testDisplayBrightnessFollowers_LeadDpcDoesNotSupportNits() {
+        DisplayPowerControllerHolder followerDpc =
+                createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
+        when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+        when(followerDpc.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        followerDpc.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
+                ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
+        verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture());
+        BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
+
+        mHolder.dpc.addDisplayBrightnessFollower(followerDpc.dpc);
+
+        float brightness = 0.3f;
+        when(mHolder.automaticBrightnessController.convertToNits(anyFloat())).thenReturn(-1f);
+        when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
+        listener.onBrightnessChanged(brightness);
+        advanceTime(1); // Send messages, run updatePowerState
+        verify(mHolder.animator).animateTo(eq(brightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+        verify(followerDpc.animator).animateTo(eq(brightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+    }
+
+    @Test
+    public void testDisplayBrightnessFollowers_NeitherDpcSupportsNits() {
+        DisplayPowerControllerHolder followerDpc =
+                createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
+        when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+        when(followerDpc.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        followerDpc.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
+                ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
+        verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture());
+        BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
+
+        mHolder.dpc.addDisplayBrightnessFollower(followerDpc.dpc);
+
+        float brightness = 0.3f;
+        when(mHolder.automaticBrightnessController.convertToNits(anyFloat())).thenReturn(-1f);
+        when(followerDpc.automaticBrightnessController.convertToFloatScale(anyFloat()))
+                .thenReturn(PowerManager.BRIGHTNESS_INVALID_FLOAT);
+        when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
+        listener.onBrightnessChanged(brightness);
+        advanceTime(1); // Send messages, run updatePowerState
+        verify(mHolder.animator).animateTo(eq(brightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+        verify(followerDpc.animator).animateTo(eq(brightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+    }
+
+    @Test
+    public void testDisplayBrightnessFollowers_AutomaticBrightness() {
+        Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+        DisplayPowerControllerHolder followerDpc =
+                createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+        when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+        when(followerDpc.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+        float leadBrightness = 0.1f;
+        float rawLeadBrightness = 0.3f;
+        float followerBrightness = 0.4f;
+        float nits = 300;
+        float ambientLux = 3000;
+        when(mHolder.automaticBrightnessController.getRawAutomaticScreenBrightness())
+                .thenReturn(rawLeadBrightness);
+        when(mHolder.automaticBrightnessController
+                .getAutomaticScreenBrightness(any(BrightnessEvent.class)))
+                .thenReturn(leadBrightness);
+        when(mHolder.automaticBrightnessController.convertToNits(rawLeadBrightness))
+                .thenReturn(nits);
+        when(mHolder.automaticBrightnessController.getAmbientLux()).thenReturn(ambientLux);
+        when(followerDpc.automaticBrightnessController.convertToFloatScale(nits))
+                .thenReturn(followerBrightness);
+
+        mHolder.dpc.addDisplayBrightnessFollower(followerDpc.dpc);
+
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        followerDpc.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        verify(mHolder.animator).animateTo(eq(leadBrightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+        // One triggered by handleBrightnessModeChange, another triggered by setBrightnessToFollow
+        verify(followerDpc.hbmController, times(2)).onAmbientLuxChange(ambientLux);
+        verify(followerDpc.animator, times(2)).animateTo(eq(followerBrightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+
+        when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(leadBrightness);
+        when(followerDpc.displayPowerState.getScreenBrightness()).thenReturn(followerBrightness);
+        clearInvocations(mHolder.animator, followerDpc.animator);
+
+        leadBrightness = 0.05f;
+        rawLeadBrightness = 0.2f;
+        followerBrightness = 0.3f;
+        nits = 200;
+        ambientLux = 2000;
+        when(mHolder.automaticBrightnessController.getRawAutomaticScreenBrightness())
+                .thenReturn(rawLeadBrightness);
+        when(mHolder.automaticBrightnessController
+                .getAutomaticScreenBrightness(any(BrightnessEvent.class)))
+                .thenReturn(leadBrightness);
+        when(mHolder.automaticBrightnessController.convertToNits(rawLeadBrightness))
+                .thenReturn(nits);
+        when(mHolder.automaticBrightnessController.getAmbientLux()).thenReturn(ambientLux);
+        when(followerDpc.automaticBrightnessController.convertToFloatScale(nits))
+                .thenReturn(followerBrightness);
+
+        mHolder.dpc.updateBrightness();
+        advanceTime(1); // Run updatePowerState
+
+        // The second time, the animation rate should be slow
+        verify(mHolder.animator).animateTo(eq(leadBrightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_SLOW_DECREASE));
+        verify(followerDpc.hbmController).onAmbientLuxChange(ambientLux);
+        verify(followerDpc.animator).animateTo(eq(followerBrightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_SLOW_DECREASE));
+    }
+
+    @Test
+    public void testDisplayBrightnessFollowersRemoval_RemoveSingleFollower() {
+        DisplayPowerControllerHolder followerDpc = createDisplayPowerController(FOLLOWER_DISPLAY_ID,
+                FOLLOWER_UNIQUE_ID);
+        DisplayPowerControllerHolder secondFollowerDpc = createDisplayPowerController(
+                SECOND_FOLLOWER_DISPLAY_ID, SECOND_FOLLOWER_UNIQUE_DISPLAY_ID);
+        when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+        when(followerDpc.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+        when(secondFollowerDpc.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        followerDpc.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        secondFollowerDpc.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
+                ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
+        verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture());
+        BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
+
+        // Set the initial brightness on the DPC we're going to remove so we have a fixed value for
+        // it to return to.
+        listenerCaptor = ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
+        verify(followerDpc.brightnessSetting).registerListener(listenerCaptor.capture());
+        BrightnessSetting.BrightnessSettingListener followerListener = listenerCaptor.getValue();
+        final float initialFollowerBrightness = 0.3f;
+        when(followerDpc.brightnessSetting.getBrightness()).thenReturn(initialFollowerBrightness);
+        followerListener.onBrightnessChanged(initialFollowerBrightness);
+        advanceTime(1);
+        verify(followerDpc.animator).animateTo(eq(initialFollowerBrightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+
+        when(followerDpc.displayPowerState.getScreenBrightness())
+                .thenReturn(initialFollowerBrightness);
+
+        mHolder.dpc.addDisplayBrightnessFollower(followerDpc.dpc);
+        mHolder.dpc.addDisplayBrightnessFollower(secondFollowerDpc.dpc);
+        clearInvocations(followerDpc.animator);
+
+        // Validate both followers are correctly registered and receiving brightness updates
+        float brightness = 0.6f;
+        float nits = 600;
+        when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
+        when(followerDpc.automaticBrightnessController.convertToFloatScale(nits))
+                .thenReturn(brightness);
+        when(secondFollowerDpc.automaticBrightnessController.convertToFloatScale(nits))
+                .thenReturn(brightness);
+        when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
+        listener.onBrightnessChanged(brightness);
+        advanceTime(1); // Send messages, run updatePowerState
+        verify(mHolder.animator).animateTo(eq(brightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+        verify(followerDpc.animator).animateTo(eq(brightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+        verify(secondFollowerDpc.animator).animateTo(eq(brightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+
+        when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(brightness);
+        when(followerDpc.displayPowerState.getScreenBrightness()).thenReturn(brightness);
+        when(secondFollowerDpc.displayPowerState.getScreenBrightness()).thenReturn(brightness);
+        clearInvocations(mHolder.animator, followerDpc.animator, secondFollowerDpc.animator);
+
+        // Remove the first follower and validate it goes back to its original brightness.
+        mHolder.dpc.removeDisplayBrightnessFollower(followerDpc.dpc);
+        advanceTime(1);
+        verify(followerDpc.animator).animateTo(eq(initialFollowerBrightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_DECREASE));
+
+        when(followerDpc.displayPowerState.getScreenBrightness())
+                .thenReturn(initialFollowerBrightness);
+        clearInvocations(followerDpc.animator);
+
+        // Change the brightness of the lead display and validate only the second follower responds
+        brightness = 0.7f;
+        nits = 700;
+        when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
+        when(followerDpc.automaticBrightnessController.convertToFloatScale(nits))
+                .thenReturn(brightness);
+        when(secondFollowerDpc.automaticBrightnessController.convertToFloatScale(nits))
+                .thenReturn(brightness);
+        when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
+        listener.onBrightnessChanged(brightness);
+        advanceTime(1); // Send messages, run updatePowerState
+        verify(mHolder.animator).animateTo(eq(brightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+        verify(followerDpc.animator, never()).animateTo(anyFloat(), anyFloat(), anyFloat());
+        verify(secondFollowerDpc.animator).animateTo(eq(brightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+    }
+
+    @Test
+    public void testDisplayBrightnessFollowersRemoval_RemoveAllFollowers() {
+        DisplayPowerControllerHolder followerHolder =
+                createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
+        DisplayPowerControllerHolder secondFollowerHolder =
+                createDisplayPowerController(SECOND_FOLLOWER_DISPLAY_ID,
+                        SECOND_FOLLOWER_UNIQUE_DISPLAY_ID);
+        when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+        when(followerHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+        when(secondFollowerHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        followerHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        secondFollowerHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
+                ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
+        verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture());
+        BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
+
+        // Set the initial brightness on the DPCs we're going to remove so we have a fixed value for
+        // it to return to.
+        listenerCaptor = ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
+        verify(followerHolder.brightnessSetting).registerListener(listenerCaptor.capture());
+        BrightnessSetting.BrightnessSettingListener followerListener = listenerCaptor.getValue();
+        listenerCaptor = ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
+        verify(secondFollowerHolder.brightnessSetting).registerListener(listenerCaptor.capture());
+        BrightnessSetting.BrightnessSettingListener secondFollowerListener =
+                listenerCaptor.getValue();
+        final float initialFollowerBrightness = 0.3f;
+        when(followerHolder.brightnessSetting.getBrightness()).thenReturn(
+                initialFollowerBrightness);
+        when(secondFollowerHolder.brightnessSetting.getBrightness()).thenReturn(
+                initialFollowerBrightness);
+        followerListener.onBrightnessChanged(initialFollowerBrightness);
+        secondFollowerListener.onBrightnessChanged(initialFollowerBrightness);
+        advanceTime(1);
+        verify(followerHolder.animator).animateTo(eq(initialFollowerBrightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+        verify(secondFollowerHolder.animator).animateTo(eq(initialFollowerBrightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+
+        when(followerHolder.displayPowerState.getScreenBrightness())
+                .thenReturn(initialFollowerBrightness);
+        when(secondFollowerHolder.displayPowerState.getScreenBrightness())
+                .thenReturn(initialFollowerBrightness);
+
+        mHolder.dpc.addDisplayBrightnessFollower(followerHolder.dpc);
+        mHolder.dpc.addDisplayBrightnessFollower(secondFollowerHolder.dpc);
+        clearInvocations(followerHolder.animator, secondFollowerHolder.animator);
+
+        // Validate both followers are correctly registered and receiving brightness updates
+        float brightness = 0.6f;
+        float nits = 600;
+        when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
+        when(followerHolder.automaticBrightnessController.convertToFloatScale(nits))
+                .thenReturn(brightness);
+        when(secondFollowerHolder.automaticBrightnessController.convertToFloatScale(nits))
+                .thenReturn(brightness);
+        when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
+        listener.onBrightnessChanged(brightness);
+        advanceTime(1); // Send messages, run updatePowerState
+        verify(mHolder.animator).animateTo(eq(brightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+        verify(followerHolder.animator).animateTo(eq(brightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+        verify(secondFollowerHolder.animator).animateTo(eq(brightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+
+        when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(brightness);
+        when(followerHolder.displayPowerState.getScreenBrightness()).thenReturn(brightness);
+        when(secondFollowerHolder.displayPowerState.getScreenBrightness()).thenReturn(brightness);
+        clearInvocations(mHolder.animator, followerHolder.animator, secondFollowerHolder.animator);
+
+        // Stop the lead DPC and validate that the followers go back to their original brightness.
+        mHolder.dpc.stop();
+        advanceTime(1);
+        verify(followerHolder.animator).animateTo(eq(initialFollowerBrightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_DECREASE));
+        verify(secondFollowerHolder.animator).animateTo(eq(initialFollowerBrightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_DECREASE));
+        clearInvocations(followerHolder.animator, secondFollowerHolder.animator);
+    }
+
+    @Test
+    public void testDoesNotSetScreenStateForNonDefaultDisplayUntilBootCompleted() {
+        // We should still set screen state for the default display
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+        verify(mHolder.displayPowerState, times(2)).setScreenState(anyInt());
+
+        mHolder = createDisplayPowerController(42, UNIQUE_ID);
+
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+        verify(mHolder.displayPowerState, never()).setScreenState(anyInt());
+
+        mHolder.dpc.onBootCompleted();
+        advanceTime(1); // Run updatePowerState
+        verify(mHolder.displayPowerState).setScreenState(anyInt());
+    }
+
+    @Test
+    public void testSetScreenOffBrightnessSensorEnabled_DisplayIsOff() {
+        Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_OFF);
+
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        dpr.policy = DisplayPowerRequest.POLICY_OFF;
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        verify(mHolder.screenOffBrightnessSensorController, atLeastOnce())
+                .setLightSensorEnabled(true);
+
+        // The display turns on and we use the brightness value recommended by
+        // ScreenOffBrightnessSensorController
+        clearInvocations(mHolder.screenOffBrightnessSensorController);
+        float brightness = 0.14f;
+        when(mHolder.screenOffBrightnessSensorController.getAutomaticScreenBrightness())
+                .thenReturn(brightness);
+        dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+        when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness(
+                any(BrightnessEvent.class))).thenReturn(PowerManager.BRIGHTNESS_INVALID_FLOAT);
+
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        verify(mHolder.screenOffBrightnessSensorController, atLeastOnce())
+                .getAutomaticScreenBrightness();
+        verify(mHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
+    }
+
+    @Test
+    public void testSetScreenOffBrightnessSensorEnabled_DisplayIsInDoze() {
+        mContext.getOrCreateTestableResources().addOverride(
+                com.android.internal.R.bool.config_allowAutoBrightnessWhileDozing, false);
+        mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
+
+        Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_DOZE);
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        dpr.policy = DisplayPowerRequest.POLICY_DOZE;
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        verify(mHolder.screenOffBrightnessSensorController, atLeastOnce())
+                .setLightSensorEnabled(true);
+
+        // The display turns on and we use the brightness value recommended by
+        // ScreenOffBrightnessSensorController
+        clearInvocations(mHolder.screenOffBrightnessSensorController);
+        float brightness = 0.14f;
+        when(mHolder.screenOffBrightnessSensorController.getAutomaticScreenBrightness())
+                .thenReturn(brightness);
+        dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+        when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness(
+                any(BrightnessEvent.class))).thenReturn(PowerManager.BRIGHTNESS_INVALID_FLOAT);
+
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        verify(mHolder.screenOffBrightnessSensorController, atLeastOnce())
+                .getAutomaticScreenBrightness();
+        verify(mHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
+    }
+
+    @Test
+    public void testSetScreenOffBrightnessSensorDisabled_AutoBrightnessIsDisabled() {
+        // Tests are set up with manual brightness by default, so no need to set it here.
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        dpr.policy = DisplayPowerRequest.POLICY_OFF;
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        verify(mHolder.screenOffBrightnessSensorController, atLeastOnce())
+                .setLightSensorEnabled(false);
+    }
+
+    @Test
+    public void testSetScreenOffBrightnessSensorDisabled_DisplayIsDisabled() {
+        Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+        mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID, /* isEnabled= */ false);
+
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        verify(mHolder.screenOffBrightnessSensorController, atLeastOnce())
+                .setLightSensorEnabled(false);
+    }
+
+    @Test
+    public void testSetScreenOffBrightnessSensorDisabled_DisplayIsOn() {
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
+
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        verify(mHolder.screenOffBrightnessSensorController, atLeastOnce())
+                .setLightSensorEnabled(false);
+    }
+
+    @Test
+    public void testSetScreenOffBrightnessSensorDisabled_DisplayIsAFollower() {
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        dpr.policy = DisplayPowerRequest.POLICY_OFF;
+
+        mHolder.dpc.onDisplayChanged(mHolder.hbmMetadata, /* leadDisplayId= */ 42);
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        verify(mHolder.screenOffBrightnessSensorController, atLeastOnce())
+                .setLightSensorEnabled(false);
+    }
+
+    @Test
+    public void testStopScreenOffBrightnessSensorControllerWhenDisplayDeviceChanges() {
+        // New display device
+        setUpDisplay(DISPLAY_ID, "new_unique_id", mHolder.display, mock(DisplayDevice.class),
+                mock(DisplayDeviceConfig.class), /* isEnabled= */ true);
+
+        mHolder.dpc.onDisplayChanged(mHolder.hbmMetadata, Layout.NO_LEAD_DISPLAY);
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        verify(mHolder.screenOffBrightnessSensorController).stop();
+    }
+
+    @Test
+    public void testAutoBrightnessEnabled_DisplayIsOn() {
+        Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        verify(mHolder.automaticBrightnessController).configure(
+                AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED,
+                /* configuration= */ null, PowerManager.BRIGHTNESS_INVALID_FLOAT,
+                /* userChangedBrightness= */ false, /* adjustment= */ 0,
+                /* userChangedAutoBrightnessAdjustment= */ false, DisplayPowerRequest.POLICY_BRIGHT,
+                /* shouldResetShortTermModel= */ false
+        );
+        verify(mHolder.hbmController)
+                .setAutoBrightnessEnabled(AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED);
+    }
+
+    @Test
+    public void testAutoBrightnessEnabled_DisplayIsInDoze() {
+        Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+        mContext.getOrCreateTestableResources().addOverride(
+                com.android.internal.R.bool.config_allowAutoBrightnessWhileDozing, true);
+        mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
+
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        dpr.policy = DisplayPowerRequest.POLICY_DOZE;
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_DOZE);
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        verify(mHolder.automaticBrightnessController).configure(
+                AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED,
+                /* configuration= */ null, PowerManager.BRIGHTNESS_INVALID_FLOAT,
+                /* userChangedBrightness= */ false, /* adjustment= */ 0,
+                /* userChangedAutoBrightnessAdjustment= */ false, DisplayPowerRequest.POLICY_DOZE,
+                /* shouldResetShortTermModel= */ false
+        );
+        verify(mHolder.hbmController)
+                .setAutoBrightnessEnabled(AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED);
+    }
+
+    @Test
+    public void testAutoBrightnessDisabled_ManualBrightnessMode() {
+        Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+                Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL);
+
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        // One triggered by the test, the other by handleBrightnessModeChange
+        verify(mHolder.automaticBrightnessController, times(2)).configure(
+                AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED,
+                /* configuration= */ null, PowerManager.BRIGHTNESS_INVALID_FLOAT,
+                /* userChangedBrightness= */ false, /* adjustment= */ 0,
+                /* userChangedAutoBrightnessAdjustment= */ false, DisplayPowerRequest.POLICY_BRIGHT,
+                /* shouldResetShortTermModel= */ false
+        );
+        verify(mHolder.hbmController, times(2))
+                .setAutoBrightnessEnabled(AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED);
+    }
+
+    @Test
+    public void testAutoBrightnessDisabled_DisplayIsOff() {
+        Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        dpr.policy = DisplayPowerRequest.POLICY_OFF;
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_OFF);
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        verify(mHolder.automaticBrightnessController).configure(
+                AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE,
+                /* configuration= */ null, PowerManager.BRIGHTNESS_INVALID_FLOAT,
+                /* userChangedBrightness= */ false, /* adjustment= */ 0,
+                /* userChangedAutoBrightnessAdjustment= */ false, DisplayPowerRequest.POLICY_OFF,
+                /* shouldResetShortTermModel= */ false
+        );
+        verify(mHolder.hbmController).setAutoBrightnessEnabled(
+                AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE);
+    }
+
+    @Test
+    public void testAutoBrightnessDisabled_DisplayIsInDoze() {
+        Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+        mContext.getOrCreateTestableResources().addOverride(
+                com.android.internal.R.bool.config_allowAutoBrightnessWhileDozing, false);
+        mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
+
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        dpr.policy = DisplayPowerRequest.POLICY_DOZE;
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_DOZE);
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        verify(mHolder.automaticBrightnessController).configure(
+                AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE,
+                /* configuration= */ null, PowerManager.BRIGHTNESS_INVALID_FLOAT,
+                /* userChangedBrightness= */ false, /* adjustment= */ 0,
+                /* userChangedAutoBrightnessAdjustment= */ false, DisplayPowerRequest.POLICY_DOZE,
+                /* shouldResetShortTermModel= */ false
+        );
+        verify(mHolder.hbmController).setAutoBrightnessEnabled(
+                AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE);
+    }
+
+    @Test
+    public void testAutoBrightnessDisabled_FollowerDisplay() {
+        Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+        mHolder.dpc.setBrightnessToFollow(0.3f, -1, 0, /* slowChange= */ false);
+
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        // One triggered by the test, the other by handleBrightnessModeChange
+        verify(mHolder.automaticBrightnessController, times(2)).configure(
+                AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED,
+                /* configuration= */ null, PowerManager.BRIGHTNESS_INVALID_FLOAT,
+                /* userChangedBrightness= */ false, /* adjustment= */ 0,
+                /* userChangedAutoBrightnessAdjustment= */ false, DisplayPowerRequest.POLICY_BRIGHT,
+                /* shouldResetShortTermModel= */ false
+        );
+
+        // HBM should be allowed for the follower display
+        verify(mHolder.hbmController)
+                .setAutoBrightnessEnabled(AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED);
+    }
+
+    @Test
+    public void testBrightnessNitsPersistWhenDisplayDeviceChanges() {
+        float brightness = 0.3f;
+        float nits = 500;
+        mContext.getOrCreateTestableResources().addOverride(
+                com.android.internal.R.bool.config_persistBrightnessNitsForDefaultDisplay,
+                true);
+        mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
+        when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
+
+        mHolder.dpc.setBrightness(brightness);
+        verify(mHolder.brightnessSetting).setBrightnessNitsForDefaultDisplay(nits);
+
+        float newBrightness = 0.4f;
+        when(mHolder.brightnessSetting.getBrightnessNitsForDefaultDisplay()).thenReturn(nits);
+        when(mHolder.automaticBrightnessController.convertToFloatScale(nits))
+                .thenReturn(newBrightness);
+        // New display device
+        setUpDisplay(DISPLAY_ID, "new_unique_id", mHolder.display, mock(DisplayDevice.class),
+                mock(DisplayDeviceConfig.class), /* isEnabled= */ true);
+        mHolder.dpc.onDisplayChanged(mHolder.hbmMetadata, Layout.NO_LEAD_DISPLAY);
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+        // One triggered by handleBrightnessModeChange, another triggered by onDisplayChanged
+        verify(mHolder.animator, times(2)).animateTo(eq(newBrightness), anyFloat(), anyFloat());
+    }
+
+    @Test
+    public void testShortTermModelPersistsWhenDisplayDeviceChanges() {
+        float lux = 2000;
+        float brightness = 0.4f;
+        float nits = 500;
+        when(mHolder.brightnessMappingStrategy.getUserLux()).thenReturn(lux);
+        when(mHolder.brightnessMappingStrategy.getUserBrightness()).thenReturn(brightness);
+        when(mHolder.brightnessMappingStrategy.convertToNits(brightness)).thenReturn(nits);
+        when(mHolder.brightnessMappingStrategy.convertToFloatScale(nits)).thenReturn(brightness);
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1);
+        clearInvocations(mHolder.injector);
+
+        // New display device
+        setUpDisplay(DISPLAY_ID, "new_unique_id", mHolder.display, mock(DisplayDevice.class),
+                mock(DisplayDeviceConfig.class), /* isEnabled= */ true);
+        mHolder.dpc.onDisplayChanged(mHolder.hbmMetadata, Layout.NO_LEAD_DISPLAY);
+        advanceTime(1);
+
+        verify(mHolder.injector).getAutomaticBrightnessController(
+                any(AutomaticBrightnessController.Callbacks.class),
+                any(Looper.class),
+                eq(mSensorManagerMock),
+                any(),
+                eq(mHolder.brightnessMappingStrategy),
+                anyInt(),
+                anyFloat(),
+                anyFloat(),
+                anyFloat(),
+                anyInt(),
+                anyInt(),
+                anyLong(),
+                anyLong(),
+                anyBoolean(),
+                any(HysteresisLevels.class),
+                any(HysteresisLevels.class),
+                any(HysteresisLevels.class),
+                any(HysteresisLevels.class),
+                eq(mContext),
+                any(BrightnessRangeController.class),
+                any(BrightnessThrottler.class),
+                isNull(),
+                anyInt(),
+                anyInt(),
+                eq(lux),
+                eq(brightness)
+        );
+    }
+
+    @Test
+    public void testUpdateBrightnessThrottlingDataId() {
+        mHolder.display.getDisplayInfoLocked().thermalBrightnessThrottlingDataId =
+                "throttling-data-id";
+        clearInvocations(mHolder.display.getPrimaryDisplayDeviceLocked().getDisplayDeviceConfig());
+
+        mHolder.dpc.onDisplayChanged(mHolder.hbmMetadata, Layout.NO_LEAD_DISPLAY);
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        verify(mHolder.display.getPrimaryDisplayDeviceLocked().getDisplayDeviceConfig())
+                .getThermalBrightnessThrottlingDataMapByThrottlingId();
+    }
+
+    @Test
+    public void testSetBrightness_BrightnessShouldBeClamped() {
+        float clampedBrightness = 0.6f;
+        when(mHolder.hbmController.getCurrentBrightnessMax()).thenReturn(clampedBrightness);
+
+        mHolder.dpc.setBrightness(PowerManager.BRIGHTNESS_MAX);
+
+        verify(mHolder.brightnessSetting).setBrightness(clampedBrightness);
+    }
+
+    @Test
+    public void testDwbcCallsHappenOnHandler() {
+        mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
+
+        mHolder.dpc.setAutomaticScreenBrightnessMode(true);
+        verify(mDisplayWhiteBalanceControllerMock, never()).setStrongModeEnabled(true);
+
+        // dispatch handler looper
+        advanceTime(1);
+        verify(mDisplayWhiteBalanceControllerMock, times(1)).setStrongModeEnabled(true);
+    }
+
+    /**
+     * Creates a mock and registers it to {@link LocalServices}.
+     */
+    private static <T> void addLocalServiceMock(Class<T> clazz, T mock) {
+        LocalServices.removeServiceForTest(clazz);
+        LocalServices.addService(clazz, mock);
+    }
+
+    private void advanceTime(long timeMs) {
+        mClock.fastForward(timeMs);
+        mTestLooper.dispatchAll();
+    }
+
+    private void setUpSensors() throws Exception {
+        mProxSensor = TestUtils.createSensor(Sensor.TYPE_PROXIMITY, Sensor.STRING_TYPE_PROXIMITY,
+                PROX_SENSOR_MAX_RANGE);
+        Sensor screenOffBrightnessSensor = TestUtils.createSensor(
+                Sensor.TYPE_LIGHT, Sensor.STRING_TYPE_LIGHT);
+        when(mSensorManagerMock.getSensorList(eq(Sensor.TYPE_ALL)))
+                .thenReturn(List.of(mProxSensor, screenOffBrightnessSensor));
+    }
+
+    private SensorEventListener getSensorEventListener(Sensor sensor) {
+        verify(mSensorManagerMock).registerListener(mSensorEventListenerCaptor.capture(),
+                eq(sensor), eq(SensorManager.SENSOR_DELAY_NORMAL), isA(Handler.class));
+        return mSensorEventListenerCaptor.getValue();
+    }
+
+    private void setUpDisplay(int displayId, String uniqueId, LogicalDisplay logicalDisplayMock,
+            DisplayDevice displayDeviceMock, DisplayDeviceConfig displayDeviceConfigMock,
+            boolean isEnabled) {
+        DisplayInfo info = new DisplayInfo();
+        DisplayDeviceInfo deviceInfo = new DisplayDeviceInfo();
+        deviceInfo.uniqueId = uniqueId;
+
+        when(logicalDisplayMock.getDisplayIdLocked()).thenReturn(displayId);
+        when(logicalDisplayMock.getPrimaryDisplayDeviceLocked()).thenReturn(displayDeviceMock);
+        when(logicalDisplayMock.getDisplayInfoLocked()).thenReturn(info);
+        when(logicalDisplayMock.isEnabledLocked()).thenReturn(isEnabled);
+        when(logicalDisplayMock.isInTransitionLocked()).thenReturn(false);
+        when(displayDeviceMock.getDisplayDeviceInfoLocked()).thenReturn(deviceInfo);
+        when(displayDeviceMock.getUniqueId()).thenReturn(uniqueId);
+        when(displayDeviceMock.getDisplayDeviceConfig()).thenReturn(displayDeviceConfigMock);
+        when(displayDeviceConfigMock.getProximitySensor()).thenReturn(
+                new DisplayDeviceConfig.SensorData() {
+                    {
+                        type = Sensor.STRING_TYPE_PROXIMITY;
+                        name = null;
+                    }
+                });
+        when(displayDeviceConfigMock.getNits()).thenReturn(new float[]{2, 500});
+        when(displayDeviceConfigMock.isAutoBrightnessAvailable()).thenReturn(true);
+        when(displayDeviceConfigMock.getAmbientLightSensor()).thenReturn(
+                new DisplayDeviceConfig.SensorData());
+        when(displayDeviceConfigMock.getScreenOffBrightnessSensor()).thenReturn(
+                new DisplayDeviceConfig.SensorData() {
+                    {
+                        type = Sensor.STRING_TYPE_LIGHT;
+                        name = null;
+                    }
+                });
+        when(displayDeviceConfigMock.getScreenOffBrightnessSensorValueToLux())
+                .thenReturn(new int[0]);
+        when(displayDeviceConfigMock.getBrightnessRampFastDecrease())
+                .thenReturn(BRIGHTNESS_RAMP_RATE_FAST_DECREASE);
+        when(displayDeviceConfigMock.getBrightnessRampFastIncrease())
+                .thenReturn(BRIGHTNESS_RAMP_RATE_FAST_INCREASE);
+        when(displayDeviceConfigMock.getBrightnessRampSlowDecrease())
+                .thenReturn(BRIGHTNESS_RAMP_RATE_SLOW_DECREASE);
+        when(displayDeviceConfigMock.getBrightnessRampSlowIncrease())
+                .thenReturn(BRIGHTNESS_RAMP_RATE_SLOW_INCREASE);
+    }
+
+    private DisplayPowerControllerHolder createDisplayPowerController(int displayId,
+            String uniqueId) {
+        return createDisplayPowerController(displayId, uniqueId, /* isEnabled= */ true);
+    }
+
+    private DisplayPowerControllerHolder createDisplayPowerController(int displayId,
+            String uniqueId, boolean isEnabled) {
+        final DisplayPowerState displayPowerState = mock(DisplayPowerState.class);
+        final DualRampAnimator<DisplayPowerState> animator = mock(DualRampAnimator.class);
+        final AutomaticBrightnessController automaticBrightnessController =
+                mock(AutomaticBrightnessController.class);
+        final WakelockController wakelockController = mock(WakelockController.class);
+        final BrightnessMappingStrategy brightnessMappingStrategy =
+                mock(BrightnessMappingStrategy.class);
+        final HysteresisLevels hysteresisLevels = mock(HysteresisLevels.class);
+        final ScreenOffBrightnessSensorController screenOffBrightnessSensorController =
+                mock(ScreenOffBrightnessSensorController.class);
+        final HighBrightnessModeController hbmController = mock(HighBrightnessModeController.class);
+
+        when(hbmController.getCurrentBrightnessMax()).thenReturn(PowerManager.BRIGHTNESS_MAX);
+
+        TestInjector injector = spy(new TestInjector(displayPowerState, animator,
+                automaticBrightnessController, wakelockController, brightnessMappingStrategy,
+                hysteresisLevels, screenOffBrightnessSensorController, hbmController));
+
+        final LogicalDisplay display = mock(LogicalDisplay.class);
+        final DisplayDevice device = mock(DisplayDevice.class);
+        final HighBrightnessModeMetadata hbmMetadata = mock(HighBrightnessModeMetadata.class);
+        final BrightnessSetting brightnessSetting = mock(BrightnessSetting.class);
+        final DisplayDeviceConfig config = mock(DisplayDeviceConfig.class);
+
+        setUpDisplay(displayId, uniqueId, display, device, config, isEnabled);
+
+        final DisplayPowerController2 dpc = new DisplayPowerController2(
+                mContext, injector, mDisplayPowerCallbacksMock, mHandler,
+                mSensorManagerMock, mDisplayBlankerMock, display,
+                mBrightnessTrackerMock, brightnessSetting, () -> {},
+                hbmMetadata, /* bootCompleted= */ false);
+
+        return new DisplayPowerControllerHolder(dpc, display, displayPowerState, brightnessSetting,
+                animator, automaticBrightnessController, wakelockController,
+                screenOffBrightnessSensorController, hbmController, hbmMetadata,
+                brightnessMappingStrategy, injector);
+    }
+
+    /**
+     * A class for holding a DisplayPowerController under test and all the mocks specifically
+     * related to it.
+     */
+    private static class DisplayPowerControllerHolder {
+        public final DisplayPowerController2 dpc;
+        public final LogicalDisplay display;
+        public final DisplayPowerState displayPowerState;
+        public final BrightnessSetting brightnessSetting;
+        public final DualRampAnimator<DisplayPowerState> animator;
+        public final AutomaticBrightnessController automaticBrightnessController;
+        public final WakelockController wakelockController;
+        public final ScreenOffBrightnessSensorController screenOffBrightnessSensorController;
+        public final HighBrightnessModeController hbmController;
+        public final HighBrightnessModeMetadata hbmMetadata;
+        public final BrightnessMappingStrategy brightnessMappingStrategy;
+        public final DisplayPowerController2.Injector injector;
+
+        DisplayPowerControllerHolder(DisplayPowerController2 dpc, LogicalDisplay display,
+                DisplayPowerState displayPowerState, BrightnessSetting brightnessSetting,
+                DualRampAnimator<DisplayPowerState> animator,
+                AutomaticBrightnessController automaticBrightnessController,
+                WakelockController wakelockController,
+                ScreenOffBrightnessSensorController screenOffBrightnessSensorController,
+                HighBrightnessModeController hbmController,
+                HighBrightnessModeMetadata hbmMetadata,
+                BrightnessMappingStrategy brightnessMappingStrategy,
+                DisplayPowerController2.Injector injector) {
+            this.dpc = dpc;
+            this.display = display;
+            this.displayPowerState = displayPowerState;
+            this.brightnessSetting = brightnessSetting;
+            this.animator = animator;
+            this.automaticBrightnessController = automaticBrightnessController;
+            this.wakelockController = wakelockController;
+            this.screenOffBrightnessSensorController = screenOffBrightnessSensorController;
+            this.hbmController = hbmController;
+            this.hbmMetadata = hbmMetadata;
+            this.brightnessMappingStrategy = brightnessMappingStrategy;
+            this.injector = injector;
+        }
+    }
+
+    private class TestInjector extends DisplayPowerController2.Injector {
+        private final DisplayPowerState mDisplayPowerState;
+        private final DualRampAnimator<DisplayPowerState> mAnimator;
+        private final AutomaticBrightnessController mAutomaticBrightnessController;
+        private final WakelockController mWakelockController;
+        private final BrightnessMappingStrategy mBrightnessMappingStrategy;
+        private final HysteresisLevels mHysteresisLevels;
+        private final ScreenOffBrightnessSensorController mScreenOffBrightnessSensorController;
+        private final HighBrightnessModeController mHighBrightnessModeController;
+
+        TestInjector(DisplayPowerState dps, DualRampAnimator<DisplayPowerState> animator,
+                AutomaticBrightnessController automaticBrightnessController,
+                WakelockController wakelockController,
+                BrightnessMappingStrategy brightnessMappingStrategy,
+                HysteresisLevels hysteresisLevels,
+                ScreenOffBrightnessSensorController screenOffBrightnessSensorController,
+                HighBrightnessModeController highBrightnessModeController) {
+            mDisplayPowerState = dps;
+            mAnimator = animator;
+            mAutomaticBrightnessController = automaticBrightnessController;
+            mWakelockController = wakelockController;
+            mBrightnessMappingStrategy = brightnessMappingStrategy;
+            mHysteresisLevels = hysteresisLevels;
+            mScreenOffBrightnessSensorController = screenOffBrightnessSensorController;
+            mHighBrightnessModeController = highBrightnessModeController;
+        }
+
+        @Override
+        DisplayPowerController2.Clock getClock() {
+            return mClock::now;
+        }
+
+        @Override
+        DisplayPowerState getDisplayPowerState(DisplayBlanker blanker, ColorFade colorFade,
+                int displayId, int displayState) {
+            return mDisplayPowerState;
+        }
+
+        @Override
+        DualRampAnimator<DisplayPowerState> getDualRampAnimator(DisplayPowerState dps,
+                FloatProperty<DisplayPowerState> firstProperty,
+                FloatProperty<DisplayPowerState> secondProperty) {
+            return mAnimator;
+        }
+
+        @Override
+        WakelockController getWakelockController(int displayId,
+                DisplayPowerCallbacks displayPowerCallbacks) {
+            return mWakelockController;
+        }
+
+        @Override
+        DisplayPowerProximityStateController getDisplayPowerProximityStateController(
+                WakelockController wakelockController, DisplayDeviceConfig displayDeviceConfig,
+                Looper looper, Runnable nudgeUpdatePowerState, int displayId,
+                SensorManager sensorManager) {
+            return new DisplayPowerProximityStateController(wakelockController,
+                    displayDeviceConfig, looper, nudgeUpdatePowerState, displayId,
+                    sensorManager,
+                    new DisplayPowerProximityStateController.Injector() {
+                        @Override
+                        DisplayPowerProximityStateController.Clock createClock() {
+                            return mClock::now;
+                        }
+                    });
+        }
+
+        @Override
+        AutomaticBrightnessController getAutomaticBrightnessController(
+                AutomaticBrightnessController.Callbacks callbacks, Looper looper,
+                SensorManager sensorManager, Sensor lightSensor,
+                BrightnessMappingStrategy interactiveModeBrightnessMapper,
+                int lightSensorWarmUpTime, float brightnessMin, float brightnessMax,
+                float dozeScaleFactor, int lightSensorRate, int initialLightSensorRate,
+                long brighteningLightDebounceConfig, long darkeningLightDebounceConfig,
+                boolean resetAmbientLuxAfterWarmUpConfig,
+                HysteresisLevels ambientBrightnessThresholds,
+                HysteresisLevels screenBrightnessThresholds,
+                HysteresisLevels ambientBrightnessThresholdsIdle,
+                HysteresisLevels screenBrightnessThresholdsIdle, Context context,
+                BrightnessRangeController brightnessRangeController,
+                BrightnessThrottler brightnessThrottler,
+                BrightnessMappingStrategy idleModeBrightnessMapper,
+                int ambientLightHorizonShort, int ambientLightHorizonLong, float userLux,
+                float userBrightness) {
+            return mAutomaticBrightnessController;
+        }
+
+        @Override
+        BrightnessMappingStrategy getInteractiveModeBrightnessMapper(Resources resources,
+                DisplayDeviceConfig displayDeviceConfig,
+                DisplayWhiteBalanceController displayWhiteBalanceController) {
+            return mBrightnessMappingStrategy;
+        }
+
+        @Override
+        HysteresisLevels getHysteresisLevels(float[] brighteningThresholdsPercentages,
+                float[] darkeningThresholdsPercentages, float[] brighteningThresholdLevels,
+                float[] darkeningThresholdLevels, float minDarkeningThreshold,
+                float minBrighteningThreshold) {
+            return mHysteresisLevels;
+        }
+
+        @Override
+        HysteresisLevels getHysteresisLevels(float[] brighteningThresholdsPercentages,
+                float[] darkeningThresholdsPercentages, float[] brighteningThresholdLevels,
+                float[] darkeningThresholdLevels, float minDarkeningThreshold,
+                float minBrighteningThreshold, boolean potentialOldBrightnessRange) {
+            return mHysteresisLevels;
+        }
+
+        @Override
+        ScreenOffBrightnessSensorController getScreenOffBrightnessSensorController(
+                SensorManager sensorManager, Sensor lightSensor, Handler handler,
+                ScreenOffBrightnessSensorController.Clock clock, int[] sensorValueToLux,
+                BrightnessMappingStrategy brightnessMapper) {
+            return mScreenOffBrightnessSensorController;
+        }
+
+        @Override
+        HighBrightnessModeController getHighBrightnessModeController(Handler handler, int width,
+                int height, IBinder displayToken, String displayUniqueId, float brightnessMin,
+                float brightnessMax, DisplayDeviceConfig.HighBrightnessModeData hbmData,
+                HighBrightnessModeController.HdrBrightnessDeviceConfig hdrBrightnessCfg,
+                Runnable hbmChangeCallback, HighBrightnessModeMetadata hbmMetadata,
+                Context context) {
+            return mHighBrightnessModeController;
+        }
+
+        @Override
+        DisplayWhiteBalanceController getDisplayWhiteBalanceController(Handler handler,
+                SensorManager sensorManager, Resources resources) {
+            return mDisplayWhiteBalanceControllerMock;
+        }
+    }
+}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
new file mode 100644
index 0000000..113c46b
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -0,0 +1,1384 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+
+import static org.junit.Assert.assertNotNull;
+import static org.mockito.ArgumentMatchers.any;
+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.isA;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.hardware.Sensor;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.hardware.display.DisplayManagerInternal.DisplayPowerCallbacks;
+import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.PowerManager;
+import android.os.SystemProperties;
+import android.os.test.TestLooper;
+import android.provider.Settings;
+import android.testing.TestableContext;
+import android.util.FloatProperty;
+import android.view.Display;
+import android.view.DisplayInfo;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.modules.utils.testing.ExtendedMockitoRule;
+import com.android.server.LocalServices;
+import com.android.server.am.BatteryStatsService;
+import com.android.server.display.RampAnimator.DualRampAnimator;
+import com.android.server.display.brightness.BrightnessEvent;
+import com.android.server.display.color.ColorDisplayService;
+import com.android.server.display.layout.Layout;
+import com.android.server.display.whitebalance.DisplayWhiteBalanceController;
+import com.android.server.policy.WindowManagerPolicy;
+import com.android.server.testutils.OffsettableClock;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.quality.Strictness;
+import org.mockito.stubbing.Answer;
+
+import java.util.List;
+
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public final class DisplayPowerControllerTest {
+    private static final int DISPLAY_ID = Display.DEFAULT_DISPLAY;
+    private static final String UNIQUE_ID = "unique_id_test123";
+    private static final int FOLLOWER_DISPLAY_ID = DISPLAY_ID + 1;
+    private static final String FOLLOWER_UNIQUE_ID = "unique_id_456";
+    private static final int SECOND_FOLLOWER_DISPLAY_ID = FOLLOWER_DISPLAY_ID + 1;
+    private static final String SECOND_FOLLOWER_UNIQUE_DISPLAY_ID = "unique_id_789";
+    private static final float PROX_SENSOR_MAX_RANGE = 5;
+    private static final float BRIGHTNESS_RAMP_RATE_FAST_DECREASE = 0.3f;
+    private static final float BRIGHTNESS_RAMP_RATE_FAST_INCREASE = 0.4f;
+    private static final float BRIGHTNESS_RAMP_RATE_SLOW_DECREASE = 0.1f;
+    private static final float BRIGHTNESS_RAMP_RATE_SLOW_INCREASE = 0.2f;
+
+    private OffsettableClock mClock;
+    private TestLooper mTestLooper;
+    private Handler mHandler;
+    private DisplayPowerControllerHolder mHolder;
+    private Sensor mProxSensor;
+
+    @Mock
+    private DisplayPowerCallbacks mDisplayPowerCallbacksMock;
+    @Mock
+    private SensorManager mSensorManagerMock;
+    @Mock
+    private DisplayBlanker mDisplayBlankerMock;
+    @Mock
+    private BrightnessTracker mBrightnessTrackerMock;
+    @Mock
+    private WindowManagerPolicy mWindowManagerPolicyMock;
+    @Mock
+    private PowerManager mPowerManagerMock;
+    @Mock
+    private ColorDisplayService.ColorDisplayServiceInternal mCdsiMock;
+    @Mock
+    private DisplayWhiteBalanceController mDisplayWhiteBalanceControllerMock;
+    @Captor
+    private ArgumentCaptor<SensorEventListener> mSensorEventListenerCaptor;
+
+    @Rule
+    public final TestableContext mContext = new TestableContext(
+            InstrumentationRegistry.getInstrumentation().getContext());
+
+    @Rule
+    public final ExtendedMockitoRule mExtendedMockitoRule =
+            new ExtendedMockitoRule.Builder(this)
+                    .setStrictness(Strictness.LENIENT)
+                    .spyStatic(SystemProperties.class)
+                    .spyStatic(BatteryStatsService.class)
+                    .build();
+
+    @Before
+    public void setUp() throws Exception {
+        mClock = new OffsettableClock.Stopped();
+        mTestLooper = new TestLooper(mClock::now);
+        mHandler = new Handler(mTestLooper.getLooper());
+
+        // Put the system into manual brightness by default, just to minimize unexpected events and
+        // have a consistent starting state
+        Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+                Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL);
+
+
+        addLocalServiceMock(WindowManagerPolicy.class, mWindowManagerPolicyMock);
+        addLocalServiceMock(ColorDisplayService.ColorDisplayServiceInternal.class,
+                mCdsiMock);
+
+        mContext.addMockSystemService(PowerManager.class, mPowerManagerMock);
+
+        doAnswer((Answer<Void>) invocationOnMock -> null).when(() ->
+                SystemProperties.set(anyString(), any()));
+        doAnswer((Answer<Void>) invocationOnMock -> null).when(BatteryStatsService::getService);
+
+        setUpSensors();
+        mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
+    }
+
+    @After
+    public void tearDown() {
+        LocalServices.removeServiceForTest(WindowManagerPolicy.class);
+        LocalServices.removeServiceForTest(ColorDisplayService.ColorDisplayServiceInternal.class);
+    }
+
+    @Test
+    public void testReleaseProxSuspendBlockersOnExit() throws Exception {
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+        // Send a display power request
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
+        dpr.useProximitySensor = true;
+        mHolder.dpc.requestPowerState(dpr, false /* waitForNegativeProximity */);
+
+        // Run updatePowerState to start listener for the prox sensor
+        advanceTime(1);
+
+        SensorEventListener listener = getSensorEventListener(mProxSensor);
+        assertNotNull(listener);
+
+        listener.onSensorChanged(TestUtils.createSensorEvent(mProxSensor, /* value= */ 5));
+        advanceTime(1);
+
+        // two times, one for unfinished business and one for proximity
+        verify(mDisplayPowerCallbacksMock).acquireSuspendBlocker(
+                mHolder.dpc.getSuspendBlockerUnfinishedBusinessId(DISPLAY_ID));
+        verify(mDisplayPowerCallbacksMock).acquireSuspendBlocker(
+                mHolder.dpc.getSuspendBlockerProxDebounceId(DISPLAY_ID));
+
+        mHolder.dpc.stop();
+        advanceTime(1);
+
+        // two times, one for unfinished business and one for proximity
+        verify(mDisplayPowerCallbacksMock).releaseSuspendBlocker(
+                mHolder.dpc.getSuspendBlockerUnfinishedBusinessId(DISPLAY_ID));
+        verify(mDisplayPowerCallbacksMock).releaseSuspendBlocker(
+                mHolder.dpc.getSuspendBlockerProxDebounceId(DISPLAY_ID));
+    }
+
+    @Test
+    public void testScreenOffBecauseOfProximity() throws Exception {
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+        // Send a display power request
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
+        dpr.useProximitySensor = true;
+        mHolder.dpc.requestPowerState(dpr, false /* waitForNegativeProximity */);
+
+        // Run updatePowerState to start listener for the prox sensor
+        advanceTime(1);
+
+        SensorEventListener listener = getSensorEventListener(mProxSensor);
+        assertNotNull(listener);
+
+        // Send a positive proximity event
+        listener.onSensorChanged(TestUtils.createSensorEvent(mProxSensor, /* value= */ 1));
+        advanceTime(1);
+
+        // The display should have been turned off
+        verify(mHolder.displayPowerState).setScreenState(Display.STATE_OFF);
+
+        clearInvocations(mHolder.displayPowerState);
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_OFF);
+        // Send a negative proximity event
+        listener.onSensorChanged(TestUtils.createSensorEvent(mProxSensor,
+                (int) PROX_SENSOR_MAX_RANGE + 1));
+        // Advance time by less than PROXIMITY_SENSOR_NEGATIVE_DEBOUNCE_DELAY
+        advanceTime(1);
+
+        // The prox sensor is debounced so the display should not have been turned back on yet
+        verify(mHolder.displayPowerState, never()).setScreenState(Display.STATE_ON);
+
+        // Advance time by more than PROXIMITY_SENSOR_NEGATIVE_DEBOUNCE_DELAY
+        advanceTime(1000);
+
+        // The display should have been turned back on
+        verify(mHolder.displayPowerState).setScreenState(Display.STATE_ON);
+    }
+
+    @Test
+    public void testScreenOffBecauseOfProximity_ProxSensorGone() throws Exception {
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+        // Send a display power request
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
+        dpr.useProximitySensor = true;
+        mHolder.dpc.requestPowerState(dpr, false /* waitForNegativeProximity */);
+
+        // Run updatePowerState to start listener for the prox sensor
+        advanceTime(1);
+
+        SensorEventListener listener = getSensorEventListener(mProxSensor);
+        assertNotNull(listener);
+
+        // Send a positive proximity event
+        listener.onSensorChanged(TestUtils.createSensorEvent(mProxSensor, /* value= */ 1));
+        advanceTime(1);
+
+        // The display should have been turned off
+        verify(mHolder.displayPowerState).setScreenState(Display.STATE_OFF);
+
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_OFF);
+        // The display device changes and we no longer have a prox sensor
+        reset(mSensorManagerMock);
+        setUpDisplay(DISPLAY_ID, "new_unique_id", mHolder.display, mock(DisplayDevice.class),
+                mock(DisplayDeviceConfig.class), /* isEnabled= */ true);
+        mHolder.dpc.onDisplayChanged(mHolder.hbmMetadata, Layout.NO_LEAD_DISPLAY);
+
+        advanceTime(1); // Run updatePowerState
+
+        // The display should have been turned back on and the listener should have been
+        // unregistered
+        verify(mHolder.displayPowerState).setScreenState(Display.STATE_ON);
+        verify(mSensorManagerMock).unregisterListener(listener);
+    }
+
+    @Test
+    public void testProximitySensorListenerNotRegisteredForNonDefaultDisplay() {
+        DisplayPowerControllerHolder followerDpc =
+                createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
+
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+        // send a display power request
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
+        dpr.useProximitySensor = true;
+        followerDpc.dpc.requestPowerState(dpr, false /* waitForNegativeProximity */);
+
+        // Run updatePowerState
+        advanceTime(1);
+
+        verify(mSensorManagerMock, never()).registerListener(any(SensorEventListener.class),
+                eq(mProxSensor), anyInt(), any(Handler.class));
+    }
+
+    @Test
+    public void testDisplayBrightnessFollowers_BothDpcsSupportNits() {
+        DisplayPowerControllerHolder followerDpc =
+                createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
+        when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+        when(followerDpc.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        followerDpc.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
+                ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
+        verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture());
+        BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
+
+        mHolder.dpc.addDisplayBrightnessFollower(followerDpc.dpc);
+
+        // Test different float scale values
+        float leadBrightness = 0.3f;
+        float followerBrightness = 0.4f;
+        float nits = 300;
+        when(mHolder.automaticBrightnessController.convertToNits(leadBrightness)).thenReturn(nits);
+        when(followerDpc.automaticBrightnessController.convertToFloatScale(nits))
+                .thenReturn(followerBrightness);
+        when(mHolder.brightnessSetting.getBrightness()).thenReturn(leadBrightness);
+        listener.onBrightnessChanged(leadBrightness);
+        advanceTime(1); // Send messages, run updatePowerState
+        verify(mHolder.animator).animateTo(eq(leadBrightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+        verify(followerDpc.animator).animateTo(eq(followerBrightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+
+        when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(leadBrightness);
+        when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(followerBrightness);
+        clearInvocations(mHolder.animator, followerDpc.animator);
+
+        // Test the same float scale value
+        float brightness = 0.6f;
+        nits = 600;
+        when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
+        when(followerDpc.automaticBrightnessController.convertToFloatScale(nits))
+                .thenReturn(brightness);
+        when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
+        listener.onBrightnessChanged(brightness);
+        advanceTime(1); // Send messages, run updatePowerState
+        verify(mHolder.animator).animateTo(eq(brightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+        verify(followerDpc.animator).animateTo(eq(brightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+    }
+
+    @Test
+    public void testDisplayBrightnessFollowers_FollowerDoesNotSupportNits() {
+        DisplayPowerControllerHolder followerDpc =
+                createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
+        when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+        when(followerDpc.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        followerDpc.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
+                ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
+        verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture());
+        BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
+
+        mHolder.dpc.addDisplayBrightnessFollower(followerDpc.dpc);
+
+        float brightness = 0.3f;
+        when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(300f);
+        when(followerDpc.automaticBrightnessController.convertToFloatScale(anyFloat()))
+                .thenReturn(PowerManager.BRIGHTNESS_INVALID_FLOAT);
+        when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
+        listener.onBrightnessChanged(brightness);
+        advanceTime(1); // Send messages, run updatePowerState
+        verify(mHolder.animator).animateTo(eq(brightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+        verify(followerDpc.animator).animateTo(eq(brightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+    }
+
+    @Test
+    public void testDisplayBrightnessFollowers_LeadDpcDoesNotSupportNits() {
+        DisplayPowerControllerHolder followerDpc =
+                createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
+        when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+        when(followerDpc.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        followerDpc.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
+                ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
+        verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture());
+        BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
+
+        mHolder.dpc.addDisplayBrightnessFollower(followerDpc.dpc);
+
+        float brightness = 0.3f;
+        when(mHolder.automaticBrightnessController.convertToNits(anyFloat())).thenReturn(-1f);
+        when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
+        listener.onBrightnessChanged(brightness);
+        advanceTime(1); // Send messages, run updatePowerState
+        verify(mHolder.animator).animateTo(eq(brightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+        verify(followerDpc.animator).animateTo(eq(brightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+    }
+
+    @Test
+    public void testDisplayBrightnessFollowers_NeitherDpcSupportsNits() {
+        DisplayPowerControllerHolder followerDpc =
+                createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
+        when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+        when(followerDpc.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        followerDpc.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
+                ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
+        verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture());
+        BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
+
+        mHolder.dpc.addDisplayBrightnessFollower(followerDpc.dpc);
+
+        float brightness = 0.3f;
+        when(mHolder.automaticBrightnessController.convertToNits(anyFloat())).thenReturn(-1f);
+        when(followerDpc.automaticBrightnessController.convertToFloatScale(anyFloat()))
+                .thenReturn(PowerManager.BRIGHTNESS_INVALID_FLOAT);
+        when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
+        listener.onBrightnessChanged(brightness);
+        advanceTime(1); // Send messages, run updatePowerState
+        verify(mHolder.animator).animateTo(eq(brightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+        verify(followerDpc.animator).animateTo(eq(brightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+    }
+
+    @Test
+    public void testDisplayBrightnessFollowers_AutomaticBrightness() {
+        Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+        DisplayPowerControllerHolder followerDpc =
+                createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+        when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+        when(followerDpc.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+        float leadBrightness = 0.1f;
+        float rawLeadBrightness = 0.3f;
+        float followerBrightness = 0.4f;
+        float nits = 300;
+        float ambientLux = 3000;
+        when(mHolder.automaticBrightnessController.getRawAutomaticScreenBrightness())
+                .thenReturn(rawLeadBrightness);
+        when(mHolder.automaticBrightnessController
+                .getAutomaticScreenBrightness(any(BrightnessEvent.class)))
+                .thenReturn(leadBrightness);
+        when(mHolder.automaticBrightnessController.convertToNits(rawLeadBrightness))
+                .thenReturn(nits);
+        when(mHolder.automaticBrightnessController.getAmbientLux()).thenReturn(ambientLux);
+        when(followerDpc.automaticBrightnessController.convertToFloatScale(nits))
+                .thenReturn(followerBrightness);
+
+        mHolder.dpc.addDisplayBrightnessFollower(followerDpc.dpc);
+
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        followerDpc.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        verify(mHolder.animator).animateTo(eq(leadBrightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+        // One triggered by handleBrightnessModeChange, another triggered by setBrightnessToFollow
+        verify(followerDpc.hbmController, times(2)).onAmbientLuxChange(ambientLux);
+        verify(followerDpc.animator, times(2)).animateTo(eq(followerBrightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+
+        when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(leadBrightness);
+        when(followerDpc.displayPowerState.getScreenBrightness()).thenReturn(followerBrightness);
+        clearInvocations(mHolder.animator, followerDpc.animator);
+
+        leadBrightness = 0.05f;
+        rawLeadBrightness = 0.2f;
+        followerBrightness = 0.3f;
+        nits = 200;
+        ambientLux = 2000;
+        when(mHolder.automaticBrightnessController.getRawAutomaticScreenBrightness())
+                .thenReturn(rawLeadBrightness);
+        when(mHolder.automaticBrightnessController
+                .getAutomaticScreenBrightness(any(BrightnessEvent.class)))
+                .thenReturn(leadBrightness);
+        when(mHolder.automaticBrightnessController.convertToNits(rawLeadBrightness))
+                .thenReturn(nits);
+        when(mHolder.automaticBrightnessController.getAmbientLux()).thenReturn(ambientLux);
+        when(followerDpc.automaticBrightnessController.convertToFloatScale(nits))
+                .thenReturn(followerBrightness);
+
+        mHolder.dpc.updateBrightness();
+        advanceTime(1); // Run updatePowerState
+
+        // The second time, the animation rate should be slow
+        verify(mHolder.animator).animateTo(eq(leadBrightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_SLOW_DECREASE));
+        verify(followerDpc.hbmController).onAmbientLuxChange(ambientLux);
+        verify(followerDpc.animator).animateTo(eq(followerBrightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_SLOW_DECREASE));
+    }
+
+    @Test
+    public void testDisplayBrightnessFollowersRemoval_RemoveSingleFollower() {
+        DisplayPowerControllerHolder followerDpc = createDisplayPowerController(FOLLOWER_DISPLAY_ID,
+                FOLLOWER_UNIQUE_ID);
+        DisplayPowerControllerHolder secondFollowerDpc = createDisplayPowerController(
+                SECOND_FOLLOWER_DISPLAY_ID, SECOND_FOLLOWER_UNIQUE_DISPLAY_ID);
+        when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+        when(followerDpc.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+        when(secondFollowerDpc.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        followerDpc.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        secondFollowerDpc.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
+                ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
+        verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture());
+        BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
+
+        // Set the initial brightness on the DPC we're going to remove so we have a fixed value for
+        // it to return to.
+        listenerCaptor = ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
+        verify(followerDpc.brightnessSetting).registerListener(listenerCaptor.capture());
+        BrightnessSetting.BrightnessSettingListener followerListener = listenerCaptor.getValue();
+        final float initialFollowerBrightness = 0.3f;
+        when(followerDpc.brightnessSetting.getBrightness()).thenReturn(initialFollowerBrightness);
+        followerListener.onBrightnessChanged(initialFollowerBrightness);
+        advanceTime(1);
+        verify(followerDpc.animator).animateTo(eq(initialFollowerBrightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+
+        when(followerDpc.displayPowerState.getScreenBrightness())
+                .thenReturn(initialFollowerBrightness);
+
+        mHolder.dpc.addDisplayBrightnessFollower(followerDpc.dpc);
+        mHolder.dpc.addDisplayBrightnessFollower(secondFollowerDpc.dpc);
+        clearInvocations(followerDpc.animator);
+
+        // Validate both followers are correctly registered and receiving brightness updates
+        float brightness = 0.6f;
+        float nits = 600;
+        when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
+        when(followerDpc.automaticBrightnessController.convertToFloatScale(nits))
+                .thenReturn(brightness);
+        when(secondFollowerDpc.automaticBrightnessController.convertToFloatScale(nits))
+                .thenReturn(brightness);
+        when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
+        listener.onBrightnessChanged(brightness);
+        advanceTime(1); // Send messages, run updatePowerState
+        verify(mHolder.animator).animateTo(eq(brightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+        verify(followerDpc.animator).animateTo(eq(brightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+        verify(secondFollowerDpc.animator).animateTo(eq(brightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+
+        when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(brightness);
+        when(followerDpc.displayPowerState.getScreenBrightness()).thenReturn(brightness);
+        when(secondFollowerDpc.displayPowerState.getScreenBrightness()).thenReturn(brightness);
+        clearInvocations(mHolder.animator, followerDpc.animator, secondFollowerDpc.animator);
+
+        // Remove the first follower and validate it goes back to its original brightness.
+        mHolder.dpc.removeDisplayBrightnessFollower(followerDpc.dpc);
+        advanceTime(1);
+        verify(followerDpc.animator).animateTo(eq(initialFollowerBrightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_DECREASE));
+
+        when(followerDpc.displayPowerState.getScreenBrightness())
+                .thenReturn(initialFollowerBrightness);
+        clearInvocations(followerDpc.animator);
+
+        // Change the brightness of the lead display and validate only the second follower responds
+        brightness = 0.7f;
+        nits = 700;
+        when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
+        when(followerDpc.automaticBrightnessController.convertToFloatScale(nits))
+                .thenReturn(brightness);
+        when(secondFollowerDpc.automaticBrightnessController.convertToFloatScale(nits))
+                .thenReturn(brightness);
+        when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
+        listener.onBrightnessChanged(brightness);
+        advanceTime(1); // Send messages, run updatePowerState
+        verify(mHolder.animator).animateTo(eq(brightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+        verify(followerDpc.animator, never()).animateTo(anyFloat(), anyFloat(), anyFloat());
+        verify(secondFollowerDpc.animator).animateTo(eq(brightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+    }
+
+    @Test
+    public void testDisplayBrightnessFollowersRemoval_RemoveAllFollowers() {
+        DisplayPowerControllerHolder followerHolder =
+                createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
+        DisplayPowerControllerHolder secondFollowerHolder =
+                createDisplayPowerController(SECOND_FOLLOWER_DISPLAY_ID,
+                        SECOND_FOLLOWER_UNIQUE_DISPLAY_ID);
+        when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+        when(followerHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+        when(secondFollowerHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        followerHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        secondFollowerHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
+                ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
+        verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture());
+        BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
+
+        // Set the initial brightness on the DPCs we're going to remove so we have a fixed value for
+        // it to return to.
+        listenerCaptor = ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
+        verify(followerHolder.brightnessSetting).registerListener(listenerCaptor.capture());
+        BrightnessSetting.BrightnessSettingListener followerListener = listenerCaptor.getValue();
+        listenerCaptor = ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
+        verify(secondFollowerHolder.brightnessSetting).registerListener(listenerCaptor.capture());
+        BrightnessSetting.BrightnessSettingListener secondFollowerListener =
+                listenerCaptor.getValue();
+        final float initialFollowerBrightness = 0.3f;
+        when(followerHolder.brightnessSetting.getBrightness()).thenReturn(
+                initialFollowerBrightness);
+        when(secondFollowerHolder.brightnessSetting.getBrightness()).thenReturn(
+                initialFollowerBrightness);
+        followerListener.onBrightnessChanged(initialFollowerBrightness);
+        secondFollowerListener.onBrightnessChanged(initialFollowerBrightness);
+        advanceTime(1);
+        verify(followerHolder.animator).animateTo(eq(initialFollowerBrightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+        verify(secondFollowerHolder.animator).animateTo(eq(initialFollowerBrightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+
+        when(followerHolder.displayPowerState.getScreenBrightness())
+                .thenReturn(initialFollowerBrightness);
+        when(secondFollowerHolder.displayPowerState.getScreenBrightness())
+                .thenReturn(initialFollowerBrightness);
+
+        mHolder.dpc.addDisplayBrightnessFollower(followerHolder.dpc);
+        mHolder.dpc.addDisplayBrightnessFollower(secondFollowerHolder.dpc);
+        clearInvocations(followerHolder.animator, secondFollowerHolder.animator);
+
+        // Validate both followers are correctly registered and receiving brightness updates
+        float brightness = 0.6f;
+        float nits = 600;
+        when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
+        when(followerHolder.automaticBrightnessController.convertToFloatScale(nits))
+                .thenReturn(brightness);
+        when(secondFollowerHolder.automaticBrightnessController.convertToFloatScale(nits))
+                .thenReturn(brightness);
+        when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
+        listener.onBrightnessChanged(brightness);
+        advanceTime(1); // Send messages, run updatePowerState
+        verify(mHolder.animator).animateTo(eq(brightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+        verify(followerHolder.animator).animateTo(eq(brightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+        verify(secondFollowerHolder.animator).animateTo(eq(brightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+
+        when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(brightness);
+        when(followerHolder.displayPowerState.getScreenBrightness()).thenReturn(brightness);
+        when(secondFollowerHolder.displayPowerState.getScreenBrightness()).thenReturn(brightness);
+        clearInvocations(mHolder.animator, followerHolder.animator, secondFollowerHolder.animator);
+
+        // Stop the lead DPC and validate that the followers go back to their original brightness.
+        mHolder.dpc.stop();
+        advanceTime(1);
+        verify(followerHolder.animator).animateTo(eq(initialFollowerBrightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_DECREASE));
+        verify(secondFollowerHolder.animator).animateTo(eq(initialFollowerBrightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_DECREASE));
+        clearInvocations(followerHolder.animator, secondFollowerHolder.animator);
+    }
+
+    @Test
+    public void testDoesNotSetScreenStateForNonDefaultDisplayUntilBootCompleted() {
+        // We should still set screen state for the default display
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+        verify(mHolder.displayPowerState, times(2)).setScreenState(anyInt());
+
+        mHolder = createDisplayPowerController(42, UNIQUE_ID);
+
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+        verify(mHolder.displayPowerState, never()).setScreenState(anyInt());
+
+        mHolder.dpc.onBootCompleted();
+        advanceTime(1); // Run updatePowerState
+        verify(mHolder.displayPowerState).setScreenState(anyInt());
+    }
+
+    @Test
+    public void testSetScreenOffBrightnessSensorEnabled_DisplayIsOff() {
+        Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        dpr.policy = DisplayPowerRequest.POLICY_OFF;
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        verify(mHolder.screenOffBrightnessSensorController, atLeastOnce())
+                .setLightSensorEnabled(true);
+
+        // The display turns on and we use the brightness value recommended by
+        // ScreenOffBrightnessSensorController
+        clearInvocations(mHolder.screenOffBrightnessSensorController);
+        float brightness = 0.14f;
+        when(mHolder.screenOffBrightnessSensorController.getAutomaticScreenBrightness())
+                .thenReturn(brightness);
+        dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+        when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness(
+                any(BrightnessEvent.class))).thenReturn(PowerManager.BRIGHTNESS_INVALID_FLOAT);
+
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        verify(mHolder.screenOffBrightnessSensorController, atLeastOnce())
+                .getAutomaticScreenBrightness();
+        verify(mHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
+    }
+
+    @Test
+    public void testSetScreenOffBrightnessSensorEnabled_DisplayIsInDoze() {
+        Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        dpr.policy = DisplayPowerRequest.POLICY_DOZE;
+        mContext.getOrCreateTestableResources().addOverride(
+                com.android.internal.R.bool.config_allowAutoBrightnessWhileDozing, true);
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        verify(mHolder.screenOffBrightnessSensorController, atLeastOnce())
+                .setLightSensorEnabled(true);
+
+        // The display turns on and we use the brightness value recommended by
+        // ScreenOffBrightnessSensorController
+        clearInvocations(mHolder.screenOffBrightnessSensorController);
+        float brightness = 0.14f;
+        when(mHolder.screenOffBrightnessSensorController.getAutomaticScreenBrightness())
+                .thenReturn(brightness);
+        dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+        when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness(
+                any(BrightnessEvent.class))).thenReturn(PowerManager.BRIGHTNESS_INVALID_FLOAT);
+
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        verify(mHolder.screenOffBrightnessSensorController, atLeastOnce())
+                .getAutomaticScreenBrightness();
+        verify(mHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
+    }
+
+    @Test
+    public void testSetScreenOffBrightnessSensorDisabled_AutoBrightnessIsDisabled() {
+        // Tests are set up with manual brightness by default, so no need to set it here.
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        dpr.policy = DisplayPowerRequest.POLICY_OFF;
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        verify(mHolder.screenOffBrightnessSensorController, atLeastOnce())
+                .setLightSensorEnabled(false);
+    }
+
+    @Test
+    public void testSetScreenOffBrightnessSensorDisabled_DisplayIsDisabled() {
+        Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+
+        mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID, /* isEnabled= */ false);
+
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        verify(mHolder.screenOffBrightnessSensorController, atLeastOnce())
+                .setLightSensorEnabled(false);
+    }
+
+    @Test
+    public void testSetScreenOffBrightnessSensorDisabled_DisplayIsOn() {
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
+
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        verify(mHolder.screenOffBrightnessSensorController, atLeastOnce())
+                .setLightSensorEnabled(false);
+    }
+
+    @Test
+    public void testSetScreenOffBrightnessSensorDisabled_DisplayIsAFollower() {
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        dpr.policy = DisplayPowerRequest.POLICY_OFF;
+
+        mHolder.dpc.onDisplayChanged(mHolder.hbmMetadata, /* leadDisplayId= */ 42);
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        verify(mHolder.screenOffBrightnessSensorController, atLeastOnce())
+                .setLightSensorEnabled(false);
+    }
+
+    @Test
+    public void testStopScreenOffBrightnessSensorControllerWhenDisplayDeviceChanges() {
+        // New display device
+        setUpDisplay(DISPLAY_ID, "new_unique_id", mHolder.display, mock(DisplayDevice.class),
+                mock(DisplayDeviceConfig.class), /* isEnabled= */ true);
+
+        mHolder.dpc.onDisplayChanged(mHolder.hbmMetadata, Layout.NO_LEAD_DISPLAY);
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        verify(mHolder.screenOffBrightnessSensorController).stop();
+    }
+
+    @Test
+    public void testAutoBrightnessEnabled_DisplayIsOn() {
+        Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        verify(mHolder.automaticBrightnessController).configure(
+                AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED,
+                /* configuration= */ null, PowerManager.BRIGHTNESS_INVALID_FLOAT,
+                /* userChangedBrightness= */ false, /* adjustment= */ 0,
+                /* userChangedAutoBrightnessAdjustment= */ false, DisplayPowerRequest.POLICY_BRIGHT,
+                /* shouldResetShortTermModel= */ false
+        );
+        verify(mHolder.hbmController)
+                .setAutoBrightnessEnabled(AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED);
+    }
+
+    @Test
+    public void testAutoBrightnessEnabled_DisplayIsInDoze() {
+        Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+        mContext.getOrCreateTestableResources().addOverride(
+                com.android.internal.R.bool.config_allowAutoBrightnessWhileDozing, true);
+        mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
+
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        dpr.policy = DisplayPowerRequest.POLICY_DOZE;
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_DOZE);
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        verify(mHolder.automaticBrightnessController).configure(
+                AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED,
+                /* configuration= */ null, PowerManager.BRIGHTNESS_INVALID_FLOAT,
+                /* userChangedBrightness= */ false, /* adjustment= */ 0,
+                /* userChangedAutoBrightnessAdjustment= */ false, DisplayPowerRequest.POLICY_DOZE,
+                /* shouldResetShortTermModel= */ false
+        );
+        verify(mHolder.hbmController)
+                .setAutoBrightnessEnabled(AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED);
+    }
+
+    @Test
+    public void testAutoBrightnessDisabled_ManualBrightnessMode() {
+        Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+                Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL);
+
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        // One triggered by the test, the other by handleBrightnessModeChange
+        verify(mHolder.automaticBrightnessController, times(2)).configure(
+                AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED,
+                /* configuration= */ null, PowerManager.BRIGHTNESS_INVALID_FLOAT,
+                /* userChangedBrightness= */ false, /* adjustment= */ 0,
+                /* userChangedAutoBrightnessAdjustment= */ false, DisplayPowerRequest.POLICY_BRIGHT,
+                /* shouldResetShortTermModel= */ false
+        );
+        verify(mHolder.hbmController, times(2))
+                .setAutoBrightnessEnabled(AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED);
+    }
+
+    @Test
+    public void testAutoBrightnessDisabled_DisplayIsOff() {
+        Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        dpr.policy = DisplayPowerRequest.POLICY_OFF;
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_OFF);
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        verify(mHolder.automaticBrightnessController).configure(
+                AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE,
+                /* configuration= */ null, PowerManager.BRIGHTNESS_INVALID_FLOAT,
+                /* userChangedBrightness= */ false, /* adjustment= */ 0,
+                /* userChangedAutoBrightnessAdjustment= */ false, DisplayPowerRequest.POLICY_OFF,
+                /* shouldResetShortTermModel= */ false
+        );
+        verify(mHolder.hbmController).setAutoBrightnessEnabled(
+                AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE);
+    }
+
+    @Test
+    public void testAutoBrightnessDisabled_DisplayIsInDoze() {
+        Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+        mContext.getOrCreateTestableResources().addOverride(
+                com.android.internal.R.bool.config_allowAutoBrightnessWhileDozing, false);
+        mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
+
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        dpr.policy = DisplayPowerRequest.POLICY_DOZE;
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_DOZE);
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        verify(mHolder.automaticBrightnessController).configure(
+                AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE,
+                /* configuration= */ null, PowerManager.BRIGHTNESS_INVALID_FLOAT,
+                /* userChangedBrightness= */ false, /* adjustment= */ 0,
+                /* userChangedAutoBrightnessAdjustment= */ false, DisplayPowerRequest.POLICY_DOZE,
+                /* shouldResetShortTermModel= */ false
+        );
+        verify(mHolder.hbmController).setAutoBrightnessEnabled(
+                AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE);
+    }
+
+    @Test
+    public void testAutoBrightnessDisabled_FollowerDisplay() {
+        Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+        mHolder.dpc.setBrightnessToFollow(0.3f, -1, 0, /* slowChange= */ false);
+
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        // One triggered by the test, the other by handleBrightnessModeChange
+        verify(mHolder.automaticBrightnessController, times(2)).configure(
+                AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED,
+                /* configuration= */ null, PowerManager.BRIGHTNESS_INVALID_FLOAT,
+                /* userChangedBrightness= */ false, /* adjustment= */ 0,
+                /* userChangedAutoBrightnessAdjustment= */ false, DisplayPowerRequest.POLICY_BRIGHT,
+                /* shouldResetShortTermModel= */ false
+        );
+
+        // HBM should be allowed for the follower display
+        verify(mHolder.hbmController)
+                .setAutoBrightnessEnabled(AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED);
+    }
+
+    @Test
+    public void testBrightnessNitsPersistWhenDisplayDeviceChanges() {
+        float brightness = 0.3f;
+        float nits = 500;
+        mContext.getOrCreateTestableResources().addOverride(
+                com.android.internal.R.bool.config_persistBrightnessNitsForDefaultDisplay,
+                true);
+
+        mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
+        when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
+
+        mHolder.dpc.setBrightness(brightness);
+        verify(mHolder.brightnessSetting).setBrightnessNitsForDefaultDisplay(nits);
+
+        float newBrightness = 0.4f;
+        when(mHolder.brightnessSetting.getBrightnessNitsForDefaultDisplay()).thenReturn(nits);
+        when(mHolder.automaticBrightnessController.convertToFloatScale(nits))
+                .thenReturn(newBrightness);
+        // New display device
+        setUpDisplay(DISPLAY_ID, "new_unique_id", mHolder.display, mock(DisplayDevice.class),
+                mock(DisplayDeviceConfig.class), /* isEnabled= */ true);
+        mHolder.dpc.onDisplayChanged(mHolder.hbmMetadata, Layout.NO_LEAD_DISPLAY);
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+        // One triggered by handleBrightnessModeChange, another triggered by onDisplayChanged
+        verify(mHolder.animator, times(2)).animateTo(eq(newBrightness), anyFloat(), anyFloat());
+    }
+
+    @Test
+    public void testShortTermModelPersistsWhenDisplayDeviceChanges() {
+        float lux = 2000;
+        float brightness = 0.4f;
+        float nits = 500;
+        when(mHolder.brightnessMappingStrategy.getUserLux()).thenReturn(lux);
+        when(mHolder.brightnessMappingStrategy.getUserBrightness()).thenReturn(brightness);
+        when(mHolder.brightnessMappingStrategy.convertToNits(brightness)).thenReturn(nits);
+        when(mHolder.brightnessMappingStrategy.convertToFloatScale(nits)).thenReturn(brightness);
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1);
+        clearInvocations(mHolder.injector);
+
+        // New display device
+        setUpDisplay(DISPLAY_ID, "new_unique_id", mHolder.display, mock(DisplayDevice.class),
+                mock(DisplayDeviceConfig.class), /* isEnabled= */ true);
+        mHolder.dpc.onDisplayChanged(mHolder.hbmMetadata, Layout.NO_LEAD_DISPLAY);
+        advanceTime(1);
+
+        verify(mHolder.injector).getAutomaticBrightnessController(
+                any(AutomaticBrightnessController.Callbacks.class),
+                any(Looper.class),
+                eq(mSensorManagerMock),
+                any(),
+                eq(mHolder.brightnessMappingStrategy),
+                anyInt(),
+                anyFloat(),
+                anyFloat(),
+                anyFloat(),
+                anyInt(),
+                anyInt(),
+                anyLong(),
+                anyLong(),
+                anyBoolean(),
+                any(HysteresisLevels.class),
+                any(HysteresisLevels.class),
+                any(HysteresisLevels.class),
+                any(HysteresisLevels.class),
+                eq(mContext),
+                any(BrightnessRangeController.class),
+                any(BrightnessThrottler.class),
+                isNull(),
+                anyInt(),
+                anyInt(),
+                eq(lux),
+                eq(brightness)
+        );
+    }
+
+    @Test
+    public void testUpdateBrightnessThrottlingDataId() {
+        mHolder.display.getDisplayInfoLocked().thermalBrightnessThrottlingDataId =
+                "throttling-data-id";
+        clearInvocations(mHolder.display.getPrimaryDisplayDeviceLocked().getDisplayDeviceConfig());
+
+        mHolder.dpc.onDisplayChanged(mHolder.hbmMetadata, Layout.NO_LEAD_DISPLAY);
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        verify(mHolder.display.getPrimaryDisplayDeviceLocked().getDisplayDeviceConfig())
+                .getThermalBrightnessThrottlingDataMapByThrottlingId();
+    }
+
+    @Test
+    public void testSetBrightness_BrightnessShouldBeClamped() {
+        float clampedBrightness = 0.6f;
+        when(mHolder.hbmController.getCurrentBrightnessMax()).thenReturn(clampedBrightness);
+
+        mHolder.dpc.setBrightness(PowerManager.BRIGHTNESS_MAX);
+
+        verify(mHolder.brightnessSetting).setBrightness(clampedBrightness);
+    }
+
+    @Test
+    public void testDwbcCallsHappenOnHandler() {
+        mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
+
+        mHolder.dpc.setAutomaticScreenBrightnessMode(true);
+        verify(mDisplayWhiteBalanceControllerMock, never()).setStrongModeEnabled(true);
+
+        // dispatch handler looper
+        advanceTime(1);
+        verify(mDisplayWhiteBalanceControllerMock, times(1)).setStrongModeEnabled(true);
+    }
+
+    /**
+     * Creates a mock and registers it to {@link LocalServices}.
+     */
+    private static <T> void addLocalServiceMock(Class<T> clazz, T mock) {
+        LocalServices.removeServiceForTest(clazz);
+        LocalServices.addService(clazz, mock);
+    }
+
+    private void advanceTime(long timeMs) {
+        mClock.fastForward(timeMs);
+        mTestLooper.dispatchAll();
+    }
+
+    private void setUpSensors() throws Exception {
+        mProxSensor = TestUtils.createSensor(Sensor.TYPE_PROXIMITY, Sensor.STRING_TYPE_PROXIMITY,
+                PROX_SENSOR_MAX_RANGE);
+        Sensor screenOffBrightnessSensor = TestUtils.createSensor(
+                Sensor.TYPE_LIGHT, Sensor.STRING_TYPE_LIGHT);
+        when(mSensorManagerMock.getSensorList(eq(Sensor.TYPE_ALL)))
+                .thenReturn(List.of(mProxSensor, screenOffBrightnessSensor));
+    }
+
+    private SensorEventListener getSensorEventListener(Sensor sensor) {
+        verify(mSensorManagerMock).registerListener(mSensorEventListenerCaptor.capture(),
+                eq(sensor), eq(SensorManager.SENSOR_DELAY_NORMAL), isA(Handler.class));
+        return mSensorEventListenerCaptor.getValue();
+    }
+
+    private void setUpDisplay(int displayId, String uniqueId, LogicalDisplay logicalDisplayMock,
+            DisplayDevice displayDeviceMock, DisplayDeviceConfig displayDeviceConfigMock,
+            boolean isEnabled) {
+        DisplayInfo info = new DisplayInfo();
+        DisplayDeviceInfo deviceInfo = new DisplayDeviceInfo();
+        deviceInfo.uniqueId = uniqueId;
+
+        when(logicalDisplayMock.getDisplayIdLocked()).thenReturn(displayId);
+        when(logicalDisplayMock.getPrimaryDisplayDeviceLocked()).thenReturn(displayDeviceMock);
+        when(logicalDisplayMock.getDisplayInfoLocked()).thenReturn(info);
+        when(logicalDisplayMock.isEnabledLocked()).thenReturn(isEnabled);
+        when(logicalDisplayMock.isInTransitionLocked()).thenReturn(false);
+        when(displayDeviceMock.getDisplayDeviceInfoLocked()).thenReturn(deviceInfo);
+        when(displayDeviceMock.getUniqueId()).thenReturn(uniqueId);
+        when(displayDeviceMock.getDisplayDeviceConfig()).thenReturn(displayDeviceConfigMock);
+        when(displayDeviceConfigMock.getProximitySensor()).thenReturn(
+                new DisplayDeviceConfig.SensorData() {
+                    {
+                        type = Sensor.STRING_TYPE_PROXIMITY;
+                        name = null;
+                    }
+                });
+        when(displayDeviceConfigMock.getNits()).thenReturn(new float[]{2, 500});
+        when(displayDeviceConfigMock.isAutoBrightnessAvailable()).thenReturn(true);
+        when(displayDeviceConfigMock.getAmbientLightSensor()).thenReturn(
+                new DisplayDeviceConfig.SensorData());
+        when(displayDeviceConfigMock.getScreenOffBrightnessSensor()).thenReturn(
+                new DisplayDeviceConfig.SensorData() {
+                    {
+                        type = Sensor.STRING_TYPE_LIGHT;
+                        name = null;
+                    }
+                });
+        when(displayDeviceConfigMock.getScreenOffBrightnessSensorValueToLux())
+                .thenReturn(new int[0]);
+        when(displayDeviceConfigMock.getBrightnessRampFastDecrease())
+                .thenReturn(BRIGHTNESS_RAMP_RATE_FAST_DECREASE);
+        when(displayDeviceConfigMock.getBrightnessRampFastIncrease())
+                .thenReturn(BRIGHTNESS_RAMP_RATE_FAST_INCREASE);
+        when(displayDeviceConfigMock.getBrightnessRampSlowDecrease())
+                .thenReturn(BRIGHTNESS_RAMP_RATE_SLOW_DECREASE);
+        when(displayDeviceConfigMock.getBrightnessRampSlowIncrease())
+                .thenReturn(BRIGHTNESS_RAMP_RATE_SLOW_INCREASE);
+    }
+
+    private DisplayPowerControllerHolder createDisplayPowerController(int displayId,
+            String uniqueId) {
+        return createDisplayPowerController(displayId, uniqueId, /* isEnabled= */ true);
+    }
+
+    private DisplayPowerControllerHolder createDisplayPowerController(int displayId,
+            String uniqueId, boolean isEnabled) {
+        final DisplayPowerState displayPowerState = mock(DisplayPowerState.class);
+        final DualRampAnimator<DisplayPowerState> animator = mock(DualRampAnimator.class);
+        final AutomaticBrightnessController automaticBrightnessController =
+                mock(AutomaticBrightnessController.class);
+        final BrightnessMappingStrategy brightnessMappingStrategy =
+                mock(BrightnessMappingStrategy.class);
+        final HysteresisLevels hysteresisLevels = mock(HysteresisLevels.class);
+        final ScreenOffBrightnessSensorController screenOffBrightnessSensorController =
+                mock(ScreenOffBrightnessSensorController.class);
+        final HighBrightnessModeController hbmController = mock(HighBrightnessModeController.class);
+
+        when(hbmController.getCurrentBrightnessMax()).thenReturn(PowerManager.BRIGHTNESS_MAX);
+
+        DisplayPowerController.Injector injector = spy(new TestInjector(displayPowerState, animator,
+                automaticBrightnessController, brightnessMappingStrategy, hysteresisLevels,
+                screenOffBrightnessSensorController, hbmController));
+
+        final LogicalDisplay display = mock(LogicalDisplay.class);
+        final DisplayDevice device = mock(DisplayDevice.class);
+        final HighBrightnessModeMetadata hbmMetadata = mock(HighBrightnessModeMetadata.class);
+        final BrightnessSetting brightnessSetting = mock(BrightnessSetting.class);
+        final DisplayDeviceConfig config = mock(DisplayDeviceConfig.class);
+
+        setUpDisplay(displayId, uniqueId, display, device, config, isEnabled);
+
+        final DisplayPowerController dpc = new DisplayPowerController(
+                mContext, injector, mDisplayPowerCallbacksMock, mHandler,
+                mSensorManagerMock, mDisplayBlankerMock, display,
+                mBrightnessTrackerMock, brightnessSetting, () -> {},
+                hbmMetadata, /* bootCompleted= */ false);
+
+        return new DisplayPowerControllerHolder(dpc, display, displayPowerState, brightnessSetting,
+                animator, automaticBrightnessController, screenOffBrightnessSensorController,
+                hbmController, hbmMetadata, brightnessMappingStrategy, injector);
+    }
+
+    /**
+     * A class for holding a DisplayPowerController under test and all the mocks specifically
+     * related to it.
+     */
+    private static class DisplayPowerControllerHolder {
+        public final DisplayPowerController dpc;
+        public final LogicalDisplay display;
+        public final DisplayPowerState displayPowerState;
+        public final BrightnessSetting brightnessSetting;
+        public final DualRampAnimator<DisplayPowerState> animator;
+        public final AutomaticBrightnessController automaticBrightnessController;
+        public final ScreenOffBrightnessSensorController screenOffBrightnessSensorController;
+        public final HighBrightnessModeController hbmController;
+        public final HighBrightnessModeMetadata hbmMetadata;
+        public final BrightnessMappingStrategy brightnessMappingStrategy;
+        public final DisplayPowerController.Injector injector;
+
+        DisplayPowerControllerHolder(DisplayPowerController dpc, LogicalDisplay display,
+                DisplayPowerState displayPowerState, BrightnessSetting brightnessSetting,
+                DualRampAnimator<DisplayPowerState> animator,
+                AutomaticBrightnessController automaticBrightnessController,
+                ScreenOffBrightnessSensorController screenOffBrightnessSensorController,
+                HighBrightnessModeController hbmController,
+                HighBrightnessModeMetadata hbmMetadata,
+                BrightnessMappingStrategy brightnessMappingStrategy,
+                DisplayPowerController.Injector injector) {
+            this.dpc = dpc;
+            this.display = display;
+            this.displayPowerState = displayPowerState;
+            this.brightnessSetting = brightnessSetting;
+            this.animator = animator;
+            this.automaticBrightnessController = automaticBrightnessController;
+            this.screenOffBrightnessSensorController = screenOffBrightnessSensorController;
+            this.hbmController = hbmController;
+            this.hbmMetadata = hbmMetadata;
+            this.brightnessMappingStrategy = brightnessMappingStrategy;
+            this.injector = injector;
+        }
+    }
+
+    private class TestInjector extends DisplayPowerController.Injector {
+        private final DisplayPowerState mDisplayPowerState;
+        private final DualRampAnimator<DisplayPowerState> mAnimator;
+        private final AutomaticBrightnessController mAutomaticBrightnessController;
+        private final BrightnessMappingStrategy mBrightnessMappingStrategy;
+        private final HysteresisLevels mHysteresisLevels;
+        private final ScreenOffBrightnessSensorController mScreenOffBrightnessSensorController;
+        private final HighBrightnessModeController mHighBrightnessModeController;
+
+        TestInjector(DisplayPowerState dps, DualRampAnimator<DisplayPowerState> animator,
+                AutomaticBrightnessController automaticBrightnessController,
+                BrightnessMappingStrategy brightnessMappingStrategy,
+                HysteresisLevels hysteresisLevels,
+                ScreenOffBrightnessSensorController screenOffBrightnessSensorController,
+                HighBrightnessModeController highBrightnessModeController) {
+            mDisplayPowerState = dps;
+            mAnimator = animator;
+            mAutomaticBrightnessController = automaticBrightnessController;
+            mBrightnessMappingStrategy = brightnessMappingStrategy;
+            mHysteresisLevels = hysteresisLevels;
+            mScreenOffBrightnessSensorController = screenOffBrightnessSensorController;
+            mHighBrightnessModeController = highBrightnessModeController;
+        }
+
+        @Override
+        DisplayPowerController.Clock getClock() {
+            return mClock::now;
+        }
+
+        @Override
+        DisplayPowerState getDisplayPowerState(DisplayBlanker blanker, ColorFade colorFade,
+                int displayId, int displayState) {
+            return mDisplayPowerState;
+        }
+
+        @Override
+        DualRampAnimator<DisplayPowerState> getDualRampAnimator(DisplayPowerState dps,
+                FloatProperty<DisplayPowerState> firstProperty,
+                FloatProperty<DisplayPowerState> secondProperty) {
+            return mAnimator;
+        }
+
+        @Override
+        AutomaticBrightnessController getAutomaticBrightnessController(
+                AutomaticBrightnessController.Callbacks callbacks, Looper looper,
+                SensorManager sensorManager, Sensor lightSensor,
+                BrightnessMappingStrategy interactiveModeBrightnessMapper,
+                int lightSensorWarmUpTime, float brightnessMin, float brightnessMax,
+                float dozeScaleFactor, int lightSensorRate, int initialLightSensorRate,
+                long brighteningLightDebounceConfig, long darkeningLightDebounceConfig,
+                boolean resetAmbientLuxAfterWarmUpConfig,
+                HysteresisLevels ambientBrightnessThresholds,
+                HysteresisLevels screenBrightnessThresholds,
+                HysteresisLevels ambientBrightnessThresholdsIdle,
+                HysteresisLevels screenBrightnessThresholdsIdle, Context context,
+                BrightnessRangeController brightnessRangeController,
+                BrightnessThrottler brightnessThrottler,
+                BrightnessMappingStrategy idleModeBrightnessMapper,
+                int ambientLightHorizonShort, int ambientLightHorizonLong, float userLux,
+                float userBrightness) {
+            return mAutomaticBrightnessController;
+        }
+
+        @Override
+        BrightnessMappingStrategy getInteractiveModeBrightnessMapper(Resources resources,
+                DisplayDeviceConfig displayDeviceConfig,
+                DisplayWhiteBalanceController displayWhiteBalanceController) {
+            return mBrightnessMappingStrategy;
+        }
+
+        @Override
+        HysteresisLevels getHysteresisLevels(float[] brighteningThresholdsPercentages,
+                float[] darkeningThresholdsPercentages, float[] brighteningThresholdLevels,
+                float[] darkeningThresholdLevels, float minDarkeningThreshold,
+                float minBrighteningThreshold) {
+            return mHysteresisLevels;
+        }
+
+        @Override
+        HysteresisLevels getHysteresisLevels(float[] brighteningThresholdsPercentages,
+                float[] darkeningThresholdsPercentages, float[] brighteningThresholdLevels,
+                float[] darkeningThresholdLevels, float minDarkeningThreshold,
+                float minBrighteningThreshold, boolean potentialOldBrightnessRange) {
+            return mHysteresisLevels;
+        }
+
+        @Override
+        ScreenOffBrightnessSensorController getScreenOffBrightnessSensorController(
+                SensorManager sensorManager, Sensor lightSensor, Handler handler,
+                ScreenOffBrightnessSensorController.Clock clock, int[] sensorValueToLux,
+                BrightnessMappingStrategy brightnessMapper) {
+            return mScreenOffBrightnessSensorController;
+        }
+
+        @Override
+        HighBrightnessModeController getHighBrightnessModeController(Handler handler, int width,
+                int height, IBinder displayToken, String displayUniqueId, float brightnessMin,
+                float brightnessMax, DisplayDeviceConfig.HighBrightnessModeData hbmData,
+                HighBrightnessModeController.HdrBrightnessDeviceConfig hdrBrightnessCfg,
+                Runnable hbmChangeCallback, HighBrightnessModeMetadata hbmMetadata,
+                Context context) {
+            return mHighBrightnessModeController;
+        }
+
+        @Override
+        DisplayWhiteBalanceController getDisplayWhiteBalanceController(Handler handler,
+                SensorManager sensorManager, Resources resources) {
+            return mDisplayWhiteBalanceControllerMock;
+        }
+    }
+}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerProximityStateControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerProximityStateControllerTest.java
new file mode 100644
index 0000000..534a708
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerProximityStateControllerTest.java
@@ -0,0 +1,453 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+import android.hardware.Sensor;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.hardware.display.DisplayManagerInternal;
+import android.os.test.TestLooper;
+import android.view.Display;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.server.testutils.OffsettableClock;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.List;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public final class DisplayPowerProximityStateControllerTest {
+    @Mock
+    WakelockController mWakelockController;
+
+    @Mock
+    DisplayDeviceConfig mDisplayDeviceConfig;
+
+    @Mock
+    Runnable mNudgeUpdatePowerState;
+
+    @Mock
+    SensorManager mSensorManager;
+
+    private Sensor mProximitySensor;
+    private OffsettableClock mClock;
+    private TestLooper mTestLooper;
+    private SensorEventListener mSensorEventListener;
+    private DisplayPowerProximityStateController mDisplayPowerProximityStateController;
+
+    @Before
+    public void before() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mClock = new OffsettableClock.Stopped();
+        mTestLooper = new TestLooper(mClock::now);
+        when(mDisplayDeviceConfig.getProximitySensor()).thenReturn(
+                new DisplayDeviceConfig.SensorData() {
+                    {
+                        type = Sensor.STRING_TYPE_PROXIMITY;
+                        // This is kept null because currently there is no way to define a sensor
+                        // name in TestUtils
+                        name = null;
+                    }
+                });
+        setUpProxSensor();
+        DisplayPowerProximityStateController.Injector injector =
+                new DisplayPowerProximityStateController.Injector() {
+                    @Override
+                    DisplayPowerProximityStateController.Clock createClock() {
+                        return mClock::now;
+                    }
+                };
+        mDisplayPowerProximityStateController = new DisplayPowerProximityStateController(
+                mWakelockController, mDisplayDeviceConfig, mTestLooper.getLooper(),
+                mNudgeUpdatePowerState, Display.DEFAULT_DISPLAY,
+                mSensorManager, injector);
+        mSensorEventListener = mDisplayPowerProximityStateController.getProximitySensorListener();
+    }
+
+    @Test
+    public void updatePendingProximityRequestsWorksAsExpectedWhenPending() {
+        // Set the system to pending wait for proximity
+        assertTrue(mDisplayPowerProximityStateController.setPendingWaitForNegativeProximityLocked(
+                true));
+        assertTrue(
+                mDisplayPowerProximityStateController.getPendingWaitForNegativeProximityLocked());
+
+        // Update the pending proximity wait request
+        mDisplayPowerProximityStateController.updatePendingProximityRequestsLocked();
+        assertTrue(mDisplayPowerProximityStateController.getWaitingForNegativeProximity());
+        assertFalse(
+                mDisplayPowerProximityStateController.getPendingWaitForNegativeProximityLocked());
+    }
+
+    @Test
+    public void updatePendingProximityRequestsWorksAsExpectedWhenNotPending() {
+        // Will not wait or be in the pending wait state of not already pending
+        mDisplayPowerProximityStateController.updatePendingProximityRequestsLocked();
+        assertFalse(mDisplayPowerProximityStateController.getWaitingForNegativeProximity());
+        assertFalse(
+                mDisplayPowerProximityStateController.getPendingWaitForNegativeProximityLocked());
+    }
+
+    @Test
+    public void updatePendingProximityRequestsWorksAsExpectedWhenPendingAndProximityIgnored()
+            throws Exception {
+        // Set the system to the state where it will ignore proximity unless changed
+        enableProximitySensor();
+        emitAndValidatePositiveProximityEvent();
+        mDisplayPowerProximityStateController.ignoreProximitySensorUntilChangedInternal();
+        advanceTime();
+        assertTrue(mDisplayPowerProximityStateController.shouldIgnoreProximityUntilChanged());
+        verify(mNudgeUpdatePowerState, times(2)).run();
+
+        // Do not set the system to pending wait for proximity
+        mDisplayPowerProximityStateController.updatePendingProximityRequestsLocked();
+        assertFalse(mDisplayPowerProximityStateController.getWaitingForNegativeProximity());
+        assertFalse(
+                mDisplayPowerProximityStateController.getPendingWaitForNegativeProximityLocked());
+
+        // Set the system to pending wait for proximity. But because the proximity is being
+        // ignored, it will not wait or not set the pending wait
+        assertTrue(mDisplayPowerProximityStateController.setPendingWaitForNegativeProximityLocked(
+                true));
+        mDisplayPowerProximityStateController.updatePendingProximityRequestsLocked();
+        assertFalse(mDisplayPowerProximityStateController.getWaitingForNegativeProximity());
+        assertFalse(
+                mDisplayPowerProximityStateController.getPendingWaitForNegativeProximityLocked());
+    }
+
+    @Test
+    public void cleanupDisablesTheProximitySensor() {
+        enableProximitySensor();
+        mDisplayPowerProximityStateController.cleanup();
+        verify(mSensorManager).unregisterListener(
+                mSensorEventListener);
+        assertFalse(mDisplayPowerProximityStateController.isProximitySensorEnabled());
+        assertFalse(mDisplayPowerProximityStateController.getWaitingForNegativeProximity());
+        assertFalse(mDisplayPowerProximityStateController.shouldIgnoreProximityUntilChanged());
+        assertEquals(mDisplayPowerProximityStateController.getProximity(),
+                DisplayPowerProximityStateController.PROXIMITY_UNKNOWN);
+        when(mWakelockController.releaseWakelock(
+                WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE)).thenReturn(true);
+        assertEquals(mDisplayPowerProximityStateController.getPendingProximityDebounceTime(), -1);
+    }
+
+    @Test
+    public void isProximitySensorAvailableReturnsTrueWhenAvailable() {
+        assertTrue(mDisplayPowerProximityStateController.isProximitySensorAvailable());
+    }
+
+    @Test
+    public void isProximitySensorAvailableReturnsFalseWhenNotAvailableAndNoDefault() {
+        when(mDisplayDeviceConfig.getProximitySensor()).thenReturn(
+                new DisplayDeviceConfig.SensorData() {
+                    {
+                        type = null;
+                        name = null;
+                    }
+                });
+        mDisplayPowerProximityStateController = new DisplayPowerProximityStateController(
+                mWakelockController, mDisplayDeviceConfig, mTestLooper.getLooper(),
+                mNudgeUpdatePowerState, Display.DEFAULT_DISPLAY,
+                mSensorManager, null);
+        assertFalse(mDisplayPowerProximityStateController.isProximitySensorAvailable());
+    }
+
+    @Test
+    public void isProximitySensorAvailableReturnsTrueWhenNotAvailableAndHasDefault()
+            throws Exception {
+        when(mDisplayDeviceConfig.getProximitySensor()).thenReturn(
+                new DisplayDeviceConfig.SensorData() {
+                    {
+                        type = null;
+                        name = null;
+                    }
+                });
+        when(mSensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY)).thenReturn(
+                TestUtils.createSensor(Sensor.TYPE_PROXIMITY, "proximity"));
+        mDisplayPowerProximityStateController = new DisplayPowerProximityStateController(
+                mWakelockController, mDisplayDeviceConfig, mTestLooper.getLooper(),
+                mNudgeUpdatePowerState, Display.DEFAULT_DISPLAY,
+                mSensorManager, null);
+        assertTrue(mDisplayPowerProximityStateController.isProximitySensorAvailable());
+    }
+
+    @Test
+    public void isProximitySensorAvailableReturnsFalseWhenNotAvailableHasDefaultNonDefaultDisplay()
+            throws Exception {
+        when(mDisplayDeviceConfig.getProximitySensor()).thenReturn(
+                new DisplayDeviceConfig.SensorData() {
+                    {
+                        type = null;
+                        name = null;
+                    }
+                });
+        when(mSensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY)).thenReturn(
+                TestUtils.createSensor(Sensor.TYPE_PROXIMITY, "proximity"));
+        mDisplayPowerProximityStateController = new DisplayPowerProximityStateController(
+                mWakelockController, mDisplayDeviceConfig, mTestLooper.getLooper(),
+                mNudgeUpdatePowerState, 1,
+                mSensorManager, null);
+        assertFalse(mDisplayPowerProximityStateController.isProximitySensorAvailable());
+    }
+
+    @Test
+    public void isProximitySensorAvailableReturnsTrueWhenNoSensorConfigured() throws Exception {
+        when(mDisplayDeviceConfig.getProximitySensor()).thenReturn(null);
+        when(mSensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY)).thenReturn(
+                TestUtils.createSensor(Sensor.TYPE_PROXIMITY, Sensor.STRING_TYPE_PROXIMITY));
+
+        mDisplayPowerProximityStateController = new DisplayPowerProximityStateController(
+                mWakelockController, mDisplayDeviceConfig, mTestLooper.getLooper(),
+                mNudgeUpdatePowerState, Display.DEFAULT_DISPLAY,
+                mSensorManager, null);
+        assertFalse(mDisplayPowerProximityStateController.isProximitySensorAvailable());
+    }
+
+    @Test
+    public void notifyDisplayDeviceChangedReloadsTheProximitySensor() throws Exception {
+        DisplayDeviceConfig updatedDisplayDeviceConfig = mock(DisplayDeviceConfig.class);
+        when(updatedDisplayDeviceConfig.getProximitySensor()).thenReturn(
+                new DisplayDeviceConfig.SensorData() {
+                    {
+                        type = Sensor.STRING_TYPE_PROXIMITY;
+                        name = null;
+                    }
+                });
+        Sensor newProxSensor = TestUtils.createSensor(
+                Sensor.TYPE_PROXIMITY, Sensor.STRING_TYPE_PROXIMITY, 4.0f);
+        when(mSensorManager.getSensorList(eq(Sensor.TYPE_ALL)))
+                .thenReturn(List.of(newProxSensor));
+        mDisplayPowerProximityStateController.notifyDisplayDeviceChanged(
+                updatedDisplayDeviceConfig);
+        assertTrue(mDisplayPowerProximityStateController.isProximitySensorAvailable());
+    }
+
+    @Test
+    public void setPendingWaitForNegativeProximityLockedWorksAsExpected() {
+        // Doesn't do anything not asked to wait
+        assertFalse(mDisplayPowerProximityStateController.setPendingWaitForNegativeProximityLocked(
+                false));
+        assertFalse(
+                mDisplayPowerProximityStateController.getPendingWaitForNegativeProximityLocked());
+
+        // Sets pending wait negative proximity if not already waiting
+        assertTrue(mDisplayPowerProximityStateController.setPendingWaitForNegativeProximityLocked(
+                true));
+        assertTrue(
+                mDisplayPowerProximityStateController.getPendingWaitForNegativeProximityLocked());
+
+        // Will not set pending wait negative proximity if already waiting
+        assertFalse(mDisplayPowerProximityStateController.setPendingWaitForNegativeProximityLocked(
+                true));
+        assertTrue(
+                mDisplayPowerProximityStateController.getPendingWaitForNegativeProximityLocked());
+
+    }
+
+    @Test
+    public void evaluateProximityStateWhenRequestedUseOfProximitySensor() throws Exception {
+        // Enable the proximity sensor
+        enableProximitySensor();
+
+        // Emit a positive proximity event to move the system to a state to mimic a scenario
+        // where the system is in positive proximity
+        emitAndValidatePositiveProximityEvent();
+
+        // Again evaluate the proximity state, with system having positive proximity
+        setScreenOffBecauseOfPositiveProximityState();
+    }
+
+    @Test
+    public void evaluateProximityStateWhenScreenOffBecauseOfPositiveProximity() throws Exception {
+        // Enable the proximity sensor
+        enableProximitySensor();
+
+        // Emit a positive proximity event to move the system to a state to mimic a scenario
+        // where the system is in positive proximity
+        emitAndValidatePositiveProximityEvent();
+
+        // Again evaluate the proximity state, with system having positive proximity
+        setScreenOffBecauseOfPositiveProximityState();
+
+        // Set the system to pending wait for proximity
+        mDisplayPowerProximityStateController.setPendingWaitForNegativeProximityLocked(true);
+        // Update the pending proximity wait request
+        mDisplayPowerProximityStateController.updatePendingProximityRequestsLocked();
+
+        // Start ignoring proximity sensor
+        mDisplayPowerProximityStateController.ignoreProximitySensorUntilChangedInternal();
+        // Re-evaluate the proximity state, such that the system is detecting the positive
+        // proximity, and screen is off because of that
+        when(mWakelockController.getOnProximityNegativeRunnable()).thenReturn(mock(Runnable.class));
+        mDisplayPowerProximityStateController.updateProximityState(mock(
+                DisplayManagerInternal.DisplayPowerRequest.class), Display.STATE_ON);
+        assertTrue(mDisplayPowerProximityStateController.isProximitySensorEnabled());
+        assertFalse(mDisplayPowerProximityStateController.isScreenOffBecauseOfProximity());
+        assertTrue(
+                mDisplayPowerProximityStateController
+                        .shouldSkipRampBecauseOfProximityChangeToNegative());
+        verify(mWakelockController).acquireWakelock(
+                WakelockController.WAKE_LOCK_PROXIMITY_NEGATIVE);
+    }
+
+    @Test
+    public void evaluateProximityStateWhenDisplayIsTurningOff() throws Exception {
+        // Enable the proximity sensor
+        enableProximitySensor();
+
+        // Emit a positive proximity event to move the system to a state to mimic a scenario
+        // where the system is in positive proximity
+        emitAndValidatePositiveProximityEvent();
+
+        // Again evaluate the proximity state, with system having positive proximity
+        setScreenOffBecauseOfPositiveProximityState();
+
+        // Re-evaluate the proximity state, such that the system is detecting the positive
+        // proximity, and screen is off because of that
+        mDisplayPowerProximityStateController.updateProximityState(mock(
+                DisplayManagerInternal.DisplayPowerRequest.class), Display.STATE_OFF);
+        verify(mSensorManager).unregisterListener(
+                mSensorEventListener);
+        assertFalse(mDisplayPowerProximityStateController.isProximitySensorEnabled());
+        assertFalse(mDisplayPowerProximityStateController.getWaitingForNegativeProximity());
+        assertFalse(mDisplayPowerProximityStateController.shouldIgnoreProximityUntilChanged());
+        assertEquals(mDisplayPowerProximityStateController.getProximity(),
+                DisplayPowerProximityStateController.PROXIMITY_UNKNOWN);
+        when(mWakelockController.releaseWakelock(
+                WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE)).thenReturn(true);
+        assertEquals(mDisplayPowerProximityStateController.getPendingProximityDebounceTime(), -1);
+    }
+
+    @Test
+    public void evaluateProximityStateNotWaitingForNegativeProximityAndNotUsingProxSensor()
+            throws Exception {
+        // Enable the proximity sensor
+        enableProximitySensor();
+
+        // Emit a positive proximity event to move the system to a state to mimic a scenario
+        // where the system is in positive proximity
+        emitAndValidatePositiveProximityEvent();
+
+        // Re-evaluate the proximity state, such that the system is detecting the positive
+        // proximity, and screen is off because of that
+        mDisplayPowerProximityStateController.updateProximityState(mock(
+                DisplayManagerInternal.DisplayPowerRequest.class), Display.STATE_ON);
+        verify(mSensorManager).unregisterListener(
+                mSensorEventListener);
+        assertFalse(mDisplayPowerProximityStateController.isProximitySensorEnabled());
+        assertFalse(mDisplayPowerProximityStateController.getWaitingForNegativeProximity());
+        assertFalse(mDisplayPowerProximityStateController.shouldIgnoreProximityUntilChanged());
+        assertEquals(mDisplayPowerProximityStateController.getProximity(),
+                DisplayPowerProximityStateController.PROXIMITY_UNKNOWN);
+        when(mWakelockController.releaseWakelock(
+                WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE)).thenReturn(true);
+        assertEquals(mDisplayPowerProximityStateController.getPendingProximityDebounceTime(), -1);
+    }
+
+    private void advanceTime() {
+        mClock.fastForward(1);
+        mTestLooper.dispatchAll();
+    }
+
+    private void setUpProxSensor() throws Exception {
+        mProximitySensor = TestUtils.createSensor(
+                Sensor.TYPE_PROXIMITY, Sensor.STRING_TYPE_PROXIMITY, 5.0f);
+        when(mSensorManager.getSensorList(eq(Sensor.TYPE_ALL)))
+                .thenReturn(List.of(mProximitySensor));
+    }
+
+    private void emitAndValidatePositiveProximityEvent() throws Exception {
+        // Emit a positive proximity event to move the system to a state to mimic a scenario
+        // where the system is in positive proximity
+        when(mWakelockController.releaseWakelock(
+                WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE)).thenReturn(true);
+        mSensorEventListener.onSensorChanged(TestUtils.createSensorEvent(mProximitySensor, 4));
+        verify(mSensorManager).registerListener(mSensorEventListener,
+                mProximitySensor, SensorManager.SENSOR_DELAY_NORMAL,
+                mDisplayPowerProximityStateController.getHandler());
+        verify(mWakelockController).acquireWakelock(
+                WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE);
+        assertEquals(mDisplayPowerProximityStateController.getPendingProximity(),
+                DisplayPowerProximityStateController.PROXIMITY_POSITIVE);
+        assertFalse(mDisplayPowerProximityStateController.shouldIgnoreProximityUntilChanged());
+        assertEquals(mDisplayPowerProximityStateController.getProximity(),
+                DisplayPowerProximityStateController.PROXIMITY_POSITIVE);
+        verify(mNudgeUpdatePowerState).run();
+        assertEquals(mDisplayPowerProximityStateController.getPendingProximityDebounceTime(), -1);
+    }
+
+    // Call evaluateProximityState with the request for using the proximity sensor. This will
+    // register the proximity sensor listener, which will be needed for mocking positive
+    // proximity scenarios.
+    private void enableProximitySensor() {
+        DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
+                DisplayManagerInternal.DisplayPowerRequest.class);
+        displayPowerRequest.useProximitySensor = true;
+        mDisplayPowerProximityStateController.updateProximityState(displayPowerRequest,
+                Display.STATE_ON);
+        verify(mSensorManager).registerListener(
+                mSensorEventListener,
+                mProximitySensor, SensorManager.SENSOR_DELAY_NORMAL,
+                mDisplayPowerProximityStateController.getHandler());
+        assertTrue(mDisplayPowerProximityStateController.isProximitySensorEnabled());
+        assertFalse(mDisplayPowerProximityStateController.shouldIgnoreProximityUntilChanged());
+        assertFalse(mDisplayPowerProximityStateController.isScreenOffBecauseOfProximity());
+        verifyZeroInteractions(mWakelockController);
+    }
+
+    private void setScreenOffBecauseOfPositiveProximityState() {
+        // Prepare a request to indicate that the proximity sensor is to be used
+        DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
+                DisplayManagerInternal.DisplayPowerRequest.class);
+        displayPowerRequest.useProximitySensor = true;
+
+        Runnable onProximityPositiveRunnable = mock(Runnable.class);
+        when(mWakelockController.getOnProximityPositiveRunnable()).thenReturn(
+                onProximityPositiveRunnable);
+
+        mDisplayPowerProximityStateController.updateProximityState(displayPowerRequest,
+                Display.STATE_ON);
+        verify(mSensorManager).registerListener(
+                mSensorEventListener,
+                mProximitySensor, SensorManager.SENSOR_DELAY_NORMAL,
+                mDisplayPowerProximityStateController.getHandler());
+        assertTrue(mDisplayPowerProximityStateController.isProximitySensorEnabled());
+        assertFalse(mDisplayPowerProximityStateController.shouldIgnoreProximityUntilChanged());
+        assertTrue(mDisplayPowerProximityStateController.isScreenOffBecauseOfProximity());
+        verify(mWakelockController).acquireWakelock(
+                WakelockController.WAKE_LOCK_PROXIMITY_POSITIVE);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/display/HbmEventTest.java b/services/tests/displayservicetests/src/com/android/server/display/HbmEventTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/display/HbmEventTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/HbmEventTest.java
diff --git a/services/tests/displayservicetests/src/com/android/server/display/HighBrightnessModeControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/HighBrightnessModeControllerTest.java
new file mode 100644
index 0000000..76e6ec7
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/HighBrightnessModeControllerTest.java
@@ -0,0 +1,756 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 android.hardware.display.BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR;
+import static android.hardware.display.BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF;
+import static android.hardware.display.BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT;
+
+import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED;
+import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED;
+import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE;
+import static com.android.server.display.DisplayDeviceConfig.HDR_PERCENT_OF_SCREEN_REQUIRED_DEFAULT;
+import static com.android.server.display.HighBrightnessModeController.HBM_TRANSITION_POINT_INVALID;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.anyFloat;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.hardware.display.BrightnessInfo;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.Message;
+import android.os.test.TestLooper;
+import android.test.mock.MockContentResolver;
+import android.util.MathUtils;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.util.FrameworkStatsLog;
+import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.internal.util.test.FakeSettingsProviderRule;
+import com.android.server.display.DisplayDeviceConfig.HighBrightnessModeData;
+import com.android.server.display.HighBrightnessModeController.Injector;
+import com.android.server.testutils.OffsettableClock;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class HighBrightnessModeControllerTest {
+
+    private static final int MINIMUM_LUX = 100;
+    private static final float TRANSITION_POINT = 0.763f;
+    private static final long TIME_WINDOW_MILLIS = 55 * 1000;
+    private static final long TIME_ALLOWED_IN_WINDOW_MILLIS = 12 * 1000;
+    private static final long TIME_MINIMUM_AVAILABLE_TO_ENABLE_MILLIS = 5 * 1000;
+    private static final boolean ALLOW_IN_LOW_POWER_MODE = false;
+
+    private static final float DEFAULT_MIN = 0.01f;
+    private static final float DEFAULT_MAX = 0.80f;
+
+    private static final int DISPLAY_WIDTH = 900;
+    private static final int DISPLAY_HEIGHT = 1600;
+
+    private static final float EPSILON = 0.000001f;
+
+    private OffsettableClock mClock;
+    private TestLooper mTestLooper;
+    private Handler mHandler;
+    private Binder mDisplayToken;
+    private String mDisplayUniqueId;
+    private Context mContextSpy;
+    private HighBrightnessModeMetadata mHighBrightnessModeMetadata;
+
+    @Rule
+    public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
+
+    @Mock Injector mInjectorMock;
+    @Mock HighBrightnessModeController.HdrBrightnessDeviceConfig mHdrBrightnessDeviceConfigMock;
+
+    private static final HighBrightnessModeData DEFAULT_HBM_DATA =
+            new HighBrightnessModeData(MINIMUM_LUX, TRANSITION_POINT, TIME_WINDOW_MILLIS,
+                    TIME_ALLOWED_IN_WINDOW_MILLIS, TIME_MINIMUM_AVAILABLE_TO_ENABLE_MILLIS,
+                    ALLOW_IN_LOW_POWER_MODE, HDR_PERCENT_OF_SCREEN_REQUIRED_DEFAULT);
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mClock = new OffsettableClock.Stopped();
+        mTestLooper = new TestLooper(mClock::now);
+        mDisplayToken = null;
+        mDisplayUniqueId = "unique_id";
+
+        mContextSpy = spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
+        final MockContentResolver resolver = mSettingsProviderRule.mockContentResolver(mContextSpy);
+        when(mContextSpy.getContentResolver()).thenReturn(resolver);
+    }
+
+    /////////////////
+    // Test Methods
+    /////////////////
+
+    @Test
+    public void testNoHbmData() {
+        initHandler(null);
+        final HighBrightnessModeController hbmc = new HighBrightnessModeController(
+                mInjectorMock, mHandler, DISPLAY_WIDTH, DISPLAY_HEIGHT, mDisplayToken,
+                mDisplayUniqueId, DEFAULT_MIN, DEFAULT_MAX, null, null, () -> {},
+                null, mContextSpy);
+        assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_OFF);
+        assertEquals(hbmc.getTransitionPoint(), HBM_TRANSITION_POINT_INVALID, 0.0f);
+    }
+
+    @Test
+    public void testNoHbmData_Enabled() {
+        initHandler(null);
+        final HighBrightnessModeController hbmc = new HighBrightnessModeController(
+                mInjectorMock, mHandler, DISPLAY_WIDTH, DISPLAY_HEIGHT, mDisplayToken,
+                mDisplayUniqueId, DEFAULT_MIN, DEFAULT_MAX, null, null, () -> {},
+                null, mContextSpy);
+        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
+        hbmc.onAmbientLuxChange(MINIMUM_LUX - 1); // below allowed range
+        assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_OFF);
+        assertEquals(hbmc.getTransitionPoint(), HBM_TRANSITION_POINT_INVALID, 0.0f);
+    }
+
+    @Test
+    public void testOffByDefault() {
+        final HighBrightnessModeController hbmc = createDefaultHbm();
+
+        assertState(hbmc, DEFAULT_MIN, TRANSITION_POINT, HIGH_BRIGHTNESS_MODE_OFF);
+    }
+
+    @Test
+    public void testAutoBrightnessEnabled_NoLux() {
+        final HighBrightnessModeController hbmc = createDefaultHbm();
+
+        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
+        assertState(hbmc, DEFAULT_MIN, TRANSITION_POINT, HIGH_BRIGHTNESS_MODE_OFF);
+    }
+
+    @Test
+    public void testAutoBrightnessEnabled_LowLux() {
+        final HighBrightnessModeController hbmc = createDefaultHbm();
+
+        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
+        hbmc.onAmbientLuxChange(MINIMUM_LUX - 1); // below allowed range
+        assertState(hbmc, DEFAULT_MIN, TRANSITION_POINT, HIGH_BRIGHTNESS_MODE_OFF);
+    }
+
+    @Test
+    public void testAutoBrightnessEnabled_HighLux() {
+        final HighBrightnessModeController hbmc = createDefaultHbm();
+
+        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
+        hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
+        assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT);
+    }
+
+    @Test
+    public void testAutoBrightnessEnabled_HighLux_ThenDisable() {
+        final HighBrightnessModeController hbmc = createDefaultHbm();
+
+        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
+        hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
+        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_DISABLED);
+
+        assertState(hbmc, DEFAULT_MIN, TRANSITION_POINT, HIGH_BRIGHTNESS_MODE_OFF);
+    }
+
+    @Test
+    public void testWithinHighRange_thenOverTime_thenEarnBackTime() {
+        final HighBrightnessModeController hbmc = createDefaultHbm();
+
+        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
+        hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
+        hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 0.01f);
+
+        // Verify we are in HBM
+        assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT);
+
+        // Use up all the time in the window.
+        advanceTime(TIME_ALLOWED_IN_WINDOW_MILLIS);
+
+        // Verify we are not out of HBM
+        assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT);
+
+        // Shift time so that the HBM event is at the beginning of the current window
+        advanceTime(TIME_WINDOW_MILLIS - TIME_ALLOWED_IN_WINDOW_MILLIS);
+        // Shift time again so that we are just below the minimum allowable
+        advanceTime(TIME_MINIMUM_AVAILABLE_TO_ENABLE_MILLIS - 1);
+
+        // Verify we are not out of HBM
+        assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT);
+
+        // Advance the necessary millisecond
+        advanceTime(1);
+
+        // Verify we are allowed HBM again.
+        assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT);
+    }
+
+    @Test
+    public void testInHBM_ThenLowLux() {
+        final HighBrightnessModeController hbmc = createDefaultHbm();
+
+        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
+        hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
+        hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 0.01f);
+
+        // Verify we are in HBM
+        assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT);
+
+        advanceTime(TIME_ALLOWED_IN_WINDOW_MILLIS / 2);
+
+        // Verify we are in HBM
+        assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT);
+
+        hbmc.onAmbientLuxChange(1);
+        advanceTime(TIME_ALLOWED_IN_WINDOW_MILLIS / 2 + 1);
+
+        // Verify we are out of HBM
+        assertState(hbmc, DEFAULT_MIN, TRANSITION_POINT, HIGH_BRIGHTNESS_MODE_OFF);
+
+    }
+
+    @Test
+    public void testInHBM_TestMultipleEvents_DueToAutoBrightness() {
+        final HighBrightnessModeController hbmc = createDefaultHbm();
+
+        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
+        hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
+
+        hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 0.01f);
+        advanceTime(TIME_ALLOWED_IN_WINDOW_MILLIS / 2);
+
+        // Verify we are in HBM
+        assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT);
+
+        hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT - 0.01f);
+        advanceTime(1);
+
+        assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT);
+
+        hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 0.01f);
+        advanceTime(TIME_ALLOWED_IN_WINDOW_MILLIS / 2);
+
+        assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT);
+
+        advanceTime(2);
+
+        // Now we should be out again.
+        assertState(hbmc, DEFAULT_MIN, TRANSITION_POINT, HIGH_BRIGHTNESS_MODE_OFF);
+    }
+
+    @Test
+    public void testInHBM_TestMultipleEvents_DueToLux() {
+        final HighBrightnessModeController hbmc = createDefaultHbm();
+
+        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
+        hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
+
+        // Go into HBM for half the allowed window
+        hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 0.01f);
+        advanceTime(TIME_ALLOWED_IN_WINDOW_MILLIS / 2);
+        assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT);
+
+        // Move lux below threshold (ending first event);
+        hbmc.onAmbientLuxChange(MINIMUM_LUX - 1);
+        hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT);
+        assertState(hbmc, DEFAULT_MIN, TRANSITION_POINT, HIGH_BRIGHTNESS_MODE_OFF);
+
+        // Move up some amount of time so that there's still time in the window even after a
+        // second event.
+        advanceTime((TIME_WINDOW_MILLIS - TIME_ALLOWED_IN_WINDOW_MILLIS) / 2);
+        assertState(hbmc, DEFAULT_MIN, TRANSITION_POINT, HIGH_BRIGHTNESS_MODE_OFF);
+
+        // Go into HBM for just under the second half of allowed window
+        hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
+        hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 1);
+        advanceTime((TIME_ALLOWED_IN_WINDOW_MILLIS / 2) - 1);
+
+        assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT);
+
+        // Now exhaust the time
+        advanceTime(2);
+        assertState(hbmc, DEFAULT_MIN, TRANSITION_POINT, HIGH_BRIGHTNESS_MODE_OFF);
+    }
+
+    @Test
+    public void testHbmIsNotTurnedOffInHighThermalState() throws Exception {
+        final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock());
+
+        // Disabled thermal throttling
+        hbmc.onBrightnessChanged(/*brightness=*/ 1f, /*unthrottledBrightness*/ 1f,
+                BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE);
+
+        assertFalse(hbmc.isThermalThrottlingActive());
+
+        // Try to go into HBM mode
+        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
+        hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
+        advanceTime(1);
+
+        assertEquals(HIGH_BRIGHTNESS_MODE_SUNLIGHT, hbmc.getHighBrightnessMode());
+
+        // Enable thermal throttling
+        hbmc.onBrightnessChanged(/*brightness=*/ TRANSITION_POINT - 0.01f,
+                /*unthrottledBrightness*/ 1f, BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL);
+        advanceTime(10);
+        assertEquals(HIGH_BRIGHTNESS_MODE_SUNLIGHT, hbmc.getHighBrightnessMode());
+        assertTrue(hbmc.isThermalThrottlingActive());
+
+        // Disabled thermal throttling
+        hbmc.onBrightnessChanged(/*brightness=*/ 1f, /*unthrottledBrightness*/ 1f,
+                BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE);
+        advanceTime(1);
+        assertEquals(HIGH_BRIGHTNESS_MODE_SUNLIGHT, hbmc.getHighBrightnessMode());
+        assertFalse(hbmc.isThermalThrottlingActive());
+    }
+
+    @Test
+    public void testHdrRequires50PercentOfScreen() {
+        final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock());
+
+        final int layerWidth = DISPLAY_WIDTH;
+        final int smallLayerHeight = DISPLAY_HEIGHT / 2 - 1; // height to use for <50%
+        final int largeLayerHeight = DISPLAY_HEIGHT / 2 + 1; // height to use for >50%
+
+        // ensure hdr doesn't turn on if layer is too small
+        hbmc.getHdrListener().onHdrInfoChanged(null /*displayToken*/, 1 /*numberOfHdrLayers*/,
+                layerWidth, smallLayerHeight, 0 /*flags*/, 1.0f /*maxDesiredHdrSdrRatio*/);
+        advanceTime(0);
+        assertEquals(HIGH_BRIGHTNESS_MODE_OFF, hbmc.getHighBrightnessMode());
+
+        // Now check with layer larger than 50%
+        hbmc.getHdrListener().onHdrInfoChanged(null /*displayToken*/, 1 /*numberOfHdrLayers*/,
+                layerWidth, largeLayerHeight, 0 /*flags*/, 1.0f /*maxDesiredHdrSdrRatio*/);
+        advanceTime(0);
+        assertEquals(HIGH_BRIGHTNESS_MODE_HDR, hbmc.getHighBrightnessMode());
+    }
+
+    @Test
+    public void testHdrRespectsMaxDesiredHdrSdrRatio() {
+        final HighBrightnessModeController hbmc = new TestHbmBuilder()
+                .setClock(new OffsettableClock())
+                .setHdrBrightnessConfig(mHdrBrightnessDeviceConfigMock)
+                .build();
+
+        // Passthrough return the max desired hdr/sdr ratio
+        when(mHdrBrightnessDeviceConfigMock.getHdrBrightnessFromSdr(anyFloat(), anyFloat()))
+                .thenAnswer(i -> i.getArgument(1));
+
+        hbmc.getHdrListener().onHdrInfoChanged(null /*displayToken*/, 1 /*numberOfHdrLayers*/,
+                DISPLAY_WIDTH, DISPLAY_HEIGHT, 0 /*flags*/, 2.0f /*maxDesiredHdrSdrRatio*/);
+        advanceTime(0);
+        assertEquals(2.0f, hbmc.getHdrBrightnessValue(), EPSILON);
+
+        // The hdr ratio cannot be less than 1.
+        hbmc.getHdrListener().onHdrInfoChanged(null /*displayToken*/, 1 /*numberOfHdrLayers*/,
+                DISPLAY_WIDTH, DISPLAY_HEIGHT, 0 /*flags*/, 0.5f /*maxDesiredHdrSdrRatio*/);
+        advanceTime(0);
+        assertEquals(1.0f, hbmc.getHdrBrightnessValue(), EPSILON);
+
+        // The hdr ratio can be as much as positive infinity
+        hbmc.getHdrListener().onHdrInfoChanged(null /*displayToken*/, 1 /*numberOfHdrLayers*/,
+                DISPLAY_WIDTH, DISPLAY_HEIGHT, 0 /*flags*/,
+                Float.POSITIVE_INFINITY /*maxDesiredHdrSdrRatio*/);
+        advanceTime(0);
+        assertEquals(Float.POSITIVE_INFINITY, hbmc.getHdrBrightnessValue(), 0.0);
+    }
+
+
+    @Test
+    public void testHdrTrumpsSunlight() {
+        final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock());
+
+        // Turn on sunlight
+        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
+        hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
+        advanceTime(0);
+        assertEquals(HIGH_BRIGHTNESS_MODE_SUNLIGHT, hbmc.getHighBrightnessMode());
+        assertEquals(DEFAULT_MAX, hbmc.getCurrentBrightnessMax(), EPSILON);
+
+        // turn on hdr
+        hbmc.getHdrListener().onHdrInfoChanged(null /*displayToken*/, 1 /*numberOfHdrLayers*/,
+                DISPLAY_WIDTH, DISPLAY_HEIGHT, 0 /*flags*/, 1.0f /*maxDesiredHdrSdrRatio*/);
+        advanceTime(0);
+        assertEquals(HIGH_BRIGHTNESS_MODE_HDR, hbmc.getHighBrightnessMode());
+        assertEquals(TRANSITION_POINT, hbmc.getCurrentBrightnessMax(), EPSILON);
+    }
+
+    @Test
+    public void testHdrBrightnessLimitSameAsNormalLimit() {
+        final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock());
+
+        // Check limit when HBM is off
+        hbmc.getHdrListener().onHdrInfoChanged(null /*displayToken*/, 1 /*numberOfHdrLayers*/,
+                DISPLAY_WIDTH, DISPLAY_HEIGHT, 0 /*flags*/, 1.0f /*maxDesiredHdrSdrRatio*/);
+        advanceTime(0);
+        assertEquals(HIGH_BRIGHTNESS_MODE_HDR, hbmc.getHighBrightnessMode());
+        assertEquals(TRANSITION_POINT, hbmc.getCurrentBrightnessMax(), EPSILON);
+
+        // Check limit with HBM is set to HDR
+        hbmc.getHdrListener().onHdrInfoChanged(null /*displayToken*/, 0 /*numberOfHdrLayers*/,
+                DISPLAY_WIDTH, DISPLAY_HEIGHT, 0 /*flags*/, 1.0f /*maxDesiredHdrSdrRatio*/);
+        advanceTime(0);
+        assertEquals(HIGH_BRIGHTNESS_MODE_OFF, hbmc.getHighBrightnessMode());
+        assertEquals(TRANSITION_POINT, hbmc.getCurrentBrightnessMax(), EPSILON);
+    }
+
+    @Test
+    public void testHdrBrightnessScaledNormalBrightness() {
+        final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock());
+
+        hbmc.getHdrListener().onHdrInfoChanged(null /*displayToken*/, 1 /*numberOfHdrLayers*/,
+                DISPLAY_WIDTH, DISPLAY_HEIGHT, 0 /*flags*/, 1.0f /*maxDesiredHdrSdrRatio*/);
+        advanceTime(0);
+        assertEquals(HIGH_BRIGHTNESS_MODE_HDR, hbmc.getHighBrightnessMode());
+
+        // verify things are scaled for 0.5f
+        float brightness = 0.5f;
+        float expectedHdrBrightness = MathUtils.map(DEFAULT_MIN, TRANSITION_POINT,
+                DEFAULT_MIN, DEFAULT_MAX, brightness); // map value from normal range to hdr range
+        hbmcOnBrightnessChanged(hbmc, brightness);
+        advanceTime(0);
+        assertEquals(expectedHdrBrightness, hbmc.getHdrBrightnessValue(), EPSILON);
+
+        // Try another value
+        brightness = 0.33f;
+        expectedHdrBrightness = MathUtils.map(DEFAULT_MIN, TRANSITION_POINT,
+                DEFAULT_MIN, DEFAULT_MAX, brightness); // map value from normal range to hdr range
+        hbmcOnBrightnessChanged(hbmc, brightness);
+        advanceTime(0);
+        assertEquals(expectedHdrBrightness, hbmc.getHdrBrightnessValue(), EPSILON);
+
+        // Try the min value
+        brightness = DEFAULT_MIN;
+        expectedHdrBrightness = DEFAULT_MIN;
+        hbmcOnBrightnessChanged(hbmc, brightness);
+        advanceTime(0);
+        assertEquals(expectedHdrBrightness, hbmc.getHdrBrightnessValue(), EPSILON);
+
+        // Try the max value
+        brightness = TRANSITION_POINT;
+        expectedHdrBrightness = DEFAULT_MAX;
+        hbmcOnBrightnessChanged(hbmc, brightness);
+        advanceTime(0);
+        assertEquals(expectedHdrBrightness, hbmc.getHdrBrightnessValue(), EPSILON);
+    }
+
+    @Test
+    public void testHbmStats_StateChange() {
+        final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock());
+        final int displayStatsId = mDisplayUniqueId.hashCode();
+
+        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
+        hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT);
+        hbmc.getHdrListener().onHdrInfoChanged(null /*displayToken*/, 1 /*numberOfHdrLayers*/,
+                DISPLAY_WIDTH, DISPLAY_HEIGHT, 0 /*flags*/, 1.0f /*maxDesiredHdrSdrRatio*/);
+        advanceTime(0);
+        assertEquals(HIGH_BRIGHTNESS_MODE_HDR, hbmc.getHighBrightnessMode());
+
+        // Verify Stats HBM_ON_HDR
+        verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
+                eq(FrameworkStatsLog
+                        .DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_HDR),
+                eq(FrameworkStatsLog
+                        .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN));
+
+        hbmc.getHdrListener().onHdrInfoChanged(null /*displayToken*/, 0 /*numberOfHdrLayers*/,
+                0, 0, 0 /*flags*/, 1.0f /*maxDesiredHdrSdrRatio*/);
+        advanceTime(0);
+
+        // Verify Stats HBM_OFF
+        verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
+                eq(FrameworkStatsLog
+                        .DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF),
+                eq(FrameworkStatsLog
+                        .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN));
+
+        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
+        hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
+        hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 0.01f);
+
+        // Verify Stats HBM_ON_SUNLIGHT
+        verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
+                eq(FrameworkStatsLog
+                        .DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT),
+                eq(FrameworkStatsLog
+                        .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN));
+
+        hbmc.onAmbientLuxChange(1);
+        advanceTime(TIME_ALLOWED_IN_WINDOW_MILLIS / 2 + 1);
+
+        // Verify Stats HBM_OFF
+        verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
+                eq(FrameworkStatsLog
+                        .DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF),
+                eq(FrameworkStatsLog
+                        .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_LUX_DROP));
+    }
+
+    @Test
+    public void testHbmStats_NbmHdrNoReport() {
+        final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock());
+        final int displayStatsId = mDisplayUniqueId.hashCode();
+
+        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
+        hbmcOnBrightnessChanged(hbmc, DEFAULT_MIN);
+        hbmc.getHdrListener().onHdrInfoChanged(null /*displayToken*/, 1 /*numberOfHdrLayers*/,
+                DISPLAY_WIDTH, DISPLAY_HEIGHT, 0 /*flags*/, 1.0f /*maxDesiredHdrSdrRatio*/);
+        advanceTime(0);
+        assertEquals(HIGH_BRIGHTNESS_MODE_HDR, hbmc.getHighBrightnessMode());
+
+        // Verify Stats HBM_ON_HDR not report
+        verify(mInjectorMock, never()).reportHbmStateChange(eq(displayStatsId),
+                eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_HDR),
+                anyInt());
+    }
+
+    @Test
+    public void testHbmStats_HighLuxLowBrightnessNoReport() {
+        final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock());
+        final int displayStatsId = mDisplayUniqueId.hashCode();
+
+        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
+        hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
+        hbmcOnBrightnessChanged(hbmc, DEFAULT_MIN);
+        advanceTime(0);
+        // verify in HBM sunlight mode
+        assertEquals(HIGH_BRIGHTNESS_MODE_SUNLIGHT, hbmc.getHighBrightnessMode());
+
+        // Verify Stats HBM_ON_SUNLIGHT not report
+        verify(mInjectorMock, never()).reportHbmStateChange(eq(displayStatsId),
+                eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT),
+                anyInt());
+    }
+
+    // Test reporting of thermal throttling when triggered externally through
+    // HighBrightnessModeController.onBrightnessChanged()
+    @Test
+    public void testHbmStats_ExternalThermalOff() throws Exception {
+        final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock());
+        final int displayStatsId = mDisplayUniqueId.hashCode();
+        final float hbmBrightness = TRANSITION_POINT + 0.01f;
+        final float nbmBrightness = TRANSITION_POINT - 0.01f;
+
+        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
+        hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
+        // Brightness is unthrottled, HBM brightness granted
+        hbmc.onBrightnessChanged(hbmBrightness, hbmBrightness,
+                BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE);
+        advanceTime(1);
+        verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
+                eq(FrameworkStatsLog
+                        .DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT),
+                eq(FrameworkStatsLog
+                        .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN));
+
+        // Brightness is thermally throttled, HBM brightness denied (NBM brightness granted)
+        hbmc.onBrightnessChanged(nbmBrightness, hbmBrightness,
+                BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL);
+        advanceTime(1);
+        // We expect HBM mode to remain set to sunlight, indicating that HBMC *allows* this mode.
+        // However, we expect the HBM state reported by HBMC to be off, since external thermal
+        // throttling (reported to HBMC through onBrightnessChanged()) lowers brightness to below
+        // the HBM transition point.
+        assertEquals(HIGH_BRIGHTNESS_MODE_SUNLIGHT, hbmc.getHighBrightnessMode());
+        verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
+                eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF),
+                eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_THERMAL_LIMIT));
+    }
+
+    @Test
+    public void testHbmStats_TimeOut() {
+        final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock());
+        final int displayStatsId = mDisplayUniqueId.hashCode();
+
+        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
+        hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
+        hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 0.01f);
+        advanceTime(0);
+        verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
+                eq(FrameworkStatsLog
+                        .DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT),
+                eq(FrameworkStatsLog
+                        .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN));
+
+        // Use up all the time in the window.
+        advanceTime(TIME_WINDOW_MILLIS + 1);
+
+        verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
+                eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF),
+                eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_TIME_LIMIT));
+    }
+
+    @Test
+    public void testHbmStats_DisplayOff() {
+        final HighBrightnessModeController hbmc = createDefaultHbm();
+        final int displayStatsId = mDisplayUniqueId.hashCode();
+
+        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
+        hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
+        hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 0.01f);
+        advanceTime(0);
+        verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
+                eq(FrameworkStatsLog
+                        .DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT),
+                eq(FrameworkStatsLog
+                        .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN));
+
+        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE);
+        verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
+                eq(FrameworkStatsLog
+                        .DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF),
+                eq(FrameworkStatsLog
+                        .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_DISPLAY_OFF));
+    }
+
+    @Test
+    public void testHbmStats_HdrPlaying() {
+        final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock());
+        final int displayStatsId = mDisplayUniqueId.hashCode();
+
+        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
+        hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
+        hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 0.01f);
+        advanceTime(0);
+        verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
+                eq(FrameworkStatsLog
+                        .DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT),
+                eq(FrameworkStatsLog
+                        .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN));
+
+        hbmc.getHdrListener().onHdrInfoChanged(null /*displayToken*/, 1 /*numberOfHdrLayers*/,
+                DISPLAY_WIDTH, DISPLAY_HEIGHT, 0 /*flags*/, 1.0f /*maxDesiredHdrSdrRatio*/);
+        advanceTime(0);
+
+        verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
+                eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_HDR),
+                eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_HDR_PLAYING));
+    }
+
+    @Test
+    public void tetHbmStats_LowRequestedBrightness() {
+        final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock());
+        final int displayStatsId = mDisplayUniqueId.hashCode();
+
+        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
+        hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
+        hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 0.01f);
+        advanceTime(0);
+        // verify in HBM sunlight mode
+        assertEquals(HIGH_BRIGHTNESS_MODE_SUNLIGHT, hbmc.getHighBrightnessMode());
+        // verify HBM_ON_SUNLIGHT
+        verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
+                eq(FrameworkStatsLog
+                        .DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT),
+                eq(FrameworkStatsLog
+                        .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN));
+
+        hbmcOnBrightnessChanged(hbmc, DEFAULT_MIN);
+        // verify HBM_SV_OFF due to LOW_REQUESTED_BRIGHTNESS
+        verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
+                eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF),
+                eq(FrameworkStatsLog
+                        .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_LOW_REQUESTED_BRIGHTNESS));
+    }
+
+    private void assertState(HighBrightnessModeController hbmc,
+            float brightnessMin, float brightnessMax, int hbmMode) {
+        assertEquals(brightnessMin, hbmc.getCurrentBrightnessMin(), EPSILON);
+        assertEquals(brightnessMax, hbmc.getCurrentBrightnessMax(), EPSILON);
+        assertEquals(hbmMode, hbmc.getHighBrightnessMode());
+    }
+
+    private class TestHbmBuilder {
+        OffsettableClock mClock;
+        HighBrightnessModeController.HdrBrightnessDeviceConfig mHdrBrightnessCfg;
+
+        TestHbmBuilder setClock(OffsettableClock clock) {
+            mClock = clock;
+            return this;
+        }
+
+        TestHbmBuilder setHdrBrightnessConfig(
+                HighBrightnessModeController.HdrBrightnessDeviceConfig hdrBrightnessCfg
+        ) {
+            mHdrBrightnessCfg = hdrBrightnessCfg;
+            return this;
+        }
+
+        HighBrightnessModeController build() {
+            initHandler(mClock);
+            if (mHighBrightnessModeMetadata == null) {
+                mHighBrightnessModeMetadata = new HighBrightnessModeMetadata();
+            }
+            return new HighBrightnessModeController(mInjectorMock, mHandler, DISPLAY_WIDTH,
+                    DISPLAY_HEIGHT, mDisplayToken, mDisplayUniqueId, DEFAULT_MIN, DEFAULT_MAX,
+                    DEFAULT_HBM_DATA, mHdrBrightnessCfg, () -> {}, mHighBrightnessModeMetadata,
+                    mContextSpy);
+        }
+
+    }
+
+    private HighBrightnessModeController createDefaultHbm() {
+        return new TestHbmBuilder().build();
+    }
+
+    // Creates instance with standard initialization values.
+    private HighBrightnessModeController createDefaultHbm(OffsettableClock clock) {
+        return new TestHbmBuilder().setClock(clock).build();
+    }
+
+    private void initHandler(OffsettableClock clock) {
+        mClock = clock != null ? clock : new OffsettableClock.Stopped();
+        when(mInjectorMock.getClock()).thenReturn(mClock::now);
+        mTestLooper = new TestLooper(mClock::now);
+        mHandler = new Handler(mTestLooper.getLooper(), new Handler.Callback() {
+            @Override
+            public boolean handleMessage(Message msg) {
+                return true;
+            }
+        });
+    }
+
+    private void advanceTime(long timeMs) {
+        mClock.fastForward(timeMs);
+        mTestLooper.dispatchAll();
+    }
+
+    private void hbmcOnBrightnessChanged(HighBrightnessModeController hbmc, float brightness) {
+        hbmc.onBrightnessChanged(brightness, brightness, BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE);
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/HighBrightnessModeMetadataMapperTest.java b/services/tests/displayservicetests/src/com/android/server/display/HighBrightnessModeMetadataMapperTest.java
similarity index 100%
rename from services/tests/mockingservicestests/src/com/android/server/display/HighBrightnessModeMetadataMapperTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/HighBrightnessModeMetadataMapperTest.java
diff --git a/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeMetadataTest.java b/services/tests/displayservicetests/src/com/android/server/display/HighBrightnessModeMetadataTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/display/HighBrightnessModeMetadataTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/HighBrightnessModeMetadataTest.java
diff --git a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
new file mode 100644
index 0000000..aa0a2fe
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
@@ -0,0 +1,1317 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Rect;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.view.Display;
+import android.view.DisplayAddress;
+import android.view.SurfaceControl;
+import android.view.SurfaceControl.RefreshRateRange;
+import android.view.SurfaceControl.RefreshRateRanges;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.dx.mockito.inline.extended.StaticMockitoSession;
+import com.android.internal.R;
+import com.android.server.LocalServices;
+import com.android.server.display.LocalDisplayAdapter.BacklightAdapter;
+import com.android.server.display.mode.DisplayModeDirector;
+import com.android.server.lights.LightsManager;
+import com.android.server.lights.LogicalLight;
+
+import com.google.common.truth.Truth;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.quality.Strictness;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class LocalDisplayAdapterTest {
+    private static final Long DISPLAY_MODEL = Long.valueOf(0xAAAAAAAAL);
+    private static final int PORT_A = 0;
+    private static final int PORT_B = 0x80;
+    private static final int PORT_C = 0xFF;
+    private static final float REFRESH_RATE = 60f;
+    private static final RefreshRateRange REFRESH_RATE_RANGE =
+            new RefreshRateRange(REFRESH_RATE, REFRESH_RATE);
+    private static final RefreshRateRanges REFRESH_RATE_RANGES =
+            new RefreshRateRanges(REFRESH_RATE_RANGE, REFRESH_RATE_RANGE);
+
+    private static final long HANDLER_WAIT_MS = 100;
+
+    private static final int[] HDR_TYPES = new int[]{1, 2};
+
+    private StaticMockitoSession mMockitoSession;
+
+    private LocalDisplayAdapter mAdapter;
+
+    @Mock
+    private DisplayManagerService.SyncRoot mMockedSyncRoot;
+    @Mock
+    private Context mMockedContext;
+    @Mock
+    private Resources mMockedResources;
+    @Mock
+    private LightsManager mMockedLightsManager;
+    @Mock
+    private LogicalLight mMockedBacklight;
+
+    private Handler mHandler;
+
+    private TestListener mListener = new TestListener();
+
+    private LinkedList<DisplayAddress.Physical> mAddresses = new LinkedList<>();
+
+    private Injector mInjector;
+
+    @Mock
+    private LocalDisplayAdapter.SurfaceControlProxy mSurfaceControlProxy;
+    private static final float[] DISPLAY_RANGE_NITS = { 2.685f, 478.5f };
+    private static final int[] BACKLIGHT_RANGE = { 1, 255 };
+    private static final float[] BACKLIGHT_RANGE_ZERO_TO_ONE = { 0.0f, 1.0f };
+
+    @Before
+    public void setUp() throws Exception {
+        mMockitoSession = mockitoSession()
+                .initMocks(this)
+                .strictness(Strictness.LENIENT)
+                .startMocking();
+        mHandler = new Handler(Looper.getMainLooper());
+        doReturn(mMockedResources).when(mMockedContext).getResources();
+        LocalServices.removeServiceForTest(LightsManager.class);
+        LocalServices.addService(LightsManager.class, mMockedLightsManager);
+        mInjector = new Injector();
+        when(mSurfaceControlProxy.getBootDisplayModeSupport()).thenReturn(true);
+        mAdapter = new LocalDisplayAdapter(mMockedSyncRoot, mMockedContext, mHandler,
+                mListener, mInjector);
+        spyOn(mAdapter);
+        doReturn(mMockedContext).when(mAdapter).getOverlayContext();
+
+        TypedArray mockNitsRange = createFloatTypedArray(DISPLAY_RANGE_NITS);
+        when(mMockedResources.obtainTypedArray(R.array.config_screenBrightnessNits))
+                .thenReturn(mockNitsRange);
+        when(mMockedResources.getIntArray(R.array.config_screenBrightnessBacklight))
+                .thenReturn(BACKLIGHT_RANGE);
+        when(mMockedResources.getFloat(com.android.internal.R.dimen
+                .config_screenBrightnessSettingMinimumFloat))
+                .thenReturn(BACKLIGHT_RANGE_ZERO_TO_ONE[0]);
+        when(mMockedResources.getFloat(com.android.internal.R.dimen
+                .config_screenBrightnessSettingMaximumFloat))
+                .thenReturn(BACKLIGHT_RANGE_ZERO_TO_ONE[1]);
+        when(mMockedResources.getStringArray(R.array.config_displayUniqueIdArray))
+                .thenReturn(new String[]{});
+        TypedArray mockArray = mock(TypedArray.class);
+        when(mockArray.length()).thenReturn(0);
+        when(mMockedResources.obtainTypedArray(R.array.config_maskBuiltInDisplayCutoutArray))
+                .thenReturn(mockArray);
+        when(mMockedResources.obtainTypedArray(R.array.config_waterfallCutoutArray))
+                .thenReturn(mockArray);
+        when(mMockedResources.obtainTypedArray(R.array.config_roundedCornerRadiusArray))
+                .thenReturn(mockArray);
+        when(mMockedResources.obtainTypedArray(R.array.config_roundedCornerTopRadiusArray))
+                .thenReturn(mockArray);
+        when(mMockedResources.obtainTypedArray(R.array.config_roundedCornerBottomRadiusArray))
+                .thenReturn(mockArray);
+        when(mMockedResources.obtainTypedArray(
+                com.android.internal.R.array.config_autoBrightnessDisplayValuesNits))
+                .thenReturn(mockArray);
+        when(mMockedResources.getIntArray(
+                com.android.internal.R.array.config_autoBrightnessLevels))
+                .thenReturn(new int[]{});
+        when(mMockedResources.obtainTypedArray(R.array.config_displayShapeArray))
+                .thenReturn(mockArray);
+        when(mMockedResources.obtainTypedArray(R.array.config_builtInDisplayIsRoundArray))
+                .thenReturn(mockArray);
+        when(mMockedResources.getIntArray(
+            com.android.internal.R.array.config_brightnessThresholdsOfPeakRefreshRate))
+            .thenReturn(new int[]{});
+        when(mMockedResources.getIntArray(
+            com.android.internal.R.array.config_ambientThresholdsOfPeakRefreshRate))
+            .thenReturn(new int[]{});
+        when(mMockedResources.getIntArray(
+            com.android.internal.R.array.config_highDisplayBrightnessThresholdsOfFixedRefreshRate))
+            .thenReturn(new int[]{});
+        when(mMockedResources.getIntArray(
+            com.android.internal.R.array.config_highAmbientBrightnessThresholdsOfFixedRefreshRate))
+            .thenReturn(new int[]{});
+    }
+
+    @After
+    public void tearDown() {
+        if (mMockitoSession != null) {
+            mMockitoSession.finishMocking();
+        }
+    }
+
+    /**
+     * Confirm that display is marked as private when it is listed in
+     * com.android.internal.R.array.config_localPrivateDisplayPorts.
+     */
+    @Test
+    public void testPrivateDisplay() throws Exception {
+        setUpDisplay(new FakeDisplay(PORT_A));
+        setUpDisplay(new FakeDisplay(PORT_B));
+        setUpDisplay(new FakeDisplay(PORT_C));
+        updateAvailableDisplays();
+
+        doReturn(new int[]{ PORT_B }).when(mMockedResources)
+                .getIntArray(com.android.internal.R.array.config_localPrivateDisplayPorts);
+        mAdapter.registerLocked();
+
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+        // This should be public
+        assertDisplayPrivateFlag(mListener.addedDisplays.get(0).getDisplayDeviceInfoLocked(),
+                PORT_A, false);
+        // This should be private
+        assertDisplayPrivateFlag(mListener.addedDisplays.get(1).getDisplayDeviceInfoLocked(),
+                PORT_B, true);
+        // This should be public
+        assertDisplayPrivateFlag(mListener.addedDisplays.get(2).getDisplayDeviceInfoLocked(),
+                PORT_C, false);
+    }
+
+    @Test
+    public void testSupportedDisplayModesGetOverriddenWhenDisplayIsUpdated()
+            throws InterruptedException {
+        SurfaceControl.DisplayMode displayMode = createFakeDisplayMode(0, 1920, 1080, 0);
+        displayMode.supportedHdrTypes = new int[0];
+        FakeDisplay display = new FakeDisplay(PORT_A, new SurfaceControl.DisplayMode[]{displayMode},
+                0, 0);
+        setUpDisplay(display);
+        updateAvailableDisplays();
+        mAdapter.registerLocked();
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+        DisplayDevice displayDevice = mListener.addedDisplays.get(0);
+        displayDevice.applyPendingDisplayDeviceInfoChangesLocked();
+        Display.Mode[] supportedModes = displayDevice.getDisplayDeviceInfoLocked().supportedModes;
+        Assert.assertEquals(1, supportedModes.length);
+        Assert.assertEquals(0, supportedModes[0].getSupportedHdrTypes().length);
+
+        displayMode.supportedHdrTypes = new int[]{3, 2};
+        display.dynamicInfo.supportedDisplayModes = new SurfaceControl.DisplayMode[]{displayMode};
+        setUpDisplay(display);
+        mInjector.getTransmitter().sendHotplug(display, true);
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+        displayDevice = mListener.changedDisplays.get(0);
+        displayDevice.applyPendingDisplayDeviceInfoChangesLocked();
+        supportedModes = displayDevice.getDisplayDeviceInfoLocked().supportedModes;
+
+        Assert.assertEquals(1, supportedModes.length);
+        assertArrayEquals(new int[]{2, 3}, supportedModes[0].getSupportedHdrTypes());
+    }
+
+    /**
+     * Confirm that all local displays are public when config_localPrivateDisplayPorts is empty.
+     */
+    @Test
+    public void testPublicDisplaysForNoConfigLocalPrivateDisplayPorts() throws Exception {
+        setUpDisplay(new FakeDisplay(PORT_A));
+        setUpDisplay(new FakeDisplay(PORT_C));
+        updateAvailableDisplays();
+
+        // config_localPrivateDisplayPorts is null
+        mAdapter.registerLocked();
+
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+        // This should be public
+        assertDisplayPrivateFlag(mListener.addedDisplays.get(0).getDisplayDeviceInfoLocked(),
+                PORT_A, false);
+        // This should be public
+        assertDisplayPrivateFlag(mListener.addedDisplays.get(1).getDisplayDeviceInfoLocked(),
+                PORT_C, false);
+    }
+
+    private static void assertDisplayPrivateFlag(
+            DisplayDeviceInfo info, int expectedPort, boolean shouldBePrivate) {
+        final DisplayAddress.Physical address = (DisplayAddress.Physical) info.address;
+        assertNotNull(address);
+        assertEquals(expectedPort, address.getPort());
+        assertEquals(DISPLAY_MODEL, address.getModel());
+        assertEquals(shouldBePrivate, (info.flags & DisplayDeviceInfo.FLAG_PRIVATE) != 0);
+    }
+
+    /**
+     * Confirm that external display uses physical density.
+     */
+    @Test
+    public void testDpiValues() throws Exception {
+        // needs default one always
+        setUpDisplay(new FakeDisplay(PORT_A));
+        setUpDisplay(new FakeDisplay(PORT_B));
+        updateAvailableDisplays();
+        mAdapter.registerLocked();
+
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+        assertDisplayDpi(
+                mListener.addedDisplays.get(0).getDisplayDeviceInfoLocked(), PORT_A, 100, 100,
+                16000);
+        assertDisplayDpi(
+                mListener.addedDisplays.get(1).getDisplayDeviceInfoLocked(), PORT_B, 100, 100,
+                16000);
+    }
+
+    private static class DisplayModeWrapper {
+        public SurfaceControl.DisplayMode mode;
+        public float[] expectedAlternativeRefreshRates;
+
+        DisplayModeWrapper(SurfaceControl.DisplayMode mode,
+                float[] expectedAlternativeRefreshRates) {
+            this.mode = mode;
+            this.expectedAlternativeRefreshRates = expectedAlternativeRefreshRates;
+        }
+    }
+
+    /**
+     * Updates the <code>display</code> using the given <code>modes</code> and then checks if the
+     * <code>expectedAlternativeRefreshRates</code> are present for each of the
+     * <code>modes</code>.
+     */
+    private void testAlternativeRefreshRatesCommon(FakeDisplay display,
+                DisplayModeWrapper[] wrappedModes)
+            throws InterruptedException {
+        // Update the display.
+        SurfaceControl.DisplayMode[] modes = new SurfaceControl.DisplayMode[wrappedModes.length];
+        for (int i = 0; i < wrappedModes.length; i++) {
+            modes[i] = wrappedModes[i].mode;
+        }
+        display.dynamicInfo.supportedDisplayModes = modes;
+        setUpDisplay(display);
+        mInjector.getTransmitter().sendHotplug(display, /* connected */ true);
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+        assertTrue(mListener.traversalRequested);
+        assertThat(mListener.changedDisplays.size()).isGreaterThan(0);
+
+        // Verify the supported modes are updated accordingly.
+        DisplayDevice displayDevice =
+                mListener.changedDisplays.get(mListener.changedDisplays.size() - 1);
+        displayDevice.applyPendingDisplayDeviceInfoChangesLocked();
+        Display.Mode[] supportedModes = displayDevice.getDisplayDeviceInfoLocked().supportedModes;
+        assertThat(supportedModes.length).isEqualTo(modes.length);
+
+        for (int i = 0; i < wrappedModes.length; i++) {
+            assertModeIsSupported(supportedModes, modes[i],
+                    wrappedModes[i].expectedAlternativeRefreshRates);
+        }
+    }
+
+    @Test
+    public void testAfterDisplayChange_AlternativeRefreshRatesAreUpdated() throws Exception {
+        FakeDisplay display = new FakeDisplay(PORT_A);
+        setUpDisplay(display);
+        updateAvailableDisplays();
+        mAdapter.registerLocked();
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+        testAlternativeRefreshRatesCommon(display, new DisplayModeWrapper[] {
+                new DisplayModeWrapper(
+                        createFakeDisplayMode(0, 1920, 1080, 60f, 0), new float[]{24f, 50f}),
+                new DisplayModeWrapper(
+                        createFakeDisplayMode(1, 1920, 1080, 50f, 0), new float[]{24f, 60f}),
+                new DisplayModeWrapper(
+                        createFakeDisplayMode(2, 1920, 1080, 24f, 0), new float[]{50f, 60f}),
+                new DisplayModeWrapper(
+                        createFakeDisplayMode(3, 3840, 2160, 60f, 0), new float[]{24f, 50f}),
+                new DisplayModeWrapper(
+                        createFakeDisplayMode(4, 3840, 2160, 50f, 0), new float[]{24f, 60f}),
+                new DisplayModeWrapper(
+                        createFakeDisplayMode(5, 3840, 2160, 24f, 0), new float[]{50f, 60f}),
+        });
+
+        testAlternativeRefreshRatesCommon(display, new DisplayModeWrapper[] {
+                new DisplayModeWrapper(
+                        createFakeDisplayMode(0, 1920, 1080, 60f, 0), new float[]{50f}),
+                new DisplayModeWrapper(
+                        createFakeDisplayMode(1, 1920, 1080, 50f, 0), new float[]{60f}),
+                new DisplayModeWrapper(
+                        createFakeDisplayMode(2, 1920, 1080, 24f, 1), new float[0]),
+                new DisplayModeWrapper(
+                        createFakeDisplayMode(3, 3840, 2160, 60f, 2), new float[0]),
+                new DisplayModeWrapper(
+                        createFakeDisplayMode(4, 3840, 2160, 50f, 3), new float[]{24f}),
+                new DisplayModeWrapper(
+                        createFakeDisplayMode(5, 3840, 2160, 24f, 3), new float[]{50f}),
+        });
+
+        testAlternativeRefreshRatesCommon(display, new DisplayModeWrapper[] {
+                new DisplayModeWrapper(
+                        createFakeDisplayMode(0, 1920, 1080, 60f, 0), new float[0]),
+                new DisplayModeWrapper(
+                        createFakeDisplayMode(1, 1920, 1080, 50f, 1), new float[0]),
+                new DisplayModeWrapper(
+                        createFakeDisplayMode(2, 1920, 1080, 24f, 2), new float[0]),
+                new DisplayModeWrapper(
+                        createFakeDisplayMode(3, 3840, 2160, 60f, 3), new float[0]),
+                new DisplayModeWrapper(
+                        createFakeDisplayMode(4, 3840, 2160, 50f, 4), new float[0]),
+                new DisplayModeWrapper(
+                        createFakeDisplayMode(5, 3840, 2160, 24f, 5), new float[0]),
+        });
+    }
+
+    @Test
+    public void testAfterDisplayChange_DefaultDisplayModeIsUpdated() throws Exception {
+        SurfaceControl.DisplayMode displayMode = createFakeDisplayMode(0, 1920, 1080, 60f);
+        SurfaceControl.DisplayMode[] modes =
+                new SurfaceControl.DisplayMode[]{displayMode};
+        FakeDisplay display = new FakeDisplay(PORT_A, modes, 0, displayMode.refreshRate);
+        setUpDisplay(display);
+        updateAvailableDisplays();
+        mAdapter.registerLocked();
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+        assertThat(mListener.changedDisplays).isEmpty();
+
+        DisplayDeviceInfo displayDeviceInfo = mListener.addedDisplays.get(
+                0).getDisplayDeviceInfoLocked();
+
+        assertThat(displayDeviceInfo.supportedModes.length).isEqualTo(modes.length);
+        assertModeIsSupported(displayDeviceInfo.supportedModes, displayMode);
+
+        Display.Mode defaultMode = getModeById(displayDeviceInfo, displayDeviceInfo.defaultModeId);
+        assertThat(matches(defaultMode, displayMode)).isTrue();
+
+        // Set the display mode to an unsupported mode
+        SurfaceControl.DisplayMode displayMode2 = createFakeDisplayMode(1, 1920, 1080, 120f);
+        mListener.addedDisplays.get(0).setUserPreferredDisplayModeLocked(
+                new Display.Mode(displayMode2.width, displayMode2.height,
+                        displayMode2.refreshRate));
+        updateAvailableDisplays();
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+        defaultMode = getModeById(displayDeviceInfo, displayDeviceInfo.defaultModeId);
+        assertThat(matches(defaultMode, displayMode2)).isFalse();
+
+        // Change the display
+        modes = new SurfaceControl.DisplayMode[]{displayMode, displayMode2};
+        display.dynamicInfo.supportedDisplayModes = modes;
+        setUpDisplay(display);
+        mInjector.getTransmitter().sendHotplug(display, /* connected */ true);
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+        assertTrue(mListener.traversalRequested);
+        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+        assertThat(mListener.changedDisplays.size()).isEqualTo(1);
+
+        DisplayDevice displayDevice = mListener.changedDisplays.get(0);
+        displayDevice.applyPendingDisplayDeviceInfoChangesLocked();
+        displayDeviceInfo = mListener.addedDisplays.get(0).getDisplayDeviceInfoLocked();
+
+        assertThat(displayDeviceInfo.supportedModes.length).isEqualTo(modes.length);
+        assertModeIsSupported(displayDeviceInfo.supportedModes, displayMode);
+        assertModeIsSupported(displayDeviceInfo.supportedModes, displayMode2);
+
+        defaultMode = getModeById(displayDeviceInfo, displayDeviceInfo.defaultModeId);
+        assertThat(matches(defaultMode, displayMode2)).isTrue();
+    }
+
+    @Test
+    public void testAfterDisplayChange_DisplayModesAreUpdated() throws Exception {
+        SurfaceControl.DisplayMode displayMode = createFakeDisplayMode(0, 1920, 1080, 60f);
+        SurfaceControl.DisplayMode[] modes =
+                new SurfaceControl.DisplayMode[]{displayMode};
+        FakeDisplay display = new FakeDisplay(PORT_A, modes, 0, displayMode.refreshRate);
+        setUpDisplay(display);
+        updateAvailableDisplays();
+        mAdapter.registerLocked();
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+        assertThat(mListener.changedDisplays).isEmpty();
+
+        DisplayDeviceInfo displayDeviceInfo = mListener.addedDisplays.get(
+                0).getDisplayDeviceInfoLocked();
+
+        assertThat(displayDeviceInfo.supportedModes.length).isEqualTo(modes.length);
+        assertModeIsSupported(displayDeviceInfo.supportedModes, displayMode);
+
+        Display.Mode defaultMode = getModeById(displayDeviceInfo, displayDeviceInfo.defaultModeId);
+        assertThat(matches(defaultMode, displayMode)).isTrue();
+
+        Display.Mode activeMode = getModeById(displayDeviceInfo, displayDeviceInfo.modeId);
+        assertThat(matches(activeMode, displayMode)).isTrue();
+
+        // Change the display
+        SurfaceControl.DisplayMode addedDisplayInfo = createFakeDisplayMode(1, 3840, 2160, 60f);
+        modes = new SurfaceControl.DisplayMode[]{displayMode, addedDisplayInfo};
+        display.dynamicInfo.supportedDisplayModes = modes;
+        display.dynamicInfo.activeDisplayModeId = 1;
+        setUpDisplay(display);
+        mInjector.getTransmitter().sendHotplug(display, /* connected */ true);
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+        assertTrue(mListener.traversalRequested);
+        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+        assertThat(mListener.changedDisplays.size()).isEqualTo(1);
+
+        DisplayDevice displayDevice = mListener.changedDisplays.get(0);
+        displayDevice.applyPendingDisplayDeviceInfoChangesLocked();
+        displayDeviceInfo = displayDevice.getDisplayDeviceInfoLocked();
+
+        assertThat(displayDeviceInfo.supportedModes.length).isEqualTo(modes.length);
+        assertModeIsSupported(displayDeviceInfo.supportedModes, displayMode);
+        assertModeIsSupported(displayDeviceInfo.supportedModes, addedDisplayInfo);
+
+        activeMode = getModeById(displayDeviceInfo, displayDeviceInfo.modeId);
+        assertThat(matches(activeMode, addedDisplayInfo)).isTrue();
+
+        defaultMode = getModeById(displayDeviceInfo, displayDeviceInfo.defaultModeId);
+        assertThat(matches(defaultMode, addedDisplayInfo)).isTrue();
+    }
+
+    @Test
+    public void testAfterDisplayChange_ActiveModeIsUpdated() throws Exception {
+        SurfaceControl.DisplayMode[] modes = new SurfaceControl.DisplayMode[]{
+                createFakeDisplayMode(0, 1920, 1080, 60f),
+                createFakeDisplayMode(1, 1920, 1080, 50f)
+        };
+        FakeDisplay display = new FakeDisplay(PORT_A, modes, /* activeMode */ 0, 60f);
+        setUpDisplay(display);
+        updateAvailableDisplays();
+        mAdapter.registerLocked();
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+        assertThat(mListener.changedDisplays).isEmpty();
+
+        DisplayDeviceInfo displayDeviceInfo = mListener.addedDisplays.get(0)
+                .getDisplayDeviceInfoLocked();
+
+        Display.Mode activeMode = getModeById(displayDeviceInfo, displayDeviceInfo.modeId);
+        assertThat(activeMode.matches(1920, 1080, 60f)).isTrue();
+
+        // Change the display
+        display.dynamicInfo.activeDisplayModeId = 1;
+        setUpDisplay(display);
+        mInjector.getTransmitter().sendHotplug(display, /* connected */ true);
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+        assertTrue(mListener.traversalRequested);
+        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+        assertThat(mListener.changedDisplays.size()).isEqualTo(1);
+
+        DisplayDevice displayDevice = mListener.changedDisplays.get(0);
+        displayDevice.applyPendingDisplayDeviceInfoChangesLocked();
+        displayDeviceInfo = displayDevice.getDisplayDeviceInfoLocked();
+
+        activeMode = getModeById(displayDeviceInfo, displayDeviceInfo.modeId);
+        assertThat(activeMode.matches(1920, 1080, 50f)).isTrue();
+    }
+
+    @Test
+    public void testAfterDisplayChange_RenderFrameRateIsUpdated() throws Exception {
+        SurfaceControl.DisplayMode[] modes = new SurfaceControl.DisplayMode[]{
+                createFakeDisplayMode(0, 1920, 1080, 60f),
+        };
+        FakeDisplay display = new FakeDisplay(PORT_A, modes, /* activeMode */ 0,
+                /* renderFrameRate */30f);
+        setUpDisplay(display);
+        updateAvailableDisplays();
+        mAdapter.registerLocked();
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+        assertThat(mListener.changedDisplays).isEmpty();
+
+        DisplayDeviceInfo displayDeviceInfo = mListener.addedDisplays.get(0)
+                .getDisplayDeviceInfoLocked();
+
+        Display.Mode activeMode = getModeById(displayDeviceInfo, displayDeviceInfo.modeId);
+        assertThat(activeMode.matches(1920, 1080, 60f)).isTrue();
+        assertEquals(Float.floatToIntBits(30f),
+                Float.floatToIntBits(displayDeviceInfo.renderFrameRate));
+
+        // Change the render frame rate
+        display.dynamicInfo.renderFrameRate = 60f;
+        setUpDisplay(display);
+        mInjector.getTransmitter().sendHotplug(display, /* connected */ true);
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+        assertTrue(mListener.traversalRequested);
+        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+        assertThat(mListener.changedDisplays.size()).isEqualTo(1);
+
+        DisplayDevice displayDevice = mListener.changedDisplays.get(0);
+        displayDevice.applyPendingDisplayDeviceInfoChangesLocked();
+        displayDeviceInfo = displayDevice.getDisplayDeviceInfoLocked();
+
+        activeMode = getModeById(displayDeviceInfo, displayDeviceInfo.modeId);
+        assertThat(activeMode.matches(1920, 1080, 60f)).isTrue();
+        assertEquals(Float.floatToIntBits(60f),
+                Float.floatToIntBits(displayDeviceInfo.renderFrameRate));
+    }
+
+    @Test
+    public void testAfterDisplayChange_HdrCapabilitiesAreUpdated() throws Exception {
+        FakeDisplay display = new FakeDisplay(PORT_A);
+        Display.HdrCapabilities initialHdrCapabilities = new Display.HdrCapabilities(new int[0],
+                1000, 1000, 0);
+        display.dynamicInfo.hdrCapabilities = initialHdrCapabilities;
+        setUpDisplay(display);
+        updateAvailableDisplays();
+        mAdapter.registerLocked();
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+        assertThat(mListener.changedDisplays).isEmpty();
+
+        DisplayDeviceInfo displayDeviceInfo = mListener.addedDisplays.get(
+                0).getDisplayDeviceInfoLocked();
+
+        assertThat(displayDeviceInfo.hdrCapabilities).isEqualTo(initialHdrCapabilities);
+
+        // Change the display
+        Display.HdrCapabilities changedHdrCapabilities = new Display.HdrCapabilities(
+                new int[Display.HdrCapabilities.HDR_TYPE_HDR10_PLUS], 1000, 1000, 0);
+        display.dynamicInfo.hdrCapabilities = changedHdrCapabilities;
+        setUpDisplay(display);
+        mInjector.getTransmitter().sendHotplug(display, /* connected */ true);
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+        assertTrue(mListener.traversalRequested);
+        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+        assertThat(mListener.changedDisplays.size()).isEqualTo(1);
+
+        DisplayDevice displayDevice = mListener.changedDisplays.get(0);
+        displayDevice.applyPendingDisplayDeviceInfoChangesLocked();
+        displayDeviceInfo = displayDevice.getDisplayDeviceInfoLocked();
+
+        assertThat(displayDeviceInfo.hdrCapabilities).isEqualTo(changedHdrCapabilities);
+    }
+
+    @Test
+    public void testAfterDisplayChange_AllmSupportIsUpdated() throws Exception {
+        FakeDisplay display = new FakeDisplay(PORT_A);
+        display.dynamicInfo.autoLowLatencyModeSupported = true;
+        setUpDisplay(display);
+        updateAvailableDisplays();
+        mAdapter.registerLocked();
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+        assertThat(mListener.changedDisplays).isEmpty();
+
+        DisplayDeviceInfo displayDeviceInfo = mListener.addedDisplays.get(0)
+                .getDisplayDeviceInfoLocked();
+
+        assertThat(displayDeviceInfo.allmSupported).isTrue();
+
+        // Change the display
+        display.dynamicInfo.autoLowLatencyModeSupported = false;
+        setUpDisplay(display);
+        mInjector.getTransmitter().sendHotplug(display, /* connected */ true);
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+        assertTrue(mListener.traversalRequested);
+        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+        assertThat(mListener.changedDisplays.size()).isEqualTo(1);
+
+        DisplayDevice displayDevice = mListener.changedDisplays.get(0);
+        displayDevice.applyPendingDisplayDeviceInfoChangesLocked();
+        displayDeviceInfo = displayDevice.getDisplayDeviceInfoLocked();
+
+        assertThat(displayDeviceInfo.allmSupported).isFalse();
+    }
+
+    @Test
+    public void testAfterDisplayStateChanges_committedSetAfterState() throws Exception {
+        FakeDisplay display = new FakeDisplay(PORT_A);
+        setUpDisplay(display);
+        updateAvailableDisplays();
+        mAdapter.registerLocked();
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+        DisplayDevice displayDevice = mListener.addedDisplays.get(0);
+
+        // Turn off.
+        Runnable changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_OFF, 0,
+                0);
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+        assertThat(mListener.changedDisplays.size()).isEqualTo(1);
+        mListener.changedDisplays.clear();
+        assertThat(displayDevice.getDisplayDeviceInfoLocked().state).isEqualTo(Display.STATE_OFF);
+        assertThat(displayDevice.getDisplayDeviceInfoLocked().committedState).isNotEqualTo(
+                Display.STATE_OFF);
+        verify(mSurfaceControlProxy, never()).setDisplayPowerMode(display.token, Display.STATE_OFF);
+
+        // Execute powerstate change.
+        changeStateRunnable.run();
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+
+        // Verify that committed triggered a new change event and is set correctly.
+        verify(mSurfaceControlProxy, never()).setDisplayPowerMode(display.token, Display.STATE_OFF);
+        // We expect at least 1 update for the state change, but
+        // could get a second update for the initial brightness change if a nits mapping
+        // is available
+        assertThat(mListener.changedDisplays.size()).isAnyOf(1, 2);
+        assertThat(displayDevice.getDisplayDeviceInfoLocked().state).isEqualTo(Display.STATE_OFF);
+        assertThat(displayDevice.getDisplayDeviceInfoLocked().committedState).isEqualTo(
+                Display.STATE_OFF);
+    }
+
+    @Test
+    public void testAfterDisplayChange_GameContentTypeSupportIsUpdated() throws Exception {
+        FakeDisplay display = new FakeDisplay(PORT_A);
+        display.dynamicInfo.gameContentTypeSupported = true;
+        setUpDisplay(display);
+        updateAvailableDisplays();
+        mAdapter.registerLocked();
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+        assertThat(mListener.changedDisplays).isEmpty();
+
+        DisplayDeviceInfo displayDeviceInfo = mListener.addedDisplays.get(0)
+                .getDisplayDeviceInfoLocked();
+
+        assertThat(displayDeviceInfo.gameContentTypeSupported).isTrue();
+
+        // Change the display
+        display.dynamicInfo.gameContentTypeSupported = false;
+        setUpDisplay(display);
+        mInjector.getTransmitter().sendHotplug(display, /* connected */ true);
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+        assertTrue(mListener.traversalRequested);
+        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+        assertThat(mListener.changedDisplays.size()).isEqualTo(1);
+
+        DisplayDevice displayDevice = mListener.changedDisplays.get(0);
+        displayDevice.applyPendingDisplayDeviceInfoChangesLocked();
+        displayDeviceInfo = displayDevice.getDisplayDeviceInfoLocked();
+
+        assertThat(displayDeviceInfo.gameContentTypeSupported).isFalse();
+    }
+
+    @Test
+    public void testAfterDisplayChange_ColorModesAreUpdated() throws Exception {
+        FakeDisplay display = new FakeDisplay(PORT_A);
+        final int[] initialColorModes = new int[]{Display.COLOR_MODE_BT709};
+        display.dynamicInfo.supportedColorModes = initialColorModes;
+        setUpDisplay(display);
+        updateAvailableDisplays();
+        mAdapter.registerLocked();
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+        assertThat(mListener.changedDisplays).isEmpty();
+
+        DisplayDeviceInfo displayDeviceInfo = mListener.addedDisplays.get(0)
+                .getDisplayDeviceInfoLocked();
+
+        assertThat(displayDeviceInfo.colorMode).isEqualTo(Display.COLOR_MODE_BT709);
+        assertThat(displayDeviceInfo.supportedColorModes).isEqualTo(initialColorModes);
+
+        // Change the display
+        final int[] changedColorModes = new int[]{Display.COLOR_MODE_DEFAULT};
+        display.dynamicInfo.supportedColorModes = changedColorModes;
+        setUpDisplay(display);
+        mInjector.getTransmitter().sendHotplug(display, /* connected */ true);
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+        assertTrue(mListener.traversalRequested);
+        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+        assertThat(mListener.changedDisplays.size()).isEqualTo(1);
+
+        DisplayDevice displayDevice = mListener.changedDisplays.get(0);
+        displayDevice.applyPendingDisplayDeviceInfoChangesLocked();
+        displayDeviceInfo = displayDevice.getDisplayDeviceInfoLocked();
+
+        assertThat(displayDeviceInfo.colorMode).isEqualTo(Display.COLOR_MODE_DEFAULT);
+        assertThat(displayDeviceInfo.supportedColorModes).isEqualTo(changedColorModes);
+    }
+
+    @Test
+    public void testDisplayChange_withStaleDesiredDisplayModeSpecs() throws Exception {
+        SurfaceControl.DisplayMode[] modes = new SurfaceControl.DisplayMode[]{
+                createFakeDisplayMode(0, 1920, 1080, 60f),
+                createFakeDisplayMode(1, 1920, 1080, 50f)
+        };
+        final int activeMode = 0;
+        FakeDisplay display = new FakeDisplay(PORT_A, modes, activeMode, 60f);
+        display.desiredDisplayModeSpecs.defaultMode = 1;
+
+        setUpDisplay(display);
+        updateAvailableDisplays();
+        mAdapter.registerLocked();
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+        DisplayDevice displayDevice = mListener.addedDisplays.get(0);
+
+        int baseModeId = Arrays.stream(displayDevice.getDisplayDeviceInfoLocked().supportedModes)
+                .filter(mode -> mode.getRefreshRate() == 60f)
+                .findFirst()
+                .get()
+                .getModeId();
+
+        displayDevice.setDesiredDisplayModeSpecsLocked(
+                new DisplayModeDirector.DesiredDisplayModeSpecs(
+                        /*baseModeId*/ baseModeId,
+                        /*allowGroupSwitching*/ false,
+                        REFRESH_RATE_RANGES, REFRESH_RATE_RANGES
+                ));
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+        verify(mSurfaceControlProxy).setDesiredDisplayModeSpecs(display.token,
+                new SurfaceControl.DesiredDisplayModeSpecs(
+                        /* baseModeId */ 0,
+                        /* allowGroupSwitching */ false,
+                        REFRESH_RATE_RANGES, REFRESH_RATE_RANGES
+                ));
+
+        // Change the display
+        display.dynamicInfo.supportedDisplayModes = new SurfaceControl.DisplayMode[]{
+                createFakeDisplayMode(2, 1920, 1080, 60f)
+        };
+        display.dynamicInfo.activeDisplayModeId = 2;
+        // SurfaceFlinger can return a stale defaultMode. Make sure this doesn't crash.
+        display.desiredDisplayModeSpecs.defaultMode = 1;
+
+        setUpDisplay(display);
+        mInjector.getTransmitter().sendHotplug(display, /* connected */ true);
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+        assertTrue(mListener.traversalRequested);
+
+        displayDevice.applyPendingDisplayDeviceInfoChangesLocked();
+
+        baseModeId = displayDevice.getDisplayDeviceInfoLocked().supportedModes[0].getModeId();
+
+        // The traversal request will call setDesiredDisplayModeSpecsLocked on the display device
+        displayDevice.setDesiredDisplayModeSpecsLocked(
+                new DisplayModeDirector.DesiredDisplayModeSpecs(
+                        /*baseModeId*/ baseModeId,
+                        /*allowGroupSwitching*/ false,
+                        REFRESH_RATE_RANGES, REFRESH_RATE_RANGES
+                ));
+
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+        // Verify that this will reapply the desired modes.
+        verify(mSurfaceControlProxy).setDesiredDisplayModeSpecs(display.token,
+                new SurfaceControl.DesiredDisplayModeSpecs(
+                        /* baseModeId */ 2,
+                        /* allowGroupSwitching */ false,
+                        REFRESH_RATE_RANGES, REFRESH_RATE_RANGES
+                ));
+    }
+
+    @Test
+    public void testBacklightAdapter_withSurfaceControlSupport() {
+        final Binder displayToken = new Binder();
+
+        when(mSurfaceControlProxy.getDisplayBrightnessSupport(displayToken)).thenReturn(true);
+
+        // Test as default display
+        BacklightAdapter ba = new BacklightAdapter(displayToken, true /*isDefault*/,
+                mSurfaceControlProxy);
+        ba.setBacklight(0.514f, 100f, 0.614f, 500f);
+        verify(mSurfaceControlProxy).setDisplayBrightness(displayToken, 0.514f, 100f, 0.614f, 500f);
+
+        // Test as not default display
+        BacklightAdapter ba2 = new BacklightAdapter(displayToken, false /*isDefault*/,
+                mSurfaceControlProxy);
+        ba2.setBacklight(0.323f, 101f, 0.723f, 601f);
+        verify(mSurfaceControlProxy).setDisplayBrightness(displayToken, 0.323f, 101f, 0.723f, 601f);
+    }
+
+    @Test
+    public void testBacklightAdapter_withoutSourceControlSupport_defaultDisplay() {
+        final Binder displayToken = new Binder();
+        when(mSurfaceControlProxy.getDisplayBrightnessSupport(displayToken)).thenReturn(false);
+        doReturn(mMockedBacklight).when(mMockedLightsManager)
+                .getLight(LightsManager.LIGHT_ID_BACKLIGHT);
+
+        BacklightAdapter ba = new BacklightAdapter(displayToken, true /*isDefault*/,
+                mSurfaceControlProxy);
+        ba.setBacklight(1f, 1f, 0.123f, 1f);
+        verify(mMockedBacklight).setBrightness(0.123f);
+    }
+
+    @Test
+    public void testBacklightAdapter_withoutSourceControlSupport_nonDefaultDisplay() {
+        final Binder displayToken = new Binder();
+        when(mSurfaceControlProxy.getDisplayBrightnessSupport(displayToken)).thenReturn(false);
+        doReturn(mMockedBacklight).when(mMockedLightsManager)
+                .getLight(LightsManager.LIGHT_ID_BACKLIGHT);
+
+        BacklightAdapter ba = new BacklightAdapter(displayToken, false /*isDefault*/,
+                mSurfaceControlProxy);
+        ba.setBacklight(0.456f, 1f, 1f, 1f);
+
+        // Adapter does not forward any brightness in this case.
+        verify(mMockedBacklight, never()).setBrightness(anyFloat());
+    }
+
+    @Test
+    public void testGetSystemPreferredDisplayMode() throws Exception {
+        SurfaceControl.DisplayMode displayMode1 = createFakeDisplayMode(0, 1920, 1080, 60f);
+        // system preferred mode
+        SurfaceControl.DisplayMode displayMode2 = createFakeDisplayMode(1, 3840, 2160, 60f);
+        // user preferred mode
+        SurfaceControl.DisplayMode displayMode3 = createFakeDisplayMode(2, 1920, 1080, 30f);
+
+        SurfaceControl.DisplayMode[] modes =
+                new SurfaceControl.DisplayMode[]{displayMode1, displayMode2, displayMode3};
+        FakeDisplay display = new FakeDisplay(PORT_A, modes, 0, 1);
+        setUpDisplay(display);
+        updateAvailableDisplays();
+        mAdapter.registerLocked();
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+        assertThat(mListener.changedDisplays).isEmpty();
+
+        DisplayDeviceInfo displayDeviceInfo = mListener.addedDisplays.get(
+                0).getDisplayDeviceInfoLocked();
+        assertThat(displayDeviceInfo.supportedModes.length).isEqualTo(modes.length);
+        Display.Mode defaultMode = getModeById(displayDeviceInfo, displayDeviceInfo.defaultModeId);
+        assertThat(matches(defaultMode, displayMode1)).isTrue();
+
+        // Set the user preferred display mode
+        mListener.addedDisplays.get(0).setUserPreferredDisplayModeLocked(
+                new Display.Mode(
+                        displayMode3.width, displayMode3.height, displayMode3.refreshRate));
+        updateAvailableDisplays();
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+        displayDeviceInfo = mListener.addedDisplays.get(
+                0).getDisplayDeviceInfoLocked();
+        defaultMode = getModeById(displayDeviceInfo, displayDeviceInfo.defaultModeId);
+        assertThat(matches(defaultMode, displayMode3)).isTrue();
+
+        // clear the user preferred mode
+        mListener.addedDisplays.get(0).setUserPreferredDisplayModeLocked(null);
+        updateAvailableDisplays();
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+        displayDeviceInfo = mListener.addedDisplays.get(
+                0).getDisplayDeviceInfoLocked();
+        defaultMode = getModeById(displayDeviceInfo, displayDeviceInfo.defaultModeId);
+        assertThat(matches(defaultMode, displayMode2)).isTrue();
+
+        // Change the display and add new system preferred mode
+        SurfaceControl.DisplayMode addedDisplayInfo = createFakeDisplayMode(3, 2340, 1080, 20f);
+        modes = new SurfaceControl.DisplayMode[]{
+                displayMode1, displayMode2, displayMode3, addedDisplayInfo};
+        display.dynamicInfo.supportedDisplayModes = modes;
+        display.dynamicInfo.preferredBootDisplayMode = 3;
+        setUpDisplay(display);
+        mInjector.getTransmitter().sendHotplug(display, /* connected */ true);
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+        assertTrue(mListener.traversalRequested);
+        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+        assertThat(mListener.changedDisplays.size()).isEqualTo(3);
+
+        DisplayDevice displayDevice = mListener.changedDisplays.get(0);
+        displayDevice.applyPendingDisplayDeviceInfoChangesLocked();
+        displayDeviceInfo = displayDevice.getDisplayDeviceInfoLocked();
+
+        assertThat(displayDeviceInfo.supportedModes.length).isEqualTo(modes.length);
+        assertModeIsSupported(displayDeviceInfo.supportedModes, displayMode1);
+        assertModeIsSupported(displayDeviceInfo.supportedModes, displayMode2);
+        assertModeIsSupported(displayDeviceInfo.supportedModes, addedDisplayInfo);
+
+        assertThat(matches(displayDevice.getSystemPreferredDisplayModeLocked(), addedDisplayInfo))
+                .isTrue();
+    }
+
+    @Test
+    public void testHdrSdrRatio_notifiesOnChange() throws Exception {
+        FakeDisplay display = new FakeDisplay(PORT_A);
+        setUpDisplay(display);
+        updateAvailableDisplays();
+        mAdapter.registerLocked();
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+        DisplayDevice displayDevice = mListener.addedDisplays.get(0);
+
+        // Turn on / initialize
+        assumeTrue(displayDevice.getDisplayDeviceConfig().hasSdrToHdrRatioSpline());
+        Runnable changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 0,
+                0);
+        changeStateRunnable.run();
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+        mListener.changedDisplays.clear();
+
+        assertEquals(1.0f, displayDevice.getDisplayDeviceInfoLocked().hdrSdrRatio, 0.001f);
+
+        // HDR time!
+        Runnable goHdrRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 1f,
+                0);
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+        // Display state didn't change, no listeners should have happened
+        assertThat(mListener.changedDisplays.size()).isEqualTo(0);
+
+        // Execute hdr change.
+        goHdrRunnable.run();
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+        // Display state didn't change, expect to only get the HDR/SDR ratio change notification
+        assertThat(mListener.changedDisplays.size()).isEqualTo(1);
+
+        final float expectedRatio = DISPLAY_RANGE_NITS[1] / DISPLAY_RANGE_NITS[0];
+        assertEquals(expectedRatio, displayDevice.getDisplayDeviceInfoLocked().hdrSdrRatio,
+                0.001f);
+    }
+
+    @Test
+    public void test_getDisplayDeviceInfoLocked_internalDisplay_usesCutoutAndCorners()
+            throws Exception {
+        setupCutoutAndRoundedCorners();
+        FakeDisplay display = new FakeDisplay(PORT_A);
+        display.info.isInternal = true;
+        setUpDisplay(display);
+        updateAvailableDisplays();
+        mAdapter.registerLocked();
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+        DisplayDevice displayDevice = mListener.addedDisplays.get(0);
+
+        // Turn on / initialize
+        Runnable changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 0,
+                0);
+        changeStateRunnable.run();
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+        mListener.changedDisplays.clear();
+
+        DisplayDeviceInfo info = displayDevice.getDisplayDeviceInfoLocked();
+
+        assertThat(info.displayCutout).isNotNull();
+        assertThat(info.displayCutout.getBoundingRectTop()).isEqualTo(new Rect(507, 33, 573, 99));
+        assertThat(info.roundedCorners).isNotNull();
+        assertThat(info.roundedCorners.getRoundedCorner(0).getRadius()).isEqualTo(5);
+    }
+
+    @Test public void test_getDisplayDeviceInfoLocked_externalDisplay_doesNotUseCutoutOrCorners()
+            throws Exception {
+        setupCutoutAndRoundedCorners();
+        FakeDisplay display = new FakeDisplay(PORT_A);
+        display.info.isInternal = false;
+        setUpDisplay(display);
+        updateAvailableDisplays();
+        mAdapter.registerLocked();
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+        DisplayDevice displayDevice = mListener.addedDisplays.get(0);
+
+        // Turn on / initialize
+        Runnable changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 0,
+                0);
+        changeStateRunnable.run();
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+        mListener.changedDisplays.clear();
+
+        DisplayDeviceInfo info = displayDevice.getDisplayDeviceInfoLocked();
+
+        assertThat(info.displayCutout).isNull();
+        assertThat(info.roundedCorners).isNull();
+    }
+
+    private void setupCutoutAndRoundedCorners() {
+        String sampleCutout = "M 507,66\n"
+                + "a 33,33 0 1 0 66,0 33,33 0 1 0 -66,0\n"
+                + "Z\n"
+                + "@left\n";
+        // Setup some default cutout
+        when(mMockedResources.getString(
+                com.android.internal.R.string.config_mainBuiltInDisplayCutout))
+                .thenReturn(sampleCutout);
+        when(mMockedResources.getDimensionPixelSize(
+                com.android.internal.R.dimen.rounded_corner_radius)).thenReturn(5);
+    }
+
+    private void assertDisplayDpi(DisplayDeviceInfo info, int expectedPort,
+                                  float expectedXdpi,
+                                  float expectedYDpi,
+                                  int expectedDensityDpi) {
+        final DisplayAddress.Physical physical = (DisplayAddress.Physical) info.address;
+        assertNotNull(physical);
+        assertEquals(expectedPort, physical.getPort());
+        assertEquals(expectedXdpi, info.xDpi, 0.01);
+        assertEquals(expectedYDpi, info.yDpi, 0.01);
+        assertEquals(expectedDensityDpi, info.densityDpi);
+    }
+
+    private Display.Mode getModeById(DisplayDeviceInfo displayDeviceInfo, int modeId) {
+        return Arrays.stream(displayDeviceInfo.supportedModes)
+                .filter(mode -> mode.getModeId() == modeId)
+                .findFirst()
+                .get();
+    }
+
+    private void assertModeIsSupported(Display.Mode[] supportedModes,
+            SurfaceControl.DisplayMode mode) {
+        assertThat(Arrays.stream(supportedModes).anyMatch(
+                x -> x.matches(mode.width, mode.height, mode.refreshRate))).isTrue();
+    }
+
+    private void assertModeIsSupported(Display.Mode[] supportedModes,
+            SurfaceControl.DisplayMode mode, float[] alternativeRefreshRates) {
+        float[] sortedAlternativeRates =
+                Arrays.copyOf(alternativeRefreshRates, alternativeRefreshRates.length);
+        Arrays.sort(sortedAlternativeRates);
+
+        String message = "Expected " + mode + " with alternativeRefreshRates = "
+                + Arrays.toString(alternativeRefreshRates) + " to be in list of supported modes = "
+                + Arrays.toString(supportedModes);
+        Truth.assertWithMessage(message)
+            .that(Arrays.stream(supportedModes)
+                .anyMatch(x -> x.matches(mode.width, mode.height, mode.refreshRate)
+                        && Arrays.equals(x.getAlternativeRefreshRates(), sortedAlternativeRates)))
+                .isTrue();
+    }
+
+
+    private static class FakeDisplay {
+        public final DisplayAddress.Physical address;
+        public final IBinder token = new Binder();
+        public final SurfaceControl.StaticDisplayInfo info;
+        public SurfaceControl.DynamicDisplayInfo dynamicInfo =
+                new SurfaceControl.DynamicDisplayInfo();
+        {
+            dynamicInfo.supportedColorModes = new int[]{ Display.COLOR_MODE_DEFAULT };
+            dynamicInfo.hdrCapabilities = new Display.HdrCapabilities(new int[0],
+                    1000, 1000, 0);
+        }
+
+        public SurfaceControl.DesiredDisplayModeSpecs desiredDisplayModeSpecs =
+                new SurfaceControl.DesiredDisplayModeSpecs(
+                        /* defaultMode */ 0,
+                        /* allowGroupSwitching */ false,
+                        REFRESH_RATE_RANGES, REFRESH_RATE_RANGES
+                );
+
+        private FakeDisplay(int port) {
+            address = createDisplayAddress(port);
+            info = createFakeDisplayInfo();
+            dynamicInfo.supportedDisplayModes = new SurfaceControl.DisplayMode[]{
+                    createFakeDisplayMode(0, 800, 600, 60f)
+            };
+            dynamicInfo.activeDisplayModeId = 0;
+        }
+
+        private FakeDisplay(int port, SurfaceControl.DisplayMode[] modes, int activeMode,
+                float renderFrameRate) {
+            address = createDisplayAddress(port);
+            info = createFakeDisplayInfo();
+            dynamicInfo.supportedDisplayModes = modes;
+            dynamicInfo.activeDisplayModeId = activeMode;
+            dynamicInfo.renderFrameRate = renderFrameRate;
+        }
+
+        private FakeDisplay(int port, SurfaceControl.DisplayMode[] modes, int activeMode,
+                int preferredMode) {
+            address = createDisplayAddress(port);
+            info = createFakeDisplayInfo();
+            dynamicInfo.supportedDisplayModes = modes;
+            dynamicInfo.activeDisplayModeId = activeMode;
+            dynamicInfo.preferredBootDisplayMode = preferredMode;
+        }
+
+    }
+
+    private void setUpDisplay(FakeDisplay display) {
+        mAddresses.add(display.address);
+        when(mSurfaceControlProxy.getPhysicalDisplayToken(display.address.getPhysicalDisplayId()))
+                .thenReturn(display.token);
+        when(mSurfaceControlProxy.getStaticDisplayInfo(display.address.getPhysicalDisplayId()))
+                .thenReturn(display.info);
+        when(mSurfaceControlProxy.getDynamicDisplayInfo(display.address.getPhysicalDisplayId()))
+                .thenReturn(display.dynamicInfo);
+        when(mSurfaceControlProxy.getDesiredDisplayModeSpecs(display.token))
+                .thenReturn(display.desiredDisplayModeSpecs);
+    }
+
+    private void updateAvailableDisplays() {
+        long[] ids = new long[mAddresses.size()];
+        int i = 0;
+        for (DisplayAddress.Physical address : mAddresses) {
+            ids[i] = address.getPhysicalDisplayId();
+            i++;
+        }
+        when(mSurfaceControlProxy.getPhysicalDisplayIds()).thenReturn(ids);
+    }
+
+    private static DisplayAddress.Physical createDisplayAddress(int port) {
+        return DisplayAddress.fromPortAndModel(port, DISPLAY_MODEL);
+    }
+
+    private static SurfaceControl.StaticDisplayInfo createFakeDisplayInfo() {
+        final SurfaceControl.StaticDisplayInfo info = new SurfaceControl.StaticDisplayInfo();
+        info.density = 100;
+        return info;
+    }
+
+    private static SurfaceControl.DisplayMode createFakeDisplayMode(int id, int width, int height,
+            float refreshRate) {
+        return createFakeDisplayMode(id, width, height, refreshRate, /* group */ 0);
+    }
+
+    private static SurfaceControl.DisplayMode createFakeDisplayMode(int id, int width, int height,
+            float refreshRate, int group) {
+        final SurfaceControl.DisplayMode mode = new SurfaceControl.DisplayMode();
+        mode.id = id;
+        mode.width = width;
+        mode.height = height;
+        mode.refreshRate = refreshRate;
+        mode.xDpi = 100;
+        mode.yDpi = 100;
+        mode.group = group;
+        mode.supportedHdrTypes = HDR_TYPES;
+        return mode;
+    }
+
+    private static void waitForHandlerToComplete(Handler handler, long waitTimeMs)
+            throws InterruptedException {
+        final CountDownLatch fence = new CountDownLatch(1);
+        handler.post(fence::countDown);
+        assertTrue(fence.await(waitTimeMs, TimeUnit.MILLISECONDS));
+    }
+
+    private class HotplugTransmitter {
+        private final Handler mHandler;
+        private final LocalDisplayAdapter.DisplayEventListener mListener;
+
+        HotplugTransmitter(Looper looper, LocalDisplayAdapter.DisplayEventListener listener) {
+            mHandler = new Handler(looper);
+            mListener = listener;
+        }
+
+        public void sendHotplug(FakeDisplay display, boolean connected)
+                throws InterruptedException {
+            mHandler.post(() -> mListener.onHotplug(/* timestampNanos = */ 0,
+                    display.address.getPhysicalDisplayId(), connected));
+            waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+        }
+    }
+
+    private class Injector extends LocalDisplayAdapter.Injector {
+        private HotplugTransmitter mTransmitter;
+        @Override
+        public void setDisplayEventListenerLocked(Looper looper,
+                LocalDisplayAdapter.DisplayEventListener listener) {
+            mTransmitter = new HotplugTransmitter(looper, listener);
+        }
+
+        public HotplugTransmitter getTransmitter() {
+            return mTransmitter;
+        }
+
+        @Override
+        public LocalDisplayAdapter.SurfaceControlProxy getSurfaceControlProxy() {
+            return mSurfaceControlProxy;
+        }
+
+        // Instead of using DisplayDeviceConfig.create(context, physicalDisplayId, isFirstDisplay)
+        // we should use DisplayDeviceConfig.create(context, isFirstDisplay) for the test to ensure
+        // that real device DisplayDeviceConfig is not loaded for FakeDisplay and we are getting
+        // consistent behaviour. Please also note that context passed to this method, is
+        // mMockContext and values will be loaded from mMockResources.
+        @Override
+        public DisplayDeviceConfig createDisplayDeviceConfig(Context context,
+                long physicalDisplayId, boolean isFirstDisplay) {
+            return DisplayDeviceConfig.create(context, isFirstDisplay);
+        }
+    }
+
+    private class TestListener implements DisplayAdapter.Listener {
+        public ArrayList<DisplayDevice> addedDisplays = new ArrayList<>();
+        public ArrayList<DisplayDevice> changedDisplays = new ArrayList<>();
+        public boolean traversalRequested = false;
+
+        @Override
+        public void onDisplayDeviceEvent(DisplayDevice device, int event) {
+            if (event == DisplayAdapter.DISPLAY_DEVICE_EVENT_ADDED) {
+                addedDisplays.add(device);
+            } else if (event == DisplayAdapter.DISPLAY_DEVICE_EVENT_CHANGED) {
+                changedDisplays.add(device);
+            }
+        }
+
+        @Override
+        public void onTraversalRequested() {
+            traversalRequested = true;
+        }
+    }
+
+    private TypedArray createFloatTypedArray(float[] vals) {
+        TypedArray mockArray = mock(TypedArray.class);
+        when(mockArray.length()).thenAnswer(invocation -> {
+            return vals.length;
+        });
+        when(mockArray.getFloat(anyInt(), anyFloat())).thenAnswer(invocation -> {
+            final float def = (float) invocation.getArguments()[1];
+            if (vals == null) {
+                return def;
+            }
+            int idx = (int) invocation.getArguments()[0];
+            if (idx >= 0 && idx < vals.length) {
+                return vals[idx];
+            } else {
+                return def;
+            }
+        });
+        return mockArray;
+    }
+
+    private boolean matches(Display.Mode a, SurfaceControl.DisplayMode b) {
+        return a.getPhysicalWidth() == b.width && a.getPhysicalHeight() == b.height
+                && Float.floatToIntBits(a.getRefreshRate()) == Float.floatToIntBits(b.refreshRate);
+    }
+}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
new file mode 100644
index 0000000..0fe6e64
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
@@ -0,0 +1,994 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE;
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.Display.DEFAULT_DISPLAY_GROUP;
+import static android.view.Display.FLAG_REAR;
+import static android.view.Display.TYPE_EXTERNAL;
+import static android.view.Display.TYPE_INTERNAL;
+import static android.view.Display.TYPE_VIRTUAL;
+
+import static com.android.server.display.DeviceStateToLayoutMap.STATE_DEFAULT;
+import static com.android.server.display.DisplayAdapter.DISPLAY_DEVICE_EVENT_ADDED;
+import static com.android.server.display.DisplayAdapter.DISPLAY_DEVICE_EVENT_CHANGED;
+import static com.android.server.display.DisplayAdapter.DISPLAY_DEVICE_EVENT_REMOVED;
+import static com.android.server.display.DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY;
+import static com.android.server.display.LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_ADDED;
+import static com.android.server.display.LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_REMOVED;
+import static com.android.server.display.layout.Layout.Display.POSITION_REAR;
+import static com.android.server.display.layout.Layout.Display.POSITION_UNKNOWN;
+
+import static com.google.common.truth.Truth.assertThat;
+
+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.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.PropertyInvalidatedCache;
+import android.content.Context;
+import android.content.res.Resources;
+import android.os.Handler;
+import android.os.IPowerManager;
+import android.os.IThermalService;
+import android.os.PowerManager;
+import android.os.Process;
+import android.os.test.TestLooper;
+import android.view.Display;
+import android.view.DisplayAddress;
+import android.view.DisplayInfo;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.server.display.layout.DisplayIdProducer;
+import com.android.server.display.layout.Layout;
+import com.android.server.utils.FoldSettingWrapper;
+
+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.MockitoAnnotations;
+import org.mockito.Spy;
+
+import java.io.File;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Arrays;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class LogicalDisplayMapperTest {
+    private static int sUniqueTestDisplayId = 0;
+    private static final int DEVICE_STATE_CLOSED = 0;
+    private static final int DEVICE_STATE_OPEN = 2;
+    private static int sNextNonDefaultDisplayId = DEFAULT_DISPLAY + 1;
+    private static final File NON_EXISTING_FILE = new File("/non_existing_folder/should_not_exist");
+
+    private DisplayDeviceRepository mDisplayDeviceRepo;
+    private LogicalDisplayMapper mLogicalDisplayMapper;
+    private TestLooper mLooper;
+    private Handler mHandler;
+    private PowerManager mPowerManager;
+
+    private final DisplayIdProducer mIdProducer = (isDefault) ->
+            isDefault ? DEFAULT_DISPLAY : sNextNonDefaultDisplayId++;
+
+    @Mock LogicalDisplayMapper.Listener mListenerMock;
+    @Mock Context mContextMock;
+    @Mock FoldSettingWrapper mFoldSettingWrapperMock;
+    @Mock Resources mResourcesMock;
+    @Mock IPowerManager mIPowerManagerMock;
+    @Mock IThermalService mIThermalServiceMock;
+    @Spy DeviceStateToLayoutMap mDeviceStateToLayoutMapSpy =
+            new DeviceStateToLayoutMap(mIdProducer, NON_EXISTING_FILE);
+
+    @Captor ArgumentCaptor<LogicalDisplay> mDisplayCaptor;
+
+    @Before
+    public void setUp() {
+        // Share classloader to allow package private access.
+        System.setProperty("dexmaker.share_classloader", "true");
+        MockitoAnnotations.initMocks(this);
+
+        mDisplayDeviceRepo = new DisplayDeviceRepository(
+                new DisplayManagerService.SyncRoot(),
+                new PersistentDataStore(new PersistentDataStore.Injector() {
+                    @Override
+                    public InputStream openRead() {
+                        return null;
+                    }
+
+                    @Override
+                    public OutputStream startWrite() {
+                        return null;
+                    }
+
+                    @Override
+                    public void finishWrite(OutputStream os, boolean success) {}
+                }));
+
+        // Disable binder caches in this process.
+        PropertyInvalidatedCache.disableForTestMode();
+
+        mPowerManager = new PowerManager(mContextMock, mIPowerManagerMock, mIThermalServiceMock,
+                null);
+
+        when(mContextMock.getSystemServiceName(PowerManager.class))
+                .thenReturn(Context.POWER_SERVICE);
+        when(mFoldSettingWrapperMock.shouldStayAwakeOnFold()).thenReturn(false);
+        when(mContextMock.getSystemService(PowerManager.class)).thenReturn(mPowerManager);
+        when(mContextMock.getResources()).thenReturn(mResourcesMock);
+        when(mResourcesMock.getBoolean(
+                com.android.internal.R.bool.config_supportsConcurrentInternalDisplays))
+                .thenReturn(true);
+        when(mResourcesMock.getIntArray(
+                com.android.internal.R.array.config_deviceStatesOnWhichToWakeUp))
+                .thenReturn(new int[]{1, 2});
+        when(mResourcesMock.getIntArray(
+                com.android.internal.R.array.config_deviceStatesOnWhichToSleep))
+                .thenReturn(new int[]{0});
+
+        mLooper = new TestLooper();
+        mHandler = new Handler(mLooper.getLooper());
+        mLogicalDisplayMapper = new LogicalDisplayMapper(mContextMock, mDisplayDeviceRepo,
+                mListenerMock, new DisplayManagerService.SyncRoot(), mHandler,
+                mDeviceStateToLayoutMapSpy, mFoldSettingWrapperMock);
+    }
+
+
+    /////////////////
+    // Test Methods
+    /////////////////
+
+    @Test
+    public void testDisplayDeviceAddAndRemove_Internal() {
+        DisplayDevice device = createDisplayDevice(TYPE_INTERNAL, 600, 800,
+                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+
+        // add
+        LogicalDisplay displayAdded = add(device);
+        assertEquals(info(displayAdded).address, info(device).address);
+        assertEquals(DEFAULT_DISPLAY, id(displayAdded));
+
+        // remove
+        mDisplayDeviceRepo.onDisplayDeviceEvent(device, DISPLAY_DEVICE_EVENT_REMOVED);
+        verify(mListenerMock).onLogicalDisplayEventLocked(
+                mDisplayCaptor.capture(), eq(LOGICAL_DISPLAY_EVENT_REMOVED));
+        LogicalDisplay displayRemoved = mDisplayCaptor.getValue();
+        assertEquals(DEFAULT_DISPLAY, id(displayRemoved));
+        assertEquals(displayAdded, displayRemoved);
+    }
+
+    @Test
+    public void testDisplayDeviceAddAndRemove_NonInternalTypes() {
+        testDisplayDeviceAddAndRemove_NonInternal(TYPE_EXTERNAL);
+        testDisplayDeviceAddAndRemove_NonInternal(Display.TYPE_WIFI);
+        testDisplayDeviceAddAndRemove_NonInternal(Display.TYPE_OVERLAY);
+        testDisplayDeviceAddAndRemove_NonInternal(TYPE_VIRTUAL);
+        testDisplayDeviceAddAndRemove_NonInternal(Display.TYPE_UNKNOWN);
+
+        // Call the internal test again, just to verify that adding non-internal displays
+        // doesn't affect the ability for an internal display to become the default display.
+        testDisplayDeviceAddAndRemove_Internal();
+    }
+
+    @Test
+    public void testDisplayDeviceAdd_TwoInternalOneDefault() {
+        DisplayDevice device1 = createDisplayDevice(TYPE_INTERNAL, 600, 800, 0);
+        DisplayDevice device2 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
+                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+
+        LogicalDisplay display1 = add(device1);
+        assertEquals(info(display1).address, info(device1).address);
+        assertNotEquals(DEFAULT_DISPLAY, id(display1));
+
+        LogicalDisplay display2 = add(device2);
+        assertEquals(info(display2).address, info(device2).address);
+        assertEquals(DEFAULT_DISPLAY, id(display2));
+    }
+
+    @Test
+    public void testDisplayDeviceAdd_TwoInternalBothDefault() {
+        DisplayDevice device1 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
+                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+        DisplayDevice device2 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
+                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+
+        LogicalDisplay display1 = add(device1);
+        assertEquals(info(display1).address, info(device1).address);
+        assertEquals(DEFAULT_DISPLAY, id(display1));
+
+        LogicalDisplay display2 = add(device2);
+        assertEquals(info(display2).address, info(device2).address);
+        // Despite the flags, we can only have one default display
+        assertNotEquals(DEFAULT_DISPLAY, id(display2));
+    }
+
+    @Test
+    public void testDisplayDeviceAddAndRemove_OneExternalDefault() {
+        DisplayDevice device = createDisplayDevice(TYPE_EXTERNAL, 600, 800,
+                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+
+        // add
+        LogicalDisplay displayAdded = add(device);
+        assertEquals(info(displayAdded).address, info(device).address);
+        assertEquals(Display.DEFAULT_DISPLAY, id(displayAdded));
+
+        // remove
+        mDisplayDeviceRepo.onDisplayDeviceEvent(device, DISPLAY_DEVICE_EVENT_REMOVED);
+        verify(mListenerMock).onLogicalDisplayEventLocked(
+                mDisplayCaptor.capture(), eq(LOGICAL_DISPLAY_EVENT_REMOVED));
+        LogicalDisplay displayRemoved = mDisplayCaptor.getValue();
+        assertEquals(DEFAULT_DISPLAY, id(displayRemoved));
+        assertEquals(displayAdded, displayRemoved);
+    }
+
+    @Test
+    public void testDisplayDeviceAddAndRemove_SwitchDefault() {
+        DisplayDevice device1 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
+                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+        DisplayDevice device2 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
+                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+
+        LogicalDisplay display1 = add(device1);
+        assertEquals(info(display1).address, info(device1).address);
+        assertEquals(DEFAULT_DISPLAY, id(display1));
+
+        LogicalDisplay display2 = add(device2);
+        assertEquals(info(display2).address, info(device2).address);
+        // We can only have one default display
+        assertEquals(DEFAULT_DISPLAY, id(display1));
+
+        // remove
+        mDisplayDeviceRepo.onDisplayDeviceEvent(device1, DISPLAY_DEVICE_EVENT_REMOVED);
+
+        verify(mListenerMock).onLogicalDisplayEventLocked(
+                mDisplayCaptor.capture(), eq(LOGICAL_DISPLAY_EVENT_REMOVED));
+        LogicalDisplay displayRemoved = mDisplayCaptor.getValue();
+        // Display 1 is still the default logical display
+        assertEquals(DEFAULT_DISPLAY, id(display1));
+        // The logical displays had their devices swapped and Display 2 was removed
+        assertEquals(display2, displayRemoved);
+        assertEquals(info(display1).address, info(device2).address);
+    }
+
+    @Test
+    public void testGetDisplayIdsLocked() {
+        add(createDisplayDevice(TYPE_INTERNAL, 600, 800,
+                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY));
+        add(createDisplayDevice(TYPE_EXTERNAL, 600, 800, 0));
+        add(createDisplayDevice(TYPE_VIRTUAL, 600, 800, 0));
+
+        int [] ids = mLogicalDisplayMapper.getDisplayIdsLocked(Process.SYSTEM_UID,
+                /* includeDisabled= */ true);
+        assertEquals(3, ids.length);
+        Arrays.sort(ids);
+        assertEquals(DEFAULT_DISPLAY, ids[0]);
+    }
+
+    @Test
+    public void testGetDisplayInfoForStateLocked_defaultLayout() {
+        final DisplayDevice device1 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
+                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+        final DisplayDevice device2 = createDisplayDevice(TYPE_INTERNAL, 200, 800,
+                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+
+        add(device1);
+        add(device2);
+
+        Layout layout1 = new Layout();
+        createDefaultDisplay(layout1, device1);
+        createNonDefaultDisplay(layout1, device2, /* enabled= */ true, /* group= */ null);
+        when(mDeviceStateToLayoutMapSpy.get(STATE_DEFAULT)).thenReturn(layout1);
+        assertThat(layout1.size()).isEqualTo(2);
+        final int logicalId2 = layout1.getByAddress(info(device2).address).getLogicalDisplayId();
+
+        final DisplayInfo displayInfoDefault = mLogicalDisplayMapper.getDisplayInfoForStateLocked(
+                STATE_DEFAULT, DEFAULT_DISPLAY);
+        assertThat(displayInfoDefault.displayId).isEqualTo(DEFAULT_DISPLAY);
+        assertThat(displayInfoDefault.logicalWidth).isEqualTo(width(device1));
+        assertThat(displayInfoDefault.logicalHeight).isEqualTo(height(device1));
+
+        final DisplayInfo displayInfoOther = mLogicalDisplayMapper.getDisplayInfoForStateLocked(
+                STATE_DEFAULT, logicalId2);
+        assertThat(displayInfoOther).isNotNull();
+        assertThat(displayInfoOther.displayId).isEqualTo(logicalId2);
+        assertThat(displayInfoOther.logicalWidth).isEqualTo(width(device2));
+        assertThat(displayInfoOther.logicalHeight).isEqualTo(height(device2));
+    }
+
+    @Test
+    public void testGetDisplayInfoForStateLocked_multipleLayouts() {
+        final DisplayDevice device1 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
+                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+        final DisplayDevice device2 = createDisplayDevice(TYPE_INTERNAL, 200, 800,
+                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+        final DisplayDevice device3 = createDisplayDevice(TYPE_VIRTUAL, 700, 800,
+                DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP);
+
+        add(device1);
+        add(device2);
+        add(device3);
+
+        Layout layout1 = new Layout();
+        createDefaultDisplay(layout1, device1);
+        when(mDeviceStateToLayoutMapSpy.get(STATE_DEFAULT)).thenReturn(layout1);
+
+        final int layoutState2 = 2;
+        Layout layout2 = new Layout();
+        createNonDefaultDisplay(layout2, device2, /* enabled= */ true, /* group= */ null);
+        // Device3 is the default display.
+        createDefaultDisplay(layout2, device3);
+        when(mDeviceStateToLayoutMapSpy.get(layoutState2)).thenReturn(layout2);
+        assertThat(layout2.size()).isEqualTo(2);
+        final int logicalId2 = layout2.getByAddress(info(device2).address).getLogicalDisplayId();
+
+        // Default layout.
+        final DisplayInfo displayInfoLayout1Default =
+                mLogicalDisplayMapper.getDisplayInfoForStateLocked(
+                        STATE_DEFAULT, DEFAULT_DISPLAY);
+        assertThat(displayInfoLayout1Default.displayId).isEqualTo(DEFAULT_DISPLAY);
+        assertThat(displayInfoLayout1Default.logicalWidth).isEqualTo(width(device1));
+        assertThat(displayInfoLayout1Default.logicalHeight).isEqualTo(height(device1));
+
+        // Second layout, where device3 is the default display.
+        final DisplayInfo displayInfoLayout2Default =
+                mLogicalDisplayMapper.getDisplayInfoForStateLocked(
+                        layoutState2, DEFAULT_DISPLAY);
+        assertThat(displayInfoLayout2Default.displayId).isEqualTo(DEFAULT_DISPLAY);
+        assertThat(displayInfoLayout2Default.logicalWidth).isEqualTo(width(device3));
+        assertThat(displayInfoLayout2Default.logicalHeight).isEqualTo(height(device3));
+
+        final DisplayInfo displayInfoLayout2Other =
+                mLogicalDisplayMapper.getDisplayInfoForStateLocked(
+                        layoutState2, logicalId2);
+        assertThat(displayInfoLayout2Other).isNotNull();
+        assertThat(displayInfoLayout2Other.displayId).isEqualTo(logicalId2);
+        assertThat(displayInfoLayout2Other.logicalWidth).isEqualTo(width(device2));
+        assertThat(displayInfoLayout2Other.logicalHeight).isEqualTo(height(device2));
+    }
+
+    @Test
+    public void testGetDisplayInfoForStateLocked_multipleDisplayGroups() {
+        DisplayDevice device1 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
+                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+        DisplayDevice device2 = createDisplayDevice(TYPE_INTERNAL, 200, 800,
+                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+        DisplayDevice device3 = createDisplayDevice(TYPE_INTERNAL, 700, 800,
+                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+        DisplayDevice device4 = createDisplayDevice(TYPE_INTERNAL, 400, 600,
+                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+
+        Layout layout = new Layout();
+        createDefaultDisplay(layout, device1);
+        createNonDefaultDisplay(layout, device2, /* enabled= */  true, /* group= */ "group1");
+        createNonDefaultDisplay(layout, device3, /* enabled= */  true, /* group= */ "group1");
+        createNonDefaultDisplay(layout, device4, /* enabled= */  true, /* group= */ "group2");
+
+        when(mDeviceStateToLayoutMapSpy.get(STATE_DEFAULT)).thenReturn(layout);
+
+        LogicalDisplay display1 = add(device1);
+        LogicalDisplay display2 = add(device2);
+        LogicalDisplay display3 = add(device3);
+        LogicalDisplay display4 = add(device4);
+
+        int displayGroupId1 =
+                mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display1));
+        int displayGroupId2 =
+                mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display2));
+        int displayGroupId3 =
+                mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display3));
+        int displayGroupId4 =
+                mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display4));
+        assertThat(displayGroupId1).isEqualTo(DEFAULT_DISPLAY_GROUP);
+        assertThat(displayGroupId2).isNotEqualTo(DEFAULT_DISPLAY_GROUP);
+        assertThat(displayGroupId2).isEqualTo(displayGroupId3);
+        assertThat(displayGroupId3).isNotEqualTo(DEFAULT_DISPLAY_GROUP);
+        assertThat(displayGroupId2).isNotEqualTo(displayGroupId4);
+    }
+
+    @Test
+    public void testSingleDisplayGroup() {
+        LogicalDisplay display1 = add(createDisplayDevice(TYPE_INTERNAL, 600, 800,
+                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY));
+        LogicalDisplay display2 = add(createDisplayDevice(TYPE_INTERNAL, 600, 800, 0));
+        LogicalDisplay display3 = add(createDisplayDevice(TYPE_VIRTUAL, 600, 800, 0));
+
+        assertEquals(DEFAULT_DISPLAY_GROUP,
+                mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display1)));
+        assertEquals(DEFAULT_DISPLAY_GROUP,
+                mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display2)));
+        assertEquals(DEFAULT_DISPLAY_GROUP,
+                mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display3)));
+    }
+
+    @Test
+    public void testMultipleDisplayGroups() {
+        LogicalDisplay display1 = add(createDisplayDevice(TYPE_INTERNAL, 600, 800,
+                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY));
+        LogicalDisplay display2 = add(createDisplayDevice(TYPE_INTERNAL, 600, 800, 0));
+
+
+        TestDisplayDevice device3 = createDisplayDevice(TYPE_VIRTUAL, 600, 800,
+                DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP);
+        LogicalDisplay display3 = add(device3);
+
+        assertEquals(DEFAULT_DISPLAY_GROUP,
+                mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display1)));
+        assertEquals(DEFAULT_DISPLAY_GROUP,
+                mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display2)));
+        assertNotEquals(DEFAULT_DISPLAY_GROUP,
+                mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display3)));
+
+        // Now switch it back to the default group by removing the flag and issuing an update
+        DisplayDeviceInfo info = device3.getSourceInfo();
+        info.flags = info.flags & ~DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP;
+        mDisplayDeviceRepo.onDisplayDeviceEvent(device3, DISPLAY_DEVICE_EVENT_CHANGED);
+
+        // Verify the new group is correct.
+        assertEquals(DEFAULT_DISPLAY_GROUP,
+                mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display3)));
+    }
+
+    @Test
+    public void testDevicesAreAddedToDeviceDisplayGroups() {
+        // Create the default internal display of the device.
+        LogicalDisplay defaultDisplay =
+                add(
+                        createDisplayDevice(
+                                Display.TYPE_INTERNAL,
+                                600,
+                                800,
+                                DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY));
+
+        // Create 3 virtual displays associated with a first virtual device.
+        int deviceId1 = 1;
+        TestDisplayDevice display1 =
+                createDisplayDevice(Display.TYPE_VIRTUAL, "virtualDevice1Display1", 600, 800, 0);
+        mLogicalDisplayMapper.associateDisplayDeviceWithVirtualDevice(display1, deviceId1);
+        LogicalDisplay virtualDevice1Display1 = add(display1);
+
+        TestDisplayDevice display2 =
+                createDisplayDevice(Display.TYPE_VIRTUAL, "virtualDevice1Display2", 600, 800, 0);
+        mLogicalDisplayMapper.associateDisplayDeviceWithVirtualDevice(display2, deviceId1);
+        LogicalDisplay virtualDevice1Display2 = add(display2);
+
+        TestDisplayDevice display3 =
+                createDisplayDevice(Display.TYPE_VIRTUAL, "virtualDevice1Display3", 600, 800, 0);
+        mLogicalDisplayMapper.associateDisplayDeviceWithVirtualDevice(display3, deviceId1);
+        LogicalDisplay virtualDevice1Display3 = add(display3);
+
+        // Create another 3 virtual displays associated with a second virtual device.
+        int deviceId2 = 2;
+        TestDisplayDevice display4 =
+                createDisplayDevice(Display.TYPE_VIRTUAL, "virtualDevice2Display1", 600, 800, 0);
+        mLogicalDisplayMapper.associateDisplayDeviceWithVirtualDevice(display4, deviceId2);
+        LogicalDisplay virtualDevice2Display1 = add(display4);
+
+        TestDisplayDevice display5 =
+                createDisplayDevice(Display.TYPE_VIRTUAL, "virtualDevice2Display2", 600, 800, 0);
+        mLogicalDisplayMapper.associateDisplayDeviceWithVirtualDevice(display5, deviceId2);
+        LogicalDisplay virtualDevice2Display2 = add(display5);
+
+        // The final display is created with FLAG_OWN_DISPLAY_GROUP set.
+        TestDisplayDevice display6 =
+                createDisplayDevice(
+                        Display.TYPE_VIRTUAL,
+                        "virtualDevice2Display3",
+                        600,
+                        800,
+                        DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP);
+        mLogicalDisplayMapper.associateDisplayDeviceWithVirtualDevice(display6, deviceId2);
+        LogicalDisplay virtualDevice2Display3 = add(display6);
+
+        // Verify that the internal display is in the default display group.
+        assertEquals(
+                DEFAULT_DISPLAY_GROUP,
+                mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(defaultDisplay)));
+
+        // Verify that all the displays for virtual device 1 are in the same (non-default) display
+        // group.
+        int virtualDevice1DisplayGroupId =
+                mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(
+                        id(virtualDevice1Display1));
+        assertNotEquals(DEFAULT_DISPLAY_GROUP, virtualDevice1DisplayGroupId);
+        assertEquals(
+                virtualDevice1DisplayGroupId,
+                mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(
+                        id(virtualDevice1Display2)));
+        assertEquals(
+                virtualDevice1DisplayGroupId,
+                mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(
+                        id(virtualDevice1Display3)));
+
+        // The first 2 displays for virtual device 2 should be in the same non-default group.
+        int virtualDevice2DisplayGroupId =
+                mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(
+                        id(virtualDevice2Display1));
+        assertNotEquals(DEFAULT_DISPLAY_GROUP, virtualDevice2DisplayGroupId);
+        assertEquals(
+                virtualDevice2DisplayGroupId,
+                mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(
+                        id(virtualDevice2Display2)));
+        // virtualDevice2Display3 was created with FLAG_OWN_DISPLAY_GROUP and shouldn't be grouped
+        // with other displays of this device or be in the default display group.
+        assertNotEquals(
+                virtualDevice2DisplayGroupId,
+                mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(
+                        id(virtualDevice2Display3)));
+        assertNotEquals(
+                DEFAULT_DISPLAY_GROUP,
+                mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(
+                        id(virtualDevice2Display3)));
+    }
+
+    @Test
+    public void testDeviceShouldBeWoken() {
+        assertTrue(mLogicalDisplayMapper.shouldDeviceBeWoken(DEVICE_STATE_OPEN,
+                DEVICE_STATE_CLOSED,
+                /* isInteractive= */false,
+                /* isBootCompleted= */true));
+    }
+
+    @Test
+    public void testDeviceShouldNotBeWoken() {
+        assertFalse(mLogicalDisplayMapper.shouldDeviceBeWoken(DEVICE_STATE_CLOSED,
+                DEVICE_STATE_OPEN,
+                /* isInteractive= */false,
+                /* isBootCompleted= */true));
+    }
+
+    @Test
+    public void testDeviceShouldBePutToSleep() {
+        assertTrue(mLogicalDisplayMapper.shouldDeviceBePutToSleep(DEVICE_STATE_CLOSED,
+                DEVICE_STATE_OPEN,
+                /* isOverrideActive= */false,
+                /* isInteractive= */true,
+                /* isBootCompleted= */true));
+    }
+
+    @Test
+    public void testDeviceShouldNotSleepWhenFoldSettingTrue() {
+        when(mFoldSettingWrapperMock.shouldStayAwakeOnFold()).thenReturn(true);
+
+        assertFalse(mLogicalDisplayMapper.shouldDeviceBePutToSleep(DEVICE_STATE_CLOSED,
+                DEVICE_STATE_OPEN,
+                /* isOverrideActive= */false,
+                /* isInteractive= */true,
+                /* isBootCompleted= */true));
+    }
+
+    @Test
+    public void testDeviceShouldNotBePutToSleep() {
+        assertFalse(mLogicalDisplayMapper.shouldDeviceBePutToSleep(DEVICE_STATE_OPEN,
+                DEVICE_STATE_CLOSED,
+                /* isOverrideActive= */false,
+                /* isInteractive= */true,
+                /* isBootCompleted= */true));
+        assertFalse(mLogicalDisplayMapper.shouldDeviceBePutToSleep(DEVICE_STATE_CLOSED,
+                INVALID_DEVICE_STATE,
+                /* isOverrideActive= */false,
+                /* isInteractive= */true,
+                /* isBootCompleted= */true));
+    }
+
+    @Test
+    public void testDeviceShouldNotBePutToSleepDifferentBaseState() {
+        assertFalse(mLogicalDisplayMapper.shouldDeviceBePutToSleep(DEVICE_STATE_CLOSED,
+                DEVICE_STATE_OPEN,
+                /* isOverrideActive= */true,
+                /* isInteractive= */true,
+                /* isBootCompleted= */true));
+    }
+
+    @Test
+    public void testDeviceStateLocked() {
+        DisplayDevice device1 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
+                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+        DisplayDevice device2 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
+                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+
+        Layout layout = new Layout();
+        layout.createDisplayLocked(device1.getDisplayDeviceInfoLocked().address,
+                /* isDefault= */ true, /* isEnabled= */ true, /* displayGroup= */ null,
+                mIdProducer, POSITION_UNKNOWN,
+                /* leadDisplayAddress= */ null,
+                /* brightnessThrottlingMapId= */ "concurrent",
+                /* refreshRateZoneId= */ null, /* refreshRateThermalThrottlingMapId= */ null);
+        layout.createDisplayLocked(device2.getDisplayDeviceInfoLocked().address,
+                /* isDefault= */ false, /* isEnabled= */ true, /* displayGroup= */ null,
+                mIdProducer, POSITION_UNKNOWN,
+                /* leadDisplayAddress= */ null,
+                /* brightnessThrottlingMapId= */ "concurrent",
+                /* refreshRateZoneId= */ null, /* refreshRateThermalThrottlingMapId= */ null);
+        when(mDeviceStateToLayoutMapSpy.get(0)).thenReturn(layout);
+
+        layout = new Layout();
+        createNonDefaultDisplay(layout, device1, /* enabled= */ false, /* group= */ null);
+        createDefaultDisplay(layout, device2);
+        when(mDeviceStateToLayoutMapSpy.get(1)).thenReturn(layout);
+        when(mDeviceStateToLayoutMapSpy.get(2)).thenReturn(layout);
+
+        when(mDeviceStateToLayoutMapSpy.size()).thenReturn(4);
+
+        LogicalDisplay display1 = add(device1);
+        assertEquals(info(display1).address, info(device1).address);
+        assertEquals(DEFAULT_DISPLAY, id(display1));
+
+        LogicalDisplay display2 = add(device2);
+        assertEquals(info(display2).address, info(device2).address);
+        // We can only have one default display
+        assertEquals(DEFAULT_DISPLAY, id(display1));
+
+        mLogicalDisplayMapper.setDeviceStateLocked(0, false);
+        advanceTime(1000);
+        // The new state is not applied until the boot is completed
+        assertTrue(mLogicalDisplayMapper.getDisplayLocked(device1).isEnabledLocked());
+        assertFalse(mLogicalDisplayMapper.getDisplayLocked(device2).isEnabledLocked());
+
+        mLogicalDisplayMapper.onBootCompleted();
+        advanceTime(1000);
+        assertTrue(mLogicalDisplayMapper.getDisplayLocked(device1).isEnabledLocked());
+        assertTrue(mLogicalDisplayMapper.getDisplayLocked(device2).isEnabledLocked());
+        assertFalse(mLogicalDisplayMapper.getDisplayLocked(device1).isInTransitionLocked());
+        assertFalse(mLogicalDisplayMapper.getDisplayLocked(device2).isInTransitionLocked());
+        assertEquals(-1, mLogicalDisplayMapper.getDisplayLocked(device1)
+                .getLeadDisplayIdLocked());
+        assertEquals(-1, mLogicalDisplayMapper.getDisplayLocked(device2)
+                .getLeadDisplayIdLocked());
+        assertEquals("concurrent", mLogicalDisplayMapper.getDisplayLocked(device1)
+                .getDisplayInfoLocked().thermalBrightnessThrottlingDataId);
+        assertEquals("concurrent", mLogicalDisplayMapper.getDisplayLocked(device2)
+                .getDisplayInfoLocked().thermalBrightnessThrottlingDataId);
+
+        mLogicalDisplayMapper.setDeviceStateLocked(1, false);
+        advanceTime(1000);
+        assertFalse(mLogicalDisplayMapper.getDisplayLocked(device1).isEnabledLocked());
+        assertTrue(mLogicalDisplayMapper.getDisplayLocked(device2).isEnabledLocked());
+        assertFalse(mLogicalDisplayMapper.getDisplayLocked(device1).isInTransitionLocked());
+        assertFalse(mLogicalDisplayMapper.getDisplayLocked(device2).isInTransitionLocked());
+        assertEquals(DisplayDeviceConfig.DEFAULT_ID,
+                mLogicalDisplayMapper.getDisplayLocked(device1)
+                        .getDisplayInfoLocked().thermalBrightnessThrottlingDataId);
+        assertEquals(DisplayDeviceConfig.DEFAULT_ID,
+                mLogicalDisplayMapper.getDisplayLocked(device2)
+                        .getDisplayInfoLocked().thermalBrightnessThrottlingDataId);
+
+        mLogicalDisplayMapper.setDeviceStateLocked(2, false);
+        advanceTime(1000);
+        assertFalse(mLogicalDisplayMapper.getDisplayLocked(device1).isEnabledLocked());
+        assertTrue(mLogicalDisplayMapper.getDisplayLocked(device2).isEnabledLocked());
+        assertFalse(mLogicalDisplayMapper.getDisplayLocked(device1).isInTransitionLocked());
+        assertFalse(mLogicalDisplayMapper.getDisplayLocked(device2).isInTransitionLocked());
+        assertEquals(DisplayDeviceConfig.DEFAULT_ID,
+                mLogicalDisplayMapper.getDisplayLocked(device1)
+                        .getDisplayInfoLocked().thermalBrightnessThrottlingDataId);
+        assertEquals(DisplayDeviceConfig.DEFAULT_ID,
+                mLogicalDisplayMapper.getDisplayLocked(device2)
+                        .getDisplayInfoLocked().thermalBrightnessThrottlingDataId);
+    }
+
+    @Test
+    public void testEnabledAndDisabledDisplays() {
+        DisplayAddress displayAddressOne = new TestUtils.TestDisplayAddress();
+        DisplayAddress displayAddressTwo = new TestUtils.TestDisplayAddress();
+        DisplayAddress displayAddressThree = new TestUtils.TestDisplayAddress();
+
+        TestDisplayDevice device1 = createDisplayDevice(displayAddressOne, "one",
+                TYPE_INTERNAL, 600, 800, DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+        TestDisplayDevice device2 = createDisplayDevice(displayAddressTwo, "two",
+                TYPE_INTERNAL, 200, 800, DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP);
+        TestDisplayDevice device3 = createDisplayDevice(displayAddressThree, "three",
+                TYPE_INTERNAL, 600, 900, DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP);
+
+        Layout threeDevicesEnabledLayout = new Layout();
+        createDefaultDisplay(threeDevicesEnabledLayout, displayAddressOne);
+        createNonDefaultDisplay(threeDevicesEnabledLayout, displayAddressTwo,
+                /* enabled= */ true, /* group= */ null);
+        createNonDefaultDisplay(threeDevicesEnabledLayout, displayAddressThree,
+                /* enabled= */ true, /* group= */ null);
+
+        when(mDeviceStateToLayoutMapSpy.get(STATE_DEFAULT))
+                .thenReturn(threeDevicesEnabledLayout);
+
+        LogicalDisplay display1 = add(device1);
+        LogicalDisplay display2 = add(device2);
+        LogicalDisplay display3 = add(device3);
+
+        // ensure 3 displays are returned
+        int [] ids = mLogicalDisplayMapper.getDisplayIdsLocked(Process.SYSTEM_UID, false);
+        assertEquals(3, ids.length);
+        Arrays.sort(ids);
+        assertEquals(DEFAULT_DISPLAY, ids[0]);
+        assertNotNull(mLogicalDisplayMapper.getDisplayLocked(device1,
+                /* includeDisabled= */ false));
+        assertNotNull(mLogicalDisplayMapper.getDisplayLocked(device2,
+                /* includeDisabled= */ false));
+        assertNotNull(mLogicalDisplayMapper.getDisplayLocked(device3,
+                /* includeDisabled= */ false));
+        assertNotNull(mLogicalDisplayMapper.getDisplayLocked(
+                threeDevicesEnabledLayout.getByAddress(displayAddressOne).getLogicalDisplayId(),
+                /* includeDisabled= */ false));
+        assertNotNull(mLogicalDisplayMapper.getDisplayLocked(
+                threeDevicesEnabledLayout.getByAddress(displayAddressTwo).getLogicalDisplayId(),
+                /* includeDisabled= */ false));
+        assertNotNull(mLogicalDisplayMapper.getDisplayLocked(
+                threeDevicesEnabledLayout.getByAddress(displayAddressThree).getLogicalDisplayId(),
+                /* includeDisabled= */ false));
+
+        Layout oneDeviceEnabledLayout = new Layout();
+        createDefaultDisplay(oneDeviceEnabledLayout, displayAddressOne);
+        createNonDefaultDisplay(oneDeviceEnabledLayout, displayAddressTwo,
+                /* enabled= */ false, /* group= */ null);
+        createNonDefaultDisplay(oneDeviceEnabledLayout, displayAddressThree,
+                /* enabled= */ false, /* group= */ null);
+
+        when(mDeviceStateToLayoutMapSpy.get(0)).thenReturn(oneDeviceEnabledLayout);
+        when(mDeviceStateToLayoutMapSpy.get(1)).thenReturn(threeDevicesEnabledLayout);
+
+        // 1) Set the new state
+        // 2) Mark the displays as STATE_OFF so that it can continue with transition
+        // 3) Send DISPLAY_DEVICE_EVENT_CHANGE to inform the mapper of the new display state
+        // 4) Dispatch handler events.
+        mLogicalDisplayMapper.onBootCompleted();
+        mLogicalDisplayMapper.setDeviceStateLocked(0, false);
+        mDisplayDeviceRepo.onDisplayDeviceEvent(device3, DISPLAY_DEVICE_EVENT_CHANGED);
+        advanceTime(1000);
+        final int[] allDisplayIds = mLogicalDisplayMapper.getDisplayIdsLocked(
+                Process.SYSTEM_UID, false);
+        if (allDisplayIds.length != 1) {
+            throw new RuntimeException("Displays: \n"
+                    + mLogicalDisplayMapper.getDisplayLocked(device1).toString()
+                    + "\n" + mLogicalDisplayMapper.getDisplayLocked(device2).toString()
+                    + "\n" + mLogicalDisplayMapper.getDisplayLocked(device3).toString());
+        }
+        // ensure only one display is returned
+        assertEquals(1, allDisplayIds.length);
+        assertNotNull(mLogicalDisplayMapper.getDisplayLocked(device1,
+                /* includeDisabled= */ false));
+        assertNull(mLogicalDisplayMapper.getDisplayLocked(device2,
+                /* includeDisabled= */ false));
+        assertNull(mLogicalDisplayMapper.getDisplayLocked(device3,
+                /* includeDisabled= */ false));
+        assertNotNull(mLogicalDisplayMapper.getDisplayLocked(
+                oneDeviceEnabledLayout.getByAddress(displayAddressOne).getLogicalDisplayId(),
+                /* includeDisabled= */ false));
+        assertNull(mLogicalDisplayMapper.getDisplayLocked(
+                oneDeviceEnabledLayout.getByAddress(displayAddressTwo).getLogicalDisplayId(),
+                /* includeDisabled= */ false));
+        assertNull(mLogicalDisplayMapper.getDisplayLocked(
+                oneDeviceEnabledLayout.getByAddress(displayAddressThree).getLogicalDisplayId(),
+                /* includeDisabled= */ false));
+
+        // Now do it again to go back to state 1
+        mLogicalDisplayMapper.setDeviceStateLocked(1, false);
+        mDisplayDeviceRepo.onDisplayDeviceEvent(device3, DISPLAY_DEVICE_EVENT_CHANGED);
+        advanceTime(1000);
+        final int[] threeDisplaysEnabled = mLogicalDisplayMapper.getDisplayIdsLocked(
+                Process.SYSTEM_UID, false);
+
+        // ensure all three displays are returned
+        assertEquals(3, threeDisplaysEnabled.length);
+    }
+
+    @Test
+    public void testCreateNewLogicalDisplay() {
+        DisplayDevice device1 = createDisplayDevice(TYPE_EXTERNAL, 600, 800,
+                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+        when(mDeviceStateToLayoutMapSpy.size()).thenReturn(1);
+        LogicalDisplay display1 = add(device1);
+
+        assertTrue(display1.isEnabledLocked());
+
+        DisplayDevice device2 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
+                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+        when(mDeviceStateToLayoutMapSpy.size()).thenReturn(2);
+        LogicalDisplay display2 = add(device2);
+
+        assertFalse(display2.isEnabledLocked());
+    }
+    @Test
+    public void testDisplayFlagRear() {
+        DisplayDevice device1 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
+                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+        DisplayDevice device2 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
+                FLAG_REAR);
+
+        Layout layout = new Layout();
+        layout.createDefaultDisplayLocked(device1.getDisplayDeviceInfoLocked().address,
+                mIdProducer);
+        layout.createDisplayLocked(device2.getDisplayDeviceInfoLocked().address,
+                /* isDefault= */ false, /* isEnabled= */ true, /* displayGroupName= */ null,
+                mIdProducer, POSITION_REAR, /* leadDisplayAddress= */ null,
+                /* brightnessThrottlingMapId= */ null, /* refreshRateZoneId= */ null,
+                /* refreshRateThermalThrottlingMapId= */null);
+        when(mDeviceStateToLayoutMapSpy.get(0)).thenReturn(layout);
+
+        when(mDeviceStateToLayoutMapSpy.size()).thenReturn(1);
+
+        LogicalDisplay display1 = add(device1);
+        assertEquals(info(display1).address, info(device1).address);
+        assertEquals(DEFAULT_DISPLAY, id(display1));
+
+        LogicalDisplay display2 = add(device2);
+        assertEquals(info(display2).address, info(device2).address);
+        // We can only have one default display
+        assertEquals(DEFAULT_DISPLAY, id(display1));
+
+        mLogicalDisplayMapper.setDeviceStateLocked(0, false);
+        advanceTime(1000);
+        mLogicalDisplayMapper.onBootCompleted();
+        advanceTime(1000);
+
+        assertTrue(mLogicalDisplayMapper.getDisplayLocked(device1).isEnabledLocked());
+        assertTrue(mLogicalDisplayMapper.getDisplayLocked(device2).isEnabledLocked());
+
+        assertEquals(POSITION_UNKNOWN,
+                mLogicalDisplayMapper.getDisplayLocked(device1).getDevicePositionLocked());
+        assertEquals(POSITION_REAR,
+                mLogicalDisplayMapper.getDisplayLocked(device2).getDevicePositionLocked());
+    }
+
+    /////////////////
+    // Helper Methods
+    /////////////////
+
+    private void createDefaultDisplay(Layout layout, DisplayDevice device) {
+        createDefaultDisplay(layout, info(device).address);
+    }
+
+    private void createDefaultDisplay(Layout layout, DisplayAddress address) {
+        layout.createDefaultDisplayLocked(address, mIdProducer);
+    }
+
+    private void createNonDefaultDisplay(Layout layout, DisplayDevice device, boolean enabled,
+            String group) {
+        createNonDefaultDisplay(layout, info(device).address, enabled, group);
+    }
+
+    private void createNonDefaultDisplay(Layout layout, DisplayAddress address, boolean enabled,
+            String group) {
+        layout.createDisplayLocked(address, /* isDefault= */ false, enabled, group, mIdProducer,
+                Layout.Display.POSITION_UNKNOWN, /* leadDisplayAddress= */ null,
+                /* brightnessThrottlingMapId= */ null, /* refreshRateZoneId= */ null,
+                /* refreshRateThermalThrottlingMapId= */ null);
+    }
+
+    private void advanceTime(long timeMs) {
+        mLooper.moveTimeForward(1000);
+        mLooper.dispatchAll();
+    }
+
+    private TestDisplayDevice createDisplayDevice(int type, int width, int height, int flags) {
+        return createDisplayDevice(
+                new TestUtils.TestDisplayAddress(), /*  uniqueId */ "", type, width, height, flags);
+    }
+
+    private TestDisplayDevice createDisplayDevice(
+            int type, String uniqueId, int width, int height, int flags) {
+        return createDisplayDevice(
+                new TestUtils.TestDisplayAddress(), uniqueId, type, width, height, flags);
+    }
+
+    private TestDisplayDevice createDisplayDevice(
+            DisplayAddress address, String uniqueId, int type, int width, int height, int flags) {
+        TestDisplayDevice device = new TestDisplayDevice();
+        DisplayDeviceInfo displayDeviceInfo = device.getSourceInfo();
+        displayDeviceInfo.type = type;
+        displayDeviceInfo.uniqueId = uniqueId;
+        displayDeviceInfo.width = width;
+        displayDeviceInfo.height = height;
+        displayDeviceInfo.flags = flags;
+        displayDeviceInfo.supportedModes = new Display.Mode[1];
+        displayDeviceInfo.supportedModes[0] = new Display.Mode(1, width, height, 60f);
+        displayDeviceInfo.modeId = 1;
+        displayDeviceInfo.address = address;
+        return device;
+    }
+
+    private DisplayDeviceInfo info(DisplayDevice device) {
+        return device.getDisplayDeviceInfoLocked();
+    }
+
+    private int width(DisplayDevice device) {
+        return info(device).width;
+    }
+
+    private int height(DisplayDevice device) {
+        return info(device).height;
+    }
+
+    private DisplayInfo info(LogicalDisplay display) {
+        return display.getDisplayInfoLocked();
+    }
+
+    private int id(LogicalDisplay display) {
+        return display.getDisplayIdLocked();
+    }
+
+    private LogicalDisplay add(DisplayDevice device) {
+        mDisplayDeviceRepo.onDisplayDeviceEvent(device, DISPLAY_DEVICE_EVENT_ADDED);
+        ArgumentCaptor<LogicalDisplay> displayCaptor =
+                ArgumentCaptor.forClass(LogicalDisplay.class);
+        verify(mListenerMock).onLogicalDisplayEventLocked(
+                displayCaptor.capture(), eq(LOGICAL_DISPLAY_EVENT_ADDED));
+        clearInvocations(mListenerMock);
+        return displayCaptor.getValue();
+    }
+
+    private void testDisplayDeviceAddAndRemove_NonInternal(int type) {
+        DisplayDevice device = createDisplayDevice(type, 600, 800, 0);
+
+        // add
+        LogicalDisplay displayAdded = add(device);
+        assertEquals(info(displayAdded).address, info(device).address);
+        assertNotEquals(DEFAULT_DISPLAY, id(displayAdded));
+
+        // remove
+        mDisplayDeviceRepo.onDisplayDeviceEvent(device, DISPLAY_DEVICE_EVENT_REMOVED);
+        verify(mListenerMock).onLogicalDisplayEventLocked(
+                mDisplayCaptor.capture(), eq(LOGICAL_DISPLAY_EVENT_REMOVED));
+        LogicalDisplay displayRemoved = mDisplayCaptor.getValue();
+        assertNotEquals(DEFAULT_DISPLAY, id(displayRemoved));
+    }
+
+    class TestDisplayDevice extends DisplayDevice {
+        private DisplayDeviceInfo mInfo;
+        private DisplayDeviceInfo mSentInfo;
+        private int mState;
+
+        TestDisplayDevice() {
+            super(null, null, "test_display_" + sUniqueTestDisplayId++, mContextMock);
+            mInfo = new DisplayDeviceInfo();
+        }
+
+        @Override
+        public DisplayDeviceInfo getDisplayDeviceInfoLocked() {
+            if (mSentInfo == null) {
+                mSentInfo = new DisplayDeviceInfo();
+                mSentInfo.copyFrom(mInfo);
+            }
+            return mSentInfo;
+        }
+
+        @Override
+        public void applyPendingDisplayDeviceInfoChangesLocked() {
+            mSentInfo = null;
+        }
+
+        @Override
+        public boolean hasStableUniqueId() {
+            return true;
+        }
+
+        public DisplayDeviceInfo getSourceInfo() {
+            return mInfo;
+        }
+    }
+}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java
new file mode 100644
index 0000000..2065479
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java
@@ -0,0 +1,290 @@
+/*
+ * 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.
+ */
+
+package com.android.server.display;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.PropertyInvalidatedCache;
+import android.graphics.Point;
+import android.util.SparseArray;
+import android.view.Display;
+import android.view.DisplayInfo;
+import android.view.Surface;
+import android.view.SurfaceControl;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.display.layout.Layout;
+
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+
+@SmallTest
+public class LogicalDisplayTest {
+    private static final int DISPLAY_ID = 0;
+    private static final int LAYER_STACK = 0;
+    private static final int DISPLAY_WIDTH = 100;
+    private static final int DISPLAY_HEIGHT = 200;
+    private static final int MODE_ID = 1;
+
+    private LogicalDisplay mLogicalDisplay;
+    private DisplayDevice mDisplayDevice;
+    private DisplayDeviceRepository mDeviceRepo;
+    private final DisplayDeviceInfo mDisplayDeviceInfo = new DisplayDeviceInfo();
+
+    @Before
+    public void setUp() {
+        // Share classloader to allow package private access.
+        System.setProperty("dexmaker.share_classloader", "true");
+        mDisplayDevice = mock(DisplayDevice.class);
+        mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice);
+
+        mDisplayDeviceInfo.copyFrom(new DisplayDeviceInfo());
+        mDisplayDeviceInfo.width = DISPLAY_WIDTH;
+        mDisplayDeviceInfo.height = DISPLAY_HEIGHT;
+        mDisplayDeviceInfo.flags = DisplayDeviceInfo.FLAG_ROTATES_WITH_CONTENT;
+        mDisplayDeviceInfo.touch = DisplayDeviceInfo.TOUCH_INTERNAL;
+        mDisplayDeviceInfo.modeId = MODE_ID;
+        mDisplayDeviceInfo.supportedModes = new Display.Mode[] {new Display.Mode(MODE_ID,
+                DISPLAY_WIDTH, DISPLAY_HEIGHT, /* refreshRate= */ 60)};
+        when(mDisplayDevice.getDisplayDeviceInfoLocked()).thenReturn(mDisplayDeviceInfo);
+
+        // Disable binder caches in this process.
+        PropertyInvalidatedCache.disableForTestMode();
+
+        mDeviceRepo = new DisplayDeviceRepository(
+                new DisplayManagerService.SyncRoot(),
+                new PersistentDataStore(new PersistentDataStore.Injector() {
+                    @Override
+                    public InputStream openRead() {
+                        return null;
+                    }
+
+                    @Override
+                    public OutputStream startWrite() {
+                        return null;
+                    }
+
+                    @Override
+                    public void finishWrite(OutputStream os, boolean success) {}
+                }));
+        mDeviceRepo.onDisplayDeviceEvent(mDisplayDevice, DisplayAdapter.DISPLAY_DEVICE_EVENT_ADDED);
+        mLogicalDisplay.updateLocked(mDeviceRepo);
+    }
+
+    @Test
+    public void testGetDisplayPosition() {
+        Point expectedPosition = new Point();
+
+        SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
+        mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
+        assertEquals(expectedPosition, mLogicalDisplay.getDisplayPosition());
+
+        expectedPosition.set(20, 40);
+        mLogicalDisplay.setDisplayOffsetsLocked(20, 40);
+        mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
+        assertEquals(expectedPosition, mLogicalDisplay.getDisplayPosition());
+
+        expectedPosition.set(40, -20);
+        DisplayInfo displayInfo = new DisplayInfo();
+        displayInfo.logicalWidth = DISPLAY_HEIGHT;
+        displayInfo.logicalHeight = DISPLAY_WIDTH;
+        displayInfo.rotation = Surface.ROTATION_90;
+        mLogicalDisplay.setDisplayInfoOverrideFromWindowManagerLocked(displayInfo);
+        mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
+        assertEquals(expectedPosition, mLogicalDisplay.getDisplayPosition());
+    }
+
+    // TODO: b/288880734 - fix test after display tests migration
+    @Test
+    @Ignore
+    public void testDisplayInputFlags() {
+        SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
+        mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
+        verify(t).setDisplayFlags(any(), eq(SurfaceControl.DISPLAY_RECEIVES_INPUT));
+        reset(t);
+
+        mDisplayDeviceInfo.touch = DisplayDeviceInfo.TOUCH_NONE;
+        mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
+        verify(t).setDisplayFlags(any(), eq(0));
+        reset(t);
+
+        mDisplayDeviceInfo.touch = DisplayDeviceInfo.TOUCH_VIRTUAL;
+        mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
+        verify(t).setDisplayFlags(any(), eq(SurfaceControl.DISPLAY_RECEIVES_INPUT));
+        reset(t);
+
+        mLogicalDisplay.setEnabledLocked(false);
+        mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
+        verify(t).setDisplayFlags(any(), eq(0));
+        reset(t);
+
+        mLogicalDisplay.setEnabledLocked(true);
+        mDisplayDeviceInfo.touch = DisplayDeviceInfo.TOUCH_EXTERNAL;
+        mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
+        verify(t).setDisplayFlags(any(), eq(SurfaceControl.DISPLAY_RECEIVES_INPUT));
+        reset(t);
+    }
+
+    @Test
+    public void testRearDisplaysArePresentationDisplaysThatDestroyContentOnRemoval() {
+        // Assert that the display isn't a presentation display by default, with a default remove
+        // mode
+        assertEquals(0, mLogicalDisplay.getDisplayInfoLocked().flags);
+        assertEquals(Display.REMOVE_MODE_MOVE_CONTENT_TO_PRIMARY,
+                mLogicalDisplay.getDisplayInfoLocked().removeMode);
+
+        // Update position and test to see that it's been updated to a rear, presentation display
+        // that destroys content on removal
+        mLogicalDisplay.setDevicePositionLocked(Layout.Display.POSITION_REAR);
+        mLogicalDisplay.updateLocked(mDeviceRepo);
+        assertEquals(Display.FLAG_REAR | Display.FLAG_PRESENTATION,
+                mLogicalDisplay.getDisplayInfoLocked().flags);
+        assertEquals(Display.REMOVE_MODE_DESTROY_CONTENT,
+                mLogicalDisplay.getDisplayInfoLocked().removeMode);
+
+        // And then check the unsetting the position resets both
+        mLogicalDisplay.setDevicePositionLocked(Layout.Display.POSITION_UNKNOWN);
+        mLogicalDisplay.updateLocked(mDeviceRepo);
+        assertEquals(0, mLogicalDisplay.getDisplayInfoLocked().flags);
+        assertEquals(Display.REMOVE_MODE_MOVE_CONTENT_TO_PRIMARY,
+                mLogicalDisplay.getDisplayInfoLocked().removeMode);
+    }
+
+    @Test
+    public void testUpdateLayoutLimitedRefreshRate() {
+        SurfaceControl.RefreshRateRange layoutLimitedRefreshRate =
+                new SurfaceControl.RefreshRateRange(0, 120);
+        DisplayInfo info1 = mLogicalDisplay.getDisplayInfoLocked();
+        mLogicalDisplay.updateLayoutLimitedRefreshRateLocked(layoutLimitedRefreshRate);
+        DisplayInfo info2 = mLogicalDisplay.getDisplayInfoLocked();
+        // Display info should only be updated when updateLocked is called
+        assertEquals(info2, info1);
+
+        mLogicalDisplay.updateLocked(mDeviceRepo);
+        DisplayInfo info3 = mLogicalDisplay.getDisplayInfoLocked();
+        assertNotEquals(info3, info2);
+        assertEquals(layoutLimitedRefreshRate, info3.layoutLimitedRefreshRate);
+    }
+
+    @Test
+    public void testUpdateLayoutLimitedRefreshRate_setsDirtyFlag() {
+        SurfaceControl.RefreshRateRange layoutLimitedRefreshRate =
+                new SurfaceControl.RefreshRateRange(0, 120);
+        assertFalse(mLogicalDisplay.isDirtyLocked());
+
+        mLogicalDisplay.updateLayoutLimitedRefreshRateLocked(layoutLimitedRefreshRate);
+        assertTrue(mLogicalDisplay.isDirtyLocked());
+
+        mLogicalDisplay.updateLocked(mDeviceRepo);
+        assertFalse(mLogicalDisplay.isDirtyLocked());
+    }
+
+    @Test
+    public void testUpdateRefreshRateThermalThrottling() {
+        SparseArray<SurfaceControl.RefreshRateRange> refreshRanges = new SparseArray<>();
+        refreshRanges.put(0, new SurfaceControl.RefreshRateRange(0, 120));
+        DisplayInfo info1 = mLogicalDisplay.getDisplayInfoLocked();
+        mLogicalDisplay.updateThermalRefreshRateThrottling(refreshRanges);
+        DisplayInfo info2 = mLogicalDisplay.getDisplayInfoLocked();
+        // Display info should only be updated when updateLocked is called
+        assertEquals(info2, info1);
+
+        mLogicalDisplay.updateLocked(mDeviceRepo);
+        DisplayInfo info3 = mLogicalDisplay.getDisplayInfoLocked();
+        assertNotEquals(info3, info2);
+        assertTrue(refreshRanges.contentEquals(info3.thermalRefreshRateThrottling));
+    }
+
+    @Test
+    public void testUpdateRefreshRateThermalThrottling_setsDirtyFlag() {
+        SparseArray<SurfaceControl.RefreshRateRange> refreshRanges = new SparseArray<>();
+        refreshRanges.put(0, new SurfaceControl.RefreshRateRange(0, 120));
+        assertFalse(mLogicalDisplay.isDirtyLocked());
+
+        mLogicalDisplay.updateThermalRefreshRateThrottling(refreshRanges);
+        assertTrue(mLogicalDisplay.isDirtyLocked());
+
+        mLogicalDisplay.updateLocked(mDeviceRepo);
+        assertFalse(mLogicalDisplay.isDirtyLocked());
+    }
+
+    @Test
+    public void testUpdateDisplayGroupIdLocked() {
+        int newId = 999;
+        DisplayInfo info1 = mLogicalDisplay.getDisplayInfoLocked();
+        mLogicalDisplay.updateDisplayGroupIdLocked(newId);
+        DisplayInfo info2 = mLogicalDisplay.getDisplayInfoLocked();
+        // Display info should only be updated when updateLocked is called
+        assertEquals(info2, info1);
+
+        mLogicalDisplay.updateLocked(mDeviceRepo);
+        DisplayInfo info3 = mLogicalDisplay.getDisplayInfoLocked();
+        assertNotEquals(info3, info2);
+        assertEquals(newId, info3.displayGroupId);
+    }
+
+    @Test
+    public void testUpdateDisplayGroupIdLocked_setsDirtyFlag() {
+        assertFalse(mLogicalDisplay.isDirtyLocked());
+
+        mLogicalDisplay.updateDisplayGroupIdLocked(99);
+        assertTrue(mLogicalDisplay.isDirtyLocked());
+
+        mLogicalDisplay.updateLocked(mDeviceRepo);
+        assertFalse(mLogicalDisplay.isDirtyLocked());
+    }
+
+    @Test
+    public void testSetThermalBrightnessThrottlingDataId() {
+        String brightnessThrottlingDataId = "throttling_data_id";
+        DisplayInfo info1 = mLogicalDisplay.getDisplayInfoLocked();
+        mLogicalDisplay.setThermalBrightnessThrottlingDataIdLocked(brightnessThrottlingDataId);
+        DisplayInfo info2 = mLogicalDisplay.getDisplayInfoLocked();
+        // Display info should only be updated when updateLocked is called
+        assertEquals(info2, info1);
+
+        mLogicalDisplay.updateLocked(mDeviceRepo);
+        DisplayInfo info3 = mLogicalDisplay.getDisplayInfoLocked();
+        assertNotEquals(info3, info2);
+        assertEquals(brightnessThrottlingDataId, info3.thermalBrightnessThrottlingDataId);
+    }
+
+    @Test
+    public void testSetThermalBrightnessThrottlingDataId_setsDirtyFlag() {
+        assertFalse(mLogicalDisplay.isDirtyLocked());
+
+        mLogicalDisplay.setThermalBrightnessThrottlingDataIdLocked("99");
+        assertTrue(mLogicalDisplay.isDirtyLocked());
+
+        mLogicalDisplay.updateLocked(mDeviceRepo);
+        assertFalse(mLogicalDisplay.isDirtyLocked());
+    }
+}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/NormalBrightnessModeControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/NormalBrightnessModeControllerTest.java
new file mode 100644
index 0000000..c379d6b
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/NormalBrightnessModeControllerTest.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display;
+
+import static org.junit.Assert.assertEquals;
+
+import android.os.PowerManager;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.annotations.Keep;
+import com.android.server.display.DisplayDeviceConfig.BrightnessLimitMapType;
+
+import com.google.common.collect.ImmutableMap;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
+@SmallTest
+@RunWith(JUnitParamsRunner.class)
+public class NormalBrightnessModeControllerTest {
+    private static final float FLOAT_TOLERANCE = 0.001f;
+
+    private final NormalBrightnessModeController mController = new NormalBrightnessModeController();
+
+    @Keep
+    private static Object[][] brightnessData() {
+        return new Object[][]{
+                // no brightness config
+                {0, AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED, new HashMap<>(),
+                        PowerManager.BRIGHTNESS_MAX},
+                {0, AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED, new HashMap<>(),
+                        PowerManager.BRIGHTNESS_MAX},
+                {100, AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED, new HashMap<>(),
+                        PowerManager.BRIGHTNESS_MAX},
+                // Auto brightness - on, config only for default
+                {100, AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED, ImmutableMap.of(
+                        BrightnessLimitMapType.DEFAULT,
+                        ImmutableMap.of(99f, 0.1f, 101f, 0.2f)
+                ), 0.2f},
+                // Auto brightness - off, config only for default
+                {100, AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED, ImmutableMap.of(
+                        BrightnessLimitMapType.DEFAULT,
+                        ImmutableMap.of(99f, 0.1f, 101f, 0.2f)
+                ), 0.2f},
+                // Auto brightness - off, config only for adaptive
+                {100, AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED, ImmutableMap.of(
+                        BrightnessLimitMapType.ADAPTIVE,
+                        ImmutableMap.of(99f, 0.1f, 101f, 0.2f)
+                ), PowerManager.BRIGHTNESS_MAX},
+                // Auto brightness - on, config only for adaptive
+                {100, AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED, ImmutableMap.of(
+                        BrightnessLimitMapType.ADAPTIVE,
+                        ImmutableMap.of(99f, 0.1f, 101f, 0.2f)
+                ), 0.2f},
+                // Auto brightness - on, config for both
+                {100, AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED, ImmutableMap.of(
+                        BrightnessLimitMapType.DEFAULT,
+                        ImmutableMap.of(99f, 0.1f, 101f, 0.2f),
+                        BrightnessLimitMapType.ADAPTIVE,
+                        ImmutableMap.of(99f, 0.3f, 101f, 0.4f)
+                ), 0.4f},
+                // Auto brightness - off, config for both
+                {100, AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED, ImmutableMap.of(
+                        BrightnessLimitMapType.DEFAULT,
+                        ImmutableMap.of(99f, 0.1f, 101f, 0.2f),
+                        BrightnessLimitMapType.ADAPTIVE,
+                        ImmutableMap.of(99f, 0.3f, 101f, 0.4f)
+                ), 0.2f},
+                // Auto brightness - on, config for both, ambient high
+                {1000, AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED, ImmutableMap.of(
+                        BrightnessLimitMapType.DEFAULT,
+                        ImmutableMap.of(1000f, 0.1f, 2000f, 0.2f),
+                        BrightnessLimitMapType.ADAPTIVE,
+                        ImmutableMap.of(99f, 0.3f, 101f, 0.4f)
+                ), PowerManager.BRIGHTNESS_MAX},
+        };
+    }
+
+    @Test
+    @Parameters(method = "brightnessData")
+    public void testReturnsCorrectMaxBrightness(float ambientLux, int autoBrightnessState,
+            Map<BrightnessLimitMapType, Map<Float, Float>> maxBrightnessConfig,
+            float expectedBrightness) {
+        setupController(ambientLux, autoBrightnessState, maxBrightnessConfig);
+
+        assertEquals(expectedBrightness, mController.getCurrentBrightnessMax(), FLOAT_TOLERANCE);
+    }
+
+    private void setupController(float ambientLux, int autoBrightnessState,
+            Map<BrightnessLimitMapType, Map<Float, Float>> maxBrightnessConfig) {
+        mController.onAmbientLuxChange(ambientLux);
+        mController.setAutoBrightnessState(autoBrightnessState);
+        mController.resetNbmData(maxBrightnessConfig);
+    }
+}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/PersistentDataStoreTest.java b/services/tests/displayservicetests/src/com/android/server/display/PersistentDataStoreTest.java
new file mode 100644
index 0000000..9f91916
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/PersistentDataStoreTest.java
@@ -0,0 +1,570 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.content.Context;
+import android.hardware.display.BrightnessConfiguration;
+import android.os.Handler;
+import android.os.test.TestLooper;
+import android.util.Pair;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class PersistentDataStoreTest {
+    private PersistentDataStore mDataStore;
+    private TestInjector mInjector;
+    private TestLooper mTestLooper;
+
+    @Before
+    public void setUp() {
+        mInjector = new TestInjector();
+        mTestLooper = new TestLooper();
+        Handler handler = new Handler(mTestLooper.getLooper());
+        mDataStore = new PersistentDataStore(mInjector, handler);
+    }
+
+    @Test
+    public void testLoadBrightness() {
+        final String uniqueDisplayId = "test:123";
+        final DisplayDevice testDisplayDevice = new DisplayDevice(
+                null, null, uniqueDisplayId, null) {
+            @Override
+            public boolean hasStableUniqueId() {
+                return true;
+            }
+
+            @Override
+            public DisplayDeviceInfo getDisplayDeviceInfoLocked() {
+                return null;
+            }
+        };
+
+        String contents = "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
+                + "<display-manager-state>\n"
+                + "  <display-states>\n"
+                + "    <display unique-id=\"test:123\">\n"
+                + "      <brightness-value user-serial=\"1\">0.1</brightness-value>\n"
+                + "      <brightness-value user-serial=\"2\">0.2</brightness-value>\n"
+                + "    </display>\n"
+                + "  </display-states>\n"
+                + "</display-manager-state>\n";
+
+        InputStream is = new ByteArrayInputStream(contents.getBytes(StandardCharsets.UTF_8));
+        mInjector.setReadStream(is);
+        mDataStore.loadIfNeeded();
+
+        float brightness = mDataStore.getBrightness(testDisplayDevice, 1);
+        assertEquals(0.1, brightness, 0.01);
+
+        brightness = mDataStore.getBrightness(testDisplayDevice, 2);
+        assertEquals(0.2, brightness, 0.01);
+    }
+
+    @Test
+    public void testSetBrightness_brightnessTagWithNoUserId_updatesToBrightnessTagWithUserId() {
+        final String uniqueDisplayId = "test:123";
+        final DisplayDevice testDisplayDevice =
+                new DisplayDevice(null, null, uniqueDisplayId, null) {
+            @Override
+            public boolean hasStableUniqueId() {
+                return true;
+            }
+
+            @Override
+            public DisplayDeviceInfo getDisplayDeviceInfoLocked() {
+                return null;
+            }
+        };
+
+        String contents = "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
+                + "<display-manager-state>\n"
+                + "  <display-states>\n"
+                + "    <color-mode>0</color-mode>\n"
+                + "    <display unique-id=\"test:123\">\n"
+                + "      <brightness-value>0.5</brightness-value>\n"
+                + "    </display>\n"
+                + "  </display-states>\n"
+                + "</display-manager-state>\n";
+
+        InputStream is = new ByteArrayInputStream(contents.getBytes(StandardCharsets.UTF_8));
+        mInjector.setReadStream(is);
+        mDataStore.loadIfNeeded();
+
+        float user1Brightness = mDataStore.getBrightness(testDisplayDevice, 1 /* userSerial */);
+        float user2Brightness = mDataStore.getBrightness(testDisplayDevice, 2 /* userSerial */);
+        assertEquals(0.5, user1Brightness, 0.01);
+        assertEquals(0.5, user2Brightness, 0.01);
+
+        // Override the value for user 2. Default user must have been removed.
+        mDataStore.setBrightness(testDisplayDevice, 0.2f, 2 /* userSerial */  /* brightness*/);
+
+        user1Brightness = mDataStore.getBrightness(testDisplayDevice, 1 /* userSerial */);
+        user2Brightness = mDataStore.getBrightness(testDisplayDevice, 2 /* userSerial */);
+        assertTrue(Float.isNaN(user1Brightness));
+        assertEquals(0.2f, user2Brightness, 0.01);
+
+        // Override the value for user 1. User-specific brightness values should co-exist.
+        mDataStore.setBrightness(testDisplayDevice, 0.1f, 1 /* userSerial */  /* brightness*/);
+        user1Brightness = mDataStore.getBrightness(testDisplayDevice, 1 /* userSerial */);
+        user2Brightness = mDataStore.getBrightness(testDisplayDevice, 2 /* userSerial */);
+        assertEquals(0.1f, user1Brightness, 0.01);
+        assertEquals(0.2f, user2Brightness, 0.01);
+
+        // Validate saveIfNeeded writes user-specific brightnes.
+        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        mInjector.setWriteStream(baos);
+        mDataStore.saveIfNeeded();
+        mTestLooper.dispatchAll();
+        assertTrue(mInjector.wasWriteSuccessful());
+        TestInjector newInjector = new TestInjector();
+        PersistentDataStore newDataStore = new PersistentDataStore(newInjector);
+        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+        newInjector.setReadStream(bais);
+        newDataStore.loadIfNeeded();
+
+        user1Brightness = newDataStore.getBrightness(testDisplayDevice, 1 /* userSerial */);
+        user2Brightness = newDataStore.getBrightness(testDisplayDevice, 2 /* userSerial */);
+        float unknownUserBrightness =
+                newDataStore.getBrightness(testDisplayDevice, 999 /* userSerial */);
+        assertEquals(0.1f, user1Brightness, 0.01);
+        assertEquals(0.2f, user2Brightness, 0.01);
+        assertTrue(Float.isNaN(unknownUserBrightness));
+    }
+
+    @Test
+    public void testLoadingBrightnessConfigurations() {
+        String contents = "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
+                + "<display-manager-state>\n"
+                + "  <brightness-configurations>\n"
+                + "    <brightness-configuration"
+                + "         user-serial=\"1\""
+                + "         package-name=\"example.com\""
+                + "         timestamp=\"123456\">\n"
+                + "      <brightness-curve description=\"something\">\n"
+                + "        <brightness-point lux=\"0\" nits=\"13.25\"/>\n"
+                + "        <brightness-point lux=\"25\" nits=\"35.94\"/>\n"
+                + "      </brightness-curve>\n"
+                + "    </brightness-configuration>\n"
+                + "    <brightness-configuration user-serial=\"3\">\n"
+                + "      <brightness-curve>\n"
+                + "        <brightness-point lux=\"0\" nits=\"13.25\"/>\n"
+                + "        <brightness-point lux=\"10.2\" nits=\"15\"/>\n"
+                + "      </brightness-curve>\n"
+                + "    </brightness-configuration>\n"
+                + "  </brightness-configurations>\n"
+                + "</display-manager-state>\n";
+        InputStream is = new ByteArrayInputStream(contents.getBytes(StandardCharsets.UTF_8));
+        mInjector.setReadStream(is);
+        mDataStore.loadIfNeeded();
+        BrightnessConfiguration config = mDataStore.getBrightnessConfiguration(1 /*userSerial*/);
+        Pair<float[], float[]> curve = config.getCurve();
+        float[] expectedLux = { 0f, 25f };
+        float[] expectedNits = { 13.25f, 35.94f };
+        assertArrayEquals(expectedLux, curve.first, "lux");
+        assertArrayEquals(expectedNits, curve.second, "nits");
+        assertEquals("something", config.getDescription());
+
+        config = mDataStore.getBrightnessConfiguration(3 /*userSerial*/);
+        curve = config.getCurve();
+        expectedLux = new float[] { 0f, 10.2f };
+        expectedNits = new float[] { 13.25f, 15f };
+        assertArrayEquals(expectedLux, curve.first, "lux");
+        assertArrayEquals(expectedNits, curve.second, "nits");
+        assertNull(config.getDescription());
+    }
+
+    @Test
+    public void testBrightnessConfigWithInvalidCurveIsIgnored() {
+        String contents = "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
+                + "<display-manager-state>\n"
+                + "  <brightness-configurations>\n"
+                + "    <brightness-configuration user-serial=\"0\">\n"
+                + "      <brightness-curve>\n"
+                + "        <brightness-point lux=\"1\" nits=\"13.25\"/>\n"
+                + "        <brightness-point lux=\"25\" nits=\"35.94\"/>\n"
+                + "      </brightness-curve>\n"
+                + "    </brightness-configuration>\n"
+                + "  </brightness-configurations>\n"
+                + "</display-manager-state>\n";
+        InputStream is = new ByteArrayInputStream(contents.getBytes(StandardCharsets.UTF_8));
+        mInjector.setReadStream(is);
+        mDataStore.loadIfNeeded();
+        assertNull(mDataStore.getBrightnessConfiguration(0 /*userSerial*/));
+    }
+
+    @Test
+    public void testBrightnessConfigWithInvalidFloatsIsIgnored() {
+        String contents = "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
+                + "<display-manager-state>\n"
+                + "  <brightness-configurations>\n"
+                + "    <brightness-configuration user-serial=\"0\">\n"
+                + "      <brightness-curve>\n"
+                + "        <brightness-point lux=\"0\" nits=\"13.25\"/>\n"
+                + "        <brightness-point lux=\"0xFF\" nits=\"foo\"/>\n"
+                + "      </brightness-curve>\n"
+                + "    </brightness-configuration>\n"
+                + "  </brightness-configurations>\n"
+                + "</display-manager-state>\n";
+        InputStream is = new ByteArrayInputStream(contents.getBytes(StandardCharsets.UTF_8));
+        mInjector.setReadStream(is);
+        mDataStore.loadIfNeeded();
+        assertNull(mDataStore.getBrightnessConfiguration(0 /*userSerial*/));
+    }
+
+    @Test
+    public void testEmptyBrightnessConfigurationsDoesntCrash() {
+        String contents = "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
+                + "<display-manager-state>\n"
+                + "  <brightness-configurations />\n"
+                + "</display-manager-state>\n";
+        InputStream is = new ByteArrayInputStream(contents.getBytes(StandardCharsets.UTF_8));
+        mInjector.setReadStream(is);
+        mDataStore.loadIfNeeded();
+        assertNull(mDataStore.getBrightnessConfiguration(0 /*userSerial*/));
+    }
+
+    @Test
+    public void testStoreAndReloadOfDisplayBrightnessConfigurations() throws InterruptedException {
+        final String uniqueDisplayId = "test:123";
+        int userSerial = 0;
+        String packageName = "pdsTestPackage";
+        final float[] lux = { 0f, 10f };
+        final float[] nits = {1f, 100f };
+        final BrightnessConfiguration config = new BrightnessConfiguration.Builder(lux, nits)
+                .setDescription("a description")
+                .build();
+        mDataStore.loadIfNeeded();
+        assertNull(mDataStore.getBrightnessConfigurationForDisplayLocked(uniqueDisplayId,
+                userSerial));
+
+        DisplayDevice testDisplayDevice = new DisplayDevice(null, null, uniqueDisplayId, null) {
+            @Override
+            public boolean hasStableUniqueId() {
+                return true;
+            }
+
+            @Override
+            public DisplayDeviceInfo getDisplayDeviceInfoLocked() {
+                return null;
+            }
+        };
+
+        mDataStore.setBrightnessConfigurationForDisplayLocked(config, testDisplayDevice, userSerial,
+                packageName);
+
+        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        mInjector.setWriteStream(baos);
+        mDataStore.saveIfNeeded();
+        mTestLooper.dispatchAll();
+        assertTrue(mInjector.wasWriteSuccessful());
+        TestInjector newInjector = new TestInjector();
+        PersistentDataStore newDataStore = new PersistentDataStore(newInjector);
+        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+        newInjector.setReadStream(bais);
+        newDataStore.loadIfNeeded();
+        assertNotNull(newDataStore.getBrightnessConfigurationForDisplayLocked(uniqueDisplayId,
+                userSerial));
+        assertEquals(mDataStore.getBrightnessConfigurationForDisplayLocked(uniqueDisplayId,
+                userSerial), newDataStore.getBrightnessConfigurationForDisplayLocked(
+                        uniqueDisplayId, userSerial));
+    }
+
+    @Test
+    public void testSetBrightnessConfigurationFailsWithUnstableId() {
+        final String uniqueDisplayId = "test:123";
+        int userSerial = 0;
+        String packageName = "pdsTestPackage";
+        final float[] lux = { 0f, 10f };
+        final float[] nits = {1f, 100f };
+        final BrightnessConfiguration config = new BrightnessConfiguration.Builder(lux, nits)
+                .setDescription("a description")
+                .build();
+        mDataStore.loadIfNeeded();
+        assertNull(mDataStore.getBrightnessConfigurationForDisplayLocked(uniqueDisplayId,
+                userSerial));
+
+        DisplayDevice testDisplayDevice = new DisplayDevice(null, null, uniqueDisplayId, null) {
+            @Override
+            public boolean hasStableUniqueId() {
+                return false;
+            }
+
+            @Override
+            public DisplayDeviceInfo getDisplayDeviceInfoLocked() {
+                return null;
+            }
+        };
+
+        assertFalse(mDataStore.setBrightnessConfigurationForDisplayLocked(
+                config, testDisplayDevice, userSerial, packageName));
+    }
+
+    @Test
+    public void testStoreAndReloadOfBrightnessConfigurations() throws InterruptedException {
+        final float[] lux = { 0f, 10f };
+        final float[] nits = {1f, 100f };
+        final BrightnessConfiguration config = new BrightnessConfiguration.Builder(lux, nits)
+                .setDescription("a description")
+                .build();
+        Context context = InstrumentationRegistry.getInstrumentation().getContext();
+        String packageName = context.getPackageName();
+
+        mDataStore.loadIfNeeded();
+        assertNull(mDataStore.getBrightnessConfiguration(0 /*userSerial*/));
+        mDataStore.setBrightnessConfigurationForUser(config, 0, packageName);
+
+        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        mInjector.setWriteStream(baos);
+        mDataStore.saveIfNeeded();
+        mTestLooper.dispatchAll();
+        assertTrue(mInjector.wasWriteSuccessful());
+
+        TestInjector newInjector = new TestInjector();
+        PersistentDataStore newDataStore = new PersistentDataStore(newInjector);
+        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+        newInjector.setReadStream(bais);
+        newDataStore.loadIfNeeded();
+        assertNotNull(newDataStore.getBrightnessConfiguration(0 /*userSerial*/));
+        assertEquals(mDataStore.getBrightnessConfiguration(0 /*userSerial*/),
+                newDataStore.getBrightnessConfiguration(0 /*userSerial*/));
+    }
+
+    @Test
+    public void testNullBrightnessConfiguration() {
+        final float[] lux = { 0f, 10f };
+        final float[] nits = {1f, 100f };
+        int userSerial = 0;
+        final BrightnessConfiguration config = new BrightnessConfiguration.Builder(lux, nits)
+                .setDescription("a description")
+                .build();
+        mDataStore.loadIfNeeded();
+        assertNull(mDataStore.getBrightnessConfiguration(userSerial));
+
+        mDataStore.setBrightnessConfigurationForUser(config, userSerial, "packagename");
+        assertNotNull(mDataStore.getBrightnessConfiguration(userSerial));
+
+        mDataStore.setBrightnessConfigurationForUser(null, userSerial, "packagename");
+        assertNull(mDataStore.getBrightnessConfiguration(userSerial));
+    }
+
+    @Test
+    public void testStoreAndRestoreResolution() {
+        final String uniqueDisplayId = "test:123";
+        DisplayDevice testDisplayDevice = new DisplayDevice(null, null, uniqueDisplayId, null) {
+            @Override
+            public boolean hasStableUniqueId() {
+                return true;
+            }
+
+            @Override
+            public DisplayDeviceInfo getDisplayDeviceInfoLocked() {
+                return null;
+            }
+        };
+        int width = 35;
+        int height = 45;
+        mDataStore.loadIfNeeded();
+        mDataStore.setUserPreferredResolution(testDisplayDevice, width, height);
+
+        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        mInjector.setWriteStream(baos);
+        mDataStore.saveIfNeeded();
+        mTestLooper.dispatchAll();
+        assertTrue(mInjector.wasWriteSuccessful());
+        TestInjector newInjector = new TestInjector();
+        PersistentDataStore newDataStore = new PersistentDataStore(newInjector);
+        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+        newInjector.setReadStream(bais);
+        newDataStore.loadIfNeeded();
+        assertNotNull(newDataStore.getUserPreferredResolution(testDisplayDevice));
+        assertEquals(35, newDataStore.getUserPreferredResolution(testDisplayDevice).x);
+        assertEquals(35, mDataStore.getUserPreferredResolution(testDisplayDevice).x);
+        assertEquals(45, newDataStore.getUserPreferredResolution(testDisplayDevice).y);
+        assertEquals(45, mDataStore.getUserPreferredResolution(testDisplayDevice).y);
+    }
+
+    @Test
+    public void testStoreAndRestoreRefreshRate() {
+        final String uniqueDisplayId = "test:123";
+        DisplayDevice testDisplayDevice = new DisplayDevice(null, null, uniqueDisplayId, null) {
+            @Override
+            public boolean hasStableUniqueId() {
+                return true;
+            }
+
+            @Override
+            public DisplayDeviceInfo getDisplayDeviceInfoLocked() {
+                return null;
+            }
+        };
+        float refreshRate = 85.3f;
+        mDataStore.loadIfNeeded();
+        mDataStore.setUserPreferredRefreshRate(testDisplayDevice, refreshRate);
+
+        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        mInjector.setWriteStream(baos);
+        mDataStore.saveIfNeeded();
+        mTestLooper.dispatchAll();
+        assertTrue(mInjector.wasWriteSuccessful());
+        TestInjector newInjector = new TestInjector();
+        PersistentDataStore newDataStore = new PersistentDataStore(newInjector);
+        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+        newInjector.setReadStream(bais);
+        newDataStore.loadIfNeeded();
+        assertNotNull(newDataStore.getUserPreferredRefreshRate(testDisplayDevice));
+        assertEquals(85.3f, mDataStore.getUserPreferredRefreshRate(testDisplayDevice), 01.f);
+        assertEquals(85.3f, newDataStore.getUserPreferredRefreshRate(testDisplayDevice), 0.1f);
+    }
+
+    @Test
+    public void testBrightnessInitialisesWithInvalidFloat() {
+        final String uniqueDisplayId = "test:123";
+        DisplayDevice testDisplayDevice = new DisplayDevice(null, null, uniqueDisplayId, null) {
+            @Override
+            public boolean hasStableUniqueId() {
+                return true;
+            }
+
+            @Override
+            public DisplayDeviceInfo getDisplayDeviceInfoLocked() {
+                return null;
+            }
+        };
+
+        // Set any value which initialises Display state
+        float refreshRate = 85.3f;
+        mDataStore.loadIfNeeded();
+        mDataStore.setUserPreferredRefreshRate(testDisplayDevice, refreshRate);
+
+        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        mInjector.setWriteStream(baos);
+        mDataStore.saveIfNeeded();
+        mTestLooper.dispatchAll();
+        assertTrue(mInjector.wasWriteSuccessful());
+        TestInjector newInjector = new TestInjector();
+        PersistentDataStore newDataStore = new PersistentDataStore(newInjector);
+        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+        newInjector.setReadStream(bais);
+        newDataStore.loadIfNeeded();
+        assertTrue(Float.isNaN(mDataStore.getBrightness(testDisplayDevice, 1 /* userSerial */)));
+    }
+
+    @Test
+    public void testStoreAndRestoreBrightnessNitsForDefaultDisplay() {
+        float brightnessNitsForDefaultDisplay = 190;
+        mDataStore.loadIfNeeded();
+        mDataStore.setBrightnessNitsForDefaultDisplay(brightnessNitsForDefaultDisplay);
+
+        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        mInjector.setWriteStream(baos);
+        mDataStore.saveIfNeeded();
+        mTestLooper.dispatchAll();
+        assertTrue(mInjector.wasWriteSuccessful());
+        TestInjector newInjector = new TestInjector();
+        PersistentDataStore newDataStore = new PersistentDataStore(newInjector);
+        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+        newInjector.setReadStream(bais);
+        newDataStore.loadIfNeeded();
+        assertEquals(brightnessNitsForDefaultDisplay,
+                mDataStore.getBrightnessNitsForDefaultDisplay(), 0);
+        assertEquals(brightnessNitsForDefaultDisplay,
+                newDataStore.getBrightnessNitsForDefaultDisplay(), 0);
+    }
+
+    @Test
+    public void testInitialBrightnessNitsForDefaultDisplay() {
+        mDataStore.loadIfNeeded();
+        assertEquals(-1, mDataStore.getBrightnessNitsForDefaultDisplay(), 0);
+    }
+
+    public class TestInjector extends PersistentDataStore.Injector {
+        private InputStream mReadStream;
+        private OutputStream mWriteStream;
+
+        private boolean mWasSuccessful;
+
+        @Override
+        public InputStream openRead() throws FileNotFoundException {
+            if (mReadStream != null) {
+                return mReadStream;
+            } else {
+                throw new FileNotFoundException();
+            }
+        }
+
+        @Override
+        public OutputStream startWrite() {
+            return mWriteStream;
+        }
+
+        @Override
+        public void finishWrite(OutputStream os, boolean success) {
+            mWasSuccessful = success;
+            try {
+                os.close();
+            } catch (IOException e) {
+                // This method can't throw IOException since the super implementation doesn't, so
+                // we just wrap it in a RuntimeException so we end up crashing the test all the
+                // same.
+                throw new RuntimeException(e);
+            }
+        }
+
+        public void setReadStream(InputStream is) {
+            mReadStream = is;
+        }
+
+        public void setWriteStream(OutputStream os) {
+            mWriteStream = os;
+        }
+
+        public boolean wasWriteSuccessful() {
+            return mWasSuccessful;
+        }
+    }
+
+    private static void assertArrayEquals(float[] expected, float[] actual, String name) {
+        assertEquals("Expected " + name + " arrays to be the same length!",
+                expected.length, actual.length);
+        for (int i = 0; i < expected.length; i++) {
+            assertEquals("Expected " + name + " arrays to be equivalent when value " + i
+                    + "differs", expected[i], actual[i], 0.01 /*tolerance*/);
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/display/ScreenOffBrightnessSensorControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/ScreenOffBrightnessSensorControllerTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/display/ScreenOffBrightnessSensorControllerTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/ScreenOffBrightnessSensorControllerTest.java
diff --git a/services/tests/displayservicetests/src/com/android/server/display/TestUtils.java b/services/tests/displayservicetests/src/com/android/server/display/TestUtils.java
new file mode 100644
index 0000000..8b45145
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/TestUtils.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.input.InputSensorInfo;
+import android.os.Parcel;
+import android.os.SystemClock;
+import android.view.DisplayAddress;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+
+public final class TestUtils {
+
+    public static SensorEvent createSensorEvent(Sensor sensor, int value) throws Exception {
+        final Constructor<SensorEvent> constructor =
+                SensorEvent.class.getDeclaredConstructor(int.class);
+        constructor.setAccessible(true);
+        final SensorEvent event = constructor.newInstance(1);
+        event.sensor = sensor;
+        event.values[0] = value;
+        event.timestamp = SystemClock.elapsedRealtimeNanos();
+        return event;
+    }
+
+
+    public static void setSensorType(Sensor sensor, int type, String strType) throws Exception {
+        Method setter = Sensor.class.getDeclaredMethod("setType", Integer.TYPE);
+        setter.setAccessible(true);
+        setter.invoke(sensor, type);
+        if (strType != null) {
+            Field f = sensor.getClass().getDeclaredField("mStringType");
+            f.setAccessible(true);
+            f.set(sensor, strType);
+        }
+    }
+
+    public static void setMaximumRange(Sensor sensor, float maximumRange) throws Exception {
+        Method setter = Sensor.class.getDeclaredMethod("setRange", Float.TYPE, Float.TYPE);
+        setter.setAccessible(true);
+        setter.invoke(sensor, maximumRange, 1);
+    }
+
+    public static Sensor createSensor(int type, String strType) throws Exception {
+        Constructor<Sensor> constr = Sensor.class.getDeclaredConstructor();
+        constr.setAccessible(true);
+        Sensor sensor = constr.newInstance();
+        setSensorType(sensor, type, strType);
+        return sensor;
+    }
+
+    public static Sensor createSensor(int type, String strType, float maximumRange)
+            throws Exception {
+        Constructor<Sensor> constr = Sensor.class.getDeclaredConstructor();
+        constr.setAccessible(true);
+        Sensor sensor = constr.newInstance();
+        setSensorType(sensor, type, strType);
+        setMaximumRange(sensor, maximumRange);
+        return sensor;
+    }
+
+    public static Sensor createSensor(String type, String name) {
+        return new Sensor(new InputSensorInfo(
+                name, "vendor", 0, 0, 0, 1f, 1f, 1, 1, 1, 1,
+                type, "", 0, 0, 0));
+    }
+
+    /**
+     * Create a custom {@link DisplayAddress} to ensure we're not relying on any specific
+     * display-address implementation in our code. Intentionally uses default object (reference)
+     * equality rules.
+     */
+    public static class TestDisplayAddress extends DisplayAddress {
+        @Override
+        public void writeToParcel(Parcel out, int flags) { }
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/WakelockControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/WakelockControllerTest.java
similarity index 100%
rename from services/tests/mockingservicestests/src/com/android/server/display/WakelockControllerTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/WakelockControllerTest.java
diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/BrightnessEventTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/BrightnessEventTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/display/brightness/BrightnessEventTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/brightness/BrightnessEventTest.java
diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/BrightnessReasonTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/BrightnessReasonTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/display/brightness/BrightnessReasonTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/brightness/BrightnessReasonTest.java
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
new file mode 100644
index 0000000..c4f4838
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
@@ -0,0 +1,387 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.brightness;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
+import android.os.HandlerExecutor;
+import android.os.PowerManager;
+import android.view.Display;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+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.TemporaryBrightnessStrategy;
+
+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)
+public final class DisplayBrightnessControllerTest {
+    private static final int DISPLAY_ID = Display.DEFAULT_DISPLAY;
+    private static final float DEFAULT_BRIGHTNESS = 0.15f;
+
+    @Mock
+    private DisplayBrightnessStrategySelector mDisplayBrightnessStrategySelector;
+    @Mock
+    private Context mContext;
+    @Mock
+    private Resources mResources;
+    @Mock
+    private BrightnessSetting mBrightnessSetting;
+    @Mock
+    private Runnable mOnBrightnessChangeRunnable;
+
+    @Mock
+    private HandlerExecutor mBrightnessChangeExecutor;
+
+    private final DisplayBrightnessController.Injector mInjector = new
+            DisplayBrightnessController.Injector() {
+        @Override
+        DisplayBrightnessStrategySelector getDisplayBrightnessStrategySelector(
+                Context context, int displayId) {
+            return mDisplayBrightnessStrategySelector;
+        }
+    };
+
+    private DisplayBrightnessController mDisplayBrightnessController;
+
+    @Before
+    public void before() {
+        MockitoAnnotations.initMocks(this);
+        when(mContext.getResources()).thenReturn(mResources);
+        when(mBrightnessSetting.getBrightness()).thenReturn(Float.NaN);
+        when(mBrightnessSetting.getBrightnessNitsForDefaultDisplay()).thenReturn(-1f);
+        when(mResources.getBoolean(
+                com.android.internal.R.bool.config_persistBrightnessNitsForDefaultDisplay))
+                .thenReturn(true);
+        mDisplayBrightnessController = new DisplayBrightnessController(mContext, mInjector,
+                DISPLAY_ID, DEFAULT_BRIGHTNESS, mBrightnessSetting, mOnBrightnessChangeRunnable,
+                mBrightnessChangeExecutor);
+    }
+
+    @Test
+    public void testIfFirstScreenBrightnessIsDefault() {
+        assertEquals(DEFAULT_BRIGHTNESS, mDisplayBrightnessController.getCurrentBrightness(),
+                /* delta= */ 0.0f);
+    }
+
+    @Test
+    public void testUpdateBrightness() {
+        DisplayPowerRequest displayPowerRequest = mock(DisplayPowerRequest.class);
+        DisplayBrightnessStrategy displayBrightnessStrategy = mock(DisplayBrightnessStrategy.class);
+        int targetDisplayState = Display.STATE_DOZE;
+        when(mDisplayBrightnessStrategySelector.selectStrategy(displayPowerRequest,
+                targetDisplayState)).thenReturn(displayBrightnessStrategy);
+        mDisplayBrightnessController.updateBrightness(displayPowerRequest, targetDisplayState);
+        verify(displayBrightnessStrategy).updateBrightness(displayPowerRequest);
+        assertEquals(mDisplayBrightnessController.getCurrentDisplayBrightnessStrategy(),
+                displayBrightnessStrategy);
+    }
+
+    @Test
+    public void isAllowAutoBrightnessWhileDozingConfigDelegatesToDozeBrightnessStrategy() {
+        mDisplayBrightnessController.isAllowAutoBrightnessWhileDozingConfig();
+        verify(mDisplayBrightnessStrategySelector).isAllowAutoBrightnessWhileDozingConfig();
+    }
+
+    @Test
+    public void setTemporaryBrightness() {
+        float temporaryBrightness = 0.4f;
+        TemporaryBrightnessStrategy temporaryBrightnessStrategy = mock(
+                TemporaryBrightnessStrategy.class);
+        when(mDisplayBrightnessStrategySelector.getTemporaryDisplayBrightnessStrategy()).thenReturn(
+                temporaryBrightnessStrategy);
+        mDisplayBrightnessController.setTemporaryBrightness(temporaryBrightness);
+        verify(temporaryBrightnessStrategy).setTemporaryScreenBrightness(temporaryBrightness);
+    }
+
+    @Test
+    public void setCurrentScreenBrightness() {
+        // Current Screen brightness is set as expected when a different value than the current
+        // is set
+        float currentScreenBrightness = 0.4f;
+        mDisplayBrightnessController.setAndNotifyCurrentScreenBrightness(currentScreenBrightness);
+        assertEquals(mDisplayBrightnessController.getCurrentBrightness(),
+                currentScreenBrightness, /* delta= */ 0.0f);
+        verify(mBrightnessChangeExecutor).execute(mOnBrightnessChangeRunnable);
+
+        // No change to the current screen brightness is same as the existing one
+        mDisplayBrightnessController.setAndNotifyCurrentScreenBrightness(currentScreenBrightness);
+        verifyNoMoreInteractions(mBrightnessChangeExecutor);
+    }
+
+    @Test
+    public void setPendingScreenBrightnessSetting() {
+        float pendingScreenBrightness = 0.4f;
+        mDisplayBrightnessController.setPendingScreenBrightness(pendingScreenBrightness);
+        assertEquals(mDisplayBrightnessController.getPendingScreenBrightness(),
+                pendingScreenBrightness, /* delta= */ 0.0f);
+    }
+
+    @Test
+    public void updateUserSetScreenBrightness() {
+        // No brightness is set if the pending brightness is invalid
+        mDisplayBrightnessController.setPendingScreenBrightness(Float.NaN);
+        assertFalse(mDisplayBrightnessController.updateUserSetScreenBrightness());
+
+        // user set brightness is not set if the current and the pending brightness are same.
+        float currentBrightness = 0.4f;
+        TemporaryBrightnessStrategy temporaryBrightnessStrategy = mock(
+                TemporaryBrightnessStrategy.class);
+        when(mDisplayBrightnessStrategySelector.getTemporaryDisplayBrightnessStrategy()).thenReturn(
+                temporaryBrightnessStrategy);
+        mDisplayBrightnessController.setAndNotifyCurrentScreenBrightness(currentBrightness);
+        mDisplayBrightnessController.setPendingScreenBrightness(currentBrightness);
+        mDisplayBrightnessController.setTemporaryBrightness(currentBrightness);
+        assertFalse(mDisplayBrightnessController.updateUserSetScreenBrightness());
+        verify(temporaryBrightnessStrategy).setTemporaryScreenBrightness(
+                PowerManager.BRIGHTNESS_INVALID_FLOAT);
+        assertEquals(mDisplayBrightnessController.getPendingScreenBrightness(),
+                PowerManager.BRIGHTNESS_INVALID_FLOAT, /* delta= */ 0.0f);
+
+        // user set brightness is set as expected
+        currentBrightness = 0.4f;
+        float pendingScreenBrightness = 0.3f;
+        float temporaryScreenBrightness = 0.2f;
+        mDisplayBrightnessController.setAndNotifyCurrentScreenBrightness(currentBrightness);
+        mDisplayBrightnessController.setPendingScreenBrightness(pendingScreenBrightness);
+        mDisplayBrightnessController.setTemporaryBrightness(temporaryScreenBrightness);
+        assertTrue(mDisplayBrightnessController.updateUserSetScreenBrightness());
+        assertEquals(mDisplayBrightnessController.getCurrentBrightness(),
+                pendingScreenBrightness, /* delta= */ 0.0f);
+        assertEquals(mDisplayBrightnessController.getLastUserSetScreenBrightness(),
+                pendingScreenBrightness, /* delta= */ 0.0f);
+        verify(mBrightnessChangeExecutor, times(2))
+                .execute(mOnBrightnessChangeRunnable);
+        verify(temporaryBrightnessStrategy, times(2))
+                .setTemporaryScreenBrightness(PowerManager.BRIGHTNESS_INVALID_FLOAT);
+        assertEquals(mDisplayBrightnessController.getPendingScreenBrightness(),
+                PowerManager.BRIGHTNESS_INVALID_FLOAT, /* delta= */ 0.0f);
+    }
+
+    @Test
+    public void registerBrightnessSettingChangeListener() {
+        BrightnessSetting.BrightnessSettingListener brightnessSettingListener = mock(
+                BrightnessSetting.BrightnessSettingListener.class);
+        mDisplayBrightnessController.registerBrightnessSettingChangeListener(
+                brightnessSettingListener);
+        verify(mBrightnessSetting).registerListener(brightnessSettingListener);
+        assertEquals(mDisplayBrightnessController.getBrightnessSettingListener(),
+                brightnessSettingListener);
+    }
+
+    @Test
+    public void getScreenBrightnessSetting() {
+        // getScreenBrightnessSetting returns the value relayed by BrightnessSetting, if the
+        // valid is valid and in range
+        float brightnessSetting = 0.2f;
+        when(mBrightnessSetting.getBrightness()).thenReturn(brightnessSetting);
+        assertEquals(mDisplayBrightnessController.getScreenBrightnessSetting(), brightnessSetting,
+                /* delta= */ 0.0f);
+
+        // getScreenBrightnessSetting value is clamped if BrightnessSetting returns value beyond max
+        brightnessSetting = 1.1f;
+        when(mBrightnessSetting.getBrightness()).thenReturn(brightnessSetting);
+        assertEquals(mDisplayBrightnessController.getScreenBrightnessSetting(), 1.0f,
+                /* delta= */ 0.0f);
+
+        // getScreenBrightnessSetting returns default value is BrightnessSetting returns invalid
+        // value.
+        brightnessSetting = Float.NaN;
+        when(mBrightnessSetting.getBrightness()).thenReturn(brightnessSetting);
+        assertEquals(mDisplayBrightnessController.getScreenBrightnessSetting(), DEFAULT_BRIGHTNESS,
+                /* delta= */ 0.0f);
+    }
+
+    @Test
+    public void setBrightnessSetsInBrightnessSetting() {
+        float brightnessValue = 0.3f;
+        mDisplayBrightnessController.setBrightness(brightnessValue);
+        verify(mBrightnessSetting).setBrightness(brightnessValue);
+    }
+
+    @Test
+    public void updateScreenBrightnessSetting() {
+        // This interaction happens in the constructor itself
+        verify(mBrightnessSetting).getBrightness();
+
+        // Sets the appropriate value when valid, and not equal to the current brightness
+        float brightnessValue = 0.3f;
+        mDisplayBrightnessController.updateScreenBrightnessSetting(brightnessValue);
+        assertEquals(mDisplayBrightnessController.getCurrentBrightness(), brightnessValue,
+                0.0f);
+        verify(mBrightnessChangeExecutor).execute(mOnBrightnessChangeRunnable);
+        verify(mBrightnessSetting).setBrightness(brightnessValue);
+        verify(mBrightnessSetting).setUserSerial(anyInt());
+
+        // Does nothing if the value is invalid
+        mDisplayBrightnessController.updateScreenBrightnessSetting(Float.NaN);
+        verifyNoMoreInteractions(mBrightnessChangeExecutor, mBrightnessSetting);
+
+        // Does nothing if the value is same as the current brightness
+        brightnessValue = 0.2f;
+        mDisplayBrightnessController.setAndNotifyCurrentScreenBrightness(brightnessValue);
+        verify(mBrightnessChangeExecutor, times(2))
+                .execute(mOnBrightnessChangeRunnable);
+        mDisplayBrightnessController.updateScreenBrightnessSetting(brightnessValue);
+        verifyNoMoreInteractions(mBrightnessChangeExecutor, mBrightnessSetting);
+    }
+
+    @Test
+    public void testConvertToNits() {
+        final float brightness = 0.5f;
+        final float nits = 300;
+        final float adjustedNits = 200;
+
+        // ABC is null
+        assertEquals(-1f, mDisplayBrightnessController.convertToNits(brightness),
+                /* delta= */ 0);
+        assertEquals(-1f, mDisplayBrightnessController.convertToAdjustedNits(brightness),
+                /* delta= */ 0);
+
+        AutomaticBrightnessController automaticBrightnessController =
+                mock(AutomaticBrightnessController.class);
+        when(automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
+        when(automaticBrightnessController.convertToAdjustedNits(brightness))
+                .thenReturn(adjustedNits);
+        mDisplayBrightnessController.setAutomaticBrightnessController(
+                automaticBrightnessController);
+
+        assertEquals(nits, mDisplayBrightnessController.convertToNits(brightness), /* delta= */ 0);
+        assertEquals(adjustedNits, mDisplayBrightnessController.convertToAdjustedNits(brightness),
+                /* delta= */ 0);
+    }
+
+    @Test
+    public void testConvertToFloatScale() {
+        float brightness = 0.5f;
+        float nits = 300;
+
+        // ABC is null
+        assertEquals(PowerManager.BRIGHTNESS_INVALID_FLOAT,
+                mDisplayBrightnessController.convertToFloatScale(nits), /* delta= */ 0);
+
+        AutomaticBrightnessController automaticBrightnessController =
+                mock(AutomaticBrightnessController.class);
+        when(automaticBrightnessController.convertToFloatScale(nits)).thenReturn(brightness);
+        mDisplayBrightnessController.setAutomaticBrightnessController(
+                automaticBrightnessController);
+
+        assertEquals(brightness, mDisplayBrightnessController.convertToFloatScale(nits),
+                /* delta= */ 0);
+    }
+
+    @Test
+    public void stop() {
+        BrightnessSetting.BrightnessSettingListener brightnessSettingListener = mock(
+                BrightnessSetting.BrightnessSettingListener.class);
+        mDisplayBrightnessController.registerBrightnessSettingChangeListener(
+                brightnessSettingListener);
+        mDisplayBrightnessController.stop();
+        verify(mBrightnessSetting).unregisterListener(brightnessSettingListener);
+    }
+
+    @Test
+    public void testLoadNitBasedBrightnessSetting() {
+        // When the nits value is valid, the brightness is set from the old default display nits
+        // value
+        float nits = 200f;
+        float brightness = 0.3f;
+        AutomaticBrightnessController automaticBrightnessController =
+                mock(AutomaticBrightnessController.class);
+        when(automaticBrightnessController.convertToFloatScale(nits)).thenReturn(brightness);
+        when(mBrightnessSetting.getBrightnessNitsForDefaultDisplay()).thenReturn(nits);
+        mDisplayBrightnessController.setAutomaticBrightnessController(
+                automaticBrightnessController);
+        verify(mBrightnessSetting).setBrightness(brightness);
+        assertEquals(brightness, mDisplayBrightnessController.getCurrentBrightness(), 0.01f);
+        clearInvocations(automaticBrightnessController, mBrightnessSetting);
+
+        // When the nits value is invalid, the brightness is resumed from where it was last set
+        nits = -1;
+        brightness = 0.4f;
+        when(automaticBrightnessController.convertToFloatScale(nits)).thenReturn(brightness);
+        when(mBrightnessSetting.getBrightnessNitsForDefaultDisplay()).thenReturn(nits);
+        when(mBrightnessSetting.getBrightness()).thenReturn(brightness);
+        mDisplayBrightnessController.setAutomaticBrightnessController(
+                automaticBrightnessController);
+        verify(mBrightnessSetting, never()).setBrightness(brightness);
+        assertEquals(brightness, mDisplayBrightnessController.getCurrentBrightness(), 0.01f);
+        clearInvocations(automaticBrightnessController, mBrightnessSetting);
+
+        // When the display is a non-default display, the brightness is resumed from where it was
+        // last set
+        int nonDefaultDisplayId = 1;
+        mDisplayBrightnessController = new DisplayBrightnessController(mContext, mInjector,
+                nonDefaultDisplayId, DEFAULT_BRIGHTNESS, mBrightnessSetting,
+                mOnBrightnessChangeRunnable, mBrightnessChangeExecutor);
+        brightness = 0.5f;
+        when(mBrightnessSetting.getBrightness()).thenReturn(brightness);
+        mDisplayBrightnessController.setAutomaticBrightnessController(
+                automaticBrightnessController);
+        assertEquals(brightness, mDisplayBrightnessController.getCurrentBrightness(), 0.01f);
+        verifyZeroInteractions(automaticBrightnessController);
+        verify(mBrightnessSetting, never()).getBrightnessNitsForDefaultDisplay();
+        verify(mBrightnessSetting, never()).setBrightness(brightness);
+    }
+
+    @Test
+    public void testChangeBrightnessNitsWhenUserChanges() {
+        float brightnessValue1 = 0.3f;
+        float nits1 = 200f;
+        float brightnessValue2 = 0.5f;
+        float nits2 = 300f;
+        AutomaticBrightnessController automaticBrightnessController =
+                mock(AutomaticBrightnessController.class);
+        when(automaticBrightnessController.convertToNits(brightnessValue1)).thenReturn(nits1);
+        when(automaticBrightnessController.convertToNits(brightnessValue2)).thenReturn(nits2);
+        mDisplayBrightnessController.setAutomaticBrightnessController(
+                automaticBrightnessController);
+
+        mDisplayBrightnessController.setBrightness(brightnessValue1, 1 /* user-serial */);
+        verify(mBrightnessSetting).setUserSerial(1);
+        verify(mBrightnessSetting).setBrightness(brightnessValue1);
+        verify(mBrightnessSetting).setBrightnessNitsForDefaultDisplay(nits1);
+
+        mDisplayBrightnessController.setBrightness(brightnessValue2, 2 /* user-serial */);
+        verify(mBrightnessSetting).setUserSerial(2);
+        verify(mBrightnessSetting).setBrightness(brightnessValue2);
+        verify(mBrightnessSetting).setBrightnessNitsForDefaultDisplay(nits2);
+    }
+}
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
new file mode 100644
index 0000000..8497dab
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.brightness;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.res.Resources;
+import android.hardware.display.DisplayManagerInternal;
+import android.view.Display;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+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.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.OverrideBrightnessStrategy;
+import com.android.server.display.brightness.strategy.ScreenOffBrightnessStrategy;
+import com.android.server.display.brightness.strategy.TemporaryBrightnessStrategy;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public final class DisplayBrightnessStrategySelectorTest {
+    private static final boolean DISALLOW_AUTO_BRIGHTNESS_WHILE_DOZING = false;
+    private static final int DISPLAY_ID = 1;
+
+    @Mock
+    private ScreenOffBrightnessStrategy mScreenOffBrightnessModeStrategy;
+    @Mock
+    private DozeBrightnessStrategy mDozeBrightnessModeStrategy;
+    @Mock
+    private OverrideBrightnessStrategy mOverrideBrightnessStrategy;
+    @Mock
+    private TemporaryBrightnessStrategy mTemporaryBrightnessStrategy;
+    @Mock
+    private BoostBrightnessStrategy mBoostBrightnessStrategy;
+    @Mock
+    private InvalidBrightnessStrategy mInvalidBrightnessStrategy;
+    @Mock
+    private FollowerBrightnessStrategy mFollowerBrightnessStrategy;
+    @Mock
+    private Resources mResources;
+
+    private DisplayBrightnessStrategySelector mDisplayBrightnessStrategySelector;
+    private Context mContext;
+
+    @Rule
+    public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
+
+    @Before
+    public void before() {
+        MockitoAnnotations.initMocks(this);
+        mContext = spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
+        ContentResolver contentResolver = mSettingsProviderRule.mockContentResolver(mContext);
+        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);
+
+    }
+
+    @Test
+    public void selectStrategySelectsDozeStrategyWhenValid() {
+        DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
+                DisplayManagerInternal.DisplayPowerRequest.class);
+        displayPowerRequest.policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE;
+        when(mResources.getBoolean(R.bool.config_allowAutoBrightnessWhileDozing)).thenReturn(
+                DISALLOW_AUTO_BRIGHTNESS_WHILE_DOZING);
+        assertEquals(mDisplayBrightnessStrategySelector.selectStrategy(displayPowerRequest,
+                Display.STATE_DOZE), mDozeBrightnessModeStrategy);
+    }
+
+    @Test
+    public void selectStrategySelectsScreenOffStrategyWhenValid() {
+        DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
+                DisplayManagerInternal.DisplayPowerRequest.class);
+        assertEquals(mDisplayBrightnessStrategySelector.selectStrategy(displayPowerRequest,
+                Display.STATE_OFF), mScreenOffBrightnessModeStrategy);
+    }
+
+    @Test
+    public void selectStrategySelectsOverrideStrategyWhenValid() {
+        DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
+                DisplayManagerInternal.DisplayPowerRequest.class);
+        displayPowerRequest.screenBrightnessOverride = 0.4f;
+        when(mFollowerBrightnessStrategy.getBrightnessToFollow()).thenReturn(Float.NaN);
+        assertEquals(mDisplayBrightnessStrategySelector.selectStrategy(displayPowerRequest,
+                Display.STATE_ON), mOverrideBrightnessStrategy);
+    }
+
+    @Test
+    public void selectStrategySelectsTemporaryStrategyWhenValid() {
+        DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
+                DisplayManagerInternal.DisplayPowerRequest.class);
+        displayPowerRequest.screenBrightnessOverride = Float.NaN;
+        when(mFollowerBrightnessStrategy.getBrightnessToFollow()).thenReturn(Float.NaN);
+        when(mTemporaryBrightnessStrategy.getTemporaryScreenBrightness()).thenReturn(0.3f);
+        assertEquals(mDisplayBrightnessStrategySelector.selectStrategy(displayPowerRequest,
+                Display.STATE_ON), mTemporaryBrightnessStrategy);
+    }
+
+    @Test
+    public void selectStrategySelectsBoostStrategyWhenValid() {
+        DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
+                DisplayManagerInternal.DisplayPowerRequest.class);
+        displayPowerRequest.boostScreenBrightness = true;
+        when(mFollowerBrightnessStrategy.getBrightnessToFollow()).thenReturn(Float.NaN);
+        displayPowerRequest.screenBrightnessOverride = Float.NaN;
+        when(mTemporaryBrightnessStrategy.getTemporaryScreenBrightness()).thenReturn(Float.NaN);
+        assertEquals(mDisplayBrightnessStrategySelector.selectStrategy(displayPowerRequest,
+                Display.STATE_ON), mBoostBrightnessStrategy);
+    }
+
+    @Test
+    public void selectStrategySelectsInvalidStrategyWhenNoStrategyIsValid() {
+        DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
+                DisplayManagerInternal.DisplayPowerRequest.class);
+        displayPowerRequest.screenBrightnessOverride = Float.NaN;
+        when(mFollowerBrightnessStrategy.getBrightnessToFollow()).thenReturn(Float.NaN);
+        when(mTemporaryBrightnessStrategy.getTemporaryScreenBrightness()).thenReturn(Float.NaN);
+        assertEquals(mDisplayBrightnessStrategySelector.selectStrategy(displayPowerRequest,
+                Display.STATE_ON), mInvalidBrightnessStrategy);
+    }
+
+    @Test
+    public void selectStrategySelectsFollowerStrategyWhenValid() {
+        DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
+                DisplayManagerInternal.DisplayPowerRequest.class);
+        when(mFollowerBrightnessStrategy.getBrightnessToFollow()).thenReturn(0.3f);
+        assertEquals(mDisplayBrightnessStrategySelector.selectStrategy(displayPowerRequest,
+                Display.STATE_ON), mFollowerBrightnessStrategy);
+    }
+}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessThermalClamperTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessThermalClamperTest.java
new file mode 100644
index 0000000..37d0f62
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessThermalClamperTest.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 com.android.server.display.brightness.clamper;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+
+import android.hardware.display.DisplayManager;
+import android.os.IThermalEventListener;
+import android.os.IThermalService;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.Temperature;
+import android.provider.DeviceConfig;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.internal.annotations.Keep;
+import com.android.server.display.DisplayDeviceConfig;
+import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData;
+import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel;
+import com.android.server.display.feature.DeviceConfigParameterProvider;
+import com.android.server.testutils.FakeDeviceConfigInterface;
+import com.android.server.testutils.TestHandler;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.List;
+
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
+@RunWith(JUnitParamsRunner.class)
+public class BrightnessThermalClamperTest {
+
+    private static final float FLOAT_TOLERANCE = 0.001f;
+
+    private static final String DISPLAY_ID = "displayId";
+    @Mock
+    private IThermalService mMockThermalService;
+    @Mock
+    private BrightnessClamperController.ClamperChangeListener mMockClamperChangeListener;
+
+    private final FakeDeviceConfigInterface mFakeDeviceConfigInterface =
+            new FakeDeviceConfigInterface();
+    private final TestHandler mTestHandler = new TestHandler(null);
+    private BrightnessThermalClamper mClamper;
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mClamper = new BrightnessThermalClamper(new TestInjector(), mTestHandler,
+                mMockClamperChangeListener, new TestThermalData());
+        mTestHandler.flush();
+    }
+
+    @Test
+    public void testTypeIsThermal() {
+        assertEquals(BrightnessClamper.Type.THERMAL, mClamper.getType());
+    }
+
+    @Test
+    public void testNoThrottlingData() {
+        assertFalse(mClamper.isActive());
+        assertEquals(PowerManager.BRIGHTNESS_MAX, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
+    }
+
+    @Keep
+    private static Object[][] testThrottlingData() {
+        // throttlingLevels, throttlingStatus, expectedActive, expectedBrightness
+        return new Object[][] {
+                // no throttling data
+                {List.of(), Temperature.THROTTLING_LIGHT, false, PowerManager.BRIGHTNESS_MAX},
+                // throttlingStatus < min throttling data
+                {List.of(
+                        new ThrottlingLevel(PowerManager.THERMAL_STATUS_MODERATE, 0.5f),
+                        new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 0.1f)),
+                        Temperature.THROTTLING_LIGHT, false, PowerManager.BRIGHTNESS_MAX},
+                // throttlingStatus = min throttling data
+                {List.of(
+                        new ThrottlingLevel(PowerManager.THERMAL_STATUS_MODERATE, 0.5f),
+                        new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 0.1f)),
+                        Temperature.THROTTLING_MODERATE, true, 0.5f},
+                // throttlingStatus between min and max throttling data
+                {List.of(
+                        new ThrottlingLevel(PowerManager.THERMAL_STATUS_MODERATE, 0.5f),
+                        new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 0.1f)),
+                        Temperature.THROTTLING_SEVERE, true, 0.5f},
+                // throttlingStatus = max throttling data
+                {List.of(
+                        new ThrottlingLevel(PowerManager.THERMAL_STATUS_MODERATE, 0.5f),
+                        new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 0.1f)),
+                        Temperature.THROTTLING_CRITICAL, true, 0.1f},
+                // throttlingStatus > max throttling data
+                {List.of(
+                        new ThrottlingLevel(PowerManager.THERMAL_STATUS_MODERATE, 0.5f),
+                        new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 0.1f)),
+                        Temperature.THROTTLING_EMERGENCY, true, 0.1f},
+        };
+    }
+    @Test
+    @Parameters(method = "testThrottlingData")
+    public void testNotifyThrottlingAfterOnDisplayChange(List<ThrottlingLevel> throttlingLevels,
+            @Temperature.ThrottlingStatus int throttlingStatus,
+            boolean expectedActive, float expectedBrightness) throws RemoteException {
+        IThermalEventListener thermalEventListener = captureThermalEventListener();
+        mClamper.onDisplayChanged(new TestThermalData(throttlingLevels));
+        mTestHandler.flush();
+        assertFalse(mClamper.isActive());
+        assertEquals(PowerManager.BRIGHTNESS_MAX, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
+
+        thermalEventListener.notifyThrottling(createTemperature(throttlingStatus));
+        mTestHandler.flush();
+        assertEquals(expectedActive, mClamper.isActive());
+        assertEquals(expectedBrightness, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
+    }
+
+    @Test
+    @Parameters(method = "testThrottlingData")
+    public void testOnDisplayChangeAfterNotifyThrottlng(List<ThrottlingLevel> throttlingLevels,
+            @Temperature.ThrottlingStatus int throttlingStatus,
+            boolean expectedActive, float expectedBrightness) throws RemoteException {
+        IThermalEventListener thermalEventListener = captureThermalEventListener();
+        thermalEventListener.notifyThrottling(createTemperature(throttlingStatus));
+        mTestHandler.flush();
+        assertFalse(mClamper.isActive());
+        assertEquals(PowerManager.BRIGHTNESS_MAX, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
+
+        mClamper.onDisplayChanged(new TestThermalData(throttlingLevels));
+        mTestHandler.flush();
+        assertEquals(expectedActive, mClamper.isActive());
+        assertEquals(expectedBrightness, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
+    }
+
+    @Test
+    public void testOverrideData() throws RemoteException {
+        IThermalEventListener thermalEventListener = captureThermalEventListener();
+        thermalEventListener.notifyThrottling(createTemperature(Temperature.THROTTLING_SEVERE));
+        mTestHandler.flush();
+        assertFalse(mClamper.isActive());
+        assertEquals(PowerManager.BRIGHTNESS_MAX, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
+
+        mClamper.onDisplayChanged(new TestThermalData(
+                List.of(new ThrottlingLevel(PowerManager.THERMAL_STATUS_SEVERE, 0.5f))));
+        mTestHandler.flush();
+        assertTrue(mClamper.isActive());
+        assertEquals(0.5f, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
+
+        overrideThrottlingData("displayId,1,emergency,0.4");
+        mClamper.onDeviceConfigChanged();
+        mTestHandler.flush();
+
+        assertFalse(mClamper.isActive());
+        assertEquals(PowerManager.BRIGHTNESS_MAX, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
+
+        overrideThrottlingData("displayId,1,moderate,0.4");
+        mClamper.onDeviceConfigChanged();
+        mTestHandler.flush();
+
+        assertTrue(mClamper.isActive());
+        assertEquals(0.4f, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
+    }
+
+    private IThermalEventListener captureThermalEventListener() throws RemoteException {
+        ArgumentCaptor<IThermalEventListener> captor = ArgumentCaptor.forClass(
+                IThermalEventListener.class);
+        verify(mMockThermalService).registerThermalEventListenerWithType(captor.capture(), eq(
+                Temperature.TYPE_SKIN));
+        return captor.getValue();
+    }
+
+    private Temperature createTemperature(@Temperature.ThrottlingStatus int status) {
+        return new Temperature(100, Temperature.TYPE_SKIN, "test_temperature", status);
+    }
+
+    private void overrideThrottlingData(String data) {
+        mFakeDeviceConfigInterface.putProperty(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+                DisplayManager.DeviceConfig.KEY_BRIGHTNESS_THROTTLING_DATA, data);
+    }
+
+    private class TestInjector extends BrightnessThermalClamper.Injector {
+        @Override
+        IThermalService getThermalService() {
+            return mMockThermalService;
+        }
+
+        @Override
+        DeviceConfigParameterProvider getDeviceConfigParameterProvider() {
+            return new DeviceConfigParameterProvider(mFakeDeviceConfigInterface);
+        }
+    }
+
+    private static class TestThermalData implements BrightnessThermalClamper.ThermalData {
+
+        private final String mUniqueDisplayId;
+        private final String mDataId;
+        private final ThermalBrightnessThrottlingData mData;
+
+        private TestThermalData() {
+            this(DISPLAY_ID, DisplayDeviceConfig.DEFAULT_ID, null);
+        }
+
+        private TestThermalData(List<ThrottlingLevel> data) {
+            this(DISPLAY_ID, DisplayDeviceConfig.DEFAULT_ID, data);
+        }
+        private TestThermalData(String uniqueDisplayId, String dataId, List<ThrottlingLevel> data) {
+            mUniqueDisplayId = uniqueDisplayId;
+            mDataId = dataId;
+            mData = ThermalBrightnessThrottlingData.create(data);
+        }
+        @NonNull
+        @Override
+        public String getUniqueDisplayId() {
+            return mUniqueDisplayId;
+        }
+
+        @NonNull
+        @Override
+        public String getThermalThrottlingDataId() {
+            return mDataId;
+        }
+
+        @Nullable
+        @Override
+        public ThermalBrightnessThrottlingData getThermalBrightnessThrottlingData() {
+            return mData;
+        }
+    }
+}
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
new file mode 100644
index 0000000..b652576
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java
@@ -0,0 +1,402 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.hardware.display.BrightnessConfiguration;
+import android.hardware.display.DisplayManagerInternal;
+import android.os.PowerManager;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.test.mock.MockContentResolver;
+import android.view.Display;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.internal.util.test.FakeSettingsProviderRule;
+import com.android.server.display.AutomaticBrightnessController;
+import com.android.server.display.brightness.BrightnessEvent;
+import com.android.server.display.brightness.BrightnessReason;
+
+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;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class AutomaticBrightnessStrategyTest {
+    private static final int DISPLAY_ID = 0;
+    @Rule
+    public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
+
+    @Mock
+    private AutomaticBrightnessController mAutomaticBrightnessController;
+
+    private BrightnessConfiguration mBrightnessConfiguration;
+    private float mDefaultScreenAutoBrightnessAdjustment;
+    private Context mContext;
+    private AutomaticBrightnessStrategy mAutomaticBrightnessStrategy;
+
+    @Before
+    public void before() {
+        MockitoAnnotations.initMocks(this);
+        mContext = spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
+        final MockContentResolver resolver = mSettingsProviderRule.mockContentResolver(mContext);
+        when(mContext.getContentResolver()).thenReturn(resolver);
+        mDefaultScreenAutoBrightnessAdjustment = Settings.System.getFloat(
+                mContext.getContentResolver(),
+                Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, Float.NaN);
+        Settings.System.putFloat(mContext.getContentResolver(),
+                Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, 0.5f);
+        mAutomaticBrightnessStrategy = new AutomaticBrightnessStrategy(mContext, DISPLAY_ID);
+
+        mBrightnessConfiguration = new BrightnessConfiguration.Builder(
+                new float[]{0f, 1f}, new float[]{0, PowerManager.BRIGHTNESS_ON}).build();
+        when(mAutomaticBrightnessController.hasUserDataPoints()).thenReturn(true);
+        mAutomaticBrightnessStrategy.setAutomaticBrightnessController(
+                mAutomaticBrightnessController);
+        mAutomaticBrightnessStrategy.setBrightnessConfiguration(mBrightnessConfiguration,
+                true);
+    }
+
+    @After
+    public void after() {
+        Settings.System.putFloat(mContext.getContentResolver(),
+                Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, mDefaultScreenAutoBrightnessAdjustment);
+    }
+
+    @Test
+    public void testAutoBrightnessState_AutoBrightnessDisabled() {
+        mAutomaticBrightnessStrategy.setUseAutoBrightness(false);
+        int targetDisplayState = Display.STATE_ON;
+        boolean allowAutoBrightnessWhileDozing = false;
+        int brightnessReason = BrightnessReason.REASON_UNKNOWN;
+        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_DisplayIsOff() {
+        mAutomaticBrightnessStrategy.setUseAutoBrightness(true);
+        int targetDisplayState = Display.STATE_OFF;
+        boolean allowAutoBrightnessWhileDozing = false;
+        int brightnessReason = BrightnessReason.REASON_UNKNOWN;
+        int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_OFF;
+        float lastUserSetBrightness = 0.2f;
+        boolean userSetBrightnessChanged = true;
+        mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments(true);
+        mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
+                allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness,
+                userSetBrightnessChanged);
+        verify(mAutomaticBrightnessController)
+                .configure(AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE,
+                        mBrightnessConfiguration,
+                        lastUserSetBrightness,
+                        userSetBrightnessChanged, 0.5f,
+                        false, policy, true);
+        assertFalse(mAutomaticBrightnessStrategy.isAutoBrightnessEnabled());
+        assertTrue(mAutomaticBrightnessStrategy.isAutoBrightnessDisabledDueToDisplayOff());
+    }
+
+    @Test
+    public void testAutoBrightnessState_DisplayIsInDoze_ConfigDoesNotAllow() {
+        mAutomaticBrightnessStrategy.setUseAutoBrightness(true);
+        int targetDisplayState = Display.STATE_DOZE;
+        boolean allowAutoBrightnessWhileDozing = false;
+        int brightnessReason = BrightnessReason.REASON_UNKNOWN;
+        int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE;
+        float lastUserSetBrightness = 0.2f;
+        boolean userSetBrightnessChanged = true;
+        mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments(true);
+        mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
+                allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness,
+                userSetBrightnessChanged);
+        verify(mAutomaticBrightnessController)
+                .configure(AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE,
+                        mBrightnessConfiguration,
+                        lastUserSetBrightness,
+                        userSetBrightnessChanged, 0.5f,
+                        false, policy, true);
+        assertFalse(mAutomaticBrightnessStrategy.isAutoBrightnessEnabled());
+        assertTrue(mAutomaticBrightnessStrategy.isAutoBrightnessDisabledDueToDisplayOff());
+    }
+
+    @Test
+    public void testAutoBrightnessState_BrightnessReasonIsOverride() {
+        mAutomaticBrightnessStrategy.setUseAutoBrightness(true);
+        int targetDisplayState = Display.STATE_ON;
+        boolean allowAutoBrightnessWhileDozing = false;
+        int brightnessReason = BrightnessReason.REASON_OVERRIDE;
+        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;
+        boolean allowAutoBrightnessWhileDozing = true;
+        int brightnessReason = BrightnessReason.REASON_DOZE;
+        int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE;
+        float lastUserSetBrightness = 0.2f;
+        boolean userSetBrightnessChanged = true;
+        Settings.System.putFloat(mContext.getContentResolver(),
+                Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, 0.4f);
+        mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments(false);
+        mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
+                allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness,
+                userSetBrightnessChanged);
+        verify(mAutomaticBrightnessController)
+                .configure(AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED,
+                        mBrightnessConfiguration,
+                        lastUserSetBrightness,
+                        userSetBrightnessChanged, 0.4f,
+                        true, policy, true);
+        assertTrue(mAutomaticBrightnessStrategy.isAutoBrightnessEnabled());
+        assertFalse(mAutomaticBrightnessStrategy.isAutoBrightnessDisabledDueToDisplayOff());
+    }
+
+    @Test
+    public void testAutoBrightnessState_DisplayIsOn() {
+        mAutomaticBrightnessStrategy.setUseAutoBrightness(true);
+        int targetDisplayState = Display.STATE_ON;
+        boolean allowAutoBrightnessWhileDozing = false;
+        int brightnessReason = BrightnessReason.REASON_UNKNOWN;
+        float lastUserSetBrightness = 0.2f;
+        boolean userSetBrightnessChanged = true;
+        int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
+        float pendingBrightnessAdjustment = 0.1f;
+        Settings.System.putFloat(mContext.getContentResolver(),
+                Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, pendingBrightnessAdjustment);
+        mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments(false);
+        mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
+                allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness,
+                userSetBrightnessChanged);
+        verify(mAutomaticBrightnessController)
+                .configure(AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED,
+                        mBrightnessConfiguration,
+                        lastUserSetBrightness,
+                        userSetBrightnessChanged, pendingBrightnessAdjustment,
+                        true, policy, true);
+        assertTrue(mAutomaticBrightnessStrategy.isAutoBrightnessEnabled());
+        assertFalse(mAutomaticBrightnessStrategy.isAutoBrightnessDisabledDueToDisplayOff());
+    }
+
+    @Test
+    public void accommodateUserBrightnessChangesWorksAsExpected() {
+        // Verify the state if automaticBrightnessController is configured.
+        assertFalse(mAutomaticBrightnessStrategy.isShortTermModelActive());
+        boolean userSetBrightnessChanged = true;
+        float lastUserSetScreenBrightness = 0.2f;
+        int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
+        BrightnessConfiguration brightnessConfiguration = new BrightnessConfiguration.Builder(
+                new float[]{0f, 1f}, new float[]{0, PowerManager.BRIGHTNESS_ON}).build();
+        int autoBrightnessState = AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED;
+        float temporaryAutoBrightnessAdjustments = 0.4f;
+        mAutomaticBrightnessStrategy.setShouldResetShortTermModel(true);
+        setTemporaryAutoBrightnessAdjustment(temporaryAutoBrightnessAdjustments);
+        mAutomaticBrightnessStrategy.accommodateUserBrightnessChanges(userSetBrightnessChanged,
+                lastUserSetScreenBrightness, policy, brightnessConfiguration,
+                autoBrightnessState);
+        verify(mAutomaticBrightnessController).configure(autoBrightnessState,
+                brightnessConfiguration,
+                lastUserSetScreenBrightness,
+                userSetBrightnessChanged, temporaryAutoBrightnessAdjustments,
+                /* userChangedAutoBrightnessAdjustment= */ false, policy,
+                /* shouldResetShortTermModel= */ true);
+        assertTrue(mAutomaticBrightnessStrategy.isTemporaryAutoBrightnessAdjustmentApplied());
+        assertFalse(mAutomaticBrightnessStrategy.shouldResetShortTermModel());
+        assertTrue(mAutomaticBrightnessStrategy.isShortTermModelActive());
+        // Verify the state when automaticBrightnessController is not configured
+        setTemporaryAutoBrightnessAdjustment(Float.NaN);
+        mAutomaticBrightnessStrategy.setAutomaticBrightnessController(null);
+        mAutomaticBrightnessStrategy.setShouldResetShortTermModel(true);
+        mAutomaticBrightnessStrategy.accommodateUserBrightnessChanges(userSetBrightnessChanged,
+                lastUserSetScreenBrightness, policy, brightnessConfiguration,
+                autoBrightnessState);
+        assertFalse(mAutomaticBrightnessStrategy.isTemporaryAutoBrightnessAdjustmentApplied());
+        assertTrue(mAutomaticBrightnessStrategy.shouldResetShortTermModel());
+        assertFalse(mAutomaticBrightnessStrategy.isShortTermModelActive());
+    }
+
+    @Test
+    public void adjustAutomaticBrightnessStateIfValid() throws Settings.SettingNotFoundException {
+        float brightnessState = 0.3f;
+        float autoBrightnessAdjustment = 0.2f;
+        when(mAutomaticBrightnessController.getAutomaticScreenBrightnessAdjustment()).thenReturn(
+                autoBrightnessAdjustment);
+        mAutomaticBrightnessStrategy.adjustAutomaticBrightnessStateIfValid(brightnessState);
+        assertEquals(autoBrightnessAdjustment,
+                mAutomaticBrightnessStrategy.getAutoBrightnessAdjustment(), 0.0f);
+        assertEquals(autoBrightnessAdjustment, Settings.System.getFloatForUser(
+                mContext.getContentResolver(),
+                Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ,
+                UserHandle.USER_CURRENT), 0.0f);
+        assertEquals(BrightnessReason.ADJUSTMENT_AUTO,
+                mAutomaticBrightnessStrategy.getAutoBrightnessAdjustmentReasonsFlags());
+        float invalidBrightness = -0.5f;
+        mAutomaticBrightnessStrategy
+                .adjustAutomaticBrightnessStateIfValid(invalidBrightness);
+        assertEquals(autoBrightnessAdjustment,
+                mAutomaticBrightnessStrategy.getAutoBrightnessAdjustment(), 0.0f);
+        assertEquals(0,
+                mAutomaticBrightnessStrategy.getAutoBrightnessAdjustmentReasonsFlags());
+    }
+
+    @Test
+    public void updatePendingAutoBrightnessAdjustments() {
+        // Verify the state when the pendingAutoBrightnessAdjustments are not present
+        setPendingAutoBrightnessAdjustment(Float.NaN);
+        assertFalse(mAutomaticBrightnessStrategy.processPendingAutoBrightnessAdjustments());
+        assertFalse(mAutomaticBrightnessStrategy.getAutoBrightnessAdjustmentChanged());
+        // Verify the state when the pendingAutoBrightnessAdjustments are present, but
+        // pendingAutoBrightnessAdjustments and autoBrightnessAdjustments are the same
+        float autoBrightnessAdjustment = 0.3f;
+        setPendingAutoBrightnessAdjustment(autoBrightnessAdjustment);
+        setAutoBrightnessAdjustment(autoBrightnessAdjustment);
+        assertFalse(mAutomaticBrightnessStrategy.processPendingAutoBrightnessAdjustments());
+        assertFalse(mAutomaticBrightnessStrategy.getAutoBrightnessAdjustmentChanged());
+        assertEquals(Float.NaN, mAutomaticBrightnessStrategy.getPendingAutoBrightnessAdjustment(),
+                0.0f);
+        // Verify the state when the pendingAutoBrightnessAdjustments are present, and
+        // pendingAutoBrightnessAdjustments and autoBrightnessAdjustments are not the same
+        float pendingAutoBrightnessAdjustment = 0.2f;
+        setPendingAutoBrightnessAdjustment(pendingAutoBrightnessAdjustment);
+        setTemporaryAutoBrightnessAdjustment(0.1f);
+        assertTrue(mAutomaticBrightnessStrategy.processPendingAutoBrightnessAdjustments());
+        assertTrue(mAutomaticBrightnessStrategy.getAutoBrightnessAdjustmentChanged());
+        assertEquals(pendingAutoBrightnessAdjustment,
+                mAutomaticBrightnessStrategy.getAutoBrightnessAdjustment(), 0.0f);
+        assertEquals(Float.NaN, mAutomaticBrightnessStrategy.getPendingAutoBrightnessAdjustment(),
+                0.0f);
+        assertEquals(Float.NaN, mAutomaticBrightnessStrategy.getTemporaryAutoBrightnessAdjustment(),
+                0.0f);
+    }
+
+    @Test
+    public void setAutomaticBrightnessWorksAsExpected() {
+        float automaticScreenBrightness = 0.3f;
+        AutomaticBrightnessController automaticBrightnessController = mock(
+                AutomaticBrightnessController.class);
+        when(automaticBrightnessController.getAutomaticScreenBrightness(any(BrightnessEvent.class)))
+                .thenReturn(automaticScreenBrightness);
+        mAutomaticBrightnessStrategy.setAutomaticBrightnessController(
+                automaticBrightnessController);
+        assertEquals(automaticScreenBrightness,
+                mAutomaticBrightnessStrategy.getAutomaticScreenBrightness(
+                        new BrightnessEvent(DISPLAY_ID)), 0.0f);
+    }
+
+    @Test
+    public void shouldUseAutoBrightness() {
+        mAutomaticBrightnessStrategy.setUseAutoBrightness(true);
+        assertTrue(mAutomaticBrightnessStrategy.shouldUseAutoBrightness());
+    }
+
+    @Test
+    public void setPendingAutoBrightnessAdjustments() throws Settings.SettingNotFoundException {
+        float pendingAutoBrightnessAdjustments = 0.3f;
+        setPendingAutoBrightnessAdjustment(pendingAutoBrightnessAdjustments);
+        assertEquals(pendingAutoBrightnessAdjustments,
+                mAutomaticBrightnessStrategy.getPendingAutoBrightnessAdjustment(), 0.0f);
+        assertEquals(pendingAutoBrightnessAdjustments, Settings.System.getFloatForUser(
+                mContext.getContentResolver(),
+                Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ,
+                UserHandle.USER_CURRENT), 0.0f);
+    }
+
+    @Test
+    public void setTemporaryAutoBrightnessAdjustment() {
+        float temporaryAutoBrightnessAdjustment = 0.3f;
+        mAutomaticBrightnessStrategy.setTemporaryAutoBrightnessAdjustment(
+                temporaryAutoBrightnessAdjustment);
+        assertEquals(temporaryAutoBrightnessAdjustment,
+                mAutomaticBrightnessStrategy.getTemporaryAutoBrightnessAdjustment(), 0.0f);
+    }
+
+    @Test
+    public void setAutoBrightnessApplied() {
+        mAutomaticBrightnessStrategy.setAutoBrightnessApplied(true);
+        assertTrue(mAutomaticBrightnessStrategy.hasAppliedAutoBrightness());
+    }
+
+    @Test
+    public void testVerifyNoAutoBrightnessAdjustmentsArePopulatedForNonDefaultDisplay() {
+        int newDisplayId = 1;
+        mAutomaticBrightnessStrategy = new AutomaticBrightnessStrategy(mContext, newDisplayId);
+        mAutomaticBrightnessStrategy.putAutoBrightnessAdjustmentSetting(0.3f);
+        assertEquals(0.5f, mAutomaticBrightnessStrategy.getAutoBrightnessAdjustment(),
+                0.0f);
+    }
+
+    private void setPendingAutoBrightnessAdjustment(float pendingAutoBrightnessAdjustment) {
+        Settings.System.putFloat(mContext.getContentResolver(),
+                Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, pendingAutoBrightnessAdjustment);
+        mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments(false);
+    }
+
+    private void setTemporaryAutoBrightnessAdjustment(float temporaryAutoBrightnessAdjustment) {
+        mAutomaticBrightnessStrategy.setTemporaryAutoBrightnessAdjustment(
+                temporaryAutoBrightnessAdjustment);
+    }
+
+    private void setAutoBrightnessAdjustment(float autoBrightnessAdjustment) {
+        mAutomaticBrightnessStrategy.putAutoBrightnessAdjustmentSetting(autoBrightnessAdjustment);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/strategy/BoostBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/BoostBrightnessStrategyTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/display/brightness/strategy/BoostBrightnessStrategyTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/BoostBrightnessStrategyTest.java
diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/strategy/DozeBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/DozeBrightnessStrategyTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/display/brightness/strategy/DozeBrightnessStrategyTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/DozeBrightnessStrategyTest.java
diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/strategy/FollowerBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/FollowerBrightnessStrategyTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/display/brightness/strategy/FollowerBrightnessStrategyTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/FollowerBrightnessStrategyTest.java
diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/strategy/OverrideBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/OverrideBrightnessStrategyTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/display/brightness/strategy/OverrideBrightnessStrategyTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/OverrideBrightnessStrategyTest.java
diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategyTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategyTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategyTest.java
diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/strategy/TemporaryBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/TemporaryBrightnessStrategyTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/display/brightness/strategy/TemporaryBrightnessStrategyTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/TemporaryBrightnessStrategyTest.java
diff --git a/services/tests/servicestests/src/com/android/server/display/color/AppSaturationControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/color/AppSaturationControllerTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/display/color/AppSaturationControllerTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/color/AppSaturationControllerTest.java
diff --git a/services/tests/servicestests/src/com/android/server/display/color/CctEvaluatorTest.java b/services/tests/displayservicetests/src/com/android/server/display/color/CctEvaluatorTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/display/color/CctEvaluatorTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/color/CctEvaluatorTest.java
diff --git a/services/tests/displayservicetests/src/com/android/server/display/color/ColorDisplayServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/color/ColorDisplayServiceTest.java
new file mode 100644
index 0000000..c7c09b5
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/color/ColorDisplayServiceTest.java
@@ -0,0 +1,1406 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.color;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.annotation.NonNull;
+import android.app.ActivityManager;
+import android.app.AlarmManager;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.res.Resources;
+import android.hardware.display.ColorDisplayManager;
+import android.hardware.display.DisplayManagerInternal;
+import android.hardware.display.Time;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.provider.Settings.Secure;
+import android.provider.Settings.System;
+import android.test.mock.MockContentResolver;
+import android.view.Display;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.R;
+import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.server.LocalServices;
+import com.android.server.SystemService;
+import com.android.server.twilight.TwilightListener;
+import com.android.server.twilight.TwilightManager;
+import com.android.server.twilight.TwilightState;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mockito;
+
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.ZoneId;
+import java.util.Calendar;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidJUnit4.class)
+public class ColorDisplayServiceTest {
+
+    private Context mContext;
+    private int mUserId;
+
+    private MockTwilightManager mTwilightManager;
+    private DisplayTransformManager mDisplayTransformManager;
+    private DisplayManagerInternal mDisplayManagerInternal;
+
+    private ColorDisplayService mCds;
+    private ColorDisplayService.BinderService mBinderService;
+
+    private Resources mResourcesSpy;
+
+    private static final int[] MINIMAL_COLOR_MODES = new int[] {
+        ColorDisplayManager.COLOR_MODE_NATURAL,
+        ColorDisplayManager.COLOR_MODE_BOOSTED,
+    };
+
+    @Before
+    public void setUp() {
+        mContext = Mockito.spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
+        doReturn(mContext).when(mContext).getApplicationContext();
+
+        final Resources res = Mockito.spy(mContext.getResources());
+        doReturn(MINIMAL_COLOR_MODES).when(res).getIntArray(R.array.config_availableColorModes);
+        doReturn(true).when(res).getBoolean(R.bool.config_nightDisplayAvailable);
+        doReturn(true).when(res).getBoolean(R.bool.config_displayWhiteBalanceAvailable);
+        when(mContext.getResources()).thenReturn(res);
+        mResourcesSpy = res;
+
+        mUserId = ActivityManager.getCurrentUser();
+
+        final MockContentResolver cr = new MockContentResolver(mContext);
+        cr.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
+        doReturn(cr).when(mContext).getContentResolver();
+
+        final AlarmManager am = Mockito.mock(AlarmManager.class);
+        doReturn(am).when(mContext).getSystemService(Context.ALARM_SERVICE);
+
+        mTwilightManager = new MockTwilightManager();
+        LocalServices.addService(TwilightManager.class, mTwilightManager);
+
+        mDisplayTransformManager = Mockito.mock(DisplayTransformManager.class);
+        doReturn(true).when(mDisplayTransformManager).needsLinearColorMatrix();
+        LocalServices.addService(DisplayTransformManager.class, mDisplayTransformManager);
+
+        mDisplayManagerInternal = Mockito.mock(DisplayManagerInternal.class);
+        LocalServices.removeServiceForTest(DisplayManagerInternal.class);
+        LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternal);
+
+        mCds = new ColorDisplayService(mContext);
+        mBinderService = mCds.new BinderService();
+        LocalServices.addService(ColorDisplayService.ColorDisplayServiceInternal.class,
+                mCds.new ColorDisplayServiceInternal());
+    }
+
+    @After
+    public void tearDown() {
+        /*
+         * Wait for internal {@link Handler} to finish processing pending messages, so that test
+         * code can safelyremove {@link DisplayTransformManager} mock from {@link LocalServices}.
+         */
+        mCds.mHandler.runWithScissors(() -> { /* nop */ }, /* timeout */ 1000);
+        mCds = null;
+
+        LocalServices.removeServiceForTest(TwilightManager.class);
+        mTwilightManager = null;
+
+        LocalServices.removeServiceForTest(DisplayTransformManager.class);
+
+        mUserId = UserHandle.USER_NULL;
+        mContext = null;
+
+        FakeSettingsProvider.clearSettingsProvider();
+
+        LocalServices.removeServiceForTest(ColorDisplayService.ColorDisplayServiceInternal.class);
+        LocalServices.removeServiceForTest(DisplayManagerInternal.class);
+    }
+
+    @Test
+    public void customSchedule_whenStartedAfterNight_ifOffAfterNight_turnsOff() {
+        setAutoModeCustom(-120 /* startTimeOffset */, -60 /* endTimeOffset */);
+        setNightDisplayActivated(false /* activated */, -30 /* lastActivatedTimeOffset */);
+
+        startService();
+        assertActivated(false /* activated */);
+    }
+
+    @Test
+    public void customSchedule_whenStartedAfterNight_ifOffBeforeNight_turnsOff() {
+        setAutoModeCustom(-120 /* startTimeOffset */, -60 /* endTimeOffset */);
+        setNightDisplayActivated(false /* activated */, -180 /* lastActivatedTimeOffset */);
+
+        startService();
+        assertActivated(false /* activated */);
+    }
+
+    @Test
+    public void customSchedule_whenStartedAfterNight_ifOffDuringNight_turnsOff() {
+        setAutoModeCustom(-120 /* startTimeOffset */, -60 /* endTimeOffset */);
+        setNightDisplayActivated(false /* activated */, -90 /* lastActivatedTimeOffset */);
+
+        startService();
+        assertActivated(false /* activated */);
+    }
+
+    @Test
+    public void customSchedule_whenStartedAfterNight_ifOffInFuture_turnsOff() {
+        setAutoModeCustom(-120 /* startTimeOffset */, -60 /* endTimeOffset */);
+        setNightDisplayActivated(false /* activated */, 30 /* lastActivatedTimeOffset */);
+
+        startService();
+        assertActivated(false /* activated */);
+    }
+
+    @Test
+    public void customSchedule_whenStartedAfterNight_ifOnAfterNight_turnsOn() {
+        setAutoModeCustom(-120 /* startTimeOffset */, -60 /* endTimeOffset */);
+        setNightDisplayActivated(true /* activated */, -30 /* lastActivatedTimeOffset */);
+
+        startService();
+        assertActivated(true /* activated */);
+    }
+
+    @Test
+    public void customSchedule_whenStartedAfterNight_ifOnBeforeNight_turnsOff() {
+        setAutoModeCustom(-120 /* startTimeOffset */, -60 /* endTimeOffset */);
+        setNightDisplayActivated(true /* activated */, -180 /* lastActivatedTimeOffset */);
+
+        startService();
+        assertActivated(false /* activated */);
+    }
+
+    @Test
+    public void customSchedule_whenStartedAfterNight_ifOnDuringNight_turnsOff() {
+        setAutoModeCustom(-120 /* startTimeOffset */, -60 /* endTimeOffset */);
+        setNightDisplayActivated(true /* activated */, -90 /* lastActivatedTimeOffset */);
+
+        startService();
+        assertActivated(false /* activated */);
+    }
+
+    @Test
+    public void customSchedule_whenStartedAfterNight_ifOnInFuture_turnsOff() {
+        setAutoModeCustom(-120 /* startTimeOffset */, -60 /* endTimeOffset */);
+        setNightDisplayActivated(true /* activated */, 30 /* lastActivatedTimeOffset */);
+
+        startService();
+        assertActivated(false /* activated */);
+    }
+
+    @Test
+    public void customSchedule_whenStartedBeforeNight_ifOffAfterNight_turnsOff() {
+        setAutoModeCustom(60 /* startTimeOffset */, 120 /* endTimeOffset */);
+        setNightDisplayActivated(false /* activated */, 180 /* lastActivatedTimeOffset */);
+
+        startService();
+        assertActivated(false /* activated */);
+    }
+
+    @Test
+    public void customSchedule_whenStartedBeforeNight_ifOffBeforeNight_turnsOff() {
+        setAutoModeCustom(60 /* startTimeOffset */, 120 /* endTimeOffset */);
+        setNightDisplayActivated(false /* activated */, 30 /* lastActivatedTimeOffset */);
+
+        startService();
+        assertActivated(false /* activated */);
+    }
+
+    @Test
+    public void customSchedule_whenStartedBeforeNight_ifOffDuringNight_turnsOff() {
+        setAutoModeCustom(60 /* startTimeOffset */, 120 /* endTimeOffset */);
+        setNightDisplayActivated(false /* activated */, 90 /* lastActivatedTimeOffset */);
+
+        startService();
+        assertActivated(false /* activated */);
+    }
+
+    @Test
+    public void customSchedule_whenStartedBeforeNight_ifOffInPast_turnsOff() {
+        setAutoModeCustom(60 /* startTimeOffset */, 120 /* endTimeOffset */);
+        setNightDisplayActivated(false /* activated */, -30 /* lastActivatedTimeOffset */);
+
+        startService();
+        assertActivated(false /* activated */);
+    }
+
+    @Test
+    public void customSchedule_whenStartedBeforeNight_ifOnAfterNight_turnsOff() {
+        setAutoModeCustom(60 /* startTimeOffset */, 120 /* endTimeOffset */);
+        setNightDisplayActivated(true /* activated */, 180 /* lastActivatedTimeOffset */);
+
+        startService();
+        assertActivated(false /* activated */);
+    }
+
+    @Test
+    public void customSchedule_whenStartedBeforeNight_ifOnBeforeNight_turnsOff() {
+        setAutoModeCustom(60 /* startTimeOffset */, 120 /* endTimeOffset */);
+        setNightDisplayActivated(true /* activated */, 30 /* lastActivatedTimeOffset */);
+
+        startService();
+        assertActivated(false /* activated */);
+    }
+
+    @Test
+    public void customSchedule_whenStartedBeforeNight_ifOnDuringNight_turnsOff() {
+        setAutoModeCustom(60 /* startTimeOffset */, 120 /* endTimeOffset */);
+        setNightDisplayActivated(true /* activated */, 90 /* lastActivatedTimeOffset */);
+
+        startService();
+        assertActivated(false /* activated */);
+    }
+
+    @Test
+    public void customSchedule_whenStartedBeforeNight_ifOnInPast_turnsOn() {
+        setAutoModeCustom(60 /* startTimeOffset */, 120 /* endTimeOffset */);
+        setNightDisplayActivated(true /* activated */, -30 /* lastActivatedTimeOffset */);
+
+        startService();
+        assertActivated(true /* activated */);
+    }
+
+    @Test
+    public void customSchedule_whenStartedDuringNight_ifOffAfterNight_turnsOn() {
+        setAutoModeCustom(-60 /* startTimeOffset */, 60 /* endTimeOffset */);
+        setNightDisplayActivated(false /* activated */, 90 /* lastActivatedTimeOffset */);
+
+        startService();
+        assertActivated(true /* activated */);
+    }
+
+    @Test
+    public void customSchedule_whenStartedDuringNight_ifOffBeforeNight_turnsOn() {
+        setAutoModeCustom(-60 /* startTimeOffset */, 60 /* endTimeOffset */);
+        setNightDisplayActivated(false /* activated */, -90 /* lastActivatedTimeOffset */);
+
+        startService();
+        assertActivated(true /* activated */);
+    }
+
+    @Test
+    public void customSchedule_whenStartedDuringNight_ifOffDuringNightInFuture_turnsOn() {
+        setAutoModeCustom(-60 /* startTimeOffset */, 60 /* endTimeOffset */);
+        setNightDisplayActivated(false /* activated */, 30 /* lastActivatedTimeOffset */);
+
+        startService();
+        assertActivated(true /* activated */);
+    }
+
+    @Test
+    public void customSchedule_whenStartedDuringNight_ifOffDuringNightInPast_turnsOff() {
+        setAutoModeCustom(-60 /* startTimeOffset */, 60 /* endTimeOffset */);
+        setNightDisplayActivated(false /* activated */, -30 /* lastActivatedTimeOffset */);
+
+        startService();
+        assertActivated(false /* activated */);
+    }
+
+    @Test
+    public void customSchedule_whenStartedDuringNight_ifOnAfterNight_turnsOn() {
+        setAutoModeCustom(-60 /* startTimeOffset */, 60 /* endTimeOffset */);
+        setNightDisplayActivated(true /* activated */, 90 /* lastActivatedTimeOffset */);
+
+        startService();
+        assertActivated(true /* activated */);
+    }
+
+    @Test
+    public void customSchedule_whenStartedDuringNight_ifOnBeforeNight_turnsOn() {
+        setAutoModeCustom(-60 /* startTimeOffset */, 60 /* endTimeOffset */);
+        setNightDisplayActivated(true /* activated */, -90 /* lastActivatedTimeOffset */);
+
+        startService();
+        assertActivated(true /* activated */);
+    }
+
+    @Test
+    public void customSchedule_whenStartedDuringNight_ifOnDuringNightInFuture_turnsOn() {
+        setAutoModeCustom(-60 /* startTimeOffset */, 60 /* endTimeOffset */);
+        setNightDisplayActivated(true /* activated */, 30 /* lastActivatedTimeOffset */);
+
+        startService();
+        assertActivated(true /* activated */);
+    }
+
+    @Test
+    public void customSchedule_whenStartedDuringNight_ifOnDuringNightInPast_turnsOn() {
+        setAutoModeCustom(-60 /* startTimeOffset */, 60 /* endTimeOffset */);
+        setNightDisplayActivated(true /* activated */, -30 /* lastActivatedTimeOffset */);
+
+        startService();
+        assertActivated(true /* activated */);
+    }
+
+    @Test
+    public void twilightSchedule_whenStartedAfterNight_ifOffAfterNight_turnsOff() {
+        setAutoModeTwilight(-120 /* sunsetOffset */, -60 /* sunriseOffset */);
+        setNightDisplayActivated(false /* activated */, -30 /* lastActivatedTimeOffset */);
+
+        startService();
+        assertActivated(false /* activated */);
+    }
+
+    @Test
+    public void twilightSchedule_whenStartedAfterNight_ifOffBeforeNight_turnsOff() {
+        setAutoModeTwilight(-120 /* sunsetOffset */, -60 /* sunriseOffset */);
+        setNightDisplayActivated(false /* activated */, -180 /* lastActivatedTimeOffset */);
+
+        startService();
+        assertActivated(false /* activated */);
+    }
+
+    @Test
+    public void twilightSchedule_whenStartedAfterNight_ifOffDuringNight_turnsOff() {
+        setAutoModeTwilight(-120 /* sunsetOffset */, -60 /* sunriseOffset */);
+        setNightDisplayActivated(false /* activated */, -90 /* lastActivatedTimeOffset */);
+
+        startService();
+        assertActivated(false /* activated */);
+    }
+
+    @Test
+    public void twilightSchedule_whenStartedAfterNight_ifOffInFuture_turnsOff() {
+        setAutoModeTwilight(-120 /* sunsetOffset */, -60 /* sunriseOffset */);
+        setNightDisplayActivated(false /* activated */, 30 /* lastActivatedTimeOffset */);
+
+        startService();
+        assertActivated(false /* activated */);
+    }
+
+    @Test
+    public void twilightSchedule_whenStartedAfterNight_ifOnAfterNight_turnsOn() {
+        setAutoModeTwilight(-120 /* sunsetOffset */, -60 /* sunriseOffset */);
+        setNightDisplayActivated(true /* activated */, -30 /* lastActivatedTimeOffset */);
+
+        startService();
+        assertActivated(true /* activated */);
+    }
+
+    @Test
+    public void twilightSchedule_whenStartedAfterNight_ifOnBeforeNight_turnsOff() {
+        setAutoModeTwilight(-120 /* sunsetOffset */, -60 /* sunriseOffset */);
+        setNightDisplayActivated(true /* activated */, -180 /* lastActivatedTimeOffset */);
+
+        startService();
+        assertActivated(false /* activated */);
+    }
+
+    @Test
+    public void twilightSchedule_whenStartedAfterNight_ifOnDuringNight_turnsOff() {
+        setAutoModeTwilight(-120 /* sunsetOffset */, -60 /* sunriseOffset */);
+        setNightDisplayActivated(true /* activated */, -90 /* lastActivatedTimeOffset */);
+
+        startService();
+        assertActivated(false /* activated */);
+    }
+
+    @Test
+    public void twilightSchedule_whenStartedAfterNight_ifOnInFuture_turnsOff() {
+        setAutoModeTwilight(-120 /* sunsetOffset */, -60 /* sunriseOffset */);
+        setNightDisplayActivated(true /* activated */, 30 /* lastActivatedTimeOffset */);
+
+        startService();
+        assertActivated(false /* activated */);
+    }
+
+    @Test
+    public void twilightSchedule_whenStartedBeforeNight_ifOffAfterNight_turnsOff() {
+        setAutoModeTwilight(60 /* sunsetOffset */, 120 /* sunriseOffset */);
+        setNightDisplayActivated(false /* activated */, 180 /* lastActivatedTimeOffset */);
+
+        startService();
+        assertActivated(false /* activated */);
+    }
+
+    @Test
+    public void twilightSchedule_whenStartedBeforeNight_ifOffBeforeNight_turnsOff() {
+        setAutoModeTwilight(60 /* sunsetOffset */, 120 /* sunriseOffset */);
+        setNightDisplayActivated(false /* activated */, 30 /* lastActivatedTimeOffset */);
+
+        startService();
+        assertActivated(false /* activated */);
+    }
+
+    @Test
+    public void twilightSchedule_whenStartedBeforeNight_ifOffDuringNight_turnsOff() {
+        setAutoModeTwilight(60 /* sunsetOffset */, 120 /* sunriseOffset */);
+        setNightDisplayActivated(false /* activated */, 90 /* lastActivatedTimeOffset */);
+
+        startService();
+        assertActivated(false /* activated */);
+    }
+
+    @Test
+    public void twilightSchedule_whenStartedBeforeNight_ifOffInPast_turnsOff() {
+        setAutoModeTwilight(60 /* sunsetOffset */, 120 /* sunriseOffset */);
+        setNightDisplayActivated(false /* activated */, -30 /* lastActivatedTimeOffset */);
+
+        startService();
+        assertActivated(false /* activated */);
+    }
+
+    @Test
+    public void twilightSchedule_whenStartedBeforeNight_ifOnAfterNight_turnsOff() {
+        setAutoModeTwilight(60 /* sunsetOffset */, 120 /* sunriseOffset */);
+        setNightDisplayActivated(true /* activated */, 180 /* lastActivatedTimeOffset */);
+
+        startService();
+        assertActivated(false /* activated */);
+    }
+
+    @Test
+    public void twilightSchedule_whenStartedBeforeNight_ifOnBeforeNight_turnsOff() {
+        setAutoModeTwilight(60 /* sunsetOffset */, 120 /* sunriseOffset */);
+        setNightDisplayActivated(true /* activated */, 30 /* lastActivatedTimeOffset */);
+
+        startService();
+        assertActivated(false /* activated */);
+    }
+
+    @Test
+    public void twilightSchedule_whenStartedBeforeNight_ifOnDuringNight_turnsOff() {
+        setAutoModeTwilight(60 /* sunsetOffset */, 120 /* sunriseOffset */);
+        setNightDisplayActivated(true /* activated */, 90 /* lastActivatedTimeOffset */);
+
+        startService();
+        assertActivated(false /* activated */);
+    }
+
+    @Test
+    public void twilightSchedule_whenStartedBeforeNight_ifOnInPast_turnsOn() {
+        setAutoModeTwilight(60 /* sunsetOffset */, 120 /* sunriseOffset */);
+        setNightDisplayActivated(true /* activated */, -30 /* lastActivatedTimeOffset */);
+
+        startService();
+        assertActivated(true /* activated */);
+    }
+
+    @Test
+    public void twilightSchedule_whenStartedDuringNight_ifOffAfterNight_turnsOn() {
+        setAutoModeTwilight(-60 /* sunsetOffset */, 60 /* sunriseOffset */);
+        setNightDisplayActivated(false /* activated */, 90 /* lastActivatedTimeOffset */);
+
+        startService();
+        assertActivated(true /* activated */);
+    }
+
+    @Test
+    public void twilightSchedule_whenStartedDuringNight_ifOffBeforeNight_turnsOn() {
+        setAutoModeTwilight(-60 /* sunsetOffset */, 60 /* sunriseOffset */);
+        setNightDisplayActivated(false /* activated */, -90 /* lastActivatedTimeOffset */);
+
+        startService();
+        assertActivated(true /* activated */);
+    }
+
+    @Test
+    public void twilightSchedule_whenStartedDuringNight_ifOffDuringNightInFuture_turnsOn() {
+        setAutoModeTwilight(-60 /* sunsetOffset */, 60 /* sunriseOffset */);
+        setNightDisplayActivated(false /* activated */, 30 /* lastActivatedTimeOffset */);
+
+        startService();
+        assertActivated(true /* activated */);
+    }
+
+    @Test
+    public void twilightSchedule_whenStartedDuringNight_ifOffDuringNightInPast_turnsOff() {
+        setAutoModeTwilight(-60 /* sunsetOffset */, 60 /* sunriseOffset */);
+        setNightDisplayActivated(false /* activated */, -30 /* lastActivatedTimeOffset */);
+
+        startService();
+        assertActivated(false /* activated */);
+    }
+
+    @Test
+    public void twilightSchedule_whenStartedDuringNight_ifOnAfterNight_turnsOn() {
+        setAutoModeTwilight(-60 /* sunsetOffset */, 60 /* sunriseOffset */);
+        setNightDisplayActivated(true /* activated */, 90 /* lastActivatedTimeOffset */);
+
+        startService();
+        assertActivated(true /* activated */);
+    }
+
+    @Test
+    public void twilightSchedule_whenStartedDuringNight_ifOnBeforeNight_turnsOn() {
+        setAutoModeTwilight(-60 /* sunsetOffset */, 60 /* sunriseOffset */);
+        setNightDisplayActivated(true /* activated */, -90 /* lastActivatedTimeOffset */);
+
+        startService();
+        assertActivated(true /* activated */);
+    }
+
+    @Test
+    public void twilightSchedule_whenStartedDuringNight_ifOnDuringNightInFuture_turnsOn() {
+        setAutoModeTwilight(-60 /* sunsetOffset */, 60 /* sunriseOffset */);
+        setNightDisplayActivated(true /* activated */, 30 /* lastActivatedTimeOffset */);
+
+        startService();
+        assertActivated(true /* activated */);
+    }
+
+    @Test
+    public void twilightSchedule_whenStartedDuringNight_ifOnDuringNightInPast_turnsOn() {
+        setAutoModeTwilight(-60 /* sunsetOffset */, 60 /* sunriseOffset */);
+        setNightDisplayActivated(true /* activated */, -30 /* lastActivatedTimeOffset */);
+
+        startService();
+        assertActivated(true /* activated */);
+    }
+
+    @Test
+    public void twilightSchedule_whenRebootedAfterNight_ifOffAfterNight_turnsOff() {
+        setAutoModeTwilight(-120 /* sunsetOffset */, -60 /* sunriseOffset */);
+        setNightDisplayActivated(false /* activated */, -30 /* lastActivatedTimeOffset */);
+
+        final TwilightState state = mTwilightManager.getLastTwilightState();
+        mTwilightManager.setTwilightState(null);
+
+        startService();
+        assertActivated(false /* activated */);
+
+        mTwilightManager.setTwilightState(state);
+        assertActivated(false /* activated */);
+    }
+
+    @Test
+    public void twilightSchedule_whenRebootedAfterNight_ifOffBeforeNight_turnsOff() {
+        setAutoModeTwilight(-120 /* sunsetOffset */, -60 /* sunriseOffset */);
+        setNightDisplayActivated(false /* activated */, -180 /* lastActivatedTimeOffset */);
+
+        final TwilightState state = mTwilightManager.getLastTwilightState();
+        mTwilightManager.setTwilightState(null);
+
+        startService();
+        assertActivated(false /* activated */);
+
+        mTwilightManager.setTwilightState(state);
+        assertActivated(false /* activated */);
+    }
+
+    @Test
+    public void twilightSchedule_whenRebootedAfterNight_ifOffDuringNight_turnsOff() {
+        setAutoModeTwilight(-120 /* sunsetOffset */, -60 /* sunriseOffset */);
+        setNightDisplayActivated(false /* activated */, -90 /* lastActivatedTimeOffset */);
+
+        final TwilightState state = mTwilightManager.getLastTwilightState();
+        mTwilightManager.setTwilightState(null);
+
+        startService();
+        assertActivated(false /* activated */);
+
+        mTwilightManager.setTwilightState(state);
+        assertActivated(false /* activated */);
+    }
+
+    @Test
+    public void twilightSchedule_whenRebootedAfterNight_ifOffInFuture_turnsOff() {
+        setAutoModeTwilight(-120 /* sunsetOffset */, -60 /* sunriseOffset */);
+        setNightDisplayActivated(false /* activated */, 30 /* lastActivatedTimeOffset */);
+
+        final TwilightState state = mTwilightManager.getLastTwilightState();
+        mTwilightManager.setTwilightState(null);
+
+        startService();
+        assertActivated(false /* activated */);
+
+        mTwilightManager.setTwilightState(state);
+        assertActivated(false /* activated */);
+    }
+
+    @Test
+    public void twilightSchedule_whenRebootedAfterNight_ifOnAfterNight_turnsOn() {
+        setAutoModeTwilight(-120 /* sunsetOffset */, -60 /* sunriseOffset */);
+        setNightDisplayActivated(true /* activated */, -30 /* lastActivatedTimeOffset */);
+
+        final TwilightState state = mTwilightManager.getLastTwilightState();
+        mTwilightManager.setTwilightState(null);
+
+        startService();
+        assertActivated(true /* activated */);
+
+        mTwilightManager.setTwilightState(state);
+        assertActivated(true /* activated */);
+    }
+
+    @Test
+    public void twilightSchedule_whenRebootedAfterNight_ifOnBeforeNight_turnsOff() {
+        setAutoModeTwilight(-120 /* sunsetOffset */, -60 /* sunriseOffset */);
+        setNightDisplayActivated(true /* activated */, -180 /* lastActivatedTimeOffset */);
+
+        final TwilightState state = mTwilightManager.getLastTwilightState();
+        mTwilightManager.setTwilightState(null);
+
+        startService();
+        assertActivated(true /* activated */);
+
+        mTwilightManager.setTwilightState(state);
+        assertActivated(false /* activated */);
+    }
+
+    @Test
+    public void twilightSchedule_whenRebootedAfterNight_ifOnDuringNight_turnsOff() {
+        setAutoModeTwilight(-120 /* sunsetOffset */, -60 /* sunriseOffset */);
+        setNightDisplayActivated(true /* activated */, -90 /* lastActivatedTimeOffset */);
+
+        final TwilightState state = mTwilightManager.getLastTwilightState();
+        mTwilightManager.setTwilightState(null);
+
+        startService();
+        assertActivated(true /* activated */);
+
+        mTwilightManager.setTwilightState(state);
+        assertActivated(false /* activated */);
+    }
+
+    @Test
+    public void twilightSchedule_whenRebootedAfterNight_ifOnInFuture_turnsOff() {
+        setAutoModeTwilight(-120 /* sunsetOffset */, -60 /* sunriseOffset */);
+        setNightDisplayActivated(true /* activated */, 30 /* lastActivatedTimeOffset */);
+
+        final TwilightState state = mTwilightManager.getLastTwilightState();
+        mTwilightManager.setTwilightState(null);
+
+        startService();
+        assertActivated(true /* activated */);
+
+        mTwilightManager.setTwilightState(state);
+        assertActivated(false /* activated */);
+    }
+
+    @Test
+    public void twilightSchedule_whenRebootedBeforeNight_ifOffAfterNight_turnsOff() {
+        setAutoModeTwilight(60 /* sunsetOffset */, 120 /* sunriseOffset */);
+        setNightDisplayActivated(false /* activated */, 180 /* lastActivatedTimeOffset */);
+
+        final TwilightState state = mTwilightManager.getLastTwilightState();
+        mTwilightManager.setTwilightState(null);
+
+        startService();
+        assertActivated(false /* activated */);
+
+        mTwilightManager.setTwilightState(state);
+        assertActivated(false /* activated */);
+    }
+
+    @Test
+    public void twilightSchedule_whenRebootedBeforeNight_ifOffBeforeNight_turnsOff() {
+        setAutoModeTwilight(60 /* sunsetOffset */, 120 /* sunriseOffset */);
+        setNightDisplayActivated(false /* activated */, 30 /* lastActivatedTimeOffset */);
+
+        final TwilightState state = mTwilightManager.getLastTwilightState();
+        mTwilightManager.setTwilightState(null);
+
+        startService();
+        assertActivated(false /* activated */);
+
+        mTwilightManager.setTwilightState(state);
+        assertActivated(false /* activated */);
+    }
+
+    @Test
+    public void twilightSchedule_whenRebootedBeforeNight_ifOffDuringNight_turnsOff() {
+        setAutoModeTwilight(60 /* sunsetOffset */, 120 /* sunriseOffset */);
+        setNightDisplayActivated(false /* activated */, 90 /* lastActivatedTimeOffset */);
+
+        final TwilightState state = mTwilightManager.getLastTwilightState();
+        mTwilightManager.setTwilightState(null);
+
+        startService();
+        assertActivated(false /* activated */);
+
+        mTwilightManager.setTwilightState(state);
+        assertActivated(false /* activated */);
+    }
+
+    @Test
+    public void twilightSchedule_whenRebootedBeforeNight_ifOffInPast_turnsOff() {
+        setAutoModeTwilight(60 /* sunsetOffset */, 120 /* sunriseOffset */);
+        setNightDisplayActivated(false /* activated */, -30 /* lastActivatedTimeOffset */);
+
+        final TwilightState state = mTwilightManager.getLastTwilightState();
+        mTwilightManager.setTwilightState(null);
+
+        startService();
+        assertActivated(false /* activated */);
+
+        mTwilightManager.setTwilightState(state);
+        assertActivated(false /* activated */);
+    }
+
+    @Test
+    public void twilightSchedule_whenRebootedBeforeNight_ifOnAfterNight_turnsOff() {
+        setAutoModeTwilight(60 /* sunsetOffset */, 120 /* sunriseOffset */);
+        setNightDisplayActivated(true /* activated */, 180 /* lastActivatedTimeOffset */);
+
+        final TwilightState state = mTwilightManager.getLastTwilightState();
+        mTwilightManager.setTwilightState(null);
+
+        startService();
+        assertActivated(true /* activated */);
+
+        mTwilightManager.setTwilightState(state);
+        assertActivated(false /* activated */);
+    }
+
+    @Test
+    public void twilightSchedule_whenRebootedBeforeNight_ifOnBeforeNight_turnsOff() {
+        setAutoModeTwilight(60 /* sunsetOffset */, 120 /* sunriseOffset */);
+        setNightDisplayActivated(true /* activated */, 30 /* lastActivatedTimeOffset */);
+
+        final TwilightState state = mTwilightManager.getLastTwilightState();
+        mTwilightManager.setTwilightState(null);
+
+        startService();
+        assertActivated(true /* activated */);
+
+        mTwilightManager.setTwilightState(state);
+        assertActivated(false /* activated */);
+    }
+
+    @Test
+    public void twilightSchedule_whenRebootedBeforeNight_ifOnDuringNight_turnsOff() {
+        setAutoModeTwilight(60 /* sunsetOffset */, 120 /* sunriseOffset */);
+        setNightDisplayActivated(true /* activated */, 90 /* lastActivatedTimeOffset */);
+
+        final TwilightState state = mTwilightManager.getLastTwilightState();
+        mTwilightManager.setTwilightState(null);
+
+        startService();
+        assertActivated(true /* activated */);
+
+        mTwilightManager.setTwilightState(state);
+        assertActivated(false /* activated */);
+    }
+
+    @Test
+    public void twilightSchedule_whenRebootedBeforeNight_ifOnInPast_turnsOn() {
+        setAutoModeTwilight(60 /* sunsetOffset */, 120 /* sunriseOffset */);
+        setNightDisplayActivated(true /* activated */, -30 /* lastActivatedTimeOffset */);
+
+        final TwilightState state = mTwilightManager.getLastTwilightState();
+        mTwilightManager.setTwilightState(null);
+
+        startService();
+        assertActivated(true /* activated */);
+
+        mTwilightManager.setTwilightState(state);
+        assertActivated(true /* activated */);
+    }
+
+    @Test
+    public void twilightSchedule_whenRebootedDuringNight_ifOffAfterNight_turnsOn() {
+        setAutoModeTwilight(-60 /* sunsetOffset */, 60 /* sunriseOffset */);
+        setNightDisplayActivated(false /* activated */, 90 /* lastActivatedTimeOffset */);
+
+        final TwilightState state = mTwilightManager.getLastTwilightState();
+        mTwilightManager.setTwilightState(null);
+
+        startService();
+        assertActivated(false /* activated */);
+
+        mTwilightManager.setTwilightState(state);
+        assertActivated(true /* activated */);
+    }
+
+    @Test
+    public void twilightSchedule_whenRebootedDuringNight_ifOffBeforeNight_turnsOn() {
+        setAutoModeTwilight(-60 /* sunsetOffset */, 60 /* sunriseOffset */);
+        setNightDisplayActivated(false /* activated */, -90 /* lastActivatedTimeOffset */);
+
+        final TwilightState state = mTwilightManager.getLastTwilightState();
+        mTwilightManager.setTwilightState(null);
+
+        startService();
+        assertActivated(false /* activated */);
+
+        mTwilightManager.setTwilightState(state);
+        assertActivated(true /* activated */);
+    }
+
+    @Test
+    public void twilightSchedule_whenRebootedDuringNight_ifOffDuringNightInFuture_turnsOn() {
+        setAutoModeTwilight(-60 /* sunsetOffset */, 60 /* sunriseOffset */);
+        setNightDisplayActivated(false /* activated */, 30 /* lastActivatedTimeOffset */);
+
+        final TwilightState state = mTwilightManager.getLastTwilightState();
+        mTwilightManager.setTwilightState(null);
+
+        startService();
+        assertActivated(false /* activated */);
+
+        mTwilightManager.setTwilightState(state);
+        assertActivated(true /* activated */);
+    }
+
+    @Test
+    public void twilightSchedule_whenRebootedDuringNight_ifOffDuringNightInPast_turnsOff() {
+        setAutoModeTwilight(-60 /* sunsetOffset */, 60 /* sunriseOffset */);
+        setNightDisplayActivated(false /* activated */, -30 /* lastActivatedTimeOffset */);
+
+        final TwilightState state = mTwilightManager.getLastTwilightState();
+        mTwilightManager.setTwilightState(null);
+
+        startService();
+        assertActivated(false /* activated */);
+
+        mTwilightManager.setTwilightState(state);
+        assertActivated(false /* activated */);
+    }
+
+    @Test
+    public void twilightSchedule_whenRebootedDuringNight_ifOnAfterNight_turnsOn() {
+        setAutoModeTwilight(-60 /* sunsetOffset */, 60 /* sunriseOffset */);
+        setNightDisplayActivated(true /* activated */, 90 /* lastActivatedTimeOffset */);
+
+        final TwilightState state = mTwilightManager.getLastTwilightState();
+        mTwilightManager.setTwilightState(null);
+
+        startService();
+        assertActivated(true /* activated */);
+
+        mTwilightManager.setTwilightState(state);
+        assertActivated(true /* activated */);
+    }
+
+    @Test
+    public void twilightSchedule_whenRebootedDuringNight_ifOnBeforeNight_turnsOn() {
+        setAutoModeTwilight(-60 /* sunsetOffset */, 60 /* sunriseOffset */);
+        setNightDisplayActivated(true /* activated */, -90 /* lastActivatedTimeOffset */);
+
+        final TwilightState state = mTwilightManager.getLastTwilightState();
+        mTwilightManager.setTwilightState(null);
+
+        startService();
+        assertActivated(true /* activated */);
+
+        mTwilightManager.setTwilightState(state);
+        assertActivated(true /* activated */);
+    }
+
+    @Test
+    public void twilightSchedule_whenRebootedDuringNight_ifOnDuringNightInFuture_turnsOn() {
+        setAutoModeTwilight(-60 /* sunsetOffset */, 60 /* sunriseOffset */);
+        setNightDisplayActivated(true /* activated */, 30 /* lastActivatedTimeOffset */);
+
+        final TwilightState state = mTwilightManager.getLastTwilightState();
+        mTwilightManager.setTwilightState(null);
+
+        startService();
+        assertActivated(true /* activated */);
+
+        mTwilightManager.setTwilightState(state);
+        assertActivated(true /* activated */);
+    }
+
+    @Test
+    public void twilightSchedule_whenRebootedDuringNight_ifOnDuringNightInPast_turnsOn() {
+        setAutoModeTwilight(-60 /* sunsetOffset */, 60 /* sunriseOffset */);
+        setNightDisplayActivated(true /* activated */, -30 /* lastActivatedTimeOffset */);
+
+        final TwilightState state = mTwilightManager.getLastTwilightState();
+        mTwilightManager.setTwilightState(null);
+
+        startService();
+        assertActivated(true /* activated */);
+
+        mTwilightManager.setTwilightState(state);
+        assertActivated(true /* activated */);
+    }
+
+    @Test
+    public void accessibility_colorInversion_transformActivated() {
+        if (!mContext.getResources().getConfiguration().isScreenWideColorGamut()) {
+            return;
+        }
+
+        setAccessibilityColorInversion(true);
+        setColorMode(ColorDisplayManager.COLOR_MODE_NATURAL);
+
+        startService();
+        assertUserColorMode(ColorDisplayManager.COLOR_MODE_NATURAL);
+        assertActiveColorMode(mContext.getResources().getInteger(
+                R.integer.config_accessibilityColorMode));
+    }
+
+    @Test
+    public void accessibility_colorCorrection_transformActivated() {
+        if (!mContext.getResources().getConfiguration().isScreenWideColorGamut()) {
+            return;
+        }
+
+        setAccessibilityColorCorrection(true);
+        setColorMode(ColorDisplayManager.COLOR_MODE_NATURAL);
+
+        startService();
+        assertUserColorMode(ColorDisplayManager.COLOR_MODE_NATURAL);
+        assertActiveColorMode(mContext.getResources().getInteger(
+                R.integer.config_accessibilityColorMode));
+    }
+
+    @Test
+    public void accessibility_all_transformActivated() {
+        if (!mContext.getResources().getConfiguration().isScreenWideColorGamut()) {
+            return;
+        }
+
+        setAccessibilityColorCorrection(true);
+        setAccessibilityColorInversion(true);
+        setColorMode(ColorDisplayManager.COLOR_MODE_NATURAL);
+
+        startService();
+        assertUserColorMode(ColorDisplayManager.COLOR_MODE_NATURAL);
+        assertActiveColorMode(mContext.getResources().getInteger(
+                R.integer.config_accessibilityColorMode));
+    }
+
+    @Test
+    public void accessibility_none_transformActivated() {
+        if (!mContext.getResources().getConfiguration().isScreenWideColorGamut()) {
+            return;
+        }
+
+        setAccessibilityColorCorrection(false);
+        setAccessibilityColorInversion(false);
+        setColorMode(ColorDisplayManager.COLOR_MODE_NATURAL);
+
+        startService();
+        assertUserColorMode(ColorDisplayManager.COLOR_MODE_NATURAL);
+        assertActiveColorMode(ColorDisplayManager.COLOR_MODE_NATURAL);
+    }
+
+    @Test
+    public void displayWhiteBalance_enabled() {
+        setDisplayWhiteBalanceEnabled(true);
+        setNightDisplayActivated(false /* activated */, -30 /* lastActivatedTimeOffset */);
+        mBinderService.setColorMode(ColorDisplayManager.COLOR_MODE_NATURAL);
+        startService();
+        assertDwbActive(true);
+    }
+
+    @Test
+    public void displayWhiteBalance_disabledAfterNightDisplayEnabled() {
+        setDisplayWhiteBalanceEnabled(true);
+        startService();
+        setNightDisplayActivated(true /* activated */, -30 /* lastActivatedTimeOffset */);
+
+        /* Since we are using FakeSettingsProvider which could not trigger observer change,
+         * force an update here.*/
+        mCds.updateDisplayWhiteBalanceStatus();
+        assertDwbActive(false);
+    }
+
+    @Test
+    public void displayWhiteBalance_enabledAfterNightDisplayDisabled() {
+        setDisplayWhiteBalanceEnabled(true);
+        startService();
+        setNightDisplayActivated(true /* activated */, -30 /* lastActivatedTimeOffset */);
+
+        mCds.updateDisplayWhiteBalanceStatus();
+        assertDwbActive(false);
+
+        setNightDisplayActivated(false /* activated */, -30 /* lastActivatedTimeOffset */);
+        mCds.updateDisplayWhiteBalanceStatus();
+        assertDwbActive(true);
+    }
+
+    @Test
+    public void displayWhiteBalance_enabledAfterLinearColorModeSelected() {
+        if (!isColorModeValid(ColorDisplayManager.COLOR_MODE_SATURATED)) {
+            return;
+        }
+        setDisplayWhiteBalanceEnabled(true);
+        mBinderService.setColorMode(ColorDisplayManager.COLOR_MODE_SATURATED);
+        startService();
+        assertDwbActive(false);
+
+        mBinderService.setColorMode(ColorDisplayManager.COLOR_MODE_NATURAL);
+        mCds.updateDisplayWhiteBalanceStatus();
+        assertDwbActive(true);
+    }
+
+    @Test
+    public void displayWhiteBalance_disabledWhileAccessibilityColorCorrectionEnabled() {
+        setDisplayWhiteBalanceEnabled(true);
+        setAccessibilityColorCorrection(true);
+        startService();
+        assertDwbActive(false);
+
+        setAccessibilityColorCorrection(false);
+        mCds.updateDisplayWhiteBalanceStatus();
+        assertDwbActive(true);
+    }
+
+    @Test
+    public void displayWhiteBalance_disabledWhileAccessibilityColorInversionEnabled() {
+        setDisplayWhiteBalanceEnabled(true);
+        setAccessibilityColorInversion(true);
+        startService();
+        assertDwbActive(false);
+
+        setAccessibilityColorInversion(false);
+        mCds.updateDisplayWhiteBalanceStatus();
+        assertDwbActive(true);
+    }
+
+    @Test
+    public void compositionColorSpaces_noResources() {
+        when(mResourcesSpy.getIntArray(R.array.config_displayCompositionColorModes))
+            .thenReturn(new int[] {});
+        when(mResourcesSpy.getIntArray(R.array.config_displayCompositionColorSpaces))
+            .thenReturn(new int[] {});
+        setColorMode(ColorDisplayManager.COLOR_MODE_NATURAL);
+        startService();
+        verify(mDisplayTransformManager).setColorMode(
+                eq(ColorDisplayManager.COLOR_MODE_NATURAL), any(), eq(Display.COLOR_MODE_INVALID));
+    }
+
+    @Test
+    public void compositionColorSpaces_invalidResources() {
+        when(mResourcesSpy.getIntArray(R.array.config_displayCompositionColorModes))
+                .thenReturn(new int[] {
+                        ColorDisplayManager.COLOR_MODE_NATURAL,
+                        // Missing second color mode
+                });
+        when(mResourcesSpy.getIntArray(R.array.config_displayCompositionColorSpaces))
+                .thenReturn(new int[] {
+                        Display.COLOR_MODE_SRGB,
+                        Display.COLOR_MODE_DISPLAY_P3
+                });
+        setColorMode(ColorDisplayManager.COLOR_MODE_NATURAL);
+        startService();
+        verify(mDisplayTransformManager).setColorMode(
+                eq(ColorDisplayManager.COLOR_MODE_NATURAL), any(), eq(Display.COLOR_MODE_INVALID));
+    }
+
+    @Test
+    public void compositionColorSpaces_validResources_validColorMode() {
+        when(mResourcesSpy.getIntArray(R.array.config_displayCompositionColorModes))
+                .thenReturn(new int[] {
+                        ColorDisplayManager.COLOR_MODE_NATURAL
+                });
+        when(mResourcesSpy.getIntArray(R.array.config_displayCompositionColorSpaces))
+                .thenReturn(new int[] {
+                        Display.COLOR_MODE_SRGB,
+                });
+        setColorMode(ColorDisplayManager.COLOR_MODE_NATURAL);
+        startService();
+        verify(mDisplayTransformManager).setColorMode(
+                eq(ColorDisplayManager.COLOR_MODE_NATURAL), any(), eq(Display.COLOR_MODE_SRGB));
+    }
+
+    @Test
+    public void compositionColorSpaces_validResources_invalidColorMode() {
+        when(mResourcesSpy.getIntArray(R.array.config_displayCompositionColorModes))
+                .thenReturn(new int[] {
+                        ColorDisplayManager.COLOR_MODE_NATURAL
+                });
+        when(mResourcesSpy.getIntArray(R.array.config_displayCompositionColorSpaces))
+                .thenReturn(new int[] {
+                        Display.COLOR_MODE_SRGB,
+                });
+        setColorMode(ColorDisplayManager.COLOR_MODE_BOOSTED);
+        startService();
+        verify(mDisplayTransformManager).setColorMode(
+                eq(ColorDisplayManager.COLOR_MODE_BOOSTED), any(), eq(Display.COLOR_MODE_INVALID));
+    }
+
+    @Test
+    public void getColorMode_noAvailableModes_returnsNotSet() {
+        when(mResourcesSpy.getIntArray(R.array.config_availableColorModes))
+                    .thenReturn(new int[] {});
+        startService();
+        verify(mDisplayTransformManager, never()).setColorMode(anyInt(), any(), anyInt());
+        assertThat(mBinderService.getColorMode()).isEqualTo(-1);
+    }
+
+    /**
+     * Configures Night display to use a custom schedule.
+     *
+     * @param startTimeOffset the offset relative to now to activate Night display (in minutes)
+     * @param endTimeOffset the offset relative to now to deactivate Night display (in minutes)
+     */
+    private void setAutoModeCustom(int startTimeOffset, int endTimeOffset) {
+        mBinderService.setNightDisplayAutoMode(ColorDisplayManager.AUTO_MODE_CUSTOM_TIME);
+        mBinderService.setNightDisplayCustomStartTime(
+                new Time(getLocalTimeRelativeToNow(startTimeOffset)));
+        mBinderService
+                .setNightDisplayCustomEndTime(new Time(getLocalTimeRelativeToNow(endTimeOffset)));
+    }
+
+    /**
+     * Configures Night display to use the twilight schedule.
+     *
+     * @param sunsetOffset the offset relative to now for sunset (in minutes)
+     * @param sunriseOffset the offset relative to now for sunrise (in minutes)
+     */
+    private void setAutoModeTwilight(int sunsetOffset, int sunriseOffset) {
+        mBinderService.setNightDisplayAutoMode(ColorDisplayManager.AUTO_MODE_TWILIGHT);
+        mTwilightManager.setTwilightState(
+                getTwilightStateRelativeToNow(sunsetOffset, sunriseOffset));
+    }
+
+    /**
+     * Configures the Night display activated state.
+     *
+     * @param activated {@code true} if Night display should be activated
+     * @param lastActivatedTimeOffset the offset relative to now to record that Night display was
+     * activated (in minutes)
+     */
+    private void setNightDisplayActivated(boolean activated, int lastActivatedTimeOffset) {
+        mBinderService.setNightDisplayActivated(activated);
+        Secure.putStringForUser(mContext.getContentResolver(),
+                Secure.NIGHT_DISPLAY_LAST_ACTIVATED_TIME,
+                LocalDateTime.now().plusMinutes(lastActivatedTimeOffset).toString(),
+                mUserId);
+    }
+
+    /**
+     * Configures the Accessibility color correction setting state.
+     *
+     * @param state {@code true} if color inversion should be activated
+     */
+    private void setAccessibilityColorCorrection(boolean state) {
+        Secure.putIntForUser(mContext.getContentResolver(),
+                Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED, state ? 1 : 0, mUserId);
+    }
+
+    /**
+     * Configures the Accessibility color inversion setting state.
+     *
+     * @param state {@code true} if color inversion should be activated
+     */
+    private void setAccessibilityColorInversion(boolean state) {
+        Secure.putIntForUser(mContext.getContentResolver(),
+                Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED, state ? 1 : 0, mUserId);
+    }
+
+    /**
+     * Configures the Display White Balance setting state.
+     *
+     * @param enabled {@code true} if display white balance should be enabled
+     */
+    private void setDisplayWhiteBalanceEnabled(boolean enabled) {
+        Secure.putIntForUser(mContext.getContentResolver(),
+                Secure.DISPLAY_WHITE_BALANCE_ENABLED, enabled ? 1 : 0, mUserId);
+    }
+
+    /**
+     * Configures color mode.
+     */
+    private void setColorMode(int colorMode) {
+        mBinderService.setColorMode(colorMode);
+    }
+
+    /**
+     * Returns whether the color mode is valid on the device the tests are running on.
+     */
+    private boolean isColorModeValid(int mode) {
+        final int[] availableColorModes = mContext.getResources().getIntArray(
+                R.array.config_availableColorModes);
+        if (availableColorModes != null) {
+            for (int availableMode : availableColorModes) {
+                if (mode == availableMode) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Convenience method to start {@link #mCds}.
+     */
+    private void startService() {
+        Secure.putIntForUser(mContext.getContentResolver(), Secure.USER_SETUP_COMPLETE, 1, mUserId);
+
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+            mCds.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
+            mCds.onUserChanged(mUserId);
+        });
+    }
+
+    /**
+     * Convenience method for asserting whether Night display should be activated.
+     *
+     * @param activated the expected activated state of Night display
+     */
+    private void assertActivated(boolean activated) {
+        assertWithMessage("Incorrect Night display activated state")
+                .that(mBinderService.isNightDisplayActivated())
+                .isEqualTo(activated);
+    }
+
+    /**
+     * Convenience method for asserting that the active color mode matches expectation.
+     *
+     * @param mode the expected active color mode.
+     */
+    private void assertActiveColorMode(int mode) {
+        assertWithMessage("Unexpected color mode setting")
+                .that(mBinderService.getColorMode())
+                .isEqualTo(mode);
+    }
+
+    /**
+     * Convenience method for asserting that the user chosen color mode matches expectation.
+     *
+     * @param mode the expected color mode setting.
+     */
+    private void assertUserColorMode(int mode) {
+        final int actualMode = System.getIntForUser(mContext.getContentResolver(),
+                System.DISPLAY_COLOR_MODE, -1, mUserId);
+        assertWithMessage("Unexpected color mode setting")
+                .that(actualMode)
+                .isEqualTo(mode);
+    }
+
+    /**
+     * Convenience method for asserting that the DWB active status matches expectation.
+     *
+     * @param enabled the expected active status.
+     */
+    private void assertDwbActive(boolean enabled) {
+        assertWithMessage("Incorrect Display White Balance state")
+                .that(mCds.mDisplayWhiteBalanceTintController.isActivated())
+                .isEqualTo(enabled);
+    }
+
+    /**
+     * Convenience for making a {@link LocalTime} instance with an offset relative to now.
+     *
+     * @param offsetMinutes the offset relative to now (in minutes)
+     * @return the LocalTime instance
+     */
+    private static LocalTime getLocalTimeRelativeToNow(int offsetMinutes) {
+        final Calendar c = Calendar.getInstance();
+        c.add(Calendar.MINUTE, offsetMinutes);
+        return LocalTime.of(c.get(Calendar.HOUR_OF_DAY), c.get(Calendar.MINUTE));
+    }
+
+    /**
+     * Convenience for making a {@link TwilightState} instance with sunrise/sunset relative to now.
+     *
+     * @param sunsetOffset the offset relative to now for sunset (in minutes)
+     * @param sunriseOffset the offset relative to now for sunrise (in minutes)
+     * @return the TwilightState instance
+     */
+    private static TwilightState getTwilightStateRelativeToNow(int sunsetOffset,
+            int sunriseOffset) {
+        final LocalTime sunset = getLocalTimeRelativeToNow(sunsetOffset);
+        final LocalTime sunrise = getLocalTimeRelativeToNow(sunriseOffset);
+
+        final LocalDateTime now = LocalDateTime.now();
+        final ZoneId zoneId = ZoneId.systemDefault();
+
+        long sunsetMillis = ColorDisplayService.getDateTimeBefore(sunset, now)
+                .atZone(zoneId)
+                .toInstant()
+                .toEpochMilli();
+        long sunriseMillis = ColorDisplayService.getDateTimeBefore(sunrise, now)
+                .atZone(zoneId)
+                .toInstant()
+                .toEpochMilli();
+        if (sunsetMillis < sunriseMillis) {
+            sunsetMillis = ColorDisplayService.getDateTimeAfter(sunset, now)
+                    .atZone(zoneId)
+                    .toInstant()
+                    .toEpochMilli();
+        } else {
+            sunriseMillis = ColorDisplayService.getDateTimeAfter(sunrise, now)
+                    .atZone(zoneId)
+                    .toInstant()
+                    .toEpochMilli();
+        }
+
+        return new TwilightState(sunriseMillis, sunsetMillis);
+    }
+
+    private static class MockTwilightManager implements TwilightManager {
+
+        private final Map<TwilightListener, Handler> mListeners = new HashMap<>();
+        private TwilightState mTwilightState;
+
+        /**
+         * Updates the TwilightState and notifies any registered listeners.
+         *
+         * @param state the new TwilightState to use
+         */
+        void setTwilightState(TwilightState state) {
+            synchronized (mListeners) {
+                mTwilightState = state;
+
+                final CountDownLatch latch = new CountDownLatch(mListeners.size());
+                for (Map.Entry<TwilightListener, Handler> entry : mListeners.entrySet()) {
+                    entry.getValue().post(new Runnable() {
+                        @Override
+                        public void run() {
+                            entry.getKey().onTwilightStateChanged(state);
+                            latch.countDown();
+                        }
+                    });
+                }
+
+                try {
+                    latch.await(5, TimeUnit.SECONDS);
+                } catch (InterruptedException e) {
+                    throw new RuntimeException(e);
+                }
+            }
+        }
+
+        @Override
+        public void registerListener(@NonNull TwilightListener listener, @NonNull Handler handler) {
+            synchronized (mListeners) {
+                mListeners.put(listener, handler);
+            }
+        }
+
+        @Override
+        public void unregisterListener(@NonNull TwilightListener listener) {
+            synchronized (mListeners) {
+                mListeners.remove(listener);
+            }
+        }
+
+        @Override
+        public TwilightState getLastTwilightState() {
+            return mTwilightState;
+        }
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/color/DisplayTransformManagerTest.java b/services/tests/displayservicetests/src/com/android/server/display/color/DisplayTransformManagerTest.java
similarity index 100%
rename from services/tests/mockingservicestests/src/com/android/server/display/color/DisplayTransformManagerTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/color/DisplayTransformManagerTest.java
diff --git a/services/tests/displayservicetests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java
new file mode 100644
index 0000000..c280349
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java
@@ -0,0 +1,369 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.color;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.hardware.display.DisplayManagerInternal;
+import android.os.Binder;
+import android.os.IBinder;
+import android.view.SurfaceControl;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.internal.R;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+import java.util.Arrays;
+
+public class DisplayWhiteBalanceTintControllerTest {
+    @Mock
+    private Context mMockedContext;
+    @Mock
+    private Resources mMockedResources;
+    @Mock
+    private DisplayManagerInternal mDisplayManagerInternal;
+
+    private MockitoSession mSession;
+    private Resources mResources;
+    IBinder mDisplayToken;
+    DisplayWhiteBalanceTintController mDisplayWhiteBalanceTintController;
+
+    @Before
+    public void setUp() {
+        DisplayManagerInternal displayManagerInternal = mock(DisplayManagerInternal.class);
+        mDisplayWhiteBalanceTintController =
+                new DisplayWhiteBalanceTintController(displayManagerInternal);
+        mDisplayWhiteBalanceTintController.setUp(InstrumentationRegistry.getContext(), true);
+        mDisplayWhiteBalanceTintController.setActivated(true);
+
+        mSession = ExtendedMockito.mockitoSession()
+                .initMocks(this)
+                .mockStatic(SurfaceControl.class)
+                .strictness(Strictness.LENIENT)
+                .startMocking();
+
+        mResources = InstrumentationRegistry.getContext().getResources();
+        // These Resources are common to all tests.
+        doReturn(4000)
+                .when(mMockedResources)
+                .getInteger(R.integer.config_displayWhiteBalanceColorTemperatureMin);
+        doReturn(8000)
+                .when(mMockedResources)
+                .getInteger(R.integer.config_displayWhiteBalanceColorTemperatureMax);
+        doReturn(6500)
+                .when(mMockedResources)
+                .getInteger(R.integer.config_displayWhiteBalanceColorTemperatureDefault);
+        doReturn(new String[] {"0.950456", "1.000000", "1.089058"})
+                .when(mMockedResources)
+                .getStringArray(R.array.config_displayWhiteBalanceDisplayNominalWhite);
+        doReturn(6500)
+                .when(mMockedResources)
+                .getInteger(R.integer.config_displayWhiteBalanceDisplayNominalWhiteCct);
+        doReturn(new int[] {0})
+                .when(mMockedResources)
+                .getIntArray(R.array.config_displayWhiteBalanceDisplaySteps);
+        doReturn(new int[] {20})
+                .when(mMockedResources)
+                .getIntArray(R.array.config_displayWhiteBalanceDisplayRangeMinimums);
+
+        doReturn(mMockedResources).when(mMockedContext).getResources();
+
+        mDisplayToken = new Binder();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        if (mSession != null) {
+            mSession.finishMocking();
+        }
+    }
+
+    @Test
+    public void displayWhiteBalance_setTemperatureOverMax() {
+        final int max = mDisplayWhiteBalanceTintController.mTemperatureMax;
+        mDisplayWhiteBalanceTintController.setMatrix(max + 1);
+        assertWithMessage("Unexpected temperature set")
+                .that(mDisplayWhiteBalanceTintController.mCurrentColorTemperature)
+                .isEqualTo(max);
+    }
+
+    @Test
+    public void displayWhiteBalance_setTemperatureBelowMin() {
+        final int min = mDisplayWhiteBalanceTintController.mTemperatureMin;
+        mDisplayWhiteBalanceTintController.setMatrix(min - 1);
+        assertWithMessage("Unexpected temperature set")
+                .that(mDisplayWhiteBalanceTintController.mCurrentColorTemperature)
+                .isEqualTo(min);
+    }
+
+    @Test
+    public void displayWhiteBalance_setValidTemperature() {
+        final int colorTemperature = (mDisplayWhiteBalanceTintController.mTemperatureMin
+                + mDisplayWhiteBalanceTintController.mTemperatureMax) / 2;
+        mDisplayWhiteBalanceTintController.setMatrix(colorTemperature);
+
+        assertWithMessage("Unexpected temperature set")
+                .that(mDisplayWhiteBalanceTintController.mCurrentColorTemperature)
+                .isEqualTo(colorTemperature);
+    }
+
+    @Test
+    public void displayWhiteBalance_setMatrixValidDwbCalculation() {
+        float[] currentMatrix = mDisplayWhiteBalanceTintController.getMatrix();
+        float[] oldMatrix = Arrays.copyOf(currentMatrix, currentMatrix.length);
+
+        mDisplayWhiteBalanceTintController
+                .setMatrix(mDisplayWhiteBalanceTintController.mCurrentColorTemperature + 1);
+        assertWithMessage("DWB matrix did not change when setting a new temperature")
+                .that(Arrays.equals(oldMatrix, currentMatrix))
+                .isFalse();
+    }
+
+    @Test
+    public void displayWhiteBalance_setMatrixInvalidDwbCalculation() {
+        Arrays.fill(mDisplayWhiteBalanceTintController.mDisplayNominalWhiteXYZ, 0);
+        mDisplayWhiteBalanceTintController
+            .setMatrix(mDisplayWhiteBalanceTintController.mCurrentColorTemperature + 1);
+        assertWithMessage("DWB matrix not set to identity after an invalid DWB calculation")
+                .that(Arrays.equals(mDisplayWhiteBalanceTintController.getMatrix(),
+                    new float[] {
+                        1, 0, 0, 0,
+                        0, 1, 0, 0,
+                        0, 0, 1, 0,
+                        0, 0, 0, 1
+                    })
+                ).isTrue();
+    }
+
+
+    /**
+     * Setup should succeed when SurfaceControl setup results in a valid color transform.
+     */
+    @Test
+    public void displayWhiteBalance_setupWithSurfaceControl() {
+        // Make SurfaceControl return sRGB primaries
+        SurfaceControl.DisplayPrimaries displayPrimaries = new SurfaceControl.DisplayPrimaries();
+        displayPrimaries.red = new SurfaceControl.CieXyz();
+        displayPrimaries.red.X = 0.412315f;
+        displayPrimaries.red.Y = 0.212600f;
+        displayPrimaries.red.Z = 0.019327f;
+        displayPrimaries.green = new SurfaceControl.CieXyz();
+        displayPrimaries.green.X = 0.357600f;
+        displayPrimaries.green.Y = 0.715200f;
+        displayPrimaries.green.Z = 0.119200f;
+        displayPrimaries.blue = new SurfaceControl.CieXyz();
+        displayPrimaries.blue.X = 0.180500f;
+        displayPrimaries.blue.Y = 0.072200f;
+        displayPrimaries.blue.Z = 0.950633f;
+        displayPrimaries.white = new SurfaceControl.CieXyz();
+        displayPrimaries.white.X = 0.950456f;
+        displayPrimaries.white.Y = 1.000000f;
+        displayPrimaries.white.Z = 1.089058f;
+        when(mDisplayManagerInternal.getDisplayNativePrimaries(DEFAULT_DISPLAY))
+                .thenReturn(displayPrimaries);
+
+        setUpTintController();
+        assertWithMessage("Setup with valid SurfaceControl failed")
+                .that(mDisplayWhiteBalanceTintController.mSetUp)
+                .isTrue();
+    }
+
+    /**
+     * Setup should fail when SurfaceControl setup results in an invalid color transform.
+     */
+    @Test
+    public void displayWhiteBalance_setupWithInvalidSurfaceControlData() {
+        // Make SurfaceControl return invalid display primaries
+        SurfaceControl.DisplayPrimaries displayPrimaries = new SurfaceControl.DisplayPrimaries();
+        displayPrimaries.red = new SurfaceControl.CieXyz();
+        displayPrimaries.green = new SurfaceControl.CieXyz();
+        displayPrimaries.blue = new SurfaceControl.CieXyz();
+        displayPrimaries.white = new SurfaceControl.CieXyz();
+        when(mDisplayManagerInternal.getDisplayNativePrimaries(DEFAULT_DISPLAY))
+                .thenReturn(displayPrimaries);
+
+        setUpTintController();
+        assertWithMessage("Setup with invalid SurfaceControl succeeded")
+                .that(mDisplayWhiteBalanceTintController.mSetUp)
+                .isFalse();
+    }
+
+    /**
+     * Setup should succeed when SurfaceControl setup fails and Resources result in a valid color
+     * transform.
+     */
+    @Test
+    public void displayWhiteBalance_setupWithResources() {
+        // Use default (valid) Resources
+        doReturn(mResources.getStringArray(R.array.config_displayWhiteBalanceDisplayPrimaries))
+                .when(mMockedResources)
+                .getStringArray(R.array.config_displayWhiteBalanceDisplayPrimaries);
+        // Make SurfaceControl setup fail
+        when(mDisplayManagerInternal.getDisplayNativePrimaries(DEFAULT_DISPLAY)).thenReturn(null);
+
+        setUpTintController();
+        assertWithMessage("Setup with valid Resources failed")
+                .that(mDisplayWhiteBalanceTintController.mSetUp)
+                .isTrue();
+    }
+
+    /**
+     * Setup should fail when SurfaceControl setup fails and Resources result in an invalid color
+     * transform.
+     */
+    @Test
+    public void displayWhiteBalance_setupWithInvalidResources() {
+        // Use Resources with invalid color data
+        doReturn(new String[] {
+                "0", "0", "0", // Red X, Y, Z
+                "0", "0", "0", // Green X, Y, Z
+                "0", "0", "0", // Blue X, Y, Z
+                "0", "0", "0", // White X, Y, Z
+        })
+                .when(mMockedResources)
+                .getStringArray(R.array.config_displayWhiteBalanceDisplayPrimaries);
+        // Make SurfaceControl setup fail
+        when(mDisplayManagerInternal.getDisplayNativePrimaries(DEFAULT_DISPLAY)).thenReturn(null);
+
+        setUpTintController();
+        assertWithMessage("Setup with invalid Resources succeeded")
+                .that(mDisplayWhiteBalanceTintController.mSetUp)
+                .isFalse();
+    }
+
+    /**
+     * Matrix should match the precalculated one for given cct and display primaries.
+     */
+    @Test
+    public void displayWhiteBalance_getAndSetMatrix_validateTransformMatrix() {
+        SurfaceControl.DisplayPrimaries displayPrimaries = new SurfaceControl.DisplayPrimaries();
+        displayPrimaries.red = new SurfaceControl.CieXyz();
+        displayPrimaries.red.X = 0.412315f;
+        displayPrimaries.red.Y = 0.212600f;
+        displayPrimaries.red.Z = 0.019327f;
+        displayPrimaries.green = new SurfaceControl.CieXyz();
+        displayPrimaries.green.X = 0.357600f;
+        displayPrimaries.green.Y = 0.715200f;
+        displayPrimaries.green.Z = 0.119200f;
+        displayPrimaries.blue = new SurfaceControl.CieXyz();
+        displayPrimaries.blue.X = 0.180500f;
+        displayPrimaries.blue.Y = 0.072200f;
+        displayPrimaries.blue.Z = 0.950633f;
+        displayPrimaries.white = new SurfaceControl.CieXyz();
+        displayPrimaries.white.X = 0.950456f;
+        displayPrimaries.white.Y = 1.000000f;
+        displayPrimaries.white.Z = 1.089058f;
+        when(mDisplayManagerInternal.getDisplayNativePrimaries(DEFAULT_DISPLAY))
+                .thenReturn(displayPrimaries);
+
+        setUpTintController();
+        assertWithMessage("Setup with valid SurfaceControl failed")
+                .that(mDisplayWhiteBalanceTintController.mSetUp)
+                .isTrue();
+
+        final int cct = 6500;
+        mDisplayWhiteBalanceTintController.setMatrix(cct);
+        mDisplayWhiteBalanceTintController.setAppliedCct(
+                mDisplayWhiteBalanceTintController.getTargetCct());
+
+        assertWithMessage("Failed to set temperature")
+                .that(mDisplayWhiteBalanceTintController.mCurrentColorTemperature)
+                .isEqualTo(cct);
+        float[] matrixDwb = mDisplayWhiteBalanceTintController.getMatrix();
+        final float[] expectedMatrixDwb = {
+                0.971848f,   -0.001421f,  0.000491f, 0.0f,
+                0.028193f,    0.945798f,  0.003207f, 0.0f,
+                -0.000042f,  -0.000989f,  0.988659f, 0.0f,
+                0.0f,         0.0f,       0.0f,      1.0f
+        };
+        assertArrayEquals("Unexpected DWB matrix", expectedMatrixDwb, matrixDwb,
+                1e-6f /* tolerance */);
+    }
+
+    /**
+     * Matrix should match the precalculated one for given cct and display primaries.
+     */
+    @Test
+    public void displayWhiteBalance_targetApplied_validateTransformMatrix() {
+        SurfaceControl.DisplayPrimaries displayPrimaries = new SurfaceControl.DisplayPrimaries();
+        displayPrimaries.red = new SurfaceControl.CieXyz();
+        displayPrimaries.red.X = 0.412315f;
+        displayPrimaries.red.Y = 0.212600f;
+        displayPrimaries.red.Z = 0.019327f;
+        displayPrimaries.green = new SurfaceControl.CieXyz();
+        displayPrimaries.green.X = 0.357600f;
+        displayPrimaries.green.Y = 0.715200f;
+        displayPrimaries.green.Z = 0.119200f;
+        displayPrimaries.blue = new SurfaceControl.CieXyz();
+        displayPrimaries.blue.X = 0.180500f;
+        displayPrimaries.blue.Y = 0.072200f;
+        displayPrimaries.blue.Z = 0.950633f;
+        displayPrimaries.white = new SurfaceControl.CieXyz();
+        displayPrimaries.white.X = 0.950456f;
+        displayPrimaries.white.Y = 1.000000f;
+        displayPrimaries.white.Z = 1.089058f;
+        when(mDisplayManagerInternal.getDisplayNativePrimaries(DEFAULT_DISPLAY))
+                .thenReturn(displayPrimaries);
+
+        setUpTintController();
+        assertWithMessage("Setup with valid SurfaceControl failed")
+                .that(mDisplayWhiteBalanceTintController.mSetUp)
+                .isTrue();
+
+        final int cct = 6500;
+        mDisplayWhiteBalanceTintController.setTargetCct(cct);
+        final float[] matrixDwb = mDisplayWhiteBalanceTintController.computeMatrixForCct(cct);
+        mDisplayWhiteBalanceTintController.setAppliedCct(cct);
+
+        assertWithMessage("Failed to set temperature")
+                .that(mDisplayWhiteBalanceTintController.mCurrentColorTemperature)
+                .isEqualTo(cct);
+        final float[] expectedMatrixDwb = {
+                0.971848f,   -0.001421f,  0.000491f, 0.0f,
+                0.028193f,    0.945798f,  0.003207f, 0.0f,
+                -0.000042f,  -0.000989f,  0.988659f, 0.0f,
+                0.0f,         0.0f,       0.0f,      1.0f
+        };
+        assertArrayEquals("Unexpected DWB matrix", expectedMatrixDwb, matrixDwb,
+                1e-6f /* tolerance */);
+    }
+
+    private void setUpTintController() {
+        mDisplayWhiteBalanceTintController = new DisplayWhiteBalanceTintController(
+                mDisplayManagerInternal);
+        mDisplayWhiteBalanceTintController.setUp(mMockedContext, true);
+        mDisplayWhiteBalanceTintController.setActivated(true);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/display/color/GlobalSaturationTintControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/color/GlobalSaturationTintControllerTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/display/color/GlobalSaturationTintControllerTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/color/GlobalSaturationTintControllerTest.java
diff --git a/services/tests/servicestests/src/com/android/server/display/color/ReduceBrightColorsTintControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/color/ReduceBrightColorsTintControllerTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/display/color/ReduceBrightColorsTintControllerTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/color/ReduceBrightColorsTintControllerTest.java
diff --git a/services/tests/servicestests/src/com/android/server/display/mode/DisplayModeDirectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
diff --git a/services/tests/servicestests/src/com/android/server/display/mode/SkinThermalStatusObserverTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/SkinThermalStatusObserverTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/display/mode/SkinThermalStatusObserverTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/mode/SkinThermalStatusObserverTest.java
diff --git a/services/tests/servicestests/src/com/android/server/display/mode/VotesStorageTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/VotesStorageTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/display/mode/VotesStorageTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/mode/VotesStorageTest.java
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/state/DisplayStateControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/state/DisplayStateControllerTest.java
similarity index 100%
rename from services/tests/mockingservicestests/src/com/android/server/display/state/DisplayStateControllerTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/state/DisplayStateControllerTest.java
diff --git a/services/tests/servicestests/src/com/android/server/display/utils/AmbientFilterTest.java b/services/tests/displayservicetests/src/com/android/server/display/utils/AmbientFilterTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/display/utils/AmbientFilterTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/utils/AmbientFilterTest.java
diff --git a/services/tests/displayservicetests/src/com/android/server/display/utils/DeviceConfigParsingUtilsTest.java b/services/tests/displayservicetests/src/com/android/server/display/utils/DeviceConfigParsingUtilsTest.java
new file mode 100644
index 0000000..5e28e63
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/utils/DeviceConfigParsingUtilsTest.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.utils;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+
+import android.os.PowerManager;
+import android.util.Pair;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.annotations.Keep;
+import com.android.server.display.DisplayDeviceConfig;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+import java.util.Map;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
+@SmallTest
+@RunWith(JUnitParamsRunner.class)
+public class DeviceConfigParsingUtilsTest {
+    private static final String VALID_DATA_STRING = "display1,1,key1,value1";
+    private static final float FLOAT_TOLERANCE = 0.001f;
+
+    private final BiFunction<String, String, Pair<String, String>> mDataPointToPair = Pair::create;
+    private final Function<List<Pair<String, String>>, List<Pair<String, String>>>
+            mDataSetIdentity = (dataSet) -> dataSet;
+
+    @Keep
+    private static Object[][] parseDeviceConfigMapData() {
+        // dataString, expectedMap
+        return new Object[][]{
+                // null
+                {null, Map.of()},
+                // empty string
+                {"", Map.of()},
+                // 1 display, 1 incomplete data point
+                {"display1,1,key1", Map.of()},
+                // 1 display,2 data points required, only 1 present
+                {"display1,2,key1,value1", Map.of()},
+                // 1 display, 1 data point, dataSetId and some extra data
+                {"display1,1,key1,value1,setId1,extraData", Map.of()},
+                // 1 display, random string instead of number of data points
+                {"display1,one,key1,value1", Map.of()},
+                // 1 display, 1 data point no dataSetId
+                {VALID_DATA_STRING, Map.of("display1", Map.of(DisplayDeviceConfig.DEFAULT_ID,
+                        List.of(Pair.create("key1", "value1"))))},
+                // 1 display, 1 data point, dataSetId
+                {"display1,1,key1,value1,setId1", Map.of("display1", Map.of("setId1",
+                        List.of(Pair.create("key1", "value1"))))},
+                // 1 display, 2 data point, dataSetId
+                {"display1,2,key1,value1,key2,value2,setId1", Map.of("display1", Map.of("setId1",
+                        List.of(Pair.create("key1", "value1"), Pair.create("key2", "value2"))))},
+        };
+    }
+
+    @Test
+    @Parameters(method = "parseDeviceConfigMapData")
+    public void testParseDeviceConfigMap(String dataString,
+            Map<String, Map<String, List<Pair<String, String>>>> expectedMap) {
+        Map<String, Map<String, List<Pair<String, String>>>> result =
+                DeviceConfigParsingUtils.parseDeviceConfigMap(dataString, mDataPointToPair,
+                        mDataSetIdentity);
+
+        assertEquals(expectedMap, result);
+    }
+
+    @Test
+    public void testDataPointMapperReturnsNull() {
+        Map<String, Map<String, List<Pair<String, String>>>> result =
+                DeviceConfigParsingUtils.parseDeviceConfigMap(VALID_DATA_STRING, (s1, s2) -> null,
+                        mDataSetIdentity);
+
+        assertEquals(Map.of(), result);
+    }
+
+    @Test
+    public void testDataSetMapperReturnsNull() {
+        Map<String, Map<String, List<Pair<String, String>>>> result =
+                DeviceConfigParsingUtils.parseDeviceConfigMap(VALID_DATA_STRING, mDataPointToPair,
+                        (dataSet) -> null);
+
+        assertEquals(Map.of(), result);
+    }
+
+    @Keep
+    private static Object[][] parseThermalStatusData() {
+        // thermalStatusString, expectedThermalStatus
+        return new Object[][]{
+                {"none", PowerManager.THERMAL_STATUS_NONE},
+                {"light", PowerManager.THERMAL_STATUS_LIGHT},
+                {"moderate", PowerManager.THERMAL_STATUS_MODERATE},
+                {"severe", PowerManager.THERMAL_STATUS_SEVERE},
+                {"critical", PowerManager.THERMAL_STATUS_CRITICAL},
+                {"emergency", PowerManager.THERMAL_STATUS_EMERGENCY},
+                {"shutdown", PowerManager.THERMAL_STATUS_SHUTDOWN},
+        };
+    }
+
+    @Test
+    @Parameters(method = "parseThermalStatusData")
+    public void testParseThermalStatus(String thermalStatusString,
+            @PowerManager.ThermalStatus int expectedThermalStatus) {
+        int result = DeviceConfigParsingUtils.parseThermalStatus(thermalStatusString);
+
+        assertEquals(expectedThermalStatus, result);
+    }
+
+    @Test
+    public void testParseThermalStatus_illegalStatus() {
+        Throwable result = assertThrows(IllegalArgumentException.class,
+                () -> DeviceConfigParsingUtils.parseThermalStatus("invalid_status"));
+
+        assertEquals("Invalid Thermal Status: invalid_status", result.getMessage());
+    }
+
+    @Test
+    public void testParseBrightness() {
+        float result = DeviceConfigParsingUtils.parseBrightness("0.65");
+
+        assertEquals(0.65, result, FLOAT_TOLERANCE);
+    }
+
+    @Test
+    public void testParseBrightness_lessThanMin() {
+        Throwable result = assertThrows(IllegalArgumentException.class,
+                () -> DeviceConfigParsingUtils.parseBrightness("-0.65"));
+
+        assertEquals("Brightness value out of bounds: -0.65", result.getMessage());
+    }
+
+    @Test
+    public void testParseBrightness_moreThanMax() {
+        Throwable result = assertThrows(IllegalArgumentException.class,
+                () -> DeviceConfigParsingUtils.parseBrightness("1.65"));
+
+        assertEquals("Brightness value out of bounds: 1.65", result.getMessage());
+    }
+}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/utils/SensorUtilsTest.java b/services/tests/displayservicetests/src/com/android/server/display/utils/SensorUtilsTest.java
new file mode 100644
index 0000000..4494b0c
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/utils/SensorUtilsTest.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.utils;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.mockito.Mockito.when;
+
+import android.annotation.Nullable;
+import android.hardware.Sensor;
+import android.hardware.SensorManager;
+import android.hardware.input.InputSensorInfo;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.annotations.Keep;
+import com.android.server.display.DisplayDeviceConfig.SensorData;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Collections;
+import java.util.List;
+
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
+@SmallTest
+@RunWith(JUnitParamsRunner.class)
+public class SensorUtilsTest {
+
+    private static final String TEST_SENSOR_NAME = "test_sensor_name";
+    private static final String TEST_SENSOR_TYPE = "test_sensor_type";
+    private static final Sensor TEST_SENSOR = createSensor();
+    @Mock
+    private SensorManager mSensorManager;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    @Test
+    public void testNoSensorData() {
+        Sensor result = SensorUtils.findSensor(mSensorManager, null, Sensor.TYPE_LIGHT);
+        assertNull(result);
+    }
+
+    @Test
+    public void testNoSensorManager() {
+        Sensor result = SensorUtils.findSensor(null, new SensorData(), Sensor.TYPE_LIGHT);
+        assertNull(result);
+    }
+
+    @Keep
+    private static Object[][] findSensorData() {
+        // sensorName, sensorType, fallbackType, allSensors, defaultSensor, expectedResult
+        return new Object[][]{
+                // no data, no default
+                {null, null, Sensor.TYPE_LIGHT,
+                        Collections.singletonList(TEST_SENSOR), null, null},
+                // matching name, matching type, no default
+                {TEST_SENSOR_NAME, TEST_SENSOR_TYPE, Sensor.TYPE_LIGHT,
+                        Collections.singletonList(TEST_SENSOR), null, TEST_SENSOR},
+                // matching name, no default
+                {TEST_SENSOR_NAME, null, Sensor.TYPE_LIGHT,
+                        Collections.singletonList(TEST_SENSOR), null, TEST_SENSOR},
+                // not matching name, no default
+                {"not_matching_name", null, Sensor.TYPE_LIGHT,
+                        Collections.singletonList(TEST_SENSOR), null, null},
+                // matching type, no default
+                {null, TEST_SENSOR_TYPE, Sensor.TYPE_LIGHT,
+                        Collections.singletonList(TEST_SENSOR), null, TEST_SENSOR},
+                // not matching type, no default
+                {null, "not_matching_type", Sensor.TYPE_LIGHT,
+                        Collections.singletonList(TEST_SENSOR), null, null},
+                // not matching type, matching name, no default
+                {TEST_SENSOR_NAME, "not_matching_type", Sensor.TYPE_LIGHT,
+                        Collections.singletonList(TEST_SENSOR), null, null},
+                // not matching name, matching type, no default
+                {"not_matching_name", TEST_SENSOR_TYPE, Sensor.TYPE_LIGHT,
+                        Collections.singletonList(TEST_SENSOR), null, null},
+                // not matching type, not matching name, no default
+                {"not_matching_name", "not_matching_type", Sensor.TYPE_LIGHT,
+                        Collections.singletonList(TEST_SENSOR), null, null},
+                // not matching type, not matching name, with default
+                {"not_matching_name", "not_matching_type", Sensor.TYPE_LIGHT,
+                        Collections.singletonList(TEST_SENSOR), TEST_SENSOR, TEST_SENSOR},
+                // no data, with default
+                {null, null, Sensor.TYPE_LIGHT,
+                        Collections.singletonList(TEST_SENSOR), TEST_SENSOR, TEST_SENSOR},
+                // empty data, with default
+                {"", "", Sensor.TYPE_LIGHT,
+                        Collections.singletonList(TEST_SENSOR), TEST_SENSOR, TEST_SENSOR},
+                // empty data, with default, no fallback
+                {"", "", SensorUtils.NO_FALLBACK,
+                        Collections.singletonList(TEST_SENSOR), TEST_SENSOR, null},
+        };
+    }
+
+    @Test
+    @Parameters(method = "findSensorData")
+    public void testFindSensor(@Nullable String sensorName, @Nullable String sensorType,
+            int fallbackType, List<Sensor> allSensors, @Nullable Sensor defaultSensor,
+            @Nullable Sensor expectedResult) {
+        when(mSensorManager.getSensorList(Sensor.TYPE_ALL)).thenReturn(allSensors);
+        when(mSensorManager.getDefaultSensor(fallbackType)).thenReturn(defaultSensor);
+
+        SensorData sensorData = new SensorData();
+        sensorData.name = sensorName;
+        sensorData.type = sensorType;
+
+        Sensor result = SensorUtils.findSensor(mSensorManager, sensorData, fallbackType);
+
+        assertEquals(expectedResult, result);
+    }
+
+    private static Sensor createSensor() {
+        return new Sensor(new InputSensorInfo(
+                TEST_SENSOR_NAME, "vendor", 0, 0, 0, 1f, 1f, 1, 1, 1, 1,
+                TEST_SENSOR_TYPE, "", 0, 0, 0));
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/display/whitebalance/AmbientFilterStubber.java b/services/tests/displayservicetests/src/com/android/server/display/whitebalance/AmbientFilterStubber.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/display/whitebalance/AmbientFilterStubber.java
rename to services/tests/displayservicetests/src/com/android/server/display/whitebalance/AmbientFilterStubber.java
diff --git a/services/tests/displayservicetests/src/com/android/server/display/whitebalance/AmbientLuxTest.java b/services/tests/displayservicetests/src/com/android/server/display/whitebalance/AmbientLuxTest.java
new file mode 100644
index 0000000..f975b6f
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/whitebalance/AmbientLuxTest.java
@@ -0,0 +1,559 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.whitebalance;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyLong;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.content.ContextWrapper;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.hardware.Sensor;
+import android.hardware.SensorManager;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.TypedValue;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.internal.R;
+import com.android.server.LocalServices;
+import com.android.server.display.TestUtils;
+import com.android.server.display.color.ColorDisplayService;
+import com.android.server.display.utils.AmbientFilter;
+import com.android.server.display.utils.AmbientFilterStubber;
+
+import com.google.common.collect.ImmutableList;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.util.List;
+
+@RunWith(JUnit4.class)
+public final class AmbientLuxTest {
+
+    private static final float ALLOWED_ERROR_DELTA = 0.001f;
+    private static final int AMBIENT_COLOR_TYPE = 20705;
+    private static final String AMBIENT_COLOR_TYPE_STR = "colorSensoryDensoryDoc";
+    private static final float LOW_LIGHT_AMBIENT_COLOR_TEMPERATURE = 5432.1f;
+    private static final float HIGH_LIGHT_AMBIENT_COLOR_TEMPERATURE = 3456.7f;
+
+    private Handler mHandler = new Handler(Looper.getMainLooper());
+    private Sensor mLightSensor;
+    private Sensor mAmbientColorSensor;
+    private ContextWrapper mContextSpy;
+    private Resources mResourcesSpy;
+
+    @Mock private SensorManager mSensorManagerMock;
+
+    @Mock private TypedArray mBrightnesses;
+    @Mock private TypedArray mBiases;
+    @Mock private TypedArray mHighLightBrightnesses;
+    @Mock private TypedArray mHighLightBiases;
+    @Mock private TypedArray mAmbientColorTemperatures;
+    @Mock private TypedArray mDisplayColorTemperatures;
+    @Mock private TypedArray mStrongAmbientColorTemperatures;
+    @Mock private TypedArray mStrongDisplayColorTemperatures;
+    @Mock private ColorDisplayService.ColorDisplayServiceInternal mColorDisplayServiceInternalMock;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mLightSensor = TestUtils.createSensor(Sensor.TYPE_LIGHT, null);
+        mAmbientColorSensor = TestUtils.createSensor(AMBIENT_COLOR_TYPE, AMBIENT_COLOR_TYPE_STR);
+        mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getContext()));
+        mResourcesSpy = spy(mContextSpy.getResources());
+        when(mContextSpy.getResources()).thenReturn(mResourcesSpy);
+        when(mSensorManagerMock.getDefaultSensor(Sensor.TYPE_LIGHT)).thenReturn(mLightSensor);
+        final List<Sensor> sensorList = ImmutableList.of(mLightSensor, mAmbientColorSensor);
+        when(mSensorManagerMock.getSensorList(Sensor.TYPE_ALL)).thenReturn(sensorList);
+        when(mResourcesSpy.getString(
+                R.string.config_displayWhiteBalanceColorTemperatureSensorName))
+                .thenReturn(AMBIENT_COLOR_TYPE_STR);
+        when(mResourcesSpy.getInteger(
+                R.integer.config_displayWhiteBalanceDecreaseDebounce))
+                .thenReturn(0);
+        when(mResourcesSpy.getInteger(
+                R.integer.config_displayWhiteBalanceIncreaseDebounce))
+                .thenReturn(0);
+        mockResourcesFloat(R.dimen.config_displayWhiteBalanceLowLightAmbientColorTemperature,
+                LOW_LIGHT_AMBIENT_COLOR_TEMPERATURE);
+        mockResourcesFloat(R.dimen.config_displayWhiteBalanceHighLightAmbientColorTemperature,
+                HIGH_LIGHT_AMBIENT_COLOR_TEMPERATURE);
+        when(mResourcesSpy.obtainTypedArray(
+                R.array.config_displayWhiteBalanceAmbientColorTemperatures))
+                .thenReturn(mAmbientColorTemperatures);
+        when(mResourcesSpy.obtainTypedArray(
+                R.array.config_displayWhiteBalanceDisplayColorTemperatures))
+                .thenReturn(mDisplayColorTemperatures);
+        when(mResourcesSpy.obtainTypedArray(
+                R.array.config_displayWhiteBalanceStrongAmbientColorTemperatures))
+                .thenReturn(mStrongAmbientColorTemperatures);
+        when(mResourcesSpy.obtainTypedArray(
+                R.array.config_displayWhiteBalanceStrongDisplayColorTemperatures))
+                .thenReturn(mStrongDisplayColorTemperatures);
+
+        when(mResourcesSpy.obtainTypedArray(
+                R.array.config_displayWhiteBalanceLowLightAmbientBrightnesses))
+                .thenReturn(mBrightnesses);
+        when(mResourcesSpy.obtainTypedArray(
+                R.array.config_displayWhiteBalanceLowLightAmbientBiases))
+                .thenReturn(mBiases);
+        when(mResourcesSpy.obtainTypedArray(
+                R.array.config_displayWhiteBalanceHighLightAmbientBrightnesses))
+                .thenReturn(mHighLightBrightnesses);
+        when(mResourcesSpy.obtainTypedArray(
+                R.array.config_displayWhiteBalanceHighLightAmbientBiases))
+                .thenReturn(mHighLightBiases);
+        mockThrottler();
+        LocalServices.removeServiceForTest(ColorDisplayService.ColorDisplayServiceInternal.class);
+        LocalServices.addService(ColorDisplayService.ColorDisplayServiceInternal.class,
+                mColorDisplayServiceInternalMock);
+    }
+
+    @Test
+    public void testCalculateAdjustedBrightnessNits() {
+        doReturn(0.9f).when(mColorDisplayServiceInternalMock).getDisplayWhiteBalanceLuminance();
+        DisplayWhiteBalanceController controller =
+                DisplayWhiteBalanceFactory.create(mHandler, mSensorManagerMock, mResourcesSpy);
+        final float adjustedNits = controller.calculateAdjustedBrightnessNits(500f);
+        assertEquals(/* expected= */ 550f, adjustedNits, /* delta= */ 0.001);
+    }
+
+    @Test
+    public void testNoSpline() throws Exception {
+        setBrightnesses();
+        setBiases();
+
+        DisplayWhiteBalanceController controller =
+                DisplayWhiteBalanceFactory.create(mHandler, mSensorManagerMock, mResourcesSpy);
+        final float ambientColorTemperature = 8000.0f;
+        setEstimatedColorTemperature(controller, ambientColorTemperature);
+        controller.mBrightnessFilter = spy(new AmbientFilterStubber());
+
+        for (float luxOverride = 0.1f; luxOverride <= 10000; luxOverride *= 10) {
+            setEstimatedBrightnessAndUpdate(controller, luxOverride);
+            assertEquals(controller.mPendingAmbientColorTemperature,
+                    ambientColorTemperature, 0.001);
+        }
+    }
+
+    @Test
+    public void testSpline_OneSegment() throws Exception {
+        final float lowerBrightness = 10.0f;
+        final float upperBrightness = 50.0f;
+        setBrightnesses(lowerBrightness, upperBrightness);
+        setBiases(0.0f, 1.0f);
+
+        DisplayWhiteBalanceController controller =
+                DisplayWhiteBalanceFactory.create(mHandler, mSensorManagerMock, mResourcesSpy);
+        final float ambientColorTemperature = 8000.0f;
+        setEstimatedColorTemperature(controller, ambientColorTemperature);
+        controller.mBrightnessFilter = spy(new AmbientFilterStubber());
+
+        for (float t = 0.0f; t <= 1.0f; t += 0.1f) {
+            setEstimatedBrightnessAndUpdate(controller,
+                    mix(lowerBrightness, upperBrightness, t));
+            assertEquals(controller.mPendingAmbientColorTemperature,
+                    mix(LOW_LIGHT_AMBIENT_COLOR_TEMPERATURE, ambientColorTemperature, t), 0.001);
+        }
+
+        setEstimatedBrightnessAndUpdate(controller, 0.0f);
+        assertEquals(controller.mPendingAmbientColorTemperature,
+                LOW_LIGHT_AMBIENT_COLOR_TEMPERATURE, 0.001);
+
+        setEstimatedBrightnessAndUpdate(controller, upperBrightness + 1.0f);
+        assertEquals(controller.mPendingAmbientColorTemperature, ambientColorTemperature, 0.001);
+    }
+
+    @Test
+    public void testSpline_TwoSegments() throws Exception {
+        final float brightness0 = 10.0f;
+        final float brightness1 = 50.0f;
+        final float brightness2 = 60.0f;
+        setBrightnesses(brightness0, brightness1, brightness2);
+        final float bias0 = 0.0f;
+        final float bias1 = 0.25f;
+        final float bias2 = 1.0f;
+        setBiases(bias0, bias1, bias2);
+
+        DisplayWhiteBalanceController controller =
+                DisplayWhiteBalanceFactory.create(mHandler, mSensorManagerMock, mResourcesSpy);
+        final float ambientColorTemperature = 8000.0f;
+        setEstimatedColorTemperature(controller, ambientColorTemperature);
+        controller.mBrightnessFilter = spy(new AmbientFilterStubber());
+
+        for (float t = 0.0f; t <= 1.0f; t += 0.1f) {
+            float luxOverride = mix(brightness0, brightness1, t);
+            setEstimatedBrightnessAndUpdate(controller, luxOverride);
+            float bias = mix(bias0, bias1, t);
+            assertEquals(controller.mPendingAmbientColorTemperature,
+                    mix(LOW_LIGHT_AMBIENT_COLOR_TEMPERATURE, ambientColorTemperature, bias), 0.001);
+        }
+
+        for (float t = 0.0f; t <= 1.0f; t += 0.1f) {
+            float luxOverride = mix(brightness1, brightness2, t);
+            setEstimatedBrightnessAndUpdate(controller, luxOverride);
+            float bias = mix(bias1, bias2, t);
+            assertEquals(controller.mPendingAmbientColorTemperature,
+                    mix(LOW_LIGHT_AMBIENT_COLOR_TEMPERATURE, ambientColorTemperature, bias), 0.001);
+        }
+
+        setEstimatedBrightnessAndUpdate(controller, 0.0f);
+        assertEquals(controller.mPendingAmbientColorTemperature,
+                LOW_LIGHT_AMBIENT_COLOR_TEMPERATURE, 0.001);
+
+        setEstimatedBrightnessAndUpdate(controller, brightness2 + 1.0f);
+        assertEquals(controller.mPendingAmbientColorTemperature, ambientColorTemperature, 0.001);
+    }
+
+    @Test
+    public void testSpline_VerticalSegment() throws Exception {
+        final float lowerBrightness = 10.0f;
+        final float upperBrightness = 10.0f;
+        setBrightnesses(lowerBrightness, upperBrightness);
+        setBiases(0.0f, 1.0f);
+
+        DisplayWhiteBalanceController controller =
+                DisplayWhiteBalanceFactory.create(mHandler, mSensorManagerMock, mResourcesSpy);
+        final float ambientColorTemperature = 8000.0f;
+        setEstimatedColorTemperature(controller, ambientColorTemperature);
+        controller.mBrightnessFilter = spy(new AmbientFilterStubber());
+
+        setEstimatedBrightnessAndUpdate(controller, 0.0f);
+        assertEquals(controller.mPendingAmbientColorTemperature,
+                LOW_LIGHT_AMBIENT_COLOR_TEMPERATURE, 0.001);
+
+        setEstimatedBrightnessAndUpdate(controller, upperBrightness + 1.0f);
+        assertEquals(controller.mPendingAmbientColorTemperature, ambientColorTemperature, 0.001);
+    }
+
+    @Test
+    public void testSpline_InvalidEndBias() throws Exception {
+        setBrightnesses(10.0f, 1000.0f);
+        setBiases(0.0f, 2.0f);
+
+        DisplayWhiteBalanceController controller =
+                DisplayWhiteBalanceFactory.create(mHandler, mSensorManagerMock, mResourcesSpy);
+        final float ambientColorTemperature = 8000.0f;
+        setEstimatedColorTemperature(controller, ambientColorTemperature);
+        controller.mBrightnessFilter = spy(new AmbientFilterStubber());
+
+        for (float luxOverride = 0.1f; luxOverride <= 10000; luxOverride *= 10) {
+            setEstimatedBrightnessAndUpdate(controller, luxOverride);
+            assertEquals(controller.mPendingAmbientColorTemperature,
+                    ambientColorTemperature, 0.001);
+        }
+    }
+
+    @Test
+    public void testSpline_InvalidBeginBias() throws Exception {
+        setBrightnesses(10.0f, 1000.0f);
+        setBiases(0.1f, 1.0f);
+
+        DisplayWhiteBalanceController controller =
+                DisplayWhiteBalanceFactory.create(mHandler, mSensorManagerMock, mResourcesSpy);
+        final float ambientColorTemperature = 8000.0f;
+        setEstimatedColorTemperature(controller, ambientColorTemperature);
+        controller.mBrightnessFilter = spy(new AmbientFilterStubber());
+
+        for (float luxOverride = 0.1f; luxOverride <= 10000; luxOverride *= 10) {
+            setEstimatedBrightnessAndUpdate(controller, luxOverride);
+            assertEquals(controller.mPendingAmbientColorTemperature,
+                    ambientColorTemperature, 0.001);
+        }
+    }
+
+    @Test
+    public void testSpline_OneSegmentHighLight() throws Exception {
+        final float lowerBrightness = 10.0f;
+        final float upperBrightness = 50.0f;
+        setHighLightBrightnesses(lowerBrightness, upperBrightness);
+        setHighLightBiases(0.0f, 1.0f);
+
+        DisplayWhiteBalanceController controller =
+                DisplayWhiteBalanceFactory.create(mHandler, mSensorManagerMock, mResourcesSpy);
+        final float ambientColorTemperature = 8000.0f;
+        setEstimatedColorTemperature(controller, ambientColorTemperature);
+        controller.mBrightnessFilter = spy(new AmbientFilterStubber());
+
+        for (float t = 0.0f; t <= 1.0f; t += 0.1f) {
+            setEstimatedBrightnessAndUpdate(controller,
+                    mix(lowerBrightness, upperBrightness, t));
+            assertEquals(controller.mPendingAmbientColorTemperature,
+                    mix(HIGH_LIGHT_AMBIENT_COLOR_TEMPERATURE, ambientColorTemperature, 1.0f - t),
+                    0.001);
+        }
+
+        setEstimatedBrightnessAndUpdate(controller, upperBrightness + 1.0f);
+        assertEquals(controller.mPendingAmbientColorTemperature,
+                HIGH_LIGHT_AMBIENT_COLOR_TEMPERATURE, 0.001);
+
+        setEstimatedBrightnessAndUpdate(controller, 0.0f);
+        assertEquals(controller.mPendingAmbientColorTemperature, ambientColorTemperature, 0.001);
+    }
+
+    @Test
+    public void testSpline_TwoSegmentsHighLight() throws Exception {
+        final float brightness0 = 10.0f;
+        final float brightness1 = 50.0f;
+        final float brightness2 = 60.0f;
+        setHighLightBrightnesses(brightness0, brightness1, brightness2);
+        final float bias0 = 0.0f;
+        final float bias1 = 0.25f;
+        final float bias2 = 1.0f;
+        setHighLightBiases(bias0, bias1, bias2);
+
+        DisplayWhiteBalanceController controller =
+                DisplayWhiteBalanceFactory.create(mHandler, mSensorManagerMock, mResourcesSpy);
+        final float ambientColorTemperature = 6000.0f;
+        setEstimatedColorTemperature(controller, ambientColorTemperature);
+        controller.mBrightnessFilter = spy(new AmbientFilterStubber());
+
+        for (float t = 0.0f; t <= 1.0f; t += 0.1f) {
+            float luxOverride = mix(brightness0, brightness1, t);
+            setEstimatedBrightnessAndUpdate(controller, luxOverride);
+            float bias = mix(bias0, bias1, t);
+            assertEquals(controller.mPendingAmbientColorTemperature,
+                    mix(HIGH_LIGHT_AMBIENT_COLOR_TEMPERATURE, ambientColorTemperature, 1.0f - bias),
+                    0.01);
+        }
+
+        for (float t = 0.0f; t <= 1.0f; t += 0.1f) {
+            float luxOverride = mix(brightness1, brightness2, t);
+            setEstimatedBrightnessAndUpdate(controller, luxOverride);
+            float bias = mix(bias1, bias2, t);
+            assertEquals(controller.mPendingAmbientColorTemperature,
+                    mix(HIGH_LIGHT_AMBIENT_COLOR_TEMPERATURE, ambientColorTemperature, 1.0f - bias),
+                    0.01);
+        }
+
+        setEstimatedBrightnessAndUpdate(controller, brightness2 + 1.0f);
+        assertEquals(controller.mPendingAmbientColorTemperature,
+                HIGH_LIGHT_AMBIENT_COLOR_TEMPERATURE, 0.001);
+
+        setEstimatedBrightnessAndUpdate(controller, 0.0f);
+        assertEquals(controller.mPendingAmbientColorTemperature, ambientColorTemperature, 0.001);
+    }
+
+    @Test
+    public void testSpline_InvalidCombinations() throws Exception {
+        setBrightnesses(100.0f, 200.0f);
+        setBiases(0.0f, 1.0f);
+        setHighLightBrightnesses(150.0f, 250.0f);
+        setHighLightBiases(0.0f, 1.0f);
+
+        DisplayWhiteBalanceController controller =
+                DisplayWhiteBalanceFactory.create(mHandler, mSensorManagerMock, mResourcesSpy);
+        final float ambientColorTemperature = 8000.0f;
+        setEstimatedColorTemperature(controller, ambientColorTemperature);
+        controller.mBrightnessFilter = spy(new AmbientFilterStubber());
+
+        for (float luxOverride = 0.1f; luxOverride <= 10000; luxOverride *= 10) {
+            setEstimatedBrightnessAndUpdate(controller, luxOverride);
+            assertEquals(controller.mPendingAmbientColorTemperature,
+                    ambientColorTemperature, 0.001);
+        }
+    }
+
+    @Test
+    public void testStrongMode() {
+        final float lowerBrightness = 10.0f;
+        final float upperBrightness = 50.0f;
+        setBrightnesses(lowerBrightness, upperBrightness);
+        setBiases(0.0f, 1.0f);
+        final int ambientColorTempLow = 6000;
+        final int ambientColorTempHigh = 8000;
+        final int displayColorTempLow = 6400;
+        final int displayColorTempHigh = 7400;
+        setStrongAmbientColorTemperatures(ambientColorTempLow, ambientColorTempHigh);
+        setStrongDisplayColorTemperatures(displayColorTempLow, displayColorTempHigh);
+
+        DisplayWhiteBalanceController controller =
+                DisplayWhiteBalanceFactory.create(mHandler, mSensorManagerMock, mResourcesSpy);
+        controller.setStrongModeEnabled(true);
+        controller.mBrightnessFilter = spy(new AmbientFilterStubber());
+
+        for (float ambientTempFraction = 0.0f; ambientTempFraction <= 1.0f;
+                ambientTempFraction += 0.1f) {
+            final float ambientTemp =
+                    (ambientColorTempHigh - ambientColorTempLow) * ambientTempFraction
+                            + ambientColorTempLow;
+            setEstimatedColorTemperature(controller, ambientTemp);
+            for (float brightnessFraction = 0.0f; brightnessFraction <= 1.0f;
+                    brightnessFraction += 0.1f) {
+                setEstimatedBrightnessAndUpdate(controller,
+                        mix(lowerBrightness, upperBrightness, brightnessFraction));
+                assertEquals(controller.mPendingAmbientColorTemperature,
+                        mix(LOW_LIGHT_AMBIENT_COLOR_TEMPERATURE,
+                                mix(displayColorTempLow, displayColorTempHigh, ambientTempFraction),
+                                brightnessFraction),
+                        ALLOWED_ERROR_DELTA);
+            }
+        }
+    }
+
+    @Test
+    public void testLowLight_DefaultAmbient() throws Exception {
+        final float lowerBrightness = 10.0f;
+        final float upperBrightness = 50.0f;
+        setBrightnesses(lowerBrightness, upperBrightness);
+        setBiases(0.0f, 1.0f);
+
+        DisplayWhiteBalanceController controller =
+                DisplayWhiteBalanceFactory.create(mHandler, mSensorManagerMock, mResourcesSpy);
+        final float ambientColorTemperature = -1.0f;
+        setEstimatedColorTemperature(controller, ambientColorTemperature);
+        controller.mBrightnessFilter = spy(new AmbientFilterStubber());
+
+        for (float t = 0.0f; t <= 1.0f; t += 0.1f) {
+            setEstimatedBrightnessAndUpdate(controller,
+                    mix(lowerBrightness, upperBrightness, t));
+            assertEquals(controller.mPendingAmbientColorTemperature, ambientColorTemperature,
+                        0.001);
+        }
+
+        setEstimatedBrightnessAndUpdate(controller, 0.0f);
+        assertEquals(controller.mPendingAmbientColorTemperature, ambientColorTemperature, 0.001);
+
+        setEstimatedBrightnessAndUpdate(controller, upperBrightness + 1.0f);
+        assertEquals(controller.mPendingAmbientColorTemperature, ambientColorTemperature, 0.001);
+    }
+
+    @Test
+    public void testWhiteBalance_updateWithEmptyFilter() throws Exception {
+        setAmbientColorTemperatures(5300.0f, 6000.0f, 7000.0f, 8000.0f);
+        setDisplayColorTemperatures(6300.0f, 6400.0f, 6850.0f, 7450.0f);
+        DisplayWhiteBalanceController controller =
+                DisplayWhiteBalanceFactory.create(mHandler, mSensorManagerMock, mResourcesSpy);
+        controller.updateAmbientColorTemperature();
+        assertEquals(-1.0f, controller.mPendingAmbientColorTemperature, 0);
+    }
+
+    void mockThrottler() {
+        when(mResourcesSpy.getInteger(
+                R.integer.config_displayWhiteBalanceDecreaseDebounce)).thenReturn(0);
+        when(mResourcesSpy.getInteger(
+                R.integer.config_displayWhiteBalanceIncreaseDebounce)).thenReturn(0);
+        TypedArray base = mResourcesSpy.obtainTypedArray(
+                R.array.config_displayWhiteBalanceBaseThresholds);
+        TypedArray inc = mResourcesSpy.obtainTypedArray(
+                R.array.config_displayWhiteBalanceIncreaseThresholds);
+        TypedArray dec = mResourcesSpy.obtainTypedArray(
+                R.array.config_displayWhiteBalanceDecreaseThresholds);
+        base = spy(base);
+        inc = spy(inc);
+        dec = spy(dec);
+        when(mResourcesSpy.obtainTypedArray(
+                R.array.config_displayWhiteBalanceBaseThresholds)).thenReturn(base);
+        when(mResourcesSpy.obtainTypedArray(
+                R.array.config_displayWhiteBalanceIncreaseThresholds)).thenReturn(inc);
+        when(mResourcesSpy.obtainTypedArray(
+                R.array.config_displayWhiteBalanceDecreaseThresholds)).thenReturn(dec);
+        setFloatArrayResource(base, new float[]{0.0f});
+        setFloatArrayResource(inc, new float[]{0.0f});
+        setFloatArrayResource(dec, new float[]{0.0f});
+    }
+
+    private void mockResourcesFloat(int id, float floatValue) {
+        doAnswer(new Answer<Void>() {
+            public Void answer(InvocationOnMock invocation) {
+                TypedValue value = (TypedValue) invocation.getArgument(1);
+                value.type = TypedValue.TYPE_FLOAT;
+                value.data = Float.floatToIntBits(floatValue);
+                return null;
+            }
+        }).when(mResourcesSpy).getValue(
+                eq(id),
+                any(TypedValue.class), eq(true));
+    }
+
+    private void setEstimatedColorTemperature(DisplayWhiteBalanceController controller,
+                                              float ambientColorTemperature) {
+        AmbientFilter colorTemperatureFilter = spy(new AmbientFilterStubber());
+        controller.mColorTemperatureFilter = colorTemperatureFilter;
+        when(colorTemperatureFilter.getEstimate(anyLong())).thenReturn(ambientColorTemperature);
+    }
+
+    private void setEstimatedBrightnessAndUpdate(DisplayWhiteBalanceController controller,
+                                                 float brightness) {
+        when(controller.mBrightnessFilter.getEstimate(anyLong())).thenReturn(brightness);
+        controller.updateAmbientColorTemperature();
+    }
+
+    private void setBrightnesses(float... vals) {
+        setFloatArrayResource(mBrightnesses, vals);
+    }
+
+    private void setBiases(float... vals) {
+        setFloatArrayResource(mBiases, vals);
+    }
+
+    private void setHighLightBrightnesses(float... vals) {
+        setFloatArrayResource(mHighLightBrightnesses, vals);
+    }
+
+    private void setHighLightBiases(float... vals) {
+        setFloatArrayResource(mHighLightBiases, vals);
+    }
+
+    private void setAmbientColorTemperatures(float... vals) {
+        setFloatArrayResource(mAmbientColorTemperatures, vals);
+    }
+
+    private void setDisplayColorTemperatures(float... vals) {
+        setFloatArrayResource(mDisplayColorTemperatures, vals);
+    }
+
+    private void setStrongAmbientColorTemperatures(float... vals) {
+        setFloatArrayResource(mStrongAmbientColorTemperatures, vals);
+    }
+
+    private void setStrongDisplayColorTemperatures(float... vals) {
+        setFloatArrayResource(mStrongDisplayColorTemperatures, vals);
+    }
+
+    private void setFloatArrayResource(TypedArray array, float[] vals) {
+        when(array.length()).thenReturn(vals.length);
+        for (int i = 0; i < vals.length; i++) {
+            when(array.getFloat(i, Float.NaN)).thenReturn(vals[i]);
+        }
+    }
+
+    private TypedArray createTypedArray() throws Exception {
+        TypedArray mockArray = mock(TypedArray.class);
+        return mockArray;
+    }
+
+    private static float mix(float a, float b, float t) {
+        return (1.0f - t) * a + t * b;
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/display/whitebalance/AmbientSensorTest.java b/services/tests/displayservicetests/src/com/android/server/display/whitebalance/AmbientSensorTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/display/whitebalance/AmbientSensorTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/whitebalance/AmbientSensorTest.java
diff --git a/services/tests/mockingservicestests/assets/AppOpsUpgradeTest/OWNERS b/services/tests/mockingservicestests/assets/AppOpsUpgradeTest/OWNERS
index 999ea0e..2fe78c5 100644
--- a/services/tests/mockingservicestests/assets/AppOpsUpgradeTest/OWNERS
+++ b/services/tests/mockingservicestests/assets/AppOpsUpgradeTest/OWNERS
@@ -1 +1,2 @@
+#Bug component: 137825
 include /core/java/android/permission/OWNERS
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy0/affected_cpus b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy0/affected_cpus
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy0/affected_cpus
@@ -0,0 +1 @@
+
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy0/related_cpus b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy0/related_cpus
new file mode 100644
index 0000000..c227083
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy0/related_cpus
@@ -0,0 +1 @@
+0
\ No newline at end of file
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy0/scaling_cur_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy0/scaling_cur_freq
new file mode 100644
index 0000000..dadd973
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy0/scaling_cur_freq
@@ -0,0 +1 @@
+1230000
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy0/scaling_max_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy0/scaling_max_freq
new file mode 100644
index 0000000..a93d6f7
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy0/scaling_max_freq
@@ -0,0 +1 @@
+2500000
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy0/stats/time_in_state b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy0/stats/time_in_state
new file mode 100644
index 0000000..5121f66
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy0/stats/time_in_state
@@ -0,0 +1,4 @@
+200000 500
+350000 500
+500000 1000
+2500000 100
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy1/affected_cpus b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy1/affected_cpus
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy1/affected_cpus
@@ -0,0 +1 @@
+1
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy1/related_cpus b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy1/related_cpus
new file mode 100644
index 0000000..56a6051
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy1/related_cpus
@@ -0,0 +1 @@
+1
\ No newline at end of file
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy1/scaling_cur_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy1/scaling_cur_freq
new file mode 100644
index 0000000..2bc4ce9
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy1/scaling_cur_freq
@@ -0,0 +1 @@
+1450000
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy1/scaling_max_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy1/scaling_max_freq
new file mode 100644
index 0000000..c754f1a
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy1/scaling_max_freq
@@ -0,0 +1 @@
+2800000
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy1/stats/time_in_state b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy1/stats/time_in_state
new file mode 100644
index 0000000..7ed12cf
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy1/stats/time_in_state
@@ -0,0 +1,4 @@
+200000 500
+350000 500
+500000 1000
+2800000 100
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy0/affected_cpus b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy0/affected_cpus
new file mode 100644
index 0000000..573541a
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy0/affected_cpus
@@ -0,0 +1 @@
+0
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy0/related_cpus b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy0/related_cpus
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy0/related_cpus
@@ -0,0 +1 @@
+
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy0/scaling_cur_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy0/scaling_cur_freq
new file mode 100644
index 0000000..dadd973
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy0/scaling_cur_freq
@@ -0,0 +1 @@
+1230000
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy0/scaling_max_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy0/scaling_max_freq
new file mode 100644
index 0000000..a93d6f7
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy0/scaling_max_freq
@@ -0,0 +1 @@
+2500000
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy0/stats/time_in_state b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy0/stats/time_in_state
new file mode 100644
index 0000000..5121f66
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy0/stats/time_in_state
@@ -0,0 +1,4 @@
+200000 500
+350000 500
+500000 1000
+2500000 100
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy1/affected_cpus b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy1/affected_cpus
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy1/affected_cpus
@@ -0,0 +1 @@
+1
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy1/related_cpus b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy1/related_cpus
new file mode 100644
index 0000000..56a6051
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy1/related_cpus
@@ -0,0 +1 @@
+1
\ No newline at end of file
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy1/scaling_cur_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy1/scaling_cur_freq
new file mode 100644
index 0000000..2bc4ce9
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy1/scaling_cur_freq
@@ -0,0 +1 @@
+1450000
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy1/scaling_max_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy1/scaling_max_freq
new file mode 100644
index 0000000..c754f1a
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy1/scaling_max_freq
@@ -0,0 +1 @@
+2800000
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy1/stats/time_in_state b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy1/stats/time_in_state
new file mode 100644
index 0000000..7ed12cf
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy1/stats/time_in_state
@@ -0,0 +1,4 @@
+200000 500
+350000 500
+500000 1000
+2800000 100
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state_2/policy0/scaling_max_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state_2/policy0/scaling_max_freq
index a93d6f7..d17275f 100644
--- a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state_2/policy0/scaling_max_freq
+++ b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state_2/policy0/scaling_max_freq
@@ -1 +1 @@
-2500000
+2600000
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state_2/policy1/scaling_max_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state_2/policy1/scaling_max_freq
index c754f1a..79c41c7 100644
--- a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state_2/policy1/scaling_max_freq
+++ b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state_2/policy1/scaling_max_freq
@@ -1 +1 @@
-2800000
+2900000
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state_2/policy2/scaling_max_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state_2/policy2/scaling_max_freq
index deebb18..526a717 100644
--- a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state_2/policy2/scaling_max_freq
+++ b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state_2/policy2/scaling_max_freq
@@ -1 +1 @@
-2000000
+2100000
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpuset_with_empty_cpus/background/cpus b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpuset_with_empty_cpus/background/cpus
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpuset_with_empty_cpus/background/cpus
@@ -0,0 +1 @@
+
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpuset_with_empty_cpus/top-app/cpus b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpuset_with_empty_cpus/top-app/cpus
new file mode 100644
index 0000000..40c7bb2
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpuset_with_empty_cpus/top-app/cpus
@@ -0,0 +1 @@
+0-3
diff --git a/services/tests/mockingservicestests/src/android/service/games/GameSessionTrampolineActivityTest.java b/services/tests/mockingservicestests/src/android/service/games/GameSessionTrampolineActivityTest.java
index 353341e..82e02ca 100644
--- a/services/tests/mockingservicestests/src/android/service/games/GameSessionTrampolineActivityTest.java
+++ b/services/tests/mockingservicestests/src/android/service/games/GameSessionTrampolineActivityTest.java
@@ -47,11 +47,14 @@
 
 import com.android.internal.infra.AndroidFuture;
 
+import com.google.common.util.concurrent.Uninterruptibles;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.time.Duration;
 import java.util.concurrent.TimeUnit;
 
 /**
@@ -62,6 +65,8 @@
 @Presubmit
 public class GameSessionTrampolineActivityTest {
 
+    private static final Duration TEST_ACTIVITY_OPEN_DURATION = Duration.ofSeconds(5);
+
     @Before
     public void setUp() {
         setAlwaysFinishActivities(false);
@@ -145,10 +150,15 @@
             startTestActivityViaGameSessionTrampolineActivity() {
         Intent testActivityIntent = new Intent();
         testActivityIntent.setClass(getInstrumentation().getTargetContext(), TestActivity.class);
+        sleep(TEST_ACTIVITY_OPEN_DURATION);
 
         return startGameSessionTrampolineActivity(testActivityIntent);
     }
 
+    private static void sleep(Duration sleepDuration) {
+        Uninterruptibles.sleepUninterruptibly(sleepDuration.toMillis(), TimeUnit.MILLISECONDS);
+    }
+
     private static AndroidFuture<GameSessionActivityResult> startGameSessionTrampolineActivity(
             Intent targetIntent) {
         AndroidFuture<GameSessionActivityResult> resultFuture = new AndroidFuture<>();
diff --git a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
index 2b57c59..88f3b2e 100644
--- a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
@@ -32,8 +32,6 @@
 import static com.android.server.DeviceIdleController.LIGHT_STATE_OVERRIDE;
 import static com.android.server.DeviceIdleController.LIGHT_STATE_WAITING_FOR_NETWORK;
 import static com.android.server.DeviceIdleController.MSG_REPORT_STATIONARY_STATUS;
-import static com.android.server.DeviceIdleController.MSG_RESET_PRE_IDLE_TIMEOUT_FACTOR;
-import static com.android.server.DeviceIdleController.MSG_UPDATE_PRE_IDLE_TIMEOUT_FACTOR;
 import static com.android.server.DeviceIdleController.STATE_ACTIVE;
 import static com.android.server.DeviceIdleController.STATE_IDLE;
 import static com.android.server.DeviceIdleController.STATE_IDLE_MAINTENANCE;
@@ -50,6 +48,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;
@@ -146,6 +145,8 @@
     private SensorManager mSensorManager;
     @Mock
     private TelephonyManager mTelephonyManager;
+    @Mock
+    private Sensor mOffBodySensor;
 
     class InjectorForTest extends DeviceIdleController.Injector {
         ConnectivityManager connectivityManager;
@@ -198,9 +199,7 @@
                 mHandler = controller.new MyHandler(getContext().getMainLooper());
                 spyOn(mHandler);
                 doNothing().when(mHandler).handleMessage(argThat((message) ->
-                        message.what != MSG_REPORT_STATIONARY_STATUS
-                        && message.what != MSG_UPDATE_PRE_IDLE_TIMEOUT_FACTOR
-                        && message.what != MSG_RESET_PRE_IDLE_TIMEOUT_FACTOR));
+                        message.what != MSG_REPORT_STATIONARY_STATUS));
                 doAnswer(new Answer<Boolean>() {
                     @Override
                     public Boolean answer(InvocationOnMock invocation) throws Throwable {
@@ -209,9 +208,7 @@
                         return true;
                     }
                 }).when(mHandler).sendMessageDelayed(
-                        argThat((message) -> message.what == MSG_REPORT_STATIONARY_STATUS
-                                || message.what == MSG_UPDATE_PRE_IDLE_TIMEOUT_FACTOR
-                                || message.what == MSG_RESET_PRE_IDLE_TIMEOUT_FACTOR),
+                        argThat((message) -> message.what == MSG_REPORT_STATIONARY_STATUS),
                         anyLong());
             }
 
@@ -2085,43 +2082,6 @@
     }
 
     @Test
-    public void testStepToIdleMode() {
-        float delta = mDeviceIdleController.MIN_PRE_IDLE_FACTOR_CHANGE;
-        for (int mode = PowerManager.PRE_IDLE_TIMEOUT_MODE_NORMAL;
-                mode <= PowerManager.PRE_IDLE_TIMEOUT_MODE_LONG;
-                mode++) {
-            int ret = mDeviceIdleController.setPreIdleTimeoutMode(mode);
-            if (mode == PowerManager.PRE_IDLE_TIMEOUT_MODE_NORMAL) {
-                assertEquals("setPreIdleTimeoutMode: " + mode + " failed.",
-                        mDeviceIdleController.SET_IDLE_FACTOR_RESULT_IGNORED, ret);
-            } else {
-                assertEquals("setPreIdleTimeoutMode: " + mode + " failed.",
-                        mDeviceIdleController.SET_IDLE_FACTOR_RESULT_OK, ret);
-            }
-            //TODO(b/123045185): Mocked Handler of DeviceIdleController to make message loop
-            //workable in this test class
-            float expectedfactor = mDeviceIdleController.getPreIdleTimeoutByMode(mode);
-            float curfactor = mDeviceIdleController.getPreIdleTimeoutFactor();
-            assertEquals("Pre idle time factor of mode [" + mode + "].",
-                    expectedfactor, curfactor, delta);
-            mDeviceIdleController.resetPreIdleTimeoutMode();
-
-            checkNextAlarmTimeWithNewPreIdleFactor(expectedfactor, STATE_INACTIVE);
-            checkNextAlarmTimeWithNewPreIdleFactor(expectedfactor, STATE_IDLE_PENDING);
-
-            checkNextAlarmTimeWithNewPreIdleFactor(expectedfactor, STATE_SENSING);
-            checkNextAlarmTimeWithNewPreIdleFactor(expectedfactor, STATE_LOCATING);
-            checkNextAlarmTimeWithNewPreIdleFactor(expectedfactor, STATE_QUICK_DOZE_DELAY);
-            checkNextAlarmTimeWithNewPreIdleFactor(expectedfactor, STATE_IDLE_MAINTENANCE);
-            checkNextAlarmTimeWithNewPreIdleFactor(expectedfactor, STATE_IDLE);
-            checkMaybeDoAnImmediateMaintenance(expectedfactor);
-        }
-        float curfactor = mDeviceIdleController.getPreIdleTimeoutFactor();
-        assertEquals("Pre idle time factor of mode default.",
-                1.0f, curfactor, delta);
-    }
-
-    @Test
     public void testStationaryDetection_QuickDozeOff() {
         setQuickDozeEnabled(false);
         enterDeepState(STATE_IDLE);
@@ -2452,6 +2412,82 @@
         verifyLightStateConditions(LIGHT_STATE_ACTIVE);
     }
 
+    @Test
+    public void testLowLatencyBodyDetection_NoBodySensor() {
+        mConstants.USE_BODY_SENSOR = true;
+        doReturn(null).when(mSensorManager).getDefaultSensor(
+                eq(Sensor.TYPE_LOW_LATENCY_OFFBODY_DETECT), anyBoolean());
+        cleanupDeviceIdleController();
+        setupDeviceIdleController();
+        verify(mSensorManager, never())
+                .registerListener(any(), any(), anyInt());
+    }
+
+    @Test
+    public void testLowLatencyBodyDetection_NoBatterySaver_QuickDoze() {
+        mConstants.USE_BODY_SENSOR = true;
+        doReturn(mOffBodySensor)
+                .when(mSensorManager)
+                .getDefaultSensor(eq(Sensor.TYPE_LOW_LATENCY_OFFBODY_DETECT), anyBoolean());
+        PowerSaveState powerSaveState = new PowerSaveState.Builder().setBatterySaverEnabled(
+                false).build();
+        when(mPowerManagerInternal.getLowPowerState(anyInt()))
+                .thenReturn(powerSaveState);
+        cleanupDeviceIdleController();
+        setupDeviceIdleController();
+
+        ArgumentCaptor<SensorEventListener> listenerCaptor =
+                ArgumentCaptor.forClass(SensorEventListener.class);
+        verify(mSensorManager)
+                .registerListener(listenerCaptor.capture(), eq(mOffBodySensor),
+                        eq(SensorManager.SENSOR_DELAY_NORMAL));
+        final SensorEventListener listener = listenerCaptor.getValue();
+        // Set the device as off body
+        float[] valsZero = {0.0f};
+        SensorEvent offbodyEvent = new SensorEvent(mOffBodySensor, 1, 1L, valsZero);
+        listener.onSensorChanged(offbodyEvent);
+        assertTrue(mDeviceIdleController.isQuickDozeEnabled());
+
+        // Set the device as on body
+        float[] valsNonZero = {1.0f};
+        SensorEvent onbodyEvent = new SensorEvent(mOffBodySensor, 1, 1L, valsNonZero);
+        listener.onSensorChanged(onbodyEvent);
+        assertFalse(mDeviceIdleController.isQuickDozeEnabled());
+        verifyStateConditions(STATE_ACTIVE);
+    }
+
+    @Test
+    public void testLowLatencyBodyDetection_WithBatterySaver_QuickDoze() {
+        mConstants.USE_BODY_SENSOR = true;
+        doReturn(mOffBodySensor)
+                .when(mSensorManager)
+                .getDefaultSensor(eq(Sensor.TYPE_LOW_LATENCY_OFFBODY_DETECT), anyBoolean());
+        PowerSaveState powerSaveState = new PowerSaveState.Builder().setBatterySaverEnabled(
+                true).build();
+        when(mPowerManagerInternal.getLowPowerState(anyInt()))
+                .thenReturn(powerSaveState);
+        cleanupDeviceIdleController();
+        setupDeviceIdleController();
+
+        ArgumentCaptor<SensorEventListener> listenerCaptor =
+                ArgumentCaptor.forClass(SensorEventListener.class);
+        verify(mSensorManager)
+                .registerListener(listenerCaptor.capture(), eq(mOffBodySensor),
+                        eq(SensorManager.SENSOR_DELAY_NORMAL));
+        final SensorEventListener listener = listenerCaptor.getValue();
+        // Set the device as off body
+        float[] valsZero = {0.0f};
+        SensorEvent offbodyEvent = new SensorEvent(mOffBodySensor, 1, 1L, valsZero);
+        listener.onSensorChanged(offbodyEvent);
+        assertTrue(mDeviceIdleController.isQuickDozeEnabled());
+
+        // Set the device as on body. Quick doze should remain enabled because battery saver is on.
+        float[] valsNonZero = {1.0f};
+        SensorEvent onbodyEvent = new SensorEvent(mOffBodySensor, 1, 1L, valsNonZero);
+        listener.onSensorChanged(onbodyEvent);
+        assertTrue(mDeviceIdleController.isQuickDozeEnabled());
+    }
+
     private void enterDeepState(int state) {
         switch (state) {
             case STATE_ACTIVE:
@@ -2706,68 +2742,4 @@
                 fail("Conditions for " + lightStateToString(expectedLightState) + " unknown.");
         }
     }
-
-    private void checkNextAlarmTimeWithNewPreIdleFactor(float factor, int state) {
-        final long errorTolerance = 1000;
-        enterDeepState(state);
-        long now = SystemClock.elapsedRealtime();
-        long alarm = mDeviceIdleController.getNextAlarmTime();
-        if (state == STATE_INACTIVE || state == STATE_IDLE_PENDING) {
-            int ret = mDeviceIdleController.setPreIdleTimeoutFactor(factor);
-            if (Float.compare(factor, 1.0f) == 0) {
-                assertEquals("setPreIdleTimeoutMode: " + factor + " failed.",
-                        mDeviceIdleController.SET_IDLE_FACTOR_RESULT_IGNORED, ret);
-            } else {
-                assertEquals("setPreIdleTimeoutMode: " + factor + " failed.",
-                        mDeviceIdleController.SET_IDLE_FACTOR_RESULT_OK, ret);
-            }
-            if (ret == mDeviceIdleController.SET_IDLE_FACTOR_RESULT_OK) {
-                long newAlarm = mDeviceIdleController.getNextAlarmTime();
-                long newDelay = (long) ((alarm - now) * factor);
-                assertTrue("setPreIdleTimeoutFactor: " + factor,
-                        Math.abs(newDelay - (newAlarm - now)) <  errorTolerance);
-                mDeviceIdleController.resetPreIdleTimeoutMode();
-                newAlarm = mDeviceIdleController.getNextAlarmTime();
-                assertTrue("resetPreIdleTimeoutMode from: " + factor,
-                        Math.abs(newAlarm - alarm) < errorTolerance);
-                mDeviceIdleController.setPreIdleTimeoutFactor(factor);
-                now = SystemClock.elapsedRealtime();
-                enterDeepState(state);
-                newAlarm = mDeviceIdleController.getNextAlarmTime();
-                assertTrue("setPreIdleTimeoutFactor: " + factor + " before step to idle",
-                        Math.abs(newDelay - (newAlarm - now)) <  errorTolerance);
-                mDeviceIdleController.resetPreIdleTimeoutMode();
-            }
-        } else {
-            mDeviceIdleController.setPreIdleTimeoutFactor(factor);
-            long newAlarm = mDeviceIdleController.getNextAlarmTime();
-            assertTrue("setPreIdleTimeoutFactor: " + factor
-                    + " shounld not change next alarm" ,
-                    (newAlarm == alarm));
-            mDeviceIdleController.resetPreIdleTimeoutMode();
-        }
-    }
-
-    private void checkMaybeDoAnImmediateMaintenance(float factor) {
-        int ret = mDeviceIdleController.setPreIdleTimeoutFactor(factor);
-        final long minuteInMillis = 60 * 1000;
-        if (Float.compare(factor, 1.0f) == 0) {
-            assertEquals("setPreIdleTimeoutMode: " + factor + " failed.",
-                    mDeviceIdleController.SET_IDLE_FACTOR_RESULT_IGNORED, ret);
-        } else {
-            assertEquals("setPreIdleTimeoutMode: " + factor + " failed.",
-                    mDeviceIdleController.SET_IDLE_FACTOR_RESULT_OK, ret);
-        }
-        if (ret == mDeviceIdleController.SET_IDLE_FACTOR_RESULT_OK) {
-            enterDeepState(STATE_IDLE);
-            long now = SystemClock.elapsedRealtime();
-            mDeviceIdleController.setIdleStartTimeForTest(
-                    now - (long) (mConstants.IDLE_TIMEOUT * 0.6));
-            verifyStateConditions(STATE_IDLE);
-            mDeviceIdleController.setIdleStartTimeForTest(
-                    now - (long) (mConstants.IDLE_TIMEOUT * 1.2));
-            verifyStateConditions(STATE_IDLE_MAINTENANCE);
-            mDeviceIdleController.resetPreIdleTimeoutMode();
-        }
-    }
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/ExtendedMockitoRule.java b/services/tests/mockingservicestests/src/com/android/server/ExtendedMockitoRule.java
deleted file mode 100644
index 881dd50..0000000
--- a/services/tests/mockingservicestests/src/com/android/server/ExtendedMockitoRule.java
+++ /dev/null
@@ -1,183 +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.server;
-
-import android.annotation.Nullable;
-import android.util.Log;
-
-import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
-import com.android.internal.util.Preconditions;
-import com.android.modules.utils.testing.StaticMockFixture;
-import com.android.modules.utils.testing.StaticMockFixtureRule;
-
-import org.mockito.Mockito;
-import org.mockito.quality.Strictness;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Objects;
-
-/**
- * Rule to make it easier to use Extended Mockito.
- *
- * <p>It's derived from {@link StaticMockFixtureRule}, with the additional features:
- *
- * <ul>
- *   <li>Easier to define which classes must be statically mocked or spied
- *   <li>Automatically starts mocking (so tests don't need a mockito runner or rule)
- *   <li>Automatically clears the inlined mocks at the end (to avoid OOM)
- *   <li>Allows other customization like strictness
- * </ul>
- */
-public final class ExtendedMockitoRule extends StaticMockFixtureRule {
-
-    private static final String TAG = ExtendedMockitoRule.class.getSimpleName();
-
-    private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
-
-    private final Object mTestClassInstance;
-    private final Strictness mStrictness;
-
-    private ExtendedMockitoRule(Builder builder) {
-        super(() -> new SimpleStatickMockFixture(builder.mMockedStaticClasses,
-                builder.mSpiedStaticClasses, builder.mDynamicSessionBuilderConfigurator,
-                builder.mAfterSessionFinishedCallback));
-        mTestClassInstance = builder.mTestClassInstance;
-        mStrictness = builder.mStrictness;
-        if (VERBOSE) {
-            Log.v(TAG, "strictness=" + mStrictness + ", testClassInstance" + mTestClassInstance
-                    + ", mockedStaticClasses=" + builder.mMockedStaticClasses
-                    + ", spiedStaticClasses=" + builder.mSpiedStaticClasses
-                    + ", dynamicSessionBuilderConfigurator="
-                    + builder.mDynamicSessionBuilderConfigurator
-                    + ", afterSessionFinishedCallback=" + builder.mAfterSessionFinishedCallback);
-        }
-    }
-
-    @Override
-    public StaticMockitoSessionBuilder getSessionBuilder() {
-        StaticMockitoSessionBuilder sessionBuilder = super.getSessionBuilder();
-        if (mStrictness != null) {
-            if (VERBOSE) {
-                Log.v(TAG, "Setting strictness to " + mStrictness + " on " + sessionBuilder);
-            }
-            sessionBuilder.strictness(mStrictness);
-        }
-        return sessionBuilder.initMocks(mTestClassInstance);
-    }
-
-    public static final class Builder {
-        private final Object mTestClassInstance;
-        private @Nullable Strictness mStrictness;
-        private final List<Class<?>> mMockedStaticClasses = new ArrayList<>();
-        private final List<Class<?>> mSpiedStaticClasses = new ArrayList<>();
-        private @Nullable Visitor<StaticMockitoSessionBuilder> mDynamicSessionBuilderConfigurator;
-        private @Nullable Runnable mAfterSessionFinishedCallback;
-
-        public Builder(Object testClassInstance) {
-            mTestClassInstance = Objects.requireNonNull(testClassInstance);
-        }
-
-        public Builder setStrictness(Strictness strictness) {
-            mStrictness = Objects.requireNonNull(strictness);
-            return this;
-        }
-
-        public Builder mockStatic(Class<?> clazz) {
-            Objects.requireNonNull(clazz);
-            Preconditions.checkState(!mMockedStaticClasses.contains(clazz),
-                    "class %s already mocked", clazz);
-            mMockedStaticClasses.add(clazz);
-            return this;
-        }
-
-        public Builder spyStatic(Class<?> clazz) {
-            Objects.requireNonNull(clazz);
-            Preconditions.checkState(!mSpiedStaticClasses.contains(clazz),
-                    "class %s already spied", clazz);
-            mSpiedStaticClasses.add(clazz);
-            return this;
-        }
-
-        public Builder dynamiclyConfigureSessionBuilder(
-                Visitor<StaticMockitoSessionBuilder> dynamicSessionBuilderConfigurator) {
-            mDynamicSessionBuilderConfigurator = Objects
-                    .requireNonNull(dynamicSessionBuilderConfigurator);
-            return this;
-        }
-
-        public Builder afterSessionFinished(Runnable runnable) {
-            mAfterSessionFinishedCallback = Objects.requireNonNull(runnable);
-            return this;
-        }
-
-        public ExtendedMockitoRule build() {
-            return new ExtendedMockitoRule(this);
-        }
-    }
-
-    private static final class SimpleStatickMockFixture implements StaticMockFixture {
-
-        private final List<Class<?>> mMockedStaticClasses;
-        private final List<Class<?>> mSpiedStaticClasses;
-        @Nullable
-        private final Visitor<StaticMockitoSessionBuilder> mDynamicSessionBuilderConfigurator;
-        @Nullable
-        private final Runnable mAfterSessionFinishedCallback;
-
-        private SimpleStatickMockFixture(List<Class<?>> mockedStaticClasses,
-                List<Class<?>> spiedStaticClasses,
-                @Nullable Visitor<StaticMockitoSessionBuilder> dynamicSessionBuilderConfigurator,
-                @Nullable Runnable afterSessionFinishedCallback) {
-            mMockedStaticClasses = mockedStaticClasses;
-            mSpiedStaticClasses = spiedStaticClasses;
-            mDynamicSessionBuilderConfigurator = dynamicSessionBuilderConfigurator;
-            mAfterSessionFinishedCallback = afterSessionFinishedCallback;
-        }
-
-        @Override
-        public StaticMockitoSessionBuilder setUpMockedClasses(
-                StaticMockitoSessionBuilder sessionBuilder) {
-            mMockedStaticClasses.forEach((c) -> sessionBuilder.mockStatic(c));
-            mSpiedStaticClasses.forEach((c) -> sessionBuilder.spyStatic(c));
-            if (mDynamicSessionBuilderConfigurator != null) {
-                mDynamicSessionBuilderConfigurator.visit(sessionBuilder);
-            }
-            return sessionBuilder;
-        }
-
-        @Override
-        public void setUpMockBehaviors() {
-        }
-
-        @Override
-        public void tearDown() {
-            try {
-                if (mAfterSessionFinishedCallback != null) {
-                    mAfterSessionFinishedCallback.run();
-                }
-            } finally {
-                if (VERBOSE) {
-                    Log.v(TAG, "calling Mockito.framework().clearInlineMocks()");
-                }
-                // When using inline mock maker, clean up inline mocks to prevent OutOfMemory
-                // errors. See https://github.com/mockito/mockito/issues/1614 and b/259280359.
-                Mockito.framework().clearInlineMocks();
-            }
-        }
-    }
-}
diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
index f1d4de9..5b51963 100644
--- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
@@ -143,6 +143,7 @@
 import android.os.Message;
 import android.os.PowerExemptionManager;
 import android.os.PowerManager;
+import android.os.Process;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemProperties;
@@ -162,11 +163,11 @@
 import com.android.internal.app.IAppOpsCallback;
 import com.android.internal.app.IAppOpsService;
 import com.android.internal.util.ArrayUtils;
+import com.android.modules.utils.testing.ExtendedMockitoRule;
 import com.android.server.AlarmManagerInternal;
 import com.android.server.AppStateTracker;
 import com.android.server.AppStateTrackerImpl;
 import com.android.server.DeviceIdleInternal;
-import com.android.server.ExtendedMockitoRule;
 import com.android.server.LocalServices;
 import com.android.server.SystemClockTime.TimeConfidence;
 import com.android.server.SystemService;
@@ -327,16 +328,6 @@
         }
 
         @Override
-        void setKernelTimeZoneOffset(int utcOffsetMillis) {
-            // Do nothing.
-        }
-
-        @Override
-        void syncKernelTimeZoneOffset() {
-            // Do nothing.
-        }
-
-        @Override
         int getCallingUid() {
             return mTestCallingUid;
         }
@@ -2146,31 +2137,59 @@
 
         assertEquals(deviceIdleUntil, mTestTimer.getElapsed());
 
-        final PendingIntent pi5wakeup = getNewMockPendingIntent();
-        final PendingIntent pi4wakeupPackage = getNewMockPendingIntent();
-        final PendingIntent pi2nonWakeup = getNewMockPendingIntent(57, "test.different.package");
+        final PendingIntent pi4System = getNewMockPendingIntent(Process.SYSTEM_UID, "android");
+        final PendingIntent pi9System = getNewMockPendingIntent(Process.SYSTEM_UID, "android");
 
-        setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 5, pi5wakeup);
-        setTestAlarm(ELAPSED_REALTIME, mNowElapsedTest + 4, pi4wakeupPackage);
+        final PendingIntent pi5wakeupPackage = getNewMockPendingIntent();
+        final IAlarmListener listener2WakeupPackage = getNewListener(() -> {});
+        final PendingIntent pi4wakeupPackage = getNewMockPendingIntent(41, "test.wakeup.package2");
+        final PendingIntent pi19wakeupPackage = getNewMockPendingIntent(41, "test.wakeup.package2");
+
+        final PendingIntent pi2nonWakeup = getNewMockPendingIntent(57, "test.nonwakeup.package1");
+        final PendingIntent pi8nonWakeup = getNewMockPendingIntent(59, "test.nonwakeup.package2");
+        final PendingIntent pi11nonWakeup = getNewMockPendingIntent(51, "test.nonwakeup.package3");
+
+        // Alarms from the system
+        setTestAlarm(ELAPSED_REALTIME, mNowElapsedTest + 4, pi4System);
+        setTestAlarm(ELAPSED_REALTIME, mNowElapsedTest + 9, pi9System);
+
+        // Alarms from TEST_CALLING_PACKAGE, TEST_CALLING_UID, having one wakeup alarm
+        setTestAlarmWithListener(ELAPSED_REALTIME, mNowElapsedTest + 20, mService.mTimeTickTrigger);
+        setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 5, pi5wakeupPackage);
+        setTestAlarmWithListener(ELAPSED_REALTIME, mNowElapsedTest + 2, listener2WakeupPackage);
+
+        // Alarms from 41, "test.wakeup.package2", having one wakeup alarm
+        setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 4, pi4wakeupPackage);
+        setTestAlarm(ELAPSED_REALTIME, mNowElapsedTest + 19, pi19wakeupPackage);
+
+        // Alarms from other packages having no wakeup alarms
+        setTestAlarm(ELAPSED_REALTIME, mNowElapsedTest + 8, pi8nonWakeup);
+        setTestAlarm(ELAPSED_REALTIME, mNowElapsedTest + 11, pi11nonWakeup);
         setTestAlarm(ELAPSED_REALTIME, mNowElapsedTest + 2, pi2nonWakeup);
 
         mNowElapsedTest = deviceIdleUntil;
         mTestTimer.expire();
 
-        // The order of the alarms in delivery list should be:
-        // IdleUntil, all alarms of a package with any wakeup alarms, then the rest.
-        // Within a package, alarms should be ordered by requested delivery time.
-        final PendingIntent[] expectedOrder = new PendingIntent[]{
-                idleUntilPi, pi4wakeupPackage, pi5wakeup, pi2nonWakeup};
+        // IdleUntil should always go out first. The others are divided into three classes:
+        // System alarms | Alarms of packages with any wakeup alarms | Others.
+        // Within each class, time_tick should go first if present, others should be ordered by
+        // requested delivery time.
+        final PendingIntent[] expectedPiOrder = new PendingIntent[]{
+                idleUntilPi, pi4System, pi9System, null, null, pi4wakeupPackage, pi5wakeupPackage,
+                pi19wakeupPackage, pi2nonWakeup, pi8nonWakeup, pi11nonWakeup};
+
+        final IAlarmListener[] expectedListenerOrder = new IAlarmListener[expectedPiOrder.length];
+        expectedListenerOrder[3] = mService.mTimeTickTrigger;
+        expectedListenerOrder[4] = listener2WakeupPackage;
 
         ArgumentCaptor<ArrayList<Alarm>> listCaptor = ArgumentCaptor.forClass(ArrayList.class);
         verify(mService).deliverAlarmsLocked(listCaptor.capture(), anyLong());
         final ArrayList<Alarm> deliveryList = listCaptor.getValue();
 
-        assertEquals(expectedOrder.length, deliveryList.size());
-        for (int i = 0; i < expectedOrder.length; i++) {
+        assertEquals(expectedPiOrder.length, deliveryList.size());
+        for (int i = 0; i < expectedPiOrder.length; i++) {
             assertTrue("Unexpected alarm: " + deliveryList.get(i) + " at pos: " + i,
-                    deliveryList.get(i).matches(expectedOrder[i], null));
+                    deliveryList.get(i).matches(expectedPiOrder[i], expectedListenerOrder[i]));
         }
     }
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceInjectorTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceInjectorTest.java
index 9ceb5e7..405272ec 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceInjectorTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceInjectorTest.java
@@ -31,7 +31,7 @@
 import android.util.Log;
 import android.view.Display;
 
-import com.android.server.ExtendedMockitoRule;
+import com.android.modules.utils.testing.ExtendedMockitoRule;
 import com.android.server.am.ActivityManagerService.Injector;
 
 import org.junit.Before;
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java b/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java
index 7c5d96e..0abf46b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java
@@ -131,6 +131,7 @@
 
         mRealAms = new ActivityManagerService(
                 new TestInjector(mContext), mServiceThreadRule.getThread());
+        mRealAms.mConstants.loadDeviceConfigConstants();
         mRealAms.mActivityTaskManager = new ActivityTaskManagerService(mContext);
         mRealAms.mActivityTaskManager.initialize(null, null, mContext.getMainLooper());
         mRealAms.mAtmInternal = mActivityTaskManagerInt;
@@ -195,12 +196,14 @@
             Log.v(TAG, "Intercepting bindApplication() for "
                     + Arrays.toString(invocation.getArguments()));
             if (!wedge) {
-                mRealAms.finishAttachApplication(0);
+                if (mRealAms.mConstants.mEnableWaitForFinishAttachApplication) {
+                    mRealAms.finishAttachApplication(0);
+                }
             }
             return null;
         }).when(thread).bindApplication(
                 any(), any(),
-                any(), any(),
+                any(), any(), anyBoolean(),
                 any(), any(),
                 any(), any(),
                 any(),
@@ -237,9 +240,10 @@
      */
     @Test
     public void testNormal() throws Exception {
-        ProcessRecord app = startProcessAndWait(false);
-
-        verify(app, never()).killLocked(any(), anyInt(), anyBoolean());
+        if (mRealAms.mConstants.mEnableWaitForFinishAttachApplication) {
+            ProcessRecord app = startProcessAndWait(false);
+            verify(app, never()).killLocked(any(), anyInt(), anyBoolean());
+        }
     }
 
     /**
@@ -260,7 +264,7 @@
                 /* expectedStartSeq */ 0, /* procAttached */ false);
 
         app.getThread().bindApplication(PACKAGE, appInfo,
-                null, null,
+                null, null, false,
                 null,
                 null,
                 null, null,
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java
index bad04dc..bb91939 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java
@@ -2435,10 +2435,18 @@
     private void setPermissionState(String packageName, int uid, String perm, boolean granted) {
         doReturn(granted ? PERMISSION_GRANTED : PERMISSION_DENIED)
                 .when(mPermissionManagerServiceInternal)
-                .checkUidPermission(uid, perm);
+                .checkUidPermission(uid, perm, Context.DEVICE_ID_DEFAULT);
         doReturn(granted ? PERMISSION_GRANTED : PERMISSION_DENIED)
                 .when(mPermissionManagerServiceInternal)
-                .checkPermission(packageName, perm, UserHandle.getUserId(uid));
+                .checkPermission(
+                        packageName, perm, Context.DEVICE_ID_DEFAULT, UserHandle.getUserId(uid));
+        try {
+            doReturn(granted ? PERMISSION_GRANTED : PERMISSION_DENIED)
+                    .when(mIActivityManager)
+                    .checkPermission(perm, Process.INVALID_PID, uid);
+        } catch (RemoteException e) {
+            // Ignore.
+        }
     }
 
     private void setAppOpState(String packageName, int uid, int op, boolean granted) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
index 6bcc14e..08f5d03 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
@@ -92,7 +92,7 @@
 import androidx.test.platform.app.InstrumentationRegistry;
 
 import com.android.internal.util.FrameworkStatsLog;
-import com.android.server.ExtendedMockitoRule;
+import com.android.modules.utils.testing.ExtendedMockitoRule;
 
 import org.junit.After;
 import org.junit.Before;
@@ -620,6 +620,28 @@
         assertEquals(BroadcastProcessQueue.REASON_CORE_UID, queue.getRunnableAtReason());
     }
 
+    @Test
+    public void testRunnableAt_freezableCoreUid() {
+        final BroadcastProcessQueue queue = new BroadcastProcessQueue(mConstants,
+                "com.android.bluetooth", Process.BLUETOOTH_UID);
+
+        // Mark the process as freezable
+        queue.setProcessAndUidState(mProcess, false, true);
+        final Intent timeTick = new Intent(Intent.ACTION_TIME_TICK);
+        final BroadcastOptions options = BroadcastOptions.makeWithDeferUntilActive(true);
+        final BroadcastRecord timeTickRecord = makeBroadcastRecord(timeTick, options,
+                List.of(makeMockRegisteredReceiver()), false);
+        enqueueOrReplaceBroadcast(queue, timeTickRecord, 0);
+
+        assertEquals(Long.MAX_VALUE, queue.getRunnableAt());
+        assertEquals(BroadcastProcessQueue.REASON_CACHED_INFINITE_DEFER,
+                queue.getRunnableAtReason());
+
+        queue.setProcessAndUidState(mProcess, false, false);
+        assertThat(queue.getRunnableAt()).isEqualTo(timeTickRecord.enqueueTime);
+        assertEquals(BroadcastProcessQueue.REASON_CORE_UID, queue.getRunnableAtReason());
+    }
+
     /**
      * Verify that a cached process that would normally be delayed becomes
      * immediately runnable when the given broadcast is enqueued.
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
index 73eb237..410ae35 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -125,6 +125,7 @@
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
+import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.function.UnaryOperator;
@@ -327,7 +328,7 @@
                 eq(ActivityManager.PROCESS_STATE_LAST_ACTIVITY), any());
 
         mConstants = new BroadcastConstants(Settings.Global.BROADCAST_FG_CONSTANTS);
-        mConstants.TIMEOUT = 100;
+        mConstants.TIMEOUT = 200;
         mConstants.ALLOW_BG_ACTIVITY_START_TIMEOUT = 0;
         mConstants.PENDING_COLD_START_CHECK_INTERVAL_MILLIS = 500;
 
@@ -707,6 +708,9 @@
     private void waitForIdle() throws Exception {
         mLooper.release();
         mQueue.waitForIdle(LOG_WRITER_INFO);
+        final CountDownLatch latch = new CountDownLatch(1);
+        mHandlerThread.getThreadHandler().post(latch::countDown);
+        latch.await();
         mLooper = Objects.requireNonNull(InstrumentationRegistry.getInstrumentation()
                 .acquireLooperManager(mHandlerThread.getLooper()));
     }
@@ -2342,6 +2346,7 @@
 
         mUidObserver.onUidStateChanged(receiverGreenApp.info.uid,
                 ActivityManager.PROCESS_STATE_TOP, 0, ActivityManager.PROCESS_CAPABILITY_NONE);
+        waitForIdle();
 
         final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
         final Intent timeTick = new Intent(Intent.ACTION_TIME_TICK);
@@ -2375,6 +2380,7 @@
 
         mUidObserver.onUidStateChanged(receiverGreenApp.info.uid,
                 ActivityManager.PROCESS_STATE_TOP, 0, ActivityManager.PROCESS_CAPABILITY_NONE);
+        waitForIdle();
 
         final Intent timeTick = new Intent(Intent.ACTION_TIME_TICK);
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java b/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java
index eb6efd2..03439e55 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java
@@ -39,8 +39,8 @@
 
 import androidx.test.platform.app.InstrumentationRegistry;
 
+import com.android.modules.utils.testing.ExtendedMockitoRule;
 import com.android.modules.utils.testing.TestableDeviceConfig;
-import com.android.server.ExtendedMockitoRule;
 import com.android.server.LocalServices;
 import com.android.server.ServiceThread;
 import com.android.server.appop.AppOpsService;
@@ -85,22 +85,17 @@
     @Mock
     private PackageManagerInternal mPackageManagerInt;
 
-    private final TestableDeviceConfig mDeviceConfig = new TestableDeviceConfig();
-
     @Rule
     public final ApplicationExitInfoTest.ServiceThreadRule
             mServiceThreadRule = new ApplicationExitInfoTest.ServiceThreadRule();
 
     @Rule
     public final ExtendedMockitoRule mExtendedMockitoRule = new ExtendedMockitoRule.Builder(this)
-            .dynamiclyConfigureSessionBuilder(
-                    sessionBuilder -> mDeviceConfig.setUpMockedClasses(sessionBuilder))
-            .build();
+            .addStaticMockFixtures(TestableDeviceConfig::new).build();
 
     @Before
     public void setUp() {
         System.loadLibrary("mockingservicestestjni");
-        mDeviceConfig.setUpMockBehaviors();
         mHandlerThread = new HandlerThread("");
         mHandlerThread.start();
         mHandler = new Handler(mHandlerThread.getLooper());
@@ -131,7 +126,6 @@
         mHandlerThread.quit();
         mThread.quit();
         mCountDown = null;
-        mDeviceConfig.tearDown();
     }
 
     private ProcessRecord makeProcessRecord(int pid, int uid, int packageUid, String processName,
@@ -975,7 +969,7 @@
 
         mCachedAppOptimizerUnderTest.mLastCompactionStats.clear();
 
-        if (CachedAppOptimizer.ENABLE_FILE_COMPACT) {
+        if (CachedAppOptimizer.ENABLE_SHARED_AND_CODE_COMPACT) {
             // We force a some compaction
             mCachedAppOptimizerUnderTest.compactApp(processRecord,
                     CachedAppOptimizer.CompactProfile.SOME, CachedAppOptimizer.CompactSource.APP,
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
index c6a914b..1f4563f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -68,6 +68,7 @@
 import static com.android.server.am.ProcessList.VISIBLE_APP_ADJ;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.AdditionalAnswers.answer;
@@ -1970,6 +1971,36 @@
 
     @SuppressWarnings("GuardedBy")
     @Test
+    public void testUpdateOomAdj_DoOne_PendingFinishAttach() {
+        ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
+                MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
+        app.setPendingFinishAttach(true);
+        app.mState.setHasForegroundActivities(false);
+
+        sService.mOomAdjuster.setAttachingProcessStatesLSP(app);
+        updateOomAdj(app);
+
+        assertProcStates(app, PROCESS_STATE_CACHED_EMPTY, FOREGROUND_APP_ADJ,
+                SCHED_GROUP_DEFAULT);
+    }
+
+    @SuppressWarnings("GuardedBy")
+    @Test
+    public void testUpdateOomAdj_DoOne_TopApp_PendingFinishAttach() {
+        ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
+                MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
+        app.setPendingFinishAttach(true);
+        app.mState.setHasForegroundActivities(true);
+
+        sService.mOomAdjuster.setAttachingProcessStatesLSP(app);
+        updateOomAdj(app);
+
+        assertProcStates(app, PROCESS_STATE_TOP, FOREGROUND_APP_ADJ,
+                SCHED_GROUP_TOP_APP);
+    }
+
+    @SuppressWarnings("GuardedBy")
+    @Test
     public void testUpdateOomAdj_UidIdle_StopService() {
         final ProcessRecord app1 = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
                 MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
@@ -2491,6 +2522,28 @@
 
     @SuppressWarnings("GuardedBy")
     @Test
+    public void testUpdateOomAdj_DoOne_AboveClient_SameProcess() {
+        ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
+                MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
+        doReturn(PROCESS_STATE_TOP).when(sService.mAtmInternal).getTopProcessState();
+        doReturn(app).when(sService).getTopApp();
+        sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+        sService.mOomAdjuster.updateOomAdjLocked(app, OOM_ADJ_REASON_NONE);
+
+        assertEquals(FOREGROUND_APP_ADJ, app.mState.getSetAdj());
+
+        // Simulate binding to a service in the same process using BIND_ABOVE_CLIENT and
+        // verify that its OOM adjustment level is unaffected.
+        bindService(app, app, null, Context.BIND_ABOVE_CLIENT, mock(IBinder.class));
+        app.mServices.updateHasAboveClientLocked();
+        assertFalse(app.mServices.hasAboveClient());
+
+        sService.mOomAdjuster.updateOomAdjLocked(app, OOM_ADJ_REASON_NONE);
+        assertEquals(FOREGROUND_APP_ADJ, app.mState.getSetAdj());
+    }
+
+    @SuppressWarnings("GuardedBy")
+    @Test
     public void testUpdateOomAdj_DoAll_Side_Cycle() {
         final ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
                 MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
diff --git a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
index 32243f0..cd3a78e 100644
--- a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
@@ -52,6 +52,7 @@
 import android.app.GameModeInfo;
 import android.app.GameState;
 import android.app.IGameModeListener;
+import android.app.IGameStateListener;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.ContextWrapper;
@@ -1578,6 +1579,71 @@
         assertFalse(gameManagerService.mHandler.hasMessages(SET_GAME_STATE));
     }
 
+    @Test
+    public void testAddGameStateListener() throws Exception {
+        mockModifyGameModeGranted();
+        GameManagerService gameManagerService =
+                new GameManagerService(mMockContext, mTestLooper.getLooper());
+        mockDeviceConfigAll();
+        startUser(gameManagerService, USER_ID_1);
+
+        IGameStateListener mockListener = Mockito.mock(IGameStateListener.class);
+        IBinder binder = Mockito.mock(IBinder.class);
+        when(mockListener.asBinder()).thenReturn(binder);
+        gameManagerService.addGameStateListener(mockListener);
+        verify(binder).linkToDeath(mDeathRecipientCaptor.capture(), anyInt());
+
+        mockAppCategory(mPackageName, ApplicationInfo.CATEGORY_AUDIO);
+        GameState gameState = new GameState(true, GameState.MODE_NONE);
+        gameManagerService.setGameState(mPackageName, gameState, USER_ID_1);
+        assertFalse(gameManagerService.mHandler.hasMessages(SET_GAME_STATE));
+        mTestLooper.dispatchAll();
+        verify(mockListener, never()).onGameStateChanged(anyString(), any(), anyInt());
+
+        mockAppCategory(mPackageName, ApplicationInfo.CATEGORY_GAME);
+        gameState = new GameState(true, GameState.MODE_NONE);
+        gameManagerService.setGameState(mPackageName, gameState, USER_ID_1);
+        assertTrue(gameManagerService.mHandler.hasMessages(SET_GAME_STATE));
+        mTestLooper.dispatchAll();
+        verify(mockListener).onGameStateChanged(mPackageName, gameState, USER_ID_1);
+        reset(mockListener);
+
+        gameState = new GameState(false, GameState.MODE_CONTENT);
+        gameManagerService.setGameState(mPackageName, gameState, USER_ID_1);
+        mTestLooper.dispatchAll();
+        verify(mockListener).onGameStateChanged(mPackageName, gameState, USER_ID_1);
+        reset(mockListener);
+
+        mDeathRecipientCaptor.getValue().binderDied();
+        verify(binder).unlinkToDeath(eq(mDeathRecipientCaptor.getValue()), anyInt());
+        gameManagerService.setGameState(mPackageName, gameState, USER_ID_1);
+        assertTrue(gameManagerService.mHandler.hasMessages(SET_GAME_STATE));
+        mTestLooper.dispatchAll();
+        verify(mockListener, never()).onGameStateChanged(anyString(), any(), anyInt());
+    }
+
+    @Test
+    public void testRemoveGameStateListener() throws Exception {
+        mockModifyGameModeGranted();
+        GameManagerService gameManagerService =
+                new GameManagerService(mMockContext, mTestLooper.getLooper());
+        mockDeviceConfigAll();
+        startUser(gameManagerService, USER_ID_1);
+
+        IGameStateListener mockListener = Mockito.mock(IGameStateListener.class);
+        IBinder binder = Mockito.mock(IBinder.class);
+        when(mockListener.asBinder()).thenReturn(binder);
+
+        gameManagerService.addGameStateListener(mockListener);
+        gameManagerService.removeGameStateListener(mockListener);
+        mockAppCategory(mPackageName, ApplicationInfo.CATEGORY_GAME);
+        GameState gameState = new GameState(false, GameState.MODE_CONTENT);
+        gameManagerService.setGameState(mPackageName, gameState, USER_ID_1);
+        assertTrue(gameManagerService.mHandler.hasMessages(SET_GAME_STATE));
+        mTestLooper.dispatchAll();
+        verify(mockListener, never()).onGameStateChanged(anyString(), any(), anyInt());
+    }
+
     private List<String> readGameModeInterventionList() throws Exception {
         final File interventionFile = new File(InstrumentationRegistry.getContext().getFilesDir(),
                 "system/game_mode_intervention.list");
@@ -2221,7 +2287,7 @@
         String[] packages = {mPackageName};
         when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages);
         gameManagerService.mUidObserver.onUidStateChanged(
-                DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0);
+                DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TOP, 0, 0);
         verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, true);
     }
 
@@ -2238,12 +2304,12 @@
         doAnswer(inv -> powerState.put(inv.getArgument(0), inv.getArgument(1)))
                 .when(mMockPowerManager).setPowerMode(anyInt(), anyBoolean());
         gameManagerService.mUidObserver.onUidStateChanged(
-                DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0);
+                DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TOP, 0, 0);
         assertTrue(powerState.get(Mode.GAME));
         gameManagerService.mUidObserver.onUidStateChanged(
                 DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
         gameManagerService.mUidObserver.onUidStateChanged(
-                somePackageId, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0);
+                somePackageId, ActivityManager.PROCESS_STATE_TOP, 0, 0);
         assertTrue(powerState.get(Mode.GAME));
         gameManagerService.mUidObserver.onUidStateChanged(
                 somePackageId, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
@@ -2260,13 +2326,13 @@
         int somePackageId = DEFAULT_PACKAGE_UID + 1;
         when(mMockPackageManager.getPackagesForUid(somePackageId)).thenReturn(packages2);
         gameManagerService.mUidObserver.onUidStateChanged(
+                DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TOP, 0, 0);
+        gameManagerService.mUidObserver.onUidStateChanged(
+                somePackageId, ActivityManager.PROCESS_STATE_TOP, 0, 0);
+        gameManagerService.mUidObserver.onUidStateChanged(
                 DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0);
         gameManagerService.mUidObserver.onUidStateChanged(
                 somePackageId, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0);
-        gameManagerService.mUidObserver.onUidStateChanged(
-                DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
-        gameManagerService.mUidObserver.onUidStateChanged(
-                somePackageId, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
         verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, true);
         verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, false);
     }
@@ -2277,9 +2343,9 @@
         String[] packages = {mPackageName};
         when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages);
         gameManagerService.mUidObserver.onUidStateChanged(
-                DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0);
+                DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TOP, 0, 0);
         gameManagerService.mUidObserver.onUidStateChanged(
-                DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
+                DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0);
         verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, false);
     }
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsLegacyRestrictionsTest.java b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsLegacyRestrictionsTest.java
index 021d01c..1973428 100644
--- a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsLegacyRestrictionsTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsLegacyRestrictionsTest.java
@@ -57,7 +57,7 @@
     Handler mHandler;
 
     @Mock
-    AppOpsCheckingServiceInterface mLegacyAppOpsService;
+    AppOpsRestrictions.AppOpsRestrictionRemovedListener mRestrictionRemovedListener;
 
     AppOpsRestrictions mAppOpsRestrictions;
 
@@ -75,7 +75,8 @@
             r.run();
             return true;
         });
-        mAppOpsRestrictions = new AppOpsRestrictionsImpl(mContext, mHandler, mLegacyAppOpsService);
+        mAppOpsRestrictions = new AppOpsRestrictionsImpl(mContext, mHandler,
+                mRestrictionRemovedListener);
     }
 
     @After
@@ -271,7 +272,7 @@
     public void testNotify() {
         mAppOpsRestrictions.setUserRestriction(mClientToken, mUserId1, mOpCode1, true, null);
         mAppOpsRestrictions.clearUserRestrictions(mClientToken);
-        Mockito.verify(mLegacyAppOpsService, Mockito.times(1))
-                .notifyWatchersOfChange(mOpCode1, UID_ANY);
+        Mockito.verify(mRestrictionRemovedListener, Mockito.times(1))
+                .onAppOpsRestrictionRemoved(mOpCode1);
     }
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java
index 44ec26e..646f486 100644
--- a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java
@@ -23,6 +23,7 @@
 import static android.app.AppOpsManager.OP_READ_SMS;
 import static android.app.AppOpsManager.OP_WIFI_SCAN;
 import static android.app.AppOpsManager.OP_WRITE_SMS;
+import static android.os.UserHandle.getAppId;
 import static android.os.UserHandle.getUserId;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
@@ -37,8 +38,10 @@
 
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assume.assumeFalse;
 import static org.mockito.ArgumentMatchers.any;
 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;
@@ -52,6 +55,7 @@
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.Process;
+import android.permission.PermissionManager;
 import android.provider.Settings;
 import android.util.ArrayMap;
 
@@ -108,6 +112,7 @@
         mAppOpsService = new AppOpsService(mRecentAccessesFile, mStorageFile, mHandler,
                 spy(sContext));
         mAppOpsService.mHistoricalRegistry.systemReady(sContext.getContentResolver());
+        mAppOpsService.prepareInternalCallbacks();
 
         // Always approve all permission checks
         doNothing().when(mAppOpsService.mContext).enforcePermission(anyString(), anyInt(),
@@ -116,6 +121,8 @@
 
     @Before
     public void setUp() {
+        assumeFalse(PermissionManager.USE_ACCESS_CHECKING_SERVICE);
+
         mStorageFile = new File(sContext.getFilesDir(), APP_OPS_FILENAME);
         mRecentAccessesFile = new File(sContext.getFilesDir(), APP_OPS_ACCESSES_FILENAME);
         mStorageFile.delete();
@@ -135,6 +142,11 @@
 
     @After
     public void tearDown() {
+        // @After methods are still executed even if there's assumption failure in @Before.
+        if (PermissionManager.USE_ACCESS_CHECKING_SERVICE) {
+            return;
+        }
+
         mAppOpsService.shutdown();
 
         mMockingSession.finishMocking();
@@ -161,6 +173,8 @@
         when(mockPackageManagerInternal.getPackageStateInternal(sMyPackageName))
                 .thenReturn(mockMyPSInternal);
         when(mockPackageManagerInternal.getPackage(sMyPackageName)).thenReturn(mockMyPkg);
+        when(mockPackageManagerInternal.getPackageUid(eq(sMyPackageName), anyLong(),
+                eq(getUserId(mMyUid)))).thenReturn(mMyUid);
         doReturn(mockPackageManagerInternal).when(
                 () -> LocalServices.getService(PackageManagerInternal.class));
 
@@ -184,6 +198,16 @@
         // Mock behavior to use specific Settings.Global.APPOP_HISTORY_PARAMETERS
         doReturn(null).when(() -> Settings.Global.getString(any(ContentResolver.class),
                 eq(Settings.Global.APPOP_HISTORY_PARAMETERS)));
+
+        prepareInstallInvocation(mockPackageManagerInternal);
+    }
+
+    private void prepareInstallInvocation(PackageManagerInternal mockPackageManagerInternal) {
+        when(mockPackageManagerInternal.getPackageList(any())).thenAnswer(invocation -> {
+            PackageManagerInternal.PackageListObserver observer = invocation.getArgument(0);
+            observer.onPackageAdded(sMyPackageName, getAppId(mMyUid));
+            return null;
+        });
     }
 
     @Test
diff --git a/services/tests/mockingservicestests/src/com/android/server/appop/OWNERS b/services/tests/mockingservicestests/src/com/android/server/appop/OWNERS
index 999ea0e..2fe78c5 100644
--- a/services/tests/mockingservicestests/src/com/android/server/appop/OWNERS
+++ b/services/tests/mockingservicestests/src/com/android/server/appop/OWNERS
@@ -1 +1,2 @@
+#Bug component: 137825
 include /core/java/android/permission/OWNERS
diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/SystemBackupAgentTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/SystemBackupAgentTest.java
index 327fc19..c6d8848 100644
--- a/services/tests/mockingservicestests/src/com/android/server/backup/SystemBackupAgentTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/backup/SystemBackupAgentTest.java
@@ -21,6 +21,7 @@
 import android.annotation.NonNull;
 import android.app.backup.BackupHelper;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.platform.test.annotations.Presubmit;
@@ -47,14 +48,20 @@
 
     private TestableSystemBackupAgent mSystemBackupAgent;
 
-    @Mock private Context mContextMock;
-    @Mock private UserManager mUserManagerMock;
+    @Mock
+    private Context mContextMock;
+    @Mock
+    private UserManager mUserManagerMock;
+    @Mock
+    private PackageManager mPackageManagerMock;
 
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
         mSystemBackupAgent = new TestableSystemBackupAgent();
         when(mContextMock.getSystemService(UserManager.class)).thenReturn(mUserManagerMock);
+        when(mPackageManagerMock.hasSystemFeature(
+                PackageManager.FEATURE_SLICES_DISABLED)).thenReturn(false);
     }
 
     @Test
@@ -80,6 +87,29 @@
     }
 
     @Test
+    public void onCreate_systemUser_slicesDisabled_addsAllNonSlicesHelpers() {
+        UserHandle userHandle = new UserHandle(UserHandle.USER_SYSTEM);
+        when(mUserManagerMock.isProfile()).thenReturn(false);
+        when(mPackageManagerMock.hasSystemFeature(
+                PackageManager.FEATURE_SLICES_DISABLED)).thenReturn(true);
+
+        mSystemBackupAgent.onCreate(userHandle, /* backupDestination= */ 0);
+
+        assertThat(mSystemBackupAgent.mAddedHelpers)
+                .containsExactly(
+                        "account_sync_settings",
+                        "preferred_activities",
+                        "notifications",
+                        "permissions",
+                        "usage_stats",
+                        "shortcut_manager",
+                        "account_manager",
+                        "people",
+                        "app_locales",
+                        "app_gender");
+    }
+
+    @Test
     public void onCreate_profileUser_addsProfileEligibleHelpers() {
         UserHandle userHandle = new UserHandle(NON_SYSTEM_USER_ID);
         when(mUserManagerMock.isProfile()).thenReturn(true);
@@ -130,5 +160,10 @@
         public Object getSystemService(@ServiceName @NonNull String name) {
             return null;
         }
+
+        @Override
+        public PackageManager getPackageManager() {
+            return mPackageManagerMock;
+        }
     }
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java
index dc1c6d5..7eb78eb 100644
--- a/services/tests/mockingservicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java
@@ -16,36 +16,46 @@
 
 package com.android.server.backup;
 
-import static com.google.common.truth.Truth.assertThat;
-
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
 
+import static com.google.common.truth.Truth.assertThat;
+
 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.anyString;
+import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.when;
 
+import android.annotation.UserIdInt;
 import android.app.backup.BackupAgent;
 import android.app.backup.BackupAnnotations;
 import android.app.backup.BackupAnnotations.BackupDestination;
 import android.app.backup.BackupRestoreEventLogger.DataTypeResult;
 import android.app.backup.IBackupManagerMonitor;
 import android.app.backup.IBackupObserver;
+import android.app.job.JobInfo;
+import android.app.job.JobScheduler;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
+import android.os.Handler;
 import android.platform.test.annotations.Presubmit;
+import android.provider.Settings;
+import android.testing.TestableContext;
 import android.util.FeatureFlagUtils;
+import android.util.KeyValueListParser;
 
+import androidx.test.core.app.ApplicationProvider;
 import androidx.test.filters.FlakyTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.server.backup.internal.BackupHandler;
 import com.android.server.backup.internal.LifecycleOperationStorage;
 import com.android.server.backup.internal.OnTaskFinishedListener;
 import com.android.server.backup.params.BackupParams;
@@ -75,9 +85,9 @@
     private static final String TEST_PACKAGE = "package1";
     private static final String[] TEST_PACKAGES = new String[] { TEST_PACKAGE };
     private static final String TEST_TRANSPORT = "transport";
-    private static final int WORKER_THREAD_TIMEOUT_MILLISECONDS = 1;
+    private static final int WORKER_THREAD_TIMEOUT_MILLISECONDS = 100;
+    @UserIdInt private static final int USER_ID = 0;
 
-    @Mock Context mContext;
     @Mock IBackupManagerMonitor mBackupManagerMonitor;
     @Mock IBackupObserver mBackupObserver;
     @Mock PackageManager mPackageManager;
@@ -86,7 +96,10 @@
     @Mock BackupTransportClient mBackupTransport;
     @Mock BackupEligibilityRules mBackupEligibilityRules;
     @Mock LifecycleOperationStorage mOperationStorage;
+    @Mock JobScheduler mJobScheduler;
+    @Mock BackupHandler mBackupHandler;
 
+    private TestableContext mContext;
     private MockitoSession mSession;
     private TestBackupService mService;
 
@@ -101,10 +114,16 @@
                 .startMocking();
         MockitoAnnotations.initMocks(this);
 
+        mContext = new TestableContext(ApplicationProvider.getApplicationContext());
+        mContext.addMockSystemService(JobScheduler.class, mJobScheduler);
+        mContext.getTestablePermissions().setPermission(android.Manifest.permission.BACKUP,
+                PackageManager.PERMISSION_GRANTED);
+
         mService = new TestBackupService(mContext, mPackageManager, mOperationStorage,
-                mTransportManager);
+                mTransportManager, mBackupHandler);
         mService.setEnabled(true);
         mService.setSetupComplete(true);
+        mService.enqueueFullBackup("com.test.backup.app", /* lastBackedUp= */ 0);
     }
 
     @After
@@ -115,6 +134,38 @@
     }
 
     @Test
+    public void testSetFrameworkSchedulingEnabled_enablesAndSchedulesBackups() throws Exception {
+        Settings.Secure.putInt(mContext.getContentResolver(),
+                Settings.Secure.BACKUP_SCHEDULING_ENABLED, 0);
+
+        mService.setFrameworkSchedulingEnabled(true);
+
+        assertThat(mService.isFrameworkSchedulingEnabled()).isTrue();
+        verify(mJobScheduler).schedule(
+                matchesJobWithId(KeyValueBackupJob.getJobIdForUserId(
+                        USER_ID)));
+        verify(mJobScheduler).schedule(
+                matchesJobWithId(FullBackupJob.getJobIdForUserId(
+                        USER_ID)));
+    }
+
+    private static JobInfo matchesJobWithId(int id) {
+        return argThat((jobInfo) -> jobInfo.getId() == id);
+    }
+
+    @Test
+    public void testSetFrameworkSchedulingEnabled_disablesAndCancelBackups() throws Exception {
+        Settings.Secure.putInt(mContext.getContentResolver(),
+                Settings.Secure.BACKUP_SCHEDULING_ENABLED, 1);
+
+        mService.setFrameworkSchedulingEnabled(false);
+
+        assertThat(mService.isFrameworkSchedulingEnabled()).isFalse();
+        verify(mJobScheduler).cancel(FullBackupJob.getJobIdForUserId(USER_ID));
+        verify(mJobScheduler).cancel(KeyValueBackupJob.getJobIdForUserId(USER_ID));
+    }
+
+    @Test
     public void initializeBackupEnableState_doesntWriteStateToDisk() {
         mService.initializeBackupEnableState();
 
@@ -265,8 +316,20 @@
         private volatile Thread mWorkerThread = null;
 
         TestBackupService(Context context, PackageManager packageManager,
-                LifecycleOperationStorage operationStorage, TransportManager transportManager) {
-            super(context, packageManager, operationStorage, transportManager);
+                LifecycleOperationStorage operationStorage, TransportManager transportManager,
+                BackupHandler backupHandler) {
+            super(context, packageManager, operationStorage, transportManager, backupHandler,
+                    createConstants(context));
+        }
+
+        private static BackupManagerConstants createConstants(Context context) {
+            BackupManagerConstants constants = new BackupManagerConstants(
+                    Handler.getMain(),
+                    context.getContentResolver());
+            // This will trigger constants default values to be set thus preventing invalid values
+            // being used in tests.
+            constants.update(new KeyValueListParser(','));
+            return constants;
         }
 
         @Override
diff --git a/services/tests/mockingservicestests/src/com/android/server/cpu/CpuInfoReaderTest.java b/services/tests/mockingservicestests/src/com/android/server/cpu/CpuInfoReaderTest.java
index 04f6f8b..2fbe8aa 100644
--- a/services/tests/mockingservicestests/src/com/android/server/cpu/CpuInfoReaderTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/cpu/CpuInfoReaderTest.java
@@ -48,6 +48,11 @@
     private static final String TAG = CpuInfoReaderTest.class.getSimpleName();
     private static final String ROOT_DIR_NAME = "CpuInfoReaderTest";
     private static final String VALID_CPUSET_DIR = "valid_cpuset";
+    private static final String VALID_CPUSET_WITH_EMPTY_CPUS = "valid_cpuset_with_empty_cpus";
+    private static final String VALID_CPUFREQ_WITH_EMPTY_AFFECTED_CPUS =
+            "valid_cpufreq_with_empty_affected_cpus";
+    private static final String VALID_CPUFREQ_WITH_EMPTY_RELATED_CPUS =
+            "valid_cpufreq_with_empty_related_cpus";
     private static final String VALID_CPUFREQ_WITH_TIME_IN_STATE_DIR =
             "valid_cpufreq_with_time_in_state";
     private static final String VALID_CPUFREQ_WITH_TIME_IN_STATE_2_DIR =
@@ -142,8 +147,8 @@
         expectedCpuInfos.clear();
         expectedCpuInfos.append(0, new CpuInfoReader.CpuInfo(/* cpuCore= */ 0,
                 FLAG_CPUSET_CATEGORY_TOP_APP, /* isOnline= */ true, /* curCpuFreqKHz= */ 1_000_000,
-                /* maxCpuFreqKHz= */ 2_500_000, /* avgTimeInStateCpuFreqKHz= */ 419_354,
-                /* normalizedAvailableCpuFreqKHz= */ 2_425_919,
+                /* maxCpuFreqKHz= */ 2_600_000, /* avgTimeInStateCpuFreqKHz= */ 419_354,
+                /* normalizedAvailableCpuFreqKHz= */ 2_525_919,
                 new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 10_000_000,
                         /* niceTimeMillis= */ 1_000_000, /* systemTimeMillis= */ 10_000_000,
                         /* idleTimeMillis= */ 110_000_000, /* iowaitTimeMillis= */ 1_100_000,
@@ -152,8 +157,8 @@
                         /* guestNiceTimeMillis= */ 0)));
         expectedCpuInfos.append(1, new CpuInfoReader.CpuInfo(/* cpuCore= */ 1,
                 FLAG_CPUSET_CATEGORY_TOP_APP, /* isOnline= */ true, /* curCpuFreqKHz= */ 2_800_000,
-                /* maxCpuFreqKHz= */ 2_800_000, /* avgTimeInStateCpuFreqKHz= */ 429_032,
-                /* normalizedAvailableCpuFreqKHz= */ 2_403_009,
+                /* maxCpuFreqKHz= */ 2_900_000, /* avgTimeInStateCpuFreqKHz= */ 429_032,
+                /* normalizedAvailableCpuFreqKHz= */ 2_503_009,
                 new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 900_000,
                         /* niceTimeMillis= */ 1_000_000, /* systemTimeMillis= */ 10_000_000,
                         /* idleTimeMillis= */ 1_000_000, /* iowaitTimeMillis= */ 90_000,
@@ -163,8 +168,8 @@
         expectedCpuInfos.append(2, new CpuInfoReader.CpuInfo(/* cpuCore= */ 2,
                 FLAG_CPUSET_CATEGORY_TOP_APP | FLAG_CPUSET_CATEGORY_BACKGROUND,
                 /* isOnline= */ true, /* curCpuFreqKHz= */ 2_000_000,
-                /* maxCpuFreqKHz= */ 2_000_000, /* avgTimeInStateCpuFreqKHz= */ 403_225,
-                /* normalizedAvailableCpuFreqKHz= */ 1_688_209,
+                /* maxCpuFreqKHz= */ 2_100_000, /* avgTimeInStateCpuFreqKHz= */ 403_225,
+                /* normalizedAvailableCpuFreqKHz= */ 1_788_209,
                 new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 10_000_000,
                         /* niceTimeMillis= */ 2_000_000, /* systemTimeMillis= */ 0,
                         /* idleTimeMillis= */ 10_000_000, /* iowaitTimeMillis= */ 1_000_000,
@@ -174,7 +179,7 @@
         expectedCpuInfos.append(3, new CpuInfoReader.CpuInfo(/* cpuCore= */ 3,
                 FLAG_CPUSET_CATEGORY_TOP_APP | FLAG_CPUSET_CATEGORY_BACKGROUND,
                 /* isOnline= */ false, /* curCpuFreqKHz= */ MISSING_FREQUENCY,
-                /* maxCpuFreqKHz= */ 2_000_000, /* avgTimeInStateCpuFreqKHz= */ MISSING_FREQUENCY,
+                /* maxCpuFreqKHz= */ 2_100_000, /* avgTimeInStateCpuFreqKHz= */ MISSING_FREQUENCY,
                 /* normalizedAvailableCpuFreqKHz= */ MISSING_FREQUENCY,
                 new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 2_000_000,
                         /* niceTimeMillis= */ 1_000_000, /* systemTimeMillis= */ 1_000_000,
@@ -403,6 +408,104 @@
     }
 
     @Test
+    public void testReadCpuInfoWithEmptyRelatedCpus() throws Exception {
+        CpuInfoReader cpuInfoReader = newCpuInfoReader(getCacheFile(VALID_CPUSET_DIR),
+                getCacheFile(VALID_CPUFREQ_WITH_EMPTY_RELATED_CPUS),
+                getCacheFile(VALID_PROC_STAT));
+
+        SparseArray<CpuInfoReader.CpuInfo> actualCpuInfos = cpuInfoReader.readCpuInfos();
+        SparseArray<CpuInfoReader.CpuInfo> expectedCpuInfos = new SparseArray<>();
+
+        expectedCpuInfos.append(1, new CpuInfoReader.CpuInfo(/* cpuCore= */ 1,
+                FLAG_CPUSET_CATEGORY_TOP_APP, /* isOnline= */ true, /* curCpuFreqKHz= */ 1_450_000,
+                /* maxCpuFreqKHz= */ 2_800_000, /* avgTimeInStateCpuFreqKHz= */ 502_380,
+                /* normalizedAvailableCpuFreqKHz= */ 2_693_525,
+                new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 28_949_280,
+                        /* niceTimeMillis= */ 7_799_450, /* systemTimeMillis= */ 54_004_020,
+                        /* idleTimeMillis= */ 402_707_120, /* iowaitTimeMillis= */ 1_186_960,
+                        /* irqTimeMillis= */ 14_786_940, /* softirqTimeMillis= */ 1_498_130,
+                        /* stealTimeMillis= */ 78_780, /* guestTimeMillis= */ 0,
+                        /* guestNiceTimeMillis= */ 0)));
+
+        compareCpuInfos("CPU infos with policy 0 containing an empty related_cpus file",
+                expectedCpuInfos, actualCpuInfos);
+    }
+
+    @Test
+    public void testReadCpuInfoWithEmptyCpusetCpus() throws Exception {
+        CpuInfoReader cpuInfoReader = newCpuInfoReader(getCacheFile(VALID_CPUSET_WITH_EMPTY_CPUS),
+                getCacheFile(VALID_CPUFREQ_WITH_TIME_IN_STATE_DIR),
+                getCacheFile(VALID_PROC_STAT));
+
+        SparseArray<CpuInfoReader.CpuInfo> actualCpuInfos = cpuInfoReader.readCpuInfos();
+        SparseArray<CpuInfoReader.CpuInfo> expectedCpuInfos = new SparseArray<>();
+        expectedCpuInfos.append(0, new CpuInfoReader.CpuInfo(/* cpuCore= */ 0,
+                FLAG_CPUSET_CATEGORY_TOP_APP, /* isOnline= */ true, /* curCpuFreqKHz= */ 1_230_000,
+                /* maxCpuFreqKHz= */ 2_500_000, /* avgTimeInStateCpuFreqKHz= */ 488_095,
+                new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 32_249_610,
+                        /* niceTimeMillis= */ 7_950_930, /* systemTimeMillis= */ 52_227_050,
+                        /* idleTimeMillis= */ 409_036_950, /* iowaitTimeMillis= */ 1_322_810,
+                        /* irqTimeMillis= */ 8_146_740, /* softirqTimeMillis= */ 428_970,
+                        /* stealTimeMillis= */ 81_950, /* guestTimeMillis= */ 0,
+                        /* guestNiceTimeMillis= */ 0)));
+        expectedCpuInfos.append(1, new CpuInfoReader.CpuInfo(/* cpuCore= */ 1,
+                FLAG_CPUSET_CATEGORY_TOP_APP, /* isOnline= */ true, /* curCpuFreqKHz= */ 1_450_000,
+                /* maxCpuFreqKHz= */ 2_800_000, /* avgTimeInStateCpuFreqKHz= */ 502_380,
+                new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 28_949_280,
+                        /* niceTimeMillis= */ 7_799_450, /* systemTimeMillis= */ 54_004_020,
+                        /* idleTimeMillis= */ 402_707_120, /* iowaitTimeMillis= */ 1_186_960,
+                        /* irqTimeMillis= */ 14_786_940, /* softirqTimeMillis= */ 1_498_130,
+                        /* stealTimeMillis= */ 78_780, /* guestTimeMillis= */ 0,
+                        /* guestNiceTimeMillis= */ 0)));
+        expectedCpuInfos.append(2, new CpuInfoReader.CpuInfo(/* cpuCore= */ 2,
+                FLAG_CPUSET_CATEGORY_TOP_APP, /* isOnline= */ true, /* curCpuFreqKHz= */ 1_000_000,
+                /* maxCpuFreqKHz= */ 2_000_000, /* avgTimeInStateCpuFreqKHz= */ 464_285,
+                new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 28_959_280,
+                        /* niceTimeMillis= */ 7_789_450, /* systemTimeMillis= */ 54_014_020,
+                        /* idleTimeMillis= */ 402_717_120, /* iowaitTimeMillis= */ 1_166_960,
+                        /* irqTimeMillis= */ 14_796_940, /* softirqTimeMillis= */ 1_478_130,
+                        /* stealTimeMillis= */ 88_780, /* guestTimeMillis= */ 0,
+                        /* guestNiceTimeMillis= */ 0)));
+        expectedCpuInfos.append(3, new CpuInfoReader.CpuInfo(/* cpuCore= */ 3,
+                FLAG_CPUSET_CATEGORY_TOP_APP, /* isOnline= */ true, /* curCpuFreqKHz= */ 1_000_000,
+                /* maxCpuFreqKHz= */ 2_000_000, /* avgTimeInStateCpuFreqKHz= */ 464_285,
+                /* normalizedAvailableCpuFreqKHz= */ 1_907_125,
+                new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 32_349_610,
+                        /* niceTimeMillis= */ 7_850_930, /* systemTimeMillis= */ 52_127_050,
+                        /* idleTimeMillis= */ 409_136_950, /* iowaitTimeMillis= */ 1_332_810,
+                        /* irqTimeMillis= */ 8_136_740, /* softirqTimeMillis= */ 438_970,
+                        /* stealTimeMillis= */ 71_950, /* guestTimeMillis= */ 0,
+                        /* guestNiceTimeMillis= */ 0)));
+
+        compareCpuInfos("CPU infos with empty background cpu set", expectedCpuInfos,
+                actualCpuInfos);
+    }
+
+    @Test
+    public void testReadCpuInfoWithEmptyAffectedCpus() throws Exception {
+        CpuInfoReader cpuInfoReader = newCpuInfoReader(getCacheFile(VALID_CPUSET_DIR),
+                getCacheFile(VALID_CPUFREQ_WITH_EMPTY_AFFECTED_CPUS),
+                getCacheFile(VALID_PROC_STAT));
+
+        SparseArray<CpuInfoReader.CpuInfo> actualCpuInfos = cpuInfoReader.readCpuInfos();
+        SparseArray<CpuInfoReader.CpuInfo> expectedCpuInfos = new SparseArray<>();
+
+        expectedCpuInfos.append(1, new CpuInfoReader.CpuInfo(/* cpuCore= */ 1,
+                FLAG_CPUSET_CATEGORY_TOP_APP, /* isOnline= */ true, /* curCpuFreqKHz= */ 1_450_000,
+                /* maxCpuFreqKHz= */ 2_800_000, /* avgTimeInStateCpuFreqKHz= */ 502_380,
+                /* normalizedAvailableCpuFreqKHz= */ 2_693_525,
+                new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 28_949_280,
+                        /* niceTimeMillis= */ 7_799_450, /* systemTimeMillis= */ 54_004_020,
+                        /* idleTimeMillis= */ 402_707_120, /* iowaitTimeMillis= */ 1_186_960,
+                        /* irqTimeMillis= */ 14_786_940, /* softirqTimeMillis= */ 1_498_130,
+                        /* stealTimeMillis= */ 78_780, /* guestTimeMillis= */ 0,
+                        /* guestNiceTimeMillis= */ 0)));
+
+        compareCpuInfos("CPU infos with policy 0 containing an empty affected_cpus file",
+                expectedCpuInfos, actualCpuInfos);
+    }
+
+    @Test
     public void testReadCpuInfoWithEmptyProcStat() throws Exception {
         File emptyFile = getCacheFile(EMPTY_FILE);
         assertWithMessage("Create empty file %s", emptyFile).that(emptyFile.createNewFile())
diff --git a/services/tests/mockingservicestests/src/com/android/server/cpu/CpuMonitorServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/cpu/CpuMonitorServiceTest.java
index 5a5f525..994313f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/cpu/CpuMonitorServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/cpu/CpuMonitorServiceTest.java
@@ -48,7 +48,7 @@
 import android.util.ArraySet;
 import android.util.SparseArray;
 
-import com.android.server.ExtendedMockitoRule;
+import com.android.modules.utils.testing.ExtendedMockitoRule;
 import com.android.server.LocalServices;
 import com.android.server.Watchdog;
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayBrightnessStateTest.java b/services/tests/mockingservicestests/src/com/android/server/display/DisplayBrightnessStateTest.java
deleted file mode 100644
index 50996d7..0000000
--- a/services/tests/mockingservicestests/src/com/android/server/display/DisplayBrightnessStateTest.java
+++ /dev/null
@@ -1,67 +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.server.display;
-
-import static org.junit.Assert.assertEquals;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.server.display.brightness.BrightnessReason;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class DisplayBrightnessStateTest {
-    private static final float FLOAT_DELTA = 0.001f;
-
-    private DisplayBrightnessState.Builder mDisplayBrightnessStateBuilder;
-
-    @Before
-    public void before() {
-        mDisplayBrightnessStateBuilder = new DisplayBrightnessState.Builder();
-    }
-
-    @Test
-    public void validateAllDisplayBrightnessStateFieldsAreSetAsExpected() {
-        float brightness = 0.3f;
-        float sdrBrightness = 0.2f;
-        BrightnessReason brightnessReason = new BrightnessReason();
-        brightnessReason.setReason(BrightnessReason.REASON_AUTOMATIC);
-        brightnessReason.setModifier(BrightnessReason.MODIFIER_DIMMED);
-        DisplayBrightnessState displayBrightnessState =
-                mDisplayBrightnessStateBuilder.setBrightness(brightness).setSdrBrightness(
-                        sdrBrightness).setBrightnessReason(brightnessReason).build();
-
-        assertEquals(displayBrightnessState.getBrightness(), brightness, FLOAT_DELTA);
-        assertEquals(displayBrightnessState.getSdrBrightness(), sdrBrightness, FLOAT_DELTA);
-        assertEquals(displayBrightnessState.getBrightnessReason(), brightnessReason);
-        assertEquals(displayBrightnessState.toString(), getString(displayBrightnessState));
-    }
-
-    private String getString(DisplayBrightnessState displayBrightnessState) {
-        StringBuilder sb = new StringBuilder();
-        sb.append("DisplayBrightnessState:");
-        sb.append("\n    brightness:" + displayBrightnessState.getBrightness());
-        sb.append("\n    sdrBrightness:" + displayBrightnessState.getSdrBrightness());
-        sb.append("\n    brightnessReason:" + displayBrightnessState.getBrightnessReason());
-        return sb.toString();
-    }
-}
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
deleted file mode 100644
index 1700760..0000000
--- a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
+++ /dev/null
@@ -1,1106 +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.server.display;
-
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
-
-import static org.junit.Assert.assertNotNull;
-import static org.mockito.ArgumentMatchers.any;
-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.isA;
-import static org.mockito.ArgumentMatchers.isNull;
-import static org.mockito.Mockito.atLeastOnce;
-import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.hardware.Sensor;
-import android.hardware.SensorEventListener;
-import android.hardware.SensorManager;
-import android.hardware.display.DisplayManagerInternal.DisplayPowerCallbacks;
-import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.PowerManager;
-import android.os.SystemProperties;
-import android.os.test.TestLooper;
-import android.provider.Settings;
-import android.testing.TestableContext;
-import android.util.FloatProperty;
-import android.view.Display;
-import android.view.DisplayInfo;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-import androidx.test.platform.app.InstrumentationRegistry;
-
-import com.android.server.ExtendedMockitoRule;
-import com.android.server.LocalServices;
-import com.android.server.am.BatteryStatsService;
-import com.android.server.display.RampAnimator.DualRampAnimator;
-import com.android.server.display.brightness.BrightnessEvent;
-import com.android.server.display.color.ColorDisplayService;
-import com.android.server.display.layout.Layout;
-import com.android.server.display.whitebalance.DisplayWhiteBalanceController;
-import com.android.server.policy.WindowManagerPolicy;
-import com.android.server.testutils.OffsettableClock;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
-import org.mockito.Mock;
-import org.mockito.quality.Strictness;
-import org.mockito.stubbing.Answer;
-
-import java.util.List;
-
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public final class DisplayPowerController2Test {
-    private static final int DISPLAY_ID = Display.DEFAULT_DISPLAY;
-    private static final String UNIQUE_ID = "unique_id_test123";
-    private static final int FOLLOWER_DISPLAY_ID = DISPLAY_ID + 1;
-    private static final String FOLLOWER_UNIQUE_ID = "unique_id_456";
-    private static final int SECOND_FOLLOWER_DISPLAY_ID = FOLLOWER_DISPLAY_ID + 1;
-    private static final String SECOND_FOLLOWER_UNIQUE_DISPLAY_ID = "unique_id_789";
-    private static final float PROX_SENSOR_MAX_RANGE = 5;
-
-    private OffsettableClock mClock;
-    private TestLooper mTestLooper;
-    private Handler mHandler;
-    private DisplayPowerControllerHolder mHolder;
-    private Sensor mProxSensor;
-
-    @Mock
-    private DisplayPowerCallbacks mDisplayPowerCallbacksMock;
-    @Mock
-    private SensorManager mSensorManagerMock;
-    @Mock
-    private DisplayBlanker mDisplayBlankerMock;
-    @Mock
-    private BrightnessTracker mBrightnessTrackerMock;
-    @Mock
-    private WindowManagerPolicy mWindowManagerPolicyMock;
-    @Mock
-    private PowerManager mPowerManagerMock;
-    @Mock
-    private ColorDisplayService.ColorDisplayServiceInternal mCdsiMock;
-
-    @Captor
-    private ArgumentCaptor<SensorEventListener> mSensorEventListenerCaptor;
-
-    @Rule
-    public final TestableContext mContext = new TestableContext(
-            InstrumentationRegistry.getInstrumentation().getContext());
-
-    @Rule
-    public final ExtendedMockitoRule mExtendedMockitoRule =
-            new ExtendedMockitoRule.Builder(this)
-                    .setStrictness(Strictness.LENIENT)
-                    .spyStatic(SystemProperties.class)
-                    .spyStatic(BatteryStatsService.class)
-                    .build();
-
-    @Before
-    public void setUp() throws Exception {
-        mClock = new OffsettableClock.Stopped();
-        mTestLooper = new TestLooper(mClock::now);
-        mHandler = new Handler(mTestLooper.getLooper());
-
-        // Put the system into manual brightness by default, just to minimize unexpected events and
-        // have a consistent starting state
-        Settings.System.putInt(mContext.getContentResolver(),
-                Settings.System.SCREEN_BRIGHTNESS_MODE,
-                Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL);
-
-        addLocalServiceMock(WindowManagerPolicy.class, mWindowManagerPolicyMock);
-        addLocalServiceMock(ColorDisplayService.ColorDisplayServiceInternal.class,
-                mCdsiMock);
-
-        mContext.addMockSystemService(PowerManager.class, mPowerManagerMock);
-
-        doAnswer((Answer<Void>) invocationOnMock -> null).when(() ->
-                SystemProperties.set(anyString(), any()));
-        doAnswer((Answer<Void>) invocationOnMock -> null).when(BatteryStatsService::getService);
-
-        setUpSensors();
-        mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
-    }
-
-    @After
-    public void tearDown() {
-        LocalServices.removeServiceForTest(WindowManagerPolicy.class);
-        LocalServices.removeServiceForTest(ColorDisplayService.ColorDisplayServiceInternal.class);
-    }
-
-    @Test
-    public void testReleaseProxSuspendBlockersOnExit() throws Exception {
-        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
-        // Send a display power request
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
-        dpr.useProximitySensor = true;
-        mHolder.dpc.requestPowerState(dpr, false /* waitForNegativeProximity */);
-
-        // Run updatePowerState to start listener for the prox sensor
-        advanceTime(1);
-
-        SensorEventListener listener = getSensorEventListener(mProxSensor);
-        assertNotNull(listener);
-
-        listener.onSensorChanged(TestUtils.createSensorEvent(mProxSensor, /* value= */ 5));
-        advanceTime(1);
-
-        // two times, one for unfinished business and one for proximity
-        verify(mHolder.wakelockController, times(2)).acquireWakelock(
-                WakelockController.WAKE_LOCK_UNFINISHED_BUSINESS);
-        verify(mHolder.wakelockController).acquireWakelock(
-                WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE);
-
-        mHolder.dpc.stop();
-        advanceTime(1);
-        // two times, one for unfinished business and one for proximity
-        verify(mHolder.wakelockController, times(2)).acquireWakelock(
-                WakelockController.WAKE_LOCK_UNFINISHED_BUSINESS);
-        verify(mHolder.wakelockController).acquireWakelock(
-                WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE);
-    }
-
-    @Test
-    public void testScreenOffBecauseOfProximity() throws Exception {
-        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
-        // Send a display power request
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
-        dpr.useProximitySensor = true;
-        mHolder.dpc.requestPowerState(dpr, false /* waitForNegativeProximity */);
-
-        // Run updatePowerState to start listener for the prox sensor
-        advanceTime(1);
-
-        SensorEventListener listener = getSensorEventListener(mProxSensor);
-        assertNotNull(listener);
-
-        // Send a positive proximity event
-        listener.onSensorChanged(TestUtils.createSensorEvent(mProxSensor, /* value= */ 1));
-        advanceTime(1);
-
-        // The display should have been turned off
-        verify(mHolder.displayPowerState).setScreenState(Display.STATE_OFF);
-
-        clearInvocations(mHolder.displayPowerState);
-        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_OFF);
-        // Send a negative proximity event
-        listener.onSensorChanged(TestUtils.createSensorEvent(mProxSensor,
-                (int) PROX_SENSOR_MAX_RANGE + 1));
-        // Advance time by less than PROXIMITY_SENSOR_NEGATIVE_DEBOUNCE_DELAY
-        advanceTime(1);
-
-        // The prox sensor is debounced so the display should not have been turned back on yet
-        verify(mHolder.displayPowerState, never()).setScreenState(Display.STATE_ON);
-
-        // Advance time by more than PROXIMITY_SENSOR_NEGATIVE_DEBOUNCE_DELAY
-        advanceTime(1000);
-
-        // The display should have been turned back on
-        verify(mHolder.displayPowerState).setScreenState(Display.STATE_ON);
-    }
-
-    @Test
-    public void testScreenOffBecauseOfProximity_ProxSensorGone() throws Exception {
-        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
-        // Send a display power request
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
-        dpr.useProximitySensor = true;
-        mHolder.dpc.requestPowerState(dpr, false /* waitForNegativeProximity */);
-
-        // Run updatePowerState to start listener for the prox sensor
-        advanceTime(1);
-
-        SensorEventListener listener = getSensorEventListener(mProxSensor);
-        assertNotNull(listener);
-
-        // Send a positive proximity event
-        listener.onSensorChanged(TestUtils.createSensorEvent(mProxSensor, /* value= */ 1));
-        advanceTime(1);
-
-        // The display should have been turned off
-        verify(mHolder.displayPowerState).setScreenState(Display.STATE_OFF);
-
-        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_OFF);
-        // The display device changes and we no longer have a prox sensor
-        reset(mSensorManagerMock);
-        setUpDisplay(DISPLAY_ID, "new_unique_id", mHolder.display, mock(DisplayDevice.class),
-                mock(DisplayDeviceConfig.class), /* isEnabled= */ true);
-        mHolder.dpc.onDisplayChanged(mHolder.hbmMetadata, Layout.NO_LEAD_DISPLAY);
-
-        advanceTime(1); // Run updatePowerState
-
-        // The display should have been turned back on and the listener should have been
-        // unregistered
-        verify(mHolder.displayPowerState).setScreenState(Display.STATE_ON);
-        verify(mSensorManagerMock).unregisterListener(listener);
-    }
-
-    @Test
-    public void testProximitySensorListenerNotRegisteredForNonDefaultDisplay() {
-        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
-        // send a display power request
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
-        dpr.useProximitySensor = true;
-        final DisplayPowerControllerHolder followerDpc =
-                createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
-        followerDpc.dpc.requestPowerState(dpr, false /* waitForNegativeProximity */);
-
-        // Run updatePowerState
-        advanceTime(1);
-
-        verify(mSensorManagerMock, never()).registerListener(any(SensorEventListener.class),
-                eq(mProxSensor), anyInt(), any(Handler.class));
-    }
-
-    @Test
-    public void testDisplayBrightnessFollowers_BothDpcsSupportNits() {
-        DisplayPowerControllerHolder followerDpc =
-                createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
-
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        followerDpc.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
-                ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
-        verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture());
-        BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
-
-        mHolder.dpc.addDisplayBrightnessFollower(followerDpc.dpc);
-
-        // Test different float scale values
-        float leadBrightness = 0.3f;
-        float followerBrightness = 0.4f;
-        float nits = 300;
-        when(mHolder.automaticBrightnessController.convertToNits(leadBrightness)).thenReturn(nits);
-        when(followerDpc.automaticBrightnessController.convertToFloatScale(nits))
-                .thenReturn(followerBrightness);
-        when(mHolder.brightnessSetting.getBrightness()).thenReturn(leadBrightness);
-        listener.onBrightnessChanged(leadBrightness);
-        advanceTime(1); // Send messages, run updatePowerState
-        verify(mHolder.animator).animateTo(eq(leadBrightness), anyFloat(), anyFloat());
-        verify(followerDpc.animator).animateTo(eq(followerBrightness), anyFloat(),
-                anyFloat());
-
-        clearInvocations(mHolder.animator, followerDpc.animator);
-
-        // Test the same float scale value
-        float brightness = 0.6f;
-        nits = 600;
-        when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
-        when(followerDpc.automaticBrightnessController.convertToFloatScale(nits))
-                .thenReturn(brightness);
-        when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
-        listener.onBrightnessChanged(brightness);
-        advanceTime(1); // Send messages, run updatePowerState
-        verify(mHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
-        verify(followerDpc.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
-    }
-
-    @Test
-    public void testDisplayBrightnessFollowers_FollowerDoesNotSupportNits() {
-        DisplayPowerControllerHolder followerDpc =
-                createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
-
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        followerDpc.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
-                ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
-        verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture());
-        BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
-
-        mHolder.dpc.addDisplayBrightnessFollower(followerDpc.dpc);
-
-        float brightness = 0.3f;
-        when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(300f);
-        when(followerDpc.automaticBrightnessController.convertToFloatScale(anyFloat()))
-                .thenReturn(PowerManager.BRIGHTNESS_INVALID_FLOAT);
-        when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
-        listener.onBrightnessChanged(brightness);
-        advanceTime(1); // Send messages, run updatePowerState
-        verify(mHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
-        verify(followerDpc.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
-    }
-
-    @Test
-    public void testDisplayBrightnessFollowers_LeadDpcDoesNotSupportNits() {
-        DisplayPowerControllerHolder followerDpc =
-                createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
-
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        followerDpc.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
-                ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
-        verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture());
-        BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
-
-        mHolder.dpc.addDisplayBrightnessFollower(followerDpc.dpc);
-
-        float brightness = 0.3f;
-        when(mHolder.automaticBrightnessController.convertToNits(anyFloat())).thenReturn(-1f);
-        when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
-        listener.onBrightnessChanged(brightness);
-        advanceTime(1); // Send messages, run updatePowerState
-        verify(mHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
-        verify(followerDpc.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
-    }
-
-    @Test
-    public void testDisplayBrightnessFollowers_NeitherDpcSupportsNits() {
-        DisplayPowerControllerHolder followerDpc =
-                createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
-
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        followerDpc.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
-                ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
-        verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture());
-        BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
-
-        mHolder.dpc.addDisplayBrightnessFollower(followerDpc.dpc);
-
-        float brightness = 0.3f;
-        when(mHolder.automaticBrightnessController.convertToNits(anyFloat())).thenReturn(-1f);
-        when(followerDpc.automaticBrightnessController.convertToFloatScale(anyFloat()))
-                .thenReturn(PowerManager.BRIGHTNESS_INVALID_FLOAT);
-        when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
-        listener.onBrightnessChanged(brightness);
-        advanceTime(1); // Send messages, run updatePowerState
-        verify(mHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
-        verify(followerDpc.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
-    }
-
-    @Test
-    public void testDisplayBrightnessFollowers_AutomaticBrightness() {
-        Settings.System.putInt(mContext.getContentResolver(),
-                Settings.System.SCREEN_BRIGHTNESS_MODE,
-                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
-        final float brightness = 0.4f;
-        final float nits = 300;
-        final float ambientLux = 3000;
-        when(mHolder.automaticBrightnessController.getRawAutomaticScreenBrightness())
-                .thenReturn(brightness);
-        when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness())
-                .thenReturn(0.3f);
-        when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
-        when(mHolder.automaticBrightnessController.getAmbientLux()).thenReturn(ambientLux);
-        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
-        DisplayPowerController2 followerDpc = mock(DisplayPowerController2.class);
-
-        mHolder.dpc.addDisplayBrightnessFollower(followerDpc);
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        verify(followerDpc).setBrightnessToFollow(brightness, nits, ambientLux);
-    }
-
-    @Test
-    public void testDisplayBrightnessFollowersRemoval_RemoveSingleFollower() {
-        DisplayPowerControllerHolder followerDpc = createDisplayPowerController(FOLLOWER_DISPLAY_ID,
-                FOLLOWER_UNIQUE_ID);
-        DisplayPowerControllerHolder secondFollowerDpc = createDisplayPowerController(
-                SECOND_FOLLOWER_DISPLAY_ID, SECOND_FOLLOWER_UNIQUE_DISPLAY_ID);
-
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        followerDpc.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        secondFollowerDpc.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
-                ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
-        verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture());
-        BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
-
-        // Set the initial brightness on the DPC we're going to remove so we have a fixed value for
-        // it to return to.
-        listenerCaptor = ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
-        verify(followerDpc.brightnessSetting).registerListener(listenerCaptor.capture());
-        BrightnessSetting.BrightnessSettingListener followerListener = listenerCaptor.getValue();
-        final float initialFollowerBrightness = 0.3f;
-        when(followerDpc.brightnessSetting.getBrightness()).thenReturn(initialFollowerBrightness);
-        followerListener.onBrightnessChanged(initialFollowerBrightness);
-        advanceTime(1);
-        verify(followerDpc.animator).animateTo(eq(initialFollowerBrightness),
-                anyFloat(), anyFloat());
-
-
-        mHolder.dpc.addDisplayBrightnessFollower(followerDpc.dpc);
-        mHolder.dpc.addDisplayBrightnessFollower(secondFollowerDpc.dpc);
-        clearInvocations(followerDpc.animator);
-
-        // Validate both followers are correctly registered and receiving brightness updates
-        float brightness = 0.6f;
-        float nits = 600;
-        when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
-        when(followerDpc.automaticBrightnessController.convertToFloatScale(nits))
-                .thenReturn(brightness);
-        when(secondFollowerDpc.automaticBrightnessController.convertToFloatScale(nits))
-                .thenReturn(brightness);
-        when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
-        listener.onBrightnessChanged(brightness);
-        advanceTime(1); // Send messages, run updatePowerState
-        verify(mHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
-        verify(followerDpc.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
-        verify(secondFollowerDpc.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
-
-        clearInvocations(mHolder.animator, followerDpc.animator, secondFollowerDpc.animator);
-
-        // Remove the first follower and validate it goes back to its original brightness.
-        mHolder.dpc.removeDisplayBrightnessFollower(followerDpc.dpc);
-        advanceTime(1);
-        verify(followerDpc.animator).animateTo(eq(initialFollowerBrightness),
-                anyFloat(), anyFloat());
-        clearInvocations(followerDpc.animator);
-
-        // Change the brightness of the lead display and validate only the second follower responds
-        brightness = 0.7f;
-        nits = 700;
-        when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
-        when(followerDpc.automaticBrightnessController.convertToFloatScale(nits))
-                .thenReturn(brightness);
-        when(secondFollowerDpc.automaticBrightnessController.convertToFloatScale(nits))
-                .thenReturn(brightness);
-        when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
-        listener.onBrightnessChanged(brightness);
-        advanceTime(1); // Send messages, run updatePowerState
-        verify(mHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
-        verify(followerDpc.animator, never()).animateTo(anyFloat(), anyFloat(), anyFloat());
-        verify(secondFollowerDpc.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
-    }
-
-    @Test
-    public void testDisplayBrightnessFollowersRemoval_RemoveAllFollowers() {
-        DisplayPowerControllerHolder followerHolder =
-                createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
-        DisplayPowerControllerHolder secondFollowerHolder =
-                createDisplayPowerController(SECOND_FOLLOWER_DISPLAY_ID,
-                        SECOND_FOLLOWER_UNIQUE_DISPLAY_ID);
-
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        followerHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        secondFollowerHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
-                ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
-        verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture());
-        BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
-
-        // Set the initial brightness on the DPCs we're going to remove so we have a fixed value for
-        // it to return to.
-        listenerCaptor = ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
-        verify(followerHolder.brightnessSetting).registerListener(listenerCaptor.capture());
-        BrightnessSetting.BrightnessSettingListener followerListener = listenerCaptor.getValue();
-        listenerCaptor = ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
-        verify(secondFollowerHolder.brightnessSetting).registerListener(listenerCaptor.capture());
-        BrightnessSetting.BrightnessSettingListener secondFollowerListener =
-                listenerCaptor.getValue();
-        final float initialFollowerBrightness = 0.3f;
-        when(followerHolder.brightnessSetting.getBrightness()).thenReturn(
-                initialFollowerBrightness);
-        when(secondFollowerHolder.brightnessSetting.getBrightness()).thenReturn(
-                initialFollowerBrightness);
-        followerListener.onBrightnessChanged(initialFollowerBrightness);
-        secondFollowerListener.onBrightnessChanged(initialFollowerBrightness);
-        advanceTime(1);
-        verify(followerHolder.animator).animateTo(eq(initialFollowerBrightness),
-                anyFloat(), anyFloat());
-        verify(secondFollowerHolder.animator).animateTo(eq(initialFollowerBrightness),
-                anyFloat(), anyFloat());
-
-        mHolder.dpc.addDisplayBrightnessFollower(followerHolder.dpc);
-        mHolder.dpc.addDisplayBrightnessFollower(secondFollowerHolder.dpc);
-        clearInvocations(followerHolder.animator, secondFollowerHolder.animator);
-
-        // Validate both followers are correctly registered and receiving brightness updates
-        float brightness = 0.6f;
-        float nits = 600;
-        when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
-        when(followerHolder.automaticBrightnessController.convertToFloatScale(nits))
-                .thenReturn(brightness);
-        when(secondFollowerHolder.automaticBrightnessController.convertToFloatScale(nits))
-                .thenReturn(brightness);
-        when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
-        listener.onBrightnessChanged(brightness);
-        advanceTime(1); // Send messages, run updatePowerState
-        verify(mHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
-        verify(followerHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
-        verify(secondFollowerHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
-
-        clearInvocations(mHolder.animator, followerHolder.animator, secondFollowerHolder.animator);
-
-        // Stop the lead DPC and validate that the followers go back to their original brightness.
-        mHolder.dpc.stop();
-        advanceTime(1);
-        verify(followerHolder.animator).animateTo(eq(initialFollowerBrightness),
-                anyFloat(), anyFloat());
-        verify(secondFollowerHolder.animator).animateTo(eq(initialFollowerBrightness),
-                anyFloat(), anyFloat());
-        clearInvocations(followerHolder.animator, secondFollowerHolder.animator);
-    }
-
-    @Test
-    public void testDoesNotSetScreenStateForNonDefaultDisplayUntilBootCompleted() {
-        // We should still set screen state for the default display
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-        verify(mHolder.displayPowerState, times(2)).setScreenState(anyInt());
-
-        mHolder = createDisplayPowerController(42, UNIQUE_ID);
-
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-        verify(mHolder.displayPowerState, never()).setScreenState(anyInt());
-
-        mHolder.dpc.onBootCompleted();
-        advanceTime(1); // Run updatePowerState
-        verify(mHolder.displayPowerState).setScreenState(anyInt());
-    }
-
-    @Test
-    public void testSetScreenOffBrightnessSensorEnabled_DisplayIsOff() {
-        Settings.System.putInt(mContext.getContentResolver(),
-                Settings.System.SCREEN_BRIGHTNESS_MODE,
-                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
-
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        dpr.policy = DisplayPowerRequest.POLICY_OFF;
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        verify(mHolder.screenOffBrightnessSensorController, atLeastOnce())
-                .setLightSensorEnabled(true);
-
-        // The display turns on and we use the brightness value recommended by
-        // ScreenOffBrightnessSensorController
-        clearInvocations(mHolder.screenOffBrightnessSensorController);
-        float brightness = 0.14f;
-        when(mHolder.screenOffBrightnessSensorController.getAutomaticScreenBrightness())
-                .thenReturn(brightness);
-        dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
-        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
-        when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness(
-                any(BrightnessEvent.class))).thenReturn(PowerManager.BRIGHTNESS_INVALID_FLOAT);
-
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        verify(mHolder.screenOffBrightnessSensorController, atLeastOnce())
-                .getAutomaticScreenBrightness();
-        verify(mHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
-    }
-
-    @Test
-    public void testSetScreenOffBrightnessSensorEnabled_DisplayIsInDoze() {
-        mContext.getOrCreateTestableResources().addOverride(
-                com.android.internal.R.bool.config_allowAutoBrightnessWhileDozing, false);
-        mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
-
-        Settings.System.putInt(mContext.getContentResolver(),
-                Settings.System.SCREEN_BRIGHTNESS_MODE,
-                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
-
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        dpr.policy = DisplayPowerRequest.POLICY_DOZE;
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        verify(mHolder.screenOffBrightnessSensorController, atLeastOnce())
-                .setLightSensorEnabled(true);
-
-        // The display turns on and we use the brightness value recommended by
-        // ScreenOffBrightnessSensorController
-        clearInvocations(mHolder.screenOffBrightnessSensorController);
-        float brightness = 0.14f;
-        when(mHolder.screenOffBrightnessSensorController.getAutomaticScreenBrightness())
-                .thenReturn(brightness);
-        dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
-        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
-        when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness(
-                any(BrightnessEvent.class))).thenReturn(PowerManager.BRIGHTNESS_INVALID_FLOAT);
-
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        verify(mHolder.screenOffBrightnessSensorController, atLeastOnce())
-                .getAutomaticScreenBrightness();
-        verify(mHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
-    }
-
-    @Test
-    public void testSetScreenOffBrightnessSensorDisabled_AutoBrightnessIsDisabled() {
-        // Tests are set up with manual brightness by default, so no need to set it here.
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        dpr.policy = DisplayPowerRequest.POLICY_OFF;
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        verify(mHolder.screenOffBrightnessSensorController, atLeastOnce())
-                .setLightSensorEnabled(false);
-    }
-
-    @Test
-    public void testSetScreenOffBrightnessSensorDisabled_DisplayIsDisabled() {
-        Settings.System.putInt(mContext.getContentResolver(),
-                Settings.System.SCREEN_BRIGHTNESS_MODE,
-                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
-        mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID, /* isEnabled= */ false);
-
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        verify(mHolder.screenOffBrightnessSensorController, atLeastOnce())
-                .setLightSensorEnabled(false);
-    }
-
-    @Test
-    public void testSetScreenOffBrightnessSensorDisabled_DisplayIsOn() {
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
-
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        verify(mHolder.screenOffBrightnessSensorController, atLeastOnce())
-                .setLightSensorEnabled(false);
-    }
-
-    @Test
-    public void testSetScreenOffBrightnessSensorDisabled_DisplayIsAFollower() {
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        dpr.policy = DisplayPowerRequest.POLICY_OFF;
-
-        mHolder.dpc.onDisplayChanged(mHolder.hbmMetadata, /* leadDisplayId= */ 42);
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        verify(mHolder.screenOffBrightnessSensorController, atLeastOnce())
-                .setLightSensorEnabled(false);
-    }
-
-    @Test
-    public void testStopScreenOffBrightnessSensorControllerWhenDisplayDeviceChanges() {
-        // New display device
-        setUpDisplay(DISPLAY_ID, "new_unique_id", mHolder.display, mock(DisplayDevice.class),
-                mock(DisplayDeviceConfig.class), /* isEnabled= */ true);
-
-        mHolder.dpc.onDisplayChanged(mHolder.hbmMetadata, Layout.NO_LEAD_DISPLAY);
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        verify(mHolder.screenOffBrightnessSensorController).stop();
-    }
-
-    @Test
-    public void testBrightnessNitsPersistWhenDisplayDeviceChanges() {
-        float brightness = 0.3f;
-        float nits = 500;
-        mContext.getOrCreateTestableResources().addOverride(
-                com.android.internal.R.bool.config_persistBrightnessNitsForDefaultDisplay,
-                true);
-        mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
-        when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
-
-        mHolder.dpc.setBrightness(brightness);
-        verify(mHolder.brightnessSetting).setBrightnessNitsForDefaultDisplay(nits);
-
-        float newBrightness = 0.4f;
-        when(mHolder.brightnessSetting.getBrightnessNitsForDefaultDisplay()).thenReturn(nits);
-        when(mHolder.automaticBrightnessController.convertToFloatScale(nits))
-                .thenReturn(newBrightness);
-        // New display device
-        setUpDisplay(DISPLAY_ID, "new_unique_id", mHolder.display, mock(DisplayDevice.class),
-                mock(DisplayDeviceConfig.class), /* isEnabled= */ true);
-        mHolder.dpc.onDisplayChanged(mHolder.hbmMetadata, Layout.NO_LEAD_DISPLAY);
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-        // One triggered by handleBrightnessModeChange, another triggered by onDisplayChanged
-        verify(mHolder.animator, times(2)).animateTo(eq(newBrightness), anyFloat(), anyFloat());
-    }
-
-    @Test
-    public void testShortTermModelPersistsWhenDisplayDeviceChanges() {
-        float lux = 2000;
-        float brightness = 0.4f;
-        float nits = 500;
-        when(mHolder.brightnessMappingStrategy.getUserLux()).thenReturn(lux);
-        when(mHolder.brightnessMappingStrategy.getUserBrightness()).thenReturn(brightness);
-        when(mHolder.brightnessMappingStrategy.convertToNits(brightness)).thenReturn(nits);
-        when(mHolder.brightnessMappingStrategy.convertToFloatScale(nits)).thenReturn(brightness);
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1);
-        clearInvocations(mHolder.injector);
-
-        // New display device
-        setUpDisplay(DISPLAY_ID, "new_unique_id", mHolder.display, mock(DisplayDevice.class),
-                mock(DisplayDeviceConfig.class), /* isEnabled= */ true);
-        mHolder.dpc.onDisplayChanged(mHolder.hbmMetadata, Layout.NO_LEAD_DISPLAY);
-        advanceTime(1);
-
-        verify(mHolder.injector).getAutomaticBrightnessController(
-                any(AutomaticBrightnessController.Callbacks.class),
-                any(Looper.class),
-                eq(mSensorManagerMock),
-                any(),
-                eq(mHolder.brightnessMappingStrategy),
-                anyInt(),
-                anyFloat(),
-                anyFloat(),
-                anyFloat(),
-                anyInt(),
-                anyInt(),
-                anyLong(),
-                anyLong(),
-                anyBoolean(),
-                any(HysteresisLevels.class),
-                any(HysteresisLevels.class),
-                any(HysteresisLevels.class),
-                any(HysteresisLevels.class),
-                eq(mContext),
-                any(HighBrightnessModeController.class),
-                any(BrightnessThrottler.class),
-                isNull(),
-                anyInt(),
-                anyInt(),
-                eq(lux),
-                eq(brightness)
-        );
-    }
-
-    @Test
-    public void testUpdateBrightnessThrottlingDataId() {
-        mHolder.display.getDisplayInfoLocked().thermalBrightnessThrottlingDataId =
-                "throttling-data-id";
-        clearInvocations(mHolder.display.getPrimaryDisplayDeviceLocked().getDisplayDeviceConfig());
-
-        mHolder.dpc.onDisplayChanged(mHolder.hbmMetadata, Layout.NO_LEAD_DISPLAY);
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        verify(mHolder.display.getPrimaryDisplayDeviceLocked().getDisplayDeviceConfig())
-                .getThermalBrightnessThrottlingDataMapByThrottlingId();
-    }
-
-    /**
-     * Creates a mock and registers it to {@link LocalServices}.
-     */
-    private static <T> void addLocalServiceMock(Class<T> clazz, T mock) {
-        LocalServices.removeServiceForTest(clazz);
-        LocalServices.addService(clazz, mock);
-    }
-
-    private void advanceTime(long timeMs) {
-        mClock.fastForward(timeMs);
-        mTestLooper.dispatchAll();
-    }
-
-    private void setUpSensors() throws Exception {
-        mProxSensor = TestUtils.createSensor(Sensor.TYPE_PROXIMITY, Sensor.STRING_TYPE_PROXIMITY,
-                PROX_SENSOR_MAX_RANGE);
-        Sensor screenOffBrightnessSensor = TestUtils.createSensor(
-                Sensor.TYPE_LIGHT, Sensor.STRING_TYPE_LIGHT);
-        when(mSensorManagerMock.getSensorList(eq(Sensor.TYPE_ALL)))
-                .thenReturn(List.of(mProxSensor, screenOffBrightnessSensor));
-    }
-
-    private SensorEventListener getSensorEventListener(Sensor sensor) {
-        verify(mSensorManagerMock).registerListener(mSensorEventListenerCaptor.capture(),
-                eq(sensor), eq(SensorManager.SENSOR_DELAY_NORMAL), isA(Handler.class));
-        return mSensorEventListenerCaptor.getValue();
-    }
-
-    private void setUpDisplay(int displayId, String uniqueId, LogicalDisplay logicalDisplayMock,
-            DisplayDevice displayDeviceMock, DisplayDeviceConfig displayDeviceConfigMock,
-            boolean isEnabled) {
-        DisplayInfo info = new DisplayInfo();
-        DisplayDeviceInfo deviceInfo = new DisplayDeviceInfo();
-        deviceInfo.uniqueId = uniqueId;
-
-        when(logicalDisplayMock.getDisplayIdLocked()).thenReturn(displayId);
-        when(logicalDisplayMock.getPrimaryDisplayDeviceLocked()).thenReturn(displayDeviceMock);
-        when(logicalDisplayMock.getDisplayInfoLocked()).thenReturn(info);
-        when(logicalDisplayMock.isEnabledLocked()).thenReturn(isEnabled);
-        when(logicalDisplayMock.isInTransitionLocked()).thenReturn(false);
-        when(displayDeviceMock.getDisplayDeviceInfoLocked()).thenReturn(deviceInfo);
-        when(displayDeviceMock.getUniqueId()).thenReturn(uniqueId);
-        when(displayDeviceMock.getDisplayDeviceConfig()).thenReturn(displayDeviceConfigMock);
-        when(displayDeviceConfigMock.getProximitySensor()).thenReturn(
-                new DisplayDeviceConfig.SensorData() {
-                    {
-                        type = Sensor.STRING_TYPE_PROXIMITY;
-                        name = null;
-                    }
-                });
-        when(displayDeviceConfigMock.getNits()).thenReturn(new float[]{2, 500});
-        when(displayDeviceConfigMock.isAutoBrightnessAvailable()).thenReturn(true);
-        when(displayDeviceConfigMock.getAmbientLightSensor()).thenReturn(
-                new DisplayDeviceConfig.SensorData());
-        when(displayDeviceConfigMock.getScreenOffBrightnessSensor()).thenReturn(
-                new DisplayDeviceConfig.SensorData() {
-                    {
-                        type = Sensor.STRING_TYPE_LIGHT;
-                        name = null;
-                    }
-                });
-        when(displayDeviceConfigMock.getScreenOffBrightnessSensorValueToLux())
-                .thenReturn(new int[0]);
-    }
-
-    private DisplayPowerControllerHolder createDisplayPowerController(int displayId,
-            String uniqueId) {
-        return createDisplayPowerController(displayId, uniqueId, /* isEnabled= */ true);
-    }
-
-    private DisplayPowerControllerHolder createDisplayPowerController(int displayId,
-            String uniqueId, boolean isEnabled) {
-        final DisplayPowerState displayPowerState = mock(DisplayPowerState.class);
-        final DualRampAnimator<DisplayPowerState> animator = mock(DualRampAnimator.class);
-        final AutomaticBrightnessController automaticBrightnessController =
-                mock(AutomaticBrightnessController.class);
-        final WakelockController wakelockController = mock(WakelockController.class);
-        final BrightnessMappingStrategy brightnessMappingStrategy =
-                mock(BrightnessMappingStrategy.class);
-        final HysteresisLevels hysteresisLevels = mock(HysteresisLevels.class);
-        final ScreenOffBrightnessSensorController screenOffBrightnessSensorController =
-                mock(ScreenOffBrightnessSensorController.class);
-
-        TestInjector injector = spy(new TestInjector(displayPowerState, animator,
-                automaticBrightnessController, wakelockController, brightnessMappingStrategy,
-                hysteresisLevels, screenOffBrightnessSensorController));
-
-        final LogicalDisplay display = mock(LogicalDisplay.class);
-        final DisplayDevice device = mock(DisplayDevice.class);
-        final HighBrightnessModeMetadata hbmMetadata = mock(HighBrightnessModeMetadata.class);
-        final BrightnessSetting brightnessSetting = mock(BrightnessSetting.class);
-        final DisplayDeviceConfig config = mock(DisplayDeviceConfig.class);
-
-        setUpDisplay(displayId, uniqueId, display, device, config, isEnabled);
-
-        final DisplayPowerController2 dpc = new DisplayPowerController2(
-                mContext, injector, mDisplayPowerCallbacksMock, mHandler,
-                mSensorManagerMock, mDisplayBlankerMock, display,
-                mBrightnessTrackerMock, brightnessSetting, () -> {},
-                hbmMetadata, /* bootCompleted= */ false);
-
-        return new DisplayPowerControllerHolder(dpc, display, displayPowerState, brightnessSetting,
-                animator, automaticBrightnessController, wakelockController,
-                screenOffBrightnessSensorController, hbmMetadata, brightnessMappingStrategy,
-                injector);
-    }
-
-    /**
-     * A class for holding a DisplayPowerController under test and all the mocks specifically
-     * related to it.
-     */
-    private static class DisplayPowerControllerHolder {
-        public final DisplayPowerController2 dpc;
-        public final LogicalDisplay display;
-        public final DisplayPowerState displayPowerState;
-        public final BrightnessSetting brightnessSetting;
-        public final DualRampAnimator<DisplayPowerState> animator;
-        public final AutomaticBrightnessController automaticBrightnessController;
-        public final WakelockController wakelockController;
-        public final ScreenOffBrightnessSensorController screenOffBrightnessSensorController;
-        public final HighBrightnessModeMetadata hbmMetadata;
-        public final BrightnessMappingStrategy brightnessMappingStrategy;
-        public final DisplayPowerController2.Injector injector;
-
-        DisplayPowerControllerHolder(DisplayPowerController2 dpc, LogicalDisplay display,
-                DisplayPowerState displayPowerState, BrightnessSetting brightnessSetting,
-                DualRampAnimator<DisplayPowerState> animator,
-                AutomaticBrightnessController automaticBrightnessController,
-                WakelockController wakelockController,
-                ScreenOffBrightnessSensorController screenOffBrightnessSensorController,
-                HighBrightnessModeMetadata hbmMetadata,
-                BrightnessMappingStrategy brightnessMappingStrategy,
-                DisplayPowerController2.Injector injector) {
-            this.dpc = dpc;
-            this.display = display;
-            this.displayPowerState = displayPowerState;
-            this.brightnessSetting = brightnessSetting;
-            this.animator = animator;
-            this.automaticBrightnessController = automaticBrightnessController;
-            this.wakelockController = wakelockController;
-            this.screenOffBrightnessSensorController = screenOffBrightnessSensorController;
-            this.hbmMetadata = hbmMetadata;
-            this.brightnessMappingStrategy = brightnessMappingStrategy;
-            this.injector = injector;
-        }
-    }
-
-    private class TestInjector extends DisplayPowerController2.Injector {
-        private final DisplayPowerState mDisplayPowerState;
-        private final DualRampAnimator<DisplayPowerState> mAnimator;
-        private final AutomaticBrightnessController mAutomaticBrightnessController;
-        private final WakelockController mWakelockController;
-        private final BrightnessMappingStrategy mBrightnessMappingStrategy;
-        private final HysteresisLevels mHysteresisLevels;
-        private final ScreenOffBrightnessSensorController mScreenOffBrightnessSensorController;
-
-        TestInjector(DisplayPowerState dps, DualRampAnimator<DisplayPowerState> animator,
-                AutomaticBrightnessController automaticBrightnessController,
-                WakelockController wakelockController,
-                BrightnessMappingStrategy brightnessMappingStrategy,
-                HysteresisLevels hysteresisLevels,
-                ScreenOffBrightnessSensorController screenOffBrightnessSensorController) {
-            mDisplayPowerState = dps;
-            mAnimator = animator;
-            mAutomaticBrightnessController = automaticBrightnessController;
-            mWakelockController = wakelockController;
-            mBrightnessMappingStrategy = brightnessMappingStrategy;
-            mHysteresisLevels = hysteresisLevels;
-            mScreenOffBrightnessSensorController = screenOffBrightnessSensorController;
-        }
-
-        @Override
-        DisplayPowerController2.Clock getClock() {
-            return mClock::now;
-        }
-
-        @Override
-        DisplayPowerState getDisplayPowerState(DisplayBlanker blanker, ColorFade colorFade,
-                int displayId, int displayState) {
-            return mDisplayPowerState;
-        }
-
-        @Override
-        DualRampAnimator<DisplayPowerState> getDualRampAnimator(DisplayPowerState dps,
-                FloatProperty<DisplayPowerState> firstProperty,
-                FloatProperty<DisplayPowerState> secondProperty) {
-            return mAnimator;
-        }
-
-        @Override
-        WakelockController getWakelockController(int displayId,
-                DisplayPowerCallbacks displayPowerCallbacks) {
-            return mWakelockController;
-        }
-
-        @Override
-        DisplayPowerProximityStateController getDisplayPowerProximityStateController(
-                WakelockController wakelockController, DisplayDeviceConfig displayDeviceConfig,
-                Looper looper, Runnable nudgeUpdatePowerState, int displayId,
-                SensorManager sensorManager) {
-            return new DisplayPowerProximityStateController(wakelockController,
-                    displayDeviceConfig, looper, nudgeUpdatePowerState, displayId,
-                    sensorManager,
-                    new DisplayPowerProximityStateController.Injector() {
-                        @Override
-                        DisplayPowerProximityStateController.Clock createClock() {
-                            return mClock::now;
-                        }
-                    });
-        }
-
-        @Override
-        AutomaticBrightnessController getAutomaticBrightnessController(
-                AutomaticBrightnessController.Callbacks callbacks, Looper looper,
-                SensorManager sensorManager, Sensor lightSensor,
-                BrightnessMappingStrategy interactiveModeBrightnessMapper,
-                int lightSensorWarmUpTime, float brightnessMin, float brightnessMax,
-                float dozeScaleFactor, int lightSensorRate, int initialLightSensorRate,
-                long brighteningLightDebounceConfig, long darkeningLightDebounceConfig,
-                boolean resetAmbientLuxAfterWarmUpConfig,
-                HysteresisLevels ambientBrightnessThresholds,
-                HysteresisLevels screenBrightnessThresholds,
-                HysteresisLevels ambientBrightnessThresholdsIdle,
-                HysteresisLevels screenBrightnessThresholdsIdle, Context context,
-                HighBrightnessModeController hbmController,
-                BrightnessThrottler brightnessThrottler,
-                BrightnessMappingStrategy idleModeBrightnessMapper,
-                int ambientLightHorizonShort, int ambientLightHorizonLong, float userLux,
-                float userBrightness) {
-            return mAutomaticBrightnessController;
-        }
-
-        @Override
-        BrightnessMappingStrategy getInteractiveModeBrightnessMapper(Resources resources,
-                DisplayDeviceConfig displayDeviceConfig,
-                DisplayWhiteBalanceController displayWhiteBalanceController) {
-            return mBrightnessMappingStrategy;
-        }
-
-        @Override
-        HysteresisLevels getHysteresisLevels(float[] brighteningThresholdsPercentages,
-                float[] darkeningThresholdsPercentages, float[] brighteningThresholdLevels,
-                float[] darkeningThresholdLevels, float minDarkeningThreshold,
-                float minBrighteningThreshold) {
-            return mHysteresisLevels;
-        }
-
-        @Override
-        HysteresisLevels getHysteresisLevels(float[] brighteningThresholdsPercentages,
-                float[] darkeningThresholdsPercentages, float[] brighteningThresholdLevels,
-                float[] darkeningThresholdLevels, float minDarkeningThreshold,
-                float minBrighteningThreshold, boolean potentialOldBrightnessRange) {
-            return mHysteresisLevels;
-        }
-
-        @Override
-        ScreenOffBrightnessSensorController getScreenOffBrightnessSensorController(
-                SensorManager sensorManager, Sensor lightSensor, Handler handler,
-                ScreenOffBrightnessSensorController.Clock clock, int[] sensorValueToLux,
-                BrightnessMappingStrategy brightnessMapper) {
-            return mScreenOffBrightnessSensorController;
-        }
-    }
-}
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java
deleted file mode 100644
index 5c0810f..0000000
--- a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ /dev/null
@@ -1,1080 +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.server.display;
-
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
-
-import static org.junit.Assert.assertNotNull;
-import static org.mockito.ArgumentMatchers.any;
-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.isA;
-import static org.mockito.ArgumentMatchers.isNull;
-import static org.mockito.Mockito.atLeastOnce;
-import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.hardware.Sensor;
-import android.hardware.SensorEventListener;
-import android.hardware.SensorManager;
-import android.hardware.display.DisplayManagerInternal.DisplayPowerCallbacks;
-import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.PowerManager;
-import android.os.SystemProperties;
-import android.os.test.TestLooper;
-import android.provider.Settings;
-import android.testing.TestableContext;
-import android.util.FloatProperty;
-import android.view.Display;
-import android.view.DisplayInfo;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-import androidx.test.platform.app.InstrumentationRegistry;
-
-import com.android.server.ExtendedMockitoRule;
-import com.android.server.LocalServices;
-import com.android.server.am.BatteryStatsService;
-import com.android.server.display.RampAnimator.DualRampAnimator;
-import com.android.server.display.brightness.BrightnessEvent;
-import com.android.server.display.color.ColorDisplayService;
-import com.android.server.display.layout.Layout;
-import com.android.server.display.whitebalance.DisplayWhiteBalanceController;
-import com.android.server.policy.WindowManagerPolicy;
-import com.android.server.testutils.OffsettableClock;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
-import org.mockito.Mock;
-import org.mockito.quality.Strictness;
-import org.mockito.stubbing.Answer;
-
-import java.util.List;
-
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public final class DisplayPowerControllerTest {
-    private static final int DISPLAY_ID = Display.DEFAULT_DISPLAY;
-    private static final String UNIQUE_ID = "unique_id_test123";
-    private static final int FOLLOWER_DISPLAY_ID = DISPLAY_ID + 1;
-    private static final String FOLLOWER_UNIQUE_ID = "unique_id_456";
-    private static final int SECOND_FOLLOWER_DISPLAY_ID = FOLLOWER_DISPLAY_ID + 1;
-    private static final String SECOND_FOLLOWER_UNIQUE_DISPLAY_ID = "unique_id_789";
-    private static final float PROX_SENSOR_MAX_RANGE = 5;
-
-    private OffsettableClock mClock;
-    private TestLooper mTestLooper;
-    private Handler mHandler;
-    private DisplayPowerControllerHolder mHolder;
-    private Sensor mProxSensor;
-
-    @Mock
-    private DisplayPowerCallbacks mDisplayPowerCallbacksMock;
-    @Mock
-    private SensorManager mSensorManagerMock;
-    @Mock
-    private DisplayBlanker mDisplayBlankerMock;
-    @Mock
-    private BrightnessTracker mBrightnessTrackerMock;
-    @Mock
-    private WindowManagerPolicy mWindowManagerPolicyMock;
-    @Mock
-    private PowerManager mPowerManagerMock;
-    @Mock
-    private ColorDisplayService.ColorDisplayServiceInternal mCdsiMock;
-
-    @Captor
-    private ArgumentCaptor<SensorEventListener> mSensorEventListenerCaptor;
-
-    @Rule
-    public final TestableContext mContext = new TestableContext(
-            InstrumentationRegistry.getInstrumentation().getContext());
-
-    @Rule
-    public final ExtendedMockitoRule mExtendedMockitoRule =
-            new ExtendedMockitoRule.Builder(this)
-                    .setStrictness(Strictness.LENIENT)
-                    .spyStatic(SystemProperties.class)
-                    .spyStatic(BatteryStatsService.class)
-                    .build();
-
-    @Before
-    public void setUp() throws Exception {
-        mClock = new OffsettableClock.Stopped();
-        mTestLooper = new TestLooper(mClock::now);
-        mHandler = new Handler(mTestLooper.getLooper());
-
-        // Put the system into manual brightness by default, just to minimize unexpected events and
-        // have a consistent starting state
-        Settings.System.putInt(mContext.getContentResolver(),
-                Settings.System.SCREEN_BRIGHTNESS_MODE,
-                Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL);
-
-
-        addLocalServiceMock(WindowManagerPolicy.class, mWindowManagerPolicyMock);
-        addLocalServiceMock(ColorDisplayService.ColorDisplayServiceInternal.class,
-                mCdsiMock);
-
-        mContext.addMockSystemService(PowerManager.class, mPowerManagerMock);
-
-        doAnswer((Answer<Void>) invocationOnMock -> null).when(() ->
-                SystemProperties.set(anyString(), any()));
-        doAnswer((Answer<Void>) invocationOnMock -> null).when(BatteryStatsService::getService);
-
-        setUpSensors();
-        mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
-    }
-
-    @After
-    public void tearDown() {
-        LocalServices.removeServiceForTest(WindowManagerPolicy.class);
-        LocalServices.removeServiceForTest(ColorDisplayService.ColorDisplayServiceInternal.class);
-    }
-
-    @Test
-    public void testReleaseProxSuspendBlockersOnExit() throws Exception {
-        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
-        // Send a display power request
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
-        dpr.useProximitySensor = true;
-        mHolder.dpc.requestPowerState(dpr, false /* waitForNegativeProximity */);
-
-        // Run updatePowerState to start listener for the prox sensor
-        advanceTime(1);
-
-        SensorEventListener listener = getSensorEventListener(mProxSensor);
-        assertNotNull(listener);
-
-        listener.onSensorChanged(TestUtils.createSensorEvent(mProxSensor, /* value= */ 5));
-        advanceTime(1);
-
-        // two times, one for unfinished business and one for proximity
-        verify(mDisplayPowerCallbacksMock).acquireSuspendBlocker(
-                mHolder.dpc.getSuspendBlockerUnfinishedBusinessId(DISPLAY_ID));
-        verify(mDisplayPowerCallbacksMock).acquireSuspendBlocker(
-                mHolder.dpc.getSuspendBlockerProxDebounceId(DISPLAY_ID));
-
-        mHolder.dpc.stop();
-        advanceTime(1);
-
-        // two times, one for unfinished business and one for proximity
-        verify(mDisplayPowerCallbacksMock).releaseSuspendBlocker(
-                mHolder.dpc.getSuspendBlockerUnfinishedBusinessId(DISPLAY_ID));
-        verify(mDisplayPowerCallbacksMock).releaseSuspendBlocker(
-                mHolder.dpc.getSuspendBlockerProxDebounceId(DISPLAY_ID));
-    }
-
-    @Test
-    public void testScreenOffBecauseOfProximity() throws Exception {
-        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
-        // Send a display power request
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
-        dpr.useProximitySensor = true;
-        mHolder.dpc.requestPowerState(dpr, false /* waitForNegativeProximity */);
-
-        // Run updatePowerState to start listener for the prox sensor
-        advanceTime(1);
-
-        SensorEventListener listener = getSensorEventListener(mProxSensor);
-        assertNotNull(listener);
-
-        // Send a positive proximity event
-        listener.onSensorChanged(TestUtils.createSensorEvent(mProxSensor, /* value= */ 1));
-        advanceTime(1);
-
-        // The display should have been turned off
-        verify(mHolder.displayPowerState).setScreenState(Display.STATE_OFF);
-
-        clearInvocations(mHolder.displayPowerState);
-        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_OFF);
-        // Send a negative proximity event
-        listener.onSensorChanged(TestUtils.createSensorEvent(mProxSensor,
-                (int) PROX_SENSOR_MAX_RANGE + 1));
-        // Advance time by less than PROXIMITY_SENSOR_NEGATIVE_DEBOUNCE_DELAY
-        advanceTime(1);
-
-        // The prox sensor is debounced so the display should not have been turned back on yet
-        verify(mHolder.displayPowerState, never()).setScreenState(Display.STATE_ON);
-
-        // Advance time by more than PROXIMITY_SENSOR_NEGATIVE_DEBOUNCE_DELAY
-        advanceTime(1000);
-
-        // The display should have been turned back on
-        verify(mHolder.displayPowerState).setScreenState(Display.STATE_ON);
-    }
-
-    @Test
-    public void testScreenOffBecauseOfProximity_ProxSensorGone() throws Exception {
-        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
-        // Send a display power request
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
-        dpr.useProximitySensor = true;
-        mHolder.dpc.requestPowerState(dpr, false /* waitForNegativeProximity */);
-
-        // Run updatePowerState to start listener for the prox sensor
-        advanceTime(1);
-
-        SensorEventListener listener = getSensorEventListener(mProxSensor);
-        assertNotNull(listener);
-
-        // Send a positive proximity event
-        listener.onSensorChanged(TestUtils.createSensorEvent(mProxSensor, /* value= */ 1));
-        advanceTime(1);
-
-        // The display should have been turned off
-        verify(mHolder.displayPowerState).setScreenState(Display.STATE_OFF);
-
-        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_OFF);
-        // The display device changes and we no longer have a prox sensor
-        reset(mSensorManagerMock);
-        setUpDisplay(DISPLAY_ID, "new_unique_id", mHolder.display, mock(DisplayDevice.class),
-                mock(DisplayDeviceConfig.class), /* isEnabled= */ true);
-        mHolder.dpc.onDisplayChanged(mHolder.hbmMetadata, Layout.NO_LEAD_DISPLAY);
-
-        advanceTime(1); // Run updatePowerState
-
-        // The display should have been turned back on and the listener should have been
-        // unregistered
-        verify(mHolder.displayPowerState).setScreenState(Display.STATE_ON);
-        verify(mSensorManagerMock).unregisterListener(listener);
-    }
-
-    @Test
-    public void testProximitySensorListenerNotRegisteredForNonDefaultDisplay() {
-        DisplayPowerControllerHolder followerDpc =
-                createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
-
-        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
-        // send a display power request
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
-        dpr.useProximitySensor = true;
-        followerDpc.dpc.requestPowerState(dpr, false /* waitForNegativeProximity */);
-
-        // Run updatePowerState
-        advanceTime(1);
-
-        verify(mSensorManagerMock, never()).registerListener(any(SensorEventListener.class),
-                eq(mProxSensor), anyInt(), any(Handler.class));
-    }
-
-    @Test
-    public void testDisplayBrightnessFollowers_BothDpcsSupportNits() {
-        DisplayPowerControllerHolder followerDpc =
-                createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
-
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        followerDpc.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
-                ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
-        verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture());
-        BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
-
-        mHolder.dpc.addDisplayBrightnessFollower(followerDpc.dpc);
-
-        // Test different float scale values
-        float leadBrightness = 0.3f;
-        float followerBrightness = 0.4f;
-        float nits = 300;
-        when(mHolder.automaticBrightnessController.convertToNits(leadBrightness)).thenReturn(nits);
-        when(followerDpc.automaticBrightnessController.convertToFloatScale(nits))
-                .thenReturn(followerBrightness);
-        when(mHolder.brightnessSetting.getBrightness()).thenReturn(leadBrightness);
-        listener.onBrightnessChanged(leadBrightness);
-        advanceTime(1); // Send messages, run updatePowerState
-        verify(mHolder.animator).animateTo(eq(leadBrightness), anyFloat(), anyFloat());
-        verify(followerDpc.animator).animateTo(eq(followerBrightness), anyFloat(),
-                anyFloat());
-
-        clearInvocations(mHolder.animator, followerDpc.animator);
-
-        // Test the same float scale value
-        float brightness = 0.6f;
-        nits = 600;
-        when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
-        when(followerDpc.automaticBrightnessController.convertToFloatScale(nits))
-                .thenReturn(brightness);
-        when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
-        listener.onBrightnessChanged(brightness);
-        advanceTime(1); // Send messages, run updatePowerState
-        verify(mHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
-        verify(followerDpc.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
-    }
-
-    @Test
-    public void testDisplayBrightnessFollowers_FollowerDoesNotSupportNits() {
-        DisplayPowerControllerHolder followerDpc =
-                createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
-
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        followerDpc.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
-                ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
-        verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture());
-        BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
-
-        mHolder.dpc.addDisplayBrightnessFollower(followerDpc.dpc);
-
-        float brightness = 0.3f;
-        when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(300f);
-        when(followerDpc.automaticBrightnessController.convertToFloatScale(anyFloat()))
-                .thenReturn(PowerManager.BRIGHTNESS_INVALID_FLOAT);
-        when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
-        listener.onBrightnessChanged(brightness);
-        advanceTime(1); // Send messages, run updatePowerState
-        verify(mHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
-        verify(followerDpc.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
-    }
-
-    @Test
-    public void testDisplayBrightnessFollowers_LeadDpcDoesNotSupportNits() {
-        DisplayPowerControllerHolder followerDpc =
-                createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
-
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        followerDpc.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
-                ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
-        verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture());
-        BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
-
-        mHolder.dpc.addDisplayBrightnessFollower(followerDpc.dpc);
-
-        float brightness = 0.3f;
-        when(mHolder.automaticBrightnessController.convertToNits(anyFloat())).thenReturn(-1f);
-        when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
-        listener.onBrightnessChanged(brightness);
-        advanceTime(1); // Send messages, run updatePowerState
-        verify(mHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
-        verify(followerDpc.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
-    }
-
-    @Test
-    public void testDisplayBrightnessFollowers_NeitherDpcSupportsNits() {
-        DisplayPowerControllerHolder followerDpc =
-                createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
-
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        followerDpc.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
-                ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
-        verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture());
-        BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
-
-        mHolder.dpc.addDisplayBrightnessFollower(followerDpc.dpc);
-
-        float brightness = 0.3f;
-        when(mHolder.automaticBrightnessController.convertToNits(anyFloat())).thenReturn(-1f);
-        when(followerDpc.automaticBrightnessController.convertToFloatScale(anyFloat()))
-                .thenReturn(PowerManager.BRIGHTNESS_INVALID_FLOAT);
-        when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
-        listener.onBrightnessChanged(brightness);
-        advanceTime(1); // Send messages, run updatePowerState
-        verify(mHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
-        verify(followerDpc.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
-    }
-
-    @Test
-    public void testDisplayBrightnessFollowers_AutomaticBrightness() {
-        Settings.System.putInt(mContext.getContentResolver(),
-                Settings.System.SCREEN_BRIGHTNESS_MODE,
-                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
-        final float brightness = 0.4f;
-        final float nits = 300;
-        final float ambientLux = 3000;
-        when(mHolder.automaticBrightnessController.getRawAutomaticScreenBrightness())
-                .thenReturn(brightness);
-        when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness())
-                .thenReturn(0.3f);
-        when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
-        when(mHolder.automaticBrightnessController.getAmbientLux()).thenReturn(ambientLux);
-        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
-        DisplayPowerController followerDpc = mock(DisplayPowerController.class);
-
-        mHolder.dpc.addDisplayBrightnessFollower(followerDpc);
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        verify(followerDpc).setBrightnessToFollow(brightness, nits, ambientLux);
-    }
-
-    @Test
-    public void testDisplayBrightnessFollowersRemoval_RemoveSingleFollower() {
-        DisplayPowerControllerHolder followerHolder =
-                createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
-        DisplayPowerControllerHolder secondFollowerHolder =
-                createDisplayPowerController(SECOND_FOLLOWER_DISPLAY_ID,
-                        SECOND_FOLLOWER_UNIQUE_DISPLAY_ID);
-
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        followerHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        secondFollowerHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
-                ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
-        verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture());
-        BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
-
-        // Set the initial brightness on the DPC we're going to remove so we have a fixed value for
-        // it to return to.
-        listenerCaptor = ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
-        verify(followerHolder.brightnessSetting).registerListener(listenerCaptor.capture());
-        BrightnessSetting.BrightnessSettingListener followerListener = listenerCaptor.getValue();
-        final float initialFollowerBrightness = 0.3f;
-        when(followerHolder.brightnessSetting.getBrightness()).thenReturn(
-                initialFollowerBrightness);
-        followerListener.onBrightnessChanged(initialFollowerBrightness);
-        advanceTime(1);
-        verify(followerHolder.animator).animateTo(eq(initialFollowerBrightness),
-                anyFloat(), anyFloat());
-
-        mHolder.dpc.addDisplayBrightnessFollower(followerHolder.dpc);
-        mHolder.dpc.addDisplayBrightnessFollower(secondFollowerHolder.dpc);
-        clearInvocations(followerHolder.animator);
-
-        // Validate both followers are correctly registered and receiving brightness updates
-        float brightness = 0.6f;
-        float nits = 600;
-        when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
-        when(followerHolder.automaticBrightnessController.convertToFloatScale(nits))
-                .thenReturn(brightness);
-        when(secondFollowerHolder.automaticBrightnessController.convertToFloatScale(nits))
-                .thenReturn(brightness);
-        when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
-        listener.onBrightnessChanged(brightness);
-        advanceTime(1); // Send messages, run updatePowerState
-        verify(mHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
-        verify(followerHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
-        verify(secondFollowerHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
-
-        clearInvocations(mHolder.animator, followerHolder.animator, secondFollowerHolder.animator);
-
-        // Remove the first follower and validate it goes back to its original brightness.
-        mHolder.dpc.removeDisplayBrightnessFollower(followerHolder.dpc);
-        advanceTime(1);
-        verify(followerHolder.animator).animateTo(eq(initialFollowerBrightness),
-                anyFloat(), anyFloat());
-        clearInvocations(followerHolder.animator);
-
-        // Change the brightness of the lead display and validate only the second follower responds
-        brightness = 0.7f;
-        nits = 700;
-        when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
-        when(followerHolder.automaticBrightnessController.convertToFloatScale(nits))
-                .thenReturn(brightness);
-        when(secondFollowerHolder.automaticBrightnessController.convertToFloatScale(nits))
-                .thenReturn(brightness);
-        when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
-        listener.onBrightnessChanged(brightness);
-        advanceTime(1); // Send messages, run updatePowerState
-        verify(mHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
-        verify(followerHolder.animator, never()).animateTo(anyFloat(), anyFloat(), anyFloat());
-        verify(secondFollowerHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
-    }
-
-    @Test
-    public void testDisplayBrightnessFollowersRemoval_RemoveAllFollowers() {
-        DisplayPowerControllerHolder followerHolder =
-                createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
-        DisplayPowerControllerHolder secondFollowerHolder =
-                createDisplayPowerController(SECOND_FOLLOWER_DISPLAY_ID,
-                        SECOND_FOLLOWER_UNIQUE_DISPLAY_ID);
-
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        followerHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        secondFollowerHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
-                ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
-        verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture());
-        BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
-
-        // Set the initial brightness on the DPCs we're going to remove so we have a fixed value for
-        // it to return to.
-        listenerCaptor = ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
-        verify(followerHolder.brightnessSetting).registerListener(listenerCaptor.capture());
-        BrightnessSetting.BrightnessSettingListener followerListener = listenerCaptor.getValue();
-        listenerCaptor = ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
-        verify(secondFollowerHolder.brightnessSetting).registerListener(listenerCaptor.capture());
-        BrightnessSetting.BrightnessSettingListener secondFollowerListener =
-                listenerCaptor.getValue();
-        final float initialFollowerBrightness = 0.3f;
-        when(followerHolder.brightnessSetting.getBrightness()).thenReturn(
-                initialFollowerBrightness);
-        when(secondFollowerHolder.brightnessSetting.getBrightness()).thenReturn(
-                initialFollowerBrightness);
-        followerListener.onBrightnessChanged(initialFollowerBrightness);
-        secondFollowerListener.onBrightnessChanged(initialFollowerBrightness);
-        advanceTime(1);
-        verify(followerHolder.animator).animateTo(eq(initialFollowerBrightness),
-                anyFloat(), anyFloat());
-        verify(secondFollowerHolder.animator).animateTo(eq(initialFollowerBrightness),
-                anyFloat(), anyFloat());
-
-        mHolder.dpc.addDisplayBrightnessFollower(followerHolder.dpc);
-        mHolder.dpc.addDisplayBrightnessFollower(secondFollowerHolder.dpc);
-        clearInvocations(followerHolder.animator, secondFollowerHolder.animator);
-
-        // Validate both followers are correctly registered and receiving brightness updates
-        float brightness = 0.6f;
-        float nits = 600;
-        when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
-        when(followerHolder.automaticBrightnessController.convertToFloatScale(nits))
-                .thenReturn(brightness);
-        when(secondFollowerHolder.automaticBrightnessController.convertToFloatScale(nits))
-                .thenReturn(brightness);
-        when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
-        listener.onBrightnessChanged(brightness);
-        advanceTime(1); // Send messages, run updatePowerState
-        verify(mHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
-        verify(followerHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
-        verify(secondFollowerHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
-
-        clearInvocations(mHolder.animator, followerHolder.animator, secondFollowerHolder.animator);
-
-        // Stop the lead DPC and validate that the followers go back to their original brightness.
-        mHolder.dpc.stop();
-        advanceTime(1);
-        verify(followerHolder.animator).animateTo(eq(initialFollowerBrightness),
-                anyFloat(), anyFloat());
-        verify(secondFollowerHolder.animator).animateTo(eq(initialFollowerBrightness),
-                anyFloat(), anyFloat());
-        clearInvocations(followerHolder.animator, secondFollowerHolder.animator);
-    }
-
-    @Test
-    public void testDoesNotSetScreenStateForNonDefaultDisplayUntilBootCompleted() {
-        // We should still set screen state for the default display
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-        verify(mHolder.displayPowerState, times(2)).setScreenState(anyInt());
-
-        mHolder = createDisplayPowerController(42, UNIQUE_ID);
-
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-        verify(mHolder.displayPowerState, never()).setScreenState(anyInt());
-
-        mHolder.dpc.onBootCompleted();
-        advanceTime(1); // Run updatePowerState
-        verify(mHolder.displayPowerState).setScreenState(anyInt());
-    }
-
-    @Test
-    public void testSetScreenOffBrightnessSensorEnabled_DisplayIsOff() {
-        Settings.System.putInt(mContext.getContentResolver(),
-                Settings.System.SCREEN_BRIGHTNESS_MODE,
-                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
-
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        dpr.policy = DisplayPowerRequest.POLICY_OFF;
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        verify(mHolder.screenOffBrightnessSensorController, atLeastOnce())
-                .setLightSensorEnabled(true);
-
-        // The display turns on and we use the brightness value recommended by
-        // ScreenOffBrightnessSensorController
-        clearInvocations(mHolder.screenOffBrightnessSensorController);
-        float brightness = 0.14f;
-        when(mHolder.screenOffBrightnessSensorController.getAutomaticScreenBrightness())
-                .thenReturn(brightness);
-        dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
-        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
-        when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness(
-                any(BrightnessEvent.class))).thenReturn(PowerManager.BRIGHTNESS_INVALID_FLOAT);
-
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        verify(mHolder.screenOffBrightnessSensorController, atLeastOnce())
-                .getAutomaticScreenBrightness();
-        verify(mHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
-    }
-
-    @Test
-    public void testSetScreenOffBrightnessSensorEnabled_DisplayIsInDoze() {
-        Settings.System.putInt(mContext.getContentResolver(),
-                Settings.System.SCREEN_BRIGHTNESS_MODE,
-                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
-
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        dpr.policy = DisplayPowerRequest.POLICY_DOZE;
-        mContext.getOrCreateTestableResources().addOverride(
-                com.android.internal.R.bool.config_allowAutoBrightnessWhileDozing, true);
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        verify(mHolder.screenOffBrightnessSensorController, atLeastOnce())
-                .setLightSensorEnabled(true);
-
-        // The display turns on and we use the brightness value recommended by
-        // ScreenOffBrightnessSensorController
-        clearInvocations(mHolder.screenOffBrightnessSensorController);
-        float brightness = 0.14f;
-        when(mHolder.screenOffBrightnessSensorController.getAutomaticScreenBrightness())
-                .thenReturn(brightness);
-        dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
-        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
-        when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness(
-                any(BrightnessEvent.class))).thenReturn(PowerManager.BRIGHTNESS_INVALID_FLOAT);
-
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        verify(mHolder.screenOffBrightnessSensorController, atLeastOnce())
-                .getAutomaticScreenBrightness();
-        verify(mHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
-    }
-
-    @Test
-    public void testSetScreenOffBrightnessSensorDisabled_AutoBrightnessIsDisabled() {
-        // Tests are set up with manual brightness by default, so no need to set it here.
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        dpr.policy = DisplayPowerRequest.POLICY_OFF;
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        verify(mHolder.screenOffBrightnessSensorController, atLeastOnce())
-                .setLightSensorEnabled(false);
-    }
-
-    @Test
-    public void testSetScreenOffBrightnessSensorDisabled_DisplayIsDisabled() {
-        Settings.System.putInt(mContext.getContentResolver(),
-                Settings.System.SCREEN_BRIGHTNESS_MODE,
-                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
-
-        mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID, /* isEnabled= */ false);
-
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        verify(mHolder.screenOffBrightnessSensorController, atLeastOnce())
-                .setLightSensorEnabled(false);
-    }
-
-    @Test
-    public void testSetScreenOffBrightnessSensorDisabled_DisplayIsOn() {
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
-
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        verify(mHolder.screenOffBrightnessSensorController, atLeastOnce())
-                .setLightSensorEnabled(false);
-    }
-
-    @Test
-    public void testSetScreenOffBrightnessSensorDisabled_DisplayIsAFollower() {
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        dpr.policy = DisplayPowerRequest.POLICY_OFF;
-
-        mHolder.dpc.onDisplayChanged(mHolder.hbmMetadata, /* leadDisplayId= */ 42);
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        verify(mHolder.screenOffBrightnessSensorController, atLeastOnce())
-                .setLightSensorEnabled(false);
-    }
-
-    @Test
-    public void testStopScreenOffBrightnessSensorControllerWhenDisplayDeviceChanges() {
-        // New display device
-        setUpDisplay(DISPLAY_ID, "new_unique_id", mHolder.display, mock(DisplayDevice.class),
-                mock(DisplayDeviceConfig.class), /* isEnabled= */ true);
-
-        mHolder.dpc.onDisplayChanged(mHolder.hbmMetadata, Layout.NO_LEAD_DISPLAY);
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        verify(mHolder.screenOffBrightnessSensorController).stop();
-    }
-
-    @Test
-    public void testBrightnessNitsPersistWhenDisplayDeviceChanges() {
-        float brightness = 0.3f;
-        float nits = 500;
-        mContext.getOrCreateTestableResources().addOverride(
-                com.android.internal.R.bool.config_persistBrightnessNitsForDefaultDisplay,
-                true);
-
-        mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
-        when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
-
-        mHolder.dpc.setBrightness(brightness);
-        verify(mHolder.brightnessSetting).setBrightnessNitsForDefaultDisplay(nits);
-
-        float newBrightness = 0.4f;
-        when(mHolder.brightnessSetting.getBrightnessNitsForDefaultDisplay()).thenReturn(nits);
-        when(mHolder.automaticBrightnessController.convertToFloatScale(nits))
-                .thenReturn(newBrightness);
-        // New display device
-        setUpDisplay(DISPLAY_ID, "new_unique_id", mHolder.display, mock(DisplayDevice.class),
-                mock(DisplayDeviceConfig.class), /* isEnabled= */ true);
-        mHolder.dpc.onDisplayChanged(mHolder.hbmMetadata, Layout.NO_LEAD_DISPLAY);
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-        // One triggered by handleBrightnessModeChange, another triggered by onDisplayChanged
-        verify(mHolder.animator, times(2)).animateTo(eq(newBrightness), anyFloat(), anyFloat());
-    }
-
-    @Test
-    public void testShortTermModelPersistsWhenDisplayDeviceChanges() {
-        float lux = 2000;
-        float brightness = 0.4f;
-        float nits = 500;
-        when(mHolder.brightnessMappingStrategy.getUserLux()).thenReturn(lux);
-        when(mHolder.brightnessMappingStrategy.getUserBrightness()).thenReturn(brightness);
-        when(mHolder.brightnessMappingStrategy.convertToNits(brightness)).thenReturn(nits);
-        when(mHolder.brightnessMappingStrategy.convertToFloatScale(nits)).thenReturn(brightness);
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1);
-        clearInvocations(mHolder.injector);
-
-        // New display device
-        setUpDisplay(DISPLAY_ID, "new_unique_id", mHolder.display, mock(DisplayDevice.class),
-                mock(DisplayDeviceConfig.class), /* isEnabled= */ true);
-        mHolder.dpc.onDisplayChanged(mHolder.hbmMetadata, Layout.NO_LEAD_DISPLAY);
-        advanceTime(1);
-
-        verify(mHolder.injector).getAutomaticBrightnessController(
-                any(AutomaticBrightnessController.Callbacks.class),
-                any(Looper.class),
-                eq(mSensorManagerMock),
-                any(),
-                eq(mHolder.brightnessMappingStrategy),
-                anyInt(),
-                anyFloat(),
-                anyFloat(),
-                anyFloat(),
-                anyInt(),
-                anyInt(),
-                anyLong(),
-                anyLong(),
-                anyBoolean(),
-                any(HysteresisLevels.class),
-                any(HysteresisLevels.class),
-                any(HysteresisLevels.class),
-                any(HysteresisLevels.class),
-                eq(mContext),
-                any(HighBrightnessModeController.class),
-                any(BrightnessThrottler.class),
-                isNull(),
-                anyInt(),
-                anyInt(),
-                eq(lux),
-                eq(brightness)
-        );
-    }
-
-    @Test
-    public void testUpdateBrightnessThrottlingDataId() {
-        mHolder.display.getDisplayInfoLocked().thermalBrightnessThrottlingDataId =
-                "throttling-data-id";
-        clearInvocations(mHolder.display.getPrimaryDisplayDeviceLocked().getDisplayDeviceConfig());
-
-        mHolder.dpc.onDisplayChanged(mHolder.hbmMetadata, Layout.NO_LEAD_DISPLAY);
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        verify(mHolder.display.getPrimaryDisplayDeviceLocked().getDisplayDeviceConfig())
-                .getThermalBrightnessThrottlingDataMapByThrottlingId();
-    }
-
-    /**
-     * Creates a mock and registers it to {@link LocalServices}.
-     */
-    private static <T> void addLocalServiceMock(Class<T> clazz, T mock) {
-        LocalServices.removeServiceForTest(clazz);
-        LocalServices.addService(clazz, mock);
-    }
-
-    private void advanceTime(long timeMs) {
-        mClock.fastForward(timeMs);
-        mTestLooper.dispatchAll();
-    }
-
-    private void setUpSensors() throws Exception {
-        mProxSensor = TestUtils.createSensor(Sensor.TYPE_PROXIMITY, Sensor.STRING_TYPE_PROXIMITY,
-                PROX_SENSOR_MAX_RANGE);
-        Sensor screenOffBrightnessSensor = TestUtils.createSensor(
-                Sensor.TYPE_LIGHT, Sensor.STRING_TYPE_LIGHT);
-        when(mSensorManagerMock.getSensorList(eq(Sensor.TYPE_ALL)))
-                .thenReturn(List.of(mProxSensor, screenOffBrightnessSensor));
-    }
-
-    private SensorEventListener getSensorEventListener(Sensor sensor) {
-        verify(mSensorManagerMock).registerListener(mSensorEventListenerCaptor.capture(),
-                eq(sensor), eq(SensorManager.SENSOR_DELAY_NORMAL), isA(Handler.class));
-        return mSensorEventListenerCaptor.getValue();
-    }
-
-    private void setUpDisplay(int displayId, String uniqueId, LogicalDisplay logicalDisplayMock,
-            DisplayDevice displayDeviceMock, DisplayDeviceConfig displayDeviceConfigMock,
-            boolean isEnabled) {
-        DisplayInfo info = new DisplayInfo();
-        DisplayDeviceInfo deviceInfo = new DisplayDeviceInfo();
-        deviceInfo.uniqueId = uniqueId;
-
-        when(logicalDisplayMock.getDisplayIdLocked()).thenReturn(displayId);
-        when(logicalDisplayMock.getPrimaryDisplayDeviceLocked()).thenReturn(displayDeviceMock);
-        when(logicalDisplayMock.getDisplayInfoLocked()).thenReturn(info);
-        when(logicalDisplayMock.isEnabledLocked()).thenReturn(isEnabled);
-        when(logicalDisplayMock.isInTransitionLocked()).thenReturn(false);
-        when(displayDeviceMock.getDisplayDeviceInfoLocked()).thenReturn(deviceInfo);
-        when(displayDeviceMock.getUniqueId()).thenReturn(uniqueId);
-        when(displayDeviceMock.getDisplayDeviceConfig()).thenReturn(displayDeviceConfigMock);
-        when(displayDeviceConfigMock.getProximitySensor()).thenReturn(
-                new DisplayDeviceConfig.SensorData() {
-                    {
-                        type = Sensor.STRING_TYPE_PROXIMITY;
-                        name = null;
-                    }
-                });
-        when(displayDeviceConfigMock.getNits()).thenReturn(new float[]{2, 500});
-        when(displayDeviceConfigMock.isAutoBrightnessAvailable()).thenReturn(true);
-        when(displayDeviceConfigMock.getAmbientLightSensor()).thenReturn(
-                new DisplayDeviceConfig.SensorData());
-        when(displayDeviceConfigMock.getScreenOffBrightnessSensor()).thenReturn(
-                new DisplayDeviceConfig.SensorData() {
-                    {
-                        type = Sensor.STRING_TYPE_LIGHT;
-                        name = null;
-                    }
-                });
-        when(displayDeviceConfigMock.getScreenOffBrightnessSensorValueToLux())
-                .thenReturn(new int[0]);
-    }
-
-    private DisplayPowerControllerHolder createDisplayPowerController(int displayId,
-            String uniqueId) {
-        return createDisplayPowerController(displayId, uniqueId, /* isEnabled= */ true);
-    }
-
-    private DisplayPowerControllerHolder createDisplayPowerController(int displayId,
-            String uniqueId, boolean isEnabled) {
-        final DisplayPowerState displayPowerState = mock(DisplayPowerState.class);
-        final DualRampAnimator<DisplayPowerState> animator = mock(DualRampAnimator.class);
-        final AutomaticBrightnessController automaticBrightnessController =
-                mock(AutomaticBrightnessController.class);
-        final BrightnessMappingStrategy brightnessMappingStrategy =
-                mock(BrightnessMappingStrategy.class);
-        final HysteresisLevels hysteresisLevels = mock(HysteresisLevels.class);
-        final ScreenOffBrightnessSensorController screenOffBrightnessSensorController =
-                mock(ScreenOffBrightnessSensorController.class);
-
-        DisplayPowerController.Injector injector = spy(new TestInjector(displayPowerState, animator,
-                automaticBrightnessController, brightnessMappingStrategy, hysteresisLevels,
-                screenOffBrightnessSensorController));
-
-        final LogicalDisplay display = mock(LogicalDisplay.class);
-        final DisplayDevice device = mock(DisplayDevice.class);
-        final HighBrightnessModeMetadata hbmMetadata = mock(HighBrightnessModeMetadata.class);
-        final BrightnessSetting brightnessSetting = mock(BrightnessSetting.class);
-        final DisplayDeviceConfig config = mock(DisplayDeviceConfig.class);
-
-        setUpDisplay(displayId, uniqueId, display, device, config, isEnabled);
-
-        final DisplayPowerController dpc = new DisplayPowerController(
-                mContext, injector, mDisplayPowerCallbacksMock, mHandler,
-                mSensorManagerMock, mDisplayBlankerMock, display,
-                mBrightnessTrackerMock, brightnessSetting, () -> {},
-                hbmMetadata, /* bootCompleted= */ false);
-
-        return new DisplayPowerControllerHolder(dpc, display, displayPowerState, brightnessSetting,
-                animator, automaticBrightnessController, screenOffBrightnessSensorController,
-                hbmMetadata, brightnessMappingStrategy, injector);
-    }
-
-    /**
-     * A class for holding a DisplayPowerController under test and all the mocks specifically
-     * related to it.
-     */
-    private static class DisplayPowerControllerHolder {
-        public final DisplayPowerController dpc;
-        public final LogicalDisplay display;
-        public final DisplayPowerState displayPowerState;
-        public final BrightnessSetting brightnessSetting;
-        public final DualRampAnimator<DisplayPowerState> animator;
-        public final AutomaticBrightnessController automaticBrightnessController;
-        public final ScreenOffBrightnessSensorController screenOffBrightnessSensorController;
-        public final HighBrightnessModeMetadata hbmMetadata;
-        public final BrightnessMappingStrategy brightnessMappingStrategy;
-        public final DisplayPowerController.Injector injector;
-
-        DisplayPowerControllerHolder(DisplayPowerController dpc, LogicalDisplay display,
-                DisplayPowerState displayPowerState, BrightnessSetting brightnessSetting,
-                DualRampAnimator<DisplayPowerState> animator,
-                AutomaticBrightnessController automaticBrightnessController,
-                ScreenOffBrightnessSensorController screenOffBrightnessSensorController,
-                HighBrightnessModeMetadata hbmMetadata,
-                BrightnessMappingStrategy brightnessMappingStrategy,
-                DisplayPowerController.Injector injector) {
-            this.dpc = dpc;
-            this.display = display;
-            this.displayPowerState = displayPowerState;
-            this.brightnessSetting = brightnessSetting;
-            this.animator = animator;
-            this.automaticBrightnessController = automaticBrightnessController;
-            this.screenOffBrightnessSensorController = screenOffBrightnessSensorController;
-            this.hbmMetadata = hbmMetadata;
-            this.brightnessMappingStrategy = brightnessMappingStrategy;
-            this.injector = injector;
-        }
-    }
-
-    private class TestInjector extends DisplayPowerController.Injector {
-        private final DisplayPowerState mDisplayPowerState;
-        private final DualRampAnimator<DisplayPowerState> mAnimator;
-        private final AutomaticBrightnessController mAutomaticBrightnessController;
-        private final BrightnessMappingStrategy mBrightnessMappingStrategy;
-        private final HysteresisLevels mHysteresisLevels;
-        private final ScreenOffBrightnessSensorController mScreenOffBrightnessSensorController;
-
-        TestInjector(DisplayPowerState dps, DualRampAnimator<DisplayPowerState> animator,
-                AutomaticBrightnessController automaticBrightnessController,
-                BrightnessMappingStrategy brightnessMappingStrategy,
-                HysteresisLevels hysteresisLevels,
-                ScreenOffBrightnessSensorController screenOffBrightnessSensorController) {
-            mDisplayPowerState = dps;
-            mAnimator = animator;
-            mAutomaticBrightnessController = automaticBrightnessController;
-            mBrightnessMappingStrategy = brightnessMappingStrategy;
-            mHysteresisLevels = hysteresisLevels;
-            mScreenOffBrightnessSensorController = screenOffBrightnessSensorController;
-        }
-
-        @Override
-        DisplayPowerController.Clock getClock() {
-            return mClock::now;
-        }
-
-        @Override
-        DisplayPowerState getDisplayPowerState(DisplayBlanker blanker, ColorFade colorFade,
-                int displayId, int displayState) {
-            return mDisplayPowerState;
-        }
-
-        @Override
-        DualRampAnimator<DisplayPowerState> getDualRampAnimator(DisplayPowerState dps,
-                FloatProperty<DisplayPowerState> firstProperty,
-                FloatProperty<DisplayPowerState> secondProperty) {
-            return mAnimator;
-        }
-
-        @Override
-        AutomaticBrightnessController getAutomaticBrightnessController(
-                AutomaticBrightnessController.Callbacks callbacks, Looper looper,
-                SensorManager sensorManager, Sensor lightSensor,
-                BrightnessMappingStrategy interactiveModeBrightnessMapper,
-                int lightSensorWarmUpTime, float brightnessMin, float brightnessMax,
-                float dozeScaleFactor, int lightSensorRate, int initialLightSensorRate,
-                long brighteningLightDebounceConfig, long darkeningLightDebounceConfig,
-                boolean resetAmbientLuxAfterWarmUpConfig,
-                HysteresisLevels ambientBrightnessThresholds,
-                HysteresisLevels screenBrightnessThresholds,
-                HysteresisLevels ambientBrightnessThresholdsIdle,
-                HysteresisLevels screenBrightnessThresholdsIdle, Context context,
-                HighBrightnessModeController hbmController,
-                BrightnessThrottler brightnessThrottler,
-                BrightnessMappingStrategy idleModeBrightnessMapper,
-                int ambientLightHorizonShort, int ambientLightHorizonLong, float userLux,
-                float userBrightness) {
-            return mAutomaticBrightnessController;
-        }
-
-        @Override
-        BrightnessMappingStrategy getInteractiveModeBrightnessMapper(Resources resources,
-                DisplayDeviceConfig displayDeviceConfig,
-                DisplayWhiteBalanceController displayWhiteBalanceController) {
-            return mBrightnessMappingStrategy;
-        }
-
-        @Override
-        HysteresisLevels getHysteresisLevels(float[] brighteningThresholdsPercentages,
-                float[] darkeningThresholdsPercentages, float[] brighteningThresholdLevels,
-                float[] darkeningThresholdLevels, float minDarkeningThreshold,
-                float minBrighteningThreshold) {
-            return mHysteresisLevels;
-        }
-
-        @Override
-        HysteresisLevels getHysteresisLevels(float[] brighteningThresholdsPercentages,
-                float[] darkeningThresholdsPercentages, float[] brighteningThresholdLevels,
-                float[] darkeningThresholdLevels, float minDarkeningThreshold,
-                float minBrighteningThreshold, boolean potentialOldBrightnessRange) {
-            return mHysteresisLevels;
-        }
-
-        @Override
-        ScreenOffBrightnessSensorController getScreenOffBrightnessSensorController(
-                SensorManager sensorManager, Sensor lightSensor, Handler handler,
-                ScreenOffBrightnessSensorController.Clock clock, int[] sensorValueToLux,
-                BrightnessMappingStrategy brightnessMapper) {
-            return mScreenOffBrightnessSensorController;
-        }
-    }
-}
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerProximityStateControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerProximityStateControllerTest.java
deleted file mode 100644
index 5b0b989..0000000
--- a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerProximityStateControllerTest.java
+++ /dev/null
@@ -1,402 +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.server.display;
-
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verifyZeroInteractions;
-import static org.mockito.Mockito.when;
-
-import android.hardware.Sensor;
-import android.hardware.SensorEventListener;
-import android.hardware.SensorManager;
-import android.hardware.display.DisplayManagerInternal;
-import android.os.test.TestLooper;
-import android.view.Display;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.server.testutils.OffsettableClock;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.List;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public final class DisplayPowerProximityStateControllerTest {
-    @Mock
-    WakelockController mWakelockController;
-
-    @Mock
-    DisplayDeviceConfig mDisplayDeviceConfig;
-
-    @Mock
-    Runnable mNudgeUpdatePowerState;
-
-    @Mock
-    SensorManager mSensorManager;
-
-    private Sensor mProximitySensor;
-    private OffsettableClock mClock;
-    private TestLooper mTestLooper;
-    private SensorEventListener mSensorEventListener;
-    private DisplayPowerProximityStateController mDisplayPowerProximityStateController;
-
-    @Before
-    public void before() throws Exception {
-        MockitoAnnotations.initMocks(this);
-        mClock = new OffsettableClock.Stopped();
-        mTestLooper = new TestLooper(mClock::now);
-        when(mDisplayDeviceConfig.getProximitySensor()).thenReturn(
-                new DisplayDeviceConfig.SensorData() {
-                    {
-                        type = Sensor.STRING_TYPE_PROXIMITY;
-                        // This is kept null because currently there is no way to define a sensor
-                        // name in TestUtils
-                        name = null;
-                    }
-                });
-        setUpProxSensor();
-        DisplayPowerProximityStateController.Injector injector =
-                new DisplayPowerProximityStateController.Injector() {
-                    @Override
-                    DisplayPowerProximityStateController.Clock createClock() {
-                        return mClock::now;
-                    }
-                };
-        mDisplayPowerProximityStateController = new DisplayPowerProximityStateController(
-                mWakelockController, mDisplayDeviceConfig, mTestLooper.getLooper(),
-                mNudgeUpdatePowerState, 0,
-                mSensorManager, injector);
-        mSensorEventListener = mDisplayPowerProximityStateController.getProximitySensorListener();
-    }
-
-    @Test
-    public void updatePendingProximityRequestsWorksAsExpectedWhenPending() {
-        // Set the system to pending wait for proximity
-        assertTrue(mDisplayPowerProximityStateController.setPendingWaitForNegativeProximityLocked(
-                true));
-        assertTrue(
-                mDisplayPowerProximityStateController.getPendingWaitForNegativeProximityLocked());
-
-        // Update the pending proximity wait request
-        mDisplayPowerProximityStateController.updatePendingProximityRequestsLocked();
-        assertTrue(mDisplayPowerProximityStateController.getWaitingForNegativeProximity());
-        assertFalse(
-                mDisplayPowerProximityStateController.getPendingWaitForNegativeProximityLocked());
-    }
-
-    @Test
-    public void updatePendingProximityRequestsWorksAsExpectedWhenNotPending() {
-        // Will not wait or be in the pending wait state of not already pending
-        mDisplayPowerProximityStateController.updatePendingProximityRequestsLocked();
-        assertFalse(mDisplayPowerProximityStateController.getWaitingForNegativeProximity());
-        assertFalse(
-                mDisplayPowerProximityStateController.getPendingWaitForNegativeProximityLocked());
-    }
-
-    @Test
-    public void updatePendingProximityRequestsWorksAsExpectedWhenPendingAndProximityIgnored()
-            throws Exception {
-        // Set the system to the state where it will ignore proximity unless changed
-        enableProximitySensor();
-        emitAndValidatePositiveProximityEvent();
-        mDisplayPowerProximityStateController.ignoreProximitySensorUntilChangedInternal();
-        advanceTime(1);
-        assertTrue(mDisplayPowerProximityStateController.shouldIgnoreProximityUntilChanged());
-        verify(mNudgeUpdatePowerState, times(2)).run();
-
-        // Do not set the system to pending wait for proximity
-        mDisplayPowerProximityStateController.updatePendingProximityRequestsLocked();
-        assertFalse(mDisplayPowerProximityStateController.getWaitingForNegativeProximity());
-        assertFalse(
-                mDisplayPowerProximityStateController.getPendingWaitForNegativeProximityLocked());
-
-        // Set the system to pending wait for proximity. But because the proximity is being
-        // ignored, it will not wait or not set the pending wait
-        assertTrue(mDisplayPowerProximityStateController.setPendingWaitForNegativeProximityLocked(
-                true));
-        mDisplayPowerProximityStateController.updatePendingProximityRequestsLocked();
-        assertFalse(mDisplayPowerProximityStateController.getWaitingForNegativeProximity());
-        assertFalse(
-                mDisplayPowerProximityStateController.getPendingWaitForNegativeProximityLocked());
-    }
-
-    @Test
-    public void cleanupDisablesTheProximitySensor() {
-        enableProximitySensor();
-        mDisplayPowerProximityStateController.cleanup();
-        verify(mSensorManager).unregisterListener(
-                mSensorEventListener);
-        assertFalse(mDisplayPowerProximityStateController.isProximitySensorEnabled());
-        assertFalse(mDisplayPowerProximityStateController.getWaitingForNegativeProximity());
-        assertFalse(mDisplayPowerProximityStateController.shouldIgnoreProximityUntilChanged());
-        assertEquals(mDisplayPowerProximityStateController.getProximity(),
-                DisplayPowerProximityStateController.PROXIMITY_UNKNOWN);
-        when(mWakelockController.releaseWakelock(
-                WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE)).thenReturn(true);
-        assertEquals(mDisplayPowerProximityStateController.getPendingProximityDebounceTime(), -1);
-    }
-
-    @Test
-    public void isProximitySensorAvailableReturnsTrueWhenAvailable() {
-        assertTrue(mDisplayPowerProximityStateController.isProximitySensorAvailable());
-    }
-
-    @Test
-    public void isProximitySensorAvailableReturnsFalseWhenNotAvailable() {
-        when(mDisplayDeviceConfig.getProximitySensor()).thenReturn(
-                new DisplayDeviceConfig.SensorData() {
-                    {
-                        type = null;
-                        name = null;
-                    }
-                });
-        mDisplayPowerProximityStateController = new DisplayPowerProximityStateController(
-                mWakelockController, mDisplayDeviceConfig, mTestLooper.getLooper(),
-                mNudgeUpdatePowerState, 1,
-                mSensorManager, null);
-        assertFalse(mDisplayPowerProximityStateController.isProximitySensorAvailable());
-    }
-
-    @Test
-    public void notifyDisplayDeviceChangedReloadsTheProximitySensor() throws Exception {
-        DisplayDeviceConfig updatedDisplayDeviceConfig = mock(DisplayDeviceConfig.class);
-        when(updatedDisplayDeviceConfig.getProximitySensor()).thenReturn(
-                new DisplayDeviceConfig.SensorData() {
-                    {
-                        type = Sensor.STRING_TYPE_PROXIMITY;
-                        name = null;
-                    }
-                });
-        Sensor newProxSensor = TestUtils.createSensor(
-                Sensor.TYPE_PROXIMITY, Sensor.STRING_TYPE_PROXIMITY, 4.0f);
-        when(mSensorManager.getSensorList(eq(Sensor.TYPE_ALL)))
-                .thenReturn(List.of(newProxSensor));
-        mDisplayPowerProximityStateController.notifyDisplayDeviceChanged(
-                updatedDisplayDeviceConfig);
-        assertTrue(mDisplayPowerProximityStateController.isProximitySensorAvailable());
-    }
-
-    @Test
-    public void setPendingWaitForNegativeProximityLockedWorksAsExpected() {
-        // Doesn't do anything not asked to wait
-        assertFalse(mDisplayPowerProximityStateController.setPendingWaitForNegativeProximityLocked(
-                false));
-        assertFalse(
-                mDisplayPowerProximityStateController.getPendingWaitForNegativeProximityLocked());
-
-        // Sets pending wait negative proximity if not already waiting
-        assertTrue(mDisplayPowerProximityStateController.setPendingWaitForNegativeProximityLocked(
-                true));
-        assertTrue(
-                mDisplayPowerProximityStateController.getPendingWaitForNegativeProximityLocked());
-
-        // Will not set pending wait negative proximity if already waiting
-        assertFalse(mDisplayPowerProximityStateController.setPendingWaitForNegativeProximityLocked(
-                true));
-        assertTrue(
-                mDisplayPowerProximityStateController.getPendingWaitForNegativeProximityLocked());
-
-    }
-
-    @Test
-    public void evaluateProximityStateWhenRequestedUseOfProximitySensor() throws Exception {
-        // Enable the proximity sensor
-        enableProximitySensor();
-
-        // Emit a positive proximity event to move the system to a state to mimic a scenario
-        // where the system is in positive proximity
-        emitAndValidatePositiveProximityEvent();
-
-        // Again evaluate the proximity state, with system having positive proximity
-        setScreenOffBecauseOfPositiveProximityState();
-    }
-
-    @Test
-    public void evaluateProximityStateWhenScreenOffBecauseOfPositiveProximity() throws Exception {
-        // Enable the proximity sensor
-        enableProximitySensor();
-
-        // Emit a positive proximity event to move the system to a state to mimic a scenario
-        // where the system is in positive proximity
-        emitAndValidatePositiveProximityEvent();
-
-        // Again evaluate the proximity state, with system having positive proximity
-        setScreenOffBecauseOfPositiveProximityState();
-
-        // Set the system to pending wait for proximity
-        mDisplayPowerProximityStateController.setPendingWaitForNegativeProximityLocked(true);
-        // Update the pending proximity wait request
-        mDisplayPowerProximityStateController.updatePendingProximityRequestsLocked();
-
-        // Start ignoring proximity sensor
-        mDisplayPowerProximityStateController.ignoreProximitySensorUntilChangedInternal();
-        // Re-evaluate the proximity state, such that the system is detecting the positive
-        // proximity, and screen is off because of that
-        when(mWakelockController.getOnProximityNegativeRunnable()).thenReturn(mock(Runnable.class));
-        mDisplayPowerProximityStateController.updateProximityState(mock(
-                DisplayManagerInternal.DisplayPowerRequest.class), Display.STATE_ON);
-        assertTrue(mDisplayPowerProximityStateController.isProximitySensorEnabled());
-        assertFalse(mDisplayPowerProximityStateController.isScreenOffBecauseOfProximity());
-        assertTrue(
-                mDisplayPowerProximityStateController
-                        .shouldSkipRampBecauseOfProximityChangeToNegative());
-        verify(mWakelockController).acquireWakelock(
-                WakelockController.WAKE_LOCK_PROXIMITY_NEGATIVE);
-    }
-
-    @Test
-    public void evaluateProximityStateWhenDisplayIsTurningOff() throws Exception {
-        // Enable the proximity sensor
-        enableProximitySensor();
-
-        // Emit a positive proximity event to move the system to a state to mimic a scenario
-        // where the system is in positive proximity
-        emitAndValidatePositiveProximityEvent();
-
-        // Again evaluate the proximity state, with system having positive proximity
-        setScreenOffBecauseOfPositiveProximityState();
-
-        // Re-evaluate the proximity state, such that the system is detecting the positive
-        // proximity, and screen is off because of that
-        mDisplayPowerProximityStateController.updateProximityState(mock(
-                DisplayManagerInternal.DisplayPowerRequest.class), Display.STATE_OFF);
-        verify(mSensorManager).unregisterListener(
-                mSensorEventListener);
-        assertFalse(mDisplayPowerProximityStateController.isProximitySensorEnabled());
-        assertFalse(mDisplayPowerProximityStateController.getWaitingForNegativeProximity());
-        assertFalse(mDisplayPowerProximityStateController.shouldIgnoreProximityUntilChanged());
-        assertEquals(mDisplayPowerProximityStateController.getProximity(),
-                DisplayPowerProximityStateController.PROXIMITY_UNKNOWN);
-        when(mWakelockController.releaseWakelock(
-                WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE)).thenReturn(true);
-        assertEquals(mDisplayPowerProximityStateController.getPendingProximityDebounceTime(), -1);
-    }
-
-    @Test
-    public void evaluateProximityStateNotWaitingForNegativeProximityAndNotUsingProxSensor()
-            throws Exception {
-        // Enable the proximity sensor
-        enableProximitySensor();
-
-        // Emit a positive proximity event to move the system to a state to mimic a scenario
-        // where the system is in positive proximity
-        emitAndValidatePositiveProximityEvent();
-
-        // Re-evaluate the proximity state, such that the system is detecting the positive
-        // proximity, and screen is off because of that
-        mDisplayPowerProximityStateController.updateProximityState(mock(
-                DisplayManagerInternal.DisplayPowerRequest.class), Display.STATE_ON);
-        verify(mSensorManager).unregisterListener(
-                mSensorEventListener);
-        assertFalse(mDisplayPowerProximityStateController.isProximitySensorEnabled());
-        assertFalse(mDisplayPowerProximityStateController.getWaitingForNegativeProximity());
-        assertFalse(mDisplayPowerProximityStateController.shouldIgnoreProximityUntilChanged());
-        assertEquals(mDisplayPowerProximityStateController.getProximity(),
-                DisplayPowerProximityStateController.PROXIMITY_UNKNOWN);
-        when(mWakelockController.releaseWakelock(
-                WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE)).thenReturn(true);
-        assertEquals(mDisplayPowerProximityStateController.getPendingProximityDebounceTime(), -1);
-    }
-
-    private void advanceTime(long timeMs) {
-        mClock.fastForward(timeMs);
-        mTestLooper.dispatchAll();
-    }
-
-    private void setUpProxSensor() throws Exception {
-        mProximitySensor = TestUtils.createSensor(
-                Sensor.TYPE_PROXIMITY, Sensor.STRING_TYPE_PROXIMITY, 5.0f);
-        when(mSensorManager.getSensorList(eq(Sensor.TYPE_ALL)))
-                .thenReturn(List.of(mProximitySensor));
-    }
-
-    private void emitAndValidatePositiveProximityEvent() throws Exception {
-        // Emit a positive proximity event to move the system to a state to mimic a scenario
-        // where the system is in positive proximity
-        when(mWakelockController.releaseWakelock(
-                WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE)).thenReturn(true);
-        mSensorEventListener.onSensorChanged(TestUtils.createSensorEvent(mProximitySensor, 4));
-        verify(mSensorManager).registerListener(mSensorEventListener,
-                mProximitySensor, SensorManager.SENSOR_DELAY_NORMAL,
-                mDisplayPowerProximityStateController.getHandler());
-        verify(mWakelockController).acquireWakelock(
-                WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE);
-        assertEquals(mDisplayPowerProximityStateController.getPendingProximity(),
-                DisplayPowerProximityStateController.PROXIMITY_POSITIVE);
-        assertFalse(mDisplayPowerProximityStateController.shouldIgnoreProximityUntilChanged());
-        assertEquals(mDisplayPowerProximityStateController.getProximity(),
-                DisplayPowerProximityStateController.PROXIMITY_POSITIVE);
-        verify(mNudgeUpdatePowerState).run();
-        assertEquals(mDisplayPowerProximityStateController.getPendingProximityDebounceTime(), -1);
-    }
-
-    // Call evaluateProximityState with the request for using the proximity sensor. This will
-    // register the proximity sensor listener, which will be needed for mocking positive
-    // proximity scenarios.
-    private void enableProximitySensor() {
-        DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
-                DisplayManagerInternal.DisplayPowerRequest.class);
-        displayPowerRequest.useProximitySensor = true;
-        mDisplayPowerProximityStateController.updateProximityState(displayPowerRequest,
-                Display.STATE_ON);
-        verify(mSensorManager).registerListener(
-                mSensorEventListener,
-                mProximitySensor, SensorManager.SENSOR_DELAY_NORMAL,
-                mDisplayPowerProximityStateController.getHandler());
-        assertTrue(mDisplayPowerProximityStateController.isProximitySensorEnabled());
-        assertFalse(mDisplayPowerProximityStateController.shouldIgnoreProximityUntilChanged());
-        assertFalse(mDisplayPowerProximityStateController.isScreenOffBecauseOfProximity());
-        verifyZeroInteractions(mWakelockController);
-    }
-
-    private void setScreenOffBecauseOfPositiveProximityState() {
-        // Prepare a request to indicate that the proximity sensor is to be used
-        DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
-                DisplayManagerInternal.DisplayPowerRequest.class);
-        displayPowerRequest.useProximitySensor = true;
-
-        Runnable onProximityPositiveRunnable = mock(Runnable.class);
-        when(mWakelockController.getOnProximityPositiveRunnable()).thenReturn(
-                onProximityPositiveRunnable);
-
-        mDisplayPowerProximityStateController.updateProximityState(displayPowerRequest,
-                Display.STATE_ON);
-        verify(mSensorManager).registerListener(
-                mSensorEventListener,
-                mProximitySensor, SensorManager.SENSOR_DELAY_NORMAL,
-                mDisplayPowerProximityStateController.getHandler());
-        assertTrue(mDisplayPowerProximityStateController.isProximitySensorEnabled());
-        assertFalse(mDisplayPowerProximityStateController.shouldIgnoreProximityUntilChanged());
-        assertTrue(mDisplayPowerProximityStateController.isScreenOffBecauseOfProximity());
-        verify(mWakelockController).acquireWakelock(
-                WakelockController.WAKE_LOCK_PROXIMITY_POSITIVE);
-    }
-}
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
deleted file mode 100644
index b7dbaf9..0000000
--- a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
+++ /dev/null
@@ -1,1239 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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 com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assume.assumeTrue;
-import static org.mockito.ArgumentMatchers.anyFloat;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.os.Binder;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Looper;
-import android.view.Display;
-import android.view.DisplayAddress;
-import android.view.SurfaceControl;
-import android.view.SurfaceControl.RefreshRateRange;
-import android.view.SurfaceControl.RefreshRateRanges;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.dx.mockito.inline.extended.StaticMockitoSession;
-import com.android.internal.R;
-import com.android.server.LocalServices;
-import com.android.server.display.LocalDisplayAdapter.BacklightAdapter;
-import com.android.server.display.mode.DisplayModeDirector;
-import com.android.server.lights.LightsManager;
-import com.android.server.lights.LogicalLight;
-
-import com.google.common.truth.Truth;
-
-import org.junit.After;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.quality.Strictness;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.LinkedList;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class LocalDisplayAdapterTest {
-    private static final Long DISPLAY_MODEL = Long.valueOf(0xAAAAAAAAL);
-    private static final int PORT_A = 0;
-    private static final int PORT_B = 0x80;
-    private static final int PORT_C = 0xFF;
-    private static final float REFRESH_RATE = 60f;
-    private static final RefreshRateRange REFRESH_RATE_RANGE =
-            new RefreshRateRange(REFRESH_RATE, REFRESH_RATE);
-    private static final RefreshRateRanges REFRESH_RATE_RANGES =
-            new RefreshRateRanges(REFRESH_RATE_RANGE, REFRESH_RATE_RANGE);
-
-    private static final long HANDLER_WAIT_MS = 100;
-
-    private static final int[] HDR_TYPES = new int[]{1, 2};
-
-    private StaticMockitoSession mMockitoSession;
-
-    private LocalDisplayAdapter mAdapter;
-
-    @Mock
-    private DisplayManagerService.SyncRoot mMockedSyncRoot;
-    @Mock
-    private Context mMockedContext;
-    @Mock
-    private Resources mMockedResources;
-    @Mock
-    private LightsManager mMockedLightsManager;
-    @Mock
-    private LogicalLight mMockedBacklight;
-
-    private Handler mHandler;
-
-    private TestListener mListener = new TestListener();
-
-    private LinkedList<DisplayAddress.Physical> mAddresses = new LinkedList<>();
-
-    private Injector mInjector;
-
-    @Mock
-    private LocalDisplayAdapter.SurfaceControlProxy mSurfaceControlProxy;
-    private static final float[] DISPLAY_RANGE_NITS = { 2.685f, 478.5f };
-    private static final int[] BACKLIGHT_RANGE = { 1, 255 };
-    private static final float[] BACKLIGHT_RANGE_ZERO_TO_ONE = { 0.0f, 1.0f };
-
-    @Before
-    public void setUp() throws Exception {
-        mMockitoSession = mockitoSession()
-                .initMocks(this)
-                .strictness(Strictness.LENIENT)
-                .startMocking();
-        mHandler = new Handler(Looper.getMainLooper());
-        doReturn(mMockedResources).when(mMockedContext).getResources();
-        LocalServices.removeServiceForTest(LightsManager.class);
-        LocalServices.addService(LightsManager.class, mMockedLightsManager);
-        mInjector = new Injector();
-        when(mSurfaceControlProxy.getBootDisplayModeSupport()).thenReturn(true);
-        mAdapter = new LocalDisplayAdapter(mMockedSyncRoot, mMockedContext, mHandler,
-                mListener, mInjector);
-        spyOn(mAdapter);
-        doReturn(mMockedContext).when(mAdapter).getOverlayContext();
-
-        TypedArray mockNitsRange = createFloatTypedArray(DISPLAY_RANGE_NITS);
-        when(mMockedResources.obtainTypedArray(R.array.config_screenBrightnessNits))
-                .thenReturn(mockNitsRange);
-        when(mMockedResources.getIntArray(R.array.config_screenBrightnessBacklight))
-                .thenReturn(BACKLIGHT_RANGE);
-        when(mMockedResources.getFloat(com.android.internal.R.dimen
-                .config_screenBrightnessSettingMinimumFloat))
-                .thenReturn(BACKLIGHT_RANGE_ZERO_TO_ONE[0]);
-        when(mMockedResources.getFloat(com.android.internal.R.dimen
-                .config_screenBrightnessSettingMaximumFloat))
-                .thenReturn(BACKLIGHT_RANGE_ZERO_TO_ONE[1]);
-        when(mMockedResources.getStringArray(R.array.config_displayUniqueIdArray))
-                .thenReturn(new String[]{});
-        TypedArray mockArray = mock(TypedArray.class);
-        when(mockArray.length()).thenReturn(0);
-        when(mMockedResources.obtainTypedArray(R.array.config_maskBuiltInDisplayCutoutArray))
-                .thenReturn(mockArray);
-        when(mMockedResources.obtainTypedArray(R.array.config_waterfallCutoutArray))
-                .thenReturn(mockArray);
-        when(mMockedResources.obtainTypedArray(R.array.config_roundedCornerRadiusArray))
-                .thenReturn(mockArray);
-        when(mMockedResources.obtainTypedArray(R.array.config_roundedCornerTopRadiusArray))
-                .thenReturn(mockArray);
-        when(mMockedResources.obtainTypedArray(R.array.config_roundedCornerBottomRadiusArray))
-                .thenReturn(mockArray);
-        when(mMockedResources.obtainTypedArray(
-                com.android.internal.R.array.config_autoBrightnessDisplayValuesNits))
-                .thenReturn(mockArray);
-        when(mMockedResources.getIntArray(
-                com.android.internal.R.array.config_autoBrightnessLevels))
-                .thenReturn(new int[]{});
-        when(mMockedResources.obtainTypedArray(R.array.config_displayShapeArray))
-                .thenReturn(mockArray);
-        when(mMockedResources.obtainTypedArray(R.array.config_builtInDisplayIsRoundArray))
-                .thenReturn(mockArray);
-        when(mMockedResources.getIntArray(
-            com.android.internal.R.array.config_brightnessThresholdsOfPeakRefreshRate))
-            .thenReturn(new int[]{});
-        when(mMockedResources.getIntArray(
-            com.android.internal.R.array.config_ambientThresholdsOfPeakRefreshRate))
-            .thenReturn(new int[]{});
-        when(mMockedResources.getIntArray(
-            com.android.internal.R.array.config_highDisplayBrightnessThresholdsOfFixedRefreshRate))
-            .thenReturn(new int[]{});
-        when(mMockedResources.getIntArray(
-            com.android.internal.R.array.config_highAmbientBrightnessThresholdsOfFixedRefreshRate))
-            .thenReturn(new int[]{});
-    }
-
-    @After
-    public void tearDown() {
-        if (mMockitoSession != null) {
-            mMockitoSession.finishMocking();
-        }
-    }
-
-    /**
-     * Confirm that display is marked as private when it is listed in
-     * com.android.internal.R.array.config_localPrivateDisplayPorts.
-     */
-    @Test
-    public void testPrivateDisplay() throws Exception {
-        setUpDisplay(new FakeDisplay(PORT_A));
-        setUpDisplay(new FakeDisplay(PORT_B));
-        setUpDisplay(new FakeDisplay(PORT_C));
-        updateAvailableDisplays();
-
-        doReturn(new int[]{ PORT_B }).when(mMockedResources)
-                .getIntArray(com.android.internal.R.array.config_localPrivateDisplayPorts);
-        mAdapter.registerLocked();
-
-        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
-
-        // This should be public
-        assertDisplayPrivateFlag(mListener.addedDisplays.get(0).getDisplayDeviceInfoLocked(),
-                PORT_A, false);
-        // This should be private
-        assertDisplayPrivateFlag(mListener.addedDisplays.get(1).getDisplayDeviceInfoLocked(),
-                PORT_B, true);
-        // This should be public
-        assertDisplayPrivateFlag(mListener.addedDisplays.get(2).getDisplayDeviceInfoLocked(),
-                PORT_C, false);
-    }
-
-    @Test
-    public void testSupportedDisplayModesGetOverriddenWhenDisplayIsUpdated()
-            throws InterruptedException {
-        SurfaceControl.DisplayMode displayMode = createFakeDisplayMode(0, 1920, 1080, 0);
-        displayMode.supportedHdrTypes = new int[0];
-        FakeDisplay display = new FakeDisplay(PORT_A, new SurfaceControl.DisplayMode[]{displayMode},
-                0, 0);
-        setUpDisplay(display);
-        updateAvailableDisplays();
-        mAdapter.registerLocked();
-        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
-
-        DisplayDevice displayDevice = mListener.addedDisplays.get(0);
-        displayDevice.applyPendingDisplayDeviceInfoChangesLocked();
-        Display.Mode[] supportedModes = displayDevice.getDisplayDeviceInfoLocked().supportedModes;
-        Assert.assertEquals(1, supportedModes.length);
-        Assert.assertEquals(0, supportedModes[0].getSupportedHdrTypes().length);
-
-        displayMode.supportedHdrTypes = new int[]{3, 2};
-        display.dynamicInfo.supportedDisplayModes = new SurfaceControl.DisplayMode[]{displayMode};
-        setUpDisplay(display);
-        mInjector.getTransmitter().sendHotplug(display, true);
-        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
-
-        displayDevice = mListener.changedDisplays.get(0);
-        displayDevice.applyPendingDisplayDeviceInfoChangesLocked();
-        supportedModes = displayDevice.getDisplayDeviceInfoLocked().supportedModes;
-
-        Assert.assertEquals(1, supportedModes.length);
-        assertArrayEquals(new int[]{2, 3}, supportedModes[0].getSupportedHdrTypes());
-    }
-
-    /**
-     * Confirm that all local displays are public when config_localPrivateDisplayPorts is empty.
-     */
-    @Test
-    public void testPublicDisplaysForNoConfigLocalPrivateDisplayPorts() throws Exception {
-        setUpDisplay(new FakeDisplay(PORT_A));
-        setUpDisplay(new FakeDisplay(PORT_C));
-        updateAvailableDisplays();
-
-        // config_localPrivateDisplayPorts is null
-        mAdapter.registerLocked();
-
-        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
-
-        // This should be public
-        assertDisplayPrivateFlag(mListener.addedDisplays.get(0).getDisplayDeviceInfoLocked(),
-                PORT_A, false);
-        // This should be public
-        assertDisplayPrivateFlag(mListener.addedDisplays.get(1).getDisplayDeviceInfoLocked(),
-                PORT_C, false);
-    }
-
-    private static void assertDisplayPrivateFlag(
-            DisplayDeviceInfo info, int expectedPort, boolean shouldBePrivate) {
-        final DisplayAddress.Physical address = (DisplayAddress.Physical) info.address;
-        assertNotNull(address);
-        assertEquals(expectedPort, address.getPort());
-        assertEquals(DISPLAY_MODEL, address.getModel());
-        assertEquals(shouldBePrivate, (info.flags & DisplayDeviceInfo.FLAG_PRIVATE) != 0);
-    }
-
-    /**
-     * Confirm that external display uses physical density.
-     */
-    @Test
-    public void testDpiValues() throws Exception {
-        // needs default one always
-        setUpDisplay(new FakeDisplay(PORT_A));
-        setUpDisplay(new FakeDisplay(PORT_B));
-        updateAvailableDisplays();
-        mAdapter.registerLocked();
-
-        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
-
-        assertDisplayDpi(
-                mListener.addedDisplays.get(0).getDisplayDeviceInfoLocked(), PORT_A, 100, 100,
-                16000);
-        assertDisplayDpi(
-                mListener.addedDisplays.get(1).getDisplayDeviceInfoLocked(), PORT_B, 100, 100,
-                16000);
-    }
-
-    private static class DisplayModeWrapper {
-        public SurfaceControl.DisplayMode mode;
-        public float[] expectedAlternativeRefreshRates;
-
-        DisplayModeWrapper(SurfaceControl.DisplayMode mode,
-                float[] expectedAlternativeRefreshRates) {
-            this.mode = mode;
-            this.expectedAlternativeRefreshRates = expectedAlternativeRefreshRates;
-        }
-    }
-
-    /**
-     * Updates the <code>display</code> using the given <code>modes</code> and then checks if the
-     * <code>expectedAlternativeRefreshRates</code> are present for each of the
-     * <code>modes</code>.
-     */
-    private void testAlternativeRefreshRatesCommon(FakeDisplay display,
-                DisplayModeWrapper[] wrappedModes)
-            throws InterruptedException {
-        // Update the display.
-        SurfaceControl.DisplayMode[] modes = new SurfaceControl.DisplayMode[wrappedModes.length];
-        for (int i = 0; i < wrappedModes.length; i++) {
-            modes[i] = wrappedModes[i].mode;
-        }
-        display.dynamicInfo.supportedDisplayModes = modes;
-        setUpDisplay(display);
-        mInjector.getTransmitter().sendHotplug(display, /* connected */ true);
-        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
-        assertTrue(mListener.traversalRequested);
-        assertThat(mListener.changedDisplays.size()).isGreaterThan(0);
-
-        // Verify the supported modes are updated accordingly.
-        DisplayDevice displayDevice =
-                mListener.changedDisplays.get(mListener.changedDisplays.size() - 1);
-        displayDevice.applyPendingDisplayDeviceInfoChangesLocked();
-        Display.Mode[] supportedModes = displayDevice.getDisplayDeviceInfoLocked().supportedModes;
-        assertThat(supportedModes.length).isEqualTo(modes.length);
-
-        for (int i = 0; i < wrappedModes.length; i++) {
-            assertModeIsSupported(supportedModes, modes[i],
-                    wrappedModes[i].expectedAlternativeRefreshRates);
-        }
-    }
-
-    @Test
-    public void testAfterDisplayChange_AlternativeRefreshRatesAreUpdated() throws Exception {
-        FakeDisplay display = new FakeDisplay(PORT_A);
-        setUpDisplay(display);
-        updateAvailableDisplays();
-        mAdapter.registerLocked();
-        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
-
-        testAlternativeRefreshRatesCommon(display, new DisplayModeWrapper[] {
-                new DisplayModeWrapper(
-                        createFakeDisplayMode(0, 1920, 1080, 60f, 0), new float[]{24f, 50f}),
-                new DisplayModeWrapper(
-                        createFakeDisplayMode(1, 1920, 1080, 50f, 0), new float[]{24f, 60f}),
-                new DisplayModeWrapper(
-                        createFakeDisplayMode(2, 1920, 1080, 24f, 0), new float[]{50f, 60f}),
-                new DisplayModeWrapper(
-                        createFakeDisplayMode(3, 3840, 2160, 60f, 0), new float[]{24f, 50f}),
-                new DisplayModeWrapper(
-                        createFakeDisplayMode(4, 3840, 2160, 50f, 0), new float[]{24f, 60f}),
-                new DisplayModeWrapper(
-                        createFakeDisplayMode(5, 3840, 2160, 24f, 0), new float[]{50f, 60f}),
-        });
-
-        testAlternativeRefreshRatesCommon(display, new DisplayModeWrapper[] {
-                new DisplayModeWrapper(
-                        createFakeDisplayMode(0, 1920, 1080, 60f, 0), new float[]{50f}),
-                new DisplayModeWrapper(
-                        createFakeDisplayMode(1, 1920, 1080, 50f, 0), new float[]{60f}),
-                new DisplayModeWrapper(
-                        createFakeDisplayMode(2, 1920, 1080, 24f, 1), new float[0]),
-                new DisplayModeWrapper(
-                        createFakeDisplayMode(3, 3840, 2160, 60f, 2), new float[0]),
-                new DisplayModeWrapper(
-                        createFakeDisplayMode(4, 3840, 2160, 50f, 3), new float[]{24f}),
-                new DisplayModeWrapper(
-                        createFakeDisplayMode(5, 3840, 2160, 24f, 3), new float[]{50f}),
-        });
-
-        testAlternativeRefreshRatesCommon(display, new DisplayModeWrapper[] {
-                new DisplayModeWrapper(
-                        createFakeDisplayMode(0, 1920, 1080, 60f, 0), new float[0]),
-                new DisplayModeWrapper(
-                        createFakeDisplayMode(1, 1920, 1080, 50f, 1), new float[0]),
-                new DisplayModeWrapper(
-                        createFakeDisplayMode(2, 1920, 1080, 24f, 2), new float[0]),
-                new DisplayModeWrapper(
-                        createFakeDisplayMode(3, 3840, 2160, 60f, 3), new float[0]),
-                new DisplayModeWrapper(
-                        createFakeDisplayMode(4, 3840, 2160, 50f, 4), new float[0]),
-                new DisplayModeWrapper(
-                        createFakeDisplayMode(5, 3840, 2160, 24f, 5), new float[0]),
-        });
-    }
-
-    @Test
-    public void testAfterDisplayChange_DefaultDisplayModeIsUpdated() throws Exception {
-        SurfaceControl.DisplayMode displayMode = createFakeDisplayMode(0, 1920, 1080, 60f);
-        SurfaceControl.DisplayMode[] modes =
-                new SurfaceControl.DisplayMode[]{displayMode};
-        FakeDisplay display = new FakeDisplay(PORT_A, modes, 0, displayMode.refreshRate);
-        setUpDisplay(display);
-        updateAvailableDisplays();
-        mAdapter.registerLocked();
-        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
-
-        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
-        assertThat(mListener.changedDisplays).isEmpty();
-
-        DisplayDeviceInfo displayDeviceInfo = mListener.addedDisplays.get(
-                0).getDisplayDeviceInfoLocked();
-
-        assertThat(displayDeviceInfo.supportedModes.length).isEqualTo(modes.length);
-        assertModeIsSupported(displayDeviceInfo.supportedModes, displayMode);
-
-        Display.Mode defaultMode = getModeById(displayDeviceInfo, displayDeviceInfo.defaultModeId);
-        assertThat(matches(defaultMode, displayMode)).isTrue();
-
-        // Set the display mode to an unsupported mode
-        SurfaceControl.DisplayMode displayMode2 = createFakeDisplayMode(1, 1920, 1080, 120f);
-        mListener.addedDisplays.get(0).setUserPreferredDisplayModeLocked(
-                new Display.Mode(displayMode2.width, displayMode2.height,
-                        displayMode2.refreshRate));
-        updateAvailableDisplays();
-        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
-        defaultMode = getModeById(displayDeviceInfo, displayDeviceInfo.defaultModeId);
-        assertThat(matches(defaultMode, displayMode2)).isFalse();
-
-        // Change the display
-        modes = new SurfaceControl.DisplayMode[]{displayMode, displayMode2};
-        display.dynamicInfo.supportedDisplayModes = modes;
-        setUpDisplay(display);
-        mInjector.getTransmitter().sendHotplug(display, /* connected */ true);
-        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
-
-        assertTrue(mListener.traversalRequested);
-        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
-        assertThat(mListener.changedDisplays.size()).isEqualTo(1);
-
-        DisplayDevice displayDevice = mListener.changedDisplays.get(0);
-        displayDevice.applyPendingDisplayDeviceInfoChangesLocked();
-        displayDeviceInfo = mListener.addedDisplays.get(0).getDisplayDeviceInfoLocked();
-
-        assertThat(displayDeviceInfo.supportedModes.length).isEqualTo(modes.length);
-        assertModeIsSupported(displayDeviceInfo.supportedModes, displayMode);
-        assertModeIsSupported(displayDeviceInfo.supportedModes, displayMode2);
-
-        defaultMode = getModeById(displayDeviceInfo, displayDeviceInfo.defaultModeId);
-        assertThat(matches(defaultMode, displayMode2)).isTrue();
-    }
-
-    @Test
-    public void testAfterDisplayChange_DisplayModesAreUpdated() throws Exception {
-        SurfaceControl.DisplayMode displayMode = createFakeDisplayMode(0, 1920, 1080, 60f);
-        SurfaceControl.DisplayMode[] modes =
-                new SurfaceControl.DisplayMode[]{displayMode};
-        FakeDisplay display = new FakeDisplay(PORT_A, modes, 0, displayMode.refreshRate);
-        setUpDisplay(display);
-        updateAvailableDisplays();
-        mAdapter.registerLocked();
-        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
-
-        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
-        assertThat(mListener.changedDisplays).isEmpty();
-
-        DisplayDeviceInfo displayDeviceInfo = mListener.addedDisplays.get(
-                0).getDisplayDeviceInfoLocked();
-
-        assertThat(displayDeviceInfo.supportedModes.length).isEqualTo(modes.length);
-        assertModeIsSupported(displayDeviceInfo.supportedModes, displayMode);
-
-        Display.Mode defaultMode = getModeById(displayDeviceInfo, displayDeviceInfo.defaultModeId);
-        assertThat(matches(defaultMode, displayMode)).isTrue();
-
-        Display.Mode activeMode = getModeById(displayDeviceInfo, displayDeviceInfo.modeId);
-        assertThat(matches(activeMode, displayMode)).isTrue();
-
-        // Change the display
-        SurfaceControl.DisplayMode addedDisplayInfo = createFakeDisplayMode(1, 3840, 2160, 60f);
-        modes = new SurfaceControl.DisplayMode[]{displayMode, addedDisplayInfo};
-        display.dynamicInfo.supportedDisplayModes = modes;
-        display.dynamicInfo.activeDisplayModeId = 1;
-        setUpDisplay(display);
-        mInjector.getTransmitter().sendHotplug(display, /* connected */ true);
-        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
-
-        assertTrue(mListener.traversalRequested);
-        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
-        assertThat(mListener.changedDisplays.size()).isEqualTo(1);
-
-        DisplayDevice displayDevice = mListener.changedDisplays.get(0);
-        displayDevice.applyPendingDisplayDeviceInfoChangesLocked();
-        displayDeviceInfo = displayDevice.getDisplayDeviceInfoLocked();
-
-        assertThat(displayDeviceInfo.supportedModes.length).isEqualTo(modes.length);
-        assertModeIsSupported(displayDeviceInfo.supportedModes, displayMode);
-        assertModeIsSupported(displayDeviceInfo.supportedModes, addedDisplayInfo);
-
-        activeMode = getModeById(displayDeviceInfo, displayDeviceInfo.modeId);
-        assertThat(matches(activeMode, addedDisplayInfo)).isTrue();
-
-        defaultMode = getModeById(displayDeviceInfo, displayDeviceInfo.defaultModeId);
-        assertThat(matches(defaultMode, addedDisplayInfo)).isTrue();
-    }
-
-    @Test
-    public void testAfterDisplayChange_ActiveModeIsUpdated() throws Exception {
-        SurfaceControl.DisplayMode[] modes = new SurfaceControl.DisplayMode[]{
-                createFakeDisplayMode(0, 1920, 1080, 60f),
-                createFakeDisplayMode(1, 1920, 1080, 50f)
-        };
-        FakeDisplay display = new FakeDisplay(PORT_A, modes, /* activeMode */ 0, 60f);
-        setUpDisplay(display);
-        updateAvailableDisplays();
-        mAdapter.registerLocked();
-        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
-
-        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
-        assertThat(mListener.changedDisplays).isEmpty();
-
-        DisplayDeviceInfo displayDeviceInfo = mListener.addedDisplays.get(0)
-                .getDisplayDeviceInfoLocked();
-
-        Display.Mode activeMode = getModeById(displayDeviceInfo, displayDeviceInfo.modeId);
-        assertThat(activeMode.matches(1920, 1080, 60f)).isTrue();
-
-        // Change the display
-        display.dynamicInfo.activeDisplayModeId = 1;
-        setUpDisplay(display);
-        mInjector.getTransmitter().sendHotplug(display, /* connected */ true);
-        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
-
-        assertTrue(mListener.traversalRequested);
-        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
-        assertThat(mListener.changedDisplays.size()).isEqualTo(1);
-
-        DisplayDevice displayDevice = mListener.changedDisplays.get(0);
-        displayDevice.applyPendingDisplayDeviceInfoChangesLocked();
-        displayDeviceInfo = displayDevice.getDisplayDeviceInfoLocked();
-
-        activeMode = getModeById(displayDeviceInfo, displayDeviceInfo.modeId);
-        assertThat(activeMode.matches(1920, 1080, 50f)).isTrue();
-    }
-
-    @Test
-    public void testAfterDisplayChange_RenderFrameRateIsUpdated() throws Exception {
-        SurfaceControl.DisplayMode[] modes = new SurfaceControl.DisplayMode[]{
-                createFakeDisplayMode(0, 1920, 1080, 60f),
-        };
-        FakeDisplay display = new FakeDisplay(PORT_A, modes, /* activeMode */ 0,
-                /* renderFrameRate */30f);
-        setUpDisplay(display);
-        updateAvailableDisplays();
-        mAdapter.registerLocked();
-        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
-
-        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
-        assertThat(mListener.changedDisplays).isEmpty();
-
-        DisplayDeviceInfo displayDeviceInfo = mListener.addedDisplays.get(0)
-                .getDisplayDeviceInfoLocked();
-
-        Display.Mode activeMode = getModeById(displayDeviceInfo, displayDeviceInfo.modeId);
-        assertThat(activeMode.matches(1920, 1080, 60f)).isTrue();
-        assertEquals(Float.floatToIntBits(30f),
-                Float.floatToIntBits(displayDeviceInfo.renderFrameRate));
-
-        // Change the render frame rate
-        display.dynamicInfo.renderFrameRate = 60f;
-        setUpDisplay(display);
-        mInjector.getTransmitter().sendHotplug(display, /* connected */ true);
-        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
-
-        assertTrue(mListener.traversalRequested);
-        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
-        assertThat(mListener.changedDisplays.size()).isEqualTo(1);
-
-        DisplayDevice displayDevice = mListener.changedDisplays.get(0);
-        displayDevice.applyPendingDisplayDeviceInfoChangesLocked();
-        displayDeviceInfo = displayDevice.getDisplayDeviceInfoLocked();
-
-        activeMode = getModeById(displayDeviceInfo, displayDeviceInfo.modeId);
-        assertThat(activeMode.matches(1920, 1080, 60f)).isTrue();
-        assertEquals(Float.floatToIntBits(60f),
-                Float.floatToIntBits(displayDeviceInfo.renderFrameRate));
-    }
-
-    @Test
-    public void testAfterDisplayChange_HdrCapabilitiesAreUpdated() throws Exception {
-        FakeDisplay display = new FakeDisplay(PORT_A);
-        Display.HdrCapabilities initialHdrCapabilities = new Display.HdrCapabilities(new int[0],
-                1000, 1000, 0);
-        display.dynamicInfo.hdrCapabilities = initialHdrCapabilities;
-        setUpDisplay(display);
-        updateAvailableDisplays();
-        mAdapter.registerLocked();
-        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
-
-        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
-        assertThat(mListener.changedDisplays).isEmpty();
-
-        DisplayDeviceInfo displayDeviceInfo = mListener.addedDisplays.get(
-                0).getDisplayDeviceInfoLocked();
-
-        assertThat(displayDeviceInfo.hdrCapabilities).isEqualTo(initialHdrCapabilities);
-
-        // Change the display
-        Display.HdrCapabilities changedHdrCapabilities = new Display.HdrCapabilities(
-                new int[Display.HdrCapabilities.HDR_TYPE_HDR10_PLUS], 1000, 1000, 0);
-        display.dynamicInfo.hdrCapabilities = changedHdrCapabilities;
-        setUpDisplay(display);
-        mInjector.getTransmitter().sendHotplug(display, /* connected */ true);
-        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
-
-        assertTrue(mListener.traversalRequested);
-        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
-        assertThat(mListener.changedDisplays.size()).isEqualTo(1);
-
-        DisplayDevice displayDevice = mListener.changedDisplays.get(0);
-        displayDevice.applyPendingDisplayDeviceInfoChangesLocked();
-        displayDeviceInfo = displayDevice.getDisplayDeviceInfoLocked();
-
-        assertThat(displayDeviceInfo.hdrCapabilities).isEqualTo(changedHdrCapabilities);
-    }
-
-    @Test
-    public void testAfterDisplayChange_AllmSupportIsUpdated() throws Exception {
-        FakeDisplay display = new FakeDisplay(PORT_A);
-        display.dynamicInfo.autoLowLatencyModeSupported = true;
-        setUpDisplay(display);
-        updateAvailableDisplays();
-        mAdapter.registerLocked();
-        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
-
-        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
-        assertThat(mListener.changedDisplays).isEmpty();
-
-        DisplayDeviceInfo displayDeviceInfo = mListener.addedDisplays.get(0)
-                .getDisplayDeviceInfoLocked();
-
-        assertThat(displayDeviceInfo.allmSupported).isTrue();
-
-        // Change the display
-        display.dynamicInfo.autoLowLatencyModeSupported = false;
-        setUpDisplay(display);
-        mInjector.getTransmitter().sendHotplug(display, /* connected */ true);
-        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
-
-        assertTrue(mListener.traversalRequested);
-        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
-        assertThat(mListener.changedDisplays.size()).isEqualTo(1);
-
-        DisplayDevice displayDevice = mListener.changedDisplays.get(0);
-        displayDevice.applyPendingDisplayDeviceInfoChangesLocked();
-        displayDeviceInfo = displayDevice.getDisplayDeviceInfoLocked();
-
-        assertThat(displayDeviceInfo.allmSupported).isFalse();
-    }
-
-    @Test
-    public void testAfterDisplayStateChanges_committedSetAfterState() throws Exception {
-        FakeDisplay display = new FakeDisplay(PORT_A);
-        setUpDisplay(display);
-        updateAvailableDisplays();
-        mAdapter.registerLocked();
-        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
-        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
-        DisplayDevice displayDevice = mListener.addedDisplays.get(0);
-
-        // Turn off.
-        Runnable changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_OFF, 0,
-                0);
-        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
-        assertThat(mListener.changedDisplays.size()).isEqualTo(1);
-        mListener.changedDisplays.clear();
-        assertThat(displayDevice.getDisplayDeviceInfoLocked().state).isEqualTo(Display.STATE_OFF);
-        assertThat(displayDevice.getDisplayDeviceInfoLocked().committedState).isNotEqualTo(
-                Display.STATE_OFF);
-        verify(mSurfaceControlProxy, never()).setDisplayPowerMode(display.token, Display.STATE_OFF);
-
-        // Execute powerstate change.
-        changeStateRunnable.run();
-        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
-
-
-        // Verify that committed triggered a new change event and is set correctly.
-        verify(mSurfaceControlProxy, never()).setDisplayPowerMode(display.token, Display.STATE_OFF);
-        // We expect at least 1 update for the state change, but
-        // could get a second update for the initial brightness change if a nits mapping
-        // is available
-        assertThat(mListener.changedDisplays.size()).isAnyOf(1, 2);
-        assertThat(displayDevice.getDisplayDeviceInfoLocked().state).isEqualTo(Display.STATE_OFF);
-        assertThat(displayDevice.getDisplayDeviceInfoLocked().committedState).isEqualTo(
-                Display.STATE_OFF);
-    }
-
-    @Test
-    public void testAfterDisplayChange_GameContentTypeSupportIsUpdated() throws Exception {
-        FakeDisplay display = new FakeDisplay(PORT_A);
-        display.dynamicInfo.gameContentTypeSupported = true;
-        setUpDisplay(display);
-        updateAvailableDisplays();
-        mAdapter.registerLocked();
-        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
-
-        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
-        assertThat(mListener.changedDisplays).isEmpty();
-
-        DisplayDeviceInfo displayDeviceInfo = mListener.addedDisplays.get(0)
-                .getDisplayDeviceInfoLocked();
-
-        assertThat(displayDeviceInfo.gameContentTypeSupported).isTrue();
-
-        // Change the display
-        display.dynamicInfo.gameContentTypeSupported = false;
-        setUpDisplay(display);
-        mInjector.getTransmitter().sendHotplug(display, /* connected */ true);
-        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
-
-        assertTrue(mListener.traversalRequested);
-        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
-        assertThat(mListener.changedDisplays.size()).isEqualTo(1);
-
-        DisplayDevice displayDevice = mListener.changedDisplays.get(0);
-        displayDevice.applyPendingDisplayDeviceInfoChangesLocked();
-        displayDeviceInfo = displayDevice.getDisplayDeviceInfoLocked();
-
-        assertThat(displayDeviceInfo.gameContentTypeSupported).isFalse();
-    }
-
-    @Test
-    public void testAfterDisplayChange_ColorModesAreUpdated() throws Exception {
-        FakeDisplay display = new FakeDisplay(PORT_A);
-        final int[] initialColorModes = new int[]{Display.COLOR_MODE_BT709};
-        display.dynamicInfo.supportedColorModes = initialColorModes;
-        setUpDisplay(display);
-        updateAvailableDisplays();
-        mAdapter.registerLocked();
-        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
-
-        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
-        assertThat(mListener.changedDisplays).isEmpty();
-
-        DisplayDeviceInfo displayDeviceInfo = mListener.addedDisplays.get(0)
-                .getDisplayDeviceInfoLocked();
-
-        assertThat(displayDeviceInfo.colorMode).isEqualTo(Display.COLOR_MODE_BT709);
-        assertThat(displayDeviceInfo.supportedColorModes).isEqualTo(initialColorModes);
-
-        // Change the display
-        final int[] changedColorModes = new int[]{Display.COLOR_MODE_DEFAULT};
-        display.dynamicInfo.supportedColorModes = changedColorModes;
-        setUpDisplay(display);
-        mInjector.getTransmitter().sendHotplug(display, /* connected */ true);
-        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
-
-        assertTrue(mListener.traversalRequested);
-        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
-        assertThat(mListener.changedDisplays.size()).isEqualTo(1);
-
-        DisplayDevice displayDevice = mListener.changedDisplays.get(0);
-        displayDevice.applyPendingDisplayDeviceInfoChangesLocked();
-        displayDeviceInfo = displayDevice.getDisplayDeviceInfoLocked();
-
-        assertThat(displayDeviceInfo.colorMode).isEqualTo(Display.COLOR_MODE_DEFAULT);
-        assertThat(displayDeviceInfo.supportedColorModes).isEqualTo(changedColorModes);
-    }
-
-    @Test
-    public void testDisplayChange_withStaleDesiredDisplayModeSpecs() throws Exception {
-        SurfaceControl.DisplayMode[] modes = new SurfaceControl.DisplayMode[]{
-                createFakeDisplayMode(0, 1920, 1080, 60f),
-                createFakeDisplayMode(1, 1920, 1080, 50f)
-        };
-        final int activeMode = 0;
-        FakeDisplay display = new FakeDisplay(PORT_A, modes, activeMode, 60f);
-        display.desiredDisplayModeSpecs.defaultMode = 1;
-
-        setUpDisplay(display);
-        updateAvailableDisplays();
-        mAdapter.registerLocked();
-        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
-
-        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
-        DisplayDevice displayDevice = mListener.addedDisplays.get(0);
-
-        int baseModeId = Arrays.stream(displayDevice.getDisplayDeviceInfoLocked().supportedModes)
-                .filter(mode -> mode.getRefreshRate() == 60f)
-                .findFirst()
-                .get()
-                .getModeId();
-
-        displayDevice.setDesiredDisplayModeSpecsLocked(
-                new DisplayModeDirector.DesiredDisplayModeSpecs(
-                        /*baseModeId*/ baseModeId,
-                        /*allowGroupSwitching*/ false,
-                        REFRESH_RATE_RANGES, REFRESH_RATE_RANGES
-                ));
-        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
-        verify(mSurfaceControlProxy).setDesiredDisplayModeSpecs(display.token,
-                new SurfaceControl.DesiredDisplayModeSpecs(
-                        /* baseModeId */ 0,
-                        /* allowGroupSwitching */ false,
-                        REFRESH_RATE_RANGES, REFRESH_RATE_RANGES
-                ));
-
-        // Change the display
-        display.dynamicInfo.supportedDisplayModes = new SurfaceControl.DisplayMode[]{
-                createFakeDisplayMode(2, 1920, 1080, 60f)
-        };
-        display.dynamicInfo.activeDisplayModeId = 2;
-        // SurfaceFlinger can return a stale defaultMode. Make sure this doesn't crash.
-        display.desiredDisplayModeSpecs.defaultMode = 1;
-
-        setUpDisplay(display);
-        mInjector.getTransmitter().sendHotplug(display, /* connected */ true);
-        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
-
-        assertTrue(mListener.traversalRequested);
-
-        displayDevice.applyPendingDisplayDeviceInfoChangesLocked();
-
-        baseModeId = displayDevice.getDisplayDeviceInfoLocked().supportedModes[0].getModeId();
-
-        // The traversal request will call setDesiredDisplayModeSpecsLocked on the display device
-        displayDevice.setDesiredDisplayModeSpecsLocked(
-                new DisplayModeDirector.DesiredDisplayModeSpecs(
-                        /*baseModeId*/ baseModeId,
-                        /*allowGroupSwitching*/ false,
-                        REFRESH_RATE_RANGES, REFRESH_RATE_RANGES
-                ));
-
-        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
-
-        // Verify that this will reapply the desired modes.
-        verify(mSurfaceControlProxy).setDesiredDisplayModeSpecs(display.token,
-                new SurfaceControl.DesiredDisplayModeSpecs(
-                        /* baseModeId */ 2,
-                        /* allowGroupSwitching */ false,
-                        REFRESH_RATE_RANGES, REFRESH_RATE_RANGES
-                ));
-    }
-
-    @Test
-    public void testBacklightAdapter_withSurfaceControlSupport() {
-        final Binder displayToken = new Binder();
-
-        when(mSurfaceControlProxy.getDisplayBrightnessSupport(displayToken)).thenReturn(true);
-
-        // Test as default display
-        BacklightAdapter ba = new BacklightAdapter(displayToken, true /*isDefault*/,
-                mSurfaceControlProxy);
-        ba.setBacklight(0.514f, 100f, 0.614f, 500f);
-        verify(mSurfaceControlProxy).setDisplayBrightness(displayToken, 0.514f, 100f, 0.614f, 500f);
-
-        // Test as not default display
-        BacklightAdapter ba2 = new BacklightAdapter(displayToken, false /*isDefault*/,
-                mSurfaceControlProxy);
-        ba2.setBacklight(0.323f, 101f, 0.723f, 601f);
-        verify(mSurfaceControlProxy).setDisplayBrightness(displayToken, 0.323f, 101f, 0.723f, 601f);
-    }
-
-    @Test
-    public void testBacklightAdapter_withoutSourceControlSupport_defaultDisplay() {
-        final Binder displayToken = new Binder();
-        when(mSurfaceControlProxy.getDisplayBrightnessSupport(displayToken)).thenReturn(false);
-        doReturn(mMockedBacklight).when(mMockedLightsManager)
-                .getLight(LightsManager.LIGHT_ID_BACKLIGHT);
-
-        BacklightAdapter ba = new BacklightAdapter(displayToken, true /*isDefault*/,
-                mSurfaceControlProxy);
-        ba.setBacklight(1f, 1f, 0.123f, 1f);
-        verify(mMockedBacklight).setBrightness(0.123f);
-    }
-
-    @Test
-    public void testBacklightAdapter_withoutSourceControlSupport_nonDefaultDisplay() {
-        final Binder displayToken = new Binder();
-        when(mSurfaceControlProxy.getDisplayBrightnessSupport(displayToken)).thenReturn(false);
-        doReturn(mMockedBacklight).when(mMockedLightsManager)
-                .getLight(LightsManager.LIGHT_ID_BACKLIGHT);
-
-        BacklightAdapter ba = new BacklightAdapter(displayToken, false /*isDefault*/,
-                mSurfaceControlProxy);
-        ba.setBacklight(0.456f, 1f, 1f, 1f);
-
-        // Adapter does not forward any brightness in this case.
-        verify(mMockedBacklight, never()).setBrightness(anyFloat());
-    }
-
-    @Test
-    public void testGetSystemPreferredDisplayMode() throws Exception {
-        SurfaceControl.DisplayMode displayMode1 = createFakeDisplayMode(0, 1920, 1080, 60f);
-        // system preferred mode
-        SurfaceControl.DisplayMode displayMode2 = createFakeDisplayMode(1, 3840, 2160, 60f);
-        // user preferred mode
-        SurfaceControl.DisplayMode displayMode3 = createFakeDisplayMode(2, 1920, 1080, 30f);
-
-        SurfaceControl.DisplayMode[] modes =
-                new SurfaceControl.DisplayMode[]{displayMode1, displayMode2, displayMode3};
-        FakeDisplay display = new FakeDisplay(PORT_A, modes, 0, 1);
-        setUpDisplay(display);
-        updateAvailableDisplays();
-        mAdapter.registerLocked();
-        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
-
-        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
-        assertThat(mListener.changedDisplays).isEmpty();
-
-        DisplayDeviceInfo displayDeviceInfo = mListener.addedDisplays.get(
-                0).getDisplayDeviceInfoLocked();
-        assertThat(displayDeviceInfo.supportedModes.length).isEqualTo(modes.length);
-        Display.Mode defaultMode = getModeById(displayDeviceInfo, displayDeviceInfo.defaultModeId);
-        assertThat(matches(defaultMode, displayMode1)).isTrue();
-
-        // Set the user preferred display mode
-        mListener.addedDisplays.get(0).setUserPreferredDisplayModeLocked(
-                new Display.Mode(
-                        displayMode3.width, displayMode3.height, displayMode3.refreshRate));
-        updateAvailableDisplays();
-        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
-        displayDeviceInfo = mListener.addedDisplays.get(
-                0).getDisplayDeviceInfoLocked();
-        defaultMode = getModeById(displayDeviceInfo, displayDeviceInfo.defaultModeId);
-        assertThat(matches(defaultMode, displayMode3)).isTrue();
-
-        // clear the user preferred mode
-        mListener.addedDisplays.get(0).setUserPreferredDisplayModeLocked(null);
-        updateAvailableDisplays();
-        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
-        displayDeviceInfo = mListener.addedDisplays.get(
-                0).getDisplayDeviceInfoLocked();
-        defaultMode = getModeById(displayDeviceInfo, displayDeviceInfo.defaultModeId);
-        assertThat(matches(defaultMode, displayMode2)).isTrue();
-
-        // Change the display and add new system preferred mode
-        SurfaceControl.DisplayMode addedDisplayInfo = createFakeDisplayMode(3, 2340, 1080, 20f);
-        modes = new SurfaceControl.DisplayMode[]{
-                displayMode1, displayMode2, displayMode3, addedDisplayInfo};
-        display.dynamicInfo.supportedDisplayModes = modes;
-        display.dynamicInfo.preferredBootDisplayMode = 3;
-        setUpDisplay(display);
-        mInjector.getTransmitter().sendHotplug(display, /* connected */ true);
-        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
-
-        assertTrue(mListener.traversalRequested);
-        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
-        assertThat(mListener.changedDisplays.size()).isEqualTo(3);
-
-        DisplayDevice displayDevice = mListener.changedDisplays.get(0);
-        displayDevice.applyPendingDisplayDeviceInfoChangesLocked();
-        displayDeviceInfo = displayDevice.getDisplayDeviceInfoLocked();
-
-        assertThat(displayDeviceInfo.supportedModes.length).isEqualTo(modes.length);
-        assertModeIsSupported(displayDeviceInfo.supportedModes, displayMode1);
-        assertModeIsSupported(displayDeviceInfo.supportedModes, displayMode2);
-        assertModeIsSupported(displayDeviceInfo.supportedModes, addedDisplayInfo);
-
-        assertThat(matches(displayDevice.getSystemPreferredDisplayModeLocked(), addedDisplayInfo))
-                .isTrue();
-    }
-
-    @Test
-    public void testHdrSdrRatio_notifiesOnChange() throws Exception {
-        FakeDisplay display = new FakeDisplay(PORT_A);
-        setUpDisplay(display);
-        updateAvailableDisplays();
-        mAdapter.registerLocked();
-        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
-        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
-        DisplayDevice displayDevice = mListener.addedDisplays.get(0);
-
-        // Turn on / initialize
-        assumeTrue(displayDevice.getDisplayDeviceConfig().hasSdrToHdrRatioSpline());
-        Runnable changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 0,
-                0);
-        changeStateRunnable.run();
-        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
-        mListener.changedDisplays.clear();
-
-        assertEquals(1.0f, displayDevice.getDisplayDeviceInfoLocked().hdrSdrRatio, 0.001f);
-
-        // HDR time!
-        Runnable goHdrRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 1f,
-                0);
-        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
-        // Display state didn't change, no listeners should have happened
-        assertThat(mListener.changedDisplays.size()).isEqualTo(0);
-
-        // Execute hdr change.
-        goHdrRunnable.run();
-        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
-        // Display state didn't change, expect to only get the HDR/SDR ratio change notification
-        assertThat(mListener.changedDisplays.size()).isEqualTo(1);
-
-        final float expectedRatio = DISPLAY_RANGE_NITS[1] / DISPLAY_RANGE_NITS[0];
-        assertEquals(expectedRatio, displayDevice.getDisplayDeviceInfoLocked().hdrSdrRatio,
-                0.001f);
-    }
-
-    private void assertDisplayDpi(DisplayDeviceInfo info, int expectedPort,
-                                  float expectedXdpi,
-                                  float expectedYDpi,
-                                  int expectedDensityDpi) {
-        final DisplayAddress.Physical physical = (DisplayAddress.Physical) info.address;
-        assertNotNull(physical);
-        assertEquals(expectedPort, physical.getPort());
-        assertEquals(expectedXdpi, info.xDpi, 0.01);
-        assertEquals(expectedYDpi, info.yDpi, 0.01);
-        assertEquals(expectedDensityDpi, info.densityDpi);
-    }
-
-    private Display.Mode getModeById(DisplayDeviceInfo displayDeviceInfo, int modeId) {
-        return Arrays.stream(displayDeviceInfo.supportedModes)
-                .filter(mode -> mode.getModeId() == modeId)
-                .findFirst()
-                .get();
-    }
-
-    private void assertModeIsSupported(Display.Mode[] supportedModes,
-            SurfaceControl.DisplayMode mode) {
-        assertThat(Arrays.stream(supportedModes).anyMatch(
-                x -> x.matches(mode.width, mode.height, mode.refreshRate))).isTrue();
-    }
-
-    private void assertModeIsSupported(Display.Mode[] supportedModes,
-            SurfaceControl.DisplayMode mode, float[] alternativeRefreshRates) {
-        float[] sortedAlternativeRates =
-                Arrays.copyOf(alternativeRefreshRates, alternativeRefreshRates.length);
-        Arrays.sort(sortedAlternativeRates);
-
-        String message = "Expected " + mode + " with alternativeRefreshRates = "
-                + Arrays.toString(alternativeRefreshRates) + " to be in list of supported modes = "
-                + Arrays.toString(supportedModes);
-        Truth.assertWithMessage(message)
-            .that(Arrays.stream(supportedModes)
-                .anyMatch(x -> x.matches(mode.width, mode.height, mode.refreshRate)
-                        && Arrays.equals(x.getAlternativeRefreshRates(), sortedAlternativeRates)))
-                .isTrue();
-    }
-
-
-    private static class FakeDisplay {
-        public final DisplayAddress.Physical address;
-        public final IBinder token = new Binder();
-        public final SurfaceControl.StaticDisplayInfo info;
-        public SurfaceControl.DynamicDisplayInfo dynamicInfo =
-                new SurfaceControl.DynamicDisplayInfo();
-        {
-            dynamicInfo.supportedColorModes = new int[]{ Display.COLOR_MODE_DEFAULT };
-            dynamicInfo.hdrCapabilities = new Display.HdrCapabilities(new int[0],
-                    1000, 1000, 0);
-        }
-
-        public SurfaceControl.DesiredDisplayModeSpecs desiredDisplayModeSpecs =
-                new SurfaceControl.DesiredDisplayModeSpecs(
-                        /* defaultMode */ 0,
-                        /* allowGroupSwitching */ false,
-                        REFRESH_RATE_RANGES, REFRESH_RATE_RANGES
-                );
-
-        private FakeDisplay(int port) {
-            address = createDisplayAddress(port);
-            info = createFakeDisplayInfo();
-            dynamicInfo.supportedDisplayModes = new SurfaceControl.DisplayMode[]{
-                    createFakeDisplayMode(0, 800, 600, 60f)
-            };
-            dynamicInfo.activeDisplayModeId = 0;
-        }
-
-        private FakeDisplay(int port, SurfaceControl.DisplayMode[] modes, int activeMode,
-                float renderFrameRate) {
-            address = createDisplayAddress(port);
-            info = createFakeDisplayInfo();
-            dynamicInfo.supportedDisplayModes = modes;
-            dynamicInfo.activeDisplayModeId = activeMode;
-            dynamicInfo.renderFrameRate = renderFrameRate;
-        }
-
-        private FakeDisplay(int port, SurfaceControl.DisplayMode[] modes, int activeMode,
-                int preferredMode) {
-            address = createDisplayAddress(port);
-            info = createFakeDisplayInfo();
-            dynamicInfo.supportedDisplayModes = modes;
-            dynamicInfo.activeDisplayModeId = activeMode;
-            dynamicInfo.preferredBootDisplayMode = preferredMode;
-        }
-
-    }
-
-    private void setUpDisplay(FakeDisplay display) {
-        mAddresses.add(display.address);
-        when(mSurfaceControlProxy.getPhysicalDisplayToken(display.address.getPhysicalDisplayId()))
-                .thenReturn(display.token);
-        when(mSurfaceControlProxy.getStaticDisplayInfo(display.address.getPhysicalDisplayId()))
-                .thenReturn(display.info);
-        when(mSurfaceControlProxy.getDynamicDisplayInfo(display.address.getPhysicalDisplayId()))
-                .thenReturn(display.dynamicInfo);
-        when(mSurfaceControlProxy.getDesiredDisplayModeSpecs(display.token))
-                .thenReturn(display.desiredDisplayModeSpecs);
-    }
-
-    private void updateAvailableDisplays() {
-        long[] ids = new long[mAddresses.size()];
-        int i = 0;
-        for (DisplayAddress.Physical address : mAddresses) {
-            ids[i] = address.getPhysicalDisplayId();
-            i++;
-        }
-        when(mSurfaceControlProxy.getPhysicalDisplayIds()).thenReturn(ids);
-    }
-
-    private static DisplayAddress.Physical createDisplayAddress(int port) {
-        return DisplayAddress.fromPortAndModel(port, DISPLAY_MODEL);
-    }
-
-    private static SurfaceControl.StaticDisplayInfo createFakeDisplayInfo() {
-        final SurfaceControl.StaticDisplayInfo info = new SurfaceControl.StaticDisplayInfo();
-        info.density = 100;
-        return info;
-    }
-
-    private static SurfaceControl.DisplayMode createFakeDisplayMode(int id, int width, int height,
-            float refreshRate) {
-        return createFakeDisplayMode(id, width, height, refreshRate, /* group */ 0);
-    }
-
-    private static SurfaceControl.DisplayMode createFakeDisplayMode(int id, int width, int height,
-            float refreshRate, int group) {
-        final SurfaceControl.DisplayMode mode = new SurfaceControl.DisplayMode();
-        mode.id = id;
-        mode.width = width;
-        mode.height = height;
-        mode.refreshRate = refreshRate;
-        mode.xDpi = 100;
-        mode.yDpi = 100;
-        mode.group = group;
-        mode.supportedHdrTypes = HDR_TYPES;
-        return mode;
-    }
-
-    private static void waitForHandlerToComplete(Handler handler, long waitTimeMs)
-            throws InterruptedException {
-        final CountDownLatch fence = new CountDownLatch(1);
-        handler.post(fence::countDown);
-        assertTrue(fence.await(waitTimeMs, TimeUnit.MILLISECONDS));
-    }
-
-    private class HotplugTransmitter {
-        private final Handler mHandler;
-        private final LocalDisplayAdapter.DisplayEventListener mListener;
-
-        HotplugTransmitter(Looper looper, LocalDisplayAdapter.DisplayEventListener listener) {
-            mHandler = new Handler(looper);
-            mListener = listener;
-        }
-
-        public void sendHotplug(FakeDisplay display, boolean connected)
-                throws InterruptedException {
-            mHandler.post(() -> mListener.onHotplug(/* timestampNanos = */ 0,
-                    display.address.getPhysicalDisplayId(), connected));
-            waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
-        }
-    }
-
-    private class Injector extends LocalDisplayAdapter.Injector {
-        private HotplugTransmitter mTransmitter;
-        @Override
-        public void setDisplayEventListenerLocked(Looper looper,
-                LocalDisplayAdapter.DisplayEventListener listener) {
-            mTransmitter = new HotplugTransmitter(looper, listener);
-        }
-
-        public HotplugTransmitter getTransmitter() {
-            return mTransmitter;
-        }
-
-        @Override
-        public LocalDisplayAdapter.SurfaceControlProxy getSurfaceControlProxy() {
-            return mSurfaceControlProxy;
-        }
-    }
-
-    private class TestListener implements DisplayAdapter.Listener {
-        public ArrayList<DisplayDevice> addedDisplays = new ArrayList<>();
-        public ArrayList<DisplayDevice> changedDisplays = new ArrayList<>();
-        public boolean traversalRequested = false;
-
-        @Override
-        public void onDisplayDeviceEvent(DisplayDevice device, int event) {
-            if (event == DisplayAdapter.DISPLAY_DEVICE_EVENT_ADDED) {
-                addedDisplays.add(device);
-            } else if (event == DisplayAdapter.DISPLAY_DEVICE_EVENT_CHANGED) {
-                changedDisplays.add(device);
-            }
-        }
-
-        @Override
-        public void onTraversalRequested() {
-            traversalRequested = true;
-        }
-    }
-
-    private TypedArray createFloatTypedArray(float[] vals) {
-        TypedArray mockArray = mock(TypedArray.class);
-        when(mockArray.length()).thenAnswer(invocation -> {
-            return vals.length;
-        });
-        when(mockArray.getFloat(anyInt(), anyFloat())).thenAnswer(invocation -> {
-            final float def = (float) invocation.getArguments()[1];
-            if (vals == null) {
-                return def;
-            }
-            int idx = (int) invocation.getArguments()[0];
-            if (idx >= 0 && idx < vals.length) {
-                return vals[idx];
-            } else {
-                return def;
-            }
-        });
-        return mockArray;
-    }
-
-    private boolean matches(Display.Mode a, SurfaceControl.DisplayMode b) {
-        return a.getPhysicalWidth() == b.width && a.getPhysicalHeight() == b.height
-                && Float.floatToIntBits(a.getRefreshRate()) == Float.floatToIntBits(b.refreshRate);
-    }
-}
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/OWNERS b/services/tests/mockingservicestests/src/com/android/server/display/OWNERS
deleted file mode 100644
index 6ce1ee4..0000000
--- a/services/tests/mockingservicestests/src/com/android/server/display/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-include /services/core/java/com/android/server/display/OWNERS
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java
deleted file mode 100644
index 3faf394..0000000
--- a/services/tests/mockingservicestests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java
+++ /dev/null
@@ -1,307 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.color;
-
-import static android.view.Display.DEFAULT_DISPLAY;
-
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
-
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import static org.junit.Assert.assertArrayEquals;
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.hardware.display.DisplayManagerInternal;
-import android.os.Binder;
-import android.os.IBinder;
-import android.view.SurfaceControl;
-import android.view.SurfaceControl.CieXyz;
-import android.view.SurfaceControl.DisplayPrimaries;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.dx.mockito.inline.extended.ExtendedMockito;
-import com.android.internal.R;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoSession;
-import org.mockito.quality.Strictness;
-
-@RunWith(AndroidJUnit4.class)
-public class DisplayWhiteBalanceTintControllerTest {
-    @Mock
-    private Context mMockedContext;
-    @Mock
-    private Resources mMockedResources;
-    @Mock
-    private DisplayManagerInternal mDisplayManagerInternal;
-
-    private MockitoSession mSession;
-    private Resources mResources;
-    IBinder mDisplayToken;
-    DisplayWhiteBalanceTintController mDisplayWhiteBalanceTintController;
-
-    @Before
-    public void setUp() {
-        mSession = ExtendedMockito.mockitoSession()
-                .initMocks(this)
-                .mockStatic(SurfaceControl.class)
-                .strictness(Strictness.LENIENT)
-                .startMocking();
-
-        mResources = InstrumentationRegistry.getContext().getResources();
-        // These Resources are common to all tests.
-        doReturn(4000)
-            .when(mMockedResources)
-            .getInteger(R.integer.config_displayWhiteBalanceColorTemperatureMin);
-        doReturn(8000)
-            .when(mMockedResources)
-            .getInteger(R.integer.config_displayWhiteBalanceColorTemperatureMax);
-        doReturn(6500)
-            .when(mMockedResources)
-            .getInteger(R.integer.config_displayWhiteBalanceColorTemperatureDefault);
-        doReturn(new String[] {"0.950456", "1.000000", "1.089058"})
-                .when(mMockedResources)
-                .getStringArray(R.array.config_displayWhiteBalanceDisplayNominalWhite);
-        doReturn(6500)
-                .when(mMockedResources)
-                .getInteger(R.integer.config_displayWhiteBalanceDisplayNominalWhiteCct);
-        doReturn(new int[] {0})
-                .when(mMockedResources)
-                .getIntArray(R.array.config_displayWhiteBalanceDisplaySteps);
-        doReturn(new int[] {20})
-                .when(mMockedResources)
-                .getIntArray(R.array.config_displayWhiteBalanceDisplayRangeMinimums);
-
-        doReturn(mMockedResources).when(mMockedContext).getResources();
-
-        mDisplayToken = new Binder();
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        if (mSession != null) {
-            mSession.finishMocking();
-        }
-    }
-
-    /**
-     * Setup should succeed when SurfaceControl setup results in a valid color transform.
-     */
-    @Test
-    public void displayWhiteBalance_setupWithSurfaceControl() {
-        // Make SurfaceControl return sRGB primaries
-        DisplayPrimaries displayPrimaries = new DisplayPrimaries();
-        displayPrimaries.red = new CieXyz();
-        displayPrimaries.red.X = 0.412315f;
-        displayPrimaries.red.Y = 0.212600f;
-        displayPrimaries.red.Z = 0.019327f;
-        displayPrimaries.green = new CieXyz();
-        displayPrimaries.green.X = 0.357600f;
-        displayPrimaries.green.Y = 0.715200f;
-        displayPrimaries.green.Z = 0.119200f;
-        displayPrimaries.blue = new CieXyz();
-        displayPrimaries.blue.X = 0.180500f;
-        displayPrimaries.blue.Y = 0.072200f;
-        displayPrimaries.blue.Z = 0.950633f;
-        displayPrimaries.white = new CieXyz();
-        displayPrimaries.white.X = 0.950456f;
-        displayPrimaries.white.Y = 1.000000f;
-        displayPrimaries.white.Z = 1.089058f;
-        when(mDisplayManagerInternal.getDisplayNativePrimaries(DEFAULT_DISPLAY))
-                .thenReturn(displayPrimaries);
-
-        setUpTintController();
-        assertWithMessage("Setup with valid SurfaceControl failed")
-                .that(mDisplayWhiteBalanceTintController.mSetUp)
-                .isTrue();
-    }
-
-    /**
-     * Setup should fail when SurfaceControl setup results in an invalid color transform.
-     */
-    @Test
-    public void displayWhiteBalance_setupWithInvalidSurfaceControlData() {
-        // Make SurfaceControl return invalid display primaries
-        DisplayPrimaries displayPrimaries = new DisplayPrimaries();
-        displayPrimaries.red = new CieXyz();
-        displayPrimaries.green = new CieXyz();
-        displayPrimaries.blue = new CieXyz();
-        displayPrimaries.white = new CieXyz();
-        when(mDisplayManagerInternal.getDisplayNativePrimaries(DEFAULT_DISPLAY))
-                .thenReturn(displayPrimaries);
-
-        setUpTintController();
-        assertWithMessage("Setup with invalid SurfaceControl succeeded")
-                .that(mDisplayWhiteBalanceTintController.mSetUp)
-                .isFalse();
-    }
-
-    /**
-     * Setup should succeed when SurfaceControl setup fails and Resources result in a valid color
-     * transform.
-     */
-    @Test
-    public void displayWhiteBalance_setupWithResources() {
-        // Use default (valid) Resources
-        doReturn(mResources.getStringArray(R.array.config_displayWhiteBalanceDisplayPrimaries))
-            .when(mMockedResources)
-            .getStringArray(R.array.config_displayWhiteBalanceDisplayPrimaries);
-        // Make SurfaceControl setup fail
-        when(mDisplayManagerInternal.getDisplayNativePrimaries(DEFAULT_DISPLAY)).thenReturn(null);
-
-        setUpTintController();
-        assertWithMessage("Setup with valid Resources failed")
-                .that(mDisplayWhiteBalanceTintController.mSetUp)
-                .isTrue();
-    }
-
-    /**
-     * Setup should fail when SurfaceControl setup fails and Resources result in an invalid color
-     * transform.
-     */
-    @Test
-    public void displayWhiteBalance_setupWithInvalidResources() {
-        // Use Resources with invalid color data
-        doReturn(new String[] {
-                "0", "0", "0", // Red X, Y, Z
-                "0", "0", "0", // Green X, Y, Z
-                "0", "0", "0", // Blue X, Y, Z
-                "0", "0", "0", // White X, Y, Z
-            })
-            .when(mMockedResources)
-            .getStringArray(R.array.config_displayWhiteBalanceDisplayPrimaries);
-        // Make SurfaceControl setup fail
-        when(mDisplayManagerInternal.getDisplayNativePrimaries(DEFAULT_DISPLAY)).thenReturn(null);
-
-        setUpTintController();
-        assertWithMessage("Setup with invalid Resources succeeded")
-                .that(mDisplayWhiteBalanceTintController.mSetUp)
-                .isFalse();
-    }
-
-    /**
-     * Matrix should match the precalculated one for given cct and display primaries.
-     */
-    @Test
-    public void displayWhiteBalance_getAndSetMatrix_validateTransformMatrix() {
-        DisplayPrimaries displayPrimaries = new DisplayPrimaries();
-        displayPrimaries.red = new CieXyz();
-        displayPrimaries.red.X = 0.412315f;
-        displayPrimaries.red.Y = 0.212600f;
-        displayPrimaries.red.Z = 0.019327f;
-        displayPrimaries.green = new CieXyz();
-        displayPrimaries.green.X = 0.357600f;
-        displayPrimaries.green.Y = 0.715200f;
-        displayPrimaries.green.Z = 0.119200f;
-        displayPrimaries.blue = new CieXyz();
-        displayPrimaries.blue.X = 0.180500f;
-        displayPrimaries.blue.Y = 0.072200f;
-        displayPrimaries.blue.Z = 0.950633f;
-        displayPrimaries.white = new CieXyz();
-        displayPrimaries.white.X = 0.950456f;
-        displayPrimaries.white.Y = 1.000000f;
-        displayPrimaries.white.Z = 1.089058f;
-        when(mDisplayManagerInternal.getDisplayNativePrimaries(DEFAULT_DISPLAY))
-                .thenReturn(displayPrimaries);
-
-        setUpTintController();
-        assertWithMessage("Setup with valid SurfaceControl failed")
-                .that(mDisplayWhiteBalanceTintController.mSetUp)
-                .isTrue();
-
-        final int cct = 6500;
-        mDisplayWhiteBalanceTintController.setMatrix(cct);
-        mDisplayWhiteBalanceTintController.setAppliedCct(
-                mDisplayWhiteBalanceTintController.getTargetCct());
-
-        assertWithMessage("Failed to set temperature")
-                .that(mDisplayWhiteBalanceTintController.mCurrentColorTemperature)
-                .isEqualTo(cct);
-        float[] matrixDwb = mDisplayWhiteBalanceTintController.getMatrix();
-        final float[] expectedMatrixDwb = {
-            0.971848f,   -0.001421f,  0.000491f, 0.0f,
-            0.028193f,    0.945798f,  0.003207f, 0.0f,
-            -0.000042f,  -0.000989f,  0.988659f, 0.0f,
-            0.0f,         0.0f,       0.0f,      1.0f
-        };
-        assertArrayEquals("Unexpected DWB matrix", expectedMatrixDwb, matrixDwb,
-            1e-6f /* tolerance */);
-    }
-
-    /**
-     * Matrix should match the precalculated one for given cct and display primaries.
-     */
-    @Test
-    public void displayWhiteBalance_targetApplied_validateTransformMatrix() {
-        DisplayPrimaries displayPrimaries = new DisplayPrimaries();
-        displayPrimaries.red = new CieXyz();
-        displayPrimaries.red.X = 0.412315f;
-        displayPrimaries.red.Y = 0.212600f;
-        displayPrimaries.red.Z = 0.019327f;
-        displayPrimaries.green = new CieXyz();
-        displayPrimaries.green.X = 0.357600f;
-        displayPrimaries.green.Y = 0.715200f;
-        displayPrimaries.green.Z = 0.119200f;
-        displayPrimaries.blue = new CieXyz();
-        displayPrimaries.blue.X = 0.180500f;
-        displayPrimaries.blue.Y = 0.072200f;
-        displayPrimaries.blue.Z = 0.950633f;
-        displayPrimaries.white = new CieXyz();
-        displayPrimaries.white.X = 0.950456f;
-        displayPrimaries.white.Y = 1.000000f;
-        displayPrimaries.white.Z = 1.089058f;
-        when(mDisplayManagerInternal.getDisplayNativePrimaries(DEFAULT_DISPLAY))
-                .thenReturn(displayPrimaries);
-
-        setUpTintController();
-        assertWithMessage("Setup with valid SurfaceControl failed")
-                .that(mDisplayWhiteBalanceTintController.mSetUp)
-                .isTrue();
-
-        final int cct = 6500;
-        mDisplayWhiteBalanceTintController.setTargetCct(cct);
-        final float[] matrixDwb = mDisplayWhiteBalanceTintController.computeMatrixForCct(cct);
-        mDisplayWhiteBalanceTintController.setAppliedCct(cct);
-
-        assertWithMessage("Failed to set temperature")
-                .that(mDisplayWhiteBalanceTintController.mCurrentColorTemperature)
-                .isEqualTo(cct);
-        final float[] expectedMatrixDwb = {
-                0.971848f,   -0.001421f,  0.000491f, 0.0f,
-                0.028193f,    0.945798f,  0.003207f, 0.0f,
-                -0.000042f,  -0.000989f,  0.988659f, 0.0f,
-                0.0f,         0.0f,       0.0f,      1.0f
-        };
-        assertArrayEquals("Unexpected DWB matrix", expectedMatrixDwb, matrixDwb,
-                1e-6f /* tolerance */);
-    }
-
-    private void setUpTintController() {
-        mDisplayWhiteBalanceTintController = new DisplayWhiteBalanceTintController(
-                mDisplayManagerInternal);
-        mDisplayWhiteBalanceTintController.setUp(mMockedContext, true);
-        mDisplayWhiteBalanceTintController.setActivated(true);
-    }
-}
diff --git a/services/tests/mockingservicestests/src/com/android/server/dreams/DreamManagerServiceMockingTest.java b/services/tests/mockingservicestests/src/com/android/server/dreams/DreamManagerServiceMockingTest.java
new file mode 100644
index 0000000..c02cbd1
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/dreams/DreamManagerServiceMockingTest.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.dreams;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManagerInternal;
+import android.content.ContextWrapper;
+import android.content.pm.UserInfo;
+import android.content.res.Resources;
+import android.os.PowerManagerInternal;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.Settings;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.server.LocalServices;
+import com.android.server.SystemService;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+/**
+ * Collection of tests for exercising the {@link DreamManagerService} lifecycle.
+ */
+public class DreamManagerServiceMockingTest {
+    private ContextWrapper mContextSpy;
+    private Resources mResourcesSpy;
+
+    @Mock
+    private ActivityManagerInternal mActivityManagerInternalMock;
+
+    @Mock
+    private PowerManagerInternal mPowerManagerInternalMock;
+
+    @Mock
+    private UserManager mUserManagerMock;
+
+    private MockitoSession mMockitoSession;
+
+    private static <T> void addLocalServiceMock(Class<T> clazz, T mock) {
+        LocalServices.removeServiceForTest(clazz);
+        LocalServices.addService(clazz, mock);
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getContext()));
+        mResourcesSpy = spy(mContextSpy.getResources());
+        when(mContextSpy.getResources()).thenReturn(mResourcesSpy);
+
+        addLocalServiceMock(ActivityManagerInternal.class, mActivityManagerInternalMock);
+        addLocalServiceMock(PowerManagerInternal.class, mPowerManagerInternalMock);
+
+        when(mContextSpy.getSystemService(UserManager.class)).thenReturn(mUserManagerMock);
+        mMockitoSession = mockitoSession()
+                .initMocks(this)
+                .strictness(Strictness.LENIENT)
+                .mockStatic(Settings.Secure.class)
+                .startMocking();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mMockitoSession.finishMocking();
+        LocalServices.removeServiceForTest(ActivityManagerInternal.class);
+        LocalServices.removeServiceForTest(PowerManagerInternal.class);
+    }
+
+    private DreamManagerService createService() {
+        return new DreamManagerService(mContextSpy);
+    }
+
+    @Test
+    public void testSettingsQueryUserChange() {
+        final DreamManagerService service = createService();
+
+        final SystemService.TargetUser from =
+                new SystemService.TargetUser(mock(UserInfo.class));
+        final SystemService.TargetUser to =
+                new SystemService.TargetUser(mock(UserInfo.class));
+
+        service.onUserSwitching(from, to);
+
+        verify(() -> Settings.Secure.getIntForUser(any(),
+                eq(Settings.Secure.SCREENSAVER_ENABLED),
+                anyInt(),
+                eq(UserHandle.USER_CURRENT)));
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/altitude/AltitudeConverterTest.java b/services/tests/mockingservicestests/src/com/android/server/location/altitude/AltitudeConverterTest.java
index 0d9aeb5..8d9a6c5 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/altitude/AltitudeConverterTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/altitude/AltitudeConverterTest.java
@@ -49,10 +49,10 @@
 
     @Test
     public void testAddMslAltitudeToLocation_expectedBehavior() throws IOException {
-        // Interpolates between bffffc, 955554, and 000004.
+        // Interpolates in boundary region (bffffc).
         Location location = new Location("");
-        location.setLatitude(-35.246789);
-        location.setLongitude(-44.962683);
+        location.setLatitude(-35.334815);
+        location.setLongitude(-45);
         location.setAltitude(-1);
         location.setVerticalAccuracyMeters(1);
         // Requires data to be loaded from raw assets.
@@ -61,43 +61,27 @@
         assertThat(location.hasMslAltitudeAccuracy()).isFalse();
         // Loads data from raw assets.
         mAltitudeConverter.addMslAltitudeToLocation(mContext, location);
-        assertThat(location.getMslAltitudeMeters()).isWithin(2).of(5.1076);
+        assertThat(location.getMslAltitudeMeters()).isWithin(2).of(5.0622);
         assertThat(location.getMslAltitudeAccuracyMeters()).isGreaterThan(1f);
         assertThat(location.getMslAltitudeAccuracyMeters()).isLessThan(1.1f);
 
-        // Again interpolates between bffffc, 955554, and 000004.
+        // Again interpolates at same location to assert no loading from raw assets. Also checks
+        // behavior w.r.t. invalid vertical accuracy.
         location = new Location("");
-        location.setLatitude(-35.246789);
-        location.setLongitude(-44.962683);
-        location.setAltitude(-1);
-        location.setVerticalAccuracyMeters(1);
-        // Requires no data to be loaded from raw assets.
-        assertThat(mAltitudeConverter.addMslAltitudeToLocation(location)).isTrue();
-        assertThat(location.getMslAltitudeMeters()).isWithin(2).of(5.1076);
-        assertThat(location.getMslAltitudeAccuracyMeters()).isGreaterThan(1f);
-        assertThat(location.getMslAltitudeAccuracyMeters()).isLessThan(1.1f);
-        // Results in same outcome.
-        mAltitudeConverter.addMslAltitudeToLocation(mContext, location);
-        assertThat(location.getMslAltitudeMeters()).isWithin(2).of(5.1076);
-        assertThat(location.getMslAltitudeAccuracyMeters()).isGreaterThan(1f);
-        assertThat(location.getMslAltitudeAccuracyMeters()).isLessThan(1.1f);
-
-        // Interpolate between 955554, 000004, 00000c, and 95554c - no vertical accuracy.
-        location = new Location("");
-        location.setLatitude(-35.176383);
-        location.setLongitude(-44.962683);
+        location.setLatitude(-35.334815);
+        location.setLongitude(-45);
         location.setAltitude(-1);
         location.setVerticalAccuracyMeters(-1); // Invalid vertical accuracy
         // Requires no data to be loaded from raw assets.
         assertThat(mAltitudeConverter.addMslAltitudeToLocation(location)).isTrue();
-        assertThat(location.getMslAltitudeMeters()).isWithin(2).of(5.1919);
+        assertThat(location.getMslAltitudeMeters()).isWithin(2).of(5.0622);
         assertThat(location.hasMslAltitudeAccuracy()).isFalse();
         // Results in same outcome.
         mAltitudeConverter.addMslAltitudeToLocation(mContext, location);
-        assertThat(location.getMslAltitudeMeters()).isWithin(2).of(5.1919);
+        assertThat(location.getMslAltitudeMeters()).isWithin(2).of(5.0622);
         assertThat(location.hasMslAltitudeAccuracy()).isFalse();
 
-        // Interpolates somewhere else more interesting, i.e., Hawaii.
+        // Interpolates out of boundary region, e.g., Hawaii.
         location = new Location("");
         location.setLatitude(19.545519);
         location.setLongitude(-155.998774);
@@ -112,6 +96,29 @@
         assertThat(location.getMslAltitudeMeters()).isWithin(2).of(-19.2359);
         assertThat(location.getMslAltitudeAccuracyMeters()).isGreaterThan(1f);
         assertThat(location.getMslAltitudeAccuracyMeters()).isLessThan(1.1f);
+
+        // The following round out test coverage for boundary regions.
+
+        location = new Location("");
+        location.setLatitude(-35.229154);
+        location.setLongitude(44.925335);
+        location.setAltitude(-1);
+        mAltitudeConverter.addMslAltitudeToLocation(mContext, location);
+        assertThat(location.getMslAltitudeMeters()).isWithin(2).of(-34.1913);
+
+        location = new Location("");
+        location.setLatitude(-35.334815);
+        location.setLongitude(45);
+        location.setAltitude(-1);
+        mAltitudeConverter.addMslAltitudeToLocation(mContext, location);
+        assertThat(location.getMslAltitudeMeters()).isWithin(2).of(-34.2258);
+
+        location = new Location("");
+        location.setLatitude(35.229154);
+        location.setLongitude(-44.925335);
+        location.setAltitude(-1);
+        mAltitudeConverter.addMslAltitudeToLocation(mContext, location);
+        assertThat(location.getMslAltitudeMeters()).isWithin(2).of(-11.0691);
     }
 
     @Test
@@ -122,15 +129,15 @@
 
         location.setLatitude(Double.NaN);
         assertThrows(IllegalArgumentException.class,
-                () -> mAltitudeConverter.addMslAltitudeToLocation(location));
+                () -> mAltitudeConverter.addMslAltitudeToLocation(mContext, location));
 
         location.setLatitude(91);
         assertThrows(IllegalArgumentException.class,
-                () -> mAltitudeConverter.addMslAltitudeToLocation(location));
+                () -> mAltitudeConverter.addMslAltitudeToLocation(mContext, location));
 
         location.setLatitude(-91);
         assertThrows(IllegalArgumentException.class,
-                () -> mAltitudeConverter.addMslAltitudeToLocation(location));
+                () -> mAltitudeConverter.addMslAltitudeToLocation(mContext, location));
     }
 
     @Test
@@ -141,15 +148,15 @@
 
         location.setLongitude(Double.NaN);
         assertThrows(IllegalArgumentException.class,
-                () -> mAltitudeConverter.addMslAltitudeToLocation(location));
+                () -> mAltitudeConverter.addMslAltitudeToLocation(mContext, location));
 
         location.setLongitude(181);
         assertThrows(IllegalArgumentException.class,
-                () -> mAltitudeConverter.addMslAltitudeToLocation(location));
+                () -> mAltitudeConverter.addMslAltitudeToLocation(mContext, location));
 
         location.setLongitude(-181);
         assertThrows(IllegalArgumentException.class,
-                () -> mAltitudeConverter.addMslAltitudeToLocation(location));
+                () -> mAltitudeConverter.addMslAltitudeToLocation(mContext, location));
     }
 
     @Test
@@ -159,14 +166,14 @@
         location.setLongitude(-44.962683);
 
         assertThrows(IllegalArgumentException.class,
-                () -> mAltitudeConverter.addMslAltitudeToLocation(location));
+                () -> mAltitudeConverter.addMslAltitudeToLocation(mContext, location));
 
         location.setAltitude(Double.NaN);
         assertThrows(IllegalArgumentException.class,
-                () -> mAltitudeConverter.addMslAltitudeToLocation(location));
+                () -> mAltitudeConverter.addMslAltitudeToLocation(mContext, location));
 
         location.setAltitude(Double.POSITIVE_INFINITY);
         assertThrows(IllegalArgumentException.class,
-                () -> mAltitudeConverter.addMslAltitudeToLocation(location));
+                () -> mAltitudeConverter.addMslAltitudeToLocation(mContext, location));
     }
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeEmergencyHelper.java b/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeEmergencyHelper.java
index 2cf57da..7ee411b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeEmergencyHelper.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeEmergencyHelper.java
@@ -27,6 +27,7 @@
 
     public void setInEmergency(boolean inEmergency) {
         mInEmergency = inEmergency;
+        dispatchEmergencyStateChanged();
     }
 
     @Override
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
index 7dc1935..293003d 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
@@ -80,8 +80,12 @@
 import android.os.RemoteException;
 import android.os.WorkSource;
 import android.platform.test.annotations.Presubmit;
+import android.provider.DeviceConfig;
+import android.provider.Settings;
 import android.util.Log;
 
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.filters.MediumTest;
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
@@ -174,6 +178,8 @@
         doReturn(mResources).when(mContext).getResources();
         doReturn(mPackageManager).when(mContext).getPackageManager();
         doReturn(mPowerManager).when(mContext).getSystemService(PowerManager.class);
+        doReturn(ApplicationProvider.getApplicationContext()).when(
+                mContext).getApplicationContext();
         doReturn(mWakeLock).when(mPowerManager).newWakeLock(anyInt(), anyString());
         doReturn(PackageManager.PERMISSION_DENIED)
                 .when(mContext)
@@ -210,6 +216,8 @@
 
     @After
     public void tearDown() throws Exception {
+        DeviceConfig.resetToDefaults(Settings.RESET_MODE_PACKAGE_DEFAULTS,
+                DeviceConfig.NAMESPACE_LOCATION);
         LocalServices.removeServiceForTest(LocationManagerInternal.class);
 
         // some test failures may leave the fg thread stuck, interrupt until we get out of it
@@ -1339,6 +1347,144 @@
         assertThat(mManager.isVisibleToCaller()).isFalse();
     }
 
+    @MediumTest
+    @Test
+    public void testEnableMsl_expectedBehavior() throws Exception {
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_LOCATION,
+                "enable_location_provider_manager_msl", Boolean.toString(true), false);
+
+        // Create a random location and set provider location to cache necessary MSL assets.
+        Location loc = createLocation(NAME, mRandom);
+        loc.setAltitude(mRandom.nextDouble());
+        loc.setVerticalAccuracyMeters(mRandom.nextFloat());
+        mProvider.setProviderLocation(LocationResult.wrap(loc));
+        Thread.sleep(1000);
+
+        // Register listener and reset provider location to capture.
+        ILocationListener listener = createMockLocationListener();
+        LocationRequest request = new LocationRequest.Builder(0).setWorkSource(WORK_SOURCE).build();
+        mPassive.registerLocationRequest(request, IDENTITY, PERMISSION_FINE, listener);
+        mProvider.setProviderLocation(LocationResult.wrap(loc));
+        ArgumentCaptor<List<Location>> captor = ArgumentCaptor.forClass(List.class);
+        verify(listener).onLocationChanged(captor.capture(), nullable(IRemoteCallback.class));
+
+        // Assert that MSL fields are populated.
+        Location actual = captor.getValue().get(0);
+        assertThat(actual.hasMslAltitude()).isTrue();
+        assertThat(actual.hasMslAltitudeAccuracy()).isTrue();
+    }
+
+    @MediumTest
+    @Test
+    public void testEnableMsl_noVerticalAccuracy() throws Exception {
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_LOCATION,
+                "enable_location_provider_manager_msl", Boolean.toString(true), false);
+
+        // Create a random location and set provider location to cache necessary MSL assets.
+        Location loc = createLocation(NAME, mRandom);
+        loc.setAltitude(mRandom.nextDouble());
+        loc.setVerticalAccuracyMeters(mRandom.nextFloat());
+        mProvider.setProviderLocation(LocationResult.wrap(loc));
+        Thread.sleep(1000);
+
+        // Register listener and reset provider location with no vertical accuracy to capture.
+        ILocationListener listener = createMockLocationListener();
+        LocationRequest request = new LocationRequest.Builder(0).setWorkSource(WORK_SOURCE).build();
+        mPassive.registerLocationRequest(request, IDENTITY, PERMISSION_FINE, listener);
+        loc.removeVerticalAccuracy();
+        mProvider.setProviderLocation(LocationResult.wrap(loc));
+        ArgumentCaptor<List<Location>> captor = ArgumentCaptor.forClass(List.class);
+        verify(listener).onLocationChanged(captor.capture(), nullable(IRemoteCallback.class));
+
+        // Assert that only the MSL accuracy field is populated.
+        Location actual = captor.getValue().get(0);
+        assertThat(actual.hasMslAltitude()).isTrue();
+        assertThat(actual.hasMslAltitudeAccuracy()).isFalse();
+    }
+
+    @MediumTest
+    @Test
+    public void testEnableMsl_noAltitude() throws Exception {
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_LOCATION,
+                "enable_location_provider_manager_msl", Boolean.toString(true), false);
+
+        // Create a random location and set provider location to cache necessary MSL assets.
+        Location loc = createLocation(NAME, mRandom);
+        loc.setAltitude(mRandom.nextDouble());
+        loc.setVerticalAccuracyMeters(mRandom.nextFloat());
+        mProvider.setProviderLocation(LocationResult.wrap(loc));
+        Thread.sleep(1000);
+
+        // Register listener and reset provider location with no altitude to capture.
+        ILocationListener listener = createMockLocationListener();
+        LocationRequest request = new LocationRequest.Builder(0).setWorkSource(WORK_SOURCE).build();
+        mPassive.registerLocationRequest(request, IDENTITY, PERMISSION_FINE, listener);
+        loc.removeAltitude();
+        mProvider.setProviderLocation(LocationResult.wrap(loc));
+        ArgumentCaptor<List<Location>> captor = ArgumentCaptor.forClass(List.class);
+        verify(listener).onLocationChanged(captor.capture(), nullable(IRemoteCallback.class));
+
+        // Assert that no MSL fields are populated.
+        Location actual = captor.getValue().get(0);
+        assertThat(actual.hasMslAltitude()).isFalse();
+        assertThat(actual.hasMslAltitudeAccuracy()).isFalse();
+    }
+
+    @MediumTest
+    @Test
+    public void testEnableMsl_invalidAltitude() throws Exception {
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_LOCATION,
+                "enable_location_provider_manager_msl", Boolean.toString(true), false);
+
+        // Create a random location and set provider location to cache necessary MSL assets.
+        Location loc = createLocation(NAME, mRandom);
+        loc.setAltitude(mRandom.nextDouble());
+        loc.setVerticalAccuracyMeters(mRandom.nextFloat());
+        mProvider.setProviderLocation(LocationResult.wrap(loc));
+        Thread.sleep(1000);
+
+        // Register listener and reset provider location with invalid altitude to capture.
+        ILocationListener listener = createMockLocationListener();
+        LocationRequest request = new LocationRequest.Builder(0).setWorkSource(WORK_SOURCE).build();
+        mPassive.registerLocationRequest(request, IDENTITY, PERMISSION_FINE, listener);
+        loc.setAltitude(Double.POSITIVE_INFINITY);
+        mProvider.setProviderLocation(LocationResult.wrap(loc));
+        ArgumentCaptor<List<Location>> captor = ArgumentCaptor.forClass(List.class);
+        verify(listener).onLocationChanged(captor.capture(), nullable(IRemoteCallback.class));
+
+        // Assert that no MSL fields are populated.
+        Location actual = captor.getValue().get(0);
+        assertThat(actual.hasMslAltitude()).isFalse();
+        assertThat(actual.hasMslAltitudeAccuracy()).isFalse();
+    }
+
+    @MediumTest
+    @Test
+    public void testDisableMsl_expectedBehavior() throws Exception {
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_LOCATION,
+                "enable_location_provider_manager_msl", Boolean.toString(false), false);
+
+        // Create a random location and set provider location to cache necessary MSL assets.
+        Location loc = createLocation(NAME, mRandom);
+        loc.setAltitude(mRandom.nextDouble());
+        loc.setVerticalAccuracyMeters(mRandom.nextFloat());
+        mProvider.setProviderLocation(LocationResult.wrap(loc));
+        Thread.sleep(1000);
+
+        // Register listener and reset provider location to capture.
+        ILocationListener listener = createMockLocationListener();
+        LocationRequest request = new LocationRequest.Builder(0).setWorkSource(WORK_SOURCE).build();
+        mPassive.registerLocationRequest(request, IDENTITY, PERMISSION_FINE, listener);
+        mProvider.setProviderLocation(LocationResult.wrap(loc));
+        ArgumentCaptor<List<Location>> captor = ArgumentCaptor.forClass(List.class);
+        verify(listener).onLocationChanged(captor.capture(), nullable(IRemoteCallback.class));
+
+        // Assert that no MSL fields are populated.
+        Location actual = captor.getValue().get(0);
+        assertThat(actual.hasMslAltitude()).isFalse();
+        assertThat(actual.hasMslAltitudeAccuracy()).isFalse();
+    }
+
     private ILocationListener createMockLocationListener() {
         return spy(new ILocationListener.Stub() {
             @Override
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/ApexManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/ApexManagerTest.java
index 5b0e2f3..1ae9124 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/ApexManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/ApexManagerTest.java
@@ -474,15 +474,6 @@
     }
 
     @Test
-    public void testGetBackingApexFiles_flattenedApex() {
-        ApexManager flattenedApexManager = new ApexManager.ApexManagerFlattenedApex();
-        final File backingApexFile = flattenedApexManager.getBackingApexFile(
-                new File(mMockSystem.system().getApexDirectory(),
-                        "com.android.apex.cts.shim/app/CtsShim/CtsShim.apk"));
-        assertThat(backingApexFile).isNull();
-    }
-
-    @Test
     public void testActiveApexChanged() throws RemoteException {
         ApexInfo apex1 = createApexInfo(
                 "com.apex1", 37, true, true, new File("/data/apex/active/com.apex@37.apex"));
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/ArchiveManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/ArchiveManagerTest.java
new file mode 100644
index 0000000..7b16545
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/ArchiveManagerTest.java
@@ -0,0 +1,250 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import static android.content.pm.PackageManager.DELETE_KEEP_DATA;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.IntentSender;
+import android.content.pm.LauncherActivityInfo;
+import android.content.pm.LauncherApps;
+import android.content.pm.PackageManager;
+import android.content.pm.VersionedPackage;
+import android.os.Binder;
+import android.os.Build;
+import android.os.Process;
+import android.os.UserHandle;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.pm.pkg.ArchiveState;
+import com.android.server.pm.pkg.PackageStateInternal;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class ArchiveManagerTest {
+
+    private static final String PACKAGE = "com.example";
+    private static final String CALLER_PACKAGE = "com.vending";
+
+    @Rule
+    public final MockSystemRule mMockSystem = new MockSystemRule();
+
+    @Mock
+    private IntentSender mIntentSender;
+    @Mock
+    private Computer mComputer;
+    @Mock
+    private Context mContext;
+    @Mock
+    private LauncherApps mLauncherApps;
+    @Mock
+    private PackageInstallerService mInstallerService;
+    @Mock
+    private PackageStateInternal mPackageState;
+
+    private final InstallSource mInstallSource =
+            InstallSource.create(
+                    CALLER_PACKAGE,
+                    CALLER_PACKAGE,
+                    CALLER_PACKAGE,
+                    Binder.getCallingUid(),
+                    CALLER_PACKAGE,
+                    /* installerAttributionTag= */ null,
+                    /* packageSource= */ 0);
+
+    private final List<LauncherActivityInfo> mLauncherActivityInfos = createLauncherActivities();
+
+    private PackageSetting mPackageSetting;
+
+    private PackageManagerService mPm;
+    private ArchiveManager mArchiveManager;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mMockSystem.system().stageNominalSystemState();
+        when(mMockSystem.mocks().getInjector().getPackageInstallerService()).thenReturn(
+                mInstallerService);
+        mPm = spy(new PackageManagerService(mMockSystem.mocks().getInjector(),
+                /* factoryTest= */false,
+                MockSystem.Companion.getDEFAULT_VERSION_INFO().fingerprint,
+                /* isEngBuild= */ false,
+                /* isUserDebugBuild= */false,
+                Build.VERSION_CODES.CUR_DEVELOPMENT,
+                Build.VERSION.INCREMENTAL));
+
+        when(mComputer.getPackageStateFiltered(eq(PACKAGE), anyInt(), anyInt())).thenReturn(
+                mPackageState);
+        when(mPackageState.getPackageName()).thenReturn(PACKAGE);
+        when(mPackageState.getInstallSource()).thenReturn(mInstallSource);
+        mPackageSetting = createBasicPackageSetting();
+        when(mMockSystem.mocks().getSettings().getPackageLPr(eq(PACKAGE))).thenReturn(
+                mPackageSetting);
+        when(mContext.getSystemService(LauncherApps.class)).thenReturn(mLauncherApps);
+        when(mLauncherApps.getActivityList(eq(PACKAGE), eq(UserHandle.CURRENT))).thenReturn(
+                mLauncherActivityInfos);
+        doReturn(mComputer).when(mPm).snapshotComputer();
+        when(mComputer.getNameForUid(eq(Binder.getCallingUid()))).thenReturn(CALLER_PACKAGE);
+        mArchiveManager = new ArchiveManager(mContext, mPm);
+    }
+
+    @Test
+    public void archiveApp_callerPackageNameIncorrect() {
+        Exception e = assertThrows(
+                SecurityException.class,
+                () -> mArchiveManager.archiveApp(PACKAGE, "different", UserHandle.CURRENT,
+                        mIntentSender)
+        );
+        assertThat(e).hasMessageThat().isEqualTo(
+                String.format(
+                        "The callerPackageName %s set by the caller doesn't match the "
+                                + "caller's own package name %s.",
+                        "different",
+                        CALLER_PACKAGE));
+    }
+
+    @Test
+    public void archiveApp_packageNotInstalled() {
+        when(mMockSystem.mocks().getSettings().getPackageLPr(eq(PACKAGE))).thenReturn(
+                null);
+
+        Exception e = assertThrows(
+                PackageManager.NameNotFoundException.class,
+                () -> mArchiveManager.archiveApp(PACKAGE, CALLER_PACKAGE, UserHandle.CURRENT,
+                        mIntentSender)
+        );
+        assertThat(e).hasMessageThat().isEqualTo(String.format("Package %s not found.", PACKAGE));
+    }
+
+    @Test
+    public void archiveApp_packageNotInstalledForUser() {
+        mPackageSetting.modifyUserState(UserHandle.CURRENT.getIdentifier()).setInstalled(false);
+
+        Exception e = assertThrows(
+                PackageManager.NameNotFoundException.class,
+                () -> mArchiveManager.archiveApp(PACKAGE, CALLER_PACKAGE, UserHandle.CURRENT,
+                        mIntentSender)
+        );
+        assertThat(e).hasMessageThat().isEqualTo(String.format("Package %s not found.", PACKAGE));
+    }
+
+    @Test
+    public void archiveApp_callerNotInstallerOfRecord() {
+        InstallSource otherInstallSource =
+                InstallSource.create(
+                        CALLER_PACKAGE,
+                        CALLER_PACKAGE,
+                        /* installerPackageName= */ "different",
+                        Binder.getCallingUid(),
+                        CALLER_PACKAGE,
+                        /* installerAttributionTag= */ null,
+                        /* packageSource= */ 0);
+        when(mPackageState.getInstallSource()).thenReturn(otherInstallSource);
+
+        Exception e = assertThrows(
+                SecurityException.class,
+                () -> mArchiveManager.archiveApp(PACKAGE, CALLER_PACKAGE, Process.myUserHandle(),
+                        mIntentSender)
+        );
+        assertThat(e).hasMessageThat().isEqualTo(
+                String.format("Caller is not the installer of record for %s.", PACKAGE));
+    }
+
+    @Test
+    public void archiveApp_callerNotUpdateOwner() {
+        InstallSource otherInstallSource =
+                InstallSource.create(
+                        CALLER_PACKAGE,
+                        CALLER_PACKAGE,
+                        CALLER_PACKAGE,
+                        Binder.getCallingUid(),
+                        /* updateOwnerPackageName= */ "different",
+                        /* installerAttributionTag= */ null,
+                        /* packageSource= */ 0);
+        when(mPackageState.getInstallSource()).thenReturn(otherInstallSource);
+
+        Exception e = assertThrows(
+                SecurityException.class,
+                () -> mArchiveManager.archiveApp(PACKAGE, CALLER_PACKAGE, UserHandle.CURRENT,
+                        mIntentSender)
+        );
+        assertThat(e).hasMessageThat().isEqualTo(
+                String.format("Caller is not the update owner for %s.", PACKAGE));
+    }
+
+    @Test
+    public void archiveApp_success() throws PackageManager.NameNotFoundException {
+        List<ArchiveState.ArchiveActivityInfo> activityInfos = new ArrayList<>();
+        for (LauncherActivityInfo mainActivity : createLauncherActivities()) {
+            // TODO(b/278553670) Extract and store launcher icons
+            ArchiveState.ArchiveActivityInfo activityInfo = new ArchiveState.ArchiveActivityInfo(
+                    mainActivity.getLabel().toString(),
+                    Path.of("/TODO"), null);
+            activityInfos.add(activityInfo);
+        }
+
+        mArchiveManager.archiveApp(PACKAGE, CALLER_PACKAGE, UserHandle.CURRENT, mIntentSender);
+        verify(mInstallerService).uninstall(
+                eq(new VersionedPackage(PACKAGE, PackageManager.VERSION_CODE_HIGHEST)),
+                eq(CALLER_PACKAGE), eq(DELETE_KEEP_DATA), eq(mIntentSender),
+                eq(UserHandle.CURRENT.getIdentifier()));
+        assertThat(mPackageSetting.readUserState(
+                UserHandle.CURRENT.getIdentifier()).getArchiveState()).isEqualTo(
+                new ArchiveState(activityInfos, CALLER_PACKAGE));
+    }
+
+    private static List<LauncherActivityInfo> createLauncherActivities() {
+        LauncherActivityInfo activity1 = mock(LauncherActivityInfo.class);
+        when(activity1.getLabel()).thenReturn("activity1");
+        LauncherActivityInfo activity2 = mock(LauncherActivityInfo.class);
+        when(activity2.getLabel()).thenReturn("activity2");
+        return List.of(activity1, activity2);
+    }
+
+    private PackageSetting createBasicPackageSetting() {
+        return new PackageSettingBuilder()
+                .setName(PACKAGE).setCodePath("/data/app/" + PACKAGE + "-randompath")
+                .setInstallState(UserHandle.CURRENT.getIdentifier(), /* installed= */ true)
+                .build();
+    }
+}
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 c197b34..d6a4d40 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/DeletePackageHelperTest.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/DeletePackageHelperTest.kt
@@ -16,9 +16,13 @@
 
 package com.android.server.pm
 
+import android.Manifest.permission.CONTROL_KEYGUARD
 import android.content.pm.PackageManager
+import android.content.pm.PackageManager.PERMISSION_DENIED
+import android.content.pm.PackageManager.PERMISSION_GRANTED
 import android.content.pm.UserInfo
 import android.os.Build
+import android.os.UserHandle.USER_SYSTEM
 import android.util.Log
 import com.android.server.testutils.any
 import com.android.server.testutils.spy
@@ -105,6 +109,8 @@
         whenever(PackageManagerServiceUtils.isSystemApp(ps)).thenReturn(true)
         whenever(mUserManagerInternal.getUserInfo(1)).thenReturn(
             UserInfo(1, "test", UserInfo.FLAG_ADMIN))
+        whenever(mPms.checkPermission(CONTROL_KEYGUARD, "a.data.package", USER_SYSTEM))
+            .thenReturn(PERMISSION_DENIED)
 
         val dph = DeletePackageHelper(mPms)
         val result = dph.deletePackageX("a.data.package", 1L, 1,
@@ -124,6 +130,8 @@
         whenever(mUserManagerInternal.getProfileParentId(userId)).thenReturn(parentId)
         whenever(mUserManagerInternal.getUserInfo(parentId)).thenReturn(
             UserInfo(userId, "testparent", UserInfo.FLAG_ADMIN))
+        whenever(mPms.checkPermission(CONTROL_KEYGUARD, "a.data.package", USER_SYSTEM))
+            .thenReturn(PERMISSION_DENIED)
 
         val dph = DeletePackageHelper(mPms)
         val result = dph.deletePackageX("a.data.package", 1L, userId,
@@ -138,6 +146,9 @@
         whenever(PackageManagerServiceUtils.isUpdatedSystemApp(ps)).thenReturn(true)
         whenever(mUserManagerInternal.getUserInfo(1)).thenReturn(UserInfo(1, "test", 0))
         whenever(mUserManagerInternal.getProfileParentId(1)).thenReturn(1)
+        whenever(PackageManagerServiceUtils.isSystemApp(ps)).thenReturn(true)
+        whenever(mPms.checkPermission(CONTROL_KEYGUARD, "a.data.package", USER_SYSTEM))
+            .thenReturn(PERMISSION_DENIED)
 
         val dph = DeletePackageHelper(mPms)
         val result = dph.deletePackageX("a.data.package", 1L, 1,
@@ -145,4 +156,18 @@
 
         assertThat(result).isEqualTo(PackageManager.DELETE_SUCCEEDED)
     }
+
+    @Test
+    fun deleteSystemPackageWithKeyguard_fails() {
+        val ps = mPms.mSettings.getPackageLPr("a.data.package")
+        whenever(PackageManagerServiceUtils.isSystemApp(ps)).thenReturn(true)
+        whenever(mPms.checkPermission(CONTROL_KEYGUARD, "a.data.package", USER_SYSTEM))
+            .thenReturn(PERMISSION_GRANTED)
+
+        val dph = DeletePackageHelper(mPms)
+        val result = dph.deletePackageX("a.data.package", 1L, 1,
+            PackageManager.DELETE_SYSTEM_APP, false)
+
+        assertThat(result).isEqualTo(PackageManager.DELETE_FAILED_INTERNAL_ERROR)
+    }
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageMonitorCallbackHelperTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/PackageMonitorCallbackHelperTest.java
new file mode 100644
index 0000000..f206333
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageMonitorCallbackHelperTest.java
@@ -0,0 +1,311 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.after;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Intent;
+import android.content.pm.PackageInstaller;
+import android.content.pm.PackageManager;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IRemoteCallback;
+import android.os.Looper;
+import android.os.Process;
+import android.util.SparseArray;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
+
+import java.util.ArrayList;
+
+/**
+ * A unit test for PackageMonitorCallbackHelper implementation.
+ */
+@RunWith(JUnit4.class)
+public class PackageMonitorCallbackHelperTest {
+
+    private static final String FAKE_PACKAGE_NAME = "com.android.server.pm.fakeapp";
+    private static final int FAKE_PACKAGE_UID = 123;
+    private static final int WAIT_CALLBACK_CALLED_IN_MS = 300;
+
+    private final Handler mHandler = new Handler(Looper.getMainLooper());
+    PackageMonitorCallbackHelper mPackageMonitorCallbackHelper;
+
+    @Rule
+    public final MockSystemRule mMockSystem = new MockSystemRule();
+
+    @Before
+    public void setup() {
+        when(mMockSystem.mocks().getInjector().getHandler()).thenReturn(mHandler);
+        mPackageMonitorCallbackHelper = new PackageMonitorCallbackHelper(
+                mMockSystem.mocks().getInjector());
+    }
+
+
+    @After
+    public void teardown() {
+        mPackageMonitorCallbackHelper = null;
+    }
+
+    @Test
+    public void testWithoutRegisterPackageMonitorCallback_callbackNotCalled() throws Exception {
+        IRemoteCallback callback = createMockPackageMonitorCallback();
+
+        mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED,
+                FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{0} /* userIds */,
+                null /* instantUserIds */, null /* broadcastAllowList */);
+
+        verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).never()).sendResult(any());
+    }
+
+    @Test
+    public void testUnregisterPackageMonitorCallback_callbackShouldNotCalled() throws Exception {
+        IRemoteCallback callback = createMockPackageMonitorCallback();
+
+        mPackageMonitorCallbackHelper.registerPackageMonitorCallback(callback, 0 /* userId */,
+                Binder.getCallingUid());
+        mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED,
+                FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{0}, null /* instantUserIds */,
+                null /* broadcastAllowList */);
+
+        verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).times(1)).sendResult(any());
+
+        reset(callback);
+        mPackageMonitorCallbackHelper.unregisterPackageMonitorCallback(callback);
+        mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED,
+                FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{0} /* userIds */,
+                null /* instantUserIds */, null /* broadcastAllowList */);
+
+        verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).never()).sendResult(any());
+    }
+
+    @Test
+    public void testRegisterPackageMonitorCallback_callbackCalled() throws Exception {
+        IRemoteCallback callback = createMockPackageMonitorCallback();
+
+        mPackageMonitorCallbackHelper.registerPackageMonitorCallback(callback, 0 /* userId */,
+                Binder.getCallingUid());
+        mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED,
+                FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{0} /* userIds */,
+                null /* instantUserIds */, null /* broadcastAllowList */);
+
+        ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
+        verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).times(1)).sendResult(
+                bundleCaptor.capture());
+        Bundle bundle = bundleCaptor.getValue();
+        Intent intent = bundle.getParcelable(
+                PackageManager.EXTRA_PACKAGE_MONITOR_CALLBACK_RESULT, Intent.class);
+        assertThat(intent).isNotNull();
+        assertThat(intent.getAction()).isEqualTo(Intent.ACTION_PACKAGE_ADDED);
+        assertThat(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1)).isEqualTo(0);
+    }
+
+    @Test
+    public void testRegisterPackageMonitorCallbackUserNotMatch_callbackShouldNotCalled()
+            throws Exception {
+        IRemoteCallback callback = createMockPackageMonitorCallback();
+
+        // Register for user 0
+        mPackageMonitorCallbackHelper.registerPackageMonitorCallback(callback, 0 /* userId */,
+                Binder.getCallingUid());
+        // Notify for user 10
+        mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED,
+                FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{10} /* userIds */,
+                null /* instantUserIds */, null /* broadcastAllowList */);
+
+        verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).never()).sendResult(any());
+    }
+
+    @Test
+    public void testNotifyPackageChanged_callbackCalled() throws Exception {
+        IRemoteCallback callback = createMockPackageMonitorCallback();
+
+        ArrayList<String> components = new ArrayList<>();
+        String component1 = FAKE_PACKAGE_NAME + "/.Component1";
+        components.add(component1);
+        mPackageMonitorCallbackHelper.registerPackageMonitorCallback(callback, 0 /* userId */,
+                Binder.getCallingUid());
+        mPackageMonitorCallbackHelper.notifyPackageChanged(FAKE_PACKAGE_NAME,
+                false /* dontKillApp */, components, FAKE_PACKAGE_UID, null /* reason */,
+                new int[]{0} /* userIds */, null /* instantUserIds */,
+                null /* broadcastAllowList */);
+
+        ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
+        verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).times(1)).sendResult(
+                bundleCaptor.capture());
+        Bundle bundle = bundleCaptor.getValue();
+        Intent intent = bundle.getParcelable(
+                PackageManager.EXTRA_PACKAGE_MONITOR_CALLBACK_RESULT, Intent.class);
+        assertThat(intent).isNotNull();
+        assertThat(intent.getAction()).isEqualTo(Intent.ACTION_PACKAGE_CHANGED);
+        assertThat(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1)).isEqualTo(0);
+        assertThat(intent.getStringExtra(Intent.EXTRA_REASON)).isNull();
+        assertThat(intent.getBooleanExtra(Intent.EXTRA_DONT_KILL_APP, true)).isFalse();
+        assertThat(intent.getIntExtra(Intent.EXTRA_UID, -1)).isEqualTo(FAKE_PACKAGE_UID);
+        String[] result = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST);
+        assertThat(result).isNotNull();
+        assertThat(result.length).isEqualTo(1);
+        assertThat(result[0]).isEqualTo(component1);
+    }
+
+    @Test
+    public void testNotifyPackageAddedForNewUsers_callbackCalled() throws Exception {
+        IRemoteCallback callback = createMockPackageMonitorCallback();
+
+        mPackageMonitorCallbackHelper.registerPackageMonitorCallback(callback, 0 /* userId */,
+                Binder.getCallingUid());
+        mPackageMonitorCallbackHelper.notifyPackageAddedForNewUsers(FAKE_PACKAGE_NAME,
+                FAKE_PACKAGE_UID, new int[]{0} /* userIds */, new int[0],
+                PackageInstaller.DATA_LOADER_TYPE_STREAMING, null /* broadcastAllowList */);
+
+        ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
+        verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).times(1)).sendResult(
+                bundleCaptor.capture());
+        Bundle bundle = bundleCaptor.getValue();
+        Intent intent = bundle.getParcelable(
+                PackageManager.EXTRA_PACKAGE_MONITOR_CALLBACK_RESULT, Intent.class);
+        assertThat(intent).isNotNull();
+        assertThat(intent.getAction()).isEqualTo(Intent.ACTION_PACKAGE_ADDED);
+        assertThat(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1)).isEqualTo(0);
+        assertThat(intent.getIntExtra(PackageInstaller.EXTRA_DATA_LOADER_TYPE, -1)).isEqualTo(
+                PackageInstaller.DATA_LOADER_TYPE_STREAMING);
+        assertThat(intent.getIntExtra(Intent.EXTRA_UID, -1)).isEqualTo(FAKE_PACKAGE_UID);
+    }
+
+    @Test
+    public void testNotifyResourcesChanged_callbackCalled() throws Exception {
+        IRemoteCallback callback = createMockPackageMonitorCallback();
+
+        mPackageMonitorCallbackHelper.registerPackageMonitorCallback(callback, 0 /* userId */,
+                Binder.getCallingUid());
+        mPackageMonitorCallbackHelper.notifyResourcesChanged(true /* mediaStatus */,
+                true /* replacing */, new String[]{FAKE_PACKAGE_NAME},
+                new int[]{FAKE_PACKAGE_UID} /* uids */);
+
+        ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
+        verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).times(1)).sendResult(
+                bundleCaptor.capture());
+        Bundle bundle = bundleCaptor.getValue();
+        Intent intent = bundle.getParcelable(
+                PackageManager.EXTRA_PACKAGE_MONITOR_CALLBACK_RESULT, Intent.class);
+        assertThat(intent).isNotNull();
+        assertThat(intent.getAction()).isEqualTo(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
+        assertThat(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1)).isEqualTo(0);
+        assertThat(intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)).isTrue();
+
+        int[] uids = intent.getIntArrayExtra(Intent.EXTRA_CHANGED_UID_LIST);
+        assertThat(uids).isNotNull();
+        assertThat(uids.length).isEqualTo(1);
+        assertThat(uids[0]).isEqualTo(FAKE_PACKAGE_UID);
+
+        String[] pkgNames = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
+        assertThat(pkgNames).isNotNull();
+        assertThat(pkgNames.length).isEqualTo(1);
+        assertThat(pkgNames[0]).isEqualTo(FAKE_PACKAGE_NAME);
+    }
+
+    @Test
+    public void testPackageMonitorCallback_onUserRemoved_callbackNotCalled() throws Exception {
+        IRemoteCallback callback = createMockPackageMonitorCallback();
+        mPackageMonitorCallbackHelper.registerPackageMonitorCallback(callback, 10 /* userId */,
+                Binder.getCallingUid());
+
+        mPackageMonitorCallbackHelper.onUserRemoved(10);
+        mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED,
+                FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{10} /* userIds */,
+                null /* instantUserIds */, null /* broadcastAllowList */);
+
+        verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).never()).sendResult(any());
+    }
+
+    @Test
+    public void testRegisterPackageMonitorCallbackInAllowList_callbackShouldCalled()
+            throws Exception {
+        IRemoteCallback callback = createMockPackageMonitorCallback();
+        SparseArray<int[]> broadcastAllowList = new SparseArray<>();
+        broadcastAllowList.put(0, new int[] {Binder.getCallingUid()});
+
+        mPackageMonitorCallbackHelper.registerPackageMonitorCallback(callback, 0 /* userId */,
+                Binder.getCallingUid());
+        mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED,
+                FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{0} /* userIds */,
+                null /* instantUserIds */, broadcastAllowList);
+
+        verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).times(1)).sendResult(any());
+    }
+
+    @Test
+    public void testRegisterPackageMonitorCallbackNotInAllowList_callbackShouldNotCalled()
+            throws Exception {
+        IRemoteCallback callback = createMockPackageMonitorCallback();
+        SparseArray<int[]> broadcastAllowList = new SparseArray<>();
+        broadcastAllowList.put(0, new int[] {12345});
+
+        mPackageMonitorCallbackHelper.registerPackageMonitorCallback(callback, 0 /* userId */,
+                Binder.getCallingUid());
+        mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED,
+                FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{0} /* userIds */,
+                null /* instantUserIds */, broadcastAllowList);
+
+        verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).never()).sendResult(any());
+    }
+
+    @Test
+    public void testRegisterPackageMonitorCallbackNotInAllowListSystemUid_callbackShouldCalled()
+            throws Exception {
+        IRemoteCallback callback = createMockPackageMonitorCallback();
+        SparseArray<int[]> broadcastAllowList = new SparseArray<>();
+        broadcastAllowList.put(0, new int[] {12345});
+
+        mPackageMonitorCallbackHelper.registerPackageMonitorCallback(callback, 0 /* userId */,
+                Process.SYSTEM_UID);
+        mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED,
+                FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{0} /* userIds */,
+                null /* instantUserIds */, broadcastAllowList);
+
+        verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).times(1)).sendResult(any());
+    }
+
+    private IRemoteCallback createMockPackageMonitorCallback() {
+        return spy(new IRemoteCallback.Stub() {
+            @Override
+            public void sendResult(Bundle data) {
+                // no op
+            }
+        });
+    }
+
+    private Bundle createFakeBundle() {
+        Bundle bundle = new Bundle();
+        bundle.putInt(Intent.EXTRA_UID, FAKE_PACKAGE_UID);
+        return bundle;
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
index e7b3e6f..0664ab8 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
@@ -15,12 +15,18 @@
  */
 package com.android.server.pm;
 
+import static android.os.UserManager.DISALLOW_USER_SWITCH;
+
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 
+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.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.when;
@@ -30,6 +36,7 @@
 import android.content.Context;
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.UserInfo;
+import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.os.storage.StorageManager;
@@ -41,7 +48,7 @@
 import androidx.test.annotation.UiThreadTest;
 
 import com.android.internal.widget.LockSettingsInternal;
-import com.android.server.ExtendedMockitoRule;
+import com.android.modules.utils.testing.ExtendedMockitoRule;
 import com.android.server.LocalServices;
 import com.android.server.am.UserState;
 import com.android.server.pm.UserManagerService.UserData;
@@ -88,6 +95,7 @@
     public final ExtendedMockitoRule mExtendedMockitoRule = new ExtendedMockitoRule.Builder(this)
             .spyStatic(UserManager.class)
             .spyStatic(LocalServices.class)
+            .spyStatic(SystemProperties.class)
             .mockStatic(Settings.Global.class)
             .build();
 
@@ -325,6 +333,192 @@
                 () -> mUmi.getBootUser(/* waitUntilSet= */ false));
     }
 
+    @Test
+    public void testGetPreviousFullUserToEnterForeground() throws Exception {
+        addUser(USER_ID);
+        setLastForegroundTime(USER_ID, 1_000_000L);
+        addUser(OTHER_USER_ID);
+        setLastForegroundTime(OTHER_USER_ID, 2_000_000L);
+
+        assertWithMessage("getPreviousFullUserToEnterForeground")
+                .that(mUms.getPreviousFullUserToEnterForeground())
+                .isEqualTo(OTHER_USER_ID);
+    }
+
+    @Test
+    public void testGetPreviousFullUserToEnterForeground_SkipsCurrentUser() throws Exception {
+        addUser(USER_ID);
+        setLastForegroundTime(USER_ID, 1_000_000L);
+        addUser(OTHER_USER_ID);
+        setLastForegroundTime(OTHER_USER_ID, 2_000_000L);
+
+        mockCurrentUser(OTHER_USER_ID);
+        assertWithMessage("getPreviousFullUserToEnterForeground should skip current user")
+                .that(mUms.getPreviousFullUserToEnterForeground())
+                .isEqualTo(USER_ID);
+    }
+
+    @Test
+    public void testGetPreviousFullUserToEnterForeground_SkipsNonFullUsers() throws Exception {
+        addUser(USER_ID);
+        setLastForegroundTime(USER_ID, 1_000_000L);
+        addUser(OTHER_USER_ID);
+        setLastForegroundTime(OTHER_USER_ID, 2_000_000L);
+
+        mUsers.get(OTHER_USER_ID).info.flags &= ~UserInfo.FLAG_FULL;
+        assertWithMessage("getPreviousFullUserToEnterForeground should skip non-full users")
+                .that(mUms.getPreviousFullUserToEnterForeground())
+                .isEqualTo(USER_ID);
+    }
+
+    @Test
+    public void testGetPreviousFullUserToEnterForeground_SkipsPartialUsers() throws Exception {
+        addUser(USER_ID);
+        setLastForegroundTime(USER_ID, 1_000_000L);
+        addUser(OTHER_USER_ID);
+        setLastForegroundTime(OTHER_USER_ID, 2_000_000L);
+
+        mUsers.get(OTHER_USER_ID).info.partial = true;
+        assertWithMessage("getPreviousFullUserToEnterForeground should skip partial users")
+                .that(mUms.getPreviousFullUserToEnterForeground())
+                .isEqualTo(USER_ID);
+    }
+
+    @Test
+    public void testGetPreviousFullUserToEnterForeground_SkipsDisabledUsers() throws Exception {
+        addUser(USER_ID);
+        setLastForegroundTime(USER_ID, 1_000_000L);
+        addUser(OTHER_USER_ID);
+        setLastForegroundTime(OTHER_USER_ID, 2_000_000L);
+
+        mUsers.get(OTHER_USER_ID).info.flags |= UserInfo.FLAG_DISABLED;
+        assertWithMessage("getPreviousFullUserToEnterForeground should skip disabled users")
+                .that(mUms.getPreviousFullUserToEnterForeground())
+                .isEqualTo(USER_ID);
+    }
+
+    @Test
+    public void testGetPreviousFullUserToEnterForeground_SkipsRemovingUsers() throws Exception {
+        addUser(USER_ID);
+        setLastForegroundTime(USER_ID, 1_000_000L);
+        addUser(OTHER_USER_ID);
+        setLastForegroundTime(OTHER_USER_ID, 2_000_000L);
+
+        mUms.addRemovingUserId(OTHER_USER_ID);
+        assertWithMessage("getPreviousFullUserToEnterForeground should skip removing users")
+                .that(mUms.getPreviousFullUserToEnterForeground())
+                .isEqualTo(USER_ID);
+    }
+
+    @Test
+    public void assertIsUserSwitcherEnabledOnMultiUserSettings() throws Exception {
+        resetUserSwitcherEnabled();
+
+        mockUserSwitcherEnabled(false);
+        assertThat(mUms.isUserSwitcherEnabled(USER_ID)).isFalse();
+
+        mockUserSwitcherEnabled(true);
+        assertThat(mUms.isUserSwitcherEnabled(USER_ID)).isTrue();
+    }
+
+    @Test
+    public void assertIsUserSwitcherEnabledOnMaxSupportedUsers()  throws Exception {
+        resetUserSwitcherEnabled();
+
+        mockMaxSupportedUsers(/* maxUsers= */ 1);
+        assertThat(UserManager.supportsMultipleUsers()).isFalse();
+        assertThat(mUms.isUserSwitcherEnabled(USER_ID)).isFalse();
+
+        mockMaxSupportedUsers(/* maxUsers= */ 8);
+        assertThat(UserManager.supportsMultipleUsers()).isTrue();
+        assertThat(mUms.isUserSwitcherEnabled(USER_ID)).isTrue();
+    }
+
+    @Test
+    public void assertIsUserSwitcherEnabled()  throws Exception {
+        resetUserSwitcherEnabled();
+
+        mockMaxSupportedUsers(/* maxUsers= */ 8);
+        assertThat(mUms.isUserSwitcherEnabled(true, USER_ID)).isTrue();
+
+        mockUserSwitcherEnabled(false);
+        assertThat(mUms.isUserSwitcherEnabled(true, USER_ID)).isFalse();
+
+        mockUserSwitcherEnabled(true);
+        assertThat(mUms.isUserSwitcherEnabled(false, USER_ID)).isTrue();
+
+        mUms.setUserRestriction(DISALLOW_USER_SWITCH, true, USER_ID);
+        assertThat(mUms.isUserSwitcherEnabled(false, USER_ID)).isFalse();
+
+        mUms.setUserRestriction(DISALLOW_USER_SWITCH, false, USER_ID);
+        mockMaxSupportedUsers(1);
+        assertThat(mUms.isUserSwitcherEnabled(true, USER_ID)).isFalse();
+    }
+
+    @Test
+    public void assertIsUserSwitcherEnabledOnShowMultiuserUI()  throws Exception {
+        resetUserSwitcherEnabled();
+
+        mockShowMultiuserUI(/* show= */ false);
+        assertThat(UserManager.supportsMultipleUsers()).isFalse();
+        assertThat(mUms.isUserSwitcherEnabled(USER_ID)).isFalse();
+
+        mockShowMultiuserUI(/* show= */ true);
+        assertThat(UserManager.supportsMultipleUsers()).isTrue();
+        assertThat(mUms.isUserSwitcherEnabled(USER_ID)).isTrue();
+    }
+
+    @Test
+    public void assertIsUserSwitcherEnabledOnUserRestrictions() throws Exception {
+        resetUserSwitcherEnabled();
+
+        mUms.setUserRestriction(DISALLOW_USER_SWITCH, true, USER_ID);
+        assertThat(mUms.isUserSwitcherEnabled(USER_ID)).isFalse();
+
+        mUms.setUserRestriction(DISALLOW_USER_SWITCH, false, USER_ID);
+        assertThat(mUms.isUserSwitcherEnabled(USER_ID)).isTrue();
+    }
+
+    @Test
+    public void assertIsUserSwitcherEnabledOnDemoMode() throws Exception {
+        resetUserSwitcherEnabled();
+
+        mockDeviceDemoMode(/* enabled= */ true);
+        assertThat(mUms.isUserSwitcherEnabled(USER_ID)).isFalse();
+
+        mockDeviceDemoMode(/* enabled= */ false);
+        assertThat(mUms.isUserSwitcherEnabled(USER_ID)).isTrue();
+    }
+
+    private void resetUserSwitcherEnabled() {
+        mUms.putUserInfo(new UserInfo(USER_ID, "Test User", 0));
+        mUms.setUserRestriction(DISALLOW_USER_SWITCH, false, USER_ID);
+        mockUserSwitcherEnabled(/* enabled= */ true);
+        mockDeviceDemoMode(/* enabled= */ false);
+        mockMaxSupportedUsers(/* maxUsers= */ 8);
+        mockShowMultiuserUI(/* show= */ true);
+    }
+
+    private void mockUserSwitcherEnabled(boolean enabled) {
+        doReturn(enabled ? 1 : 0).when(() -> Settings.Global.getInt(
+                any(), eq(android.provider.Settings.Global.USER_SWITCHER_ENABLED), anyInt()));
+    }
+
+    private void mockDeviceDemoMode(boolean enabled) {
+        doReturn(enabled ? 1 : 0).when(() -> Settings.Global.getInt(
+                any(), eq(android.provider.Settings.Global.DEVICE_DEMO_MODE), anyInt()));
+    }
+
+    private void mockMaxSupportedUsers(int maxUsers) {
+        doReturn(maxUsers).when(() ->
+                SystemProperties.getInt(eq("fw.max_users"), anyInt()));
+    }
+
+    private void mockShowMultiuserUI(boolean show) {
+        doReturn(show).when(() ->
+                SystemProperties.getBoolean(eq("fw.show_multiuserui"), anyBoolean()));
+    }
+
     private void mockCurrentUser(@UserIdInt int userId) {
         mockGetLocalService(ActivityManagerInternal.class, mActivityManagerInternal);
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUPANDTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUPANDTest.java
index 8979585..6e248b3 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUPANDTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUPANDTest.java
@@ -55,7 +55,7 @@
         AsyncUserVisibilityListener listener = addListenerForEvents(onVisible(visibleBgUserId));
 
         int result = mMediator.assignUserToDisplayOnStart(visibleBgUserId, visibleBgUserId,
-                BG_VISIBLE, DEFAULT_DISPLAY);
+                BG_VISIBLE, DEFAULT_DISPLAY, false);
         assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
         expectVisibleUsers(currentUserId, visibleBgUserId);
 
@@ -76,7 +76,7 @@
 
         // Make sure another user cannot be started on default display
         int result2 = mMediator.assignUserToDisplayOnStart(otherUserId, otherUserId, BG_VISIBLE,
-                DEFAULT_DISPLAY);
+                DEFAULT_DISPLAY, false);
         assertStartUserResult(result2, USER_ASSIGNMENT_RESULT_FAILURE,
                 "when user (%d) is starting on default display after it was started by user %d",
                 otherUserId, visibleBgUserId);
@@ -99,7 +99,7 @@
         startForegroundUser(currentUserId);
 
         int result = mMediator.assignUserToDisplayOnStart(visibleBgUserId, visibleBgUserId,
-                BG_VISIBLE, DEFAULT_DISPLAY);
+                BG_VISIBLE, DEFAULT_DISPLAY, false);
         assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
         expectVisibleUsers(currentUserId, visibleBgUserId);
 
@@ -120,7 +120,7 @@
 
         // Make sure another user cannot be started on default display
         int result2 = mMediator.assignUserToDisplayOnStart(otherUserId, otherUserId, BG_VISIBLE,
-                DEFAULT_DISPLAY);
+                DEFAULT_DISPLAY, false);
         assertStartUserResult(result2, USER_ASSIGNMENT_RESULT_FAILURE,
                 "when user (%d) is starting on default display after it was started by user %d",
                 otherUserId, visibleBgUserId);
@@ -137,7 +137,7 @@
         startUserInSecondaryDisplay(PARENT_USER_ID, OTHER_SECONDARY_DISPLAY_ID);
 
         int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID,
-                BG_VISIBLE, DEFAULT_DISPLAY);
+                BG_VISIBLE, DEFAULT_DISPLAY, false);
         assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
 
         expectUserIsNotVisibleAtAll(PROFILE_USER_ID);
@@ -159,7 +159,7 @@
         startUserInSecondaryDisplay(PARENT_USER_ID, DEFAULT_DISPLAY);
 
         int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID,
-                BG_VISIBLE, DEFAULT_DISPLAY);
+                BG_VISIBLE, DEFAULT_DISPLAY, false);
         assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
 
         // Assert parent user visibility
@@ -188,7 +188,7 @@
         startUserInSecondaryDisplay(PARENT_USER_ID, DEFAULT_DISPLAY);
 
         int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID, BG,
-                DEFAULT_DISPLAY);
+                DEFAULT_DISPLAY, false);
         assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE);
 
         // Assert parent user visibility
@@ -213,7 +213,7 @@
         AsyncUserVisibilityListener listener = addListenerForNoEvents();
 
         int result = mMediator.assignUserToDisplayOnStart(currentUserId, currentUserId,
-                BG_VISIBLE, DEFAULT_DISPLAY);
+                BG_VISIBLE, DEFAULT_DISPLAY, false);
         assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_ALREADY_VISIBLE);
 
         // Assert current user visibility
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorSUSDTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorSUSDTest.java
index c195064..9e2829f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorSUSDTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorSUSDTest.java
@@ -55,7 +55,7 @@
                 onVisible(USER_ID));
 
         int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, FG,
-                DEFAULT_DISPLAY);
+                DEFAULT_DISPLAY, false);
         assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
         expectUserCannotBeUnassignedFromDisplay(USER_ID, DEFAULT_DISPLAY);
 
@@ -87,7 +87,7 @@
         startForegroundUser(previousCurrentUserId);
 
         int result = mMediator.assignUserToDisplayOnStart(currentUserId, currentUserId, FG,
-                DEFAULT_DISPLAY);
+                DEFAULT_DISPLAY, false);
         assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
         expectUserCannotBeUnassignedFromDisplay(currentUserId, DEFAULT_DISPLAY);
 
@@ -123,7 +123,7 @@
         startForegroundUser(PARENT_USER_ID);
 
         int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID,
-                BG_VISIBLE, DEFAULT_DISPLAY);
+                BG_VISIBLE, DEFAULT_DISPLAY, false);
         assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
         expectUserCannotBeUnassignedFromDisplay(PROFILE_USER_ID, DEFAULT_DISPLAY);
 
@@ -144,7 +144,7 @@
         AsyncUserVisibilityListener listener = addListenerForNoEvents();
 
         int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, BG_VISIBLE,
-                SECONDARY_DISPLAY_ID);
+                SECONDARY_DISPLAY_ID, false);
         assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
 
         expectUserIsNotVisibleAtAll(USER_ID);
@@ -154,4 +154,27 @@
 
         listener.verify();
     }
+
+    @Test
+    public void testStartVisibleBgProfile_communalProfile() throws Exception {
+        AsyncUserVisibilityListener listener = addListenerForEvents(
+                onVisible(PROFILE_USER_ID));
+
+        int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID,
+                /* profileGroup= */ -12345,
+                BG_VISIBLE, DEFAULT_DISPLAY,
+                /* isCommunalProfile= */ true);
+        assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
+        expectUserCannotBeUnassignedFromDisplay(PROFILE_USER_ID, DEFAULT_DISPLAY);
+
+        expectUserIsVisible(PROFILE_USER_ID);
+        expectUserIsNotVisibleOnDisplay(PROFILE_USER_ID, INVALID_DISPLAY);
+        expectUserIsVisibleOnDisplay(PROFILE_USER_ID, DEFAULT_DISPLAY);
+        expectUserIsVisibleOnDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID);
+        expectVisibleUsers(INITIAL_CURRENT_USER_ID, PROFILE_USER_ID);
+
+        expectDisplayAssignedToUser(PROFILE_USER_ID, DEFAULT_DISPLAY);
+
+        listener.verify();
+    }
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java
index 386fd3e..2f27436 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java
@@ -148,27 +148,28 @@
     @Test
     public final void testAssignUserToDisplayOnStart_invalidUserIds() {
         assertThrows(IllegalArgumentException.class, () -> mMediator
-                .assignUserToDisplayOnStart(USER_NULL, USER_ID, FG, DEFAULT_DISPLAY));
+                .assignUserToDisplayOnStart(USER_NULL, USER_ID, FG, DEFAULT_DISPLAY, false));
         assertThrows(IllegalArgumentException.class, () -> mMediator
-                .assignUserToDisplayOnStart(USER_ALL, USER_ID, FG, DEFAULT_DISPLAY));
+                .assignUserToDisplayOnStart(USER_ALL, USER_ID, FG, DEFAULT_DISPLAY, false));
         assertThrows(IllegalArgumentException.class, () -> mMediator
-                .assignUserToDisplayOnStart(USER_CURRENT, USER_ID, FG, DEFAULT_DISPLAY));
+                .assignUserToDisplayOnStart(USER_CURRENT, USER_ID, FG, DEFAULT_DISPLAY, false));
         assertThrows(IllegalArgumentException.class, () -> mMediator
-                .assignUserToDisplayOnStart(USER_CURRENT_OR_SELF, USER_ID, FG, DEFAULT_DISPLAY));
+                .assignUserToDisplayOnStart(USER_CURRENT_OR_SELF, USER_ID, FG, DEFAULT_DISPLAY,
+                        false));
     }
 
     @Test
     public final void testAssignUserToDisplayOnStart_invalidUserStartMode() {
         assertThrows(IllegalArgumentException.class, () -> mMediator
-                .assignUserToDisplayOnStart(USER_ID, USER_ID, 666, DEFAULT_DISPLAY));
+                .assignUserToDisplayOnStart(USER_ID, USER_ID, 666, DEFAULT_DISPLAY, false));
     }
 
     @Test
     public final void testStartFgUser_onSecondaryDisplay() throws Exception {
         AsyncUserVisibilityListener listener = addListenerForNoEvents();
 
-        int result =
-                mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, FG, SECONDARY_DISPLAY_ID);
+        int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, FG,
+                SECONDARY_DISPLAY_ID, false);
         assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
 
         expectUserIsNotVisibleAtAll(USER_ID);
@@ -182,7 +183,8 @@
     public final void testStartBgUser_onDefaultDisplay() throws Exception {
         AsyncUserVisibilityListener listener = addListenerForNoEvents();
 
-        int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, BG, DEFAULT_DISPLAY);
+        int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, BG, DEFAULT_DISPLAY,
+                false);
         assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE);
 
         expectUserIsNotVisibleAtAll(USER_ID);
@@ -199,7 +201,7 @@
         AsyncUserVisibilityListener listener = addListenerForNoEvents();
 
         int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, BG_VISIBLE,
-                DEFAULT_DISPLAY);
+                DEFAULT_DISPLAY, false);
         assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
 
         expectUserIsNotVisibleAtAll(USER_ID);
@@ -215,7 +217,7 @@
         AsyncUserVisibilityListener listener = addListenerForNoEvents();
 
         int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, BG,
-                SECONDARY_DISPLAY_ID);
+                SECONDARY_DISPLAY_ID, false);
         assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
 
         expectUserIsNotVisibleAtAll(USER_ID);
@@ -236,7 +238,7 @@
         startForegroundUser(USER_ID);
 
         int result = mMediator.assignUserToDisplayOnStart(USER_SYSTEM, USER_SYSTEM, BG,
-                SECONDARY_DISPLAY_ID);
+                SECONDARY_DISPLAY_ID, false);
         assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
 
         expectUserIsNotVisibleAtAll(USER_SYSTEM);
@@ -297,7 +299,7 @@
         AsyncUserVisibilityListener listener = addListenerForNoEvents();
 
         int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID,
-                BG_VISIBLE, DEFAULT_DISPLAY);
+                BG_VISIBLE, DEFAULT_DISPLAY, false);
         assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
 
         expectUserIsNotVisibleAtAll(PROFILE_USER_ID);
@@ -313,7 +315,7 @@
         startBackgroundUser(PARENT_USER_ID);
 
         int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID,
-                BG_VISIBLE, DEFAULT_DISPLAY);
+                BG_VISIBLE, DEFAULT_DISPLAY, false);
         assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
 
         expectUserIsNotVisibleAtAll(PROFILE_USER_ID);
@@ -332,7 +334,7 @@
         AsyncUserVisibilityListener listener = addListenerForNoEvents();
 
         int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID,
-                BG_VISIBLE, SECONDARY_DISPLAY_ID);
+                BG_VISIBLE, SECONDARY_DISPLAY_ID, false);
         assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
 
         expectUserIsNotVisibleAtAll(PROFILE_USER_ID);
@@ -352,7 +354,7 @@
         AsyncUserVisibilityListener listener = addListenerForNoEvents();
 
         int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID, BG,
-                DEFAULT_DISPLAY);
+                DEFAULT_DISPLAY, false);
         assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE);
 
         expectUserIsNotVisibleAtAll(PROFILE_USER_ID);
@@ -368,7 +370,7 @@
         startBackgroundUser(PARENT_USER_ID);
 
         int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID, BG,
-                DEFAULT_DISPLAY);
+                DEFAULT_DISPLAY, false);
         assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE);
 
         expectUserIsNotVisibleAtAll(PROFILE_USER_ID);
@@ -386,7 +388,7 @@
         AsyncUserVisibilityListener listener = addListenerForNoEvents();
 
         int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID, BG,
-                SECONDARY_DISPLAY_ID);
+                SECONDARY_DISPLAY_ID, false);
         assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
 
         expectUserIsNotVisibleAtAll(PROFILE_USER_ID);
@@ -405,7 +407,7 @@
         AsyncUserVisibilityListener listener = addListenerForNoEvents();
 
         int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID, FG,
-                DEFAULT_DISPLAY);
+                DEFAULT_DISPLAY, false);
         assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
 
         expectUserIsNotVisibleAtAll(PROFILE_USER_ID);
@@ -424,7 +426,7 @@
         AsyncUserVisibilityListener listener = addListenerForNoEvents();
 
         int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID, FG,
-                SECONDARY_DISPLAY_ID);
+                SECONDARY_DISPLAY_ID, false);
         assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
 
         expectUserIsNotVisibleAtAll(PROFILE_USER_ID);
@@ -482,7 +484,8 @@
      */
     protected void startForegroundUser(@UserIdInt int userId) {
         Log.d(TAG, "startForegroundUSer(" + userId + ")");
-        int result = mMediator.assignUserToDisplayOnStart(userId, userId, FG, DEFAULT_DISPLAY);
+        int result = mMediator.assignUserToDisplayOnStart(userId, userId, FG, DEFAULT_DISPLAY,
+                false);
         if (result != USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE) {
             throw new IllegalStateException("Failed to start foreground user " + userId
                     + ": mediator returned " + userAssignmentResultToString(result));
@@ -498,7 +501,8 @@
      */
     protected void startBackgroundUser(@UserIdInt int userId) {
         Log.d(TAG, "startBackgroundUser(" + userId + ")");
-        int result = mMediator.assignUserToDisplayOnStart(userId, userId, BG, DEFAULT_DISPLAY);
+        int result = mMediator.assignUserToDisplayOnStart(userId, userId, BG, DEFAULT_DISPLAY,
+                false);
         if (result != USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE) {
             throw new IllegalStateException("Failed to start background user " + userId
                     + ": mediator returned " + userAssignmentResultToString(result));
@@ -520,7 +524,7 @@
                 + " its parent (" + PARENT_USER_ID + ") on foreground");
 
         int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID,
-                BG_VISIBLE, DEFAULT_DISPLAY);
+                BG_VISIBLE, DEFAULT_DISPLAY, false);
         if (result != USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE) {
             throw new IllegalStateException("Failed to start profile user " + PROFILE_USER_ID
                     + ": mediator returned " + userAssignmentResultToString(result));
@@ -536,7 +540,8 @@
      */
     protected final void startUserInSecondaryDisplay(@UserIdInt int userId, int displayId) {
         Log.d(TAG, "startUserInSecondaryDisplay(" + userId + ", " + displayId + ")");
-        int result = mMediator.assignUserToDisplayOnStart(userId, userId, BG_VISIBLE, displayId);
+        int result = mMediator.assignUserToDisplayOnStart(userId, userId, BG_VISIBLE, displayId,
+                false);
         if (result != USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE) {
             throw new IllegalStateException("Failed to startuser " + userId
                     + " on background: mediator returned " + userAssignmentResultToString(result));
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorVisibleBackgroundUserTestCase.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorVisibleBackgroundUserTestCase.java
index 08b094b..065f29b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorVisibleBackgroundUserTestCase.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorVisibleBackgroundUserTestCase.java
@@ -51,7 +51,7 @@
                 onVisible(USER_ID));
 
         int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, FG,
-                DEFAULT_DISPLAY);
+                DEFAULT_DISPLAY, false);
         assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
         expectUserCannotBeUnassignedFromDisplay(USER_ID, DEFAULT_DISPLAY);
 
@@ -85,7 +85,7 @@
         startForegroundUser(previousCurrentUserId);
 
         int result = mMediator.assignUserToDisplayOnStart(currentUserId, currentUserId, FG,
-                DEFAULT_DISPLAY);
+                DEFAULT_DISPLAY, false);
         assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
         expectUserCannotBeUnassignedFromDisplay(currentUserId, DEFAULT_DISPLAY);
 
@@ -112,7 +112,8 @@
     public final void testStartFgUser_onInvalidDisplay() throws Exception {
         AsyncUserVisibilityListener listener = addListenerForNoEvents();
 
-        int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, FG, INVALID_DISPLAY);
+        int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, FG, INVALID_DISPLAY,
+                false);
 
         assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
 
@@ -127,7 +128,7 @@
         AsyncUserVisibilityListener listener = addListenerForNoEvents();
 
         int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, BG_VISIBLE,
-                INVALID_DISPLAY);
+                INVALID_DISPLAY, false);
 
         assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
 
@@ -145,7 +146,7 @@
         AsyncUserVisibilityListener listener = addListenerForEvents(onVisible(USER_ID));
 
         int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, BG_VISIBLE,
-                SECONDARY_DISPLAY_ID);
+                SECONDARY_DISPLAY_ID, false);
         assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
         expectUserCannotBeUnassignedFromDisplay(USER_ID, SECONDARY_DISPLAY_ID);
 
@@ -182,7 +183,7 @@
         expectUserIsNotVisibleOnDisplay("before", PROFILE_USER_ID, SECONDARY_DISPLAY_ID);
 
         int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, BG_VISIBLE,
-                SECONDARY_DISPLAY_ID);
+                SECONDARY_DISPLAY_ID, false);
         assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
 
         expectUserIsNotVisibleOnDisplay("after", PARENT_USER_ID, SECONDARY_DISPLAY_ID);
@@ -196,7 +197,7 @@
         startUserInSecondaryDisplay(OTHER_USER_ID, SECONDARY_DISPLAY_ID);
 
         int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, BG_VISIBLE,
-                SECONDARY_DISPLAY_ID);
+                SECONDARY_DISPLAY_ID, false);
         assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
 
         expectUserIsNotVisibleAtAll(USER_ID);
@@ -224,7 +225,7 @@
         assertUserCanBeAssignedExtraDisplay(USER_ID, OTHER_SECONDARY_DISPLAY_ID);
 
         int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, BG_VISIBLE,
-                SECONDARY_DISPLAY_ID);
+                SECONDARY_DISPLAY_ID, false);
         assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_ALREADY_VISIBLE);
 
         // Run same assertions above
@@ -247,7 +248,7 @@
         startUserInSecondaryDisplay(USER_ID, OTHER_SECONDARY_DISPLAY_ID);
 
         int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, BG_VISIBLE,
-                SECONDARY_DISPLAY_ID);
+                SECONDARY_DISPLAY_ID, false);
         assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
 
         expectUserIsVisible(USER_ID);
@@ -284,7 +285,7 @@
         startForegroundUser(PARENT_USER_ID);
 
         int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID,
-                BG_VISIBLE, DEFAULT_DISPLAY);
+                BG_VISIBLE, DEFAULT_DISPLAY, false);
         assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
         expectUserCannotBeUnassignedFromDisplay(PROFILE_USER_ID, DEFAULT_DISPLAY);
 
@@ -310,7 +311,7 @@
         startUserInSecondaryDisplay(PARENT_USER_ID, OTHER_SECONDARY_DISPLAY_ID);
 
         int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID,
-                BG_VISIBLE, DEFAULT_DISPLAY);
+                BG_VISIBLE, DEFAULT_DISPLAY, false);
         assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
 
         expectUserIsNotVisibleAtAll(PROFILE_USER_ID);
@@ -331,7 +332,7 @@
         startUserInSecondaryDisplay(PARENT_USER_ID, OTHER_SECONDARY_DISPLAY_ID);
 
         int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID,
-                BG_VISIBLE, DEFAULT_DISPLAY);
+                BG_VISIBLE, DEFAULT_DISPLAY, false);
         assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
 
         expectUserIsNotVisibleAtAll(PROFILE_USER_ID);
@@ -351,7 +352,7 @@
         startUserInSecondaryDisplay(PARENT_USER_ID, OTHER_SECONDARY_DISPLAY_ID);
 
         int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID, BG,
-                DEFAULT_DISPLAY);
+                DEFAULT_DISPLAY, false);
         assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE);
 
         expectUserIsNotVisibleAtAll(PROFILE_USER_ID);
diff --git a/services/tests/mockingservicestests/src/com/android/server/power/PowerManagerServiceMockingTest.java b/services/tests/mockingservicestests/src/com/android/server/power/PowerManagerServiceMockingTest.java
deleted file mode 100644
index 0f2c27c..0000000
--- a/services/tests/mockingservicestests/src/com/android/server/power/PowerManagerServiceMockingTest.java
+++ /dev/null
@@ -1,316 +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.server.power;
-
-import static com.google.common.truth.Truth.assertThat;
-
-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.spy;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.app.ActivityManagerInternal;
-import android.attention.AttentionManagerInternal;
-import android.content.Context;
-import android.content.ContextWrapper;
-import android.content.res.Resources;
-import android.hardware.SensorManager;
-import android.hardware.devicestate.DeviceStateManager;
-import android.hardware.devicestate.DeviceStateManager.DeviceStateCallback;
-import android.hardware.display.AmbientDisplayConfiguration;
-import android.hardware.display.DisplayManagerInternal;
-import android.os.BatteryManagerInternal;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.PowerManager;
-import android.os.PowerSaveState;
-import android.os.test.TestLooper;
-import android.provider.Settings;
-import android.service.dreams.DreamManagerInternal;
-import android.test.mock.MockContentResolver;
-import android.view.Display;
-import android.view.DisplayInfo;
-
-import androidx.test.InstrumentationRegistry;
-
-import com.android.internal.app.IBatteryStats;
-import com.android.internal.util.test.FakeSettingsProvider;
-import com.android.server.LocalServices;
-import com.android.server.SystemService;
-import com.android.server.lights.LightsManager;
-import com.android.server.policy.WindowManagerPolicy;
-import com.android.server.power.PowerManagerService.BatteryReceiver;
-import com.android.server.power.PowerManagerService.Injector;
-import com.android.server.power.PowerManagerService.NativeWrapper;
-import com.android.server.power.PowerManagerService.UserSwitchedReceiver;
-import com.android.server.power.batterysaver.BatterySaverController;
-import com.android.server.power.batterysaver.BatterySaverPolicy;
-import com.android.server.power.batterysaver.BatterySaverStateMachine;
-import com.android.server.power.batterysaver.BatterySavingStats;
-import com.android.server.testutils.OffsettableClock;
-
-import java.util.concurrent.Executor;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-/**
- * Tests for {@link com.android.server.power.PowerManagerService}.
- *
- * Build/Install/Run:
- *  atest FrameworksServicesTests:PowerManagerServiceMockingTest
- */
-public class PowerManagerServiceMockingTest {
-    private static final String SYSTEM_PROPERTY_QUIESCENT = "ro.boot.quiescent";
-    private static final String SYSTEM_PROPERTY_REBOOT_REASON = "sys.boot.reason";
-
-    private static final float BRIGHTNESS_FACTOR = 0.7f;
-    private static final boolean BATTERY_SAVER_ENABLED = true;
-
-    @Mock private BatterySaverController mBatterySaverControllerMock;
-    @Mock private BatterySaverPolicy mBatterySaverPolicyMock;
-    @Mock private BatterySaverStateMachine mBatterySaverStateMachineMock;
-    @Mock private LightsManager mLightsManagerMock;
-    @Mock private DisplayManagerInternal mDisplayManagerInternalMock;
-    @Mock private BatteryManagerInternal mBatteryManagerInternalMock;
-    @Mock private ActivityManagerInternal mActivityManagerInternalMock;
-    @Mock private AttentionManagerInternal mAttentionManagerInternalMock;
-    @Mock private DreamManagerInternal mDreamManagerInternalMock;
-    @Mock private PowerManagerService.NativeWrapper mNativeWrapperMock;
-    @Mock private Notifier mNotifierMock;
-    @Mock private WirelessChargerDetector mWirelessChargerDetectorMock;
-    @Mock private AmbientDisplayConfiguration mAmbientDisplayConfigurationMock;
-    @Mock private SystemPropertiesWrapper mSystemPropertiesMock;
-    @Mock private DeviceStateManager mDeviceStateManagerMock;
-
-    @Mock
-    private InattentiveSleepWarningController mInattentiveSleepWarningControllerMock;
-
-    private PowerManagerService mService;
-    private PowerSaveState mPowerSaveState;
-    private ContextWrapper mContextSpy;
-    private BatteryReceiver mBatteryReceiver;
-    private UserSwitchedReceiver mUserSwitchedReceiver;
-    private Resources mResourcesSpy;
-    private OffsettableClock mClock;
-    private TestLooper mTestLooper;
-
-    @Before
-    public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
-        FakeSettingsProvider.clearSettingsProvider();
-
-        mPowerSaveState = new PowerSaveState.Builder()
-                .setBatterySaverEnabled(BATTERY_SAVER_ENABLED)
-                .setBrightnessFactor(BRIGHTNESS_FACTOR)
-                .build();
-        when(mBatterySaverPolicyMock.getBatterySaverPolicy(
-                eq(PowerManager.ServiceType.SCREEN_BRIGHTNESS)))
-                .thenReturn(mPowerSaveState);
-        when(mBatteryManagerInternalMock.isPowered(anyInt())).thenReturn(false);
-        when(mInattentiveSleepWarningControllerMock.isShown()).thenReturn(false);
-        when(mDisplayManagerInternalMock.requestPowerState(anyInt(), any(), anyBoolean()))
-                .thenReturn(true);
-        when(mSystemPropertiesMock.get(eq(SYSTEM_PROPERTY_QUIESCENT), anyString())).thenReturn("");
-        when(mAmbientDisplayConfigurationMock.ambientDisplayAvailable()).thenReturn(true);
-
-        addLocalServiceMock(LightsManager.class, mLightsManagerMock);
-        addLocalServiceMock(DisplayManagerInternal.class, mDisplayManagerInternalMock);
-        addLocalServiceMock(BatteryManagerInternal.class, mBatteryManagerInternalMock);
-        addLocalServiceMock(ActivityManagerInternal.class, mActivityManagerInternalMock);
-        addLocalServiceMock(AttentionManagerInternal.class, mAttentionManagerInternalMock);
-        addLocalServiceMock(DreamManagerInternal.class, mDreamManagerInternalMock);
-
-        mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getContext()));
-        mResourcesSpy = spy(mContextSpy.getResources());
-        when(mContextSpy.getResources()).thenReturn(mResourcesSpy);
-
-        MockContentResolver cr = new MockContentResolver(mContextSpy);
-        cr.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
-        when(mContextSpy.getContentResolver()).thenReturn(cr);
-
-        when(mContextSpy.getSystemService(DeviceStateManager.class))
-                .thenReturn(mDeviceStateManagerMock);
-
-        Settings.Global.putInt(mContextSpy.getContentResolver(),
-                Settings.Global.STAY_ON_WHILE_PLUGGED_IN, 0);
-
-        mClock = new OffsettableClock.Stopped();
-        mTestLooper = new TestLooper(mClock::now);
-    }
-
-    private PowerManagerService createService() {
-        mService = new PowerManagerService(mContextSpy, new Injector() {
-            @Override
-            Notifier createNotifier(Looper looper, Context context, IBatteryStats batteryStats,
-                    SuspendBlocker suspendBlocker, WindowManagerPolicy policy,
-                    FaceDownDetector faceDownDetector, ScreenUndimDetector screenUndimDetector,
-                    Executor executor) {
-                return mNotifierMock;
-            }
-
-            @Override
-            SuspendBlocker createSuspendBlocker(PowerManagerService service, String name) {
-                return super.createSuspendBlocker(service, name);
-            }
-
-            @Override
-            BatterySaverPolicy createBatterySaverPolicy(
-                    Object lock, Context context, BatterySavingStats batterySavingStats) {
-                return mBatterySaverPolicyMock;
-            }
-
-            @Override
-            BatterySaverController createBatterySaverController(
-                    Object lock, Context context, BatterySaverPolicy batterySaverPolicy,
-                    BatterySavingStats batterySavingStats) {
-                return mBatterySaverControllerMock;
-            }
-
-            @Override
-            BatterySaverStateMachine createBatterySaverStateMachine(Object lock, Context context,
-                    BatterySaverController batterySaverController) {
-                return mBatterySaverStateMachineMock;
-            }
-
-            @Override
-            NativeWrapper createNativeWrapper() {
-                return mNativeWrapperMock;
-            }
-
-            @Override
-            WirelessChargerDetector createWirelessChargerDetector(
-                    SensorManager sensorManager, SuspendBlocker suspendBlocker, Handler handler) {
-                return mWirelessChargerDetectorMock;
-            }
-
-            @Override
-            AmbientDisplayConfiguration createAmbientDisplayConfiguration(Context context) {
-                return mAmbientDisplayConfigurationMock;
-            }
-
-            @Override
-            InattentiveSleepWarningController createInattentiveSleepWarningController() {
-                return mInattentiveSleepWarningControllerMock;
-            }
-
-            @Override
-            public SystemPropertiesWrapper createSystemPropertiesWrapper() {
-                return mSystemPropertiesMock;
-            }
-
-            @Override
-            PowerManagerService.Clock createClock() {
-                return new PowerManagerService.Clock() {
-                    @Override
-                    public long uptimeMillis() {
-                        return mClock.now();
-                    }
-
-                    @Override
-                    public long elapsedRealtime() {
-                        return mClock.now();
-                    }
-                };
-            }
-
-            @Override
-            Handler createHandler(Looper looper, Handler.Callback callback) {
-                return new Handler(mTestLooper.getLooper(), callback);
-            }
-
-            @Override
-            void invalidateIsInteractiveCaches() {
-                // Avoids an SELinux failure.
-            }
-        });
-        return mService;
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        LocalServices.removeServiceForTest(LightsManager.class);
-        LocalServices.removeServiceForTest(DisplayManagerInternal.class);
-        LocalServices.removeServiceForTest(BatteryManagerInternal.class);
-        LocalServices.removeServiceForTest(ActivityManagerInternal.class);
-        LocalServices.removeServiceForTest(AttentionManagerInternal.class);
-        LocalServices.removeServiceForTest(DreamManagerInternal.class);
-        FakeSettingsProvider.clearSettingsProvider();
-    }
-
-    /**
-     * Creates a mock and registers it to {@link LocalServices}.
-     */
-    private static <T> void addLocalServiceMock(Class<T> clazz, T mock) {
-        LocalServices.removeServiceForTest(clazz);
-        LocalServices.addService(clazz, mock);
-    }
-
-    private void advanceTime(long timeMs) {
-        mClock.fastForward(timeMs);
-        mTestLooper.dispatchAll();
-    }
-
-    @Test
-    public void testUserActivityOnDeviceStateChange() {
-        createService();
-        mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY);
-        mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
-
-        final DisplayInfo info = new DisplayInfo();
-        info.displayGroupId = Display.DEFAULT_DISPLAY_GROUP;
-        when(mDisplayManagerInternalMock.getDisplayInfo(Display.DEFAULT_DISPLAY)).thenReturn(info);
-
-        final ArgumentCaptor<DeviceStateCallback> deviceStateCallbackCaptor =
-                ArgumentCaptor.forClass(DeviceStateCallback.class);
-        verify(mDeviceStateManagerMock).registerCallback(any(),
-                deviceStateCallbackCaptor.capture());
-
-        // Advance the time 10001 and verify that the device thinks it has been idle
-        // for just less than that.
-        mService.onUserActivity();
-        advanceTime(10001);
-        assertThat(mService.wasDeviceIdleForInternal(10000)).isTrue();
-
-        // Send a display state change event and advance the clock 10.
-        final DeviceStateCallback deviceStateCallback = deviceStateCallbackCaptor.getValue();
-        deviceStateCallback.onStateChanged(1);
-        final long timeToAdvance = 10;
-        advanceTime(timeToAdvance);
-
-        // Ensure that the device has been idle for only 10 (doesn't include the idle time
-        // before the display state event).
-        assertThat(mService.wasDeviceIdleForInternal(timeToAdvance - 1)).isTrue();
-        assertThat(mService.wasDeviceIdleForInternal(timeToAdvance)).isFalse();
-
-        // Send the same state and ensure that does not trigger an update.
-        deviceStateCallback.onStateChanged(1);
-        advanceTime(timeToAdvance);
-        final long newTime = timeToAdvance * 2;
-
-        assertThat(mService.wasDeviceIdleForInternal(newTime - 1)).isTrue();
-        assertThat(mService.wasDeviceIdleForInternal(newTime)).isFalse();
-    }
-}
diff --git a/services/tests/mockingservicestests/src/com/android/server/power/ScreenUndimDetectorTest.java b/services/tests/mockingservicestests/src/com/android/server/power/ScreenUndimDetectorTest.java
index 93a1f30..e8e1dac 100644
--- a/services/tests/mockingservicestests/src/com/android/server/power/ScreenUndimDetectorTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/power/ScreenUndimDetectorTest.java
@@ -38,13 +38,13 @@
 
 import com.android.modules.utils.testing.TestableDeviceConfig;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.ClassRule;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
-import org.mockito.MockitoAnnotations;
 
 import java.util.Arrays;
 import java.util.List;
@@ -86,7 +86,7 @@
 
     @Before
     public void setup() {
-        MockitoAnnotations.initMocks(this);
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
 
         DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE,
                 KEY_UNDIMS_REQUIRED,
@@ -100,12 +100,17 @@
         mScreenUndimDetector.systemReady(sContext);
     }
 
+    @After
+    public void tearDown() {
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+    }
+
     @Test
     public void recordScreenPolicy_disabledByFlag_noop() {
         DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE,
                 KEY_KEEP_SCREEN_ON_ENABLED, Boolean.FALSE.toString(), false /*makeDefault*/);
+        mScreenUndimDetector.readValuesFromDeviceConfig();
 
-        setup();
         mScreenUndimDetector.recordScreenPolicy(DEFAULT_DISPLAY_GROUP, POLICY_DIM);
         mScreenUndimDetector.recordScreenPolicy(DEFAULT_DISPLAY_GROUP, POLICY_BRIGHT);
 
@@ -115,7 +120,7 @@
     @Test
     public void recordScreenPolicy_samePolicy_noop() {
         for (int policy : ALL_POLICIES) {
-            setup();
+            resetDetector();
             mScreenUndimDetector.recordScreenPolicy(DEFAULT_DISPLAY_GROUP, policy);
             mScreenUndimDetector.recordScreenPolicy(DEFAULT_DISPLAY_GROUP, policy);
 
@@ -149,7 +154,7 @@
                 if (from == POLICY_DIM && to == POLICY_BRIGHT) {
                     continue;
                 }
-                setup();
+                resetDetector();
                 mScreenUndimDetector.recordScreenPolicy(DEFAULT_DISPLAY_GROUP, from);
                 mScreenUndimDetector.recordScreenPolicy(DEFAULT_DISPLAY_GROUP, to);
 
@@ -290,7 +295,8 @@
     @Test
     public void recordScreenPolicy_dimToNonBright_resets() {
         for (int to : Arrays.asList(POLICY_OFF, POLICY_DOZE)) {
-            setup();
+            resetDetector();
+
             mScreenUndimDetector.mUndimCounter = 1;
             mScreenUndimDetector.mUndimCounterStartedMillis = 123;
             mScreenUndimDetector.mWakeLock.acquire();
@@ -308,7 +314,8 @@
     @Test
     public void recordScreenPolicy_brightToNonDim_resets() {
         for (int to : Arrays.asList(POLICY_OFF, POLICY_DOZE)) {
-            setup();
+            resetDetector();
+
             mScreenUndimDetector.mUndimCounter = 1;
             mScreenUndimDetector.mUndimCounterStartedMillis = 123;
             mScreenUndimDetector.mWakeLock.acquire();
@@ -351,4 +358,9 @@
             }
         }
     }
+
+    private void resetDetector() {
+        mScreenUndimDetector.reset();
+        mScreenUndimDetector.mCurrentScreenPolicy = 0;
+    }
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/utils/AlarmQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/utils/AlarmQueueTest.java
index a3a49d70..f3aa427 100644
--- a/services/tests/mockingservicestests/src/com/android/server/utils/AlarmQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/utils/AlarmQueueTest.java
@@ -259,6 +259,29 @@
     }
 
     @Test
+    public void testMinTimeBetweenAlarms_freshAlarm() {
+        final AlarmQueue<String> alarmQueue = createAlarmQueue(true, 5 * MINUTE_IN_MILLIS);
+        final long fixedTimeElapsed = mInjector.getElapsedRealtime();
+
+        InOrder inOrder = inOrder(mAlarmManager);
+
+        final String pkg1 = "com.android.test.1";
+        final String pkg2 = "com.android.test.2";
+        alarmQueue.addAlarm(pkg1, fixedTimeElapsed + MINUTE_IN_MILLIS);
+        inOrder.verify(mAlarmManager, timeout(1000).times(1)).setExact(
+                anyInt(), eq(fixedTimeElapsed + MINUTE_IN_MILLIS), eq(ALARM_TAG), any(), any());
+
+        advanceElapsedClock(MINUTE_IN_MILLIS);
+
+        alarmQueue.onAlarm();
+        // Minimum of 5 minutes between alarms, so the next alarm should be 5 minutes after the
+        // first.
+        alarmQueue.addAlarm(pkg2, fixedTimeElapsed + 2 * MINUTE_IN_MILLIS);
+        inOrder.verify(mAlarmManager, timeout(1000).times(1)).setExact(
+                anyInt(), eq(fixedTimeElapsed + 6 * MINUTE_IN_MILLIS), eq(ALARM_TAG), any(), any());
+    }
+
+    @Test
     public void testOnAlarm() {
         final AlarmQueue<String> alarmQueue = createAlarmQueue(true, 0);
         final long nowElapsed = mInjector.getElapsedRealtime();
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 206af5b..eefe5af 100644
--- a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
@@ -31,7 +31,6 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.server.wallpaper.WallpaperUtils.WALLPAPER;
-import static com.android.server.wallpaper.WallpaperUtils.WALLPAPER_CROP;
 
 import static org.hamcrest.core.IsNot.not;
 import static org.junit.Assert.assertEquals;
@@ -92,7 +91,6 @@
 import org.junit.Before;
 import org.junit.BeforeClass;
 import org.junit.ClassRule;
-import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TemporaryFolder;
@@ -175,12 +173,12 @@
         sImageWallpaperComponentName = ComponentName.unflattenFromString(
                 sContext.getResources().getString(R.string.image_wallpaper_component));
         // Mock default wallpaper as image wallpaper if there is no pre-defined default wallpaper.
-        sDefaultWallpaperComponent = WallpaperManager.getDefaultWallpaperComponent(sContext);
+        sDefaultWallpaperComponent = WallpaperManager.getCmfDefaultWallpaperComponent(sContext);
 
         if (sDefaultWallpaperComponent == null) {
             sDefaultWallpaperComponent = sImageWallpaperComponentName;
             doReturn(sImageWallpaperComponentName).when(() ->
-                    WallpaperManager.getDefaultWallpaperComponent(any()));
+                    WallpaperManager.getCmfDefaultWallpaperComponent(any()));
         } else {
             sContext.addMockService(sDefaultWallpaperComponent, sWallpaperService);
         }
@@ -276,10 +274,10 @@
             assertEquals(testUserId, newWallpaperData.userId);
 
             WallpaperData wallpaperData = mService.getWallpaperSafeLocked(testUserId, which);
-            assertEquals(wallpaperData.cropFile.getAbsolutePath(),
-                    newWallpaperData.cropFile.getAbsolutePath());
-            assertEquals(wallpaperData.wallpaperFile.getAbsolutePath(),
-                    newWallpaperData.wallpaperFile.getAbsolutePath());
+            assertEquals(wallpaperData.getCropFile().getAbsolutePath(),
+                    newWallpaperData.getCropFile().getAbsolutePath());
+            assertEquals(wallpaperData.getWallpaperFile().getAbsolutePath(),
+                    newWallpaperData.getWallpaperFile().getAbsolutePath());
         }
     }
 
@@ -300,10 +298,8 @@
 
     /**
      * Tests setWallpaperComponent and clearWallpaper should work as expected.
-     * TODO ignored since the assumption never passes. to be investigated.
      */
     @Test
-    @Ignore("b/264533465")
     public void testSetThenClearComponent() {
         // Skip if there is no pre-defined default wallpaper component.
         assumeThat(sDefaultWallpaperComponent,
@@ -528,7 +524,8 @@
     @Test
     public void getWallpaperWithFeature_getCropped_returnsCropFile() throws Exception {
         File cropSystemWallpaperFile =
-                new File(WallpaperUtils.getWallpaperDir(USER_SYSTEM), WALLPAPER_CROP);
+                new WallpaperData(USER_SYSTEM, FLAG_SYSTEM).getCropFile();
+        cropSystemWallpaperFile.getParentFile().mkdirs();
         cropSystemWallpaperFile.createNewFile();
         try (FileOutputStream outputStream = new FileOutputStream(cropSystemWallpaperFile)) {
             outputStream.write("Crop system wallpaper".getBytes());
@@ -550,7 +547,8 @@
     @Test
     public void getWallpaperWithFeature_notGetCropped_returnsOriginalFile() throws Exception {
         File originalSystemWallpaperFile =
-                new File(WallpaperUtils.getWallpaperDir(USER_SYSTEM), WALLPAPER);
+                new WallpaperData(USER_SYSTEM, FLAG_SYSTEM).getWallpaperFile();
+        originalSystemWallpaperFile.getParentFile().mkdirs();
         originalSystemWallpaperFile.createNewFile();
         try (FileOutputStream outputStream = new FileOutputStream(originalSystemWallpaperFile)) {
             outputStream.write("Original system wallpaper".getBytes());
diff --git a/services/tests/powerservicetests/Android.bp b/services/tests/powerservicetests/Android.bp
new file mode 100644
index 0000000..9384015
--- /dev/null
+++ b/services/tests/powerservicetests/Android.bp
@@ -0,0 +1,43 @@
+package {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test {
+    name: "PowerServiceTests",
+
+    srcs: [
+        "src/**/*.java",
+    ],
+
+    static_libs: [
+        "frameworks-base-testutils",
+        "platform-compat-test-rules",
+        "platform-test-annotations",
+        "services.core",
+        "servicestests-utils",
+        "testables",
+    ],
+
+    libs: [
+        "android.test.mock",
+    ],
+
+    defaults: [
+        "modules-utils-testable-device-config-defaults",
+    ],
+
+    platform_apis: true,
+    test_suites: [
+        "device-tests",
+        "automotive-tests",
+    ],
+
+    certificate: "platform",
+
+    dxflags: ["--multi-dex"],
+
+    optimize: {
+        enabled: false,
+    },
+}
diff --git a/services/tests/powerservicetests/AndroidManifest.xml b/services/tests/powerservicetests/AndroidManifest.xml
new file mode 100644
index 0000000..26d9eec
--- /dev/null
+++ b/services/tests/powerservicetests/AndroidManifest.xml
@@ -0,0 +1,43 @@
+<?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.frameworks.powerservicetests">
+
+    <!-- Permissions -->
+    <uses-permission android:name="android.permission.DEVICE_POWER"/>
+    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS"/>
+    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL"/>
+    <uses-permission android:name="android.permission.READ_DEVICE_CONFIG"/>
+    <uses-permission android:name="android.permission.READ_DREAM_STATE"/>
+    <uses-permission android:name="android.permission.READ_DREAM_SUPPRESSION"/>
+    <uses-permission android:name="android.permission.STATUS_BAR_SERVICE"/>
+    <uses-permission android:name="android.permission.UPDATE_APP_OPS_STATS"/>
+    <uses-permission android:name="android.permission.UPDATE_DEVICE_STATS"/>
+    <uses-permission android:name="android.permission.WAKE_LOCK"/>
+    <uses-permission android:name="android.permission.WRITE_DREAM_STATE"/>
+
+    <application android:debuggable="true"
+                 android:testOnly="true">
+        <uses-library android:name="android.test.mock" android:required="true" />
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:label="Power Service Tests"
+        android:targetPackage="com.android.frameworks.powerservicetests" />
+</manifest>
diff --git a/services/tests/powerservicetests/AndroidTest.xml b/services/tests/powerservicetests/AndroidTest.xml
new file mode 100644
index 0000000..fa6645b
--- /dev/null
+++ b/services/tests/powerservicetests/AndroidTest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<configuration description="Runs Power Service Tests.">
+    <option name="test-suite-tag" value="apct" />
+    <option name="test-suite-tag" value="apct-instrumentation" />
+    <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="PowerServiceTests.apk" />
+    </target_preparer>
+
+    <option name="test-tag" value="PowerServiceTests" />
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+        <option name="package" value="com.android.frameworks.powerservicetests" />
+        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+        <option name="hidden-api-checks" value="false" />
+    </test>
+</configuration>
diff --git a/services/tests/powerservicetests/OWNERS b/services/tests/powerservicetests/OWNERS
new file mode 100644
index 0000000..aff0930
--- /dev/null
+++ b/services/tests/powerservicetests/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 46788
+
+include /services/core/java/com/android/server/power/OWNERS
+
diff --git a/services/tests/servicestests/src/com/android/server/power/LowPowerStandbyControllerTest.java b/services/tests/powerservicetests/src/com/android/server/power/LowPowerStandbyControllerTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/power/LowPowerStandbyControllerTest.java
rename to services/tests/powerservicetests/src/com/android/server/power/LowPowerStandbyControllerTest.java
diff --git a/services/tests/servicestests/src/com/android/server/power/NotifierTest.java b/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/power/NotifierTest.java
rename to services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java
diff --git a/services/tests/servicestests/src/com/android/server/power/PowerGroupTest.java b/services/tests/powerservicetests/src/com/android/server/power/PowerGroupTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/power/PowerGroupTest.java
rename to services/tests/powerservicetests/src/com/android/server/power/PowerGroupTest.java
diff --git a/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java
new file mode 100644
index 0000000..9463b2d
--- /dev/null
+++ b/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -0,0 +1,2760 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.power;
+
+import static android.app.ActivityManager.PROCESS_STATE_BOUND_TOP;
+import static android.app.ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
+import static android.os.PowerManager.USER_ACTIVITY_EVENT_BUTTON;
+import static android.os.PowerManagerInternal.WAKEFULNESS_ASLEEP;
+import static android.os.PowerManagerInternal.WAKEFULNESS_AWAKE;
+import static android.os.PowerManagerInternal.WAKEFULNESS_DOZING;
+import static android.os.PowerManagerInternal.WAKEFULNESS_DREAMING;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isA;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.ArgumentMatchers.same;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.atMost;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManagerInternal;
+import android.attention.AttentionManagerInternal;
+import android.compat.testing.PlatformCompatChangeRule;
+import android.content.AttributionSource;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.PermissionChecker;
+import android.content.res.Resources;
+import android.hardware.SensorManager;
+import android.hardware.devicestate.DeviceStateManager;
+import android.hardware.devicestate.DeviceStateManager.DeviceStateCallback;
+import android.hardware.display.AmbientDisplayConfiguration;
+import android.hardware.display.DisplayManagerInternal;
+import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
+import android.hardware.power.Boost;
+import android.hardware.power.Mode;
+import android.os.BatteryManager;
+import android.os.BatteryManagerInternal;
+import android.os.BatterySaverPolicyConfig;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.IWakeLockCallback;
+import android.os.Looper;
+import android.os.PowerManager;
+import android.os.PowerManagerInternal;
+import android.os.PowerSaveState;
+import android.os.UserHandle;
+import android.os.test.TestLooper;
+import android.provider.Settings;
+import android.service.dreams.DreamManagerInternal;
+import android.sysprop.PowerProperties;
+import android.test.mock.MockContentResolver;
+import android.util.IntArray;
+import android.view.Display;
+import android.view.DisplayInfo;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.internal.app.IBatteryStats;
+import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.server.LocalServices;
+import com.android.server.SystemService;
+import com.android.server.lights.LightsManager;
+import com.android.server.policy.WindowManagerPolicy;
+import com.android.server.power.PowerManagerService.BatteryReceiver;
+import com.android.server.power.PowerManagerService.BinderService;
+import com.android.server.power.PowerManagerService.NativeWrapper;
+import com.android.server.power.PowerManagerService.UserSwitchedReceiver;
+import com.android.server.power.PowerManagerService.WakeLock;
+import com.android.server.power.batterysaver.BatterySaverController;
+import com.android.server.power.batterysaver.BatterySaverPolicy;
+import com.android.server.power.batterysaver.BatterySaverStateMachine;
+import com.android.server.power.batterysaver.BatterySavingStats;
+import com.android.server.testutils.OffsettableClock;
+
+import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
+import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.mockito.ArgumentCaptor;
+import org.mockito.ArgumentMatcher;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.mockito.stubbing.Answer;
+
+import java.time.Duration;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.Callable;
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Tests for {@link com.android.server.power.PowerManagerService}.
+ *
+ * Build/Install/Run:
+ *  atest FrameworksServicesTests:PowerManagerServiceTest
+ */
+@SuppressWarnings("GuardedBy")
+public class PowerManagerServiceTest {
+    private static final String SYSTEM_PROPERTY_QUIESCENT = "ro.boot.quiescent";
+    private static final String SYSTEM_PROPERTY_REBOOT_REASON = "sys.boot.reason";
+
+    private static final float BRIGHTNESS_FACTOR = 0.7f;
+    private static final boolean BATTERY_SAVER_ENABLED = true;
+
+    @Mock private BatterySaverController mBatterySaverControllerMock;
+    @Mock private BatterySaverPolicy mBatterySaverPolicyMock;
+    @Mock private BatterySaverStateMachine mBatterySaverStateMachineMock;
+    @Mock private LightsManager mLightsManagerMock;
+    @Mock private DisplayManagerInternal mDisplayManagerInternalMock;
+    @Mock private BatteryManagerInternal mBatteryManagerInternalMock;
+    @Mock private ActivityManagerInternal mActivityManagerInternalMock;
+    @Mock private AttentionManagerInternal mAttentionManagerInternalMock;
+    @Mock private DreamManagerInternal mDreamManagerInternalMock;
+    @Mock private PowerManagerService.NativeWrapper mNativeWrapperMock;
+    @Mock private Notifier mNotifierMock;
+    @Mock private WirelessChargerDetector mWirelessChargerDetectorMock;
+    @Mock private AmbientDisplayConfiguration mAmbientDisplayConfigurationMock;
+    @Mock private SystemPropertiesWrapper mSystemPropertiesMock;
+    @Mock private LowPowerStandbyController mLowPowerStandbyControllerMock;
+    @Mock private Callable<Void> mInvalidateInteractiveCachesMock;
+    @Mock private InattentiveSleepWarningController mInattentiveSleepWarningControllerMock;
+    @Mock private PowerManagerService.PermissionCheckerWrapper mPermissionCheckerWrapperMock;
+    @Mock private PowerManagerService.PowerPropertiesWrapper mPowerPropertiesWrapper;
+    @Mock private DeviceStateManager mDeviceStateManagerMock;
+
+    @Rule public TestRule compatChangeRule = new PlatformCompatChangeRule();
+
+    private PowerManagerService mService;
+    private ContextWrapper mContextSpy;
+    private BatteryReceiver mBatteryReceiver;
+    private UserSwitchedReceiver mUserSwitchedReceiver;
+    private Resources mResourcesSpy;
+    private OffsettableClock mClock;
+    private long mLastElapsedRealtime;
+    private TestLooper mTestLooper;
+
+    private static class IntentFilterMatcher implements ArgumentMatcher<IntentFilter> {
+        private final IntentFilter mFilter;
+
+        IntentFilterMatcher(IntentFilter filter) {
+            mFilter = filter;
+        }
+
+        @Override
+        public boolean matches(IntentFilter other) {
+            if (other.countActions() != mFilter.countActions()) {
+                return false;
+            }
+            for (int i = 0; i < mFilter.countActions(); i++) {
+                if (!mFilter.getAction(i).equals(other.getAction(i))) {
+                    return false;
+                }
+            }
+            return true;
+        }
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        FakeSettingsProvider.clearSettingsProvider();
+
+        PowerSaveState powerSaveState = new PowerSaveState.Builder()
+                .setBatterySaverEnabled(BATTERY_SAVER_ENABLED)
+                .setBrightnessFactor(BRIGHTNESS_FACTOR)
+                .build();
+        when(mBatterySaverPolicyMock.getBatterySaverPolicy(
+                eq(PowerManager.ServiceType.SCREEN_BRIGHTNESS)))
+                .thenReturn(powerSaveState);
+        when(mBatteryManagerInternalMock.isPowered(anyInt())).thenReturn(false);
+        when(mInattentiveSleepWarningControllerMock.isShown()).thenReturn(false);
+        when(mDisplayManagerInternalMock.requestPowerState(anyInt(), any(), anyBoolean()))
+                .thenReturn(true);
+        when(mSystemPropertiesMock.get(eq(SYSTEM_PROPERTY_QUIESCENT), anyString())).thenReturn("");
+        when(mAmbientDisplayConfigurationMock.ambientDisplayAvailable()).thenReturn(true);
+
+        addLocalServiceMock(LightsManager.class, mLightsManagerMock);
+        addLocalServiceMock(DisplayManagerInternal.class, mDisplayManagerInternalMock);
+        addLocalServiceMock(BatteryManagerInternal.class, mBatteryManagerInternalMock);
+        addLocalServiceMock(ActivityManagerInternal.class, mActivityManagerInternalMock);
+        addLocalServiceMock(AttentionManagerInternal.class, mAttentionManagerInternalMock);
+        addLocalServiceMock(DreamManagerInternal.class, mDreamManagerInternalMock);
+
+        mContextSpy = spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
+        mResourcesSpy = spy(mContextSpy.getResources());
+        when(mContextSpy.getResources()).thenReturn(mResourcesSpy);
+
+        MockContentResolver cr = new MockContentResolver(mContextSpy);
+        cr.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
+        when(mContextSpy.getContentResolver()).thenReturn(cr);
+
+        Settings.Global.putInt(mContextSpy.getContentResolver(),
+                Settings.Global.STAY_ON_WHILE_PLUGGED_IN, 0);
+        Settings.Secure.putInt(mContextSpy.getContentResolver(),
+                Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, 0);
+
+        mClock = new OffsettableClock.Stopped();
+        mTestLooper = new TestLooper(mClock::now);
+    }
+
+    private PowerManagerService createService() {
+        mService = new PowerManagerService(mContextSpy, new PowerManagerService.Injector() {
+            @Override
+            Notifier createNotifier(Looper looper, Context context, IBatteryStats batteryStats,
+                    SuspendBlocker suspendBlocker, WindowManagerPolicy policy,
+                    FaceDownDetector faceDownDetector, ScreenUndimDetector screenUndimDetector,
+                    Executor executor) {
+                return mNotifierMock;
+            }
+
+            @Override
+            SuspendBlocker createSuspendBlocker(PowerManagerService service, String name) {
+                return super.createSuspendBlocker(service, name);
+            }
+
+            @Override
+            BatterySaverPolicy createBatterySaverPolicy(
+                    Object lock, Context context, BatterySavingStats batterySavingStats) {
+                return mBatterySaverPolicyMock;
+            }
+
+            @Override
+            BatterySaverController createBatterySaverController(
+                    Object lock, Context context, BatterySaverPolicy batterySaverPolicy,
+                    BatterySavingStats batterySavingStats) {
+                return mBatterySaverControllerMock;
+            }
+
+            @Override
+            BatterySaverStateMachine createBatterySaverStateMachine(Object lock, Context context,
+                    BatterySaverController batterySaverController) {
+                return mBatterySaverStateMachineMock;
+            }
+
+            @Override
+            NativeWrapper createNativeWrapper() {
+                return mNativeWrapperMock;
+            }
+
+            @Override
+            WirelessChargerDetector createWirelessChargerDetector(
+                    SensorManager sensorManager, SuspendBlocker suspendBlocker, Handler handler) {
+                return mWirelessChargerDetectorMock;
+            }
+
+            @Override
+            AmbientDisplayConfiguration createAmbientDisplayConfiguration(Context context) {
+                return mAmbientDisplayConfigurationMock;
+            }
+
+            @Override
+            InattentiveSleepWarningController createInattentiveSleepWarningController() {
+                return mInattentiveSleepWarningControllerMock;
+            }
+
+            @Override
+            public SystemPropertiesWrapper createSystemPropertiesWrapper() {
+                return mSystemPropertiesMock;
+            }
+
+            @Override
+            PowerManagerService.Clock createClock() {
+                return new PowerManagerService.Clock() {
+                    @Override
+                    public long uptimeMillis() {
+                        return mClock.now();
+                    }
+
+                    @Override
+                    public long elapsedRealtime() {
+                        mLastElapsedRealtime = mClock.now();
+                        return mLastElapsedRealtime;
+                    }
+                };
+            }
+
+            @Override
+            Handler createHandler(Looper looper, Handler.Callback callback) {
+                return new Handler(mTestLooper.getLooper(), callback);
+            }
+
+            @Override
+            void invalidateIsInteractiveCaches() {
+                try {
+                    mInvalidateInteractiveCachesMock.call();
+                } catch (Exception e) {
+                    throw new RuntimeException(e);
+                }
+            }
+
+            @Override
+            LowPowerStandbyController createLowPowerStandbyController(Context context,
+                    Looper looper) {
+                return mLowPowerStandbyControllerMock;
+            }
+
+            @Override
+            PowerManagerService.PermissionCheckerWrapper createPermissionCheckerWrapper() {
+                return mPermissionCheckerWrapperMock;
+            }
+
+            @Override
+            PowerManagerService.PowerPropertiesWrapper createPowerPropertiesWrapper() {
+                return mPowerPropertiesWrapper;
+            }
+        });
+        return mService;
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        LocalServices.removeServiceForTest(LightsManager.class);
+        LocalServices.removeServiceForTest(DisplayManagerInternal.class);
+        LocalServices.removeServiceForTest(BatteryManagerInternal.class);
+        LocalServices.removeServiceForTest(ActivityManagerInternal.class);
+        FakeSettingsProvider.clearSettingsProvider();
+    }
+
+    /**
+     * Creates a mock and registers it to {@link LocalServices}.
+     */
+    private static <T> void addLocalServiceMock(Class<T> clazz, T mock) {
+        LocalServices.removeServiceForTest(clazz);
+        LocalServices.addService(clazz, mock);
+    }
+
+    private void startSystem() {
+        mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY);
+
+        // Grab the BatteryReceiver
+        ArgumentCaptor<BatteryReceiver> batCaptor = ArgumentCaptor.forClass(BatteryReceiver.class);
+        IntentFilter batFilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
+        batFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
+        verify(mContextSpy).registerReceiver(batCaptor.capture(),
+                argThat(new IntentFilterMatcher(batFilter)), isNull(), isA(Handler.class));
+        mBatteryReceiver = batCaptor.getValue();
+
+        // Grab the UserSwitchedReceiver
+        ArgumentCaptor<UserSwitchedReceiver> userSwitchedCaptor =
+                ArgumentCaptor.forClass(UserSwitchedReceiver.class);
+        IntentFilter usFilter = new IntentFilter(Intent.ACTION_USER_SWITCHED);
+        verify(mContextSpy).registerReceiver(userSwitchedCaptor.capture(),
+                argThat(new IntentFilterMatcher(usFilter)), isNull(), isA(Handler.class));
+        mUserSwitchedReceiver = userSwitchedCaptor.getValue();
+
+        mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
+    }
+
+    private void forceSleep() {
+        mService.getBinderServiceInstance().goToSleep(mClock.now(),
+                PowerManager.GO_TO_SLEEP_REASON_APPLICATION, PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE);
+    }
+
+    private void forceDream() {
+        mService.getBinderServiceInstance().nap(mClock.now());
+    }
+
+    private void forceAwake() {
+        mService.getBinderServiceInstance().wakeUp(mClock.now(),
+                PowerManager.WAKE_REASON_UNKNOWN, "testing IPowerManager.wakeUp()", "pkg.name");
+    }
+
+    private void forceDozing() {
+        mService.getBinderServiceInstance().goToSleep(mClock.now(),
+                PowerManager.GO_TO_SLEEP_REASON_APPLICATION, 0);
+    }
+
+    private void setPluggedIn(boolean isPluggedIn) {
+        // Set the callback to return the new state
+        when(mBatteryManagerInternalMock.isPowered(anyInt()))
+                .thenReturn(isPluggedIn);
+        // Trigger PowerManager to reread the plug-in state
+        mBatteryReceiver.onReceive(mContextSpy, new Intent(Intent.ACTION_BATTERY_CHANGED));
+    }
+
+    private void setBatteryLevel(int batteryLevel) {
+        when(mBatteryManagerInternalMock.getBatteryLevel())
+                .thenReturn(batteryLevel);
+        mBatteryReceiver.onReceive(mContextSpy, new Intent(Intent.ACTION_BATTERY_CHANGED));
+    }
+
+    private void setBatteryHealth(int batteryHealth) {
+        when(mBatteryManagerInternalMock.getBatteryHealth())
+                .thenReturn(batteryHealth);
+        mBatteryReceiver.onReceive(mContextSpy, new Intent(Intent.ACTION_BATTERY_CHANGED));
+    }
+
+    private void setAttentiveTimeout(int attentiveTimeoutMillis) {
+        Settings.Secure.putInt(
+                mContextSpy.getContentResolver(), Settings.Secure.ATTENTIVE_TIMEOUT,
+                attentiveTimeoutMillis);
+    }
+
+    private void setAttentiveWarningDuration(int attentiveWarningDurationMillis) {
+        when(mResourcesSpy.getInteger(
+                com.android.internal.R.integer.config_attentiveWarningDuration))
+                .thenReturn(attentiveWarningDurationMillis);
+    }
+
+    private void setMinimumScreenOffTimeoutConfig(int minimumScreenOffTimeoutConfigMillis) {
+        when(mResourcesSpy.getInteger(
+                com.android.internal.R.integer.config_minimumScreenOffTimeout))
+                .thenReturn(minimumScreenOffTimeoutConfigMillis);
+    }
+
+    private void setDreamsDisabledByAmbientModeSuppressionConfig(boolean disable) {
+        when(mResourcesSpy.getBoolean(
+                com.android.internal.R.bool.config_dreamsDisabledByAmbientModeSuppressionConfig))
+                .thenReturn(disable);
+    }
+
+    private void setDreamsBatteryLevelDrainConfig(int threshold) {
+        when(mResourcesSpy.getInteger(
+                com.android.internal.R.integer.config_dreamsBatteryLevelDrainCutoff)).thenReturn(
+                threshold);
+    }
+
+    private void advanceTime(long timeMs) {
+        mClock.fastForward(timeMs);
+        mTestLooper.dispatchAll();
+    }
+
+    @Test
+    public void testCreateService_initializesNativeServiceAndSetsPowerModes() {
+        PowerManagerService service = createService();
+        verify(mNativeWrapperMock).nativeInit(same(service));
+        verify(mNativeWrapperMock).nativeSetPowerMode(eq(Mode.INTERACTIVE), eq(true));
+        verify(mNativeWrapperMock).nativeSetPowerMode(eq(Mode.DOUBLE_TAP_TO_WAKE), eq(false));
+    }
+
+    @Test
+    public void testGetLastShutdownReasonInternal() {
+        when(mSystemPropertiesMock.get(eq(SYSTEM_PROPERTY_REBOOT_REASON), any())).thenReturn(
+                "shutdown,thermal");
+        createService();
+        int reason = mService.getLastShutdownReasonInternal();
+        assertThat(reason).isEqualTo(PowerManager.SHUTDOWN_REASON_THERMAL_SHUTDOWN);
+    }
+
+    @Test
+    public void testWakefulnessAwake_InitialValue() {
+        createService();
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+    }
+
+    @Test
+    public void testWakefulnessSleep_NoDozeSleepFlag() {
+        createService();
+        // Start with AWAKE state
+        startSystem();
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+
+        // Take a nap and verify.
+        mService.getBinderServiceInstance().goToSleep(mClock.now(),
+                PowerManager.GO_TO_SLEEP_REASON_APPLICATION, PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
+    }
+
+    @Test
+    public void testWakefulnessSleep_SoftSleepFlag_NoWakelocks() {
+        createService();
+        // Start with AWAKE state
+        startSystem();
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+
+        // Take a nap and verify we go to sleep.
+        mService.getBinderServiceInstance().goToSleep(mClock.now(),
+                PowerManager.GO_TO_SLEEP_REASON_APPLICATION,
+                PowerManager.GO_TO_SLEEP_FLAG_SOFT_SLEEP);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DOZING);
+    }
+
+    @Test
+    public void testWakefulnessSleep_SoftSleepFlag_WithPartialWakelock() {
+        createService();
+        // Start with AWAKE state
+        startSystem();
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+
+        // Grab a wakelock
+        final String tag = "wakelock1";
+        final String packageName = "pkg.name";
+        final IBinder token = new Binder();
+        final int flags = PowerManager.PARTIAL_WAKE_LOCK;
+        mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName,
+                null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY,
+                null /* callback */);
+
+        // Take a nap and verify we stay awake.
+        mService.getBinderServiceInstance().goToSleep(mClock.now(),
+                PowerManager.GO_TO_SLEEP_REASON_APPLICATION,
+                PowerManager.GO_TO_SLEEP_FLAG_SOFT_SLEEP);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DOZING);
+    }
+
+    @Test
+    public void testWakefulnessSleep_SoftSleepFlag_WithFullWakelock() {
+        createService();
+        // Start with AWAKE state
+        startSystem();
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+
+        // Grab a wakelock
+        final String tag = "wakelock1";
+        final String packageName = "pkg.name";
+        final IBinder token = new Binder();
+        final int flags = PowerManager.FULL_WAKE_LOCK;
+        mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName,
+                null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY,
+                null /* callback */);
+
+        // Take a nap and verify we stay awake.
+        mService.getBinderServiceInstance().goToSleep(mClock.now(),
+                PowerManager.GO_TO_SLEEP_REASON_APPLICATION,
+                PowerManager.GO_TO_SLEEP_FLAG_SOFT_SLEEP);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+    }
+
+    @Test
+    public void testWakefulnessSleep_SoftSleepFlag_WithScreenBrightWakelock() {
+        createService();
+        // Start with AWAKE state
+        startSystem();
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+
+        // Grab a wakelock
+        final String tag = "wakelock1";
+        final String packageName = "pkg.name";
+        final IBinder token = new Binder();
+        final int flags = PowerManager.SCREEN_BRIGHT_WAKE_LOCK;
+        mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName,
+                null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY,
+                null /* callback */);
+
+        // Take a nap and verify we stay awake.
+        mService.getBinderServiceInstance().goToSleep(mClock.now(),
+                PowerManager.GO_TO_SLEEP_REASON_APPLICATION,
+                PowerManager.GO_TO_SLEEP_FLAG_SOFT_SLEEP);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+    }
+    @Test
+    public void testWakefulnessSleep_SoftSleepFlag_WithScreenDimWakelock() {
+        createService();
+        // Start with AWAKE state
+        startSystem();
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+
+        // Grab a wakelock
+        final String tag = "wakelock1";
+        final String packageName = "pkg.name";
+        final IBinder token = new Binder();
+        final int flags = PowerManager.SCREEN_DIM_WAKE_LOCK;
+        mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName,
+                null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY,
+                null /* callback */);
+
+        // Take a nap and verify we stay awake.
+        mService.getBinderServiceInstance().goToSleep(mClock.now(),
+                PowerManager.GO_TO_SLEEP_REASON_APPLICATION,
+                PowerManager.GO_TO_SLEEP_FLAG_SOFT_SLEEP);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+    }
+
+    @Test
+    @EnableCompatChanges({PowerManagerService.REQUIRE_TURN_SCREEN_ON_PERMISSION})
+    public void testWakefulnessAwake_AcquireCausesWakeup_turnScreenOnAllowed() {
+        createService();
+        startSystem();
+        forceSleep();
+
+        IBinder token = new Binder();
+        String tag = "acq_causes_wakeup";
+        String packageName = "pkg.name";
+        AttributionSource attrSrc = new AttributionSource(Binder.getCallingUid(),
+                packageName, /* attributionTag= */ null);
+
+        doReturn(PermissionChecker.PERMISSION_GRANTED).when(
+                mPermissionCheckerWrapperMock).checkPermissionForDataDelivery(any(),
+                eq(android.Manifest.permission.TURN_SCREEN_ON), anyInt(), eq(attrSrc), anyString());
+
+        // First, ensure that a normal full wake lock does not cause a wakeup
+        int flags = PowerManager.FULL_WAKE_LOCK;
+        mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName,
+                null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, null);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
+        mService.getBinderServiceInstance().releaseWakeLock(token, 0 /* flags */);
+
+        // Ensure that the flag does *NOT* work with a partial wake lock.
+        flags = PowerManager.PARTIAL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP;
+        mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName,
+                null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, null);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
+        mService.getBinderServiceInstance().releaseWakeLock(token, 0 /* flags */);
+
+        // Verify that flag forces a wakeup when paired to a FULL_WAKE_LOCK
+        flags = PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP;
+        mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName,
+                null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, null);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+        mService.getBinderServiceInstance().releaseWakeLock(token, 0 /* flags */);
+    }
+
+    @Test
+    @DisableCompatChanges({PowerManagerService.REQUIRE_TURN_SCREEN_ON_PERMISSION})
+    public void testWakefulnessAwake_AcquireCausesWakeupOldSdk_turnScreenOnAllowed() {
+        createService();
+        startSystem();
+        forceSleep();
+
+        IBinder token = new Binder();
+        String tag = "acq_causes_wakeup";
+        String packageName = "pkg.name";
+        AttributionSource attrSrc = new AttributionSource(Binder.getCallingUid(),
+                packageName, /* attributionTag= */ null);
+
+        // verify that the wakeup is allowed for apps targeting older sdks, and therefore won't have
+        // the TURN_SCREEN_ON permission granted
+        doReturn(PermissionChecker.PERMISSION_HARD_DENIED).when(
+                mPermissionCheckerWrapperMock).checkPermissionForDataDelivery(any(),
+                eq(android.Manifest.permission.TURN_SCREEN_ON), anyInt(), eq(attrSrc), anyString());
+
+        doReturn(false).when(mPowerPropertiesWrapper).waive_target_sdk_check_for_turn_screen_on();
+
+        int flags = PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP;
+        mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName,
+                null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, null);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+        mService.getBinderServiceInstance().releaseWakeLock(token, 0 /* flags */);
+    }
+
+    @Test
+    @EnableCompatChanges({PowerManagerService.REQUIRE_TURN_SCREEN_ON_PERMISSION})
+    public void testWakefulnessAwake_AcquireCausesWakeup_turnScreenOnDenied() {
+        createService();
+        startSystem();
+        forceSleep();
+
+        IBinder token = new Binder();
+        String tag = "acq_causes_wakeup";
+        String packageName = "pkg.name";
+        AttributionSource attrSrc = new AttributionSource(Binder.getCallingUid(),
+                packageName, /* attributionTag= */ null);
+        doReturn(PermissionChecker.PERMISSION_HARD_DENIED).when(
+                mPermissionCheckerWrapperMock).checkPermissionForDataDelivery(any(),
+                eq(android.Manifest.permission.TURN_SCREEN_ON), anyInt(), eq(attrSrc), anyString());
+
+        doReturn(false).when(mPowerPropertiesWrapper).waive_target_sdk_check_for_turn_screen_on();
+        doReturn(false).when(mPowerPropertiesWrapper).permissionless_turn_screen_on();
+
+        // Verify that flag has no effect when TURN_SCREEN_ON is not allowed for apps targeting U+
+        int flags = PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP;
+        mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName,
+                null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, null);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
+        mService.getBinderServiceInstance().releaseWakeLock(token, 0 /* flags */);
+    }
+
+    @Test
+    @EnableCompatChanges({PowerManagerService.REQUIRE_TURN_SCREEN_ON_PERMISSION})
+    public void testWakefulnessAwake_AcquireCausesWakeupOldSdk_turnScreenOnDenied() {
+        createService();
+        startSystem();
+        forceSleep();
+
+        IBinder token = new Binder();
+        String tag = "acq_causes_wakeup";
+        String packageName = "pkg.name";
+        AttributionSource attrSrc = new AttributionSource(Binder.getCallingUid(),
+                packageName, /* attributionTag= */ null);
+        doReturn(PermissionChecker.PERMISSION_HARD_DENIED).when(
+                mPermissionCheckerWrapperMock).checkPermissionForDataDelivery(any(),
+                eq(android.Manifest.permission.TURN_SCREEN_ON), anyInt(), eq(attrSrc), anyString());
+
+        doReturn(true).when(mPowerPropertiesWrapper).waive_target_sdk_check_for_turn_screen_on();
+
+        // Verify that flag has no effect when TURN_SCREEN_ON is not allowed for apps targeting U+
+        int flags = PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP;
+        mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName,
+                null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, null);
+        if (PowerProperties.permissionless_turn_screen_on().orElse(false)) {
+            assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+        } else {
+            assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
+        }
+        mService.getBinderServiceInstance().releaseWakeLock(token, 0 /* flags */);
+    }
+
+    @Test
+    public void testWakefulnessAwake_IPowerManagerWakeUp() {
+        createService();
+        startSystem();
+        forceSleep();
+        mService.getBinderServiceInstance().wakeUp(mClock.now(),
+                PowerManager.WAKE_REASON_UNKNOWN, "testing IPowerManager.wakeUp()", "pkg.name");
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+    }
+
+    /**
+     * Tests a series of variants that control whether a device wakes-up when it is plugged in
+     * or docked.
+     */
+    @Test
+    public void testWakefulnessAwake_ShouldWakeUpWhenPluggedIn() {
+        createService();
+        startSystem();
+        forceSleep();
+
+        // Test 1:
+        // Set config to prevent it wake up, test, verify, reset config value.
+        when(mResourcesSpy.getBoolean(com.android.internal.R.bool.config_unplugTurnsOnScreen))
+                .thenReturn(false);
+        mService.readConfigurationLocked();
+        setPluggedIn(true);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
+        when(mResourcesSpy.getBoolean(com.android.internal.R.bool.config_unplugTurnsOnScreen))
+                .thenReturn(true);
+        mService.readConfigurationLocked();
+
+        // Test 2:
+        // Turn the power off, sleep, then plug into a wireless charger.
+        // Verify that we do not wake up if the phone is being plugged into a wireless charger.
+        setPluggedIn(false);
+        forceSleep();
+        when(mBatteryManagerInternalMock.getPlugType())
+                .thenReturn(BatteryManager.BATTERY_PLUGGED_WIRELESS);
+        when(mWirelessChargerDetectorMock.update(true /* isPowered */,
+                BatteryManager.BATTERY_PLUGGED_WIRELESS)).thenReturn(false);
+        setPluggedIn(true);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
+
+        // Test 3:
+        // Do not wake up if the phone is being REMOVED from a wireless charger
+        when(mBatteryManagerInternalMock.getPlugType()).thenReturn(0);
+        setPluggedIn(false);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
+
+        // Test 4:
+        // Do not wake if we are dreaming.
+        forceAwake();  // Needs to be awake first before it can dream.
+        forceDream();
+        setPluggedIn(true);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING);
+        forceSleep();
+
+        // Test 5:
+        // Don't wake if the device is configured not to wake up in theater mode (and theater
+        // mode is enabled).
+        Settings.Global.putInt(
+                mContextSpy.getContentResolver(), Settings.Global.THEATER_MODE_ON, 1);
+        mUserSwitchedReceiver.onReceive(mContextSpy, new Intent(Intent.ACTION_USER_SWITCHED));
+        when(mResourcesSpy.getBoolean(
+                com.android.internal.R.bool.config_allowTheaterModeWakeFromUnplug))
+                .thenReturn(false);
+        setPluggedIn(false);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
+        Settings.Global.putInt(
+                mContextSpy.getContentResolver(), Settings.Global.THEATER_MODE_ON, 0);
+        mUserSwitchedReceiver.onReceive(mContextSpy, new Intent(Intent.ACTION_USER_SWITCHED));
+
+        // Test 6:
+        // Don't wake up if we are Dozing away and always-on is enabled.
+        when(mAmbientDisplayConfigurationMock.alwaysOnEnabled(UserHandle.USER_CURRENT))
+                .thenReturn(true);
+        mUserSwitchedReceiver.onReceive(mContextSpy, new Intent(Intent.ACTION_USER_SWITCHED));
+        forceAwake();
+        forceDozing();
+        setPluggedIn(true);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DOZING);
+
+        // Test 7:
+        // Finally, take away all the factors above and ensure the device wakes up!
+        forceAwake();
+        forceSleep();
+        setPluggedIn(false);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+    }
+
+    /**
+     * Tests that dreaming stops when undocking and not configured to keep dreaming.
+     */
+    @Test
+    public void testWakefulnessDream_shouldStopDreamingWhenUndocked_whenNotConfigured() {
+        // Make sure "unplug turns on screen" is configured to true.
+        when(mResourcesSpy.getBoolean(com.android.internal.R.bool.config_unplugTurnsOnScreen))
+                .thenReturn(true);
+
+        createService();
+        startSystem();
+
+        ArgumentCaptor<DreamManagerInternal.DreamManagerStateListener> dreamManagerStateListener =
+                ArgumentCaptor.forClass(DreamManagerInternal.DreamManagerStateListener.class);
+        verify(mDreamManagerInternalMock).registerDreamManagerStateListener(
+                dreamManagerStateListener.capture());
+        dreamManagerStateListener.getValue().onKeepDreamingWhenUnpluggingChanged(false);
+
+        when(mBatteryManagerInternalMock.getPlugType())
+                .thenReturn(BatteryManager.BATTERY_PLUGGED_DOCK);
+        setPluggedIn(true);
+
+        forceAwake();  // Needs to be awake first before it can dream.
+        forceDream();
+        when(mBatteryManagerInternalMock.getPlugType()).thenReturn(0);
+        setPluggedIn(false);
+
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+    }
+
+    /**
+     * Tests that dreaming continues when undocking and configured to do so.
+     */
+    @Test
+    public void testWakefulnessDream_shouldKeepDreamingWhenUndocked_whenConfigured() {
+        // Make sure "unplug turns on screen" is configured to true.
+        when(mResourcesSpy.getBoolean(com.android.internal.R.bool.config_unplugTurnsOnScreen))
+                .thenReturn(true);
+
+        createService();
+        startSystem();
+
+        ArgumentCaptor<DreamManagerInternal.DreamManagerStateListener> dreamManagerStateListener =
+                ArgumentCaptor.forClass(DreamManagerInternal.DreamManagerStateListener.class);
+        verify(mDreamManagerInternalMock).registerDreamManagerStateListener(
+                dreamManagerStateListener.capture());
+        dreamManagerStateListener.getValue().onKeepDreamingWhenUnpluggingChanged(true);
+
+        when(mBatteryManagerInternalMock.getPlugType())
+                .thenReturn(BatteryManager.BATTERY_PLUGGED_DOCK);
+        setPluggedIn(true);
+
+        forceAwake();  // Needs to be awake first before it can dream.
+        forceDream();
+        when(mBatteryManagerInternalMock.getPlugType()).thenReturn(0);
+        setPluggedIn(false);
+
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING);
+    }
+
+    /**
+     * Tests that dreaming stops when undocking while showing a dream that prevents it.
+     */
+    @Test
+    public void testWakefulnessDream_shouldStopDreamingWhenUndocked_whenDreamPrevents() {
+        // Make sure "unplug turns on screen" is configured to true.
+        when(mResourcesSpy.getBoolean(com.android.internal.R.bool.config_unplugTurnsOnScreen))
+                .thenReturn(true);
+
+        createService();
+        startSystem();
+
+        ArgumentCaptor<DreamManagerInternal.DreamManagerStateListener> dreamManagerStateListener =
+                ArgumentCaptor.forClass(DreamManagerInternal.DreamManagerStateListener.class);
+        verify(mDreamManagerInternalMock).registerDreamManagerStateListener(
+                dreamManagerStateListener.capture());
+        dreamManagerStateListener.getValue().onKeepDreamingWhenUnpluggingChanged(true);
+
+        when(mBatteryManagerInternalMock.getPlugType())
+                .thenReturn(BatteryManager.BATTERY_PLUGGED_DOCK);
+        setPluggedIn(true);
+
+        forceAwake();  // Needs to be awake first before it can dream.
+        forceDream();
+        dreamManagerStateListener.getValue().onKeepDreamingWhenUnpluggingChanged(false);
+        when(mBatteryManagerInternalMock.getPlugType()).thenReturn(0);
+        setPluggedIn(false);
+
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+    }
+
+    @Test
+    public void testWakefulnessDream_shouldStopDreamingWhenUnplugging_whenDreamPrevents() {
+        // Make sure "unplug turns on screen" is configured to true.
+        when(mResourcesSpy.getBoolean(com.android.internal.R.bool.config_unplugTurnsOnScreen))
+                .thenReturn(true);
+
+        createService();
+        startSystem();
+
+        ArgumentCaptor<DreamManagerInternal.DreamManagerStateListener> dreamManagerStateListener =
+                ArgumentCaptor.forClass(DreamManagerInternal.DreamManagerStateListener.class);
+        verify(mDreamManagerInternalMock).registerDreamManagerStateListener(
+                dreamManagerStateListener.capture());
+
+        when(mBatteryManagerInternalMock.getPlugType())
+                .thenReturn(BatteryManager.BATTERY_PLUGGED_AC);
+        setPluggedIn(true);
+
+        forceAwake();  // Needs to be awake first before it can dream.
+        forceDream();
+        dreamManagerStateListener.getValue().onKeepDreamingWhenUnpluggingChanged(false);
+        when(mBatteryManagerInternalMock.getPlugType()).thenReturn(0);
+        setPluggedIn(false);
+
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+    }
+
+
+    @Test
+    public void testWakefulnessDoze_goToSleep() {
+        createService();
+        // Start with AWAKE state
+        startSystem();
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+
+        // Take a nap and verify.
+        mService.getBinderServiceInstance().goToSleep(mClock.now(),
+                PowerManager.GO_TO_SLEEP_REASON_APPLICATION, 0);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DOZING);
+    }
+
+    @Test
+    public void testWasDeviceIdleFor_true() {
+        int interval = 1000;
+        createService();
+        startSystem();
+        mService.onUserActivity();
+        advanceTime(interval + 1 /* just a little more */);
+        assertThat(mService.wasDeviceIdleForInternal(interval)).isTrue();
+    }
+
+    @Test
+    public void testWasDeviceIdleFor_false() {
+        int interval = 1000;
+        createService();
+        startSystem();
+        mService.onUserActivity();
+        assertThat(mService.wasDeviceIdleForInternal(interval)).isFalse();
+    }
+
+    @Test
+    public void testForceSuspend_putsDeviceToSleep() {
+        createService();
+        startSystem();
+
+        // Verify that we start awake
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+
+        // Grab the wakefulness value when PowerManager finally calls into the
+        // native component to actually perform the suspend.
+        when(mNativeWrapperMock.nativeForceSuspend()).then(inv -> {
+            assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
+            return true;
+        });
+
+        boolean retval = mService.getBinderServiceInstance().forceSuspend();
+        assertThat(retval).isTrue();
+
+        // Still asleep when the function returns.
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
+    }
+
+    @Test
+    public void testForceSuspend_wakeLocksDisabled() {
+        final String tag = "TestWakelockTag_098213";
+        final int flags = PowerManager.PARTIAL_WAKE_LOCK;
+        final String pkg = mContextSpy.getOpPackageName();
+
+        createService();
+
+        // Set up the Notification mock to keep track of the wakelocks that are currently
+        // active or disabled. We'll use this to verify that wakelocks are disabled when
+        // they should be.
+        final Map<String, Integer> wakelockMap = new HashMap<>(1);
+        doAnswer(inv -> {
+            wakelockMap.put((String) inv.getArguments()[1], (int) inv.getArguments()[0]);
+            return null;
+        }).when(mNotifierMock).onWakeLockAcquired(anyInt(), anyString(), anyString(), anyInt(),
+                anyInt(), any(), any(), any());
+        doAnswer(inv -> {
+            wakelockMap.remove((String) inv.getArguments()[1]);
+            return null;
+        }).when(mNotifierMock).onWakeLockReleased(anyInt(), anyString(), anyString(), anyInt(),
+                anyInt(), any(), any(), any());
+
+        //
+        // TEST STARTS HERE
+        //
+        startSystem();
+
+        // Verify that we start awake
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+
+        // Create a wakelock
+        mService.getBinderServiceInstance().acquireWakeLock(new Binder(), flags, tag, pkg,
+                null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, null);
+        assertThat(wakelockMap.get(tag)).isEqualTo(flags);  // Verify wakelock is active.
+
+        // Confirm that the wakelocks have been disabled when the forceSuspend is in flight.
+        when(mNativeWrapperMock.nativeForceSuspend()).then(inv -> {
+            // Verify that the wakelock is disabled by the time we get to the native force
+            // suspend call.
+            assertThat(wakelockMap.containsKey(tag)).isFalse();
+            return true;
+        });
+
+        assertThat(mService.getBinderServiceInstance().forceSuspend()).isTrue();
+        assertThat(wakelockMap.get(tag)).isEqualTo(flags);
+
+    }
+
+    @Test
+    public void testForceSuspend_forceSuspendFailurePropagated() {
+        createService();
+        startSystem();
+        when(mNativeWrapperMock.nativeForceSuspend()).thenReturn(false);
+        assertThat(mService.getBinderServiceInstance().forceSuspend()).isFalse();
+    }
+
+    @SuppressWarnings("GuardedBy")
+    @Test
+    public void testScreensaverActivateOnSleepDisabled_powered_afterTimeout_goesToDozing() {
+        when(mBatteryManagerInternalMock.isPowered(anyInt())).thenReturn(true);
+
+        doAnswer(inv -> {
+            when(mDreamManagerInternalMock.isDreaming()).thenReturn(true);
+            return null;
+        }).when(mDreamManagerInternalMock).startDream(anyBoolean(), anyString());
+
+        setMinimumScreenOffTimeoutConfig(5);
+        createService();
+        startSystem();
+
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+
+        advanceTime(15000);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DOZING);
+    }
+
+    @SuppressWarnings("GuardedBy")
+    @Test
+    public void testScreensaverActivateOnSleepEnabled_powered_afterTimeout_goesToDreaming() {
+        when(mResourcesSpy.getBoolean(com.android.internal.R.bool.config_dreamsSupported))
+                .thenReturn(true);
+        when(mBatteryManagerInternalMock.isPowered(anyInt())).thenReturn(true);
+        Settings.Secure.putInt(mContextSpy.getContentResolver(),
+                Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, 1);
+        Settings.Secure.putInt(mContextSpy.getContentResolver(),
+                Settings.Secure.SCREENSAVER_ENABLED, 1);
+
+        doAnswer(inv -> {
+            when(mDreamManagerInternalMock.isDreaming()).thenReturn(true);
+            return null;
+        }).when(mDreamManagerInternalMock).startDream(anyBoolean(), anyString());
+
+        setMinimumScreenOffTimeoutConfig(5);
+        createService();
+        startSystem();
+
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+
+        advanceTime(15000);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING);
+    }
+
+    @SuppressWarnings("GuardedBy")
+    @Test
+    public void testAmbientSuppression_disablesDreamingAndWakesDevice() {
+        Settings.Secure.putInt(mContextSpy.getContentResolver(),
+                Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, 1);
+        Settings.Secure.putInt(mContextSpy.getContentResolver(),
+                Settings.Secure.SCREENSAVER_ENABLED, 1);
+
+        setDreamsDisabledByAmbientModeSuppressionConfig(true);
+        setMinimumScreenOffTimeoutConfig(10000);
+        createService();
+        startSystem();
+
+        doAnswer(inv -> {
+            when(mDreamManagerInternalMock.isDreaming()).thenReturn(true);
+            return null;
+        }).when(mDreamManagerInternalMock).startDream(anyBoolean(), anyString());
+
+        setPluggedIn(true);
+        // Allow asynchronous sandman calls to execute.
+        advanceTime(10000);
+
+        forceDream();
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING);
+        mService.getBinderServiceInstance().suppressAmbientDisplay("test", true);
+        advanceTime(50);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+    }
+
+    @SuppressWarnings("GuardedBy")
+    @Test
+    public void testAmbientSuppressionDisabled_shouldNotWakeDevice() {
+        Settings.Secure.putInt(mContextSpy.getContentResolver(),
+                Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, 1);
+        Settings.Secure.putInt(mContextSpy.getContentResolver(),
+                Settings.Secure.SCREENSAVER_ENABLED, 1);
+
+        setDreamsDisabledByAmbientModeSuppressionConfig(false);
+        setMinimumScreenOffTimeoutConfig(10000);
+        createService();
+        startSystem();
+
+        doAnswer(inv -> {
+            when(mDreamManagerInternalMock.isDreaming()).thenReturn(true);
+            return null;
+        }).when(mDreamManagerInternalMock).startDream(anyBoolean(), anyString());
+
+        setPluggedIn(true);
+        // Allow asynchronous sandman calls to execute.
+        advanceTime(10000);
+
+        forceDream();
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING);
+        mService.getBinderServiceInstance().suppressAmbientDisplay("test", true);
+        advanceTime(50);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING);
+    }
+
+    @Test
+    public void testAmbientSuppression_doesNotAffectDreamForcing() {
+        Settings.Secure.putInt(mContextSpy.getContentResolver(),
+                Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, 1);
+        Settings.Secure.putInt(mContextSpy.getContentResolver(),
+                Settings.Secure.SCREENSAVER_ENABLED, 1);
+
+        setDreamsDisabledByAmbientModeSuppressionConfig(true);
+        setMinimumScreenOffTimeoutConfig(10000);
+        createService();
+        startSystem();
+
+        doAnswer(inv -> {
+            when(mDreamManagerInternalMock.isDreaming()).thenReturn(true);
+            return null;
+        }).when(mDreamManagerInternalMock).startDream(anyBoolean(), anyString());
+
+        mService.getBinderServiceInstance().suppressAmbientDisplay("test", true);
+        setPluggedIn(true);
+        // Allow asynchronous sandman calls to execute.
+        advanceTime(10000);
+
+        // Verify that forcing dream still works even though ambient display is suppressed
+        forceDream();
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING);
+    }
+
+    @Test
+    public void testBatteryDrainDuringDream() {
+        Settings.Secure.putInt(mContextSpy.getContentResolver(),
+                Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, 1);
+        Settings.Secure.putInt(mContextSpy.getContentResolver(),
+                Settings.Secure.SCREENSAVER_ENABLED, 1);
+
+        setMinimumScreenOffTimeoutConfig(100);
+        setDreamsBatteryLevelDrainConfig(5);
+        createService();
+        startSystem();
+
+        doAnswer(inv -> {
+            when(mDreamManagerInternalMock.isDreaming()).thenReturn(true);
+            return null;
+        }).when(mDreamManagerInternalMock).startDream(anyBoolean(), anyString());
+
+        setBatteryLevel(100);
+        setPluggedIn(true);
+
+        forceAwake();  // Needs to be awake first before it can dream.
+        forceDream();
+        advanceTime(10); // Allow async calls to happen
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING);
+        setBatteryLevel(90);
+        advanceTime(10); // Allow async calls to happen
+        assertThat(mService.getDreamsBatteryLevelDrain()).isEqualTo(10);
+
+        // If battery overheat protection is enabled, we shouldn't count battery drain
+        setBatteryHealth(BatteryManager.BATTERY_HEALTH_OVERHEAT);
+        setBatteryLevel(70);
+        advanceTime(10); // Allow async calls to happen
+        assertThat(mService.getDreamsBatteryLevelDrain()).isEqualTo(10);
+    }
+
+    @Test
+    public void testSetDozeOverrideFromDreamManager_triggersSuspendBlocker() {
+        final String suspendBlockerName = "PowerManagerService.Display";
+        final String tag = "acq_causes_wakeup";
+        final String packageName = "pkg.name";
+        final IBinder token = new Binder();
+
+        final boolean[] isAcquired = new boolean[1];
+        doAnswer(inv -> {
+            if (suspendBlockerName.equals(inv.getArguments()[0])) {
+                isAcquired[0] = false;
+            }
+            return null;
+        }).when(mNativeWrapperMock).nativeReleaseSuspendBlocker(any());
+
+        doAnswer(inv -> {
+            if (suspendBlockerName.equals(inv.getArguments()[0])) {
+                isAcquired[0] = true;
+            }
+            return null;
+        }).when(mNativeWrapperMock).nativeAcquireSuspendBlocker(any());
+
+        // Need to create the service after we stub the mocks for this test because some of the
+        // mocks are used during the constructor.
+        createService();
+
+        // Start with AWAKE state
+        startSystem();
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+        assertTrue(isAcquired[0]);
+
+        // Take a nap and verify we no longer hold the blocker
+        int flags = PowerManager.DOZE_WAKE_LOCK;
+        mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName,
+                null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, null);
+        when(mDreamManagerInternalMock.isDreaming()).thenReturn(true);
+        mService.getBinderServiceInstance().goToSleep(mClock.now(),
+                PowerManager.GO_TO_SLEEP_REASON_APPLICATION, 0);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DOZING);
+        assertFalse(isAcquired[0]);
+
+        // Override the display state by DreamManager and verify is reacquires the blocker.
+        mService.getLocalServiceInstance()
+                .setDozeOverrideFromDreamManager(Display.STATE_ON, PowerManager.BRIGHTNESS_DEFAULT);
+        assertTrue(isAcquired[0]);
+    }
+
+    @Test
+    public void testSuspendBlockerHeldDuringBoot() {
+        final String suspendBlockerName = "PowerManagerService.Booting";
+
+        final boolean[] isAcquired = new boolean[1];
+        doAnswer(inv -> {
+            isAcquired[0] = false;
+            return null;
+        }).when(mNativeWrapperMock).nativeReleaseSuspendBlocker(eq(suspendBlockerName));
+
+        doAnswer(inv -> {
+            isAcquired[0] = true;
+            return null;
+        }).when(mNativeWrapperMock).nativeAcquireSuspendBlocker(eq(suspendBlockerName));
+
+        // Need to create the service after we stub the mocks for this test because some of the
+        // mocks are used during the constructor.
+        createService();
+        assertTrue(isAcquired[0]);
+
+        mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY);
+        assertTrue(isAcquired[0]);
+
+        mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
+        assertFalse(isAcquired[0]);
+    }
+
+    @Test
+    public void testInattentiveSleep_hideWarningIfStayOnIsEnabledAndPluggedIn() {
+        setMinimumScreenOffTimeoutConfig(5);
+        setAttentiveWarningDuration(120);
+        setAttentiveTimeout(100);
+
+        Settings.Global.putInt(mContextSpy.getContentResolver(),
+                Settings.Global.STAY_ON_WHILE_PLUGGED_IN, BatteryManager.BATTERY_PLUGGED_AC);
+
+        createService();
+        startSystem();
+
+        verify(mInattentiveSleepWarningControllerMock, times(1)).show();
+        verify(mInattentiveSleepWarningControllerMock, never()).dismiss(anyBoolean());
+        when(mInattentiveSleepWarningControllerMock.isShown()).thenReturn(true);
+
+        setPluggedIn(true);
+        verify(mInattentiveSleepWarningControllerMock, atLeastOnce()).dismiss(true);
+    }
+
+    @Test
+    public void testInattentiveSleep_hideWarningIfInattentiveSleepIsDisabled() {
+        setMinimumScreenOffTimeoutConfig(5);
+        setAttentiveWarningDuration(120);
+        setAttentiveTimeout(100);
+
+        createService();
+        startSystem();
+
+        verify(mInattentiveSleepWarningControllerMock, times(1)).show();
+        verify(mInattentiveSleepWarningControllerMock, never()).dismiss(anyBoolean());
+        when(mInattentiveSleepWarningControllerMock.isShown()).thenReturn(true);
+
+        setAttentiveTimeout(-1);
+        mService.handleSettingsChangedLocked();
+
+        verify(mInattentiveSleepWarningControllerMock, atLeastOnce()).dismiss(true);
+    }
+
+    @Test
+    public void testInattentiveSleep_userActivityDismissesWarning() {
+        final DisplayInfo info = new DisplayInfo();
+        info.displayGroupId = Display.DEFAULT_DISPLAY_GROUP;
+        when(mDisplayManagerInternalMock.getDisplayInfo(Display.DEFAULT_DISPLAY)).thenReturn(info);
+        setMinimumScreenOffTimeoutConfig(5);
+        setAttentiveWarningDuration(1900);
+        setAttentiveTimeout(2000);
+
+        createService();
+        startSystem();
+
+        mService.getBinderServiceInstance().userActivity(Display.DEFAULT_DISPLAY, mClock.now(),
+                PowerManager.USER_ACTIVITY_EVENT_TOUCH, 0);
+        verify(mInattentiveSleepWarningControllerMock, never()).show();
+
+        advanceTime(150);
+        verify(mInattentiveSleepWarningControllerMock, times(1)).show();
+        verify(mInattentiveSleepWarningControllerMock, never()).dismiss(anyBoolean());
+        when(mInattentiveSleepWarningControllerMock.isShown()).thenReturn(true);
+
+        mService.getBinderServiceInstance().userActivity(Display.DEFAULT_DISPLAY, mClock.now(),
+                PowerManager.USER_ACTIVITY_EVENT_TOUCH, 0);
+        verify(mInattentiveSleepWarningControllerMock, times(1)).dismiss(true);
+    }
+
+    @Test
+    public void testInattentiveSleep_warningHiddenAfterWakingUp() {
+        setMinimumScreenOffTimeoutConfig(5);
+        setAttentiveWarningDuration(70);
+        setAttentiveTimeout(100);
+
+        createService();
+        startSystem();
+        advanceTime(50);
+        verify(mInattentiveSleepWarningControllerMock, atLeastOnce()).show();
+        when(mInattentiveSleepWarningControllerMock.isShown()).thenReturn(true);
+        advanceTime(70);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
+        forceAwake();
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+        verify(mInattentiveSleepWarningControllerMock, atLeastOnce()).dismiss(false);
+    }
+
+    @Test
+    public void testInattentiveSleep_warningStaysWhenDreaming() {
+        setMinimumScreenOffTimeoutConfig(5);
+        setAttentiveWarningDuration(70);
+        setAttentiveTimeout(100);
+        createService();
+        startSystem();
+        advanceTime(50);
+        verify(mInattentiveSleepWarningControllerMock, atLeastOnce()).show();
+        when(mInattentiveSleepWarningControllerMock.isShown()).thenReturn(true);
+
+        forceDream();
+        when(mDreamManagerInternalMock.isDreaming()).thenReturn(true);
+
+        advanceTime(10);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING);
+        verify(mInattentiveSleepWarningControllerMock, never()).dismiss(anyBoolean());
+    }
+
+    @Test
+    public void testInattentiveSleep_warningNotShownWhenSleeping() {
+        setMinimumScreenOffTimeoutConfig(5);
+        setAttentiveWarningDuration(70);
+        setAttentiveTimeout(100);
+        createService();
+        startSystem();
+
+        advanceTime(10);
+        forceSleep();
+
+        advanceTime(50);
+        verify(mInattentiveSleepWarningControllerMock, never()).show();
+    }
+
+    @Test
+    public void testInattentiveSleep_noWarningShownIfInattentiveSleepDisabled() {
+        setAttentiveTimeout(-1);
+        createService();
+        startSystem();
+        verify(mInattentiveSleepWarningControllerMock, never()).show();
+    }
+
+    @Test
+    public void testInattentiveSleep_goesToSleepAfterTimeout() {
+        setMinimumScreenOffTimeoutConfig(5);
+        setAttentiveTimeout(5);
+        createService();
+        startSystem();
+        advanceTime(20);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
+        assertThat(mService.getBinderServiceInstance().getLastSleepReason()).isEqualTo(
+                PowerManager.GO_TO_SLEEP_REASON_INATTENTIVE);
+    }
+
+    @Test
+    public void testInattentiveSleep_goesToSleepWithWakeLock() {
+        final String pkg = mContextSpy.getOpPackageName();
+        final Binder token = new Binder();
+        final String tag = "testInattentiveSleep_goesToSleepWithWakeLock";
+
+        setMinimumScreenOffTimeoutConfig(5);
+        setAttentiveTimeout(30);
+        createService();
+        startSystem();
+
+        mService.getBinderServiceInstance().acquireWakeLock(token,
+                PowerManager.SCREEN_BRIGHT_WAKE_LOCK, tag, pkg,
+                null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, null);
+
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+        advanceTime(60);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
+        assertThat(mService.getBinderServiceInstance().getLastSleepReason()).isEqualTo(
+                PowerManager.GO_TO_SLEEP_REASON_INATTENTIVE);
+    }
+
+    @Test
+    public void testInattentiveSleep_dreamEnds_goesToSleepAfterTimeout() {
+        setMinimumScreenOffTimeoutConfig(5);
+        setAttentiveTimeout(30000);
+        createService();
+        startSystem();
+
+        advanceTime(10000);
+        forceDream();
+        advanceTime(10000);
+        final String pkg = mContextSpy.getOpPackageName();
+        mService.getBinderServiceInstance().wakeUp(mClock.now(),
+                PowerManager.WAKE_REASON_DREAM_FINISHED, "PowerManagerServiceTest:DREAM_FINISHED",
+                pkg);
+        advanceTime(10001);
+
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
+        assertThat(mService.getBinderServiceInstance().getLastSleepReason()).isEqualTo(
+                PowerManager.GO_TO_SLEEP_REASON_INATTENTIVE);
+    }
+
+    @Test
+    public void testInattentiveSleep_wakeLockOnAfterRelease_inattentiveSleepTimeoutNotAffected() {
+        final DisplayInfo info = new DisplayInfo();
+        info.displayGroupId = Display.DEFAULT_DISPLAY_GROUP;
+        when(mDisplayManagerInternalMock.getDisplayInfo(Display.DEFAULT_DISPLAY)).thenReturn(info);
+
+        final String pkg = mContextSpy.getOpPackageName();
+        final Binder token = new Binder();
+        final String tag = "testInattentiveSleep_wakeLockOnAfterRelease";
+
+        setMinimumScreenOffTimeoutConfig(5);
+        setAttentiveTimeout(2000);
+        createService();
+        startSystem();
+
+        mService.getBinderServiceInstance().acquireWakeLock(token,
+                PowerManager.SCREEN_BRIGHT_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE, tag, pkg,
+                null /* workSource */, null /* historyTag */, Display.DEFAULT_DISPLAY, null);
+
+        advanceTime(1500);
+        mService.getBinderServiceInstance().releaseWakeLock(token, 0 /* flags */);
+
+        advanceTime(520);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
+        assertThat(mService.getBinderServiceInstance().getLastSleepReason()).isEqualTo(
+                PowerManager.GO_TO_SLEEP_REASON_INATTENTIVE);
+    }
+
+    @Test
+    public void testInattentiveSleep_userActivityNoChangeLights_inattentiveSleepTimeoutNotAffected() {
+        final DisplayInfo info = new DisplayInfo();
+        info.displayGroupId = Display.DEFAULT_DISPLAY_GROUP;
+        when(mDisplayManagerInternalMock.getDisplayInfo(Display.DEFAULT_DISPLAY)).thenReturn(info);
+
+        setMinimumScreenOffTimeoutConfig(5);
+        setAttentiveTimeout(2000);
+        createService();
+        startSystem();
+
+        advanceTime(1500);
+        mService.getBinderServiceInstance().userActivity(Display.DEFAULT_DISPLAY, mClock.now(),
+                PowerManager.USER_ACTIVITY_EVENT_OTHER,
+                PowerManager.USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS);
+
+        advanceTime(520);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
+        assertThat(mService.getBinderServiceInstance().getLastSleepReason()).isEqualTo(
+                PowerManager.GO_TO_SLEEP_REASON_INATTENTIVE);
+    }
+
+    @Test
+    public void testInattentiveSleep_userActivity_inattentiveSleepTimeoutExtended() {
+        final DisplayInfo info = new DisplayInfo();
+        info.displayGroupId = Display.DEFAULT_DISPLAY_GROUP;
+        when(mDisplayManagerInternalMock.getDisplayInfo(Display.DEFAULT_DISPLAY)).thenReturn(info);
+
+        setMinimumScreenOffTimeoutConfig(5);
+        setAttentiveTimeout(2000);
+        createService();
+        startSystem();
+
+        advanceTime(1500);
+        mService.getBinderServiceInstance().userActivity(Display.DEFAULT_DISPLAY, mClock.now(),
+                PowerManager.USER_ACTIVITY_EVENT_OTHER, 0 /* flags */);
+
+        advanceTime(520);
+        assertThat(mService.getGlobalWakefulnessLocked()).isNotEqualTo(WAKEFULNESS_ASLEEP);
+    }
+
+
+    @SuppressWarnings("GuardedBy")
+    @Test
+    public void testInattentiveSleep_goesToSleepFromDream() {
+        setAttentiveTimeout(20000);
+        createService();
+        startSystem();
+        setPluggedIn(true);
+        forceAwake();
+        forceDream();
+        when(mDreamManagerInternalMock.isDreaming()).thenReturn(true);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING);
+
+        advanceTime(20500);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
+    }
+
+    @Test
+    public void testWakeLock_affectsProperDisplayGroup() {
+        final int nonDefaultDisplayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
+        final AtomicReference<DisplayManagerInternal.DisplayGroupListener> listener =
+                new AtomicReference<>();
+        doAnswer((Answer<Void>) invocation -> {
+            listener.set(invocation.getArgument(0));
+            return null;
+        }).when(mDisplayManagerInternalMock).registerDisplayGroupListener(any());
+        final DisplayInfo info = new DisplayInfo();
+        info.displayGroupId = Display.DEFAULT_DISPLAY_GROUP;
+        when(mDisplayManagerInternalMock.getDisplayInfo(Display.DEFAULT_DISPLAY)).thenReturn(info);
+
+        final String pkg = mContextSpy.getOpPackageName();
+        final Binder token = new Binder();
+        final String tag = "testWakeLock_affectsProperDisplayGroup";
+
+        setMinimumScreenOffTimeoutConfig(5);
+        createService();
+        startSystem();
+        listener.get().onDisplayGroupAdded(nonDefaultDisplayGroupId);
+
+        mService.getBinderServiceInstance().acquireWakeLock(token,
+                PowerManager.SCREEN_BRIGHT_WAKE_LOCK, tag, pkg,
+                null /* workSource */, null /* historyTag */, Display.DEFAULT_DISPLAY, null);
+
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+        assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
+                WAKEFULNESS_AWAKE);
+        assertThat(mService.getWakefulnessLocked(nonDefaultDisplayGroupId)).isEqualTo(
+                WAKEFULNESS_AWAKE);
+
+        advanceTime(15000);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+        assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
+                WAKEFULNESS_AWAKE);
+        assertThat(mService.getWakefulnessLocked(nonDefaultDisplayGroupId)).isEqualTo(
+                WAKEFULNESS_DOZING);
+    }
+
+    @Test
+    public void testInvalidDisplayGroupWakeLock_affectsAllDisplayGroups() {
+        final int nonDefaultDisplayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
+        final AtomicReference<DisplayManagerInternal.DisplayGroupListener> listener =
+                new AtomicReference<>();
+        doAnswer((Answer<Void>) invocation -> {
+            listener.set(invocation.getArgument(0));
+            return null;
+        }).when(mDisplayManagerInternalMock).registerDisplayGroupListener(any());
+        final DisplayInfo info = new DisplayInfo();
+        info.displayGroupId = Display.DEFAULT_DISPLAY_GROUP;
+        when(mDisplayManagerInternalMock.getDisplayInfo(Display.DEFAULT_DISPLAY)).thenReturn(info);
+
+        final String pkg = mContextSpy.getOpPackageName();
+        final Binder token = new Binder();
+        final String tag = "testInvalidDisplayGroupWakeLock_affectsAllDisplayGroups";
+
+        setMinimumScreenOffTimeoutConfig(5);
+        createService();
+        startSystem();
+        listener.get().onDisplayGroupAdded(nonDefaultDisplayGroupId);
+
+        mService.getBinderServiceInstance().acquireWakeLock(token,
+                PowerManager.SCREEN_BRIGHT_WAKE_LOCK, tag, pkg,
+                null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, null);
+
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+        assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
+                WAKEFULNESS_AWAKE);
+        assertThat(mService.getWakefulnessLocked(nonDefaultDisplayGroupId)).isEqualTo(
+                WAKEFULNESS_AWAKE);
+
+        advanceTime(15000);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+        assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
+                WAKEFULNESS_AWAKE);
+        assertThat(mService.getWakefulnessLocked(nonDefaultDisplayGroupId)).isEqualTo(
+                WAKEFULNESS_AWAKE);
+    }
+
+    @Test
+    public void testRemovedDisplayGroupWakeLock_affectsNoDisplayGroups() {
+        final int nonDefaultDisplayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
+        final int nonDefaultDisplay = Display.DEFAULT_DISPLAY + 1;
+        final AtomicReference<DisplayManagerInternal.DisplayGroupListener> listener =
+                new AtomicReference<>();
+        doAnswer((Answer<Void>) invocation -> {
+            listener.set(invocation.getArgument(0));
+            return null;
+        }).when(mDisplayManagerInternalMock).registerDisplayGroupListener(any());
+        final DisplayInfo info = new DisplayInfo();
+        info.displayGroupId = nonDefaultDisplayGroupId;
+        when(mDisplayManagerInternalMock.getDisplayInfo(nonDefaultDisplay)).thenReturn(info);
+
+        doAnswer(inv -> {
+            when(mDreamManagerInternalMock.isDreaming()).thenReturn(true);
+            return null;
+        }).when(mDreamManagerInternalMock).startDream(anyBoolean(), anyString());
+
+        final String pkg = mContextSpy.getOpPackageName();
+        final Binder token = new Binder();
+        final String tag = "testRemovedDisplayGroupWakeLock_affectsNoDisplayGroups";
+
+        setMinimumScreenOffTimeoutConfig(5);
+        createService();
+        startSystem();
+        listener.get().onDisplayGroupAdded(nonDefaultDisplayGroupId);
+
+        mService.getBinderServiceInstance().acquireWakeLock(token,
+                PowerManager.SCREEN_BRIGHT_WAKE_LOCK, tag, pkg,
+                null /* workSource */, null /* historyTag */, nonDefaultDisplay, null);
+
+        assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
+                WAKEFULNESS_AWAKE);
+        assertThat(mService.getWakefulnessLocked(nonDefaultDisplayGroupId)).isEqualTo(
+                WAKEFULNESS_AWAKE);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+
+        listener.get().onDisplayGroupRemoved(nonDefaultDisplayGroupId);
+
+        advanceTime(15000);
+        assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
+                WAKEFULNESS_DOZING);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DOZING);
+    }
+
+    @Test
+    public void testBoot_ShouldBeAwake() {
+        createService();
+        startSystem();
+
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+        verify(mNotifierMock, never()).onGlobalWakefulnessChangeStarted(anyInt(), anyInt(),
+                anyLong());
+    }
+
+    @Test
+    public void testBoot_DesiredScreenPolicyShouldBeBright() {
+        createService();
+        startSystem();
+
+        assertThat(mService.getDesiredScreenPolicyLocked(Display.DEFAULT_DISPLAY)).isEqualTo(
+                DisplayPowerRequest.POLICY_BRIGHT);
+    }
+
+    @Test
+    public void testQuiescentBoot_ShouldBeAsleep() {
+        when(mSystemPropertiesMock.get(eq(SYSTEM_PROPERTY_QUIESCENT), any())).thenReturn("1");
+        createService();
+        startSystem();
+
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
+        verify(mNotifierMock).onGlobalWakefulnessChangeStarted(eq(WAKEFULNESS_ASLEEP), anyInt(),
+                anyLong());
+    }
+
+    @Test
+    public void testQuiescentBoot_DesiredScreenPolicyShouldBeOff() {
+        when(mSystemPropertiesMock.get(eq(SYSTEM_PROPERTY_QUIESCENT), any())).thenReturn("1");
+        createService();
+        startSystem();
+        assertThat(mService.getDesiredScreenPolicyLocked(Display.DEFAULT_DISPLAY)).isEqualTo(
+                DisplayPowerRequest.POLICY_OFF);
+    }
+
+    @Test
+    public void testQuiescentBoot_WakeUp_DesiredScreenPolicyShouldBeBright() {
+        when(mSystemPropertiesMock.get(eq(SYSTEM_PROPERTY_QUIESCENT), any())).thenReturn("1");
+        createService();
+        startSystem();
+        forceAwake();
+        assertThat(mService.getDesiredScreenPolicyLocked(Display.DEFAULT_DISPLAY)).isEqualTo(
+                DisplayPowerRequest.POLICY_BRIGHT);
+    }
+
+    @Test
+    public void testQuiescentBoot_WakeKeyBeforeBootCompleted_AwakeAfterBootCompleted() {
+        when(mSystemPropertiesMock.get(eq(SYSTEM_PROPERTY_QUIESCENT), any())).thenReturn("1");
+        createService();
+        startSystem();
+
+        mService.getBinderServiceInstance().wakeUp(mClock.now(),
+                PowerManager.WAKE_REASON_UNKNOWN, "testing IPowerManager.wakeUp()", "pkg.name");
+
+        mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+        assertThat(mService.getDesiredScreenPolicyLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
+                DisplayPowerRequest.POLICY_BRIGHT);
+    }
+
+    @Test
+    public void testIsAmbientDisplayAvailable_available() {
+        createService();
+        when(mAmbientDisplayConfigurationMock.ambientDisplayAvailable()).thenReturn(true);
+
+        assertThat(mService.getBinderServiceInstance().isAmbientDisplayAvailable()).isTrue();
+    }
+
+    @Test
+    public void testIsAmbientDisplayAvailable_unavailable() {
+        createService();
+        when(mAmbientDisplayConfigurationMock.ambientDisplayAvailable()).thenReturn(false);
+
+        assertThat(mService.getBinderServiceInstance().isAmbientDisplayAvailable()).isFalse();
+    }
+
+    @Test
+    public void testIsAmbientDisplaySuppressed_default_notSuppressed() {
+        createService();
+
+        assertThat(mService.getBinderServiceInstance().isAmbientDisplaySuppressed()).isFalse();
+    }
+
+    @Test
+    public void testIsAmbientDisplaySuppressed_suppressed() {
+        createService();
+        mService.getBinderServiceInstance().suppressAmbientDisplay("test", true);
+
+        assertThat(mService.getBinderServiceInstance().isAmbientDisplaySuppressed()).isTrue();
+    }
+
+    @Test
+    public void testIsAmbientDisplaySuppressed_notSuppressed() {
+        createService();
+        mService.getBinderServiceInstance().suppressAmbientDisplay("test", false);
+
+        assertThat(mService.getBinderServiceInstance().isAmbientDisplaySuppressed()).isFalse();
+    }
+
+    @Test
+    public void testIsAmbientDisplaySuppressed_multipleTokens_suppressed() {
+        createService();
+        mService.getBinderServiceInstance().suppressAmbientDisplay("test1", false);
+        mService.getBinderServiceInstance().suppressAmbientDisplay("test2", true);
+
+        assertThat(mService.getBinderServiceInstance().isAmbientDisplaySuppressed()).isTrue();
+    }
+
+    @Test
+    public void testIsAmbientDisplaySuppressed_multipleTokens_notSuppressed() {
+        createService();
+        mService.getBinderServiceInstance().suppressAmbientDisplay("test1", false);
+        mService.getBinderServiceInstance().suppressAmbientDisplay("test2", false);
+
+        assertThat(mService.getBinderServiceInstance().isAmbientDisplaySuppressed()).isFalse();
+    }
+
+    @Test
+    public void testIsAmbientDisplaySuppressedForToken_default_notSuppressed() {
+        createService();
+
+        assertThat(mService.getBinderServiceInstance().isAmbientDisplaySuppressedForToken("test"))
+            .isFalse();
+    }
+
+    @Test
+    public void testIsAmbientDisplaySuppressedForToken_suppressed() {
+        createService();
+        mService.getBinderServiceInstance().suppressAmbientDisplay("test", true);
+
+        assertThat(mService.getBinderServiceInstance().isAmbientDisplaySuppressedForToken("test"))
+            .isTrue();
+    }
+
+    @Test
+    public void testIsAmbientDisplaySuppressedForToken_notSuppressed() {
+        createService();
+        mService.getBinderServiceInstance().suppressAmbientDisplay("test", false);
+
+        assertThat(mService.getBinderServiceInstance().isAmbientDisplaySuppressedForToken("test"))
+            .isFalse();
+    }
+
+    @Test
+    public void testIsAmbientDisplaySuppressedForToken_multipleTokens_suppressed() {
+        createService();
+        mService.getBinderServiceInstance().suppressAmbientDisplay("test1", true);
+        mService.getBinderServiceInstance().suppressAmbientDisplay("test2", true);
+
+        assertThat(mService.getBinderServiceInstance().isAmbientDisplaySuppressedForToken("test1"))
+            .isTrue();
+        assertThat(mService.getBinderServiceInstance().isAmbientDisplaySuppressedForToken("test2"))
+            .isTrue();
+    }
+
+    @Test
+    public void testIsAmbientDisplaySuppressedForToken_multipleTokens_notSuppressed() {
+        createService();
+        mService.getBinderServiceInstance().suppressAmbientDisplay("test1", true);
+        mService.getBinderServiceInstance().suppressAmbientDisplay("test2", false);
+
+        assertThat(mService.getBinderServiceInstance().isAmbientDisplaySuppressedForToken("test1"))
+            .isTrue();
+        assertThat(mService.getBinderServiceInstance().isAmbientDisplaySuppressedForToken("test2"))
+            .isFalse();
+    }
+
+    @Test
+    public void testIsAmbientDisplaySuppressedForTokenByApp_ambientDisplayUnavailable() {
+        createService();
+        when(mAmbientDisplayConfigurationMock.ambientDisplayAvailable()).thenReturn(false);
+
+        BinderService service = mService.getBinderServiceInstance();
+        assertThat(service.isAmbientDisplaySuppressedForTokenByApp("test", Binder.getCallingUid()))
+            .isFalse();
+    }
+
+    @Test
+    public void testIsAmbientDisplaySuppressedForTokenByApp_default() {
+        createService();
+
+        BinderService service = mService.getBinderServiceInstance();
+        assertThat(service.isAmbientDisplaySuppressedForTokenByApp("test", Binder.getCallingUid()))
+            .isFalse();
+    }
+
+    @Test
+    public void testIsAmbientDisplaySuppressedForTokenByApp_suppressedByCallingApp() {
+        createService();
+        BinderService service = mService.getBinderServiceInstance();
+        service.suppressAmbientDisplay("test", true);
+
+        assertThat(service.isAmbientDisplaySuppressedForTokenByApp("test", Binder.getCallingUid()))
+            .isTrue();
+        // Check that isAmbientDisplaySuppressedForTokenByApp doesn't return true for another app.
+        assertThat(service.isAmbientDisplaySuppressedForTokenByApp("test", /* appUid= */ 123))
+            .isFalse();
+    }
+
+    @Test
+    public void testIsAmbientDisplaySuppressedForTokenByApp_notSuppressedByCallingApp() {
+        createService();
+        BinderService service = mService.getBinderServiceInstance();
+        service.suppressAmbientDisplay("test", false);
+
+        assertThat(service.isAmbientDisplaySuppressedForTokenByApp("test", Binder.getCallingUid()))
+            .isFalse();
+        // Check that isAmbientDisplaySuppressedForTokenByApp doesn't return true for another app.
+        assertThat(service.isAmbientDisplaySuppressedForTokenByApp("test", /* appUid= */ 123))
+            .isFalse();
+    }
+
+    @Test
+    public void testIsAmbientDisplaySuppressedForTokenByApp_multipleTokensSuppressedByCallingApp() {
+        createService();
+        BinderService service = mService.getBinderServiceInstance();
+        service.suppressAmbientDisplay("test1", true);
+        service.suppressAmbientDisplay("test2", true);
+
+        assertThat(service.isAmbientDisplaySuppressedForTokenByApp("test1", Binder.getCallingUid()))
+            .isTrue();
+        assertThat(service.isAmbientDisplaySuppressedForTokenByApp("test2", Binder.getCallingUid()))
+            .isTrue();
+        // Check that isAmbientDisplaySuppressedForTokenByApp doesn't return true for another app.
+        assertThat(service.isAmbientDisplaySuppressedForTokenByApp("test1", /* appUid= */ 123))
+            .isFalse();
+        assertThat(service.isAmbientDisplaySuppressedForTokenByApp("test2", /* appUid= */ 123))
+            .isFalse();
+    }
+
+    @Test
+    public void testGetAmbientDisplaySuppressionTokens_default() {
+        createService();
+        BinderService service = mService.getBinderServiceInstance();
+
+        assertThat(service.getAmbientDisplaySuppressionTokens()).isEmpty();
+    }
+
+    @Test
+    public void testGetAmbientDisplaySuppressionTokens_singleToken() {
+        createService();
+        BinderService service = mService.getBinderServiceInstance();
+        service.suppressAmbientDisplay("test1", true);
+        service.suppressAmbientDisplay("test2", false);
+
+        assertThat(service.getAmbientDisplaySuppressionTokens()).containsExactly("test1");
+    }
+
+    @Test
+    public void testGetAmbientDisplaySuppressionTokens_multipleTokens() {
+        createService();
+        BinderService service = mService.getBinderServiceInstance();
+        service.suppressAmbientDisplay("test1", true);
+        service.suppressAmbientDisplay("test2", true);
+
+        assertThat(service.getAmbientDisplaySuppressionTokens())
+                .containsExactly("test1", "test2");
+    }
+
+    @Test
+    public void testSetPowerBoost_redirectsCallToNativeWrapper() {
+        createService();
+        startSystem();
+
+        mService.getBinderServiceInstance().setPowerBoost(Boost.INTERACTION, 1234);
+
+        verify(mNativeWrapperMock).nativeSetPowerBoost(eq(Boost.INTERACTION), eq(1234));
+    }
+
+    @Test
+    public void testSetPowerMode_redirectsCallToNativeWrapper() {
+        createService();
+        startSystem();
+
+        // Enabled launch boost in BatterySaverController to allow setting launch mode.
+        when(mBatterySaverControllerMock.isLaunchBoostDisabled()).thenReturn(false);
+        when(mNativeWrapperMock.nativeSetPowerMode(anyInt(), anyBoolean())).thenReturn(true);
+
+        mService.getBinderServiceInstance().setPowerMode(Mode.LAUNCH, true);
+
+        verify(mNativeWrapperMock).nativeSetPowerMode(eq(Mode.LAUNCH), eq(true));
+    }
+
+    @Test
+    public void testSetPowerMode_withLaunchBoostDisabledAndModeLaunch_ignoresCallToEnable() {
+        createService();
+        startSystem();
+
+        // Disables launch boost in BatterySaverController.
+        when(mBatterySaverControllerMock.isLaunchBoostDisabled()).thenReturn(true);
+        when(mNativeWrapperMock.nativeSetPowerMode(anyInt(), anyBoolean())).thenReturn(true);
+
+        mService.getBinderServiceInstance().setPowerMode(Mode.LAUNCH, true);
+        mService.getBinderServiceInstance().setPowerMode(Mode.LAUNCH, false);
+
+        verify(mNativeWrapperMock, never()).nativeSetPowerMode(eq(Mode.LAUNCH), eq(true));
+        verify(mNativeWrapperMock).nativeSetPowerMode(eq(Mode.LAUNCH), eq(false));
+    }
+
+    @Test
+    public void testSetPowerModeChecked_returnsNativeCallResult() {
+        createService();
+        startSystem();
+
+        // Disables launch boost in BatterySaverController.
+        when(mBatterySaverControllerMock.isLaunchBoostDisabled()).thenReturn(true);
+        when(mNativeWrapperMock.nativeSetPowerMode(anyInt(), anyBoolean())).thenReturn(true);
+        when(mNativeWrapperMock.nativeSetPowerMode(eq(Mode.INTERACTIVE), anyBoolean()))
+            .thenReturn(false);
+
+        // Ignored because isLaunchBoostDisabled is true. Should return false.
+        assertFalse(mService.getBinderServiceInstance().setPowerModeChecked(Mode.LAUNCH, true));
+        // Native calls return true.
+        assertTrue(mService.getBinderServiceInstance().setPowerModeChecked(Mode.LAUNCH, false));
+        assertTrue(mService.getBinderServiceInstance().setPowerModeChecked(Mode.LOW_POWER, true));
+        // Native call for interactive returns false.
+        assertFalse(
+                mService.getBinderServiceInstance().setPowerModeChecked(Mode.INTERACTIVE, false));
+    }
+
+    @Test
+    public void testMultiDisplay_wakefulnessUpdates() {
+        final int nonDefaultDisplayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
+        final AtomicReference<DisplayManagerInternal.DisplayGroupListener> listener =
+                new AtomicReference<>();
+        doAnswer((Answer<Void>) invocation -> {
+            listener.set(invocation.getArgument(0));
+            return null;
+        }).when(mDisplayManagerInternalMock).registerDisplayGroupListener(any());
+
+        createService();
+        startSystem();
+        listener.get().onDisplayGroupAdded(nonDefaultDisplayGroupId);
+
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+
+        mService.setWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP, WAKEFULNESS_ASLEEP, 0, 0, 0, 0,
+                null, null);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+
+        mService.setWakefulnessLocked(nonDefaultDisplayGroupId, WAKEFULNESS_ASLEEP, 0, 0, 0, 0,
+                null, null);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
+
+        mService.setWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP, WAKEFULNESS_AWAKE, 0, 0, 0, 0,
+                null, null);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+    }
+
+    @Test
+    public void testMultiDisplay_addDisplayGroup_wakesDeviceUp() {
+        final int nonDefaultDisplayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
+        final AtomicReference<DisplayManagerInternal.DisplayGroupListener> listener =
+                new AtomicReference<>();
+        doAnswer((Answer<Void>) invocation -> {
+            listener.set(invocation.getArgument(0));
+            return null;
+        }).when(mDisplayManagerInternalMock).registerDisplayGroupListener(any());
+
+        createService();
+        startSystem();
+
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+
+        mService.setWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP, WAKEFULNESS_ASLEEP, 0, 0, 0, 0,
+                null, null);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
+
+        listener.get().onDisplayGroupAdded(nonDefaultDisplayGroupId);
+
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+    }
+
+    @Test
+    public void testMultiDisplay_removeDisplayGroup_updatesWakefulness() {
+        final int nonDefaultDisplayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
+        final AtomicReference<DisplayManagerInternal.DisplayGroupListener> listener =
+                new AtomicReference<>();
+        doAnswer((Answer<Void>) invocation -> {
+            listener.set(invocation.getArgument(0));
+            return null;
+        }).when(mDisplayManagerInternalMock).registerDisplayGroupListener(any());
+
+        createService();
+        startSystem();
+        listener.get().onDisplayGroupAdded(nonDefaultDisplayGroupId);
+
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+
+        mService.setWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP, WAKEFULNESS_ASLEEP, 0, 0, 0, 0,
+                null, null);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+
+        listener.get().onDisplayGroupRemoved(nonDefaultDisplayGroupId);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
+
+        mService.setWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP, WAKEFULNESS_AWAKE, 0, 0, 0, 0,
+                null, null);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+    }
+
+    @Test
+    public void testMultiDisplay_updatesLastGlobalWakeTime() {
+        final int nonDefaultPowerGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
+        final AtomicReference<DisplayManagerInternal.DisplayGroupListener> listener =
+                new AtomicReference<>();
+        long eventTime1 = 10;
+        long eventTime2 = eventTime1 + 1;
+        long eventTime3 = eventTime2 + 1;
+        long eventTime4 = eventTime3 + 1;
+        doAnswer((Answer<Void>) invocation -> {
+            listener.set(invocation.getArgument(0));
+            return null;
+        }).when(mDisplayManagerInternalMock).registerDisplayGroupListener(any());
+
+        createService();
+        startSystem();
+        listener.get().onDisplayGroupAdded(nonDefaultPowerGroupId);
+
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+
+        mService.setWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP, WAKEFULNESS_DOZING, eventTime1,
+                0, PowerManager.GO_TO_SLEEP_REASON_INATTENTIVE, 0, null, null);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+
+        mService.setWakefulnessLocked(nonDefaultPowerGroupId, WAKEFULNESS_DOZING, eventTime2,
+                0, PowerManager.GO_TO_SLEEP_REASON_APPLICATION, 0, null, null);
+        long eventElapsedRealtime1 = mLastElapsedRealtime;
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DOZING);
+        assertThat(mService.getBinderServiceInstance().getLastSleepReason()).isEqualTo(
+                PowerManager.GO_TO_SLEEP_REASON_APPLICATION);
+
+        mService.setWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP, WAKEFULNESS_AWAKE,
+                eventTime3, /* uid= */ 0, PowerManager.WAKE_REASON_PLUGGED_IN, /* opUid= */
+                0, /* opPackageName= */ null, /* details= */ null);
+        long eventElapsedRealtime2 = mLastElapsedRealtime;
+        PowerManager.WakeData wakeData = mService.getLocalServiceInstance().getLastWakeup();
+        assertThat(wakeData.wakeTime).isEqualTo(eventTime3);
+        assertThat(wakeData.wakeReason).isEqualTo(PowerManager.WAKE_REASON_PLUGGED_IN);
+        assertThat(wakeData.sleepDurationRealtime)
+                .isEqualTo(eventElapsedRealtime2 - eventElapsedRealtime1);
+
+        // The global wake time and reason as well as sleep duration shouldn't change when another
+        // PowerGroup wakes up.
+        mService.setWakefulnessLocked(nonDefaultPowerGroupId, WAKEFULNESS_AWAKE,
+                eventTime4, /* uid= */ 0, PowerManager.WAKE_REASON_CAMERA_LAUNCH, /* opUid= */
+                0, /* opPackageName= */ null, /* details= */ null);
+        PowerManager.WakeData wakeData2 = mService.getLocalServiceInstance().getLastWakeup();
+        assertThat(wakeData2).isEqualTo(wakeData);
+    }
+
+    @Test
+    public void testMultiDisplay_defaultDisplayCanDoze() {
+        createService();
+        startSystem();
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+        assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
+                WAKEFULNESS_AWAKE);
+
+        forceDozing();
+        // Allow handleSandman() to be called asynchronously
+        advanceTime(500);
+        verify(mDreamManagerInternalMock).startDream(eq(true), anyString());
+    }
+
+    @Test
+    public void testMultiDisplay_twoDisplays_defaultDisplayCanDoze() {
+        final int nonDefaultDisplayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
+        final int nonDefaultDisplay = Display.DEFAULT_DISPLAY + 1;
+        final AtomicReference<DisplayManagerInternal.DisplayGroupListener> listener =
+                new AtomicReference<>();
+        doAnswer((Answer<Void>) invocation -> {
+            listener.set(invocation.getArgument(0));
+            return null;
+        }).when(mDisplayManagerInternalMock).registerDisplayGroupListener(any());
+        final DisplayInfo info = new DisplayInfo();
+        info.displayGroupId = nonDefaultDisplayGroupId;
+        when(mDisplayManagerInternalMock.getDisplayInfo(nonDefaultDisplay)).thenReturn(info);
+
+        createService();
+        startSystem();
+
+        listener.get().onDisplayGroupAdded(nonDefaultDisplayGroupId);
+
+        assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
+                WAKEFULNESS_AWAKE);
+        assertThat(mService.getWakefulnessLocked(nonDefaultDisplayGroupId)).isEqualTo(
+                WAKEFULNESS_AWAKE);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+
+        forceDozing();
+
+        assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
+                WAKEFULNESS_DOZING);
+        assertThat(mService.getWakefulnessLocked(nonDefaultDisplayGroupId)).isEqualTo(
+                WAKEFULNESS_AWAKE);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+
+        // Allow handleSandman() to be called asynchronously
+        advanceTime(500);
+        verify(mDreamManagerInternalMock).startDream(eq(true), anyString());
+    }
+
+    @Test
+    public void testMultiDisplay_addNewDisplay_becomeGloballyAwakeButDefaultRemainsDozing() {
+        final int nonDefaultDisplayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
+        final int nonDefaultDisplay = Display.DEFAULT_DISPLAY + 1;
+        final AtomicReference<DisplayManagerInternal.DisplayGroupListener> listener =
+                new AtomicReference<>();
+        doAnswer((Answer<Void>) invocation -> {
+            listener.set(invocation.getArgument(0));
+            return null;
+        }).when(mDisplayManagerInternalMock).registerDisplayGroupListener(any());
+        final DisplayInfo info = new DisplayInfo();
+        info.displayGroupId = nonDefaultDisplayGroupId;
+        when(mDisplayManagerInternalMock.getDisplayInfo(nonDefaultDisplay)).thenReturn(info);
+
+        doAnswer(inv -> {
+            when(mDreamManagerInternalMock.isDreaming()).thenReturn(true);
+            return null;
+        }).when(mDreamManagerInternalMock).startDream(anyBoolean(), anyString());
+
+        createService();
+        startSystem();
+
+        assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
+                WAKEFULNESS_AWAKE);
+
+        forceDozing();
+        advanceTime(500);
+
+        assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
+                WAKEFULNESS_DOZING);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DOZING);
+        verify(mDreamManagerInternalMock).stopDream(anyBoolean(), anyString());
+        verify(mDreamManagerInternalMock).startDream(eq(true), anyString());
+
+        listener.get().onDisplayGroupAdded(nonDefaultDisplayGroupId);
+        advanceTime(500);
+
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+        assertThat(mService.getWakefulnessLocked(nonDefaultDisplayGroupId)).isEqualTo(
+                WAKEFULNESS_AWAKE);
+        assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
+                WAKEFULNESS_DOZING);
+
+        // Make sure there were no additional calls to stopDream or startDream
+        verify(mDreamManagerInternalMock, atMost(1)).stopDream(anyBoolean(), anyString());
+        verify(mDreamManagerInternalMock, atMost(1)).startDream(eq(true), anyString());
+    }
+
+    @Test
+    public void testLastSleepTime_notUpdatedWhenDreaming() {
+        createService();
+        startSystem();
+
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+        PowerManager.WakeData initialWakeData = mService.getLocalServiceInstance().getLastWakeup();
+
+        forceDream();
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING);
+        assertThat(mService.getLocalServiceInstance().getLastWakeup()).isEqualTo(initialWakeData);
+    }
+
+    @Test
+    public void testMultiDisplay_onlyOneDisplaySleeps_onWakefulnessChangedEventsFire() {
+        createService();
+        startSystem();
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+        forceSleep();
+        assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
+                WAKEFULNESS_ASLEEP);
+
+        verify(mNotifierMock).onGroupWakefulnessChangeStarted(eq(Display.DEFAULT_DISPLAY_GROUP),
+                eq(WAKEFULNESS_ASLEEP), eq(PowerManager.GO_TO_SLEEP_REASON_APPLICATION), anyLong());
+        verify(mNotifierMock).onGlobalWakefulnessChangeStarted(eq(WAKEFULNESS_ASLEEP),
+                eq(PowerManager.GO_TO_SLEEP_REASON_APPLICATION), anyLong());
+    }
+
+    @Test
+    public void testMultiDisplay_bothDisplaysSleep_onWakefulnessChangedEventsFireCorrectly() {
+        final int nonDefaultDisplayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
+        final int nonDefaultDisplay = Display.DEFAULT_DISPLAY + 1;
+        final AtomicReference<DisplayManagerInternal.DisplayGroupListener> listener =
+                new AtomicReference<>();
+        doAnswer((Answer<Void>) invocation -> {
+            listener.set(invocation.getArgument(0));
+            return null;
+        }).when(mDisplayManagerInternalMock).registerDisplayGroupListener(any());
+        final DisplayInfo info = new DisplayInfo();
+        info.displayGroupId = nonDefaultDisplayGroupId;
+        when(mDisplayManagerInternalMock.getDisplayInfo(nonDefaultDisplay)).thenReturn(info);
+
+        createService();
+        startSystem();
+
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+        listener.get().onDisplayGroupAdded(nonDefaultDisplayGroupId);
+
+        mService.setWakefulnessLocked(nonDefaultDisplayGroupId, WAKEFULNESS_ASLEEP, 0, 0,
+                PowerManager.GO_TO_SLEEP_REASON_APPLICATION, 0, null, null);
+        mService.setWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP, WAKEFULNESS_ASLEEP, 0, 0,
+                PowerManager.GO_TO_SLEEP_REASON_APPLICATION, 0, null, null);
+
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
+        assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
+                WAKEFULNESS_ASLEEP);
+        assertThat(mService.getWakefulnessLocked(nonDefaultDisplayGroupId)).isEqualTo(
+                WAKEFULNESS_ASLEEP);
+
+        verify(mNotifierMock).onGroupWakefulnessChangeStarted(eq(nonDefaultDisplayGroupId),
+                eq(WAKEFULNESS_ASLEEP), eq(PowerManager.GO_TO_SLEEP_REASON_APPLICATION), anyLong());
+        verify(mNotifierMock).onGroupWakefulnessChangeStarted(eq(Display.DEFAULT_DISPLAY_GROUP),
+                eq(WAKEFULNESS_ASLEEP), eq(PowerManager.GO_TO_SLEEP_REASON_APPLICATION), anyLong());
+        verify(mNotifierMock).onGlobalWakefulnessChangeStarted(eq(WAKEFULNESS_ASLEEP),
+                eq(PowerManager.GO_TO_SLEEP_REASON_APPLICATION), anyLong());
+    }
+
+    @Test
+    public void testMultiDisplay_separateWakeStates_onWakefulnessChangedEventsFireCorrectly() {
+        final int nonDefaultDisplayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
+        final int nonDefaultDisplay = Display.DEFAULT_DISPLAY + 1;
+        final AtomicReference<DisplayManagerInternal.DisplayGroupListener> listener =
+                new AtomicReference<>();
+        doAnswer((Answer<Void>) invocation -> {
+            listener.set(invocation.getArgument(0));
+            return null;
+        }).when(mDisplayManagerInternalMock).registerDisplayGroupListener(any());
+        final DisplayInfo info = new DisplayInfo();
+        info.displayGroupId = nonDefaultDisplayGroupId;
+        when(mDisplayManagerInternalMock.getDisplayInfo(nonDefaultDisplay)).thenReturn(info);
+
+        createService();
+        startSystem();
+
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+        listener.get().onDisplayGroupAdded(nonDefaultDisplayGroupId);
+
+        final String pkg = mContextSpy.getOpPackageName();
+        final Binder token = new Binder();
+        final String tag =
+                "testMultiDisplay_separateWakeStates_onWakefulnessChangedEventFiresCorrectly";
+        mService.getBinderServiceInstance().acquireWakeLock(token,
+                PowerManager.SCREEN_BRIGHT_WAKE_LOCK, tag, pkg,
+                null /* workSource */, null /* historyTag */, nonDefaultDisplay, null);
+
+        forceSleep();
+
+        // The wakelock should have kept the second display awake, and we should notify that the
+        // default display went to sleep.
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+        assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
+                WAKEFULNESS_ASLEEP);
+        assertThat(mService.getWakefulnessLocked(nonDefaultDisplayGroupId)).isEqualTo(
+                WAKEFULNESS_AWAKE);
+        verify(mNotifierMock).onGroupWakefulnessChangeStarted(eq(nonDefaultDisplayGroupId),
+                eq(WAKEFULNESS_AWAKE), eq(PowerManager.WAKE_REASON_DISPLAY_GROUP_ADDED), anyLong());
+        verify(mNotifierMock).onGroupWakefulnessChangeStarted(eq(Display.DEFAULT_DISPLAY_GROUP),
+                eq(WAKEFULNESS_ASLEEP), eq(PowerManager.GO_TO_SLEEP_REASON_APPLICATION), anyLong());
+        verify(mNotifierMock, never()).onGlobalWakefulnessChangeStarted(eq(WAKEFULNESS_ASLEEP),
+                anyInt(), anyLong());
+    }
+
+    @Test
+    public void testMultiDisplay_oneDisplayGroupChanges_globalDoesNotChange() {
+        final int nonDefaultDisplayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
+        final int nonDefaultDisplay = Display.DEFAULT_DISPLAY + 1;
+        final AtomicReference<DisplayManagerInternal.DisplayGroupListener> listener =
+                new AtomicReference<>();
+        doAnswer((Answer<Void>) invocation -> {
+            listener.set(invocation.getArgument(0));
+            return null;
+        }).when(mDisplayManagerInternalMock).registerDisplayGroupListener(any());
+        final DisplayInfo info = new DisplayInfo();
+        info.displayGroupId = nonDefaultDisplayGroupId;
+        when(mDisplayManagerInternalMock.getDisplayInfo(nonDefaultDisplay)).thenReturn(info);
+
+        createService();
+        startSystem();
+
+        listener.get().onDisplayGroupAdded(nonDefaultDisplayGroupId);
+
+        assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
+                WAKEFULNESS_AWAKE);
+        assertThat(mService.getWakefulnessLocked(nonDefaultDisplayGroupId)).isEqualTo(
+                WAKEFULNESS_AWAKE);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+
+        long eventTime = mClock.now();
+        mService.setWakefulnessLocked(nonDefaultDisplayGroupId, WAKEFULNESS_ASLEEP, eventTime, 0,
+                PowerManager.GO_TO_SLEEP_REASON_APPLICATION, 0, null, null);
+
+        assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
+                WAKEFULNESS_AWAKE);
+        assertThat(mService.getWakefulnessLocked(nonDefaultDisplayGroupId)).isEqualTo(
+                WAKEFULNESS_ASLEEP);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+        verify(mNotifierMock, never()).onGlobalWakefulnessChangeStarted(anyInt(), anyInt(),
+                anyLong());
+        verify(mNotifierMock, atMost(1)).onGroupWakefulnessChangeStarted(
+                eq(nonDefaultDisplayGroupId), eq(WAKEFULNESS_ASLEEP),
+                eq(PowerManager.GO_TO_SLEEP_REASON_APPLICATION), eq(eventTime));
+    }
+
+    @Test
+    public void testMultiDisplay_isInteractive_nonExistentGroup() {
+        createService();
+        startSystem();
+
+        int nonExistentDisplayGroup = 999;
+        BinderService binderService = mService.getBinderServiceInstance();
+        assertThat(binderService.isDisplayInteractive(nonExistentDisplayGroup)).isFalse();
+    }
+
+    private void testMultiDisplay_isInteractive_returnsCorrectValue(
+            boolean defaultDisplayAwake, boolean secondGroupDisplayAwake) {
+        final int nonDefaultDisplayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
+        // We use a display id that does not match the group id, to make sure we aren't accidentally
+        // confusing display id's and display group id's in the implementation.
+        final int nonDefaultDisplay = Display.DEFAULT_DISPLAY + 17;
+        final AtomicReference<DisplayManagerInternal.DisplayGroupListener> listener =
+                new AtomicReference<>();
+        doAnswer((Answer<Void>) invocation -> {
+            listener.set(invocation.getArgument(0));
+            return null;
+        }).when(mDisplayManagerInternalMock).registerDisplayGroupListener(any());
+
+        final DisplayInfo defaultDisplayInfo = new DisplayInfo();
+        defaultDisplayInfo.displayGroupId = Display.DEFAULT_DISPLAY_GROUP;
+        when(mDisplayManagerInternalMock.getDisplayInfo(Display.DEFAULT_DISPLAY)).thenReturn(
+                defaultDisplayInfo);
+
+        final DisplayInfo secondDisplayInfo = new DisplayInfo();
+        secondDisplayInfo.displayGroupId = nonDefaultDisplayGroupId;
+        when(mDisplayManagerInternalMock.getDisplayInfo(nonDefaultDisplay)).thenReturn(
+                secondDisplayInfo);
+
+        createService();
+        startSystem();
+        listener.get().onDisplayGroupAdded(nonDefaultDisplayGroupId);
+
+        if (!defaultDisplayAwake) {
+            mService.setWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP, WAKEFULNESS_ASLEEP,
+                    mClock.now(), 0, PowerManager.GO_TO_SLEEP_REASON_APPLICATION, 0, null, null);
+        }
+        if (!secondGroupDisplayAwake) {
+            mService.setWakefulnessLocked(nonDefaultDisplayGroupId, WAKEFULNESS_ASLEEP,
+                    mClock.now(), 0,
+                    PowerManager.GO_TO_SLEEP_REASON_APPLICATION, 0, null, null);
+        }
+        assertThat(PowerManagerInternal.isInteractive(
+                mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP))).isEqualTo(
+                defaultDisplayAwake);
+        assertThat(PowerManagerInternal.isInteractive(
+                mService.getWakefulnessLocked(nonDefaultDisplayGroupId))).isEqualTo(
+                secondGroupDisplayAwake);
+
+        BinderService binderService = mService.getBinderServiceInstance();
+        assertThat(binderService.isInteractive()).isEqualTo(
+                defaultDisplayAwake || secondGroupDisplayAwake);
+        assertThat(binderService.isDisplayInteractive(Display.DEFAULT_DISPLAY)).isEqualTo(
+                defaultDisplayAwake);
+        assertThat(binderService.isDisplayInteractive(nonDefaultDisplay)).isEqualTo(
+                secondGroupDisplayAwake);
+    }
+
+    @Test
+    public void testMultiDisplay_isInteractive_defaultGroupIsAwakeSecondGroupIsAwake() {
+        testMultiDisplay_isInteractive_returnsCorrectValue(true, true);
+    }
+
+    @Test
+    public void testMultiDisplay_isInteractive_defaultGroupIsAwakeSecondGroupIsAsleep() {
+        testMultiDisplay_isInteractive_returnsCorrectValue(true, false);
+    }
+
+    @Test
+    public void testMultiDisplay_isInteractive_defaultGroupIsAsleepSecondGroupIsAwake() {
+        testMultiDisplay_isInteractive_returnsCorrectValue(false, true);
+    }
+
+    @Test
+    public void testMultiDisplay_isInteractive_bothGroupsAreAsleep() {
+        testMultiDisplay_isInteractive_returnsCorrectValue(false, false);
+    }
+
+    @Test
+    public void testMultiDisplay_defaultGroupWakefulnessChange_causesIsInteractiveInvalidate()
+            throws Exception {
+        final int nonDefaultDisplayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
+        final int nonDefaultDisplay = Display.DEFAULT_DISPLAY + 1;
+        final AtomicReference<DisplayManagerInternal.DisplayGroupListener> listener =
+                new AtomicReference<>();
+        doAnswer((Answer<Void>) invocation -> {
+            listener.set(invocation.getArgument(0));
+            return null;
+        }).when(mDisplayManagerInternalMock).registerDisplayGroupListener(any());
+        final DisplayInfo info = new DisplayInfo();
+        info.displayGroupId = nonDefaultDisplayGroupId;
+        when(mDisplayManagerInternalMock.getDisplayInfo(nonDefaultDisplay)).thenReturn(info);
+        createService();
+        startSystem();
+        listener.get().onDisplayGroupAdded(nonDefaultDisplayGroupId);
+
+        verify(mInvalidateInteractiveCachesMock).call();
+
+        mService.setWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP, WAKEFULNESS_ASLEEP,
+                mClock.now(), 0, PowerManager.GO_TO_SLEEP_REASON_APPLICATION, 0, null, null);
+
+        verify(mInvalidateInteractiveCachesMock, times(2)).call();
+    }
+
+    @Test
+    public void testMultiDisplay_secondGroupWakefulness_causesIsInteractiveInvalidate()
+            throws Exception {
+        final int nonDefaultDisplayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
+        final int nonDefaultDisplay = Display.DEFAULT_DISPLAY + 1;
+        final AtomicReference<DisplayManagerInternal.DisplayGroupListener> listener =
+                new AtomicReference<>();
+        doAnswer((Answer<Void>) invocation -> {
+            listener.set(invocation.getArgument(0));
+            return null;
+        }).when(mDisplayManagerInternalMock).registerDisplayGroupListener(any());
+        final DisplayInfo info = new DisplayInfo();
+        info.displayGroupId = nonDefaultDisplayGroupId;
+        when(mDisplayManagerInternalMock.getDisplayInfo(nonDefaultDisplay)).thenReturn(info);
+        createService();
+        startSystem();
+        listener.get().onDisplayGroupAdded(nonDefaultDisplayGroupId);
+
+        verify(mInvalidateInteractiveCachesMock).call();
+
+        mService.setWakefulnessLocked(nonDefaultDisplayGroupId, WAKEFULNESS_ASLEEP, mClock.now(),
+                0, PowerManager.GO_TO_SLEEP_REASON_APPLICATION, 0, null, null);
+
+        verify(mInvalidateInteractiveCachesMock, times(2)).call();
+    }
+
+    @Test
+    public void testGetFullPowerSavePolicy_returnsStateMachineResult() {
+        createService();
+        startSystem();
+        BatterySaverPolicyConfig mockReturnConfig = new BatterySaverPolicyConfig.Builder().build();
+        when(mBatterySaverStateMachineMock.getFullBatterySaverPolicy())
+                .thenReturn(mockReturnConfig);
+
+        mService.getBinderServiceInstance().setPowerSaveModeEnabled(true);
+        BatterySaverPolicyConfig policyConfig =
+                mService.getBinderServiceInstance().getFullPowerSavePolicy();
+        assertThat(mockReturnConfig).isEqualTo(policyConfig);
+        verify(mBatterySaverStateMachineMock).getFullBatterySaverPolicy();
+    }
+
+    @Test
+    public void testSetFullPowerSavePolicy_callsStateMachine() {
+        createService();
+        startSystem();
+        BatterySaverPolicyConfig mockSetPolicyConfig =
+                new BatterySaverPolicyConfig.Builder().build();
+        when(mBatterySaverStateMachineMock.setFullBatterySaverPolicy(any())).thenReturn(true);
+
+        mService.getBinderServiceInstance().setPowerSaveModeEnabled(true);
+        assertThat(mService.getBinderServiceInstance()
+                .setFullPowerSavePolicy(mockSetPolicyConfig)).isTrue();
+        verify(mBatterySaverStateMachineMock).setFullBatterySaverPolicy(eq(mockSetPolicyConfig));
+    }
+
+    @Test
+    public void testLowPowerStandby_whenInactive_FgsWakeLockEnabled() {
+        createService();
+        startSystem();
+        WakeLock wakeLock = acquireWakeLock("fgsWakeLock", PowerManager.PARTIAL_WAKE_LOCK);
+        mService.updateUidProcStateInternal(wakeLock.mOwnerUid, PROCESS_STATE_FOREGROUND_SERVICE);
+        mService.setDeviceIdleModeInternal(true);
+
+        assertThat(wakeLock.mDisabled).isFalse();
+    }
+
+    @Test
+    public void testLowPowerStandby_whenActive_FgsWakeLockDisabled() {
+        createService();
+        startSystem();
+        WakeLock wakeLock = acquireWakeLock("fgsWakeLock", PowerManager.PARTIAL_WAKE_LOCK);
+        mService.updateUidProcStateInternal(wakeLock.mOwnerUid, PROCESS_STATE_FOREGROUND_SERVICE);
+        mService.setDeviceIdleModeInternal(true);
+        mService.setLowPowerStandbyActiveInternal(true);
+
+        assertThat(wakeLock.mDisabled).isTrue();
+    }
+
+    @Test
+    public void testLowPowerStandby_whenActive_FgsWakeLockEnabledIfAllowlisted() {
+        createService();
+        startSystem();
+        WakeLock wakeLock = acquireWakeLock("fgsWakeLock", PowerManager.PARTIAL_WAKE_LOCK);
+        mService.updateUidProcStateInternal(wakeLock.mOwnerUid, PROCESS_STATE_FOREGROUND_SERVICE);
+        mService.setDeviceIdleModeInternal(true);
+        mService.setLowPowerStandbyActiveInternal(true);
+        mService.setLowPowerStandbyAllowlistInternal(new int[]{wakeLock.mOwnerUid});
+
+        assertThat(wakeLock.mDisabled).isFalse();
+    }
+
+    @Test
+    public void testLowPowerStandby_whenActive_BoundTopWakeLockDisabled() {
+        createService();
+        startSystem();
+        WakeLock wakeLock = acquireWakeLock("BoundTopWakeLock", PowerManager.PARTIAL_WAKE_LOCK);
+        mService.updateUidProcStateInternal(wakeLock.mOwnerUid, PROCESS_STATE_BOUND_TOP);
+        mService.setDeviceIdleModeInternal(true);
+        mService.setLowPowerStandbyActiveInternal(true);
+
+        assertThat(wakeLock.mDisabled).isFalse();
+    }
+
+    @Test
+    public void testSetLowPowerStandbyActiveDuringMaintenance_redirectsCallToNativeWrapper() {
+        createService();
+        startSystem();
+
+        mService.getBinderServiceInstance().setLowPowerStandbyActiveDuringMaintenance(true);
+        verify(mLowPowerStandbyControllerMock).setActiveDuringMaintenance(true);
+
+        mService.getBinderServiceInstance().setLowPowerStandbyActiveDuringMaintenance(false);
+        verify(mLowPowerStandbyControllerMock).setActiveDuringMaintenance(false);
+    }
+
+    @Test
+    public void testPowerGroupInitialization_multipleDisplayGroups() {
+        IntArray displayGroupIds = IntArray.wrap(new int[]{1, 2, 3});
+        when(mDisplayManagerInternalMock.getDisplayGroupIds()).thenReturn(displayGroupIds);
+
+        createService();
+        startSystem();
+
+        // Power group for DEFAULT_DISPLAY_GROUP is added by default.
+        assertThat(mService.getPowerGroupSize()).isEqualTo(4);
+    }
+
+    @Test
+    public void testPowerGroupInitialization_multipleDisplayGroupsWithDefaultGroup() {
+        IntArray displayGroupIds = IntArray.wrap(new int[]{Display.DEFAULT_DISPLAY_GROUP, 1, 2, 3});
+        when(mDisplayManagerInternalMock.getDisplayGroupIds()).thenReturn(displayGroupIds);
+
+        createService();
+        startSystem();
+
+        // Power group for DEFAULT_DISPLAY_GROUP is added once even if getDisplayGroupIds() return
+        // an array including DEFAULT_DESIPLAY_GROUP.
+        assertThat(mService.getPowerGroupSize()).isEqualTo(4);
+    }
+
+    private WakeLock acquireWakeLock(String tag, int flags) {
+        IBinder token = new Binder();
+        String packageName = "pkg.name";
+        mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName,
+                null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY,
+                null /* callback */);
+        return mService.findWakeLockLocked(token);
+    }
+
+    /**
+     * Test IPowerManager.acquireWakeLock() with a IWakeLockCallback.
+     */
+    @Test
+    public void testNotifyWakeLockCallback() {
+        createService();
+        startSystem();
+        final String tag = "wakelock1";
+        final String packageName = "pkg.name";
+        final IBinder token = new Binder();
+        final int flags = PowerManager.PARTIAL_WAKE_LOCK;
+        final IWakeLockCallback callback = Mockito.mock(IWakeLockCallback.class);
+        final IBinder callbackBinder = Mockito.mock(Binder.class);
+        when(callback.asBinder()).thenReturn(callbackBinder);
+        mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName,
+                null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, callback);
+        verify(mNotifierMock).onWakeLockAcquired(anyInt(), eq(tag), eq(packageName), anyInt(),
+                anyInt(), any(), any(), same(callback));
+
+        mService.getBinderServiceInstance().releaseWakeLock(token, 0);
+        verify(mNotifierMock).onWakeLockReleased(anyInt(), eq(tag), eq(packageName), anyInt(),
+                anyInt(), any(), any(), same(callback));
+    }
+
+    /**
+     * Test IPowerManager.updateWakeLockCallback() with a new IWakeLockCallback.
+     */
+    @Test
+    public void testNotifyWakeLockCallbackChange() {
+        createService();
+        startSystem();
+        final String tag = "wakelock1";
+        final String packageName = "pkg.name";
+        final IBinder token = new Binder();
+        int flags = PowerManager.PARTIAL_WAKE_LOCK;
+        final IWakeLockCallback callback1 = Mockito.mock(IWakeLockCallback.class);
+        final IBinder callbackBinder1 = Mockito.mock(Binder.class);
+        when(callback1.asBinder()).thenReturn(callbackBinder1);
+        mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName,
+                null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, callback1);
+        verify(mNotifierMock).onWakeLockAcquired(anyInt(), eq(tag), eq(packageName), anyInt(),
+                anyInt(), any(), any(), same(callback1));
+
+        final IWakeLockCallback callback2 = Mockito.mock(IWakeLockCallback.class);
+        final IBinder callbackBinder2 = Mockito.mock(Binder.class);
+        when(callback2.asBinder()).thenReturn(callbackBinder2);
+        mService.getBinderServiceInstance().updateWakeLockCallback(token, callback2);
+        verify(mNotifierMock).onWakeLockChanging(anyInt(), eq(tag), eq(packageName),
+                anyInt(), anyInt(), any(), any(), same(callback1),
+                anyInt(), eq(tag), eq(packageName), anyInt(), anyInt(), any(), any(),
+                same(callback2));
+
+        mService.getBinderServiceInstance().releaseWakeLock(token, 0);
+        verify(mNotifierMock).onWakeLockReleased(anyInt(), eq(tag), eq(packageName), anyInt(),
+                anyInt(), any(), any(), same(callback2));
+    }
+
+    @Test
+    public void testUserActivity_futureEventsAreIgnored() {
+        createService();
+        startSystem();
+        // Starting the system triggers a user activity event, so clear that before calling
+        // userActivity() directly.
+        clearInvocations(mNotifierMock);
+        final long eventTime = mClock.now() + Duration.ofHours(10).toMillis();
+        mService.getBinderServiceInstance().userActivity(Display.DEFAULT_DISPLAY, eventTime,
+                USER_ACTIVITY_EVENT_BUTTON, /* flags= */ 0);
+        verify(mNotifierMock, never()).onUserActivity(anyInt(),  anyInt(), anyInt());
+    }
+
+    @Test
+    public void testUserActivityOnDeviceStateChange() {
+        when(mContextSpy.getSystemService(DeviceStateManager.class))
+                .thenReturn(mDeviceStateManagerMock);
+
+        createService();
+        mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY);
+        mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
+
+        final DisplayInfo info = new DisplayInfo();
+        info.displayGroupId = Display.DEFAULT_DISPLAY_GROUP;
+        when(mDisplayManagerInternalMock.getDisplayInfo(Display.DEFAULT_DISPLAY)).thenReturn(info);
+
+        final ArgumentCaptor<DeviceStateCallback> deviceStateCallbackCaptor =
+                ArgumentCaptor.forClass(DeviceStateCallback.class);
+        verify(mDeviceStateManagerMock).registerCallback(any(),
+                deviceStateCallbackCaptor.capture());
+
+        // Advance the time 10001 and verify that the device thinks it has been idle
+        // for just less than that.
+        mService.onUserActivity();
+        advanceTime(10001);
+        assertThat(mService.wasDeviceIdleForInternal(10000)).isTrue();
+
+        // Send a display state change event and advance the clock 10.
+        final DeviceStateCallback deviceStateCallback = deviceStateCallbackCaptor.getValue();
+        deviceStateCallback.onStateChanged(1);
+        final long timeToAdvance = 10;
+        advanceTime(timeToAdvance);
+
+        // Ensure that the device has been idle for only 10 (doesn't include the idle time
+        // before the display state event).
+        assertThat(mService.wasDeviceIdleForInternal(timeToAdvance - 1)).isTrue();
+        assertThat(mService.wasDeviceIdleForInternal(timeToAdvance)).isFalse();
+
+        // Send the same state and ensure that does not trigger an update.
+        deviceStateCallback.onStateChanged(1);
+        advanceTime(timeToAdvance);
+        final long newTime = timeToAdvance * 2;
+
+        assertThat(mService.wasDeviceIdleForInternal(newTime - 1)).isTrue();
+        assertThat(mService.wasDeviceIdleForInternal(newTime)).isFalse();
+    }
+
+}
diff --git a/services/tests/powerservicetests/src/com/android/server/power/ShutdownCheckPointsTest.java b/services/tests/powerservicetests/src/com/android/server/power/ShutdownCheckPointsTest.java
new file mode 100644
index 0000000..fe6cc28
--- /dev/null
+++ b/services/tests/powerservicetests/src/com/android/server/power/ShutdownCheckPointsTest.java
@@ -0,0 +1,414 @@
+/*
+ * 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.
+ */
+
+package com.android.server.power;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManager;
+import android.app.IActivityManager;
+import android.os.Process;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
+import java.util.TimeZone;
+
+/**
+ * Run: atest FrameworksServicesTests:ShutdownCheckPointsTest
+ */
+@Presubmit
+public class ShutdownCheckPointsTest {
+
+    @Rule
+    public MockitoRule rule = MockitoJUnit.rule();
+
+    @Mock
+    private IActivityManager mActivityManager;
+
+    private TestInjector mTestInjector;
+    private ShutdownCheckPoints mInstance;
+
+    @Before
+    public void setUp() {
+        Locale.setDefault(Locale.UK);
+        TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
+        mTestInjector = new TestInjector(mActivityManager);
+        mInstance = new ShutdownCheckPoints(mTestInjector);
+    }
+
+    @Test
+    public void testSystemServerEntry() {
+        mTestInjector.setCurrentTime(1000);
+        mInstance.recordCheckPointInternal("reason1");
+
+        assertTrue(dumpToString().startsWith(
+                "Shutdown request from SYSTEM for reason reason1 "
+                        + "at 1970-01-01 00:00:01.000 UTC (epoch=1000)\n"
+                        + "com.android.server.power.ShutdownCheckPointsTest"
+                        + ".testSystemServerEntry\n at "));
+    }
+
+    @Test
+    public void testSystemServerEntryWithoutReason() {
+        mTestInjector.setCurrentTime(1000);
+        mInstance.recordCheckPointInternal(null);
+
+        assertTrue(dumpToString().startsWith(
+                "Shutdown request from SYSTEM at 1970-01-01 00:00:01.000 UTC (epoch=1000)\n"));
+    }
+
+    @Test
+    public void testSystemServiceBinderEntry() {
+        mTestInjector.setCurrentTime(1000);
+        mInstance.recordCheckPointInternal(Process.myPid(), "reason1");
+
+        assertTrue(dumpToString().startsWith(
+                "Shutdown request from SYSTEM for reason reason1 "
+                        + "at 1970-01-01 00:00:01.000 UTC (epoch=1000)\n"
+                        + "com.android.server.power.ShutdownCheckPointsTest"
+                        + ".testSystemServiceBinderEntry\n at "));
+    }
+
+    @Test
+    public void testCallerProcessBinderEntries() throws RemoteException {
+        List<ActivityManager.RunningAppProcessInfo> runningAppProcessInfos = new ArrayList<>();
+        runningAppProcessInfos.add(
+                new ActivityManager.RunningAppProcessInfo("process_name", 1, new String[0]));
+        when(mActivityManager.getRunningAppProcesses()).thenReturn(runningAppProcessInfos);
+
+        mTestInjector.setCurrentTime(1000);
+        // Matching pid in getRunningAppProcesses
+        mInstance.recordCheckPointInternal(1, "reason1");
+        // Missing pid in getRunningAppProcesses
+        mInstance.recordCheckPointInternal(2, "reason2");
+
+        assertEquals(
+                "Shutdown request from BINDER for reason reason1 "
+                        + "at 1970-01-01 00:00:01.000 UTC (epoch=1000)\n"
+                        + "com.android.server.power.ShutdownCheckPointsTest"
+                        + ".testCallerProcessBinderEntries\n"
+                        + "From process process_name (pid=1)\n\n"
+                        + "Shutdown request from BINDER for reason reason2 "
+                        + "at 1970-01-01 00:00:01.000 UTC (epoch=1000)\n"
+                        + "com.android.server.power.ShutdownCheckPointsTest"
+                        + ".testCallerProcessBinderEntries\n"
+                        + "From process ? (pid=2)\n\n",
+                dumpToString());
+    }
+
+    @Test
+    public void testNullCallerProcessBinderEntries() throws RemoteException {
+        when(mActivityManager.getRunningAppProcesses()).thenReturn(null);
+
+        mTestInjector.setCurrentTime(1000);
+        mInstance.recordCheckPointInternal(1, "reason1");
+
+        assertEquals(
+                "Shutdown request from BINDER for reason reason1 "
+                        + "at 1970-01-01 00:00:01.000 UTC (epoch=1000)\n"
+                        + "com.android.server.power.ShutdownCheckPointsTest"
+                        + ".testNullCallerProcessBinderEntries\n"
+                        + "From process ? (pid=1)\n\n",
+                dumpToString());
+    }
+
+    @Test
+    public void testRemoteExceptionOnBinderEntry() throws RemoteException {
+        when(mActivityManager.getRunningAppProcesses()).thenThrow(new RemoteException("Error"));
+
+        mTestInjector.setCurrentTime(1000);
+        mInstance.recordCheckPointInternal(1, "reason1");
+
+        assertEquals(
+                "Shutdown request from BINDER for reason reason1 "
+                        + "at 1970-01-01 00:00:01.000 UTC (epoch=1000)\n"
+                        + "com.android.server.power.ShutdownCheckPointsTest"
+                        + ".testRemoteExceptionOnBinderEntry\n"
+                        + "From process ? (pid=1)\n\n",
+                dumpToString());
+    }
+
+    @Test
+    public void testUnknownProcessBinderEntry() {
+        mTestInjector.setCurrentTime(1000);
+        mInstance.recordCheckPointInternal(1, "reason1");
+
+        assertEquals(
+                "Shutdown request from BINDER for reason reason1 "
+                        + "at 1970-01-01 00:00:01.000 UTC (epoch=1000)\n"
+                        + "com.android.server.power.ShutdownCheckPointsTest"
+                        + ".testUnknownProcessBinderEntry\n"
+                        + "From process ? (pid=1)\n\n",
+                dumpToString());
+    }
+
+    @Test
+    public void testBinderEntryWithoutReason() throws RemoteException {
+        mTestInjector.setCurrentTime(1000);
+        mInstance.recordCheckPointInternal(1, null);
+
+        assertTrue(dumpToString().startsWith(
+                "Shutdown request from BINDER at 1970-01-01 00:00:01.000 UTC (epoch=1000)\n"));
+    }
+
+    @Test
+    public void testSystemServiceIntentEntry() {
+        mTestInjector.setCurrentTime(1000);
+        mInstance.recordCheckPointInternal("some.intent", "android", "reason1");
+
+        assertTrue(dumpToString().startsWith(
+                "Shutdown request from SYSTEM for reason reason1 "
+                        + "at 1970-01-01 00:00:01.000 UTC (epoch=1000)\n"
+                        + "com.android.server.power.ShutdownCheckPointsTest"
+                        + ".testSystemServiceIntentEntry\n at "));
+    }
+
+    @Test
+    public void testIntentEntry() {
+        mTestInjector.setCurrentTime(1000);
+        mInstance.recordCheckPointInternal("some.intent", "some.app", "reason1");
+
+        assertEquals(
+                "Shutdown request from INTENT for reason reason1 "
+                        + "at 1970-01-01 00:00:01.000 UTC (epoch=1000)\n"
+                        + "Intent: some.intent\n"
+                        + "Package: some.app\n\n",
+                dumpToString());
+    }
+
+    @Test
+    public void testIntentEntryWithoutReason() {
+        mTestInjector.setCurrentTime(1000);
+        mInstance.recordCheckPointInternal("some.intent", "some.app", null);
+
+        assertTrue(dumpToString().startsWith(
+                "Shutdown request from INTENT at 1970-01-01 00:00:01.000 UTC (epoch=1000)\n"));
+    }
+
+    @Test
+    public void testMultipleEntries() {
+        mTestInjector.setCurrentTime(1000);
+        mInstance.recordCheckPointInternal(1, "reason1");
+        mTestInjector.setCurrentTime(2000);
+        mInstance.recordCheckPointInternal(2, "reason2");
+        mTestInjector.setCurrentTime(3000);
+        mInstance.recordCheckPointInternal("intent", "app", "reason3");
+
+        assertEquals(
+                "Shutdown request from BINDER for reason reason1 "
+                        + "at 1970-01-01 00:00:01.000 UTC (epoch=1000)\n"
+                        + "com.android.server.power.ShutdownCheckPointsTest.testMultipleEntries\n"
+                        + "From process ? (pid=1)\n\n"
+                        + "Shutdown request from BINDER for reason reason2 "
+                        + "at 1970-01-01 00:00:02.000 UTC (epoch=2000)\n"
+                        + "com.android.server.power.ShutdownCheckPointsTest.testMultipleEntries\n"
+                        + "From process ? (pid=2)\n\n"
+                        + "Shutdown request from INTENT for reason reason3 "
+                        + "at 1970-01-01 00:00:03.000 UTC (epoch=3000)\n"
+                        + "Intent: intent\n"
+                        + "Package: app\n\n",
+                dumpToString());
+    }
+
+    @Test
+    public void testTooManyEntriesDropsOlderOnes() {
+        mTestInjector.setCheckPointsLimit(2);
+        ShutdownCheckPoints limitedInstance = new ShutdownCheckPoints(mTestInjector);
+
+        mTestInjector.setCurrentTime(1000);
+        limitedInstance.recordCheckPointInternal("intent.1", "app.1", "reason1");
+        mTestInjector.setCurrentTime(2000);
+        limitedInstance.recordCheckPointInternal("intent.2", "app.2", "reason2");
+        mTestInjector.setCurrentTime(3000);
+        limitedInstance.recordCheckPointInternal("intent.3", "app.3", "reason3");
+
+        // Drops first intent.
+        assertEquals(
+                "Shutdown request from INTENT for reason reason2 "
+                        + "at 1970-01-01 00:00:02.000 UTC (epoch=2000)\n"
+                        + "Intent: intent.2\n"
+                        + "Package: app.2\n\n"
+                        + "Shutdown request from INTENT for reason reason3 "
+                        + "at 1970-01-01 00:00:03.000 UTC (epoch=3000)\n"
+                        + "Intent: intent.3\n"
+                        + "Package: app.3\n\n",
+                dumpToString(limitedInstance));
+    }
+
+    @Test
+    public void testDumpToFile() throws Exception {
+        File tempDir = createTempDir();
+        File baseFile = new File(tempDir, "checkpoints");
+
+        mTestInjector.setCurrentTime(1000);
+        mInstance.recordCheckPointInternal("first.intent", "first.app", "reason1");
+        dumpToFile(baseFile);
+
+        mTestInjector.setCurrentTime(2000);
+        mInstance.recordCheckPointInternal("second.intent", "second.app", "reason2");
+        dumpToFile(baseFile);
+
+        File[] dumpFiles = tempDir.listFiles();
+        Arrays.sort(dumpFiles);
+
+        assertEquals(2, dumpFiles.length);
+        assertEquals(
+                "Shutdown request from INTENT for reason reason1 "
+                        + "at 1970-01-01 00:00:01.000 UTC (epoch=1000)\n"
+                        + "Intent: first.intent\n"
+                        + "Package: first.app\n\n",
+                readFileAsString(dumpFiles[0].getAbsolutePath()));
+        assertEquals(
+                "Shutdown request from INTENT for reason reason1 "
+                        + "at 1970-01-01 00:00:01.000 UTC (epoch=1000)\n"
+                        + "Intent: first.intent\n"
+                        + "Package: first.app\n\n"
+                        + "Shutdown request from INTENT for reason reason2 "
+                        + "at 1970-01-01 00:00:02.000 UTC (epoch=2000)\n"
+                        + "Intent: second.intent\n"
+                        + "Package: second.app\n\n",
+                readFileAsString(dumpFiles[1].getAbsolutePath()));
+    }
+
+    @Test
+    public void testTooManyFilesDropsOlderOnes() throws Exception {
+        mTestInjector.setDumpFilesLimit(1);
+        ShutdownCheckPoints instance = new ShutdownCheckPoints(mTestInjector);
+        File tempDir = createTempDir();
+        File baseFile = new File(tempDir, "checkpoints");
+
+        mTestInjector.setCurrentTime(1000);
+        instance.recordCheckPointInternal("first.intent", "first.app", "reason1");
+        dumpToFile(instance, baseFile);
+
+        mTestInjector.setCurrentTime(2000);
+        instance.recordCheckPointInternal("second.intent", "second.app", "reason2");
+        dumpToFile(instance, baseFile);
+
+        File[] dumpFiles = tempDir.listFiles();
+        assertEquals(1, dumpFiles.length);
+        assertEquals(
+                "Shutdown request from INTENT for reason reason1 "
+                        + "at 1970-01-01 00:00:01.000 UTC (epoch=1000)\n"
+                        + "Intent: first.intent\n"
+                        + "Package: first.app\n\n"
+                        + "Shutdown request from INTENT for reason reason2 "
+                        + "at 1970-01-01 00:00:02.000 UTC (epoch=2000)\n"
+                        + "Intent: second.intent\n"
+                        + "Package: second.app\n\n",
+                readFileAsString(dumpFiles[0].getAbsolutePath()));
+    }
+
+    private String dumpToString() {
+        return dumpToString(mInstance);
+    }
+
+    private String dumpToString(ShutdownCheckPoints instance) {
+        StringWriter sw = new StringWriter();
+        PrintWriter pw = new PrintWriter(sw);
+        instance.dumpInternal(pw);
+        return sw.toString();
+    }
+
+    private void dumpToFile(File baseFile) throws InterruptedException {
+        dumpToFile(mInstance, baseFile);
+    }
+
+    private void dumpToFile(ShutdownCheckPoints instance, File baseFile)
+            throws InterruptedException {
+        Thread dumpThread = instance.newDumpThreadInternal(baseFile);
+        dumpThread.start();
+        dumpThread.join();
+    }
+
+    private String readFileAsString(String absolutePath) throws IOException {
+        return new String(Files.readAllBytes(Paths.get(absolutePath)), StandardCharsets.UTF_8);
+    }
+
+    private File createTempDir() throws IOException {
+        File tempDir = File.createTempFile("checkpoints", "out");
+        tempDir.delete();
+        tempDir.mkdir();
+        return tempDir;
+    }
+
+    /** Fake system dependencies for testing. */
+    private final class TestInjector implements ShutdownCheckPoints.Injector {
+        private long mNow;
+        private int mCheckPointsLimit;
+        private int mDumpFilesLimit;
+        private IActivityManager mActivityManager;
+
+        TestInjector(IActivityManager activityManager) {
+            mNow = 0;
+            mCheckPointsLimit = 100;
+            mDumpFilesLimit = 2;
+            mActivityManager = activityManager;
+        }
+
+        @Override
+        public long currentTimeMillis() {
+            return mNow;
+        }
+
+        @Override
+        public int maxCheckPoints() {
+            return mCheckPointsLimit;
+        }
+
+        @Override
+        public int maxDumpFiles() {
+            return mDumpFilesLimit;
+        }
+
+        @Override
+        public IActivityManager activityManager() {
+            return mActivityManager;
+        }
+
+        void setCurrentTime(long time) {
+            mNow = time;
+        }
+
+        void setCheckPointsLimit(int limit) {
+            mCheckPointsLimit = limit;
+        }
+
+        void setDumpFilesLimit(int dumpFilesLimit) {
+            mDumpFilesLimit = dumpFilesLimit;
+        }
+    }
+}
diff --git a/services/tests/powerservicetests/src/com/android/server/power/ShutdownThreadTest.java b/services/tests/powerservicetests/src/com/android/server/power/ShutdownThreadTest.java
new file mode 100644
index 0000000..6041e91
--- /dev/null
+++ b/services/tests/powerservicetests/src/com/android/server/power/ShutdownThreadTest.java
@@ -0,0 +1,215 @@
+/*
+ * 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.
+ */
+
+package com.android.server.power;
+
+import static com.android.server.power.ShutdownThread.DEFAULT_SHUTDOWN_VIBRATE_MS;
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.os.VibrationAttributes;
+import android.os.VibrationEffect;
+import android.os.Vibrator;
+import android.util.AtomicFile;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+/**
+ * Tests for {@link com.android.server.power.ShutdownThread}
+ */
+public class ShutdownThreadTest {
+
+    private static final String WAVEFORM_VIB_10MS_SERIALIZATION =
+            """
+            <vibration>
+                <waveform-effect>
+                    <waveform-entry durationMs="10" amplitude="100"/>
+                </waveform-effect>
+            </vibration>
+            """;
+
+    private static final VibrationEffect WAVEFORM_VIB_10MS = VibrationEffect.createOneShot(10, 100);
+
+    private static final String REPEATING_VIB_SERIALIZATION =
+            """
+            <vibration>
+                <waveform-effect>
+                    <repeating>
+                        <waveform-entry durationMs="10" amplitude="100"/>
+                    </repeating>
+                </waveform-effect>
+            </vibration>
+            """;
+
+    private static final String CLICK_VIB_SERIALIZATION =
+            """
+            <vibration>
+                <predefined-effect name="click"/>
+            </vibration>
+            """;
+
+    private static final VibrationEffect CLILCK_VIB =
+            VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK);
+
+    private static final String BAD_VIB_SERIALIZATION = "BAD SERIALIZATION";
+
+    @Mock private Context mContextMock;
+    @Mock private Vibrator mVibratorMock;
+
+    private String mDefaultShutdownVibrationFilePath;
+    private long mLastSleepDurationMs;
+
+    private ShutdownThread mShutdownThread;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        when(mVibratorMock.hasVibrator()).thenReturn(true);
+
+        when(mVibratorMock.areVibrationFeaturesSupported(any())).thenReturn(true);
+
+        mShutdownThread = new ShutdownThread(new TestInjector());
+    }
+
+    @Test
+    public void testSuccessfulShutdownVibrationFromFile() throws Exception {
+        setShutdownVibrationFileContent(WAVEFORM_VIB_10MS_SERIALIZATION);
+
+        mShutdownThread.playShutdownVibration(mContextMock);
+
+        assertShutdownVibration(WAVEFORM_VIB_10MS, /* vibrationSleepDuration= */ 10);
+    }
+
+    @Test
+    public void testIOExceptionWhenParsingShutdownVibration() throws Exception {
+        mDefaultShutdownVibrationFilePath = "non/existent/file_path";
+
+        mShutdownThread.playShutdownVibration(mContextMock);
+
+        assertDefaultShutdownVibration();
+    }
+
+    @Test
+    public void testMalformedShutdownVibrationFileContent() throws Exception {
+        setShutdownVibrationFileContent(BAD_VIB_SERIALIZATION);
+
+        mShutdownThread.playShutdownVibration(mContextMock);
+
+        assertDefaultShutdownVibration();
+    }
+
+    @Test
+    public void testVibratorUnsupportedShutdownVibrationEffect() throws Exception {
+        setShutdownVibrationFileContent(WAVEFORM_VIB_10MS_SERIALIZATION);
+        when(mVibratorMock.areVibrationFeaturesSupported(any())).thenReturn(false);
+
+        mShutdownThread.playShutdownVibration(mContextMock);
+
+        assertDefaultShutdownVibration();
+    }
+
+    @Test
+    public void testRepeatinghutdownVibrationEffect() throws Exception {
+        setShutdownVibrationFileContent(REPEATING_VIB_SERIALIZATION);
+
+        mShutdownThread.playShutdownVibration(mContextMock);
+
+        assertDefaultShutdownVibration();
+    }
+
+    @Test
+    public void testVibrationEffectWithUnknownDuration() throws Exception {
+        setShutdownVibrationFileContent(CLICK_VIB_SERIALIZATION);
+
+        mShutdownThread.playShutdownVibration(mContextMock);
+
+        assertShutdownVibration(CLILCK_VIB, DEFAULT_SHUTDOWN_VIBRATE_MS);
+    }
+
+    @Test
+    public void testNoVibrator() {
+        when(mVibratorMock.hasVibrator()).thenReturn(false);
+
+        mShutdownThread.playShutdownVibration(mContextMock);
+
+        verify(mVibratorMock, never())
+                .vibrate(any(VibrationEffect.class), any(VibrationAttributes.class));
+    }
+
+    private void assertShutdownVibration(VibrationEffect effect, long vibrationSleepDuration)
+            throws Exception {
+        verify(mVibratorMock).vibrate(
+                eq(effect),
+                eq(VibrationAttributes.createForUsage(VibrationAttributes.USAGE_TOUCH)));
+        assertEquals(vibrationSleepDuration, mLastSleepDurationMs);
+    }
+
+    private void assertDefaultShutdownVibration() throws Exception {
+        assertShutdownVibration(
+                VibrationEffect.createOneShot(
+                        DEFAULT_SHUTDOWN_VIBRATE_MS, VibrationEffect.DEFAULT_AMPLITUDE),
+                DEFAULT_SHUTDOWN_VIBRATE_MS);
+    }
+
+    private void setShutdownVibrationFileContent(String content) throws Exception {
+        mDefaultShutdownVibrationFilePath = createFileForContent(content).getAbsolutePath();
+    }
+
+    private static File createFileForContent(String content) throws Exception {
+        File file = new File(InstrumentationRegistry.getContext().getCacheDir(), "test.xml");
+        file.createNewFile();
+
+        AtomicFile atomicFile = new AtomicFile(file);
+        FileOutputStream fos = atomicFile.startWrite();
+        fos.write(content.getBytes());
+        atomicFile.finishWrite(fos);
+
+        return file;
+    }
+
+    private class TestInjector extends ShutdownThread.Injector {
+        @Override
+        public Vibrator getVibrator(Context context) {
+            return mVibratorMock;
+        }
+
+        @Override
+        public String getDefaultShutdownVibrationEffectFilePath(Context context) {
+            return mDefaultShutdownVibrationFilePath;
+        }
+
+        @Override
+        public void sleep(long durationMs) {
+            mLastSleepDurationMs = durationMs;
+        }
+    }
+}
diff --git a/services/tests/powerservicetests/src/com/android/server/power/WakeLockLogTest.java b/services/tests/powerservicetests/src/com/android/server/power/WakeLockLogTest.java
new file mode 100644
index 0000000..7af4b3d
--- /dev/null
+++ b/services/tests/powerservicetests/src/com/android/server/power/WakeLockLogTest.java
@@ -0,0 +1,453 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.PowerManager;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.text.SimpleDateFormat;
+import java.util.TimeZone;
+
+/**
+ * Tests for {@link WakeLockLog}.
+ */
+public class WakeLockLogTest {
+
+    @Mock
+    private Context mContext;
+
+    @Mock
+    private PackageManager mPackageManager;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        when(mContext.getPackageManager()).thenReturn(mPackageManager);
+
+        when(mPackageManager.getPackagesForUid(101)).thenReturn(new String[]{ "some.package1" });
+        when(mPackageManager.getPackagesForUid(102)).thenReturn(new String[]{ "some.package2" });
+    }
+
+    @Test
+    public void testAddTwoItems() {
+        final int tagDatabaseSize = 128;
+        final int logSize = 20;
+        TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize));
+        WakeLockLog log = new WakeLockLog(injectorSpy, mContext);
+
+        when(injectorSpy.currentTimeMillis()).thenReturn(1000L);
+        log.onWakeLockAcquired("TagPartial", 101,
+                PowerManager.PARTIAL_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE);
+
+        when(injectorSpy.currentTimeMillis()).thenReturn(1150L);
+        log.onWakeLockAcquired("TagFull", 102,
+                PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP);
+
+        assertEquals("Wake Lock Log\n"
+                + "  01-01 00:00:01.000 - 101 (some.package1) - ACQ TagPartial "
+                        + "(partial,on-after-release)\n"
+                + "  01-01 00:00:01.150 - 102 (some.package2) - ACQ TagFull "
+                        + "(full,acq-causes-wake)\n"
+                + "  -\n"
+                + "  Events: 2, Time-Resets: 0\n"
+                + "  Buffer, Bytes used: 6\n",
+                dumpLog(log, false));
+    }
+
+    @Test
+    public void testAddTwoItemsWithTimeReset() {
+        final int tagDatabaseSize = 128;
+        final int logSize = 20;
+        TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize));
+        WakeLockLog log = new WakeLockLog(injectorSpy, mContext);
+
+        when(injectorSpy.currentTimeMillis()).thenReturn(1000L);
+        log.onWakeLockAcquired("TagPartial", 101, PowerManager.PARTIAL_WAKE_LOCK);
+
+        when(injectorSpy.currentTimeMillis()).thenReturn(1350L);
+        log.onWakeLockAcquired("TagFull", 102, PowerManager.FULL_WAKE_LOCK);
+
+        assertEquals("Wake Lock Log\n"
+                + "  01-01 00:00:01.000 - 101 (some.package1) - ACQ TagPartial (partial)\n"
+                + "  01-01 00:00:01.350 - 102 (some.package2) - ACQ TagFull (full)\n"
+                + "  -\n"
+                + "  Events: 2, Time-Resets: 1\n"
+                + "  Buffer, Bytes used: 15\n",
+                dumpLog(log, false));
+    }
+
+    @Test
+    public void testAddTwoItemsWithTagOverwrite() {
+        final int tagDatabaseSize = 2;
+        final int logSize = 20;
+        TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize));
+        WakeLockLog log = new WakeLockLog(injectorSpy, mContext);
+
+        when(injectorSpy.currentTimeMillis()).thenReturn(1000L);
+        log.onWakeLockAcquired("TagPartial", 101, PowerManager.PARTIAL_WAKE_LOCK);
+
+        when(injectorSpy.currentTimeMillis()).thenReturn(1150L);
+        log.onWakeLockAcquired("TagFull", 102, PowerManager.FULL_WAKE_LOCK);
+
+        assertEquals("Wake Lock Log\n"
+                + "  01-01 00:00:01.000 - --- - ACQ UNKNOWN (partial)\n"
+                + "  01-01 00:00:01.150 - 102 (some.package2) - ACQ TagFull (full)\n"
+                + "  -\n"
+                + "  Events: 2, Time-Resets: 0\n"
+                + "  Buffer, Bytes used: 6\n",
+                dumpLog(log, false));
+    }
+
+    @Test
+    public void testAddFourItemsWithRingBufferOverflow() {
+        final int tagDatabaseSize = 6;
+        final int logSize = 10;
+        TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize));
+        WakeLockLog log = new WakeLockLog(injectorSpy, mContext);
+
+        // Wake lock 1 acquired - log size = 3
+        when(injectorSpy.currentTimeMillis()).thenReturn(1000L);
+        log.onWakeLockAcquired("TagPartial", 101, PowerManager.PARTIAL_WAKE_LOCK);
+
+        // Wake lock 2 acquired - log size = 3 + 3 = 6
+        when(injectorSpy.currentTimeMillis()).thenReturn(1150L);
+        log.onWakeLockAcquired("TagFull", 102, PowerManager.FULL_WAKE_LOCK);
+
+        // Wake lock 3 acquired - log size = 6 + 3 = 9
+        when(injectorSpy.currentTimeMillis()).thenReturn(1151L);
+        log.onWakeLockAcquired("TagThree", 101, PowerManager.PARTIAL_WAKE_LOCK);
+
+        // We need more space - wake lock 1 acquisition is removed from the log and saved in the
+        // list. Log size = 9 - 3 + 2 = 8
+        when(injectorSpy.currentTimeMillis()).thenReturn(1152L);
+        log.onWakeLockReleased("TagThree", 101);
+
+        // We need more space - wake lock 2 acquisition is removed from the log and saved in the
+        // list. Log size = 8 - 3 + 2 = 7
+        when(injectorSpy.currentTimeMillis()).thenReturn(1153L);
+        log.onWakeLockReleased("TagPartial", 101);
+
+        // We need more space - wake lock 3 acquisition is removed from the log and saved in the
+        // list. Log size = 7 - 3 + 3 = 7
+        when(injectorSpy.currentTimeMillis()).thenReturn(1154L);
+        log.onWakeLockAcquired("TagFour", 101, PowerManager.PARTIAL_WAKE_LOCK);
+
+        // We need more space - wake lock 3 release is removed from the log and wake lock 3
+        // acquisition is removed from the list. Log size = 7 - 2 + 3 = 8
+        when(injectorSpy.currentTimeMillis()).thenReturn(1155L);
+        log.onWakeLockAcquired("TagFive", 101, PowerManager.PARTIAL_WAKE_LOCK);
+
+        // We need more space - wake lock 1 release is removed from the log and wake lock 1
+        // acquisition is removed from the list. Log size = 8 - 2 + 2 = 8
+        when(injectorSpy.currentTimeMillis()).thenReturn(1156L);
+        log.onWakeLockReleased("TagFull", 102);
+
+        // Wake lock 2 acquisition is still printed because its release have not rolled off the log
+        // yet.
+        assertEquals("Wake Lock Log\n"
+                + "  01-01 00:00:01.150 - 102 (some.package2) - ACQ TagFull (full)\n"
+                + "  01-01 00:00:01.154 - 101 (some.package1) - ACQ TagFour (partial)\n"
+                + "  01-01 00:00:01.155 - 101 (some.package1) - ACQ TagFive (partial)\n"
+                + "  01-01 00:00:01.156 - 102 (some.package2) - REL TagFull\n"
+                + "  -\n"
+                + "  Events: 4, Time-Resets: 0\n"
+                + "  Buffer, Bytes used: 8\n",
+                dumpLog(log, false));
+    }
+
+    @Test
+    public void testAddItemWithBadTag() {
+        final int tagDatabaseSize = 6;
+        final int logSize = 10;
+        TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize));
+        WakeLockLog log = new WakeLockLog(injectorSpy, mContext);
+
+        // Bad tag means it wont get written
+        when(injectorSpy.currentTimeMillis()).thenReturn(1000L);
+        log.onWakeLockAcquired(null /* tag */, 0 /* ownerUid */, PowerManager.PARTIAL_WAKE_LOCK);
+
+        assertEquals("Wake Lock Log\n"
+                + "  -\n"
+                + "  Events: 0, Time-Resets: 0\n"
+                + "  Buffer, Bytes used: 0\n",
+                dumpLog(log, false));
+    }
+
+    @Test
+    public void testAddItemWithReducedTagName() {
+        final int tagDatabaseSize = 6;
+        final int logSize = 10;
+        TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize));
+        WakeLockLog log = new WakeLockLog(injectorSpy, mContext);
+
+        when(injectorSpy.currentTimeMillis()).thenReturn(1000L);
+        log.onWakeLockAcquired("*job*/com.one.two.3hree/.one..Last", 101,
+                PowerManager.PARTIAL_WAKE_LOCK);
+
+        assertEquals("Wake Lock Log\n"
+                + "  01-01 00:00:01.000 - 101 (some.package1) - ACQ "
+                        + "*job*/c.o.t.3/.o..Last (partial)\n"
+                + "  -\n"
+                + "  Events: 1, Time-Resets: 0\n"
+                + "  Buffer, Bytes used: 3\n",
+                dumpLog(log, false));
+    }
+
+    @Test
+    public void testAddAcquireAndReleaseWithRepeatTagName() {
+        final int tagDatabaseSize = 6;
+        final int logSize = 10;
+        TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize));
+        WakeLockLog log = new WakeLockLog(injectorSpy, mContext);
+
+        when(injectorSpy.currentTimeMillis()).thenReturn(1000L);
+        log.onWakeLockAcquired("HowdyTag", 101, PowerManager.PARTIAL_WAKE_LOCK);
+        when(injectorSpy.currentTimeMillis()).thenReturn(1001L);
+        log.onWakeLockReleased("HowdyTag", 101);
+
+        assertEquals("Wake Lock Log\n"
+                + "  01-01 00:00:01.000 - 101 (some.package1) - ACQ HowdyTag (partial)\n"
+                + "  01-01 00:00:01.001 - 101 (some.package1) - REL HowdyTag\n"
+                + "  -\n"
+                + "  Events: 2, Time-Resets: 0\n"
+                + "  Buffer, Bytes used: 5\n"
+                + "  Tag Database: size(5), entries: 1, Bytes used: 80\n",
+                dumpLog(log, true));
+    }
+
+    @Test
+    public void testAddAcquireAndReleaseWithTimeTravel() {
+        final int tagDatabaseSize = 6;
+        final int logSize = 10;
+        TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize));
+        WakeLockLog log = new WakeLockLog(injectorSpy, mContext);
+
+        when(injectorSpy.currentTimeMillis()).thenReturn(1100L);
+        log.onWakeLockAcquired("HowdyTag", 101, PowerManager.PARTIAL_WAKE_LOCK);
+
+        // New element goes back in time...should not be written to log.
+        when(injectorSpy.currentTimeMillis()).thenReturn(1000L);
+        log.onWakeLockReleased("HowdyTag", 101);
+
+        assertEquals("Wake Lock Log\n"
+                + "  01-01 00:00:01.100 - 101 (some.package1) - ACQ HowdyTag (partial)\n"
+                + "  -\n"
+                + "  Events: 1, Time-Resets: 0\n"
+                + "  Buffer, Bytes used: 3\n",
+                dumpLog(log, false));
+    }
+
+    @Test
+    public void testAddSystemWakelock() {
+        final int tagDatabaseSize = 6;
+        final int logSize = 10;
+        TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize));
+        WakeLockLog log = new WakeLockLog(injectorSpy, mContext);
+
+        when(injectorSpy.currentTimeMillis()).thenReturn(1000L);
+        log.onWakeLockAcquired("TagPartial", 101,
+                PowerManager.PARTIAL_WAKE_LOCK | PowerManager.SYSTEM_WAKELOCK);
+
+        assertEquals("Wake Lock Log\n"
+                        + "  01-01 00:00:01.000 - 101 (some.package1) - ACQ TagPartial "
+                                + "(partial,system-wakelock)\n"
+                        + "  -\n"
+                        + "  Events: 1, Time-Resets: 0\n"
+                        + "  Buffer, Bytes used: 3\n",
+                dumpLog(log, false));
+    }
+
+    @Test
+    public void testAddItemWithNoPackageName() {
+        final int tagDatabaseSize = 128;
+        final int logSize = 20;
+        TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize));
+        WakeLockLog log = new WakeLockLog(injectorSpy, mContext);
+
+        when(mPackageManager.getPackagesForUid(101)).thenReturn(null);
+        when(injectorSpy.currentTimeMillis()).thenReturn(1000L);
+        log.onWakeLockAcquired("TagPartial", 101,
+                PowerManager.PARTIAL_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE);
+
+        assertEquals("Wake Lock Log\n"
+                        + "  01-01 00:00:01.000 - 101 - ACQ TagPartial "
+                                + "(partial,on-after-release)\n"
+                        + "  -\n"
+                        + "  Events: 1, Time-Resets: 0\n"
+                        + "  Buffer, Bytes used: 3\n",
+                dumpLog(log, false));
+    }
+
+    @Test
+    public void testAddItemWithMultiplePackageNames() {
+        final int tagDatabaseSize = 128;
+        final int logSize = 20;
+        TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize));
+        WakeLockLog log = new WakeLockLog(injectorSpy, mContext);
+
+        when(mPackageManager.getPackagesForUid(101)).thenReturn(
+                new String[]{ "some.package1", "some.package2", "some.package3" });
+
+        when(injectorSpy.currentTimeMillis()).thenReturn(1000L);
+        log.onWakeLockAcquired("TagPartial", 101,
+                PowerManager.PARTIAL_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE);
+
+        assertEquals("Wake Lock Log\n"
+                        + "  01-01 00:00:01.000 - 101 (some.package1,...) - ACQ TagPartial "
+                                + "(partial,on-after-release)\n"
+                        + "  -\n"
+                        + "  Events: 1, Time-Resets: 0\n"
+                        + "  Buffer, Bytes used: 3\n",
+                dumpLog(log, false));
+    }
+
+    @Test
+    public void testAddItemsWithRepeatOwnerUid_UsesCache() {
+        final int tagDatabaseSize = 128;
+        final int logSize = 20;
+        TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize));
+        WakeLockLog log = new WakeLockLog(injectorSpy, mContext);
+
+        when(injectorSpy.currentTimeMillis()).thenReturn(1000L);
+        log.onWakeLockAcquired("TagPartial", 101,
+                PowerManager.PARTIAL_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE);
+
+        when(injectorSpy.currentTimeMillis()).thenReturn(1150L);
+        log.onWakeLockAcquired("TagFull", 101,
+                PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP);
+
+        when(injectorSpy.currentTimeMillis()).thenReturn(1151L);
+        log.onWakeLockAcquired("TagFull2", 101,
+                PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP);
+
+        assertEquals("Wake Lock Log\n"
+                        + "  01-01 00:00:01.000 - 101 (some.package1) - ACQ TagPartial "
+                        + "(partial,on-after-release)\n"
+                        + "  01-01 00:00:01.150 - 101 (some.package1) - ACQ TagFull "
+                        + "(full,acq-causes-wake)\n"
+                        + "  01-01 00:00:01.151 - 101 (some.package1) - ACQ TagFull2 "
+                        + "(full,acq-causes-wake)\n"
+                        + "  -\n"
+                        + "  Events: 3, Time-Resets: 0\n"
+                        + "  Buffer, Bytes used: 9\n",
+                dumpLog(log, false));
+
+        verify(mPackageManager, times(1)).getPackagesForUid(101);
+    }
+
+    @Test
+    public void testAddItemsWithRepeatOwnerUid_SavedAcquisitions_UsesCache() {
+        final int tagDatabaseSize = 128;
+        final int logSize = 10;
+        TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize));
+        WakeLockLog log = new WakeLockLog(injectorSpy, mContext);
+
+        when(injectorSpy.currentTimeMillis()).thenReturn(1000L);
+        log.onWakeLockAcquired("TagPartial", 101,
+                PowerManager.PARTIAL_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE);
+
+        when(injectorSpy.currentTimeMillis()).thenReturn(1150L);
+        log.onWakeLockAcquired("TagFull", 101,
+                PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP);
+
+        when(injectorSpy.currentTimeMillis()).thenReturn(1151L);
+        log.onWakeLockAcquired("TagFull2", 101,
+                PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP);
+
+        when(injectorSpy.currentTimeMillis()).thenReturn(1152L);
+        log.onWakeLockAcquired("TagFull3", 101,
+                PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP);
+
+        when(injectorSpy.currentTimeMillis()).thenReturn(1153L);
+        log.onWakeLockAcquired("TagFull4", 101,
+                PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP);
+
+        when(injectorSpy.currentTimeMillis()).thenReturn(1154L);
+        log.onWakeLockAcquired("TagFull5", 101,
+                PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP);
+
+        // The first 3 events have been removed from the log and they exist in the saved
+        // acquisitions list. They should also use the cache when fetching the package names.
+        assertEquals("Wake Lock Log\n"
+                        + "  01-01 00:00:01.000 - 101 (some.package1) - ACQ TagPartial "
+                        + "(partial,on-after-release)\n"
+                        + "  01-01 00:00:01.150 - 101 (some.package1) - ACQ TagFull "
+                        + "(full,acq-causes-wake)\n"
+                        + "  01-01 00:00:01.151 - 101 (some.package1) - ACQ TagFull2 "
+                        + "(full,acq-causes-wake)\n"
+                        + "  01-01 00:00:01.152 - 101 (some.package1) - ACQ TagFull3 "
+                        + "(full,acq-causes-wake)\n"
+                        + "  01-01 00:00:01.153 - 101 (some.package1) - ACQ TagFull4 "
+                        + "(full,acq-causes-wake)\n"
+                        + "  01-01 00:00:01.154 - 101 (some.package1) - ACQ TagFull5 "
+                        + "(full,acq-causes-wake)\n"
+                        + "  -\n"
+                        + "  Events: 6, Time-Resets: 0\n"
+                        + "  Buffer, Bytes used: 9\n",
+                dumpLog(log, false));
+
+        verify(mPackageManager, times(1)).getPackagesForUid(101);
+    }
+
+    private String dumpLog(WakeLockLog log, boolean includeTagDb) {
+        StringWriter sw = new StringWriter();
+        PrintWriter pw = new PrintWriter(sw);
+        log.dump(pw, includeTagDb);
+        return sw.toString();
+    }
+
+    public class TestInjector extends WakeLockLog.Injector {
+        private final int mTagDatabaseSize;
+        private final int mLogSize;
+
+        public TestInjector(int tagDatabaseSize, int logSize) {
+            mTagDatabaseSize = tagDatabaseSize;
+            mLogSize = logSize;
+        }
+
+        @Override
+        public int getTagDatabaseSize() {
+            return mTagDatabaseSize;
+        }
+
+        @Override
+        public int getLogSize() {
+            return mLogSize;
+        }
+
+        @Override
+        public SimpleDateFormat getDateFormat() {
+            SimpleDateFormat format = new SimpleDateFormat(super.getDateFormat().toPattern());
+            format.setTimeZone(TimeZone.getTimeZone("UTC"));
+            return format;
+        }
+    }
+}
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index 16e1b45..173c5f5 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -28,14 +28,17 @@
         "services.accessibility",
         "services.appwidget",
         "services.autofill",
+        "services.contentcapture",
         "services.backup",
         "services.companion",
         "services.core",
         "services.credentials",
         "services.devicepolicy",
+        "services.flags",
         "services.net",
         "services.people",
         "services.usage",
+        "service-permission.stubs.system_server",
         "guava",
         "guava-android-testlib",
         "androidx.test.core",
@@ -50,7 +53,7 @@
         "ShortcutManagerTestUtils",
         "truth-prebuilt",
         "testables",
-        "ub-uiautomator",
+        "androidx.test.uiautomator_uiautomator",
         "platformprotosnano",
         "framework-protos",
         "hamcrest-library",
diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml
index 107dde2..fa0a971 100644
--- a/services/tests/servicestests/AndroidManifest.xml
+++ b/services/tests/servicestests/AndroidManifest.xml
@@ -110,6 +110,7 @@
     <uses-permission android:name="android.permission.ACCESS_CONTEXT_HUB" />
     <uses-permission android:name="android.permission.USE_BIOMETRIC_INTERNAL" />
     <uses-permission android:name="android.permission.MANAGE_MEDIA_PROJECTION" />
+    <uses-permission android:name="android.permission.MANAGE_ROLE_HOLDERS" />
 
     <queries>
         <package android:name="com.android.servicestests.apps.suspendtestapp" />
diff --git a/services/tests/servicestests/res/xml/usertypes_test_profile.xml b/services/tests/servicestests/res/xml/usertypes_test_profile.xml
index 26d681b..0115db6 100644
--- a/services/tests/servicestests/res/xml/usertypes_test_profile.xml
+++ b/services/tests/servicestests/res/xml/usertypes_test_profile.xml
@@ -20,6 +20,7 @@
         icon-badge='@*android:drawable/ic_corp_icon_badge_case'
         badge-plain='garbage'
         badge-no-background='@*android:drawable/ic_corp_badge_no_background'
+        status-bar-icon='@*android:drawable/ic_test_badge_experiment'
         >
         <badge-labels>
             <item res='@*android:string/managed_profile_label_badge' />
@@ -41,6 +42,7 @@
             showInSettings='23'
             inheritDevicePolicy='450'
             deleteAppWithParent='false'
+            alwaysVisible='true'
         />
     </profile-type>
     <profile-type name='custom.test.1' max-allowed-per-parent='14' />
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/TEST_MAPPING b/services/tests/servicestests/src/com/android/server/accessibility/TEST_MAPPING
new file mode 100644
index 0000000..1c67399
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/accessibility/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "imports": [
+    {
+      "path": "frameworks/base/services/accessibility/TEST_MAPPING"
+    }
+  ]
+}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
index e6ef044..5b5c8d4 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
@@ -29,6 +29,7 @@
 import static org.mockito.ArgumentMatchers.anyFloat;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
@@ -202,6 +203,7 @@
         assertFalse(mFullScreenMagnificationController.isRegistered(DISPLAY_0));
         assertFalse(mFullScreenMagnificationController.isRegistered(DISPLAY_1));
 
+        // Once for each display on unregister
         verify(mMockThumbnail, times(2)).hideThumbnail();
     }
 
@@ -543,7 +545,11 @@
         // The first time is triggered when the thumbnail is just created.
         // The second time is triggered when the magnification region changed.
         verify(mMockThumbnail, times(2)).setThumbnailBounds(
-                any(), anyFloat(), anyFloat(), anyFloat());
+                /* currentBounds= */ any(),
+                /* scale= */ anyFloat(),
+                /* centerX= */ anyFloat(),
+                /* centerY= */ anyFloat()
+        );
     }
 
     @Test
@@ -681,6 +687,9 @@
         checkActivatedAndMagnifying(/* activated= */ true, /* magnifying= */ true, displayId);
         assertTrue(mFullScreenMagnificationController.resetIfNeeded(displayId, SERVICE_ID_2));
         checkActivatedAndMagnifying(/* activated= */ false, /* magnifying= */ false, displayId);
+
+        // Once on init before it's activated and once for reset
+        verify(mMockThumbnail, times(2)).hideThumbnail();
     }
 
     @Test
@@ -783,6 +792,9 @@
         mMessageCapturingHandler.sendAllMessages();
         checkActivatedAndMagnifying(/* activated= */ false, /* magnifying= */ false, DISPLAY_0);
         checkActivatedAndMagnifying(/* activated= */ false, /* magnifying= */ false, DISPLAY_1);
+
+        // Twice for each display: once on init before it's activated and once for screen off
+        verify(mMockThumbnail, times(4)).hideThumbnail();
     }
 
     @Test
@@ -847,6 +859,15 @@
         mMessageCapturingHandler.sendAllMessages();
         checkActivatedAndMagnifying(
                 /* activated= */ expectedActivated, /* magnifying= */ false, displayId);
+
+        if (expectedActivated) {
+            verify(mMockThumbnail, times(2)).setThumbnailBounds(
+                    /* currentBounds= */ any(),
+                    /* scale= */ anyFloat(),
+                    /* centerX= */ anyFloat(),
+                    /* centerY= */ anyFloat()
+            );
+        }
     }
 
     @Test
@@ -950,6 +971,13 @@
                 INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER, scale);
         assertThat(endSpec, closeTo(getMagnificationSpec(scale, expectedOffsets)));
         verify(mMockWindowManager).setMagnificationSpec(eq(displayId), argThat(closeTo(endSpec)));
+
+        verify(mMockThumbnail, atLeastOnce()).setThumbnailBounds(
+                /* currentBounds= */ any(),
+                eq(scale),
+                /* centerX= */ anyFloat(),
+                /* centerY= */ anyFloat()
+        );
     }
 
     @Test
@@ -984,6 +1012,13 @@
                 INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER, scale);
         assertThat(endSpec, closeTo(getMagnificationSpec(scale, expectedOffsets)));
         verify(mMockWindowManager).setMagnificationSpec(eq(displayId), argThat(closeTo(endSpec)));
+
+        verify(mMockThumbnail, atLeastOnce()).setThumbnailBounds(
+                /* currentBounds= */ any(),
+                eq(scale),
+                /* centerX= */ anyFloat(),
+                /* centerY= */ anyFloat()
+        );
     }
 
     @Test
@@ -1246,6 +1281,13 @@
         callbacks.onImeWindowVisibilityChanged(true);
         mMessageCapturingHandler.sendAllMessages();
         verify(mRequestObserver).onImeWindowVisibilityChanged(eq(DISPLAY_0), eq(true));
+
+        verify(mMockThumbnail, atLeastOnce()).setThumbnailBounds(
+                /* currentBounds= */ any(),
+                /* scale= */ anyFloat(),
+                /* centerX= */ anyFloat(),
+                /* centerY= */ anyFloat()
+        );
     }
 
     @Test
@@ -1270,6 +1312,15 @@
         mFullScreenMagnificationController.onUserContextChanged(DISPLAY_0);
 
         verify(mRequestObserver).onFullScreenMagnificationActivationState(eq(DISPLAY_0), eq(false));
+        verify(mMockThumbnail).setThumbnailBounds(
+                /* currentBounds= */ any(),
+                /* scale= */ anyFloat(),
+                /* centerX= */ anyFloat(),
+                /* centerY= */ anyFloat()
+        );
+
+        // Once on init before it's activated and once for reset
+        verify(mMockThumbnail, times(2)).hideThumbnail();
     }
 
     @Test
@@ -1281,6 +1332,12 @@
 
         assertEquals(1.0f, mFullScreenMagnificationController.getScale(DISPLAY_0), 0);
         assertTrue(mFullScreenMagnificationController.isActivated(DISPLAY_0));
+        verify(mMockThumbnail).setThumbnailBounds(
+                /* currentBounds= */ any(),
+                /* scale= */ anyFloat(),
+                /* centerX= */ anyFloat(),
+                /* centerY= */ anyFloat()
+        );
     }
 
     private void setScaleToMagnifying() {
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 2d4bf14..32d0c98 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
@@ -44,8 +44,10 @@
 import android.graphics.PointF;
 import android.os.Handler;
 import android.os.Message;
+import android.os.UserHandle;
 import android.os.VibrationEffect;
 import android.os.Vibrator;
+import android.provider.Settings;
 import android.testing.TestableContext;
 import android.util.DebugUtils;
 import android.view.InputDevice;
@@ -140,8 +142,6 @@
     @Mock
     WindowMagnificationPromptController mWindowMagnificationPromptController;
     @Mock
-    AccessibilityManagerService mMockAccessibilityManagerService;
-    @Mock
     AccessibilityTraceManager mMockTraceManager;
 
     @Rule
@@ -153,6 +153,8 @@
 
     private long mLastDownTime = Integer.MIN_VALUE;
 
+    private float mOriginalMagnificationPersistedScale;
+
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
@@ -166,6 +168,13 @@
         when(mockController.newValueAnimator()).thenReturn(new ValueAnimator());
         when(mockController.getAnimationDuration()).thenReturn(1000L);
         when(mockWindowManager.setMagnificationCallbacks(eq(DISPLAY_0), any())).thenReturn(true);
+        mOriginalMagnificationPersistedScale = Settings.Secure.getFloatForUser(
+                mContext.getContentResolver(),
+                Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, 2.0f,
+                UserHandle.USER_SYSTEM);
+        Settings.Secure.putFloatForUser(mContext.getContentResolver(),
+                Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, 2.0f,
+                UserHandle.USER_SYSTEM);
         mFullScreenMagnificationController = new FullScreenMagnificationController(
                 mockController,
                 new Object(),
@@ -192,6 +201,10 @@
         mMgh.onDestroy();
         mFullScreenMagnificationController.unregister(DISPLAY_0);
         verify(mWindowMagnificationPromptController).onDestroy();
+        Settings.Secure.putFloatForUser(mContext.getContentResolver(),
+                Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE,
+                mOriginalMagnificationPersistedScale,
+                UserHandle.USER_SYSTEM);
     }
 
     @NonNull
@@ -525,10 +538,9 @@
         final float threshold = FullScreenMagnificationGestureHandler.PanningScalingState
                 .CHECK_DETECTING_PASS_PERSISTED_SCALE_THRESHOLD;
         final float persistedScale = (1.0f + threshold) * scale + 1.0f;
-        mFullScreenMagnificationController.setScale(DISPLAY_0, persistedScale, DEFAULT_X,
-                DEFAULT_Y, /* animate= */ false,
-                AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
-        mFullScreenMagnificationController.persistScale(DISPLAY_0);
+        Settings.Secure.putFloatForUser(mContext.getContentResolver(),
+                Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, persistedScale,
+                UserHandle.USER_SYSTEM);
         mFullScreenMagnificationController.setScale(DISPLAY_0, scale, DEFAULT_X,
                 DEFAULT_Y, /* animate= */ false,
                 AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
@@ -547,10 +559,9 @@
         final float threshold = FullScreenMagnificationGestureHandler.PanningScalingState
                 .CHECK_DETECTING_PASS_PERSISTED_SCALE_THRESHOLD;
         final float persistedScale = (1.0f + threshold) * scale - 0.1f;
-        mFullScreenMagnificationController.setScale(DISPLAY_0, persistedScale, DEFAULT_X,
-                DEFAULT_Y, /* animate= */ false,
-                AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
-        mFullScreenMagnificationController.persistScale(DISPLAY_0);
+        Settings.Secure.putFloatForUser(mContext.getContentResolver(),
+                Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, persistedScale,
+                UserHandle.USER_SYSTEM);
         mFullScreenMagnificationController.setScale(DISPLAY_0, scale, DEFAULT_X,
                 DEFAULT_Y, /* animate= */ false,
                 AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
index e75fc21..d4c6fad 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
@@ -606,9 +606,10 @@
     public void onPerformScaleAction_fullScreenMagnifierEnabled_handleScaleChange()
             throws RemoteException {
         final float newScale = 4.0f;
+        final boolean updatePersistence = true;
         setMagnificationEnabled(MODE_FULLSCREEN);
 
-        mMagnificationController.onPerformScaleAction(TEST_DISPLAY, newScale);
+        mMagnificationController.onPerformScaleAction(TEST_DISPLAY, newScale, updatePersistence);
 
         verify(mScreenMagnificationController).setScaleAndCenter(eq(TEST_DISPLAY), eq(newScale),
                 anyFloat(), anyFloat(), anyBoolean(), anyInt());
@@ -619,12 +620,13 @@
     public void onPerformScaleAction_windowMagnifierEnabled_handleScaleChange()
             throws RemoteException {
         final float newScale = 4.0f;
+        final boolean updatePersistence = false;
         setMagnificationEnabled(MODE_WINDOW);
 
-        mMagnificationController.onPerformScaleAction(TEST_DISPLAY, newScale);
+        mMagnificationController.onPerformScaleAction(TEST_DISPLAY, newScale, updatePersistence);
 
         verify(mWindowMagnificationManager).setScale(eq(TEST_DISPLAY), eq(newScale));
-        verify(mWindowMagnificationManager).persistScale(eq(TEST_DISPLAY));
+        verify(mWindowMagnificationManager, never()).persistScale(eq(TEST_DISPLAY));
     }
 
     @Test
@@ -666,6 +668,21 @@
         assertEquals(config.getCenterX(), actualConfig.getCenterX(), 0);
         assertEquals(config.getCenterY(), actualConfig.getCenterY(), 0);
         assertEquals(config.getScale(), actualConfig.getScale(), 0);
+
+        verify(mWindowMagnificationManager).onUserMagnificationScaleChanged(
+                /* userId= */ anyInt(), eq(TEST_DISPLAY), eq(config.getScale()));
+    }
+
+    @Test
+    public void onSourceBoundChanged_windowEnabled_notifyMagnificationChanged()
+            throws RemoteException {
+        setMagnificationEnabled(MODE_WINDOW);
+        reset(mWindowMagnificationManager);
+
+        mMagnificationController.onSourceBoundsChanged(TEST_DISPLAY, TEST_RECT);
+
+        verify(mWindowMagnificationManager).onUserMagnificationScaleChanged(
+                /* userId= */ anyInt(), eq(TEST_DISPLAY), eq(DEFAULT_SCALE));
     }
 
     @Test
@@ -794,6 +811,7 @@
         verify(mMagnificationController).onWindowMagnificationActivationState(
                 eq(TEST_DISPLAY), eq(true));
     }
+
     @Test
     public void deactivateWindowMagnification_windowActivated_triggerCallbackAndLogUsage()
             throws RemoteException {
@@ -1294,9 +1312,9 @@
         }
 
         @Override
-        public void onPerformScaleAction(int displayId, float scale) {
+        public void onPerformScaleAction(int displayId, float scale, boolean updatePersistence) {
             if (mCallback != null) {
-                mCallback.onPerformScaleAction(displayId, scale);
+                mCallback.onPerformScaleAction(displayId, scale, updatePersistence);
             }
         }
 
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationThumbnailTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationThumbnailTest.java
index 3baa102..8faddf8 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationThumbnailTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationThumbnailTest.java
@@ -187,6 +187,29 @@
                 .addView(eq(mMagnificationThumbnail.mThumbnailLayout), any());
         verify(mMockWindowManager, never())
                 .removeView(eq(mMagnificationThumbnail.mThumbnailLayout));
+        verify(mMockWindowManager, never())
+                .updateViewLayout(eq(mMagnificationThumbnail.mThumbnailLayout), any());
+    }
+
+    @Test
+    public void whenVisible_setBoundsUpdatesLayout() throws InterruptedException {
+        runOnMainSync(() -> mMagnificationThumbnail.updateThumbnail(
+                /* scale=   */ 2f,
+                /* centerX= */ 5,
+                /* centerY= */ 10
+        ));
+        runOnMainSync(() -> mMagnificationThumbnail.setThumbnailBounds(
+                new Rect(),
+                /* scale=   */ 2f,
+                /* centerX= */ 5,
+                /* centerY= */ 10
+        ));
+        idle();
+
+        verify(mMockWindowManager).updateViewLayout(
+                eq(mMagnificationThumbnail.mThumbnailLayout),
+                /* params= */ any()
+        );
     }
 
     private static void idle() {
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapperTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapperTest.java
index 2357e65..8608199 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapperTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapperTest.java
@@ -122,6 +122,15 @@
     }
 
     @Test
+    public void onUserMagnificationScaleChanged() throws RemoteException {
+        final int testUserId = 1;
+        final float testScale = 3f;
+        mConnectionWrapper.onUserMagnificationScaleChanged(testUserId, TEST_DISPLAY, testScale);
+        verify(mConnection).onUserMagnificationScaleChanged(
+                eq(testUserId), eq(TEST_DISPLAY), eq(testScale));
+    }
+
+    @Test
     public void setMirrorWindowCallback() throws RemoteException {
         mConnectionWrapper.setConnectionCallback(mCallback);
         verify(mConnection).setConnectionCallback(mCallback);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java
index c98de7c..bbbab21 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java
@@ -66,7 +66,6 @@
 import com.android.server.statusbar.StatusBarManagerInternal;
 
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.mockito.Mock;
 import org.mockito.Mockito;
@@ -308,7 +307,6 @@
                 MagnificationScaleProvider.MAX_SCALE);
     }
 
-    @Ignore("b/278816260: We could refer to b/182561174#comment4 for solution.")
     @Test
     public void logTrackingTypingFocus_processScroll_logDuration() {
         WindowMagnificationManager spyWindowMagnificationManager = spy(mWindowMagnificationManager);
@@ -548,6 +546,18 @@
     }
 
     @Test
+    public void onUserMagnificationScaleChanged_hasConnection_invokeConnectionMethod()
+            throws RemoteException {
+        mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
+
+        final float testScale = 3f;
+        mWindowMagnificationManager.onUserMagnificationScaleChanged(
+                CURRENT_USER_ID, TEST_DISPLAY, testScale);
+        verify(mMockConnection.getConnection()).onUserMagnificationScaleChanged(
+                eq(CURRENT_USER_ID), eq(TEST_DISPLAY), eq(testScale));
+    }
+
+    @Test
     public void pointersInWindow_magnifierEnabled_returnCorrectValue() throws RemoteException {
         mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
         mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, NaN, NaN);
@@ -564,12 +574,15 @@
     @Test
     public void onPerformScaleAction_magnifierEnabled_notifyAction() throws RemoteException {
         final float newScale = 4.0f;
+        final boolean updatePersistence = true;
         mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
         mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, NaN, NaN);
 
-        mMockConnection.getConnectionCallback().onPerformScaleAction(TEST_DISPLAY, newScale);
+        mMockConnection.getConnectionCallback().onPerformScaleAction(
+                TEST_DISPLAY, newScale, updatePersistence);
 
-        verify(mMockCallback).onPerformScaleAction(eq(TEST_DISPLAY), eq(newScale));
+        verify(mMockCallback).onPerformScaleAction(
+                eq(TEST_DISPLAY), eq(newScale), eq(updatePersistence));
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityManagerTest.java b/services/tests/servicestests/src/com/android/server/am/ActivityManagerTest.java
index 327a80e..99eb047 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityManagerTest.java
@@ -52,7 +52,6 @@
 import android.provider.DeviceConfig;
 import android.provider.Settings;
 import android.server.wm.settings.SettingsSession;
-import android.support.test.uiautomator.UiDevice;
 import android.test.suitebuilder.annotation.LargeTest;
 import android.text.TextUtils;
 import android.util.KeyValueListParser;
@@ -61,6 +60,7 @@
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.FlakyTest;
+import androidx.test.uiautomator.UiDevice;
 
 import org.junit.Before;
 import org.junit.Ignore;
diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
index dccacb4..24a628e 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -1342,6 +1342,10 @@
             Message copy = new Message();
             copy.copyFrom(msg);
             mMessages.add(copy);
+            if (msg.getCallback() != null) {
+                msg.getCallback().run();
+                msg.setCallback(null);
+            }
             return super.sendMessageAtTime(msg, uptimeMillis);
         }
     }
diff --git a/services/tests/servicestests/src/com/android/server/appop/AppOpsNotedWatcherTest.java b/services/tests/servicestests/src/com/android/server/appop/AppOpsNotedWatcherTest.java
index 47fdcb6..b5229d8 100644
--- a/services/tests/servicestests/src/com/android/server/appop/AppOpsNotedWatcherTest.java
+++ b/services/tests/servicestests/src/com/android/server/appop/AppOpsNotedWatcherTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.appops;
+package com.android.server.appop;
 
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.inOrder;
diff --git a/services/tests/servicestests/src/com/android/server/appop/OWNERS b/services/tests/servicestests/src/com/android/server/appop/OWNERS
index 999ea0e..2fe78c5 100644
--- a/services/tests/servicestests/src/com/android/server/appop/OWNERS
+++ b/services/tests/servicestests/src/com/android/server/appop/OWNERS
@@ -1 +1,2 @@
+#Bug component: 137825
 include /core/java/android/permission/OWNERS
diff --git a/services/tests/servicestests/src/com/android/server/attention/AttentionManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/attention/AttentionManagerServiceTest.java
index 897b91e..3475c8f 100644
--- a/services/tests/servicestests/src/com/android/server/attention/AttentionManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/attention/AttentionManagerServiceTest.java
@@ -71,6 +71,7 @@
 @SmallTest
 public class AttentionManagerServiceTest {
     private static final double PROXIMITY_SUCCESS_STATE = 1.0;
+
     private AttentionManagerService mSpyAttentionManager;
     private final int mTimeout = 1000;
     private final Object mLock = new Object();
@@ -125,8 +126,19 @@
     }
 
     @Test
+    public void testRegisterProximityUpdates_returnFalseWhenProximityDisabled() {
+        mSpyAttentionManager.mIsServiceEnabled = true;
+        mSpyAttentionManager.mIsProximityEnabled = false;
+
+        assertThat(mSpyAttentionManager.onStartProximityUpdates(
+                mMockProximityUpdateCallbackInternal))
+                .isFalse();
+    }
+
+    @Test
     public void testRegisterProximityUpdates_returnFalseWhenServiceUnavailable() {
         mSpyAttentionManager.mIsServiceEnabled = true;
+        mSpyAttentionManager.mIsProximityEnabled = true;
         doReturn(false).when(mSpyAttentionManager).isServiceAvailable();
 
         assertThat(mSpyAttentionManager.onStartProximityUpdates(
@@ -138,6 +150,7 @@
     public void testRegisterProximityUpdates_returnFalseWhenPowerManagerNotInteract()
             throws RemoteException {
         mSpyAttentionManager.mIsServiceEnabled = true;
+        mSpyAttentionManager.mIsProximityEnabled = true;
         doReturn(true).when(mSpyAttentionManager).isServiceAvailable();
         doReturn(false).when(mMockIPowerManager).isInteractive();
 
@@ -149,6 +162,7 @@
     @Test
     public void testRegisterProximityUpdates_callOnSuccess() throws RemoteException {
         mSpyAttentionManager.mIsServiceEnabled = true;
+        mSpyAttentionManager.mIsProximityEnabled = true;
         doReturn(true).when(mSpyAttentionManager).isServiceAvailable();
         doReturn(true).when(mMockIPowerManager).isInteractive();
 
@@ -162,6 +176,7 @@
     @Test
     public void testRegisterProximityUpdates_callOnSuccessTwiceInARow() throws RemoteException {
         mSpyAttentionManager.mIsServiceEnabled = true;
+        mSpyAttentionManager.mIsProximityEnabled = true;
         doReturn(true).when(mSpyAttentionManager).isServiceAvailable();
         doReturn(true).when(mMockIPowerManager).isInteractive();
 
@@ -188,6 +203,7 @@
     public void testUnregisterProximityUpdates_noCrashWhenCallbackMismatched()
             throws RemoteException {
         mSpyAttentionManager.mIsServiceEnabled = true;
+        mSpyAttentionManager.mIsProximityEnabled = true;
         doReturn(true).when(mSpyAttentionManager).isServiceAvailable();
         doReturn(true).when(mMockIPowerManager).isInteractive();
         mSpyAttentionManager.onStartProximityUpdates(mMockProximityUpdateCallbackInternal);
@@ -209,6 +225,7 @@
     public void testUnregisterProximityUpdates_cancelRegistrationWhenMatched()
             throws RemoteException {
         mSpyAttentionManager.mIsServiceEnabled = true;
+        mSpyAttentionManager.mIsProximityEnabled = true;
         doReturn(true).when(mSpyAttentionManager).isServiceAvailable();
         doReturn(true).when(mMockIPowerManager).isInteractive();
         mSpyAttentionManager.onStartProximityUpdates(mMockProximityUpdateCallbackInternal);
@@ -221,6 +238,7 @@
     public void testUnregisterProximityUpdates_noCrashWhenTwiceInARow() throws RemoteException {
         // Attention Service registers proximity updates.
         mSpyAttentionManager.mIsServiceEnabled = true;
+        mSpyAttentionManager.mIsProximityEnabled = true;
         doReturn(true).when(mSpyAttentionManager).isServiceAvailable();
         doReturn(true).when(mMockIPowerManager).isInteractive();
         mSpyAttentionManager.onStartProximityUpdates(mMockProximityUpdateCallbackInternal);
@@ -248,6 +266,7 @@
     @Test
     public void testCheckAttention_returnFalseWhenPowerManagerNotInteract() throws RemoteException {
         mSpyAttentionManager.mIsServiceEnabled = true;
+        mSpyAttentionManager.mIsProximityEnabled = true;
         doReturn(false).when(mMockIPowerManager).isInteractive();
         AttentionCallbackInternal callback = Mockito.mock(AttentionCallbackInternal.class);
         assertThat(mSpyAttentionManager.checkAttention(mTimeout, callback)).isFalse();
@@ -256,6 +275,7 @@
     @Test
     public void testCheckAttention_callOnSuccess() throws RemoteException {
         mSpyAttentionManager.mIsServiceEnabled = true;
+        mSpyAttentionManager.mIsProximityEnabled = true;
         doReturn(true).when(mSpyAttentionManager).isServiceAvailable();
         doReturn(true).when(mMockIPowerManager).isInteractive();
         mSpyAttentionManager.mCurrentAttentionCheck = null;
diff --git a/services/tests/servicestests/src/com/android/server/audio/AudioServiceTest.java b/services/tests/servicestests/src/com/android/server/audio/AudioServiceTest.java
index 88d57ac..e565faa 100644
--- a/services/tests/servicestests/src/com/android/server/audio/AudioServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/AudioServiceTest.java
@@ -18,7 +18,7 @@
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.after;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
@@ -42,10 +42,10 @@
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
 import org.mockito.Mock;
 import org.mockito.Spy;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
 
 @MediumTest
 @RunWith(AndroidJUnit4.class)
@@ -58,7 +58,7 @@
     public final MockitoRule mockito = MockitoJUnit.rule();
 
     private Context mContext;
-    private AudioSystemAdapter mAudioSystem;
+    private AudioSystemAdapter mSpyAudioSystem;
     private SettingsAdapter mSettingsAdapter;
 
     @Spy private NoOpSystemServerAdapter mSpySystemServer;
@@ -78,11 +78,11 @@
             sLooperPrepared = true;
         }
         mContext = InstrumentationRegistry.getTargetContext();
-        mAudioSystem = new NoOpAudioSystemAdapter();
+        mSpyAudioSystem = spy(new NoOpAudioSystemAdapter());
         mSettingsAdapter = new NoOpSettingsAdapter();
         when(mMockAppOpsManager.noteOp(anyInt(), anyInt(), anyString(), anyString(), anyString()))
                 .thenReturn(AppOpsManager.MODE_ALLOWED);
-        mAudioService = new AudioService(mContext, mAudioSystem, mSpySystemServer,
+        mAudioService = new AudioService(mContext, mSpyAudioSystem, mSpySystemServer,
                 mSettingsAdapter, mMockAudioPolicy, null, mMockAppOpsManager,
                 mMockPermissionEnforcer);
     }
@@ -95,7 +95,7 @@
     public void testMuteMicrophone() throws Exception {
         Log.i(TAG, "running testMuteMicrophone");
         Assert.assertNotNull(mAudioService);
-        final NoOpAudioSystemAdapter testAudioSystem = (NoOpAudioSystemAdapter) mAudioSystem;
+        final NoOpAudioSystemAdapter testAudioSystem = (NoOpAudioSystemAdapter) mSpyAudioSystem;
         testAudioSystem.configureMuteMicrophoneToFail(false);
         for (boolean muted : new boolean[] { true, false}) {
             testAudioSystem.configureIsMicrophoneMuted(!muted);
@@ -120,7 +120,7 @@
     public void testMuteMicrophoneWhenFail() throws Exception {
         Log.i(TAG, "running testMuteMicrophoneWhenFail");
         Assert.assertNotNull(mAudioService);
-        final NoOpAudioSystemAdapter testAudioSystem = (NoOpAudioSystemAdapter) mAudioSystem;
+        final NoOpAudioSystemAdapter testAudioSystem = (NoOpAudioSystemAdapter) mSpyAudioSystem;
         testAudioSystem.configureMuteMicrophoneToFail(true);
         for (boolean muted : new boolean[] { true, false}) {
             testAudioSystem.configureIsMicrophoneMuted(!muted);
@@ -175,4 +175,28 @@
         Assert.assertEquals(false, mAudioService.isHotwordStreamSupported(false));
         Assert.assertEquals(false, mAudioService.isHotwordStreamSupported(true));
     }
+
+    /**
+     * Test master mute setter and getter
+     */
+    @Test
+    public void testMasterMute() throws Exception {
+        Log.i(TAG, "running testMasterMute");
+        Assert.assertNotNull(mAudioService);
+        for (boolean mute : new boolean[] { true, false}) {
+            boolean wasMute = mAudioService.isMasterMute();
+            mAudioService.setMasterMute(mute, 0 /* flags */, mContext.getOpPackageName(),
+                    UserHandle.getCallingUserId(), null);
+
+            Assert.assertEquals("master mute reporting wrong value",
+                    mute, mAudioService.isMasterMute());
+
+            verify(mSpyAudioSystem, times(wasMute == mute ? 0 : 1)).setMasterMute(mute);
+            // verify the intent for master mute changed is supposed to be fired
+            verify(mSpySystemServer,
+                    after(MAX_MESSAGE_HANDLING_DELAY_MS).times(wasMute == mute ? 0 : 1))
+                    .broadcastMasterMuteStatus(mute);
+            reset(mSpySystemServer);
+        }
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/audio/NoOpAudioSystemAdapter.java b/services/tests/servicestests/src/com/android/server/audio/NoOpAudioSystemAdapter.java
index 08a0878..0eac718 100644
--- a/services/tests/servicestests/src/com/android/server/audio/NoOpAudioSystemAdapter.java
+++ b/services/tests/servicestests/src/com/android/server/audio/NoOpAudioSystemAdapter.java
@@ -142,4 +142,9 @@
             @NonNull AudioAttributes attributes, boolean forVolume) {
         return new ArrayList<>();
     }
+
+    @Override
+    public int setMasterMute(boolean muted) {
+        return AudioSystem.AUDIO_STATUS_OK;
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/audio/NoOpSystemServerAdapter.java b/services/tests/servicestests/src/com/android/server/audio/NoOpSystemServerAdapter.java
index 83c5663..a715f51 100644
--- a/services/tests/servicestests/src/com/android/server/audio/NoOpSystemServerAdapter.java
+++ b/services/tests/servicestests/src/com/android/server/audio/NoOpSystemServerAdapter.java
@@ -39,4 +39,9 @@
     public void sendDeviceBecomingNoisyIntent() {
         // no-op
     }
+
+    @Override
+    public void broadcastMasterMuteStatus(boolean muted) {
+        // no-op
+    }
 }
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 662477d..0cfddd3 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
@@ -26,6 +26,7 @@
 import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTH_CALLED;
 import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTH_STARTED;
 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 junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertFalse;
@@ -48,6 +49,7 @@
 import android.app.trust.ITrustManager;
 import android.content.Context;
 import android.content.res.Resources;
+import android.hardware.biometrics.BiometricConstants;
 import android.hardware.biometrics.BiometricManager.Authenticators;
 import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.ComponentInfoInternal;
@@ -104,6 +106,7 @@
     @Mock private KeyStore mKeyStore;
     @Mock private AuthSession.ClientDeathReceiver mClientDeathReceiver;
     @Mock private BiometricFrameworkStatsLogger mBiometricFrameworkStatsLogger;
+    @Mock private BiometricCameraManager mBiometricCameraManager;
 
     private Random mRandom;
     private IBinder mToken;
@@ -210,6 +213,40 @@
     }
 
     @Test
+    public void testOnErrorReceived_lockoutError() throws RemoteException {
+        setupFingerprint(0 /* id */, FingerprintSensorProperties.TYPE_REAR);
+        setupFace(1 /* id */, false /* 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) {
+            assertEquals(BiometricSensor.STATE_WAITING_FOR_COOKIE, sensor.getSensorState());
+            session.onCookieReceived(
+                    session.mPreAuthInfo.eligibleSensors.get(sensor.id).getCookie());
+        }
+        assertTrue(session.allCookiesReceived());
+        assertEquals(STATE_AUTH_STARTED, session.getState());
+
+        // Either of strong sensor's lockout should cancel both sensors.
+        final int cookie1 = session.mPreAuthInfo.eligibleSensors.get(0).getCookie();
+        session.onErrorReceived(0, cookie1, BiometricConstants.BIOMETRIC_ERROR_LOCKOUT, 0);
+        for (BiometricSensor sensor : session.mPreAuthInfo.eligibleSensors) {
+            assertEquals(BiometricSensor.STATE_CANCELING, sensor.getSensorState());
+        }
+        assertEquals(STATE_ERROR_PENDING_SYSUI, session.getState());
+
+        // If the sensor is STATE_CANCELING, delayed onAuthenticationRejected() shouldn't change the
+        // session state to STATE_AUTH_PAUSED.
+        session.onAuthenticationRejected(1);
+        assertEquals(STATE_ERROR_PENDING_SYSUI, session.getState());
+    }
+
+    @Test
     public void testCancelReducesAppetiteForCookies() throws Exception {
         setupFace(0 /* id */, false /* confirmationAlwaysRequired */,
                 mock(IBiometricAuthenticator.class));
@@ -571,7 +608,8 @@
                 promptInfo,
                 TEST_PACKAGE,
                 checkDevicePolicyManager,
-                mContext);
+                mContext,
+                mBiometricCameraManager);
     }
 
     private AuthSession createAuthSession(List<BiometricSensor> sensors,
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
index 67be376..fc62e75 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
@@ -16,9 +16,9 @@
 
 package com.android.server.biometrics;
 
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
 import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
 import static android.hardware.biometrics.BiometricManager.Authenticators;
-import static android.hardware.biometrics.SensorProperties.STRENGTH_STRONG;
 import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS;
 
 import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTHENTICATED_PENDING_SYSUI;
@@ -114,6 +114,10 @@
     private static final String ERROR_UNABLE_TO_PROCESS = "error_unable_to_process";
     private static final String ERROR_USER_CANCELED = "error_user_canceled";
     private static final String ERROR_LOCKOUT = "error_lockout";
+    private static final String FACE_SUBTITLE = "face_subtitle";
+    private static final String FINGERPRINT_SUBTITLE = "fingerprint_subtitle";
+    private static final String DEFAULT_SUBTITLE = "default_subtitle";
+
 
     private static final String FINGERPRINT_ACQUIRED_SENSOR_DIRTY = "sensor_dirty";
 
@@ -152,6 +156,8 @@
     private AuthSessionCoordinator mAuthSessionCoordinator;
     @Mock
     private UserManager mUserManager;
+    @Mock
+    private BiometricCameraManager mBiometricCameraManager;
 
     BiometricContextProvider mBiometricContextProvider;
 
@@ -178,13 +184,24 @@
         when(mInjector.getDevicePolicyManager(any())).thenReturn(mDevicePolicyManager);
         when(mInjector.getRequestGenerator()).thenReturn(() -> TEST_REQUEST_ID);
         when(mInjector.getUserManager(any())).thenReturn(mUserManager);
+        when(mInjector.getBiometricCameraManager(any())).thenReturn(mBiometricCameraManager);
 
         when(mResources.getString(R.string.biometric_error_hw_unavailable))
                 .thenReturn(ERROR_HW_UNAVAILABLE);
         when(mResources.getString(R.string.biometric_not_recognized))
                 .thenReturn(ERROR_NOT_RECOGNIZED);
+        when(mResources.getString(R.string.biometric_face_not_recognized))
+                .thenReturn(ERROR_NOT_RECOGNIZED);
+        when(mResources.getString(R.string.fingerprint_error_not_match))
+                .thenReturn(ERROR_NOT_RECOGNIZED);
         when(mResources.getString(R.string.biometric_error_user_canceled))
                 .thenReturn(ERROR_USER_CANCELED);
+        when(mContext.getString(R.string.biometric_dialog_face_subtitle))
+                .thenReturn(FACE_SUBTITLE);
+        when(mContext.getString(R.string.biometric_dialog_fingerprint_subtitle))
+                .thenReturn(FINGERPRINT_SUBTITLE);
+        when(mContext.getString(R.string.biometric_dialog_default_subtitle))
+                .thenReturn(DEFAULT_SUBTITLE);
 
         when(mWindowManager.getDefaultDisplay()).thenReturn(
                 new Display(DisplayManagerGlobal.getInstance(), Display.DEFAULT_DISPLAY,
@@ -205,7 +222,7 @@
 
     @Test
     public void testClientBinderDied_whenPaused() throws Exception {
-        setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
+        setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
 
         invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
                 true /* requireConfirmation */, null /* authenticators */);
@@ -232,7 +249,7 @@
 
     @Test
     public void testClientBinderDied_whenAuthenticating() throws Exception {
-        setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
+        setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
 
         invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
                 true /* requireConfirmation */, null /* authenticators */);
@@ -368,7 +385,7 @@
 
         final int[] modalities = new int[] {
                 TYPE_FINGERPRINT,
-                BiometricAuthenticator.TYPE_FACE,
+                TYPE_FACE,
         };
 
         final int[] strengths = new int[] {
@@ -421,9 +438,56 @@
     }
 
     @Test
+    public void testAuthenticateFace_shouldShowSubtitleForFace() throws Exception {
+        setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
+
+        invokeAuthenticate(mBiometricService.mImpl, mReceiver1,
+                false /* requireConfirmation */,
+                null);
+        waitForIdle();
+
+        assertEquals(FACE_SUBTITLE, mBiometricService.mAuthSession.mPromptInfo.getSubtitle());
+    }
+
+    @Test
+    public void testAuthenticateFingerprint_shouldShowSubtitleForFingerprint() throws Exception {
+        setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
+
+        invokeAuthenticate(mBiometricService.mImpl, mReceiver1,
+                false /* requireConfirmation */,
+                null);
+        waitForIdle();
+
+        assertEquals(FINGERPRINT_SUBTITLE,
+                mBiometricService.mAuthSession.mPromptInfo.getSubtitle());
+    }
+
+    @Test
+    public void testAuthenticateBothFpAndFace_shouldShowDefaultSubtitle() throws Exception {
+        final int[] modalities = new int[] {
+                TYPE_FINGERPRINT,
+                TYPE_FACE,
+        };
+
+        final int[] strengths = new int[] {
+                Authenticators.BIOMETRIC_WEAK,
+                Authenticators.BIOMETRIC_STRONG,
+        };
+
+        setupAuthForMultiple(modalities, strengths);
+
+        invokeAuthenticate(mBiometricService.mImpl, mReceiver1,
+                false /* requireConfirmation */,
+                null);
+        waitForIdle();
+
+        assertEquals(DEFAULT_SUBTITLE, mBiometricService.mAuthSession.mPromptInfo.getSubtitle());
+    }
+
+    @Test
     public void testAuthenticateFace_respectsUserSetting()
             throws Exception {
-        setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
+        setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
 
         // Disabled in user settings receives onError
         when(mBiometricService.mSettingObserver.getEnabledForApps(anyInt())).thenReturn(false);
@@ -562,7 +626,7 @@
 
     @Test
     public void testAuthenticate_noBiometrics_credentialAllowed() throws Exception {
-        setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
+        setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
         when(mFaceAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(false);
         when(mTrustManager.isDeviceSecure(anyInt(), anyInt()))
                 .thenReturn(true);
@@ -589,13 +653,13 @@
 
     @Test
     public void testAuthenticate_happyPathWithConfirmation_strongBiometric() throws Exception {
-        setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
+        setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
         testAuthenticate_happyPathWithConfirmation(true /* isStrongBiometric */);
     }
 
     @Test
     public void testAuthenticate_happyPathWithConfirmation_weakBiometric() throws Exception {
-        setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_WEAK);
+        setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_WEAK);
         testAuthenticate_happyPathWithConfirmation(false /* isStrongBiometric */);
     }
 
@@ -631,7 +695,7 @@
 
     @Test
     public void testAuthenticate_no_Biometrics_noCredential() throws Exception {
-        setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
+        setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
         when(mFaceAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(false);
         when(mTrustManager.isDeviceSecure(anyInt(), anyInt()))
                 .thenReturn(false);
@@ -649,7 +713,7 @@
     @Test
     public void testRejectFace_whenAuthenticating_notifiesSystemUIAndClient_thenPaused()
             throws Exception {
-        setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
+        setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
         invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
                 false /* requireConfirmation */, null /* authenticators */);
 
@@ -657,7 +721,7 @@
         waitForIdle();
 
         verify(mBiometricService.mStatusBarService).onBiometricError(
-                eq(BiometricAuthenticator.TYPE_FACE),
+                eq(TYPE_FACE),
                 eq(BiometricConstants.BIOMETRIC_PAUSED_REJECTED),
                 eq(0 /* vendorCode */));
         verify(mReceiver1).onAuthenticationFailed();
@@ -685,7 +749,7 @@
 
     @Test
     public void testRequestAuthentication_whenAlreadyAuthenticating() throws Exception {
-        setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
+        setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
         invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
                 false /* requireConfirmation */, null /* authenticators */);
 
@@ -694,7 +758,7 @@
         waitForIdle();
 
         verify(mReceiver1).onError(
-                eq(BiometricAuthenticator.TYPE_FACE),
+                eq(TYPE_FACE),
                 eq(BiometricPrompt.BIOMETRIC_ERROR_CANCELED),
                 eq(0) /* vendorCode */);
         verify(mBiometricService.mStatusBarService).hideAuthenticationDialog(eq(TEST_REQUEST_ID));
@@ -704,7 +768,7 @@
 
     @Test
     public void testErrorHalTimeout_whenAuthenticating_entersPausedState() throws Exception {
-        setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
+        setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
         invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
                 false /* requireConfirmation */, null /* authenticators */);
 
@@ -717,7 +781,7 @@
 
         assertEquals(STATE_AUTH_PAUSED, mBiometricService.mAuthSession.getState());
         verify(mBiometricService.mStatusBarService).onBiometricError(
-                eq(BiometricAuthenticator.TYPE_FACE),
+                eq(TYPE_FACE),
                 eq(BiometricConstants.BIOMETRIC_ERROR_TIMEOUT),
                 eq(0 /* vendorCode */));
         // Timeout does not count as fail as per BiometricPrompt documentation.
@@ -753,7 +817,7 @@
 
     @Test
     public void testErrorFromHal_whenPaused_notifiesSystemUIAndClient() throws Exception {
-        setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
+        setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
         invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
                 false /* requireConfirmation */, null /* authenticators */);
 
@@ -771,7 +835,7 @@
 
         // Client receives error immediately
         verify(mReceiver1).onError(
-                eq(BiometricAuthenticator.TYPE_FACE),
+                eq(TYPE_FACE),
                 eq(BiometricConstants.BIOMETRIC_ERROR_CANCELED),
                 eq(0 /* vendorCode */));
         // Dialog is hidden immediately
@@ -903,6 +967,45 @@
     }
 
     @Test
+    public void testMultiBiometricAuth_whenLockoutTimed_sendsErrorAndModality()
+            throws Exception {
+        testMultiBiometricAuth_whenLockout(LockoutTracker.LOCKOUT_TIMED,
+                BiometricPrompt.BIOMETRIC_ERROR_LOCKOUT);
+    }
+
+    @Test
+    public void testMultiBiometricAuth_whenLockoutPermanent_sendsErrorAndModality()
+            throws Exception {
+        testMultiBiometricAuth_whenLockout(LockoutTracker.LOCKOUT_PERMANENT,
+                BiometricPrompt.BIOMETRIC_ERROR_LOCKOUT_PERMANENT);
+    }
+
+    private void testMultiBiometricAuth_whenLockout(@LockoutTracker.LockoutMode int lockoutMode,
+            int biometricPromptError) throws Exception {
+        final int[] modalities = new int[] {
+                TYPE_FINGERPRINT,
+                TYPE_FACE,
+        };
+
+        final int[] strengths = new int[] {
+                Authenticators.BIOMETRIC_STRONG,
+                Authenticators.BIOMETRIC_STRONG,
+        };
+        setupAuthForMultiple(modalities, strengths);
+
+        when(mFingerprintAuthenticator.getLockoutModeForUser(anyInt()))
+                .thenReturn(lockoutMode);
+        when(mFaceAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(false);
+        invokeAuthenticate(mBiometricService.mImpl, mReceiver1,
+                false /* requireConfirmation */, null /* authenticators */);
+        waitForIdle();
+
+        // The lockout error should be sent, instead of ERROR_NONE_ENROLLED. See b/286923477.
+        verify(mReceiver1).onError(eq(TYPE_FINGERPRINT),
+                eq(biometricPromptError), eq(0) /* vendorCode */);
+    }
+
+    @Test
     public void testBiometricOrCredentialAuth_whenBiometricLockout_showsCredential()
             throws Exception {
         when(mTrustManager.isDeviceSecure(anyInt(), anyInt())).thenReturn(true);
@@ -1078,7 +1181,7 @@
 
     @Test
     public void testDismissedReasonNegative_whilePaused_invokeHalCancel() throws Exception {
-        setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
+        setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
         invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
                 false /* requireConfirmation */, null /* authenticators */);
 
@@ -1097,7 +1200,7 @@
 
     @Test
     public void testDismissedReasonUserCancel_whilePaused_invokesHalCancel() throws Exception {
-        setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
+        setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
         invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
                 false /* requireConfirmation */, null /* authenticators */);
 
@@ -1116,7 +1219,7 @@
 
     @Test
     public void testDismissedReasonUserCancel_whenPendingConfirmation() throws Exception {
-        setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
+        setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
         invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
                 true /* requireConfirmation */, null /* authenticators */);
 
@@ -1130,7 +1233,7 @@
         verify(mBiometricService.mSensors.get(0).impl)
                 .cancelAuthenticationFromService(any(), any(), anyLong());
         verify(mReceiver1).onError(
-                eq(BiometricAuthenticator.TYPE_FACE),
+                eq(TYPE_FACE),
                 eq(BiometricConstants.BIOMETRIC_ERROR_USER_CANCELED),
                 eq(0 /* vendorCode */));
         verify(mBiometricService.mKeyStore, never()).addAuthToken(any(byte[].class));
@@ -1251,7 +1354,7 @@
 
     @Test
     public void testCanAuthenticate_whenBiometricsNotEnabledForApps() throws Exception {
-        setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
+        setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
         when(mBiometricService.mSettingObserver.getEnabledForApps(anyInt())).thenReturn(false);
         when(mTrustManager.isDeviceSecure(anyInt(), anyInt()))
                 .thenReturn(true);
@@ -1545,7 +1648,7 @@
     @Test
     public void testWorkAuthentication_faceWorksIfNotDisabledByDevicePolicyManager()
             throws Exception {
-        setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
+        setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
         when(mDevicePolicyManager
                 .getKeyguardDisabledFeatures(any() /* admin*/, anyInt() /* userHandle */))
                 .thenReturn(~DevicePolicyManager.KEYGUARD_DISABLE_FACE);
@@ -1638,7 +1741,7 @@
                     mFingerprintAuthenticator);
         }
 
-        if ((modality & BiometricAuthenticator.TYPE_FACE) != 0) {
+        if ((modality & TYPE_FACE) != 0) {
             when(mFaceAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(enrolled);
             when(mFaceAuthenticator.isHardwareDetected(any())).thenReturn(true);
             when(mFaceAuthenticator.getLockoutModeForUser(anyInt()))
@@ -1670,7 +1773,7 @@
                         strength, mFingerprintAuthenticator);
             }
 
-            if ((modality & BiometricAuthenticator.TYPE_FACE) != 0) {
+            if ((modality & TYPE_FACE) != 0) {
                 when(mFaceAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(true);
                 when(mFaceAuthenticator.isHardwareDetected(any())).thenReturn(true);
                 mBiometricService.mImpl.registerAuthenticator(SENSOR_ID_FACE, modality,
@@ -1753,6 +1856,7 @@
             boolean checkDevicePolicy) {
         final PromptInfo promptInfo = new PromptInfo();
         promptInfo.setConfirmationRequested(requireConfirmation);
+        promptInfo.setUseDefaultSubtitle(true);
 
         if (authenticators != null) {
             promptInfo.setAuthenticators(authenticators);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java b/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java
new file mode 100644
index 0000000..c2bdf50
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.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
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.biometrics;
+
+import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_NONE;
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
+
+import static com.android.server.biometrics.sensors.LockoutTracker.LOCKOUT_NONE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.when;
+
+import android.app.admin.DevicePolicyManager;
+import android.app.trust.ITrustManager;
+import android.content.Context;
+import android.hardware.biometrics.BiometricManager;
+import android.hardware.biometrics.IBiometricAuthenticator;
+import android.hardware.biometrics.PromptInfo;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.List;
+
+@Presubmit
+@SmallTest
+public class PreAuthInfoTest {
+    @Rule
+    public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+
+    private static final int SENSOR_ID_FACE = 1;
+    private static final String TEST_PACKAGE_NAME = "PreAuthInfoTestPackage";
+
+    @Mock
+    IBiometricAuthenticator mFaceAuthenticator;
+    @Mock
+    Context mContext;
+    @Mock
+    ITrustManager mTrustManager;
+    @Mock
+    DevicePolicyManager mDevicePolicyManager;
+    @Mock
+    BiometricService.SettingObserver mSettingObserver;
+    @Mock
+    BiometricCameraManager mBiometricCameraManager;
+
+    @Before
+    public void setup() throws RemoteException {
+        when(mTrustManager.isDeviceSecure(anyInt(), anyInt())).thenReturn(true);
+        when(mDevicePolicyManager.getKeyguardDisabledFeatures(any(), anyInt()))
+                .thenReturn(KEYGUARD_DISABLE_FEATURES_NONE);
+        when(mSettingObserver.getEnabledForApps(anyInt())).thenReturn(true);
+        when(mFaceAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(true);
+        when(mFaceAuthenticator.isHardwareDetected(any())).thenReturn(true);
+        when(mFaceAuthenticator.getLockoutModeForUser(anyInt()))
+                .thenReturn(LOCKOUT_NONE);
+        when(mBiometricCameraManager.isCameraPrivacyEnabled()).thenReturn(false);
+        when(mBiometricCameraManager.isAnyCameraUnavailable()).thenReturn(false);
+    }
+
+    @Test
+    public void testFaceAuthentication_whenCameraPrivacyIsEnabled() throws Exception {
+        when(mBiometricCameraManager.isCameraPrivacyEnabled()).thenReturn(true);
+
+        BiometricSensor sensor = new BiometricSensor(mContext, SENSOR_ID_FACE, TYPE_FACE,
+                BiometricManager.Authenticators.BIOMETRIC_STRONG, mFaceAuthenticator) {
+            @Override
+            boolean confirmationAlwaysRequired(int userId) {
+                return false;
+            }
+
+            @Override
+            boolean confirmationSupported() {
+                return false;
+            }
+        };
+        PromptInfo promptInfo = new PromptInfo();
+        promptInfo.setConfirmationRequested(false /* requireConfirmation */);
+        promptInfo.setAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG);
+        promptInfo.setDisallowBiometricsIfPolicyExists(false /* checkDevicePolicy */);
+        PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager, mDevicePolicyManager,
+                mSettingObserver, List.of(sensor),
+                0 /* userId */, promptInfo, TEST_PACKAGE_NAME,
+                false /* checkDevicePolicyManager */, mContext, mBiometricCameraManager);
+
+        assertThat(preAuthInfo.eligibleSensors).isEmpty();
+    }
+
+    @Test
+    public void testFaceAuthentication_whenCameraPrivacyIsDisabledAndCameraIsAvailable()
+            throws Exception {
+        BiometricSensor sensor = new BiometricSensor(mContext, SENSOR_ID_FACE, TYPE_FACE,
+                BiometricManager.Authenticators.BIOMETRIC_STRONG, mFaceAuthenticator) {
+            @Override
+            boolean confirmationAlwaysRequired(int userId) {
+                return false;
+            }
+
+            @Override
+            boolean confirmationSupported() {
+                return false;
+            }
+        };
+        PromptInfo promptInfo = new PromptInfo();
+        promptInfo.setConfirmationRequested(false /* requireConfirmation */);
+        promptInfo.setAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG);
+        promptInfo.setDisallowBiometricsIfPolicyExists(false /* checkDevicePolicy */);
+        PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager, mDevicePolicyManager,
+                mSettingObserver, List.of(sensor),
+                0 /* userId */, promptInfo, TEST_PACKAGE_NAME,
+                false /* checkDevicePolicyManager */, mContext, mBiometricCameraManager);
+
+        assertThat(preAuthInfo.eligibleSensors).hasSize(1);
+    }
+
+    @Test
+    public void testFaceAuthentication_whenCameraIsUnavailable() throws RemoteException {
+        when(mBiometricCameraManager.isAnyCameraUnavailable()).thenReturn(true);
+        BiometricSensor sensor = new BiometricSensor(mContext, SENSOR_ID_FACE, TYPE_FACE,
+                BiometricManager.Authenticators.BIOMETRIC_STRONG, mFaceAuthenticator) {
+            @Override
+            boolean confirmationAlwaysRequired(int userId) {
+                return false;
+            }
+
+            @Override
+            boolean confirmationSupported() {
+                return false;
+            }
+        };
+        PromptInfo promptInfo = new PromptInfo();
+        promptInfo.setConfirmationRequested(false /* requireConfirmation */);
+        promptInfo.setAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG);
+        promptInfo.setDisallowBiometricsIfPolicyExists(false /* checkDevicePolicy */);
+        PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager, mDevicePolicyManager,
+                mSettingObserver, List.of(sensor),
+                0 /* userId */, promptInfo, TEST_PACKAGE_NAME,
+                false /* checkDevicePolicyManager */, mContext, mBiometricCameraManager);
+
+        assertThat(preAuthInfo.eligibleSensors).hasSize(0);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
index e9d8269..8929900 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
@@ -19,6 +19,8 @@
 import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_ERROR_CANCELED;
 import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_SUCCESS;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static junit.framework.Assert.assertTrue;
 import static junit.framework.Assert.fail;
 
@@ -405,6 +407,59 @@
         testCancelsEnrollWhenRequestId(10L, 20, false /* started */);
     }
 
+    @Test
+    public void testCancelAuthenticationClientWithoutStarting() {
+        final Supplier<Object> lazyDaemon = () -> mock(Object.class);
+        final TestHalClientMonitor client1 = new TestHalClientMonitor(mContext, mToken, lazyDaemon);
+        final ClientMonitorCallbackConverter callback = mock(ClientMonitorCallbackConverter.class);
+        final TestAuthenticationClient client2 = new TestAuthenticationClient(mContext, lazyDaemon,
+                mToken, callback, mBiometricContext);
+
+        //Schedule authentication client to the pending queue
+        mScheduler.scheduleClientMonitor(client1);
+        mScheduler.scheduleClientMonitor(client2);
+        waitForIdle();
+
+        assertThat(mScheduler.getCurrentClient()).isEqualTo(client1);
+
+        client2.cancel();
+        waitForIdle();
+
+        assertThat(client2.isAlreadyCancelled()).isTrue();
+
+        client1.getCallback().onClientFinished(client1, false);
+        waitForIdle();
+
+        assertThat(mScheduler.getCurrentClient()).isNull();
+    }
+
+    @Test
+    public void testCancelAuthenticationClientWithoutStarting_whenAppCrashes() {
+        final Supplier<Object> lazyDaemon = () -> mock(Object.class);
+        final TestHalClientMonitor client1 = new TestHalClientMonitor(mContext, mToken, lazyDaemon);
+        final ClientMonitorCallbackConverter callback = mock(ClientMonitorCallbackConverter.class);
+        final TestAuthenticationClient client2 = new TestAuthenticationClient(mContext, lazyDaemon,
+                mToken, callback, mBiometricContext);
+
+        //Schedule authentication client to the pending queue
+        mScheduler.scheduleClientMonitor(client1);
+        mScheduler.scheduleClientMonitor(client2);
+        waitForIdle();
+
+        assertThat(mScheduler.getCurrentClient()).isEqualTo(client1);
+
+        //App crashes
+        client2.binderDied();
+        waitForIdle();
+
+        assertThat(client2.isAlreadyCancelled()).isTrue();
+
+        client1.getCallback().onClientFinished(client1, false);
+        waitForIdle();
+
+        assertThat(mScheduler.getCurrentClient()).isNull();
+    }
+
     private void testCancelsEnrollWhenRequestId(@Nullable Long requestId, long cancelRequestId,
             boolean started) {
         final Supplier<Object> lazyDaemon = () -> mock(Object.class);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/SensorOverlaysTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/SensorOverlaysTest.java
index 21c9c75..5012335 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/SensorOverlaysTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/SensorOverlaysTest.java
@@ -61,7 +61,7 @@
 
     @Test
     public void noopWhenBothNull() {
-        final SensorOverlays useless = new SensorOverlays(null, null, null);
+        final SensorOverlays useless = new SensorOverlays(null, null);
         useless.show(SENSOR_ID, 2, null);
         useless.hide(SENSOR_ID);
     }
@@ -69,12 +69,12 @@
     @Test
     public void testProvidesUdfps() {
         final List<IUdfpsOverlayController> udfps = new ArrayList<>();
-        SensorOverlays sensorOverlays = new SensorOverlays(null, mSidefpsController, null);
+        SensorOverlays sensorOverlays = new SensorOverlays(null, mSidefpsController);
 
         sensorOverlays.ifUdfps(udfps::add);
         assertThat(udfps).isEmpty();
 
-        sensorOverlays = new SensorOverlays(mUdfpsOverlayController, mSidefpsController, null);
+        sensorOverlays = new SensorOverlays(mUdfpsOverlayController, mSidefpsController);
         sensorOverlays.ifUdfps(udfps::add);
         assertThat(udfps).containsExactly(mUdfpsOverlayController);
     }
@@ -96,7 +96,7 @@
 
     private void testShow(IUdfpsOverlayController udfps, ISidefpsController sidefps)
             throws Exception {
-        final SensorOverlays sensorOverlays = new SensorOverlays(udfps, sidefps, null);
+        final SensorOverlays sensorOverlays = new SensorOverlays(udfps, sidefps);
         final int reason = BiometricOverlayConstants.REASON_UNKNOWN;
         sensorOverlays.show(SENSOR_ID, reason, mAcquisitionClient);
 
@@ -126,7 +126,7 @@
 
     private void testHide(IUdfpsOverlayController udfps, ISidefpsController sidefps)
             throws Exception {
-        final SensorOverlays sensorOverlays = new SensorOverlays(udfps, sidefps, null);
+        final SensorOverlays sensorOverlays = new SensorOverlays(udfps, sidefps);
         sensorOverlays.hide(SENSOR_ID);
 
         if (udfps != null) {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java
index d5d06d3..046b01c 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java
@@ -16,8 +16,10 @@
 
 package com.android.server.biometrics.sensors.face.aidl;
 
+import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_ERROR_CANCELED;
 import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_LOCKOUT;
 import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_LOCKOUT_PERMANENT;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
@@ -205,7 +207,9 @@
         client.onAuthenticated(new Face("friendly", 1 /* faceId */, 2 /* deviceId */),
                 true /* authenticated */, new ArrayList<>());
 
-        verify(mCancellationSignal).cancel();
+        verify(mCancellationSignal, never()).cancel();
+        verify(mClientMonitorCallbackConverter)
+                .onError(anyInt(), anyInt(), eq(BIOMETRIC_ERROR_CANCELED), anyInt());
     }
 
     private FaceAuthenticationClient createClient() throws RemoteException {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
index 31a58cd..d1d6e9d 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
@@ -36,7 +36,6 @@
 import android.os.UserManager;
 import android.platform.test.annotations.Presubmit;
 
-import androidx.annotation.NonNull;
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 
@@ -73,7 +72,7 @@
 
     private SensorProps[] mSensorProps;
     private LockoutResetDispatcher mLockoutResetDispatcher;
-    private TestableFaceProvider mFaceProvider;
+    private FaceProvider mFaceProvider;
 
     private static void waitForIdle() {
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
@@ -98,8 +97,9 @@
 
         mLockoutResetDispatcher = new LockoutResetDispatcher(mContext);
 
-        mFaceProvider = new TestableFaceProvider(mDaemon, mContext, mBiometricStateCallback,
-                mSensorProps, TAG, mLockoutResetDispatcher, mBiometricContext);
+        mFaceProvider = new FaceProvider(mContext, mBiometricStateCallback,
+                mSensorProps, TAG, mLockoutResetDispatcher, mBiometricContext,
+                mDaemon);
     }
 
     @Test
@@ -130,6 +130,7 @@
         for (SensorProps prop : mSensorProps) {
             final BiometricScheduler scheduler =
                     mFaceProvider.mFaceSensors.get(prop.commonProps.sensorId).getScheduler();
+            scheduler.reset();
             for (int i = 0; i < numFakeOperations; i++) {
                 final HalClientMonitor testMonitor = mock(HalClientMonitor.class);
                 when(testMonitor.getFreshDaemon()).thenReturn(new Object());
@@ -142,7 +143,7 @@
         for (SensorProps prop : mSensorProps) {
             final BiometricScheduler scheduler =
                     mFaceProvider.mFaceSensors.get(prop.commonProps.sensorId).getScheduler();
-            assertEquals(numFakeOperations, scheduler.getCurrentPendingCount());
+            assertEquals(numFakeOperations - 1, scheduler.getCurrentPendingCount());
             assertNotNull(scheduler.getCurrentClient());
         }
 
@@ -159,25 +160,4 @@
             assertEquals(0, scheduler.getCurrentPendingCount());
         }
     }
-
-    private static class TestableFaceProvider extends FaceProvider {
-        private final IFace mDaemon;
-
-        TestableFaceProvider(@NonNull IFace daemon,
-                @NonNull Context context,
-                @NonNull BiometricStateCallback biometricStateCallback,
-                @NonNull SensorProps[] props,
-                @NonNull String halInstanceName,
-                @NonNull LockoutResetDispatcher lockoutResetDispatcher,
-                @NonNull BiometricContext biometricContext) {
-            super(context, biometricStateCallback, props, halInstanceName, lockoutResetDispatcher,
-                    biometricContext);
-            mDaemon = daemon;
-        }
-
-        @Override
-        synchronized IFace getHalInstance() {
-            return mDaemon;
-        }
-    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
index f8f40fe..46af905 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
@@ -16,6 +16,8 @@
 
 package com.android.server.biometrics.sensors.fingerprint.aidl;
 
+import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_ERROR_CANCELED;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -399,7 +401,9 @@
 
         mLooper.moveTimeForward(10);
         mLooper.dispatchAll();
-        verify(mCancellationSignal).cancel();
+        verify(mCancellationSignal, never()).cancel();
+        verify(mClientMonitorCallbackConverter)
+                .onError(anyInt(), anyInt(), eq(BIOMETRIC_ERROR_CANCELED), anyInt());
     }
 
     private FingerprintAuthenticationClient createClient() throws RemoteException {
@@ -432,7 +436,7 @@
                 mBiometricLogger, mBiometricContext,
                 true /* isStrongBiometric */,
                 null /* taskStackListener */, null /* lockoutCache */,
-                mUdfpsOverlayController, mSideFpsController, null, allowBackgroundAuthentication,
+                mUdfpsOverlayController, mSideFpsController, allowBackgroundAuthentication,
                 mSensorProps,
                 new Handler(mLooper.getLooper()), 0 /* biometricStrength */, mClock) {
             @Override
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java
index 6dfdd87..20d5f93 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java
@@ -163,6 +163,6 @@
                         .setOpPackageName("a-test")
                         .build(),
                 mBiometricLogger, mBiometricContext,
-                mUdfpsOverlayController, null, true /* isStrongBiometric */);
+                mUdfpsOverlayController, true /* isStrongBiometric */);
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java
index 3c89278..ef25380 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java
@@ -296,6 +296,6 @@
         mClientMonitorCallbackConverter, 0 /* userId */,
         HAT, "owner", mBiometricUtils, 8 /* sensorId */,
         mBiometricLogger, mBiometricContext, mSensorProps, mUdfpsOverlayController,
-        mSideFpsController, null, 6 /* maxTemplatesPerUser */, FingerprintManager.ENROLL_ENROLL);
+        mSideFpsController, 6 /* maxTemplatesPerUser */, FingerprintManager.ENROLL_ENROLL);
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
index 9c01de6..8f6efff 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
@@ -39,7 +39,6 @@
 import android.os.UserManager;
 import android.platform.test.annotations.Presubmit;
 
-import androidx.annotation.NonNull;
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 
@@ -81,7 +80,7 @@
 
     private SensorProps[] mSensorProps;
     private LockoutResetDispatcher mLockoutResetDispatcher;
-    private TestableFingerprintProvider mFingerprintProvider;
+    private FingerprintProvider mFingerprintProvider;
 
     private static void waitForIdle() {
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
@@ -110,17 +109,13 @@
 
         mLockoutResetDispatcher = new LockoutResetDispatcher(mContext);
 
-        mFingerprintProvider = new TestableFingerprintProvider(mDaemon, mContext,
+        mFingerprintProvider = new FingerprintProvider(mContext,
                 mBiometricStateCallback, mSensorProps, TAG, mLockoutResetDispatcher,
-                mGestureAvailabilityDispatcher, mBiometricContext);
+                mGestureAvailabilityDispatcher, mBiometricContext, mDaemon);
     }
 
     @Test
     public void testAddingSensors() {
-        mFingerprintProvider = new TestableFingerprintProvider(mDaemon, mContext,
-                mBiometricStateCallback, mSensorProps, TAG, mLockoutResetDispatcher,
-                mGestureAvailabilityDispatcher, mBiometricContext);
-
         waitForIdle();
 
         for (SensorProps prop : mSensorProps) {
@@ -147,6 +142,7 @@
             final BiometricScheduler scheduler =
                     mFingerprintProvider.mFingerprintSensors.get(prop.commonProps.sensorId)
                             .getScheduler();
+            scheduler.reset();
             for (int i = 0; i < numFakeOperations; i++) {
                 final HalClientMonitor testMonitor = mock(HalClientMonitor.class);
                 when(testMonitor.getFreshDaemon()).thenReturn(new Object());
@@ -160,7 +156,7 @@
             final BiometricScheduler scheduler =
                     mFingerprintProvider.mFingerprintSensors.get(prop.commonProps.sensorId)
                             .getScheduler();
-            assertEquals(numFakeOperations, scheduler.getCurrentPendingCount());
+            assertEquals(numFakeOperations - 1, scheduler.getCurrentPendingCount());
             assertNotNull(scheduler.getCurrentClient());
         }
 
@@ -178,26 +174,4 @@
             assertEquals(0, scheduler.getCurrentPendingCount());
         }
     }
-
-    private static class TestableFingerprintProvider extends FingerprintProvider {
-        private final IFingerprint mDaemon;
-
-        TestableFingerprintProvider(@NonNull IFingerprint daemon,
-                @NonNull Context context,
-                @NonNull BiometricStateCallback biometricStateCallback,
-                @NonNull SensorProps[] props,
-                @NonNull String halInstanceName,
-                @NonNull LockoutResetDispatcher lockoutResetDispatcher,
-                @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
-                @NonNull BiometricContext biometricContext) {
-            super(context, biometricStateCallback, props, halInstanceName, lockoutResetDispatcher,
-                    gestureAvailabilityDispatcher, biometricContext);
-            mDaemon = daemon;
-        }
-
-        @Override
-        synchronized IFingerprint getHalInstance() {
-            return mDaemon;
-        }
-    }
 }
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 6c6b608..7e6883b 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
@@ -44,6 +44,7 @@
 import com.android.server.LocalServices;
 import com.android.server.input.InputManagerInternal;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -93,6 +94,11 @@
                 threadVerifier);
     }
 
+    @After
+    public void tearDown() {
+        mInputManagerMockHelper.tearDown();
+    }
+
     @Test
     public void registerInputDevice_deviceCreation_hasDeviceId() {
         final IBinder device1Token = new Binder("device1");
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/InputManagerMockHelper.java b/services/tests/servicestests/src/com/android/server/companion/virtual/InputManagerMockHelper.java
index 5cadc0a..3722247 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/InputManagerMockHelper.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/InputManagerMockHelper.java
@@ -46,6 +46,7 @@
     private final TestableLooper mTestableLooper;
     private final InputController.NativeWrapper mNativeWrapperMock;
     private final IInputManager mIInputManagerMock;
+    private final InputManagerGlobal.TestSession mInputManagerGlobalSession;
     private final List<InputDevice> mDevices = new ArrayList<>();
     private IInputDevicesChangedListener mDevicesChangedListener;
 
@@ -77,7 +78,13 @@
 
         // Set a new instance of InputManager for testing that uses the IInputManager mock as the
         // interface to the server.
-        InputManagerGlobal.resetInstance(mIInputManagerMock);
+        mInputManagerGlobalSession = InputManagerGlobal.createTestSession(mIInputManagerMock);
+    }
+
+    public void tearDown() {
+        if (mInputManagerGlobalSession != null) {
+            mInputManagerGlobalSession.close();
+        }
     }
 
     private long handleNativeOpenInputDevice(InvocationOnMock inv) {
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/SensorControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/SensorControllerTest.java
index 6a45d5f..faece4f 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/SensorControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/SensorControllerTest.java
@@ -23,15 +23,19 @@
 import static org.mockito.ArgumentMatchers.anyFloat;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.verify;
 
+import android.companion.virtual.IVirtualDevice;
 import android.companion.virtual.sensor.IVirtualSensorCallback;
+import android.companion.virtual.sensor.VirtualSensor;
 import android.companion.virtual.sensor.VirtualSensorConfig;
 import android.companion.virtual.sensor.VirtualSensorEvent;
 import android.hardware.Sensor;
 import android.os.Binder;
 import android.os.IBinder;
+import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
@@ -39,12 +43,16 @@
 import com.android.server.LocalServices;
 import com.android.server.sensors.SensorManagerInternal;
 
+import com.google.common.collect.Iterables;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.List;
+
 @Presubmit
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
@@ -54,14 +62,17 @@
     private static final String VIRTUAL_SENSOR_NAME = "VirtualAccelerometer";
     private static final int SENSOR_HANDLE = 7;
 
+    private static final int VIRTUAL_SENSOR_TYPE = Sensor.TYPE_ACCELEROMETER;
+
     @Mock
     private SensorManagerInternal mSensorManagerInternalMock;
     @Mock
     private IVirtualSensorCallback mVirtualSensorCallback;
-    private SensorController mSensorController;
+    @Mock
+    private IVirtualDevice mVirtualDevice;
+
     private VirtualSensorEvent mSensorEvent;
     private VirtualSensorConfig mVirtualSensorConfig;
-    private IBinder mSensorToken;
 
     @Before
     public void setUp() throws Exception {
@@ -70,12 +81,10 @@
         LocalServices.removeServiceForTest(SensorManagerInternal.class);
         LocalServices.addService(SensorManagerInternal.class, mSensorManagerInternalMock);
 
-        mSensorController = new SensorController(VIRTUAL_DEVICE_ID, mVirtualSensorCallback);
         mSensorEvent = new VirtualSensorEvent.Builder(new float[] { 1f, 2f, 3f}).build();
         mVirtualSensorConfig =
-                new VirtualSensorConfig.Builder(Sensor.TYPE_ACCELEROMETER, VIRTUAL_SENSOR_NAME)
+                new VirtualSensorConfig.Builder(VIRTUAL_SENSOR_TYPE, VIRTUAL_SENSOR_NAME)
                         .build();
-        mSensorToken = new Binder("sensorToken");
     }
 
     @Test
@@ -86,62 +95,84 @@
 
         Throwable thrown = assertThrows(
                 RuntimeException.class,
-                () -> mSensorController.createSensor(mSensorToken, mVirtualSensorConfig));
+                () -> new SensorController(mVirtualDevice, VIRTUAL_DEVICE_ID,
+                        mVirtualSensorCallback, List.of(mVirtualSensorConfig)));
 
         assertThat(thrown.getCause().getMessage())
                 .contains("Received an invalid virtual sensor handle");
     }
 
     @Test
-    public void createSensor_success() {
-        doCreateSensorSuccessfully();
+    public void createSensor_success() throws Exception {
+        SensorController sensorController = doCreateSensorSuccessfully();
 
-        assertThat(mSensorController.getSensorDescriptors()).isNotEmpty();
+        assertThat(sensorController.getSensorDescriptors()).isNotEmpty();
     }
 
     @Test
-    public void sendSensorEvent_invalidToken_throwsException() {
-        doCreateSensorSuccessfully();
+    public void getSensorByHandle_success() throws Exception {
+        SensorController sensorController = doCreateSensorSuccessfully();
+
+        VirtualSensor sensor = sensorController.getSensorByHandle(SENSOR_HANDLE);
+
+        assertThat(sensor).isNotNull();
+        assertThat(sensor.getHandle()).isEqualTo(SENSOR_HANDLE);
+        assertThat(sensor.getDeviceId()).isEqualTo(VIRTUAL_DEVICE_ID);
+        assertThat(sensor.getType()).isEqualTo(VIRTUAL_SENSOR_TYPE);
+    }
+
+    @Test
+    public void getSensorByHandle_invalidHandle_returnsNull() throws Exception {
+        SensorController sensorController = doCreateSensorSuccessfully();
+        final int invalidSensorHandle = 123456;
+
+        assertThat(sensorController.getSensorByHandle(invalidSensorHandle)).isNull();
+    }
+
+    @Test
+    public void sendSensorEvent_invalidToken_throwsException() throws Exception {
+        SensorController sensorController = doCreateSensorSuccessfully();
 
         assertThrows(
                 IllegalArgumentException.class,
-                () -> mSensorController.sendSensorEvent(
+                () -> sensorController.sendSensorEvent(
                         new Binder("invalidSensorToken"), mSensorEvent));
     }
 
     @Test
-    public void sendSensorEvent_success() {
-        doCreateSensorSuccessfully();
+    public void sendSensorEvent_success() throws Exception {
+        SensorController sensorController = doCreateSensorSuccessfully();
 
-        mSensorController.sendSensorEvent(mSensorToken, mSensorEvent);
+        clearInvocations(mSensorManagerInternalMock);
+        IBinder token = Iterables.getOnlyElement(sensorController.getSensorDescriptors().keySet());
+
+        sensorController.sendSensorEvent(token, mSensorEvent);
         verify(mSensorManagerInternalMock).sendSensorEvent(
                 SENSOR_HANDLE, Sensor.TYPE_ACCELEROMETER, mSensorEvent.getTimestampNanos(),
                 mSensorEvent.getValues());
     }
 
     @Test
-    public void unregisterSensor_invalidToken_throwsException() {
-        doCreateSensorSuccessfully();
+    public void close_unregistersSensors() throws Exception {
+        SensorController sensorController = doCreateSensorSuccessfully();
 
-        assertThrows(
-                IllegalArgumentException.class,
-                () -> mSensorController.unregisterSensor(new Binder("invalidSensorToken")));
-    }
-
-    @Test
-    public void unregisterSensor_success() {
-        doCreateSensorSuccessfully();
-
-        mSensorController.unregisterSensor(mSensorToken);
+        sensorController.close();
         verify(mSensorManagerInternalMock).removeRuntimeSensor(SENSOR_HANDLE);
-        assertThat(mSensorController.getSensorDescriptors()).isEmpty();
+        assertThat(sensorController.getSensorDescriptors()).isEmpty();
     }
 
-    private void doCreateSensorSuccessfully() {
+    private SensorController doCreateSensorSuccessfully() throws RemoteException {
         doReturn(SENSOR_HANDLE).when(mSensorManagerInternalMock).createRuntimeSensor(
                 anyInt(), anyInt(), anyString(), anyString(), anyFloat(), anyFloat(), anyFloat(),
                 anyInt(), anyInt(), anyInt(), any());
-        assertThat(mSensorController.createSensor(mSensorToken, mVirtualSensorConfig))
-                .isEqualTo(SENSOR_HANDLE);
+        doReturn(VIRTUAL_DEVICE_ID).when(mVirtualDevice).getDeviceId();
+
+        SensorController sensorController = new SensorController(mVirtualDevice, VIRTUAL_DEVICE_ID,
+                mVirtualSensorCallback, List.of(mVirtualSensorConfig));
+
+        List<VirtualSensor> sensors = sensorController.getSensorList();
+        assertThat(sensors).hasSize(1);
+        assertThat(sensors.get(0).getHandle()).isEqualTo(SENSOR_HANDLE);
+        return sensorController;
     }
 }
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 8884dba..d76d615 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
@@ -16,6 +16,7 @@
 
 package com.android.server.companion.virtual;
 
+import static android.companion.virtual.VirtualDeviceManager.ASSOCIATION_ID_INVALID;
 import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_CUSTOM;
 import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
 import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_RECENTS;
@@ -359,7 +360,6 @@
         mInputController = new InputController(mNativeWrapperMock,
                 new Handler(TestableLooper.get(this).getLooper()),
                 mContext.getSystemService(WindowManager.class), threadVerifier);
-        mSensorController = new SensorController(VIRTUAL_DEVICE_ID_1, mVirtualSensorCallback);
         mCameraAccessController =
                 new CameraAccessController(mContext, mLocalService, mCameraAccessBlockedCallback);
 
@@ -370,11 +370,13 @@
         mLocalService = mVdms.getLocalServiceInstance();
         mVdm = mVdms.new VirtualDeviceManagerImpl();
         mDeviceImpl = createVirtualDevice(VIRTUAL_DEVICE_ID_1, DEVICE_OWNER_UID_1);
+        mSensorController = mDeviceImpl.getSensorControllerForTest();
     }
 
     @After
     public void tearDown() {
         mDeviceImpl.close();
+        mInputManagerMockHelper.tearDown();
     }
 
     @Test
@@ -1711,6 +1713,22 @@
         assertThat(displayIds).containsExactly(DISPLAY_ID_1, DISPLAY_ID_2);
     }
 
+    @Test
+    public void getAssociationIdForDevice_invalidDeviceId_returnsInvalidAssociationId() {
+        assertThat(mLocalService.getAssociationIdForDevice(DEVICE_ID_INVALID))
+                .isEqualTo(ASSOCIATION_ID_INVALID);
+        assertThat(mLocalService.getAssociationIdForDevice(DEVICE_ID_DEFAULT))
+                .isEqualTo(ASSOCIATION_ID_INVALID);
+        assertThat(mLocalService.getAssociationIdForDevice(VIRTUAL_DEVICE_ID_2))
+                .isEqualTo(ASSOCIATION_ID_INVALID);
+    }
+
+    @Test
+    public void getAssociationIdForDevice_returnsCorrectAssociationId() {
+        assertThat(mLocalService.getAssociationIdForDevice(VIRTUAL_DEVICE_ID_1))
+                .isEqualTo(mAssociationInfo.getId());
+    }
+
     private VirtualDeviceImpl createVirtualDevice(int virtualDeviceId, int ownerUid) {
         VirtualDeviceParams params = new VirtualDeviceParams.Builder()
                 .setBlockedActivities(getBlockedActivities())
@@ -1722,11 +1740,12 @@
             VirtualDeviceParams params) {
         VirtualDeviceImpl virtualDeviceImpl = new VirtualDeviceImpl(mContext,
                 mAssociationInfo, mVdms, new Binder(), ownerUid, virtualDeviceId,
-                mInputController, mSensorController, mCameraAccessController
+                mInputController, mCameraAccessController
                 /* onDeviceCloseListener= */ /*deviceId -> mVdms.removeVirtualDevice(deviceId)*/,
                 mPendingTrampolineCallback, mActivityListener, mSoundEffectListener,
                 mRunningAppsChangedCallback, params, new DisplayManagerGlobal(mIDisplayManager));
         mVdms.addVirtualDevice(virtualDeviceImpl);
+        assertThat(virtualDeviceImpl.getAssociationId()).isEqualTo(mAssociationInfo.getId());
         return virtualDeviceImpl;
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceTest.java
index a2e204d..28df24c 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceTest.java
@@ -37,34 +37,36 @@
 public class VirtualDeviceTest {
 
     private static final int VIRTUAL_DEVICE_ID = 42;
-    private static final String VIRTUAL_DEVICE_NAME = "VirtualDeviceName";
+    private static final String PERSISTENT_ID = "persistentId";
+    private static final String DEVICE_NAME = "VirtualDeviceName";
 
     @Test
     public void build_invalidId_shouldThrowIllegalArgumentException() {
         assertThrows(
                 IllegalArgumentException.class,
-                () -> new VirtualDevice(DEVICE_ID_INVALID, VIRTUAL_DEVICE_NAME));
+                () -> new VirtualDevice(DEVICE_ID_INVALID, PERSISTENT_ID, DEVICE_NAME));
     }
 
     @Test
     public void build_defaultId_shouldThrowIllegalArgumentException() {
         assertThrows(
                 IllegalArgumentException.class,
-                () -> new VirtualDevice(DEVICE_ID_DEFAULT, VIRTUAL_DEVICE_NAME));
+                () -> new VirtualDevice(DEVICE_ID_DEFAULT, PERSISTENT_ID, DEVICE_NAME));
     }
 
     @Test
-    public void build_nameIsOptional() {
+    public void build_onlyRequiredFields() {
         VirtualDevice virtualDevice =
-                new VirtualDevice(VIRTUAL_DEVICE_ID, /* name= */ null);
+                new VirtualDevice(VIRTUAL_DEVICE_ID, /*persistentId=*/null, /*name=*/null);
         assertThat(virtualDevice.getDeviceId()).isEqualTo(VIRTUAL_DEVICE_ID);
+        assertThat(virtualDevice.getPersistentDeviceId()).isNull();
         assertThat(virtualDevice.getName()).isNull();
     }
 
     @Test
     public void parcelable_shouldRecreateSuccessfully() {
         VirtualDevice originalDevice =
-                new VirtualDevice(VIRTUAL_DEVICE_ID, VIRTUAL_DEVICE_NAME);
+                new VirtualDevice(VIRTUAL_DEVICE_ID, PERSISTENT_ID, DEVICE_NAME);
         Parcel parcel = Parcel.obtain();
         originalDevice.writeToParcel(parcel, 0);
         parcel.setDataPosition(0);
@@ -72,6 +74,7 @@
         VirtualDevice device = VirtualDevice.CREATOR.createFromParcel(parcel);
         assertThat(device).isEqualTo(originalDevice);
         assertThat(device.getDeviceId()).isEqualTo(VIRTUAL_DEVICE_ID);
-        assertThat(device.getName()).isEqualTo(VIRTUAL_DEVICE_NAME);
+        assertThat(device.getPersistentDeviceId()).isEqualTo(PERSISTENT_ID);
+        assertThat(device.getName()).isEqualTo(DEVICE_NAME);
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java
new file mode 100644
index 0000000..e457119
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java
@@ -0,0 +1,427 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.contentcapture;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+import android.annotation.NonNull;
+import android.content.ComponentName;
+import android.content.ContentCaptureOptions;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.ParceledListSlice;
+import android.content.pm.UserInfo;
+import android.service.contentcapture.ContentCaptureServiceInfo;
+import android.view.contentcapture.ContentCaptureEvent;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.server.LocalServices;
+import com.android.server.contentprotection.ContentProtectionBlocklistManager;
+import com.android.server.contentprotection.RemoteContentProtectionService;
+import com.android.server.pm.UserManagerInternal;
+
+import com.google.common.collect.ImmutableList;
+
+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;
+
+/**
+ * Test for {@link ContentCaptureManagerService}.
+ *
+ * <p>Run with: {@code atest
+ * FrameworksServicesTests:com.android.server.contentcapture.ContentCaptureManagerServiceTest}
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+@SuppressWarnings("GuardedBy") // Service not really running, no need to expose locks
+public class ContentCaptureManagerServiceTest {
+
+    private static final int USER_ID = 1234;
+
+    private static final String PACKAGE_NAME = "com.test.package";
+
+    private static final ComponentName COMPONENT_NAME =
+            new ComponentName(PACKAGE_NAME, "TestClass");
+
+    private static final ContentCaptureEvent EVENT =
+            new ContentCaptureEvent(/* sessionId= */ 100, /* type= */ 200);
+
+    private static final ParceledListSlice<ContentCaptureEvent> PARCELED_EVENTS =
+            new ParceledListSlice<>(ImmutableList.of(EVENT));
+
+    private static final Context sContext = ApplicationProvider.getApplicationContext();
+
+    @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+
+    @Mock private UserManagerInternal mMockUserManagerInternal;
+
+    @Mock private ContentProtectionBlocklistManager mMockContentProtectionBlocklistManager;
+
+    @Mock private ContentCaptureServiceInfo mMockContentCaptureServiceInfo;
+
+    @Mock private RemoteContentProtectionService mMockRemoteContentProtectionService;
+
+    private boolean mDevCfgEnableContentProtectionReceiver;
+
+    private int mContentProtectionBlocklistManagersCreated;
+
+    private int mContentProtectionServiceInfosCreated;
+
+    private int mRemoteContentProtectionServicesCreated;
+
+    private String mConfigDefaultContentProtectionService = COMPONENT_NAME.flattenToString();
+
+    private boolean mContentProtectionServiceInfoConstructorShouldThrow;
+
+    private ContentCaptureManagerService mContentCaptureManagerService;
+
+    @Before
+    public void setup() {
+        when(mMockUserManagerInternal.getUserInfos()).thenReturn(new UserInfo[0]);
+        LocalServices.removeServiceForTest(UserManagerInternal.class);
+        LocalServices.addService(UserManagerInternal.class, mMockUserManagerInternal);
+        mContentCaptureManagerService = new TestContentCaptureManagerService();
+    }
+
+    @Test
+    public void constructor_contentProtection_flagDisabled_noBlocklistManager() {
+        assertThat(mContentProtectionBlocklistManagersCreated).isEqualTo(0);
+        assertThat(mContentProtectionServiceInfosCreated).isEqualTo(0);
+        verifyZeroInteractions(mMockContentProtectionBlocklistManager);
+    }
+
+    @Test
+    public void constructor_contentProtection_componentNameNull_noBlocklistManager() {
+        mConfigDefaultContentProtectionService = null;
+
+        mContentCaptureManagerService = new TestContentCaptureManagerService();
+
+        assertThat(mContentProtectionBlocklistManagersCreated).isEqualTo(0);
+        assertThat(mContentProtectionServiceInfosCreated).isEqualTo(0);
+        verifyZeroInteractions(mMockContentProtectionBlocklistManager);
+    }
+
+    @Test
+    public void constructor_contentProtection_componentNameBlank_noBlocklistManager() {
+        mConfigDefaultContentProtectionService = "   ";
+
+        mContentCaptureManagerService = new TestContentCaptureManagerService();
+
+        assertThat(mContentProtectionBlocklistManagersCreated).isEqualTo(0);
+        assertThat(mContentProtectionServiceInfosCreated).isEqualTo(0);
+        verifyZeroInteractions(mMockContentProtectionBlocklistManager);
+    }
+
+    @Test
+    public void constructor_contentProtection_enabled_createsBlocklistManager() {
+        mDevCfgEnableContentProtectionReceiver = true;
+
+        mContentCaptureManagerService = new TestContentCaptureManagerService();
+
+        assertThat(mContentProtectionBlocklistManagersCreated).isEqualTo(1);
+        assertThat(mContentProtectionServiceInfosCreated).isEqualTo(0);
+        verify(mMockContentProtectionBlocklistManager).updateBlocklist(anyInt());
+    }
+
+    @Test
+    public void setFineTuneParamsFromDeviceConfig_doesNotUpdateContentProtectionBlocklist() {
+        mDevCfgEnableContentProtectionReceiver = true;
+        mContentCaptureManagerService = new TestContentCaptureManagerService();
+        mContentCaptureManagerService.mDevCfgContentProtectionAppsBlocklistSize += 100;
+        verify(mMockContentProtectionBlocklistManager).updateBlocklist(anyInt());
+
+        mContentCaptureManagerService.setFineTuneParamsFromDeviceConfig();
+
+        verifyNoMoreInteractions(mMockContentProtectionBlocklistManager);
+    }
+
+    @Test
+    public void getOptions_contentCaptureDisabled_contentProtectionDisabled() {
+        mDevCfgEnableContentProtectionReceiver = true;
+        mContentCaptureManagerService = new TestContentCaptureManagerService();
+
+        ContentCaptureOptions actual =
+                mContentCaptureManagerService.mGlobalContentCaptureOptions.getOptions(
+                        USER_ID, PACKAGE_NAME);
+
+        assertThat(actual).isNull();
+        verify(mMockContentProtectionBlocklistManager).isAllowed(PACKAGE_NAME);
+    }
+
+    @Test
+    public void getOptions_contentCaptureDisabled_contentProtectionEnabled() {
+        when(mMockContentProtectionBlocklistManager.isAllowed(PACKAGE_NAME)).thenReturn(true);
+        mDevCfgEnableContentProtectionReceiver = true;
+        mContentCaptureManagerService = new TestContentCaptureManagerService();
+
+        ContentCaptureOptions actual =
+                mContentCaptureManagerService.mGlobalContentCaptureOptions.getOptions(
+                        USER_ID, PACKAGE_NAME);
+
+        assertThat(actual).isNotNull();
+        assertThat(actual.enableReceiver).isFalse();
+        assertThat(actual.contentProtectionOptions).isNotNull();
+        assertThat(actual.contentProtectionOptions.enableReceiver).isTrue();
+        assertThat(actual.whitelistedComponents).isNull();
+    }
+
+    @Test
+    public void getOptions_contentCaptureEnabled_contentProtectionDisabled() {
+        mDevCfgEnableContentProtectionReceiver = true;
+        mContentCaptureManagerService = new TestContentCaptureManagerService();
+        mContentCaptureManagerService.mGlobalContentCaptureOptions.setWhitelist(
+                USER_ID, ImmutableList.of(PACKAGE_NAME), /* components= */ null);
+
+        ContentCaptureOptions actual =
+                mContentCaptureManagerService.mGlobalContentCaptureOptions.getOptions(
+                        USER_ID, PACKAGE_NAME);
+
+        assertThat(actual).isNotNull();
+        assertThat(actual.enableReceiver).isTrue();
+        assertThat(actual.contentProtectionOptions).isNotNull();
+        assertThat(actual.contentProtectionOptions.enableReceiver).isFalse();
+        assertThat(actual.whitelistedComponents).isNull();
+        verify(mMockContentProtectionBlocklistManager).isAllowed(PACKAGE_NAME);
+    }
+
+    @Test
+    public void getOptions_contentCaptureEnabled_contentProtectionEnabled() {
+        when(mMockContentProtectionBlocklistManager.isAllowed(PACKAGE_NAME)).thenReturn(true);
+        mDevCfgEnableContentProtectionReceiver = true;
+        mContentCaptureManagerService = new TestContentCaptureManagerService();
+        mContentCaptureManagerService.mGlobalContentCaptureOptions.setWhitelist(
+                USER_ID, ImmutableList.of(PACKAGE_NAME), /* components= */ null);
+
+        ContentCaptureOptions actual =
+                mContentCaptureManagerService.mGlobalContentCaptureOptions.getOptions(
+                        USER_ID, PACKAGE_NAME);
+
+        assertThat(actual).isNotNull();
+        assertThat(actual.enableReceiver).isTrue();
+        assertThat(actual.contentProtectionOptions).isNotNull();
+        assertThat(actual.contentProtectionOptions.enableReceiver).isTrue();
+        assertThat(actual.whitelistedComponents).isNull();
+    }
+
+    @Test
+    public void isWhitelisted_packageName_contentCaptureDisabled_contentProtectionDisabled() {
+        mDevCfgEnableContentProtectionReceiver = true;
+        mContentCaptureManagerService = new TestContentCaptureManagerService();
+
+        boolean actual =
+                mContentCaptureManagerService.mGlobalContentCaptureOptions.isWhitelisted(
+                        USER_ID, PACKAGE_NAME);
+
+        assertThat(actual).isFalse();
+        verify(mMockContentProtectionBlocklistManager).isAllowed(PACKAGE_NAME);
+    }
+
+    @Test
+    public void isWhitelisted_packageName_contentCaptureDisabled_contentProtectionEnabled() {
+        when(mMockContentProtectionBlocklistManager.isAllowed(PACKAGE_NAME)).thenReturn(true);
+        mDevCfgEnableContentProtectionReceiver = true;
+        mContentCaptureManagerService = new TestContentCaptureManagerService();
+
+        boolean actual =
+                mContentCaptureManagerService.mGlobalContentCaptureOptions.isWhitelisted(
+                        USER_ID, PACKAGE_NAME);
+
+        assertThat(actual).isTrue();
+    }
+
+    @Test
+    public void isWhitelisted_packageName_contentCaptureEnabled_contentProtectionNotChecked() {
+        mDevCfgEnableContentProtectionReceiver = true;
+        mContentCaptureManagerService = new TestContentCaptureManagerService();
+        mContentCaptureManagerService.mGlobalContentCaptureOptions.setWhitelist(
+                USER_ID, ImmutableList.of(PACKAGE_NAME), /* components= */ null);
+
+        boolean actual =
+                mContentCaptureManagerService.mGlobalContentCaptureOptions.isWhitelisted(
+                        USER_ID, PACKAGE_NAME);
+
+        assertThat(actual).isTrue();
+        verify(mMockContentProtectionBlocklistManager, never()).isAllowed(anyString());
+    }
+
+    @Test
+    public void isWhitelisted_componentName_contentCaptureDisabled_contentProtectionDisabled() {
+        mDevCfgEnableContentProtectionReceiver = true;
+        mContentCaptureManagerService = new TestContentCaptureManagerService();
+
+        boolean actual =
+                mContentCaptureManagerService.mGlobalContentCaptureOptions.isWhitelisted(
+                        USER_ID, COMPONENT_NAME);
+
+        assertThat(actual).isFalse();
+        verify(mMockContentProtectionBlocklistManager).isAllowed(PACKAGE_NAME);
+    }
+
+    @Test
+    public void isWhitelisted_componentName_contentCaptureDisabled_contentProtectionEnabled() {
+        when(mMockContentProtectionBlocklistManager.isAllowed(PACKAGE_NAME)).thenReturn(true);
+        mDevCfgEnableContentProtectionReceiver = true;
+        mContentCaptureManagerService = new TestContentCaptureManagerService();
+
+        boolean actual =
+                mContentCaptureManagerService.mGlobalContentCaptureOptions.isWhitelisted(
+                        USER_ID, COMPONENT_NAME);
+
+        assertThat(actual).isTrue();
+    }
+
+    @Test
+    public void isWhitelisted_componentName_contentCaptureEnabled_contentProtectionNotChecked() {
+        mDevCfgEnableContentProtectionReceiver = true;
+        mContentCaptureManagerService = new TestContentCaptureManagerService();
+        mContentCaptureManagerService.mGlobalContentCaptureOptions.setWhitelist(
+                USER_ID, /* packageNames= */ null, ImmutableList.of(COMPONENT_NAME));
+
+        boolean actual =
+                mContentCaptureManagerService.mGlobalContentCaptureOptions.isWhitelisted(
+                        USER_ID, COMPONENT_NAME);
+
+        assertThat(actual).isTrue();
+        verify(mMockContentProtectionBlocklistManager, never()).isAllowed(anyString());
+    }
+
+    @Test
+    public void isContentProtectionReceiverEnabled_withoutBlocklistManager() {
+        boolean actual =
+                mContentCaptureManagerService.mGlobalContentCaptureOptions.isWhitelisted(
+                        USER_ID, PACKAGE_NAME);
+
+        assertThat(actual).isFalse();
+        verify(mMockContentProtectionBlocklistManager, never()).isAllowed(anyString());
+    }
+
+    @Test
+    public void isContentProtectionReceiverEnabled_disabledWithFlag() {
+        mDevCfgEnableContentProtectionReceiver = true;
+        mContentCaptureManagerService = new TestContentCaptureManagerService();
+        mContentCaptureManagerService.mDevCfgEnableContentProtectionReceiver = false;
+
+        boolean actual =
+                mContentCaptureManagerService.mGlobalContentCaptureOptions.isWhitelisted(
+                        USER_ID, PACKAGE_NAME);
+
+        assertThat(actual).isFalse();
+        verify(mMockContentProtectionBlocklistManager, never()).isAllowed(anyString());
+    }
+
+    @Test
+    public void onLoginDetected_disabledAfterConstructor() {
+        mDevCfgEnableContentProtectionReceiver = true;
+        mContentCaptureManagerService = new TestContentCaptureManagerService();
+        mContentCaptureManagerService.mDevCfgEnableContentProtectionReceiver = false;
+
+        mContentCaptureManagerService
+                .getContentCaptureManagerServiceStub()
+                .onLoginDetected(PARCELED_EVENTS);
+
+        assertThat(mContentProtectionServiceInfosCreated).isEqualTo(0);
+        assertThat(mRemoteContentProtectionServicesCreated).isEqualTo(0);
+        verifyZeroInteractions(mMockRemoteContentProtectionService);
+    }
+
+    @Test
+    public void onLoginDetected_invalidPermissions() {
+        mDevCfgEnableContentProtectionReceiver = true;
+        mContentProtectionServiceInfoConstructorShouldThrow = true;
+        mContentCaptureManagerService = new TestContentCaptureManagerService();
+
+        mContentCaptureManagerService
+                .getContentCaptureManagerServiceStub()
+                .onLoginDetected(PARCELED_EVENTS);
+
+        assertThat(mContentProtectionServiceInfosCreated).isEqualTo(1);
+        assertThat(mRemoteContentProtectionServicesCreated).isEqualTo(0);
+        verifyZeroInteractions(mMockRemoteContentProtectionService);
+    }
+
+    @Test
+    public void onLoginDetected_enabled() {
+        mDevCfgEnableContentProtectionReceiver = true;
+        mContentCaptureManagerService = new TestContentCaptureManagerService();
+
+        mContentCaptureManagerService
+                .getContentCaptureManagerServiceStub()
+                .onLoginDetected(PARCELED_EVENTS);
+
+        assertThat(mContentProtectionServiceInfosCreated).isEqualTo(1);
+        assertThat(mRemoteContentProtectionServicesCreated).isEqualTo(1);
+        verify(mMockRemoteContentProtectionService).onLoginDetected(PARCELED_EVENTS);
+    }
+
+    private class TestContentCaptureManagerService extends ContentCaptureManagerService {
+
+        TestContentCaptureManagerService() {
+            super(sContext);
+            this.mDevCfgEnableContentProtectionReceiver =
+                    ContentCaptureManagerServiceTest.this.mDevCfgEnableContentProtectionReceiver;
+        }
+
+        @Override
+        protected boolean getEnableContentProtectionReceiverLocked() {
+            return ContentCaptureManagerServiceTest.this.mDevCfgEnableContentProtectionReceiver;
+        }
+
+        @Override
+        protected ContentProtectionBlocklistManager createContentProtectionBlocklistManager() {
+            mContentProtectionBlocklistManagersCreated++;
+            return mMockContentProtectionBlocklistManager;
+        }
+
+        @Override
+        protected String getContentProtectionServiceFlatComponentName() {
+            return mConfigDefaultContentProtectionService;
+        }
+
+        @Override
+        protected ContentCaptureServiceInfo createContentProtectionServiceInfo(
+                @NonNull ComponentName componentName) throws PackageManager.NameNotFoundException {
+            mContentProtectionServiceInfosCreated++;
+            if (mContentProtectionServiceInfoConstructorShouldThrow) {
+                throw new RuntimeException("TEST RUNTIME EXCEPTION");
+            }
+            return mMockContentCaptureServiceInfo;
+        }
+
+        @Override
+        protected RemoteContentProtectionService createRemoteContentProtectionService(
+                @NonNull ComponentName componentName) {
+            mRemoteContentProtectionServicesCreated++;
+            return mMockRemoteContentProtectionService;
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionBlocklistManagerTest.java b/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionBlocklistManagerTest.java
new file mode 100644
index 0000000..ba9956a
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionBlocklistManagerTest.java
@@ -0,0 +1,236 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.contentprotection;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+import android.annotation.NonNull;
+import android.content.pm.PackageInfo;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.google.common.collect.ImmutableList;
+
+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 java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Test for {@link ContentProtectionBlocklistManager}.
+ *
+ * <p>Run with: {@code atest
+ * FrameworksServicesTests:
+ * com.android.server.contentprotection.ContentProtectionBlocklistManagerTest}
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class ContentProtectionBlocklistManagerTest {
+
+    private static final String FIRST_PACKAGE_NAME = "com.test.first.package.name";
+
+    private static final String SECOND_PACKAGE_NAME = "com.test.second.package.name";
+
+    private static final String UNLISTED_PACKAGE_NAME = "com.test.unlisted.package.name";
+
+    private static final String PACKAGE_NAME_BLOCKLIST_FILENAME =
+            "/product/etc/res/raw/content_protection/package_name_blocklist.txt";
+
+    private static final PackageInfo PACKAGE_INFO = new PackageInfo();
+
+    private static final List<String> LINES =
+            ImmutableList.of(FIRST_PACKAGE_NAME, SECOND_PACKAGE_NAME);
+
+    @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+
+    @Mock private ContentProtectionPackageManager mMockContentProtectionPackageManager;
+
+    private final List<String> mReadRawFiles = new ArrayList<>();
+
+    private ContentProtectionBlocklistManager mContentProtectionBlocklistManager;
+
+    @Before
+    public void setup() {
+        mContentProtectionBlocklistManager = new TestContentProtectionBlocklistManager();
+    }
+
+    @Test
+    public void isAllowed_blocklistNotLoaded() {
+        boolean actual = mContentProtectionBlocklistManager.isAllowed(FIRST_PACKAGE_NAME);
+
+        assertThat(actual).isFalse();
+        assertThat(mReadRawFiles).isEmpty();
+        verifyZeroInteractions(mMockContentProtectionPackageManager);
+    }
+
+    @Test
+    public void isAllowed_inBlocklist() {
+        mContentProtectionBlocklistManager.updateBlocklist(LINES.size());
+
+        boolean actual = mContentProtectionBlocklistManager.isAllowed(FIRST_PACKAGE_NAME);
+
+        assertThat(actual).isFalse();
+        verifyZeroInteractions(mMockContentProtectionPackageManager);
+    }
+
+    @Test
+    public void isAllowed_packageInfoNotFound() {
+        mContentProtectionBlocklistManager.updateBlocklist(LINES.size());
+        when(mMockContentProtectionPackageManager.getPackageInfo(UNLISTED_PACKAGE_NAME))
+                .thenReturn(null);
+
+        boolean actual = mContentProtectionBlocklistManager.isAllowed(UNLISTED_PACKAGE_NAME);
+
+        assertThat(actual).isFalse();
+        verify(mMockContentProtectionPackageManager, never())
+                .hasRequestedInternetPermissions(any());
+        verify(mMockContentProtectionPackageManager, never()).isSystemApp(any());
+        verify(mMockContentProtectionPackageManager, never()).isUpdatedSystemApp(any());
+    }
+
+    @Test
+    public void isAllowed_notRequestedInternet() {
+        mContentProtectionBlocklistManager.updateBlocklist(LINES.size());
+        when(mMockContentProtectionPackageManager.getPackageInfo(UNLISTED_PACKAGE_NAME))
+                .thenReturn(PACKAGE_INFO);
+        when(mMockContentProtectionPackageManager.hasRequestedInternetPermissions(PACKAGE_INFO))
+                .thenReturn(false);
+
+        boolean actual = mContentProtectionBlocklistManager.isAllowed(UNLISTED_PACKAGE_NAME);
+
+        assertThat(actual).isFalse();
+        verify(mMockContentProtectionPackageManager, never()).isSystemApp(any());
+        verify(mMockContentProtectionPackageManager, never()).isUpdatedSystemApp(any());
+    }
+
+    @Test
+    public void isAllowed_systemApp() {
+        mContentProtectionBlocklistManager.updateBlocklist(LINES.size());
+        when(mMockContentProtectionPackageManager.getPackageInfo(UNLISTED_PACKAGE_NAME))
+                .thenReturn(PACKAGE_INFO);
+        when(mMockContentProtectionPackageManager.hasRequestedInternetPermissions(PACKAGE_INFO))
+                .thenReturn(true);
+        when(mMockContentProtectionPackageManager.isSystemApp(PACKAGE_INFO)).thenReturn(true);
+
+        boolean actual = mContentProtectionBlocklistManager.isAllowed(UNLISTED_PACKAGE_NAME);
+
+        assertThat(actual).isFalse();
+        verify(mMockContentProtectionPackageManager, never()).isUpdatedSystemApp(any());
+    }
+
+    @Test
+    public void isAllowed_updatedSystemApp() {
+        mContentProtectionBlocklistManager.updateBlocklist(LINES.size());
+        when(mMockContentProtectionPackageManager.getPackageInfo(UNLISTED_PACKAGE_NAME))
+                .thenReturn(PACKAGE_INFO);
+        when(mMockContentProtectionPackageManager.hasRequestedInternetPermissions(PACKAGE_INFO))
+                .thenReturn(true);
+        when(mMockContentProtectionPackageManager.isSystemApp(PACKAGE_INFO)).thenReturn(true);
+        when(mMockContentProtectionPackageManager.isUpdatedSystemApp(PACKAGE_INFO))
+                .thenReturn(true);
+
+        boolean actual = mContentProtectionBlocklistManager.isAllowed(UNLISTED_PACKAGE_NAME);
+
+        assertThat(actual).isFalse();
+    }
+
+    @Test
+    public void isAllowed_allowed() {
+        mContentProtectionBlocklistManager.updateBlocklist(LINES.size());
+        when(mMockContentProtectionPackageManager.getPackageInfo(UNLISTED_PACKAGE_NAME))
+                .thenReturn(PACKAGE_INFO);
+        when(mMockContentProtectionPackageManager.hasRequestedInternetPermissions(PACKAGE_INFO))
+                .thenReturn(true);
+        when(mMockContentProtectionPackageManager.isSystemApp(PACKAGE_INFO)).thenReturn(false);
+        when(mMockContentProtectionPackageManager.isUpdatedSystemApp(PACKAGE_INFO))
+                .thenReturn(false);
+
+        boolean actual = mContentProtectionBlocklistManager.isAllowed(UNLISTED_PACKAGE_NAME);
+
+        assertThat(actual).isTrue();
+    }
+
+    @Test
+    public void updateBlocklist_negativeSize() {
+        mContentProtectionBlocklistManager.updateBlocklist(/* blocklistSize= */ -1);
+        assertThat(mReadRawFiles).isEmpty();
+
+        mContentProtectionBlocklistManager.isAllowed(FIRST_PACKAGE_NAME);
+        verify(mMockContentProtectionPackageManager).getPackageInfo(FIRST_PACKAGE_NAME);
+    }
+
+    @Test
+    public void updateBlocklist_zeroSize() {
+        mContentProtectionBlocklistManager.updateBlocklist(/* blocklistSize= */ 0);
+        assertThat(mReadRawFiles).isEmpty();
+
+        mContentProtectionBlocklistManager.isAllowed(FIRST_PACKAGE_NAME);
+        verify(mMockContentProtectionPackageManager).getPackageInfo(FIRST_PACKAGE_NAME);
+    }
+
+    @Test
+    public void updateBlocklist_positiveSize_belowTotal() {
+        mContentProtectionBlocklistManager.updateBlocklist(/* blocklistSize= */ 1);
+        assertThat(mReadRawFiles).containsExactly(PACKAGE_NAME_BLOCKLIST_FILENAME);
+
+        mContentProtectionBlocklistManager.isAllowed(FIRST_PACKAGE_NAME);
+        mContentProtectionBlocklistManager.isAllowed(SECOND_PACKAGE_NAME);
+
+        verify(mMockContentProtectionPackageManager, never()).getPackageInfo(FIRST_PACKAGE_NAME);
+        verify(mMockContentProtectionPackageManager).getPackageInfo(SECOND_PACKAGE_NAME);
+    }
+
+    @Test
+    public void updateBlocklist_positiveSize_aboveTotal() {
+        mContentProtectionBlocklistManager.updateBlocklist(LINES.size() + 1);
+        assertThat(mReadRawFiles).containsExactly(PACKAGE_NAME_BLOCKLIST_FILENAME);
+
+        mContentProtectionBlocklistManager.isAllowed(FIRST_PACKAGE_NAME);
+        mContentProtectionBlocklistManager.isAllowed(SECOND_PACKAGE_NAME);
+
+        verify(mMockContentProtectionPackageManager, never()).getPackageInfo(FIRST_PACKAGE_NAME);
+        verify(mMockContentProtectionPackageManager, never()).getPackageInfo(SECOND_PACKAGE_NAME);
+    }
+
+    private final class TestContentProtectionBlocklistManager
+            extends ContentProtectionBlocklistManager {
+
+        TestContentProtectionBlocklistManager() {
+            super(mMockContentProtectionPackageManager);
+        }
+
+        @Override
+        protected List<String> readLinesFromRawFile(@NonNull String filename) {
+            mReadRawFiles.add(filename);
+            return LINES;
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionPackageManagerTest.java b/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionPackageManagerTest.java
new file mode 100644
index 0000000..7d45ea4
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionPackageManagerTest.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.contentprotection;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.when;
+
+import android.Manifest.permission;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.PackageManager.PackageInfoFlags;
+import android.testing.TestableContext;
+
+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;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+/**
+ * Test for {@link ContentProtectionPackageManager}.
+ *
+ * <p>Run with: {@code atest
+ * FrameworksServicesTests:com.android.server.contentprotection.ContentProtectionPackageManagerTest}
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class ContentProtectionPackageManagerTest {
+    private static final String PACKAGE_NAME = "PACKAGE_NAME";
+
+    private static final PackageInfo EMPTY_PACKAGE_INFO = new PackageInfo();
+
+    private static final PackageInfo SYSTEM_APP_PACKAGE_INFO = createSystemAppPackageInfo();
+
+    private static final PackageInfo UPDATED_SYSTEM_APP_PACKAGE_INFO =
+            createUpdatedSystemAppPackageInfo();
+
+    @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+
+    @Rule
+    public final TestableContext mContext =
+            new TestableContext(ApplicationProvider.getApplicationContext());
+
+    @Mock private PackageManager mMockPackageManager;
+
+    private ContentProtectionPackageManager mContentProtectionPackageManager;
+
+    @Before
+    public void setup() {
+        mContext.setMockPackageManager(mMockPackageManager);
+        mContentProtectionPackageManager = new ContentProtectionPackageManager(mContext);
+    }
+
+    @Test
+    public void getPackageInfo_found() throws Exception {
+        PackageInfo expected = createPackageInfo(/* flags= */ 0);
+        when(mMockPackageManager.getPackageInfo(eq(PACKAGE_NAME), any(PackageInfoFlags.class)))
+                .thenReturn(expected);
+
+        PackageInfo actual = mContentProtectionPackageManager.getPackageInfo(PACKAGE_NAME);
+
+        assertThat(actual).isEqualTo(expected);
+    }
+
+    @Test
+    public void getPackageInfo_notFound() throws Exception {
+        when(mMockPackageManager.getPackageInfo(eq(PACKAGE_NAME), any(PackageInfoFlags.class)))
+                .thenThrow(new NameNotFoundException());
+
+        PackageInfo actual = mContentProtectionPackageManager.getPackageInfo(PACKAGE_NAME);
+
+        assertThat(actual).isNull();
+    }
+
+    @Test
+    public void getPackageInfo_null() {
+        PackageInfo actual = mContentProtectionPackageManager.getPackageInfo(PACKAGE_NAME);
+
+        assertThat(actual).isNull();
+    }
+
+    @Test
+    public void isSystemApp_true() {
+        boolean actual = mContentProtectionPackageManager.isSystemApp(SYSTEM_APP_PACKAGE_INFO);
+
+        assertThat(actual).isTrue();
+    }
+
+    @Test
+    public void isSystemApp_false() {
+        boolean actual =
+                mContentProtectionPackageManager.isSystemApp(UPDATED_SYSTEM_APP_PACKAGE_INFO);
+
+        assertThat(actual).isFalse();
+    }
+
+    @Test
+    public void isSystemApp_noApplicationInfo() {
+        boolean actual = mContentProtectionPackageManager.isSystemApp(EMPTY_PACKAGE_INFO);
+
+        assertThat(actual).isFalse();
+    }
+
+    @Test
+    public void isUpdatedSystemApp_true() {
+        boolean actual =
+                mContentProtectionPackageManager.isUpdatedSystemApp(
+                        UPDATED_SYSTEM_APP_PACKAGE_INFO);
+
+        assertThat(actual).isTrue();
+    }
+
+    @Test
+    public void isUpdatedSystemApp_false() {
+        boolean actual =
+                mContentProtectionPackageManager.isUpdatedSystemApp(SYSTEM_APP_PACKAGE_INFO);
+
+        assertThat(actual).isFalse();
+    }
+
+    @Test
+    public void isUpdatedSystemApp_noApplicationInfo() {
+        boolean actual = mContentProtectionPackageManager.isUpdatedSystemApp(EMPTY_PACKAGE_INFO);
+
+        assertThat(actual).isFalse();
+    }
+
+    @Test
+    public void hasRequestedInternetPermissions_true() {
+        PackageInfo packageInfo = createPackageInfo(new String[] {permission.INTERNET});
+
+        boolean actual =
+                mContentProtectionPackageManager.hasRequestedInternetPermissions(packageInfo);
+
+        assertThat(actual).isTrue();
+    }
+
+    @Test
+    public void hasRequestedInternetPermissions_false() {
+        PackageInfo packageInfo = createPackageInfo(new String[] {permission.ACCESS_FINE_LOCATION});
+
+        boolean actual =
+                mContentProtectionPackageManager.hasRequestedInternetPermissions(packageInfo);
+
+        assertThat(actual).isFalse();
+    }
+
+    @Test
+    public void hasRequestedInternetPermissions_noRequestedPermissions() {
+        boolean actual =
+                mContentProtectionPackageManager.hasRequestedInternetPermissions(
+                        EMPTY_PACKAGE_INFO);
+
+        assertThat(actual).isFalse();
+    }
+
+    private static PackageInfo createSystemAppPackageInfo() {
+        return createPackageInfo(ApplicationInfo.FLAG_SYSTEM);
+    }
+
+    private static PackageInfo createUpdatedSystemAppPackageInfo() {
+        return createPackageInfo(ApplicationInfo.FLAG_UPDATED_SYSTEM_APP);
+    }
+
+    private static PackageInfo createPackageInfo(int flags) {
+        return createPackageInfo(flags, /* requestedPermissions= */ new String[0]);
+    }
+
+    private static PackageInfo createPackageInfo(String[] requestedPermissions) {
+        return createPackageInfo(/* flags= */ 0, requestedPermissions);
+    }
+
+    private static PackageInfo createPackageInfo(int flags, String[] requestedPermissions) {
+        PackageInfo packageInfo = new PackageInfo();
+        packageInfo.packageName = PACKAGE_NAME;
+        packageInfo.applicationInfo = new ApplicationInfo();
+        packageInfo.applicationInfo.packageName = PACKAGE_NAME;
+        packageInfo.applicationInfo.flags = flags;
+        packageInfo.requestedPermissions = requestedPermissions;
+        return packageInfo;
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/contentprotection/RemoteContentProtectionServiceTest.java b/services/tests/servicestests/src/com/android/server/contentprotection/RemoteContentProtectionServiceTest.java
new file mode 100644
index 0000000..9135ef3
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/contentprotection/RemoteContentProtectionServiceTest.java
@@ -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.server.contentprotection;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+
+import android.annotation.NonNull;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.ParceledListSlice;
+import android.os.UserHandle;
+import android.service.contentcapture.IContentProtectionService;
+import android.view.contentcapture.ContentCaptureEvent;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.infra.AndroidFuture;
+import com.android.internal.infra.ServiceConnector;
+
+import com.google.common.collect.ImmutableList;
+
+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;
+
+/**
+ * Test for {@link RemoteContentProtectionService}.
+ *
+ * <p>Run with: {@code atest
+ * FrameworksServicesTests:com.android.server.contentprotection.RemoteContentProtectionServiceTest}
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class RemoteContentProtectionServiceTest {
+
+    private final Context mContext = ApplicationProvider.getApplicationContext();
+
+    @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+
+    @Mock private IContentProtectionService mMockContentProtectionService;
+
+    private RemoteContentProtectionService mRemoteContentProtectionService;
+
+    private int mConnectCallCount = 0;
+
+    @Before
+    public void setup() {
+        ComponentName componentName = new ComponentName(mContext.getPackageName(), "TestClass");
+        mRemoteContentProtectionService =
+                new TestRemoteContentProtectionService(mContext, componentName);
+    }
+
+    @Test
+    public void doesNotAutoConnect() {
+        assertThat(mConnectCallCount).isEqualTo(0);
+        verifyZeroInteractions(mMockContentProtectionService);
+    }
+
+    @Test
+    public void getAutoDisconnectTimeoutMs() {
+        long actual = mRemoteContentProtectionService.getAutoDisconnectTimeoutMs();
+
+        assertThat(actual).isEqualTo(3000L);
+    }
+
+    @Test
+    public void onLoginDetected() throws Exception {
+        ContentCaptureEvent event =
+                new ContentCaptureEvent(/* sessionId= */ 1111, /* type= */ 2222);
+        ParceledListSlice<ContentCaptureEvent> events =
+                new ParceledListSlice<>(ImmutableList.of(event));
+
+        mRemoteContentProtectionService.onLoginDetected(events);
+
+        verify(mMockContentProtectionService).onLoginDetected(events);
+    }
+
+    private final class TestRemoteContentProtectionService extends RemoteContentProtectionService {
+
+        TestRemoteContentProtectionService(Context context, ComponentName componentName) {
+            super(context, componentName, UserHandle.myUserId(), /* bindAllowInstant= */ false);
+        }
+
+        @Override // from ServiceConnector
+        public synchronized AndroidFuture<IContentProtectionService> connect() {
+            mConnectCallCount++;
+            return AndroidFuture.completedFuture(mMockContentProtectionService);
+        }
+
+        @Override // from ServiceConnector
+        public boolean run(@NonNull ServiceConnector.VoidJob<IContentProtectionService> job) {
+            try {
+                job.run(mMockContentProtectionService);
+            } catch (Exception ex) {
+                fail("Unexpected exception: " + ex);
+            }
+            return true;
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 99a3b80..7478778 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -46,7 +46,6 @@
 import static android.app.admin.DevicePolicyManager.WIFI_SECURITY_OPEN;
 import static android.app.admin.DevicePolicyManager.WIFI_SECURITY_PERSONAL;
 import static android.app.admin.DevicePolicyManager.WIPE_EUICC;
-import static android.app.admin.PasswordMetrics.computeForPasswordOrPin;
 import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_DIRECT_BOOT_AWARE;
 import static android.location.LocationManager.FUSED_PROVIDER;
 import static android.location.LocationManager.GPS_PROVIDER;
@@ -5479,8 +5478,7 @@
 
         reset(mContext.spiedContext);
 
-        PasswordMetrics passwordMetricsNoSymbols = computeForPasswordOrPin(
-                "abcdXYZ5".getBytes(), /* isPin */ false);
+        PasswordMetrics passwordMetricsNoSymbols = metricsForPassword("abcdXYZ5");
 
         setActivePasswordState(passwordMetricsNoSymbols);
         assertThat(dpm.isActivePasswordSufficient()).isTrue();
@@ -5507,8 +5505,7 @@
         reset(mContext.spiedContext);
         assertThat(dpm.isActivePasswordSufficient()).isFalse();
 
-        PasswordMetrics passwordMetricsWithSymbols = computeForPasswordOrPin(
-                "abcd.XY5".getBytes(), /* isPin */ false);
+        PasswordMetrics passwordMetricsWithSymbols = metricsForPassword("abcd.XY5");
 
         setActivePasswordState(passwordMetricsWithSymbols);
         assertThat(dpm.isActivePasswordSufficient()).isTrue();
@@ -5564,7 +5561,7 @@
         parentDpm.setRequiredPasswordComplexity(PASSWORD_COMPLEXITY_MEDIUM);
 
         when(getServices().lockSettingsInternal.getUserPasswordMetrics(UserHandle.USER_SYSTEM))
-                .thenReturn(computeForPasswordOrPin("184342".getBytes(), /* isPin */ true));
+                .thenReturn(metricsForPin("184342"));
 
         // Numeric password is compliant with current requirement (QUALITY_NUMERIC set explicitly
         // on the parent admin)
@@ -5685,7 +5682,7 @@
 
         // Set a work challenge and verify profile.isActivePasswordSufficient is now true
         when(getServices().lockSettingsInternal.getUserPasswordMetrics(managedProfileUserId))
-                .thenReturn(computeForPasswordOrPin("abcdXYZ5".getBytes(), /* isPin */ false));
+                .thenReturn(metricsForPassword("abcdXYZ5"));
         assertThat(dpm.isActivePasswordSufficient()).isTrue();
         assertThat(parentDpm.isActivePasswordSufficient()).isTrue();
     }
@@ -5710,7 +5707,7 @@
 
         // Set a work challenge and verify profile.isActivePasswordSufficient is now true
         when(getServices().lockSettingsInternal.getUserPasswordMetrics(managedProfileUserId))
-                .thenReturn(computeForPasswordOrPin("5156".getBytes(), /* isPin */ true));
+                .thenReturn(metricsForPin("5156"));
         assertThat(dpm.isActivePasswordSufficient()).isTrue();
         assertThat(parentDpm.isActivePasswordSufficient()).isTrue();
     }
@@ -5735,7 +5732,7 @@
 
         // Set a device lockscreen and verify parent.isActivePasswordSufficient is now true
         when(getServices().lockSettingsInternal.getUserPasswordMetrics(UserHandle.USER_SYSTEM))
-                .thenReturn(computeForPasswordOrPin("acbdXYZ5".getBytes(), /* isPin */ false));
+                .thenReturn(metricsForPassword("acbdXYZ5"));
         assertThat(dpm.isActivePasswordSufficient()).isTrue();
         assertThat(parentDpm.isActivePasswordSufficient()).isTrue();
     }
@@ -5758,7 +5755,7 @@
 
         // Set a device lockscreen and verify parent.isActivePasswordSufficient is now true
         when(getServices().lockSettingsInternal.getUserPasswordMetrics(UserHandle.USER_SYSTEM))
-                .thenReturn(computeForPasswordOrPin("1234".getBytes(), /* isPin */ true));
+                .thenReturn(metricsForPin("1234"));
         assertThat(dpm.isActivePasswordSufficient()).isTrue();
         assertThat(parentDpm.isActivePasswordSufficient()).isTrue();
     }
@@ -5783,7 +5780,7 @@
 
         // Set a device lockscreen and verify {profile, parent}.isActivePasswordSufficient is true
         when(getServices().lockSettingsInternal.getUserPasswordMetrics(UserHandle.USER_SYSTEM))
-                .thenReturn(computeForPasswordOrPin("abcdXYZ5".getBytes(), /* isPin */ false));
+                .thenReturn(metricsForPassword("abcdXYZ5"));
         assertThat(dpm.isActivePasswordSufficient()).isTrue();
         assertThat(parentDpm.isActivePasswordSufficient()).isTrue();
     }
@@ -5806,7 +5803,7 @@
 
         // Set a device lockscreen and verify {profile, parent}.isActivePasswordSufficient is true
         when(getServices().lockSettingsInternal.getUserPasswordMetrics(UserHandle.USER_SYSTEM))
-                .thenReturn(computeForPasswordOrPin("51567548".getBytes(), /* isPin */ true));
+                .thenReturn(metricsForPin("51567548"));
         assertThat(dpm.isActivePasswordSufficient()).isTrue();
         assertThat(parentDpm.isActivePasswordSufficient()).isTrue();
     }
@@ -5831,7 +5828,7 @@
 
         // Set a device lockscreen and verify {profile, parent}.isActivePasswordSufficient is true
         when(getServices().lockSettingsInternal.getUserPasswordMetrics(UserHandle.USER_SYSTEM))
-                .thenReturn(computeForPasswordOrPin("abcdXYZ5".getBytes(), /* isPin */ false));
+                .thenReturn(metricsForPassword("abcdXYZ5"));
         assertThat(dpm.isActivePasswordSufficient()).isTrue();
         assertThat(parentDpm.isActivePasswordSufficient()).isTrue();
     }
@@ -5854,7 +5851,7 @@
 
         // Set a device lockscreen and verify {profile, parent}.isActivePasswordSufficient is true
         when(getServices().lockSettingsInternal.getUserPasswordMetrics(UserHandle.USER_SYSTEM))
-                .thenReturn(computeForPasswordOrPin("5156".getBytes(), /* isPin */ true));
+                .thenReturn(metricsForPin("5156"));
         assertThat(dpm.isActivePasswordSufficient()).isTrue();
         assertThat(parentDpm.isActivePasswordSufficient()).isTrue();
     }
@@ -6909,7 +6906,7 @@
                 .thenReturn(CALLER_USER_HANDLE);
         when(getServices().lockSettingsInternal
                 .getUserPasswordMetrics(CALLER_USER_HANDLE))
-                .thenReturn(computeForPasswordOrPin("asdf".getBytes(), /* isPin */ false));
+                .thenReturn(metricsForPassword("asdf"));
 
         assertThat(dpm.getPasswordComplexity()).isEqualTo(PASSWORD_COMPLEXITY_MEDIUM);
     }
@@ -6929,10 +6926,10 @@
 
         when(getServices().lockSettingsInternal
                 .getUserPasswordMetrics(CALLER_USER_HANDLE))
-                .thenReturn(computeForPasswordOrPin("asdf".getBytes(), /* isPin */ false));
+                .thenReturn(metricsForPassword("asdf"));
         when(getServices().lockSettingsInternal
                 .getUserPasswordMetrics(parentUser.id))
-                .thenReturn(computeForPasswordOrPin("parentUser".getBytes(), /* isPin */ false));
+                .thenReturn(metricsForPassword("parentUser"));
 
         assertThat(dpm.getPasswordComplexity()).isEqualTo(PASSWORD_COMPLEXITY_HIGH);
     }
@@ -7654,15 +7651,13 @@
         assertThat(dpm.getPasswordComplexity()).isEqualTo(PASSWORD_COMPLEXITY_NONE);
 
         reset(mContext.spiedContext);
-        PasswordMetrics passwordMetricsNoSymbols = computeForPasswordOrPin(
-                "1234".getBytes(), /* isPin */ true);
+        PasswordMetrics passwordMetricsNoSymbols = metricsForPin("1234");
         setActivePasswordState(passwordMetricsNoSymbols);
         assertThat(dpm.getPasswordComplexity()).isEqualTo(PASSWORD_COMPLEXITY_LOW);
         assertThat(dpm.isActivePasswordSufficient()).isFalse();
 
         reset(mContext.spiedContext);
-        passwordMetricsNoSymbols = computeForPasswordOrPin(
-                "84125312943a".getBytes(), /* isPin */ false);
+        passwordMetricsNoSymbols = metricsForPassword("84125312943a");
         setActivePasswordState(passwordMetricsNoSymbols);
         assertThat(dpm.getPasswordComplexity()).isEqualTo(PASSWORD_COMPLEXITY_HIGH);
         // using isActivePasswordSufficient
@@ -8125,14 +8120,13 @@
     }
 
     @Test
-    public void testIsUsbDataSignalingEnabledForUser_systemUser() throws Exception {
+    public void testIsUsbDataSignalingEnabledForUser() throws Exception {
         when(getServices().usbManager.enableUsbDataSignal(false)).thenReturn(true);
         when(getServices().usbManager.getUsbHalVersion()).thenReturn(UsbManager.USB_HAL_V1_3);
         setDeviceOwner();
         dpm.setUsbDataSignalingEnabled(false);
-        mContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
 
-        assertThat(dpm.isUsbDataSignalingEnabledForUser(UserHandle.myUserId())).isFalse();
+        assertThat(dpm.isUsbDataSignalingEnabled()).isFalse();
     }
 
     @Test
@@ -8838,4 +8832,12 @@
         assumeTrue("device doesn't support deprecated password APIs",
                 isDeprecatedPasswordApisSupported());
     }
+
+    private static PasswordMetrics metricsForPassword(String password) {
+        return PasswordMetrics.computeForCredential(LockscreenCredential.createPassword(password));
+    }
+
+    private static PasswordMetrics metricsForPin(String pin) {
+        return PasswordMetrics.computeForCredential(LockscreenCredential.createPin(pin));
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
index 9ff600a..e5fac7a 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
@@ -251,6 +251,8 @@
                 return mMockSystemServices.roleManager;
             case Context.TELEPHONY_SUBSCRIPTION_SERVICE:
                 return mMockSystemServices.subscriptionManager;
+            case Context.USB_SERVICE:
+                return mMockSystemServices.usbManager;
         }
         throw new UnsupportedOperationException();
     }
diff --git a/services/tests/servicestests/src/com/android/server/display/AmbientBrightnessStatsTrackerTest.java b/services/tests/servicestests/src/com/android/server/display/AmbientBrightnessStatsTrackerTest.java
deleted file mode 100644
index 2c4fe53..0000000
--- a/services/tests/servicestests/src/com/android/server/display/AmbientBrightnessStatsTrackerTest.java
+++ /dev/null
@@ -1,444 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.display;
-
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-
-import android.hardware.display.AmbientBrightnessDayStats;
-import android.os.SystemClock;
-import android.os.UserManager;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.charset.StandardCharsets;
-import java.time.LocalDate;
-import java.util.ArrayList;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class AmbientBrightnessStatsTrackerTest {
-
-    private TestInjector mTestInjector;
-
-    @Before
-    public void setUp() {
-        mTestInjector = new TestInjector();
-    }
-
-    @Test
-    public void testBrightnessStatsTrackerOverSingleDay() {
-        AmbientBrightnessStatsTracker statsTracker = getTestStatsTracker();
-        ArrayList<AmbientBrightnessDayStats> userStats;
-        float[] expectedStats;
-        // Test case where no user data
-        userStats = statsTracker.getUserStats(0);
-        assertNull(userStats);
-        // Test after adding some user data
-        statsTracker.start();
-        statsTracker.add(0, 0);
-        mTestInjector.incrementTime(1000);
-        statsTracker.stop();
-        userStats = statsTracker.getUserStats(0);
-        assertEquals(1, userStats.size());
-        assertEquals(mTestInjector.getLocalDate(), userStats.get(0).getLocalDate());
-        expectedStats = getEmptyStatsArray();
-        expectedStats[0] = 1;
-        assertArrayEquals(expectedStats, userStats.get(0).getStats(), 0);
-        // Test after adding some more user data
-        statsTracker.start();
-        statsTracker.add(0, 0.05f);
-        mTestInjector.incrementTime(1000);
-        statsTracker.add(0, 0.2f);
-        mTestInjector.incrementTime(1500);
-        statsTracker.add(0, 50000);
-        mTestInjector.incrementTime(2500);
-        statsTracker.stop();
-        userStats = statsTracker.getUserStats(0);
-        assertEquals(1, userStats.size());
-        assertEquals(mTestInjector.getLocalDate(), userStats.get(0).getLocalDate());
-        expectedStats = getEmptyStatsArray();
-        expectedStats[0] = 2;
-        expectedStats[1] = 1.5f;
-        expectedStats[11] = 2.5f;
-        assertArrayEquals(expectedStats, userStats.get(0).getStats(), 0);
-    }
-
-    @Test
-    public void testBrightnessStatsTrackerOverMultipleDays() {
-        AmbientBrightnessStatsTracker statsTracker = getTestStatsTracker();
-        ArrayList<AmbientBrightnessDayStats> userStats;
-        float[] expectedStats;
-        // Add data for day 1
-        statsTracker.start();
-        statsTracker.add(0, 0.05f);
-        mTestInjector.incrementTime(1000);
-        statsTracker.add(0, 0.2f);
-        mTestInjector.incrementTime(1500);
-        statsTracker.add(0, 1);
-        mTestInjector.incrementTime(2500);
-        statsTracker.stop();
-        // Add data for day 2
-        mTestInjector.incrementDate(1);
-        statsTracker.start();
-        statsTracker.add(0, 0);
-        mTestInjector.incrementTime(3500);
-        statsTracker.add(0, 5);
-        mTestInjector.incrementTime(5000);
-        statsTracker.stop();
-        // Test that the data is tracked as expected
-        userStats = statsTracker.getUserStats(0);
-        assertEquals(2, userStats.size());
-        assertEquals(mTestInjector.getLocalDate().minusDays(1), userStats.get(0).getLocalDate());
-        expectedStats = getEmptyStatsArray();
-        expectedStats[0] = 1;
-        expectedStats[1] = 1.5f;
-        expectedStats[3] = 2.5f;
-        assertArrayEquals(expectedStats, userStats.get(0).getStats(), 0);
-        assertEquals(mTestInjector.getLocalDate(), userStats.get(1).getLocalDate());
-        expectedStats = getEmptyStatsArray();
-        expectedStats[0] = 3.5f;
-        expectedStats[4] = 5;
-        assertArrayEquals(expectedStats, userStats.get(1).getStats(), 0);
-    }
-
-    @Test
-    public void testBrightnessStatsTrackerOverMultipleUsers() {
-        AmbientBrightnessStatsTracker statsTracker = getTestStatsTracker();
-        ArrayList<AmbientBrightnessDayStats> userStats;
-        float[] expectedStats;
-        // Add data for user 1
-        statsTracker.start();
-        statsTracker.add(0, 0.05f);
-        mTestInjector.incrementTime(1000);
-        statsTracker.add(0, 0.2f);
-        mTestInjector.incrementTime(1500);
-        statsTracker.add(0, 1);
-        mTestInjector.incrementTime(2500);
-        statsTracker.stop();
-        // Add data for user 2
-        mTestInjector.incrementDate(1);
-        statsTracker.start();
-        statsTracker.add(1, 0);
-        mTestInjector.incrementTime(3500);
-        statsTracker.add(1, 5);
-        mTestInjector.incrementTime(5000);
-        statsTracker.stop();
-        // Test that the data is tracked as expected
-        userStats = statsTracker.getUserStats(0);
-        assertEquals(1, userStats.size());
-        assertEquals(mTestInjector.getLocalDate().minusDays(1), userStats.get(0).getLocalDate());
-        expectedStats = getEmptyStatsArray();
-        expectedStats[0] = 1;
-        expectedStats[1] = 1.5f;
-        expectedStats[3] = 2.5f;
-        assertArrayEquals(expectedStats, userStats.get(0).getStats(), 0);
-        userStats = statsTracker.getUserStats(1);
-        assertEquals(1, userStats.size());
-        assertEquals(mTestInjector.getLocalDate(), userStats.get(0).getLocalDate());
-        expectedStats = getEmptyStatsArray();
-        expectedStats[0] = 3.5f;
-        expectedStats[4] = 5;
-        assertArrayEquals(expectedStats, userStats.get(0).getStats(), 0);
-    }
-
-    @Test
-    public void testBrightnessStatsTrackerOverMaxDays() {
-        AmbientBrightnessStatsTracker statsTracker = getTestStatsTracker();
-        ArrayList<AmbientBrightnessDayStats> userStats;
-        // Add 10 extra days of data over the buffer limit
-        for (int i = 0; i < AmbientBrightnessStatsTracker.MAX_DAYS_TO_TRACK + 10; i++) {
-            mTestInjector.incrementDate(1);
-            statsTracker.start();
-            statsTracker.add(0, 10);
-            mTestInjector.incrementTime(1000);
-            statsTracker.add(0, 20);
-            mTestInjector.incrementTime(1000);
-            statsTracker.stop();
-        }
-        // Assert that we are only tracking last "MAX_DAYS_TO_TRACK"
-        userStats = statsTracker.getUserStats(0);
-        assertEquals(AmbientBrightnessStatsTracker.MAX_DAYS_TO_TRACK, userStats.size());
-        LocalDate runningDate = mTestInjector.getLocalDate();
-        for (int i = AmbientBrightnessStatsTracker.MAX_DAYS_TO_TRACK - 1; i >= 0; i--) {
-            assertEquals(runningDate, userStats.get(i).getLocalDate());
-            runningDate = runningDate.minusDays(1);
-        }
-    }
-
-    @Test
-    public void testReadAmbientBrightnessStats() throws IOException {
-        AmbientBrightnessStatsTracker statsTracker = getTestStatsTracker();
-        LocalDate date = mTestInjector.getLocalDate();
-        ArrayList<AmbientBrightnessDayStats> userStats;
-        String statsFile =
-                "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\r\n"
-                        + "<ambient-brightness-stats>\r\n"
-                        // Old stats that shouldn't be read
-                        + "<ambient-brightness-day-stats user=\"10\" local-date=\""
-                        + date.minusDays(AmbientBrightnessStatsTracker.MAX_DAYS_TO_TRACK)
-                        + "\" bucket-boundaries=\"0.0,1.0,3.0,10.0,30.0,100.0,300.0,1000.0,"
-                        + "3000.0,10000.0\" bucket-stats=\"1.088,0.0,0.726,0.0,25.868,0.0,0.0,"
-                        + "0.0,0.0,0.0\" />\r\n"
-                        // Valid stats that should get read
-                        + "<ambient-brightness-day-stats user=\"10\" local-date=\""
-                        + date.minusDays(1)
-                        + "\" bucket-boundaries=\"0.0,1.0,3.0,10.0,30.0,100.0,300.0,1000.0,"
-                        + "3000.0,10000.0\" bucket-stats=\"1.088,0.0,0.726,0.0,25.868,0.0,0.0,"
-                        + "0.0,0.0,0.0\" />\r\n"
-                        // Valid stats that should get read
-                        + "<ambient-brightness-day-stats user=\"10\" local-date=\"" + date
-                        + "\" bucket-boundaries=\"0.0,1.0,3.0,10.0,30.0,100.0,300.0,1000.0,"
-                        + "3000.0,10000.0\" bucket-stats=\"0.0,0.0,0.0,0.0,4.482,0.0,0.0,0.0,0.0,"
-                        + "0.0\" />\r\n"
-                        + "</ambient-brightness-stats>";
-        statsTracker.readStats(getInputStream(statsFile));
-        userStats = statsTracker.getUserStats(0);
-        assertEquals(2, userStats.size());
-        assertEquals(new AmbientBrightnessDayStats(date.minusDays(1),
-                new float[]{0, 1, 3, 10, 30, 100, 300, 1000, 3000, 10000},
-                new float[]{1.088f, 0, 0.726f, 0, 25.868f, 0, 0, 0, 0, 0}), userStats.get(0));
-        assertEquals(new AmbientBrightnessDayStats(date,
-                new float[]{0, 1, 3, 10, 30, 100, 300, 1000, 3000, 10000},
-                new float[]{0, 0, 0, 0, 4.482f, 0, 0, 0, 0, 0}), userStats.get(1));
-    }
-
-    @Test
-    public void testFailedReadAmbientBrightnessStatsWithException() {
-        AmbientBrightnessStatsTracker statsTracker = getTestStatsTracker();
-        LocalDate date = mTestInjector.getLocalDate();
-        String statsFile;
-        // Test with parse error
-        statsFile =
-                "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\r\n"
-                        + "<ambient-brightness-stats>\r\n"
-                        // Incorrect since bucket boundaries not parsable
-                        + "<ambient-brightness-day-stats user=\"10\" local-date=\"" + date
-                        + "\" bucket-boundaries=\"asdf,1.0,3.0,10.0,30.0,100.0,300.0,1000.0,"
-                        + "3000.0,10000.0\" bucket-stats=\"1.088,0.0,0.726,0.0,25.868,0.0,0.0,"
-                        + "0.0,0.0,0.0\" />\r\n"
-                        + "</ambient-brightness-stats>";
-        try {
-            statsTracker.readStats(getInputStream(statsFile));
-        } catch (IOException e) {
-            // Expected
-        }
-        assertNull(statsTracker.getUserStats(0));
-        // Test with incorrect data (bucket boundaries length not equal to stats length)
-        statsFile =
-                "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\r\n"
-                        + "<ambient-brightness-stats>\r\n"
-                        // Correct data
-                        + "<ambient-brightness-day-stats user=\"10\" local-date=\""
-                        + date.minusDays(1)
-                        + "\" bucket-boundaries=\"0.0,1.0,3.0,10.0,30.0,100.0,300.0,1000.0,"
-                        + "3000.0,10000.0\" bucket-stats=\"0.0,0.0,0.0,0.0,4.482,0.0,0.0,0.0,0.0,"
-                        + "0.0\" />\r\n"
-                        // Incorrect data
-                        + "<ambient-brightness-day-stats user=\"10\" local-date=\"" + date
-                        + "\" bucket-boundaries=\"0.0,1.0,3.0,10.0,30.0,100.0,1000.0,"
-                        + "3000.0,10000.0\" bucket-stats=\"1.088,0.0,0.726,0.0,25.868,0.0,0.0,"
-                        + "0.0,0.0,0.0\" />\r\n"
-                        + "</ambient-brightness-stats>";
-        try {
-            statsTracker.readStats(getInputStream(statsFile));
-        } catch (Exception e) {
-            // Expected
-        }
-        assertNull(statsTracker.getUserStats(0));
-        // Test with missing attribute
-        statsFile =
-                "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\r\n"
-                        + "<ambient-brightness-stats>\r\n"
-                        + "<ambientBrightnessDayStats user=\"10\" local-date=\"" + date
-                        + "\" bucket-boundaries=\"0.0,1.0,3.0,10.0,30.0,100.0,300.0,1000.0,"
-                        + "3000.0,10000.0\" />\r\n"
-                        + "</ambient-brightness-stats>";
-        try {
-            statsTracker.readStats(getInputStream(statsFile));
-        } catch (Exception e) {
-            // Expected
-        }
-        assertNull(statsTracker.getUserStats(0));
-    }
-
-    @Test
-    public void testWriteThenReadAmbientBrightnessStats() throws IOException {
-        AmbientBrightnessStatsTracker statsTracker = getTestStatsTracker();
-        ArrayList<AmbientBrightnessDayStats> userStats;
-        float[] expectedStats;
-        // Generate some placeholder data
-        // Data: very old which should not be read
-        statsTracker.start();
-        statsTracker.add(0, 0.05f);
-        mTestInjector.incrementTime(1000);
-        statsTracker.add(0, 0.2f);
-        mTestInjector.incrementTime(1500);
-        statsTracker.add(0, 1);
-        mTestInjector.incrementTime(2500);
-        statsTracker.stop();
-        // Data: day 1 user 1
-        mTestInjector.incrementDate(AmbientBrightnessStatsTracker.MAX_DAYS_TO_TRACK - 1);
-        statsTracker.start();
-        statsTracker.add(0, 0.05f);
-        mTestInjector.incrementTime(1000);
-        statsTracker.add(0, 0.2f);
-        mTestInjector.incrementTime(1500);
-        statsTracker.add(0, 1);
-        mTestInjector.incrementTime(2500);
-        statsTracker.stop();
-        // Data: day 1 user 2
-        statsTracker.start();
-        statsTracker.add(1, 0);
-        mTestInjector.incrementTime(3500);
-        statsTracker.add(1, 5);
-        mTestInjector.incrementTime(5000);
-        statsTracker.stop();
-        // Data: day 2 user 1
-        mTestInjector.incrementDate(1);
-        statsTracker.start();
-        statsTracker.add(0, 0);
-        mTestInjector.incrementTime(3500);
-        statsTracker.add(0, 50000);
-        mTestInjector.incrementTime(5000);
-        statsTracker.stop();
-        // Write them
-        ByteArrayOutputStream baos = new ByteArrayOutputStream();
-        statsTracker.writeStats(baos);
-        baos.flush();
-        // Read them back and assert that it's the same
-        ByteArrayInputStream input = new ByteArrayInputStream(baos.toByteArray());
-        AmbientBrightnessStatsTracker newStatsTracker = getTestStatsTracker();
-        newStatsTracker.readStats(input);
-        userStats = newStatsTracker.getUserStats(0);
-        assertEquals(2, userStats.size());
-        // Check day 1 user 1
-        assertEquals(mTestInjector.getLocalDate().minusDays(1), userStats.get(0).getLocalDate());
-        expectedStats = getEmptyStatsArray();
-        expectedStats[0] = 1;
-        expectedStats[1] = 1.5f;
-        expectedStats[3] = 2.5f;
-        assertArrayEquals(expectedStats, userStats.get(0).getStats(), 0);
-        // Check day 2 user 1
-        assertEquals(mTestInjector.getLocalDate(), userStats.get(1).getLocalDate());
-        expectedStats = getEmptyStatsArray();
-        expectedStats[0] = 3.5f;
-        expectedStats[11] = 5;
-        assertArrayEquals(expectedStats, userStats.get(1).getStats(), 0);
-        userStats = newStatsTracker.getUserStats(1);
-        assertEquals(1, userStats.size());
-        // Check day 1 user 2
-        assertEquals(mTestInjector.getLocalDate().minusDays(1), userStats.get(0).getLocalDate());
-        expectedStats = getEmptyStatsArray();
-        expectedStats[0] = 3.5f;
-        expectedStats[4] = 5;
-        assertArrayEquals(expectedStats, userStats.get(0).getStats(), 0);
-    }
-
-    @Test
-    public void testTimer() {
-        AmbientBrightnessStatsTracker.Timer timer = new AmbientBrightnessStatsTracker.Timer(
-                () -> mTestInjector.elapsedRealtimeMillis());
-        assertEquals(0, timer.totalDurationSec(), 0);
-        mTestInjector.incrementTime(1000);
-        assertEquals(0, timer.totalDurationSec(), 0);
-        assertFalse(timer.isRunning());
-        // Start timer
-        timer.start();
-        assertTrue(timer.isRunning());
-        assertEquals(0, timer.totalDurationSec(), 0);
-        mTestInjector.incrementTime(1000);
-        assertTrue(timer.isRunning());
-        assertEquals(1, timer.totalDurationSec(), 0);
-        // Reset timer
-        timer.reset();
-        assertEquals(0, timer.totalDurationSec(), 0);
-        assertFalse(timer.isRunning());
-        // Start again
-        timer.start();
-        assertTrue(timer.isRunning());
-        assertEquals(0, timer.totalDurationSec(), 0);
-        mTestInjector.incrementTime(2000);
-        assertTrue(timer.isRunning());
-        assertEquals(2, timer.totalDurationSec(), 0);
-        // Reset again
-        timer.reset();
-        assertEquals(0, timer.totalDurationSec(), 0);
-        assertFalse(timer.isRunning());
-    }
-
-    private class TestInjector extends AmbientBrightnessStatsTracker.Injector {
-
-        private long mElapsedRealtimeMillis = SystemClock.elapsedRealtime();
-        private LocalDate mLocalDate = LocalDate.now();
-
-        public void incrementTime(long timeMillis) {
-            mElapsedRealtimeMillis += timeMillis;
-        }
-
-        public void incrementDate(int numDays) {
-            mLocalDate = mLocalDate.plusDays(numDays);
-        }
-
-        @Override
-        public long elapsedRealtimeMillis() {
-            return mElapsedRealtimeMillis;
-        }
-
-        @Override
-        public int getUserSerialNumber(UserManager userManager, int userId) {
-            return userId + 10;
-        }
-
-        @Override
-        public int getUserId(UserManager userManager, int userSerialNumber) {
-            return userSerialNumber - 10;
-        }
-
-        @Override
-        public LocalDate getLocalDate() {
-            return mLocalDate;
-        }
-    }
-
-    private AmbientBrightnessStatsTracker getTestStatsTracker() {
-        return new AmbientBrightnessStatsTracker(
-                InstrumentationRegistry.getContext().getSystemService(UserManager.class),
-                mTestInjector);
-    }
-
-    private float[] getEmptyStatsArray() {
-        return new float[AmbientBrightnessStatsTracker.BUCKET_BOUNDARIES_FOR_NEW_STATS.length];
-    }
-
-    private InputStream getInputStream(String data) {
-        return new ByteArrayInputStream(data.getBytes(StandardCharsets.UTF_8));
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
deleted file mode 100644
index 962e867..0000000
--- a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
+++ /dev/null
@@ -1,841 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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 com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED;
-
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.anyFloat;
-import static org.mockito.Mockito.anyInt;
-import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.hardware.Sensor;
-import android.hardware.SensorEventListener;
-import android.hardware.SensorManager;
-import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
-import android.os.Handler;
-import android.os.test.TestLooper;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.server.testutils.OffsettableClock;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class AutomaticBrightnessControllerTest {
-    private static final float BRIGHTNESS_MIN_FLOAT = 0.0f;
-    private static final float BRIGHTNESS_MAX_FLOAT = 1.0f;
-    private static final int LIGHT_SENSOR_RATE = 20;
-    private static final int INITIAL_LIGHT_SENSOR_RATE = 20;
-    private static final int BRIGHTENING_LIGHT_DEBOUNCE_CONFIG = 0;
-    private static final int DARKENING_LIGHT_DEBOUNCE_CONFIG = 0;
-    private static final float DOZE_SCALE_FACTOR = 0.0f;
-    private static final boolean RESET_AMBIENT_LUX_AFTER_WARMUP_CONFIG = false;
-    private static final int LIGHT_SENSOR_WARMUP_TIME = 0;
-    private static final int AMBIENT_LIGHT_HORIZON_SHORT = 1000;
-    private static final int AMBIENT_LIGHT_HORIZON_LONG = 2000;
-    private static final float EPSILON = 0.001f;
-    private OffsettableClock mClock = new OffsettableClock();
-    private TestLooper mTestLooper;
-    private Context mContext;
-    private AutomaticBrightnessController mController;
-    private Sensor mLightSensor;
-
-    @Mock SensorManager mSensorManager;
-    @Mock BrightnessMappingStrategy mBrightnessMappingStrategy;
-    @Mock BrightnessMappingStrategy mIdleBrightnessMappingStrategy;
-    @Mock HysteresisLevels mAmbientBrightnessThresholds;
-    @Mock HysteresisLevels mScreenBrightnessThresholds;
-    @Mock HysteresisLevels mAmbientBrightnessThresholdsIdle;
-    @Mock HysteresisLevels mScreenBrightnessThresholdsIdle;
-    @Mock Handler mNoOpHandler;
-    @Mock HighBrightnessModeController mHbmController;
-    @Mock BrightnessThrottler mBrightnessThrottler;
-
-    @Before
-    public void setUp() throws Exception {
-        // Share classloader to allow package private access.
-        System.setProperty("dexmaker.share_classloader", "true");
-        MockitoAnnotations.initMocks(this);
-
-        mLightSensor = TestUtils.createSensor(Sensor.TYPE_LIGHT, "Light Sensor");
-        mContext = InstrumentationRegistry.getContext();
-        mController = setupController(mLightSensor, BrightnessMappingStrategy.NO_USER_LUX,
-                BrightnessMappingStrategy.NO_USER_BRIGHTNESS);
-    }
-
-    @After
-    public void tearDown() {
-        if (mController != null) {
-            // Stop the update Brightness loop.
-            mController.stop();
-            mController = null;
-        }
-    }
-
-    private AutomaticBrightnessController setupController(Sensor lightSensor, float userLux,
-            float userBrightness) {
-        mClock = new OffsettableClock.Stopped();
-        mTestLooper = new TestLooper(mClock::now);
-
-        AutomaticBrightnessController controller = new AutomaticBrightnessController(
-                new AutomaticBrightnessController.Injector() {
-                    @Override
-                    public Handler getBackgroundThreadHandler() {
-                        return mNoOpHandler;
-                    }
-
-                    @Override
-                    AutomaticBrightnessController.Clock createClock() {
-                        return mClock::now;
-                    }
-
-                }, // pass in test looper instead, pass in offsettable clock
-                () -> { }, mTestLooper.getLooper(), mSensorManager, lightSensor,
-                mBrightnessMappingStrategy, LIGHT_SENSOR_WARMUP_TIME, BRIGHTNESS_MIN_FLOAT,
-                BRIGHTNESS_MAX_FLOAT, DOZE_SCALE_FACTOR, LIGHT_SENSOR_RATE,
-                INITIAL_LIGHT_SENSOR_RATE, BRIGHTENING_LIGHT_DEBOUNCE_CONFIG,
-                DARKENING_LIGHT_DEBOUNCE_CONFIG, RESET_AMBIENT_LUX_AFTER_WARMUP_CONFIG,
-                mAmbientBrightnessThresholds, mScreenBrightnessThresholds,
-                mAmbientBrightnessThresholdsIdle, mScreenBrightnessThresholdsIdle,
-                mContext, mHbmController, mBrightnessThrottler, mIdleBrightnessMappingStrategy,
-                AMBIENT_LIGHT_HORIZON_SHORT, AMBIENT_LIGHT_HORIZON_LONG, userLux, userBrightness
-        );
-
-        when(mHbmController.getCurrentBrightnessMax()).thenReturn(BRIGHTNESS_MAX_FLOAT);
-        when(mHbmController.getCurrentBrightnessMin()).thenReturn(BRIGHTNESS_MIN_FLOAT);
-        // Disable brightness throttling by default. Individual tests can enable it as needed.
-        when(mBrightnessThrottler.getBrightnessCap()).thenReturn(BRIGHTNESS_MAX_FLOAT);
-        when(mBrightnessThrottler.isThrottled()).thenReturn(false);
-
-        // Configure the brightness controller and grab an instance of the sensor listener,
-        // through which we can deliver fake (for test) sensor values.
-        controller.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration= */,
-                0 /* brightness= */, false /* userChangedBrightness= */, 0 /* adjustment= */,
-                false /* userChanged= */, DisplayPowerRequest.POLICY_BRIGHT,
-                /* shouldResetShortTermModel= */ true);
-
-        return controller;
-    }
-
-    @Test
-    public void testNoHysteresisAtMinBrightness() throws Exception {
-        ArgumentCaptor<SensorEventListener> listenerCaptor =
-                ArgumentCaptor.forClass(SensorEventListener.class);
-        verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
-                eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
-        SensorEventListener listener = listenerCaptor.getValue();
-
-        // Set up system to return 0.02f as a brightness value
-        float lux1 = 100.0f;
-        // Brightness as float (from 0.0f to 1.0f)
-        float normalizedBrightness1 = 0.02f;
-        when(mAmbientBrightnessThresholds.getBrighteningThreshold(lux1))
-                .thenReturn(lux1);
-        when(mAmbientBrightnessThresholds.getDarkeningThreshold(lux1))
-                .thenReturn(lux1);
-        when(mBrightnessMappingStrategy.getBrightness(eq(lux1), eq(null), anyInt()))
-                .thenReturn(normalizedBrightness1);
-
-        // This is the important bit: When the new brightness is set, make sure the new
-        // brightening threshold is beyond the maximum brightness value...so that we can test that
-        // our threshold clamping works.
-        when(mScreenBrightnessThresholds.getBrighteningThreshold(normalizedBrightness1))
-                .thenReturn(1.0f);
-
-        // Send new sensor value and verify
-        listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, (int) lux1));
-        assertEquals(normalizedBrightness1, mController.getAutomaticScreenBrightness(), EPSILON);
-
-        // Set up system to return 0.0f (minimum possible brightness) as a brightness value
-        float lux2 = 10.0f;
-        float normalizedBrightness2 = 0.0f;
-        when(mAmbientBrightnessThresholds.getBrighteningThreshold(lux2))
-                .thenReturn(lux2);
-        when(mAmbientBrightnessThresholds.getDarkeningThreshold(lux2))
-                .thenReturn(lux2);
-        when(mBrightnessMappingStrategy.getBrightness(anyFloat(), eq(null), anyInt()))
-                .thenReturn(normalizedBrightness2);
-
-        // Send new sensor value and verify
-        listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, (int) lux2));
-        assertEquals(normalizedBrightness2, mController.getAutomaticScreenBrightness(), EPSILON);
-    }
-
-    @Test
-    public void testNoHysteresisAtMaxBrightness() throws Exception {
-        ArgumentCaptor<SensorEventListener> listenerCaptor =
-                ArgumentCaptor.forClass(SensorEventListener.class);
-        verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
-                eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
-        SensorEventListener listener = listenerCaptor.getValue();
-
-        // Set up system to return 0.98f as a brightness value
-        float lux1 = 100.0f;
-        float normalizedBrightness1 = 0.98f;
-        when(mAmbientBrightnessThresholds.getBrighteningThreshold(lux1))
-                .thenReturn(lux1);
-        when(mAmbientBrightnessThresholds.getDarkeningThreshold(lux1))
-                .thenReturn(lux1);
-        when(mBrightnessMappingStrategy.getBrightness(eq(lux1), eq(null), anyInt()))
-                .thenReturn(normalizedBrightness1);
-
-        // This is the important bit: When the new brightness is set, make sure the new
-        // brightening threshold is beyond the maximum brightness value...so that we can test that
-        // our threshold clamping works.
-        when(mScreenBrightnessThresholds.getBrighteningThreshold(normalizedBrightness1))
-                .thenReturn(1.1f);
-
-        // Send new sensor value and verify
-        listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, (int) lux1));
-        assertEquals(normalizedBrightness1, mController.getAutomaticScreenBrightness(), EPSILON);
-
-
-        // Set up system to return 1.0f as a brightness value (brightness_max)
-        float lux2 = 110.0f;
-        float normalizedBrightness2 = 1.0f;
-        when(mAmbientBrightnessThresholds.getBrighteningThreshold(lux2))
-                .thenReturn(lux2);
-        when(mAmbientBrightnessThresholds.getDarkeningThreshold(lux2))
-                .thenReturn(lux2);
-        when(mBrightnessMappingStrategy.getBrightness(anyFloat(), eq(null), anyInt()))
-                .thenReturn(normalizedBrightness2);
-
-        // Send new sensor value and verify
-        listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, (int) lux2));
-        assertEquals(normalizedBrightness2, mController.getAutomaticScreenBrightness(), EPSILON);
-    }
-
-    @Test
-    public void testUserAddUserDataPoint() throws Exception {
-        ArgumentCaptor<SensorEventListener> listenerCaptor =
-                ArgumentCaptor.forClass(SensorEventListener.class);
-        verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
-                eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
-        SensorEventListener listener = listenerCaptor.getValue();
-
-        // Sensor reads 1000 lux,
-        listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1000));
-
-        // User sets brightness to 100
-        mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration= */,
-                0.5f /* brightness= */, true /* userChangedBrightness= */, 0 /* adjustment= */,
-                false /* userChanged= */, DisplayPowerRequest.POLICY_BRIGHT,
-                /* shouldResetShortTermModel= */ true);
-
-        // There should be a user data point added to the mapper.
-        verify(mBrightnessMappingStrategy).addUserDataPoint(1000f, 0.5f);
-    }
-
-    @Test
-    public void testRecalculateSplines() throws Exception {
-        // Enabling the light sensor, and setting the ambient lux to 1000
-        int currentLux = 1000;
-        ArgumentCaptor<SensorEventListener> listenerCaptor =
-                ArgumentCaptor.forClass(SensorEventListener.class);
-        verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
-                eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
-        SensorEventListener listener = listenerCaptor.getValue();
-        listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, currentLux));
-
-        // User sets brightness to 0.5f
-        when(mBrightnessMappingStrategy.getBrightness(currentLux,
-                null, ApplicationInfo.CATEGORY_UNDEFINED)).thenReturn(0.5f);
-        mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration= */,
-                0.5f /* brightness= */, true /* userChangedBrightness= */, 0 /* adjustment= */,
-                false /* userChanged= */, DisplayPowerRequest.POLICY_BRIGHT,
-                /* shouldResetShortTermModel= */ true);
-
-        //Recalculating the spline with RBC enabled, verifying that the short term model is reset,
-        //and the interaction is learnt in short term model
-        float[] adjustments = new float[]{0.2f, 0.6f};
-        mController.recalculateSplines(true, adjustments);
-        verify(mBrightnessMappingStrategy).clearUserDataPoints();
-        verify(mBrightnessMappingStrategy).recalculateSplines(true, adjustments);
-        verify(mBrightnessMappingStrategy, times(2)).addUserDataPoint(currentLux, 0.5f);
-
-        clearInvocations(mBrightnessMappingStrategy);
-
-        // Verify short term model is not learnt when RBC is disabled
-        mController.recalculateSplines(false, adjustments);
-        verify(mBrightnessMappingStrategy).clearUserDataPoints();
-        verify(mBrightnessMappingStrategy).recalculateSplines(false, adjustments);
-        verifyNoMoreInteractions(mBrightnessMappingStrategy);
-    }
-
-    @Test
-    public void testShortTermModelTimesOut() throws Exception {
-        ArgumentCaptor<SensorEventListener> listenerCaptor =
-                ArgumentCaptor.forClass(SensorEventListener.class);
-        verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
-                eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
-        SensorEventListener listener = listenerCaptor.getValue();
-
-        // Sensor reads 123 lux,
-        listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 123));
-        // User sets brightness to 100
-        mController.configure(AUTO_BRIGHTNESS_ENABLED, /* configuration= */ null,
-                /* brightness= */ 0.5f, /* userChangedBrightness= */ true, /* adjustment= */ 0,
-                /* userChanged= */ false, DisplayPowerRequest.POLICY_BRIGHT,
-                /* shouldResetShortTermModel= */ true);
-
-        when(mBrightnessMappingStrategy.getShortTermModelTimeout()).thenReturn(2000L);
-
-        mController.switchToIdleMode();
-        when(mIdleBrightnessMappingStrategy.isForIdleMode()).thenReturn(true);
-        when(mBrightnessMappingStrategy.shouldResetShortTermModel(
-                123f, 0.5f)).thenReturn(true);
-
-        // Sensor reads 1000 lux,
-        listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1000));
-        mTestLooper.moveTimeForward(
-                mBrightnessMappingStrategy.getShortTermModelTimeout() + 1000);
-        mTestLooper.dispatchAll();
-
-        mController.switchToInteractiveScreenBrightnessMode();
-        mTestLooper.moveTimeForward(4000);
-        mTestLooper.dispatchAll();
-
-        // Verify only happens on the first configure. (i.e. not again when switching back)
-        // Intentionally using any() to ensure it's not called whatsoever.
-        verify(mBrightnessMappingStrategy, times(1))
-                .addUserDataPoint(123.0f, 0.5f);
-        verify(mBrightnessMappingStrategy, times(1))
-                .addUserDataPoint(anyFloat(), anyFloat());
-    }
-
-    @Test
-    public void testShortTermModelDoesntTimeOut() throws Exception {
-        ArgumentCaptor<SensorEventListener> listenerCaptor =
-                ArgumentCaptor.forClass(SensorEventListener.class);
-        verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
-                eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
-        SensorEventListener listener = listenerCaptor.getValue();
-
-        // Sensor reads 123 lux,
-        listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 123));
-        // User sets brightness to 100
-        mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration= */,
-                0.51f /* brightness= */, true /* userChangedBrightness= */, 0 /* adjustment= */,
-                false /* userChanged= */, DisplayPowerRequest.POLICY_BRIGHT,
-                /* shouldResetShortTermModel= */ true);
-
-        when(mBrightnessMappingStrategy.shouldResetShortTermModel(
-                anyFloat(), anyFloat())).thenReturn(true);
-        when(mBrightnessMappingStrategy.getShortTermModelTimeout()).thenReturn(2000L);
-        when(mBrightnessMappingStrategy.getUserBrightness()).thenReturn(0.51f);
-        when(mBrightnessMappingStrategy.getUserLux()).thenReturn(123.0f);
-
-        mController.switchToIdleMode();
-        when(mIdleBrightnessMappingStrategy.isForIdleMode()).thenReturn(true);
-
-        // Time does not move forward, since clock is doesn't increment naturally.
-        mTestLooper.dispatchAll();
-
-        // Sensor reads 100000 lux,
-        listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 678910));
-        mController.switchToInteractiveScreenBrightnessMode();
-
-        // Verify short term model is not reset.
-        verify(mBrightnessMappingStrategy, never()).clearUserDataPoints();
-
-        // Verify that we add the data point once when the user sets it, and again when we return
-        // interactive mode.
-        verify(mBrightnessMappingStrategy, times(2))
-                .addUserDataPoint(123.0f, 0.51f);
-    }
-
-    @Test
-    public void testShortTermModelIsRestoredWhenSwitchingWithinTimeout() throws Exception {
-        ArgumentCaptor<SensorEventListener> listenerCaptor =
-                ArgumentCaptor.forClass(SensorEventListener.class);
-        verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
-                eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
-        SensorEventListener listener = listenerCaptor.getValue();
-
-        // Sensor reads 123 lux,
-        listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 123));
-        // User sets brightness to 100
-        mController.configure(AUTO_BRIGHTNESS_ENABLED, /* configuration= */ null,
-                /* brightness= */ 0.5f, /* userChangedBrightness= */ true, /* adjustment= */ 0,
-                /* userChanged= */ false, DisplayPowerRequest.POLICY_BRIGHT,
-                /* shouldResetShortTermModel= */ true);
-
-        when(mBrightnessMappingStrategy.getShortTermModelTimeout()).thenReturn(2000L);
-        when(mBrightnessMappingStrategy.getUserBrightness()).thenReturn(0.5f);
-        when(mBrightnessMappingStrategy.getUserLux()).thenReturn(123f);
-
-        mController.switchToIdleMode();
-        when(mIdleBrightnessMappingStrategy.isForIdleMode()).thenReturn(true);
-        when(mIdleBrightnessMappingStrategy.getUserBrightness()).thenReturn(-1f);
-        when(mIdleBrightnessMappingStrategy.getUserLux()).thenReturn(-1f);
-        when(mBrightnessMappingStrategy.shouldResetShortTermModel(
-                123f, 0.5f)).thenReturn(true);
-
-        // Sensor reads 1000 lux,
-        listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1000));
-        mTestLooper.moveTimeForward(
-                mBrightnessMappingStrategy.getShortTermModelTimeout() + 1000);
-        mTestLooper.dispatchAll();
-
-        mController.switchToInteractiveScreenBrightnessMode();
-        mTestLooper.moveTimeForward(4000);
-        mTestLooper.dispatchAll();
-
-        // Verify only happens on the first configure. (i.e. not again when switching back)
-        // Intentionally using any() to ensure it's not called whatsoever.
-        verify(mBrightnessMappingStrategy, times(1))
-                .addUserDataPoint(123.0f, 0.5f);
-        verify(mBrightnessMappingStrategy, times(1))
-                .addUserDataPoint(anyFloat(), anyFloat());
-    }
-
-    @Test
-    public void testShortTermModelNotRestoredAfterTimeout() throws Exception {
-        ArgumentCaptor<SensorEventListener> listenerCaptor =
-                ArgumentCaptor.forClass(SensorEventListener.class);
-        verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
-                eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
-        SensorEventListener listener = listenerCaptor.getValue();
-
-        // Sensor reads 123 lux,
-        listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 123));
-        // User sets brightness to 100
-        mController.configure(AUTO_BRIGHTNESS_ENABLED, /* configuration= */ null,
-                /* brightness= */ 0.5f, /* userChangedBrightness= */ true, /* adjustment= */ 0,
-                /* userChanged= */ false, DisplayPowerRequest.POLICY_BRIGHT,
-                /* shouldResetShortTermModel= */ true);
-
-        when(mBrightnessMappingStrategy.getShortTermModelTimeout()).thenReturn(2000L);
-
-        when(mBrightnessMappingStrategy.getUserBrightness()).thenReturn(0.5f);
-        when(mBrightnessMappingStrategy.getUserLux()).thenReturn(123f);
-
-        mController.switchToIdleMode();
-        when(mIdleBrightnessMappingStrategy.isForIdleMode()).thenReturn(true);
-        when(mIdleBrightnessMappingStrategy.getUserBrightness()).thenReturn(-1f);
-        when(mIdleBrightnessMappingStrategy.getUserLux()).thenReturn(-1f);
-
-        when(mBrightnessMappingStrategy.shouldResetShortTermModel(
-                123f, 0.5f)).thenReturn(true);
-
-        // Sensor reads 1000 lux,
-        listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1000));
-        // Do not fast-forward time.
-        mTestLooper.dispatchAll();
-
-        mController.switchToInteractiveScreenBrightnessMode();
-        // Do not fast-forward time
-        mTestLooper.dispatchAll();
-
-        // Verify this happens on the first configure and again when switching back
-        // Intentionally using any() to ensure it's not called any other times whatsoever.
-        verify(mBrightnessMappingStrategy, times(2))
-                .addUserDataPoint(123.0f, 0.5f);
-        verify(mBrightnessMappingStrategy, times(2))
-                .addUserDataPoint(anyFloat(), anyFloat());
-    }
-
-    @Test
-    public void testSwitchBetweenModesNoUserInteractions() throws Exception {
-        ArgumentCaptor<SensorEventListener> listenerCaptor =
-                ArgumentCaptor.forClass(SensorEventListener.class);
-        verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
-                eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
-        SensorEventListener listener = listenerCaptor.getValue();
-
-        // Sensor reads 123 lux,
-        listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 123));
-        when(mBrightnessMappingStrategy.getShortTermModelTimeout()).thenReturn(2000L);
-        when(mBrightnessMappingStrategy.getUserBrightness()).thenReturn(-1.0f);
-        when(mBrightnessMappingStrategy.getUserLux()).thenReturn(-1.0f);
-
-        // No user brightness interaction.
-
-        mController.switchToIdleMode();
-        when(mIdleBrightnessMappingStrategy.isForIdleMode()).thenReturn(true);
-        when(mIdleBrightnessMappingStrategy.getUserBrightness()).thenReturn(-1.0f);
-        when(mIdleBrightnessMappingStrategy.getUserLux()).thenReturn(-1.0f);
-
-        // Sensor reads 1000 lux,
-        listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1000));
-        // Do not fast-forward time.
-        mTestLooper.dispatchAll();
-
-        mController.switchToInteractiveScreenBrightnessMode();
-        // Do not fast-forward time
-        mTestLooper.dispatchAll();
-
-        // Ensure that there are no data points added, since the user has never adjusted the
-        // brightness
-        verify(mBrightnessMappingStrategy, times(0))
-                .addUserDataPoint(anyFloat(), anyFloat());
-    }
-
-    @Test
-    public void testSwitchToIdleMappingStrategy() throws Exception {
-        ArgumentCaptor<SensorEventListener> listenerCaptor =
-                ArgumentCaptor.forClass(SensorEventListener.class);
-        verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
-                eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
-        SensorEventListener listener = listenerCaptor.getValue();
-
-        // Sensor reads 1000 lux,
-        listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1000));
-
-        // User sets brightness to 100
-        mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration= */,
-                0.5f /* brightness= */, true /* userChangedBrightness= */, 0 /* adjustment= */,
-                false /* userChanged= */, DisplayPowerRequest.POLICY_BRIGHT,
-                /* shouldResetShortTermModel= */ true);
-
-        // There should be a user data point added to the mapper.
-        verify(mBrightnessMappingStrategy, times(1)).addUserDataPoint(1000f, 0.5f);
-        verify(mBrightnessMappingStrategy, times(2)).setBrightnessConfiguration(any());
-        verify(mBrightnessMappingStrategy, times(3)).getBrightness(anyFloat(), any(), anyInt());
-
-        // Now let's do the same for idle mode
-        mController.switchToIdleMode();
-        // Called once for init, and once when switching,
-        // setAmbientLux() is called twice and once in updateAutoBrightness()
-        verify(mBrightnessMappingStrategy, times(5)).isForIdleMode();
-        // Called when switching.
-        verify(mBrightnessMappingStrategy, times(1)).getShortTermModelTimeout();
-        verify(mBrightnessMappingStrategy, times(1)).getUserBrightness();
-        verify(mBrightnessMappingStrategy, times(1)).getUserLux();
-
-        // Ensure, after switching, original BMS is not used anymore
-        verifyNoMoreInteractions(mBrightnessMappingStrategy);
-
-        // User sets idle brightness to 0.5
-        mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration= */,
-                0.5f /* brightness= */, true /* userChangedBrightness= */, 0 /* adjustment= */,
-                false /* userChanged= */, DisplayPowerRequest.POLICY_BRIGHT,
-                /* shouldResetShortTermModel= */ true);
-
-        // Ensure we use the correct mapping strategy
-        verify(mIdleBrightnessMappingStrategy, times(1)).addUserDataPoint(1000f, 0.5f);
-    }
-
-    @Test
-    public void testAmbientLightHorizon() throws Exception {
-        ArgumentCaptor<SensorEventListener> listenerCaptor =
-                ArgumentCaptor.forClass(SensorEventListener.class);
-        verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
-                eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
-        SensorEventListener listener = listenerCaptor.getValue();
-
-        long increment = 500;
-        // set autobrightness to low
-        // t = 0
-        listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0));
-
-        // t = 500
-        mClock.fastForward(increment);
-        listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0));
-
-        // t = 1000
-        mClock.fastForward(increment);
-        listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0));
-        assertEquals(0.0f, mController.getAmbientLux(), EPSILON);
-
-        // t = 1500
-        mClock.fastForward(increment);
-        listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0));
-        assertEquals(0.0f, mController.getAmbientLux(), EPSILON);
-
-        // t = 2000
-        // ensure that our reading is at 0.
-        mClock.fastForward(increment);
-        listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0));
-        assertEquals(0.0f, mController.getAmbientLux(), EPSILON);
-
-        // t = 2500
-        // first 10000 lux sensor event reading
-        mClock.fastForward(increment);
-        listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 10000));
-        assertTrue(mController.getAmbientLux() > 0.0f);
-        assertTrue(mController.getAmbientLux() < 10000.0f);
-
-        // t = 3000
-        // lux reading should still not yet be 10000.
-        mClock.fastForward(increment);
-        listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 10000));
-        assertTrue(mController.getAmbientLux() > 0.0f);
-        assertTrue(mController.getAmbientLux() < 10000.0f);
-
-        // t = 3500
-        mClock.fastForward(increment);
-        // lux has been high (10000) for 1000ms.
-        // lux reading should be 10000
-        // short horizon (ambient lux) is high, long horizon is still not high
-        listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 10000));
-        assertEquals(10000.0f, mController.getAmbientLux(), EPSILON);
-
-        // t = 4000
-        // stay high
-        mClock.fastForward(increment);
-        listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 10000));
-        assertEquals(10000.0f, mController.getAmbientLux(), EPSILON);
-
-        // t = 4500
-        Mockito.clearInvocations(mBrightnessMappingStrategy);
-        mClock.fastForward(increment);
-        // short horizon is high, long horizon is high too
-        listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 10000));
-        verify(mBrightnessMappingStrategy, times(1)).getBrightness(10000, null, -1);
-        assertEquals(10000.0f, mController.getAmbientLux(), EPSILON);
-
-        // t = 5000
-        mClock.fastForward(increment);
-        listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0));
-        assertTrue(mController.getAmbientLux() > 0.0f);
-        assertTrue(mController.getAmbientLux() < 10000.0f);
-
-        // t = 5500
-        mClock.fastForward(increment);
-        listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0));
-        assertTrue(mController.getAmbientLux() > 0.0f);
-        assertTrue(mController.getAmbientLux() < 10000.0f);
-
-        // t = 6000
-        mClock.fastForward(increment);
-        // ambient lux goes to 0
-        listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0));
-        assertEquals(0.0f, mController.getAmbientLux(), EPSILON);
-
-        // only the values within the horizon should be kept
-        assertArrayEquals(new float[] {10000, 10000, 0, 0, 0}, mController.getLastSensorValues(),
-                EPSILON);
-        assertArrayEquals(new long[] {4000, 4500, 5000, 5500, 6000},
-                mController.getLastSensorTimestamps());
-    }
-
-    @Test
-    public void testHysteresisLevels() {
-        float[] ambientBrighteningThresholds = {50, 100};
-        float[] ambientDarkeningThresholds = {10, 20};
-        float[] ambientThresholdLevels = {0, 500};
-        float ambientDarkeningMinChangeThreshold = 3.0f;
-        float ambientBrighteningMinChangeThreshold = 1.5f;
-        HysteresisLevels hysteresisLevels = new HysteresisLevels(ambientBrighteningThresholds,
-                ambientDarkeningThresholds, ambientThresholdLevels, ambientThresholdLevels,
-                ambientDarkeningMinChangeThreshold, ambientBrighteningMinChangeThreshold);
-
-        // test low, activate minimum change thresholds.
-        assertEquals(1.5f, hysteresisLevels.getBrighteningThreshold(0.0f), EPSILON);
-        assertEquals(0f, hysteresisLevels.getDarkeningThreshold(0.0f), EPSILON);
-        assertEquals(1f, hysteresisLevels.getDarkeningThreshold(4.0f), EPSILON);
-
-        // test max
-        // epsilon is x2 here, since the next floating point value about 20,000 is 0.0019531 greater
-        assertEquals(20000f, hysteresisLevels.getBrighteningThreshold(10000.0f), EPSILON * 2);
-        assertEquals(8000f, hysteresisLevels.getDarkeningThreshold(10000.0f), EPSILON);
-
-        // test just below threshold
-        assertEquals(748.5f, hysteresisLevels.getBrighteningThreshold(499f), EPSILON);
-        assertEquals(449.1f, hysteresisLevels.getDarkeningThreshold(499f), EPSILON);
-
-        // test at (considered above) threshold
-        assertEquals(1000f, hysteresisLevels.getBrighteningThreshold(500f), EPSILON);
-        assertEquals(400f, hysteresisLevels.getDarkeningThreshold(500f), EPSILON);
-    }
-
-    @Test
-    public void testBrightnessGetsThrottled() throws Exception {
-        ArgumentCaptor<SensorEventListener> listenerCaptor =
-                ArgumentCaptor.forClass(SensorEventListener.class);
-        verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
-                eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
-        SensorEventListener listener = listenerCaptor.getValue();
-
-        // Set up system to return max brightness at 100 lux
-        final float normalizedBrightness = BRIGHTNESS_MAX_FLOAT;
-        final float lux = 100.0f;
-        when(mAmbientBrightnessThresholds.getBrighteningThreshold(lux))
-                .thenReturn(lux);
-        when(mAmbientBrightnessThresholds.getDarkeningThreshold(lux))
-                .thenReturn(lux);
-        when(mBrightnessMappingStrategy.getBrightness(eq(lux), eq(null), anyInt()))
-                .thenReturn(normalizedBrightness);
-
-        // Sensor reads 100 lux. We should get max brightness.
-        listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, (int) lux));
-        assertEquals(BRIGHTNESS_MAX_FLOAT, mController.getAutomaticScreenBrightness(), 0.0f);
-        assertEquals(BRIGHTNESS_MAX_FLOAT, mController.getRawAutomaticScreenBrightness(), 0.0f);
-
-        // Apply throttling and notify ABC (simulates DisplayPowerController#updatePowerState())
-        final float throttledBrightness = 0.123f;
-        when(mBrightnessThrottler.getBrightnessCap()).thenReturn(throttledBrightness);
-        when(mBrightnessThrottler.isThrottled()).thenReturn(true);
-        mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration= */,
-                BRIGHTNESS_MAX_FLOAT /* brightness= */, false /* userChangedBrightness= */,
-                0 /* adjustment= */, false /* userChanged= */, DisplayPowerRequest.POLICY_BRIGHT,
-                /* shouldResetShortTermModel= */ true);
-        assertEquals(throttledBrightness, mController.getAutomaticScreenBrightness(), 0.0f);
-        // The raw brightness value should not have throttling applied
-        assertEquals(BRIGHTNESS_MAX_FLOAT, mController.getRawAutomaticScreenBrightness(), 0.0f);
-
-        // Remove throttling and notify ABC again
-        when(mBrightnessThrottler.getBrightnessCap()).thenReturn(BRIGHTNESS_MAX_FLOAT);
-        when(mBrightnessThrottler.isThrottled()).thenReturn(false);
-        mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration= */,
-                BRIGHTNESS_MAX_FLOAT /* brightness= */, false /* userChangedBrightness= */,
-                0 /* adjustment= */, false /* userChanged= */, DisplayPowerRequest.POLICY_BRIGHT,
-                /* shouldResetShortTermModel= */ true);
-        assertEquals(BRIGHTNESS_MAX_FLOAT, mController.getAutomaticScreenBrightness(), 0.0f);
-        assertEquals(BRIGHTNESS_MAX_FLOAT, mController.getRawAutomaticScreenBrightness(), 0.0f);
-    }
-
-    @Test
-    public void testGetSensorReadings() throws Exception {
-        ArgumentCaptor<SensorEventListener> listenerCaptor =
-                ArgumentCaptor.forClass(SensorEventListener.class);
-        verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
-                eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
-        SensorEventListener listener = listenerCaptor.getValue();
-
-        // Choose values such that the ring buffer's capacity is extended and the buffer is pruned
-        int increment = 11;
-        int lux = 5000;
-        for (int i = 0; i < 1000; i++) {
-            lux += increment;
-            mClock.fastForward(increment);
-            listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, lux));
-        }
-
-        int valuesCount = (int) Math.ceil((double) AMBIENT_LIGHT_HORIZON_LONG / increment + 1);
-        float[] sensorValues = mController.getLastSensorValues();
-        long[] sensorTimestamps = mController.getLastSensorTimestamps();
-
-        // Only the values within the horizon should be kept
-        assertEquals(valuesCount, sensorValues.length);
-        assertEquals(valuesCount, sensorTimestamps.length);
-
-        long sensorTimestamp = mClock.now();
-        for (int i = valuesCount - 1; i >= 1; i--) {
-            assertEquals(lux, sensorValues[i], EPSILON);
-            assertEquals(sensorTimestamp, sensorTimestamps[i]);
-            lux -= increment;
-            sensorTimestamp -= increment;
-        }
-        assertEquals(lux, sensorValues[0], EPSILON);
-        assertEquals(mClock.now() - AMBIENT_LIGHT_HORIZON_LONG, sensorTimestamps[0]);
-    }
-
-    @Test
-    public void testGetSensorReadingsFullBuffer() throws Exception {
-        ArgumentCaptor<SensorEventListener> listenerCaptor =
-                ArgumentCaptor.forClass(SensorEventListener.class);
-        verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
-                eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
-        SensorEventListener listener = listenerCaptor.getValue();
-        int initialCapacity = 150;
-
-        // Choose values such that the ring buffer is pruned
-        int increment1 = 200;
-        int lux = 5000;
-        for (int i = 0; i < 20; i++) {
-            lux += increment1;
-            mClock.fastForward(increment1);
-            listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, lux));
-        }
-
-        int valuesCount = (int) Math.ceil((double) AMBIENT_LIGHT_HORIZON_LONG / increment1 + 1);
-
-        // Choose values such that the buffer becomes full
-        int increment2 = 1;
-        for (int i = 0; i < initialCapacity - valuesCount; i++) {
-            lux += increment2;
-            mClock.fastForward(increment2);
-            listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, lux));
-        }
-
-        float[] sensorValues = mController.getLastSensorValues();
-        long[] sensorTimestamps = mController.getLastSensorTimestamps();
-
-        // The buffer should be full
-        assertEquals(initialCapacity, sensorValues.length);
-        assertEquals(initialCapacity, sensorTimestamps.length);
-
-        long sensorTimestamp = mClock.now();
-        for (int i = initialCapacity - 1; i >= 1; i--) {
-            assertEquals(lux, sensorValues[i], EPSILON);
-            assertEquals(sensorTimestamp, sensorTimestamps[i]);
-
-            if (i >= valuesCount) {
-                lux -= increment2;
-                sensorTimestamp -= increment2;
-            } else {
-                lux -= increment1;
-                sensorTimestamp -= increment1;
-            }
-        }
-        assertEquals(lux, sensorValues[0], EPSILON);
-        assertEquals(mClock.now() - AMBIENT_LIGHT_HORIZON_LONG, sensorTimestamps[0]);
-    }
-
-    @Test
-    public void testResetShortTermModelWhenConfigChanges() {
-        when(mBrightnessMappingStrategy.isForIdleMode()).thenReturn(false);
-        when(mBrightnessMappingStrategy.setBrightnessConfiguration(any())).thenReturn(true);
-
-        mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration= */,
-                BRIGHTNESS_MAX_FLOAT /* brightness= */, false /* userChangedBrightness= */,
-                0 /* adjustment= */, false /* userChanged= */, DisplayPowerRequest.POLICY_BRIGHT,
-                /* shouldResetShortTermModel= */ false);
-        verify(mBrightnessMappingStrategy, never()).clearUserDataPoints();
-
-        mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration= */,
-                BRIGHTNESS_MAX_FLOAT /* brightness= */, false /* userChangedBrightness= */,
-                0 /* adjustment= */, false /* userChanged= */, DisplayPowerRequest.POLICY_BRIGHT,
-                /* shouldResetShortTermModel= */ true);
-        verify(mBrightnessMappingStrategy).clearUserDataPoints();
-    }
-
-    @Test
-    public void testUseProvidedShortTermModel() {
-        verify(mBrightnessMappingStrategy, never()).addUserDataPoint(anyFloat(), anyFloat());
-
-        float userLux = 1000;
-        float userBrightness = 0.3f;
-        setupController(mLightSensor, userLux, userBrightness);
-        verify(mBrightnessMappingStrategy).addUserDataPoint(userLux, userBrightness);
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java b/services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java
deleted file mode 100644
index 5f81869..0000000
--- a/services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java
+++ /dev/null
@@ -1,710 +0,0 @@
-/*
- * Copyright 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.display;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.anyFloat;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.hardware.display.BrightnessConfiguration;
-import android.os.PowerManager;
-import android.util.MathUtils;
-import android.util.Spline;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.server.display.whitebalance.DisplayWhiteBalanceController;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-
-import java.util.Arrays;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class BrightnessMappingStrategyTest {
-
-    private static final float[] LUX_LEVELS = {
-        0,
-        5,
-        20,
-        40,
-        100,
-        325,
-        600,
-        1250,
-        2200,
-        4000,
-        5000
-    };
-
-    private static final int[] LUX_LEVELS_IDLE = {
-        0,
-        10,
-        40,
-        80,
-        200,
-        655,
-        1200,
-        2500,
-        4400,
-        8000,
-        10000
-    };
-
-    private static final float[] DISPLAY_LEVELS_NITS = {
-        13.25f,
-        54.0f,
-        78.85f,
-        105.02f,
-        132.7f,
-        170.12f,
-        212.1f,
-        265.2f,
-        335.8f,
-        415.2f,
-        478.5f,
-    };
-
-    private static final float[] DISPLAY_LEVELS_NITS_IDLE = {
-        23.25f,
-        64.0f,
-        88.85f,
-        115.02f,
-        142.7f,
-        180.12f,
-        222.1f,
-        275.2f,
-        345.8f,
-        425.2f,
-        468.5f,
-    };
-
-    private static final int[] DISPLAY_LEVELS_BACKLIGHT = {
-        9,
-        30,
-        45,
-        62,
-        78,
-        96,
-        119,
-        146,
-        178,
-        221,
-        255
-    };
-
-    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 };
-
-    private static final float[] EMPTY_FLOAT_ARRAY = new float[0];
-    private static final int[] EMPTY_INT_ARRAY = new int[0];
-
-    private static final float MAXIMUM_GAMMA = 3.0f;
-
-    private static final float[] GAMMA_CORRECTION_LUX = {
-        0,
-        100,
-        1000,
-        2500,
-        4000,
-        4900,
-        5000
-    };
-    private static final float[] GAMMA_CORRECTION_NITS = {
-        1.0f,
-        10.55f,
-        96.5f,
-        239.75f,
-        383.0f,
-        468.95f,
-        478.5f,
-    };
-    private static final Spline GAMMA_CORRECTION_SPLINE = Spline.createSpline(
-            new float[] { 0.0f, 100.0f, 1000.0f, 2500.0f, 4000.0f, 4900.0f, 5000.0f },
-            new float[] { 0.0475f, 0.0475f, 0.2225f, 0.5140f, 0.8056f, 0.9805f, 1.0f });
-
-    private static final float TOLERANCE = 0.0001f;
-
-    @Mock
-    DisplayWhiteBalanceController mMockDwbc;
-
-    @Test
-    public void testSimpleStrategyMappingAtControlPoints() {
-        Resources res = createResources(DISPLAY_LEVELS_BACKLIGHT);
-        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]);
-            assertEquals(expectedLevel,
-                    simple.getBrightness(LUX_LEVELS[i]), 0.0001f /*tolerance*/);
-        }
-    }
-
-    @Test
-    public void testSimpleStrategyMappingBetweenControlPoints() {
-        Resources res = createResources(DISPLAY_LEVELS_BACKLIGHT);
-        DisplayDeviceConfig ddc = createDdc();
-        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 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]);
-        }
-    }
-
-    @Test
-    public void testSimpleStrategyIgnoresNewConfiguration() {
-        Resources res = createResources(DISPLAY_LEVELS_BACKLIGHT);
-        DisplayDeviceConfig ddc = createDdc();
-        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
-
-        final float[] lux = { 0f, 1f };
-        final float[] nits = { 0, PowerManager.BRIGHTNESS_ON };
-
-        BrightnessConfiguration config = new BrightnessConfiguration.Builder(lux, nits)
-                .build();
-        strategy.setBrightnessConfiguration(config);
-        assertNotEquals(1.0f, strategy.getBrightness(1f), 0.0001f /*tolerance*/);
-    }
-
-    @Test
-    public void testSimpleStrategyIgnoresNullConfiguration() {
-        Resources res = createResources(DISPLAY_LEVELS_BACKLIGHT);
-        DisplayDeviceConfig ddc = createDdc();
-        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
-
-        strategy.setBrightnessConfiguration(null);
-        final int N = DISPLAY_LEVELS_BACKLIGHT.length;
-        final float expectedBrightness =
-                (float) DISPLAY_LEVELS_BACKLIGHT[N - 1] / PowerManager.BRIGHTNESS_ON;
-        assertEquals(expectedBrightness,
-                strategy.getBrightness(LUX_LEVELS[N - 1]), 0.0001f /*tolerance*/);
-    }
-
-    @Test
-    public void testPhysicalStrategyMappingAtControlPoints() {
-        Resources res = createResources(EMPTY_INT_ARRAY);
-        DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS,
-                DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT,
-                LUX_LEVELS, DISPLAY_LEVELS_NITS);
-        BrightnessMappingStrategy physical = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
-        assertNotNull("BrightnessMappingStrategy should not be null", physical);
-        for (int i = 0; i < LUX_LEVELS.length; i++) {
-            final float expectedLevel = MathUtils.map(DISPLAY_RANGE_NITS[0], DISPLAY_RANGE_NITS[1],
-                    DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT[0],
-                    DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT[1],
-                    DISPLAY_LEVELS_NITS[i]);
-            assertEquals(expectedLevel,
-                    physical.getBrightness(LUX_LEVELS[i]),
-                    0.0001f /*tolerance*/);
-        }
-    }
-
-    @Test
-    public void testPhysicalStrategyMappingBetweenControlPoints() {
-        Resources res = createResources(EMPTY_INT_ARRAY);
-        DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS, BACKLIGHT_RANGE_ZERO_TO_ONE,
-                LUX_LEVELS, DISPLAY_LEVELS_NITS);
-        BrightnessMappingStrategy physical = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
-        assertNotNull("BrightnessMappingStrategy should not be null", physical);
-        Spline brightnessToNits =
-                Spline.createSpline(BACKLIGHT_RANGE_ZERO_TO_ONE, DISPLAY_RANGE_NITS);
-        for (int i = 1; i < LUX_LEVELS.length; i++) {
-            final float lux = (LUX_LEVELS[i - 1] + LUX_LEVELS[i]) / 2.0f;
-            final float brightness = physical.getBrightness(lux);
-            final float nits = brightnessToNits.interpolate(brightness);
-            assertTrue("Desired brightness should be between adjacent control points: " + nits,
-                    nits > DISPLAY_LEVELS_NITS[i - 1] && nits < DISPLAY_LEVELS_NITS[i]);
-        }
-    }
-
-    @Test
-    public void testPhysicalStrategyUsesNewConfigurations() {
-        Resources res = createResources(EMPTY_INT_ARRAY);
-        DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS,
-                DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, LUX_LEVELS, DISPLAY_LEVELS_NITS);
-        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
-
-        final float[] lux = {0f, 1f};
-        final float[] nits = {
-                DISPLAY_RANGE_NITS[0],
-                DISPLAY_RANGE_NITS[DISPLAY_RANGE_NITS.length - 1]
-        };
-
-        BrightnessConfiguration config = new BrightnessConfiguration.Builder(lux, nits)
-                .build();
-        strategy.setBrightnessConfiguration(config);
-        assertEquals(1.0f, strategy.getBrightness(1f), 0.0001f /*tolerance*/);
-
-        // Check that null returns us to the default configuration.
-        strategy.setBrightnessConfiguration(null);
-        final int N = DISPLAY_LEVELS_NITS.length;
-        final float expectedBrightness = DISPLAY_LEVELS_NITS[N - 1] / DISPLAY_RANGE_NITS[1];
-        assertEquals(expectedBrightness,
-                strategy.getBrightness(LUX_LEVELS[N - 1]), 0.0001f /*tolerance*/);
-    }
-
-    @Test
-    public void testPhysicalStrategyRecalculateSplines() {
-        Resources res = createResources(EMPTY_INT_ARRAY);
-        DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS,
-                DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, LUX_LEVELS, DISPLAY_LEVELS_NITS);
-        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
-        float[] adjustedNits50p = new float[DISPLAY_RANGE_NITS.length];
-        for (int i = 0; i < DISPLAY_RANGE_NITS.length; i++) {
-            adjustedNits50p[i] = DISPLAY_RANGE_NITS[i] * 0.5f;
-        }
-
-        // Default
-        assertEquals(DISPLAY_RANGE_NITS[0], strategy.convertToNits(BACKLIGHT_RANGE_ZERO_TO_ONE[0]),
-                TOLERANCE);
-        assertEquals(DISPLAY_RANGE_NITS[1], strategy.convertToNits(BACKLIGHT_RANGE_ZERO_TO_ONE[1]),
-                TOLERANCE);
-        assertEquals(DISPLAY_RANGE_NITS[0],
-                strategy.convertToAdjustedNits(BACKLIGHT_RANGE_ZERO_TO_ONE[0]), TOLERANCE);
-        assertEquals(DISPLAY_RANGE_NITS[1],
-                strategy.convertToAdjustedNits(BACKLIGHT_RANGE_ZERO_TO_ONE[1]), TOLERANCE);
-
-        // Adjustment is turned on
-        strategy.recalculateSplines(true, adjustedNits50p);
-        assertEquals(DISPLAY_RANGE_NITS[0], strategy.convertToNits(BACKLIGHT_RANGE_ZERO_TO_ONE[0]),
-                TOLERANCE);
-        assertEquals(DISPLAY_RANGE_NITS[1], strategy.convertToNits(BACKLIGHT_RANGE_ZERO_TO_ONE[1]),
-                TOLERANCE);
-        assertEquals(DISPLAY_RANGE_NITS[0] / 2,
-                strategy.convertToAdjustedNits(BACKLIGHT_RANGE_ZERO_TO_ONE[0]), TOLERANCE);
-        assertEquals(DISPLAY_RANGE_NITS[1] / 2,
-                strategy.convertToAdjustedNits(BACKLIGHT_RANGE_ZERO_TO_ONE[1]), TOLERANCE);
-
-        // Adjustment is turned off
-        strategy.recalculateSplines(false, adjustedNits50p);
-        assertEquals(DISPLAY_RANGE_NITS[0], strategy.convertToNits(BACKLIGHT_RANGE_ZERO_TO_ONE[0]),
-                TOLERANCE);
-        assertEquals(DISPLAY_RANGE_NITS[1], strategy.convertToNits(BACKLIGHT_RANGE_ZERO_TO_ONE[1]),
-                TOLERANCE);
-        assertEquals(DISPLAY_RANGE_NITS[0],
-                strategy.convertToAdjustedNits(BACKLIGHT_RANGE_ZERO_TO_ONE[0]), TOLERANCE);
-        assertEquals(DISPLAY_RANGE_NITS[1],
-                strategy.convertToAdjustedNits(BACKLIGHT_RANGE_ZERO_TO_ONE[1]), TOLERANCE);
-    }
-
-    @Test
-    public void testDefaultStrategyIsPhysical() {
-        Resources res = createResources(DISPLAY_LEVELS_BACKLIGHT);
-        DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS,
-                DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, LUX_LEVELS, DISPLAY_LEVELS_NITS);
-        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
-        assertTrue(strategy instanceof BrightnessMappingStrategy.PhysicalMappingStrategy);
-    }
-
-    @Test
-    public void testNonStrictlyIncreasingLuxLevelsFails() {
-        final float[] lux = Arrays.copyOf(LUX_LEVELS, LUX_LEVELS.length);
-        final int idx = lux.length / 2;
-        float tmp = lux[idx];
-        lux[idx] = lux[idx + 1];
-        lux[idx + 1] = tmp;
-        Resources res = createResources(EMPTY_INT_ARRAY);
-        DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS,
-                DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, lux, DISPLAY_LEVELS_NITS);
-        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
-        assertNull(strategy);
-
-        // And make sure we get the same result even if it's monotone but not increasing.
-        lux[idx] = lux[idx + 1];
-        ddc = createDdc(DISPLAY_RANGE_NITS, DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, lux,
-                DISPLAY_LEVELS_NITS);
-        strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
-        assertNull(strategy);
-    }
-
-    @Test
-    public void testDifferentNumberOfControlPointValuesFails() {
-        //Extra lux level
-        final float[] lux = Arrays.copyOf(LUX_LEVELS, LUX_LEVELS.length + 1);
-        // Make sure it's strictly increasing so that the only failure is the differing array
-        // lengths
-        lux[lux.length - 1] = lux[lux.length - 2] + 1;
-        Resources res = createResources(EMPTY_INT_ARRAY);
-        DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS,
-                DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, lux, DISPLAY_LEVELS_NITS);
-        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
-        assertNull(strategy);
-
-        res = createResources(DISPLAY_LEVELS_BACKLIGHT);
-        strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
-        assertNull(strategy);
-
-        // Extra backlight level
-        final int[] backlight = Arrays.copyOf(
-                DISPLAY_LEVELS_BACKLIGHT, DISPLAY_LEVELS_BACKLIGHT.length + 1);
-        backlight[backlight.length - 1] = backlight[backlight.length - 2] + 1;
-        res = createResources(backlight);
-        ddc = createDdc(DISPLAY_RANGE_NITS,
-                DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, LUX_LEVELS, EMPTY_FLOAT_ARRAY);
-        strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
-        assertNull(strategy);
-
-        // Extra nits level
-        final float[] nits = Arrays.copyOf(DISPLAY_RANGE_NITS, DISPLAY_LEVELS_NITS.length + 1);
-        nits[nits.length - 1] = nits[nits.length - 2] + 1;
-        res = createResources(EMPTY_INT_ARRAY);
-        ddc = createDdc(DISPLAY_RANGE_NITS,
-                DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, LUX_LEVELS, nits);
-        strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
-        assertNull(strategy);
-    }
-
-    @Test
-    public void testPhysicalStrategyRequiresNitsMapping() {
-        Resources res = createResources(EMPTY_INT_ARRAY /*brightnessLevelsBacklight*/);
-        DisplayDeviceConfig ddc = createDdc(EMPTY_FLOAT_ARRAY /*nitsRange*/);
-        BrightnessMappingStrategy physical = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
-        assertNull(physical);
-
-        res = createResources(EMPTY_INT_ARRAY /*brightnessLevelsBacklight*/);
-        physical = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
-        assertNull(physical);
-
-        res = createResources(EMPTY_INT_ARRAY /*brightnessLevelsBacklight*/);
-        physical = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
-        assertNull(physical);
-    }
-
-    @Test
-    public void testStrategiesAdaptToUserDataPoint() {
-        Resources res = createResources(EMPTY_INT_ARRAY /*brightnessLevelsBacklight*/);
-        DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS, BACKLIGHT_RANGE_ZERO_TO_ONE,
-                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);
-        assertStrategyAdaptsToUserDataPoints(BrightnessMappingStrategy.create(res, ddc, mMockDwbc));
-    }
-
-    @Test
-    public void testIdleModeConfigLoadsCorrectly() {
-        Resources res = createResourcesIdle(LUX_LEVELS_IDLE, DISPLAY_LEVELS_NITS_IDLE);
-        DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS, BACKLIGHT_RANGE_ZERO_TO_ONE);
-
-        // Create an idle mode bms
-        // This will fail if it tries to fetch the wrong configuration.
-        BrightnessMappingStrategy bms = BrightnessMappingStrategy.createForIdleMode(res, ddc,
-                mMockDwbc);
-        assertNotNull("BrightnessMappingStrategy should not be null", bms);
-
-        // Ensure that the config is the one we set
-        // Ensure that the lux -> brightness -> nits path works. ()
-        for (int i = 0; i < DISPLAY_LEVELS_NITS_IDLE.length; i++) {
-            assertEquals(LUX_LEVELS_IDLE[i], bms.getDefaultConfig().getCurve().first[i], TOLERANCE);
-            assertEquals(DISPLAY_LEVELS_NITS_IDLE[i], bms.getDefaultConfig().getCurve().second[i],
-                    TOLERANCE);
-            assertEquals(bms.convertToNits(bms.getBrightness(LUX_LEVELS_IDLE[i])),
-                    DISPLAY_LEVELS_NITS_IDLE[i], TOLERANCE);
-        }
-    }
-
-    private static void assertStrategyAdaptsToUserDataPoints(BrightnessMappingStrategy strategy) {
-        // Save out all of the initial brightness data for comparison after reset.
-        float[] initialBrightnessLevels = new float[LUX_LEVELS.length];
-        for (int i = 0; i < LUX_LEVELS.length; i++) {
-            initialBrightnessLevels[i] = strategy.getBrightness(LUX_LEVELS[i]);
-        }
-
-        // Add a data point in the middle of the curve where the user has set the brightness max
-        final int idx = LUX_LEVELS.length / 2;
-        strategy.addUserDataPoint(LUX_LEVELS[idx], 1.0f);
-
-        // Then make sure that all control points after the middle lux level are also set to max...
-        for (int i = idx; i < LUX_LEVELS.length; i++) {
-            assertEquals(strategy.getBrightness(LUX_LEVELS[idx]), 1.0, 0.0001f /*tolerance*/);
-        }
-
-        // ...and that all control points before the middle lux level are strictly less than the
-        // previous one still.
-        float prevBrightness = strategy.getBrightness(LUX_LEVELS[idx]);
-        for (int i = idx - 1; i >= 0; i--) {
-            float brightness = strategy.getBrightness(LUX_LEVELS[i]);
-            assertTrue("Brightness levels must be monotonic after adapting to user data",
-                    prevBrightness >= brightness);
-            prevBrightness = brightness;
-        }
-
-        // Now reset the curve and make sure we go back to the initial brightness levels recorded
-        // before adding the user data point.
-        strategy.clearUserDataPoints();
-        for (int i = 0; i < LUX_LEVELS.length; i++) {
-            assertEquals(initialBrightnessLevels[i], strategy.getBrightness(LUX_LEVELS[i]),
-                    0.0001f /*tolerance*/);
-        }
-
-        // Now set the middle of the lux range to something just above the minimum.
-        float minBrightness = strategy.getBrightness(LUX_LEVELS[0]);
-        strategy.addUserDataPoint(LUX_LEVELS[idx], minBrightness + 0.0001f);
-
-        // Then make sure the curve is still monotonic.
-        prevBrightness = 0f;
-        for (float lux : LUX_LEVELS) {
-            float brightness = strategy.getBrightness(lux);
-            assertTrue("Brightness levels must be monotonic after adapting to user data",
-                    prevBrightness <= brightness);
-            prevBrightness = brightness;
-        }
-
-        // And that the lowest lux level still gives the absolute minimum brightness. This should
-        // be true assuming that there are more than two lux levels in the curve since we picked a
-        // brightness just barely above the minimum for the middle of the curve.
-        minBrightness = (float) MathUtils.pow(minBrightness, MAXIMUM_GAMMA); // Gamma correction.
-        assertEquals(minBrightness, strategy.getBrightness(LUX_LEVELS[0]), 0.0001f /*tolerance*/);
-    }
-
-    private Resources createResources(int[] brightnessLevelsBacklight) {
-        return createResources(brightnessLevelsBacklight, EMPTY_INT_ARRAY, EMPTY_FLOAT_ARRAY);
-    }
-
-    private Resources createResourcesIdle(int[] luxLevelsIdle, float[] brightnessLevelsNitsIdle) {
-        return createResources(EMPTY_INT_ARRAY,
-                luxLevelsIdle, brightnessLevelsNitsIdle);
-    }
-
-    private Resources createResources(int[] brightnessLevelsBacklight, int[] luxLevelsIdle,
-            float[] brightnessLevelsNitsIdle) {
-
-        Resources mockResources = mock(Resources.class);
-        if (luxLevelsIdle.length > 0) {
-            int[] luxLevelsIdleResource = Arrays.copyOfRange(luxLevelsIdle, 1,
-                    luxLevelsIdle.length);
-            when(mockResources.getIntArray(
-                    com.android.internal.R.array.config_autoBrightnessLevelsIdle))
-                    .thenReturn(luxLevelsIdleResource);
-        }
-
-        when(mockResources.getIntArray(
-                com.android.internal.R.array.config_autoBrightnessLcdBacklightValues))
-                .thenReturn(brightnessLevelsBacklight);
-
-        TypedArray mockBrightnessLevelNitsIdle = createFloatTypedArray(brightnessLevelsNitsIdle);
-        when(mockResources.obtainTypedArray(
-                com.android.internal.R.array.config_autoBrightnessDisplayValuesNitsIdle))
-                .thenReturn(mockBrightnessLevelNitsIdle);
-
-        when(mockResources.getInteger(
-                com.android.internal.R.integer.config_screenBrightnessSettingMinimum))
-                .thenReturn(1);
-        when(mockResources.getInteger(
-                com.android.internal.R.integer.config_screenBrightnessSettingMaximum))
-                .thenReturn(255);
-        when(mockResources.getFraction(
-                com.android.internal.R.fraction.config_autoBrightnessAdjustmentMaxGamma, 1, 1))
-                .thenReturn(MAXIMUM_GAMMA);
-        return mockResources;
-    }
-
-    private DisplayDeviceConfig createDdc() {
-        return createDdc(DISPLAY_RANGE_NITS);
-    }
-
-    private DisplayDeviceConfig createDdc(float[] nitsArray) {
-        return createDdc(nitsArray, DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT);
-    }
-
-    private DisplayDeviceConfig createDdc(float[] nitsArray, float[] backlightArray) {
-        DisplayDeviceConfig mockDdc = mock(DisplayDeviceConfig.class);
-        when(mockDdc.getNits()).thenReturn(nitsArray);
-        when(mockDdc.getBrightness()).thenReturn(backlightArray);
-        when(mockDdc.getAutoBrightnessBrighteningLevelsLux()).thenReturn(LUX_LEVELS);
-        when(mockDdc.getAutoBrightnessBrighteningLevelsNits()).thenReturn(EMPTY_FLOAT_ARRAY);
-        return mockDdc;
-    }
-
-    private DisplayDeviceConfig createDdc(float[] nitsArray, float[] backlightArray,
-            float[] luxLevelsFloat, float[] brightnessLevelsNits) {
-        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);
-        return mockDdc;
-    }
-
-    private TypedArray createFloatTypedArray(float[] vals) {
-        TypedArray mockArray = mock(TypedArray.class);
-        when(mockArray.length()).thenAnswer(invocation -> {
-            return vals.length;
-        });
-        when(mockArray.getFloat(anyInt(), anyFloat())).thenAnswer(invocation -> {
-            final float def = (float) invocation.getArguments()[1];
-            if (vals == null) {
-                return def;
-            }
-            int idx = (int) invocation.getArguments()[0];
-            if (idx >= 0 && idx < vals.length) {
-                return vals[idx];
-            } else {
-                return def;
-            }
-        });
-        return mockArray;
-    }
-
-    // Gamma correction tests.
-    // x0 = 100   y0 = ~0.01
-    // x1 = 1000  y1 = ~0.20
-    // x2 = 2500  y2 = ~0.50
-    // x3 = 4000  y3 = ~0.80
-    // x4 = 4900  y4 = ~0.99
-
-    @Test
-    public void testGammaCorrectionLowChangeAtCenter() {
-        // If we set a user data point at (x2, y2^0.5), i.e. gamma = 0.5, it should bump the rest
-        // of the spline accordingly.
-        final int x1 = 1000;
-        final int x2 = 2500;
-        final int x3 = 4000;
-        final float y1 = GAMMA_CORRECTION_SPLINE.interpolate(x1);
-        final float y2 = GAMMA_CORRECTION_SPLINE.interpolate(x2);
-        final float y3 = GAMMA_CORRECTION_SPLINE.interpolate(x3);
-
-        Resources resources = createResources(EMPTY_INT_ARRAY);
-        DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS,
-                DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, GAMMA_CORRECTION_LUX,
-                GAMMA_CORRECTION_NITS);
-        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(resources, ddc,
-                mMockDwbc);
-        // Let's start with a validity check:
-        assertEquals(y1, strategy.getBrightness(x1), 0.0001f /* tolerance */);
-        assertEquals(y2, strategy.getBrightness(x2), 0.0001f /* tolerance */);
-        assertEquals(y3, strategy.getBrightness(x3), 0.0001f /* tolerance */);
-        // OK, let's roll:
-        float gamma = 0.5f;
-        strategy.addUserDataPoint(x2, (float) MathUtils.pow(y2, gamma));
-        assertEquals(MathUtils.pow(y1, gamma), strategy.getBrightness(x1), 0.0001f /* tolerance */);
-        assertEquals(MathUtils.pow(y2, gamma), strategy.getBrightness(x2), 0.0001f /* tolerance */);
-        assertEquals(MathUtils.pow(y3, gamma), strategy.getBrightness(x3), 0.0001f /* tolerance */);
-        // The adjustment should be +0.6308 (manual calculation).
-        assertEquals(+0.6308f, strategy.getAutoBrightnessAdjustment(), 0.0001f /* tolerance */);
-    }
-
-    @Test
-    public void testGammaCorrectionHighChangeAtCenter() {
-        // This time we set a user data point at (x2, y2^0.25), i.e. gamma = 0.3 (the minimum),
-        // which should bump the rest of the spline accordingly, and further correct x2 to hit
-        // y2^0.25 (not y2^0.3).
-        final int x1 = 1000;
-        final int x2 = 2500;
-        final int x3 = 4000;
-        final float y1 = GAMMA_CORRECTION_SPLINE.interpolate(x1);
-        final float y2 = GAMMA_CORRECTION_SPLINE.interpolate(x2);
-        final float y3 = GAMMA_CORRECTION_SPLINE.interpolate(x3);
-        Resources resources = createResources(EMPTY_INT_ARRAY);
-        DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS,
-                DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, GAMMA_CORRECTION_LUX,
-                GAMMA_CORRECTION_NITS);
-        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(resources, ddc,
-                mMockDwbc);
-        // Validity check:
-        assertEquals(y1, strategy.getBrightness(x1), 0.0001f /* tolerance */);
-        assertEquals(y2, strategy.getBrightness(x2), 0.0001f /* tolerance */);
-        assertEquals(y3, strategy.getBrightness(x3), 0.0001f /* tolerance */);
-        // Let's roll:
-        float gamma = 0.25f;
-        final float minGamma = 1.0f / MAXIMUM_GAMMA;
-        strategy.addUserDataPoint(x2, (float) MathUtils.pow(y2, gamma));
-        assertEquals(MathUtils.pow(y1, minGamma),
-                strategy.getBrightness(x1), 0.0001f /* tolerance */);
-        assertEquals(MathUtils.pow(y2, gamma),
-                strategy.getBrightness(x2), 0.0001f /* tolerance */);
-        assertEquals(MathUtils.pow(y3, minGamma),
-                strategy.getBrightness(x3), 0.0001f /* tolerance */);
-        // The adjustment should be +1.0 (maximum adjustment).
-        assertEquals(+1.0f, strategy.getAutoBrightnessAdjustment(), 0.0001f /* tolerance */);
-    }
-
-    @Test
-    public void testGammaCorrectionExtremeChangeAtCenter() {
-        // Extreme changes (e.g. setting brightness to 0.0 or 1.0) can't be gamma corrected, so we
-        // just make sure the adjustment reflects the change.
-        Resources resources = createResources(EMPTY_INT_ARRAY);
-        DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS,
-                DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, GAMMA_CORRECTION_LUX,
-                GAMMA_CORRECTION_NITS);
-        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(resources, ddc,
-                mMockDwbc);
-        assertEquals(0.0f, strategy.getAutoBrightnessAdjustment(), 0.0001f /* tolerance */);
-        strategy.addUserDataPoint(2500, 1.0f);
-        assertEquals(+1.0f, strategy.getAutoBrightnessAdjustment(), 0.0001f /* tolerance */);
-        strategy.addUserDataPoint(2500, 0.0f);
-        assertEquals(-1.0f, strategy.getAutoBrightnessAdjustment(), 0.0001f /* tolerance */);
-    }
-
-    @Test
-    public void testGammaCorrectionChangeAtEdges() {
-        // The algorithm behaves differently at the edges, because gamma correction there tends to
-        // be extreme. If we add a user data point at (x0, y0+0.3), the adjustment should be 0.3,
-        // resulting in a gamma of 3**-0.6 = ~0.52.
-        final int x0 = 100;
-        final int x2 = 2500;
-        final int x4 = 4900;
-        final float y0 = GAMMA_CORRECTION_SPLINE.interpolate(x0);
-        final float y2 = GAMMA_CORRECTION_SPLINE.interpolate(x2);
-        final float y4 = GAMMA_CORRECTION_SPLINE.interpolate(x4);
-        Resources resources = createResources(EMPTY_INT_ARRAY);
-        DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS,
-                DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, GAMMA_CORRECTION_LUX,
-                GAMMA_CORRECTION_NITS);
-        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(resources, ddc,
-                mMockDwbc);
-        // Validity, as per tradition:
-        assertEquals(y0, strategy.getBrightness(x0), 0.0001f /* tolerance */);
-        assertEquals(y2, strategy.getBrightness(x2), 0.0001f /* tolerance */);
-        assertEquals(y4, strategy.getBrightness(x4), 0.0001f /* tolerance */);
-        // Rollin':
-        float adjustment = 0.3f;
-        float gamma = (float) MathUtils.pow(MAXIMUM_GAMMA, -adjustment);
-        strategy.addUserDataPoint(x0, y0 + adjustment);
-        assertEquals(y0 + adjustment, strategy.getBrightness(x0), 0.0001f /* tolerance */);
-        assertEquals(MathUtils.pow(y2, gamma), strategy.getBrightness(x2), 0.0001f /* tolerance */);
-        assertEquals(MathUtils.pow(y4, gamma), strategy.getBrightness(x4), 0.0001f /* tolerance */);
-        assertEquals(adjustment, strategy.getAutoBrightnessAdjustment(), 0.0001f /* tolerance */);
-        // Similarly, if we set a user data point at (x4, 1.0), the adjustment should be 1 - y4.
-        adjustment = 1.0f - y4;
-        gamma = (float) MathUtils.pow(MAXIMUM_GAMMA, -adjustment);
-        strategy.addUserDataPoint(x4, 1.0f);
-        assertEquals(MathUtils.pow(y0, gamma), strategy.getBrightness(x0), 0.0001f /* tolerance */);
-        assertEquals(MathUtils.pow(y2, gamma), strategy.getBrightness(x2), 0.0001f /* tolerance */);
-        assertEquals(1.0f, strategy.getBrightness(x4), 0.0001f /* tolerance */);
-        assertEquals(adjustment, strategy.getAutoBrightnessAdjustment(), 0.0001f /* tolerance */);
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/display/BrightnessThrottlerTest.java b/services/tests/servicestests/src/com/android/server/display/BrightnessThrottlerTest.java
deleted file mode 100644
index 46956d7..0000000
--- a/services/tests/servicestests/src/com/android/server/display/BrightnessThrottlerTest.java
+++ /dev/null
@@ -1,493 +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.server.display;
-
-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.assertTrue;
-import static org.mockito.Mockito.eq;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.hardware.display.BrightnessInfo;
-import android.os.Handler;
-import android.os.IThermalEventListener;
-import android.os.IThermalService;
-import android.os.Message;
-import android.os.PowerManager;
-import android.os.Temperature;
-import android.os.Temperature.ThrottlingStatus;
-import android.os.test.TestLooper;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.internal.os.BackgroundThread;
-import com.android.server.display.BrightnessThrottler.Injector;
-import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData;
-import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel;
-import com.android.server.display.mode.DisplayModeDirectorTest;
-
-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.MockitoAnnotations;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class BrightnessThrottlerTest {
-    private static final float EPSILON = 0.000001f;
-
-    private Handler mHandler;
-    private TestLooper mTestLooper;
-
-    @Mock IThermalService mThermalServiceMock;
-    @Mock Injector mInjectorMock;
-
-    DisplayModeDirectorTest.FakeDeviceConfig mDeviceConfigFake;
-
-    @Captor ArgumentCaptor<IThermalEventListener> mThermalEventListenerCaptor;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-        when(mInjectorMock.getThermalService()).thenReturn(mThermalServiceMock);
-        mTestLooper = new TestLooper();
-        mHandler = new Handler(mTestLooper.getLooper(), new Handler.Callback() {
-            @Override
-            public boolean handleMessage(Message msg) {
-                return true;
-            }
-        });
-        mDeviceConfigFake = new DisplayModeDirectorTest.FakeDeviceConfig();
-        when(mInjectorMock.getDeviceConfig()).thenReturn(mDeviceConfigFake);
-
-    }
-
-    /////////////////
-    // Test Methods
-    /////////////////
-
-    @Test
-    public void testThermalBrightnessThrottlingData() {
-        List<ThrottlingLevel> singleLevel = new ArrayList<>();
-        singleLevel.add(new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 0.25f));
-
-        List<ThrottlingLevel> validLevels = new ArrayList<>();
-        validLevels.add(new ThrottlingLevel(PowerManager.THERMAL_STATUS_MODERATE, 0.62f));
-        validLevels.add(new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 0.25f));
-
-        List<ThrottlingLevel> unsortedThermalLevels = new ArrayList<>();
-        unsortedThermalLevels.add(new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 0.62f));
-        unsortedThermalLevels.add(new ThrottlingLevel(PowerManager.THERMAL_STATUS_MODERATE, 0.25f));
-
-        List<ThrottlingLevel> unsortedBrightnessLevels = new ArrayList<>();
-        unsortedBrightnessLevels.add(
-                new ThrottlingLevel(PowerManager.THERMAL_STATUS_MODERATE, 0.25f));
-        unsortedBrightnessLevels.add(
-                new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 0.62f));
-
-        List<ThrottlingLevel> unsortedLevels = new ArrayList<>();
-        unsortedLevels.add(new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 0.25f));
-        unsortedLevels.add(new ThrottlingLevel(PowerManager.THERMAL_STATUS_MODERATE, 0.62f));
-
-        List<ThrottlingLevel> invalidLevel = new ArrayList<>();
-        invalidLevel.add(new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL,
-                PowerManager.BRIGHTNESS_MAX + EPSILON));
-
-        // Test invalid data
-        ThermalBrightnessThrottlingData data;
-        data = ThermalBrightnessThrottlingData.create((List<ThrottlingLevel>) null);
-        assertEquals(data, null);
-        data = ThermalBrightnessThrottlingData.create(new ArrayList<ThrottlingLevel>());
-        assertEquals(data, null);
-        data = ThermalBrightnessThrottlingData.create(unsortedThermalLevels);
-        assertEquals(data, null);
-        data = ThermalBrightnessThrottlingData.create(unsortedBrightnessLevels);
-        assertEquals(data, null);
-        data = ThermalBrightnessThrottlingData.create(unsortedLevels);
-        assertEquals(data, null);
-        data = ThermalBrightnessThrottlingData.create(invalidLevel);
-        assertEquals(data, null);
-
-        // Test valid data
-        data = ThermalBrightnessThrottlingData.create(singleLevel);
-        assertNotEquals(data, null);
-        assertThrottlingLevelsEquals(singleLevel, data.throttlingLevels);
-
-        data = ThermalBrightnessThrottlingData.create(validLevels);
-        assertNotEquals(data, null);
-        assertThrottlingLevelsEquals(validLevels, data.throttlingLevels);
-    }
-
-    @Test
-    public void testThermalThrottlingUnsupported() {
-        final BrightnessThrottler throttler = createThrottlerUnsupported();
-        assertFalse(throttler.deviceSupportsThrottling());
-
-        // Thermal listener shouldn't be registered if throttling is unsupported
-        verify(mInjectorMock, never()).getThermalService();
-
-        // Ensure that brightness is uncapped when the device doesn't support throttling
-        assertEquals(PowerManager.BRIGHTNESS_MAX, throttler.getBrightnessCap(), 0f);
-    }
-
-    @Test
-    public void testThermalThrottlingSingleLevel() throws Exception {
-        final ThrottlingLevel level = new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL,
-            0.25f);
-
-        List<ThrottlingLevel> levels = new ArrayList<>();
-        levels.add(level);
-        final ThermalBrightnessThrottlingData data = ThermalBrightnessThrottlingData.create(levels);
-        final BrightnessThrottler throttler = createThrottlerSupported(data);
-        assertTrue(throttler.deviceSupportsThrottling());
-
-        verify(mThermalServiceMock).registerThermalEventListenerWithType(
-                mThermalEventListenerCaptor.capture(), eq(Temperature.TYPE_SKIN));
-        final IThermalEventListener listener = mThermalEventListenerCaptor.getValue();
-
-        // Set status too low to trigger throttling
-        listener.notifyThrottling(getSkinTemp(level.thermalStatus - 1));
-        mTestLooper.dispatchAll();
-        assertEquals(PowerManager.BRIGHTNESS_MAX, throttler.getBrightnessCap(), 0f);
-        assertFalse(throttler.isThrottled());
-        assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE, throttler.getBrightnessMaxReason());
-
-        // Set status just high enough to trigger throttling
-        listener.notifyThrottling(getSkinTemp(level.thermalStatus));
-        mTestLooper.dispatchAll();
-        assertEquals(level.brightness, throttler.getBrightnessCap(), 0f);
-        assertTrue(throttler.isThrottled());
-        assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL,
-            throttler.getBrightnessMaxReason());
-
-        // Set status more than high enough to trigger throttling
-        listener.notifyThrottling(getSkinTemp(level.thermalStatus + 1));
-        mTestLooper.dispatchAll();
-        assertEquals(level.brightness, throttler.getBrightnessCap(), 0f);
-        assertTrue(throttler.isThrottled());
-        assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL,
-           throttler.getBrightnessMaxReason());
-
-        // Return to the lower throttling level
-        listener.notifyThrottling(getSkinTemp(level.thermalStatus));
-        mTestLooper.dispatchAll();
-        assertEquals(level.brightness, throttler.getBrightnessCap(), 0f);
-        assertTrue(throttler.isThrottled());
-        assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL,
-            throttler.getBrightnessMaxReason());
-
-        // Cool down
-        listener.notifyThrottling(getSkinTemp(level.thermalStatus - 1));
-        mTestLooper.dispatchAll();
-        assertEquals(PowerManager.BRIGHTNESS_MAX, throttler.getBrightnessCap(), 0f);
-        assertFalse(throttler.isThrottled());
-        assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE,
-            throttler.getBrightnessMaxReason());
-    }
-
-    @Test
-    public void testThermalThrottlingMultiLevel() throws Exception {
-        final ThrottlingLevel levelLo = new ThrottlingLevel(PowerManager.THERMAL_STATUS_MODERATE,
-            0.62f);
-        final ThrottlingLevel levelHi = new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL,
-            0.25f);
-
-        List<ThrottlingLevel> levels = new ArrayList<>();
-        levels.add(levelLo);
-        levels.add(levelHi);
-        final ThermalBrightnessThrottlingData data = ThermalBrightnessThrottlingData.create(levels);
-        final BrightnessThrottler throttler = createThrottlerSupported(data);
-        assertTrue(throttler.deviceSupportsThrottling());
-
-        verify(mThermalServiceMock).registerThermalEventListenerWithType(
-                mThermalEventListenerCaptor.capture(), eq(Temperature.TYPE_SKIN));
-        final IThermalEventListener listener = mThermalEventListenerCaptor.getValue();
-
-        // Set status too low to trigger throttling
-        listener.notifyThrottling(getSkinTemp(levelLo.thermalStatus - 1));
-        mTestLooper.dispatchAll();
-        assertEquals(PowerManager.BRIGHTNESS_MAX, throttler.getBrightnessCap(), 0f);
-        assertFalse(throttler.isThrottled());
-        assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE, throttler.getBrightnessMaxReason());
-
-        // Set status just high enough to trigger throttling
-        listener.notifyThrottling(getSkinTemp(levelLo.thermalStatus));
-        mTestLooper.dispatchAll();
-        assertEquals(levelLo.brightness, throttler.getBrightnessCap(), 0f);
-        assertTrue(throttler.isThrottled());
-        assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL,
-            throttler.getBrightnessMaxReason());
-
-        // Set status to an intermediate throttling level
-        listener.notifyThrottling(getSkinTemp(levelLo.thermalStatus + 1));
-        mTestLooper.dispatchAll();
-        assertEquals(levelLo.brightness, throttler.getBrightnessCap(), 0f);
-        assertTrue(throttler.isThrottled());
-        assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL,
-            throttler.getBrightnessMaxReason());
-
-        // Set status to the highest configured throttling level
-        listener.notifyThrottling(getSkinTemp(levelHi.thermalStatus));
-        mTestLooper.dispatchAll();
-        assertEquals(levelHi.brightness, throttler.getBrightnessCap(), 0f);
-        assertTrue(throttler.isThrottled());
-        assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL,
-            throttler.getBrightnessMaxReason());
-
-        // Set status to exceed the highest configured throttling level
-        listener.notifyThrottling(getSkinTemp(levelHi.thermalStatus + 1));
-        mTestLooper.dispatchAll();
-        assertEquals(levelHi.brightness, throttler.getBrightnessCap(), 0f);
-        assertTrue(throttler.isThrottled());
-        assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL,
-            throttler.getBrightnessMaxReason());
-
-        // Return to an intermediate throttling level
-        listener.notifyThrottling(getSkinTemp(levelLo.thermalStatus + 1));
-        mTestLooper.dispatchAll();
-        assertEquals(levelLo.brightness, throttler.getBrightnessCap(), 0f);
-        assertTrue(throttler.isThrottled());
-        assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL,
-            throttler.getBrightnessMaxReason());
-
-        // Return to the lowest configured throttling level
-        listener.notifyThrottling(getSkinTemp(levelLo.thermalStatus));
-        mTestLooper.dispatchAll();
-        assertEquals(levelLo.brightness, throttler.getBrightnessCap(), 0f);
-        assertTrue(throttler.isThrottled());
-        assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL,
-            throttler.getBrightnessMaxReason());
-
-        // Cool down
-        listener.notifyThrottling(getSkinTemp(levelLo.thermalStatus - 1));
-        mTestLooper.dispatchAll();
-        assertEquals(PowerManager.BRIGHTNESS_MAX, throttler.getBrightnessCap(), 0f);
-        assertFalse(throttler.isThrottled());
-        assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE, throttler.getBrightnessMaxReason());
-    }
-
-    @Test public void testUpdateThermalThrottlingData() throws Exception {
-        // Initialise brightness throttling levels
-        // Ensure that they are overridden by setting the data through device config.
-        final ThrottlingLevel level = new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL,
-                0.25f);
-        List<ThrottlingLevel> levels = new ArrayList<>();
-        levels.add(level);
-        final ThermalBrightnessThrottlingData data = ThermalBrightnessThrottlingData.create(levels);
-        mDeviceConfigFake.setThermalBrightnessThrottlingData("123,1,critical,0.4");
-        final BrightnessThrottler throttler = createThrottlerSupported(data);
-
-        verify(mThermalServiceMock).registerThermalEventListenerWithType(
-                mThermalEventListenerCaptor.capture(), eq(Temperature.TYPE_SKIN));
-        final IThermalEventListener listener = mThermalEventListenerCaptor.getValue();
-        testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.4f);
-
-        // Set new (valid) data from device config
-        mDeviceConfigFake.setThermalBrightnessThrottlingData("123,1,critical,0.8");
-        testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.8f);
-
-        mDeviceConfigFake.setThermalBrightnessThrottlingData(
-                "123,1,critical,0.75;123,1,critical,0.99,id_2");
-        testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.75f);
-        mDeviceConfigFake.setThermalBrightnessThrottlingData(
-                "123,1,critical,0.8,default;123,1,critical,0.99,id_2");
-        testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.8f);
-    }
-
-    @Test public void testInvalidThrottlingStrings() throws Exception {
-        // Initialise brightness throttling levels
-        // Ensure that they are not overridden by invalid data through device config.
-        final ThrottlingLevel level = new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL,
-                0.25f);
-        List<ThrottlingLevel> levels = new ArrayList<>();
-        levels.add(level);
-        final ThermalBrightnessThrottlingData data = ThermalBrightnessThrottlingData.create(levels);
-        final BrightnessThrottler throttler = createThrottlerSupported(data);
-        verify(mThermalServiceMock).registerThermalEventListenerWithType(
-                mThermalEventListenerCaptor.capture(), eq(Temperature.TYPE_SKIN));
-        final IThermalEventListener listener = mThermalEventListenerCaptor.getValue();
-
-        // None of these are valid so shouldn't override the original data
-
-        // Not the current id
-        mDeviceConfigFake.setThermalBrightnessThrottlingData("321,1,critical,0.4");
-        testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
-        // Incorrect number
-        mDeviceConfigFake.setThermalBrightnessThrottlingData("123,0,critical,0.4");
-        testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
-        // Incorrect number
-        mDeviceConfigFake.setThermalBrightnessThrottlingData("123,2,critical,0.4");
-        testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
-        // Invalid level
-        mDeviceConfigFake.setThermalBrightnessThrottlingData("123,1,invalid,0.4");
-        testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
-        // Invalid brightness
-        mDeviceConfigFake.setThermalBrightnessThrottlingData("123,1,critical,none");
-        testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
-        // Invalid brightness
-        mDeviceConfigFake.setThermalBrightnessThrottlingData("123,1,critical,-3");
-        testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
-        // Invalid format
-        mDeviceConfigFake.setThermalBrightnessThrottlingData("invalid string");
-        testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
-        // Invalid format
-        mDeviceConfigFake.setThermalBrightnessThrottlingData("");
-        testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
-        // Invalid string format
-        mDeviceConfigFake.setThermalBrightnessThrottlingData(
-                "123,default,1,critical,0.75,1,critical,0.99");
-        testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
-        // Invalid level string and number string
-        mDeviceConfigFake.setThermalBrightnessThrottlingData(
-                "123,1,1,critical,0.75,id_2,1,critical,0.99");
-        testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
-        // Invalid format - (two default ids for same display)
-        mDeviceConfigFake.setThermalBrightnessThrottlingData(
-                "123,1,critical,0.75,default;123,1,critical,0.99");
-        testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
-    }
-
-    private void testThermalThrottling(BrightnessThrottler throttler,
-            IThermalEventListener listener, float tooLowCap, float tooHighCap) throws Exception {
-        final ThrottlingLevel level = new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL,
-                tooHighCap);
-
-        // Set status too low to trigger throttling
-        listener.notifyThrottling(getSkinTemp(level.thermalStatus - 1));
-        mTestLooper.dispatchAll();
-        assertEquals(tooLowCap, throttler.getBrightnessCap(), 0f);
-        assertFalse(throttler.isThrottled());
-
-        // Set status high enough to trigger throttling
-        listener.notifyThrottling(getSkinTemp(level.thermalStatus));
-        mTestLooper.dispatchAll();
-        assertEquals(tooHighCap, throttler.getBrightnessCap(), 0f);
-        assertTrue(throttler.isThrottled());
-    }
-
-    @Test public void testMultipleConfigPoints() throws Exception {
-        // Initialise brightness throttling levels
-        final ThrottlingLevel level = new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL,
-                0.25f);
-        List<ThrottlingLevel> levels = new ArrayList<>();
-        levels.add(level);
-        final ThermalBrightnessThrottlingData data = ThermalBrightnessThrottlingData.create(levels);
-
-        // These are identical to the string set below
-        final ThrottlingLevel levelSevere = new ThrottlingLevel(PowerManager.THERMAL_STATUS_SEVERE,
-                0.9f);
-        final ThrottlingLevel levelCritical = new ThrottlingLevel(
-                PowerManager.THERMAL_STATUS_CRITICAL, 0.5f);
-        final ThrottlingLevel levelEmergency = new ThrottlingLevel(
-                PowerManager.THERMAL_STATUS_EMERGENCY, 0.1f);
-
-        mDeviceConfigFake.setThermalBrightnessThrottlingData(
-                "123,3,severe,0.9,critical,0.5,emergency,0.1");
-        final BrightnessThrottler throttler = createThrottlerSupported(data);
-
-        verify(mThermalServiceMock).registerThermalEventListenerWithType(
-                mThermalEventListenerCaptor.capture(), eq(Temperature.TYPE_SKIN));
-        final IThermalEventListener listener = mThermalEventListenerCaptor.getValue();
-
-        // Ensure that the multiple levels set via the string through the device config correctly
-        // override the original display device config ones.
-
-        // levelSevere
-        // Set status too low to trigger throttling
-        listener.notifyThrottling(getSkinTemp(levelSevere.thermalStatus - 1));
-        mTestLooper.dispatchAll();
-        assertEquals(PowerManager.BRIGHTNESS_MAX, throttler.getBrightnessCap(), 0f);
-        assertFalse(throttler.isThrottled());
-
-        // Set status high enough to trigger throttling
-        listener.notifyThrottling(getSkinTemp(levelSevere.thermalStatus));
-        mTestLooper.dispatchAll();
-        assertEquals(0.9f, throttler.getBrightnessCap(), 0f);
-        assertTrue(throttler.isThrottled());
-
-        // levelCritical
-        // Set status too low to trigger throttling
-        listener.notifyThrottling(getSkinTemp(levelCritical.thermalStatus - 1));
-        mTestLooper.dispatchAll();
-        assertEquals(0.9f, throttler.getBrightnessCap(), 0f);
-        assertTrue(throttler.isThrottled());
-
-        // Set status high enough to trigger throttling
-        listener.notifyThrottling(getSkinTemp(levelCritical.thermalStatus));
-        mTestLooper.dispatchAll();
-        assertEquals(0.5f, throttler.getBrightnessCap(), 0f);
-        assertTrue(throttler.isThrottled());
-
-        //levelEmergency
-        // Set status too low to trigger throttling
-        listener.notifyThrottling(getSkinTemp(levelEmergency.thermalStatus - 1));
-        mTestLooper.dispatchAll();
-        assertEquals(0.5f, throttler.getBrightnessCap(), 0f);
-        assertTrue(throttler.isThrottled());
-
-        // Set status high enough to trigger throttling
-        listener.notifyThrottling(getSkinTemp(levelEmergency.thermalStatus));
-        mTestLooper.dispatchAll();
-        assertEquals(0.1f, throttler.getBrightnessCap(), 0f);
-        assertTrue(throttler.isThrottled());
-    }
-
-    private void assertThrottlingLevelsEquals(
-            List<ThrottlingLevel> expected,
-            List<ThrottlingLevel> actual) {
-        assertEquals(expected.size(), actual.size());
-
-        for (int i = 0; i < expected.size(); i++) {
-            ThrottlingLevel expectedLevel = expected.get(i);
-            ThrottlingLevel actualLevel = actual.get(i);
-
-            assertEquals(expectedLevel.thermalStatus, actualLevel.thermalStatus);
-            assertEquals(expectedLevel.brightness, actualLevel.brightness, 0.0f);
-        }
-    }
-
-    private BrightnessThrottler createThrottlerUnsupported() {
-        return new BrightnessThrottler(mInjectorMock, mHandler, mHandler,
-                /* throttlingChangeCallback= */ () -> {}, /* uniqueDisplayId= */ null,
-                /* thermalThrottlingDataId= */ null,
-                /* thermalThrottlingDataMap= */ new HashMap<>(1));
-    }
-
-    private BrightnessThrottler createThrottlerSupported(ThermalBrightnessThrottlingData data) {
-        assertNotNull(data);
-        HashMap<String, ThermalBrightnessThrottlingData> throttlingDataMap = new HashMap<>(1);
-        throttlingDataMap.put("default", data);
-        return new BrightnessThrottler(mInjectorMock, mHandler, BackgroundThread.getHandler(),
-                () -> {}, "123", "default", throttlingDataMap);
-    }
-
-    private Temperature getSkinTemp(@ThrottlingStatus int status) {
-        return new Temperature(30.0f, Temperature.TYPE_SKIN, "test_skin_temp", status);
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java b/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java
deleted file mode 100644
index 021f2d1..0000000
--- a/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java
+++ /dev/null
@@ -1,1247 +0,0 @@
-/*
- * Copyright 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.display;
-
-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.assertNull;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.app.ActivityTaskManager.RootTaskInfo;
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.ParceledListSlice;
-import android.database.ContentObserver;
-import android.hardware.Sensor;
-import android.hardware.SensorEvent;
-import android.hardware.SensorEventListener;
-import android.hardware.display.AmbientBrightnessDayStats;
-import android.hardware.display.BrightnessChangeEvent;
-import android.hardware.display.BrightnessConfiguration;
-import android.hardware.display.ColorDisplayManager;
-import android.hardware.display.DisplayManager;
-import android.hardware.display.DisplayedContentSample;
-import android.hardware.display.DisplayedContentSamplingAttributes;
-import android.hardware.input.InputSensorInfo;
-import android.os.BatteryManager;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.MessageQueue;
-import android.os.Parcel;
-import android.os.RemoteException;
-import android.os.SystemClock;
-import android.os.UserManager;
-import android.provider.Settings;
-import android.util.AtomicFile;
-import android.view.Display;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.internal.R;
-
-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;
-import java.io.IOException;
-import java.io.InputStream;
-import java.lang.reflect.Constructor;
-import java.nio.charset.StandardCharsets;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class BrightnessTrackerTest {
-    private static final float DEFAULT_INITIAL_BRIGHTNESS = 2.5f;
-    private static final boolean DEFAULT_COLOR_SAMPLING_ENABLED = true;
-    private static final String DEFAULT_DISPLAY_ID = "123";
-    private static final float FLOAT_DELTA = 0.01f;
-
-    @Mock private InputSensorInfo mInputSensorInfoMock;
-
-    private BrightnessTracker mTracker;
-    private TestInjector mInjector;
-    private Sensor mLightSensorFake;
-
-    private static Object sHandlerLock = new Object();
-    private static Handler sHandler;
-    private static HandlerThread sThread =
-            new HandlerThread("brightness.test", android.os.Process.THREAD_PRIORITY_BACKGROUND);
-
-    private int mDefaultNightModeColorTemperature;
-    private float mRbcOffsetFactor;
-
-    private static Handler ensureHandler() {
-        synchronized (sHandlerLock) {
-            if (sHandler == null) {
-                sThread.start();
-                sHandler = new Handler(sThread.getLooper());
-            }
-            return sHandler;
-        }
-    }
-
-
-    @Before
-    public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
-        mInjector = new TestInjector(ensureHandler());
-        mLightSensorFake = new Sensor(mInputSensorInfoMock);
-
-        mTracker = new BrightnessTracker(InstrumentationRegistry.getContext(), mInjector);
-        mTracker.setLightSensor(mLightSensorFake);
-        mDefaultNightModeColorTemperature =
-                InstrumentationRegistry.getContext().getResources().getInteger(
-                R.integer.config_nightDisplayColorTemperatureDefault);
-        mRbcOffsetFactor = InstrumentationRegistry.getContext()
-                .getSystemService(ColorDisplayManager.class).getReduceBrightColorsOffsetFactor();
-    }
-
-    @Test
-    public void testStartStopTrackerScreenOnOff() {
-        mInjector.mInteractive = false;
-        startTracker(mTracker);
-        assertNull(mInjector.mSensorListener);
-        assertNotNull(mInjector.mBroadcastReceiver);
-        assertTrue(mInjector.mIdleScheduled);
-        mInjector.sendScreenChange(/* screenOn= */ true);
-        assertNotNull(mInjector.mSensorListener);
-        assertTrue(mInjector.mColorSamplingEnabled);
-
-        mInjector.sendScreenChange(/* screenOn= */ false);
-        assertNull(mInjector.mSensorListener);
-        assertFalse(mInjector.mColorSamplingEnabled);
-
-        // Turn screen on while brightness mode is manual
-        mInjector.setBrightnessMode(/* isBrightnessModeAutomatic= */ false);
-        mInjector.sendScreenChange(/* screenOn= */ true);
-        assertNull(mInjector.mSensorListener);
-        assertFalse(mInjector.mColorSamplingEnabled);
-
-        // Set brightness mode to automatic while screen is off.
-        mInjector.sendScreenChange(/* screenOn= */ false);
-        mInjector.setBrightnessMode(/* isBrightnessModeAutomatic= */ true);
-        assertNull(mInjector.mSensorListener);
-        assertFalse(mInjector.mColorSamplingEnabled);
-
-        // Turn on screen while brightness mode is automatic.
-        mInjector.sendScreenChange(/* screenOn= */ true);
-        assertNotNull(mInjector.mSensorListener);
-        assertTrue(mInjector.mColorSamplingEnabled);
-
-        mTracker.stop();
-        assertNull(mInjector.mSensorListener);
-        assertNull(mInjector.mBroadcastReceiver);
-        assertFalse(mInjector.mIdleScheduled);
-        assertFalse(mInjector.mColorSamplingEnabled);
-    }
-
-    @Test
-    public void testModifyBrightnessConfiguration() {
-        mInjector.mInteractive = true;
-        // Start with tracker not listening for color samples.
-        startTracker(mTracker, DEFAULT_INITIAL_BRIGHTNESS, /* collectColorSamples= */ false);
-        assertFalse(mInjector.mColorSamplingEnabled);
-
-        // Update brightness config to enabled color sampling.
-        mTracker.setShouldCollectColorSample(/* collectColorSamples= */ true);
-        mInjector.waitForHandler();
-        assertTrue(mInjector.mColorSamplingEnabled);
-
-        // Update brightness config to disable color sampling.
-        mTracker.setShouldCollectColorSample(/* collectColorSamples= */ false);
-        mInjector.waitForHandler();
-        assertFalse(mInjector.mColorSamplingEnabled);
-
-        // Pretend screen is off, update config to turn on color sampling.
-        mInjector.sendScreenChange(/* screenOn= */ false);
-        mTracker.setShouldCollectColorSample(/* collectColorSamples= */ true);
-        mInjector.waitForHandler();
-        assertFalse(mInjector.mColorSamplingEnabled);
-
-        // Pretend screen is on.
-        mInjector.sendScreenChange(/* screenOn= */ true);
-        assertTrue(mInjector.mColorSamplingEnabled);
-
-        mTracker.stop();
-        assertFalse(mInjector.mColorSamplingEnabled);
-    }
-
-    @Test
-    public void testNoColorSampling_WrongPixelFormat() {
-        mInjector.mDefaultSamplingAttributes =
-                new DisplayedContentSamplingAttributes(
-                        0x23,
-                        mInjector.mDefaultSamplingAttributes.getDataspace(),
-                        mInjector.mDefaultSamplingAttributes.getComponentMask());
-        startTracker(mTracker);
-        assertFalse(mInjector.mColorSamplingEnabled);
-        assertNull(mInjector.mDisplayListener);
-    }
-
-    @Test
-    public void testNoColorSampling_MissingComponent() {
-        mInjector.mDefaultSamplingAttributes =
-                new DisplayedContentSamplingAttributes(
-                        mInjector.mDefaultSamplingAttributes.getPixelFormat(),
-                        mInjector.mDefaultSamplingAttributes.getDataspace(),
-                        0x2);
-        startTracker(mTracker);
-        assertFalse(mInjector.mColorSamplingEnabled);
-        assertNull(mInjector.mDisplayListener);
-    }
-
-    @Test
-    public void testNoColorSampling_NoSupport() {
-        mInjector.mDefaultSamplingAttributes = null;
-        startTracker(mTracker);
-        assertFalse(mInjector.mColorSamplingEnabled);
-        assertNull(mInjector.mDisplayListener);
-    }
-
-    @Test
-    public void testColorSampling_FrameRateChange() {
-        startTracker(mTracker);
-        assertTrue(mInjector.mColorSamplingEnabled);
-        assertNotNull(mInjector.mDisplayListener);
-        int noFramesSampled = mInjector.mNoColorSamplingFrames;
-        mInjector.mFrameRate = 120.0f;
-        // Wrong display
-        mInjector.mDisplayListener.onDisplayChanged(Display.DEFAULT_DISPLAY + 10);
-        assertEquals(noFramesSampled, mInjector.mNoColorSamplingFrames);
-        // Correct display
-        mInjector.mDisplayListener.onDisplayChanged(Display.DEFAULT_DISPLAY);
-        assertEquals(noFramesSampled * 2, mInjector.mNoColorSamplingFrames);
-    }
-
-    @Test
-    public void testAdaptiveOnOff() {
-        mInjector.mInteractive = true;
-        mInjector.mIsBrightnessModeAutomatic = false;
-        startTracker(mTracker);
-        assertNull(mInjector.mSensorListener);
-        assertNotNull(mInjector.mBroadcastReceiver);
-        assertNotNull(mInjector.mContentObserver);
-        assertTrue(mInjector.mIdleScheduled);
-        assertFalse(mInjector.mColorSamplingEnabled);
-        assertNull(mInjector.mDisplayListener);
-
-        mInjector.setBrightnessMode(/* isBrightnessModeAutomatic= */ true);
-        assertNotNull(mInjector.mSensorListener);
-        assertTrue(mInjector.mColorSamplingEnabled);
-        assertNotNull(mInjector.mDisplayListener);
-
-        SensorEventListener listener = mInjector.mSensorListener;
-        DisplayManager.DisplayListener displayListener = mInjector.mDisplayListener;
-        mInjector.mSensorListener = null;
-        mInjector.mColorSamplingEnabled = false;
-        mInjector.mDisplayListener = null;
-        // Duplicate notification
-        mInjector.setBrightnessMode(/* isBrightnessModeAutomatic= */ true);
-        // Sensor shouldn't have been registered as it was already registered.
-        assertNull(mInjector.mSensorListener);
-        assertFalse(mInjector.mColorSamplingEnabled);
-        assertNull(mInjector.mDisplayListener);
-        mInjector.mDisplayListener = displayListener;
-        mInjector.mColorSamplingEnabled = true;
-
-        mInjector.setBrightnessMode(/* isBrightnessModeAutomatic= */ false);
-        assertNull(mInjector.mSensorListener);
-        assertFalse(mInjector.mColorSamplingEnabled);
-        assertNull(mInjector.mDisplayListener);
-
-        mTracker.stop();
-        assertNull(mInjector.mSensorListener);
-        assertNull(mInjector.mBroadcastReceiver);
-        assertNull(mInjector.mContentObserver);
-        assertFalse(mInjector.mIdleScheduled);
-        assertFalse(mInjector.mColorSamplingEnabled);
-        assertNull(mInjector.mDisplayListener);
-    }
-
-    @Test
-    public void testBrightnessEvent() {
-        final float brightness = 0.5f;
-        final String displayId = "1234";
-
-        startTracker(mTracker);
-        final long sensorTime = TimeUnit.NANOSECONDS.toMillis(mInjector.elapsedRealtimeNanos());
-        mInjector.incrementTime(TimeUnit.SECONDS.toMillis(2));
-        final long currentTime = mInjector.currentTimeMillis();
-        notifyBrightnessChanged(mTracker, brightness, displayId, new float[] {1.0f},
-                new long[] {sensorTime});
-        List<BrightnessChangeEvent> events = mTracker.getEvents(0, true).getList();
-        mTracker.stop();
-
-        assertEquals(1, events.size());
-        BrightnessChangeEvent event = events.get(0);
-        assertEquals(currentTime, event.timeStamp);
-        assertEquals(displayId, event.uniqueDisplayId);
-        assertEquals(1, event.luxValues.length);
-        assertEquals(1.0f, event.luxValues[0], FLOAT_DELTA);
-        assertEquals(currentTime - TimeUnit.SECONDS.toMillis(2),
-                event.luxTimestamps[0]);
-        assertEquals(brightness, event.brightness, FLOAT_DELTA);
-        assertEquals(DEFAULT_INITIAL_BRIGHTNESS, event.lastBrightness, FLOAT_DELTA);
-
-        // System had no data so these should all be at defaults.
-        assertEquals(Float.NaN, event.batteryLevel, 0.0);
-        assertFalse(event.nightMode);
-        assertEquals(mDefaultNightModeColorTemperature, event.colorTemperature);
-    }
-
-    @Test
-    public void testMultipleBrightnessEvents() {
-        final float brightnessOne = 0.2f;
-        final float brightnessTwo = 0.4f;
-        final float brightnessThree = 0.6f;
-        final float brightnessFour = 0.3f;
-        final String displayId = "1234";
-        final float[] luxValues = new float[]{1.0f};
-
-        startTracker(mTracker);
-        final long sensorTime = TimeUnit.NANOSECONDS.toMillis(mInjector.elapsedRealtimeNanos());
-        final long sensorTime2 = sensorTime + TimeUnit.SECONDS.toMillis(20);
-        final long sensorTime3 = sensorTime2 + TimeUnit.SECONDS.toMillis(30);
-        final long sensorTime4 = sensorTime3 + TimeUnit.SECONDS.toMillis(40);
-        final long originalTime = mInjector.currentTimeMillis();
-
-        mInjector.incrementTime(TimeUnit.SECONDS.toMillis(2));
-        notifyBrightnessChanged(mTracker, brightnessOne, displayId, luxValues,
-                new long[] {sensorTime});
-
-        mInjector.incrementTime(TimeUnit.SECONDS.toMillis(20));
-        notifyBrightnessChanged(mTracker, brightnessTwo, displayId, luxValues,
-                new long[] {sensorTime2});
-
-        mInjector.incrementTime(TimeUnit.SECONDS.toMillis(30));
-        notifyBrightnessChanged(mTracker, brightnessThree, displayId, luxValues,
-                new long[] {sensorTime3});
-
-        mInjector.incrementTime(TimeUnit.SECONDS.toMillis(40));
-        notifyBrightnessChanged(mTracker, brightnessFour, displayId, luxValues,
-                new long[] {sensorTime4});
-        mTracker.stop();
-        List<BrightnessChangeEvent> events = mTracker.getEvents(0, true).getList();
-        assertEquals(4, events.size());
-        BrightnessChangeEvent eventOne = events.get(0);
-        assertEquals(brightnessOne, eventOne.brightness, FLOAT_DELTA);
-        assertEquals(originalTime,
-                eventOne.luxTimestamps[0]);
-
-        BrightnessChangeEvent eventTwo = events.get(1);
-        assertEquals(brightnessTwo, eventTwo.brightness, FLOAT_DELTA);
-        assertEquals(originalTime + TimeUnit.SECONDS.toMillis(20),
-                eventTwo.luxTimestamps[0]);
-
-        BrightnessChangeEvent eventThree = events.get(2);
-        assertEquals(brightnessThree, eventThree.brightness, FLOAT_DELTA);
-        assertEquals(originalTime + TimeUnit.SECONDS.toMillis(50),
-                eventThree.luxTimestamps[0]);
-
-        BrightnessChangeEvent eventFour = events.get(3);
-        assertEquals(brightnessFour, eventFour.brightness, FLOAT_DELTA);
-        assertEquals(originalTime + TimeUnit.SECONDS.toMillis(90),
-                eventFour.luxTimestamps[0]);
-    }
-
-    @Test
-    public void testBrightnessFullPopulatedEvent() {
-        final int initialBrightness = 230;
-        final int brightness = 130;
-        final String displayId = "1234";
-
-        mInjector.mSecureIntSettings.put(Settings.Secure.NIGHT_DISPLAY_ACTIVATED, 1);
-        mInjector.mSecureIntSettings.put(Settings.Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE, 3333);
-
-        mInjector.mSecureIntSettings.put(Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED, 1);
-        mInjector.mSecureIntSettings.put(Settings.Secure.REDUCE_BRIGHT_COLORS_LEVEL, 40);
-
-        startTracker(mTracker, initialBrightness, DEFAULT_COLOR_SAMPLING_ENABLED);
-        mInjector.mBroadcastReceiver.onReceive(InstrumentationRegistry.getContext(),
-                batteryChangeEvent(30, 60));
-        final long currentTime = mInjector.currentTimeMillis();
-        notifyBrightnessChanged(mTracker, brightness, displayId, new float[] {1000.0f},
-                new long[] {TimeUnit.NANOSECONDS.toMillis(mInjector.elapsedRealtimeNanos())});
-        List<BrightnessChangeEvent> eventsNoPackage
-                = mTracker.getEvents(0, false).getList();
-        List<BrightnessChangeEvent> events = mTracker.getEvents(0, true).getList();
-        mTracker.stop();
-
-        assertEquals(1, events.size());
-        BrightnessChangeEvent event = events.get(0);
-        assertEquals(event.timeStamp, currentTime);
-        assertEquals(displayId, event.uniqueDisplayId);
-        assertArrayEquals(new float[] {1000.0f}, event.luxValues, FLOAT_DELTA);
-        assertArrayEquals(new long[] {currentTime}, event.luxTimestamps);
-        assertEquals(brightness, event.brightness, FLOAT_DELTA);
-        assertEquals(initialBrightness, event.lastBrightness, FLOAT_DELTA);
-        assertEquals(0.5, event.batteryLevel, FLOAT_DELTA);
-        assertTrue(event.nightMode);
-        assertEquals(3333, event.colorTemperature);
-        assertTrue(event.reduceBrightColors);
-        assertEquals(40, event.reduceBrightColorsStrength);
-        assertEquals(brightness * mRbcOffsetFactor, event.reduceBrightColorsOffset, FLOAT_DELTA);
-        assertEquals("a.package", event.packageName);
-        assertEquals(0, event.userId);
-        assertArrayEquals(new long[] {1, 10, 100, 1000, 300, 30, 10, 1}, event.colorValueBuckets);
-        assertEquals(10000, event.colorSampleDuration);
-
-        assertEquals(1, eventsNoPackage.size());
-        assertNull(eventsNoPackage.get(0).packageName);
-    }
-
-    @Test
-    public void testIgnoreAutomaticBrightnessChange() {
-        final int initialBrightness = 30;
-        startTracker(mTracker, initialBrightness, DEFAULT_COLOR_SAMPLING_ENABLED);
-        mInjector.incrementTime(TimeUnit.SECONDS.toMillis(1));
-
-        final int systemUpdatedBrightness = 20;
-        notifyBrightnessChanged(mTracker, systemUpdatedBrightness, /* userInitiated= */ false,
-                /* powerBrightnessFactor= */ 0.5f, /* isUserSetBrightness= */ false,
-                /* isDefaultBrightnessConfig= */ false, DEFAULT_DISPLAY_ID);
-        List<BrightnessChangeEvent> events = mTracker.getEvents(0, true).getList();
-        // No events because we filtered out our change.
-        assertEquals(0, events.size());
-
-        final int firstUserUpdateBrightness = 20;
-        // Then change comes from somewhere else so we shouldn't filter.
-        notifyBrightnessChanged(mTracker, firstUserUpdateBrightness);
-
-        // and with a different brightness value.
-        final int secondUserUpdateBrightness = 34;
-        notifyBrightnessChanged(mTracker, secondUserUpdateBrightness);
-        events = mTracker.getEvents(0, true).getList();
-
-        assertEquals(2, events.size());
-        // First event is change from system update (20) to first user update (20)
-        assertEquals(systemUpdatedBrightness, events.get(0).lastBrightness, FLOAT_DELTA);
-        assertEquals(firstUserUpdateBrightness, events.get(0).brightness, FLOAT_DELTA);
-        // Second event is from first to second user update.
-        assertEquals(firstUserUpdateBrightness, events.get(1).lastBrightness, FLOAT_DELTA);
-        assertEquals(secondUserUpdateBrightness, events.get(1).brightness, FLOAT_DELTA);
-
-        mTracker.stop();
-    }
-
-    @Test
-    public void testLimitedBufferSize() {
-        startTracker(mTracker);
-
-        for (int brightness = 0; brightness <= 255; ++brightness) {
-            mInjector.incrementTime(TimeUnit.SECONDS.toNanos(1));
-            notifyBrightnessChanged(mTracker, brightness);
-        }
-        List<BrightnessChangeEvent> events = mTracker.getEvents(0, true).getList();
-        mTracker.stop();
-
-        // Should be capped at 100 events, and they should be the most recent 100.
-        assertEquals(100, events.size());
-        for (int i = 0; i < events.size(); i++) {
-            BrightnessChangeEvent event = events.get(i);
-            assertEquals(156 + i, event.brightness, FLOAT_DELTA);
-        }
-    }
-
-    @Test
-    public void testReadEvents() throws Exception {
-        BrightnessTracker tracker = new BrightnessTracker(InstrumentationRegistry.getContext(),
-                mInjector);
-        mInjector.mCurrentTimeMillis = System.currentTimeMillis();
-        long someTimeAgo = System.currentTimeMillis() - TimeUnit.HOURS.toMillis(12);
-        long twoMonthsAgo = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(60);
-        // 3 Events in the file but one too old to read.
-        String eventFile =
-                "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
-                + "<events>\n"
-                + "<event nits=\"194.2\" timestamp=\""
-                + Long.toString(someTimeAgo) + "\" packageName=\""
-                + "com.example.app\" user=\"10\" "
-                + "lastNits=\"32.333\" "
-                + "batteryLevel=\"1.0\" nightMode=\"false\" colorTemperature=\"0\" "
-                + "reduceBrightColors=\"false\" reduceBrightColorsStrength=\"40\" "
-                + "reduceBrightColorsOffset=\"0\"\n"
-                + "uniqueDisplayId=\"123\""
-                + "lux=\"32.2,31.1\" luxTimestamps=\""
-                + Long.toString(someTimeAgo) + "," + Long.toString(someTimeAgo) + "\""
-                + "defaultConfig=\"true\" powerSaveFactor=\"0.5\" userPoint=\"true\" />"
-                + "<event nits=\"71\" timestamp=\""
-                + Long.toString(someTimeAgo) + "\" packageName=\""
-                + "com.android.anapp\" user=\"11\" "
-                + "lastNits=\"32\" "
-                + "batteryLevel=\"0.5\" nightMode=\"true\" colorTemperature=\"3235\" "
-                + "reduceBrightColors=\"true\" reduceBrightColorsStrength=\"40\" "
-                + "reduceBrightColorsOffset=\"0\"\n"
-                + "uniqueDisplayId=\"456\""
-                + "lux=\"132.2,131.1\" luxTimestamps=\""
-                + Long.toString(someTimeAgo) + "," + Long.toString(someTimeAgo) + "\""
-                + "colorSampleDuration=\"3456\" colorValueBuckets=\"123,598,23,19\"/>"
-                // Event that is too old so shouldn't show up.
-                + "<event nits=\"142\" timestamp=\""
-                + Long.toString(twoMonthsAgo) + "\" packageName=\""
-                + "com.example.app\" user=\"10\" "
-                + "lastNits=\"32\" "
-                + "batteryLevel=\"1.0\" nightMode=\"false\" colorTemperature=\"0\" "
-                + "reduceBrightColors=\"false\" reduceBrightColorsStrength=\"40\" "
-                + "reduceBrightColorsOffset=\"0\"\n"
-                + "uniqueDisplayId=\"789\""
-                + "lux=\"32.2,31.1\" luxTimestamps=\""
-                + Long.toString(twoMonthsAgo) + "," + Long.toString(twoMonthsAgo) + "\"/>"
-                + "</events>";
-        tracker.readEventsLocked(getInputStream(eventFile));
-        List<BrightnessChangeEvent> events = tracker.getEvents(0, true).getList();
-        assertEquals(1, events.size());
-        BrightnessChangeEvent event = events.get(0);
-        assertEquals(someTimeAgo, event.timeStamp);
-        assertEquals(194.2, event.brightness, FLOAT_DELTA);
-        assertEquals("123", event.uniqueDisplayId);
-        assertArrayEquals(new float[] {32.2f, 31.1f}, event.luxValues, FLOAT_DELTA);
-        assertArrayEquals(new long[] {someTimeAgo, someTimeAgo}, event.luxTimestamps);
-        assertEquals(32.333, event.lastBrightness, FLOAT_DELTA);
-        assertEquals(0, event.userId);
-        assertFalse(event.nightMode);
-        assertFalse(event.reduceBrightColors);
-        assertEquals(1.0f, event.batteryLevel, FLOAT_DELTA);
-        assertEquals("com.example.app", event.packageName);
-        assertTrue(event.isDefaultBrightnessConfig);
-        assertEquals(0.5f, event.powerBrightnessFactor, FLOAT_DELTA);
-        assertTrue(event.isUserSetBrightness);
-        assertNull(event.colorValueBuckets);
-
-        events = tracker.getEvents(1, true).getList();
-        assertEquals(1, events.size());
-        event = events.get(0);
-        assertEquals(someTimeAgo, event.timeStamp);
-        assertEquals(71, event.brightness, FLOAT_DELTA);
-        assertEquals("456", event.uniqueDisplayId);
-        assertArrayEquals(new float[] {132.2f, 131.1f}, event.luxValues, FLOAT_DELTA);
-        assertArrayEquals(new long[] {someTimeAgo, someTimeAgo}, event.luxTimestamps);
-        assertEquals(32, event.lastBrightness, FLOAT_DELTA);
-        assertEquals(1, event.userId);
-        assertTrue(event.nightMode);
-        assertEquals(3235, event.colorTemperature);
-        assertTrue(event.reduceBrightColors);
-        assertEquals(0.5f, event.batteryLevel, FLOAT_DELTA);
-        assertEquals("com.android.anapp", event.packageName);
-        // Not present in the event so default to false.
-        assertFalse(event.isDefaultBrightnessConfig);
-        assertEquals(1.0, event.powerBrightnessFactor, FLOAT_DELTA);
-        assertFalse(event.isUserSetBrightness);
-        assertEquals(3456L, event.colorSampleDuration);
-        assertArrayEquals(new long[] {123L, 598L, 23L, 19L}, event.colorValueBuckets);
-
-        // Pretend user 1 is a profile of user 0.
-        mInjector.mProfiles = new int[]{0, 1};
-        events = tracker.getEvents(0, true).getList();
-        // Both events should now be returned.
-        assertEquals(2, events.size());
-        BrightnessChangeEvent userZeroEvent;
-        BrightnessChangeEvent userOneEvent;
-        if (events.get(0).userId == 0) {
-            userZeroEvent = events.get(0);
-            userOneEvent = events.get(1);
-        } else {
-            userZeroEvent = events.get(1);
-            userOneEvent = events.get(0);
-        }
-        assertEquals(0, userZeroEvent.userId);
-        assertEquals("com.example.app", userZeroEvent.packageName);
-        assertEquals(1, userOneEvent.userId);
-        // Events from user 1 should have the package name redacted
-        assertNull(userOneEvent.packageName);
-    }
-
-    @Test
-    public void testFailedRead() {
-        String someTimeAgo =
-                Long.toString(System.currentTimeMillis() - TimeUnit.HOURS.toMillis(12));
-        mInjector.mCurrentTimeMillis = System.currentTimeMillis();
-
-        BrightnessTracker tracker = new BrightnessTracker(InstrumentationRegistry.getContext(),
-                mInjector);
-        String eventFile = "junk in the file";
-        try {
-            tracker.readEventsLocked(getInputStream(eventFile));
-        } catch (IOException e) {
-            // Expected;
-        }
-        assertEquals(0, tracker.getEvents(0, true).getList().size());
-
-        // Missing lux value.
-        eventFile =
-                "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
-                        + "<events>\n"
-                        + "<event nits=\"194\" timestamp=\"" + someTimeAgo + "\" packageName=\""
-                        + "com.example.app\" user=\"10\" "
-                        + "batteryLevel=\"0.7\" nightMode=\"false\" colorTemperature=\"0\" />\n"
-                        + "</events>";
-        try {
-            tracker.readEventsLocked(getInputStream(eventFile));
-        } catch (IOException e) {
-            // Expected;
-        }
-        assertEquals(0, tracker.getEvents(0, true).getList().size());
-    }
-
-    @Test
-    public void testWriteThenRead() throws Exception {
-        final int brightness = 20;
-        final String displayId = "1234";
-
-        mInjector.mSecureIntSettings.put(Settings.Secure.NIGHT_DISPLAY_ACTIVATED, 1);
-        mInjector.mSecureIntSettings.put(Settings.Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE, 3339);
-
-        mInjector.mSecureIntSettings.put(Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED, 1);
-        mInjector.mSecureIntSettings.put(Settings.Secure.REDUCE_BRIGHT_COLORS_LEVEL, 40);
-
-        startTracker(mTracker);
-        mInjector.mBroadcastReceiver.onReceive(InstrumentationRegistry.getContext(),
-                batteryChangeEvent(30, 100));
-        final long elapsedTime1 = TimeUnit.NANOSECONDS.toMillis(mInjector.elapsedRealtimeNanos());
-        final long currentTime1 = mInjector.currentTimeMillis();
-        mInjector.incrementTime(TimeUnit.SECONDS.toMillis(2));
-        final long elapsedTime2 = TimeUnit.NANOSECONDS.toMillis(mInjector.elapsedRealtimeNanos());
-        final long currentTime2 = mInjector.currentTimeMillis();
-        mInjector.incrementTime(TimeUnit.SECONDS.toMillis(3));
-        notifyBrightnessChanged(mTracker, brightness, /* userInitiated= */ true,
-                /* powerBrightnessFactor= */ 0.5f, /* isUserSetBrightness= */ true,
-                /* isDefaultBrightnessConfig= */ false, displayId, new float[] {2000.0f, 3000.0f},
-                new long[] {elapsedTime1, elapsedTime2});
-        ByteArrayOutputStream baos = new ByteArrayOutputStream();
-        mTracker.writeEventsLocked(baos);
-        mTracker.stop();
-
-        baos.flush();
-        ByteArrayInputStream input = new ByteArrayInputStream(baos.toByteArray());
-        BrightnessTracker tracker = new BrightnessTracker(InstrumentationRegistry.getContext(),
-                mInjector);
-        tracker.readEventsLocked(input);
-        List<BrightnessChangeEvent> events = tracker.getEvents(0, true).getList();
-
-        assertEquals(1, events.size());
-        BrightnessChangeEvent event = events.get(0);
-        assertEquals(displayId, event.uniqueDisplayId);
-        assertArrayEquals(new float[] {2000.0f, 3000.0f}, event.luxValues, FLOAT_DELTA);
-        assertArrayEquals(new long[] {currentTime1, currentTime2}, event.luxTimestamps);
-        assertEquals(brightness, event.brightness, FLOAT_DELTA);
-        assertEquals(0.3, event.batteryLevel, FLOAT_DELTA);
-        assertTrue(event.nightMode);
-        assertEquals(3339, event.colorTemperature);
-        assertTrue(event.reduceBrightColors);
-        assertEquals(40, event.reduceBrightColorsStrength);
-        assertEquals(brightness * mRbcOffsetFactor, event.reduceBrightColorsOffset, FLOAT_DELTA);
-        assertEquals(0.5f, event.powerBrightnessFactor, FLOAT_DELTA);
-        assertTrue(event.isUserSetBrightness);
-        assertFalse(event.isDefaultBrightnessConfig);
-        assertArrayEquals(new long[] {1, 10, 100, 1000, 300, 30, 10, 1}, event.colorValueBuckets);
-        assertEquals(10000, event.colorSampleDuration);
-    }
-
-    @Test
-    public void testParcelUnParcel() {
-        Parcel parcel = Parcel.obtain();
-        BrightnessChangeEvent.Builder builder = new BrightnessChangeEvent.Builder();
-        builder.setBrightness(23f);
-        builder.setTimeStamp(345L);
-        builder.setPackageName("com.example");
-        builder.setUserId(12);
-        builder.setUniqueDisplayId("9876");
-        float[] luxValues = new float[2];
-        luxValues[0] = 3000.0f;
-        luxValues[1] = 4000.0f;
-        builder.setLuxValues(luxValues);
-        long[] luxTimestamps = new long[2];
-        luxTimestamps[0] = 325L;
-        luxTimestamps[1] = 315L;
-        builder.setLuxTimestamps(luxTimestamps);
-        builder.setBatteryLevel(0.7f);
-        builder.setNightMode(false);
-        builder.setColorTemperature(345);
-        builder.setReduceBrightColors(false);
-        builder.setReduceBrightColorsStrength(40);
-        builder.setReduceBrightColorsOffset(20f);
-        builder.setLastBrightness(50f);
-        builder.setColorValues(new long[] {23, 34, 45}, 1000L);
-        BrightnessChangeEvent event = builder.build();
-
-        event.writeToParcel(parcel, 0);
-        byte[] parceled = parcel.marshall();
-        parcel.recycle();
-
-        parcel = Parcel.obtain();
-        parcel.unmarshall(parceled, 0, parceled.length);
-        parcel.setDataPosition(0);
-
-        BrightnessChangeEvent event2 = BrightnessChangeEvent.CREATOR.createFromParcel(parcel);
-        parcel.recycle();
-        assertEquals(event.brightness, event2.brightness, FLOAT_DELTA);
-        assertEquals(event.timeStamp, event2.timeStamp);
-        assertEquals(event.packageName, event2.packageName);
-        assertEquals(event.userId, event2.userId);
-        assertEquals(event.uniqueDisplayId, event2.uniqueDisplayId);
-        assertArrayEquals(event.luxValues, event2.luxValues, FLOAT_DELTA);
-        assertArrayEquals(event.luxTimestamps, event2.luxTimestamps);
-        assertEquals(event.batteryLevel, event2.batteryLevel, FLOAT_DELTA);
-        assertEquals(event.nightMode, event2.nightMode);
-        assertEquals(event.colorTemperature, event2.colorTemperature);
-        assertEquals(event.reduceBrightColors, event2.reduceBrightColors);
-        assertEquals(event.reduceBrightColorsStrength, event2.reduceBrightColorsStrength);
-        assertEquals(event.reduceBrightColorsOffset, event2.reduceBrightColorsOffset, FLOAT_DELTA);
-        assertEquals(event.lastBrightness, event2.lastBrightness, FLOAT_DELTA);
-        assertArrayEquals(event.colorValueBuckets, event2.colorValueBuckets);
-        assertEquals(event.colorSampleDuration, event2.colorSampleDuration);
-
-        parcel = Parcel.obtain();
-        builder.setBatteryLevel(Float.NaN);
-        event = builder.build();
-        event.writeToParcel(parcel, 0);
-        parceled = parcel.marshall();
-        parcel.recycle();
-
-        parcel = Parcel.obtain();
-        parcel.unmarshall(parceled, 0, parceled.length);
-        parcel.setDataPosition(0);
-        event2 = BrightnessChangeEvent.CREATOR.createFromParcel(parcel);
-        assertEquals(event.batteryLevel, event2.batteryLevel, FLOAT_DELTA);
-    }
-
-    @Test
-    public void testNonNullAmbientStats() {
-        // getAmbientBrightnessStats should return an empty list rather than null when
-        // tracker isn't started or hasn't collected any data.
-        ParceledListSlice<AmbientBrightnessDayStats> slice = mTracker.getAmbientBrightnessStats(0);
-        assertNotNull(slice);
-        assertTrue(slice.getList().isEmpty());
-        startTracker(mTracker);
-        slice = mTracker.getAmbientBrightnessStats(0);
-        assertNotNull(slice);
-        assertTrue(slice.getList().isEmpty());
-    }
-
-    @Test
-    public void testBackgroundHandlerDelay() {
-        final int brightness = 20;
-
-        // Setup tracker.
-        startTracker(mTracker);
-        mInjector.mSensorListener.onSensorChanged(createSensorEvent(1.0f));
-        mInjector.incrementTime(TimeUnit.SECONDS.toMillis(2));
-
-        // Block handler from running.
-        final CountDownLatch latch = new CountDownLatch(1);
-        mInjector.mHandler.post(
-                () -> {
-                    try {
-                        latch.await();
-                    } catch (InterruptedException e) {
-                        fail(e.getMessage());
-                    }
-                });
-
-        // Send an event.
-        long eventTime = mInjector.currentTimeMillis();
-        mTracker.notifyBrightnessChanged(brightness, /* userInitiated= */ true,
-                /* powerBrightnessFactor= */ 1.0f, /* isUserSetBrightness= */ false,
-                /* isDefaultBrightnessConfig= */ false, DEFAULT_DISPLAY_ID, new float[10],
-                new long[10]);
-
-        // Time passes before handler can run.
-        mInjector.incrementTime(TimeUnit.SECONDS.toMillis(2));
-
-        // Let the handler run.
-        latch.countDown();
-        mInjector.waitForHandler();
-
-        List<BrightnessChangeEvent> events = mTracker.getEvents(0, true).getList();
-        mTracker.stop();
-
-        // Check event was recorded with time it was sent rather than handler ran.
-        assertEquals(1, events.size());
-        BrightnessChangeEvent event = events.get(0);
-        assertEquals(eventTime, event.timeStamp);
-    }
-
-    @Test
-    public void testDisplayIdChange() {
-        float firstBrightness = 0.5f;
-        float secondBrightness = 0.75f;
-        String firstDisplayId = "123";
-        String secondDisplayId = "456";
-
-        startTracker(mTracker);
-        mInjector.mSensorListener.onSensorChanged(createSensorEvent(1000.0f));
-
-        notifyBrightnessChanged(mTracker, firstBrightness, firstDisplayId);
-        mInjector.incrementTime(TimeUnit.SECONDS.toMillis(2));
-        List<BrightnessChangeEvent> events = mTracker.getEvents(0, true).getList();
-        assertEquals(1, events.size());
-        BrightnessChangeEvent firstEvent = events.get(0);
-        assertEquals(firstDisplayId, firstEvent.uniqueDisplayId);
-        assertEquals(firstBrightness, firstEvent.brightness, 0.001f);
-
-        notifyBrightnessChanged(mTracker, secondBrightness, secondDisplayId);
-        mInjector.incrementTime(TimeUnit.SECONDS.toMillis(2));
-        events = mTracker.getEvents(0, true).getList();
-        assertEquals(2, events.size());
-        BrightnessChangeEvent secondEvent = events.get(1);
-        assertEquals(secondDisplayId, secondEvent.uniqueDisplayId);
-        assertEquals(secondBrightness, secondEvent.brightness, 0.001f);
-
-        mTracker.stop();
-    }
-
-    @Test
-    public void testLightSensorChange() {
-        // verify the tracker started correctly and a listener registered
-        startTracker(mTracker);
-        assertNotNull(mInjector.mSensorListener);
-        assertEquals(mInjector.mLightSensor, mLightSensorFake);
-
-        // Setting the sensor to null should stop the registered listener.
-        mTracker.setLightSensor(null);
-        mInjector.waitForHandler();
-        assertNull(mInjector.mSensorListener);
-        assertNull(mInjector.mLightSensor);
-
-        // Resetting sensor should start listener again
-        mTracker.setLightSensor(mLightSensorFake);
-        mInjector.waitForHandler();
-        assertNotNull(mInjector.mSensorListener);
-        assertEquals(mInjector.mLightSensor, mLightSensorFake);
-
-        Sensor secondSensor = new Sensor(mInputSensorInfoMock);
-        // Setting a different listener should keep things working
-        mTracker.setLightSensor(secondSensor);
-        mInjector.waitForHandler();
-        assertNotNull(mInjector.mSensorListener);
-        assertEquals(mInjector.mLightSensor, secondSensor);
-    }
-
-    @Test
-    public void testSetLightSensorDoesntStartListener() {
-        mTracker.setLightSensor(mLightSensorFake);
-        assertNull(mInjector.mSensorListener);
-    }
-
-    @Test
-    public void testNullLightSensorWontRegister() {
-        mTracker.setLightSensor(null);
-        startTracker(mTracker);
-        assertNull(mInjector.mSensorListener);
-        assertNull(mInjector.mLightSensor);
-    }
-
-    @Test
-    public void testOnlyOneReceiverRegistered() {
-        assertNull(mInjector.mLightSensor);
-        assertNull(mInjector.mSensorListener);
-        assertNull(mInjector.mContentObserver);
-        assertNull(mInjector.mBroadcastReceiver);
-        assertFalse(mInjector.mIdleScheduled);
-        startTracker(mTracker, 0.3f, false);
-
-        assertNotNull(mInjector.mLightSensor);
-        assertNotNull(mInjector.mSensorListener);
-        assertNotNull(mInjector.mContentObserver);
-        assertNotNull(mInjector.mBroadcastReceiver);
-        assertTrue(mInjector.mIdleScheduled);
-        Sensor registeredLightSensor = mInjector.mLightSensor;
-        SensorEventListener registeredSensorListener = mInjector.mSensorListener;
-        ContentObserver registeredContentObserver = mInjector.mContentObserver;
-        BroadcastReceiver registeredBroadcastReceiver = mInjector.mBroadcastReceiver;
-
-        mTracker.start(0.3f);
-        assertSame(registeredLightSensor, mInjector.mLightSensor);
-        assertSame(registeredSensorListener, mInjector.mSensorListener);
-        assertSame(registeredContentObserver, mInjector.mContentObserver);
-        assertSame(registeredBroadcastReceiver, mInjector.mBroadcastReceiver);
-
-        mTracker.stop();
-        assertNull(mInjector.mLightSensor);
-        assertNull(mInjector.mSensorListener);
-        assertNull(mInjector.mContentObserver);
-        assertNull(mInjector.mBroadcastReceiver);
-        assertFalse(mInjector.mIdleScheduled);
-
-        // mInjector asserts that we aren't removing a null receiver
-        mTracker.stop();
-    }
-
-    private InputStream getInputStream(String data) {
-        return new ByteArrayInputStream(data.getBytes(StandardCharsets.UTF_8));
-    }
-
-    private Intent batteryChangeEvent(int level, int scale) {
-        Intent intent = new Intent();
-        intent.setAction(Intent.ACTION_BATTERY_CHANGED);
-        intent.putExtra(BatteryManager.EXTRA_LEVEL, level);
-        intent.putExtra(BatteryManager.EXTRA_SCALE, scale);
-        return intent;
-    }
-
-    private SensorEvent createSensorEvent(float lux) {
-        SensorEvent event;
-        try {
-            Constructor<SensorEvent> constr =
-                    SensorEvent.class.getDeclaredConstructor(Integer.TYPE);
-            constr.setAccessible(true);
-            event = constr.newInstance(1);
-        } catch (Exception e) {
-            throw new RuntimeException(e);
-        }
-        event.values[0] = lux;
-        event.timestamp = mInjector.mElapsedRealtimeNanos;
-
-        return event;
-    }
-
-    private void startTracker(BrightnessTracker tracker) {
-        startTracker(tracker, DEFAULT_INITIAL_BRIGHTNESS,  DEFAULT_COLOR_SAMPLING_ENABLED);
-    }
-
-    private void startTracker(BrightnessTracker tracker, float initialBrightness,
-            boolean collectColorSamples) {
-        tracker.start(initialBrightness);
-        tracker.setShouldCollectColorSample(collectColorSamples);
-        mInjector.waitForHandler();
-    }
-
-    private void notifyBrightnessChanged(BrightnessTracker tracker, float brightness) {
-        notifyBrightnessChanged(tracker, brightness, DEFAULT_DISPLAY_ID);
-    }
-
-    private void notifyBrightnessChanged(BrightnessTracker tracker, float brightness,
-            String displayId) {
-        notifyBrightnessChanged(tracker, brightness, /* userInitiated= */ true,
-                /* powerBrightnessFactor= */ 1.0f, /* isUserSetBrightness= */ false,
-                /* isDefaultBrightnessConfig= */ false, displayId, new float[10], new long[10]);
-    }
-
-    private void notifyBrightnessChanged(BrightnessTracker tracker, float brightness,
-            String displayId, float[] luxValues, long[] luxTimestamps) {
-        notifyBrightnessChanged(tracker, brightness, /* userInitiated= */ true,
-                /* powerBrightnessFactor= */ 1.0f, /* isUserSetBrightness= */ false,
-                /* isDefaultBrightnessConfig= */ false, displayId, luxValues, luxTimestamps);
-    }
-
-    private void notifyBrightnessChanged(BrightnessTracker tracker, float brightness,
-            boolean userInitiated, float powerBrightnessFactor, boolean isUserSetBrightness,
-            boolean isDefaultBrightnessConfig, String displayId) {
-        tracker.notifyBrightnessChanged(brightness, userInitiated, powerBrightnessFactor,
-                isUserSetBrightness, isDefaultBrightnessConfig, displayId, new float[10],
-                new long[10]);
-        mInjector.waitForHandler();
-    }
-
-    private void notifyBrightnessChanged(BrightnessTracker tracker, float brightness,
-            boolean userInitiated, float powerBrightnessFactor, boolean isUserSetBrightness,
-            boolean isDefaultBrightnessConfig, String displayId, float[] luxValues,
-            long[] luxTimestamps) {
-        tracker.notifyBrightnessChanged(brightness, userInitiated, powerBrightnessFactor,
-                isUserSetBrightness, isDefaultBrightnessConfig, displayId, luxValues,
-                luxTimestamps);
-        mInjector.waitForHandler();
-    }
-
-    private BrightnessConfiguration buildBrightnessConfiguration(boolean collectColorSamples) {
-        BrightnessConfiguration.Builder builder = new BrightnessConfiguration.Builder(
-                /* lux= */ new float[] {0f, 10f, 100f},
-                /* nits= */ new float[] {1f, 90f, 100f});
-        builder.setShouldCollectColorSamples(collectColorSamples);
-        return builder.build();
-    }
-
-    private static final class Idle implements MessageQueue.IdleHandler {
-        private boolean mIdle;
-
-        @Override
-        public boolean queueIdle() {
-            synchronized (this) {
-                mIdle = true;
-                notifyAll();
-            }
-            return false;
-        }
-
-        public synchronized void waitForIdle() {
-            while (!mIdle) {
-                try {
-                    wait();
-                } catch (InterruptedException e) {
-                }
-            }
-        }
-    }
-
-    private class TestInjector extends BrightnessTracker.Injector {
-        SensorEventListener mSensorListener;
-        Sensor mLightSensor;
-        BroadcastReceiver mBroadcastReceiver;
-        DisplayManager.DisplayListener mDisplayListener;
-        Map<String, Integer> mSecureIntSettings = new HashMap<>();
-        long mCurrentTimeMillis = System.currentTimeMillis();
-        long mElapsedRealtimeNanos = SystemClock.elapsedRealtimeNanos();
-        Handler mHandler;
-        boolean mIdleScheduled;
-        boolean mInteractive = true;
-        int[] mProfiles;
-        ContentObserver mContentObserver;
-        boolean mIsBrightnessModeAutomatic = true;
-        boolean mColorSamplingEnabled = false;
-        DisplayedContentSamplingAttributes mDefaultSamplingAttributes =
-                new DisplayedContentSamplingAttributes(0x37, 0, 0x4);
-        float mFrameRate = 60.0f;
-        int mNoColorSamplingFrames;
-
-
-        public TestInjector(Handler handler) {
-            mHandler = handler;
-        }
-
-        void incrementTime(long timeMillis) {
-            mCurrentTimeMillis += timeMillis;
-            mElapsedRealtimeNanos += TimeUnit.MILLISECONDS.toNanos(timeMillis);
-        }
-
-        void setBrightnessMode(boolean isBrightnessModeAutomatic) {
-          mIsBrightnessModeAutomatic = isBrightnessModeAutomatic;
-          mContentObserver.dispatchChange(false, null);
-          waitForHandler();
-        }
-
-        void sendScreenChange(boolean screenOn) {
-            mInteractive = screenOn;
-            Intent intent = new Intent();
-            intent.setAction(screenOn ? Intent.ACTION_SCREEN_ON : Intent.ACTION_SCREEN_OFF);
-            mBroadcastReceiver.onReceive(InstrumentationRegistry.getContext(), intent);
-            waitForHandler();
-        }
-
-        void waitForHandler() {
-            Idle idle = new Idle();
-            mHandler.getLooper().getQueue().addIdleHandler(idle);
-            mHandler.post(() -> {});
-            idle.waitForIdle();
-        }
-
-        @Override
-        public void registerSensorListener(Context context,
-                SensorEventListener sensorListener, Sensor lightSensor, Handler handler) {
-            mSensorListener = sensorListener;
-            mLightSensor = lightSensor;
-        }
-
-        @Override
-        public void unregisterSensorListener(Context context,
-                SensorEventListener sensorListener) {
-            mSensorListener = null;
-            mLightSensor = null;
-        }
-
-        @Override
-        public void registerBrightnessModeObserver(ContentResolver resolver,
-                ContentObserver settingsObserver) {
-            mContentObserver = settingsObserver;
-        }
-
-        @Override
-        public void unregisterBrightnessModeObserver(Context context,
-                ContentObserver settingsObserver) {
-            mContentObserver = null;
-        }
-
-        @Override
-        public void registerReceiver(Context context,
-                BroadcastReceiver shutdownReceiver, IntentFilter shutdownFilter) {
-            mBroadcastReceiver = shutdownReceiver;
-        }
-
-        @Override
-        public void unregisterReceiver(Context context,
-                BroadcastReceiver broadcastReceiver) {
-            assertEquals(mBroadcastReceiver, broadcastReceiver);
-            mBroadcastReceiver = null;
-        }
-
-        @Override
-        public Handler getBackgroundHandler() {
-            return mHandler;
-        }
-
-        @Override
-        public boolean isBrightnessModeAutomatic(ContentResolver resolver) {
-            return mIsBrightnessModeAutomatic;
-        }
-
-        @Override
-        public int getSecureIntForUser(ContentResolver resolver, String setting, int defaultValue,
-                int userId) {
-            Integer value = mSecureIntSettings.get(setting);
-            if (value == null) {
-                return defaultValue;
-            } else {
-                return value;
-            }
-        }
-
-        @Override
-        public AtomicFile getFile(String filename) {
-            // Don't have the test write / read from anywhere.
-            return null;
-        }
-
-        @Override
-        public AtomicFile getLegacyFile(String filename) {
-            // Don't have the test write / read from anywhere.
-            return null;
-        }
-
-        @Override
-        public long currentTimeMillis() {
-            return mCurrentTimeMillis;
-        }
-
-        @Override
-        public long elapsedRealtimeNanos() {
-            return mElapsedRealtimeNanos;
-        }
-
-        @Override
-        public int getUserSerialNumber(UserManager userManager, int userId) {
-            return userId + 10;
-        }
-
-        @Override
-        public int getUserId(UserManager userManager, int userSerialNumber) {
-            return userSerialNumber - 10;
-        }
-
-        @Override
-        public int[] getProfileIds(UserManager userManager, int userId) {
-            if (mProfiles != null) {
-                return mProfiles;
-            } else {
-                return new int[]{userId};
-            }
-        }
-
-        @Override
-        public RootTaskInfo getFocusedStack() throws RemoteException {
-            RootTaskInfo focusedStack = new RootTaskInfo();
-            focusedStack.userId = 0;
-            focusedStack.topActivity = new ComponentName("a.package", "a.class");
-            return focusedStack;
-        }
-
-        @Override
-        public void scheduleIdleJob(Context context) {
-            // Don't actually schedule jobs during unit tests.
-            mIdleScheduled = true;
-        }
-
-        @Override
-        public void cancelIdleJob(Context context) {
-            mIdleScheduled = false;
-        }
-
-        @Override
-        public boolean isInteractive(Context context) {
-            return mInteractive;
-        }
-
-        @Override
-        public int getNightDisplayColorTemperature(Context context) {
-          return mSecureIntSettings.getOrDefault(Settings.Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE,
-                  mDefaultNightModeColorTemperature);
-        }
-
-        @Override
-        public boolean isNightDisplayActivated(Context context) {
-            return mSecureIntSettings.getOrDefault(Settings.Secure.NIGHT_DISPLAY_ACTIVATED,
-                    0) == 1;
-        }
-
-        @Override
-        public int getReduceBrightColorsStrength(Context context) {
-            return mSecureIntSettings.getOrDefault(Settings.Secure.REDUCE_BRIGHT_COLORS_LEVEL,
-                    0);
-        }
-
-        @Override
-        public boolean isReduceBrightColorsActivated(Context context) {
-            return mSecureIntSettings.getOrDefault(Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED,
-                    0) == 1;
-        }
-
-        @Override
-        public DisplayedContentSample sampleColor(int noFramesToSample) {
-            return new DisplayedContentSample(600L,
-                    null,
-                    null,
-                     new long[] {1, 10, 100, 1000, 300, 30, 10, 1},
-                    null);
-        }
-
-        @Override
-        public float getFrameRate(Context context) {
-            return mFrameRate;
-        }
-
-        @Override
-        public DisplayedContentSamplingAttributes getSamplingAttributes() {
-            return mDefaultSamplingAttributes;
-        }
-
-        @Override
-        public boolean enableColorSampling(boolean enable, int noFrames) {
-            mColorSamplingEnabled = enable;
-            mNoColorSamplingFrames = noFrames;
-            return true;
-        }
-
-        @Override
-        public void registerDisplayListener(Context context,
-                DisplayManager.DisplayListener listener, Handler handler) {
-            mDisplayListener = listener;
-        }
-
-        @Override
-        public void unRegisterDisplayListener(Context context,
-                DisplayManager.DisplayListener listener) {
-            mDisplayListener = null;
-        }
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/display/DeviceStateToLayoutMapTest.java b/services/tests/servicestests/src/com/android/server/display/DeviceStateToLayoutMapTest.java
deleted file mode 100644
index 130e6ad..0000000
--- a/services/tests/servicestests/src/com/android/server/display/DeviceStateToLayoutMapTest.java
+++ /dev/null
@@ -1,252 +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.server.display;
-
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
-
-import android.view.Display;
-import android.view.DisplayAddress;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.server.display.layout.DisplayIdProducer;
-import com.android.server.display.layout.Layout;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.nio.file.Path;
-
-
-@SmallTest
-public class DeviceStateToLayoutMapTest {
-    private DeviceStateToLayoutMap mDeviceStateToLayoutMap;
-
-    @Mock DisplayIdProducer mDisplayIdProducerMock;
-
-    @Before
-    public void setUp() throws IOException {
-        MockitoAnnotations.initMocks(this);
-
-        Mockito.when(mDisplayIdProducerMock.getId(false)).thenReturn(1);
-
-        setupDeviceStateToLayoutMap();
-    }
-
-    //////////////////
-    // Test Methods //
-    //////////////////
-
-    @Test
-    public void testInitialState() {
-        Layout configLayout = mDeviceStateToLayoutMap.get(0);
-
-        Layout testLayout = new Layout();
-        createDefaultDisplay(testLayout, 123456L);
-        createNonDefaultDisplay(testLayout, 78910L, /* enabled= */ false, /* group= */ null);
-        createNonDefaultDisplay(testLayout, 98765L, /* enabled= */ true, /* group= */ "group1");
-        createNonDefaultDisplay(testLayout, 786L, /* enabled= */ false, /* group= */ "group2");
-
-        assertEquals(testLayout, configLayout);
-    }
-
-    @Test
-    public void testSwitchedState() {
-        Layout configLayout = mDeviceStateToLayoutMap.get(1);
-
-        Layout testLayout = new Layout();
-        createDefaultDisplay(testLayout, 78910L);
-        createNonDefaultDisplay(testLayout, 123456L, /* enabled= */ false, /* group= */ null);
-
-        assertEquals(testLayout, configLayout);
-    }
-
-    @Test
-    public void testThermalBrightnessThrottlingMapId() {
-        Layout configLayout = mDeviceStateToLayoutMap.get(2);
-
-        assertEquals("concurrent1", configLayout.getAt(0).getThermalBrightnessThrottlingMapId());
-        assertEquals("concurrent2", configLayout.getAt(1).getThermalBrightnessThrottlingMapId());
-    }
-
-    @Test
-    public void testRearDisplayLayout() {
-        Layout configLayout = mDeviceStateToLayoutMap.get(2);
-
-        assertEquals(Layout.Display.POSITION_FRONT, configLayout.getAt(0).getPosition());
-        assertEquals(Layout.Display.POSITION_REAR, configLayout.getAt(1).getPosition());
-    }
-
-    @Test
-    public void testRefreshRateZoneId() {
-        Layout configLayout = mDeviceStateToLayoutMap.get(3);
-
-        assertEquals("test1", configLayout.getAt(0).getRefreshRateZoneId());
-        assertNull(configLayout.getAt(1).getRefreshRateZoneId());
-    }
-
-    @Test
-    public void testThermalRefreshRateThrottlingMapId() {
-        Layout configLayout = mDeviceStateToLayoutMap.get(4);
-
-        assertEquals("test2", configLayout.getAt(0).getRefreshRateThermalThrottlingMapId());
-        assertNull(configLayout.getAt(1).getRefreshRateThermalThrottlingMapId());
-    }
-
-    @Test
-    public void testWholeStateConfig() {
-        Layout configLayout = mDeviceStateToLayoutMap.get(99);
-
-        Layout testLayout = new Layout();
-        testLayout.createDisplayLocked(DisplayAddress.fromPhysicalDisplayId(345L),
-                /* isDefault= */ true, /* isEnabled= */ true, /* displayGroupName= */ null,
-                mDisplayIdProducerMock,  Layout.Display.POSITION_FRONT, Display.DEFAULT_DISPLAY,
-                /* brightnessThrottlingMapId= */ "brightness1",
-                /* refreshRateZoneId= */ "zone1",
-                /* refreshRateThermalThrottlingMapId= */ "rr1");
-        testLayout.createDisplayLocked(DisplayAddress.fromPhysicalDisplayId(678L),
-                /* isDefault= */ false, /* isEnabled= */ false, /* displayGroupName= */ "group1",
-                mDisplayIdProducerMock, Layout.Display.POSITION_REAR, Display.DEFAULT_DISPLAY,
-                /* brightnessThrottlingMapId= */ "brightness2",
-                /* refreshRateZoneId= */ "zone2",
-                /* refreshRateThermalThrottlingMapId= */ "rr2");
-
-        assertEquals(testLayout, configLayout);
-    }
-
-    ////////////////////
-    // Helper Methods //
-    ////////////////////
-
-    private void createDefaultDisplay(Layout layout, long id) {
-        layout.createDefaultDisplayLocked(DisplayAddress.fromPhysicalDisplayId(id),
-                mDisplayIdProducerMock);
-    }
-
-    private void createNonDefaultDisplay(Layout layout, long id, boolean enabled, String group) {
-        layout.createDisplayLocked(DisplayAddress.fromPhysicalDisplayId(id), /* isDefault= */ false,
-                enabled, group, mDisplayIdProducerMock, Layout.Display.POSITION_UNKNOWN,
-                Display.DEFAULT_DISPLAY, /* brightnessThrottlingMapId= */ null,
-                /* refreshRateZoneId= */ null,
-                /* refreshRateThermalThrottlingMapId= */ null);
-    }
-
-    private void setupDeviceStateToLayoutMap() throws IOException {
-        Path tempFile = Files.createTempFile("device_state_layout_map", ".tmp");
-        Files.write(tempFile, getContent().getBytes(StandardCharsets.UTF_8));
-        mDeviceStateToLayoutMap = new DeviceStateToLayoutMap(mDisplayIdProducerMock,
-                tempFile.toFile());
-    }
-
-    private String getContent() {
-        return "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
-                +  "<layouts>\n"
-                +    "<layout>\n"
-                +      "<state>0</state> \n"
-                +      "<display enabled=\"true\" defaultDisplay=\"true\">\n"
-                +        "<address>123456</address>\n"
-                +      "</display>\n"
-                +      "<display enabled=\"false\">\n"
-                +        "<address>78910</address>\n"
-                +      "</display>\n"
-                +      "<display enabled=\"true\" displayGroup=\"group1\">\n"
-                +        "<address>98765</address>\n"
-                +      "</display>\n"
-                +      "<display enabled=\"false\" displayGroup=\"group2\">\n"
-                +        "<address>786</address>\n"
-                +      "</display>\n"
-                +    "</layout>\n"
-
-                +    "<layout>\n"
-                +      "<state>1</state> \n"
-                +      "<display enabled=\"true\" defaultDisplay=\"true\">\n"
-                +        "<address>78910</address>\n"
-                +      "</display>\n"
-                +      "<display enabled=\"false\">\n"
-                +        "<address>123456</address>\n"
-                +      "</display>\n"
-                +    "</layout>\n"
-
-                +    "<layout>\n"
-                +      "<state>2</state> \n"
-                +      "<display enabled=\"true\" defaultDisplay=\"true\">\n"
-                +        "<address>345</address>\n"
-                +        "<position>front</position>\n"
-                +        "<brightnessThrottlingMapId>concurrent1</brightnessThrottlingMapId>\n"
-                +      "</display>\n"
-                +      "<display enabled=\"true\">\n"
-                +        "<address>678</address>\n"
-                +        "<position>rear</position>\n"
-                +        "<brightnessThrottlingMapId>concurrent2</brightnessThrottlingMapId>\n"
-                +      "</display>\n"
-                +    "</layout>\n"
-
-                +    "<layout>\n"
-                +      "<state>3</state> \n"
-                +      "<display enabled=\"true\" defaultDisplay=\"true\" "
-                +                                               "refreshRateZoneId=\"test1\">\n"
-                +        "<address>345</address>\n"
-                +      "</display>\n"
-                +      "<display enabled=\"true\">\n"
-                +        "<address>678</address>\n"
-                +      "</display>\n"
-                +    "</layout>\n"
-
-                +    "<layout>\n"
-                +      "<state>4</state> \n"
-                +      "<display enabled=\"true\" defaultDisplay=\"true\" >\n"
-                +        "<address>345</address>\n"
-                +        "<refreshRateThermalThrottlingMapId>"
-                +          "test2"
-                +        "</refreshRateThermalThrottlingMapId>"
-                +      "</display>\n"
-                +      "<display enabled=\"true\">\n"
-                +        "<address>678</address>\n"
-                +      "</display>\n"
-                +    "</layout>\n"
-                +    "<layout>\n"
-                +      "<state>99</state> \n"
-                +      "<display enabled=\"true\" defaultDisplay=\"true\" "
-                +                                          "refreshRateZoneId=\"zone1\">\n"
-                +         "<address>345</address>\n"
-                +         "<position>front</position>\n"
-                +         "<brightnessThrottlingMapId>brightness1</brightnessThrottlingMapId>\n"
-                +         "<refreshRateThermalThrottlingMapId>"
-                +           "rr1"
-                +         "</refreshRateThermalThrottlingMapId>"
-                +       "</display>\n"
-                +       "<display enabled=\"false\" displayGroup=\"group1\" "
-                +                                           "refreshRateZoneId=\"zone2\">\n"
-                +         "<address>678</address>\n"
-                +         "<position>rear</position>\n"
-                +         "<brightnessThrottlingMapId>brightness2</brightnessThrottlingMapId>\n"
-                +         "<refreshRateThermalThrottlingMapId>"
-                +           "rr2"
-                +         "</refreshRateThermalThrottlingMapId>"
-                +       "</display>\n"
-                +     "</layout>\n"
-                +   "</layouts>\n";
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
deleted file mode 100644
index 5837b21..0000000
--- a/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
+++ /dev/null
@@ -1,833 +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.server.display;
-
-
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.mockito.ArgumentMatchers.anyFloat;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.os.Temperature;
-import android.util.SparseArray;
-import android.view.SurfaceControl;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.internal.R;
-import com.android.server.display.config.ThermalStatus;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public final class DisplayDeviceConfigTest {
-    private static final int DEFAULT_PEAK_REFRESH_RATE = 75;
-    private static final int DEFAULT_REFRESH_RATE = 120;
-    private static final int DEFAULT_HIGH_BLOCKING_ZONE_REFRESH_RATE = 55;
-    private static final int DEFAULT_LOW_BLOCKING_ZONE_REFRESH_RATE = 95;
-    private static final int DEFAULT_REFRESH_RATE_IN_HBM_HDR = 90;
-    private static final int DEFAULT_REFRESH_RATE_IN_HBM_SUNLIGHT = 100;
-    private static final int[] LOW_BRIGHTNESS_THRESHOLD_OF_PEAK_REFRESH_RATE = new int[]{10, 30};
-    private static final int[] LOW_AMBIENT_THRESHOLD_OF_PEAK_REFRESH_RATE = new int[]{1, 21};
-    private static final int[] HIGH_BRIGHTNESS_THRESHOLD_OF_PEAK_REFRESH_RATE = new int[]{160};
-    private static final int[] HIGH_AMBIENT_THRESHOLD_OF_PEAK_REFRESH_RATE = new int[]{30000};
-
-    private DisplayDeviceConfig mDisplayDeviceConfig;
-    private static final float ZERO_DELTA = 0.0f;
-    private static final float SMALL_DELTA = 0.0001f;
-    @Mock
-    private Context mContext;
-
-    @Mock
-    private Resources mResources;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-        when(mContext.getResources()).thenReturn(mResources);
-        mockDeviceConfigs();
-    }
-
-    @Test
-    public void testConfigValuesFromDisplayConfig() throws IOException {
-        setupDisplayDeviceConfigFromDisplayConfigFile();
-
-        assertEquals(mDisplayDeviceConfig.getName(), "Example Display");
-        assertEquals(mDisplayDeviceConfig.getAmbientHorizonLong(), 5000);
-        assertEquals(mDisplayDeviceConfig.getAmbientHorizonShort(), 50);
-        assertEquals(mDisplayDeviceConfig.getBrightnessRampDecreaseMaxMillis(), 3000);
-        assertEquals(mDisplayDeviceConfig.getBrightnessRampIncreaseMaxMillis(), 2000);
-        assertEquals(mDisplayDeviceConfig.getBrightnessRampFastDecrease(), 0.01f, ZERO_DELTA);
-        assertEquals(mDisplayDeviceConfig.getBrightnessRampFastIncrease(), 0.02f, ZERO_DELTA);
-        assertEquals(mDisplayDeviceConfig.getBrightnessRampSlowIncrease(), 0.04f, ZERO_DELTA);
-        assertEquals(mDisplayDeviceConfig.getBrightnessRampSlowDecrease(), 0.03f, ZERO_DELTA);
-        assertEquals(mDisplayDeviceConfig.getBrightnessDefault(), 0.5f, ZERO_DELTA);
-        assertArrayEquals(mDisplayDeviceConfig.getBrightness(), new float[]{0.0f, 0.62f, 1.0f},
-                ZERO_DELTA);
-        assertArrayEquals(mDisplayDeviceConfig.getNits(), new float[]{2.0f, 500.0f, 800.0f},
-                ZERO_DELTA);
-        assertArrayEquals(mDisplayDeviceConfig.getBacklight(), new float[]{0.0f, 0.62f, 1.0f},
-                ZERO_DELTA);
-        assertEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLightDebounce(), 2000);
-        assertEquals(mDisplayDeviceConfig.getAutoBrightnessDarkeningLightDebounce(), 1000);
-        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(),
-                ZERO_DELTA);
-        assertEquals(20, mDisplayDeviceConfig.getAmbientLuxBrighteningMinThresholdIdle(),
-                ZERO_DELTA);
-        assertEquals(30, mDisplayDeviceConfig.getAmbientLuxDarkeningMinThreshold(), ZERO_DELTA);
-        assertEquals(40, mDisplayDeviceConfig.getAmbientLuxDarkeningMinThresholdIdle(), ZERO_DELTA);
-
-        assertEquals(0.1f, mDisplayDeviceConfig.getScreenBrighteningMinThreshold(), ZERO_DELTA);
-        assertEquals(0.2f, mDisplayDeviceConfig.getScreenBrighteningMinThresholdIdle(), ZERO_DELTA);
-        assertEquals(0.3f, mDisplayDeviceConfig.getScreenDarkeningMinThreshold(), ZERO_DELTA);
-        assertEquals(0.4f, mDisplayDeviceConfig.getScreenDarkeningMinThresholdIdle(), ZERO_DELTA);
-
-        assertArrayEquals(new float[]{0, 0.10f, 0.20f},
-                mDisplayDeviceConfig.getScreenBrighteningLevels(), ZERO_DELTA);
-        assertArrayEquals(new float[]{9, 10, 11},
-                mDisplayDeviceConfig.getScreenBrighteningPercentages(), ZERO_DELTA);
-        assertArrayEquals(new float[]{0, 0.11f, 0.21f},
-                mDisplayDeviceConfig.getScreenDarkeningLevels(), ZERO_DELTA);
-        assertArrayEquals(new float[]{11, 12, 13},
-                mDisplayDeviceConfig.getScreenDarkeningPercentages(), ZERO_DELTA);
-
-        assertArrayEquals(new float[]{0, 100, 200},
-                mDisplayDeviceConfig.getAmbientBrighteningLevels(), ZERO_DELTA);
-        assertArrayEquals(new float[]{13, 14, 15},
-                mDisplayDeviceConfig.getAmbientBrighteningPercentages(), ZERO_DELTA);
-        assertArrayEquals(new float[]{0, 300, 400},
-                mDisplayDeviceConfig.getAmbientDarkeningLevels(), ZERO_DELTA);
-        assertArrayEquals(new float[]{15, 16, 17},
-                mDisplayDeviceConfig.getAmbientDarkeningPercentages(), ZERO_DELTA);
-
-        assertArrayEquals(new float[]{0, 0.12f, 0.22f},
-                mDisplayDeviceConfig.getScreenBrighteningLevelsIdle(), ZERO_DELTA);
-        assertArrayEquals(new float[]{17, 18, 19},
-                mDisplayDeviceConfig.getScreenBrighteningPercentagesIdle(), ZERO_DELTA);
-        assertArrayEquals(new float[]{0, 0.13f, 0.23f},
-                mDisplayDeviceConfig.getScreenDarkeningLevelsIdle(), ZERO_DELTA);
-        assertArrayEquals(new float[]{19, 20, 21},
-                mDisplayDeviceConfig.getScreenDarkeningPercentagesIdle(), ZERO_DELTA);
-
-        assertArrayEquals(new float[]{0, 500, 600},
-                mDisplayDeviceConfig.getAmbientBrighteningLevelsIdle(), ZERO_DELTA);
-        assertArrayEquals(new float[]{21, 22, 23},
-                mDisplayDeviceConfig.getAmbientBrighteningPercentagesIdle(), ZERO_DELTA);
-        assertArrayEquals(new float[]{0, 700, 800},
-                mDisplayDeviceConfig.getAmbientDarkeningLevelsIdle(), ZERO_DELTA);
-        assertArrayEquals(new float[]{23, 24, 25},
-                mDisplayDeviceConfig.getAmbientDarkeningPercentagesIdle(), ZERO_DELTA);
-
-        assertEquals(75, mDisplayDeviceConfig.getDefaultLowBlockingZoneRefreshRate());
-        assertEquals(90, mDisplayDeviceConfig.getDefaultHighBlockingZoneRefreshRate());
-        assertEquals(85, mDisplayDeviceConfig.getDefaultPeakRefreshRate());
-        assertEquals(45, mDisplayDeviceConfig.getDefaultRefreshRate());
-        assertEquals(2, mDisplayDeviceConfig.getRefreshRangeProfiles().size());
-        assertEquals(60, mDisplayDeviceConfig.getRefreshRange("test1").min, SMALL_DELTA);
-        assertEquals(60, mDisplayDeviceConfig.getRefreshRange("test1").max, SMALL_DELTA);
-        assertEquals(80, mDisplayDeviceConfig.getRefreshRange("test2").min, SMALL_DELTA);
-        assertEquals(90, mDisplayDeviceConfig.getRefreshRange("test2").max, SMALL_DELTA);
-        assertEquals(82, mDisplayDeviceConfig.getDefaultRefreshRateInHbmHdr());
-        assertEquals(83, mDisplayDeviceConfig.getDefaultRefreshRateInHbmSunlight());
-        assertArrayEquals(new int[]{45, 55},
-                mDisplayDeviceConfig.getLowDisplayBrightnessThresholds());
-        assertArrayEquals(new int[]{50, 60},
-                mDisplayDeviceConfig.getLowAmbientBrightnessThresholds());
-        assertArrayEquals(new int[]{65, 75},
-                mDisplayDeviceConfig.getHighDisplayBrightnessThresholds());
-        assertArrayEquals(new int[]{70, 80},
-                mDisplayDeviceConfig.getHighAmbientBrightnessThresholds());
-
-        assertEquals("sensor_12345",
-                mDisplayDeviceConfig.getScreenOffBrightnessSensor().type);
-        assertEquals("Sensor 12345",
-                mDisplayDeviceConfig.getScreenOffBrightnessSensor().name);
-
-        assertArrayEquals(new int[]{-1, 10, 20, 30, 40},
-                mDisplayDeviceConfig.getScreenOffBrightnessSensorValueToLux());
-
-        List<DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel>
-                defaultThrottlingLevels = new ArrayList<>();
-        defaultThrottlingLevels.add(
-                new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel(
-                DisplayDeviceConfig.convertThermalStatus(ThermalStatus.light), 0.4f
-        ));
-        defaultThrottlingLevels.add(
-                new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel(
-                DisplayDeviceConfig.convertThermalStatus(ThermalStatus.moderate), 0.3f
-        ));
-        defaultThrottlingLevels.add(
-                new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel(
-                DisplayDeviceConfig.convertThermalStatus(ThermalStatus.severe), 0.2f
-        ));
-        defaultThrottlingLevels.add(
-                new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel(
-                DisplayDeviceConfig.convertThermalStatus(ThermalStatus.critical), 0.1f
-        ));
-        defaultThrottlingLevels.add(
-                new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel(
-                DisplayDeviceConfig.convertThermalStatus(ThermalStatus.emergency), 0.05f
-        ));
-        defaultThrottlingLevels.add(
-                new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel(
-                DisplayDeviceConfig.convertThermalStatus(ThermalStatus.shutdown), 0.025f
-        ));
-
-        DisplayDeviceConfig.ThermalBrightnessThrottlingData defaultThrottlingData =
-                new DisplayDeviceConfig.ThermalBrightnessThrottlingData(defaultThrottlingLevels);
-
-        List<DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel>
-                concurrentThrottlingLevels = new ArrayList<>();
-        concurrentThrottlingLevels.add(
-                new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel(
-                DisplayDeviceConfig.convertThermalStatus(ThermalStatus.light), 0.2f
-        ));
-        concurrentThrottlingLevels.add(
-                new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel(
-                DisplayDeviceConfig.convertThermalStatus(ThermalStatus.moderate), 0.15f
-        ));
-        concurrentThrottlingLevels.add(
-                new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel(
-                DisplayDeviceConfig.convertThermalStatus(ThermalStatus.severe), 0.1f
-        ));
-        concurrentThrottlingLevels.add(
-                new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel(
-                DisplayDeviceConfig.convertThermalStatus(ThermalStatus.critical), 0.05f
-        ));
-        concurrentThrottlingLevels.add(
-                new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel(
-                DisplayDeviceConfig.convertThermalStatus(ThermalStatus.emergency), 0.025f
-        ));
-        concurrentThrottlingLevels.add(
-                new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel(
-                DisplayDeviceConfig.convertThermalStatus(ThermalStatus.shutdown), 0.0125f
-        ));
-        DisplayDeviceConfig.ThermalBrightnessThrottlingData concurrentThrottlingData =
-                new DisplayDeviceConfig.ThermalBrightnessThrottlingData(concurrentThrottlingLevels);
-
-        HashMap<String, DisplayDeviceConfig.ThermalBrightnessThrottlingData> throttlingDataMap =
-                new HashMap<>(2);
-        throttlingDataMap.put("default", defaultThrottlingData);
-        throttlingDataMap.put("concurrent", concurrentThrottlingData);
-
-        assertEquals(throttlingDataMap,
-                mDisplayDeviceConfig.getThermalBrightnessThrottlingDataMapByThrottlingId());
-
-        assertNotNull(mDisplayDeviceConfig.getHostUsiVersion());
-        assertEquals(mDisplayDeviceConfig.getHostUsiVersion().getMajorVersion(), 2);
-        assertEquals(mDisplayDeviceConfig.getHostUsiVersion().getMinorVersion(), 0);
-
-        // Max desired Hdr/SDR ratio upper-bounds the HDR brightness.
-        assertEquals(1.0f,
-                mDisplayDeviceConfig.getHdrBrightnessFromSdr(0.62f, Float.POSITIVE_INFINITY),
-                ZERO_DELTA);
-        assertEquals(0.62f,
-                mDisplayDeviceConfig.getHdrBrightnessFromSdr(0.62f, 1.0f),
-                ZERO_DELTA);
-        assertEquals(0.77787f,
-                mDisplayDeviceConfig.getHdrBrightnessFromSdr(0.62f, 1.25f),
-                SMALL_DELTA);
-
-        // Todo: Add asserts for DensityMapping,
-        // HighBrightnessModeData AmbientLightSensor, RefreshRateLimitations and ProximitySensor.
-    }
-
-    @Test
-    public void testConfigValuesFromConfigResource() {
-        setupDisplayDeviceConfigFromConfigResourceFile();
-        assertNull(mDisplayDeviceConfig.getName());
-        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);
-
-        // Test thresholds
-        assertEquals(0, mDisplayDeviceConfig.getAmbientLuxBrighteningMinThreshold(), ZERO_DELTA);
-        assertEquals(0, mDisplayDeviceConfig.getAmbientLuxBrighteningMinThresholdIdle(),
-                ZERO_DELTA);
-        assertEquals(0, mDisplayDeviceConfig.getAmbientLuxDarkeningMinThreshold(), ZERO_DELTA);
-        assertEquals(0, mDisplayDeviceConfig.getAmbientLuxDarkeningMinThresholdIdle(), ZERO_DELTA);
-
-        assertEquals(0, mDisplayDeviceConfig.getScreenBrighteningMinThreshold(), ZERO_DELTA);
-        assertEquals(0, mDisplayDeviceConfig.getScreenBrighteningMinThresholdIdle(), ZERO_DELTA);
-        assertEquals(0, mDisplayDeviceConfig.getScreenDarkeningMinThreshold(), ZERO_DELTA);
-        assertEquals(0, mDisplayDeviceConfig.getScreenDarkeningMinThresholdIdle(), ZERO_DELTA);
-
-        // screen levels will be considered "old screen brightness scale"
-        // and therefore will divide by 255
-        assertArrayEquals(new float[]{0, 42 / 255f, 43 / 255f},
-                mDisplayDeviceConfig.getScreenBrighteningLevels(), SMALL_DELTA);
-        assertArrayEquals(new float[]{35, 36, 37},
-                mDisplayDeviceConfig.getScreenBrighteningPercentages(), ZERO_DELTA);
-        assertArrayEquals(new float[]{0, 42 / 255f, 43 / 255f},
-                mDisplayDeviceConfig.getScreenDarkeningLevels(), SMALL_DELTA);
-        assertArrayEquals(new float[]{37, 38, 39},
-                mDisplayDeviceConfig.getScreenDarkeningPercentages(), ZERO_DELTA);
-
-        assertArrayEquals(new float[]{0, 30, 31},
-                mDisplayDeviceConfig.getAmbientBrighteningLevels(), ZERO_DELTA);
-        assertArrayEquals(new float[]{27, 28, 29},
-                mDisplayDeviceConfig.getAmbientBrighteningPercentages(), ZERO_DELTA);
-        assertArrayEquals(new float[]{0, 30, 31},
-                mDisplayDeviceConfig.getAmbientDarkeningLevels(), ZERO_DELTA);
-        assertArrayEquals(new float[]{29, 30, 31},
-                mDisplayDeviceConfig.getAmbientDarkeningPercentages(), ZERO_DELTA);
-
-        assertArrayEquals(new float[]{0, 42 / 255f, 43 / 255f},
-                mDisplayDeviceConfig.getScreenBrighteningLevelsIdle(), SMALL_DELTA);
-        assertArrayEquals(new float[]{35, 36, 37},
-                mDisplayDeviceConfig.getScreenBrighteningPercentagesIdle(), ZERO_DELTA);
-        assertArrayEquals(new float[]{0, 42 / 255f, 43 / 255f},
-                mDisplayDeviceConfig.getScreenDarkeningLevelsIdle(), SMALL_DELTA);
-        assertArrayEquals(new float[]{37, 38, 39},
-                mDisplayDeviceConfig.getScreenDarkeningPercentagesIdle(), ZERO_DELTA);
-
-        assertArrayEquals(new float[]{0, 30, 31},
-                mDisplayDeviceConfig.getAmbientBrighteningLevelsIdle(), ZERO_DELTA);
-        assertArrayEquals(new float[]{27, 28, 29},
-                mDisplayDeviceConfig.getAmbientBrighteningPercentagesIdle(), ZERO_DELTA);
-        assertArrayEquals(new float[]{0, 30, 31},
-                mDisplayDeviceConfig.getAmbientDarkeningLevelsIdle(), ZERO_DELTA);
-        assertArrayEquals(new float[]{29, 30, 31},
-                mDisplayDeviceConfig.getAmbientDarkeningPercentagesIdle(), ZERO_DELTA);
-        assertEquals(mDisplayDeviceConfig.getDefaultLowBlockingZoneRefreshRate(),
-                DEFAULT_LOW_BLOCKING_ZONE_REFRESH_RATE);
-        assertEquals(mDisplayDeviceConfig.getDefaultHighBlockingZoneRefreshRate(),
-                DEFAULT_HIGH_BLOCKING_ZONE_REFRESH_RATE);
-        assertEquals(mDisplayDeviceConfig.getDefaultPeakRefreshRate(), DEFAULT_PEAK_REFRESH_RATE);
-        assertEquals(mDisplayDeviceConfig.getDefaultRefreshRate(), DEFAULT_REFRESH_RATE);
-        assertEquals(0, mDisplayDeviceConfig.getRefreshRangeProfiles().size());
-        assertEquals(mDisplayDeviceConfig.getDefaultRefreshRateInHbmSunlight(),
-                DEFAULT_REFRESH_RATE_IN_HBM_SUNLIGHT);
-        assertEquals(mDisplayDeviceConfig.getDefaultRefreshRateInHbmHdr(),
-                DEFAULT_REFRESH_RATE_IN_HBM_HDR);
-        assertArrayEquals(mDisplayDeviceConfig.getLowDisplayBrightnessThresholds(),
-                LOW_BRIGHTNESS_THRESHOLD_OF_PEAK_REFRESH_RATE);
-        assertArrayEquals(mDisplayDeviceConfig.getLowAmbientBrightnessThresholds(),
-                LOW_AMBIENT_THRESHOLD_OF_PEAK_REFRESH_RATE);
-        assertArrayEquals(mDisplayDeviceConfig.getHighDisplayBrightnessThresholds(),
-                HIGH_BRIGHTNESS_THRESHOLD_OF_PEAK_REFRESH_RATE);
-        assertArrayEquals(mDisplayDeviceConfig.getHighAmbientBrightnessThresholds(),
-                HIGH_AMBIENT_THRESHOLD_OF_PEAK_REFRESH_RATE);
-
-        // Todo: Add asserts for ThermalBrightnessThrottlingData, DensityMapping,
-        // HighBrightnessModeData AmbientLightSensor, RefreshRateLimitations and ProximitySensor.
-    }
-
-    @Test
-    public void testThermalRefreshRateThrottlingFromDisplayConfig() throws IOException {
-        setupDisplayDeviceConfigFromDisplayConfigFile();
-
-        SparseArray<SurfaceControl.RefreshRateRange> defaultMap =
-                mDisplayDeviceConfig.getThermalRefreshRateThrottlingData(null);
-        assertNotNull(defaultMap);
-        assertEquals(2, defaultMap.size());
-        assertEquals(30, defaultMap.get(Temperature.THROTTLING_CRITICAL).min, SMALL_DELTA);
-        assertEquals(60, defaultMap.get(Temperature.THROTTLING_CRITICAL).max, SMALL_DELTA);
-        assertEquals(0, defaultMap.get(Temperature.THROTTLING_SHUTDOWN).min, SMALL_DELTA);
-        assertEquals(30, defaultMap.get(Temperature.THROTTLING_SHUTDOWN).max, SMALL_DELTA);
-
-        SparseArray<SurfaceControl.RefreshRateRange> testMap =
-                mDisplayDeviceConfig.getThermalRefreshRateThrottlingData("test");
-        assertNotNull(testMap);
-        assertEquals(1, testMap.size());
-        assertEquals(60, testMap.get(Temperature.THROTTLING_EMERGENCY).min, SMALL_DELTA);
-        assertEquals(90, testMap.get(Temperature.THROTTLING_EMERGENCY).max, SMALL_DELTA);
-    }
-
-    private String getRefreshThermalThrottlingMaps() {
-        return "<refreshRateThrottlingMap>\n"
-               + "    <refreshRateThrottlingPoint>\n"
-               + "        <thermalStatus>critical</thermalStatus>\n"
-               + "        <refreshRateRange>\n"
-               + "            <minimum>30</minimum>\n"
-               + "            <maximum>60</maximum>\n"
-               + "        </refreshRateRange>\n"
-               + "    </refreshRateThrottlingPoint>\n"
-               + "    <refreshRateThrottlingPoint>\n"
-               + "        <thermalStatus>shutdown</thermalStatus>\n"
-               + "        <refreshRateRange>\n"
-               + "            <minimum>0</minimum>\n"
-               + "            <maximum>30</maximum>\n"
-               + "        </refreshRateRange>\n"
-               + "    </refreshRateThrottlingPoint>\n"
-               + "</refreshRateThrottlingMap>\n"
-               + "<refreshRateThrottlingMap id=\"test\">\n"
-               + "    <refreshRateThrottlingPoint>\n"
-               + "        <thermalStatus>emergency</thermalStatus>\n"
-               + "        <refreshRateRange>\n"
-               + "            <minimum>60</minimum>\n"
-               + "            <maximum>90</maximum>\n"
-               + "        </refreshRateRange>\n"
-               + "    </refreshRateThrottlingPoint>\n"
-               + "</refreshRateThrottlingMap>\n";
-    }
-
-    private String getContent() {
-        return "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
-                + "<displayConfiguration>\n"
-                +   "<name>Example Display</name>"
-                +   "<screenBrightnessMap>\n"
-                +       "<point>\n"
-                +           "<value>0.0</value>\n"
-                +           "<nits>2.0</nits>\n"
-                +       "</point>\n"
-                +       "<point>\n"
-                +           "<value>0.62</value>\n"
-                +           "<nits>500.0</nits>\n"
-                +       "</point>\n"
-                +       "<point>\n"
-                +           "<value>1.0</value>\n"
-                +           "<nits>800.0</nits>\n"
-                +       "</point>\n"
-                +   "</screenBrightnessMap>\n"
-                +   "<autoBrightness>\n"
-                +       "<brighteningLightDebounceMillis>2000</brighteningLightDebounceMillis>\n"
-                +       "<darkeningLightDebounceMillis>1000</darkeningLightDebounceMillis>\n"
-                +       "<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"
-                +   "</autoBrightness>\n"
-                +   "<highBrightnessMode enabled=\"true\">\n"
-                +       "<transitionPoint>0.62</transitionPoint>\n"
-                +       "<minimumLux>10000</minimumLux>\n"
-                +       "<timing>\n"
-                +           "<!-- allow for 5 minutes out of every 30 minutes -->\n"
-                +           "<timeWindowSecs>1800</timeWindowSecs>\n"
-                +           "<timeMaxSecs>300</timeMaxSecs>\n"
-                +           "<timeMinSecs>60</timeMinSecs>\n"
-                +       "</timing>\n"
-                +       "<refreshRate>\n"
-                +           "<minimum>120</minimum>\n"
-                +           "<maximum>120</maximum>\n"
-                +       "</refreshRate>\n"
-                +       "<thermalStatusLimit>light</thermalStatusLimit>\n"
-                +       "<allowInLowPowerMode>false</allowInLowPowerMode>\n"
-                +       "<sdrHdrRatioMap>\n"
-                +            "<point>\n"
-                +                "<sdrNits>2.000</sdrNits>\n"
-                +                "<hdrRatio>4.000</hdrRatio>\n"
-                +            "</point>\n"
-                +            "<point>\n"
-                +                "<sdrNits>500.0</sdrNits>\n"
-                +                "<hdrRatio>1.6</hdrRatio>\n"
-                +            "</point>\n"
-                +       "</sdrHdrRatioMap>\n"
-                +   "</highBrightnessMode>\n"
-                +   "<screenOffBrightnessSensor>\n"
-                +       "<type>sensor_12345</type>\n"
-                +       "<name>Sensor 12345</name>\n"
-                +   "</screenOffBrightnessSensor>\n"
-                +   "<ambientBrightnessChangeThresholds>\n"
-                +       "<brighteningThresholds>\n"
-                +           "<minimum>10</minimum>\n"
-                +           "<brightnessThresholdPoints>\n"
-                +               "<brightnessThresholdPoint>\n"
-                +                   "<threshold>0</threshold><percentage>13</percentage>\n"
-                +               "</brightnessThresholdPoint>\n"
-                +               "<brightnessThresholdPoint>\n"
-                +                   "<threshold>100</threshold><percentage>14</percentage>\n"
-                +               "</brightnessThresholdPoint>\n"
-                +               "<brightnessThresholdPoint>\n"
-                +                   "<threshold>200</threshold><percentage>15</percentage>\n"
-                +               "</brightnessThresholdPoint>\n"
-                +           "</brightnessThresholdPoints>\n"
-                +       "</brighteningThresholds>\n"
-                +       "<darkeningThresholds>\n"
-                +           "<minimum>30</minimum>\n"
-                +           "<brightnessThresholdPoints>\n"
-                +               "<brightnessThresholdPoint>\n"
-                +                   "<threshold>0</threshold><percentage>15</percentage>\n"
-                +               "</brightnessThresholdPoint>\n"
-                +               "<brightnessThresholdPoint>\n"
-                +                   "<threshold>300</threshold><percentage>16</percentage>\n"
-                +               "</brightnessThresholdPoint>\n"
-                +               "<brightnessThresholdPoint>\n"
-                +                   "<threshold>400</threshold><percentage>17</percentage>\n"
-                +               "</brightnessThresholdPoint>\n"
-                +           "</brightnessThresholdPoints>\n"
-                +       "</darkeningThresholds>\n"
-                +   "</ambientBrightnessChangeThresholds>\n"
-                +   "<displayBrightnessChangeThresholds>\n"
-                +       "<brighteningThresholds>\n"
-                +           "<minimum>0.1</minimum>\n"
-                +           "<brightnessThresholdPoints>\n"
-                +               "<brightnessThresholdPoint>\n"
-                +                   "<threshold>0</threshold>\n"
-                +                   "<percentage>9</percentage>\n"
-                +               "</brightnessThresholdPoint>\n"
-                +               "<brightnessThresholdPoint>\n"
-                +                   "<threshold>0.10</threshold>\n"
-                +                   "<percentage>10</percentage>\n"
-                +               "</brightnessThresholdPoint>\n"
-                +               "<brightnessThresholdPoint>\n"
-                +                   "<threshold>0.20</threshold>\n"
-                +                   "<percentage>11</percentage>\n"
-                +               "</brightnessThresholdPoint>\n"
-                +           "</brightnessThresholdPoints>\n"
-                +       "</brighteningThresholds>\n"
-                +       "<darkeningThresholds>\n"
-                +           "<minimum>0.3</minimum>\n"
-                +           "<brightnessThresholdPoints>\n"
-                +               "<brightnessThresholdPoint>\n"
-                +                   "<threshold>0</threshold><percentage>11</percentage>\n"
-                +               "</brightnessThresholdPoint>\n"
-                +               "<brightnessThresholdPoint>\n"
-                +                   "<threshold>0.11</threshold><percentage>12</percentage>\n"
-                +               "</brightnessThresholdPoint>\n"
-                +               "<brightnessThresholdPoint>\n"
-                +                   "<threshold>0.21</threshold><percentage>13</percentage>\n"
-                +               "</brightnessThresholdPoint>\n"
-                +           "</brightnessThresholdPoints>\n"
-                +       "</darkeningThresholds>\n"
-                +   "</displayBrightnessChangeThresholds>\n"
-                +   "<ambientBrightnessChangeThresholdsIdle>\n"
-                +       "<brighteningThresholds>\n"
-                +           "<minimum>20</minimum>\n"
-                +           "<brightnessThresholdPoints>\n"
-                +               "<brightnessThresholdPoint>\n"
-                +                   "<threshold>0</threshold><percentage>21</percentage>\n"
-                +               "</brightnessThresholdPoint>\n"
-                +               "<brightnessThresholdPoint>\n"
-                +                   "<threshold>500</threshold><percentage>22</percentage>\n"
-                +               "</brightnessThresholdPoint>\n"
-                +               "<brightnessThresholdPoint>\n"
-                +                   "<threshold>600</threshold><percentage>23</percentage>\n"
-                +               "</brightnessThresholdPoint>\n"
-                +           "</brightnessThresholdPoints>\n"
-                +       "</brighteningThresholds>\n"
-                +       "<darkeningThresholds>\n"
-                +           "<minimum>40</minimum>\n"
-                +           "<brightnessThresholdPoints>\n"
-                +               "<brightnessThresholdPoint>\n"
-                +                   "<threshold>0</threshold><percentage>23</percentage>\n"
-                +               "</brightnessThresholdPoint>\n"
-                +               "<brightnessThresholdPoint>\n"
-                +                   "<threshold>700</threshold><percentage>24</percentage>\n"
-                +               "</brightnessThresholdPoint>\n"
-                +               "<brightnessThresholdPoint>\n"
-                +                   "<threshold>800</threshold><percentage>25</percentage>\n"
-                +               "</brightnessThresholdPoint>\n"
-                +           "</brightnessThresholdPoints>\n"
-                +       "</darkeningThresholds>\n"
-                +   "</ambientBrightnessChangeThresholdsIdle>\n"
-                +   "<displayBrightnessChangeThresholdsIdle>\n"
-                +       "<brighteningThresholds>\n"
-                +           "<minimum>0.2</minimum>\n"
-                +           "<brightnessThresholdPoints>\n"
-                +               "<brightnessThresholdPoint>\n"
-                +                   "<threshold>0</threshold><percentage>17</percentage>\n"
-                +               "</brightnessThresholdPoint>\n"
-                +               "<brightnessThresholdPoint>\n"
-                +                   "<threshold>0.12</threshold><percentage>18</percentage>\n"
-                +               "</brightnessThresholdPoint>\n"
-                +               "<brightnessThresholdPoint>\n"
-                +                   "<threshold>0.22</threshold><percentage>19</percentage>\n"
-                +               "</brightnessThresholdPoint>\n"
-                +           "</brightnessThresholdPoints>\n"
-                +       "</brighteningThresholds>\n"
-                +       "<darkeningThresholds>\n"
-                +           "<minimum>0.4</minimum>\n"
-                +           "<brightnessThresholdPoints>\n"
-                +               "<brightnessThresholdPoint>\n"
-                +                   "<threshold>0</threshold><percentage>19</percentage>\n"
-                +               "</brightnessThresholdPoint>\n"
-                +               "<brightnessThresholdPoint>\n"
-                +                   "<threshold>0.13</threshold><percentage>20</percentage>\n"
-                +               "</brightnessThresholdPoint>\n"
-                +               "<brightnessThresholdPoint>\n"
-                +                   "<threshold>0.23</threshold><percentage>21</percentage>\n"
-                +               "</brightnessThresholdPoint>\n"
-                +           "</brightnessThresholdPoints>\n"
-                +       "</darkeningThresholds>\n"
-                +   "</displayBrightnessChangeThresholdsIdle>\n"
-                +   "<screenBrightnessRampFastDecrease>0.01</screenBrightnessRampFastDecrease> "
-                +   "<screenBrightnessRampFastIncrease>0.02</screenBrightnessRampFastIncrease>  "
-                +   "<screenBrightnessRampSlowDecrease>0.03</screenBrightnessRampSlowDecrease>"
-                +   "<screenBrightnessRampSlowIncrease>0.04</screenBrightnessRampSlowIncrease>"
-                +   "<screenBrightnessRampIncreaseMaxMillis>"
-                +       "2000"
-                +   "</screenBrightnessRampIncreaseMaxMillis>"
-                +   "<screenBrightnessRampDecreaseMaxMillis>"
-                +       "3000"
-                +   "</screenBrightnessRampDecreaseMaxMillis>"
-                +   "<ambientLightHorizonLong>5000</ambientLightHorizonLong>\n"
-                +   "<ambientLightHorizonShort>50</ambientLightHorizonShort>\n"
-                +   "<screenBrightnessRampIncreaseMaxMillis>"
-                +       "2000"
-                +   "</screenBrightnessRampIncreaseMaxMillis>\n"
-                +   "<thermalThrottling>\n"
-                +       "<brightnessThrottlingMap>\n"
-                +           "<brightnessThrottlingPoint>\n"
-                +               "<thermalStatus>light</thermalStatus>\n"
-                +               "<brightness>0.4</brightness>\n"
-                +           "</brightnessThrottlingPoint>\n"
-                +           "<brightnessThrottlingPoint>\n"
-                +               "<thermalStatus>moderate</thermalStatus>\n"
-                +               "<brightness>0.3</brightness>\n"
-                +           "</brightnessThrottlingPoint>\n"
-                +           "<brightnessThrottlingPoint>\n"
-                +               "<thermalStatus>severe</thermalStatus>\n"
-                +               "<brightness>0.2</brightness>\n"
-                +           "</brightnessThrottlingPoint>\n"
-                +           "<brightnessThrottlingPoint>\n"
-                +               "<thermalStatus>critical</thermalStatus>\n"
-                +               "<brightness>0.1</brightness>\n"
-                +           "</brightnessThrottlingPoint>\n"
-                +           "<brightnessThrottlingPoint>\n"
-                +               "<thermalStatus>emergency</thermalStatus>\n"
-                +               "<brightness>0.05</brightness>\n"
-                +           "</brightnessThrottlingPoint>\n"
-                +           "<brightnessThrottlingPoint>\n"
-                +               "<thermalStatus>shutdown</thermalStatus>\n"
-                +               "<brightness>0.025</brightness>\n"
-                +           "</brightnessThrottlingPoint>\n"
-                +       "</brightnessThrottlingMap>\n"
-                +       "<brightnessThrottlingMap id=\"concurrent\">\n"
-                +           "<brightnessThrottlingPoint>\n"
-                +               "<thermalStatus>light</thermalStatus>\n"
-                +               "<brightness>0.2</brightness>\n"
-                +           "</brightnessThrottlingPoint>\n"
-                +           "<brightnessThrottlingPoint>\n"
-                +               "<thermalStatus>moderate</thermalStatus>\n"
-                +               "<brightness>0.15</brightness>\n"
-                +           "</brightnessThrottlingPoint>\n"
-                +           "<brightnessThrottlingPoint>\n"
-                +               "<thermalStatus>severe</thermalStatus>\n"
-                +               "<brightness>0.1</brightness>\n"
-                +           "</brightnessThrottlingPoint>\n"
-                +           "<brightnessThrottlingPoint>\n"
-                +               "<thermalStatus>critical</thermalStatus>\n"
-                +               "<brightness>0.05</brightness>\n"
-                +           "</brightnessThrottlingPoint>\n"
-                +           "<brightnessThrottlingPoint>\n"
-                +               "<thermalStatus>emergency</thermalStatus>\n"
-                +               "<brightness>0.025</brightness>\n"
-                +           "</brightnessThrottlingPoint>\n"
-                +           "<brightnessThrottlingPoint>\n"
-                +               "<thermalStatus>shutdown</thermalStatus>\n"
-                +               "<brightness>0.0125</brightness>\n"
-                +           "</brightnessThrottlingPoint>\n"
-                +       "</brightnessThrottlingMap>\n"
-                +  getRefreshThermalThrottlingMaps()
-                +   "</thermalThrottling>\n"
-                +   "<refreshRate>\n"
-                +       "<defaultRefreshRate>45</defaultRefreshRate>\n"
-                +       "<defaultPeakRefreshRate>85</defaultPeakRefreshRate>\n"
-                +       "<refreshRateZoneProfiles>"
-                +           "<refreshRateZoneProfile id=\"test1\">"
-                +               "<refreshRateRange>\n"
-                +                   "<minimum>60</minimum>\n"
-                +                   "<maximum>60</maximum>\n"
-                +               "</refreshRateRange>\n"
-                +           "</refreshRateZoneProfile>\n"
-                +           "<refreshRateZoneProfile id=\"test2\">"
-                +               "<refreshRateRange>\n"
-                +                   "<minimum>80</minimum>\n"
-                +                   "<maximum>90</maximum>\n"
-                +               "</refreshRateRange>\n"
-                +           "</refreshRateZoneProfile>\n"
-                +       "</refreshRateZoneProfiles>"
-                +       "<defaultRefreshRateInHbmHdr>82</defaultRefreshRateInHbmHdr>\n"
-                +       "<defaultRefreshRateInHbmSunlight>83</defaultRefreshRateInHbmSunlight>\n"
-                +       "<lowerBlockingZoneConfigs>\n"
-                +           "<defaultRefreshRate>75</defaultRefreshRate>\n"
-                +           "<blockingZoneThreshold>\n"
-                +               "<displayBrightnessPoint>\n"
-                +                   "<lux>50</lux>\n"
-                // This number will be rounded to integer when read by the system
-                +                   "<nits>45.3</nits>\n"
-                +               "</displayBrightnessPoint>\n"
-                +               "<displayBrightnessPoint>\n"
-                +                   "<lux>60</lux>\n"
-                // This number will be rounded to integer when read by the system
-                +                   "<nits>55.2</nits>\n"
-                +               "</displayBrightnessPoint>\n"
-                +           "</blockingZoneThreshold>\n"
-                +       "</lowerBlockingZoneConfigs>\n"
-                +       "<higherBlockingZoneConfigs>\n"
-                +           "<defaultRefreshRate>90</defaultRefreshRate>\n"
-                +           "<blockingZoneThreshold>\n"
-                +               "<displayBrightnessPoint>\n"
-                +                   "<lux>70</lux>\n"
-                // This number will be rounded to integer when read by the system
-                +                   "<nits>65.6</nits>\n"
-                +               "</displayBrightnessPoint>\n"
-                +               "<displayBrightnessPoint>\n"
-                +                   "<lux>80</lux>\n"
-                // This number will be rounded to integer when read by the system
-                +                   "<nits>75</nits>\n"
-                +               "</displayBrightnessPoint>\n"
-                +           "</blockingZoneThreshold>\n"
-                +       "</higherBlockingZoneConfigs>\n"
-                +   "</refreshRate>\n"
-                +   "<screenOffBrightnessSensorValueToLux>\n"
-                +       "<item>-1</item>\n"
-                +       "<item>10</item>\n"
-                +       "<item>20</item>\n"
-                +       "<item>30</item>\n"
-                +       "<item>40</item>\n"
-                +   "</screenOffBrightnessSensorValueToLux>\n"
-                +   "<usiVersion>\n"
-                +       "<majorVersion>2</majorVersion>\n"
-                +       "<minorVersion>0</minorVersion>\n"
-                +   "</usiVersion>\n"
-                + "</displayConfiguration>\n";
-    }
-
-    private void mockDeviceConfigs() {
-        when(mResources.getFloat(com.android.internal.R.dimen
-                .config_screenBrightnessSettingDefaultFloat)).thenReturn(0.5f);
-        when(mResources.getFloat(com.android.internal.R.dimen
-                .config_screenBrightnessSettingMaximumFloat)).thenReturn(1.0f);
-    }
-
-    private void setupDisplayDeviceConfigFromDisplayConfigFile() throws IOException {
-        Path tempFile = Files.createTempFile("display_config", ".tmp");
-        Files.write(tempFile, getContent().getBytes(StandardCharsets.UTF_8));
-        mDisplayDeviceConfig = new DisplayDeviceConfig(mContext);
-        mDisplayDeviceConfig.initFromFile(tempFile.toFile());
-    }
-
-    private void setupDisplayDeviceConfigFromConfigResourceFile() {
-        TypedArray screenBrightnessNits = createFloatTypedArray(new float[]{2.0f, 250.0f, 650.0f});
-        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};
-        when(mResources.getIntArray(
-                com.android.internal.R.array.config_autoBrightnessLevels))
-                .thenReturn(screenBrightnessLevelLux);
-
-        // Thresholds
-        // Config.xml requires the levels arrays to be of length N and the thresholds arrays to be
-        // of length N+1
-        when(mResources.getIntArray(com.android.internal.R.array.config_ambientThresholdLevels))
-                .thenReturn(new int[]{30, 31});
-        when(mResources.getIntArray(com.android.internal.R.array.config_screenThresholdLevels))
-                .thenReturn(new int[]{42, 43});
-        when(mResources.getIntArray(
-                com.android.internal.R.array.config_ambientBrighteningThresholds))
-                .thenReturn(new int[]{270, 280, 290});
-        when(mResources.getIntArray(com.android.internal.R.array.config_ambientDarkeningThresholds))
-                .thenReturn(new int[]{290, 300, 310});
-        when(mResources.getIntArray(R.array.config_screenBrighteningThresholds))
-                .thenReturn(new int[]{350, 360, 370});
-        when(mResources.getIntArray(R.array.config_screenDarkeningThresholds))
-                .thenReturn(new int[]{370, 380, 390});
-
-        // Configs related to refresh rates and blocking zones
-        when(mResources.getInteger(R.integer.config_defaultPeakRefreshRate))
-                .thenReturn(DEFAULT_PEAK_REFRESH_RATE);
-        when(mResources.getInteger(R.integer.config_defaultRefreshRate))
-                .thenReturn(DEFAULT_REFRESH_RATE);
-        when(mResources.getInteger(R.integer.config_fixedRefreshRateInHighZone))
-            .thenReturn(DEFAULT_HIGH_BLOCKING_ZONE_REFRESH_RATE);
-        when(mResources.getInteger(R.integer.config_defaultRefreshRateInZone))
-            .thenReturn(DEFAULT_LOW_BLOCKING_ZONE_REFRESH_RATE);
-        when(mResources.getIntArray(R.array.config_brightnessThresholdsOfPeakRefreshRate))
-                .thenReturn(LOW_BRIGHTNESS_THRESHOLD_OF_PEAK_REFRESH_RATE);
-        when(mResources.getIntArray(R.array.config_ambientThresholdsOfPeakRefreshRate))
-                .thenReturn(LOW_AMBIENT_THRESHOLD_OF_PEAK_REFRESH_RATE);
-        when(mResources.getIntArray(
-                R.array.config_highDisplayBrightnessThresholdsOfFixedRefreshRate))
-                .thenReturn(HIGH_BRIGHTNESS_THRESHOLD_OF_PEAK_REFRESH_RATE);
-        when(mResources.getIntArray(
-                R.array.config_highAmbientBrightnessThresholdsOfFixedRefreshRate))
-                .thenReturn(HIGH_AMBIENT_THRESHOLD_OF_PEAK_REFRESH_RATE);
-        when(mResources.getInteger(
-            R.integer.config_defaultRefreshRateInHbmHdr))
-            .thenReturn(DEFAULT_REFRESH_RATE_IN_HBM_HDR);
-        when(mResources.getInteger(
-            R.integer.config_defaultRefreshRateInHbmSunlight))
-            .thenReturn(DEFAULT_REFRESH_RATE_IN_HBM_SUNLIGHT);
-
-        mDisplayDeviceConfig = DisplayDeviceConfig.create(mContext, true);
-    }
-
-    private TypedArray createFloatTypedArray(float[] vals) {
-        TypedArray mockArray = mock(TypedArray.class);
-        when(mockArray.length()).thenAnswer(invocation -> {
-            return vals.length;
-        });
-        when(mockArray.getFloat(anyInt(), anyFloat())).thenAnswer(invocation -> {
-            final float def = (float) invocation.getArguments()[1];
-            if (vals == null) {
-                return def;
-            }
-            int idx = (int) invocation.getArguments()[0];
-            if (idx >= 0 && idx < vals.length) {
-                return vals[idx];
-            } else {
-                return def;
-            }
-        });
-        return mockArray;
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
deleted file mode 100644
index 0e775d5..0000000
--- a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ /dev/null
@@ -1,2242 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.display;
-
-import static android.Manifest.permission.ADD_ALWAYS_UNLOCKED_DISPLAY;
-import static android.Manifest.permission.ADD_TRUSTED_DISPLAY;
-import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED;
-import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
-import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP;
-import static android.view.ContentRecordingSession.RECORD_CONTENT_DISPLAY;
-import static android.view.ContentRecordingSession.RECORD_CONTENT_TASK;
-
-import static com.android.server.display.VirtualDisplayAdapter.UNIQUE_ID_PREFIX;
-
-import static com.google.common.truth.Truth.assertThat;
-
-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.assertThrows;
-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.eq;
-import static org.mockito.ArgumentMatchers.nullable;
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.atLeastOnce;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.inOrder;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.app.PropertyInvalidatedCache;
-import android.companion.virtual.IVirtualDevice;
-import android.companion.virtual.IVirtualDeviceManager;
-import android.companion.virtual.VirtualDeviceManager;
-import android.compat.testing.PlatformCompatChangeRule;
-import android.content.Context;
-import android.content.ContextWrapper;
-import android.content.pm.PackageManager;
-import android.content.res.Resources;
-import android.graphics.Insets;
-import android.graphics.Rect;
-import android.hardware.display.BrightnessConfiguration;
-import android.hardware.display.Curve;
-import android.hardware.display.DisplayManager;
-import android.hardware.display.DisplayManagerGlobal;
-import android.hardware.display.DisplayManagerInternal;
-import android.hardware.display.DisplayViewport;
-import android.hardware.display.DisplayedContentSample;
-import android.hardware.display.DisplayedContentSamplingAttributes;
-import android.hardware.display.HdrConversionMode;
-import android.hardware.display.IDisplayManagerCallback;
-import android.hardware.display.IVirtualDisplayCallback;
-import android.hardware.display.VirtualDisplayConfig;
-import android.media.projection.IMediaProjection;
-import android.media.projection.IMediaProjectionManager;
-import android.os.Binder;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.MessageQueue;
-import android.os.Process;
-import android.os.RemoteException;
-import android.view.ContentRecordingSession;
-import android.view.Display;
-import android.view.DisplayCutout;
-import android.view.DisplayEventReceiver;
-import android.view.DisplayInfo;
-import android.view.Surface;
-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;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.internal.R;
-import com.android.server.LocalServices;
-import com.android.server.SystemService;
-import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
-import com.android.server.display.DisplayManagerService.DeviceStateListener;
-import com.android.server.display.DisplayManagerService.SyncRoot;
-import com.android.server.input.InputManagerInternal;
-import com.android.server.lights.LightsManager;
-import com.android.server.sensors.SensorManagerInternal;
-import com.android.server.wm.WindowManagerInternal;
-
-import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
-import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
-
-import com.google.common.collect.ImmutableMap;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TestRule;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
-import org.mockito.InOrder;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-
-import java.time.Duration;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.stream.LongStream;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class DisplayManagerServiceTest {
-    private static final int MSG_REGISTER_DEFAULT_DISPLAY_ADAPTERS = 1;
-    private static final long SHORT_DEFAULT_DISPLAY_TIMEOUT_MILLIS = 10;
-    private static final String VIRTUAL_DISPLAY_NAME = "Test Virtual Display";
-    private static final String PACKAGE_NAME = "com.android.frameworks.servicestests";
-    private static final long STANDARD_DISPLAY_EVENTS = DisplayManager.EVENT_FLAG_DISPLAY_ADDED
-                    | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
-                    | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED;
-
-    @Rule
-    public TestRule compatChangeRule = new PlatformCompatChangeRule();
-
-    private Context mContext;
-
-    private int mHdrConversionMode;
-
-    private int mPreferredHdrOutputType;
-
-    private final DisplayManagerService.Injector mShortMockedInjector =
-            new DisplayManagerService.Injector() {
-                @Override
-                VirtualDisplayAdapter getVirtualDisplayAdapter(SyncRoot syncRoot,
-                        Context context, Handler handler, DisplayAdapter.Listener listener) {
-                    return mMockVirtualDisplayAdapter;
-                }
-
-                @Override
-                LocalDisplayAdapter getLocalDisplayAdapter(SyncRoot syncRoot, Context context,
-                        Handler handler, DisplayAdapter.Listener displayAdapterListener) {
-                    return new LocalDisplayAdapter(syncRoot, context, handler,
-                            displayAdapterListener, new LocalDisplayAdapter.Injector() {
-                        @Override
-                        public LocalDisplayAdapter.SurfaceControlProxy getSurfaceControlProxy() {
-                            return mSurfaceControlProxy;
-                        }
-                    });
-                }
-
-                @Override
-                long getDefaultDisplayDelayTimeout() {
-                    return SHORT_DEFAULT_DISPLAY_TIMEOUT_MILLIS;
-                }
-            };
-
-    class BasicInjector extends DisplayManagerService.Injector {
-        @Override
-        IMediaProjectionManager getProjectionService() {
-            return mMockProjectionService;
-        }
-
-        @Override
-        VirtualDisplayAdapter getVirtualDisplayAdapter(SyncRoot syncRoot, Context context,
-                Handler handler, DisplayAdapter.Listener displayAdapterListener) {
-            return new VirtualDisplayAdapter(syncRoot, context, handler, displayAdapterListener,
-                    new VirtualDisplayAdapter.SurfaceControlDisplayFactory() {
-                        @Override
-                        public IBinder createDisplay(String name, boolean secure,
-                                float requestedRefreshRate) {
-                            return mMockDisplayToken;
-                        }
-
-                        @Override
-                        public void destroyDisplay(IBinder displayToken) {
-                        }
-                    });
-        }
-
-        @Override
-        LocalDisplayAdapter getLocalDisplayAdapter(SyncRoot syncRoot, Context context,
-                Handler handler, DisplayAdapter.Listener displayAdapterListener) {
-            return new LocalDisplayAdapter(syncRoot, context, handler,
-                    displayAdapterListener, new LocalDisplayAdapter.Injector() {
-                        @Override
-                        public LocalDisplayAdapter.SurfaceControlProxy getSurfaceControlProxy() {
-                            return mSurfaceControlProxy;
-                        }
-                    });
-        }
-
-        @Override
-        int setHdrConversionMode(int conversionMode, int preferredHdrOutputType,
-                int[] autoHdrTypes) {
-            mHdrConversionMode = conversionMode;
-            mPreferredHdrOutputType = preferredHdrOutputType;
-            return Display.HdrCapabilities.HDR_TYPE_INVALID;
-        }
-
-        @Override
-        int[] getSupportedHdrOutputTypes() {
-            return new int[]{};
-        }
-
-        @Override
-        int[] getHdrOutputTypesWithLatency() {
-            return new int[]{Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION};
-        }
-
-        boolean getHdrOutputConversionSupport() {
-            return true;
-        }
-   }
-
-    private final DisplayManagerService.Injector mBasicInjector = new BasicInjector();
-
-    @Mock IMediaProjectionManager mMockProjectionService;
-    @Mock IVirtualDeviceManager mIVirtualDeviceManager;
-    @Mock InputManagerInternal mMockInputManagerInternal;
-    @Mock VirtualDeviceManagerInternal mMockVirtualDeviceManagerInternal;
-    @Mock IVirtualDisplayCallback.Stub mMockAppToken;
-    @Mock IVirtualDisplayCallback.Stub mMockAppToken2;
-    @Mock WindowManagerInternal mMockWindowManagerInternal;
-    @Mock LightsManager mMockLightsManager;
-    @Mock VirtualDisplayAdapter mMockVirtualDisplayAdapter;
-    @Mock LocalDisplayAdapter.SurfaceControlProxy mSurfaceControlProxy;
-    @Mock IBinder mMockDisplayToken;
-    @Mock SensorManagerInternal mMockSensorManagerInternal;
-
-    @Captor ArgumentCaptor<ContentRecordingSession> mContentRecordingSessionCaptor;
-
-    @Before
-    public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
-
-        LocalServices.removeServiceForTest(InputManagerInternal.class);
-        LocalServices.addService(InputManagerInternal.class, mMockInputManagerInternal);
-        LocalServices.removeServiceForTest(WindowManagerInternal.class);
-        LocalServices.addService(WindowManagerInternal.class, mMockWindowManagerInternal);
-        LocalServices.removeServiceForTest(LightsManager.class);
-        LocalServices.addService(LightsManager.class, mMockLightsManager);
-        LocalServices.removeServiceForTest(SensorManagerInternal.class);
-        LocalServices.addService(SensorManagerInternal.class, mMockSensorManagerInternal);
-        LocalServices.removeServiceForTest(VirtualDeviceManagerInternal.class);
-        LocalServices.addService(
-                VirtualDeviceManagerInternal.class, mMockVirtualDeviceManagerInternal);
-
-        mContext = spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
-
-        VirtualDeviceManager vdm = new VirtualDeviceManager(mIVirtualDeviceManager, mContext);
-        when(mContext.getSystemService(VirtualDeviceManager.class)).thenReturn(vdm);
-        // Disable binder caches in this process.
-        PropertyInvalidatedCache.disableForTestMode();
-        setUpDisplay();
-    }
-
-    private void setUpDisplay() {
-        long[] ids = new long[] {100};
-        when(mSurfaceControlProxy.getPhysicalDisplayIds()).thenReturn(ids);
-        when(mSurfaceControlProxy.getPhysicalDisplayToken(anyLong()))
-                .thenReturn(mMockDisplayToken);
-        SurfaceControl.StaticDisplayInfo staticDisplayInfo = new SurfaceControl.StaticDisplayInfo();
-        staticDisplayInfo.isInternal = true;
-        when(mSurfaceControlProxy.getStaticDisplayInfo(anyLong()))
-                .thenReturn(staticDisplayInfo);
-        SurfaceControl.DynamicDisplayInfo dynamicDisplayMode =
-                new SurfaceControl.DynamicDisplayInfo();
-        SurfaceControl.DisplayMode displayMode = new SurfaceControl.DisplayMode();
-        displayMode.width = 100;
-        displayMode.height = 200;
-        displayMode.supportedHdrTypes = new int[]{1, 2};
-        dynamicDisplayMode.supportedDisplayModes = new SurfaceControl.DisplayMode[] {displayMode};
-        when(mSurfaceControlProxy.getDynamicDisplayInfo(anyLong()))
-                .thenReturn(dynamicDisplayMode);
-        when(mSurfaceControlProxy.getDesiredDisplayModeSpecs(mMockDisplayToken))
-                .thenReturn(new SurfaceControl.DesiredDisplayModeSpecs());
-    }
-
-    @Test
-    public void testCreateVirtualDisplay_sentToInputManager() throws RemoteException {
-        // This is to update the display device config such that DisplayManagerService can ignore
-        // the usage of SensorManager, which is available only after the PowerManagerService
-        // is ready.
-        resetConfigToIgnoreSensorManager(mContext);
-        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
-        registerDefaultDisplays(displayManager);
-        displayManager.systemReady(false /* safeMode */);
-        displayManager.windowManagerAndInputReady();
-
-        // This is effectively the DisplayManager service published to ServiceManager.
-        DisplayManagerService.BinderService bs = displayManager.new BinderService();
-
-        String uniqueId = "uniqueId --- Test";
-        String uniqueIdPrefix = UNIQUE_ID_PREFIX + mContext.getPackageName() + ":";
-        int width = 600;
-        int height = 800;
-        int dpi = 320;
-        int flags = DisplayManager.VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH;
-
-        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
-        final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
-                VIRTUAL_DISPLAY_NAME, width, height, dpi);
-        builder.setUniqueId(uniqueId);
-        builder.setFlags(flags);
-        int displayId = bs.createVirtualDisplay(builder.build(), mMockAppToken /* callback */,
-                null /* projection */, PACKAGE_NAME);
-        verify(mMockProjectionService, never()).setContentRecordingSession(any(),
-                nullable(IMediaProjection.class));
-
-        displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
-
-        // flush the handler
-        displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */);
-
-        ArgumentCaptor<List<DisplayViewport>> viewportCaptor = ArgumentCaptor.forClass(List.class);
-        verify(mMockInputManagerInternal).setDisplayViewports(viewportCaptor.capture());
-        List<DisplayViewport> viewports = viewportCaptor.getValue();
-
-        // Expect to receive at least 2 viewports: at least 1 internal, and 1 virtual
-        assertTrue(viewports.size() >= 2);
-
-        DisplayViewport virtualViewport = null;
-        DisplayViewport internalViewport = null;
-        for (int i = 0; i < viewports.size(); i++) {
-            DisplayViewport v = viewports.get(i);
-            switch (v.type) {
-                case DisplayViewport.VIEWPORT_INTERNAL: {
-                    // If more than one internal viewport, this will get overwritten several times,
-                    // which for the purposes of this test is fine.
-                    internalViewport = v;
-                    assertTrue(internalViewport.valid);
-                    break;
-                }
-                case DisplayViewport.VIEWPORT_EXTERNAL: {
-                    // External view port is present for auto devices in the form of instrument
-                    // cluster.
-                    break;
-                }
-                case DisplayViewport.VIEWPORT_VIRTUAL: {
-                    virtualViewport = v;
-                    break;
-                }
-            }
-        }
-        // INTERNAL viewport gets created upon access.
-        assertNotNull(internalViewport);
-        assertNotNull(virtualViewport);
-
-        // VIRTUAL
-        assertEquals(height, virtualViewport.deviceHeight);
-        assertEquals(width, virtualViewport.deviceWidth);
-        assertEquals(uniqueIdPrefix + uniqueId, virtualViewport.uniqueId);
-        assertEquals(displayId, virtualViewport.displayId);
-    }
-
-    @Test
-    public void testPhysicalViewports() {
-        // This is to update the display device config such that DisplayManagerService can ignore
-        // the usage of SensorManager, which is available only after the PowerManagerService
-        // is ready.
-        resetConfigToIgnoreSensorManager(mContext);
-        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
-        registerDefaultDisplays(displayManager);
-        displayManager.systemReady(false /* safeMode */);
-        displayManager.windowManagerAndInputReady();
-
-        // This is effectively the DisplayManager service published to ServiceManager.
-        DisplayManagerService.BinderService bs = displayManager.new BinderService();
-
-        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
-
-        final int[] displayIds = bs.getDisplayIds(/* includeDisabled= */ true);
-        final int size = displayIds.length;
-        assertTrue(size > 0);
-
-        Map<Integer, Integer> expectedDisplayTypeToViewPortTypeMapping = ImmutableMap.of(
-                Display.TYPE_INTERNAL, DisplayViewport.VIEWPORT_INTERNAL,
-                Display.TYPE_EXTERNAL, DisplayViewport.VIEWPORT_EXTERNAL
-        );
-        for (int i = 0; i < size; i++) {
-            DisplayInfo info = bs.getDisplayInfo(displayIds[i]);
-            assertTrue(expectedDisplayTypeToViewPortTypeMapping.keySet().contains(info.type));
-        }
-
-        displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
-
-        // flush the handler
-        displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */);
-
-        ArgumentCaptor<List<DisplayViewport>> viewportCaptor = ArgumentCaptor.forClass(List.class);
-        verify(mMockInputManagerInternal).setDisplayViewports(viewportCaptor.capture());
-        List<DisplayViewport> viewports = viewportCaptor.getValue();
-
-        // Due to the nature of foldables, we may have a different number of viewports than
-        // displays, just verify there's at least one.
-        final int viewportSize = viewports.size();
-        assertTrue(viewportSize > 0);
-
-        // Now verify that each viewport's displayId is valid.
-        Arrays.sort(displayIds);
-        for (int i = 0; i < viewportSize; i++) {
-            DisplayViewport viewport = viewports.get(i);
-            assertNotNull(viewport);
-            DisplayInfo displayInfo = bs.getDisplayInfo(viewport.displayId);
-            assertTrue(expectedDisplayTypeToViewPortTypeMapping.get(displayInfo.type)
-                    == viewport.type);
-            assertTrue(viewport.valid);
-            assertTrue(Arrays.binarySearch(displayIds, viewport.displayId) >= 0);
-        }
-    }
-
-    @Test
-    public void testCreateVirtualDisplayRotatesWithContent() throws Exception {
-        DisplayManagerService displayManager =
-                new DisplayManagerService(mContext, mBasicInjector);
-        registerDefaultDisplays(displayManager);
-
-        // This is effectively the DisplayManager service published to ServiceManager.
-        DisplayManagerService.BinderService bs = displayManager.new BinderService();
-
-        String uniqueId = "uniqueId --- Rotates With Content Test";
-        int width = 600;
-        int height = 800;
-        int dpi = 320;
-        int flags = DisplayManager.VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT;
-
-        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
-        final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
-                VIRTUAL_DISPLAY_NAME, width, height, dpi);
-        builder.setFlags(flags);
-        builder.setUniqueId(uniqueId);
-        int displayId = bs.createVirtualDisplay(builder.build(), mMockAppToken /* callback */,
-                null /* projection */, PACKAGE_NAME);
-        verify(mMockProjectionService, never()).setContentRecordingSession(any(),
-                nullable(IMediaProjection.class));
-
-        displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
-
-        // flush the handler
-        displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */);
-
-        DisplayDeviceInfo ddi = displayManager.getDisplayDeviceInfoInternal(displayId);
-        assertNotNull(ddi);
-        assertTrue((ddi.flags & DisplayDeviceInfo.FLAG_ROTATES_WITH_CONTENT) != 0);
-    }
-
-    @Test
-    public void testCreateVirtualDisplayOwnFocus() throws RemoteException {
-        DisplayManagerService displayManager =
-                new DisplayManagerService(mContext, mBasicInjector);
-        registerDefaultDisplays(displayManager);
-
-        // This is effectively the DisplayManager service published to ServiceManager.
-        DisplayManagerService.BinderService bs = displayManager.new BinderService();
-
-        String uniqueId = "uniqueId --- Own Focus Test";
-        int width = 600;
-        int height = 800;
-        int dpi = 320;
-        int flags = DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_FOCUS
-                | DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED;
-
-        when(mContext.checkCallingPermission(ADD_TRUSTED_DISPLAY)).thenReturn(
-                PackageManager.PERMISSION_GRANTED);
-        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
-        final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
-                VIRTUAL_DISPLAY_NAME, width, height, dpi);
-        builder.setFlags(flags);
-        builder.setUniqueId(uniqueId);
-        int displayId = bs.createVirtualDisplay(builder.build(), /* callback= */ mMockAppToken,
-                /* projection= */ null, PACKAGE_NAME);
-        verify(mMockProjectionService, never()).setContentRecordingSession(any(),
-                nullable(IMediaProjection.class));
-
-        displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
-
-        // flush the handler
-        displayManager.getDisplayHandler().runWithScissors(() -> {}, /* now= */ 0);
-
-        DisplayDeviceInfo ddi = displayManager.getDisplayDeviceInfoInternal(displayId);
-        assertNotNull(ddi);
-        assertTrue((ddi.flags & DisplayDeviceInfo.FLAG_OWN_FOCUS) != 0);
-    }
-
-    @Test
-    public void testCreateVirtualDisplayOwnFocus_nonTrustedDisplay() throws RemoteException {
-        DisplayManagerService displayManager =
-                new DisplayManagerService(mContext, mBasicInjector);
-        registerDefaultDisplays(displayManager);
-
-        // This is effectively the DisplayManager service published to ServiceManager.
-        DisplayManagerService.BinderService bs = displayManager.new BinderService();
-
-        String uniqueId = "uniqueId --- Own Focus Test -- nonTrustedDisplay";
-        int width = 600;
-        int height = 800;
-        int dpi = 320;
-        int flags = DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_FOCUS;
-
-        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
-        final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
-                VIRTUAL_DISPLAY_NAME, width, height, dpi);
-        builder.setFlags(flags);
-        builder.setUniqueId(uniqueId);
-        int displayId = bs.createVirtualDisplay(builder.build(), /* callback= */ mMockAppToken,
-                /* projection= */ null, PACKAGE_NAME);
-        verify(mMockProjectionService, never()).setContentRecordingSession(any(),
-                nullable(IMediaProjection.class));
-
-        displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
-
-        // flush the handler
-        displayManager.getDisplayHandler().runWithScissors(() -> {}, /* now= */ 0);
-
-        DisplayDeviceInfo ddi = displayManager.getDisplayDeviceInfoInternal(displayId);
-        assertNotNull(ddi);
-        assertTrue((ddi.flags & DisplayDeviceInfo.FLAG_OWN_FOCUS) == 0);
-    }
-
-    /**
-     * Tests that the virtual display is created along-side the default display.
-     */
-    @Test
-    public void testStartVirtualDisplayWithDefaultDisplay_Succeeds() throws Exception {
-        DisplayManagerService displayManager =
-                new DisplayManagerService(mContext, mShortMockedInjector);
-        registerDefaultDisplays(displayManager);
-        displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
-    }
-
-    /**
-     * Tests that we send the device state to window manager
-     */
-    @Test
-    public void testOnStateChanged_sendsStateChangedEventToWm() throws Exception {
-        DisplayManagerService displayManager =
-                new DisplayManagerService(mContext, mShortMockedInjector);
-        registerDefaultDisplays(displayManager);
-        displayManager.windowManagerAndInputReady();
-        displayManager.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
-        DeviceStateListener listener = displayManager.new DeviceStateListener();
-        Handler handler = displayManager.getDisplayHandler();
-        IDisplayManagerCallback displayChangesCallback = registerDisplayChangeCallback(
-                displayManager);
-
-        listener.onStateChanged(123);
-        waitForIdleHandler(handler);
-
-        InOrder inOrder = inOrder(mMockWindowManagerInternal, displayChangesCallback);
-        // Verify there are no display events before WM call
-        inOrder.verify(displayChangesCallback, never()).onDisplayEvent(anyInt(), anyInt());
-        inOrder.verify(mMockWindowManagerInternal).onDisplayManagerReceivedDeviceState(123);
-    }
-
-    /**
-     * Tests that there should be a display change notification to WindowManager to update its own
-     * internal state for things like display cutout when nonOverrideDisplayInfo is changed.
-     */
-    @Test
-    public void testShouldNotifyChangeWhenNonOverrideDisplayInfoChanged() throws Exception {
-        DisplayManagerService displayManager =
-                new DisplayManagerService(mContext, mShortMockedInjector);
-        registerDefaultDisplays(displayManager);
-        displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
-
-        // Add the FakeDisplayDevice
-        FakeDisplayDevice displayDevice = new FakeDisplayDevice();
-        DisplayDeviceInfo displayDeviceInfo = new DisplayDeviceInfo();
-        displayDeviceInfo.width = 100;
-        displayDeviceInfo.height = 200;
-        displayDeviceInfo.supportedModes = new Display.Mode[1];
-        displayDeviceInfo.supportedModes[0] = new Display.Mode(1, 100, 200, 60f);
-        displayDeviceInfo.modeId = 1;
-        final Rect zeroRect = new Rect();
-        displayDeviceInfo.displayCutout = new DisplayCutout(
-                Insets.of(0, 10, 0, 0),
-                zeroRect, new Rect(0, 0, 10, 10), zeroRect, zeroRect);
-        displayDeviceInfo.flags = DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY;
-        displayDeviceInfo.address = new TestUtils.TestDisplayAddress();
-        displayDevice.setDisplayDeviceInfo(displayDeviceInfo);
-        displayManager.getDisplayDeviceRepository()
-                .onDisplayDeviceEvent(displayDevice, DisplayAdapter.DISPLAY_DEVICE_EVENT_ADDED);
-
-        // Find the display id of the added FakeDisplayDevice
-        DisplayManagerService.BinderService bs = displayManager.new BinderService();
-        int displayId = getDisplayIdForDisplayDevice(displayManager, bs, displayDevice);
-        // Setup override DisplayInfo
-        DisplayInfo overrideInfo = bs.getDisplayInfo(displayId);
-        displayManager.setDisplayInfoOverrideFromWindowManagerInternal(displayId, overrideInfo);
-
-        FakeDisplayManagerCallback callback = registerDisplayListenerCallback(
-                displayManager, bs, displayDevice);
-
-        // Simulate DisplayDevice change
-        DisplayDeviceInfo displayDeviceInfo2 = new DisplayDeviceInfo();
-        displayDeviceInfo2.copyFrom(displayDeviceInfo);
-        displayDeviceInfo2.displayCutout = null;
-        displayDevice.setDisplayDeviceInfo(displayDeviceInfo2);
-        displayManager.getDisplayDeviceRepository()
-                .onDisplayDeviceEvent(displayDevice, DisplayAdapter.DISPLAY_DEVICE_EVENT_CHANGED);
-
-        Handler handler = displayManager.getDisplayHandler();
-        waitForIdleHandler(handler);
-        assertTrue(callback.mDisplayChangedCalled);
-    }
-
-    /**
-     * Tests that we get a Runtime exception when we cannot initialize the default display.
-     */
-    @Test
-    public void testStartVirtualDisplayWithDefDisplay_NoDefaultDisplay() throws Exception {
-        DisplayManagerService displayManager =
-                new DisplayManagerService(mContext, mShortMockedInjector);
-        Handler handler = displayManager.getDisplayHandler();
-        waitForIdleHandler(handler);
-
-        try {
-            displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
-        } catch (RuntimeException e) {
-            return;
-        }
-        fail("Expected DisplayManager to throw RuntimeException when it cannot initialize the"
-                + " default display");
-    }
-
-    /**
-     * Tests that we get a Runtime exception when we cannot initialize the virtual display.
-     */
-    @Test
-    public void testStartVirtualDisplayWithDefDisplay_NoVirtualDisplayAdapter() throws Exception {
-        DisplayManagerService displayManager = new DisplayManagerService(mContext,
-                new DisplayManagerService.Injector() {
-                    @Override
-                    VirtualDisplayAdapter getVirtualDisplayAdapter(SyncRoot syncRoot,
-                            Context context, Handler handler, DisplayAdapter.Listener listener) {
-                        return null;  // return null for the adapter.  This should cause a failure.
-                    }
-
-                    @Override
-                    long getDefaultDisplayDelayTimeout() {
-                        return SHORT_DEFAULT_DISPLAY_TIMEOUT_MILLIS;
-                    }
-                });
-        try {
-            displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
-        } catch (RuntimeException e) {
-            return;
-        }
-        fail("Expected DisplayManager to throw RuntimeException when it cannot initialize the"
-                + " virtual display adapter");
-    }
-
-    /**
-     * Tests that an exception is raised for too dark a brightness configuration.
-     */
-    @Test
-    public void testTooDarkBrightnessConfigurationThrowException() {
-        DisplayManagerService displayManager =
-                new DisplayManagerService(mContext, mShortMockedInjector);
-        Curve minimumBrightnessCurve = displayManager.getMinimumBrightnessCurveInternal();
-        float[] lux = minimumBrightnessCurve.getX();
-        float[] minimumNits = minimumBrightnessCurve.getY();
-        float[] nits = new float[minimumNits.length];
-        // For every control point, assert that making it slightly lower than the minimum throws an
-        // exception.
-        for (int i = 0; i < nits.length; i++) {
-            for (int j = 0; j < nits.length; j++) {
-                nits[j] = minimumNits[j];
-                if (j == i) {
-                    nits[j] -= 0.1f;
-                }
-                if (nits[j] < 0) {
-                    nits[j] = 0;
-                }
-            }
-            BrightnessConfiguration config =
-                    new BrightnessConfiguration.Builder(lux, nits).build();
-            Exception thrown = null;
-            try {
-                displayManager.validateBrightnessConfiguration(config);
-            } catch (IllegalArgumentException e) {
-                thrown = e;
-            }
-            assertNotNull("Building too dark a brightness configuration must throw an exception");
-        }
-    }
-
-    /**
-     * Tests that no exception is raised for not too dark a brightness configuration.
-     */
-    @Test
-    public void testBrightEnoughBrightnessConfigurationDoesNotThrowException() {
-        DisplayManagerService displayManager =
-                new DisplayManagerService(mContext, mShortMockedInjector);
-        Curve minimumBrightnessCurve = displayManager.getMinimumBrightnessCurveInternal();
-        float[] lux = minimumBrightnessCurve.getX();
-        float[] nits = minimumBrightnessCurve.getY();
-        BrightnessConfiguration config = new BrightnessConfiguration.Builder(lux, nits).build();
-        displayManager.validateBrightnessConfiguration(config);
-    }
-
-    /**
-     * Tests that null brightness configurations are alright.
-     */
-    @Test
-    public void testNullBrightnessConfiguration() {
-        DisplayManagerService displayManager =
-                new DisplayManagerService(mContext, mShortMockedInjector);
-        displayManager.validateBrightnessConfiguration(null);
-    }
-
-    /**
-     * Tests that collection of display color sampling results are sensible.
-     */
-    @Test
-    public void testDisplayedContentSampling() {
-        DisplayManagerService displayManager =
-                new DisplayManagerService(mContext, mShortMockedInjector);
-        registerDefaultDisplays(displayManager);
-
-        DisplayDeviceInfo ddi = displayManager.getDisplayDeviceInfoInternal(0);
-        assertNotNull(ddi);
-
-        DisplayedContentSamplingAttributes attr =
-                displayManager.getDisplayedContentSamplingAttributesInternal(0);
-        if (attr == null) return; //sampling not supported on device, skip remainder of test.
-
-        boolean enabled = displayManager.setDisplayedContentSamplingEnabledInternal(0, true, 0, 0);
-        assertTrue(enabled);
-
-        displayManager.setDisplayedContentSamplingEnabledInternal(0, false, 0, 0);
-        DisplayedContentSample sample = displayManager.getDisplayedContentSampleInternal(0, 0, 0);
-        assertNotNull(sample);
-
-        long numPixels = ddi.width * ddi.height * sample.getNumFrames();
-        long[] samples = sample.getSampleComponent(DisplayedContentSample.ColorComponent.CHANNEL0);
-        assertTrue(samples.length == 0 || LongStream.of(samples).sum() == numPixels);
-
-        samples = sample.getSampleComponent(DisplayedContentSample.ColorComponent.CHANNEL1);
-        assertTrue(samples.length == 0 || LongStream.of(samples).sum() == numPixels);
-
-        samples = sample.getSampleComponent(DisplayedContentSample.ColorComponent.CHANNEL2);
-        assertTrue(samples.length == 0 || LongStream.of(samples).sum() == numPixels);
-
-        samples = sample.getSampleComponent(DisplayedContentSample.ColorComponent.CHANNEL3);
-        assertTrue(samples.length == 0 || LongStream.of(samples).sum() == numPixels);
-    }
-
-    /**
-     * Tests that the virtual display is created with
-     * {@link VirtualDisplayConfig.Builder#setDisplayIdToMirror(int)}
-     */
-    @Test
-    @FlakyTest(bugId = 127687569)
-    public void testCreateVirtualDisplay_displayIdToMirror() throws Exception {
-        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
-        registerDefaultDisplays(displayManager);
-
-        // This is effectively the DisplayManager service published to ServiceManager.
-        DisplayManagerService.BinderService binderService = displayManager.new BinderService();
-        DisplayManagerService.LocalService localDisplayManager = displayManager.new LocalService();
-
-        final String uniqueId = "uniqueId --- displayIdToMirrorTest";
-        final int width = 600;
-        final int height = 800;
-        final int dpi = 320;
-
-        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
-        final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
-                VIRTUAL_DISPLAY_NAME, width, height, dpi);
-        builder.setUniqueId(uniqueId);
-        final int firstDisplayId = binderService.createVirtualDisplay(builder.build(),
-                mMockAppToken /* callback */, null /* projection */, PACKAGE_NAME);
-        verify(mMockProjectionService, never()).setContentRecordingSession(any(),
-                nullable(IMediaProjection.class));
-
-        // The second virtual display requests to mirror the first virtual display.
-        final String uniqueId2 = "uniqueId --- displayIdToMirrorTest #2";
-        when(mMockAppToken2.asBinder()).thenReturn(mMockAppToken2);
-        final VirtualDisplayConfig.Builder builder2 = new VirtualDisplayConfig.Builder(
-                VIRTUAL_DISPLAY_NAME, width, height, dpi).setUniqueId(uniqueId2);
-        builder2.setUniqueId(uniqueId2);
-        builder2.setDisplayIdToMirror(firstDisplayId);
-        final int secondDisplayId = binderService.createVirtualDisplay(builder2.build(),
-                mMockAppToken2 /* callback */, null /* projection */,
-                PACKAGE_NAME);
-        verify(mMockProjectionService, never()).setContentRecordingSession(any(),
-                nullable(IMediaProjection.class));
-        displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
-
-        // flush the handler
-        displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */);
-
-        // The displayId to mirror should be a default display if there is none initially.
-        assertEquals(localDisplayManager.getDisplayIdToMirror(firstDisplayId),
-                Display.DEFAULT_DISPLAY);
-        assertEquals(localDisplayManager.getDisplayIdToMirror(secondDisplayId),
-                firstDisplayId);
-    }
-
-    /** Tests that the virtual device is created in a device display group. */
-    @Test
-    public void createVirtualDisplay_addsDisplaysToDeviceDisplayGroups() throws Exception {
-        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
-        DisplayManagerInternal localService = displayManager.new LocalService();
-
-        registerDefaultDisplays(displayManager);
-        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
-
-        IVirtualDevice virtualDevice = mock(IVirtualDevice.class);
-        when(virtualDevice.getDeviceId()).thenReturn(1);
-        when(mIVirtualDeviceManager.isValidVirtualDeviceId(1)).thenReturn(true);
-        // Create a first virtual display. A display group should be created for this display on the
-        // virtual device.
-        final VirtualDisplayConfig.Builder builder1 =
-                new VirtualDisplayConfig.Builder(VIRTUAL_DISPLAY_NAME, 600, 800, 320)
-                        .setUniqueId("uniqueId --- device display group 1");
-
-        int displayId1 =
-                localService.createVirtualDisplay(
-                        builder1.build(),
-                        mMockAppToken /* callback */,
-                        virtualDevice /* virtualDeviceToken */,
-                        mock(DisplayWindowPolicyController.class),
-                        PACKAGE_NAME);
-        verify(mMockProjectionService, never()).setContentRecordingSession(any(),
-                nullable(IMediaProjection.class));
-        int displayGroupId1 = localService.getDisplayInfo(displayId1).displayGroupId;
-
-        // Create a second virtual display. This should be added to the previously created display
-        // group.
-        final VirtualDisplayConfig.Builder builder2 =
-                new VirtualDisplayConfig.Builder(VIRTUAL_DISPLAY_NAME, 600, 800, 320)
-                        .setUniqueId("uniqueId --- device display group 1");
-
-        int displayId2 =
-                localService.createVirtualDisplay(
-                        builder2.build(),
-                        mMockAppToken /* callback */,
-                        virtualDevice /* virtualDeviceToken */,
-                        mock(DisplayWindowPolicyController.class),
-                        PACKAGE_NAME);
-        verify(mMockProjectionService, never()).setContentRecordingSession(any(),
-                nullable(IMediaProjection.class));
-        int displayGroupId2 = localService.getDisplayInfo(displayId2).displayGroupId;
-
-        assertEquals(
-                "Both displays should be added to the same displayGroup.",
-                displayGroupId1,
-                displayGroupId2);
-    }
-
-    /**
-     * Tests that the virtual display is not added to the device display group when
-     * VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP is set.
-     */
-    @Test
-    public void createVirtualDisplay_addsDisplaysToOwnDisplayGroups() throws Exception {
-        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
-        DisplayManagerInternal localService = displayManager.new LocalService();
-
-        registerDefaultDisplays(displayManager);
-        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
-
-        IVirtualDevice virtualDevice = mock(IVirtualDevice.class);
-        when(virtualDevice.getDeviceId()).thenReturn(1);
-        when(mIVirtualDeviceManager.isValidVirtualDeviceId(1)).thenReturn(true);
-
-        // Create a first virtual display. A display group should be created for this display on the
-        // virtual device.
-        final VirtualDisplayConfig.Builder builder1 =
-                new VirtualDisplayConfig.Builder(VIRTUAL_DISPLAY_NAME, 600, 800, 320)
-                        .setUniqueId("uniqueId --- device display group");
-
-        int displayId1 =
-                localService.createVirtualDisplay(
-                        builder1.build(),
-                        mMockAppToken /* callback */,
-                        virtualDevice /* virtualDeviceToken */,
-                        mock(DisplayWindowPolicyController.class),
-                        PACKAGE_NAME);
-        verify(mMockProjectionService, never()).setContentRecordingSession(any(),
-                nullable(IMediaProjection.class));
-        int displayGroupId1 = localService.getDisplayInfo(displayId1).displayGroupId;
-
-        // Create a second virtual display. With the flag VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP,
-        // the display should not be added to the previously created display group.
-        final VirtualDisplayConfig.Builder builder2 =
-                new VirtualDisplayConfig.Builder(VIRTUAL_DISPLAY_NAME, 600, 800, 320)
-                        .setFlags(VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP)
-                        .setUniqueId("uniqueId --- own display group");
-
-        when(mIVirtualDeviceManager.isValidVirtualDeviceId(1)).thenReturn(true);
-
-        int displayId2 =
-                localService.createVirtualDisplay(
-                        builder2.build(),
-                        mMockAppToken /* callback */,
-                        virtualDevice /* virtualDeviceToken */,
-                        mock(DisplayWindowPolicyController.class),
-                        PACKAGE_NAME);
-        verify(mMockProjectionService, never()).setContentRecordingSession(any(),
-                nullable(IMediaProjection.class));
-        int displayGroupId2 = localService.getDisplayInfo(displayId2).displayGroupId;
-
-        assertNotEquals(
-                "Display 1 should be in the device display group and display 2 in its own display"
-                        + " group.",
-                displayGroupId1,
-                displayGroupId2);
-    }
-
-    @Test
-    public void displaysInDeviceOrOwnDisplayGroupShouldPreserveAlwaysUnlockedFlag()
-            throws Exception {
-        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
-        DisplayManagerInternal localService = displayManager.new LocalService();
-
-        registerDefaultDisplays(displayManager);
-        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
-
-        IVirtualDevice virtualDevice = mock(IVirtualDevice.class);
-        when(virtualDevice.getDeviceId()).thenReturn(1);
-        when(mIVirtualDeviceManager.isValidVirtualDeviceId(1)).thenReturn(true);
-
-        // Allow an ALWAYS_UNLOCKED display to be created.
-        when(mContext.checkCallingPermission(ADD_TRUSTED_DISPLAY))
-                .thenReturn(PackageManager.PERMISSION_GRANTED);
-
-        when(mContext.checkCallingPermission(ADD_ALWAYS_UNLOCKED_DISPLAY))
-                .thenReturn(PackageManager.PERMISSION_GRANTED);
-
-        // Create a virtual display in a device display group.
-        final VirtualDisplayConfig deviceDisplayGroupDisplayConfig =
-                new VirtualDisplayConfig.Builder(VIRTUAL_DISPLAY_NAME, 600, 800, 320)
-                        .setUniqueId("uniqueId --- device display group 1")
-                        .setFlags(VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED)
-                        .build();
-
-        int deviceDisplayGroupDisplayId =
-                localService.createVirtualDisplay(
-                        deviceDisplayGroupDisplayConfig,
-                        mMockAppToken /* callback */,
-                        virtualDevice /* virtualDeviceToken */,
-                        mock(DisplayWindowPolicyController.class),
-                        PACKAGE_NAME);
-        verify(mMockProjectionService, never()).setContentRecordingSession(any(),
-                nullable(IMediaProjection.class));
-
-        // Check that FLAG_ALWAYS_UNLOCKED is set.
-        assertNotEquals(
-                "FLAG_ALWAYS_UNLOCKED should be set for displays created in a device display"
-                        + " group.",
-                (displayManager.getDisplayDeviceInfoInternal(deviceDisplayGroupDisplayId).flags
-                        & DisplayDeviceInfo.FLAG_ALWAYS_UNLOCKED),
-                0);
-
-        // Create a virtual display in its own display group.
-        final VirtualDisplayConfig ownDisplayGroupConfig =
-                new VirtualDisplayConfig.Builder(VIRTUAL_DISPLAY_NAME, 600, 800, 320)
-                        .setUniqueId("uniqueId --- own display group 1")
-                        .setFlags(
-                                VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED
-                                        | VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP)
-                        .build();
-
-        int ownDisplayGroupDisplayId =
-                localService.createVirtualDisplay(
-                        ownDisplayGroupConfig,
-                        mMockAppToken /* callback */,
-                        virtualDevice /* virtualDeviceToken */,
-                        mock(DisplayWindowPolicyController.class),
-                        PACKAGE_NAME);
-        verify(mMockProjectionService, never()).setContentRecordingSession(any(),
-                nullable(IMediaProjection.class));
-
-        // Check that FLAG_ALWAYS_UNLOCKED is set.
-        assertNotEquals(
-                "FLAG_ALWAYS_UNLOCKED should be set for displays created in their own display"
-                        + " group.",
-                (displayManager.getDisplayDeviceInfoInternal(ownDisplayGroupDisplayId).flags
-                        & DisplayDeviceInfo.FLAG_ALWAYS_UNLOCKED),
-                0);
-
-        // Create a virtual display in a device display group.
-        final VirtualDisplayConfig defaultDisplayGroupConfig =
-                new VirtualDisplayConfig.Builder(VIRTUAL_DISPLAY_NAME, 600, 800, 320)
-                        .setUniqueId("uniqueId --- default display group 1")
-                        .setFlags(VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED)
-                        .build();
-
-        int defaultDisplayGroupDisplayId =
-                localService.createVirtualDisplay(
-                        defaultDisplayGroupConfig,
-                        mMockAppToken /* callback */,
-                        null /* virtualDeviceToken */,
-                        mock(DisplayWindowPolicyController.class),
-                        PACKAGE_NAME);
-        verify(mMockProjectionService, never()).setContentRecordingSession(any(),
-                nullable(IMediaProjection.class));
-
-        // Check that FLAG_ALWAYS_UNLOCKED is not set.
-        assertEquals(
-                "FLAG_ALWAYS_UNLOCKED should not be set for displays created in the default"
-                        + " display group.",
-                (displayManager.getDisplayDeviceInfoInternal(defaultDisplayGroupDisplayId).flags
-                        & DisplayDeviceInfo.FLAG_ALWAYS_UNLOCKED),
-                0);
-    }
-
-    @Test
-    public void testGetDisplayIdToMirror() throws Exception {
-        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
-        registerDefaultDisplays(displayManager);
-
-        // This is effectively the DisplayManager service published to ServiceManager.
-        DisplayManagerService.BinderService binderService = displayManager.new BinderService();
-        DisplayManagerService.LocalService localDisplayManager = displayManager.new LocalService();
-
-        final String uniqueId = "uniqueId --- displayIdToMirrorTest";
-        final int width = 600;
-        final int height = 800;
-        final int dpi = 320;
-
-        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
-        final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
-                VIRTUAL_DISPLAY_NAME, width, height, dpi)
-                .setUniqueId(uniqueId)
-                .setFlags(VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY);
-        final int firstDisplayId = binderService.createVirtualDisplay(builder.build(),
-                mMockAppToken /* callback */, null /* projection */, PACKAGE_NAME);
-        verify(mMockProjectionService, never()).setContentRecordingSession(any(),
-                nullable(IMediaProjection.class));
-
-        // The second virtual display requests to mirror the first virtual display.
-        final String uniqueId2 = "uniqueId --- displayIdToMirrorTest #2";
-        when(mMockAppToken2.asBinder()).thenReturn(mMockAppToken2);
-        final VirtualDisplayConfig.Builder builder2 = new VirtualDisplayConfig.Builder(
-                VIRTUAL_DISPLAY_NAME, width, height, dpi)
-                .setUniqueId(uniqueId2)
-                .setWindowManagerMirroringEnabled(true);
-        final int secondDisplayId = binderService.createVirtualDisplay(builder2.build(),
-                mMockAppToken2 /* callback */, null /* projection */,
-                PACKAGE_NAME);
-        verify(mMockProjectionService, never()).setContentRecordingSession(any(),
-                nullable(IMediaProjection.class));
-        displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
-
-        // flush the handler
-        displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */);
-
-        // The displayId to mirror should be a invalid since the display had flag OWN_CONTENT_ONLY
-        assertEquals(localDisplayManager.getDisplayIdToMirror(firstDisplayId),
-                Display.INVALID_DISPLAY);
-        // The second display has mirroring managed by WindowManager so the mirror displayId should
-        // be invalid.
-        assertEquals(localDisplayManager.getDisplayIdToMirror(secondDisplayId),
-                Display.INVALID_DISPLAY);
-    }
-
-    @Test
-    public void testCreateVirtualDisplay_isValidProjection_notValid()
-            throws RemoteException {
-        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
-        IMediaProjection projection = mock(IMediaProjection.class);
-        doReturn(false).when(projection).isValid();
-        when(mMockProjectionService
-                .setContentRecordingSession(any(ContentRecordingSession.class), eq(projection)))
-                .thenReturn(true);
-        doReturn(true).when(mMockProjectionService).isCurrentProjection(eq(projection));
-
-        final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
-                VIRTUAL_DISPLAY_NAME, 600, 800, 320);
-        builder.setUniqueId("uniqueId --- isValid false");
-
-        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
-        registerDefaultDisplays(displayManager);
-        displayManager.windowManagerAndInputReady();
-
-        // Pass in a non-null projection.
-        DisplayManagerService.BinderService binderService = displayManager.new BinderService();
-        final int displayId = binderService.createVirtualDisplay(builder.build(),
-                mMockAppToken /* callback */, projection, PACKAGE_NAME);
-
-        // VirtualDisplay is created for mirroring.
-        assertThat(displayId).isNotEqualTo(Display.INVALID_DISPLAY);
-        verify(mMockProjectionService, atLeastOnce()).setContentRecordingSession(
-                any(ContentRecordingSession.class), nullable(IMediaProjection.class));
-        // But mirroring doesn't begin.
-        verify(mMockProjectionService, atLeastOnce()).setContentRecordingSession(
-                mContentRecordingSessionCaptor.capture(), nullable(IMediaProjection.class));
-        ContentRecordingSession session = mContentRecordingSessionCaptor.getValue();
-        assertThat(session.isWaitingForConsent()).isTrue();
-    }
-
-    @Test
-    public void testCreateVirtualDisplay_setContentRecordingSessionSuccess()
-            throws RemoteException {
-        final int displayToRecord = 50;
-        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
-        IMediaProjection projection = mock(IMediaProjection.class);
-        doReturn(true).when(projection).isValid();
-        when(mMockProjectionService
-                .setContentRecordingSession(any(ContentRecordingSession.class), eq(projection)))
-                .thenReturn(true);
-        doReturn(true).when(mMockProjectionService).isCurrentProjection(eq(projection));
-
-        final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
-                VIRTUAL_DISPLAY_NAME, 600, 800, 320);
-        builder.setUniqueId("uniqueId --- setContentRecordingSession true");
-        builder.setDisplayIdToMirror(displayToRecord);
-
-        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
-        registerDefaultDisplays(displayManager);
-        displayManager.windowManagerAndInputReady();
-
-        DisplayManagerService.BinderService binderService = displayManager.new BinderService();
-        final int displayId = binderService.createVirtualDisplay(builder.build(),
-                mMockAppToken /* callback */, projection, PACKAGE_NAME);
-
-        assertThat(displayId).isNotEqualTo(Display.INVALID_DISPLAY);
-        verify(mMockProjectionService, atLeastOnce()).setContentRecordingSession(
-                mContentRecordingSessionCaptor.capture(), nullable(IMediaProjection.class));
-        ContentRecordingSession session = mContentRecordingSessionCaptor.getValue();
-        assertThat(session.getContentToRecord()).isEqualTo(RECORD_CONTENT_DISPLAY);
-        assertThat(session.getVirtualDisplayId()).isEqualTo(displayId);
-        assertThat(session.getDisplayToRecord()).isEqualTo(displayToRecord);
-        assertThat(session.isWaitingForConsent()).isFalse();
-    }
-
-    @Test
-    public void testCreateVirtualDisplay_setContentRecordingSessionFail() throws RemoteException {
-        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
-        IMediaProjection projection = mock(IMediaProjection.class);
-        doReturn(true).when(projection).isValid();
-        when(mMockProjectionService
-                .setContentRecordingSession(any(ContentRecordingSession.class), eq(projection)))
-                .thenReturn(false);
-        doReturn(true).when(mMockProjectionService).isCurrentProjection(eq(projection));
-
-        final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
-                VIRTUAL_DISPLAY_NAME, 600, 800, 320);
-        builder.setUniqueId("uniqueId --- setContentRecordingSession false");
-
-        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
-        registerDefaultDisplays(displayManager);
-        displayManager.windowManagerAndInputReady();
-
-        DisplayManagerService.BinderService binderService = displayManager.new BinderService();
-        final int displayId = binderService.createVirtualDisplay(builder.build(),
-                mMockAppToken /* callback */, projection, PACKAGE_NAME);
-
-        assertThat(displayId).isEqualTo(Display.INVALID_DISPLAY);
-    }
-
-    @Test
-    public void testCreateVirtualDisplay_setContentRecordingSession_taskSession()
-            throws RemoteException {
-        final int displayToRecord = 50;
-        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
-        IMediaProjection projection = mock(IMediaProjection.class);
-        doReturn(true).when(projection).isValid();
-        when(mMockProjectionService
-                .setContentRecordingSession(any(ContentRecordingSession.class), eq(projection)))
-                .thenReturn(true);
-        doReturn(mock(IBinder.class)).when(projection).getLaunchCookie();
-        doReturn(true).when(mMockProjectionService).isCurrentProjection(eq(projection));
-
-        final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
-                VIRTUAL_DISPLAY_NAME, 600, 800, 320);
-        builder.setUniqueId("uniqueId --- setContentRecordingSession false");
-        builder.setDisplayIdToMirror(displayToRecord);
-
-        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
-        registerDefaultDisplays(displayManager);
-        displayManager.windowManagerAndInputReady();
-
-        DisplayManagerService.BinderService binderService = displayManager.new BinderService();
-        final int displayId = binderService.createVirtualDisplay(builder.build(),
-                mMockAppToken /* callback */, projection, PACKAGE_NAME);
-
-        assertThat(displayId).isNotEqualTo(Display.INVALID_DISPLAY);
-        verify(mMockProjectionService, atLeastOnce()).setContentRecordingSession(
-                mContentRecordingSessionCaptor.capture(), nullable(IMediaProjection.class));
-        ContentRecordingSession session = mContentRecordingSessionCaptor.getValue();
-        assertThat(session.getContentToRecord()).isEqualTo(RECORD_CONTENT_TASK);
-        assertThat(session.getVirtualDisplayId()).isEqualTo(displayId);
-        assertThat(session.getTokenToRecord()).isNotNull();
-    }
-
-    @Test
-    public void testCreateVirtualDisplay_setContentRecordingSession_noProjection_noFlags()
-            throws RemoteException {
-        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
-
-        // Set no flags for the VirtualDisplay.
-        final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
-                VIRTUAL_DISPLAY_NAME, 600, 800, 320);
-        builder.setUniqueId("uniqueId --- setContentRecordingSession false");
-
-        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
-        registerDefaultDisplays(displayManager);
-        displayManager.windowManagerAndInputReady();
-
-        // Pass in a null projection.
-        DisplayManagerService.BinderService binderService = displayManager.new BinderService();
-        final int displayId = binderService.createVirtualDisplay(builder.build(),
-                mMockAppToken /* callback */, null /* projection */, PACKAGE_NAME);
-
-        // VirtualDisplay is created but not for mirroring.
-        assertThat(displayId).isNotEqualTo(Display.INVALID_DISPLAY);
-        verify(mMockProjectionService, never()).setContentRecordingSession(
-                any(ContentRecordingSession.class), nullable(IMediaProjection.class));
-    }
-
-    @Test
-    public void testCreateVirtualDisplay_setContentRecordingSession_noProjection_noMirroringFlag()
-            throws RemoteException {
-        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
-
-        // Set a non-mirroring flag for the VirtualDisplay.
-        final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
-                VIRTUAL_DISPLAY_NAME, 600, 800, 320);
-        builder.setUniqueId("uniqueId --- setContentRecordingSession false");
-        builder.setFlags(VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY);
-
-        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
-        registerDefaultDisplays(displayManager);
-        displayManager.windowManagerAndInputReady();
-
-        // Pass in a null projection.
-        DisplayManagerService.BinderService binderService = displayManager.new BinderService();
-        final int displayId = binderService.createVirtualDisplay(builder.build(),
-                mMockAppToken /* callback */, null /* projection */, PACKAGE_NAME);
-
-        // VirtualDisplay is created but not for mirroring.
-        assertThat(displayId).isNotEqualTo(Display.INVALID_DISPLAY);
-        verify(mMockProjectionService, never()).setContentRecordingSession(
-                any(ContentRecordingSession.class), nullable(IMediaProjection.class));
-    }
-
-    @Test
-    public void testCreateVirtualDisplay_setContentRecordingSession_projection_noMirroringFlag()
-            throws RemoteException {
-        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
-        IMediaProjection projection = mock(IMediaProjection.class);
-        doReturn(true).when(projection).isValid();
-        when(mMockProjectionService
-                .setContentRecordingSession(any(ContentRecordingSession.class), eq(projection)))
-                .thenReturn(true);
-        doReturn(true).when(mMockProjectionService).isCurrentProjection(eq(projection));
-
-        // Set no flags for the VirtualDisplay.
-        final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
-                VIRTUAL_DISPLAY_NAME, 600, 800, 320);
-        builder.setUniqueId("uniqueId --- setContentRecordingSession false");
-
-        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
-        registerDefaultDisplays(displayManager);
-        displayManager.windowManagerAndInputReady();
-
-        // Pass in a non-null projection.
-        DisplayManagerService.BinderService binderService = displayManager.new BinderService();
-        final int displayId = binderService.createVirtualDisplay(builder.build(),
-                mMockAppToken /* callback */, projection, PACKAGE_NAME);
-
-        // VirtualDisplay is created for mirroring.
-        assertThat(displayId).isNotEqualTo(Display.INVALID_DISPLAY);
-        verify(mMockProjectionService, atLeastOnce()).setContentRecordingSession(
-                any(ContentRecordingSession.class), nullable(IMediaProjection.class));
-    }
-
-    /**
-     * Tests that the virtual display is created with
-     * {@link VirtualDisplayConfig.Builder#setSurface(Surface)}
-     */
-    @Test
-    @FlakyTest(bugId = 127687569)
-    public void testCreateVirtualDisplay_setSurface() throws Exception {
-        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
-        registerDefaultDisplays(displayManager);
-
-        // This is effectively the DisplayManager service published to ServiceManager.
-        DisplayManagerService.BinderService binderService = displayManager.new BinderService();
-
-        final String uniqueId = "uniqueId --- setSurface";
-        final int width = 600;
-        final int height = 800;
-        final int dpi = 320;
-        final Surface surface = new Surface();
-
-        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
-        final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
-                VIRTUAL_DISPLAY_NAME, width, height, dpi);
-        builder.setSurface(surface);
-        builder.setUniqueId(uniqueId);
-        final int displayId = binderService.createVirtualDisplay(builder.build(),
-                mMockAppToken /* callback */, null /* projection */, PACKAGE_NAME);
-        verify(mMockProjectionService, never()).setContentRecordingSession(any(),
-                nullable(IMediaProjection.class));
-
-        displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
-
-        // flush the handler
-        displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */);
-
-        assertEquals(displayManager.getVirtualDisplaySurfaceInternal(mMockAppToken), surface);
-    }
-
-    /**
-     * Tests that specifying VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP is allowed when the permission
-     * ADD_TRUSTED_DISPLAY is granted.
-     */
-    @Test
-    public void testOwnDisplayGroup_allowCreationWithAddTrustedDisplayPermission()
-            throws RemoteException {
-        DisplayManagerService displayManager =
-                new DisplayManagerService(mContext, mBasicInjector);
-        registerDefaultDisplays(displayManager);
-
-        DisplayManagerService.BinderService bs = displayManager.new BinderService();
-        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
-
-        when(mContext.checkCallingPermission(ADD_TRUSTED_DISPLAY)).thenReturn(
-                PackageManager.PERMISSION_GRANTED);
-
-        final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
-                VIRTUAL_DISPLAY_NAME, 600, 800, 320);
-        builder.setFlags(DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP);
-        builder.setUniqueId("uniqueId --- OWN_DISPLAY_GROUP");
-
-        int displayId = bs.createVirtualDisplay(builder.build(), mMockAppToken /* callback */,
-                null /* projection */, PACKAGE_NAME);
-        verify(mMockProjectionService, never()).setContentRecordingSession(any(),
-                nullable(IMediaProjection.class));
-        displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
-        displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */);
-        DisplayDeviceInfo ddi = displayManager.getDisplayDeviceInfoInternal(displayId);
-        assertNotNull(ddi);
-        assertNotEquals(0, ddi.flags & DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP);
-    }
-
-    /**
-     * Tests that specifying VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP is blocked when the permission
-     * ADD_TRUSTED_DISPLAY is denied.
-     */
-    @Test
-    public void testOwnDisplayGroup_withoutAddTrustedDisplayPermission_throwsSecurityException() {
-        DisplayManagerService displayManager =
-                new DisplayManagerService(mContext, mBasicInjector);
-        registerDefaultDisplays(displayManager);
-
-        DisplayManagerService.BinderService bs = displayManager.new BinderService();
-        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
-
-        when(mContext.checkCallingPermission(ADD_TRUSTED_DISPLAY)).thenReturn(
-                PackageManager.PERMISSION_DENIED);
-
-        final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
-                VIRTUAL_DISPLAY_NAME, 600, 800, 320);
-        builder.setFlags(DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP);
-        builder.setUniqueId("uniqueId --- OWN_DISPLAY_GROUP");
-
-        try {
-            bs.createVirtualDisplay(builder.build(), mMockAppToken /* callback */,
-                    null /* projection */, PACKAGE_NAME);
-            fail("Creating virtual display with VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP without "
-                    + "ADD_TRUSTED_DISPLAY permission should throw SecurityException.");
-        } catch (SecurityException e) {
-            // SecurityException is expected
-        }
-    }
-
-    /**
-     * Tests that specifying VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP is allowed when called with
-     * a virtual device, even if ADD_TRUSTED_DISPLAY is not granted.
-     */
-    @Test
-    public void testOwnDisplayGroup_allowCreationWithVirtualDevice()  throws Exception {
-        DisplayManagerService displayManager =
-                new DisplayManagerService(mContext, mBasicInjector);
-        DisplayManagerInternal localService = displayManager.new LocalService();
-
-        registerDefaultDisplays(displayManager);
-
-        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
-
-        when(mContext.checkCallingPermission(ADD_TRUSTED_DISPLAY)).thenReturn(
-                PackageManager.PERMISSION_DENIED);
-
-        final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
-                VIRTUAL_DISPLAY_NAME, 600, 800, 320);
-        builder.setFlags(DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP);
-        builder.setUniqueId("uniqueId --- OWN_DISPLAY_GROUP");
-
-        IVirtualDevice virtualDevice = mock(IVirtualDevice.class);
-        when(virtualDevice.getDeviceId()).thenReturn(1);
-        when(mIVirtualDeviceManager.isValidVirtualDeviceId(1)).thenReturn(true);
-
-        int displayId = localService.createVirtualDisplay(builder.build(),
-                mMockAppToken /* callback */, virtualDevice /* virtualDeviceToken */,
-                mock(DisplayWindowPolicyController.class), PACKAGE_NAME);
-        verify(mMockProjectionService, never()).setContentRecordingSession(any(),
-                nullable(IMediaProjection.class));
-        displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
-        displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */);
-        DisplayDeviceInfo ddi = displayManager.getDisplayDeviceInfoInternal(displayId);
-        assertNotNull(ddi);
-        assertNotEquals(0, ddi.flags & DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP);
-    }
-
-    /**
-     * Tests that there is a display change notification if the frame rate override
-     * list is updated.
-     */
-    @Test
-    public void testShouldNotifyChangeWhenDisplayInfoFrameRateOverrideChanged() throws Exception {
-        DisplayManagerService displayManager =
-                new DisplayManagerService(mContext, mShortMockedInjector);
-        DisplayManagerService.BinderService displayManagerBinderService =
-                displayManager.new BinderService();
-        registerDefaultDisplays(displayManager);
-        displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
-
-        FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager, new float[]{60f});
-        FakeDisplayManagerCallback callback = registerDisplayListenerCallback(displayManager,
-                displayManagerBinderService, displayDevice);
-
-        int myUid = Process.myUid();
-        updateFrameRateOverride(displayManager, displayDevice,
-                new DisplayEventReceiver.FrameRateOverride[]{
-                        new DisplayEventReceiver.FrameRateOverride(myUid, 30f),
-                });
-        assertTrue(callback.mDisplayChangedCalled);
-        callback.clear();
-
-        updateFrameRateOverride(displayManager, displayDevice,
-                new DisplayEventReceiver.FrameRateOverride[]{
-                        new DisplayEventReceiver.FrameRateOverride(myUid, 30f),
-                        new DisplayEventReceiver.FrameRateOverride(1234, 30f),
-                });
-        assertFalse(callback.mDisplayChangedCalled);
-
-        updateFrameRateOverride(displayManager, displayDevice,
-                new DisplayEventReceiver.FrameRateOverride[]{
-                        new DisplayEventReceiver.FrameRateOverride(myUid, 20f),
-                        new DisplayEventReceiver.FrameRateOverride(1234, 30f),
-                        new DisplayEventReceiver.FrameRateOverride(5678, 30f),
-                });
-        assertTrue(callback.mDisplayChangedCalled);
-        callback.clear();
-
-        updateFrameRateOverride(displayManager, displayDevice,
-                new DisplayEventReceiver.FrameRateOverride[]{
-                        new DisplayEventReceiver.FrameRateOverride(1234, 30f),
-                        new DisplayEventReceiver.FrameRateOverride(5678, 30f),
-                });
-        assertTrue(callback.mDisplayChangedCalled);
-        callback.clear();
-
-        updateFrameRateOverride(displayManager, displayDevice,
-                new DisplayEventReceiver.FrameRateOverride[]{
-                        new DisplayEventReceiver.FrameRateOverride(5678, 30f),
-                });
-        assertFalse(callback.mDisplayChangedCalled);
-    }
-
-    /**
-     * Tests that the DisplayInfo is updated correctly with a frame rate override
-     */
-    @Test
-    public void testDisplayInfoFrameRateOverride() throws Exception {
-        DisplayManagerService displayManager =
-                new DisplayManagerService(mContext, mShortMockedInjector);
-        DisplayManagerService.BinderService displayManagerBinderService =
-                displayManager.new BinderService();
-        registerDefaultDisplays(displayManager);
-        displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
-
-        FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager,
-                new float[]{60f, 30f, 20f});
-        int displayId = getDisplayIdForDisplayDevice(displayManager, displayManagerBinderService,
-                displayDevice);
-        DisplayInfo displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
-        assertEquals(60f, displayInfo.getRefreshRate(), 0.01f);
-
-        updateFrameRateOverride(displayManager, displayDevice,
-                new DisplayEventReceiver.FrameRateOverride[]{
-                        new DisplayEventReceiver.FrameRateOverride(
-                                Process.myUid(), 20f),
-                        new DisplayEventReceiver.FrameRateOverride(
-                                Process.myUid() + 1, 30f)
-                });
-        displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
-        assertEquals(20f, displayInfo.getRefreshRate(), 0.01f);
-
-        // Changing the mode to 30Hz should not override the refresh rate to 20Hz anymore
-        // as 20 is not a divider of 30.
-        updateModeId(displayManager, displayDevice, 2);
-        displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
-        assertEquals(30f, displayInfo.getRefreshRate(), 0.01f);
-    }
-
-    /**
-     * Tests that the frame rate override is returning the correct value from
-     * DisplayInfo#getRefreshRate
-     */
-    @Test
-    public void testDisplayInfoNonNativeFrameRateOverride() throws Exception {
-        DisplayManagerService displayManager =
-                new DisplayManagerService(mContext, mBasicInjector);
-        DisplayManagerService.BinderService displayManagerBinderService =
-                displayManager.new BinderService();
-        registerDefaultDisplays(displayManager);
-        displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
-
-        FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager,
-                new float[]{60f});
-        int displayId = getDisplayIdForDisplayDevice(displayManager, displayManagerBinderService,
-                displayDevice);
-        DisplayInfo displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
-        assertEquals(60f, displayInfo.getRefreshRate(), 0.01f);
-
-        updateFrameRateOverride(displayManager, displayDevice,
-                new DisplayEventReceiver.FrameRateOverride[]{
-                        new DisplayEventReceiver.FrameRateOverride(
-                                Process.myUid(), 20f)
-                });
-        displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
-        assertEquals(20f, displayInfo.getRefreshRate(), 0.01f);
-    }
-
-    /**
-     * Tests that the mode reflects the frame rate override is in compat mode
-     */
-    @Test
-    @DisableCompatChanges({DisplayManagerService.DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE})
-    public  void testDisplayInfoFrameRateOverrideModeCompat() throws Exception {
-        testDisplayInfoFrameRateOverrideModeCompat(/*compatChangeEnabled*/ false);
-    }
-
-    /**
-     * Tests that the mode reflects the physical display refresh rate when not in compat mode.
-     */
-    @Test
-    @EnableCompatChanges({DisplayManagerService.DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE})
-    public  void testDisplayInfoFrameRateOverrideMode() throws Exception {
-        testDisplayInfoFrameRateOverrideModeCompat(/*compatChangeEnabled*/ true);
-    }
-
-    /**
-     * Tests that the mode reflects the frame rate override is in compat mode and accordingly to the
-     * allowNonNativeRefreshRateOverride policy.
-     */
-    @Test
-    @DisableCompatChanges({DisplayManagerService.DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE})
-    public void testDisplayInfoNonNativeFrameRateOverrideModeCompat() throws Exception {
-        testDisplayInfoNonNativeFrameRateOverrideMode(/*compatChangeEnabled*/ false);
-    }
-
-    /**
-     * Tests that the mode reflects the physical display refresh rate when not in compat mode.
-     */
-    @Test
-    @EnableCompatChanges({DisplayManagerService.DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE})
-    public void testDisplayInfoNonNativeFrameRateOverrideMode() throws Exception {
-        testDisplayInfoNonNativeFrameRateOverrideMode(/*compatChangeEnabled*/ true);
-    }
-
-    /**
-     * Tests that there is a display change notification if the render frame rate is updated
-     */
-    @Test
-    public void testShouldNotifyChangeWhenDisplayInfoRenderFrameRateChanged() throws Exception {
-        DisplayManagerService displayManager =
-                new DisplayManagerService(mContext, mShortMockedInjector);
-        DisplayManagerService.BinderService displayManagerBinderService =
-                displayManager.new BinderService();
-        registerDefaultDisplays(displayManager);
-        displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
-
-        FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager, new float[]{60f});
-        FakeDisplayManagerCallback callback = registerDisplayListenerCallback(displayManager,
-                displayManagerBinderService, displayDevice);
-
-        updateRenderFrameRate(displayManager, displayDevice, 30f);
-        assertTrue(callback.mDisplayChangedCalled);
-        callback.clear();
-
-        updateRenderFrameRate(displayManager, displayDevice, 30f);
-        assertFalse(callback.mDisplayChangedCalled);
-
-        updateRenderFrameRate(displayManager, displayDevice, 20f);
-        assertTrue(callback.mDisplayChangedCalled);
-        callback.clear();
-    }
-
-    /**
-     * Tests that the DisplayInfo is updated correctly with a render frame rate
-     */
-    @Test
-    public void testDisplayInfoRenderFrameRate() throws Exception {
-        DisplayManagerService displayManager =
-                new DisplayManagerService(mContext, mShortMockedInjector);
-        DisplayManagerService.BinderService displayManagerBinderService =
-                displayManager.new BinderService();
-        registerDefaultDisplays(displayManager);
-        displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
-
-        FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager,
-                new float[]{60f, 30f, 20f});
-        int displayId = getDisplayIdForDisplayDevice(displayManager, displayManagerBinderService,
-                displayDevice);
-        DisplayInfo displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
-        assertEquals(60f, displayInfo.getRefreshRate(), 0.01f);
-
-        updateRenderFrameRate(displayManager, displayDevice, 20f);
-        displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
-        assertEquals(20f, displayInfo.getRefreshRate(), 0.01f);
-    }
-
-    /**
-     * Tests that the mode reflects the render frame rate is in compat mode
-     */
-    @Test
-    @DisableCompatChanges({DisplayManagerService.DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE})
-    public  void testDisplayInfoRenderFrameRateModeCompat() throws Exception {
-        testDisplayInfoRenderFrameRateModeCompat(/*compatChangeEnabled*/ false);
-    }
-
-    /**
-     * Tests that the mode reflects the physical display refresh rate when not in compat mode.
-     */
-    @Test
-    @EnableCompatChanges({DisplayManagerService.DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE})
-    public  void testDisplayInfoRenderFrameRateMode() throws Exception {
-        testDisplayInfoRenderFrameRateModeCompat(/*compatChangeEnabled*/ true);
-    }
-
-    /**
-     * Tests that EVENT_DISPLAY_ADDED is sent when a display is added.
-     */
-    @Test
-    public void testShouldNotifyDisplayAdded_WhenNewDisplayDeviceIsAdded() {
-        DisplayManagerService displayManager =
-                new DisplayManagerService(mContext, mShortMockedInjector);
-        DisplayManagerService.BinderService displayManagerBinderService =
-                displayManager.new BinderService();
-
-        Handler handler = displayManager.getDisplayHandler();
-        waitForIdleHandler(handler);
-
-        // register display listener callback
-        FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback();
-        displayManagerBinderService.registerCallbackWithEventMask(
-                callback, STANDARD_DISPLAY_EVENTS);
-
-        waitForIdleHandler(handler);
-
-        createFakeDisplayDevice(displayManager, new float[]{60f});
-
-        waitForIdleHandler(handler);
-
-        assertFalse(callback.mDisplayChangedCalled);
-        assertFalse(callback.mDisplayRemovedCalled);
-        assertTrue(callback.mDisplayAddedCalled);
-    }
-
-    /**
-     * Tests that EVENT_DISPLAY_ADDED is not sent when a display is added and the
-     * client has a callback which is not subscribed to this event type.
-     */
-    @Test
-    public void testShouldNotNotifyDisplayAdded_WhenClientIsNotSubscribed() {
-        DisplayManagerService displayManager =
-                new DisplayManagerService(mContext, mShortMockedInjector);
-        DisplayManagerService.BinderService displayManagerBinderService =
-                displayManager.new BinderService();
-
-        Handler handler = displayManager.getDisplayHandler();
-        waitForIdleHandler(handler);
-
-        // register display listener callback
-        FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback();
-        long allEventsExceptDisplayAdded = STANDARD_DISPLAY_EVENTS
-                & ~DisplayManager.EVENT_FLAG_DISPLAY_ADDED;
-        displayManagerBinderService.registerCallbackWithEventMask(callback,
-                allEventsExceptDisplayAdded);
-
-        waitForIdleHandler(handler);
-
-        createFakeDisplayDevice(displayManager, new float[]{60f});
-
-        waitForIdleHandler(handler);
-
-        assertFalse(callback.mDisplayChangedCalled);
-        assertFalse(callback.mDisplayRemovedCalled);
-        assertFalse(callback.mDisplayAddedCalled);
-    }
-
-    /**
-     * Tests that EVENT_DISPLAY_REMOVED is sent when a display is removed.
-     */
-    @Test
-    public void testShouldNotifyDisplayRemoved_WhenDisplayDeviceIsRemoved() {
-        DisplayManagerService displayManager =
-                new DisplayManagerService(mContext, mShortMockedInjector);
-        DisplayManagerService.BinderService displayManagerBinderService =
-                displayManager.new BinderService();
-
-        Handler handler = displayManager.getDisplayHandler();
-        waitForIdleHandler(handler);
-
-        FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager,
-                new float[]{60f});
-
-        waitForIdleHandler(handler);
-
-        FakeDisplayManagerCallback callback = registerDisplayListenerCallback(
-                displayManager, displayManagerBinderService, displayDevice);
-
-        waitForIdleHandler(handler);
-
-        displayManager.getDisplayDeviceRepository()
-                .onDisplayDeviceEvent(displayDevice, DisplayAdapter.DISPLAY_DEVICE_EVENT_REMOVED);
-
-        waitForIdleHandler(handler);
-
-        assertFalse(callback.mDisplayChangedCalled);
-        assertTrue(callback.mDisplayRemovedCalled);
-        assertFalse(callback.mDisplayAddedCalled);
-    }
-
-    /**
-     * Tests that EVENT_DISPLAY_REMOVED is not sent when a display is added and the
-     * client has a callback which is not subscribed to this event type.
-     */
-    @Test
-    public void testShouldNotNotifyDisplayRemoved_WhenClientIsNotSubscribed() {
-        DisplayManagerService displayManager =
-                new DisplayManagerService(mContext, mShortMockedInjector);
-        DisplayManagerService.BinderService displayManagerBinderService =
-                displayManager.new BinderService();
-
-        Handler handler = displayManager.getDisplayHandler();
-        waitForIdleHandler(handler);
-
-        FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager,
-                new float[]{60f});
-
-        waitForIdleHandler(handler);
-
-        FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback();
-        long allEventsExceptDisplayRemoved = STANDARD_DISPLAY_EVENTS
-                & ~DisplayManager.EVENT_FLAG_DISPLAY_REMOVED;
-        displayManagerBinderService.registerCallbackWithEventMask(callback,
-                allEventsExceptDisplayRemoved);
-
-        waitForIdleHandler(handler);
-
-        displayManager.getDisplayDeviceRepository()
-                .onDisplayDeviceEvent(displayDevice, DisplayAdapter.DISPLAY_DEVICE_EVENT_REMOVED);
-
-        waitForIdleHandler(handler);
-
-        assertFalse(callback.mDisplayChangedCalled);
-        assertFalse(callback.mDisplayRemovedCalled);
-        assertFalse(callback.mDisplayAddedCalled);
-    }
-
-
-
-    @Test
-    public void testSettingTwoBrightnessConfigurationsOnMultiDisplay() {
-        Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
-        DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
-
-        // get the first two internal displays
-        Display[] displays = displayManager.getDisplays();
-        Display internalDisplayOne = null;
-        Display internalDisplayTwo = null;
-        for (Display display : displays) {
-            if (display.getType() == Display.TYPE_INTERNAL) {
-                if (internalDisplayOne == null) {
-                    internalDisplayOne = display;
-                } else {
-                    internalDisplayTwo = display;
-                    break;
-                }
-            }
-        }
-
-        // return if there are fewer than 2 displays on this device
-        if (internalDisplayOne == null || internalDisplayTwo == null) {
-            return;
-        }
-
-        final String uniqueDisplayIdOne = internalDisplayOne.getUniqueId();
-        final String uniqueDisplayIdTwo = internalDisplayTwo.getUniqueId();
-
-        BrightnessConfiguration configOne =
-                new BrightnessConfiguration.Builder(
-                        new float[]{0.0f, 12345.0f}, new float[]{15.0f, 400.0f})
-                        .setDescription("model:1").build();
-        BrightnessConfiguration configTwo =
-                new BrightnessConfiguration.Builder(
-                        new float[]{0.0f, 6789.0f}, new float[]{12.0f, 300.0f})
-                        .setDescription("model:2").build();
-
-        displayManager.setBrightnessConfigurationForDisplay(configOne,
-                uniqueDisplayIdOne);
-        displayManager.setBrightnessConfigurationForDisplay(configTwo,
-                uniqueDisplayIdTwo);
-
-        BrightnessConfiguration configFromOne =
-                displayManager.getBrightnessConfigurationForDisplay(uniqueDisplayIdOne);
-        BrightnessConfiguration configFromTwo =
-                displayManager.getBrightnessConfigurationForDisplay(uniqueDisplayIdTwo);
-
-        assertNotNull(configFromOne);
-        assertEquals(configOne, configFromOne);
-        assertEquals(configTwo, configFromTwo);
-
-    }
-
-    @Test
-    public void testHdrConversionModeEquals() {
-        assertEquals(
-                new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_FORCE, 2),
-                new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_FORCE, 2));
-        assertNotEquals(
-                new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_FORCE, 2),
-                new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_FORCE, 3));
-        assertEquals(
-                new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_SYSTEM),
-                new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_SYSTEM));
-        assertNotEquals(
-                new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_FORCE, 2),
-                new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_SYSTEM));
-    }
-
-    @Test
-    public void testCreateHdrConversionMode_withInvalidArguments_throwsException() {
-        assertThrows(
-                "preferredHdrOutputType must not be set if the conversion mode is "
-                        + "HDR_CONVERSION_PASSTHROUGH",
-                IllegalArgumentException.class,
-                () -> new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_PASSTHROUGH,
-                        Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION));
-    }
-
-    @Test
-    public void testSetHdrConversionModeInternal_withInvalidArguments_throwsException() {
-        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
-        assertThrows("Expected DisplayManager to throw IllegalArgumentException when "
-                        + "preferredHdrOutputType is set and the conversion mode is "
-                        + "HDR_CONVERSION_SYSTEM",
-                IllegalArgumentException.class,
-                () -> displayManager.setHdrConversionModeInternal(new HdrConversionMode(
-                        HdrConversionMode.HDR_CONVERSION_SYSTEM,
-                        Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION)));
-    }
-
-    @Test
-    public void testSetAndGetHdrConversionModeInternal() {
-        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
-        final HdrConversionMode mode = new HdrConversionMode(
-                HdrConversionMode.HDR_CONVERSION_FORCE,
-                Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION);
-        displayManager.setHdrConversionModeInternal(mode);
-        assertEquals(mode, displayManager.getHdrConversionModeSettingInternal());
-        assertEquals(mode.getConversionMode(), mHdrConversionMode);
-        assertEquals(mode.getPreferredHdrOutputType(), mPreferredHdrOutputType);
-    }
-
-    @Test
-    public void testHdrConversionMode_withMinimalPostProcessing() {
-        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
-        DisplayManagerService.BinderService displayManagerBinderService =
-                displayManager.new BinderService();
-        registerDefaultDisplays(displayManager);
-        displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
-        FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager,
-                new float[]{60f, 30f, 20f});
-        int displayId = getDisplayIdForDisplayDevice(displayManager, displayManagerBinderService,
-                displayDevice);
-
-        final HdrConversionMode mode = new HdrConversionMode(
-                HdrConversionMode.HDR_CONVERSION_FORCE,
-                Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION);
-        displayManager.setHdrConversionModeInternal(mode);
-        assertEquals(mode, displayManager.getHdrConversionModeSettingInternal());
-
-        displayManager.setMinimalPostProcessingAllowed(true);
-        displayManager.setDisplayPropertiesInternal(displayId, false /* hasContent */,
-                30.0f /* requestedRefreshRate */,
-                displayDevice.getDisplayDeviceInfoLocked().modeId /* requestedModeId */,
-                30.0f /* requestedMinRefreshRate */, 120.0f /* requestedMaxRefreshRate */,
-                true /* preferMinimalPostProcessing */, false /* disableHdrConversion */,
-                true /* inTraversal */);
-
-        assertEquals(new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_PASSTHROUGH),
-                displayManager.getHdrConversionModeInternal());
-
-        displayManager.setDisplayPropertiesInternal(displayId, false /* hasContent */,
-                30.0f /* requestedRefreshRate */,
-                displayDevice.getDisplayDeviceInfoLocked().modeId /* requestedModeId */,
-                30.0f /* requestedMinRefreshRate */, 120.0f /* requestedMaxRefreshRate */,
-                false /* preferMinimalPostProcessing */, false /* disableHdrConversion */,
-                true /* inTraversal */);
-        assertEquals(mode, displayManager.getHdrConversionModeInternal());
-    }
-
-    private void testDisplayInfoFrameRateOverrideModeCompat(boolean compatChangeEnabled)
-            throws Exception {
-        DisplayManagerService displayManager =
-                new DisplayManagerService(mContext, mShortMockedInjector);
-        DisplayManagerService.BinderService displayManagerBinderService =
-                displayManager.new BinderService();
-        registerDefaultDisplays(displayManager);
-        displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
-
-        FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager,
-                new float[]{60f, 30f, 20f});
-        int displayId = getDisplayIdForDisplayDevice(displayManager, displayManagerBinderService,
-                displayDevice);
-        DisplayInfo displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
-        assertEquals(60f, displayInfo.getRefreshRate(), 0.01f);
-
-        updateFrameRateOverride(displayManager, displayDevice,
-                new DisplayEventReceiver.FrameRateOverride[]{
-                        new DisplayEventReceiver.FrameRateOverride(
-                                Process.myUid(), 20f),
-                        new DisplayEventReceiver.FrameRateOverride(
-                                Process.myUid() + 1, 30f)
-                });
-        displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
-        assertEquals(20f, displayInfo.getRefreshRate(), 0.01f);
-        Display.Mode expectedMode;
-        if (compatChangeEnabled) {
-            expectedMode = new Display.Mode(1, 100, 200, 60f);
-        } else {
-            expectedMode = new Display.Mode(3, 100, 200, 20f);
-        }
-        assertEquals(expectedMode, displayInfo.getMode());
-    }
-
-    private void testDisplayInfoRenderFrameRateModeCompat(boolean compatChangeEnabled)
-            throws Exception {
-        DisplayManagerService displayManager =
-                new DisplayManagerService(mContext, mShortMockedInjector);
-        DisplayManagerService.BinderService displayManagerBinderService =
-                displayManager.new BinderService();
-        registerDefaultDisplays(displayManager);
-        displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
-
-        FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager,
-                new float[]{60f, 30f, 20f});
-        int displayId = getDisplayIdForDisplayDevice(displayManager, displayManagerBinderService,
-                displayDevice);
-        DisplayInfo displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
-        assertEquals(60f, displayInfo.getRefreshRate(), 0.01f);
-
-        updateRenderFrameRate(displayManager, displayDevice, 20f);
-        displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
-        assertEquals(20f, displayInfo.getRefreshRate(), 0.01f);
-        Display.Mode expectedMode;
-        if (compatChangeEnabled) {
-            expectedMode = new Display.Mode(1, 100, 200, 60f);
-        } else {
-            expectedMode = new Display.Mode(3, 100, 200, 20f);
-        }
-        assertEquals(expectedMode, displayInfo.getMode());
-    }
-
-    private void testDisplayInfoNonNativeFrameRateOverrideMode(boolean compatChangeEnabled) {
-        DisplayManagerService displayManager =
-                new DisplayManagerService(mContext, mBasicInjector);
-        DisplayManagerService.BinderService displayManagerBinderService =
-                displayManager.new BinderService();
-        registerDefaultDisplays(displayManager);
-        displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
-
-        FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager,
-                new float[]{60f});
-        int displayId = getDisplayIdForDisplayDevice(displayManager, displayManagerBinderService,
-                displayDevice);
-        DisplayInfo displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
-        assertEquals(60f, displayInfo.getRefreshRate(), 0.01f);
-
-        updateFrameRateOverride(displayManager, displayDevice,
-                new DisplayEventReceiver.FrameRateOverride[]{
-                        new DisplayEventReceiver.FrameRateOverride(
-                                Process.myUid(), 20f)
-                });
-        displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
-        Display.Mode expectedMode;
-        if (compatChangeEnabled) {
-            expectedMode = new Display.Mode(1, 100, 200, 60f);
-        } else {
-            expectedMode = new Display.Mode(255, 100, 200, 20f);
-        }
-        assertEquals(expectedMode, displayInfo.getMode());
-    }
-
-    private int getDisplayIdForDisplayDevice(
-            DisplayManagerService displayManager,
-            DisplayManagerService.BinderService displayManagerBinderService,
-            FakeDisplayDevice displayDevice) {
-
-        final int[] displayIds = displayManagerBinderService.getDisplayIds(
-                /* includeDisabled= */ true);
-        assertTrue(displayIds.length > 0);
-        int displayId = Display.INVALID_DISPLAY;
-        for (int i = 0; i < displayIds.length; i++) {
-            DisplayDeviceInfo ddi = displayManager.getDisplayDeviceInfoInternal(displayIds[i]);
-            if (displayDevice.getDisplayDeviceInfoLocked().equals(ddi)) {
-                displayId = displayIds[i];
-                break;
-            }
-        }
-        assertFalse(displayId == Display.INVALID_DISPLAY);
-        return displayId;
-    }
-
-    private void updateDisplayDeviceInfo(DisplayManagerService displayManager,
-            FakeDisplayDevice displayDevice,
-            DisplayDeviceInfo displayDeviceInfo) {
-        displayDevice.setDisplayDeviceInfo(displayDeviceInfo);
-        displayManager.getDisplayDeviceRepository()
-                .onDisplayDeviceEvent(displayDevice, DisplayAdapter.DISPLAY_DEVICE_EVENT_CHANGED);
-        Handler handler = displayManager.getDisplayHandler();
-        waitForIdleHandler(handler);
-    }
-
-    private void updateFrameRateOverride(DisplayManagerService displayManager,
-            FakeDisplayDevice displayDevice,
-            DisplayEventReceiver.FrameRateOverride[] frameRateOverrides) {
-        DisplayDeviceInfo displayDeviceInfo = new DisplayDeviceInfo();
-        displayDeviceInfo.copyFrom(displayDevice.getDisplayDeviceInfoLocked());
-        displayDeviceInfo.frameRateOverrides = frameRateOverrides;
-        updateDisplayDeviceInfo(displayManager, displayDevice, displayDeviceInfo);
-    }
-
-    private void updateRenderFrameRate(DisplayManagerService displayManager,
-            FakeDisplayDevice displayDevice,
-            float renderFrameRate) {
-        DisplayDeviceInfo displayDeviceInfo = new DisplayDeviceInfo();
-        displayDeviceInfo.copyFrom(displayDevice.getDisplayDeviceInfoLocked());
-        displayDeviceInfo.renderFrameRate = renderFrameRate;
-        updateDisplayDeviceInfo(displayManager, displayDevice, displayDeviceInfo);
-    }
-
-    private void updateModeId(DisplayManagerService displayManager,
-            FakeDisplayDevice displayDevice,
-            int modeId) {
-        DisplayDeviceInfo displayDeviceInfo = new DisplayDeviceInfo();
-        displayDeviceInfo.copyFrom(displayDevice.getDisplayDeviceInfoLocked());
-        displayDeviceInfo.modeId = modeId;
-        updateDisplayDeviceInfo(displayManager, displayDevice, displayDeviceInfo);
-    }
-
-    private IDisplayManagerCallback registerDisplayChangeCallback(
-            DisplayManagerService displayManager) {
-        IDisplayManagerCallback displayChangesCallback = mock(IDisplayManagerCallback.class);
-        when(displayChangesCallback.asBinder()).thenReturn(new Binder());
-        DisplayManagerService.BinderService binderService = displayManager.new BinderService();
-        binderService.registerCallback(displayChangesCallback);
-        return displayChangesCallback;
-    }
-
-    private FakeDisplayManagerCallback registerDisplayListenerCallback(
-            DisplayManagerService displayManager,
-            DisplayManagerService.BinderService displayManagerBinderService,
-            FakeDisplayDevice displayDevice) {
-        // Find the display id of the added FakeDisplayDevice
-        int displayId = getDisplayIdForDisplayDevice(displayManager, displayManagerBinderService,
-                displayDevice);
-
-        Handler handler = displayManager.getDisplayHandler();
-        waitForIdleHandler(handler);
-
-        // register display listener callback
-        FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback(displayId);
-        displayManagerBinderService.registerCallbackWithEventMask(
-                callback, STANDARD_DISPLAY_EVENTS);
-        return callback;
-    }
-
-    private FakeDisplayDevice createFakeDisplayDevice(DisplayManagerService displayManager,
-            float[] refreshRates) {
-        FakeDisplayDevice displayDevice = new FakeDisplayDevice();
-        DisplayDeviceInfo displayDeviceInfo = new DisplayDeviceInfo();
-        int width = 100;
-        int height = 200;
-        displayDeviceInfo.supportedModes = new Display.Mode[refreshRates.length];
-        for (int i = 0; i < refreshRates.length; i++) {
-            displayDeviceInfo.supportedModes[i] =
-                    new Display.Mode(i + 1, width, height, refreshRates[i]);
-        }
-        displayDeviceInfo.modeId = 1;
-        displayDeviceInfo.renderFrameRate = displayDeviceInfo.supportedModes[0].getRefreshRate();
-        displayDeviceInfo.width = width;
-        displayDeviceInfo.height = height;
-        final Rect zeroRect = new Rect();
-        displayDeviceInfo.displayCutout = new DisplayCutout(
-                Insets.of(0, 10, 0, 0),
-                zeroRect, new Rect(0, 0, 10, 10), zeroRect, zeroRect);
-        displayDeviceInfo.flags = DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY;
-        displayDeviceInfo.address = new TestUtils.TestDisplayAddress();
-        displayDevice.setDisplayDeviceInfo(displayDeviceInfo);
-        displayManager.getDisplayDeviceRepository()
-                .onDisplayDeviceEvent(displayDevice, DisplayAdapter.DISPLAY_DEVICE_EVENT_ADDED);
-        return displayDevice;
-    }
-
-    private void registerDefaultDisplays(DisplayManagerService displayManager) {
-        Handler handler = displayManager.getDisplayHandler();
-        // Would prefer to call displayManager.onStart() directly here but it performs binderService
-        // registration which triggers security exceptions when running from a test.
-        handler.sendEmptyMessage(MSG_REGISTER_DEFAULT_DISPLAY_ADAPTERS);
-        waitForIdleHandler(handler);
-    }
-
-    private void waitForIdleHandler(Handler handler) {
-        waitForIdleHandler(handler, Duration.ofSeconds(1));
-    }
-
-    private void waitForIdleHandler(Handler handler, Duration timeout) {
-        final MessageQueue queue = handler.getLooper().getQueue();
-        final CountDownLatch latch = new CountDownLatch(1);
-        queue.addIdleHandler(() -> {
-            latch.countDown();
-            // Remove idle handler
-            return false;
-        });
-        try {
-            latch.await(timeout.toMillis(), TimeUnit.MILLISECONDS);
-        } catch (InterruptedException e) {
-            fail("Interrupted unexpectedly: " + e);
-        }
-    }
-
-    private void resetConfigToIgnoreSensorManager(Context context) {
-        final Resources res = Mockito.spy(context.getResources());
-        doReturn(new int[]{-1}).when(res).getIntArray(R.array
-                .config_ambientThresholdsOfPeakRefreshRate);
-        doReturn(new int[]{-1}).when(res).getIntArray(R.array
-                .config_brightnessThresholdsOfPeakRefreshRate);
-        doReturn(new int[]{-1}).when(res).getIntArray(R.array
-                .config_highDisplayBrightnessThresholdsOfFixedRefreshRate);
-        doReturn(new int[]{-1}).when(res).getIntArray(R.array
-                .config_highAmbientBrightnessThresholdsOfFixedRefreshRate);
-
-        when(context.getResources()).thenReturn(res);
-    }
-
-    private class FakeDisplayManagerCallback extends IDisplayManagerCallback.Stub {
-        int mDisplayId;
-        boolean mDisplayAddedCalled = false;
-        boolean mDisplayChangedCalled = false;
-        boolean mDisplayRemovedCalled = false;
-
-        FakeDisplayManagerCallback(int displayId) {
-            mDisplayId = displayId;
-        }
-
-        FakeDisplayManagerCallback() {
-            mDisplayId = -1;
-        }
-
-        @Override
-        public void onDisplayEvent(int displayId, int event) {
-            if (mDisplayId != -1 && displayId != mDisplayId) {
-                return;
-            }
-
-            if (event == DisplayManagerGlobal.EVENT_DISPLAY_ADDED) {
-                mDisplayAddedCalled = true;
-            }
-
-            if (event == DisplayManagerGlobal.EVENT_DISPLAY_CHANGED) {
-                mDisplayChangedCalled = true;
-            }
-
-            if (event == DisplayManagerGlobal.EVENT_DISPLAY_REMOVED) {
-                mDisplayRemovedCalled = true;
-            }
-        }
-
-        public void clear() {
-            mDisplayAddedCalled = false;
-            mDisplayChangedCalled = false;
-            mDisplayRemovedCalled = false;
-        }
-    }
-
-    private class FakeDisplayDevice extends DisplayDevice {
-        private DisplayDeviceInfo mDisplayDeviceInfo;
-
-        FakeDisplayDevice() {
-            super(null, null, "", mContext);
-        }
-
-        public void setDisplayDeviceInfo(DisplayDeviceInfo displayDeviceInfo) {
-            mDisplayDeviceInfo = displayDeviceInfo;
-        }
-
-        @Override
-        public boolean hasStableUniqueId() {
-            return false;
-        }
-
-        @Override
-        public DisplayDeviceInfo getDisplayDeviceInfoLocked() {
-            return mDisplayDeviceInfo;
-        }
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java b/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java
deleted file mode 100644
index e2a66f0..0000000
--- a/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java
+++ /dev/null
@@ -1,736 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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 android.hardware.display.BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR;
-import static android.hardware.display.BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF;
-import static android.hardware.display.BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT;
-
-import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED;
-import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED;
-import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE;
-import static com.android.server.display.DisplayDeviceConfig.HDR_PERCENT_OF_SCREEN_REQUIRED_DEFAULT;
-import static com.android.server.display.HighBrightnessModeController.HBM_TRANSITION_POINT_INVALID;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.anyFloat;
-import static org.mockito.Mockito.anyInt;
-import static org.mockito.Mockito.eq;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-import android.content.ContextWrapper;
-import android.hardware.display.BrightnessInfo;
-import android.os.Binder;
-import android.os.Handler;
-import android.os.Message;
-import android.os.test.TestLooper;
-import android.test.mock.MockContentResolver;
-import android.util.MathUtils;
-
-import androidx.test.core.app.ApplicationProvider;
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.internal.util.FrameworkStatsLog;
-import com.android.internal.util.test.FakeSettingsProvider;
-import com.android.internal.util.test.FakeSettingsProviderRule;
-import com.android.server.display.DisplayDeviceConfig.HighBrightnessModeData;
-import com.android.server.display.HighBrightnessModeController.Injector;
-import com.android.server.testutils.OffsettableClock;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class HighBrightnessModeControllerTest {
-
-    private static final int MINIMUM_LUX = 100;
-    private static final float TRANSITION_POINT = 0.763f;
-    private static final long TIME_WINDOW_MILLIS = 55 * 1000;
-    private static final long TIME_ALLOWED_IN_WINDOW_MILLIS = 12 * 1000;
-    private static final long TIME_MINIMUM_AVAILABLE_TO_ENABLE_MILLIS = 5 * 1000;
-    private static final boolean ALLOW_IN_LOW_POWER_MODE = false;
-
-    private static final float DEFAULT_MIN = 0.01f;
-    private static final float DEFAULT_MAX = 0.80f;
-
-    private static final int DISPLAY_WIDTH = 900;
-    private static final int DISPLAY_HEIGHT = 1600;
-
-    private static final float EPSILON = 0.000001f;
-
-    private OffsettableClock mClock;
-    private TestLooper mTestLooper;
-    private Handler mHandler;
-    private Binder mDisplayToken;
-    private String mDisplayUniqueId;
-    private Context mContextSpy;
-    private HighBrightnessModeMetadata mHighBrightnessModeMetadata;
-
-    @Rule
-    public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
-
-    @Mock Injector mInjectorMock;
-    @Mock HighBrightnessModeController.HdrBrightnessDeviceConfig mHdrBrightnessDeviceConfigMock;
-
-    private static final HighBrightnessModeData DEFAULT_HBM_DATA =
-            new HighBrightnessModeData(MINIMUM_LUX, TRANSITION_POINT, TIME_WINDOW_MILLIS,
-                    TIME_ALLOWED_IN_WINDOW_MILLIS, TIME_MINIMUM_AVAILABLE_TO_ENABLE_MILLIS,
-                    ALLOW_IN_LOW_POWER_MODE, HDR_PERCENT_OF_SCREEN_REQUIRED_DEFAULT);
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-        mClock = new OffsettableClock.Stopped();
-        mTestLooper = new TestLooper(mClock::now);
-        mDisplayToken = null;
-        mDisplayUniqueId = "unique_id";
-
-        mContextSpy = spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
-        final MockContentResolver resolver = mSettingsProviderRule.mockContentResolver(mContextSpy);
-        when(mContextSpy.getContentResolver()).thenReturn(resolver);
-    }
-
-    /////////////////
-    // Test Methods
-    /////////////////
-
-    @Test
-    public void testNoHbmData() {
-        initHandler(null);
-        final HighBrightnessModeController hbmc = new HighBrightnessModeController(
-                mInjectorMock, mHandler, DISPLAY_WIDTH, DISPLAY_HEIGHT, mDisplayToken,
-                mDisplayUniqueId, DEFAULT_MIN, DEFAULT_MAX, null, null, () -> {},
-                null, mContextSpy);
-        assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_OFF);
-        assertEquals(hbmc.getTransitionPoint(), HBM_TRANSITION_POINT_INVALID, 0.0f);
-    }
-
-    @Test
-    public void testNoHbmData_Enabled() {
-        initHandler(null);
-        final HighBrightnessModeController hbmc = new HighBrightnessModeController(
-                mInjectorMock, mHandler, DISPLAY_WIDTH, DISPLAY_HEIGHT, mDisplayToken,
-                mDisplayUniqueId, DEFAULT_MIN, DEFAULT_MAX, null, null, () -> {},
-                null, mContextSpy);
-        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
-        hbmc.onAmbientLuxChange(MINIMUM_LUX - 1); // below allowed range
-        assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_OFF);
-        assertEquals(hbmc.getTransitionPoint(), HBM_TRANSITION_POINT_INVALID, 0.0f);
-    }
-
-    @Test
-    public void testOffByDefault() {
-        final HighBrightnessModeController hbmc = createDefaultHbm();
-
-        assertState(hbmc, DEFAULT_MIN, TRANSITION_POINT, HIGH_BRIGHTNESS_MODE_OFF);
-    }
-
-    @Test
-    public void testAutoBrightnessEnabled_NoLux() {
-        final HighBrightnessModeController hbmc = createDefaultHbm();
-
-        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
-        assertState(hbmc, DEFAULT_MIN, TRANSITION_POINT, HIGH_BRIGHTNESS_MODE_OFF);
-    }
-
-    @Test
-    public void testAutoBrightnessEnabled_LowLux() {
-        final HighBrightnessModeController hbmc = createDefaultHbm();
-
-        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
-        hbmc.onAmbientLuxChange(MINIMUM_LUX - 1); // below allowed range
-        assertState(hbmc, DEFAULT_MIN, TRANSITION_POINT, HIGH_BRIGHTNESS_MODE_OFF);
-    }
-
-    @Test
-    public void testAutoBrightnessEnabled_HighLux() {
-        final HighBrightnessModeController hbmc = createDefaultHbm();
-
-        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
-        hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
-        assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT);
-    }
-
-    @Test
-    public void testAutoBrightnessEnabled_HighLux_ThenDisable() {
-        final HighBrightnessModeController hbmc = createDefaultHbm();
-
-        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
-        hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
-        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_DISABLED);
-
-        assertState(hbmc, DEFAULT_MIN, TRANSITION_POINT, HIGH_BRIGHTNESS_MODE_OFF);
-    }
-
-    @Test
-    public void testWithinHighRange_thenOverTime_thenEarnBackTime() {
-        final HighBrightnessModeController hbmc = createDefaultHbm();
-
-        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
-        hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
-        hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 0.01f);
-
-        // Verify we are in HBM
-        assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT);
-
-        // Use up all the time in the window.
-        advanceTime(TIME_ALLOWED_IN_WINDOW_MILLIS);
-
-        // Verify we are not out of HBM
-        assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT);
-
-        // Shift time so that the HBM event is at the beginning of the current window
-        advanceTime(TIME_WINDOW_MILLIS - TIME_ALLOWED_IN_WINDOW_MILLIS);
-        // Shift time again so that we are just below the minimum allowable
-        advanceTime(TIME_MINIMUM_AVAILABLE_TO_ENABLE_MILLIS - 1);
-
-        // Verify we are not out of HBM
-        assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT);
-
-        // Advance the necessary millisecond
-        advanceTime(1);
-
-        // Verify we are allowed HBM again.
-        assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT);
-    }
-
-    @Test
-    public void testInHBM_ThenLowLux() {
-        final HighBrightnessModeController hbmc = createDefaultHbm();
-
-        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
-        hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
-        hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 0.01f);
-
-        // Verify we are in HBM
-        assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT);
-
-        advanceTime(TIME_ALLOWED_IN_WINDOW_MILLIS / 2);
-
-        // Verify we are in HBM
-        assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT);
-
-        hbmc.onAmbientLuxChange(1);
-        advanceTime(TIME_ALLOWED_IN_WINDOW_MILLIS / 2 + 1);
-
-        // Verify we are out of HBM
-        assertState(hbmc, DEFAULT_MIN, TRANSITION_POINT, HIGH_BRIGHTNESS_MODE_OFF);
-
-    }
-
-    @Test
-    public void testInHBM_TestMultipleEvents_DueToAutoBrightness() {
-        final HighBrightnessModeController hbmc = createDefaultHbm();
-
-        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
-        hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
-
-        hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 0.01f);
-        advanceTime(TIME_ALLOWED_IN_WINDOW_MILLIS / 2);
-
-        // Verify we are in HBM
-        assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT);
-
-        hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT - 0.01f);
-        advanceTime(1);
-
-        assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT);
-
-        hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 0.01f);
-        advanceTime(TIME_ALLOWED_IN_WINDOW_MILLIS / 2);
-
-        assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT);
-
-        advanceTime(2);
-
-        // Now we should be out again.
-        assertState(hbmc, DEFAULT_MIN, TRANSITION_POINT, HIGH_BRIGHTNESS_MODE_OFF);
-    }
-
-    @Test
-    public void testInHBM_TestMultipleEvents_DueToLux() {
-        final HighBrightnessModeController hbmc = createDefaultHbm();
-
-        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
-        hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
-
-        // Go into HBM for half the allowed window
-        hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 0.01f);
-        advanceTime(TIME_ALLOWED_IN_WINDOW_MILLIS / 2);
-        assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT);
-
-        // Move lux below threshold (ending first event);
-        hbmc.onAmbientLuxChange(MINIMUM_LUX - 1);
-        hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT);
-        assertState(hbmc, DEFAULT_MIN, TRANSITION_POINT, HIGH_BRIGHTNESS_MODE_OFF);
-
-        // Move up some amount of time so that there's still time in the window even after a
-        // second event.
-        advanceTime((TIME_WINDOW_MILLIS - TIME_ALLOWED_IN_WINDOW_MILLIS) / 2);
-        assertState(hbmc, DEFAULT_MIN, TRANSITION_POINT, HIGH_BRIGHTNESS_MODE_OFF);
-
-        // Go into HBM for just under the second half of allowed window
-        hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
-        hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 1);
-        advanceTime((TIME_ALLOWED_IN_WINDOW_MILLIS / 2) - 1);
-
-        assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT);
-
-        // Now exhaust the time
-        advanceTime(2);
-        assertState(hbmc, DEFAULT_MIN, TRANSITION_POINT, HIGH_BRIGHTNESS_MODE_OFF);
-    }
-
-    @Test
-    public void testHbmIsNotTurnedOffInHighThermalState() throws Exception {
-        final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock());
-
-        // Disabled thermal throttling
-        hbmc.onBrightnessChanged(/*brightness=*/ 1f, /*unthrottledBrightness*/ 1f,
-                BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE);
-
-        assertFalse(hbmc.isThermalThrottlingActive());
-
-        // Try to go into HBM mode
-        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
-        hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
-        advanceTime(1);
-
-        assertEquals(HIGH_BRIGHTNESS_MODE_SUNLIGHT, hbmc.getHighBrightnessMode());
-
-        // Enable thermal throttling
-        hbmc.onBrightnessChanged(/*brightness=*/ TRANSITION_POINT - 0.01f,
-                /*unthrottledBrightness*/ 1f, BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL);
-        advanceTime(10);
-        assertEquals(HIGH_BRIGHTNESS_MODE_SUNLIGHT, hbmc.getHighBrightnessMode());
-        assertTrue(hbmc.isThermalThrottlingActive());
-
-        // Disabled thermal throttling
-        hbmc.onBrightnessChanged(/*brightness=*/ 1f, /*unthrottledBrightness*/ 1f,
-                BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE);
-        advanceTime(1);
-        assertEquals(HIGH_BRIGHTNESS_MODE_SUNLIGHT, hbmc.getHighBrightnessMode());
-        assertFalse(hbmc.isThermalThrottlingActive());
-    }
-
-    @Test
-    public void testHdrRequires50PercentOfScreen() {
-        final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock());
-
-        final int layerWidth = DISPLAY_WIDTH;
-        final int smallLayerHeight = DISPLAY_HEIGHT / 2 - 1; // height to use for <50%
-        final int largeLayerHeight = DISPLAY_HEIGHT / 2 + 1; // height to use for >50%
-
-        // ensure hdr doesn't turn on if layer is too small
-        hbmc.getHdrListener().onHdrInfoChanged(null /*displayToken*/, 1 /*numberOfHdrLayers*/,
-                layerWidth, smallLayerHeight, 0 /*flags*/, 1.0f /*maxDesiredHdrSdrRatio*/);
-        advanceTime(0);
-        assertEquals(HIGH_BRIGHTNESS_MODE_OFF, hbmc.getHighBrightnessMode());
-
-        // Now check with layer larger than 50%
-        hbmc.getHdrListener().onHdrInfoChanged(null /*displayToken*/, 1 /*numberOfHdrLayers*/,
-                layerWidth, largeLayerHeight, 0 /*flags*/, 1.0f /*maxDesiredHdrSdrRatio*/);
-        advanceTime(0);
-        assertEquals(HIGH_BRIGHTNESS_MODE_HDR, hbmc.getHighBrightnessMode());
-    }
-
-    @Test
-    public void testHdrRespectsMaxDesiredHdrSdrRatio() {
-        final HighBrightnessModeController hbmc = new TestHbmBuilder()
-                .setClock(new OffsettableClock())
-                .setHdrBrightnessConfig(mHdrBrightnessDeviceConfigMock)
-                .build();
-
-        // Passthrough return the max desired hdr/sdr ratio
-        when(mHdrBrightnessDeviceConfigMock.getHdrBrightnessFromSdr(anyFloat(), anyFloat()))
-                .thenAnswer(i -> i.getArgument(1));
-
-        hbmc.getHdrListener().onHdrInfoChanged(null /*displayToken*/, 1 /*numberOfHdrLayers*/,
-                DISPLAY_WIDTH, DISPLAY_HEIGHT, 0 /*flags*/, 2.0f /*maxDesiredHdrSdrRatio*/);
-        advanceTime(0);
-        assertEquals(2.0f, hbmc.getHdrBrightnessValue(), EPSILON);
-
-        // The hdr ratio cannot be less than 1.
-        hbmc.getHdrListener().onHdrInfoChanged(null /*displayToken*/, 1 /*numberOfHdrLayers*/,
-                DISPLAY_WIDTH, DISPLAY_HEIGHT, 0 /*flags*/, 0.5f /*maxDesiredHdrSdrRatio*/);
-        advanceTime(0);
-        assertEquals(1.0f, hbmc.getHdrBrightnessValue(), EPSILON);
-
-        // The hdr ratio can be as much as positive infinity
-        hbmc.getHdrListener().onHdrInfoChanged(null /*displayToken*/, 1 /*numberOfHdrLayers*/,
-                DISPLAY_WIDTH, DISPLAY_HEIGHT, 0 /*flags*/,
-                Float.POSITIVE_INFINITY /*maxDesiredHdrSdrRatio*/);
-        advanceTime(0);
-        assertEquals(Float.POSITIVE_INFINITY, hbmc.getHdrBrightnessValue(), 0.0);
-    }
-
-
-    @Test
-    public void testHdrTrumpsSunlight() {
-        final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock());
-
-        // Turn on sunlight
-        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
-        hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
-        advanceTime(0);
-        assertEquals(HIGH_BRIGHTNESS_MODE_SUNLIGHT, hbmc.getHighBrightnessMode());
-        assertEquals(DEFAULT_MAX, hbmc.getCurrentBrightnessMax(), EPSILON);
-
-        // turn on hdr
-        hbmc.getHdrListener().onHdrInfoChanged(null /*displayToken*/, 1 /*numberOfHdrLayers*/,
-                DISPLAY_WIDTH, DISPLAY_HEIGHT, 0 /*flags*/, 1.0f /*maxDesiredHdrSdrRatio*/);
-        advanceTime(0);
-        assertEquals(HIGH_BRIGHTNESS_MODE_HDR, hbmc.getHighBrightnessMode());
-        assertEquals(TRANSITION_POINT, hbmc.getCurrentBrightnessMax(), EPSILON);
-    }
-
-    @Test
-    public void testHdrBrightnessLimitSameAsNormalLimit() {
-        final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock());
-
-        // Check limit when HBM is off
-        hbmc.getHdrListener().onHdrInfoChanged(null /*displayToken*/, 1 /*numberOfHdrLayers*/,
-                DISPLAY_WIDTH, DISPLAY_HEIGHT, 0 /*flags*/, 1.0f /*maxDesiredHdrSdrRatio*/);
-        advanceTime(0);
-        assertEquals(HIGH_BRIGHTNESS_MODE_HDR, hbmc.getHighBrightnessMode());
-        assertEquals(TRANSITION_POINT, hbmc.getCurrentBrightnessMax(), EPSILON);
-
-        // Check limit with HBM is set to HDR
-        hbmc.getHdrListener().onHdrInfoChanged(null /*displayToken*/, 0 /*numberOfHdrLayers*/,
-                DISPLAY_WIDTH, DISPLAY_HEIGHT, 0 /*flags*/, 1.0f /*maxDesiredHdrSdrRatio*/);
-        advanceTime(0);
-        assertEquals(HIGH_BRIGHTNESS_MODE_OFF, hbmc.getHighBrightnessMode());
-        assertEquals(TRANSITION_POINT, hbmc.getCurrentBrightnessMax(), EPSILON);
-    }
-
-    @Test
-    public void testHdrBrightnessScaledNormalBrightness() {
-        final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock());
-
-        hbmc.getHdrListener().onHdrInfoChanged(null /*displayToken*/, 1 /*numberOfHdrLayers*/,
-                DISPLAY_WIDTH, DISPLAY_HEIGHT, 0 /*flags*/, 1.0f /*maxDesiredHdrSdrRatio*/);
-        advanceTime(0);
-        assertEquals(HIGH_BRIGHTNESS_MODE_HDR, hbmc.getHighBrightnessMode());
-
-        // verify things are scaled for 0.5f
-        float brightness = 0.5f;
-        float expectedHdrBrightness = MathUtils.map(DEFAULT_MIN, TRANSITION_POINT,
-                DEFAULT_MIN, DEFAULT_MAX, brightness); // map value from normal range to hdr range
-        hbmcOnBrightnessChanged(hbmc, brightness);
-        advanceTime(0);
-        assertEquals(expectedHdrBrightness, hbmc.getHdrBrightnessValue(), EPSILON);
-
-        // Try another value
-        brightness = 0.33f;
-        expectedHdrBrightness = MathUtils.map(DEFAULT_MIN, TRANSITION_POINT,
-                DEFAULT_MIN, DEFAULT_MAX, brightness); // map value from normal range to hdr range
-        hbmcOnBrightnessChanged(hbmc, brightness);
-        advanceTime(0);
-        assertEquals(expectedHdrBrightness, hbmc.getHdrBrightnessValue(), EPSILON);
-
-        // Try the min value
-        brightness = DEFAULT_MIN;
-        expectedHdrBrightness = DEFAULT_MIN;
-        hbmcOnBrightnessChanged(hbmc, brightness);
-        advanceTime(0);
-        assertEquals(expectedHdrBrightness, hbmc.getHdrBrightnessValue(), EPSILON);
-
-        // Try the max value
-        brightness = TRANSITION_POINT;
-        expectedHdrBrightness = DEFAULT_MAX;
-        hbmcOnBrightnessChanged(hbmc, brightness);
-        advanceTime(0);
-        assertEquals(expectedHdrBrightness, hbmc.getHdrBrightnessValue(), EPSILON);
-    }
-
-    @Test
-    public void testHbmStats_StateChange() {
-        final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock());
-        final int displayStatsId = mDisplayUniqueId.hashCode();
-
-        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
-        hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT);
-        hbmc.getHdrListener().onHdrInfoChanged(null /*displayToken*/, 1 /*numberOfHdrLayers*/,
-                DISPLAY_WIDTH, DISPLAY_HEIGHT, 0 /*flags*/, 1.0f /*maxDesiredHdrSdrRatio*/);
-        advanceTime(0);
-        assertEquals(HIGH_BRIGHTNESS_MODE_HDR, hbmc.getHighBrightnessMode());
-
-        // Verify Stats HBM_ON_HDR
-        verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
-            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_HDR),
-            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN));
-
-        hbmc.getHdrListener().onHdrInfoChanged(null /*displayToken*/, 0 /*numberOfHdrLayers*/,
-                0, 0, 0 /*flags*/, 1.0f /*maxDesiredHdrSdrRatio*/);
-        advanceTime(0);
-
-        // Verify Stats HBM_OFF
-        verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
-            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF),
-            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN));
-
-        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
-        hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
-        hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 0.01f);
-
-        // Verify Stats HBM_ON_SUNLIGHT
-        verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
-            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT),
-            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN));
-
-        hbmc.onAmbientLuxChange(1);
-        advanceTime(TIME_ALLOWED_IN_WINDOW_MILLIS / 2 + 1);
-
-        // Verify Stats HBM_OFF
-        verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
-            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF),
-            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_LUX_DROP));
-    }
-
-    @Test
-    public void testHbmStats_NbmHdrNoReport() {
-        final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock());
-        final int displayStatsId = mDisplayUniqueId.hashCode();
-
-        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
-        hbmcOnBrightnessChanged(hbmc, DEFAULT_MIN);
-        hbmc.getHdrListener().onHdrInfoChanged(null /*displayToken*/, 1 /*numberOfHdrLayers*/,
-                DISPLAY_WIDTH, DISPLAY_HEIGHT, 0 /*flags*/, 1.0f /*maxDesiredHdrSdrRatio*/);
-        advanceTime(0);
-        assertEquals(HIGH_BRIGHTNESS_MODE_HDR, hbmc.getHighBrightnessMode());
-
-        // Verify Stats HBM_ON_HDR not report
-        verify(mInjectorMock, never()).reportHbmStateChange(eq(displayStatsId),
-            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_HDR),
-            anyInt());
-    }
-
-    @Test
-    public void testHbmStats_HighLuxLowBrightnessNoReport() {
-        final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock());
-        final int displayStatsId = mDisplayUniqueId.hashCode();
-
-        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
-        hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
-        hbmcOnBrightnessChanged(hbmc, DEFAULT_MIN);
-        advanceTime(0);
-        // verify in HBM sunlight mode
-        assertEquals(HIGH_BRIGHTNESS_MODE_SUNLIGHT, hbmc.getHighBrightnessMode());
-
-        // Verify Stats HBM_ON_SUNLIGHT not report
-        verify(mInjectorMock, never()).reportHbmStateChange(eq(displayStatsId),
-            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT),
-            anyInt());
-    }
-
-    // Test reporting of thermal throttling when triggered externally through
-    // HighBrightnessModeController.onBrightnessChanged()
-    @Test
-    public void testHbmStats_ExternalThermalOff() throws Exception {
-        final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock());
-        final int displayStatsId = mDisplayUniqueId.hashCode();
-        final float hbmBrightness = TRANSITION_POINT + 0.01f;
-        final float nbmBrightness = TRANSITION_POINT - 0.01f;
-
-        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
-        hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
-        // Brightness is unthrottled, HBM brightness granted
-        hbmc.onBrightnessChanged(hbmBrightness, hbmBrightness,
-                BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE);
-        advanceTime(1);
-        verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
-            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT),
-            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN));
-
-        // Brightness is thermally throttled, HBM brightness denied (NBM brightness granted)
-        hbmc.onBrightnessChanged(nbmBrightness, hbmBrightness,
-                BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL);
-        advanceTime(1);
-        // We expect HBM mode to remain set to sunlight, indicating that HBMC *allows* this mode.
-        // However, we expect the HBM state reported by HBMC to be off, since external thermal
-        // throttling (reported to HBMC through onBrightnessChanged()) lowers brightness to below
-        // the HBM transition point.
-        assertEquals(HIGH_BRIGHTNESS_MODE_SUNLIGHT, hbmc.getHighBrightnessMode());
-        verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
-            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF),
-            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_THERMAL_LIMIT));
-    }
-
-    @Test
-    public void testHbmStats_TimeOut() {
-        final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock());
-        final int displayStatsId = mDisplayUniqueId.hashCode();
-
-        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
-        hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
-        hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 0.01f);
-        advanceTime(0);
-        verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
-            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT),
-            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN));
-
-        // Use up all the time in the window.
-        advanceTime(TIME_WINDOW_MILLIS + 1);
-
-        verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
-            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF),
-            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_TIME_LIMIT));
-    }
-
-    @Test
-    public void testHbmStats_DisplayOff() {
-        final HighBrightnessModeController hbmc = createDefaultHbm();
-        final int displayStatsId = mDisplayUniqueId.hashCode();
-
-        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
-        hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
-        hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 0.01f);
-        advanceTime(0);
-        verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
-            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT),
-            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN));
-
-        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE);
-        verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
-            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF),
-            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_DISPLAY_OFF));
-    }
-
-    @Test
-    public void testHbmStats_HdrPlaying() {
-        final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock());
-        final int displayStatsId = mDisplayUniqueId.hashCode();
-
-        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
-        hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
-        hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 0.01f);
-        advanceTime(0);
-        verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
-            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT),
-            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN));
-
-        hbmc.getHdrListener().onHdrInfoChanged(null /*displayToken*/, 1 /*numberOfHdrLayers*/,
-                DISPLAY_WIDTH, DISPLAY_HEIGHT, 0 /*flags*/, 1.0f /*maxDesiredHdrSdrRatio*/);
-        advanceTime(0);
-
-        verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
-            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_HDR),
-            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_HDR_PLAYING));
-    }
-
-    @Test
-    public void tetHbmStats_LowRequestedBrightness() {
-        final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock());
-        final int displayStatsId = mDisplayUniqueId.hashCode();
-
-        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
-        hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
-        hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 0.01f);
-        advanceTime(0);
-        // verify in HBM sunlight mode
-        assertEquals(HIGH_BRIGHTNESS_MODE_SUNLIGHT, hbmc.getHighBrightnessMode());
-        // verify HBM_ON_SUNLIGHT
-        verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
-            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT),
-            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN));
-
-        hbmcOnBrightnessChanged(hbmc, DEFAULT_MIN);
-        // verify HBM_SV_OFF due to LOW_REQUESTED_BRIGHTNESS
-        verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
-            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF),
-            eq(FrameworkStatsLog
-                      .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_LOW_REQUESTED_BRIGHTNESS));
-    }
-
-    private void assertState(HighBrightnessModeController hbmc,
-            float brightnessMin, float brightnessMax, int hbmMode) {
-        assertEquals(brightnessMin, hbmc.getCurrentBrightnessMin(), EPSILON);
-        assertEquals(brightnessMax, hbmc.getCurrentBrightnessMax(), EPSILON);
-        assertEquals(hbmMode, hbmc.getHighBrightnessMode());
-    }
-
-    private class TestHbmBuilder {
-        OffsettableClock mClock;
-        HighBrightnessModeController.HdrBrightnessDeviceConfig mHdrBrightnessCfg;
-
-        TestHbmBuilder setClock(OffsettableClock clock) {
-            mClock = clock;
-            return this;
-        }
-
-        TestHbmBuilder setHdrBrightnessConfig(
-                HighBrightnessModeController.HdrBrightnessDeviceConfig hdrBrightnessCfg
-        ) {
-            mHdrBrightnessCfg = hdrBrightnessCfg;
-            return this;
-        }
-
-        HighBrightnessModeController build() {
-            initHandler(mClock);
-            if (mHighBrightnessModeMetadata == null) {
-                mHighBrightnessModeMetadata = new HighBrightnessModeMetadata();
-            }
-            return new HighBrightnessModeController(mInjectorMock, mHandler, DISPLAY_WIDTH,
-                    DISPLAY_HEIGHT, mDisplayToken, mDisplayUniqueId, DEFAULT_MIN, DEFAULT_MAX,
-                    DEFAULT_HBM_DATA, mHdrBrightnessCfg, () -> {}, mHighBrightnessModeMetadata,
-                    mContextSpy);
-        }
-
-    }
-
-    private HighBrightnessModeController createDefaultHbm() {
-        return new TestHbmBuilder().build();
-    }
-
-    // Creates instance with standard initialization values.
-    private HighBrightnessModeController createDefaultHbm(OffsettableClock clock) {
-        return new TestHbmBuilder().setClock(clock).build();
-    }
-
-    private void initHandler(OffsettableClock clock) {
-        mClock = clock != null ? clock : new OffsettableClock.Stopped();
-        when(mInjectorMock.getClock()).thenReturn(mClock::now);
-        mTestLooper = new TestLooper(mClock::now);
-        mHandler = new Handler(mTestLooper.getLooper(), new Handler.Callback() {
-            @Override
-            public boolean handleMessage(Message msg) {
-                return true;
-            }
-        });
-    }
-
-    private void advanceTime(long timeMs) {
-        mClock.fastForward(timeMs);
-        mTestLooper.dispatchAll();
-    }
-
-    private void hbmcOnBrightnessChanged(HighBrightnessModeController hbmc, float brightness) {
-        hbmc.onBrightnessChanged(brightness, brightness, BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE);
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
deleted file mode 100644
index 1eec70d..0000000
--- a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
+++ /dev/null
@@ -1,981 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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 android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE;
-import static android.view.Display.DEFAULT_DISPLAY;
-import static android.view.Display.DEFAULT_DISPLAY_GROUP;
-import static android.view.Display.FLAG_REAR;
-import static android.view.Display.TYPE_EXTERNAL;
-import static android.view.Display.TYPE_INTERNAL;
-import static android.view.Display.TYPE_VIRTUAL;
-
-import static com.android.server.display.DeviceStateToLayoutMap.STATE_DEFAULT;
-import static com.android.server.display.DisplayAdapter.DISPLAY_DEVICE_EVENT_ADDED;
-import static com.android.server.display.DisplayAdapter.DISPLAY_DEVICE_EVENT_CHANGED;
-import static com.android.server.display.DisplayAdapter.DISPLAY_DEVICE_EVENT_REMOVED;
-import static com.android.server.display.DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY;
-import static com.android.server.display.LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_ADDED;
-import static com.android.server.display.LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_REMOVED;
-import static com.android.server.display.layout.Layout.Display.POSITION_REAR;
-import static com.android.server.display.layout.Layout.Display.POSITION_UNKNOWN;
-
-import static com.google.common.truth.Truth.assertThat;
-
-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.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.app.PropertyInvalidatedCache;
-import android.content.Context;
-import android.content.res.Resources;
-import android.os.Handler;
-import android.os.IPowerManager;
-import android.os.IThermalService;
-import android.os.PowerManager;
-import android.os.Process;
-import android.os.test.TestLooper;
-import android.view.Display;
-import android.view.DisplayAddress;
-import android.view.DisplayInfo;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import com.android.server.display.layout.DisplayIdProducer;
-import com.android.server.display.layout.Layout;
-
-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.MockitoAnnotations;
-import org.mockito.Spy;
-
-import java.io.File;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.util.Arrays;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class LogicalDisplayMapperTest {
-    private static int sUniqueTestDisplayId = 0;
-    private static final int DEVICE_STATE_CLOSED = 0;
-    private static final int DEVICE_STATE_OPEN = 2;
-    private static int sNextNonDefaultDisplayId = DEFAULT_DISPLAY + 1;
-    private static final File NON_EXISTING_FILE = new File("/non_existing_folder/should_not_exist");
-
-    private DisplayDeviceRepository mDisplayDeviceRepo;
-    private LogicalDisplayMapper mLogicalDisplayMapper;
-    private TestLooper mLooper;
-    private Handler mHandler;
-    private PowerManager mPowerManager;
-
-    private final DisplayIdProducer mIdProducer = (isDefault) ->
-            isDefault ? DEFAULT_DISPLAY : sNextNonDefaultDisplayId++;
-
-    @Mock LogicalDisplayMapper.Listener mListenerMock;
-    @Mock Context mContextMock;
-    @Mock Resources mResourcesMock;
-    @Mock IPowerManager mIPowerManagerMock;
-    @Mock IThermalService mIThermalServiceMock;
-    @Spy DeviceStateToLayoutMap mDeviceStateToLayoutMapSpy =
-            new DeviceStateToLayoutMap(mIdProducer, NON_EXISTING_FILE);
-
-    @Captor ArgumentCaptor<LogicalDisplay> mDisplayCaptor;
-
-    @Before
-    public void setUp() {
-        // Share classloader to allow package private access.
-        System.setProperty("dexmaker.share_classloader", "true");
-        MockitoAnnotations.initMocks(this);
-
-        mDisplayDeviceRepo = new DisplayDeviceRepository(
-                new DisplayManagerService.SyncRoot(),
-                new PersistentDataStore(new PersistentDataStore.Injector() {
-                    @Override
-                    public InputStream openRead() {
-                        return null;
-                    }
-
-                    @Override
-                    public OutputStream startWrite() {
-                        return null;
-                    }
-
-                    @Override
-                    public void finishWrite(OutputStream os, boolean success) {}
-                }));
-
-        // Disable binder caches in this process.
-        PropertyInvalidatedCache.disableForTestMode();
-
-        mPowerManager = new PowerManager(mContextMock, mIPowerManagerMock, mIThermalServiceMock,
-                null);
-
-        when(mContextMock.getSystemServiceName(PowerManager.class))
-                .thenReturn(Context.POWER_SERVICE);
-        when(mContextMock.getSystemService(PowerManager.class)).thenReturn(mPowerManager);
-        when(mContextMock.getResources()).thenReturn(mResourcesMock);
-        when(mResourcesMock.getBoolean(
-                com.android.internal.R.bool.config_supportsConcurrentInternalDisplays))
-                .thenReturn(true);
-        when(mResourcesMock.getIntArray(
-                com.android.internal.R.array.config_deviceStatesOnWhichToWakeUp))
-                .thenReturn(new int[]{1, 2});
-        when(mResourcesMock.getIntArray(
-                com.android.internal.R.array.config_deviceStatesOnWhichToSleep))
-                .thenReturn(new int[]{0});
-
-        mLooper = new TestLooper();
-        mHandler = new Handler(mLooper.getLooper());
-        mLogicalDisplayMapper = new LogicalDisplayMapper(mContextMock, mDisplayDeviceRepo,
-                mListenerMock, new DisplayManagerService.SyncRoot(), mHandler,
-                mDeviceStateToLayoutMapSpy);
-    }
-
-
-    /////////////////
-    // Test Methods
-    /////////////////
-
-    @Test
-    public void testDisplayDeviceAddAndRemove_Internal() {
-        DisplayDevice device = createDisplayDevice(TYPE_INTERNAL, 600, 800,
-                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
-
-        // add
-        LogicalDisplay displayAdded = add(device);
-        assertEquals(info(displayAdded).address, info(device).address);
-        assertEquals(DEFAULT_DISPLAY, id(displayAdded));
-
-        // remove
-        mDisplayDeviceRepo.onDisplayDeviceEvent(device, DISPLAY_DEVICE_EVENT_REMOVED);
-        verify(mListenerMock).onLogicalDisplayEventLocked(
-                mDisplayCaptor.capture(), eq(LOGICAL_DISPLAY_EVENT_REMOVED));
-        LogicalDisplay displayRemoved = mDisplayCaptor.getValue();
-        assertEquals(DEFAULT_DISPLAY, id(displayRemoved));
-        assertEquals(displayAdded, displayRemoved);
-    }
-
-    @Test
-    public void testDisplayDeviceAddAndRemove_NonInternalTypes() {
-        testDisplayDeviceAddAndRemove_NonInternal(TYPE_EXTERNAL);
-        testDisplayDeviceAddAndRemove_NonInternal(Display.TYPE_WIFI);
-        testDisplayDeviceAddAndRemove_NonInternal(Display.TYPE_OVERLAY);
-        testDisplayDeviceAddAndRemove_NonInternal(TYPE_VIRTUAL);
-        testDisplayDeviceAddAndRemove_NonInternal(Display.TYPE_UNKNOWN);
-
-        // Call the internal test again, just to verify that adding non-internal displays
-        // doesn't affect the ability for an internal display to become the default display.
-        testDisplayDeviceAddAndRemove_Internal();
-    }
-
-    @Test
-    public void testDisplayDeviceAdd_TwoInternalOneDefault() {
-        DisplayDevice device1 = createDisplayDevice(TYPE_INTERNAL, 600, 800, 0);
-        DisplayDevice device2 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
-                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
-
-        LogicalDisplay display1 = add(device1);
-        assertEquals(info(display1).address, info(device1).address);
-        assertNotEquals(DEFAULT_DISPLAY, id(display1));
-
-        LogicalDisplay display2 = add(device2);
-        assertEquals(info(display2).address, info(device2).address);
-        assertEquals(DEFAULT_DISPLAY, id(display2));
-    }
-
-    @Test
-    public void testDisplayDeviceAdd_TwoInternalBothDefault() {
-        DisplayDevice device1 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
-                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
-        DisplayDevice device2 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
-                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
-
-        LogicalDisplay display1 = add(device1);
-        assertEquals(info(display1).address, info(device1).address);
-        assertEquals(DEFAULT_DISPLAY, id(display1));
-
-        LogicalDisplay display2 = add(device2);
-        assertEquals(info(display2).address, info(device2).address);
-        // Despite the flags, we can only have one default display
-        assertNotEquals(DEFAULT_DISPLAY, id(display2));
-    }
-
-    @Test
-    public void testDisplayDeviceAddAndRemove_OneExternalDefault() {
-        DisplayDevice device = createDisplayDevice(TYPE_EXTERNAL, 600, 800,
-                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
-
-        // add
-        LogicalDisplay displayAdded = add(device);
-        assertEquals(info(displayAdded).address, info(device).address);
-        assertEquals(Display.DEFAULT_DISPLAY, id(displayAdded));
-
-        // remove
-        mDisplayDeviceRepo.onDisplayDeviceEvent(device, DISPLAY_DEVICE_EVENT_REMOVED);
-        verify(mListenerMock).onLogicalDisplayEventLocked(
-                mDisplayCaptor.capture(), eq(LOGICAL_DISPLAY_EVENT_REMOVED));
-        LogicalDisplay displayRemoved = mDisplayCaptor.getValue();
-        assertEquals(DEFAULT_DISPLAY, id(displayRemoved));
-        assertEquals(displayAdded, displayRemoved);
-    }
-
-    @Test
-    public void testDisplayDeviceAddAndRemove_SwitchDefault() {
-        DisplayDevice device1 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
-                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
-        DisplayDevice device2 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
-                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
-
-        LogicalDisplay display1 = add(device1);
-        assertEquals(info(display1).address, info(device1).address);
-        assertEquals(DEFAULT_DISPLAY, id(display1));
-
-        LogicalDisplay display2 = add(device2);
-        assertEquals(info(display2).address, info(device2).address);
-        // We can only have one default display
-        assertEquals(DEFAULT_DISPLAY, id(display1));
-
-        // remove
-        mDisplayDeviceRepo.onDisplayDeviceEvent(device1, DISPLAY_DEVICE_EVENT_REMOVED);
-
-        verify(mListenerMock).onLogicalDisplayEventLocked(
-                mDisplayCaptor.capture(), eq(LOGICAL_DISPLAY_EVENT_REMOVED));
-        LogicalDisplay displayRemoved = mDisplayCaptor.getValue();
-        // Display 1 is still the default logical display
-        assertEquals(DEFAULT_DISPLAY, id(display1));
-        // The logical displays had their devices swapped and Display 2 was removed
-        assertEquals(display2, displayRemoved);
-        assertEquals(info(display1).address, info(device2).address);
-    }
-
-    @Test
-    public void testGetDisplayIdsLocked() {
-        add(createDisplayDevice(TYPE_INTERNAL, 600, 800,
-                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY));
-        add(createDisplayDevice(TYPE_EXTERNAL, 600, 800, 0));
-        add(createDisplayDevice(TYPE_VIRTUAL, 600, 800, 0));
-
-        int [] ids = mLogicalDisplayMapper.getDisplayIdsLocked(Process.SYSTEM_UID,
-                /* includeDisabled= */ true);
-        assertEquals(3, ids.length);
-        Arrays.sort(ids);
-        assertEquals(DEFAULT_DISPLAY, ids[0]);
-    }
-
-    @Test
-    public void testGetDisplayInfoForStateLocked_defaultLayout() {
-        final DisplayDevice device1 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
-                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
-        final DisplayDevice device2 = createDisplayDevice(TYPE_INTERNAL, 200, 800,
-                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
-
-        add(device1);
-        add(device2);
-
-        Layout layout1 = new Layout();
-        createDefaultDisplay(layout1, device1);
-        createNonDefaultDisplay(layout1, device2, /* enabled= */ true, /* group= */ null);
-        when(mDeviceStateToLayoutMapSpy.get(STATE_DEFAULT)).thenReturn(layout1);
-        assertThat(layout1.size()).isEqualTo(2);
-        final int logicalId2 = layout1.getByAddress(info(device2).address).getLogicalDisplayId();
-
-        final DisplayInfo displayInfoDefault = mLogicalDisplayMapper.getDisplayInfoForStateLocked(
-                STATE_DEFAULT, DEFAULT_DISPLAY);
-        assertThat(displayInfoDefault.displayId).isEqualTo(DEFAULT_DISPLAY);
-        assertThat(displayInfoDefault.logicalWidth).isEqualTo(width(device1));
-        assertThat(displayInfoDefault.logicalHeight).isEqualTo(height(device1));
-
-        final DisplayInfo displayInfoOther = mLogicalDisplayMapper.getDisplayInfoForStateLocked(
-                STATE_DEFAULT, logicalId2);
-        assertThat(displayInfoOther).isNotNull();
-        assertThat(displayInfoOther.displayId).isEqualTo(logicalId2);
-        assertThat(displayInfoOther.logicalWidth).isEqualTo(width(device2));
-        assertThat(displayInfoOther.logicalHeight).isEqualTo(height(device2));
-    }
-
-    @Test
-    public void testGetDisplayInfoForStateLocked_multipleLayouts() {
-        final DisplayDevice device1 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
-                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
-        final DisplayDevice device2 = createDisplayDevice(TYPE_INTERNAL, 200, 800,
-                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
-        final DisplayDevice device3 = createDisplayDevice(TYPE_VIRTUAL, 700, 800,
-                DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP);
-
-        add(device1);
-        add(device2);
-        add(device3);
-
-        Layout layout1 = new Layout();
-        createDefaultDisplay(layout1, device1);
-        when(mDeviceStateToLayoutMapSpy.get(STATE_DEFAULT)).thenReturn(layout1);
-
-        final int layoutState2 = 2;
-        Layout layout2 = new Layout();
-        createNonDefaultDisplay(layout2, device2, /* enabled= */ true, /* group= */ null);
-        // Device3 is the default display.
-        createDefaultDisplay(layout2, device3);
-        when(mDeviceStateToLayoutMapSpy.get(layoutState2)).thenReturn(layout2);
-        assertThat(layout2.size()).isEqualTo(2);
-        final int logicalId2 = layout2.getByAddress(info(device2).address).getLogicalDisplayId();
-
-        // Default layout.
-        final DisplayInfo displayInfoLayout1Default =
-                mLogicalDisplayMapper.getDisplayInfoForStateLocked(
-                        STATE_DEFAULT, DEFAULT_DISPLAY);
-        assertThat(displayInfoLayout1Default.displayId).isEqualTo(DEFAULT_DISPLAY);
-        assertThat(displayInfoLayout1Default.logicalWidth).isEqualTo(width(device1));
-        assertThat(displayInfoLayout1Default.logicalHeight).isEqualTo(height(device1));
-
-        // Second layout, where device3 is the default display.
-        final DisplayInfo displayInfoLayout2Default =
-                mLogicalDisplayMapper.getDisplayInfoForStateLocked(
-                        layoutState2, DEFAULT_DISPLAY);
-        assertThat(displayInfoLayout2Default.displayId).isEqualTo(DEFAULT_DISPLAY);
-        assertThat(displayInfoLayout2Default.logicalWidth).isEqualTo(width(device3));
-        assertThat(displayInfoLayout2Default.logicalHeight).isEqualTo(height(device3));
-
-        final DisplayInfo displayInfoLayout2Other =
-                mLogicalDisplayMapper.getDisplayInfoForStateLocked(
-                        layoutState2, logicalId2);
-        assertThat(displayInfoLayout2Other).isNotNull();
-        assertThat(displayInfoLayout2Other.displayId).isEqualTo(logicalId2);
-        assertThat(displayInfoLayout2Other.logicalWidth).isEqualTo(width(device2));
-        assertThat(displayInfoLayout2Other.logicalHeight).isEqualTo(height(device2));
-    }
-
-    @Test
-    public void testGetDisplayInfoForStateLocked_multipleDisplayGroups() {
-        DisplayDevice device1 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
-                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
-        DisplayDevice device2 = createDisplayDevice(TYPE_INTERNAL, 200, 800,
-                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
-        DisplayDevice device3 = createDisplayDevice(TYPE_INTERNAL, 700, 800,
-                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
-        DisplayDevice device4 = createDisplayDevice(TYPE_INTERNAL, 400, 600,
-                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
-
-        Layout layout = new Layout();
-        createDefaultDisplay(layout, device1);
-        createNonDefaultDisplay(layout, device2, /* enabled= */  true, /* group= */ "group1");
-        createNonDefaultDisplay(layout, device3, /* enabled= */  true, /* group= */ "group1");
-        createNonDefaultDisplay(layout, device4, /* enabled= */  true, /* group= */ "group2");
-
-        when(mDeviceStateToLayoutMapSpy.get(STATE_DEFAULT)).thenReturn(layout);
-
-        LogicalDisplay display1 = add(device1);
-        LogicalDisplay display2 = add(device2);
-        LogicalDisplay display3 = add(device3);
-        LogicalDisplay display4 = add(device4);
-
-        int displayGroupId1 =
-                mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display1));
-        int displayGroupId2 =
-                mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display2));
-        int displayGroupId3 =
-                mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display3));
-        int displayGroupId4 =
-                mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display4));
-        assertThat(displayGroupId1).isEqualTo(DEFAULT_DISPLAY_GROUP);
-        assertThat(displayGroupId2).isNotEqualTo(DEFAULT_DISPLAY_GROUP);
-        assertThat(displayGroupId2).isEqualTo(displayGroupId3);
-        assertThat(displayGroupId3).isNotEqualTo(DEFAULT_DISPLAY_GROUP);
-        assertThat(displayGroupId2).isNotEqualTo(displayGroupId4);
-    }
-
-    @Test
-    public void testSingleDisplayGroup() {
-        LogicalDisplay display1 = add(createDisplayDevice(TYPE_INTERNAL, 600, 800,
-                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY));
-        LogicalDisplay display2 = add(createDisplayDevice(TYPE_INTERNAL, 600, 800, 0));
-        LogicalDisplay display3 = add(createDisplayDevice(TYPE_VIRTUAL, 600, 800, 0));
-
-        assertEquals(DEFAULT_DISPLAY_GROUP,
-                mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display1)));
-        assertEquals(DEFAULT_DISPLAY_GROUP,
-                mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display2)));
-        assertEquals(DEFAULT_DISPLAY_GROUP,
-                mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display3)));
-    }
-
-    @Test
-    public void testMultipleDisplayGroups() {
-        LogicalDisplay display1 = add(createDisplayDevice(TYPE_INTERNAL, 600, 800,
-                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY));
-        LogicalDisplay display2 = add(createDisplayDevice(TYPE_INTERNAL, 600, 800, 0));
-
-
-        TestDisplayDevice device3 = createDisplayDevice(TYPE_VIRTUAL, 600, 800,
-                DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP);
-        LogicalDisplay display3 = add(device3);
-
-        assertEquals(DEFAULT_DISPLAY_GROUP,
-                mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display1)));
-        assertEquals(DEFAULT_DISPLAY_GROUP,
-                mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display2)));
-        assertNotEquals(DEFAULT_DISPLAY_GROUP,
-                mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display3)));
-
-        // Now switch it back to the default group by removing the flag and issuing an update
-        DisplayDeviceInfo info = device3.getSourceInfo();
-        info.flags = info.flags & ~DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP;
-        mDisplayDeviceRepo.onDisplayDeviceEvent(device3, DISPLAY_DEVICE_EVENT_CHANGED);
-
-        // Verify the new group is correct.
-        assertEquals(DEFAULT_DISPLAY_GROUP,
-                mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display3)));
-    }
-
-    @Test
-    public void testDevicesAreAddedToDeviceDisplayGroups() {
-        // Create the default internal display of the device.
-        LogicalDisplay defaultDisplay =
-                add(
-                        createDisplayDevice(
-                                Display.TYPE_INTERNAL,
-                                600,
-                                800,
-                                DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY));
-
-        // Create 3 virtual displays associated with a first virtual device.
-        int deviceId1 = 1;
-        TestDisplayDevice display1 =
-                createDisplayDevice(Display.TYPE_VIRTUAL, "virtualDevice1Display1", 600, 800, 0);
-        mLogicalDisplayMapper.associateDisplayDeviceWithVirtualDevice(display1, deviceId1);
-        LogicalDisplay virtualDevice1Display1 = add(display1);
-
-        TestDisplayDevice display2 =
-                createDisplayDevice(Display.TYPE_VIRTUAL, "virtualDevice1Display2", 600, 800, 0);
-        mLogicalDisplayMapper.associateDisplayDeviceWithVirtualDevice(display2, deviceId1);
-        LogicalDisplay virtualDevice1Display2 = add(display2);
-
-        TestDisplayDevice display3 =
-                createDisplayDevice(Display.TYPE_VIRTUAL, "virtualDevice1Display3", 600, 800, 0);
-        mLogicalDisplayMapper.associateDisplayDeviceWithVirtualDevice(display3, deviceId1);
-        LogicalDisplay virtualDevice1Display3 = add(display3);
-
-        // Create another 3 virtual displays associated with a second virtual device.
-        int deviceId2 = 2;
-        TestDisplayDevice display4 =
-                createDisplayDevice(Display.TYPE_VIRTUAL, "virtualDevice2Display1", 600, 800, 0);
-        mLogicalDisplayMapper.associateDisplayDeviceWithVirtualDevice(display4, deviceId2);
-        LogicalDisplay virtualDevice2Display1 = add(display4);
-
-        TestDisplayDevice display5 =
-                createDisplayDevice(Display.TYPE_VIRTUAL, "virtualDevice2Display2", 600, 800, 0);
-        mLogicalDisplayMapper.associateDisplayDeviceWithVirtualDevice(display5, deviceId2);
-        LogicalDisplay virtualDevice2Display2 = add(display5);
-
-        // The final display is created with FLAG_OWN_DISPLAY_GROUP set.
-        TestDisplayDevice display6 =
-                createDisplayDevice(
-                        Display.TYPE_VIRTUAL,
-                        "virtualDevice2Display3",
-                        600,
-                        800,
-                        DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP);
-        mLogicalDisplayMapper.associateDisplayDeviceWithVirtualDevice(display6, deviceId2);
-        LogicalDisplay virtualDevice2Display3 = add(display6);
-
-        // Verify that the internal display is in the default display group.
-        assertEquals(
-                DEFAULT_DISPLAY_GROUP,
-                mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(defaultDisplay)));
-
-        // Verify that all the displays for virtual device 1 are in the same (non-default) display
-        // group.
-        int virtualDevice1DisplayGroupId =
-                mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(
-                        id(virtualDevice1Display1));
-        assertNotEquals(DEFAULT_DISPLAY_GROUP, virtualDevice1DisplayGroupId);
-        assertEquals(
-                virtualDevice1DisplayGroupId,
-                mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(
-                        id(virtualDevice1Display2)));
-        assertEquals(
-                virtualDevice1DisplayGroupId,
-                mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(
-                        id(virtualDevice1Display3)));
-
-        // The first 2 displays for virtual device 2 should be in the same non-default group.
-        int virtualDevice2DisplayGroupId =
-                mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(
-                        id(virtualDevice2Display1));
-        assertNotEquals(DEFAULT_DISPLAY_GROUP, virtualDevice2DisplayGroupId);
-        assertEquals(
-                virtualDevice2DisplayGroupId,
-                mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(
-                        id(virtualDevice2Display2)));
-        // virtualDevice2Display3 was created with FLAG_OWN_DISPLAY_GROUP and shouldn't be grouped
-        // with other displays of this device or be in the default display group.
-        assertNotEquals(
-                virtualDevice2DisplayGroupId,
-                mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(
-                        id(virtualDevice2Display3)));
-        assertNotEquals(
-                DEFAULT_DISPLAY_GROUP,
-                mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(
-                        id(virtualDevice2Display3)));
-    }
-
-    @Test
-    public void testDeviceShouldBeWoken() {
-        assertTrue(mLogicalDisplayMapper.shouldDeviceBeWoken(DEVICE_STATE_OPEN,
-                DEVICE_STATE_CLOSED,
-                /* isInteractive= */false,
-                /* isBootCompleted= */true));
-    }
-
-    @Test
-    public void testDeviceShouldNotBeWoken() {
-        assertFalse(mLogicalDisplayMapper.shouldDeviceBeWoken(DEVICE_STATE_CLOSED,
-                DEVICE_STATE_OPEN,
-                /* isInteractive= */false,
-                /* isBootCompleted= */true));
-    }
-
-    @Test
-    public void testDeviceShouldBePutToSleep() {
-        assertTrue(mLogicalDisplayMapper.shouldDeviceBePutToSleep(DEVICE_STATE_CLOSED,
-                DEVICE_STATE_OPEN,
-                /* isOverrideActive= */false,
-                /* isInteractive= */true,
-                /* isBootCompleted= */true));
-    }
-
-    @Test
-    public void testDeviceShouldNotBePutToSleep() {
-        assertFalse(mLogicalDisplayMapper.shouldDeviceBePutToSleep(DEVICE_STATE_OPEN,
-                DEVICE_STATE_CLOSED,
-                /* isOverrideActive= */false,
-                /* isInteractive= */true,
-                /* isBootCompleted= */true));
-        assertFalse(mLogicalDisplayMapper.shouldDeviceBePutToSleep(DEVICE_STATE_CLOSED,
-                INVALID_DEVICE_STATE,
-                /* isOverrideActive= */false,
-                /* isInteractive= */true,
-                /* isBootCompleted= */true));
-    }
-
-    @Test
-    public void testDeviceShouldNotBePutToSleepDifferentBaseState() {
-        assertFalse(mLogicalDisplayMapper.shouldDeviceBePutToSleep(DEVICE_STATE_CLOSED,
-                DEVICE_STATE_OPEN,
-                /* isOverrideActive= */true,
-                /* isInteractive= */true,
-                /* isBootCompleted= */true));
-    }
-
-    @Test
-    public void testDeviceStateLocked() {
-        DisplayDevice device1 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
-                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
-        DisplayDevice device2 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
-                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
-
-        Layout layout = new Layout();
-        layout.createDisplayLocked(device1.getDisplayDeviceInfoLocked().address,
-                /* isDefault= */ true, /* isEnabled= */ true, /* displayGroup= */ null,
-                mIdProducer, POSITION_UNKNOWN,
-                /* leadDisplayId= */ Display.DEFAULT_DISPLAY,
-                /* brightnessThrottlingMapId= */ "concurrent",
-                /* refreshRateZoneId= */ null, /* refreshRateThermalThrottlingMapId= */ null);
-        layout.createDisplayLocked(device2.getDisplayDeviceInfoLocked().address,
-                /* isDefault= */ false, /* isEnabled= */ true, /* displayGroup= */ null,
-                mIdProducer, POSITION_UNKNOWN,
-                /* leadDisplayId= */ Display.DEFAULT_DISPLAY,
-                /* brightnessThrottlingMapId= */ "concurrent",
-                /* refreshRateZoneId= */ null, /* refreshRateThermalThrottlingMapId= */ null);
-        when(mDeviceStateToLayoutMapSpy.get(0)).thenReturn(layout);
-
-        layout = new Layout();
-        createNonDefaultDisplay(layout, device1, /* enabled= */ false, /* group= */ null);
-        createDefaultDisplay(layout, device2);
-        when(mDeviceStateToLayoutMapSpy.get(1)).thenReturn(layout);
-        when(mDeviceStateToLayoutMapSpy.get(2)).thenReturn(layout);
-
-        when(mDeviceStateToLayoutMapSpy.size()).thenReturn(4);
-
-        LogicalDisplay display1 = add(device1);
-        assertEquals(info(display1).address, info(device1).address);
-        assertEquals(DEFAULT_DISPLAY, id(display1));
-
-        LogicalDisplay display2 = add(device2);
-        assertEquals(info(display2).address, info(device2).address);
-        // We can only have one default display
-        assertEquals(DEFAULT_DISPLAY, id(display1));
-
-        mLogicalDisplayMapper.setDeviceStateLocked(0, false);
-        advanceTime(1000);
-        // The new state is not applied until the boot is completed
-        assertTrue(mLogicalDisplayMapper.getDisplayLocked(device1).isEnabledLocked());
-        assertFalse(mLogicalDisplayMapper.getDisplayLocked(device2).isEnabledLocked());
-
-        mLogicalDisplayMapper.onBootCompleted();
-        advanceTime(1000);
-        assertTrue(mLogicalDisplayMapper.getDisplayLocked(device1).isEnabledLocked());
-        assertTrue(mLogicalDisplayMapper.getDisplayLocked(device2).isEnabledLocked());
-        assertFalse(mLogicalDisplayMapper.getDisplayLocked(device1).isInTransitionLocked());
-        assertFalse(mLogicalDisplayMapper.getDisplayLocked(device2).isInTransitionLocked());
-        assertEquals(-1, mLogicalDisplayMapper.getDisplayLocked(device1)
-                .getLeadDisplayIdLocked());
-        assertEquals(0, mLogicalDisplayMapper.getDisplayLocked(device2)
-                .getLeadDisplayIdLocked());
-        assertEquals("concurrent", mLogicalDisplayMapper.getDisplayLocked(device1)
-                .getDisplayInfoLocked().thermalBrightnessThrottlingDataId);
-        assertEquals("concurrent", mLogicalDisplayMapper.getDisplayLocked(device2)
-                .getDisplayInfoLocked().thermalBrightnessThrottlingDataId);
-
-        mLogicalDisplayMapper.setDeviceStateLocked(1, false);
-        advanceTime(1000);
-        assertFalse(mLogicalDisplayMapper.getDisplayLocked(device1).isEnabledLocked());
-        assertTrue(mLogicalDisplayMapper.getDisplayLocked(device2).isEnabledLocked());
-        assertFalse(mLogicalDisplayMapper.getDisplayLocked(device1).isInTransitionLocked());
-        assertFalse(mLogicalDisplayMapper.getDisplayLocked(device2).isInTransitionLocked());
-        assertEquals(DisplayDeviceConfig.DEFAULT_ID,
-                mLogicalDisplayMapper.getDisplayLocked(device1)
-                        .getDisplayInfoLocked().thermalBrightnessThrottlingDataId);
-        assertEquals(DisplayDeviceConfig.DEFAULT_ID,
-                mLogicalDisplayMapper.getDisplayLocked(device2)
-                        .getDisplayInfoLocked().thermalBrightnessThrottlingDataId);
-
-        mLogicalDisplayMapper.setDeviceStateLocked(2, false);
-        advanceTime(1000);
-        assertFalse(mLogicalDisplayMapper.getDisplayLocked(device1).isEnabledLocked());
-        assertTrue(mLogicalDisplayMapper.getDisplayLocked(device2).isEnabledLocked());
-        assertFalse(mLogicalDisplayMapper.getDisplayLocked(device1).isInTransitionLocked());
-        assertFalse(mLogicalDisplayMapper.getDisplayLocked(device2).isInTransitionLocked());
-        assertEquals(DisplayDeviceConfig.DEFAULT_ID,
-                mLogicalDisplayMapper.getDisplayLocked(device1)
-                        .getDisplayInfoLocked().thermalBrightnessThrottlingDataId);
-        assertEquals(DisplayDeviceConfig.DEFAULT_ID,
-                mLogicalDisplayMapper.getDisplayLocked(device2)
-                        .getDisplayInfoLocked().thermalBrightnessThrottlingDataId);
-    }
-
-    @Test
-    public void testEnabledAndDisabledDisplays() {
-        DisplayAddress displayAddressOne = new TestUtils.TestDisplayAddress();
-        DisplayAddress displayAddressTwo = new TestUtils.TestDisplayAddress();
-        DisplayAddress displayAddressThree = new TestUtils.TestDisplayAddress();
-
-        TestDisplayDevice device1 = createDisplayDevice(displayAddressOne, "one",
-                TYPE_INTERNAL, 600, 800, DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
-        TestDisplayDevice device2 = createDisplayDevice(displayAddressTwo, "two",
-                TYPE_INTERNAL, 200, 800, DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP);
-        TestDisplayDevice device3 = createDisplayDevice(displayAddressThree, "three",
-                TYPE_INTERNAL, 600, 900, DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP);
-
-        Layout threeDevicesEnabledLayout = new Layout();
-        createDefaultDisplay(threeDevicesEnabledLayout, displayAddressOne);
-        createNonDefaultDisplay(threeDevicesEnabledLayout, displayAddressTwo,
-                /* enabled= */ true, /* group= */ null);
-        createNonDefaultDisplay(threeDevicesEnabledLayout, displayAddressThree,
-                /* enabled= */ true, /* group= */ null);
-
-        when(mDeviceStateToLayoutMapSpy.get(STATE_DEFAULT))
-                .thenReturn(threeDevicesEnabledLayout);
-
-        LogicalDisplay display1 = add(device1);
-        LogicalDisplay display2 = add(device2);
-        LogicalDisplay display3 = add(device3);
-
-        // ensure 3 displays are returned
-        int [] ids = mLogicalDisplayMapper.getDisplayIdsLocked(Process.SYSTEM_UID, false);
-        assertEquals(3, ids.length);
-        Arrays.sort(ids);
-        assertEquals(DEFAULT_DISPLAY, ids[0]);
-        assertNotNull(mLogicalDisplayMapper.getDisplayLocked(device1,
-                /* includeDisabled= */ false));
-        assertNotNull(mLogicalDisplayMapper.getDisplayLocked(device2,
-                /* includeDisabled= */ false));
-        assertNotNull(mLogicalDisplayMapper.getDisplayLocked(device3,
-                /* includeDisabled= */ false));
-        assertNotNull(mLogicalDisplayMapper.getDisplayLocked(
-                threeDevicesEnabledLayout.getByAddress(displayAddressOne).getLogicalDisplayId(),
-                /* includeDisabled= */ false));
-        assertNotNull(mLogicalDisplayMapper.getDisplayLocked(
-                threeDevicesEnabledLayout.getByAddress(displayAddressTwo).getLogicalDisplayId(),
-                /* includeDisabled= */ false));
-        assertNotNull(mLogicalDisplayMapper.getDisplayLocked(
-                threeDevicesEnabledLayout.getByAddress(displayAddressThree).getLogicalDisplayId(),
-                /* includeDisabled= */ false));
-
-        Layout oneDeviceEnabledLayout = new Layout();
-        createDefaultDisplay(oneDeviceEnabledLayout, displayAddressOne);
-        createNonDefaultDisplay(oneDeviceEnabledLayout, displayAddressTwo,
-                /* enabled= */ false, /* group= */ null);
-        createNonDefaultDisplay(oneDeviceEnabledLayout, displayAddressThree,
-                /* enabled= */ false, /* group= */ null);
-
-        when(mDeviceStateToLayoutMapSpy.get(0)).thenReturn(oneDeviceEnabledLayout);
-        when(mDeviceStateToLayoutMapSpy.get(1)).thenReturn(threeDevicesEnabledLayout);
-
-        // 1) Set the new state
-        // 2) Mark the displays as STATE_OFF so that it can continue with transition
-        // 3) Send DISPLAY_DEVICE_EVENT_CHANGE to inform the mapper of the new display state
-        // 4) Dispatch handler events.
-        mLogicalDisplayMapper.onBootCompleted();
-        mLogicalDisplayMapper.setDeviceStateLocked(0, false);
-        mDisplayDeviceRepo.onDisplayDeviceEvent(device3, DISPLAY_DEVICE_EVENT_CHANGED);
-        advanceTime(1000);
-        final int[] allDisplayIds = mLogicalDisplayMapper.getDisplayIdsLocked(
-                Process.SYSTEM_UID, false);
-        if (allDisplayIds.length != 1) {
-            throw new RuntimeException("Displays: \n"
-                    + mLogicalDisplayMapper.getDisplayLocked(device1).toString()
-                    + "\n" + mLogicalDisplayMapper.getDisplayLocked(device2).toString()
-                    + "\n" + mLogicalDisplayMapper.getDisplayLocked(device3).toString());
-        }
-        // ensure only one display is returned
-        assertEquals(1, allDisplayIds.length);
-        assertNotNull(mLogicalDisplayMapper.getDisplayLocked(device1,
-                /* includeDisabled= */ false));
-        assertNull(mLogicalDisplayMapper.getDisplayLocked(device2,
-                /* includeDisabled= */ false));
-        assertNull(mLogicalDisplayMapper.getDisplayLocked(device3,
-                /* includeDisabled= */ false));
-        assertNotNull(mLogicalDisplayMapper.getDisplayLocked(
-                oneDeviceEnabledLayout.getByAddress(displayAddressOne).getLogicalDisplayId(),
-                /* includeDisabled= */ false));
-        assertNull(mLogicalDisplayMapper.getDisplayLocked(
-                oneDeviceEnabledLayout.getByAddress(displayAddressTwo).getLogicalDisplayId(),
-                /* includeDisabled= */ false));
-        assertNull(mLogicalDisplayMapper.getDisplayLocked(
-                oneDeviceEnabledLayout.getByAddress(displayAddressThree).getLogicalDisplayId(),
-                /* includeDisabled= */ false));
-
-        // Now do it again to go back to state 1
-        mLogicalDisplayMapper.setDeviceStateLocked(1, false);
-        mDisplayDeviceRepo.onDisplayDeviceEvent(device3, DISPLAY_DEVICE_EVENT_CHANGED);
-        advanceTime(1000);
-        final int[] threeDisplaysEnabled = mLogicalDisplayMapper.getDisplayIdsLocked(
-                Process.SYSTEM_UID, false);
-
-        // ensure all three displays are returned
-        assertEquals(3, threeDisplaysEnabled.length);
-    }
-
-    @Test
-    public void testCreateNewLogicalDisplay() {
-        DisplayDevice device1 = createDisplayDevice(TYPE_EXTERNAL, 600, 800,
-                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
-        when(mDeviceStateToLayoutMapSpy.size()).thenReturn(1);
-        LogicalDisplay display1 = add(device1);
-
-        assertTrue(display1.isEnabledLocked());
-
-        DisplayDevice device2 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
-                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
-        when(mDeviceStateToLayoutMapSpy.size()).thenReturn(2);
-        LogicalDisplay display2 = add(device2);
-
-        assertFalse(display2.isEnabledLocked());
-    }
-    @Test
-    public void testDisplayFlagRear() {
-        DisplayDevice device1 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
-                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
-        DisplayDevice device2 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
-                FLAG_REAR);
-
-        Layout layout = new Layout();
-        layout.createDefaultDisplayLocked(device1.getDisplayDeviceInfoLocked().address,
-                mIdProducer);
-        layout.createDisplayLocked(device2.getDisplayDeviceInfoLocked().address,
-                /* isDefault= */ false, /* isEnabled= */ true, /* displayGroupName= */ null,
-                mIdProducer, POSITION_REAR, Display.DEFAULT_DISPLAY,
-                /* brightnessThrottlingMapId= */ null, /* refreshRateZoneId= */ null,
-                /* refreshRateThermalThrottlingMapId= */null);
-        when(mDeviceStateToLayoutMapSpy.get(0)).thenReturn(layout);
-
-        when(mDeviceStateToLayoutMapSpy.size()).thenReturn(1);
-
-        LogicalDisplay display1 = add(device1);
-        assertEquals(info(display1).address, info(device1).address);
-        assertEquals(DEFAULT_DISPLAY, id(display1));
-
-        LogicalDisplay display2 = add(device2);
-        assertEquals(info(display2).address, info(device2).address);
-        // We can only have one default display
-        assertEquals(DEFAULT_DISPLAY, id(display1));
-
-        mLogicalDisplayMapper.setDeviceStateLocked(0, false);
-        advanceTime(1000);
-        mLogicalDisplayMapper.onBootCompleted();
-        advanceTime(1000);
-
-        assertTrue(mLogicalDisplayMapper.getDisplayLocked(device1).isEnabledLocked());
-        assertTrue(mLogicalDisplayMapper.getDisplayLocked(device2).isEnabledLocked());
-
-        assertEquals(POSITION_UNKNOWN,
-                mLogicalDisplayMapper.getDisplayLocked(device1).getDevicePositionLocked());
-        assertEquals(POSITION_REAR,
-                mLogicalDisplayMapper.getDisplayLocked(device2).getDevicePositionLocked());
-    }
-
-    /////////////////
-    // Helper Methods
-    /////////////////
-
-    private void createDefaultDisplay(Layout layout, DisplayDevice device) {
-        createDefaultDisplay(layout, info(device).address);
-    }
-
-    private void createDefaultDisplay(Layout layout, DisplayAddress address) {
-        layout.createDefaultDisplayLocked(address, mIdProducer);
-    }
-
-    private void createNonDefaultDisplay(Layout layout, DisplayDevice device, boolean enabled,
-            String group) {
-        createNonDefaultDisplay(layout, info(device).address, enabled, group);
-    }
-
-    private void createNonDefaultDisplay(Layout layout, DisplayAddress address, boolean enabled,
-            String group) {
-        layout.createDisplayLocked(address, /* isDefault= */ false, enabled, group, mIdProducer,
-                Layout.Display.POSITION_UNKNOWN, Display.DEFAULT_DISPLAY,
-                /* brightnessThrottlingMapId= */ null, /* refreshRateZoneId= */ null,
-                /* refreshRateThermalThrottlingMapId= */ null);
-    }
-
-    private void advanceTime(long timeMs) {
-        mLooper.moveTimeForward(1000);
-        mLooper.dispatchAll();
-    }
-
-    private TestDisplayDevice createDisplayDevice(int type, int width, int height, int flags) {
-        return createDisplayDevice(
-                new TestUtils.TestDisplayAddress(), /*  uniqueId */ "", type, width, height, flags);
-    }
-
-    private TestDisplayDevice createDisplayDevice(
-            int type, String uniqueId, int width, int height, int flags) {
-        return createDisplayDevice(
-                new TestUtils.TestDisplayAddress(), uniqueId, type, width, height, flags);
-    }
-
-    private TestDisplayDevice createDisplayDevice(
-            DisplayAddress address, String uniqueId, int type, int width, int height, int flags) {
-        TestDisplayDevice device = new TestDisplayDevice();
-        DisplayDeviceInfo displayDeviceInfo = device.getSourceInfo();
-        displayDeviceInfo.type = type;
-        displayDeviceInfo.uniqueId = uniqueId;
-        displayDeviceInfo.width = width;
-        displayDeviceInfo.height = height;
-        displayDeviceInfo.flags = flags;
-        displayDeviceInfo.supportedModes = new Display.Mode[1];
-        displayDeviceInfo.supportedModes[0] = new Display.Mode(1, width, height, 60f);
-        displayDeviceInfo.modeId = 1;
-        displayDeviceInfo.address = address;
-        return device;
-    }
-
-    private DisplayDeviceInfo info(DisplayDevice device) {
-        return device.getDisplayDeviceInfoLocked();
-    }
-
-    private int width(DisplayDevice device) {
-        return info(device).width;
-    }
-
-    private int height(DisplayDevice device) {
-        return info(device).height;
-    }
-
-    private DisplayInfo info(LogicalDisplay display) {
-        return display.getDisplayInfoLocked();
-    }
-
-    private int id(LogicalDisplay display) {
-        return display.getDisplayIdLocked();
-    }
-
-    private LogicalDisplay add(DisplayDevice device) {
-        mDisplayDeviceRepo.onDisplayDeviceEvent(device, DISPLAY_DEVICE_EVENT_ADDED);
-        ArgumentCaptor<LogicalDisplay> displayCaptor =
-                ArgumentCaptor.forClass(LogicalDisplay.class);
-        verify(mListenerMock).onLogicalDisplayEventLocked(
-                displayCaptor.capture(), eq(LOGICAL_DISPLAY_EVENT_ADDED));
-        clearInvocations(mListenerMock);
-        return displayCaptor.getValue();
-    }
-
-    private void testDisplayDeviceAddAndRemove_NonInternal(int type) {
-        DisplayDevice device = createDisplayDevice(type, 600, 800, 0);
-
-        // add
-        LogicalDisplay displayAdded = add(device);
-        assertEquals(info(displayAdded).address, info(device).address);
-        assertNotEquals(DEFAULT_DISPLAY, id(displayAdded));
-
-        // remove
-        mDisplayDeviceRepo.onDisplayDeviceEvent(device, DISPLAY_DEVICE_EVENT_REMOVED);
-        verify(mListenerMock).onLogicalDisplayEventLocked(
-                mDisplayCaptor.capture(), eq(LOGICAL_DISPLAY_EVENT_REMOVED));
-        LogicalDisplay displayRemoved = mDisplayCaptor.getValue();
-        assertNotEquals(DEFAULT_DISPLAY, id(displayRemoved));
-    }
-
-    class TestDisplayDevice extends DisplayDevice {
-        private DisplayDeviceInfo mInfo;
-        private DisplayDeviceInfo mSentInfo;
-        private int mState;
-
-        TestDisplayDevice() {
-            super(null, null, "test_display_" + sUniqueTestDisplayId++, mContextMock);
-            mInfo = new DisplayDeviceInfo();
-        }
-
-        @Override
-        public DisplayDeviceInfo getDisplayDeviceInfoLocked() {
-            if (mSentInfo == null) {
-                mSentInfo = new DisplayDeviceInfo();
-                mSentInfo.copyFrom(mInfo);
-            }
-            return mSentInfo;
-        }
-
-        @Override
-        public void applyPendingDisplayDeviceInfoChangesLocked() {
-            mSentInfo = null;
-        }
-
-        @Override
-        public boolean hasStableUniqueId() {
-            return true;
-        }
-
-        public DisplayDeviceInfo getSourceInfo() {
-            return mInfo;
-        }
-    }
-}
-
diff --git a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java
deleted file mode 100644
index 30ff8ba..0000000
--- a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java
+++ /dev/null
@@ -1,287 +0,0 @@
-/*
- * 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.
- */
-
-package com.android.server.display;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.app.PropertyInvalidatedCache;
-import android.graphics.Point;
-import android.util.SparseArray;
-import android.view.Display;
-import android.view.DisplayInfo;
-import android.view.Surface;
-import android.view.SurfaceControl;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.server.display.layout.Layout;
-
-import org.junit.Before;
-import org.junit.Test;
-
-import java.io.InputStream;
-import java.io.OutputStream;
-
-@SmallTest
-public class LogicalDisplayTest {
-    private static final int DISPLAY_ID = 0;
-    private static final int LAYER_STACK = 0;
-    private static final int DISPLAY_WIDTH = 100;
-    private static final int DISPLAY_HEIGHT = 200;
-    private static final int MODE_ID = 1;
-
-    private LogicalDisplay mLogicalDisplay;
-    private DisplayDevice mDisplayDevice;
-    private DisplayDeviceRepository mDeviceRepo;
-    private final DisplayDeviceInfo mDisplayDeviceInfo = new DisplayDeviceInfo();
-
-    @Before
-    public void setUp() {
-        // Share classloader to allow package private access.
-        System.setProperty("dexmaker.share_classloader", "true");
-        mDisplayDevice = mock(DisplayDevice.class);
-        mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice);
-
-        mDisplayDeviceInfo.copyFrom(new DisplayDeviceInfo());
-        mDisplayDeviceInfo.width = DISPLAY_WIDTH;
-        mDisplayDeviceInfo.height = DISPLAY_HEIGHT;
-        mDisplayDeviceInfo.flags = DisplayDeviceInfo.FLAG_ROTATES_WITH_CONTENT;
-        mDisplayDeviceInfo.touch = DisplayDeviceInfo.TOUCH_INTERNAL;
-        mDisplayDeviceInfo.modeId = MODE_ID;
-        mDisplayDeviceInfo.supportedModes = new Display.Mode[] {new Display.Mode(MODE_ID,
-                DISPLAY_WIDTH, DISPLAY_HEIGHT, /* refreshRate= */ 60)};
-        when(mDisplayDevice.getDisplayDeviceInfoLocked()).thenReturn(mDisplayDeviceInfo);
-
-        // Disable binder caches in this process.
-        PropertyInvalidatedCache.disableForTestMode();
-
-        mDeviceRepo = new DisplayDeviceRepository(
-                new DisplayManagerService.SyncRoot(),
-                new PersistentDataStore(new PersistentDataStore.Injector() {
-                    @Override
-                    public InputStream openRead() {
-                        return null;
-                    }
-
-                    @Override
-                    public OutputStream startWrite() {
-                        return null;
-                    }
-
-                    @Override
-                    public void finishWrite(OutputStream os, boolean success) {}
-                }));
-        mDeviceRepo.onDisplayDeviceEvent(mDisplayDevice, DisplayAdapter.DISPLAY_DEVICE_EVENT_ADDED);
-        mLogicalDisplay.updateLocked(mDeviceRepo);
-    }
-
-    @Test
-    public void testGetDisplayPosition() {
-        Point expectedPosition = new Point();
-
-        SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
-        mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
-        assertEquals(expectedPosition, mLogicalDisplay.getDisplayPosition());
-
-        expectedPosition.set(20, 40);
-        mLogicalDisplay.setDisplayOffsetsLocked(20, 40);
-        mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
-        assertEquals(expectedPosition, mLogicalDisplay.getDisplayPosition());
-
-        expectedPosition.set(40, -20);
-        DisplayInfo displayInfo = new DisplayInfo();
-        displayInfo.logicalWidth = DISPLAY_HEIGHT;
-        displayInfo.logicalHeight = DISPLAY_WIDTH;
-        displayInfo.rotation = Surface.ROTATION_90;
-        mLogicalDisplay.setDisplayInfoOverrideFromWindowManagerLocked(displayInfo);
-        mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
-        assertEquals(expectedPosition, mLogicalDisplay.getDisplayPosition());
-    }
-
-    @Test
-    public void testDisplayInputFlags() {
-        SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
-        mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
-        verify(t).setDisplayFlags(any(), eq(SurfaceControl.DISPLAY_RECEIVES_INPUT));
-        reset(t);
-
-        mDisplayDeviceInfo.touch = DisplayDeviceInfo.TOUCH_NONE;
-        mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
-        verify(t).setDisplayFlags(any(), eq(0));
-        reset(t);
-
-        mDisplayDeviceInfo.touch = DisplayDeviceInfo.TOUCH_VIRTUAL;
-        mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
-        verify(t).setDisplayFlags(any(), eq(SurfaceControl.DISPLAY_RECEIVES_INPUT));
-        reset(t);
-
-        mLogicalDisplay.setEnabledLocked(false);
-        mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
-        verify(t).setDisplayFlags(any(), eq(0));
-        reset(t);
-
-        mLogicalDisplay.setEnabledLocked(true);
-        mDisplayDeviceInfo.touch = DisplayDeviceInfo.TOUCH_EXTERNAL;
-        mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
-        verify(t).setDisplayFlags(any(), eq(SurfaceControl.DISPLAY_RECEIVES_INPUT));
-        reset(t);
-    }
-
-    @Test
-    public void testRearDisplaysArePresentationDisplaysThatDestroyContentOnRemoval() {
-        // Assert that the display isn't a presentation display by default, with a default remove
-        // mode
-        assertEquals(0, mLogicalDisplay.getDisplayInfoLocked().flags);
-        assertEquals(Display.REMOVE_MODE_MOVE_CONTENT_TO_PRIMARY,
-                mLogicalDisplay.getDisplayInfoLocked().removeMode);
-
-        // Update position and test to see that it's been updated to a rear, presentation display
-        // that destroys content on removal
-        mLogicalDisplay.setDevicePositionLocked(Layout.Display.POSITION_REAR);
-        mLogicalDisplay.updateLocked(mDeviceRepo);
-        assertEquals(Display.FLAG_REAR | Display.FLAG_PRESENTATION,
-                mLogicalDisplay.getDisplayInfoLocked().flags);
-        assertEquals(Display.REMOVE_MODE_DESTROY_CONTENT,
-                mLogicalDisplay.getDisplayInfoLocked().removeMode);
-
-        // And then check the unsetting the position resets both
-        mLogicalDisplay.setDevicePositionLocked(Layout.Display.POSITION_UNKNOWN);
-        mLogicalDisplay.updateLocked(mDeviceRepo);
-        assertEquals(0, mLogicalDisplay.getDisplayInfoLocked().flags);
-        assertEquals(Display.REMOVE_MODE_MOVE_CONTENT_TO_PRIMARY,
-                mLogicalDisplay.getDisplayInfoLocked().removeMode);
-    }
-
-    @Test
-    public void testUpdateLayoutLimitedRefreshRate() {
-        SurfaceControl.RefreshRateRange layoutLimitedRefreshRate =
-                new SurfaceControl.RefreshRateRange(0, 120);
-        DisplayInfo info1 = mLogicalDisplay.getDisplayInfoLocked();
-        mLogicalDisplay.updateLayoutLimitedRefreshRateLocked(layoutLimitedRefreshRate);
-        DisplayInfo info2 = mLogicalDisplay.getDisplayInfoLocked();
-        // Display info should only be updated when updateLocked is called
-        assertEquals(info2, info1);
-
-        mLogicalDisplay.updateLocked(mDeviceRepo);
-        DisplayInfo info3 = mLogicalDisplay.getDisplayInfoLocked();
-        assertNotEquals(info3, info2);
-        assertEquals(layoutLimitedRefreshRate, info3.layoutLimitedRefreshRate);
-    }
-
-    @Test
-    public void testUpdateLayoutLimitedRefreshRate_setsDirtyFlag() {
-        SurfaceControl.RefreshRateRange layoutLimitedRefreshRate =
-                new SurfaceControl.RefreshRateRange(0, 120);
-        assertFalse(mLogicalDisplay.isDirtyLocked());
-
-        mLogicalDisplay.updateLayoutLimitedRefreshRateLocked(layoutLimitedRefreshRate);
-        assertTrue(mLogicalDisplay.isDirtyLocked());
-
-        mLogicalDisplay.updateLocked(mDeviceRepo);
-        assertFalse(mLogicalDisplay.isDirtyLocked());
-    }
-
-    @Test
-    public void testUpdateRefreshRateThermalThrottling() {
-        SparseArray<SurfaceControl.RefreshRateRange> refreshRanges = new SparseArray<>();
-        refreshRanges.put(0, new SurfaceControl.RefreshRateRange(0, 120));
-        DisplayInfo info1 = mLogicalDisplay.getDisplayInfoLocked();
-        mLogicalDisplay.updateThermalRefreshRateThrottling(refreshRanges);
-        DisplayInfo info2 = mLogicalDisplay.getDisplayInfoLocked();
-        // Display info should only be updated when updateLocked is called
-        assertEquals(info2, info1);
-
-        mLogicalDisplay.updateLocked(mDeviceRepo);
-        DisplayInfo info3 = mLogicalDisplay.getDisplayInfoLocked();
-        assertNotEquals(info3, info2);
-        assertTrue(refreshRanges.contentEquals(info3.thermalRefreshRateThrottling));
-    }
-
-    @Test
-    public void testUpdateRefreshRateThermalThrottling_setsDirtyFlag() {
-        SparseArray<SurfaceControl.RefreshRateRange> refreshRanges = new SparseArray<>();
-        refreshRanges.put(0, new SurfaceControl.RefreshRateRange(0, 120));
-        assertFalse(mLogicalDisplay.isDirtyLocked());
-
-        mLogicalDisplay.updateThermalRefreshRateThrottling(refreshRanges);
-        assertTrue(mLogicalDisplay.isDirtyLocked());
-
-        mLogicalDisplay.updateLocked(mDeviceRepo);
-        assertFalse(mLogicalDisplay.isDirtyLocked());
-    }
-
-    @Test
-    public void testUpdateDisplayGroupIdLocked() {
-        int newId = 999;
-        DisplayInfo info1 = mLogicalDisplay.getDisplayInfoLocked();
-        mLogicalDisplay.updateDisplayGroupIdLocked(newId);
-        DisplayInfo info2 = mLogicalDisplay.getDisplayInfoLocked();
-        // Display info should only be updated when updateLocked is called
-        assertEquals(info2, info1);
-
-        mLogicalDisplay.updateLocked(mDeviceRepo);
-        DisplayInfo info3 = mLogicalDisplay.getDisplayInfoLocked();
-        assertNotEquals(info3, info2);
-        assertEquals(newId, info3.displayGroupId);
-    }
-
-    @Test
-    public void testUpdateDisplayGroupIdLocked_setsDirtyFlag() {
-        assertFalse(mLogicalDisplay.isDirtyLocked());
-
-        mLogicalDisplay.updateDisplayGroupIdLocked(99);
-        assertTrue(mLogicalDisplay.isDirtyLocked());
-
-        mLogicalDisplay.updateLocked(mDeviceRepo);
-        assertFalse(mLogicalDisplay.isDirtyLocked());
-    }
-
-    @Test
-    public void testSetThermalBrightnessThrottlingDataId() {
-        String brightnessThrottlingDataId = "throttling_data_id";
-        DisplayInfo info1 = mLogicalDisplay.getDisplayInfoLocked();
-        mLogicalDisplay.setThermalBrightnessThrottlingDataIdLocked(brightnessThrottlingDataId);
-        DisplayInfo info2 = mLogicalDisplay.getDisplayInfoLocked();
-        // Display info should only be updated when updateLocked is called
-        assertEquals(info2, info1);
-
-        mLogicalDisplay.updateLocked(mDeviceRepo);
-        DisplayInfo info3 = mLogicalDisplay.getDisplayInfoLocked();
-        assertNotEquals(info3, info2);
-        assertEquals(brightnessThrottlingDataId, info3.thermalBrightnessThrottlingDataId);
-    }
-
-    @Test
-    public void testSetThermalBrightnessThrottlingDataId_setsDirtyFlag() {
-        assertFalse(mLogicalDisplay.isDirtyLocked());
-
-        mLogicalDisplay.setThermalBrightnessThrottlingDataIdLocked("99");
-        assertTrue(mLogicalDisplay.isDirtyLocked());
-
-        mLogicalDisplay.updateLocked(mDeviceRepo);
-        assertFalse(mLogicalDisplay.isDirtyLocked());
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java b/services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java
deleted file mode 100644
index 817b245..0000000
--- a/services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java
+++ /dev/null
@@ -1,462 +0,0 @@
-/*
- * Copyright 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.display;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-
-import android.content.Context;
-import android.hardware.display.BrightnessConfiguration;
-import android.os.Handler;
-import android.os.test.TestLooper;
-import android.util.Pair;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.nio.charset.StandardCharsets;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class PersistentDataStoreTest {
-    private PersistentDataStore mDataStore;
-    private TestInjector mInjector;
-    private TestLooper mTestLooper;
-
-    @Before
-    public void setUp() {
-        mInjector = new TestInjector();
-        mTestLooper = new TestLooper();
-        Handler handler = new Handler(mTestLooper.getLooper());
-        mDataStore = new PersistentDataStore(mInjector, handler);
-    }
-
-    @Test
-    public void testLoadingBrightnessConfigurations() {
-        String contents = "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
-                + "<display-manager-state>\n"
-                + "  <brightness-configurations>\n"
-                + "    <brightness-configuration"
-                + "         user-serial=\"1\""
-                + "         package-name=\"example.com\""
-                + "         timestamp=\"123456\">\n"
-                + "      <brightness-curve description=\"something\">\n"
-                + "        <brightness-point lux=\"0\" nits=\"13.25\"/>\n"
-                + "        <brightness-point lux=\"25\" nits=\"35.94\"/>\n"
-                + "      </brightness-curve>\n"
-                + "    </brightness-configuration>\n"
-                + "    <brightness-configuration user-serial=\"3\">\n"
-                + "      <brightness-curve>\n"
-                + "        <brightness-point lux=\"0\" nits=\"13.25\"/>\n"
-                + "        <brightness-point lux=\"10.2\" nits=\"15\"/>\n"
-                + "      </brightness-curve>\n"
-                + "    </brightness-configuration>\n"
-                + "  </brightness-configurations>\n"
-                + "</display-manager-state>\n";
-        InputStream is = new ByteArrayInputStream(contents.getBytes(StandardCharsets.UTF_8));
-        mInjector.setReadStream(is);
-        mDataStore.loadIfNeeded();
-        BrightnessConfiguration config = mDataStore.getBrightnessConfiguration(1 /*userSerial*/);
-        Pair<float[], float[]> curve = config.getCurve();
-        float[] expectedLux = { 0f, 25f };
-        float[] expectedNits = { 13.25f, 35.94f };
-        assertArrayEquals(expectedLux, curve.first, "lux");
-        assertArrayEquals(expectedNits, curve.second, "nits");
-        assertEquals("something", config.getDescription());
-
-        config = mDataStore.getBrightnessConfiguration(3 /*userSerial*/);
-        curve = config.getCurve();
-        expectedLux = new float[] { 0f, 10.2f };
-        expectedNits = new float[] { 13.25f, 15f };
-        assertArrayEquals(expectedLux, curve.first, "lux");
-        assertArrayEquals(expectedNits, curve.second, "nits");
-        assertNull(config.getDescription());
-    }
-
-    @Test
-    public void testBrightnessConfigWithInvalidCurveIsIgnored() {
-        String contents = "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
-                + "<display-manager-state>\n"
-                + "  <brightness-configurations>\n"
-                + "    <brightness-configuration user-serial=\"0\">\n"
-                + "      <brightness-curve>\n"
-                + "        <brightness-point lux=\"1\" nits=\"13.25\"/>\n"
-                + "        <brightness-point lux=\"25\" nits=\"35.94\"/>\n"
-                + "      </brightness-curve>\n"
-                + "    </brightness-configuration>\n"
-                + "  </brightness-configurations>\n"
-                + "</display-manager-state>\n";
-        InputStream is = new ByteArrayInputStream(contents.getBytes(StandardCharsets.UTF_8));
-        mInjector.setReadStream(is);
-        mDataStore.loadIfNeeded();
-        assertNull(mDataStore.getBrightnessConfiguration(0 /*userSerial*/));
-    }
-
-    @Test
-    public void testBrightnessConfigWithInvalidFloatsIsIgnored() {
-        String contents = "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
-                + "<display-manager-state>\n"
-                + "  <brightness-configurations>\n"
-                + "    <brightness-configuration user-serial=\"0\">\n"
-                + "      <brightness-curve>\n"
-                + "        <brightness-point lux=\"0\" nits=\"13.25\"/>\n"
-                + "        <brightness-point lux=\"0xFF\" nits=\"foo\"/>\n"
-                + "      </brightness-curve>\n"
-                + "    </brightness-configuration>\n"
-                + "  </brightness-configurations>\n"
-                + "</display-manager-state>\n";
-        InputStream is = new ByteArrayInputStream(contents.getBytes(StandardCharsets.UTF_8));
-        mInjector.setReadStream(is);
-        mDataStore.loadIfNeeded();
-        assertNull(mDataStore.getBrightnessConfiguration(0 /*userSerial*/));
-    }
-
-    @Test
-    public void testEmptyBrightnessConfigurationsDoesntCrash() {
-        String contents = "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
-                + "<display-manager-state>\n"
-                + "  <brightness-configurations />\n"
-                + "</display-manager-state>\n";
-        InputStream is = new ByteArrayInputStream(contents.getBytes(StandardCharsets.UTF_8));
-        mInjector.setReadStream(is);
-        mDataStore.loadIfNeeded();
-        assertNull(mDataStore.getBrightnessConfiguration(0 /*userSerial*/));
-    }
-
-    @Test
-    public void testStoreAndReloadOfDisplayBrightnessConfigurations() throws InterruptedException {
-        final String uniqueDisplayId = "test:123";
-        int userSerial = 0;
-        String packageName = "pdsTestPackage";
-        final float[] lux = { 0f, 10f };
-        final float[] nits = {1f, 100f };
-        final BrightnessConfiguration config = new BrightnessConfiguration.Builder(lux, nits)
-                .setDescription("a description")
-                .build();
-        mDataStore.loadIfNeeded();
-        assertNull(mDataStore.getBrightnessConfigurationForDisplayLocked(uniqueDisplayId,
-                userSerial));
-
-        DisplayDevice testDisplayDevice = new DisplayDevice(null, null, uniqueDisplayId, null) {
-            @Override
-            public boolean hasStableUniqueId() {
-                return true;
-            }
-
-            @Override
-            public DisplayDeviceInfo getDisplayDeviceInfoLocked() {
-                return null;
-            }
-        };
-
-        mDataStore.setBrightnessConfigurationForDisplayLocked(config, testDisplayDevice, userSerial,
-                packageName);
-
-        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
-        mInjector.setWriteStream(baos);
-        mDataStore.saveIfNeeded();
-        mTestLooper.dispatchAll();
-        assertTrue(mInjector.wasWriteSuccessful());
-        TestInjector newInjector = new TestInjector();
-        PersistentDataStore newDataStore = new PersistentDataStore(newInjector);
-        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
-        newInjector.setReadStream(bais);
-        newDataStore.loadIfNeeded();
-        assertNotNull(newDataStore.getBrightnessConfigurationForDisplayLocked(uniqueDisplayId,
-                userSerial));
-        assertEquals(mDataStore.getBrightnessConfigurationForDisplayLocked(uniqueDisplayId,
-                userSerial), newDataStore.getBrightnessConfigurationForDisplayLocked(
-                        uniqueDisplayId, userSerial));
-    }
-
-    @Test
-    public void testSetBrightnessConfigurationFailsWithUnstableId() {
-        final String uniqueDisplayId = "test:123";
-        int userSerial = 0;
-        String packageName = "pdsTestPackage";
-        final float[] lux = { 0f, 10f };
-        final float[] nits = {1f, 100f };
-        final BrightnessConfiguration config = new BrightnessConfiguration.Builder(lux, nits)
-                .setDescription("a description")
-                .build();
-        mDataStore.loadIfNeeded();
-        assertNull(mDataStore.getBrightnessConfigurationForDisplayLocked(uniqueDisplayId,
-                userSerial));
-
-        DisplayDevice testDisplayDevice = new DisplayDevice(null, null, uniqueDisplayId, null) {
-            @Override
-            public boolean hasStableUniqueId() {
-                return false;
-            }
-
-            @Override
-            public DisplayDeviceInfo getDisplayDeviceInfoLocked() {
-                return null;
-            }
-        };
-
-        assertFalse(mDataStore.setBrightnessConfigurationForDisplayLocked(
-                config, testDisplayDevice, userSerial, packageName));
-    }
-
-    @Test
-    public void testStoreAndReloadOfBrightnessConfigurations() throws InterruptedException {
-        final float[] lux = { 0f, 10f };
-        final float[] nits = {1f, 100f };
-        final BrightnessConfiguration config = new BrightnessConfiguration.Builder(lux, nits)
-                .setDescription("a description")
-                .build();
-        Context context = InstrumentationRegistry.getInstrumentation().getContext();
-        String packageName = context.getPackageName();
-
-        mDataStore.loadIfNeeded();
-        assertNull(mDataStore.getBrightnessConfiguration(0 /*userSerial*/));
-        mDataStore.setBrightnessConfigurationForUser(config, 0, packageName);
-
-        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
-        mInjector.setWriteStream(baos);
-        mDataStore.saveIfNeeded();
-        mTestLooper.dispatchAll();
-        assertTrue(mInjector.wasWriteSuccessful());
-
-        TestInjector newInjector = new TestInjector();
-        PersistentDataStore newDataStore = new PersistentDataStore(newInjector);
-        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
-        newInjector.setReadStream(bais);
-        newDataStore.loadIfNeeded();
-        assertNotNull(newDataStore.getBrightnessConfiguration(0 /*userSerial*/));
-        assertEquals(mDataStore.getBrightnessConfiguration(0 /*userSerial*/),
-                newDataStore.getBrightnessConfiguration(0 /*userSerial*/));
-    }
-
-    @Test
-    public void testNullBrightnessConfiguration() {
-        final float[] lux = { 0f, 10f };
-        final float[] nits = {1f, 100f };
-        int userSerial = 0;
-        final BrightnessConfiguration config = new BrightnessConfiguration.Builder(lux, nits)
-                .setDescription("a description")
-                .build();
-        mDataStore.loadIfNeeded();
-        assertNull(mDataStore.getBrightnessConfiguration(userSerial));
-
-        mDataStore.setBrightnessConfigurationForUser(config, userSerial, "packagename");
-        assertNotNull(mDataStore.getBrightnessConfiguration(userSerial));
-
-        mDataStore.setBrightnessConfigurationForUser(null, userSerial, "packagename");
-        assertNull(mDataStore.getBrightnessConfiguration(userSerial));
-    }
-
-    @Test
-    public void testStoreAndRestoreResolution() {
-        final String uniqueDisplayId = "test:123";
-        DisplayDevice testDisplayDevice = new DisplayDevice(null, null, uniqueDisplayId, null) {
-            @Override
-            public boolean hasStableUniqueId() {
-                return true;
-            }
-
-            @Override
-            public DisplayDeviceInfo getDisplayDeviceInfoLocked() {
-                return null;
-            }
-        };
-        int width = 35;
-        int height = 45;
-        mDataStore.loadIfNeeded();
-        mDataStore.setUserPreferredResolution(testDisplayDevice, width, height);
-
-        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
-        mInjector.setWriteStream(baos);
-        mDataStore.saveIfNeeded();
-        mTestLooper.dispatchAll();
-        assertTrue(mInjector.wasWriteSuccessful());
-        TestInjector newInjector = new TestInjector();
-        PersistentDataStore newDataStore = new PersistentDataStore(newInjector);
-        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
-        newInjector.setReadStream(bais);
-        newDataStore.loadIfNeeded();
-        assertNotNull(newDataStore.getUserPreferredResolution(testDisplayDevice));
-        assertEquals(35, newDataStore.getUserPreferredResolution(testDisplayDevice).x);
-        assertEquals(35, mDataStore.getUserPreferredResolution(testDisplayDevice).x);
-        assertEquals(45, newDataStore.getUserPreferredResolution(testDisplayDevice).y);
-        assertEquals(45, mDataStore.getUserPreferredResolution(testDisplayDevice).y);
-    }
-
-    @Test
-    public void testStoreAndRestoreRefreshRate() {
-        final String uniqueDisplayId = "test:123";
-        DisplayDevice testDisplayDevice = new DisplayDevice(null, null, uniqueDisplayId, null) {
-            @Override
-            public boolean hasStableUniqueId() {
-                return true;
-            }
-
-            @Override
-            public DisplayDeviceInfo getDisplayDeviceInfoLocked() {
-                return null;
-            }
-        };
-        float refreshRate = 85.3f;
-        mDataStore.loadIfNeeded();
-        mDataStore.setUserPreferredRefreshRate(testDisplayDevice, refreshRate);
-
-        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
-        mInjector.setWriteStream(baos);
-        mDataStore.saveIfNeeded();
-        mTestLooper.dispatchAll();
-        assertTrue(mInjector.wasWriteSuccessful());
-        TestInjector newInjector = new TestInjector();
-        PersistentDataStore newDataStore = new PersistentDataStore(newInjector);
-        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
-        newInjector.setReadStream(bais);
-        newDataStore.loadIfNeeded();
-        assertNotNull(newDataStore.getUserPreferredRefreshRate(testDisplayDevice));
-        assertEquals(85.3f, mDataStore.getUserPreferredRefreshRate(testDisplayDevice), 01.f);
-        assertEquals(85.3f, newDataStore.getUserPreferredRefreshRate(testDisplayDevice), 0.1f);
-    }
-
-    @Test
-    public void testBrightnessInitialisesWithInvalidFloat() {
-        final String uniqueDisplayId = "test:123";
-        DisplayDevice testDisplayDevice = new DisplayDevice(null, null, uniqueDisplayId, null) {
-            @Override
-            public boolean hasStableUniqueId() {
-                return true;
-            }
-
-            @Override
-            public DisplayDeviceInfo getDisplayDeviceInfoLocked() {
-                return null;
-            }
-        };
-
-        // Set any value which initialises Display state
-        float refreshRate = 85.3f;
-        mDataStore.loadIfNeeded();
-        mDataStore.setUserPreferredRefreshRate(testDisplayDevice, refreshRate);
-
-        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
-        mInjector.setWriteStream(baos);
-        mDataStore.saveIfNeeded();
-        mTestLooper.dispatchAll();
-        assertTrue(mInjector.wasWriteSuccessful());
-        TestInjector newInjector = new TestInjector();
-        PersistentDataStore newDataStore = new PersistentDataStore(newInjector);
-        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
-        newInjector.setReadStream(bais);
-        newDataStore.loadIfNeeded();
-        assertTrue(Float.isNaN(mDataStore.getBrightness(testDisplayDevice)));
-    }
-
-    @Test
-    public void testStoreAndRestoreBrightnessNitsForDefaultDisplay() {
-        float brightnessNitsForDefaultDisplay = 190;
-        mDataStore.loadIfNeeded();
-        mDataStore.setBrightnessNitsForDefaultDisplay(brightnessNitsForDefaultDisplay);
-
-        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
-        mInjector.setWriteStream(baos);
-        mDataStore.saveIfNeeded();
-        mTestLooper.dispatchAll();
-        assertTrue(mInjector.wasWriteSuccessful());
-        TestInjector newInjector = new TestInjector();
-        PersistentDataStore newDataStore = new PersistentDataStore(newInjector);
-        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
-        newInjector.setReadStream(bais);
-        newDataStore.loadIfNeeded();
-        assertEquals(brightnessNitsForDefaultDisplay,
-                mDataStore.getBrightnessNitsForDefaultDisplay(), 0);
-        assertEquals(brightnessNitsForDefaultDisplay,
-                newDataStore.getBrightnessNitsForDefaultDisplay(), 0);
-    }
-
-    @Test
-    public void testInitialBrightnessNitsForDefaultDisplay() {
-        mDataStore.loadIfNeeded();
-        assertEquals(-1, mDataStore.getBrightnessNitsForDefaultDisplay(), 0);
-    }
-
-    public class TestInjector extends PersistentDataStore.Injector {
-        private InputStream mReadStream;
-        private OutputStream mWriteStream;
-
-        private boolean mWasSuccessful;
-
-        @Override
-        public InputStream openRead() throws FileNotFoundException {
-            if (mReadStream != null) {
-                return mReadStream;
-            } else {
-                throw new FileNotFoundException();
-            }
-        }
-
-        @Override
-        public OutputStream startWrite() {
-            return mWriteStream;
-        }
-
-        @Override
-        public void finishWrite(OutputStream os, boolean success) {
-            mWasSuccessful = success;
-            try {
-                os.close();
-            } catch (IOException e) {
-                // This method can't throw IOException since the super implementation doesn't, so
-                // we just wrap it in a RuntimeException so we end up crashing the test all the
-                // same.
-                throw new RuntimeException(e);
-            }
-        }
-
-        public void setReadStream(InputStream is) {
-            mReadStream = is;
-        }
-
-        public void setWriteStream(OutputStream os) {
-            mWriteStream = os;
-        }
-
-        public boolean wasWriteSuccessful() {
-            return mWasSuccessful;
-        }
-    }
-
-    private static void assertArrayEquals(float[] expected, float[] actual, String name) {
-        assertEquals("Expected " + name + " arrays to be the same length!",
-                expected.length, actual.length);
-        for (int i = 0; i < expected.length; i++) {
-            assertEquals("Expected " + name + " arrays to be equivalent when value " + i
-                    + "differs", expected[i], actual[i], 0.01 /*tolerance*/);
-        }
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/display/TEST_MAPPING b/services/tests/servicestests/src/com/android/server/display/TEST_MAPPING
deleted file mode 100644
index 92d8abd..0000000
--- a/services/tests/servicestests/src/com/android/server/display/TEST_MAPPING
+++ /dev/null
@@ -1,13 +0,0 @@
-{
-  "presubmit": [
-    {
-      "name": "FrameworksServicesTests",
-      "options": [
-        {"include-filter": "com.android.server.display"},
-        {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
-        {"exclude-annotation": "androidx.test.filters.FlakyTest"},
-        {"exclude-annotation": "org.junit.Ignore"}
-      ]
-    }
-  ]
-}
diff --git a/services/tests/servicestests/src/com/android/server/display/TestUtils.java b/services/tests/servicestests/src/com/android/server/display/TestUtils.java
index 90d9bae..8b45145 100644
--- a/services/tests/servicestests/src/com/android/server/display/TestUtils.java
+++ b/services/tests/servicestests/src/com/android/server/display/TestUtils.java
@@ -18,6 +18,7 @@
 
 import android.hardware.Sensor;
 import android.hardware.SensorEvent;
+import android.hardware.input.InputSensorInfo;
 import android.os.Parcel;
 import android.os.SystemClock;
 import android.view.DisplayAddress;
@@ -75,6 +76,12 @@
         return sensor;
     }
 
+    public static Sensor createSensor(String type, String name) {
+        return new Sensor(new InputSensorInfo(
+                name, "vendor", 0, 0, 0, 1f, 1f, 1, 1, 1, 1,
+                type, "", 0, 0, 0));
+    }
+
     /**
      * Create a custom {@link DisplayAddress} to ensure we're not relying on any specific
      * display-address implementation in our code. Intentionally uses default object (reference)
diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java b/services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
deleted file mode 100644
index e6d3bbc..0000000
--- a/services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
+++ /dev/null
@@ -1,361 +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.server.display.brightness;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.verifyZeroInteractions;
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
-import android.os.HandlerExecutor;
-import android.os.PowerManager;
-import android.view.Display;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-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.TemporaryBrightnessStrategy;
-
-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)
-public final class DisplayBrightnessControllerTest {
-    private static final int DISPLAY_ID = Display.DEFAULT_DISPLAY;
-    private static final float DEFAULT_BRIGHTNESS = 0.15f;
-
-    @Mock
-    private DisplayBrightnessStrategySelector mDisplayBrightnessStrategySelector;
-    @Mock
-    private Context mContext;
-    @Mock
-    private Resources mResources;
-    @Mock
-    private BrightnessSetting mBrightnessSetting;
-    @Mock
-    private Runnable mOnBrightnessChangeRunnable;
-
-    @Mock
-    private HandlerExecutor mBrightnessChangeExecutor;
-
-    private final DisplayBrightnessController.Injector mInjector = new
-            DisplayBrightnessController.Injector() {
-        @Override
-        DisplayBrightnessStrategySelector getDisplayBrightnessStrategySelector(
-                Context context, int displayId) {
-            return mDisplayBrightnessStrategySelector;
-        }
-    };
-
-    private DisplayBrightnessController mDisplayBrightnessController;
-
-    @Before
-    public void before() {
-        MockitoAnnotations.initMocks(this);
-        when(mContext.getResources()).thenReturn(mResources);
-        when(mBrightnessSetting.getBrightness()).thenReturn(Float.NaN);
-        when(mBrightnessSetting.getBrightnessNitsForDefaultDisplay()).thenReturn(-1f);
-        when(mResources.getBoolean(
-                com.android.internal.R.bool.config_persistBrightnessNitsForDefaultDisplay))
-                .thenReturn(true);
-        mDisplayBrightnessController = new DisplayBrightnessController(mContext, mInjector,
-                DISPLAY_ID, DEFAULT_BRIGHTNESS, mBrightnessSetting, mOnBrightnessChangeRunnable,
-                mBrightnessChangeExecutor);
-    }
-
-    @Test
-    public void testIfFirstScreenBrightnessIsDefault() {
-        assertEquals(DEFAULT_BRIGHTNESS, mDisplayBrightnessController.getCurrentBrightness(),
-                /* delta= */ 0.0f);
-    }
-
-    @Test
-    public void testUpdateBrightness() {
-        DisplayPowerRequest displayPowerRequest = mock(DisplayPowerRequest.class);
-        DisplayBrightnessStrategy displayBrightnessStrategy = mock(DisplayBrightnessStrategy.class);
-        int targetDisplayState = Display.STATE_DOZE;
-        when(mDisplayBrightnessStrategySelector.selectStrategy(displayPowerRequest,
-                targetDisplayState)).thenReturn(displayBrightnessStrategy);
-        mDisplayBrightnessController.updateBrightness(displayPowerRequest, targetDisplayState);
-        verify(displayBrightnessStrategy).updateBrightness(displayPowerRequest);
-        assertEquals(mDisplayBrightnessController.getCurrentDisplayBrightnessStrategy(),
-                displayBrightnessStrategy);
-    }
-
-    @Test
-    public void isAllowAutoBrightnessWhileDozingConfigDelegatesToDozeBrightnessStrategy() {
-        mDisplayBrightnessController.isAllowAutoBrightnessWhileDozingConfig();
-        verify(mDisplayBrightnessStrategySelector).isAllowAutoBrightnessWhileDozingConfig();
-    }
-
-    @Test
-    public void setTemporaryBrightness() {
-        float temporaryBrightness = 0.4f;
-        TemporaryBrightnessStrategy temporaryBrightnessStrategy = mock(
-                TemporaryBrightnessStrategy.class);
-        when(mDisplayBrightnessStrategySelector.getTemporaryDisplayBrightnessStrategy()).thenReturn(
-                temporaryBrightnessStrategy);
-        mDisplayBrightnessController.setTemporaryBrightness(temporaryBrightness);
-        verify(temporaryBrightnessStrategy).setTemporaryScreenBrightness(temporaryBrightness);
-    }
-
-    @Test
-    public void setCurrentScreenBrightness() {
-        // Current Screen brightness is set as expected when a different value than the current
-        // is set
-        float currentScreenBrightness = 0.4f;
-        mDisplayBrightnessController.setAndNotifyCurrentScreenBrightness(currentScreenBrightness);
-        assertEquals(mDisplayBrightnessController.getCurrentBrightness(),
-                currentScreenBrightness, /* delta= */ 0.0f);
-        verify(mBrightnessChangeExecutor).execute(mOnBrightnessChangeRunnable);
-
-        // No change to the current screen brightness is same as the existing one
-        mDisplayBrightnessController.setAndNotifyCurrentScreenBrightness(currentScreenBrightness);
-        verifyNoMoreInteractions(mBrightnessChangeExecutor);
-    }
-
-    @Test
-    public void setPendingScreenBrightnessSetting() {
-        float pendingScreenBrightness = 0.4f;
-        mDisplayBrightnessController.setPendingScreenBrightness(pendingScreenBrightness);
-        assertEquals(mDisplayBrightnessController.getPendingScreenBrightness(),
-                pendingScreenBrightness, /* delta= */ 0.0f);
-    }
-
-    @Test
-    public void updateUserSetScreenBrightness() {
-        // No brightness is set if the pending brightness is invalid
-        mDisplayBrightnessController.setPendingScreenBrightness(Float.NaN);
-        assertFalse(mDisplayBrightnessController.updateUserSetScreenBrightness());
-
-        // user set brightness is not set if the current and the pending brightness are same.
-        float currentBrightness = 0.4f;
-        TemporaryBrightnessStrategy temporaryBrightnessStrategy = mock(
-                TemporaryBrightnessStrategy.class);
-        when(mDisplayBrightnessStrategySelector.getTemporaryDisplayBrightnessStrategy()).thenReturn(
-                temporaryBrightnessStrategy);
-        mDisplayBrightnessController.setAndNotifyCurrentScreenBrightness(currentBrightness);
-        mDisplayBrightnessController.setPendingScreenBrightness(currentBrightness);
-        mDisplayBrightnessController.setTemporaryBrightness(currentBrightness);
-        assertFalse(mDisplayBrightnessController.updateUserSetScreenBrightness());
-        verify(temporaryBrightnessStrategy).setTemporaryScreenBrightness(
-                PowerManager.BRIGHTNESS_INVALID_FLOAT);
-        assertEquals(mDisplayBrightnessController.getPendingScreenBrightness(),
-                PowerManager.BRIGHTNESS_INVALID_FLOAT, /* delta= */ 0.0f);
-
-        // user set brightness is set as expected
-        currentBrightness = 0.4f;
-        float pendingScreenBrightness = 0.3f;
-        float temporaryScreenBrightness = 0.2f;
-        mDisplayBrightnessController.setAndNotifyCurrentScreenBrightness(currentBrightness);
-        mDisplayBrightnessController.setPendingScreenBrightness(pendingScreenBrightness);
-        mDisplayBrightnessController.setTemporaryBrightness(temporaryScreenBrightness);
-        assertTrue(mDisplayBrightnessController.updateUserSetScreenBrightness());
-        assertEquals(mDisplayBrightnessController.getCurrentBrightness(),
-                pendingScreenBrightness, /* delta= */ 0.0f);
-        assertEquals(mDisplayBrightnessController.getLastUserSetScreenBrightness(),
-                pendingScreenBrightness, /* delta= */ 0.0f);
-        verify(mBrightnessChangeExecutor, times(2))
-                .execute(mOnBrightnessChangeRunnable);
-        verify(temporaryBrightnessStrategy, times(2))
-                .setTemporaryScreenBrightness(PowerManager.BRIGHTNESS_INVALID_FLOAT);
-        assertEquals(mDisplayBrightnessController.getPendingScreenBrightness(),
-                PowerManager.BRIGHTNESS_INVALID_FLOAT, /* delta= */ 0.0f);
-    }
-
-    @Test
-    public void registerBrightnessSettingChangeListener() {
-        BrightnessSetting.BrightnessSettingListener brightnessSettingListener = mock(
-                BrightnessSetting.BrightnessSettingListener.class);
-        mDisplayBrightnessController.registerBrightnessSettingChangeListener(
-                brightnessSettingListener);
-        verify(mBrightnessSetting).registerListener(brightnessSettingListener);
-        assertEquals(mDisplayBrightnessController.getBrightnessSettingListener(),
-                brightnessSettingListener);
-    }
-
-    @Test
-    public void getScreenBrightnessSetting() {
-        // getScreenBrightnessSetting returns the value relayed by BrightnessSetting, if the
-        // valid is valid and in range
-        float brightnessSetting = 0.2f;
-        when(mBrightnessSetting.getBrightness()).thenReturn(brightnessSetting);
-        assertEquals(mDisplayBrightnessController.getScreenBrightnessSetting(), brightnessSetting,
-                /* delta= */ 0.0f);
-
-        // getScreenBrightnessSetting value is clamped if BrightnessSetting returns value beyond max
-        brightnessSetting = 1.1f;
-        when(mBrightnessSetting.getBrightness()).thenReturn(brightnessSetting);
-        assertEquals(mDisplayBrightnessController.getScreenBrightnessSetting(), 1.0f,
-                /* delta= */ 0.0f);
-
-        // getScreenBrightnessSetting returns default value is BrightnessSetting returns invalid
-        // value.
-        brightnessSetting = Float.NaN;
-        when(mBrightnessSetting.getBrightness()).thenReturn(brightnessSetting);
-        assertEquals(mDisplayBrightnessController.getScreenBrightnessSetting(), DEFAULT_BRIGHTNESS,
-                /* delta= */ 0.0f);
-    }
-
-    @Test
-    public void setBrightnessSetsInBrightnessSetting() {
-        float brightnessValue = 0.3f;
-        mDisplayBrightnessController.setBrightness(brightnessValue);
-        verify(mBrightnessSetting).setBrightness(brightnessValue);
-    }
-
-    @Test
-    public void updateScreenBrightnessSetting() {
-        // This interaction happens in the constructor itself
-        verify(mBrightnessSetting).getBrightness();
-
-        // Sets the appropriate value when valid, and not equal to the current brightness
-        float brightnessValue = 0.3f;
-        mDisplayBrightnessController.updateScreenBrightnessSetting(brightnessValue);
-        assertEquals(mDisplayBrightnessController.getCurrentBrightness(), brightnessValue,
-                0.0f);
-        verify(mBrightnessChangeExecutor).execute(mOnBrightnessChangeRunnable);
-        verify(mBrightnessSetting).setBrightness(brightnessValue);
-
-        // Does nothing if the value is invalid
-        mDisplayBrightnessController.updateScreenBrightnessSetting(Float.NaN);
-        verifyNoMoreInteractions(mBrightnessChangeExecutor, mBrightnessSetting);
-
-        // Does nothing if the value is same as the current brightness
-        brightnessValue = 0.2f;
-        mDisplayBrightnessController.setAndNotifyCurrentScreenBrightness(brightnessValue);
-        verify(mBrightnessChangeExecutor, times(2))
-                .execute(mOnBrightnessChangeRunnable);
-        mDisplayBrightnessController.updateScreenBrightnessSetting(brightnessValue);
-        verifyNoMoreInteractions(mBrightnessChangeExecutor, mBrightnessSetting);
-    }
-
-    @Test
-    public void testConvertToNits() {
-        final float brightness = 0.5f;
-        final float nits = 300;
-        final float adjustedNits = 200;
-
-        // ABC is null
-        assertEquals(-1f, mDisplayBrightnessController.convertToNits(brightness),
-                /* delta= */ 0);
-        assertEquals(-1f, mDisplayBrightnessController.convertToAdjustedNits(brightness),
-                /* delta= */ 0);
-
-        AutomaticBrightnessController automaticBrightnessController =
-                mock(AutomaticBrightnessController.class);
-        when(automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
-        when(automaticBrightnessController.convertToAdjustedNits(brightness))
-                .thenReturn(adjustedNits);
-        mDisplayBrightnessController.setAutomaticBrightnessController(
-                automaticBrightnessController);
-
-        assertEquals(nits, mDisplayBrightnessController.convertToNits(brightness), /* delta= */ 0);
-        assertEquals(adjustedNits, mDisplayBrightnessController.convertToAdjustedNits(brightness),
-                /* delta= */ 0);
-    }
-
-    @Test
-    public void testConvertToFloatScale() {
-        float brightness = 0.5f;
-        float nits = 300;
-
-        // ABC is null
-        assertEquals(PowerManager.BRIGHTNESS_INVALID_FLOAT,
-                mDisplayBrightnessController.convertToFloatScale(nits), /* delta= */ 0);
-
-        AutomaticBrightnessController automaticBrightnessController =
-                mock(AutomaticBrightnessController.class);
-        when(automaticBrightnessController.convertToFloatScale(nits)).thenReturn(brightness);
-        mDisplayBrightnessController.setAutomaticBrightnessController(
-                automaticBrightnessController);
-
-        assertEquals(brightness, mDisplayBrightnessController.convertToFloatScale(nits),
-                /* delta= */ 0);
-    }
-
-    @Test
-    public void stop() {
-        BrightnessSetting.BrightnessSettingListener brightnessSettingListener = mock(
-                BrightnessSetting.BrightnessSettingListener.class);
-        mDisplayBrightnessController.registerBrightnessSettingChangeListener(
-                brightnessSettingListener);
-        mDisplayBrightnessController.stop();
-        verify(mBrightnessSetting).unregisterListener(brightnessSettingListener);
-    }
-
-    @Test
-    public void testLoadNitBasedBrightnessSetting() {
-        // When the nits value is valid, the brightness is set from the old default display nits
-        // value
-        float nits = 200f;
-        float brightness = 0.3f;
-        AutomaticBrightnessController automaticBrightnessController =
-                mock(AutomaticBrightnessController.class);
-        when(automaticBrightnessController.convertToFloatScale(nits)).thenReturn(brightness);
-        when(mBrightnessSetting.getBrightnessNitsForDefaultDisplay()).thenReturn(nits);
-        mDisplayBrightnessController.setAutomaticBrightnessController(
-                automaticBrightnessController);
-        verify(mBrightnessSetting).setBrightness(brightness);
-        assertEquals(brightness, mDisplayBrightnessController.getCurrentBrightness(), 0.01f);
-        clearInvocations(automaticBrightnessController, mBrightnessSetting);
-
-        // When the nits value is invalid, the brightness is resumed from where it was last set
-        nits = -1;
-        brightness = 0.4f;
-        when(automaticBrightnessController.convertToFloatScale(nits)).thenReturn(brightness);
-        when(mBrightnessSetting.getBrightnessNitsForDefaultDisplay()).thenReturn(nits);
-        when(mBrightnessSetting.getBrightness()).thenReturn(brightness);
-        mDisplayBrightnessController.setAutomaticBrightnessController(
-                automaticBrightnessController);
-        verify(mBrightnessSetting, never()).setBrightness(brightness);
-        assertEquals(brightness, mDisplayBrightnessController.getCurrentBrightness(), 0.01f);
-        clearInvocations(automaticBrightnessController, mBrightnessSetting);
-
-        // When the display is a non-default display, the brightness is resumed from where it was
-        // last set
-        int nonDefaultDisplayId = 1;
-        mDisplayBrightnessController = new DisplayBrightnessController(mContext, mInjector,
-                nonDefaultDisplayId, DEFAULT_BRIGHTNESS, mBrightnessSetting,
-                mOnBrightnessChangeRunnable, mBrightnessChangeExecutor);
-        brightness = 0.5f;
-        when(mBrightnessSetting.getBrightness()).thenReturn(brightness);
-        mDisplayBrightnessController.setAutomaticBrightnessController(
-                automaticBrightnessController);
-        assertEquals(brightness, mDisplayBrightnessController.getCurrentBrightness(), 0.01f);
-        verifyZeroInteractions(automaticBrightnessController);
-        verify(mBrightnessSetting, never()).getBrightnessNitsForDefaultDisplay();
-        verify(mBrightnessSetting, never()).setBrightness(brightness);
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java b/services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java
deleted file mode 100644
index a9e616d..0000000
--- a/services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java
+++ /dev/null
@@ -1,191 +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.server.display.brightness;
-
-import static org.junit.Assert.assertEquals;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.hardware.display.DisplayManagerInternal;
-import android.view.Display;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.internal.R;
-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.OverrideBrightnessStrategy;
-import com.android.server.display.brightness.strategy.ScreenOffBrightnessStrategy;
-import com.android.server.display.brightness.strategy.TemporaryBrightnessStrategy;
-
-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)
-public final class DisplayBrightnessStrategySelectorTest {
-    private static final boolean DISALLOW_AUTO_BRIGHTNESS_WHILE_DOZING = false;
-    private static final int DISPLAY_ID = 1;
-
-    @Mock
-    private ScreenOffBrightnessStrategy mScreenOffBrightnessModeStrategy;
-    @Mock
-    private DozeBrightnessStrategy mDozeBrightnessModeStrategy;
-    @Mock
-    private OverrideBrightnessStrategy mOverrideBrightnessStrategy;
-    @Mock
-    private TemporaryBrightnessStrategy mTemporaryBrightnessStrategy;
-    @Mock
-    private BoostBrightnessStrategy mBoostBrightnessStrategy;
-    @Mock
-    private InvalidBrightnessStrategy mInvalidBrightnessStrategy;
-    @Mock
-    private FollowerBrightnessStrategy mFollowerBrightnessStrategy;
-    @Mock
-    private Context mContext;
-    @Mock
-    private Resources mResources;
-
-    private DisplayBrightnessStrategySelector mDisplayBrightnessStrategySelector;
-
-    @Before
-    public void before() {
-        MockitoAnnotations.initMocks(this);
-        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);
-
-    }
-
-    @Test
-    public void selectStrategySelectsDozeStrategyWhenValid() {
-        DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
-                DisplayManagerInternal.DisplayPowerRequest.class);
-        displayPowerRequest.policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE;
-        when(mResources.getBoolean(R.bool.config_allowAutoBrightnessWhileDozing)).thenReturn(
-                DISALLOW_AUTO_BRIGHTNESS_WHILE_DOZING);
-        assertEquals(mDisplayBrightnessStrategySelector.selectStrategy(displayPowerRequest,
-                Display.STATE_DOZE), mDozeBrightnessModeStrategy);
-    }
-
-    @Test
-    public void selectStrategySelectsScreenOffStrategyWhenValid() {
-        DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
-                DisplayManagerInternal.DisplayPowerRequest.class);
-        assertEquals(mDisplayBrightnessStrategySelector.selectStrategy(displayPowerRequest,
-                Display.STATE_OFF), mScreenOffBrightnessModeStrategy);
-    }
-
-    @Test
-    public void selectStrategySelectsOverrideStrategyWhenValid() {
-        DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
-                DisplayManagerInternal.DisplayPowerRequest.class);
-        displayPowerRequest.screenBrightnessOverride = 0.4f;
-        when(mFollowerBrightnessStrategy.getBrightnessToFollow()).thenReturn(Float.NaN);
-        assertEquals(mDisplayBrightnessStrategySelector.selectStrategy(displayPowerRequest,
-                Display.STATE_ON), mOverrideBrightnessStrategy);
-    }
-
-    @Test
-    public void selectStrategySelectsTemporaryStrategyWhenValid() {
-        DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
-                DisplayManagerInternal.DisplayPowerRequest.class);
-        displayPowerRequest.screenBrightnessOverride = Float.NaN;
-        when(mFollowerBrightnessStrategy.getBrightnessToFollow()).thenReturn(Float.NaN);
-        when(mTemporaryBrightnessStrategy.getTemporaryScreenBrightness()).thenReturn(0.3f);
-        assertEquals(mDisplayBrightnessStrategySelector.selectStrategy(displayPowerRequest,
-                Display.STATE_ON), mTemporaryBrightnessStrategy);
-    }
-
-    @Test
-    public void selectStrategySelectsBoostStrategyWhenValid() {
-        DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
-                DisplayManagerInternal.DisplayPowerRequest.class);
-        displayPowerRequest.boostScreenBrightness = true;
-        when(mFollowerBrightnessStrategy.getBrightnessToFollow()).thenReturn(Float.NaN);
-        displayPowerRequest.screenBrightnessOverride = Float.NaN;
-        when(mTemporaryBrightnessStrategy.getTemporaryScreenBrightness()).thenReturn(Float.NaN);
-        assertEquals(mDisplayBrightnessStrategySelector.selectStrategy(displayPowerRequest,
-                Display.STATE_ON), mBoostBrightnessStrategy);
-    }
-
-    @Test
-    public void selectStrategySelectsInvalidStrategyWhenNoStrategyIsValid() {
-        DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
-                DisplayManagerInternal.DisplayPowerRequest.class);
-        displayPowerRequest.screenBrightnessOverride = Float.NaN;
-        when(mFollowerBrightnessStrategy.getBrightnessToFollow()).thenReturn(Float.NaN);
-        when(mTemporaryBrightnessStrategy.getTemporaryScreenBrightness()).thenReturn(Float.NaN);
-        assertEquals(mDisplayBrightnessStrategySelector.selectStrategy(displayPowerRequest,
-                Display.STATE_ON), mInvalidBrightnessStrategy);
-    }
-
-    @Test
-    public void selectStrategySelectsFollowerStrategyWhenValid() {
-        DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
-                DisplayManagerInternal.DisplayPowerRequest.class);
-        when(mFollowerBrightnessStrategy.getBrightnessToFollow()).thenReturn(0.3f);
-        assertEquals(mDisplayBrightnessStrategySelector.selectStrategy(displayPowerRequest,
-                Display.STATE_ON), mFollowerBrightnessStrategy);
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java b/services/tests/servicestests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java
deleted file mode 100644
index d9cf15b..0000000
--- a/services/tests/servicestests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java
+++ /dev/null
@@ -1,332 +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.server.display.brightness.strategy;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-import android.content.ContextWrapper;
-import android.hardware.display.BrightnessConfiguration;
-import android.hardware.display.DisplayManagerInternal;
-import android.os.PowerManager;
-import android.os.UserHandle;
-import android.provider.Settings;
-import android.test.mock.MockContentResolver;
-import android.view.Display;
-
-import androidx.test.core.app.ApplicationProvider;
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.internal.util.test.FakeSettingsProvider;
-import com.android.internal.util.test.FakeSettingsProviderRule;
-import com.android.server.display.AutomaticBrightnessController;
-import com.android.server.display.brightness.BrightnessEvent;
-import com.android.server.display.brightness.BrightnessReason;
-
-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;
-import org.mockito.MockitoAnnotations;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class AutomaticBrightnessStrategyTest {
-    private static final int DISPLAY_ID = 0;
-    @Rule
-    public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
-
-    @Mock
-    private AutomaticBrightnessController mAutomaticBrightnessController;
-
-    private BrightnessConfiguration mBrightnessConfiguration;
-    private float mDefaultScreenAutoBrightnessAdjustment;
-    private Context mContext;
-    private AutomaticBrightnessStrategy mAutomaticBrightnessStrategy;
-
-    @Before
-    public void before() {
-        MockitoAnnotations.initMocks(this);
-        mContext = spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
-        final MockContentResolver resolver = mSettingsProviderRule.mockContentResolver(mContext);
-        when(mContext.getContentResolver()).thenReturn(resolver);
-        mDefaultScreenAutoBrightnessAdjustment = Settings.System.getFloat(
-                mContext.getContentResolver(),
-                Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, Float.NaN);
-        Settings.System.putFloat(mContext.getContentResolver(),
-                Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, 0.5f);
-        mAutomaticBrightnessStrategy = new AutomaticBrightnessStrategy(mContext, DISPLAY_ID);
-
-        mBrightnessConfiguration = new BrightnessConfiguration.Builder(
-                new float[]{0f, 1f}, new float[]{0, PowerManager.BRIGHTNESS_ON}).build();
-        when(mAutomaticBrightnessController.hasUserDataPoints()).thenReturn(true);
-        mAutomaticBrightnessStrategy.setAutomaticBrightnessController(
-                mAutomaticBrightnessController);
-        mAutomaticBrightnessStrategy.setBrightnessConfiguration(mBrightnessConfiguration,
-                true);
-    }
-
-    @After
-    public void after() {
-        Settings.System.putFloat(mContext.getContentResolver(),
-                Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, mDefaultScreenAutoBrightnessAdjustment);
-    }
-
-    @Test
-    public void setAutoBrightnessWhenDisabled() {
-        mAutomaticBrightnessStrategy.setUseAutoBrightness(false);
-        int targetDisplayState = Display.STATE_ON;
-        boolean allowAutoBrightnessWhileDozing = false;
-        float brightnessState = Float.NaN;
-        int brightnessReason = BrightnessReason.REASON_OVERRIDE;
-        int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
-        float lastUserSetBrightness = 0.2f;
-        boolean userSetBrightnessChanged = true;
-        mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments(true);
-        mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
-                allowAutoBrightnessWhileDozing, brightnessState, brightnessReason, policy,
-                lastUserSetBrightness, userSetBrightnessChanged);
-        verify(mAutomaticBrightnessController)
-                .configure(AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED,
-                        mBrightnessConfiguration,
-                        lastUserSetBrightness,
-                        userSetBrightnessChanged, 0.5f,
-                        false, policy, true);
-    }
-
-    @Test
-    public void setAutoBrightnessWhenEnabledAndDisplayIsDozing() {
-        mAutomaticBrightnessStrategy.setUseAutoBrightness(true);
-        int targetDisplayState = Display.STATE_DOZE;
-        float brightnessState = Float.NaN;
-        boolean allowAutoBrightnessWhileDozing = true;
-        int brightnessReason = BrightnessReason.REASON_DOZE;
-        int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE;
-        float lastUserSetBrightness = 0.2f;
-        boolean userSetBrightnessChanged = true;
-        Settings.System.putFloat(mContext.getContentResolver(),
-                Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, 0.4f);
-        mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments(false);
-        mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
-                allowAutoBrightnessWhileDozing, brightnessState, brightnessReason, policy,
-                lastUserSetBrightness, userSetBrightnessChanged);
-        verify(mAutomaticBrightnessController)
-                .configure(AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED,
-                        mBrightnessConfiguration,
-                        lastUserSetBrightness,
-                        userSetBrightnessChanged, 0.4f,
-                        true, policy, true);
-    }
-
-    @Test
-    public void setAutoBrightnessWhenEnabledAndDisplayIsOn() {
-        mAutomaticBrightnessStrategy.setUseAutoBrightness(true);
-        int targetDisplayState = Display.STATE_ON;
-        float brightnessState = Float.NaN;
-        boolean allowAutoBrightnessWhileDozing = false;
-        int brightnessReason = BrightnessReason.REASON_OVERRIDE;
-        float lastUserSetBrightness = 0.2f;
-        boolean userSetBrightnessChanged = true;
-        int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
-        float pendingBrightnessAdjustment = 0.1f;
-        Settings.System.putFloat(mContext.getContentResolver(),
-                Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, pendingBrightnessAdjustment);
-        mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments(false);
-        mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
-                allowAutoBrightnessWhileDozing, brightnessState, brightnessReason, policy,
-                lastUserSetBrightness, userSetBrightnessChanged);
-        verify(mAutomaticBrightnessController)
-                .configure(AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED,
-                        mBrightnessConfiguration,
-                        lastUserSetBrightness,
-                        userSetBrightnessChanged, pendingBrightnessAdjustment,
-                        true, policy, true);
-    }
-
-    @Test
-    public void accommodateUserBrightnessChangesWorksAsExpected() {
-        // Verify the state if automaticBrightnessController is configured.
-        assertFalse(mAutomaticBrightnessStrategy.isShortTermModelActive());
-        boolean userSetBrightnessChanged = true;
-        float lastUserSetScreenBrightness = 0.2f;
-        int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
-        BrightnessConfiguration brightnessConfiguration = new BrightnessConfiguration.Builder(
-                new float[]{0f, 1f}, new float[]{0, PowerManager.BRIGHTNESS_ON}).build();
-        int autoBrightnessState = AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED;
-        float temporaryAutoBrightnessAdjustments = 0.4f;
-        mAutomaticBrightnessStrategy.setShouldResetShortTermModel(true);
-        setTemporaryAutoBrightnessAdjustment(temporaryAutoBrightnessAdjustments);
-        mAutomaticBrightnessStrategy.accommodateUserBrightnessChanges(userSetBrightnessChanged,
-                lastUserSetScreenBrightness, policy, brightnessConfiguration,
-                autoBrightnessState);
-        verify(mAutomaticBrightnessController).configure(autoBrightnessState,
-                brightnessConfiguration,
-                lastUserSetScreenBrightness,
-                userSetBrightnessChanged, temporaryAutoBrightnessAdjustments,
-                /* userChangedAutoBrightnessAdjustment= */ false, policy,
-                /* shouldResetShortTermModel= */ true);
-        assertTrue(mAutomaticBrightnessStrategy.isTemporaryAutoBrightnessAdjustmentApplied());
-        assertFalse(mAutomaticBrightnessStrategy.shouldResetShortTermModel());
-        assertTrue(mAutomaticBrightnessStrategy.isShortTermModelActive());
-        // Verify the state when automaticBrightnessController is not configured
-        setTemporaryAutoBrightnessAdjustment(Float.NaN);
-        mAutomaticBrightnessStrategy.setAutomaticBrightnessController(null);
-        mAutomaticBrightnessStrategy.setShouldResetShortTermModel(true);
-        mAutomaticBrightnessStrategy.accommodateUserBrightnessChanges(userSetBrightnessChanged,
-                lastUserSetScreenBrightness, policy, brightnessConfiguration,
-                autoBrightnessState);
-        assertFalse(mAutomaticBrightnessStrategy.isTemporaryAutoBrightnessAdjustmentApplied());
-        assertTrue(mAutomaticBrightnessStrategy.shouldResetShortTermModel());
-        assertFalse(mAutomaticBrightnessStrategy.isShortTermModelActive());
-    }
-
-    @Test
-    public void adjustAutomaticBrightnessStateIfValid() throws Settings.SettingNotFoundException {
-        float brightnessState = 0.3f;
-        float autoBrightnessAdjustment = 0.2f;
-        when(mAutomaticBrightnessController.getAutomaticScreenBrightnessAdjustment()).thenReturn(
-                autoBrightnessAdjustment);
-        mAutomaticBrightnessStrategy.adjustAutomaticBrightnessStateIfValid(brightnessState);
-        assertTrue(mAutomaticBrightnessStrategy.hasAppliedAutoBrightness());
-        assertEquals(autoBrightnessAdjustment,
-                mAutomaticBrightnessStrategy.getAutoBrightnessAdjustment(), 0.0f);
-        assertEquals(autoBrightnessAdjustment, Settings.System.getFloatForUser(
-                mContext.getContentResolver(),
-                Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ,
-                UserHandle.USER_CURRENT), 0.0f);
-        assertEquals(BrightnessReason.ADJUSTMENT_AUTO,
-                mAutomaticBrightnessStrategy.getAutoBrightnessAdjustmentReasonsFlags());
-        float invalidBrightness = -0.5f;
-        mAutomaticBrightnessStrategy
-                .adjustAutomaticBrightnessStateIfValid(invalidBrightness);
-        assertFalse(mAutomaticBrightnessStrategy.hasAppliedAutoBrightness());
-        assertEquals(autoBrightnessAdjustment,
-                mAutomaticBrightnessStrategy.getAutoBrightnessAdjustment(), 0.0f);
-        assertEquals(0,
-                mAutomaticBrightnessStrategy.getAutoBrightnessAdjustmentReasonsFlags());
-    }
-
-    @Test
-    public void updatePendingAutoBrightnessAdjustments() {
-        // Verify the state when the pendingAutoBrightnessAdjustments are not present
-        setPendingAutoBrightnessAdjustment(Float.NaN);
-        assertFalse(mAutomaticBrightnessStrategy.processPendingAutoBrightnessAdjustments());
-        assertFalse(mAutomaticBrightnessStrategy.getAutoBrightnessAdjustmentChanged());
-        // Verify the state when the pendingAutoBrightnessAdjustments are present, but
-        // pendingAutoBrightnessAdjustments and autoBrightnessAdjustments are the same
-        float autoBrightnessAdjustment = 0.3f;
-        setPendingAutoBrightnessAdjustment(autoBrightnessAdjustment);
-        setAutoBrightnessAdjustment(autoBrightnessAdjustment);
-        assertFalse(mAutomaticBrightnessStrategy.processPendingAutoBrightnessAdjustments());
-        assertFalse(mAutomaticBrightnessStrategy.getAutoBrightnessAdjustmentChanged());
-        assertEquals(Float.NaN, mAutomaticBrightnessStrategy.getPendingAutoBrightnessAdjustment(),
-                0.0f);
-        // Verify the state when the pendingAutoBrightnessAdjustments are present, and
-        // pendingAutoBrightnessAdjustments and autoBrightnessAdjustments are not the same
-        float pendingAutoBrightnessAdjustment = 0.2f;
-        setPendingAutoBrightnessAdjustment(pendingAutoBrightnessAdjustment);
-        setTemporaryAutoBrightnessAdjustment(0.1f);
-        assertTrue(mAutomaticBrightnessStrategy.processPendingAutoBrightnessAdjustments());
-        assertTrue(mAutomaticBrightnessStrategy.getAutoBrightnessAdjustmentChanged());
-        assertEquals(pendingAutoBrightnessAdjustment,
-                mAutomaticBrightnessStrategy.getAutoBrightnessAdjustment(), 0.0f);
-        assertEquals(Float.NaN, mAutomaticBrightnessStrategy.getPendingAutoBrightnessAdjustment(),
-                0.0f);
-        assertEquals(Float.NaN, mAutomaticBrightnessStrategy.getTemporaryAutoBrightnessAdjustment(),
-                0.0f);
-    }
-
-    @Test
-    public void setAutomaticBrightnessWorksAsExpected() {
-        float automaticScreenBrightness = 0.3f;
-        AutomaticBrightnessController automaticBrightnessController = mock(
-                AutomaticBrightnessController.class);
-        when(automaticBrightnessController.getAutomaticScreenBrightness(any(BrightnessEvent.class)))
-                .thenReturn(automaticScreenBrightness);
-        mAutomaticBrightnessStrategy.setAutomaticBrightnessController(
-                automaticBrightnessController);
-        assertEquals(automaticScreenBrightness,
-                mAutomaticBrightnessStrategy.getAutomaticScreenBrightness(
-                        new BrightnessEvent(DISPLAY_ID)), 0.0f);
-    }
-
-    @Test
-    public void shouldUseAutoBrightness() {
-        mAutomaticBrightnessStrategy.setUseAutoBrightness(true);
-        assertTrue(mAutomaticBrightnessStrategy.shouldUseAutoBrightness());
-    }
-
-    @Test
-    public void setPendingAutoBrightnessAdjustments() throws Settings.SettingNotFoundException {
-        float pendingAutoBrightnessAdjustments = 0.3f;
-        setPendingAutoBrightnessAdjustment(pendingAutoBrightnessAdjustments);
-        assertEquals(pendingAutoBrightnessAdjustments,
-                mAutomaticBrightnessStrategy.getPendingAutoBrightnessAdjustment(), 0.0f);
-        assertEquals(pendingAutoBrightnessAdjustments, Settings.System.getFloatForUser(
-                mContext.getContentResolver(),
-                Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ,
-                UserHandle.USER_CURRENT), 0.0f);
-    }
-
-    @Test
-    public void setTemporaryAutoBrightnessAdjustment() {
-        float temporaryAutoBrightnessAdjustment = 0.3f;
-        mAutomaticBrightnessStrategy.setTemporaryAutoBrightnessAdjustment(
-                temporaryAutoBrightnessAdjustment);
-        assertEquals(temporaryAutoBrightnessAdjustment,
-                mAutomaticBrightnessStrategy.getTemporaryAutoBrightnessAdjustment(), 0.0f);
-    }
-
-    @Test
-    public void setAutoBrightnessApplied() {
-        mAutomaticBrightnessStrategy.setAutoBrightnessApplied(true);
-        assertTrue(mAutomaticBrightnessStrategy.hasAppliedAutoBrightness());
-    }
-
-    @Test
-    public void testVerifyNoAutoBrightnessAdjustmentsArePopulatedForNonDefaultDisplay() {
-        int newDisplayId = 1;
-        mAutomaticBrightnessStrategy = new AutomaticBrightnessStrategy(mContext, newDisplayId);
-        mAutomaticBrightnessStrategy.putAutoBrightnessAdjustmentSetting(0.3f);
-        assertEquals(0.5f, mAutomaticBrightnessStrategy.getAutoBrightnessAdjustment(),
-                0.0f);
-    }
-
-    private void setPendingAutoBrightnessAdjustment(float pendingAutoBrightnessAdjustment) {
-        Settings.System.putFloat(mContext.getContentResolver(),
-                Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, pendingAutoBrightnessAdjustment);
-        mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments(false);
-    }
-
-    private void setTemporaryAutoBrightnessAdjustment(float temporaryAutoBrightnessAdjustment) {
-        mAutomaticBrightnessStrategy.setTemporaryAutoBrightnessAdjustment(
-                temporaryAutoBrightnessAdjustment);
-    }
-
-    private void setAutoBrightnessAdjustment(float autoBrightnessAdjustment) {
-        mAutomaticBrightnessStrategy.putAutoBrightnessAdjustmentSetting(autoBrightnessAdjustment);
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/display/color/ColorDisplayServiceTest.java b/services/tests/servicestests/src/com/android/server/display/color/ColorDisplayServiceTest.java
deleted file mode 100644
index 618ab1b..0000000
--- a/services/tests/servicestests/src/com/android/server/display/color/ColorDisplayServiceTest.java
+++ /dev/null
@@ -1,1406 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.display.color;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.annotation.NonNull;
-import android.app.ActivityManager;
-import android.app.AlarmManager;
-import android.content.Context;
-import android.content.ContextWrapper;
-import android.content.res.Resources;
-import android.hardware.display.ColorDisplayManager;
-import android.hardware.display.DisplayManagerInternal;
-import android.hardware.display.Time;
-import android.os.Handler;
-import android.os.UserHandle;
-import android.provider.Settings;
-import android.provider.Settings.Secure;
-import android.provider.Settings.System;
-import android.test.mock.MockContentResolver;
-import android.view.Display;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.internal.R;
-import com.android.internal.util.test.FakeSettingsProvider;
-import com.android.server.LocalServices;
-import com.android.server.SystemService;
-import com.android.server.twilight.TwilightListener;
-import com.android.server.twilight.TwilightManager;
-import com.android.server.twilight.TwilightState;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mockito;
-
-import java.time.LocalDateTime;
-import java.time.LocalTime;
-import java.time.ZoneId;
-import java.util.Calendar;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-@RunWith(AndroidJUnit4.class)
-public class ColorDisplayServiceTest {
-
-    private Context mContext;
-    private int mUserId;
-
-    private MockTwilightManager mTwilightManager;
-    private DisplayTransformManager mDisplayTransformManager;
-    private DisplayManagerInternal mDisplayManagerInternal;
-
-    private ColorDisplayService mCds;
-    private ColorDisplayService.BinderService mBinderService;
-
-    private Resources mResourcesSpy;
-
-    private static final int[] MINIMAL_COLOR_MODES = new int[] {
-        ColorDisplayManager.COLOR_MODE_NATURAL,
-        ColorDisplayManager.COLOR_MODE_BOOSTED,
-    };
-
-    @Before
-    public void setUp() {
-        mContext = Mockito.spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
-        doReturn(mContext).when(mContext).getApplicationContext();
-
-        final Resources res = Mockito.spy(mContext.getResources());
-        doReturn(MINIMAL_COLOR_MODES).when(res).getIntArray(R.array.config_availableColorModes);
-        doReturn(true).when(res).getBoolean(R.bool.config_nightDisplayAvailable);
-        doReturn(true).when(res).getBoolean(R.bool.config_displayWhiteBalanceAvailable);
-        when(mContext.getResources()).thenReturn(res);
-        mResourcesSpy = res;
-
-        mUserId = ActivityManager.getCurrentUser();
-
-        final MockContentResolver cr = new MockContentResolver(mContext);
-        cr.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
-        doReturn(cr).when(mContext).getContentResolver();
-
-        final AlarmManager am = Mockito.mock(AlarmManager.class);
-        doReturn(am).when(mContext).getSystemService(Context.ALARM_SERVICE);
-
-        mTwilightManager = new MockTwilightManager();
-        LocalServices.addService(TwilightManager.class, mTwilightManager);
-
-        mDisplayTransformManager = Mockito.mock(DisplayTransformManager.class);
-        doReturn(true).when(mDisplayTransformManager).needsLinearColorMatrix();
-        LocalServices.addService(DisplayTransformManager.class, mDisplayTransformManager);
-
-        mDisplayManagerInternal = Mockito.mock(DisplayManagerInternal.class);
-        LocalServices.removeServiceForTest(DisplayManagerInternal.class);
-        LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternal);
-
-        mCds = new ColorDisplayService(mContext);
-        mBinderService = mCds.new BinderService();
-        LocalServices.addService(ColorDisplayService.ColorDisplayServiceInternal.class,
-                mCds.new ColorDisplayServiceInternal());
-    }
-
-    @After
-    public void tearDown() {
-        /*
-         * Wait for internal {@link Handler} to finish processing pending messages, so that test
-         * code can safelyremove {@link DisplayTransformManager} mock from {@link LocalServices}.
-         */
-        mCds.mHandler.runWithScissors(() -> { /* nop */ }, /* timeout */ 1000);
-        mCds = null;
-
-        LocalServices.removeServiceForTest(TwilightManager.class);
-        mTwilightManager = null;
-
-        LocalServices.removeServiceForTest(DisplayTransformManager.class);
-
-        mUserId = UserHandle.USER_NULL;
-        mContext = null;
-
-        FakeSettingsProvider.clearSettingsProvider();
-
-        LocalServices.removeServiceForTest(ColorDisplayService.ColorDisplayServiceInternal.class);
-        LocalServices.removeServiceForTest(DisplayManagerInternal.class);
-    }
-
-    @Test
-    public void customSchedule_whenStartedAfterNight_ifOffAfterNight_turnsOff() {
-        setAutoModeCustom(-120 /* startTimeOffset */, -60 /* endTimeOffset */);
-        setNightDisplayActivated(false /* activated */, -30 /* lastActivatedTimeOffset */);
-
-        startService();
-        assertActivated(false /* activated */);
-    }
-
-    @Test
-    public void customSchedule_whenStartedAfterNight_ifOffBeforeNight_turnsOff() {
-        setAutoModeCustom(-120 /* startTimeOffset */, -60 /* endTimeOffset */);
-        setNightDisplayActivated(false /* activated */, -180 /* lastActivatedTimeOffset */);
-
-        startService();
-        assertActivated(false /* activated */);
-    }
-
-    @Test
-    public void customSchedule_whenStartedAfterNight_ifOffDuringNight_turnsOff() {
-        setAutoModeCustom(-120 /* startTimeOffset */, -60 /* endTimeOffset */);
-        setNightDisplayActivated(false /* activated */, -90 /* lastActivatedTimeOffset */);
-
-        startService();
-        assertActivated(false /* activated */);
-    }
-
-    @Test
-    public void customSchedule_whenStartedAfterNight_ifOffInFuture_turnsOff() {
-        setAutoModeCustom(-120 /* startTimeOffset */, -60 /* endTimeOffset */);
-        setNightDisplayActivated(false /* activated */, 30 /* lastActivatedTimeOffset */);
-
-        startService();
-        assertActivated(false /* activated */);
-    }
-
-    @Test
-    public void customSchedule_whenStartedAfterNight_ifOnAfterNight_turnsOn() {
-        setAutoModeCustom(-120 /* startTimeOffset */, -60 /* endTimeOffset */);
-        setNightDisplayActivated(true /* activated */, -30 /* lastActivatedTimeOffset */);
-
-        startService();
-        assertActivated(true /* activated */);
-    }
-
-    @Test
-    public void customSchedule_whenStartedAfterNight_ifOnBeforeNight_turnsOff() {
-        setAutoModeCustom(-120 /* startTimeOffset */, -60 /* endTimeOffset */);
-        setNightDisplayActivated(true /* activated */, -180 /* lastActivatedTimeOffset */);
-
-        startService();
-        assertActivated(false /* activated */);
-    }
-
-    @Test
-    public void customSchedule_whenStartedAfterNight_ifOnDuringNight_turnsOff() {
-        setAutoModeCustom(-120 /* startTimeOffset */, -60 /* endTimeOffset */);
-        setNightDisplayActivated(true /* activated */, -90 /* lastActivatedTimeOffset */);
-
-        startService();
-        assertActivated(false /* activated */);
-    }
-
-    @Test
-    public void customSchedule_whenStartedAfterNight_ifOnInFuture_turnsOff() {
-        setAutoModeCustom(-120 /* startTimeOffset */, -60 /* endTimeOffset */);
-        setNightDisplayActivated(true /* activated */, 30 /* lastActivatedTimeOffset */);
-
-        startService();
-        assertActivated(false /* activated */);
-    }
-
-    @Test
-    public void customSchedule_whenStartedBeforeNight_ifOffAfterNight_turnsOff() {
-        setAutoModeCustom(60 /* startTimeOffset */, 120 /* endTimeOffset */);
-        setNightDisplayActivated(false /* activated */, 180 /* lastActivatedTimeOffset */);
-
-        startService();
-        assertActivated(false /* activated */);
-    }
-
-    @Test
-    public void customSchedule_whenStartedBeforeNight_ifOffBeforeNight_turnsOff() {
-        setAutoModeCustom(60 /* startTimeOffset */, 120 /* endTimeOffset */);
-        setNightDisplayActivated(false /* activated */, 30 /* lastActivatedTimeOffset */);
-
-        startService();
-        assertActivated(false /* activated */);
-    }
-
-    @Test
-    public void customSchedule_whenStartedBeforeNight_ifOffDuringNight_turnsOff() {
-        setAutoModeCustom(60 /* startTimeOffset */, 120 /* endTimeOffset */);
-        setNightDisplayActivated(false /* activated */, 90 /* lastActivatedTimeOffset */);
-
-        startService();
-        assertActivated(false /* activated */);
-    }
-
-    @Test
-    public void customSchedule_whenStartedBeforeNight_ifOffInPast_turnsOff() {
-        setAutoModeCustom(60 /* startTimeOffset */, 120 /* endTimeOffset */);
-        setNightDisplayActivated(false /* activated */, -30 /* lastActivatedTimeOffset */);
-
-        startService();
-        assertActivated(false /* activated */);
-    }
-
-    @Test
-    public void customSchedule_whenStartedBeforeNight_ifOnAfterNight_turnsOff() {
-        setAutoModeCustom(60 /* startTimeOffset */, 120 /* endTimeOffset */);
-        setNightDisplayActivated(true /* activated */, 180 /* lastActivatedTimeOffset */);
-
-        startService();
-        assertActivated(false /* activated */);
-    }
-
-    @Test
-    public void customSchedule_whenStartedBeforeNight_ifOnBeforeNight_turnsOff() {
-        setAutoModeCustom(60 /* startTimeOffset */, 120 /* endTimeOffset */);
-        setNightDisplayActivated(true /* activated */, 30 /* lastActivatedTimeOffset */);
-
-        startService();
-        assertActivated(false /* activated */);
-    }
-
-    @Test
-    public void customSchedule_whenStartedBeforeNight_ifOnDuringNight_turnsOff() {
-        setAutoModeCustom(60 /* startTimeOffset */, 120 /* endTimeOffset */);
-        setNightDisplayActivated(true /* activated */, 90 /* lastActivatedTimeOffset */);
-
-        startService();
-        assertActivated(false /* activated */);
-    }
-
-    @Test
-    public void customSchedule_whenStartedBeforeNight_ifOnInPast_turnsOn() {
-        setAutoModeCustom(60 /* startTimeOffset */, 120 /* endTimeOffset */);
-        setNightDisplayActivated(true /* activated */, -30 /* lastActivatedTimeOffset */);
-
-        startService();
-        assertActivated(true /* activated */);
-    }
-
-    @Test
-    public void customSchedule_whenStartedDuringNight_ifOffAfterNight_turnsOn() {
-        setAutoModeCustom(-60 /* startTimeOffset */, 60 /* endTimeOffset */);
-        setNightDisplayActivated(false /* activated */, 90 /* lastActivatedTimeOffset */);
-
-        startService();
-        assertActivated(true /* activated */);
-    }
-
-    @Test
-    public void customSchedule_whenStartedDuringNight_ifOffBeforeNight_turnsOn() {
-        setAutoModeCustom(-60 /* startTimeOffset */, 60 /* endTimeOffset */);
-        setNightDisplayActivated(false /* activated */, -90 /* lastActivatedTimeOffset */);
-
-        startService();
-        assertActivated(true /* activated */);
-    }
-
-    @Test
-    public void customSchedule_whenStartedDuringNight_ifOffDuringNightInFuture_turnsOn() {
-        setAutoModeCustom(-60 /* startTimeOffset */, 60 /* endTimeOffset */);
-        setNightDisplayActivated(false /* activated */, 30 /* lastActivatedTimeOffset */);
-
-        startService();
-        assertActivated(true /* activated */);
-    }
-
-    @Test
-    public void customSchedule_whenStartedDuringNight_ifOffDuringNightInPast_turnsOff() {
-        setAutoModeCustom(-60 /* startTimeOffset */, 60 /* endTimeOffset */);
-        setNightDisplayActivated(false /* activated */, -30 /* lastActivatedTimeOffset */);
-
-        startService();
-        assertActivated(false /* activated */);
-    }
-
-    @Test
-    public void customSchedule_whenStartedDuringNight_ifOnAfterNight_turnsOn() {
-        setAutoModeCustom(-60 /* startTimeOffset */, 60 /* endTimeOffset */);
-        setNightDisplayActivated(true /* activated */, 90 /* lastActivatedTimeOffset */);
-
-        startService();
-        assertActivated(true /* activated */);
-    }
-
-    @Test
-    public void customSchedule_whenStartedDuringNight_ifOnBeforeNight_turnsOn() {
-        setAutoModeCustom(-60 /* startTimeOffset */, 60 /* endTimeOffset */);
-        setNightDisplayActivated(true /* activated */, -90 /* lastActivatedTimeOffset */);
-
-        startService();
-        assertActivated(true /* activated */);
-    }
-
-    @Test
-    public void customSchedule_whenStartedDuringNight_ifOnDuringNightInFuture_turnsOn() {
-        setAutoModeCustom(-60 /* startTimeOffset */, 60 /* endTimeOffset */);
-        setNightDisplayActivated(true /* activated */, 30 /* lastActivatedTimeOffset */);
-
-        startService();
-        assertActivated(true /* activated */);
-    }
-
-    @Test
-    public void customSchedule_whenStartedDuringNight_ifOnDuringNightInPast_turnsOn() {
-        setAutoModeCustom(-60 /* startTimeOffset */, 60 /* endTimeOffset */);
-        setNightDisplayActivated(true /* activated */, -30 /* lastActivatedTimeOffset */);
-
-        startService();
-        assertActivated(true /* activated */);
-    }
-
-    @Test
-    public void twilightSchedule_whenStartedAfterNight_ifOffAfterNight_turnsOff() {
-        setAutoModeTwilight(-120 /* sunsetOffset */, -60 /* sunriseOffset */);
-        setNightDisplayActivated(false /* activated */, -30 /* lastActivatedTimeOffset */);
-
-        startService();
-        assertActivated(false /* activated */);
-    }
-
-    @Test
-    public void twilightSchedule_whenStartedAfterNight_ifOffBeforeNight_turnsOff() {
-        setAutoModeTwilight(-120 /* sunsetOffset */, -60 /* sunriseOffset */);
-        setNightDisplayActivated(false /* activated */, -180 /* lastActivatedTimeOffset */);
-
-        startService();
-        assertActivated(false /* activated */);
-    }
-
-    @Test
-    public void twilightSchedule_whenStartedAfterNight_ifOffDuringNight_turnsOff() {
-        setAutoModeTwilight(-120 /* sunsetOffset */, -60 /* sunriseOffset */);
-        setNightDisplayActivated(false /* activated */, -90 /* lastActivatedTimeOffset */);
-
-        startService();
-        assertActivated(false /* activated */);
-    }
-
-    @Test
-    public void twilightSchedule_whenStartedAfterNight_ifOffInFuture_turnsOff() {
-        setAutoModeTwilight(-120 /* sunsetOffset */, -60 /* sunriseOffset */);
-        setNightDisplayActivated(false /* activated */, 30 /* lastActivatedTimeOffset */);
-
-        startService();
-        assertActivated(false /* activated */);
-    }
-
-    @Test
-    public void twilightSchedule_whenStartedAfterNight_ifOnAfterNight_turnsOn() {
-        setAutoModeTwilight(-120 /* sunsetOffset */, -60 /* sunriseOffset */);
-        setNightDisplayActivated(true /* activated */, -30 /* lastActivatedTimeOffset */);
-
-        startService();
-        assertActivated(true /* activated */);
-    }
-
-    @Test
-    public void twilightSchedule_whenStartedAfterNight_ifOnBeforeNight_turnsOff() {
-        setAutoModeTwilight(-120 /* sunsetOffset */, -60 /* sunriseOffset */);
-        setNightDisplayActivated(true /* activated */, -180 /* lastActivatedTimeOffset */);
-
-        startService();
-        assertActivated(false /* activated */);
-    }
-
-    @Test
-    public void twilightSchedule_whenStartedAfterNight_ifOnDuringNight_turnsOff() {
-        setAutoModeTwilight(-120 /* sunsetOffset */, -60 /* sunriseOffset */);
-        setNightDisplayActivated(true /* activated */, -90 /* lastActivatedTimeOffset */);
-
-        startService();
-        assertActivated(false /* activated */);
-    }
-
-    @Test
-    public void twilightSchedule_whenStartedAfterNight_ifOnInFuture_turnsOff() {
-        setAutoModeTwilight(-120 /* sunsetOffset */, -60 /* sunriseOffset */);
-        setNightDisplayActivated(true /* activated */, 30 /* lastActivatedTimeOffset */);
-
-        startService();
-        assertActivated(false /* activated */);
-    }
-
-    @Test
-    public void twilightSchedule_whenStartedBeforeNight_ifOffAfterNight_turnsOff() {
-        setAutoModeTwilight(60 /* sunsetOffset */, 120 /* sunriseOffset */);
-        setNightDisplayActivated(false /* activated */, 180 /* lastActivatedTimeOffset */);
-
-        startService();
-        assertActivated(false /* activated */);
-    }
-
-    @Test
-    public void twilightSchedule_whenStartedBeforeNight_ifOffBeforeNight_turnsOff() {
-        setAutoModeTwilight(60 /* sunsetOffset */, 120 /* sunriseOffset */);
-        setNightDisplayActivated(false /* activated */, 30 /* lastActivatedTimeOffset */);
-
-        startService();
-        assertActivated(false /* activated */);
-    }
-
-    @Test
-    public void twilightSchedule_whenStartedBeforeNight_ifOffDuringNight_turnsOff() {
-        setAutoModeTwilight(60 /* sunsetOffset */, 120 /* sunriseOffset */);
-        setNightDisplayActivated(false /* activated */, 90 /* lastActivatedTimeOffset */);
-
-        startService();
-        assertActivated(false /* activated */);
-    }
-
-    @Test
-    public void twilightSchedule_whenStartedBeforeNight_ifOffInPast_turnsOff() {
-        setAutoModeTwilight(60 /* sunsetOffset */, 120 /* sunriseOffset */);
-        setNightDisplayActivated(false /* activated */, -30 /* lastActivatedTimeOffset */);
-
-        startService();
-        assertActivated(false /* activated */);
-    }
-
-    @Test
-    public void twilightSchedule_whenStartedBeforeNight_ifOnAfterNight_turnsOff() {
-        setAutoModeTwilight(60 /* sunsetOffset */, 120 /* sunriseOffset */);
-        setNightDisplayActivated(true /* activated */, 180 /* lastActivatedTimeOffset */);
-
-        startService();
-        assertActivated(false /* activated */);
-    }
-
-    @Test
-    public void twilightSchedule_whenStartedBeforeNight_ifOnBeforeNight_turnsOff() {
-        setAutoModeTwilight(60 /* sunsetOffset */, 120 /* sunriseOffset */);
-        setNightDisplayActivated(true /* activated */, 30 /* lastActivatedTimeOffset */);
-
-        startService();
-        assertActivated(false /* activated */);
-    }
-
-    @Test
-    public void twilightSchedule_whenStartedBeforeNight_ifOnDuringNight_turnsOff() {
-        setAutoModeTwilight(60 /* sunsetOffset */, 120 /* sunriseOffset */);
-        setNightDisplayActivated(true /* activated */, 90 /* lastActivatedTimeOffset */);
-
-        startService();
-        assertActivated(false /* activated */);
-    }
-
-    @Test
-    public void twilightSchedule_whenStartedBeforeNight_ifOnInPast_turnsOn() {
-        setAutoModeTwilight(60 /* sunsetOffset */, 120 /* sunriseOffset */);
-        setNightDisplayActivated(true /* activated */, -30 /* lastActivatedTimeOffset */);
-
-        startService();
-        assertActivated(true /* activated */);
-    }
-
-    @Test
-    public void twilightSchedule_whenStartedDuringNight_ifOffAfterNight_turnsOn() {
-        setAutoModeTwilight(-60 /* sunsetOffset */, 60 /* sunriseOffset */);
-        setNightDisplayActivated(false /* activated */, 90 /* lastActivatedTimeOffset */);
-
-        startService();
-        assertActivated(true /* activated */);
-    }
-
-    @Test
-    public void twilightSchedule_whenStartedDuringNight_ifOffBeforeNight_turnsOn() {
-        setAutoModeTwilight(-60 /* sunsetOffset */, 60 /* sunriseOffset */);
-        setNightDisplayActivated(false /* activated */, -90 /* lastActivatedTimeOffset */);
-
-        startService();
-        assertActivated(true /* activated */);
-    }
-
-    @Test
-    public void twilightSchedule_whenStartedDuringNight_ifOffDuringNightInFuture_turnsOn() {
-        setAutoModeTwilight(-60 /* sunsetOffset */, 60 /* sunriseOffset */);
-        setNightDisplayActivated(false /* activated */, 30 /* lastActivatedTimeOffset */);
-
-        startService();
-        assertActivated(true /* activated */);
-    }
-
-    @Test
-    public void twilightSchedule_whenStartedDuringNight_ifOffDuringNightInPast_turnsOff() {
-        setAutoModeTwilight(-60 /* sunsetOffset */, 60 /* sunriseOffset */);
-        setNightDisplayActivated(false /* activated */, -30 /* lastActivatedTimeOffset */);
-
-        startService();
-        assertActivated(false /* activated */);
-    }
-
-    @Test
-    public void twilightSchedule_whenStartedDuringNight_ifOnAfterNight_turnsOn() {
-        setAutoModeTwilight(-60 /* sunsetOffset */, 60 /* sunriseOffset */);
-        setNightDisplayActivated(true /* activated */, 90 /* lastActivatedTimeOffset */);
-
-        startService();
-        assertActivated(true /* activated */);
-    }
-
-    @Test
-    public void twilightSchedule_whenStartedDuringNight_ifOnBeforeNight_turnsOn() {
-        setAutoModeTwilight(-60 /* sunsetOffset */, 60 /* sunriseOffset */);
-        setNightDisplayActivated(true /* activated */, -90 /* lastActivatedTimeOffset */);
-
-        startService();
-        assertActivated(true /* activated */);
-    }
-
-    @Test
-    public void twilightSchedule_whenStartedDuringNight_ifOnDuringNightInFuture_turnsOn() {
-        setAutoModeTwilight(-60 /* sunsetOffset */, 60 /* sunriseOffset */);
-        setNightDisplayActivated(true /* activated */, 30 /* lastActivatedTimeOffset */);
-
-        startService();
-        assertActivated(true /* activated */);
-    }
-
-    @Test
-    public void twilightSchedule_whenStartedDuringNight_ifOnDuringNightInPast_turnsOn() {
-        setAutoModeTwilight(-60 /* sunsetOffset */, 60 /* sunriseOffset */);
-        setNightDisplayActivated(true /* activated */, -30 /* lastActivatedTimeOffset */);
-
-        startService();
-        assertActivated(true /* activated */);
-    }
-
-    @Test
-    public void twilightSchedule_whenRebootedAfterNight_ifOffAfterNight_turnsOff() {
-        setAutoModeTwilight(-120 /* sunsetOffset */, -60 /* sunriseOffset */);
-        setNightDisplayActivated(false /* activated */, -30 /* lastActivatedTimeOffset */);
-
-        final TwilightState state = mTwilightManager.getLastTwilightState();
-        mTwilightManager.setTwilightState(null);
-
-        startService();
-        assertActivated(false /* activated */);
-
-        mTwilightManager.setTwilightState(state);
-        assertActivated(false /* activated */);
-    }
-
-    @Test
-    public void twilightSchedule_whenRebootedAfterNight_ifOffBeforeNight_turnsOff() {
-        setAutoModeTwilight(-120 /* sunsetOffset */, -60 /* sunriseOffset */);
-        setNightDisplayActivated(false /* activated */, -180 /* lastActivatedTimeOffset */);
-
-        final TwilightState state = mTwilightManager.getLastTwilightState();
-        mTwilightManager.setTwilightState(null);
-
-        startService();
-        assertActivated(false /* activated */);
-
-        mTwilightManager.setTwilightState(state);
-        assertActivated(false /* activated */);
-    }
-
-    @Test
-    public void twilightSchedule_whenRebootedAfterNight_ifOffDuringNight_turnsOff() {
-        setAutoModeTwilight(-120 /* sunsetOffset */, -60 /* sunriseOffset */);
-        setNightDisplayActivated(false /* activated */, -90 /* lastActivatedTimeOffset */);
-
-        final TwilightState state = mTwilightManager.getLastTwilightState();
-        mTwilightManager.setTwilightState(null);
-
-        startService();
-        assertActivated(false /* activated */);
-
-        mTwilightManager.setTwilightState(state);
-        assertActivated(false /* activated */);
-    }
-
-    @Test
-    public void twilightSchedule_whenRebootedAfterNight_ifOffInFuture_turnsOff() {
-        setAutoModeTwilight(-120 /* sunsetOffset */, -60 /* sunriseOffset */);
-        setNightDisplayActivated(false /* activated */, 30 /* lastActivatedTimeOffset */);
-
-        final TwilightState state = mTwilightManager.getLastTwilightState();
-        mTwilightManager.setTwilightState(null);
-
-        startService();
-        assertActivated(false /* activated */);
-
-        mTwilightManager.setTwilightState(state);
-        assertActivated(false /* activated */);
-    }
-
-    @Test
-    public void twilightSchedule_whenRebootedAfterNight_ifOnAfterNight_turnsOn() {
-        setAutoModeTwilight(-120 /* sunsetOffset */, -60 /* sunriseOffset */);
-        setNightDisplayActivated(true /* activated */, -30 /* lastActivatedTimeOffset */);
-
-        final TwilightState state = mTwilightManager.getLastTwilightState();
-        mTwilightManager.setTwilightState(null);
-
-        startService();
-        assertActivated(true /* activated */);
-
-        mTwilightManager.setTwilightState(state);
-        assertActivated(true /* activated */);
-    }
-
-    @Test
-    public void twilightSchedule_whenRebootedAfterNight_ifOnBeforeNight_turnsOff() {
-        setAutoModeTwilight(-120 /* sunsetOffset */, -60 /* sunriseOffset */);
-        setNightDisplayActivated(true /* activated */, -180 /* lastActivatedTimeOffset */);
-
-        final TwilightState state = mTwilightManager.getLastTwilightState();
-        mTwilightManager.setTwilightState(null);
-
-        startService();
-        assertActivated(true /* activated */);
-
-        mTwilightManager.setTwilightState(state);
-        assertActivated(false /* activated */);
-    }
-
-    @Test
-    public void twilightSchedule_whenRebootedAfterNight_ifOnDuringNight_turnsOff() {
-        setAutoModeTwilight(-120 /* sunsetOffset */, -60 /* sunriseOffset */);
-        setNightDisplayActivated(true /* activated */, -90 /* lastActivatedTimeOffset */);
-
-        final TwilightState state = mTwilightManager.getLastTwilightState();
-        mTwilightManager.setTwilightState(null);
-
-        startService();
-        assertActivated(true /* activated */);
-
-        mTwilightManager.setTwilightState(state);
-        assertActivated(false /* activated */);
-    }
-
-    @Test
-    public void twilightSchedule_whenRebootedAfterNight_ifOnInFuture_turnsOff() {
-        setAutoModeTwilight(-120 /* sunsetOffset */, -60 /* sunriseOffset */);
-        setNightDisplayActivated(true /* activated */, 30 /* lastActivatedTimeOffset */);
-
-        final TwilightState state = mTwilightManager.getLastTwilightState();
-        mTwilightManager.setTwilightState(null);
-
-        startService();
-        assertActivated(true /* activated */);
-
-        mTwilightManager.setTwilightState(state);
-        assertActivated(false /* activated */);
-    }
-
-    @Test
-    public void twilightSchedule_whenRebootedBeforeNight_ifOffAfterNight_turnsOff() {
-        setAutoModeTwilight(60 /* sunsetOffset */, 120 /* sunriseOffset */);
-        setNightDisplayActivated(false /* activated */, 180 /* lastActivatedTimeOffset */);
-
-        final TwilightState state = mTwilightManager.getLastTwilightState();
-        mTwilightManager.setTwilightState(null);
-
-        startService();
-        assertActivated(false /* activated */);
-
-        mTwilightManager.setTwilightState(state);
-        assertActivated(false /* activated */);
-    }
-
-    @Test
-    public void twilightSchedule_whenRebootedBeforeNight_ifOffBeforeNight_turnsOff() {
-        setAutoModeTwilight(60 /* sunsetOffset */, 120 /* sunriseOffset */);
-        setNightDisplayActivated(false /* activated */, 30 /* lastActivatedTimeOffset */);
-
-        final TwilightState state = mTwilightManager.getLastTwilightState();
-        mTwilightManager.setTwilightState(null);
-
-        startService();
-        assertActivated(false /* activated */);
-
-        mTwilightManager.setTwilightState(state);
-        assertActivated(false /* activated */);
-    }
-
-    @Test
-    public void twilightSchedule_whenRebootedBeforeNight_ifOffDuringNight_turnsOff() {
-        setAutoModeTwilight(60 /* sunsetOffset */, 120 /* sunriseOffset */);
-        setNightDisplayActivated(false /* activated */, 90 /* lastActivatedTimeOffset */);
-
-        final TwilightState state = mTwilightManager.getLastTwilightState();
-        mTwilightManager.setTwilightState(null);
-
-        startService();
-        assertActivated(false /* activated */);
-
-        mTwilightManager.setTwilightState(state);
-        assertActivated(false /* activated */);
-    }
-
-    @Test
-    public void twilightSchedule_whenRebootedBeforeNight_ifOffInPast_turnsOff() {
-        setAutoModeTwilight(60 /* sunsetOffset */, 120 /* sunriseOffset */);
-        setNightDisplayActivated(false /* activated */, -30 /* lastActivatedTimeOffset */);
-
-        final TwilightState state = mTwilightManager.getLastTwilightState();
-        mTwilightManager.setTwilightState(null);
-
-        startService();
-        assertActivated(false /* activated */);
-
-        mTwilightManager.setTwilightState(state);
-        assertActivated(false /* activated */);
-    }
-
-    @Test
-    public void twilightSchedule_whenRebootedBeforeNight_ifOnAfterNight_turnsOff() {
-        setAutoModeTwilight(60 /* sunsetOffset */, 120 /* sunriseOffset */);
-        setNightDisplayActivated(true /* activated */, 180 /* lastActivatedTimeOffset */);
-
-        final TwilightState state = mTwilightManager.getLastTwilightState();
-        mTwilightManager.setTwilightState(null);
-
-        startService();
-        assertActivated(true /* activated */);
-
-        mTwilightManager.setTwilightState(state);
-        assertActivated(false /* activated */);
-    }
-
-    @Test
-    public void twilightSchedule_whenRebootedBeforeNight_ifOnBeforeNight_turnsOff() {
-        setAutoModeTwilight(60 /* sunsetOffset */, 120 /* sunriseOffset */);
-        setNightDisplayActivated(true /* activated */, 30 /* lastActivatedTimeOffset */);
-
-        final TwilightState state = mTwilightManager.getLastTwilightState();
-        mTwilightManager.setTwilightState(null);
-
-        startService();
-        assertActivated(true /* activated */);
-
-        mTwilightManager.setTwilightState(state);
-        assertActivated(false /* activated */);
-    }
-
-    @Test
-    public void twilightSchedule_whenRebootedBeforeNight_ifOnDuringNight_turnsOff() {
-        setAutoModeTwilight(60 /* sunsetOffset */, 120 /* sunriseOffset */);
-        setNightDisplayActivated(true /* activated */, 90 /* lastActivatedTimeOffset */);
-
-        final TwilightState state = mTwilightManager.getLastTwilightState();
-        mTwilightManager.setTwilightState(null);
-
-        startService();
-        assertActivated(true /* activated */);
-
-        mTwilightManager.setTwilightState(state);
-        assertActivated(false /* activated */);
-    }
-
-    @Test
-    public void twilightSchedule_whenRebootedBeforeNight_ifOnInPast_turnsOn() {
-        setAutoModeTwilight(60 /* sunsetOffset */, 120 /* sunriseOffset */);
-        setNightDisplayActivated(true /* activated */, -30 /* lastActivatedTimeOffset */);
-
-        final TwilightState state = mTwilightManager.getLastTwilightState();
-        mTwilightManager.setTwilightState(null);
-
-        startService();
-        assertActivated(true /* activated */);
-
-        mTwilightManager.setTwilightState(state);
-        assertActivated(true /* activated */);
-    }
-
-    @Test
-    public void twilightSchedule_whenRebootedDuringNight_ifOffAfterNight_turnsOn() {
-        setAutoModeTwilight(-60 /* sunsetOffset */, 60 /* sunriseOffset */);
-        setNightDisplayActivated(false /* activated */, 90 /* lastActivatedTimeOffset */);
-
-        final TwilightState state = mTwilightManager.getLastTwilightState();
-        mTwilightManager.setTwilightState(null);
-
-        startService();
-        assertActivated(false /* activated */);
-
-        mTwilightManager.setTwilightState(state);
-        assertActivated(true /* activated */);
-    }
-
-    @Test
-    public void twilightSchedule_whenRebootedDuringNight_ifOffBeforeNight_turnsOn() {
-        setAutoModeTwilight(-60 /* sunsetOffset */, 60 /* sunriseOffset */);
-        setNightDisplayActivated(false /* activated */, -90 /* lastActivatedTimeOffset */);
-
-        final TwilightState state = mTwilightManager.getLastTwilightState();
-        mTwilightManager.setTwilightState(null);
-
-        startService();
-        assertActivated(false /* activated */);
-
-        mTwilightManager.setTwilightState(state);
-        assertActivated(true /* activated */);
-    }
-
-    @Test
-    public void twilightSchedule_whenRebootedDuringNight_ifOffDuringNightInFuture_turnsOn() {
-        setAutoModeTwilight(-60 /* sunsetOffset */, 60 /* sunriseOffset */);
-        setNightDisplayActivated(false /* activated */, 30 /* lastActivatedTimeOffset */);
-
-        final TwilightState state = mTwilightManager.getLastTwilightState();
-        mTwilightManager.setTwilightState(null);
-
-        startService();
-        assertActivated(false /* activated */);
-
-        mTwilightManager.setTwilightState(state);
-        assertActivated(true /* activated */);
-    }
-
-    @Test
-    public void twilightSchedule_whenRebootedDuringNight_ifOffDuringNightInPast_turnsOff() {
-        setAutoModeTwilight(-60 /* sunsetOffset */, 60 /* sunriseOffset */);
-        setNightDisplayActivated(false /* activated */, -30 /* lastActivatedTimeOffset */);
-
-        final TwilightState state = mTwilightManager.getLastTwilightState();
-        mTwilightManager.setTwilightState(null);
-
-        startService();
-        assertActivated(false /* activated */);
-
-        mTwilightManager.setTwilightState(state);
-        assertActivated(false /* activated */);
-    }
-
-    @Test
-    public void twilightSchedule_whenRebootedDuringNight_ifOnAfterNight_turnsOn() {
-        setAutoModeTwilight(-60 /* sunsetOffset */, 60 /* sunriseOffset */);
-        setNightDisplayActivated(true /* activated */, 90 /* lastActivatedTimeOffset */);
-
-        final TwilightState state = mTwilightManager.getLastTwilightState();
-        mTwilightManager.setTwilightState(null);
-
-        startService();
-        assertActivated(true /* activated */);
-
-        mTwilightManager.setTwilightState(state);
-        assertActivated(true /* activated */);
-    }
-
-    @Test
-    public void twilightSchedule_whenRebootedDuringNight_ifOnBeforeNight_turnsOn() {
-        setAutoModeTwilight(-60 /* sunsetOffset */, 60 /* sunriseOffset */);
-        setNightDisplayActivated(true /* activated */, -90 /* lastActivatedTimeOffset */);
-
-        final TwilightState state = mTwilightManager.getLastTwilightState();
-        mTwilightManager.setTwilightState(null);
-
-        startService();
-        assertActivated(true /* activated */);
-
-        mTwilightManager.setTwilightState(state);
-        assertActivated(true /* activated */);
-    }
-
-    @Test
-    public void twilightSchedule_whenRebootedDuringNight_ifOnDuringNightInFuture_turnsOn() {
-        setAutoModeTwilight(-60 /* sunsetOffset */, 60 /* sunriseOffset */);
-        setNightDisplayActivated(true /* activated */, 30 /* lastActivatedTimeOffset */);
-
-        final TwilightState state = mTwilightManager.getLastTwilightState();
-        mTwilightManager.setTwilightState(null);
-
-        startService();
-        assertActivated(true /* activated */);
-
-        mTwilightManager.setTwilightState(state);
-        assertActivated(true /* activated */);
-    }
-
-    @Test
-    public void twilightSchedule_whenRebootedDuringNight_ifOnDuringNightInPast_turnsOn() {
-        setAutoModeTwilight(-60 /* sunsetOffset */, 60 /* sunriseOffset */);
-        setNightDisplayActivated(true /* activated */, -30 /* lastActivatedTimeOffset */);
-
-        final TwilightState state = mTwilightManager.getLastTwilightState();
-        mTwilightManager.setTwilightState(null);
-
-        startService();
-        assertActivated(true /* activated */);
-
-        mTwilightManager.setTwilightState(state);
-        assertActivated(true /* activated */);
-    }
-
-    @Test
-    public void accessibility_colorInversion_transformActivated() {
-        if (!mContext.getResources().getConfiguration().isScreenWideColorGamut()) {
-            return;
-        }
-
-        setAccessibilityColorInversion(true);
-        setColorMode(ColorDisplayManager.COLOR_MODE_NATURAL);
-
-        startService();
-        assertUserColorMode(ColorDisplayManager.COLOR_MODE_NATURAL);
-        assertActiveColorMode(mContext.getResources().getInteger(
-                R.integer.config_accessibilityColorMode));
-    }
-
-    @Test
-    public void accessibility_colorCorrection_transformActivated() {
-        if (!mContext.getResources().getConfiguration().isScreenWideColorGamut()) {
-            return;
-        }
-
-        setAccessibilityColorCorrection(true);
-        setColorMode(ColorDisplayManager.COLOR_MODE_NATURAL);
-
-        startService();
-        assertUserColorMode(ColorDisplayManager.COLOR_MODE_NATURAL);
-        assertActiveColorMode(mContext.getResources().getInteger(
-                R.integer.config_accessibilityColorMode));
-    }
-
-    @Test
-    public void accessibility_all_transformActivated() {
-        if (!mContext.getResources().getConfiguration().isScreenWideColorGamut()) {
-            return;
-        }
-
-        setAccessibilityColorCorrection(true);
-        setAccessibilityColorInversion(true);
-        setColorMode(ColorDisplayManager.COLOR_MODE_NATURAL);
-
-        startService();
-        assertUserColorMode(ColorDisplayManager.COLOR_MODE_NATURAL);
-        assertActiveColorMode(mContext.getResources().getInteger(
-                R.integer.config_accessibilityColorMode));
-    }
-
-    @Test
-    public void accessibility_none_transformActivated() {
-        if (!mContext.getResources().getConfiguration().isScreenWideColorGamut()) {
-            return;
-        }
-
-        setAccessibilityColorCorrection(false);
-        setAccessibilityColorInversion(false);
-        setColorMode(ColorDisplayManager.COLOR_MODE_NATURAL);
-
-        startService();
-        assertUserColorMode(ColorDisplayManager.COLOR_MODE_NATURAL);
-        assertActiveColorMode(ColorDisplayManager.COLOR_MODE_NATURAL);
-    }
-
-    @Test
-    public void displayWhiteBalance_enabled() {
-        setDisplayWhiteBalanceEnabled(true);
-        setNightDisplayActivated(false /* activated */, -30 /* lastActivatedTimeOffset */);
-        mBinderService.setColorMode(ColorDisplayManager.COLOR_MODE_NATURAL);
-        startService();
-        assertDwbActive(true);
-    }
-
-    @Test
-    public void displayWhiteBalance_disabledAfterNightDisplayEnabled() {
-        setDisplayWhiteBalanceEnabled(true);
-        startService();
-        setNightDisplayActivated(true /* activated */, -30 /* lastActivatedTimeOffset */);
-
-        /* Since we are using FakeSettingsProvider which could not trigger observer change,
-         * force an update here.*/
-        mCds.updateDisplayWhiteBalanceStatus();
-        assertDwbActive(false);
-    }
-
-    @Test
-    public void displayWhiteBalance_enabledAfterNightDisplayDisabled() {
-        setDisplayWhiteBalanceEnabled(true);
-        startService();
-        setNightDisplayActivated(true /* activated */, -30 /* lastActivatedTimeOffset */);
-
-        mCds.updateDisplayWhiteBalanceStatus();
-        assertDwbActive(false);
-
-        setNightDisplayActivated(false /* activated */, -30 /* lastActivatedTimeOffset */);
-        mCds.updateDisplayWhiteBalanceStatus();
-        assertDwbActive(true);
-    }
-
-    @Test
-    public void displayWhiteBalance_enabledAfterLinearColorModeSelected() {
-        if (!isColorModeValid(ColorDisplayManager.COLOR_MODE_SATURATED)) {
-            return;
-        }
-        setDisplayWhiteBalanceEnabled(true);
-        mBinderService.setColorMode(ColorDisplayManager.COLOR_MODE_SATURATED);
-        startService();
-        assertDwbActive(false);
-
-        mBinderService.setColorMode(ColorDisplayManager.COLOR_MODE_NATURAL);
-        mCds.updateDisplayWhiteBalanceStatus();
-        assertDwbActive(true);
-    }
-
-    @Test
-    public void displayWhiteBalance_disabledWhileAccessibilityColorCorrectionEnabled() {
-        setDisplayWhiteBalanceEnabled(true);
-        setAccessibilityColorCorrection(true);
-        startService();
-        assertDwbActive(false);
-
-        setAccessibilityColorCorrection(false);
-        mCds.updateDisplayWhiteBalanceStatus();
-        assertDwbActive(true);
-    }
-
-    @Test
-    public void displayWhiteBalance_disabledWhileAccessibilityColorInversionEnabled() {
-        setDisplayWhiteBalanceEnabled(true);
-        setAccessibilityColorInversion(true);
-        startService();
-        assertDwbActive(false);
-
-        setAccessibilityColorInversion(false);
-        mCds.updateDisplayWhiteBalanceStatus();
-        assertDwbActive(true);
-    }
-
-    @Test
-    public void compositionColorSpaces_noResources() {
-        when(mResourcesSpy.getIntArray(R.array.config_displayCompositionColorModes))
-            .thenReturn(new int[] {});
-        when(mResourcesSpy.getIntArray(R.array.config_displayCompositionColorSpaces))
-            .thenReturn(new int[] {});
-        setColorMode(ColorDisplayManager.COLOR_MODE_NATURAL);
-        startService();
-        verify(mDisplayTransformManager).setColorMode(
-                eq(ColorDisplayManager.COLOR_MODE_NATURAL), any(), eq(Display.COLOR_MODE_INVALID));
-    }
-
-    @Test
-    public void compositionColorSpaces_invalidResources() {
-        when(mResourcesSpy.getIntArray(R.array.config_displayCompositionColorModes))
-            .thenReturn(new int[] {
-               ColorDisplayManager.COLOR_MODE_NATURAL,
-               // Missing second color mode
-            });
-        when(mResourcesSpy.getIntArray(R.array.config_displayCompositionColorSpaces))
-            .thenReturn(new int[] {
-               Display.COLOR_MODE_SRGB,
-               Display.COLOR_MODE_DISPLAY_P3
-            });
-        setColorMode(ColorDisplayManager.COLOR_MODE_NATURAL);
-        startService();
-        verify(mDisplayTransformManager).setColorMode(
-                eq(ColorDisplayManager.COLOR_MODE_NATURAL), any(), eq(Display.COLOR_MODE_INVALID));
-    }
-
-    @Test
-    public void compositionColorSpaces_validResources_validColorMode() {
-        when(mResourcesSpy.getIntArray(R.array.config_displayCompositionColorModes))
-            .thenReturn(new int[] {
-               ColorDisplayManager.COLOR_MODE_NATURAL
-            });
-        when(mResourcesSpy.getIntArray(R.array.config_displayCompositionColorSpaces))
-            .thenReturn(new int[] {
-               Display.COLOR_MODE_SRGB,
-            });
-        setColorMode(ColorDisplayManager.COLOR_MODE_NATURAL);
-        startService();
-        verify(mDisplayTransformManager).setColorMode(
-                eq(ColorDisplayManager.COLOR_MODE_NATURAL), any(), eq(Display.COLOR_MODE_SRGB));
-    }
-
-    @Test
-    public void compositionColorSpaces_validResources_invalidColorMode() {
-        when(mResourcesSpy.getIntArray(R.array.config_displayCompositionColorModes))
-            .thenReturn(new int[] {
-               ColorDisplayManager.COLOR_MODE_NATURAL
-            });
-        when(mResourcesSpy.getIntArray(R.array.config_displayCompositionColorSpaces))
-            .thenReturn(new int[] {
-               Display.COLOR_MODE_SRGB,
-            });
-        setColorMode(ColorDisplayManager.COLOR_MODE_BOOSTED);
-        startService();
-        verify(mDisplayTransformManager).setColorMode(
-                eq(ColorDisplayManager.COLOR_MODE_BOOSTED), any(), eq(Display.COLOR_MODE_INVALID));
-    }
-
-    @Test
-    public void getColorMode_noAvailableModes_returnsNotSet() {
-        when(mResourcesSpy.getIntArray(R.array.config_availableColorModes))
-                .thenReturn(new int[] {});
-        startService();
-        verify(mDisplayTransformManager, never()).setColorMode(anyInt(), any(), anyInt());
-        assertThat(mBinderService.getColorMode()).isEqualTo(-1);
-    }
-
-    /**
-     * Configures Night display to use a custom schedule.
-     *
-     * @param startTimeOffset the offset relative to now to activate Night display (in minutes)
-     * @param endTimeOffset the offset relative to now to deactivate Night display (in minutes)
-     */
-    private void setAutoModeCustom(int startTimeOffset, int endTimeOffset) {
-        mBinderService.setNightDisplayAutoMode(ColorDisplayManager.AUTO_MODE_CUSTOM_TIME);
-        mBinderService.setNightDisplayCustomStartTime(
-                new Time(getLocalTimeRelativeToNow(startTimeOffset)));
-        mBinderService
-                .setNightDisplayCustomEndTime(new Time(getLocalTimeRelativeToNow(endTimeOffset)));
-    }
-
-    /**
-     * Configures Night display to use the twilight schedule.
-     *
-     * @param sunsetOffset the offset relative to now for sunset (in minutes)
-     * @param sunriseOffset the offset relative to now for sunrise (in minutes)
-     */
-    private void setAutoModeTwilight(int sunsetOffset, int sunriseOffset) {
-        mBinderService.setNightDisplayAutoMode(ColorDisplayManager.AUTO_MODE_TWILIGHT);
-        mTwilightManager.setTwilightState(
-                getTwilightStateRelativeToNow(sunsetOffset, sunriseOffset));
-    }
-
-    /**
-     * Configures the Night display activated state.
-     *
-     * @param activated {@code true} if Night display should be activated
-     * @param lastActivatedTimeOffset the offset relative to now to record that Night display was
-     * activated (in minutes)
-     */
-    private void setNightDisplayActivated(boolean activated, int lastActivatedTimeOffset) {
-        mBinderService.setNightDisplayActivated(activated);
-        Secure.putStringForUser(mContext.getContentResolver(),
-                Secure.NIGHT_DISPLAY_LAST_ACTIVATED_TIME,
-                LocalDateTime.now().plusMinutes(lastActivatedTimeOffset).toString(),
-                mUserId);
-    }
-
-    /**
-     * Configures the Accessibility color correction setting state.
-     *
-     * @param state {@code true} if color inversion should be activated
-     */
-    private void setAccessibilityColorCorrection(boolean state) {
-        Secure.putIntForUser(mContext.getContentResolver(),
-                Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED, state ? 1 : 0, mUserId);
-    }
-
-    /**
-     * Configures the Accessibility color inversion setting state.
-     *
-     * @param state {@code true} if color inversion should be activated
-     */
-    private void setAccessibilityColorInversion(boolean state) {
-        Secure.putIntForUser(mContext.getContentResolver(),
-                Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED, state ? 1 : 0, mUserId);
-    }
-
-    /**
-     * Configures the Display White Balance setting state.
-     *
-     * @param enabled {@code true} if display white balance should be enabled
-     */
-    private void setDisplayWhiteBalanceEnabled(boolean enabled) {
-        Secure.putIntForUser(mContext.getContentResolver(),
-                Secure.DISPLAY_WHITE_BALANCE_ENABLED, enabled ? 1 : 0, mUserId);
-    }
-
-    /**
-     * Configures color mode.
-     */
-    private void setColorMode(int colorMode) {
-        mBinderService.setColorMode(colorMode);
-    }
-
-    /**
-     * Returns whether the color mode is valid on the device the tests are running on.
-     */
-    private boolean isColorModeValid(int mode) {
-        final int[] availableColorModes = mContext.getResources().getIntArray(
-                R.array.config_availableColorModes);
-        if (availableColorModes != null) {
-            for (int availableMode : availableColorModes) {
-                if (mode == availableMode) {
-                    return true;
-                }
-            }
-        }
-        return false;
-    }
-
-    /**
-     * Convenience method to start {@link #mCds}.
-     */
-    private void startService() {
-        Secure.putIntForUser(mContext.getContentResolver(), Secure.USER_SETUP_COMPLETE, 1, mUserId);
-
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
-            mCds.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
-            mCds.onUserChanged(mUserId);
-        });
-    }
-
-    /**
-     * Convenience method for asserting whether Night display should be activated.
-     *
-     * @param activated the expected activated state of Night display
-     */
-    private void assertActivated(boolean activated) {
-        assertWithMessage("Incorrect Night display activated state")
-                .that(mBinderService.isNightDisplayActivated())
-                .isEqualTo(activated);
-    }
-
-    /**
-     * Convenience method for asserting that the active color mode matches expectation.
-     *
-     * @param mode the expected active color mode.
-     */
-    private void assertActiveColorMode(int mode) {
-        assertWithMessage("Unexpected color mode setting")
-                .that(mBinderService.getColorMode())
-                .isEqualTo(mode);
-    }
-
-    /**
-     * Convenience method for asserting that the user chosen color mode matches expectation.
-     *
-     * @param mode the expected color mode setting.
-     */
-    private void assertUserColorMode(int mode) {
-        final int actualMode = System.getIntForUser(mContext.getContentResolver(),
-                System.DISPLAY_COLOR_MODE, -1, mUserId);
-        assertWithMessage("Unexpected color mode setting")
-                .that(actualMode)
-                .isEqualTo(mode);
-    }
-
-    /**
-     * Convenience method for asserting that the DWB active status matches expectation.
-     *
-     * @param enabled the expected active status.
-     */
-    private void assertDwbActive(boolean enabled) {
-        assertWithMessage("Incorrect Display White Balance state")
-                .that(mCds.mDisplayWhiteBalanceTintController.isActivated())
-                .isEqualTo(enabled);
-    }
-
-    /**
-     * Convenience for making a {@link LocalTime} instance with an offset relative to now.
-     *
-     * @param offsetMinutes the offset relative to now (in minutes)
-     * @return the LocalTime instance
-     */
-    private static LocalTime getLocalTimeRelativeToNow(int offsetMinutes) {
-        final Calendar c = Calendar.getInstance();
-        c.add(Calendar.MINUTE, offsetMinutes);
-        return LocalTime.of(c.get(Calendar.HOUR_OF_DAY), c.get(Calendar.MINUTE));
-    }
-
-    /**
-     * Convenience for making a {@link TwilightState} instance with sunrise/sunset relative to now.
-     *
-     * @param sunsetOffset the offset relative to now for sunset (in minutes)
-     * @param sunriseOffset the offset relative to now for sunrise (in minutes)
-     * @return the TwilightState instance
-     */
-    private static TwilightState getTwilightStateRelativeToNow(int sunsetOffset,
-            int sunriseOffset) {
-        final LocalTime sunset = getLocalTimeRelativeToNow(sunsetOffset);
-        final LocalTime sunrise = getLocalTimeRelativeToNow(sunriseOffset);
-
-        final LocalDateTime now = LocalDateTime.now();
-        final ZoneId zoneId = ZoneId.systemDefault();
-
-        long sunsetMillis = ColorDisplayService.getDateTimeBefore(sunset, now)
-                .atZone(zoneId)
-                .toInstant()
-                .toEpochMilli();
-        long sunriseMillis = ColorDisplayService.getDateTimeBefore(sunrise, now)
-                .atZone(zoneId)
-                .toInstant()
-                .toEpochMilli();
-        if (sunsetMillis < sunriseMillis) {
-            sunsetMillis = ColorDisplayService.getDateTimeAfter(sunset, now)
-                    .atZone(zoneId)
-                    .toInstant()
-                    .toEpochMilli();
-        } else {
-            sunriseMillis = ColorDisplayService.getDateTimeAfter(sunrise, now)
-                    .atZone(zoneId)
-                    .toInstant()
-                    .toEpochMilli();
-        }
-
-        return new TwilightState(sunriseMillis, sunsetMillis);
-    }
-
-    private static class MockTwilightManager implements TwilightManager {
-
-        private final Map<TwilightListener, Handler> mListeners = new HashMap<>();
-        private TwilightState mTwilightState;
-
-        /**
-         * Updates the TwilightState and notifies any registered listeners.
-         *
-         * @param state the new TwilightState to use
-         */
-        void setTwilightState(TwilightState state) {
-            synchronized (mListeners) {
-                mTwilightState = state;
-
-                final CountDownLatch latch = new CountDownLatch(mListeners.size());
-                for (Map.Entry<TwilightListener, Handler> entry : mListeners.entrySet()) {
-                    entry.getValue().post(new Runnable() {
-                        @Override
-                        public void run() {
-                            entry.getKey().onTwilightStateChanged(state);
-                            latch.countDown();
-                        }
-                    });
-                }
-
-                try {
-                    latch.await(5, TimeUnit.SECONDS);
-                } catch (InterruptedException e) {
-                    throw new RuntimeException(e);
-                }
-            }
-        }
-
-        @Override
-        public void registerListener(@NonNull TwilightListener listener, @NonNull Handler handler) {
-            synchronized (mListeners) {
-                mListeners.put(listener, handler);
-            }
-        }
-
-        @Override
-        public void unregisterListener(@NonNull TwilightListener listener) {
-            synchronized (mListeners) {
-                mListeners.remove(listener);
-            }
-        }
-
-        @Override
-        public TwilightState getLastTwilightState() {
-            return mTwilightState;
-        }
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java b/services/tests/servicestests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java
deleted file mode 100644
index e0bef1a..0000000
--- a/services/tests/servicestests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.color;
-
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import static org.mockito.Mockito.mock;
-
-import android.hardware.display.DisplayManagerInternal;
-
-import androidx.test.InstrumentationRegistry;
-
-import org.junit.Before;
-import org.junit.Test;
-
-import java.util.Arrays;
-
-public class DisplayWhiteBalanceTintControllerTest {
-
-    private DisplayWhiteBalanceTintController mDisplayWhiteBalanceTintController;
-
-    @Before
-    public void setUp() {
-        DisplayManagerInternal displayManagerInternal = mock(DisplayManagerInternal.class);
-        mDisplayWhiteBalanceTintController =
-                new DisplayWhiteBalanceTintController(displayManagerInternal);
-        mDisplayWhiteBalanceTintController.setUp(InstrumentationRegistry.getContext(), true);
-        mDisplayWhiteBalanceTintController.setActivated(true);
-    }
-
-    @Test
-    public void displayWhiteBalance_setTemperatureOverMax() {
-        final int max = mDisplayWhiteBalanceTintController.mTemperatureMax;
-        mDisplayWhiteBalanceTintController.setMatrix(max + 1);
-        assertWithMessage("Unexpected temperature set")
-                .that(mDisplayWhiteBalanceTintController.mCurrentColorTemperature)
-                .isEqualTo(max);
-    }
-
-    @Test
-    public void displayWhiteBalance_setTemperatureBelowMin() {
-        final int min = mDisplayWhiteBalanceTintController.mTemperatureMin;
-        mDisplayWhiteBalanceTintController.setMatrix(min - 1);
-        assertWithMessage("Unexpected temperature set")
-                .that(mDisplayWhiteBalanceTintController.mCurrentColorTemperature)
-                .isEqualTo(min);
-    }
-
-    @Test
-    public void displayWhiteBalance_setValidTemperature() {
-        final int colorTemperature = (mDisplayWhiteBalanceTintController.mTemperatureMin
-                + mDisplayWhiteBalanceTintController.mTemperatureMax) / 2;
-        mDisplayWhiteBalanceTintController.setMatrix(colorTemperature);
-
-        assertWithMessage("Unexpected temperature set")
-                .that(mDisplayWhiteBalanceTintController.mCurrentColorTemperature)
-                .isEqualTo(colorTemperature);
-    }
-
-    @Test
-    public void displayWhiteBalance_setMatrixValidDwbCalculation() {
-        float[] currentMatrix = mDisplayWhiteBalanceTintController.getMatrix();
-        float[] oldMatrix = Arrays.copyOf(currentMatrix, currentMatrix.length);
-
-        mDisplayWhiteBalanceTintController
-                .setMatrix(mDisplayWhiteBalanceTintController.mCurrentColorTemperature + 1);
-        assertWithMessage("DWB matrix did not change when setting a new temperature")
-                .that(Arrays.equals(oldMatrix, currentMatrix))
-                .isFalse();
-    }
-
-    @Test
-    public void displayWhiteBalance_setMatrixInvalidDwbCalculation() {
-        Arrays.fill(mDisplayWhiteBalanceTintController.mDisplayNominalWhiteXYZ, 0);
-        mDisplayWhiteBalanceTintController
-            .setMatrix(mDisplayWhiteBalanceTintController.mCurrentColorTemperature + 1);
-        assertWithMessage("DWB matrix not set to identity after an invalid DWB calculation")
-                .that(Arrays.equals(mDisplayWhiteBalanceTintController.getMatrix(),
-                    new float[] {
-                        1, 0, 0, 0,
-                        0, 1, 0, 0,
-                        0, 0, 1, 0,
-                        0, 0, 0, 1
-                    })
-                ).isTrue();
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/display/whitebalance/AmbientLuxTest.java b/services/tests/servicestests/src/com/android/server/display/whitebalance/AmbientLuxTest.java
deleted file mode 100644
index ac97911..0000000
--- a/services/tests/servicestests/src/com/android/server/display/whitebalance/AmbientLuxTest.java
+++ /dev/null
@@ -1,559 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.whitebalance;
-
-import static org.junit.Assert.assertEquals;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyLong;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.when;
-
-import android.content.ContextWrapper;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.hardware.Sensor;
-import android.hardware.SensorManager;
-import android.os.Handler;
-import android.os.Looper;
-import android.util.TypedValue;
-
-import androidx.test.InstrumentationRegistry;
-
-import com.android.internal.R;
-import com.android.server.LocalServices;
-import com.android.server.display.TestUtils;
-import com.android.server.display.color.ColorDisplayService;
-import com.android.server.display.utils.AmbientFilter;
-import com.android.server.display.utils.AmbientFilterStubber;
-
-import com.google.common.collect.ImmutableList;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.stubbing.Answer;
-
-import java.util.List;
-
-@RunWith(JUnit4.class)
-public final class AmbientLuxTest {
-
-    private static final float ALLOWED_ERROR_DELTA = 0.001f;
-    private static final int AMBIENT_COLOR_TYPE = 20705;
-    private static final String AMBIENT_COLOR_TYPE_STR = "colorSensoryDensoryDoc";
-    private static final float LOW_LIGHT_AMBIENT_COLOR_TEMPERATURE = 5432.1f;
-    private static final float HIGH_LIGHT_AMBIENT_COLOR_TEMPERATURE = 3456.7f;
-
-    private Handler mHandler = new Handler(Looper.getMainLooper());
-    private Sensor mLightSensor;
-    private Sensor mAmbientColorSensor;
-    private ContextWrapper mContextSpy;
-    private Resources mResourcesSpy;
-
-    @Mock private SensorManager mSensorManagerMock;
-
-    @Mock private TypedArray mBrightnesses;
-    @Mock private TypedArray mBiases;
-    @Mock private TypedArray mHighLightBrightnesses;
-    @Mock private TypedArray mHighLightBiases;
-    @Mock private TypedArray mAmbientColorTemperatures;
-    @Mock private TypedArray mDisplayColorTemperatures;
-    @Mock private TypedArray mStrongAmbientColorTemperatures;
-    @Mock private TypedArray mStrongDisplayColorTemperatures;
-    @Mock private ColorDisplayService.ColorDisplayServiceInternal mColorDisplayServiceInternalMock;
-
-    @Before
-    public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
-        mLightSensor = TestUtils.createSensor(Sensor.TYPE_LIGHT, null);
-        mAmbientColorSensor = TestUtils.createSensor(AMBIENT_COLOR_TYPE, AMBIENT_COLOR_TYPE_STR);
-        mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getContext()));
-        mResourcesSpy = spy(mContextSpy.getResources());
-        when(mContextSpy.getResources()).thenReturn(mResourcesSpy);
-        when(mSensorManagerMock.getDefaultSensor(Sensor.TYPE_LIGHT)).thenReturn(mLightSensor);
-        final List<Sensor> sensorList = ImmutableList.of(mLightSensor, mAmbientColorSensor);
-        when(mSensorManagerMock.getSensorList(Sensor.TYPE_ALL)).thenReturn(sensorList);
-        when(mResourcesSpy.getString(
-                R.string.config_displayWhiteBalanceColorTemperatureSensorName))
-                .thenReturn(AMBIENT_COLOR_TYPE_STR);
-        when(mResourcesSpy.getInteger(
-                R.integer.config_displayWhiteBalanceDecreaseDebounce))
-                .thenReturn(0);
-        when(mResourcesSpy.getInteger(
-                R.integer.config_displayWhiteBalanceIncreaseDebounce))
-                .thenReturn(0);
-        mockResourcesFloat(R.dimen.config_displayWhiteBalanceLowLightAmbientColorTemperature,
-                LOW_LIGHT_AMBIENT_COLOR_TEMPERATURE);
-        mockResourcesFloat(R.dimen.config_displayWhiteBalanceHighLightAmbientColorTemperature,
-                HIGH_LIGHT_AMBIENT_COLOR_TEMPERATURE);
-        when(mResourcesSpy.obtainTypedArray(
-                R.array.config_displayWhiteBalanceAmbientColorTemperatures))
-                .thenReturn(mAmbientColorTemperatures);
-        when(mResourcesSpy.obtainTypedArray(
-                R.array.config_displayWhiteBalanceDisplayColorTemperatures))
-                .thenReturn(mDisplayColorTemperatures);
-        when(mResourcesSpy.obtainTypedArray(
-                R.array.config_displayWhiteBalanceStrongAmbientColorTemperatures))
-                .thenReturn(mStrongAmbientColorTemperatures);
-        when(mResourcesSpy.obtainTypedArray(
-                R.array.config_displayWhiteBalanceStrongDisplayColorTemperatures))
-                .thenReturn(mStrongDisplayColorTemperatures);
-
-        when(mResourcesSpy.obtainTypedArray(
-                R.array.config_displayWhiteBalanceLowLightAmbientBrightnesses))
-                .thenReturn(mBrightnesses);
-        when(mResourcesSpy.obtainTypedArray(
-                R.array.config_displayWhiteBalanceLowLightAmbientBiases))
-                .thenReturn(mBiases);
-        when(mResourcesSpy.obtainTypedArray(
-                R.array.config_displayWhiteBalanceHighLightAmbientBrightnesses))
-                .thenReturn(mHighLightBrightnesses);
-        when(mResourcesSpy.obtainTypedArray(
-                R.array.config_displayWhiteBalanceHighLightAmbientBiases))
-                .thenReturn(mHighLightBiases);
-        mockThrottler();
-        LocalServices.removeServiceForTest(ColorDisplayService.ColorDisplayServiceInternal.class);
-        LocalServices.addService(ColorDisplayService.ColorDisplayServiceInternal.class,
-                mColorDisplayServiceInternalMock);
-    }
-
-    @Test
-    public void testCalculateAdjustedBrightnessNits() {
-        doReturn(0.9f).when(mColorDisplayServiceInternalMock).getDisplayWhiteBalanceLuminance();
-        DisplayWhiteBalanceController controller =
-                DisplayWhiteBalanceFactory.create(mHandler, mSensorManagerMock, mResourcesSpy);
-        final float adjustedNits = controller.calculateAdjustedBrightnessNits(500f);
-        assertEquals(/* expected= */ 550f, adjustedNits, /* delta= */ 0.001);
-    }
-
-    @Test
-    public void testNoSpline() throws Exception {
-        setBrightnesses();
-        setBiases();
-
-        DisplayWhiteBalanceController controller =
-                DisplayWhiteBalanceFactory.create(mHandler, mSensorManagerMock, mResourcesSpy);
-        final float ambientColorTemperature = 8000.0f;
-        setEstimatedColorTemperature(controller, ambientColorTemperature);
-        controller.mBrightnessFilter = spy(new AmbientFilterStubber());
-
-        for (float luxOverride = 0.1f; luxOverride <= 10000; luxOverride *= 10) {
-            setEstimatedBrightnessAndUpdate(controller, luxOverride);
-            assertEquals(controller.mPendingAmbientColorTemperature,
-                    ambientColorTemperature, 0.001);
-        }
-    }
-
-    @Test
-    public void testSpline_OneSegment() throws Exception {
-        final float lowerBrightness = 10.0f;
-        final float upperBrightness = 50.0f;
-        setBrightnesses(lowerBrightness, upperBrightness);
-        setBiases(0.0f, 1.0f);
-
-        DisplayWhiteBalanceController controller =
-                DisplayWhiteBalanceFactory.create(mHandler, mSensorManagerMock, mResourcesSpy);
-        final float ambientColorTemperature = 8000.0f;
-        setEstimatedColorTemperature(controller, ambientColorTemperature);
-        controller.mBrightnessFilter = spy(new AmbientFilterStubber());
-
-        for (float t = 0.0f; t <= 1.0f; t += 0.1f) {
-            setEstimatedBrightnessAndUpdate(controller,
-                    mix(lowerBrightness, upperBrightness, t));
-            assertEquals(controller.mPendingAmbientColorTemperature,
-                    mix(LOW_LIGHT_AMBIENT_COLOR_TEMPERATURE, ambientColorTemperature, t), 0.001);
-        }
-
-        setEstimatedBrightnessAndUpdate(controller, 0.0f);
-        assertEquals(controller.mPendingAmbientColorTemperature,
-                LOW_LIGHT_AMBIENT_COLOR_TEMPERATURE, 0.001);
-
-        setEstimatedBrightnessAndUpdate(controller, upperBrightness + 1.0f);
-        assertEquals(controller.mPendingAmbientColorTemperature, ambientColorTemperature, 0.001);
-    }
-
-    @Test
-    public void testSpline_TwoSegments() throws Exception {
-        final float brightness0 = 10.0f;
-        final float brightness1 = 50.0f;
-        final float brightness2 = 60.0f;
-        setBrightnesses(brightness0, brightness1, brightness2);
-        final float bias0 = 0.0f;
-        final float bias1 = 0.25f;
-        final float bias2 = 1.0f;
-        setBiases(bias0, bias1, bias2);
-
-        DisplayWhiteBalanceController controller =
-                DisplayWhiteBalanceFactory.create(mHandler, mSensorManagerMock, mResourcesSpy);
-        final float ambientColorTemperature = 8000.0f;
-        setEstimatedColorTemperature(controller, ambientColorTemperature);
-        controller.mBrightnessFilter = spy(new AmbientFilterStubber());
-
-        for (float t = 0.0f; t <= 1.0f; t += 0.1f) {
-            float luxOverride = mix(brightness0, brightness1, t);
-            setEstimatedBrightnessAndUpdate(controller, luxOverride);
-            float bias = mix(bias0, bias1, t);
-            assertEquals(controller.mPendingAmbientColorTemperature,
-                    mix(LOW_LIGHT_AMBIENT_COLOR_TEMPERATURE, ambientColorTemperature, bias), 0.001);
-        }
-
-        for (float t = 0.0f; t <= 1.0f; t += 0.1f) {
-            float luxOverride = mix(brightness1, brightness2, t);
-            setEstimatedBrightnessAndUpdate(controller, luxOverride);
-            float bias = mix(bias1, bias2, t);
-            assertEquals(controller.mPendingAmbientColorTemperature,
-                    mix(LOW_LIGHT_AMBIENT_COLOR_TEMPERATURE, ambientColorTemperature, bias), 0.001);
-        }
-
-        setEstimatedBrightnessAndUpdate(controller, 0.0f);
-        assertEquals(controller.mPendingAmbientColorTemperature,
-                LOW_LIGHT_AMBIENT_COLOR_TEMPERATURE, 0.001);
-
-        setEstimatedBrightnessAndUpdate(controller, brightness2 + 1.0f);
-        assertEquals(controller.mPendingAmbientColorTemperature, ambientColorTemperature, 0.001);
-    }
-
-    @Test
-    public void testSpline_VerticalSegment() throws Exception {
-        final float lowerBrightness = 10.0f;
-        final float upperBrightness = 10.0f;
-        setBrightnesses(lowerBrightness, upperBrightness);
-        setBiases(0.0f, 1.0f);
-
-        DisplayWhiteBalanceController controller =
-                DisplayWhiteBalanceFactory.create(mHandler, mSensorManagerMock, mResourcesSpy);
-        final float ambientColorTemperature = 8000.0f;
-        setEstimatedColorTemperature(controller, ambientColorTemperature);
-        controller.mBrightnessFilter = spy(new AmbientFilterStubber());
-
-        setEstimatedBrightnessAndUpdate(controller, 0.0f);
-        assertEquals(controller.mPendingAmbientColorTemperature,
-                LOW_LIGHT_AMBIENT_COLOR_TEMPERATURE, 0.001);
-
-        setEstimatedBrightnessAndUpdate(controller, upperBrightness + 1.0f);
-        assertEquals(controller.mPendingAmbientColorTemperature, ambientColorTemperature, 0.001);
-    }
-
-    @Test
-    public void testSpline_InvalidEndBias() throws Exception {
-        setBrightnesses(10.0f, 1000.0f);
-        setBiases(0.0f, 2.0f);
-
-        DisplayWhiteBalanceController controller =
-                DisplayWhiteBalanceFactory.create(mHandler, mSensorManagerMock, mResourcesSpy);
-        final float ambientColorTemperature = 8000.0f;
-        setEstimatedColorTemperature(controller, ambientColorTemperature);
-        controller.mBrightnessFilter = spy(new AmbientFilterStubber());
-
-        for (float luxOverride = 0.1f; luxOverride <= 10000; luxOverride *= 10) {
-        setEstimatedBrightnessAndUpdate(controller, luxOverride);
-        assertEquals(controller.mPendingAmbientColorTemperature,
-                ambientColorTemperature, 0.001);
-        }
-    }
-
-    @Test
-    public void testSpline_InvalidBeginBias() throws Exception {
-        setBrightnesses(10.0f, 1000.0f);
-        setBiases(0.1f, 1.0f);
-
-        DisplayWhiteBalanceController controller =
-                DisplayWhiteBalanceFactory.create(mHandler, mSensorManagerMock, mResourcesSpy);
-        final float ambientColorTemperature = 8000.0f;
-        setEstimatedColorTemperature(controller, ambientColorTemperature);
-        controller.mBrightnessFilter = spy(new AmbientFilterStubber());
-
-        for (float luxOverride = 0.1f; luxOverride <= 10000; luxOverride *= 10) {
-        setEstimatedBrightnessAndUpdate(controller, luxOverride);
-        assertEquals(controller.mPendingAmbientColorTemperature,
-                ambientColorTemperature, 0.001);
-        }
-    }
-
-    @Test
-    public void testSpline_OneSegmentHighLight() throws Exception {
-        final float lowerBrightness = 10.0f;
-        final float upperBrightness = 50.0f;
-        setHighLightBrightnesses(lowerBrightness, upperBrightness);
-        setHighLightBiases(0.0f, 1.0f);
-
-        DisplayWhiteBalanceController controller =
-                DisplayWhiteBalanceFactory.create(mHandler, mSensorManagerMock, mResourcesSpy);
-        final float ambientColorTemperature = 8000.0f;
-        setEstimatedColorTemperature(controller, ambientColorTemperature);
-        controller.mBrightnessFilter = spy(new AmbientFilterStubber());
-
-        for (float t = 0.0f; t <= 1.0f; t += 0.1f) {
-            setEstimatedBrightnessAndUpdate(controller,
-                    mix(lowerBrightness, upperBrightness, t));
-            assertEquals(controller.mPendingAmbientColorTemperature,
-                    mix(HIGH_LIGHT_AMBIENT_COLOR_TEMPERATURE, ambientColorTemperature, 1.0f - t),
-                    0.001);
-        }
-
-        setEstimatedBrightnessAndUpdate(controller, upperBrightness + 1.0f);
-        assertEquals(controller.mPendingAmbientColorTemperature,
-                HIGH_LIGHT_AMBIENT_COLOR_TEMPERATURE, 0.001);
-
-        setEstimatedBrightnessAndUpdate(controller, 0.0f);
-        assertEquals(controller.mPendingAmbientColorTemperature, ambientColorTemperature, 0.001);
-    }
-
-    @Test
-    public void testSpline_TwoSegmentsHighLight() throws Exception {
-        final float brightness0 = 10.0f;
-        final float brightness1 = 50.0f;
-        final float brightness2 = 60.0f;
-        setHighLightBrightnesses(brightness0, brightness1, brightness2);
-        final float bias0 = 0.0f;
-        final float bias1 = 0.25f;
-        final float bias2 = 1.0f;
-        setHighLightBiases(bias0, bias1, bias2);
-
-        DisplayWhiteBalanceController controller =
-                DisplayWhiteBalanceFactory.create(mHandler, mSensorManagerMock, mResourcesSpy);
-        final float ambientColorTemperature = 6000.0f;
-        setEstimatedColorTemperature(controller, ambientColorTemperature);
-        controller.mBrightnessFilter = spy(new AmbientFilterStubber());
-
-        for (float t = 0.0f; t <= 1.0f; t += 0.1f) {
-            float luxOverride = mix(brightness0, brightness1, t);
-            setEstimatedBrightnessAndUpdate(controller, luxOverride);
-            float bias = mix(bias0, bias1, t);
-            assertEquals(controller.mPendingAmbientColorTemperature,
-                    mix(HIGH_LIGHT_AMBIENT_COLOR_TEMPERATURE, ambientColorTemperature, 1.0f - bias),
-                    0.01);
-        }
-
-        for (float t = 0.0f; t <= 1.0f; t += 0.1f) {
-            float luxOverride = mix(brightness1, brightness2, t);
-            setEstimatedBrightnessAndUpdate(controller, luxOverride);
-            float bias = mix(bias1, bias2, t);
-            assertEquals(controller.mPendingAmbientColorTemperature,
-                    mix(HIGH_LIGHT_AMBIENT_COLOR_TEMPERATURE, ambientColorTemperature, 1.0f - bias),
-                    0.01);
-        }
-
-        setEstimatedBrightnessAndUpdate(controller, brightness2 + 1.0f);
-        assertEquals(controller.mPendingAmbientColorTemperature,
-                HIGH_LIGHT_AMBIENT_COLOR_TEMPERATURE, 0.001);
-
-        setEstimatedBrightnessAndUpdate(controller, 0.0f);
-        assertEquals(controller.mPendingAmbientColorTemperature, ambientColorTemperature, 0.001);
-    }
-
-    @Test
-    public void testSpline_InvalidCombinations() throws Exception {
-            setBrightnesses(100.0f, 200.0f);
-            setBiases(0.0f, 1.0f);
-            setHighLightBrightnesses(150.0f, 250.0f);
-            setHighLightBiases(0.0f, 1.0f);
-
-            DisplayWhiteBalanceController controller =
-                    DisplayWhiteBalanceFactory.create(mHandler, mSensorManagerMock, mResourcesSpy);
-            final float ambientColorTemperature = 8000.0f;
-            setEstimatedColorTemperature(controller, ambientColorTemperature);
-            controller.mBrightnessFilter = spy(new AmbientFilterStubber());
-
-            for (float luxOverride = 0.1f; luxOverride <= 10000; luxOverride *= 10) {
-                setEstimatedBrightnessAndUpdate(controller, luxOverride);
-                assertEquals(controller.mPendingAmbientColorTemperature,
-                        ambientColorTemperature, 0.001);
-            }
-    }
-
-    @Test
-    public void testStrongMode() {
-        final float lowerBrightness = 10.0f;
-        final float upperBrightness = 50.0f;
-        setBrightnesses(lowerBrightness, upperBrightness);
-        setBiases(0.0f, 1.0f);
-        final int ambientColorTempLow = 6000;
-        final int ambientColorTempHigh = 8000;
-        final int displayColorTempLow = 6400;
-        final int displayColorTempHigh = 7400;
-        setStrongAmbientColorTemperatures(ambientColorTempLow, ambientColorTempHigh);
-        setStrongDisplayColorTemperatures(displayColorTempLow, displayColorTempHigh);
-
-        DisplayWhiteBalanceController controller =
-                DisplayWhiteBalanceFactory.create(mHandler, mSensorManagerMock, mResourcesSpy);
-        controller.setStrongModeEnabled(true);
-        controller.mBrightnessFilter = spy(new AmbientFilterStubber());
-
-        for (float ambientTempFraction = 0.0f; ambientTempFraction <= 1.0f;
-                ambientTempFraction += 0.1f) {
-            final float ambientTemp =
-                    (ambientColorTempHigh - ambientColorTempLow) * ambientTempFraction
-                            + ambientColorTempLow;
-            setEstimatedColorTemperature(controller, ambientTemp);
-            for (float brightnessFraction = 0.0f; brightnessFraction <= 1.0f;
-                    brightnessFraction += 0.1f) {
-                setEstimatedBrightnessAndUpdate(controller,
-                        mix(lowerBrightness, upperBrightness, brightnessFraction));
-                assertEquals(controller.mPendingAmbientColorTemperature,
-                        mix(LOW_LIGHT_AMBIENT_COLOR_TEMPERATURE,
-                                mix(displayColorTempLow, displayColorTempHigh, ambientTempFraction),
-                                brightnessFraction),
-                        ALLOWED_ERROR_DELTA);
-            }
-        }
-    }
-
-    @Test
-    public void testLowLight_DefaultAmbient() throws Exception {
-        final float lowerBrightness = 10.0f;
-        final float upperBrightness = 50.0f;
-        setBrightnesses(lowerBrightness, upperBrightness);
-        setBiases(0.0f, 1.0f);
-
-        DisplayWhiteBalanceController controller =
-                DisplayWhiteBalanceFactory.create(mHandler, mSensorManagerMock, mResourcesSpy);
-        final float ambientColorTemperature = -1.0f;
-        setEstimatedColorTemperature(controller, ambientColorTemperature);
-        controller.mBrightnessFilter = spy(new AmbientFilterStubber());
-
-        for (float t = 0.0f; t <= 1.0f; t += 0.1f) {
-            setEstimatedBrightnessAndUpdate(controller,
-                    mix(lowerBrightness, upperBrightness, t));
-            assertEquals(controller.mPendingAmbientColorTemperature, ambientColorTemperature,
-                        0.001);
-        }
-
-        setEstimatedBrightnessAndUpdate(controller, 0.0f);
-        assertEquals(controller.mPendingAmbientColorTemperature, ambientColorTemperature, 0.001);
-
-        setEstimatedBrightnessAndUpdate(controller, upperBrightness + 1.0f);
-        assertEquals(controller.mPendingAmbientColorTemperature, ambientColorTemperature, 0.001);
-    }
-
-    @Test
-    public void testWhiteBalance_updateWithEmptyFilter() throws Exception {
-        setAmbientColorTemperatures(5300.0f, 6000.0f, 7000.0f, 8000.0f);
-        setDisplayColorTemperatures(6300.0f, 6400.0f, 6850.0f, 7450.0f);
-        DisplayWhiteBalanceController controller =
-                DisplayWhiteBalanceFactory.create(mHandler, mSensorManagerMock, mResourcesSpy);
-        controller.updateAmbientColorTemperature();
-        assertEquals(-1.0f, controller.mPendingAmbientColorTemperature, 0);
-    }
-
-    void mockThrottler() {
-        when(mResourcesSpy.getInteger(
-                R.integer.config_displayWhiteBalanceDecreaseDebounce)).thenReturn(0);
-        when(mResourcesSpy.getInteger(
-                R.integer.config_displayWhiteBalanceIncreaseDebounce)).thenReturn(0);
-        TypedArray base = mResourcesSpy.obtainTypedArray(
-                R.array.config_displayWhiteBalanceBaseThresholds);
-        TypedArray inc = mResourcesSpy.obtainTypedArray(
-                R.array.config_displayWhiteBalanceIncreaseThresholds);
-        TypedArray dec = mResourcesSpy.obtainTypedArray(
-                R.array.config_displayWhiteBalanceDecreaseThresholds);
-        base = spy(base);
-        inc = spy(inc);
-        dec = spy(dec);
-        when(mResourcesSpy.obtainTypedArray(
-                R.array.config_displayWhiteBalanceBaseThresholds)).thenReturn(base);
-        when(mResourcesSpy.obtainTypedArray(
-                R.array.config_displayWhiteBalanceIncreaseThresholds)).thenReturn(inc);
-        when(mResourcesSpy.obtainTypedArray(
-                R.array.config_displayWhiteBalanceDecreaseThresholds)).thenReturn(dec);
-        setFloatArrayResource(base, new float[]{0.0f});
-        setFloatArrayResource(inc, new float[]{0.0f});
-        setFloatArrayResource(dec, new float[]{0.0f});
-    }
-
-    private void mockResourcesFloat(int id, float floatValue) {
-        doAnswer(new Answer<Void>() {
-            public Void answer(InvocationOnMock invocation) {
-                TypedValue value = (TypedValue)invocation.getArgument(1);
-                value.type = TypedValue.TYPE_FLOAT;
-                value.data = Float.floatToIntBits(floatValue);
-                return null;
-            }
-        }).when(mResourcesSpy).getValue(
-                eq(id),
-                any(TypedValue.class), eq(true));
-    }
-
-    private void setEstimatedColorTemperature(DisplayWhiteBalanceController controller,
-                                              float ambientColorTemperature) {
-        AmbientFilter colorTemperatureFilter = spy(new AmbientFilterStubber());
-        controller.mColorTemperatureFilter = colorTemperatureFilter;
-        when(colorTemperatureFilter.getEstimate(anyLong())).thenReturn(ambientColorTemperature);
-    }
-
-    private void setEstimatedBrightnessAndUpdate(DisplayWhiteBalanceController controller,
-                                                 float brightness) {
-        when(controller.mBrightnessFilter.getEstimate(anyLong())).thenReturn(brightness);
-        controller.updateAmbientColorTemperature();
-    }
-
-    private void setBrightnesses(float... vals) {
-        setFloatArrayResource(mBrightnesses, vals);
-    }
-
-    private void setBiases(float... vals) {
-        setFloatArrayResource(mBiases, vals);
-    }
-
-    private void setHighLightBrightnesses(float... vals) {
-        setFloatArrayResource(mHighLightBrightnesses, vals);
-    }
-
-    private void setHighLightBiases(float... vals) {
-        setFloatArrayResource(mHighLightBiases, vals);
-    }
-
-    private void setAmbientColorTemperatures(float... vals) {
-        setFloatArrayResource(mAmbientColorTemperatures, vals);
-    }
-
-    private void setDisplayColorTemperatures(float... vals) {
-        setFloatArrayResource(mDisplayColorTemperatures, vals);
-    }
-
-    private void setStrongAmbientColorTemperatures(float... vals) {
-        setFloatArrayResource(mStrongAmbientColorTemperatures, vals);
-    }
-
-    private void setStrongDisplayColorTemperatures(float... vals) {
-        setFloatArrayResource(mStrongDisplayColorTemperatures, vals);
-    }
-
-    private void setFloatArrayResource(TypedArray array, float[] vals) {
-        when(array.length()).thenReturn(vals.length);
-        for (int i = 0; i < vals.length; i++) {
-            when(array.getFloat(i, Float.NaN)).thenReturn(vals[i]);
-        }
-    }
-
-    private TypedArray createTypedArray() throws Exception {
-        TypedArray mockArray = mock(TypedArray.class);
-        return mockArray;
-    }
-
-    private static float mix(float a, float b, float t) {
-        return (1.0f - t) * a + t * b;
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/flags/FeatureFlagsServiceTest.java b/services/tests/servicestests/src/com/android/server/flags/FeatureFlagsServiceTest.java
new file mode 100644
index 0000000..df4731f
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/flags/FeatureFlagsServiceTest.java
@@ -0,0 +1,293 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.flags;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.flags.IFeatureFlagsCallback;
+import android.flags.SyncableFlag;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.List;
+
+@Presubmit
+@SmallTest
+public class FeatureFlagsServiceTest {
+    private static final String NS = "ns";
+    private static final String NAME = "name";
+    private static final String PROP_NAME = FlagOverrideStore.getPropName(NS, NAME);
+
+    @Rule
+    public final MockitoRule mockito = MockitoJUnit.rule();
+
+    @Mock
+    private FlagOverrideStore mFlagStore;
+    @Mock
+    private FlagsShellCommand mFlagCommand;
+    @Mock
+    private IFeatureFlagsCallback mIFeatureFlagsCallback;
+    @Mock
+    private IBinder mIFeatureFlagsCallbackAsBinder;
+    @Mock
+    private FeatureFlagsService.PermissionsChecker mPermissionsChecker;
+
+    private FeatureFlagsBinder mFeatureFlagsService;
+
+    @Before
+    public void setup() {
+        when(mIFeatureFlagsCallback.asBinder()).thenReturn(mIFeatureFlagsCallbackAsBinder);
+        mFeatureFlagsService = new FeatureFlagsBinder(
+                mFlagStore, mFlagCommand, mPermissionsChecker);
+    }
+
+    @Test
+    public void testRegisterCallback() {
+        mFeatureFlagsService.registerCallback(mIFeatureFlagsCallback);
+        try {
+            verify(mIFeatureFlagsCallbackAsBinder).linkToDeath(any(), eq(0));
+        } catch (RemoteException e) {
+            fail("Our mock threw a Remote Exception?");
+        }
+    }
+
+    @Test
+    public void testOverrideFlag_requiresWritePermission() {
+        SecurityException exc = new SecurityException("not allowed");
+        doThrow(exc).when(mPermissionsChecker).assertWritePermission();
+
+        SyncableFlag f = new SyncableFlag(NS, "a", "false", false);
+
+        try {
+            mFeatureFlagsService.overrideFlag(f);
+            fail("Should have thrown exception");
+        } catch (SecurityException e) {
+            assertThat(exc).isEqualTo(e);
+        } catch (Exception e) {
+            fail("should have thrown a security exception");
+        }
+    }
+
+    @Test
+    public void testResetFlag_requiresWritePermission() {
+        SecurityException exc = new SecurityException("not allowed");
+        doThrow(exc).when(mPermissionsChecker).assertWritePermission();
+
+        SyncableFlag f = new SyncableFlag(NS, "a", "false", false);
+
+        try {
+            mFeatureFlagsService.resetFlag(f);
+            fail("Should have thrown exception");
+        } catch (SecurityException e) {
+            assertThat(exc).isEqualTo(e);
+        } catch (Exception e) {
+            fail("should have thrown a security exception");
+        }
+    }
+
+    @Test
+    public void testSyncFlags_noOverrides() {
+        List<SyncableFlag> inputFlags = List.of(
+                new SyncableFlag(NS, "a", "false", false),
+                new SyncableFlag(NS, "b", "true", false),
+                new SyncableFlag(NS, "c", "false", false)
+        );
+
+        List<SyncableFlag> outputFlags = mFeatureFlagsService.syncFlags(inputFlags);
+
+        assertThat(inputFlags.size()).isEqualTo(outputFlags.size());
+
+        for (SyncableFlag inpF: inputFlags) {
+            boolean found = false;
+            for (SyncableFlag outF : outputFlags) {
+                if (compareSyncableFlagsNames(inpF, outF)) {
+                    found = true;
+                    break;
+                }
+            }
+            assertWithMessage("Failed to find input flag " + inpF + " in the output")
+                    .that(found).isTrue();
+        }
+    }
+
+    @Test
+    public void testSyncFlags_withSomeOverrides() {
+        List<SyncableFlag> inputFlags = List.of(
+                new SyncableFlag(NS, "a", "false", false),
+                new SyncableFlag(NS, "b", "true", false),
+                new SyncableFlag(NS, "c", "false", false)
+        );
+
+        assertThat(mFlagStore).isNotNull();
+        when(mFlagStore.get(NS, "c")).thenReturn("true");
+        List<SyncableFlag> outputFlags = mFeatureFlagsService.syncFlags(inputFlags);
+
+        assertThat(inputFlags.size()).isEqualTo(outputFlags.size());
+
+        for (SyncableFlag inpF: inputFlags) {
+            boolean found = false;
+            for (SyncableFlag outF : outputFlags) {
+                if (compareSyncableFlagsNames(inpF, outF)) {
+                    found = true;
+
+                    // Once we've found "c", do an extra check
+                    if (outF.getName().equals("c")) {
+                        assertWithMessage("Flag " + outF + "was not returned with an override")
+                                .that(outF.getValue()).isEqualTo("true");
+                    }
+                    break;
+                }
+            }
+            assertWithMessage("Failed to find input flag " + inpF + " in the output")
+                    .that(found).isTrue();
+        }
+    }
+
+    @Test
+    public void testSyncFlags_twoCallsWithDifferentDefaults() {
+        List<SyncableFlag> inputFlagsFirst = List.of(
+                new SyncableFlag(NS, "a", "false", false)
+        );
+        List<SyncableFlag> inputFlagsSecond = List.of(
+                new SyncableFlag(NS, "a", "true", false),
+                new SyncableFlag(NS, "b", "false", false)
+        );
+
+        List<SyncableFlag> outputFlagsFirst = mFeatureFlagsService.syncFlags(inputFlagsFirst);
+        List<SyncableFlag> outputFlagsSecond = mFeatureFlagsService.syncFlags(inputFlagsSecond);
+
+        assertThat(inputFlagsFirst.size()).isEqualTo(outputFlagsFirst.size());
+        assertThat(inputFlagsSecond.size()).isEqualTo(outputFlagsSecond.size());
+
+        // This test only cares that the "a" flag passed in the second time came out with the
+        // same value that was passed in the first time.
+
+        boolean found = false;
+        for (SyncableFlag second : outputFlagsSecond) {
+            if (compareSyncableFlagsNames(second, inputFlagsFirst.get(0))) {
+                found = true;
+                assertThat(second.getValue()).isEqualTo(inputFlagsFirst.get(0).getValue());
+                break;
+            }
+        }
+
+        assertWithMessage(
+                "Failed to find flag " + inputFlagsFirst.get(0) + " in the second calls output")
+                .that(found).isTrue();
+    }
+
+    @Test
+    public void testQueryFlags_onlyOnce() {
+        List<SyncableFlag> inputFlags = List.of(
+                new SyncableFlag(NS, "a", "false", false),
+                new SyncableFlag(NS, "b", "true", false),
+                new SyncableFlag(NS, "c", "false", false)
+        );
+
+        List<SyncableFlag> outputFlags = mFeatureFlagsService.queryFlags(inputFlags);
+
+        assertThat(inputFlags.size()).isEqualTo(outputFlags.size());
+
+        for (SyncableFlag inpF: inputFlags) {
+            boolean found = false;
+            for (SyncableFlag outF : outputFlags) {
+                if (compareSyncableFlagsNames(inpF, outF)) {
+                    found = true;
+                    break;
+                }
+            }
+            assertWithMessage("Failed to find input flag " + inpF + " in the output")
+                    .that(found).isTrue();
+        }
+    }
+
+    @Test
+    public void testQueryFlags_twoCallsWithDifferentDefaults() {
+        List<SyncableFlag> inputFlagsFirst = List.of(
+                new SyncableFlag(NS, "a", "false", false)
+        );
+        List<SyncableFlag> inputFlagsSecond = List.of(
+                new SyncableFlag(NS, "a", "true", false),
+                new SyncableFlag(NS, "b", "false", false)
+        );
+
+        List<SyncableFlag> outputFlagsFirst = mFeatureFlagsService.queryFlags(inputFlagsFirst);
+        List<SyncableFlag> outputFlagsSecond = mFeatureFlagsService.queryFlags(inputFlagsSecond);
+
+        assertThat(inputFlagsFirst.size()).isEqualTo(outputFlagsFirst.size());
+        assertThat(inputFlagsSecond.size()).isEqualTo(outputFlagsSecond.size());
+
+        // This test only cares that the "a" flag passed in the second time came out with the
+        // same value that was passed in (i.e. it wasn't cached).
+
+        boolean found = false;
+        for (SyncableFlag second : outputFlagsSecond) {
+            if (compareSyncableFlagsNames(second, inputFlagsSecond.get(0))) {
+                found = true;
+                assertThat(second.getValue()).isEqualTo(inputFlagsSecond.get(0).getValue());
+                break;
+            }
+        }
+
+        assertWithMessage(
+                "Failed to find flag " + inputFlagsSecond.get(0) + " in the second calls output")
+                .that(found).isTrue();
+    }
+
+    @Test
+    public void testOverrideFlag() {
+        SyncableFlag f = new SyncableFlag(NS, "a", "false", false);
+
+        mFeatureFlagsService.overrideFlag(f);
+
+        verify(mFlagStore).set(f.getNamespace(), f.getName(), f.getValue());
+    }
+
+    @Test
+    public void testResetFlag() {
+        SyncableFlag f = new SyncableFlag(NS, "a", "false", false);
+
+        mFeatureFlagsService.resetFlag(f);
+
+        verify(mFlagStore).erase(f.getNamespace(), f.getName());
+    }
+
+
+    private static boolean compareSyncableFlagsNames(SyncableFlag a, SyncableFlag b) {
+        return a.getNamespace().equals(b.getNamespace())
+                && a.getName().equals(b.getName())
+                && a.isDynamic() == b.isDynamic();
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/flags/FlagCacheTest.java b/services/tests/servicestests/src/com/android/server/flags/FlagCacheTest.java
new file mode 100644
index 0000000..c2cf540
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/flags/FlagCacheTest.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.server.flags;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class FlagCacheTest {
+    private static final String NS = "ns";
+    private static final String NAME = "name";
+
+    FlagCache mFlagCache = new FlagCache();
+
+    @Test
+    public void testGetOrNull_unset() {
+        assertThat(mFlagCache.getOrNull(NS, NAME)).isNull();
+    }
+
+    @Test
+    public void testGetOrSet_unset() {
+        assertThat(mFlagCache.getOrSet(NS, NAME, "value")).isEqualTo("value");
+    }
+
+    @Test
+    public void testGetOrSet_alreadySet() {
+        mFlagCache.setIfChanged(NS, NAME, "value");
+        assertThat(mFlagCache.getOrSet(NS, NAME, "newvalue")).isEqualTo("value");
+    }
+
+    @Test
+    public void testSetIfChanged_unset() {
+        assertThat(mFlagCache.setIfChanged(NS, NAME, "value")).isTrue();
+    }
+
+    @Test
+    public void testSetIfChanged_noChange() {
+        mFlagCache.setIfChanged(NS, NAME, "value");
+        assertThat(mFlagCache.setIfChanged(NS, NAME, "value")).isFalse();
+    }
+
+    @Test
+    public void testSetIfChanged_changing() {
+        mFlagCache.setIfChanged(NS, NAME, "value");
+        assertThat(mFlagCache.setIfChanged(NS, NAME, "newvalue")).isTrue();
+    }
+
+    @Test
+    public void testContainsNamespace_unset() {
+        assertThat(mFlagCache.containsNamespace(NS)).isFalse();
+    }
+
+    @Test
+    public void testContainsNamespace_set() {
+        mFlagCache.setIfChanged(NS, NAME, "value");
+        assertThat(mFlagCache.containsNamespace(NS)).isTrue();
+    }
+
+    @Test
+    public void testContains_unset() {
+        assertThat(mFlagCache.contains(NS, NAME)).isFalse();
+    }
+
+    @Test
+    public void testContains_set() {
+        mFlagCache.setIfChanged(NS, NAME, "value");
+        assertThat(mFlagCache.contains(NS, NAME)).isTrue();
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/flags/FlagOverrideStoreTest.java b/services/tests/servicestests/src/com/android/server/flags/FlagOverrideStoreTest.java
new file mode 100644
index 0000000..6cc3acf
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/flags/FlagOverrideStoreTest.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.flags;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+@Presubmit
+@SmallTest
+public class FlagOverrideStoreTest {
+    private static final String NS = "ns";
+    private static final String NAME = "name";
+    private static final String PROP_NAME = FlagOverrideStore.getPropName(NS, NAME);
+
+    @Rule
+    public final MockitoRule mockito = MockitoJUnit.rule();
+
+    @Mock
+    private SettingsProxy mSettingsProxy;
+    @Mock
+    private FlagOverrideStore.FlagChangeCallback mCallback;
+
+    private FlagOverrideStore mFlagStore;
+
+    @Before
+    public void setup() {
+        mFlagStore = new FlagOverrideStore(mSettingsProxy);
+        mFlagStore.setChangeCallback(mCallback);
+    }
+
+    @Test
+    public void testSet_unset() {
+        mFlagStore.set(NS, NAME, "value");
+        verify(mSettingsProxy).putString(PROP_NAME, "value");
+    }
+
+    @Test
+    public void testSet_setTwice() {
+        mFlagStore.set(NS, NAME, "value");
+        mFlagStore.set(NS, NAME, "newvalue");
+        verify(mSettingsProxy).putString(PROP_NAME, "value");
+        verify(mSettingsProxy).putString(PROP_NAME, "newvalue");
+    }
+
+    @Test
+    public void testGet_unset() {
+        assertThat(mFlagStore.get(NS, NAME)).isNull();
+    }
+
+    @Test
+    public void testGet_set() {
+        when(mSettingsProxy.getString(PROP_NAME)).thenReturn("value");
+        assertThat(mFlagStore.get(NS, NAME)).isEqualTo("value");
+    }
+
+    @Test
+    public void testErase() {
+        mFlagStore.erase(NS, NAME);
+        verify(mSettingsProxy).putString(PROP_NAME, null);
+    }
+
+    @Test
+    public void testContains_unset() {
+        assertThat(mFlagStore.contains(NS, NAME)).isFalse();
+    }
+
+    @Test
+    public void testContains_set() {
+        when(mSettingsProxy.getString(PROP_NAME)).thenReturn("value");
+        assertThat(mFlagStore.contains(NS, NAME)).isTrue();
+    }
+
+    @Test
+    public void testCallback_onSet() {
+        mFlagStore.set(NS, NAME, "value");
+        verify(mCallback).onFlagChanged(NS, NAME, "value");
+    }
+
+    @Test
+    public void testCallback_onErase() {
+        mFlagStore.erase(NS, NAME);
+        verify(mCallback).onFlagChanged(NS, NAME, null);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/flags/OWNERS b/services/tests/servicestests/src/com/android/server/flags/OWNERS
new file mode 100644
index 0000000..7ed369e
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/flags/OWNERS
@@ -0,0 +1 @@
+include /services/flags/OWNERS
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeBehaviorTest.java b/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeBehaviorTest.java
index bc09d4b..399655ffa 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeBehaviorTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeBehaviorTest.java
@@ -137,6 +137,20 @@
                     protected void writeStringSystemProperty(String key, String value) {
                         // do nothing
                     }
+
+                    /**
+                     * Override displayOsd to prevent it from broadcasting an intent, which
+                     * can trigger a SecurityException.
+                     */
+                    @Override
+                    void displayOsd(int messageId) {
+                        // do nothing
+                    }
+
+                    @Override
+                    void displayOsd(int messageId, int extra) {
+                        // do nothing
+                    }
                 };
 
         mLooper = mTestLooper.getLooper();
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java
index 5070b08..68ef80f 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java
@@ -118,6 +118,11 @@
                     boolean isPowerStandbyOrTransient() {
                         return false;
                     }
+
+                    @Override
+                    boolean isPowerStandby() {
+                        return false;
+                    }
                 };
 
 
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java
index 49023c6..26b448a 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java
@@ -127,6 +127,11 @@
                     boolean isPowerStandbyOrTransient() {
                         return false;
                     }
+
+                    @Override
+                    boolean isPowerStandby() {
+                        return false;
+                    }
                 };
 
 
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java b/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java
index 3bde665..cb19029 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java
@@ -61,6 +61,7 @@
     private HdmiPortInfo[] mHdmiPortInfo = null;
     private HdmiCecController.HdmiCecCallback mCallback = null;
     private int mCecVersion = HdmiControlManager.HDMI_CEC_VERSION_2_0;
+    private boolean mIsCecControlEnabled = true;
 
     @Override
     public String nativeInit() {
@@ -128,7 +129,9 @@
     public void enableCec(boolean enabled) {}
 
     @Override
-    public void enableSystemCecControl(boolean enabled) {}
+    public void enableSystemCecControl(boolean enabled) {
+        mIsCecControlEnabled = enabled;
+    }
 
     @Override
     public void nativeSetLanguage(String language) {}
@@ -154,6 +157,10 @@
         mPortConnectionStatus.put(port, connected);
     }
 
+    public boolean getIsCecControlEnabled() {
+        return mIsCecControlEnabled;
+    }
+
     public void setCecVersion(@HdmiControlManager.HdmiCecVersion int cecVersion) {
         mCecVersion = cecVersion;
     }
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/FakePowerManagerWrapper.java b/services/tests/servicestests/src/com/android/server/hdmi/FakePowerManagerWrapper.java
index 7c8a11e..04f921f 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/FakePowerManagerWrapper.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/FakePowerManagerWrapper.java
@@ -24,10 +24,18 @@
  */
 final class FakePowerManagerWrapper extends PowerManagerWrapper {
     private boolean mInteractive;
+    private WakeLockWrapper mWakeLock;
+    private boolean mWasWakeLockInstanceCreated = false;
+
 
     FakePowerManagerWrapper(@NonNull Context context) {
+        this(context, null);
+    }
+
+    FakePowerManagerWrapper(@NonNull Context context, WakeLockWrapper wakeLock) {
         super(context);
         mInteractive = true;
+        mWakeLock = wakeLock;
     }
 
     @Override
@@ -51,5 +59,48 @@
         return;
     }
 
-    // Don't stub WakeLock.
+    @Override
+    WakeLockWrapper newWakeLock(int levelAndFlags, String tag) {
+        if (mWakeLock == null) {
+            mWakeLock = new FakeWakeLockWrapper();
+        }
+        mWasWakeLockInstanceCreated = true;
+        return mWakeLock;
+    }
+
+    boolean wasWakeLockInstanceCreated() {
+        return mWasWakeLockInstanceCreated;
+    }
+
+    /**
+     * "Fake" wrapper for {@link PowerManager.WakeLock}, as opposed to a "Default" wrapper used by
+     * the framework - see {@link PowerManagerWrapper.DefaultWakeLockWrapper}.
+     */
+    public static class FakeWakeLockWrapper implements WakeLockWrapper {
+        private static final String TAG = "FakeWakeLockWrapper";
+        private boolean mWakeLockHeld = false;
+
+        @Override
+        public void acquire(long timeout) {
+            mWakeLockHeld = true;
+        }
+
+        @Override
+        public void acquire() {
+            mWakeLockHeld = true;
+        }
+
+        @Override
+        public void release() {
+            mWakeLockHeld = false;
+        }
+
+        @Override
+        public boolean isHeld() {
+            return mWakeLockHeld;
+        }
+
+        @Override
+        public void setReferenceCounted(boolean value) {}
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java
index 5e54d3b..ffe088c 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java
@@ -76,6 +76,8 @@
     private TestLooper mTestLooper = new TestLooper();
     private int mPhysicalAddress = 0x1110;
     private HdmiPortInfo[] mHdmiPortInfo;
+    private HdmiEarcController mHdmiEarcController;
+    private FakeEarcNativeWrapper mEarcNativeWrapper;
 
     @Before
     public void setUp() throws RemoteException {
@@ -94,7 +96,6 @@
         doNothing().when(mHdmiControlServiceSpy)
                 .writeStringSystemProperty(anyString(), anyString());
         doReturn(mHdmiCecAtomWriterSpy).when(mHdmiControlServiceSpy).getAtomWriter();
-
         HdmiCecConfig hdmiCecConfig = new FakeHdmiCecConfig(mContextSpy);
         doReturn(hdmiCecConfig).when(mHdmiControlServiceSpy).getHdmiCecConfig();
 
@@ -118,6 +119,11 @@
         mHdmiControlServiceSpy.setHdmiCecNetwork(mHdmiCecNetwork);
         mHdmiControlServiceSpy.setDeviceConfig(new FakeDeviceConfigWrapper());
 
+        mEarcNativeWrapper = new FakeEarcNativeWrapper();
+        mHdmiEarcController = HdmiEarcController.createWithNativeWrapper(
+                mHdmiControlServiceSpy, mEarcNativeWrapper);
+        mHdmiControlServiceSpy.setEarcController(mHdmiEarcController);
+
         HdmiPortInfo[] hdmiPortInfos = new HdmiPortInfo[1];
         hdmiPortInfos[0] =
                 new HdmiPortInfo.Builder(1, HdmiPortInfo.PORT_OUTPUT, 0x0000)
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTvTest.java
new file mode 100644
index 0000000..30ce961
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTvTest.java
@@ -0,0 +1,252 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.hdmi;
+
+import static com.android.server.hdmi.Constants.HDMI_EARC_STATUS_EARC_PENDING;
+import static com.android.server.hdmi.Constants.HDMI_EARC_STATUS_UNKNOWN;
+import static com.android.server.hdmi.HdmiControlService.WAKE_UP_SCREEN_ON;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.hardware.hdmi.HdmiControlManager;
+import android.hardware.hdmi.HdmiDeviceInfo;
+import android.hardware.hdmi.HdmiPortInfo;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.os.test.TestLooper;
+import android.platform.test.annotations.Presubmit;
+import android.stats.hdmi.HdmiStatsEnums;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.server.SystemService;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mockito;
+
+import java.util.Collections;
+
+/**
+ * Tests for the {@link HdmiCecAtomWriter} class and its usage by the HDMI-CEC framework.
+ */
+@SmallTest
+@Presubmit
+@RunWith(JUnit4.class)
+public class HdmiCecAtomLoggingTvTest {
+    private HdmiCecAtomWriter mHdmiCecAtomWriterSpy;
+    private HdmiControlService mHdmiControlServiceSpy;
+    private HdmiCecController mHdmiCecController;
+    private HdmiMhlControllerStub mHdmiMhlControllerStub;
+    private FakeNativeWrapper mNativeWrapper;
+    private FakePowerManagerWrapper mPowerManager;
+    private HdmiCecNetwork mHdmiCecNetwork;
+    private Looper mLooper;
+    private Context mContextSpy;
+    private TestLooper mTestLooper = new TestLooper();
+    private int mPhysicalAddress = 0x0000;
+    private static final int EARC_PORT_ID = 1;
+    private HdmiEarcController mHdmiEarcController;
+    private FakeEarcNativeWrapper mEarcNativeWrapper;
+
+    @Before
+    public void setUp() throws RemoteException {
+        mHdmiCecAtomWriterSpy = spy(new HdmiCecAtomWriter());
+
+        mLooper = mTestLooper.getLooper();
+
+        mContextSpy = spy(new ContextWrapper(
+                InstrumentationRegistry.getInstrumentation().getTargetContext()));
+
+        FakeAudioFramework audioFramework = new FakeAudioFramework();
+
+        mHdmiControlServiceSpy = spy(new HdmiControlService(mContextSpy,
+                Collections.singletonList(HdmiDeviceInfo.DEVICE_TV),
+                audioFramework.getAudioManager(), audioFramework.getAudioDeviceVolumeManager()));
+        doNothing().when(mHdmiControlServiceSpy)
+                .writeStringSystemProperty(anyString(), anyString());
+        doReturn(mHdmiCecAtomWriterSpy).when(mHdmiControlServiceSpy).getAtomWriter();
+
+        HdmiCecConfig hdmiCecConfig = new FakeHdmiCecConfig(mContextSpy);
+        doReturn(hdmiCecConfig).when(mHdmiControlServiceSpy).getHdmiCecConfig();
+
+        mHdmiControlServiceSpy.setIoLooper(mLooper);
+        mHdmiControlServiceSpy.setCecMessageBuffer(
+                new CecMessageBuffer(mHdmiControlServiceSpy));
+
+        mNativeWrapper = new FakeNativeWrapper();
+        mNativeWrapper.setPhysicalAddress(mPhysicalAddress);
+
+        mHdmiCecController = HdmiCecController.createWithNativeWrapper(
+                mHdmiControlServiceSpy, mNativeWrapper, mHdmiCecAtomWriterSpy);
+        mHdmiControlServiceSpy.setCecController(mHdmiCecController);
+
+        mHdmiMhlControllerStub = HdmiMhlControllerStub.create(mHdmiControlServiceSpy);
+        mHdmiControlServiceSpy.setHdmiMhlController(
+                mHdmiMhlControllerStub);
+
+        mHdmiCecNetwork = new HdmiCecNetwork(mHdmiControlServiceSpy,
+                mHdmiCecController, mHdmiMhlControllerStub);
+        mHdmiControlServiceSpy.setHdmiCecNetwork(mHdmiCecNetwork);
+        mHdmiControlServiceSpy.setDeviceConfig(new FakeDeviceConfigWrapper());
+
+        mEarcNativeWrapper = new FakeEarcNativeWrapper();
+        mHdmiEarcController = HdmiEarcController.createWithNativeWrapper(
+                mHdmiControlServiceSpy, mEarcNativeWrapper);
+        mHdmiControlServiceSpy.setEarcController(mHdmiEarcController);
+
+        HdmiPortInfo[] hdmiPortInfos = new HdmiPortInfo[1];
+        hdmiPortInfos[0] =
+                new HdmiPortInfo.Builder(EARC_PORT_ID, HdmiPortInfo.PORT_OUTPUT, 0x0000)
+                        .setCecSupported(true)
+                        .setMhlSupported(false)
+                        .setArcSupported(false)
+                        .setEarcSupported(true)
+                        .build();
+        mNativeWrapper.setPortInfo(hdmiPortInfos);
+        mNativeWrapper.setPortConnectionStatus(EARC_PORT_ID, true);
+
+        mHdmiControlServiceSpy.initService();
+        mPowerManager = new FakePowerManagerWrapper(mContextSpy);
+        mHdmiControlServiceSpy.setPowerManager(mPowerManager);
+        mHdmiControlServiceSpy.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY);
+        mHdmiControlServiceSpy.setEarcEnabled(HdmiControlManager.EARC_FEATURE_DISABLED);
+        mTestLooper.dispatchAll();
+
+        Mockito.reset(mHdmiCecAtomWriterSpy);
+    }
+
+    @Test
+    public void testEarcStatusChanged_handleEarcStateChange_writesAtom() {
+        mHdmiControlServiceSpy.setEarcEnabled(HdmiControlManager.EARC_FEATURE_ENABLED);
+        mTestLooper.dispatchAll();
+        Mockito.clearInvocations(mHdmiCecAtomWriterSpy);
+
+        mHdmiControlServiceSpy.handleEarcStateChange(HDMI_EARC_STATUS_EARC_PENDING,
+                EARC_PORT_ID);
+        verify(mHdmiCecAtomWriterSpy, times(1))
+                .earcStatusChanged(true, true, HDMI_EARC_STATUS_EARC_PENDING,
+                        HDMI_EARC_STATUS_EARC_PENDING,
+                        HdmiStatsEnums.LOG_REASON_EARC_STATUS_CHANGED);
+    }
+
+    @Test
+    public void testEarcStatusChanged_onWakeUp_earcSupported_earcEnabled_writesAtom() {
+        mHdmiControlServiceSpy.setEarcEnabled(HdmiControlManager.EARC_FEATURE_ENABLED);
+        Mockito.clearInvocations(mHdmiCecAtomWriterSpy);
+        mTestLooper.dispatchAll();
+
+        mHdmiControlServiceSpy.onWakeUp(WAKE_UP_SCREEN_ON);
+        verify(mHdmiCecAtomWriterSpy, times(1))
+                .earcStatusChanged(true, true, HDMI_EARC_STATUS_EARC_PENDING,
+                        HDMI_EARC_STATUS_EARC_PENDING, HdmiStatsEnums.LOG_REASON_WAKE);
+    }
+
+    @Test
+    public void testEarcStatusChanged_onWakeUp_earcSupported_earcDisabled_writesAtom() {
+        mHdmiControlServiceSpy.setEarcEnabled(HdmiControlManager.EARC_FEATURE_DISABLED);
+        Mockito.clearInvocations(mHdmiCecAtomWriterSpy);
+        mTestLooper.dispatchAll();
+
+        mHdmiControlServiceSpy.onWakeUp(WAKE_UP_SCREEN_ON);
+        verify(mHdmiCecAtomWriterSpy, times(1))
+                .earcStatusChanged(true, false, HDMI_EARC_STATUS_UNKNOWN,
+                        HDMI_EARC_STATUS_UNKNOWN, HdmiStatsEnums.LOG_REASON_WAKE);
+    }
+
+    @Test
+    public void testEarcStatusChanged_onWakeUp_earcNotSupported_earcEnabled_writesAtom() {
+        doReturn(false).when(mHdmiControlServiceSpy).isEarcSupported();
+        mTestLooper.dispatchAll();
+
+        mHdmiControlServiceSpy.setEarcEnabled(HdmiControlManager.EARC_FEATURE_ENABLED);
+        Mockito.clearInvocations(mHdmiCecAtomWriterSpy);
+        mTestLooper.dispatchAll();
+
+        mHdmiControlServiceSpy.onWakeUp(WAKE_UP_SCREEN_ON);
+        verify(mHdmiCecAtomWriterSpy, times(1))
+                .earcStatusChanged(false, true, HDMI_EARC_STATUS_UNKNOWN,
+                        HDMI_EARC_STATUS_UNKNOWN, HdmiStatsEnums.LOG_REASON_WAKE);
+    }
+
+    @Test
+    public void testEarcStatusChanged_onWakeUp_earcNotSupported_earcDisabled_writesAtom() {
+        doReturn(false).when(mHdmiControlServiceSpy).isEarcSupported();
+        mTestLooper.dispatchAll();
+
+        mHdmiControlServiceSpy.setEarcEnabled(HdmiControlManager.EARC_FEATURE_DISABLED);
+        Mockito.clearInvocations(mHdmiCecAtomWriterSpy);
+        mTestLooper.dispatchAll();
+
+        mHdmiControlServiceSpy.onWakeUp(WAKE_UP_SCREEN_ON);
+        verify(mHdmiCecAtomWriterSpy, times(1))
+                .earcStatusChanged(false, false, HDMI_EARC_STATUS_UNKNOWN,
+                        HDMI_EARC_STATUS_UNKNOWN, HdmiStatsEnums.LOG_REASON_WAKE);
+    }
+
+    @Test
+    public void testEarcStatusChanged_handleEarcStateChange_unSupportedPort_writesAtom() {
+        // Initialize HDMI port with eARC not supported.
+        HdmiPortInfo[] hdmiPortInfos = new HdmiPortInfo[1];
+        hdmiPortInfos[0] =
+                new HdmiPortInfo.Builder(EARC_PORT_ID, HdmiPortInfo.PORT_OUTPUT, 0x0000)
+                        .setCecSupported(true)
+                        .setMhlSupported(false)
+                        .setArcSupported(false)
+                        .setEarcSupported(false)
+                        .build();
+        mNativeWrapper.setPortInfo(hdmiPortInfos);
+        mNativeWrapper.setPortConnectionStatus(EARC_PORT_ID, true);
+        mHdmiControlServiceSpy.initService();
+        mTestLooper.dispatchAll();
+
+        mHdmiControlServiceSpy.setEarcEnabled(HdmiControlManager.EARC_FEATURE_ENABLED);
+        mTestLooper.dispatchAll();
+        Mockito.clearInvocations(mHdmiCecAtomWriterSpy);
+
+        mHdmiControlServiceSpy.handleEarcStateChange(HDMI_EARC_STATUS_EARC_PENDING, EARC_PORT_ID);
+        verify(mHdmiCecAtomWriterSpy, times(1))
+                .earcStatusChanged(eq(false), eq(true), anyInt(),
+                        eq(HDMI_EARC_STATUS_EARC_PENDING),
+                        eq(HdmiStatsEnums.LOG_REASON_EARC_STATUS_CHANGED_UNSUPPORTED_PORT));
+    }
+
+    @Test
+    public void testEarcStatusChanged_handleEarcStateChange_wrongState_writesAtom() {
+        mHdmiControlServiceSpy.setEarcEnabled(HdmiControlManager.EARC_FEATURE_DISABLED);
+        mTestLooper.dispatchAll();
+        Mockito.clearInvocations(mHdmiCecAtomWriterSpy);
+
+        // mEarcLocalDevice should be empty since eARC is disabled.
+        mHdmiControlServiceSpy.handleEarcStateChange(HDMI_EARC_STATUS_EARC_PENDING, EARC_PORT_ID);
+        verify(mHdmiCecAtomWriterSpy, times(1))
+                .earcStatusChanged(eq(true), eq(false), anyInt(),
+                        eq(HDMI_EARC_STATUS_EARC_PENDING),
+                        eq(HdmiStatsEnums.LOG_REASON_EARC_STATUS_CHANGED_WRONG_STATE));
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java
index 9882670..3dd8312 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java
@@ -504,6 +504,19 @@
     }
 
     @Test
+    public void handleUserControlPressed_ignoreAdditionalParameters() {
+        byte[] params = new byte[] {
+                (byte) (HdmiCecKeycode.CEC_KEYCODE_POWER_TOGGLE_FUNCTION & 0xFF), (byte) 0xFF};
+        mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY;
+        @Constants.HandleMessageResult int result = mHdmiLocalDevice.handleUserControlPressed(
+                HdmiCecMessageBuilder.buildUserControlPressed(ADDR_TV, ADDR_PLAYBACK_1, params));
+
+        assertEquals(Constants.HANDLED, result);
+        assertThat(mWakeupMessageReceived).isTrue();
+        assertThat(mStandbyMessageReceived).isFalse();
+    }
+
+    @Test
     public void handleVendorCommand_notHandled() {
         HdmiCecMessage vendorCommand = HdmiCecMessageBuilder.buildVendorCommand(ADDR_TV,
                 ADDR_PLAYBACK_1, new byte[]{0});
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
index d52b7ea..55e5dbd 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
@@ -1013,6 +1013,24 @@
     }
 
     @Test
+    public void receiveSetAudioVolumeLevel_volumeOutOfBounds_noVolumeChange() {
+        mAudioFramework.setStreamMaxVolume(AudioManager.STREAM_MUSIC, 25);
+
+        // Max volume of STREAM_MUSIC is retrieved on boot
+        mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
+        mTestLooper.dispatchAll();
+
+        mNativeWrapper.onCecMessage(SetAudioVolumeLevelMessage.build(
+                ADDR_PLAYBACK_1,
+                ADDR_TV,
+                127));
+        mTestLooper.dispatchAll();
+
+        verify(mAudioManager, never()).setStreamVolume(eq(AudioManager.STREAM_MUSIC), anyInt(),
+                anyInt());
+    }
+
+    @Test
     public void tvSendRequestArcTerminationOnSleep() {
         // Emulate Audio device on port 0x2000 (supports ARC)
         mNativeWrapper.setPortConnectionStatus(2, true);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java
index bdf3a5f..0aa72d0 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java
@@ -18,9 +18,6 @@
 
 import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM;
 import static com.android.server.hdmi.Constants.ADDR_BROADCAST;
-import static com.android.server.hdmi.Constants.ADDR_RECORDER_1;
-import static com.android.server.hdmi.Constants.ADDR_RECORDER_2;
-import static com.android.server.hdmi.Constants.ADDR_RECORDER_3;
 import static com.android.server.hdmi.HdmiCecMessageValidator.ERROR_DESTINATION;
 import static com.android.server.hdmi.HdmiCecMessageValidator.ERROR_PARAMETER;
 import static com.android.server.hdmi.HdmiCecMessageValidator.ERROR_PARAMETER_LONG;
@@ -43,9 +40,7 @@
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 
-import java.util.Arrays;
 import java.util.Collections;
-import java.util.List;
 
 /** Tests for {@link com.android.server.hdmi.HdmiCecMessageValidator} class. */
 @SmallTest
@@ -651,13 +646,9 @@
 
     @Test
     public void isValid_activeSource() {
-        // Only source devices should broadcast <Active Source> messages.
-        List<Integer> nonSourceDevicesAddresses = Arrays.asList(ADDR_RECORDER_1, ADDR_RECORDER_2,
-                ADDR_AUDIO_SYSTEM, ADDR_RECORDER_3);
-
         for (int i = 0; i < ADDR_BROADCAST; ++i) {
             String message = Integer.toHexString(i) + "F:82:10:00";
-            if (nonSourceDevicesAddresses.contains(i)) {
+            if (i == ADDR_AUDIO_SYSTEM) {
                 assertMessageValidity(message).isEqualTo(ERROR_SOURCE);
             } else {
                 assertMessageValidity(message).isEqualTo(OK);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
index 39930bc..0d172fdb 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
@@ -21,6 +21,7 @@
 
 import static com.android.server.SystemService.PHASE_BOOT_COMPLETED;
 import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
+import static com.android.server.hdmi.HdmiControlService.DEVICE_CLEANUP_TIMEOUT;
 import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
 import static com.android.server.hdmi.HdmiControlService.WAKE_UP_SCREEN_ON;
 
@@ -37,6 +38,7 @@
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -63,6 +65,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
+import org.mockito.InOrder;
 import org.mockito.Mockito;
 
 import java.util.ArrayList;
@@ -87,6 +90,7 @@
     private HdmiEarcController mHdmiEarcController;
     private FakeEarcNativeWrapper mEarcNativeWrapper;
     private FakePowerManagerWrapper mPowerManager;
+    private WakeLockWrapper mWakeLockSpy;
     private Looper mMyLooper;
     private TestLooper mTestLooper = new TestLooper();
     private ArrayList<HdmiCecLocalDevice> mLocalDevices = new ArrayList<>();
@@ -164,7 +168,8 @@
                         .build();
         mNativeWrapper.setPortInfo(mHdmiPortInfo);
         mHdmiControlServiceSpy.initService();
-        mPowerManager = new FakePowerManagerWrapper(mContextSpy);
+        mWakeLockSpy = spy(new FakePowerManagerWrapper.FakeWakeLockWrapper());
+        mPowerManager = new FakePowerManagerWrapper(mContextSpy, mWakeLockSpy);
         mHdmiControlServiceSpy.setPowerManager(mPowerManager);
         mHdmiControlServiceSpy.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
         mHdmiControlServiceSpy.setEarcSupported(true);
@@ -190,6 +195,7 @@
         doReturn(true).when(mHdmiControlServiceSpy).isStandbyMessageReceived();
 
         mHdmiControlServiceSpy.onStandby(HdmiControlService.STANDBY_SCREEN_OFF);
+
         assertTrue(mPlaybackDeviceSpy.isStandby());
         assertTrue(mAudioSystemDeviceSpy.isStandby());
         assertTrue(mPlaybackDeviceSpy.isDisabled());
@@ -197,6 +203,75 @@
     }
 
     @Test
+    public void playbackOnlyDevice_onStandbyCompleted_disableCecController() {
+        mLocalDevices.remove(mAudioSystemDeviceSpy);
+        mHdmiControlServiceSpy.clearCecLocalDevices();
+        mHdmiControlServiceSpy.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+        mTestLooper.dispatchAll();
+        mHdmiControlServiceSpy.setPowerStatus(HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY);
+
+        assertTrue(mNativeWrapper.getIsCecControlEnabled());
+        mHdmiControlServiceSpy.disableCecLocalDevices(
+                new HdmiCecLocalDevice.PendingActionClearedCallback() {
+                    @Override
+                    public void onCleared(HdmiCecLocalDevice device) {
+                        assertTrue(mNativeWrapper.getIsCecControlEnabled());
+                        mHdmiControlServiceSpy.onPendingActionsCleared(
+                                HdmiControlService.STANDBY_SCREEN_OFF);
+                    }
+                });
+        mTestLooper.dispatchAll();
+
+        verify(mPlaybackDeviceSpy, times(1)).invokeStandbyCompletedCallback(any());
+        assertFalse(mNativeWrapper.getIsCecControlEnabled());
+    }
+
+
+    @Test
+    public void playbackAndAudioDevice_onStandbyCompleted_doNotDisableCecController() {
+        mLocalDeviceTypes.add(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+        mHdmiControlServiceSpy.setPowerStatus(HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY);
+
+        assertTrue(mNativeWrapper.getIsCecControlEnabled());
+        mHdmiControlServiceSpy.disableCecLocalDevices(
+                new HdmiCecLocalDevice.PendingActionClearedCallback() {
+                    @Override
+                    public void onCleared(HdmiCecLocalDevice device) {
+                        assertTrue(mNativeWrapper.getIsCecControlEnabled());
+                        mHdmiControlServiceSpy.onPendingActionsCleared(
+                                HdmiControlService.STANDBY_SCREEN_OFF);
+                    }
+                });
+        mTestLooper.dispatchAll();
+
+        verify(mPlaybackDeviceSpy, times(1)).invokeStandbyCompletedCallback(any());
+        verify(mAudioSystemDeviceSpy, times(1)).invokeStandbyCompletedCallback(any());
+        assertTrue(mNativeWrapper.getIsCecControlEnabled());
+    }
+
+    @Test
+    public void onStandby_acquireAndReleaseWakeLockSuccessfully() {
+        mHdmiControlServiceSpy.getHdmiCecConfig().setStringValue(
+                HdmiControlManager.CEC_SETTING_NAME_POWER_CONTROL_MODE,
+                HdmiControlManager.POWER_CONTROL_MODE_TV_AND_AUDIO_SYSTEM);
+        mHdmiControlServiceSpy.setPowerStatus(HdmiControlManager.POWER_STATUS_ON);
+        doReturn(true).when(mHdmiControlServiceSpy).isStandbyMessageReceived();
+        mTestLooper.dispatchAll();
+
+        assertFalse(mPowerManager.wasWakeLockInstanceCreated());
+        mHdmiControlServiceSpy.onStandby(HdmiControlService.STANDBY_SCREEN_OFF);
+
+        InOrder inOrder = inOrder(mHdmiControlServiceSpy, mWakeLockSpy);
+        inOrder.verify(mWakeLockSpy, times(1)).acquire(DEVICE_CLEANUP_TIMEOUT);
+        inOrder.verify(mHdmiControlServiceSpy, times(1)).disableCecLocalDevices(any());
+        inOrder.verify(mHdmiControlServiceSpy, times(1))
+                .onPendingActionsCleared(HdmiControlService.STANDBY_SCREEN_OFF);
+        inOrder.verify(mWakeLockSpy, times(1)).release();
+
+        assertTrue(mPowerManager.wasWakeLockInstanceCreated());
+    }
+
+    @Test
     public void initialPowerStatus_normalBoot_isTransientToStandby() {
         assertThat(mHdmiControlServiceSpy.getInitialPowerStatus()).isEqualTo(
                 HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY);
@@ -903,6 +978,33 @@
         assertThat(vendorCmdListener.mVendorCommandCallbackReceived).isFalse();
     }
 
+    @Test
+    public void multipleVendorCommandListeners_receiveCallback() {
+        int destAddress = mHdmiControlServiceSpy.playback().getDeviceInfo().getLogicalAddress();
+        int sourceAddress = Constants.ADDR_TV;
+        byte[] params = {0x00, 0x01, 0x02, 0x03};
+        int vendorId = 0x123456;
+        mHdmiControlServiceSpy.setPowerStatus(HdmiControlManager.POWER_STATUS_ON);
+
+        VendorCommandListener vendorCmdListener =
+                new VendorCommandListener(sourceAddress, destAddress, params, vendorId);
+        VendorCommandListener secondVendorCmdListener =
+                new VendorCommandListener(sourceAddress, destAddress, params, vendorId);
+        mHdmiControlServiceSpy.addVendorCommandListener(vendorCmdListener, vendorId);
+        mHdmiControlServiceSpy.addVendorCommandListener(secondVendorCmdListener, vendorId);
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage vendorCommandNoId =
+                HdmiCecMessageBuilder.buildVendorCommand(sourceAddress, destAddress, params);
+        mNativeWrapper.onCecMessage(vendorCommandNoId);
+        mTestLooper.dispatchAll();
+        assertThat(vendorCmdListener.mVendorCommandCallbackReceived).isTrue();
+        assertThat(vendorCmdListener.mParamsCorrect).isTrue();
+
+        assertThat(secondVendorCmdListener.mVendorCommandCallbackReceived).isTrue();
+        assertThat(secondVendorCmdListener.mParamsCorrect).isTrue();
+    }
+
     private static class VendorCommandListener extends IHdmiVendorCommandListener.Stub {
         boolean mVendorCommandCallbackReceived = false;
         boolean mParamsCorrect = false;
@@ -1423,8 +1525,10 @@
         }
 
         @Override
-        protected void onStandby(boolean initiatedByCec, int standbyAction) {
+        protected void onStandby(boolean initiatedByCec, int standbyAction,
+                StandbyCompletedCallback callback) {
             mIsStandby = true;
+            invokeStandbyCompletedCallback(callback);
         }
 
         protected boolean isStandby() {
@@ -1476,8 +1580,10 @@
         }
 
         @Override
-        protected void onStandby(boolean initiatedByCec, int standbyAction) {
+        protected void onStandby(boolean initiatedByCec, int standbyAction,
+                StandbyCompletedCallback callback) {
             mIsStandby = true;
+            invokeStandbyCompletedCallback(callback);
         }
 
         protected boolean isStandby() {
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ResendCecCommandActionPlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ResendCecCommandActionPlaybackTest.java
new file mode 100644
index 0000000..061e1f9
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/hdmi/ResendCecCommandActionPlaybackTest.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.server.hdmi;
+
+import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
+import static com.android.server.hdmi.ResendCecCommandAction.SEND_COMMAND_RETRY_MS;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.hardware.hdmi.HdmiControlManager;
+import android.hardware.hdmi.HdmiDeviceInfo;
+import android.hardware.tv.cec.V1_0.SendMessageResult;
+import android.os.Looper;
+import android.os.test.TestLooper;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.Collections;
+
+/** Tests for {@link ResendCecCommandAction} */
+@SmallTest
+@Presubmit
+@RunWith(JUnit4.class)
+public class ResendCecCommandActionPlaybackTest {
+    private static final String TAG = "SendCecCommandActionPlaybackTest";
+    private HdmiControlService mHdmiControlService;
+    private HdmiCecLocalDevice mPlaybackDevice;
+    private FakeNativeWrapper mNativeWrapper;
+    private FakePowerManagerWrapper mPowerManager;
+    private Looper mMyLooper;
+    private TestLooper mTestLooper = new TestLooper();
+    private int mPhysicalAddress;
+    private boolean mIsPowerStandby;
+
+    @Before
+    public void setUp() throws Exception {
+        Context context = InstrumentationRegistry.getTargetContext();
+        FakeAudioFramework audioFramework = new FakeAudioFramework();
+
+        mHdmiControlService = new HdmiControlService(context,
+                Collections.singletonList(HdmiDeviceInfo.DEVICE_PLAYBACK),
+                audioFramework.getAudioManager(), audioFramework.getAudioDeviceVolumeManager()) {
+
+            @Override
+            boolean isPowerStandby() {
+                return mIsPowerStandby;
+            }
+
+            @Override
+            protected void writeStringSystemProperty(String key, String value) {
+                // do nothing
+            }
+        };
+        mIsPowerStandby = false;
+
+        mMyLooper = mTestLooper.getLooper();
+        mHdmiControlService.setIoLooper(mMyLooper);
+        mHdmiControlService.setHdmiCecConfig(new FakeHdmiCecConfig(context));
+        mHdmiControlService.setDeviceConfig(new FakeDeviceConfigWrapper());
+        mNativeWrapper = new FakeNativeWrapper();
+        HdmiCecController hdmiCecController = HdmiCecController.createWithNativeWrapper(
+                this.mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
+        mHdmiControlService.setCecController(hdmiCecController);
+        mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
+        mHdmiControlService.initService();
+        mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
+        mPowerManager = new FakePowerManagerWrapper(context);
+        mHdmiControlService.setPowerManager(mPowerManager);
+        mPhysicalAddress = 0x2000;
+        mNativeWrapper.setPhysicalAddress(mPhysicalAddress);
+        mTestLooper.dispatchAll();
+        mPlaybackDevice = mHdmiControlService.playback();
+
+        mTestLooper.dispatchAll();
+        mNativeWrapper.clearResultMessages();
+    }
+
+    @Test
+    public void sendCecCommand_activeSource_sendMessageFails_resendMessage() {
+        mNativeWrapper.setMessageSendResult(Constants.MESSAGE_ACTIVE_SOURCE,
+                SendMessageResult.BUSY);
+        mTestLooper.dispatchAll();
+        HdmiCecMessage activeSource = HdmiCecMessageBuilder.buildActiveSource(
+                mPlaybackDevice.getDeviceInfo().getLogicalAddress(), mPhysicalAddress);
+        mHdmiControlService.sendCecCommand(activeSource);
+        mTestLooper.dispatchAll();
+        assertThat(mNativeWrapper.getResultMessages()).contains(activeSource);
+
+        mNativeWrapper.clearResultMessages();
+        mTestLooper.moveTimeForward(SEND_COMMAND_RETRY_MS);
+        mTestLooper.dispatchAll();
+        assertThat(mNativeWrapper.getResultMessages()).contains(activeSource);
+
+        mNativeWrapper.clearResultMessages();
+        mTestLooper.moveTimeForward(SEND_COMMAND_RETRY_MS);
+        mTestLooper.dispatchAll();
+        assertThat(mNativeWrapper.getResultMessages()).contains(activeSource);
+
+        mNativeWrapper.clearResultMessages();
+        mTestLooper.moveTimeForward(SEND_COMMAND_RETRY_MS);
+        mTestLooper.dispatchAll();
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(activeSource);
+    }
+
+    @Test
+    public void sendCecCommand_inactiveSource_sendMessageFails_resendMessage() {
+        mNativeWrapper.setMessageSendResult(Constants.MESSAGE_INACTIVE_SOURCE,
+                SendMessageResult.BUSY);
+        mTestLooper.dispatchAll();
+        HdmiCecMessage inactiveSourceMessage = HdmiCecMessageBuilder.buildInactiveSource(
+                mPlaybackDevice.getDeviceInfo().getLogicalAddress(), mPhysicalAddress);
+        mHdmiControlService.sendCecCommand(inactiveSourceMessage);
+        mTestLooper.dispatchAll();
+        assertThat(mNativeWrapper.getResultMessages()).contains(inactiveSourceMessage);
+
+        mNativeWrapper.clearResultMessages();
+        mTestLooper.moveTimeForward(SEND_COMMAND_RETRY_MS);
+        mTestLooper.dispatchAll();
+        assertThat(mNativeWrapper.getResultMessages()).contains(inactiveSourceMessage);
+
+        mNativeWrapper.clearResultMessages();
+        mTestLooper.moveTimeForward(SEND_COMMAND_RETRY_MS);
+        mTestLooper.dispatchAll();
+        assertThat(mNativeWrapper.getResultMessages()).contains(inactiveSourceMessage);
+
+        mNativeWrapper.clearResultMessages();
+        mTestLooper.moveTimeForward(SEND_COMMAND_RETRY_MS);
+        mTestLooper.dispatchAll();
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(inactiveSourceMessage);
+    }
+
+    @Test
+    public void sendCecCommand_inactiveSource_onStandby_powerControlModeNone_sendMessage() {
+        mPlaybackDevice.mService.getHdmiCecConfig().setStringValue(
+                HdmiControlManager.CEC_SETTING_NAME_POWER_CONTROL_MODE,
+                HdmiControlManager.POWER_CONTROL_MODE_NONE);
+        mPlaybackDevice.setActiveSource(mPlaybackDevice.getDeviceInfo().getLogicalAddress(),
+                mPhysicalAddress, "SendCecCommandActionPlaybackTest");
+        mIsPowerStandby = true;
+        mPlaybackDevice.onStandby(false, HdmiControlService.STANDBY_SCREEN_OFF);
+        mTestLooper.dispatchAll();
+        HdmiCecMessage inactiveSourceMessage = HdmiCecMessageBuilder.buildInactiveSource(
+                mPlaybackDevice.getDeviceInfo().getLogicalAddress(), mPhysicalAddress);
+
+        assertThat(mNativeWrapper.getResultMessages()).contains(inactiveSourceMessage);
+    }
+
+    @Test
+    public void sendCecCommand_inactiveSource_onStandby_initiatedByCec_sendMessage() {
+        mPlaybackDevice.setActiveSource(mPlaybackDevice.getDeviceInfo().getLogicalAddress(),
+                mPhysicalAddress, "SendCecCommandActionPlaybackTest");
+        mIsPowerStandby = true;
+        mPlaybackDevice.onStandby(true, HdmiControlService.STANDBY_SCREEN_OFF);
+        mTestLooper.dispatchAll();
+        HdmiCecMessage inactiveSourceMessage = HdmiCecMessageBuilder.buildInactiveSource(
+                mPlaybackDevice.getDeviceInfo().getLogicalAddress(), mPhysicalAddress);
+
+        assertThat(mNativeWrapper.getResultMessages()).contains(inactiveSourceMessage);
+    }
+
+    @Test
+    public void sendCecCommand_routingChange_sendMessageFails_resendMessage() {
+        mNativeWrapper.setMessageSendResult(Constants.MESSAGE_ROUTING_CHANGE,
+                SendMessageResult.BUSY);
+        mTestLooper.dispatchAll();
+        int otherPhysicalAddress = 0x3000;
+        HdmiCecMessage routingChange = HdmiCecMessageBuilder.buildRoutingChange(
+                mPlaybackDevice.getDeviceInfo().getLogicalAddress(), mPhysicalAddress,
+                otherPhysicalAddress);
+        mHdmiControlService.sendCecCommand(routingChange);
+        mTestLooper.dispatchAll();
+        assertThat(mNativeWrapper.getResultMessages()).contains(routingChange);
+
+        mNativeWrapper.clearResultMessages();
+        mTestLooper.moveTimeForward(SEND_COMMAND_RETRY_MS);
+        mTestLooper.dispatchAll();
+        assertThat(mNativeWrapper.getResultMessages()).contains(routingChange);
+
+        mNativeWrapper.clearResultMessages();
+        mTestLooper.moveTimeForward(SEND_COMMAND_RETRY_MS);
+        mTestLooper.dispatchAll();
+        assertThat(mNativeWrapper.getResultMessages()).contains(routingChange);
+
+        mNativeWrapper.clearResultMessages();
+        mTestLooper.moveTimeForward(SEND_COMMAND_RETRY_MS);
+        mTestLooper.dispatchAll();
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(routingChange);
+    }
+
+    @Test
+    public void sendCecCommand_setStreamPath_sendMessageFails_resendMessage() {
+        mNativeWrapper.setMessageSendResult(Constants.MESSAGE_SET_STREAM_PATH,
+                SendMessageResult.BUSY);
+        mTestLooper.dispatchAll();
+        int otherPhysicalAddress = 0x3000;
+        HdmiCecMessage setStreamPath = HdmiCecMessageBuilder.buildSetStreamPath(
+                mPlaybackDevice.getDeviceInfo().getLogicalAddress(), otherPhysicalAddress);
+        mHdmiControlService.sendCecCommand(setStreamPath);
+        mTestLooper.dispatchAll();
+        assertThat(mNativeWrapper.getResultMessages()).contains(setStreamPath);
+
+        mNativeWrapper.clearResultMessages();
+        mTestLooper.moveTimeForward(SEND_COMMAND_RETRY_MS);
+        mTestLooper.dispatchAll();
+        assertThat(mNativeWrapper.getResultMessages()).contains(setStreamPath);
+
+        mNativeWrapper.clearResultMessages();
+        mTestLooper.moveTimeForward(SEND_COMMAND_RETRY_MS);
+        mTestLooper.dispatchAll();
+        assertThat(mNativeWrapper.getResultMessages()).contains(setStreamPath);
+
+        mNativeWrapper.clearResultMessages();
+        mTestLooper.moveTimeForward(SEND_COMMAND_RETRY_MS);
+        mTestLooper.dispatchAll();
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(setStreamPath);
+    }
+
+    @Test
+    public void sendCecCommand_textViewOn_sendMessageFails_resendMessage() {
+        mNativeWrapper.setMessageSendResult(Constants.MESSAGE_TEXT_VIEW_ON, SendMessageResult.BUSY);
+        mTestLooper.dispatchAll();
+        HdmiCecMessage textViewOn = HdmiCecMessageBuilder.buildTextViewOn(
+                mPlaybackDevice.getDeviceInfo().getLogicalAddress(), mPhysicalAddress);
+        mHdmiControlService.sendCecCommand(textViewOn);
+        mTestLooper.dispatchAll();
+        assertThat(mNativeWrapper.getResultMessages()).contains(textViewOn);
+
+        mNativeWrapper.clearResultMessages();
+        mTestLooper.moveTimeForward(SEND_COMMAND_RETRY_MS);
+        mTestLooper.dispatchAll();
+        assertThat(mNativeWrapper.getResultMessages()).contains(textViewOn);
+
+        mNativeWrapper.clearResultMessages();
+        mTestLooper.moveTimeForward(SEND_COMMAND_RETRY_MS);
+        mTestLooper.dispatchAll();
+        assertThat(mNativeWrapper.getResultMessages()).contains(textViewOn);
+
+        mNativeWrapper.clearResultMessages();
+        mTestLooper.moveTimeForward(SEND_COMMAND_RETRY_MS);
+        mTestLooper.dispatchAll();
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(textViewOn);
+    }
+
+    @Test
+    public void sendCecCommand_imageViewOn_sendMessageFails_resendMessage() {
+        mNativeWrapper.setMessageSendResult(Constants.MESSAGE_IMAGE_VIEW_ON,
+                SendMessageResult.BUSY);
+        mTestLooper.dispatchAll();
+        HdmiCecMessage imageViewOn = HdmiCecMessageBuilder.buildImageViewOn(
+                mPlaybackDevice.getDeviceInfo().getLogicalAddress(), mPhysicalAddress);
+        mHdmiControlService.sendCecCommand(imageViewOn);
+        mTestLooper.dispatchAll();
+        assertThat(mNativeWrapper.getResultMessages()).contains(imageViewOn);
+
+        mNativeWrapper.clearResultMessages();
+        mTestLooper.moveTimeForward(SEND_COMMAND_RETRY_MS);
+        mTestLooper.dispatchAll();
+        assertThat(mNativeWrapper.getResultMessages()).contains(imageViewOn);
+
+        mNativeWrapper.clearResultMessages();
+        mTestLooper.moveTimeForward(SEND_COMMAND_RETRY_MS);
+        mTestLooper.dispatchAll();
+        assertThat(mNativeWrapper.getResultMessages()).contains(imageViewOn);
+
+        mNativeWrapper.clearResultMessages();
+        mTestLooper.moveTimeForward(SEND_COMMAND_RETRY_MS);
+        mTestLooper.dispatchAll();
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(imageViewOn);
+    }
+
+    @Test
+    public void sendCecCommand_activeSource_sendMessageSuccess_noResendMessage() {
+        mNativeWrapper.setMessageSendResult(Constants.MESSAGE_ACTIVE_SOURCE,
+                SendMessageResult.SUCCESS);
+        mTestLooper.dispatchAll();
+        HdmiCecMessage activeSource = HdmiCecMessageBuilder.buildActiveSource(
+                mPlaybackDevice.getDeviceInfo().getLogicalAddress(), mPhysicalAddress);
+        mHdmiControlService.sendCecCommand(activeSource);
+        mTestLooper.dispatchAll();
+        assertThat(mNativeWrapper.getResultMessages()).contains(activeSource);
+
+        mNativeWrapper.clearResultMessages();
+        mTestLooper.moveTimeForward(SEND_COMMAND_RETRY_MS);
+        mTestLooper.dispatchAll();
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(activeSource);
+    }
+
+    @Test
+    public void sendCecCommand_reportPhysicalAddress_sendMessageFails_noResendMessage() {
+        mNativeWrapper.setMessageSendResult(Constants.MESSAGE_REPORT_PHYSICAL_ADDRESS,
+                SendMessageResult.BUSY);
+        mTestLooper.dispatchAll();
+        HdmiCecMessage reportPhysicalAddress = HdmiCecMessageBuilder
+                .buildReportPhysicalAddressCommand(
+                        mPlaybackDevice.getDeviceInfo().getLogicalAddress(), mPhysicalAddress,
+                        Constants.ALL_DEVICE_TYPES_PLAYBACK);
+        mHdmiControlService.sendCecCommand(reportPhysicalAddress);
+        mTestLooper.dispatchAll();
+        assertThat(mNativeWrapper.getResultMessages()).contains(reportPhysicalAddress);
+
+        mNativeWrapper.clearResultMessages();
+        mTestLooper.moveTimeForward(SEND_COMMAND_RETRY_MS);
+        mTestLooper.dispatchAll();
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(reportPhysicalAddress);
+    }
+
+    @Test
+    public void sendCecCommand_inactiveSource_sendMessageFails_afterStandby_noResendMessage() {
+        mNativeWrapper.setMessageSendResult(Constants.MESSAGE_INACTIVE_SOURCE,
+                SendMessageResult.BUSY);
+        mPlaybackDevice.setActiveSource(mPlaybackDevice.getDeviceInfo().getLogicalAddress(),
+                mPhysicalAddress, "SendCecCommandActionPlaybackTest");
+        mIsPowerStandby = true;
+        mPlaybackDevice.onStandby(true, HdmiControlService.STANDBY_SCREEN_OFF);
+        mTestLooper.dispatchAll();
+        HdmiCecMessage inactiveSourceMessage = HdmiCecMessageBuilder.buildInactiveSource(
+                mPlaybackDevice.getDeviceInfo().getLogicalAddress(), mPhysicalAddress);
+        assertThat(mNativeWrapper.getResultMessages()).contains(inactiveSourceMessage);
+
+        mNativeWrapper.clearResultMessages();
+        mHdmiControlService.onWakeUp(HdmiControlService.WAKE_UP_SCREEN_ON);
+        mTestLooper.dispatchAll();
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(inactiveSourceMessage);
+
+        mTestLooper.moveTimeForward(SEND_COMMAND_RETRY_MS);
+        mTestLooper.dispatchAll();
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(inactiveSourceMessage);
+    }
+
+    @Test
+    public void sendCecCommand_onStandby_removeAction_noResendMessage() {
+        mNativeWrapper.setMessageSendResult(Constants.MESSAGE_INACTIVE_SOURCE,
+                SendMessageResult.BUSY);
+        mTestLooper.dispatchAll();
+        HdmiCecMessage inactiveSourceMessage = HdmiCecMessageBuilder.buildInactiveSource(
+                mPlaybackDevice.getDeviceInfo().getLogicalAddress(), mPhysicalAddress);
+        mHdmiControlService.sendCecCommand(inactiveSourceMessage);
+        mTestLooper.dispatchAll();
+        assertThat(mNativeWrapper.getResultMessages()).contains(inactiveSourceMessage);
+        mHdmiControlService.onStandby(HdmiControlService.STANDBY_SCREEN_OFF);
+
+        mNativeWrapper.clearResultMessages();
+        mTestLooper.moveTimeForward(SEND_COMMAND_RETRY_MS);
+        mTestLooper.dispatchAll();
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(inactiveSourceMessage);
+
+        mTestLooper.dispatchAll();
+        mHdmiControlService.onWakeUp(HdmiControlService.WAKE_UP_SCREEN_ON);
+        mTestLooper.dispatchAll();
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(inactiveSourceMessage);
+
+        mTestLooper.moveTimeForward(SEND_COMMAND_RETRY_MS);
+        mTestLooper.dispatchAll();
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(inactiveSourceMessage);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ResendCecCommandActionTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ResendCecCommandActionTvTest.java
new file mode 100644
index 0000000..b25ea2c
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/hdmi/ResendCecCommandActionTvTest.java
@@ -0,0 +1,212 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.hdmi;
+
+import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
+import static com.android.server.hdmi.ResendCecCommandAction.SEND_COMMAND_RETRY_MS;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.hardware.hdmi.HdmiDeviceInfo;
+import android.hardware.tv.cec.V1_0.SendMessageResult;
+import android.os.Looper;
+import android.os.test.TestLooper;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.Collections;
+
+/** Tests for {@link ResendCecCommandAction} */
+@SmallTest
+@Presubmit
+@RunWith(JUnit4.class)
+public class ResendCecCommandActionTvTest {
+    private static final String TAG = "SendCecCommandActionTvTest";
+    private HdmiControlService mHdmiControlService;
+    private HdmiCecLocalDevice mTvDevice;
+    private FakeNativeWrapper mNativeWrapper;
+    private FakePowerManagerWrapper mPowerManager;
+    private Looper mMyLooper;
+    private TestLooper mTestLooper = new TestLooper();
+    private int mPhysicalAddress;
+
+    @Before
+    public void setUp() throws Exception {
+        Context context = InstrumentationRegistry.getTargetContext();
+        FakeAudioFramework audioFramework = new FakeAudioFramework();
+
+        mHdmiControlService = new HdmiControlService(context,
+                Collections.singletonList(HdmiDeviceInfo.DEVICE_TV),
+                audioFramework.getAudioManager(), audioFramework.getAudioDeviceVolumeManager()) {
+
+            @Override
+            boolean isPowerStandby() {
+                return false;
+            }
+
+            @Override
+            protected void writeStringSystemProperty(String key, String value) {
+                // do nothing
+            }
+
+            @Override
+            boolean verifyPhysicalAddresses(HdmiCecMessage message) {
+                return true;
+            }
+        };
+
+        mMyLooper = mTestLooper.getLooper();
+        mHdmiControlService.setIoLooper(mMyLooper);
+        mHdmiControlService.setHdmiCecConfig(new FakeHdmiCecConfig(context));
+        mHdmiControlService.setDeviceConfig(new FakeDeviceConfigWrapper());
+        mNativeWrapper = new FakeNativeWrapper();
+        HdmiCecController hdmiCecController = HdmiCecController.createWithNativeWrapper(
+                this.mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
+        mHdmiControlService.setCecController(hdmiCecController);
+        mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
+        mHdmiControlService.initService();
+        mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
+        mPowerManager = new FakePowerManagerWrapper(context);
+        mHdmiControlService.setPowerManager(mPowerManager);
+        mPhysicalAddress = 0x0000;
+        mNativeWrapper.setPhysicalAddress(mPhysicalAddress);
+        mTestLooper.dispatchAll();
+        mTvDevice = mHdmiControlService.tv();
+
+        mTestLooper.dispatchAll();
+        mNativeWrapper.clearResultMessages();
+    }
+
+    @Test
+    public void sendCecCommand_activeSource_sendMessageFails_resendMessage() {
+        mNativeWrapper.setMessageSendResult(Constants.MESSAGE_ACTIVE_SOURCE,
+                SendMessageResult.BUSY);
+        mTestLooper.dispatchAll();
+        HdmiCecMessage activeSource = HdmiCecMessageBuilder.buildActiveSource(
+                mTvDevice.getDeviceInfo().getLogicalAddress(), mPhysicalAddress);
+        mHdmiControlService.sendCecCommand(activeSource);
+        mTestLooper.dispatchAll();
+        assertThat(mNativeWrapper.getResultMessages()).contains(activeSource);
+
+        mNativeWrapper.clearResultMessages();
+        mTestLooper.moveTimeForward(SEND_COMMAND_RETRY_MS);
+        mTestLooper.dispatchAll();
+        assertThat(mNativeWrapper.getResultMessages()).contains(activeSource);
+
+        mNativeWrapper.clearResultMessages();
+        mTestLooper.moveTimeForward(SEND_COMMAND_RETRY_MS);
+        mTestLooper.dispatchAll();
+        assertThat(mNativeWrapper.getResultMessages()).contains(activeSource);
+
+        mNativeWrapper.clearResultMessages();
+        mTestLooper.moveTimeForward(SEND_COMMAND_RETRY_MS);
+        mTestLooper.dispatchAll();
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(activeSource);
+    }
+
+    @Test
+    public void sendCecCommand_inactiveSource_sendMessageFails_resendMessage() {
+        mNativeWrapper.setMessageSendResult(Constants.MESSAGE_INACTIVE_SOURCE,
+                SendMessageResult.BUSY);
+        mTestLooper.dispatchAll();
+        HdmiCecMessage inactiveSourceMessage = HdmiCecMessageBuilder.buildInactiveSource(
+                mTvDevice.getDeviceInfo().getLogicalAddress(), mPhysicalAddress);
+        mHdmiControlService.sendCecCommand(inactiveSourceMessage);
+        mTestLooper.dispatchAll();
+        assertThat(mNativeWrapper.getResultMessages()).contains(inactiveSourceMessage);
+
+        mNativeWrapper.clearResultMessages();
+        mTestLooper.moveTimeForward(SEND_COMMAND_RETRY_MS);
+        mTestLooper.dispatchAll();
+        assertThat(mNativeWrapper.getResultMessages()).contains(inactiveSourceMessage);
+
+        mNativeWrapper.clearResultMessages();
+        mTestLooper.moveTimeForward(SEND_COMMAND_RETRY_MS);
+        mTestLooper.dispatchAll();
+        assertThat(mNativeWrapper.getResultMessages()).contains(inactiveSourceMessage);
+
+        mNativeWrapper.clearResultMessages();
+        mTestLooper.moveTimeForward(SEND_COMMAND_RETRY_MS);
+        mTestLooper.dispatchAll();
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(inactiveSourceMessage);
+    }
+
+    @Test
+    public void sendCecCommand_routingChange_sendMessageFails_resendMessage() {
+        mNativeWrapper.setMessageSendResult(Constants.MESSAGE_ROUTING_CHANGE,
+                SendMessageResult.BUSY);
+        mTestLooper.dispatchAll();
+        int otherPhysicalAddress = 0x3000;
+        HdmiCecMessage routingChange = HdmiCecMessageBuilder.buildRoutingChange(
+                mTvDevice.getDeviceInfo().getLogicalAddress(), mPhysicalAddress,
+                otherPhysicalAddress);
+        mHdmiControlService.sendCecCommand(routingChange);
+        mTestLooper.dispatchAll();
+        assertThat(mNativeWrapper.getResultMessages()).contains(routingChange);
+
+        mNativeWrapper.clearResultMessages();
+        mTestLooper.moveTimeForward(SEND_COMMAND_RETRY_MS);
+        mTestLooper.dispatchAll();
+        assertThat(mNativeWrapper.getResultMessages()).contains(routingChange);
+
+        mNativeWrapper.clearResultMessages();
+        mTestLooper.moveTimeForward(SEND_COMMAND_RETRY_MS);
+        mTestLooper.dispatchAll();
+        assertThat(mNativeWrapper.getResultMessages()).contains(routingChange);
+
+        mNativeWrapper.clearResultMessages();
+        mTestLooper.moveTimeForward(SEND_COMMAND_RETRY_MS);
+        mTestLooper.dispatchAll();
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(routingChange);
+    }
+
+    @Test
+    public void sendCecCommand_setStreamPath_sendMessageFails_resendMessage() {
+        mNativeWrapper.setMessageSendResult(Constants.MESSAGE_SET_STREAM_PATH,
+                SendMessageResult.BUSY);
+        mTestLooper.dispatchAll();
+        int otherPhysicalAddress = 0x3000;
+        HdmiCecMessage setStreamPath = HdmiCecMessageBuilder.buildSetStreamPath(
+                mTvDevice.getDeviceInfo().getLogicalAddress(), otherPhysicalAddress);
+        mHdmiControlService.sendCecCommand(setStreamPath);
+        mTestLooper.dispatchAll();
+        assertThat(mNativeWrapper.getResultMessages()).contains(setStreamPath);
+
+        mNativeWrapper.clearResultMessages();
+        mTestLooper.moveTimeForward(SEND_COMMAND_RETRY_MS);
+        mTestLooper.dispatchAll();
+        assertThat(mNativeWrapper.getResultMessages()).contains(setStreamPath);
+
+        mNativeWrapper.clearResultMessages();
+        mTestLooper.moveTimeForward(SEND_COMMAND_RETRY_MS);
+        mTestLooper.dispatchAll();
+        assertThat(mNativeWrapper.getResultMessages()).contains(setStreamPath);
+
+        mNativeWrapper.clearResultMessages();
+        mTestLooper.moveTimeForward(SEND_COMMAND_RETRY_MS);
+        mTestLooper.dispatchAll();
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(setStreamPath);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java
index 1bc99b6..f608c23 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java
@@ -169,6 +169,11 @@
                     }
 
                     @Override
+                    boolean isPowerStandby() {
+                        return false;
+                    }
+
+                    @Override
                     protected HdmiCecConfig getHdmiCecConfig() {
                         return hdmiCecConfig;
                     }
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/TvToAudioSystemAvbTest.java b/services/tests/servicestests/src/com/android/server/hdmi/TvToAudioSystemAvbTest.java
index 079ef2e..024e36d 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/TvToAudioSystemAvbTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/TvToAudioSystemAvbTest.java
@@ -262,4 +262,63 @@
 
         assertThat(mNativeWrapper.getResultMessages()).isEmpty();
     }
+
+    @Test
+    public void adjustOnlyAvbEnabled_savlBecomesSupported_switchToAvb() {
+        enableAdjustOnlyAbsoluteVolumeBehavior();
+        mNativeWrapper.clearResultMessages();
+
+        // When the Audio System reports support for <Set Audio Volume Level>,
+        // the device should start the process for adopting AVB by sending <Give Audio Status>
+        receiveSetAudioVolumeLevelSupport(DeviceFeatures.FEATURE_SUPPORTED);
+        verifyGiveAudioStatusSent();
+
+        // The device should use adjust-only AVB while waiting for <Report Audio Status>
+        assertThat(mAudioManager.getDeviceVolumeBehavior(getAudioOutputDevice())).isEqualTo(
+                AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY);
+
+        // The device should switch to AVB upon receiving <Report Audio Status>
+        receiveReportAudioStatus(60, false);
+        assertThat(mAudioManager.getDeviceVolumeBehavior(getAudioOutputDevice())).isEqualTo(
+                AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE);
+
+    }
+
+    /**
+     * Tests that adjust-only AVB is not interrupted when a device's support for
+     * <Set Audio Volume Level> becomes unknown.
+     *
+     * This may currently occur when NewDeviceAction overwrites a device's info in HdmiCecNetwork.
+     * However, because replicating this scenario would be brittle and the behavior may change,
+     * this test does not simulate it and instead changes HdmiCecNetwork directly.
+     */
+    @Test
+    public void adjustOnlyAvbEnabled_savlSupportBecomesUnknown_keepUsingAdjustOnlyAvb() {
+        enableAdjustOnlyAbsoluteVolumeBehavior();
+        mNativeWrapper.clearResultMessages();
+
+        // Make sure the existing SetAudioVolumeLevelDiscoveryAction expires,
+        // so that we can check whether a new one is started.
+        mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS + 1);
+        mTestLooper.dispatchAll();
+
+        // Replace Audio System device info with one that has unknown support for all features
+        HdmiDeviceInfo updatedAudioSystemDeviceInfo =
+                mHdmiControlService.getHdmiCecNetwork().getDeviceInfo(Constants.ADDR_AUDIO_SYSTEM)
+                .toBuilder()
+                .setDeviceFeatures(DeviceFeatures.ALL_FEATURES_SUPPORT_UNKNOWN)
+                .build();
+        mHdmiControlService.getHdmiCecNetwork().addCecDevice(updatedAudioSystemDeviceInfo);
+        mTestLooper.dispatchAll();
+
+        // The device should not switch away from adjust-only AVB
+        assertThat(mAudioManager.getDeviceVolumeBehavior(getAudioOutputDevice())).isEqualTo(
+                AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY);
+
+        // The device should query support for <Set Audio Volume Level> again
+        assertThat(mNativeWrapper.getResultMessages()).contains(
+                SetAudioVolumeLevelMessage.build(
+                        getLogicalAddress(), getSystemAudioDeviceLogicalAddress(),
+                        Constants.AUDIO_VOLUME_STATUS_UNKNOWN));
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt b/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt
deleted file mode 100644
index 416b1f4..0000000
--- a/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt
+++ /dev/null
@@ -1,896 +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.server.input
-
-import android.bluetooth.BluetoothAdapter
-import android.bluetooth.BluetoothDevice
-import android.bluetooth.BluetoothManager
-import android.hardware.BatteryState.STATUS_CHARGING
-import android.hardware.BatteryState.STATUS_DISCHARGING
-import android.hardware.BatteryState.STATUS_FULL
-import android.hardware.BatteryState.STATUS_UNKNOWN
-import android.hardware.input.HostUsiVersion
-import android.hardware.input.IInputDeviceBatteryListener
-import android.hardware.input.IInputDeviceBatteryState
-import android.hardware.input.IInputDevicesChangedListener
-import android.hardware.input.IInputManager
-import android.hardware.input.InputManager
-import android.hardware.input.InputManagerGlobal
-import android.os.Binder
-import android.os.IBinder
-import android.os.test.TestLooper
-import android.platform.test.annotations.Presubmit
-import android.testing.TestableContext
-import android.view.InputDevice
-import androidx.test.core.app.ApplicationProvider
-import com.android.server.input.BatteryController.BluetoothBatteryManager
-import com.android.server.input.BatteryController.BluetoothBatteryManager.BluetoothBatteryListener
-import com.android.server.input.BatteryController.POLLING_PERIOD_MILLIS
-import com.android.server.input.BatteryController.UEventManager
-import com.android.server.input.BatteryController.UEventManager.UEventBatteryListener
-import com.android.server.input.BatteryController.USI_BATTERY_VALIDITY_DURATION_MILLIS
-import org.hamcrest.Description
-import org.hamcrest.Matcher
-import org.hamcrest.MatcherAssert.assertThat
-import org.hamcrest.Matchers
-import org.hamcrest.TypeSafeMatcher
-import org.hamcrest.core.IsEqual.equalTo
-import org.junit.After
-import org.junit.Assert.assertEquals
-import org.junit.Assert.assertFalse
-import org.junit.Assert.assertTrue
-import org.junit.Assert.fail
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-import org.mockito.ArgumentCaptor
-import org.mockito.ArgumentMatchers.any
-import org.mockito.ArgumentMatchers.notNull
-import org.mockito.Mock
-import org.mockito.Mockito.anyInt
-import org.mockito.Mockito.clearInvocations
-import org.mockito.Mockito.eq
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.never
-import org.mockito.Mockito.times
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.verifyNoMoreInteractions
-import org.mockito.Mockito.`when`
-import org.mockito.hamcrest.MockitoHamcrest
-import org.mockito.junit.MockitoJUnit
-import org.mockito.verification.VerificationMode
-
-private fun createInputDevice(
-    deviceId: Int,
-    hasBattery: Boolean = true,
-    supportsUsi: Boolean = false,
-    generation: Int = -1,
-): InputDevice =
-    InputDevice.Builder()
-        .setId(deviceId)
-        .setName("Device $deviceId")
-        .setDescriptor("descriptor $deviceId")
-        .setExternal(true)
-        .setHasBattery(hasBattery)
-        .setUsiVersion(if (supportsUsi) HostUsiVersion(1, 0) else null)
-        .setGeneration(generation)
-        .build()
-
-// Returns a matcher that helps match member variables of a class.
-private fun <T, U> memberMatcher(
-    member: String,
-    memberProvider: (T) -> U,
-    match: Matcher<U>
-): TypeSafeMatcher<T> =
-    object : TypeSafeMatcher<T>() {
-
-        override fun matchesSafely(item: T?): Boolean {
-            return match.matches(memberProvider(item!!))
-        }
-
-        override fun describeMismatchSafely(item: T?, mismatchDescription: Description?) {
-            match.describeMismatch(item, mismatchDescription)
-        }
-
-        override fun describeTo(description: Description?) {
-            match.describeTo(description?.appendText("matches member $member"))
-        }
-    }
-
-// Returns a matcher for IInputDeviceBatteryState that optionally matches some arguments.
-private fun matchesState(
-    deviceId: Int,
-    isPresent: Boolean = true,
-    status: Int? = null,
-    capacity: Float? = null,
-    eventTime: Long? = null
-): Matcher<IInputDeviceBatteryState> {
-    val batteryStateMatchers = mutableListOf<Matcher<IInputDeviceBatteryState>>(
-        memberMatcher("deviceId", { it.deviceId }, equalTo(deviceId)),
-        memberMatcher("isPresent", { it.isPresent }, equalTo(isPresent))
-    )
-    if (eventTime != null) {
-        batteryStateMatchers.add(memberMatcher("updateTime", { it.updateTime }, equalTo(eventTime)))
-    }
-    if (status != null) {
-        batteryStateMatchers.add(memberMatcher("status", { it.status }, equalTo(status)))
-    }
-    if (capacity != null) {
-        batteryStateMatchers.add(memberMatcher("capacity", { it.capacity }, equalTo(capacity)))
-    }
-    return Matchers.allOf(batteryStateMatchers)
-}
-
-private fun isInvalidBatteryState(deviceId: Int): Matcher<IInputDeviceBatteryState> =
-    matchesState(deviceId, isPresent = false, status = STATUS_UNKNOWN, capacity = Float.NaN)
-
-// Helpers used to verify interactions with a mocked battery listener.
-private fun IInputDeviceBatteryListener.verifyNotified(
-    deviceId: Int,
-    mode: VerificationMode = times(1),
-    isPresent: Boolean = true,
-    status: Int? = null,
-    capacity: Float? = null,
-    eventTime: Long? = null
-) {
-    verifyNotified(matchesState(deviceId, isPresent, status, capacity, eventTime), mode)
-}
-
-private fun IInputDeviceBatteryListener.verifyNotified(
-    matcher: Matcher<IInputDeviceBatteryState>,
-    mode: VerificationMode = times(1)
-) {
-    verify(this, mode).onBatteryStateChanged(MockitoHamcrest.argThat(matcher))
-}
-
-private fun createMockListener(): IInputDeviceBatteryListener {
-    val listener = mock(IInputDeviceBatteryListener::class.java)
-    val binder = mock(Binder::class.java)
-    `when`(listener.asBinder()).thenReturn(binder)
-    return listener
-}
-
-/**
- * Tests for {@link InputDeviceBatteryController}.
- *
- * Build/Install/Run:
- * atest FrameworksServicesTests:InputDeviceBatteryControllerTests
- */
-@Presubmit
-class BatteryControllerTests {
-    companion object {
-        const val PID = 42
-        const val DEVICE_ID = 13
-        const val SECOND_DEVICE_ID = 11
-        const val USI_DEVICE_ID = 101
-        const val SECOND_USI_DEVICE_ID = 102
-        const val BT_DEVICE_ID = 100001
-        const val SECOND_BT_DEVICE_ID = 100002
-        const val TIMESTAMP = 123456789L
-    }
-
-    @get:Rule
-    val rule = MockitoJUnit.rule()!!
-
-    @Mock
-    private lateinit var native: NativeInputManagerService
-    @Mock
-    private lateinit var iInputManager: IInputManager
-    @Mock
-    private lateinit var uEventManager: UEventManager
-    @Mock
-    private lateinit var bluetoothBatteryManager: BluetoothBatteryManager
-
-    private lateinit var batteryController: BatteryController
-    private lateinit var context: TestableContext
-    private lateinit var testLooper: TestLooper
-    private lateinit var devicesChangedListener: IInputDevicesChangedListener
-    private val deviceGenerationMap = mutableMapOf<Int /*deviceId*/, Int /*generation*/>()
-
-    @Before
-    fun setup() {
-        context = TestableContext(ApplicationProvider.getApplicationContext())
-        testLooper = TestLooper()
-        InputManagerGlobal.resetInstance(iInputManager)
-        val inputManager = InputManager(context)
-        context.addMockSystemService(InputManager::class.java, inputManager)
-        `when`(iInputManager.inputDeviceIds).then {
-            deviceGenerationMap.keys.toIntArray()
-        }
-        addInputDevice(DEVICE_ID)
-        addInputDevice(SECOND_DEVICE_ID)
-
-        batteryController = BatteryController(context, native, testLooper.looper, uEventManager,
-            bluetoothBatteryManager)
-        batteryController.systemRunning()
-        val listenerCaptor = ArgumentCaptor.forClass(IInputDevicesChangedListener::class.java)
-        verify(iInputManager).registerInputDevicesChangedListener(listenerCaptor.capture())
-        devicesChangedListener = listenerCaptor.value
-        testLooper.dispatchAll()
-    }
-
-    private fun notifyDeviceChanged(
-            deviceId: Int,
-        hasBattery: Boolean = true,
-        supportsUsi: Boolean = false
-    ) {
-        val generation = deviceGenerationMap[deviceId]?.plus(1)
-            ?: throw IllegalArgumentException("Device $deviceId was never added!")
-        deviceGenerationMap[deviceId] = generation
-
-        `when`(iInputManager.getInputDevice(deviceId))
-            .thenReturn(createInputDevice(deviceId, hasBattery, supportsUsi, generation))
-        val list = deviceGenerationMap.flatMap { listOf(it.key, it.value) }
-        if (::devicesChangedListener.isInitialized) {
-            devicesChangedListener.onInputDevicesChanged(list.toIntArray())
-        }
-    }
-
-    private fun addInputDevice(
-            deviceId: Int,
-        hasBattery: Boolean = true,
-        supportsUsi: Boolean = false,
-    ) {
-        deviceGenerationMap[deviceId] = 0
-        notifyDeviceChanged(deviceId, hasBattery, supportsUsi)
-    }
-
-    private fun createBluetoothDevice(address: String): BluetoothDevice {
-        return context.getSystemService(BluetoothManager::class.java)!!
-            .adapter.getRemoteDevice(address)
-    }
-
-    @After
-    fun tearDown() {
-        InputManagerGlobal.clearInstance()
-    }
-
-    @Test
-    fun testRegisterAndUnregisterBinderLifecycle() {
-        val listener = createMockListener()
-        // Ensure the binder lifecycle is tracked when registering a listener.
-        batteryController.registerBatteryListener(DEVICE_ID, listener, PID)
-        verify(listener.asBinder()).linkToDeath(notNull(), anyInt())
-        batteryController.registerBatteryListener(SECOND_DEVICE_ID, listener, PID)
-        verify(listener.asBinder(), times(1)).linkToDeath(notNull(), anyInt())
-
-        // Ensure the binder lifecycle stops being tracked when all devices stopped being monitored.
-        batteryController.unregisterBatteryListener(SECOND_DEVICE_ID, listener, PID)
-        verify(listener.asBinder(), never()).unlinkToDeath(notNull(), anyInt())
-        batteryController.unregisterBatteryListener(DEVICE_ID, listener, PID)
-        verify(listener.asBinder()).unlinkToDeath(notNull(), anyInt())
-    }
-
-    @Test
-    fun testOneListenerPerProcess() {
-        val listener1 = createMockListener()
-        batteryController.registerBatteryListener(DEVICE_ID, listener1, PID)
-        verify(listener1.asBinder()).linkToDeath(notNull(), anyInt())
-
-        // If a second listener is added for the same process, a security exception is thrown.
-        val listener2 = createMockListener()
-        try {
-            batteryController.registerBatteryListener(DEVICE_ID, listener2, PID)
-            fail("Expected security exception when registering more than one listener per process")
-        } catch (ignored: SecurityException) {
-        }
-    }
-
-    @Test
-    fun testProcessDeathRemovesListener() {
-        val deathRecipient = ArgumentCaptor.forClass(IBinder.DeathRecipient::class.java)
-        val listener = createMockListener()
-        batteryController.registerBatteryListener(DEVICE_ID, listener, PID)
-        verify(listener.asBinder()).linkToDeath(deathRecipient.capture(), anyInt())
-
-        // When the binder dies, the callback is unregistered.
-        deathRecipient.value!!.binderDied(listener.asBinder())
-        verify(listener.asBinder()).unlinkToDeath(notNull(), anyInt())
-
-        // It is now possible to register the same listener again.
-        batteryController.registerBatteryListener(DEVICE_ID, listener, PID)
-        verify(listener.asBinder(), times(2)).linkToDeath(notNull(), anyInt())
-    }
-
-    @Test
-    fun testRegisteringListenerNotifiesStateImmediately() {
-        `when`(native.getBatteryStatus(DEVICE_ID)).thenReturn(STATUS_FULL)
-        `when`(native.getBatteryCapacity(DEVICE_ID)).thenReturn(100)
-        val listener = createMockListener()
-        batteryController.registerBatteryListener(DEVICE_ID, listener, PID)
-        listener.verifyNotified(DEVICE_ID, status = STATUS_FULL, capacity = 1.0f)
-
-        `when`(native.getBatteryStatus(SECOND_DEVICE_ID)).thenReturn(STATUS_CHARGING)
-        `when`(native.getBatteryCapacity(SECOND_DEVICE_ID)).thenReturn(78)
-        batteryController.registerBatteryListener(SECOND_DEVICE_ID, listener, PID)
-        listener.verifyNotified(SECOND_DEVICE_ID, status = STATUS_CHARGING, capacity = 0.78f)
-    }
-
-    @Test
-    fun testListenersNotifiedOnUEventNotification() {
-        `when`(native.getBatteryDevicePath(DEVICE_ID)).thenReturn("/sys/dev/test/device1")
-        `when`(native.getBatteryStatus(DEVICE_ID)).thenReturn(STATUS_CHARGING)
-        `when`(native.getBatteryCapacity(DEVICE_ID)).thenReturn(78)
-        val listener = createMockListener()
-        val uEventListener = ArgumentCaptor.forClass(UEventBatteryListener::class.java)
-        batteryController.registerBatteryListener(DEVICE_ID, listener, PID)
-        // The device paths for UEvent notifications do not include the "/sys" prefix, so verify
-        // that the added listener is configured to match the path without that prefix.
-        verify(uEventManager)
-            .addListener(uEventListener.capture(), eq("DEVPATH=/dev/test/device1"))
-        listener.verifyNotified(DEVICE_ID, status = STATUS_CHARGING, capacity = 0.78f)
-
-        // If the battery state has changed when an UEvent is sent, the listeners are notified.
-        `when`(native.getBatteryCapacity(DEVICE_ID)).thenReturn(80)
-        uEventListener.value!!.onBatteryUEvent(TIMESTAMP)
-        listener.verifyNotified(DEVICE_ID, status = STATUS_CHARGING, capacity = 0.80f,
-            eventTime = TIMESTAMP)
-
-        // If the battery state has not changed when an UEvent is sent, the listeners are not
-        // notified.
-        clearInvocations(listener)
-        uEventListener.value!!.onBatteryUEvent(TIMESTAMP + 1)
-        verifyNoMoreInteractions(listener)
-
-        batteryController.unregisterBatteryListener(DEVICE_ID, listener, PID)
-        verify(uEventManager).removeListener(uEventListener.capture())
-        assertEquals("The same observer must be registered and unregistered",
-            uEventListener.allValues[0], uEventListener.allValues[1])
-    }
-
-    @Test
-    fun testBatteryPresenceChanged() {
-        `when`(native.getBatteryDevicePath(DEVICE_ID)).thenReturn("/test/device1")
-        `when`(native.getBatteryStatus(DEVICE_ID)).thenReturn(STATUS_CHARGING)
-        `when`(native.getBatteryCapacity(DEVICE_ID)).thenReturn(78)
-        val listener = createMockListener()
-        val uEventListener = ArgumentCaptor.forClass(UEventBatteryListener::class.java)
-        batteryController.registerBatteryListener(DEVICE_ID, listener, PID)
-        verify(uEventManager).addListener(uEventListener.capture(), eq("DEVPATH=/test/device1"))
-        listener.verifyNotified(DEVICE_ID, status = STATUS_CHARGING, capacity = 0.78f)
-
-        // If the battery presence for the InputDevice changes, the listener is notified.
-        notifyDeviceChanged(DEVICE_ID, hasBattery = false)
-        testLooper.dispatchNext()
-        listener.verifyNotified(isInvalidBatteryState(DEVICE_ID))
-        // Since the battery is no longer present, the UEventListener should be removed.
-        verify(uEventManager).removeListener(uEventListener.value)
-
-        // If the battery becomes present again, the listener is notified.
-        notifyDeviceChanged(DEVICE_ID, hasBattery = true)
-        testLooper.dispatchNext()
-        listener.verifyNotified(DEVICE_ID, mode = times(2), status = STATUS_CHARGING,
-            capacity = 0.78f)
-        // Ensure that a new UEventListener was added.
-        verify(uEventManager, times(2))
-            .addListener(uEventListener.capture(), eq("DEVPATH=/test/device1"))
-    }
-
-    @Test
-    fun testStartPollingWhenListenerIsRegistered() {
-        val listener = createMockListener()
-        `when`(native.getBatteryCapacity(DEVICE_ID)).thenReturn(78)
-        batteryController.registerBatteryListener(DEVICE_ID, listener, PID)
-        listener.verifyNotified(DEVICE_ID, capacity = 0.78f)
-
-        // Assume there is a change in the battery state. Ensure the listener is not notified
-        // while the polling period has not elapsed.
-        `when`(native.getBatteryCapacity(DEVICE_ID)).thenReturn(80)
-        testLooper.moveTimeForward(1)
-        testLooper.dispatchAll()
-        listener.verifyNotified(DEVICE_ID, mode = never(), capacity = 0.80f)
-
-        // Move the time forward so that the polling period has elapsed.
-        // The listener should be notified.
-        testLooper.moveTimeForward(POLLING_PERIOD_MILLIS - 1)
-        assertTrue("There should be a polling callbacks posted to the handler", testLooper.isIdle)
-        testLooper.dispatchNext()
-        listener.verifyNotified(DEVICE_ID, capacity = 0.80f)
-
-        // Move the time forward so that another polling period has elapsed.
-        // The battery should still be polled, but there is no change so listeners are not notified.
-        testLooper.moveTimeForward(POLLING_PERIOD_MILLIS)
-        assertTrue("There should be a polling callbacks posted to the handler", testLooper.isIdle)
-        testLooper.dispatchNext()
-        listener.verifyNotified(DEVICE_ID, mode = times(1), capacity = 0.80f)
-    }
-
-    @Test
-    fun testNoPollingWhenTheDeviceIsNotInteractive() {
-        batteryController.onInteractiveChanged(false /*interactive*/)
-
-        val listener = createMockListener()
-        `when`(native.getBatteryCapacity(DEVICE_ID)).thenReturn(78)
-        batteryController.registerBatteryListener(DEVICE_ID, listener, PID)
-        listener.verifyNotified(DEVICE_ID, capacity = 0.78f)
-
-        // The battery state changed, but we should not be polling for battery changes when the
-        // device is not interactive.
-        `when`(native.getBatteryCapacity(DEVICE_ID)).thenReturn(80)
-        testLooper.moveTimeForward(POLLING_PERIOD_MILLIS)
-        assertFalse("There should be no polling callbacks posted to the handler", testLooper.isIdle)
-        testLooper.dispatchAll()
-        listener.verifyNotified(DEVICE_ID, mode = never(), capacity = 0.80f)
-
-        // The device is now interactive. Battery state polling begins immediately.
-        batteryController.onInteractiveChanged(true /*interactive*/)
-        testLooper.dispatchNext()
-        listener.verifyNotified(DEVICE_ID, capacity = 0.80f)
-
-        // Ensure that we continue to poll for battery changes.
-        `when`(native.getBatteryCapacity(DEVICE_ID)).thenReturn(90)
-        testLooper.moveTimeForward(POLLING_PERIOD_MILLIS)
-        assertTrue("There should be a polling callbacks posted to the handler", testLooper.isIdle)
-        testLooper.dispatchNext()
-        listener.verifyNotified(DEVICE_ID, capacity = 0.90f)
-    }
-
-    @Test
-    fun testGetBatteryState() {
-        `when`(native.getBatteryStatus(DEVICE_ID)).thenReturn(STATUS_CHARGING)
-        `when`(native.getBatteryCapacity(DEVICE_ID)).thenReturn(78)
-        val batteryState = batteryController.getBatteryState(DEVICE_ID)
-        assertThat("battery state matches", batteryState,
-            matchesState(DEVICE_ID, status = STATUS_CHARGING, capacity = 0.78f))
-    }
-
-    @Test
-    fun testGetBatteryStateWithListener() {
-        val listener = createMockListener()
-        `when`(native.getBatteryStatus(DEVICE_ID)).thenReturn(STATUS_CHARGING)
-        `when`(native.getBatteryCapacity(DEVICE_ID)).thenReturn(78)
-        batteryController.registerBatteryListener(DEVICE_ID, listener, PID)
-        listener.verifyNotified(DEVICE_ID, status = STATUS_CHARGING, capacity = 0.78f)
-
-        // If getBatteryState() is called when a listener is monitoring the device and there is a
-        // change in the battery state, the listener is also notified.
-        `when`(native.getBatteryCapacity(DEVICE_ID)).thenReturn(80)
-        val batteryState = batteryController.getBatteryState(DEVICE_ID)
-        assertThat("battery matches state", batteryState,
-            matchesState(DEVICE_ID, status = STATUS_CHARGING, capacity = 0.80f))
-        listener.verifyNotified(DEVICE_ID, status = STATUS_CHARGING, capacity = 0.80f)
-    }
-
-    @Test
-    fun testUsiDeviceIsMonitoredPersistently() {
-        `when`(native.getBatteryDevicePath(USI_DEVICE_ID)).thenReturn("/sys/dev/usi_device")
-        addInputDevice(USI_DEVICE_ID, supportsUsi = true)
-        testLooper.dispatchNext()
-
-        // Even though there is no listener added for this device, it is being monitored.
-        val uEventListener = ArgumentCaptor.forClass(UEventBatteryListener::class.java)
-        verify(uEventManager)
-            .addListener(uEventListener.capture(), eq("DEVPATH=/dev/usi_device"))
-
-        // Add and remove a listener for the device.
-        val listener = createMockListener()
-        batteryController.registerBatteryListener(USI_DEVICE_ID, listener, PID)
-        batteryController.unregisterBatteryListener(USI_DEVICE_ID, listener, PID)
-
-        // The device is still being monitored.
-        verify(uEventManager, never()).removeListener(uEventListener.value)
-    }
-
-    @Test
-    fun testNoPollingWhenUsiDevicesAreMonitored() {
-        `when`(native.getBatteryDevicePath(USI_DEVICE_ID)).thenReturn("/sys/dev/usi_device")
-        addInputDevice(USI_DEVICE_ID, supportsUsi = true)
-        testLooper.dispatchNext()
-        `when`(native.getBatteryDevicePath(SECOND_USI_DEVICE_ID)).thenReturn("/sys/dev/usi_device2")
-        addInputDevice(SECOND_USI_DEVICE_ID, supportsUsi = true)
-        testLooper.dispatchNext()
-
-        testLooper.moveTimeForward(POLLING_PERIOD_MILLIS)
-        assertFalse("There should be no polling callbacks posted to the handler", testLooper.isIdle)
-
-        // Add a listener.
-        val listener = createMockListener()
-        batteryController.registerBatteryListener(USI_DEVICE_ID, listener, PID)
-
-        testLooper.moveTimeForward(POLLING_PERIOD_MILLIS)
-        assertFalse("There should be no polling callbacks posted to the handler", testLooper.isIdle)
-    }
-
-    @Test
-    fun testExpectedFlowForUsiBattery() {
-        `when`(native.getBatteryDevicePath(USI_DEVICE_ID)).thenReturn("/sys/dev/usi_device")
-        `when`(native.getBatteryStatus(USI_DEVICE_ID)).thenReturn(STATUS_DISCHARGING)
-        `when`(native.getBatteryCapacity(USI_DEVICE_ID)).thenReturn(78)
-
-        addInputDevice(USI_DEVICE_ID, supportsUsi = true)
-        testLooper.dispatchNext()
-        val uEventListener = ArgumentCaptor.forClass(UEventBatteryListener::class.java)
-        verify(uEventManager)
-            .addListener(uEventListener.capture(), eq("DEVPATH=/dev/usi_device"))
-
-        // A USI device's battery state is not valid until the first UEvent notification.
-        // Add a listener, and ensure it is notified that the battery state is not present.
-        val listener = createMockListener()
-        batteryController.registerBatteryListener(USI_DEVICE_ID, listener, PID)
-        listener.verifyNotified(isInvalidBatteryState(USI_DEVICE_ID))
-
-        // Ensure that querying for battery state also returns the same invalid result.
-        assertThat("battery state matches", batteryController.getBatteryState(USI_DEVICE_ID),
-            isInvalidBatteryState(USI_DEVICE_ID))
-
-        // There is a UEvent signaling a battery change. The battery state is now valid.
-        uEventListener.value!!.onBatteryUEvent(TIMESTAMP)
-        listener.verifyNotified(USI_DEVICE_ID, status = STATUS_DISCHARGING, capacity = 0.78f)
-        assertThat("battery state matches", batteryController.getBatteryState(USI_DEVICE_ID),
-            matchesState(USI_DEVICE_ID, status = STATUS_DISCHARGING, capacity = 0.78f))
-
-        // There is another UEvent notification. The battery state is now updated.
-        `when`(native.getBatteryCapacity(USI_DEVICE_ID)).thenReturn(64)
-        uEventListener.value!!.onBatteryUEvent(TIMESTAMP + 1)
-        listener.verifyNotified(USI_DEVICE_ID, status = STATUS_DISCHARGING, capacity = 0.64f)
-        assertThat("battery state matches", batteryController.getBatteryState(USI_DEVICE_ID),
-            matchesState(USI_DEVICE_ID, status = STATUS_DISCHARGING, capacity = 0.64f))
-
-        // The battery state is still valid after a millisecond.
-        testLooper.moveTimeForward(1)
-        testLooper.dispatchAll()
-        assertThat("battery state matches", batteryController.getBatteryState(USI_DEVICE_ID),
-            matchesState(USI_DEVICE_ID, status = STATUS_DISCHARGING, capacity = 0.64f))
-
-        // The battery is no longer present after the timeout expires.
-        testLooper.moveTimeForward(USI_BATTERY_VALIDITY_DURATION_MILLIS - 1)
-        testLooper.dispatchNext()
-        listener.verifyNotified(isInvalidBatteryState(USI_DEVICE_ID), times(2))
-        assertThat("battery state matches", batteryController.getBatteryState(USI_DEVICE_ID),
-            isInvalidBatteryState(USI_DEVICE_ID))
-    }
-
-    @Test
-    fun testStylusPresenceExtendsValidUsiBatteryState() {
-        `when`(native.getBatteryDevicePath(USI_DEVICE_ID)).thenReturn("/sys/dev/usi_device")
-        `when`(native.getBatteryStatus(USI_DEVICE_ID)).thenReturn(STATUS_DISCHARGING)
-        `when`(native.getBatteryCapacity(USI_DEVICE_ID)).thenReturn(78)
-
-        addInputDevice(USI_DEVICE_ID, supportsUsi = true)
-        testLooper.dispatchNext()
-        val uEventListener = ArgumentCaptor.forClass(UEventBatteryListener::class.java)
-        verify(uEventManager)
-            .addListener(uEventListener.capture(), eq("DEVPATH=/dev/usi_device"))
-
-        // There is a UEvent signaling a battery change. The battery state is now valid.
-        uEventListener.value!!.onBatteryUEvent(TIMESTAMP)
-        val listener = createMockListener()
-        batteryController.registerBatteryListener(USI_DEVICE_ID, listener, PID)
-        listener.verifyNotified(USI_DEVICE_ID, status = STATUS_DISCHARGING, capacity = 0.78f)
-        assertThat("battery state matches", batteryController.getBatteryState(USI_DEVICE_ID),
-            matchesState(USI_DEVICE_ID, status = STATUS_DISCHARGING, capacity = 0.78f))
-
-        // Stylus presence is detected before the validity timeout expires.
-        testLooper.moveTimeForward(100)
-        testLooper.dispatchAll()
-        batteryController.notifyStylusGestureStarted(USI_DEVICE_ID, TIMESTAMP)
-
-        // Ensure that timeout was extended, and the battery state is now valid for longer.
-        testLooper.moveTimeForward(USI_BATTERY_VALIDITY_DURATION_MILLIS - 100)
-        testLooper.dispatchAll()
-        assertThat("battery state matches", batteryController.getBatteryState(USI_DEVICE_ID),
-            matchesState(USI_DEVICE_ID, status = STATUS_DISCHARGING, capacity = 0.78f))
-
-        // Ensure the validity period expires after the expected amount of time.
-        testLooper.moveTimeForward(100)
-        testLooper.dispatchNext()
-        listener.verifyNotified(isInvalidBatteryState(USI_DEVICE_ID))
-        assertThat("battery state matches", batteryController.getBatteryState(USI_DEVICE_ID),
-            isInvalidBatteryState(USI_DEVICE_ID))
-    }
-
-    @Test
-    fun testStylusPresenceMakesUsiBatteryStateValid() {
-        `when`(native.getBatteryDevicePath(USI_DEVICE_ID)).thenReturn("/sys/dev/usi_device")
-        `when`(native.getBatteryStatus(USI_DEVICE_ID)).thenReturn(STATUS_DISCHARGING)
-        `when`(native.getBatteryCapacity(USI_DEVICE_ID)).thenReturn(78)
-
-        addInputDevice(USI_DEVICE_ID, supportsUsi = true)
-        testLooper.dispatchNext()
-        val uEventListener = ArgumentCaptor.forClass(UEventBatteryListener::class.java)
-        verify(uEventManager)
-            .addListener(uEventListener.capture(), eq("DEVPATH=/dev/usi_device"))
-
-        // The USI battery state is initially invalid.
-        val listener = createMockListener()
-        batteryController.registerBatteryListener(USI_DEVICE_ID, listener, PID)
-        listener.verifyNotified(isInvalidBatteryState(USI_DEVICE_ID))
-        assertThat("battery state matches", batteryController.getBatteryState(USI_DEVICE_ID),
-            isInvalidBatteryState(USI_DEVICE_ID))
-
-        // A stylus presence is detected. This validates the battery state.
-        batteryController.notifyStylusGestureStarted(USI_DEVICE_ID, TIMESTAMP)
-
-        listener.verifyNotified(USI_DEVICE_ID, status = STATUS_DISCHARGING, capacity = 0.78f)
-        assertThat("battery state matches", batteryController.getBatteryState(USI_DEVICE_ID),
-            matchesState(USI_DEVICE_ID, status = STATUS_DISCHARGING, capacity = 0.78f))
-    }
-
-    @Test
-    fun testStylusPresenceDoesNotMakeUsiBatteryStateValidAtBoot() {
-        `when`(native.getBatteryDevicePath(USI_DEVICE_ID)).thenReturn("/sys/dev/usi_device")
-        // At boot, the USI device always reports a capacity value of 0.
-        `when`(native.getBatteryStatus(USI_DEVICE_ID)).thenReturn(STATUS_UNKNOWN)
-        `when`(native.getBatteryCapacity(USI_DEVICE_ID)).thenReturn(0)
-
-        addInputDevice(USI_DEVICE_ID, supportsUsi = true)
-        testLooper.dispatchNext()
-        val uEventListener = ArgumentCaptor.forClass(UEventBatteryListener::class.java)
-        verify(uEventManager)
-            .addListener(uEventListener.capture(), eq("DEVPATH=/dev/usi_device"))
-
-        // The USI battery state is initially invalid.
-        val listener = createMockListener()
-        batteryController.registerBatteryListener(USI_DEVICE_ID, listener, PID)
-        listener.verifyNotified(isInvalidBatteryState(USI_DEVICE_ID))
-        assertThat("battery state matches", batteryController.getBatteryState(USI_DEVICE_ID),
-            isInvalidBatteryState(USI_DEVICE_ID))
-
-        // Since the capacity reported is 0, stylus presence does not validate the battery state.
-        batteryController.notifyStylusGestureStarted(USI_DEVICE_ID, TIMESTAMP)
-
-        assertThat("battery state matches", batteryController.getBatteryState(USI_DEVICE_ID),
-            isInvalidBatteryState(USI_DEVICE_ID))
-
-        // However, if a UEvent reports a battery capacity of 0, the battery state is now valid.
-        uEventListener.value!!.onBatteryUEvent(TIMESTAMP)
-        listener.verifyNotified(USI_DEVICE_ID, status = STATUS_UNKNOWN, capacity = 0f)
-        assertThat("battery state matches", batteryController.getBatteryState(USI_DEVICE_ID),
-            matchesState(USI_DEVICE_ID, status = STATUS_UNKNOWN, capacity = 0f))
-    }
-
-    @Test
-    fun testRegisterBluetoothListenerForMonitoredBluetoothDevices() {
-        `when`(iInputManager.getInputDeviceBluetoothAddress(BT_DEVICE_ID))
-            .thenReturn("AA:BB:CC:DD:EE:FF")
-        `when`(iInputManager.getInputDeviceBluetoothAddress(SECOND_BT_DEVICE_ID))
-            .thenReturn("11:22:33:44:55:66")
-        addInputDevice(BT_DEVICE_ID)
-        testLooper.dispatchNext()
-        addInputDevice(SECOND_BT_DEVICE_ID)
-        testLooper.dispatchNext()
-
-        // Listen to a non-Bluetooth device and ensure that the BT battery listener is not added
-        // when there are no monitored BT devices.
-        val listener = createMockListener()
-        batteryController.registerBatteryListener(DEVICE_ID, listener, PID)
-        verify(bluetoothBatteryManager, never()).addBatteryListener(any())
-
-        val bluetoothListener = ArgumentCaptor.forClass(BluetoothBatteryListener::class.java)
-
-        // The BT battery listener is added when the first BT input device is monitored.
-        batteryController.registerBatteryListener(BT_DEVICE_ID, listener, PID)
-        verify(bluetoothBatteryManager).addBatteryListener(bluetoothListener.capture())
-
-        // The BT listener is only added once for all BT devices.
-        batteryController.registerBatteryListener(SECOND_BT_DEVICE_ID, listener, PID)
-        verify(bluetoothBatteryManager, times(1)).addBatteryListener(any())
-
-        // The BT listener is only removed when there are no monitored BT devices.
-        batteryController.unregisterBatteryListener(BT_DEVICE_ID, listener, PID)
-        verify(bluetoothBatteryManager, never()).removeBatteryListener(any())
-
-        `when`(iInputManager.getInputDeviceBluetoothAddress(SECOND_BT_DEVICE_ID))
-            .thenReturn(null)
-        notifyDeviceChanged(SECOND_BT_DEVICE_ID)
-        testLooper.dispatchNext()
-        verify(bluetoothBatteryManager).removeBatteryListener(bluetoothListener.value)
-    }
-
-    @Test
-    fun testNotifiesBluetoothBatteryChanges() {
-        `when`(iInputManager.getInputDeviceBluetoothAddress(BT_DEVICE_ID))
-            .thenReturn("AA:BB:CC:DD:EE:FF")
-        `when`(bluetoothBatteryManager.getBatteryLevel(eq("AA:BB:CC:DD:EE:FF"))).thenReturn(21)
-        addInputDevice(BT_DEVICE_ID)
-        val bluetoothListener = ArgumentCaptor.forClass(BluetoothBatteryListener::class.java)
-        val listener = createMockListener()
-        batteryController.registerBatteryListener(BT_DEVICE_ID, listener, PID)
-        verify(bluetoothBatteryManager).addBatteryListener(bluetoothListener.capture())
-        listener.verifyNotified(BT_DEVICE_ID, capacity = 0.21f)
-
-        // When the state has not changed, the listener is not notified again.
-        bluetoothListener.value!!.onBluetoothBatteryChanged(TIMESTAMP, "AA:BB:CC:DD:EE:FF", 21)
-        listener.verifyNotified(BT_DEVICE_ID, mode = times(1), capacity = 0.21f)
-
-        bluetoothListener.value!!.onBluetoothBatteryChanged(TIMESTAMP, "AA:BB:CC:DD:EE:FF", 25)
-        listener.verifyNotified(BT_DEVICE_ID, capacity = 0.25f)
-    }
-
-    @Test
-    fun testBluetoothBatteryIsPrioritized() {
-        `when`(native.getBatteryDevicePath(BT_DEVICE_ID)).thenReturn("/sys/dev/bt_device")
-        `when`(iInputManager.getInputDeviceBluetoothAddress(BT_DEVICE_ID))
-            .thenReturn("AA:BB:CC:DD:EE:FF")
-        `when`(bluetoothBatteryManager.getBatteryLevel(eq("AA:BB:CC:DD:EE:FF"))).thenReturn(21)
-        `when`(native.getBatteryCapacity(BT_DEVICE_ID)).thenReturn(98)
-        addInputDevice(BT_DEVICE_ID)
-        val bluetoothListener = ArgumentCaptor.forClass(BluetoothBatteryListener::class.java)
-        val listener = createMockListener()
-        val uEventListener = ArgumentCaptor.forClass(UEventBatteryListener::class.java)
-
-        // When the device is first monitored and both native and BT battery is available,
-        // the latter is used.
-        batteryController.registerBatteryListener(BT_DEVICE_ID, listener, PID)
-        verify(bluetoothBatteryManager).addBatteryListener(bluetoothListener.capture())
-        verify(uEventManager).addListener(uEventListener.capture(), any())
-        listener.verifyNotified(BT_DEVICE_ID, capacity = 0.21f)
-        assertThat("battery state matches", batteryController.getBatteryState(BT_DEVICE_ID),
-            matchesState(BT_DEVICE_ID, capacity = 0.21f))
-
-        // If only the native battery state changes the listener is not notified.
-        `when`(native.getBatteryCapacity(BT_DEVICE_ID)).thenReturn(97)
-        uEventListener.value!!.onBatteryUEvent(TIMESTAMP)
-        listener.verifyNotified(BT_DEVICE_ID, mode = times(1), capacity = 0.21f)
-        assertThat("battery state matches", batteryController.getBatteryState(BT_DEVICE_ID),
-            matchesState(BT_DEVICE_ID, capacity = 0.21f))
-    }
-
-    @Test
-    fun testFallBackToNativeBatteryStateWhenBluetoothStateInvalid() {
-        `when`(native.getBatteryDevicePath(BT_DEVICE_ID)).thenReturn("/sys/dev/bt_device")
-        `when`(iInputManager.getInputDeviceBluetoothAddress(BT_DEVICE_ID))
-            .thenReturn("AA:BB:CC:DD:EE:FF")
-        `when`(bluetoothBatteryManager.getBatteryLevel(eq("AA:BB:CC:DD:EE:FF"))).thenReturn(21)
-        `when`(native.getBatteryCapacity(BT_DEVICE_ID)).thenReturn(98)
-        addInputDevice(BT_DEVICE_ID)
-        val bluetoothListener = ArgumentCaptor.forClass(BluetoothBatteryListener::class.java)
-        val listener = createMockListener()
-        val uEventListener = ArgumentCaptor.forClass(UEventBatteryListener::class.java)
-
-        batteryController.registerBatteryListener(BT_DEVICE_ID, listener, PID)
-        verify(bluetoothBatteryManager).addBatteryListener(bluetoothListener.capture())
-        verify(uEventManager).addListener(uEventListener.capture(), any())
-        listener.verifyNotified(BT_DEVICE_ID, capacity = 0.21f)
-
-        // Fall back to the native state when BT is off.
-        bluetoothListener.value!!.onBluetoothBatteryChanged(TIMESTAMP, "AA:BB:CC:DD:EE:FF",
-            BluetoothDevice.BATTERY_LEVEL_BLUETOOTH_OFF)
-        listener.verifyNotified(BT_DEVICE_ID, capacity = 0.98f)
-
-        bluetoothListener.value!!.onBluetoothBatteryChanged(TIMESTAMP, "AA:BB:CC:DD:EE:FF", 22)
-        verify(bluetoothBatteryManager).addBatteryListener(bluetoothListener.capture())
-        listener.verifyNotified(BT_DEVICE_ID, capacity = 0.22f)
-
-        // Fall back to the native state when BT battery is unknown.
-        bluetoothListener.value!!.onBluetoothBatteryChanged(TIMESTAMP, "AA:BB:CC:DD:EE:FF",
-            BluetoothDevice.BATTERY_LEVEL_UNKNOWN)
-        listener.verifyNotified(BT_DEVICE_ID, mode = times(2), capacity = 0.98f)
-    }
-
-    @Test
-    fun testRegisterBluetoothMetadataListenerForMonitoredBluetoothDevices() {
-        `when`(iInputManager.getInputDeviceBluetoothAddress(BT_DEVICE_ID))
-            .thenReturn("AA:BB:CC:DD:EE:FF")
-        `when`(iInputManager.getInputDeviceBluetoothAddress(SECOND_BT_DEVICE_ID))
-            .thenReturn("11:22:33:44:55:66")
-        addInputDevice(BT_DEVICE_ID)
-        testLooper.dispatchNext()
-        addInputDevice(SECOND_BT_DEVICE_ID)
-        testLooper.dispatchNext()
-
-        // Listen to a non-Bluetooth device and ensure that the metadata listener is not added when
-        // there are no monitored BT devices.
-        val listener = createMockListener()
-        batteryController.registerBatteryListener(DEVICE_ID, listener, PID)
-        verify(bluetoothBatteryManager, never()).addMetadataListener(any(), any())
-
-        val metadataListener1 = ArgumentCaptor.forClass(
-            BluetoothAdapter.OnMetadataChangedListener::class.java)
-        val metadataListener2 = ArgumentCaptor.forClass(
-            BluetoothAdapter.OnMetadataChangedListener::class.java)
-
-        // The metadata listener is added when the first BT input device is monitored.
-        batteryController.registerBatteryListener(BT_DEVICE_ID, listener, PID)
-        verify(bluetoothBatteryManager)
-            .addMetadataListener(eq("AA:BB:CC:DD:EE:FF"), metadataListener1.capture())
-
-        // There is one metadata listener added for each BT device.
-        batteryController.registerBatteryListener(SECOND_BT_DEVICE_ID, listener, PID)
-        verify(bluetoothBatteryManager)
-            .addMetadataListener(eq("11:22:33:44:55:66"), metadataListener2.capture())
-
-        // The metadata listener is removed when the device is no longer monitored.
-        batteryController.unregisterBatteryListener(BT_DEVICE_ID, listener, PID)
-        verify(bluetoothBatteryManager)
-            .removeMetadataListener("AA:BB:CC:DD:EE:FF", metadataListener1.value)
-
-        `when`(iInputManager.getInputDeviceBluetoothAddress(SECOND_BT_DEVICE_ID))
-            .thenReturn(null)
-        notifyDeviceChanged(SECOND_BT_DEVICE_ID)
-        testLooper.dispatchNext()
-        verify(bluetoothBatteryManager)
-            .removeMetadataListener("11:22:33:44:55:66", metadataListener2.value)
-    }
-
-    @Test
-    fun testNotifiesBluetoothMetadataBatteryChanges() {
-        `when`(iInputManager.getInputDeviceBluetoothAddress(BT_DEVICE_ID))
-            .thenReturn("AA:BB:CC:DD:EE:FF")
-        `when`(bluetoothBatteryManager.getMetadata("AA:BB:CC:DD:EE:FF",
-                BluetoothDevice.METADATA_MAIN_BATTERY))
-            .thenReturn("21".toByteArray())
-        addInputDevice(BT_DEVICE_ID)
-        val metadataListener = ArgumentCaptor.forClass(
-            BluetoothAdapter.OnMetadataChangedListener::class.java)
-        val listener = createMockListener()
-        val bluetoothDevice = createBluetoothDevice("AA:BB:CC:DD:EE:FF")
-        batteryController.registerBatteryListener(BT_DEVICE_ID, listener, PID)
-        verify(bluetoothBatteryManager)
-            .addMetadataListener(eq("AA:BB:CC:DD:EE:FF"), metadataListener.capture())
-        listener.verifyNotified(BT_DEVICE_ID, capacity = 0.21f, status = STATUS_UNKNOWN)
-
-        // When the state has not changed, the listener is not notified again.
-        metadataListener.value!!.onMetadataChanged(
-            bluetoothDevice, BluetoothDevice.METADATA_MAIN_BATTERY, "21".toByteArray())
-        listener.verifyNotified(BT_DEVICE_ID, mode = times(1), capacity = 0.21f)
-
-        metadataListener.value!!.onMetadataChanged(
-            bluetoothDevice, BluetoothDevice.METADATA_MAIN_BATTERY, "25".toByteArray())
-        listener.verifyNotified(BT_DEVICE_ID, capacity = 0.25f, status = STATUS_UNKNOWN)
-
-        metadataListener.value!!.onMetadataChanged(
-            bluetoothDevice, BluetoothDevice.METADATA_MAIN_CHARGING, "true".toByteArray())
-        listener.verifyNotified(BT_DEVICE_ID, capacity = 0.25f, status = STATUS_CHARGING)
-
-        metadataListener.value!!.onMetadataChanged(
-            bluetoothDevice, BluetoothDevice.METADATA_MAIN_CHARGING, "false".toByteArray())
-        listener.verifyNotified(BT_DEVICE_ID, capacity = 0.25f, status = STATUS_DISCHARGING)
-
-        metadataListener.value!!.onMetadataChanged(
-            bluetoothDevice, BluetoothDevice.METADATA_MAIN_CHARGING, null)
-        listener.verifyNotified(BT_DEVICE_ID, mode = times(2), capacity = 0.25f,
-            status = STATUS_UNKNOWN)
-    }
-
-    @Test
-    fun testBluetoothMetadataBatteryIsPrioritized() {
-        `when`(iInputManager.getInputDeviceBluetoothAddress(BT_DEVICE_ID))
-            .thenReturn("AA:BB:CC:DD:EE:FF")
-        `when`(bluetoothBatteryManager.getBatteryLevel(eq("AA:BB:CC:DD:EE:FF"))).thenReturn(21)
-        `when`(bluetoothBatteryManager.getMetadata("AA:BB:CC:DD:EE:FF",
-                BluetoothDevice.METADATA_MAIN_BATTERY))
-            .thenReturn("22".toByteArray())
-        addInputDevice(BT_DEVICE_ID)
-        val bluetoothListener = ArgumentCaptor.forClass(BluetoothBatteryListener::class.java)
-        val metadataListener = ArgumentCaptor.forClass(
-            BluetoothAdapter.OnMetadataChangedListener::class.java)
-        val listener = createMockListener()
-        val bluetoothDevice = createBluetoothDevice("AA:BB:CC:DD:EE:FF")
-        batteryController.registerBatteryListener(BT_DEVICE_ID, listener, PID)
-
-        verify(bluetoothBatteryManager).addBatteryListener(bluetoothListener.capture())
-        verify(bluetoothBatteryManager)
-            .addMetadataListener(eq("AA:BB:CC:DD:EE:FF"), metadataListener.capture())
-        listener.verifyNotified(BT_DEVICE_ID, capacity = 0.22f)
-
-        // A change in the Bluetooth battery level has no effect while there is a valid battery
-        // level obtained through the metadata.
-        bluetoothListener.value!!.onBluetoothBatteryChanged(TIMESTAMP, "AA:BB:CC:DD:EE:FF", 23)
-        listener.verifyNotified(BT_DEVICE_ID, mode = never(), capacity = 0.23f)
-
-        metadataListener.value!!.onMetadataChanged(
-            bluetoothDevice, BluetoothDevice.METADATA_MAIN_BATTERY, "24".toByteArray())
-        listener.verifyNotified(BT_DEVICE_ID, capacity = 0.24f)
-
-        // When the battery level from the metadata is no longer valid, we fall back to using the
-        // Bluetooth battery level.
-        metadataListener.value!!.onMetadataChanged(
-            bluetoothDevice, BluetoothDevice.METADATA_MAIN_BATTERY, null)
-        listener.verifyNotified(BT_DEVICE_ID, capacity = 0.23f)
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/input/ConfigurationProcessorTest.java b/services/tests/servicestests/src/com/android/server/input/ConfigurationProcessorTest.java
deleted file mode 100644
index 2bd4a3a..0000000
--- a/services/tests/servicestests/src/com/android/server/input/ConfigurationProcessorTest.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.input;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.fail;
-
-import android.content.Context;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.InputStream;
-import java.util.Map;
-
-/**
- * Build/Install/Run:
- * atest ConfigurationProcessorTest
- */
-@RunWith(AndroidJUnit4.class)
-public class ConfigurationProcessorTest {
-
-    private Context mContext;
-
-    @Before
-    public void setUp() throws Exception {
-        mContext = InstrumentationRegistry.getContext();
-    }
-
-    @Test
-    public void testGetInputPortAssociations() {
-        final int res = com.android.frameworks.servicestests.R.raw.input_port_associations;
-        InputStream xml = mContext.getResources().openRawResource(res);
-        Map<String, Integer> associations = null;
-        try {
-            associations = ConfigurationProcessor.processInputPortAssociations(xml);
-        } catch (Exception e) {
-            fail("Could not process xml file for input associations");
-        }
-        assertNotNull(associations);
-        assertEquals(2, associations.size());
-        assertEquals(0, associations.get("USB1").intValue());
-        assertEquals(1, associations.get("USB2").intValue());
-    }
-
-    @Test
-    public void testGetInputPortAssociationsBadDisplayport() {
-        final int res =
-                com.android.frameworks.servicestests.R.raw.input_port_associations_bad_displayport;
-        InputStream xml = mContext.getResources().openRawResource(res);
-        Map<String, Integer> associations = null;
-        try {
-            associations = ConfigurationProcessor.processInputPortAssociations(xml);
-        } catch (Exception e) {
-            fail("Could not process xml file for input associations");
-        }
-        assertNotNull(associations);
-        assertEquals(0, associations.size());
-    }
-
-    @Test
-    public void testGetInputPortAssociationsEmptyConfig() {
-        final int res = com.android.frameworks.servicestests.R.raw.input_port_associations_bad_xml;
-        InputStream xml = mContext.getResources().openRawResource(res);
-        try {
-            ConfigurationProcessor.processInputPortAssociations(xml);
-            fail("Parsing should fail, because xml contains bad data");
-        } catch (Exception e) {
-            // This is expected
-        }
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/input/InputManagerServiceTests.kt b/services/tests/servicestests/src/com/android/server/input/InputManagerServiceTests.kt
deleted file mode 100644
index 677144c..0000000
--- a/services/tests/servicestests/src/com/android/server/input/InputManagerServiceTests.kt
+++ /dev/null
@@ -1,339 +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.server.input
-
-
-import android.content.Context
-import android.content.ContextWrapper
-import android.hardware.display.DisplayViewport
-import android.os.IInputConstants
-import android.os.test.TestLooper
-import android.platform.test.annotations.Presubmit
-import android.view.Display
-import android.view.PointerIcon
-import androidx.test.InstrumentationRegistry
-import com.google.common.truth.Truth.assertThat
-import org.junit.Assert.assertFalse
-import org.junit.Assert.assertTrue
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-import org.mockito.ArgumentMatchers.any
-import org.mockito.ArgumentMatchers.anyInt
-import org.mockito.ArgumentMatchers.eq
-import org.mockito.Mock
-import org.mockito.Mockito.`when`
-import org.mockito.Mockito.clearInvocations
-import org.mockito.Mockito.doAnswer
-import org.mockito.Mockito.never
-import org.mockito.Mockito.spy
-import org.mockito.Mockito.times
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.verifyNoMoreInteractions
-import org.mockito.junit.MockitoJUnit
-import java.util.concurrent.CountDownLatch
-import java.util.concurrent.TimeUnit
-
-/**
- * Tests for {@link InputManagerService}.
- *
- * Build/Install/Run:
- * atest FrameworksServicesTests:InputManagerServiceTests
- */
-@Presubmit
-class InputManagerServiceTests {
-
-    @get:Rule
-    val rule = MockitoJUnit.rule()!!
-
-    @Mock
-    private lateinit var native: NativeInputManagerService
-
-    @Mock
-    private lateinit var wmCallbacks: InputManagerService.WindowManagerCallbacks
-
-    private lateinit var service: InputManagerService
-    private lateinit var localService: InputManagerInternal
-    private lateinit var context: Context
-    private lateinit var testLooper: TestLooper
-
-    @Before
-    fun setup() {
-        context = spy(ContextWrapper(InstrumentationRegistry.getContext()))
-        testLooper = TestLooper()
-        service =
-            InputManagerService(object : InputManagerService.Injector(context, testLooper.looper) {
-                override fun getNativeService(
-                    service: InputManagerService?
-                ): NativeInputManagerService {
-                    return native
-                }
-
-                override fun registerLocalService(service: InputManagerInternal?) {
-                    localService = service!!
-                }
-            })
-        assertTrue("Local service must be registered", this::localService.isInitialized)
-        service.setWindowManagerCallbacks(wmCallbacks)
-    }
-
-    @Test
-    fun testPointerDisplayUpdatesWhenDisplayViewportsChanged() {
-        val displayId = 123
-        `when`(wmCallbacks.pointerDisplayId).thenReturn(displayId)
-        val viewports = listOf<DisplayViewport>()
-        localService.setDisplayViewports(viewports)
-        verify(native).setDisplayViewports(any(Array<DisplayViewport>::class.java))
-        verify(native).setPointerDisplayId(displayId)
-
-        val x = 42f
-        val y = 314f
-        service.onPointerDisplayIdChanged(displayId, x, y)
-        testLooper.dispatchNext()
-        verify(wmCallbacks).notifyPointerDisplayIdChanged(displayId, x, y)
-    }
-
-    @Test
-    fun testSetVirtualMousePointerDisplayId() {
-        // Set the virtual mouse pointer displayId, and ensure that the calling thread is blocked
-        // until the native callback happens.
-        var countDownLatch = CountDownLatch(1)
-        val overrideDisplayId = 123
-        Thread {
-            assertTrue("Setting virtual pointer display should succeed",
-                localService.setVirtualMousePointerDisplayId(overrideDisplayId))
-            countDownLatch.countDown()
-        }.start()
-        assertFalse("Setting virtual pointer display should block",
-            countDownLatch.await(100, TimeUnit.MILLISECONDS))
-
-        val x = 42f
-        val y = 314f
-        service.onPointerDisplayIdChanged(overrideDisplayId, x, y)
-        testLooper.dispatchNext()
-        verify(wmCallbacks).notifyPointerDisplayIdChanged(overrideDisplayId, x, y)
-        assertTrue("Native callback unblocks calling thread",
-            countDownLatch.await(100, TimeUnit.MILLISECONDS))
-        verify(native).setPointerDisplayId(overrideDisplayId)
-
-        // Ensure that setting the same override again succeeds immediately.
-        assertTrue("Setting the same virtual mouse pointer displayId again should succeed",
-            localService.setVirtualMousePointerDisplayId(overrideDisplayId))
-
-        // Ensure that we did not query WM for the pointerDisplayId when setting the override
-        verify(wmCallbacks, never()).pointerDisplayId
-
-        // Unset the virtual mouse pointer displayId, and ensure that we query WM for the new
-        // pointer displayId and the calling thread is blocked until the native callback happens.
-        countDownLatch = CountDownLatch(1)
-        val pointerDisplayId = 42
-        `when`(wmCallbacks.pointerDisplayId).thenReturn(pointerDisplayId)
-        Thread {
-            assertTrue("Unsetting virtual mouse pointer displayId should succeed",
-                localService.setVirtualMousePointerDisplayId(Display.INVALID_DISPLAY))
-            countDownLatch.countDown()
-        }.start()
-        assertFalse("Unsetting virtual mouse pointer displayId should block",
-            countDownLatch.await(100, TimeUnit.MILLISECONDS))
-
-        service.onPointerDisplayIdChanged(pointerDisplayId, x, y)
-        testLooper.dispatchNext()
-        verify(wmCallbacks).notifyPointerDisplayIdChanged(pointerDisplayId, x, y)
-        assertTrue("Native callback unblocks calling thread",
-            countDownLatch.await(100, TimeUnit.MILLISECONDS))
-        verify(native).setPointerDisplayId(pointerDisplayId)
-    }
-
-    @Test
-    fun testSetVirtualMousePointerDisplayId_unsuccessfulUpdate() {
-        // Set the virtual mouse pointer displayId, and ensure that the calling thread is blocked
-        // until the native callback happens.
-        val countDownLatch = CountDownLatch(1)
-        val overrideDisplayId = 123
-        Thread {
-            assertFalse("Setting virtual pointer display should be unsuccessful",
-                localService.setVirtualMousePointerDisplayId(overrideDisplayId))
-            countDownLatch.countDown()
-        }.start()
-        assertFalse("Setting virtual pointer display should block",
-            countDownLatch.await(100, TimeUnit.MILLISECONDS))
-
-        val x = 42f
-        val y = 314f
-        // Assume the native callback updates the pointerDisplayId to the incorrect value.
-        service.onPointerDisplayIdChanged(Display.INVALID_DISPLAY, x, y)
-        testLooper.dispatchNext()
-        verify(wmCallbacks).notifyPointerDisplayIdChanged(Display.INVALID_DISPLAY, x, y)
-        assertTrue("Native callback unblocks calling thread",
-            countDownLatch.await(100, TimeUnit.MILLISECONDS))
-        verify(native).setPointerDisplayId(overrideDisplayId)
-    }
-
-    @Test
-    fun testSetVirtualMousePointerDisplayId_competingRequests() {
-        val firstRequestSyncLatch = CountDownLatch(1)
-        doAnswer {
-            firstRequestSyncLatch.countDown()
-        }.`when`(native).setPointerDisplayId(anyInt())
-
-        val firstRequestLatch = CountDownLatch(1)
-        val firstOverride = 123
-        Thread {
-            assertFalse("Setting virtual pointer display from thread 1 should be unsuccessful",
-                localService.setVirtualMousePointerDisplayId(firstOverride))
-            firstRequestLatch.countDown()
-        }.start()
-        assertFalse("Setting virtual pointer display should block",
-            firstRequestLatch.await(100, TimeUnit.MILLISECONDS))
-
-        assertTrue("Wait for first thread's request should succeed",
-            firstRequestSyncLatch.await(100, TimeUnit.MILLISECONDS))
-
-        val secondRequestLatch = CountDownLatch(1)
-        val secondOverride = 42
-        Thread {
-            assertTrue("Setting virtual mouse pointer from thread 2 should be successful",
-                localService.setVirtualMousePointerDisplayId(secondOverride))
-            secondRequestLatch.countDown()
-        }.start()
-        assertFalse("Setting virtual mouse pointer should block",
-            secondRequestLatch.await(100, TimeUnit.MILLISECONDS))
-
-        val x = 42f
-        val y = 314f
-        // Assume the native callback updates directly to the second request.
-        service.onPointerDisplayIdChanged(secondOverride, x, y)
-        testLooper.dispatchNext()
-        verify(wmCallbacks).notifyPointerDisplayIdChanged(secondOverride, x, y)
-        assertTrue("Native callback unblocks first thread",
-            firstRequestLatch.await(100, TimeUnit.MILLISECONDS))
-        assertTrue("Native callback unblocks second thread",
-            secondRequestLatch.await(100, TimeUnit.MILLISECONDS))
-        verify(native, times(2)).setPointerDisplayId(anyInt())
-    }
-
-    @Test
-    fun onDisplayRemoved_resetAllAdditionalInputProperties() {
-        setVirtualMousePointerDisplayIdAndVerify(10)
-
-        localService.setPointerIconVisible(false, 10)
-        verify(native).setPointerIconType(eq(PointerIcon.TYPE_NULL))
-        localService.setPointerAcceleration(5f, 10)
-        verify(native).setPointerAcceleration(eq(5f))
-
-        service.onDisplayRemoved(10)
-        verify(native).displayRemoved(eq(10))
-        verify(native).setPointerIconType(eq(PointerIcon.TYPE_NOT_SPECIFIED))
-        verify(native).setPointerAcceleration(
-            eq(IInputConstants.DEFAULT_POINTER_ACCELERATION.toFloat()))
-        verifyNoMoreInteractions(native)
-
-        // This call should not block because the virtual mouse pointer override was never removed.
-        localService.setVirtualMousePointerDisplayId(10)
-
-        verify(native).setPointerDisplayId(eq(10))
-        verifyNoMoreInteractions(native)
-    }
-
-    @Test
-    fun updateAdditionalInputPropertiesForOverrideDisplay() {
-        setVirtualMousePointerDisplayIdAndVerify(10)
-
-        localService.setPointerIconVisible(false, 10)
-        verify(native).setPointerIconType(eq(PointerIcon.TYPE_NULL))
-        localService.setPointerAcceleration(5f, 10)
-        verify(native).setPointerAcceleration(eq(5f))
-
-        localService.setPointerIconVisible(true, 10)
-        verify(native).setPointerIconType(eq(PointerIcon.TYPE_NOT_SPECIFIED))
-        localService.setPointerAcceleration(1f, 10)
-        verify(native).setPointerAcceleration(eq(1f))
-
-        // Verify that setting properties on a different display is not propagated until the
-        // pointer is moved to that display.
-        localService.setPointerIconVisible(false, 20)
-        localService.setPointerAcceleration(6f, 20)
-        verifyNoMoreInteractions(native)
-
-        clearInvocations(native)
-        setVirtualMousePointerDisplayIdAndVerify(20)
-
-        verify(native).setPointerIconType(eq(PointerIcon.TYPE_NULL))
-        verify(native).setPointerAcceleration(eq(6f))
-    }
-
-    @Test
-    fun setAdditionalInputPropertiesBeforeOverride() {
-        localService.setPointerIconVisible(false, 10)
-        localService.setPointerAcceleration(5f, 10)
-
-        verifyNoMoreInteractions(native)
-
-        setVirtualMousePointerDisplayIdAndVerify(10)
-
-        verify(native).setPointerIconType(eq(PointerIcon.TYPE_NULL))
-        verify(native).setPointerAcceleration(eq(5f))
-    }
-
-    @Test
-    fun setDeviceTypeAssociation_setsDeviceTypeAssociation() {
-        val inputPort = "inputPort"
-        val type = "type"
-
-        localService.setTypeAssociation(inputPort, type)
-
-        assertThat(service.getDeviceTypeAssociations()).asList().containsExactly(inputPort, type)
-            .inOrder()
-    }
-
-    @Test
-    fun setAndUnsetDeviceTypeAssociation_deviceTypeAssociationIsMissing() {
-        val inputPort = "inputPort"
-        val type = "type"
-
-        localService.setTypeAssociation(inputPort, type)
-        localService.unsetTypeAssociation(inputPort)
-
-        assertTrue(service.getDeviceTypeAssociations().isEmpty())
-    }
-
-    @Test
-    fun testAddAndRemoveVirtualmKeyboardLayoutAssociation() {
-        val inputPort = "input port"
-        val languageTag = "language"
-        val layoutType = "layoutType"
-        localService.addKeyboardLayoutAssociation(inputPort, languageTag, layoutType)
-        verify(native).changeKeyboardLayoutAssociation()
-
-        localService.removeKeyboardLayoutAssociation(inputPort)
-        verify(native, times(2)).changeKeyboardLayoutAssociation()
-    }
-
-    private fun setVirtualMousePointerDisplayIdAndVerify(overrideDisplayId: Int) {
-        val thread = Thread { localService.setVirtualMousePointerDisplayId(overrideDisplayId) }
-        thread.start()
-
-        // Allow some time for the set override call to park while waiting for the native callback.
-        Thread.sleep(100 /*millis*/)
-        verify(native).setPointerDisplayId(overrideDisplayId)
-
-        service.onPointerDisplayIdChanged(overrideDisplayId, 0f, 0f)
-        testLooper.dispatchNext()
-        verify(wmCallbacks).notifyPointerDisplayIdChanged(overrideDisplayId, 0f, 0f)
-        thread.join(100 /*millis*/)
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/input/KeyRemapperTests.kt b/services/tests/servicestests/src/com/android/server/input/KeyRemapperTests.kt
deleted file mode 100644
index c10f651..0000000
--- a/services/tests/servicestests/src/com/android/server/input/KeyRemapperTests.kt
+++ /dev/null
@@ -1,190 +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.server.input
-
-import android.content.Context
-import android.content.ContextWrapper
-import android.hardware.input.IInputManager
-import android.hardware.input.InputManager
-import android.hardware.input.InputManagerGlobal
-import android.os.test.TestLooper
-import android.platform.test.annotations.Presubmit
-import android.provider.Settings
-import android.view.InputDevice
-import android.view.KeyEvent
-import androidx.test.core.app.ApplicationProvider
-import org.junit.After
-import org.junit.Assert.assertEquals
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-import org.mockito.Mock
-import org.mockito.Mockito
-import org.mockito.junit.MockitoJUnit
-import java.io.FileNotFoundException
-import java.io.FileOutputStream
-import java.io.IOException
-import java.io.InputStream
-
-private fun createKeyboard(deviceId: Int): InputDevice =
-    InputDevice.Builder()
-        .setId(deviceId)
-        .setName("Device $deviceId")
-        .setDescriptor("descriptor $deviceId")
-        .setSources(InputDevice.SOURCE_KEYBOARD)
-        .setKeyboardType(InputDevice.KEYBOARD_TYPE_ALPHABETIC)
-        .setExternal(true)
-        .build()
-
-/**
- * Tests for {@link KeyRemapper}.
- *
- * Build/Install/Run:
- * atest FrameworksServicesTests:KeyRemapperTests
- */
-@Presubmit
-class KeyRemapperTests {
-
-    companion object {
-        const val DEVICE_ID = 1
-        val REMAPPABLE_KEYS = intArrayOf(
-            KeyEvent.KEYCODE_CTRL_LEFT, KeyEvent.KEYCODE_CTRL_RIGHT,
-            KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_META_RIGHT,
-            KeyEvent.KEYCODE_ALT_LEFT, KeyEvent.KEYCODE_ALT_RIGHT,
-            KeyEvent.KEYCODE_SHIFT_LEFT, KeyEvent.KEYCODE_SHIFT_RIGHT,
-            KeyEvent.KEYCODE_CAPS_LOCK
-        )
-    }
-
-    @get:Rule
-    val rule = MockitoJUnit.rule()!!
-
-    @Mock
-    private lateinit var iInputManager: IInputManager
-    @Mock
-    private lateinit var native: NativeInputManagerService
-    private lateinit var mKeyRemapper: KeyRemapper
-    private lateinit var context: Context
-    private lateinit var dataStore: PersistentDataStore
-    private lateinit var testLooper: TestLooper
-
-    @Before
-    fun setup() {
-        context = Mockito.spy(ContextWrapper(ApplicationProvider.getApplicationContext()))
-        dataStore = PersistentDataStore(object : PersistentDataStore.Injector() {
-            override fun openRead(): InputStream? {
-                throw FileNotFoundException()
-            }
-
-            override fun startWrite(): FileOutputStream? {
-                throw IOException()
-            }
-
-            override fun finishWrite(fos: FileOutputStream?, success: Boolean) {}
-        })
-        testLooper = TestLooper()
-        mKeyRemapper = KeyRemapper(
-            context,
-            native,
-            dataStore,
-            testLooper.looper
-        )
-        InputManagerGlobal.resetInstance(iInputManager)
-        val inputManager = InputManager(context)
-        Mockito.`when`(context.getSystemService(Mockito.eq(Context.INPUT_SERVICE)))
-            .thenReturn(inputManager)
-        Mockito.`when`(iInputManager.inputDeviceIds).thenReturn(intArrayOf(DEVICE_ID))
-    }
-
-    @After
-    fun tearDown() {
-        InputManagerGlobal.clearInstance()
-    }
-
-    @Test
-    fun testKeyRemapping_whenRemappingEnabled() {
-        ModifierRemappingFlag(true).use {
-            val keyboard = createKeyboard(DEVICE_ID)
-            Mockito.`when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboard)
-
-            for (i in REMAPPABLE_KEYS.indices) {
-                val fromKeyCode = REMAPPABLE_KEYS[i]
-                val toKeyCode = REMAPPABLE_KEYS[(i + 1) % REMAPPABLE_KEYS.size]
-                mKeyRemapper.remapKey(fromKeyCode, toKeyCode)
-                testLooper.dispatchNext()
-            }
-
-            val remapping = mKeyRemapper.keyRemapping
-            val expectedSize = REMAPPABLE_KEYS.size
-            assertEquals("Remapping size should be $expectedSize", expectedSize, remapping.size)
-
-            for (i in REMAPPABLE_KEYS.indices) {
-                val fromKeyCode = REMAPPABLE_KEYS[i]
-                val toKeyCode = REMAPPABLE_KEYS[(i + 1) % REMAPPABLE_KEYS.size]
-                assertEquals(
-                    "Remapping should include mapping from $fromKeyCode to $toKeyCode",
-                    toKeyCode,
-                    remapping.getOrDefault(fromKeyCode, -1)
-                )
-            }
-
-            mKeyRemapper.clearAllKeyRemappings()
-            testLooper.dispatchNext()
-
-            assertEquals(
-                "Remapping size should be 0 after clearAllModifierKeyRemappings",
-                0,
-                mKeyRemapper.keyRemapping.size
-            )
-        }
-    }
-
-    @Test
-    fun testKeyRemapping_whenRemappingDisabled() {
-        ModifierRemappingFlag(false).use {
-            val keyboard = createKeyboard(DEVICE_ID)
-            Mockito.`when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboard)
-
-            mKeyRemapper.remapKey(REMAPPABLE_KEYS[0], REMAPPABLE_KEYS[1])
-            testLooper.dispatchAll()
-
-            val remapping = mKeyRemapper.keyRemapping
-            assertEquals(
-                "Remapping should not be done if modifier key remapping is disabled",
-                0,
-                remapping.size
-            )
-        }
-    }
-
-    private inner class ModifierRemappingFlag constructor(enabled: Boolean) : AutoCloseable {
-        init {
-            Settings.Global.putString(
-                context.contentResolver,
-                "settings_new_keyboard_modifier_key", enabled.toString()
-            )
-        }
-
-        override fun close() {
-            Settings.Global.putString(
-                context.contentResolver,
-                "settings_new_keyboard_modifier_key",
-                ""
-            )
-        }
-    }
-}
\ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/input/KeyboardBacklightControllerTests.kt b/services/tests/servicestests/src/com/android/server/input/KeyboardBacklightControllerTests.kt
deleted file mode 100644
index 64c05dc..0000000
--- a/services/tests/servicestests/src/com/android/server/input/KeyboardBacklightControllerTests.kt
+++ /dev/null
@@ -1,499 +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.server.input
-
-import android.content.Context
-import android.content.ContextWrapper
-import android.graphics.Color
-import android.hardware.input.IInputManager
-import android.hardware.input.IKeyboardBacklightListener
-import android.hardware.input.IKeyboardBacklightState
-import android.hardware.input.InputManager
-import android.hardware.input.InputManagerGlobal
-import android.hardware.lights.Light
-import android.os.UEventObserver
-import android.os.test.TestLooper
-import android.platform.test.annotations.Presubmit
-import android.view.InputDevice
-import androidx.test.core.app.ApplicationProvider
-import com.android.server.input.KeyboardBacklightController.BRIGHTNESS_VALUE_FOR_LEVEL
-import com.android.server.input.KeyboardBacklightController.USER_INACTIVITY_THRESHOLD_MILLIS
-import org.junit.After
-import org.junit.Assert.assertEquals
-import org.junit.Assert.assertNotNull
-import org.junit.Assert.assertNull
-import org.junit.Assert.assertTrue
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-import org.mockito.Mock
-import org.mockito.Mockito.any
-import org.mockito.Mockito.anyInt
-import org.mockito.Mockito.eq
-import org.mockito.Mockito.spy
-import org.mockito.Mockito.`when`
-import org.mockito.junit.MockitoJUnit
-import java.io.FileNotFoundException
-import java.io.FileOutputStream
-import java.io.IOException
-import java.io.InputStream
-
-private fun createKeyboard(deviceId: Int): InputDevice =
-    InputDevice.Builder()
-        .setId(deviceId)
-        .setName("Device $deviceId")
-        .setDescriptor("descriptor $deviceId")
-        .setSources(InputDevice.SOURCE_KEYBOARD)
-        .setKeyboardType(InputDevice.KEYBOARD_TYPE_ALPHABETIC)
-        .setExternal(true)
-        .build()
-
-private fun createLight(lightId: Int, lightType: Int): Light =
-    Light(
-        lightId,
-        "Light $lightId",
-        1,
-        lightType,
-        Light.LIGHT_CAPABILITY_BRIGHTNESS
-    )
-/**
- * Tests for {@link KeyboardBacklightController}.
- *
- * Build/Install/Run:
- * atest FrameworksServicesTests:KeyboardBacklightControllerTests
- */
-@Presubmit
-class KeyboardBacklightControllerTests {
-    companion object {
-        const val DEVICE_ID = 1
-        const val LIGHT_ID = 2
-        const val SECOND_LIGHT_ID = 3
-        const val MAX_BRIGHTNESS = 255
-    }
-
-    @get:Rule
-    val rule = MockitoJUnit.rule()!!
-
-    @Mock
-    private lateinit var iInputManager: IInputManager
-    @Mock
-    private lateinit var native: NativeInputManagerService
-    private lateinit var keyboardBacklightController: KeyboardBacklightController
-    private lateinit var context: Context
-    private lateinit var dataStore: PersistentDataStore
-    private lateinit var testLooper: TestLooper
-    private var lightColorMap: HashMap<Int, Int> = HashMap()
-    private var lastBacklightState: KeyboardBacklightState? = null
-    private var sysfsNodeChanges = 0
-
-    @Before
-    fun setup() {
-        context = spy(ContextWrapper(ApplicationProvider.getApplicationContext()))
-        dataStore = PersistentDataStore(object : PersistentDataStore.Injector() {
-            override fun openRead(): InputStream? {
-                throw FileNotFoundException()
-            }
-
-            override fun startWrite(): FileOutputStream? {
-                throw IOException()
-            }
-
-            override fun finishWrite(fos: FileOutputStream?, success: Boolean) {}
-        })
-        testLooper = TestLooper()
-        keyboardBacklightController =
-            KeyboardBacklightController(context, native, dataStore, testLooper.looper)
-        InputManagerGlobal.resetInstance(iInputManager)
-        val inputManager = InputManager(context)
-        `when`(context.getSystemService(eq(Context.INPUT_SERVICE))).thenReturn(inputManager)
-        `when`(iInputManager.inputDeviceIds).thenReturn(intArrayOf(DEVICE_ID))
-        `when`(native.setLightColor(anyInt(), anyInt(), anyInt())).then {
-            val args = it.arguments
-            lightColorMap.put(args[1] as Int, args[2] as Int)
-        }
-        lightColorMap.clear()
-        `when`(native.sysfsNodeChanged(any())).then {
-            sysfsNodeChanges++
-        }
-    }
-
-    @After
-    fun tearDown() {
-        InputManagerGlobal.clearInstance()
-    }
-
-    @Test
-    fun testKeyboardBacklightIncrementDecrement() {
-        val keyboardWithBacklight = createKeyboard(DEVICE_ID)
-        val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
-        `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
-        `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
-        keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
-
-        for (level in 1 until BRIGHTNESS_VALUE_FOR_LEVEL.size) {
-            incrementKeyboardBacklight(DEVICE_ID)
-            assertEquals(
-                "Light value for level $level mismatched",
-                Color.argb(BRIGHTNESS_VALUE_FOR_LEVEL[level], 0, 0, 0),
-                lightColorMap[LIGHT_ID]
-            )
-            assertEquals(
-                "Light value for level $level must be correctly stored in the datastore",
-                BRIGHTNESS_VALUE_FOR_LEVEL[level],
-                dataStore.getKeyboardBacklightBrightness(
-                    keyboardWithBacklight.descriptor,
-                    LIGHT_ID
-                ).asInt
-            )
-        }
-
-        // Increment above max level
-        incrementKeyboardBacklight(DEVICE_ID)
-        assertEquals(
-            "Light value for max level mismatched",
-            Color.argb(MAX_BRIGHTNESS, 0, 0, 0),
-            lightColorMap[LIGHT_ID]
-        )
-        assertEquals(
-            "Light value for max level must be correctly stored in the datastore",
-            MAX_BRIGHTNESS,
-            dataStore.getKeyboardBacklightBrightness(
-                keyboardWithBacklight.descriptor,
-                LIGHT_ID
-            ).asInt
-        )
-
-        for (level in BRIGHTNESS_VALUE_FOR_LEVEL.size - 2 downTo 0) {
-            decrementKeyboardBacklight(DEVICE_ID)
-            assertEquals(
-                "Light value for level $level mismatched",
-                Color.argb(BRIGHTNESS_VALUE_FOR_LEVEL[level], 0, 0, 0),
-                lightColorMap[LIGHT_ID]
-            )
-            assertEquals(
-                "Light value for level $level must be correctly stored in the datastore",
-                BRIGHTNESS_VALUE_FOR_LEVEL[level],
-                dataStore.getKeyboardBacklightBrightness(
-                    keyboardWithBacklight.descriptor,
-                    LIGHT_ID
-                ).asInt
-            )
-        }
-
-        // Decrement below min level
-        decrementKeyboardBacklight(DEVICE_ID)
-        assertEquals(
-            "Light value for min level mismatched",
-            Color.argb(0, 0, 0, 0),
-            lightColorMap[LIGHT_ID]
-        )
-        assertEquals(
-            "Light value for min level must be correctly stored in the datastore",
-            0,
-            dataStore.getKeyboardBacklightBrightness(
-                keyboardWithBacklight.descriptor,
-                LIGHT_ID
-            ).asInt
-        )
-    }
-
-    @Test
-    fun testKeyboardWithoutBacklight() {
-        val keyboardWithoutBacklight = createKeyboard(DEVICE_ID)
-        val keyboardInputLight = createLight(LIGHT_ID, Light.LIGHT_TYPE_INPUT)
-        `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithoutBacklight)
-        `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardInputLight))
-        keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
-
-        incrementKeyboardBacklight(DEVICE_ID)
-        assertTrue("Non Keyboard backlights should not change", lightColorMap.isEmpty())
-    }
-
-    @Test
-    fun testKeyboardWithMultipleLight() {
-        val keyboardWithBacklight = createKeyboard(DEVICE_ID)
-        val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
-        val keyboardInputLight = createLight(SECOND_LIGHT_ID, Light.LIGHT_TYPE_INPUT)
-        `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
-        `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(
-            listOf(
-                keyboardBacklight,
-                keyboardInputLight
-            )
-        )
-        keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
-
-        incrementKeyboardBacklight(DEVICE_ID)
-        assertEquals("Only keyboard backlights should change", 1, lightColorMap.size)
-        assertNotNull("Keyboard backlight should change", lightColorMap[LIGHT_ID])
-        assertNull("Input lights should not change", lightColorMap[SECOND_LIGHT_ID])
-    }
-
-    @Test
-    fun testRestoreBacklightOnInputDeviceAdded() {
-        val keyboardWithBacklight = createKeyboard(DEVICE_ID)
-        val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
-        `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
-        `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
-
-        for (level in 1 until BRIGHTNESS_VALUE_FOR_LEVEL.size) {
-            dataStore.setKeyboardBacklightBrightness(
-                    keyboardWithBacklight.descriptor,
-                    LIGHT_ID,
-                    BRIGHTNESS_VALUE_FOR_LEVEL[level] - 1
-            )
-
-            keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
-            keyboardBacklightController.notifyUserActivity()
-            testLooper.dispatchNext()
-            assertEquals(
-                    "Keyboard backlight level should be restored to the level saved in the data " +
-                            "store",
-                    Color.argb(BRIGHTNESS_VALUE_FOR_LEVEL[level], 0, 0, 0),
-                    lightColorMap[LIGHT_ID]
-            )
-            keyboardBacklightController.onInputDeviceRemoved(DEVICE_ID)
-        }
-    }
-
-    @Test
-    fun testRestoreBacklightOnInputDeviceChanged() {
-        val keyboardWithBacklight = createKeyboard(DEVICE_ID)
-        val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
-        `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
-        dataStore.setKeyboardBacklightBrightness(
-            keyboardWithBacklight.descriptor,
-            LIGHT_ID,
-            MAX_BRIGHTNESS
-        )
-
-        keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
-        keyboardBacklightController.notifyUserActivity()
-        testLooper.dispatchNext()
-        assertTrue(
-            "Keyboard backlight should not be changed until its added",
-            lightColorMap.isEmpty()
-        )
-
-        `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
-        keyboardBacklightController.onInputDeviceChanged(DEVICE_ID)
-        keyboardBacklightController.notifyUserActivity()
-        testLooper.dispatchNext()
-        assertEquals(
-            "Keyboard backlight level should be restored to the level saved in the data store",
-            Color.argb(MAX_BRIGHTNESS, 0, 0, 0),
-            lightColorMap[LIGHT_ID]
-        )
-    }
-
-    @Test
-    fun testKeyboardBacklight_registerUnregisterListener() {
-        val keyboardWithBacklight = createKeyboard(DEVICE_ID)
-        val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
-        `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
-        `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
-        keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
-
-        // Register backlight listener
-        val listener = KeyboardBacklightListener()
-        keyboardBacklightController.registerKeyboardBacklightListener(listener, 0)
-
-        lastBacklightState = null
-        keyboardBacklightController.incrementKeyboardBacklight(DEVICE_ID)
-        testLooper.dispatchNext()
-
-        assertEquals(
-            "Backlight state device Id should be $DEVICE_ID",
-            DEVICE_ID,
-            lastBacklightState!!.deviceId
-        )
-        assertEquals(
-            "Backlight state brightnessLevel should be " + 1,
-            1,
-            lastBacklightState!!.brightnessLevel
-        )
-        assertEquals(
-            "Backlight state maxBrightnessLevel should be " + (BRIGHTNESS_VALUE_FOR_LEVEL.size - 1),
-            (BRIGHTNESS_VALUE_FOR_LEVEL.size - 1),
-            lastBacklightState!!.maxBrightnessLevel
-        )
-        assertEquals(
-            "Backlight state isTriggeredByKeyPress should be true",
-            true,
-            lastBacklightState!!.isTriggeredByKeyPress
-        )
-
-        // Unregister listener
-        keyboardBacklightController.unregisterKeyboardBacklightListener(listener, 0)
-
-        lastBacklightState = null
-        incrementKeyboardBacklight(DEVICE_ID)
-
-        assertNull("Listener should not receive any updates", lastBacklightState)
-    }
-
-    @Test
-    fun testKeyboardBacklight_userActivity() {
-        val keyboardWithBacklight = createKeyboard(DEVICE_ID)
-        val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
-        `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
-        `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
-        dataStore.setKeyboardBacklightBrightness(
-            keyboardWithBacklight.descriptor,
-            LIGHT_ID,
-            MAX_BRIGHTNESS
-        )
-
-        keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
-        keyboardBacklightController.notifyUserActivity()
-        testLooper.dispatchNext()
-        assertEquals(
-            "Keyboard backlight level should be restored to the level saved in the data store",
-            Color.argb(MAX_BRIGHTNESS, 0, 0, 0),
-            lightColorMap[LIGHT_ID]
-        )
-
-        testLooper.moveTimeForward(USER_INACTIVITY_THRESHOLD_MILLIS + 1000)
-        testLooper.dispatchNext()
-        assertEquals(
-            "Keyboard backlight level should be turned off after inactivity",
-            0,
-            lightColorMap[LIGHT_ID]
-        )
-    }
-
-    @Test
-    fun testKeyboardBacklight_displayOnOff() {
-        val keyboardWithBacklight = createKeyboard(DEVICE_ID)
-        val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
-        `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
-        `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
-        dataStore.setKeyboardBacklightBrightness(
-            keyboardWithBacklight.descriptor,
-            LIGHT_ID,
-            MAX_BRIGHTNESS
-        )
-
-        keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
-        keyboardBacklightController.handleInteractiveStateChange(true /* isDisplayOn */)
-        assertEquals(
-            "Keyboard backlight level should be restored to the level saved in the data " +
-                    "store when display turned on",
-            Color.argb(MAX_BRIGHTNESS, 0, 0, 0),
-            lightColorMap[LIGHT_ID]
-        )
-
-        keyboardBacklightController.handleInteractiveStateChange(false /* isDisplayOn */)
-        assertEquals(
-            "Keyboard backlight level should be turned off after display is turned off",
-            0,
-            lightColorMap[LIGHT_ID]
-        )
-    }
-
-    @Test
-    fun testKeyboardBacklightSysfsNodeAdded_AfterInputDeviceAdded() {
-        var counter = sysfsNodeChanges
-        keyboardBacklightController.onKeyboardBacklightUEvent(UEventObserver.UEvent(
-            "ACTION=add\u0000SUBSYSTEM=leds\u0000DEVPATH=/xyz/leds/abc::no_backlight\u0000"
-        ))
-        assertEquals(
-            "Should not reload sysfs node if UEvent path doesn't contain kbd_backlight",
-            counter,
-            sysfsNodeChanges
-        )
-
-        keyboardBacklightController.onKeyboardBacklightUEvent(UEventObserver.UEvent(
-            "ACTION=add\u0000SUBSYSTEM=power\u0000DEVPATH=/xyz/leds/abc::kbd_backlight\u0000"
-        ))
-        assertEquals(
-            "Should not reload sysfs node if UEvent doesn't belong to subsystem LED",
-            counter,
-            sysfsNodeChanges
-        )
-
-        keyboardBacklightController.onKeyboardBacklightUEvent(UEventObserver.UEvent(
-            "ACTION=remove\u0000SUBSYSTEM=leds\u0000DEVPATH=/xyz/leds/abc::kbd_backlight\u0000"
-        ))
-        assertEquals(
-            "Should not reload sysfs node if UEvent doesn't have ACTION(add)",
-            counter,
-            sysfsNodeChanges
-        )
-
-        keyboardBacklightController.onKeyboardBacklightUEvent(UEventObserver.UEvent(
-            "ACTION=add\u0000SUBSYSTEM=leds\u0000DEVPATH=/xyz/pqr/abc::kbd_backlight\u0000"
-        ))
-        assertEquals(
-            "Should not reload sysfs node if UEvent path doesn't belong to leds/ directory",
-            counter,
-            sysfsNodeChanges
-        )
-
-        keyboardBacklightController.onKeyboardBacklightUEvent(UEventObserver.UEvent(
-            "ACTION=add\u0000SUBSYSTEM=leds\u0000DEVPATH=/xyz/leds/abc::kbd_backlight\u0000"
-        ))
-        assertEquals(
-            "Should reload sysfs node if a valid Keyboard backlight LED UEvent occurs",
-            ++counter,
-            sysfsNodeChanges
-        )
-
-        keyboardBacklightController.onKeyboardBacklightUEvent(UEventObserver.UEvent(
-            "ACTION=add\u0000SUBSYSTEM=leds\u0000DEVPATH=/xyz/leds/abc:kbd_backlight:red\u0000"
-        ))
-        assertEquals(
-            "Should reload sysfs node if a valid Keyboard backlight LED UEvent occurs",
-            ++counter,
-            sysfsNodeChanges
-        )
-    }
-
-    inner class KeyboardBacklightListener : IKeyboardBacklightListener.Stub() {
-        override fun onBrightnessChanged(
-            deviceId: Int,
-            state: IKeyboardBacklightState,
-            isTriggeredByKeyPress: Boolean
-        ) {
-            lastBacklightState = KeyboardBacklightState(
-                deviceId,
-                state.brightnessLevel,
-                state.maxBrightnessLevel,
-                isTriggeredByKeyPress
-            )
-        }
-    }
-
-    private fun incrementKeyboardBacklight(deviceId: Int) {
-        keyboardBacklightController.incrementKeyboardBacklight(deviceId)
-        keyboardBacklightController.notifyUserActivity()
-        testLooper.dispatchAll()
-    }
-
-    private fun decrementKeyboardBacklight(deviceId: Int) {
-        keyboardBacklightController.decrementKeyboardBacklight(deviceId)
-        keyboardBacklightController.notifyUserActivity()
-        testLooper.dispatchAll()
-    }
-
-    class KeyboardBacklightState(
-        val deviceId: Int,
-        val brightnessLevel: Int,
-        val maxBrightnessLevel: Int,
-        val isTriggeredByKeyPress: Boolean
-    )
-}
diff --git a/services/tests/servicestests/src/com/android/server/input/KeyboardLayoutManagerTests.kt b/services/tests/servicestests/src/com/android/server/input/KeyboardLayoutManagerTests.kt
deleted file mode 100644
index 55c45df..0000000
--- a/services/tests/servicestests/src/com/android/server/input/KeyboardLayoutManagerTests.kt
+++ /dev/null
@@ -1,903 +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.server.input
-
-import android.content.Context
-import android.content.ContextWrapper
-import android.content.pm.ActivityInfo
-import android.content.pm.ApplicationInfo
-import android.content.pm.PackageManager
-import android.content.pm.ResolveInfo
-import android.content.pm.ServiceInfo
-import android.hardware.input.IInputManager
-import android.hardware.input.InputManager
-import android.hardware.input.InputManagerGlobal
-import android.hardware.input.KeyboardLayout
-import android.icu.util.ULocale
-import android.os.Bundle
-import android.os.test.TestLooper
-import android.platform.test.annotations.Presubmit
-import android.provider.Settings
-import android.view.InputDevice
-import android.view.inputmethod.InputMethodInfo
-import android.view.inputmethod.InputMethodSubtype
-import androidx.test.core.R
-import androidx.test.core.app.ApplicationProvider
-import org.junit.Assert.assertEquals
-import org.junit.Assert.assertNotEquals
-import org.junit.Assert.assertNull
-import org.junit.Assert.assertTrue
-import org.junit.Assert.assertThrows
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-import org.mockito.Mock
-import org.mockito.Mockito
-import org.mockito.junit.MockitoJUnit
-import java.io.FileNotFoundException
-import java.io.FileOutputStream
-import java.io.IOException
-import java.io.InputStream
-
-private fun createKeyboard(
-    deviceId: Int,
-    vendorId: Int,
-    productId: Int,
-    languageTag: String,
-    layoutType: String
-): InputDevice =
-    InputDevice.Builder()
-        .setId(deviceId)
-        .setName("Device $deviceId")
-        .setDescriptor("descriptor $deviceId")
-        .setSources(InputDevice.SOURCE_KEYBOARD)
-        .setKeyboardType(InputDevice.KEYBOARD_TYPE_ALPHABETIC)
-        .setExternal(true)
-        .setVendorId(vendorId)
-        .setProductId(productId)
-        .setKeyboardLanguageTag(languageTag)
-        .setKeyboardLayoutType(layoutType)
-        .build()
-
-/**
- * Tests for {@link Default UI} and {@link New UI}.
- *
- * Build/Install/Run:
- * atest FrameworksServicesTests:KeyboardLayoutManagerTests
- */
-@Presubmit
-class KeyboardLayoutManagerTests {
-    companion object {
-        const val DEVICE_ID = 1
-        const val VENDOR_SPECIFIC_DEVICE_ID = 2
-        const val ENGLISH_DVORAK_DEVICE_ID = 3
-        const val ENGLISH_QWERTY_DEVICE_ID = 4
-        const val DEFAULT_VENDOR_ID = 123
-        const val DEFAULT_PRODUCT_ID = 456
-        const val USER_ID = 4
-        const val IME_ID = "ime_id"
-        const val PACKAGE_NAME = "KeyboardLayoutManagerTests"
-        const val RECEIVER_NAME = "DummyReceiver"
-        private const val ENGLISH_US_LAYOUT_NAME = "keyboard_layout_english_us"
-        private const val ENGLISH_UK_LAYOUT_NAME = "keyboard_layout_english_uk"
-        private const val VENDOR_SPECIFIC_LAYOUT_NAME = "keyboard_layout_vendorId:1,productId:1"
-    }
-
-    private val ENGLISH_US_LAYOUT_DESCRIPTOR = createLayoutDescriptor(ENGLISH_US_LAYOUT_NAME)
-    private val ENGLISH_UK_LAYOUT_DESCRIPTOR = createLayoutDescriptor(ENGLISH_UK_LAYOUT_NAME)
-    private val VENDOR_SPECIFIC_LAYOUT_DESCRIPTOR =
-        createLayoutDescriptor(VENDOR_SPECIFIC_LAYOUT_NAME)
-
-    @get:Rule
-    val rule = MockitoJUnit.rule()!!
-
-    @Mock
-    private lateinit var iInputManager: IInputManager
-
-    @Mock
-    private lateinit var native: NativeInputManagerService
-
-    @Mock
-    private lateinit var packageManager: PackageManager
-    private lateinit var keyboardLayoutManager: KeyboardLayoutManager
-
-    private lateinit var imeInfo: InputMethodInfo
-    private var nextImeSubtypeId = 0
-    private lateinit var context: Context
-    private lateinit var dataStore: PersistentDataStore
-    private lateinit var testLooper: TestLooper
-
-    // Devices
-    private lateinit var keyboardDevice: InputDevice
-    private lateinit var vendorSpecificKeyboardDevice: InputDevice
-    private lateinit var englishDvorakKeyboardDevice: InputDevice
-    private lateinit var englishQwertyKeyboardDevice: InputDevice
-
-    @Before
-    fun setup() {
-        context = Mockito.spy(ContextWrapper(ApplicationProvider.getApplicationContext()))
-        dataStore = PersistentDataStore(object : PersistentDataStore.Injector() {
-            override fun openRead(): InputStream? {
-                throw FileNotFoundException()
-            }
-
-            override fun startWrite(): FileOutputStream? {
-                throw IOException()
-            }
-
-            override fun finishWrite(fos: FileOutputStream?, success: Boolean) {}
-        })
-        testLooper = TestLooper()
-        keyboardLayoutManager = KeyboardLayoutManager(context, native, dataStore, testLooper.looper)
-        setupInputDevices()
-        setupBroadcastReceiver()
-        setupIme()
-    }
-
-    private fun setupInputDevices() {
-        InputManagerGlobal.resetInstance(iInputManager)
-        val inputManager = InputManager(context)
-        Mockito.`when`(context.getSystemService(Mockito.eq(Context.INPUT_SERVICE)))
-            .thenReturn(inputManager)
-
-        keyboardDevice = createKeyboard(DEVICE_ID, DEFAULT_VENDOR_ID, DEFAULT_PRODUCT_ID, "", "")
-        vendorSpecificKeyboardDevice = createKeyboard(VENDOR_SPECIFIC_DEVICE_ID, 1, 1, "", "")
-        englishDvorakKeyboardDevice = createKeyboard(ENGLISH_DVORAK_DEVICE_ID, DEFAULT_VENDOR_ID,
-                DEFAULT_PRODUCT_ID, "en", "dvorak")
-        englishQwertyKeyboardDevice = createKeyboard(ENGLISH_QWERTY_DEVICE_ID, DEFAULT_VENDOR_ID,
-                DEFAULT_PRODUCT_ID, "en", "qwerty")
-        Mockito.`when`(iInputManager.inputDeviceIds)
-            .thenReturn(intArrayOf(
-                DEVICE_ID,
-                VENDOR_SPECIFIC_DEVICE_ID,
-                ENGLISH_DVORAK_DEVICE_ID,
-                ENGLISH_QWERTY_DEVICE_ID
-            ))
-        Mockito.`when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardDevice)
-        Mockito.`when`(iInputManager.getInputDevice(VENDOR_SPECIFIC_DEVICE_ID))
-            .thenReturn(vendorSpecificKeyboardDevice)
-        Mockito.`when`(iInputManager.getInputDevice(ENGLISH_DVORAK_DEVICE_ID))
-            .thenReturn(englishDvorakKeyboardDevice)
-        Mockito.`when`(iInputManager.getInputDevice(ENGLISH_QWERTY_DEVICE_ID))
-                .thenReturn(englishQwertyKeyboardDevice)
-    }
-
-    private fun setupBroadcastReceiver() {
-        Mockito.`when`(context.packageManager).thenReturn(packageManager)
-
-        val info = createMockReceiver()
-        Mockito.`when`(packageManager.queryBroadcastReceiversAsUser(Mockito.any(), Mockito.anyInt(),
-                Mockito.anyInt())).thenReturn(listOf(info))
-        Mockito.`when`(packageManager.getReceiverInfo(Mockito.any(), Mockito.anyInt()))
-            .thenReturn(info.activityInfo)
-
-        val resources = context.resources
-        Mockito.`when`(
-            packageManager.getResourcesForApplication(
-                Mockito.any(
-                    ApplicationInfo::class.java
-                )
-            )
-        ).thenReturn(resources)
-    }
-
-    private fun setupIme() {
-        imeInfo = InputMethodInfo(PACKAGE_NAME, RECEIVER_NAME, "", "", 0)
-    }
-
-    @Test
-    fun testDefaultUi_getKeyboardLayouts() {
-        NewSettingsApiFlag(false).use {
-            val keyboardLayouts = keyboardLayoutManager.keyboardLayouts
-            assertNotEquals(
-                "Default UI: Keyboard layout API should not return empty array",
-                0,
-                keyboardLayouts.size
-            )
-            assertTrue(
-                "Default UI: Keyboard layout API should provide English(US) layout",
-                hasLayout(keyboardLayouts, ENGLISH_US_LAYOUT_DESCRIPTOR)
-            )
-        }
-    }
-
-    @Test
-    fun testNewUi_getKeyboardLayouts() {
-        NewSettingsApiFlag(true).use {
-            val keyboardLayouts = keyboardLayoutManager.keyboardLayouts
-            assertNotEquals(
-                "New UI: Keyboard layout API should not return empty array",
-                0,
-                keyboardLayouts.size
-            )
-            assertTrue(
-                "New UI: Keyboard layout API should provide English(US) layout",
-                hasLayout(keyboardLayouts, ENGLISH_US_LAYOUT_DESCRIPTOR)
-            )
-        }
-    }
-
-    @Test
-    fun testDefaultUi_getKeyboardLayoutsForInputDevice() {
-        NewSettingsApiFlag(false).use {
-            val keyboardLayouts =
-                keyboardLayoutManager.getKeyboardLayoutsForInputDevice(keyboardDevice.identifier)
-            assertNotEquals(
-                "Default UI: getKeyboardLayoutsForInputDevice API should not return empty array",
-                0,
-                keyboardLayouts.size
-            )
-            assertTrue(
-                "Default UI: getKeyboardLayoutsForInputDevice API should provide English(US) " +
-                        "layout",
-                hasLayout(keyboardLayouts, ENGLISH_US_LAYOUT_DESCRIPTOR)
-            )
-
-            val vendorSpecificKeyboardLayouts =
-                keyboardLayoutManager.getKeyboardLayoutsForInputDevice(
-                    vendorSpecificKeyboardDevice.identifier
-                )
-            assertEquals(
-                "Default UI: getKeyboardLayoutsForInputDevice API should return only vendor " +
-                        "specific layout",
-                1,
-                vendorSpecificKeyboardLayouts.size
-            )
-            assertEquals(
-                "Default UI: getKeyboardLayoutsForInputDevice API should return vendor specific " +
-                        "layout",
-                VENDOR_SPECIFIC_LAYOUT_DESCRIPTOR,
-                vendorSpecificKeyboardLayouts[0].descriptor
-            )
-        }
-    }
-
-    @Test
-    fun testNewUi_getKeyboardLayoutsForInputDevice() {
-        NewSettingsApiFlag(true).use {
-            val keyboardLayouts = keyboardLayoutManager.keyboardLayouts
-            assertNotEquals(
-                    "New UI: getKeyboardLayoutsForInputDevice API should not return empty array",
-                    0,
-                    keyboardLayouts.size
-            )
-            assertTrue(
-                    "New UI: getKeyboardLayoutsForInputDevice API should provide English(US) " +
-                            "layout",
-                    hasLayout(keyboardLayouts, ENGLISH_US_LAYOUT_DESCRIPTOR)
-            )
-        }
-    }
-
-    @Test
-    fun testDefaultUi_getSetCurrentKeyboardLayoutForInputDevice() {
-        NewSettingsApiFlag(false).use {
-            assertNull(
-                "Default UI: getCurrentKeyboardLayoutForInputDevice API should return null if " +
-                        "nothing was set",
-                keyboardLayoutManager.getCurrentKeyboardLayoutForInputDevice(
-                    keyboardDevice.identifier
-                )
-            )
-
-            keyboardLayoutManager.setCurrentKeyboardLayoutForInputDevice(
-                keyboardDevice.identifier,
-                ENGLISH_US_LAYOUT_DESCRIPTOR
-            )
-            val keyboardLayout =
-                keyboardLayoutManager.getCurrentKeyboardLayoutForInputDevice(
-                    keyboardDevice.identifier
-                )
-            assertEquals(
-                "Default UI: getCurrentKeyboardLayoutForInputDevice API should return the set " +
-                        "layout",
-                ENGLISH_US_LAYOUT_DESCRIPTOR,
-                keyboardLayout
-            )
-        }
-    }
-
-    @Test
-    fun testNewUi_getSetCurrentKeyboardLayoutForInputDevice() {
-        NewSettingsApiFlag(true).use {
-            keyboardLayoutManager.setCurrentKeyboardLayoutForInputDevice(
-                keyboardDevice.identifier,
-                ENGLISH_US_LAYOUT_DESCRIPTOR
-            )
-            assertNull(
-                "New UI: getCurrentKeyboardLayoutForInputDevice API should always return null " +
-                        "even after setCurrentKeyboardLayoutForInputDevice",
-                keyboardLayoutManager.getCurrentKeyboardLayoutForInputDevice(
-                    keyboardDevice.identifier
-                )
-            )
-        }
-    }
-
-    @Test
-    fun testDefaultUi_getEnabledKeyboardLayoutsForInputDevice() {
-        NewSettingsApiFlag(false).use {
-            keyboardLayoutManager.addKeyboardLayoutForInputDevice(
-                keyboardDevice.identifier, ENGLISH_US_LAYOUT_DESCRIPTOR
-            )
-
-            val keyboardLayouts =
-                keyboardLayoutManager.getEnabledKeyboardLayoutsForInputDevice(
-                    keyboardDevice.identifier
-                )
-            assertEquals(
-                "Default UI: getEnabledKeyboardLayoutsForInputDevice API should return added " +
-                        "layout",
-                1,
-                keyboardLayouts.size
-            )
-            assertEquals(
-                "Default UI: getEnabledKeyboardLayoutsForInputDevice API should return " +
-                        "English(US) layout",
-                ENGLISH_US_LAYOUT_DESCRIPTOR,
-                keyboardLayouts[0]
-            )
-            assertEquals(
-                "Default UI: getCurrentKeyboardLayoutForInputDevice API should return " +
-                        "English(US) layout (Auto select the first enabled layout)",
-                ENGLISH_US_LAYOUT_DESCRIPTOR,
-                keyboardLayoutManager.getCurrentKeyboardLayoutForInputDevice(
-                    keyboardDevice.identifier
-                )
-            )
-
-            keyboardLayoutManager.removeKeyboardLayoutForInputDevice(
-                keyboardDevice.identifier, ENGLISH_US_LAYOUT_DESCRIPTOR
-            )
-            assertEquals(
-                "Default UI: getKeyboardLayoutsForInputDevice API should return 0 layouts",
-                0,
-                keyboardLayoutManager.getEnabledKeyboardLayoutsForInputDevice(
-                    keyboardDevice.identifier
-                ).size
-            )
-            assertNull(
-                "Default UI: getCurrentKeyboardLayoutForInputDevice API should return null after " +
-                        "the enabled layout is removed",
-                keyboardLayoutManager.getCurrentKeyboardLayoutForInputDevice(
-                    keyboardDevice.identifier
-                )
-            )
-        }
-    }
-
-    @Test
-    fun testNewUi_getEnabledKeyboardLayoutsForInputDevice() {
-        NewSettingsApiFlag(true).use {
-            keyboardLayoutManager.addKeyboardLayoutForInputDevice(
-                keyboardDevice.identifier, ENGLISH_US_LAYOUT_DESCRIPTOR
-            )
-
-            assertEquals(
-                "New UI: getEnabledKeyboardLayoutsForInputDevice API should return always return " +
-                        "an empty array",
-                0,
-                keyboardLayoutManager.getEnabledKeyboardLayoutsForInputDevice(
-                    keyboardDevice.identifier
-                ).size
-            )
-            assertNull(
-                "New UI: getCurrentKeyboardLayoutForInputDevice API should always return null",
-                keyboardLayoutManager.getCurrentKeyboardLayoutForInputDevice(
-                    keyboardDevice.identifier
-                )
-            )
-        }
-    }
-
-    @Test
-    fun testDefaultUi_switchKeyboardLayout() {
-        NewSettingsApiFlag(false).use {
-            keyboardLayoutManager.addKeyboardLayoutForInputDevice(
-                keyboardDevice.identifier, ENGLISH_US_LAYOUT_DESCRIPTOR
-            )
-            keyboardLayoutManager.addKeyboardLayoutForInputDevice(
-                keyboardDevice.identifier, ENGLISH_UK_LAYOUT_DESCRIPTOR
-            )
-            assertEquals(
-                "Default UI: getCurrentKeyboardLayoutForInputDevice API should return " +
-                        "English(US) layout",
-                ENGLISH_US_LAYOUT_DESCRIPTOR,
-                keyboardLayoutManager.getCurrentKeyboardLayoutForInputDevice(
-                    keyboardDevice.identifier
-                )
-            )
-
-            keyboardLayoutManager.switchKeyboardLayout(DEVICE_ID, 1)
-
-            // Throws null pointer because trying to show toast using TestLooper
-            assertThrows(NullPointerException::class.java) { testLooper.dispatchAll() }
-            assertEquals("Default UI: getCurrentKeyboardLayoutForInputDevice API should return " +
-                    "English(UK) layout",
-                ENGLISH_UK_LAYOUT_DESCRIPTOR,
-                keyboardLayoutManager.getCurrentKeyboardLayoutForInputDevice(
-                    keyboardDevice.identifier
-                )
-            )
-        }
-    }
-
-    @Test
-    fun testNewUi_switchKeyboardLayout() {
-        NewSettingsApiFlag(true).use {
-            keyboardLayoutManager.addKeyboardLayoutForInputDevice(
-                keyboardDevice.identifier, ENGLISH_US_LAYOUT_DESCRIPTOR
-            )
-            keyboardLayoutManager.addKeyboardLayoutForInputDevice(
-                keyboardDevice.identifier, ENGLISH_UK_LAYOUT_DESCRIPTOR
-            )
-
-            keyboardLayoutManager.switchKeyboardLayout(DEVICE_ID, 1)
-            testLooper.dispatchAll()
-
-            assertNull("New UI: getCurrentKeyboardLayoutForInputDevice API should always return " +
-                    "null",
-                keyboardLayoutManager.getCurrentKeyboardLayoutForInputDevice(
-                    keyboardDevice.identifier
-                )
-            )
-        }
-    }
-
-    @Test
-    fun testDefaultUi_getKeyboardLayout() {
-        NewSettingsApiFlag(false).use {
-            val keyboardLayout =
-                keyboardLayoutManager.getKeyboardLayout(ENGLISH_US_LAYOUT_DESCRIPTOR)
-            assertEquals("Default UI: getKeyboardLayout API should return correct Layout from " +
-                    "available layouts",
-                ENGLISH_US_LAYOUT_DESCRIPTOR,
-                keyboardLayout!!.descriptor
-            )
-        }
-    }
-
-    @Test
-    fun testNewUi_getKeyboardLayout() {
-        NewSettingsApiFlag(true).use {
-            val keyboardLayout =
-                keyboardLayoutManager.getKeyboardLayout(ENGLISH_US_LAYOUT_DESCRIPTOR)
-            assertEquals("New UI: getKeyboardLayout API should return correct Layout from " +
-                    "available layouts",
-                ENGLISH_US_LAYOUT_DESCRIPTOR,
-                keyboardLayout!!.descriptor
-            )
-        }
-    }
-
-    @Test
-    fun testDefaultUi_getSetKeyboardLayoutForInputDevice_WithImeInfo() {
-        NewSettingsApiFlag(false).use {
-            val imeSubtype = createImeSubtype()
-            keyboardLayoutManager.setKeyboardLayoutForInputDevice(
-                keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype,
-                ENGLISH_UK_LAYOUT_DESCRIPTOR
-            )
-            val keyboardLayout =
-                keyboardLayoutManager.getKeyboardLayoutForInputDevice(
-                    keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype
-                )
-            assertNull(
-                "Default UI: getKeyboardLayoutForInputDevice API should always return null",
-                keyboardLayout
-            )
-        }
-    }
-
-    @Test
-    fun testNewUi_getSetKeyboardLayoutForInputDevice_withImeInfo() {
-        NewSettingsApiFlag(true).use {
-            val imeSubtype = createImeSubtype()
-
-            keyboardLayoutManager.setKeyboardLayoutForInputDevice(
-                keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype,
-                ENGLISH_UK_LAYOUT_DESCRIPTOR
-            )
-            assertEquals(
-                "New UI: getKeyboardLayoutForInputDevice API should return the set layout",
-                ENGLISH_UK_LAYOUT_DESCRIPTOR,
-                keyboardLayoutManager.getKeyboardLayoutForInputDevice(
-                    keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype
-                )
-            )
-
-            // This should replace previously set layout
-            keyboardLayoutManager.setKeyboardLayoutForInputDevice(
-                keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype,
-                ENGLISH_US_LAYOUT_DESCRIPTOR
-            )
-            assertEquals(
-                "New UI: getKeyboardLayoutForInputDevice API should return the last set layout",
-                ENGLISH_US_LAYOUT_DESCRIPTOR,
-                keyboardLayoutManager.getKeyboardLayoutForInputDevice(
-                    keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype
-                )
-            )
-        }
-    }
-
-    @Test
-    fun testDefaultUi_getKeyboardLayoutListForInputDevice() {
-        NewSettingsApiFlag(false).use {
-            val keyboardLayouts =
-                keyboardLayoutManager.getKeyboardLayoutListForInputDevice(
-                    keyboardDevice.identifier, USER_ID, imeInfo,
-                    createImeSubtype()
-                )
-            assertEquals("Default UI: getKeyboardLayoutListForInputDevice API should always " +
-                    "return empty array",
-                0,
-                keyboardLayouts.size
-            )
-        }
-    }
-
-    @Test
-    fun testNewUi_getKeyboardLayoutListForInputDevice() {
-        NewSettingsApiFlag(true).use {
-            // Check Layouts for "hi-Latn". It should return all 'Latn' keyboard layouts
-            var keyboardLayouts =
-                keyboardLayoutManager.getKeyboardLayoutListForInputDevice(
-                    keyboardDevice.identifier, USER_ID, imeInfo,
-                    createImeSubtypeForLanguageTag("hi-Latn")
-                )
-            assertNotEquals(
-                "New UI: getKeyboardLayoutListForInputDevice API should return the list of " +
-                        "supported layouts with matching script code",
-                0,
-                keyboardLayouts.size
-            )
-            assertTrue("New UI: getKeyboardLayoutListForInputDevice API should return a list " +
-                    "containing English(US) layout for hi-Latn",
-                containsLayout(keyboardLayouts, ENGLISH_US_LAYOUT_DESCRIPTOR)
-            )
-            assertTrue("New UI: getKeyboardLayoutListForInputDevice API should return a list " +
-                    "containing English(No script code) layout for hi-Latn",
-                containsLayout(
-                    keyboardLayouts,
-                    createLayoutDescriptor("keyboard_layout_english_without_script_code")
-                )
-            )
-
-            // Check Layouts for "hi" which by default uses 'Deva' script.
-            keyboardLayouts =
-                keyboardLayoutManager.getKeyboardLayoutListForInputDevice(
-                    keyboardDevice.identifier, USER_ID, imeInfo,
-                    createImeSubtypeForLanguageTag("hi")
-                )
-            assertEquals("New UI: getKeyboardLayoutListForInputDevice API should return empty " +
-                    "list if no supported layouts available",
-                0,
-                keyboardLayouts.size
-            )
-
-            // If user manually selected some layout, always provide it in the layout list
-            val imeSubtype = createImeSubtypeForLanguageTag("hi")
-            keyboardLayoutManager.setKeyboardLayoutForInputDevice(
-                keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype,
-                ENGLISH_US_LAYOUT_DESCRIPTOR
-            )
-            keyboardLayouts =
-                keyboardLayoutManager.getKeyboardLayoutListForInputDevice(
-                    keyboardDevice.identifier, USER_ID, imeInfo,
-                    imeSubtype
-                )
-            assertEquals("New UI: getKeyboardLayoutListForInputDevice API should return user " +
-                    "selected layout even if the script is incompatible with IME",
-                    1,
-                keyboardLayouts.size
-            )
-
-            // Special case Japanese: UScript ignores provided script code for certain language tags
-            // Should manually match provided script codes and then rely on Uscript to derive
-            // script from language tags and match those.
-            keyboardLayouts =
-                keyboardLayoutManager.getKeyboardLayoutListForInputDevice(
-                        keyboardDevice.identifier, USER_ID, imeInfo,
-                        createImeSubtypeForLanguageTag("ja-Latn-JP")
-                )
-            assertNotEquals(
-                "New UI: getKeyboardLayoutListForInputDevice API should return the list of " +
-                        "supported layouts with matching script code for ja-Latn-JP",
-                0,
-                keyboardLayouts.size
-            )
-            assertTrue("New UI: getKeyboardLayoutListForInputDevice API should return a list " +
-                    "containing English(US) layout for ja-Latn-JP",
-                containsLayout(keyboardLayouts, ENGLISH_US_LAYOUT_DESCRIPTOR)
-            )
-            assertTrue("New UI: getKeyboardLayoutListForInputDevice API should return a list " +
-                    "containing English(No script code) layout for ja-Latn-JP",
-                containsLayout(
-                    keyboardLayouts,
-                    createLayoutDescriptor("keyboard_layout_english_without_script_code")
-                )
-            )
-
-            // If script code not explicitly provided for Japanese should rely on Uscript to find
-            // derived script code and hence no suitable layout will be found.
-            keyboardLayouts =
-                keyboardLayoutManager.getKeyboardLayoutListForInputDevice(
-                        keyboardDevice.identifier, USER_ID, imeInfo,
-                        createImeSubtypeForLanguageTag("ja-JP")
-                )
-            assertEquals(
-                "New UI: getKeyboardLayoutListForInputDevice API should return empty list of " +
-                        "supported layouts with matching script code for ja-JP",
-                0,
-                keyboardLayouts.size
-            )
-
-            // If IME doesn't have a corresponding language tag, then should show all available
-            // layouts no matter the script code.
-            keyboardLayouts =
-                keyboardLayoutManager.getKeyboardLayoutListForInputDevice(
-                    keyboardDevice.identifier, USER_ID, imeInfo, null
-                )
-            assertNotEquals(
-                "New UI: getKeyboardLayoutListForInputDevice API should return all layouts if" +
-                    "language tag or subtype not provided",
-                0,
-                keyboardLayouts.size
-            )
-            assertTrue("New UI: getKeyboardLayoutListForInputDevice API should contain Latin " +
-                "layouts if language tag or subtype not provided",
-                containsLayout(keyboardLayouts, ENGLISH_US_LAYOUT_DESCRIPTOR)
-            )
-            assertTrue("New UI: getKeyboardLayoutListForInputDevice API should contain Cyrillic " +
-                "layouts if language tag or subtype not provided",
-                containsLayout(
-                    keyboardLayouts,
-                    createLayoutDescriptor("keyboard_layout_russian")
-                )
-            )
-        }
-    }
-
-    @Test
-    fun testNewUi_getDefaultKeyboardLayoutForInputDevice_withImeLanguageTag() {
-        NewSettingsApiFlag(true).use {
-            assertCorrectLayout(
-                keyboardDevice,
-                createImeSubtypeForLanguageTag("en-US"),
-                ENGLISH_US_LAYOUT_DESCRIPTOR
-            )
-            assertCorrectLayout(
-                keyboardDevice,
-                createImeSubtypeForLanguageTag("en-GB"),
-                ENGLISH_UK_LAYOUT_DESCRIPTOR
-            )
-            assertCorrectLayout(
-                keyboardDevice,
-                createImeSubtypeForLanguageTag("de"),
-                createLayoutDescriptor("keyboard_layout_german")
-            )
-            assertCorrectLayout(
-                keyboardDevice,
-                createImeSubtypeForLanguageTag("fr-FR"),
-                createLayoutDescriptor("keyboard_layout_french")
-            )
-            assertCorrectLayout(
-                keyboardDevice,
-                createImeSubtypeForLanguageTag("ru"),
-                createLayoutDescriptor("keyboard_layout_russian")
-            )
-            assertNull(
-                "New UI: getDefaultKeyboardLayoutForInputDevice should return null when no " +
-                        "layout available",
-                keyboardLayoutManager.getKeyboardLayoutForInputDevice(
-                    keyboardDevice.identifier, USER_ID, imeInfo,
-                    createImeSubtypeForLanguageTag("it")
-                )
-            )
-            assertNull(
-                "New UI: getDefaultKeyboardLayoutForInputDevice should return null when no " +
-                        "layout for script code is available",
-                keyboardLayoutManager.getKeyboardLayoutForInputDevice(
-                    keyboardDevice.identifier, USER_ID, imeInfo,
-                    createImeSubtypeForLanguageTag("en-Deva")
-                )
-            )
-        }
-    }
-
-    @Test
-    fun testNewUi_getDefaultKeyboardLayoutForInputDevice_withImeLanguageTagAndLayoutType() {
-        NewSettingsApiFlag(true).use {
-            assertCorrectLayout(
-                keyboardDevice,
-                createImeSubtypeForLanguageTagAndLayoutType("en-US", "qwerty"),
-                ENGLISH_US_LAYOUT_DESCRIPTOR
-            )
-            assertCorrectLayout(
-                keyboardDevice,
-                createImeSubtypeForLanguageTagAndLayoutType("en-US", "dvorak"),
-                createLayoutDescriptor("keyboard_layout_english_us_dvorak")
-            )
-            // Try to match layout type even if country doesn't match
-            assertCorrectLayout(
-                keyboardDevice,
-                createImeSubtypeForLanguageTagAndLayoutType("en-GB", "dvorak"),
-                createLayoutDescriptor("keyboard_layout_english_us_dvorak")
-            )
-            // Choose layout based on layout type priority, if layout type is not provided by IME
-            // (Qwerty > Dvorak > Extended)
-            assertCorrectLayout(
-                keyboardDevice,
-                createImeSubtypeForLanguageTagAndLayoutType("en-US", ""),
-                ENGLISH_US_LAYOUT_DESCRIPTOR
-            )
-            assertCorrectLayout(
-                keyboardDevice,
-                createImeSubtypeForLanguageTagAndLayoutType("en-GB", "qwerty"),
-                ENGLISH_UK_LAYOUT_DESCRIPTOR
-            )
-            assertCorrectLayout(
-                keyboardDevice,
-                createImeSubtypeForLanguageTagAndLayoutType("de", "qwertz"),
-                createLayoutDescriptor("keyboard_layout_german")
-            )
-            // Wrong layout type should match with language if provided layout type not available
-            assertCorrectLayout(
-                keyboardDevice,
-                createImeSubtypeForLanguageTagAndLayoutType("de", "qwerty"),
-                createLayoutDescriptor("keyboard_layout_german")
-            )
-            assertCorrectLayout(
-                keyboardDevice,
-                createImeSubtypeForLanguageTagAndLayoutType("fr-FR", "azerty"),
-                createLayoutDescriptor("keyboard_layout_french")
-            )
-            assertCorrectLayout(
-                keyboardDevice,
-                createImeSubtypeForLanguageTagAndLayoutType("ru", "qwerty"),
-                createLayoutDescriptor("keyboard_layout_russian_qwerty")
-            )
-            // If layout type is empty then prioritize KCM with empty layout type
-            assertCorrectLayout(
-                keyboardDevice,
-                createImeSubtypeForLanguageTagAndLayoutType("ru", ""),
-                createLayoutDescriptor("keyboard_layout_russian")
-            )
-            assertNull("New UI: getDefaultKeyboardLayoutForInputDevice should return null when " +
-                    "no layout for script code is available",
-                keyboardLayoutManager.getKeyboardLayoutForInputDevice(
-                    keyboardDevice.identifier, USER_ID, imeInfo,
-                    createImeSubtypeForLanguageTagAndLayoutType("en-Deva-US", "")
-                )
-            )
-        }
-    }
-
-    @Test
-    fun testNewUi_getDefaultKeyboardLayoutForInputDevice_withHwLanguageTagAndLayoutType() {
-        NewSettingsApiFlag(true).use {
-            val frenchSubtype = createImeSubtypeForLanguageTagAndLayoutType("fr", "azerty")
-            // Should return English dvorak even if IME current layout is French, since HW says the
-            // keyboard is a Dvorak keyboard
-            assertCorrectLayout(
-                englishDvorakKeyboardDevice,
-                frenchSubtype,
-                createLayoutDescriptor("keyboard_layout_english_us_dvorak")
-            )
-
-            // Back to back changing HW keyboards with same product and vendor ID but different
-            // language and layout type should configure the layouts correctly.
-            assertCorrectLayout(
-                englishQwertyKeyboardDevice,
-                frenchSubtype,
-                createLayoutDescriptor("keyboard_layout_english_us")
-            )
-
-            // Fallback to IME information if the HW provided layout script is incompatible with the
-            // provided IME subtype
-            assertCorrectLayout(
-                englishDvorakKeyboardDevice,
-                createImeSubtypeForLanguageTagAndLayoutType("ru", ""),
-                createLayoutDescriptor("keyboard_layout_russian")
-            )
-        }
-    }
-
-    private fun assertCorrectLayout(
-        device: InputDevice,
-        imeSubtype: InputMethodSubtype,
-        expectedLayout: String
-    ) {
-        assertEquals(
-            "New UI: getDefaultKeyboardLayoutForInputDevice should return $expectedLayout",
-            expectedLayout,
-            keyboardLayoutManager.getKeyboardLayoutForInputDevice(
-                device.identifier, USER_ID, imeInfo, imeSubtype
-            )
-        )
-    }
-
-    private fun createImeSubtype(): InputMethodSubtype =
-        InputMethodSubtype.InputMethodSubtypeBuilder().setSubtypeId(nextImeSubtypeId++).build()
-
-    private fun createImeSubtypeForLanguageTag(languageTag: String): InputMethodSubtype =
-        InputMethodSubtype.InputMethodSubtypeBuilder().setSubtypeId(nextImeSubtypeId++)
-            .setLanguageTag(languageTag).build()
-
-    private fun createImeSubtypeForLanguageTagAndLayoutType(
-        languageTag: String,
-        layoutType: String
-    ): InputMethodSubtype =
-        InputMethodSubtype.InputMethodSubtypeBuilder().setSubtypeId(nextImeSubtypeId++)
-            .setPhysicalKeyboardHint(ULocale.forLanguageTag(languageTag), layoutType).build()
-
-    private fun hasLayout(layoutList: Array<KeyboardLayout>, layoutDesc: String): Boolean {
-        for (kl in layoutList) {
-            if (kl.descriptor == layoutDesc) {
-                return true
-            }
-        }
-        return false
-    }
-
-    private fun createLayoutDescriptor(keyboardName: String): String =
-        "$PACKAGE_NAME/$RECEIVER_NAME/$keyboardName"
-
-    private fun containsLayout(layoutList: Array<KeyboardLayout>, layoutDesc: String): Boolean {
-        for (kl in layoutList) {
-            if (kl.descriptor.equals(layoutDesc)) {
-                return true
-            }
-        }
-        return false
-    }
-
-    private fun createMockReceiver(): ResolveInfo {
-        val info = ResolveInfo()
-        info.activityInfo = ActivityInfo()
-        info.activityInfo.packageName = PACKAGE_NAME
-        info.activityInfo.name = RECEIVER_NAME
-        info.activityInfo.applicationInfo = ApplicationInfo()
-        info.activityInfo.metaData = Bundle()
-        info.activityInfo.metaData.putInt(
-            InputManager.META_DATA_KEYBOARD_LAYOUTS,
-            R.xml.keyboard_layouts
-        )
-        info.serviceInfo = ServiceInfo()
-        info.serviceInfo.packageName = PACKAGE_NAME
-        info.serviceInfo.name = RECEIVER_NAME
-        return info
-    }
-
-    private inner class NewSettingsApiFlag constructor(enabled: Boolean) : AutoCloseable {
-        init {
-            Settings.Global.putString(
-                context.contentResolver,
-                "settings_new_keyboard_ui", enabled.toString()
-            )
-        }
-
-        override fun close() {
-            Settings.Global.putString(
-                context.contentResolver,
-                "settings_new_keyboard_ui",
-                ""
-            )
-        }
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/input/OWNERS b/services/tests/servicestests/src/com/android/server/input/OWNERS
deleted file mode 100644
index 6e9aa1d..0000000
--- a/services/tests/servicestests/src/com/android/server/input/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-include /services/core/java/com/android/server/input/OWNERS
-
diff --git a/services/tests/servicestests/src/com/android/server/inputmethod/OWNERS b/services/tests/servicestests/src/com/android/server/inputmethod/OWNERS
index 5deb2ce..cbd94ba 100644
--- a/services/tests/servicestests/src/com/android/server/inputmethod/OWNERS
+++ b/services/tests/servicestests/src/com/android/server/inputmethod/OWNERS
@@ -1 +1,2 @@
+# Bug component: 34867
 include /core/java/android/view/inputmethod/OWNERS
diff --git a/services/tests/servicestests/src/com/android/server/lights/LightsServiceTest.java b/services/tests/servicestests/src/com/android/server/lights/LightsServiceTest.java
index 75aacd1..392dcdb 100644
--- a/services/tests/servicestests/src/com/android/server/lights/LightsServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/lights/LightsServiceTest.java
@@ -24,6 +24,10 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+
+import android.Manifest;
 import android.content.Context;
 import android.hardware.light.HwLight;
 import android.hardware.light.HwLightState;
@@ -33,6 +37,8 @@
 import android.hardware.lights.LightsManager;
 import android.hardware.lights.SystemLightsManager;
 import android.os.Looper;
+import android.os.PermissionEnforcer;
+import android.os.test.FakePermissionEnforcer;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -92,6 +98,13 @@
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
+        // The AIDL stub will use PermissionEnforcer to check permission from the caller.
+        FakePermissionEnforcer permissionEnforcer = new FakePermissionEnforcer();
+        permissionEnforcer.grant(Manifest.permission.CONTROL_DEVICE_LIGHTS);
+        doReturn(Context.PERMISSION_ENFORCER_SERVICE).when(mContext).getSystemServiceName(
+                eq(PermissionEnforcer.class));
+        doReturn(permissionEnforcer).when(mContext).getSystemService(
+                eq(Context.PERMISSION_ENFORCER_SERVICE));
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
index e960e99..fe2ac17 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
@@ -254,6 +254,8 @@
                 .thenReturn(true);
         when(res.getBoolean(eq(com.android.internal.R.bool.config_strongAuthRequiredOnBoot)))
                 .thenReturn(true);
+        when(res.getBoolean(eq(com.android.internal.R.bool.config_repairModeSupported)))
+                .thenReturn(true);
         return res;
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
index 60a033f..5a62d92 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
@@ -77,6 +77,26 @@
         testSetCredentialFailsWithoutLockScreen(PRIMARY_USER_ID, newPassword("password"));
     }
 
+    @Test(expected = IllegalArgumentException.class)
+    public void testSetTooShortPatternFails() throws RemoteException {
+        mService.setLockCredential(newPattern("123"), nonePassword(), PRIMARY_USER_ID);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testSetTooShortPinFails() throws RemoteException {
+        mService.setLockCredential(newPin("123"), nonePassword(), PRIMARY_USER_ID);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testSetTooShortPassword() throws RemoteException {
+        mService.setLockCredential(newPassword("123"), nonePassword(), PRIMARY_USER_ID);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testSetPasswordWithInvalidChars() throws RemoteException {
+        mService.setLockCredential(newPassword("§µ¿¶¥£"), nonePassword(), PRIMARY_USER_ID);
+    }
+
     @Test
     public void testSetPatternPrimaryUser() throws RemoteException {
         setAndVerifyCredential(PRIMARY_USER_ID, newPattern("123456789"));
@@ -94,7 +114,7 @@
 
     @Test
     public void testChangePatternPrimaryUser() throws RemoteException {
-        testChangeCredential(PRIMARY_USER_ID, newPassword("!£$%^&*(())"), newPattern("1596321"));
+        testChangeCredential(PRIMARY_USER_ID, newPassword("password"), newPattern("1596321"));
     }
 
     @Test
@@ -185,7 +205,7 @@
         assertNotNull(mGateKeeperService.getAuthToken(MANAGED_PROFILE_USER_ID));
         assertEquals(profileSid, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID));
 
-        setCredential(PRIMARY_USER_ID, newPassword("pwd"), primaryPassword);
+        setCredential(PRIMARY_USER_ID, newPassword("password"), primaryPassword);
         assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
                 profilePassword, MANAGED_PROFILE_USER_ID, 0 /* flags */)
                 .getResponseCode());
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java
index 36dc6c5..a029db9 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java
@@ -84,6 +84,11 @@
     }
 
     @Override
+    File getRepairModePersistentDataFile() {
+        return remapToStorageDir(super.getRepairModePersistentDataFile());
+    }
+
+    @Override
     PersistentDataBlockManagerInternal getPersistentDataBlockManager() {
         return mPersistentDataBlockManager;
     }
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java
index 10ed882..23f14f8 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java
@@ -457,6 +457,31 @@
         assertEquals(2, PersistentData.TYPE_SP_WEAVER);
     }
 
+    @Test
+    public void testRepairMode_emptyPersistentData() {
+        assertSame(PersistentData.NONE, mStorage.readPersistentDataBlock());
+    }
+
+    @Test
+    public void testRepairMode_writeGatekeeperPersistentData() {
+        mStorage.writeRepairModePersistentData(
+                PersistentData.TYPE_SP_GATEKEEPER, SOME_USER_ID, PAYLOAD);
+
+        final PersistentData data = mStorage.readRepairModePersistentData();
+        assertEquals(PersistentData.TYPE_SP_GATEKEEPER, data.type);
+        assertArrayEquals(PAYLOAD, data.payload);
+    }
+
+    @Test
+    public void testRepairMode_writeWeaverPersistentData() {
+        mStorage.writeRepairModePersistentData(
+                PersistentData.TYPE_SP_WEAVER, SOME_USER_ID, PAYLOAD);
+
+        final PersistentData data = mStorage.readRepairModePersistentData();
+        assertEquals(PersistentData.TYPE_SP_WEAVER, data.type);
+        assertArrayEquals(PAYLOAD, data.payload);
+    }
+
     private static void assertArrayEquals(byte[] expected, byte[] actual) {
         if (!Arrays.equals(expected, actual)) {
             fail("expected:<" + Arrays.toString(expected) +
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockscreenRepairModeTest.java b/services/tests/servicestests/src/com/android/server/locksettings/LockscreenRepairModeTest.java
new file mode 100644
index 0000000..70150c5
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockscreenRepairModeTest.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.locksettings;
+
+import static com.android.internal.widget.LockPatternUtils.USER_REPAIR_MODE;
+import static com.android.internal.widget.LockPatternUtils.VERIFY_FLAG_WRITE_REPAIR_MODE_PW;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+
+import android.app.PropertyInvalidatedCache;
+import android.platform.test.annotations.Presubmit;
+import android.provider.Settings;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.VerifyCredentialResponse;
+import com.android.server.locksettings.LockSettingsStorage.PersistentData;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class LockscreenRepairModeTest extends BaseLockSettingsServiceTests {
+
+    @Before
+    public void setUp() throws Exception {
+        PropertyInvalidatedCache.disableForTestMode();
+        mService.initializeSyntheticPassword(PRIMARY_USER_ID);
+    }
+
+    @Test
+    public void verifyPin_writeRepairModePW() {
+        mService.setLockCredential(newPin("1234"), nonePassword(), PRIMARY_USER_ID);
+        assertSame(PersistentData.NONE, mStorage.readRepairModePersistentData());
+
+        assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+                mService.verifyCredential(
+                        newPin("1234"), PRIMARY_USER_ID, VERIFY_FLAG_WRITE_REPAIR_MODE_PW)
+                        .getResponseCode());
+        assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PIN,
+                mService.getCredentialType(USER_REPAIR_MODE));
+    }
+
+    @Test
+    public void verifyPattern_writeRepairModePW() {
+        mService.setLockCredential(newPattern("4321"), nonePassword(), PRIMARY_USER_ID);
+        assertSame(PersistentData.NONE, mStorage.readRepairModePersistentData());
+
+        assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+                mService.verifyCredential(
+                        newPattern("4321"), PRIMARY_USER_ID, VERIFY_FLAG_WRITE_REPAIR_MODE_PW)
+                        .getResponseCode());
+        assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PATTERN,
+                mService.getCredentialType(USER_REPAIR_MODE));
+    }
+
+    @Test
+    public void verifyPassword_writeRepairModePW() {
+        mService.setLockCredential(newPassword("4321"), nonePassword(), PRIMARY_USER_ID);
+        assertSame(PersistentData.NONE, mStorage.readRepairModePersistentData());
+
+        assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+                mService.verifyCredential(
+                        newPassword("4321"), PRIMARY_USER_ID, VERIFY_FLAG_WRITE_REPAIR_MODE_PW)
+                        .getResponseCode());
+        assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PASSWORD,
+                mService.getCredentialType(USER_REPAIR_MODE));
+    }
+
+    @Test
+    public void verifyCredential_writeRepairModePW_repairModeActive() {
+        mService.setLockCredential(newPin("1234"), nonePassword(), PRIMARY_USER_ID);
+        assertSame(PersistentData.NONE, mStorage.readRepairModePersistentData());
+
+        setRepairModeActive(true);
+        assertEquals(VerifyCredentialResponse.RESPONSE_ERROR,
+                mService.verifyCredential(
+                                newPin("1234"), PRIMARY_USER_ID, VERIFY_FLAG_WRITE_REPAIR_MODE_PW)
+                        .getResponseCode());
+        assertSame(PersistentData.NONE, mStorage.readRepairModePersistentData());
+    }
+
+    @Test
+    public void deleteRepairModePersistentData() {
+        mService.setLockCredential(newPin("1234"), nonePassword(), PRIMARY_USER_ID);
+        assertSame(PersistentData.NONE, mStorage.readRepairModePersistentData());
+        assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+                mService.verifyCredential(
+                                newPin("1234"), PRIMARY_USER_ID, VERIFY_FLAG_WRITE_REPAIR_MODE_PW)
+                        .getResponseCode());
+        assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PIN,
+                mService.getCredentialType(USER_REPAIR_MODE));
+
+        mService.deleteRepairModePersistentDataIfNeeded();
+        assertSame(PersistentData.NONE, mStorage.readRepairModePersistentData());
+    }
+
+    @Test
+    public void verifyPin_userRepairMode() {
+        mService.setLockCredential(newPin("1234"), nonePassword(), PRIMARY_USER_ID);
+        assertSame(PersistentData.NONE, mStorage.readRepairModePersistentData());
+        assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+                mService.verifyCredential(
+                        newPin("1234"), PRIMARY_USER_ID, VERIFY_FLAG_WRITE_REPAIR_MODE_PW)
+                        .getResponseCode());
+        setRepairModeActive(true);
+
+        assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PIN,
+                mService.getCredentialType(USER_REPAIR_MODE));
+        assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+                mService.verifyCredential(newPin("1234"), USER_REPAIR_MODE, 0 /* flags */)
+                        .getResponseCode());
+    }
+
+    @Test
+    public void verifyPattern_userRepairMode() {
+        mService.setLockCredential(newPattern("4321"), nonePassword(), PRIMARY_USER_ID);
+        assertSame(PersistentData.NONE, mStorage.readRepairModePersistentData());
+        assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+                mService.verifyCredential(
+                        newPattern("4321"), PRIMARY_USER_ID, VERIFY_FLAG_WRITE_REPAIR_MODE_PW)
+                        .getResponseCode());
+        setRepairModeActive(true);
+
+        assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PATTERN,
+                mService.getCredentialType(USER_REPAIR_MODE));
+        assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+                mService.verifyCredential(newPattern("4321"), USER_REPAIR_MODE, 0 /* flags */)
+                        .getResponseCode());
+    }
+
+    @Test
+    public void verifyPassword_userRepairMode() {
+        mService.setLockCredential(newPassword("4321"), nonePassword(), PRIMARY_USER_ID);
+        assertSame(PersistentData.NONE, mStorage.readRepairModePersistentData());
+        assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+                mService.verifyCredential(
+                        newPassword("4321"), PRIMARY_USER_ID, VERIFY_FLAG_WRITE_REPAIR_MODE_PW)
+                        .getResponseCode());
+        setRepairModeActive(true);
+
+        assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PASSWORD,
+                mService.getCredentialType(USER_REPAIR_MODE));
+        assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+                mService.verifyCredential(newPassword("4321"), USER_REPAIR_MODE, 0 /* flags */)
+                        .getResponseCode());
+    }
+
+    @Test
+    public void verifyCredential_userRepairMode_repairModeIsNotActive() {
+        mService.setLockCredential(newPin("1234"), nonePassword(), PRIMARY_USER_ID);
+        assertSame(PersistentData.NONE, mStorage.readRepairModePersistentData());
+        assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+                mService.verifyCredential(
+                                newPin("1234"), PRIMARY_USER_ID, VERIFY_FLAG_WRITE_REPAIR_MODE_PW)
+                        .getResponseCode());
+
+        assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PIN,
+                mService.getCredentialType(USER_REPAIR_MODE));
+        assertEquals(VerifyCredentialResponse.RESPONSE_ERROR,
+                mService.verifyCredential(newPin("1234"), USER_REPAIR_MODE, 0 /* flags */)
+                        .getResponseCode());
+    }
+
+    @Test
+    public void verifyCredential_userRepairMode_wrongPin() {
+        mService.setLockCredential(newPin("1234"), nonePassword(), PRIMARY_USER_ID);
+        assertSame(PersistentData.NONE, mStorage.readRepairModePersistentData());
+        assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+                mService.verifyCredential(
+                                newPin("1234"), PRIMARY_USER_ID, VERIFY_FLAG_WRITE_REPAIR_MODE_PW)
+                        .getResponseCode());
+        setRepairModeActive(true);
+
+        assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PIN,
+                mService.getCredentialType(USER_REPAIR_MODE));
+        assertEquals(VerifyCredentialResponse.RESPONSE_ERROR,
+                mService.verifyCredential(newPin("5678"), USER_REPAIR_MODE, 0 /* flags */)
+                        .getResponseCode());
+    }
+
+    private void setRepairModeActive(boolean active) {
+        Settings.Global.putInt(mContext.getContentResolver(),
+                Settings.Global.REPAIR_MODE_ACTIVE, active ? 1 : 0);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
index ce0347d..dee7780 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
@@ -215,12 +215,12 @@
     @Test
     public void testChangeCredentialKeepsAuthSecret() throws RemoteException {
         LockscreenCredential password = newPassword("password");
-        LockscreenCredential badPassword = newPassword("new");
+        LockscreenCredential newPassword = newPassword("newPassword");
 
         initSpAndSetCredential(PRIMARY_USER_ID, password);
-        mService.setLockCredential(badPassword, password, PRIMARY_USER_ID);
+        mService.setLockCredential(newPassword, password, PRIMARY_USER_ID);
         assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
-                badPassword, PRIMARY_USER_ID, 0 /* flags */).getResponseCode());
+                newPassword, PRIMARY_USER_ID, 0 /* flags */).getResponseCode());
 
         // Check the same secret was passed each time
         ArgumentCaptor<byte[]> secret = ArgumentCaptor.forClass(byte[].class);
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java
index c546a74..c09e09c 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java
@@ -27,6 +27,7 @@
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertThrows;
 
 import android.app.KeyguardManager;
 import android.content.Context;
@@ -54,6 +55,7 @@
 
 import java.io.File;
 import java.security.KeyStore;
+import java.security.KeyStoreException;
 import java.security.UnrecoverableKeyException;
 import java.util.List;
 
@@ -393,6 +395,18 @@
     }
 
     @Test
+    public void getEncryptKey_noScreenlock() throws Exception {
+        when(mKeyguardManager.isDeviceSecure(USER_ID_FIXTURE)).thenReturn(false);
+        doThrow(new KeyStoreException()).when(mKeyStoreProxy).setEntry(
+                anyString(),
+                any(),
+                any());
+
+        assertThrows(InsecureUserException.class,
+                () -> mPlatformKeyManager.getEncryptKey(USER_ID_FIXTURE));
+    }
+
+    @Test
     public void getDecryptKey_generatesNewKeyIfOldOneIsInvalid() throws Exception {
         doThrow(new UnrecoverableKeyException()).when(mKeyStoreProxy).getKey(
                 eq(DECRYPTION_KEY_ALIAS_1),
diff --git a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
index c42928e..bb8b986 100644
--- a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
@@ -38,6 +38,7 @@
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 import static org.testng.Assert.assertThrows;
 
 import android.app.ActivityManagerInternal;
@@ -49,10 +50,14 @@
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.media.projection.IMediaProjection;
 import android.media.projection.IMediaProjectionCallback;
+import android.media.projection.IMediaProjectionWatcherCallback;
 import android.media.projection.ReviewGrantedConsentResult;
+import android.os.Binder;
 import android.os.IBinder;
+import android.os.Looper;
 import android.os.RemoteException;
 import android.os.UserHandle;
+import android.os.test.TestLooper;
 import android.platform.test.annotations.Presubmit;
 import android.view.ContentRecordingSession;
 
@@ -86,6 +91,7 @@
     private static final int UID = 10;
     private static final String PACKAGE_NAME = "test.package";
     private final ApplicationInfo mAppInfo = new ApplicationInfo();
+    private final TestLooper mTestLooper = new TestLooper();
     private static final ContentRecordingSession DISPLAY_SESSION =
             ContentRecordingSession.createDisplaySession(DEFAULT_DISPLAY);
     // Callback registered by an app on a MediaProjection instance.
@@ -110,6 +116,14 @@
                 }
             };
 
+    private final MediaProjectionManagerService.Injector mTestLooperInjector =
+            new MediaProjectionManagerService.Injector() {
+                @Override
+                Looper createCallbackLooper() {
+                    return mTestLooper.getLooper();
+                }
+            };
+
     private Context mContext;
     private MediaProjectionManagerService mService;
     private OffsettableClock mClock;
@@ -122,12 +136,15 @@
     private WindowManagerInternal mWindowManagerInternal;
     @Mock
     private PackageManager mPackageManager;
+    @Mock
+    private IMediaProjectionWatcherCallback mWatcherCallback;
     @Captor
     private ArgumentCaptor<ContentRecordingSession> mSessionCaptor;
 
     @Before
     public void setup() throws Exception {
         MockitoAnnotations.initMocks(this);
+        when(mWatcherCallback.asBinder()).thenReturn(new Binder());
 
         LocalServices.removeServiceForTest(ActivityManagerInternal.class);
         LocalServices.addService(ActivityManagerInternal.class, mAmInternal);
@@ -671,6 +688,59 @@
         assertThat(mService.isCurrentProjection(projection)).isTrue();
     }
 
+    @Test
+    public void setContentRecordingSession_successful_notifiesListeners()
+            throws Exception {
+        mService.addCallback(mWatcherCallback);
+        MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions();
+        projection.start(mIMediaProjectionCallback);
+
+        doReturn(true).when(mWindowManagerInternal).setContentRecordingSession(
+                any(ContentRecordingSession.class));
+        mService.setContentRecordingSession(DISPLAY_SESSION);
+
+        verify(mWatcherCallback).onRecordingSessionSet(
+                projection.getProjectionInfo(),
+                DISPLAY_SESSION
+        );
+    }
+
+    @Test
+    public void setContentRecordingSession_notifiesListenersOnCallbackLooper()
+            throws Exception {
+        mService = new MediaProjectionManagerService(mContext, mTestLooperInjector);
+        mService.addCallback(mWatcherCallback);
+        MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions();
+        projection.start(mIMediaProjectionCallback);
+        doReturn(true).when(mWindowManagerInternal).setContentRecordingSession(
+                any(ContentRecordingSession.class));
+
+        mService.setContentRecordingSession(DISPLAY_SESSION);
+        // Callback not notified yet, as test looper hasn't dispatched the message yet
+        verify(mWatcherCallback, never()).onRecordingSessionSet(any(), any());
+
+        mTestLooper.dispatchAll();
+        // Message dispatched on test looper. Callback should now be notified.
+        verify(mWatcherCallback).onRecordingSessionSet(
+                projection.getProjectionInfo(),
+                DISPLAY_SESSION
+        );
+    }
+
+    @Test
+    public void setContentRecordingSession_failure_doesNotNotifyListeners()
+            throws Exception {
+        mService.addCallback(mWatcherCallback);
+        MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions();
+        projection.start(mIMediaProjectionCallback);
+
+        doReturn(false).when(mWindowManagerInternal).setContentRecordingSession(
+                any(ContentRecordingSession.class));
+        mService.setContentRecordingSession(DISPLAY_SESSION);
+
+        verify(mWatcherCallback, never()).onRecordingSessionSet(any(), any());
+    }
+
     private void verifySetSessionWithContent(@ContentRecordingSession.RecordContent int content) {
         verify(mWindowManagerInternal, atLeastOnce()).setContentRecordingSession(
                 mSessionCaptor.capture());
diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
index c04df30..2a76452 100644
--- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
@@ -157,6 +157,7 @@
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.SubscriptionPlan;
+import android.telephony.TelephonyCallback;
 import android.telephony.TelephonyManager;
 import android.test.suitebuilder.annotation.MediumTest;
 import android.text.TextUtils;
@@ -247,6 +248,7 @@
     private static final String TEST_WIFI_NETWORK_KEY = "TestWifiNetworkKey";
     private static final String TEST_IMSI = "310210";
     private static final int TEST_SUB_ID = 42;
+    private static final int TEST_SUB_ID2 = 24;
     private static final Network TEST_NETWORK = mock(Network.class, CALLS_REAL_METHODS);
 
     private static NetworkTemplate sTemplateWifi = new NetworkTemplate.Builder(MATCH_WIFI)
@@ -288,6 +290,8 @@
     private ArgumentCaptor<ConnectivityManager.NetworkCallback> mNetworkCallbackCaptor =
             ArgumentCaptor.forClass(ConnectivityManager.NetworkCallback.class);
 
+    private TelephonyCallback.ActiveDataSubscriptionIdListener mActiveDataSubIdListener;
+
     private ActivityManagerInternal mActivityManagerInternal;
     private PackageManagerInternal mPackageManagerInternal;
 
@@ -363,6 +367,8 @@
 
     private class TestDependencies extends NetworkPolicyManagerService.Dependencies {
         private final SparseArray<NetworkStats.Bucket> mMockedStats = new SparseArray<>();
+        private int mMockDefaultDataSubId;
+        private int mMockedActiveDataSubId;
 
         TestDependencies(Context context) {
             super(context);
@@ -400,6 +406,21 @@
             final NetworkStats.Bucket bucket = mMockedStats.get(uid);
             setMockedTotalBytes(uid, bucket.getRxBytes() + rxBytes, bucket.getTxBytes() + txBytes);
         }
+
+        void setDefaultAndActiveDataSubId(int defaultDataSubId, int activeDataSubId) {
+            mMockDefaultDataSubId = defaultDataSubId;
+            mMockedActiveDataSubId = activeDataSubId;
+        }
+
+        @Override
+        int getDefaultDataSubId() {
+            return mMockDefaultDataSubId;
+        }
+
+        @Override
+        int getActivateDataSubId() {
+            return mMockedActiveDataSubId;
+        }
     }
 
     // TODO: Use TestLooperManager instead.
@@ -577,6 +598,14 @@
         NetworkPolicy defaultPolicy = mService.buildDefaultCarrierPolicy(0, "");
         mDefaultWarningBytes = defaultPolicy.warningBytes;
         mDefaultLimitBytes = defaultPolicy.limitBytes;
+
+        // Catch TelephonyCallback during systemReady().
+        ArgumentCaptor<TelephonyCallback> telephonyCallbackArgumentCaptor =
+                ArgumentCaptor.forClass(TelephonyCallback.class);
+        verify(mTelephonyManager).registerTelephonyCallback(any(),
+                telephonyCallbackArgumentCaptor.capture());
+        mActiveDataSubIdListener = (TelephonyCallback.ActiveDataSubscriptionIdListener)
+                telephonyCallbackArgumentCaptor.getValue();
     }
 
     @After
@@ -1357,6 +1386,7 @@
 
             reset(mTelephonyManager, mNetworkManager, mNotifManager);
             TelephonyManager tmSub = expectMobileDefaults();
+            clearInvocations(mNotifManager);
 
             mService.updateNetworks();
 
@@ -1372,6 +1402,7 @@
 
             reset(mTelephonyManager, mNetworkManager, mNotifManager);
             TelephonyManager tmSub = expectMobileDefaults();
+            clearInvocations(mNotifManager);
 
             mService.updateNetworks();
 
@@ -1389,6 +1420,7 @@
             reset(mTelephonyManager, mNetworkManager, mNotifManager);
             TelephonyManager tmSub = expectMobileDefaults();
             expectDefaultCarrierConfig();
+            clearInvocations(mNotifManager);
 
             mService.updateNetworks();
 
@@ -1405,6 +1437,7 @@
 
             reset(mTelephonyManager, mNetworkManager, mNotifManager);
             TelephonyManager tmSub = expectMobileDefaults();
+            clearInvocations(mNotifManager);
 
             mService.updateNetworks();
 
@@ -1418,6 +1451,7 @@
         {
             reset(mTelephonyManager, mNetworkManager, mNotifManager);
             TelephonyManager tmSub = expectMobileDefaults();
+            clearInvocations(mNotifManager);
 
             mService.snoozeLimit(sTemplateCarrierMetered);
             mService.updateNetworks();
@@ -1428,6 +1462,31 @@
             verify(mNotifManager, atLeastOnce()).notifyAsUser(any(), eq(TYPE_LIMIT_SNOOZED),
                     isA(Notification.class), eq(UserHandle.ALL));
         }
+        // The sub is no longer used for data(e.g. user uses another sub), hide the notifications.
+        {
+            reset(mTelephonyManager, mNetworkManager, mNotifManager);
+
+            notifyDefaultAndActiveDataSubIdChange(TEST_SUB_ID2, TEST_SUB_ID2);
+            verify(mNotifManager, atLeastOnce()).cancel(any(), eq(TYPE_LIMIT_SNOOZED));
+        }
+        // The sub is not active for data(e.g. due to auto data switch), but still default for data,
+        // show notification.
+        {
+            reset(mTelephonyManager, mNetworkManager, mNotifManager);
+
+            notifyDefaultAndActiveDataSubIdChange(TEST_SUB_ID, TEST_SUB_ID2);
+            verify(mNotifManager, atLeastOnce()).notifyAsUser(any(), eq(TYPE_LIMIT_SNOOZED),
+                    isA(Notification.class), eq(UserHandle.ALL));
+        }
+        // The sub is active for data, but not the default(e.g. due to auto data switch),
+        // show notification.
+        {
+            reset(mTelephonyManager, mNetworkManager, mNotifManager);
+
+            notifyDefaultAndActiveDataSubIdChange(TEST_SUB_ID2, TEST_SUB_ID);
+            verify(mNotifManager, atLeastOnce()).notifyAsUser(any(), eq(TYPE_LIMIT_SNOOZED),
+                    isA(Notification.class), eq(UserHandle.ALL));
+        }
     }
 
     @Test
@@ -2508,6 +2567,7 @@
             String subscriberId) {
         when(mSubscriptionManager.getActiveSubscriptionInfoList()).thenReturn(
                 createSubscriptionInfoList(subscriptionId));
+        notifyDefaultAndActiveDataSubIdChange(subscriptionId, subscriptionId);
 
         TelephonyManager subTelephonyManager;
         subTelephonyManager = mock(TelephonyManager.class);
@@ -2519,6 +2579,16 @@
     }
 
     /**
+     * Telephony Manager callback notifies data sub Id changes.
+     * @param defaultDataSubId The mock default data sub Id.
+     * @param activeDataSubId The mock active data sub Id.
+     */
+    private void notifyDefaultAndActiveDataSubIdChange(int defaultDataSubId, int activeDataSubId) {
+        mDeps.setDefaultAndActiveDataSubId(defaultDataSubId, activeDataSubId);
+        mActiveDataSubIdListener.onActiveDataSubscriptionIdChanged(activeDataSubId);
+    }
+
+    /**
      * Creates mock {@link SubscriptionInfo} from subscription id.
      */
     private List<SubscriptionInfo> createSubscriptionInfoList(int subId) {
diff --git a/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java b/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
index 52c6777..24029b1 100644
--- a/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
@@ -16,15 +16,18 @@
 
 package com.android.server.os;
 
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertThrows;
 
+import android.app.role.RoleManager;
 import android.content.Context;
 import android.os.Binder;
 import android.os.BugreportManager.BugreportCallback;
 import android.os.IBinder;
 import android.os.IDumpstateListener;
+import android.os.Process;
 import android.os.RemoteException;
 import android.util.ArraySet;
 import android.util.Pair;
@@ -37,21 +40,23 @@
 import org.junit.runner.RunWith;
 
 import java.io.FileDescriptor;
+import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
 
 @RunWith(AndroidJUnit4.class)
 public class BugreportManagerServiceImplTest {
 
-    Context mContext;
-    BugreportManagerServiceImpl mService;
-    BugreportManagerServiceImpl.BugreportFileManager mBugreportFileManager;
+    private Context mContext;
+    private BugreportManagerServiceImpl mService;
+    private BugreportManagerServiceImpl.BugreportFileManager mBugreportFileManager;
 
-    int mCallingUid = 1234;
-    String mCallingPackage  = "test.package";
+    private int mCallingUid = 1234;
+    private String mCallingPackage  = "test.package";
 
-    String mBugreportFile = "bugreport-file.zip";
-    String mBugreportFile2 = "bugreport-file2.zip";
+    private String mBugreportFile = "bugreport-file.zip";
+    private String mBugreportFile2 = "bugreport-file2.zip";
 
     @Before
     public void setUp() {
@@ -109,6 +114,36 @@
                 BugreportCallback.BUGREPORT_ERROR_NO_BUGREPORT_TO_RETRIEVE);
     }
 
+    @Test
+    public void testCancelBugreportWithoutRole() throws Exception {
+        // Clear out allowlisted packages.
+        mService = new BugreportManagerServiceImpl(
+                new BugreportManagerServiceImpl.Injector(mContext, new ArraySet<>()));
+
+        assertThrows(SecurityException.class, () -> mService.cancelBugreport(
+                Binder.getCallingUid(), mContext.getPackageName()));
+    }
+
+    @Test
+    public void testCancelBugreportWithRole() throws Exception {
+        // Clear out allowlisted packages.
+        mService = new BugreportManagerServiceImpl(
+                new BugreportManagerServiceImpl.Injector(mContext, new ArraySet<>()));
+        RoleManager roleManager = mContext.getSystemService(RoleManager.class);
+        CallbackFuture future = new CallbackFuture();
+        runWithShellPermissionIdentity(() -> roleManager.setBypassingRoleQualification(true));
+        runWithShellPermissionIdentity(() -> roleManager.addRoleHolderAsUser(
+                "android.app.role.SYSTEM_AUTOMOTIVE_PROJECTION",
+                mContext.getPackageName(),
+                /* flags= */ 0,
+                Process.myUserHandle(),
+                mContext.getMainExecutor(),
+                future));
+
+        assertThat(future.get()).isEqualTo(true);
+        mService.cancelBugreport(Binder.getCallingUid(), mContext.getPackageName());
+    }
+
     private static class Listener implements IDumpstateListener {
         CountDownLatch mLatch;
         int mErrorCode;
@@ -149,4 +184,12 @@
             return mErrorCode;
         }
     }
+
+    private static class CallbackFuture extends CompletableFuture<Boolean>
+            implements Consumer<Boolean> {
+        @Override
+        public void accept(Boolean successful) {
+            complete(successful);
+        }
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/people/PeopleServiceTest.java b/services/tests/servicestests/src/com/android/server/people/PeopleServiceTest.java
index 5066240..aed8491 100644
--- a/services/tests/servicestests/src/com/android/server/people/PeopleServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/PeopleServiceTest.java
@@ -41,8 +41,10 @@
 import android.app.prediction.AppTarget;
 import android.app.prediction.IPredictionCallback;
 import android.content.Context;
+import android.content.pm.PackageManagerInternal;
 import android.content.pm.ParceledListSlice;
 import android.content.pm.ShortcutInfo;
+import android.content.pm.ShortcutServiceInternal;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.RemoteException;
@@ -56,6 +58,7 @@
 
 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
 import com.android.server.LocalServices;
+import com.android.server.notification.NotificationManagerInternal;
 
 import org.junit.After;
 import org.junit.Before;
@@ -87,6 +90,13 @@
     private AppPredictionContext mPredictionContext;
 
     @Mock
+    ShortcutServiceInternal mShortcutServiceInternal;
+    @Mock
+    PackageManagerInternal mPackageManagerInternal;
+    @Mock
+    NotificationManagerInternal mNotificationManagerInternal;
+
+    @Mock
     private Context mMockContext;
 
     @Rule
@@ -110,6 +120,10 @@
     public void setUp() {
         MockitoAnnotations.initMocks(this);
 
+        LocalServices.addService(ShortcutServiceInternal.class, mShortcutServiceInternal);
+        LocalServices.addService(PackageManagerInternal.class, mPackageManagerInternal);
+        LocalServices.addService(NotificationManagerInternal.class, mNotificationManagerInternal);
+
         mPeopleService = new TestablePeopleService(mContext);
         mTestableLooper = TestableLooper.get(this);
         mIPeopleManager = ((IPeopleManager) mPeopleService.mService);
@@ -137,6 +151,9 @@
     @After
     public void tearDown() {
         LocalServices.removeServiceForTest(PeopleServiceInternal.class);
+        LocalServices.removeServiceForTest(ShortcutServiceInternal.class);
+        LocalServices.removeServiceForTest(PackageManagerInternal.class);
+        LocalServices.removeServiceForTest(NotificationManagerInternal.class);
     }
 
     @Test
@@ -167,25 +184,29 @@
     @Test
     public void testRegisterConversationListener() throws Exception {
         assertEquals(0,
-                mPeopleService.mConversationListenerHelper.mListeners.getRegisteredCallbackCount());
+                mPeopleService.getConversationListenerHelper()
+                        .mListeners.getRegisteredCallbackCount());
 
         mIPeopleManager.registerConversationListener(TEST_PACKAGE_NAME, 0, CONVERSATION_ID_1,
                 new TestableConversationListener());
         mTestableLooper.processAllMessages();
         assertEquals(1,
-                mPeopleService.mConversationListenerHelper.mListeners.getRegisteredCallbackCount());
+                mPeopleService.getConversationListenerHelper()
+                        .mListeners.getRegisteredCallbackCount());
 
         mIPeopleManager.registerConversationListener(TEST_PACKAGE_NAME, 0, CONVERSATION_ID_1,
                 new TestableConversationListener());
         mTestableLooper.processAllMessages();
         assertEquals(2,
-                mPeopleService.mConversationListenerHelper.mListeners.getRegisteredCallbackCount());
+                mPeopleService.getConversationListenerHelper()
+                        .mListeners.getRegisteredCallbackCount());
 
         mIPeopleManager.registerConversationListener(TEST_PACKAGE_NAME, 0, CONVERSATION_ID_2,
                 new TestableConversationListener());
         mTestableLooper.processAllMessages();
         assertEquals(3,
-                mPeopleService.mConversationListenerHelper.mListeners.getRegisteredCallbackCount());
+                mPeopleService.getConversationListenerHelper()
+                        .mListeners.getRegisteredCallbackCount());
     }
 
     @Test
@@ -201,20 +222,24 @@
                 listener3);
         mTestableLooper.processAllMessages();
         assertEquals(3,
-                mPeopleService.mConversationListenerHelper.mListeners.getRegisteredCallbackCount());
+                mPeopleService.getConversationListenerHelper()
+                        .mListeners.getRegisteredCallbackCount());
 
         mIPeopleManager.unregisterConversationListener(
                 listener2);
         assertEquals(2,
-                mPeopleService.mConversationListenerHelper.mListeners.getRegisteredCallbackCount());
+                mPeopleService.getConversationListenerHelper()
+                        .mListeners.getRegisteredCallbackCount());
         mIPeopleManager.unregisterConversationListener(
                 listener1);
         assertEquals(1,
-                mPeopleService.mConversationListenerHelper.mListeners.getRegisteredCallbackCount());
+                mPeopleService.getConversationListenerHelper()
+                        .mListeners.getRegisteredCallbackCount());
         mIPeopleManager.unregisterConversationListener(
                 listener3);
         assertEquals(0,
-                mPeopleService.mConversationListenerHelper.mListeners.getRegisteredCallbackCount());
+                mPeopleService.getConversationListenerHelper()
+                        .mListeners.getRegisteredCallbackCount());
     }
 
     @Test
@@ -229,12 +254,13 @@
                 PeopleManager.ConversationListener.class);
         registerListener(CONVERSATION_ID_2, listenerForConversation2);
         assertEquals(3,
-                mPeopleService.mConversationListenerHelper.mListeners.getRegisteredCallbackCount());
+                mPeopleService.getConversationListenerHelper()
+                        .mListeners.getRegisteredCallbackCount());
 
         // Update conversation with two listeners.
         ConversationStatus status = new ConversationStatus.Builder(CONVERSATION_ID_1,
                 ACTIVITY_GAME).build();
-        mPeopleService.mConversationListenerHelper.onConversationsUpdate(
+        mPeopleService.getConversationListenerHelper().onConversationsUpdate(
                 Arrays.asList(getConversation(CONVERSATION_ID_1, status)));
         mTestLooper.dispatchAll();
 
diff --git a/services/tests/servicestests/src/com/android/server/pm/BackgroundInstallControlServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/BackgroundInstallControlServiceTest.java
index ba91647..daf18ed 100644
--- a/services/tests/servicestests/src/com/android/server/pm/BackgroundInstallControlServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/BackgroundInstallControlServiceTest.java
@@ -402,7 +402,7 @@
         assertEquals(0,
                 mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
         doReturn(PackageManager.PERMISSION_DENIED).when(mPermissionManager).checkPermission(
-                anyString(), anyString(), anyInt());
+                anyString(), anyString(), anyInt(), anyInt());
         generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED,
                 USER_ID_1, INSTALLER_NAME_1, 0);
         mTestLooper.dispatchAll();
@@ -415,7 +415,7 @@
         assertEquals(0,
                 mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
         doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission(
-                anyString(), anyString(), anyInt());
+                anyString(), anyString(), anyInt(), anyInt());
         generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED,
                 USER_ID_1, INSTALLER_NAME_1, 0);
         mTestLooper.dispatchAll();
@@ -428,7 +428,7 @@
         assertEquals(0,
                 mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
         doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission(
-                anyString(), anyString(), anyInt());
+                anyString(), anyString(), anyInt(), anyInt());
         generateUsageEvent(UsageEvents.Event.USER_INTERACTION,
                 USER_ID_1, INSTALLER_NAME_1, 0);
         mTestLooper.dispatchAll();
@@ -441,7 +441,7 @@
         assertEquals(0,
                 mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
         doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission(
-                anyString(), anyString(), anyInt());
+                anyString(), anyString(), anyInt(), anyInt());
         generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED,
                 USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_1);
         mTestLooper.dispatchAll();
@@ -464,7 +464,7 @@
         assertEquals(0,
                 mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
         doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission(
-                anyString(), anyString(), anyInt());
+                anyString(), anyString(), anyInt(), anyInt());
         generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED,
                 USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_1);
         generateUsageEvent(Event.ACTIVITY_STOPPED,
@@ -489,7 +489,7 @@
         assertEquals(0,
                 mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
         doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission(
-                anyString(), anyString(), anyInt());
+                anyString(), anyString(), anyInt(), anyInt());
         generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED,
                 USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_1);
         generateUsageEvent(Event.ACTIVITY_STOPPED,
@@ -520,7 +520,7 @@
         assertEquals(0,
                 mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
         doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission(
-                anyString(), anyString(), anyInt());
+                anyString(), anyString(), anyInt(), anyInt());
         generateUsageEvent(Event.ACTIVITY_STOPPED,
                 USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_1);
         mTestLooper.dispatchAll();
@@ -605,7 +605,7 @@
         // So it's not a background install. Thus, it's null for the return of
         // mBackgroundInstallControlService.getBackgroundInstalledPackages()
         doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission(
-                anyString(), anyString(), anyInt());
+                anyString(), anyString(), anyInt(), anyInt());
         generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED,
                 USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_1);
         generateUsageEvent(Event.ACTIVITY_STOPPED,
@@ -651,7 +651,7 @@
         // it's a background install. Thus, it's not null for the return of
         // mBackgroundInstallControlService.getBackgroundInstalledPackages()
         doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission(
-                anyString(), anyString(), anyInt());
+                anyString(), anyString(), anyInt(), anyInt());
         generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED,
                 USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_2);
         generateUsageEvent(Event.ACTIVITY_STOPPED,
@@ -701,7 +701,7 @@
         // it's a background install. Thus, it's not null for the return of
         // mBackgroundInstallControlService.getBackgroundInstalledPackages()
         doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission(
-                anyString(), anyString(), anyInt());
+                anyString(), anyString(), anyInt(), anyInt());
         generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED,
                 USER_ID_2, INSTALLER_NAME_2, USAGE_EVENT_TIMESTAMP_2);
         generateUsageEvent(Event.ACTIVITY_STOPPED,
@@ -752,7 +752,7 @@
         // as a background install. Since we do not want to treat side-loaded apps as background
         // install getBackgroundInstalledPackages() is expected to return null
         doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission(
-                anyString(), anyString(), anyInt());
+                anyString(), anyString(), anyInt(), anyInt());
         generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED,
                 USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_2);
         generateUsageEvent(Event.ACTIVITY_STOPPED,
@@ -801,7 +801,7 @@
         // as a background install. Since we do not want to treat side-loaded apps as background
         // install getBackgroundInstalledPackages() is expected to return null
         doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission(
-                anyString(), anyString(), anyInt());
+                anyString(), anyString(), anyInt(), anyInt());
         generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED,
                 USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_2);
         generateUsageEvent(Event.ACTIVITY_STOPPED,
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 77bdf19..3a3ab84 100644
--- a/services/tests/servicestests/src/com/android/server/pm/SELinuxMMACTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/SELinuxMMACTest.java
@@ -18,6 +18,8 @@
 
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsNot.not;
+import static org.hamcrest.core.StringContains.containsString;
 import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.when;
@@ -53,7 +55,7 @@
 
     @Test
     public void getSeInfoOptInToLatest() {
-        var packageState = makePackageState(Build.VERSION_CODES.P);
+        var packageState = new PackageStateBuilder(Build.VERSION_CODES.P).build();
         when(mMockCompatibility.isChangeEnabledInternal(eq(SELinuxMMAC.SELINUX_LATEST_CHANGES),
                 argThat(argument -> argument.packageName.equals(packageState.getPackageName()))))
                 .thenReturn(true);
@@ -64,7 +66,7 @@
 
     @Test
     public void getSeInfoOptInToR() {
-        var packageState = makePackageState(Build.VERSION_CODES.P);
+        var packageState = new PackageStateBuilder(Build.VERSION_CODES.P).build();
         when(mMockCompatibility.isChangeEnabledInternal(eq(SELinuxMMAC.SELINUX_R_CHANGES),
                 argThat(argument -> argument.packageName.equals(packageState.getPackageName()))))
                 .thenReturn(true);
@@ -75,7 +77,7 @@
 
     @Test
     public void getSeInfoNoOptIn() {
-        var packageState = makePackageState(Build.VERSION_CODES.P);
+        var packageState = new PackageStateBuilder(Build.VERSION_CODES.P).build();
         when(mMockCompatibility.isChangeEnabledInternal(eq(SELinuxMMAC.SELINUX_LATEST_CHANGES),
                 argThat(argument -> argument.packageName.equals(packageState.getPackageName()))))
                 .thenReturn(false);
@@ -86,7 +88,7 @@
 
     @Test
     public void getSeInfoNoOptInButAlreadyLatest() {
-        var packageState = makePackageState(LATEST_OPT_IN_VERSION);
+        var packageState = new PackageStateBuilder(LATEST_OPT_IN_VERSION).build();
         when(mMockCompatibility.isChangeEnabledInternal(eq(SELinuxMMAC.SELINUX_LATEST_CHANGES),
                 argThat(argument -> argument.packageName.equals(packageState.getPackageName()))))
                 .thenReturn(false);
@@ -97,7 +99,7 @@
 
     @Test
     public void getSeInfoTargetingCurDevelopment() {
-        var packageState = makePackageState(Build.VERSION_CODES.CUR_DEVELOPMENT);
+        var packageState = new PackageStateBuilder(Build.VERSION_CODES.CUR_DEVELOPMENT).build();
         when(mMockCompatibility.isChangeEnabledInternal(eq(SELinuxMMAC.SELINUX_LATEST_CHANGES),
                 argThat(argument -> argument.packageName.equals(packageState.getPackageName()))))
                 .thenReturn(true);
@@ -108,7 +110,7 @@
 
     @Test
     public void getSeInfoNoOptInButAlreadyR() {
-        var packageState = makePackageState(R_OPT_IN_VERSION);
+        var packageState = new PackageStateBuilder(R_OPT_IN_VERSION).build();
         when(mMockCompatibility.isChangeEnabledInternal(eq(SELinuxMMAC.SELINUX_R_CHANGES),
                 argThat(argument -> argument.packageName.equals(packageState.getPackageName()))))
                 .thenReturn(false);
@@ -119,7 +121,7 @@
 
     @Test
     public void getSeInfoOptInRButLater() {
-        var packageState = makePackageState(R_OPT_IN_VERSION + 1);
+        var packageState = new PackageStateBuilder(R_OPT_IN_VERSION + 1).build();
         when(mMockCompatibility.isChangeEnabledInternal(eq(SELinuxMMAC.SELINUX_R_CHANGES),
                 argThat(argument -> argument.packageName.equals(packageState.getPackageName()))))
                 .thenReturn(true);
@@ -128,15 +130,114 @@
                 is("default:targetSdkVersion=" + (R_OPT_IN_VERSION + 1)));
     }
 
-    private PackageState makePackageState(int targetSdkVersion) {
-        var packageState = Mockito.mock(PackageState.class);
-        when(packageState.getPackageName()).thenReturn(PACKAGE_NAME);
-        when(packageState.getAndroidPackage()).thenReturn(
-                ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME)
-                        .setTargetSdkVersion(targetSdkVersion)
-                        .hideAsParsed())
-                        .hideAsFinal()
-        );
-        return packageState;
+    @Test
+    public void getSeInfoPreinstalledToSystem() {
+        var packageState = new PackageStateBuilder(Build.VERSION_CODES.CUR_DEVELOPMENT)
+                .setSystem(true).build();
+        when(mMockCompatibility.isChangeEnabledInternal(eq(SELinuxMMAC.SELINUX_LATEST_CHANGES),
+                argThat(argument -> argument.packageName.equals(packageState.getPackageName()))))
+                .thenReturn(true);
+        assertThat(SELinuxMMAC.getSeInfo(packageState, packageState.getAndroidPackage(), null,
+                        mMockCompatibility),
+                containsString(":partition=system"));
+    }
+
+
+    @Test
+    public void getSeInfoPreinstalledToSystemExt() {
+        var packageState = new PackageStateBuilder(Build.VERSION_CODES.CUR_DEVELOPMENT)
+                .setSystem(true).setSystemExt(true).build();
+        when(mMockCompatibility.isChangeEnabledInternal(eq(SELinuxMMAC.SELINUX_LATEST_CHANGES),
+                argThat(argument -> argument.packageName.equals(packageState.getPackageName()))))
+                .thenReturn(true);
+        assertThat(SELinuxMMAC.getSeInfo(packageState, packageState.getAndroidPackage(), null,
+                        mMockCompatibility),
+                containsString(":partition=system_ext"));
+    }
+
+
+    @Test
+    public void getSeInfoPreinstalledToProduct() {
+        var packageState = new PackageStateBuilder(Build.VERSION_CODES.CUR_DEVELOPMENT)
+                .setSystem(true).setProduct(true).build();
+        when(mMockCompatibility.isChangeEnabledInternal(eq(SELinuxMMAC.SELINUX_LATEST_CHANGES),
+                argThat(argument -> argument.packageName.equals(packageState.getPackageName()))))
+                .thenReturn(true);
+        assertThat(SELinuxMMAC.getSeInfo(packageState, packageState.getAndroidPackage(), null,
+                        mMockCompatibility),
+                containsString(":partition=product"));
+    }
+
+
+    @Test
+    public void getSeInfoPreinstalledToVendor() {
+        var packageState = new PackageStateBuilder(Build.VERSION_CODES.CUR_DEVELOPMENT)
+                .setSystem(true).setVendor(true).build();
+        when(mMockCompatibility.isChangeEnabledInternal(eq(SELinuxMMAC.SELINUX_LATEST_CHANGES),
+                argThat(argument -> argument.packageName.equals(packageState.getPackageName()))))
+                .thenReturn(true);
+        assertThat(SELinuxMMAC.getSeInfo(packageState, packageState.getAndroidPackage(), null,
+                        mMockCompatibility),
+                containsString(":partition=vendor"));
+    }
+
+
+    @Test
+    public void getSeInfoNotPreinstalled() {
+        var packageState = new PackageStateBuilder(Build.VERSION_CODES.CUR_DEVELOPMENT).build();
+        when(mMockCompatibility.isChangeEnabledInternal(eq(SELinuxMMAC.SELINUX_LATEST_CHANGES),
+                argThat(argument -> argument.packageName.equals(packageState.getPackageName()))))
+                .thenReturn(true);
+        assertThat(SELinuxMMAC.getSeInfo(packageState, packageState.getAndroidPackage(), null,
+                        mMockCompatibility),
+                not(containsString(":partition=")));
+    }
+
+    private static class PackageStateBuilder {
+        private final int mTargetSdkVersion;
+        private boolean mIsSystem = false;
+        private boolean mIsSystemExt = false;
+        private boolean mIsProduct = false;
+        private boolean mIsVendor = false;
+
+        PackageStateBuilder(int targetSdkVersion) {
+            mTargetSdkVersion = targetSdkVersion;
+        }
+
+        PackageStateBuilder setSystem(boolean isSystem) {
+            mIsSystem = isSystem;
+            return this;
+        }
+
+        PackageStateBuilder setSystemExt(boolean isSystemExt) {
+            mIsSystemExt = isSystemExt;
+            return this;
+        }
+
+        PackageStateBuilder setProduct(boolean isProduct) {
+            mIsProduct = isProduct;
+            return this;
+        }
+
+        PackageStateBuilder setVendor(boolean isVendor) {
+            mIsVendor = isVendor;
+            return this;
+        }
+
+        PackageState build() {
+            var packageState = Mockito.mock(PackageState.class);
+            when(packageState.getPackageName()).thenReturn(PACKAGE_NAME);
+            when(packageState.getAndroidPackage()).thenReturn(
+                    ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME)
+                            .setTargetSdkVersion(mTargetSdkVersion)
+                            .hideAsParsed())
+                            .hideAsFinal()
+            );
+            when(packageState.isSystem()).thenReturn(mIsSystem);
+            when(packageState.isSystemExt()).thenReturn(mIsSystemExt);
+            when(packageState.isProduct()).thenReturn(mIsProduct);
+            when(packageState.isVendor()).thenReturn(mIsVendor);
+            return packageState;
+        }
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
index 7c1845f..0f5fb91 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
@@ -125,6 +125,8 @@
 import java.io.OutputStream;
 import java.io.Writer;
 import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.StandardCopyOption;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Locale;
@@ -4014,8 +4016,10 @@
 
         ShortcutUser user = new ShortcutUser(mService, 0);
 
-        File corruptedShortcutPackage = new File("/data/local/tmp/cts/content/",
+        File corruptedShortcutPackage = new File(getTestContext().getFilesDir(),
                 "broken_shortcut.xml");
+        Files.copy(new File("/data/local/tmp/cts/content/", "broken_shortcut.xml").toPath(),
+                corruptedShortcutPackage.toPath(), StandardCopyOption.REPLACE_EXISTING);
         assertNull(ShortcutPackage.loadFromFile(mService, user, corruptedShortcutPackage, false));
     }
 
@@ -4116,6 +4120,11 @@
         try (Writer os = new FileWriter(mService.getUserFile(USER_10).getBaseFile())) {
             os.write("<?xml version='1.0' encoding='utf");
         }
+        ShortcutPackage sp = mService.getUserShortcutsLocked(USER_0).getPackageShortcutsIfExists(
+                CALLING_PACKAGE_1);
+        try (Writer os = new FileWriter(sp.getShortcutPackageItemFile().getPath())) {
+            os.write("<?xml version='1.0' encoding='utf");
+        }
 
         // Restore.
         initService();
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserLifecycleStressTest.java b/services/tests/servicestests/src/com/android/server/pm/UserLifecycleStressTest.java
index c9f00d7..43bf537 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserLifecycleStressTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserLifecycleStressTest.java
@@ -16,16 +16,19 @@
 package com.android.server.pm;
 
 import static android.os.UserHandle.USER_NULL;
-import static android.os.UserHandle.USER_SYSTEM;
 
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
 
+import static org.junit.Assume.assumeFalse;
+
 import android.app.ActivityManager;
 import android.app.IStopUserCallback;
 import android.content.Context;
+import android.content.Intent;
 import android.content.pm.UserInfo;
 import android.os.RemoteException;
+import android.os.UserHandle;
 import android.os.UserManager;
 import android.platform.test.annotations.Postsubmit;
 import android.provider.Settings;
@@ -35,6 +38,8 @@
 import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.compatibility.common.util.BlockingBroadcastReceiver;
+import com.android.compatibility.common.util.ShellUtils;
 import com.android.internal.util.FunctionalUtils;
 
 import org.junit.After;
@@ -46,6 +51,7 @@
 import java.util.List;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
 
 /**
  * To run the test:
@@ -58,14 +64,20 @@
     private static final String TAG = "UserLifecycleStressTest";
     // TODO: Make this smaller once we have improved it.
     private static final int TIMEOUT_IN_SECOND = 40;
+    private static final int CHECK_USER_REMOVED_INTERVAL_MS = 500;
+
     private static final int NUM_ITERATIONS = 8;
     private static final int WAIT_BEFORE_STOP_USER_IN_SECOND = 3;
 
+    /** Name of users/profiles in the test. Users with this name may be freely removed. */
+    private static final String TEST_USER_NAME = "UserLifecycleStressTest_test_user";
+
     private Context mContext;
     private UserManager mUserManager;
     private ActivityManager mActivityManager;
     private UserSwitchWaiter mUserSwitchWaiter;
     private String mRemoveGuestOnExitOriginalValue;
+    private int mOriginalCurrentUserId;
 
     @Before
     public void setup() throws RemoteException {
@@ -75,13 +87,17 @@
         mUserSwitchWaiter = new UserSwitchWaiter(TAG, TIMEOUT_IN_SECOND);
         mRemoveGuestOnExitOriginalValue = Settings.Global.getString(mContext.getContentResolver(),
                 Settings.Global.REMOVE_GUEST_ON_EXIT);
+        waitForBroadcastBarrier(); // isolate tests from each other
+        mOriginalCurrentUserId = ActivityManager.getCurrentUser();
     }
 
     @After
     public void tearDown() throws IOException {
+        switchUser(mOriginalCurrentUserId);
         mUserSwitchWaiter.close();
         Settings.Global.putString(mContext.getContentResolver(),
                 Settings.Global.REMOVE_GUEST_ON_EXIT, mRemoveGuestOnExitOriginalValue);
+        waitForBroadcastBarrier(); // isolate tests from each other
     }
 
     /**
@@ -90,9 +106,15 @@
      */
     @Test
     public void stopManagedProfileStressTest() throws RemoteException, InterruptedException {
+        UserHandle mainUser = mUserManager.getMainUser();
+        assumeFalse("There is no main user", mainUser == null);
+        switchUser(mainUser.getIdentifier());
+
         for (int i = 0; i < NUM_ITERATIONS; i++) {
-            final UserInfo userInfo = mUserManager.createProfileForUser("TestUser",
-                    UserManager.USER_TYPE_PROFILE_MANAGED, 0, mActivityManager.getCurrentUser());
+            logIteration(i, "stopManagedProfileStressTest");
+
+            final UserInfo userInfo = mUserManager.createProfileForUser(TEST_USER_NAME,
+                    UserManager.USER_TYPE_PROFILE_MANAGED, 0, ActivityManager.getCurrentUser());
             assertThat(userInfo).isNotNull();
             try {
                 assertWithMessage("Failed to start the profile")
@@ -109,6 +131,35 @@
     }
 
     /**
+     * Create a user, and then remove it immediately after starting it in background
+     * {@link #NUM_ITERATIONS} times in a row.
+     * Check device is not crashed when user data directory is deleted while some other processes
+     * might still be trying to access those deleted files.
+     */
+    @Test
+    public void removeRecentlyStartedUserStressTest() throws RemoteException, InterruptedException {
+        for (int i = 0; i < NUM_ITERATIONS; i++) {
+            logIteration(i, "removeRecentlyStartedUserStressTest");
+
+            Log.d(TAG, "Creating a new user");
+            final UserInfo userInfo = mUserManager.createUser(TEST_USER_NAME,
+                    UserManager.USER_TYPE_FULL_SECONDARY, 0);
+            assertWithMessage("Failed to create the user")
+                    .that(userInfo)
+                    .isNotNull();
+            try {
+                Log.d(TAG, "Starting user " + userInfo.id);
+                startUserInBackgroundAndWaitForUserStartedBroadcast(userInfo.id);
+            } finally {
+                Log.d(TAG, "Removing user " + userInfo.id);
+                assertWithMessage("Failed to remove the user " + userInfo.id)
+                        .that(removeUser(userInfo.id))
+                        .isTrue();
+            }
+        }
+    }
+
+    /**
      * Starts over the guest user {@link #NUM_ITERATIONS} times in a row.
      *
      * Starting over the guest means the following:
@@ -122,18 +173,13 @@
         Settings.Global.putString(mContext.getContentResolver(),
                 Settings.Global.REMOVE_GUEST_ON_EXIT, "0");
 
-        if (ActivityManager.getCurrentUser() != USER_SYSTEM) {
-            switchUser(USER_SYSTEM);
-        }
-
         final List<UserInfo> guestUsers = mUserManager.getGuestUsers();
         int nextGuestId = guestUsers.isEmpty() ? USER_NULL : guestUsers.get(0).id;
 
         for (int i = 0; i < NUM_ITERATIONS; i++) {
-            final int currentGuestId = nextGuestId;
+            logIteration(i, "switchToExistingGuestAndStartOverStressTest");
 
-            Log.d(TAG, "switchToExistingGuestAndStartOverStressTest"
-                    + " - Run " + (i + 1) + " / " + NUM_ITERATIONS);
+            final int currentGuestId = nextGuestId;
 
             if (currentGuestId != USER_NULL) {
                 Log.d(TAG, "Switching to the existing guest");
@@ -161,8 +207,8 @@
                         .isTrue();
             }
 
-            Log.d(TAG, "Switching back to the system user");
-            switchUser(USER_SYSTEM);
+            Log.d(TAG, "Switching back to the initial user");
+            switchUser(mOriginalCurrentUserId);
 
             nextGuestId = newGuest.id;
         }
@@ -173,6 +219,25 @@
         Log.d(TAG, "testSwitchToExistingGuestAndStartOver - End");
     }
 
+    private boolean removeUser(int userId) {
+        if (!mUserManager.removeUser(userId)) {
+            return false;
+        }
+        try {
+            final long startTime = System.currentTimeMillis();
+            final long timeoutInMs = TIMEOUT_IN_SECOND * 1000;
+            while (mUserManager.getUserInfo(userId) != null
+                    && System.currentTimeMillis() - startTime < timeoutInMs) {
+                TimeUnit.MILLISECONDS.sleep(CHECK_USER_REMOVED_INTERVAL_MS);
+            }
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+        } catch (Exception e) {
+            // Ignore
+        }
+        return mUserManager.getUserInfo(userId) == null;
+    }
+
     /** Stops the given user and waits for the stop to finish. */
     private void stopUser(int userId) throws RemoteException, InterruptedException {
         runWithLatch("stop user", countDownLatch -> {
@@ -193,6 +258,10 @@
 
     /** Starts the given user in the foreground and waits for the switch to finish. */
     private void switchUser(int userId) {
+        if (ActivityManager.getCurrentUser() == userId) {
+            Log.d(TAG, "No need to switch, current user is already user " + userId);
+            return;
+        }
         Log.d(TAG, "Switching to user " + userId);
 
         mUserSwitchWaiter.runThenWaitUntilSwitchCompleted(userId, () -> {
@@ -204,6 +273,40 @@
     }
 
     /**
+     * Start user in background and wait for {@link Intent#ACTION_USER_STARTED} broadcast.
+     * <p> To start in foreground instead, see {@link #switchUser(int)}.
+     * <p> This should always be used for profiles since profiles cannot be started in foreground.
+     */
+    private void startUserInBackgroundAndWaitForUserStartedBroadcast(int userId) {
+        runWithBlockingBroadcastReceiver("start user and wait for ACTION_USER_STARTED broadcast",
+                userId, Intent.ACTION_USER_STARTED,
+                () -> ActivityManager.getService().startUserInBackground(userId));
+    }
+
+    /**
+     * Calls the given runnable and expects the given broadcast to be received before timeout,
+     * or fails the test otherwise.
+     * @param tag tag for logging
+     * @param userId id of the user to register the broadcast receiver with
+     *               see {@link Context#registerReceiverAsUser}
+     * @param action action of the broadcast intent filter i.e. {@link Intent#ACTION_USER_STARTED}
+     * @param runnable this will be called after registering the broadcast receiver
+     */
+    private void runWithBlockingBroadcastReceiver(String tag, int userId, String action,
+            FunctionalUtils.ThrowingRunnable runnable) {
+        try (BlockingBroadcastReceiver blockingBroadcastReceiver = new BlockingBroadcastReceiver(
+                mContext, action,
+                intent -> intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_NULL) == userId)) {
+            blockingBroadcastReceiver.setTimeout(TIMEOUT_IN_SECOND);
+            blockingBroadcastReceiver.registerForAllUsers();
+            runnable.run();
+            assertWithMessage("Took more than " + TIMEOUT_IN_SECOND + "s to " + tag)
+                    .that(blockingBroadcastReceiver.awaitForBroadcast())
+                    .isNotNull();
+        }
+    }
+
+    /**
      * Calls the given consumer with a CountDownLatch parameter, and expects it's countDown() method
      * to be called before timeout, or fails the test otherwise.
      */
@@ -222,5 +325,20 @@
         final long elapsedTime = System.currentTimeMillis() - startTime;
         Log.d(TAG, tag + " takes " + elapsedTime + " ms");
     }
+
+    private void logIteration(int iteration, String testMethodName) {
+        Log.d(TAG, testMethodName + " - Iteration " + (iteration + 1) + " / " + NUM_ITERATIONS);
+    }
+
+    private static void waitForBroadcastBarrier() {
+        try {
+            Log.d(TAG, "Starting to waitForBroadcastBarrier");
+            ShellUtils.runShellCommandWithTimeout("am wait-for-broadcast-barrier",
+                    TIMEOUT_IN_SECOND);
+            Log.d(TAG, "waitForBroadcastBarrier is finished");
+        } catch (TimeoutException e) {
+            Log.e(TAG, "Timeout while running waitForBroadcastBarrier", e);
+        }
+    }
 }
 
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceCreateProfileTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceCreateProfileTest.java
index fdf94be..39cc653 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceCreateProfileTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceCreateProfileTest.java
@@ -182,7 +182,7 @@
         UserInfo secondaryUser = addUser();
         UserInfo profile = addProfile(secondaryUser);
         // Add the profile it to the users being removed.
-        mUserManagerService.addRemovingUserIdLocked(profile.id);
+        mUserManagerService.addRemovingUserId(profile.id);
         // We should reuse the badge from the profile being removed.
         assertEquals("Badge index not reused while removing a user", 0,
                 mUserManagerService.getFreeProfileBadgeLU(secondaryUser.id,
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceIdRecyclingTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceIdRecyclingTest.java
index 1f4c9f8..b6fd65e 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceIdRecyclingTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceIdRecyclingTest.java
@@ -111,7 +111,7 @@
 
     private void removeUser(int userId) {
         mUserManagerService.removeUserInfo(userId);
-        mUserManagerService.addRemovingUserIdLocked(userId);
+        mUserManagerService.addRemovingUserId(userId);
     }
 
     private void assertNoNextIdAvailable(String message) {
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java
index 4af0323..b4281d63c 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java
@@ -31,11 +31,11 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.platform.test.annotations.Postsubmit;
-import android.support.test.uiautomator.UiDevice;
 import android.util.AtomicFile;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
+import androidx.test.uiautomator.UiDevice;
 
 import com.android.server.LocalServices;
 
@@ -70,7 +70,9 @@
 
         LocalServices.removeServiceForTest(UserManagerInternal.class);
         mUserManagerService = new UserManagerService(InstrumentationRegistry.getContext());
-
+        // Put the current user to mUsers. UMS can't find userlist.xml, and fallbackToSingleUserLP.
+        mUserManagerService.putUserInfo(
+                new UserInfo(ActivityManager.getCurrentUser(), "Current User", 0));
         restrictionsFile = new File(mContext.getCacheDir(), "restrictions.xml");
         restrictionsFile.delete();
     }
@@ -193,127 +195,8 @@
                 .isFalse();
     }
 
-    @Test
-    public void assertIsUserSwitcherEnabledOnMultiUserSettings() throws Exception {
-        int userId = ActivityManager.getCurrentUser();
-        resetUserSwitcherEnabled();
-
-        setUserSwitch(false);
-        assertThat(mUserManagerService.isUserSwitcherEnabled(userId)).isFalse();
-
-        setUserSwitch(true);
-        assertThat(mUserManagerService.isUserSwitcherEnabled(userId)).isTrue();
-    }
-
-    @Test
-    public void assertIsUserSwitcherEnabledOnMaxSupportedUsers()  throws Exception {
-        int userId = ActivityManager.getCurrentUser();
-        setMaxSupportedUsers(1);
-
-        assertThat(UserManager.supportsMultipleUsers()).isFalse();
-        assertThat(mUserManagerService.isUserSwitcherEnabled(userId)).isFalse();
-
-        setMaxSupportedUsers(8);
-
-        assertThat(UserManager.supportsMultipleUsers()).isTrue();
-        assertThat(mUserManagerService.isUserSwitcherEnabled(userId)).isTrue();
-    }
-
-    @Test
-    public void assertIsUserSwitcherEnabled()  throws Exception {
-        int userId = ActivityManager.getCurrentUser();
-        setMaxSupportedUsers(8);
-        assertThat(mUserManagerService.isUserSwitcherEnabled(true, userId)).isTrue();
-
-        setUserSwitch(false);
-        assertThat(mUserManagerService.isUserSwitcherEnabled(true, userId)).isFalse();
-
-        setUserSwitch(true);
-        assertThat(mUserManagerService.isUserSwitcherEnabled(false, userId)).isTrue();
-
-        mUserManagerService.setUserRestriction(UserManager.DISALLOW_ADD_USER, true, userId);
-        assertThat(mUserManagerService.isUserSwitcherEnabled(false, userId)).isFalse();
-
-        mUserManagerService.setUserRestriction(UserManager.DISALLOW_ADD_USER, false, userId);
-        setMaxSupportedUsers(1);
-        assertThat(mUserManagerService.isUserSwitcherEnabled(true, userId)).isFalse();
-    }
-
-    @Test
-    public void assertIsUserSwitcherEnabledOnShowMultiuserUI()  throws Exception {
-        int userId = ActivityManager.getCurrentUser();
-        setShowMultiuserUI(false);
-
-        assertThat(UserManager.supportsMultipleUsers()).isFalse();
-        assertThat(mUserManagerService.isUserSwitcherEnabled(userId)).isFalse();
-
-        setShowMultiuserUI(true);
-
-        assertThat(UserManager.supportsMultipleUsers()).isTrue();
-        assertThat(mUserManagerService.isUserSwitcherEnabled(userId)).isTrue();
-    }
-
-    @Test
-    public void assertIsUserSwitcherEnabledOnUserRestrictions() throws Exception {
-        int userId = ActivityManager.getCurrentUser();
-        resetUserSwitcherEnabled();
-
-        mUserManagerService.setUserRestriction(DISALLOW_USER_SWITCH, true, userId);
-        assertThat(mUserManagerService.isUserSwitcherEnabled(userId)).isFalse();
-
-        mUserManagerService.setUserRestriction(DISALLOW_USER_SWITCH, false, userId);
-        assertThat(mUserManagerService.isUserSwitcherEnabled(userId)).isTrue();
-    }
-
-    @Test
-    public void assertIsUserSwitcherEnabledOnDemoMode()  throws Exception {
-        int userId = ActivityManager.getCurrentUser();
-        resetUserSwitcherEnabled();
-
-        setDeviceDemoMode(true);
-        assertThat(mUserManagerService.isUserSwitcherEnabled(userId)).isFalse();
-
-        setDeviceDemoMode(false);
-        assertThat(mUserManagerService.isUserSwitcherEnabled(userId)).isTrue();
-    }
-
-    private void resetUserSwitcherEnabled() throws Exception {
-        int userId = ActivityManager.getCurrentUser();
-        setUserSwitch(true);
-        setShowMultiuserUI(true);
-        setDeviceDemoMode(false);
-        setMaxSupportedUsers(8);
-        mUserManagerService.setUserRestriction(DISALLOW_USER_SWITCH, false, userId);
-    }
-
-    private void setUserSwitch(boolean enabled) {
-        android.provider.Settings.Global.putInt(mContext.getContentResolver(),
-                android.provider.Settings.Global.USER_SWITCHER_ENABLED, enabled ? 1 : 0);
-    }
-
-    private void setDeviceDemoMode(boolean enabled) {
-        android.provider.Settings.Global.putInt(mContext.getContentResolver(),
-                android.provider.Settings.Global.DEVICE_DEMO_MODE, enabled ? 1 : 0);
-    }
-
-
     private static String runShellCommand(String cmd) throws Exception {
         return UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
                 .executeShellCommand(cmd);
     }
-
-    private static String setSystemProperty(String name, String value) throws Exception {
-        final String oldValue = runShellCommand("getprop " + name);
-        assertThat(runShellCommand("setprop " + name + " " + value))
-                .isEqualTo("");
-        return oldValue;
-    }
-
-    private static void setMaxSupportedUsers(int max) throws Exception {
-        setSystemProperty("fw.max_users", String.valueOf(max));
-    }
-
-    public static void setShowMultiuserUI(boolean show) throws Exception {
-        setSystemProperty("fw.show_multiuserui", String.valueOf(show));
-    }
 }
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 2675f05..a54bc91 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java
@@ -67,6 +67,7 @@
                 .setMediaSharedWithParent(false)
                 .setCredentialShareableWithParent(true)
                 .setDeleteAppWithParent(false)
+                .setAlwaysVisible(false)
                 .build();
         final UserProperties actualProps = new UserProperties(defaultProps);
         actualProps.setShowInLauncher(14);
@@ -78,6 +79,7 @@
         actualProps.setMediaSharedWithParent(true);
         actualProps.setCredentialShareableWithParent(false);
         actualProps.setDeleteAppWithParent(true);
+        actualProps.setAlwaysVisible(true);
 
         // Write the properties to xml.
         final ByteArrayOutputStream baos = new ByteArrayOutputStream();
@@ -119,6 +121,7 @@
                 .setInheritDevicePolicy(1732)
                 .setMediaSharedWithParent(true)
                 .setDeleteAppWithParent(true)
+                .setAlwaysVisible(true)
                 .build();
         final UserProperties orig = new UserProperties(defaultProps);
         orig.setShowInLauncher(2841);
@@ -126,6 +129,7 @@
         orig.setShowInSettings(1437);
         orig.setInheritDevicePolicy(9456);
         orig.setDeleteAppWithParent(false);
+        orig.setAlwaysVisible(false);
 
         // Test every permission level. (Currently, it's linear so it's easy.)
         for (int permLevel = 0; permLevel < 4; permLevel++) {
@@ -169,6 +173,7 @@
                 copy::getCrossProfileIntentResolutionStrategy, exposeAll);
         assertEqualGetterOrThrows(orig::getDeleteAppWithParent,
                 copy::getDeleteAppWithParent, exposeAll);
+        assertEqualGetterOrThrows(orig::getAlwaysVisible, copy::getAlwaysVisible, exposeAll);
 
         // Items requiring hasManagePermission - put them here using hasManagePermission.
         assertEqualGetterOrThrows(orig::getShowInSettings, copy::getShowInSettings,
@@ -234,5 +239,6 @@
         assertThat(expected.isCredentialShareableWithParent())
                 .isEqualTo(actual.isCredentialShareableWithParent());
         assertThat(expected.getDeleteAppWithParent()).isEqualTo(actual.getDeleteAppWithParent());
+        assertThat(expected.getAlwaysVisible()).isEqualTo(actual.getAlwaysVisible());
     }
 }
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 ff9a79e..e3579b4 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java
@@ -91,7 +91,8 @@
                 .setCredentialShareableWithParent(false)
                 .setShowInSettings(900)
                 .setInheritDevicePolicy(340)
-                .setDeleteAppWithParent(true);
+                .setDeleteAppWithParent(true)
+                .setAlwaysVisible(true);
 
         final UserTypeDetails type = new UserTypeDetails.Builder()
                 .setName("a.name")
@@ -106,6 +107,7 @@
                 .setBadgeNoBackground(30)
                 .setLabel(31)
                 .setMaxAllowedPerParent(32)
+                .setStatusBarIcon(33)
                 .setDefaultRestrictions(restrictions)
                 .setDefaultSystemSettings(systemSettings)
                 .setDefaultSecureSettings(secureSettings)
@@ -122,6 +124,7 @@
         assertEquals(30, type.getBadgeNoBackground());
         assertEquals(31, type.getLabel());
         assertEquals(32, type.getMaxAllowedPerParent());
+        assertEquals(33, type.getStatusBarIcon());
 
         assertTrue(UserRestrictionsUtils.areEqual(restrictions, type.getDefaultRestrictions()));
         assertNotSame(restrictions, type.getDefaultRestrictions());
@@ -160,6 +163,7 @@
         assertEquals(340, type.getDefaultUserPropertiesReference()
                 .getInheritDevicePolicy());
         assertTrue(type.getDefaultUserPropertiesReference().getDeleteAppWithParent());
+        assertTrue(type.getDefaultUserPropertiesReference().getAlwaysVisible());
 
         assertEquals(23, type.getBadgeLabel(0));
         assertEquals(24, type.getBadgeLabel(1));
@@ -191,6 +195,7 @@
         assertEquals(Resources.ID_NULL, type.getIconBadge());
         assertEquals(Resources.ID_NULL, type.getBadgePlain());
         assertEquals(Resources.ID_NULL, type.getBadgeNoBackground());
+        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());
@@ -210,6 +215,8 @@
                 props.getCrossProfileIntentResolutionStrategy());
         assertFalse(props.isMediaSharedWithParent());
         assertFalse(props.isCredentialShareableWithParent());
+        assertFalse(props.getDeleteAppWithParent());
+        assertFalse(props.getAlwaysVisible());
 
         assertFalse(type.hasBadge());
     }
@@ -298,7 +305,8 @@
                 .setCredentialShareableWithParent(true)
                 .setShowInSettings(20)
                 .setInheritDevicePolicy(21)
-                .setDeleteAppWithParent(true);
+                .setDeleteAppWithParent(true)
+                .setAlwaysVisible(false);
 
         final ArrayMap<String, UserTypeDetails.Builder> builders = new ArrayMap<>();
         builders.put(userTypeAosp1, new UserTypeDetails.Builder()
@@ -339,6 +347,7 @@
         assertEquals(21, aospType.getDefaultUserPropertiesReference()
                 .getInheritDevicePolicy());
         assertTrue(aospType.getDefaultUserPropertiesReference().getDeleteAppWithParent());
+        assertFalse(aospType.getDefaultUserPropertiesReference().getAlwaysVisible());
 
         // userTypeAosp2 should be modified.
         aospType = builders.get(userTypeAosp2).createUserTypeDetails();
@@ -348,6 +357,8 @@
         assertEquals(Resources.ID_NULL, aospType.getBadgePlain()); // No resId for 'garbage'
         assertEquals(com.android.internal.R.drawable.ic_corp_badge_no_background,
                 aospType.getBadgeNoBackground());
+        assertEquals(com.android.internal.R.drawable.ic_test_badge_experiment,
+                aospType.getStatusBarIcon());
         assertEquals(com.android.internal.R.string.managed_profile_label_badge,
                 aospType.getBadgeLabel(0));
         assertEquals(com.android.internal.R.string.managed_profile_label_badge_2,
@@ -381,8 +392,8 @@
         assertEquals(23, aospType.getDefaultUserPropertiesReference().getShowInSettings());
         assertEquals(450, aospType.getDefaultUserPropertiesReference()
                 .getInheritDevicePolicy());
-        assertFalse(aospType.getDefaultUserPropertiesReference()
-                .getDeleteAppWithParent());
+        assertFalse(aospType.getDefaultUserPropertiesReference().getDeleteAppWithParent());
+        assertTrue(aospType.getDefaultUserPropertiesReference().getAlwaysVisible());
 
         // userTypeOem1 should be created.
         UserTypeDetails.Builder customType = builders.get(userTypeOem1);
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 6bcda3f..dd681aa 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -53,6 +53,8 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
@@ -118,10 +120,17 @@
 
     private void removeExistingUsers() {
         int currentUser = ActivityManager.getCurrentUser();
+
+        UserHandle communalProfile = mUserManager.getCommunalProfile();
+        int communalProfileId = communalProfile != null
+                ? communalProfile.getIdentifier() : UserHandle.USER_NULL;
+
         List<UserInfo> list = mUserManager.getUsers();
         for (UserInfo user : list) {
             // Keep system and current user
-            if (user.id != UserHandle.USER_SYSTEM && user.id != currentUser) {
+            if (user.id != UserHandle.USER_SYSTEM &&
+                    user.id != currentUser &&
+                    user.id != communalProfileId) {
                 removeUser(user.id);
             }
         }
@@ -206,6 +215,7 @@
         assertThat(typeProps.isCredentialShareableWithParent())
                 .isEqualTo(cloneUserProperties.isCredentialShareableWithParent());
         assertThrows(SecurityException.class, cloneUserProperties::getDeleteAppWithParent);
+        assertThrows(SecurityException.class, cloneUserProperties::getAlwaysVisible);
 
         // Verify clone user parent
         assertThat(mUserManager.getProfileParent(mainUserId)).isNull();
@@ -216,6 +226,91 @@
         assertThat(mUserManager.getProfileParent(mainUserId)).isNull();
     }
 
+    @Test
+    public void testCommunalProfile() throws Exception {
+        assumeTrue("Device doesn't support communal profiles ",
+                mUserManager.isUserTypeEnabled(UserManager.USER_TYPE_PROFILE_COMMUNAL));
+
+        // Create communal profile if needed
+        if (mUserManager.getCommunalProfile() == null) {
+            Slog.i(TAG, "Attempting to create a communal profile for a test");
+            createUser("Communal", UserManager.USER_TYPE_PROFILE_COMMUNAL, /*flags*/ 0);
+        }
+        final UserHandle communal = mUserManager.getCommunalProfile();
+        assertWithMessage("Couldn't create communal profile").that(communal).isNotNull();
+
+        final UserTypeDetails userTypeDetails =
+                UserTypeFactory.getUserTypes().get(UserManager.USER_TYPE_PROFILE_COMMUNAL);
+        assertWithMessage("No communal user type on device").that(userTypeDetails).isNotNull();
+
+        // Test that only one communal profile can be created
+        final UserInfo secondCommunalProfile =
+                createUser("Communal", UserManager.USER_TYPE_PROFILE_COMMUNAL, /*flags*/ 0);
+        assertThat(secondCommunalProfile).isNull();
+
+        // Verify that communal profile doesn't have a parent
+        assertThat(mUserManager.getProfileParent(communal.getIdentifier())).isNull();
+
+        // Make sure that, when switching users, the communal profile remains visible.
+        final boolean isStarted = mActivityManager.startProfile(communal);
+        assertWithMessage("Unable to start communal profile").that(isStarted).isTrue();
+        final UserManager umCommunal = (UserManager) mContext.createPackageContextAsUser(
+                        "android", 0, communal).getSystemService(Context.USER_SERVICE);
+        final int originalCurrent = ActivityManager.getCurrentUser();
+        final UserInfo testUser = createUser("TestUser", /* flags= */ 0);
+        assertWithMessage("Communal profile not visible").that(umCommunal.isUserVisible()).isTrue();
+        switchUser(testUser.id);
+        assertWithMessage("Communal profile not visible").that(umCommunal.isUserVisible()).isTrue();
+        switchUser(originalCurrent);
+        assertWithMessage("Communal profile not visible").that(umCommunal.isUserVisible()).isTrue();
+    }
+
+    @Test
+    public void testPrivateProfile() throws Exception {
+        UserHandle mainUser = mUserManager.getMainUser();
+        assumeTrue("Main user is null", mainUser != null);
+        // Get the default properties for private profile user type.
+        final UserTypeDetails userTypeDetails =
+                UserTypeFactory.getUserTypes().get(UserManager.USER_TYPE_PROFILE_PRIVATE);
+        assertWithMessage("No %s type on device", UserManager.USER_TYPE_PROFILE_PRIVATE)
+                .that(userTypeDetails).isNotNull();
+        final UserProperties typeProps = userTypeDetails.getDefaultUserPropertiesReference();
+
+        // Test that only one private profile  can be created
+        final int mainUserId = mainUser.getIdentifier();
+        UserInfo userInfo = createProfileForUser("Private profile1",
+                UserManager.USER_TYPE_PROFILE_PRIVATE,
+                mainUserId);
+        assertThat(userInfo).isNotNull();
+        UserInfo userInfo2 = createProfileForUser("Private profile2",
+                UserManager.USER_TYPE_PROFILE_PRIVATE,
+                mainUserId);
+        assertThat(userInfo2).isNull();
+
+        // Check that the new private profile has the expected properties (relative to the defaults)
+        // provided that the test caller has the necessary permissions.
+        UserProperties privateProfileUserProperties =
+                mUserManager.getUserProperties(UserHandle.of(userInfo.id));
+        assertThat(typeProps.getShowInLauncher())
+                .isEqualTo(privateProfileUserProperties.getShowInLauncher());
+        assertThrows(SecurityException.class, privateProfileUserProperties::getStartWithParent);
+        assertThrows(SecurityException.class,
+                privateProfileUserProperties::getCrossProfileIntentFilterAccessControl);
+        assertThat(typeProps.isMediaSharedWithParent())
+                .isEqualTo(privateProfileUserProperties.isMediaSharedWithParent());
+        assertThat(typeProps.isCredentialShareableWithParent())
+                .isEqualTo(privateProfileUserProperties.isCredentialShareableWithParent());
+        assertThrows(SecurityException.class, privateProfileUserProperties::getDeleteAppWithParent);
+
+        // Verify private profile parent
+        assertThat(mUserManager.getProfileParent(mainUserId)).isNull();
+        UserInfo parentProfileInfo = mUserManager.getProfileParent(userInfo.id);
+        assertThat(parentProfileInfo).isNotNull();
+        assertThat(mainUserId).isEqualTo(parentProfileInfo.id);
+        removeUser(userInfo.id);
+        assertThat(mUserManager.getProfileParent(mainUserId)).isNull();
+    }
+
     @MediumTest
     @Test
     public void testAdd2Users() throws Exception {
@@ -545,23 +640,42 @@
     public void testRemoveUserWhenPossible_withProfiles() throws Exception {
         assumeHeadlessModeEnabled();
         assumeCloneEnabled();
-        final UserInfo parentUser = createUser("Human User", /* flags= */ 0);
-        final UserInfo cloneProfileUser = createProfileForUser("Clone Profile user",
+        final List<String> profileTypesToCreate = Arrays.asList(
                 UserManager.USER_TYPE_PROFILE_CLONE,
-                parentUser.id);
+                UserManager.USER_TYPE_PROFILE_MANAGED
+        );
 
-        final UserInfo workProfileUser = createProfileForUser("Work Profile user",
-                UserManager.USER_TYPE_PROFILE_MANAGED,
-                parentUser.id);
+        final UserInfo parentUser = createUser("Human User", /* flags= */ 0);
+        assertWithMessage("Could not create parent user")
+                .that(parentUser).isNotNull();
+
+        final List<Integer> profileIds = new ArrayList<>();
+        for (String profileType : profileTypesToCreate) {
+            final String name = profileType.substring(profileType.lastIndexOf('.') + 1);
+            if (mUserManager.canAddMoreProfilesToUser(profileType, parentUser.id)) {
+                final UserInfo profile = createProfileForUser(name, profileType, parentUser.id);
+                assertWithMessage("Could not create " + name)
+                        .that(profile).isNotNull();
+                profileIds.add(profile.id);
+            } else {
+                Slog.w(TAG, "Can not add " + name + " to user #" + parentUser.id);
+            }
+        }
+
+        // Test shouldn't pass or fail unless it's allowed to add profiles to secondary users.
+        assumeTrue("Not possible to create any profiles to user #" + parentUser.id,
+                profileIds.size() > 0);
 
         assertThat(mUserManager.removeUserWhenPossible(parentUser.getUserHandle(),
                 /* overrideDevicePolicy= */ false))
                 .isEqualTo(UserManager.REMOVE_RESULT_REMOVED);
         waitForUserRemoval(parentUser.id);
 
-        assertThat(hasUser(parentUser.id)).isFalse();
-        assertThat(hasUser(cloneProfileUser.id)).isFalse();
-        assertThat(hasUser(workProfileUser.id)).isFalse();
+        assertWithMessage("Parent user still exists")
+                .that(hasUser(parentUser.id)).isFalse();
+        profileIds.forEach(id ->
+                assertWithMessage("Profile still exists")
+                        .that(hasUser(id)).isFalse());
     }
 
     /** Tests creating a FULL user via specifying userType. */
@@ -803,6 +917,8 @@
                 .isEqualTo(userTypeDetails.getBadgePlain());
         assertThat(mUserManager.getUserBadgeNoBackgroundResId(userId))
                 .isEqualTo(userTypeDetails.getBadgeNoBackground());
+        assertThat(mUserManager.getUserStatusBarIconResId(userId))
+                .isEqualTo(userTypeDetails.getStatusBarIcon());
 
         final int badgeIndex = userInfo.profileBadge;
         assertThat(mUserManager.getUserBadgeColor(userId)).isEqualTo(
@@ -854,6 +970,7 @@
         assertThat(userProps.isMediaSharedWithParent()).isFalse();
         assertThat(userProps.isCredentialShareableWithParent()).isTrue();
         assertThrows(SecurityException.class, userProps::getDeleteAppWithParent);
+        assertThrows(SecurityException.class, userProps::getAlwaysVisible);
     }
 
 
@@ -1278,6 +1395,24 @@
 
     @MediumTest
     @Test
+    public void testConcurrentUserSwitch() {
+        final int startUser = ActivityManager.getCurrentUser();
+        final UserInfo user1 = createUser("User 1", 0);
+        assertThat(user1).isNotNull();
+        final UserInfo user2 = createUser("User 2", 0);
+        assertThat(user2).isNotNull();
+        final UserInfo user3 = createUser("User 3", 0);
+        assertThat(user3).isNotNull();
+
+        // Switch to the users just created without waiting for the completion of the previous one.
+        switchUserThenRun(user1.id, () -> switchUserThenRun(user2.id, () -> switchUser(user3.id)));
+
+        // Switch back to the starting user.
+        switchUser(startUser);
+    }
+
+    @MediumTest
+    @Test
     public void testConcurrentUserCreate() throws Exception {
         int userCount = mUserManager.getUsers().size();
         int maxSupportedUsers = UserManager.getMaxSupportedUsers();
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 cca924e..07d4065e 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserSystemPackageInstallerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserSystemPackageInstallerTest.java
@@ -44,7 +44,6 @@
 import android.os.SystemProperties;
 import android.os.UserManager;
 import android.platform.test.annotations.Postsubmit;
-import android.support.test.uiautomator.UiDevice;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Log;
@@ -52,6 +51,7 @@
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.MediumTest;
 import androidx.test.runner.AndroidJUnit4;
+import androidx.test.uiautomator.UiDevice;
 
 import com.android.server.LocalServices;
 import com.android.server.SystemConfig;
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
new file mode 100644
index 0000000..0037970
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingValidationTest.kt
@@ -0,0 +1,685 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm.parsing
+
+import android.content.res.Validator
+import android.os.Environment
+import android.platform.test.annotations.Postsubmit
+import com.android.internal.R
+import com.android.server.pm.PackageManagerService
+import com.android.server.pm.pkg.parsing.ParsingPackageUtils
+import com.google.common.truth.Truth.assertThat
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertThrows
+import org.junit.Assert.fail
+import org.junit.Test
+import org.xmlpull.v1.XmlPullParser
+import org.xmlpull.v1.XmlPullParserException
+import org.xmlpull.v1.XmlPullParserFactory
+import java.io.ByteArrayInputStream
+import java.io.File
+
+@Postsubmit
+class AndroidPackageParsingValidationTest {
+    companion object {
+        private val parser2 = PackageParser2.forParsingFileWithDefaults()
+        private val apks = ((PackageManagerService.SYSTEM_PARTITIONS)
+                .flatMap {
+                    listOfNotNull(it.privAppFolder, it.appFolder, it.overlayFolder)
+                } + File(Environment.getRootDirectory(), "framework"))
+                .flatMap {
+                    it.walkTopDown()
+                            .filter { file -> file.name.endsWith(".apk") }
+                            .toList()
+                }
+                .distinct()
+        private val pullParser: XmlPullParser = run {
+            val factory = XmlPullParserFactory.newInstance()
+            factory.isNamespaceAware = true
+            factory.newPullParser()
+        }
+        private val ns = "xmlns:android=\"http://schemas.android.com/apk/res/android\""
+    }
+
+    @Test
+    fun parseExistingApks_NoParseFailures() {
+        val failedParsePackages = mutableListOf<File>()
+        for (apk in apks) {
+            try {
+                parser2.parsePackage(apk, ParsingPackageUtils.PARSE_IS_SYSTEM_DIR, false)
+            } catch (e: Exception) {
+                if (e.message!!.startsWith("Failed to parse")) {
+                    failedParsePackages.add(apk)
+                } else if (e.message!!.startsWith("Skipping target and overlay pair")) {
+                    // ignore
+                } else {
+                    throw e
+                }
+            }
+        }
+        assertThat(failedParsePackages).isEmpty()
+    }
+
+    @Test
+    fun parseBadManifests() {
+        val tag = "manifest"
+        val prefix = "<manifest $ns>"
+        val suffix = "</manifest>"
+        parseTagBadAttr(tag, "package", 256, )
+        parseTagBadAttr(tag, "android:sharedUserId", 256)
+        parseTagBadAttr(tag, "android:versionName", 4000)
+        parseBadApplicationTags(100, prefix, suffix, tag)
+        parseBadOverlayTags(100, prefix, suffix, tag)
+        parseBadInstrumentationTags(100, prefix, suffix, tag)
+        parseBadPermissionGroupTags(100, prefix, suffix, tag)
+        parseBadPermissionTreeTags(100, prefix, suffix, tag)
+        parseBadSupportsGlTextureTags(100, prefix, suffix, tag)
+        parseBadSupportsScreensTags(100, prefix, suffix, tag)
+        parseBadUsesConfigurationTags(100, prefix, suffix, tag)
+        parseBadUsesPermissionSdk23Tags(100, prefix, suffix, tag)
+        parseBadUsesSdkTags(100, prefix, suffix, tag)
+        parseBadCompatibleScreensTags(200, prefix, suffix, tag)
+        parseBadQueriesTags(200, prefix, suffix, tag)
+        parseBadAttributionTags(400, prefix, suffix, tag)
+        parseBadUsesFeatureTags(400, prefix, suffix, tag)
+        parseBadPermissionTags(2000, prefix, suffix, tag)
+        parseBadUsesPermissionTags(20000, prefix, suffix, tag)
+    }
+
+    private fun parseBadApplicationTags(
+            maxNum: Int,
+            prefix: String,
+            suffix: String,
+            parentTag: String
+    ) {
+        val tag = "application"
+        val newPrefix = "$prefix<$tag>"
+        val newSuffix = "</$tag>$suffix"
+
+        parseTagBadAttr(tag, "android:backupAgent", 1024, prefix, suffix)
+        parseTagBadAttr(tag, "android:manageSpaceActivity", 1024, prefix, suffix)
+        parseTagBadAttr(tag, "android:name", 1024, prefix, suffix)
+        parseTagBadAttr(tag, "android:permission", 1024, prefix, suffix)
+        parseTagBadAttr(tag, "android:process", 1024, prefix, suffix)
+        parseTagBadAttr(tag, "android:requiredAccountType", 1024, prefix, suffix)
+        parseTagBadAttr(tag, "android:restrictedAccountType", 1024, prefix, suffix)
+        parseTagBadAttr(tag, "android:taskAffinity", 1024, prefix, suffix)
+        parseBadTagCount(tag, maxNum, parentTag, prefix, suffix)
+
+        parseBadProfileableTags(100, newPrefix, newSuffix, tag)
+        parseBadUsesNativeLibraryTags(100, newPrefix, newSuffix, tag)
+        parseBadReceiverTags(1000, newPrefix, newSuffix, tag)
+        parseBadServiceTags(1000, newPrefix, newSuffix, tag)
+        parseBadActivityAliasTags(4000, newPrefix, newSuffix, tag)
+        parseBadUsesLibraryTags(4000, newPrefix, newSuffix, tag)
+        parseBadProviderTags(8000, newPrefix, newSuffix, tag)
+        parseBadMetaDataTags(8000, newPrefix, newSuffix, tag)
+        parseBadActivityTags(40000, newPrefix, newSuffix, tag)
+    }
+
+    private fun parseBadProfileableTags(
+            maxNum: Int,
+            prefix: String,
+            suffix: String,
+            parentTag: String
+    ) {
+        val tag = "profileable"
+        parseBadTagCount(tag, maxNum, parentTag, prefix, suffix)
+    }
+
+    private fun parseBadUsesNativeLibraryTags(
+            maxNum: Int,
+            prefix: String,
+            suffix: String,
+            parentTag: String
+    ) {
+        val tag = "uses-native-library"
+        parseTagBadAttr(tag, "android:name", 1024, prefix, suffix)
+        parseBadTagCount(tag, maxNum, parentTag, prefix, suffix)
+    }
+
+    private fun parseBadReceiverTags(
+            maxNum: Int,
+            prefix: String,
+            suffix: String,
+            parentTag: String
+    ) {
+        val tag = "receiver"
+        val newPrefix = "$prefix<$tag>"
+        val newSuffix = "</$tag>$suffix"
+        parseTagBadAttr(tag, "android:name", 1024, prefix, suffix)
+        parseTagBadAttr(tag, "android:permission", 1024, prefix, suffix)
+        parseTagBadAttr(tag, "android:process", 1024, prefix, suffix)
+
+        parseBadTagCount(tag, maxNum, parentTag, prefix, suffix)
+        parseBadMetaDataTags(8000, newPrefix, newSuffix, tag)
+        parseBadIntentFilterTags(20000, newPrefix, newSuffix, tag)
+    }
+
+    private fun parseBadServiceTags(
+            maxNum: Int,
+            prefix: String,
+            suffix: String,
+            parentTag: String
+    ) {
+        val tag = "service"
+        val newPrefix = "$prefix<$tag>"
+        val newSuffix = "</$tag>$suffix"
+        parseTagBadAttr(tag, "android:name", 1024, prefix, suffix)
+        parseTagBadAttr(tag, "android:permission", 1024, prefix, suffix)
+        parseTagBadAttr(tag, "android:process", 1024, prefix, suffix)
+
+        parseBadTagCount(tag, maxNum, parentTag, prefix, suffix)
+        parseBadMetaDataTags(8000, newPrefix, newSuffix, tag)
+        parseBadIntentFilterTags(20000, newPrefix, newSuffix, tag)
+    }
+
+    private fun parseBadActivityAliasTags(
+            maxNum: Int,
+            prefix: String,
+            suffix: String,
+            parentTag: String
+    ) {
+        val tag = "activity-alias"
+        val newPrefix = "$prefix<$tag>"
+        val newSuffix = "</$tag>$suffix"
+        parseTagBadAttr(tag, "android:name", 1024, prefix, suffix)
+        parseTagBadAttr(tag, "android:permission", 1024, prefix, suffix)
+        parseTagBadAttr(tag, "android:targetActivity", 1024, prefix, suffix)
+        parseBadTagCount(tag, maxNum, parentTag, prefix, suffix)
+        parseBadMetaDataTags(8000, newPrefix, newSuffix, tag)
+        parseBadIntentFilterTags(20000, newPrefix, newSuffix, tag)
+    }
+
+    private fun parseBadUsesLibraryTags(
+            maxNum: Int,
+            prefix: String,
+            suffix: String,
+            parentTag: String
+    ) {
+        val tag = "uses-library"
+        parseTagBadAttr(tag, "android:name", 1024, prefix, suffix)
+        parseBadTagCount(tag, maxNum, parentTag, prefix, suffix)
+    }
+
+    private fun parseBadActivityTags(
+            maxNum: Int,
+            prefix: String,
+            suffix: String,
+            parentTag: String
+    ) {
+        val tag = "activity"
+        val newPrefix = "$prefix<$tag>"
+        val newSuffix = "</$tag>$suffix"
+        parseTagBadAttr(tag, "android:name", 1024, prefix, suffix)
+        parseTagBadAttr(tag, "android:parentActivityName", 1024, prefix, suffix)
+        parseTagBadAttr(tag, "android:permission", 1024, prefix, suffix)
+        parseTagBadAttr(tag, "android:process", 1024, prefix, suffix)
+        parseTagBadAttr(tag, "android:taskAffinity", 1024, prefix, suffix)
+        parseBadTagCount(tag, maxNum, parentTag, prefix, suffix)
+        parseBadLayoutTags(1000, newPrefix, newSuffix, tag)
+        parseBadMetaDataTags(8000, newPrefix, newSuffix, tag)
+        parseBadIntentFilterTags(20000, newPrefix, newSuffix, tag)
+    }
+
+    private fun parseBadLayoutTags(
+            maxNum: Int,
+            prefix: String,
+            suffix: String,
+            parentTag: String
+    ) {
+        val tag = "layout"
+        parseBadTagCount(tag, maxNum, parentTag, prefix, suffix)
+    }
+
+    private fun parseBadOverlayTags(
+            maxNum: Int,
+            prefix: String,
+            suffix: String,
+            parentTag: String
+    ) {
+        val tag = "overlay"
+        parseTagBadAttr(tag, "android:category", 1024, prefix, suffix)
+        parseTagBadAttr(tag, "android:requiredSystemPropertyName", 32768, prefix, suffix)
+        parseTagBadAttr(tag, "android:requiredSystemPropertyValue", 256, prefix, suffix)
+        parseTagBadAttr(tag, "android:targetPackage", 256, prefix, suffix)
+        parseTagBadAttr(tag, "android:targetName", 1024, prefix, suffix)
+        parseBadTagCount(tag, maxNum, parentTag, prefix, suffix)
+    }
+
+    private fun parseBadInstrumentationTags(
+            maxNum: Int,
+            prefix: String,
+            suffix: String,
+            parentTag: String
+    ) {
+        val tag = "instrumentation"
+        parseTagBadAttr(tag, "android:name", 1024, prefix, suffix)
+        parseTagBadAttr(tag, "android:targetPackage", 256, prefix, suffix)
+        parseTagBadAttr(tag, "android:targetProcesses", 1024, prefix, suffix)
+        parseBadTagCount(tag, maxNum, parentTag, prefix, suffix)
+    }
+
+    private fun parseBadPermissionGroupTags(
+            maxNum: Int,
+            prefix: String,
+            suffix: String,
+            parentTag: String
+    ) {
+        val tag = "permission-group"
+        parseTagBadAttr(tag, "android:name", 1024, prefix, suffix)
+        parseBadTagCount(tag, maxNum, parentTag, prefix, suffix)
+    }
+
+    private fun parseBadPermissionTreeTags(
+            maxNum: Int,
+            prefix: String,
+            suffix: String,
+            parentTag: String
+    ) {
+        val tag = "permission-tree"
+        parseTagBadAttr(tag, "android:name", 1024, prefix, suffix)
+        parseBadTagCount(tag, maxNum, parentTag, prefix, suffix)
+    }
+
+    private fun parseBadSupportsGlTextureTags(
+            maxNum: Int,
+            prefix: String,
+            suffix: String,
+            parentTag: String
+    ) {
+        val tag = "supports-gl-texture"
+        parseTagBadAttr(tag, "android:name", 1024, prefix, suffix)
+        parseBadTagCount(tag, maxNum, parentTag, prefix, suffix)
+    }
+
+    private fun parseBadSupportsScreensTags(
+            maxNum: Int,
+            prefix: String,
+            suffix: String,
+            parentTag: String
+    ) {
+        val tag = "supports-screens"
+        parseBadTagCount(tag, maxNum, parentTag, prefix, suffix)
+    }
+
+    private fun parseBadUsesConfigurationTags(
+            maxNum: Int,
+            prefix: String,
+            suffix: String,
+            parentTag: String
+    ) {
+        val tag = "uses-configuration"
+        parseBadTagCount(tag, maxNum, parentTag, prefix, suffix)
+    }
+
+    private fun parseBadUsesPermissionSdk23Tags(
+            maxNum: Int,
+            prefix: String,
+            suffix: String,
+            parentTag: String
+    ) {
+        val tag = "uses-permission-sdk-23"
+        parseTagBadAttr(tag, "android:name", 1024, prefix, suffix)
+        parseBadTagCount(tag, maxNum, parentTag, prefix, suffix)
+    }
+
+    private fun parseBadUsesSdkTags(
+            maxNum: Int,
+            prefix: String,
+            suffix: String,
+            parentTag: String
+    ) {
+        val tag = "uses-sdk"
+        parseTagBadAttr(tag, "android:name", 1024, prefix, suffix)
+        parseBadTagCount(tag, maxNum, parentTag, prefix, suffix)
+    }
+
+    private fun parseBadCompatibleScreensTags(
+            maxNum: Int,
+            prefix: String,
+            suffix: String,
+            parentTag: String
+    ) {
+        val tag = "compatible-screens"
+        val newPrefix = "$prefix<$tag>"
+        val newSuffix = "</$tag>$suffix"
+        parseBadTagCount(tag, maxNum, parentTag, prefix, suffix)
+        parseBadScreenTags(4000, newPrefix, newSuffix, tag)
+    }
+
+    private fun parseBadScreenTags(
+            maxNum: Int,
+            prefix: String,
+            suffix: String,
+            parentTag: String
+    ) {
+        val tag = "screen"
+        parseBadTagCount(tag, maxNum, parentTag, prefix, suffix)
+    }
+
+    private fun parseBadQueriesTags(
+            maxNum: Int,
+            prefix: String,
+            suffix: String,
+            parentTag: String
+    ) {
+        val tag = "queries"
+        val newPrefix = "$prefix<$tag>"
+        val newSuffix = "</$tag>$suffix"
+        parseBadTagCount(tag, maxNum, parentTag, prefix, suffix)
+        parseBadPackageTags(1000, newPrefix, newSuffix, tag)
+        parseBadIntentTags(2000, newPrefix, newSuffix, tag)
+        parseBadProviderTags(8000, newPrefix, newSuffix, tag)
+    }
+
+    private fun parseBadPackageTags(
+            maxNum: Int,
+            prefix: String,
+            suffix: String,
+            parentTag: String
+    ) {
+        val tag = "package"
+        parseTagBadAttr(tag, "android:name", 1024, prefix, suffix)
+        parseBadTagCount(tag, maxNum, parentTag, prefix, suffix)
+    }
+
+    private fun parseBadIntentTags(
+            maxNum: Int,
+            prefix: String,
+            suffix: String,
+            parentTag: String
+    ) {
+        val tag = "intent"
+        val newPrefix = "$prefix<$tag>"
+        val newSuffix = "</$tag>$suffix"
+        parseBadTagCount(tag, maxNum, parentTag, prefix, suffix)
+        parseBadActionTags(20000, newPrefix, newSuffix, tag)
+        parseBadCategoryTags(40000, newPrefix, newSuffix, tag)
+        parseBadDataTags(40000, newPrefix, newSuffix, tag)
+    }
+
+    private fun parseBadProviderTags(
+            maxNum: Int,
+            prefix: String,
+            suffix: String,
+            parentTag: String
+    ) {
+        val tag = "provider"
+        val newPrefix = "$prefix<$tag>"
+        val newSuffix = "</$tag>$suffix"
+        parseTagBadAttr(tag, "android:name", 1024, prefix, suffix)
+        parseTagBadAttr(tag, "android:permission", 1024, prefix, suffix)
+        parseTagBadAttr(tag, "android:process", 1024, prefix, suffix)
+        parseTagBadAttr(tag, "android:readPermission", 1024, prefix, suffix)
+        parseTagBadAttr(tag, "android:writePermission", 1024, prefix, suffix)
+        parseBadTagCount(tag, maxNum, parentTag, prefix, suffix)
+        parseBadGrantUriPermissionTags(100, newPrefix, newSuffix, tag)
+        parseBadPathPermissionTags(100, newPrefix, newSuffix, tag)
+        parseBadMetaDataTags(8000, newPrefix, newSuffix, tag)
+        parseBadIntentFilterTags(20000, newPrefix, newSuffix, tag)
+    }
+
+    private fun parseBadGrantUriPermissionTags(
+            maxNum: Int,
+            prefix: String,
+            suffix: String,
+            parentTag: String
+    ) {
+        val tag = "grant-uri-permission"
+        parseTagBadAttr(tag, "android:path", 4000, prefix, suffix)
+        parseTagBadAttr(tag, "android:pathPrefix", 4000, prefix, suffix)
+        parseTagBadAttr(tag, "android:pathPattern", 4000, prefix, suffix)
+        parseBadTagCount(tag, maxNum, parentTag, prefix, suffix)
+    }
+
+    private fun parseBadPathPermissionTags(
+            maxNum: Int,
+            prefix: String,
+            suffix: String,
+            parentTag: String
+    ) {
+        val tag = "path-permission"
+        parseTagBadAttr(tag, "android:path", 4000, prefix, suffix)
+        parseTagBadAttr(tag, "android:pathPrefix", 4000, prefix, suffix)
+        parseTagBadAttr(tag, "android:pathPattern", 4000, prefix, suffix)
+        parseTagBadAttr(tag, "android:permission", 1024, prefix, suffix)
+        parseTagBadAttr(tag, "android:readPermission", 1024, prefix, suffix)
+        parseTagBadAttr(tag, "android:writePermission", 1024, prefix, suffix)
+        parseBadTagCount(tag, maxNum, parentTag, prefix, suffix)
+    }
+
+    private fun parseBadMetaDataTags(
+            maxNum: Int,
+            prefix: String,
+            suffix: String,
+            parentTag: String
+    ) {
+        val tag = "meta-data"
+        parseTagBadAttr(tag, "android:name", 1024, prefix, suffix)
+        parseTagBadAttr(tag, "android:value", 32768, prefix, suffix)
+        parseBadTagCount(tag, maxNum, parentTag, prefix, suffix)
+    }
+
+    private fun parseBadIntentFilterTags(
+            maxNum: Int,
+            prefix: String,
+            suffix: String,
+            parentTag: String
+    ) {
+        val tag = "intent-filter"
+        val newPrefix = "$prefix<$tag>"
+        val newSuffix = "</$tag>$suffix"
+        parseBadTagCount(tag, maxNum, parentTag, prefix, suffix)
+        parseBadActionTags(20000, newPrefix, newSuffix, tag)
+        parseBadCategoryTags(40000, newPrefix, newSuffix, tag)
+        parseBadDataTags(40000, newPrefix, newSuffix, tag)
+    }
+
+    private fun parseBadActionTags(
+            maxNum: Int,
+            prefix: String,
+            suffix: String,
+            parentTag: String
+    ) {
+        val tag = "action"
+        parseTagBadAttr(tag, "android:name", 1024, prefix, suffix)
+        parseBadTagCount(tag, maxNum, parentTag, prefix, suffix)
+    }
+
+    private fun parseBadCategoryTags(
+            maxNum: Int,
+            prefix: String,
+            suffix: String,
+            parentTag: String
+    ) {
+        val tag = "category"
+        parseTagBadAttr(tag, "android:name", 1024, prefix, suffix)
+        parseBadTagCount(tag, maxNum, parentTag, prefix, suffix)
+    }
+
+    private fun parseBadDataTags(
+            maxNum: Int,
+            prefix: String,
+            suffix: String,
+            parentTag: String
+    ) {
+        val tag = "data"
+        parseTagBadAttr(tag, "android:scheme", 256, prefix, suffix)
+        parseTagBadAttr(tag, "android:host", 256, prefix, suffix)
+        parseTagBadAttr(tag, "android:path", 4000, prefix, suffix)
+        parseTagBadAttr(tag, "android:pathPattern", 4000, prefix, suffix)
+        parseTagBadAttr(tag, "android:pathPrefix", 4000, prefix, suffix)
+        parseTagBadAttr(tag, "android:pathSuffix", 4000, prefix, suffix)
+        parseTagBadAttr(tag, "android:pathAdvancedPattern", 4000, prefix, suffix)
+        parseTagBadAttr(tag, "android:mimeType", 512, prefix, suffix)
+        parseBadTagCount(tag, maxNum, parentTag, prefix, suffix)
+    }
+
+    private fun parseBadAttributionTags(
+            maxNum: Int,
+            prefix: String,
+            suffix: String,
+            parentTag: String
+    ) {
+        val tag = "attribution"
+        parseBadTagCount(tag, maxNum, parentTag, prefix, suffix)
+    }
+
+    private fun parseBadUsesFeatureTags(
+            maxNum: Int,
+            prefix: String,
+            suffix: String,
+            parentTag: String
+    ) {
+        val tag = "uses-feature"
+        parseTagBadAttr(tag, "android:name", 1024, prefix, suffix)
+        parseBadTagCount(tag, maxNum, parentTag, prefix, suffix)
+    }
+
+    private fun parseBadPermissionTags(
+            maxNum: Int,
+            prefix: String,
+            suffix: String,
+            parentTag: String
+    ) {
+        val tag = "permission"
+        parseTagBadAttr(tag, "android:name", 1024, prefix, suffix)
+        parseTagBadAttr(tag, "android:permissionGroup", 256, prefix, suffix)
+        parseBadTagCount(tag, maxNum, parentTag, prefix, suffix)
+    }
+
+    private fun parseBadUsesPermissionTags(
+            maxNum: Int,
+            prefix: String,
+            suffix: String,
+            parentTag: String
+    ) {
+        val tag = "uses-permission"
+        parseTagBadAttr(tag, "android:name", 1024, prefix, suffix)
+        parseBadTagCount(tag, maxNum, parentTag, prefix, suffix)
+    }
+
+    private fun parseTagBadAttr(
+            tag: String,
+            attrName: String,
+            maxLength: Int,
+            prefix: String = "",
+            suffix: String = ""
+    ) {
+        var attrValue = "x".repeat(maxLength)
+        var tagValue = if (tag.equals("manifest")) "$tag $ns" else tag
+        var manifestStr = "$prefix<$tagValue $attrName=\"$attrValue\" />$suffix"
+        try {
+            parseManifestStr(manifestStr)
+        } catch (e: XmlPullParserException) {
+            fail("Failed to parse valid <$tag> attribute $attrName with max length of $maxLength:" +
+                    " ${e.message}")
+        }
+        attrValue = "x".repeat(maxLength + 1)
+        manifestStr = "$prefix<$tagValue $attrName=\"$attrValue\" />$suffix"
+        val e = assertThrows(XmlPullParserException::class.java) {
+            parseManifestStr(manifestStr)
+        }
+        assertEquals(expectedAttrLengthErrorMsg(attrName.split(":").last(), tag), e.message)
+    }
+
+    private fun parseBadTagCount(
+            tag: String,
+            maxNum: Int,
+            parentTag: String,
+            prefix: String,
+            suffix: String
+    ) {
+        var tags = "<$tag />".repeat(maxNum)
+        var manifestStr = "$prefix$tags$suffix"
+        try {
+            parseManifestStr(manifestStr)
+        } catch (e: XmlPullParserException) {
+            fail("Failed to parse <$tag> with max count limit of $maxNum under" +
+                    " <$parentTag>: ${e.message}")
+        }
+        tags = "<$tag />".repeat(maxNum + 1)
+        manifestStr = "$prefix$tags$suffix"
+        val e = assertThrows(XmlPullParserException::class.java) {
+            parseManifestStr(manifestStr)
+        }
+        assertEquals(expectedCountErrorMsg(tag, parentTag), e.message)
+    }
+
+    @Test
+    fun parseUnexpectedTag_shouldSkip() {
+        val host = "x".repeat(256)
+        val dataTags = "<data android:host=\"$host\" />".repeat(2049)
+        val ns = "http://schemas.android.com/apk/res/android"
+        val manifestStr = "<manifest xmlns:android=\"$ns\" package=\"test\">$dataTags</manifest>"
+        parseManifestStr(manifestStr)
+    }
+
+    fun parseManifestStr(manifestStr: String) {
+        pullParser.setInput(ByteArrayInputStream(manifestStr.toByteArray()), null)
+        val validator = Validator()
+        do {
+            val type = pullParser.next()
+            validator.validate(pullParser)
+        } while (type != XmlPullParser.END_DOCUMENT)
+    }
+
+    fun expectedCountErrorMsg(tag: String, parentTag: String) =
+            "The number of child $tag elements exceeded the max allowed in $parentTag"
+
+    fun expectedAttrLengthErrorMsg(attr: String, tag: String) =
+            "String length limit exceeded for attribute $attr in $tag"
+
+    fun expectedResAttrLengthErrorMsg(tag: String) =
+            "String length limit exceeded for attribute in $tag"
+
+    @Test
+    fun validateResAttrs() {
+        pullParser.setInput(ByteArrayInputStream("<manifest />".toByteArray()), null)
+        pullParser.next()
+        val validator = Validator()
+        validator.validate(pullParser)
+        validateResAttr(pullParser, validator, R.styleable.AndroidManifestData_host,
+                "R.styleable.AndroidManifestData_host", 255)
+        validateResAttr(pullParser, validator, R.styleable.AndroidManifestData_port,
+                "R.styleable.AndroidManifestData_port", 255)
+        validateResAttr(pullParser, validator, R.styleable.AndroidManifestData_scheme,
+                "R.styleable.AndroidManifestData_scheme", 255)
+        validateResAttr(pullParser, validator, R.styleable.AndroidManifestData_mimeType,
+                "R.styleable.AndroidManifestData_mimeType", 512)
+    }
+
+    fun validateResAttr(
+            parser: XmlPullParser,
+            validator: Validator,
+            resId: Int,
+            resIdStr: String,
+            maxLength: Int
+    ) {
+        try {
+            validator.validateAttr(parser, resId, "x".repeat(maxLength))
+        } catch (e: XmlPullParserException) {
+            fail("Failed to parse valid string resource attribute $resIdStr with max length of" +
+                    " $maxLength: ${e.message}")
+        }
+        val e = assertThrows(XmlPullParserException::class.java) {
+            validator.validateAttr(parser, resId, "x".repeat(maxLength + 1))
+        }
+        assertEquals(expectedResAttrLengthErrorMsg("manifest"), e.message)
+    }
+}
\ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
deleted file mode 100644
index 5c6164e..0000000
--- a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
+++ /dev/null
@@ -1,2683 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.power;
-
-import static android.app.ActivityManager.PROCESS_STATE_BOUND_TOP;
-import static android.app.ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
-import static android.os.PowerManager.USER_ACTIVITY_EVENT_BUTTON;
-import static android.os.PowerManagerInternal.WAKEFULNESS_ASLEEP;
-import static android.os.PowerManagerInternal.WAKEFULNESS_AWAKE;
-import static android.os.PowerManagerInternal.WAKEFULNESS_DOZING;
-import static android.os.PowerManagerInternal.WAKEFULNESS_DREAMING;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyLong;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.argThat;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.ArgumentMatchers.isA;
-import static org.mockito.ArgumentMatchers.isNull;
-import static org.mockito.ArgumentMatchers.same;
-import static org.mockito.Mockito.atLeastOnce;
-import static org.mockito.Mockito.atMost;
-import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.app.ActivityManagerInternal;
-import android.attention.AttentionManagerInternal;
-import android.compat.testing.PlatformCompatChangeRule;
-import android.content.AttributionSource;
-import android.content.Context;
-import android.content.ContextWrapper;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.PermissionChecker;
-import android.content.res.Resources;
-import android.hardware.SensorManager;
-import android.hardware.display.AmbientDisplayConfiguration;
-import android.hardware.display.DisplayManagerInternal;
-import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
-import android.hardware.power.Boost;
-import android.hardware.power.Mode;
-import android.os.BatteryManager;
-import android.os.BatteryManagerInternal;
-import android.os.BatterySaverPolicyConfig;
-import android.os.Binder;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.IWakeLockCallback;
-import android.os.Looper;
-import android.os.PowerManager;
-import android.os.PowerManagerInternal;
-import android.os.PowerSaveState;
-import android.os.UserHandle;
-import android.os.test.TestLooper;
-import android.provider.Settings;
-import android.service.dreams.DreamManagerInternal;
-import android.sysprop.PowerProperties;
-import android.test.mock.MockContentResolver;
-import android.view.Display;
-import android.view.DisplayInfo;
-
-import androidx.test.core.app.ApplicationProvider;
-
-import com.android.internal.app.IBatteryStats;
-import com.android.internal.util.test.FakeSettingsProvider;
-import com.android.server.LocalServices;
-import com.android.server.SystemService;
-import com.android.server.lights.LightsManager;
-import com.android.server.policy.WindowManagerPolicy;
-import com.android.server.power.PowerManagerService.BatteryReceiver;
-import com.android.server.power.PowerManagerService.BinderService;
-import com.android.server.power.PowerManagerService.NativeWrapper;
-import com.android.server.power.PowerManagerService.UserSwitchedReceiver;
-import com.android.server.power.PowerManagerService.WakeLock;
-import com.android.server.power.batterysaver.BatterySaverController;
-import com.android.server.power.batterysaver.BatterySaverPolicy;
-import com.android.server.power.batterysaver.BatterySaverStateMachine;
-import com.android.server.power.batterysaver.BatterySavingStats;
-import com.android.server.testutils.OffsettableClock;
-
-import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
-import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TestRule;
-import org.mockito.ArgumentCaptor;
-import org.mockito.ArgumentMatcher;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-import org.mockito.stubbing.Answer;
-
-import java.time.Duration;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.Callable;
-import java.util.concurrent.Executor;
-import java.util.concurrent.atomic.AtomicReference;
-
-/**
- * Tests for {@link com.android.server.power.PowerManagerService}.
- *
- * Build/Install/Run:
- *  atest FrameworksServicesTests:PowerManagerServiceTest
- */
-@SuppressWarnings("GuardedBy")
-public class PowerManagerServiceTest {
-    private static final String SYSTEM_PROPERTY_QUIESCENT = "ro.boot.quiescent";
-    private static final String SYSTEM_PROPERTY_REBOOT_REASON = "sys.boot.reason";
-
-    private static final float BRIGHTNESS_FACTOR = 0.7f;
-    private static final boolean BATTERY_SAVER_ENABLED = true;
-
-    @Mock private BatterySaverController mBatterySaverControllerMock;
-    @Mock private BatterySaverPolicy mBatterySaverPolicyMock;
-    @Mock private BatterySaverStateMachine mBatterySaverStateMachineMock;
-    @Mock private LightsManager mLightsManagerMock;
-    @Mock private DisplayManagerInternal mDisplayManagerInternalMock;
-    @Mock private BatteryManagerInternal mBatteryManagerInternalMock;
-    @Mock private ActivityManagerInternal mActivityManagerInternalMock;
-    @Mock private AttentionManagerInternal mAttentionManagerInternalMock;
-    @Mock private DreamManagerInternal mDreamManagerInternalMock;
-    @Mock private PowerManagerService.NativeWrapper mNativeWrapperMock;
-    @Mock private Notifier mNotifierMock;
-    @Mock private WirelessChargerDetector mWirelessChargerDetectorMock;
-    @Mock private AmbientDisplayConfiguration mAmbientDisplayConfigurationMock;
-    @Mock private SystemPropertiesWrapper mSystemPropertiesMock;
-    @Mock private LowPowerStandbyController mLowPowerStandbyControllerMock;
-    @Mock private Callable<Void> mInvalidateInteractiveCachesMock;
-    @Mock private InattentiveSleepWarningController mInattentiveSleepWarningControllerMock;
-    @Mock private PowerManagerService.PermissionCheckerWrapper mPermissionCheckerWrapperMock;
-    @Mock private PowerManagerService.PowerPropertiesWrapper mPowerPropertiesWrapper;
-
-    @Rule public TestRule compatChangeRule = new PlatformCompatChangeRule();
-
-    private PowerManagerService mService;
-    private ContextWrapper mContextSpy;
-    private BatteryReceiver mBatteryReceiver;
-    private UserSwitchedReceiver mUserSwitchedReceiver;
-    private Resources mResourcesSpy;
-    private OffsettableClock mClock;
-    private long mLastElapsedRealtime;
-    private TestLooper mTestLooper;
-
-    private static class IntentFilterMatcher implements ArgumentMatcher<IntentFilter> {
-        private final IntentFilter mFilter;
-
-        IntentFilterMatcher(IntentFilter filter) {
-            mFilter = filter;
-        }
-
-        @Override
-        public boolean matches(IntentFilter other) {
-            if (other.countActions() != mFilter.countActions()) {
-                return false;
-            }
-            for (int i = 0; i < mFilter.countActions(); i++) {
-                if (!mFilter.getAction(i).equals(other.getAction(i))) {
-                    return false;
-                }
-            }
-            return true;
-        }
-    }
-
-    @Before
-    public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
-        FakeSettingsProvider.clearSettingsProvider();
-
-        PowerSaveState powerSaveState = new PowerSaveState.Builder()
-                .setBatterySaverEnabled(BATTERY_SAVER_ENABLED)
-                .setBrightnessFactor(BRIGHTNESS_FACTOR)
-                .build();
-        when(mBatterySaverPolicyMock.getBatterySaverPolicy(
-                eq(PowerManager.ServiceType.SCREEN_BRIGHTNESS)))
-                .thenReturn(powerSaveState);
-        when(mBatteryManagerInternalMock.isPowered(anyInt())).thenReturn(false);
-        when(mInattentiveSleepWarningControllerMock.isShown()).thenReturn(false);
-        when(mDisplayManagerInternalMock.requestPowerState(anyInt(), any(), anyBoolean()))
-                .thenReturn(true);
-        when(mSystemPropertiesMock.get(eq(SYSTEM_PROPERTY_QUIESCENT), anyString())).thenReturn("");
-        when(mAmbientDisplayConfigurationMock.ambientDisplayAvailable()).thenReturn(true);
-
-        addLocalServiceMock(LightsManager.class, mLightsManagerMock);
-        addLocalServiceMock(DisplayManagerInternal.class, mDisplayManagerInternalMock);
-        addLocalServiceMock(BatteryManagerInternal.class, mBatteryManagerInternalMock);
-        addLocalServiceMock(ActivityManagerInternal.class, mActivityManagerInternalMock);
-        addLocalServiceMock(AttentionManagerInternal.class, mAttentionManagerInternalMock);
-        addLocalServiceMock(DreamManagerInternal.class, mDreamManagerInternalMock);
-
-        mContextSpy = spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
-        mResourcesSpy = spy(mContextSpy.getResources());
-        when(mContextSpy.getResources()).thenReturn(mResourcesSpy);
-
-        MockContentResolver cr = new MockContentResolver(mContextSpy);
-        cr.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
-        when(mContextSpy.getContentResolver()).thenReturn(cr);
-
-        Settings.Global.putInt(mContextSpy.getContentResolver(),
-                Settings.Global.STAY_ON_WHILE_PLUGGED_IN, 0);
-        Settings.Secure.putInt(mContextSpy.getContentResolver(),
-                Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, 0);
-
-        mClock = new OffsettableClock.Stopped();
-        mTestLooper = new TestLooper(mClock::now);
-    }
-
-    private PowerManagerService createService() {
-        mService = new PowerManagerService(mContextSpy, new PowerManagerService.Injector() {
-            @Override
-            Notifier createNotifier(Looper looper, Context context, IBatteryStats batteryStats,
-                    SuspendBlocker suspendBlocker, WindowManagerPolicy policy,
-                    FaceDownDetector faceDownDetector, ScreenUndimDetector screenUndimDetector,
-                    Executor executor) {
-                return mNotifierMock;
-            }
-
-            @Override
-            SuspendBlocker createSuspendBlocker(PowerManagerService service, String name) {
-                return super.createSuspendBlocker(service, name);
-            }
-
-            @Override
-            BatterySaverPolicy createBatterySaverPolicy(
-                    Object lock, Context context, BatterySavingStats batterySavingStats) {
-                return mBatterySaverPolicyMock;
-            }
-
-            @Override
-            BatterySaverController createBatterySaverController(
-                    Object lock, Context context, BatterySaverPolicy batterySaverPolicy,
-                    BatterySavingStats batterySavingStats) {
-                return mBatterySaverControllerMock;
-            }
-
-            @Override
-            BatterySaverStateMachine createBatterySaverStateMachine(Object lock, Context context,
-                    BatterySaverController batterySaverController) {
-                return mBatterySaverStateMachineMock;
-            }
-
-            @Override
-            NativeWrapper createNativeWrapper() {
-                return mNativeWrapperMock;
-            }
-
-            @Override
-            WirelessChargerDetector createWirelessChargerDetector(
-                    SensorManager sensorManager, SuspendBlocker suspendBlocker, Handler handler) {
-                return mWirelessChargerDetectorMock;
-            }
-
-            @Override
-            AmbientDisplayConfiguration createAmbientDisplayConfiguration(Context context) {
-                return mAmbientDisplayConfigurationMock;
-            }
-
-            @Override
-            InattentiveSleepWarningController createInattentiveSleepWarningController() {
-                return mInattentiveSleepWarningControllerMock;
-            }
-
-            @Override
-            public SystemPropertiesWrapper createSystemPropertiesWrapper() {
-                return mSystemPropertiesMock;
-            }
-
-            @Override
-            PowerManagerService.Clock createClock() {
-                return new PowerManagerService.Clock() {
-                    @Override
-                    public long uptimeMillis() {
-                        return mClock.now();
-                    }
-
-                    @Override
-                    public long elapsedRealtime() {
-                        mLastElapsedRealtime = mClock.now();
-                        return mLastElapsedRealtime;
-                    }
-                };
-            }
-
-            @Override
-            Handler createHandler(Looper looper, Handler.Callback callback) {
-                return new Handler(mTestLooper.getLooper(), callback);
-            }
-
-            @Override
-            void invalidateIsInteractiveCaches() {
-                try {
-                    mInvalidateInteractiveCachesMock.call();
-                } catch (Exception e) {
-                    throw new RuntimeException(e);
-                }
-            }
-
-            @Override
-            LowPowerStandbyController createLowPowerStandbyController(Context context,
-                    Looper looper) {
-                return mLowPowerStandbyControllerMock;
-            }
-
-            @Override
-            PowerManagerService.PermissionCheckerWrapper createPermissionCheckerWrapper() {
-                return mPermissionCheckerWrapperMock;
-            }
-
-            @Override
-            PowerManagerService.PowerPropertiesWrapper createPowerPropertiesWrapper() {
-                return mPowerPropertiesWrapper;
-            }
-        });
-        return mService;
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        LocalServices.removeServiceForTest(LightsManager.class);
-        LocalServices.removeServiceForTest(DisplayManagerInternal.class);
-        LocalServices.removeServiceForTest(BatteryManagerInternal.class);
-        LocalServices.removeServiceForTest(ActivityManagerInternal.class);
-        FakeSettingsProvider.clearSettingsProvider();
-    }
-
-    /**
-     * Creates a mock and registers it to {@link LocalServices}.
-     */
-    private static <T> void addLocalServiceMock(Class<T> clazz, T mock) {
-        LocalServices.removeServiceForTest(clazz);
-        LocalServices.addService(clazz, mock);
-    }
-
-    private void startSystem() {
-        mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY);
-
-        // Grab the BatteryReceiver
-        ArgumentCaptor<BatteryReceiver> batCaptor = ArgumentCaptor.forClass(BatteryReceiver.class);
-        IntentFilter batFilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
-        batFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
-        verify(mContextSpy).registerReceiver(batCaptor.capture(),
-                argThat(new IntentFilterMatcher(batFilter)), isNull(), isA(Handler.class));
-        mBatteryReceiver = batCaptor.getValue();
-
-        // Grab the UserSwitchedReceiver
-        ArgumentCaptor<UserSwitchedReceiver> userSwitchedCaptor =
-                ArgumentCaptor.forClass(UserSwitchedReceiver.class);
-        IntentFilter usFilter = new IntentFilter(Intent.ACTION_USER_SWITCHED);
-        verify(mContextSpy).registerReceiver(userSwitchedCaptor.capture(),
-                argThat(new IntentFilterMatcher(usFilter)), isNull(), isA(Handler.class));
-        mUserSwitchedReceiver = userSwitchedCaptor.getValue();
-
-        mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
-    }
-
-    private void forceSleep() {
-        mService.getBinderServiceInstance().goToSleep(mClock.now(),
-                PowerManager.GO_TO_SLEEP_REASON_APPLICATION, PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE);
-    }
-
-    private void forceDream() {
-        mService.getBinderServiceInstance().nap(mClock.now());
-    }
-
-    private void forceAwake() {
-        mService.getBinderServiceInstance().wakeUp(mClock.now(),
-                PowerManager.WAKE_REASON_UNKNOWN, "testing IPowerManager.wakeUp()", "pkg.name");
-    }
-
-    private void forceDozing() {
-        mService.getBinderServiceInstance().goToSleep(mClock.now(),
-                PowerManager.GO_TO_SLEEP_REASON_APPLICATION, 0);
-    }
-
-    private void setPluggedIn(boolean isPluggedIn) {
-        // Set the callback to return the new state
-        when(mBatteryManagerInternalMock.isPowered(anyInt()))
-                .thenReturn(isPluggedIn);
-        // Trigger PowerManager to reread the plug-in state
-        mBatteryReceiver.onReceive(mContextSpy, new Intent(Intent.ACTION_BATTERY_CHANGED));
-    }
-
-    private void setBatteryLevel(int batteryLevel) {
-        when(mBatteryManagerInternalMock.getBatteryLevel())
-                .thenReturn(batteryLevel);
-        mBatteryReceiver.onReceive(mContextSpy, new Intent(Intent.ACTION_BATTERY_CHANGED));
-    }
-
-    private void setBatteryHealth(int batteryHealth) {
-        when(mBatteryManagerInternalMock.getBatteryHealth())
-                .thenReturn(batteryHealth);
-        mBatteryReceiver.onReceive(mContextSpy, new Intent(Intent.ACTION_BATTERY_CHANGED));
-    }
-
-    private void setAttentiveTimeout(int attentiveTimeoutMillis) {
-        Settings.Secure.putInt(
-                mContextSpy.getContentResolver(), Settings.Secure.ATTENTIVE_TIMEOUT,
-                attentiveTimeoutMillis);
-    }
-
-    private void setAttentiveWarningDuration(int attentiveWarningDurationMillis) {
-        when(mResourcesSpy.getInteger(
-                com.android.internal.R.integer.config_attentiveWarningDuration))
-                .thenReturn(attentiveWarningDurationMillis);
-    }
-
-    private void setMinimumScreenOffTimeoutConfig(int minimumScreenOffTimeoutConfigMillis) {
-        when(mResourcesSpy.getInteger(
-                com.android.internal.R.integer.config_minimumScreenOffTimeout))
-                .thenReturn(minimumScreenOffTimeoutConfigMillis);
-    }
-
-    private void setDreamsDisabledByAmbientModeSuppressionConfig(boolean disable) {
-        when(mResourcesSpy.getBoolean(
-                com.android.internal.R.bool.config_dreamsDisabledByAmbientModeSuppressionConfig))
-                .thenReturn(disable);
-    }
-
-    private void setDreamsBatteryLevelDrainConfig(int threshold) {
-        when(mResourcesSpy.getInteger(
-                com.android.internal.R.integer.config_dreamsBatteryLevelDrainCutoff)).thenReturn(
-                threshold);
-    }
-
-    private void advanceTime(long timeMs) {
-        mClock.fastForward(timeMs);
-        mTestLooper.dispatchAll();
-    }
-
-    @Test
-    public void testCreateService_initializesNativeServiceAndSetsPowerModes() {
-        PowerManagerService service = createService();
-        verify(mNativeWrapperMock).nativeInit(same(service));
-        verify(mNativeWrapperMock).nativeSetPowerMode(eq(Mode.INTERACTIVE), eq(true));
-        verify(mNativeWrapperMock).nativeSetPowerMode(eq(Mode.DOUBLE_TAP_TO_WAKE), eq(false));
-    }
-
-    @Test
-    public void testGetLastShutdownReasonInternal() {
-        when(mSystemPropertiesMock.get(eq(SYSTEM_PROPERTY_REBOOT_REASON), any())).thenReturn(
-                "shutdown,thermal");
-        createService();
-        int reason = mService.getLastShutdownReasonInternal();
-        assertThat(reason).isEqualTo(PowerManager.SHUTDOWN_REASON_THERMAL_SHUTDOWN);
-    }
-
-    @Test
-    public void testWakefulnessAwake_InitialValue() {
-        createService();
-        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
-    }
-
-    @Test
-    public void testWakefulnessSleep_NoDozeSleepFlag() {
-        createService();
-        // Start with AWAKE state
-        startSystem();
-        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
-
-        // Take a nap and verify.
-        mService.getBinderServiceInstance().goToSleep(mClock.now(),
-                PowerManager.GO_TO_SLEEP_REASON_APPLICATION, PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE);
-        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
-    }
-
-    @Test
-    public void testWakefulnessSleep_SoftSleepFlag_NoWakelocks() {
-        createService();
-        // Start with AWAKE state
-        startSystem();
-        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
-
-        // Take a nap and verify we go to sleep.
-        mService.getBinderServiceInstance().goToSleep(mClock.now(),
-                PowerManager.GO_TO_SLEEP_REASON_APPLICATION,
-                PowerManager.GO_TO_SLEEP_FLAG_SOFT_SLEEP);
-        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DOZING);
-    }
-
-    @Test
-    public void testWakefulnessSleep_SoftSleepFlag_WithPartialWakelock() {
-        createService();
-        // Start with AWAKE state
-        startSystem();
-        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
-
-        // Grab a wakelock
-        final String tag = "wakelock1";
-        final String packageName = "pkg.name";
-        final IBinder token = new Binder();
-        final int flags = PowerManager.PARTIAL_WAKE_LOCK;
-        mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName,
-                null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY,
-                null /* callback */);
-
-        // Take a nap and verify we stay awake.
-        mService.getBinderServiceInstance().goToSleep(mClock.now(),
-                PowerManager.GO_TO_SLEEP_REASON_APPLICATION,
-                PowerManager.GO_TO_SLEEP_FLAG_SOFT_SLEEP);
-        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DOZING);
-    }
-
-    @Test
-    public void testWakefulnessSleep_SoftSleepFlag_WithFullWakelock() {
-        createService();
-        // Start with AWAKE state
-        startSystem();
-        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
-
-        // Grab a wakelock
-        final String tag = "wakelock1";
-        final String packageName = "pkg.name";
-        final IBinder token = new Binder();
-        final int flags = PowerManager.FULL_WAKE_LOCK;
-        mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName,
-                null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY,
-                null /* callback */);
-
-        // Take a nap and verify we stay awake.
-        mService.getBinderServiceInstance().goToSleep(mClock.now(),
-                PowerManager.GO_TO_SLEEP_REASON_APPLICATION,
-                PowerManager.GO_TO_SLEEP_FLAG_SOFT_SLEEP);
-        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
-    }
-
-    @Test
-    public void testWakefulnessSleep_SoftSleepFlag_WithScreenBrightWakelock() {
-        createService();
-        // Start with AWAKE state
-        startSystem();
-        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
-
-        // Grab a wakelock
-        final String tag = "wakelock1";
-        final String packageName = "pkg.name";
-        final IBinder token = new Binder();
-        final int flags = PowerManager.SCREEN_BRIGHT_WAKE_LOCK;
-        mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName,
-                null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY,
-                null /* callback */);
-
-        // Take a nap and verify we stay awake.
-        mService.getBinderServiceInstance().goToSleep(mClock.now(),
-                PowerManager.GO_TO_SLEEP_REASON_APPLICATION,
-                PowerManager.GO_TO_SLEEP_FLAG_SOFT_SLEEP);
-        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
-    }
-    @Test
-    public void testWakefulnessSleep_SoftSleepFlag_WithScreenDimWakelock() {
-        createService();
-        // Start with AWAKE state
-        startSystem();
-        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
-
-        // Grab a wakelock
-        final String tag = "wakelock1";
-        final String packageName = "pkg.name";
-        final IBinder token = new Binder();
-        final int flags = PowerManager.SCREEN_DIM_WAKE_LOCK;
-        mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName,
-                null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY,
-                null /* callback */);
-
-        // Take a nap and verify we stay awake.
-        mService.getBinderServiceInstance().goToSleep(mClock.now(),
-                PowerManager.GO_TO_SLEEP_REASON_APPLICATION,
-                PowerManager.GO_TO_SLEEP_FLAG_SOFT_SLEEP);
-        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
-    }
-
-    @Test
-    @EnableCompatChanges({PowerManagerService.REQUIRE_TURN_SCREEN_ON_PERMISSION})
-    public void testWakefulnessAwake_AcquireCausesWakeup_turnScreenOnAllowed() {
-        createService();
-        startSystem();
-        forceSleep();
-
-        IBinder token = new Binder();
-        String tag = "acq_causes_wakeup";
-        String packageName = "pkg.name";
-        AttributionSource attrSrc = new AttributionSource(Binder.getCallingUid(),
-                packageName, /* attributionTag= */ null);
-
-        doReturn(PermissionChecker.PERMISSION_GRANTED).when(
-                mPermissionCheckerWrapperMock).checkPermissionForDataDelivery(any(),
-                eq(android.Manifest.permission.TURN_SCREEN_ON), anyInt(), eq(attrSrc), anyString());
-
-        // First, ensure that a normal full wake lock does not cause a wakeup
-        int flags = PowerManager.FULL_WAKE_LOCK;
-        mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName,
-                null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, null);
-        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
-        mService.getBinderServiceInstance().releaseWakeLock(token, 0 /* flags */);
-
-        // Ensure that the flag does *NOT* work with a partial wake lock.
-        flags = PowerManager.PARTIAL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP;
-        mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName,
-                null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, null);
-        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
-        mService.getBinderServiceInstance().releaseWakeLock(token, 0 /* flags */);
-
-        // Verify that flag forces a wakeup when paired to a FULL_WAKE_LOCK
-        flags = PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP;
-        mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName,
-                null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, null);
-        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
-        mService.getBinderServiceInstance().releaseWakeLock(token, 0 /* flags */);
-    }
-
-    @Test
-    @DisableCompatChanges({PowerManagerService.REQUIRE_TURN_SCREEN_ON_PERMISSION})
-    public void testWakefulnessAwake_AcquireCausesWakeupOldSdk_turnScreenOnAllowed() {
-        createService();
-        startSystem();
-        forceSleep();
-
-        IBinder token = new Binder();
-        String tag = "acq_causes_wakeup";
-        String packageName = "pkg.name";
-        AttributionSource attrSrc = new AttributionSource(Binder.getCallingUid(),
-                packageName, /* attributionTag= */ null);
-
-        // verify that the wakeup is allowed for apps targeting older sdks, and therefore won't have
-        // the TURN_SCREEN_ON permission granted
-        doReturn(PermissionChecker.PERMISSION_HARD_DENIED).when(
-                mPermissionCheckerWrapperMock).checkPermissionForDataDelivery(any(),
-                eq(android.Manifest.permission.TURN_SCREEN_ON), anyInt(), eq(attrSrc), anyString());
-
-        doReturn(false).when(mPowerPropertiesWrapper).waive_target_sdk_check_for_turn_screen_on();
-
-        int flags = PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP;
-        mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName,
-                null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, null);
-        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
-        mService.getBinderServiceInstance().releaseWakeLock(token, 0 /* flags */);
-    }
-
-    @Test
-    @EnableCompatChanges({PowerManagerService.REQUIRE_TURN_SCREEN_ON_PERMISSION})
-    public void testWakefulnessAwake_AcquireCausesWakeup_turnScreenOnDenied() {
-        createService();
-        startSystem();
-        forceSleep();
-
-        IBinder token = new Binder();
-        String tag = "acq_causes_wakeup";
-        String packageName = "pkg.name";
-        AttributionSource attrSrc = new AttributionSource(Binder.getCallingUid(),
-                packageName, /* attributionTag= */ null);
-        doReturn(PermissionChecker.PERMISSION_HARD_DENIED).when(
-                mPermissionCheckerWrapperMock).checkPermissionForDataDelivery(any(),
-                eq(android.Manifest.permission.TURN_SCREEN_ON), anyInt(), eq(attrSrc), anyString());
-
-        doReturn(false).when(mPowerPropertiesWrapper).waive_target_sdk_check_for_turn_screen_on();
-        doReturn(false).when(mPowerPropertiesWrapper).permissionless_turn_screen_on();
-
-        // Verify that flag has no effect when TURN_SCREEN_ON is not allowed for apps targeting U+
-        int flags = PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP;
-        mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName,
-                null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, null);
-        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
-        mService.getBinderServiceInstance().releaseWakeLock(token, 0 /* flags */);
-    }
-
-    @Test
-    @EnableCompatChanges({PowerManagerService.REQUIRE_TURN_SCREEN_ON_PERMISSION})
-    public void testWakefulnessAwake_AcquireCausesWakeupOldSdk_turnScreenOnDenied() {
-        createService();
-        startSystem();
-        forceSleep();
-
-        IBinder token = new Binder();
-        String tag = "acq_causes_wakeup";
-        String packageName = "pkg.name";
-        AttributionSource attrSrc = new AttributionSource(Binder.getCallingUid(),
-                packageName, /* attributionTag= */ null);
-        doReturn(PermissionChecker.PERMISSION_HARD_DENIED).when(
-                mPermissionCheckerWrapperMock).checkPermissionForDataDelivery(any(),
-                eq(android.Manifest.permission.TURN_SCREEN_ON), anyInt(), eq(attrSrc), anyString());
-
-        doReturn(true).when(mPowerPropertiesWrapper).waive_target_sdk_check_for_turn_screen_on();
-
-        // Verify that flag has no effect when TURN_SCREEN_ON is not allowed for apps targeting U+
-        int flags = PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP;
-        mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName,
-                null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, null);
-        if (PowerProperties.permissionless_turn_screen_on().orElse(false)) {
-            assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
-        } else {
-            assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
-        }
-        mService.getBinderServiceInstance().releaseWakeLock(token, 0 /* flags */);
-    }
-
-    @Test
-    public void testWakefulnessAwake_IPowerManagerWakeUp() {
-        createService();
-        startSystem();
-        forceSleep();
-        mService.getBinderServiceInstance().wakeUp(mClock.now(),
-                PowerManager.WAKE_REASON_UNKNOWN, "testing IPowerManager.wakeUp()", "pkg.name");
-        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
-    }
-
-    /**
-     * Tests a series of variants that control whether a device wakes-up when it is plugged in
-     * or docked.
-     */
-    @Test
-    public void testWakefulnessAwake_ShouldWakeUpWhenPluggedIn() {
-        createService();
-        startSystem();
-        forceSleep();
-
-        // Test 1:
-        // Set config to prevent it wake up, test, verify, reset config value.
-        when(mResourcesSpy.getBoolean(com.android.internal.R.bool.config_unplugTurnsOnScreen))
-                .thenReturn(false);
-        mService.readConfigurationLocked();
-        setPluggedIn(true);
-        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
-        when(mResourcesSpy.getBoolean(com.android.internal.R.bool.config_unplugTurnsOnScreen))
-                .thenReturn(true);
-        mService.readConfigurationLocked();
-
-        // Test 2:
-        // Turn the power off, sleep, then plug into a wireless charger.
-        // Verify that we do not wake up if the phone is being plugged into a wireless charger.
-        setPluggedIn(false);
-        forceSleep();
-        when(mBatteryManagerInternalMock.getPlugType())
-                .thenReturn(BatteryManager.BATTERY_PLUGGED_WIRELESS);
-        when(mWirelessChargerDetectorMock.update(true /* isPowered */,
-                BatteryManager.BATTERY_PLUGGED_WIRELESS)).thenReturn(false);
-        setPluggedIn(true);
-        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
-
-        // Test 3:
-        // Do not wake up if the phone is being REMOVED from a wireless charger
-        when(mBatteryManagerInternalMock.getPlugType()).thenReturn(0);
-        setPluggedIn(false);
-        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
-
-        // Test 4:
-        // Do not wake if we are dreaming.
-        forceAwake();  // Needs to be awake first before it can dream.
-        forceDream();
-        setPluggedIn(true);
-        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING);
-        forceSleep();
-
-        // Test 5:
-        // Don't wake if the device is configured not to wake up in theater mode (and theater
-        // mode is enabled).
-        Settings.Global.putInt(
-                mContextSpy.getContentResolver(), Settings.Global.THEATER_MODE_ON, 1);
-        mUserSwitchedReceiver.onReceive(mContextSpy, new Intent(Intent.ACTION_USER_SWITCHED));
-        when(mResourcesSpy.getBoolean(
-                com.android.internal.R.bool.config_allowTheaterModeWakeFromUnplug))
-                .thenReturn(false);
-        setPluggedIn(false);
-        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
-        Settings.Global.putInt(
-                mContextSpy.getContentResolver(), Settings.Global.THEATER_MODE_ON, 0);
-        mUserSwitchedReceiver.onReceive(mContextSpy, new Intent(Intent.ACTION_USER_SWITCHED));
-
-        // Test 6:
-        // Don't wake up if we are Dozing away and always-on is enabled.
-        when(mAmbientDisplayConfigurationMock.alwaysOnEnabled(UserHandle.USER_CURRENT))
-                .thenReturn(true);
-        mUserSwitchedReceiver.onReceive(mContextSpy, new Intent(Intent.ACTION_USER_SWITCHED));
-        forceAwake();
-        forceDozing();
-        setPluggedIn(true);
-        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DOZING);
-
-        // Test 7:
-        // Finally, take away all the factors above and ensure the device wakes up!
-        forceAwake();
-        forceSleep();
-        setPluggedIn(false);
-        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
-    }
-
-    /**
-     * Tests that dreaming stops when undocking and not configured to keep dreaming.
-     */
-    @Test
-    public void testWakefulnessDream_shouldStopDreamingWhenUndocked_whenNotConfigured() {
-        // Make sure "unplug turns on screen" is configured to true.
-        when(mResourcesSpy.getBoolean(com.android.internal.R.bool.config_unplugTurnsOnScreen))
-                .thenReturn(true);
-
-        createService();
-        startSystem();
-
-        ArgumentCaptor<DreamManagerInternal.DreamManagerStateListener> dreamManagerStateListener =
-                ArgumentCaptor.forClass(DreamManagerInternal.DreamManagerStateListener.class);
-        verify(mDreamManagerInternalMock).registerDreamManagerStateListener(
-                dreamManagerStateListener.capture());
-        dreamManagerStateListener.getValue().onKeepDreamingWhenUnpluggingChanged(false);
-
-        when(mBatteryManagerInternalMock.getPlugType())
-                .thenReturn(BatteryManager.BATTERY_PLUGGED_DOCK);
-        setPluggedIn(true);
-
-        forceAwake();  // Needs to be awake first before it can dream.
-        forceDream();
-        when(mBatteryManagerInternalMock.getPlugType()).thenReturn(0);
-        setPluggedIn(false);
-
-        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
-    }
-
-    /**
-     * Tests that dreaming continues when undocking and configured to do so.
-     */
-    @Test
-    public void testWakefulnessDream_shouldKeepDreamingWhenUndocked_whenConfigured() {
-        // Make sure "unplug turns on screen" is configured to true.
-        when(mResourcesSpy.getBoolean(com.android.internal.R.bool.config_unplugTurnsOnScreen))
-                .thenReturn(true);
-
-        createService();
-        startSystem();
-
-        ArgumentCaptor<DreamManagerInternal.DreamManagerStateListener> dreamManagerStateListener =
-                ArgumentCaptor.forClass(DreamManagerInternal.DreamManagerStateListener.class);
-        verify(mDreamManagerInternalMock).registerDreamManagerStateListener(
-                dreamManagerStateListener.capture());
-        dreamManagerStateListener.getValue().onKeepDreamingWhenUnpluggingChanged(true);
-
-        when(mBatteryManagerInternalMock.getPlugType())
-                .thenReturn(BatteryManager.BATTERY_PLUGGED_DOCK);
-        setPluggedIn(true);
-
-        forceAwake();  // Needs to be awake first before it can dream.
-        forceDream();
-        when(mBatteryManagerInternalMock.getPlugType()).thenReturn(0);
-        setPluggedIn(false);
-
-        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING);
-    }
-
-    /**
-     * Tests that dreaming stops when undocking while showing a dream that prevents it.
-     */
-    @Test
-    public void testWakefulnessDream_shouldStopDreamingWhenUndocked_whenDreamPrevents() {
-        // Make sure "unplug turns on screen" is configured to true.
-        when(mResourcesSpy.getBoolean(com.android.internal.R.bool.config_unplugTurnsOnScreen))
-                .thenReturn(true);
-
-        createService();
-        startSystem();
-
-        ArgumentCaptor<DreamManagerInternal.DreamManagerStateListener> dreamManagerStateListener =
-                ArgumentCaptor.forClass(DreamManagerInternal.DreamManagerStateListener.class);
-        verify(mDreamManagerInternalMock).registerDreamManagerStateListener(
-                dreamManagerStateListener.capture());
-        dreamManagerStateListener.getValue().onKeepDreamingWhenUnpluggingChanged(true);
-
-        when(mBatteryManagerInternalMock.getPlugType())
-                .thenReturn(BatteryManager.BATTERY_PLUGGED_DOCK);
-        setPluggedIn(true);
-
-        forceAwake();  // Needs to be awake first before it can dream.
-        forceDream();
-        dreamManagerStateListener.getValue().onKeepDreamingWhenUnpluggingChanged(false);
-        when(mBatteryManagerInternalMock.getPlugType()).thenReturn(0);
-        setPluggedIn(false);
-
-        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
-    }
-
-    @Test
-    public void testWakefulnessDream_shouldStopDreamingWhenUnplugging_whenDreamPrevents() {
-        // Make sure "unplug turns on screen" is configured to true.
-        when(mResourcesSpy.getBoolean(com.android.internal.R.bool.config_unplugTurnsOnScreen))
-                .thenReturn(true);
-
-        createService();
-        startSystem();
-
-        ArgumentCaptor<DreamManagerInternal.DreamManagerStateListener> dreamManagerStateListener =
-                ArgumentCaptor.forClass(DreamManagerInternal.DreamManagerStateListener.class);
-        verify(mDreamManagerInternalMock).registerDreamManagerStateListener(
-                dreamManagerStateListener.capture());
-
-        when(mBatteryManagerInternalMock.getPlugType())
-                .thenReturn(BatteryManager.BATTERY_PLUGGED_AC);
-        setPluggedIn(true);
-
-        forceAwake();  // Needs to be awake first before it can dream.
-        forceDream();
-        dreamManagerStateListener.getValue().onKeepDreamingWhenUnpluggingChanged(false);
-        when(mBatteryManagerInternalMock.getPlugType()).thenReturn(0);
-        setPluggedIn(false);
-
-        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
-    }
-
-
-    @Test
-    public void testWakefulnessDoze_goToSleep() {
-        createService();
-        // Start with AWAKE state
-        startSystem();
-        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
-
-        // Take a nap and verify.
-        mService.getBinderServiceInstance().goToSleep(mClock.now(),
-                PowerManager.GO_TO_SLEEP_REASON_APPLICATION, 0);
-        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DOZING);
-    }
-
-    @Test
-    public void testWasDeviceIdleFor_true() {
-        int interval = 1000;
-        createService();
-        startSystem();
-        mService.onUserActivity();
-        advanceTime(interval + 1 /* just a little more */);
-        assertThat(mService.wasDeviceIdleForInternal(interval)).isTrue();
-    }
-
-    @Test
-    public void testWasDeviceIdleFor_false() {
-        int interval = 1000;
-        createService();
-        startSystem();
-        mService.onUserActivity();
-        assertThat(mService.wasDeviceIdleForInternal(interval)).isFalse();
-    }
-
-    @Test
-    public void testForceSuspend_putsDeviceToSleep() {
-        createService();
-        startSystem();
-
-        // Verify that we start awake
-        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
-
-        // Grab the wakefulness value when PowerManager finally calls into the
-        // native component to actually perform the suspend.
-        when(mNativeWrapperMock.nativeForceSuspend()).then(inv -> {
-            assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
-            return true;
-        });
-
-        boolean retval = mService.getBinderServiceInstance().forceSuspend();
-        assertThat(retval).isTrue();
-
-        // Still asleep when the function returns.
-        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
-    }
-
-    @Test
-    public void testForceSuspend_wakeLocksDisabled() {
-        final String tag = "TestWakelockTag_098213";
-        final int flags = PowerManager.PARTIAL_WAKE_LOCK;
-        final String pkg = mContextSpy.getOpPackageName();
-
-        createService();
-
-        // Set up the Notification mock to keep track of the wakelocks that are currently
-        // active or disabled. We'll use this to verify that wakelocks are disabled when
-        // they should be.
-        final Map<String, Integer> wakelockMap = new HashMap<>(1);
-        doAnswer(inv -> {
-            wakelockMap.put((String) inv.getArguments()[1], (int) inv.getArguments()[0]);
-            return null;
-        }).when(mNotifierMock).onWakeLockAcquired(anyInt(), anyString(), anyString(), anyInt(),
-                anyInt(), any(), any(), any());
-        doAnswer(inv -> {
-            wakelockMap.remove((String) inv.getArguments()[1]);
-            return null;
-        }).when(mNotifierMock).onWakeLockReleased(anyInt(), anyString(), anyString(), anyInt(),
-                anyInt(), any(), any(), any());
-
-        //
-        // TEST STARTS HERE
-        //
-        startSystem();
-
-        // Verify that we start awake
-        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
-
-        // Create a wakelock
-        mService.getBinderServiceInstance().acquireWakeLock(new Binder(), flags, tag, pkg,
-                null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, null);
-        assertThat(wakelockMap.get(tag)).isEqualTo(flags);  // Verify wakelock is active.
-
-        // Confirm that the wakelocks have been disabled when the forceSuspend is in flight.
-        when(mNativeWrapperMock.nativeForceSuspend()).then(inv -> {
-            // Verify that the wakelock is disabled by the time we get to the native force
-            // suspend call.
-            assertThat(wakelockMap.containsKey(tag)).isFalse();
-            return true;
-        });
-
-        assertThat(mService.getBinderServiceInstance().forceSuspend()).isTrue();
-        assertThat(wakelockMap.get(tag)).isEqualTo(flags);
-
-    }
-
-    @Test
-    public void testForceSuspend_forceSuspendFailurePropagated() {
-        createService();
-        startSystem();
-        when(mNativeWrapperMock.nativeForceSuspend()).thenReturn(false);
-        assertThat(mService.getBinderServiceInstance().forceSuspend()).isFalse();
-    }
-
-    @SuppressWarnings("GuardedBy")
-    @Test
-    public void testScreensaverActivateOnSleepDisabled_powered_afterTimeout_goesToDozing() {
-        when(mBatteryManagerInternalMock.isPowered(anyInt())).thenReturn(true);
-
-        doAnswer(inv -> {
-            when(mDreamManagerInternalMock.isDreaming()).thenReturn(true);
-            return null;
-        }).when(mDreamManagerInternalMock).startDream(anyBoolean(), anyString());
-
-        setMinimumScreenOffTimeoutConfig(5);
-        createService();
-        startSystem();
-
-        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
-
-        advanceTime(15000);
-        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DOZING);
-    }
-
-    @SuppressWarnings("GuardedBy")
-    @Test
-    public void testScreensaverActivateOnSleepEnabled_powered_afterTimeout_goesToDreaming() {
-        when(mBatteryManagerInternalMock.isPowered(anyInt())).thenReturn(true);
-        Settings.Secure.putInt(mContextSpy.getContentResolver(),
-                Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, 1);
-
-        doAnswer(inv -> {
-            when(mDreamManagerInternalMock.isDreaming()).thenReturn(true);
-            return null;
-        }).when(mDreamManagerInternalMock).startDream(anyBoolean(), anyString());
-
-        setMinimumScreenOffTimeoutConfig(5);
-        createService();
-        startSystem();
-
-        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
-
-        advanceTime(15000);
-        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING);
-    }
-
-    @SuppressWarnings("GuardedBy")
-    @Test
-    public void testAmbientSuppression_disablesDreamingAndWakesDevice() {
-        Settings.Secure.putInt(mContextSpy.getContentResolver(),
-                Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, 1);
-        Settings.Secure.putInt(mContextSpy.getContentResolver(),
-                Settings.Secure.SCREENSAVER_ENABLED, 1);
-
-        setDreamsDisabledByAmbientModeSuppressionConfig(true);
-        setMinimumScreenOffTimeoutConfig(10000);
-        createService();
-        startSystem();
-
-        doAnswer(inv -> {
-            when(mDreamManagerInternalMock.isDreaming()).thenReturn(true);
-            return null;
-        }).when(mDreamManagerInternalMock).startDream(anyBoolean(), anyString());
-
-        setPluggedIn(true);
-        // Allow asynchronous sandman calls to execute.
-        advanceTime(10000);
-
-        forceDream();
-        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING);
-        mService.getBinderServiceInstance().suppressAmbientDisplay("test", true);
-        advanceTime(50);
-        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
-    }
-
-    @SuppressWarnings("GuardedBy")
-    @Test
-    public void testAmbientSuppressionDisabled_shouldNotWakeDevice() {
-        Settings.Secure.putInt(mContextSpy.getContentResolver(),
-                Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, 1);
-        Settings.Secure.putInt(mContextSpy.getContentResolver(),
-                Settings.Secure.SCREENSAVER_ENABLED, 1);
-
-        setDreamsDisabledByAmbientModeSuppressionConfig(false);
-        setMinimumScreenOffTimeoutConfig(10000);
-        createService();
-        startSystem();
-
-        doAnswer(inv -> {
-            when(mDreamManagerInternalMock.isDreaming()).thenReturn(true);
-            return null;
-        }).when(mDreamManagerInternalMock).startDream(anyBoolean(), anyString());
-
-        setPluggedIn(true);
-        // Allow asynchronous sandman calls to execute.
-        advanceTime(10000);
-
-        forceDream();
-        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING);
-        mService.getBinderServiceInstance().suppressAmbientDisplay("test", true);
-        advanceTime(50);
-        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING);
-    }
-
-    @Test
-    public void testAmbientSuppression_doesNotAffectDreamForcing() {
-        Settings.Secure.putInt(mContextSpy.getContentResolver(),
-                Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, 1);
-        Settings.Secure.putInt(mContextSpy.getContentResolver(),
-                Settings.Secure.SCREENSAVER_ENABLED, 1);
-
-        setDreamsDisabledByAmbientModeSuppressionConfig(true);
-        setMinimumScreenOffTimeoutConfig(10000);
-        createService();
-        startSystem();
-
-        doAnswer(inv -> {
-            when(mDreamManagerInternalMock.isDreaming()).thenReturn(true);
-            return null;
-        }).when(mDreamManagerInternalMock).startDream(anyBoolean(), anyString());
-
-        mService.getBinderServiceInstance().suppressAmbientDisplay("test", true);
-        setPluggedIn(true);
-        // Allow asynchronous sandman calls to execute.
-        advanceTime(10000);
-
-        // Verify that forcing dream still works even though ambient display is suppressed
-        forceDream();
-        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING);
-    }
-
-    @Test
-    public void testBatteryDrainDuringDream() {
-        Settings.Secure.putInt(mContextSpy.getContentResolver(),
-                Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, 1);
-        Settings.Secure.putInt(mContextSpy.getContentResolver(),
-                Settings.Secure.SCREENSAVER_ENABLED, 1);
-
-        setMinimumScreenOffTimeoutConfig(100);
-        setDreamsBatteryLevelDrainConfig(5);
-        createService();
-        startSystem();
-
-        doAnswer(inv -> {
-            when(mDreamManagerInternalMock.isDreaming()).thenReturn(true);
-            return null;
-        }).when(mDreamManagerInternalMock).startDream(anyBoolean(), anyString());
-
-        setBatteryLevel(100);
-        setPluggedIn(true);
-
-        forceAwake();  // Needs to be awake first before it can dream.
-        forceDream();
-        advanceTime(10); // Allow async calls to happen
-        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING);
-        setBatteryLevel(90);
-        advanceTime(10); // Allow async calls to happen
-        assertThat(mService.getDreamsBatteryLevelDrain()).isEqualTo(10);
-
-        // If battery overheat protection is enabled, we shouldn't count battery drain
-        setBatteryHealth(BatteryManager.BATTERY_HEALTH_OVERHEAT);
-        setBatteryLevel(70);
-        advanceTime(10); // Allow async calls to happen
-        assertThat(mService.getDreamsBatteryLevelDrain()).isEqualTo(10);
-    }
-
-    @Test
-    public void testSetDozeOverrideFromDreamManager_triggersSuspendBlocker() {
-        final String suspendBlockerName = "PowerManagerService.Display";
-        final String tag = "acq_causes_wakeup";
-        final String packageName = "pkg.name";
-        final IBinder token = new Binder();
-
-        final boolean[] isAcquired = new boolean[1];
-        doAnswer(inv -> {
-            if (suspendBlockerName.equals(inv.getArguments()[0])) {
-                isAcquired[0] = false;
-            }
-            return null;
-        }).when(mNativeWrapperMock).nativeReleaseSuspendBlocker(any());
-
-        doAnswer(inv -> {
-            if (suspendBlockerName.equals(inv.getArguments()[0])) {
-                isAcquired[0] = true;
-            }
-            return null;
-        }).when(mNativeWrapperMock).nativeAcquireSuspendBlocker(any());
-
-        // Need to create the service after we stub the mocks for this test because some of the
-        // mocks are used during the constructor.
-        createService();
-
-        // Start with AWAKE state
-        startSystem();
-        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
-        assertTrue(isAcquired[0]);
-
-        // Take a nap and verify we no longer hold the blocker
-        int flags = PowerManager.DOZE_WAKE_LOCK;
-        mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName,
-                null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, null);
-        when(mDreamManagerInternalMock.isDreaming()).thenReturn(true);
-        mService.getBinderServiceInstance().goToSleep(mClock.now(),
-                PowerManager.GO_TO_SLEEP_REASON_APPLICATION, 0);
-        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DOZING);
-        assertFalse(isAcquired[0]);
-
-        // Override the display state by DreamManager and verify is reacquires the blocker.
-        mService.getLocalServiceInstance()
-                .setDozeOverrideFromDreamManager(Display.STATE_ON, PowerManager.BRIGHTNESS_DEFAULT);
-        assertTrue(isAcquired[0]);
-    }
-
-    @Test
-    public void testSuspendBlockerHeldDuringBoot() {
-        final String suspendBlockerName = "PowerManagerService.Booting";
-
-        final boolean[] isAcquired = new boolean[1];
-        doAnswer(inv -> {
-            isAcquired[0] = false;
-            return null;
-        }).when(mNativeWrapperMock).nativeReleaseSuspendBlocker(eq(suspendBlockerName));
-
-        doAnswer(inv -> {
-            isAcquired[0] = true;
-            return null;
-        }).when(mNativeWrapperMock).nativeAcquireSuspendBlocker(eq(suspendBlockerName));
-
-        // Need to create the service after we stub the mocks for this test because some of the
-        // mocks are used during the constructor.
-        createService();
-        assertTrue(isAcquired[0]);
-
-        mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY);
-        assertTrue(isAcquired[0]);
-
-        mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
-        assertFalse(isAcquired[0]);
-    }
-
-    @Test
-    public void testInattentiveSleep_hideWarningIfStayOnIsEnabledAndPluggedIn() {
-        setMinimumScreenOffTimeoutConfig(5);
-        setAttentiveWarningDuration(120);
-        setAttentiveTimeout(100);
-
-        Settings.Global.putInt(mContextSpy.getContentResolver(),
-                Settings.Global.STAY_ON_WHILE_PLUGGED_IN, BatteryManager.BATTERY_PLUGGED_AC);
-
-        createService();
-        startSystem();
-
-        verify(mInattentiveSleepWarningControllerMock, times(1)).show();
-        verify(mInattentiveSleepWarningControllerMock, never()).dismiss(anyBoolean());
-        when(mInattentiveSleepWarningControllerMock.isShown()).thenReturn(true);
-
-        setPluggedIn(true);
-        verify(mInattentiveSleepWarningControllerMock, atLeastOnce()).dismiss(true);
-    }
-
-    @Test
-    public void testInattentiveSleep_hideWarningIfInattentiveSleepIsDisabled() {
-        setMinimumScreenOffTimeoutConfig(5);
-        setAttentiveWarningDuration(120);
-        setAttentiveTimeout(100);
-
-        createService();
-        startSystem();
-
-        verify(mInattentiveSleepWarningControllerMock, times(1)).show();
-        verify(mInattentiveSleepWarningControllerMock, never()).dismiss(anyBoolean());
-        when(mInattentiveSleepWarningControllerMock.isShown()).thenReturn(true);
-
-        setAttentiveTimeout(-1);
-        mService.handleSettingsChangedLocked();
-
-        verify(mInattentiveSleepWarningControllerMock, atLeastOnce()).dismiss(true);
-    }
-
-    @Test
-    public void testInattentiveSleep_userActivityDismissesWarning() {
-        final DisplayInfo info = new DisplayInfo();
-        info.displayGroupId = Display.DEFAULT_DISPLAY_GROUP;
-        when(mDisplayManagerInternalMock.getDisplayInfo(Display.DEFAULT_DISPLAY)).thenReturn(info);
-        setMinimumScreenOffTimeoutConfig(5);
-        setAttentiveWarningDuration(1900);
-        setAttentiveTimeout(2000);
-
-        createService();
-        startSystem();
-
-        mService.getBinderServiceInstance().userActivity(Display.DEFAULT_DISPLAY, mClock.now(),
-                PowerManager.USER_ACTIVITY_EVENT_TOUCH, 0);
-        verify(mInattentiveSleepWarningControllerMock, never()).show();
-
-        advanceTime(150);
-        verify(mInattentiveSleepWarningControllerMock, times(1)).show();
-        verify(mInattentiveSleepWarningControllerMock, never()).dismiss(anyBoolean());
-        when(mInattentiveSleepWarningControllerMock.isShown()).thenReturn(true);
-
-        mService.getBinderServiceInstance().userActivity(Display.DEFAULT_DISPLAY, mClock.now(),
-                PowerManager.USER_ACTIVITY_EVENT_TOUCH, 0);
-        verify(mInattentiveSleepWarningControllerMock, times(1)).dismiss(true);
-    }
-
-    @Test
-    public void testInattentiveSleep_warningHiddenAfterWakingUp() {
-        setMinimumScreenOffTimeoutConfig(5);
-        setAttentiveWarningDuration(70);
-        setAttentiveTimeout(100);
-
-        createService();
-        startSystem();
-        advanceTime(50);
-        verify(mInattentiveSleepWarningControllerMock, atLeastOnce()).show();
-        when(mInattentiveSleepWarningControllerMock.isShown()).thenReturn(true);
-        advanceTime(70);
-        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
-        forceAwake();
-        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
-        verify(mInattentiveSleepWarningControllerMock, atLeastOnce()).dismiss(false);
-    }
-
-    @Test
-    public void testInattentiveSleep_warningStaysWhenDreaming() {
-        setMinimumScreenOffTimeoutConfig(5);
-        setAttentiveWarningDuration(70);
-        setAttentiveTimeout(100);
-        createService();
-        startSystem();
-        advanceTime(50);
-        verify(mInattentiveSleepWarningControllerMock, atLeastOnce()).show();
-        when(mInattentiveSleepWarningControllerMock.isShown()).thenReturn(true);
-
-        forceDream();
-        when(mDreamManagerInternalMock.isDreaming()).thenReturn(true);
-
-        advanceTime(10);
-        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING);
-        verify(mInattentiveSleepWarningControllerMock, never()).dismiss(anyBoolean());
-    }
-
-    @Test
-    public void testInattentiveSleep_warningNotShownWhenSleeping() {
-        setMinimumScreenOffTimeoutConfig(5);
-        setAttentiveWarningDuration(70);
-        setAttentiveTimeout(100);
-        createService();
-        startSystem();
-
-        advanceTime(10);
-        forceSleep();
-
-        advanceTime(50);
-        verify(mInattentiveSleepWarningControllerMock, never()).show();
-    }
-
-    @Test
-    public void testInattentiveSleep_noWarningShownIfInattentiveSleepDisabled() {
-        setAttentiveTimeout(-1);
-        createService();
-        startSystem();
-        verify(mInattentiveSleepWarningControllerMock, never()).show();
-    }
-
-    @Test
-    public void testInattentiveSleep_goesToSleepAfterTimeout() {
-        setMinimumScreenOffTimeoutConfig(5);
-        setAttentiveTimeout(5);
-        createService();
-        startSystem();
-        advanceTime(20);
-        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
-        assertThat(mService.getBinderServiceInstance().getLastSleepReason()).isEqualTo(
-                PowerManager.GO_TO_SLEEP_REASON_INATTENTIVE);
-    }
-
-    @Test
-    public void testInattentiveSleep_goesToSleepWithWakeLock() {
-        final String pkg = mContextSpy.getOpPackageName();
-        final Binder token = new Binder();
-        final String tag = "testInattentiveSleep_goesToSleepWithWakeLock";
-
-        setMinimumScreenOffTimeoutConfig(5);
-        setAttentiveTimeout(30);
-        createService();
-        startSystem();
-
-        mService.getBinderServiceInstance().acquireWakeLock(token,
-                PowerManager.SCREEN_BRIGHT_WAKE_LOCK, tag, pkg,
-                null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, null);
-
-        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
-        advanceTime(60);
-        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
-        assertThat(mService.getBinderServiceInstance().getLastSleepReason()).isEqualTo(
-                PowerManager.GO_TO_SLEEP_REASON_INATTENTIVE);
-    }
-
-    @Test
-    public void testInattentiveSleep_dreamEnds_goesToSleepAfterTimeout() {
-        setMinimumScreenOffTimeoutConfig(5);
-        setAttentiveTimeout(30000);
-        createService();
-        startSystem();
-
-        advanceTime(10000);
-        forceDream();
-        advanceTime(10000);
-        final String pkg = mContextSpy.getOpPackageName();
-        mService.getBinderServiceInstance().wakeUp(mClock.now(),
-                PowerManager.WAKE_REASON_DREAM_FINISHED, "PowerManagerServiceTest:DREAM_FINISHED",
-                pkg);
-        advanceTime(10001);
-
-        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
-        assertThat(mService.getBinderServiceInstance().getLastSleepReason()).isEqualTo(
-                PowerManager.GO_TO_SLEEP_REASON_INATTENTIVE);
-    }
-
-    @Test
-    public void testInattentiveSleep_wakeLockOnAfterRelease_inattentiveSleepTimeoutNotAffected() {
-        final DisplayInfo info = new DisplayInfo();
-        info.displayGroupId = Display.DEFAULT_DISPLAY_GROUP;
-        when(mDisplayManagerInternalMock.getDisplayInfo(Display.DEFAULT_DISPLAY)).thenReturn(info);
-
-        final String pkg = mContextSpy.getOpPackageName();
-        final Binder token = new Binder();
-        final String tag = "testInattentiveSleep_wakeLockOnAfterRelease";
-
-        setMinimumScreenOffTimeoutConfig(5);
-        setAttentiveTimeout(2000);
-        createService();
-        startSystem();
-
-        mService.getBinderServiceInstance().acquireWakeLock(token,
-                PowerManager.SCREEN_BRIGHT_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE, tag, pkg,
-                null /* workSource */, null /* historyTag */, Display.DEFAULT_DISPLAY, null);
-
-        advanceTime(1500);
-        mService.getBinderServiceInstance().releaseWakeLock(token, 0 /* flags */);
-
-        advanceTime(520);
-        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
-        assertThat(mService.getBinderServiceInstance().getLastSleepReason()).isEqualTo(
-                PowerManager.GO_TO_SLEEP_REASON_INATTENTIVE);
-    }
-
-    @Test
-    public void testInattentiveSleep_userActivityNoChangeLights_inattentiveSleepTimeoutNotAffected() {
-        final DisplayInfo info = new DisplayInfo();
-        info.displayGroupId = Display.DEFAULT_DISPLAY_GROUP;
-        when(mDisplayManagerInternalMock.getDisplayInfo(Display.DEFAULT_DISPLAY)).thenReturn(info);
-
-        setMinimumScreenOffTimeoutConfig(5);
-        setAttentiveTimeout(2000);
-        createService();
-        startSystem();
-
-        advanceTime(1500);
-        mService.getBinderServiceInstance().userActivity(Display.DEFAULT_DISPLAY, mClock.now(),
-                PowerManager.USER_ACTIVITY_EVENT_OTHER,
-                PowerManager.USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS);
-
-        advanceTime(520);
-        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
-        assertThat(mService.getBinderServiceInstance().getLastSleepReason()).isEqualTo(
-                PowerManager.GO_TO_SLEEP_REASON_INATTENTIVE);
-    }
-
-    @Test
-    public void testInattentiveSleep_userActivity_inattentiveSleepTimeoutExtended() {
-        final DisplayInfo info = new DisplayInfo();
-        info.displayGroupId = Display.DEFAULT_DISPLAY_GROUP;
-        when(mDisplayManagerInternalMock.getDisplayInfo(Display.DEFAULT_DISPLAY)).thenReturn(info);
-
-        setMinimumScreenOffTimeoutConfig(5);
-        setAttentiveTimeout(2000);
-        createService();
-        startSystem();
-
-        advanceTime(1500);
-        mService.getBinderServiceInstance().userActivity(Display.DEFAULT_DISPLAY, mClock.now(),
-                PowerManager.USER_ACTIVITY_EVENT_OTHER, 0 /* flags */);
-
-        advanceTime(520);
-        assertThat(mService.getGlobalWakefulnessLocked()).isNotEqualTo(WAKEFULNESS_ASLEEP);
-    }
-
-
-    @SuppressWarnings("GuardedBy")
-    @Test
-    public void testInattentiveSleep_goesToSleepFromDream() {
-        setAttentiveTimeout(20000);
-        createService();
-        startSystem();
-        setPluggedIn(true);
-        forceAwake();
-        forceDream();
-        when(mDreamManagerInternalMock.isDreaming()).thenReturn(true);
-        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING);
-
-        advanceTime(20500);
-        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
-    }
-
-    @Test
-    public void testWakeLock_affectsProperDisplayGroup() {
-        final int nonDefaultDisplayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
-        final AtomicReference<DisplayManagerInternal.DisplayGroupListener> listener =
-                new AtomicReference<>();
-        doAnswer((Answer<Void>) invocation -> {
-            listener.set(invocation.getArgument(0));
-            return null;
-        }).when(mDisplayManagerInternalMock).registerDisplayGroupListener(any());
-        final DisplayInfo info = new DisplayInfo();
-        info.displayGroupId = Display.DEFAULT_DISPLAY_GROUP;
-        when(mDisplayManagerInternalMock.getDisplayInfo(Display.DEFAULT_DISPLAY)).thenReturn(info);
-
-        final String pkg = mContextSpy.getOpPackageName();
-        final Binder token = new Binder();
-        final String tag = "testWakeLock_affectsProperDisplayGroup";
-
-        setMinimumScreenOffTimeoutConfig(5);
-        createService();
-        startSystem();
-        listener.get().onDisplayGroupAdded(nonDefaultDisplayGroupId);
-
-        mService.getBinderServiceInstance().acquireWakeLock(token,
-                PowerManager.SCREEN_BRIGHT_WAKE_LOCK, tag, pkg,
-                null /* workSource */, null /* historyTag */, Display.DEFAULT_DISPLAY, null);
-
-        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
-        assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
-                WAKEFULNESS_AWAKE);
-        assertThat(mService.getWakefulnessLocked(nonDefaultDisplayGroupId)).isEqualTo(
-                WAKEFULNESS_AWAKE);
-
-        advanceTime(15000);
-        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
-        assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
-                WAKEFULNESS_AWAKE);
-        assertThat(mService.getWakefulnessLocked(nonDefaultDisplayGroupId)).isEqualTo(
-                WAKEFULNESS_DOZING);
-    }
-
-    @Test
-    public void testInvalidDisplayGroupWakeLock_affectsAllDisplayGroups() {
-        final int nonDefaultDisplayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
-        final AtomicReference<DisplayManagerInternal.DisplayGroupListener> listener =
-                new AtomicReference<>();
-        doAnswer((Answer<Void>) invocation -> {
-            listener.set(invocation.getArgument(0));
-            return null;
-        }).when(mDisplayManagerInternalMock).registerDisplayGroupListener(any());
-        final DisplayInfo info = new DisplayInfo();
-        info.displayGroupId = Display.DEFAULT_DISPLAY_GROUP;
-        when(mDisplayManagerInternalMock.getDisplayInfo(Display.DEFAULT_DISPLAY)).thenReturn(info);
-
-        final String pkg = mContextSpy.getOpPackageName();
-        final Binder token = new Binder();
-        final String tag = "testInvalidDisplayGroupWakeLock_affectsAllDisplayGroups";
-
-        setMinimumScreenOffTimeoutConfig(5);
-        createService();
-        startSystem();
-        listener.get().onDisplayGroupAdded(nonDefaultDisplayGroupId);
-
-        mService.getBinderServiceInstance().acquireWakeLock(token,
-                PowerManager.SCREEN_BRIGHT_WAKE_LOCK, tag, pkg,
-                null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, null);
-
-        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
-        assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
-                WAKEFULNESS_AWAKE);
-        assertThat(mService.getWakefulnessLocked(nonDefaultDisplayGroupId)).isEqualTo(
-                WAKEFULNESS_AWAKE);
-
-        advanceTime(15000);
-        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
-        assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
-                WAKEFULNESS_AWAKE);
-        assertThat(mService.getWakefulnessLocked(nonDefaultDisplayGroupId)).isEqualTo(
-                WAKEFULNESS_AWAKE);
-    }
-
-    @Test
-    public void testRemovedDisplayGroupWakeLock_affectsNoDisplayGroups() {
-        final int nonDefaultDisplayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
-        final int nonDefaultDisplay = Display.DEFAULT_DISPLAY + 1;
-        final AtomicReference<DisplayManagerInternal.DisplayGroupListener> listener =
-                new AtomicReference<>();
-        doAnswer((Answer<Void>) invocation -> {
-            listener.set(invocation.getArgument(0));
-            return null;
-        }).when(mDisplayManagerInternalMock).registerDisplayGroupListener(any());
-        final DisplayInfo info = new DisplayInfo();
-        info.displayGroupId = nonDefaultDisplayGroupId;
-        when(mDisplayManagerInternalMock.getDisplayInfo(nonDefaultDisplay)).thenReturn(info);
-
-        doAnswer(inv -> {
-            when(mDreamManagerInternalMock.isDreaming()).thenReturn(true);
-            return null;
-        }).when(mDreamManagerInternalMock).startDream(anyBoolean(), anyString());
-
-        final String pkg = mContextSpy.getOpPackageName();
-        final Binder token = new Binder();
-        final String tag = "testRemovedDisplayGroupWakeLock_affectsNoDisplayGroups";
-
-        setMinimumScreenOffTimeoutConfig(5);
-        createService();
-        startSystem();
-        listener.get().onDisplayGroupAdded(nonDefaultDisplayGroupId);
-
-        mService.getBinderServiceInstance().acquireWakeLock(token,
-                PowerManager.SCREEN_BRIGHT_WAKE_LOCK, tag, pkg,
-                null /* workSource */, null /* historyTag */, nonDefaultDisplay, null);
-
-        assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
-                WAKEFULNESS_AWAKE);
-        assertThat(mService.getWakefulnessLocked(nonDefaultDisplayGroupId)).isEqualTo(
-                WAKEFULNESS_AWAKE);
-        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
-
-        listener.get().onDisplayGroupRemoved(nonDefaultDisplayGroupId);
-
-        advanceTime(15000);
-        assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
-                WAKEFULNESS_DOZING);
-        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DOZING);
-    }
-
-    @Test
-    public void testBoot_ShouldBeAwake() {
-        createService();
-        startSystem();
-
-        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
-        verify(mNotifierMock, never()).onGlobalWakefulnessChangeStarted(anyInt(), anyInt(),
-                anyLong());
-    }
-
-    @Test
-    public void testBoot_DesiredScreenPolicyShouldBeBright() {
-        createService();
-        startSystem();
-
-        assertThat(mService.getDesiredScreenPolicyLocked(Display.DEFAULT_DISPLAY)).isEqualTo(
-                DisplayPowerRequest.POLICY_BRIGHT);
-    }
-
-    @Test
-    public void testQuiescentBoot_ShouldBeAsleep() {
-        when(mSystemPropertiesMock.get(eq(SYSTEM_PROPERTY_QUIESCENT), any())).thenReturn("1");
-        createService();
-        startSystem();
-
-        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
-        verify(mNotifierMock).onGlobalWakefulnessChangeStarted(eq(WAKEFULNESS_ASLEEP), anyInt(),
-                anyLong());
-    }
-
-    @Test
-    public void testQuiescentBoot_DesiredScreenPolicyShouldBeOff() {
-        when(mSystemPropertiesMock.get(eq(SYSTEM_PROPERTY_QUIESCENT), any())).thenReturn("1");
-        createService();
-        startSystem();
-        assertThat(mService.getDesiredScreenPolicyLocked(Display.DEFAULT_DISPLAY)).isEqualTo(
-                DisplayPowerRequest.POLICY_OFF);
-    }
-
-    @Test
-    public void testQuiescentBoot_WakeUp_DesiredScreenPolicyShouldBeBright() {
-        when(mSystemPropertiesMock.get(eq(SYSTEM_PROPERTY_QUIESCENT), any())).thenReturn("1");
-        createService();
-        startSystem();
-        forceAwake();
-        assertThat(mService.getDesiredScreenPolicyLocked(Display.DEFAULT_DISPLAY)).isEqualTo(
-                DisplayPowerRequest.POLICY_BRIGHT);
-    }
-
-    @Test
-    public void testQuiescentBoot_WakeKeyBeforeBootCompleted_AwakeAfterBootCompleted() {
-        when(mSystemPropertiesMock.get(eq(SYSTEM_PROPERTY_QUIESCENT), any())).thenReturn("1");
-        createService();
-        startSystem();
-
-        mService.getBinderServiceInstance().wakeUp(mClock.now(),
-                PowerManager.WAKE_REASON_UNKNOWN, "testing IPowerManager.wakeUp()", "pkg.name");
-
-        mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
-        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
-        assertThat(mService.getDesiredScreenPolicyLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
-                DisplayPowerRequest.POLICY_BRIGHT);
-    }
-
-    @Test
-    public void testIsAmbientDisplayAvailable_available() {
-        createService();
-        when(mAmbientDisplayConfigurationMock.ambientDisplayAvailable()).thenReturn(true);
-
-        assertThat(mService.getBinderServiceInstance().isAmbientDisplayAvailable()).isTrue();
-    }
-
-    @Test
-    public void testIsAmbientDisplayAvailable_unavailable() {
-        createService();
-        when(mAmbientDisplayConfigurationMock.ambientDisplayAvailable()).thenReturn(false);
-
-        assertThat(mService.getBinderServiceInstance().isAmbientDisplayAvailable()).isFalse();
-    }
-
-    @Test
-    public void testIsAmbientDisplaySuppressed_default_notSuppressed() {
-        createService();
-
-        assertThat(mService.getBinderServiceInstance().isAmbientDisplaySuppressed()).isFalse();
-    }
-
-    @Test
-    public void testIsAmbientDisplaySuppressed_suppressed() {
-        createService();
-        mService.getBinderServiceInstance().suppressAmbientDisplay("test", true);
-
-        assertThat(mService.getBinderServiceInstance().isAmbientDisplaySuppressed()).isTrue();
-    }
-
-    @Test
-    public void testIsAmbientDisplaySuppressed_notSuppressed() {
-        createService();
-        mService.getBinderServiceInstance().suppressAmbientDisplay("test", false);
-
-        assertThat(mService.getBinderServiceInstance().isAmbientDisplaySuppressed()).isFalse();
-    }
-
-    @Test
-    public void testIsAmbientDisplaySuppressed_multipleTokens_suppressed() {
-        createService();
-        mService.getBinderServiceInstance().suppressAmbientDisplay("test1", false);
-        mService.getBinderServiceInstance().suppressAmbientDisplay("test2", true);
-
-        assertThat(mService.getBinderServiceInstance().isAmbientDisplaySuppressed()).isTrue();
-    }
-
-    @Test
-    public void testIsAmbientDisplaySuppressed_multipleTokens_notSuppressed() {
-        createService();
-        mService.getBinderServiceInstance().suppressAmbientDisplay("test1", false);
-        mService.getBinderServiceInstance().suppressAmbientDisplay("test2", false);
-
-        assertThat(mService.getBinderServiceInstance().isAmbientDisplaySuppressed()).isFalse();
-    }
-
-    @Test
-    public void testIsAmbientDisplaySuppressedForToken_default_notSuppressed() {
-        createService();
-
-        assertThat(mService.getBinderServiceInstance().isAmbientDisplaySuppressedForToken("test"))
-            .isFalse();
-    }
-
-    @Test
-    public void testIsAmbientDisplaySuppressedForToken_suppressed() {
-        createService();
-        mService.getBinderServiceInstance().suppressAmbientDisplay("test", true);
-
-        assertThat(mService.getBinderServiceInstance().isAmbientDisplaySuppressedForToken("test"))
-            .isTrue();
-    }
-
-    @Test
-    public void testIsAmbientDisplaySuppressedForToken_notSuppressed() {
-        createService();
-        mService.getBinderServiceInstance().suppressAmbientDisplay("test", false);
-
-        assertThat(mService.getBinderServiceInstance().isAmbientDisplaySuppressedForToken("test"))
-            .isFalse();
-    }
-
-    @Test
-    public void testIsAmbientDisplaySuppressedForToken_multipleTokens_suppressed() {
-        createService();
-        mService.getBinderServiceInstance().suppressAmbientDisplay("test1", true);
-        mService.getBinderServiceInstance().suppressAmbientDisplay("test2", true);
-
-        assertThat(mService.getBinderServiceInstance().isAmbientDisplaySuppressedForToken("test1"))
-            .isTrue();
-        assertThat(mService.getBinderServiceInstance().isAmbientDisplaySuppressedForToken("test2"))
-            .isTrue();
-    }
-
-    @Test
-    public void testIsAmbientDisplaySuppressedForToken_multipleTokens_notSuppressed() {
-        createService();
-        mService.getBinderServiceInstance().suppressAmbientDisplay("test1", true);
-        mService.getBinderServiceInstance().suppressAmbientDisplay("test2", false);
-
-        assertThat(mService.getBinderServiceInstance().isAmbientDisplaySuppressedForToken("test1"))
-            .isTrue();
-        assertThat(mService.getBinderServiceInstance().isAmbientDisplaySuppressedForToken("test2"))
-            .isFalse();
-    }
-
-    @Test
-    public void testIsAmbientDisplaySuppressedForTokenByApp_ambientDisplayUnavailable() {
-        createService();
-        when(mAmbientDisplayConfigurationMock.ambientDisplayAvailable()).thenReturn(false);
-
-        BinderService service = mService.getBinderServiceInstance();
-        assertThat(service.isAmbientDisplaySuppressedForTokenByApp("test", Binder.getCallingUid()))
-            .isFalse();
-    }
-
-    @Test
-    public void testIsAmbientDisplaySuppressedForTokenByApp_default() {
-        createService();
-
-        BinderService service = mService.getBinderServiceInstance();
-        assertThat(service.isAmbientDisplaySuppressedForTokenByApp("test", Binder.getCallingUid()))
-            .isFalse();
-    }
-
-    @Test
-    public void testIsAmbientDisplaySuppressedForTokenByApp_suppressedByCallingApp() {
-        createService();
-        BinderService service = mService.getBinderServiceInstance();
-        service.suppressAmbientDisplay("test", true);
-
-        assertThat(service.isAmbientDisplaySuppressedForTokenByApp("test", Binder.getCallingUid()))
-            .isTrue();
-        // Check that isAmbientDisplaySuppressedForTokenByApp doesn't return true for another app.
-        assertThat(service.isAmbientDisplaySuppressedForTokenByApp("test", /* appUid= */ 123))
-            .isFalse();
-    }
-
-    @Test
-    public void testIsAmbientDisplaySuppressedForTokenByApp_notSuppressedByCallingApp() {
-        createService();
-        BinderService service = mService.getBinderServiceInstance();
-        service.suppressAmbientDisplay("test", false);
-
-        assertThat(service.isAmbientDisplaySuppressedForTokenByApp("test", Binder.getCallingUid()))
-            .isFalse();
-        // Check that isAmbientDisplaySuppressedForTokenByApp doesn't return true for another app.
-        assertThat(service.isAmbientDisplaySuppressedForTokenByApp("test", /* appUid= */ 123))
-            .isFalse();
-    }
-
-    @Test
-    public void testIsAmbientDisplaySuppressedForTokenByApp_multipleTokensSuppressedByCallingApp() {
-        createService();
-        BinderService service = mService.getBinderServiceInstance();
-        service.suppressAmbientDisplay("test1", true);
-        service.suppressAmbientDisplay("test2", true);
-
-        assertThat(service.isAmbientDisplaySuppressedForTokenByApp("test1", Binder.getCallingUid()))
-            .isTrue();
-        assertThat(service.isAmbientDisplaySuppressedForTokenByApp("test2", Binder.getCallingUid()))
-            .isTrue();
-        // Check that isAmbientDisplaySuppressedForTokenByApp doesn't return true for another app.
-        assertThat(service.isAmbientDisplaySuppressedForTokenByApp("test1", /* appUid= */ 123))
-            .isFalse();
-        assertThat(service.isAmbientDisplaySuppressedForTokenByApp("test2", /* appUid= */ 123))
-            .isFalse();
-    }
-
-    @Test
-    public void testGetAmbientDisplaySuppressionTokens_default() {
-        createService();
-        BinderService service = mService.getBinderServiceInstance();
-
-        assertThat(service.getAmbientDisplaySuppressionTokens()).isEmpty();
-    }
-
-    @Test
-    public void testGetAmbientDisplaySuppressionTokens_singleToken() {
-        createService();
-        BinderService service = mService.getBinderServiceInstance();
-        service.suppressAmbientDisplay("test1", true);
-        service.suppressAmbientDisplay("test2", false);
-
-        assertThat(service.getAmbientDisplaySuppressionTokens()).containsExactly("test1");
-    }
-
-    @Test
-    public void testGetAmbientDisplaySuppressionTokens_multipleTokens() {
-        createService();
-        BinderService service = mService.getBinderServiceInstance();
-        service.suppressAmbientDisplay("test1", true);
-        service.suppressAmbientDisplay("test2", true);
-
-        assertThat(service.getAmbientDisplaySuppressionTokens())
-                .containsExactly("test1", "test2");
-    }
-
-    @Test
-    public void testSetPowerBoost_redirectsCallToNativeWrapper() {
-        createService();
-        startSystem();
-
-        mService.getBinderServiceInstance().setPowerBoost(Boost.INTERACTION, 1234);
-
-        verify(mNativeWrapperMock).nativeSetPowerBoost(eq(Boost.INTERACTION), eq(1234));
-    }
-
-    @Test
-    public void testSetPowerMode_redirectsCallToNativeWrapper() {
-        createService();
-        startSystem();
-
-        // Enabled launch boost in BatterySaverController to allow setting launch mode.
-        when(mBatterySaverControllerMock.isLaunchBoostDisabled()).thenReturn(false);
-        when(mNativeWrapperMock.nativeSetPowerMode(anyInt(), anyBoolean())).thenReturn(true);
-
-        mService.getBinderServiceInstance().setPowerMode(Mode.LAUNCH, true);
-
-        verify(mNativeWrapperMock).nativeSetPowerMode(eq(Mode.LAUNCH), eq(true));
-    }
-
-    @Test
-    public void testSetPowerMode_withLaunchBoostDisabledAndModeLaunch_ignoresCallToEnable() {
-        createService();
-        startSystem();
-
-        // Disables launch boost in BatterySaverController.
-        when(mBatterySaverControllerMock.isLaunchBoostDisabled()).thenReturn(true);
-        when(mNativeWrapperMock.nativeSetPowerMode(anyInt(), anyBoolean())).thenReturn(true);
-
-        mService.getBinderServiceInstance().setPowerMode(Mode.LAUNCH, true);
-        mService.getBinderServiceInstance().setPowerMode(Mode.LAUNCH, false);
-
-        verify(mNativeWrapperMock, never()).nativeSetPowerMode(eq(Mode.LAUNCH), eq(true));
-        verify(mNativeWrapperMock).nativeSetPowerMode(eq(Mode.LAUNCH), eq(false));
-    }
-
-    @Test
-    public void testSetPowerModeChecked_returnsNativeCallResult() {
-        createService();
-        startSystem();
-
-        // Disables launch boost in BatterySaverController.
-        when(mBatterySaverControllerMock.isLaunchBoostDisabled()).thenReturn(true);
-        when(mNativeWrapperMock.nativeSetPowerMode(anyInt(), anyBoolean())).thenReturn(true);
-        when(mNativeWrapperMock.nativeSetPowerMode(eq(Mode.INTERACTIVE), anyBoolean()))
-            .thenReturn(false);
-
-        // Ignored because isLaunchBoostDisabled is true. Should return false.
-        assertFalse(mService.getBinderServiceInstance().setPowerModeChecked(Mode.LAUNCH, true));
-        // Native calls return true.
-        assertTrue(mService.getBinderServiceInstance().setPowerModeChecked(Mode.LAUNCH, false));
-        assertTrue(mService.getBinderServiceInstance().setPowerModeChecked(Mode.LOW_POWER, true));
-        // Native call for interactive returns false.
-        assertFalse(
-                mService.getBinderServiceInstance().setPowerModeChecked(Mode.INTERACTIVE, false));
-    }
-
-    @Test
-    public void testMultiDisplay_wakefulnessUpdates() {
-        final int nonDefaultDisplayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
-        final AtomicReference<DisplayManagerInternal.DisplayGroupListener> listener =
-                new AtomicReference<>();
-        doAnswer((Answer<Void>) invocation -> {
-            listener.set(invocation.getArgument(0));
-            return null;
-        }).when(mDisplayManagerInternalMock).registerDisplayGroupListener(any());
-
-        createService();
-        startSystem();
-        listener.get().onDisplayGroupAdded(nonDefaultDisplayGroupId);
-
-        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
-
-        mService.setWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP, WAKEFULNESS_ASLEEP, 0, 0, 0, 0,
-                null, null);
-        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
-
-        mService.setWakefulnessLocked(nonDefaultDisplayGroupId, WAKEFULNESS_ASLEEP, 0, 0, 0, 0,
-                null, null);
-        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
-
-        mService.setWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP, WAKEFULNESS_AWAKE, 0, 0, 0, 0,
-                null, null);
-        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
-    }
-
-    @Test
-    public void testMultiDisplay_addDisplayGroup_wakesDeviceUp() {
-        final int nonDefaultDisplayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
-        final AtomicReference<DisplayManagerInternal.DisplayGroupListener> listener =
-                new AtomicReference<>();
-        doAnswer((Answer<Void>) invocation -> {
-            listener.set(invocation.getArgument(0));
-            return null;
-        }).when(mDisplayManagerInternalMock).registerDisplayGroupListener(any());
-
-        createService();
-        startSystem();
-
-        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
-
-        mService.setWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP, WAKEFULNESS_ASLEEP, 0, 0, 0, 0,
-                null, null);
-        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
-
-        listener.get().onDisplayGroupAdded(nonDefaultDisplayGroupId);
-
-        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
-    }
-
-    @Test
-    public void testMultiDisplay_removeDisplayGroup_updatesWakefulness() {
-        final int nonDefaultDisplayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
-        final AtomicReference<DisplayManagerInternal.DisplayGroupListener> listener =
-                new AtomicReference<>();
-        doAnswer((Answer<Void>) invocation -> {
-            listener.set(invocation.getArgument(0));
-            return null;
-        }).when(mDisplayManagerInternalMock).registerDisplayGroupListener(any());
-
-        createService();
-        startSystem();
-        listener.get().onDisplayGroupAdded(nonDefaultDisplayGroupId);
-
-        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
-
-        mService.setWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP, WAKEFULNESS_ASLEEP, 0, 0, 0, 0,
-                null, null);
-        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
-
-        listener.get().onDisplayGroupRemoved(nonDefaultDisplayGroupId);
-        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
-
-        mService.setWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP, WAKEFULNESS_AWAKE, 0, 0, 0, 0,
-                null, null);
-        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
-    }
-
-    @Test
-    public void testMultiDisplay_updatesLastGlobalWakeTime() {
-        final int nonDefaultPowerGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
-        final AtomicReference<DisplayManagerInternal.DisplayGroupListener> listener =
-                new AtomicReference<>();
-        long eventTime1 = 10;
-        long eventTime2 = eventTime1 + 1;
-        long eventTime3 = eventTime2 + 1;
-        long eventTime4 = eventTime3 + 1;
-        doAnswer((Answer<Void>) invocation -> {
-            listener.set(invocation.getArgument(0));
-            return null;
-        }).when(mDisplayManagerInternalMock).registerDisplayGroupListener(any());
-
-        createService();
-        startSystem();
-        listener.get().onDisplayGroupAdded(nonDefaultPowerGroupId);
-
-        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
-
-        mService.setWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP, WAKEFULNESS_DOZING, eventTime1,
-                0, PowerManager.GO_TO_SLEEP_REASON_INATTENTIVE, 0, null, null);
-        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
-
-        mService.setWakefulnessLocked(nonDefaultPowerGroupId, WAKEFULNESS_DOZING, eventTime2,
-                0, PowerManager.GO_TO_SLEEP_REASON_APPLICATION, 0, null, null);
-        long eventElapsedRealtime1 = mLastElapsedRealtime;
-        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DOZING);
-        assertThat(mService.getBinderServiceInstance().getLastSleepReason()).isEqualTo(
-                PowerManager.GO_TO_SLEEP_REASON_APPLICATION);
-
-        mService.setWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP, WAKEFULNESS_AWAKE,
-                eventTime3, /* uid= */ 0, PowerManager.WAKE_REASON_PLUGGED_IN, /* opUid= */
-                0, /* opPackageName= */ null, /* details= */ null);
-        long eventElapsedRealtime2 = mLastElapsedRealtime;
-        PowerManager.WakeData wakeData = mService.getLocalServiceInstance().getLastWakeup();
-        assertThat(wakeData.wakeTime).isEqualTo(eventTime3);
-        assertThat(wakeData.wakeReason).isEqualTo(PowerManager.WAKE_REASON_PLUGGED_IN);
-        assertThat(wakeData.sleepDurationRealtime)
-                .isEqualTo(eventElapsedRealtime2 - eventElapsedRealtime1);
-
-        // The global wake time and reason as well as sleep duration shouldn't change when another
-        // PowerGroup wakes up.
-        mService.setWakefulnessLocked(nonDefaultPowerGroupId, WAKEFULNESS_AWAKE,
-                eventTime4, /* uid= */ 0, PowerManager.WAKE_REASON_CAMERA_LAUNCH, /* opUid= */
-                0, /* opPackageName= */ null, /* details= */ null);
-        PowerManager.WakeData wakeData2 = mService.getLocalServiceInstance().getLastWakeup();
-        assertThat(wakeData2).isEqualTo(wakeData);
-    }
-
-    @Test
-    public void testMultiDisplay_defaultDisplayCanDoze() {
-        createService();
-        startSystem();
-        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
-        assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
-                WAKEFULNESS_AWAKE);
-
-        forceDozing();
-        // Allow handleSandman() to be called asynchronously
-        advanceTime(500);
-        verify(mDreamManagerInternalMock).startDream(eq(true), anyString());
-    }
-
-    @Test
-    public void testMultiDisplay_twoDisplays_defaultDisplayCanDoze() {
-        final int nonDefaultDisplayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
-        final int nonDefaultDisplay = Display.DEFAULT_DISPLAY + 1;
-        final AtomicReference<DisplayManagerInternal.DisplayGroupListener> listener =
-                new AtomicReference<>();
-        doAnswer((Answer<Void>) invocation -> {
-            listener.set(invocation.getArgument(0));
-            return null;
-        }).when(mDisplayManagerInternalMock).registerDisplayGroupListener(any());
-        final DisplayInfo info = new DisplayInfo();
-        info.displayGroupId = nonDefaultDisplayGroupId;
-        when(mDisplayManagerInternalMock.getDisplayInfo(nonDefaultDisplay)).thenReturn(info);
-
-        createService();
-        startSystem();
-
-        listener.get().onDisplayGroupAdded(nonDefaultDisplayGroupId);
-
-        assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
-                WAKEFULNESS_AWAKE);
-        assertThat(mService.getWakefulnessLocked(nonDefaultDisplayGroupId)).isEqualTo(
-                WAKEFULNESS_AWAKE);
-        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
-
-        forceDozing();
-
-        assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
-                WAKEFULNESS_DOZING);
-        assertThat(mService.getWakefulnessLocked(nonDefaultDisplayGroupId)).isEqualTo(
-                WAKEFULNESS_AWAKE);
-        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
-
-        // Allow handleSandman() to be called asynchronously
-        advanceTime(500);
-        verify(mDreamManagerInternalMock).startDream(eq(true), anyString());
-    }
-
-    @Test
-    public void testMultiDisplay_addNewDisplay_becomeGloballyAwakeButDefaultRemainsDozing() {
-        final int nonDefaultDisplayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
-        final int nonDefaultDisplay = Display.DEFAULT_DISPLAY + 1;
-        final AtomicReference<DisplayManagerInternal.DisplayGroupListener> listener =
-                new AtomicReference<>();
-        doAnswer((Answer<Void>) invocation -> {
-            listener.set(invocation.getArgument(0));
-            return null;
-        }).when(mDisplayManagerInternalMock).registerDisplayGroupListener(any());
-        final DisplayInfo info = new DisplayInfo();
-        info.displayGroupId = nonDefaultDisplayGroupId;
-        when(mDisplayManagerInternalMock.getDisplayInfo(nonDefaultDisplay)).thenReturn(info);
-
-        doAnswer(inv -> {
-            when(mDreamManagerInternalMock.isDreaming()).thenReturn(true);
-            return null;
-        }).when(mDreamManagerInternalMock).startDream(anyBoolean(), anyString());
-
-        createService();
-        startSystem();
-
-        assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
-                WAKEFULNESS_AWAKE);
-
-        forceDozing();
-        advanceTime(500);
-
-        assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
-                WAKEFULNESS_DOZING);
-        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DOZING);
-        verify(mDreamManagerInternalMock).stopDream(anyBoolean(), anyString());
-        verify(mDreamManagerInternalMock).startDream(eq(true), anyString());
-
-        listener.get().onDisplayGroupAdded(nonDefaultDisplayGroupId);
-        advanceTime(500);
-
-        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
-        assertThat(mService.getWakefulnessLocked(nonDefaultDisplayGroupId)).isEqualTo(
-                WAKEFULNESS_AWAKE);
-        assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
-                WAKEFULNESS_DOZING);
-
-        // Make sure there were no additional calls to stopDream or startDream
-        verify(mDreamManagerInternalMock, atMost(1)).stopDream(anyBoolean(), anyString());
-        verify(mDreamManagerInternalMock, atMost(1)).startDream(eq(true), anyString());
-    }
-
-    @Test
-    public void testLastSleepTime_notUpdatedWhenDreaming() {
-        createService();
-        startSystem();
-
-        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
-        PowerManager.WakeData initialWakeData = mService.getLocalServiceInstance().getLastWakeup();
-
-        forceDream();
-        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING);
-        assertThat(mService.getLocalServiceInstance().getLastWakeup()).isEqualTo(initialWakeData);
-    }
-
-    @Test
-    public void testMultiDisplay_onlyOneDisplaySleeps_onWakefulnessChangedEventsFire() {
-        createService();
-        startSystem();
-        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
-        forceSleep();
-        assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
-                WAKEFULNESS_ASLEEP);
-
-        verify(mNotifierMock).onGroupWakefulnessChangeStarted(eq(Display.DEFAULT_DISPLAY_GROUP),
-                eq(WAKEFULNESS_ASLEEP), eq(PowerManager.GO_TO_SLEEP_REASON_APPLICATION), anyLong());
-        verify(mNotifierMock).onGlobalWakefulnessChangeStarted(eq(WAKEFULNESS_ASLEEP),
-                eq(PowerManager.GO_TO_SLEEP_REASON_APPLICATION), anyLong());
-    }
-
-    @Test
-    public void testMultiDisplay_bothDisplaysSleep_onWakefulnessChangedEventsFireCorrectly() {
-        final int nonDefaultDisplayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
-        final int nonDefaultDisplay = Display.DEFAULT_DISPLAY + 1;
-        final AtomicReference<DisplayManagerInternal.DisplayGroupListener> listener =
-                new AtomicReference<>();
-        doAnswer((Answer<Void>) invocation -> {
-            listener.set(invocation.getArgument(0));
-            return null;
-        }).when(mDisplayManagerInternalMock).registerDisplayGroupListener(any());
-        final DisplayInfo info = new DisplayInfo();
-        info.displayGroupId = nonDefaultDisplayGroupId;
-        when(mDisplayManagerInternalMock.getDisplayInfo(nonDefaultDisplay)).thenReturn(info);
-
-        createService();
-        startSystem();
-
-        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
-        listener.get().onDisplayGroupAdded(nonDefaultDisplayGroupId);
-
-        mService.setWakefulnessLocked(nonDefaultDisplayGroupId, WAKEFULNESS_ASLEEP, 0, 0,
-                PowerManager.GO_TO_SLEEP_REASON_APPLICATION, 0, null, null);
-        mService.setWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP, WAKEFULNESS_ASLEEP, 0, 0,
-                PowerManager.GO_TO_SLEEP_REASON_APPLICATION, 0, null, null);
-
-        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
-        assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
-                WAKEFULNESS_ASLEEP);
-        assertThat(mService.getWakefulnessLocked(nonDefaultDisplayGroupId)).isEqualTo(
-                WAKEFULNESS_ASLEEP);
-
-        verify(mNotifierMock).onGroupWakefulnessChangeStarted(eq(nonDefaultDisplayGroupId),
-                eq(WAKEFULNESS_ASLEEP), eq(PowerManager.GO_TO_SLEEP_REASON_APPLICATION), anyLong());
-        verify(mNotifierMock).onGroupWakefulnessChangeStarted(eq(Display.DEFAULT_DISPLAY_GROUP),
-                eq(WAKEFULNESS_ASLEEP), eq(PowerManager.GO_TO_SLEEP_REASON_APPLICATION), anyLong());
-        verify(mNotifierMock).onGlobalWakefulnessChangeStarted(eq(WAKEFULNESS_ASLEEP),
-                eq(PowerManager.GO_TO_SLEEP_REASON_APPLICATION), anyLong());
-    }
-
-    @Test
-    public void testMultiDisplay_separateWakeStates_onWakefulnessChangedEventsFireCorrectly() {
-        final int nonDefaultDisplayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
-        final int nonDefaultDisplay = Display.DEFAULT_DISPLAY + 1;
-        final AtomicReference<DisplayManagerInternal.DisplayGroupListener> listener =
-                new AtomicReference<>();
-        doAnswer((Answer<Void>) invocation -> {
-            listener.set(invocation.getArgument(0));
-            return null;
-        }).when(mDisplayManagerInternalMock).registerDisplayGroupListener(any());
-        final DisplayInfo info = new DisplayInfo();
-        info.displayGroupId = nonDefaultDisplayGroupId;
-        when(mDisplayManagerInternalMock.getDisplayInfo(nonDefaultDisplay)).thenReturn(info);
-
-        createService();
-        startSystem();
-
-        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
-        listener.get().onDisplayGroupAdded(nonDefaultDisplayGroupId);
-
-        final String pkg = mContextSpy.getOpPackageName();
-        final Binder token = new Binder();
-        final String tag =
-                "testMultiDisplay_separateWakeStates_onWakefulnessChangedEventFiresCorrectly";
-        mService.getBinderServiceInstance().acquireWakeLock(token,
-                PowerManager.SCREEN_BRIGHT_WAKE_LOCK, tag, pkg,
-                null /* workSource */, null /* historyTag */, nonDefaultDisplay, null);
-
-        forceSleep();
-
-        // The wakelock should have kept the second display awake, and we should notify that the
-        // default display went to sleep.
-        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
-        assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
-                WAKEFULNESS_ASLEEP);
-        assertThat(mService.getWakefulnessLocked(nonDefaultDisplayGroupId)).isEqualTo(
-                WAKEFULNESS_AWAKE);
-        verify(mNotifierMock).onGroupWakefulnessChangeStarted(eq(nonDefaultDisplayGroupId),
-                eq(WAKEFULNESS_AWAKE), eq(PowerManager.WAKE_REASON_DISPLAY_GROUP_ADDED), anyLong());
-        verify(mNotifierMock).onGroupWakefulnessChangeStarted(eq(Display.DEFAULT_DISPLAY_GROUP),
-                eq(WAKEFULNESS_ASLEEP), eq(PowerManager.GO_TO_SLEEP_REASON_APPLICATION), anyLong());
-        verify(mNotifierMock, never()).onGlobalWakefulnessChangeStarted(eq(WAKEFULNESS_ASLEEP),
-                anyInt(), anyLong());
-    }
-
-    @Test
-    public void testMultiDisplay_oneDisplayGroupChanges_globalDoesNotChange() {
-        final int nonDefaultDisplayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
-        final int nonDefaultDisplay = Display.DEFAULT_DISPLAY + 1;
-        final AtomicReference<DisplayManagerInternal.DisplayGroupListener> listener =
-                new AtomicReference<>();
-        doAnswer((Answer<Void>) invocation -> {
-            listener.set(invocation.getArgument(0));
-            return null;
-        }).when(mDisplayManagerInternalMock).registerDisplayGroupListener(any());
-        final DisplayInfo info = new DisplayInfo();
-        info.displayGroupId = nonDefaultDisplayGroupId;
-        when(mDisplayManagerInternalMock.getDisplayInfo(nonDefaultDisplay)).thenReturn(info);
-
-        createService();
-        startSystem();
-
-        listener.get().onDisplayGroupAdded(nonDefaultDisplayGroupId);
-
-        assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
-                WAKEFULNESS_AWAKE);
-        assertThat(mService.getWakefulnessLocked(nonDefaultDisplayGroupId)).isEqualTo(
-                WAKEFULNESS_AWAKE);
-        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
-
-        long eventTime = mClock.now();
-        mService.setWakefulnessLocked(nonDefaultDisplayGroupId, WAKEFULNESS_ASLEEP, eventTime, 0,
-                PowerManager.GO_TO_SLEEP_REASON_APPLICATION, 0, null, null);
-
-        assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
-                WAKEFULNESS_AWAKE);
-        assertThat(mService.getWakefulnessLocked(nonDefaultDisplayGroupId)).isEqualTo(
-                WAKEFULNESS_ASLEEP);
-        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
-        verify(mNotifierMock, never()).onGlobalWakefulnessChangeStarted(anyInt(), anyInt(),
-                anyLong());
-        verify(mNotifierMock, atMost(1)).onGroupWakefulnessChangeStarted(
-                eq(nonDefaultDisplayGroupId), eq(WAKEFULNESS_ASLEEP),
-                eq(PowerManager.GO_TO_SLEEP_REASON_APPLICATION), eq(eventTime));
-    }
-
-    @Test
-    public void testMultiDisplay_isInteractive_nonExistentGroup() {
-        createService();
-        startSystem();
-
-        int nonExistentDisplayGroup = 999;
-        BinderService binderService = mService.getBinderServiceInstance();
-        assertThat(binderService.isDisplayInteractive(nonExistentDisplayGroup)).isFalse();
-    }
-
-    private void testMultiDisplay_isInteractive_returnsCorrectValue(
-            boolean defaultDisplayAwake, boolean secondGroupDisplayAwake) {
-        final int nonDefaultDisplayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
-        // We use a display id that does not match the group id, to make sure we aren't accidentally
-        // confusing display id's and display group id's in the implementation.
-        final int nonDefaultDisplay = Display.DEFAULT_DISPLAY + 17;
-        final AtomicReference<DisplayManagerInternal.DisplayGroupListener> listener =
-                new AtomicReference<>();
-        doAnswer((Answer<Void>) invocation -> {
-            listener.set(invocation.getArgument(0));
-            return null;
-        }).when(mDisplayManagerInternalMock).registerDisplayGroupListener(any());
-
-        final DisplayInfo defaultDisplayInfo = new DisplayInfo();
-        defaultDisplayInfo.displayGroupId = Display.DEFAULT_DISPLAY_GROUP;
-        when(mDisplayManagerInternalMock.getDisplayInfo(Display.DEFAULT_DISPLAY)).thenReturn(
-                defaultDisplayInfo);
-
-        final DisplayInfo secondDisplayInfo = new DisplayInfo();
-        secondDisplayInfo.displayGroupId = nonDefaultDisplayGroupId;
-        when(mDisplayManagerInternalMock.getDisplayInfo(nonDefaultDisplay)).thenReturn(
-                secondDisplayInfo);
-
-        createService();
-        startSystem();
-        listener.get().onDisplayGroupAdded(nonDefaultDisplayGroupId);
-
-        if (!defaultDisplayAwake) {
-            mService.setWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP, WAKEFULNESS_ASLEEP,
-                    mClock.now(), 0, PowerManager.GO_TO_SLEEP_REASON_APPLICATION, 0, null, null);
-        }
-        if (!secondGroupDisplayAwake) {
-            mService.setWakefulnessLocked(nonDefaultDisplayGroupId, WAKEFULNESS_ASLEEP,
-                    mClock.now(), 0,
-                    PowerManager.GO_TO_SLEEP_REASON_APPLICATION, 0, null, null);
-        }
-        assertThat(PowerManagerInternal.isInteractive(
-                mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP))).isEqualTo(
-                defaultDisplayAwake);
-        assertThat(PowerManagerInternal.isInteractive(
-                mService.getWakefulnessLocked(nonDefaultDisplayGroupId))).isEqualTo(
-                secondGroupDisplayAwake);
-
-        BinderService binderService = mService.getBinderServiceInstance();
-        assertThat(binderService.isInteractive()).isEqualTo(
-                defaultDisplayAwake || secondGroupDisplayAwake);
-        assertThat(binderService.isDisplayInteractive(Display.DEFAULT_DISPLAY)).isEqualTo(
-                defaultDisplayAwake);
-        assertThat(binderService.isDisplayInteractive(nonDefaultDisplay)).isEqualTo(
-                secondGroupDisplayAwake);
-    }
-
-    @Test
-    public void testMultiDisplay_isInteractive_defaultGroupIsAwakeSecondGroupIsAwake() {
-        testMultiDisplay_isInteractive_returnsCorrectValue(true, true);
-    }
-
-    @Test
-    public void testMultiDisplay_isInteractive_defaultGroupIsAwakeSecondGroupIsAsleep() {
-        testMultiDisplay_isInteractive_returnsCorrectValue(true, false);
-    }
-
-    @Test
-    public void testMultiDisplay_isInteractive_defaultGroupIsAsleepSecondGroupIsAwake() {
-        testMultiDisplay_isInteractive_returnsCorrectValue(false, true);
-    }
-
-    @Test
-    public void testMultiDisplay_isInteractive_bothGroupsAreAsleep() {
-        testMultiDisplay_isInteractive_returnsCorrectValue(false, false);
-    }
-
-    @Test
-    public void testMultiDisplay_defaultGroupWakefulnessChange_causesIsInteractiveInvalidate()
-            throws Exception {
-        final int nonDefaultDisplayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
-        final int nonDefaultDisplay = Display.DEFAULT_DISPLAY + 1;
-        final AtomicReference<DisplayManagerInternal.DisplayGroupListener> listener =
-                new AtomicReference<>();
-        doAnswer((Answer<Void>) invocation -> {
-            listener.set(invocation.getArgument(0));
-            return null;
-        }).when(mDisplayManagerInternalMock).registerDisplayGroupListener(any());
-        final DisplayInfo info = new DisplayInfo();
-        info.displayGroupId = nonDefaultDisplayGroupId;
-        when(mDisplayManagerInternalMock.getDisplayInfo(nonDefaultDisplay)).thenReturn(info);
-        createService();
-        startSystem();
-        listener.get().onDisplayGroupAdded(nonDefaultDisplayGroupId);
-
-        verify(mInvalidateInteractiveCachesMock).call();
-
-        mService.setWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP, WAKEFULNESS_ASLEEP,
-                mClock.now(), 0, PowerManager.GO_TO_SLEEP_REASON_APPLICATION, 0, null, null);
-
-        verify(mInvalidateInteractiveCachesMock, times(2)).call();
-    }
-
-    @Test
-    public void testMultiDisplay_secondGroupWakefulness_causesIsInteractiveInvalidate()
-            throws Exception {
-        final int nonDefaultDisplayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
-        final int nonDefaultDisplay = Display.DEFAULT_DISPLAY + 1;
-        final AtomicReference<DisplayManagerInternal.DisplayGroupListener> listener =
-                new AtomicReference<>();
-        doAnswer((Answer<Void>) invocation -> {
-            listener.set(invocation.getArgument(0));
-            return null;
-        }).when(mDisplayManagerInternalMock).registerDisplayGroupListener(any());
-        final DisplayInfo info = new DisplayInfo();
-        info.displayGroupId = nonDefaultDisplayGroupId;
-        when(mDisplayManagerInternalMock.getDisplayInfo(nonDefaultDisplay)).thenReturn(info);
-        createService();
-        startSystem();
-        listener.get().onDisplayGroupAdded(nonDefaultDisplayGroupId);
-
-        verify(mInvalidateInteractiveCachesMock).call();
-
-        mService.setWakefulnessLocked(nonDefaultDisplayGroupId, WAKEFULNESS_ASLEEP, mClock.now(),
-                0, PowerManager.GO_TO_SLEEP_REASON_APPLICATION, 0, null, null);
-
-        verify(mInvalidateInteractiveCachesMock, times(2)).call();
-    }
-
-    @Test
-    public void testGetFullPowerSavePolicy_returnsStateMachineResult() {
-        createService();
-        startSystem();
-        BatterySaverPolicyConfig mockReturnConfig = new BatterySaverPolicyConfig.Builder().build();
-        when(mBatterySaverStateMachineMock.getFullBatterySaverPolicy())
-                .thenReturn(mockReturnConfig);
-
-        mService.getBinderServiceInstance().setPowerSaveModeEnabled(true);
-        BatterySaverPolicyConfig policyConfig =
-                mService.getBinderServiceInstance().getFullPowerSavePolicy();
-        assertThat(mockReturnConfig).isEqualTo(policyConfig);
-        verify(mBatterySaverStateMachineMock).getFullBatterySaverPolicy();
-    }
-
-    @Test
-    public void testSetFullPowerSavePolicy_callsStateMachine() {
-        createService();
-        startSystem();
-        BatterySaverPolicyConfig mockSetPolicyConfig =
-                new BatterySaverPolicyConfig.Builder().build();
-        when(mBatterySaverStateMachineMock.setFullBatterySaverPolicy(any())).thenReturn(true);
-
-        mService.getBinderServiceInstance().setPowerSaveModeEnabled(true);
-        assertThat(mService.getBinderServiceInstance()
-                .setFullPowerSavePolicy(mockSetPolicyConfig)).isTrue();
-        verify(mBatterySaverStateMachineMock).setFullBatterySaverPolicy(eq(mockSetPolicyConfig));
-    }
-
-    @Test
-    public void testLowPowerStandby_whenInactive_FgsWakeLockEnabled() {
-        createService();
-        startSystem();
-        WakeLock wakeLock = acquireWakeLock("fgsWakeLock", PowerManager.PARTIAL_WAKE_LOCK);
-        mService.updateUidProcStateInternal(wakeLock.mOwnerUid, PROCESS_STATE_FOREGROUND_SERVICE);
-        mService.setDeviceIdleModeInternal(true);
-
-        assertThat(wakeLock.mDisabled).isFalse();
-    }
-
-    @Test
-    public void testLowPowerStandby_whenActive_FgsWakeLockDisabled() {
-        createService();
-        startSystem();
-        WakeLock wakeLock = acquireWakeLock("fgsWakeLock", PowerManager.PARTIAL_WAKE_LOCK);
-        mService.updateUidProcStateInternal(wakeLock.mOwnerUid, PROCESS_STATE_FOREGROUND_SERVICE);
-        mService.setDeviceIdleModeInternal(true);
-        mService.setLowPowerStandbyActiveInternal(true);
-
-        assertThat(wakeLock.mDisabled).isTrue();
-    }
-
-    @Test
-    public void testLowPowerStandby_whenActive_FgsWakeLockEnabledIfAllowlisted() {
-        createService();
-        startSystem();
-        WakeLock wakeLock = acquireWakeLock("fgsWakeLock", PowerManager.PARTIAL_WAKE_LOCK);
-        mService.updateUidProcStateInternal(wakeLock.mOwnerUid, PROCESS_STATE_FOREGROUND_SERVICE);
-        mService.setDeviceIdleModeInternal(true);
-        mService.setLowPowerStandbyActiveInternal(true);
-        mService.setLowPowerStandbyAllowlistInternal(new int[]{wakeLock.mOwnerUid});
-
-        assertThat(wakeLock.mDisabled).isFalse();
-    }
-
-    @Test
-    public void testLowPowerStandby_whenActive_BoundTopWakeLockDisabled() {
-        createService();
-        startSystem();
-        WakeLock wakeLock = acquireWakeLock("BoundTopWakeLock", PowerManager.PARTIAL_WAKE_LOCK);
-        mService.updateUidProcStateInternal(wakeLock.mOwnerUid, PROCESS_STATE_BOUND_TOP);
-        mService.setDeviceIdleModeInternal(true);
-        mService.setLowPowerStandbyActiveInternal(true);
-
-        assertThat(wakeLock.mDisabled).isFalse();
-    }
-
-    @Test
-    public void testSetLowPowerStandbyActiveDuringMaintenance_redirectsCallToNativeWrapper() {
-        createService();
-        startSystem();
-
-        mService.getBinderServiceInstance().setLowPowerStandbyActiveDuringMaintenance(true);
-        verify(mLowPowerStandbyControllerMock).setActiveDuringMaintenance(true);
-
-        mService.getBinderServiceInstance().setLowPowerStandbyActiveDuringMaintenance(false);
-        verify(mLowPowerStandbyControllerMock).setActiveDuringMaintenance(false);
-    }
-
-    private WakeLock acquireWakeLock(String tag, int flags) {
-        IBinder token = new Binder();
-        String packageName = "pkg.name";
-        mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName,
-                null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY,
-                null /* callback */);
-        return mService.findWakeLockLocked(token);
-    }
-
-    /**
-     * Test IPowerManager.acquireWakeLock() with a IWakeLockCallback.
-     */
-    @Test
-    public void testNotifyWakeLockCallback() {
-        createService();
-        startSystem();
-        final String tag = "wakelock1";
-        final String packageName = "pkg.name";
-        final IBinder token = new Binder();
-        final int flags = PowerManager.PARTIAL_WAKE_LOCK;
-        final IWakeLockCallback callback = Mockito.mock(IWakeLockCallback.class);
-        final IBinder callbackBinder = Mockito.mock(Binder.class);
-        when(callback.asBinder()).thenReturn(callbackBinder);
-        mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName,
-                null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, callback);
-        verify(mNotifierMock).onWakeLockAcquired(anyInt(), eq(tag), eq(packageName), anyInt(),
-                anyInt(), any(), any(), same(callback));
-
-        mService.getBinderServiceInstance().releaseWakeLock(token, 0);
-        verify(mNotifierMock).onWakeLockReleased(anyInt(), eq(tag), eq(packageName), anyInt(),
-                anyInt(), any(), any(), same(callback));
-    }
-
-    /**
-     * Test IPowerManager.updateWakeLockCallback() with a new IWakeLockCallback.
-     */
-    @Test
-    public void testNotifyWakeLockCallbackChange() {
-        createService();
-        startSystem();
-        final String tag = "wakelock1";
-        final String packageName = "pkg.name";
-        final IBinder token = new Binder();
-        int flags = PowerManager.PARTIAL_WAKE_LOCK;
-        final IWakeLockCallback callback1 = Mockito.mock(IWakeLockCallback.class);
-        final IBinder callbackBinder1 = Mockito.mock(Binder.class);
-        when(callback1.asBinder()).thenReturn(callbackBinder1);
-        mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName,
-                null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, callback1);
-        verify(mNotifierMock).onWakeLockAcquired(anyInt(), eq(tag), eq(packageName), anyInt(),
-                anyInt(), any(), any(), same(callback1));
-
-        final IWakeLockCallback callback2 = Mockito.mock(IWakeLockCallback.class);
-        final IBinder callbackBinder2 = Mockito.mock(Binder.class);
-        when(callback2.asBinder()).thenReturn(callbackBinder2);
-        mService.getBinderServiceInstance().updateWakeLockCallback(token, callback2);
-        verify(mNotifierMock).onWakeLockChanging(anyInt(), eq(tag), eq(packageName),
-                anyInt(), anyInt(), any(), any(), same(callback1),
-                anyInt(), eq(tag), eq(packageName), anyInt(), anyInt(), any(), any(),
-                same(callback2));
-
-        mService.getBinderServiceInstance().releaseWakeLock(token, 0);
-        verify(mNotifierMock).onWakeLockReleased(anyInt(), eq(tag), eq(packageName), anyInt(),
-                anyInt(), any(), any(), same(callback2));
-    }
-
-    @Test
-    public void testUserActivity_futureEventsAreIgnored() {
-        createService();
-        startSystem();
-        // Starting the system triggers a user activity event, so clear that before calling
-        // userActivity() directly.
-        clearInvocations(mNotifierMock);
-        final long eventTime = mClock.now() + Duration.ofHours(10).toMillis();
-        mService.getBinderServiceInstance().userActivity(Display.DEFAULT_DISPLAY, eventTime,
-                USER_ACTIVITY_EVENT_BUTTON, /* flags= */ 0);
-        verify(mNotifierMock, never()).onUserActivity(anyInt(),  anyInt(), anyInt());
-    }
-
-}
diff --git a/services/tests/servicestests/src/com/android/server/power/ShutdownCheckPointsTest.java b/services/tests/servicestests/src/com/android/server/power/ShutdownCheckPointsTest.java
deleted file mode 100644
index 2bde51b..0000000
--- a/services/tests/servicestests/src/com/android/server/power/ShutdownCheckPointsTest.java
+++ /dev/null
@@ -1,414 +0,0 @@
-/*
- * 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.
- */
-
-package com.android.server.power;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.when;
-
-import android.app.ActivityManager;
-import android.app.IActivityManager;
-import android.os.Process;
-import android.os.RemoteException;
-import android.platform.test.annotations.Presubmit;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.io.StringWriter;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.nio.file.Paths;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Locale;
-import java.util.TimeZone;
-
-/**
- * Run: atest FrameworksServicesTests:ShutdownCheckPointsTest
- */
-@Presubmit
-public class ShutdownCheckPointsTest {
-
-    @Rule
-    public MockitoRule rule = MockitoJUnit.rule();
-
-    @Mock
-    private IActivityManager mActivityManager;
-
-    private TestInjector mTestInjector;
-    private ShutdownCheckPoints mInstance;
-
-    @Before
-    public void setUp() {
-        Locale.setDefault(Locale.UK);
-        TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
-        mTestInjector = new TestInjector(mActivityManager);
-        mInstance = new ShutdownCheckPoints(mTestInjector);
-    }
-
-    @Test
-    public void testSystemServerEntry() {
-        mTestInjector.setCurrentTime(1000);
-        mInstance.recordCheckPointInternal("reason1");
-
-        assertTrue(dumpToString().startsWith(
-                "Shutdown request from SYSTEM for reason reason1 "
-                        + "at 1970-01-01 00:00:01.000 UTC (epoch=1000)\n"
-                        + "com.android.server.power.ShutdownCheckPointsTest"
-                        + ".testSystemServerEntry\n at "));
-    }
-
-    @Test
-    public void testSystemServerEntryWithoutReason() {
-        mTestInjector.setCurrentTime(1000);
-        mInstance.recordCheckPointInternal(null);
-
-        assertTrue(dumpToString().startsWith(
-                "Shutdown request from SYSTEM at 1970-01-01 00:00:01.000 UTC (epoch=1000)\n"));
-    }
-
-    @Test
-    public void testSystemServiceBinderEntry() {
-        mTestInjector.setCurrentTime(1000);
-        mInstance.recordCheckPointInternal(Process.myPid(), "reason1");
-
-        assertTrue(dumpToString().startsWith(
-                "Shutdown request from SYSTEM for reason reason1 "
-                        + "at 1970-01-01 00:00:01.000 UTC (epoch=1000)\n"
-                        + "com.android.server.power.ShutdownCheckPointsTest"
-                        + ".testSystemServiceBinderEntry\n at "));
-    }
-
-    @Test
-    public void testCallerProcessBinderEntries() throws RemoteException {
-        List<ActivityManager.RunningAppProcessInfo> runningAppProcessInfos = new ArrayList<>();
-        runningAppProcessInfos.add(
-                new ActivityManager.RunningAppProcessInfo("process_name", 1, new String[0]));
-        when(mActivityManager.getRunningAppProcesses()).thenReturn(runningAppProcessInfos);
-
-        mTestInjector.setCurrentTime(1000);
-        // Matching pid in getRunningAppProcesses
-        mInstance.recordCheckPointInternal(1, "reason1");
-        // Mising pid in getRunningAppProcesses
-        mInstance.recordCheckPointInternal(2, "reason2");
-
-        assertEquals(
-                "Shutdown request from BINDER for reason reason1 "
-                        + "at 1970-01-01 00:00:01.000 UTC (epoch=1000)\n"
-                        + "com.android.server.power.ShutdownCheckPointsTest"
-                        + ".testCallerProcessBinderEntries\n"
-                        + "From process process_name (pid=1)\n\n"
-                        + "Shutdown request from BINDER for reason reason2 "
-                        + "at 1970-01-01 00:00:01.000 UTC (epoch=1000)\n"
-                        + "com.android.server.power.ShutdownCheckPointsTest"
-                        + ".testCallerProcessBinderEntries\n"
-                        + "From process ? (pid=2)\n\n",
-                dumpToString());
-    }
-
-    @Test
-    public void testNullCallerProcessBinderEntries() throws RemoteException {
-        when(mActivityManager.getRunningAppProcesses()).thenReturn(null);
-
-        mTestInjector.setCurrentTime(1000);
-        mInstance.recordCheckPointInternal(1, "reason1");
-
-        assertEquals(
-                "Shutdown request from BINDER for reason reason1 "
-                        + "at 1970-01-01 00:00:01.000 UTC (epoch=1000)\n"
-                        + "com.android.server.power.ShutdownCheckPointsTest"
-                        + ".testNullCallerProcessBinderEntries\n"
-                        + "From process ? (pid=1)\n\n",
-                dumpToString());
-    }
-
-    @Test
-    public void testRemoteExceptionOnBinderEntry() throws RemoteException {
-        when(mActivityManager.getRunningAppProcesses()).thenThrow(new RemoteException("Error"));
-
-        mTestInjector.setCurrentTime(1000);
-        mInstance.recordCheckPointInternal(1, "reason1");
-
-        assertEquals(
-                "Shutdown request from BINDER for reason reason1 "
-                        + "at 1970-01-01 00:00:01.000 UTC (epoch=1000)\n"
-                        + "com.android.server.power.ShutdownCheckPointsTest"
-                        + ".testRemoteExceptionOnBinderEntry\n"
-                        + "From process ? (pid=1)\n\n",
-                dumpToString());
-    }
-
-    @Test
-    public void testUnknownProcessBinderEntry() {
-        mTestInjector.setCurrentTime(1000);
-        mInstance.recordCheckPointInternal(1, "reason1");
-
-        assertEquals(
-                "Shutdown request from BINDER for reason reason1 "
-                        + "at 1970-01-01 00:00:01.000 UTC (epoch=1000)\n"
-                        + "com.android.server.power.ShutdownCheckPointsTest"
-                        + ".testUnknownProcessBinderEntry\n"
-                        + "From process ? (pid=1)\n\n",
-                dumpToString());
-    }
-
-    @Test
-    public void testBinderEntryWithoutReason() throws RemoteException {
-        mTestInjector.setCurrentTime(1000);
-        mInstance.recordCheckPointInternal(1, null);
-
-        assertTrue(dumpToString().startsWith(
-                "Shutdown request from BINDER at 1970-01-01 00:00:01.000 UTC (epoch=1000)\n"));
-    }
-
-    @Test
-    public void testSystemServiceIntentEntry() {
-        mTestInjector.setCurrentTime(1000);
-        mInstance.recordCheckPointInternal("some.intent", "android", "reason1");
-
-        assertTrue(dumpToString().startsWith(
-                "Shutdown request from SYSTEM for reason reason1 "
-                        + "at 1970-01-01 00:00:01.000 UTC (epoch=1000)\n"
-                        + "com.android.server.power.ShutdownCheckPointsTest"
-                        + ".testSystemServiceIntentEntry\n at "));
-    }
-
-    @Test
-    public void testIntentEntry() {
-        mTestInjector.setCurrentTime(1000);
-        mInstance.recordCheckPointInternal("some.intent", "some.app", "reason1");
-
-        assertEquals(
-                "Shutdown request from INTENT for reason reason1 "
-                        + "at 1970-01-01 00:00:01.000 UTC (epoch=1000)\n"
-                        + "Intent: some.intent\n"
-                        + "Package: some.app\n\n",
-                dumpToString());
-    }
-
-    @Test
-    public void testIntentEntryWithoutReason() {
-        mTestInjector.setCurrentTime(1000);
-        mInstance.recordCheckPointInternal("some.intent", "some.app", null);
-
-        assertTrue(dumpToString().startsWith(
-                "Shutdown request from INTENT at 1970-01-01 00:00:01.000 UTC (epoch=1000)\n"));
-    }
-
-    @Test
-    public void testMultipleEntries() {
-        mTestInjector.setCurrentTime(1000);
-        mInstance.recordCheckPointInternal(1, "reason1");
-        mTestInjector.setCurrentTime(2000);
-        mInstance.recordCheckPointInternal(2, "reason2");
-        mTestInjector.setCurrentTime(3000);
-        mInstance.recordCheckPointInternal("intent", "app", "reason3");
-
-        assertEquals(
-                "Shutdown request from BINDER for reason reason1 "
-                        + "at 1970-01-01 00:00:01.000 UTC (epoch=1000)\n"
-                        + "com.android.server.power.ShutdownCheckPointsTest.testMultipleEntries\n"
-                        + "From process ? (pid=1)\n\n"
-                        + "Shutdown request from BINDER for reason reason2 "
-                        + "at 1970-01-01 00:00:02.000 UTC (epoch=2000)\n"
-                        + "com.android.server.power.ShutdownCheckPointsTest.testMultipleEntries\n"
-                        + "From process ? (pid=2)\n\n"
-                        + "Shutdown request from INTENT for reason reason3 "
-                        + "at 1970-01-01 00:00:03.000 UTC (epoch=3000)\n"
-                        + "Intent: intent\n"
-                        + "Package: app\n\n",
-                dumpToString());
-    }
-
-    @Test
-    public void testTooManyEntriesDropsOlderOnes() {
-        mTestInjector.setCheckPointsLimit(2);
-        ShutdownCheckPoints limitedInstance = new ShutdownCheckPoints(mTestInjector);
-
-        mTestInjector.setCurrentTime(1000);
-        limitedInstance.recordCheckPointInternal("intent.1", "app.1", "reason1");
-        mTestInjector.setCurrentTime(2000);
-        limitedInstance.recordCheckPointInternal("intent.2", "app.2", "reason2");
-        mTestInjector.setCurrentTime(3000);
-        limitedInstance.recordCheckPointInternal("intent.3", "app.3", "reason3");
-
-        // Drops first intent.
-        assertEquals(
-                "Shutdown request from INTENT for reason reason2 "
-                        + "at 1970-01-01 00:00:02.000 UTC (epoch=2000)\n"
-                        + "Intent: intent.2\n"
-                        + "Package: app.2\n\n"
-                        + "Shutdown request from INTENT for reason reason3 "
-                        + "at 1970-01-01 00:00:03.000 UTC (epoch=3000)\n"
-                        + "Intent: intent.3\n"
-                        + "Package: app.3\n\n",
-                dumpToString(limitedInstance));
-    }
-
-    @Test
-    public void testDumpToFile() throws Exception {
-        File tempDir = createTempDir();
-        File baseFile = new File(tempDir, "checkpoints");
-
-        mTestInjector.setCurrentTime(1000);
-        mInstance.recordCheckPointInternal("first.intent", "first.app", "reason1");
-        dumpToFile(baseFile);
-
-        mTestInjector.setCurrentTime(2000);
-        mInstance.recordCheckPointInternal("second.intent", "second.app", "reason2");
-        dumpToFile(baseFile);
-
-        File[] dumpFiles = tempDir.listFiles();
-        Arrays.sort(dumpFiles);
-
-        assertEquals(2, dumpFiles.length);
-        assertEquals(
-                "Shutdown request from INTENT for reason reason1 "
-                        + "at 1970-01-01 00:00:01.000 UTC (epoch=1000)\n"
-                        + "Intent: first.intent\n"
-                        + "Package: first.app\n\n",
-                readFileAsString(dumpFiles[0].getAbsolutePath()));
-        assertEquals(
-                "Shutdown request from INTENT for reason reason1 "
-                        + "at 1970-01-01 00:00:01.000 UTC (epoch=1000)\n"
-                        + "Intent: first.intent\n"
-                        + "Package: first.app\n\n"
-                        + "Shutdown request from INTENT for reason reason2 "
-                        + "at 1970-01-01 00:00:02.000 UTC (epoch=2000)\n"
-                        + "Intent: second.intent\n"
-                        + "Package: second.app\n\n",
-                readFileAsString(dumpFiles[1].getAbsolutePath()));
-    }
-
-    @Test
-    public void testTooManyFilesDropsOlderOnes() throws Exception {
-        mTestInjector.setDumpFilesLimit(1);
-        ShutdownCheckPoints instance = new ShutdownCheckPoints(mTestInjector);
-        File tempDir = createTempDir();
-        File baseFile = new File(tempDir, "checkpoints");
-
-        mTestInjector.setCurrentTime(1000);
-        instance.recordCheckPointInternal("first.intent", "first.app", "reason1");
-        dumpToFile(instance, baseFile);
-
-        mTestInjector.setCurrentTime(2000);
-        instance.recordCheckPointInternal("second.intent", "second.app", "reason2");
-        dumpToFile(instance, baseFile);
-
-        File[] dumpFiles = tempDir.listFiles();
-        assertEquals(1, dumpFiles.length);
-        assertEquals(
-                "Shutdown request from INTENT for reason reason1 "
-                        + "at 1970-01-01 00:00:01.000 UTC (epoch=1000)\n"
-                        + "Intent: first.intent\n"
-                        + "Package: first.app\n\n"
-                        + "Shutdown request from INTENT for reason reason2 "
-                        + "at 1970-01-01 00:00:02.000 UTC (epoch=2000)\n"
-                        + "Intent: second.intent\n"
-                        + "Package: second.app\n\n",
-                readFileAsString(dumpFiles[0].getAbsolutePath()));
-    }
-
-    private String dumpToString() {
-        return dumpToString(mInstance);
-    }
-
-    private String dumpToString(ShutdownCheckPoints instance) {
-        StringWriter sw = new StringWriter();
-        PrintWriter pw = new PrintWriter(sw);
-        instance.dumpInternal(pw);
-        return sw.toString();
-    }
-
-    private void dumpToFile(File baseFile) throws InterruptedException {
-        dumpToFile(mInstance, baseFile);
-    }
-
-    private void dumpToFile(ShutdownCheckPoints instance, File baseFile)
-            throws InterruptedException {
-        Thread dumpThread = instance.newDumpThreadInternal(baseFile);
-        dumpThread.start();
-        dumpThread.join();
-    }
-
-    private String readFileAsString(String absolutePath) throws IOException {
-        return new String(Files.readAllBytes(Paths.get(absolutePath)), StandardCharsets.UTF_8);
-    }
-
-    private File createTempDir() throws IOException {
-        File tempDir = File.createTempFile("checkpoints", "out");
-        tempDir.delete();
-        tempDir.mkdir();
-        return tempDir;
-    }
-
-    /** Fake system dependencies for testing. */
-    private final class TestInjector implements ShutdownCheckPoints.Injector {
-        private long mNow;
-        private int mCheckPointsLimit;
-        private int mDumpFilesLimit;
-        private IActivityManager mActivityManager;
-
-        TestInjector(IActivityManager activityManager) {
-            mNow = 0;
-            mCheckPointsLimit = 100;
-            mDumpFilesLimit = 2;
-            mActivityManager = activityManager;
-        }
-
-        @Override
-        public long currentTimeMillis() {
-            return mNow;
-        }
-
-        @Override
-        public int maxCheckPoints() {
-            return mCheckPointsLimit;
-        }
-
-        @Override
-        public int maxDumpFiles() {
-            return mDumpFilesLimit;
-        }
-
-        @Override
-        public IActivityManager activityManager() {
-            return mActivityManager;
-        }
-
-        void setCurrentTime(long time) {
-            mNow = time;
-        }
-
-        void setCheckPointsLimit(int limit) {
-            mCheckPointsLimit = limit;
-        }
-
-        void setDumpFilesLimit(int dumpFilesLimit) {
-            mDumpFilesLimit = dumpFilesLimit;
-        }
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/power/WakeLockLogTest.java b/services/tests/servicestests/src/com/android/server/power/WakeLockLogTest.java
deleted file mode 100644
index a73fcb8..0000000
--- a/services/tests/servicestests/src/com/android/server/power/WakeLockLogTest.java
+++ /dev/null
@@ -1,266 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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;
-
-import static org.junit.Assert.assertEquals;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.when;
-
-import android.os.PowerManager;
-
-import org.junit.Test;
-
-import java.io.PrintWriter;
-import java.io.StringWriter;
-import java.text.SimpleDateFormat;
-import java.util.TimeZone;
-
-/**
- * Tests for {@link WakeLockLog}.
- */
-public class WakeLockLogTest {
-
-    @Test
-    public void testAddTwoItems() {
-        final int tagDatabaseSize = 128;
-        final int logSize = 20;
-        TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize));
-        WakeLockLog log = new WakeLockLog(injectorSpy);
-
-        when(injectorSpy.currentTimeMillis()).thenReturn(1000L);
-        log.onWakeLockAcquired("TagPartial", 101,
-                PowerManager.PARTIAL_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE);
-
-        when(injectorSpy.currentTimeMillis()).thenReturn(1150L);
-        log.onWakeLockAcquired("TagFull", 102,
-                PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP);
-
-        assertEquals("Wake Lock Log\n"
-                + "  01-01 00:00:01.000 - 101 - ACQ TagPartial (partial,on-after-release)\n"
-                + "  01-01 00:00:01.150 - 102 - ACQ TagFull (full,acq-causes-wake)\n"
-                + "  -\n"
-                + "  Events: 2, Time-Resets: 0\n"
-                + "  Buffer, Bytes used: 6\n",
-                dumpLog(log, false));
-    }
-
-    @Test
-    public void testAddTwoItemsWithTimeReset() {
-        final int tagDatabaseSize = 128;
-        final int logSize = 20;
-        TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize));
-        WakeLockLog log = new WakeLockLog(injectorSpy);
-
-        when(injectorSpy.currentTimeMillis()).thenReturn(1000L);
-        log.onWakeLockAcquired("TagPartial", 101, PowerManager.PARTIAL_WAKE_LOCK);
-
-        when(injectorSpy.currentTimeMillis()).thenReturn(1350L);
-        log.onWakeLockAcquired("TagFull", 102, PowerManager.FULL_WAKE_LOCK);
-
-        assertEquals("Wake Lock Log\n"
-                + "  01-01 00:00:01.000 - 101 - ACQ TagPartial (partial)\n"
-                + "  01-01 00:00:01.350 - 102 - ACQ TagFull (full)\n"
-                + "  -\n"
-                + "  Events: 2, Time-Resets: 1\n"
-                + "  Buffer, Bytes used: 15\n",
-                dumpLog(log, false));
-    }
-
-    @Test
-    public void testAddTwoItemsWithTagOverwrite() {
-        final int tagDatabaseSize = 2;
-        final int logSize = 20;
-        TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize));
-        WakeLockLog log = new WakeLockLog(injectorSpy);
-
-        when(injectorSpy.currentTimeMillis()).thenReturn(1000L);
-        log.onWakeLockAcquired("TagPartial", 101, PowerManager.PARTIAL_WAKE_LOCK);
-
-        when(injectorSpy.currentTimeMillis()).thenReturn(1150L);
-        log.onWakeLockAcquired("TagFull", 102, PowerManager.FULL_WAKE_LOCK);
-
-        assertEquals("Wake Lock Log\n"
-                + "  01-01 00:00:01.000 - --- - ACQ UNKNOWN (partial)\n"
-                + "  01-01 00:00:01.150 - 102 - ACQ TagFull (full)\n"
-                + "  -\n"
-                + "  Events: 2, Time-Resets: 0\n"
-                + "  Buffer, Bytes used: 6\n",
-                dumpLog(log, false));
-    }
-
-    @Test
-    public void testAddFourItemsWithRingBufferOverflow() {
-        final int tagDatabaseSize = 6;
-        final int logSize = 10;
-        TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize));
-        WakeLockLog log = new WakeLockLog(injectorSpy);
-
-        // This first item will get deleted when ring buffer loops around
-        when(injectorSpy.currentTimeMillis()).thenReturn(1000L);
-        log.onWakeLockAcquired("TagPartial", 101, PowerManager.PARTIAL_WAKE_LOCK);
-
-        when(injectorSpy.currentTimeMillis()).thenReturn(1150L);
-        log.onWakeLockAcquired("TagFull", 102, PowerManager.FULL_WAKE_LOCK);
-        when(injectorSpy.currentTimeMillis()).thenReturn(1151L);
-        log.onWakeLockAcquired("TagThree", 101, PowerManager.PARTIAL_WAKE_LOCK);
-        when(injectorSpy.currentTimeMillis()).thenReturn(1152L);
-        log.onWakeLockAcquired("TagFour", 101, PowerManager.PARTIAL_WAKE_LOCK);
-
-        assertEquals("Wake Lock Log\n"
-                + "  01-01 00:00:01.150 - 102 - ACQ TagFull (full)\n"
-                + "  01-01 00:00:01.151 - 101 - ACQ TagThree (partial)\n"
-                + "  01-01 00:00:01.152 - 101 - ACQ TagFour (partial)\n"
-                + "  -\n"
-                + "  Events: 3, Time-Resets: 0\n"
-                + "  Buffer, Bytes used: 9\n",
-                dumpLog(log, false));
-    }
-
-    @Test
-    public void testAddItemWithBadTag() {
-        final int tagDatabaseSize = 6;
-        final int logSize = 10;
-        TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize));
-        WakeLockLog log = new WakeLockLog(injectorSpy);
-
-        // Bad tag means it wont get written
-        when(injectorSpy.currentTimeMillis()).thenReturn(1000L);
-        log.onWakeLockAcquired(null /* tag */, 0 /* ownerUid */, PowerManager.PARTIAL_WAKE_LOCK);
-
-        assertEquals("Wake Lock Log\n"
-                + "  -\n"
-                + "  Events: 0, Time-Resets: 0\n"
-                + "  Buffer, Bytes used: 0\n",
-                dumpLog(log, false));
-    }
-
-    @Test
-    public void testAddItemWithReducedTagName() {
-        final int tagDatabaseSize = 6;
-        final int logSize = 10;
-        TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize));
-        WakeLockLog log = new WakeLockLog(injectorSpy);
-
-        when(injectorSpy.currentTimeMillis()).thenReturn(1000L);
-        log.onWakeLockAcquired("*job*/com.one.two.3hree/.one..Last", 101,
-                PowerManager.PARTIAL_WAKE_LOCK);
-
-        assertEquals("Wake Lock Log\n"
-                + "  01-01 00:00:01.000 - 101 - ACQ *job*/c.o.t.3/.o..Last (partial)\n"
-                + "  -\n"
-                + "  Events: 1, Time-Resets: 0\n"
-                + "  Buffer, Bytes used: 3\n",
-                dumpLog(log, false));
-    }
-
-    @Test
-    public void testAddAcquireAndReleaseWithRepeatTagName() {
-        final int tagDatabaseSize = 6;
-        final int logSize = 10;
-        TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize));
-        WakeLockLog log = new WakeLockLog(injectorSpy);
-
-        when(injectorSpy.currentTimeMillis()).thenReturn(1000L);
-        log.onWakeLockAcquired("HowdyTag", 101, PowerManager.PARTIAL_WAKE_LOCK);
-        when(injectorSpy.currentTimeMillis()).thenReturn(1001L);
-        log.onWakeLockReleased("HowdyTag", 101);
-
-        assertEquals("Wake Lock Log\n"
-                + "  01-01 00:00:01.000 - 101 - ACQ HowdyTag (partial)\n"
-                + "  01-01 00:00:01.001 - 101 - REL HowdyTag\n"
-                + "  -\n"
-                + "  Events: 2, Time-Resets: 0\n"
-                + "  Buffer, Bytes used: 5\n"
-                + "  Tag Database: size(5), entries: 1, Bytes used: 80\n",
-                dumpLog(log, true));
-    }
-
-    @Test
-    public void testAddAcquireAndReleaseWithTimeTravel() {
-        final int tagDatabaseSize = 6;
-        final int logSize = 10;
-        TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize));
-        WakeLockLog log = new WakeLockLog(injectorSpy);
-
-        when(injectorSpy.currentTimeMillis()).thenReturn(1100L);
-        log.onWakeLockAcquired("HowdyTag", 101, PowerManager.PARTIAL_WAKE_LOCK);
-
-        // New element goes back in time...should not be written to log.
-        when(injectorSpy.currentTimeMillis()).thenReturn(1000L);
-        log.onWakeLockReleased("HowdyTag", 101);
-
-        assertEquals("Wake Lock Log\n"
-                + "  01-01 00:00:01.100 - 101 - ACQ HowdyTag (partial)\n"
-                + "  -\n"
-                + "  Events: 1, Time-Resets: 0\n"
-                + "  Buffer, Bytes used: 3\n",
-                dumpLog(log, false));
-    }
-
-    @Test
-    public void testAddSystemWakelock() {
-        final int tagDatabaseSize = 6;
-        final int logSize = 10;
-        TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize));
-        WakeLockLog log = new WakeLockLog(injectorSpy);
-
-        when(injectorSpy.currentTimeMillis()).thenReturn(1000L);
-        log.onWakeLockAcquired("TagPartial", 101,
-                PowerManager.PARTIAL_WAKE_LOCK | PowerManager.SYSTEM_WAKELOCK);
-
-        assertEquals("Wake Lock Log\n"
-                        + "  01-01 00:00:01.000 - 101 - ACQ TagPartial (partial,system-wakelock)\n"
-                        + "  -\n"
-                        + "  Events: 1, Time-Resets: 0\n"
-                        + "  Buffer, Bytes used: 3\n",
-                dumpLog(log, false));
-    }
-
-    private String dumpLog(WakeLockLog log, boolean includeTagDb) {
-        StringWriter sw = new StringWriter();
-        PrintWriter pw = new PrintWriter(sw);
-        log.dump(pw, includeTagDb);
-        return sw.toString();
-    }
-
-    public class TestInjector extends WakeLockLog.Injector {
-        private final int mTagDatabaseSize;
-        private final int mLogSize;
-
-        public TestInjector(int tagDatabaseSize, int logSize) {
-            mTagDatabaseSize = tagDatabaseSize;
-            mLogSize = logSize;
-        }
-
-        @Override
-        public int getTagDatabaseSize() {
-            return mTagDatabaseSize;
-        }
-
-        @Override
-        public int getLogSize() {
-            return mLogSize;
-        }
-
-        @Override
-        public SimpleDateFormat getDateFormat() {
-            SimpleDateFormat format = new SimpleDateFormat(super.getDateFormat().toPattern());
-            format.setTimeZone(TimeZone.getTimeZone("UTC"));
-            return format;
-        }
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverPolicyTest.java b/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverPolicyTest.java
index aa6ee09..835ccf0 100644
--- a/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverPolicyTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverPolicyTest.java
@@ -30,6 +30,7 @@
 import android.provider.Settings.Global;
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
+import android.test.suitebuilder.annotation.Suppress;
 import android.util.ArrayMap;
 
 import com.android.frameworks.servicestests.R;
@@ -47,12 +48,8 @@
     private static final float DEFAULT_BRIGHTNESS_FACTOR = 0.5f;
     private static final float PRECISION = 0.001f;
     private static final int GPS_MODE = 0; // LOCATION_MODE_NO_CHANGE
-    private static final int DEFAULT_GPS_MODE =
-            PowerManager.LOCATION_MODE_FOREGROUND_ONLY;
     private static final int SOUND_TRIGGER_MODE = 0; // SOUND_TRIGGER_MODE_ALL_ENABLED
-    private static final int DEFAULT_SOUND_TRIGGER_MODE =
-            PowerManager.SOUND_TRIGGER_MODE_CRITICAL_ONLY;
-    private static final String BATTERY_SAVER_CONSTANTS = "disable_vibration=true,"
+    private static final String BATTERY_SAVER_CONSTANTS = "disable_vibration=false,"
             + "advertise_is_enabled=true,"
             + "disable_animation=false,"
             + "enable_firewall=true,"
@@ -115,9 +112,12 @@
         testServiceDefaultValue_On(ServiceType.NULL);
     }
 
+    @Suppress
     @SmallTest
     public void testGetBatterySaverPolicy_PolicyVibration_DefaultValueCorrect() {
-        testServiceDefaultValue_On(ServiceType.VIBRATION);
+        testDefaultValue(
+                com.android.internal.R.bool.config_batterySaver_full_disableVibration,
+                ServiceType.VIBRATION);
     }
 
     @SmallTest
@@ -128,27 +128,39 @@
 
     @SmallTest
     public void testGetBatterySaverPolicy_PolicySound_DefaultValueCorrect() {
-        testServiceDefaultValue_On(ServiceType.SOUND);
+        final int defaultMode = getContext().getResources().getInteger(
+                com.android.internal.R.integer.config_batterySaver_full_soundTriggerMode);
+        if (defaultMode == PowerManager.SOUND_TRIGGER_MODE_ALL_ENABLED) {
+            testServiceDefaultValue_Off(ServiceType.SOUND);
+        } else {
+            testServiceDefaultValue_On(ServiceType.SOUND);
+        }
 
         mBatterySaverPolicy.setPolicyLevel(POLICY_LEVEL_FULL);
         PowerSaveState stateOn =
                 mBatterySaverPolicy.getBatterySaverPolicy(ServiceType.SOUND);
-        assertThat(stateOn.soundTriggerMode).isEqualTo(DEFAULT_SOUND_TRIGGER_MODE);
+        assertThat(stateOn.soundTriggerMode).isEqualTo(defaultMode);
     }
 
     @SmallTest
     public void testGetBatterySaverPolicy_PolicyFullBackup_DefaultValueCorrect() {
-        testServiceDefaultValue_On(ServiceType.FULL_BACKUP);
+        testDefaultValue(
+                com.android.internal.R.bool.config_batterySaver_full_deferFullBackup,
+                ServiceType.FULL_BACKUP);
     }
 
     @SmallTest
     public void testGetBatterySaverPolicy_PolicyKeyValueBackup_DefaultValueCorrect() {
-        testServiceDefaultValue_On(ServiceType.KEYVALUE_BACKUP);
+        testDefaultValue(
+                com.android.internal.R.bool.config_batterySaver_full_deferKeyValueBackup,
+                ServiceType.KEYVALUE_BACKUP);
     }
 
     @SmallTest
     public void testGetBatterySaverPolicy_PolicyAnimation_DefaultValueCorrect() {
-        testServiceDefaultValue_Off(ServiceType.ANIMATION);
+        testDefaultValue(
+                com.android.internal.R.bool.config_batterySaver_full_disableAnimation,
+                ServiceType.ANIMATION);
     }
 
     @SmallTest
@@ -158,48 +170,56 @@
 
     @SmallTest
     public void testGetBatterySaverPolicy_PolicyNetworkFirewall_DefaultValueCorrect() {
-        testServiceDefaultValue_On(ServiceType.NETWORK_FIREWALL);
+        testDefaultValue(
+                com.android.internal.R.bool.config_batterySaver_full_enableFirewall,
+                ServiceType.NETWORK_FIREWALL);
     }
 
     @SmallTest
     public void testGetBatterySaverPolicy_PolicyNightMode_DefaultValueCorrect() {
-        testServiceDefaultValue_On(ServiceType.NIGHT_MODE);
+        testDefaultValue(
+                com.android.internal.R.bool.config_batterySaver_full_enableNightMode,
+                ServiceType.NIGHT_MODE);
     }
 
     @SmallTest
     public void testGetBatterySaverPolicy_PolicyDataSaver_DefaultValueCorrect() {
-        mBatterySaverPolicy.updateConstantsLocked("", "");
-        mBatterySaverPolicy.setPolicyLevel(POLICY_LEVEL_FULL);
-        final PowerSaveState batterySaverStateOn =
-                mBatterySaverPolicy.getBatterySaverPolicy(ServiceType.DATA_SAVER);
-        assertThat(batterySaverStateOn.batterySaverEnabled).isFalse();
-
-        mBatterySaverPolicy.setPolicyLevel(POLICY_LEVEL_OFF);
-        final PowerSaveState batterySaverStateOff = mBatterySaverPolicy.getBatterySaverPolicy(
+        testDefaultValue(
+                com.android.internal.R.bool.config_batterySaver_full_enableDataSaver,
                 ServiceType.DATA_SAVER);
-        assertThat(batterySaverStateOff.batterySaverEnabled).isFalse();
     }
 
     @SmallTest
     public void testGetBatterySaverPolicy_PolicyScreenBrightness_DefaultValueCorrect() {
-        testServiceDefaultValue_Off(ServiceType.SCREEN_BRIGHTNESS);
+        testDefaultValue(
+                com.android.internal.R.bool.config_batterySaver_full_enableAdjustBrightness,
+                ServiceType.SCREEN_BRIGHTNESS);
     }
 
     @SmallTest
-    public void testGetBatterySaverPolicy_PolicyGps_DefaultValueCorrect() {
-        testServiceDefaultValue_On(ServiceType.LOCATION);
+    public void testGetBatterySaverPolicy_PolicyLocation_DefaultValueCorrect() {
+        final int defaultMode = getContext().getResources()
+                .getInteger(com.android.internal.R.integer.config_batterySaver_full_locationMode);
+        if (defaultMode == PowerManager.LOCATION_MODE_NO_CHANGE) {
+            testServiceDefaultValue_Off(ServiceType.LOCATION);
+        } else {
+            testServiceDefaultValue_On(ServiceType.LOCATION);
+        }
 
         mBatterySaverPolicy.setPolicyLevel(POLICY_LEVEL_FULL);
         PowerSaveState stateOn =
                 mBatterySaverPolicy.getBatterySaverPolicy(ServiceType.LOCATION);
-        assertThat(stateOn.locationMode).isEqualTo(DEFAULT_GPS_MODE);
+        assertThat(stateOn.locationMode).isEqualTo(defaultMode);
     }
 
     @SmallTest
     public void testGetBatterySaverPolicy_PolicyQuickDoze_DefaultValueCorrect() {
-        testServiceDefaultValue_On(ServiceType.QUICK_DOZE);
+        testDefaultValue(
+                com.android.internal.R.bool.config_batterySaver_full_enableQuickDoze,
+                ServiceType.QUICK_DOZE);
     }
 
+    @Suppress
     @SmallTest
     public void testUpdateConstants_getCorrectData() {
         mBatterySaverPolicy.updateConstantsLocked(BATTERY_SAVER_CONSTANTS, "");
@@ -211,7 +231,7 @@
     private void verifyBatterySaverConstantsUpdated() {
         final PowerSaveState vibrationState =
                 mBatterySaverPolicy.getBatterySaverPolicy(ServiceType.VIBRATION);
-        assertThat(vibrationState.batterySaverEnabled).isTrue();
+        assertThat(vibrationState.batterySaverEnabled).isFalse();
 
         final PowerSaveState animationState =
                 mBatterySaverPolicy.getBatterySaverPolicy(ServiceType.ANIMATION);
@@ -264,6 +284,14 @@
         mBatterySaverPolicy.updateConstantsLocked(null, "");
     }
 
+    private void testDefaultValue(int boolResId, @ServiceType int type) {
+        if (getContext().getResources().getBoolean(boolResId)) {
+            testServiceDefaultValue_On(type);
+        } else {
+            testServiceDefaultValue_Off(type);
+        }
+    }
+
     private void testServiceDefaultValue_On(@ServiceType int type) {
         mBatterySaverPolicy.updateConstantsLocked("", "");
         mBatterySaverPolicy.setPolicyLevel(POLICY_LEVEL_FULL);
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java b/services/tests/servicestests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java
index cff4cc7..5a2d2e3 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java
@@ -42,6 +42,7 @@
 
 import androidx.test.InstrumentationRegistry;
 
+import com.android.internal.os.CpuScalingPolicies;
 import com.android.internal.os.PowerProfile;
 
 import org.junit.Before;
@@ -214,6 +215,15 @@
     public class TestBatteryStatsImpl extends BatteryStatsImpl {
         public TestBatteryStatsImpl(Context context) {
             mPowerProfile = new PowerProfile(context, true /* forTest */);
+
+            SparseArray<int[]> cpusByPolicy = new SparseArray<>();
+            cpusByPolicy.put(0, new int[]{0, 1, 2, 3});
+            cpusByPolicy.put(4, new int[]{4, 5, 6, 7});
+            SparseArray<int[]> freqsByPolicy = new SparseArray<>();
+            freqsByPolicy.put(0, new int[]{300000, 1000000, 2000000});
+            freqsByPolicy.put(4, new int[]{300000, 1000000, 2500000, 3000000});
+            mCpuScalingPolicies = new CpuScalingPolicies(freqsByPolicy, freqsByPolicy);
+
             initTimersAndCounters();
         }
     }
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsCpuTimesTest.java b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsCpuTimesTest.java
index f48a296..55ffa1a 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsCpuTimesTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsCpuTimesTest.java
@@ -32,25 +32,25 @@
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.isNull;
 import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
 import android.os.BatteryStats;
 import android.os.UserHandle;
+import android.util.SparseArray;
 import android.util.SparseLongArray;
 import android.view.Display;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.internal.os.CpuScalingPolicies;
 import com.android.internal.os.KernelCpuSpeedReader;
 import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidActiveTimeReader;
 import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidClusterTimeReader;
 import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidFreqTimeReader;
 import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidUserSysTimeReader;
-import com.android.internal.os.PowerProfile;
 import com.android.internal.util.ArrayUtils;
 
 import org.junit.Before;
@@ -95,8 +95,6 @@
     SystemServerCpuThreadReader mSystemServerCpuThreadReader;
     @Mock
     BatteryStatsImpl.UserInfoProvider mUserInfoProvider;
-    @Mock
-    PowerProfile mPowerProfile;
 
     private MockClock mClocks;
     private MockBatteryStatsImpl mBatteryStatsImpl;
@@ -108,6 +106,7 @@
 
         mClocks = new MockClock();
         mBatteryStatsImpl = new MockBatteryStatsImpl(mClocks)
+                .setTestCpuScalingPolicies()
                 .setKernelCpuUidUserSysTimeReader(mCpuUidUserSysTimeReader)
                 .setKernelCpuUidFreqTimeReader(mCpuUidFreqTimeReader)
                 .setKernelCpuUidActiveTimeReader(mCpuUidActiveTimeReader)
@@ -119,18 +118,14 @@
     @Test
     public void testUpdateCpuTimeLocked() {
         // PRECONDITIONS
-        mBatteryStatsImpl.setPowerProfile(mPowerProfile);
         mBatteryStatsImpl.setOnBatteryInternal(false);
         final int numClusters = 3;
         initKernelCpuSpeedReaders(numClusters);
-        final long[] freqs = {1, 12, 123, 12, 1234};
-        when(mCpuUidFreqTimeReader.readFreqs(mPowerProfile)).thenReturn(freqs);
 
         // RUN
         mBatteryStatsImpl.updateCpuTimeLocked(false, false, null);
 
         // VERIFY
-        assertArrayEquals("Unexpected cpu freqs", freqs, mBatteryStatsImpl.getCpuFreqs());
         verify(mCpuUidUserSysTimeReader).readDelta(anyBoolean(), isNull());
         verify(mCpuUidFreqTimeReader).readDelta(anyBoolean(), isNull());
         for (int i = 0; i < numClusters; ++i) {
@@ -153,9 +148,8 @@
         verify(mUserInfoProvider).refreshUserIds();
         verify(mCpuUidUserSysTimeReader).readDelta(anyBoolean(),
                 any(KernelCpuUidUserSysTimeReader.Callback.class));
-        // perClusterTimesAvailable is called twice, once in updateCpuTimeLocked() and the other
-        // in readKernelUidCpuFreqTimesLocked.
-        verify(mCpuUidFreqTimeReader, times(2)).perClusterTimesAvailable();
+        verify(mCpuUidFreqTimeReader).perClusterTimesAvailable();
+        verify(mCpuUidFreqTimeReader).allUidTimesAvailable();
         verify(mCpuUidFreqTimeReader).readDelta(anyBoolean(),
                 any(KernelCpuUidFreqTimeReader.Callback.class));
         verify(mCpuUidActiveTimeReader).readAbsolute(
@@ -210,15 +204,25 @@
         // PRECONDITIONS
         updateTimeBasesLocked(true, Display.STATE_ON, 0, 0);
         final long[][] clusterSpeedTimesMs = {{20, 30}, {40, 50, 60}};
+
+        CpuScalingPolicies scalingPolicies = new CpuScalingPolicies(
+                new SparseArray<>() {{
+                    for (int i = 0; i < clusterSpeedTimesMs.length; i++) {
+                        put(i, new int[]{i});
+                    }
+                }},
+                new SparseArray<>() {{
+                    for (int i = 0; i < clusterSpeedTimesMs.length; i++) {
+                        put(i, new int[clusterSpeedTimesMs[i].length]);
+                    }
+                }});
+        mBatteryStatsImpl.setCpuScalingPolicies(scalingPolicies);
+
         initKernelCpuSpeedReaders(clusterSpeedTimesMs.length);
         for (int i = 0; i < clusterSpeedTimesMs.length; ++i) {
             when(mKernelCpuSpeedReaders[i].readDelta()).thenReturn(clusterSpeedTimesMs[i]);
         }
-        when(mPowerProfile.getNumCpuClusters()).thenReturn(clusterSpeedTimesMs.length);
-        for (int i = 0; i < clusterSpeedTimesMs.length; ++i) {
-            when(mPowerProfile.getNumSpeedStepsInCpuCluster(i))
-                    .thenReturn(clusterSpeedTimesMs[i].length);
-        }
+
         final SparseLongArray updatedUids = new SparseLongArray();
         final int[] testUids = {10012, 10014, 10016};
         final int[] cpuTimeUs = {89, 31, 43};
@@ -626,14 +630,19 @@
                 FIRST_APPLICATION_UID + 27,
                 FIRST_APPLICATION_UID + 33
         });
-        final long[] freqs = {1, 12, 123, 12, 1234};
+        CpuScalingPolicies scalingPolicies = new CpuScalingPolicies(
+                new SparseArray<>() {{
+                    put(0, new int[]{0});
+                    put(1, new int[]{1});
+                }},
+                new SparseArray<>() {{
+                    put(0, new int[]{1, 12, 123});
+                    put(1, new int[]{12, 1234});
+                }});
+        mBatteryStatsImpl.setCpuScalingPolicies(scalingPolicies);
         // Derived from freqs above, 2 clusters with {3, 2} freqs in each of them.
         final int[] clusterFreqs = {3, 2};
-        when(mPowerProfile.getNumCpuClusters()).thenReturn(clusterFreqs.length);
-        for (int i = 0; i < clusterFreqs.length; ++i) {
-            when(mPowerProfile.getNumSpeedStepsInCpuCluster(i))
-                    .thenReturn(clusterFreqs[i]);
-        }
+
         final long[][] uidTimesMs = {
                 {4, 10, 5, 9, 4},
                 {5, 1, 12, 2, 10},
@@ -736,14 +745,21 @@
         };
         final ArrayList<BatteryStatsImpl.StopwatchTimer> partialTimers
                 = getPartialTimers(partialTimerUids);
-        final long[] freqs = {1, 12, 123, 12, 1234};
+
+        CpuScalingPolicies scalingPolicies = new CpuScalingPolicies(
+                new SparseArray<>() {{
+                    put(0, new int[]{0});
+                    put(1, new int[]{1});
+                }},
+                new SparseArray<>() {{
+                    put(0, new int[]{1, 12, 123});
+                    put(1, new int[]{12, 1234});
+                }});
+        mBatteryStatsImpl.setCpuScalingPolicies(scalingPolicies);
+
         // Derived from freqs above, 2 clusters with {3, 2} freqs in each of them.
         final int[] clusterFreqs = {3, 2};
-        when(mPowerProfile.getNumCpuClusters()).thenReturn(clusterFreqs.length);
-        for (int i = 0; i < clusterFreqs.length; ++i) {
-            when(mPowerProfile.getNumSpeedStepsInCpuCluster(i))
-                    .thenReturn(clusterFreqs[i]);
-        }
+
         final long[][] uidTimesMs = {
                 {4, 10, 5, 9, 4},
                 {5, 1, 12, 2, 10},
@@ -764,7 +780,7 @@
         mBatteryStatsImpl.readKernelUidCpuFreqTimesLocked(partialTimers, true, false, null);
 
         // VERIFY
-        final long[][] expectedWakeLockUidTimesUs = new long[clusterFreqs.length][];
+        final long[][] expectedWakeLockUidTimesUs = new long[2][];
         for (int cluster = 0; cluster < clusterFreqs.length; ++cluster) {
             expectedWakeLockUidTimesUs[cluster] = new long[clusterFreqs[cluster]];
         }
@@ -1357,11 +1373,7 @@
 
     private void updateTimeBasesLocked(boolean unplugged, int screenState,
             long upTime, long realTime) {
-        // Set PowerProfile=null before calling updateTimeBasesLocked to avoid execution of
-        // BatteryStatsImpl.updateCpuTimeLocked
-        mBatteryStatsImpl.setPowerProfile(null);
         mBatteryStatsImpl.updateTimeBasesLocked(unplugged, screenState, upTime, realTime);
-        mBatteryStatsImpl.setPowerProfile(mPowerProfile);
     }
 
     private void initKernelCpuSpeedReaders(int count) {
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java
index 1ba1462..eb3bd0e 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java
@@ -126,6 +126,19 @@
     }
 
     @Test
+    public void testAtraceExcludedState() {
+        mHistory.forceRecordAllHistory();
+
+        Mockito.when(mTracer.tracingEnabled()).thenReturn(true);
+
+        mHistory.recordStateStartEvent(mClock.elapsedRealtime(),
+                mClock.uptimeMillis(), HistoryItem.STATE_WAKE_LOCK_FLAG);
+
+        Mockito.verify(mTracer, Mockito.never()).traceCounter(
+                Mockito.anyString(), Mockito.anyInt());
+    }
+
+    @Test
     public void testAtraceNumericalState() {
         InOrder inOrder = Mockito.inOrder(mTracer);
         Mockito.when(mTracer.tracingEnabled()).thenReturn(true);
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsImplTest.java b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsImplTest.java
index ee4b839..f20f061 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsImplTest.java
@@ -23,6 +23,7 @@
 import static android.os.BatteryStats.Uid.PROCESS_STATE_TOP;
 
 import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
 
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
@@ -49,12 +50,14 @@
 import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.internal.os.CpuScalingPolicies;
 import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidFreqTimeReader;
 import com.android.internal.os.KernelSingleUidTimeReader;
 import com.android.internal.os.LongArrayMultiStateCounter;
 import com.android.internal.os.PowerProfile;
 
 import com.google.common.collect.ImmutableList;
+import com.google.common.truth.LongSubject;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -68,15 +71,25 @@
 @RunWith(AndroidJUnit4.class)
 @SuppressWarnings("GuardedBy")
 public class BatteryStatsImplTest {
-    private static final long[] CPU_FREQS = {1, 2, 3, 4, 5};
-    private static final int NUM_CPU_FREQS = CPU_FREQS.length;
-
     @Mock
     private KernelCpuUidFreqTimeReader mKernelUidCpuFreqTimeReader;
     @Mock
     private KernelSingleUidTimeReader mKernelSingleUidTimeReader;
     @Mock
     private PowerProfile mPowerProfile;
+    @Mock
+    private KernelWakelockReader mKernelWakelockReader;
+    private KernelWakelockStats mKernelWakelockStats = new KernelWakelockStats();
+
+    private static final int NUM_CPU_FREQS = 5;
+
+    private final CpuScalingPolicies mCpuScalingPolicies = new CpuScalingPolicies(
+            new SparseArray<>() {{
+                put(0, new int[1]);
+            }},
+            new SparseArray<>() {{
+                put(0, new int[NUM_CPU_FREQS]);
+            }});
 
     private final MockClock mMockClock = new MockClock();
     private MockBatteryStatsImpl mBatteryStatsImpl;
@@ -86,13 +99,16 @@
         MockitoAnnotations.initMocks(this);
 
         when(mKernelUidCpuFreqTimeReader.isFastCpuTimesReader()).thenReturn(true);
-        when(mKernelUidCpuFreqTimeReader.readFreqs(any())).thenReturn(CPU_FREQS);
         when(mKernelUidCpuFreqTimeReader.allUidTimesAvailable()).thenReturn(true);
         when(mKernelSingleUidTimeReader.singleUidCpuTimesAvailable()).thenReturn(true);
+        when(mKernelWakelockReader.readKernelWakelockStats(
+                any(KernelWakelockStats.class))).thenReturn(mKernelWakelockStats);
         mBatteryStatsImpl = new MockBatteryStatsImpl(mMockClock)
                 .setPowerProfile(mPowerProfile)
+                .setCpuScalingPolicies(mCpuScalingPolicies)
                 .setKernelCpuUidFreqTimeReader(mKernelUidCpuFreqTimeReader)
-                .setKernelSingleUidTimeReader(mKernelSingleUidTimeReader);
+                .setKernelSingleUidTimeReader(mKernelSingleUidTimeReader)
+                .setKernelWakelockReader(mKernelWakelockReader);
     }
 
     @Test
@@ -559,6 +575,48 @@
     }
 
     @Test
+    public void kernelWakelocks() {
+        mBatteryStatsImpl.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0);
+
+        mKernelWakelockStats.put("lock1", new KernelWakelockStats.Entry(42, 1000, 314, 0));
+        mKernelWakelockStats.put("lock2", new KernelWakelockStats.Entry(6, 2000, 0, 0));
+
+        mMockClock.realtime = 5000;
+
+        // The fist call makes a snapshot of the initial state of the wakelocks
+        mBatteryStatsImpl.updateKernelWakelocksLocked(mMockClock.realtime * 1000);
+
+        assertThat(mBatteryStatsImpl.getKernelWakelockStats()).hasSize(2);
+
+        mMockClock.realtime += 2000;
+
+        assertThatKernelWakelockTotalTime("lock1").isEqualTo(314);  // active
+        assertThatKernelWakelockTotalTime("lock2").isEqualTo(0);        // inactive
+
+        mKernelWakelockStats.put("lock1", new KernelWakelockStats.Entry(43, 1100, 414, 0));
+        mKernelWakelockStats.put("lock2", new KernelWakelockStats.Entry(6, 2222, 0, 0));
+
+        mMockClock.realtime += 3000;
+
+        // Compute delta from the initial snapshot
+        mBatteryStatsImpl.updateKernelWakelocksLocked(mMockClock.realtime * 1000);
+
+        mMockClock.realtime += 4000;
+
+        assertThatKernelWakelockTotalTime("lock1").isEqualTo(414);
+
+        // Wake lock not active. Expect relative total time as reported by Kernel:
+        // 2_222 - 2_000 = 222
+        assertThatKernelWakelockTotalTime("lock2").isEqualTo(222);
+    }
+
+    private LongSubject assertThatKernelWakelockTotalTime(String name) {
+        return assertWithMessage("Kernel wakelock " + name + " at " + mMockClock.realtime)
+                .that(mBatteryStatsImpl.getKernelWakelockStats().get(name)
+                        .getTotalTimeLocked(mMockClock.realtime * 1000, 0));
+    }
+
+    @Test
     public void testGetBluetoothBatteryStats() {
         when(mPowerProfile.getAveragePower(
                 PowerProfile.POWER_BLUETOOTH_CONTROLLER_OPERATING_VOLTAGE)).thenReturn(3.0);
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsNoteTest.java b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsNoteTest.java
index 6b21eb0..090c8c8 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsNoteTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsNoteTest.java
@@ -32,7 +32,6 @@
 
 import android.app.ActivityManager;
 import android.app.usage.NetworkStatsManager;
-import android.hardware.radio.V1_5.AccessNetwork;
 import android.os.BatteryStats;
 import android.os.BatteryStats.HistoryItem;
 import android.os.BatteryStats.Uid.Sensor;
@@ -1458,6 +1457,148 @@
     }
 
     @SmallTest
+    public void testGetPerStateActiveRadioDurationMs_initialModemActivity() {
+        final MockClock clock = new MockClock(); // holds realtime and uptime in ms
+        final MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clock);
+        bi.setPowerProfile(mock(PowerProfile.class));
+
+        final int ratCount = RADIO_ACCESS_TECHNOLOGY_COUNT;
+        final int frequencyCount = ServiceState.FREQUENCY_RANGE_MMWAVE + 1;
+        final int txLevelCount = CellSignalStrength.getNumSignalStrengthLevels();
+
+        List<ActivityStatsTechSpecificInfo> specificInfoList = new ArrayList();
+
+        final long[][][] expectedDurationsMs = new long[ratCount][frequencyCount][txLevelCount];
+        final long[][] expectedRxDurationsMs = new long[ratCount][frequencyCount];
+        final long[][][] expectedTxDurationsMs = new long[ratCount][frequencyCount][txLevelCount];
+        for (int rat = 0; rat < ratCount; rat++) {
+            for (int freq = 0; freq < frequencyCount; freq++) {
+                if (rat == RADIO_ACCESS_TECHNOLOGY_NR
+                        || freq == ServiceState.FREQUENCY_RANGE_UNKNOWN) {
+                    // Only the NR RAT should have per frequency data.
+                    expectedRxDurationsMs[rat][freq] = 0;
+                } else {
+                    expectedRxDurationsMs[rat][freq] = POWER_DATA_UNAVAILABLE;
+                }
+                for (int txLvl = 0; txLvl < txLevelCount; txLvl++) {
+                    if (rat == RADIO_ACCESS_TECHNOLOGY_NR
+                            || freq == ServiceState.FREQUENCY_RANGE_UNKNOWN) {
+                        // Only the NR RAT should have per frequency data.
+                        expectedTxDurationsMs[rat][freq][txLvl] = 0;
+                    } else {
+                        expectedTxDurationsMs[rat][freq][txLvl] = POWER_DATA_UNAVAILABLE;
+                    }
+                }
+            }
+        }
+
+        // The first modem activity pulled from modem with activity stats for each RATs.
+        specificInfoList.add(new ActivityStatsTechSpecificInfo(
+                AccessNetworkConstants.AccessNetworkType.UNKNOWN,
+                ServiceState.FREQUENCY_RANGE_UNKNOWN, new int[txLevelCount], 101));
+        specificInfoList.add(new ActivityStatsTechSpecificInfo(
+                AccessNetworkConstants.AccessNetworkType.GERAN,
+                ServiceState.FREQUENCY_RANGE_UNKNOWN, new int[txLevelCount], 202));
+        specificInfoList.add(new ActivityStatsTechSpecificInfo(
+                AccessNetworkConstants.AccessNetworkType.UTRAN,
+                ServiceState.FREQUENCY_RANGE_UNKNOWN, new int[txLevelCount], 303));
+        specificInfoList.add(new ActivityStatsTechSpecificInfo(
+                AccessNetworkConstants.AccessNetworkType.EUTRAN,
+                ServiceState.FREQUENCY_RANGE_UNKNOWN, new int[]{3, 9, 133, 48, 218}, 404));
+        specificInfoList.add(new ActivityStatsTechSpecificInfo(
+                AccessNetworkConstants.AccessNetworkType.CDMA2000,
+                ServiceState.FREQUENCY_RANGE_UNKNOWN, new int[txLevelCount], 505));
+        specificInfoList.add(new ActivityStatsTechSpecificInfo(
+                AccessNetworkConstants.AccessNetworkType.IWLAN,
+                ServiceState.FREQUENCY_RANGE_UNKNOWN, new int[txLevelCount], 606));
+        specificInfoList.add(new ActivityStatsTechSpecificInfo(
+                AccessNetworkConstants.AccessNetworkType.NGRAN,
+                ServiceState.FREQUENCY_RANGE_UNKNOWN, new int[txLevelCount], 707));
+        specificInfoList.add(new ActivityStatsTechSpecificInfo(
+                AccessNetworkConstants.AccessNetworkType.NGRAN,
+                ServiceState.FREQUENCY_RANGE_LOW, new int[txLevelCount], 808));
+        specificInfoList.add(new ActivityStatsTechSpecificInfo(
+                AccessNetworkConstants.AccessNetworkType.NGRAN,
+                ServiceState.FREQUENCY_RANGE_MID, new int[txLevelCount], 909));
+        specificInfoList.add(new ActivityStatsTechSpecificInfo(
+                AccessNetworkConstants.AccessNetworkType.NGRAN,
+                ServiceState.FREQUENCY_RANGE_HIGH, new int[txLevelCount], 1010));
+        specificInfoList.add(new ActivityStatsTechSpecificInfo(
+                AccessNetworkConstants.AccessNetworkType.NGRAN,
+                ServiceState.FREQUENCY_RANGE_MMWAVE, new int[txLevelCount], 1111));
+
+
+        final ActivityStatsTechSpecificInfo[] specificInfos = specificInfoList.toArray(
+                new ActivityStatsTechSpecificInfo[specificInfoList.size()]);
+        final ModemActivityInfo mai = new ModemActivityInfo(0L, 2002L, 3003L, specificInfos);
+        final ModemAndBatteryState state = new ModemAndBatteryState(bi, mai, specificInfos);
+
+
+        IntConsumer incrementTime = inc -> {
+            state.currentTimeMs += inc;
+            clock.realtime = clock.uptime = state.currentTimeMs;
+
+            final int currRat = state.currentRat;
+            final int currRant = state.currentRadioAccessNetworkType;
+            final int currFreqRange =
+                    currRat == RADIO_ACCESS_TECHNOLOGY_NR ? state.currentFrequencyRange : 0;
+            int currSignalStrength = state.currentSignalStrengths.get(currRat);
+
+            if (state.modemActive) {
+                // Don't count the duration if the modem is not active
+                expectedDurationsMs[currRat][currFreqRange][currSignalStrength] += inc;
+            }
+
+            // Evaluate the HAL provided time in states.
+            final ActivityStatsTechSpecificInfo info = state.getSpecificInfo(currRant,
+                    currFreqRange);
+            switch (state.modemState) {
+                case SLEEP:
+                    long sleepMs = state.modemActivityInfo.getSleepTimeMillis();
+                    state.modemActivityInfo.setSleepTimeMillis(sleepMs + inc);
+                    break;
+                case IDLE:
+                    long idleMs = state.modemActivityInfo.getIdleTimeMillis();
+                    state.modemActivityInfo.setIdleTimeMillis(idleMs + inc);
+                    break;
+                case RECEIVING:
+                    long rxMs = info.getReceiveTimeMillis();
+                    info.setReceiveTimeMillis(rxMs + inc);
+                    expectedRxDurationsMs[currRat][currFreqRange] += inc;
+                    break;
+                case TRANSMITTING:
+                    int[] txMs = info.getTransmitTimeMillis().clone();
+                    txMs[currSignalStrength] += inc;
+                    info.setTransmitTimeMillis(txMs);
+                    expectedTxDurationsMs[currRat][currFreqRange][currSignalStrength] += inc;
+                    break;
+            }
+        };
+
+        // On battery, but the modem is not active
+        bi.updateTimeBasesLocked(true, Display.STATE_OFF, state.currentTimeMs * 1000,
+                state.currentTimeMs * 1000);
+        bi.setOnBatteryInternal(true);
+        state.noteModemControllerActivity();
+        // Ensure the first modem activity should not be counted up.
+        checkPerStateActiveRadioDurations(expectedDurationsMs, expectedRxDurationsMs,
+                expectedTxDurationsMs, bi, state.currentTimeMs);
+        // Start counting.
+        state.setRatType(TelephonyManager.NETWORK_TYPE_NR, BatteryStats.RADIO_ACCESS_TECHNOLOGY_NR,
+                AccessNetworkConstants.AccessNetworkType.NGRAN);
+        // Frequency changed to low.
+        state.setFrequencyRange(ServiceState.FREQUENCY_RANGE_LOW);
+        incrementTime.accept(300);
+        state.setModemState(ModemState.RECEIVING);
+        incrementTime.accept(500);
+        state.setModemState(ModemState.TRANSMITTING);
+        incrementTime.accept(600);
+        state.noteModemControllerActivity();
+        checkPerStateActiveRadioDurations(expectedDurationsMs, expectedRxDurationsMs,
+                expectedTxDurationsMs, bi, state.currentTimeMs);
+    }
+
+    @SmallTest
     public void testGetPerStateActiveRadioDurationMs_withModemActivity() {
         final MockClock clock = new MockClock(); // holds realtime and uptime in ms
         final MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clock);
@@ -1727,27 +1868,38 @@
             }
         }
 
-        specificInfoList.add(new ActivityStatsTechSpecificInfo(AccessNetwork.UNKNOWN,
+        specificInfoList.add(new ActivityStatsTechSpecificInfo(
+                AccessNetworkConstants.AccessNetworkType.UNKNOWN,
                 ServiceState.FREQUENCY_RANGE_UNKNOWN, new int[txLevelCount], 0));
-        specificInfoList.add(new ActivityStatsTechSpecificInfo(AccessNetwork.GERAN,
+        specificInfoList.add(new ActivityStatsTechSpecificInfo(
+                AccessNetworkConstants.AccessNetworkType.GERAN,
                 ServiceState.FREQUENCY_RANGE_UNKNOWN, new int[txLevelCount], 0));
-        specificInfoList.add(new ActivityStatsTechSpecificInfo(AccessNetwork.UTRAN,
+        specificInfoList.add(new ActivityStatsTechSpecificInfo(
+                AccessNetworkConstants.AccessNetworkType.UTRAN,
                 ServiceState.FREQUENCY_RANGE_UNKNOWN, new int[txLevelCount], 0));
-        specificInfoList.add(new ActivityStatsTechSpecificInfo(AccessNetwork.EUTRAN,
+        specificInfoList.add(new ActivityStatsTechSpecificInfo(
+                AccessNetworkConstants.AccessNetworkType.EUTRAN,
                 ServiceState.FREQUENCY_RANGE_UNKNOWN, new int[txLevelCount], 0));
-        specificInfoList.add(new ActivityStatsTechSpecificInfo(AccessNetwork.CDMA2000,
+        specificInfoList.add(new ActivityStatsTechSpecificInfo(
+                AccessNetworkConstants.AccessNetworkType.CDMA2000,
                 ServiceState.FREQUENCY_RANGE_UNKNOWN, new int[txLevelCount], 0));
-        specificInfoList.add(new ActivityStatsTechSpecificInfo(AccessNetwork.IWLAN,
+        specificInfoList.add(new ActivityStatsTechSpecificInfo(
+                AccessNetworkConstants.AccessNetworkType.IWLAN,
                 ServiceState.FREQUENCY_RANGE_UNKNOWN, new int[txLevelCount], 0));
-        specificInfoList.add(new ActivityStatsTechSpecificInfo(AccessNetwork.NGRAN,
+        specificInfoList.add(new ActivityStatsTechSpecificInfo(
+                AccessNetworkConstants.AccessNetworkType.NGRAN,
                 ServiceState.FREQUENCY_RANGE_UNKNOWN, new int[txLevelCount], 0));
-        specificInfoList.add(new ActivityStatsTechSpecificInfo(AccessNetwork.NGRAN,
+        specificInfoList.add(new ActivityStatsTechSpecificInfo(
+                AccessNetworkConstants.AccessNetworkType.NGRAN,
                 ServiceState.FREQUENCY_RANGE_LOW, new int[txLevelCount], 0));
-        specificInfoList.add(new ActivityStatsTechSpecificInfo(AccessNetwork.NGRAN,
+        specificInfoList.add(new ActivityStatsTechSpecificInfo(
+                AccessNetworkConstants.AccessNetworkType.NGRAN,
                 ServiceState.FREQUENCY_RANGE_MID, new int[txLevelCount], 0));
-        specificInfoList.add(new ActivityStatsTechSpecificInfo(AccessNetwork.NGRAN,
+        specificInfoList.add(new ActivityStatsTechSpecificInfo(
+                AccessNetworkConstants.AccessNetworkType.NGRAN,
                 ServiceState.FREQUENCY_RANGE_HIGH, new int[txLevelCount], 0));
-        specificInfoList.add(new ActivityStatsTechSpecificInfo(AccessNetwork.NGRAN,
+        specificInfoList.add(new ActivityStatsTechSpecificInfo(
+                AccessNetworkConstants.AccessNetworkType.NGRAN,
                 ServiceState.FREQUENCY_RANGE_MMWAVE, new int[txLevelCount], 0));
 
         final ActivityStatsTechSpecificInfo[] specificInfos = specificInfoList.toArray(
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsUserLifecycleTests.java b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsUserLifecycleTests.java
index b27ba88..face849 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsUserLifecycleTests.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsUserLifecycleTests.java
@@ -28,12 +28,12 @@
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.UserManager;
-import android.support.test.uiautomator.UiDevice;
 import android.util.ArraySet;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
+import androidx.test.uiautomator.UiDevice;
 
 import org.junit.After;
 import org.junit.Before;
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsRule.java b/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
index 3135215..534aa89 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
@@ -34,6 +34,7 @@
 
 import androidx.test.InstrumentationRegistry;
 
+import com.android.internal.os.CpuScalingPolicies;
 import com.android.internal.os.PowerProfile;
 import com.android.internal.power.EnergyConsumerStats;
 
@@ -59,6 +60,9 @@
 
     private BatteryUsageStats mBatteryUsageStats;
     private boolean mScreenOn;
+    private boolean mDefaultCpuScalingPolicy = true;
+    private SparseArray<int[]> mCpusByPolicy = new SparseArray<>();
+    private SparseArray<int[]> mFreqsByPolicy = new SparseArray<>();
 
     public BatteryUsageStatsRule() {
         this(0, null);
@@ -74,6 +78,13 @@
         mMockClock.currentTime = currentTime;
         mBatteryStats = new MockBatteryStatsImpl(mMockClock, historyDir);
         mBatteryStats.setPowerProfile(mPowerProfile);
+
+        mCpusByPolicy.put(0, new int[]{0, 1, 2, 3});
+        mCpusByPolicy.put(4, new int[]{4, 5, 6, 7});
+        mFreqsByPolicy.put(0, new int[]{300000, 1000000, 2000000});
+        mFreqsByPolicy.put(4, new int[]{300000, 1000000, 2500000, 3000000});
+        mBatteryStats.setCpuScalingPolicies(new CpuScalingPolicies(mCpusByPolicy, mFreqsByPolicy));
+
         mBatteryStats.onSystemReady();
     }
 
@@ -82,6 +93,19 @@
         return this;
     }
 
+    public BatteryUsageStatsRule setCpuScalingPolicy(int policy, int[] relatedCpus,
+            int[] frequencies) {
+        if (mDefaultCpuScalingPolicy) {
+            mCpusByPolicy.clear();
+            mFreqsByPolicy.clear();
+            mDefaultCpuScalingPolicy = false;
+        }
+        mCpusByPolicy.put(policy, relatedCpus);
+        mFreqsByPolicy.put(policy, frequencies);
+        mBatteryStats.setCpuScalingPolicies(new CpuScalingPolicies(mCpusByPolicy, mFreqsByPolicy));
+        return this;
+    }
+
     public BatteryUsageStatsRule setAveragePower(String key, double value) {
         when(mPowerProfile.getAveragePower(key)).thenReturn(value);
         when(mPowerProfile.getAveragePowerOrDefault(eq(key), anyDouble())).thenReturn(value);
@@ -103,23 +127,14 @@
         return this;
     }
 
-    public BatteryUsageStatsRule setNumCpuClusters(int number) {
-        when(mPowerProfile.getNumCpuClusters()).thenReturn(number);
+    public BatteryUsageStatsRule setAveragePowerForCpuScalingPolicy(int policy, double value) {
+        when(mPowerProfile.getAveragePowerForCpuScalingPolicy(policy)).thenReturn(value);
         return this;
     }
 
-    public BatteryUsageStatsRule setNumSpeedStepsInCpuCluster(int cluster, int speeds) {
-        when(mPowerProfile.getNumSpeedStepsInCpuCluster(cluster)).thenReturn(speeds);
-        return this;
-    }
-
-    public BatteryUsageStatsRule setAveragePowerForCpuCluster(int cluster, double value) {
-        when(mPowerProfile.getAveragePowerForCpuCluster(cluster)).thenReturn(value);
-        return this;
-    }
-
-    public BatteryUsageStatsRule setAveragePowerForCpuCore(int cluster, int step, double value) {
-        when(mPowerProfile.getAveragePowerForCpuCore(cluster, step)).thenReturn(value);
+    public BatteryUsageStatsRule setAveragePowerForCpuScalingStep(int policy, int step,
+            double value) {
+        when(mPowerProfile.getAveragePowerForCpuScalingStep(policy, step)).thenReturn(value);
         return this;
     }
 
@@ -193,6 +208,12 @@
         return mPowerProfile;
     }
 
+    public CpuScalingPolicies getCpuScalingPolicies() {
+        synchronized (mBatteryStats) {
+            return mBatteryStats.getCpuScalingPolicies();
+        }
+    }
+
     public MockBatteryStatsImpl getBatteryStats() {
         return mBatteryStats;
     }
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BstatsCpuTimesValidationTest.java b/services/tests/servicestests/src/com/android/server/power/stats/BstatsCpuTimesValidationTest.java
index 25a5b29..ccace40 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/BstatsCpuTimesValidationTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/BstatsCpuTimesValidationTest.java
@@ -47,7 +47,6 @@
 import android.os.Process;
 import android.os.SystemClock;
 import android.provider.Settings;
-import android.support.test.uiautomator.UiDevice;
 import android.util.ArrayMap;
 import android.util.DebugUtils;
 import android.util.KeyValueListParser;
@@ -56,6 +55,7 @@
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
+import androidx.test.uiautomator.UiDevice;
 
 import com.android.frameworks.coretests.aidl.ICmdCallback;
 import com.android.frameworks.coretests.aidl.ICmdReceiver;
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/CpuPowerCalculatorTest.java b/services/tests/servicestests/src/com/android/server/power/stats/CpuPowerCalculatorTest.java
index ced996f3..888bc62 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/CpuPowerCalculatorTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/CpuPowerCalculatorTest.java
@@ -65,15 +65,14 @@
     @Rule
     public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule()
             .setAveragePower(PowerProfile.POWER_CPU_ACTIVE, 720)
-            .setNumCpuClusters(2)
-            .setNumSpeedStepsInCpuCluster(0, 2)
-            .setNumSpeedStepsInCpuCluster(1, 2)
-            .setAveragePowerForCpuCluster(0, 360)
-            .setAveragePowerForCpuCluster(1, 480)
-            .setAveragePowerForCpuCore(0, 0, 300)
-            .setAveragePowerForCpuCore(0, 1, 400)
-            .setAveragePowerForCpuCore(1, 0, 500)
-            .setAveragePowerForCpuCore(1, 1, 600);
+            .setCpuScalingPolicy(0, new int[]{0, 1}, new int[]{100, 200})
+            .setCpuScalingPolicy(2, new int[]{2, 3}, new int[]{300, 400})
+            .setAveragePowerForCpuScalingPolicy(0, 360)
+            .setAveragePowerForCpuScalingPolicy(2, 480)
+            .setAveragePowerForCpuScalingStep(0, 0, 300)
+            .setAveragePowerForCpuScalingStep(0, 1, 400)
+            .setAveragePowerForCpuScalingStep(2, 0, 500)
+            .setAveragePowerForCpuScalingStep(2, 1, 600);
 
     private final KernelCpuSpeedReader[] mMockKernelCpuSpeedReaders = new KernelCpuSpeedReader[]{
             mock(KernelCpuSpeedReader.class),
@@ -179,7 +178,8 @@
         mStatsRule.getUidStats(APP_UID1).getProcessStatsLocked("bar").addCpuTimeLocked(5432, 2345);
 
         CpuPowerCalculator calculator =
-                new CpuPowerCalculator(mStatsRule.getPowerProfile());
+                new CpuPowerCalculator(mStatsRule.getCpuScalingPolicies(),
+                        mStatsRule.getPowerProfile());
 
         mStatsRule.apply(BatteryUsageStatsRule.POWER_PROFILE_MODEL_ONLY, calculator);
 
@@ -246,8 +246,8 @@
         mStatsRule.getUidStats(APP_UID1).getProcessStatsLocked("foo").addCpuTimeLocked(4321, 1234);
         mStatsRule.getUidStats(APP_UID1).getProcessStatsLocked("bar").addCpuTimeLocked(5432, 2345);
 
-        CpuPowerCalculator calculator =
-                new CpuPowerCalculator(mStatsRule.getPowerProfile());
+        CpuPowerCalculator calculator = new CpuPowerCalculator(mStatsRule.getCpuScalingPolicies(),
+                mStatsRule.getPowerProfile());
 
         mStatsRule.apply(calculator);
 
@@ -287,7 +287,6 @@
         when(mMockUserInfoProvider.exists(anyInt())).thenReturn(true);
 
         when(mMockCpuUidFreqTimeReader.allUidTimesAvailable()).thenReturn(true);
-        when(mMockCpuUidFreqTimeReader.readFreqs(any())).thenReturn(new long[]{100, 200, 300, 400});
 
         when(mMockKernelSingleUidTimeReader.singleUidCpuTimesAvailable()).thenReturn(true);
 
@@ -353,8 +352,8 @@
         mStatsRule.getBatteryStats().updateCpuTimeLocked(true, true, null);
         mStatsRule.getBatteryStats().updateCpuTimesForAllUids();
 
-        CpuPowerCalculator calculator =
-                new CpuPowerCalculator(mStatsRule.getPowerProfile());
+        CpuPowerCalculator calculator = new CpuPowerCalculator(mStatsRule.getCpuScalingPolicies(),
+                mStatsRule.getPowerProfile());
 
         mStatsRule.apply(new BatteryUsageStatsQuery.Builder()
                 .powerProfileModeledOnly()
@@ -461,8 +460,8 @@
         clusterChargesUC[1] += 20000000;
         mStatsRule.getBatteryStats().updateCpuTimeLocked(true, true, clusterChargesUC);
 
-        CpuPowerCalculator calculator =
-                new CpuPowerCalculator(mStatsRule.getPowerProfile());
+        CpuPowerCalculator calculator = new CpuPowerCalculator(mStatsRule.getCpuScalingPolicies(),
+                mStatsRule.getPowerProfile());
 
         mStatsRule.apply(new BatteryUsageStatsQuery.Builder()
                 .includePowerModels()
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/KernelWakelockReaderTest.java b/services/tests/servicestests/src/com/android/server/power/stats/KernelWakelockReaderTest.java
index 70643d9..c0f3c77 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/KernelWakelockReaderTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/KernelWakelockReaderTest.java
@@ -43,8 +43,13 @@
         }
 
         public ProcFileBuilder addLine(String name, int count, long timeMillis) {
+            return addLine(name, count, timeMillis, 0);
+        }
+
+        public ProcFileBuilder addLine(String name, int count, long timeMillis, long activeTimeMs) {
             ensureHeader();
-            mStringBuilder.append(name).append("\t").append(count).append("\t0\t0\t0\t0\t")
+            mStringBuilder.append(name).append("\t").append(count).append("\t0\t0\t0\t")
+                    .append(activeTimeMs).append("\t")
                     .append(timeMillis).append("\t0\t0\t0\n");
             return this;
         }
@@ -66,10 +71,19 @@
      * @return the created WakeLockInfo object.
      */
     private WakeLockInfo createWakeLockInfo(String name, int activeCount, long totalTime) {
+        return createWakeLockInfo(name, activeCount, totalTime, 0);
+    }
+
+    private WakeLockInfo createWakeLockInfo(String name, int activeCount, long totalTime,
+            long activeTimeMs) {
         WakeLockInfo info = new WakeLockInfo();
         info.name = name;
         info.activeCount = activeCount;
         info.totalTime = totalTime;
+        info.activeTime = activeTimeMs;
+        if (activeTimeMs != 0) {
+            info.isActive = true;
+        }
         return info;
     }
 
@@ -118,7 +132,7 @@
     @SmallTest
     public void testOneWakelock() throws Exception {
         byte[] buffer = new ProcFileBuilder()
-                .addLine("Wakelock", 34, 123) // Milliseconds
+                .addLine("Wakelock", 34, 123, 456) // Milliseconds
                 .getBytes();
 
         KernelWakelockStats staleStats = mReader.parseProcWakelocks(buffer, buffer.length, true,
@@ -129,8 +143,9 @@
         assertTrue(staleStats.containsKey("Wakelock"));
 
         KernelWakelockStats.Entry entry = staleStats.get("Wakelock");
-        assertEquals(34, entry.mCount);
-        assertEquals(123 * 1000, entry.mTotalTime); // Microseconds
+        assertEquals(34, entry.count);
+        assertEquals(123 * 1000, entry.totalTimeUs); // Microseconds
+        assertEquals(456 * 1000, entry.activeTimeUs); // Microseconds
     }
 
     @SmallTest
@@ -163,8 +178,8 @@
         assertTrue(staleStats.containsKey("Wakelock"));
 
         KernelWakelockStats.Entry entry = staleStats.get("Wakelock");
-        assertEquals(2, entry.mCount);
-        assertEquals(20 * 1000, entry.mTotalTime); // Microseconds
+        assertEquals(2, entry.count);
+        assertEquals(20 * 1000, entry.totalTimeUs); // Microseconds
     }
 
     @SmallTest
@@ -203,7 +218,7 @@
     @SmallTest
     public void testOneWakeLockInfo() {
         WakeLockInfo[] wlStats = new WakeLockInfo[1];
-        wlStats[0] = createWakeLockInfo("WakeLock", 20, 1000);   // Milliseconds
+        wlStats[0] = createWakeLockInfo("WakeLock", 20, 1000, 500);   // Milliseconds
 
         KernelWakelockStats staleStats = mReader.updateWakelockStats(wlStats,
                 new KernelWakelockStats());
@@ -213,8 +228,9 @@
         assertTrue(staleStats.containsKey("WakeLock"));
 
         KernelWakelockStats.Entry entry = staleStats.get("WakeLock");
-        assertEquals(20, entry.mCount);
-        assertEquals(1000 * 1000, entry.mTotalTime);   // Microseconds
+        assertEquals(20, entry.count);
+        assertEquals(1000 * 1000, entry.totalTimeUs);   // Microseconds
+        assertEquals(500 * 1000, entry.activeTimeUs);   // Microseconds
     }
 
     @SmallTest
@@ -232,12 +248,12 @@
         assertTrue(staleStats.containsKey("WakeLock2"));
 
         KernelWakelockStats.Entry entry1 = staleStats.get("WakeLock1");
-        assertEquals(10, entry1.mCount);
-        assertEquals(1000 * 1000, entry1.mTotalTime); // Microseconds
+        assertEquals(10, entry1.count);
+        assertEquals(1000 * 1000, entry1.totalTimeUs); // Microseconds
 
         KernelWakelockStats.Entry entry2 = staleStats.get("WakeLock2");
-        assertEquals(20, entry2.mCount);
-        assertEquals(2000 * 1000, entry2.mTotalTime); // Microseconds
+        assertEquals(20, entry2.count);
+        assertEquals(2000 * 1000, entry2.totalTimeUs); // Microseconds
     }
 
     @SmallTest
@@ -253,8 +269,8 @@
 
         assertTrue(staleStats.containsKey("WakeLock1"));
         KernelWakelockStats.Entry entry = staleStats.get("WakeLock1");
-        assertEquals(10, entry.mCount);
-        assertEquals(1000 * 1000, entry.mTotalTime);  // Microseconds
+        assertEquals(10, entry.count);
+        assertEquals(1000 * 1000, entry.totalTimeUs);  // Microseconds
 
         wlStats[0] = createWakeLockInfo("WakeLock2", 20, 2000); // Milliseconds
 
@@ -265,8 +281,8 @@
         assertFalse(staleStats.containsKey("WakeLock1"));
         assertTrue(staleStats.containsKey("WakeLock2"));
         entry = staleStats.get("WakeLock2");
-        assertEquals(20, entry.mCount);
-        assertEquals(2000 * 1000, entry.mTotalTime); // Micro seconds
+        assertEquals(20, entry.count);
+        assertEquals(2000 * 1000, entry.totalTimeUs); // Micro seconds
     }
 
 // -------------------- Aggregate  Wakelock Stats Tests --------------------
@@ -298,8 +314,8 @@
         assertTrue(staleStats.containsKey("Wakelock"));
 
         KernelWakelockStats.Entry entry = staleStats.get("Wakelock");
-        assertEquals(34, entry.mCount);
-        assertEquals(1000 * 123, entry.mTotalTime);  // Microseconds
+        assertEquals(34, entry.count);
+        assertEquals(1000 * 123, entry.totalTimeUs);  // Microseconds
     }
 
     @SmallTest
@@ -317,8 +333,8 @@
         assertTrue(staleStats.containsKey("WakeLock"));
 
         KernelWakelockStats.Entry entry = staleStats.get("WakeLock");
-        assertEquals(10, entry.mCount);
-        assertEquals(1000 * 1000, entry.mTotalTime);  // Microseconds
+        assertEquals(10, entry.count);
+        assertEquals(1000 * 1000, entry.totalTimeUs);  // Microseconds
     }
 
     @SmallTest
@@ -337,13 +353,13 @@
 
         assertTrue(staleStats.containsKey("WakeLock1"));
         KernelWakelockStats.Entry entry1 = staleStats.get("WakeLock1");
-        assertEquals(34, entry1.mCount);
-        assertEquals(123 * 1000, entry1.mTotalTime);  // Microseconds
+        assertEquals(34, entry1.count);
+        assertEquals(123 * 1000, entry1.totalTimeUs);  // Microseconds
 
         assertTrue(staleStats.containsKey("WakeLock2"));
         KernelWakelockStats.Entry entry2 = staleStats.get("WakeLock2");
-        assertEquals(10, entry2.mCount);
-        assertEquals(1000 * 1000, entry2.mTotalTime);  // Microseconds
+        assertEquals(10, entry2.count);
+        assertEquals(1000 * 1000, entry2.totalTimeUs);  // Microseconds
     }
 
     @SmallTest
@@ -368,20 +384,20 @@
         assertTrue(staleStats.containsKey("WakeLock4"));
 
         KernelWakelockStats.Entry entry1 = staleStats.get("WakeLock1");
-        assertEquals(34, entry1.mCount);
-        assertEquals(123 * 1000, entry1.mTotalTime); // Microseconds
+        assertEquals(34, entry1.count);
+        assertEquals(123 * 1000, entry1.totalTimeUs); // Microseconds
 
         KernelWakelockStats.Entry entry2 = staleStats.get("WakeLock2");
-        assertEquals(46, entry2.mCount);
-        assertEquals(345 * 1000, entry2.mTotalTime); // Microseconds
+        assertEquals(46, entry2.count);
+        assertEquals(345 * 1000, entry2.totalTimeUs); // Microseconds
 
         KernelWakelockStats.Entry entry3 = staleStats.get("WakeLock3");
-        assertEquals(10, entry3.mCount);
-        assertEquals(1000 * 1000, entry3.mTotalTime); // Microseconds
+        assertEquals(10, entry3.count);
+        assertEquals(1000 * 1000, entry3.totalTimeUs); // Microseconds
 
         KernelWakelockStats.Entry entry4 = staleStats.get("WakeLock4");
-        assertEquals(20, entry4.mCount);
-        assertEquals(2000 * 1000, entry4.mTotalTime); // Microseconds
+        assertEquals(20, entry4.count);
+        assertEquals(2000 * 1000, entry4.totalTimeUs); // Microseconds
 
         buffer = new ProcFileBuilder()
                 .addLine("WakeLock1", 45, 789)  // Milliseconds
@@ -401,11 +417,11 @@
         assertFalse(staleStats.containsKey("WakeLock3"));
 
         entry1 = staleStats.get("WakeLock1");
-        assertEquals(45 + 56, entry1.mCount);
-        assertEquals((789 + 123) * 1000, entry1.mTotalTime);  // Microseconds
+        assertEquals(45 + 56, entry1.count);
+        assertEquals((789 + 123) * 1000, entry1.totalTimeUs);  // Microseconds
 
         entry2 = staleStats.get("WakeLock4");
-        assertEquals(40, entry2.mCount);
-        assertEquals(4000 * 1000, entry4.mTotalTime); // Microseconds
+        assertEquals(40, entry2.count);
+        assertEquals(4000 * 1000, entry4.totalTimeUs); // Microseconds
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/MobileRadioPowerCalculatorTest.java b/services/tests/servicestests/src/com/android/server/power/stats/MobileRadioPowerCalculatorTest.java
index 2e647c4..d3ec0d7 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/MobileRadioPowerCalculatorTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/MobileRadioPowerCalculatorTest.java
@@ -27,7 +27,6 @@
 import static org.mockito.Mockito.when;
 
 import android.app.usage.NetworkStatsManager;
-import android.hardware.radio.V1_5.AccessNetwork;
 import android.net.NetworkCapabilities;
 import android.net.NetworkStats;
 import android.os.BatteryConsumer;
@@ -35,6 +34,7 @@
 import android.os.BatteryUsageStatsQuery;
 import android.os.Process;
 import android.os.UidBatteryConsumer;
+import android.telephony.AccessNetworkConstants;
 import android.telephony.ActivityStatsTechSpecificInfo;
 import android.telephony.CellSignalStrength;
 import android.telephony.DataConnectionRealTimeInfo;
@@ -76,6 +76,9 @@
                 .initMeasuredEnergyStatsLocked();
         BatteryStatsImpl stats = mStatsRule.getBatteryStats();
 
+        // The first ModemActivityInfo doesn't count up.
+        setInitialEmptyModemActivityInfo(stats);
+
         // Scan for a cell
         stats.notePhoneStateLocked(ServiceState.STATE_OUT_OF_SERVICE,
                 TelephonyManager.SIM_STATE_READY,
@@ -193,6 +196,9 @@
                 .initMeasuredEnergyStatsLocked();
         BatteryStatsImpl stats = mStatsRule.getBatteryStats();
 
+        // The first ModemActivityInfo doesn't count up.
+        setInitialEmptyModemActivityInfo(stats);
+
         // Scan for a cell
         stats.notePhoneStateLocked(ServiceState.STATE_OUT_OF_SERVICE,
                 TelephonyManager.SIM_STATE_READY,
@@ -248,22 +254,24 @@
         mStatsRule.setNetworkStats(networkStats);
 
         ActivityStatsTechSpecificInfo cdmaInfo = new ActivityStatsTechSpecificInfo(
-                AccessNetwork.CDMA2000, ServiceState.FREQUENCY_RANGE_UNKNOWN,
+                AccessNetworkConstants.AccessNetworkType.CDMA2000,
+                ServiceState.FREQUENCY_RANGE_UNKNOWN,
                 new int[]{10, 11, 12, 13, 14}, 15);
         ActivityStatsTechSpecificInfo lteInfo = new ActivityStatsTechSpecificInfo(
-                AccessNetwork.EUTRAN, ServiceState.FREQUENCY_RANGE_UNKNOWN,
+                AccessNetworkConstants.AccessNetworkType.EUTRAN,
+                ServiceState.FREQUENCY_RANGE_UNKNOWN,
                 new int[]{20, 21, 22, 23, 24}, 25);
         ActivityStatsTechSpecificInfo nrLowFreqInfo = new ActivityStatsTechSpecificInfo(
-                AccessNetwork.NGRAN, ServiceState.FREQUENCY_RANGE_LOW,
+                AccessNetworkConstants.AccessNetworkType.NGRAN, ServiceState.FREQUENCY_RANGE_LOW,
                 new int[]{30, 31, 32, 33, 34}, 35);
         ActivityStatsTechSpecificInfo nrMidFreqInfo = new ActivityStatsTechSpecificInfo(
-                AccessNetwork.NGRAN, ServiceState.FREQUENCY_RANGE_MID,
+                AccessNetworkConstants.AccessNetworkType.NGRAN, ServiceState.FREQUENCY_RANGE_MID,
                 new int[]{40, 41, 42, 43, 44}, 45);
         ActivityStatsTechSpecificInfo nrHighFreqInfo = new ActivityStatsTechSpecificInfo(
-                AccessNetwork.NGRAN, ServiceState.FREQUENCY_RANGE_HIGH,
+                AccessNetworkConstants.AccessNetworkType.NGRAN, ServiceState.FREQUENCY_RANGE_HIGH,
                 new int[]{50, 51, 52, 53, 54}, 55);
         ActivityStatsTechSpecificInfo nrMmwaveFreqInfo = new ActivityStatsTechSpecificInfo(
-                AccessNetwork.NGRAN, ServiceState.FREQUENCY_RANGE_MMWAVE,
+                AccessNetworkConstants.AccessNetworkType.NGRAN, ServiceState.FREQUENCY_RANGE_MMWAVE,
                 new int[]{60, 61, 62, 63, 64}, 65);
 
         ActivityStatsTechSpecificInfo[] ratInfos =
@@ -345,6 +353,9 @@
                 .initMeasuredEnergyStatsLocked();
         BatteryStatsImpl stats = mStatsRule.getBatteryStats();
 
+        // The first ModemActivityInfo doesn't count up.
+        setInitialEmptyModemActivityInfo(stats);
+
         // Scan for a cell
         stats.notePhoneStateLocked(ServiceState.STATE_OUT_OF_SERVICE,
                 TelephonyManager.SIM_STATE_READY,
@@ -581,6 +592,9 @@
                 .initMeasuredEnergyStatsLocked();
         BatteryStatsImpl stats = mStatsRule.getBatteryStats();
 
+        // The first ModemActivityInfo doesn't count up.
+        setInitialEmptyModemActivityInfo(stats);
+
         // Scan for a cell
         stats.notePhoneStateLocked(ServiceState.STATE_OUT_OF_SERVICE,
                 TelephonyManager.SIM_STATE_READY,
@@ -658,6 +672,9 @@
                 .initMeasuredEnergyStatsLocked();
         BatteryStatsImpl stats = mStatsRule.getBatteryStats();
 
+        // The first ModemActivityInfo doesn't count up.
+        setInitialEmptyModemActivityInfo(stats);
+
         stats.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH, 0, -1,
                 0, 0);
 
@@ -719,22 +736,24 @@
         mStatsRule.setNetworkStats(networkStats);
 
         ActivityStatsTechSpecificInfo cdmaInfo = new ActivityStatsTechSpecificInfo(
-                AccessNetwork.CDMA2000, ServiceState.FREQUENCY_RANGE_UNKNOWN,
+                AccessNetworkConstants.AccessNetworkType.CDMA2000,
+                ServiceState.FREQUENCY_RANGE_UNKNOWN,
                 new int[]{10, 11, 12, 13, 14}, 15);
         ActivityStatsTechSpecificInfo lteInfo = new ActivityStatsTechSpecificInfo(
-                AccessNetwork.EUTRAN, ServiceState.FREQUENCY_RANGE_UNKNOWN,
+                AccessNetworkConstants.AccessNetworkType.EUTRAN,
+                ServiceState.FREQUENCY_RANGE_UNKNOWN,
                 new int[]{20, 21, 22, 23, 24}, 25);
         ActivityStatsTechSpecificInfo nrLowFreqInfo = new ActivityStatsTechSpecificInfo(
-                AccessNetwork.NGRAN, ServiceState.FREQUENCY_RANGE_LOW,
+                AccessNetworkConstants.AccessNetworkType.NGRAN, ServiceState.FREQUENCY_RANGE_LOW,
                 new int[]{30, 31, 32, 33, 34}, 35);
         ActivityStatsTechSpecificInfo nrMidFreqInfo = new ActivityStatsTechSpecificInfo(
-                AccessNetwork.NGRAN, ServiceState.FREQUENCY_RANGE_MID,
+                AccessNetworkConstants.AccessNetworkType.NGRAN, ServiceState.FREQUENCY_RANGE_MID,
                 new int[]{40, 41, 42, 43, 44}, 45);
         ActivityStatsTechSpecificInfo nrHighFreqInfo = new ActivityStatsTechSpecificInfo(
-                AccessNetwork.NGRAN, ServiceState.FREQUENCY_RANGE_HIGH,
+                AccessNetworkConstants.AccessNetworkType.NGRAN, ServiceState.FREQUENCY_RANGE_HIGH,
                 new int[]{50, 51, 52, 53, 54}, 55);
         ActivityStatsTechSpecificInfo nrMmwaveFreqInfo = new ActivityStatsTechSpecificInfo(
-                AccessNetwork.NGRAN, ServiceState.FREQUENCY_RANGE_MMWAVE,
+                AccessNetworkConstants.AccessNetworkType.NGRAN, ServiceState.FREQUENCY_RANGE_MMWAVE,
                 new int[]{60, 61, 62, 63, 64}, 65);
 
         ActivityStatsTechSpecificInfo[] ratInfos =
@@ -837,6 +856,9 @@
                 .initMeasuredEnergyStatsLocked();
         BatteryStatsImpl stats = mStatsRule.getBatteryStats();
 
+        // The first ModemActivityInfo doesn't count up.
+        setInitialEmptyModemActivityInfo(stats);
+
         stats.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH, 0, -1,
                 0, 0);
 
@@ -1019,4 +1041,10 @@
         assertThat(uidConsumer.getConsumedPower(background)).isWithin(PRECISION).of(2.08130);
         assertThat(uidConsumer.getConsumedPower(fgs)).isWithin(PRECISION).of(0);
     }
+
+    public void setInitialEmptyModemActivityInfo(BatteryStatsImpl stats) {
+        // Initial empty ModemActivityInfo.
+        final ModemActivityInfo emptyMai = new ModemActivityInfo(0L, 0L, 0L, new int[5], 0L);
+        stats.noteModemControllerActivity(emptyMai, 0, 0, 0, mNetworkStatsManager);
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/MockBatteryStatsImpl.java b/services/tests/servicestests/src/com/android/server/power/stats/MockBatteryStatsImpl.java
index d78ab867..6d3f1f2 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/MockBatteryStatsImpl.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/MockBatteryStatsImpl.java
@@ -16,18 +16,18 @@
 
 package com.android.server.power.stats;
 
-import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
 
 import android.annotation.NonNull;
 import android.app.usage.NetworkStatsManager;
 import android.net.NetworkStats;
 import android.os.Handler;
 import android.os.Looper;
+import android.util.SparseArray;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.os.Clock;
+import com.android.internal.os.CpuScalingPolicies;
 import com.android.internal.os.KernelCpuSpeedReader;
 import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidActiveTimeReader;
 import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidClusterTimeReader;
@@ -73,7 +73,7 @@
         };
 
         mCpuUidFreqTimeReader = mock(KernelCpuUidFreqTimeReader.class);
-        when(mCpuUidFreqTimeReader.readFreqs(any())).thenReturn(new long[]{100, 200});
+        mKernelWakelockReader = null;
     }
 
     public void initMeasuredEnergyStats(String[] customBucketNames) {
@@ -135,8 +135,25 @@
             @NonNull NetworkStatsManager networkStatsManager) {
         return mNetworkStats;
     }
+
     public MockBatteryStatsImpl setPowerProfile(PowerProfile powerProfile) {
         mPowerProfile = powerProfile;
+        setTestCpuScalingPolicies();
+        return this;
+    }
+
+    public MockBatteryStatsImpl setTestCpuScalingPolicies() {
+        SparseArray<int[]> cpusByPolicy = new SparseArray<>();
+        cpusByPolicy.put(0, new int[]{0});
+        SparseArray<int[]> freqsByPolicy = new SparseArray<>();
+        freqsByPolicy.put(0, new int[]{100, 200, 300});
+
+        setCpuScalingPolicies(new CpuScalingPolicies(freqsByPolicy, freqsByPolicy));
+        return this;
+    }
+
+    public MockBatteryStatsImpl setCpuScalingPolicies(CpuScalingPolicies cpuScalingPolicies) {
+        mCpuScalingPolicies = cpuScalingPolicies;
         return this;
     }
 
@@ -173,6 +190,11 @@
         return this;
     }
 
+    public MockBatteryStatsImpl setKernelWakelockReader(KernelWakelockReader reader) {
+        mKernelWakelockReader = reader;
+        return this;
+    }
+
     public MockBatteryStatsImpl setSystemServerCpuThreadReader(
             SystemServerCpuThreadReader systemServerCpuThreadReader) {
         mSystemServerCpuThreadReader = systemServerCpuThreadReader;
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/SystemServicePowerCalculatorTest.java b/services/tests/servicestests/src/com/android/server/power/stats/SystemServicePowerCalculatorTest.java
index 3c5c0a3..4dae2d5 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/SystemServicePowerCalculatorTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/SystemServicePowerCalculatorTest.java
@@ -62,15 +62,14 @@
     @Rule
     public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule()
             .setAveragePower(PowerProfile.POWER_CPU_ACTIVE, 720)
-            .setNumCpuClusters(2)
-            .setNumSpeedStepsInCpuCluster(0, 2)
-            .setNumSpeedStepsInCpuCluster(1, 2)
-            .setAveragePowerForCpuCluster(0, 360)
-            .setAveragePowerForCpuCluster(1, 480)
-            .setAveragePowerForCpuCore(0, 0, 300)
-            .setAveragePowerForCpuCore(0, 1, 400)
-            .setAveragePowerForCpuCore(1, 0, 500)
-            .setAveragePowerForCpuCore(1, 1, 600);
+            .setCpuScalingPolicy(0, new int[]{0, 1}, new int[]{100, 200})
+            .setCpuScalingPolicy(2, new int[]{2, 3}, new int[]{300, 400})
+            .setAveragePowerForCpuScalingPolicy(0, 360)
+            .setAveragePowerForCpuScalingPolicy(2, 480)
+            .setAveragePowerForCpuScalingStep(0, 0, 300)
+            .setAveragePowerForCpuScalingStep(0, 1, 400)
+            .setAveragePowerForCpuScalingStep(2, 0, 500)
+            .setAveragePowerForCpuScalingStep(2, 1, 600);
 
     @Mock
     private BatteryStatsImpl.UserInfoProvider mMockUserInfoProvider;
@@ -113,9 +112,10 @@
         prepareBatteryStats(null);
 
         SystemServicePowerCalculator calculator = new SystemServicePowerCalculator(
-                mStatsRule.getPowerProfile());
+                mStatsRule.getCpuScalingPolicies(), mStatsRule.getPowerProfile());
 
-        mStatsRule.apply(new CpuPowerCalculator(mStatsRule.getPowerProfile()), calculator);
+        mStatsRule.apply(new CpuPowerCalculator(mStatsRule.getCpuScalingPolicies(),
+                mStatsRule.getPowerProfile()), calculator);
 
         assertThat(mStatsRule.getUidBatteryConsumer(APP_UID1)
                 .getConsumedPower(BatteryConsumer.POWER_COMPONENT_SYSTEM_SERVICES))
@@ -145,9 +145,10 @@
         prepareBatteryStats(new long[]{50000000, 100000000});
 
         SystemServicePowerCalculator calculator = new SystemServicePowerCalculator(
-                mStatsRule.getPowerProfile());
+                mStatsRule.getCpuScalingPolicies(), mStatsRule.getPowerProfile());
 
-        mStatsRule.apply(new CpuPowerCalculator(mStatsRule.getPowerProfile()), calculator);
+        mStatsRule.apply(new CpuPowerCalculator(mStatsRule.getCpuScalingPolicies(),
+                mStatsRule.getPowerProfile()), calculator);
 
         assertThat(mStatsRule.getUidBatteryConsumer(APP_UID1)
                 .getConsumedPower(BatteryConsumer.POWER_COMPONENT_SYSTEM_SERVICES))
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/wakeups/CpuWakeupStatsTest.java b/services/tests/servicestests/src/com/android/server/power/stats/wakeups/CpuWakeupStatsTest.java
index d181419..b81b776 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/wakeups/CpuWakeupStatsTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/wakeups/CpuWakeupStatsTest.java
@@ -106,7 +106,7 @@
         for (int i = 0; i < 100; i++) {
             final long now = mRandom.nextLong(retention + 1, 100 * retention);
             obj.noteWakeupTimeAndReason(now, i, KERNEL_REASON_SOUND_TRIGGER_IRQ);
-            assertThat(obj.mWakeupEvents.closestIndexOnOrBefore(now - retention)).isLessThan(0);
+            assertThat(obj.mWakeupEvents.lastIndexOnOrBefore(now - retention)).isLessThan(0);
         }
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/wakeups/WakingActivityHistoryTest.java b/services/tests/servicestests/src/com/android/server/power/stats/wakeups/WakingActivityHistoryTest.java
index b02618e..99bc25a 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/wakeups/WakingActivityHistoryTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/wakeups/WakingActivityHistoryTest.java
@@ -18,8 +18,8 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import android.util.LongSparseArray;
 import android.util.SparseIntArray;
-import android.util.TimeSparseArray;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 
@@ -73,7 +73,7 @@
         assertThat(history.mWakingActivity.contains(subsystem + 1)).isFalse();
         assertThat(history.mWakingActivity.contains(subsystem - 1)).isFalse();
 
-        final TimeSparseArray<SparseIntArray> recordedHistory = history.mWakingActivity.get(
+        final LongSparseArray<SparseIntArray> recordedHistory = history.mWakingActivity.get(
                 subsystem);
 
         assertThat(recordedHistory.size()).isEqualTo(1);
@@ -119,7 +119,7 @@
         assertThat(history.mWakingActivity.contains(subsystem + 1)).isFalse();
         assertThat(history.mWakingActivity.contains(subsystem - 1)).isFalse();
 
-        final TimeSparseArray<SparseIntArray> recordedHistory = history.mWakingActivity.get(
+        final LongSparseArray<SparseIntArray> recordedHistory = history.mWakingActivity.get(
                 subsystem);
 
         assertThat(recordedHistory.size()).isEqualTo(1);
@@ -266,7 +266,7 @@
             final long time = random.nextLong(firstTime + mTestRetention + 100,
                     456 * mTestRetention);
             history.recordActivity(subsystem, time, new SparseIntArray());
-            assertThat(history.mWakingActivity.get(subsystem).closestIndexOnOrBefore(
+            assertThat(history.mWakingActivity.get(subsystem).lastIndexOnOrBefore(
                     time - mTestRetention)).isLessThan(0);
         }
     }
diff --git a/services/tests/servicestests/src/com/android/server/powerstats/IntervalRandomNoiseGeneratorTest.java b/services/tests/servicestests/src/com/android/server/powerstats/IntervalRandomNoiseGeneratorTest.java
new file mode 100644
index 0000000..9962146
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/powerstats/IntervalRandomNoiseGeneratorTest.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.powerstats;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import org.jetbrains.annotations.NotNull;
+import org.junit.Test;
+
+import java.util.function.Supplier;
+
+public class IntervalRandomNoiseGeneratorTest {
+
+    @Test
+    public void parameterizedDistribution() {
+         // Assert closeness to theoretical distribution
+        assertDistribution(3.0,
+                0.0392,
+                0.2617,
+                0.6990);
+
+        assertDistribution(5.0,
+                0.0003,
+                0.0098,
+                0.0676,
+                0.2502,
+                0.6720);
+
+        assertDistribution(9.0,
+                0.0000,
+                0.0002,
+                0.0097,
+                0.1242,
+                0.8658);
+    }
+
+    private void assertDistribution(double alpha, Double... expectedBuckets) {
+        IntervalRandomNoiseGenerator generator = new IntervalRandomNoiseGenerator(alpha);
+        generator.reseed(42);  // Make test repeatable
+        final int sampleCount = 1000;
+        final int bucketCount = expectedBuckets.length;
+        int[] histogram = buildHistogram(() -> {
+            generator.refresh();
+            return generator.addNoise(100, 200, 12345);
+        }, sampleCount, bucketCount, 100, 200);
+
+        for (int i = 0; i < expectedBuckets.length; i++) {
+            assertWithMessage("Bucket #" + i)
+                    .that((double) histogram[i] / sampleCount)
+                    .isWithin(0.05)
+                    .of(expectedBuckets[i]);
+        }
+    }
+
+    @NotNull
+    private int[] buildHistogram(Supplier<Long> generator, int sampleCount,
+            int bucketCount, int lowBound, int highBound) {
+        int[] buckets = new int[bucketCount];
+        for (int i = 0; i < sampleCount; i++) {
+            long sample = generator.get();
+            assertThat(sample).isAtLeast(lowBound);
+            assertThat(sample).isAtMost(highBound);
+            buckets[(int) ((double) (sample - lowBound) / (highBound - lowBound) * bucketCount)]++;
+        }
+        return buckets;
+    }
+
+    @Test
+    public void stickiness() {
+        IntervalRandomNoiseGenerator generator = new IntervalRandomNoiseGenerator(9);
+        generator.reseed(42);  // Make test repeatable
+
+        long value1a = generator.addNoise(1000, 5000, 123);
+        long value1b = generator.addNoise(1000, 5000, 123);
+        long value1c = generator.addNoise(1000, 5000, 123);
+        assertThat(value1b).isEqualTo(value1a);
+        assertThat(value1c).isEqualTo(value1a);
+
+        // Different stickyKey
+        long value2a = generator.addNoise(1000, 5000, 321);
+        long value2b = generator.addNoise(1000, 5000, 321);
+        assertThat(value2a).isNotEqualTo(value1a);
+        assertThat(value2b).isEqualTo(value2a);
+
+        generator.refresh();
+
+        // Same stickyKey after a refresh - different value
+        long value3 = generator.addNoise(1000, 5000, 123);
+        long value4 = generator.addNoise(1000, 5000, 321);
+        assertThat(value3).isNotEqualTo(value1a);
+        assertThat(value4).isNotEqualTo(value2a);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/powerstats/PowerStatsServiceTest.java b/services/tests/servicestests/src/com/android/server/powerstats/PowerStatsServiceTest.java
index 304fe5a..2ffe4aa 100644
--- a/services/tests/servicestests/src/com/android/server/powerstats/PowerStatsServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/powerstats/PowerStatsServiceTest.java
@@ -16,6 +16,8 @@
 
 package com.android.server.powerstats;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
@@ -30,10 +32,17 @@
 import android.hardware.power.stats.State;
 import android.hardware.power.stats.StateResidency;
 import android.hardware.power.stats.StateResidencyResult;
+import android.os.Bundle;
+import android.os.IPowerStatsService;
 import android.os.Looper;
+import android.os.PowerMonitor;
+import android.os.ResultReceiver;
+import android.provider.DeviceConfig;
+import android.provider.DeviceConfigInterface;
 
 import androidx.test.InstrumentationRegistry;
 
+import com.android.internal.os.Clock;
 import com.android.server.SystemService;
 import com.android.server.powerstats.PowerStatsHALWrapper.IPowerStatsHALWrapper;
 import com.android.server.powerstats.ProtoStreamUtils.ChannelUtils;
@@ -46,6 +55,7 @@
 import com.android.server.powerstats.nano.StateProto;
 import com.android.server.powerstats.nano.StateResidencyProto;
 import com.android.server.powerstats.nano.StateResidencyResultProto;
+import com.android.server.testutils.FakeDeviceConfigInterface;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -58,13 +68,15 @@
 import java.nio.ByteBuffer;
 import java.nio.file.Files;
 import java.util.Arrays;
+import java.util.Map;
 import java.util.Random;
+import java.util.stream.Collectors;
 
 /**
  * Tests for {@link com.android.server.powerstats.PowerStatsService}.
  *
  * Build/Install/Run:
- *  atest FrameworksServicesTests:PowerStatsServiceTest
+ * atest FrameworksServicesTests:PowerStatsServiceTest
  */
 public class PowerStatsServiceTest {
     private static final String TAG = PowerStatsServiceTest.class.getSimpleName();
@@ -87,16 +99,35 @@
     private static final int POWER_ENTITY_COUNT = 3;
     private static final int STATE_INFO_COUNT = 5;
     private static final int STATE_RESIDENCY_COUNT = 4;
+    private static final int APP_UID = 10042;
 
     private final Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
     private PowerStatsService mService;
+    private TestPowerStatsHALWrapper mPowerStatsHALWrapper = new TestPowerStatsHALWrapper();
     private File mDataStorageDir;
     private TimerTrigger mTimerTrigger;
     private BatteryTrigger mBatteryTrigger;
     private PowerStatsLogger mPowerStatsLogger;
+    private MockClock mMockClock = new MockClock();
+    private DeviceConfigInterface mMockDeviceConfig = new FakeDeviceConfigInterface();
+    private IntervalRandomNoiseGenerator mMockNoiseGenerator = new IntervalRandomNoiseGenerator(42);
+
+    private class MockClock extends Clock {
+        public long realtime;
+
+        @Override
+        public long elapsedRealtime() {
+            return realtime;
+        }
+    }
 
     private final PowerStatsService.Injector mInjector = new PowerStatsService.Injector() {
-        private TestPowerStatsHALWrapper mTestPowerStatsHALWrapper = new TestPowerStatsHALWrapper();
+
+        @Override
+        Clock getClock() {
+            return mMockClock;
+        }
+
         @Override
         File createDataStoragePath() {
             if (mDataStorageDir == null) {
@@ -142,7 +173,7 @@
 
         @Override
         IPowerStatsHALWrapper getPowerStatsHALWrapperImpl() {
-            return mTestPowerStatsHALWrapper;
+            return mPowerStatsHALWrapper;
         }
 
         @Override
@@ -152,29 +183,41 @@
                 String residencyFilename, String residencyCacheFilename,
                 IPowerStatsHALWrapper powerStatsHALWrapper) {
             mPowerStatsLogger = new PowerStatsLogger(context, looper, dataStoragePath,
-                meterFilename, meterCacheFilename,
-                modelFilename, modelCacheFilename,
-                residencyFilename, residencyCacheFilename,
-                powerStatsHALWrapper);
+                    meterFilename, meterCacheFilename,
+                    modelFilename, modelCacheFilename,
+                    residencyFilename, residencyCacheFilename,
+                    powerStatsHALWrapper);
             return mPowerStatsLogger;
         }
 
         @Override
         BatteryTrigger createBatteryTrigger(Context context, PowerStatsLogger powerStatsLogger) {
             mBatteryTrigger = new BatteryTrigger(context, powerStatsLogger,
-                false /* trigger enabled */);
+                    false /* trigger enabled */);
             return mBatteryTrigger;
         }
 
         @Override
         TimerTrigger createTimerTrigger(Context context, PowerStatsLogger powerStatsLogger) {
             mTimerTrigger = new TimerTrigger(context, powerStatsLogger,
-                false /* trigger enabled */);
+                    false /* trigger enabled */);
             return mTimerTrigger;
         }
+
+        DeviceConfigInterface getDeviceConfig() {
+            return mMockDeviceConfig;
+        }
+
+        @Override
+        IntervalRandomNoiseGenerator createIntervalRandomNoiseGenerator() {
+            return mMockNoiseGenerator;
+        }
     };
 
     public static final class TestPowerStatsHALWrapper implements IPowerStatsHALWrapper {
+        public EnergyConsumerResult[] energyConsumerResults;
+        public EnergyMeasurement[] energyMeasurements;
+
         @Override
         public PowerEntity[] getPowerEntityInfo() {
             PowerEntity[] powerEntityList = new PowerEntity[POWER_ENTITY_COUNT];
@@ -195,12 +238,12 @@
         @Override
         public StateResidencyResult[] getStateResidency(int[] powerEntityIds) {
             StateResidencyResult[] stateResidencyResultList =
-                new StateResidencyResult[POWER_ENTITY_COUNT];
+                    new StateResidencyResult[POWER_ENTITY_COUNT];
             for (int i = 0; i < stateResidencyResultList.length; i++) {
                 stateResidencyResultList[i] = new StateResidencyResult();
                 stateResidencyResultList[i].id = i;
                 stateResidencyResultList[i].stateResidencyData =
-                    new StateResidency[STATE_RESIDENCY_COUNT];
+                        new StateResidency[STATE_RESIDENCY_COUNT];
                 for (int j = 0; j < stateResidencyResultList[i].stateResidencyData.length; j++) {
                     stateResidencyResultList[i].stateResidencyData[j] = new StateResidency();
                     stateResidencyResultList[i].stateResidencyData[j].id = j;
@@ -226,24 +269,26 @@
             return energyConsumerList;
         }
 
-        @Override
-        public EnergyConsumerResult[] getEnergyConsumed(int[] energyConsumerIds) {
-            EnergyConsumerResult[] energyConsumedList =
-                new EnergyConsumerResult[ENERGY_CONSUMER_COUNT];
-            for (int i = 0; i < energyConsumedList.length; i++) {
-                energyConsumedList[i] = new EnergyConsumerResult();
-                energyConsumedList[i].id = i;
-                energyConsumedList[i].timestampMs = i;
-                energyConsumedList[i].energyUWs = i;
-                energyConsumedList[i].attribution =
-                    new EnergyConsumerAttribution[ENERGY_CONSUMER_ATTRIBUTION_COUNT];
-                for (int j = 0; j < energyConsumedList[i].attribution.length; j++) {
-                    energyConsumedList[i].attribution[j] = new EnergyConsumerAttribution();
-                    energyConsumedList[i].attribution[j].uid = j;
-                    energyConsumedList[i].attribution[j].energyUWs = j;
+        private void buildEnergyConsumerResult() {
+            energyConsumerResults = new EnergyConsumerResult[ENERGY_CONSUMER_COUNT];
+            for (int i = 0; i < energyConsumerResults.length; i++) {
+                energyConsumerResults[i] = new EnergyConsumerResult();
+                energyConsumerResults[i].id = i;
+                energyConsumerResults[i].timestampMs = i;
+                energyConsumerResults[i].energyUWs = i;
+                energyConsumerResults[i].attribution =
+                        new EnergyConsumerAttribution[ENERGY_CONSUMER_ATTRIBUTION_COUNT];
+                for (int j = 0; j < energyConsumerResults[i].attribution.length; j++) {
+                    energyConsumerResults[i].attribution[j] = new EnergyConsumerAttribution();
+                    energyConsumerResults[i].attribution[j].uid = j;
+                    energyConsumerResults[i].attribution[j].energyUWs = j;
                 }
             }
-            return energyConsumedList;
+        }
+
+        @Override
+        public EnergyConsumerResult[] getEnergyConsumed(int[] energyConsumerIds) {
+            return energyConsumerResults;
         }
 
         @Override
@@ -258,17 +303,20 @@
             return energyMeterList;
         }
 
+        private void buildEnergyMeasurements() {
+            energyMeasurements = new EnergyMeasurement[ENERGY_METER_COUNT];
+            for (int i = 0; i < energyMeasurements.length; i++) {
+                energyMeasurements[i] = new EnergyMeasurement();
+                energyMeasurements[i].id = i;
+                energyMeasurements[i].timestampMs = i;
+                energyMeasurements[i].durationMs = i;
+                energyMeasurements[i].energyUWs = i;
+            }
+        }
+
         @Override
         public EnergyMeasurement[] readEnergyMeter(int[] channelIds) {
-            EnergyMeasurement[] energyMeasurementList = new EnergyMeasurement[ENERGY_METER_COUNT];
-            for (int i = 0; i < energyMeasurementList.length; i++) {
-                energyMeasurementList[i] = new EnergyMeasurement();
-                energyMeasurementList[i].id = i;
-                energyMeasurementList[i].timestampMs = i;
-                energyMeasurementList[i].durationMs = i;
-                energyMeasurementList[i].energyUWs = i;
-            }
-            return energyMeasurementList;
+            return energyMeasurements;
         }
 
         @Override
@@ -286,6 +334,7 @@
     public void testWrittenMeterDataMatchesReadIncidentReportData()
             throws InterruptedException, IOException {
         mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
+        mPowerStatsHALWrapper.buildEnergyMeasurements();
 
         // Write data to on-device storage.
         mTimerTrigger.logPowerStatsData(PowerStatsLogger.MSG_LOG_TO_DATA_STORAGE_HIGH_FREQUENCY);
@@ -331,6 +380,8 @@
             throws InterruptedException, IOException {
         mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
 
+        mPowerStatsHALWrapper.buildEnergyConsumerResult();
+
         // Write data to on-device storage.
         mTimerTrigger.logPowerStatsData(PowerStatsLogger.MSG_LOG_TO_DATA_STORAGE_LOW_FREQUENCY);
 
@@ -368,7 +419,7 @@
                     == ENERGY_CONSUMER_ATTRIBUTION_COUNT);
             for (int j = 0; j < pssProto.energyConsumerResult[i].attribution.length; j++) {
                 assertTrue(pssProto.energyConsumerResult[i].attribution[j].uid == j);
-                assertTrue(pssProto.energyConsumerResult[i].attribution[j].energyUws  == j);
+                assertTrue(pssProto.energyConsumerResult[i].attribution[j].energyUws == j);
             }
         }
     }
@@ -1008,4 +1059,143 @@
         assertTrue(modelFile.exists());
         assertTrue(residencyFile.exists());
     }
+
+    private static class GetSupportedPowerMonitorsResult extends ResultReceiver {
+        public PowerMonitor[] powerMonitors;
+
+        GetSupportedPowerMonitorsResult() {
+            super(null);
+        }
+
+        @Override
+        protected void onReceiveResult(int resultCode, Bundle resultData) {
+            powerMonitors = resultData.getParcelableArray(IPowerStatsService.KEY_MONITORS,
+                    PowerMonitor.class);
+        }
+    }
+
+    @Test
+    public void getSupportedPowerMonitors() {
+        GetSupportedPowerMonitorsResult result = new GetSupportedPowerMonitorsResult();
+        mService.getSupportedPowerMonitorsImpl(result);
+        assertThat(result.powerMonitors).isNotNull();
+        assertThat(Arrays.stream(result.powerMonitors).map(pm -> pm.name).toList())
+                .containsAtLeast(
+                        "energyconsumer0",
+                        "BLUETOOTH/1",
+                        "[channelname0]:channelsubsystem0",
+                        "[channelname1]:channelsubsystem1");
+    }
+
+    private static class GetPowerMonitorsResult extends ResultReceiver {
+        public int resultCode;
+        public long[] energyUws;
+        public long[] timestamps;
+
+        GetPowerMonitorsResult() {
+            super(null);
+        }
+
+        @Override
+        protected void onReceiveResult(int resultCode, Bundle resultData) {
+            this.resultCode = resultCode;
+            if (resultData != null) {
+                energyUws = resultData.getLongArray(IPowerStatsService.KEY_ENERGY);
+                timestamps = resultData.getLongArray(IPowerStatsService.KEY_TIMESTAMPS);
+            }
+        }
+    }
+
+    @Test
+    public void getPowerMonitors() {
+        mMockClock.realtime = 10 * 60_000;
+        mMockNoiseGenerator.reseed(314);
+
+        mPowerStatsHALWrapper.buildEnergyConsumerResult();
+        EnergyConsumerResult[] energyConsumerResults = mPowerStatsHALWrapper.energyConsumerResults;
+        for (int i = 0; i < energyConsumerResults.length; i++) {
+            energyConsumerResults[i].energyUWs = 42 + 100 * i;
+            energyConsumerResults[i].timestampMs = mMockClock.realtime + 100 * i;
+        }
+
+        mPowerStatsHALWrapper.buildEnergyMeasurements();
+        EnergyMeasurement[] energyMeasurements = mPowerStatsHALWrapper.energyMeasurements;
+        for (int i = 0; i < energyMeasurements.length; i++) {
+            energyMeasurements[i].energyUWs = 314 + 200 * i;
+            energyMeasurements[i].timestampMs = mMockClock.realtime + 200 * i;
+        }
+
+        GetSupportedPowerMonitorsResult supportedPowerMonitorsResult =
+                new GetSupportedPowerMonitorsResult();
+        mService.getSupportedPowerMonitorsImpl(supportedPowerMonitorsResult);
+        Map<String, PowerMonitor> map =
+                Arrays.stream(supportedPowerMonitorsResult.powerMonitors)
+                        .collect(Collectors.toMap(pm -> pm.name, pm -> pm));
+        PowerMonitor consumer1 = map.get("energyconsumer0");
+        PowerMonitor consumer2 = map.get("BLUETOOTH/1");
+        PowerMonitor measurement1 = map.get("[channelname0]:channelsubsystem0");
+        PowerMonitor measurement2 = map.get("[channelname1]:channelsubsystem1");
+
+        GetPowerMonitorsResult result = new GetPowerMonitorsResult();
+        mService.getPowerMonitorReadingsImpl(
+                new int[]{consumer1.index, consumer2.index, measurement1.index,
+                        measurement2.index}, result, APP_UID);
+
+        assertThat(result.energyUws).isEqualTo(new long[]{42, 142, 314, 514});
+        assertThat(result.timestamps).isEqualTo(new long[]{600_000, 600_100, 600_000, 600_200});
+
+        // Test caching/throttling
+        mMockClock.realtime += 1;
+
+        for (EnergyConsumerResult energyConsumerResult : energyConsumerResults) {
+            energyConsumerResult.energyUWs = 300;
+            energyConsumerResult.timestampMs = mMockClock.realtime + 300;
+        }
+
+        for (EnergyMeasurement energyMeasurement : energyMeasurements) {
+            energyMeasurement.energyUWs = 400;
+            energyMeasurement.timestampMs = mMockClock.realtime + 400;
+        }
+
+        mService.getPowerMonitorReadingsImpl(new int[]{consumer1.index, measurement1.index},
+                result, APP_UID);
+
+        assertThat(result.energyUws).isEqualTo(new long[]{42, 314});
+        assertThat(result.timestamps).isEqualTo(new long[]{600_000, 600_000});
+
+        mMockClock.realtime += 10 * 60000;
+
+        mService.getPowerMonitorReadingsImpl(new int[]{consumer1.index, measurement1.index},
+                result, APP_UID);
+
+        // This time, random noise is added
+        assertThat(result.energyUws).isEqualTo(new long[]{298, 399});
+        assertThat(result.timestamps).isEqualTo(new long[]{600_301, 600_401});
+    }
+
+    @Test
+    public void featureFlag() {
+        mMockDeviceConfig.setProperty(DeviceConfig.NAMESPACE_BATTERY_STATS,
+                PowerStatsService.KEY_POWER_MONITOR_API_ENABLED, "false", false);
+
+        mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY);
+
+        GetSupportedPowerMonitorsResult supportedPowerMonitorsResult =
+                new GetSupportedPowerMonitorsResult();
+        mService.getSupportedPowerMonitorsImpl(supportedPowerMonitorsResult);
+        assertThat(supportedPowerMonitorsResult.powerMonitors).isNotNull();
+        assertThat(supportedPowerMonitorsResult.powerMonitors).isEmpty();
+
+        GetPowerMonitorsResult getPowerMonitorsResult = new GetPowerMonitorsResult();
+        mService.getPowerMonitorReadingsImpl(new int[]{0}, getPowerMonitorsResult, APP_UID);
+        assertThat(getPowerMonitorsResult.resultCode).isEqualTo(
+                IPowerStatsService.RESULT_UNSUPPORTED_POWER_MONITOR);
+
+        mMockDeviceConfig.setProperty(DeviceConfig.NAMESPACE_BATTERY_STATS,
+                PowerStatsService.KEY_POWER_MONITOR_API_ENABLED, "true", false);
+        supportedPowerMonitorsResult = new GetSupportedPowerMonitorsResult();
+        mService.getSupportedPowerMonitorsImpl(supportedPowerMonitorsResult);
+        assertThat(Arrays.stream(supportedPowerMonitorsResult.powerMonitors)
+                .map(pm -> pm.name).toList()).contains("energyconsumer0");
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/timedetector/FakeServiceConfigAccessor.java b/services/tests/servicestests/src/com/android/server/timedetector/FakeServiceConfigAccessor.java
index 93464cd..d9bc74d 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/FakeServiceConfigAccessor.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/FakeServiceConfigAccessor.java
@@ -35,7 +35,7 @@
 
     private final List<StateChangeListener> mConfigurationInternalChangeListeners =
             new ArrayList<>();
-    private ConfigurationInternal mConfigurationInternal;
+    private ConfigurationInternal mCurrentUserConfigurationInternal;
 
     @Override
     public void addConfigurationInternalChangeListener(StateChangeListener listener) {
@@ -49,21 +49,23 @@
 
     @Override
     public ConfigurationInternal getCurrentUserConfigurationInternal() {
-        return mConfigurationInternal;
+        return mCurrentUserConfigurationInternal;
     }
 
     @Override
     public boolean updateConfiguration(
-            @UserIdInt int userID, @NonNull TimeConfiguration requestedChanges,
+            @UserIdInt int userId, @NonNull TimeConfiguration requestedChanges,
             boolean bypassUserPolicyChecks) {
-        assertNotNull(mConfigurationInternal);
+        assertNotNull(mCurrentUserConfigurationInternal);
         assertNotNull(requestedChanges);
 
+        ConfigurationInternal toUpdate = getConfigurationInternal(userId);
+
         // Simulate the real strategy's behavior: the new configuration will be updated to be the
         // old configuration merged with the new if the user has the capability to up the settings.
         // Then, if the configuration changed, the change listener is invoked.
         TimeCapabilitiesAndConfig capabilitiesAndConfig =
-                mConfigurationInternal.createCapabilitiesAndConfig(bypassUserPolicyChecks);
+                toUpdate.createCapabilitiesAndConfig(bypassUserPolicyChecks);
         TimeCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
         TimeConfiguration configuration = capabilitiesAndConfig.getConfiguration();
         TimeConfiguration newConfiguration =
@@ -73,28 +75,36 @@
         }
 
         if (!newConfiguration.equals(capabilitiesAndConfig.getConfiguration())) {
-            mConfigurationInternal = mConfigurationInternal.merge(newConfiguration);
+            mCurrentUserConfigurationInternal = toUpdate.merge(newConfiguration);
 
             // Note: Unlike the real strategy, the listeners are invoked synchronously.
-            simulateConfigurationChangeForTests();
+            notifyConfigurationChange();
         }
         return true;
     }
 
-    void initializeConfiguration(ConfigurationInternal configurationInternal) {
-        mConfigurationInternal = configurationInternal;
+
+    void initializeCurrentUserConfiguration(ConfigurationInternal configurationInternal) {
+        mCurrentUserConfigurationInternal = configurationInternal;
     }
 
-    void simulateConfigurationChangeForTests() {
-        for (StateChangeListener listener : mConfigurationInternalChangeListeners) {
-            listener.onChange();
-        }
+    void simulateCurrentUserConfigurationInternalChange(
+            ConfigurationInternal configurationInternal) {
+        mCurrentUserConfigurationInternal = configurationInternal;
+        // Note: Unlike the real strategy, the listeners are invoked synchronously.
+        notifyConfigurationChange();
     }
 
     @Override
     public ConfigurationInternal getConfigurationInternal(int userId) {
         assertEquals("Multi-user testing not supported currently",
-                userId, mConfigurationInternal.getUserId());
-        return mConfigurationInternal;
+                userId, mCurrentUserConfigurationInternal.getUserId());
+        return mCurrentUserConfigurationInternal;
+    }
+
+    private void notifyConfigurationChange() {
+        for (StateChangeListener listener : mConfigurationInternalChangeListeners) {
+            listener.onChange();
+        }
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/timedetector/FakeTimeDetectorStrategy.java b/services/tests/servicestests/src/com/android/server/timedetector/FakeTimeDetectorStrategy.java
index 87aa272..a7a9c0c 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/FakeTimeDetectorStrategy.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/FakeTimeDetectorStrategy.java
@@ -18,6 +18,8 @@
 
 import android.annotation.UserIdInt;
 import android.app.time.ExternalTimeSuggestion;
+import android.app.time.TimeCapabilitiesAndConfig;
+import android.app.time.TimeConfiguration;
 import android.app.time.TimeState;
 import android.app.time.UnixEpochTime;
 import android.app.timedetector.ManualTimeSuggestion;
@@ -31,10 +33,20 @@
  * in tests.
  */
 public class FakeTimeDetectorStrategy implements TimeDetectorStrategy {
+    private final FakeServiceConfigAccessor mFakeServiceConfigAccessor;
+
     // State
     private TimeState mTimeState;
     private NetworkTimeSuggestion mLatestNetworkTimeSuggestion;
 
+    FakeTimeDetectorStrategy() {
+        mFakeServiceConfigAccessor = new FakeServiceConfigAccessor();
+    }
+
+    void initializeConfiguration(ConfigurationInternal configuration) {
+        mFakeServiceConfigAccessor.initializeCurrentUserConfiguration(configuration);
+    }
+
     @Override
     public TimeState getTimeState() {
         return mTimeState;
@@ -51,6 +63,26 @@
     }
 
     @Override
+    public void addChangeListener(StateChangeListener listener) {
+        mFakeServiceConfigAccessor.addConfigurationInternalChangeListener(listener);
+    }
+
+    @Override
+    public TimeCapabilitiesAndConfig getCapabilitiesAndConfig(int userId,
+            boolean bypassUserPolicyChecks) {
+        ConfigurationInternal configurationInternal =
+                mFakeServiceConfigAccessor.getConfigurationInternal(userId);
+        return configurationInternal.createCapabilitiesAndConfig(bypassUserPolicyChecks);
+    }
+
+    @Override
+    public boolean updateConfiguration(int userId, TimeConfiguration configuration,
+            boolean bypassUserPolicyChecks) {
+        return mFakeServiceConfigAccessor.updateConfiguration(
+                userId, configuration, bypassUserPolicyChecks);
+    }
+
+    @Override
     public void suggestTelephonyTime(TelephonyTimeSuggestion suggestion) {
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorInternalImplTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorInternalImplTest.java
index a0845a6..de5a37b 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorInternalImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorInternalImplTest.java
@@ -89,7 +89,7 @@
     public void testGetCapabilitiesAndConfigForDpm() throws Exception {
         final boolean autoDetectionEnabled = true;
         ConfigurationInternal testConfig = createConfigurationInternal(autoDetectionEnabled);
-        mFakeServiceConfigAccessorSpy.initializeConfiguration(testConfig);
+        mFakeServiceConfigAccessorSpy.initializeCurrentUserConfiguration(testConfig);
 
         TimeCapabilitiesAndConfig actualCapabilitiesAndConfig =
                 mTimeDetectorInternal.getCapabilitiesAndConfigForDpm();
@@ -108,7 +108,8 @@
         final boolean autoDetectionEnabled = false;
         ConfigurationInternal initialConfigurationInternal =
                 createConfigurationInternal(autoDetectionEnabled);
-        mFakeServiceConfigAccessorSpy.initializeConfiguration(initialConfigurationInternal);
+        mFakeServiceConfigAccessorSpy.initializeCurrentUserConfiguration(
+                initialConfigurationInternal);
 
         TimeConfiguration timeConfiguration = new TimeConfiguration.Builder()
                 .setAutoDetectionEnabled(true)
diff --git a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
index daa6823..6b2d4b0 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
@@ -83,7 +83,6 @@
     private HandlerThread mHandlerThread;
     private TestHandler mTestHandler;
     private TestCallerIdentityInjector mTestCallerIdentityInjector;
-    private FakeServiceConfigAccessor mFakeServiceConfigAccessorSpy;
     private FakeTimeDetectorStrategy mFakeTimeDetectorStrategySpy;
 
     private NtpTrustedTime mMockNtpTrustedTime;
@@ -101,13 +100,12 @@
         mTestCallerIdentityInjector = new TestCallerIdentityInjector();
         mTestCallerIdentityInjector.initializeCallingUserId(ARBITRARY_USER_ID);
 
-        mFakeServiceConfigAccessorSpy = spy(new FakeServiceConfigAccessor());
         mFakeTimeDetectorStrategySpy = spy(new FakeTimeDetectorStrategy());
         mMockNtpTrustedTime = mock(NtpTrustedTime.class);
 
         mTimeDetectorService = new TimeDetectorService(
                 mMockContext, mTestHandler, mTestCallerIdentityInjector,
-                mFakeServiceConfigAccessorSpy, mFakeTimeDetectorStrategySpy, mMockNtpTrustedTime);
+                mFakeTimeDetectorStrategySpy, mMockNtpTrustedTime);
     }
 
     @After
@@ -132,14 +130,14 @@
 
         ConfigurationInternal configuration =
                 createConfigurationInternal(true /* autoDetectionEnabled*/);
-        mFakeServiceConfigAccessorSpy.initializeConfiguration(configuration);
+        mFakeTimeDetectorStrategySpy.initializeConfiguration(configuration);
 
         TimeCapabilitiesAndConfig actualCapabilitiesAndConfig =
                 mTimeDetectorService.getCapabilitiesAndConfig();
         verify(mMockContext).enforceCallingPermission(
                 eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), anyString());
         int expectedUserId = mTestCallerIdentityInjector.getCallingUserId();
-        verify(mFakeServiceConfigAccessorSpy).getConfigurationInternal(expectedUserId);
+        verify(mFakeTimeDetectorStrategySpy).getCapabilitiesAndConfig(expectedUserId, false);
 
         boolean bypassUserPolicyChecks = false;
         TimeCapabilitiesAndConfig expectedCapabilitiesAndConfig =
@@ -174,7 +172,7 @@
     public void testListenerRegistrationAndCallbacks() throws Exception {
         ConfigurationInternal initialConfiguration =
                 createConfigurationInternal(false /* autoDetectionEnabled */);
-        mFakeServiceConfigAccessorSpy.initializeConfiguration(initialConfiguration);
+        mFakeTimeDetectorStrategySpy.initializeConfiguration(initialConfiguration);
 
         IBinder mockListenerBinder = mock(IBinder.class);
         ITimeDetectorListener mockListener = mock(ITimeDetectorListener.class);
diff --git a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
index 4df21e0..dd58135 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
@@ -29,14 +29,22 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
 
 import android.annotation.UserIdInt;
 import android.app.time.ExternalTimeSuggestion;
+import android.app.time.TimeCapabilitiesAndConfig;
+import android.app.time.TimeConfiguration;
 import android.app.time.TimeState;
 import android.app.time.UnixEpochTime;
 import android.app.timedetector.ManualTimeSuggestion;
 import android.app.timedetector.TelephonyTimeSuggestion;
 import android.os.TimestampedValue;
+import android.util.IndentingPrintWriter;
 
 import com.android.server.SystemClockTime.TimeConfidence;
 import com.android.server.timedetector.TimeDetectorStrategy.Origin;
@@ -47,14 +55,12 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import java.io.PrintWriter;
 import java.time.Duration;
 import java.time.Instant;
 import java.time.LocalDateTime;
 import java.time.ZoneOffset;
 import java.util.ArrayList;
 import java.util.List;
-import java.util.Objects;
 
 import junitparams.JUnitParamsRunner;
 import junitparams.Parameters;
@@ -120,13 +126,108 @@
                     .build();
 
     private FakeEnvironment mFakeEnvironment;
+    private FakeServiceConfigAccessor mFakeServiceConfigAccessorSpy;
+    private TimeDetectorStrategyImpl mTimeDetectorStrategy;
 
     @Before
     public void setUp() {
         mFakeEnvironment = new FakeEnvironment();
-        mFakeEnvironment.initializeConfig(CONFIG_AUTO_DISABLED);
         mFakeEnvironment.initializeFakeClocks(
                 ARBITRARY_CLOCK_INITIALIZATION_INFO, TIME_CONFIDENCE_LOW);
+
+        mFakeServiceConfigAccessorSpy = spy(new FakeServiceConfigAccessor());
+        mFakeServiceConfigAccessorSpy.initializeCurrentUserConfiguration(CONFIG_AUTO_DISABLED);
+
+        mTimeDetectorStrategy = new TimeDetectorStrategyImpl(
+                mFakeEnvironment, mFakeServiceConfigAccessorSpy);
+    }
+
+    @Test
+    public void testChangeListenerBehavior() throws Exception {
+        TestStateChangeListener stateChangeListener = new TestStateChangeListener();
+        mTimeDetectorStrategy.addChangeListener(stateChangeListener);
+
+        boolean bypassUserPolicyChecks = false;
+
+        // Report a config change, but not one that actually changes anything.
+        {
+            mFakeServiceConfigAccessorSpy.simulateCurrentUserConfigurationInternalChange(
+                    CONFIG_AUTO_DISABLED);
+            assertStateChangeNotificationsSent(stateChangeListener, 0);
+            assertEquals(CONFIG_AUTO_DISABLED,
+                    mTimeDetectorStrategy.getCachedCapabilitiesAndConfigForTests());
+        }
+
+        // Report a config change that actually changes something.
+        {
+            mFakeServiceConfigAccessorSpy.simulateCurrentUserConfigurationInternalChange(
+                    CONFIG_AUTO_ENABLED);
+            assertStateChangeNotificationsSent(stateChangeListener, 1);
+            assertEquals(CONFIG_AUTO_ENABLED,
+                    mTimeDetectorStrategy.getCachedCapabilitiesAndConfigForTests());
+        }
+
+        // Perform a (current user) update via the strategy.
+        {
+            TimeConfiguration requestedChanges =
+                    new TimeConfiguration.Builder().setAutoDetectionEnabled(false).build();
+            mTimeDetectorStrategy.updateConfiguration(
+                    ARBITRARY_USER_ID, requestedChanges, bypassUserPolicyChecks);
+            assertStateChangeNotificationsSent(stateChangeListener, 1);
+        }
+    }
+
+    // Current user behavior: the strategy caches and returns the latest configuration.
+    @Test
+    public void testReadAndWriteConfiguration() throws Exception {
+        ConfigurationInternal currentUserConfig = CONFIG_AUTO_ENABLED;
+        mFakeServiceConfigAccessorSpy.simulateCurrentUserConfigurationInternalChange(
+                currentUserConfig);
+
+        final boolean bypassUserPolicyChecks = false;
+
+        ConfigurationInternal cachedConfigurationInternal =
+                mTimeDetectorStrategy.getCachedCapabilitiesAndConfigForTests();
+        assertEquals(currentUserConfig, cachedConfigurationInternal);
+
+        // Confirm getCapabilitiesAndConfig() does not call through to the ServiceConfigAccessor.
+        {
+            reset(mFakeServiceConfigAccessorSpy);
+            TimeCapabilitiesAndConfig actualCapabilitiesAndConfig =
+                    mTimeDetectorStrategy.getCapabilitiesAndConfig(
+                            currentUserConfig.getUserId(), bypassUserPolicyChecks);
+            verify(mFakeServiceConfigAccessorSpy, never()).getConfigurationInternal(
+                    currentUserConfig.getUserId());
+
+            TimeCapabilitiesAndConfig expectedCapabilitiesAndConfig =
+                    currentUserConfig.createCapabilitiesAndConfig(bypassUserPolicyChecks);
+            assertEquals(expectedCapabilitiesAndConfig.getCapabilities(),
+                    actualCapabilitiesAndConfig.getCapabilities());
+            assertEquals(expectedCapabilitiesAndConfig.getConfiguration(),
+                    actualCapabilitiesAndConfig.getConfiguration());
+        }
+
+        // Confirm updateConfiguration() calls through to the ServiceConfigAccessor and updates
+        // the cached copy.
+        {
+            boolean newAutoDetectionEnabled =
+                    !cachedConfigurationInternal.getAutoDetectionEnabledBehavior();
+            TimeConfiguration requestedChanges = new TimeConfiguration.Builder()
+                    .setAutoDetectionEnabled(newAutoDetectionEnabled)
+                    .build();
+            ConfigurationInternal expectedConfigAfterChange =
+                    new ConfigurationInternal.Builder(cachedConfigurationInternal)
+                            .setAutoDetectionEnabledSetting(newAutoDetectionEnabled)
+                            .build();
+
+            reset(mFakeServiceConfigAccessorSpy);
+            mTimeDetectorStrategy.updateConfiguration(
+                    currentUserConfig.getUserId(), requestedChanges, bypassUserPolicyChecks);
+            verify(mFakeServiceConfigAccessorSpy, times(1)).updateConfiguration(
+                    currentUserConfig.getUserId(), requestedChanges, bypassUserPolicyChecks);
+            assertEquals(expectedConfigAfterChange,
+                    mTimeDetectorStrategy.getCachedCapabilitiesAndConfigForTests());
+        }
     }
 
     @Test
@@ -1939,20 +2040,14 @@
 
         private final List<Runnable> mAsyncRunnables = new ArrayList<>();
 
-        private ConfigurationInternal mConfigurationInternal;
         private boolean mWakeLockAcquired;
         private long mElapsedRealtimeMillis;
         private long mSystemClockMillis;
         private int mSystemClockConfidence = TIME_CONFIDENCE_LOW;
-        private StateChangeListener mConfigurationInternalChangeListener;
 
         // Tracking operations.
         private boolean mSystemClockWasSet;
 
-        void initializeConfig(ConfigurationInternal configurationInternal) {
-            mConfigurationInternal = configurationInternal;
-        }
-
         public void initializeFakeClocks(
                 TimestampedValue<Instant> timeInfo, @TimeConfidence int timeConfidence) {
             pokeElapsedRealtimeMillis(timeInfo.getReferenceTimeMillis());
@@ -1960,16 +2055,6 @@
         }
 
         @Override
-        public void setConfigurationInternalChangeListener(StateChangeListener listener) {
-            mConfigurationInternalChangeListener = Objects.requireNonNull(listener);
-        }
-
-        @Override
-        public ConfigurationInternal getCurrentUserConfigurationInternal() {
-            return mConfigurationInternal;
-        }
-
-        @Override
         public void acquireWakeLock() {
             if (mWakeLockAcquired) {
                 fail("Wake lock already acquired");
@@ -2019,7 +2104,7 @@
         }
 
         @Override
-        public void dumpDebugLog(PrintWriter printWriter) {
+        public void dumpDebugLog(IndentingPrintWriter pw) {
             // No-op for tests
         }
 
@@ -2037,11 +2122,6 @@
             mAsyncRunnables.clear();
         }
 
-        void simulateConfigurationInternalChange(ConfigurationInternal configurationInternal) {
-            mConfigurationInternal = configurationInternal;
-            mConfigurationInternalChangeListener.onChange();
-        }
-
         void pokeElapsedRealtimeMillis(long elapsedRealtimeMillis) {
             mElapsedRealtimeMillis = elapsedRealtimeMillis;
         }
@@ -2095,13 +2175,6 @@
      */
     private class Script {
 
-        private final TimeDetectorStrategyImpl mTimeDetectorStrategy;
-
-        Script() {
-            mFakeEnvironment = new FakeEnvironment();
-            mTimeDetectorStrategy = new TimeDetectorStrategyImpl(mFakeEnvironment);
-        }
-
         Script pokeFakeClocks(TimestampedValue<Instant> initialClockTime,
                 @TimeConfidence int timeConfidence) {
             mFakeEnvironment.pokeElapsedRealtimeMillis(initialClockTime.getReferenceTimeMillis());
@@ -2122,7 +2195,8 @@
          * Simulates the user / user's configuration changing.
          */
         Script simulateConfigurationInternalChange(ConfigurationInternal configurationInternal) {
-            mFakeEnvironment.simulateConfigurationInternalChange(configurationInternal);
+            mFakeServiceConfigAccessorSpy.simulateCurrentUserConfigurationInternalChange(
+                    configurationInternal);
             return this;
         }
 
@@ -2167,14 +2241,15 @@
 
         Script simulateAutoTimeDetectionToggle() {
             ConfigurationInternal configurationInternal =
-                    mFakeEnvironment.getCurrentUserConfigurationInternal();
+                    mFakeServiceConfigAccessorSpy.getCurrentUserConfigurationInternal();
             boolean autoDetectionEnabledSetting =
                     !configurationInternal.getAutoDetectionEnabledSetting();
             ConfigurationInternal newConfigurationInternal =
                     new ConfigurationInternal.Builder(configurationInternal)
                             .setAutoDetectionEnabledSetting(autoDetectionEnabledSetting)
                             .build();
-            mFakeEnvironment.simulateConfigurationInternalChange(newConfigurationInternal);
+            mFakeServiceConfigAccessorSpy.simulateCurrentUserConfigurationInternalChange(
+                    newConfigurationInternal);
             return this;
         }
 
@@ -2389,4 +2464,12 @@
         return LocalDateTime.of(year, monthInYear, day, hourOfDay, minute, second)
                 .toInstant(ZoneOffset.UTC);
     }
+
+    private void assertStateChangeNotificationsSent(
+            TestStateChangeListener stateChangeListener, int expectedCount) {
+        // The fake environment needs to be told to run posted work.
+        mFakeEnvironment.runAsyncRunnables();
+
+        stateChangeListener.assertNotificationsReceivedAndReset(expectedCount);
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerTest.java
index aeb8ec8..7ff015d 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerTest.java
@@ -88,18 +88,21 @@
     private static final long ARBITRARY_TIME_MILLIS = 12345L;
 
     private static final TimeZoneProviderEvent USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1 =
-            createSuggestionEvent(asList("Europe/London"));
+            createSuggestionEvent(ARBITRARY_TIME_MILLIS, asList("Europe/London"));
     private static final TimeZoneProviderEvent USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2 =
-            createSuggestionEvent(asList("Europe/Paris"));
+            createSuggestionEvent(ARBITRARY_TIME_MILLIS + 1, asList("Europe/Paris"));
     private static final TimeZoneProviderStatus UNCERTAIN_PROVIDER_STATUS =
             new TimeZoneProviderStatus.Builder()
                     .setLocationDetectionDependencyStatus(DEPENDENCY_STATUS_TEMPORARILY_UNAVAILABLE)
                     .setConnectivityDependencyStatus(DEPENDENCY_STATUS_OK)
                     .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_UNKNOWN)
                     .build();
-    private static final TimeZoneProviderEvent USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT =
+    private static final TimeZoneProviderEvent USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT1 =
             TimeZoneProviderEvent.createUncertainEvent(
                     ARBITRARY_TIME_MILLIS, UNCERTAIN_PROVIDER_STATUS);
+    private static final TimeZoneProviderEvent USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT2 =
+            TimeZoneProviderEvent.createUncertainEvent(
+                    ARBITRARY_TIME_MILLIS + 1, UNCERTAIN_PROVIDER_STATUS);
     private static final TimeZoneProviderEvent USER1_PERM_FAILURE_LOCATION_TIME_ZONE_EVENT =
             TimeZoneProviderEvent.createPermanentFailureEvent(ARBITRARY_TIME_MILLIS, "Test");
 
@@ -328,7 +331,7 @@
 
         // Finally, the uncertainty timeout should cause the controller to make an uncertain
         // suggestion.
-        mTestThreadingDomain.executeNext();
+        mTestThreadingDomain.executeAll();
 
         assertControllerState(controller, STATE_UNCERTAIN);
         mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
@@ -649,7 +652,7 @@
         // cause a suggestion to be made straight away, but the uncertainty timeout should be
         // started and the secondary should be started.
         mTestPrimaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent(
-                USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT);
+                USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT1);
 
         assertControllerState(controller, STATE_CERTAIN);
         mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
@@ -680,7 +683,7 @@
         // cause a suggestion to be made straight away, but the uncertainty timeout should be
         // started. Both providers are now started, with no initialization timeout set.
         mTestSecondaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent(
-                USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT);
+                USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT1);
 
         assertControllerState(controller, STATE_CERTAIN);
         mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
@@ -693,7 +696,7 @@
 
         // Simulate time passing. This means the uncertainty timeout should fire and the uncertain
         // suggestion should be made.
-        mTestThreadingDomain.executeNext();
+        mTestThreadingDomain.executeAll();
 
         assertControllerState(controller, STATE_UNCERTAIN);
         mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
@@ -702,7 +705,7 @@
                 PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
         mTestMetricsLogger.assertStateChangesAndCommit(STATE_UNCERTAIN);
         mTestCallback.assertEventWithUncertainSuggestionReportedAndCommit(
-                USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT);
+                USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT1);
         assertFalse(controller.isUncertaintyTimeoutSet());
     }
 
@@ -744,7 +747,7 @@
         // Uncertainty should not cause a suggestion to be made straight away, but the uncertainty
         // timeout should be started and the secondary should be started.
         mTestPrimaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent(
-                USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT);
+                USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT1);
 
         assertControllerState(controller, STATE_CERTAIN);
         mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
@@ -772,6 +775,147 @@
     }
 
     @Test
+    public void enabled_uncertaintyDuringUncertaintyTimeoutTriggersNoSuggestion() {
+        LocationTimeZoneProviderController controller = new LocationTimeZoneProviderController(
+                mTestThreadingDomain, mTestMetricsLogger, mTestPrimaryLocationTimeZoneProvider,
+                mTestSecondaryLocationTimeZoneProvider, false /* recordStateChanges */);
+        TestEnvironment testEnvironment = new TestEnvironment(
+                mTestThreadingDomain, controller, USER1_CONFIG_GEO_DETECTION_ENABLED);
+
+        // Initialize and check initial state.
+        controller.initialize(testEnvironment, mTestCallback);
+
+        assertControllerState(controller, STATE_INITIALIZING);
+        mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+                PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
+        mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
+        mTestMetricsLogger.assertStateChangesAndCommit(
+                STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING);
+        mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+                DETECTION_ALGORITHM_STATUS_RUNNING);
+        assertFalse(controller.isUncertaintyTimeoutSet());
+
+        // Simulate a location event being received from the primary provider. This should cause a
+        // suggestion to be made.
+        mTestPrimaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent(
+                USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
+
+        assertControllerState(controller, STATE_CERTAIN);
+        mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+                PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
+        mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
+        mTestMetricsLogger.assertStateChangesAndCommit(STATE_CERTAIN);
+        mTestCallback.assertEventWithCertainSuggestionReportedAndCommit(
+                USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
+        assertFalse(controller.isUncertaintyTimeoutSet());
+
+        // Uncertainty should not cause a suggestion to be made straight away, but the uncertainty
+        // timeout should be started and the secondary should be started.
+        mTestPrimaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent(
+                USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT1);
+
+        assertControllerState(controller, STATE_CERTAIN);
+        mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+                PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
+        mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+                PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
+        mTestMetricsLogger.assertStateChangesAndCommit();
+        mTestCallback.assertNoEventReported();
+        assertUncertaintyTimeoutSet(testEnvironment, controller);
+
+        // Another uncertain suggestion from the primary during the uncertainty timeout should have
+        // no effect.
+        mTestPrimaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent(
+                USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT1);
+        assertControllerState(controller, STATE_CERTAIN);
+        mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+                PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
+        mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+                PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
+        mTestMetricsLogger.assertStateChangesAndCommit();
+        mTestCallback.assertNoEventReported();
+        assertUncertaintyTimeoutSet(testEnvironment, controller);
+    }
+
+    @Test
+    public void enabled_uncertaintyAfterUncertaintyTimeoutTriggersImmediateSuggestion() {
+        LocationTimeZoneProviderController controller = new LocationTimeZoneProviderController(
+                mTestThreadingDomain, mTestMetricsLogger, mTestPrimaryLocationTimeZoneProvider,
+                mTestSecondaryLocationTimeZoneProvider, false /* recordStateChanges */);
+        TestEnvironment testEnvironment = new TestEnvironment(
+                mTestThreadingDomain, controller, USER1_CONFIG_GEO_DETECTION_ENABLED);
+
+        // Initialize and check initial state.
+        controller.initialize(testEnvironment, mTestCallback);
+
+        assertControllerState(controller, STATE_INITIALIZING);
+        mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+                PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
+        mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
+        mTestMetricsLogger.assertStateChangesAndCommit(
+                STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING);
+        mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+                DETECTION_ALGORITHM_STATUS_RUNNING);
+        assertFalse(controller.isUncertaintyTimeoutSet());
+
+        // Simulate a location event being received from the primary provider. This should cause a
+        // suggestion to be made.
+        mTestPrimaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent(
+                USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
+
+        assertControllerState(controller, STATE_CERTAIN);
+        mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+                PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
+        mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
+        mTestMetricsLogger.assertStateChangesAndCommit(STATE_CERTAIN);
+        mTestCallback.assertEventWithCertainSuggestionReportedAndCommit(
+                USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
+        assertFalse(controller.isUncertaintyTimeoutSet());
+
+        // Uncertainty should not cause a suggestion to be made straight away, but the uncertainty
+        // timeout should be started and the secondary should be started.
+        mTestPrimaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent(
+                USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT1);
+
+        assertControllerState(controller, STATE_CERTAIN);
+        mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+                PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
+        mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+                PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
+        mTestMetricsLogger.assertStateChangesAndCommit();
+        mTestCallback.assertNoEventReported();
+        assertUncertaintyTimeoutSet(testEnvironment, controller);
+
+        // Simulate time passing. This means the uncertainty timeout should fire and the uncertain
+        // suggestion should be made.
+        mTestThreadingDomain.executeAll();
+
+        assertControllerState(controller, STATE_UNCERTAIN);
+        mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+                PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
+        mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+                PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
+        mTestMetricsLogger.assertStateChangesAndCommit(STATE_UNCERTAIN);
+        mTestCallback.assertEventWithUncertainSuggestionReportedAndCommit(
+                USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT1);
+        assertFalse(controller.isUncertaintyTimeoutSet());
+
+        // Another uncertain suggestion from the primary should cause an immediate suggestion.
+        mTestPrimaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent(
+                USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT2);
+
+        assertControllerState(controller, STATE_UNCERTAIN);
+        mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+                PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
+        mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+                PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
+        mTestMetricsLogger.assertStateChangesAndCommit();
+        mTestCallback.assertEventWithUncertainSuggestionReportedAndCommit(
+                USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT2);
+        assertFalse(controller.isUncertaintyTimeoutSet());
+    }
+
+    @Test
     public void configChanges_enableAndDisableWithNoPreviousSuggestion() {
         LocationTimeZoneProviderController controller = new LocationTimeZoneProviderController(
                 mTestThreadingDomain, mTestMetricsLogger, mTestPrimaryLocationTimeZoneProvider,
@@ -965,7 +1109,7 @@
 
         // Simulate uncertainty from the secondary.
         mTestSecondaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent(
-                USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT);
+                USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT1);
 
         assertControllerState(controller, STATE_INITIALIZING);
         mTestPrimaryLocationTimeZoneProvider.assertIsPermFailedAndCommit();
@@ -991,7 +1135,7 @@
 
         // Simulate uncertainty from the secondary.
         mTestSecondaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent(
-                USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT);
+                USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT1);
 
         assertControllerState(controller, STATE_CERTAIN);
         mTestPrimaryLocationTimeZoneProvider.assertIsPermFailedAndCommit();
@@ -1085,7 +1229,7 @@
         // give this test the opportunity to simulate its failure. Then it will be possible to
         // demonstrate controller behavior with only the primary working.
         mTestPrimaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent(
-                USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT);
+                USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT1);
 
         assertControllerState(controller, STATE_INITIALIZING);
         mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
@@ -1124,7 +1268,7 @@
 
         // Simulate uncertainty from the primary. The secondary cannot be started.
         mTestPrimaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent(
-                USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT);
+                USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT1);
 
         assertControllerState(controller, STATE_CERTAIN);
         mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
@@ -1160,7 +1304,7 @@
         // give this test the opportunity to simulate its failure. Then it will be possible to
         // demonstrate controller behavior with only the primary working.
         mTestPrimaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent(
-                USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT);
+                USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT1);
 
         assertControllerState(controller, STATE_INITIALIZING);
         mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
@@ -1282,7 +1426,7 @@
 
         // Simulate an uncertain event from the primary. This will start the secondary.
         mTestPrimaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent(
-                USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT);
+                USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT1);
 
         {
             LocationTimeZoneManagerServiceState state = controller.getStateForTests();
@@ -1471,18 +1615,19 @@
                 controller.getUncertaintyTimeoutDelayMillis());
     }
 
-    private static TimeZoneProviderEvent createSuggestionEvent(@NonNull List<String> timeZoneIds) {
+    private static TimeZoneProviderEvent createSuggestionEvent(
+            long elapsedRealtimeMillis, @NonNull List<String> timeZoneIds) {
         TimeZoneProviderStatus providerStatus = new TimeZoneProviderStatus.Builder()
                 .setLocationDetectionDependencyStatus(DEPENDENCY_STATUS_NOT_APPLICABLE)
                 .setConnectivityDependencyStatus(DEPENDENCY_STATUS_NOT_APPLICABLE)
                 .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_OK)
                 .build();
         TimeZoneProviderSuggestion suggestion = new TimeZoneProviderSuggestion.Builder()
-                .setElapsedRealtimeMillis(ARBITRARY_TIME_MILLIS)
+                .setElapsedRealtimeMillis(elapsedRealtimeMillis)
                 .setTimeZoneIds(timeZoneIds)
                 .build();
         return TimeZoneProviderEvent.createSuggestionEvent(
-                ARBITRARY_TIME_MILLIS, suggestion, providerStatus);
+                elapsedRealtimeMillis, suggestion, providerStatus);
     }
 
     private static void assertControllerState(LocationTimeZoneProviderController controller,
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/location/TestThreadingDomain.java b/services/tests/servicestests/src/com/android/server/timezonedetector/location/TestThreadingDomain.java
index e08fea0..a3fb5e6 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/location/TestThreadingDomain.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/location/TestThreadingDomain.java
@@ -142,4 +142,10 @@
         mCurrentTimeMillis = queued.executionTimeMillis;
         queued.runnable.run();
     }
+
+    void executeAll() {
+        while (!mQueue.isEmpty()) {
+            executeNext();
+        }
+    }
 }
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 3adee0f..83fa29a 100644
--- a/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java
+++ b/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java
@@ -31,7 +31,7 @@
 import android.content.res.Configuration;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.util.AtomicFile;
-import android.util.TimeSparseArray;
+import android.util.LongSparseArray;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
@@ -568,7 +568,7 @@
         mUsageStatsDatabase.forceIndexFiles();
         final int len = mUsageStatsDatabase.mSortedStatFiles.length;
         for (int i = 0; i < len; i++) {
-            final TimeSparseArray<AtomicFile> files =  mUsageStatsDatabase.mSortedStatFiles[i];
+            final LongSparseArray<AtomicFile> files =  mUsageStatsDatabase.mSortedStatFiles[i];
             // The stats file for each interval type equals to max allowed.
             assertEquals(UsageStatsDatabase.MAX_FILES_PER_INTERVAL_TYPE[i],
                     files.size());
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/DeviceVibrationEffectAdapterTest.java b/services/tests/servicestests/src/com/android/server/vibrator/DeviceVibrationEffectAdapterTest.java
deleted file mode 100644
index 1a146f6..0000000
--- a/services/tests/servicestests/src/com/android/server/vibrator/DeviceVibrationEffectAdapterTest.java
+++ /dev/null
@@ -1,222 +0,0 @@
-/*
- * 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.
- */
-
-package com.android.server.vibrator;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.when;
-
-import android.content.ComponentName;
-import android.content.pm.PackageManagerInternal;
-import android.hardware.vibrator.IVibrator;
-import android.os.Handler;
-import android.os.VibrationEffect;
-import android.os.VibratorInfo;
-import android.os.test.TestLooper;
-import android.os.vibrator.PrebakedSegment;
-import android.os.vibrator.PrimitiveSegment;
-import android.os.vibrator.RampSegment;
-import android.os.vibrator.StepSegment;
-import android.os.vibrator.VibrationEffectSegment;
-import android.platform.test.annotations.Presubmit;
-
-import androidx.test.InstrumentationRegistry;
-
-import com.android.server.LocalServices;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
-
-import java.util.Arrays;
-import java.util.stream.IntStream;
-
-/**
- * Tests for {@link DeviceVibrationEffectAdapter}.
- *
- * Build/Install/Run:
- * atest FrameworksServicesTests:DeviceVibrationEffectAdapterTest
- */
-@Presubmit
-public class DeviceVibrationEffectAdapterTest {
-    private static final float TEST_MIN_FREQUENCY = 50;
-    private static final float TEST_RESONANT_FREQUENCY = 150;
-    private static final float TEST_FREQUENCY_RESOLUTION = 25;
-    private static final float[] TEST_AMPLITUDE_MAP = new float[]{
-            /* 50Hz= */ 0.1f, 0.2f, 0.4f, 0.8f, /* 150Hz= */ 1f, 0.9f, /* 200Hz= */ 0.8f};
-
-    private static final VibratorInfo.FrequencyProfile EMPTY_FREQUENCY_PROFILE =
-            new VibratorInfo.FrequencyProfile(Float.NaN, Float.NaN, Float.NaN, null);
-    private static final VibratorInfo.FrequencyProfile TEST_FREQUENCY_PROFILE =
-            new VibratorInfo.FrequencyProfile(TEST_RESONANT_FREQUENCY, TEST_MIN_FREQUENCY,
-                    TEST_FREQUENCY_RESOLUTION, TEST_AMPLITUDE_MAP);
-
-    @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
-
-    @Mock private PackageManagerInternal mPackageManagerInternalMock;
-
-    private DeviceVibrationEffectAdapter mAdapter;
-
-    @Before
-    public void setUp() throws Exception {
-        when(mPackageManagerInternalMock.getSystemUiServiceComponent())
-                .thenReturn(new ComponentName("", ""));
-        LocalServices.removeServiceForTest(PackageManagerInternal.class);
-        LocalServices.addService(PackageManagerInternal.class, mPackageManagerInternalMock);
-
-        VibrationSettings vibrationSettings = new VibrationSettings(
-                InstrumentationRegistry.getContext(), new Handler(new TestLooper().getLooper()));
-        mAdapter = new DeviceVibrationEffectAdapter(vibrationSettings);
-    }
-
-    @Test
-    public void testPrebakedAndPrimitiveSegments_returnsOriginalSegment() {
-        VibrationEffect.Composed effect = new VibrationEffect.Composed(Arrays.asList(
-                new PrebakedSegment(
-                        VibrationEffect.EFFECT_CLICK, false, VibrationEffect.EFFECT_STRENGTH_LIGHT),
-                new PrimitiveSegment(VibrationEffect.Composition.PRIMITIVE_TICK, 1, 10),
-                new PrebakedSegment(
-                        VibrationEffect.EFFECT_THUD, true, VibrationEffect.EFFECT_STRENGTH_STRONG),
-                new PrimitiveSegment(VibrationEffect.Composition.PRIMITIVE_SPIN, 0.5f, 100)),
-                /* repeatIndex= */ -1);
-
-        assertEquals(effect, mAdapter.apply(effect, createVibratorInfo(EMPTY_FREQUENCY_PROFILE)));
-        assertEquals(effect, mAdapter.apply(effect, createVibratorInfo(TEST_FREQUENCY_PROFILE)));
-    }
-
-    @Test
-    public void testStepAndRampSegments_withoutPwleCapability_convertsRampsToSteps() {
-        VibrationEffect.Composed effect = new VibrationEffect.Composed(Arrays.asList(
-                new StepSegment(/* amplitude= */ 0, /* frequencyHz= */ 200, /* duration= */ 10),
-                new StepSegment(/* amplitude= */ 0.5f, /* frequencyHz= */ 150, /* duration= */ 100),
-                new RampSegment(/* startAmplitude= */ 1, /* endAmplitude= */ 0.2f,
-                        /* startFrequencyHz= */ 1, /* endFrequencyHz= */ 300, /* duration= */ 10),
-                new RampSegment(/* startAmplitude= */ 0.8f, /* endAmplitude= */ 0.2f,
-                        /* startFrequencyHz= */ 0, /* endFrequencyHz= */ 0, /* duration= */ 100),
-                new RampSegment(/* startAmplitude= */ 0.65f, /* endAmplitude= */ 0.65f,
-                        /* startFrequencyHz= */ 0, /* endFrequencyHz= */ 1, /* duration= */ 1000)),
-                /* repeatIndex= */ 3);
-
-        VibrationEffect.Composed adaptedEffect = (VibrationEffect.Composed) mAdapter.apply(effect,
-                createVibratorInfo(EMPTY_FREQUENCY_PROFILE));
-        assertTrue(adaptedEffect.getSegments().size() > effect.getSegments().size());
-        assertTrue(adaptedEffect.getRepeatIndex() >= effect.getRepeatIndex());
-
-        for (VibrationEffectSegment adaptedSegment : adaptedEffect.getSegments()) {
-            assertTrue(adaptedSegment instanceof StepSegment);
-        }
-    }
-
-    @Test
-    public void testStepAndRampSegments_withPwleCapability_convertsStepsToRamps() {
-        VibrationEffect.Composed effect = new VibrationEffect.Composed(Arrays.asList(
-                new StepSegment(/* amplitude= */ 0, /* frequencyHz= */ 175, /* duration= */ 10),
-                new StepSegment(/* amplitude= */ 0.5f, /* frequencyHz= */ 150, /* duration= */ 60),
-                new RampSegment(/* startAmplitude= */ 1, /* endAmplitude= */ 1,
-                        /* startFrequencyHz= */ 50, /* endFrequencyHz= */ 200, /* duration= */ 50),
-                new RampSegment(/* startAmplitude= */ 0.8f, /* endAmplitude= */ 0.2f,
-                        /* startFrequencyHz= */ 1000, /* endFrequencyHz= */ 1, /* duration= */ 20)),
-                /* repeatIndex= */ 2);
-
-        VibrationEffect.Composed expected = new VibrationEffect.Composed(Arrays.asList(
-                new RampSegment(/* startAmplitude= */ 0, /* endAmplitude*/ 0,
-                        /* startFrequencyHz= */ 175, /* endFrequencyHz= */ 175, /* duration= */ 10),
-                new RampSegment(/* startAmplitude= */ 0.5f, /* endAmplitude= */ 0.5f,
-                        /* startFrequencyHz= */ 150, /* endFrequencyHz= */ 150, /* duration= */ 60),
-                new RampSegment(/* startAmplitude= */ 0.1f, /* endAmplitude= */ 0.8f,
-                        /* startFrequencyHz= */ 50, /* endFrequencyHz= */ 200, /* duration= */ 50),
-                new RampSegment(/* startAmplitude= */ 0.8f, /* endAmplitude= */ 0.1f,
-                        /* startFrequencyHz= */ 200, /* endFrequencyHz= */ 50, /* duration= */ 20)),
-                /* repeatIndex= */ 2);
-
-        VibratorInfo info = createVibratorInfo(TEST_FREQUENCY_PROFILE,
-                IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
-        assertEquals(expected, mAdapter.apply(effect, info));
-    }
-
-    @Test
-    public void testStepAndRampSegments_withEmptyFreqMapping_returnsAmplitudesWithResonantFreq() {
-        VibrationEffect.Composed effect = new VibrationEffect.Composed(Arrays.asList(
-                new StepSegment(/* amplitude= */ 0, /* frequencyHz= */ 175, /* duration= */ 10),
-                new StepSegment(/* amplitude= */ 0.5f, /* frequencyHz= */ 0, /* duration= */ 100),
-                new RampSegment(/* startAmplitude= */ 0.8f, /* endAmplitude= */ 1,
-                        /* startFrequencyHz= */ 50, /* endFrequencyHz= */ 200, /* duration= */ 50),
-                new RampSegment(/* startAmplitude= */ 0.7f, /* endAmplitude= */ 0.5f,
-                        /* startFrequencyHz= */ 1000, /* endFrequencyHz= */ 1, /* duration= */ 20)),
-                /* repeatIndex= */ 2);
-
-        VibrationEffect.Composed expected = new VibrationEffect.Composed(Arrays.asList(
-                new RampSegment(/* startAmplitude= */ 0, /* endAmplitude= */ 0,
-                        /* startFrequencyHz= */ Float.NaN, /* endFrequencyHz= */ Float.NaN,
-                        /* duration= */ 10),
-                new RampSegment(/* startAmplitude= */ 0.5f, /* endAmplitude= */ 0.5f,
-                        /* startFrequencyHz= */ Float.NaN, /* endFrequencyHz= */ Float.NaN,
-                        /* duration= */ 100),
-                new RampSegment(/* startAmplitude= */ 0.8f, /* endAmplitude= */ 1,
-                        /* startFrequencyHz= */ Float.NaN, /* endFrequencyHz= */ Float.NaN,
-                        /* duration= */ 50),
-                new RampSegment(/* startAmplitude= */ 0.7f, /* endAmplitude= */ 0.5f,
-                        /* startFrequencyHz= */ Float.NaN, /* endFrequencyHz= */ Float.NaN,
-                        /* duration= */ 20)),
-                /* repeatIndex= */ 2);
-
-        VibratorInfo info = createVibratorInfo(EMPTY_FREQUENCY_PROFILE,
-                IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
-        assertEquals(expected, mAdapter.apply(effect, info));
-    }
-
-    @Test
-    public void testStepAndRampSegments_withValidFreqMapping_returnsClippedValues() {
-        VibrationEffect.Composed effect = new VibrationEffect.Composed(Arrays.asList(
-                new StepSegment(/* amplitude= */ 0.5f, /* frequencyHz= */ 0, /* duration= */ 10),
-                new StepSegment(/* amplitude= */ 1, /* frequencyHz= */ 125, /* duration= */ 100),
-                new RampSegment(/* startAmplitude= */ 1, /* endAmplitude= */ 1,
-                        /* startFrequencyHz= */ 50, /* endFrequencyHz= */ 200, /* duration= */ 50),
-                new RampSegment(/* startAmplitude= */ 0.8f, /* endAmplitude= */ 0.2f,
-                        /* startFrequencyHz= */ 1000, /* endFrequencyHz= */ 1, /* duration= */ 20)),
-                /* repeatIndex= */ 2);
-
-        VibrationEffect.Composed expected = new VibrationEffect.Composed(Arrays.asList(
-                new RampSegment(/* startAmplitude= */ 0.5f, /* endAmplitude= */ 0.5f,
-                        /* startFrequencyHz= */ 150, /* endFrequencyHz= */ 150,
-                        /* duration= */ 10),
-                new RampSegment(/* startAmplitude= */ 0.8f, /* endAmplitude= */ 0.8f,
-                        /* startFrequencyHz= */ 125, /* endFrequencyHz= */ 125,
-                        /* duration= */ 100),
-                new RampSegment(/* startAmplitude= */ 0.1f, /* endAmplitude= */ 0.8f,
-                        /* startFrequencyHz= */ 50, /* endFrequencyHz= */ 200, /* duration= */ 50),
-                new RampSegment(/* startAmplitude= */ 0.8f, /* endAmplitude= */ 0.1f,
-                        /* startFrequencyHz= */ 200, /* endFrequencyHz= */ 50, /* duration= */ 20)),
-                /* repeatIndex= */ 2);
-
-        VibratorInfo info = createVibratorInfo(TEST_FREQUENCY_PROFILE,
-                IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
-        assertEquals(expected, mAdapter.apply(effect, info));
-    }
-
-    private static VibratorInfo createVibratorInfo(VibratorInfo.FrequencyProfile frequencyProfile,
-            int... capabilities) {
-        int cap = IntStream.of(capabilities).reduce((a, b) -> a | b).orElse(0);
-        return new VibratorInfo.Builder(0)
-                .setCapabilities(cap)
-                .setFrequencyProfile(frequencyProfile)
-                .build();
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/FakeVibrator.java b/services/tests/servicestests/src/com/android/server/vibrator/FakeVibrator.java
deleted file mode 100644
index 4556a4a..0000000
--- a/services/tests/servicestests/src/com/android/server/vibrator/FakeVibrator.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * 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.
- */
-
-package com.android.server.vibrator;
-
-import android.annotation.NonNull;
-import android.content.Context;
-import android.os.VibrationAttributes;
-import android.os.VibrationEffect;
-import android.os.Vibrator;
-
-/** Fake implementation of {@link Vibrator} for service tests. */
-final class FakeVibrator extends Vibrator {
-
-    FakeVibrator(Context context) {
-        super(context);
-    }
-
-    @Override
-    public boolean hasVibrator() {
-        return true;
-    }
-
-    @Override
-    public boolean hasAmplitudeControl() {
-        return true;
-    }
-
-    @Override
-    public void vibrate(int uid, String opPkg, @NonNull VibrationEffect vibe, String reason,
-            @NonNull VibrationAttributes attributes) {
-    }
-
-    @Override
-    public void cancel() {
-    }
-
-    @Override
-    public void cancel(int usageFilter) {
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java b/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java
deleted file mode 100644
index c484f45..0000000
--- a/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java
+++ /dev/null
@@ -1,379 +0,0 @@
-/*
- * 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.
- */
-
-package com.android.server.vibrator;
-
-import android.annotation.Nullable;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.VibrationEffect;
-import android.os.VibratorInfo;
-import android.os.vibrator.PrebakedSegment;
-import android.os.vibrator.PrimitiveSegment;
-import android.os.vibrator.RampSegment;
-import android.os.vibrator.StepSegment;
-import android.os.vibrator.VibrationEffectSegment;
-
-import com.android.server.vibrator.VibratorController.OnVibrationCompleteListener;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.TreeMap;
-
-/**
- * Provides {@link VibratorController} with controlled vibrator hardware capabilities and
- * interactions.
- */
-final class FakeVibratorControllerProvider {
-    private static final int EFFECT_DURATION = 20;
-
-    private final Map<Long, PrebakedSegment> mEnabledAlwaysOnEffects = new HashMap<>();
-    private final Map<Long, List<VibrationEffectSegment>> mEffectSegments = new TreeMap<>();
-    private final Map<Long, List<Integer>> mBraking = new HashMap<>();
-    private final List<Float> mAmplitudes = new ArrayList<>();
-    private final List<Boolean> mExternalControlStates = new ArrayList<>();
-    private final Handler mHandler;
-    private final FakeNativeWrapper mNativeWrapper;
-
-    private boolean mIsAvailable = true;
-    private boolean mIsInfoLoadSuccessful = true;
-    private long mOnLatency;
-    private long mOffLatency;
-    private int mOffCount;
-
-    private int mCapabilities;
-    private int[] mSupportedEffects;
-    private int[] mSupportedBraking;
-    private int[] mSupportedPrimitives;
-    private int mCompositionSizeMax;
-    private int mPwleSizeMax;
-    private float mMinFrequency = Float.NaN;
-    private float mResonantFrequency = Float.NaN;
-    private float mFrequencyResolution = Float.NaN;
-    private float mQFactor = Float.NaN;
-    private float[] mMaxAmplitudes;
-
-    void recordEffectSegment(long vibrationId, VibrationEffectSegment segment) {
-        mEffectSegments.computeIfAbsent(vibrationId, k -> new ArrayList<>()).add(segment);
-    }
-
-    void recordBraking(long vibrationId, int braking) {
-        mBraking.computeIfAbsent(vibrationId, k -> new ArrayList<>()).add(braking);
-    }
-
-    private final class FakeNativeWrapper extends VibratorController.NativeWrapper {
-        public int vibratorId;
-        public OnVibrationCompleteListener listener;
-        public boolean isInitialized;
-
-        @Override
-        public void init(int vibratorId, OnVibrationCompleteListener listener) {
-            isInitialized = true;
-            this.vibratorId = vibratorId;
-            this.listener = listener;
-        }
-
-        @Override
-        public boolean isAvailable() {
-            return mIsAvailable;
-        }
-
-        @Override
-        public long on(long milliseconds, long vibrationId) {
-            recordEffectSegment(vibrationId, new StepSegment(VibrationEffect.DEFAULT_AMPLITUDE,
-                    /* frequencyHz= */ 0, (int) milliseconds));
-            applyLatency(mOnLatency);
-            scheduleListener(milliseconds, vibrationId);
-            return milliseconds;
-        }
-
-        @Override
-        public void off() {
-            mOffCount++;
-            applyLatency(mOffLatency);
-        }
-
-        @Override
-        public void setAmplitude(float amplitude) {
-            mAmplitudes.add(amplitude);
-            applyLatency(mOnLatency);
-        }
-
-        @Override
-        public long perform(long effect, long strength, long vibrationId) {
-            if (mSupportedEffects == null
-                    || Arrays.binarySearch(mSupportedEffects, (int) effect) < 0) {
-                return 0;
-            }
-            recordEffectSegment(vibrationId,
-                    new PrebakedSegment((int) effect, false, (int) strength));
-            applyLatency(mOnLatency);
-            scheduleListener(EFFECT_DURATION, vibrationId);
-            return EFFECT_DURATION;
-        }
-
-        @Override
-        public long compose(PrimitiveSegment[] primitives, long vibrationId) {
-            if (mSupportedPrimitives == null) {
-                return 0;
-            }
-            for (PrimitiveSegment primitive : primitives) {
-                if (Arrays.binarySearch(mSupportedPrimitives, primitive.getPrimitiveId()) < 0) {
-                    return 0;
-                }
-            }
-            long duration = 0;
-            for (PrimitiveSegment primitive : primitives) {
-                duration += EFFECT_DURATION + primitive.getDelay();
-                recordEffectSegment(vibrationId, primitive);
-            }
-            applyLatency(mOnLatency);
-            scheduleListener(duration, vibrationId);
-            return duration;
-        }
-
-        @Override
-        public long composePwle(RampSegment[] primitives, int braking, long vibrationId) {
-            long duration = 0;
-            for (RampSegment primitive : primitives) {
-                duration += primitive.getDuration();
-                recordEffectSegment(vibrationId, primitive);
-            }
-            recordBraking(vibrationId, braking);
-            applyLatency(mOnLatency);
-            scheduleListener(duration, vibrationId);
-            return duration;
-        }
-
-        @Override
-        public void setExternalControl(boolean enabled) {
-            mExternalControlStates.add(enabled);
-        }
-
-        @Override
-        public void alwaysOnEnable(long id, long effect, long strength) {
-            PrebakedSegment prebaked = new PrebakedSegment((int) effect, false, (int) strength);
-            mEnabledAlwaysOnEffects.put(id, prebaked);
-        }
-
-        @Override
-        public void alwaysOnDisable(long id) {
-            mEnabledAlwaysOnEffects.remove(id);
-        }
-
-        @Override
-        public boolean getInfo(VibratorInfo.Builder infoBuilder) {
-            infoBuilder.setCapabilities(mCapabilities);
-            infoBuilder.setSupportedBraking(mSupportedBraking);
-            infoBuilder.setPwleSizeMax(mPwleSizeMax);
-            infoBuilder.setSupportedEffects(mSupportedEffects);
-            if (mSupportedPrimitives != null) {
-                for (int primitive : mSupportedPrimitives) {
-                    infoBuilder.setSupportedPrimitive(primitive, EFFECT_DURATION);
-                }
-            }
-            infoBuilder.setCompositionSizeMax(mCompositionSizeMax);
-            infoBuilder.setQFactor(mQFactor);
-            infoBuilder.setFrequencyProfile(new VibratorInfo.FrequencyProfile(
-                    mResonantFrequency, mMinFrequency, mFrequencyResolution, mMaxAmplitudes));
-            return mIsInfoLoadSuccessful;
-        }
-
-        private void applyLatency(long latencyMillis) {
-            try {
-                if (latencyMillis > 0) {
-                    Thread.sleep(latencyMillis);
-                }
-            } catch (InterruptedException e) {
-            }
-        }
-
-        private void scheduleListener(long vibrationDuration, long vibrationId) {
-            mHandler.postDelayed(() -> listener.onComplete(vibratorId, vibrationId),
-                    vibrationDuration);
-        }
-    }
-
-    public FakeVibratorControllerProvider(Looper looper) {
-        mHandler = new Handler(looper);
-        mNativeWrapper = new FakeNativeWrapper();
-    }
-
-    public VibratorController newVibratorController(
-            int vibratorId, OnVibrationCompleteListener listener) {
-        return new VibratorController(vibratorId, listener, mNativeWrapper);
-    }
-
-    /** Return {@code true} if this controller was initialized. */
-    public boolean isInitialized() {
-        return mNativeWrapper.isInitialized;
-    }
-
-    /**
-     * Disable fake vibrator hardware, mocking a state where the underlying service is unavailable.
-     */
-    public void disableVibrators() {
-        mIsAvailable = false;
-    }
-
-    /**
-     * Sets the result for the method that loads the {@link VibratorInfo}, for faking a vibrator
-     * that fails to load some of the hardware data.
-     */
-    public void setVibratorInfoLoadSuccessful(boolean successful) {
-        mIsInfoLoadSuccessful = successful;
-    }
-
-    /**
-     * Sets the latency this controller should fake for turning the vibrator hardware on or setting
-     * the vibration amplitude.
-     */
-    public void setOnLatency(long millis) {
-        mOnLatency = millis;
-    }
-
-    /** Sets the latency this controller should fake for turning the vibrator off. */
-    public void setOffLatency(long millis) {
-        mOffLatency = millis;
-    }
-
-    /** Set the capabilities of the fake vibrator hardware. */
-    public void setCapabilities(int... capabilities) {
-        mCapabilities = Arrays.stream(capabilities).reduce(0, (a, b) -> a | b);
-    }
-
-    /** Set the effects supported by the fake vibrator hardware. */
-    public void setSupportedEffects(int... effects) {
-        if (effects != null) {
-            effects = Arrays.copyOf(effects, effects.length);
-            Arrays.sort(effects);
-        }
-        mSupportedEffects = effects;
-    }
-
-    /** Set the effects supported by the fake vibrator hardware. */
-    public void setSupportedBraking(int... braking) {
-        if (braking != null) {
-            braking = Arrays.copyOf(braking, braking.length);
-            Arrays.sort(braking);
-        }
-        mSupportedBraking = braking;
-    }
-
-    /** Set the primitives supported by the fake vibrator hardware. */
-    public void setSupportedPrimitives(int... primitives) {
-        if (primitives != null) {
-            primitives = Arrays.copyOf(primitives, primitives.length);
-            Arrays.sort(primitives);
-        }
-        mSupportedPrimitives = primitives;
-    }
-
-    /** Set the max number of primitives allowed in a composition by the fake vibrator hardware. */
-    public void setCompositionSizeMax(int compositionSizeMax) {
-        mCompositionSizeMax = compositionSizeMax;
-    }
-
-    /** Set the max number of PWLEs allowed in a composition by the fake vibrator hardware. */
-    public void setPwleSizeMax(int pwleSizeMax) {
-        mPwleSizeMax = pwleSizeMax;
-    }
-
-    /** Set the resonant frequency of the fake vibrator hardware. */
-    public void setResonantFrequency(float frequencyHz) {
-        mResonantFrequency = frequencyHz;
-    }
-
-    /** Set the minimum frequency of the fake vibrator hardware. */
-    public void setMinFrequency(float frequencyHz) {
-        mMinFrequency = frequencyHz;
-    }
-
-    /** Set the frequency resolution of the fake vibrator hardware. */
-    public void setFrequencyResolution(float frequencyHz) {
-        mFrequencyResolution = frequencyHz;
-    }
-
-    /** Set the Q factor of the fake vibrator hardware. */
-    public void setQFactor(float qFactor) {
-        mQFactor = qFactor;
-    }
-
-    /** Set the max amplitude supported for each frequency f the fake vibrator hardware. */
-    public void setMaxAmplitudes(float... maxAmplitudes) {
-        mMaxAmplitudes = maxAmplitudes;
-    }
-
-    /**
-     * Return the amplitudes set by this controller, including zeroes for each time the vibrator was
-     * turned off.
-     */
-    public List<Float> getAmplitudes() {
-        return new ArrayList<>(mAmplitudes);
-    }
-
-    /** Return the braking values passed to the compose PWLE method. */
-    public List<Integer> getBraking(long vibrationId) {
-        if (mBraking.containsKey(vibrationId)) {
-            return new ArrayList<>(mBraking.get(vibrationId));
-        } else {
-            return new ArrayList<>();
-        }
-    }
-
-    /** Return list of {@link VibrationEffectSegment} played by this controller, in order. */
-    public List<VibrationEffectSegment> getEffectSegments(long vibrationId) {
-        if (mEffectSegments.containsKey(vibrationId)) {
-            return new ArrayList<>(mEffectSegments.get(vibrationId));
-        } else {
-            return new ArrayList<>();
-        }
-    }
-
-    /**
-     * Returns a list of all vibrations' effect segments, for external-use where vibration IDs
-     * aren't exposed.
-     */
-    public List<VibrationEffectSegment> getAllEffectSegments() {
-        // Returns segments in order of vibrationId, which increases over time. TreeMap gives order.
-        ArrayList<VibrationEffectSegment> result = new ArrayList<>();
-        for (List<VibrationEffectSegment> subList : mEffectSegments.values()) {
-            result.addAll(subList);
-        }
-        return result;
-    }
-    /** Return list of states set for external control to the fake vibrator hardware. */
-    public List<Boolean> getExternalControlStates() {
-        return mExternalControlStates;
-    }
-
-    /** Returns the number of times the vibrator was turned off. */
-    public int getOffCount() {
-        return mOffCount;
-    }
-
-    /**
-     * Return the {@link PrebakedSegment} effect enabled with given id, or {@code null} if
-     * missing or disabled.
-     */
-    @Nullable
-    public PrebakedSegment getAlwaysOnEffect(int id) {
-        return mEnabledAlwaysOnEffects.get((long) id);
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/InputDeviceDelegateTest.java b/services/tests/servicestests/src/com/android/server/vibrator/InputDeviceDelegateTest.java
deleted file mode 100644
index 2ab79fc..0000000
--- a/services/tests/servicestests/src/com/android/server/vibrator/InputDeviceDelegateTest.java
+++ /dev/null
@@ -1,343 +0,0 @@
-/*
- * 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.
- */
-
-package com.android.server.vibrator;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.ArgumentMatchers.same;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-import android.content.ContextWrapper;
-import android.hardware.input.IInputDevicesChangedListener;
-import android.hardware.input.IInputManager;
-import android.hardware.input.InputManager;
-import android.hardware.input.InputManagerGlobal;
-import android.os.CombinedVibration;
-import android.os.Handler;
-import android.os.Process;
-import android.os.VibrationAttributes;
-import android.os.VibrationEffect;
-import android.os.test.TestLooper;
-import android.platform.test.annotations.Presubmit;
-import android.view.InputDevice;
-
-import androidx.test.InstrumentationRegistry;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
-
-/**
- * Tests for {@link InputDeviceDelegate}.
- *
- * Build/Install/Run:
- * atest FrameworksServicesTests:InputDeviceDelegateTest
- */
-@Presubmit
-public class InputDeviceDelegateTest {
-
-    private static final int UID = Process.ROOT_UID;
-    private static final String PACKAGE_NAME = "package";
-    private static final String REASON = "some reason";
-    private static final VibrationAttributes VIBRATION_ATTRIBUTES =
-            new VibrationAttributes.Builder().setUsage(VibrationAttributes.USAGE_ALARM).build();
-    private static final CombinedVibration SYNCED_EFFECT =
-            CombinedVibration.createParallel(VibrationEffect.createOneShot(100, 255));
-
-    @Rule public MockitoRule rule = MockitoJUnit.rule();
-
-    @Mock private IInputManager mIInputManagerMock;
-
-    private TestLooper mTestLooper;
-    private ContextWrapper mContextSpy;
-    private InputManager mInputManager;
-    private InputDeviceDelegate mInputDeviceDelegate;
-    private IInputDevicesChangedListener mIInputDevicesChangedListener;
-
-    @Before
-    public void setUp() throws Exception {
-        mTestLooper = new TestLooper();
-        InputManagerGlobal.resetInstance(mIInputManagerMock);
-        mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getContext()));
-
-        mInputManager = new InputManager(mContextSpy);
-        when(mContextSpy.getSystemService(eq(Context.INPUT_SERVICE)))
-                .thenReturn(mInputManager);
-        doAnswer(invocation -> mIInputDevicesChangedListener = invocation.getArgument(0))
-                .when(mIInputManagerMock).registerInputDevicesChangedListener(any());
-
-        mInputDeviceDelegate = new InputDeviceDelegate(
-                mContextSpy, new Handler(mTestLooper.getLooper()));
-        mInputDeviceDelegate.onSystemReady();
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        InputManagerGlobal.clearInstance();
-    }
-
-    @Test
-    public void beforeSystemReady_ignoresAnyUpdate() throws Exception {
-        when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[0]);
-        InputDeviceDelegate inputDeviceDelegate = new InputDeviceDelegate(
-                mContextSpy, new Handler(mTestLooper.getLooper()));
-
-        inputDeviceDelegate.updateInputDeviceVibrators(/* vibrateInputDevices= */ true);
-        assertFalse(inputDeviceDelegate.isAvailable());
-
-        inputDeviceDelegate.onInputDeviceAdded(1);
-        assertFalse(inputDeviceDelegate.isAvailable());
-
-        updateInputDevices(new int[]{1});
-        assertFalse(inputDeviceDelegate.isAvailable());
-
-        verify(mIInputManagerMock, never()).getInputDevice(anyInt());
-    }
-
-    @Test
-    public void onInputDeviceAdded_withSettingsDisabled_ignoresNewDevice() throws Exception {
-        when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[0]);
-        mInputDeviceDelegate.updateInputDeviceVibrators(/* vibrateInputDevices= */ false);
-        assertFalse(mInputDeviceDelegate.isAvailable());
-
-        when(mIInputManagerMock.getVibratorIds(eq(1))).thenReturn(new int[]{1});
-        when(mIInputManagerMock.getInputDevice(eq(1))).thenReturn(createInputDeviceWithVibrator(1));
-        mInputDeviceDelegate.onInputDeviceAdded(1);
-
-        assertFalse(mInputDeviceDelegate.isAvailable());
-        verify(mIInputManagerMock, never()).getInputDevice(anyInt());
-    }
-
-    @Test
-    public void onInputDeviceAdded_withDeviceWithoutVibrator_ignoresNewDevice() throws Exception {
-        when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[0]);
-        mInputDeviceDelegate.updateInputDeviceVibrators(/* vibrateInputDevices= */ true);
-        assertFalse(mInputDeviceDelegate.isAvailable());
-
-        when(mIInputManagerMock.getVibratorIds(eq(1))).thenReturn(new int[0]);
-        when(mIInputManagerMock.getInputDevice(eq(1)))
-                .thenReturn(createInputDeviceWithoutVibrator(1));
-        updateInputDevices(new int[]{1});
-
-        assertFalse(mInputDeviceDelegate.isAvailable());
-        verify(mIInputManagerMock).getInputDevice(eq(1));
-    }
-
-    @Test
-    public void onInputDeviceAdded_withDeviceWithVibrator_addsNewDevice() throws Exception {
-        when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[0]);
-        mInputDeviceDelegate.updateInputDeviceVibrators(/* vibrateInputDevices= */ true);
-        assertFalse(mInputDeviceDelegate.isAvailable());
-
-        when(mIInputManagerMock.getVibratorIds(eq(1))).thenReturn(new int[]{1});
-        when(mIInputManagerMock.getInputDevice(eq(1))).thenReturn(createInputDeviceWithVibrator(1));
-        updateInputDevices(new int[]{1});
-
-        assertTrue(mInputDeviceDelegate.isAvailable());
-        verify(mIInputManagerMock).getInputDevice(eq(1));
-    }
-
-    @Test
-    public void onInputDeviceChanged_withSettingsDisabled_ignoresDevice() throws Exception {
-        when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1});
-        when(mIInputManagerMock.getVibratorIds(eq(1))).thenReturn(new int[]{1});
-        when(mIInputManagerMock.getInputDevice(eq(1))).thenReturn(createInputDeviceWithVibrator(1));
-        mInputDeviceDelegate.updateInputDeviceVibrators(/* vibrateInputDevices= */ false);
-
-        updateInputDevices(new int[]{1});
-        assertFalse(mInputDeviceDelegate.isAvailable());
-        verify(mIInputManagerMock, never()).getInputDevice(anyInt());
-    }
-
-    @Test
-    public void onInputDeviceChanged_deviceLosesVibrator_removesDevice() throws Exception {
-        when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1});
-        when(mIInputManagerMock.getVibratorIds(eq(1))).thenReturn(new int[]{1}, new int[0]);
-        when(mIInputManagerMock.getInputDevice(eq(1)))
-                .thenReturn(createInputDeviceWithVibrator(1), createInputDeviceWithoutVibrator(1));
-
-        mInputDeviceDelegate.updateInputDeviceVibrators(/* vibrateInputDevices= */ true);
-        assertTrue(mInputDeviceDelegate.isAvailable());
-
-        updateInputDevices(new int[]{1});
-        assertFalse(mInputDeviceDelegate.isAvailable());
-        verify(mIInputManagerMock, times(2)).getInputDevice(eq(1));
-    }
-
-    @Test
-    public void onInputDeviceChanged_deviceLost_removesDevice() throws Exception {
-        when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1});
-        when(mIInputManagerMock.getVibratorIds(eq(1))).thenReturn(new int[]{1}, new int[0]);
-        when(mIInputManagerMock.getInputDevice(eq(1)))
-                .thenReturn(createInputDeviceWithVibrator(1), (InputDevice) null);
-
-        mInputDeviceDelegate.updateInputDeviceVibrators(/* vibrateInputDevices= */ true);
-        assertTrue(mInputDeviceDelegate.isAvailable());
-
-        updateInputDevices(new int[]{1});
-        assertFalse(mInputDeviceDelegate.isAvailable());
-        verify(mIInputManagerMock, times(2)).getInputDevice(eq(1));
-    }
-
-    @Test
-    public void onInputDeviceChanged_deviceAddsVibrator_addsDevice() throws Exception {
-        when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1});
-        when(mIInputManagerMock.getVibratorIds(eq(1))).thenReturn(new int[0], new int[]{1});
-        when(mIInputManagerMock.getInputDevice(eq(1)))
-                .thenReturn(createInputDeviceWithoutVibrator(1), createInputDeviceWithVibrator(1));
-
-        mInputDeviceDelegate.updateInputDeviceVibrators(/* vibrateInputDevices= */ true);
-        assertFalse(mInputDeviceDelegate.isAvailable());
-
-        updateInputDevices(new int[]{1});
-        assertTrue(mInputDeviceDelegate.isAvailable());
-        verify(mIInputManagerMock, times(2)).getInputDevice(eq(1));
-    }
-
-    @Test
-    public void onInputDeviceRemoved_removesDevice() throws Exception {
-        when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1, 2});
-        when(mIInputManagerMock.getVibratorIds(eq(1))).thenReturn(new int[0]);
-        when(mIInputManagerMock.getInputDevice(eq(1))).thenReturn(
-                createInputDeviceWithoutVibrator(1));
-        when(mIInputManagerMock.getVibratorIds(eq(2))).thenReturn(new int[]{1});
-        when(mIInputManagerMock.getInputDevice(eq(2))).thenReturn(createInputDeviceWithVibrator(2));
-
-        mInputDeviceDelegate.updateInputDeviceVibrators(/* vibrateInputDevices= */ true);
-        assertTrue(mInputDeviceDelegate.isAvailable());
-
-        updateInputDevices(new int[]{1});
-        assertFalse(mInputDeviceDelegate.isAvailable());
-    }
-
-    @Test
-    public void updateInputDeviceVibrators_usesFlagToLoadDeviceList() throws Exception {
-        when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1, 2});
-        when(mIInputManagerMock.getVibratorIds(eq(1))).thenReturn(new int[]{1});
-        when(mIInputManagerMock.getInputDevice(eq(1))).thenReturn(createInputDeviceWithVibrator(1));
-        when(mIInputManagerMock.getVibratorIds(eq(2))).thenReturn(new int[]{1});
-        when(mIInputManagerMock.getInputDevice(eq(2))).thenReturn(createInputDeviceWithVibrator(2));
-
-        mInputDeviceDelegate.updateInputDeviceVibrators(/* vibrateInputDevices= */ true);
-        assertTrue(mInputDeviceDelegate.isAvailable());
-
-        mInputDeviceDelegate.updateInputDeviceVibrators(/* vibrateInputDevices= */ false);
-        assertFalse(mInputDeviceDelegate.isAvailable());
-    }
-
-    @Test
-    public void updateInputDeviceVibrators_withDeviceWithoutVibrator_deviceIsIgnored()
-            throws Exception {
-        when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1});
-        when(mIInputManagerMock.getVibratorIds(eq(1))).thenReturn(new int[0]);
-        when(mIInputManagerMock.getInputDevice(eq(1)))
-                .thenReturn(createInputDeviceWithoutVibrator(1));
-        mInputDeviceDelegate.updateInputDeviceVibrators(/* vibrateInputDevices= */ true);
-        assertFalse(mInputDeviceDelegate.isAvailable());
-    }
-
-    @Test
-    public void vibrateIfAvailable_withNoInputDevice_returnsFalse() {
-        assertFalse(mInputDeviceDelegate.isAvailable());
-        assertFalse(mInputDeviceDelegate.vibrateIfAvailable(
-                new Vibration.CallerInfo(VIBRATION_ATTRIBUTES, UID, -1, PACKAGE_NAME, REASON),
-                SYNCED_EFFECT));
-    }
-
-    @Test
-    public void vibrateIfAvailable_withInputDevices_returnsTrueAndVibratesAllDevices()
-            throws Exception {
-        when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1, 2});
-        when(mIInputManagerMock.getVibratorIds(eq(1))).thenReturn(new int[]{1});
-        when(mIInputManagerMock.getInputDevice(eq(1))).thenReturn(createInputDeviceWithVibrator(1));
-        when(mIInputManagerMock.getVibratorIds(eq(2))).thenReturn(new int[]{1});
-        when(mIInputManagerMock.getInputDevice(eq(2))).thenReturn(createInputDeviceWithVibrator(2));
-        mInputDeviceDelegate.updateInputDeviceVibrators(/* vibrateInputDevices= */ true);
-
-        assertTrue(mInputDeviceDelegate.vibrateIfAvailable(
-                new Vibration.CallerInfo(VIBRATION_ATTRIBUTES, UID, -1, PACKAGE_NAME, REASON),
-                SYNCED_EFFECT));
-        verify(mIInputManagerMock).vibrateCombined(eq(1), same(SYNCED_EFFECT), any());
-        verify(mIInputManagerMock).vibrateCombined(eq(2), same(SYNCED_EFFECT), any());
-    }
-
-    @Test
-    public void cancelVibrateIfAvailable_withNoInputDevice_returnsFalse() throws Exception {
-        assertFalse(mInputDeviceDelegate.isAvailable());
-        assertFalse(mInputDeviceDelegate.cancelVibrateIfAvailable());
-        verify(mIInputManagerMock, never()).cancelVibrate(anyInt(), any());
-    }
-
-    @Test
-    public void cancelVibrateIfAvailable_withInputDevices_returnsTrueAndStopsAllDevices()
-            throws Exception {
-        when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1, 2});
-        when(mIInputManagerMock.getVibratorIds(eq(1))).thenReturn(new int[]{1});
-        when(mIInputManagerMock.getInputDevice(eq(1))).thenReturn(createInputDeviceWithVibrator(1));
-        when(mIInputManagerMock.getVibratorIds(eq(2))).thenReturn(new int[]{1});
-        when(mIInputManagerMock.getInputDevice(eq(2))).thenReturn(createInputDeviceWithVibrator(2));
-        mInputDeviceDelegate.updateInputDeviceVibrators(/* vibrateInputDevices= */ true);
-
-        assertTrue(mInputDeviceDelegate.isAvailable());
-        assertTrue(mInputDeviceDelegate.cancelVibrateIfAvailable());
-        verify(mIInputManagerMock).cancelVibrate(eq(1), any());
-        verify(mIInputManagerMock).cancelVibrate(eq(2), any());
-    }
-
-    private void updateInputDevices(int[] deviceIds) throws Exception {
-        int[] deviceIdsAndGenerations = new int[deviceIds.length * 2];
-        for (int i = 0; i < deviceIdsAndGenerations.length; i += 2) {
-            deviceIdsAndGenerations[i] = deviceIds[i / 2];
-            deviceIdsAndGenerations[i + 1] = 2; // update by increasing it's generation to 2.
-        }
-        // Force initialization of mIInputDevicesChangedListener, if it still haven't
-        InputManagerGlobal.getInstance().getInputDeviceIds();
-        mIInputDevicesChangedListener.onInputDevicesChanged(deviceIdsAndGenerations);
-        // Makes sure all callbacks from InputDeviceDelegate are executed.
-        mTestLooper.dispatchAll();
-    }
-
-    private InputDevice createInputDeviceWithVibrator(int id) {
-        return createInputDevice(id, /* hasVibrator= */ true);
-    }
-
-    private InputDevice createInputDeviceWithoutVibrator(int id) {
-        return createInputDevice(id, /* hasVibrator= */ false);
-    }
-
-    private InputDevice createInputDevice(int id, boolean hasVibrator) {
-        return new InputDevice.Builder()
-                .setId(id)
-                .setName("name")
-                .setDescriptor("descriptor")
-                .setHasVibrator(hasVibrator)
-                .build();
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/OWNERS b/services/tests/servicestests/src/com/android/server/vibrator/OWNERS
deleted file mode 100644
index cc63ceb..0000000
--- a/services/tests/servicestests/src/com/android/server/vibrator/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-include /services/core/java/com/android/server/vibrator/OWNERS
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/RampDownAdapterTest.java b/services/tests/servicestests/src/com/android/server/vibrator/RampDownAdapterTest.java
deleted file mode 100644
index a3edf23..0000000
--- a/services/tests/servicestests/src/com/android/server/vibrator/RampDownAdapterTest.java
+++ /dev/null
@@ -1,355 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.vibrator;
-
-import static org.junit.Assert.assertEquals;
-
-import android.os.VibrationEffect;
-import android.os.VibratorInfo;
-import android.os.vibrator.PrebakedSegment;
-import android.os.vibrator.PrimitiveSegment;
-import android.os.vibrator.RampSegment;
-import android.os.vibrator.StepSegment;
-import android.os.vibrator.VibrationEffectSegment;
-import android.platform.test.annotations.Presubmit;
-
-import org.junit.Before;
-import org.junit.Test;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-/**
- * Tests for {@link RampDownAdapter}.
- *
- * Build/Install/Run:
- * atest FrameworksServicesTests:RampDownAdapterTest
- */
-@Presubmit
-public class RampDownAdapterTest {
-    private static final int TEST_RAMP_DOWN_DURATION = 20;
-    private static final int TEST_STEP_DURATION = 5;
-    private static final VibratorInfo TEST_VIBRATOR_INFO = new VibratorInfo.Builder(0).build();
-
-    private RampDownAdapter mAdapter;
-
-    @Before
-    public void setUp() throws Exception {
-        mAdapter = new RampDownAdapter(TEST_RAMP_DOWN_DURATION, TEST_STEP_DURATION);
-    }
-
-    @Test
-    public void testPrebakedAndPrimitiveSegments_keepsListUnchanged() {
-        List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
-                new PrebakedSegment(
-                        VibrationEffect.EFFECT_CLICK, false, VibrationEffect.EFFECT_STRENGTH_LIGHT),
-                new PrimitiveSegment(VibrationEffect.Composition.PRIMITIVE_TICK, 1, 10)));
-        List<VibrationEffectSegment> originalSegments = new ArrayList<>(segments);
-
-        assertEquals(-1, mAdapter.apply(segments, -1, TEST_VIBRATOR_INFO));
-        assertEquals(1, mAdapter.apply(segments, 1, TEST_VIBRATOR_INFO));
-
-        assertEquals(originalSegments, segments);
-    }
-
-    @Test
-    public void testRampAndStepSegments_withNoOffSegment_keepsListUnchanged() {
-        List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
-                new StepSegment(/* amplitude= */ 0.5f, /* frequencyHz= */ 0, /* duration= */ 100),
-                new RampSegment(/* startAmplitude= */ 0.8f, /* endAmplitude= */ 0.2f,
-                        /* startFrequencyHz= */ 10, /* endFrequencyHz= */ 50, /* duration= */ 20)));
-        List<VibrationEffectSegment> originalSegments = new ArrayList<>(segments);
-
-        assertEquals(-1, mAdapter.apply(segments, -1, TEST_VIBRATOR_INFO));
-        assertEquals(0, mAdapter.apply(segments, 0, TEST_VIBRATOR_INFO));
-
-        assertEquals(originalSegments, segments);
-    }
-
-    @Test
-    public void testRampAndStepSegments_withNoRampDownDuration_keepsOriginalSteps() {
-        mAdapter = new RampDownAdapter(/* rampDownDuration= */ 0, TEST_STEP_DURATION);
-
-        List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
-                new StepSegment(/* amplitude= */ 1, /* frequencyHz= */ 0, /* duration= */ 10),
-                new StepSegment(/* amplitude= */ 0, /* frequencyHz= */ 0, /* duration= */ 100),
-                new RampSegment(/* startAmplitude= */ 0.8f, /* endAmplitude= */ 0.2f,
-                        /* startFrequencyHz= */ 10, /* endFrequencyHz= */ 50, /* duration= */ 20),
-                new RampSegment(/* startAmplitude= */ 0, /* endAmplitude= */ 0,
-                        /* startFrequencyHz= */ 0, /* endFrequencyHz= */ 0, /* duration= */ 50)));
-        List<VibrationEffectSegment> originalSegments = new ArrayList<>(segments);
-
-        assertEquals(-1, mAdapter.apply(segments, -1, TEST_VIBRATOR_INFO));
-        assertEquals(2, mAdapter.apply(segments, 2, TEST_VIBRATOR_INFO));
-        assertEquals(originalSegments, segments);
-    }
-
-    @Test
-    public void testStepSegments_withShortZeroSegment_replaceWithStepsDown() {
-        List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
-                new StepSegment(/* amplitude= */ 1, /* frequencyHz= */ 0, /* duration= */ 10),
-                new StepSegment(/* amplitude= */ 0, /* frequencyHz= */ 0, /* duration= */ 10)));
-        List<VibrationEffectSegment> expectedSegments = Arrays.asList(
-                new StepSegment(/* amplitude= */ 1, /* frequencyHz= */ 0, /* duration= */ 10),
-                new StepSegment(/* amplitude= */ 0.5f, /* frequencyHz= */ 0, /* duration= */ 5),
-                new StepSegment(/* amplitude= */ 0, /* frequencyHz= */ 0, /* duration= */ 5));
-
-        assertEquals(-1, mAdapter.apply(segments, -1, TEST_VIBRATOR_INFO));
-        assertEquals(expectedSegments, segments);
-    }
-
-    @Test
-    public void testStepSegments_withLongZeroSegment_replaceWithStepsDownWithRemainingOffSegment() {
-        List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
-                new StepSegment(/* amplitude= */ 1, /* frequencyHz= */ 0, /* duration= */ 10),
-                new RampSegment(/* startAmplitude= */ 0, /* endAmplitude= */ 0,
-                        /* startFrequencyHz= */ 0, /* endFrequencyHz= */ 0, /* duration= */ 50),
-                new StepSegment(/* amplitude= */ 0.8f, /* frequencyHz= */ 0, /* duration= */ 100)));
-        List<VibrationEffectSegment> expectedSegments = Arrays.asList(
-                new StepSegment(/* amplitude= */ 1, /* frequencyHz= */ 0, /* duration= */ 10),
-                new StepSegment(/* amplitude= */ 0.75f, /* frequencyHz= */ 0, /* duration= */ 5),
-                new StepSegment(/* amplitude= */ 0.5f, /* frequencyHz= */ 0, /* duration= */ 5),
-                new StepSegment(/* amplitude= */ 0.25f, /* frequencyHz= */ 0, /* duration= */ 5),
-                new StepSegment(/* amplitude= */ 0, /* frequencyHz= */ 0, /* duration= */ 35),
-                new StepSegment(/* amplitude= */ 0.8f, /* frequencyHz= */ 0, /* duration= */ 100));
-
-        assertEquals(-1, mAdapter.apply(segments, -1, TEST_VIBRATOR_INFO));
-        assertEquals(expectedSegments, segments);
-    }
-
-    @Test
-    public void testStepSegments_withZeroSegmentBeforeRepeat_fixesRepeat() {
-        List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
-                new StepSegment(/* amplitude= */ 1, /* frequencyHz= */ 0, /* duration= */ 10),
-                new StepSegment(/* amplitude= */ 0, /* frequencyHz= */ 0, /* duration= */ 50),
-                new StepSegment(/* amplitude= */ 0.8f, /* frequencyHz= */ 0, /* duration= */ 100)));
-        List<VibrationEffectSegment> expectedSegments = Arrays.asList(
-                new StepSegment(/* amplitude= */ 1, /* frequencyHz= */ 0, /* duration= */ 10),
-                new StepSegment(/* amplitude= */ 0.75f, /* frequencyHz= */ 0, /* duration= */ 5),
-                new StepSegment(/* amplitude= */ 0.5f, /* frequencyHz= */ 0, /* duration= */ 5),
-                new StepSegment(/* amplitude= */ 0.25f, /* frequencyHz= */ 0, /* duration= */ 5),
-                new StepSegment(/* amplitude= */ 0, /* frequencyHz= */ 0, /* duration= */ 35),
-                new StepSegment(/* amplitude= */ 0.8f, /* frequencyHz= */ 0, /* duration= */ 100));
-
-        // Repeat index fixed after intermediate steps added
-        assertEquals(5, mAdapter.apply(segments, 2, TEST_VIBRATOR_INFO));
-        assertEquals(expectedSegments, segments);
-    }
-
-    @Test
-    public void testStepSegments_withZeroSegmentAfterRepeat_preservesRepeat() {
-        List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
-                new StepSegment(/* amplitude= */ 1, /* frequencyHz= */ 0, /* duration= */ 10),
-                new StepSegment(/* amplitude= */ 0, /* frequencyHz= */ 0, /* duration= */ 10),
-                new StepSegment(/* amplitude= */ 0.8f, /* frequencyHz= */ 0, /* duration= */ 100)));
-        List<VibrationEffectSegment> expectedSegments = Arrays.asList(
-                new StepSegment(/* amplitude= */ 1, /* frequencyHz= */ 0, /* duration= */ 10),
-                new StepSegment(/* amplitude= */ 0.5f, /* frequencyHz= */ 0, /* duration= */ 5),
-                new StepSegment(/* amplitude= */ 0, /* frequencyHz= */ 0, /* duration= */ 5),
-                new StepSegment(/* amplitude= */ 0.8f, /* frequencyHz= */ 0, /* duration= */ 100));
-
-        assertEquals(3, mAdapter.apply(segments, 2, TEST_VIBRATOR_INFO));
-        assertEquals(expectedSegments, segments);
-    }
-
-    @Test
-    public void testStepSegments_withZeroSegmentAtRepeat_fixesRepeatAndAppendOriginalToListEnd() {
-        List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
-                new StepSegment(/* amplitude= */ 1, /* frequencyHz= */ 0, /* duration= */ 10),
-                new StepSegment(/* amplitude= */ 0, /* frequencyHz= */ 0, /* duration= */ 50),
-                new StepSegment(/* amplitude= */ 1, /* frequencyHz= */ 0, /* duration= */ 100)));
-        List<VibrationEffectSegment> expectedSegments = Arrays.asList(
-                new StepSegment(/* amplitude= */ 1, /* frequencyHz= */ 0, /* duration= */ 10),
-                new StepSegment(/* amplitude= */ 0.75f, /* frequencyHz= */ 0, /* duration= */ 5),
-                new StepSegment(/* amplitude= */ 0.5f, /* frequencyHz= */ 0, /* duration= */ 5),
-                new StepSegment(/* amplitude= */ 0.25f, /* frequencyHz= */ 0, /* duration= */ 5),
-                new StepSegment(/* amplitude= */ 0, /* frequencyHz= */ 0, /* duration= */ 35),
-                new StepSegment(/* amplitude= */ 1, /* frequencyHz= */ 0, /* duration= */ 100),
-                // Original zero segment appended to the end of new looping vibration,
-                // then converted to ramp down as well.
-                new StepSegment(/* amplitude= */ 0.75f, /* frequencyHz= */ 0, /* duration= */ 5),
-                new StepSegment(/* amplitude= */ 0.5f, /* frequencyHz= */ 0, /* duration= */ 5),
-                new StepSegment(/* amplitude= */ 0.25f, /* frequencyHz= */ 0, /* duration= */ 5),
-                new StepSegment(/* amplitude= */ 0, /* frequencyHz= */ 0, /* duration= */ 35));
-
-        // Repeat index fixed after intermediate steps added
-        assertEquals(5, mAdapter.apply(segments, 1, TEST_VIBRATOR_INFO));
-        assertEquals(expectedSegments, segments);
-    }
-
-    @Test
-    public void testStepSegments_withRepeatToNonZeroSegment_keepsOriginalSteps() {
-        List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
-                new StepSegment(/* amplitude= */ 0.8f, /* frequencyHz= */ 0, /* duration= */ 10),
-                new StepSegment(/* amplitude= */ 0.5f, /* frequencyHz= */ 0, /* duration= */ 100)));
-        List<VibrationEffectSegment> originalSegments = new ArrayList<>(segments);
-
-        assertEquals(0, mAdapter.apply(segments, 0, TEST_VIBRATOR_INFO));
-
-        assertEquals(originalSegments, segments);
-    }
-
-    @Test
-    public void testStepSegments_withRepeatToShortZeroSegment_skipAndAppendRampDown() {
-        List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
-                new RampSegment(/* startAmplitude= */ 0, /* endAmplitude= */ 0,
-                        /* startfrequencyHz= */ 0, /* endfrequencyHz= */ 0, /* duration= */ 10),
-                new StepSegment(/* amplitude= */ 1, /* frequencyHz= */ 0, /* duration= */ 30)));
-        List<VibrationEffectSegment> expectedSegments = Arrays.asList(
-                new RampSegment(/* startAmplitude= */ 0, /* endAmplitude= */ 0,
-                        /* startfrequencyHz= */ 0, /* endfrequencyHz= */ 0, /* duration= */ 10),
-                new StepSegment(/* amplitude= */ 1, /* frequencyHz= */ 0, /* duration= */ 30),
-                new StepSegment(/* amplitude= */ 0.5f, /* frequencyHz= */ 0, /* duration= */ 5),
-                new StepSegment(/* amplitude= */ 0, /* frequencyHz= */ 0, /* duration= */ 5));
-
-        // Shift repeat index to the right to use append instead of zero segment.
-        assertEquals(1, mAdapter.apply(segments, 0, TEST_VIBRATOR_INFO));
-
-        assertEquals(expectedSegments, segments);
-    }
-
-    @Test
-    public void testStepSegments_withRepeatToLongZeroSegment_splitAndAppendRampDown() {
-        List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
-                new StepSegment(/* amplitude= */ 0, /* frequencyHz= */ 0, /* duration= */ 120),
-                new StepSegment(/* amplitude= */ 1, /* frequencyHz= */ 0, /* duration= */ 30)));
-        List<VibrationEffectSegment> expectedSegments = Arrays.asList(
-                // Split long zero segment to skip part of it.
-                new StepSegment(/* amplitude= */ 0, /* frequencyHz= */ 0, /* duration= */ 20),
-                new StepSegment(/* amplitude= */ 0, /* frequencyHz= */ 0, /* duration= */ 100),
-                new StepSegment(/* amplitude= */ 1, /* frequencyHz= */ 0, /* duration= */ 30),
-                new StepSegment(/* amplitude= */ 0.75f, /* frequencyHz= */ 0, /* duration= */ 5),
-                new StepSegment(/* amplitude= */ 0.5f, /* frequencyHz= */ 0, /* duration= */ 5),
-                new StepSegment(/* amplitude= */ 0.25f, /* frequencyHz= */ 0, /* duration= */ 5),
-                new StepSegment(/* amplitude= */ 0, /* frequencyHz= */ 0, /* duration= */ 5));
-
-        // Shift repeat index to the right to use append with part of the zero segment.
-        assertEquals(1, mAdapter.apply(segments, 0, TEST_VIBRATOR_INFO));
-
-        assertEquals(expectedSegments, segments);
-    }
-
-    @Test
-    public void testRampSegments_withShortZeroSegment_replaceWithRampDown() {
-        List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
-                new RampSegment(/* startAmplitude= */ 0.5f, /* endAmplitude*/ 0.5f,
-                        /* startFrequencyHz= */ 100, /* endFrequencyHz= */ 100, /* duration= */ 10),
-                new RampSegment(/* startAmplitude= */ 0, /* endAmplitude= */ 0,
-                        /* startFrequencyHz= */ 100, /* endFrequencyHz= */ 100, /* duration= */ 20),
-                new RampSegment(/* startAmplitude= */ 1, /* endAmplitude= */ 1,
-                        /* startFrequencyHz= */ 200, /* endFrequencyHz= */ 200,
-                        /* duration= */ 30)));
-        List<VibrationEffectSegment> expectedSegments = Arrays.asList(
-                new RampSegment(/* startAmplitude= */ 0.5f, /* endAmplitude*/ 0.5f,
-                        /* startFrequencyHz= */ 100, /* endFrequencyHz= */ 100, /* duration= */ 10),
-                new RampSegment(/* startAmplitude= */ 0.5f, /* endAmplitude= */ 0,
-                        /* startFrequencyHz= */ 100, /* endFrequencyHz= */ 100, /* duration= */ 20),
-                new RampSegment(/* startAmplitude= */ 1, /* endAmplitude= */ 1,
-                        /* startFrequencyHz= */ 200, /* endFrequencyHz= */ 200,
-                        /* duration= */ 30));
-
-        assertEquals(2, mAdapter.apply(segments, 2, TEST_VIBRATOR_INFO));
-
-        assertEquals(expectedSegments, segments);
-    }
-
-    @Test
-    public void testRampSegments_withLongZeroSegment_splitAndAddRampDown() {
-        List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
-                new RampSegment(/* startAmplitude= */ 0.5f, /* endAmplitude= */ 0.5f,
-                        /* startFrequencyHz= */ 100, /* endFrequencyHz= */ 100, /* duration= */ 10),
-                new StepSegment(/* amplitude= */ 0, /* frequencyHz= */ 150, /* duration= */ 150),
-                new RampSegment(/* startAmplitude= */ 1, /* endAmplitude= */ 1,
-                        /* startFrequencyHz= */ 200, /* endFrequencyHz= */ 200,
-                        /* duration= */ 30)));
-        List<VibrationEffectSegment> expectedSegments = Arrays.asList(
-                new RampSegment(/* startAmplitude= */ 0.5f, /* endAmplitude*/ 0.5f,
-                        /* startFrequencyHz= */ 100, /* endFrequencyHz= */ 100, /* duration= */ 10),
-                new RampSegment(/* startAmplitude= */ 0.5f, /* endAmplitude= */ 0,
-                        /* startFrequencyHz= */ 100, /* endFrequencyHz= */ 100, /* duration= */ 20),
-                new RampSegment(/* startAmplitude= */ 0, /* endAmplitude= */ 0,
-                        /* startFrequencyHz= */ 100, /* endFrequencyHz= */ 100,
-                        /* duration= */ 130),
-                new RampSegment(/* startAmplitude= */ 1, /* endAmplitude= */ 1,
-                        /* startFrequencyHz= */ 200, /* endFrequencyHz= */ 200,
-                        /* duration= */ 30));
-
-        // Repeat index fixed after intermediate steps added
-        assertEquals(3, mAdapter.apply(segments, 2, TEST_VIBRATOR_INFO));
-
-        assertEquals(expectedSegments, segments);
-    }
-
-    @Test
-    public void testRampSegments_withRepeatToNonZeroSegment_keepsOriginalSteps() {
-        List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
-                new RampSegment(/* startAmplitude= */ 0.5f, /* endAmplitude*/ 0.5f,
-                        /* startFrequencyHz= */ 100, /* endFrequencyHz= */ 100, /* duration= */ 10),
-                new RampSegment(/* startAmplitude= */ 1, /* endAmplitude= */ 1,
-                        /* startFrequencyHz= */ 200, /* endFrequencyHz= */ 200,
-                        /* duration= */ 30)));
-        List<VibrationEffectSegment> originalSegments = new ArrayList<>(segments);
-
-        assertEquals(0, mAdapter.apply(segments, 0, TEST_VIBRATOR_INFO));
-
-        assertEquals(originalSegments, segments);
-    }
-
-    @Test
-    public void testRampSegments_withRepeatToShortZeroSegment_skipAndAppendRampDown() {
-        List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
-                new StepSegment(/* amplitude= */ 0, /* frequencyHz= */ 200, /* duration= */ 20),
-                new RampSegment(/* startAmplitude= */ 0, /* endAmplitude*/ 1,
-                        /* startFrequencyHz= */ 40, /* endFrequencyHz= */ 80, /* duration= */ 20)));
-        List<VibrationEffectSegment> expectedSegments = Arrays.asList(
-                new StepSegment(/* amplitude= */ 0, /* frequencyHz= */ 200, /* duration= */ 20),
-                new RampSegment(/* startAmplitude= */ 0, /* endAmplitude= */ 1,
-                        /* startFrequencyHz= */ 40, /* endFrequencyHz= */ 80, /* duration= */ 20),
-                new RampSegment(/* startAmplitude= */ 1, /* endAmplitude= */ 0,
-                        /* startFrequencyHz= */ 80, /* endFrequencyHz= */ 80, /* duration= */ 20));
-
-        // Shift repeat index to the right to use append instead of zero segment.
-        assertEquals(1, mAdapter.apply(segments, 0, TEST_VIBRATOR_INFO));
-
-        assertEquals(expectedSegments, segments);
-    }
-
-    @Test
-    public void testRampSegments_withRepeatToLongZeroSegment_splitAndAppendRampDown() {
-        List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
-                new RampSegment(/* startAmplitude= */ 0, /* endAmplitude*/ 0,
-                        /* startFrequencyHz= */ 1, /* endFrequencyHz= */ 1, /* duration= */ 70),
-                new RampSegment(/* startAmplitude= */ 0, /* endAmplitude= */ 1,
-                        /* startFrequencyHz= */ 1, /* endFrequencyHz= */ 1, /* duration= */ 30)));
-        List<VibrationEffectSegment> expectedSegments = Arrays.asList(
-                // Split long zero segment to skip part of it.
-                new RampSegment(/* startAmplitude= */ 0, /* endAmplitude*/ 0,
-                        /* startFrequencyHz= */ 1, /* endFrequencyHz= */ 1, /* duration= */ 20),
-                new RampSegment(/* startAmplitude= */ 0, /* endAmplitude*/ 0,
-                        /* startFrequencyHz= */ 1, /* endFrequencyHz= */ 1, /* duration= */ 50),
-                new RampSegment(/* startAmplitude= */ 0, /* endAmplitude= */ 1,
-                        /* startFrequencyHz= */ 1, /* endFrequencyHz= */ 1, /* duration= */ 30),
-                new RampSegment(/* startAmplitude= */ 1, /* endAmplitude= */ 0,
-                        /* startFrequencyHz= */ 1, /* endFrequencyHz= */ 1, /* duration= */ 20));
-
-        // Shift repeat index to the right to use append with part of the zero segment.
-        assertEquals(1, mAdapter.apply(segments, 0, TEST_VIBRATOR_INFO));
-
-        assertEquals(expectedSegments, segments);
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/RampToStepAdapterTest.java b/services/tests/servicestests/src/com/android/server/vibrator/RampToStepAdapterTest.java
deleted file mode 100644
index a9f37f3..0000000
--- a/services/tests/servicestests/src/com/android/server/vibrator/RampToStepAdapterTest.java
+++ /dev/null
@@ -1,130 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.vibrator;
-
-import static org.junit.Assert.assertEquals;
-
-import android.hardware.vibrator.IVibrator;
-import android.os.VibrationEffect;
-import android.os.VibratorInfo;
-import android.os.vibrator.PrebakedSegment;
-import android.os.vibrator.PrimitiveSegment;
-import android.os.vibrator.RampSegment;
-import android.os.vibrator.StepSegment;
-import android.os.vibrator.VibrationEffectSegment;
-import android.platform.test.annotations.Presubmit;
-
-import org.junit.Before;
-import org.junit.Test;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.stream.IntStream;
-
-/**
- * Tests for {@link RampToStepAdapter}.
- *
- * Build/Install/Run:
- * atest FrameworksServicesTests:RampToStepAdapterTest
- */
-@Presubmit
-public class RampToStepAdapterTest {
-    private static final int TEST_STEP_DURATION = 5;
-    private static final float[] TEST_AMPLITUDE_MAP = new float[]{
-            /* 50Hz= */ 0.1f, 0.2f, 0.4f, 0.8f, /* 150Hz= */ 1f, 0.9f, /* 200Hz= */ 0.8f};
-    private static final VibratorInfo.FrequencyProfile TEST_FREQUENCY_PROFILE =
-            new VibratorInfo.FrequencyProfile(
-                    /* resonantFrequencyHz= */ 150f, /* minFrequencyHz= */ 50f,
-                    /* frequencyResolutionHz= */ 25f, TEST_AMPLITUDE_MAP);
-
-    private RampToStepAdapter mAdapter;
-
-    @Before
-    public void setUp() throws Exception {
-        mAdapter = new RampToStepAdapter(TEST_STEP_DURATION);
-    }
-
-    @Test
-    public void testStepAndPrebakedAndPrimitiveSegments_keepsListUnchanged() {
-        List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
-                new StepSegment(/* amplitude= */ 0, /* frequencyHz= */ 1, /* duration= */ 10),
-                new PrebakedSegment(
-                        VibrationEffect.EFFECT_CLICK, false, VibrationEffect.EFFECT_STRENGTH_LIGHT),
-                new PrimitiveSegment(VibrationEffect.Composition.PRIMITIVE_TICK, 1, 10)));
-        List<VibrationEffectSegment> originalSegments = new ArrayList<>(segments);
-
-        assertEquals(-1, mAdapter.apply(segments, -1, createVibratorInfo()));
-        assertEquals(1, mAdapter.apply(segments, 1, createVibratorInfo()));
-
-        assertEquals(originalSegments, segments);
-    }
-
-    @Test
-    public void testRampSegments_withPwleCapability_keepsListUnchanged() {
-        List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
-                new StepSegment(/* amplitude= */ 0.5f, /* frequencyHz= */ 0, /* duration= */ 100),
-                new RampSegment(/* startAmplitude= */ 0.8f, /* endAmplitude= */ 0.2f,
-                        /* startFrequencyHz= */ 100, /* endFrequencyHz= */ 1, /* duration= */ 20)));
-        List<VibrationEffectSegment> originalSegments = new ArrayList<>(segments);
-
-        VibratorInfo vibratorInfo = createVibratorInfo(IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
-        assertEquals(-1, mAdapter.apply(segments, -1, vibratorInfo));
-        assertEquals(0, mAdapter.apply(segments, 0, vibratorInfo));
-
-        assertEquals(originalSegments, segments);
-    }
-
-    @Test
-    public void testRampSegments_withoutPwleCapability_convertsRampsToSteps() {
-        List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
-                new StepSegment(/* amplitude= */ 0, /* frequencyHz= */ 1, /* duration= */ 10),
-                new StepSegment(/* amplitude= */ 0.5f, /* frequencyHz= */ 10, /* duration= */ 100),
-                new RampSegment(/* startAmplitude= */ 1, /* endAmplitude= */ 0.2f,
-                        /* startFrequencyHz= */ 10, /* endFrequencyHz= */ 0, /* duration= */ 10),
-                new RampSegment(/* startAmplitude= */ 0.8f, /* endAmplitude= */ 0.2f,
-                        /* startFrequencyHz= */ 30, /* endFrequencyHz= */ 60, /* duration= */ 11),
-                new RampSegment(/* startAmplitude= */ 0.65f, /* endAmplitude= */ 0.65f,
-                        /* startFrequencyHz= */ 0, /* endFrequencyHz= */ 1, /* duration= */ 200)));
-
-        List<VibrationEffectSegment> expectedSegments = Arrays.asList(
-                new StepSegment(/* amplitude= */ 0, /* frequencyHz= */ 1, /* duration= */ 10),
-                new StepSegment(/* amplitude= */ 0.5f, /* frequencyHz= */ 10, /* duration= */ 100),
-                // 10ms ramp becomes 2 steps
-                new StepSegment(/* amplitude= */ 1, /* frequencyHz= */ 10, /* duration= */ 5),
-                new StepSegment(/* amplitude= */ 0.2f, /* frequencyHz= */ 150, /* duration= */ 5),
-                // 11ms ramp becomes 3 steps
-                new StepSegment(/* amplitude= */ 0.8f, /* frequencyHz= */ 30, /* duration= */ 5),
-                new StepSegment(/* amplitude= */ 0.6f, /* frequencyHz= */ 40, /* duration= */ 5),
-                new StepSegment(/* amplitude= */ 0.2f, /* frequencyHz= */ 60, /* duration= */ 1),
-                // 200ms ramp with same amplitude becomes a single step
-                new StepSegment(/* amplitude= */ 0.65f, /* frequencyHz= */ 150,
-                        /* duration= */ 200));
-
-        // Repeat index fixed after intermediate steps added
-        assertEquals(4, mAdapter.apply(segments, 3, createVibratorInfo()));
-
-        assertEquals(expectedSegments, segments);
-    }
-
-    private static VibratorInfo createVibratorInfo(int... capabilities) {
-        return new VibratorInfo.Builder(0)
-                .setCapabilities(IntStream.of(capabilities).reduce((a, b) -> a | b).orElse(0))
-                .setFrequencyProfile(TEST_FREQUENCY_PROFILE)
-                .build();
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/StepToRampAdapterTest.java b/services/tests/servicestests/src/com/android/server/vibrator/StepToRampAdapterTest.java
deleted file mode 100644
index 54627c4..0000000
--- a/services/tests/servicestests/src/com/android/server/vibrator/StepToRampAdapterTest.java
+++ /dev/null
@@ -1,197 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.vibrator;
-
-import static org.junit.Assert.assertEquals;
-
-import android.hardware.vibrator.IVibrator;
-import android.os.VibrationEffect;
-import android.os.VibratorInfo;
-import android.os.vibrator.PrebakedSegment;
-import android.os.vibrator.PrimitiveSegment;
-import android.os.vibrator.RampSegment;
-import android.os.vibrator.StepSegment;
-import android.os.vibrator.VibrationEffectSegment;
-import android.platform.test.annotations.Presubmit;
-
-import org.junit.Before;
-import org.junit.Test;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.stream.IntStream;
-
-/**
- * Tests for {@link StepToRampAdapter}.
- *
- * Build/Install/Run:
- * atest FrameworksServicesTests:StepToRampAdapterTest
- */
-@Presubmit
-public class StepToRampAdapterTest {
-    private static final float[] TEST_AMPLITUDE_MAP = new float[]{
-            /* 50Hz= */ 0.1f, 0.2f, 0.4f, 0.8f, /* 150Hz= */ 1f, 0.9f, /* 200Hz= */ 0.8f};
-    private static final VibratorInfo.FrequencyProfile TEST_FREQUENCY_PROFILE =
-            new VibratorInfo.FrequencyProfile(
-                    /* resonantFrequencyHz= */ 150f, /* minFrequencyHz= */ 50f,
-                    /* frequencyResolutionHz= */ 25f, TEST_AMPLITUDE_MAP);
-
-    private StepToRampAdapter mAdapter;
-
-    @Before
-    public void setUp() throws Exception {
-        mAdapter = new StepToRampAdapter();
-    }
-
-    @Test
-    public void testRampAndPrebakedAndPrimitiveSegments_returnsOriginalSegments() {
-        List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
-                new RampSegment(/* startAmplitude= */ 1, /* endAmplitude= */ 0.2f,
-                        /* startFrequencyHz= */ 40f, /* endFrequencyHz= */ 20f, /* duration= */ 10),
-                new PrebakedSegment(
-                        VibrationEffect.EFFECT_CLICK, false, VibrationEffect.EFFECT_STRENGTH_LIGHT),
-                new PrimitiveSegment(VibrationEffect.Composition.PRIMITIVE_TICK, 1, 10)));
-        List<VibrationEffectSegment> originalSegments = new ArrayList<>(segments);
-
-        assertEquals(-1, mAdapter.apply(segments, -1, createVibratorInfo()));
-        assertEquals(1, mAdapter.apply(segments, 1, createVibratorInfo()));
-
-        assertEquals(originalSegments, segments);
-    }
-
-    @Test
-    public void testRampSegments_withPwleDurationLimit_splitsLongRamps() {
-        List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
-                new RampSegment(/* startAmplitude= */ 0.5f, /* endAmplitude*/ 0.5f,
-                        /* startFrequencyHz= */ 10, /* endFrequencyHz= */ 10, /* duration= */ 10),
-                new RampSegment(/* startAmplitude= */ 0, /* endAmplitude= */ 1,
-                        /* startFrequencyHz= */ 0, /* endFrequencyHz= */ 50, /* duration= */ 25),
-                new RampSegment(/* startAmplitude= */ 1, /* endAmplitude*/ 1,
-                        /* startFrequencyHz= */ 10, /* endFrequencyHz= */ 20, /* duration= */ 5)));
-        List<VibrationEffectSegment> expectedSegments = Arrays.asList(
-                new RampSegment(/* startAmplitude= */ 0.5f, /* endAmplitude*/ 0.5f,
-                        /* startFrequencyHz= */ 10, /* endFrequencyHz= */ 10, /* duration= */ 10),
-                new RampSegment(/* startAmplitude= */ 0, /* endAmplitude= */ 0.32f,
-                        /* startFrequencyHz= */ 150, /* endFrequencyHz= */ 118f, /* duration= */ 8),
-                new RampSegment(/* startAmplitude= */ 0.32f, /* endAmplitude= */ 0.64f,
-                        /* startFrequencyHz= */ 118f, /* endFrequencyHz= */ 86f,
-                        /* duration= */ 8),
-                new RampSegment(/* startAmplitude= */ 0.64f, /* endAmplitude= */ 1,
-                        /* startFrequencyHz= */ 86f, /* endFrequencyHz= */ 50f, /* duration= */ 9),
-                new RampSegment(/* startAmplitude= */ 1, /* endAmplitude*/ 1,
-                        /* startFrequencyHz= */ 10, /* endFrequencyHz= */ 20, /* duration= */ 5));
-
-        VibratorInfo vibratorInfo = new VibratorInfo.Builder(0)
-                .setCapabilities(IVibrator.CAP_COMPOSE_PWLE_EFFECTS)
-                .setPwlePrimitiveDurationMax(10)
-                .setFrequencyProfile(TEST_FREQUENCY_PROFILE)
-                .build();
-
-        // Update repeat index to skip the ramp splits.
-        assertEquals(4, mAdapter.apply(segments, 2, vibratorInfo));
-        assertEquals(expectedSegments, segments);
-    }
-
-    @Test
-    public void testStepAndRampSegments_withoutPwleCapability_keepsListUnchanged() {
-        List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
-                new StepSegment(/* amplitude= */ 0, /* frequencyHz= */ 1, /* duration= */ 10),
-                new RampSegment(/* startAmplitude= */ 0.8f, /* endAmplitude= */ 0.2f,
-                        /* startFrequencyHz= */ 10, /* endFrequencyHz= */ 50, /* duration= */ 20)));
-        List<VibrationEffectSegment> originalSegments = new ArrayList<>(segments);
-
-        assertEquals(-1, mAdapter.apply(segments, -1, createVibratorInfo()));
-        assertEquals(0, mAdapter.apply(segments, 0, createVibratorInfo()));
-
-        assertEquals(originalSegments, segments);
-    }
-
-    @Test
-    public void testStepAndRampSegments_withPwleCapabilityAndNoFrequency_keepsOriginalSteps() {
-        List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
-                new StepSegment(/* amplitude= */ 0, /* frequencyHz= */ 0, /* duration= */ 10),
-                new StepSegment(/* amplitude= */ 0.5f, /* frequencyHz= */ 0, /* duration= */ 100),
-                new PrimitiveSegment(VibrationEffect.Composition.PRIMITIVE_TICK, 1, 10),
-                new RampSegment(/* startAmplitude= */ 1, /* endAmplitude= */ 1,
-                        /* startFrequencyHz= */ 40, /* endFrequencyHz= */ 200, /* duration= */ 50),
-                new RampSegment(/* startAmplitude= */ 0.8f, /* endAmplitude= */ 0.2f,
-                        /* startFrequencyHz= */ 10, /* endFrequencyHz= */ 1, /* duration= */ 20)));
-        List<VibrationEffectSegment> originalSegments = new ArrayList<>(segments);
-
-        VibratorInfo vibratorInfo = createVibratorInfo(IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
-        assertEquals(-1, mAdapter.apply(segments, -1, vibratorInfo));
-        assertEquals(3, mAdapter.apply(segments, 3, vibratorInfo));
-
-        assertEquals(originalSegments, segments);
-    }
-
-    @Test
-    public void testStepAndRampSegments_withPwleCapabilityAndStepNextToRamp_convertsStepsToRamps() {
-        List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
-                new StepSegment(/* amplitude= */ 0, /* frequencyHz= */ 200, /* duration= */ 10),
-                new StepSegment(/* amplitude= */ 0.5f, /* frequencyHz= */ 150, /* duration= */ 60),
-                new RampSegment(/* startAmplitude= */ 1, /* endAmplitude= */ 1,
-                        /* startFrequencyHz= */ 1, /* endFrequencyHz= */ 300, /* duration= */ 50),
-                new RampSegment(/* startAmplitude= */ 0.8f, /* endAmplitude= */ 0.2f,
-                        /* startFrequencyHz= */ 1000, /* endFrequencyHz= */ 1, /* duration= */ 20),
-                new StepSegment(/* amplitude= */ 0.8f, /* frequencyHz= */ 10, /* duration= */ 60)));
-
-        List<VibrationEffectSegment> expectedSegments = Arrays.asList(
-                new RampSegment(/* startAmplitude= */ 0, /* endAmplitude*/ 0,
-                        /* startFrequencyHz= */ 200, /* endFrequencyHz= */ 200, /* duration= */ 10),
-                new RampSegment(/* startAmplitude= */ 0.5f, /* endAmplitude= */ 0.5f,
-                        /* startFrequencyHz= */ 150, /* endFrequencyHz= */ 150, /* duration= */ 60),
-                new RampSegment(/* startAmplitude= */ 1, /* endAmplitude= */ 1,
-                        /* startFrequencyHz= */ 1, /* endFrequencyHz= */ 300, /* duration= */ 50),
-                new RampSegment(/* startAmplitude= */ 0.8f, /* endAmplitude= */ 0.2f,
-                        /* startFrequencyHz= */ 1000, /* endFrequencyHz= */ 1, /* duration= */ 20),
-                new RampSegment(/* startAmplitude= */ 0.8f, /* endAmplitude= */ 0.8f,
-                        /* startFrequencyHz= */ 10, /* endFrequencyHz= */ 10, /* duration= */ 60));
-
-        VibratorInfo vibratorInfo = createVibratorInfo(IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
-        assertEquals(-1, mAdapter.apply(segments, -1, vibratorInfo));
-        assertEquals(2, mAdapter.apply(segments, 2, vibratorInfo));
-
-        assertEquals(expectedSegments, segments);
-    }
-
-    @Test
-    public void testStepSegments_withPwleCapabilityAndFrequency_convertsStepsToRamps() {
-        List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
-                new StepSegment(/* amplitude= */ 0, /* frequencyHz= */ 100, /* duration= */ 10),
-                new StepSegment(/* amplitude= */ 0.5f, /* frequencyHz= */ 0, /* duration= */ 6)));
-        List<VibrationEffectSegment> expectedSegments = Arrays.asList(
-                new RampSegment(/* startAmplitude= */ 0, /* endAmplitude*/ 0,
-                        /* startFrequencyHz= */ 100, /* endFrequencyHz= */ 100, /* duration= */ 10),
-                new RampSegment(/* startAmplitude= */ 0.5f, /* endAmplitude= */ 0.5f,
-                        /* startFrequencyHz= */ 150, /* endFrequencyHz= */ 150, /* duration= */ 6));
-
-        VibratorInfo vibratorInfo = createVibratorInfo(IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
-        assertEquals(-1, mAdapter.apply(segments, -1, vibratorInfo));
-        assertEquals(0, mAdapter.apply(segments, 0, vibratorInfo));
-
-        assertEquals(expectedSegments, segments);
-    }
-
-    private static VibratorInfo createVibratorInfo(int... capabilities) {
-        return new VibratorInfo.Builder(0)
-                .setCapabilities(IntStream.of(capabilities).reduce((a, b) -> a | b).orElse(0))
-                .setFrequencyProfile(TEST_FREQUENCY_PROFILE)
-                .build();
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibrationScalerTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibrationScalerTest.java
deleted file mode 100644
index 64950aa..0000000
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibrationScalerTest.java
+++ /dev/null
@@ -1,282 +0,0 @@
-/*
- * 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.
- */
-
-package com.android.server.vibrator;
-
-import static android.os.VibrationAttributes.USAGE_NOTIFICATION;
-import static android.os.VibrationAttributes.USAGE_RINGTONE;
-import static android.os.VibrationAttributes.USAGE_TOUCH;
-import static android.os.Vibrator.VIBRATION_INTENSITY_HIGH;
-import static android.os.Vibrator.VIBRATION_INTENSITY_LOW;
-import static android.os.Vibrator.VIBRATION_INTENSITY_MEDIUM;
-import static android.os.Vibrator.VIBRATION_INTENSITY_OFF;
-
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertTrue;
-
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.when;
-
-import android.content.ComponentName;
-import android.content.ContentResolver;
-import android.content.ContextWrapper;
-import android.content.pm.PackageManagerInternal;
-import android.os.Handler;
-import android.os.IExternalVibratorService;
-import android.os.PowerManagerInternal;
-import android.os.UserHandle;
-import android.os.VibrationAttributes;
-import android.os.VibrationEffect;
-import android.os.Vibrator;
-import android.os.test.TestLooper;
-import android.os.vibrator.PrebakedSegment;
-import android.os.vibrator.PrimitiveSegment;
-import android.os.vibrator.StepSegment;
-import android.os.vibrator.VibrationConfig;
-import android.os.vibrator.VibrationEffectSegment;
-import android.platform.test.annotations.Presubmit;
-import android.provider.Settings;
-
-import androidx.test.InstrumentationRegistry;
-
-import com.android.internal.util.test.FakeSettingsProvider;
-import com.android.internal.util.test.FakeSettingsProviderRule;
-import com.android.server.LocalServices;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
-
-/**
- * Tests for {@link VibrationScaler}.
- *
- * Build/Install/Run:
- * atest FrameworksServicesTests:VibrationScalerTest
- */
-@Presubmit
-public class VibrationScalerTest {
-
-    @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
-    @Rule public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
-
-    @Mock private PowerManagerInternal mPowerManagerInternalMock;
-    @Mock private PackageManagerInternal mPackageManagerInternalMock;
-    @Mock private VibrationConfig mVibrationConfigMock;
-
-    private TestLooper mTestLooper;
-    private ContextWrapper mContextSpy;
-    private VibrationSettings mVibrationSettings;
-    private VibrationScaler mVibrationScaler;
-
-    @Before
-    public void setUp() throws Exception {
-        mTestLooper = new TestLooper();
-        mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getContext()));
-
-        ContentResolver contentResolver = mSettingsProviderRule.mockContentResolver(mContextSpy);
-        when(mContextSpy.getContentResolver()).thenReturn(contentResolver);
-        when(mPackageManagerInternalMock.getSystemUiServiceComponent())
-                .thenReturn(new ComponentName("", ""));
-
-        LocalServices.removeServiceForTest(PackageManagerInternal.class);
-        LocalServices.addService(PackageManagerInternal.class, mPackageManagerInternalMock);
-        LocalServices.removeServiceForTest(PowerManagerInternal.class);
-        LocalServices.addService(PowerManagerInternal.class, mPowerManagerInternalMock);
-
-        Settings.System.putInt(contentResolver, Settings.System.HAPTIC_FEEDBACK_ENABLED, 1);
-        Settings.System.putInt(contentResolver, Settings.System.VIBRATE_WHEN_RINGING, 1);
-
-        mVibrationSettings = new VibrationSettings(
-                mContextSpy, new Handler(mTestLooper.getLooper()), mVibrationConfigMock);
-        mVibrationScaler = new VibrationScaler(mContextSpy, mVibrationSettings);
-
-        mVibrationSettings.onSystemReady();
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        LocalServices.removeServiceForTest(PowerManagerInternal.class);
-    }
-
-    @Test
-    public void testGetExternalVibrationScale() {
-        setDefaultIntensity(USAGE_TOUCH, Vibrator.VIBRATION_INTENSITY_LOW);
-        setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_HIGH);
-        assertEquals(IExternalVibratorService.SCALE_VERY_HIGH,
-                mVibrationScaler.getExternalVibrationScale(USAGE_TOUCH));
-
-        setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_MEDIUM);
-        assertEquals(IExternalVibratorService.SCALE_HIGH,
-                mVibrationScaler.getExternalVibrationScale(USAGE_TOUCH));
-
-        setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_LOW);
-        assertEquals(IExternalVibratorService.SCALE_NONE,
-                mVibrationScaler.getExternalVibrationScale(USAGE_TOUCH));
-
-        setDefaultIntensity(USAGE_TOUCH, VIBRATION_INTENSITY_MEDIUM);
-        assertEquals(IExternalVibratorService.SCALE_LOW,
-                mVibrationScaler.getExternalVibrationScale(USAGE_TOUCH));
-
-        setDefaultIntensity(USAGE_TOUCH, VIBRATION_INTENSITY_HIGH);
-        assertEquals(IExternalVibratorService.SCALE_VERY_LOW,
-                mVibrationScaler.getExternalVibrationScale(USAGE_TOUCH));
-
-        setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF);
-        // Vibration setting being bypassed will use default setting and not scale.
-        assertEquals(IExternalVibratorService.SCALE_NONE,
-                mVibrationScaler.getExternalVibrationScale(USAGE_TOUCH));
-    }
-
-    @Test
-    public void scale_withPrebakedSegment_setsEffectStrengthBasedOnSettings() {
-        setDefaultIntensity(USAGE_NOTIFICATION, VIBRATION_INTENSITY_MEDIUM);
-        setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_HIGH);
-        PrebakedSegment effect = new PrebakedSegment(VibrationEffect.EFFECT_CLICK,
-                /* shouldFallback= */ false, VibrationEffect.EFFECT_STRENGTH_MEDIUM);
-
-        PrebakedSegment scaled = mVibrationScaler.scale(effect, USAGE_NOTIFICATION);
-        assertEquals(scaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_STRONG);
-
-        setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
-                VIBRATION_INTENSITY_MEDIUM);
-        scaled = mVibrationScaler.scale(effect, USAGE_NOTIFICATION);
-        assertEquals(scaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_MEDIUM);
-
-        setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_LOW);
-        scaled = mVibrationScaler.scale(effect, USAGE_NOTIFICATION);
-        assertEquals(scaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_LIGHT);
-
-        setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_OFF);
-        scaled = mVibrationScaler.scale(effect, USAGE_NOTIFICATION);
-        // Vibration setting being bypassed will use default setting.
-        assertEquals(scaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_MEDIUM);
-    }
-
-    @Test
-    public void scale_withPrebakedEffect_setsEffectStrengthBasedOnSettings() {
-        setDefaultIntensity(USAGE_NOTIFICATION, VIBRATION_INTENSITY_LOW);
-        setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_HIGH);
-        VibrationEffect effect = VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK);
-
-        PrebakedSegment scaled =
-                getFirstSegment(mVibrationScaler.scale(effect, USAGE_NOTIFICATION));
-        assertEquals(scaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_STRONG);
-
-        setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
-                VIBRATION_INTENSITY_MEDIUM);
-        scaled = getFirstSegment(mVibrationScaler.scale(effect, USAGE_NOTIFICATION));
-        assertEquals(scaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_MEDIUM);
-
-        setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_LOW);
-        scaled = getFirstSegment(mVibrationScaler.scale(effect, USAGE_NOTIFICATION));
-        assertEquals(scaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_LIGHT);
-
-        setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_OFF);
-        scaled = getFirstSegment(mVibrationScaler.scale(effect, USAGE_NOTIFICATION));
-        // Vibration setting being bypassed will use default setting.
-        assertEquals(scaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_LIGHT);
-    }
-
-    @Test
-    public void scale_withOneShotAndWaveform_resolvesAmplitude() {
-        // No scale, default amplitude still resolved
-        setDefaultIntensity(USAGE_RINGTONE, VIBRATION_INTENSITY_LOW);
-        setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, VIBRATION_INTENSITY_LOW);
-
-        StepSegment resolved = getFirstSegment(mVibrationScaler.scale(
-                VibrationEffect.createOneShot(10, VibrationEffect.DEFAULT_AMPLITUDE),
-                USAGE_RINGTONE));
-        assertTrue(resolved.getAmplitude() > 0);
-
-        resolved = getFirstSegment(mVibrationScaler.scale(
-                VibrationEffect.createWaveform(new long[]{10},
-                        new int[]{VibrationEffect.DEFAULT_AMPLITUDE}, -1),
-                USAGE_RINGTONE));
-        assertTrue(resolved.getAmplitude() > 0);
-    }
-
-    @Test
-    public void scale_withOneShotAndWaveform_scalesAmplitude() {
-        setDefaultIntensity(USAGE_RINGTONE, VIBRATION_INTENSITY_LOW);
-        setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, VIBRATION_INTENSITY_HIGH);
-        setDefaultIntensity(USAGE_NOTIFICATION, VIBRATION_INTENSITY_HIGH);
-        setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_LOW);
-        setDefaultIntensity(USAGE_TOUCH, VIBRATION_INTENSITY_MEDIUM);
-        setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_MEDIUM);
-
-        StepSegment scaled = getFirstSegment(mVibrationScaler.scale(
-                VibrationEffect.createOneShot(128, 128), USAGE_RINGTONE));
-        // Ringtone scales up.
-        assertTrue(scaled.getAmplitude() > 0.5);
-
-        scaled = getFirstSegment(mVibrationScaler.scale(
-                VibrationEffect.createWaveform(new long[]{128}, new int[]{128}, -1),
-                USAGE_NOTIFICATION));
-        // Notification scales down.
-        assertTrue(scaled.getAmplitude() < 0.5);
-
-        scaled = getFirstSegment(mVibrationScaler.scale(VibrationEffect.createOneShot(128, 128),
-                USAGE_TOUCH));
-        // Haptic feedback does not scale.
-        assertEquals(128f / 255, scaled.getAmplitude(), 1e-5);
-    }
-
-    @Test
-    public void scale_withComposed_scalesPrimitives() {
-        setDefaultIntensity(USAGE_RINGTONE, VIBRATION_INTENSITY_LOW);
-        setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, VIBRATION_INTENSITY_HIGH);
-        setDefaultIntensity(USAGE_NOTIFICATION, VIBRATION_INTENSITY_HIGH);
-        setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_LOW);
-        setDefaultIntensity(USAGE_TOUCH, VIBRATION_INTENSITY_MEDIUM);
-        setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_MEDIUM);
-
-        VibrationEffect composed = VibrationEffect.startComposition()
-                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f).compose();
-
-        PrimitiveSegment scaled = getFirstSegment(mVibrationScaler.scale(composed, USAGE_RINGTONE));
-        // Ringtone scales up.
-        assertTrue(scaled.getScale() > 0.5f);
-
-        scaled = getFirstSegment(mVibrationScaler.scale(composed, USAGE_NOTIFICATION));
-        // Notification scales down.
-        assertTrue(scaled.getScale() < 0.5f);
-
-        scaled = getFirstSegment(mVibrationScaler.scale(composed, USAGE_TOUCH));
-        // Haptic feedback does not scale.
-        assertEquals(0.5, scaled.getScale(), 1e-5);
-    }
-
-    private void setDefaultIntensity(@VibrationAttributes.Usage int usage,
-            @Vibrator.VibrationIntensity int intensity) {
-        when(mVibrationConfigMock.getDefaultVibrationIntensity(eq(usage))).thenReturn(intensity);
-    }
-
-    private <T extends VibrationEffectSegment> T getFirstSegment(VibrationEffect.Composed effect) {
-        return (T) effect.getSegments().get(0);
-    }
-
-    private void setUserSetting(String settingName, int value) {
-        Settings.System.putIntForUser(
-                mContextSpy.getContentResolver(), settingName, value, UserHandle.USER_CURRENT);
-        // FakeSettingsProvider don't support testing triggering ContentObserver yet.
-        mVibrationSettings.mSettingObserver.onChange(false);
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibrationSettingsTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibrationSettingsTest.java
deleted file mode 100644
index 2efd9fc..0000000
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibrationSettingsTest.java
+++ /dev/null
@@ -1,851 +0,0 @@
-/*
- * 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.
- */
-
-package com.android.server.vibrator;
-
-import static android.os.VibrationAttributes.USAGE_ACCESSIBILITY;
-import static android.os.VibrationAttributes.USAGE_ALARM;
-import static android.os.VibrationAttributes.USAGE_COMMUNICATION_REQUEST;
-import static android.os.VibrationAttributes.USAGE_HARDWARE_FEEDBACK;
-import static android.os.VibrationAttributes.USAGE_MEDIA;
-import static android.os.VibrationAttributes.USAGE_NOTIFICATION;
-import static android.os.VibrationAttributes.USAGE_PHYSICAL_EMULATION;
-import static android.os.VibrationAttributes.USAGE_RINGTONE;
-import static android.os.VibrationAttributes.USAGE_TOUCH;
-import static android.os.VibrationAttributes.USAGE_UNKNOWN;
-import static android.os.Vibrator.VIBRATION_INTENSITY_HIGH;
-import static android.os.Vibrator.VIBRATION_INTENSITY_LOW;
-import static android.os.Vibrator.VIBRATION_INTENSITY_MEDIUM;
-import static android.os.Vibrator.VIBRATION_INTENSITY_OFF;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.when;
-
-import android.app.ActivityManager;
-import android.content.ComponentName;
-import android.content.ContentResolver;
-import android.content.ContextWrapper;
-import android.content.Intent;
-import android.content.pm.PackageManagerInternal;
-import android.media.AudioManager;
-import android.os.Handler;
-import android.os.PowerManager;
-import android.os.PowerManagerInternal;
-import android.os.PowerSaveState;
-import android.os.Process;
-import android.os.UserHandle;
-import android.os.VibrationAttributes;
-import android.os.VibrationEffect;
-import android.os.Vibrator;
-import android.os.test.TestLooper;
-import android.os.vibrator.VibrationConfig;
-import android.platform.test.annotations.Presubmit;
-import android.provider.Settings;
-import android.util.ArraySet;
-import android.view.Display;
-
-import androidx.test.InstrumentationRegistry;
-
-import com.android.internal.util.test.FakeSettingsProvider;
-import com.android.internal.util.test.FakeSettingsProviderRule;
-import com.android.server.LocalServices;
-import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
-
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.Set;
-
-/**
- * Tests for {@link VibrationSettings}.
- *
- * Build/Install/Run:
- * atest FrameworksServicesTests:VibrationSettingsTest
- */
-@Presubmit
-public class VibrationSettingsTest {
-
-    private static final int UID = 1;
-    private static final int VIRTUAL_DISPLAY_ID = 1;
-    private static final String SYSUI_PACKAGE_NAME = "sysui";
-    private static final PowerSaveState NORMAL_POWER_STATE = new PowerSaveState.Builder().build();
-    private static final PowerSaveState LOW_POWER_STATE = new PowerSaveState.Builder()
-            .setBatterySaverEnabled(true).build();
-
-    private static final int[] ALL_USAGES = new int[] {
-            USAGE_UNKNOWN,
-            USAGE_ACCESSIBILITY,
-            USAGE_ALARM,
-            USAGE_COMMUNICATION_REQUEST,
-            USAGE_HARDWARE_FEEDBACK,
-            USAGE_MEDIA,
-            USAGE_NOTIFICATION,
-            USAGE_PHYSICAL_EMULATION,
-            USAGE_RINGTONE,
-            USAGE_TOUCH,
-    };
-
-    @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
-    @Rule public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
-
-    @Mock private VibrationSettings.OnVibratorSettingsChanged mListenerMock;
-    @Mock
-    private PowerManagerInternal mPowerManagerInternalMock;
-    @Mock
-    private VirtualDeviceManagerInternal mVirtualDeviceManagerInternalMock;
-    @Mock
-    private PackageManagerInternal mPackageManagerInternalMock;
-    @Mock
-    private VibrationConfig mVibrationConfigMock;
-
-    private TestLooper mTestLooper;
-    private ContextWrapper mContextSpy;
-    private AudioManager mAudioManager;
-    private VibrationSettings mVibrationSettings;
-    private PowerManagerInternal.LowPowerModeListener mRegisteredPowerModeListener;
-    private VirtualDeviceManagerInternal.VirtualDisplayListener mRegisteredVirtualDisplayListener;
-    private VirtualDeviceManagerInternal.AppsOnVirtualDeviceListener
-            mRegisteredAppsOnVirtualDeviceListener;
-
-    @Before
-    public void setUp() throws Exception {
-        mTestLooper = new TestLooper();
-        mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getContext()));
-
-        ContentResolver contentResolver = mSettingsProviderRule.mockContentResolver(mContextSpy);
-        when(mContextSpy.getContentResolver()).thenReturn(contentResolver);
-        doAnswer(invocation -> {
-            mRegisteredPowerModeListener = invocation.getArgument(0);
-            return null;
-        }).when(mPowerManagerInternalMock).registerLowPowerModeObserver(any());
-        when(mPackageManagerInternalMock.getSystemUiServiceComponent())
-                .thenReturn(new ComponentName(SYSUI_PACKAGE_NAME, ""));
-        doAnswer(invocation -> {
-            mRegisteredVirtualDisplayListener = invocation.getArgument(0);
-            return null;
-        }).when(mVirtualDeviceManagerInternalMock).registerVirtualDisplayListener(any());
-        doAnswer(invocation -> {
-            mRegisteredAppsOnVirtualDeviceListener = invocation.getArgument(0);
-            return null;
-        }).when(mVirtualDeviceManagerInternalMock).registerAppsOnVirtualDeviceListener(any());
-
-        removeServicesForTest();
-        addServicesForTest();
-
-        setDefaultIntensity(VIBRATION_INTENSITY_MEDIUM);
-        mAudioManager = mContextSpy.getSystemService(AudioManager.class);
-        mVibrationSettings = new VibrationSettings(mContextSpy,
-                new Handler(mTestLooper.getLooper()), mVibrationConfigMock);
-
-        mockGoToSleep(/* goToSleepTime= */ 0, PowerManager.GO_TO_SLEEP_REASON_TIMEOUT);
-
-        // Simulate System defaults.
-        setUserSetting(Settings.System.HAPTIC_FEEDBACK_ENABLED, 1);
-        setUserSetting(Settings.System.VIBRATE_INPUT_DEVICES, 0);
-        setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 1);
-        setUserSetting(Settings.System.APPLY_RAMPING_RINGER, 0);
-        setRingerMode(AudioManager.RINGER_MODE_NORMAL);
-        mVibrationSettings.onSystemReady();
-    }
-
-    private void removeServicesForTest() {
-        LocalServices.removeServiceForTest(PowerManagerInternal.class);
-        LocalServices.removeServiceForTest(PackageManagerInternal.class);
-        LocalServices.removeServiceForTest(VirtualDeviceManagerInternal.class);
-    }
-
-    private void addServicesForTest() {
-        LocalServices.addService(PowerManagerInternal.class, mPowerManagerInternalMock);
-        LocalServices.addService(PackageManagerInternal.class, mPackageManagerInternalMock);
-        LocalServices.addService(VirtualDeviceManagerInternal.class,
-                mVirtualDeviceManagerInternalMock);
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        removeServicesForTest();
-    }
-
-    @Test
-    public void create_withOnlyRequiredSystemServices() {
-        // The only core services that we depend on are PowerManager and PackageManager
-        removeServicesForTest();
-        LocalServices.addService(PowerManagerInternal.class, mPowerManagerInternalMock);
-        LocalServices.addService(PackageManagerInternal.class, mPackageManagerInternalMock);
-
-        VibrationSettings minimalVibrationSettings = new VibrationSettings(mContextSpy,
-                new Handler(mTestLooper.getLooper()), mVibrationConfigMock);
-        minimalVibrationSettings.onSystemReady();
-    }
-
-    @Test
-    public void addListener_settingsChangeTriggerListener() {
-        mVibrationSettings.addListener(mListenerMock);
-
-        mVibrationSettings.mSettingObserver.onChange(false);
-        mVibrationSettings.mSettingObserver.onChange(false);
-
-        verify(mListenerMock, times(2)).onChange();
-    }
-
-    @Test
-    public void addListener_lowPowerModeChangeTriggerListener() {
-        mVibrationSettings.addListener(mListenerMock);
-
-        mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE);
-        mRegisteredPowerModeListener.onLowPowerModeChanged(NORMAL_POWER_STATE);
-        mRegisteredPowerModeListener.onLowPowerModeChanged(NORMAL_POWER_STATE); // No change.
-
-        verify(mListenerMock, times(2)).onChange();
-    }
-
-    @Test
-    public void removeListener_noMoreCallbacksToListener() {
-        mVibrationSettings.addListener(mListenerMock);
-
-        setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, 0);
-        verify(mListenerMock).onChange();
-
-        mVibrationSettings.removeListener(mListenerMock);
-
-        verifyNoMoreInteractions(mListenerMock);
-        setUserSetting(Settings.System.VIBRATE_INPUT_DEVICES, 1);
-    }
-
-    @Test
-    public void shouldIgnoreVibration_fromBackground_doesNotIgnoreUsagesFromAllowlist() {
-        Set<Integer> expectedAllowedVibrations = new HashSet<>(Arrays.asList(
-                USAGE_RINGTONE,
-                USAGE_ALARM,
-                USAGE_NOTIFICATION,
-                USAGE_COMMUNICATION_REQUEST,
-                USAGE_HARDWARE_FEEDBACK,
-                USAGE_PHYSICAL_EMULATION
-        ));
-
-        mVibrationSettings.mUidObserver.onUidStateChanged(
-                UID, ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND, 0, 0);
-
-        for (int usage : ALL_USAGES) {
-            if (expectedAllowedVibrations.contains(usage)) {
-                assertVibrationNotIgnoredForUsage(usage);
-            } else {
-                assertVibrationIgnoredForUsage(usage, Vibration.Status.IGNORED_BACKGROUND);
-            }
-        }
-    }
-
-    @Test
-    public void shouldIgnoreVibration_fromForeground_allowsAnyUsage() {
-        mVibrationSettings.mUidObserver.onUidStateChanged(
-                UID, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND, 0, 0);
-
-        for (int usage : ALL_USAGES) {
-            assertVibrationNotIgnoredForUsage(usage);
-        }
-    }
-
-    @Test
-    public void shouldIgnoreVibration_inBatterySaverMode_doesNotIgnoreUsagesFromAllowlist() {
-        Set<Integer> expectedAllowedVibrations = new HashSet<>(Arrays.asList(
-                USAGE_RINGTONE,
-                USAGE_ALARM,
-                USAGE_COMMUNICATION_REQUEST,
-                USAGE_PHYSICAL_EMULATION,
-                USAGE_HARDWARE_FEEDBACK
-        ));
-
-        mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE);
-
-        for (int usage : ALL_USAGES) {
-            if (expectedAllowedVibrations.contains(usage)) {
-                assertVibrationNotIgnoredForUsage(usage);
-            } else {
-                assertVibrationIgnoredForUsage(usage, Vibration.Status.IGNORED_FOR_POWER);
-            }
-        }
-    }
-
-    @Test
-    public void shouldIgnoreVibration_notInBatterySaverMode_allowsAnyUsage() {
-        mRegisteredPowerModeListener.onLowPowerModeChanged(NORMAL_POWER_STATE);
-
-        for (int usage : ALL_USAGES) {
-            assertVibrationNotIgnoredForUsage(usage);
-        }
-    }
-
-    @Test
-    public void shouldIgnoreVibration_withRingerModeSilent_ignoresRingtoneAndNotification() {
-        // Vibrating settings on are overruled by ringer mode.
-        setUserSetting(Settings.System.HAPTIC_FEEDBACK_ENABLED, 1);
-        setRingerMode(AudioManager.RINGER_MODE_SILENT);
-
-        for (int usage : ALL_USAGES) {
-            if (usage == USAGE_RINGTONE || usage == USAGE_NOTIFICATION) {
-                assertVibrationIgnoredForUsage(usage, Vibration.Status.IGNORED_FOR_RINGER_MODE);
-            } else {
-                assertVibrationNotIgnoredForUsage(usage);
-            }
-        }
-    }
-
-    @Test
-    public void shouldIgnoreVibration_withRingerModeSilentAndBypassFlag_allowsAllVibrations() {
-        setRingerMode(AudioManager.RINGER_MODE_SILENT);
-
-        for (int usage : ALL_USAGES) {
-            assertVibrationNotIgnoredForUsageAndFlags(usage,
-                    VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY);
-        }
-    }
-
-    @Test
-    public void shouldIgnoreVibration_withRingerModeVibrate_allowsAllVibrations() {
-        setRingerMode(AudioManager.RINGER_MODE_VIBRATE);
-
-        for (int usage : ALL_USAGES) {
-            assertVibrationNotIgnoredForUsage(usage);
-        }
-    }
-
-    @Test
-    public void shouldIgnoreVibration_withRingerModeNormal_allowsAllVibrations() {
-        setRingerMode(AudioManager.RINGER_MODE_NORMAL);
-
-        for (int usage : ALL_USAGES) {
-            assertVibrationNotIgnoredForUsage(usage);
-        }
-    }
-
-
-    @Test
-    public void shouldIgnoreVibration_vibrateOnDisabled_ignoresUsagesNotAccessibility() {
-        setUserSetting(Settings.System.VIBRATE_ON, 0);
-
-        for (int usage : ALL_USAGES) {
-            if (usage == USAGE_ACCESSIBILITY) {
-                assertVibrationNotIgnoredForUsage(usage);
-            } else {
-                assertVibrationIgnoredForUsage(usage, Vibration.Status.IGNORED_FOR_SETTINGS);
-            }
-            assertVibrationNotIgnoredForUsageAndFlags(usage,
-                    VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF);
-        }
-    }
-
-    @Test
-    public void shouldIgnoreVibration_vibrateOnEnabledOrUnset_allowsAnyUsage() {
-        deleteUserSetting(Settings.System.VIBRATE_ON);
-        for (int usage : ALL_USAGES) {
-            assertVibrationNotIgnoredForUsage(usage);
-        }
-
-        setUserSetting(Settings.System.VIBRATE_ON, 1);
-        for (int usage : ALL_USAGES) {
-            assertVibrationNotIgnoredForUsage(usage);
-        }
-    }
-
-    @Test
-    public void shouldIgnoreVibration_withRingSettingsOff_allowsAllVibrations() {
-        // VIBRATE_WHEN_RINGING is deprecated and should have no effect on the ring vibration
-        // setting. The ramping ringer is also independent now, instead of a 3-state setting.
-        setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0);
-        setUserSetting(Settings.System.APPLY_RAMPING_RINGER, 0);
-
-        for (int usage : ALL_USAGES) {
-            assertVibrationNotIgnoredForUsage(usage);
-            assertVibrationNotIgnoredForUsageAndFlags(usage,
-                    VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF);
-        }
-    }
-
-    @Test
-    public void shouldIgnoreVibration_withHapticFeedbackDisabled_ignoresTouchVibration() {
-        // HAPTIC_FEEDBACK_ENABLED is deprecated but it was the only setting used to disable touch
-        // feedback vibrations. Continue to apply this on top of the intensity setting.
-        setUserSetting(Settings.System.HAPTIC_FEEDBACK_ENABLED, 0);
-
-        for (int usage : ALL_USAGES) {
-            if (usage == USAGE_TOUCH) {
-                assertVibrationIgnoredForUsage(usage, Vibration.Status.IGNORED_FOR_SETTINGS);
-            } else {
-                assertVibrationNotIgnoredForUsage(usage);
-            }
-            assertVibrationNotIgnoredForUsageAndFlags(usage,
-                    VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF);
-        }
-    }
-
-    @Test
-    public void shouldIgnoreVibration_withHapticFeedbackSettingsOff_ignoresTouchVibration() {
-        setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF);
-
-        for (int usage : ALL_USAGES) {
-            if (usage == USAGE_TOUCH) {
-                assertVibrationIgnoredForUsage(usage, Vibration.Status.IGNORED_FOR_SETTINGS);
-            } else {
-                assertVibrationNotIgnoredForUsage(usage);
-            }
-            assertVibrationNotIgnoredForUsageAndFlags(usage,
-                    VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF);
-        }
-    }
-
-    @Test
-    public void shouldIgnoreVibration_withHardwareFeedbackSettingsOff_ignoresHardwareVibrations() {
-        setUserSetting(Settings.System.HARDWARE_HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF);
-
-        for (int usage : ALL_USAGES) {
-            if (usage == USAGE_HARDWARE_FEEDBACK || usage == USAGE_PHYSICAL_EMULATION) {
-                assertVibrationIgnoredForUsage(usage, Vibration.Status.IGNORED_FOR_SETTINGS);
-            } else {
-                assertVibrationNotIgnoredForUsage(usage);
-            }
-            assertVibrationNotIgnoredForUsageAndFlags(usage,
-                    VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF);
-        }
-    }
-
-    @Test
-    public void shouldIgnoreVibration_withNotificationSettingsOff_ignoresNotificationVibrations() {
-        setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_OFF);
-
-        for (int usage : ALL_USAGES) {
-            if (usage == USAGE_NOTIFICATION) {
-                assertVibrationIgnoredForUsage(usage, Vibration.Status.IGNORED_FOR_SETTINGS);
-            } else {
-                assertVibrationNotIgnoredForUsage(usage);
-            }
-            assertVibrationNotIgnoredForUsageAndFlags(usage,
-                    VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF);
-        }
-    }
-
-    @Test
-    public void shouldIgnoreVibration_withRingSettingsOff_ignoresRingtoneVibrations() {
-        // Vibrating settings on are overruled by ring intensity setting.
-        setRingerMode(AudioManager.RINGER_MODE_VIBRATE);
-        setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, VIBRATION_INTENSITY_OFF);
-
-        for (int usage : ALL_USAGES) {
-            if (usage == USAGE_RINGTONE) {
-                assertVibrationIgnoredForUsage(usage, Vibration.Status.IGNORED_FOR_SETTINGS);
-            } else {
-                assertVibrationNotIgnoredForUsage(usage);
-            }
-            assertVibrationNotIgnoredForUsageAndFlags(usage,
-                    VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF);
-        }
-    }
-
-    @Test
-    public void shouldIgnoreVibration_updateTriggeredAfterInternalRingerModeChanged() {
-        // Vibrating settings on are overruled by ringer mode.
-        setUserSetting(Settings.System.HAPTIC_FEEDBACK_ENABLED, 1);
-        setUserSetting(Settings.System.APPLY_RAMPING_RINGER, 1);
-        setRingerMode(AudioManager.RINGER_MODE_NORMAL);
-
-        assertVibrationNotIgnoredForUsage(USAGE_RINGTONE);
-
-        // Testing the broadcast flow manually.
-        mAudioManager.setRingerModeInternal(AudioManager.RINGER_MODE_SILENT);
-        mVibrationSettings.mSettingChangeReceiver.onReceive(mContextSpy,
-                new Intent(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION));
-
-        assertVibrationIgnoredForUsage(USAGE_RINGTONE, Vibration.Status.IGNORED_FOR_RINGER_MODE);
-    }
-
-    @Test
-    public void shouldIgnoreVibrationFromVirtualDisplays_displayNonVirtual_neverIgnored() {
-        // Vibrations from the primary display is never ignored regardless of the creation and
-        // removal of virtual displays and of the changes of apps running on virtual displays.
-        mRegisteredVirtualDisplayListener.onVirtualDisplayCreated(VIRTUAL_DISPLAY_ID);
-        mRegisteredAppsOnVirtualDeviceListener.onAppsOnAnyVirtualDeviceChanged(
-                new ArraySet<>(Arrays.asList(UID)));
-        for (int usage : ALL_USAGES) {
-            assertVibrationNotIgnoredForUsageAndDisplay(usage, Display.DEFAULT_DISPLAY);
-        }
-
-        mRegisteredVirtualDisplayListener.onVirtualDisplayRemoved(VIRTUAL_DISPLAY_ID);
-        for (int usage : ALL_USAGES) {
-            assertVibrationNotIgnoredForUsageAndDisplay(usage, Display.DEFAULT_DISPLAY);
-        }
-
-        mRegisteredAppsOnVirtualDeviceListener.onAppsOnAnyVirtualDeviceChanged(new ArraySet<>());
-        for (int usage : ALL_USAGES) {
-            assertVibrationNotIgnoredForUsageAndDisplay(usage, Display.DEFAULT_DISPLAY);
-        }
-    }
-
-    @Test
-    public void shouldIgnoreVibrationFromVirtualDisplays_displayVirtual() {
-        // Ignore the vibration when the coming display id represents a virtual display.
-        mRegisteredVirtualDisplayListener.onVirtualDisplayCreated(VIRTUAL_DISPLAY_ID);
-
-        for (int usage : ALL_USAGES) {
-            assertVibrationIgnoredForUsageAndDisplay(usage, VIRTUAL_DISPLAY_ID,
-                    Vibration.Status.IGNORED_FROM_VIRTUAL_DEVICE);
-        }
-
-        // Stop ignoring when the virtual display is removed.
-        mRegisteredVirtualDisplayListener.onVirtualDisplayRemoved(VIRTUAL_DISPLAY_ID);
-        for (int usage : ALL_USAGES) {
-            assertVibrationNotIgnoredForUsageAndDisplay(usage, VIRTUAL_DISPLAY_ID);
-        }
-    }
-
-
-    @Test
-    public void shouldIgnoreVibrationFromVirtualDisplays_appsOnVirtualDisplay() {
-        // Ignore when the passed-in display id is invalid and the calling uid is on a virtual
-        // display.
-        mRegisteredAppsOnVirtualDeviceListener.onAppsOnAnyVirtualDeviceChanged(
-                new ArraySet<>(Arrays.asList(UID)));
-        for (int usage : ALL_USAGES) {
-            assertVibrationIgnoredForUsageAndDisplay(usage, Display.INVALID_DISPLAY,
-                    Vibration.Status.IGNORED_FROM_VIRTUAL_DEVICE);
-        }
-
-        // Stop ignoring when the app is no longer on virtual display.
-        mRegisteredAppsOnVirtualDeviceListener.onAppsOnAnyVirtualDeviceChanged(new ArraySet<>());
-        for (int usage : ALL_USAGES) {
-            assertVibrationNotIgnoredForUsageAndDisplay(usage, Display.INVALID_DISPLAY);
-        }
-
-    }
-
-    @Test
-    public void shouldVibrateInputDevices_returnsSettingsValue() {
-        setUserSetting(Settings.System.VIBRATE_INPUT_DEVICES, 1);
-        assertTrue(mVibrationSettings.shouldVibrateInputDevices());
-
-        setUserSetting(Settings.System.VIBRATE_INPUT_DEVICES, 0);
-        assertFalse(mVibrationSettings.shouldVibrateInputDevices());
-    }
-
-    @Test
-    public void shouldCancelVibrationOnScreenOff_withEventBeforeVibration_returnsAlwaysFalse() {
-        long vibrateStartTime = 100;
-        mockGoToSleep(vibrateStartTime - 10, PowerManager.GO_TO_SLEEP_REASON_APPLICATION);
-
-        for (int usage : ALL_USAGES) {
-            // Non-system vibration
-            assertFalse(mVibrationSettings.shouldCancelVibrationOnScreenOff(createCallerInfo(
-                    UID, "some.app", usage), vibrateStartTime));
-            // Vibration with UID zero
-            assertFalse(mVibrationSettings.shouldCancelVibrationOnScreenOff(
-                    createCallerInfo(/* uid= */ 0, "", usage), vibrateStartTime));
-            // System vibration
-            assertFalse(mVibrationSettings.shouldCancelVibrationOnScreenOff(
-                    createCallerInfo(Process.SYSTEM_UID, "", usage), vibrateStartTime));
-            // SysUI vibration
-            assertFalse(mVibrationSettings.shouldCancelVibrationOnScreenOff(
-                    createCallerInfo(UID, SYSUI_PACKAGE_NAME, usage), vibrateStartTime));
-        }
-    }
-
-    @Test
-    public void shouldCancelVibrationOnScreenOff_withSleepReasonInAllowlist_returnsAlwaysFalse() {
-        long vibrateStartTime = 100;
-        int[] allowedSleepReasons = new int[]{
-                PowerManager.GO_TO_SLEEP_REASON_TIMEOUT,
-                PowerManager.GO_TO_SLEEP_REASON_INATTENTIVE,
-        };
-
-        for (int sleepReason : allowedSleepReasons) {
-            mockGoToSleep(vibrateStartTime + 10, sleepReason);
-
-            for (int usage : ALL_USAGES) {
-                // Non-system vibration
-                assertFalse(mVibrationSettings.shouldCancelVibrationOnScreenOff(
-                        createCallerInfo(UID, "some.app", usage), vibrateStartTime));
-                // Vibration with UID zero
-                assertFalse(mVibrationSettings.shouldCancelVibrationOnScreenOff(
-                        createCallerInfo(/* uid= */ 0, "", usage), vibrateStartTime));
-                // System vibration
-                assertFalse(mVibrationSettings.shouldCancelVibrationOnScreenOff(
-                        createCallerInfo(Process.SYSTEM_UID, "", usage), vibrateStartTime));
-                // SysUI vibration
-                assertFalse(mVibrationSettings.shouldCancelVibrationOnScreenOff(
-                        createCallerInfo(UID, SYSUI_PACKAGE_NAME, usage), vibrateStartTime));
-            }
-        }
-    }
-
-    @Test
-    public void shouldCancelVibrationOnScreenOff_withNonSystem_returnsTrueIfReasonNotInAllowlist() {
-        long vibrateStartTime = 100;
-        mockGoToSleep(vibrateStartTime + 10, PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON);
-
-        for (int usage : ALL_USAGES) {
-            assertTrue(mVibrationSettings.shouldCancelVibrationOnScreenOff(
-                    createCallerInfo(UID, "some.app", usage), vibrateStartTime));
-        }
-    }
-
-    @Test
-    public void shouldCancelVibrationOnScreenOff_withUidZero_returnsFalseForUsagesInAllowlist() {
-        long vibrateStartTime = 100;
-        mockGoToSleep(vibrateStartTime + 10, PowerManager.GO_TO_SLEEP_REASON_DEVICE_ADMIN);
-
-        Set<Integer> expectedAllowedVibrations = new HashSet<>(Arrays.asList(
-                USAGE_TOUCH,
-                USAGE_ACCESSIBILITY,
-                USAGE_PHYSICAL_EMULATION,
-                USAGE_HARDWARE_FEEDBACK
-        ));
-
-        for (int usage : ALL_USAGES) {
-            if (expectedAllowedVibrations.contains(usage)) {
-                assertFalse(mVibrationSettings.shouldCancelVibrationOnScreenOff(
-                        createCallerInfo(/* uid= */ 0, "", usage), vibrateStartTime));
-            } else {
-                assertTrue(mVibrationSettings.shouldCancelVibrationOnScreenOff(
-                        createCallerInfo(/* uid= */ 0, "", usage), vibrateStartTime));
-            }
-        }
-    }
-
-    @Test
-    public void shouldCancelVibrationOnScreenOff_withSystemUid__returnsFalseForUsagesInAllowlist() {
-        long vibrateStartTime = 100;
-        mockGoToSleep(vibrateStartTime + 10, PowerManager.GO_TO_SLEEP_REASON_DEVICE_FOLD);
-
-        Set<Integer> expectedAllowedVibrations = new HashSet<>(Arrays.asList(
-                USAGE_TOUCH,
-                USAGE_ACCESSIBILITY,
-                USAGE_PHYSICAL_EMULATION,
-                USAGE_HARDWARE_FEEDBACK
-        ));
-
-        for (int usage : ALL_USAGES) {
-            if (expectedAllowedVibrations.contains(usage)) {
-                assertFalse(mVibrationSettings.shouldCancelVibrationOnScreenOff(
-                        createCallerInfo(Process.SYSTEM_UID, "", usage), vibrateStartTime));
-            } else {
-                assertTrue(mVibrationSettings.shouldCancelVibrationOnScreenOff(
-                        createCallerInfo(Process.SYSTEM_UID, "", usage), vibrateStartTime));
-            }
-        }
-    }
-
-    @Test
-    public void shouldCancelVibrationOnScreenOff_withSysUiPkg_returnsFalseForUsagesInAllowlist() {
-        long vibrateStartTime = 100;
-        mockGoToSleep(vibrateStartTime + 10, PowerManager.GO_TO_SLEEP_REASON_HDMI);
-
-        Set<Integer> expectedAllowedVibrations = new HashSet<>(Arrays.asList(
-                USAGE_TOUCH,
-                USAGE_ACCESSIBILITY,
-                USAGE_PHYSICAL_EMULATION,
-                USAGE_HARDWARE_FEEDBACK
-        ));
-
-        for (int usage : ALL_USAGES) {
-            if (expectedAllowedVibrations.contains(usage)) {
-                assertFalse(mVibrationSettings.shouldCancelVibrationOnScreenOff(
-                        createCallerInfo(UID, SYSUI_PACKAGE_NAME, usage), vibrateStartTime));
-            } else {
-                assertTrue(mVibrationSettings.shouldCancelVibrationOnScreenOff(
-                        createCallerInfo(UID, SYSUI_PACKAGE_NAME, usage), vibrateStartTime));
-            }
-        }
-    }
-
-    @Test
-    public void getDefaultIntensity_returnsIntensityFromVibratorConfig() {
-        setDefaultIntensity(VIBRATION_INTENSITY_HIGH);
-        setUserSetting(Settings.System.ALARM_VIBRATION_INTENSITY, VIBRATION_INTENSITY_OFF);
-        setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF);
-        setUserSetting(Settings.System.HARDWARE_HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF);
-        setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_OFF);
-        setUserSetting(Settings.System.MEDIA_VIBRATION_INTENSITY, VIBRATION_INTENSITY_OFF);
-        setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, VIBRATION_INTENSITY_OFF);
-
-        for (int usage : ALL_USAGES) {
-            assertEquals(VIBRATION_INTENSITY_HIGH, mVibrationSettings.getDefaultIntensity(usage));
-        }
-    }
-
-    @Test
-    public void getCurrentIntensity_returnsIntensityFromSettings() {
-        setDefaultIntensity(VIBRATION_INTENSITY_OFF);
-        setUserSetting(Settings.System.ALARM_VIBRATION_INTENSITY, VIBRATION_INTENSITY_LOW);
-        setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_LOW);
-        setUserSetting(Settings.System.HARDWARE_HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_LOW);
-        setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_LOW);
-        setUserSetting(Settings.System.MEDIA_VIBRATION_INTENSITY, VIBRATION_INTENSITY_LOW);
-        setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, VIBRATION_INTENSITY_LOW);
-
-        for (int usage : ALL_USAGES) {
-            assertEquals(errorMessageForUsage(usage),
-                    VIBRATION_INTENSITY_LOW,
-                    mVibrationSettings.getCurrentIntensity(usage));
-        }
-    }
-
-    @Test
-    public void getCurrentIntensity_updateTriggeredAfterUserSwitched() {
-        setDefaultIntensity(USAGE_RINGTONE, VIBRATION_INTENSITY_OFF);
-        setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, VIBRATION_INTENSITY_HIGH);
-        assertEquals(VIBRATION_INTENSITY_HIGH,
-                mVibrationSettings.getCurrentIntensity(USAGE_RINGTONE));
-
-        // Switching user is not working with FakeSettingsProvider.
-        // Testing the broadcast flow manually.
-        Settings.System.putIntForUser(mContextSpy.getContentResolver(),
-                Settings.System.RING_VIBRATION_INTENSITY, VIBRATION_INTENSITY_LOW,
-                UserHandle.USER_CURRENT);
-        mVibrationSettings.mSettingChangeReceiver.onReceive(mContextSpy,
-                new Intent(Intent.ACTION_USER_SWITCHED));
-        assertEquals(VIBRATION_INTENSITY_LOW,
-                mVibrationSettings.getCurrentIntensity(USAGE_RINGTONE));
-    }
-
-    @Test
-    public void getCurrentIntensity_noHardwareFeedbackValueUsesHapticFeedbackValue() {
-        setDefaultIntensity(USAGE_HARDWARE_FEEDBACK, VIBRATION_INTENSITY_MEDIUM);
-        setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF);
-        assertEquals(VIBRATION_INTENSITY_OFF, mVibrationSettings.getCurrentIntensity(USAGE_TOUCH));
-        // If haptic feedback is off, fallback to default value.
-        assertEquals(VIBRATION_INTENSITY_MEDIUM,
-                mVibrationSettings.getCurrentIntensity(USAGE_HARDWARE_FEEDBACK));
-        assertEquals(VIBRATION_INTENSITY_MEDIUM,
-                mVibrationSettings.getCurrentIntensity(USAGE_PHYSICAL_EMULATION));
-
-        setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_HIGH);
-        assertEquals(VIBRATION_INTENSITY_HIGH,
-                mVibrationSettings.getCurrentIntensity(USAGE_TOUCH));
-        // If haptic feedback is on, fallback to that value.
-        assertEquals(VIBRATION_INTENSITY_HIGH,
-                mVibrationSettings.getCurrentIntensity(USAGE_HARDWARE_FEEDBACK));
-        assertEquals(VIBRATION_INTENSITY_HIGH,
-                mVibrationSettings.getCurrentIntensity(USAGE_PHYSICAL_EMULATION));
-    }
-
-    @Test
-    public void getFallbackEffect_returnsEffectsFromSettings() {
-        assertNotNull(mVibrationSettings.getFallbackEffect(VibrationEffect.EFFECT_TICK));
-        assertNotNull(mVibrationSettings.getFallbackEffect(VibrationEffect.EFFECT_TEXTURE_TICK));
-        assertNotNull(mVibrationSettings.getFallbackEffect(VibrationEffect.EFFECT_CLICK));
-        assertNotNull(mVibrationSettings.getFallbackEffect(VibrationEffect.EFFECT_HEAVY_CLICK));
-        assertNotNull(mVibrationSettings.getFallbackEffect(VibrationEffect.EFFECT_DOUBLE_CLICK));
-    }
-
-    private void assertVibrationIgnoredForUsage(@VibrationAttributes.Usage int usage,
-            Vibration.Status expectedStatus) {
-        assertVibrationIgnoredForUsageAndDisplay(usage, Display.DEFAULT_DISPLAY, expectedStatus);
-    }
-
-    private void assertVibrationIgnoredForUsageAndDisplay(@VibrationAttributes.Usage int usage,
-            int displayId, Vibration.Status expectedStatus) {
-        Vibration.CallerInfo callerInfo = new Vibration.CallerInfo(
-                VibrationAttributes.createForUsage(usage), UID, displayId, null, null);
-        assertEquals(errorMessageForUsage(usage), expectedStatus,
-                mVibrationSettings.shouldIgnoreVibration(callerInfo));
-    }
-
-    private void assertVibrationNotIgnoredForUsage(@VibrationAttributes.Usage int usage) {
-        assertVibrationNotIgnoredForUsageAndFlags(usage, /* flags= */ 0);
-    }
-
-    private void assertVibrationNotIgnoredForUsageAndFlags(@VibrationAttributes.Usage int usage,
-            @VibrationAttributes.Flag int flags) {
-        assertVibrationNotIgnoredForUsageAndFlagsAndDisplay(usage, Display.DEFAULT_DISPLAY, flags);
-    }
-
-    private void assertVibrationNotIgnoredForUsageAndDisplay(@VibrationAttributes.Usage int usage,
-            int displayId) {
-        assertVibrationNotIgnoredForUsageAndFlagsAndDisplay(usage, displayId, /* flags= */ 0);
-    }
-
-    private void assertVibrationNotIgnoredForUsageAndFlagsAndDisplay(
-            @VibrationAttributes.Usage int usage, int displayId,
-            @VibrationAttributes.Flag int flags) {
-        Vibration.CallerInfo callerInfo = new Vibration.CallerInfo(
-                new VibrationAttributes.Builder().setUsage(usage).setFlags(flags).build(), UID,
-                displayId, null, null);
-        assertNull(errorMessageForUsage(usage),
-                mVibrationSettings.shouldIgnoreVibration(callerInfo));
-    }
-
-
-    private String errorMessageForUsage(int usage) {
-        return "Error for usage " + VibrationAttributes.usageToString(usage);
-    }
-
-    private void setDefaultIntensity(@Vibrator.VibrationIntensity int intensity) {
-        when(mVibrationConfigMock.getDefaultVibrationIntensity(anyInt())).thenReturn(intensity);
-    }
-
-    private void setDefaultIntensity(@VibrationAttributes.Usage int usage,
-            @Vibrator.VibrationIntensity int intensity) {
-        when(mVibrationConfigMock.getDefaultVibrationIntensity(eq(usage))).thenReturn(intensity);
-    }
-
-    private void deleteUserSetting(String settingName) {
-        Settings.System.putStringForUser(
-                mContextSpy.getContentResolver(), settingName, null, UserHandle.USER_CURRENT);
-        // FakeSettingsProvider doesn't support testing triggering ContentObserver yet.
-        mVibrationSettings.mSettingObserver.onChange(false);
-    }
-
-    private void setUserSetting(String settingName, int value) {
-        Settings.System.putIntForUser(
-                mContextSpy.getContentResolver(), settingName, value, UserHandle.USER_CURRENT);
-        // FakeSettingsProvider doesn't support testing triggering ContentObserver yet.
-        mVibrationSettings.mSettingObserver.onChange(false);
-    }
-
-    private void setRingerMode(int ringerMode) {
-        mAudioManager.setRingerModeInternal(ringerMode);
-        assertEquals(ringerMode, mAudioManager.getRingerModeInternal());
-        mVibrationSettings.mSettingChangeReceiver.onReceive(mContextSpy,
-                new Intent(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION));
-    }
-
-    private void mockGoToSleep(long sleepTime, int reason) {
-        when(mPowerManagerInternalMock.getLastGoToSleep()).thenReturn(
-                new PowerManager.SleepData(sleepTime, reason));
-    }
-
-    private Vibration.CallerInfo createCallerInfo(int uid, String opPkg,
-            @VibrationAttributes.Usage int usage) {
-        VibrationAttributes attrs = VibrationAttributes.createForUsage(usage);
-        return new Vibration.CallerInfo(attrs, uid, VIRTUAL_DISPLAY_ID, opPkg, null);
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibrationTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibrationTest.java
deleted file mode 100644
index b469299..0000000
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibrationTest.java
+++ /dev/null
@@ -1,46 +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.server.vibrator;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static java.util.stream.Collectors.toList;
-
-import android.platform.test.annotations.Presubmit;
-
-import org.junit.Test;
-
-import java.util.Arrays;
-
-/**
- * Tests for {@link Vibration}.
- *
- * Build/Install/Run:
- * atest FrameworksServicesTests:VibrationTest
- */
-@Presubmit
-public class VibrationTest {
-
-    @Test
-    public void status_hasUniqueProtoEnumValues() {
-        assertThat(
-                Arrays.stream(Vibration.Status.values())
-                        .map(Vibration.Status::getProtoEnumValue)
-                        .collect(toList()))
-                .containsNoDuplicates();
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
deleted file mode 100644
index 12810bb..0000000
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
+++ /dev/null
@@ -1,1707 +0,0 @@
-/*
- * 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.
- */
-
-package com.android.server.vibrator;
-
-import static android.os.VibrationEffect.VibrationParameter.targetAmplitude;
-import static android.os.VibrationEffect.VibrationParameter.targetFrequency;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-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.ArgumentMatchers.same;
-import static org.mockito.Mockito.atLeast;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.inOrder;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.timeout;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.pm.PackageManagerInternal;
-import android.hardware.vibrator.Braking;
-import android.hardware.vibrator.IVibrator;
-import android.hardware.vibrator.IVibratorManager;
-import android.os.CombinedVibration;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.PowerManager;
-import android.os.Process;
-import android.os.SystemClock;
-import android.os.VibrationAttributes;
-import android.os.VibrationEffect;
-import android.os.Vibrator;
-import android.os.test.TestLooper;
-import android.os.vibrator.PrebakedSegment;
-import android.os.vibrator.PrimitiveSegment;
-import android.os.vibrator.RampSegment;
-import android.os.vibrator.StepSegment;
-import android.os.vibrator.VibrationConfig;
-import android.os.vibrator.VibrationEffectSegment;
-import android.platform.test.annotations.LargeTest;
-import android.platform.test.annotations.Presubmit;
-import android.util.SparseArray;
-
-import androidx.test.InstrumentationRegistry;
-
-import com.android.server.LocalServices;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.mockito.InOrder;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
-
-import java.time.Duration;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.function.BooleanSupplier;
-import java.util.stream.Collectors;
-
-/**
- * Tests for {@link VibrationThread}.
- *
- * Build/Install/Run:
- * atest FrameworksServicesTests:VibrationThreadTest
- */
-@Presubmit
-public class VibrationThreadTest {
-
-    private static final int TEST_TIMEOUT_MILLIS = 900;
-    private static final int UID = Process.ROOT_UID;
-    private static final int DISPLAY_ID = 10;
-    private static final int VIBRATOR_ID = 1;
-    private static final String PACKAGE_NAME = "package";
-    private static final VibrationAttributes ATTRS = new VibrationAttributes.Builder().build();
-    private static final int TEST_RAMP_STEP_DURATION = 5;
-
-    @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
-
-    @Mock private PackageManagerInternal mPackageManagerInternalMock;
-    @Mock private VibrationThread.VibratorManagerHooks mManagerHooks;
-    @Mock private VibratorController.OnVibrationCompleteListener mControllerCallbacks;
-    @Mock private IBinder mVibrationToken;
-    @Mock private VibrationConfig mVibrationConfigMock;
-
-    private final Map<Integer, FakeVibratorControllerProvider> mVibratorProviders = new HashMap<>();
-    private VibrationSettings mVibrationSettings;
-    private DeviceVibrationEffectAdapter mEffectAdapter;
-    private TestLooper mTestLooper;
-    private TestLooperAutoDispatcher mCustomTestLooperDispatcher;
-    private VibrationThread mThread;
-
-    // Setup from the providers when VibrationThread is initialized.
-    private SparseArray<VibratorController> mControllers;
-
-    @Before
-    public void setUp() throws Exception {
-        mTestLooper = new TestLooper();
-
-        when(mVibrationConfigMock.getDefaultVibrationIntensity(anyInt()))
-                .thenReturn(Vibrator.VIBRATION_INTENSITY_MEDIUM);
-        when(mVibrationConfigMock.getRampStepDurationMs()).thenReturn(TEST_RAMP_STEP_DURATION);
-        when(mPackageManagerInternalMock.getSystemUiServiceComponent())
-                .thenReturn(new ComponentName("", ""));
-
-        LocalServices.removeServiceForTest(PackageManagerInternal.class);
-        LocalServices.addService(PackageManagerInternal.class, mPackageManagerInternalMock);
-
-        Context context = InstrumentationRegistry.getContext();
-        mVibrationSettings = new VibrationSettings(context, new Handler(mTestLooper.getLooper()),
-                mVibrationConfigMock);
-
-        mockVibrators(VIBRATOR_ID);
-
-        mEffectAdapter = new DeviceVibrationEffectAdapter(mVibrationSettings);
-        PowerManager.WakeLock wakeLock = context.getSystemService(
-                PowerManager.class).newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*vibrator*");
-        mThread = new VibrationThread(wakeLock, mManagerHooks);
-        mThread.start();
-    }
-
-    @After
-    public void tearDown() {
-        if (mCustomTestLooperDispatcher != null) {
-            mCustomTestLooperDispatcher.cancel();
-        }
-    }
-
-    @Test
-    public void vibrate_noVibrator_ignoresVibration() {
-        mVibratorProviders.clear();
-        CombinedVibration effect = CombinedVibration.createParallel(
-                VibrationEffect.get(VibrationEffect.EFFECT_CLICK));
-        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
-        long vibrationId = conductor.getVibration().id;
-        waitForCompletion();
-
-        verify(mControllerCallbacks, never()).onComplete(anyInt(), eq(vibrationId));
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.IGNORED_UNSUPPORTED);
-    }
-
-    @Test
-    public void vibrate_missingVibrators_ignoresVibration() {
-        CombinedVibration effect = CombinedVibration.startSequential()
-                .addNext(2, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
-                .addNext(3, VibrationEffect.get(VibrationEffect.EFFECT_TICK))
-                .combine();
-        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
-        long vibrationId = conductor.getVibration().id;
-        waitForCompletion();
-
-        verify(mControllerCallbacks, never()).onComplete(anyInt(), eq(vibrationId));
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.IGNORED_UNSUPPORTED);
-    }
-
-    @Test
-    public void vibrate_singleVibratorOneShot_runsVibrationAndSetsAmplitude() throws Exception {
-        mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
-
-        VibrationEffect effect = VibrationEffect.createOneShot(10, 100);
-        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
-        long vibrationId = conductor.getVibration().id;
-        waitForCompletion();
-
-        verify(mManagerHooks).noteVibratorOn(eq(UID), eq(10L));
-        verify(mManagerHooks).noteVibratorOff(eq(UID));
-        verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
-        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
-
-        assertEquals(Arrays.asList(expectedOneShot(10)),
-                mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId));
-        assertEquals(expectedAmplitudes(100), mVibratorProviders.get(VIBRATOR_ID).getAmplitudes());
-    }
-
-    @Test
-    public void vibrate_oneShotWithoutAmplitudeControl_runsVibrationWithDefaultAmplitude()
-            throws Exception {
-        VibrationEffect effect = VibrationEffect.createOneShot(10, 100);
-        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
-        long vibrationId = conductor.getVibration().id;
-        waitForCompletion();
-
-        verify(mManagerHooks).noteVibratorOn(eq(UID), eq(10L));
-        verify(mManagerHooks).noteVibratorOff(eq(UID));
-        verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
-        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
-
-        assertEquals(Arrays.asList(expectedOneShot(10)),
-                mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId));
-        assertTrue(mVibratorProviders.get(VIBRATOR_ID).getAmplitudes().isEmpty());
-    }
-
-    @Test
-    public void vibrate_singleVibratorWaveform_runsVibrationAndChangesAmplitudes()
-            throws Exception {
-        mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
-
-        VibrationEffect effect = VibrationEffect.createWaveform(
-                new long[]{5, 5, 5}, new int[]{1, 2, 3}, -1);
-        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
-        long vibrationId = conductor.getVibration().id;
-        waitForCompletion();
-
-        verify(mManagerHooks).noteVibratorOn(eq(UID), eq(15L));
-        verify(mManagerHooks).noteVibratorOff(eq(UID));
-        verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
-        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
-
-        assertEquals(Arrays.asList(expectedOneShot(15)),
-                mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId));
-        assertEquals(expectedAmplitudes(1, 2, 3),
-                mVibratorProviders.get(VIBRATOR_ID).getAmplitudes());
-    }
-
-    @Test
-    public void vibrate_singleVibratorRepeatingWaveform_runsVibrationUntilThreadCancelled()
-            throws Exception {
-        FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
-        fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
-
-        int[] amplitudes = new int[]{1, 2, 3};
-        VibrationEffect effect = VibrationEffect.createWaveform(new long[]{5, 5, 5}, amplitudes, 0);
-        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
-        long vibrationId = conductor.getVibration().id;
-
-        assertTrue(
-                waitUntil(() -> fakeVibrator.getAmplitudes().size() > 2 * amplitudes.length,
-                        TEST_TIMEOUT_MILLIS));
-        // Vibration still running after 2 cycles.
-        assertTrue(mThread.isRunningVibrationId(vibrationId));
-        assertTrue(mControllers.get(VIBRATOR_ID).isVibrating());
-
-        Vibration.EndInfo cancelVibrationInfo = new Vibration.EndInfo(
-                Vibration.Status.CANCELLED_SUPERSEDED, new Vibration.CallerInfo(
-                VibrationAttributes.createForUsage(VibrationAttributes.USAGE_ALARM), /* uid= */
-                1, /* displayId= */ -1, /* opPkg= */ null, /* reason= */ null));
-        conductor.notifyCancelled(
-                cancelVibrationInfo,
-                /* immediate= */ false);
-        waitForCompletion();
-        assertFalse(mThread.isRunningVibrationId(vibrationId));
-
-        verify(mManagerHooks).noteVibratorOn(eq(UID), anyLong());
-        verify(mManagerHooks).noteVibratorOff(eq(UID));
-        verifyCallbacksTriggered(vibrationId, cancelVibrationInfo);
-        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
-
-        List<Float> playedAmplitudes = fakeVibrator.getAmplitudes();
-        assertFalse(fakeVibrator.getEffectSegments(vibrationId).isEmpty());
-        assertFalse(playedAmplitudes.isEmpty());
-
-        for (int i = 0; i < playedAmplitudes.size(); i++) {
-            assertEquals(amplitudes[i % amplitudes.length] / 255f, playedAmplitudes.get(i), 1e-5);
-        }
-    }
-
-    @Test
-    public void vibrate_singleVibratorRepeatingShortAlwaysOnWaveform_turnsVibratorOnForLonger()
-            throws Exception {
-        FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
-        fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
-
-        int[] amplitudes = new int[]{1, 2, 3};
-        VibrationEffect effect = VibrationEffect.createWaveform(
-                new long[]{1, 10, 100}, amplitudes, 0);
-        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
-        long vibrationId = conductor.getVibration().id;
-
-        assertTrue(waitUntil(() -> !fakeVibrator.getAmplitudes().isEmpty(), TEST_TIMEOUT_MILLIS));
-        conductor.notifyCancelled(
-                new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_USER),
-                /* immediate= */ false);
-        waitForCompletion();
-
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_USER);
-        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
-        assertEquals(Arrays.asList(expectedOneShot(5000)),
-                fakeVibrator.getEffectSegments(vibrationId));
-    }
-
-    @Test
-    public void vibrate_singleVibratorRepeatingPwle_generatesLargestPwles() throws Exception {
-        FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
-        fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
-        fakeVibrator.setMinFrequency(100);
-        fakeVibrator.setResonantFrequency(150);
-        fakeVibrator.setFrequencyResolution(50);
-        fakeVibrator.setMaxAmplitudes(1, 1, 1);
-        fakeVibrator.setPwleSizeMax(10);
-
-        VibrationEffect effect = VibrationEffect.startWaveform(targetAmplitude(1))
-                // Very long segment so thread will be cancelled after first PWLE is triggered.
-                .addTransition(Duration.ofMillis(100), targetFrequency(100))
-                .build();
-        VibrationEffect repeatingEffect = VibrationEffect.startComposition()
-                .repeatEffectIndefinitely(effect)
-                .compose();
-        VibrationStepConductor conductor = startThreadAndDispatcher(repeatingEffect);
-        long vibrationId = conductor.getVibration().id;
-
-        assertTrue(waitUntil(() -> !fakeVibrator.getEffectSegments(vibrationId).isEmpty(),
-                TEST_TIMEOUT_MILLIS));
-        conductor.notifyCancelled(
-                new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_USER),
-                /* immediate= */ false);
-        waitForCompletion();
-
-        // PWLE size max was used to generate a single vibrate call with 10 segments.
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_USER);
-        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
-        assertEquals(10, fakeVibrator.getEffectSegments(vibrationId).size());
-    }
-
-    @Test
-    public void vibrate_singleVibratorRepeatingPrimitives_generatesLargestComposition()
-            throws Exception {
-        FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
-        fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
-        fakeVibrator.setSupportedPrimitives(VibrationEffect.Composition.PRIMITIVE_CLICK);
-        fakeVibrator.setCompositionSizeMax(10);
-
-        VibrationEffect effect = VibrationEffect.startComposition()
-                // Very long delay so thread will be cancelled after first PWLE is triggered.
-                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 100)
-                .compose();
-        VibrationEffect repeatingEffect = VibrationEffect.startComposition()
-                .repeatEffectIndefinitely(effect)
-                .compose();
-        VibrationStepConductor conductor = startThreadAndDispatcher(repeatingEffect);
-        long vibrationId = conductor.getVibration().id;
-
-        assertTrue(waitUntil(() -> !fakeVibrator.getEffectSegments(vibrationId).isEmpty(),
-                TEST_TIMEOUT_MILLIS));
-        conductor.notifyCancelled(
-                new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_SCREEN_OFF),
-                /* immediate= */ false);
-        waitForCompletion();
-
-        // Composition size max was used to generate a single vibrate call with 10 primitives.
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_SCREEN_OFF);
-        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
-        assertEquals(10, fakeVibrator.getEffectSegments(vibrationId).size());
-    }
-
-    @Test
-    public void vibrate_singleVibratorRepeatingLongAlwaysOnWaveform_turnsVibratorOnForACycle()
-            throws Exception {
-        FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
-        fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
-
-        int[] amplitudes = new int[]{1, 2, 3};
-        VibrationEffect effect = VibrationEffect.createWaveform(
-                new long[]{5000, 500, 50}, amplitudes, 0);
-        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
-        long vibrationId = conductor.getVibration().id;
-
-        assertTrue(waitUntil(() -> !fakeVibrator.getAmplitudes().isEmpty(), TEST_TIMEOUT_MILLIS));
-        conductor.notifyCancelled(
-                new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_USER),
-                /* immediate= */ false);
-        waitForCompletion();
-
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_USER);
-        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
-        assertEquals(Arrays.asList(expectedOneShot(5550)),
-                fakeVibrator.getEffectSegments(vibrationId));
-    }
-
-    @LargeTest
-    @Test
-    public void vibrate_singleVibratorRepeatingAlwaysOnWaveform_turnsVibratorBackOn()
-            throws Exception {
-        FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
-        fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
-
-        int[] amplitudes = new int[]{1, 2};
-        VibrationEffect effect = VibrationEffect.createWaveform(
-                new long[]{4900, 50}, amplitudes, 0);
-        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
-        long vibrationId = conductor.getVibration().id;
-
-        assertTrue(waitUntil(() -> fakeVibrator.getEffectSegments(vibrationId).size() > 1,
-                5000 + TEST_TIMEOUT_MILLIS));
-        conductor.notifyCancelled(
-                new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_USER),
-                /* immediate= */ false);
-        waitForCompletion();
-
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_USER);
-        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
-        // First time turn vibrator ON for minimum of 5s.
-        assertEquals(5000L, fakeVibrator.getEffectSegments(vibrationId).get(0).getDuration());
-        // Vibrator turns off in the middle of the second execution of first step, turn it back ON
-        // for another 5s + remaining of 850ms.
-        assertEquals(4900 + 50 + 4900,
-                fakeVibrator.getEffectSegments(vibrationId).get(1).getDuration(), /* delta= */ 20);
-        // Set amplitudes for a cycle {1, 2}, start second loop then turn it back on to same value.
-        assertEquals(expectedAmplitudes(1, 2, 1, 1),
-                mVibratorProviders.get(VIBRATOR_ID).getAmplitudes().subList(0, 4));
-    }
-
-    @Test
-    public void vibrate_singleVibratorPredefinedCancel_cancelsVibrationImmediately()
-            throws Exception {
-        mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
-        mVibratorProviders.get(VIBRATOR_ID).setSupportedPrimitives(
-                VibrationEffect.Composition.PRIMITIVE_CLICK);
-
-        VibrationEffect effect = VibrationEffect.startComposition()
-                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 100)
-                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 100)
-                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 100)
-                .compose();
-        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
-        long vibrationId = conductor.getVibration().id;
-
-        assertTrue(waitUntil(() -> mControllers.get(VIBRATOR_ID).isVibrating(),
-                TEST_TIMEOUT_MILLIS));
-        assertTrue(mThread.isRunningVibrationId(vibrationId));
-
-        // Run cancel in a separate thread so if VibrationThread.cancel blocks then this test should
-        // fail at waitForCompletion(vibrationThread) if the vibration not cancelled immediately.
-        Thread cancellingThread =
-                new Thread(() -> conductor.notifyCancelled(
-                        new Vibration.EndInfo(
-                                Vibration.Status.CANCELLED_BY_SETTINGS_UPDATE),
-                        /* immediate= */ false));
-        cancellingThread.start();
-
-        waitForCompletion(/* timeout= */ 50);
-        cancellingThread.join();
-
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_SETTINGS_UPDATE);
-        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
-    }
-
-    @Test
-    public void vibrate_singleVibratorWaveformCancel_cancelsVibrationImmediately()
-            throws Exception {
-        mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
-
-        VibrationEffect effect = VibrationEffect.createWaveform(new long[]{100}, new int[]{100}, 0);
-        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
-        long vibrationId = conductor.getVibration().id;
-
-        assertTrue(waitUntil(() -> mControllers.get(VIBRATOR_ID).isVibrating(),
-                TEST_TIMEOUT_MILLIS));
-        assertTrue(mThread.isRunningVibrationId(vibrationId));
-
-        // Run cancel in a separate thread so if VibrationThread.cancel blocks then this test should
-        // fail at waitForCompletion(vibrationThread) if the vibration not cancelled immediately.
-        Thread cancellingThread =
-                new Thread(() -> conductor.notifyCancelled(
-                        new Vibration.EndInfo(
-                                Vibration.Status.CANCELLED_BY_SCREEN_OFF),
-                        /* immediate= */ false));
-        cancellingThread.start();
-
-        waitForCompletion(/* timeout= */ 50);
-        cancellingThread.join();
-
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_SCREEN_OFF);
-        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
-    }
-
-    @Test
-    public void vibrate_singleVibratorPrebaked_runsVibration() throws Exception {
-        mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_THUD);
-
-        VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_THUD);
-        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
-        long vibrationId = conductor.getVibration().id;
-        waitForCompletion();
-
-        verify(mManagerHooks).noteVibratorOn(eq(UID), eq(20L));
-        verify(mManagerHooks).noteVibratorOff(eq(UID));
-        verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
-        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
-
-        assertEquals(Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_THUD)),
-                mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId));
-    }
-
-    @Test
-    public void vibrate_singleVibratorPrebakedAndUnsupportedEffectWithFallback_runsFallback()
-            throws Exception {
-        mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
-
-        VibrationEffect fallback = VibrationEffect.createOneShot(10, 100);
-        HalVibration vibration = createVibration(CombinedVibration.createParallel(
-                VibrationEffect.get(VibrationEffect.EFFECT_CLICK)));
-        vibration.addFallback(VibrationEffect.EFFECT_CLICK, fallback);
-        VibrationStepConductor conductor = startThreadAndDispatcher(vibration);
-        long vibrationId = conductor.getVibration().id;
-        waitForCompletion();
-
-        verify(mManagerHooks).noteVibratorOn(eq(UID), eq(10L));
-        verify(mManagerHooks).noteVibratorOff(eq(UID));
-        verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
-        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
-
-        assertEquals(Arrays.asList(expectedOneShot(10)),
-                mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId));
-        assertEquals(expectedAmplitudes(100), mVibratorProviders.get(VIBRATOR_ID).getAmplitudes());
-    }
-
-    @Test
-    public void vibrate_singleVibratorPrebakedAndUnsupportedEffect_ignoresVibration()
-            throws Exception {
-        VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
-        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
-        long vibrationId = conductor.getVibration().id;
-        waitForCompletion();
-
-        verify(mManagerHooks).noteVibratorOn(eq(UID), eq(0L));
-        verify(mManagerHooks, never()).noteVibratorOff(eq(UID));
-        verify(mControllerCallbacks, never()).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.IGNORED_UNSUPPORTED);
-        assertTrue(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId).isEmpty());
-    }
-
-    @Test
-    public void vibrate_singleVibratorComposed_runsVibration() throws Exception {
-        FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
-        fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
-        fakeVibrator.setSupportedPrimitives(VibrationEffect.Composition.PRIMITIVE_CLICK,
-                VibrationEffect.Composition.PRIMITIVE_TICK);
-
-        VibrationEffect effect = VibrationEffect.startComposition()
-                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f)
-                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f)
-                .compose();
-        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
-        long vibrationId = conductor.getVibration().id;
-        waitForCompletion();
-
-        verify(mManagerHooks).noteVibratorOn(eq(UID), eq(40L));
-        verify(mManagerHooks).noteVibratorOff(eq(UID));
-        verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
-        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
-        assertEquals(Arrays.asList(
-                expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0),
-                expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f, 0)),
-                fakeVibrator.getEffectSegments(vibrationId));
-    }
-
-    @Test
-    public void vibrate_singleVibratorComposedAndNoCapability_ignoresVibration() {
-        VibrationEffect effect = VibrationEffect.startComposition()
-                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f)
-                .compose();
-        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
-        long vibrationId = conductor.getVibration().id;
-        waitForCompletion();
-
-        verify(mManagerHooks).noteVibratorOn(eq(UID), eq(0L));
-        verify(mManagerHooks, never()).noteVibratorOff(eq(UID));
-        verify(mControllerCallbacks, never()).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.IGNORED_UNSUPPORTED);
-        assertTrue(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId).isEmpty());
-    }
-
-    @Test
-    public void vibrate_singleVibratorLargeComposition_splitsVibratorComposeCalls() {
-        FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
-        fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
-        fakeVibrator.setSupportedPrimitives(
-                VibrationEffect.Composition.PRIMITIVE_CLICK,
-                VibrationEffect.Composition.PRIMITIVE_TICK,
-                VibrationEffect.Composition.PRIMITIVE_SPIN);
-        fakeVibrator.setCompositionSizeMax(2);
-
-        VibrationEffect effect = VibrationEffect.startComposition()
-                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f)
-                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f)
-                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_SPIN, 0.8f)
-                .compose();
-        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
-        long vibrationId = conductor.getVibration().id;
-        waitForCompletion();
-
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
-        // Vibrator compose called twice.
-        verify(mControllerCallbacks, times(2)).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
-        assertEquals(3, fakeVibrator.getEffectSegments(vibrationId).size());
-    }
-
-    @Test
-    public void vibrate_singleVibratorComposedEffects_runsDifferentVibrations() throws Exception {
-        FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
-        fakeVibrator.setSupportedEffects(VibrationEffect.EFFECT_CLICK);
-        fakeVibrator.setSupportedPrimitives(
-                VibrationEffect.Composition.PRIMITIVE_CLICK,
-                VibrationEffect.Composition.PRIMITIVE_TICK);
-        fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS,
-                IVibrator.CAP_COMPOSE_PWLE_EFFECTS, IVibrator.CAP_AMPLITUDE_CONTROL);
-        fakeVibrator.setMinFrequency(100);
-        fakeVibrator.setResonantFrequency(150);
-        fakeVibrator.setFrequencyResolution(50);
-        fakeVibrator.setMaxAmplitudes(
-                0.5f /* 100Hz*/, 1 /* 150Hz */, 0.6f /* 200Hz */);
-
-        VibrationEffect effect = VibrationEffect.startComposition()
-                .addEffect(VibrationEffect.createOneShot(10, 100))
-                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f)
-                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f)
-                .addEffect(VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
-                .addEffect(VibrationEffect.startWaveform()
-                        .addTransition(Duration.ofMillis(10),
-                                targetAmplitude(1), targetFrequency(100))
-                        .addTransition(Duration.ofMillis(20), targetFrequency(120))
-                        .build())
-                .addEffect(VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
-                .compose();
-        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
-        long vibrationId = conductor.getVibration().id;
-        waitForCompletion();
-
-        // Use first duration the vibrator is turned on since we cannot estimate the clicks.
-        verify(mManagerHooks).noteVibratorOn(eq(UID), eq(10L));
-        verify(mManagerHooks).noteVibratorOff(eq(UID));
-        verify(mControllerCallbacks, times(5)).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
-        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
-        assertEquals(Arrays.asList(
-                expectedOneShot(10),
-                expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0),
-                expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f, 0),
-                expectedPrebaked(VibrationEffect.EFFECT_CLICK),
-                expectedRamp(/* startAmplitude= */ 0, /* endAmplitude= */ 0.5f,
-                        /* startFrequencyHz= */ 150, /* endFrequencyHz= */ 100, /* duration= */ 10),
-                expectedRamp(/* startAmplitude= */ 0.5f, /* endAmplitude= */ 0.7f,
-                        /* startFrequencyHz= */ 100, /* endFrequencyHz= */ 120, /* duration= */ 20),
-                expectedPrebaked(VibrationEffect.EFFECT_CLICK)),
-                mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId));
-        assertEquals(expectedAmplitudes(100), mVibratorProviders.get(VIBRATOR_ID).getAmplitudes());
-    }
-
-    @Test
-    public void vibrate_singleVibratorComposedWithFallback_replacedInTheMiddleOfComposition() {
-        FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
-        fakeVibrator.setSupportedEffects(VibrationEffect.EFFECT_CLICK);
-        fakeVibrator.setSupportedPrimitives(
-                VibrationEffect.Composition.PRIMITIVE_CLICK,
-                VibrationEffect.Composition.PRIMITIVE_TICK);
-        fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
-
-        VibrationEffect fallback = VibrationEffect.createOneShot(10, 100);
-        VibrationEffect effect = VibrationEffect.startComposition()
-                .addEffect(VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
-                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f)
-                .addEffect(VibrationEffect.get(VibrationEffect.EFFECT_TICK))
-                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f)
-                .compose();
-        HalVibration vib = createVibration(CombinedVibration.createParallel(effect));
-        vib.addFallback(VibrationEffect.EFFECT_TICK, fallback);
-        VibrationStepConductor conductor = startThreadAndDispatcher(vib);
-        long vibrationId = conductor.getVibration().id;
-        waitForCompletion();
-
-        // Use first duration the vibrator is turned on since we cannot estimate the clicks.
-        verify(mManagerHooks).noteVibratorOn(eq(UID), anyLong());
-        verify(mManagerHooks).noteVibratorOff(eq(UID));
-        verify(mControllerCallbacks, times(4)).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
-        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
-
-        List<VibrationEffectSegment> segments =
-                mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId);
-        assertTrue("Wrong segments: " + segments, segments.size() >= 4);
-        assertTrue(segments.get(0) instanceof PrebakedSegment);
-        assertTrue(segments.get(1) instanceof PrimitiveSegment);
-        for (int i = 2; i < segments.size() - 1; i++) {
-            // One or more step segments as fallback for the EFFECT_TICK.
-            assertTrue(segments.get(i) instanceof StepSegment);
-        }
-        assertTrue(segments.get(segments.size() - 1) instanceof PrimitiveSegment);
-    }
-
-    @Test
-    public void vibrate_singleVibratorPwle_runsComposePwle() throws Exception {
-        FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
-        fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
-        fakeVibrator.setSupportedBraking(Braking.CLAB);
-        fakeVibrator.setMinFrequency(100);
-        fakeVibrator.setResonantFrequency(150);
-        fakeVibrator.setFrequencyResolution(50);
-        fakeVibrator.setMaxAmplitudes(
-                0.5f /* 100Hz*/, 1 /* 150Hz */, 0.6f /* 200Hz */);
-
-        VibrationEffect effect = VibrationEffect.startWaveform(targetAmplitude(1))
-                .addSustain(Duration.ofMillis(10))
-                .addTransition(Duration.ofMillis(20), targetAmplitude(0))
-                .addTransition(Duration.ZERO, targetAmplitude(0.8f), targetFrequency(100))
-                .addSustain(Duration.ofMillis(30))
-                .addTransition(Duration.ofMillis(40), targetAmplitude(0.6f), targetFrequency(200))
-                .build();
-        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
-        long vibrationId = conductor.getVibration().id;
-        waitForCompletion();
-
-        verify(mManagerHooks).noteVibratorOn(eq(UID), eq(100L));
-        verify(mManagerHooks).noteVibratorOff(eq(UID));
-        verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
-        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
-        assertEquals(Arrays.asList(
-                expectedRamp(/* amplitude= */ 1, /* frequencyHz= */ 150, /* duration= */ 10),
-                expectedRamp(/* startAmplitude= */ 1, /* endAmplitude= */ 0,
-                        /* startFrequencyHz= */ 150, /* endFrequencyHz= */ 150, /* duration= */ 20),
-                expectedRamp(/* amplitude= */ 0.5f, /* frequencyHz= */ 100, /* duration= */ 30),
-                expectedRamp(/* startAmplitude= */ 0.5f, /* endAmplitude= */ 0.6f,
-                        /* startFrequencyHz= */ 100, /* endFrequencyHz= */ 200,
-                        /* duration= */ 40)),
-                fakeVibrator.getEffectSegments(vibrationId));
-        assertEquals(Arrays.asList(Braking.CLAB), fakeVibrator.getBraking(vibrationId));
-    }
-
-    @Test
-    public void vibrate_singleVibratorLargePwle_splitsComposeCallWhenAmplitudeIsLowest() {
-        FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
-        fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
-        fakeVibrator.setMinFrequency(100);
-        fakeVibrator.setResonantFrequency(150);
-        fakeVibrator.setFrequencyResolution(50);
-        fakeVibrator.setMaxAmplitudes(1, 1, 1);
-        fakeVibrator.setPwleSizeMax(3);
-
-        VibrationEffect effect = VibrationEffect.startWaveform(targetAmplitude(1))
-                .addSustain(Duration.ofMillis(10))
-                .addTransition(Duration.ofMillis(20), targetAmplitude(0))
-                // Waveform will be split here, after vibration goes to zero amplitude
-                .addTransition(Duration.ZERO, targetAmplitude(0.8f), targetFrequency(100))
-                .addSustain(Duration.ofMillis(30))
-                .addTransition(Duration.ofMillis(40), targetAmplitude(0.6f), targetFrequency(200))
-                // Waveform will be split here at lowest amplitude.
-                .addTransition(Duration.ofMillis(40), targetAmplitude(0.7f), targetFrequency(200))
-                .addTransition(Duration.ofMillis(40), targetAmplitude(0.6f), targetFrequency(200))
-                .build();
-        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
-        long vibrationId = conductor.getVibration().id;
-        waitForCompletion();
-
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
-
-        // Vibrator compose called 3 times with 2 segments instead of 2 times with 3 segments.
-        // Using best split points instead of max-packing PWLEs.
-        verify(mControllerCallbacks, times(3)).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
-        assertEquals(6, fakeVibrator.getEffectSegments(vibrationId).size());
-    }
-
-    @Test
-    public void vibrate_singleVibratorCancelled_vibratorStopped() throws Exception {
-        FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
-        fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
-
-        VibrationEffect effect = VibrationEffect.createWaveform(new long[]{5}, new int[]{100}, 0);
-        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
-        long vibrationId = conductor.getVibration().id;
-
-        assertTrue(waitUntil(() -> fakeVibrator.getAmplitudes().size() > 2, TEST_TIMEOUT_MILLIS));
-        // Vibration still running after 2 cycles.
-        assertTrue(mThread.isRunningVibrationId(vibrationId));
-        assertTrue(mControllers.get(VIBRATOR_ID).isVibrating());
-
-        conductor.binderDied();
-        waitForCompletion();
-        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
-
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BINDER_DIED);
-    }
-
-    @Test
-    public void vibrate_singleVibrator_skipsSyncedCallbacks() {
-        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
-
-        VibrationStepConductor conductor = startThreadAndDispatcher(
-                VibrationEffect.createOneShot(10, 100));
-        long vibrationId = conductor.getVibration().id;
-        waitForCompletion();
-
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
-        verify(mManagerHooks, never()).prepareSyncedVibration(anyLong(), any());
-        verify(mManagerHooks, never()).triggerSyncedVibration(anyLong());
-        verify(mManagerHooks, never()).cancelSyncedVibration();
-    }
-
-    @Test
-    public void vibrate_multipleExistingAndMissingVibrators_vibratesOnlyExistingOnes()
-            throws Exception {
-        mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_TICK);
-
-        CombinedVibration effect = CombinedVibration.startParallel()
-                .addVibrator(VIBRATOR_ID, VibrationEffect.get(VibrationEffect.EFFECT_TICK))
-                .addVibrator(2, VibrationEffect.get(VibrationEffect.EFFECT_TICK))
-                .combine();
-        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
-        long vibrationId = conductor.getVibration().id;
-        waitForCompletion();
-
-        verify(mManagerHooks).noteVibratorOn(eq(UID), eq(20L));
-        verify(mManagerHooks).noteVibratorOff(eq(UID));
-        verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
-        verify(mControllerCallbacks, never()).onComplete(eq(2), eq(vibrationId));
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
-        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
-
-        assertEquals(Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_TICK)),
-                mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId));
-    }
-
-    @Test
-    public void vibrate_multipleMono_runsSameEffectInAllVibrators() throws Exception {
-        mockVibrators(1, 2, 3);
-        mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
-        mVibratorProviders.get(2).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
-        mVibratorProviders.get(3).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
-
-        CombinedVibration effect = CombinedVibration.createParallel(
-                VibrationEffect.get(VibrationEffect.EFFECT_CLICK));
-        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
-        long vibrationId = conductor.getVibration().id;
-        waitForCompletion();
-
-        verify(mManagerHooks).noteVibratorOn(eq(UID), eq(20L));
-        verify(mManagerHooks).noteVibratorOff(eq(UID));
-        verify(mControllerCallbacks).onComplete(eq(1), eq(vibrationId));
-        verify(mControllerCallbacks).onComplete(eq(2), eq(vibrationId));
-        verify(mControllerCallbacks).onComplete(eq(3), eq(vibrationId));
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
-        assertFalse(mControllers.get(1).isVibrating());
-        assertFalse(mControllers.get(2).isVibrating());
-        assertFalse(mControllers.get(3).isVibrating());
-
-        VibrationEffectSegment expected = expectedPrebaked(VibrationEffect.EFFECT_CLICK);
-        assertEquals(Arrays.asList(expected),
-                mVibratorProviders.get(1).getEffectSegments(vibrationId));
-        assertEquals(Arrays.asList(expected),
-                mVibratorProviders.get(2).getEffectSegments(vibrationId));
-        assertEquals(Arrays.asList(expected),
-                mVibratorProviders.get(3).getEffectSegments(vibrationId));
-    }
-
-    @Test
-    public void vibrate_multipleStereo_runsVibrationOnRightVibrators() throws Exception {
-        mockVibrators(1, 2, 3, 4);
-        mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
-        mVibratorProviders.get(2).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
-        mVibratorProviders.get(3).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
-        mVibratorProviders.get(4).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
-        mVibratorProviders.get(4).setSupportedPrimitives(
-                VibrationEffect.Composition.PRIMITIVE_CLICK);
-
-        VibrationEffect composed = VibrationEffect.startComposition()
-                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
-                .compose();
-        CombinedVibration effect = CombinedVibration.startParallel()
-                .addVibrator(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
-                .addVibrator(2, VibrationEffect.createOneShot(10, 100))
-                .addVibrator(3, VibrationEffect.createWaveform(
-                        new long[]{10, 10}, new int[]{1, 2}, -1))
-                .addVibrator(4, composed)
-                .combine();
-        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
-        long vibrationId = conductor.getVibration().id;
-        waitForCompletion();
-
-        verify(mManagerHooks).noteVibratorOn(eq(UID), eq(20L));
-        verify(mManagerHooks).noteVibratorOff(eq(UID));
-        verify(mControllerCallbacks).onComplete(eq(1), eq(vibrationId));
-        verify(mControllerCallbacks).onComplete(eq(2), eq(vibrationId));
-        verify(mControllerCallbacks).onComplete(eq(3), eq(vibrationId));
-        verify(mControllerCallbacks).onComplete(eq(4), eq(vibrationId));
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
-        assertFalse(mControllers.get(1).isVibrating());
-        assertFalse(mControllers.get(2).isVibrating());
-        assertFalse(mControllers.get(3).isVibrating());
-        assertFalse(mControllers.get(4).isVibrating());
-
-        assertEquals(Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_CLICK)),
-                mVibratorProviders.get(1).getEffectSegments(vibrationId));
-        assertEquals(Arrays.asList(expectedOneShot(10)),
-                mVibratorProviders.get(2).getEffectSegments(vibrationId));
-        assertEquals(expectedAmplitudes(100), mVibratorProviders.get(2).getAmplitudes());
-        assertEquals(Arrays.asList(expectedOneShot(20)),
-                mVibratorProviders.get(3).getEffectSegments(vibrationId));
-        assertEquals(expectedAmplitudes(1, 2), mVibratorProviders.get(3).getAmplitudes());
-        assertEquals(Arrays.asList(
-                expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0)),
-                mVibratorProviders.get(4).getEffectSegments(vibrationId));
-    }
-
-    @Test
-    public void vibrate_multipleSequential_runsVibrationInOrderWithDelays() throws Exception {
-        mockVibrators(1, 2, 3);
-        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
-        mVibratorProviders.get(2).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
-        mVibratorProviders.get(2).setSupportedPrimitives(
-                VibrationEffect.Composition.PRIMITIVE_CLICK);
-        mVibratorProviders.get(3).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
-
-        VibrationEffect composed = VibrationEffect.startComposition()
-                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
-                .compose();
-        CombinedVibration effect = CombinedVibration.startSequential()
-                .addNext(3, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), /* delay= */ 50)
-                .addNext(1, VibrationEffect.createOneShot(10, 100), /* delay= */ 50)
-                .addNext(2, composed, /* delay= */ 50)
-                .combine();
-        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
-        long vibrationId = conductor.getVibration().id;
-
-        waitForCompletion();
-        InOrder controllerVerifier = inOrder(mControllerCallbacks);
-        controllerVerifier.verify(mControllerCallbacks).onComplete(eq(3), eq(vibrationId));
-        controllerVerifier.verify(mControllerCallbacks).onComplete(eq(1), eq(vibrationId));
-        controllerVerifier.verify(mControllerCallbacks).onComplete(eq(2), eq(vibrationId));
-
-        InOrder batteryVerifier = inOrder(mManagerHooks);
-        batteryVerifier.verify(mManagerHooks).noteVibratorOn(eq(UID), eq(20L));
-        batteryVerifier.verify(mManagerHooks).noteVibratorOff(eq(UID));
-        batteryVerifier.verify(mManagerHooks).noteVibratorOn(eq(UID), eq(10L));
-        batteryVerifier.verify(mManagerHooks).noteVibratorOff(eq(UID));
-        batteryVerifier.verify(mManagerHooks).noteVibratorOn(eq(UID), eq(20L));
-        batteryVerifier.verify(mManagerHooks).noteVibratorOff(eq(UID));
-
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
-        assertFalse(mControllers.get(1).isVibrating());
-        assertFalse(mControllers.get(2).isVibrating());
-        assertFalse(mControllers.get(3).isVibrating());
-
-        assertEquals(Arrays.asList(expectedOneShot(10)),
-                mVibratorProviders.get(1).getEffectSegments(vibrationId));
-        assertEquals(expectedAmplitudes(100), mVibratorProviders.get(1).getAmplitudes());
-        assertEquals(Arrays.asList(
-                expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0)),
-                mVibratorProviders.get(2).getEffectSegments(vibrationId));
-        assertEquals(Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_CLICK)),
-                mVibratorProviders.get(3).getEffectSegments(vibrationId));
-    }
-
-    @Test
-    public void vibrate_multipleSyncedCallbackTriggered_finishSteps() throws Exception {
-        int[] vibratorIds = new int[]{1, 2};
-        mockVibrators(vibratorIds);
-        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
-        mVibratorProviders.get(1).setSupportedPrimitives(
-                VibrationEffect.Composition.PRIMITIVE_CLICK);
-        mVibratorProviders.get(2).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
-        mVibratorProviders.get(2).setSupportedPrimitives(
-                VibrationEffect.Composition.PRIMITIVE_CLICK);
-        when(mManagerHooks.prepareSyncedVibration(anyLong(), eq(vibratorIds))).thenReturn(true);
-
-        VibrationEffect composed = VibrationEffect.startComposition()
-                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 100)
-                .compose();
-        CombinedVibration effect = CombinedVibration.createParallel(composed);
-        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
-        long vibrationId = conductor.getVibration().id;
-
-        when(mManagerHooks.triggerSyncedVibration(eq(vibrationId))).thenReturn(true);
-
-        assertTrue(waitUntil(
-                () -> !mVibratorProviders.get(1).getEffectSegments(vibrationId).isEmpty()
-                        && !mVibratorProviders.get(2).getEffectSegments(vibrationId).isEmpty(),
-                TEST_TIMEOUT_MILLIS));
-        conductor.notifySyncedVibrationComplete();
-        waitForCompletion();
-
-        long expectedCap = IVibratorManager.CAP_SYNC | IVibratorManager.CAP_PREPARE_COMPOSE;
-        verify(mManagerHooks).prepareSyncedVibration(eq(expectedCap), eq(vibratorIds));
-        verify(mManagerHooks).triggerSyncedVibration(eq(vibrationId));
-        verify(mManagerHooks, never()).cancelSyncedVibration();
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
-
-        VibrationEffectSegment expected = expectedPrimitive(
-                VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 100);
-        assertEquals(Arrays.asList(expected),
-                mVibratorProviders.get(1).getEffectSegments(vibrationId));
-        assertEquals(Arrays.asList(expected),
-                mVibratorProviders.get(2).getEffectSegments(vibrationId));
-    }
-
-    @Test
-    public void vibrate_multipleSynced_callsPrepareAndTriggerCallbacks() {
-        int[] vibratorIds = new int[]{1, 2, 3, 4};
-        mockVibrators(vibratorIds);
-        mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
-        mVibratorProviders.get(4).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
-        mVibratorProviders.get(4).setSupportedPrimitives(
-                VibrationEffect.Composition.PRIMITIVE_CLICK);
-        when(mManagerHooks.prepareSyncedVibration(anyLong(), any())).thenReturn(true);
-        when(mManagerHooks.triggerSyncedVibration(anyLong())).thenReturn(true);
-
-        VibrationEffect composed = VibrationEffect.startComposition()
-                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
-                .compose();
-        CombinedVibration effect = CombinedVibration.startParallel()
-                .addVibrator(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
-                .addVibrator(2, VibrationEffect.createOneShot(10, 100))
-                .addVibrator(3, VibrationEffect.createWaveform(new long[]{10}, new int[]{100}, -1))
-                .addVibrator(4, composed)
-                .combine();
-        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
-        long vibrationId = conductor.getVibration().id;
-        waitForCompletion();
-
-        long expectedCap = IVibratorManager.CAP_SYNC
-                | IVibratorManager.CAP_PREPARE_ON
-                | IVibratorManager.CAP_PREPARE_PERFORM
-                | IVibratorManager.CAP_PREPARE_COMPOSE
-                | IVibratorManager.CAP_MIXED_TRIGGER_ON
-                | IVibratorManager.CAP_MIXED_TRIGGER_PERFORM
-                | IVibratorManager.CAP_MIXED_TRIGGER_COMPOSE;
-        verify(mManagerHooks).prepareSyncedVibration(eq(expectedCap), eq(vibratorIds));
-        verify(mManagerHooks).triggerSyncedVibration(eq(vibrationId));
-        verify(mManagerHooks, never()).cancelSyncedVibration();
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
-    }
-
-    @Test
-    public void vibrate_multipleSyncedPrepareFailed_skipTriggerStepAndVibrates() {
-        int[] vibratorIds = new int[]{1, 2};
-        mockVibrators(vibratorIds);
-        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
-        mVibratorProviders.get(2).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
-        when(mManagerHooks.prepareSyncedVibration(anyLong(), any())).thenReturn(false);
-
-        CombinedVibration effect = CombinedVibration.startParallel()
-                .addVibrator(1, VibrationEffect.createOneShot(10, 100))
-                .addVibrator(2, VibrationEffect.createWaveform(new long[]{5}, new int[]{200}, -1))
-                .combine();
-        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
-        long vibrationId = conductor.getVibration().id;
-        waitForCompletion();
-
-        long expectedCap = IVibratorManager.CAP_SYNC | IVibratorManager.CAP_PREPARE_ON;
-        verify(mManagerHooks).prepareSyncedVibration(eq(expectedCap), eq(vibratorIds));
-        verify(mManagerHooks, never()).triggerSyncedVibration(eq(vibrationId));
-        verify(mManagerHooks, never()).cancelSyncedVibration();
-
-        assertEquals(Arrays.asList(expectedOneShot(10)),
-                mVibratorProviders.get(1).getEffectSegments(vibrationId));
-        assertEquals(expectedAmplitudes(100), mVibratorProviders.get(1).getAmplitudes());
-        assertEquals(Arrays.asList(expectedOneShot(5)),
-                mVibratorProviders.get(2).getEffectSegments(vibrationId));
-        assertEquals(expectedAmplitudes(200), mVibratorProviders.get(2).getAmplitudes());
-    }
-
-    @Test
-    public void vibrate_multipleSyncedTriggerFailed_cancelPreparedVibrationAndSkipSetAmplitude() {
-        int[] vibratorIds = new int[]{1, 2};
-        mockVibrators(vibratorIds);
-        mVibratorProviders.get(2).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
-        when(mManagerHooks.prepareSyncedVibration(anyLong(), any())).thenReturn(true);
-        when(mManagerHooks.triggerSyncedVibration(anyLong())).thenReturn(false);
-
-        CombinedVibration effect = CombinedVibration.startParallel()
-                .addVibrator(1, VibrationEffect.createOneShot(10, 100))
-                .addVibrator(2, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
-                .combine();
-        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
-        long vibrationId = conductor.getVibration().id;
-        waitForCompletion();
-
-        long expectedCap = IVibratorManager.CAP_SYNC
-                | IVibratorManager.CAP_PREPARE_ON
-                | IVibratorManager.CAP_PREPARE_PERFORM
-                | IVibratorManager.CAP_MIXED_TRIGGER_ON
-                | IVibratorManager.CAP_MIXED_TRIGGER_PERFORM;
-        verify(mManagerHooks).prepareSyncedVibration(eq(expectedCap), eq(vibratorIds));
-        verify(mManagerHooks).triggerSyncedVibration(eq(vibrationId));
-        verify(mManagerHooks).cancelSyncedVibration();
-        assertTrue(mVibratorProviders.get(1).getAmplitudes().isEmpty());
-    }
-
-    @Test
-    public void vibrate_multipleWaveforms_playsWaveformsInParallel() throws Exception {
-        mockVibrators(1, 2, 3);
-        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
-        mVibratorProviders.get(2).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
-        mVibratorProviders.get(3).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
-
-        CombinedVibration effect = CombinedVibration.startParallel()
-                .addVibrator(1, VibrationEffect.createWaveform(
-                        new long[]{5, 10, 10}, new int[]{1, 2, 3}, -1))
-                .addVibrator(2, VibrationEffect.createWaveform(
-                        new long[]{20, 60}, new int[]{4, 5}, -1))
-                .addVibrator(3, VibrationEffect.createWaveform(
-                        new long[]{60}, new int[]{6}, -1))
-                .combine();
-        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
-        long vibrationId = conductor.getVibration().id;
-
-        // All vibrators are turned on in parallel.
-        assertTrue(waitUntil(
-                () -> mControllers.get(1).isVibrating()
-                        && mControllers.get(2).isVibrating()
-                        && mControllers.get(3).isVibrating(),
-                TEST_TIMEOUT_MILLIS));
-
-        waitForCompletion();
-
-        verify(mManagerHooks).noteVibratorOn(eq(UID), eq(80L));
-        verify(mManagerHooks).noteVibratorOff(eq(UID));
-        verify(mControllerCallbacks).onComplete(eq(1), eq(vibrationId));
-        verify(mControllerCallbacks).onComplete(eq(2), eq(vibrationId));
-        verify(mControllerCallbacks).onComplete(eq(3), eq(vibrationId));
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
-        assertFalse(mControllers.get(1).isVibrating());
-        assertFalse(mControllers.get(2).isVibrating());
-        assertFalse(mControllers.get(3).isVibrating());
-
-        assertEquals(Arrays.asList(expectedOneShot(25)),
-                mVibratorProviders.get(1).getEffectSegments(vibrationId));
-        assertEquals(Arrays.asList(expectedOneShot(80)),
-                mVibratorProviders.get(2).getEffectSegments(vibrationId));
-        assertEquals(Arrays.asList(expectedOneShot(60)),
-                mVibratorProviders.get(3).getEffectSegments(vibrationId));
-        assertEquals(expectedAmplitudes(1, 2, 3), mVibratorProviders.get(1).getAmplitudes());
-        assertEquals(expectedAmplitudes(4, 5), mVibratorProviders.get(2).getAmplitudes());
-        assertEquals(expectedAmplitudes(6), mVibratorProviders.get(3).getAmplitudes());
-    }
-
-    @LargeTest
-    @Test
-    public void vibrate_withWaveform_totalVibrationTimeRespected() {
-        int totalDuration = 10_000; // 10s
-        int stepDuration = 25; // 25ms
-
-        // 25% of the first waveform step will be spent on the native on() call.
-        // 25% of each waveform step will be spent on the native setAmplitude() call..
-        mVibratorProviders.get(VIBRATOR_ID).setOnLatency(stepDuration / 4);
-        mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
-
-        int stepCount = totalDuration / stepDuration;
-        long[] timings = new long[stepCount];
-        int[] amplitudes = new int[stepCount];
-        Arrays.fill(timings, stepDuration);
-        Arrays.fill(amplitudes, VibrationEffect.DEFAULT_AMPLITUDE);
-        VibrationEffect effect = VibrationEffect.createWaveform(timings, amplitudes, -1);
-
-        startThreadAndDispatcher(effect);
-        long startTime = SystemClock.elapsedRealtime();
-
-        waitForCompletion(totalDuration + TEST_TIMEOUT_MILLIS);
-        long delay = Math.abs(SystemClock.elapsedRealtime() - startTime - totalDuration);
-
-        // Allow some delay for thread scheduling and callback triggering.
-        int maxDelay = (int) (0.05 * totalDuration); // < 5% of total duration
-        assertTrue("Waveform with perceived delay of " + delay + "ms,"
-                        + " expected less than " + maxDelay + "ms",
-                delay < maxDelay);
-    }
-
-    @LargeTest
-    @Test
-    public void vibrate_cancelSlowVibrator_cancelIsNotBlockedByVibrationThread() throws Exception {
-        FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
-        fakeVibrator.setSupportedEffects(VibrationEffect.EFFECT_CLICK);
-
-        long latency = 5_000; // 5s
-        fakeVibrator.setOnLatency(latency);
-
-        VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
-        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
-        long vibrationId = conductor.getVibration().id;
-
-        assertTrue(waitUntil(() -> !fakeVibrator.getEffectSegments(vibrationId).isEmpty(),
-                TEST_TIMEOUT_MILLIS));
-        assertTrue(mThread.isRunningVibrationId(vibrationId));
-
-        // Run cancel in a separate thread so if VibrationThread.cancel blocks then this test should
-        // fail at waitForCompletion(cancellingThread).
-        Thread cancellingThread = new Thread(
-                () -> conductor.notifyCancelled(
-                        new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_USER),
-                        /* immediate= */ false));
-        cancellingThread.start();
-
-        // Cancelling the vibration should be fast and return right away, even if the thread is
-        // stuck at the slow call to the vibrator.
-        waitForCompletion(/* timeout= */ 50);
-
-        // After the vibrator call ends the vibration is cancelled and the vibrator is turned off.
-        waitForCompletion(/* timeout= */ latency + TEST_TIMEOUT_MILLIS);
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_USER);
-        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
-    }
-
-    @Test
-    public void vibrate_multiplePredefinedCancel_cancelsVibrationImmediately() throws Exception {
-        mockVibrators(1, 2);
-        mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
-        mVibratorProviders.get(2).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
-        mVibratorProviders.get(2).setSupportedPrimitives(
-                VibrationEffect.Composition.PRIMITIVE_CLICK);
-
-        CombinedVibration effect = CombinedVibration.startParallel()
-                .addVibrator(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
-                .addVibrator(2, VibrationEffect.startComposition()
-                        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 100)
-                        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 100)
-                        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 100)
-                        .compose())
-                .combine();
-        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
-        long vibrationId = conductor.getVibration().id;
-
-        assertTrue(waitUntil(() -> mControllers.get(2).isVibrating(),
-                TEST_TIMEOUT_MILLIS));
-        assertTrue(mThread.isRunningVibrationId(vibrationId));
-
-        // Run cancel in a separate thread so if VibrationThread.cancel blocks then this test should
-        // fail at waitForCompletion(vibrationThread) if the vibration not cancelled immediately.
-        Thread cancellingThread = new Thread(
-                () -> conductor.notifyCancelled(
-                        new Vibration.EndInfo(
-                                Vibration.Status.CANCELLED_BY_SCREEN_OFF),
-                        /* immediate= */ false));
-        cancellingThread.start();
-
-        waitForCompletion(/* timeout= */ 50);
-        cancellingThread.join();
-
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_SCREEN_OFF);
-        assertFalse(mControllers.get(1).isVibrating());
-        assertFalse(mControllers.get(2).isVibrating());
-    }
-
-    @Test
-    public void vibrate_multipleWaveformCancel_cancelsVibrationImmediately() throws Exception {
-        mockVibrators(1, 2);
-        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
-        mVibratorProviders.get(2).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
-
-        CombinedVibration effect = CombinedVibration.startParallel()
-                .addVibrator(1, VibrationEffect.createWaveform(
-                        new long[]{100, 100}, new int[]{1, 2}, 0))
-                .addVibrator(2, VibrationEffect.createOneShot(100, 100))
-                .combine();
-        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
-        long vibrationId = conductor.getVibration().id;
-
-        assertTrue(waitUntil(() -> mControllers.get(1).isVibrating()
-                        && mControllers.get(2).isVibrating(),
-                TEST_TIMEOUT_MILLIS));
-        assertTrue(mThread.isRunningVibrationId(vibrationId));
-
-        // Run cancel in a separate thread so if VibrationThread.cancel blocks then this test should
-        // fail at waitForCompletion(vibrationThread) if the vibration not cancelled immediately.
-        Thread cancellingThread = new Thread(
-                () -> conductor.notifyCancelled(
-                        new Vibration.EndInfo(
-                                Vibration.Status.CANCELLED_BY_SCREEN_OFF),
-                        /* immediate= */ false));
-        cancellingThread.start();
-
-        waitForCompletion(/* timeout= */ 50);
-        cancellingThread.join();
-
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_SCREEN_OFF);
-        assertFalse(mControllers.get(1).isVibrating());
-        assertFalse(mControllers.get(2).isVibrating());
-    }
-
-    @Test
-    public void vibrate_binderDied_cancelsVibration() throws Exception {
-        VibrationEffect effect = VibrationEffect.createWaveform(new long[]{5}, new int[]{100}, 0);
-        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
-        long vibrationId = conductor.getVibration().id;
-
-        assertTrue(waitUntil(() -> mControllers.get(VIBRATOR_ID).isVibrating(),
-                TEST_TIMEOUT_MILLIS));
-        assertTrue(mThread.isRunningVibrationId(vibrationId));
-
-        conductor.binderDied();
-        waitForCompletion();
-
-        verify(mVibrationToken).linkToDeath(same(conductor), eq(0));
-        verify(mVibrationToken).unlinkToDeath(same(conductor), eq(0));
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BINDER_DIED);
-        assertFalse(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId).isEmpty());
-        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
-    }
-
-    @Test
-    public void vibrate_waveformWithRampDown_addsRampDownAfterVibrationCompleted() {
-        when(mVibrationConfigMock.getRampDownDurationMs()).thenReturn(15);
-        mEffectAdapter = new DeviceVibrationEffectAdapter(mVibrationSettings);
-        mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
-
-        VibrationEffect effect = VibrationEffect.createWaveform(
-                new long[]{5, 5, 5}, new int[]{60, 120, 240}, -1);
-        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
-        long vibrationId = conductor.getVibration().id;
-        waitForCompletion();
-
-        verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
-
-        // Duration extended for 5 + 5 + 5 + 15.
-        assertEquals(Arrays.asList(expectedOneShot(30)),
-                mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId));
-        List<Float> amplitudes = mVibratorProviders.get(VIBRATOR_ID).getAmplitudes();
-        assertTrue(amplitudes.size() > 3);
-        assertEquals(expectedAmplitudes(60, 120, 240), amplitudes.subList(0, 3));
-        for (int i = 3; i < amplitudes.size(); i++) {
-            assertTrue(amplitudes.get(i) < amplitudes.get(i - 1));
-        }
-    }
-
-    @Test
-    public void vibrate_waveformWithRampDown_triggersCallbackWhenOriginalVibrationEnds() {
-        when(mVibrationConfigMock.getRampDownDurationMs()).thenReturn(10_000);
-        mEffectAdapter = new DeviceVibrationEffectAdapter(mVibrationSettings);
-        mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
-
-        VibrationEffect effect = VibrationEffect.createOneShot(10, 200);
-        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
-        long vibrationId = conductor.getVibration().id;
-
-        // Vibration completed but vibrator not yet released.
-        verify(mManagerHooks, timeout(TEST_TIMEOUT_MILLIS)).onVibrationCompleted(eq(vibrationId),
-                eq(new Vibration.EndInfo(Vibration.Status.FINISHED)));
-        verify(mManagerHooks, never()).onVibrationThreadReleased(anyLong());
-
-        // Thread still running ramp down.
-        assertTrue(mThread.isRunningVibrationId(vibrationId));
-
-        // Duration extended for 10 + 10000.
-        assertEquals(Arrays.asList(expectedOneShot(10_010)),
-                mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId));
-
-        // Will stop the ramp down right away.
-        conductor.notifyCancelled(
-                new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_SETTINGS_UPDATE),
-                /* immediate= */ true);
-        waitForCompletion();
-
-        // Does not cancel already finished vibration, but releases vibrator.
-        verify(mManagerHooks, never()).onVibrationCompleted(eq(vibrationId),
-                eq(new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_SETTINGS_UPDATE)));
-        verify(mManagerHooks).onVibrationThreadReleased(vibrationId);
-    }
-
-    @Test
-    public void vibrate_waveformCancelledWithRampDown_addsRampDownAfterVibrationCancelled()
-            throws Exception {
-        when(mVibrationConfigMock.getRampDownDurationMs()).thenReturn(15);
-        mEffectAdapter = new DeviceVibrationEffectAdapter(mVibrationSettings);
-        mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
-
-        VibrationEffect effect = VibrationEffect.createOneShot(10_000, 240);
-        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
-        long vibrationId = conductor.getVibration().id;
-        assertTrue(waitUntil(() -> mControllers.get(VIBRATOR_ID).isVibrating(),
-                TEST_TIMEOUT_MILLIS));
-        conductor.notifyCancelled(
-                new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_USER),
-                /* immediate= */ false);
-        waitForCompletion();
-
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_USER);
-
-        // Duration extended for 10000 + 15.
-        assertEquals(Arrays.asList(expectedOneShot(10_015)),
-                mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId));
-        List<Float> amplitudes = mVibratorProviders.get(VIBRATOR_ID).getAmplitudes();
-        assertTrue(amplitudes.size() > 1);
-        for (int i = 1; i < amplitudes.size(); i++) {
-            assertTrue(amplitudes.get(i) < amplitudes.get(i - 1));
-        }
-    }
-
-    @Test
-    public void vibrate_predefinedWithRampDown_doesNotAddRampDown() {
-        when(mVibrationConfigMock.getRampDownDurationMs()).thenReturn(15);
-        mEffectAdapter = new DeviceVibrationEffectAdapter(mVibrationSettings);
-        mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
-        mVibratorProviders.get(VIBRATOR_ID).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
-
-        VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
-        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
-        long vibrationId = conductor.getVibration().id;
-        waitForCompletion();
-
-        verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
-
-        assertEquals(Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_CLICK)),
-                mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId));
-        assertTrue(mVibratorProviders.get(VIBRATOR_ID).getAmplitudes().isEmpty());
-    }
-
-    @Test
-    public void vibrate_composedWithRampDown_doesNotAddRampDown() {
-        when(mVibrationConfigMock.getRampDownDurationMs()).thenReturn(15);
-        mEffectAdapter = new DeviceVibrationEffectAdapter(mVibrationSettings);
-        mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL,
-                IVibrator.CAP_COMPOSE_EFFECTS);
-        mVibratorProviders.get(VIBRATOR_ID).setSupportedPrimitives(
-                VibrationEffect.Composition.PRIMITIVE_CLICK);
-
-        VibrationEffect effect = VibrationEffect.startComposition()
-                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
-                .compose();
-        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
-        long vibrationId = conductor.getVibration().id;
-        waitForCompletion();
-
-        verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
-
-        assertEquals(
-                Arrays.asList(expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0)),
-                mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId));
-        assertTrue(mVibratorProviders.get(VIBRATOR_ID).getAmplitudes().isEmpty());
-    }
-
-    @Test
-    public void vibrate_pwleWithRampDown_doesNotAddRampDown() {
-        when(mVibrationConfigMock.getRampDownDurationMs()).thenReturn(15);
-        mEffectAdapter = new DeviceVibrationEffectAdapter(mVibrationSettings);
-        FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
-        fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL,
-                IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
-        fakeVibrator.setMinFrequency(100);
-        fakeVibrator.setResonantFrequency(150);
-        fakeVibrator.setFrequencyResolution(50);
-        fakeVibrator.setMaxAmplitudes(1, 1, 1);
-        fakeVibrator.setPwleSizeMax(2);
-
-        VibrationEffect effect = VibrationEffect.startWaveform()
-                .addTransition(Duration.ofMillis(1), targetAmplitude(1))
-                .build();
-        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
-        long vibrationId = conductor.getVibration().id;
-        waitForCompletion();
-
-        verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
-
-        assertEquals(Arrays.asList(expectedRamp(0, 1, 150, 150, 1)),
-                fakeVibrator.getEffectSegments(vibrationId));
-        assertTrue(fakeVibrator.getAmplitudes().isEmpty());
-    }
-
-    @Test
-    public void vibrate_multipleVibrations_withCancel() throws Exception {
-        mVibratorProviders.get(VIBRATOR_ID).setSupportedEffects(
-                VibrationEffect.EFFECT_CLICK, VibrationEffect.EFFECT_TICK);
-        mVibratorProviders.get(VIBRATOR_ID).setSupportedPrimitives(
-                VibrationEffect.Composition.PRIMITIVE_CLICK);
-        mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL,
-                IVibrator.CAP_COMPOSE_EFFECTS);
-
-        // A simple effect, followed by a repeating effect that gets cancelled, followed by another
-        // simple effect.
-        VibrationEffect effect1 = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
-        VibrationEffect effect2 = VibrationEffect.startComposition()
-                .repeatEffectIndefinitely(VibrationEffect.get(VibrationEffect.EFFECT_TICK))
-                .compose();
-        VibrationEffect effect3 = VibrationEffect.startComposition()
-                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
-                .compose();
-        VibrationEffect effect4 = VibrationEffect.createOneShot(8000, 100);
-        VibrationEffect effect5 = VibrationEffect.createOneShot(20, 222);
-
-        VibrationStepConductor conductor1 = startThreadAndDispatcher(effect1);
-        long vibrationId1 = conductor1.getVibration().id;
-        waitForCompletion();
-        verify(mControllerCallbacks).onComplete(VIBRATOR_ID, vibrationId1);
-        verifyCallbacksTriggered(vibrationId1, Vibration.Status.FINISHED);
-
-        VibrationStepConductor conductor2 = startThreadAndDispatcher(effect2);
-        long vibrationId2 = conductor2.getVibration().id;
-        // Effect2 won't complete on its own. Cancel it after a couple of repeats.
-        Thread.sleep(150);  // More than two TICKs.
-        conductor2.notifyCancelled(
-                new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_USER),
-                /* immediate= */ false);
-        waitForCompletion();
-
-        VibrationStepConductor conductor3 = startThreadAndDispatcher(effect3);
-        long vibrationId3 = conductor3.getVibration().id;
-        waitForCompletion();
-
-        // Effect4 is a long oneshot, but it gets cancelled as fast as possible.
-        long start4 = System.currentTimeMillis();
-        VibrationStepConductor conductor4 = startThreadAndDispatcher(effect4);
-        long vibrationId4 = conductor4.getVibration().id;
-        conductor4.notifyCancelled(
-                new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_SCREEN_OFF),
-                /* immediate= */ true);
-        waitForCompletion();
-        long duration4 = System.currentTimeMillis() - start4;
-
-        // Effect5 is to show that things keep going after the immediate cancel.
-        VibrationStepConductor conductor5 = startThreadAndDispatcher(effect5);
-        long vibrationId5 = conductor5.getVibration().id;
-        waitForCompletion();
-
-        FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
-        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
-
-        // Effect1
-        verify(mControllerCallbacks).onComplete(VIBRATOR_ID, vibrationId1);
-        verifyCallbacksTriggered(vibrationId1, Vibration.Status.FINISHED);
-
-        assertEquals(Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_CLICK)),
-                fakeVibrator.getEffectSegments(vibrationId1));
-
-        // Effect2: repeating, cancelled.
-        verify(mControllerCallbacks, atLeast(2)).onComplete(VIBRATOR_ID, vibrationId2);
-        verifyCallbacksTriggered(vibrationId2, Vibration.Status.CANCELLED_BY_USER);
-
-        // The exact count of segments might vary, so just check that there's more than 2 and
-        // all elements are the same segment.
-        List<VibrationEffectSegment> actualSegments2 = fakeVibrator.getEffectSegments(vibrationId2);
-        assertTrue(actualSegments2.size() + " > 2", actualSegments2.size() > 2);
-        for (VibrationEffectSegment segment : actualSegments2) {
-            assertEquals(expectedPrebaked(VibrationEffect.EFFECT_TICK), segment);
-        }
-
-        // Effect3
-        verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId3));
-        verifyCallbacksTriggered(vibrationId3, Vibration.Status.FINISHED);
-        assertEquals(Arrays.asList(
-                        expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0)),
-                fakeVibrator.getEffectSegments(vibrationId3));
-
-        // Effect4: cancelled quickly.
-        verifyCallbacksTriggered(vibrationId4, Vibration.Status.CANCELLED_BY_SCREEN_OFF);
-        assertTrue("Tested duration=" + duration4, duration4 < 2000);
-
-        // Effect5: normal oneshot. Don't worry about amplitude, as effect4 may or may not have
-        // started.
-
-        verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId5));
-        verifyCallbacksTriggered(vibrationId5, Vibration.Status.FINISHED);
-
-        assertEquals(Arrays.asList(expectedOneShot(20)),
-                fakeVibrator.getEffectSegments(vibrationId5));
-    }
-
-    private void mockVibrators(int... vibratorIds) {
-        for (int vibratorId : vibratorIds) {
-            mVibratorProviders.put(vibratorId,
-                    new FakeVibratorControllerProvider(mTestLooper.getLooper()));
-        }
-    }
-
-    private VibrationStepConductor startThreadAndDispatcher(VibrationEffect effect) {
-        return startThreadAndDispatcher(CombinedVibration.createParallel(effect));
-    }
-
-    private VibrationStepConductor startThreadAndDispatcher(CombinedVibration effect) {
-        return startThreadAndDispatcher(createVibration(effect));
-    }
-
-    private VibrationStepConductor startThreadAndDispatcher(HalVibration vib) {
-        mControllers = createVibratorControllers();
-        VibrationStepConductor conductor = new VibrationStepConductor(vib, mVibrationSettings,
-                mEffectAdapter, mControllers, mManagerHooks);
-        doAnswer(answer -> {
-            conductor.notifyVibratorComplete(answer.getArgument(0));
-            return null;
-        }).when(mControllerCallbacks).onComplete(anyInt(), eq(vib.id));
-        assertTrue(mThread.runVibrationOnVibrationThread(conductor));
-        return conductor;
-    }
-
-    private boolean waitUntil(BooleanSupplier predicate, long timeout)
-            throws InterruptedException {
-        long timeoutTimestamp = SystemClock.uptimeMillis() + timeout;
-        boolean predicateResult = false;
-        while (!predicateResult && SystemClock.uptimeMillis() < timeoutTimestamp) {
-            Thread.sleep(10);
-            predicateResult = predicate.getAsBoolean();
-        }
-        return predicateResult;
-    }
-
-    private void waitForCompletion() {
-        waitForCompletion(TEST_TIMEOUT_MILLIS);
-    }
-
-    private void waitForCompletion(long timeout) {
-        mThread.waitForThreadIdle(timeout);
-        mTestLooper.dispatchAll();  // Flush callbacks
-    }
-
-    private HalVibration createVibration(CombinedVibration effect) {
-        return new HalVibration(mVibrationToken, effect,
-                new Vibration.CallerInfo(ATTRS, UID, DISPLAY_ID, PACKAGE_NAME, "reason"));
-    }
-
-    private SparseArray<VibratorController> createVibratorControllers() {
-        SparseArray<VibratorController> array = new SparseArray<>();
-        for (Map.Entry<Integer, FakeVibratorControllerProvider> e : mVibratorProviders.entrySet()) {
-            int id = e.getKey();
-            array.put(id, e.getValue().newVibratorController(id, mControllerCallbacks));
-        }
-        // Start a looper for the vibrationcontrollers if it's not already running.
-        // TestLooper.AutoDispatchThread has a fixed 1s duration. Use a custom auto-dispatcher.
-        if (mCustomTestLooperDispatcher == null) {
-            mCustomTestLooperDispatcher = new TestLooperAutoDispatcher(mTestLooper);
-            mCustomTestLooperDispatcher.start();
-        }
-        return array;
-    }
-
-    private VibrationEffectSegment expectedOneShot(long millis) {
-        return new StepSegment(VibrationEffect.DEFAULT_AMPLITUDE,
-                /* frequencyHz= */ 0, (int) millis);
-    }
-
-    private VibrationEffectSegment expectedPrebaked(int effectId) {
-        return new PrebakedSegment(effectId, false, VibrationEffect.EFFECT_STRENGTH_MEDIUM);
-    }
-
-    private VibrationEffectSegment expectedPrimitive(int primitiveId, float scale, int delay) {
-        return new PrimitiveSegment(primitiveId, scale, delay);
-    }
-
-    private VibrationEffectSegment expectedRamp(float amplitude, float frequencyHz, int duration) {
-        return expectedRamp(amplitude, amplitude, frequencyHz, frequencyHz, duration);
-    }
-
-    private VibrationEffectSegment expectedRamp(float startAmplitude, float endAmplitude,
-            float startFrequencyHz, float endFrequencyHz, int duration) {
-        return new RampSegment(startAmplitude, endAmplitude, startFrequencyHz, endFrequencyHz,
-                duration);
-    }
-
-    private List<Float> expectedAmplitudes(int... amplitudes) {
-        return Arrays.stream(amplitudes)
-                .mapToObj(amplitude -> amplitude / 255f)
-                .collect(Collectors.toList());
-    }
-
-    private void verifyCallbacksTriggered(long vibrationId, Vibration.Status expectedStatus) {
-        verifyCallbacksTriggered(vibrationId, new Vibration.EndInfo(expectedStatus));
-    }
-
-    private void verifyCallbacksTriggered(long vibrationId, Vibration.EndInfo expectedEndInfo) {
-        verify(mManagerHooks).onVibrationCompleted(eq(vibrationId), eq(expectedEndInfo));
-        verify(mManagerHooks).onVibrationThreadReleased(vibrationId);
-    }
-
-    private static final class TestLooperAutoDispatcher extends Thread {
-        private final TestLooper mTestLooper;
-        private boolean mCancelled;
-
-        TestLooperAutoDispatcher(TestLooper testLooper) {
-            mTestLooper = testLooper;
-        }
-
-        @Override
-        public void run() {
-            while (!mCancelled) {
-                mTestLooper.dispatchAll();
-                try {
-                    Thread.sleep(10);
-                } catch (InterruptedException e) {
-                    return;
-                }
-            }
-        }
-
-        public void cancel() {
-            mCancelled = true;
-        }
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibratorControllerTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibratorControllerTest.java
deleted file mode 100644
index f2c1874..0000000
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibratorControllerTest.java
+++ /dev/null
@@ -1,327 +0,0 @@
-/*
- * 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.
- */
-
-package com.android.server.vibrator;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyLong;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.ArgumentMatchers.notNull;
-import static org.mockito.Mockito.inOrder;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.when;
-
-import android.content.ContentResolver;
-import android.content.ContextWrapper;
-import android.hardware.vibrator.Braking;
-import android.hardware.vibrator.IVibrator;
-import android.os.IBinder;
-import android.os.IVibratorStateListener;
-import android.os.VibrationEffect;
-import android.os.VibratorInfo;
-import android.os.test.TestLooper;
-import android.os.vibrator.PrebakedSegment;
-import android.os.vibrator.PrimitiveSegment;
-import android.os.vibrator.RampSegment;
-import android.platform.test.annotations.Presubmit;
-
-import androidx.test.InstrumentationRegistry;
-
-import com.android.internal.util.test.FakeSettingsProvider;
-import com.android.internal.util.test.FakeSettingsProviderRule;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.mockito.InOrder;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
-
-/**
- * Tests for {@link VibratorController}.
- *
- * Build/Install/Run:
- * atest FrameworksServicesTests:VibratorControllerTest
- */
-@Presubmit
-public class VibratorControllerTest {
-    private static final int VIBRATOR_ID = 0;
-
-    @Rule public MockitoRule rule = MockitoJUnit.rule();
-    @Rule public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
-
-    @Mock private VibratorController.OnVibrationCompleteListener mOnCompleteListenerMock;
-    @Mock private VibratorController.NativeWrapper mNativeWrapperMock;
-    @Mock private IVibratorStateListener mVibratorStateListenerMock;
-    @Mock private IBinder mVibratorStateListenerBinderMock;
-
-    private TestLooper mTestLooper;
-    private ContextWrapper mContextSpy;
-
-    @Before
-    public void setUp() throws Exception {
-        mTestLooper = new TestLooper();
-        mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getContext()));
-
-        ContentResolver contentResolver = mSettingsProviderRule.mockContentResolver(mContextSpy);
-        when(mContextSpy.getContentResolver()).thenReturn(contentResolver);
-        when(mVibratorStateListenerMock.asBinder()).thenReturn(mVibratorStateListenerBinderMock);
-        mockVibratorCapabilities(0);
-    }
-
-    private VibratorController createController() {
-        return new VibratorController(VIBRATOR_ID, mOnCompleteListenerMock, mNativeWrapperMock);
-    }
-
-    @Test
-    public void createController_initializesNativeWrapper() {
-        VibratorController controller = createController();
-        assertEquals(VIBRATOR_ID, controller.getVibratorInfo().getId());
-        verify(mNativeWrapperMock).init(eq(VIBRATOR_ID), notNull());
-    }
-
-    @Test
-    public void isAvailable_withVibratorHalPresent_returnsTrue() {
-        when(mNativeWrapperMock.isAvailable()).thenReturn(true);
-        assertTrue(createController().isAvailable());
-    }
-
-    @Test
-    public void isAvailable_withNoVibratorHalPresent_returnsFalse() {
-        when(mNativeWrapperMock.isAvailable()).thenReturn(false);
-        assertFalse(createController().isAvailable());
-    }
-
-    @Test
-    public void hasCapability_withSupport_returnsTrue() {
-        mockVibratorCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
-        assertTrue(createController().hasCapability(IVibrator.CAP_AMPLITUDE_CONTROL));
-    }
-
-    @Test
-    public void hasCapability_withNoSupport_returnsFalse() {
-        assertFalse(createController().hasCapability(IVibrator.CAP_ALWAYS_ON_CONTROL));
-        assertFalse(createController().hasCapability(IVibrator.CAP_AMPLITUDE_CONTROL));
-        assertFalse(createController().hasCapability(IVibrator.CAP_COMPOSE_EFFECTS));
-        assertFalse(createController().hasCapability(IVibrator.CAP_EXTERNAL_CONTROL));
-        assertFalse(createController().hasCapability(IVibrator.CAP_ON_CALLBACK));
-    }
-
-    @Test
-    public void setExternalControl_withCapability_enablesExternalControl() {
-        mockVibratorCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
-        VibratorController controller = createController();
-        assertFalse(controller.isUnderExternalControl());
-
-        controller.setExternalControl(true);
-        assertTrue(controller.isUnderExternalControl());
-
-        controller.setExternalControl(false);
-        assertFalse(controller.isUnderExternalControl());
-
-        InOrder inOrderVerifier = inOrder(mNativeWrapperMock);
-        inOrderVerifier.verify(mNativeWrapperMock).setExternalControl(eq(true));
-        inOrderVerifier.verify(mNativeWrapperMock).setExternalControl(eq(false));
-    }
-
-    @Test
-    public void setExternalControl_withNoCapability_ignoresExternalControl() {
-        VibratorController controller = createController();
-        assertFalse(controller.isUnderExternalControl());
-
-        controller.setExternalControl(true);
-        assertFalse(controller.isUnderExternalControl());
-
-        verify(mNativeWrapperMock, never()).setExternalControl(anyBoolean());
-    }
-
-    @Test
-    public void updateAlwaysOn_withCapability_enablesAlwaysOnEffect() {
-        mockVibratorCapabilities(IVibrator.CAP_ALWAYS_ON_CONTROL);
-        PrebakedSegment prebaked = createPrebaked(VibrationEffect.EFFECT_CLICK,
-                VibrationEffect.EFFECT_STRENGTH_MEDIUM);
-        createController().updateAlwaysOn(1, prebaked);
-
-        verify(mNativeWrapperMock).alwaysOnEnable(
-                eq(1L), eq((long) VibrationEffect.EFFECT_CLICK),
-                eq((long) VibrationEffect.EFFECT_STRENGTH_MEDIUM));
-    }
-
-    @Test
-    public void updateAlwaysOn_withNullEffect_disablesAlwaysOnEffect() {
-        mockVibratorCapabilities(IVibrator.CAP_ALWAYS_ON_CONTROL);
-        createController().updateAlwaysOn(1, null);
-        verify(mNativeWrapperMock).alwaysOnDisable(eq(1L));
-    }
-
-    @Test
-    public void updateAlwaysOn_withoutCapability_ignoresEffect() {
-        PrebakedSegment prebaked = createPrebaked(VibrationEffect.EFFECT_CLICK,
-                VibrationEffect.EFFECT_STRENGTH_MEDIUM);
-        createController().updateAlwaysOn(1, prebaked);
-
-        verify(mNativeWrapperMock, never()).alwaysOnDisable(anyLong());
-        verify(mNativeWrapperMock, never()).alwaysOnEnable(anyLong(), anyLong(), anyLong());
-    }
-
-    @Test
-    public void on_withDuration_turnsVibratorOn() {
-        when(mNativeWrapperMock.on(anyLong(), anyLong())).thenAnswer(args -> args.getArgument(0));
-        VibratorController controller = createController();
-        controller.on(100, 10);
-
-        assertTrue(controller.isVibrating());
-        verify(mNativeWrapperMock).on(eq(100L), eq(10L));
-    }
-
-    @Test
-    public void on_withPrebaked_performsEffect() {
-        when(mNativeWrapperMock.perform(anyLong(), anyLong(), anyLong())).thenReturn(10L);
-        VibratorController controller = createController();
-
-        PrebakedSegment prebaked = createPrebaked(VibrationEffect.EFFECT_CLICK,
-                VibrationEffect.EFFECT_STRENGTH_MEDIUM);
-        assertEquals(10L, controller.on(prebaked, 11));
-
-        assertTrue(controller.isVibrating());
-        verify(mNativeWrapperMock).perform(eq((long) VibrationEffect.EFFECT_CLICK),
-                eq((long) VibrationEffect.EFFECT_STRENGTH_MEDIUM), eq(11L));
-    }
-
-    @Test
-    public void on_withComposed_performsEffect() {
-        mockVibratorCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
-        when(mNativeWrapperMock.compose(any(), anyLong())).thenReturn(15L);
-        VibratorController controller = createController();
-
-        PrimitiveSegment[] primitives = new PrimitiveSegment[]{
-                new PrimitiveSegment(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f, 10)
-        };
-        assertEquals(15L, controller.on(primitives, 12));
-
-        assertTrue(controller.isVibrating());
-        verify(mNativeWrapperMock).compose(eq(primitives), eq(12L));
-    }
-
-    @Test
-    public void on_withComposedPwle_performsEffect() {
-        mockVibratorCapabilities(IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
-        when(mNativeWrapperMock.composePwle(any(), anyInt(), anyLong())).thenReturn(15L);
-        VibratorController controller = createController();
-
-        RampSegment[] primitives = new RampSegment[]{
-                new RampSegment(/* startAmplitude= */ 0, /* endAmplitude= */ 1,
-                        /* startFrequencyHz= */ 100, /* endFrequencyHz= */ 200, /* duration= */ 10)
-        };
-        assertEquals(15L, controller.on(primitives, 12));
-        assertTrue(controller.isVibrating());
-
-        verify(mNativeWrapperMock).composePwle(eq(primitives), eq(Braking.NONE), eq(12L));
-    }
-
-    @Test
-    public void off_turnsOffVibrator() {
-        when(mNativeWrapperMock.on(anyLong(), anyLong())).thenAnswer(args -> args.getArgument(0));
-        VibratorController controller = createController();
-
-        controller.on(100, 1);
-        assertTrue(controller.isVibrating());
-
-        controller.off();
-        controller.off();
-        assertFalse(controller.isVibrating());
-        verify(mNativeWrapperMock, times(2)).off();
-    }
-
-    @Test
-    public void reset_turnsOffVibratorAndDisablesExternalControl() {
-        mockVibratorCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
-        when(mNativeWrapperMock.on(anyLong(), anyLong())).thenAnswer(args -> args.getArgument(0));
-        VibratorController controller = createController();
-
-        controller.on(100, 1);
-        assertTrue(controller.isVibrating());
-
-        controller.reset();
-        assertFalse(controller.isVibrating());
-        verify(mNativeWrapperMock).setExternalControl(eq(false));
-        verify(mNativeWrapperMock).off();
-    }
-
-    @Test
-    public void registerVibratorStateListener_callbacksAreTriggered() throws Exception {
-        when(mNativeWrapperMock.on(anyLong(), anyLong())).thenAnswer(args -> args.getArgument(0));
-        VibratorController controller = createController();
-
-        controller.registerVibratorStateListener(mVibratorStateListenerMock);
-        controller.on(10, 1);
-        controller.on(100, 2);
-        controller.off();
-        controller.off();
-
-        InOrder inOrderVerifier = inOrder(mVibratorStateListenerMock);
-        // First notification done when listener is registered.
-        inOrderVerifier.verify(mVibratorStateListenerMock).onVibrating(false);
-        inOrderVerifier.verify(mVibratorStateListenerMock).onVibrating(eq(true));
-        inOrderVerifier.verify(mVibratorStateListenerMock).onVibrating(eq(false));
-        inOrderVerifier.verifyNoMoreInteractions();
-    }
-
-    @Test
-    public void unregisterVibratorStateListener_callbackNotTriggeredAfter() throws Exception {
-        when(mNativeWrapperMock.on(anyLong(), anyLong())).thenAnswer(args -> args.getArgument(0));
-        VibratorController controller = createController();
-
-        controller.registerVibratorStateListener(mVibratorStateListenerMock);
-        verify(mVibratorStateListenerMock).onVibrating(false);
-
-        controller.on(10, 1);
-        verify(mVibratorStateListenerMock).onVibrating(true);
-
-        controller.unregisterVibratorStateListener(mVibratorStateListenerMock);
-        Mockito.clearInvocations(mVibratorStateListenerMock);
-
-        controller.on(10, 1);
-        verifyNoMoreInteractions(mVibratorStateListenerMock);
-    }
-
-    private void mockVibratorCapabilities(int capabilities) {
-        VibratorInfo.FrequencyProfile frequencyProfile = new VibratorInfo.FrequencyProfile(
-                Float.NaN, Float.NaN, Float.NaN, null);
-        when(mNativeWrapperMock.getInfo(any(VibratorInfo.Builder.class)))
-                .then(invocation -> {
-                    ((VibratorInfo.Builder) invocation.getArgument(0))
-                            .setCapabilities(capabilities)
-                            .setFrequencyProfile(frequencyProfile);
-                    return true;
-                });
-    }
-
-    private PrebakedSegment createPrebaked(int effectId, int effectStrength) {
-        return new PrebakedSegment(effectId, /* shouldFallback= */ false, effectStrength);
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibratorFrameworkStatsLoggerTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibratorFrameworkStatsLoggerTest.java
deleted file mode 100644
index c1ab1db..0000000
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibratorFrameworkStatsLoggerTest.java
+++ /dev/null
@@ -1,127 +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.server.vibrator;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import android.os.Handler;
-import android.os.test.TestLooper;
-import android.platform.test.annotations.Presubmit;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
-
-/**
- * Tests for {@link VibratorFrameworkStatsLogger}.
- *
- * Build/Install/Run:
- * atest FrameworksServicesTests:VibratorFrameworkStatsLoggerTest
- */
-@Presubmit
-public class VibratorFrameworkStatsLoggerTest {
-
-    @Rule public MockitoRule rule = MockitoJUnit.rule();
-
-    private TestLooper mTestLooper;
-    private VibratorFrameworkStatsLogger mLogger;
-
-    @Before
-    public void setUp() {
-        mTestLooper = new TestLooper();
-    }
-
-    @Test
-    public void writeVibrationReportedAsync_afterMinInterval_writesRightAway() {
-        setUpLogger(/* minIntervalMillis= */ 10, /* queueMaxSize= */ 10);
-
-        VibrationStats.StatsInfo firstStats = newEmptyStatsInfo();
-        assertFalse(firstStats.isWritten());
-
-        mLogger.writeVibrationReportedAsync(firstStats);
-        mTestLooper.dispatchAll();
-        assertTrue(firstStats.isWritten());
-    }
-
-    @Test
-    public void writeVibrationReportedAsync_rightAfterLogging_schedulesToRunAfterRemainingDelay() {
-        setUpLogger(/* minIntervalMillis= */ 100, /* queueMaxSize= */ 10);
-
-        VibrationStats.StatsInfo firstStats = newEmptyStatsInfo();
-        VibrationStats.StatsInfo secondStats = newEmptyStatsInfo();
-        assertFalse(firstStats.isWritten());
-        assertFalse(secondStats.isWritten());
-
-        // Write first message at current SystemClock.uptimeMillis
-        mLogger.writeVibrationReportedAsync(firstStats);
-        mTestLooper.dispatchAll();
-        assertTrue(firstStats.isWritten());
-
-        // Second message is not written right away, it needs to wait the configured interval.
-        mLogger.writeVibrationReportedAsync(secondStats);
-        mTestLooper.dispatchAll();
-        assertFalse(secondStats.isWritten());
-
-        // Second message is written after delay passes.
-        mTestLooper.moveTimeForward(100);
-        mTestLooper.dispatchAll();
-        assertTrue(secondStats.isWritten());
-    }
-
-    @Test
-    public void writeVibrationReportedAsync_tooFast_logsUsingIntervalAndDropsMessagesFromQueue() {
-        setUpLogger(/* minIntervalMillis= */ 100, /* queueMaxSize= */ 2);
-
-        VibrationStats.StatsInfo firstStats = newEmptyStatsInfo();
-        VibrationStats.StatsInfo secondStats = newEmptyStatsInfo();
-        VibrationStats.StatsInfo thirdStats = newEmptyStatsInfo();
-
-        mLogger.writeVibrationReportedAsync(firstStats);
-        mLogger.writeVibrationReportedAsync(secondStats);
-        mLogger.writeVibrationReportedAsync(thirdStats);
-
-        // Only first message is logged.
-        mTestLooper.dispatchAll();
-        assertTrue(firstStats.isWritten());
-        assertFalse(secondStats.isWritten());
-        assertFalse(thirdStats.isWritten());
-
-        // Wait one interval to check only the second one is logged.
-        mTestLooper.moveTimeForward(100);
-        mTestLooper.dispatchAll();
-        assertTrue(secondStats.isWritten());
-        assertFalse(thirdStats.isWritten());
-
-        // Wait a long interval to check the third one was dropped and will never be logged.
-        mTestLooper.moveTimeForward(1_000);
-        mTestLooper.dispatchAll();
-        assertFalse(thirdStats.isWritten());
-    }
-
-    private void setUpLogger(int minIntervalMillis, int queueMaxSize) {
-        mLogger = new VibratorFrameworkStatsLogger(new Handler(mTestLooper.getLooper()),
-                minIntervalMillis, queueMaxSize);
-    }
-
-    private static VibrationStats.StatsInfo newEmptyStatsInfo() {
-        return new VibrationStats.StatsInfo(
-                0, 0, 0, Vibration.Status.FINISHED, new VibrationStats(), 0L);
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
deleted file mode 100644
index f158ce1..0000000
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ /dev/null
@@ -1,2278 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.vibrator;
-
-import static org.junit.Assert.assertArrayEquals;
-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.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
-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.atLeastOnce;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.inOrder;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.timeout;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.app.AppOpsManager;
-import android.content.ComponentName;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.ContextWrapper;
-import android.content.pm.PackageManagerInternal;
-import android.hardware.input.IInputManager;
-import android.hardware.input.InputManager;
-import android.hardware.input.InputManagerGlobal;
-import android.hardware.vibrator.IVibrator;
-import android.hardware.vibrator.IVibratorManager;
-import android.media.AudioAttributes;
-import android.media.AudioManager;
-import android.os.CombinedVibration;
-import android.os.ExternalVibration;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.IExternalVibrationController;
-import android.os.IExternalVibratorService;
-import android.os.IVibratorStateListener;
-import android.os.Looper;
-import android.os.PowerManager;
-import android.os.PowerManagerInternal;
-import android.os.PowerSaveState;
-import android.os.Process;
-import android.os.SystemClock;
-import android.os.UserHandle;
-import android.os.VibrationAttributes;
-import android.os.VibrationEffect;
-import android.os.Vibrator;
-import android.os.VibratorInfo;
-import android.os.test.TestLooper;
-import android.os.vibrator.PrebakedSegment;
-import android.os.vibrator.PrimitiveSegment;
-import android.os.vibrator.StepSegment;
-import android.os.vibrator.VibrationConfig;
-import android.os.vibrator.VibrationEffectSegment;
-import android.platform.test.annotations.Presubmit;
-import android.provider.Settings;
-import android.util.ArraySet;
-import android.util.SparseBooleanArray;
-import android.view.Display;
-import android.view.InputDevice;
-
-import androidx.test.InstrumentationRegistry;
-
-import com.android.internal.app.IBatteryStats;
-import com.android.internal.util.FrameworkStatsLog;
-import com.android.internal.util.test.FakeSettingsProvider;
-import com.android.internal.util.test.FakeSettingsProviderRule;
-import com.android.server.LocalServices;
-import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.mockito.ArgumentCaptor;
-import org.mockito.InOrder;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
-
-import java.time.Duration;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.function.Predicate;
-
-/**
- * Tests for {@link VibratorManagerService}.
- *
- * Build/Install/Run:
- * atest FrameworksServicesTests:VibratorManagerServiceTest
- */
-@Presubmit
-public class VibratorManagerServiceTest {
-
-    private static final int TEST_TIMEOUT_MILLIS = 1_000;
-    // Time to allow for a cancellation to complete (notably including system ramp down), but not so
-    // long that tests easily get really slow or flaky. If a vibration is close to this, it should
-    // be cancelled in the body of the individual test.
-    private static final int CLEANUP_TIMEOUT_MILLIS = 100;
-    private static final int UID = Process.ROOT_UID;
-    private static final int VIRTUAL_DISPLAY_ID = 1;
-    private static final String PACKAGE_NAME = "package";
-    private static final PowerSaveState NORMAL_POWER_STATE = new PowerSaveState.Builder().build();
-    private static final PowerSaveState LOW_POWER_STATE = new PowerSaveState.Builder()
-            .setBatterySaverEnabled(true).build();
-    private static final AudioAttributes AUDIO_ALARM_ATTRS =
-            new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_ALARM).build();
-    private static final AudioAttributes AUDIO_NOTIFICATION_ATTRS =
-            new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_NOTIFICATION).build();
-    private static final VibrationAttributes ALARM_ATTRS =
-            new VibrationAttributes.Builder().setUsage(VibrationAttributes.USAGE_ALARM).build();
-    private static final VibrationAttributes HAPTIC_FEEDBACK_ATTRS =
-            new VibrationAttributes.Builder().setUsage(VibrationAttributes.USAGE_TOUCH).build();
-    private static final VibrationAttributes NOTIFICATION_ATTRS =
-            new VibrationAttributes.Builder().setUsage(
-                    VibrationAttributes.USAGE_NOTIFICATION).build();
-    private static final VibrationAttributes RINGTONE_ATTRS =
-            new VibrationAttributes.Builder().setUsage(
-                    VibrationAttributes.USAGE_RINGTONE).build();
-    private static final VibrationAttributes UNKNOWN_ATTRS =
-            new VibrationAttributes.Builder().setUsage(VibrationAttributes.USAGE_UNKNOWN).build();
-
-    @Rule
-    public MockitoRule rule = MockitoJUnit.rule();
-    @Rule
-    public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
-
-    @Mock
-    private VibratorManagerService.NativeWrapper mNativeWrapperMock;
-    @Mock
-    private PackageManagerInternal mPackageManagerInternalMock;
-    @Mock
-    private PowerManagerInternal mPowerManagerInternalMock;
-    @Mock
-    private PowerSaveState mPowerSaveStateMock;
-    @Mock
-    private AppOpsManager mAppOpsManagerMock;
-    @Mock
-    private IInputManager mIInputManagerMock;
-    @Mock
-    private IBatteryStats mBatteryStatsMock;
-    @Mock
-    private VibratorFrameworkStatsLogger mVibratorFrameworkStatsLoggerMock;
-    @Mock
-    private VirtualDeviceManagerInternal mVirtualDeviceManagerInternalMock;
-
-    private final Map<Integer, FakeVibratorControllerProvider> mVibratorProviders = new HashMap<>();
-
-    private VibratorManagerService mService;
-    private Context mContextSpy;
-    private TestLooper mTestLooper;
-    private FakeVibrator mVibrator;
-    private PowerManagerInternal.LowPowerModeListener mRegisteredPowerModeListener;
-    private VibratorManagerService.ExternalVibratorService mExternalVibratorService;
-    private VibrationConfig mVibrationConfig;
-    private VirtualDeviceManagerInternal.VirtualDisplayListener mRegisteredVirtualDisplayListener;
-    private VirtualDeviceManagerInternal.AppsOnVirtualDeviceListener
-            mRegisteredAppsOnVirtualDeviceListener;
-
-    private InputManager mInputManager;
-
-    @Before
-    public void setUp() throws Exception {
-        mTestLooper = new TestLooper();
-        mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getContext()));
-        InputManagerGlobal.resetInstance(mIInputManagerMock);
-        mVibrationConfig = new VibrationConfig(mContextSpy.getResources());
-
-        ContentResolver contentResolver = mSettingsProviderRule.mockContentResolver(mContextSpy);
-        when(mContextSpy.getContentResolver()).thenReturn(contentResolver);
-
-        mVibrator = new FakeVibrator(mContextSpy);
-        when(mContextSpy.getSystemService(eq(Context.VIBRATOR_SERVICE))).thenReturn(mVibrator);
-
-        mInputManager = new InputManager(mContextSpy);
-        when(mContextSpy.getSystemService(eq(Context.INPUT_SERVICE)))
-                .thenReturn(mInputManager);
-        when(mContextSpy.getSystemService(Context.APP_OPS_SERVICE)).thenReturn(mAppOpsManagerMock);
-        when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[0]);
-        when(mPackageManagerInternalMock.getSystemUiServiceComponent())
-                .thenReturn(new ComponentName("", ""));
-        when(mPowerManagerInternalMock.getLowPowerState(PowerManager.ServiceType.VIBRATION))
-                .thenReturn(mPowerSaveStateMock);
-        doAnswer(invocation -> {
-            mRegisteredPowerModeListener = invocation.getArgument(0);
-            return null;
-        }).when(mPowerManagerInternalMock).registerLowPowerModeObserver(any());
-        doAnswer(invocation -> {
-            mRegisteredVirtualDisplayListener = invocation.getArgument(0);
-            return null;
-        }).when(mVirtualDeviceManagerInternalMock).registerVirtualDisplayListener(any());
-        doAnswer(invocation -> {
-            mRegisteredAppsOnVirtualDeviceListener = invocation.getArgument(0);
-            return null;
-        }).when(mVirtualDeviceManagerInternalMock).registerAppsOnVirtualDeviceListener(any());
-
-        setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 1);
-        setUserSetting(Settings.System.HAPTIC_FEEDBACK_ENABLED, 1);
-        setUserSetting(Settings.System.ALARM_VIBRATION_INTENSITY,
-                Vibrator.VIBRATION_INTENSITY_MEDIUM);
-        setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
-                Vibrator.VIBRATION_INTENSITY_MEDIUM);
-        setUserSetting(Settings.System.MEDIA_VIBRATION_INTENSITY,
-                Vibrator.VIBRATION_INTENSITY_MEDIUM);
-        setUserSetting(Settings.System.RING_VIBRATION_INTENSITY,
-                Vibrator.VIBRATION_INTENSITY_MEDIUM);
-        setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY,
-                Vibrator.VIBRATION_INTENSITY_MEDIUM);
-
-        addLocalServiceMock(PackageManagerInternal.class, mPackageManagerInternalMock);
-        addLocalServiceMock(PowerManagerInternal.class, mPowerManagerInternalMock);
-        addLocalServiceMock(VirtualDeviceManagerInternal.class, mVirtualDeviceManagerInternalMock);
-
-        mTestLooper.startAutoDispatch();
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        if (mService != null) {
-            // Wait until all vibrators have stopped vibrating, with a bit of flexibility for tests
-            // that just do a click or have cancelled at the end (waiting for ramp-down).
-            //
-            // Note: if a test is flaky here, check whether a VibrationEffect duration is close to
-            // CLEANUP_TIMEOUT_MILLIS - in which case it's probably best to just cancel that effect
-            // explicitly at the end of the test case (rather than letting it run and race flakily).
-            assertTrue(waitUntil(s -> {
-                for (int vibratorId : mService.getVibratorIds()) {
-                    if (s.isVibrating(vibratorId)) {
-                        return false;
-                    }
-                }
-                return true;
-            }, mService, CLEANUP_TIMEOUT_MILLIS));
-        }
-
-        LocalServices.removeServiceForTest(PackageManagerInternal.class);
-        LocalServices.removeServiceForTest(PowerManagerInternal.class);
-        // Ignore potential exceptions about the looper having never dispatched any messages.
-        mTestLooper.stopAutoDispatchAndIgnoreExceptions();
-        InputManagerGlobal.clearInstance();
-    }
-
-    private VibratorManagerService createSystemReadyService() {
-        VibratorManagerService service = createService();
-        service.systemReady();
-        return service;
-    }
-
-    private VibratorManagerService createService() {
-        mService = new VibratorManagerService(
-                mContextSpy,
-                new VibratorManagerService.Injector() {
-                    @Override
-                    VibratorManagerService.NativeWrapper getNativeWrapper() {
-                        return mNativeWrapperMock;
-                    }
-
-                    @Override
-                    Handler createHandler(Looper looper) {
-                        return new Handler(mTestLooper.getLooper());
-                    }
-
-                    @Override
-                    IBatteryStats getBatteryStatsService() {
-                        return mBatteryStatsMock;
-                    }
-
-                    @Override
-                    VibratorFrameworkStatsLogger getFrameworkStatsLogger(Handler handler) {
-                        return mVibratorFrameworkStatsLoggerMock;
-                    }
-
-                    @Override
-                    VibratorController createVibratorController(int vibratorId,
-                            VibratorController.OnVibrationCompleteListener listener) {
-                        return mVibratorProviders.get(vibratorId)
-                                .newVibratorController(vibratorId, listener);
-                    }
-
-                    @Override
-                    void addService(String name, IBinder service) {
-                        Object serviceInstance = service;
-                        mExternalVibratorService =
-                                (VibratorManagerService.ExternalVibratorService) serviceInstance;
-                    }
-                });
-        return mService;
-    }
-
-    @Test
-    public void createService_initializesNativeManagerServiceAndVibrators() {
-        mockVibrators(1, 2);
-        createService();
-        verify(mNativeWrapperMock).init(any());
-        assertTrue(mVibratorProviders.get(1).isInitialized());
-        assertTrue(mVibratorProviders.get(2).isInitialized());
-    }
-
-    @Test
-    public void createService_resetsVibrators() {
-        mockVibrators(1, 2);
-        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
-        mVibratorProviders.get(2).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
-
-        createService();
-        assertEquals(1, mVibratorProviders.get(1).getOffCount());
-        assertEquals(1, mVibratorProviders.get(2).getOffCount());
-        assertEquals(Arrays.asList(false), mVibratorProviders.get(1).getExternalControlStates());
-        assertEquals(Arrays.asList(false), mVibratorProviders.get(2).getExternalControlStates());
-    }
-
-    @Test
-    public void createService_doNotCrashIfUsedBeforeSystemReady() {
-        mockVibrators(1, 2);
-        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_ALWAYS_ON_CONTROL);
-        mVibratorProviders.get(2).setCapabilities(IVibrator.CAP_ALWAYS_ON_CONTROL);
-        VibratorManagerService service = createService();
-
-        assertNotNull(service.getVibratorIds());
-        assertNotNull(service.getVibratorInfo(1));
-        assertFalse(service.isVibrating(1));
-
-        CombinedVibration effect = CombinedVibration.createParallel(
-                VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK));
-        vibrate(service, effect, HAPTIC_FEEDBACK_ATTRS);
-        service.cancelVibrate(VibrationAttributes.USAGE_FILTER_MATCH_ALL, service);
-
-        assertTrue(service.setAlwaysOnEffect(UID, PACKAGE_NAME, 1, effect, ALARM_ATTRS));
-
-        IVibratorStateListener listener = mockVibratorStateListener();
-        assertTrue(service.registerVibratorStateListener(1, listener));
-        assertTrue(service.unregisterVibratorStateListener(1, listener));
-    }
-
-    @Test
-    public void getVibratorIds_withNullResultFromNative_returnsEmptyArray() {
-        when(mNativeWrapperMock.getVibratorIds()).thenReturn(null);
-        assertArrayEquals(new int[0], createSystemReadyService().getVibratorIds());
-    }
-
-    @Test
-    public void getVibratorIds_withNonEmptyResultFromNative_returnsSameArray() {
-        mockVibrators(2, 1);
-        assertArrayEquals(new int[]{2, 1}, createSystemReadyService().getVibratorIds());
-    }
-
-    @Test
-    public void getVibratorInfo_withMissingVibratorId_returnsNull() {
-        mockVibrators(1);
-        assertNull(createSystemReadyService().getVibratorInfo(2));
-    }
-
-    @Test
-    public void getVibratorInfo_vibratorFailedLoadBeforeSystemReady_returnsNull() {
-        mockVibrators(1);
-        mVibratorProviders.get(1).setVibratorInfoLoadSuccessful(false);
-        assertNull(createService().getVibratorInfo(1));
-    }
-
-    @Test
-    public void getVibratorInfo_vibratorFailedLoadAfterSystemReady_returnsInfoForVibrator() {
-        mockVibrators(1);
-        mVibratorProviders.get(1).setVibratorInfoLoadSuccessful(false);
-        mVibratorProviders.get(1).setResonantFrequency(123.f);
-        VibratorInfo info = createSystemReadyService().getVibratorInfo(1);
-
-        assertNotNull(info);
-        assertEquals(1, info.getId());
-        assertEquals(123.f, info.getResonantFrequencyHz(), 0.01 /*tolerance*/);
-    }
-
-    @Test
-    public void getVibratorInfo_vibratorSuccessfulLoadBeforeSystemReady_returnsInfoForVibrator() {
-        mockVibrators(1);
-        FakeVibratorControllerProvider vibrator = mVibratorProviders.get(1);
-        vibrator.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS, IVibrator.CAP_AMPLITUDE_CONTROL);
-        vibrator.setSupportedEffects(VibrationEffect.EFFECT_CLICK);
-        vibrator.setSupportedPrimitives(VibrationEffect.Composition.PRIMITIVE_CLICK);
-        vibrator.setResonantFrequency(123.f);
-        vibrator.setQFactor(Float.NaN);
-        VibratorInfo info = createService().getVibratorInfo(1);
-
-        assertNotNull(info);
-        assertEquals(1, info.getId());
-        assertTrue(info.hasAmplitudeControl());
-        assertTrue(info.hasCapability(IVibrator.CAP_COMPOSE_EFFECTS));
-        assertFalse(info.hasCapability(IVibrator.CAP_ON_CALLBACK));
-        assertEquals(Vibrator.VIBRATION_EFFECT_SUPPORT_YES,
-                info.isEffectSupported(VibrationEffect.EFFECT_CLICK));
-        assertEquals(Vibrator.VIBRATION_EFFECT_SUPPORT_NO,
-                info.isEffectSupported(VibrationEffect.EFFECT_TICK));
-        assertTrue(info.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_CLICK));
-        assertFalse(info.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_TICK));
-        assertEquals(123.f, info.getResonantFrequencyHz(), 0.01 /*tolerance*/);
-        assertTrue(Float.isNaN(info.getQFactor()));
-    }
-
-    @Test
-    public void getVibratorInfo_vibratorFailedThenSuccessfulLoad_returnsNullThenInfo() {
-        mockVibrators(1);
-        mVibratorProviders.get(1).setVibratorInfoLoadSuccessful(false);
-
-        VibratorManagerService service = createService();
-        assertNull(createService().getVibratorInfo(1));
-
-        mVibratorProviders.get(1).setVibratorInfoLoadSuccessful(true);
-        mVibratorProviders.get(1).setResonantFrequency(123.f);
-        service.systemReady();
-
-        VibratorInfo info = createService().getVibratorInfo(1);
-        assertNotNull(info);
-        assertEquals(1, info.getId());
-        assertEquals(123.f, info.getResonantFrequencyHz(), 0.01 /*tolerance*/);
-    }
-
-    @Test
-    public void registerVibratorStateListener_callbacksAreTriggered() throws Exception {
-        mockVibrators(1);
-        VibratorManagerService service = createSystemReadyService();
-        IVibratorStateListener listenerMock = mockVibratorStateListener();
-        service.registerVibratorStateListener(1, listenerMock);
-
-        long oneShotDuration = 20;
-        vibrateAndWaitUntilFinished(service,
-                VibrationEffect.createOneShot(oneShotDuration, VibrationEffect.DEFAULT_AMPLITUDE),
-                ALARM_ATTRS);
-
-        InOrder inOrderVerifier = inOrder(listenerMock);
-        // First notification done when listener is registered.
-        inOrderVerifier.verify(listenerMock).onVibrating(eq(false));
-        inOrderVerifier.verify(listenerMock).onVibrating(eq(true));
-        // The last notification is after the vibration has completed.
-        inOrderVerifier.verify(listenerMock, timeout(TEST_TIMEOUT_MILLIS)).onVibrating(eq(false));
-        inOrderVerifier.verifyNoMoreInteractions();
-
-        InOrder batteryVerifier = inOrder(mBatteryStatsMock);
-        batteryVerifier.verify(mBatteryStatsMock)
-                .noteVibratorOn(UID, oneShotDuration + mVibrationConfig.getRampDownDurationMs());
-        batteryVerifier.verify(mBatteryStatsMock).noteVibratorOff(UID);
-    }
-
-    @Test
-    public void unregisterVibratorStateListener_callbackNotTriggeredAfter() throws Exception {
-        mockVibrators(1);
-        VibratorManagerService service = createSystemReadyService();
-        IVibratorStateListener listenerMock = mockVibratorStateListener();
-        service.registerVibratorStateListener(1, listenerMock);
-
-        vibrate(service, VibrationEffect.createOneShot(40, 100), ALARM_ATTRS);
-
-        // Wait until service knows vibrator is on.
-        assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
-
-        service.unregisterVibratorStateListener(1, listenerMock);
-
-        // Wait until vibrator is off.
-        assertTrue(waitUntil(s -> !s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
-
-        InOrder inOrderVerifier = inOrder(listenerMock);
-        // First notification done when listener is registered.
-        inOrderVerifier.verify(listenerMock).onVibrating(eq(false));
-        inOrderVerifier.verify(listenerMock).onVibrating(eq(true));
-        inOrderVerifier.verify(listenerMock, atLeastOnce()).asBinder(); // unregister
-        inOrderVerifier.verifyNoMoreInteractions();
-    }
-
-    @Test
-    public void registerVibratorStateListener_multipleVibratorsAreTriggered() throws Exception {
-        mockVibrators(0, 1, 2);
-        mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
-        VibratorManagerService service = createSystemReadyService();
-        IVibratorStateListener[] listeners = new IVibratorStateListener[3];
-        for (int i = 0; i < 3; i++) {
-            listeners[i] = mockVibratorStateListener();
-            service.registerVibratorStateListener(i, listeners[i]);
-        }
-
-        vibrate(service, CombinedVibration.startParallel()
-                .addVibrator(0, VibrationEffect.createOneShot(40, 100))
-                .addVibrator(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
-                .combine(), ALARM_ATTRS);
-        // Wait until service knows vibrator is on.
-        assertTrue(waitUntil(s -> s.isVibrating(0), service, TEST_TIMEOUT_MILLIS));
-
-        verify(listeners[0]).onVibrating(eq(true));
-        verify(listeners[1]).onVibrating(eq(true));
-        verify(listeners[2], never()).onVibrating(eq(true));
-        cancelVibrate(service);
-    }
-
-    @Test
-    public void setAlwaysOnEffect_withMono_enablesAlwaysOnEffectToAllVibratorsWithCapability() {
-        mockVibrators(1, 2, 3);
-        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_ALWAYS_ON_CONTROL);
-        mVibratorProviders.get(3).setCapabilities(IVibrator.CAP_ALWAYS_ON_CONTROL);
-
-        CombinedVibration effect = CombinedVibration.createParallel(
-                VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK));
-        assertTrue(createSystemReadyService().setAlwaysOnEffect(
-                UID, PACKAGE_NAME, 1, effect, ALARM_ATTRS));
-
-        PrebakedSegment expected = new PrebakedSegment(
-                VibrationEffect.EFFECT_CLICK, false, VibrationEffect.EFFECT_STRENGTH_MEDIUM);
-
-        // Only vibrators 1 and 3 have always-on capabilities.
-        assertEquals(mVibratorProviders.get(1).getAlwaysOnEffect(1), expected);
-        assertNull(mVibratorProviders.get(2).getAlwaysOnEffect(1));
-        assertEquals(mVibratorProviders.get(3).getAlwaysOnEffect(1), expected);
-    }
-
-    @Test
-    public void setAlwaysOnEffect_withStereo_enablesAlwaysOnEffectToAllVibratorsWithCapability() {
-        mockVibrators(1, 2, 3, 4);
-        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_ALWAYS_ON_CONTROL);
-        mVibratorProviders.get(2).setCapabilities(IVibrator.CAP_ALWAYS_ON_CONTROL);
-        mVibratorProviders.get(4).setCapabilities(IVibrator.CAP_ALWAYS_ON_CONTROL);
-
-        CombinedVibration effect = CombinedVibration.startParallel()
-                .addVibrator(1, VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK))
-                .addVibrator(2, VibrationEffect.createPredefined(VibrationEffect.EFFECT_TICK))
-                .addVibrator(3, VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK))
-                .combine();
-        assertTrue(createSystemReadyService().setAlwaysOnEffect(
-                UID, PACKAGE_NAME, 1, effect, ALARM_ATTRS));
-
-        PrebakedSegment expectedClick = new PrebakedSegment(
-                VibrationEffect.EFFECT_CLICK, false, VibrationEffect.EFFECT_STRENGTH_MEDIUM);
-
-        PrebakedSegment expectedTick = new PrebakedSegment(
-                VibrationEffect.EFFECT_TICK, false, VibrationEffect.EFFECT_STRENGTH_MEDIUM);
-
-        // Enables click on vibrator 1 and tick on vibrator 2 only.
-        assertEquals(mVibratorProviders.get(1).getAlwaysOnEffect(1), expectedClick);
-        assertEquals(mVibratorProviders.get(2).getAlwaysOnEffect(1), expectedTick);
-        assertNull(mVibratorProviders.get(3).getAlwaysOnEffect(1));
-        assertNull(mVibratorProviders.get(4).getAlwaysOnEffect(1));
-    }
-
-    @Test
-    public void setAlwaysOnEffect_withNullEffect_disablesAlwaysOnEffects() {
-        mockVibrators(1, 2, 3);
-        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_ALWAYS_ON_CONTROL);
-        mVibratorProviders.get(3).setCapabilities(IVibrator.CAP_ALWAYS_ON_CONTROL);
-
-        CombinedVibration effect = CombinedVibration.createParallel(
-                VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK));
-        assertTrue(createSystemReadyService().setAlwaysOnEffect(
-                UID, PACKAGE_NAME, 1, effect, ALARM_ATTRS));
-
-        assertTrue(createSystemReadyService().setAlwaysOnEffect(
-                UID, PACKAGE_NAME, 1, null, ALARM_ATTRS));
-
-        assertNull(mVibratorProviders.get(1).getAlwaysOnEffect(1));
-        assertNull(mVibratorProviders.get(2).getAlwaysOnEffect(1));
-        assertNull(mVibratorProviders.get(3).getAlwaysOnEffect(1));
-    }
-
-    @Test
-    public void setAlwaysOnEffect_withNonPrebakedEffect_ignoresEffect() {
-        mockVibrators(1);
-        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_ALWAYS_ON_CONTROL);
-
-        CombinedVibration effect = CombinedVibration.createParallel(
-                VibrationEffect.createOneShot(100, VibrationEffect.DEFAULT_AMPLITUDE));
-        assertFalse(createSystemReadyService().setAlwaysOnEffect(
-                UID, PACKAGE_NAME, 1, effect, ALARM_ATTRS));
-
-        assertNull(mVibratorProviders.get(1).getAlwaysOnEffect(1));
-    }
-
-    @Test
-    public void setAlwaysOnEffect_withNonSyncedEffect_ignoresEffect() {
-        mockVibrators(1);
-        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_ALWAYS_ON_CONTROL);
-
-        CombinedVibration effect = CombinedVibration.startSequential()
-                .addNext(0, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
-                .combine();
-        assertFalse(createSystemReadyService().setAlwaysOnEffect(
-                UID, PACKAGE_NAME, 1, effect, ALARM_ATTRS));
-
-        assertNull(mVibratorProviders.get(1).getAlwaysOnEffect(1));
-    }
-
-    @Test
-    public void setAlwaysOnEffect_withNoVibratorWithCapability_ignoresEffect() {
-        mockVibrators(1);
-        VibratorManagerService service = createSystemReadyService();
-
-        CombinedVibration mono = CombinedVibration.createParallel(
-                VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK));
-        CombinedVibration stereo = CombinedVibration.startParallel()
-                .addVibrator(0, VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK))
-                .combine();
-        assertFalse(service.setAlwaysOnEffect(UID, PACKAGE_NAME, 1, mono, ALARM_ATTRS));
-        assertFalse(service.setAlwaysOnEffect(UID, PACKAGE_NAME, 2, stereo, ALARM_ATTRS));
-
-        assertNull(mVibratorProviders.get(1).getAlwaysOnEffect(1));
-    }
-
-    @Test
-    public void vibrate_withRingtone_usesRingerModeSettings() throws Exception {
-        mockVibrators(1);
-        FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
-        fakeVibrator.setSupportedEffects(VibrationEffect.EFFECT_CLICK,
-                VibrationEffect.EFFECT_HEAVY_CLICK, VibrationEffect.EFFECT_DOUBLE_CLICK);
-
-        setRingerMode(AudioManager.RINGER_MODE_SILENT);
-        VibratorManagerService service = createSystemReadyService();
-        vibrateAndWaitUntilFinished(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK),
-                RINGTONE_ATTRS);
-
-        setRingerMode(AudioManager.RINGER_MODE_NORMAL);
-        service = createSystemReadyService();
-        vibrateAndWaitUntilFinished(
-                service, VibrationEffect.get(VibrationEffect.EFFECT_HEAVY_CLICK), RINGTONE_ATTRS);
-
-        setRingerMode(AudioManager.RINGER_MODE_VIBRATE);
-        service = createSystemReadyService();
-        vibrateAndWaitUntilFinished(
-                service, VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK), RINGTONE_ATTRS);
-
-        assertEquals(
-                Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_HEAVY_CLICK),
-                        expectedPrebaked(VibrationEffect.EFFECT_DOUBLE_CLICK)),
-                mVibratorProviders.get(1).getAllEffectSegments());
-    }
-
-    @Test
-    public void vibrate_withPowerMode_usesPowerModeState() throws Exception {
-        mockVibrators(1);
-        FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
-        fakeVibrator.setSupportedEffects(VibrationEffect.EFFECT_TICK, VibrationEffect.EFFECT_CLICK,
-                VibrationEffect.EFFECT_HEAVY_CLICK, VibrationEffect.EFFECT_DOUBLE_CLICK);
-        VibratorManagerService service = createSystemReadyService();
-
-        mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE);
-        vibrateAndWaitUntilFinished(service, VibrationEffect.get(VibrationEffect.EFFECT_TICK),
-                HAPTIC_FEEDBACK_ATTRS);
-        vibrateAndWaitUntilFinished(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK),
-                RINGTONE_ATTRS);
-
-        mRegisteredPowerModeListener.onLowPowerModeChanged(NORMAL_POWER_STATE);
-        vibrateAndWaitUntilFinished(service,
-                VibrationEffect.get(VibrationEffect.EFFECT_HEAVY_CLICK), /* attrs= */ null);
-        vibrateAndWaitUntilFinished(service,
-                VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK), NOTIFICATION_ATTRS);
-
-        assertEquals(
-                Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_CLICK),
-                        expectedPrebaked(VibrationEffect.EFFECT_HEAVY_CLICK),
-                        expectedPrebaked(VibrationEffect.EFFECT_DOUBLE_CLICK)),
-                mVibratorProviders.get(1).getAllEffectSegments());
-    }
-
-    @Test
-    public void vibrate_withAudioAttributes_usesOriginalAudioUsageInAppOpsManager() {
-        VibratorManagerService service = createSystemReadyService();
-
-        VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
-        AudioAttributes audioAttributes = new AudioAttributes.Builder()
-                .setUsage(AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY).build();
-        VibrationAttributes vibrationAttributes =
-                new VibrationAttributes.Builder(audioAttributes).build();
-
-        vibrate(service, effect, vibrationAttributes);
-
-        verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
-                eq(AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY), anyInt(), anyString());
-    }
-
-    @Test
-    public void vibrate_withVibrationAttributes_usesCorrespondingAudioUsageInAppOpsManager() {
-        VibratorManagerService service = createSystemReadyService();
-
-        vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), ALARM_ATTRS);
-        vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_TICK), NOTIFICATION_ATTRS);
-        vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), RINGTONE_ATTRS);
-        vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_TICK), HAPTIC_FEEDBACK_ATTRS);
-        vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK),
-                new VibrationAttributes.Builder().setUsage(
-                        VibrationAttributes.USAGE_COMMUNICATION_REQUEST).build());
-        vibrate(service, VibrationEffect.createOneShot(2000, 200),
-                new VibrationAttributes.Builder().setUsage(
-                        VibrationAttributes.USAGE_UNKNOWN).build());
-
-        InOrder inOrderVerifier = inOrder(mAppOpsManagerMock);
-        inOrderVerifier.verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
-                eq(AudioAttributes.USAGE_ALARM), anyInt(), anyString());
-        inOrderVerifier.verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
-                eq(AudioAttributes.USAGE_NOTIFICATION), anyInt(), anyString());
-        inOrderVerifier.verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
-                eq(AudioAttributes.USAGE_NOTIFICATION_RINGTONE), anyInt(), anyString());
-        inOrderVerifier.verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
-                eq(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION), anyInt(), anyString());
-        inOrderVerifier.verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
-                eq(AudioAttributes.USAGE_VOICE_COMMUNICATION),
-                anyInt(), anyString());
-        inOrderVerifier.verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
-                eq(AudioAttributes.USAGE_UNKNOWN), anyInt(), anyString());
-    }
-
-    @Test
-    public void vibrate_withVibrationAttributesEnforceFreshSettings_refreshesVibrationSettings()
-            throws Exception {
-        mockVibrators(0);
-        mVibratorProviders.get(0).setSupportedEffects(VibrationEffect.EFFECT_CLICK,
-                VibrationEffect.EFFECT_TICK);
-        setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
-                Vibrator.VIBRATION_INTENSITY_HIGH);
-        VibratorManagerService service = createSystemReadyService();
-
-        VibrationAttributes notificationWithFreshAttrs = new VibrationAttributes.Builder()
-                .setUsage(VibrationAttributes.USAGE_NOTIFICATION)
-                .setFlags(VibrationAttributes.FLAG_INVALIDATE_SETTINGS_CACHE)
-                .build();
-
-        setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
-                Vibrator.VIBRATION_INTENSITY_LOW);
-        vibrateAndWaitUntilFinished(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK),
-                NOTIFICATION_ATTRS);
-        vibrateAndWaitUntilFinished(service, VibrationEffect.get(VibrationEffect.EFFECT_TICK),
-                notificationWithFreshAttrs);
-
-        assertEquals(
-                Arrays.asList(
-                        expectedPrebaked(VibrationEffect.EFFECT_CLICK,
-                                VibrationEffect.EFFECT_STRENGTH_STRONG),
-                        expectedPrebaked(VibrationEffect.EFFECT_TICK,
-                                VibrationEffect.EFFECT_STRENGTH_LIGHT)),
-                mVibratorProviders.get(0).getAllEffectSegments());
-    }
-
-    @Test
-    public void vibrate_withAttributesUnknownUsage_usesEffectToIdentifyTouchUsage() {
-        VibratorManagerService service = createSystemReadyService();
-
-        VibrationAttributes unknownAttributes = VibrationAttributes.createForUsage(
-                VibrationAttributes.USAGE_UNKNOWN);
-        vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), unknownAttributes);
-        vibrate(service, VibrationEffect.createOneShot(200, 200), unknownAttributes);
-        vibrate(service, VibrationEffect.createWaveform(
-                new long[] { 100, 200, 300 }, new int[] {1, 2, 3}, -1), unknownAttributes);
-        vibrate(service,
-                VibrationEffect.startComposition()
-                        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_QUICK_RISE)
-                        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_QUICK_FALL)
-                        .compose(),
-                unknownAttributes);
-
-        verify(mAppOpsManagerMock, times(4))
-                .checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
-                        eq(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION), anyInt(), anyString());
-        verify(mAppOpsManagerMock, never())
-                .checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
-                        eq(AudioAttributes.USAGE_UNKNOWN), anyInt(), anyString());
-    }
-
-    @Test
-    public void vibrate_withAttributesUnknownUsage_ignoresEffectIfNotHapticFeedbackCandidate() {
-        VibratorManagerService service = createSystemReadyService();
-
-        VibrationAttributes unknownAttributes = VibrationAttributes.createForUsage(
-                VibrationAttributes.USAGE_UNKNOWN);
-        vibrate(service, VibrationEffect.get(VibrationEffect.RINGTONES[0]), unknownAttributes);
-        vibrate(service, VibrationEffect.createOneShot(2000, 200), unknownAttributes);
-        vibrate(service, VibrationEffect.createWaveform(
-                new long[] { 100, 200, 300 }, new int[] {1, 2, 3}, 0), unknownAttributes);
-        vibrate(service,
-                VibrationEffect.startComposition()
-                        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_QUICK_RISE)
-                        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_QUICK_FALL)
-                        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_SPIN)
-                        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_THUD)
-                        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
-                        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK)
-                        .compose(),
-                unknownAttributes);
-
-        verify(mAppOpsManagerMock, never())
-                .checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
-                        eq(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION), anyInt(), anyString());
-        verify(mAppOpsManagerMock, times(4))
-                .checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
-                        eq(AudioAttributes.USAGE_UNKNOWN), anyInt(), anyString());
-    }
-
-    @Test
-    public void vibrate_withOngoingRepeatingVibration_ignoresEffect() throws Exception {
-        mockVibrators(1);
-        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
-        VibratorManagerService service = createSystemReadyService();
-
-        VibrationEffect repeatingEffect = VibrationEffect.createWaveform(
-                new long[]{10_000, 10_000}, new int[]{128, 255}, 1);
-        vibrate(service, repeatingEffect, new VibrationAttributes.Builder().setUsage(
-                VibrationAttributes.USAGE_UNKNOWN).build());
-
-        // VibrationThread will start this vibration async, wait until it has started.
-        assertTrue(waitUntil(s -> !mVibratorProviders.get(1).getAllEffectSegments().isEmpty(),
-                service, TEST_TIMEOUT_MILLIS));
-
-        vibrateAndWaitUntilFinished(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK),
-                HAPTIC_FEEDBACK_ATTRS);
-
-        // The time estimate is recorded when the vibration starts, repeating vibrations
-        // are capped at BATTERY_STATS_REPEATING_VIBRATION_DURATION (=5000).
-        verify(mBatteryStatsMock).noteVibratorOn(UID, 5000);
-        // The second vibration shouldn't have recorded that the vibrators were turned on.
-        verify(mBatteryStatsMock, times(1)).noteVibratorOn(anyInt(), anyLong());
-        // No segment played is the prebaked CLICK from the second vibration.
-        assertFalse(mVibratorProviders.get(1).getAllEffectSegments().stream()
-                .anyMatch(PrebakedSegment.class::isInstance));
-        cancelVibrate(service);  // Clean up repeating effect.
-    }
-
-    @Test
-    public void vibrate_withOngoingRepeatingVibrationBeingCancelled_playsAfterPreviousIsCancelled()
-            throws Exception {
-        mockVibrators(1);
-        FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
-        fakeVibrator.setOffLatency(50); // Add latency so cancellation is slow.
-        fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
-        fakeVibrator.setSupportedEffects(VibrationEffect.EFFECT_CLICK);
-        VibratorManagerService service = createSystemReadyService();
-
-        VibrationEffect repeatingEffect = VibrationEffect.createWaveform(
-                new long[]{10, 10_000}, new int[]{255, 0}, 1);
-        vibrate(service, repeatingEffect, ALARM_ATTRS);
-
-        // VibrationThread will start this vibration async, wait until the off waveform step.
-        assertTrue(waitUntil(s -> fakeVibrator.getOffCount() > 0, service, TEST_TIMEOUT_MILLIS));
-
-        // Cancel vibration right before requesting a new one.
-        // This should trigger slow IVibrator.off before setting the vibration status to cancelled.
-        cancelVibrate(service);
-        vibrateAndWaitUntilFinished(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK),
-                ALARM_ATTRS);
-
-        // Check that second vibration was played.
-        assertTrue(fakeVibrator.getAllEffectSegments().stream()
-                .anyMatch(PrebakedSegment.class::isInstance));
-    }
-
-    @Test
-    public void vibrate_withNewSameImportanceVibrationAndBothRepeating_cancelsOngoingEffect()
-            throws Exception {
-        mockVibrators(1);
-        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
-        VibratorManagerService service = createSystemReadyService();
-
-        VibrationEffect repeatingEffect = VibrationEffect.createWaveform(
-                new long[]{10_000, 10_000}, new int[]{128, 255}, 1);
-        vibrate(service, repeatingEffect, ALARM_ATTRS);
-
-        // VibrationThread will start this vibration async, wait until it has started.
-        assertTrue(waitUntil(s -> !mVibratorProviders.get(1).getAllEffectSegments().isEmpty(),
-                service, TEST_TIMEOUT_MILLIS));
-
-        VibrationEffect repeatingEffect2 = VibrationEffect.createWaveform(
-                new long[]{10_000, 10_000}, new int[]{128, 255}, 1);
-        vibrate(service, repeatingEffect2, ALARM_ATTRS);
-
-        // VibrationThread will start this vibration async, so wait before checking it started.
-        assertTrue(waitUntil(s -> mVibratorProviders.get(1).getAllEffectSegments().size() == 2,
-                service, TEST_TIMEOUT_MILLIS));
-
-        // The second vibration should have recorded that the vibrators were turned on.
-        verify(mBatteryStatsMock, times(2)).noteVibratorOn(anyInt(), anyLong());
-
-        cancelVibrate(service);  // Clean up repeating effect.
-    }
-
-    @Test
-    public void vibrate_withNewSameImportanceVibrationButOngoingIsRepeating_ignoreNewVibration()
-            throws Exception {
-        mockVibrators(1);
-        FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
-        VibratorManagerService service = createSystemReadyService();
-
-        VibrationEffect repeatingEffect = VibrationEffect.createWaveform(
-                new long[]{10, 10_000}, new int[]{255, 0}, 1);
-        vibrate(service, repeatingEffect, ALARM_ATTRS);
-
-        // VibrationThread will start this vibration async, wait until the off waveform step.
-        assertTrue(waitUntil(s -> fakeVibrator.getOffCount() > 0, service, TEST_TIMEOUT_MILLIS));
-
-        vibrateAndWaitUntilFinished(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK),
-                ALARM_ATTRS);
-
-        // The second vibration shouldn't have recorded that the vibrators were turned on.
-        verify(mBatteryStatsMock, times(1)).noteVibratorOn(anyInt(), anyLong());
-        // The second vibration shouldn't have played any prebaked segment.
-        assertFalse(mVibratorProviders.get(1).getAllEffectSegments().stream()
-                .anyMatch(PrebakedSegment.class::isInstance));
-        cancelVibrate(service);  // Clean up long effect.
-    }
-
-    @Test
-    public void vibrate_withNewUnknownUsageVibrationAndRepeating_cancelsOngoingEffect()
-            throws Exception {
-        mockVibrators(1);
-        VibratorManagerService service = createSystemReadyService();
-
-        VibrationEffect repeatingEffect = VibrationEffect.createWaveform(
-                new long[]{10_000, 10_000}, new int[]{128, 255}, 1);
-        vibrate(service, repeatingEffect, ALARM_ATTRS);
-
-        // VibrationThread will start this vibration async, wait until it has started.
-        assertTrue(waitUntil(s -> !mVibratorProviders.get(1).getAllEffectSegments().isEmpty(),
-                service, TEST_TIMEOUT_MILLIS));
-
-        VibrationEffect repeatingEffect2 = VibrationEffect.createWaveform(
-                new long[]{10_000, 10_000}, new int[]{128, 255}, 1);
-        vibrate(service, repeatingEffect2, UNKNOWN_ATTRS);
-
-        // VibrationThread will start this vibration async, so wait before checking it started.
-        assertTrue(waitUntil(s -> mVibratorProviders.get(1).getAllEffectSegments().size() == 2,
-                service, TEST_TIMEOUT_MILLIS));
-
-        // The second vibration should have recorded that the vibrators were turned on.
-        verify(mBatteryStatsMock, times(2)).noteVibratorOn(anyInt(), anyLong());
-
-        cancelVibrate(service);  // Clean up repeating effect.
-    }
-
-    @Test
-    public void vibrate_withNewUnknownUsageVibrationAndNotRepeating_ignoreNewVibration()
-            throws Exception {
-        mockVibrators(1);
-        FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
-        VibratorManagerService service = createSystemReadyService();
-
-        VibrationEffect alarmEffect = VibrationEffect.createWaveform(
-                new long[]{10, 10_000}, new int[]{255, 0}, -1);
-        vibrate(service, alarmEffect, ALARM_ATTRS);
-
-        // VibrationThread will start this vibration async, wait until the off waveform step.
-        assertTrue(waitUntil(s -> fakeVibrator.getOffCount() > 0, service, TEST_TIMEOUT_MILLIS));
-
-        vibrateAndWaitUntilFinished(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK),
-                UNKNOWN_ATTRS);
-
-        // The second vibration shouldn't have recorded that the vibrators were turned on.
-        verify(mBatteryStatsMock, times(1)).noteVibratorOn(anyInt(), anyLong());
-        // The second vibration shouldn't have played any prebaked segment.
-        assertFalse(mVibratorProviders.get(1).getAllEffectSegments().stream()
-                .anyMatch(PrebakedSegment.class::isInstance));
-        cancelVibrate(service);  // Clean up long effect.
-    }
-
-    @Test
-    public void vibrate_withOngoingHigherImportanceVibration_ignoresEffect() throws Exception {
-        mockVibrators(1);
-        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
-        VibratorManagerService service = createSystemReadyService();
-
-        VibrationEffect effect = VibrationEffect.createWaveform(
-                new long[]{10_000, 10_000}, new int[]{128, 255}, -1);
-        vibrate(service, effect, ALARM_ATTRS);
-
-        // VibrationThread will start this vibration async, so wait before checking it started.
-        assertTrue(waitUntil(s -> !mVibratorProviders.get(1).getAllEffectSegments().isEmpty(),
-                service, TEST_TIMEOUT_MILLIS));
-
-        vibrateAndWaitUntilFinished(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK),
-                HAPTIC_FEEDBACK_ATTRS);
-
-        // The second vibration shouldn't have recorded that the vibrators were turned on.
-        verify(mBatteryStatsMock, times(1)).noteVibratorOn(anyInt(), anyLong());
-        // The second vibration shouldn't have played any prebaked segment.
-        assertFalse(mVibratorProviders.get(1).getAllEffectSegments().stream()
-                .anyMatch(PrebakedSegment.class::isInstance));
-        cancelVibrate(service);  // Clean up long effect.
-    }
-
-    @Test
-    public void vibrate_withOngoingLowerImportanceVibration_cancelsOngoingEffect()
-            throws Exception {
-        mockVibrators(1);
-        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
-        mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
-        VibratorManagerService service = createSystemReadyService();
-
-        VibrationEffect effect = VibrationEffect.createWaveform(
-                new long[]{10_000, 10_000}, new int[]{128, 255}, -1);
-        vibrate(service, effect, HAPTIC_FEEDBACK_ATTRS);
-
-        // VibrationThread will start this vibration async, so wait before checking it started.
-        assertTrue(waitUntil(s -> !mVibratorProviders.get(1).getAllEffectSegments().isEmpty(),
-                service, TEST_TIMEOUT_MILLIS));
-
-        vibrateAndWaitUntilFinished(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK),
-                RINGTONE_ATTRS);
-
-        // The second vibration should have recorded that the vibrators were turned on.
-        verify(mBatteryStatsMock, times(2)).noteVibratorOn(anyInt(), anyLong());
-        // One segment played is the prebaked CLICK from the second vibration.
-        assertEquals(1,
-                mVibratorProviders.get(1).getAllEffectSegments().stream()
-                        .filter(PrebakedSegment.class::isInstance)
-                        .count());
-    }
-
-    @Test
-    public void vibrate_withOngoingLowerImportanceExternalVibration_cancelsOngoingVibration()
-            throws Exception {
-        mockVibrators(1);
-        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
-        mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
-        setRingerMode(AudioManager.RINGER_MODE_NORMAL);
-        VibratorManagerService service = createSystemReadyService();
-
-        IBinder firstToken = mock(IBinder.class);
-        IExternalVibrationController controller = mock(IExternalVibrationController.class);
-        ExternalVibration externalVibration = new ExternalVibration(UID, PACKAGE_NAME,
-                AUDIO_ALARM_ATTRS,
-                controller, firstToken);
-        int scale = mExternalVibratorService.onExternalVibrationStart(externalVibration);
-
-        vibrateAndWaitUntilFinished(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK),
-                RINGTONE_ATTRS);
-
-        assertNotEquals(IExternalVibratorService.SCALE_MUTE, scale);
-        // The external vibration should have been cancelled
-        verify(controller).mute();
-        assertEquals(Arrays.asList(false, true, false),
-                mVibratorProviders.get(1).getExternalControlStates());
-        // The new vibration should have recorded that the vibrators were turned on.
-        verify(mBatteryStatsMock, times(1)).noteVibratorOn(anyInt(), anyLong());
-        // One segment played is the prebaked CLICK from the new vibration.
-        assertEquals(1,
-                mVibratorProviders.get(1).getAllEffectSegments().stream()
-                        .filter(PrebakedSegment.class::isInstance)
-                        .count());
-    }
-
-    @Test
-    public void vibrate_withOngoingSameImportancePipelinedVibration_continuesOngoingEffect()
-            throws Exception {
-        VibrationAttributes pipelineAttrs = new VibrationAttributes.Builder(HAPTIC_FEEDBACK_ATTRS)
-                .setFlags(VibrationAttributes.FLAG_PIPELINED_EFFECT)
-                .build();
-
-        mockVibrators(1);
-        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
-        mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
-        VibratorManagerService service = createSystemReadyService();
-
-        VibrationEffect effect = VibrationEffect.createWaveform(
-                new long[]{100, 100}, new int[]{128, 255}, -1);
-        vibrate(service, effect, pipelineAttrs);
-        // This vibration will be enqueued, but evicted by the EFFECT_CLICK.
-        vibrate(service, VibrationEffect.startComposition()
-                .addOffDuration(Duration.ofSeconds(10))
-                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_SLOW_RISE)
-                .compose(), pipelineAttrs);  // This will queue and be evicted for the click.
-
-        vibrateAndWaitUntilFinished(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK),
-                pipelineAttrs);
-
-        // The second vibration should have recorded that the vibrators were turned on.
-        verify(mBatteryStatsMock, times(2)).noteVibratorOn(anyInt(), anyLong());
-        // One step segment (with several amplitudes) and one click should have played. Notably
-        // there is no primitive segment.
-        List<VibrationEffectSegment> played = mVibratorProviders.get(1).getAllEffectSegments();
-        assertEquals(2, played.size());
-        assertEquals(1, played.stream().filter(StepSegment.class::isInstance).count());
-        assertEquals(1, played.stream().filter(PrebakedSegment.class::isInstance).count());
-    }
-
-    @Test
-    public void vibrate_withInputDevices_vibratesInputDevices() throws Exception {
-        mockVibrators(1);
-        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
-        mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
-        when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1});
-        when(mIInputManagerMock.getVibratorIds(eq(1))).thenReturn(new int[]{1});
-        when(mIInputManagerMock.getInputDevice(eq(1))).thenReturn(createInputDeviceWithVibrator(1));
-        setUserSetting(Settings.System.VIBRATE_INPUT_DEVICES, 1);
-        // Mock alarm intensity equals to default value to avoid scaling in this test.
-        setUserSetting(Settings.System.ALARM_VIBRATION_INTENSITY,
-                mVibrator.getDefaultVibrationIntensity(VibrationAttributes.USAGE_ALARM));
-        VibratorManagerService service = createSystemReadyService();
-
-        CombinedVibration effect = CombinedVibration.createParallel(
-                VibrationEffect.createOneShot(10, 10));
-        vibrateAndWaitUntilFinished(service, effect, ALARM_ATTRS);
-
-        verify(mIInputManagerMock).vibrateCombined(eq(1), eq(effect), any());
-        assertTrue(mVibratorProviders.get(1).getAllEffectSegments().isEmpty());
-    }
-
-    @Test
-    public void vibrate_withNativeCallbackTriggered_finishesVibration() throws Exception {
-        mockVibrators(1);
-        mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
-        VibratorManagerService service = createSystemReadyService();
-        // The native callback will be dispatched manually in this test.
-        mTestLooper.stopAutoDispatchAndIgnoreExceptions();
-
-        vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), ALARM_ATTRS);
-
-        // VibrationThread will start this vibration async, so wait before triggering callbacks.
-        assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
-
-        // Trigger callbacks from controller.
-        mTestLooper.moveTimeForward(50);
-        mTestLooper.dispatchAll();
-
-        // VibrationThread needs some time to react to native callbacks and stop the vibrator.
-        assertTrue(waitUntil(s -> !s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
-    }
-
-    @Test
-    public void vibrate_withTriggerCallback_finishesVibration() throws Exception {
-        mockCapabilities(IVibratorManager.CAP_SYNC, IVibratorManager.CAP_PREPARE_COMPOSE);
-        mockVibrators(1, 2);
-        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
-        mVibratorProviders.get(1).setSupportedPrimitives(
-                VibrationEffect.Composition.PRIMITIVE_CLICK);
-        mVibratorProviders.get(2).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
-        mVibratorProviders.get(2).setSupportedPrimitives(
-                VibrationEffect.Composition.PRIMITIVE_CLICK);
-        // Mock alarm intensity equals to default value to avoid scaling in this test.
-        setUserSetting(Settings.System.ALARM_VIBRATION_INTENSITY,
-                mVibrator.getDefaultVibrationIntensity(VibrationAttributes.USAGE_ALARM));
-        VibratorManagerService service = createSystemReadyService();
-        // The native callback will be dispatched manually in this test.
-        mTestLooper.stopAutoDispatchAndIgnoreExceptions();
-
-        ArgumentCaptor<VibratorManagerService.OnSyncedVibrationCompleteListener> listenerCaptor =
-                ArgumentCaptor.forClass(
-                        VibratorManagerService.OnSyncedVibrationCompleteListener.class);
-        verify(mNativeWrapperMock).init(listenerCaptor.capture());
-
-        CountDownLatch triggerCountDown = new CountDownLatch(1);
-        // Mock trigger callback on registered listener right after the synced vibration starts.
-        when(mNativeWrapperMock.prepareSynced(eq(new int[]{1, 2}))).thenReturn(true);
-        when(mNativeWrapperMock.triggerSynced(anyLong())).then(answer -> {
-            listenerCaptor.getValue().onComplete(answer.getArgument(0));
-            triggerCountDown.countDown();
-            return true;
-        });
-
-        VibrationEffect composed = VibrationEffect.startComposition()
-                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 100)
-                .compose();
-        CombinedVibration effect = CombinedVibration.createParallel(composed);
-
-        vibrate(service, effect, ALARM_ATTRS);
-        // VibrationThread will start this vibration async, so wait until vibration is triggered.
-        triggerCountDown.await(TEST_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
-
-        verify(mNativeWrapperMock).prepareSynced(eq(new int[]{1, 2}));
-        verify(mNativeWrapperMock).triggerSynced(anyLong());
-        PrimitiveSegment expected = new PrimitiveSegment(
-                VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 100);
-        assertEquals(Arrays.asList(expected), mVibratorProviders.get(1).getAllEffectSegments());
-        assertEquals(Arrays.asList(expected), mVibratorProviders.get(2).getAllEffectSegments());
-
-        // VibrationThread needs some time to react to native callbacks and stop the vibrator.
-        assertTrue(waitUntil(s -> !s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
-    }
-
-    @Test
-    public void vibrate_withMultipleVibratorsAndCapabilities_prepareAndTriggerCalled()
-            throws Exception {
-        mockCapabilities(IVibratorManager.CAP_SYNC, IVibratorManager.CAP_PREPARE_PERFORM,
-                IVibratorManager.CAP_PREPARE_COMPOSE, IVibratorManager.CAP_MIXED_TRIGGER_PERFORM,
-                IVibratorManager.CAP_MIXED_TRIGGER_COMPOSE);
-        mockVibrators(1, 2);
-        when(mNativeWrapperMock.prepareSynced(eq(new int[]{1, 2}))).thenReturn(true);
-        when(mNativeWrapperMock.triggerSynced(anyLong())).thenReturn(true);
-        FakeVibratorControllerProvider fakeVibrator1 = mVibratorProviders.get(1);
-        fakeVibrator1.setSupportedEffects(VibrationEffect.EFFECT_CLICK);
-        mVibratorProviders.get(2).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
-        VibratorManagerService service = createSystemReadyService();
-
-        CombinedVibration effect = CombinedVibration.startParallel()
-                .addVibrator(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
-                .addVibrator(2, VibrationEffect.startComposition()
-                        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
-                        .compose())
-                .combine();
-        vibrateAndWaitUntilFinished(service, effect, ALARM_ATTRS);
-
-        verify(mNativeWrapperMock).prepareSynced(eq(new int[]{1, 2}));
-        verify(mNativeWrapperMock).triggerSynced(anyLong());
-        verify(mNativeWrapperMock).cancelSynced(); // Trigger on service creation only.
-    }
-
-    @Test
-    public void vibrate_withMultipleVibratorsWithoutCapabilities_skipPrepareAndTrigger()
-            throws Exception {
-        // Missing CAP_MIXED_TRIGGER_ON and CAP_MIXED_TRIGGER_PERFORM.
-        mockCapabilities(IVibratorManager.CAP_SYNC, IVibratorManager.CAP_PREPARE_ON,
-                IVibratorManager.CAP_PREPARE_PERFORM);
-        mockVibrators(1, 2);
-        FakeVibratorControllerProvider fakeVibrator1 = mVibratorProviders.get(1);
-        fakeVibrator1.setSupportedEffects(VibrationEffect.EFFECT_CLICK);
-        VibratorManagerService service = createSystemReadyService();
-
-        CombinedVibration effect = CombinedVibration.startParallel()
-                .addVibrator(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
-                .addVibrator(2, VibrationEffect.createOneShot(10, 100))
-                .combine();
-        vibrateAndWaitUntilFinished(service, effect, ALARM_ATTRS);
-
-        verify(mNativeWrapperMock, never()).prepareSynced(any());
-        verify(mNativeWrapperMock, never()).triggerSynced(anyLong());
-        verify(mNativeWrapperMock).cancelSynced(); // Trigger on service creation only.
-    }
-
-    @Test
-    public void vibrate_withMultipleVibratorsPrepareFailed_skipTrigger() throws Exception {
-        mockCapabilities(IVibratorManager.CAP_SYNC, IVibratorManager.CAP_PREPARE_ON);
-        mockVibrators(1, 2);
-        when(mNativeWrapperMock.prepareSynced(any())).thenReturn(false);
-        VibratorManagerService service = createSystemReadyService();
-
-        CombinedVibration effect = CombinedVibration.startParallel()
-                .addVibrator(1, VibrationEffect.createOneShot(10, 50))
-                .addVibrator(2, VibrationEffect.createOneShot(10, 100))
-                .combine();
-        vibrateAndWaitUntilFinished(service, effect, ALARM_ATTRS);
-
-        verify(mNativeWrapperMock).prepareSynced(eq(new int[]{1, 2}));
-        verify(mNativeWrapperMock, never()).triggerSynced(anyLong());
-        verify(mNativeWrapperMock).cancelSynced(); // Trigger on service creation only.
-    }
-
-    @Test
-    public void vibrate_withMultipleVibratorsTriggerFailed_cancelPreparedSynced() throws Exception {
-        mockCapabilities(IVibratorManager.CAP_SYNC, IVibratorManager.CAP_PREPARE_ON);
-        mockVibrators(1, 2);
-        when(mNativeWrapperMock.prepareSynced(eq(new int[]{1, 2}))).thenReturn(true);
-        when(mNativeWrapperMock.triggerSynced(anyLong())).thenReturn(false);
-        VibratorManagerService service = createSystemReadyService();
-
-        CombinedVibration effect = CombinedVibration.startParallel()
-                .addVibrator(1, VibrationEffect.createOneShot(10, 50))
-                .addVibrator(2, VibrationEffect.createOneShot(10, 100))
-                .combine();
-        vibrateAndWaitUntilFinished(service, effect, ALARM_ATTRS);
-
-        verify(mNativeWrapperMock).prepareSynced(eq(new int[]{1, 2}));
-        verify(mNativeWrapperMock).triggerSynced(anyLong());
-        verify(mNativeWrapperMock, times(2)).cancelSynced(); // Trigger on service creation too.
-    }
-
-    @Test
-    public void vibrate_withIntensitySettings_appliesSettingsToScaleVibrations() throws Exception {
-        int defaultNotificationIntensity =
-                mVibrator.getDefaultVibrationIntensity(VibrationAttributes.USAGE_NOTIFICATION);
-        setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
-                defaultNotificationIntensity < Vibrator.VIBRATION_INTENSITY_HIGH
-                        ? defaultNotificationIntensity + 1
-                        : defaultNotificationIntensity);
-
-        int defaultTouchIntensity =
-                mVibrator.getDefaultVibrationIntensity(VibrationAttributes.USAGE_TOUCH);
-        setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY,
-                defaultTouchIntensity > Vibrator.VIBRATION_INTENSITY_LOW
-                        ? defaultTouchIntensity - 1
-                        : defaultTouchIntensity);
-
-        setUserSetting(Settings.System.ALARM_VIBRATION_INTENSITY,
-                mVibrator.getDefaultVibrationIntensity(VibrationAttributes.USAGE_ALARM));
-        setUserSetting(Settings.System.MEDIA_VIBRATION_INTENSITY,
-                Vibrator.VIBRATION_INTENSITY_HIGH);
-        setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, Vibrator.VIBRATION_INTENSITY_OFF);
-
-        mockVibrators(1);
-        FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
-        fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL,
-                IVibrator.CAP_COMPOSE_EFFECTS);
-        fakeVibrator.setSupportedPrimitives(VibrationEffect.Composition.PRIMITIVE_CLICK,
-                VibrationEffect.Composition.PRIMITIVE_TICK);
-        VibratorManagerService service = createSystemReadyService();
-
-        vibrateAndWaitUntilFinished(service, VibrationEffect.startComposition()
-                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f)
-                .compose(), HAPTIC_FEEDBACK_ATTRS);
-
-        vibrateAndWaitUntilFinished(service, CombinedVibration.startSequential()
-                .addNext(1, VibrationEffect.createOneShot(100, 125))
-                .combine(), NOTIFICATION_ATTRS);
-
-        vibrateAndWaitUntilFinished(service, VibrationEffect.startComposition()
-                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f)
-                .compose(), ALARM_ATTRS);
-
-        // Ring vibrations have intensity OFF and are not played.
-        vibrateAndWaitUntilFinished(service, VibrationEffect.createOneShot(100, 125),
-                RINGTONE_ATTRS);
-
-        // Only 3 effects played successfully.
-        assertEquals(3, fakeVibrator.getAllEffectSegments().size());
-
-        // Haptic feedback vibrations will be scaled with SCALE_LOW or none if default is low.
-        assertEquals(defaultTouchIntensity > Vibrator.VIBRATION_INTENSITY_LOW,
-                0.5 > ((PrimitiveSegment) fakeVibrator.getAllEffectSegments().get(0)).getScale());
-
-        // Notification vibrations will be scaled with SCALE_HIGH or none if default is high.
-        assertEquals(defaultNotificationIntensity < Vibrator.VIBRATION_INTENSITY_HIGH,
-                0.6 < fakeVibrator.getAmplitudes().get(0));
-
-        // Alarm vibration will be scaled with SCALE_NONE.
-        assertEquals(1f,
-                ((PrimitiveSegment) fakeVibrator.getAllEffectSegments().get(2)).getScale(), 1e-5);
-    }
-
-    @Test
-    public void vibrate_withPowerModeChange_cancelVibrationIfNotAllowed() throws Exception {
-        mockVibrators(1, 2);
-        VibratorManagerService service = createSystemReadyService();
-        vibrate(service,
-                CombinedVibration.startParallel()
-                        .addVibrator(1, VibrationEffect.createOneShot(1000, 100))
-                        .combine(),
-                HAPTIC_FEEDBACK_ATTRS);
-
-        // VibrationThread will start this vibration async, so wait until vibration is triggered.
-        assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
-
-        mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE);
-
-        // Haptic feedback cancelled on low power mode.
-        assertTrue(waitUntil(s -> !s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
-    }
-
-    @Test
-    public void vibrate_withSettingsChange_doNotCancelVibration() throws Exception {
-        mockVibrators(1);
-        VibratorManagerService service = createSystemReadyService();
-
-        vibrate(service, VibrationEffect.createOneShot(1000, 100), HAPTIC_FEEDBACK_ATTRS);
-
-        // VibrationThread will start this vibration async, so wait until vibration is triggered.
-        assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
-
-        service.updateServiceState();
-
-        // Vibration is not stopped nearly after updating service.
-        assertFalse(waitUntil(s -> !s.isVibrating(1), service, 50));
-        cancelVibrate(service);
-    }
-
-    @Test
-    public void vibrate_withVirtualDisplayChange_ignoreVibrationFromVirtualDisplay()
-            throws Exception {
-        mockVibrators(1);
-        VibratorManagerService service = createSystemReadyService();
-        mRegisteredVirtualDisplayListener.onVirtualDisplayCreated(VIRTUAL_DISPLAY_ID);
-
-        vibrateWithDisplay(service,
-                VIRTUAL_DISPLAY_ID,
-                CombinedVibration.startParallel()
-                        .addVibrator(1, VibrationEffect.createOneShot(1000, 100))
-                        .combine(),
-                HAPTIC_FEEDBACK_ATTRS);
-
-        // Haptic feedback ignored when it's from a virtual display.
-        assertFalse(waitUntil(s -> s.isVibrating(1), service, /* timeout= */ 50));
-
-        mRegisteredVirtualDisplayListener.onVirtualDisplayRemoved(VIRTUAL_DISPLAY_ID);
-        vibrateWithDisplay(service,
-                VIRTUAL_DISPLAY_ID,
-                CombinedVibration.startParallel()
-                        .addVibrator(1, VibrationEffect.createOneShot(1000, 100))
-                        .combine(),
-                HAPTIC_FEEDBACK_ATTRS);
-        // Haptic feedback played normally when the virtual display is removed.
-        assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
-
-        cancelVibrate(service);  // Clean up long-ish effect.
-    }
-
-    @Test
-    public void vibrate_withAppsOnVirtualDisplayChange_ignoreVibrationFromVirtualDisplay()
-            throws Exception {
-        mockVibrators(1);
-        VibratorManagerService service = createSystemReadyService();
-        mRegisteredAppsOnVirtualDeviceListener.onAppsOnAnyVirtualDeviceChanged(
-                new ArraySet<>(Arrays.asList(UID)));
-        vibrateWithDisplay(service,
-                Display.INVALID_DISPLAY,
-                CombinedVibration.startParallel()
-                        .addVibrator(1, VibrationEffect.createOneShot(1000, 100))
-                        .combine(),
-                HAPTIC_FEEDBACK_ATTRS);
-
-        // Haptic feedback ignored when it's from an app running virtual display.
-        assertFalse(waitUntil(s -> s.isVibrating(1), service, /* timeout= */ 50));
-
-        mRegisteredAppsOnVirtualDeviceListener.onAppsOnAnyVirtualDeviceChanged(new ArraySet<>());
-        vibrateWithDisplay(service,
-                Display.INVALID_DISPLAY,
-                CombinedVibration.startParallel()
-                        .addVibrator(1, VibrationEffect.createOneShot(1000, 100))
-                        .combine(),
-                HAPTIC_FEEDBACK_ATTRS);
-        // Haptic feedback played normally when the same app no long runs on a virtual display.
-        assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
-        cancelVibrate(service);  // Clean up long-ish effect.
-    }
-
-    @Test
-    public void vibrate_prebakedAndComposedVibrationsWithFallbacks_playsFallbackOnlyForPredefined()
-            throws Exception {
-        mockVibrators(1);
-        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
-        mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
-        mVibratorProviders.get(1).setSupportedPrimitives(
-                VibrationEffect.Composition.PRIMITIVE_CLICK);
-
-        VibratorManagerService service = createSystemReadyService();
-        vibrateAndWaitUntilFinished(service,
-                VibrationEffect.startComposition()
-                        .addEffect(VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK))
-                        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK)
-                        .addEffect(VibrationEffect.createPredefined(VibrationEffect.EFFECT_TICK))
-                        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
-                        .compose(),
-                ALARM_ATTRS);
-
-        List<VibrationEffectSegment> segments = mVibratorProviders.get(1).getAllEffectSegments();
-        // At least one step segment played as fallback for unusupported vibration effect
-        assertTrue(segments.size() > 2);
-        // 0: Supported effect played
-        assertTrue(segments.get(0) instanceof PrebakedSegment);
-        // 1: No segment for unsupported primitive
-        // 2: One or more intermediate step segments as fallback for unsupported effect
-        for (int i = 1; i < segments.size() - 1; i++) {
-            assertTrue(segments.get(i) instanceof StepSegment);
-        }
-        // 3: Supported primitive played
-        assertTrue(segments.get(segments.size() - 1) instanceof PrimitiveSegment);
-    }
-
-    @Test
-    public void cancelVibrate_withoutUsageFilter_stopsVibrating() throws Exception {
-        mockVibrators(1);
-        VibratorManagerService service = createSystemReadyService();
-
-        service.cancelVibrate(VibrationAttributes.USAGE_FILTER_MATCH_ALL, service);
-        assertFalse(service.isVibrating(1));
-
-        vibrate(service, VibrationEffect.createOneShot(10 * TEST_TIMEOUT_MILLIS, 100), ALARM_ATTRS);
-
-        // VibrationThread will start this vibration async, so wait until vibration is triggered.
-        assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
-
-        service.cancelVibrate(VibrationAttributes.USAGE_FILTER_MATCH_ALL, service);
-
-        // Alarm cancelled on filter match all.
-        assertTrue(waitUntil(s -> !s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
-    }
-
-    @Test
-    public void cancelVibrate_withFilter_onlyCancelsVibrationWithFilteredUsage() throws Exception {
-        mockVibrators(1);
-        VibratorManagerService service = createSystemReadyService();
-
-        vibrate(service, VibrationEffect.createOneShot(10 * TEST_TIMEOUT_MILLIS, 100), ALARM_ATTRS);
-
-        // VibrationThread will start this vibration async, so wait until vibration is triggered.
-        assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
-
-        // Vibration is not cancelled with a different usage.
-        service.cancelVibrate(VibrationAttributes.USAGE_RINGTONE, service);
-        assertFalse(waitUntil(s -> !s.isVibrating(1), service, /* timeout= */ 50));
-
-        // Vibration is not cancelled with a different usage class used as filter.
-        service.cancelVibrate(
-                VibrationAttributes.USAGE_CLASS_FEEDBACK | ~VibrationAttributes.USAGE_CLASS_MASK,
-                service);
-        assertFalse(waitUntil(s -> !s.isVibrating(1), service, /* timeout= */ 50));
-
-        // Vibration is cancelled with usage class as filter.
-        service.cancelVibrate(
-                VibrationAttributes.USAGE_CLASS_ALARM | ~VibrationAttributes.USAGE_CLASS_MASK,
-                service);
-        assertTrue(waitUntil(s -> !s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
-    }
-
-    @Test
-    public void cancelVibrate_withoutUnknownUsage_onlyStopsIfFilteringUnknownOrAllUsages()
-            throws Exception {
-        mockVibrators(1);
-        VibrationAttributes attrs = new VibrationAttributes.Builder()
-                .setUsage(VibrationAttributes.USAGE_UNKNOWN)
-                .build();
-        VibratorManagerService service = createSystemReadyService();
-
-        vibrate(service, VibrationEffect.createOneShot(10 * TEST_TIMEOUT_MILLIS, 100), attrs);
-
-        // VibrationThread will start this vibration async, so wait until vibration is triggered.
-        assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
-
-        // Do not cancel UNKNOWN vibration when filter is being applied for other usages.
-        service.cancelVibrate(VibrationAttributes.USAGE_RINGTONE, service);
-        assertFalse(waitUntil(s -> !s.isVibrating(1), service, /* timeout= */ 50));
-
-        service.cancelVibrate(
-                VibrationAttributes.USAGE_CLASS_ALARM | ~VibrationAttributes.USAGE_CLASS_MASK,
-                service);
-        assertFalse(waitUntil(s -> !s.isVibrating(1), service, /* timeout= */ 50));
-
-        // Cancel UNKNOWN vibration when filtered for that vibration specifically.
-        service.cancelVibrate(VibrationAttributes.USAGE_UNKNOWN, service);
-        assertTrue(waitUntil(s -> !s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
-
-        vibrate(service, VibrationEffect.createOneShot(10 * TEST_TIMEOUT_MILLIS, 100), attrs);
-
-        // VibrationThread will start this vibration async, so wait until vibration is triggered.
-        assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
-
-        // Cancel UNKNOWN vibration when all vibrations are being cancelled.
-        service.cancelVibrate(VibrationAttributes.USAGE_FILTER_MATCH_ALL, service);
-        assertTrue(waitUntil(s -> !s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
-    }
-
-    @Test
-    public void onExternalVibration_ignoreVibrationFromVirtualDevices() throws Exception {
-        mockVibrators(1);
-        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
-        createSystemReadyService();
-
-        IBinder binderToken = mock(IBinder.class);
-        ExternalVibration externalVibration = new ExternalVibration(UID, PACKAGE_NAME,
-                AUDIO_ALARM_ATTRS,
-                mock(IExternalVibrationController.class), binderToken);
-        int scale = mExternalVibratorService.onExternalVibrationStart(externalVibration);
-        assertNotEquals(IExternalVibratorService.SCALE_MUTE, scale);
-
-        mRegisteredAppsOnVirtualDeviceListener.onAppsOnAnyVirtualDeviceChanged(
-                new ArraySet<>(Arrays.asList(UID)));
-        scale = mExternalVibratorService.onExternalVibrationStart(externalVibration);
-        assertEquals(IExternalVibratorService.SCALE_MUTE, scale);
-    }
-
-    @Test
-    public void onExternalVibration_setsExternalControl() throws Exception {
-        mockVibrators(1);
-        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
-        createSystemReadyService();
-
-        IBinder binderToken = mock(IBinder.class);
-        ExternalVibration externalVibration = new ExternalVibration(UID, PACKAGE_NAME,
-                AUDIO_ALARM_ATTRS,
-                mock(IExternalVibrationController.class), binderToken);
-        int scale = mExternalVibratorService.onExternalVibrationStart(externalVibration);
-        mExternalVibratorService.onExternalVibrationStop(externalVibration);
-
-        assertNotEquals(IExternalVibratorService.SCALE_MUTE, scale);
-        assertEquals(Arrays.asList(false, true, false),
-                mVibratorProviders.get(1).getExternalControlStates());
-
-        verify(binderToken).linkToDeath(any(), eq(0));
-        verify(binderToken).unlinkToDeath(any(), eq(0));
-    }
-
-    @Test
-    public void onExternalVibration_withOngoingExternalVibration_mutesPreviousVibration()
-            throws Exception {
-        mockVibrators(1);
-        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
-        setRingerMode(AudioManager.RINGER_MODE_NORMAL);
-        createSystemReadyService();
-
-        IBinder firstToken = mock(IBinder.class);
-        IBinder secondToken = mock(IBinder.class);
-        IExternalVibrationController firstController = mock(IExternalVibrationController.class);
-        IExternalVibrationController secondController = mock(IExternalVibrationController.class);
-        ExternalVibration firstVibration = new ExternalVibration(UID, PACKAGE_NAME,
-                AUDIO_ALARM_ATTRS,
-                firstController, firstToken);
-        int firstScale = mExternalVibratorService.onExternalVibrationStart(firstVibration);
-
-        AudioAttributes ringtoneAudioAttrs = new AudioAttributes.Builder()
-                .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
-                .build();
-        ExternalVibration secondVibration = new ExternalVibration(UID, PACKAGE_NAME,
-                ringtoneAudioAttrs, secondController, secondToken);
-        int secondScale = mExternalVibratorService.onExternalVibrationStart(secondVibration);
-
-        assertNotEquals(IExternalVibratorService.SCALE_MUTE, firstScale);
-        assertNotEquals(IExternalVibratorService.SCALE_MUTE, secondScale);
-        verify(firstController).mute();
-        verify(secondController, never()).mute();
-        // Set external control called only once.
-        assertEquals(Arrays.asList(false, true),
-                mVibratorProviders.get(1).getExternalControlStates());
-
-        mExternalVibratorService.onExternalVibrationStop(secondVibration);
-        mExternalVibratorService.onExternalVibrationStop(firstVibration);
-        assertEquals(Arrays.asList(false, true, false),
-                mVibratorProviders.get(1).getExternalControlStates());
-
-        verify(firstToken).linkToDeath(any(), eq(0));
-        verify(firstToken).unlinkToDeath(any(), eq(0));
-
-        verify(secondToken).linkToDeath(any(), eq(0));
-        verify(secondToken).unlinkToDeath(any(), eq(0));
-    }
-
-    @Test
-    public void onExternalVibration_withOngoingVibration_cancelsOngoingVibrationImmediately()
-            throws Exception {
-        mockVibrators(1);
-        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL,
-                IVibrator.CAP_AMPLITUDE_CONTROL);
-        VibratorManagerService service = createSystemReadyService();
-
-        VibrationEffect effect = VibrationEffect.createOneShot(10 * TEST_TIMEOUT_MILLIS, 100);
-        vibrate(service, effect, HAPTIC_FEEDBACK_ATTRS);
-
-        // VibrationThread will start this vibration async, so wait until vibration is triggered.
-        assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
-
-        ExternalVibration externalVibration = new ExternalVibration(UID, PACKAGE_NAME,
-                AUDIO_ALARM_ATTRS,
-                mock(IExternalVibrationController.class));
-        int scale = mExternalVibratorService.onExternalVibrationStart(externalVibration);
-        assertNotEquals(IExternalVibratorService.SCALE_MUTE, scale);
-
-        // Vibration is cancelled.
-        assertTrue(waitUntil(s -> !s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
-        assertEquals(Arrays.asList(false, true),
-                mVibratorProviders.get(1).getExternalControlStates());
-    }
-
-    @Test
-    public void onExternalVibration_withOngoingHigherImportanceVibration_ignoreNewVibration()
-            throws Exception {
-        mockVibrators(1);
-        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL,
-                IVibrator.CAP_AMPLITUDE_CONTROL);
-        VibratorManagerService service = createSystemReadyService();
-
-        VibrationEffect effect = VibrationEffect.createOneShot(10 * TEST_TIMEOUT_MILLIS, 100);
-        vibrate(service, effect, RINGTONE_ATTRS);
-
-        // VibrationThread will start this vibration async, so wait until vibration is triggered.
-        assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
-
-        ExternalVibration externalVibration = new ExternalVibration(UID, PACKAGE_NAME,
-                AUDIO_ALARM_ATTRS,
-                mock(IExternalVibrationController.class));
-        int scale = mExternalVibratorService.onExternalVibrationStart(externalVibration);
-        // External vibration is ignored.
-        assertEquals(IExternalVibratorService.SCALE_MUTE, scale);
-
-        // Vibration is not cancelled.
-        assertFalse(waitUntil(s -> !s.isVibrating(1), service, CLEANUP_TIMEOUT_MILLIS));
-        assertEquals(Arrays.asList(false), mVibratorProviders.get(1).getExternalControlStates());
-        cancelVibrate(service);  // Clean up long effect.
-    }
-
-    @Test
-    public void onExternalVibration_withNewSameImportanceButRepeating_cancelsOngoingVibration()
-            throws Exception {
-        mockVibrators(1);
-        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL,
-                IVibrator.CAP_AMPLITUDE_CONTROL);
-        VibratorManagerService service = createSystemReadyService();
-
-        VibrationEffect repeatingEffect = VibrationEffect.createWaveform(
-                new long[]{10, 10_000}, new int[]{255, 0}, 1);
-        vibrate(service, repeatingEffect, ALARM_ATTRS);
-
-        // VibrationThread will start this vibration async, so wait until vibration is triggered.
-        assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
-
-        ExternalVibration externalVibration = new ExternalVibration(UID, PACKAGE_NAME,
-                AUDIO_ALARM_ATTRS, mock(IExternalVibrationController.class));
-        int scale = mExternalVibratorService.onExternalVibrationStart(externalVibration);
-        assertNotEquals(IExternalVibratorService.SCALE_MUTE, scale);
-
-        // Vibration is cancelled.
-        assertTrue(waitUntil(s -> !s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
-        assertEquals(Arrays.asList(false, true),
-                mVibratorProviders.get(1).getExternalControlStates());
-    }
-
-    @Test
-    public void onExternalVibration_withNewSameImportanceButOngoingIsRepeating_ignoreNewVibration()
-            throws Exception {
-        mockVibrators(1);
-        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL,
-                IVibrator.CAP_AMPLITUDE_CONTROL);
-        VibratorManagerService service = createSystemReadyService();
-
-        VibrationEffect repeatingEffect = VibrationEffect.createWaveform(
-                new long[]{10_000, 10_000}, new int[]{128, 255}, 1);
-        vibrate(service, repeatingEffect, NOTIFICATION_ATTRS);
-
-        // VibrationThread will start this vibration async, so wait until vibration is triggered.
-        assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
-
-        ExternalVibration externalVibration = new ExternalVibration(UID, PACKAGE_NAME,
-                AUDIO_NOTIFICATION_ATTRS,
-                mock(IExternalVibrationController.class));
-        int scale = mExternalVibratorService.onExternalVibrationStart(externalVibration);
-        // New vibration is ignored.
-        assertEquals(IExternalVibratorService.SCALE_MUTE, scale);
-
-        // Vibration is not cancelled.
-        assertFalse(waitUntil(s -> !s.isVibrating(1), service, CLEANUP_TIMEOUT_MILLIS));
-        assertEquals(Arrays.asList(false), mVibratorProviders.get(1).getExternalControlStates());
-        cancelVibrate(service);  // Clean up long effect.
-    }
-
-    @Test
-    public void onExternalVibration_withRingtone_usesRingerModeSettings() {
-        mockVibrators(1);
-        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
-        AudioAttributes audioAttrs = new AudioAttributes.Builder()
-                .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
-                .build();
-        ExternalVibration externalVibration = new ExternalVibration(UID, PACKAGE_NAME, audioAttrs,
-                mock(IExternalVibrationController.class));
-
-        setRingerMode(AudioManager.RINGER_MODE_SILENT);
-        createSystemReadyService();
-        int scale = mExternalVibratorService.onExternalVibrationStart(externalVibration);
-        assertEquals(IExternalVibratorService.SCALE_MUTE, scale);
-
-        setRingerMode(AudioManager.RINGER_MODE_NORMAL);
-        createSystemReadyService();
-        scale = mExternalVibratorService.onExternalVibrationStart(externalVibration);
-        assertNotEquals(IExternalVibratorService.SCALE_MUTE, scale);
-
-        setRingerMode(AudioManager.RINGER_MODE_VIBRATE);
-        createSystemReadyService();
-        scale = mExternalVibratorService.onExternalVibrationStart(externalVibration);
-        assertNotEquals(IExternalVibratorService.SCALE_MUTE, scale);
-    }
-
-    @Test
-    public void onExternalVibration_withBypassMuteAudioFlag_ignoresUserSettings() {
-        mockVibrators(1);
-        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
-        setUserSetting(Settings.System.ALARM_VIBRATION_INTENSITY,
-                Vibrator.VIBRATION_INTENSITY_OFF);
-        AudioAttributes audioAttrs = new AudioAttributes.Builder()
-                .setUsage(AudioAttributes.USAGE_ALARM)
-                .build();
-        AudioAttributes flaggedAudioAttrs = new AudioAttributes.Builder()
-                .setUsage(AudioAttributes.USAGE_ALARM)
-                .setFlags(AudioAttributes.FLAG_BYPASS_MUTE)
-                .build();
-        createSystemReadyService();
-
-        int scale = mExternalVibratorService.onExternalVibrationStart(
-                new ExternalVibration(UID, PACKAGE_NAME, audioAttrs,
-                        mock(IExternalVibrationController.class)));
-        assertEquals(IExternalVibratorService.SCALE_MUTE, scale);
-
-        createSystemReadyService();
-        scale = mExternalVibratorService.onExternalVibrationStart(
-                new ExternalVibration(UID, PACKAGE_NAME, flaggedAudioAttrs,
-                        mock(IExternalVibrationController.class)));
-        assertNotEquals(IExternalVibratorService.SCALE_MUTE, scale);
-    }
-
-    @Test
-    public void onExternalVibration_withUnknownUsage_appliesMediaSettings() {
-        mockVibrators(1);
-        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
-        setUserSetting(Settings.System.MEDIA_VIBRATION_INTENSITY,
-                Vibrator.VIBRATION_INTENSITY_OFF);
-        AudioAttributes flaggedAudioAttrs = new AudioAttributes.Builder()
-                .setUsage(AudioAttributes.USAGE_UNKNOWN)
-                .setFlags(AudioAttributes.FLAG_BYPASS_MUTE)
-                .build();
-        createSystemReadyService();
-
-        int scale = mExternalVibratorService.onExternalVibrationStart(
-                new ExternalVibration(/* uid= */ 123, PACKAGE_NAME, flaggedAudioAttrs,
-                        mock(IExternalVibrationController.class)));
-        assertEquals(IExternalVibratorService.SCALE_MUTE, scale);
-    }
-
-    @Test
-    public void frameworkStats_externalVibration_reportsAllMetrics() throws Exception {
-        mockVibrators(1);
-        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
-        createSystemReadyService();
-
-        AudioAttributes audioAttrs = new AudioAttributes.Builder()
-                .setUsage(AudioAttributes.USAGE_ALARM)
-                .build();
-
-        ExternalVibration vib = new ExternalVibration(UID, PACKAGE_NAME, audioAttrs,
-                mock(IExternalVibrationController.class));
-        mExternalVibratorService.onExternalVibrationStart(vib);
-
-        Thread.sleep(10);
-        mExternalVibratorService.onExternalVibrationStop(vib);
-
-        ArgumentCaptor<VibrationStats.StatsInfo> argumentCaptor =
-                ArgumentCaptor.forClass(VibrationStats.StatsInfo.class);
-        verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS))
-                .writeVibrationReportedAsync(argumentCaptor.capture());
-
-        VibrationStats.StatsInfo statsInfo = argumentCaptor.getValue();
-        assertEquals(UID, statsInfo.uid);
-        assertEquals(FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__EXTERNAL,
-                statsInfo.vibrationType);
-        assertEquals(VibrationAttributes.USAGE_ALARM, statsInfo.usage);
-        assertEquals(Vibration.Status.FINISHED.getProtoEnumValue(), statsInfo.status);
-        assertTrue(statsInfo.totalDurationMillis > 0);
-        assertTrue(
-                "Expected vibrator ON for at least 10ms, got " + statsInfo.vibratorOnMillis + "ms",
-                statsInfo.vibratorOnMillis >= 10);
-        assertEquals(2, statsInfo.halSetExternalControlCount);
-    }
-
-    @Test
-    public void frameworkStats_waveformVibration_reportsAllMetrics() throws Exception {
-        mockVibrators(1);
-        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
-
-        VibratorManagerService service = createSystemReadyService();
-        vibrateAndWaitUntilFinished(service,
-                VibrationEffect.createWaveform(new long[] {0, 10, 20, 10}, -1), RINGTONE_ATTRS);
-
-        verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS))
-                .writeVibratorStateOnAsync(eq(UID), anyLong());
-        verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS))
-                .writeVibratorStateOffAsync(eq(UID));
-
-        ArgumentCaptor<VibrationStats.StatsInfo> argumentCaptor =
-                ArgumentCaptor.forClass(VibrationStats.StatsInfo.class);
-        verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS))
-                .writeVibrationReportedAsync(argumentCaptor.capture());
-
-        VibrationStats.StatsInfo metrics = argumentCaptor.getValue();
-        assertEquals(UID, metrics.uid);
-        assertEquals(FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__SINGLE,
-                metrics.vibrationType);
-        assertEquals(VibrationAttributes.USAGE_RINGTONE, metrics.usage);
-        assertEquals(Vibration.Status.FINISHED.getProtoEnumValue(), metrics.status);
-        assertTrue("Total duration was too low, " + metrics.totalDurationMillis + "ms",
-                metrics.totalDurationMillis >= 20);
-        assertTrue("Vibrator ON duration was too low, " + metrics.vibratorOnMillis + "ms",
-                metrics.vibratorOnMillis >= 20);
-
-        // All unrelated metrics are empty.
-        assertEquals(0, metrics.repeatCount);
-        assertEquals(0, metrics.halComposeCount);
-        assertEquals(0, metrics.halComposePwleCount);
-        assertEquals(0, metrics.halPerformCount);
-        assertEquals(0, metrics.halSetExternalControlCount);
-        assertEquals(0, metrics.halCompositionSize);
-        assertEquals(0, metrics.halPwleSize);
-        assertNull(metrics.halSupportedCompositionPrimitivesUsed);
-        assertNull(metrics.halSupportedEffectsUsed);
-        assertNull(metrics.halUnsupportedCompositionPrimitivesUsed);
-        assertNull(metrics.halUnsupportedEffectsUsed);
-
-        // Accommodate for ramping off config that might add extra setAmplitudes.
-        assertEquals(2, metrics.halOnCount);
-        assertTrue(metrics.halOffCount > 0);
-        assertTrue(metrics.halSetAmplitudeCount >= 2);
-    }
-
-    @Test
-    public void frameworkStats_repeatingVibration_reportsAllMetrics() throws Exception {
-        mockVibrators(1);
-        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
-
-        VibratorManagerService service = createSystemReadyService();
-        vibrate(service, VibrationEffect.createWaveform(new long[] {10, 100}, 1), RINGTONE_ATTRS);
-
-        verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS))
-                .writeVibratorStateOnAsync(eq(UID), anyLong());
-
-        // Wait for at least one loop before cancelling it.
-        Thread.sleep(100);
-        service.cancelVibrate(VibrationAttributes.USAGE_RINGTONE, service);
-
-        verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS))
-                .writeVibratorStateOffAsync(eq(UID));
-
-        ArgumentCaptor<VibrationStats.StatsInfo> argumentCaptor =
-                ArgumentCaptor.forClass(VibrationStats.StatsInfo.class);
-        verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS))
-                .writeVibrationReportedAsync(argumentCaptor.capture());
-
-        VibrationStats.StatsInfo metrics = argumentCaptor.getValue();
-        assertEquals(UID, metrics.uid);
-        assertEquals(FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__REPEATED,
-                metrics.vibrationType);
-        assertEquals(VibrationAttributes.USAGE_RINGTONE, metrics.usage);
-        assertEquals(Vibration.Status.CANCELLED_BY_USER.getProtoEnumValue(), metrics.status);
-        assertTrue("Total duration was too low, " + metrics.totalDurationMillis + "ms",
-                metrics.totalDurationMillis >= 100);
-        assertTrue("Vibrator ON duration was too low, " + metrics.vibratorOnMillis + "ms",
-                metrics.vibratorOnMillis >= 100);
-
-        // All unrelated metrics are empty.
-        assertTrue(metrics.repeatCount > 0);
-        assertEquals(0, metrics.halComposeCount);
-        assertEquals(0, metrics.halComposePwleCount);
-        assertEquals(0, metrics.halPerformCount);
-        assertEquals(0, metrics.halSetExternalControlCount);
-        assertEquals(0, metrics.halCompositionSize);
-        assertEquals(0, metrics.halPwleSize);
-        assertNull(metrics.halSupportedCompositionPrimitivesUsed);
-        assertNull(metrics.halSupportedEffectsUsed);
-        assertNull(metrics.halUnsupportedCompositionPrimitivesUsed);
-        assertNull(metrics.halUnsupportedEffectsUsed);
-
-        // Accommodate for ramping off config that might add extra setAmplitudes.
-        assertTrue(metrics.halOnCount > 0);
-        assertTrue(metrics.halOffCount > 0);
-        assertTrue(metrics.halSetAmplitudeCount > 0);
-    }
-
-    @Test
-    public void frameworkStats_prebakedAndComposedVibrations_reportsAllMetrics() throws Exception {
-        mockVibrators(1);
-        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
-        mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
-        mVibratorProviders.get(1).setSupportedPrimitives(
-                VibrationEffect.Composition.PRIMITIVE_TICK);
-
-        VibratorManagerService service = createSystemReadyService();
-        vibrateAndWaitUntilFinished(service,
-                VibrationEffect.startComposition()
-                        .addEffect(VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK))
-                        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK)
-                        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK)
-                        .addEffect(VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK))
-                        .addEffect(VibrationEffect.createPredefined(VibrationEffect.EFFECT_TICK))
-                        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
-                        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
-                        .compose(),
-                ALARM_ATTRS);
-
-        verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS))
-                .writeVibratorStateOnAsync(eq(UID), anyLong());
-        verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS))
-                .writeVibratorStateOffAsync(eq(UID));
-
-        ArgumentCaptor<VibrationStats.StatsInfo> argumentCaptor =
-                ArgumentCaptor.forClass(VibrationStats.StatsInfo.class);
-        verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS))
-                .writeVibrationReportedAsync(argumentCaptor.capture());
-
-        VibrationStats.StatsInfo metrics = argumentCaptor.getValue();
-        assertEquals(UID, metrics.uid);
-        assertEquals(FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__SINGLE,
-                metrics.vibrationType);
-        assertEquals(VibrationAttributes.USAGE_ALARM, metrics.usage);
-        assertEquals(Vibration.Status.FINISHED.getProtoEnumValue(), metrics.status);
-
-        // At least 4 effect/primitive played, 20ms each, plus configured fallback.
-        assertTrue("Total duration was too low, " + metrics.totalDurationMillis + "ms",
-                metrics.totalDurationMillis >= 80);
-        assertTrue("Vibrator ON duration was too low, " + metrics.vibratorOnMillis + "ms",
-                metrics.vibratorOnMillis >= 80);
-
-        // Related metrics were collected.
-        assertEquals(2, metrics.halComposeCount); // TICK+TICK, then CLICK+CLICK
-        assertEquals(3, metrics.halPerformCount); // CLICK, TICK, then CLICK
-        assertEquals(4, metrics.halCompositionSize); // 2*TICK + 2*CLICK
-        // No repetitions in reported effect/primitive IDs.
-        assertArrayEquals(new int[] {VibrationEffect.Composition.PRIMITIVE_TICK},
-                metrics.halSupportedCompositionPrimitivesUsed);
-        assertArrayEquals(new int[] {VibrationEffect.Composition.PRIMITIVE_CLICK},
-                metrics.halUnsupportedCompositionPrimitivesUsed);
-        assertArrayEquals(new int[] {VibrationEffect.EFFECT_CLICK},
-                metrics.halSupportedEffectsUsed);
-        assertArrayEquals(new int[] {VibrationEffect.EFFECT_TICK},
-                metrics.halUnsupportedEffectsUsed);
-
-        // All unrelated metrics are empty.
-        assertEquals(0, metrics.repeatCount);
-        assertEquals(0, metrics.halComposePwleCount);
-        assertEquals(0, metrics.halSetExternalControlCount);
-        assertEquals(0, metrics.halPwleSize);
-
-        // Accommodate for ramping off config that might add extra setAmplitudes
-        // for the effect that plays the fallback instead of "perform".
-        assertTrue(metrics.halOnCount > 0);
-        assertTrue(metrics.halOffCount > 0);
-        assertTrue(metrics.halSetAmplitudeCount > 0);
-    }
-
-    @Test
-    public void frameworkStats_interruptingVibrations_reportsAllMetrics() throws Exception {
-        mockVibrators(1);
-        VibratorManagerService service = createSystemReadyService();
-
-        vibrate(service, VibrationEffect.createOneShot(1_000, 128), HAPTIC_FEEDBACK_ATTRS);
-
-        // VibrationThread will start this vibration async, so wait until vibration is triggered.
-        assertTrue(waitUntil(s -> !mVibratorProviders.get(1).getAllEffectSegments().isEmpty(),
-                service, TEST_TIMEOUT_MILLIS));
-
-        vibrateAndWaitUntilFinished(service, VibrationEffect.createOneShot(10, 255), ALARM_ATTRS);
-
-        ArgumentCaptor<VibrationStats.StatsInfo> argumentCaptor =
-                ArgumentCaptor.forClass(VibrationStats.StatsInfo.class);
-        verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS).times(2))
-                .writeVibrationReportedAsync(argumentCaptor.capture());
-
-        VibrationStats.StatsInfo touchMetrics = argumentCaptor.getAllValues().get(0);
-        assertEquals(UID, touchMetrics.uid);
-        assertEquals(VibrationAttributes.USAGE_TOUCH, touchMetrics.usage);
-        assertEquals(Vibration.Status.CANCELLED_SUPERSEDED.getProtoEnumValue(),
-                touchMetrics.status);
-        assertTrue(touchMetrics.endedBySameUid);
-        assertEquals(VibrationAttributes.USAGE_ALARM, touchMetrics.endedByUsage);
-        assertEquals(-1, touchMetrics.interruptedUsage);
-
-        VibrationStats.StatsInfo alarmMetrics = argumentCaptor.getAllValues().get(1);
-        assertEquals(UID, alarmMetrics.uid);
-        assertEquals(VibrationAttributes.USAGE_ALARM, alarmMetrics.usage);
-        assertEquals(Vibration.Status.FINISHED.getProtoEnumValue(), alarmMetrics.status);
-        assertFalse(alarmMetrics.endedBySameUid);
-        assertEquals(-1, alarmMetrics.endedByUsage);
-        assertEquals(VibrationAttributes.USAGE_TOUCH, alarmMetrics.interruptedUsage);
-    }
-
-    @Test
-    public void frameworkStats_ignoredVibration_reportsStatus() throws Exception {
-        setUserSetting(Settings.System.RING_VIBRATION_INTENSITY,
-                Vibrator.VIBRATION_INTENSITY_OFF);
-
-        mockVibrators(1);
-        VibratorManagerService service = createSystemReadyService();
-        mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE);
-
-        // Haptic feedback ignored in low power state
-        vibrateAndWaitUntilFinished(service, VibrationEffect.createOneShot(100, 128),
-                HAPTIC_FEEDBACK_ATTRS);
-        // Ringtone vibration user settings are off
-        vibrateAndWaitUntilFinished(service, VibrationEffect.createOneShot(200, 128),
-                RINGTONE_ATTRS);
-
-        ArgumentCaptor<VibrationStats.StatsInfo> argumentCaptor =
-                ArgumentCaptor.forClass(VibrationStats.StatsInfo.class);
-        verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS).times(2))
-                .writeVibrationReportedAsync(argumentCaptor.capture());
-
-        VibrationStats.StatsInfo touchMetrics = argumentCaptor.getAllValues().get(0);
-        assertEquals(UID, touchMetrics.uid);
-        assertEquals(VibrationAttributes.USAGE_TOUCH, touchMetrics.usage);
-        assertEquals(Vibration.Status.IGNORED_FOR_POWER.getProtoEnumValue(), touchMetrics.status);
-
-        VibrationStats.StatsInfo ringtoneMetrics = argumentCaptor.getAllValues().get(1);
-        assertEquals(UID, ringtoneMetrics.uid);
-        assertEquals(VibrationAttributes.USAGE_RINGTONE, ringtoneMetrics.usage);
-        assertEquals(Vibration.Status.IGNORED_FOR_SETTINGS.getProtoEnumValue(),
-                ringtoneMetrics.status);
-
-        for (VibrationStats.StatsInfo metrics : argumentCaptor.getAllValues()) {
-            // Latencies are empty since vibrations never started
-            assertEquals(0, metrics.startLatencyMillis);
-            assertEquals(0, metrics.endLatencyMillis);
-            assertEquals(0, metrics.vibratorOnMillis);
-
-            // All unrelated metrics are empty.
-            assertEquals(0, metrics.repeatCount);
-            assertEquals(0, metrics.halComposeCount);
-            assertEquals(0, metrics.halComposePwleCount);
-            assertEquals(0, metrics.halOffCount);
-            assertEquals(0, metrics.halOnCount);
-            assertEquals(0, metrics.halPerformCount);
-            assertEquals(0, metrics.halSetExternalControlCount);
-            assertEquals(0, metrics.halCompositionSize);
-            assertEquals(0, metrics.halPwleSize);
-            assertNull(metrics.halSupportedCompositionPrimitivesUsed);
-            assertNull(metrics.halSupportedEffectsUsed);
-            assertNull(metrics.halUnsupportedCompositionPrimitivesUsed);
-            assertNull(metrics.halUnsupportedEffectsUsed);
-        }
-    }
-
-    @Test
-    public void frameworkStats_multiVibrators_reportsAllMetrics() throws Exception {
-        mockVibrators(1, 2);
-        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
-        mVibratorProviders.get(1).setSupportedPrimitives(
-                VibrationEffect.Composition.PRIMITIVE_TICK);
-        mVibratorProviders.get(2).setSupportedEffects(VibrationEffect.EFFECT_TICK);
-
-        VibratorManagerService service = createSystemReadyService();
-        vibrateAndWaitUntilFinished(service,
-                CombinedVibration.startParallel()
-                        .addVibrator(1,
-                                VibrationEffect.startComposition()
-                                        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK)
-                                        .compose())
-                        .addVibrator(2,
-                                VibrationEffect.createPredefined(VibrationEffect.EFFECT_TICK))
-                        .combine(),
-                NOTIFICATION_ATTRS);
-
-        SparseBooleanArray expectedEffectsUsed = new SparseBooleanArray();
-        expectedEffectsUsed.put(VibrationEffect.EFFECT_TICK, true);
-
-        SparseBooleanArray expectedPrimitivesUsed = new SparseBooleanArray();
-        expectedPrimitivesUsed.put(VibrationEffect.Composition.PRIMITIVE_TICK, true);
-
-        verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS))
-                .writeVibratorStateOnAsync(eq(UID), anyLong());
-        verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS))
-                .writeVibratorStateOffAsync(eq(UID));
-
-        ArgumentCaptor<VibrationStats.StatsInfo> argumentCaptor =
-                ArgumentCaptor.forClass(VibrationStats.StatsInfo.class);
-        verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS))
-                .writeVibrationReportedAsync(argumentCaptor.capture());
-
-        VibrationStats.StatsInfo metrics = argumentCaptor.getValue();
-        assertEquals(UID, metrics.uid);
-        assertEquals(FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__SINGLE,
-                metrics.vibrationType);
-        assertEquals(VibrationAttributes.USAGE_NOTIFICATION, metrics.usage);
-        assertEquals(Vibration.Status.FINISHED.getProtoEnumValue(), metrics.status);
-        assertTrue(metrics.totalDurationMillis >= 20);
-
-        // vibratorOnMillis accumulates both vibrators, it's 20 for each constant.
-        assertEquals(40, metrics.vibratorOnMillis);
-
-        // Related metrics were collected.
-        assertEquals(1, metrics.halComposeCount);
-        assertEquals(1, metrics.halPerformCount);
-        assertEquals(1, metrics.halCompositionSize);
-        assertEquals(2, metrics.halOffCount);
-        assertArrayEquals(new int[] {VibrationEffect.Composition.PRIMITIVE_TICK},
-                metrics.halSupportedCompositionPrimitivesUsed);
-        assertArrayEquals(new int[] {VibrationEffect.EFFECT_TICK},
-                metrics.halSupportedEffectsUsed);
-
-        // All unrelated metrics are empty.
-        assertEquals(0, metrics.repeatCount);
-        assertEquals(0, metrics.halComposePwleCount);
-        assertEquals(0, metrics.halOnCount);
-        assertEquals(0, metrics.halSetAmplitudeCount);
-        assertEquals(0, metrics.halSetExternalControlCount);
-        assertEquals(0, metrics.halPwleSize);
-        assertNull(metrics.halUnsupportedCompositionPrimitivesUsed);
-        assertNull(metrics.halUnsupportedEffectsUsed);
-    }
-
-    private VibrationEffectSegment expectedPrebaked(int effectId) {
-        return expectedPrebaked(effectId, VibrationEffect.EFFECT_STRENGTH_MEDIUM);
-    }
-
-    private VibrationEffectSegment expectedPrebaked(int effectId, int effectStrength) {
-        return new PrebakedSegment(effectId, false, effectStrength);
-    }
-
-    private void mockCapabilities(long... capabilities) {
-        when(mNativeWrapperMock.getCapabilities()).thenReturn(
-                Arrays.stream(capabilities).reduce(0, (a, b) -> a | b));
-    }
-
-    private void mockVibrators(int... vibratorIds) {
-        for (int vibratorId : vibratorIds) {
-            mVibratorProviders.put(vibratorId,
-                    new FakeVibratorControllerProvider(mTestLooper.getLooper()));
-        }
-        when(mNativeWrapperMock.getVibratorIds()).thenReturn(vibratorIds);
-    }
-
-    private void cancelVibrate(VibratorManagerService service) {
-        service.cancelVibrate(VibrationAttributes.USAGE_FILTER_MATCH_ALL, service);
-    }
-
-    private IVibratorStateListener mockVibratorStateListener() {
-        IVibratorStateListener listenerMock = mock(IVibratorStateListener.class);
-        IBinder binderMock = mock(IBinder.class);
-        when(listenerMock.asBinder()).thenReturn(binderMock);
-        return listenerMock;
-    }
-
-    private InputDevice createInputDeviceWithVibrator(int id) {
-        return new InputDevice.Builder()
-                .setId(id)
-                .setName("Test Device " + id)
-                .setHasVibrator(true)
-                .build();
-    }
-
-    private static <T> void addLocalServiceMock(Class<T> clazz, T mock) {
-        LocalServices.removeServiceForTest(clazz);
-        LocalServices.addService(clazz, mock);
-    }
-
-    private void setRingerMode(int ringerMode) {
-        AudioManager audioManager = mContextSpy.getSystemService(AudioManager.class);
-        audioManager.setRingerModeInternal(ringerMode);
-        assertEquals(ringerMode, audioManager.getRingerModeInternal());
-    }
-
-    private void setUserSetting(String settingName, int value) {
-        Settings.System.putIntForUser(
-                mContextSpy.getContentResolver(), settingName, value, UserHandle.USER_CURRENT);
-    }
-
-    private void vibrateAndWaitUntilFinished(VibratorManagerService service, VibrationEffect effect,
-            VibrationAttributes attrs) throws InterruptedException {
-        vibrateAndWaitUntilFinished(service, CombinedVibration.createParallel(effect), attrs);
-    }
-
-    private void vibrateAndWaitUntilFinished(VibratorManagerService service,
-            CombinedVibration effect, VibrationAttributes attrs) throws InterruptedException {
-        HalVibration vib =
-                service.vibrateInternal(UID, Display.DEFAULT_DISPLAY, PACKAGE_NAME, effect, attrs,
-                        "some reason", service);
-        if (vib != null) {
-            vib.waitForEnd();
-        }
-    }
-
-    private void vibrate(VibratorManagerService service, VibrationEffect effect,
-            VibrationAttributes attrs) {
-        vibrate(service, CombinedVibration.createParallel(effect), attrs);
-    }
-
-    private void vibrate(VibratorManagerService service, CombinedVibration effect,
-            VibrationAttributes attrs) {
-        vibrateWithDisplay(service, Display.DEFAULT_DISPLAY, effect, attrs);
-    }
-
-    private void vibrateWithDisplay(VibratorManagerService service, int displayId,
-            CombinedVibration effect, VibrationAttributes attrs) {
-        service.vibrate(UID, displayId, PACKAGE_NAME, effect, attrs, "some reason", service);
-    }
-
-    private boolean waitUntil(Predicate<VibratorManagerService> predicate,
-            VibratorManagerService service, long timeout) throws InterruptedException {
-        long timeoutTimestamp = SystemClock.uptimeMillis() + timeout;
-        boolean predicateResult = false;
-        while (!predicateResult && SystemClock.uptimeMillis() < timeoutTimestamp) {
-            Thread.sleep(10);
-            predicateResult = predicate.test(service);
-        }
-        return predicateResult;
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/webkit/OWNERS b/services/tests/servicestests/src/com/android/server/webkit/OWNERS
index 00e540a..e7fd7a5 100644
--- a/services/tests/servicestests/src/com/android/server/webkit/OWNERS
+++ b/services/tests/servicestests/src/com/android/server/webkit/OWNERS
@@ -1,3 +1,3 @@
-changwan@google.com
-tobiasjs@google.com
+# Bug component: 76427
+ntfschr@google.com
 torne@google.com
diff --git a/services/tests/servicestests/test-apps/SuspendTestApp/Android.bp b/services/tests/servicestests/test-apps/SuspendTestApp/Android.bp
index 5e77498..60cb529 100644
--- a/services/tests/servicestests/test-apps/SuspendTestApp/Android.bp
+++ b/services/tests/servicestests/test-apps/SuspendTestApp/Android.bp
@@ -28,7 +28,7 @@
 
     static_libs: [
         "androidx.test.runner",
-        "ub-uiautomator",
+        "androidx.test.uiautomator_uiautomator",
     ],
 
     srcs: [
diff --git a/services/tests/uiservicestests/Android.bp b/services/tests/uiservicestests/Android.bp
index 94f2d2e..4b65895 100644
--- a/services/tests/uiservicestests/Android.bp
+++ b/services/tests/uiservicestests/Android.bp
@@ -26,6 +26,7 @@
         "services.devicepolicy",
         "services.net",
         "services.usage",
+        "service-permission.stubs.system_server",
         "guava",
         "androidx.test.rules",
         "hamcrest-library",
diff --git a/services/tests/uiservicestests/OWNERS b/services/tests/uiservicestests/OWNERS
new file mode 100644
index 0000000..c0cbea9
--- /dev/null
+++ b/services/tests/uiservicestests/OWNERS
@@ -0,0 +1 @@
+include /services/core/java/com/android/server/notification/OWNERS
\ No newline at end of file
diff --git a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
index 8e81e2d..8a0a764 100644
--- a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
@@ -27,14 +27,10 @@
 import static android.app.UiModeManager.PROJECTION_TYPE_ALL;
 import static android.app.UiModeManager.PROJECTION_TYPE_AUTOMOTIVE;
 import static android.app.UiModeManager.PROJECTION_TYPE_NONE;
-
 import static com.android.server.UiModeManagerService.SUPPORTED_NIGHT_MODE_CUSTOM_TYPES;
-
 import static com.google.common.truth.Truth.assertThat;
-
 import static junit.framework.TestCase.assertFalse;
 import static junit.framework.TestCase.assertTrue;
-
 import static org.hamcrest.Matchers.contains;
 import static org.hamcrest.Matchers.empty;
 import static org.junit.Assert.assertEquals;
@@ -83,6 +79,7 @@
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.UserHandle;
+import android.os.test.FakePermissionEnforcer;
 import android.provider.Settings;
 import android.test.mock.MockContentResolver;
 import android.testing.AndroidTestingRunner;
@@ -151,11 +148,16 @@
     private AlarmManager.OnAlarmListener mCustomListener;
     private Consumer<PowerSaveState> mPowerSaveConsumer;
     private TwilightListener mTwilightListener;
+    private FakePermissionEnforcer mPermissionEnforcer;
 
     @Before
     public void setUp() {
-        when(mContext.checkCallingOrSelfPermission(anyString()))
-                .thenReturn(PackageManager.PERMISSION_GRANTED);
+        // The AIDL stub will use PermissionEnforcer to check permission from the caller.
+        mPermissionEnforcer = new FakePermissionEnforcer();
+        mPermissionEnforcer.grant(Manifest.permission.MODIFY_DAY_NIGHT_MODE);
+        mPermissionEnforcer.grant(Manifest.permission.READ_PROJECTION_STATE);
+        doReturn(mPermissionEnforcer).when(mContext).getSystemService(
+                eq(Context.PERMISSION_ENFORCER_SERVICE));
         doAnswer(inv -> {
             mTwilightListener = (TwilightListener) inv.getArgument(0);
             return null;
@@ -312,8 +314,7 @@
 
     @Test
     public void setNightModeCustomType_noPermission_shouldThrow() throws RemoteException {
-        when(mContext.checkCallingOrSelfPermission(eq(MODIFY_DAY_NIGHT_MODE)))
-                .thenReturn(PackageManager.PERMISSION_DENIED);
+        mPermissionEnforcer.revoke(MODIFY_DAY_NIGHT_MODE);
 
         assertThrows(SecurityException.class,
                 () -> mService.setNightModeCustomType(MODE_NIGHT_CUSTOM_TYPE_BEDTIME));
@@ -753,8 +754,7 @@
     @Test
     public void getNightModeCustomType_permissionNotGranted_shouldThrow()
             throws RemoteException {
-        when(mContext.checkCallingOrSelfPermission(eq(MODIFY_DAY_NIGHT_MODE)))
-                .thenReturn(PackageManager.PERMISSION_DENIED);
+        mPermissionEnforcer.revoke(MODIFY_DAY_NIGHT_MODE);
 
         assertThrows(SecurityException.class, () -> mService.getNightModeCustomType());
     }
@@ -1116,8 +1116,7 @@
 
     @Test
     public void addOnProjectionStateChangedListener_enforcesReadProjStatePermission() {
-        doThrow(new SecurityException()).when(mContext).enforceCallingOrSelfPermission(
-                eq(android.Manifest.permission.READ_PROJECTION_STATE), any());
+        mPermissionEnforcer.revoke(android.Manifest.permission.READ_PROJECTION_STATE);
         IOnProjectionStateChangedListener listener = mock(IOnProjectionStateChangedListener.class);
 
         assertThrows(SecurityException.class, () -> mService.addOnProjectionStateChangedListener(
@@ -1141,8 +1140,7 @@
 
     @Test
     public void removeOnProjectionStateChangedListener_enforcesReadProjStatePermission() {
-        doThrow(new SecurityException()).when(mContext).enforceCallingOrSelfPermission(
-                eq(android.Manifest.permission.READ_PROJECTION_STATE), any());
+        mPermissionEnforcer.revoke(android.Manifest.permission.READ_PROJECTION_STATE);
         IOnProjectionStateChangedListener listener = mock(IOnProjectionStateChangedListener.class);
 
         assertThrows(SecurityException.class, () -> mService.removeOnProjectionStateChangedListener(
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 541739d..2136811 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
@@ -1933,6 +1933,36 @@
         }, 20, 30);
     }
 
+    @Test
+    public void isComponentEnabledForCurrentProfiles_profileUserId() {
+        final int profileUserId = 10;
+        when(mUserProfiles.isProfileUser(profileUserId)).thenReturn(true);
+        // Only approve for parent user (0)
+        mService.addApprovedList("pkg1/cmp1:pkg2/cmp2:pkg3/cmp3", 0, true);
+
+        // Test that the component is enabled after calling rebindServices with profile userId (10)
+        mService.rebindServices(false, profileUserId);
+        assertThat(mService.isComponentEnabledForCurrentProfiles(
+                new ComponentName("pkg1", "cmp1"))).isTrue();
+    }
+
+    @Test
+    public void isComponentEnabledForCurrentProfiles_profileUserId_NAS() {
+        final int profileUserId = 10;
+        when(mUserProfiles.isProfileUser(profileUserId)).thenReturn(true);
+        // Do not rebind for parent users (NAS use-case)
+        ManagedServices service = spy(mService);
+        when(service.allowRebindForParentUser()).thenReturn(false);
+
+        // Only approve for parent user (0)
+        service.addApprovedList("pkg1/cmp1:pkg2/cmp2:pkg3/cmp3", 0, true);
+
+        // Test that the component is disabled after calling rebindServices with profile userId (10)
+        service.rebindServices(false, profileUserId);
+        assertThat(service.isComponentEnabledForCurrentProfiles(
+                new ComponentName("pkg1", "cmp1"))).isFalse();
+    }
+
     private void mockServiceInfoWithMetaData(List<ComponentName> componentNames,
             ManagedServices service, ArrayMap<ComponentName, Bundle> metaDatas)
             throws RemoteException {
@@ -2276,6 +2306,11 @@
         protected String getRequiredPermission() {
             return null;
         }
+
+        @Override
+        protected boolean allowRebindForParentUser() {
+            return true;
+        }
     }
 
     class TestManagedServicesSettings extends TestManagedServices {
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelLoggerFake.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelLoggerFake.java
index 6f7bace..082b675 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelLoggerFake.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelLoggerFake.java
@@ -16,24 +16,44 @@
 
 package com.android.server.notification;
 
+import android.annotation.Nullable;
 import android.app.NotificationChannel;
 import android.app.NotificationChannelGroup;
 
+import com.google.common.base.MoreObjects;
+
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Objects;
 
 public class NotificationChannelLoggerFake implements NotificationChannelLogger {
     static class CallRecord {
-        public NotificationChannelEvent event;
-        CallRecord(NotificationChannelEvent event) {
+        public final NotificationChannelEvent event;
+        @Nullable public final String channelId;
+
+        CallRecord(NotificationChannelEvent event, @Nullable String channelId) {
             this.event = event;
+            this.channelId = channelId;
         }
 
         @Override
         public String toString() {
-            return "CallRecord{" +
-                    "event=" + event +
-                    '}';
+            return MoreObjects.toStringHelper(this)
+                    .add("event", event)
+                    .add(channelId, channelId)
+                    .toString();
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            return (obj instanceof CallRecord other)
+                    && Objects.equals(this.event, other.event)
+                    && Objects.equals(this.channelId, other.channelId);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(event, channelId);
         }
     }
 
@@ -47,20 +67,24 @@
         return mCalls.get(index);
     }
 
+    void clear() {
+        mCalls.clear();
+    }
+
     @Override
     public void logNotificationChannel(NotificationChannelEvent event, NotificationChannel channel,
             int uid, String pkg, int oldImportance, int newImportance) {
-        mCalls.add(new CallRecord(event));
+        mCalls.add(new CallRecord(event, channel.getId()));
     }
 
     @Override
     public void logNotificationChannelGroup(NotificationChannelEvent event,
             NotificationChannelGroup channelGroup, int uid, String pkg, boolean wasBlocked) {
-        mCalls.add(new CallRecord(event));
+        mCalls.add(new CallRecord(event, channelGroup.getId()));
     }
 
     @Override
     public void logAppEvent(NotificationChannelEvent event, int uid, String pkg) {
-        mCalls.add(new CallRecord(event));
+        mCalls.add(new CallRecord(event, null));
     }
 }
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 4849665..a109d5c 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -62,6 +62,8 @@
 import static android.os.Build.VERSION_CODES.O_MR1;
 import static android.os.Build.VERSION_CODES.P;
 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;
 import static android.os.UserHandle.USER_SYSTEM;
 import static android.os.UserManager.USER_TYPE_FULL_SECONDARY;
 import static android.os.UserManager.USER_TYPE_PROFILE_CLONE;
@@ -71,6 +73,7 @@
 import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ALERTING;
 import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_CONVERSATIONS;
 import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ONGOING;
+import static android.service.notification.NotificationListenerService.REASON_CANCEL;
 import static android.service.notification.NotificationListenerService.REASON_LOCKDOWN;
 import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE;
 import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL;
@@ -83,6 +86,10 @@
 import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.SHOW_STICKY_HUN_FOR_DENIED_FSI;
 import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.WAKE_LOCK_FOR_POSTING_NOTIFICATION;
 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;
+import static com.android.server.am.PendingIntentRecord.FLAG_SERVICE_SENDER;
+import static com.android.server.notification.NotificationManagerService.DEFAULT_MAX_NOTIFICATION_ENQUEUE_RATE;
 import static com.android.server.notification.NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_ADJUSTED;
 import static com.android.server.notification.NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_POSTED;
 import static com.android.server.notification.NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_UPDATED;
@@ -185,6 +192,7 @@
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Parcel;
+import android.os.Parcelable;
 import android.os.PowerManager;
 import android.os.PowerManager.WakeLock;
 import android.os.Process;
@@ -398,6 +406,7 @@
     UriGrantsManagerInternal mUgmInternal;
     @Mock
     AppOpsManager mAppOpsManager;
+    private AppOpsManager.OnOpChangedListener mOnPermissionChangeListener;
     @Mock
     private TestableNotificationManagerService.NotificationAssistantAccessGrantedCallback
             mNotificationAssistantAccessGrantedCallback;
@@ -597,6 +606,12 @@
         tr.addOverride(com.android.internal.R.string.config_defaultSearchSelectorPackageName,
                 SEARCH_SELECTOR_PKG);
 
+        doAnswer(invocation -> {
+            mOnPermissionChangeListener = invocation.getArgument(2);
+            return null;
+        }).when(mAppOpsManager).startWatchingMode(eq(AppOpsManager.OP_POST_NOTIFICATION), any(),
+                any());
+
         mWorkerHandler = spy(mService.new WorkerHandler(mTestableLooper.getLooper()));
         mService.init(mWorkerHandler, mRankingHandler, mPackageManager, mPackageManagerClient,
                 mockLightsManager, mListeners, mAssistants, mConditionProviders, mCompanionMgr,
@@ -705,6 +720,7 @@
 
     @After
     public void assertAllTrackersFinishedOrCancelled() {
+        waitForIdle(); // Finish async work.
         // Verify that no trackers were left dangling.
         for (PostNotificationTracker tracker : mPostNotificationTrackerFactory.mCreatedTrackers) {
             assertThat(tracker.isOngoing()).isFalse();
@@ -714,6 +730,7 @@
 
     @After
     public void assertAllWakeLocksReleased() {
+        waitForIdle(); // Finish async work.
         for (WakeLock wakeLock : mAcquiredWakeLocks) {
             assertThat(wakeLock.isHeld()).isFalse();
         }
@@ -865,13 +882,18 @@
     }
 
     private NotificationRecord generateNotificationRecord(NotificationChannel channel, int userId) {
+        return generateNotificationRecord(channel, 1, userId);
+    }
+
+    private NotificationRecord generateNotificationRecord(NotificationChannel channel, int id,
+            int userId) {
         if (channel == null) {
             channel = mTestNotificationChannel;
         }
         Notification.Builder nb = new Notification.Builder(mContext, channel.getId())
                 .setContentTitle("foo")
                 .setSmallIcon(android.R.drawable.sym_def_app_icon);
-        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, "tag", mUid, 0,
+        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, id, "tag", mUid, 0,
                 nb.build(), new UserHandle(userId), null, 0);
         return new NotificationRecord(mContext, sbn, channel);
     }
@@ -1988,10 +2010,10 @@
         final StatusBarNotification sbn = generateNotificationRecord(null).getSbn();
         mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", sbn.getId(),
                 sbn.getNotification(), sbn.getUserId());
-        Thread.sleep(1);  // make sure the system clock advances before the next step
+        mTestableLooper.moveTimeForward(1);
         // THEN it is canceled
         mBinderService.cancelNotificationWithTag(PKG, PKG, "tag", sbn.getId(), sbn.getUserId());
-        Thread.sleep(1);  // here too
+        mTestableLooper.moveTimeForward(1);
         // THEN it is posted again (before the cancel has a chance to finish)
         mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", sbn.getId(),
                 sbn.getNotification(), sbn.getUserId());
@@ -2281,8 +2303,8 @@
                 mTestNotificationChannel, 1, "group", true);
         notif.getNotification().flags |= Notification.FLAG_NO_CLEAR;
         mService.addNotification(notif);
-        mService.cancelAllNotificationsInt(mUid, 0, PKG, null, 0, 0, true,
-                notif.getUserId(), 0, null);
+        mService.cancelAllNotificationsInt(mUid, 0, PKG, null, 0, 0,
+                notif.getUserId(), REASON_CANCEL);
         waitForIdle();
         StatusBarNotification[] notifs =
                 mBinderService.getActiveNotifications(notif.getSbn().getPackageName());
@@ -3020,7 +3042,7 @@
         notif.getNotification().flags |= Notification.FLAG_NO_CLEAR;
         mService.addNotification(notif);
         mService.cancelAllNotificationsInt(mUid, 0, PKG, null, 0,
-                Notification.FLAG_ONGOING_EVENT, true, notif.getUserId(), 0, null);
+                Notification.FLAG_ONGOING_EVENT, notif.getUserId(), REASON_CANCEL);
         waitForIdle();
         StatusBarNotification[] notifs =
                 mBinderService.getActiveNotifications(notif.getSbn().getPackageName());
@@ -3047,8 +3069,8 @@
                 mTestNotificationChannel, 1, "group", true);
         notif.getNotification().flags |= Notification.FLAG_ONGOING_EVENT;
         mService.addNotification(notif);
-        mService.cancelAllNotificationsInt(mUid, 0, PKG, null, 0, 0, true,
-                notif.getUserId(), 0, null);
+        mService.cancelAllNotificationsInt(mUid, 0, PKG, null, 0, 0,
+                notif.getUserId(), REASON_CANCEL);
         waitForIdle();
         StatusBarNotification[] notifs =
                 mBinderService.getActiveNotifications(notif.getSbn().getPackageName());
@@ -3202,48 +3224,6 @@
     }
 
     @Test
-    public void testUpdateAppNotifyCreatorBlock() throws Exception {
-        when(mPermissionHelper.hasPermission(mUid)).thenReturn(true);
-
-        mBinderService.setNotificationsEnabledForPackage(PKG, mUid, false);
-        Thread.sleep(500);
-        waitForIdle();
-
-        ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
-        verify(mContext, times(1)).sendBroadcastAsUser(captor.capture(), any(), eq(null));
-
-        assertEquals(NotificationManager.ACTION_APP_BLOCK_STATE_CHANGED,
-                captor.getValue().getAction());
-        assertEquals(PKG, captor.getValue().getPackage());
-        assertTrue(captor.getValue().getBooleanExtra(EXTRA_BLOCKED_STATE, true));
-    }
-
-    @Test
-    public void testUpdateAppNotifyCreatorBlock_notIfMatchesExistingSetting() throws Exception {
-        when(mPermissionHelper.hasPermission(mUid)).thenReturn(false);
-
-        mBinderService.setNotificationsEnabledForPackage(PKG, 0, false);
-        verify(mContext, never()).sendBroadcastAsUser(any(), any(), eq(null));
-    }
-
-    @Test
-    public void testUpdateAppNotifyCreatorUnblock() throws Exception {
-        when(mPermissionHelper.hasPermission(mUid)).thenReturn(false);
-
-        mBinderService.setNotificationsEnabledForPackage(PKG, mUid, true);
-        Thread.sleep(500);
-        waitForIdle();
-
-        ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
-        verify(mContext, times(1)).sendBroadcastAsUser(captor.capture(), any(), eq(null));
-
-        assertEquals(NotificationManager.ACTION_APP_BLOCK_STATE_CHANGED,
-                captor.getValue().getAction());
-        assertEquals(PKG, captor.getValue().getPackage());
-        assertFalse(captor.getValue().getBooleanExtra(EXTRA_BLOCKED_STATE, true));
-    }
-
-    @Test
     public void testUpdateChannelNotifyCreatorBlock() throws Exception {
         mService.setPreferencesHelper(mPreferencesHelper);
         when(mPreferencesHelper.getNotificationChannel(eq(PKG), anyInt(),
@@ -10752,6 +10732,51 @@
     }
 
     @Test
+    public void testUngroupingAutoSummary_differentUsers() throws Exception {
+        NotificationRecord nr0 =
+                generateNotificationRecord(mTestNotificationChannel, 0, USER_SYSTEM);
+        NotificationRecord nr1 =
+                generateNotificationRecord(mTestNotificationChannel, 1, USER_SYSTEM);
+
+        // add notifications + summary for USER_SYSTEM
+        mService.addNotification(nr0);
+        mService.addNotification(nr1);
+        mService.addNotification(mService.createAutoGroupSummary(nr1.getUserId(),
+                nr1.getSbn().getPackageName(), nr1.getKey(), GroupHelper.BASE_FLAGS));
+
+        // add notifications + summary for USER_ALL
+        NotificationRecord nr0_all =
+                generateNotificationRecord(mTestNotificationChannel, 2, UserHandle.USER_ALL);
+        NotificationRecord nr1_all =
+                generateNotificationRecord(mTestNotificationChannel, 3, UserHandle.USER_ALL);
+
+        mService.addNotification(nr0_all);
+        mService.addNotification(nr1_all);
+        mService.addNotification(mService.createAutoGroupSummary(nr0_all.getUserId(),
+                nr0_all.getSbn().getPackageName(), nr0_all.getKey(), GroupHelper.BASE_FLAGS));
+
+        // cancel both children for USER_ALL
+        mBinderService.cancelNotificationWithTag(PKG, PKG, nr0_all.getSbn().getTag(),
+                nr0_all.getSbn().getId(), UserHandle.USER_ALL);
+        mBinderService.cancelNotificationWithTag(PKG, PKG, nr1_all.getSbn().getTag(),
+                nr1_all.getSbn().getId(), UserHandle.USER_ALL);
+        waitForIdle();
+
+        // group helper would send 'remove summary' event
+        mService.clearAutogroupSummaryLocked(UserHandle.USER_ALL,
+                nr0_all.getSbn().getPackageName());
+        waitForIdle();
+
+        // make sure the right summary was removed
+        assertThat(mService.getNotificationCount(nr0_all.getSbn().getPackageName(),
+                UserHandle.USER_ALL, 0, null)).isEqualTo(0);
+
+        // the USER_SYSTEM notifications + summary were not removed
+        assertThat(mService.getNotificationCount(nr0.getSbn().getPackageName(),
+                USER_SYSTEM, 0, null)).isEqualTo(3);
+    }
+
+    @Test
     public void testStrongAuthTracker_isInLockDownMode() {
         mStrongAuthTracker.setGetStrongAuthForUserReturnValue(
                 STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN);
@@ -11967,6 +11992,295 @@
         assertFalse(n.isUserInitiatedJob());
     }
 
+    @Test
+    public void enqueue_updatesEnqueueRate() throws Exception {
+        Notification n = generateNotificationRecord(null).getNotification();
+
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", 0, n, mUserId);
+        // Don't waitForIdle() here. We want to verify the "intermediate" state.
+
+        verify(mUsageStats).registerEnqueuedByApp(eq(PKG));
+        verify(mUsageStats).registerEnqueuedByAppAndAccepted(eq(PKG));
+        verify(mUsageStats, never()).registerPostedByApp(any());
+
+        waitForIdle();
+    }
+
+    @Test
+    public void enqueue_withPost_updatesEnqueueRateAndPost() throws Exception {
+        Notification n = generateNotificationRecord(null).getNotification();
+
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", 0, n, mUserId);
+        waitForIdle();
+
+        verify(mUsageStats).registerEnqueuedByApp(eq(PKG));
+        verify(mUsageStats).registerEnqueuedByAppAndAccepted(eq(PKG));
+        verify(mUsageStats).registerPostedByApp(any());
+    }
+
+    @Test
+    public void enqueueNew_whenOverEnqueueRate_accepts() throws Exception {
+        Notification n = generateNotificationRecord(null).getNotification();
+        when(mUsageStats.getAppEnqueueRate(eq(PKG)))
+                .thenReturn(DEFAULT_MAX_NOTIFICATION_ENQUEUE_RATE + 1f);
+
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", 0, n, mUserId);
+        waitForIdle();
+
+        assertThat(mService.mNotificationsByKey).hasSize(1);
+        verify(mUsageStats).registerEnqueuedByApp(eq(PKG));
+        verify(mUsageStats).registerEnqueuedByAppAndAccepted(eq(PKG));
+        verify(mUsageStats).registerPostedByApp(any());
+    }
+
+    @Test
+    public void enqueueUpdate_whenBelowMaxEnqueueRate_accepts() throws Exception {
+        // Post the first version.
+        Notification original = generateNotificationRecord(null).getNotification();
+        original.when = 111;
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", 0, original, mUserId);
+        waitForIdle();
+        assertThat(mService.mNotificationList).hasSize(1);
+        assertThat(mService.mNotificationList.get(0).getNotification().when).isEqualTo(111);
+
+        reset(mUsageStats);
+        when(mUsageStats.getAppEnqueueRate(eq(PKG)))
+                .thenReturn(DEFAULT_MAX_NOTIFICATION_ENQUEUE_RATE - 1f);
+
+        // Post the update.
+        Notification update = generateNotificationRecord(null).getNotification();
+        update.when = 222;
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", 0, update, mUserId);
+        waitForIdle();
+
+        verify(mUsageStats).registerEnqueuedByApp(eq(PKG));
+        verify(mUsageStats).registerEnqueuedByAppAndAccepted(eq(PKG));
+        verify(mUsageStats, never()).registerPostedByApp(any());
+        verify(mUsageStats).registerUpdatedByApp(any(), any());
+        assertThat(mService.mNotificationList).hasSize(1);
+        assertThat(mService.mNotificationList.get(0).getNotification().when).isEqualTo(222);
+    }
+
+    @Test
+    public void enqueueUpdate_whenAboveMaxEnqueueRate_rejects() throws Exception {
+        // Post the first version.
+        Notification original = generateNotificationRecord(null).getNotification();
+        original.when = 111;
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", 0, original, mUserId);
+        waitForIdle();
+        assertThat(mService.mNotificationList).hasSize(1);
+        assertThat(mService.mNotificationList.get(0).getNotification().when).isEqualTo(111);
+
+        reset(mUsageStats);
+        when(mUsageStats.getAppEnqueueRate(eq(PKG)))
+                .thenReturn(DEFAULT_MAX_NOTIFICATION_ENQUEUE_RATE + 1f);
+
+        // Post the update.
+        Notification update = generateNotificationRecord(null).getNotification();
+        update.when = 222;
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", 0, update, mUserId);
+        waitForIdle();
+
+        verify(mUsageStats).registerEnqueuedByApp(eq(PKG));
+        verify(mUsageStats, never()).registerEnqueuedByAppAndAccepted(any());
+        verify(mUsageStats, never()).registerPostedByApp(any());
+        verify(mUsageStats, never()).registerUpdatedByApp(any(), any());
+        assertThat(mService.mNotificationList).hasSize(1);
+        assertThat(mService.mNotificationList.get(0).getNotification().when).isEqualTo(111); // old
+    }
+
+    @Test
+    public void enqueueNotification_allowlistsPendingIntents() throws RemoteException {
+        PendingIntent contentIntent = createPendingIntent("content");
+        PendingIntent actionIntent1 = createPendingIntent("action1");
+        PendingIntent actionIntent2 = createPendingIntent("action2");
+        Notification n = new Notification.Builder(mContext, TEST_CHANNEL_ID)
+                .setContentIntent(contentIntent)
+                .addAction(new Notification.Action.Builder(null, "action1", actionIntent1).build())
+                .addAction(new Notification.Action.Builder(null, "action2", actionIntent2).build())
+                .build();
+
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", 1,
+                parcelAndUnparcel(n, Notification.CREATOR), mUserId);
+
+        verify(mAmi, times(3)).setPendingIntentAllowlistDuration(
+                any(), any(), anyLong(),
+                eq(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED),
+                eq(REASON_NOTIFICATION_SERVICE), any());
+        verify(mAmi, times(3)).setPendingIntentAllowBgActivityStarts(any(),
+                any(), eq(FLAG_ACTIVITY_SENDER | FLAG_BROADCAST_SENDER | FLAG_SERVICE_SENDER));
+    }
+
+    @Test
+    public void enqueueNotification_allowlistsPendingIntents_includingFromPublicVersion()
+            throws RemoteException {
+        PendingIntent contentIntent = createPendingIntent("content");
+        PendingIntent actionIntent = createPendingIntent("action");
+        PendingIntent publicContentIntent = createPendingIntent("publicContent");
+        PendingIntent publicActionIntent = createPendingIntent("publicAction");
+        Notification source = new Notification.Builder(mContext, TEST_CHANNEL_ID)
+                .setContentIntent(contentIntent)
+                .addAction(new Notification.Action.Builder(null, "action", actionIntent).build())
+                .setPublicVersion(new Notification.Builder(mContext, "channel")
+                        .setContentIntent(publicContentIntent)
+                        .addAction(new Notification.Action.Builder(
+                                null, "publicAction", publicActionIntent).build())
+                        .build())
+                .build();
+
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", 1,
+                parcelAndUnparcel(source, Notification.CREATOR), mUserId);
+
+        verify(mAmi, times(4)).setPendingIntentAllowlistDuration(
+                any(), any(), anyLong(),
+                eq(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED),
+                eq(REASON_NOTIFICATION_SERVICE), any());
+        verify(mAmi, times(4)).setPendingIntentAllowBgActivityStarts(any(),
+                any(), eq(FLAG_ACTIVITY_SENDER | FLAG_BROADCAST_SENDER | FLAG_SERVICE_SENDER));
+    }
+
+    @Test
+    public void onOpChanged_permissionRevoked_cancelsAllNotificationsFromPackage()
+            throws RemoteException {
+        // Have preexisting posted notifications from revoked package and other packages.
+        mService.addNotification(new NotificationRecord(mContext,
+                generateSbn("revoked", 1001, 1, 0), mTestNotificationChannel));
+        mService.addNotification(new NotificationRecord(mContext,
+                generateSbn("other", 1002, 2, 0), mTestNotificationChannel));
+        // Have preexisting enqueued notifications from revoked package and other packages.
+        mService.addEnqueuedNotification(new NotificationRecord(mContext,
+                generateSbn("revoked", 1001, 3, 0), mTestNotificationChannel));
+        mService.addEnqueuedNotification(new NotificationRecord(mContext,
+                generateSbn("other", 1002, 4, 0), mTestNotificationChannel));
+        assertThat(mService.mNotificationList).hasSize(2);
+        assertThat(mService.mEnqueuedNotifications).hasSize(2);
+
+        when(mPackageManagerInternal.getPackageUid("revoked", 0, 0)).thenReturn(1001);
+        when(mPermissionHelper.hasPermission(eq(1001))).thenReturn(false);
+
+        mOnPermissionChangeListener.onOpChanged(
+                AppOpsManager.OPSTR_POST_NOTIFICATION, "revoked", 0);
+        waitForIdle();
+
+        assertThat(mService.mNotificationList).hasSize(1);
+        assertThat(mService.mNotificationList.get(0).getSbn().getPackageName()).isEqualTo("other");
+        assertThat(mService.mEnqueuedNotifications).hasSize(1);
+        assertThat(mService.mEnqueuedNotifications.get(0).getSbn().getPackageName()).isEqualTo(
+                "other");
+    }
+
+    @Test
+    public void onOpChanged_permissionStillGranted_notificationsAreNotAffected()
+            throws RemoteException {
+        // NOTE: This combination (receiving the onOpChanged broadcast for a package, the permission
+        // being now granted, AND having previously posted notifications from said package) should
+        // never happen (if we trust the broadcasts are correct). So this test is for a what-if
+        // scenario, to verify we still handle it reasonably.
+
+        // Have preexisting posted notifications from specific package and other packages.
+        mService.addNotification(new NotificationRecord(mContext,
+                generateSbn("granted", 1001, 1, 0), mTestNotificationChannel));
+        mService.addNotification(new NotificationRecord(mContext,
+                generateSbn("other", 1002, 2, 0), mTestNotificationChannel));
+        // Have preexisting enqueued notifications from specific package and other packages.
+        mService.addEnqueuedNotification(new NotificationRecord(mContext,
+                generateSbn("granted", 1001, 3, 0), mTestNotificationChannel));
+        mService.addEnqueuedNotification(new NotificationRecord(mContext,
+                generateSbn("other", 1002, 4, 0), mTestNotificationChannel));
+        assertThat(mService.mNotificationList).hasSize(2);
+        assertThat(mService.mEnqueuedNotifications).hasSize(2);
+
+        when(mPackageManagerInternal.getPackageUid("granted", 0, 0)).thenReturn(1001);
+        when(mPermissionHelper.hasPermission(eq(1001))).thenReturn(true);
+
+        mOnPermissionChangeListener.onOpChanged(
+                AppOpsManager.OPSTR_POST_NOTIFICATION, "granted", 0);
+        waitForIdle();
+
+        assertThat(mService.mNotificationList).hasSize(2);
+        assertThat(mService.mEnqueuedNotifications).hasSize(2);
+    }
+
+    @Test
+    public void onOpChanged_permissionGranted_notifiesAppUnblocked() throws Exception {
+        when(mPackageManagerInternal.getPackageUid(PKG, 0, 0)).thenReturn(1001);
+        when(mPermissionHelper.hasPermission(eq(1001))).thenReturn(true);
+
+        mOnPermissionChangeListener.onOpChanged(
+                AppOpsManager.OPSTR_POST_NOTIFICATION, PKG, 0);
+        waitForIdle();
+        mTestableLooper.moveTimeForward(500);
+        waitForIdle();
+
+        ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
+        verify(mContext).sendBroadcastAsUser(captor.capture(), any(), eq(null));
+        assertThat(captor.getValue().getAction()).isEqualTo(
+                NotificationManager.ACTION_APP_BLOCK_STATE_CHANGED);
+        assertThat(captor.getValue().getPackage()).isEqualTo(PKG);
+        assertThat(captor.getValue().getBooleanExtra(EXTRA_BLOCKED_STATE, true)).isFalse();
+    }
+
+    @Test
+    public void onOpChanged_permissionRevoked_notifiesAppBlocked() throws Exception {
+        when(mPackageManagerInternal.getPackageUid(PKG, 0, 0)).thenReturn(1001);
+        when(mPermissionHelper.hasPermission(eq(1001))).thenReturn(false);
+
+        mOnPermissionChangeListener.onOpChanged(
+                AppOpsManager.OPSTR_POST_NOTIFICATION, PKG, 0);
+        waitForIdle();
+        mTestableLooper.moveTimeForward(500);
+        waitForIdle();
+
+        ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
+        verify(mContext).sendBroadcastAsUser(captor.capture(), any(), eq(null));
+        assertThat(captor.getValue().getAction()).isEqualTo(
+                NotificationManager.ACTION_APP_BLOCK_STATE_CHANGED);
+        assertThat(captor.getValue().getPackage()).isEqualTo(PKG);
+        assertThat(captor.getValue().getBooleanExtra(EXTRA_BLOCKED_STATE, false)).isTrue();
+    }
+
+    @Test
+    public void setNotificationsEnabledForPackage_disabling_clearsNotifications() throws Exception {
+        mService.addNotification(new NotificationRecord(mContext,
+                generateSbn("package", 1001, 1, 0), mTestNotificationChannel));
+        assertThat(mService.mNotificationList).hasSize(1);
+        when(mPackageManagerInternal.getPackageUid("package", 0, 0)).thenReturn(1001);
+        when(mPermissionHelper.hasRequestedPermission(any(), eq("package"), anyInt())).thenReturn(
+                true);
+
+        // Start with granted permission and simulate effect of revoking it.
+        when(mPermissionHelper.hasPermission(1001)).thenReturn(true);
+        doAnswer(invocation -> {
+            when(mPermissionHelper.hasPermission(1001)).thenReturn(false);
+            mOnPermissionChangeListener.onOpChanged(
+                    AppOpsManager.OPSTR_POST_NOTIFICATION, "package", 0);
+            return null;
+        }).when(mPermissionHelper).setNotificationPermission("package", 0, false, true);
+
+        mBinderService.setNotificationsEnabledForPackage("package", 1001, false);
+        waitForIdle();
+
+        assertThat(mService.mNotificationList).hasSize(0);
+
+        mTestableLooper.moveTimeForward(500);
+        waitForIdle();
+        verify(mContext).sendBroadcastAsUser(any(), eq(UserHandle.of(0)), eq(null));
+    }
+
+    private static <T extends Parcelable> T parcelAndUnparcel(T source,
+            Parcelable.Creator<T> creator) {
+        Parcel parcel = Parcel.obtain();
+        source.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        return creator.createFromParcel(parcel);
+    }
+
+    private PendingIntent createPendingIntent(String action) {
+        return PendingIntent.getActivity(mContext, 0,
+                new Intent(action).setPackage(mContext.getPackageName()),
+                PendingIntent.FLAG_MUTABLE);
+    }
+
     private void setDpmAppOppsExemptFromDismissal(boolean isOn) {
         DeviceConfig.setProperty(
                 DeviceConfig.NAMESPACE_DEVICE_POLICY_MANAGER,
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 beab107..b522cab 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerTest.java
@@ -18,6 +18,16 @@
 
 import static android.app.Notification.FLAG_FOREGROUND_SERVICE;
 import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
+import static android.service.notification.NotificationListenerService.REASON_CANCEL;
+import static android.service.notification.NotificationListenerService.REASON_GROUP_SUMMARY_CANCELED;
+import static android.service.notification.NotificationStats.DISMISSAL_BUBBLE;
+import static android.service.notification.NotificationStats.DISMISSAL_OTHER;
+
+import static com.android.server.notification.NotificationRecordLogger.NotificationCancelledEvent.NOTIFICATION_CANCEL_CLICK;
+import static com.android.server.notification.NotificationRecordLogger.NotificationCancelledEvent.NOTIFICATION_CANCEL_GROUP_SUMMARY_CANCELED;
+import static com.android.server.notification.NotificationRecordLogger.NotificationCancelledEvent.NOTIFICATION_CANCEL_USER_OTHER;
+import static com.android.server.notification.NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_POSTED;
+import static com.android.server.notification.NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_UPDATED;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -33,6 +43,7 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.server.UiServiceTestCase;
+import com.android.internal.util.FrameworkStatsLog;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -155,4 +166,68 @@
         // Then: should return false
         assertFalse(NotificationRecordLogger.isNonDismissible(p.r));
     }
+
+    @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);
+        assertEquals(0, fsiState);
+    }
+
+    @Test
+    public void testGetFsiState_hasFsi_allowedEnum() {
+        final int fsiState = NotificationRecordLogger.getFsiState(
+                /* isStickyHunFlagEnabled= */ true,
+                /* hasFullScreenIntent= */ true,
+                /* hasFsiRequestedButDeniedFlag= */ false,
+                /* eventType= */ NOTIFICATION_POSTED);
+        assertEquals(FrameworkStatsLog.NOTIFICATION_REPORTED__FSI_STATE__FSI_ALLOWED, fsiState);
+    }
+
+    @Test
+    public void testGetFsiState_fsiPermissionDenied_deniedEnum() {
+        final int fsiState = NotificationRecordLogger.getFsiState(
+                /* isStickyHunFlagEnabled= */ true,
+                /* hasFullScreenIntent= */ false,
+                /* hasFsiRequestedButDeniedFlag= */ true,
+                /* eventType= */ NOTIFICATION_POSTED);
+        assertEquals(FrameworkStatsLog.NOTIFICATION_REPORTED__FSI_STATE__FSI_DENIED, fsiState);
+    }
+
+    @Test
+    public void testGetFsiState_noFsi_noFsiEnum() {
+        final int fsiState = NotificationRecordLogger.getFsiState(
+                /* isStickyHunFlagEnabled= */ true,
+                /* hasFullScreenIntent= */ false,
+                /* hasFsiRequestedButDeniedFlag= */ false,
+                /* eventType= */ NOTIFICATION_POSTED);
+        assertEquals(FrameworkStatsLog.NOTIFICATION_REPORTED__FSI_STATE__NO_FSI, fsiState);
+    }
+
+    @Test
+    public void testBubbleGroupSummaryDismissal() {
+        assertEquals(NOTIFICATION_CANCEL_GROUP_SUMMARY_CANCELED,
+                NotificationRecordLogger.NotificationCancelledEvent.fromCancelReason(
+                REASON_GROUP_SUMMARY_CANCELED, DISMISSAL_BUBBLE));
+    }
+
+    @Test
+    public void testOtherNotificationCancel() {
+        assertEquals(NOTIFICATION_CANCEL_USER_OTHER,
+                NotificationRecordLogger.NotificationCancelledEvent.fromCancelReason(
+                        REASON_CANCEL, DISMISSAL_OTHER));
+    }
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java
index 024c38e..121e296 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java
@@ -34,6 +34,7 @@
 import android.os.IBinder;
 import android.os.Parcel;
 import android.util.Log;
+import android.view.LayoutInflater;
 import android.view.View;
 import android.widget.RemoteViews;
 
@@ -96,7 +97,8 @@
     // Types that we can't really produce. No methods receiving these parameters will be invoked.
     private static final ImmutableSet<Class<?>> UNUSABLE_TYPES =
             ImmutableSet.of(Consumer.class, IBinder.class, MediaSession.Token.class, Parcel.class,
-                    PrintWriter.class, Resources.Theme.class, View.class);
+                    PrintWriter.class, Resources.Theme.class, View.class,
+                    LayoutInflater.Factory2.class);
 
     // Maximum number of times we allow generating the same class recursively.
     // E.g. new RemoteViews.addView(new RemoteViews()) but stop there.
@@ -116,11 +118,19 @@
             PREFERRED_CONSTRUCTORS = ImmutableMap.of(
                     Notification.Builder.class,
                     Notification.Builder.class.getConstructor(Context.class, String.class));
+
+            EXCLUDED_SETTERS_OVERLOADS = ImmutableMultimap.<Class<?>, Method>builder()
+                    .put(RemoteViews.class,
+                            // b/245950570: Tries to connect to service and will crash.
+                            RemoteViews.class.getMethod("setRemoteAdapter",
+                                    int.class, Intent.class))
+                    .build();
         } catch (NoSuchMethodException e) {
             throw new RuntimeException(e);
         }
     }
 
+    // Setters that shouldn't be called, for various reasons (but NOT because they are KNOWN_BAD).
     private static final Multimap<Class<?>, String> EXCLUDED_SETTERS =
             ImmutableMultimap.<Class<?>, String>builder()
                     // Handled by testAllStyles().
@@ -135,6 +145,9 @@
                     .put(RemoteViews.class, "mergeRemoteViews")
                     .build();
 
+    // Same as above, but specific overloads that should not be called.
+    private static final Multimap<Class<?>, Method> EXCLUDED_SETTERS_OVERLOADS;
+
     private Context mContext;
 
     @Rule
@@ -147,8 +160,10 @@
 
     @Test // This is a meta-test, checks that the generators are not broken.
     public void verifyTest() {
-        Generated<Notification> notification = buildNotification(mContext, /* styleClass= */ null,
-                /* extenderClass= */ null, /* actionExtenderClass= */ null,
+        Generated<Notification> notification = buildNotification(mContext,
+                /* styleClass= */ Notification.MessagingStyle.class,
+                /* extenderClass= */ Notification.WearableExtender.class,
+                /* actionExtenderClass= */ Notification.Action.WearableExtender.class,
                 /* includeRemoteViews= */ true);
         assertThat(notification.includedUris.size()).isAtLeast(900);
     }
@@ -504,6 +519,7 @@
                         || method.getReturnType().equals(clazz))
                         && method.getParameterCount() >= 1
                         && !EXCLUDED_SETTERS.containsEntry(clazz, method.getName())
+                        && !EXCLUDED_SETTERS_OVERLOADS.containsEntry(clazz, method)
                         && Arrays.stream(method.getParameterTypes())
                             .noneMatch(excludingParameterTypes::contains)) {
                     methods.put(method.getName(), method);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java
index 539f329..318f932 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java
@@ -246,10 +246,10 @@
         mPermissionHelper.setNotificationPermission("pkg", 10, true, true);
 
         verify(mPermManager).grantRuntimePermission(
-                "pkg", Manifest.permission.POST_NOTIFICATIONS, 10);
+                "pkg", Manifest.permission.POST_NOTIFICATIONS, Context.DEVICE_ID_DEFAULT, 10);
         verify(mPermManager).updatePermissionFlags("pkg", Manifest.permission.POST_NOTIFICATIONS,
                 USER_FLAG_MASK | FLAG_PERMISSION_GRANTED_BY_DEFAULT,
-                FLAG_PERMISSION_USER_SET, true, 10);
+                FLAG_PERMISSION_USER_SET, true, Context.DEVICE_ID_DEFAULT, 10);
     }
 
     @Test
@@ -259,16 +259,16 @@
                 .thenReturn(PERMISSION_DENIED);
         when(mPermManager.getPermissionFlags(anyString(),
                 eq(Manifest.permission.POST_NOTIFICATIONS),
-                anyInt())).thenReturn(FLAG_PERMISSION_GRANTED_BY_DEFAULT);
+                anyInt(), anyInt())).thenReturn(FLAG_PERMISSION_GRANTED_BY_DEFAULT);
         PermissionHelper.PackagePermission pkgPerm = new PermissionHelper.PackagePermission(
                 "pkg", 10, true, false);
 
         mPermissionHelper.setNotificationPermission(pkgPerm);
         verify(mPermManager).grantRuntimePermission(
-                "pkg", Manifest.permission.POST_NOTIFICATIONS, 10);
+                "pkg", Manifest.permission.POST_NOTIFICATIONS, Context.DEVICE_ID_DEFAULT, 10);
         verify(mPermManager).updatePermissionFlags("pkg", Manifest.permission.POST_NOTIFICATIONS,
                 USER_FLAG_MASK | FLAG_PERMISSION_GRANTED_BY_DEFAULT,
-                FLAG_PERMISSION_USER_SET, true, 10);
+                FLAG_PERMISSION_USER_SET, true, Context.DEVICE_ID_DEFAULT, 10);
     }
 
     @Test
@@ -279,10 +279,11 @@
         mPermissionHelper.setNotificationPermission("pkg", 10, false, true);
 
         verify(mPermManager).revokeRuntimePermission(
-                eq("pkg"), eq(Manifest.permission.POST_NOTIFICATIONS), eq(10), anyString());
+                eq("pkg"), eq(Manifest.permission.POST_NOTIFICATIONS),
+                eq(Context.DEVICE_ID_DEFAULT), eq(10), anyString());
         verify(mPermManager).updatePermissionFlags("pkg", Manifest.permission.POST_NOTIFICATIONS,
                 USER_FLAG_MASK | FLAG_PERMISSION_GRANTED_BY_DEFAULT,
-                FLAG_PERMISSION_USER_SET, true, 10);
+                FLAG_PERMISSION_USER_SET, true, Context.DEVICE_ID_DEFAULT, 10);
     }
 
     @Test
@@ -293,9 +294,9 @@
         mPermissionHelper.setNotificationPermission("pkg", 10, true, false);
 
         verify(mPermManager).grantRuntimePermission(
-                "pkg", Manifest.permission.POST_NOTIFICATIONS, 10);
+                "pkg", Manifest.permission.POST_NOTIFICATIONS, Context.DEVICE_ID_DEFAULT, 10);
         verify(mPermManager).updatePermissionFlags("pkg", Manifest.permission.POST_NOTIFICATIONS,
-                USER_FLAG_MASK, 0, true, 10);
+                USER_FLAG_MASK, 0, true, Context.DEVICE_ID_DEFAULT, 10);
     }
 
     @Test
@@ -306,36 +307,37 @@
         mPermissionHelper.setNotificationPermission("pkg", 10, false, false);
 
         verify(mPermManager).revokeRuntimePermission(
-                eq("pkg"), eq(Manifest.permission.POST_NOTIFICATIONS), eq(10), anyString());
+                eq("pkg"), eq(Manifest.permission.POST_NOTIFICATIONS),
+                eq(Context.DEVICE_ID_DEFAULT), eq(10), anyString());
         verify(mPermManager).updatePermissionFlags("pkg", Manifest.permission.POST_NOTIFICATIONS,
                 USER_FLAG_MASK | FLAG_PERMISSION_GRANTED_BY_DEFAULT, 0,
-                true, 10);
+                true, Context.DEVICE_ID_DEFAULT, 10);
     }
 
     @Test
     public void testSetNotificationPermission_SystemFixedPermNotSet() throws Exception {
         when(mPermManager.getPermissionFlags(anyString(),
                 eq(Manifest.permission.POST_NOTIFICATIONS),
-                anyInt())).thenReturn(FLAG_PERMISSION_SYSTEM_FIXED);
+                anyInt(), anyInt())).thenReturn(FLAG_PERMISSION_SYSTEM_FIXED);
 
         mPermissionHelper.setNotificationPermission("pkg", 10, false, true);
         verify(mPermManager, never()).revokeRuntimePermission(
-                anyString(), anyString(), anyInt(), anyString());
+                anyString(), anyString(), anyInt(), anyInt(), anyString());
         verify(mPermManager, never()).updatePermissionFlags(
-                anyString(), anyString(), anyInt(), anyInt(), anyBoolean(), anyInt());
+                anyString(), anyString(), anyInt(), anyInt(), anyBoolean(), anyInt(), anyInt());
     }
 
     @Test
     public void testSetNotificationPermission_PolicyFixedPermNotSet() throws Exception {
         when(mPermManager.getPermissionFlags(anyString(),
                 eq(Manifest.permission.POST_NOTIFICATIONS),
-                anyInt())).thenReturn(FLAG_PERMISSION_POLICY_FIXED);
+                anyInt(), anyInt())).thenReturn(FLAG_PERMISSION_POLICY_FIXED);
 
         mPermissionHelper.setNotificationPermission("pkg", 10, false, true);
         verify(mPermManager, never()).revokeRuntimePermission(
-                anyString(), anyString(), anyInt(), anyString());
+                anyString(), anyString(), anyInt(), anyInt(), anyString());
         verify(mPermManager, never()).updatePermissionFlags(
-                anyString(), anyString(), anyInt(), anyInt(), anyBoolean(), anyInt());
+                anyString(), anyString(), anyInt(), anyInt(), anyBoolean(), anyInt(), anyInt());
     }
 
     @Test
@@ -345,7 +347,7 @@
         mPermissionHelper.setNotificationPermission("pkg", 10, true, false);
 
         verify(mPermManager, never()).grantRuntimePermission(
-                "pkg", Manifest.permission.POST_NOTIFICATIONS, 10);
+                "pkg", Manifest.permission.POST_NOTIFICATIONS, Context.DEVICE_ID_DEFAULT, 10);
     }
 
     @Test
@@ -355,7 +357,8 @@
         mPermissionHelper.setNotificationPermission("pkg", 10, false, false);
 
         verify(mPermManager, never()).revokeRuntimePermission(
-                eq("pkg"), eq(Manifest.permission.POST_NOTIFICATIONS), eq(10), anyString());
+                eq("pkg"), eq(Manifest.permission.POST_NOTIFICATIONS),
+                eq(Context.DEVICE_ID_DEFAULT), eq(10), anyString());
     }
 
     @Test
@@ -374,26 +377,27 @@
         verify(mContext, never()).checkPermission(
                 eq(Manifest.permission.POST_NOTIFICATIONS), eq(-1), eq(testUid));
         verify(mPermManager, never()).revokeRuntimePermission(
-                eq("pkg"), eq(Manifest.permission.POST_NOTIFICATIONS), eq(10), anyString());
+                eq("pkg"), eq(Manifest.permission.POST_NOTIFICATIONS),
+                eq(Context.DEVICE_ID_DEFAULT), eq(10), anyString());
     }
 
     @Test
     public void testIsPermissionFixed() throws Exception {
         when(mPermManager.getPermissionFlags(anyString(),
                 eq(Manifest.permission.POST_NOTIFICATIONS),
-                anyInt())).thenReturn(FLAG_PERMISSION_USER_SET);
+                anyInt(), anyInt())).thenReturn(FLAG_PERMISSION_USER_SET);
 
         assertThat(mPermissionHelper.isPermissionFixed("pkg", 0)).isFalse();
 
         when(mPermManager.getPermissionFlags(anyString(),
-                eq(Manifest.permission.POST_NOTIFICATIONS),
+                eq(Manifest.permission.POST_NOTIFICATIONS), anyInt(),
                 anyInt())).thenReturn(FLAG_PERMISSION_USER_SET|FLAG_PERMISSION_POLICY_FIXED);
 
         assertThat(mPermissionHelper.isPermissionFixed("pkg", 0)).isTrue();
 
         when(mPermManager.getPermissionFlags(anyString(),
                 eq(Manifest.permission.POST_NOTIFICATIONS),
-                anyInt())).thenReturn(FLAG_PERMISSION_SYSTEM_FIXED);
+                anyInt(), anyInt())).thenReturn(FLAG_PERMISSION_SYSTEM_FIXED);
 
         assertThat(mPermissionHelper.isPermissionFixed("pkg", 0)).isTrue();
     }
@@ -434,13 +438,14 @@
                 .thenReturn(requesting);
 
         // 2 and 3 are user-set permissions
-        when(mPermManager.getPermissionFlags(
-                "first", Manifest.permission.POST_NOTIFICATIONS, userId)).thenReturn(0);
-        when(mPermManager.getPermissionFlags(
-                "second", Manifest.permission.POST_NOTIFICATIONS, userId))
+        when(mPermManager.getPermissionFlags("first", Manifest.permission.POST_NOTIFICATIONS,
+                Context.DEVICE_ID_DEFAULT, userId))
+                .thenReturn(0);
+        when(mPermManager.getPermissionFlags("second", Manifest.permission.POST_NOTIFICATIONS,
+                Context.DEVICE_ID_DEFAULT, userId))
                 .thenReturn(FLAG_PERMISSION_USER_SET);
-        when(mPermManager.getPermissionFlags(
-                "third", Manifest.permission.POST_NOTIFICATIONS, userId))
+        when(mPermManager.getPermissionFlags("third", Manifest.permission.POST_NOTIFICATIONS,
+                Context.DEVICE_ID_DEFAULT, userId))
                 .thenReturn(FLAG_PERMISSION_USER_SET);
 
         Map<Pair<Integer, String>, Pair<Boolean, Boolean>> expected =
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 48ad86d..ea670bd 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -18,8 +18,19 @@
 import static android.app.AppOpsManager.MODE_ALLOWED;
 import static android.app.AppOpsManager.MODE_DEFAULT;
 import static android.app.AppOpsManager.OP_SYSTEM_ALERT_WINDOW;
+import static android.app.Notification.VISIBILITY_PRIVATE;
+import static android.app.Notification.VISIBILITY_SECRET;
+import static android.app.NotificationChannel.ALLOW_BUBBLE_ON;
 import static android.app.NotificationChannel.CONVERSATION_CHANNEL_ID_FORMAT;
+import static android.app.NotificationChannel.DEFAULT_ALLOW_BUBBLE;
+import static android.app.NotificationChannel.USER_LOCKED_ALLOW_BUBBLE;
 import static android.app.NotificationChannel.USER_LOCKED_IMPORTANCE;
+import static android.app.NotificationChannel.USER_LOCKED_LIGHTS;
+import static android.app.NotificationChannel.USER_LOCKED_PRIORITY;
+import static android.app.NotificationChannel.USER_LOCKED_SHOW_BADGE;
+import static android.app.NotificationChannel.USER_LOCKED_SOUND;
+import static android.app.NotificationChannel.USER_LOCKED_VIBRATION;
+import static android.app.NotificationChannel.USER_LOCKED_VISIBILITY;
 import static android.app.NotificationManager.BUBBLE_PREFERENCE_ALL;
 import static android.app.NotificationManager.BUBBLE_PREFERENCE_NONE;
 import static android.app.NotificationManager.BUBBLE_PREFERENCE_SELECTED;
@@ -29,13 +40,18 @@
 import static android.app.NotificationManager.IMPORTANCE_MAX;
 import static android.app.NotificationManager.IMPORTANCE_NONE;
 import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
+import static android.app.NotificationManager.VISIBILITY_NO_OVERRIDE;
 import static android.media.AudioAttributes.CONTENT_TYPE_SONIFICATION;
 import static android.media.AudioAttributes.USAGE_NOTIFICATION;
 import static android.os.UserHandle.USER_SYSTEM;
 import static android.util.StatsLog.ANNOTATION_ID_IS_UID;
 
+import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.PROPAGATE_CHANNEL_UPDATES_TO_CONVERSATIONS;
 import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES;
 import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES;
+import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__DENIED;
+import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__GRANTED;
+import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__NOT_REQUESTED;
 import static com.android.os.AtomsProto.PackageNotificationChannelPreferences.CHANNEL_ID_FIELD_NUMBER;
 import static com.android.os.AtomsProto.PackageNotificationChannelPreferences.CHANNEL_NAME_FIELD_NUMBER;
 import static com.android.os.AtomsProto.PackageNotificationChannelPreferences.IMPORTANCE_FIELD_NUMBER;
@@ -44,6 +60,7 @@
 import static com.android.os.AtomsProto.PackageNotificationChannelPreferences.IS_DEMOTED_CONVERSATION_FIELD_NUMBER;
 import static com.android.os.AtomsProto.PackageNotificationChannelPreferences.IS_IMPORTANT_CONVERSATION_FIELD_NUMBER;
 import static com.android.os.AtomsProto.PackageNotificationChannelPreferences.UID_FIELD_NUMBER;
+import static com.android.server.notification.NotificationChannelLogger.NotificationChannelEvent.NOTIFICATION_CHANNEL_UPDATED_BY_USER;
 import static com.android.server.notification.PreferencesHelper.DEFAULT_BUBBLE_PREFERENCE;
 import static com.android.server.notification.PreferencesHelper.NOTIFICATION_CHANNEL_COUNT_LIMIT;
 import static com.android.server.notification.PreferencesHelper.NOTIFICATION_CHANNEL_GROUP_COUNT_LIMIT;
@@ -102,6 +119,7 @@
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.permission.PermissionManager;
 import android.provider.Settings;
 import android.provider.Settings.Global;
 import android.provider.Settings.Secure;
@@ -121,6 +139,8 @@
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags;
+import com.android.internal.config.sysui.TestableFlagResolver;
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
 import com.android.os.AtomsProto.PackageNotificationPreferences;
@@ -131,6 +151,7 @@
 
 import org.json.JSONArray;
 import org.json.JSONObject;
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -178,6 +199,9 @@
     private static final Uri FILE_SOUND_URI =
             Uri.parse("file://" + TEST_AUTHORITY + "/product/media/test.ogg");
 
+    private static final Uri DEFAULT_SOUND_URI = Uri.parse(
+            "content://settings/system/notification_sound");
+
     @Mock PermissionHelper mPermissionHelper;
     @Mock RankingHandler mHandler;
     @Mock PackageManager mPm;
@@ -185,6 +209,7 @@
     @Mock Context mContext;
     @Mock ZenModeHelper mMockZenModeHelper;
     @Mock AppOpsManager mAppOpsManager;
+    @Mock PermissionManager mPermissionManager;
 
     private NotificationManager.Policy mTestNotificationPolicy;
 
@@ -218,6 +243,8 @@
                 InstrumentationRegistry.getContext().getResources());
         when(mContext.getContentResolver()).thenReturn(
                 InstrumentationRegistry.getContext().getContentResolver());
+        when(mPm.getPermissionFlags(any(), any(), any()))
+                .thenReturn(PackageManager.FLAG_PERMISSION_USER_SET);
         when(mContext.getPackageManager()).thenReturn(mPm);
         when(mContext.getApplicationInfo()).thenReturn(legacy);
         // most tests assume badging is enabled
@@ -303,7 +330,8 @@
         mStatsEventBuilderFactory = new WrappedSysUiStatsEvent.WrappedBuilderFactory();
 
         mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
-                mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false);
+                mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager,
+                mStatsEventBuilderFactory, false);
         resetZenModeHelper();
 
         mAudioAttributes = new AudioAttributes.Builder()
@@ -318,6 +346,11 @@
                 NotificationManagerService.REVIEW_NOTIF_STATE_UNKNOWN);
     }
 
+    @After
+    public void tearDown() {
+        SystemUiSystemPropertiesFlags.TEST_RESOLVER = null;
+    }
+
     private ByteArrayOutputStream writeXmlAndPurge(
             String pkg, int uid, boolean forBackup, int userId, String... channelIds)
             throws Exception {
@@ -502,7 +535,7 @@
         channel2.setSound(new Uri.Builder().scheme("test").build(), mAudioAttributes);
         channel2.enableLights(true);
         channel2.setBypassDnd(true);
-        channel2.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
+        channel2.setLockscreenVisibility(VISIBILITY_SECRET);
         channel2.enableVibration(true);
         channel2.setGroup(ncg.getId());
         channel2.setVibrationPattern(new long[]{100, 67, 145, 156});
@@ -570,7 +603,7 @@
         channel2.setSound(SOUND_URI, mAudioAttributes);
         channel2.enableLights(true);
         channel2.setBypassDnd(true);
-        channel2.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
+        channel2.setLockscreenVisibility(VISIBILITY_SECRET);
         channel2.enableVibration(false);
         channel2.setGroup(ncg.getId());
         channel2.setLightColor(Color.BLUE);
@@ -645,7 +678,7 @@
     @Test
     public void testReadXml_oldXml_migrates() throws Exception {
         mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
-                mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory, true);
+                mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mStatsEventBuilderFactory, true);
 
         String xml = "<ranking version=\"2\">\n"
                 + "<package name=\"" + PKG_N_MR1 + "\" uid=\"" + UID_N_MR1
@@ -711,7 +744,7 @@
     @Test
     public void testReadXml_oldXml_backup_migratesWhenPkgInstalled() throws Exception {
         mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
-                mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false);
+                mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false);
 
         when(mPm.getPackageUidAsUser("pkg1", USER_SYSTEM)).thenReturn(UNKNOWN_UID);
         when(mPm.getPackageUidAsUser("pkg2", USER_SYSTEM)).thenReturn(UNKNOWN_UID);
@@ -789,7 +822,7 @@
     @Test
     public void testReadXml_newXml_noMigration_showPermissionNotification() throws Exception {
         mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
-                mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory, true);
+                mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mStatsEventBuilderFactory, true);
 
         String xml = "<ranking version=\"3\">\n"
                 + "<package name=\"" + PKG_N_MR1 + "\" show_badge=\"true\">\n"
@@ -846,7 +879,7 @@
     @Test
     public void testReadXml_newXml_permissionNotificationOff() throws Exception {
         mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
-                mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false);
+                mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false);
 
         String xml = "<ranking version=\"3\">\n"
                 + "<package name=\"" + PKG_N_MR1 + "\" show_badge=\"true\">\n"
@@ -903,7 +936,7 @@
     @Test
     public void testReadXml_newXml_noMigration_noPermissionNotification() throws Exception {
         mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
-                mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory, true);
+                mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mStatsEventBuilderFactory, true);
 
         String xml = "<ranking version=\"4\">\n"
                 + "<package name=\"" + PKG_N_MR1 + "\" show_badge=\"true\">\n"
@@ -959,7 +992,7 @@
     @Test
     public void testReadXml_oldXml_migration_NoUid() throws Exception {
         mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
-                mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false);
+                mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false);
 
         when(mPm.getPackageUidAsUser("something", USER_SYSTEM)).thenReturn(UNKNOWN_UID);
         String xml = "<ranking version=\"2\">\n"
@@ -992,7 +1025,7 @@
     @Test
     public void testReadXml_newXml_noMigration_NoUid() throws Exception {
         mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
-                mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false);
+                mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false);
 
         when(mPm.getPackageUidAsUser("something", USER_SYSTEM)).thenReturn(UNKNOWN_UID);
         String xml = "<ranking version=\"3\">\n"
@@ -1024,7 +1057,7 @@
     @Test
     public void testChannelXmlForNonBackup_postMigration() throws Exception {
         mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
-                mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false);
+                mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false);
 
         ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>();
         appPermissions.put(new Pair<>(1, "first"), new Pair<>(true, false));
@@ -1047,7 +1080,7 @@
         channel2.setSound(null, null);
         channel2.enableLights(true);
         channel2.setBypassDnd(true);
-        channel2.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
+        channel2.setLockscreenVisibility(VISIBILITY_SECRET);
         channel2.enableVibration(false);
         channel2.setGroup(ncg.getId());
         channel2.setLightColor(Color.BLUE);
@@ -1110,7 +1143,7 @@
     @Test
     public void testChannelXmlForBackup_postMigration() throws Exception {
         mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
-                mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false);
+                mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false);
 
         ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>();
         appPermissions.put(new Pair<>(1, "first"), new Pair<>(true, false));
@@ -1133,7 +1166,7 @@
         channel2.setSound(null, null);
         channel2.enableLights(true);
         channel2.setBypassDnd(true);
-        channel2.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
+        channel2.setLockscreenVisibility(VISIBILITY_SECRET);
         channel2.enableVibration(false);
         channel2.setGroup(ncg.getId());
         channel2.setLightColor(Color.BLUE);
@@ -1202,7 +1235,7 @@
     @Test
     public void testChannelXmlForBackup_postMigration_noExternal() throws Exception {
         mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
-                mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false);
+                mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false);
 
         ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>();
         appPermissions.put(new Pair<>(UID_P, PKG_P), new Pair<>(true, false));
@@ -1221,7 +1254,7 @@
         channel2.setSound(null, null);
         channel2.enableLights(true);
         channel2.setBypassDnd(true);
-        channel2.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
+        channel2.setLockscreenVisibility(VISIBILITY_SECRET);
         channel2.enableVibration(false);
         channel2.setGroup(ncg.getId());
         channel2.setLightColor(Color.BLUE);
@@ -1287,7 +1320,7 @@
     @Test
     public void testChannelXmlForBackup_postMigration_noLocalSettings() throws Exception {
         mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
-                mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false);
+                mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false);
 
         ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>();
         appPermissions.put(new Pair<>(1, "first"), new Pair<>(true, false));
@@ -1498,7 +1531,7 @@
                 new FileNotFoundException("")).thenReturn(resId);
 
         mHelper = new PreferencesHelper(mContext, mPm, mHandler, mMockZenModeHelper,
-                mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false);
+                mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false);
 
         NotificationChannel channel =
                 new NotificationChannel("id", "name", IMPORTANCE_LOW);
@@ -1614,7 +1647,7 @@
                 NotificationChannel.DEFAULT_CHANNEL_ID, false);
         assertEquals(NotificationManager.IMPORTANCE_UNSPECIFIED, updated.getImportance());
         assertFalse(updated.canBypassDnd());
-        assertEquals(NotificationManager.VISIBILITY_NO_OVERRIDE, updated.getLockscreenVisibility());
+        assertEquals(VISIBILITY_NO_OVERRIDE, updated.getLockscreenVisibility());
         assertEquals(0, updated.getUserLockedFields());
     }
 
@@ -1642,9 +1675,9 @@
                 + "<package name=\"" + PKG_N_MR1
                 + "\" importance=\"" + NotificationManager.IMPORTANCE_HIGH
                 + "\" priority=\"" + Notification.PRIORITY_MAX + "\" visibility=\""
-                + Notification.VISIBILITY_SECRET + "\"" +" uid=\"" + UID_N_MR1 + "\" />\n"
+                + VISIBILITY_SECRET + "\"" + " uid=\"" + UID_N_MR1 + "\" />\n"
                 + "<package name=\"" + PKG_O + "\" uid=\"" + UID_O + "\" visibility=\""
-                + Notification.VISIBILITY_PRIVATE + "\" />\n"
+                + VISIBILITY_PRIVATE + "\" />\n"
                 + "</ranking>";
         TypedXmlPullParser parser = Xml.newFastPullParser();
         parser.setInput(new BufferedInputStream(new ByteArrayInputStream(preupgradeXml.getBytes())),
@@ -1656,10 +1689,10 @@
             mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, NotificationChannel.DEFAULT_CHANNEL_ID, false);
         assertEquals(NotificationManager.IMPORTANCE_HIGH, updated1.getImportance());
         assertTrue(updated1.canBypassDnd());
-        assertEquals(Notification.VISIBILITY_SECRET, updated1.getLockscreenVisibility());
+        assertEquals(VISIBILITY_SECRET, updated1.getLockscreenVisibility());
         assertEquals(NotificationChannel.USER_LOCKED_IMPORTANCE
-                | NotificationChannel.USER_LOCKED_PRIORITY
-                | NotificationChannel.USER_LOCKED_VISIBILITY,
+                | USER_LOCKED_PRIORITY
+                | USER_LOCKED_VISIBILITY,
                 updated1.getUserLockedFields());
 
         // No Default Channel created for updated packages
@@ -1804,7 +1837,7 @@
         channel.setSound(new Uri.Builder().scheme("test").build(), mAudioAttributes);
         channel.enableLights(true);
         channel.setBypassDnd(true);
-        channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
+        channel.setLockscreenVisibility(VISIBILITY_SECRET);
 
         assertTrue(mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel, false, false,
                 SYSTEM_UID, true));
@@ -1831,7 +1864,7 @@
     public void testUpdate_preUpgrade_updatesAppFields() throws Exception {
         assertTrue(mHelper.canShowBadge(PKG_N_MR1, UID_N_MR1));
         assertEquals(Notification.PRIORITY_DEFAULT, mHelper.getPackagePriority(PKG_N_MR1, UID_N_MR1));
-        assertEquals(NotificationManager.VISIBILITY_NO_OVERRIDE,
+        assertEquals(VISIBILITY_NO_OVERRIDE,
                 mHelper.getPackageVisibility(PKG_N_MR1, UID_N_MR1));
 
         NotificationChannel defaultChannel = mHelper.getNotificationChannel(
@@ -1840,7 +1873,7 @@
         defaultChannel.setShowBadge(false);
         defaultChannel.setImportance(IMPORTANCE_NONE);
         defaultChannel.setBypassDnd(true);
-        defaultChannel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
+        defaultChannel.setLockscreenVisibility(VISIBILITY_SECRET);
 
         mHelper.setAppImportanceLocked(PKG_N_MR1, UID_N_MR1);
         mHelper.updateNotificationChannel(PKG_N_MR1, UID_N_MR1, defaultChannel, true,
@@ -1849,7 +1882,7 @@
         // ensure app level fields are changed
         assertFalse(mHelper.canShowBadge(PKG_N_MR1, UID_N_MR1));
         assertEquals(Notification.PRIORITY_MAX, mHelper.getPackagePriority(PKG_N_MR1, UID_N_MR1));
-        assertEquals(Notification.VISIBILITY_SECRET, mHelper.getPackageVisibility(PKG_N_MR1,
+        assertEquals(VISIBILITY_SECRET, mHelper.getPackageVisibility(PKG_N_MR1,
                 UID_N_MR1));
     }
 
@@ -1861,13 +1894,13 @@
                 SYSTEM_UID, true);
         assertTrue(mHelper.canShowBadge(PKG_O, UID_O));
         assertEquals(Notification.PRIORITY_DEFAULT, mHelper.getPackagePriority(PKG_O, UID_O));
-        assertEquals(NotificationManager.VISIBILITY_NO_OVERRIDE,
+        assertEquals(VISIBILITY_NO_OVERRIDE,
                 mHelper.getPackageVisibility(PKG_O, UID_O));
 
         channel.setShowBadge(false);
         channel.setImportance(IMPORTANCE_NONE);
         channel.setBypassDnd(true);
-        channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
+        channel.setLockscreenVisibility(VISIBILITY_SECRET);
 
         mHelper.updateNotificationChannel(PKG_O, UID_O, channel, true,
                 SYSTEM_UID, true);
@@ -1875,7 +1908,7 @@
         // ensure app level fields are not changed
         assertTrue(mHelper.canShowBadge(PKG_O, UID_O));
         assertEquals(Notification.PRIORITY_DEFAULT, mHelper.getPackagePriority(PKG_O, UID_O));
-        assertEquals(NotificationManager.VISIBILITY_NO_OVERRIDE,
+        assertEquals(VISIBILITY_NO_OVERRIDE,
                 mHelper.getPackageVisibility(PKG_O, UID_O));
     }
 
@@ -1887,13 +1920,13 @@
                 SYSTEM_UID, true);
         assertTrue(mHelper.canShowBadge(PKG_N_MR1, UID_N_MR1));
         assertEquals(Notification.PRIORITY_DEFAULT, mHelper.getPackagePriority(PKG_N_MR1, UID_N_MR1));
-        assertEquals(NotificationManager.VISIBILITY_NO_OVERRIDE,
+        assertEquals(VISIBILITY_NO_OVERRIDE,
                 mHelper.getPackageVisibility(PKG_N_MR1, UID_N_MR1));
 
         channel.setShowBadge(false);
         channel.setImportance(IMPORTANCE_NONE);
         channel.setBypassDnd(true);
-        channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
+        channel.setLockscreenVisibility(VISIBILITY_SECRET);
 
         mHelper.updateNotificationChannel(PKG_N_MR1, UID_N_MR1, channel, true,
                 SYSTEM_UID, true);
@@ -1904,7 +1937,7 @@
         defaultChannel.setShowBadge(false);
         defaultChannel.setImportance(IMPORTANCE_NONE);
         defaultChannel.setBypassDnd(true);
-        defaultChannel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
+        defaultChannel.setLockscreenVisibility(VISIBILITY_SECRET);
 
         mHelper.updateNotificationChannel(PKG_N_MR1, UID_N_MR1, defaultChannel, true,
                 SYSTEM_UID, true);
@@ -1912,7 +1945,7 @@
         // ensure app level fields are not changed
         assertTrue(mHelper.canShowBadge(PKG_N_MR1, UID_N_MR1));
         assertEquals(Notification.PRIORITY_DEFAULT, mHelper.getPackagePriority(PKG_N_MR1, UID_N_MR1));
-        assertEquals(NotificationManager.VISIBILITY_NO_OVERRIDE,
+        assertEquals(VISIBILITY_NO_OVERRIDE,
                 mHelper.getPackageVisibility(PKG_N_MR1, UID_N_MR1));
     }
 
@@ -1928,7 +1961,7 @@
         channel.setSound(new Uri.Builder().scheme("test").build(), mAudioAttributes);
         channel.enableLights(true);
         channel.setBypassDnd(true);
-        channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
+        channel.setLockscreenVisibility(VISIBILITY_SECRET);
         channel.setShowBadge(true);
         channel.setAllowBubbles(false);
         channel.setImportantConversation(true);
@@ -1947,7 +1980,7 @@
         assertEquals(channel.getName(), savedChannel.getName());
         assertEquals(channel.shouldShowLights(), savedChannel.shouldShowLights());
         assertFalse(savedChannel.canBypassDnd());
-        assertFalse(Notification.VISIBILITY_SECRET == savedChannel.getLockscreenVisibility());
+        assertFalse(VISIBILITY_SECRET == savedChannel.getLockscreenVisibility());
         assertFalse(channel.isImportantConversation());
         assertEquals(channel.canShowBadge(), savedChannel.canShowBadge());
         assertEquals(channel.canBubble(), savedChannel.canBubble());
@@ -1962,7 +1995,7 @@
         channel.setSound(new Uri.Builder().scheme("test").build(), mAudioAttributes);
         channel.enableLights(true);
         channel.setBypassDnd(true);
-        channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
+        channel.setLockscreenVisibility(VISIBILITY_SECRET);
         channel.setShowBadge(true);
         channel.setAllowBubbles(false);
         int lockMask = 0;
@@ -1980,7 +2013,7 @@
         assertEquals(channel.getName(), savedChannel.getName());
         assertEquals(channel.shouldShowLights(), savedChannel.shouldShowLights());
         assertFalse(savedChannel.canBypassDnd());
-        assertFalse(Notification.VISIBILITY_SECRET == savedChannel.getLockscreenVisibility());
+        assertFalse(VISIBILITY_SECRET == savedChannel.getLockscreenVisibility());
         assertEquals(channel.canShowBadge(), savedChannel.canShowBadge());
         assertEquals(channel.canBubble(), savedChannel.canBubble());
     }
@@ -1991,7 +2024,7 @@
         mHelper.clearLockedFieldsLocked(channel);
         assertEquals(0, channel.getUserLockedFields());
 
-        channel.lockFields(NotificationChannel.USER_LOCKED_PRIORITY
+        channel.lockFields(USER_LOCKED_PRIORITY
                 | NotificationChannel.USER_LOCKED_IMPORTANCE);
         mHelper.clearLockedFieldsLocked(channel);
         assertEquals(0, channel.getUserLockedFields());
@@ -2005,19 +2038,19 @@
         final NotificationChannel update1 = getChannel();
         update1.setSound(new Uri.Builder().scheme("test").build(),
                 new AudioAttributes.Builder().build());
-        update1.lockFields(NotificationChannel.USER_LOCKED_PRIORITY);
+        update1.lockFields(USER_LOCKED_PRIORITY);
         mHelper.updateNotificationChannel(PKG_N_MR1, UID_N_MR1, update1, true, SYSTEM_UID, true);
-        assertEquals(NotificationChannel.USER_LOCKED_PRIORITY
-                | NotificationChannel.USER_LOCKED_SOUND,
+        assertEquals(USER_LOCKED_PRIORITY
+                | USER_LOCKED_SOUND,
                 mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, update1.getId(), false)
                         .getUserLockedFields());
 
         NotificationChannel update2 = getChannel();
         update2.enableVibration(true);
         mHelper.updateNotificationChannel(PKG_N_MR1, UID_N_MR1, update2, true, SYSTEM_UID, true);
-        assertEquals(NotificationChannel.USER_LOCKED_PRIORITY
-                        | NotificationChannel.USER_LOCKED_SOUND
-                        | NotificationChannel.USER_LOCKED_VIBRATION,
+        assertEquals(USER_LOCKED_PRIORITY
+                        | USER_LOCKED_SOUND
+                        | USER_LOCKED_VIBRATION,
                 mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, update2.getId(), false)
                         .getUserLockedFields());
     }
@@ -2030,15 +2063,15 @@
         final NotificationChannel update1 = getChannel();
         update1.setVibrationPattern(new long[]{7945, 46 ,246});
         mHelper.updateNotificationChannel(PKG_N_MR1, UID_N_MR1, update1, true, SYSTEM_UID, true);
-        assertEquals(NotificationChannel.USER_LOCKED_VIBRATION,
+        assertEquals(USER_LOCKED_VIBRATION,
                 mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, update1.getId(), false)
                         .getUserLockedFields());
 
         final NotificationChannel update2 = getChannel();
         update2.enableLights(true);
         mHelper.updateNotificationChannel(PKG_N_MR1, UID_N_MR1, update2, true, SYSTEM_UID, true);
-        assertEquals(NotificationChannel.USER_LOCKED_VIBRATION
-                        | NotificationChannel.USER_LOCKED_LIGHTS,
+        assertEquals(USER_LOCKED_VIBRATION
+                        | USER_LOCKED_LIGHTS,
                 mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, update2.getId(), false)
                         .getUserLockedFields());
     }
@@ -2051,7 +2084,7 @@
         final NotificationChannel update1 = getChannel();
         update1.setLightColor(Color.GREEN);
         mHelper.updateNotificationChannel(PKG_N_MR1, UID_N_MR1, update1, true, SYSTEM_UID, true);
-        assertEquals(NotificationChannel.USER_LOCKED_LIGHTS,
+        assertEquals(USER_LOCKED_LIGHTS,
                 mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, update1.getId(), false)
                         .getUserLockedFields());
 
@@ -2059,7 +2092,7 @@
         update2.setImportance(IMPORTANCE_DEFAULT);
         mHelper.updateNotificationChannel(PKG_N_MR1, UID_N_MR1, update2, true,
                 SYSTEM_UID, true);
-        assertEquals(NotificationChannel.USER_LOCKED_LIGHTS
+        assertEquals(USER_LOCKED_LIGHTS
                         | NotificationChannel.USER_LOCKED_IMPORTANCE,
                 mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, update2.getId(), false)
                         .getUserLockedFields());
@@ -2076,24 +2109,24 @@
         final NotificationChannel update1 = getChannel();
         update1.setBypassDnd(true);
         mHelper.updateNotificationChannel(PKG_N_MR1, UID_N_MR1, update1, true, SYSTEM_UID, true);
-        assertEquals(NotificationChannel.USER_LOCKED_PRIORITY,
+        assertEquals(USER_LOCKED_PRIORITY,
                 mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, update1.getId(), false)
                         .getUserLockedFields());
 
         final NotificationChannel update2 = getChannel();
-        update2.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
+        update2.setLockscreenVisibility(VISIBILITY_SECRET);
         mHelper.updateNotificationChannel(PKG_N_MR1, UID_N_MR1, update2, true, SYSTEM_UID, true);
-        assertEquals(NotificationChannel.USER_LOCKED_PRIORITY
-                        | NotificationChannel.USER_LOCKED_VISIBILITY,
+        assertEquals(USER_LOCKED_PRIORITY
+                        | USER_LOCKED_VISIBILITY,
                 mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, update2.getId(), false)
                         .getUserLockedFields());
 
         final NotificationChannel update3 = getChannel();
         update3.setShowBadge(false);
         mHelper.updateNotificationChannel(PKG_N_MR1, UID_N_MR1, update3, true, SYSTEM_UID, true);
-        assertEquals(NotificationChannel.USER_LOCKED_PRIORITY
-                        | NotificationChannel.USER_LOCKED_VISIBILITY
-                        | NotificationChannel.USER_LOCKED_SHOW_BADGE,
+        assertEquals(USER_LOCKED_PRIORITY
+                        | USER_LOCKED_VISIBILITY
+                        | USER_LOCKED_SHOW_BADGE,
                 mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, update3.getId(), false)
                         .getUserLockedFields());
     }
@@ -2110,7 +2143,7 @@
         update.setAllowBubbles(true);
         mHelper.updateNotificationChannel(PKG_N_MR1, UID_N_MR1, update, true,
                 SYSTEM_UID, true);
-        assertEquals(NotificationChannel.USER_LOCKED_ALLOW_BUBBLE,
+        assertEquals(USER_LOCKED_ALLOW_BUBBLE,
                 mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, update.getId(), false)
                         .getUserLockedFields());
     }
@@ -2146,7 +2179,7 @@
         channel.setSound(new Uri.Builder().scheme("test").build(), mAudioAttributes);
         channel.enableLights(true);
         channel.setBypassDnd(true);
-        channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
+        channel.setLockscreenVisibility(VISIBILITY_SECRET);
         channel.enableVibration(true);
         channel.setVibrationPattern(new long[]{100, 67, 145, 156});
 
@@ -2174,7 +2207,7 @@
         channel.setSound(new Uri.Builder().scheme("test").build(), mAudioAttributes);
         channel.enableLights(true);
         channel.setBypassDnd(true);
-        channel.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
+        channel.setLockscreenVisibility(VISIBILITY_PRIVATE);
         channel.enableVibration(true);
         channel.setVibrationPattern(new long[]{100, 67, 145, 156});
         channelMap.put(channel.getId(), channel);
@@ -2443,7 +2476,7 @@
                 NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND, 0);
         when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy);
         mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
-                mPermissionHelper, mLogger,
+                mPermissionHelper, mPermissionManager, mLogger,
                 mAppOpsManager, mStatsEventBuilderFactory, false);
 
 
@@ -2474,7 +2507,7 @@
                 NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND, 0);
         when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy);
         mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
-                mPermissionHelper, mLogger,
+                mPermissionHelper, mPermissionManager, mLogger,
                 mAppOpsManager, mStatsEventBuilderFactory, false);
 
         // create notification channel that can bypass dnd, but app is blocked
@@ -2498,7 +2531,7 @@
                 NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND, 0);
         when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy);
         mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
-                mPermissionHelper, mLogger,
+                mPermissionHelper, mPermissionManager, mLogger,
                 mAppOpsManager, mStatsEventBuilderFactory, false);
 
         // create notification channel that can bypass dnd, but app is blocked
@@ -2552,7 +2585,7 @@
                 NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND, 0);
         when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy);
         mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
-                mPermissionHelper, mLogger,
+                mPermissionHelper, mPermissionManager, mLogger,
                 mAppOpsManager, mStatsEventBuilderFactory, false);
         assertFalse(mHelper.areChannelsBypassingDnd());
         verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyBoolean());
@@ -2565,7 +2598,7 @@
         mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0, 0, 0);
         when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy);
         mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
-                mPermissionHelper, mLogger,
+                mPermissionHelper, mPermissionManager, mLogger,
                 mAppOpsManager, mStatsEventBuilderFactory, false);
         assertFalse(mHelper.areChannelsBypassingDnd());
         verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), anyInt(), anyBoolean());
@@ -3668,7 +3701,7 @@
                 + "</package>\n"
                 + "</ranking>\n";
         mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
-                mPermissionHelper, mLogger,
+                mPermissionHelper, mPermissionManager, mLogger,
                 mAppOpsManager, mStatsEventBuilderFactory, false);
         loadByteArrayXml(preQXml.getBytes(), true, USER_SYSTEM);
 
@@ -3682,7 +3715,7 @@
 
         ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false, UserHandle.USER_ALL);
         mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
-                mPermissionHelper, mLogger,
+                mPermissionHelper, mPermissionManager, mLogger,
                 mAppOpsManager, mStatsEventBuilderFactory, false);
         loadStreamXml(baos, false, UserHandle.USER_ALL);
 
@@ -3752,7 +3785,7 @@
 
         ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false, UserHandle.USER_ALL);
         mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
-                mPermissionHelper, mLogger,
+                mPermissionHelper, mPermissionManager, mLogger,
                 mAppOpsManager, mStatsEventBuilderFactory, false);
         loadStreamXml(baos, false, UserHandle.USER_ALL);
 
@@ -3765,7 +3798,7 @@
 
         ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false, UserHandle.USER_ALL);
         mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
-                mPermissionHelper, mLogger,
+                mPermissionHelper, mPermissionManager, mLogger,
                 mAppOpsManager, mStatsEventBuilderFactory, false);
         loadStreamXml(baos, false, UserHandle.USER_ALL);
 
@@ -3779,7 +3812,7 @@
 
         ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false, UserHandle.USER_ALL);
         mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
-                mPermissionHelper, mLogger,
+                mPermissionHelper, mPermissionManager, mLogger,
                 mAppOpsManager, mStatsEventBuilderFactory, false);
         loadStreamXml(baos, false, UserHandle.USER_ALL);
 
@@ -3795,7 +3828,7 @@
 
         ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false, UserHandle.USER_ALL);
         mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
-                mPermissionHelper, mLogger,
+                mPermissionHelper, mPermissionManager, mLogger,
                 mAppOpsManager, mStatsEventBuilderFactory, false);
         loadStreamXml(baos, false, UserHandle.USER_ALL);
 
@@ -3851,7 +3884,7 @@
 
         ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false, UserHandle.USER_ALL);
         mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
-                mPermissionHelper, mLogger,
+                mPermissionHelper, mPermissionManager, mLogger,
                 mAppOpsManager, mStatsEventBuilderFactory, false);
         loadStreamXml(baos, false, UserHandle.USER_ALL);
 
@@ -3889,7 +3922,7 @@
 
         ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false, UserHandle.USER_ALL);
         mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
-                mPermissionHelper, mLogger,
+                mPermissionHelper, mPermissionManager, mLogger,
                 mAppOpsManager, mStatsEventBuilderFactory, false);
         loadStreamXml(baos, false, UserHandle.USER_ALL);
 
@@ -4547,7 +4580,7 @@
     @Test
     public void testPlaceholderConversationId_shortcutRequired() throws Exception {
         mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
-                mPermissionHelper, mLogger,
+                mPermissionHelper, mPermissionManager, mLogger,
                 mAppOpsManager, mStatsEventBuilderFactory, false);
 
         final String xml = "<ranking version=\"1\">\n"
@@ -4567,7 +4600,7 @@
     @Test
     public void testNormalConversationId_shortcutRequired() throws Exception {
         mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
-                mPermissionHelper, mLogger,
+                mPermissionHelper, mPermissionManager, mLogger,
                 mAppOpsManager, mStatsEventBuilderFactory, false);
 
         final String xml = "<ranking version=\"1\">\n"
@@ -4587,7 +4620,7 @@
     @Test
     public void testNoConversationId_shortcutRequired() throws Exception {
         mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
-                mPermissionHelper, mLogger,
+                mPermissionHelper, mPermissionManager, mLogger,
                 mAppOpsManager, mStatsEventBuilderFactory, false);
 
         final String xml = "<ranking version=\"1\">\n"
@@ -4607,7 +4640,7 @@
     @Test
     public void testDeleted_noTime() throws Exception {
         mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
-                mPermissionHelper, mLogger,
+                mPermissionHelper, mPermissionManager, mLogger,
                 mAppOpsManager, mStatsEventBuilderFactory, false);
 
         final String xml = "<ranking version=\"1\">\n"
@@ -4627,7 +4660,7 @@
     @Test
     public void testDeleted_twice() throws Exception {
         mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
-                mPermissionHelper, mLogger,
+                mPermissionHelper, mPermissionManager, mLogger,
                 mAppOpsManager, mStatsEventBuilderFactory, false);
 
         mHelper.createNotificationChannel(
@@ -4642,7 +4675,7 @@
     @Test
     public void testDeleted_recentTime() throws Exception {
         mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
-                mPermissionHelper, mLogger,
+                mPermissionHelper, mPermissionManager, mLogger,
                 mAppOpsManager, mStatsEventBuilderFactory, false);
 
         mHelper.createNotificationChannel(
@@ -4661,7 +4694,7 @@
                 null);
         parser.nextTag();
         mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
-                mPermissionHelper, mLogger,
+                mPermissionHelper, mPermissionManager, mLogger,
                 mAppOpsManager, mStatsEventBuilderFactory, false);
         mHelper.readXml(parser, true, USER_SYSTEM);
 
@@ -4673,7 +4706,7 @@
     @Test
     public void testUnDelete_time() throws Exception {
         mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
-                mPermissionHelper, mLogger,
+                mPermissionHelper, mPermissionManager, mLogger,
                 mAppOpsManager, mStatsEventBuilderFactory, false);
 
         mHelper.createNotificationChannel(
@@ -4695,7 +4728,7 @@
     @Test
     public void testDeleted_longTime() throws Exception {
         mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
-                mPermissionHelper, mLogger,
+                mPermissionHelper, mPermissionManager, mLogger,
                 mAppOpsManager, mStatsEventBuilderFactory, false);
 
         long time = System.currentTimeMillis() - (DateUtils.DAY_IN_MILLIS * 30);
@@ -5108,6 +5141,231 @@
     }
 
     @Test
+    public void testUpdateConversationParent_updatesConversations() {
+        SystemUiSystemPropertiesFlags.TEST_RESOLVER = new TestableFlagResolver()
+                .setFlagOverride(PROPAGATE_CHANNEL_UPDATES_TO_CONVERSATIONS, true);
+
+        NotificationChannel parent =
+                new NotificationChannel("messages", "Messages", IMPORTANCE_DEFAULT);
+        mHelper.createNotificationChannel(PKG_O, UID_O, parent, /* fromTargetApp= */ true,
+                /* hasDndAccess= */ false, UID_O, /* fromSystemOrSystemUi= */ false);
+        NotificationChannel convoA = new NotificationChannel("A", "With A", IMPORTANCE_DEFAULT);
+        convoA.setConversationId(parent.getId(), "A");
+        mHelper.createNotificationChannel(PKG_O, UID_O, convoA, /* fromTargetApp= */ true,
+                /* hasDndAccess= */ false, UID_O, /* fromSystemOrSystemUi= */ false);
+        NotificationChannel convoB = new NotificationChannel("B", "With B", IMPORTANCE_DEFAULT);
+        convoB.setConversationId(parent.getId(), "B");
+        mHelper.createNotificationChannel(PKG_O, UID_O, convoB, /* fromTargetApp= */ true,
+                /* hasDndAccess= */ false, UID_O, /* fromSystemOrSystemUi= */ false);
+        assertThat(mHelper.getNotificationChannel(PKG_O, UID_O, "messages", /* includeDeleted= */
+                false).shouldVibrate()).isFalse();
+        assertThat(mHelper.getNotificationChannel(PKG_O, UID_O, "A",
+                /* includeDeleted= */ false).shouldVibrate()).isFalse();
+        assertThat(mHelper.getNotificationChannel(PKG_O, UID_O, "B",
+                /* includeDeleted= */ false).shouldVibrate()).isFalse();
+        mLogger.clear();
+
+        NotificationChannel parentUpdate = cloneChannel(parent);
+        parentUpdate.enableVibration(true);
+        mHelper.updateNotificationChannel(PKG_O, UID_O, parentUpdate, /* fromUser= */ true, UID_O,
+                /* fromSystemOrSystemUi= */ true);
+
+        assertThat(mHelper.getNotificationChannel(PKG_O, UID_O, "messages",
+                /* includeDeleted= */ false).shouldVibrate()).isTrue();
+        assertThat(mHelper.getNotificationChannel(PKG_O, UID_O, "A",
+                /* includeDeleted= */ false).shouldVibrate()).isTrue();
+        assertThat(mHelper.getNotificationChannel(PKG_O, UID_O, "B",
+                /* includeDeleted= */ false).shouldVibrate()).isTrue();
+
+        // Verify that the changes to parent and children were logged.
+        assertThat(mLogger.getCalls()).containsExactly(
+                        new NotificationChannelLoggerFake.CallRecord(
+                                NOTIFICATION_CHANNEL_UPDATED_BY_USER, "messages"),
+                        new NotificationChannelLoggerFake.CallRecord(
+                                NOTIFICATION_CHANNEL_UPDATED_BY_USER, "A"),
+                        new NotificationChannelLoggerFake.CallRecord(
+                                NOTIFICATION_CHANNEL_UPDATED_BY_USER, "B"))
+                .inOrder();
+    }
+
+    @Test
+    public void testUpdateConversationParent_updatesUnlockedFields() {
+        SystemUiSystemPropertiesFlags.TEST_RESOLVER = new TestableFlagResolver()
+                .setFlagOverride(PROPAGATE_CHANNEL_UPDATES_TO_CONVERSATIONS, true);
+
+        NotificationChannel parent =
+                new NotificationChannel("messages", "Messages", IMPORTANCE_DEFAULT);
+        mHelper.createNotificationChannel(PKG_O, UID_O, parent, /* fromTargetApp= */ true,
+                /* hasDndAccess= */ false, UID_O, /* fromSystemOrSystemUi= */ false);
+        NotificationChannel convo = new NotificationChannel("A", "With A", IMPORTANCE_DEFAULT);
+        convo.setConversationId(parent.getId(), "A");
+        mHelper.createNotificationChannel(PKG_O, UID_O, convo, /* fromTargetApp= */ true,
+                /* hasDndAccess= */ false, UID_O, /* fromSystemOrSystemUi= */ false);
+        NotificationChannel originalChild = mHelper.getNotificationChannel(PKG_O, UID_O,
+                convo.getId(), /* includeDeleted= */ false);
+        assertThat(originalChild.canBypassDnd()).isFalse();
+        assertThat(originalChild.getLockscreenVisibility()).isEqualTo(VISIBILITY_NO_OVERRIDE);
+        assertThat(originalChild.getImportance()).isEqualTo(IMPORTANCE_DEFAULT);
+        assertThat(originalChild.shouldShowLights()).isFalse();
+        assertThat(originalChild.getSound()).isEqualTo(DEFAULT_SOUND_URI);
+        assertThat(originalChild.shouldVibrate()).isFalse();
+        assertThat(originalChild.canShowBadge()).isTrue();
+        assertThat(originalChild.getAllowBubbles()).isEqualTo(DEFAULT_ALLOW_BUBBLE);
+
+        NotificationChannel parentUpdate = cloneChannel(parent);
+        parentUpdate.setBypassDnd(true);
+        parentUpdate.setLockscreenVisibility(VISIBILITY_SECRET);
+        parentUpdate.setImportance(IMPORTANCE_HIGH);
+        parentUpdate.enableLights(true);
+        parentUpdate.setSound(SOUND_URI, mAudioAttributes);
+        parentUpdate.enableVibration(true);
+        parentUpdate.setShowBadge(false);
+        parentUpdate.setAllowBubbles(true);
+        mHelper.updateNotificationChannel(PKG_O, UID_O, parentUpdate, /* fromUser= */ true,
+                UID_O, /* fromSystemOrSystemUi= */ true);
+
+        NotificationChannel updatedChild = mHelper.getNotificationChannel(PKG_O, UID_O,
+                "A", /* includeDeleted= */ false);
+        assertThat(updatedChild.canBypassDnd()).isTrue();
+        assertThat(updatedChild.getLockscreenVisibility()).isEqualTo(VISIBILITY_SECRET);
+        assertThat(updatedChild.getImportance()).isEqualTo(IMPORTANCE_HIGH);
+        assertThat(updatedChild.shouldShowLights()).isTrue();
+        assertThat(updatedChild.getSound()).isEqualTo(SOUND_URI);
+        assertThat(updatedChild.shouldVibrate()).isTrue();
+        assertThat(updatedChild.canShowBadge()).isFalse();
+        assertThat(updatedChild.getAllowBubbles()).isEqualTo(ALLOW_BUBBLE_ON);
+    }
+
+    @Test
+    public void testUpdateConversationParent_doesNotUpdateLockedFields() {
+        SystemUiSystemPropertiesFlags.TEST_RESOLVER = new TestableFlagResolver()
+                .setFlagOverride(PROPAGATE_CHANNEL_UPDATES_TO_CONVERSATIONS, true);
+        NotificationChannel parent =
+                new NotificationChannel("messages", "Messages", IMPORTANCE_DEFAULT);
+        mHelper.createNotificationChannel(PKG_O, UID_O, parent, /* fromTargetApp= */ true,
+                /* hasDndAccess= */ false, UID_O, /* fromSystemOrSystemUi= */ false);
+        NotificationChannel convo = new NotificationChannel("A", "With A", IMPORTANCE_DEFAULT);
+        convo.setConversationId(parent.getId(), "A");
+        mHelper.createNotificationChannel(PKG_O, UID_O, convo, /* fromTargetApp= */ true,
+                /* hasDndAccess= */ false, UID_O, /* fromSystemOrSystemUi= */ false);
+        // Directly update the child to lock every field.
+        // Normally this would be the result of one or more "fromUser" updates with modified fields.
+        convo.lockFields(
+                USER_LOCKED_PRIORITY | USER_LOCKED_VISIBILITY | USER_LOCKED_IMPORTANCE
+                        | USER_LOCKED_LIGHTS | USER_LOCKED_VIBRATION | USER_LOCKED_SOUND
+                        | USER_LOCKED_SHOW_BADGE | USER_LOCKED_ALLOW_BUBBLE);
+        mLogger.clear();
+
+        NotificationChannel parentUpdate = cloneChannel(parent);
+        parentUpdate.setBypassDnd(true);
+        parentUpdate.setLockscreenVisibility(VISIBILITY_SECRET);
+        parentUpdate.setImportance(IMPORTANCE_HIGH);
+        parentUpdate.enableLights(true);
+        parentUpdate.setSound(SOUND_URI, mAudioAttributes);
+        parentUpdate.enableVibration(true);
+        parentUpdate.setShowBadge(false);
+        parentUpdate.setAllowBubbles(true);
+        mHelper.updateNotificationChannel(PKG_O, UID_O, parentUpdate, /* fromUser= */ true,
+                UID_O, /* fromSystemOrSystemUi= */ true);
+
+        NotificationChannel updatedChild = mHelper.getNotificationChannel(PKG_O, UID_O,
+                "A", /* includeDeleted= */ false);
+        assertThat(updatedChild.canBypassDnd()).isFalse();
+        assertThat(updatedChild.getLockscreenVisibility()).isEqualTo(VISIBILITY_NO_OVERRIDE);
+        assertThat(updatedChild.getImportance()).isEqualTo(IMPORTANCE_DEFAULT);
+        assertThat(updatedChild.shouldShowLights()).isFalse();
+        assertThat(updatedChild.getSound()).isEqualTo(DEFAULT_SOUND_URI);
+        assertThat(updatedChild.shouldVibrate()).isFalse();
+        assertThat(updatedChild.canShowBadge()).isTrue();
+        assertThat(updatedChild.getAllowBubbles()).isEqualTo(DEFAULT_ALLOW_BUBBLE);
+
+        // Verify that only the changes to the parent were logged.
+        assertThat(mLogger.getCalls()).containsExactly(
+                        new NotificationChannelLoggerFake.CallRecord(
+                                NOTIFICATION_CHANNEL_UPDATED_BY_USER, "messages"));
+    }
+
+    @Test
+    public void testUpdateConversationParent_updatesDemotedConversation() {
+        SystemUiSystemPropertiesFlags.TEST_RESOLVER = new TestableFlagResolver()
+                .setFlagOverride(PROPAGATE_CHANNEL_UPDATES_TO_CONVERSATIONS, true);
+        NotificationChannel parent =
+                new NotificationChannel("messages", "Messages", IMPORTANCE_DEFAULT);
+        mHelper.createNotificationChannel(PKG_O, UID_O, parent, /* fromTargetApp= */ true,
+                /* hasDndAccess= */ false, UID_O, /* fromSystemOrSystemUi= */ false);
+        NotificationChannel convo = new NotificationChannel("A", "With A", IMPORTANCE_DEFAULT);
+        convo.setConversationId(parent.getId(), "A");
+        convo.setDemoted(true);
+        mHelper.createNotificationChannel(PKG_O, UID_O, convo, /* fromTargetApp= */ true,
+                /* hasDndAccess= */ false, UID_O, /* fromSystemOrSystemUi= */ false);
+        NotificationChannel originalChild = mHelper.getNotificationChannel(PKG_O, UID_O,
+                convo.getId(), /* includeDeleted= */ false);
+        assertThat(originalChild.shouldVibrate()).isFalse();
+
+        NotificationChannel parentUpdate = cloneChannel(parent);
+        parentUpdate.enableVibration(true);
+        mHelper.updateNotificationChannel(PKG_O, UID_O, parentUpdate, /* fromUser= */ true,
+                UID_O, /* fromSystemOrSystemUi= */ true);
+
+        NotificationChannel updatedChild = mHelper.getNotificationChannel(PKG_O, UID_O,
+                "A", /* includeDeleted= */ false);
+        assertThat(updatedChild.shouldVibrate()).isTrue();
+    }
+
+    @Test
+    public void testUpdateConversationParent_updatesDeletedConversation() {
+        SystemUiSystemPropertiesFlags.TEST_RESOLVER = new TestableFlagResolver()
+                .setFlagOverride(PROPAGATE_CHANNEL_UPDATES_TO_CONVERSATIONS, true);
+        NotificationChannel parent =
+                new NotificationChannel("messages", "Messages", IMPORTANCE_DEFAULT);
+        mHelper.createNotificationChannel(PKG_O, UID_O, parent, /* fromTargetApp= */ true,
+                /* hasDndAccess= */ false, UID_O, /* fromSystemOrSystemUi= */ false);
+        NotificationChannel convo = new NotificationChannel("A", "With A", IMPORTANCE_DEFAULT);
+        convo.setConversationId(parent.getId(), "A");
+        mHelper.createNotificationChannel(PKG_O, UID_O, convo, /* fromTargetApp= */ true,
+                /* hasDndAccess= */ false, UID_O, /* fromSystemOrSystemUi= */ false);
+        mHelper.deleteNotificationChannel(PKG_O, UID_O, "A", UID_O,
+                /* fromSystemOrSystemUi= */ false);
+        assertThat(mHelper.getNotificationChannel(PKG_O, UID_O, "A",
+                /* includeDeleted= */ false)).isNull();
+
+        NotificationChannel parentUpdate = cloneChannel(parent);
+        parentUpdate.enableVibration(true);
+        mHelper.updateNotificationChannel(PKG_O, UID_O, parentUpdate, /* fromUser= */ true,
+                UID_O, /* fromSystemOrSystemUi= */ true);
+
+        NotificationChannel updatedChild = mHelper.getNotificationChannel(PKG_O, UID_O,
+                "A", /* includeDeleted= */ true);
+        assertThat(updatedChild.shouldVibrate()).isTrue();
+    }
+
+    @Test
+    public void testUpdateConversationParent_flagOff_doesNotUpdateConversations() {
+        SystemUiSystemPropertiesFlags.TEST_RESOLVER = new TestableFlagResolver()
+                .setFlagOverride(PROPAGATE_CHANNEL_UPDATES_TO_CONVERSATIONS, false);
+        NotificationChannel parent =
+                new NotificationChannel("messages", "Messages", IMPORTANCE_DEFAULT);
+        mHelper.createNotificationChannel(PKG_O, UID_O, parent, /* fromTargetApp= */ true,
+                /* hasDndAccess= */ false, UID_O, /* fromSystemOrSystemUi= */ false);
+        NotificationChannel convo = new NotificationChannel("A", "With A", IMPORTANCE_DEFAULT);
+        convo.setConversationId(parent.getId(), "A");
+        mHelper.createNotificationChannel(PKG_O, UID_O, convo, /* fromTargetApp= */ true,
+                /* hasDndAccess= */ false, UID_O, /* fromSystemOrSystemUi= */ false);
+        NotificationChannel originalChild = mHelper.getNotificationChannel(PKG_O, UID_O,
+                convo.getId(), /* includeDeleted= */ false);
+        assertThat(originalChild.shouldVibrate()).isFalse();
+
+        NotificationChannel parentUpdate = cloneChannel(parent);
+        parentUpdate.enableVibration(true);
+        mHelper.updateNotificationChannel(PKG_O, UID_O, parentUpdate, /* fromUser= */ true,
+                UID_O, /* fromSystemOrSystemUi= */ true);
+
+        NotificationChannel untouchedChild = mHelper.getNotificationChannel(PKG_O, UID_O,
+                "A", /* includeDeleted= */ false);
+        assertThat(untouchedChild.shouldVibrate()).isFalse();
+    }
+
+    @Test
     public void testInvalidMessageSent() {
         // create package preferences
         mHelper.canShowBadge(PKG_P, UID_P);
@@ -5440,11 +5698,7 @@
         clearInvocations(mHandler);
         // Note: Creating a NotificationChannel identical to the original is not equals(), because
         // of mOriginalImportance. So we create a "true copy" instead.
-        Parcel parcel = Parcel.obtain();
-        original.writeToParcel(parcel, 0);
-        parcel.setDataPosition(0);
-        NotificationChannel same = NotificationChannel.CREATOR.createFromParcel(parcel);
-        parcel.recycle();
+        NotificationChannel same = cloneChannel(original);
 
         mHelper.updateNotificationChannel(PKG_P, 0, same, false, 0, false);
 
@@ -5464,4 +5718,104 @@
 
         verifyZeroInteractions(mHandler);
     }
+
+    @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);
+
+        assertEquals(PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__NOT_REQUESTED, fsiState);
+    }
+
+    @Test
+    public void testGetFsiState_permissionGranted_enumGranted() {
+        when(mPermissionManager.checkPermissionForPreflight(any(), any()))
+                .thenReturn(PermissionManager.PERMISSION_GRANTED);
+
+        final int fsiState = mHelper.getFsiState("pkg", /* uid= */ 0,
+                /* requestedFsiPermission= */ true, /* isFlagEnabled= */ true);
+
+        assertEquals(PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__GRANTED, fsiState);
+    }
+
+    @Test
+    public void testGetFsiState_permissionSoftDenied_enumDenied() {
+        when(mPermissionManager.checkPermissionForPreflight(any(), any()))
+                .thenReturn(PermissionManager.PERMISSION_SOFT_DENIED);
+
+        final int fsiState = mHelper.getFsiState("pkg", /* uid= */ 0,
+                /* requestedFsiPermission = */ true, /* isFlagEnabled= */ true);
+
+        assertEquals(PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__DENIED, fsiState);
+    }
+
+    @Test
+    public void testGetFsiState_permissionHardDenied_enumDenied() {
+        when(mPermissionManager.checkPermissionForPreflight(any(), any()))
+                .thenReturn(PermissionManager.PERMISSION_HARD_DENIED);
+
+        final int fsiState = mHelper.getFsiState("pkg", /* uid= */ 0,
+                /* requestedFsiPermission = */ true, /* isFlagEnabled= */ true);
+
+        assertEquals(PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__DENIED, fsiState);
+    }
+
+    @Test
+    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);
+
+        assertFalse(isUserSet);
+    }
+
+    @Test
+    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);
+
+        assertTrue(isUserSet);
+    }
+
+    @Test
+    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);
+
+        assertFalse(isUserSet);
+    }
+
+    private static NotificationChannel cloneChannel(NotificationChannel original) {
+        Parcel parcel = Parcel.obtain();
+        try {
+            original.writeToParcel(parcel, 0);
+            parcel.setDataPosition(0);
+            return NotificationChannel.CREATOR.createFromParcel(parcel);
+        } finally {
+            parcel.recycle();
+        }
+    }
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/RateEstimatorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/RateEstimatorTest.java
index 68aa1dc..131aaa0 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/RateEstimatorTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/RateEstimatorTest.java
@@ -15,8 +15,9 @@
  */
 package com.android.server.notification;
 
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
+import static com.google.common.truth.Truth.assertThat;
+
+import static java.util.concurrent.TimeUnit.HOURS;
 
 import android.test.suitebuilder.annotation.SmallTest;
 
@@ -42,110 +43,120 @@
 
     @Test
     public void testRunningTimeBackwardDoesntExplodeUpdate() throws Exception {
-        assertUpdateTime(mTestStartTime);
-        assertUpdateTime(mTestStartTime - 1000L);
+        updateAndVerifyRate(mTestStartTime);
+        updateAndVerifyRate(mTestStartTime - 1000L);
     }
 
     @Test
     public void testRunningTimeBackwardDoesntExplodeGet() throws Exception {
-        assertUpdateTime(mTestStartTime);
+        updateAndVerifyRate(mTestStartTime);
         final float rate = mEstimator.getRate(mTestStartTime - 1000L);
-        assertFalse(Float.isInfinite(rate));
-        assertFalse(Float.isNaN(rate));
+        assertThat(rate).isFinite();
     }
 
     @Test
     public void testInstantaneousEventsDontExplodeUpdate() throws Exception {
-        assertUpdateTime(mTestStartTime);
-        assertUpdateTime(mTestStartTime);
+        updateAndVerifyRate(mTestStartTime);
+        updateAndVerifyRate(mTestStartTime);
     }
 
     @Test
     public void testInstantaneousEventsDontExplodeGet() throws Exception {
-        assertUpdateTime(mTestStartTime);
-        assertUpdateTime(mTestStartTime);
+        updateAndVerifyRate(mTestStartTime);
+        updateAndVerifyRate(mTestStartTime);
         final float rate = mEstimator.getRate(mTestStartTime);
-        assertFalse(Float.isInfinite(rate));
-        assertFalse(Float.isNaN(rate));
+        assertThat(rate).isFinite();
     }
 
     @Test
     public void testInstantaneousBurstIsEstimatedUnderTwoPercent() throws Exception {
-        assertUpdateTime(mTestStartTime);
+        updateAndVerifyRate(mTestStartTime);
         long eventStart = mTestStartTime + 1000; // start event a long time after initialization
         long nextEventTime = postEvents(eventStart, 0, 5); // five events at \inf
         final float rate = mEstimator.getRate(nextEventTime);
-        assertLessThan("Rate", rate, 20f);
+        assertThat(rate).isLessThan(20f);
     }
 
     @Test
     public void testCompactBurstIsEstimatedUnderTwoPercent() throws Exception {
-        assertUpdateTime(mTestStartTime);
+        updateAndVerifyRate(mTestStartTime);
         long eventStart = mTestStartTime + 1000; // start event a long time after initialization
         long nextEventTime = postEvents(eventStart, 1, 5); // five events at 1000Hz
         final float rate = mEstimator.getRate(nextEventTime);
-        assertLessThan("Rate", rate, 20f);
+        assertThat(rate).isLessThan(20f);
     }
 
     @Test
     public void testSustained1000HzBurstIsEstimatedOverNinetyPercent() throws Exception {
-        assertUpdateTime(mTestStartTime);
+        updateAndVerifyRate(mTestStartTime);
         long eventStart = mTestStartTime + 1000; // start event a long time after initialization
         long nextEventTime = postEvents(eventStart, 1, 100); // one hundred events at 1000Hz
         final float rate = mEstimator.getRate(nextEventTime);
-        assertGreaterThan("Rate", rate, 900f);
+        assertThat(rate).isGreaterThan(900f);
     }
 
     @Test
     public void testSustained100HzBurstIsEstimatedOverNinetyPercent() throws Exception {
-        assertUpdateTime(mTestStartTime);
+        updateAndVerifyRate(mTestStartTime);
         long eventStart = mTestStartTime + 1000; // start event a long time after initialization
         long nextEventTime = postEvents(eventStart, 10, 100); // one hundred events at 100Hz
         final float rate = mEstimator.getRate(nextEventTime);
 
-        assertGreaterThan("Rate", rate, 90f);
+        assertThat(rate).isGreaterThan(90f);
     }
 
     @Test
     public void testRecoverQuicklyAfterSustainedBurst() throws Exception {
-        assertUpdateTime(mTestStartTime);
+        updateAndVerifyRate(mTestStartTime);
         long eventStart = mTestStartTime + 1000; // start event a long time after initialization
-        long nextEventTime = postEvents(eventStart, 10, 1000); // one hundred events at 100Hz
-        final float rate = mEstimator.getRate(nextEventTime + 5000L); // two seconds later
-        assertLessThan("Rate", rate, 2f);
+        long nextEventTime = postEvents(eventStart, 10, 1000); // one thousand events at 100Hz
+        final float rate = mEstimator.getRate(nextEventTime + 5000L); // five seconds later
+        assertThat(rate).isLessThan(2f);
     }
 
     @Test
     public void testEstimateShouldNotOvershoot() throws Exception {
-        assertUpdateTime(mTestStartTime);
+        updateAndVerifyRate(mTestStartTime);
         long eventStart = mTestStartTime + 1000; // start event a long time after initialization
-        long nextEventTime = postEvents(eventStart, 1, 1000); // one thousand events at 1000Hz
+        long nextEventTime = postEvents(eventStart, 1, 5000); // five thousand events at 1000Hz
         final float rate = mEstimator.getRate(nextEventTime);
-        assertLessThan("Rate", rate, 1000f);
+        assertThat(rate).isAtMost(1000f);
     }
 
     @Test
     public void testGetRateWithoutUpdate() throws Exception {
         final float rate = mEstimator.getRate(mTestStartTime);
-        assertLessThan("Rate", rate, 0.1f);
+        assertThat(rate).isLessThan(0.1f);
     }
 
     @Test
     public void testGetRateWithOneUpdate() throws Exception {
-        assertUpdateTime(mTestStartTime);
+        updateAndVerifyRate(mTestStartTime);
         final float rate = mEstimator.getRate(mTestStartTime+1);
-        assertLessThan("Rate", rate, 1f);
+        assertThat(rate).isLessThan(1f);
     }
 
-    private void assertLessThan(String label, float a, float b)  {
-        assertTrue(String.format("%s was %f, but should be less than %f", label, a, b), a <= b);
+    @Test
+    public void testEstimateCatchesUpQuickly() {
+        long nextEventTime = postEvents(mTestStartTime, 100, 30); // 30 events at 10Hz
+
+        final float firstBurstRate = mEstimator.getRate(nextEventTime);
+        assertThat(firstBurstRate).isWithin(2f).of(10);
+
+        nextEventTime += HOURS.toMillis(3); // 3 hours later...
+        nextEventTime = postEvents(nextEventTime, 100, 30); // same burst of 30 events at 10Hz
+
+        // Catching up. Rate is not yet 10, since we had a long period of inactivity...
+        float secondBurstRate = mEstimator.getRate(nextEventTime);
+        assertThat(secondBurstRate).isWithin(1f).of(6);
+
+        // ... but after a few more events, we are there.
+        nextEventTime = postEvents(nextEventTime, 100, 10); // 10 more events at 10Hz
+        secondBurstRate = mEstimator.getRate(nextEventTime);
+        assertThat(secondBurstRate).isWithin(1f).of(10);
     }
 
-    private void assertGreaterThan(String label, float a, float b)  {
-        assertTrue(String.format("%s was %f, but should be more than %f", label, a, b), a >= b);
-    }
-
-    /** @returns the next event time. */
+    /** @return the next event time. */
     private long postEvents(long start, long dt, int num) {
         long time = start;
         for (int i = 0; i < num; i++) {
@@ -155,9 +166,8 @@
         return time;
     }
 
-    private void assertUpdateTime(long time) {
-        final float rate = mEstimator.update(time);
-        assertFalse(Float.isInfinite(rate));
-        assertFalse(Float.isNaN(rate));
+    private void updateAndVerifyRate(long time) {
+        mEstimator.update(time);
+        assertThat(mEstimator.getRate(time)).isFinite();
     }
-}
+}
\ No newline at end of file
diff --git a/services/tests/vibrator/Android.bp b/services/tests/vibrator/Android.bp
new file mode 100644
index 0000000..ca5cfa5
--- /dev/null
+++ b/services/tests/vibrator/Android.bp
@@ -0,0 +1,62 @@
+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: "FrameworksVibratorServicesTests",
+
+    srcs: [
+        "src/**/*.java",
+    ],
+
+    libs: [
+        "android.hardware.vibrator-V2-java",
+        "android.test.mock",
+        "android.test.base",
+        "android.test.runner",
+    ],
+
+    static_libs: [
+        "androidx.test.core",
+        "androidx.test.ext.truth",
+        "androidx.test.runner",
+        "androidx.test.rules",
+        "androidx.test.ext.junit",
+        "frameworks-base-testutils",
+        "frameworks-services-vibrator-testutils",
+        "junit",
+        "mockito-target-minus-junit4",
+        "platform-test-annotations",
+        "service-permission.stubs.system_server",
+        "services.core",
+    ],
+
+    platform_apis: true,
+    certificate: "platform",
+    dxflags: ["--multi-dex"],
+
+    test_suites: [
+        "device-tests",
+        "automotive-tests",
+    ],
+
+    optimize: {
+        enabled: false,
+    },
+}
+
+java_library {
+    name: "frameworks-services-vibrator-testutils",
+    visibility: [":__subpackages__"],
+    srcs: [
+        "utils/**/*.java",
+    ],
+    static_libs: [
+        "services.core",
+    ],
+}
diff --git a/services/tests/vibrator/AndroidManifest.xml b/services/tests/vibrator/AndroidManifest.xml
new file mode 100644
index 0000000..2a15c15
--- /dev/null
+++ b/services/tests/vibrator/AndroidManifest.xml
@@ -0,0 +1,45 @@
+<?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.framework.services.tests.vibrator">
+
+    <!-- Required to set user settings -->
+    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
+    <!-- Required to register uid observer -->
+    <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
+    <!-- Required to acquire wake locks during vibrations -->
+    <uses-permission android:name="android.permission.WAKE_LOCK" />
+    <uses-permission android:name="android.permission.UPDATE_DEVICE_STATS" />
+    <!-- Required to request vibrations -->
+    <uses-permission android:name="android.permission.VIBRATE" />
+    <!-- Required to listen to the vibrator state -->
+    <uses-permission android:name="android.permission.ACCESS_VIBRATOR_STATE" />
+    <!-- Required to set always-on vibrations -->
+    <uses-permission android:name="android.permission.VIBRATE_ALWAYS_ON" />
+
+    <application android:debuggable="true"
+        android:testOnly="true">
+        <uses-library android:name="android.test.mock" android:required="true" />
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:label="Vibrator Service Tests"
+        android:targetPackage="com.android.framework.services.tests.vibrator" />
+
+</manifest>
diff --git a/services/tests/vibrator/AndroidTest.xml b/services/tests/vibrator/AndroidTest.xml
new file mode 100644
index 0000000..d5ee3af
--- /dev/null
+++ b/services/tests/vibrator/AndroidTest.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<configuration description="Runs Frameworks Vibrator Services Tests.">
+    <option name="test-suite-tag" value="apct" />
+    <option name="test-suite-tag" value="apct-instrumentation" />
+
+    <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="FrameworksVibratorServicesTests.apk" />
+    </target_preparer>
+
+    <option name="test-tag" value="FrameworksVibratorServicesTests" />
+
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+        <option name="package" value="com.android.framework.services.tests.vibrator" />
+        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+        <option name="hidden-api-checks" value="false" />
+    </test>
+</configuration>
diff --git a/services/tests/vibrator/OWNERS b/services/tests/vibrator/OWNERS
new file mode 100644
index 0000000..93b44f4
--- /dev/null
+++ b/services/tests/vibrator/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 345036
+
+include /services/core/java/com/android/server/vibrator/OWNERS
diff --git a/services/tests/vibrator/TEST_MAPPING b/services/tests/vibrator/TEST_MAPPING
new file mode 100644
index 0000000..f0a7e47
--- /dev/null
+++ b/services/tests/vibrator/TEST_MAPPING
@@ -0,0 +1,21 @@
+{
+  "presubmit": [
+    {
+      "name": "FrameworksVibratorServicesTests",
+      "options": [
+        {"exclude-annotation": "android.platform.test.annotations.LargeTest"},
+        {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
+        {"exclude-annotation": "androidx.test.filters.FlakyTest"},
+        {"exclude-annotation": "org.junit.Ignore"}
+      ]
+    }
+  ],
+  "postsubmit": [
+    {
+      "name": "FrameworksVibratorServicesTests",
+      "options": [
+        {"exclude-annotation": "org.junit.Ignore"}
+      ]
+    }
+  ]
+}
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/DeviceAdapterTest.java b/services/tests/vibrator/src/com/android/server/vibrator/DeviceAdapterTest.java
new file mode 100644
index 0000000..3013ed0
--- /dev/null
+++ b/services/tests/vibrator/src/com/android/server/vibrator/DeviceAdapterTest.java
@@ -0,0 +1,300 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.vibrator;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.content.ComponentName;
+import android.content.pm.PackageManagerInternal;
+import android.hardware.vibrator.IVibrator;
+import android.os.CombinedVibration;
+import android.os.Handler;
+import android.os.VibrationEffect;
+import android.os.test.TestLooper;
+import android.os.vibrator.PrebakedSegment;
+import android.os.vibrator.PrimitiveSegment;
+import android.os.vibrator.RampSegment;
+import android.os.vibrator.StepSegment;
+import android.os.vibrator.VibrationEffectSegment;
+import android.util.SparseArray;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.server.LocalServices;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.Arrays;
+
+public class DeviceAdapterTest {
+    private static final int EMPTY_VIBRATOR_ID = 1;
+    private static final int PWLE_VIBRATOR_ID = 2;
+    private static final int PWLE_WITHOUT_FREQUENCIES_VIBRATOR_ID = 3;
+    private static final float TEST_MIN_FREQUENCY = 50;
+    private static final float TEST_RESONANT_FREQUENCY = 150;
+    private static final float TEST_FREQUENCY_RESOLUTION = 25;
+    private static final float[] TEST_AMPLITUDE_MAP = new float[]{
+            /* 50Hz= */ 0.08f, 0.16f, 0.32f, 0.64f, /* 150Hz= */ 0.8f, 0.72f, /* 200Hz= */ 0.64f};
+
+    @Rule
+    public MockitoRule mMockitoRule = MockitoJUnit.rule();
+
+    @Mock
+    private PackageManagerInternal mPackageManagerInternalMock;
+
+    private TestLooper mTestLooper;
+    private VibrationSettings mVibrationSettings;
+    private DeviceAdapter mAdapter;
+
+    @Before
+    public void setUp() throws Exception {
+        when(mPackageManagerInternalMock.getSystemUiServiceComponent())
+                .thenReturn(new ComponentName("", ""));
+        LocalServices.removeServiceForTest(PackageManagerInternal.class);
+        LocalServices.addService(PackageManagerInternal.class, mPackageManagerInternalMock);
+
+        mTestLooper = new TestLooper();
+        mVibrationSettings = new VibrationSettings(
+                InstrumentationRegistry.getContext(), new Handler(mTestLooper.getLooper()));
+
+        SparseArray<VibratorController> vibrators = new SparseArray<>();
+        vibrators.put(EMPTY_VIBRATOR_ID, createEmptyVibratorController(EMPTY_VIBRATOR_ID));
+        vibrators.put(PWLE_VIBRATOR_ID, createPwleVibratorController(PWLE_VIBRATOR_ID));
+        vibrators.put(PWLE_WITHOUT_FREQUENCIES_VIBRATOR_ID,
+                createPwleWithoutFrequenciesVibratorController(
+                        PWLE_WITHOUT_FREQUENCIES_VIBRATOR_ID));
+        mAdapter = new DeviceAdapter(mVibrationSettings, vibrators);
+    }
+
+    @Test
+    public void testPrebakedAndPrimitiveSegments_returnsOriginalSegment() {
+        VibrationEffect.Composed effect = new VibrationEffect.Composed(Arrays.asList(
+                new PrebakedSegment(
+                        VibrationEffect.EFFECT_CLICK, false, VibrationEffect.EFFECT_STRENGTH_LIGHT),
+                new PrimitiveSegment(VibrationEffect.Composition.PRIMITIVE_TICK, 1, 10),
+                new PrebakedSegment(
+                        VibrationEffect.EFFECT_THUD, true, VibrationEffect.EFFECT_STRENGTH_STRONG),
+                new PrimitiveSegment(VibrationEffect.Composition.PRIMITIVE_SPIN, 0.5f, 100)),
+                /* repeatIndex= */ -1);
+
+        assertThat(mAdapter.adaptToVibrator(EMPTY_VIBRATOR_ID, effect)).isEqualTo(effect);
+        assertThat(mAdapter.adaptToVibrator(PWLE_VIBRATOR_ID, effect)).isEqualTo(effect);
+    }
+
+    @Test
+    public void testStepAndRampSegments_withoutPwleCapability_convertsRampsToSteps() {
+        VibrationEffect.Composed effect = new VibrationEffect.Composed(Arrays.asList(
+                // Step(amplitude, frequencyHz, duration)
+                new StepSegment(0, 200, 10),
+                new StepSegment(0.5f, 150, 100),
+                // Ramp(startAmplitude, endAmplitude, startFrequencyHz, endFrequencyHz, duration)
+                new RampSegment(1, 0.2f, 1, 300, 10),
+                new RampSegment(0.8f, 0.2f, 0, 0, 100),
+                new RampSegment(0.65f, 0.65f, 0, 1, 1000)),
+                /* repeatIndex= */ 3);
+
+        VibrationEffect.Composed adaptedEffect =
+                (VibrationEffect.Composed) mAdapter.adaptToVibrator(EMPTY_VIBRATOR_ID, effect);
+        assertThat(adaptedEffect.getSegments().size()).isGreaterThan(effect.getSegments().size());
+        assertThat(adaptedEffect.getRepeatIndex()).isAtLeast(effect.getRepeatIndex());
+
+        for (VibrationEffectSegment adaptedSegment : adaptedEffect.getSegments()) {
+            assertThat(adaptedSegment).isInstanceOf(StepSegment.class);
+        }
+    }
+
+    @Test
+    public void testStepAndRampSegments_withPwleCapability_convertsStepsToRamps() {
+        VibrationEffect.Composed effect = new VibrationEffect.Composed(Arrays.asList(
+                // Step(amplitude, frequencyHz, duration)
+                new StepSegment(0, 175, 10),
+                new StepSegment(0.5f, 150, 60),
+                // Ramp(startAmplitude, endAmplitude, startFrequencyHz, endFrequencyHz, duration)
+                new RampSegment(1, 1, 50, 200, 50),
+                new RampSegment(0.8f, 0.2f, 1000, 1, 20)),
+                /* repeatIndex= */ 2);
+
+        VibrationEffect.Composed expected = new VibrationEffect.Composed(Arrays.asList(
+                // Ramp(startAmplitude, endAmplitude, startFrequencyHz, endFrequencyHz, duration)
+                new RampSegment(0, 0, 175, 175, 10),
+                new RampSegment(0.5f, 0.5f, 150, 150, 60),
+                new RampSegment(0.08f, 0.64f, 50, 200, 50),
+                new RampSegment(0.64f, 0.08f, 200, 50, 20)),
+                /* repeatIndex= */ 2);
+
+        assertThat(mAdapter.adaptToVibrator(PWLE_VIBRATOR_ID, effect)).isEqualTo(expected);
+    }
+
+    @Test
+    public void testStepAndRampSegments_withEmptyFreqMapping_returnsAmplitudesWithResonantFreq() {
+        VibrationEffect.Composed effect = new VibrationEffect.Composed(Arrays.asList(
+                // Step(amplitude, frequencyHz, duration)
+                new StepSegment(0, 175, 10),
+                new StepSegment(0.5f, 0, 100),
+                // Ramp(startAmplitude, endAmplitude, startFrequencyHz, endFrequencyHz, duration)
+                new RampSegment(0.8f, 1, 50, 200, 50),
+                new RampSegment(0.7f, 0.5f, 1000, 1, 20)),
+                /* repeatIndex= */ 2);
+
+        VibrationEffect.Composed expected = new VibrationEffect.Composed(Arrays.asList(
+                // Ramp(startAmplitude, endAmplitude, startFrequencyHz, endFrequencyHz, duration)
+                new RampSegment(0, 0, 0, 0, 10),
+                new RampSegment(0.5f, 0.5f, 0, 0, 100),
+                new RampSegment(0.8f, 1, 0, 0, 50),
+                new RampSegment(0.7f, 0.5f, 0, 0, 20)),
+                /* repeatIndex= */ 2);
+
+        assertThat(mAdapter.adaptToVibrator(PWLE_WITHOUT_FREQUENCIES_VIBRATOR_ID, effect))
+                .isEqualTo(expected);
+    }
+
+    @Test
+    public void testStepAndRampSegments_withValidFreqMapping_returnsClippedValuesOnlyInRamps() {
+        VibrationEffect.Composed effect = new VibrationEffect.Composed(Arrays.asList(
+                // Individual step without frequency control, will not use PWLE composition
+                // Step(amplitude, frequencyHz, duration)
+                new StepSegment(1, 0, 10),
+                new PrimitiveSegment(VibrationEffect.Composition.PRIMITIVE_TICK, 1, 10),
+                // Step with frequency control and followed by ramps, will use PWLE composition
+                // Step(amplitude, frequencyHz, duration)
+                new StepSegment(0.5f, 0, 10),
+                new StepSegment(1, 125, 100),
+                // Ramp(startAmplitude, endAmplitude, startFrequencyHz, endFrequencyHz, duration)
+                new RampSegment(1, 1, 50, 200, 50),
+                new RampSegment(0.8f, 0.2f, 1000, 1, 20)),
+                /* repeatIndex= */ 2);
+
+        VibrationEffect.Composed expected = new VibrationEffect.Composed(Arrays.asList(
+                // Step(amplitude, frequencyHz, duration)
+                new StepSegment(1, 0, 10),
+                new PrimitiveSegment(VibrationEffect.Composition.PRIMITIVE_TICK, 1, 10),
+                // Ramp(startAmplitude, endAmplitude, startFrequencyHz, endFrequencyHz, duration)
+                new RampSegment(0.5f, 0.5f, 150, 150, 10),
+                new RampSegment(0.64f, 0.64f, 125, 125, 100),
+                new RampSegment(0.08f, 0.64f, 50, 200, 50),
+                new RampSegment(0.64f, 0.08f, 200, 50, 20)),
+                /* repeatIndex= */ 2);
+
+        assertThat(mAdapter.adaptToVibrator(PWLE_VIBRATOR_ID, effect)).isEqualTo(expected);
+    }
+
+    @Test
+    public void testMonoCombinedVibration_returnsSameVibrationWhenEffectsUnchanged() {
+        VibrationEffect.Composed effect = new VibrationEffect.Composed(Arrays.asList(
+                new PrebakedSegment(
+                        VibrationEffect.EFFECT_CLICK, false, VibrationEffect.EFFECT_STRENGTH_LIGHT),
+                new PrimitiveSegment(VibrationEffect.Composition.PRIMITIVE_TICK, 1, 10),
+                new PrebakedSegment(
+                        VibrationEffect.EFFECT_THUD, true, VibrationEffect.EFFECT_STRENGTH_STRONG),
+                new PrimitiveSegment(VibrationEffect.Composition.PRIMITIVE_SPIN, 0.5f, 100)),
+                /* repeatIndex= */ -1);
+
+        CombinedVibration expected = CombinedVibration.createParallel(effect);
+
+        assertThat(expected.adapt(mAdapter)).isEqualTo(expected);
+    }
+
+    @Test
+    public void testMonoCombinedVibration_mapsEffectsToAllVibrators() {
+        VibrationEffect.Composed effect = new VibrationEffect.Composed(Arrays.asList(
+                // Step(amplitude, frequencyHz, duration)
+                new StepSegment(1, 175, 10),
+                // Ramp(startAmplitude, endAmplitude, startFrequencyHz, endFrequencyHz, duration)
+                new RampSegment(1, 1, 50, 200, 50)),
+                /* repeatIndex= */ 1);
+
+        CombinedVibration expected = CombinedVibration.startParallel()
+                .addVibrator(EMPTY_VIBRATOR_ID, new VibrationEffect.Composed(Arrays.asList(
+                        // Step(amplitude, frequencyHz, duration)
+                        new StepSegment(1, 175, 10),
+                        new StepSegment(1, 0, 50)),
+                        /* repeatIndex= */ 1))
+                .addVibrator(PWLE_VIBRATOR_ID, new VibrationEffect.Composed(Arrays.asList(
+                // Ramp(startAmplitude, endAmplitude, startFrequencyHz, endFrequencyHz, duration)
+                        new RampSegment(0.72f, 0.72f, 175, 175, 10),
+                        new RampSegment(0.08f, 0.64f, 50, 200, 50)),
+                        /* repeatIndex= */ 1))
+                .addVibrator(PWLE_WITHOUT_FREQUENCIES_VIBRATOR_ID,
+                        new VibrationEffect.Composed(Arrays.asList(
+                // Ramp(startAmplitude, endAmplitude, startFrequencyHz, endFrequencyHz, duration)
+                                new RampSegment(1, 1, 0, 0, 10),
+                                new RampSegment(1, 1, 0, 0, 50)),
+                                /* repeatIndex= */ 1))
+                .combine();
+
+        assertThat(CombinedVibration.createParallel(effect).adapt(mAdapter)).isEqualTo(expected);
+    }
+
+    @Test
+    public void testStereoCombinedVibration_adaptMappedEffectsAndLeaveUnmappedOnesUnchanged() {
+        VibrationEffect.Composed effect = new VibrationEffect.Composed(Arrays.asList(
+                // Step(amplitude, frequencyHz, duration)
+                new StepSegment(1, 175, 10)),
+                /* repeatIndex= */ -1);
+
+        int missingVibratorId = 1234;
+        CombinedVibration vibration = CombinedVibration.startParallel()
+                .addVibrator(missingVibratorId, effect)
+                .addVibrator(EMPTY_VIBRATOR_ID, effect)
+                .addVibrator(PWLE_VIBRATOR_ID, effect)
+                .combine();
+
+        CombinedVibration expected = CombinedVibration.startParallel()
+                .addVibrator(missingVibratorId, effect) // unchanged
+                .addVibrator(EMPTY_VIBRATOR_ID, new VibrationEffect.Composed(Arrays.asList(
+                        // Step(amplitude, frequencyHz, duration)
+                        new StepSegment(1, 175, 10)),
+                        /* repeatIndex= */ -1))
+                .addVibrator(PWLE_VIBRATOR_ID, new VibrationEffect.Composed(Arrays.asList(
+                // Ramp(startAmplitude, endAmplitude, startFrequencyHz, endFrequencyHz, duration)
+                        new RampSegment(0.72f, 0.72f, 175, 175, 10)),
+                        /* repeatIndex= */ -1))
+                .combine();
+
+        assertThat(vibration.adapt(mAdapter)).isEqualTo(expected);
+    }
+
+    private VibratorController createEmptyVibratorController(int vibratorId) {
+        return new FakeVibratorControllerProvider(mTestLooper.getLooper())
+                .newVibratorController(vibratorId, (id, vibrationId)  -> {});
+    }
+
+    private VibratorController createPwleWithoutFrequenciesVibratorController(int vibratorId) {
+        FakeVibratorControllerProvider provider = new FakeVibratorControllerProvider(
+                mTestLooper.getLooper());
+        provider.setCapabilities(IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
+        return provider.newVibratorController(vibratorId, (id, vibrationId)  -> {});
+    }
+
+    private VibratorController createPwleVibratorController(int vibratorId) {
+        FakeVibratorControllerProvider provider = new FakeVibratorControllerProvider(
+                mTestLooper.getLooper());
+        provider.setCapabilities(IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
+        provider.setResonantFrequency(TEST_RESONANT_FREQUENCY);
+        provider.setMinFrequency(TEST_MIN_FREQUENCY);
+        provider.setFrequencyResolution(TEST_FREQUENCY_RESOLUTION);
+        provider.setMaxAmplitudes(TEST_AMPLITUDE_MAP);
+        return provider.newVibratorController(vibratorId, (id, vibrationId)  -> {});
+    }
+}
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/InputDeviceDelegateTest.java b/services/tests/vibrator/src/com/android/server/vibrator/InputDeviceDelegateTest.java
new file mode 100644
index 0000000..f3ecfcc
--- /dev/null
+++ b/services/tests/vibrator/src/com/android/server/vibrator/InputDeviceDelegateTest.java
@@ -0,0 +1,338 @@
+/*
+ * 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.
+ */
+
+package com.android.server.vibrator;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.same;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.hardware.input.IInputDevicesChangedListener;
+import android.hardware.input.IInputManager;
+import android.hardware.input.InputManager;
+import android.hardware.input.InputManagerGlobal;
+import android.os.CombinedVibration;
+import android.os.Handler;
+import android.os.Process;
+import android.os.VibrationAttributes;
+import android.os.VibrationEffect;
+import android.os.test.TestLooper;
+import android.view.InputDevice;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+public class InputDeviceDelegateTest {
+
+    private static final int UID = Process.ROOT_UID;
+    private static final String PACKAGE_NAME = "package";
+    private static final String REASON = "some reason";
+    private static final VibrationAttributes VIBRATION_ATTRIBUTES =
+            new VibrationAttributes.Builder().setUsage(VibrationAttributes.USAGE_ALARM).build();
+    private static final CombinedVibration SYNCED_EFFECT =
+            CombinedVibration.createParallel(VibrationEffect.createOneShot(100, 255));
+
+    @Rule public MockitoRule rule = MockitoJUnit.rule();
+
+    @Mock private IInputManager mIInputManagerMock;
+
+    private TestLooper mTestLooper;
+    private ContextWrapper mContextSpy;
+    private InputManagerGlobal.TestSession mInputManagerGlobalSession;
+    private InputManager mInputManager;
+    private InputDeviceDelegate mInputDeviceDelegate;
+    private IInputDevicesChangedListener mIInputDevicesChangedListener;
+
+    @Before
+    public void setUp() throws Exception {
+        mTestLooper = new TestLooper();
+        mInputManagerGlobalSession = InputManagerGlobal.createTestSession(mIInputManagerMock);
+        mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getContext()));
+
+        mInputManager = new InputManager(mContextSpy);
+        when(mContextSpy.getSystemService(eq(Context.INPUT_SERVICE)))
+                .thenReturn(mInputManager);
+        doAnswer(invocation -> mIInputDevicesChangedListener = invocation.getArgument(0))
+                .when(mIInputManagerMock).registerInputDevicesChangedListener(any());
+
+        mInputDeviceDelegate = new InputDeviceDelegate(
+                mContextSpy, new Handler(mTestLooper.getLooper()));
+        mInputDeviceDelegate.onSystemReady();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        if (mInputManagerGlobalSession != null) {
+            mInputManagerGlobalSession.close();
+        }
+    }
+
+    @Test
+    public void beforeSystemReady_ignoresAnyUpdate() throws Exception {
+        when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[0]);
+        InputDeviceDelegate inputDeviceDelegate = new InputDeviceDelegate(
+                mContextSpy, new Handler(mTestLooper.getLooper()));
+
+        inputDeviceDelegate.updateInputDeviceVibrators(/* vibrateInputDevices= */ true);
+        assertFalse(inputDeviceDelegate.isAvailable());
+
+        inputDeviceDelegate.onInputDeviceAdded(1);
+        assertFalse(inputDeviceDelegate.isAvailable());
+
+        updateInputDevices(new int[]{1});
+        assertFalse(inputDeviceDelegate.isAvailable());
+
+        verify(mIInputManagerMock, never()).getInputDevice(anyInt());
+    }
+
+    @Test
+    public void onInputDeviceAdded_withSettingsDisabled_ignoresNewDevice() throws Exception {
+        when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[0]);
+        mInputDeviceDelegate.updateInputDeviceVibrators(/* vibrateInputDevices= */ false);
+        assertFalse(mInputDeviceDelegate.isAvailable());
+
+        when(mIInputManagerMock.getVibratorIds(eq(1))).thenReturn(new int[]{1});
+        when(mIInputManagerMock.getInputDevice(eq(1))).thenReturn(createInputDeviceWithVibrator(1));
+        mInputDeviceDelegate.onInputDeviceAdded(1);
+
+        assertFalse(mInputDeviceDelegate.isAvailable());
+        verify(mIInputManagerMock, never()).getInputDevice(anyInt());
+    }
+
+    @Test
+    public void onInputDeviceAdded_withDeviceWithoutVibrator_ignoresNewDevice() throws Exception {
+        when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[0]);
+        mInputDeviceDelegate.updateInputDeviceVibrators(/* vibrateInputDevices= */ true);
+        assertFalse(mInputDeviceDelegate.isAvailable());
+
+        when(mIInputManagerMock.getVibratorIds(eq(1))).thenReturn(new int[0]);
+        when(mIInputManagerMock.getInputDevice(eq(1)))
+                .thenReturn(createInputDeviceWithoutVibrator(1));
+        updateInputDevices(new int[]{1});
+
+        assertFalse(mInputDeviceDelegate.isAvailable());
+        verify(mIInputManagerMock).getInputDevice(eq(1));
+    }
+
+    @Test
+    public void onInputDeviceAdded_withDeviceWithVibrator_addsNewDevice() throws Exception {
+        when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[0]);
+        mInputDeviceDelegate.updateInputDeviceVibrators(/* vibrateInputDevices= */ true);
+        assertFalse(mInputDeviceDelegate.isAvailable());
+
+        when(mIInputManagerMock.getVibratorIds(eq(1))).thenReturn(new int[]{1});
+        when(mIInputManagerMock.getInputDevice(eq(1))).thenReturn(createInputDeviceWithVibrator(1));
+        updateInputDevices(new int[]{1});
+
+        assertTrue(mInputDeviceDelegate.isAvailable());
+        verify(mIInputManagerMock).getInputDevice(eq(1));
+    }
+
+    @Test
+    public void onInputDeviceChanged_withSettingsDisabled_ignoresDevice() throws Exception {
+        when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1});
+        when(mIInputManagerMock.getVibratorIds(eq(1))).thenReturn(new int[]{1});
+        when(mIInputManagerMock.getInputDevice(eq(1))).thenReturn(createInputDeviceWithVibrator(1));
+        mInputDeviceDelegate.updateInputDeviceVibrators(/* vibrateInputDevices= */ false);
+
+        updateInputDevices(new int[]{1});
+        assertFalse(mInputDeviceDelegate.isAvailable());
+        verify(mIInputManagerMock, never()).getInputDevice(anyInt());
+    }
+
+    @Test
+    public void onInputDeviceChanged_deviceLosesVibrator_removesDevice() throws Exception {
+        when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1});
+        when(mIInputManagerMock.getVibratorIds(eq(1))).thenReturn(new int[]{1}, new int[0]);
+        when(mIInputManagerMock.getInputDevice(eq(1)))
+                .thenReturn(createInputDeviceWithVibrator(1), createInputDeviceWithoutVibrator(1));
+
+        mInputDeviceDelegate.updateInputDeviceVibrators(/* vibrateInputDevices= */ true);
+        assertTrue(mInputDeviceDelegate.isAvailable());
+
+        updateInputDevices(new int[]{1});
+        assertFalse(mInputDeviceDelegate.isAvailable());
+        verify(mIInputManagerMock, times(2)).getInputDevice(eq(1));
+    }
+
+    @Test
+    public void onInputDeviceChanged_deviceLost_removesDevice() throws Exception {
+        when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1});
+        when(mIInputManagerMock.getVibratorIds(eq(1))).thenReturn(new int[]{1}, new int[0]);
+        when(mIInputManagerMock.getInputDevice(eq(1)))
+                .thenReturn(createInputDeviceWithVibrator(1), (InputDevice) null);
+
+        mInputDeviceDelegate.updateInputDeviceVibrators(/* vibrateInputDevices= */ true);
+        assertTrue(mInputDeviceDelegate.isAvailable());
+
+        updateInputDevices(new int[]{1});
+        assertFalse(mInputDeviceDelegate.isAvailable());
+        verify(mIInputManagerMock, times(2)).getInputDevice(eq(1));
+    }
+
+    @Test
+    public void onInputDeviceChanged_deviceAddsVibrator_addsDevice() throws Exception {
+        when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1});
+        when(mIInputManagerMock.getVibratorIds(eq(1))).thenReturn(new int[0], new int[]{1});
+        when(mIInputManagerMock.getInputDevice(eq(1)))
+                .thenReturn(createInputDeviceWithoutVibrator(1), createInputDeviceWithVibrator(1));
+
+        mInputDeviceDelegate.updateInputDeviceVibrators(/* vibrateInputDevices= */ true);
+        assertFalse(mInputDeviceDelegate.isAvailable());
+
+        updateInputDevices(new int[]{1});
+        assertTrue(mInputDeviceDelegate.isAvailable());
+        verify(mIInputManagerMock, times(2)).getInputDevice(eq(1));
+    }
+
+    @Test
+    public void onInputDeviceRemoved_removesDevice() throws Exception {
+        when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1, 2});
+        when(mIInputManagerMock.getVibratorIds(eq(1))).thenReturn(new int[0]);
+        when(mIInputManagerMock.getInputDevice(eq(1))).thenReturn(
+                createInputDeviceWithoutVibrator(1));
+        when(mIInputManagerMock.getVibratorIds(eq(2))).thenReturn(new int[]{1});
+        when(mIInputManagerMock.getInputDevice(eq(2))).thenReturn(createInputDeviceWithVibrator(2));
+
+        mInputDeviceDelegate.updateInputDeviceVibrators(/* vibrateInputDevices= */ true);
+        assertTrue(mInputDeviceDelegate.isAvailable());
+
+        updateInputDevices(new int[]{1});
+        assertFalse(mInputDeviceDelegate.isAvailable());
+    }
+
+    @Test
+    public void updateInputDeviceVibrators_usesFlagToLoadDeviceList() throws Exception {
+        when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1, 2});
+        when(mIInputManagerMock.getVibratorIds(eq(1))).thenReturn(new int[]{1});
+        when(mIInputManagerMock.getInputDevice(eq(1))).thenReturn(createInputDeviceWithVibrator(1));
+        when(mIInputManagerMock.getVibratorIds(eq(2))).thenReturn(new int[]{1});
+        when(mIInputManagerMock.getInputDevice(eq(2))).thenReturn(createInputDeviceWithVibrator(2));
+
+        mInputDeviceDelegate.updateInputDeviceVibrators(/* vibrateInputDevices= */ true);
+        assertTrue(mInputDeviceDelegate.isAvailable());
+
+        mInputDeviceDelegate.updateInputDeviceVibrators(/* vibrateInputDevices= */ false);
+        assertFalse(mInputDeviceDelegate.isAvailable());
+    }
+
+    @Test
+    public void updateInputDeviceVibrators_withDeviceWithoutVibrator_deviceIsIgnored()
+            throws Exception {
+        when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1});
+        when(mIInputManagerMock.getVibratorIds(eq(1))).thenReturn(new int[0]);
+        when(mIInputManagerMock.getInputDevice(eq(1)))
+                .thenReturn(createInputDeviceWithoutVibrator(1));
+        mInputDeviceDelegate.updateInputDeviceVibrators(/* vibrateInputDevices= */ true);
+        assertFalse(mInputDeviceDelegate.isAvailable());
+    }
+
+    @Test
+    public void vibrateIfAvailable_withNoInputDevice_returnsFalse() {
+        assertFalse(mInputDeviceDelegate.isAvailable());
+        assertFalse(mInputDeviceDelegate.vibrateIfAvailable(
+                new Vibration.CallerInfo(VIBRATION_ATTRIBUTES, UID, -1, PACKAGE_NAME, REASON),
+                SYNCED_EFFECT));
+    }
+
+    @Test
+    public void vibrateIfAvailable_withInputDevices_returnsTrueAndVibratesAllDevices()
+            throws Exception {
+        when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1, 2});
+        when(mIInputManagerMock.getVibratorIds(eq(1))).thenReturn(new int[]{1});
+        when(mIInputManagerMock.getInputDevice(eq(1))).thenReturn(createInputDeviceWithVibrator(1));
+        when(mIInputManagerMock.getVibratorIds(eq(2))).thenReturn(new int[]{1});
+        when(mIInputManagerMock.getInputDevice(eq(2))).thenReturn(createInputDeviceWithVibrator(2));
+        mInputDeviceDelegate.updateInputDeviceVibrators(/* vibrateInputDevices= */ true);
+
+        assertTrue(mInputDeviceDelegate.vibrateIfAvailable(
+                new Vibration.CallerInfo(VIBRATION_ATTRIBUTES, UID, -1, PACKAGE_NAME, REASON),
+                SYNCED_EFFECT));
+        verify(mIInputManagerMock).vibrateCombined(eq(1), same(SYNCED_EFFECT), any());
+        verify(mIInputManagerMock).vibrateCombined(eq(2), same(SYNCED_EFFECT), any());
+    }
+
+    @Test
+    public void cancelVibrateIfAvailable_withNoInputDevice_returnsFalse() throws Exception {
+        assertFalse(mInputDeviceDelegate.isAvailable());
+        assertFalse(mInputDeviceDelegate.cancelVibrateIfAvailable());
+        verify(mIInputManagerMock, never()).cancelVibrate(anyInt(), any());
+    }
+
+    @Test
+    public void cancelVibrateIfAvailable_withInputDevices_returnsTrueAndStopsAllDevices()
+            throws Exception {
+        when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1, 2});
+        when(mIInputManagerMock.getVibratorIds(eq(1))).thenReturn(new int[]{1});
+        when(mIInputManagerMock.getInputDevice(eq(1))).thenReturn(createInputDeviceWithVibrator(1));
+        when(mIInputManagerMock.getVibratorIds(eq(2))).thenReturn(new int[]{1});
+        when(mIInputManagerMock.getInputDevice(eq(2))).thenReturn(createInputDeviceWithVibrator(2));
+        mInputDeviceDelegate.updateInputDeviceVibrators(/* vibrateInputDevices= */ true);
+
+        assertTrue(mInputDeviceDelegate.isAvailable());
+        assertTrue(mInputDeviceDelegate.cancelVibrateIfAvailable());
+        verify(mIInputManagerMock).cancelVibrate(eq(1), any());
+        verify(mIInputManagerMock).cancelVibrate(eq(2), any());
+    }
+
+    private void updateInputDevices(int[] deviceIds) throws Exception {
+        int[] deviceIdsAndGenerations = new int[deviceIds.length * 2];
+        for (int i = 0; i < deviceIdsAndGenerations.length; i += 2) {
+            deviceIdsAndGenerations[i] = deviceIds[i / 2];
+            deviceIdsAndGenerations[i + 1] = 2; // update by increasing it's generation to 2.
+        }
+        // Force initialization of mIInputDevicesChangedListener, if it still haven't
+        InputManagerGlobal.getInstance().getInputDeviceIds();
+        mIInputDevicesChangedListener.onInputDevicesChanged(deviceIdsAndGenerations);
+        // Makes sure all callbacks from InputDeviceDelegate are executed.
+        mTestLooper.dispatchAll();
+    }
+
+    private InputDevice createInputDeviceWithVibrator(int id) {
+        return createInputDevice(id, /* hasVibrator= */ true);
+    }
+
+    private InputDevice createInputDeviceWithoutVibrator(int id) {
+        return createInputDevice(id, /* hasVibrator= */ false);
+    }
+
+    private InputDevice createInputDevice(int id, boolean hasVibrator) {
+        return new InputDevice.Builder()
+                .setId(id)
+                .setName("name")
+                .setDescriptor("descriptor")
+                .setHasVibrator(hasVibrator)
+                .build();
+    }
+}
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/RampDownAdapterTest.java b/services/tests/vibrator/src/com/android/server/vibrator/RampDownAdapterTest.java
new file mode 100644
index 0000000..141da7cb
--- /dev/null
+++ b/services/tests/vibrator/src/com/android/server/vibrator/RampDownAdapterTest.java
@@ -0,0 +1,347 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.vibrator;
+
+import static org.junit.Assert.assertEquals;
+
+import android.os.VibrationEffect;
+import android.os.VibratorInfo;
+import android.os.vibrator.PrebakedSegment;
+import android.os.vibrator.PrimitiveSegment;
+import android.os.vibrator.RampSegment;
+import android.os.vibrator.StepSegment;
+import android.os.vibrator.VibrationEffectSegment;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+public class RampDownAdapterTest {
+    private static final int TEST_RAMP_DOWN_DURATION = 20;
+    private static final int TEST_STEP_DURATION = 5;
+    private static final VibratorInfo EMPTY_VIBRATOR_INFO = new VibratorInfo.Builder(0).build();
+
+    private RampDownAdapter mAdapter;
+
+    @Before
+    public void setUp() throws Exception {
+        mAdapter = new RampDownAdapter(TEST_RAMP_DOWN_DURATION, TEST_STEP_DURATION);
+    }
+
+    @Test
+    public void testPrebakedAndPrimitiveSegments_keepsListUnchanged() {
+        List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
+                new PrebakedSegment(
+                        VibrationEffect.EFFECT_CLICK, false, VibrationEffect.EFFECT_STRENGTH_LIGHT),
+                new PrimitiveSegment(VibrationEffect.Composition.PRIMITIVE_TICK, 1, 10)));
+        List<VibrationEffectSegment> originalSegments = new ArrayList<>(segments);
+
+        assertEquals(-1, mAdapter.adaptToVibrator(EMPTY_VIBRATOR_INFO, segments, -1));
+        assertEquals(1, mAdapter.adaptToVibrator(EMPTY_VIBRATOR_INFO, segments, 1));
+
+        assertEquals(originalSegments, segments);
+    }
+
+    @Test
+    public void testRampAndStepSegments_withNoOffSegment_keepsListUnchanged() {
+        List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
+                new StepSegment(/* amplitude= */ 0.5f, /* frequencyHz= */ 0, /* duration= */ 100),
+                new RampSegment(/* startAmplitude= */ 0.8f, /* endAmplitude= */ 0.2f,
+                        /* startFrequencyHz= */ 10, /* endFrequencyHz= */ 50, /* duration= */ 20)));
+        List<VibrationEffectSegment> originalSegments = new ArrayList<>(segments);
+
+        assertEquals(-1, mAdapter.adaptToVibrator(EMPTY_VIBRATOR_INFO, segments, -1));
+        assertEquals(0, mAdapter.adaptToVibrator(EMPTY_VIBRATOR_INFO, segments, 0));
+
+        assertEquals(originalSegments, segments);
+    }
+
+    @Test
+    public void testRampAndStepSegments_withNoRampDownDuration_keepsOriginalSteps() {
+        mAdapter = new RampDownAdapter(/* rampDownDuration= */ 0, TEST_STEP_DURATION);
+
+        List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
+                new StepSegment(/* amplitude= */ 1, /* frequencyHz= */ 0, /* duration= */ 10),
+                new StepSegment(/* amplitude= */ 0, /* frequencyHz= */ 0, /* duration= */ 100),
+                new RampSegment(/* startAmplitude= */ 0.8f, /* endAmplitude= */ 0.2f,
+                        /* startFrequencyHz= */ 10, /* endFrequencyHz= */ 50, /* duration= */ 20),
+                new RampSegment(/* startAmplitude= */ 0, /* endAmplitude= */ 0,
+                        /* startFrequencyHz= */ 0, /* endFrequencyHz= */ 0, /* duration= */ 50)));
+        List<VibrationEffectSegment> originalSegments = new ArrayList<>(segments);
+
+        assertEquals(-1, mAdapter.adaptToVibrator(EMPTY_VIBRATOR_INFO, segments, -1));
+        assertEquals(2, mAdapter.adaptToVibrator(EMPTY_VIBRATOR_INFO, segments, 2));
+        assertEquals(originalSegments, segments);
+    }
+
+    @Test
+    public void testStepSegments_withShortZeroSegment_replaceWithStepsDown() {
+        List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
+                new StepSegment(/* amplitude= */ 1, /* frequencyHz= */ 0, /* duration= */ 10),
+                new StepSegment(/* amplitude= */ 0, /* frequencyHz= */ 0, /* duration= */ 10)));
+        List<VibrationEffectSegment> expectedSegments = Arrays.asList(
+                new StepSegment(/* amplitude= */ 1, /* frequencyHz= */ 0, /* duration= */ 10),
+                new StepSegment(/* amplitude= */ 0.5f, /* frequencyHz= */ 0, /* duration= */ 5),
+                new StepSegment(/* amplitude= */ 0, /* frequencyHz= */ 0, /* duration= */ 5));
+
+        assertEquals(-1, mAdapter.adaptToVibrator(EMPTY_VIBRATOR_INFO, segments, -1));
+        assertEquals(expectedSegments, segments);
+    }
+
+    @Test
+    public void testStepSegments_withLongZeroSegment_replaceWithStepsDownWithRemainingOffSegment() {
+        List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
+                new StepSegment(/* amplitude= */ 1, /* frequencyHz= */ 0, /* duration= */ 10),
+                new RampSegment(/* startAmplitude= */ 0, /* endAmplitude= */ 0,
+                        /* startFrequencyHz= */ 0, /* endFrequencyHz= */ 0, /* duration= */ 50),
+                new StepSegment(/* amplitude= */ 0.8f, /* frequencyHz= */ 0, /* duration= */ 100)));
+        List<VibrationEffectSegment> expectedSegments = Arrays.asList(
+                new StepSegment(/* amplitude= */ 1, /* frequencyHz= */ 0, /* duration= */ 10),
+                new StepSegment(/* amplitude= */ 0.75f, /* frequencyHz= */ 0, /* duration= */ 5),
+                new StepSegment(/* amplitude= */ 0.5f, /* frequencyHz= */ 0, /* duration= */ 5),
+                new StepSegment(/* amplitude= */ 0.25f, /* frequencyHz= */ 0, /* duration= */ 5),
+                new StepSegment(/* amplitude= */ 0, /* frequencyHz= */ 0, /* duration= */ 35),
+                new StepSegment(/* amplitude= */ 0.8f, /* frequencyHz= */ 0, /* duration= */ 100));
+
+        assertEquals(-1, mAdapter.adaptToVibrator(EMPTY_VIBRATOR_INFO, segments, -1));
+        assertEquals(expectedSegments, segments);
+    }
+
+    @Test
+    public void testStepSegments_withZeroSegmentBeforeRepeat_fixesRepeat() {
+        List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
+                new StepSegment(/* amplitude= */ 1, /* frequencyHz= */ 0, /* duration= */ 10),
+                new StepSegment(/* amplitude= */ 0, /* frequencyHz= */ 0, /* duration= */ 50),
+                new StepSegment(/* amplitude= */ 0.8f, /* frequencyHz= */ 0, /* duration= */ 100)));
+        List<VibrationEffectSegment> expectedSegments = Arrays.asList(
+                new StepSegment(/* amplitude= */ 1, /* frequencyHz= */ 0, /* duration= */ 10),
+                new StepSegment(/* amplitude= */ 0.75f, /* frequencyHz= */ 0, /* duration= */ 5),
+                new StepSegment(/* amplitude= */ 0.5f, /* frequencyHz= */ 0, /* duration= */ 5),
+                new StepSegment(/* amplitude= */ 0.25f, /* frequencyHz= */ 0, /* duration= */ 5),
+                new StepSegment(/* amplitude= */ 0, /* frequencyHz= */ 0, /* duration= */ 35),
+                new StepSegment(/* amplitude= */ 0.8f, /* frequencyHz= */ 0, /* duration= */ 100));
+
+        // Repeat index fixed after intermediate steps added
+        assertEquals(5, mAdapter.adaptToVibrator(EMPTY_VIBRATOR_INFO, segments, 2));
+        assertEquals(expectedSegments, segments);
+    }
+
+    @Test
+    public void testStepSegments_withZeroSegmentAfterRepeat_preservesRepeat() {
+        List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
+                new StepSegment(/* amplitude= */ 1, /* frequencyHz= */ 0, /* duration= */ 10),
+                new StepSegment(/* amplitude= */ 0, /* frequencyHz= */ 0, /* duration= */ 10),
+                new StepSegment(/* amplitude= */ 0.8f, /* frequencyHz= */ 0, /* duration= */ 100)));
+        List<VibrationEffectSegment> expectedSegments = Arrays.asList(
+                new StepSegment(/* amplitude= */ 1, /* frequencyHz= */ 0, /* duration= */ 10),
+                new StepSegment(/* amplitude= */ 0.5f, /* frequencyHz= */ 0, /* duration= */ 5),
+                new StepSegment(/* amplitude= */ 0, /* frequencyHz= */ 0, /* duration= */ 5),
+                new StepSegment(/* amplitude= */ 0.8f, /* frequencyHz= */ 0, /* duration= */ 100));
+
+        assertEquals(3, mAdapter.adaptToVibrator(EMPTY_VIBRATOR_INFO, segments, 2));
+        assertEquals(expectedSegments, segments);
+    }
+
+    @Test
+    public void testStepSegments_withZeroSegmentAtRepeat_fixesRepeatAndAppendOriginalToListEnd() {
+        List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
+                new StepSegment(/* amplitude= */ 1, /* frequencyHz= */ 0, /* duration= */ 10),
+                new StepSegment(/* amplitude= */ 0, /* frequencyHz= */ 0, /* duration= */ 50),
+                new StepSegment(/* amplitude= */ 1, /* frequencyHz= */ 0, /* duration= */ 100)));
+        List<VibrationEffectSegment> expectedSegments = Arrays.asList(
+                new StepSegment(/* amplitude= */ 1, /* frequencyHz= */ 0, /* duration= */ 10),
+                new StepSegment(/* amplitude= */ 0.75f, /* frequencyHz= */ 0, /* duration= */ 5),
+                new StepSegment(/* amplitude= */ 0.5f, /* frequencyHz= */ 0, /* duration= */ 5),
+                new StepSegment(/* amplitude= */ 0.25f, /* frequencyHz= */ 0, /* duration= */ 5),
+                new StepSegment(/* amplitude= */ 0, /* frequencyHz= */ 0, /* duration= */ 35),
+                new StepSegment(/* amplitude= */ 1, /* frequencyHz= */ 0, /* duration= */ 100),
+                // Original zero segment appended to the end of new looping vibration,
+                // then converted to ramp down as well.
+                new StepSegment(/* amplitude= */ 0.75f, /* frequencyHz= */ 0, /* duration= */ 5),
+                new StepSegment(/* amplitude= */ 0.5f, /* frequencyHz= */ 0, /* duration= */ 5),
+                new StepSegment(/* amplitude= */ 0.25f, /* frequencyHz= */ 0, /* duration= */ 5),
+                new StepSegment(/* amplitude= */ 0, /* frequencyHz= */ 0, /* duration= */ 35));
+
+        // Repeat index fixed after intermediate steps added
+        assertEquals(5, mAdapter.adaptToVibrator(EMPTY_VIBRATOR_INFO, segments, 1));
+        assertEquals(expectedSegments, segments);
+    }
+
+    @Test
+    public void testStepSegments_withRepeatToNonZeroSegment_keepsOriginalSteps() {
+        List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
+                new StepSegment(/* amplitude= */ 0.8f, /* frequencyHz= */ 0, /* duration= */ 10),
+                new StepSegment(/* amplitude= */ 0.5f, /* frequencyHz= */ 0, /* duration= */ 100)));
+        List<VibrationEffectSegment> originalSegments = new ArrayList<>(segments);
+
+        assertEquals(0, mAdapter.adaptToVibrator(EMPTY_VIBRATOR_INFO, segments, 0));
+
+        assertEquals(originalSegments, segments);
+    }
+
+    @Test
+    public void testStepSegments_withRepeatToShortZeroSegment_skipAndAppendRampDown() {
+        List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
+                new RampSegment(/* startAmplitude= */ 0, /* endAmplitude= */ 0,
+                        /* startfrequencyHz= */ 0, /* endfrequencyHz= */ 0, /* duration= */ 10),
+                new StepSegment(/* amplitude= */ 1, /* frequencyHz= */ 0, /* duration= */ 30)));
+        List<VibrationEffectSegment> expectedSegments = Arrays.asList(
+                new RampSegment(/* startAmplitude= */ 0, /* endAmplitude= */ 0,
+                        /* startfrequencyHz= */ 0, /* endfrequencyHz= */ 0, /* duration= */ 10),
+                new StepSegment(/* amplitude= */ 1, /* frequencyHz= */ 0, /* duration= */ 30),
+                new StepSegment(/* amplitude= */ 0.5f, /* frequencyHz= */ 0, /* duration= */ 5),
+                new StepSegment(/* amplitude= */ 0, /* frequencyHz= */ 0, /* duration= */ 5));
+
+        // Shift repeat index to the right to use append instead of zero segment.
+        assertEquals(1, mAdapter.adaptToVibrator(EMPTY_VIBRATOR_INFO, segments, 0));
+
+        assertEquals(expectedSegments, segments);
+    }
+
+    @Test
+    public void testStepSegments_withRepeatToLongZeroSegment_splitAndAppendRampDown() {
+        List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
+                new StepSegment(/* amplitude= */ 0, /* frequencyHz= */ 0, /* duration= */ 120),
+                new StepSegment(/* amplitude= */ 1, /* frequencyHz= */ 0, /* duration= */ 30)));
+        List<VibrationEffectSegment> expectedSegments = Arrays.asList(
+                // Split long zero segment to skip part of it.
+                new StepSegment(/* amplitude= */ 0, /* frequencyHz= */ 0, /* duration= */ 20),
+                new StepSegment(/* amplitude= */ 0, /* frequencyHz= */ 0, /* duration= */ 100),
+                new StepSegment(/* amplitude= */ 1, /* frequencyHz= */ 0, /* duration= */ 30),
+                new StepSegment(/* amplitude= */ 0.75f, /* frequencyHz= */ 0, /* duration= */ 5),
+                new StepSegment(/* amplitude= */ 0.5f, /* frequencyHz= */ 0, /* duration= */ 5),
+                new StepSegment(/* amplitude= */ 0.25f, /* frequencyHz= */ 0, /* duration= */ 5),
+                new StepSegment(/* amplitude= */ 0, /* frequencyHz= */ 0, /* duration= */ 5));
+
+        // Shift repeat index to the right to use append with part of the zero segment.
+        assertEquals(1, mAdapter.adaptToVibrator(EMPTY_VIBRATOR_INFO, segments, 0));
+
+        assertEquals(expectedSegments, segments);
+    }
+
+    @Test
+    public void testRampSegments_withShortZeroSegment_replaceWithRampDown() {
+        List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
+                new RampSegment(/* startAmplitude= */ 0.5f, /* endAmplitude*/ 0.5f,
+                        /* startFrequencyHz= */ 100, /* endFrequencyHz= */ 100, /* duration= */ 10),
+                new RampSegment(/* startAmplitude= */ 0, /* endAmplitude= */ 0,
+                        /* startFrequencyHz= */ 100, /* endFrequencyHz= */ 100, /* duration= */ 20),
+                new RampSegment(/* startAmplitude= */ 1, /* endAmplitude= */ 1,
+                        /* startFrequencyHz= */ 200, /* endFrequencyHz= */ 200,
+                        /* duration= */ 30)));
+        List<VibrationEffectSegment> expectedSegments = Arrays.asList(
+                new RampSegment(/* startAmplitude= */ 0.5f, /* endAmplitude*/ 0.5f,
+                        /* startFrequencyHz= */ 100, /* endFrequencyHz= */ 100, /* duration= */ 10),
+                new RampSegment(/* startAmplitude= */ 0.5f, /* endAmplitude= */ 0,
+                        /* startFrequencyHz= */ 100, /* endFrequencyHz= */ 100, /* duration= */ 20),
+                new RampSegment(/* startAmplitude= */ 1, /* endAmplitude= */ 1,
+                        /* startFrequencyHz= */ 200, /* endFrequencyHz= */ 200,
+                        /* duration= */ 30));
+
+        assertEquals(2, mAdapter.adaptToVibrator(EMPTY_VIBRATOR_INFO, segments, 2));
+
+        assertEquals(expectedSegments, segments);
+    }
+
+    @Test
+    public void testRampSegments_withLongZeroSegment_splitAndAddRampDown() {
+        List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
+                new RampSegment(/* startAmplitude= */ 0.5f, /* endAmplitude= */ 0.5f,
+                        /* startFrequencyHz= */ 100, /* endFrequencyHz= */ 100, /* duration= */ 10),
+                new StepSegment(/* amplitude= */ 0, /* frequencyHz= */ 150, /* duration= */ 150),
+                new RampSegment(/* startAmplitude= */ 1, /* endAmplitude= */ 1,
+                        /* startFrequencyHz= */ 200, /* endFrequencyHz= */ 200,
+                        /* duration= */ 30)));
+        List<VibrationEffectSegment> expectedSegments = Arrays.asList(
+                new RampSegment(/* startAmplitude= */ 0.5f, /* endAmplitude*/ 0.5f,
+                        /* startFrequencyHz= */ 100, /* endFrequencyHz= */ 100, /* duration= */ 10),
+                new RampSegment(/* startAmplitude= */ 0.5f, /* endAmplitude= */ 0,
+                        /* startFrequencyHz= */ 100, /* endFrequencyHz= */ 100, /* duration= */ 20),
+                new RampSegment(/* startAmplitude= */ 0, /* endAmplitude= */ 0,
+                        /* startFrequencyHz= */ 100, /* endFrequencyHz= */ 100,
+                        /* duration= */ 130),
+                new RampSegment(/* startAmplitude= */ 1, /* endAmplitude= */ 1,
+                        /* startFrequencyHz= */ 200, /* endFrequencyHz= */ 200,
+                        /* duration= */ 30));
+
+        // Repeat index fixed after intermediate steps added
+        assertEquals(3, mAdapter.adaptToVibrator(EMPTY_VIBRATOR_INFO, segments, 2));
+
+        assertEquals(expectedSegments, segments);
+    }
+
+    @Test
+    public void testRampSegments_withRepeatToNonZeroSegment_keepsOriginalSteps() {
+        List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
+                new RampSegment(/* startAmplitude= */ 0.5f, /* endAmplitude*/ 0.5f,
+                        /* startFrequencyHz= */ 100, /* endFrequencyHz= */ 100, /* duration= */ 10),
+                new RampSegment(/* startAmplitude= */ 1, /* endAmplitude= */ 1,
+                        /* startFrequencyHz= */ 200, /* endFrequencyHz= */ 200,
+                        /* duration= */ 30)));
+        List<VibrationEffectSegment> originalSegments = new ArrayList<>(segments);
+
+        assertEquals(0, mAdapter.adaptToVibrator(EMPTY_VIBRATOR_INFO, segments, 0));
+
+        assertEquals(originalSegments, segments);
+    }
+
+    @Test
+    public void testRampSegments_withRepeatToShortZeroSegment_skipAndAppendRampDown() {
+        List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
+                new StepSegment(/* amplitude= */ 0, /* frequencyHz= */ 200, /* duration= */ 20),
+                new RampSegment(/* startAmplitude= */ 0, /* endAmplitude*/ 1,
+                        /* startFrequencyHz= */ 40, /* endFrequencyHz= */ 80, /* duration= */ 20)));
+        List<VibrationEffectSegment> expectedSegments = Arrays.asList(
+                new StepSegment(/* amplitude= */ 0, /* frequencyHz= */ 200, /* duration= */ 20),
+                new RampSegment(/* startAmplitude= */ 0, /* endAmplitude= */ 1,
+                        /* startFrequencyHz= */ 40, /* endFrequencyHz= */ 80, /* duration= */ 20),
+                new RampSegment(/* startAmplitude= */ 1, /* endAmplitude= */ 0,
+                        /* startFrequencyHz= */ 80, /* endFrequencyHz= */ 80, /* duration= */ 20));
+
+        // Shift repeat index to the right to use append instead of zero segment.
+        assertEquals(1, mAdapter.adaptToVibrator(EMPTY_VIBRATOR_INFO, segments, 0));
+
+        assertEquals(expectedSegments, segments);
+    }
+
+    @Test
+    public void testRampSegments_withRepeatToLongZeroSegment_splitAndAppendRampDown() {
+        List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
+                new RampSegment(/* startAmplitude= */ 0, /* endAmplitude*/ 0,
+                        /* startFrequencyHz= */ 1, /* endFrequencyHz= */ 1, /* duration= */ 70),
+                new RampSegment(/* startAmplitude= */ 0, /* endAmplitude= */ 1,
+                        /* startFrequencyHz= */ 1, /* endFrequencyHz= */ 1, /* duration= */ 30)));
+        List<VibrationEffectSegment> expectedSegments = Arrays.asList(
+                // Split long zero segment to skip part of it.
+                new RampSegment(/* startAmplitude= */ 0, /* endAmplitude*/ 0,
+                        /* startFrequencyHz= */ 1, /* endFrequencyHz= */ 1, /* duration= */ 20),
+                new RampSegment(/* startAmplitude= */ 0, /* endAmplitude*/ 0,
+                        /* startFrequencyHz= */ 1, /* endFrequencyHz= */ 1, /* duration= */ 50),
+                new RampSegment(/* startAmplitude= */ 0, /* endAmplitude= */ 1,
+                        /* startFrequencyHz= */ 1, /* endFrequencyHz= */ 1, /* duration= */ 30),
+                new RampSegment(/* startAmplitude= */ 1, /* endAmplitude= */ 0,
+                        /* startFrequencyHz= */ 1, /* endFrequencyHz= */ 1, /* duration= */ 20));
+
+        // Shift repeat index to the right to use append with part of the zero segment.
+        assertEquals(1, mAdapter.adaptToVibrator(EMPTY_VIBRATOR_INFO, segments, 0));
+
+        assertEquals(expectedSegments, segments);
+    }
+}
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/RampToStepAdapterTest.java b/services/tests/vibrator/src/com/android/server/vibrator/RampToStepAdapterTest.java
new file mode 100644
index 0000000..8bb21b3
--- /dev/null
+++ b/services/tests/vibrator/src/com/android/server/vibrator/RampToStepAdapterTest.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.vibrator;
+
+import static org.junit.Assert.assertEquals;
+
+import android.hardware.vibrator.IVibrator;
+import android.os.VibrationEffect;
+import android.os.VibratorInfo;
+import android.os.vibrator.PrebakedSegment;
+import android.os.vibrator.PrimitiveSegment;
+import android.os.vibrator.RampSegment;
+import android.os.vibrator.StepSegment;
+import android.os.vibrator.VibrationEffectSegment;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.IntStream;
+
+public class RampToStepAdapterTest {
+    private static final int TEST_STEP_DURATION = 5;
+    private static final float[] TEST_AMPLITUDE_MAP = new float[]{
+            /* 50Hz= */ 0.1f, 0.2f, 0.4f, 0.8f, /* 150Hz= */ 1f, 0.9f, /* 200Hz= */ 0.8f};
+    private static final VibratorInfo.FrequencyProfile TEST_FREQUENCY_PROFILE =
+            new VibratorInfo.FrequencyProfile(
+                    /* resonantFrequencyHz= */ 150f, /* minFrequencyHz= */ 50f,
+                    /* frequencyResolutionHz= */ 25f, TEST_AMPLITUDE_MAP);
+    private static final VibratorInfo EMPTY_VIBRATOR_INFO = createVibratorInfo();
+    private static final VibratorInfo PWLE_VIBRATOR_INFO = createVibratorInfo(
+            IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
+
+    private RampToStepAdapter mAdapter;
+
+    @Before
+    public void setUp() throws Exception {
+        mAdapter = new RampToStepAdapter(TEST_STEP_DURATION);
+    }
+
+    @Test
+    public void testStepAndPrebakedAndPrimitiveSegments_keepsListUnchanged() {
+        List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
+                new StepSegment(/* amplitude= */ 0, /* frequencyHz= */ 1, /* duration= */ 10),
+                new PrebakedSegment(
+                        VibrationEffect.EFFECT_CLICK, false, VibrationEffect.EFFECT_STRENGTH_LIGHT),
+                new PrimitiveSegment(VibrationEffect.Composition.PRIMITIVE_TICK, 1, 10)));
+        List<VibrationEffectSegment> originalSegments = new ArrayList<>(segments);
+
+        assertEquals(-1, mAdapter.adaptToVibrator(EMPTY_VIBRATOR_INFO, segments, -1));
+        assertEquals(1, mAdapter.adaptToVibrator(EMPTY_VIBRATOR_INFO, segments, 1));
+
+        assertEquals(originalSegments, segments);
+    }
+
+    @Test
+    public void testRampSegments_withPwleCapability_keepsListUnchanged() {
+        List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
+                new StepSegment(/* amplitude= */ 0.5f, /* frequencyHz= */ 0, /* duration= */ 100),
+                new RampSegment(/* startAmplitude= */ 0.8f, /* endAmplitude= */ 0.2f,
+                        /* startFrequencyHz= */ 100, /* endFrequencyHz= */ 1, /* duration= */ 20)));
+        List<VibrationEffectSegment> originalSegments = new ArrayList<>(segments);
+
+        assertEquals(-1, mAdapter.adaptToVibrator(PWLE_VIBRATOR_INFO, segments, -1));
+        assertEquals(0, mAdapter.adaptToVibrator(PWLE_VIBRATOR_INFO, segments, 0));
+
+        assertEquals(originalSegments, segments);
+    }
+
+    @Test
+    public void testRampSegments_withoutPwleCapability_convertsRampsToSteps() {
+        List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
+                new StepSegment(/* amplitude= */ 0, /* frequencyHz= */ 1, /* duration= */ 10),
+                new StepSegment(/* amplitude= */ 0.5f, /* frequencyHz= */ 10, /* duration= */ 100),
+                new RampSegment(/* startAmplitude= */ 1, /* endAmplitude= */ 0.2f,
+                        /* startFrequencyHz= */ 10, /* endFrequencyHz= */ 0, /* duration= */ 10),
+                new RampSegment(/* startAmplitude= */ 0.8f, /* endAmplitude= */ 0.2f,
+                        /* startFrequencyHz= */ 30, /* endFrequencyHz= */ 60, /* duration= */ 11),
+                new RampSegment(/* startAmplitude= */ 0.65f, /* endAmplitude= */ 0.65f,
+                        /* startFrequencyHz= */ 0, /* endFrequencyHz= */ 1, /* duration= */ 200)));
+
+        List<VibrationEffectSegment> expectedSegments = Arrays.asList(
+                new StepSegment(/* amplitude= */ 0, /* frequencyHz= */ 1, /* duration= */ 10),
+                new StepSegment(/* amplitude= */ 0.5f, /* frequencyHz= */ 10, /* duration= */ 100),
+                // 10ms ramp becomes 2 steps
+                new StepSegment(/* amplitude= */ 1, /* frequencyHz= */ 10, /* duration= */ 5),
+                new StepSegment(/* amplitude= */ 0.2f, /* frequencyHz= */ 150, /* duration= */ 5),
+                // 11ms ramp becomes 3 steps
+                new StepSegment(/* amplitude= */ 0.8f, /* frequencyHz= */ 30, /* duration= */ 5),
+                new StepSegment(/* amplitude= */ 0.6f, /* frequencyHz= */ 40, /* duration= */ 5),
+                new StepSegment(/* amplitude= */ 0.2f, /* frequencyHz= */ 60, /* duration= */ 1),
+                // 200ms ramp with same amplitude becomes a single step
+                new StepSegment(/* amplitude= */ 0.65f, /* frequencyHz= */ 150,
+                        /* duration= */ 200));
+
+        // Repeat index fixed after intermediate steps added
+        assertEquals(4, mAdapter.adaptToVibrator(EMPTY_VIBRATOR_INFO, segments, 3));
+
+        assertEquals(expectedSegments, segments);
+    }
+
+    private static VibratorInfo createVibratorInfo(int... capabilities) {
+        return new VibratorInfo.Builder(0)
+                .setCapabilities(IntStream.of(capabilities).reduce((a, b) -> a | b).orElse(0))
+                .setFrequencyProfile(TEST_FREQUENCY_PROFILE)
+                .build();
+    }
+}
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/StepToRampAdapterTest.java b/services/tests/vibrator/src/com/android/server/vibrator/StepToRampAdapterTest.java
new file mode 100644
index 0000000..58deeec
--- /dev/null
+++ b/services/tests/vibrator/src/com/android/server/vibrator/StepToRampAdapterTest.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.vibrator;
+
+import static org.junit.Assert.assertEquals;
+
+import android.hardware.vibrator.IVibrator;
+import android.os.VibrationEffect;
+import android.os.VibratorInfo;
+import android.os.vibrator.PrebakedSegment;
+import android.os.vibrator.PrimitiveSegment;
+import android.os.vibrator.RampSegment;
+import android.os.vibrator.StepSegment;
+import android.os.vibrator.VibrationEffectSegment;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.IntStream;
+
+public class StepToRampAdapterTest {
+    private static final float[] TEST_AMPLITUDE_MAP = new float[]{
+            /* 50Hz= */ 0.1f, 0.2f, 0.4f, 0.8f, /* 150Hz= */ 1f, 0.9f, /* 200Hz= */ 0.8f};
+    private static final VibratorInfo.FrequencyProfile TEST_FREQUENCY_PROFILE =
+            new VibratorInfo.FrequencyProfile(
+                    /* resonantFrequencyHz= */ 150f, /* minFrequencyHz= */ 50f,
+                    /* frequencyResolutionHz= */ 25f, TEST_AMPLITUDE_MAP);
+    private static final VibratorInfo EMPTY_VIBRATOR_INFO = createVibratorInfo();
+    private static final VibratorInfo PWLE_VIBRATOR_INFO = createVibratorInfo(
+            IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
+
+    private StepToRampAdapter mAdapter;
+
+    @Before
+    public void setUp() throws Exception {
+        mAdapter = new StepToRampAdapter();
+    }
+
+    @Test
+    public void testRampAndPrebakedAndPrimitiveSegments_returnsOriginalSegments() {
+        List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
+                new RampSegment(/* startAmplitude= */ 1, /* endAmplitude= */ 0.2f,
+                        /* startFrequencyHz= */ 40f, /* endFrequencyHz= */ 20f, /* duration= */ 10),
+                new PrebakedSegment(
+                        VibrationEffect.EFFECT_CLICK, false, VibrationEffect.EFFECT_STRENGTH_LIGHT),
+                new PrimitiveSegment(VibrationEffect.Composition.PRIMITIVE_TICK, 1, 10)));
+        List<VibrationEffectSegment> originalSegments = new ArrayList<>(segments);
+
+        assertEquals(-1, mAdapter.adaptToVibrator(EMPTY_VIBRATOR_INFO, segments, -1));
+        assertEquals(1, mAdapter.adaptToVibrator(EMPTY_VIBRATOR_INFO, segments, 1));
+
+        assertEquals(originalSegments, segments);
+    }
+
+    @Test
+    public void testRampSegments_withPwleDurationLimit_splitsLongRamps() {
+        List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
+                new RampSegment(/* startAmplitude= */ 0.5f, /* endAmplitude*/ 0.5f,
+                        /* startFrequencyHz= */ 10, /* endFrequencyHz= */ 10, /* duration= */ 10),
+                new RampSegment(/* startAmplitude= */ 0, /* endAmplitude= */ 1,
+                        /* startFrequencyHz= */ 0, /* endFrequencyHz= */ 50, /* duration= */ 25),
+                new RampSegment(/* startAmplitude= */ 1, /* endAmplitude*/ 1,
+                        /* startFrequencyHz= */ 10, /* endFrequencyHz= */ 20, /* duration= */ 5)));
+        List<VibrationEffectSegment> expectedSegments = Arrays.asList(
+                new RampSegment(/* startAmplitude= */ 0.5f, /* endAmplitude*/ 0.5f,
+                        /* startFrequencyHz= */ 10, /* endFrequencyHz= */ 10, /* duration= */ 10),
+                new RampSegment(/* startAmplitude= */ 0, /* endAmplitude= */ 0.32f,
+                        /* startFrequencyHz= */ 150, /* endFrequencyHz= */ 118f, /* duration= */ 8),
+                new RampSegment(/* startAmplitude= */ 0.32f, /* endAmplitude= */ 0.64f,
+                        /* startFrequencyHz= */ 118f, /* endFrequencyHz= */ 86f,
+                        /* duration= */ 8),
+                new RampSegment(/* startAmplitude= */ 0.64f, /* endAmplitude= */ 1,
+                        /* startFrequencyHz= */ 86f, /* endFrequencyHz= */ 50f, /* duration= */ 9),
+                new RampSegment(/* startAmplitude= */ 1, /* endAmplitude*/ 1,
+                        /* startFrequencyHz= */ 10, /* endFrequencyHz= */ 20, /* duration= */ 5));
+
+        VibratorInfo vibratorInfo = new VibratorInfo.Builder(0)
+                .setCapabilities(IVibrator.CAP_COMPOSE_PWLE_EFFECTS)
+                .setPwlePrimitiveDurationMax(10)
+                .setFrequencyProfile(TEST_FREQUENCY_PROFILE)
+                .build();
+
+        // Update repeat index to skip the ramp splits.
+        assertEquals(4, mAdapter.adaptToVibrator(vibratorInfo, segments, 2));
+        assertEquals(expectedSegments, segments);
+    }
+
+    @Test
+    public void testStepAndRampSegments_withoutPwleCapability_keepsListUnchanged() {
+        List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
+                new StepSegment(/* amplitude= */ 0, /* frequencyHz= */ 1, /* duration= */ 10),
+                new RampSegment(/* startAmplitude= */ 0.8f, /* endAmplitude= */ 0.2f,
+                        /* startFrequencyHz= */ 10, /* endFrequencyHz= */ 50, /* duration= */ 20)));
+        List<VibrationEffectSegment> originalSegments = new ArrayList<>(segments);
+
+        assertEquals(-1, mAdapter.adaptToVibrator(EMPTY_VIBRATOR_INFO, segments, -1));
+        assertEquals(0, mAdapter.adaptToVibrator(EMPTY_VIBRATOR_INFO, segments, 0));
+
+        assertEquals(originalSegments, segments);
+    }
+
+    @Test
+    public void testStepAndRampSegments_withPwleCapabilityAndNoFrequency_keepsOriginalSteps() {
+        List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
+                new StepSegment(/* amplitude= */ 0, /* frequencyHz= */ 0, /* duration= */ 10),
+                new StepSegment(/* amplitude= */ 0.5f, /* frequencyHz= */ 0, /* duration= */ 100),
+                new PrimitiveSegment(VibrationEffect.Composition.PRIMITIVE_TICK, 1, 10),
+                new RampSegment(/* startAmplitude= */ 1, /* endAmplitude= */ 1,
+                        /* startFrequencyHz= */ 40, /* endFrequencyHz= */ 200, /* duration= */ 50),
+                new RampSegment(/* startAmplitude= */ 0.8f, /* endAmplitude= */ 0.2f,
+                        /* startFrequencyHz= */ 10, /* endFrequencyHz= */ 1, /* duration= */ 20)));
+        List<VibrationEffectSegment> originalSegments = new ArrayList<>(segments);
+
+        assertEquals(-1, mAdapter.adaptToVibrator(PWLE_VIBRATOR_INFO, segments, -1));
+        assertEquals(3, mAdapter.adaptToVibrator(PWLE_VIBRATOR_INFO, segments, 3));
+
+        assertEquals(originalSegments, segments);
+    }
+
+    @Test
+    public void testStepAndRampSegments_withPwleCapabilityAndStepNextToRamp_convertsStepsToRamps() {
+        List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
+                new StepSegment(/* amplitude= */ 0, /* frequencyHz= */ 200, /* duration= */ 10),
+                new StepSegment(/* amplitude= */ 0.5f, /* frequencyHz= */ 150, /* duration= */ 60),
+                new RampSegment(/* startAmplitude= */ 1, /* endAmplitude= */ 1,
+                        /* startFrequencyHz= */ 1, /* endFrequencyHz= */ 300, /* duration= */ 50),
+                new RampSegment(/* startAmplitude= */ 0.8f, /* endAmplitude= */ 0.2f,
+                        /* startFrequencyHz= */ 1000, /* endFrequencyHz= */ 1, /* duration= */ 20),
+                new StepSegment(/* amplitude= */ 0.8f, /* frequencyHz= */ 10, /* duration= */ 60)));
+
+        List<VibrationEffectSegment> expectedSegments = Arrays.asList(
+                new RampSegment(/* startAmplitude= */ 0, /* endAmplitude*/ 0,
+                        /* startFrequencyHz= */ 200, /* endFrequencyHz= */ 200, /* duration= */ 10),
+                new RampSegment(/* startAmplitude= */ 0.5f, /* endAmplitude= */ 0.5f,
+                        /* startFrequencyHz= */ 150, /* endFrequencyHz= */ 150, /* duration= */ 60),
+                new RampSegment(/* startAmplitude= */ 1, /* endAmplitude= */ 1,
+                        /* startFrequencyHz= */ 1, /* endFrequencyHz= */ 300, /* duration= */ 50),
+                new RampSegment(/* startAmplitude= */ 0.8f, /* endAmplitude= */ 0.2f,
+                        /* startFrequencyHz= */ 1000, /* endFrequencyHz= */ 1, /* duration= */ 20),
+                new RampSegment(/* startAmplitude= */ 0.8f, /* endAmplitude= */ 0.8f,
+                        /* startFrequencyHz= */ 10, /* endFrequencyHz= */ 10, /* duration= */ 60));
+
+        assertEquals(-1, mAdapter.adaptToVibrator(PWLE_VIBRATOR_INFO, segments, -1));
+        assertEquals(2, mAdapter.adaptToVibrator(PWLE_VIBRATOR_INFO, segments, 2));
+
+        assertEquals(expectedSegments, segments);
+    }
+
+    @Test
+    public void testStepSegments_withPwleCapabilityAndFrequency_convertsStepsToRamps() {
+        List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
+                new StepSegment(/* amplitude= */ 0, /* frequencyHz= */ 100, /* duration= */ 10),
+                new StepSegment(/* amplitude= */ 0.5f, /* frequencyHz= */ 0, /* duration= */ 6)));
+        List<VibrationEffectSegment> expectedSegments = Arrays.asList(
+                new RampSegment(/* startAmplitude= */ 0, /* endAmplitude*/ 0,
+                        /* startFrequencyHz= */ 100, /* endFrequencyHz= */ 100, /* duration= */ 10),
+                new RampSegment(/* startAmplitude= */ 0.5f, /* endAmplitude= */ 0.5f,
+                        /* startFrequencyHz= */ 150, /* endFrequencyHz= */ 150, /* duration= */ 6));
+
+        assertEquals(-1, mAdapter.adaptToVibrator(PWLE_VIBRATOR_INFO, segments, -1));
+        assertEquals(0, mAdapter.adaptToVibrator(PWLE_VIBRATOR_INFO, segments, 0));
+
+        assertEquals(expectedSegments, segments);
+    }
+
+    private static VibratorInfo createVibratorInfo(int... capabilities) {
+        return new VibratorInfo.Builder(0)
+                .setCapabilities(IntStream.of(capabilities).reduce((a, b) -> a | b).orElse(0))
+                .setFrequencyProfile(TEST_FREQUENCY_PROFILE)
+                .build();
+    }
+}
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibrationScalerTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationScalerTest.java
new file mode 100644
index 0000000..bbca704e
--- /dev/null
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationScalerTest.java
@@ -0,0 +1,275 @@
+/*
+ * 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.
+ */
+
+package com.android.server.vibrator;
+
+import static android.os.VibrationAttributes.USAGE_NOTIFICATION;
+import static android.os.VibrationAttributes.USAGE_RINGTONE;
+import static android.os.VibrationAttributes.USAGE_TOUCH;
+import static android.os.Vibrator.VIBRATION_INTENSITY_HIGH;
+import static android.os.Vibrator.VIBRATION_INTENSITY_LOW;
+import static android.os.Vibrator.VIBRATION_INTENSITY_MEDIUM;
+import static android.os.Vibrator.VIBRATION_INTENSITY_OFF;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.ContextWrapper;
+import android.content.pm.PackageManagerInternal;
+import android.os.Handler;
+import android.os.IExternalVibratorService;
+import android.os.PowerManagerInternal;
+import android.os.UserHandle;
+import android.os.VibrationAttributes;
+import android.os.VibrationEffect;
+import android.os.Vibrator;
+import android.os.test.TestLooper;
+import android.os.vibrator.PrebakedSegment;
+import android.os.vibrator.PrimitiveSegment;
+import android.os.vibrator.StepSegment;
+import android.os.vibrator.VibrationConfig;
+import android.os.vibrator.VibrationEffectSegment;
+import android.provider.Settings;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.internal.util.test.FakeSettingsProviderRule;
+import com.android.server.LocalServices;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+public class VibrationScalerTest {
+
+    @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
+    @Rule public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
+
+    @Mock private PowerManagerInternal mPowerManagerInternalMock;
+    @Mock private PackageManagerInternal mPackageManagerInternalMock;
+    @Mock private VibrationConfig mVibrationConfigMock;
+
+    private TestLooper mTestLooper;
+    private ContextWrapper mContextSpy;
+    private VibrationSettings mVibrationSettings;
+    private VibrationScaler mVibrationScaler;
+
+    @Before
+    public void setUp() throws Exception {
+        mTestLooper = new TestLooper();
+        mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getContext()));
+
+        ContentResolver contentResolver = mSettingsProviderRule.mockContentResolver(mContextSpy);
+        when(mContextSpy.getContentResolver()).thenReturn(contentResolver);
+        when(mPackageManagerInternalMock.getSystemUiServiceComponent())
+                .thenReturn(new ComponentName("", ""));
+
+        LocalServices.removeServiceForTest(PackageManagerInternal.class);
+        LocalServices.addService(PackageManagerInternal.class, mPackageManagerInternalMock);
+        LocalServices.removeServiceForTest(PowerManagerInternal.class);
+        LocalServices.addService(PowerManagerInternal.class, mPowerManagerInternalMock);
+
+        Settings.System.putInt(contentResolver, Settings.System.HAPTIC_FEEDBACK_ENABLED, 1);
+        Settings.System.putInt(contentResolver, Settings.System.VIBRATE_WHEN_RINGING, 1);
+
+        mVibrationSettings = new VibrationSettings(
+                mContextSpy, new Handler(mTestLooper.getLooper()), mVibrationConfigMock);
+        mVibrationScaler = new VibrationScaler(mContextSpy, mVibrationSettings);
+
+        mVibrationSettings.onSystemReady();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        LocalServices.removeServiceForTest(PowerManagerInternal.class);
+    }
+
+    @Test
+    public void testGetExternalVibrationScale() {
+        setDefaultIntensity(USAGE_TOUCH, Vibrator.VIBRATION_INTENSITY_LOW);
+        setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_HIGH);
+        assertEquals(IExternalVibratorService.SCALE_VERY_HIGH,
+                mVibrationScaler.getExternalVibrationScale(USAGE_TOUCH));
+
+        setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_MEDIUM);
+        assertEquals(IExternalVibratorService.SCALE_HIGH,
+                mVibrationScaler.getExternalVibrationScale(USAGE_TOUCH));
+
+        setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_LOW);
+        assertEquals(IExternalVibratorService.SCALE_NONE,
+                mVibrationScaler.getExternalVibrationScale(USAGE_TOUCH));
+
+        setDefaultIntensity(USAGE_TOUCH, VIBRATION_INTENSITY_MEDIUM);
+        assertEquals(IExternalVibratorService.SCALE_LOW,
+                mVibrationScaler.getExternalVibrationScale(USAGE_TOUCH));
+
+        setDefaultIntensity(USAGE_TOUCH, VIBRATION_INTENSITY_HIGH);
+        assertEquals(IExternalVibratorService.SCALE_VERY_LOW,
+                mVibrationScaler.getExternalVibrationScale(USAGE_TOUCH));
+
+        setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF);
+        // Vibration setting being bypassed will use default setting and not scale.
+        assertEquals(IExternalVibratorService.SCALE_NONE,
+                mVibrationScaler.getExternalVibrationScale(USAGE_TOUCH));
+    }
+
+    @Test
+    public void scale_withPrebakedSegment_setsEffectStrengthBasedOnSettings() {
+        setDefaultIntensity(USAGE_NOTIFICATION, VIBRATION_INTENSITY_MEDIUM);
+        setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_HIGH);
+        PrebakedSegment effect = new PrebakedSegment(VibrationEffect.EFFECT_CLICK,
+                /* shouldFallback= */ false, VibrationEffect.EFFECT_STRENGTH_MEDIUM);
+
+        PrebakedSegment scaled = mVibrationScaler.scale(effect, USAGE_NOTIFICATION);
+        assertEquals(scaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_STRONG);
+
+        setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
+                VIBRATION_INTENSITY_MEDIUM);
+        scaled = mVibrationScaler.scale(effect, USAGE_NOTIFICATION);
+        assertEquals(scaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_MEDIUM);
+
+        setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_LOW);
+        scaled = mVibrationScaler.scale(effect, USAGE_NOTIFICATION);
+        assertEquals(scaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_LIGHT);
+
+        setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_OFF);
+        scaled = mVibrationScaler.scale(effect, USAGE_NOTIFICATION);
+        // Vibration setting being bypassed will use default setting.
+        assertEquals(scaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_MEDIUM);
+    }
+
+    @Test
+    public void scale_withPrebakedEffect_setsEffectStrengthBasedOnSettings() {
+        setDefaultIntensity(USAGE_NOTIFICATION, VIBRATION_INTENSITY_LOW);
+        setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_HIGH);
+        VibrationEffect effect = VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK);
+
+        PrebakedSegment scaled =
+                getFirstSegment(mVibrationScaler.scale(effect, USAGE_NOTIFICATION));
+        assertEquals(scaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_STRONG);
+
+        setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
+                VIBRATION_INTENSITY_MEDIUM);
+        scaled = getFirstSegment(mVibrationScaler.scale(effect, USAGE_NOTIFICATION));
+        assertEquals(scaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_MEDIUM);
+
+        setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_LOW);
+        scaled = getFirstSegment(mVibrationScaler.scale(effect, USAGE_NOTIFICATION));
+        assertEquals(scaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_LIGHT);
+
+        setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_OFF);
+        scaled = getFirstSegment(mVibrationScaler.scale(effect, USAGE_NOTIFICATION));
+        // Vibration setting being bypassed will use default setting.
+        assertEquals(scaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_LIGHT);
+    }
+
+    @Test
+    public void scale_withOneShotAndWaveform_resolvesAmplitude() {
+        // No scale, default amplitude still resolved
+        setDefaultIntensity(USAGE_RINGTONE, VIBRATION_INTENSITY_LOW);
+        setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, VIBRATION_INTENSITY_LOW);
+
+        StepSegment resolved = getFirstSegment(mVibrationScaler.scale(
+                VibrationEffect.createOneShot(10, VibrationEffect.DEFAULT_AMPLITUDE),
+                USAGE_RINGTONE));
+        assertTrue(resolved.getAmplitude() > 0);
+
+        resolved = getFirstSegment(mVibrationScaler.scale(
+                VibrationEffect.createWaveform(new long[]{10},
+                        new int[]{VibrationEffect.DEFAULT_AMPLITUDE}, -1),
+                USAGE_RINGTONE));
+        assertTrue(resolved.getAmplitude() > 0);
+    }
+
+    @Test
+    public void scale_withOneShotAndWaveform_scalesAmplitude() {
+        setDefaultIntensity(USAGE_RINGTONE, VIBRATION_INTENSITY_LOW);
+        setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, VIBRATION_INTENSITY_HIGH);
+        setDefaultIntensity(USAGE_NOTIFICATION, VIBRATION_INTENSITY_HIGH);
+        setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_LOW);
+        setDefaultIntensity(USAGE_TOUCH, VIBRATION_INTENSITY_MEDIUM);
+        setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_MEDIUM);
+
+        StepSegment scaled = getFirstSegment(mVibrationScaler.scale(
+                VibrationEffect.createOneShot(128, 128), USAGE_RINGTONE));
+        // Ringtone scales up.
+        assertTrue(scaled.getAmplitude() > 0.5);
+
+        scaled = getFirstSegment(mVibrationScaler.scale(
+                VibrationEffect.createWaveform(new long[]{128}, new int[]{128}, -1),
+                USAGE_NOTIFICATION));
+        // Notification scales down.
+        assertTrue(scaled.getAmplitude() < 0.5);
+
+        scaled = getFirstSegment(mVibrationScaler.scale(VibrationEffect.createOneShot(128, 128),
+                USAGE_TOUCH));
+        // Haptic feedback does not scale.
+        assertEquals(128f / 255, scaled.getAmplitude(), 1e-5);
+    }
+
+    @Test
+    public void scale_withComposed_scalesPrimitives() {
+        setDefaultIntensity(USAGE_RINGTONE, VIBRATION_INTENSITY_LOW);
+        setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, VIBRATION_INTENSITY_HIGH);
+        setDefaultIntensity(USAGE_NOTIFICATION, VIBRATION_INTENSITY_HIGH);
+        setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_LOW);
+        setDefaultIntensity(USAGE_TOUCH, VIBRATION_INTENSITY_MEDIUM);
+        setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_MEDIUM);
+
+        VibrationEffect composed = VibrationEffect.startComposition()
+                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f).compose();
+
+        PrimitiveSegment scaled = getFirstSegment(mVibrationScaler.scale(composed, USAGE_RINGTONE));
+        // Ringtone scales up.
+        assertTrue(scaled.getScale() > 0.5f);
+
+        scaled = getFirstSegment(mVibrationScaler.scale(composed, USAGE_NOTIFICATION));
+        // Notification scales down.
+        assertTrue(scaled.getScale() < 0.5f);
+
+        scaled = getFirstSegment(mVibrationScaler.scale(composed, USAGE_TOUCH));
+        // Haptic feedback does not scale.
+        assertEquals(0.5, scaled.getScale(), 1e-5);
+    }
+
+    private void setDefaultIntensity(@VibrationAttributes.Usage int usage,
+            @Vibrator.VibrationIntensity int intensity) {
+        when(mVibrationConfigMock.getDefaultVibrationIntensity(eq(usage))).thenReturn(intensity);
+    }
+
+    private <T extends VibrationEffectSegment> T getFirstSegment(VibrationEffect effect) {
+        assertTrue(effect instanceof VibrationEffect.Composed);
+        return (T) ((VibrationEffect.Composed) effect).getSegments().get(0);
+    }
+
+    private void setUserSetting(String settingName, int value) {
+        Settings.System.putIntForUser(
+                mContextSpy.getContentResolver(), settingName, value, UserHandle.USER_CURRENT);
+        // FakeSettingsProvider don't support testing triggering ContentObserver yet.
+        mVibrationSettings.mSettingObserver.onChange(false);
+    }
+}
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java
new file mode 100644
index 0000000..1ae0966
--- /dev/null
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java
@@ -0,0 +1,984 @@
+/*
+ * 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.
+ */
+
+package com.android.server.vibrator;
+
+import static android.os.BatteryManager.BATTERY_PLUGGED_USB;
+import static android.os.BatteryManager.BATTERY_PLUGGED_WIRELESS;
+import static android.os.BatteryManager.EXTRA_PLUGGED;
+import static android.os.VibrationAttributes.USAGE_ACCESSIBILITY;
+import static android.os.VibrationAttributes.USAGE_ALARM;
+import static android.os.VibrationAttributes.USAGE_COMMUNICATION_REQUEST;
+import static android.os.VibrationAttributes.USAGE_HARDWARE_FEEDBACK;
+import static android.os.VibrationAttributes.USAGE_MEDIA;
+import static android.os.VibrationAttributes.USAGE_NOTIFICATION;
+import static android.os.VibrationAttributes.USAGE_PHYSICAL_EMULATION;
+import static android.os.VibrationAttributes.USAGE_RINGTONE;
+import static android.os.VibrationAttributes.USAGE_TOUCH;
+import static android.os.VibrationAttributes.USAGE_UNKNOWN;
+import static android.os.Vibrator.VIBRATION_INTENSITY_HIGH;
+import static android.os.Vibrator.VIBRATION_INTENSITY_LOW;
+import static android.os.Vibrator.VIBRATION_INTENSITY_MEDIUM;
+import static android.os.Vibrator.VIBRATION_INTENSITY_OFF;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManager;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.Intent;
+import android.content.pm.PackageManagerInternal;
+import android.media.AudioManager;
+import android.os.Handler;
+import android.os.PowerManager;
+import android.os.PowerManagerInternal;
+import android.os.PowerSaveState;
+import android.os.Process;
+import android.os.UserHandle;
+import android.os.VibrationAttributes;
+import android.os.VibrationEffect;
+import android.os.Vibrator;
+import android.os.test.TestLooper;
+import android.os.vibrator.VibrationConfig;
+import android.provider.Settings;
+import android.util.ArraySet;
+import android.view.Display;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.internal.util.test.FakeSettingsProviderRule;
+import com.android.server.LocalServices;
+import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+public class VibrationSettingsTest {
+
+    private static final int UID = 1;
+    private static final int VIRTUAL_DISPLAY_ID = 1;
+    private static final String SYSUI_PACKAGE_NAME = "sysui";
+    private static final PowerSaveState NORMAL_POWER_STATE = new PowerSaveState.Builder().build();
+    private static final PowerSaveState LOW_POWER_STATE = new PowerSaveState.Builder()
+            .setBatterySaverEnabled(true).build();
+
+    private static final int[] ALL_USAGES = new int[] {
+            USAGE_UNKNOWN,
+            USAGE_ACCESSIBILITY,
+            USAGE_ALARM,
+            USAGE_COMMUNICATION_REQUEST,
+            USAGE_HARDWARE_FEEDBACK,
+            USAGE_MEDIA,
+            USAGE_NOTIFICATION,
+            USAGE_PHYSICAL_EMULATION,
+            USAGE_RINGTONE,
+            USAGE_TOUCH,
+    };
+
+    @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
+    @Rule public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
+
+    @Mock private VibrationSettings.OnVibratorSettingsChanged mListenerMock;
+    @Mock private PowerManagerInternal mPowerManagerInternalMock;
+    @Mock private VirtualDeviceManagerInternal mVirtualDeviceManagerInternalMock;
+    @Mock private PackageManagerInternal mPackageManagerInternalMock;
+    @Mock private AudioManager mAudioManagerMock;
+    @Mock private VibrationConfig mVibrationConfigMock;
+
+    private TestLooper mTestLooper;
+    private ContextWrapper mContextSpy;
+    private VibrationSettings mVibrationSettings;
+    private PowerManagerInternal.LowPowerModeListener mRegisteredPowerModeListener;
+    private BroadcastReceiver mRegisteredBatteryBroadcastReceiver;
+    private VirtualDeviceManagerInternal.VirtualDisplayListener mRegisteredVirtualDisplayListener;
+    private VirtualDeviceManagerInternal.AppsOnVirtualDeviceListener
+            mRegisteredAppsOnVirtualDeviceListener;
+
+    @Before
+    public void setUp() throws Exception {
+        mTestLooper = new TestLooper();
+        mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getContext()));
+
+        ContentResolver contentResolver = mSettingsProviderRule.mockContentResolver(mContextSpy);
+        when(mContextSpy.getContentResolver()).thenReturn(contentResolver);
+        when(mContextSpy.getSystemService(eq(Context.AUDIO_SERVICE))).thenReturn(mAudioManagerMock);
+        doAnswer(invocation -> {
+            mRegisteredPowerModeListener = invocation.getArgument(0);
+            return null;
+        }).when(mPowerManagerInternalMock).registerLowPowerModeObserver(any());
+        when(mPackageManagerInternalMock.getSystemUiServiceComponent())
+                .thenReturn(new ComponentName(SYSUI_PACKAGE_NAME, ""));
+        doAnswer(invocation -> {
+            mRegisteredVirtualDisplayListener = invocation.getArgument(0);
+            return null;
+        }).when(mVirtualDeviceManagerInternalMock).registerVirtualDisplayListener(any());
+        doAnswer(invocation -> {
+            mRegisteredAppsOnVirtualDeviceListener = invocation.getArgument(0);
+            return null;
+        }).when(mVirtualDeviceManagerInternalMock).registerAppsOnVirtualDeviceListener(any());
+
+        removeServicesForTest();
+        addServicesForTest();
+
+        setDefaultIntensity(VIBRATION_INTENSITY_MEDIUM);
+
+        setIgnoreVibrationsOnWirelessCharger(false);
+        createSystemReadyVibrationSettings();
+
+        mockGoToSleep(/* goToSleepTime= */ 0, PowerManager.GO_TO_SLEEP_REASON_TIMEOUT);
+    }
+
+    private void createSystemReadyVibrationSettings() {
+        mVibrationSettings = new VibrationSettings(mContextSpy,
+                new Handler(mTestLooper.getLooper()), mVibrationConfigMock);
+
+        // Simulate System defaults.
+        setUserSetting(Settings.System.HAPTIC_FEEDBACK_ENABLED, 1);
+        setUserSetting(Settings.System.VIBRATE_INPUT_DEVICES, 0);
+        setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 1);
+        setUserSetting(Settings.System.APPLY_RAMPING_RINGER, 0);
+        setRingerMode(AudioManager.RINGER_MODE_NORMAL);
+
+        mVibrationSettings.onSystemReady();
+    }
+
+    private void removeServicesForTest() {
+        LocalServices.removeServiceForTest(PowerManagerInternal.class);
+        LocalServices.removeServiceForTest(PackageManagerInternal.class);
+        LocalServices.removeServiceForTest(VirtualDeviceManagerInternal.class);
+    }
+
+    private void addServicesForTest() {
+        LocalServices.addService(PowerManagerInternal.class, mPowerManagerInternalMock);
+        LocalServices.addService(PackageManagerInternal.class, mPackageManagerInternalMock);
+        LocalServices.addService(VirtualDeviceManagerInternal.class,
+                mVirtualDeviceManagerInternalMock);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        removeServicesForTest();
+    }
+
+    @Test
+    public void create_withOnlyRequiredSystemServices() {
+        // The only core services that we depend on are PowerManager and PackageManager
+        removeServicesForTest();
+        LocalServices.addService(PowerManagerInternal.class, mPowerManagerInternalMock);
+        LocalServices.addService(PackageManagerInternal.class, mPackageManagerInternalMock);
+
+        VibrationSettings minimalVibrationSettings = new VibrationSettings(mContextSpy,
+                new Handler(mTestLooper.getLooper()), mVibrationConfigMock);
+        minimalVibrationSettings.onSystemReady();
+    }
+
+    @Test
+    public void addListener_switchUserTriggerListener() {
+        mVibrationSettings.addListener(mListenerMock);
+
+        // Testing the broadcast flow manually.
+        mVibrationSettings.mSettingChangeReceiver.onReceive(mContextSpy,
+                new Intent(Intent.ACTION_USER_SWITCHED));
+
+        verify(mListenerMock).onChange();
+    }
+
+    @Test
+    public void addListener_ringerModeChangeTriggerListener() {
+        mVibrationSettings.addListener(mListenerMock);
+
+        // Testing the broadcast flow manually.
+        mVibrationSettings.mSettingChangeReceiver.onReceive(mContextSpy,
+                new Intent(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION));
+        mVibrationSettings.mSettingChangeReceiver.onReceive(mContextSpy,
+                new Intent(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION));
+
+        verify(mListenerMock, times(2)).onChange();
+    }
+
+    @Test
+    public void addListener_settingsChangeTriggerListener() {
+        mVibrationSettings.addListener(mListenerMock);
+
+        // Testing the broadcast flow manually.
+        mVibrationSettings.mSettingObserver.onChange(false);
+        mVibrationSettings.mSettingObserver.onChange(false);
+
+        verify(mListenerMock, times(2)).onChange();
+    }
+
+    @Test
+    public void addListener_lowPowerModeChangeTriggerListener() {
+        mVibrationSettings.addListener(mListenerMock);
+
+        // Testing the broadcast flow manually.
+        mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE);
+        mRegisteredPowerModeListener.onLowPowerModeChanged(NORMAL_POWER_STATE);
+        mRegisteredPowerModeListener.onLowPowerModeChanged(NORMAL_POWER_STATE); // No change.
+
+        verify(mListenerMock, times(2)).onChange();
+    }
+
+    @Test
+    public void removeListener_noMoreCallbacksToListener() {
+        mVibrationSettings.addListener(mListenerMock);
+
+        mVibrationSettings.mSettingObserver.onChange(false);
+        verify(mListenerMock).onChange();
+
+        mVibrationSettings.removeListener(mListenerMock);
+
+        // Trigger multiple observers manually.
+        mVibrationSettings.mSettingObserver.onChange(false);
+        mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE);
+        mVibrationSettings.mSettingChangeReceiver.onReceive(mContextSpy,
+                new Intent(Intent.ACTION_USER_SWITCHED));
+        mVibrationSettings.mSettingChangeReceiver.onReceive(mContextSpy,
+                new Intent(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION));
+
+        verifyNoMoreInteractions(mListenerMock);
+    }
+
+    @Test
+    public void shouldIgnoreVibration_fromBackground_doesNotIgnoreUsagesFromAllowlist() {
+        Set<Integer> expectedAllowedVibrations = new HashSet<>(Arrays.asList(
+                USAGE_RINGTONE,
+                USAGE_ALARM,
+                USAGE_NOTIFICATION,
+                USAGE_COMMUNICATION_REQUEST,
+                USAGE_HARDWARE_FEEDBACK,
+                USAGE_PHYSICAL_EMULATION
+        ));
+
+        mVibrationSettings.mUidObserver.onUidStateChanged(
+                UID, ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND, 0, 0);
+
+        for (int usage : ALL_USAGES) {
+            if (expectedAllowedVibrations.contains(usage)) {
+                assertVibrationNotIgnoredForUsage(usage);
+            } else {
+                assertVibrationIgnoredForUsage(usage, Vibration.Status.IGNORED_BACKGROUND);
+            }
+        }
+    }
+
+    @Test
+    public void shouldIgnoreVibration_fromForeground_allowsAnyUsage() {
+        mVibrationSettings.mUidObserver.onUidStateChanged(
+                UID, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND, 0, 0);
+
+        for (int usage : ALL_USAGES) {
+            assertVibrationNotIgnoredForUsage(usage);
+        }
+    }
+
+    @Test
+    public void wirelessChargingVibrationsEnabled_doesNotRegisterBatteryReceiver_allowsAnyUsage() {
+        setBatteryReceiverRegistrationResult(getBatteryChangedIntent(BATTERY_PLUGGED_WIRELESS));
+        setIgnoreVibrationsOnWirelessCharger(false);
+        createSystemReadyVibrationSettings();
+
+        assertNull(mRegisteredBatteryBroadcastReceiver);
+        for (int usage : ALL_USAGES) {
+            assertVibrationNotIgnoredForUsage(usage);
+        }
+    }
+
+    @Test
+    public void shouldIgnoreVibration_noBatteryIntentWhenSystemReady_allowsAnyUsage() {
+        setBatteryReceiverRegistrationResult(null);
+        setIgnoreVibrationsOnWirelessCharger(true);
+        createSystemReadyVibrationSettings();
+
+        for (int usage : ALL_USAGES) {
+            assertVibrationNotIgnoredForUsage(usage);
+        }
+    }
+
+    @Test
+    public void shouldIgnoreVibration_onNonWirelessChargerWhenSystemReady_allowsAnyUsage() {
+        Intent nonWirelessChargingIntent = getBatteryChangedIntent(BATTERY_PLUGGED_USB);
+        setBatteryReceiverRegistrationResult(nonWirelessChargingIntent);
+        setIgnoreVibrationsOnWirelessCharger(true);
+        createSystemReadyVibrationSettings();
+
+        for (int usage : ALL_USAGES) {
+            assertVibrationNotIgnoredForUsage(usage);
+        }
+    }
+
+    @Test
+    public void shouldIgnoreVibration_onWirelessChargerWhenSystemReady_doesNotAllowFromAnyUsage() {
+        Intent wirelessChargingIntent = getBatteryChangedIntent(BATTERY_PLUGGED_WIRELESS);
+        setBatteryReceiverRegistrationResult(wirelessChargingIntent);
+        setIgnoreVibrationsOnWirelessCharger(true);
+        createSystemReadyVibrationSettings();
+
+        for (int usage : ALL_USAGES) {
+            assertVibrationIgnoredForUsage(usage, Vibration.Status.IGNORED_ON_WIRELESS_CHARGER);
+        }
+    }
+
+    @Test
+    public void shouldIgnoreVibration_receivesWirelessChargingIntent_doesNotAllowFromAnyUsage() {
+        Intent nonWirelessChargingIntent = getBatteryChangedIntent(BATTERY_PLUGGED_USB);
+        setBatteryReceiverRegistrationResult(nonWirelessChargingIntent);
+        setIgnoreVibrationsOnWirelessCharger(true);
+        createSystemReadyVibrationSettings();
+
+        Intent wirelessChargingIntent = getBatteryChangedIntent(BATTERY_PLUGGED_WIRELESS);
+        mRegisteredBatteryBroadcastReceiver.onReceive(mContextSpy, wirelessChargingIntent);
+
+        for (int usage : ALL_USAGES) {
+            assertVibrationIgnoredForUsage(usage, Vibration.Status.IGNORED_ON_WIRELESS_CHARGER);
+        }
+    }
+
+    @Test
+    public void shouldIgnoreVibration_receivesNonWirelessChargingIntent_allowsAnyUsage() {
+        Intent wirelessChargingIntent = getBatteryChangedIntent(BATTERY_PLUGGED_WIRELESS);
+        setBatteryReceiverRegistrationResult(wirelessChargingIntent);
+        setIgnoreVibrationsOnWirelessCharger(true);
+        createSystemReadyVibrationSettings();
+        // Check that initially, all usages are ignored due to the wireless charging.
+        for (int usage : ALL_USAGES) {
+            assertVibrationIgnoredForUsage(usage, Vibration.Status.IGNORED_ON_WIRELESS_CHARGER);
+        }
+
+        Intent nonWirelessChargingIntent = getBatteryChangedIntent(BATTERY_PLUGGED_USB);
+        mRegisteredBatteryBroadcastReceiver.onReceive(mContextSpy, nonWirelessChargingIntent);
+
+        for (int usage : ALL_USAGES) {
+            assertVibrationNotIgnoredForUsage(usage);
+        }
+    }
+
+    @Test
+    public void shouldIgnoreVibration_inBatterySaverMode_doesNotIgnoreUsagesFromAllowlist() {
+        Set<Integer> expectedAllowedVibrations = new HashSet<>(Arrays.asList(
+                USAGE_RINGTONE,
+                USAGE_ALARM,
+                USAGE_COMMUNICATION_REQUEST,
+                USAGE_PHYSICAL_EMULATION,
+                USAGE_HARDWARE_FEEDBACK
+        ));
+
+        mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE);
+
+        for (int usage : ALL_USAGES) {
+            if (expectedAllowedVibrations.contains(usage)) {
+                assertVibrationNotIgnoredForUsage(usage);
+            } else {
+                assertVibrationIgnoredForUsage(usage, Vibration.Status.IGNORED_FOR_POWER);
+            }
+        }
+    }
+
+    @Test
+    public void shouldIgnoreVibration_notInBatterySaverMode_allowsAnyUsage() {
+        mRegisteredPowerModeListener.onLowPowerModeChanged(NORMAL_POWER_STATE);
+
+        for (int usage : ALL_USAGES) {
+            assertVibrationNotIgnoredForUsage(usage);
+        }
+    }
+
+    @Test
+    public void shouldIgnoreVibration_withRingerModeSilent_ignoresRingtoneAndNotification() {
+        // Vibrating settings on are overruled by ringer mode.
+        setUserSetting(Settings.System.HAPTIC_FEEDBACK_ENABLED, 1);
+        setRingerMode(AudioManager.RINGER_MODE_SILENT);
+
+        for (int usage : ALL_USAGES) {
+            if (usage == USAGE_RINGTONE || usage == USAGE_NOTIFICATION) {
+                assertVibrationIgnoredForUsage(usage, Vibration.Status.IGNORED_FOR_RINGER_MODE);
+            } else {
+                assertVibrationNotIgnoredForUsage(usage);
+            }
+        }
+    }
+
+    @Test
+    public void shouldIgnoreVibration_withRingerModeSilentAndBypassFlag_allowsAllVibrations() {
+        setRingerMode(AudioManager.RINGER_MODE_SILENT);
+
+        for (int usage : ALL_USAGES) {
+            assertVibrationNotIgnoredForUsageAndFlags(usage,
+                    VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY);
+        }
+    }
+
+    @Test
+    public void shouldIgnoreVibration_withRingerModeVibrate_allowsAllVibrations() {
+        setRingerMode(AudioManager.RINGER_MODE_VIBRATE);
+
+        for (int usage : ALL_USAGES) {
+            assertVibrationNotIgnoredForUsage(usage);
+        }
+    }
+
+    @Test
+    public void shouldIgnoreVibration_withRingerModeNormal_allowsAllVibrations() {
+        setRingerMode(AudioManager.RINGER_MODE_NORMAL);
+
+        for (int usage : ALL_USAGES) {
+            assertVibrationNotIgnoredForUsage(usage);
+        }
+    }
+
+
+    @Test
+    public void shouldIgnoreVibration_vibrateOnDisabled_ignoresUsagesNotAccessibility() {
+        setUserSetting(Settings.System.VIBRATE_ON, 0);
+
+        for (int usage : ALL_USAGES) {
+            if (usage == USAGE_ACCESSIBILITY) {
+                assertVibrationNotIgnoredForUsage(usage);
+            } else {
+                assertVibrationIgnoredForUsage(usage, Vibration.Status.IGNORED_FOR_SETTINGS);
+            }
+            assertVibrationNotIgnoredForUsageAndFlags(usage,
+                    VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF);
+        }
+    }
+
+    @Test
+    public void shouldIgnoreVibration_vibrateOnEnabledOrUnset_allowsAnyUsage() {
+        deleteUserSetting(Settings.System.VIBRATE_ON);
+        for (int usage : ALL_USAGES) {
+            assertVibrationNotIgnoredForUsage(usage);
+        }
+
+        setUserSetting(Settings.System.VIBRATE_ON, 1);
+        for (int usage : ALL_USAGES) {
+            assertVibrationNotIgnoredForUsage(usage);
+        }
+    }
+
+    @Test
+    public void shouldIgnoreVibration_withRingSettingsOff_allowsAllVibrations() {
+        // VIBRATE_WHEN_RINGING is deprecated and should have no effect on the ring vibration
+        // setting. The ramping ringer is also independent now, instead of a 3-state setting.
+        setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0);
+        setUserSetting(Settings.System.APPLY_RAMPING_RINGER, 0);
+
+        for (int usage : ALL_USAGES) {
+            assertVibrationNotIgnoredForUsage(usage);
+            assertVibrationNotIgnoredForUsageAndFlags(usage,
+                    VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF);
+        }
+    }
+
+    @Test
+    public void shouldIgnoreVibration_withHapticFeedbackDisabled_ignoresTouchVibration() {
+        // HAPTIC_FEEDBACK_ENABLED is deprecated but it was the only setting used to disable touch
+        // feedback vibrations. Continue to apply this on top of the intensity setting.
+        setUserSetting(Settings.System.HAPTIC_FEEDBACK_ENABLED, 0);
+
+        for (int usage : ALL_USAGES) {
+            if (usage == USAGE_TOUCH) {
+                assertVibrationIgnoredForUsage(usage, Vibration.Status.IGNORED_FOR_SETTINGS);
+            } else {
+                assertVibrationNotIgnoredForUsage(usage);
+            }
+            assertVibrationNotIgnoredForUsageAndFlags(usage,
+                    VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF);
+        }
+    }
+
+    @Test
+    public void shouldIgnoreVibration_withHapticFeedbackSettingsOff_ignoresTouchVibration() {
+        setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF);
+
+        for (int usage : ALL_USAGES) {
+            if (usage == USAGE_TOUCH) {
+                assertVibrationIgnoredForUsage(usage, Vibration.Status.IGNORED_FOR_SETTINGS);
+            } else {
+                assertVibrationNotIgnoredForUsage(usage);
+            }
+            assertVibrationNotIgnoredForUsageAndFlags(usage,
+                    VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF);
+        }
+    }
+
+    @Test
+    public void shouldIgnoreVibration_withHardwareFeedbackSettingsOff_ignoresHardwareVibrations() {
+        setUserSetting(Settings.System.HARDWARE_HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF);
+
+        for (int usage : ALL_USAGES) {
+            if (usage == USAGE_HARDWARE_FEEDBACK || usage == USAGE_PHYSICAL_EMULATION) {
+                assertVibrationIgnoredForUsage(usage, Vibration.Status.IGNORED_FOR_SETTINGS);
+            } else {
+                assertVibrationNotIgnoredForUsage(usage);
+            }
+            assertVibrationNotIgnoredForUsageAndFlags(usage,
+                    VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF);
+        }
+    }
+
+    @Test
+    public void shouldIgnoreVibration_withNotificationSettingsOff_ignoresNotificationVibrations() {
+        setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_OFF);
+
+        for (int usage : ALL_USAGES) {
+            if (usage == USAGE_NOTIFICATION) {
+                assertVibrationIgnoredForUsage(usage, Vibration.Status.IGNORED_FOR_SETTINGS);
+            } else {
+                assertVibrationNotIgnoredForUsage(usage);
+            }
+            assertVibrationNotIgnoredForUsageAndFlags(usage,
+                    VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF);
+        }
+    }
+
+    @Test
+    public void shouldIgnoreVibration_withRingSettingsOff_ignoresRingtoneVibrations() {
+        // Vibrating settings on are overruled by ring intensity setting.
+        setRingerMode(AudioManager.RINGER_MODE_VIBRATE);
+        setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, VIBRATION_INTENSITY_OFF);
+
+        for (int usage : ALL_USAGES) {
+            if (usage == USAGE_RINGTONE) {
+                assertVibrationIgnoredForUsage(usage, Vibration.Status.IGNORED_FOR_SETTINGS);
+            } else {
+                assertVibrationNotIgnoredForUsage(usage);
+            }
+            assertVibrationNotIgnoredForUsageAndFlags(usage,
+                    VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF);
+        }
+    }
+
+    @Test
+    public void shouldIgnoreVibration_updateTriggeredAfterInternalRingerModeChanged() {
+        // Vibrating settings on are overruled by ringer mode.
+        setUserSetting(Settings.System.HAPTIC_FEEDBACK_ENABLED, 1);
+        setUserSetting(Settings.System.APPLY_RAMPING_RINGER, 1);
+        setRingerMode(AudioManager.RINGER_MODE_NORMAL);
+
+        assertVibrationNotIgnoredForUsage(USAGE_RINGTONE);
+
+        // Testing the broadcast flow manually.
+        when(mAudioManagerMock.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_SILENT);
+        mVibrationSettings.mSettingChangeReceiver.onReceive(mContextSpy,
+                new Intent(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION));
+
+        assertVibrationIgnoredForUsage(USAGE_RINGTONE, Vibration.Status.IGNORED_FOR_RINGER_MODE);
+    }
+
+    @Test
+    public void shouldIgnoreVibrationFromVirtualDisplays_displayNonVirtual_neverIgnored() {
+        // Vibrations from the primary display is never ignored regardless of the creation and
+        // removal of virtual displays and of the changes of apps running on virtual displays.
+        mRegisteredVirtualDisplayListener.onVirtualDisplayCreated(VIRTUAL_DISPLAY_ID);
+        mRegisteredAppsOnVirtualDeviceListener.onAppsOnAnyVirtualDeviceChanged(
+                new ArraySet<>(Arrays.asList(UID)));
+        for (int usage : ALL_USAGES) {
+            assertVibrationNotIgnoredForUsageAndDisplay(usage, Display.DEFAULT_DISPLAY);
+        }
+
+        mRegisteredVirtualDisplayListener.onVirtualDisplayRemoved(VIRTUAL_DISPLAY_ID);
+        for (int usage : ALL_USAGES) {
+            assertVibrationNotIgnoredForUsageAndDisplay(usage, Display.DEFAULT_DISPLAY);
+        }
+
+        mRegisteredAppsOnVirtualDeviceListener.onAppsOnAnyVirtualDeviceChanged(new ArraySet<>());
+        for (int usage : ALL_USAGES) {
+            assertVibrationNotIgnoredForUsageAndDisplay(usage, Display.DEFAULT_DISPLAY);
+        }
+    }
+
+    @Test
+    public void shouldIgnoreVibrationFromVirtualDisplays_displayVirtual() {
+        // Ignore the vibration when the coming display id represents a virtual display.
+        mRegisteredVirtualDisplayListener.onVirtualDisplayCreated(VIRTUAL_DISPLAY_ID);
+
+        for (int usage : ALL_USAGES) {
+            assertVibrationIgnoredForUsageAndDisplay(usage, VIRTUAL_DISPLAY_ID,
+                    Vibration.Status.IGNORED_FROM_VIRTUAL_DEVICE);
+        }
+
+        // Stop ignoring when the virtual display is removed.
+        mRegisteredVirtualDisplayListener.onVirtualDisplayRemoved(VIRTUAL_DISPLAY_ID);
+        for (int usage : ALL_USAGES) {
+            assertVibrationNotIgnoredForUsageAndDisplay(usage, VIRTUAL_DISPLAY_ID);
+        }
+    }
+
+
+    @Test
+    public void shouldIgnoreVibrationFromVirtualDisplays_appsOnVirtualDisplay() {
+        // Ignore when the passed-in display id is invalid and the calling uid is on a virtual
+        // display.
+        mRegisteredAppsOnVirtualDeviceListener.onAppsOnAnyVirtualDeviceChanged(
+                new ArraySet<>(Arrays.asList(UID)));
+        for (int usage : ALL_USAGES) {
+            assertVibrationIgnoredForUsageAndDisplay(usage, Display.INVALID_DISPLAY,
+                    Vibration.Status.IGNORED_FROM_VIRTUAL_DEVICE);
+        }
+
+        // Stop ignoring when the app is no longer on virtual display.
+        mRegisteredAppsOnVirtualDeviceListener.onAppsOnAnyVirtualDeviceChanged(new ArraySet<>());
+        for (int usage : ALL_USAGES) {
+            assertVibrationNotIgnoredForUsageAndDisplay(usage, Display.INVALID_DISPLAY);
+        }
+
+    }
+
+    @Test
+    public void shouldVibrateInputDevices_returnsSettingsValue() {
+        setUserSetting(Settings.System.VIBRATE_INPUT_DEVICES, 1);
+        assertTrue(mVibrationSettings.shouldVibrateInputDevices());
+
+        setUserSetting(Settings.System.VIBRATE_INPUT_DEVICES, 0);
+        assertFalse(mVibrationSettings.shouldVibrateInputDevices());
+    }
+
+    @Test
+    public void shouldCancelVibrationOnScreenOff_withEventBeforeVibration_returnsAlwaysFalse() {
+        long vibrateStartTime = 100;
+        mockGoToSleep(vibrateStartTime - 10, PowerManager.GO_TO_SLEEP_REASON_APPLICATION);
+
+        for (int usage : ALL_USAGES) {
+            // Non-system vibration
+            assertFalse(mVibrationSettings.shouldCancelVibrationOnScreenOff(createCallerInfo(
+                    UID, "some.app", usage), vibrateStartTime));
+            // Vibration with UID zero
+            assertFalse(mVibrationSettings.shouldCancelVibrationOnScreenOff(
+                    createCallerInfo(/* uid= */ 0, "", usage), vibrateStartTime));
+            // System vibration
+            assertFalse(mVibrationSettings.shouldCancelVibrationOnScreenOff(
+                    createCallerInfo(Process.SYSTEM_UID, "", usage), vibrateStartTime));
+            // SysUI vibration
+            assertFalse(mVibrationSettings.shouldCancelVibrationOnScreenOff(
+                    createCallerInfo(UID, SYSUI_PACKAGE_NAME, usage), vibrateStartTime));
+        }
+    }
+
+    @Test
+    public void shouldCancelVibrationOnScreenOff_withSleepReasonInAllowlist_returnsAlwaysFalse() {
+        long vibrateStartTime = 100;
+        int[] allowedSleepReasons = new int[]{
+                PowerManager.GO_TO_SLEEP_REASON_TIMEOUT,
+                PowerManager.GO_TO_SLEEP_REASON_INATTENTIVE,
+        };
+
+        for (int sleepReason : allowedSleepReasons) {
+            mockGoToSleep(vibrateStartTime + 10, sleepReason);
+
+            for (int usage : ALL_USAGES) {
+                // Non-system vibration
+                assertFalse(mVibrationSettings.shouldCancelVibrationOnScreenOff(
+                        createCallerInfo(UID, "some.app", usage), vibrateStartTime));
+                // Vibration with UID zero
+                assertFalse(mVibrationSettings.shouldCancelVibrationOnScreenOff(
+                        createCallerInfo(/* uid= */ 0, "", usage), vibrateStartTime));
+                // System vibration
+                assertFalse(mVibrationSettings.shouldCancelVibrationOnScreenOff(
+                        createCallerInfo(Process.SYSTEM_UID, "", usage), vibrateStartTime));
+                // SysUI vibration
+                assertFalse(mVibrationSettings.shouldCancelVibrationOnScreenOff(
+                        createCallerInfo(UID, SYSUI_PACKAGE_NAME, usage), vibrateStartTime));
+            }
+        }
+    }
+
+    @Test
+    public void shouldCancelVibrationOnScreenOff_withNonSystem_returnsTrueIfReasonNotInAllowlist() {
+        long vibrateStartTime = 100;
+        mockGoToSleep(vibrateStartTime + 10, PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON);
+
+        for (int usage : ALL_USAGES) {
+            assertTrue(mVibrationSettings.shouldCancelVibrationOnScreenOff(
+                    createCallerInfo(UID, "some.app", usage), vibrateStartTime));
+        }
+    }
+
+    @Test
+    public void shouldCancelVibrationOnScreenOff_withUidZero_returnsFalseForUsagesInAllowlist() {
+        long vibrateStartTime = 100;
+        mockGoToSleep(vibrateStartTime + 10, PowerManager.GO_TO_SLEEP_REASON_DEVICE_ADMIN);
+
+        Set<Integer> expectedAllowedVibrations = new HashSet<>(Arrays.asList(
+                USAGE_TOUCH,
+                USAGE_ACCESSIBILITY,
+                USAGE_PHYSICAL_EMULATION,
+                USAGE_HARDWARE_FEEDBACK
+        ));
+
+        for (int usage : ALL_USAGES) {
+            if (expectedAllowedVibrations.contains(usage)) {
+                assertFalse(mVibrationSettings.shouldCancelVibrationOnScreenOff(
+                        createCallerInfo(/* uid= */ 0, "", usage), vibrateStartTime));
+            } else {
+                assertTrue(mVibrationSettings.shouldCancelVibrationOnScreenOff(
+                        createCallerInfo(/* uid= */ 0, "", usage), vibrateStartTime));
+            }
+        }
+    }
+
+    @Test
+    public void shouldCancelVibrationOnScreenOff_withSystemUid__returnsFalseForUsagesInAllowlist() {
+        long vibrateStartTime = 100;
+        mockGoToSleep(vibrateStartTime + 10, PowerManager.GO_TO_SLEEP_REASON_DEVICE_FOLD);
+
+        Set<Integer> expectedAllowedVibrations = new HashSet<>(Arrays.asList(
+                USAGE_TOUCH,
+                USAGE_ACCESSIBILITY,
+                USAGE_PHYSICAL_EMULATION,
+                USAGE_HARDWARE_FEEDBACK
+        ));
+
+        for (int usage : ALL_USAGES) {
+            if (expectedAllowedVibrations.contains(usage)) {
+                assertFalse(mVibrationSettings.shouldCancelVibrationOnScreenOff(
+                        createCallerInfo(Process.SYSTEM_UID, "", usage), vibrateStartTime));
+            } else {
+                assertTrue(mVibrationSettings.shouldCancelVibrationOnScreenOff(
+                        createCallerInfo(Process.SYSTEM_UID, "", usage), vibrateStartTime));
+            }
+        }
+    }
+
+    @Test
+    public void shouldCancelVibrationOnScreenOff_withSysUiPkg_returnsFalseForUsagesInAllowlist() {
+        long vibrateStartTime = 100;
+        mockGoToSleep(vibrateStartTime + 10, PowerManager.GO_TO_SLEEP_REASON_HDMI);
+
+        Set<Integer> expectedAllowedVibrations = new HashSet<>(Arrays.asList(
+                USAGE_TOUCH,
+                USAGE_ACCESSIBILITY,
+                USAGE_PHYSICAL_EMULATION,
+                USAGE_HARDWARE_FEEDBACK
+        ));
+
+        for (int usage : ALL_USAGES) {
+            if (expectedAllowedVibrations.contains(usage)) {
+                assertFalse(mVibrationSettings.shouldCancelVibrationOnScreenOff(
+                        createCallerInfo(UID, SYSUI_PACKAGE_NAME, usage), vibrateStartTime));
+            } else {
+                assertTrue(mVibrationSettings.shouldCancelVibrationOnScreenOff(
+                        createCallerInfo(UID, SYSUI_PACKAGE_NAME, usage), vibrateStartTime));
+            }
+        }
+    }
+
+    @Test
+    public void getDefaultIntensity_returnsIntensityFromVibratorConfig() {
+        setDefaultIntensity(VIBRATION_INTENSITY_HIGH);
+        setUserSetting(Settings.System.ALARM_VIBRATION_INTENSITY, VIBRATION_INTENSITY_OFF);
+        setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF);
+        setUserSetting(Settings.System.HARDWARE_HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF);
+        setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_OFF);
+        setUserSetting(Settings.System.MEDIA_VIBRATION_INTENSITY, VIBRATION_INTENSITY_OFF);
+        setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, VIBRATION_INTENSITY_OFF);
+
+        for (int usage : ALL_USAGES) {
+            assertEquals(VIBRATION_INTENSITY_HIGH, mVibrationSettings.getDefaultIntensity(usage));
+        }
+    }
+
+    @Test
+    public void getCurrentIntensity_returnsIntensityFromSettings() {
+        setDefaultIntensity(VIBRATION_INTENSITY_OFF);
+        setUserSetting(Settings.System.ALARM_VIBRATION_INTENSITY, VIBRATION_INTENSITY_LOW);
+        setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_LOW);
+        setUserSetting(Settings.System.HARDWARE_HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_LOW);
+        setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_LOW);
+        setUserSetting(Settings.System.MEDIA_VIBRATION_INTENSITY, VIBRATION_INTENSITY_LOW);
+        setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, VIBRATION_INTENSITY_LOW);
+
+        for (int usage : ALL_USAGES) {
+            assertEquals(errorMessageForUsage(usage),
+                    VIBRATION_INTENSITY_LOW,
+                    mVibrationSettings.getCurrentIntensity(usage));
+        }
+    }
+
+    @Test
+    public void getCurrentIntensity_updateTriggeredAfterUserSwitched() {
+        setDefaultIntensity(USAGE_RINGTONE, VIBRATION_INTENSITY_OFF);
+        setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, VIBRATION_INTENSITY_HIGH);
+        assertEquals(VIBRATION_INTENSITY_HIGH,
+                mVibrationSettings.getCurrentIntensity(USAGE_RINGTONE));
+
+        // Switching user is not working with FakeSettingsProvider.
+        // Testing the broadcast flow manually.
+        Settings.System.putIntForUser(mContextSpy.getContentResolver(),
+                Settings.System.RING_VIBRATION_INTENSITY, VIBRATION_INTENSITY_LOW,
+                UserHandle.USER_CURRENT);
+        mVibrationSettings.mSettingChangeReceiver.onReceive(mContextSpy,
+                new Intent(Intent.ACTION_USER_SWITCHED));
+        assertEquals(VIBRATION_INTENSITY_LOW,
+                mVibrationSettings.getCurrentIntensity(USAGE_RINGTONE));
+    }
+
+    @Test
+    public void getCurrentIntensity_noHardwareFeedbackValueUsesHapticFeedbackValue() {
+        setDefaultIntensity(USAGE_HARDWARE_FEEDBACK, VIBRATION_INTENSITY_MEDIUM);
+        setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF);
+        assertEquals(VIBRATION_INTENSITY_OFF, mVibrationSettings.getCurrentIntensity(USAGE_TOUCH));
+        // If haptic feedback is off, fallback to default value.
+        assertEquals(VIBRATION_INTENSITY_MEDIUM,
+                mVibrationSettings.getCurrentIntensity(USAGE_HARDWARE_FEEDBACK));
+        assertEquals(VIBRATION_INTENSITY_MEDIUM,
+                mVibrationSettings.getCurrentIntensity(USAGE_PHYSICAL_EMULATION));
+
+        setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_HIGH);
+        assertEquals(VIBRATION_INTENSITY_HIGH,
+                mVibrationSettings.getCurrentIntensity(USAGE_TOUCH));
+        // If haptic feedback is on, fallback to that value.
+        assertEquals(VIBRATION_INTENSITY_HIGH,
+                mVibrationSettings.getCurrentIntensity(USAGE_HARDWARE_FEEDBACK));
+        assertEquals(VIBRATION_INTENSITY_HIGH,
+                mVibrationSettings.getCurrentIntensity(USAGE_PHYSICAL_EMULATION));
+    }
+
+    @Test
+    public void getFallbackEffect_returnsEffectsFromSettings() {
+        assertNotNull(mVibrationSettings.getFallbackEffect(VibrationEffect.EFFECT_TICK));
+        assertNotNull(mVibrationSettings.getFallbackEffect(VibrationEffect.EFFECT_TEXTURE_TICK));
+        assertNotNull(mVibrationSettings.getFallbackEffect(VibrationEffect.EFFECT_CLICK));
+        assertNotNull(mVibrationSettings.getFallbackEffect(VibrationEffect.EFFECT_HEAVY_CLICK));
+        assertNotNull(mVibrationSettings.getFallbackEffect(VibrationEffect.EFFECT_DOUBLE_CLICK));
+    }
+
+    private void assertVibrationIgnoredForUsage(@VibrationAttributes.Usage int usage,
+            Vibration.Status expectedStatus) {
+        assertVibrationIgnoredForUsageAndDisplay(usage, Display.DEFAULT_DISPLAY, expectedStatus);
+    }
+
+    private void assertVibrationIgnoredForUsageAndDisplay(@VibrationAttributes.Usage int usage,
+            int displayId, Vibration.Status expectedStatus) {
+        Vibration.CallerInfo callerInfo = new Vibration.CallerInfo(
+                VibrationAttributes.createForUsage(usage), UID, displayId, null, null);
+        assertEquals(errorMessageForUsage(usage), expectedStatus,
+                mVibrationSettings.shouldIgnoreVibration(callerInfo));
+    }
+
+    private void assertVibrationNotIgnoredForUsage(@VibrationAttributes.Usage int usage) {
+        assertVibrationNotIgnoredForUsageAndFlags(usage, /* flags= */ 0);
+    }
+
+    private void assertVibrationNotIgnoredForUsageAndFlags(@VibrationAttributes.Usage int usage,
+            @VibrationAttributes.Flag int flags) {
+        assertVibrationNotIgnoredForUsageAndFlagsAndDisplay(usage, Display.DEFAULT_DISPLAY, flags);
+    }
+
+    private void assertVibrationNotIgnoredForUsageAndDisplay(@VibrationAttributes.Usage int usage,
+            int displayId) {
+        assertVibrationNotIgnoredForUsageAndFlagsAndDisplay(usage, displayId, /* flags= */ 0);
+    }
+
+    private void assertVibrationNotIgnoredForUsageAndFlagsAndDisplay(
+            @VibrationAttributes.Usage int usage, int displayId,
+            @VibrationAttributes.Flag int flags) {
+        Vibration.CallerInfo callerInfo = new Vibration.CallerInfo(
+                new VibrationAttributes.Builder().setUsage(usage).setFlags(flags).build(), UID,
+                displayId, null, null);
+        assertNull(errorMessageForUsage(usage),
+                mVibrationSettings.shouldIgnoreVibration(callerInfo));
+    }
+
+    private String errorMessageForUsage(int usage) {
+        return "Error for usage " + VibrationAttributes.usageToString(usage);
+    }
+
+    private void setDefaultIntensity(@Vibrator.VibrationIntensity int intensity) {
+        when(mVibrationConfigMock.getDefaultVibrationIntensity(anyInt())).thenReturn(intensity);
+    }
+
+    private void setDefaultIntensity(@VibrationAttributes.Usage int usage,
+            @Vibrator.VibrationIntensity int intensity) {
+        when(mVibrationConfigMock.getDefaultVibrationIntensity(eq(usage))).thenReturn(intensity);
+    }
+
+    private void setIgnoreVibrationsOnWirelessCharger(boolean ignore) {
+        when(mVibrationConfigMock.ignoreVibrationsOnWirelessCharger()).thenReturn(ignore);
+    }
+
+    private void deleteUserSetting(String settingName) {
+        Settings.System.putStringForUser(
+                mContextSpy.getContentResolver(), settingName, null, UserHandle.USER_CURRENT);
+        // FakeSettingsProvider doesn't support testing triggering ContentObserver yet.
+        mVibrationSettings.mSettingObserver.onChange(false);
+    }
+
+    private void setUserSetting(String settingName, int value) {
+        Settings.System.putIntForUser(
+                mContextSpy.getContentResolver(), settingName, value, UserHandle.USER_CURRENT);
+        // FakeSettingsProvider doesn't support testing triggering ContentObserver yet.
+        mVibrationSettings.mSettingObserver.onChange(false);
+    }
+
+    private void setRingerMode(int ringerMode) {
+        when(mAudioManagerMock.getRingerModeInternal()).thenReturn(ringerMode);
+        // Mock AudioManager broadcast of internal ringer mode change.
+        mVibrationSettings.mSettingChangeReceiver.onReceive(mContextSpy,
+                new Intent(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION));
+    }
+
+    private void mockGoToSleep(long sleepTime, int reason) {
+        when(mPowerManagerInternalMock.getLastGoToSleep()).thenReturn(
+                new PowerManager.SleepData(sleepTime, reason));
+    }
+
+    private Vibration.CallerInfo createCallerInfo(int uid, String opPkg,
+            @VibrationAttributes.Usage int usage) {
+        VibrationAttributes attrs = VibrationAttributes.createForUsage(usage);
+        return new Vibration.CallerInfo(attrs, uid, VIRTUAL_DISPLAY_ID, opPkg, null);
+    }
+
+    private void setBatteryReceiverRegistrationResult(Intent result) {
+        doAnswer(invocation -> {
+            mRegisteredBatteryBroadcastReceiver = invocation.getArgument(0);
+            return result;
+        }).when(mContextSpy).registerReceiver(any(BroadcastReceiver.class),
+                argThat(filter -> filter.matchAction(Intent.ACTION_BATTERY_CHANGED)), anyInt());
+    }
+
+    private Intent getBatteryChangedIntent(int extraPluggedValue) {
+        Intent batteryIntent = new Intent(Intent.ACTION_BATTERY_CHANGED);
+        batteryIntent.putExtra(EXTRA_PLUGGED, extraPluggedValue);
+        return batteryIntent;
+    }
+}
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibrationTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationTest.java
new file mode 100644
index 0000000..84f8412
--- /dev/null
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationTest.java
@@ -0,0 +1,37 @@
+/*
+ * 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.vibrator;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static java.util.stream.Collectors.toList;
+
+import org.junit.Test;
+
+import java.util.Arrays;
+
+public class VibrationTest {
+
+    @Test
+    public void status_hasUniqueProtoEnumValues() {
+        assertThat(
+                Arrays.stream(Vibration.Status.values())
+                        .map(Vibration.Status::getProtoEnumValue)
+                        .collect(toList()))
+                .containsNoDuplicates();
+    }
+}
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
new file mode 100644
index 0000000..aa3bee4
--- /dev/null
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
@@ -0,0 +1,1776 @@
+/*
+ * 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.
+ */
+
+package com.android.server.vibrator;
+
+import static android.os.VibrationEffect.VibrationParameter.targetAmplitude;
+import static android.os.VibrationEffect.VibrationParameter.targetFrequency;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+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.ArgumentMatchers.same;
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManagerInternal;
+import android.hardware.vibrator.Braking;
+import android.hardware.vibrator.IVibrator;
+import android.hardware.vibrator.IVibratorManager;
+import android.os.CombinedVibration;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.PowerManager;
+import android.os.Process;
+import android.os.SystemClock;
+import android.os.VibrationAttributes;
+import android.os.VibrationEffect;
+import android.os.Vibrator;
+import android.os.test.TestLooper;
+import android.os.vibrator.PrebakedSegment;
+import android.os.vibrator.PrimitiveSegment;
+import android.os.vibrator.RampSegment;
+import android.os.vibrator.StepSegment;
+import android.os.vibrator.VibrationConfig;
+import android.os.vibrator.VibrationEffectSegment;
+import android.platform.test.annotations.LargeTest;
+import android.util.SparseArray;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.server.LocalServices;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.time.Duration;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.BooleanSupplier;
+import java.util.stream.Collectors;
+
+public class VibrationThreadTest {
+
+    private static final int TEST_TIMEOUT_MILLIS = 900;
+    private static final int UID = Process.ROOT_UID;
+    private static final int DISPLAY_ID = 10;
+    private static final int VIBRATOR_ID = 1;
+    private static final String PACKAGE_NAME = "package";
+    private static final VibrationAttributes ATTRS = new VibrationAttributes.Builder().build();
+    private static final int TEST_RAMP_STEP_DURATION = 5;
+
+    @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
+
+    @Mock private PackageManagerInternal mPackageManagerInternalMock;
+    @Mock private VibrationThread.VibratorManagerHooks mManagerHooks;
+    @Mock private VibratorController.OnVibrationCompleteListener mControllerCallbacks;
+    @Mock private IBinder mVibrationToken;
+    @Mock private VibrationConfig mVibrationConfigMock;
+
+    private final Map<Integer, FakeVibratorControllerProvider> mVibratorProviders = new HashMap<>();
+    private VibrationSettings mVibrationSettings;
+    private TestLooper mTestLooper;
+    private TestLooperAutoDispatcher mCustomTestLooperDispatcher;
+    private VibrationThread mThread;
+
+    // Setup from the providers when VibrationThread is initialized.
+    private SparseArray<VibratorController> mControllers;
+
+    @Before
+    public void setUp() throws Exception {
+        mTestLooper = new TestLooper();
+
+        when(mVibrationConfigMock.getDefaultVibrationIntensity(anyInt()))
+                .thenReturn(Vibrator.VIBRATION_INTENSITY_MEDIUM);
+        when(mVibrationConfigMock.getRampStepDurationMs()).thenReturn(TEST_RAMP_STEP_DURATION);
+        when(mPackageManagerInternalMock.getSystemUiServiceComponent())
+                .thenReturn(new ComponentName("", ""));
+
+        LocalServices.removeServiceForTest(PackageManagerInternal.class);
+        LocalServices.addService(PackageManagerInternal.class, mPackageManagerInternalMock);
+
+        Context context = InstrumentationRegistry.getContext();
+        mVibrationSettings = new VibrationSettings(context, new Handler(mTestLooper.getLooper()),
+                mVibrationConfigMock);
+
+        mockVibrators(VIBRATOR_ID);
+
+        PowerManager.WakeLock wakeLock = context.getSystemService(
+                PowerManager.class).newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*vibrator*");
+        mThread = new VibrationThread(wakeLock, mManagerHooks);
+        mThread.start();
+    }
+
+    @After
+    public void tearDown() {
+        if (mCustomTestLooperDispatcher != null) {
+            mCustomTestLooperDispatcher.cancel();
+        }
+    }
+
+    @Test
+    public void vibrate_noVibrator_ignoresVibration() {
+        mVibratorProviders.clear();
+        CombinedVibration effect = CombinedVibration.createParallel(
+                VibrationEffect.get(VibrationEffect.EFFECT_CLICK));
+        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
+        long vibrationId = conductor.getVibration().id;
+        waitForCompletion();
+
+        verify(mControllerCallbacks, never()).onComplete(anyInt(), eq(vibrationId));
+        verifyCallbacksTriggered(vibrationId, Vibration.Status.IGNORED_UNSUPPORTED);
+    }
+
+    @Test
+    public void vibrate_missingVibrators_ignoresVibration() {
+        CombinedVibration effect = CombinedVibration.startSequential()
+                .addNext(2, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
+                .addNext(3, VibrationEffect.get(VibrationEffect.EFFECT_TICK))
+                .combine();
+        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
+        long vibrationId = conductor.getVibration().id;
+        waitForCompletion();
+
+        verify(mControllerCallbacks, never()).onComplete(anyInt(), eq(vibrationId));
+        verifyCallbacksTriggered(vibrationId, Vibration.Status.IGNORED_UNSUPPORTED);
+    }
+
+    @Test
+    public void vibrate_singleVibratorOneShot_runsVibrationAndSetsAmplitude() throws Exception {
+        mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+
+        VibrationEffect effect = VibrationEffect.createOneShot(10, 100);
+        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
+        long vibrationId = conductor.getVibration().id;
+        waitForCompletion();
+
+        verify(mManagerHooks).noteVibratorOn(eq(UID), eq(10L));
+        verify(mManagerHooks).noteVibratorOff(eq(UID));
+        verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
+        verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
+        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
+
+        assertEquals(Arrays.asList(expectedOneShot(10)),
+                mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId));
+        assertEquals(expectedAmplitudes(100), mVibratorProviders.get(VIBRATOR_ID).getAmplitudes());
+    }
+
+    @Test
+    public void vibrate_oneShotWithoutAmplitudeControl_runsVibrationWithDefaultAmplitude()
+            throws Exception {
+        VibrationEffect effect = VibrationEffect.createOneShot(10, 100);
+        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
+        long vibrationId = conductor.getVibration().id;
+        waitForCompletion();
+
+        verify(mManagerHooks).noteVibratorOn(eq(UID), eq(10L));
+        verify(mManagerHooks).noteVibratorOff(eq(UID));
+        verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
+        verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
+        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
+
+        assertEquals(Arrays.asList(expectedOneShot(10)),
+                mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId));
+        assertTrue(mVibratorProviders.get(VIBRATOR_ID).getAmplitudes().isEmpty());
+    }
+
+    @Test
+    public void vibrate_singleVibratorWaveform_runsVibrationAndChangesAmplitudes()
+            throws Exception {
+        mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+
+        VibrationEffect effect = VibrationEffect.createWaveform(
+                new long[]{5, 5, 5}, new int[]{1, 2, 3}, -1);
+        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
+        long vibrationId = conductor.getVibration().id;
+        waitForCompletion();
+
+        verify(mManagerHooks).noteVibratorOn(eq(UID), eq(15L));
+        verify(mManagerHooks).noteVibratorOff(eq(UID));
+        verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
+        verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
+        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
+
+        assertEquals(Arrays.asList(expectedOneShot(15)),
+                mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId));
+        assertEquals(expectedAmplitudes(1, 2, 3),
+                mVibratorProviders.get(VIBRATOR_ID).getAmplitudes());
+    }
+
+    @Test
+    public void vibrate_singleVibratorRepeatingWaveform_runsVibrationUntilThreadCancelled()
+            throws Exception {
+        FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
+        fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+
+        int[] amplitudes = new int[]{1, 2, 3};
+        VibrationEffect effect = VibrationEffect.createWaveform(new long[]{5, 5, 5}, amplitudes, 0);
+        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
+        long vibrationId = conductor.getVibration().id;
+
+        assertTrue(
+                waitUntil(() -> fakeVibrator.getAmplitudes().size() > 2 * amplitudes.length,
+                        TEST_TIMEOUT_MILLIS));
+        // Vibration still running after 2 cycles.
+        assertTrue(mThread.isRunningVibrationId(vibrationId));
+        assertTrue(mControllers.get(VIBRATOR_ID).isVibrating());
+
+        Vibration.EndInfo cancelVibrationInfo = new Vibration.EndInfo(
+                Vibration.Status.CANCELLED_SUPERSEDED, new Vibration.CallerInfo(
+                VibrationAttributes.createForUsage(VibrationAttributes.USAGE_ALARM), /* uid= */
+                1, /* displayId= */ -1, /* opPkg= */ null, /* reason= */ null));
+        conductor.notifyCancelled(
+                cancelVibrationInfo,
+                /* immediate= */ false);
+        waitForCompletion();
+        assertFalse(mThread.isRunningVibrationId(vibrationId));
+
+        verify(mManagerHooks).noteVibratorOn(eq(UID), anyLong());
+        verify(mManagerHooks).noteVibratorOff(eq(UID));
+        verifyCallbacksTriggered(vibrationId, cancelVibrationInfo);
+        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
+
+        List<Float> playedAmplitudes = fakeVibrator.getAmplitudes();
+        assertFalse(fakeVibrator.getEffectSegments(vibrationId).isEmpty());
+        assertFalse(playedAmplitudes.isEmpty());
+
+        for (int i = 0; i < playedAmplitudes.size(); i++) {
+            assertEquals(amplitudes[i % amplitudes.length] / 255f, playedAmplitudes.get(i), 1e-5);
+        }
+    }
+
+    @Test
+    public void vibrate_singleVibratorRepeatingShortAlwaysOnWaveform_turnsVibratorOnForLonger()
+            throws Exception {
+        FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
+        fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+
+        int[] amplitudes = new int[]{1, 2, 3};
+        VibrationEffect effect = VibrationEffect.createWaveform(
+                new long[]{1, 10, 100}, amplitudes, 0);
+        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
+        long vibrationId = conductor.getVibration().id;
+
+        assertTrue(waitUntil(() -> !fakeVibrator.getAmplitudes().isEmpty(), TEST_TIMEOUT_MILLIS));
+        conductor.notifyCancelled(
+                new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_USER),
+                /* immediate= */ false);
+        waitForCompletion();
+
+        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_USER);
+        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
+        assertEquals(Arrays.asList(expectedOneShot(5000)),
+                fakeVibrator.getEffectSegments(vibrationId));
+    }
+
+    @Test
+    public void vibrate_singleVibratorPatternWithZeroDurationSteps_skipsZeroDurationSteps() {
+        mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+
+        VibrationEffect effect = VibrationEffect.createWaveform(
+                /* timings= */ new long[]{0, 100, 50, 100, 0, 0, 0, 50}, /* repeat= */ -1);
+        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
+        long vibrationId = conductor.getVibration().id;
+        waitForCompletion();
+
+        verify(mManagerHooks).noteVibratorOn(eq(UID), eq(300L));
+        verify(mManagerHooks).noteVibratorOff(eq(UID));
+
+        verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
+        assertThat(mControllers.get(VIBRATOR_ID).isVibrating()).isFalse();
+
+        assertThat(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId))
+                .isEqualTo(expectedOneShots(100L, 150L));
+    }
+
+    @Test
+    public void vibrate_singleVibratorPatternWithZeroDurationAndAmplitude_skipsZeroDurationSteps() {
+        mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+
+        int[] amplitudes = new int[]{1, 2, 0, 3, 4, 5, 0, 6};
+        VibrationEffect effect = VibrationEffect.createWaveform(
+                /* timings= */ new long[]{0, 100, 0, 50, 50, 0, 100, 50}, amplitudes,
+                /* repeat= */ -1);
+        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
+        long vibrationId = conductor.getVibration().id;
+        waitForCompletion();
+
+        verify(mManagerHooks).noteVibratorOn(eq(UID), eq(350L));
+        verify(mManagerHooks).noteVibratorOff(eq(UID));
+
+        verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
+        assertThat(mControllers.get(VIBRATOR_ID).isVibrating()).isFalse();
+
+        assertThat(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId))
+                .isEqualTo(expectedOneShots(200L, 50L));
+    }
+
+    @LargeTest
+    @Test
+    public void vibrate_singleVibratorRepeatingPatternWithZeroDurationSteps_repeatsEffectCorrectly()
+            throws Exception {
+        FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
+        fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+
+        VibrationEffect effect = VibrationEffect.createWaveform(
+                /* timings= */ new long[]{0, 200, 50, 100, 0, 50, 50, 100}, /* repeat= */ 0);
+        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
+        long vibrationId = conductor.getVibration().id;
+        // We are expect this test to repeat the vibration effect twice, which would result in 5
+        // segments being played:
+        // 200ms ON
+        // 150ms ON (100ms + 50ms, skips 0ms)
+        // 300ms ON (100ms + 200ms looping to the start and skipping first 0ms)
+        // 150ms ON (100ms + 50ms, skips 0ms)
+        // 300ms ON (100ms + 200ms looping to the start and skipping first 0ms)
+        assertTrue(waitUntil(() -> fakeVibrator.getEffectSegments(vibrationId).size() >= 5,
+                5000L + TEST_TIMEOUT_MILLIS));
+        conductor.notifyCancelled(
+                new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_USER),
+                /* immediate= */ false);
+        waitForCompletion();
+
+        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_USER);
+        assertThat(mControllers.get(VIBRATOR_ID).isVibrating()).isFalse();
+
+        assertThat(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId).subList(0, 5))
+                .isEqualTo(expectedOneShots(200L, 150L, 300L, 150L, 300L));
+    }
+
+    @Test
+    public void vibrate_singleVibratorRepeatingPwle_generatesLargestPwles() throws Exception {
+        FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
+        fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
+        fakeVibrator.setMinFrequency(100);
+        fakeVibrator.setResonantFrequency(150);
+        fakeVibrator.setFrequencyResolution(50);
+        fakeVibrator.setMaxAmplitudes(1, 1, 1);
+        fakeVibrator.setPwleSizeMax(10);
+
+        VibrationEffect effect = VibrationEffect.startWaveform(targetAmplitude(1))
+                // Very long segment so thread will be cancelled after first PWLE is triggered.
+                .addTransition(Duration.ofMillis(100), targetFrequency(100))
+                .build();
+        VibrationEffect repeatingEffect = VibrationEffect.startComposition()
+                .repeatEffectIndefinitely(effect)
+                .compose();
+        VibrationStepConductor conductor = startThreadAndDispatcher(repeatingEffect);
+        long vibrationId = conductor.getVibration().id;
+
+        assertTrue(waitUntil(() -> !fakeVibrator.getEffectSegments(vibrationId).isEmpty(),
+                TEST_TIMEOUT_MILLIS));
+        conductor.notifyCancelled(
+                new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_USER),
+                /* immediate= */ false);
+        waitForCompletion();
+
+        // PWLE size max was used to generate a single vibrate call with 10 segments.
+        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_USER);
+        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
+        assertEquals(10, fakeVibrator.getEffectSegments(vibrationId).size());
+    }
+
+    @Test
+    public void vibrate_singleVibratorRepeatingPrimitives_generatesLargestComposition()
+            throws Exception {
+        FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
+        fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+        fakeVibrator.setSupportedPrimitives(VibrationEffect.Composition.PRIMITIVE_CLICK);
+        fakeVibrator.setCompositionSizeMax(10);
+
+        VibrationEffect effect = VibrationEffect.startComposition()
+                // Very long delay so thread will be cancelled after first PWLE is triggered.
+                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 100)
+                .compose();
+        VibrationEffect repeatingEffect = VibrationEffect.startComposition()
+                .repeatEffectIndefinitely(effect)
+                .compose();
+        VibrationStepConductor conductor = startThreadAndDispatcher(repeatingEffect);
+        long vibrationId = conductor.getVibration().id;
+
+        assertTrue(waitUntil(() -> !fakeVibrator.getEffectSegments(vibrationId).isEmpty(),
+                TEST_TIMEOUT_MILLIS));
+        conductor.notifyCancelled(
+                new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_SCREEN_OFF),
+                /* immediate= */ false);
+        waitForCompletion();
+
+        // Composition size max was used to generate a single vibrate call with 10 primitives.
+        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_SCREEN_OFF);
+        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
+        assertEquals(10, fakeVibrator.getEffectSegments(vibrationId).size());
+    }
+
+    @Test
+    public void vibrate_singleVibratorRepeatingLongAlwaysOnWaveform_turnsVibratorOnForACycle()
+            throws Exception {
+        FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
+        fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+
+        int[] amplitudes = new int[]{1, 2, 3};
+        VibrationEffect effect = VibrationEffect.createWaveform(
+                new long[]{5000, 500, 50}, amplitudes, 0);
+        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
+        long vibrationId = conductor.getVibration().id;
+
+        assertTrue(waitUntil(() -> !fakeVibrator.getAmplitudes().isEmpty(), TEST_TIMEOUT_MILLIS));
+        conductor.notifyCancelled(
+                new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_USER),
+                /* immediate= */ false);
+        waitForCompletion();
+
+        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_USER);
+        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
+        assertEquals(Arrays.asList(expectedOneShot(5550)),
+                fakeVibrator.getEffectSegments(vibrationId));
+    }
+
+    @Ignore("b/290940400")
+    @LargeTest
+    @Test
+    public void vibrate_singleVibratorRepeatingAlwaysOnWaveform_turnsVibratorBackOn()
+            throws Exception {
+        FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
+        fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+
+        int[] amplitudes = new int[]{1, 2};
+        VibrationEffect effect = VibrationEffect.createWaveform(
+                new long[]{4900, 50}, amplitudes, 0);
+        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
+        long vibrationId = conductor.getVibration().id;
+
+        assertTrue(waitUntil(() -> fakeVibrator.getEffectSegments(vibrationId).size() > 1,
+                5000 + TEST_TIMEOUT_MILLIS));
+        conductor.notifyCancelled(
+                new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_USER),
+                /* immediate= */ false);
+        waitForCompletion();
+
+        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_USER);
+        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
+        // First time turn vibrator ON for minimum of 5s.
+        assertEquals(5000L, fakeVibrator.getEffectSegments(vibrationId).get(0).getDuration());
+        // Vibrator turns off in the middle of the second execution of first step, turn it back ON
+        // for another 5s + remaining of 850ms.
+        assertEquals(4900 + 50 + 4900,
+                fakeVibrator.getEffectSegments(vibrationId).get(1).getDuration(), /* delta= */ 20);
+        // Set amplitudes for a cycle {1, 2}, start second loop then turn it back on to same value.
+        assertEquals(expectedAmplitudes(1, 2, 1, 1),
+                mVibratorProviders.get(VIBRATOR_ID).getAmplitudes().subList(0, 4));
+    }
+
+    @Test
+    public void vibrate_singleVibratorPredefinedCancel_cancelsVibrationImmediately()
+            throws Exception {
+        mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+        mVibratorProviders.get(VIBRATOR_ID).setSupportedPrimitives(
+                VibrationEffect.Composition.PRIMITIVE_CLICK);
+
+        VibrationEffect effect = VibrationEffect.startComposition()
+                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 100)
+                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 100)
+                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 100)
+                .compose();
+        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
+        long vibrationId = conductor.getVibration().id;
+
+        assertTrue(waitUntil(() -> mControllers.get(VIBRATOR_ID).isVibrating(),
+                TEST_TIMEOUT_MILLIS));
+        assertTrue(mThread.isRunningVibrationId(vibrationId));
+
+        // Run cancel in a separate thread so if VibrationThread.cancel blocks then this test should
+        // fail at waitForCompletion(vibrationThread) if the vibration not cancelled immediately.
+        Thread cancellingThread =
+                new Thread(() -> conductor.notifyCancelled(
+                        new Vibration.EndInfo(
+                                Vibration.Status.CANCELLED_BY_SETTINGS_UPDATE),
+                        /* immediate= */ false));
+        cancellingThread.start();
+
+        waitForCompletion(/* timeout= */ 50);
+        cancellingThread.join();
+
+        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_SETTINGS_UPDATE);
+        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
+    }
+
+    @Test
+    public void vibrate_singleVibratorWaveformCancel_cancelsVibrationImmediately()
+            throws Exception {
+        mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+
+        VibrationEffect effect = VibrationEffect.createWaveform(new long[]{100}, new int[]{100}, 0);
+        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
+        long vibrationId = conductor.getVibration().id;
+
+        assertTrue(waitUntil(() -> mControllers.get(VIBRATOR_ID).isVibrating(),
+                TEST_TIMEOUT_MILLIS));
+        assertTrue(mThread.isRunningVibrationId(vibrationId));
+
+        // Run cancel in a separate thread so if VibrationThread.cancel blocks then this test should
+        // fail at waitForCompletion(vibrationThread) if the vibration not cancelled immediately.
+        Thread cancellingThread =
+                new Thread(() -> conductor.notifyCancelled(
+                        new Vibration.EndInfo(
+                                Vibration.Status.CANCELLED_BY_SCREEN_OFF),
+                        /* immediate= */ false));
+        cancellingThread.start();
+
+        waitForCompletion(/* timeout= */ 50);
+        cancellingThread.join();
+
+        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_SCREEN_OFF);
+        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
+    }
+
+    @Test
+    public void vibrate_singleVibratorPrebaked_runsVibration() throws Exception {
+        mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_THUD);
+
+        VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_THUD);
+        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
+        long vibrationId = conductor.getVibration().id;
+        waitForCompletion();
+
+        verify(mManagerHooks).noteVibratorOn(eq(UID), eq(20L));
+        verify(mManagerHooks).noteVibratorOff(eq(UID));
+        verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
+        verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
+        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
+
+        assertEquals(Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_THUD)),
+                mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId));
+    }
+
+    @Test
+    public void vibrate_singleVibratorPrebakedAndUnsupportedEffectWithFallback_runsFallback()
+            throws Exception {
+        mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+
+        VibrationEffect fallback = VibrationEffect.createOneShot(10, 100);
+        HalVibration vibration = createVibration(CombinedVibration.createParallel(
+                VibrationEffect.get(VibrationEffect.EFFECT_CLICK)));
+        vibration.addFallback(VibrationEffect.EFFECT_CLICK, fallback);
+        VibrationStepConductor conductor = startThreadAndDispatcher(vibration);
+        long vibrationId = conductor.getVibration().id;
+        waitForCompletion();
+
+        verify(mManagerHooks).noteVibratorOn(eq(UID), eq(10L));
+        verify(mManagerHooks).noteVibratorOff(eq(UID));
+        verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
+        verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
+        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
+
+        assertEquals(Arrays.asList(expectedOneShot(10)),
+                mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId));
+        assertEquals(expectedAmplitudes(100), mVibratorProviders.get(VIBRATOR_ID).getAmplitudes());
+    }
+
+    @Test
+    public void vibrate_singleVibratorPrebakedAndUnsupportedEffect_ignoresVibration()
+            throws Exception {
+        VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
+        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
+        long vibrationId = conductor.getVibration().id;
+        waitForCompletion();
+
+        verify(mManagerHooks).noteVibratorOn(eq(UID), eq(0L));
+        verify(mManagerHooks, never()).noteVibratorOff(eq(UID));
+        verify(mControllerCallbacks, never()).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
+        verifyCallbacksTriggered(vibrationId, Vibration.Status.IGNORED_UNSUPPORTED);
+        assertTrue(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId).isEmpty());
+    }
+
+    @Test
+    public void vibrate_singleVibratorComposed_runsVibration() throws Exception {
+        FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
+        fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+        fakeVibrator.setSupportedPrimitives(VibrationEffect.Composition.PRIMITIVE_CLICK,
+                VibrationEffect.Composition.PRIMITIVE_TICK);
+
+        VibrationEffect effect = VibrationEffect.startComposition()
+                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f)
+                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f)
+                .compose();
+        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
+        long vibrationId = conductor.getVibration().id;
+        waitForCompletion();
+
+        verify(mManagerHooks).noteVibratorOn(eq(UID), eq(40L));
+        verify(mManagerHooks).noteVibratorOff(eq(UID));
+        verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
+        verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
+        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
+        assertEquals(Arrays.asList(
+                expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0),
+                expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f, 0)),
+                fakeVibrator.getEffectSegments(vibrationId));
+    }
+
+    @Test
+    public void vibrate_singleVibratorComposedAndNoCapability_ignoresVibration() {
+        VibrationEffect effect = VibrationEffect.startComposition()
+                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f)
+                .compose();
+        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
+        long vibrationId = conductor.getVibration().id;
+        waitForCompletion();
+
+        verify(mManagerHooks).noteVibratorOn(eq(UID), eq(0L));
+        verify(mManagerHooks, never()).noteVibratorOff(eq(UID));
+        verify(mControllerCallbacks, never()).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
+        verifyCallbacksTriggered(vibrationId, Vibration.Status.IGNORED_UNSUPPORTED);
+        assertTrue(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId).isEmpty());
+    }
+
+    @Test
+    public void vibrate_singleVibratorLargeComposition_splitsVibratorComposeCalls() {
+        FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
+        fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+        fakeVibrator.setSupportedPrimitives(
+                VibrationEffect.Composition.PRIMITIVE_CLICK,
+                VibrationEffect.Composition.PRIMITIVE_TICK,
+                VibrationEffect.Composition.PRIMITIVE_SPIN);
+        fakeVibrator.setCompositionSizeMax(2);
+
+        VibrationEffect effect = VibrationEffect.startComposition()
+                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f)
+                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f)
+                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_SPIN, 0.8f)
+                .compose();
+        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
+        long vibrationId = conductor.getVibration().id;
+        waitForCompletion();
+
+        verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
+        // Vibrator compose called twice.
+        verify(mControllerCallbacks, times(2)).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
+        assertEquals(3, fakeVibrator.getEffectSegments(vibrationId).size());
+    }
+
+    @Test
+    public void vibrate_singleVibratorComposedEffects_runsDifferentVibrations() throws Exception {
+        FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
+        fakeVibrator.setSupportedEffects(VibrationEffect.EFFECT_CLICK);
+        fakeVibrator.setSupportedPrimitives(
+                VibrationEffect.Composition.PRIMITIVE_CLICK,
+                VibrationEffect.Composition.PRIMITIVE_TICK);
+        fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS,
+                IVibrator.CAP_COMPOSE_PWLE_EFFECTS, IVibrator.CAP_AMPLITUDE_CONTROL);
+        fakeVibrator.setMinFrequency(100);
+        fakeVibrator.setResonantFrequency(150);
+        fakeVibrator.setFrequencyResolution(50);
+        fakeVibrator.setMaxAmplitudes(
+                0.5f /* 100Hz*/, 1 /* 150Hz */, 0.6f /* 200Hz */);
+
+        VibrationEffect effect = VibrationEffect.startComposition()
+                .addEffect(VibrationEffect.createOneShot(10, 100))
+                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f)
+                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f)
+                .addEffect(VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
+                .addEffect(VibrationEffect.startWaveform()
+                        .addTransition(Duration.ofMillis(10),
+                                targetAmplitude(1), targetFrequency(100))
+                        .addTransition(Duration.ofMillis(20), targetFrequency(120))
+                        .build())
+                .addEffect(VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
+                .compose();
+        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
+        long vibrationId = conductor.getVibration().id;
+        waitForCompletion();
+
+        // Use first duration the vibrator is turned on since we cannot estimate the clicks.
+        verify(mManagerHooks).noteVibratorOn(eq(UID), eq(10L));
+        verify(mManagerHooks).noteVibratorOff(eq(UID));
+        verify(mControllerCallbacks, times(5)).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
+        verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
+        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
+        assertEquals(Arrays.asList(
+                expectedOneShot(10),
+                expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0),
+                expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f, 0),
+                expectedPrebaked(VibrationEffect.EFFECT_CLICK),
+                expectedRamp(/* startAmplitude= */ 0, /* endAmplitude= */ 0.5f,
+                        /* startFrequencyHz= */ 150, /* endFrequencyHz= */ 100, /* duration= */ 10),
+                expectedRamp(/* startAmplitude= */ 0.5f, /* endAmplitude= */ 0.7f,
+                        /* startFrequencyHz= */ 100, /* endFrequencyHz= */ 120, /* duration= */ 20),
+                expectedPrebaked(VibrationEffect.EFFECT_CLICK)),
+                mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId));
+        assertEquals(expectedAmplitudes(100), mVibratorProviders.get(VIBRATOR_ID).getAmplitudes());
+    }
+
+    @Test
+    public void vibrate_singleVibratorComposedWithFallback_replacedInTheMiddleOfComposition() {
+        FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
+        fakeVibrator.setSupportedEffects(VibrationEffect.EFFECT_CLICK);
+        fakeVibrator.setSupportedPrimitives(
+                VibrationEffect.Composition.PRIMITIVE_CLICK,
+                VibrationEffect.Composition.PRIMITIVE_TICK);
+        fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+
+        VibrationEffect fallback = VibrationEffect.createOneShot(10, 100);
+        VibrationEffect effect = VibrationEffect.startComposition()
+                .addEffect(VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
+                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f)
+                .addEffect(VibrationEffect.get(VibrationEffect.EFFECT_TICK))
+                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f)
+                .compose();
+        HalVibration vib = createVibration(CombinedVibration.createParallel(effect));
+        vib.addFallback(VibrationEffect.EFFECT_TICK, fallback);
+        VibrationStepConductor conductor = startThreadAndDispatcher(vib);
+        long vibrationId = conductor.getVibration().id;
+        waitForCompletion();
+
+        // Use first duration the vibrator is turned on since we cannot estimate the clicks.
+        verify(mManagerHooks).noteVibratorOn(eq(UID), anyLong());
+        verify(mManagerHooks).noteVibratorOff(eq(UID));
+        verify(mControllerCallbacks, times(4)).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
+        verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
+        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
+
+        List<VibrationEffectSegment> segments =
+                mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId);
+        assertTrue("Wrong segments: " + segments, segments.size() >= 4);
+        assertTrue(segments.get(0) instanceof PrebakedSegment);
+        assertTrue(segments.get(1) instanceof PrimitiveSegment);
+        for (int i = 2; i < segments.size() - 1; i++) {
+            // One or more step segments as fallback for the EFFECT_TICK.
+            assertTrue(segments.get(i) instanceof StepSegment);
+        }
+        assertTrue(segments.get(segments.size() - 1) instanceof PrimitiveSegment);
+    }
+
+    @Test
+    public void vibrate_singleVibratorPwle_runsComposePwle() throws Exception {
+        FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
+        fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
+        fakeVibrator.setSupportedBraking(Braking.CLAB);
+        fakeVibrator.setMinFrequency(100);
+        fakeVibrator.setResonantFrequency(150);
+        fakeVibrator.setFrequencyResolution(50);
+        fakeVibrator.setMaxAmplitudes(
+                0.5f /* 100Hz*/, 1 /* 150Hz */, 0.6f /* 200Hz */);
+
+        VibrationEffect effect = VibrationEffect.startWaveform(targetAmplitude(1))
+                .addSustain(Duration.ofMillis(10))
+                .addTransition(Duration.ofMillis(20), targetAmplitude(0))
+                .addTransition(Duration.ZERO, targetAmplitude(0.8f), targetFrequency(100))
+                .addSustain(Duration.ofMillis(30))
+                .addTransition(Duration.ofMillis(40), targetAmplitude(0.6f), targetFrequency(200))
+                .build();
+        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
+        long vibrationId = conductor.getVibration().id;
+        waitForCompletion();
+
+        verify(mManagerHooks).noteVibratorOn(eq(UID), eq(100L));
+        verify(mManagerHooks).noteVibratorOff(eq(UID));
+        verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
+        verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
+        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
+        assertEquals(Arrays.asList(
+                expectedRamp(/* amplitude= */ 1, /* frequencyHz= */ 150, /* duration= */ 10),
+                expectedRamp(/* startAmplitude= */ 1, /* endAmplitude= */ 0,
+                        /* startFrequencyHz= */ 150, /* endFrequencyHz= */ 150, /* duration= */ 20),
+                expectedRamp(/* amplitude= */ 0.5f, /* frequencyHz= */ 100, /* duration= */ 30),
+                expectedRamp(/* startAmplitude= */ 0.5f, /* endAmplitude= */ 0.6f,
+                        /* startFrequencyHz= */ 100, /* endFrequencyHz= */ 200,
+                        /* duration= */ 40)),
+                fakeVibrator.getEffectSegments(vibrationId));
+        assertEquals(Arrays.asList(Braking.CLAB), fakeVibrator.getBraking(vibrationId));
+    }
+
+    @Test
+    public void vibrate_singleVibratorLargePwle_splitsComposeCallWhenAmplitudeIsLowest() {
+        FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
+        fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
+        fakeVibrator.setMinFrequency(100);
+        fakeVibrator.setResonantFrequency(150);
+        fakeVibrator.setFrequencyResolution(50);
+        fakeVibrator.setMaxAmplitudes(1, 1, 1);
+        fakeVibrator.setPwleSizeMax(3);
+
+        VibrationEffect effect = VibrationEffect.startWaveform(targetAmplitude(1))
+                .addSustain(Duration.ofMillis(10))
+                .addTransition(Duration.ofMillis(20), targetAmplitude(0))
+                // Waveform will be split here, after vibration goes to zero amplitude
+                .addTransition(Duration.ZERO, targetAmplitude(0.8f), targetFrequency(100))
+                .addSustain(Duration.ofMillis(30))
+                .addTransition(Duration.ofMillis(40), targetAmplitude(0.6f), targetFrequency(200))
+                // Waveform will be split here at lowest amplitude.
+                .addTransition(Duration.ofMillis(40), targetAmplitude(0.7f), targetFrequency(200))
+                .addTransition(Duration.ofMillis(40), targetAmplitude(0.6f), targetFrequency(200))
+                .build();
+        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
+        long vibrationId = conductor.getVibration().id;
+        waitForCompletion();
+
+        verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
+
+        // Vibrator compose called 3 times with 2 segments instead of 2 times with 3 segments.
+        // Using best split points instead of max-packing PWLEs.
+        verify(mControllerCallbacks, times(3)).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
+        assertEquals(6, fakeVibrator.getEffectSegments(vibrationId).size());
+    }
+
+    @Test
+    public void vibrate_singleVibratorCancelled_vibratorStopped() throws Exception {
+        FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
+        fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+
+        VibrationEffect effect = VibrationEffect.createWaveform(new long[]{5}, new int[]{100}, 0);
+        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
+        long vibrationId = conductor.getVibration().id;
+
+        assertTrue(waitUntil(() -> fakeVibrator.getAmplitudes().size() > 2, TEST_TIMEOUT_MILLIS));
+        // Vibration still running after 2 cycles.
+        assertTrue(mThread.isRunningVibrationId(vibrationId));
+        assertTrue(mControllers.get(VIBRATOR_ID).isVibrating());
+
+        conductor.binderDied();
+        waitForCompletion();
+        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
+
+        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BINDER_DIED);
+    }
+
+    @Test
+    public void vibrate_singleVibrator_skipsSyncedCallbacks() {
+        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+
+        VibrationStepConductor conductor = startThreadAndDispatcher(
+                VibrationEffect.createOneShot(10, 100));
+        long vibrationId = conductor.getVibration().id;
+        waitForCompletion();
+
+        verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
+        verify(mManagerHooks, never()).prepareSyncedVibration(anyLong(), any());
+        verify(mManagerHooks, never()).triggerSyncedVibration(anyLong());
+        verify(mManagerHooks, never()).cancelSyncedVibration();
+    }
+
+    @Test
+    public void vibrate_multipleExistingAndMissingVibrators_vibratesOnlyExistingOnes()
+            throws Exception {
+        mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_TICK);
+
+        CombinedVibration effect = CombinedVibration.startParallel()
+                .addVibrator(VIBRATOR_ID, VibrationEffect.get(VibrationEffect.EFFECT_TICK))
+                .addVibrator(2, VibrationEffect.get(VibrationEffect.EFFECT_TICK))
+                .combine();
+        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
+        long vibrationId = conductor.getVibration().id;
+        waitForCompletion();
+
+        verify(mManagerHooks).noteVibratorOn(eq(UID), eq(20L));
+        verify(mManagerHooks).noteVibratorOff(eq(UID));
+        verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
+        verify(mControllerCallbacks, never()).onComplete(eq(2), eq(vibrationId));
+        verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
+        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
+
+        assertEquals(Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_TICK)),
+                mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId));
+    }
+
+    @Test
+    public void vibrate_multipleMono_runsSameEffectInAllVibrators() throws Exception {
+        mockVibrators(1, 2, 3);
+        mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
+        mVibratorProviders.get(2).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
+        mVibratorProviders.get(3).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
+
+        CombinedVibration effect = CombinedVibration.createParallel(
+                VibrationEffect.get(VibrationEffect.EFFECT_CLICK));
+        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
+        long vibrationId = conductor.getVibration().id;
+        waitForCompletion();
+
+        verify(mManagerHooks).noteVibratorOn(eq(UID), eq(20L));
+        verify(mManagerHooks).noteVibratorOff(eq(UID));
+        verify(mControllerCallbacks).onComplete(eq(1), eq(vibrationId));
+        verify(mControllerCallbacks).onComplete(eq(2), eq(vibrationId));
+        verify(mControllerCallbacks).onComplete(eq(3), eq(vibrationId));
+        verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
+        assertFalse(mControllers.get(1).isVibrating());
+        assertFalse(mControllers.get(2).isVibrating());
+        assertFalse(mControllers.get(3).isVibrating());
+
+        VibrationEffectSegment expected = expectedPrebaked(VibrationEffect.EFFECT_CLICK);
+        assertEquals(Arrays.asList(expected),
+                mVibratorProviders.get(1).getEffectSegments(vibrationId));
+        assertEquals(Arrays.asList(expected),
+                mVibratorProviders.get(2).getEffectSegments(vibrationId));
+        assertEquals(Arrays.asList(expected),
+                mVibratorProviders.get(3).getEffectSegments(vibrationId));
+    }
+
+    @Test
+    public void vibrate_multipleStereo_runsVibrationOnRightVibrators() throws Exception {
+        mockVibrators(1, 2, 3, 4);
+        mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
+        mVibratorProviders.get(2).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+        mVibratorProviders.get(3).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+        mVibratorProviders.get(4).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+        mVibratorProviders.get(4).setSupportedPrimitives(
+                VibrationEffect.Composition.PRIMITIVE_CLICK);
+
+        VibrationEffect composed = VibrationEffect.startComposition()
+                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
+                .compose();
+        CombinedVibration effect = CombinedVibration.startParallel()
+                .addVibrator(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
+                .addVibrator(2, VibrationEffect.createOneShot(10, 100))
+                .addVibrator(3, VibrationEffect.createWaveform(
+                        new long[]{10, 10}, new int[]{1, 2}, -1))
+                .addVibrator(4, composed)
+                .combine();
+        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
+        long vibrationId = conductor.getVibration().id;
+        waitForCompletion();
+
+        verify(mManagerHooks).noteVibratorOn(eq(UID), eq(20L));
+        verify(mManagerHooks).noteVibratorOff(eq(UID));
+        verify(mControllerCallbacks).onComplete(eq(1), eq(vibrationId));
+        verify(mControllerCallbacks).onComplete(eq(2), eq(vibrationId));
+        verify(mControllerCallbacks).onComplete(eq(3), eq(vibrationId));
+        verify(mControllerCallbacks).onComplete(eq(4), eq(vibrationId));
+        verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
+        assertFalse(mControllers.get(1).isVibrating());
+        assertFalse(mControllers.get(2).isVibrating());
+        assertFalse(mControllers.get(3).isVibrating());
+        assertFalse(mControllers.get(4).isVibrating());
+
+        assertEquals(Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_CLICK)),
+                mVibratorProviders.get(1).getEffectSegments(vibrationId));
+        assertEquals(Arrays.asList(expectedOneShot(10)),
+                mVibratorProviders.get(2).getEffectSegments(vibrationId));
+        assertEquals(expectedAmplitudes(100), mVibratorProviders.get(2).getAmplitudes());
+        assertEquals(Arrays.asList(expectedOneShot(20)),
+                mVibratorProviders.get(3).getEffectSegments(vibrationId));
+        assertEquals(expectedAmplitudes(1, 2), mVibratorProviders.get(3).getAmplitudes());
+        assertEquals(Arrays.asList(
+                expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0)),
+                mVibratorProviders.get(4).getEffectSegments(vibrationId));
+    }
+
+    @Test
+    public void vibrate_multipleSequential_runsVibrationInOrderWithDelays() throws Exception {
+        mockVibrators(1, 2, 3);
+        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+        mVibratorProviders.get(2).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+        mVibratorProviders.get(2).setSupportedPrimitives(
+                VibrationEffect.Composition.PRIMITIVE_CLICK);
+        mVibratorProviders.get(3).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
+
+        VibrationEffect composed = VibrationEffect.startComposition()
+                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
+                .compose();
+        CombinedVibration effect = CombinedVibration.startSequential()
+                .addNext(3, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), /* delay= */ 50)
+                .addNext(1, VibrationEffect.createOneShot(10, 100), /* delay= */ 50)
+                .addNext(2, composed, /* delay= */ 50)
+                .combine();
+        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
+        long vibrationId = conductor.getVibration().id;
+
+        waitForCompletion();
+        InOrder controllerVerifier = inOrder(mControllerCallbacks);
+        controllerVerifier.verify(mControllerCallbacks).onComplete(eq(3), eq(vibrationId));
+        controllerVerifier.verify(mControllerCallbacks).onComplete(eq(1), eq(vibrationId));
+        controllerVerifier.verify(mControllerCallbacks).onComplete(eq(2), eq(vibrationId));
+
+        InOrder batteryVerifier = inOrder(mManagerHooks);
+        batteryVerifier.verify(mManagerHooks).noteVibratorOn(eq(UID), eq(20L));
+        batteryVerifier.verify(mManagerHooks).noteVibratorOff(eq(UID));
+        batteryVerifier.verify(mManagerHooks).noteVibratorOn(eq(UID), eq(10L));
+        batteryVerifier.verify(mManagerHooks).noteVibratorOff(eq(UID));
+        batteryVerifier.verify(mManagerHooks).noteVibratorOn(eq(UID), eq(20L));
+        batteryVerifier.verify(mManagerHooks).noteVibratorOff(eq(UID));
+
+        verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
+        assertFalse(mControllers.get(1).isVibrating());
+        assertFalse(mControllers.get(2).isVibrating());
+        assertFalse(mControllers.get(3).isVibrating());
+
+        assertEquals(Arrays.asList(expectedOneShot(10)),
+                mVibratorProviders.get(1).getEffectSegments(vibrationId));
+        assertEquals(expectedAmplitudes(100), mVibratorProviders.get(1).getAmplitudes());
+        assertEquals(Arrays.asList(
+                expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0)),
+                mVibratorProviders.get(2).getEffectSegments(vibrationId));
+        assertEquals(Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_CLICK)),
+                mVibratorProviders.get(3).getEffectSegments(vibrationId));
+    }
+
+    @Test
+    public void vibrate_multipleSyncedCallbackTriggered_finishSteps() throws Exception {
+        int[] vibratorIds = new int[]{1, 2};
+        mockVibrators(vibratorIds);
+        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+        mVibratorProviders.get(1).setSupportedPrimitives(
+                VibrationEffect.Composition.PRIMITIVE_CLICK);
+        mVibratorProviders.get(2).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+        mVibratorProviders.get(2).setSupportedPrimitives(
+                VibrationEffect.Composition.PRIMITIVE_CLICK);
+        when(mManagerHooks.prepareSyncedVibration(anyLong(), eq(vibratorIds))).thenReturn(true);
+
+        VibrationEffect composed = VibrationEffect.startComposition()
+                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 100)
+                .compose();
+        CombinedVibration effect = CombinedVibration.createParallel(composed);
+        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
+        long vibrationId = conductor.getVibration().id;
+
+        when(mManagerHooks.triggerSyncedVibration(eq(vibrationId))).thenReturn(true);
+
+        assertTrue(waitUntil(
+                () -> !mVibratorProviders.get(1).getEffectSegments(vibrationId).isEmpty()
+                        && !mVibratorProviders.get(2).getEffectSegments(vibrationId).isEmpty(),
+                TEST_TIMEOUT_MILLIS));
+        conductor.notifySyncedVibrationComplete();
+        waitForCompletion();
+
+        long expectedCap = IVibratorManager.CAP_SYNC | IVibratorManager.CAP_PREPARE_COMPOSE;
+        verify(mManagerHooks).prepareSyncedVibration(eq(expectedCap), eq(vibratorIds));
+        verify(mManagerHooks).triggerSyncedVibration(eq(vibrationId));
+        verify(mManagerHooks, never()).cancelSyncedVibration();
+        verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
+
+        VibrationEffectSegment expected = expectedPrimitive(
+                VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 100);
+        assertEquals(Arrays.asList(expected),
+                mVibratorProviders.get(1).getEffectSegments(vibrationId));
+        assertEquals(Arrays.asList(expected),
+                mVibratorProviders.get(2).getEffectSegments(vibrationId));
+    }
+
+    @Test
+    public void vibrate_multipleSynced_callsPrepareAndTriggerCallbacks() {
+        int[] vibratorIds = new int[]{1, 2, 3, 4};
+        mockVibrators(vibratorIds);
+        mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
+        mVibratorProviders.get(4).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+        mVibratorProviders.get(4).setSupportedPrimitives(
+                VibrationEffect.Composition.PRIMITIVE_CLICK);
+        when(mManagerHooks.prepareSyncedVibration(anyLong(), any())).thenReturn(true);
+        when(mManagerHooks.triggerSyncedVibration(anyLong())).thenReturn(true);
+
+        VibrationEffect composed = VibrationEffect.startComposition()
+                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
+                .compose();
+        CombinedVibration effect = CombinedVibration.startParallel()
+                .addVibrator(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
+                .addVibrator(2, VibrationEffect.createOneShot(10, 100))
+                .addVibrator(3, VibrationEffect.createWaveform(new long[]{10}, new int[]{100}, -1))
+                .addVibrator(4, composed)
+                .combine();
+        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
+        long vibrationId = conductor.getVibration().id;
+        waitForCompletion();
+
+        long expectedCap = IVibratorManager.CAP_SYNC
+                | IVibratorManager.CAP_PREPARE_ON
+                | IVibratorManager.CAP_PREPARE_PERFORM
+                | IVibratorManager.CAP_PREPARE_COMPOSE
+                | IVibratorManager.CAP_MIXED_TRIGGER_ON
+                | IVibratorManager.CAP_MIXED_TRIGGER_PERFORM
+                | IVibratorManager.CAP_MIXED_TRIGGER_COMPOSE;
+        verify(mManagerHooks).prepareSyncedVibration(eq(expectedCap), eq(vibratorIds));
+        verify(mManagerHooks).triggerSyncedVibration(eq(vibrationId));
+        verify(mManagerHooks, never()).cancelSyncedVibration();
+        verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
+    }
+
+    @Test
+    public void vibrate_multipleSyncedPrepareFailed_skipTriggerStepAndVibrates() {
+        int[] vibratorIds = new int[]{1, 2};
+        mockVibrators(vibratorIds);
+        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+        mVibratorProviders.get(2).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+        when(mManagerHooks.prepareSyncedVibration(anyLong(), any())).thenReturn(false);
+
+        CombinedVibration effect = CombinedVibration.startParallel()
+                .addVibrator(1, VibrationEffect.createOneShot(10, 100))
+                .addVibrator(2, VibrationEffect.createWaveform(new long[]{5}, new int[]{200}, -1))
+                .combine();
+        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
+        long vibrationId = conductor.getVibration().id;
+        waitForCompletion();
+
+        long expectedCap = IVibratorManager.CAP_SYNC | IVibratorManager.CAP_PREPARE_ON;
+        verify(mManagerHooks).prepareSyncedVibration(eq(expectedCap), eq(vibratorIds));
+        verify(mManagerHooks, never()).triggerSyncedVibration(eq(vibrationId));
+        verify(mManagerHooks, never()).cancelSyncedVibration();
+
+        assertEquals(Arrays.asList(expectedOneShot(10)),
+                mVibratorProviders.get(1).getEffectSegments(vibrationId));
+        assertEquals(expectedAmplitudes(100), mVibratorProviders.get(1).getAmplitudes());
+        assertEquals(Arrays.asList(expectedOneShot(5)),
+                mVibratorProviders.get(2).getEffectSegments(vibrationId));
+        assertEquals(expectedAmplitudes(200), mVibratorProviders.get(2).getAmplitudes());
+    }
+
+    @Test
+    public void vibrate_multipleSyncedTriggerFailed_cancelPreparedVibrationAndSkipSetAmplitude() {
+        int[] vibratorIds = new int[]{1, 2};
+        mockVibrators(vibratorIds);
+        mVibratorProviders.get(2).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
+        when(mManagerHooks.prepareSyncedVibration(anyLong(), any())).thenReturn(true);
+        when(mManagerHooks.triggerSyncedVibration(anyLong())).thenReturn(false);
+
+        CombinedVibration effect = CombinedVibration.startParallel()
+                .addVibrator(1, VibrationEffect.createOneShot(10, 100))
+                .addVibrator(2, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
+                .combine();
+        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
+        long vibrationId = conductor.getVibration().id;
+        waitForCompletion();
+
+        long expectedCap = IVibratorManager.CAP_SYNC
+                | IVibratorManager.CAP_PREPARE_ON
+                | IVibratorManager.CAP_PREPARE_PERFORM
+                | IVibratorManager.CAP_MIXED_TRIGGER_ON
+                | IVibratorManager.CAP_MIXED_TRIGGER_PERFORM;
+        verify(mManagerHooks).prepareSyncedVibration(eq(expectedCap), eq(vibratorIds));
+        verify(mManagerHooks).triggerSyncedVibration(eq(vibrationId));
+        verify(mManagerHooks).cancelSyncedVibration();
+        assertTrue(mVibratorProviders.get(1).getAmplitudes().isEmpty());
+    }
+
+    @Test
+    public void vibrate_multipleWaveforms_playsWaveformsInParallel() throws Exception {
+        mockVibrators(1, 2, 3);
+        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+        mVibratorProviders.get(2).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+        mVibratorProviders.get(3).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+
+        CombinedVibration effect = CombinedVibration.startParallel()
+                .addVibrator(1, VibrationEffect.createWaveform(
+                        new long[]{5, 10, 10}, new int[]{1, 2, 3}, -1))
+                .addVibrator(2, VibrationEffect.createWaveform(
+                        new long[]{20, 60}, new int[]{4, 5}, -1))
+                .addVibrator(3, VibrationEffect.createWaveform(
+                        new long[]{60}, new int[]{6}, -1))
+                .combine();
+        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
+        long vibrationId = conductor.getVibration().id;
+
+        // All vibrators are turned on in parallel.
+        assertTrue(waitUntil(
+                () -> mControllers.get(1).isVibrating()
+                        && mControllers.get(2).isVibrating()
+                        && mControllers.get(3).isVibrating(),
+                TEST_TIMEOUT_MILLIS));
+
+        waitForCompletion();
+
+        verify(mManagerHooks).noteVibratorOn(eq(UID), eq(80L));
+        verify(mManagerHooks).noteVibratorOff(eq(UID));
+        verify(mControllerCallbacks).onComplete(eq(1), eq(vibrationId));
+        verify(mControllerCallbacks).onComplete(eq(2), eq(vibrationId));
+        verify(mControllerCallbacks).onComplete(eq(3), eq(vibrationId));
+        verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
+        assertFalse(mControllers.get(1).isVibrating());
+        assertFalse(mControllers.get(2).isVibrating());
+        assertFalse(mControllers.get(3).isVibrating());
+
+        assertEquals(Arrays.asList(expectedOneShot(25)),
+                mVibratorProviders.get(1).getEffectSegments(vibrationId));
+        assertEquals(Arrays.asList(expectedOneShot(80)),
+                mVibratorProviders.get(2).getEffectSegments(vibrationId));
+        assertEquals(Arrays.asList(expectedOneShot(60)),
+                mVibratorProviders.get(3).getEffectSegments(vibrationId));
+        assertEquals(expectedAmplitudes(1, 2, 3), mVibratorProviders.get(1).getAmplitudes());
+        assertEquals(expectedAmplitudes(4, 5), mVibratorProviders.get(2).getAmplitudes());
+        assertEquals(expectedAmplitudes(6), mVibratorProviders.get(3).getAmplitudes());
+    }
+
+    @LargeTest
+    @Test
+    public void vibrate_withWaveform_totalVibrationTimeRespected() {
+        int totalDuration = 10_000; // 10s
+        int stepDuration = 25; // 25ms
+
+        // 25% of the first waveform step will be spent on the native on() call.
+        // 25% of each waveform step will be spent on the native setAmplitude() call..
+        mVibratorProviders.get(VIBRATOR_ID).setOnLatency(stepDuration / 4);
+        mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+
+        int stepCount = totalDuration / stepDuration;
+        long[] timings = new long[stepCount];
+        int[] amplitudes = new int[stepCount];
+        Arrays.fill(timings, stepDuration);
+        Arrays.fill(amplitudes, VibrationEffect.DEFAULT_AMPLITUDE);
+        VibrationEffect effect = VibrationEffect.createWaveform(timings, amplitudes, -1);
+
+        startThreadAndDispatcher(effect);
+        long startTime = SystemClock.elapsedRealtime();
+
+        waitForCompletion(totalDuration + TEST_TIMEOUT_MILLIS);
+        long delay = Math.abs(SystemClock.elapsedRealtime() - startTime - totalDuration);
+
+        // Allow some delay for thread scheduling and callback triggering.
+        int maxDelay = (int) (0.05 * totalDuration); // < 5% of total duration
+        assertTrue("Waveform with perceived delay of " + delay + "ms,"
+                        + " expected less than " + maxDelay + "ms",
+                delay < maxDelay);
+    }
+
+    @LargeTest
+    @Test
+    public void vibrate_cancelSlowVibrator_cancelIsNotBlockedByVibrationThread() throws Exception {
+        FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
+        fakeVibrator.setSupportedEffects(VibrationEffect.EFFECT_CLICK);
+
+        long latency = 5_000; // 5s
+        fakeVibrator.setOnLatency(latency);
+
+        VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
+        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
+        long vibrationId = conductor.getVibration().id;
+
+        assertTrue(waitUntil(() -> !fakeVibrator.getEffectSegments(vibrationId).isEmpty(),
+                TEST_TIMEOUT_MILLIS));
+        assertTrue(mThread.isRunningVibrationId(vibrationId));
+
+        // Run cancel in a separate thread so if VibrationThread.cancel blocks then this test should
+        // fail at waitForCompletion(cancellingThread).
+        Thread cancellingThread = new Thread(
+                () -> conductor.notifyCancelled(
+                        new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_USER),
+                        /* immediate= */ false));
+        cancellingThread.start();
+
+        // Cancelling the vibration should be fast and return right away, even if the thread is
+        // stuck at the slow call to the vibrator.
+        waitForCompletion(/* timeout= */ 50);
+
+        // After the vibrator call ends the vibration is cancelled and the vibrator is turned off.
+        waitForCompletion(/* timeout= */ latency + TEST_TIMEOUT_MILLIS);
+        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_USER);
+        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
+    }
+
+    @Test
+    public void vibrate_multiplePredefinedCancel_cancelsVibrationImmediately() throws Exception {
+        mockVibrators(1, 2);
+        mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
+        mVibratorProviders.get(2).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+        mVibratorProviders.get(2).setSupportedPrimitives(
+                VibrationEffect.Composition.PRIMITIVE_CLICK);
+
+        CombinedVibration effect = CombinedVibration.startParallel()
+                .addVibrator(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
+                .addVibrator(2, VibrationEffect.startComposition()
+                        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 100)
+                        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 100)
+                        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 100)
+                        .compose())
+                .combine();
+        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
+        long vibrationId = conductor.getVibration().id;
+
+        assertTrue(waitUntil(() -> mControllers.get(2).isVibrating(),
+                TEST_TIMEOUT_MILLIS));
+        assertTrue(mThread.isRunningVibrationId(vibrationId));
+
+        // Run cancel in a separate thread so if VibrationThread.cancel blocks then this test should
+        // fail at waitForCompletion(vibrationThread) if the vibration not cancelled immediately.
+        Thread cancellingThread = new Thread(
+                () -> conductor.notifyCancelled(
+                        new Vibration.EndInfo(
+                                Vibration.Status.CANCELLED_BY_SCREEN_OFF),
+                        /* immediate= */ false));
+        cancellingThread.start();
+
+        waitForCompletion(/* timeout= */ 50);
+        cancellingThread.join();
+
+        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_SCREEN_OFF);
+        assertFalse(mControllers.get(1).isVibrating());
+        assertFalse(mControllers.get(2).isVibrating());
+    }
+
+    @Test
+    public void vibrate_multipleWaveformCancel_cancelsVibrationImmediately() throws Exception {
+        mockVibrators(1, 2);
+        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+        mVibratorProviders.get(2).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+
+        CombinedVibration effect = CombinedVibration.startParallel()
+                .addVibrator(1, VibrationEffect.createWaveform(
+                        new long[]{100, 100}, new int[]{1, 2}, 0))
+                .addVibrator(2, VibrationEffect.createOneShot(100, 100))
+                .combine();
+        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
+        long vibrationId = conductor.getVibration().id;
+
+        assertTrue(waitUntil(() -> mControllers.get(1).isVibrating()
+                        && mControllers.get(2).isVibrating(),
+                TEST_TIMEOUT_MILLIS));
+        assertTrue(mThread.isRunningVibrationId(vibrationId));
+
+        // Run cancel in a separate thread so if VibrationThread.cancel blocks then this test should
+        // fail at waitForCompletion(vibrationThread) if the vibration not cancelled immediately.
+        Thread cancellingThread = new Thread(
+                () -> conductor.notifyCancelled(
+                        new Vibration.EndInfo(
+                                Vibration.Status.CANCELLED_BY_SCREEN_OFF),
+                        /* immediate= */ false));
+        cancellingThread.start();
+
+        waitForCompletion(/* timeout= */ 50);
+        cancellingThread.join();
+
+        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_SCREEN_OFF);
+        assertFalse(mControllers.get(1).isVibrating());
+        assertFalse(mControllers.get(2).isVibrating());
+    }
+
+    @Test
+    public void vibrate_binderDied_cancelsVibration() throws Exception {
+        VibrationEffect effect = VibrationEffect.createWaveform(new long[]{5}, new int[]{100}, 0);
+        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
+        long vibrationId = conductor.getVibration().id;
+
+        assertTrue(waitUntil(() -> mControllers.get(VIBRATOR_ID).isVibrating(),
+                TEST_TIMEOUT_MILLIS));
+        assertTrue(mThread.isRunningVibrationId(vibrationId));
+
+        conductor.binderDied();
+        waitForCompletion();
+
+        verify(mVibrationToken).linkToDeath(same(conductor), eq(0));
+        verify(mVibrationToken).unlinkToDeath(same(conductor), eq(0));
+        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BINDER_DIED);
+        assertFalse(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId).isEmpty());
+        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
+    }
+
+    @Test
+    public void vibrate_waveformWithRampDown_addsRampDownAfterVibrationCompleted() {
+        when(mVibrationConfigMock.getRampDownDurationMs()).thenReturn(15);
+        mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+
+        VibrationEffect effect = VibrationEffect.createWaveform(
+                new long[]{5, 5, 5}, new int[]{60, 120, 240}, -1);
+        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
+        long vibrationId = conductor.getVibration().id;
+        waitForCompletion();
+
+        verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
+        verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
+
+        // Duration extended for 5 + 5 + 5 + 15.
+        assertEquals(Arrays.asList(expectedOneShot(30)),
+                mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId));
+        List<Float> amplitudes = mVibratorProviders.get(VIBRATOR_ID).getAmplitudes();
+        assertTrue(amplitudes.size() > 3);
+        assertEquals(expectedAmplitudes(60, 120, 240), amplitudes.subList(0, 3));
+        for (int i = 3; i < amplitudes.size(); i++) {
+            assertTrue(amplitudes.get(i) < amplitudes.get(i - 1));
+        }
+    }
+
+    @Test
+    public void vibrate_waveformWithRampDown_triggersCallbackWhenOriginalVibrationEnds() {
+        when(mVibrationConfigMock.getRampDownDurationMs()).thenReturn(10_000);
+        mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+
+        VibrationEffect effect = VibrationEffect.createOneShot(10, 200);
+        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
+        long vibrationId = conductor.getVibration().id;
+
+        // Vibration completed but vibrator not yet released.
+        verify(mManagerHooks, timeout(TEST_TIMEOUT_MILLIS)).onVibrationCompleted(eq(vibrationId),
+                eq(new Vibration.EndInfo(Vibration.Status.FINISHED)));
+        verify(mManagerHooks, never()).onVibrationThreadReleased(anyLong());
+
+        // Thread still running ramp down.
+        assertTrue(mThread.isRunningVibrationId(vibrationId));
+
+        // Duration extended for 10 + 10000.
+        assertEquals(Arrays.asList(expectedOneShot(10_010)),
+                mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId));
+
+        // Will stop the ramp down right away.
+        conductor.notifyCancelled(
+                new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_SETTINGS_UPDATE),
+                /* immediate= */ true);
+        waitForCompletion();
+
+        // Does not cancel already finished vibration, but releases vibrator.
+        verify(mManagerHooks, never()).onVibrationCompleted(eq(vibrationId),
+                eq(new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_SETTINGS_UPDATE)));
+        verify(mManagerHooks).onVibrationThreadReleased(vibrationId);
+    }
+
+    @Test
+    public void vibrate_waveformCancelledWithRampDown_addsRampDownAfterVibrationCancelled()
+            throws Exception {
+        when(mVibrationConfigMock.getRampDownDurationMs()).thenReturn(15);
+        mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+
+        VibrationEffect effect = VibrationEffect.createOneShot(10_000, 240);
+        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
+        long vibrationId = conductor.getVibration().id;
+        assertTrue(waitUntil(() -> mControllers.get(VIBRATOR_ID).isVibrating(),
+                TEST_TIMEOUT_MILLIS));
+        conductor.notifyCancelled(
+                new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_USER),
+                /* immediate= */ false);
+        waitForCompletion();
+
+        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_USER);
+
+        // Duration extended for 10000 + 15.
+        assertEquals(Arrays.asList(expectedOneShot(10_015)),
+                mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId));
+        List<Float> amplitudes = mVibratorProviders.get(VIBRATOR_ID).getAmplitudes();
+        assertTrue(amplitudes.size() > 1);
+        for (int i = 1; i < amplitudes.size(); i++) {
+            assertTrue(amplitudes.get(i) < amplitudes.get(i - 1));
+        }
+    }
+
+    @Test
+    public void vibrate_predefinedWithRampDown_doesNotAddRampDown() {
+        when(mVibrationConfigMock.getRampDownDurationMs()).thenReturn(15);
+        mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+        mVibratorProviders.get(VIBRATOR_ID).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
+
+        VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
+        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
+        long vibrationId = conductor.getVibration().id;
+        waitForCompletion();
+
+        verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
+        verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
+
+        assertEquals(Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_CLICK)),
+                mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId));
+        assertTrue(mVibratorProviders.get(VIBRATOR_ID).getAmplitudes().isEmpty());
+    }
+
+    @Test
+    public void vibrate_composedWithRampDown_doesNotAddRampDown() {
+        when(mVibrationConfigMock.getRampDownDurationMs()).thenReturn(15);
+        mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL,
+                IVibrator.CAP_COMPOSE_EFFECTS);
+        mVibratorProviders.get(VIBRATOR_ID).setSupportedPrimitives(
+                VibrationEffect.Composition.PRIMITIVE_CLICK);
+
+        VibrationEffect effect = VibrationEffect.startComposition()
+                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
+                .compose();
+        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
+        long vibrationId = conductor.getVibration().id;
+        waitForCompletion();
+
+        verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
+        verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
+
+        assertEquals(
+                Arrays.asList(expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0)),
+                mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId));
+        assertTrue(mVibratorProviders.get(VIBRATOR_ID).getAmplitudes().isEmpty());
+    }
+
+    @Test
+    public void vibrate_pwleWithRampDown_doesNotAddRampDown() {
+        when(mVibrationConfigMock.getRampDownDurationMs()).thenReturn(15);
+        FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
+        fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL,
+                IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
+        fakeVibrator.setMinFrequency(100);
+        fakeVibrator.setResonantFrequency(150);
+        fakeVibrator.setFrequencyResolution(50);
+        fakeVibrator.setMaxAmplitudes(1, 1, 1);
+        fakeVibrator.setPwleSizeMax(2);
+
+        VibrationEffect effect = VibrationEffect.startWaveform()
+                .addTransition(Duration.ofMillis(1), targetAmplitude(1))
+                .build();
+        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
+        long vibrationId = conductor.getVibration().id;
+        waitForCompletion();
+
+        verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
+        verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
+
+        assertEquals(Arrays.asList(expectedRamp(0, 1, 150, 150, 1)),
+                fakeVibrator.getEffectSegments(vibrationId));
+        assertTrue(fakeVibrator.getAmplitudes().isEmpty());
+    }
+
+    @Test
+    public void vibrate_multipleVibrations_withCancel() throws Exception {
+        mVibratorProviders.get(VIBRATOR_ID).setSupportedEffects(
+                VibrationEffect.EFFECT_CLICK, VibrationEffect.EFFECT_TICK);
+        mVibratorProviders.get(VIBRATOR_ID).setSupportedPrimitives(
+                VibrationEffect.Composition.PRIMITIVE_CLICK);
+        mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL,
+                IVibrator.CAP_COMPOSE_EFFECTS);
+
+        // A simple effect, followed by a repeating effect that gets cancelled, followed by another
+        // simple effect.
+        VibrationEffect effect1 = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
+        VibrationEffect effect2 = VibrationEffect.startComposition()
+                .repeatEffectIndefinitely(VibrationEffect.get(VibrationEffect.EFFECT_TICK))
+                .compose();
+        VibrationEffect effect3 = VibrationEffect.startComposition()
+                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
+                .compose();
+        VibrationEffect effect4 = VibrationEffect.createOneShot(8000, 100);
+        VibrationEffect effect5 = VibrationEffect.createOneShot(20, 222);
+
+        VibrationStepConductor conductor1 = startThreadAndDispatcher(effect1);
+        long vibrationId1 = conductor1.getVibration().id;
+        waitForCompletion();
+        verify(mControllerCallbacks).onComplete(VIBRATOR_ID, vibrationId1);
+        verifyCallbacksTriggered(vibrationId1, Vibration.Status.FINISHED);
+
+        VibrationStepConductor conductor2 = startThreadAndDispatcher(effect2);
+        long vibrationId2 = conductor2.getVibration().id;
+        // Effect2 won't complete on its own. Cancel it after a couple of repeats.
+        Thread.sleep(150);  // More than two TICKs.
+        conductor2.notifyCancelled(
+                new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_USER),
+                /* immediate= */ false);
+        waitForCompletion();
+
+        VibrationStepConductor conductor3 = startThreadAndDispatcher(effect3);
+        long vibrationId3 = conductor3.getVibration().id;
+        waitForCompletion();
+
+        // Effect4 is a long oneshot, but it gets cancelled as fast as possible.
+        long start4 = System.currentTimeMillis();
+        VibrationStepConductor conductor4 = startThreadAndDispatcher(effect4);
+        long vibrationId4 = conductor4.getVibration().id;
+        conductor4.notifyCancelled(
+                new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_SCREEN_OFF),
+                /* immediate= */ true);
+        waitForCompletion();
+        long duration4 = System.currentTimeMillis() - start4;
+
+        // Effect5 is to show that things keep going after the immediate cancel.
+        VibrationStepConductor conductor5 = startThreadAndDispatcher(effect5);
+        long vibrationId5 = conductor5.getVibration().id;
+        waitForCompletion();
+
+        FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
+        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
+
+        // Effect1
+        verify(mControllerCallbacks).onComplete(VIBRATOR_ID, vibrationId1);
+        verifyCallbacksTriggered(vibrationId1, Vibration.Status.FINISHED);
+
+        assertEquals(Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_CLICK)),
+                fakeVibrator.getEffectSegments(vibrationId1));
+
+        // Effect2: repeating, cancelled.
+        verify(mControllerCallbacks, atLeast(2)).onComplete(VIBRATOR_ID, vibrationId2);
+        verifyCallbacksTriggered(vibrationId2, Vibration.Status.CANCELLED_BY_USER);
+
+        // The exact count of segments might vary, so just check that there's more than 2 and
+        // all elements are the same segment.
+        List<VibrationEffectSegment> actualSegments2 = fakeVibrator.getEffectSegments(vibrationId2);
+        assertTrue(actualSegments2.size() + " > 2", actualSegments2.size() > 2);
+        for (VibrationEffectSegment segment : actualSegments2) {
+            assertEquals(expectedPrebaked(VibrationEffect.EFFECT_TICK), segment);
+        }
+
+        // Effect3
+        verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId3));
+        verifyCallbacksTriggered(vibrationId3, Vibration.Status.FINISHED);
+        assertEquals(Arrays.asList(
+                        expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0)),
+                fakeVibrator.getEffectSegments(vibrationId3));
+
+        // Effect4: cancelled quickly.
+        verifyCallbacksTriggered(vibrationId4, Vibration.Status.CANCELLED_BY_SCREEN_OFF);
+        assertTrue("Tested duration=" + duration4, duration4 < 2000);
+
+        // Effect5: normal oneshot. Don't worry about amplitude, as effect4 may or may not have
+        // started.
+
+        verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId5));
+        verifyCallbacksTriggered(vibrationId5, Vibration.Status.FINISHED);
+
+        assertEquals(Arrays.asList(expectedOneShot(20)),
+                fakeVibrator.getEffectSegments(vibrationId5));
+    }
+
+    private void mockVibrators(int... vibratorIds) {
+        for (int vibratorId : vibratorIds) {
+            mVibratorProviders.put(vibratorId,
+                    new FakeVibratorControllerProvider(mTestLooper.getLooper()));
+        }
+    }
+
+    private VibrationStepConductor startThreadAndDispatcher(VibrationEffect effect) {
+        return startThreadAndDispatcher(CombinedVibration.createParallel(effect));
+    }
+
+    private VibrationStepConductor startThreadAndDispatcher(CombinedVibration effect) {
+        return startThreadAndDispatcher(createVibration(effect));
+    }
+
+    private VibrationStepConductor startThreadAndDispatcher(HalVibration vib) {
+        mControllers = createVibratorControllers();
+        DeviceAdapter deviceAdapter = new DeviceAdapter(mVibrationSettings, mControllers);
+        VibrationStepConductor conductor =
+                new VibrationStepConductor(vib, mVibrationSettings, deviceAdapter, mManagerHooks);
+        doAnswer(answer -> {
+            conductor.notifyVibratorComplete(answer.getArgument(0));
+            return null;
+        }).when(mControllerCallbacks).onComplete(anyInt(), eq(vib.id));
+        assertTrue(mThread.runVibrationOnVibrationThread(conductor));
+        return conductor;
+    }
+
+    private boolean waitUntil(BooleanSupplier predicate, long timeout)
+            throws InterruptedException {
+        long timeoutTimestamp = SystemClock.uptimeMillis() + timeout;
+        boolean predicateResult = false;
+        while (!predicateResult && SystemClock.uptimeMillis() < timeoutTimestamp) {
+            Thread.sleep(10);
+            predicateResult = predicate.getAsBoolean();
+        }
+        return predicateResult;
+    }
+
+    private void waitForCompletion() {
+        waitForCompletion(TEST_TIMEOUT_MILLIS);
+    }
+
+    private void waitForCompletion(long timeout) {
+        mThread.waitForThreadIdle(timeout);
+        mTestLooper.dispatchAll();  // Flush callbacks
+    }
+
+    private HalVibration createVibration(CombinedVibration effect) {
+        return new HalVibration(mVibrationToken, effect,
+                new Vibration.CallerInfo(ATTRS, UID, DISPLAY_ID, PACKAGE_NAME, "reason"));
+    }
+
+    private SparseArray<VibratorController> createVibratorControllers() {
+        SparseArray<VibratorController> array = new SparseArray<>();
+        for (Map.Entry<Integer, FakeVibratorControllerProvider> e : mVibratorProviders.entrySet()) {
+            int id = e.getKey();
+            array.put(id, e.getValue().newVibratorController(id, mControllerCallbacks));
+        }
+        // Start a looper for the vibrationcontrollers if it's not already running.
+        // TestLooper.AutoDispatchThread has a fixed 1s duration. Use a custom auto-dispatcher.
+        if (mCustomTestLooperDispatcher == null) {
+            mCustomTestLooperDispatcher = new TestLooperAutoDispatcher(mTestLooper);
+            mCustomTestLooperDispatcher.start();
+        }
+        return array;
+    }
+
+    private VibrationEffectSegment expectedOneShot(long millis) {
+        return new StepSegment(VibrationEffect.DEFAULT_AMPLITUDE,
+                /* frequencyHz= */ 0, (int) millis);
+    }
+
+    private List<VibrationEffectSegment> expectedOneShots(long... millis) {
+        return Arrays.stream(millis)
+                .mapToObj(this::expectedOneShot)
+                .collect(Collectors.toList());
+    }
+
+    private VibrationEffectSegment expectedPrebaked(int effectId) {
+        return new PrebakedSegment(effectId, false, VibrationEffect.EFFECT_STRENGTH_MEDIUM);
+    }
+
+    private VibrationEffectSegment expectedPrimitive(int primitiveId, float scale, int delay) {
+        return new PrimitiveSegment(primitiveId, scale, delay);
+    }
+
+    private VibrationEffectSegment expectedRamp(float amplitude, float frequencyHz, int duration) {
+        return expectedRamp(amplitude, amplitude, frequencyHz, frequencyHz, duration);
+    }
+
+    private VibrationEffectSegment expectedRamp(float startAmplitude, float endAmplitude,
+            float startFrequencyHz, float endFrequencyHz, int duration) {
+        return new RampSegment(startAmplitude, endAmplitude, startFrequencyHz, endFrequencyHz,
+                duration);
+    }
+
+    private List<Float> expectedAmplitudes(int... amplitudes) {
+        return Arrays.stream(amplitudes)
+                .mapToObj(amplitude -> amplitude / 255f)
+                .collect(Collectors.toList());
+    }
+
+    private void verifyCallbacksTriggered(long vibrationId, Vibration.Status expectedStatus) {
+        verifyCallbacksTriggered(vibrationId, new Vibration.EndInfo(expectedStatus));
+    }
+
+    private void verifyCallbacksTriggered(long vibrationId, Vibration.EndInfo expectedEndInfo) {
+        verify(mManagerHooks).onVibrationCompleted(eq(vibrationId), eq(expectedEndInfo));
+        verify(mManagerHooks).onVibrationThreadReleased(vibrationId);
+    }
+
+    private static final class TestLooperAutoDispatcher extends Thread {
+        private final TestLooper mTestLooper;
+        private boolean mCancelled;
+
+        TestLooperAutoDispatcher(TestLooper testLooper) {
+            mTestLooper = testLooper;
+        }
+
+        @Override
+        public void run() {
+            while (!mCancelled) {
+                mTestLooper.dispatchAll();
+                try {
+                    Thread.sleep(10);
+                } catch (InterruptedException e) {
+                    return;
+                }
+            }
+        }
+
+        public void cancel() {
+            mCancelled = true;
+        }
+    }
+}
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorControllerTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControllerTest.java
new file mode 100644
index 0000000..0d13be6
--- /dev/null
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControllerTest.java
@@ -0,0 +1,319 @@
+/*
+ * 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.
+ */
+
+package com.android.server.vibrator;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.notNull;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.content.ContentResolver;
+import android.content.ContextWrapper;
+import android.hardware.vibrator.Braking;
+import android.hardware.vibrator.IVibrator;
+import android.os.IBinder;
+import android.os.IVibratorStateListener;
+import android.os.VibrationEffect;
+import android.os.VibratorInfo;
+import android.os.test.TestLooper;
+import android.os.vibrator.PrebakedSegment;
+import android.os.vibrator.PrimitiveSegment;
+import android.os.vibrator.RampSegment;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.internal.util.test.FakeSettingsProviderRule;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+public class VibratorControllerTest {
+    private static final int VIBRATOR_ID = 0;
+
+    @Rule public MockitoRule rule = MockitoJUnit.rule();
+    @Rule public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
+
+    @Mock private VibratorController.OnVibrationCompleteListener mOnCompleteListenerMock;
+    @Mock private VibratorController.NativeWrapper mNativeWrapperMock;
+    @Mock private IVibratorStateListener mVibratorStateListenerMock;
+    @Mock private IBinder mVibratorStateListenerBinderMock;
+
+    private TestLooper mTestLooper;
+    private ContextWrapper mContextSpy;
+
+    @Before
+    public void setUp() throws Exception {
+        mTestLooper = new TestLooper();
+        mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getContext()));
+
+        ContentResolver contentResolver = mSettingsProviderRule.mockContentResolver(mContextSpy);
+        when(mContextSpy.getContentResolver()).thenReturn(contentResolver);
+        when(mVibratorStateListenerMock.asBinder()).thenReturn(mVibratorStateListenerBinderMock);
+        mockVibratorCapabilities(0);
+    }
+
+    private VibratorController createController() {
+        return new VibratorController(VIBRATOR_ID, mOnCompleteListenerMock, mNativeWrapperMock);
+    }
+
+    @Test
+    public void createController_initializesNativeWrapper() {
+        VibratorController controller = createController();
+        assertEquals(VIBRATOR_ID, controller.getVibratorInfo().getId());
+        verify(mNativeWrapperMock).init(eq(VIBRATOR_ID), notNull());
+    }
+
+    @Test
+    public void isAvailable_withVibratorHalPresent_returnsTrue() {
+        when(mNativeWrapperMock.isAvailable()).thenReturn(true);
+        assertTrue(createController().isAvailable());
+    }
+
+    @Test
+    public void isAvailable_withNoVibratorHalPresent_returnsFalse() {
+        when(mNativeWrapperMock.isAvailable()).thenReturn(false);
+        assertFalse(createController().isAvailable());
+    }
+
+    @Test
+    public void hasCapability_withSupport_returnsTrue() {
+        mockVibratorCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+        assertTrue(createController().hasCapability(IVibrator.CAP_AMPLITUDE_CONTROL));
+    }
+
+    @Test
+    public void hasCapability_withNoSupport_returnsFalse() {
+        assertFalse(createController().hasCapability(IVibrator.CAP_ALWAYS_ON_CONTROL));
+        assertFalse(createController().hasCapability(IVibrator.CAP_AMPLITUDE_CONTROL));
+        assertFalse(createController().hasCapability(IVibrator.CAP_COMPOSE_EFFECTS));
+        assertFalse(createController().hasCapability(IVibrator.CAP_EXTERNAL_CONTROL));
+        assertFalse(createController().hasCapability(IVibrator.CAP_ON_CALLBACK));
+    }
+
+    @Test
+    public void setExternalControl_withCapability_enablesExternalControl() {
+        mockVibratorCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
+        VibratorController controller = createController();
+        assertFalse(controller.isUnderExternalControl());
+
+        controller.setExternalControl(true);
+        assertTrue(controller.isUnderExternalControl());
+
+        controller.setExternalControl(false);
+        assertFalse(controller.isUnderExternalControl());
+
+        InOrder inOrderVerifier = inOrder(mNativeWrapperMock);
+        inOrderVerifier.verify(mNativeWrapperMock).setExternalControl(eq(true));
+        inOrderVerifier.verify(mNativeWrapperMock).setExternalControl(eq(false));
+    }
+
+    @Test
+    public void setExternalControl_withNoCapability_ignoresExternalControl() {
+        VibratorController controller = createController();
+        assertFalse(controller.isUnderExternalControl());
+
+        controller.setExternalControl(true);
+        assertFalse(controller.isUnderExternalControl());
+
+        verify(mNativeWrapperMock, never()).setExternalControl(anyBoolean());
+    }
+
+    @Test
+    public void updateAlwaysOn_withCapability_enablesAlwaysOnEffect() {
+        mockVibratorCapabilities(IVibrator.CAP_ALWAYS_ON_CONTROL);
+        PrebakedSegment prebaked = createPrebaked(VibrationEffect.EFFECT_CLICK,
+                VibrationEffect.EFFECT_STRENGTH_MEDIUM);
+        createController().updateAlwaysOn(1, prebaked);
+
+        verify(mNativeWrapperMock).alwaysOnEnable(
+                eq(1L), eq((long) VibrationEffect.EFFECT_CLICK),
+                eq((long) VibrationEffect.EFFECT_STRENGTH_MEDIUM));
+    }
+
+    @Test
+    public void updateAlwaysOn_withNullEffect_disablesAlwaysOnEffect() {
+        mockVibratorCapabilities(IVibrator.CAP_ALWAYS_ON_CONTROL);
+        createController().updateAlwaysOn(1, null);
+        verify(mNativeWrapperMock).alwaysOnDisable(eq(1L));
+    }
+
+    @Test
+    public void updateAlwaysOn_withoutCapability_ignoresEffect() {
+        PrebakedSegment prebaked = createPrebaked(VibrationEffect.EFFECT_CLICK,
+                VibrationEffect.EFFECT_STRENGTH_MEDIUM);
+        createController().updateAlwaysOn(1, prebaked);
+
+        verify(mNativeWrapperMock, never()).alwaysOnDisable(anyLong());
+        verify(mNativeWrapperMock, never()).alwaysOnEnable(anyLong(), anyLong(), anyLong());
+    }
+
+    @Test
+    public void on_withDuration_turnsVibratorOn() {
+        when(mNativeWrapperMock.on(anyLong(), anyLong())).thenAnswer(args -> args.getArgument(0));
+        VibratorController controller = createController();
+        controller.on(100, 10);
+
+        assertTrue(controller.isVibrating());
+        verify(mNativeWrapperMock).on(eq(100L), eq(10L));
+    }
+
+    @Test
+    public void on_withPrebaked_performsEffect() {
+        when(mNativeWrapperMock.perform(anyLong(), anyLong(), anyLong())).thenReturn(10L);
+        VibratorController controller = createController();
+
+        PrebakedSegment prebaked = createPrebaked(VibrationEffect.EFFECT_CLICK,
+                VibrationEffect.EFFECT_STRENGTH_MEDIUM);
+        assertEquals(10L, controller.on(prebaked, 11));
+
+        assertTrue(controller.isVibrating());
+        verify(mNativeWrapperMock).perform(eq((long) VibrationEffect.EFFECT_CLICK),
+                eq((long) VibrationEffect.EFFECT_STRENGTH_MEDIUM), eq(11L));
+    }
+
+    @Test
+    public void on_withComposed_performsEffect() {
+        mockVibratorCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+        when(mNativeWrapperMock.compose(any(), anyLong())).thenReturn(15L);
+        VibratorController controller = createController();
+
+        PrimitiveSegment[] primitives = new PrimitiveSegment[]{
+                new PrimitiveSegment(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f, 10)
+        };
+        assertEquals(15L, controller.on(primitives, 12));
+
+        assertTrue(controller.isVibrating());
+        verify(mNativeWrapperMock).compose(eq(primitives), eq(12L));
+    }
+
+    @Test
+    public void on_withComposedPwle_performsEffect() {
+        mockVibratorCapabilities(IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
+        when(mNativeWrapperMock.composePwle(any(), anyInt(), anyLong())).thenReturn(15L);
+        VibratorController controller = createController();
+
+        RampSegment[] primitives = new RampSegment[]{
+                new RampSegment(/* startAmplitude= */ 0, /* endAmplitude= */ 1,
+                        /* startFrequencyHz= */ 100, /* endFrequencyHz= */ 200, /* duration= */ 10)
+        };
+        assertEquals(15L, controller.on(primitives, 12));
+        assertTrue(controller.isVibrating());
+
+        verify(mNativeWrapperMock).composePwle(eq(primitives), eq(Braking.NONE), eq(12L));
+    }
+
+    @Test
+    public void off_turnsOffVibrator() {
+        when(mNativeWrapperMock.on(anyLong(), anyLong())).thenAnswer(args -> args.getArgument(0));
+        VibratorController controller = createController();
+
+        controller.on(100, 1);
+        assertTrue(controller.isVibrating());
+
+        controller.off();
+        controller.off();
+        assertFalse(controller.isVibrating());
+        verify(mNativeWrapperMock, times(2)).off();
+    }
+
+    @Test
+    public void reset_turnsOffVibratorAndDisablesExternalControl() {
+        mockVibratorCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
+        when(mNativeWrapperMock.on(anyLong(), anyLong())).thenAnswer(args -> args.getArgument(0));
+        VibratorController controller = createController();
+
+        controller.on(100, 1);
+        assertTrue(controller.isVibrating());
+
+        controller.reset();
+        assertFalse(controller.isVibrating());
+        verify(mNativeWrapperMock).setExternalControl(eq(false));
+        verify(mNativeWrapperMock).off();
+    }
+
+    @Test
+    public void registerVibratorStateListener_callbacksAreTriggered() throws Exception {
+        when(mNativeWrapperMock.on(anyLong(), anyLong())).thenAnswer(args -> args.getArgument(0));
+        VibratorController controller = createController();
+
+        controller.registerVibratorStateListener(mVibratorStateListenerMock);
+        controller.on(10, 1);
+        controller.on(100, 2);
+        controller.off();
+        controller.off();
+
+        InOrder inOrderVerifier = inOrder(mVibratorStateListenerMock);
+        // First notification done when listener is registered.
+        inOrderVerifier.verify(mVibratorStateListenerMock).onVibrating(false);
+        inOrderVerifier.verify(mVibratorStateListenerMock).onVibrating(eq(true));
+        inOrderVerifier.verify(mVibratorStateListenerMock).onVibrating(eq(false));
+        inOrderVerifier.verifyNoMoreInteractions();
+    }
+
+    @Test
+    public void unregisterVibratorStateListener_callbackNotTriggeredAfter() throws Exception {
+        when(mNativeWrapperMock.on(anyLong(), anyLong())).thenAnswer(args -> args.getArgument(0));
+        VibratorController controller = createController();
+
+        controller.registerVibratorStateListener(mVibratorStateListenerMock);
+        verify(mVibratorStateListenerMock).onVibrating(false);
+
+        controller.on(10, 1);
+        verify(mVibratorStateListenerMock).onVibrating(true);
+
+        controller.unregisterVibratorStateListener(mVibratorStateListenerMock);
+        Mockito.clearInvocations(mVibratorStateListenerMock);
+
+        controller.on(10, 1);
+        verifyNoMoreInteractions(mVibratorStateListenerMock);
+    }
+
+    private void mockVibratorCapabilities(int capabilities) {
+        VibratorInfo.FrequencyProfile frequencyProfile = new VibratorInfo.FrequencyProfile(
+                Float.NaN, Float.NaN, Float.NaN, null);
+        when(mNativeWrapperMock.getInfo(any(VibratorInfo.Builder.class)))
+                .then(invocation -> {
+                    ((VibratorInfo.Builder) invocation.getArgument(0))
+                            .setCapabilities(capabilities)
+                            .setFrequencyProfile(frequencyProfile);
+                    return true;
+                });
+    }
+
+    private PrebakedSegment createPrebaked(int effectId, int effectStrength) {
+        return new PrebakedSegment(effectId, /* shouldFallback= */ false, effectStrength);
+    }
+}
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorFrameworkStatsLoggerTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorFrameworkStatsLoggerTest.java
new file mode 100644
index 0000000..3466bbb
--- /dev/null
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorFrameworkStatsLoggerTest.java
@@ -0,0 +1,119 @@
+/*
+ * 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.vibrator;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.os.Handler;
+import android.os.test.TestLooper;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+public class VibratorFrameworkStatsLoggerTest {
+
+    @Rule public MockitoRule rule = MockitoJUnit.rule();
+
+    private TestLooper mTestLooper;
+    private VibratorFrameworkStatsLogger mLogger;
+
+    @Before
+    public void setUp() {
+        mTestLooper = new TestLooper();
+    }
+
+    @Test
+    public void writeVibrationReportedAsync_afterMinInterval_writesRightAway() {
+        setUpLogger(/* minIntervalMillis= */ 10, /* queueMaxSize= */ 10);
+
+        VibrationStats.StatsInfo firstStats = newEmptyStatsInfo();
+        assertFalse(firstStats.isWritten());
+
+        mLogger.writeVibrationReportedAsync(firstStats);
+        mTestLooper.dispatchAll();
+        assertTrue(firstStats.isWritten());
+    }
+
+    @Test
+    public void writeVibrationReportedAsync_rightAfterLogging_schedulesToRunAfterRemainingDelay() {
+        setUpLogger(/* minIntervalMillis= */ 100, /* queueMaxSize= */ 10);
+
+        VibrationStats.StatsInfo firstStats = newEmptyStatsInfo();
+        VibrationStats.StatsInfo secondStats = newEmptyStatsInfo();
+        assertFalse(firstStats.isWritten());
+        assertFalse(secondStats.isWritten());
+
+        // Write first message at current SystemClock.uptimeMillis
+        mLogger.writeVibrationReportedAsync(firstStats);
+        mTestLooper.dispatchAll();
+        assertTrue(firstStats.isWritten());
+
+        // Second message is not written right away, it needs to wait the configured interval.
+        mLogger.writeVibrationReportedAsync(secondStats);
+        mTestLooper.dispatchAll();
+        assertFalse(secondStats.isWritten());
+
+        // Second message is written after delay passes.
+        mTestLooper.moveTimeForward(100);
+        mTestLooper.dispatchAll();
+        assertTrue(secondStats.isWritten());
+    }
+
+    @Test
+    public void writeVibrationReportedAsync_tooFast_logsUsingIntervalAndDropsMessagesFromQueue() {
+        setUpLogger(/* minIntervalMillis= */ 100, /* queueMaxSize= */ 2);
+
+        VibrationStats.StatsInfo firstStats = newEmptyStatsInfo();
+        VibrationStats.StatsInfo secondStats = newEmptyStatsInfo();
+        VibrationStats.StatsInfo thirdStats = newEmptyStatsInfo();
+
+        mLogger.writeVibrationReportedAsync(firstStats);
+        mLogger.writeVibrationReportedAsync(secondStats);
+        mLogger.writeVibrationReportedAsync(thirdStats);
+
+        // Only first message is logged.
+        mTestLooper.dispatchAll();
+        assertTrue(firstStats.isWritten());
+        assertFalse(secondStats.isWritten());
+        assertFalse(thirdStats.isWritten());
+
+        // Wait one interval to check only the second one is logged.
+        mTestLooper.moveTimeForward(100);
+        mTestLooper.dispatchAll();
+        assertTrue(secondStats.isWritten());
+        assertFalse(thirdStats.isWritten());
+
+        // Wait a long interval to check the third one was dropped and will never be logged.
+        mTestLooper.moveTimeForward(1_000);
+        mTestLooper.dispatchAll();
+        assertFalse(thirdStats.isWritten());
+    }
+
+    private void setUpLogger(int minIntervalMillis, int queueMaxSize) {
+        mLogger = new VibratorFrameworkStatsLogger(new Handler(mTestLooper.getLooper()),
+                minIntervalMillis, queueMaxSize);
+    }
+
+    private static VibrationStats.StatsInfo newEmptyStatsInfo() {
+        return new VibrationStats.StatsInfo(
+                0, 0, 0, Vibration.Status.FINISHED, new VibrationStats(), 0L);
+    }
+}
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
new file mode 100644
index 0000000..c6cd078
--- /dev/null
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -0,0 +1,2272 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.vibrator;
+
+import static org.junit.Assert.assertArrayEquals;
+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.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+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.atLeastOnce;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.AppOpsManager;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.pm.PackageManagerInternal;
+import android.hardware.input.IInputManager;
+import android.hardware.input.InputManager;
+import android.hardware.input.InputManagerGlobal;
+import android.hardware.vibrator.IVibrator;
+import android.hardware.vibrator.IVibratorManager;
+import android.media.AudioAttributes;
+import android.media.AudioManager;
+import android.os.CombinedVibration;
+import android.os.ExternalVibration;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.IExternalVibrationController;
+import android.os.IExternalVibratorService;
+import android.os.IVibratorStateListener;
+import android.os.Looper;
+import android.os.PowerManager;
+import android.os.PowerManagerInternal;
+import android.os.PowerSaveState;
+import android.os.Process;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.os.VibrationAttributes;
+import android.os.VibrationEffect;
+import android.os.Vibrator;
+import android.os.VibratorInfo;
+import android.os.test.FakeVibrator;
+import android.os.test.TestLooper;
+import android.os.vibrator.PrebakedSegment;
+import android.os.vibrator.PrimitiveSegment;
+import android.os.vibrator.StepSegment;
+import android.os.vibrator.VibrationConfig;
+import android.os.vibrator.VibrationEffectSegment;
+import android.provider.Settings;
+import android.util.ArraySet;
+import android.util.SparseBooleanArray;
+import android.view.Display;
+import android.view.InputDevice;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.internal.app.IBatteryStats;
+import com.android.internal.util.FrameworkStatsLog;
+import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.internal.util.test.FakeSettingsProviderRule;
+import com.android.server.LocalServices;
+import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.time.Duration;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Predicate;
+
+public class VibratorManagerServiceTest {
+
+    private static final int TEST_TIMEOUT_MILLIS = 1_000;
+    // Time to allow for a cancellation to complete (notably including system ramp down), but not so
+    // long that tests easily get really slow or flaky. If a vibration is close to this, it should
+    // be cancelled in the body of the individual test.
+    private static final int CLEANUP_TIMEOUT_MILLIS = 100;
+    private static final int UID = Process.ROOT_UID;
+    private static final int VIRTUAL_DISPLAY_ID = 1;
+    private static final String PACKAGE_NAME = "package";
+    private static final PowerSaveState NORMAL_POWER_STATE = new PowerSaveState.Builder().build();
+    private static final PowerSaveState LOW_POWER_STATE = new PowerSaveState.Builder()
+            .setBatterySaverEnabled(true).build();
+    private static final AudioAttributes AUDIO_ALARM_ATTRS =
+            new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_ALARM).build();
+    private static final AudioAttributes AUDIO_NOTIFICATION_ATTRS =
+            new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_NOTIFICATION).build();
+    private static final VibrationAttributes ALARM_ATTRS =
+            new VibrationAttributes.Builder().setUsage(VibrationAttributes.USAGE_ALARM).build();
+    private static final VibrationAttributes HAPTIC_FEEDBACK_ATTRS =
+            new VibrationAttributes.Builder().setUsage(VibrationAttributes.USAGE_TOUCH).build();
+    private static final VibrationAttributes NOTIFICATION_ATTRS =
+            new VibrationAttributes.Builder().setUsage(
+                    VibrationAttributes.USAGE_NOTIFICATION).build();
+    private static final VibrationAttributes RINGTONE_ATTRS =
+            new VibrationAttributes.Builder().setUsage(
+                    VibrationAttributes.USAGE_RINGTONE).build();
+    private static final VibrationAttributes UNKNOWN_ATTRS =
+            new VibrationAttributes.Builder().setUsage(VibrationAttributes.USAGE_UNKNOWN).build();
+
+    @Rule
+    public MockitoRule rule = MockitoJUnit.rule();
+    @Rule
+    public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
+
+    @Mock
+    private VibratorManagerService.NativeWrapper mNativeWrapperMock;
+    @Mock
+    private PackageManagerInternal mPackageManagerInternalMock;
+    @Mock
+    private PowerManagerInternal mPowerManagerInternalMock;
+    @Mock
+    private PowerSaveState mPowerSaveStateMock;
+    @Mock
+    private AppOpsManager mAppOpsManagerMock;
+    @Mock
+    private IInputManager mIInputManagerMock;
+    @Mock
+    private IBatteryStats mBatteryStatsMock;
+    @Mock
+    private VibratorFrameworkStatsLogger mVibratorFrameworkStatsLoggerMock;
+    @Mock
+    private VirtualDeviceManagerInternal mVirtualDeviceManagerInternalMock;
+    @Mock
+    private AudioManager mAudioManagerMock;
+
+    private final Map<Integer, FakeVibratorControllerProvider> mVibratorProviders = new HashMap<>();
+
+    private VibratorManagerService mService;
+    private Context mContextSpy;
+    private TestLooper mTestLooper;
+    private FakeVibrator mVibrator;
+    private PowerManagerInternal.LowPowerModeListener mRegisteredPowerModeListener;
+    private VibratorManagerService.ExternalVibratorService mExternalVibratorService;
+    private VibrationConfig mVibrationConfig;
+    private VirtualDeviceManagerInternal.VirtualDisplayListener mRegisteredVirtualDisplayListener;
+    private VirtualDeviceManagerInternal.AppsOnVirtualDeviceListener
+            mRegisteredAppsOnVirtualDeviceListener;
+    private InputManagerGlobal.TestSession mInputManagerGlobalSession;
+    private InputManager mInputManager;
+
+    @Before
+    public void setUp() throws Exception {
+        mTestLooper = new TestLooper();
+        mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getContext()));
+        mInputManagerGlobalSession = InputManagerGlobal.createTestSession(mIInputManagerMock);
+        mVibrationConfig = new VibrationConfig(mContextSpy.getResources());
+
+        ContentResolver contentResolver = mSettingsProviderRule.mockContentResolver(mContextSpy);
+        when(mContextSpy.getContentResolver()).thenReturn(contentResolver);
+
+        mVibrator = new FakeVibrator(mContextSpy);
+        when(mContextSpy.getSystemService(eq(Context.VIBRATOR_SERVICE))).thenReturn(mVibrator);
+
+        mInputManager = new InputManager(mContextSpy);
+        when(mContextSpy.getSystemService(eq(Context.INPUT_SERVICE)))
+                .thenReturn(mInputManager);
+        when(mContextSpy.getSystemService(Context.APP_OPS_SERVICE)).thenReturn(mAppOpsManagerMock);
+        when(mContextSpy.getSystemService(eq(Context.AUDIO_SERVICE))).thenReturn(mAudioManagerMock);
+        when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[0]);
+        when(mPackageManagerInternalMock.getSystemUiServiceComponent())
+                .thenReturn(new ComponentName("", ""));
+        when(mPowerManagerInternalMock.getLowPowerState(PowerManager.ServiceType.VIBRATION))
+                .thenReturn(mPowerSaveStateMock);
+        doAnswer(invocation -> {
+            mRegisteredPowerModeListener = invocation.getArgument(0);
+            return null;
+        }).when(mPowerManagerInternalMock).registerLowPowerModeObserver(any());
+        doAnswer(invocation -> {
+            mRegisteredVirtualDisplayListener = invocation.getArgument(0);
+            return null;
+        }).when(mVirtualDeviceManagerInternalMock).registerVirtualDisplayListener(any());
+        doAnswer(invocation -> {
+            mRegisteredAppsOnVirtualDeviceListener = invocation.getArgument(0);
+            return null;
+        }).when(mVirtualDeviceManagerInternalMock).registerAppsOnVirtualDeviceListener(any());
+
+        setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 1);
+        setUserSetting(Settings.System.HAPTIC_FEEDBACK_ENABLED, 1);
+        setUserSetting(Settings.System.ALARM_VIBRATION_INTENSITY,
+                Vibrator.VIBRATION_INTENSITY_MEDIUM);
+        setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
+                Vibrator.VIBRATION_INTENSITY_MEDIUM);
+        setUserSetting(Settings.System.MEDIA_VIBRATION_INTENSITY,
+                Vibrator.VIBRATION_INTENSITY_MEDIUM);
+        setUserSetting(Settings.System.RING_VIBRATION_INTENSITY,
+                Vibrator.VIBRATION_INTENSITY_MEDIUM);
+        setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY,
+                Vibrator.VIBRATION_INTENSITY_MEDIUM);
+        setRingerMode(AudioManager.RINGER_MODE_NORMAL);
+        addLocalServiceMock(PackageManagerInternal.class, mPackageManagerInternalMock);
+        addLocalServiceMock(PowerManagerInternal.class, mPowerManagerInternalMock);
+        addLocalServiceMock(VirtualDeviceManagerInternal.class, mVirtualDeviceManagerInternalMock);
+
+        mTestLooper.startAutoDispatch();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        if (mService != null) {
+            // Wait until all vibrators have stopped vibrating, with a bit of flexibility for tests
+            // that just do a click or have cancelled at the end (waiting for ramp-down).
+            //
+            // Note: if a test is flaky here, check whether a VibrationEffect duration is close to
+            // CLEANUP_TIMEOUT_MILLIS - in which case it's probably best to just cancel that effect
+            // explicitly at the end of the test case (rather than letting it run and race flakily).
+            assertTrue(waitUntil(s -> {
+                for (int vibratorId : mService.getVibratorIds()) {
+                    if (s.isVibrating(vibratorId)) {
+                        return false;
+                    }
+                }
+                return true;
+            }, mService, CLEANUP_TIMEOUT_MILLIS));
+        }
+
+        LocalServices.removeServiceForTest(PackageManagerInternal.class);
+        LocalServices.removeServiceForTest(PowerManagerInternal.class);
+        // Ignore potential exceptions about the looper having never dispatched any messages.
+        mTestLooper.stopAutoDispatchAndIgnoreExceptions();
+        if (mInputManagerGlobalSession != null) {
+            mInputManagerGlobalSession.close();
+        }
+    }
+
+    private VibratorManagerService createSystemReadyService() {
+        VibratorManagerService service = createService();
+        service.systemReady();
+        return service;
+    }
+
+    private VibratorManagerService createService() {
+        mService = new VibratorManagerService(
+                mContextSpy,
+                new VibratorManagerService.Injector() {
+                    @Override
+                    VibratorManagerService.NativeWrapper getNativeWrapper() {
+                        return mNativeWrapperMock;
+                    }
+
+                    @Override
+                    Handler createHandler(Looper looper) {
+                        return new Handler(mTestLooper.getLooper());
+                    }
+
+                    @Override
+                    IBatteryStats getBatteryStatsService() {
+                        return mBatteryStatsMock;
+                    }
+
+                    @Override
+                    VibratorFrameworkStatsLogger getFrameworkStatsLogger(Handler handler) {
+                        return mVibratorFrameworkStatsLoggerMock;
+                    }
+
+                    @Override
+                    VibratorController createVibratorController(int vibratorId,
+                            VibratorController.OnVibrationCompleteListener listener) {
+                        return mVibratorProviders.get(vibratorId)
+                                .newVibratorController(vibratorId, listener);
+                    }
+
+                    @Override
+                    void addService(String name, IBinder service) {
+                        Object serviceInstance = service;
+                        mExternalVibratorService =
+                                (VibratorManagerService.ExternalVibratorService) serviceInstance;
+                    }
+                });
+        return mService;
+    }
+
+    @Test
+    public void createService_initializesNativeManagerServiceAndVibrators() {
+        mockVibrators(1, 2);
+        createService();
+        verify(mNativeWrapperMock).init(any());
+        assertTrue(mVibratorProviders.get(1).isInitialized());
+        assertTrue(mVibratorProviders.get(2).isInitialized());
+    }
+
+    @Test
+    public void createService_resetsVibrators() {
+        mockVibrators(1, 2);
+        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
+        mVibratorProviders.get(2).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
+
+        createService();
+        assertEquals(1, mVibratorProviders.get(1).getOffCount());
+        assertEquals(1, mVibratorProviders.get(2).getOffCount());
+        assertEquals(Arrays.asList(false), mVibratorProviders.get(1).getExternalControlStates());
+        assertEquals(Arrays.asList(false), mVibratorProviders.get(2).getExternalControlStates());
+    }
+
+    @Test
+    public void createService_doNotCrashIfUsedBeforeSystemReady() {
+        mockVibrators(1, 2);
+        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_ALWAYS_ON_CONTROL);
+        mVibratorProviders.get(2).setCapabilities(IVibrator.CAP_ALWAYS_ON_CONTROL);
+        VibratorManagerService service = createService();
+
+        assertNotNull(service.getVibratorIds());
+        assertNotNull(service.getVibratorInfo(1));
+        assertFalse(service.isVibrating(1));
+
+        CombinedVibration effect = CombinedVibration.createParallel(
+                VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK));
+        vibrate(service, effect, HAPTIC_FEEDBACK_ATTRS);
+        service.cancelVibrate(VibrationAttributes.USAGE_FILTER_MATCH_ALL, service);
+
+        assertTrue(service.setAlwaysOnEffect(UID, PACKAGE_NAME, 1, effect, ALARM_ATTRS));
+
+        IVibratorStateListener listener = mockVibratorStateListener();
+        assertTrue(service.registerVibratorStateListener(1, listener));
+        assertTrue(service.unregisterVibratorStateListener(1, listener));
+    }
+
+    @Test
+    public void getVibratorIds_withNullResultFromNative_returnsEmptyArray() {
+        when(mNativeWrapperMock.getVibratorIds()).thenReturn(null);
+        assertArrayEquals(new int[0], createSystemReadyService().getVibratorIds());
+    }
+
+    @Test
+    public void getVibratorIds_withNonEmptyResultFromNative_returnsSameArray() {
+        mockVibrators(2, 1);
+        assertArrayEquals(new int[]{2, 1}, createSystemReadyService().getVibratorIds());
+    }
+
+    @Test
+    public void getVibratorInfo_withMissingVibratorId_returnsNull() {
+        mockVibrators(1);
+        assertNull(createSystemReadyService().getVibratorInfo(2));
+    }
+
+    @Test
+    public void getVibratorInfo_vibratorFailedLoadBeforeSystemReady_returnsNull() {
+        mockVibrators(1);
+        mVibratorProviders.get(1).setVibratorInfoLoadSuccessful(false);
+        assertNull(createService().getVibratorInfo(1));
+    }
+
+    @Test
+    public void getVibratorInfo_vibratorFailedLoadAfterSystemReady_returnsInfoForVibrator() {
+        mockVibrators(1);
+        mVibratorProviders.get(1).setVibratorInfoLoadSuccessful(false);
+        mVibratorProviders.get(1).setResonantFrequency(123.f);
+        VibratorInfo info = createSystemReadyService().getVibratorInfo(1);
+
+        assertNotNull(info);
+        assertEquals(1, info.getId());
+        assertEquals(123.f, info.getResonantFrequencyHz(), 0.01 /*tolerance*/);
+    }
+
+    @Test
+    public void getVibratorInfo_vibratorSuccessfulLoadBeforeSystemReady_returnsInfoForVibrator() {
+        mockVibrators(1);
+        FakeVibratorControllerProvider vibrator = mVibratorProviders.get(1);
+        vibrator.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS, IVibrator.CAP_AMPLITUDE_CONTROL);
+        vibrator.setSupportedEffects(VibrationEffect.EFFECT_CLICK);
+        vibrator.setSupportedPrimitives(VibrationEffect.Composition.PRIMITIVE_CLICK);
+        vibrator.setResonantFrequency(123.f);
+        vibrator.setQFactor(Float.NaN);
+        VibratorInfo info = createService().getVibratorInfo(1);
+
+        assertNotNull(info);
+        assertEquals(1, info.getId());
+        assertTrue(info.hasAmplitudeControl());
+        assertTrue(info.hasCapability(IVibrator.CAP_COMPOSE_EFFECTS));
+        assertFalse(info.hasCapability(IVibrator.CAP_ON_CALLBACK));
+        assertEquals(Vibrator.VIBRATION_EFFECT_SUPPORT_YES,
+                info.isEffectSupported(VibrationEffect.EFFECT_CLICK));
+        assertEquals(Vibrator.VIBRATION_EFFECT_SUPPORT_NO,
+                info.isEffectSupported(VibrationEffect.EFFECT_TICK));
+        assertTrue(info.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_CLICK));
+        assertFalse(info.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_TICK));
+        assertEquals(123.f, info.getResonantFrequencyHz(), 0.01 /*tolerance*/);
+        assertTrue(Float.isNaN(info.getQFactor()));
+    }
+
+    @Test
+    public void getVibratorInfo_vibratorFailedThenSuccessfulLoad_returnsNullThenInfo() {
+        mockVibrators(1);
+        mVibratorProviders.get(1).setVibratorInfoLoadSuccessful(false);
+
+        VibratorManagerService service = createService();
+        assertNull(createService().getVibratorInfo(1));
+
+        mVibratorProviders.get(1).setVibratorInfoLoadSuccessful(true);
+        mVibratorProviders.get(1).setResonantFrequency(123.f);
+        service.systemReady();
+
+        VibratorInfo info = createService().getVibratorInfo(1);
+        assertNotNull(info);
+        assertEquals(1, info.getId());
+        assertEquals(123.f, info.getResonantFrequencyHz(), 0.01 /*tolerance*/);
+    }
+
+    @Test
+    public void registerVibratorStateListener_callbacksAreTriggered() throws Exception {
+        mockVibrators(1);
+        VibratorManagerService service = createSystemReadyService();
+        IVibratorStateListener listenerMock = mockVibratorStateListener();
+        service.registerVibratorStateListener(1, listenerMock);
+
+        long oneShotDuration = 20;
+        vibrateAndWaitUntilFinished(service,
+                VibrationEffect.createOneShot(oneShotDuration, VibrationEffect.DEFAULT_AMPLITUDE),
+                ALARM_ATTRS);
+
+        InOrder inOrderVerifier = inOrder(listenerMock);
+        // First notification done when listener is registered.
+        inOrderVerifier.verify(listenerMock).onVibrating(eq(false));
+        inOrderVerifier.verify(listenerMock).onVibrating(eq(true));
+        // The last notification is after the vibration has completed.
+        inOrderVerifier.verify(listenerMock, timeout(TEST_TIMEOUT_MILLIS)).onVibrating(eq(false));
+        inOrderVerifier.verifyNoMoreInteractions();
+
+        InOrder batteryVerifier = inOrder(mBatteryStatsMock);
+        batteryVerifier.verify(mBatteryStatsMock)
+                .noteVibratorOn(UID, oneShotDuration + mVibrationConfig.getRampDownDurationMs());
+        batteryVerifier.verify(mBatteryStatsMock).noteVibratorOff(UID);
+    }
+
+    @Test
+    public void unregisterVibratorStateListener_callbackNotTriggeredAfter() throws Exception {
+        mockVibrators(1);
+        VibratorManagerService service = createSystemReadyService();
+        IVibratorStateListener listenerMock = mockVibratorStateListener();
+        service.registerVibratorStateListener(1, listenerMock);
+
+        vibrate(service, VibrationEffect.createOneShot(40, 100), ALARM_ATTRS);
+
+        // Wait until service knows vibrator is on.
+        assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
+
+        service.unregisterVibratorStateListener(1, listenerMock);
+
+        // Wait until vibrator is off.
+        assertTrue(waitUntil(s -> !s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
+
+        InOrder inOrderVerifier = inOrder(listenerMock);
+        // First notification done when listener is registered.
+        inOrderVerifier.verify(listenerMock).onVibrating(eq(false));
+        inOrderVerifier.verify(listenerMock).onVibrating(eq(true));
+        inOrderVerifier.verify(listenerMock, atLeastOnce()).asBinder(); // unregister
+        inOrderVerifier.verifyNoMoreInteractions();
+    }
+
+    @Test
+    public void registerVibratorStateListener_multipleVibratorsAreTriggered() throws Exception {
+        mockVibrators(0, 1, 2);
+        mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
+        VibratorManagerService service = createSystemReadyService();
+        IVibratorStateListener[] listeners = new IVibratorStateListener[3];
+        for (int i = 0; i < 3; i++) {
+            listeners[i] = mockVibratorStateListener();
+            service.registerVibratorStateListener(i, listeners[i]);
+        }
+
+        vibrateAndWaitUntilFinished(service, CombinedVibration.startParallel()
+                .addVibrator(0, VibrationEffect.createOneShot(40, 100))
+                .addVibrator(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
+                .combine(), ALARM_ATTRS);
+
+        verify(listeners[0]).onVibrating(eq(true));
+        verify(listeners[1]).onVibrating(eq(true));
+        verify(listeners[2], never()).onVibrating(eq(true));
+        cancelVibrate(service);
+    }
+
+    @Test
+    public void setAlwaysOnEffect_withMono_enablesAlwaysOnEffectToAllVibratorsWithCapability() {
+        mockVibrators(1, 2, 3);
+        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_ALWAYS_ON_CONTROL);
+        mVibratorProviders.get(3).setCapabilities(IVibrator.CAP_ALWAYS_ON_CONTROL);
+
+        CombinedVibration effect = CombinedVibration.createParallel(
+                VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK));
+        assertTrue(createSystemReadyService().setAlwaysOnEffect(
+                UID, PACKAGE_NAME, 1, effect, ALARM_ATTRS));
+
+        PrebakedSegment expected = new PrebakedSegment(
+                VibrationEffect.EFFECT_CLICK, false, VibrationEffect.EFFECT_STRENGTH_MEDIUM);
+
+        // Only vibrators 1 and 3 have always-on capabilities.
+        assertEquals(mVibratorProviders.get(1).getAlwaysOnEffect(1), expected);
+        assertNull(mVibratorProviders.get(2).getAlwaysOnEffect(1));
+        assertEquals(mVibratorProviders.get(3).getAlwaysOnEffect(1), expected);
+    }
+
+    @Test
+    public void setAlwaysOnEffect_withStereo_enablesAlwaysOnEffectToAllVibratorsWithCapability() {
+        mockVibrators(1, 2, 3, 4);
+        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_ALWAYS_ON_CONTROL);
+        mVibratorProviders.get(2).setCapabilities(IVibrator.CAP_ALWAYS_ON_CONTROL);
+        mVibratorProviders.get(4).setCapabilities(IVibrator.CAP_ALWAYS_ON_CONTROL);
+
+        CombinedVibration effect = CombinedVibration.startParallel()
+                .addVibrator(1, VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK))
+                .addVibrator(2, VibrationEffect.createPredefined(VibrationEffect.EFFECT_TICK))
+                .addVibrator(3, VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK))
+                .combine();
+        assertTrue(createSystemReadyService().setAlwaysOnEffect(
+                UID, PACKAGE_NAME, 1, effect, ALARM_ATTRS));
+
+        PrebakedSegment expectedClick = new PrebakedSegment(
+                VibrationEffect.EFFECT_CLICK, false, VibrationEffect.EFFECT_STRENGTH_MEDIUM);
+
+        PrebakedSegment expectedTick = new PrebakedSegment(
+                VibrationEffect.EFFECT_TICK, false, VibrationEffect.EFFECT_STRENGTH_MEDIUM);
+
+        // Enables click on vibrator 1 and tick on vibrator 2 only.
+        assertEquals(mVibratorProviders.get(1).getAlwaysOnEffect(1), expectedClick);
+        assertEquals(mVibratorProviders.get(2).getAlwaysOnEffect(1), expectedTick);
+        assertNull(mVibratorProviders.get(3).getAlwaysOnEffect(1));
+        assertNull(mVibratorProviders.get(4).getAlwaysOnEffect(1));
+    }
+
+    @Test
+    public void setAlwaysOnEffect_withNullEffect_disablesAlwaysOnEffects() {
+        mockVibrators(1, 2, 3);
+        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_ALWAYS_ON_CONTROL);
+        mVibratorProviders.get(3).setCapabilities(IVibrator.CAP_ALWAYS_ON_CONTROL);
+
+        CombinedVibration effect = CombinedVibration.createParallel(
+                VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK));
+        assertTrue(createSystemReadyService().setAlwaysOnEffect(
+                UID, PACKAGE_NAME, 1, effect, ALARM_ATTRS));
+
+        assertTrue(createSystemReadyService().setAlwaysOnEffect(
+                UID, PACKAGE_NAME, 1, null, ALARM_ATTRS));
+
+        assertNull(mVibratorProviders.get(1).getAlwaysOnEffect(1));
+        assertNull(mVibratorProviders.get(2).getAlwaysOnEffect(1));
+        assertNull(mVibratorProviders.get(3).getAlwaysOnEffect(1));
+    }
+
+    @Test
+    public void setAlwaysOnEffect_withNonPrebakedEffect_ignoresEffect() {
+        mockVibrators(1);
+        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_ALWAYS_ON_CONTROL);
+
+        CombinedVibration effect = CombinedVibration.createParallel(
+                VibrationEffect.createOneShot(100, VibrationEffect.DEFAULT_AMPLITUDE));
+        assertFalse(createSystemReadyService().setAlwaysOnEffect(
+                UID, PACKAGE_NAME, 1, effect, ALARM_ATTRS));
+
+        assertNull(mVibratorProviders.get(1).getAlwaysOnEffect(1));
+    }
+
+    @Test
+    public void setAlwaysOnEffect_withNonSyncedEffect_ignoresEffect() {
+        mockVibrators(1);
+        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_ALWAYS_ON_CONTROL);
+
+        CombinedVibration effect = CombinedVibration.startSequential()
+                .addNext(0, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
+                .combine();
+        assertFalse(createSystemReadyService().setAlwaysOnEffect(
+                UID, PACKAGE_NAME, 1, effect, ALARM_ATTRS));
+
+        assertNull(mVibratorProviders.get(1).getAlwaysOnEffect(1));
+    }
+
+    @Test
+    public void setAlwaysOnEffect_withNoVibratorWithCapability_ignoresEffect() {
+        mockVibrators(1);
+        VibratorManagerService service = createSystemReadyService();
+
+        CombinedVibration mono = CombinedVibration.createParallel(
+                VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK));
+        CombinedVibration stereo = CombinedVibration.startParallel()
+                .addVibrator(0, VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK))
+                .combine();
+        assertFalse(service.setAlwaysOnEffect(UID, PACKAGE_NAME, 1, mono, ALARM_ATTRS));
+        assertFalse(service.setAlwaysOnEffect(UID, PACKAGE_NAME, 2, stereo, ALARM_ATTRS));
+
+        assertNull(mVibratorProviders.get(1).getAlwaysOnEffect(1));
+    }
+
+    @Test
+    public void vibrate_withRingtone_usesRingerModeSettings() throws Exception {
+        mockVibrators(1);
+        FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
+        fakeVibrator.setSupportedEffects(VibrationEffect.EFFECT_CLICK,
+                VibrationEffect.EFFECT_HEAVY_CLICK, VibrationEffect.EFFECT_DOUBLE_CLICK);
+
+        setRingerMode(AudioManager.RINGER_MODE_SILENT);
+        VibratorManagerService service = createSystemReadyService();
+        vibrateAndWaitUntilFinished(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK),
+                RINGTONE_ATTRS);
+
+        setRingerMode(AudioManager.RINGER_MODE_NORMAL);
+        service = createSystemReadyService();
+        vibrateAndWaitUntilFinished(
+                service, VibrationEffect.get(VibrationEffect.EFFECT_HEAVY_CLICK), RINGTONE_ATTRS);
+
+        setRingerMode(AudioManager.RINGER_MODE_VIBRATE);
+        service = createSystemReadyService();
+        vibrateAndWaitUntilFinished(
+                service, VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK), RINGTONE_ATTRS);
+
+        assertEquals(
+                Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_HEAVY_CLICK),
+                        expectedPrebaked(VibrationEffect.EFFECT_DOUBLE_CLICK)),
+                mVibratorProviders.get(1).getAllEffectSegments());
+    }
+
+    @Test
+    public void vibrate_withPowerMode_usesPowerModeState() throws Exception {
+        mockVibrators(1);
+        FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
+        fakeVibrator.setSupportedEffects(VibrationEffect.EFFECT_TICK, VibrationEffect.EFFECT_CLICK,
+                VibrationEffect.EFFECT_HEAVY_CLICK, VibrationEffect.EFFECT_DOUBLE_CLICK);
+        VibratorManagerService service = createSystemReadyService();
+
+        mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE);
+        vibrateAndWaitUntilFinished(service, VibrationEffect.get(VibrationEffect.EFFECT_TICK),
+                HAPTIC_FEEDBACK_ATTRS);
+        vibrateAndWaitUntilFinished(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK),
+                RINGTONE_ATTRS);
+
+        mRegisteredPowerModeListener.onLowPowerModeChanged(NORMAL_POWER_STATE);
+        vibrateAndWaitUntilFinished(service,
+                VibrationEffect.get(VibrationEffect.EFFECT_HEAVY_CLICK), /* attrs= */ null);
+        vibrateAndWaitUntilFinished(service,
+                VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK), NOTIFICATION_ATTRS);
+
+        assertEquals(
+                Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_CLICK),
+                        expectedPrebaked(VibrationEffect.EFFECT_HEAVY_CLICK),
+                        expectedPrebaked(VibrationEffect.EFFECT_DOUBLE_CLICK)),
+                mVibratorProviders.get(1).getAllEffectSegments());
+    }
+
+    @Test
+    public void vibrate_withAudioAttributes_usesOriginalAudioUsageInAppOpsManager() {
+        VibratorManagerService service = createSystemReadyService();
+
+        VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
+        AudioAttributes audioAttributes = new AudioAttributes.Builder()
+                .setUsage(AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY).build();
+        VibrationAttributes vibrationAttributes =
+                new VibrationAttributes.Builder(audioAttributes).build();
+
+        vibrate(service, effect, vibrationAttributes);
+
+        verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
+                eq(AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY), anyInt(), anyString());
+    }
+
+    @Test
+    public void vibrate_withVibrationAttributes_usesCorrespondingAudioUsageInAppOpsManager() {
+        VibratorManagerService service = createSystemReadyService();
+
+        vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), ALARM_ATTRS);
+        vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_TICK), NOTIFICATION_ATTRS);
+        vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), RINGTONE_ATTRS);
+        vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_TICK), HAPTIC_FEEDBACK_ATTRS);
+        vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK),
+                new VibrationAttributes.Builder().setUsage(
+                        VibrationAttributes.USAGE_COMMUNICATION_REQUEST).build());
+        vibrate(service, VibrationEffect.createOneShot(2000, 200),
+                new VibrationAttributes.Builder().setUsage(
+                        VibrationAttributes.USAGE_UNKNOWN).build());
+
+        InOrder inOrderVerifier = inOrder(mAppOpsManagerMock);
+        inOrderVerifier.verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
+                eq(AudioAttributes.USAGE_ALARM), anyInt(), anyString());
+        inOrderVerifier.verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
+                eq(AudioAttributes.USAGE_NOTIFICATION), anyInt(), anyString());
+        inOrderVerifier.verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
+                eq(AudioAttributes.USAGE_NOTIFICATION_RINGTONE), anyInt(), anyString());
+        inOrderVerifier.verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
+                eq(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION), anyInt(), anyString());
+        inOrderVerifier.verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
+                eq(AudioAttributes.USAGE_VOICE_COMMUNICATION),
+                anyInt(), anyString());
+        inOrderVerifier.verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
+                eq(AudioAttributes.USAGE_UNKNOWN), anyInt(), anyString());
+    }
+
+    @Test
+    public void vibrate_withVibrationAttributesEnforceFreshSettings_refreshesVibrationSettings()
+            throws Exception {
+        mockVibrators(0);
+        mVibratorProviders.get(0).setSupportedEffects(VibrationEffect.EFFECT_CLICK,
+                VibrationEffect.EFFECT_TICK);
+        setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
+                Vibrator.VIBRATION_INTENSITY_HIGH);
+        VibratorManagerService service = createSystemReadyService();
+
+        VibrationAttributes notificationWithFreshAttrs = new VibrationAttributes.Builder()
+                .setUsage(VibrationAttributes.USAGE_NOTIFICATION)
+                .setFlags(VibrationAttributes.FLAG_INVALIDATE_SETTINGS_CACHE)
+                .build();
+
+        setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
+                Vibrator.VIBRATION_INTENSITY_LOW);
+        vibrateAndWaitUntilFinished(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK),
+                NOTIFICATION_ATTRS);
+        vibrateAndWaitUntilFinished(service, VibrationEffect.get(VibrationEffect.EFFECT_TICK),
+                notificationWithFreshAttrs);
+
+        assertEquals(
+                Arrays.asList(
+                        expectedPrebaked(VibrationEffect.EFFECT_CLICK,
+                                VibrationEffect.EFFECT_STRENGTH_STRONG),
+                        expectedPrebaked(VibrationEffect.EFFECT_TICK,
+                                VibrationEffect.EFFECT_STRENGTH_LIGHT)),
+                mVibratorProviders.get(0).getAllEffectSegments());
+    }
+
+    @Test
+    public void vibrate_withAttributesUnknownUsage_usesEffectToIdentifyTouchUsage() {
+        VibratorManagerService service = createSystemReadyService();
+
+        VibrationAttributes unknownAttributes = VibrationAttributes.createForUsage(
+                VibrationAttributes.USAGE_UNKNOWN);
+        vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), unknownAttributes);
+        vibrate(service, VibrationEffect.createOneShot(200, 200), unknownAttributes);
+        vibrate(service, VibrationEffect.createWaveform(
+                new long[] { 100, 200, 300 }, new int[] {1, 2, 3}, -1), unknownAttributes);
+        vibrate(service,
+                VibrationEffect.startComposition()
+                        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_QUICK_RISE)
+                        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_QUICK_FALL)
+                        .compose(),
+                unknownAttributes);
+
+        verify(mAppOpsManagerMock, times(4))
+                .checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
+                        eq(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION), anyInt(), anyString());
+        verify(mAppOpsManagerMock, never())
+                .checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
+                        eq(AudioAttributes.USAGE_UNKNOWN), anyInt(), anyString());
+    }
+
+    @Test
+    public void vibrate_withAttributesUnknownUsage_ignoresEffectIfNotHapticFeedbackCandidate() {
+        VibratorManagerService service = createSystemReadyService();
+
+        VibrationAttributes unknownAttributes = VibrationAttributes.createForUsage(
+                VibrationAttributes.USAGE_UNKNOWN);
+        vibrate(service, VibrationEffect.get(VibrationEffect.RINGTONES[0]), unknownAttributes);
+        vibrate(service, VibrationEffect.createOneShot(2000, 200), unknownAttributes);
+        vibrate(service, VibrationEffect.createWaveform(
+                new long[] { 100, 200, 300 }, new int[] {1, 2, 3}, 0), unknownAttributes);
+        vibrate(service,
+                VibrationEffect.startComposition()
+                        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_QUICK_RISE)
+                        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_QUICK_FALL)
+                        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_SPIN)
+                        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_THUD)
+                        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
+                        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK)
+                        .compose(),
+                unknownAttributes);
+
+        verify(mAppOpsManagerMock, never())
+                .checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
+                        eq(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION), anyInt(), anyString());
+        verify(mAppOpsManagerMock, times(4))
+                .checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
+                        eq(AudioAttributes.USAGE_UNKNOWN), anyInt(), anyString());
+    }
+
+    @Test
+    public void vibrate_withOngoingRepeatingVibration_ignoresEffect() throws Exception {
+        mockVibrators(1);
+        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+        VibratorManagerService service = createSystemReadyService();
+
+        VibrationEffect repeatingEffect = VibrationEffect.createWaveform(
+                new long[]{10_000, 10_000}, new int[]{128, 255}, 1);
+        vibrate(service, repeatingEffect, new VibrationAttributes.Builder().setUsage(
+                VibrationAttributes.USAGE_UNKNOWN).build());
+
+        // VibrationThread will start this vibration async, wait until it has started.
+        assertTrue(waitUntil(s -> !mVibratorProviders.get(1).getAllEffectSegments().isEmpty(),
+                service, TEST_TIMEOUT_MILLIS));
+
+        vibrateAndWaitUntilFinished(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK),
+                HAPTIC_FEEDBACK_ATTRS);
+
+        // The time estimate is recorded when the vibration starts, repeating vibrations
+        // are capped at BATTERY_STATS_REPEATING_VIBRATION_DURATION (=5000).
+        verify(mBatteryStatsMock).noteVibratorOn(UID, 5000);
+        // The second vibration shouldn't have recorded that the vibrators were turned on.
+        verify(mBatteryStatsMock, times(1)).noteVibratorOn(anyInt(), anyLong());
+        // No segment played is the prebaked CLICK from the second vibration.
+        assertFalse(mVibratorProviders.get(1).getAllEffectSegments().stream()
+                .anyMatch(PrebakedSegment.class::isInstance));
+        cancelVibrate(service);  // Clean up repeating effect.
+    }
+
+    @Test
+    public void vibrate_withOngoingRepeatingVibrationBeingCancelled_playsAfterPreviousIsCancelled()
+            throws Exception {
+        mockVibrators(1);
+        FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
+        fakeVibrator.setOffLatency(50); // Add latency so cancellation is slow.
+        fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+        fakeVibrator.setSupportedEffects(VibrationEffect.EFFECT_CLICK);
+        VibratorManagerService service = createSystemReadyService();
+
+        VibrationEffect repeatingEffect = VibrationEffect.createWaveform(
+                new long[]{10, 10_000}, new int[]{255, 0}, 1);
+        vibrate(service, repeatingEffect, ALARM_ATTRS);
+
+        // VibrationThread will start this vibration async, wait until the off waveform step.
+        assertTrue(waitUntil(s -> fakeVibrator.getOffCount() > 0, service, TEST_TIMEOUT_MILLIS));
+
+        // Cancel vibration right before requesting a new one.
+        // This should trigger slow IVibrator.off before setting the vibration status to cancelled.
+        cancelVibrate(service);
+        vibrateAndWaitUntilFinished(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK),
+                ALARM_ATTRS);
+
+        // Check that second vibration was played.
+        assertTrue(fakeVibrator.getAllEffectSegments().stream()
+                .anyMatch(PrebakedSegment.class::isInstance));
+    }
+
+    @Test
+    public void vibrate_withNewSameImportanceVibrationAndBothRepeating_cancelsOngoingEffect()
+            throws Exception {
+        mockVibrators(1);
+        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+        VibratorManagerService service = createSystemReadyService();
+
+        VibrationEffect repeatingEffect = VibrationEffect.createWaveform(
+                new long[]{10_000, 10_000}, new int[]{128, 255}, 1);
+        vibrate(service, repeatingEffect, ALARM_ATTRS);
+
+        // VibrationThread will start this vibration async, wait until it has started.
+        assertTrue(waitUntil(s -> !mVibratorProviders.get(1).getAllEffectSegments().isEmpty(),
+                service, TEST_TIMEOUT_MILLIS));
+
+        VibrationEffect repeatingEffect2 = VibrationEffect.createWaveform(
+                new long[]{10_000, 10_000}, new int[]{128, 255}, 1);
+        vibrate(service, repeatingEffect2, ALARM_ATTRS);
+
+        // VibrationThread will start this vibration async, so wait before checking it started.
+        assertTrue(waitUntil(s -> mVibratorProviders.get(1).getAllEffectSegments().size() == 2,
+                service, TEST_TIMEOUT_MILLIS));
+
+        // The second vibration should have recorded that the vibrators were turned on.
+        verify(mBatteryStatsMock, times(2)).noteVibratorOn(anyInt(), anyLong());
+
+        cancelVibrate(service);  // Clean up repeating effect.
+    }
+
+    @Test
+    public void vibrate_withNewSameImportanceVibrationButOngoingIsRepeating_ignoreNewVibration()
+            throws Exception {
+        mockVibrators(1);
+        FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
+        VibratorManagerService service = createSystemReadyService();
+
+        VibrationEffect repeatingEffect = VibrationEffect.createWaveform(
+                new long[]{10, 10_000}, new int[]{255, 0}, 1);
+        vibrate(service, repeatingEffect, ALARM_ATTRS);
+
+        // VibrationThread will start this vibration async, wait until the off waveform step.
+        assertTrue(waitUntil(s -> fakeVibrator.getOffCount() > 0, service, TEST_TIMEOUT_MILLIS));
+
+        vibrateAndWaitUntilFinished(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK),
+                ALARM_ATTRS);
+
+        // The second vibration shouldn't have recorded that the vibrators were turned on.
+        verify(mBatteryStatsMock, times(1)).noteVibratorOn(anyInt(), anyLong());
+        // The second vibration shouldn't have played any prebaked segment.
+        assertFalse(mVibratorProviders.get(1).getAllEffectSegments().stream()
+                .anyMatch(PrebakedSegment.class::isInstance));
+        cancelVibrate(service);  // Clean up long effect.
+    }
+
+    @Test
+    public void vibrate_withNewUnknownUsageVibrationAndRepeating_cancelsOngoingEffect()
+            throws Exception {
+        mockVibrators(1);
+        VibratorManagerService service = createSystemReadyService();
+
+        VibrationEffect repeatingEffect = VibrationEffect.createWaveform(
+                new long[]{10_000, 10_000}, new int[]{128, 255}, 1);
+        vibrate(service, repeatingEffect, ALARM_ATTRS);
+
+        // VibrationThread will start this vibration async, wait until it has started.
+        assertTrue(waitUntil(s -> !mVibratorProviders.get(1).getAllEffectSegments().isEmpty(),
+                service, TEST_TIMEOUT_MILLIS));
+
+        VibrationEffect repeatingEffect2 = VibrationEffect.createWaveform(
+                new long[]{10_000, 10_000}, new int[]{128, 255}, 1);
+        vibrate(service, repeatingEffect2, UNKNOWN_ATTRS);
+
+        // VibrationThread will start this vibration async, so wait before checking it started.
+        assertTrue(waitUntil(s -> mVibratorProviders.get(1).getAllEffectSegments().size() == 2,
+                service, TEST_TIMEOUT_MILLIS));
+
+        // The second vibration should have recorded that the vibrators were turned on.
+        verify(mBatteryStatsMock, times(2)).noteVibratorOn(anyInt(), anyLong());
+
+        cancelVibrate(service);  // Clean up repeating effect.
+    }
+
+    @Test
+    public void vibrate_withNewUnknownUsageVibrationAndNotRepeating_ignoreNewVibration()
+            throws Exception {
+        mockVibrators(1);
+        FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
+        VibratorManagerService service = createSystemReadyService();
+
+        VibrationEffect alarmEffect = VibrationEffect.createWaveform(
+                new long[]{10, 10_000}, new int[]{255, 0}, -1);
+        vibrate(service, alarmEffect, ALARM_ATTRS);
+
+        // VibrationThread will start this vibration async, wait until the off waveform step.
+        assertTrue(waitUntil(s -> fakeVibrator.getOffCount() > 0, service, TEST_TIMEOUT_MILLIS));
+
+        vibrateAndWaitUntilFinished(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK),
+                UNKNOWN_ATTRS);
+
+        // The second vibration shouldn't have recorded that the vibrators were turned on.
+        verify(mBatteryStatsMock, times(1)).noteVibratorOn(anyInt(), anyLong());
+        // The second vibration shouldn't have played any prebaked segment.
+        assertFalse(mVibratorProviders.get(1).getAllEffectSegments().stream()
+                .anyMatch(PrebakedSegment.class::isInstance));
+        cancelVibrate(service);  // Clean up long effect.
+    }
+
+    @Test
+    public void vibrate_withOngoingHigherImportanceVibration_ignoresEffect() throws Exception {
+        mockVibrators(1);
+        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+        VibratorManagerService service = createSystemReadyService();
+
+        VibrationEffect effect = VibrationEffect.createWaveform(
+                new long[]{10_000, 10_000}, new int[]{128, 255}, -1);
+        vibrate(service, effect, ALARM_ATTRS);
+
+        // VibrationThread will start this vibration async, so wait before checking it started.
+        assertTrue(waitUntil(s -> !mVibratorProviders.get(1).getAllEffectSegments().isEmpty(),
+                service, TEST_TIMEOUT_MILLIS));
+
+        vibrateAndWaitUntilFinished(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK),
+                HAPTIC_FEEDBACK_ATTRS);
+
+        // The second vibration shouldn't have recorded that the vibrators were turned on.
+        verify(mBatteryStatsMock, times(1)).noteVibratorOn(anyInt(), anyLong());
+        // The second vibration shouldn't have played any prebaked segment.
+        assertFalse(mVibratorProviders.get(1).getAllEffectSegments().stream()
+                .anyMatch(PrebakedSegment.class::isInstance));
+        cancelVibrate(service);  // Clean up long effect.
+    }
+
+    @Test
+    public void vibrate_withOngoingLowerImportanceVibration_cancelsOngoingEffect()
+            throws Exception {
+        mockVibrators(1);
+        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+        mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
+        VibratorManagerService service = createSystemReadyService();
+
+        VibrationEffect effect = VibrationEffect.createWaveform(
+                new long[]{10_000, 10_000}, new int[]{128, 255}, -1);
+        vibrate(service, effect, HAPTIC_FEEDBACK_ATTRS);
+
+        // VibrationThread will start this vibration async, so wait before checking it started.
+        assertTrue(waitUntil(s -> !mVibratorProviders.get(1).getAllEffectSegments().isEmpty(),
+                service, TEST_TIMEOUT_MILLIS));
+
+        vibrateAndWaitUntilFinished(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK),
+                RINGTONE_ATTRS);
+
+        // The second vibration should have recorded that the vibrators were turned on.
+        verify(mBatteryStatsMock, times(2)).noteVibratorOn(anyInt(), anyLong());
+        // One segment played is the prebaked CLICK from the second vibration.
+        assertEquals(1,
+                mVibratorProviders.get(1).getAllEffectSegments().stream()
+                        .filter(PrebakedSegment.class::isInstance)
+                        .count());
+    }
+
+    @Test
+    public void vibrate_withOngoingLowerImportanceExternalVibration_cancelsOngoingVibration()
+            throws Exception {
+        mockVibrators(1);
+        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
+        mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
+        setRingerMode(AudioManager.RINGER_MODE_NORMAL);
+        VibratorManagerService service = createSystemReadyService();
+
+        IBinder firstToken = mock(IBinder.class);
+        IExternalVibrationController controller = mock(IExternalVibrationController.class);
+        ExternalVibration externalVibration = new ExternalVibration(UID, PACKAGE_NAME,
+                AUDIO_ALARM_ATTRS,
+                controller, firstToken);
+        int scale = mExternalVibratorService.onExternalVibrationStart(externalVibration);
+
+        vibrateAndWaitUntilFinished(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK),
+                RINGTONE_ATTRS);
+
+        assertNotEquals(IExternalVibratorService.SCALE_MUTE, scale);
+        // The external vibration should have been cancelled
+        verify(controller).mute();
+        assertEquals(Arrays.asList(false, true, false),
+                mVibratorProviders.get(1).getExternalControlStates());
+        // The new vibration should have recorded that the vibrators were turned on.
+        verify(mBatteryStatsMock, times(1)).noteVibratorOn(anyInt(), anyLong());
+        // One segment played is the prebaked CLICK from the new vibration.
+        assertEquals(1,
+                mVibratorProviders.get(1).getAllEffectSegments().stream()
+                        .filter(PrebakedSegment.class::isInstance)
+                        .count());
+    }
+
+    @Test
+    public void vibrate_withOngoingSameImportancePipelinedVibration_continuesOngoingEffect()
+            throws Exception {
+        VibrationAttributes pipelineAttrs = new VibrationAttributes.Builder(HAPTIC_FEEDBACK_ATTRS)
+                .setFlags(VibrationAttributes.FLAG_PIPELINED_EFFECT)
+                .build();
+
+        mockVibrators(1);
+        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+        mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
+        VibratorManagerService service = createSystemReadyService();
+
+        VibrationEffect effect = VibrationEffect.createWaveform(
+                new long[]{100, 100}, new int[]{128, 255}, -1);
+        vibrate(service, effect, pipelineAttrs);
+        // This vibration will be enqueued, but evicted by the EFFECT_CLICK.
+        vibrate(service, VibrationEffect.startComposition()
+                .addOffDuration(Duration.ofSeconds(10))
+                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_SLOW_RISE)
+                .compose(), pipelineAttrs);  // This will queue and be evicted for the click.
+
+        vibrateAndWaitUntilFinished(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK),
+                pipelineAttrs);
+
+        // The second vibration should have recorded that the vibrators were turned on.
+        verify(mBatteryStatsMock, times(2)).noteVibratorOn(anyInt(), anyLong());
+        // One step segment (with several amplitudes) and one click should have played. Notably
+        // there is no primitive segment.
+        List<VibrationEffectSegment> played = mVibratorProviders.get(1).getAllEffectSegments();
+        assertEquals(2, played.size());
+        assertEquals(1, played.stream().filter(StepSegment.class::isInstance).count());
+        assertEquals(1, played.stream().filter(PrebakedSegment.class::isInstance).count());
+    }
+
+    @Test
+    public void vibrate_withInputDevices_vibratesInputDevices() throws Exception {
+        mockVibrators(1);
+        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+        mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
+        when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1});
+        when(mIInputManagerMock.getVibratorIds(eq(1))).thenReturn(new int[]{1});
+        when(mIInputManagerMock.getInputDevice(eq(1))).thenReturn(createInputDeviceWithVibrator(1));
+        setUserSetting(Settings.System.VIBRATE_INPUT_DEVICES, 1);
+        // Mock alarm intensity equals to default value to avoid scaling in this test.
+        setUserSetting(Settings.System.ALARM_VIBRATION_INTENSITY,
+                mVibrator.getDefaultVibrationIntensity(VibrationAttributes.USAGE_ALARM));
+        VibratorManagerService service = createSystemReadyService();
+
+        CombinedVibration effect = CombinedVibration.createParallel(
+                VibrationEffect.createOneShot(10, 10));
+        vibrateAndWaitUntilFinished(service, effect, ALARM_ATTRS);
+
+        verify(mIInputManagerMock).vibrateCombined(eq(1), eq(effect), any());
+        assertTrue(mVibratorProviders.get(1).getAllEffectSegments().isEmpty());
+    }
+
+    @Test
+    public void vibrate_withNativeCallbackTriggered_finishesVibration() throws Exception {
+        mockVibrators(1);
+        mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
+        VibratorManagerService service = createSystemReadyService();
+        // The native callback will be dispatched manually in this test.
+        mTestLooper.stopAutoDispatchAndIgnoreExceptions();
+
+        vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), ALARM_ATTRS);
+
+        // VibrationThread will start this vibration async, so wait before triggering callbacks.
+        assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
+
+        // Trigger callbacks from controller.
+        mTestLooper.moveTimeForward(50);
+        mTestLooper.dispatchAll();
+
+        // VibrationThread needs some time to react to native callbacks and stop the vibrator.
+        assertTrue(waitUntil(s -> !s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
+    }
+
+    @Test
+    public void vibrate_withTriggerCallback_finishesVibration() throws Exception {
+        mockCapabilities(IVibratorManager.CAP_SYNC, IVibratorManager.CAP_PREPARE_COMPOSE);
+        mockVibrators(1, 2);
+        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+        mVibratorProviders.get(1).setSupportedPrimitives(
+                VibrationEffect.Composition.PRIMITIVE_CLICK);
+        mVibratorProviders.get(2).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+        mVibratorProviders.get(2).setSupportedPrimitives(
+                VibrationEffect.Composition.PRIMITIVE_CLICK);
+        // Mock alarm intensity equals to default value to avoid scaling in this test.
+        setUserSetting(Settings.System.ALARM_VIBRATION_INTENSITY,
+                mVibrator.getDefaultVibrationIntensity(VibrationAttributes.USAGE_ALARM));
+        VibratorManagerService service = createSystemReadyService();
+        // The native callback will be dispatched manually in this test.
+        mTestLooper.stopAutoDispatchAndIgnoreExceptions();
+
+        ArgumentCaptor<VibratorManagerService.OnSyncedVibrationCompleteListener> listenerCaptor =
+                ArgumentCaptor.forClass(
+                        VibratorManagerService.OnSyncedVibrationCompleteListener.class);
+        verify(mNativeWrapperMock).init(listenerCaptor.capture());
+
+        CountDownLatch triggerCountDown = new CountDownLatch(1);
+        // Mock trigger callback on registered listener right after the synced vibration starts.
+        when(mNativeWrapperMock.prepareSynced(eq(new int[]{1, 2}))).thenReturn(true);
+        when(mNativeWrapperMock.triggerSynced(anyLong())).then(answer -> {
+            listenerCaptor.getValue().onComplete(answer.getArgument(0));
+            triggerCountDown.countDown();
+            return true;
+        });
+
+        VibrationEffect composed = VibrationEffect.startComposition()
+                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 100)
+                .compose();
+        CombinedVibration effect = CombinedVibration.createParallel(composed);
+
+        vibrate(service, effect, ALARM_ATTRS);
+        // VibrationThread will start this vibration async, so wait until vibration is triggered.
+        triggerCountDown.await(TEST_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+
+        verify(mNativeWrapperMock).prepareSynced(eq(new int[]{1, 2}));
+        verify(mNativeWrapperMock).triggerSynced(anyLong());
+        PrimitiveSegment expected = new PrimitiveSegment(
+                VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 100);
+        assertEquals(Arrays.asList(expected), mVibratorProviders.get(1).getAllEffectSegments());
+        assertEquals(Arrays.asList(expected), mVibratorProviders.get(2).getAllEffectSegments());
+
+        // VibrationThread needs some time to react to native callbacks and stop the vibrator.
+        assertTrue(waitUntil(s -> !s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
+    }
+
+    @Test
+    public void vibrate_withMultipleVibratorsAndCapabilities_prepareAndTriggerCalled()
+            throws Exception {
+        mockCapabilities(IVibratorManager.CAP_SYNC, IVibratorManager.CAP_PREPARE_PERFORM,
+                IVibratorManager.CAP_PREPARE_COMPOSE, IVibratorManager.CAP_MIXED_TRIGGER_PERFORM,
+                IVibratorManager.CAP_MIXED_TRIGGER_COMPOSE);
+        mockVibrators(1, 2);
+        when(mNativeWrapperMock.prepareSynced(eq(new int[]{1, 2}))).thenReturn(true);
+        when(mNativeWrapperMock.triggerSynced(anyLong())).thenReturn(true);
+        FakeVibratorControllerProvider fakeVibrator1 = mVibratorProviders.get(1);
+        fakeVibrator1.setSupportedEffects(VibrationEffect.EFFECT_CLICK);
+        mVibratorProviders.get(2).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+        VibratorManagerService service = createSystemReadyService();
+
+        CombinedVibration effect = CombinedVibration.startParallel()
+                .addVibrator(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
+                .addVibrator(2, VibrationEffect.startComposition()
+                        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
+                        .compose())
+                .combine();
+        vibrateAndWaitUntilFinished(service, effect, ALARM_ATTRS);
+
+        verify(mNativeWrapperMock).prepareSynced(eq(new int[]{1, 2}));
+        verify(mNativeWrapperMock).triggerSynced(anyLong());
+        verify(mNativeWrapperMock).cancelSynced(); // Trigger on service creation only.
+    }
+
+    @Test
+    public void vibrate_withMultipleVibratorsWithoutCapabilities_skipPrepareAndTrigger()
+            throws Exception {
+        // Missing CAP_MIXED_TRIGGER_ON and CAP_MIXED_TRIGGER_PERFORM.
+        mockCapabilities(IVibratorManager.CAP_SYNC, IVibratorManager.CAP_PREPARE_ON,
+                IVibratorManager.CAP_PREPARE_PERFORM);
+        mockVibrators(1, 2);
+        FakeVibratorControllerProvider fakeVibrator1 = mVibratorProviders.get(1);
+        fakeVibrator1.setSupportedEffects(VibrationEffect.EFFECT_CLICK);
+        VibratorManagerService service = createSystemReadyService();
+
+        CombinedVibration effect = CombinedVibration.startParallel()
+                .addVibrator(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
+                .addVibrator(2, VibrationEffect.createOneShot(10, 100))
+                .combine();
+        vibrateAndWaitUntilFinished(service, effect, ALARM_ATTRS);
+
+        verify(mNativeWrapperMock, never()).prepareSynced(any());
+        verify(mNativeWrapperMock, never()).triggerSynced(anyLong());
+        verify(mNativeWrapperMock).cancelSynced(); // Trigger on service creation only.
+    }
+
+    @Test
+    public void vibrate_withMultipleVibratorsPrepareFailed_skipTrigger() throws Exception {
+        mockCapabilities(IVibratorManager.CAP_SYNC, IVibratorManager.CAP_PREPARE_ON);
+        mockVibrators(1, 2);
+        when(mNativeWrapperMock.prepareSynced(any())).thenReturn(false);
+        VibratorManagerService service = createSystemReadyService();
+
+        CombinedVibration effect = CombinedVibration.startParallel()
+                .addVibrator(1, VibrationEffect.createOneShot(10, 50))
+                .addVibrator(2, VibrationEffect.createOneShot(10, 100))
+                .combine();
+        vibrateAndWaitUntilFinished(service, effect, ALARM_ATTRS);
+
+        verify(mNativeWrapperMock).prepareSynced(eq(new int[]{1, 2}));
+        verify(mNativeWrapperMock, never()).triggerSynced(anyLong());
+        verify(mNativeWrapperMock).cancelSynced(); // Trigger on service creation only.
+    }
+
+    @Test
+    public void vibrate_withMultipleVibratorsTriggerFailed_cancelPreparedSynced() throws Exception {
+        mockCapabilities(IVibratorManager.CAP_SYNC, IVibratorManager.CAP_PREPARE_ON);
+        mockVibrators(1, 2);
+        when(mNativeWrapperMock.prepareSynced(eq(new int[]{1, 2}))).thenReturn(true);
+        when(mNativeWrapperMock.triggerSynced(anyLong())).thenReturn(false);
+        VibratorManagerService service = createSystemReadyService();
+
+        CombinedVibration effect = CombinedVibration.startParallel()
+                .addVibrator(1, VibrationEffect.createOneShot(10, 50))
+                .addVibrator(2, VibrationEffect.createOneShot(10, 100))
+                .combine();
+        vibrateAndWaitUntilFinished(service, effect, ALARM_ATTRS);
+
+        verify(mNativeWrapperMock).prepareSynced(eq(new int[]{1, 2}));
+        verify(mNativeWrapperMock).triggerSynced(anyLong());
+        verify(mNativeWrapperMock, times(2)).cancelSynced(); // Trigger on service creation too.
+    }
+
+    @Test
+    public void vibrate_withIntensitySettings_appliesSettingsToScaleVibrations() throws Exception {
+        int defaultNotificationIntensity =
+                mVibrator.getDefaultVibrationIntensity(VibrationAttributes.USAGE_NOTIFICATION);
+        setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
+                defaultNotificationIntensity < Vibrator.VIBRATION_INTENSITY_HIGH
+                        ? defaultNotificationIntensity + 1
+                        : defaultNotificationIntensity);
+
+        int defaultTouchIntensity =
+                mVibrator.getDefaultVibrationIntensity(VibrationAttributes.USAGE_TOUCH);
+        setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY,
+                defaultTouchIntensity > Vibrator.VIBRATION_INTENSITY_LOW
+                        ? defaultTouchIntensity - 1
+                        : defaultTouchIntensity);
+
+        setUserSetting(Settings.System.ALARM_VIBRATION_INTENSITY,
+                mVibrator.getDefaultVibrationIntensity(VibrationAttributes.USAGE_ALARM));
+        setUserSetting(Settings.System.MEDIA_VIBRATION_INTENSITY,
+                Vibrator.VIBRATION_INTENSITY_HIGH);
+        setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, Vibrator.VIBRATION_INTENSITY_OFF);
+
+        mockVibrators(1);
+        FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
+        fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL,
+                IVibrator.CAP_COMPOSE_EFFECTS);
+        fakeVibrator.setSupportedPrimitives(VibrationEffect.Composition.PRIMITIVE_CLICK,
+                VibrationEffect.Composition.PRIMITIVE_TICK);
+        VibratorManagerService service = createSystemReadyService();
+
+        vibrateAndWaitUntilFinished(service, VibrationEffect.startComposition()
+                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f)
+                .compose(), HAPTIC_FEEDBACK_ATTRS);
+
+        vibrateAndWaitUntilFinished(service, CombinedVibration.startSequential()
+                .addNext(1, VibrationEffect.createOneShot(100, 125))
+                .combine(), NOTIFICATION_ATTRS);
+
+        vibrateAndWaitUntilFinished(service, VibrationEffect.startComposition()
+                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f)
+                .compose(), ALARM_ATTRS);
+
+        // Ring vibrations have intensity OFF and are not played.
+        vibrateAndWaitUntilFinished(service, VibrationEffect.createOneShot(100, 125),
+                RINGTONE_ATTRS);
+
+        // Only 3 effects played successfully.
+        assertEquals(3, fakeVibrator.getAllEffectSegments().size());
+
+        // Haptic feedback vibrations will be scaled with SCALE_LOW or none if default is low.
+        assertEquals(defaultTouchIntensity > Vibrator.VIBRATION_INTENSITY_LOW,
+                0.5 > ((PrimitiveSegment) fakeVibrator.getAllEffectSegments().get(0)).getScale());
+
+        // Notification vibrations will be scaled with SCALE_HIGH or none if default is high.
+        assertEquals(defaultNotificationIntensity < Vibrator.VIBRATION_INTENSITY_HIGH,
+                0.6 < fakeVibrator.getAmplitudes().get(0));
+
+        // Alarm vibration will be scaled with SCALE_NONE.
+        assertEquals(1f,
+                ((PrimitiveSegment) fakeVibrator.getAllEffectSegments().get(2)).getScale(), 1e-5);
+    }
+
+    @Test
+    public void vibrate_withPowerModeChange_cancelVibrationIfNotAllowed() throws Exception {
+        mockVibrators(1, 2);
+        VibratorManagerService service = createSystemReadyService();
+        vibrate(service,
+                CombinedVibration.startParallel()
+                        .addVibrator(1, VibrationEffect.createOneShot(1000, 100))
+                        .combine(),
+                HAPTIC_FEEDBACK_ATTRS);
+
+        // VibrationThread will start this vibration async, so wait until vibration is triggered.
+        assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
+
+        mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE);
+
+        // Haptic feedback cancelled on low power mode.
+        assertTrue(waitUntil(s -> !s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
+    }
+
+    @Test
+    public void vibrate_withSettingsChange_doNotCancelVibration() throws Exception {
+        mockVibrators(1);
+        VibratorManagerService service = createSystemReadyService();
+
+        vibrate(service, VibrationEffect.createOneShot(1000, 100), HAPTIC_FEEDBACK_ATTRS);
+
+        // VibrationThread will start this vibration async, so wait until vibration is triggered.
+        assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
+
+        service.updateServiceState();
+
+        // Vibration is not stopped nearly after updating service.
+        assertFalse(waitUntil(s -> !s.isVibrating(1), service, 50));
+        cancelVibrate(service);
+    }
+
+    @Test
+    public void vibrate_withVirtualDisplayChange_ignoreVibrationFromVirtualDisplay()
+            throws Exception {
+        mockVibrators(1);
+        VibratorManagerService service = createSystemReadyService();
+        mRegisteredVirtualDisplayListener.onVirtualDisplayCreated(VIRTUAL_DISPLAY_ID);
+
+        vibrateWithDisplay(service,
+                VIRTUAL_DISPLAY_ID,
+                CombinedVibration.startParallel()
+                        .addVibrator(1, VibrationEffect.createOneShot(1000, 100))
+                        .combine(),
+                HAPTIC_FEEDBACK_ATTRS);
+
+        // Haptic feedback ignored when it's from a virtual display.
+        assertFalse(waitUntil(s -> s.isVibrating(1), service, /* timeout= */ 50));
+
+        mRegisteredVirtualDisplayListener.onVirtualDisplayRemoved(VIRTUAL_DISPLAY_ID);
+        vibrateWithDisplay(service,
+                VIRTUAL_DISPLAY_ID,
+                CombinedVibration.startParallel()
+                        .addVibrator(1, VibrationEffect.createOneShot(1000, 100))
+                        .combine(),
+                HAPTIC_FEEDBACK_ATTRS);
+        // Haptic feedback played normally when the virtual display is removed.
+        assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
+
+        cancelVibrate(service);  // Clean up long-ish effect.
+    }
+
+    @Test
+    public void vibrate_withAppsOnVirtualDisplayChange_ignoreVibrationFromVirtualDisplay()
+            throws Exception {
+        mockVibrators(1);
+        VibratorManagerService service = createSystemReadyService();
+        mRegisteredAppsOnVirtualDeviceListener.onAppsOnAnyVirtualDeviceChanged(
+                new ArraySet<>(Arrays.asList(UID)));
+        vibrateWithDisplay(service,
+                Display.INVALID_DISPLAY,
+                CombinedVibration.startParallel()
+                        .addVibrator(1, VibrationEffect.createOneShot(1000, 100))
+                        .combine(),
+                HAPTIC_FEEDBACK_ATTRS);
+
+        // Haptic feedback ignored when it's from an app running virtual display.
+        assertFalse(waitUntil(s -> s.isVibrating(1), service, /* timeout= */ 50));
+
+        mRegisteredAppsOnVirtualDeviceListener.onAppsOnAnyVirtualDeviceChanged(new ArraySet<>());
+        vibrateWithDisplay(service,
+                Display.INVALID_DISPLAY,
+                CombinedVibration.startParallel()
+                        .addVibrator(1, VibrationEffect.createOneShot(1000, 100))
+                        .combine(),
+                HAPTIC_FEEDBACK_ATTRS);
+        // Haptic feedback played normally when the same app no long runs on a virtual display.
+        assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
+        cancelVibrate(service);  // Clean up long-ish effect.
+    }
+
+    @Test
+    public void vibrate_prebakedAndComposedVibrationsWithFallbacks_playsFallbackOnlyForPredefined()
+            throws Exception {
+        mockVibrators(1);
+        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+        mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
+        mVibratorProviders.get(1).setSupportedPrimitives(
+                VibrationEffect.Composition.PRIMITIVE_CLICK);
+
+        VibratorManagerService service = createSystemReadyService();
+        vibrateAndWaitUntilFinished(service,
+                VibrationEffect.startComposition()
+                        .addEffect(VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK))
+                        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK)
+                        .addEffect(VibrationEffect.createPredefined(VibrationEffect.EFFECT_TICK))
+                        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
+                        .compose(),
+                ALARM_ATTRS);
+
+        List<VibrationEffectSegment> segments = mVibratorProviders.get(1).getAllEffectSegments();
+        // At least one step segment played as fallback for unusupported vibration effect
+        assertTrue(segments.size() > 2);
+        // 0: Supported effect played
+        assertTrue(segments.get(0) instanceof PrebakedSegment);
+        // 1: No segment for unsupported primitive
+        // 2: One or more intermediate step segments as fallback for unsupported effect
+        for (int i = 1; i < segments.size() - 1; i++) {
+            assertTrue(segments.get(i) instanceof StepSegment);
+        }
+        // 3: Supported primitive played
+        assertTrue(segments.get(segments.size() - 1) instanceof PrimitiveSegment);
+    }
+
+    @Test
+    public void cancelVibrate_withoutUsageFilter_stopsVibrating() throws Exception {
+        mockVibrators(1);
+        VibratorManagerService service = createSystemReadyService();
+
+        service.cancelVibrate(VibrationAttributes.USAGE_FILTER_MATCH_ALL, service);
+        assertFalse(service.isVibrating(1));
+
+        vibrate(service, VibrationEffect.createOneShot(10 * TEST_TIMEOUT_MILLIS, 100), ALARM_ATTRS);
+
+        // VibrationThread will start this vibration async, so wait until vibration is triggered.
+        assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
+
+        service.cancelVibrate(VibrationAttributes.USAGE_FILTER_MATCH_ALL, service);
+
+        // Alarm cancelled on filter match all.
+        assertTrue(waitUntil(s -> !s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
+    }
+
+    @Test
+    public void cancelVibrate_withFilter_onlyCancelsVibrationWithFilteredUsage() throws Exception {
+        mockVibrators(1);
+        VibratorManagerService service = createSystemReadyService();
+
+        vibrate(service, VibrationEffect.createOneShot(10 * TEST_TIMEOUT_MILLIS, 100), ALARM_ATTRS);
+
+        // VibrationThread will start this vibration async, so wait until vibration is triggered.
+        assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
+
+        // Vibration is not cancelled with a different usage.
+        service.cancelVibrate(VibrationAttributes.USAGE_RINGTONE, service);
+        assertFalse(waitUntil(s -> !s.isVibrating(1), service, /* timeout= */ 50));
+
+        // Vibration is not cancelled with a different usage class used as filter.
+        service.cancelVibrate(
+                VibrationAttributes.USAGE_CLASS_FEEDBACK | ~VibrationAttributes.USAGE_CLASS_MASK,
+                service);
+        assertFalse(waitUntil(s -> !s.isVibrating(1), service, /* timeout= */ 50));
+
+        // Vibration is cancelled with usage class as filter.
+        service.cancelVibrate(
+                VibrationAttributes.USAGE_CLASS_ALARM | ~VibrationAttributes.USAGE_CLASS_MASK,
+                service);
+        assertTrue(waitUntil(s -> !s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
+    }
+
+    @Test
+    public void cancelVibrate_withoutUnknownUsage_onlyStopsIfFilteringUnknownOrAllUsages()
+            throws Exception {
+        mockVibrators(1);
+        VibrationAttributes attrs = new VibrationAttributes.Builder()
+                .setUsage(VibrationAttributes.USAGE_UNKNOWN)
+                .build();
+        VibratorManagerService service = createSystemReadyService();
+
+        vibrate(service, VibrationEffect.createOneShot(10 * TEST_TIMEOUT_MILLIS, 100), attrs);
+
+        // VibrationThread will start this vibration async, so wait until vibration is triggered.
+        assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
+
+        // Do not cancel UNKNOWN vibration when filter is being applied for other usages.
+        service.cancelVibrate(VibrationAttributes.USAGE_RINGTONE, service);
+        assertFalse(waitUntil(s -> !s.isVibrating(1), service, /* timeout= */ 50));
+
+        service.cancelVibrate(
+                VibrationAttributes.USAGE_CLASS_ALARM | ~VibrationAttributes.USAGE_CLASS_MASK,
+                service);
+        assertFalse(waitUntil(s -> !s.isVibrating(1), service, /* timeout= */ 50));
+
+        // Cancel UNKNOWN vibration when filtered for that vibration specifically.
+        service.cancelVibrate(VibrationAttributes.USAGE_UNKNOWN, service);
+        assertTrue(waitUntil(s -> !s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
+
+        vibrate(service, VibrationEffect.createOneShot(10 * TEST_TIMEOUT_MILLIS, 100), attrs);
+
+        // VibrationThread will start this vibration async, so wait until vibration is triggered.
+        assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
+
+        // Cancel UNKNOWN vibration when all vibrations are being cancelled.
+        service.cancelVibrate(VibrationAttributes.USAGE_FILTER_MATCH_ALL, service);
+        assertTrue(waitUntil(s -> !s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
+    }
+
+    @Test
+    public void onExternalVibration_ignoreVibrationFromVirtualDevices() throws Exception {
+        mockVibrators(1);
+        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
+        createSystemReadyService();
+
+        IBinder binderToken = mock(IBinder.class);
+        ExternalVibration externalVibration = new ExternalVibration(UID, PACKAGE_NAME,
+                AUDIO_ALARM_ATTRS,
+                mock(IExternalVibrationController.class), binderToken);
+        int scale = mExternalVibratorService.onExternalVibrationStart(externalVibration);
+        assertNotEquals(IExternalVibratorService.SCALE_MUTE, scale);
+
+        mRegisteredAppsOnVirtualDeviceListener.onAppsOnAnyVirtualDeviceChanged(
+                new ArraySet<>(Arrays.asList(UID)));
+        scale = mExternalVibratorService.onExternalVibrationStart(externalVibration);
+        assertEquals(IExternalVibratorService.SCALE_MUTE, scale);
+    }
+
+    @Test
+    public void onExternalVibration_setsExternalControl() throws Exception {
+        mockVibrators(1);
+        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
+        createSystemReadyService();
+
+        IBinder binderToken = mock(IBinder.class);
+        ExternalVibration externalVibration = new ExternalVibration(UID, PACKAGE_NAME,
+                AUDIO_ALARM_ATTRS,
+                mock(IExternalVibrationController.class), binderToken);
+        int scale = mExternalVibratorService.onExternalVibrationStart(externalVibration);
+        mExternalVibratorService.onExternalVibrationStop(externalVibration);
+
+        assertNotEquals(IExternalVibratorService.SCALE_MUTE, scale);
+        assertEquals(Arrays.asList(false, true, false),
+                mVibratorProviders.get(1).getExternalControlStates());
+
+        verify(binderToken).linkToDeath(any(), eq(0));
+        verify(binderToken).unlinkToDeath(any(), eq(0));
+    }
+
+    @Test
+    public void onExternalVibration_withOngoingExternalVibration_mutesPreviousVibration()
+            throws Exception {
+        mockVibrators(1);
+        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
+        setRingerMode(AudioManager.RINGER_MODE_NORMAL);
+        createSystemReadyService();
+
+        IBinder firstToken = mock(IBinder.class);
+        IBinder secondToken = mock(IBinder.class);
+        IExternalVibrationController firstController = mock(IExternalVibrationController.class);
+        IExternalVibrationController secondController = mock(IExternalVibrationController.class);
+        ExternalVibration firstVibration = new ExternalVibration(UID, PACKAGE_NAME,
+                AUDIO_ALARM_ATTRS,
+                firstController, firstToken);
+        int firstScale = mExternalVibratorService.onExternalVibrationStart(firstVibration);
+
+        AudioAttributes ringtoneAudioAttrs = new AudioAttributes.Builder()
+                .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
+                .build();
+        ExternalVibration secondVibration = new ExternalVibration(UID, PACKAGE_NAME,
+                ringtoneAudioAttrs, secondController, secondToken);
+        int secondScale = mExternalVibratorService.onExternalVibrationStart(secondVibration);
+
+        assertNotEquals(IExternalVibratorService.SCALE_MUTE, firstScale);
+        assertNotEquals(IExternalVibratorService.SCALE_MUTE, secondScale);
+        verify(firstController).mute();
+        verify(secondController, never()).mute();
+        // Set external control called only once.
+        assertEquals(Arrays.asList(false, true),
+                mVibratorProviders.get(1).getExternalControlStates());
+
+        mExternalVibratorService.onExternalVibrationStop(secondVibration);
+        mExternalVibratorService.onExternalVibrationStop(firstVibration);
+        assertEquals(Arrays.asList(false, true, false),
+                mVibratorProviders.get(1).getExternalControlStates());
+
+        verify(firstToken).linkToDeath(any(), eq(0));
+        verify(firstToken).unlinkToDeath(any(), eq(0));
+
+        verify(secondToken).linkToDeath(any(), eq(0));
+        verify(secondToken).unlinkToDeath(any(), eq(0));
+    }
+
+    @Test
+    public void onExternalVibration_withOngoingVibration_cancelsOngoingVibrationImmediately()
+            throws Exception {
+        mockVibrators(1);
+        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL,
+                IVibrator.CAP_AMPLITUDE_CONTROL);
+        VibratorManagerService service = createSystemReadyService();
+
+        VibrationEffect effect = VibrationEffect.createOneShot(10 * TEST_TIMEOUT_MILLIS, 100);
+        vibrate(service, effect, HAPTIC_FEEDBACK_ATTRS);
+
+        // VibrationThread will start this vibration async, so wait until vibration is triggered.
+        assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
+
+        ExternalVibration externalVibration = new ExternalVibration(UID, PACKAGE_NAME,
+                AUDIO_ALARM_ATTRS,
+                mock(IExternalVibrationController.class));
+        int scale = mExternalVibratorService.onExternalVibrationStart(externalVibration);
+        assertNotEquals(IExternalVibratorService.SCALE_MUTE, scale);
+
+        // Vibration is cancelled.
+        assertTrue(waitUntil(s -> !s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
+        assertEquals(Arrays.asList(false, true),
+                mVibratorProviders.get(1).getExternalControlStates());
+    }
+
+    @Test
+    public void onExternalVibration_withOngoingHigherImportanceVibration_ignoreNewVibration()
+            throws Exception {
+        mockVibrators(1);
+        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL,
+                IVibrator.CAP_AMPLITUDE_CONTROL);
+        VibratorManagerService service = createSystemReadyService();
+
+        VibrationEffect effect = VibrationEffect.createOneShot(10 * TEST_TIMEOUT_MILLIS, 100);
+        vibrate(service, effect, RINGTONE_ATTRS);
+
+        // VibrationThread will start this vibration async, so wait until vibration is triggered.
+        assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
+
+        ExternalVibration externalVibration = new ExternalVibration(UID, PACKAGE_NAME,
+                AUDIO_ALARM_ATTRS,
+                mock(IExternalVibrationController.class));
+        int scale = mExternalVibratorService.onExternalVibrationStart(externalVibration);
+        // External vibration is ignored.
+        assertEquals(IExternalVibratorService.SCALE_MUTE, scale);
+
+        // Vibration is not cancelled.
+        assertFalse(waitUntil(s -> !s.isVibrating(1), service, CLEANUP_TIMEOUT_MILLIS));
+        assertEquals(Arrays.asList(false), mVibratorProviders.get(1).getExternalControlStates());
+        cancelVibrate(service);  // Clean up long effect.
+    }
+
+    @Test
+    public void onExternalVibration_withNewSameImportanceButRepeating_cancelsOngoingVibration()
+            throws Exception {
+        mockVibrators(1);
+        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL,
+                IVibrator.CAP_AMPLITUDE_CONTROL);
+        VibratorManagerService service = createSystemReadyService();
+
+        VibrationEffect repeatingEffect = VibrationEffect.createWaveform(
+                new long[]{100, 200, 300}, new int[]{128, 255, 255}, 1);
+        vibrate(service, repeatingEffect, ALARM_ATTRS);
+
+        // VibrationThread will start this vibration async, so wait until vibration is triggered.
+        assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
+
+        ExternalVibration externalVibration = new ExternalVibration(UID, PACKAGE_NAME,
+                AUDIO_ALARM_ATTRS, mock(IExternalVibrationController.class));
+        int scale = mExternalVibratorService.onExternalVibrationStart(externalVibration);
+        assertNotEquals(IExternalVibratorService.SCALE_MUTE, scale);
+
+        // Vibration is cancelled.
+        assertTrue(waitUntil(s -> !s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
+        assertEquals(Arrays.asList(false, true),
+                mVibratorProviders.get(1).getExternalControlStates());
+    }
+
+    @Test
+    public void onExternalVibration_withNewSameImportanceButOngoingIsRepeating_ignoreNewVibration()
+            throws Exception {
+        mockVibrators(1);
+        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL,
+                IVibrator.CAP_AMPLITUDE_CONTROL);
+        VibratorManagerService service = createSystemReadyService();
+
+        VibrationEffect repeatingEffect = VibrationEffect.createWaveform(
+                new long[]{10_000, 10_000}, new int[]{128, 255}, 1);
+        vibrate(service, repeatingEffect, NOTIFICATION_ATTRS);
+
+        // VibrationThread will start this vibration async, so wait until vibration is triggered.
+        assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
+
+        ExternalVibration externalVibration = new ExternalVibration(UID, PACKAGE_NAME,
+                AUDIO_NOTIFICATION_ATTRS,
+                mock(IExternalVibrationController.class));
+        int scale = mExternalVibratorService.onExternalVibrationStart(externalVibration);
+        // New vibration is ignored.
+        assertEquals(IExternalVibratorService.SCALE_MUTE, scale);
+
+        // Vibration is not cancelled.
+        assertFalse(waitUntil(s -> !s.isVibrating(1), service, CLEANUP_TIMEOUT_MILLIS));
+        assertEquals(Arrays.asList(false), mVibratorProviders.get(1).getExternalControlStates());
+        cancelVibrate(service);  // Clean up long effect.
+    }
+
+    @Test
+    public void onExternalVibration_withRingtone_usesRingerModeSettings() {
+        mockVibrators(1);
+        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
+        AudioAttributes audioAttrs = new AudioAttributes.Builder()
+                .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
+                .build();
+        ExternalVibration externalVibration = new ExternalVibration(UID, PACKAGE_NAME, audioAttrs,
+                mock(IExternalVibrationController.class));
+
+        setRingerMode(AudioManager.RINGER_MODE_SILENT);
+        createSystemReadyService();
+        int scale = mExternalVibratorService.onExternalVibrationStart(externalVibration);
+        assertEquals(IExternalVibratorService.SCALE_MUTE, scale);
+
+        setRingerMode(AudioManager.RINGER_MODE_NORMAL);
+        createSystemReadyService();
+        scale = mExternalVibratorService.onExternalVibrationStart(externalVibration);
+        assertNotEquals(IExternalVibratorService.SCALE_MUTE, scale);
+
+        setRingerMode(AudioManager.RINGER_MODE_VIBRATE);
+        createSystemReadyService();
+        scale = mExternalVibratorService.onExternalVibrationStart(externalVibration);
+        assertNotEquals(IExternalVibratorService.SCALE_MUTE, scale);
+    }
+
+    @Test
+    public void onExternalVibration_withBypassMuteAudioFlag_ignoresUserSettings() {
+        mockVibrators(1);
+        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
+        setUserSetting(Settings.System.ALARM_VIBRATION_INTENSITY,
+                Vibrator.VIBRATION_INTENSITY_OFF);
+        AudioAttributes audioAttrs = new AudioAttributes.Builder()
+                .setUsage(AudioAttributes.USAGE_ALARM)
+                .build();
+        AudioAttributes flaggedAudioAttrs = new AudioAttributes.Builder()
+                .setUsage(AudioAttributes.USAGE_ALARM)
+                .setFlags(AudioAttributes.FLAG_BYPASS_MUTE)
+                .build();
+        createSystemReadyService();
+
+        int scale = mExternalVibratorService.onExternalVibrationStart(
+                new ExternalVibration(UID, PACKAGE_NAME, audioAttrs,
+                        mock(IExternalVibrationController.class)));
+        assertEquals(IExternalVibratorService.SCALE_MUTE, scale);
+
+        createSystemReadyService();
+        scale = mExternalVibratorService.onExternalVibrationStart(
+                new ExternalVibration(UID, PACKAGE_NAME, flaggedAudioAttrs,
+                        mock(IExternalVibrationController.class)));
+        assertNotEquals(IExternalVibratorService.SCALE_MUTE, scale);
+    }
+
+    @Test
+    public void onExternalVibration_withUnknownUsage_appliesMediaSettings() {
+        mockVibrators(1);
+        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
+        setUserSetting(Settings.System.MEDIA_VIBRATION_INTENSITY,
+                Vibrator.VIBRATION_INTENSITY_OFF);
+        AudioAttributes flaggedAudioAttrs = new AudioAttributes.Builder()
+                .setUsage(AudioAttributes.USAGE_UNKNOWN)
+                .setFlags(AudioAttributes.FLAG_BYPASS_MUTE)
+                .build();
+        createSystemReadyService();
+
+        int scale = mExternalVibratorService.onExternalVibrationStart(
+                new ExternalVibration(/* uid= */ 123, PACKAGE_NAME, flaggedAudioAttrs,
+                        mock(IExternalVibrationController.class)));
+        assertEquals(IExternalVibratorService.SCALE_MUTE, scale);
+    }
+
+    @Test
+    public void frameworkStats_externalVibration_reportsAllMetrics() throws Exception {
+        mockVibrators(1);
+        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
+        createSystemReadyService();
+
+        AudioAttributes audioAttrs = new AudioAttributes.Builder()
+                .setUsage(AudioAttributes.USAGE_ALARM)
+                .build();
+
+        ExternalVibration vib = new ExternalVibration(UID, PACKAGE_NAME, audioAttrs,
+                mock(IExternalVibrationController.class));
+        mExternalVibratorService.onExternalVibrationStart(vib);
+
+        Thread.sleep(10);
+        mExternalVibratorService.onExternalVibrationStop(vib);
+
+        ArgumentCaptor<VibrationStats.StatsInfo> argumentCaptor =
+                ArgumentCaptor.forClass(VibrationStats.StatsInfo.class);
+        verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS))
+                .writeVibrationReportedAsync(argumentCaptor.capture());
+
+        VibrationStats.StatsInfo statsInfo = argumentCaptor.getValue();
+        assertEquals(UID, statsInfo.uid);
+        assertEquals(FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__EXTERNAL,
+                statsInfo.vibrationType);
+        assertEquals(VibrationAttributes.USAGE_ALARM, statsInfo.usage);
+        assertEquals(Vibration.Status.FINISHED.getProtoEnumValue(), statsInfo.status);
+        assertTrue(statsInfo.totalDurationMillis > 0);
+        assertTrue(
+                "Expected vibrator ON for at least 10ms, got " + statsInfo.vibratorOnMillis + "ms",
+                statsInfo.vibratorOnMillis >= 10);
+        assertEquals(2, statsInfo.halSetExternalControlCount);
+    }
+
+    @Test
+    public void frameworkStats_waveformVibration_reportsAllMetrics() throws Exception {
+        mockVibrators(1);
+        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+
+        VibratorManagerService service = createSystemReadyService();
+        vibrateAndWaitUntilFinished(service,
+                VibrationEffect.createWaveform(new long[] {0, 10, 20, 10}, -1), RINGTONE_ATTRS);
+
+        verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS))
+                .writeVibratorStateOnAsync(eq(UID), anyLong());
+        verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS))
+                .writeVibratorStateOffAsync(eq(UID));
+
+        ArgumentCaptor<VibrationStats.StatsInfo> argumentCaptor =
+                ArgumentCaptor.forClass(VibrationStats.StatsInfo.class);
+        verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS))
+                .writeVibrationReportedAsync(argumentCaptor.capture());
+
+        VibrationStats.StatsInfo metrics = argumentCaptor.getValue();
+        assertEquals(UID, metrics.uid);
+        assertEquals(FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__SINGLE,
+                metrics.vibrationType);
+        assertEquals(VibrationAttributes.USAGE_RINGTONE, metrics.usage);
+        assertEquals(Vibration.Status.FINISHED.getProtoEnumValue(), metrics.status);
+        assertTrue("Total duration was too low, " + metrics.totalDurationMillis + "ms",
+                metrics.totalDurationMillis >= 20);
+        assertTrue("Vibrator ON duration was too low, " + metrics.vibratorOnMillis + "ms",
+                metrics.vibratorOnMillis >= 20);
+
+        // All unrelated metrics are empty.
+        assertEquals(0, metrics.repeatCount);
+        assertEquals(0, metrics.halComposeCount);
+        assertEquals(0, metrics.halComposePwleCount);
+        assertEquals(0, metrics.halPerformCount);
+        assertEquals(0, metrics.halSetExternalControlCount);
+        assertEquals(0, metrics.halCompositionSize);
+        assertEquals(0, metrics.halPwleSize);
+        assertNull(metrics.halSupportedCompositionPrimitivesUsed);
+        assertNull(metrics.halSupportedEffectsUsed);
+        assertNull(metrics.halUnsupportedCompositionPrimitivesUsed);
+        assertNull(metrics.halUnsupportedEffectsUsed);
+
+        // Accommodate for ramping off config that might add extra setAmplitudes.
+        assertEquals(2, metrics.halOnCount);
+        assertTrue(metrics.halOffCount > 0);
+        assertTrue(metrics.halSetAmplitudeCount >= 2);
+    }
+
+    @Test
+    public void frameworkStats_repeatingVibration_reportsAllMetrics() throws Exception {
+        mockVibrators(1);
+        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+
+        VibratorManagerService service = createSystemReadyService();
+        vibrate(service, VibrationEffect.createWaveform(new long[] {10, 100}, 1), RINGTONE_ATTRS);
+
+        verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS))
+                .writeVibratorStateOnAsync(eq(UID), anyLong());
+
+        // Wait for at least one loop before cancelling it.
+        Thread.sleep(100);
+        service.cancelVibrate(VibrationAttributes.USAGE_RINGTONE, service);
+
+        verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS))
+                .writeVibratorStateOffAsync(eq(UID));
+
+        ArgumentCaptor<VibrationStats.StatsInfo> argumentCaptor =
+                ArgumentCaptor.forClass(VibrationStats.StatsInfo.class);
+        verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS))
+                .writeVibrationReportedAsync(argumentCaptor.capture());
+
+        VibrationStats.StatsInfo metrics = argumentCaptor.getValue();
+        assertEquals(UID, metrics.uid);
+        assertEquals(FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__REPEATED,
+                metrics.vibrationType);
+        assertEquals(VibrationAttributes.USAGE_RINGTONE, metrics.usage);
+        assertEquals(Vibration.Status.CANCELLED_BY_USER.getProtoEnumValue(), metrics.status);
+        assertTrue("Total duration was too low, " + metrics.totalDurationMillis + "ms",
+                metrics.totalDurationMillis >= 100);
+        assertTrue("Vibrator ON duration was too low, " + metrics.vibratorOnMillis + "ms",
+                metrics.vibratorOnMillis >= 100);
+
+        // All unrelated metrics are empty.
+        assertTrue(metrics.repeatCount > 0);
+        assertEquals(0, metrics.halComposeCount);
+        assertEquals(0, metrics.halComposePwleCount);
+        assertEquals(0, metrics.halPerformCount);
+        assertEquals(0, metrics.halSetExternalControlCount);
+        assertEquals(0, metrics.halCompositionSize);
+        assertEquals(0, metrics.halPwleSize);
+        assertNull(metrics.halSupportedCompositionPrimitivesUsed);
+        assertNull(metrics.halSupportedEffectsUsed);
+        assertNull(metrics.halUnsupportedCompositionPrimitivesUsed);
+        assertNull(metrics.halUnsupportedEffectsUsed);
+
+        // Accommodate for ramping off config that might add extra setAmplitudes.
+        assertTrue(metrics.halOnCount > 0);
+        assertTrue(metrics.halOffCount > 0);
+        assertTrue(metrics.halSetAmplitudeCount > 0);
+    }
+
+    @Test
+    public void frameworkStats_prebakedAndComposedVibrations_reportsAllMetrics() throws Exception {
+        mockVibrators(1);
+        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+        mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
+        mVibratorProviders.get(1).setSupportedPrimitives(
+                VibrationEffect.Composition.PRIMITIVE_TICK);
+
+        VibratorManagerService service = createSystemReadyService();
+        vibrateAndWaitUntilFinished(service,
+                VibrationEffect.startComposition()
+                        .addEffect(VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK))
+                        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK)
+                        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK)
+                        .addEffect(VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK))
+                        .addEffect(VibrationEffect.createPredefined(VibrationEffect.EFFECT_TICK))
+                        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
+                        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
+                        .compose(),
+                ALARM_ATTRS);
+
+        verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS))
+                .writeVibratorStateOnAsync(eq(UID), anyLong());
+        verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS))
+                .writeVibratorStateOffAsync(eq(UID));
+
+        ArgumentCaptor<VibrationStats.StatsInfo> argumentCaptor =
+                ArgumentCaptor.forClass(VibrationStats.StatsInfo.class);
+        verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS))
+                .writeVibrationReportedAsync(argumentCaptor.capture());
+
+        VibrationStats.StatsInfo metrics = argumentCaptor.getValue();
+        assertEquals(UID, metrics.uid);
+        assertEquals(FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__SINGLE,
+                metrics.vibrationType);
+        assertEquals(VibrationAttributes.USAGE_ALARM, metrics.usage);
+        assertEquals(Vibration.Status.FINISHED.getProtoEnumValue(), metrics.status);
+
+        // At least 4 effect/primitive played, 20ms each, plus configured fallback.
+        assertTrue("Total duration was too low, " + metrics.totalDurationMillis + "ms",
+                metrics.totalDurationMillis >= 80);
+        assertTrue("Vibrator ON duration was too low, " + metrics.vibratorOnMillis + "ms",
+                metrics.vibratorOnMillis >= 80);
+
+        // Related metrics were collected.
+        assertEquals(2, metrics.halComposeCount); // TICK+TICK, then CLICK+CLICK
+        assertEquals(3, metrics.halPerformCount); // CLICK, TICK, then CLICK
+        assertEquals(4, metrics.halCompositionSize); // 2*TICK + 2*CLICK
+        // No repetitions in reported effect/primitive IDs.
+        assertArrayEquals(new int[] {VibrationEffect.Composition.PRIMITIVE_TICK},
+                metrics.halSupportedCompositionPrimitivesUsed);
+        assertArrayEquals(new int[] {VibrationEffect.Composition.PRIMITIVE_CLICK},
+                metrics.halUnsupportedCompositionPrimitivesUsed);
+        assertArrayEquals(new int[] {VibrationEffect.EFFECT_CLICK},
+                metrics.halSupportedEffectsUsed);
+        assertArrayEquals(new int[] {VibrationEffect.EFFECT_TICK},
+                metrics.halUnsupportedEffectsUsed);
+
+        // All unrelated metrics are empty.
+        assertEquals(0, metrics.repeatCount);
+        assertEquals(0, metrics.halComposePwleCount);
+        assertEquals(0, metrics.halSetExternalControlCount);
+        assertEquals(0, metrics.halPwleSize);
+
+        // Accommodate for ramping off config that might add extra setAmplitudes
+        // for the effect that plays the fallback instead of "perform".
+        assertTrue(metrics.halOnCount > 0);
+        assertTrue(metrics.halOffCount > 0);
+        assertTrue(metrics.halSetAmplitudeCount > 0);
+    }
+
+    @Test
+    public void frameworkStats_interruptingVibrations_reportsAllMetrics() throws Exception {
+        mockVibrators(1);
+        VibratorManagerService service = createSystemReadyService();
+
+        vibrate(service, VibrationEffect.createOneShot(1_000, 128), HAPTIC_FEEDBACK_ATTRS);
+
+        // VibrationThread will start this vibration async, so wait until vibration is triggered.
+        assertTrue(waitUntil(s -> !mVibratorProviders.get(1).getAllEffectSegments().isEmpty(),
+                service, TEST_TIMEOUT_MILLIS));
+
+        vibrateAndWaitUntilFinished(service, VibrationEffect.createOneShot(10, 255), ALARM_ATTRS);
+
+        ArgumentCaptor<VibrationStats.StatsInfo> argumentCaptor =
+                ArgumentCaptor.forClass(VibrationStats.StatsInfo.class);
+        verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS).times(2))
+                .writeVibrationReportedAsync(argumentCaptor.capture());
+
+        VibrationStats.StatsInfo touchMetrics = argumentCaptor.getAllValues().get(0);
+        assertEquals(UID, touchMetrics.uid);
+        assertEquals(VibrationAttributes.USAGE_TOUCH, touchMetrics.usage);
+        assertEquals(Vibration.Status.CANCELLED_SUPERSEDED.getProtoEnumValue(),
+                touchMetrics.status);
+        assertTrue(touchMetrics.endedBySameUid);
+        assertEquals(VibrationAttributes.USAGE_ALARM, touchMetrics.endedByUsage);
+        assertEquals(-1, touchMetrics.interruptedUsage);
+
+        VibrationStats.StatsInfo alarmMetrics = argumentCaptor.getAllValues().get(1);
+        assertEquals(UID, alarmMetrics.uid);
+        assertEquals(VibrationAttributes.USAGE_ALARM, alarmMetrics.usage);
+        assertEquals(Vibration.Status.FINISHED.getProtoEnumValue(), alarmMetrics.status);
+        assertFalse(alarmMetrics.endedBySameUid);
+        assertEquals(-1, alarmMetrics.endedByUsage);
+        assertEquals(VibrationAttributes.USAGE_TOUCH, alarmMetrics.interruptedUsage);
+    }
+
+    @Test
+    public void frameworkStats_ignoredVibration_reportsStatus() throws Exception {
+        setUserSetting(Settings.System.RING_VIBRATION_INTENSITY,
+                Vibrator.VIBRATION_INTENSITY_OFF);
+
+        mockVibrators(1);
+        VibratorManagerService service = createSystemReadyService();
+        mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE);
+
+        // Haptic feedback ignored in low power state
+        vibrateAndWaitUntilFinished(service, VibrationEffect.createOneShot(100, 128),
+                HAPTIC_FEEDBACK_ATTRS);
+        // Ringtone vibration user settings are off
+        vibrateAndWaitUntilFinished(service, VibrationEffect.createOneShot(200, 128),
+                RINGTONE_ATTRS);
+
+        ArgumentCaptor<VibrationStats.StatsInfo> argumentCaptor =
+                ArgumentCaptor.forClass(VibrationStats.StatsInfo.class);
+        verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS).times(2))
+                .writeVibrationReportedAsync(argumentCaptor.capture());
+
+        VibrationStats.StatsInfo touchMetrics = argumentCaptor.getAllValues().get(0);
+        assertEquals(UID, touchMetrics.uid);
+        assertEquals(VibrationAttributes.USAGE_TOUCH, touchMetrics.usage);
+        assertEquals(Vibration.Status.IGNORED_FOR_POWER.getProtoEnumValue(), touchMetrics.status);
+
+        VibrationStats.StatsInfo ringtoneMetrics = argumentCaptor.getAllValues().get(1);
+        assertEquals(UID, ringtoneMetrics.uid);
+        assertEquals(VibrationAttributes.USAGE_RINGTONE, ringtoneMetrics.usage);
+        assertEquals(Vibration.Status.IGNORED_FOR_SETTINGS.getProtoEnumValue(),
+                ringtoneMetrics.status);
+
+        for (VibrationStats.StatsInfo metrics : argumentCaptor.getAllValues()) {
+            // Latencies are empty since vibrations never started
+            assertEquals(0, metrics.startLatencyMillis);
+            assertEquals(0, metrics.endLatencyMillis);
+            assertEquals(0, metrics.vibratorOnMillis);
+
+            // All unrelated metrics are empty.
+            assertEquals(0, metrics.repeatCount);
+            assertEquals(0, metrics.halComposeCount);
+            assertEquals(0, metrics.halComposePwleCount);
+            assertEquals(0, metrics.halOffCount);
+            assertEquals(0, metrics.halOnCount);
+            assertEquals(0, metrics.halPerformCount);
+            assertEquals(0, metrics.halSetExternalControlCount);
+            assertEquals(0, metrics.halCompositionSize);
+            assertEquals(0, metrics.halPwleSize);
+            assertNull(metrics.halSupportedCompositionPrimitivesUsed);
+            assertNull(metrics.halSupportedEffectsUsed);
+            assertNull(metrics.halUnsupportedCompositionPrimitivesUsed);
+            assertNull(metrics.halUnsupportedEffectsUsed);
+        }
+    }
+
+    @Test
+    public void frameworkStats_multiVibrators_reportsAllMetrics() throws Exception {
+        mockVibrators(1, 2);
+        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+        mVibratorProviders.get(1).setSupportedPrimitives(
+                VibrationEffect.Composition.PRIMITIVE_TICK);
+        mVibratorProviders.get(2).setSupportedEffects(VibrationEffect.EFFECT_TICK);
+
+        VibratorManagerService service = createSystemReadyService();
+        vibrateAndWaitUntilFinished(service,
+                CombinedVibration.startParallel()
+                        .addVibrator(1,
+                                VibrationEffect.startComposition()
+                                        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK)
+                                        .compose())
+                        .addVibrator(2,
+                                VibrationEffect.createPredefined(VibrationEffect.EFFECT_TICK))
+                        .combine(),
+                NOTIFICATION_ATTRS);
+
+        SparseBooleanArray expectedEffectsUsed = new SparseBooleanArray();
+        expectedEffectsUsed.put(VibrationEffect.EFFECT_TICK, true);
+
+        SparseBooleanArray expectedPrimitivesUsed = new SparseBooleanArray();
+        expectedPrimitivesUsed.put(VibrationEffect.Composition.PRIMITIVE_TICK, true);
+
+        verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS))
+                .writeVibratorStateOnAsync(eq(UID), anyLong());
+        verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS))
+                .writeVibratorStateOffAsync(eq(UID));
+
+        ArgumentCaptor<VibrationStats.StatsInfo> argumentCaptor =
+                ArgumentCaptor.forClass(VibrationStats.StatsInfo.class);
+        verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS))
+                .writeVibrationReportedAsync(argumentCaptor.capture());
+
+        VibrationStats.StatsInfo metrics = argumentCaptor.getValue();
+        assertEquals(UID, metrics.uid);
+        assertEquals(FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__SINGLE,
+                metrics.vibrationType);
+        assertEquals(VibrationAttributes.USAGE_NOTIFICATION, metrics.usage);
+        assertEquals(Vibration.Status.FINISHED.getProtoEnumValue(), metrics.status);
+        assertTrue(metrics.totalDurationMillis >= 20);
+
+        // vibratorOnMillis accumulates both vibrators, it's 20 for each constant.
+        assertEquals(40, metrics.vibratorOnMillis);
+
+        // Related metrics were collected.
+        assertEquals(1, metrics.halComposeCount);
+        assertEquals(1, metrics.halPerformCount);
+        assertEquals(1, metrics.halCompositionSize);
+        assertEquals(2, metrics.halOffCount);
+        assertArrayEquals(new int[] {VibrationEffect.Composition.PRIMITIVE_TICK},
+                metrics.halSupportedCompositionPrimitivesUsed);
+        assertArrayEquals(new int[] {VibrationEffect.EFFECT_TICK},
+                metrics.halSupportedEffectsUsed);
+
+        // All unrelated metrics are empty.
+        assertEquals(0, metrics.repeatCount);
+        assertEquals(0, metrics.halComposePwleCount);
+        assertEquals(0, metrics.halOnCount);
+        assertEquals(0, metrics.halSetAmplitudeCount);
+        assertEquals(0, metrics.halSetExternalControlCount);
+        assertEquals(0, metrics.halPwleSize);
+        assertNull(metrics.halUnsupportedCompositionPrimitivesUsed);
+        assertNull(metrics.halUnsupportedEffectsUsed);
+    }
+
+    private VibrationEffectSegment expectedPrebaked(int effectId) {
+        return expectedPrebaked(effectId, VibrationEffect.EFFECT_STRENGTH_MEDIUM);
+    }
+
+    private VibrationEffectSegment expectedPrebaked(int effectId, int effectStrength) {
+        return new PrebakedSegment(effectId, false, effectStrength);
+    }
+
+    private void mockCapabilities(long... capabilities) {
+        when(mNativeWrapperMock.getCapabilities()).thenReturn(
+                Arrays.stream(capabilities).reduce(0, (a, b) -> a | b));
+    }
+
+    private void mockVibrators(int... vibratorIds) {
+        for (int vibratorId : vibratorIds) {
+            mVibratorProviders.put(vibratorId,
+                    new FakeVibratorControllerProvider(mTestLooper.getLooper()));
+        }
+        when(mNativeWrapperMock.getVibratorIds()).thenReturn(vibratorIds);
+    }
+
+    private void cancelVibrate(VibratorManagerService service) {
+        service.cancelVibrate(VibrationAttributes.USAGE_FILTER_MATCH_ALL, service);
+    }
+
+    private IVibratorStateListener mockVibratorStateListener() {
+        IVibratorStateListener listenerMock = mock(IVibratorStateListener.class);
+        IBinder binderMock = mock(IBinder.class);
+        when(listenerMock.asBinder()).thenReturn(binderMock);
+        return listenerMock;
+    }
+
+    private InputDevice createInputDeviceWithVibrator(int id) {
+        return new InputDevice.Builder()
+                .setId(id)
+                .setName("Test Device " + id)
+                .setHasVibrator(true)
+                .build();
+    }
+
+    private static <T> void addLocalServiceMock(Class<T> clazz, T mock) {
+        LocalServices.removeServiceForTest(clazz);
+        LocalServices.addService(clazz, mock);
+    }
+
+    private void setRingerMode(int ringerMode) {
+        when(mAudioManagerMock.getRingerModeInternal()).thenReturn(ringerMode);
+    }
+
+    private void setUserSetting(String settingName, int value) {
+        Settings.System.putIntForUser(
+                mContextSpy.getContentResolver(), settingName, value, UserHandle.USER_CURRENT);
+    }
+
+    private void vibrateAndWaitUntilFinished(VibratorManagerService service, VibrationEffect effect,
+            VibrationAttributes attrs) throws InterruptedException {
+        vibrateAndWaitUntilFinished(service, CombinedVibration.createParallel(effect), attrs);
+    }
+
+    private void vibrateAndWaitUntilFinished(VibratorManagerService service,
+            CombinedVibration effect, VibrationAttributes attrs) throws InterruptedException {
+        HalVibration vib =
+                service.vibrateInternal(UID, Display.DEFAULT_DISPLAY, PACKAGE_NAME, effect, attrs,
+                        "some reason", service);
+        if (vib != null) {
+            vib.waitForEnd();
+        }
+    }
+
+    private void vibrate(VibratorManagerService service, VibrationEffect effect,
+            VibrationAttributes attrs) {
+        vibrate(service, CombinedVibration.createParallel(effect), attrs);
+    }
+
+    private void vibrate(VibratorManagerService service, CombinedVibration effect,
+            VibrationAttributes attrs) {
+        vibrateWithDisplay(service, Display.DEFAULT_DISPLAY, effect, attrs);
+    }
+
+    private void vibrateWithDisplay(VibratorManagerService service, int displayId,
+            CombinedVibration effect, VibrationAttributes attrs) {
+        service.vibrate(UID, displayId, PACKAGE_NAME, effect, attrs, "some reason", service);
+    }
+
+    private boolean waitUntil(Predicate<VibratorManagerService> predicate,
+            VibratorManagerService service, long timeout) throws InterruptedException {
+        long timeoutTimestamp = SystemClock.uptimeMillis() + timeout;
+        boolean predicateResult = false;
+        while (!predicateResult && SystemClock.uptimeMillis() < timeoutTimestamp) {
+            Thread.sleep(10);
+            predicateResult = predicate.test(service);
+        }
+        return predicateResult;
+    }
+}
diff --git a/services/tests/vibrator/utils/android/os/test/FakeVibrator.java b/services/tests/vibrator/utils/android/os/test/FakeVibrator.java
new file mode 100644
index 0000000..56f49d4e
--- /dev/null
+++ b/services/tests/vibrator/utils/android/os/test/FakeVibrator.java
@@ -0,0 +1,54 @@
+/*
+ * 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.
+ */
+
+package android.os.test;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.os.VibrationAttributes;
+import android.os.VibrationEffect;
+import android.os.Vibrator;
+
+/** Fake implementation of {@link Vibrator} for service tests. */
+public final class FakeVibrator extends Vibrator {
+
+    public FakeVibrator(Context context) {
+        super(context);
+    }
+
+    @Override
+    public boolean hasVibrator() {
+        return true;
+    }
+
+    @Override
+    public boolean hasAmplitudeControl() {
+        return true;
+    }
+
+    @Override
+    public void vibrate(int uid, String opPkg, @NonNull VibrationEffect vibe, String reason,
+            @NonNull VibrationAttributes attributes) {
+    }
+
+    @Override
+    public void cancel() {
+    }
+
+    @Override
+    public void cancel(int usageFilter) {
+    }
+}
diff --git a/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorControllerProvider.java b/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorControllerProvider.java
new file mode 100644
index 0000000..12815fa
--- /dev/null
+++ b/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorControllerProvider.java
@@ -0,0 +1,379 @@
+/*
+ * 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.
+ */
+
+package com.android.server.vibrator;
+
+import android.annotation.Nullable;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.VibrationEffect;
+import android.os.VibratorInfo;
+import android.os.vibrator.PrebakedSegment;
+import android.os.vibrator.PrimitiveSegment;
+import android.os.vibrator.RampSegment;
+import android.os.vibrator.StepSegment;
+import android.os.vibrator.VibrationEffectSegment;
+
+import com.android.server.vibrator.VibratorController.OnVibrationCompleteListener;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+/**
+ * Provides {@link VibratorController} with configurable vibrator hardware capabilities and
+ * fake interactions for tests.
+ */
+public final class FakeVibratorControllerProvider {
+    private static final int EFFECT_DURATION = 20;
+
+    private final Map<Long, PrebakedSegment> mEnabledAlwaysOnEffects = new HashMap<>();
+    private final Map<Long, List<VibrationEffectSegment>> mEffectSegments = new TreeMap<>();
+    private final Map<Long, List<Integer>> mBraking = new HashMap<>();
+    private final List<Float> mAmplitudes = new ArrayList<>();
+    private final List<Boolean> mExternalControlStates = new ArrayList<>();
+    private final Handler mHandler;
+    private final FakeNativeWrapper mNativeWrapper;
+
+    private boolean mIsAvailable = true;
+    private boolean mIsInfoLoadSuccessful = true;
+    private long mOnLatency;
+    private long mOffLatency;
+    private int mOffCount;
+
+    private int mCapabilities;
+    private int[] mSupportedEffects;
+    private int[] mSupportedBraking;
+    private int[] mSupportedPrimitives;
+    private int mCompositionSizeMax;
+    private int mPwleSizeMax;
+    private float mMinFrequency = Float.NaN;
+    private float mResonantFrequency = Float.NaN;
+    private float mFrequencyResolution = Float.NaN;
+    private float mQFactor = Float.NaN;
+    private float[] mMaxAmplitudes;
+
+    void recordEffectSegment(long vibrationId, VibrationEffectSegment segment) {
+        mEffectSegments.computeIfAbsent(vibrationId, k -> new ArrayList<>()).add(segment);
+    }
+
+    void recordBraking(long vibrationId, int braking) {
+        mBraking.computeIfAbsent(vibrationId, k -> new ArrayList<>()).add(braking);
+    }
+
+    private final class FakeNativeWrapper extends VibratorController.NativeWrapper {
+        public int vibratorId;
+        public OnVibrationCompleteListener listener;
+        public boolean isInitialized;
+
+        @Override
+        public void init(int vibratorId, OnVibrationCompleteListener listener) {
+            isInitialized = true;
+            this.vibratorId = vibratorId;
+            this.listener = listener;
+        }
+
+        @Override
+        public boolean isAvailable() {
+            return mIsAvailable;
+        }
+
+        @Override
+        public long on(long milliseconds, long vibrationId) {
+            recordEffectSegment(vibrationId, new StepSegment(VibrationEffect.DEFAULT_AMPLITUDE,
+                    /* frequencyHz= */ 0, (int) milliseconds));
+            applyLatency(mOnLatency);
+            scheduleListener(milliseconds, vibrationId);
+            return milliseconds;
+        }
+
+        @Override
+        public void off() {
+            mOffCount++;
+            applyLatency(mOffLatency);
+        }
+
+        @Override
+        public void setAmplitude(float amplitude) {
+            mAmplitudes.add(amplitude);
+            applyLatency(mOnLatency);
+        }
+
+        @Override
+        public long perform(long effect, long strength, long vibrationId) {
+            if (mSupportedEffects == null
+                    || Arrays.binarySearch(mSupportedEffects, (int) effect) < 0) {
+                return 0;
+            }
+            recordEffectSegment(vibrationId,
+                    new PrebakedSegment((int) effect, false, (int) strength));
+            applyLatency(mOnLatency);
+            scheduleListener(EFFECT_DURATION, vibrationId);
+            return EFFECT_DURATION;
+        }
+
+        @Override
+        public long compose(PrimitiveSegment[] primitives, long vibrationId) {
+            if (mSupportedPrimitives == null) {
+                return 0;
+            }
+            for (PrimitiveSegment primitive : primitives) {
+                if (Arrays.binarySearch(mSupportedPrimitives, primitive.getPrimitiveId()) < 0) {
+                    return 0;
+                }
+            }
+            long duration = 0;
+            for (PrimitiveSegment primitive : primitives) {
+                duration += EFFECT_DURATION + primitive.getDelay();
+                recordEffectSegment(vibrationId, primitive);
+            }
+            applyLatency(mOnLatency);
+            scheduleListener(duration, vibrationId);
+            return duration;
+        }
+
+        @Override
+        public long composePwle(RampSegment[] primitives, int braking, long vibrationId) {
+            long duration = 0;
+            for (RampSegment primitive : primitives) {
+                duration += primitive.getDuration();
+                recordEffectSegment(vibrationId, primitive);
+            }
+            recordBraking(vibrationId, braking);
+            applyLatency(mOnLatency);
+            scheduleListener(duration, vibrationId);
+            return duration;
+        }
+
+        @Override
+        public void setExternalControl(boolean enabled) {
+            mExternalControlStates.add(enabled);
+        }
+
+        @Override
+        public void alwaysOnEnable(long id, long effect, long strength) {
+            PrebakedSegment prebaked = new PrebakedSegment((int) effect, false, (int) strength);
+            mEnabledAlwaysOnEffects.put(id, prebaked);
+        }
+
+        @Override
+        public void alwaysOnDisable(long id) {
+            mEnabledAlwaysOnEffects.remove(id);
+        }
+
+        @Override
+        public boolean getInfo(VibratorInfo.Builder infoBuilder) {
+            infoBuilder.setCapabilities(mCapabilities);
+            infoBuilder.setSupportedBraking(mSupportedBraking);
+            infoBuilder.setPwleSizeMax(mPwleSizeMax);
+            infoBuilder.setSupportedEffects(mSupportedEffects);
+            if (mSupportedPrimitives != null) {
+                for (int primitive : mSupportedPrimitives) {
+                    infoBuilder.setSupportedPrimitive(primitive, EFFECT_DURATION);
+                }
+            }
+            infoBuilder.setCompositionSizeMax(mCompositionSizeMax);
+            infoBuilder.setQFactor(mQFactor);
+            infoBuilder.setFrequencyProfile(new VibratorInfo.FrequencyProfile(
+                    mResonantFrequency, mMinFrequency, mFrequencyResolution, mMaxAmplitudes));
+            return mIsInfoLoadSuccessful;
+        }
+
+        private void applyLatency(long latencyMillis) {
+            try {
+                if (latencyMillis > 0) {
+                    Thread.sleep(latencyMillis);
+                }
+            } catch (InterruptedException e) {
+            }
+        }
+
+        private void scheduleListener(long vibrationDuration, long vibrationId) {
+            mHandler.postDelayed(() -> listener.onComplete(vibratorId, vibrationId),
+                    vibrationDuration);
+        }
+    }
+
+    public FakeVibratorControllerProvider(Looper looper) {
+        mHandler = new Handler(looper);
+        mNativeWrapper = new FakeNativeWrapper();
+    }
+
+    public VibratorController newVibratorController(
+            int vibratorId, OnVibrationCompleteListener listener) {
+        return new VibratorController(vibratorId, listener, mNativeWrapper);
+    }
+
+    /** Return {@code true} if this controller was initialized. */
+    public boolean isInitialized() {
+        return mNativeWrapper.isInitialized;
+    }
+
+    /**
+     * Disable fake vibrator hardware, mocking a state where the underlying service is unavailable.
+     */
+    public void disableVibrators() {
+        mIsAvailable = false;
+    }
+
+    /**
+     * Sets the result for the method that loads the {@link VibratorInfo}, for faking a vibrator
+     * that fails to load some of the hardware data.
+     */
+    public void setVibratorInfoLoadSuccessful(boolean successful) {
+        mIsInfoLoadSuccessful = successful;
+    }
+
+    /**
+     * Sets the latency this controller should fake for turning the vibrator hardware on or setting
+     * the vibration amplitude.
+     */
+    public void setOnLatency(long millis) {
+        mOnLatency = millis;
+    }
+
+    /** Sets the latency this controller should fake for turning the vibrator off. */
+    public void setOffLatency(long millis) {
+        mOffLatency = millis;
+    }
+
+    /** Set the capabilities of the fake vibrator hardware. */
+    public void setCapabilities(int... capabilities) {
+        mCapabilities = Arrays.stream(capabilities).reduce(0, (a, b) -> a | b);
+    }
+
+    /** Set the effects supported by the fake vibrator hardware. */
+    public void setSupportedEffects(int... effects) {
+        if (effects != null) {
+            effects = Arrays.copyOf(effects, effects.length);
+            Arrays.sort(effects);
+        }
+        mSupportedEffects = effects;
+    }
+
+    /** Set the effects supported by the fake vibrator hardware. */
+    public void setSupportedBraking(int... braking) {
+        if (braking != null) {
+            braking = Arrays.copyOf(braking, braking.length);
+            Arrays.sort(braking);
+        }
+        mSupportedBraking = braking;
+    }
+
+    /** Set the primitives supported by the fake vibrator hardware. */
+    public void setSupportedPrimitives(int... primitives) {
+        if (primitives != null) {
+            primitives = Arrays.copyOf(primitives, primitives.length);
+            Arrays.sort(primitives);
+        }
+        mSupportedPrimitives = primitives;
+    }
+
+    /** Set the max number of primitives allowed in a composition by the fake vibrator hardware. */
+    public void setCompositionSizeMax(int compositionSizeMax) {
+        mCompositionSizeMax = compositionSizeMax;
+    }
+
+    /** Set the max number of PWLEs allowed in a composition by the fake vibrator hardware. */
+    public void setPwleSizeMax(int pwleSizeMax) {
+        mPwleSizeMax = pwleSizeMax;
+    }
+
+    /** Set the resonant frequency of the fake vibrator hardware. */
+    public void setResonantFrequency(float frequencyHz) {
+        mResonantFrequency = frequencyHz;
+    }
+
+    /** Set the minimum frequency of the fake vibrator hardware. */
+    public void setMinFrequency(float frequencyHz) {
+        mMinFrequency = frequencyHz;
+    }
+
+    /** Set the frequency resolution of the fake vibrator hardware. */
+    public void setFrequencyResolution(float frequencyHz) {
+        mFrequencyResolution = frequencyHz;
+    }
+
+    /** Set the Q factor of the fake vibrator hardware. */
+    public void setQFactor(float qFactor) {
+        mQFactor = qFactor;
+    }
+
+    /** Set the max amplitude supported for each frequency f the fake vibrator hardware. */
+    public void setMaxAmplitudes(float... maxAmplitudes) {
+        mMaxAmplitudes = maxAmplitudes;
+    }
+
+    /**
+     * Return the amplitudes set by this controller, including zeroes for each time the vibrator was
+     * turned off.
+     */
+    public List<Float> getAmplitudes() {
+        return new ArrayList<>(mAmplitudes);
+    }
+
+    /** Return the braking values passed to the compose PWLE method. */
+    public List<Integer> getBraking(long vibrationId) {
+        if (mBraking.containsKey(vibrationId)) {
+            return new ArrayList<>(mBraking.get(vibrationId));
+        } else {
+            return new ArrayList<>();
+        }
+    }
+
+    /** Return list of {@link VibrationEffectSegment} played by this controller, in order. */
+    public List<VibrationEffectSegment> getEffectSegments(long vibrationId) {
+        if (mEffectSegments.containsKey(vibrationId)) {
+            return new ArrayList<>(mEffectSegments.get(vibrationId));
+        } else {
+            return new ArrayList<>();
+        }
+    }
+
+    /**
+     * Returns a list of all vibrations' effect segments, for external-use where vibration IDs
+     * aren't exposed.
+     */
+    public List<VibrationEffectSegment> getAllEffectSegments() {
+        // Returns segments in order of vibrationId, which increases over time. TreeMap gives order.
+        ArrayList<VibrationEffectSegment> result = new ArrayList<>();
+        for (List<VibrationEffectSegment> subList : mEffectSegments.values()) {
+            result.addAll(subList);
+        }
+        return result;
+    }
+    /** Return list of states set for external control to the fake vibrator hardware. */
+    public List<Boolean> getExternalControlStates() {
+        return mExternalControlStates;
+    }
+
+    /** Returns the number of times the vibrator was turned off. */
+    public int getOffCount() {
+        return mOffCount;
+    }
+
+    /**
+     * Return the {@link PrebakedSegment} effect enabled with given id, or {@code null} if
+     * missing or disabled.
+     */
+    @Nullable
+    public PrebakedSegment getAlwaysOnEffect(int id) {
+        return mEnabledAlwaysOnEffects.get((long) id);
+    }
+}
diff --git a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLoggingLatencyTest.java b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLoggingLatencyTest.java
index 6a1674b..efe1af3 100644
--- a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLoggingLatencyTest.java
+++ b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLoggingLatencyTest.java
@@ -38,13 +38,17 @@
 import android.os.Process;
 import android.os.RemoteException;
 
+import androidx.test.filters.FlakyTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 
 import com.android.internal.util.FakeLatencyTracker;
+import com.android.modules.utils.testing.TestableDeviceConfig.TestableDeviceConfigRule;
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.Timeout;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 import org.mockito.ArgumentCaptor;
@@ -52,8 +56,14 @@
 import org.mockito.MockitoAnnotations;
 
 @RunWith(JUnit4.class)
+@FlakyTest(bugId = 275746222)
 public class SoundTriggerMiddlewareLoggingLatencyTest {
 
+    @Rule
+    public TestableDeviceConfigRule mDeviceConfigRule = new TestableDeviceConfigRule();
+    @Rule
+    public Timeout mGlobalTimeout = Timeout.seconds(30);
+
     private FakeLatencyTracker mLatencyTracker;
     @Mock
     private BatteryStatsInternal mBatteryStatsInternal;
diff --git a/services/tests/wmtests/Android.bp b/services/tests/wmtests/Android.bp
index e5371975..a0e6cf5 100644
--- a/services/tests/wmtests/Android.bp
+++ b/services/tests/wmtests/Android.bp
@@ -49,6 +49,7 @@
     static_libs: [
         "frameworks-base-testutils",
         "services.core",
+        "service-permission.stubs.system_server",
         "androidx.test.runner",
         "androidx.test.rules",
         "mockito-target-extended-minus-junit4",
@@ -57,7 +58,6 @@
         "testng",
         "truth-prebuilt",
         "testables",
-        "ub-uiautomator",
         "hamcrest-library",
         "platform-compat-test-rules",
         "CtsSurfaceValidatorLib",
@@ -82,7 +82,10 @@
     ],
 
     platform_apis: true,
-    test_suites: ["device-tests"],
+    test_suites: [
+        "device-tests",
+        "automotive-tests",
+    ],
 
     certificate: "platform",
 
diff --git a/services/tests/wmtests/AndroidManifest.xml b/services/tests/wmtests/AndroidManifest.xml
index 37e5da5..554b0f4 100644
--- a/services/tests/wmtests/AndroidManifest.xml
+++ b/services/tests/wmtests/AndroidManifest.xml
@@ -96,10 +96,19 @@
             android:theme="@style/WhiteBackgroundTheme"
             android:exported="true"/>
 
-        <service android:name="android.view.cts.surfacevalidator.LocalMediaProjectionService"
-            android:foregroundServiceType="mediaProjection"
-            android:enabled="true">
-        </service>
+        <activity android:name="com.android.server.wm.TrustedPresentationCallbackTest$TestActivity"
+            android:exported="true"
+            android:showWhenLocked="true"
+            android:turnScreenOn="true" />
+
+        <activity
+            android:name="androidx.test.core.app.InstrumentationActivityInvoker$EmptyActivity"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+            </intent-filter>
+        </activity>
+
     </application>
 
     <instrumentation
diff --git a/services/tests/wmtests/OWNERS b/services/tests/wmtests/OWNERS
index 7a128fc..cece37f 100644
--- a/services/tests/wmtests/OWNERS
+++ b/services/tests/wmtests/OWNERS
@@ -1,3 +1,4 @@
+# Bug template url: https://b.corp.google.com/issues/new?component=316125&template=1018199
 include /services/core/java/com/android/server/wm/OWNERS
 
 # Voice Interaction
diff --git a/services/tests/wmtests/src/com/android/server/policy/PowerKeyGestureTests.java b/services/tests/wmtests/src/com/android/server/policy/PowerKeyGestureTests.java
index 5863e9d..c3b7849 100644
--- a/services/tests/wmtests/src/com/android/server/policy/PowerKeyGestureTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/PowerKeyGestureTests.java
@@ -21,6 +21,7 @@
 import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_POWER_ASSISTANT;
 import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_POWER_GLOBAL_ACTIONS;
 import static com.android.server.policy.PhoneWindowManager.SHORT_PRESS_POWER_DREAM_OR_SLEEP;
+import static com.android.server.policy.PhoneWindowManager.SHORT_PRESS_POWER_GO_TO_SLEEP;
 
 import android.provider.Settings;
 import android.view.Display;
@@ -39,6 +40,7 @@
      */
     @Test
     public void testPowerSinglePress() {
+        mPhoneWindowManager.overrideShortPressOnPower(SHORT_PRESS_POWER_GO_TO_SLEEP);
         sendKey(KEYCODE_POWER);
         mPhoneWindowManager.assertPowerSleep();
 
@@ -58,9 +60,20 @@
         mPhoneWindowManager.overrideCanStartDreaming(true);
         sendKey(KEYCODE_POWER);
         mPhoneWindowManager.assertDreamRequest();
+        mPhoneWindowManager.overrideIsDreaming(true);
         mPhoneWindowManager.assertLockedAfterAppTransitionFinished();
     }
 
+    @Test
+    public void testAppTransitionFinishedCalledAfterDreamStoppedWillNotLockAgain() {
+        mPhoneWindowManager.overrideShortPressOnPower(SHORT_PRESS_POWER_DREAM_OR_SLEEP);
+        mPhoneWindowManager.overrideCanStartDreaming(true);
+        sendKey(KEYCODE_POWER);
+        mPhoneWindowManager.assertDreamRequest();
+        mPhoneWindowManager.overrideIsDreaming(false);
+        mPhoneWindowManager.assertDidNotLockAfterAppTransitionFinished();
+    }
+
     /**
      * Power double-press to launch camera does not lock device when the single press behavior is to
      * dream.
@@ -72,7 +85,7 @@
         sendKey(KEYCODE_POWER);
         sendKey(KEYCODE_POWER);
         mPhoneWindowManager.assertCameraLaunch();
-        mPhoneWindowManager.assertWillNotLockAfterAppTransitionFinished();
+        mPhoneWindowManager.assertDidNotLockAfterAppTransitionFinished();
     }
 
     /**
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 676bfb0..2015ae9 100644
--- a/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
+++ b/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
@@ -39,6 +39,7 @@
 
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
 import static com.android.server.policy.WindowManagerPolicy.ACTION_PASS_TO_USER;
 
 import static java.util.Collections.unmodifiableMap;
@@ -59,7 +60,7 @@
 
 class ShortcutKeyTestBase {
     TestPhoneWindowManager mPhoneWindowManager;
-    final Context mContext = getInstrumentation().getTargetContext();
+    final Context mContext = spy(getInstrumentation().getTargetContext());
 
     /** Modifier key to meta state */
     private static final Map<Integer, Integer> MODIFIER;
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 280afba..3bb86a7 100644
--- a/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java
@@ -316,4 +316,16 @@
         pressKey(KEYCODE_POWER, 0 /* pressTime */);
         assertTrue(mShortPressed.await(mWaitTimeout, TimeUnit.MILLISECONDS));
     }
+
+    // Verify short press should not be triggered if no very long press behavior defined but the
+    // press time exceeded the very long press timeout.
+    @Test
+    public void testTimeoutExceedVeryLongPress() throws InterruptedException {
+        mVeryLongPressOnPowerBehavior = false;
+
+        pressKey(KEYCODE_POWER, mVeryLongPressTime + 50);
+        assertTrue(mLongPressed.await(mWaitTimeout, TimeUnit.MILLISECONDS));
+        assertEquals(mVeryLongPressed.getCount(), 1);
+        assertEquals(mShortPressed.getCount(), 1);
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java b/services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java
new file mode 100644
index 0000000..fe8017e
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java
@@ -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.server.policy;
+
+import static android.view.KeyEvent.KEYCODE_STEM_PRIMARY;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.server.policy.PhoneWindowManager.SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+
+import android.content.Context;
+import android.content.res.Resources;
+
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+
+/**
+ * Test class for stem key gesture.
+ *
+ * Build/Install/Run:
+ * atest WmTests:StemKeyGestureTests
+ */
+public class StemKeyGestureTests extends ShortcutKeyTestBase {
+    @Mock private Resources mResources;
+
+    /**
+     * Stem single key should not launch behavior during set up.
+     */
+    @Test
+    public void stemSingleKey_duringSetup_doNothing() {
+        stemKeySetup(
+                () -> overrideBehavior(
+                        com.android.internal.R.integer.config_shortPressOnStemPrimaryBehavior,
+                        SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS));
+        mPhoneWindowManager.setKeyguardServiceDelegateIsShowing(false);
+        mPhoneWindowManager.overrideIsUserSetupComplete(false);
+
+        sendKey(KEYCODE_STEM_PRIMARY);
+
+        mPhoneWindowManager.assertNotOpenAllAppView();
+    }
+
+    /**
+     * Stem single key should launch all app after set up.
+     */
+    @Test
+    public void stemSingleKey_AfterSetup_openAllApp() {
+        stemKeySetup(
+                () -> overrideBehavior(
+                        com.android.internal.R.integer.config_shortPressOnStemPrimaryBehavior,
+                        SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS));
+        mPhoneWindowManager.setKeyguardServiceDelegateIsShowing(false);
+        mPhoneWindowManager.overrideIsUserSetupComplete(true);
+
+        sendKey(KEYCODE_STEM_PRIMARY);
+
+        mPhoneWindowManager.assertOpenAllAppView();
+    }
+
+    private void stemKeySetup(Runnable behaviorOverrideRunnable) {
+        super.tearDown();
+        setupResourcesMock();
+        behaviorOverrideRunnable.run();
+        super.setUp();
+    }
+
+    private void setupResourcesMock() {
+        Resources realResources = mContext.getResources();
+
+        mResources = Mockito.mock(Resources.class);
+        doReturn(mResources).when(mContext).getResources();
+
+        doAnswer(invocation -> realResources.getXml((Integer) invocation.getArguments()[0]))
+                .when(mResources).getXml(anyInt());
+        doAnswer(invocation -> realResources.getString((Integer) invocation.getArguments()[0]))
+                .when(mResources).getString(anyInt());
+        doAnswer(invocation -> realResources.getBoolean((Integer) invocation.getArguments()[0]))
+                .when(mResources).getBoolean(anyInt());
+    }
+
+    private void overrideBehavior(int resId, int expectedBehavior) {
+        doReturn(expectedBehavior).when(mResources).getInteger(eq(resId));
+    }
+}
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 d383024..766a88f 100644
--- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
+++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
@@ -16,6 +16,7 @@
 
 package com.android.server.policy;
 
+import static android.os.Build.HW_TIMEOUT_MULTIPLIER;
 import static android.provider.Settings.Secure.VOLUME_HUSH_MUTE;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.STATE_ON;
@@ -43,8 +44,11 @@
 import static com.android.server.policy.PhoneWindowManager.POWER_VOLUME_UP_BEHAVIOR_MUTE;
 
 import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.isNull;
 import static org.mockito.Mockito.CALLS_REAL_METHODS;
+import static org.mockito.Mockito.after;
 import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mockingDetails;
 import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.withSettings;
@@ -61,9 +65,11 @@
 import android.media.AudioManagerInternal;
 import android.os.Handler;
 import android.os.HandlerThread;
+import android.os.IBinder;
 import android.os.PowerManager;
 import android.os.PowerManagerInternal;
 import android.os.RemoteException;
+import android.os.UserHandle;
 import android.os.Vibrator;
 import android.service.dreams.DreamManagerInternal;
 import android.telecom.TelecomManager;
@@ -79,6 +85,7 @@
 import com.android.server.input.InputManagerInternal;
 import com.android.server.inputmethod.InputMethodManagerInternal;
 import com.android.server.pm.UserManagerInternal;
+import com.android.server.policy.keyguard.KeyguardServiceDelegate;
 import com.android.server.statusbar.StatusBarManagerInternal;
 import com.android.server.vr.VrManagerInternal;
 import com.android.server.wm.ActivityTaskManagerInternal;
@@ -100,6 +107,8 @@
 
 class TestPhoneWindowManager {
     private static final long SHORTCUT_KEY_DELAY_MILLIS = 150;
+    private static final long TEST_SINGLE_KEY_DELAY_MILLIS
+            = SingleKeyGestureDetector.MULTI_PRESS_TIMEOUT + 1000L * HW_TIMEOUT_MULTIPLIER;
 
     private PhoneWindowManager mPhoneWindowManager;
     private Context mContext;
@@ -134,6 +143,8 @@
 
     @Mock private StatusBarManagerInternal mStatusBarManagerInternal;
 
+    @Mock private KeyguardServiceDelegate mKeyguardServiceDelegate;
+
     private StaticMockitoSession mMockitoSession;
     private HandlerThread mHandlerThread;
     private Handler mHandler;
@@ -151,6 +162,10 @@
         Supplier<GlobalActions> getGlobalActionsFactory() {
             return () -> mGlobalActions;
         }
+
+        KeyguardServiceDelegate getKeyguardServiceDelegate() {
+            return mKeyguardServiceDelegate;
+        }
     }
 
     TestPhoneWindowManager(Context context) {
@@ -158,12 +173,12 @@
         mHandlerThread = new HandlerThread("fake window manager");
         mHandlerThread.start();
         mHandler = new Handler(mHandlerThread.getLooper());
-        mHandler.runWithScissors(()-> setUp(context),  0 /* timeout */);
+        mContext = mockingDetails(context).isSpy() ? context : spy(context);
+        mHandler.runWithScissors(this::setUp,  0 /* timeout */);
     }
 
-    private void setUp(Context context) {
+    private void setUp() {
         mPhoneWindowManager = spy(new PhoneWindowManager());
-        mContext = spy(context);
 
         // Use stubOnly() to reduce memory usage if it doesn't need verification.
         final MockSettings spyStubOnly = withSettings().stubOnly()
@@ -251,6 +266,7 @@
         overrideLaunchAccessibility();
         doReturn(false).when(mPhoneWindowManager).keyguardOn();
         doNothing().when(mContext).startActivityAsUser(any(), any());
+        doNothing().when(mContext).startActivityAsUser(any(), any(), any());
         Mockito.reset(mContext);
     }
 
@@ -329,6 +345,10 @@
         doReturn(canDream).when(mDreamManagerInternal).canStartDreaming(anyBoolean());
     }
 
+    void overrideIsDreaming(boolean isDreaming) {
+        doReturn(isDreaming).when(mDreamManagerInternal).isDreaming();
+    }
+
     void overrideDisplayState(int state) {
         doReturn(state).when(mDisplay).getState();
         doReturn(state == STATE_ON).when(mDisplayPolicy).isAwake();
@@ -381,6 +401,14 @@
         doNothing().when(mPhoneWindowManager).launchHomeFromHotKey(anyInt());
     }
 
+    void overrideIsUserSetupComplete(boolean isCompleted) {
+        doReturn(isCompleted).when(mPhoneWindowManager).isUserSetupComplete();
+    }
+
+    void setKeyguardServiceDelegateIsShowing(boolean isShowing) {
+        doReturn(isShowing).when(mKeyguardServiceDelegate).isShowing();
+    }
+
     /**
      * Below functions will check the policy behavior could be invoked.
      */
@@ -497,21 +525,42 @@
         verify(mInputManagerInternal).toggleCapsLock(anyInt());
     }
 
-    void assertWillNotLockAfterAppTransitionFinished() {
-        Assert.assertFalse(mPhoneWindowManager.mLockAfterAppTransitionFinished);
-    }
-
     void assertLockedAfterAppTransitionFinished() {
         ArgumentCaptor<AppTransitionListener> transitionCaptor =
                 ArgumentCaptor.forClass(AppTransitionListener.class);
         verify(mWindowManagerInternal).registerAppTransitionListener(
                 transitionCaptor.capture());
-        transitionCaptor.getValue().onAppTransitionFinishedLocked(any());
+        final IBinder token = mock(IBinder.class);
+        transitionCaptor.getValue().onAppTransitionFinishedLocked(token);
         verify(mPhoneWindowManager).lockNow(null);
     }
 
+    void assertDidNotLockAfterAppTransitionFinished() {
+        ArgumentCaptor<AppTransitionListener> transitionCaptor =
+                ArgumentCaptor.forClass(AppTransitionListener.class);
+        verify(mWindowManagerInternal).registerAppTransitionListener(
+                transitionCaptor.capture());
+        final IBinder token = mock(IBinder.class);
+        transitionCaptor.getValue().onAppTransitionFinishedLocked(token);
+        verify(mPhoneWindowManager, never()).lockNow(null);
+    }
+
     void assertGoToHomescreen() {
         waitForIdle();
         verify(mPhoneWindowManager).launchHomeFromHotKey(anyInt());
     }
+
+    void assertOpenAllAppView() {
+        waitForIdle();
+        ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mContext, timeout(TEST_SINGLE_KEY_DELAY_MILLIS))
+                .startActivityAsUser(intentCaptor.capture(), isNull(), any(UserHandle.class));
+        Assert.assertEquals(Intent.ACTION_ALL_APPS, intentCaptor.getValue().getAction());
+    }
+
+    void assertNotOpenAllAppView() {
+        waitForIdle();
+        verify(mContext, after(TEST_SINGLE_KEY_DELAY_MILLIS).never())
+                .startActivityAsUser(any(Intent.class), any(), any(UserHandle.class));
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java b/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java
index fe1ea0d..f6f3f03 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java
@@ -22,11 +22,17 @@
 
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.verify;
 
 import android.app.Activity;
 import android.app.ActivityManager.RunningTaskInfo;
@@ -41,6 +47,7 @@
 import android.os.Bundle;
 import android.os.IBinder;
 import android.platform.test.annotations.Presubmit;
+import android.util.Log;
 import android.util.Rational;
 import android.view.SurfaceControl;
 import android.window.TaskOrganizer;
@@ -48,7 +55,10 @@
 import androidx.test.filters.MediumTest;
 
 import org.junit.Test;
+import org.mockito.ArgumentCaptor;
 
+import java.util.ArrayList;
+import java.util.List;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
@@ -166,6 +176,122 @@
         }
     }
 
+    /**
+     * Tests if any unknown key is being used in the ActivityOptions bundle. If so, please review
+     * if the newly added bundle should be protected with permissions to avoid malicious attacks.
+     *
+     * @see SafeActivityOptionsTest#test_getOptions
+     */
+    @Test
+    public void testActivityOptionsFromBundle() {
+        // Spy on a bundle that is generated from a basic ActivityOptions.
+        final ActivityOptions options = ActivityOptions.makeBasic();
+        Bundle bundle = options.toBundle();
+        spyOn(bundle);
+
+        // Create a new ActivityOptions from the bundle
+        new ActivityOptions(bundle);
+
+        // Verify the keys that are being used.
+        final ArgumentCaptor<String> stringCaptor =  ArgumentCaptor.forClass(String.class);
+        verify(bundle, atLeastOnce()).getString(stringCaptor.capture());
+        verify(bundle, atLeastOnce()).getBoolean(stringCaptor.capture());
+        verify(bundle, atLeastOnce()).getParcelable(stringCaptor.capture(), any());
+        verify(bundle, atLeastOnce()).getInt(stringCaptor.capture(), anyInt());
+        verify(bundle, atLeastOnce()).getBinder(stringCaptor.capture());
+        verify(bundle, atLeastOnce()).getBundle(stringCaptor.capture());
+        final List<String> keys = stringCaptor.getAllValues();
+        final List<String> unknownKeys = new ArrayList<>();
+        for (String key : keys) {
+            switch (key) {
+                case ActivityOptions.KEY_PACKAGE_NAME:
+                case ActivityOptions.KEY_LAUNCH_BOUNDS:
+                case ActivityOptions.KEY_ANIM_TYPE:
+                case ActivityOptions.KEY_ANIM_ENTER_RES_ID:
+                case ActivityOptions.KEY_ANIM_EXIT_RES_ID:
+                case ActivityOptions.KEY_ANIM_IN_PLACE_RES_ID:
+                case ActivityOptions.KEY_ANIM_BACKGROUND_COLOR:
+                case ActivityOptions.KEY_ANIM_THUMBNAIL:
+                case ActivityOptions.KEY_ANIM_START_X:
+                case ActivityOptions.KEY_ANIM_START_Y:
+                case ActivityOptions.KEY_ANIM_WIDTH:
+                case ActivityOptions.KEY_ANIM_HEIGHT:
+                case ActivityOptions.KEY_ANIM_START_LISTENER:
+                case ActivityOptions.KEY_SPLASH_SCREEN_THEME:
+                case ActivityOptions.KEY_LEGACY_PERMISSION_PROMPT_ELIGIBLE:
+                case ActivityOptions.KEY_LAUNCH_ROOT_TASK_TOKEN:
+                case ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN:
+                case ActivityOptions.KEY_TRANSIENT_LAUNCH:
+                case "android:activity.animationFinishedListener":
+                    // KEY_ANIMATION_FINISHED_LISTENER
+                case "android:activity.animSpecs": // KEY_ANIM_SPECS
+                case "android:activity.lockTaskMode": // KEY_LOCK_TASK_MODE
+                case "android:activity.shareIdentity": // KEY_SHARE_IDENTITY
+                case "android.activity.launchDisplayId": // KEY_LAUNCH_DISPLAY_ID
+                case "android.activity.callerDisplayId": // KEY_CALLER_DISPLAY_ID
+                case "android.activity.launchTaskDisplayAreaToken":
+                    // KEY_LAUNCH_TASK_DISPLAY_AREA_TOKEN
+                case "android.activity.launchTaskDisplayAreaFeatureId":
+                    // KEY_LAUNCH_TASK_DISPLAY_AREA_FEATURE_ID
+                case "android.activity.windowingMode": // KEY_LAUNCH_WINDOWING_MODE
+                case "android.activity.activityType": // KEY_LAUNCH_ACTIVITY_TYPE
+                case "android.activity.launchTaskId": // KEY_LAUNCH_TASK_ID
+                case "android.activity.disableStarting": // KEY_DISABLE_STARTING_WINDOW
+                case "android.activity.pendingIntentLaunchFlags":
+                    // KEY_PENDING_INTENT_LAUNCH_FLAGS
+                case "android.activity.alwaysOnTop": // KEY_TASK_ALWAYS_ON_TOP
+                case "android.activity.taskOverlay": // KEY_TASK_OVERLAY
+                case "android.activity.taskOverlayCanResume": // KEY_TASK_OVERLAY_CAN_RESUME
+                case "android.activity.avoidMoveToFront": // KEY_AVOID_MOVE_TO_FRONT
+                case "android.activity.freezeRecentTasksReordering":
+                    // KEY_FREEZE_RECENT_TASKS_REORDERING
+                case "android:activity.disallowEnterPictureInPictureWhileLaunching":
+                    // KEY_DISALLOW_ENTER_PICTURE_IN_PICTURE_WHILE_LAUNCHING
+                case "android:activity.applyActivityFlagsForBubbles":
+                    // KEY_APPLY_ACTIVITY_FLAGS_FOR_BUBBLES
+                case "android:activity.applyMultipleTaskFlagForShortcut":
+                    // KEY_APPLY_MULTIPLE_TASK_FLAG_FOR_SHORTCUT
+                case "android:activity.applyNoUserActionFlagForShortcut":
+                    // KEY_APPLY_NO_USER_ACTION_FLAG_FOR_SHORTCUT
+                case "android:activity.transitionCompleteListener":
+                    // KEY_TRANSITION_COMPLETE_LISTENER
+                case "android:activity.transitionIsReturning": // KEY_TRANSITION_IS_RETURNING
+                case "android:activity.sharedElementNames": // KEY_TRANSITION_SHARED_ELEMENTS
+                case "android:activity.resultData": // KEY_RESULT_DATA
+                case "android:activity.resultCode": // KEY_RESULT_CODE
+                case "android:activity.exitCoordinatorIndex": // KEY_EXIT_COORDINATOR_INDEX
+                case "android.activity.sourceInfo": // KEY_SOURCE_INFO
+                case "android:activity.usageTimeReport": // KEY_USAGE_TIME_REPORT
+                case "android:activity.rotationAnimationHint": // KEY_ROTATION_ANIMATION_HINT
+                case "android:instantapps.installerbundle": // KEY_INSTANT_APP_VERIFICATION_BUNDLE
+                case "android:activity.specsFuture": // KEY_SPECS_FUTURE
+                case "android:activity.remoteAnimationAdapter": // KEY_REMOTE_ANIMATION_ADAPTER
+                case "android:activity.remoteTransition": // KEY_REMOTE_TRANSITION
+                case "android:activity.overrideTaskTransition": // KEY_OVERRIDE_TASK_TRANSITION
+                case "android.activity.removeWithTaskOrganizer": // KEY_REMOVE_WITH_TASK_ORGANIZER
+                case "android.activity.launchTypeBubble": // KEY_LAUNCHED_FROM_BUBBLE
+                case "android.activity.splashScreenStyle": // KEY_SPLASH_SCREEN_STYLE
+                case "android.activity.launchIntoPipParams": // KEY_LAUNCH_INTO_PIP_PARAMS
+                case "android.activity.dismissKeyguard": // KEY_DISMISS_KEYGUARD
+                case "android.activity.pendingIntentCreatorBackgroundActivityStartMode":
+                    // KEY_PENDING_INTENT_CREATOR_BACKGROUND_ACTIVITY_START_MODE
+                case "android.activity.launchCookie": // KEY_LAUNCH_COOKIE
+                    // Existing keys
+                    break;
+                default:
+                    unknownKeys.add(key);
+                    break;
+            }
+        }
+
+        // Report if any unknown key exists.
+        for (String key : unknownKeys) {
+            Log.e("ActivityOptionsTests", "Unknown key " + key + " is found. "
+                    + "Please review if the given bundle should be protected with permissions.");
+        }
+        assertTrue(unknownKeys.isEmpty());
+    }
+
     public static class TrampolineActivity extends Activity {
         static int sTaskId;
 
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 e881039..3db53eb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -3334,7 +3334,7 @@
 
         // app1 requests IME visible.
         app1.setRequestedVisibleTypes(ime(), ime());
-        mDisplayContent.getInsetsStateController().onInsetsModified(app1);
+        mDisplayContent.getInsetsStateController().onRequestedVisibleTypesChanged(app1);
 
         // Verify app1's IME insets is visible and app2's IME insets frozen flag set.
         assertTrue(app1.getInsetsState().peekSource(ID_IME).isVisible());
@@ -3403,7 +3403,7 @@
         assertFalse(activity2.mImeInsetsFrozenUntilStartInput);
 
         app1.setRequestedVisibleTypes(ime());
-        controller.onInsetsModified(app1);
+        controller.onRequestedVisibleTypesChanged(app1);
 
         // Expect all activities in split-screen will get IME insets visible state
         assertTrue(app1.getInsetsState().peekSource(ID_IME).isVisible());
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 9842d70..858a384 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -74,6 +74,7 @@
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -102,7 +103,6 @@
 import android.os.IBinder;
 import android.os.Process;
 import android.os.RemoteException;
-import android.os.UserHandle;
 import android.platform.test.annotations.Presubmit;
 import android.provider.DeviceConfig;
 import android.service.voice.IVoiceInteractionSession;
@@ -159,9 +159,6 @@
     private static final String FAKE_CALLING_PACKAGE = "com.whatever.dude";
     private static final int UNIMPORTANT_UID = 12345;
     private static final int UNIMPORTANT_UID2 = 12346;
-    private static final int SDK_SANDBOX_UID = Process.toSdkSandboxUid(UNIMPORTANT_UID);
-    private static final int SECONDARY_USER_SDK_SANDBOX_UID =
-            UserHandle.getUid(10, SDK_SANDBOX_UID);
     private static final int CURRENT_IME_UID = 12347;
 
     protected final DeviceConfigStateHelper mDeviceConfig = new DeviceConfigStateHelper(
@@ -961,48 +958,6 @@
         mockingSession.finishMocking();
     }
 
-
-    @Test
-    public void testBackgroundActivityStartsAllowed_sdkSandboxClientAppHasVisibleWindow() {
-        doReturn(false).when(mAtm).isBackgroundActivityStartsEnabled();
-        // The SDK's associated client app has a visible window
-        doReturn(true).when(mAtm).hasActiveVisibleWindow(
-                Process.getAppUidForSdkSandboxUid(SDK_SANDBOX_UID));
-        runAndVerifyBackgroundActivityStartsSubtest(
-                "allowed_sdkSandboxClientAppHasVisibleWindow", false, SDK_SANDBOX_UID,
-                false, PROCESS_STATE_TOP, SDK_SANDBOX_UID, false,
-                PROCESS_STATE_TOP, true, false, false,
-                false, false, false, false, false);
-    }
-
-    @Test
-    public void testBackgroundActivityStartsDisallowed_sdkSandboxClientHasNoVisibleWindow() {
-        doReturn(false).when(mAtm).isBackgroundActivityStartsEnabled();
-        // The SDK's associated client app does not have a visible window
-        doReturn(false).when(mAtm).hasActiveVisibleWindow(
-                Process.getAppUidForSdkSandboxUid(SDK_SANDBOX_UID));
-        runAndVerifyBackgroundActivityStartsSubtest(
-                "disallowed_sdkSandboxClientHasNoVisibleWindow", true, SDK_SANDBOX_UID,
-                false, PROCESS_STATE_TOP, SDK_SANDBOX_UID, false,
-                PROCESS_STATE_TOP, true, false, false,
-                false, false, false, false, false);
-
-    }
-
-    @Test
-    public void testBackgroundActivityStartsAllowed_sdkSandboxMultiUserClientHasVisibleWindow() {
-        doReturn(false).when(mAtm).isBackgroundActivityStartsEnabled();
-        // The SDK's associated client app has a visible window
-        doReturn(true).when(mAtm).hasActiveVisibleWindow(
-                Process.getAppUidForSdkSandboxUid(SECONDARY_USER_SDK_SANDBOX_UID));
-        runAndVerifyBackgroundActivityStartsSubtest(
-                "allowed_sdkSandboxMultiUserClientHasVisibleWindow", false,
-                SECONDARY_USER_SDK_SANDBOX_UID, false, PROCESS_STATE_TOP,
-                SECONDARY_USER_SDK_SANDBOX_UID, false, PROCESS_STATE_TOP,
-                false, false, false, false,
-                false, false, false, false);
-    }
-
     private void runAndVerifyBackgroundActivityStartsSubtest(String name, boolean shouldHaveAborted,
             int callingUid, boolean callingUidHasVisibleWindow, int callingUidProcState,
             int realCallingUid, boolean realCallingUidHasVisibleWindow, int realCallingUidProcState,
@@ -1377,6 +1332,10 @@
         starter.setReason("testNoActivityInfo").setIntent(intent)
                 .setActivityInfo(null).execute();
         verify(starter.mRequest).resolveActivity(any());
+
+        // Also verifies the value of Request#componentSpecified should be true even the
+        // ActivityStarter#setComponentSpecified is not explicitly set.
+        assertTrue(starter.mRequest.componentSpecified);
     }
 
     @Test
@@ -1470,6 +1429,30 @@
     }
 
     @Test
+    public void testTransientLaunchWithKeyguard() {
+        final ActivityStarter starter = prepareStarter(0 /* flags */);
+        final ActivityRecord target = new ActivityBuilder(mAtm).setCreateTask(true).build();
+        final ActivityRecord top = new ActivityBuilder(mAtm).setCreateTask(true).build();
+        final KeyguardController keyguard = mSupervisor.getKeyguardController();
+        doReturn(true).when(keyguard).isKeyguardLocked(anyInt());
+        doReturn(true).when(keyguard).isDisplayOccluded(anyInt());
+        registerTestTransitionPlayer();
+        starter.setReason("testTransientLaunchWithKeyguard")
+                .setActivityOptions(ActivityOptions.makeBasic().setTransientLaunch().toBundle())
+                .setIntent(target.intent)
+                .execute();
+        final TransitionController controller = mRootWindowContainer.mTransitionController;
+        final Transition transition = controller.getCollectingTransition();
+        final Transition.ChangeInfo targetChangeInfo = transition.mChanges.get(target);
+
+        assertThat(targetChangeInfo).isNotNull();
+        assertThat(targetChangeInfo.hasChanged()).isTrue();
+        assertThat(controller.isCollecting(top.getTask())).isTrue();
+        assertThat(transition.isTransientLaunch(target)).isTrue();
+        assertThat(transition.isInTransientHide(top.getTask())).isTrue();
+    }
+
+    @Test
     public void testActivityStart_expectAddedToRecentTask() {
         RecentTasks recentTasks = mock(RecentTasks.class);
         mAtm.mTaskSupervisor.setRecentTasks(recentTasks);
@@ -1747,6 +1730,9 @@
 
     @Test
     public void testRecordActivityMovementBeforeDeliverToTop() {
+        // Mock recents as task is only marked to be in recents
+        mAtm.mTaskSupervisor.setRecentTasks(mock(RecentTasks.class));
+
         final Task task = new TaskBuilder(mAtm.mTaskSupervisor).build();
         final ActivityRecord activityBot = new ActivityBuilder(mAtm).setTask(task).build();
         final ActivityRecord activityTop = new ActivityBuilder(mAtm).setTask(task).build();
@@ -1787,8 +1773,7 @@
     public void testLaunchActivityWithoutDisplayCategory() {
         final ActivityInfo info = new ActivityInfo();
         info.applicationInfo = new ApplicationInfo();
-        info.taskAffinity = ActivityRecord.computeTaskAffinity("test", DEFAULT_FAKE_UID,
-                0 /* launchMode */, null /* componentName */);
+        info.taskAffinity = ActivityRecord.computeTaskAffinity("test", DEFAULT_FAKE_UID);
         info.requiredDisplayCategory = "automotive";
         final Task task = new TaskBuilder(mSupervisor).setCreateActivity(true).setActivityInfo(info)
                 .build();
@@ -1813,8 +1798,7 @@
     public void testLaunchActivityWithDifferentDisplayCategory() {
         final ActivityInfo info = new ActivityInfo();
         info.applicationInfo = new ApplicationInfo();
-        info.taskAffinity = ActivityRecord.computeTaskAffinity("test", DEFAULT_FAKE_UID,
-                0 /* launchMode */, null /* componentName */);
+        info.taskAffinity = ActivityRecord.computeTaskAffinity("test", DEFAULT_FAKE_UID);
         info.requiredDisplayCategory = "automotive";
         final Task task = new TaskBuilder(mSupervisor).setCreateActivity(true).setActivityInfo(info)
                 .build();
@@ -1839,8 +1823,7 @@
     public void testLaunchActivityWithSameDisplayCategory() {
         final ActivityInfo info = new ActivityInfo();
         info.applicationInfo = new ApplicationInfo();
-        info.taskAffinity = ActivityRecord.computeTaskAffinity("test", DEFAULT_FAKE_UID,
-                0 /* launchMode */, null /* componentName */);
+        info.taskAffinity = ActivityRecord.computeTaskAffinity("test", DEFAULT_FAKE_UID);
         info.requiredDisplayCategory = "automotive";
         final Task task = new TaskBuilder(mSupervisor).setCreateActivity(true).setActivityInfo(info)
                 .build();
diff --git a/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
index 3a456fb..dc4e47d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
@@ -18,6 +18,7 @@
 
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.util.DisplayMetrics.DENSITY_DEFAULT;
 
@@ -80,13 +81,27 @@
     }
 
     @Test
-    public void testReturnsSkipIfTaskNotUsingActivityTypeStandard() {
+    public void testReturnsSkipIfTaskNotUsingActivityTypeStandardOrUndefined() {
         final Task task = new TaskBuilder(mSupervisor).setActivityType(
                 ACTIVITY_TYPE_ASSISTANT).build();
         assertEquals(RESULT_SKIP, new CalculateRequestBuilder().setTask(task).calculate());
     }
 
     @Test
+    public void testReturnsDoneIfTaskUsingActivityTypeStandard() {
+        final Task task = new TaskBuilder(mSupervisor).setActivityType(
+                ACTIVITY_TYPE_STANDARD).build();
+        assertEquals(RESULT_DONE, new CalculateRequestBuilder().setTask(task).calculate());
+    }
+
+    @Test
+    public void testReturnsDoneIfTaskUsingActivityTypeUndefined() {
+        final Task task = new TaskBuilder(mSupervisor).setActivityType(
+                ACTIVITY_TYPE_UNDEFINED).build();
+        assertEquals(RESULT_DONE, new CalculateRequestBuilder().setTask(task).calculate());
+    }
+
+    @Test
     public void testReturnsSkipIfCurrentParamsHasBounds() {
         final Task task = new TaskBuilder(mSupervisor).setActivityType(
                 ACTIVITY_TYPE_STANDARD).build();
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 340b591..bdd178b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -797,6 +797,7 @@
         final int baseDensity = 320;
         final float baseXDpi = 60;
         final float baseYDpi = 60;
+        final int originalMinTaskSizeDp = displayContent.mMinSizeOfResizeableTaskDp;
 
         displayContent.mInitialDisplayWidth = baseWidth;
         displayContent.mInitialDisplayHeight = baseHeight;
@@ -814,6 +815,9 @@
         displayContent.setForcedDensity(forcedDensity, 0 /* userId */);
         verifySizes(displayContent, baseWidth, baseHeight, forcedDensity);
 
+        // Verify that minimal task size (dp) doesn't change with density of display.
+        assertEquals(originalMinTaskSizeDp, displayContent.mMinSizeOfResizeableTaskDp);
+
         // Verify that forcing resolution won't affect the already forced density.
         displayContent.setForcedSize(1800, 1200);
         verifySizes(displayContent, 1800, 1200, forcedDensity);
@@ -1811,30 +1815,6 @@
         assertFalse(mDisplayContent.hasTopFixedRotationLaunchingApp());
     }
 
-    /**
-     * Creates different types of displays, verifies that minimal task size doesn't change
-     * with density of display.
-     */
-    @Test
-    public void testCalculatesDisplaySpecificMinTaskSizes() {
-        DisplayContent defaultTestDisplay =
-                new TestDisplayContent.Builder(mAtm, 1000, 2000).build();
-        final int defaultMinTaskSize = defaultTestDisplay.mMinSizeOfResizeableTaskDp;
-        DisplayContent firstDisplay = new TestDisplayContent.Builder(mAtm, 1000, 2000)
-                .setDensityDpi(300)
-                .updateDisplayMetrics()
-                .setDefaultMinTaskSizeDp(defaultMinTaskSize + 10)
-                .build();
-        assertEquals(defaultMinTaskSize + 10, firstDisplay.mMinSizeOfResizeableTaskDp);
-
-        DisplayContent secondDisplay = new TestDisplayContent.Builder(mAtm, 200, 200)
-                .setDensityDpi(320)
-                .updateDisplayMetrics()
-                .setDefaultMinTaskSizeDp(defaultMinTaskSize + 20)
-                .build();
-        assertEquals(defaultMinTaskSize + 20, secondDisplay.mMinSizeOfResizeableTaskDp);
-    }
-
     @Test
     public void testRecentsNotRotatingWithFixedRotation() {
         unblockDisplayRotation(mDisplayContent);
@@ -2066,6 +2046,7 @@
 
         // Once transition starts, rotation is applied and transition shows DC rotating.
         testPlayer.startTransition();
+        waitUntilHandlersIdle();
         assertNotEquals(origRot, dc.getConfiguration().windowConfiguration.getRotation());
         assertNotNull(testPlayer.mLastReady);
         assertTrue(testPlayer.mController.isPlaying());
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 b0609dd..dd90e04 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
@@ -182,6 +182,44 @@
                 dimmingNonImTarget, imeNonDrawNavBar, NAV_BAR_BOTTOM));
     }
 
+    @Test
+    public void testChooseNavigationBackgroundWindow() {
+        final WindowState drawBarWin = createOpaqueFullscreen(false);
+        final WindowState nonDrawBarWin = createDimmingDialogWindow(true);
+
+        final WindowState visibleIme = createInputMethodWindow(true, true, false);
+        final WindowState invisibleIme = createInputMethodWindow(false, true, false);
+        final WindowState nonDrawBarIme = createInputMethodWindow(true, false, false);
+
+        assertEquals(drawBarWin, DisplayPolicy.chooseNavigationBackgroundWindow(
+                drawBarWin, null, NAV_BAR_BOTTOM));
+        assertNull(DisplayPolicy.chooseNavigationBackgroundWindow(
+                null, null, NAV_BAR_BOTTOM));
+        assertNull(DisplayPolicy.chooseNavigationBackgroundWindow(
+                nonDrawBarWin, null, NAV_BAR_BOTTOM));
+
+        assertEquals(visibleIme, DisplayPolicy.chooseNavigationBackgroundWindow(
+                drawBarWin, visibleIme, NAV_BAR_BOTTOM));
+        assertEquals(visibleIme, DisplayPolicy.chooseNavigationBackgroundWindow(
+                null, visibleIme, NAV_BAR_BOTTOM));
+        assertEquals(visibleIme, DisplayPolicy.chooseNavigationBackgroundWindow(
+                nonDrawBarWin, visibleIme, NAV_BAR_BOTTOM));
+
+        assertEquals(drawBarWin, DisplayPolicy.chooseNavigationBackgroundWindow(
+                drawBarWin, invisibleIme, NAV_BAR_BOTTOM));
+        assertNull(DisplayPolicy.chooseNavigationBackgroundWindow(
+                null, invisibleIme, NAV_BAR_BOTTOM));
+        assertNull(DisplayPolicy.chooseNavigationBackgroundWindow(
+                nonDrawBarWin, invisibleIme, NAV_BAR_BOTTOM));
+
+        assertEquals(drawBarWin, DisplayPolicy.chooseNavigationBackgroundWindow(
+                drawBarWin, nonDrawBarIme, NAV_BAR_BOTTOM));
+        assertNull(DisplayPolicy.chooseNavigationBackgroundWindow(
+                null, nonDrawBarIme, NAV_BAR_BOTTOM));
+        assertNull(DisplayPolicy.chooseNavigationBackgroundWindow(
+                nonDrawBarWin, nonDrawBarIme, NAV_BAR_BOTTOM));
+    }
+
     @SetupWindows(addWindows = W_NAVIGATION_BAR)
     @Test
     public void testUpdateLightNavigationBarLw() {
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 8e015d4..769a309 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
@@ -103,8 +103,7 @@
     public void setUp() throws Exception {
         mLetterboxConfiguration = mDisplayContent.mWmService.mLetterboxConfiguration;
         spyOn(mLetterboxConfiguration);
-        when(mLetterboxConfiguration.isCameraCompatTreatmentEnabled(
-                    /* checkDeviceConfig */ anyBoolean()))
+        when(mLetterboxConfiguration.isCameraCompatTreatmentEnabled())
                 .thenReturn(true);
         when(mLetterboxConfiguration.isCameraCompatRefreshEnabled())
                 .thenReturn(true);
@@ -177,8 +176,7 @@
 
     @Test
     public void testOnScreenRotationAnimationFinished_treatmentNotEnabled_doNotShowToast() {
-        when(mLetterboxConfiguration.isCameraCompatTreatmentEnabled(
-                    /* checkDeviceConfig */ anyBoolean()))
+        when(mLetterboxConfiguration.isCameraCompatTreatmentEnabled())
                 .thenReturn(false);
         spyOn(mDisplayRotationCompatPolicy);
 
@@ -238,8 +236,7 @@
 
     @Test
     public void testTreatmentNotEnabled_noForceRotationOrRefresh() throws Exception {
-        when(mLetterboxConfiguration.isCameraCompatTreatmentEnabled(
-                    /* checkDeviceConfig */ anyBoolean()))
+        when(mLetterboxConfiguration.isCameraCompatTreatmentEnabled())
                 .thenReturn(false);
 
         configureActivity(SCREEN_ORIENTATION_PORTRAIT);
@@ -253,8 +250,7 @@
 
     @Test
     public void testTreatmentDisabledViaDeviceConfig_noForceRotationOrRefresh() throws Exception {
-        when(mLetterboxConfiguration.isCameraCompatTreatmentEnabled(
-                    /* checkDeviceConfig */ true))
+        when(mLetterboxConfiguration.isCameraCompatTreatmentEnabled())
                 .thenReturn(false);
 
         configureActivity(SCREEN_ORIENTATION_PORTRAIT);
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationImmersiveAppCompatPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationImmersiveAppCompatPolicyTests.java
index d29b18f..b105703 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationImmersiveAppCompatPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationImmersiveAppCompatPolicyTests.java
@@ -31,7 +31,6 @@
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.anyBoolean;
 
 import android.platform.test.annotations.Presubmit;
 import android.view.Surface;
@@ -79,8 +78,11 @@
         when(mDisplayContent.getIgnoreOrientationRequest()).thenReturn(true);
 
         mMockLetterboxConfiguration = mock(LetterboxConfiguration.class);
-        when(mMockLetterboxConfiguration.isDisplayRotationImmersiveAppCompatPolicyEnabled(
-                /* checkDeviceConfig */ anyBoolean())).thenReturn(true);
+        when(mMockLetterboxConfiguration.isDisplayRotationImmersiveAppCompatPolicyEnabled())
+                .thenReturn(true);
+        when(mMockLetterboxConfiguration
+                .isDisplayRotationImmersiveAppCompatPolicyEnabledAtBuildTime())
+                    .thenReturn(true);
 
         mPolicy = DisplayRotationImmersiveAppCompatPolicy.createIfNeeded(
                 mMockLetterboxConfiguration, createDisplayRotationMock(),
@@ -204,8 +206,8 @@
 
     @Test
     public void testRotationChoiceEnforcedOnly_featureFlagDisabled_lockNotEnforced() {
-        when(mMockLetterboxConfiguration.isDisplayRotationImmersiveAppCompatPolicyEnabled(
-                /* checkDeviceConfig */ true)).thenReturn(false);
+        when(mMockLetterboxConfiguration.isDisplayRotationImmersiveAppCompatPolicyEnabled())
+                .thenReturn(false);
 
         assertIsRotationLockEnforcedReturnsFalseForAllRotations();
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
index 2a8f0ffc..42422d9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
@@ -26,6 +26,7 @@
 import static android.view.IWindowManager.FIXED_TO_USER_ROTATION_DEFAULT;
 import static android.view.IWindowManager.FIXED_TO_USER_ROTATION_DISABLED;
 import static android.view.IWindowManager.FIXED_TO_USER_ROTATION_ENABLED;
+import static android.view.IWindowManager.FIXED_TO_USER_ROTATION_IF_NO_AUTO_ROTATION;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyBoolean;
@@ -1128,6 +1129,22 @@
                 SCREEN_ORIENTATION_UNSPECIFIED, Surface.ROTATION_0));
     }
 
+    @Test
+    public void testReturnsUserRotation_FixedToUserRotationIfNoAutoRotation_AutoRotationNotSupport()
+            throws Exception {
+        mBuilder.setSupportAutoRotation(false).build();
+        configureDisplayRotation(SCREEN_ORIENTATION_PORTRAIT, false, false);
+        mTarget.setFixedToUserRotation(FIXED_TO_USER_ROTATION_IF_NO_AUTO_ROTATION);
+
+        freezeRotation(Surface.ROTATION_180);
+
+        assertEquals(WindowManagerPolicy.USER_ROTATION_LOCKED, mTarget.getUserRotationMode());
+        assertEquals(Surface.ROTATION_180, mTarget.getUserRotation());
+
+        assertEquals(Surface.ROTATION_180, mTarget.rotationForOrientation(
+                SCREEN_ORIENTATION_UNSPECIFIED, Surface.ROTATION_0));
+    }
+
     // ========================
     // Non-rotation API Tests
     // ========================
@@ -1148,6 +1165,15 @@
                 + " fixed to user rotation.", mTarget.isFixedToUserRotation());
     }
 
+    @Test
+    public void testIsFixedToUserRotation_FixedToUserRotationIfNoAutoRotation() throws Exception {
+        mBuilder.build();
+        mTarget.setFixedToUserRotation(FIXED_TO_USER_ROTATION_IF_NO_AUTO_ROTATION);
+
+        assertFalse("Display rotation should respect app requested orientation if"
+                + " fixed to user rotation if no auto rotation.", mTarget.isFixedToUserRotation());
+    }
+
     private void moveTimeForward(long timeMillis) {
         sCurrentUptimeMillis += timeMillis;
         sClock.fastForward(timeMillis);
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsProviderTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsProviderTests.java
index 9d839fc..7d9fdd5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsProviderTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsProviderTests.java
@@ -19,6 +19,7 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.view.Display.TYPE_VIRTUAL;
 import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL;
 
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
@@ -26,6 +27,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
+import static org.testng.Assert.assertFalse;
 
 import android.annotation.Nullable;
 import android.platform.test.annotations.Presubmit;
@@ -233,6 +235,22 @@
     }
 
     @Test
+    public void testDoNotWriteVirtualDisplaySettingsToStorage() throws Exception {
+        final DisplayInfo secondaryDisplayInfo = mSecondaryDisplay.getDisplayInfo();
+        secondaryDisplayInfo.type = TYPE_VIRTUAL;
+
+        // No write to storage on virtual display change.
+        final DisplayWindowSettingsProvider provider = new DisplayWindowSettingsProvider(
+                mDefaultVendorSettingsStorage, mOverrideSettingsStorage);
+        final SettingsEntry virtualSettings = provider.getOverrideSettings(secondaryDisplayInfo);
+        virtualSettings.mShouldShowSystemDecors = true;
+        virtualSettings.mImePolicy = DISPLAY_IME_POLICY_LOCAL;
+        virtualSettings.mDontMoveToTop = true;
+        provider.updateOverrideSettings(secondaryDisplayInfo, virtualSettings);
+        assertFalse(mOverrideSettingsStorage.wasWriteSuccessful());
+    }
+
+    @Test
     public void testWritingDisplaySettingsToStorage_UsePortAsId() throws Exception {
         prepareOverrideDisplaySettings(null /* displayIdentifier */, true /* usePortAsId */);
 
@@ -260,6 +278,54 @@
                 getStoredDisplayAttributeValue(mOverrideSettingsStorage, "imePolicy"));
     }
 
+    @Test
+    public void testCleanUpEmptyDisplaySettingsOnDisplayRemoved() {
+        final DisplayWindowSettingsProvider provider = new DisplayWindowSettingsProvider(
+                mDefaultVendorSettingsStorage, mOverrideSettingsStorage);
+        final int initialSize = provider.getOverrideSettingsSize();
+
+        // Size + 1 when query for a new display.
+        final DisplayInfo secondaryDisplayInfo = mSecondaryDisplay.getDisplayInfo();
+        final SettingsEntry overrideSettings = provider.getOverrideSettings(secondaryDisplayInfo);
+
+        assertEquals(initialSize + 1, provider.getOverrideSettingsSize());
+
+        // When a display is removed, its override Settings is not removed if there is any override.
+        overrideSettings.mShouldShowSystemDecors = true;
+        provider.updateOverrideSettings(secondaryDisplayInfo, overrideSettings);
+        provider.onDisplayRemoved(secondaryDisplayInfo);
+
+        assertEquals(initialSize + 1, provider.getOverrideSettingsSize());
+
+        // When a display is removed, its override Settings is removed if there is no override.
+        provider.updateOverrideSettings(secondaryDisplayInfo, new SettingsEntry());
+        provider.onDisplayRemoved(secondaryDisplayInfo);
+
+        assertEquals(initialSize, provider.getOverrideSettingsSize());
+    }
+
+    @Test
+    public void testCleanUpVirtualDisplaySettingsOnDisplayRemoved() {
+        final DisplayWindowSettingsProvider provider = new DisplayWindowSettingsProvider(
+                mDefaultVendorSettingsStorage, mOverrideSettingsStorage);
+        final int initialSize = provider.getOverrideSettingsSize();
+
+        // Size + 1 when query for a new display.
+        final DisplayInfo secondaryDisplayInfo = mSecondaryDisplay.getDisplayInfo();
+        secondaryDisplayInfo.type = TYPE_VIRTUAL;
+        final SettingsEntry overrideSettings = provider.getOverrideSettings(secondaryDisplayInfo);
+
+        assertEquals(initialSize + 1, provider.getOverrideSettingsSize());
+
+        // When a virtual display is removed, its override Settings is removed even if it has
+        // override.
+        overrideSettings.mShouldShowSystemDecors = true;
+        provider.updateOverrideSettings(secondaryDisplayInfo, overrideSettings);
+        provider.onDisplayRemoved(secondaryDisplayInfo);
+
+        assertEquals(initialSize, provider.getOverrideSettingsSize());
+    }
+
     /**
      * Prepares display settings and stores in {@link #mOverrideSettingsStorage}. Uses provided
      * display identifier and stores windowingMode=WINDOWING_MODE_PINNED.
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java
index 1cec0ef..e54b8e5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java
@@ -484,6 +484,18 @@
         assertTrue(dcIgnoreOrientation.getIgnoreOrientationRequest());
     }
 
+    @Test
+    public void testDisplayRemoval() {
+        spyOn(mWm.mDisplayWindowSettings);
+        spyOn(mWm.mDisplayWindowSettingsProvider);
+
+        mPrivateDisplay.removeImmediately();
+
+        verify(mWm.mDisplayWindowSettings).onDisplayRemoved(mPrivateDisplay);
+        verify(mWm.mDisplayWindowSettingsProvider).onDisplayRemoved(
+                mPrivateDisplay.getDisplayInfo());
+    }
+
     public final class TestSettingsProvider implements DisplayWindowSettings.SettingsProvider {
         Map<DisplayInfo, SettingsEntry> mOverrideSettingsCache = new HashMap<>();
 
@@ -513,5 +525,10 @@
 
             overrideSettings.setTo(settings);
         }
+
+        @Override
+        public void onDisplayRemoved(@NonNull DisplayInfo info) {
+            mOverrideSettingsCache.remove(info);
+        }
     }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java
index d1a41ae..83ad7b1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java
@@ -34,6 +34,7 @@
 import static android.window.DisplayAreaOrganizer.FEATURE_VENDOR_FIRST;
 import static android.window.DisplayAreaOrganizer.FEATURE_WINDOWED_MAGNIFICATION;
 
+import static com.android.compatibility.common.util.PackageUtil.supportsRotation;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.server.wm.SizeCompatTests.prepareLimitedBounds;
 import static com.android.server.wm.SizeCompatTests.prepareUnresizable;
@@ -41,6 +42,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.junit.Assume.assumeTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
@@ -128,6 +130,8 @@
 
     @Test
     public void testNotIgnoreOrientationRequest_differentOrientationFromDisplay_reversesRequest() {
+        assumeTrue(supportsRotation());
+
         mFirstRoot.setIgnoreOrientationRequest(false /* ignoreOrientationRequest */);
         mDisplay.onLastFocusedTaskDisplayAreaChanged(mFirstTda);
 
@@ -144,6 +148,8 @@
 
     @Test
     public void testNotIgnoreOrientationRequest_onlyRespectsFocusedTaskDisplayArea() {
+        assumeTrue(supportsRotation());
+
         mFirstRoot.setIgnoreOrientationRequest(false /* ignoreOrientationRequest */);
         mSecondRoot.setIgnoreOrientationRequest(false /* ignoreOrientationRequest */);
         mDisplay.onLastFocusedTaskDisplayAreaChanged(mFirstTda);
@@ -249,6 +255,8 @@
 
     @Test
     public void testLaunchNoSensorApp_noSizeCompatAfterRotation() {
+        assumeTrue(supportsRotation());
+
         mFirstRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
         mSecondRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
         mDisplay.onLastFocusedTaskDisplayAreaChanged(mFirstTda);
@@ -285,6 +293,8 @@
 
     @Test
     public void testLaunchNoSensorApp_activityIsNotLetterboxForFixedOrientationDisplayAreaGroup() {
+        assumeTrue(supportsRotation());
+
         mFirstRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
         mSecondRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
         mDisplay.onLastFocusedTaskDisplayAreaChanged(mFirstTda);
@@ -334,6 +344,8 @@
 
     @Test
     public void testLaunchNoSensorApp_fixedOrientationLetterboxBecomesSizeCompatAfterRotation() {
+        assumeTrue(supportsRotation());
+
         mFirstRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
         mSecondRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
         mDisplay.onLastFocusedTaskDisplayAreaChanged(mFirstTda);
@@ -496,6 +508,8 @@
 
     @Test
     public void testResizableFixedOrientationApp_fixedOrientationLetterboxing() {
+        assumeTrue(supportsRotation());
+
         mFirstRoot.setIgnoreOrientationRequest(false /* ignoreOrientationRequest */);
         mSecondRoot.setIgnoreOrientationRequest(false /* ignoreOrientationRequest */);
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
index a8fc25f..994dcf1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
@@ -31,17 +31,12 @@
 import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
 import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL;
 
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
-
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.verify;
 
 import android.app.StatusBarManager;
@@ -268,8 +263,6 @@
         navBar.setHasSurface(true);
         navBarProvider.setServerVisible(true);
         final InsetsPolicy policy = mDisplayContent.getInsetsPolicy();
-        spyOn(policy);
-        doNothing().when(policy).startAnimation(anyBoolean(), any());
 
         // Make both system bars invisible.
         mAppWindow.setRequestedVisibleTypes(
@@ -305,8 +298,6 @@
         addNavigationBar().getControllableInsetProvider().setServerVisible(true);
 
         final InsetsPolicy policy = mDisplayContent.getInsetsPolicy();
-        spyOn(policy);
-        doNothing().when(policy).startAnimation(anyBoolean(), any());
         policy.updateBarControlTarget(mAppWindow);
         policy.showTransient(navigationBars() | statusBars(),
                 true /* isGestureOnSystemBar */);
@@ -341,8 +332,6 @@
         mAppWindow.mAboveInsetsState.addSource(navBarSource);
         mAppWindow.mAboveInsetsState.addSource(statusBarSource);
         final InsetsPolicy policy = mDisplayContent.getInsetsPolicy();
-        spyOn(policy);
-        doNothing().when(policy).startAnimation(anyBoolean(), any());
         policy.updateBarControlTarget(mAppWindow);
         policy.showTransient(navigationBars() | statusBars(),
                 true /* isGestureOnSystemBar */);
@@ -370,7 +359,7 @@
 
         mAppWindow.setRequestedVisibleTypes(
                 navigationBars() | statusBars(), navigationBars() | statusBars());
-        policy.onInsetsModified(mAppWindow);
+        policy.onRequestedVisibleTypesChanged(mAppWindow);
         waitUntilWindowAnimatorIdle();
 
         controls = mDisplayContent.getInsetsStateController().getControlsForDispatch(mAppWindow);
@@ -390,8 +379,6 @@
         final WindowState app2 = addWindow(TYPE_APPLICATION, "app");
 
         final InsetsPolicy policy = mDisplayContent.getInsetsPolicy();
-        spyOn(policy);
-        doNothing().when(policy).startAnimation(anyBoolean(), any());
         policy.updateBarControlTarget(app);
         policy.showTransient(navigationBars() | statusBars(),
                 true /* isGestureOnSystemBar */);
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
index d7e736d..114796d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
@@ -192,17 +192,16 @@
         // This can be the IME z-order target while app cannot be the IME z-order target.
         // This is also the only IME control target in this test, so IME won't be invisible caused
         // by the control-target change.
-        mDisplayContent.updateImeInputAndControlTarget(
-                createWindow(null, TYPE_APPLICATION, "base"));
+        final WindowState base = createWindow(null, TYPE_APPLICATION, "base");
+        mDisplayContent.updateImeInputAndControlTarget(base);
 
         // Make IME and stay visible during the test.
         mImeWindow.setHasSurface(true);
         getController().getOrCreateSourceProvider(ID_IME, ime())
                 .setWindowContainer(mImeWindow, null, null);
-        getController().onImeControlTargetChanged(
-                mDisplayContent.getImeInputTarget().getWindowState());
-        mDisplayContent.getImeInputTarget().getWindowState().setRequestedVisibleTypes(ime(), ime());
-        getController().onInsetsModified(mDisplayContent.getImeInputTarget().getWindowState());
+        getController().onImeControlTargetChanged(base);
+        base.setRequestedVisibleTypes(ime(), ime());
+        getController().onRequestedVisibleTypesChanged(base);
 
         // Send our spy window (app) into the system so that we can detect the invocation.
         final WindowState win = createWindow(null, TYPE_APPLICATION, "app");
@@ -485,7 +484,7 @@
         mDisplayContent.updateImeInputAndControlTarget(app);
 
         app.setRequestedVisibleTypes(ime(), ime());
-        getController().onInsetsModified(app);
+        getController().onRequestedVisibleTypesChanged(app);
         assertTrue(ime.getControllableInsetProvider().getSource().isVisible());
 
         getController().updateAboveInsetsState(true /* notifyInsetsChange */);
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 7cb7c79d..2b19ad9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java
@@ -107,6 +107,9 @@
                 InstrumentationRegistry.getInstrumentation().getContext().getCacheDir();
         mFolder = new File(cacheFolder, "launch_params_tests");
         deleteRecursively(mFolder);
+        mFolder.mkdir();
+        mUserFolderGetter.apply(TEST_USER_ID).mkdir();
+        mUserFolderGetter.apply(ALTERNATIVE_USER_ID).mkdir();
 
         mDisplayUniqueId = "test:" + sNextUniqueId++;
         mTestDisplay = new TestDisplayContent.Builder(mAtm, 1000, 1500)
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationDeviceConfigTests.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationDeviceConfigTests.java
deleted file mode 100644
index 2b7a06b..0000000
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationDeviceConfigTests.java
+++ /dev/null
@@ -1,109 +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.server.wm;
-
-import static com.android.server.wm.LetterboxConfigurationDeviceConfig.sKeyToDefaultValueMap;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import android.platform.test.annotations.Presubmit;
-import android.provider.DeviceConfig;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.modules.utils.testing.TestableDeviceConfig;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-
-import java.util.Map;
-
-/**
- * Test class for {@link LetterboxConfigurationDeviceConfig}.
- *
- * atest WmTests:LetterboxConfigurationDeviceConfigTests
- */
-@SmallTest
-@Presubmit
-public class LetterboxConfigurationDeviceConfigTests {
-
-    private LetterboxConfigurationDeviceConfig mDeviceConfig;
-
-    @Rule
-    public final TestableDeviceConfig.TestableDeviceConfigRule
-            mDeviceConfigRule = new TestableDeviceConfig.TestableDeviceConfigRule();
-
-    @Before
-    public void setUp() {
-        mDeviceConfig = new LetterboxConfigurationDeviceConfig(/* executor */ Runnable::run);
-    }
-
-    @Test
-    public void testGetFlag_flagIsActive_flagChanges() throws Throwable {
-        for (Map.Entry<String, Boolean> entry : sKeyToDefaultValueMap.entrySet()) {
-            testGetFlagForKey_flagIsActive_flagChanges(entry.getKey(), entry.getValue());
-        }
-    }
-
-    private void testGetFlagForKey_flagIsActive_flagChanges(final String key, boolean defaultValue)
-            throws InterruptedException {
-        mDeviceConfig.updateFlagActiveStatus(/* isActive */ true, key);
-
-        assertEquals("Unexpected default value for " + key,
-                mDeviceConfig.getFlag(key), defaultValue);
-
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_WINDOW_MANAGER, key,
-                /* value */ Boolean.TRUE.toString(), /* makeDefault */ false);
-
-        assertTrue("Flag " + key + "is not true after change", mDeviceConfig.getFlag(key));
-
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_WINDOW_MANAGER, key,
-                /* value */ Boolean.FALSE.toString(), /* makeDefault */ false);
-
-        assertFalse("Flag " + key + "is not false after change", mDeviceConfig.getFlag(key));
-    }
-
-    @Test
-    public void testGetFlag_flagIsNotActive_alwaysReturnDefaultValue() throws Throwable {
-        for (Map.Entry<String, Boolean> entry : sKeyToDefaultValueMap.entrySet()) {
-            testGetFlagForKey_flagIsNotActive_alwaysReturnDefaultValue(
-                    entry.getKey(), entry.getValue());
-        }
-    }
-
-    private void testGetFlagForKey_flagIsNotActive_alwaysReturnDefaultValue(final String key,
-            boolean defaultValue) throws InterruptedException {
-        assertEquals("Unexpected default value for " + key,
-                mDeviceConfig.getFlag(key), defaultValue);
-
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_WINDOW_MANAGER, key,
-                /* value */ Boolean.TRUE.toString(), /* makeDefault */ false);
-
-        assertEquals("Flag " + key + "is not set to default after change",
-                mDeviceConfig.getFlag(key), defaultValue);
-
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_WINDOW_MANAGER, key,
-                /* value */ Boolean.FALSE.toString(), /* makeDefault */ false);
-
-        assertEquals("Flag " + key + "is not set to default after change",
-                mDeviceConfig.getFlag(key), defaultValue);
-    }
-
-}
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationPersisterTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationPersisterTest.java
index 51a7e74..06033c7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationPersisterTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationPersisterTest.java
@@ -20,7 +20,6 @@
 
 import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT;
 import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP;
-import static com.android.server.wm.LetterboxConfigurationPersister.LETTERBOX_CONFIGURATION_FILENAME;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -42,13 +41,26 @@
 import java.io.File;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.Consumer;
+import java.util.function.Supplier;
 
+/**
+ * Tests for the {@link LetterboxConfigurationPersister} class.
+ *
+ * Build/Install/Run:
+ *  atest WmTests:LetterboxConfigurationPersisterTest
+ */
 @SmallTest
 @Presubmit
 public class LetterboxConfigurationPersisterTest {
 
     private static final long TIMEOUT = 2000L; // 2 secs
 
+    private static final int DEFAULT_REACHABILITY_TEST = -1;
+    private static final Supplier<Integer> DEFAULT_REACHABILITY_SUPPLIER_TEST =
+            () -> DEFAULT_REACHABILITY_TEST;
+
+    private static final String LETTERBOX_CONFIGURATION_TEST_FILENAME = "letterbox_config_test";
+
     private LetterboxConfigurationPersister mLetterboxConfigurationPersister;
     private Context mContext;
     private PersisterQueue mPersisterQueue;
@@ -62,7 +74,7 @@
         mConfigFolder = mContext.getFilesDir();
         mPersisterQueue = new PersisterQueue();
         mQueueState = new QueueState();
-        mLetterboxConfigurationPersister = new LetterboxConfigurationPersister(mContext,
+        mLetterboxConfigurationPersister = new LetterboxConfigurationPersister(
                 () -> mContext.getResources().getInteger(
                         R.integer.config_letterboxDefaultPositionForHorizontalReachability),
                 () -> mContext.getResources().getInteger(
@@ -72,7 +84,8 @@
                 () -> mContext.getResources().getInteger(
                         R.integer.config_letterboxDefaultPositionForTabletopModeReachability
                 ),
-                mConfigFolder, mPersisterQueue, mQueueState);
+                mConfigFolder, mPersisterQueue, mQueueState,
+                LETTERBOX_CONFIGURATION_TEST_FILENAME);
         mQueueListener = queueEmpty -> mQueueState.onItemAdded();
         mPersisterQueue.addListener(mQueueListener);
         mLetterboxConfigurationPersister.start();
@@ -127,8 +140,10 @@
     public void test_whenUpdatedWithNewValues_valuesAreReadAfterRestart() {
         final PersisterQueue firstPersisterQueue = new PersisterQueue();
         final LetterboxConfigurationPersister firstPersister = new LetterboxConfigurationPersister(
-                mContext, () -> -1, () -> -1, () -> -1, () -> -1, mContext.getFilesDir(),
-                firstPersisterQueue, mQueueState);
+                DEFAULT_REACHABILITY_SUPPLIER_TEST, DEFAULT_REACHABILITY_SUPPLIER_TEST,
+                DEFAULT_REACHABILITY_SUPPLIER_TEST, DEFAULT_REACHABILITY_SUPPLIER_TEST,
+                mContext.getFilesDir(), firstPersisterQueue, mQueueState,
+                LETTERBOX_CONFIGURATION_TEST_FILENAME);
         firstPersister.start();
         firstPersister.setLetterboxPositionForHorizontalReachability(false,
                 LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT);
@@ -138,8 +153,10 @@
         stopPersisterSafe(firstPersisterQueue);
         final PersisterQueue secondPersisterQueue = new PersisterQueue();
         final LetterboxConfigurationPersister secondPersister = new LetterboxConfigurationPersister(
-                mContext, () -> -1, () -> -1, () -> -1, () -> -1, mContext.getFilesDir(),
-                secondPersisterQueue, mQueueState);
+                DEFAULT_REACHABILITY_SUPPLIER_TEST, DEFAULT_REACHABILITY_SUPPLIER_TEST,
+                DEFAULT_REACHABILITY_SUPPLIER_TEST, DEFAULT_REACHABILITY_SUPPLIER_TEST,
+                mContext.getFilesDir(), secondPersisterQueue, mQueueState,
+                LETTERBOX_CONFIGURATION_TEST_FILENAME);
         secondPersister.start();
         final int newPositionForHorizontalReachability =
                 secondPersister.getLetterboxPositionForHorizontalReachability(false);
@@ -156,37 +173,46 @@
 
     @Test
     public void test_whenUpdatedWithNewValuesAndDeleted_valuesAreDefaults() {
-        mLetterboxConfigurationPersister.setLetterboxPositionForHorizontalReachability(false,
+        final PersisterQueue firstPersisterQueue = new PersisterQueue();
+        final LetterboxConfigurationPersister firstPersister = new LetterboxConfigurationPersister(
+                DEFAULT_REACHABILITY_SUPPLIER_TEST, DEFAULT_REACHABILITY_SUPPLIER_TEST,
+                DEFAULT_REACHABILITY_SUPPLIER_TEST, DEFAULT_REACHABILITY_SUPPLIER_TEST,
+                mContext.getFilesDir(), firstPersisterQueue, mQueueState,
+                LETTERBOX_CONFIGURATION_TEST_FILENAME);
+        firstPersister.start();
+        firstPersister.setLetterboxPositionForHorizontalReachability(false,
                 LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT);
-        mLetterboxConfigurationPersister.setLetterboxPositionForVerticalReachability(false,
+        firstPersister.setLetterboxPositionForVerticalReachability(false,
                 LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP);
         waitForCompletion(mPersisterQueue);
         final int newPositionForHorizontalReachability =
-                mLetterboxConfigurationPersister.getLetterboxPositionForHorizontalReachability(
-                        false);
+                firstPersister.getLetterboxPositionForHorizontalReachability(false);
         final int newPositionForVerticalReachability =
-                mLetterboxConfigurationPersister.getLetterboxPositionForVerticalReachability(false);
+                firstPersister.getLetterboxPositionForVerticalReachability(false);
         Assert.assertEquals(LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT,
                 newPositionForHorizontalReachability);
         Assert.assertEquals(LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP,
                 newPositionForVerticalReachability);
-        deleteConfiguration(mLetterboxConfigurationPersister, mPersisterQueue);
-        waitForCompletion(mPersisterQueue);
+        deleteConfiguration(firstPersister, firstPersisterQueue);
+        waitForCompletion(firstPersisterQueue);
+        stopPersisterSafe(firstPersisterQueue);
+
+        final PersisterQueue secondPersisterQueue = new PersisterQueue();
+        final LetterboxConfigurationPersister secondPersister = new LetterboxConfigurationPersister(
+                DEFAULT_REACHABILITY_SUPPLIER_TEST, DEFAULT_REACHABILITY_SUPPLIER_TEST,
+                DEFAULT_REACHABILITY_SUPPLIER_TEST, DEFAULT_REACHABILITY_SUPPLIER_TEST,
+                mContext.getFilesDir(), secondPersisterQueue, mQueueState,
+                LETTERBOX_CONFIGURATION_TEST_FILENAME);
+        secondPersister.start();
         final int positionForHorizontalReachability =
-                mLetterboxConfigurationPersister.getLetterboxPositionForHorizontalReachability(
-                        false);
-        final int defaultPositionForHorizontalReachability =
-                mContext.getResources().getInteger(
-                        R.integer.config_letterboxDefaultPositionForHorizontalReachability);
-        Assert.assertEquals(defaultPositionForHorizontalReachability,
-                positionForHorizontalReachability);
+                secondPersister.getLetterboxPositionForHorizontalReachability(false);
         final int positionForVerticalReachability =
-                mLetterboxConfigurationPersister.getLetterboxPositionForVerticalReachability(false);
-        final int defaultPositionForVerticalReachability =
-                mContext.getResources().getInteger(
-                        R.integer.config_letterboxDefaultPositionForVerticalReachability);
-        Assert.assertEquals(defaultPositionForVerticalReachability,
-                positionForVerticalReachability);
+                secondPersister.getLetterboxPositionForVerticalReachability(false);
+        Assert.assertEquals(DEFAULT_REACHABILITY_TEST, positionForHorizontalReachability);
+        Assert.assertEquals(DEFAULT_REACHABILITY_TEST, positionForVerticalReachability);
+        deleteConfiguration(secondPersister, secondPersisterQueue);
+        waitForCompletion(secondPersisterQueue);
+        stopPersisterSafe(secondPersisterQueue);
     }
 
     private void stopPersisterSafe(PersisterQueue persisterQueue) {
@@ -222,7 +248,7 @@
     private void deleteConfiguration(LetterboxConfigurationPersister persister,
             PersisterQueue persisterQueue) {
         final AtomicFile fileToDelete = new AtomicFile(
-                new File(mConfigFolder, LETTERBOX_CONFIGURATION_FILENAME));
+                new File(mConfigFolder, LETTERBOX_CONFIGURATION_TEST_FILENAME));
         persisterQueue.addItem(
                 new DeleteFileCommand(fileToDelete, mQueueState.andThen(
                         s -> persister.useDefaultValue())), true);
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
index 7d507e9..81a3794 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
@@ -38,6 +38,7 @@
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static android.view.InsetsSource.FLAG_INSETS_ROUNDED_CORNER;
 import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION;
 import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH;
 import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE;
@@ -62,7 +63,6 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.verify;
 
@@ -146,7 +146,9 @@
     @Test
     @EnableCompatChanges({OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION})
     public void testShouldIgnoreRequestedOrientation_cameraCompatTreatment_returnsTrue() {
-        doReturn(true).when(mLetterboxConfiguration).isCameraCompatTreatmentEnabled(anyBoolean());
+        doReturn(true).when(mLetterboxConfiguration).isCameraCompatTreatmentEnabled();
+        doReturn(true).when(mLetterboxConfiguration)
+                .isCameraCompatTreatmentEnabledAtBuildTime();
 
         // Recreate DisplayContent with DisplayRotationCompatPolicy
         mActivity = setUpActivityWithComponent();
@@ -305,7 +307,7 @@
     @Test
     public void testShouldRefreshActivityForCameraCompat_flagIsDisabled_returnsFalse() {
         doReturn(false).when(mLetterboxConfiguration)
-                .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true);
+                .isCameraCompatTreatmentEnabled();
 
         assertFalse(mController.shouldRefreshActivityForCameraCompat());
     }
@@ -314,7 +316,7 @@
     @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH})
     public void testShouldRefreshActivityForCameraCompat_overrideEnabled_returnsFalse() {
         doReturn(true).when(mLetterboxConfiguration)
-                .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true);
+                .isCameraCompatTreatmentEnabled();
 
         assertFalse(mController.shouldRefreshActivityForCameraCompat());
     }
@@ -324,7 +326,7 @@
     public void testShouldRefreshActivityForCameraCompat_propertyIsTrueAndOverride_returnsFalse()
             throws Exception {
         doReturn(true).when(mLetterboxConfiguration)
-                .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true);
+                .isCameraCompatTreatmentEnabled();
         mockThatProperty(PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH, /* value */ true);
 
         mController = new LetterboxUiController(mWm, mActivity);
@@ -336,7 +338,7 @@
     public void testShouldRefreshActivityForCameraCompat_propertyIsFalse_returnsFalse()
             throws Exception {
         doReturn(true).when(mLetterboxConfiguration)
-                .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true);
+                .isCameraCompatTreatmentEnabled();
         mockThatProperty(PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH, /* value */ false);
 
         mController = new LetterboxUiController(mWm, mActivity);
@@ -348,7 +350,7 @@
     public void testShouldRefreshActivityForCameraCompat_propertyIsTrue_returnsTrue()
             throws Exception {
         doReturn(true).when(mLetterboxConfiguration)
-                .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true);
+                .isCameraCompatTreatmentEnabled();
         mockThatProperty(PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH, /* value */ true);
 
         mController = new LetterboxUiController(mWm, mActivity);
@@ -362,7 +364,7 @@
     @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE})
     public void testShouldRefreshActivityViaPauseForCameraCompat_flagIsDisabled_returnsFalse() {
         doReturn(false).when(mLetterboxConfiguration)
-                .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true);
+                .isCameraCompatTreatmentEnabled();
 
         assertFalse(mController.shouldRefreshActivityViaPauseForCameraCompat());
     }
@@ -371,7 +373,7 @@
     @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE})
     public void testShouldRefreshActivityViaPauseForCameraCompat_overrideEnabled_returnsTrue() {
         doReturn(true).when(mLetterboxConfiguration)
-                .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true);
+                .isCameraCompatTreatmentEnabled();
 
         assertTrue(mController.shouldRefreshActivityViaPauseForCameraCompat());
     }
@@ -381,7 +383,7 @@
     public void testShouldRefreshActivityViaPauseForCameraCompat_propertyIsFalseAndOverride_returnFalse()
             throws Exception {
         doReturn(true).when(mLetterboxConfiguration)
-                .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true);
+                .isCameraCompatTreatmentEnabled();
         mockThatProperty(PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE, /* value */ false);
 
         mController = new LetterboxUiController(mWm, mActivity);
@@ -393,7 +395,7 @@
     public void testShouldRefreshActivityViaPauseForCameraCompat_propertyIsTrue_returnsTrue()
             throws Exception {
         doReturn(true).when(mLetterboxConfiguration)
-                .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true);
+                .isCameraCompatTreatmentEnabled();
         mockThatProperty(PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE, /* value */ true);
 
         mController = new LetterboxUiController(mWm, mActivity);
@@ -406,7 +408,7 @@
     @Test
     public void testShouldForceRotateForCameraCompat_flagIsDisabled_returnsFalse() {
         doReturn(false).when(mLetterboxConfiguration)
-                .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true);
+                .isCameraCompatTreatmentEnabled();
 
         assertFalse(mController.shouldForceRotateForCameraCompat());
     }
@@ -415,7 +417,7 @@
     @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION})
     public void testShouldForceRotateForCameraCompat_overrideEnabled_returnsFalse() {
         doReturn(true).when(mLetterboxConfiguration)
-                .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true);
+                .isCameraCompatTreatmentEnabled();
 
         assertFalse(mController.shouldForceRotateForCameraCompat());
     }
@@ -425,7 +427,7 @@
     public void testShouldForceRotateForCameraCompat_propertyIsTrueAndOverride_returnsFalse()
             throws Exception {
         doReturn(true).when(mLetterboxConfiguration)
-                .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true);
+                .isCameraCompatTreatmentEnabled();
         mockThatProperty(PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION, /* value */ true);
 
         mController = new LetterboxUiController(mWm, mActivity);
@@ -437,7 +439,7 @@
     public void testShouldForceRotateForCameraCompat_propertyIsFalse_returnsFalse()
             throws Exception {
         doReturn(true).when(mLetterboxConfiguration)
-                .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true);
+                .isCameraCompatTreatmentEnabled();
         mockThatProperty(PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION, /* value */ false);
 
         mController = new LetterboxUiController(mWm, mActivity);
@@ -449,7 +451,7 @@
     public void testShouldForceRotateForCameraCompat_propertyIsTrue_returnsTrue()
             throws Exception {
         doReturn(true).when(mLetterboxConfiguration)
-                .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true);
+                .isCameraCompatTreatmentEnabled();
         mockThatProperty(PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION, /* value */ true);
 
         mController = new LetterboxUiController(mWm, mActivity);
@@ -461,7 +463,7 @@
     public void testGetCropBoundsIfNeeded_handleCropForTransparentActivityBasedOnOpaqueBounds() {
         final InsetsSource taskbar = new InsetsSource(/*id=*/ 0,
                  WindowInsets.Type.navigationBars());
-        taskbar.setInsetsRoundedCornerFrame(true);
+        taskbar.setFlags(FLAG_INSETS_ROUNDED_CORNER, FLAG_INSETS_ROUNDED_CORNER);
         final WindowState mainWindow = mockForGetCropBoundsAndRoundedCorners(taskbar);
         final Rect opaqueBounds = new Rect(0, 0, 500, 300);
         doReturn(opaqueBounds).when(mActivity).getBounds();
@@ -505,7 +507,7 @@
     public void testGetCropBoundsIfNeeded_appliesCrop() {
         final InsetsSource taskbar = new InsetsSource(/*id=*/ 0,
                 WindowInsets.Type.navigationBars());
-        taskbar.setInsetsRoundedCornerFrame(true);
+        taskbar.setFlags(FLAG_INSETS_ROUNDED_CORNER, FLAG_INSETS_ROUNDED_CORNER);
         final WindowState mainWindow = mockForGetCropBoundsAndRoundedCorners(taskbar);
 
         // Apply crop if taskbar is expanded
@@ -528,7 +530,7 @@
     public void testGetCropBoundsIfNeeded_appliesCropWithSizeCompatScaling() {
         final InsetsSource taskbar = new InsetsSource(/*id=*/ 0,
                 WindowInsets.Type.navigationBars());
-        taskbar.setInsetsRoundedCornerFrame(true);
+        taskbar.setFlags(FLAG_INSETS_ROUNDED_CORNER, FLAG_INSETS_ROUNDED_CORNER);
         final WindowState mainWindow = mockForGetCropBoundsAndRoundedCorners(taskbar);
         final float scaling = 2.0f;
 
@@ -740,7 +742,9 @@
     @EnableCompatChanges({OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT,
             OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA})
     public void testOverrideOrientationIfNeeded_whenCameraNotActive_returnsUnchanged() {
-        doReturn(true).when(mLetterboxConfiguration).isCameraCompatTreatmentEnabled(anyBoolean());
+        doReturn(true).when(mLetterboxConfiguration).isCameraCompatTreatmentEnabled();
+        doReturn(true).when(mLetterboxConfiguration)
+                .isCameraCompatTreatmentEnabledAtBuildTime();
 
         // Recreate DisplayContent with DisplayRotationCompatPolicy
         mActivity = setUpActivityWithComponent();
@@ -758,7 +762,9 @@
     @EnableCompatChanges({OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT,
             OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA})
     public void testOverrideOrientationIfNeeded_whenCameraActive_returnsPortrait() {
-        doReturn(true).when(mLetterboxConfiguration).isCameraCompatTreatmentEnabled(anyBoolean());
+        doReturn(true).when(mLetterboxConfiguration).isCameraCompatTreatmentEnabled();
+        doReturn(true).when(mLetterboxConfiguration)
+                .isCameraCompatTreatmentEnabledAtBuildTime();
 
         // Recreate DisplayContent with DisplayRotationCompatPolicy
         mActivity = setUpActivityWithComponent();
@@ -1059,7 +1065,9 @@
     @Test
     public void testgetFixedOrientationLetterboxAspectRatio_splitScreenAspectEnabled() {
         doReturn(true).when(mActivity.mWmService.mLetterboxConfiguration)
-                .isCameraCompatTreatmentEnabled(anyBoolean());
+                .isCameraCompatTreatmentEnabled();
+        doReturn(true).when(mActivity.mWmService.mLetterboxConfiguration)
+                .isCameraCompatTreatmentEnabledAtBuildTime();
         doReturn(true).when(mActivity.mWmService.mLetterboxConfiguration)
                 .isCameraCompatSplitScreenAspectRatioEnabled();
         doReturn(false).when(mActivity.mWmService.mLetterboxConfiguration)
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
index 0ccb0d0..7cb5802 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
@@ -27,8 +27,6 @@
 import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
-import static android.content.pm.ActivityInfo.LAUNCH_MULTIPLE;
-import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE;
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
 import static android.os.Process.NOBODY_UID;
 
@@ -49,10 +47,12 @@
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
 
 import static java.lang.Integer.MAX_VALUE;
 
@@ -449,25 +449,15 @@
         final int uid = 10123;
         final Task task1 = createTaskBuilder(".Task1").build();
         final ComponentName componentName = getUniqueComponentName();
-        task1.affinity = ActivityRecord.computeTaskAffinity(taskAffinity, uid, LAUNCH_MULTIPLE,
-                componentName);
+        task1.affinity = ActivityRecord.computeTaskAffinity(taskAffinity, uid);
         mRecentTasks.add(task1);
 
         // Add another task to recents, and make sure the previous task was removed.
         final Task task2 = createTaskBuilder(".Task2").build();
-        task2.affinity = ActivityRecord.computeTaskAffinity(taskAffinity, uid, LAUNCH_MULTIPLE,
-                componentName);
+        task2.affinity = ActivityRecord.computeTaskAffinity(taskAffinity, uid);
         mRecentTasks.add(task2);
         assertEquals(1, mRecentTasks.getRecentTasks(MAX_VALUE, 0 /* flags */,
                 true /* getTasksAllowed */, TEST_USER_0_ID, 0).getList().size());
-
-        // Add another single-instance task to recents, and make sure no task is removed.
-        final Task task3 = createTaskBuilder(".Task3").build();
-        task3.affinity = ActivityRecord.computeTaskAffinity(taskAffinity, uid,
-                LAUNCH_SINGLE_INSTANCE, componentName);
-        mRecentTasks.add(task3);
-        assertEquals(2, mRecentTasks.getRecentTasks(MAX_VALUE, 0 /* flags */,
-                true /* getTasksAllowed */, TEST_USER_0_ID, 0).getList().size());
     }
 
     @Test
@@ -1139,6 +1129,40 @@
     }
 
     @Test
+    public void addTask_taskAlreadyInRecentsMovedToTop_callsTaskNotificationController() {
+        final Task firstTask = createTaskBuilder(".Task").build();
+        final Task secondTask = createTaskBuilder(".Task2").build();
+
+        mRecentTasks.add(firstTask);
+        mRecentTasks.add(secondTask);
+
+        TaskChangeNotificationController controller =
+                mAtm.getTaskChangeNotificationController();
+        clearInvocations(controller);
+
+        // Add firstTask back to top
+        mRecentTasks.add(firstTask);
+        verify(controller).notifyTaskListUpdated();
+    }
+
+    @Test
+    public void addTask_taskAlreadyInRecentsOnTop_doesNotNotify() {
+        final Task firstTask = createTaskBuilder(".Task").build();
+        final Task secondTask = createTaskBuilder(".Task2").build();
+
+        mRecentTasks.add(firstTask);
+        mRecentTasks.add(secondTask);
+
+        TaskChangeNotificationController controller =
+                mAtm.getTaskChangeNotificationController();
+        clearInvocations(controller);
+
+        // Add secondTask to top again
+        mRecentTasks.add(secondTask);
+        verifyZeroInteractions(controller);
+    }
+
+    @Test
     public void removeTask_callsTaskNotificationController() {
         final Task task = createTaskBuilder(".Task").build();
 
@@ -1279,6 +1303,26 @@
         assertTrue(info.supportsMultiWindow);
     }
 
+    @Test
+    public void testRemoveCompatibleRecentTask() {
+        final Task task1 = createTaskBuilder(".Task").setWindowingMode(
+                WINDOWING_MODE_FULLSCREEN).build();
+        mRecentTasks.add(task1);
+        final Task task2 = createTaskBuilder(".Task").setWindowingMode(
+                WINDOWING_MODE_MULTI_WINDOW).build();
+        mRecentTasks.add(task2);
+        assertEquals(2, mRecentTasks.getRecentTasks(MAX_VALUE, 0 /* flags */,
+                true /* getTasksAllowed */, TEST_USER_0_ID, 0).getList().size());
+
+        // Set windowing mode and ensure the same fullscreen task that created earlier is removed.
+        task2.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+        mRecentTasks.removeCompatibleRecentTask(task2);
+        assertEquals(1, mRecentTasks.getRecentTasks(MAX_VALUE, 0 /* flags */,
+                true /* getTasksAllowed */, TEST_USER_0_ID, 0).getList().size());
+        assertEquals(task2.mTaskId, mRecentTasks.getRecentTasks(MAX_VALUE, 0 /* flags */,
+                true /* getTasksAllowed */, TEST_USER_0_ID, 0).getList().get(0).taskId);
+    }
+
     private TaskSnapshot createSnapshot(Point taskSize, Point bufferSize) {
         HardwareBuffer buffer = null;
         if (bufferSize != null) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/SafeActivityOptionsTest.java b/services/tests/wmtests/src/com/android/server/wm/SafeActivityOptionsTest.java
index 24e932f..6c48a69 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SafeActivityOptionsTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SafeActivityOptionsTest.java
@@ -16,17 +16,36 @@
 
 package com.android.server.wm;
 
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.content.pm.PackageManager.PERMISSION_DENIED;
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.mock;
 
 import android.app.ActivityOptions;
+import android.content.pm.ActivityInfo;
+import android.os.Looper;
 import android.platform.test.annotations.Presubmit;
+import android.view.RemoteAnimationAdapter;
+import android.window.RemoteTransition;
 import android.window.WindowContainerToken;
 
 import androidx.test.filters.MediumTest;
 
 import org.junit.Test;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
 
 /**
  * Build/Install/Run:
@@ -73,4 +92,119 @@
 
         assertSame(clone.getOriginalOptions().getLaunchRootTask(), token);
     }
+
+    @Test
+    public void test_getOptions() {
+        // Mock everything necessary
+        MockitoSession mockingSession = mockitoSession()
+                .mockStatic(ActivityTaskManagerService.class)
+                .strictness(Strictness.LENIENT)
+                .startMocking();
+        doReturn(PERMISSION_DENIED).when(() -> ActivityTaskManagerService.checkPermission(
+                any(), anyInt(), anyInt()));
+
+        final LockTaskController lockTaskController = mock(LockTaskController.class);
+        doReturn(false).when(lockTaskController).isPackageAllowlisted(anyInt(), any());
+
+        final ActivityTaskManagerService atm = mock(ActivityTaskManagerService.class);
+        doReturn(lockTaskController).when(atm).getLockTaskController();
+
+        final ActivityTaskSupervisor taskSupervisor =
+                new ActivityTaskSupervisor(atm, mock(Looper.class));
+        spyOn(taskSupervisor);
+        doReturn(false).when(taskSupervisor).isCallerAllowedToLaunchOnDisplay(anyInt(),
+                anyInt(), anyInt(), any());
+        doReturn(false).when(taskSupervisor).isCallerAllowedToLaunchOnTaskDisplayArea(anyInt(),
+                anyInt(), any(), any());
+
+        taskSupervisor.mRecentTasks = mock(RecentTasks.class);
+        doReturn(false).when(taskSupervisor.mRecentTasks).isCallerRecents(anyInt());
+
+        // Ensure exceptions are thrown when lack of permissions.
+        ActivityOptions activityOptions = ActivityOptions.makeBasic();
+        try {
+            activityOptions.setLaunchTaskId(100);
+            verifySecureExceptionThrown(activityOptions, taskSupervisor);
+
+            activityOptions = ActivityOptions.makeBasic();
+            activityOptions.setDisableStartingWindow(true);
+            verifySecureExceptionThrown(activityOptions, taskSupervisor);
+
+            activityOptions = ActivityOptions.makeBasic();
+            activityOptions.setTransientLaunch();
+            verifySecureExceptionThrown(activityOptions, taskSupervisor);
+
+            activityOptions = ActivityOptions.makeBasic();
+            activityOptions.setDismissKeyguard();
+            verifySecureExceptionThrown(activityOptions, taskSupervisor);
+
+            activityOptions = ActivityOptions.makeBasic();
+            activityOptions.setLaunchActivityType(ACTIVITY_TYPE_STANDARD);
+            verifySecureExceptionThrown(activityOptions, taskSupervisor);
+
+            activityOptions = ActivityOptions.makeBasic();
+            activityOptions.setLaunchedFromBubble(true);
+            verifySecureExceptionThrown(activityOptions, taskSupervisor);
+
+            activityOptions = ActivityOptions.makeBasic();
+            activityOptions.setLaunchDisplayId(DEFAULT_DISPLAY);
+            verifySecureExceptionThrown(activityOptions, taskSupervisor);
+
+            activityOptions = ActivityOptions.makeBasic();
+            activityOptions.setLockTaskEnabled(true);
+            verifySecureExceptionThrown(activityOptions, taskSupervisor);
+
+            activityOptions = ActivityOptions.makeCustomTaskAnimation(
+                    getInstrumentation().getContext(), 0, 0, null, null, null);
+            verifySecureExceptionThrown(activityOptions, taskSupervisor);
+
+            RemoteAnimationAdapter remoteAnimationAdapter = mock(RemoteAnimationAdapter.class);
+            RemoteTransition remoteTransition = mock(RemoteTransition.class);
+            activityOptions = ActivityOptions.makeRemoteAnimation(remoteAnimationAdapter);
+            verifySecureExceptionThrown(activityOptions, taskSupervisor);
+
+            activityOptions = ActivityOptions.makeRemoteAnimation(remoteAnimationAdapter,
+                    remoteTransition);
+            verifySecureExceptionThrown(activityOptions, taskSupervisor);
+
+            activityOptions = ActivityOptions.makeBasic();
+            activityOptions.setRemoteAnimationAdapter(remoteAnimationAdapter);
+            verifySecureExceptionThrown(activityOptions, taskSupervisor);
+
+            activityOptions = ActivityOptions.makeRemoteTransition(remoteTransition);
+            verifySecureExceptionThrown(activityOptions, taskSupervisor);
+
+            activityOptions = ActivityOptions.makeBasic();
+            activityOptions.setRemoteTransition(remoteTransition);
+            verifySecureExceptionThrown(activityOptions, taskSupervisor);
+
+            verifySecureExceptionThrown(activityOptions, taskSupervisor,
+                    mock(TaskDisplayArea.class));
+        } finally {
+            mockingSession.finishMocking();
+        }
+    }
+
+    private void verifySecureExceptionThrown(ActivityOptions activityOptions,
+            ActivityTaskSupervisor taskSupervisor) {
+        verifySecureExceptionThrown(activityOptions, taskSupervisor, null /* mockTda */);
+    }
+
+    private void verifySecureExceptionThrown(ActivityOptions activityOptions,
+            ActivityTaskSupervisor taskSupervisor, TaskDisplayArea mockTda) {
+        SafeActivityOptions safeActivityOptions = new SafeActivityOptions(activityOptions);
+        if (mockTda != null) {
+            spyOn(safeActivityOptions);
+            doReturn(mockTda).when(safeActivityOptions).getLaunchTaskDisplayArea(any(), any());
+        }
+
+        boolean isExceptionThrow = false;
+        final ActivityInfo aInfo = mock(ActivityInfo.class);
+        try {
+            safeActivityOptions.getOptions(null, aInfo, null, taskSupervisor);
+        } catch (SecurityException ex) {
+            isExceptionThrow = true;
+        }
+        assertTrue(isExceptionThrow);
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 6a89b55..3908947 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -30,6 +30,7 @@
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
 import static android.provider.DeviceConfig.NAMESPACE_CONSTRAIN_DISPLAY_APIS;
+import static android.view.InsetsSource.FLAG_INSETS_ROUNDED_CORNER;
 import static android.view.Surface.ROTATION_0;
 import static android.view.Surface.ROTATION_180;
 import static android.view.Surface.ROTATION_270;
@@ -3548,7 +3549,7 @@
 
         final InsetsSource navSource = new InsetsSource(
                 InsetsSource.createId(null, 0, navigationBars()), navigationBars());
-        navSource.setInsetsRoundedCornerFrame(true);
+        navSource.setFlags(FLAG_INSETS_ROUNDED_CORNER, FLAG_INSETS_ROUNDED_CORNER);
         navSource.setFrame(new Rect(0, screenHeight - taskbarHeight, screenWidth, screenHeight));
 
         mActivity.mWmService.mLetterboxConfiguration.setLetterboxActivityCornersRadius(15);
@@ -3596,7 +3597,7 @@
 
         final InsetsSource navSource = new InsetsSource(
                 InsetsSource.createId(null, 0, navigationBars()), navigationBars());
-        navSource.setInsetsRoundedCornerFrame(true);
+        navSource.setFlags(FLAG_INSETS_ROUNDED_CORNER, FLAG_INSETS_ROUNDED_CORNER);
         // Immersive activity has transient navbar
         navSource.setVisible(!immersive);
         navSource.setFrame(new Rect(0, screenHeight - taskbarHeight, screenWidth, screenHeight));
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 41bfc80..1a4b94b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SurfaceControlViewHostTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceControlViewHostTests.java
@@ -98,7 +98,7 @@
         mView1 = new Button(mActivity);
         mView2 = new Button(mActivity);
 
-        mActivity.runOnUiThread(() -> {
+        mInstrumentation.runOnMainSync(() -> {
             TestWindowlessWindowManager wwm = new TestWindowlessWindowManager(
                     mActivity.getResources().getConfiguration(), sc, null);
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/SurfaceViewSyncContinuousTest.java b/services/tests/wmtests/src/com/android/server/wm/SurfaceViewSyncContinuousTest.java
index ad7314c..f958e6f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SurfaceViewSyncContinuousTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceViewSyncContinuousTest.java
@@ -18,7 +18,6 @@
 
 import static android.server.wm.UiDeviceUtils.pressUnlockButton;
 import static android.server.wm.UiDeviceUtils.pressWakeupButton;
-import static android.server.wm.WindowManagerState.getLogicalDisplaySize;
 
 import android.app.KeyguardManager;
 import android.os.PowerManager;
@@ -46,7 +45,6 @@
     @Before
     public void setup() {
         mCapturedActivity = mActivityRule.getActivity();
-        mCapturedActivity.setLogicalDisplaySize(getLogicalDisplaySize());
 
         final KeyguardManager km = mCapturedActivity.getSystemService(KeyguardManager.class);
         if (km != null && km.isKeyguardLocked() || !Objects.requireNonNull(
diff --git a/services/tests/wmtests/src/com/android/server/wm/SynchedDeviceConfigTests.java b/services/tests/wmtests/src/com/android/server/wm/SynchedDeviceConfigTests.java
new file mode 100644
index 0000000..7d44e11
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/SynchedDeviceConfigTests.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+
+import android.app.ActivityThread;
+import android.platform.test.annotations.Presubmit;
+import android.provider.DeviceConfig;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.modules.utils.testing.TestableDeviceConfig;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.util.Objects;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Test class for {@link SynchedDeviceConfig}.
+ *
+ * atest WmTests:SynchedDeviceConfigTests
+ */
+@SmallTest
+@Presubmit
+public class SynchedDeviceConfigTests {
+
+    private static final long WAIT_FOR_PROPERTY_CHANGE_TIMEOUT_MILLIS = 2000; // 2 sec
+    private static final String NAMESPACE_FOR_TEST = "TestingNameSpace";
+
+    private SynchedDeviceConfig mDeviceConfig;
+
+    private Executor mExecutor;
+
+    @Rule
+    public final TestableDeviceConfig.TestableDeviceConfigRule
+            mDeviceConfigRule = new TestableDeviceConfig.TestableDeviceConfigRule();
+
+    @Before
+    public void setUp() {
+        mExecutor = Objects.requireNonNull(ActivityThread.currentApplication()).getMainExecutor();
+        mDeviceConfig = SynchedDeviceConfig
+                .builder(/* nameSpace */ NAMESPACE_FOR_TEST, /* executor */ mExecutor)
+                .addDeviceConfigEntry(/* key */ "key1", /* default */ true, /* enabled */ true)
+                .addDeviceConfigEntry(/* key */ "key2", /* default */ false, /* enabled */ true)
+                .addDeviceConfigEntry(/* key */ "key3",  /* default */ true, /* enabled */ false)
+                .addDeviceConfigEntry(/* key */ "key4",  /* default */ false, /* enabled */ false)
+                .build();
+    }
+
+    @After
+    public void tearDown() {
+        DeviceConfig.removeOnPropertiesChangedListener(mDeviceConfig);
+    }
+
+    @Test
+    public void testWhenStarted_initialValuesAreDefaultOrFalseIfDisabled() {
+        assertFlagValue(/* key */ "key1", /* expected */ true); // enabled
+        assertFlagValue(/* key */ "key2", /* expected */ false); // enabled
+        assertFlagValue(/* key */ "key3", /* expected */ false); // disabled
+        assertFlagValue(/* key */ "key4", /* expected */ false); // disabled
+    }
+
+    @Test
+    public void testIsEnabled() {
+        assertFlagEnabled(/* key */ "key1", /* expected */ true);
+        assertFlagEnabled(/* key */ "key2", /* expected */ true);
+        assertFlagEnabled(/* key */ "key3", /* expected */ false);
+        assertFlagEnabled(/* key */ "key4", /* expected */ false);
+    }
+
+    @Test
+    public void testWhenUpdated_onlyEnabledChanges() {
+        final CountDownLatch countDownLatch = new CountDownLatch(4);
+        spyOn(mDeviceConfig);
+        doAnswer(invocation -> {
+            invocation.callRealMethod();
+            countDownLatch.countDown();
+            return null;
+        }).when(mDeviceConfig).onPropertiesChanged(any());
+        try {
+            // We update all the keys
+            updateProperty(/* key */ "key1", /* value */ false);
+            updateProperty(/* key */ "key2", /* value */ true);
+            updateProperty(/* key */ "key3", /* value */ false);
+            updateProperty(/* key */ "key4", /* value */ true);
+
+            assertThat(countDownLatch.await(
+                    WAIT_FOR_PROPERTY_CHANGE_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)).isTrue();
+
+            // We update all the flags but only the enabled ones change
+            assertFlagValue(/* key */ "key1", /* expected */ false); // changes
+            assertFlagValue(/* key */ "key2", /* expected */ true); // changes
+            assertFlagValue(/* key */ "key3", /* expected */ false); // disabled
+            assertFlagValue(/* key */ "key4", /* expected */ false); // disabled
+        } catch (InterruptedException e) {
+            Assert.fail(e.getMessage());
+        }
+    }
+
+    private void assertFlagValue(String key, boolean expectedValue) {
+        assertEquals(/* message */"Flag " + key + " value is not " + expectedValue, /* expected */
+                expectedValue, /* actual */ mDeviceConfig.getFlagValue(key));
+    }
+
+
+    private void assertFlagEnabled(String key, boolean expectedValue) {
+        assertEquals(/* message */
+                "Flag " + key + " enabled is not " + expectedValue, /* expected */
+                expectedValue, /* actual */ mDeviceConfig.isBuildTimeFlagEnabled(key));
+    }
+
+    private void updateProperty(String key, Boolean value) {
+        DeviceConfig.setProperty(NAMESPACE_FOR_TEST, key, /* value */
+                value.toString(), /* makeDefault */ false);
+    }
+}
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 7edfd9a..e252b9e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
@@ -208,7 +208,11 @@
         spyOn(mContext);
 
         doReturn(null).when(mContext)
-                .registerReceiver(nullable(BroadcastReceiver.class), any(IntentFilter.class));
+                .registerReceiver(nullable(BroadcastReceiver.class), any(IntentFilter.class),
+                        nullable(String.class), nullable(Handler.class));
+        doReturn(null).when(mContext)
+                .registerReceiver(nullable(BroadcastReceiver.class), any(IntentFilter.class),
+                        nullable(String.class), nullable(Handler.class), anyInt());
         doReturn(null).when(mContext)
                 .registerReceiverAsUser(any(BroadcastReceiver.class), any(UserHandle.class),
                         any(IntentFilter.class), nullable(String.class), nullable(Handler.class));
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
index 99ab715..54b9351 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
@@ -26,6 +26,7 @@
 import static android.view.WindowManager.TRANSIT_OPEN;
 import static android.window.TaskFragmentOperation.OP_TYPE_CREATE_TASK_FRAGMENT;
 import static android.window.TaskFragmentOperation.OP_TYPE_DELETE_TASK_FRAGMENT;
+import static android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_FRONT;
 import static android.window.TaskFragmentOperation.OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT;
 import static android.window.TaskFragmentOperation.OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS;
 import static android.window.TaskFragmentOperation.OP_TYPE_SET_ANIMATION_PARAMS;
@@ -1590,6 +1591,46 @@
         assertEquals(taskFragmentBounds, mTaskFragment.getBounds());
     }
 
+    @Test
+    public void testApplyTransaction_reorderTaskFragmentToFront() {
+        final Task task = createTask(mDisplayContent);
+        // Create a TaskFragment.
+        final IBinder token0 = new Binder();
+        final TaskFragment tf0 = new TaskFragmentBuilder(mAtm)
+                .setParentTask(task)
+                .setFragmentToken(token0)
+                .setOrganizer(mOrganizer)
+                .createActivityCount(1)
+                .build();
+        // Create another TaskFragment
+        final IBinder token1 = new Binder();
+        final TaskFragment tf1 = new TaskFragmentBuilder(mAtm)
+                .setParentTask(task)
+                .setFragmentToken(token1)
+                .setOrganizer(mOrganizer)
+                .createActivityCount(1)
+                .build();
+        // Create a non-embedded Activity on top.
+        final ActivityRecord topActivity = new ActivityBuilder(mAtm)
+                .setTask(task)
+                .build();
+        mWindowOrganizerController.mLaunchTaskFragments.put(token0, tf0);
+        mWindowOrganizerController.mLaunchTaskFragments.put(token1, tf1);
+
+        // Reorder TaskFragment to front
+        final TaskFragmentOperation operation = new TaskFragmentOperation.Builder(
+                OP_TYPE_REORDER_TO_FRONT).build();
+        mTransaction.addTaskFragmentOperation(token0, operation);
+        assertApplyTransactionAllowed(mTransaction);
+
+        // Ensure the non-embedded activity still on top.
+        assertEquals(topActivity, task.getTopChild().asActivityRecord());
+
+        // Ensure the TaskFragment is moved to front.
+        final TaskFragment frontMostTaskFragment = task.getTaskFragment(tf -> tf.asTask() == null);
+        assertEquals(frontMostTaskFragment, tf0);
+    }
+
     /**
      * Creates a {@link TaskFragment} with the {@link WindowContainerTransaction}. Calls
      * {@link WindowOrganizerController#applyTransaction(WindowContainerTransaction)} to apply the
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 28241d3..f332b69 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -563,6 +563,36 @@
         assertEquals(freeformBounds, task.getBounds());
     }
 
+    @Test
+    public void testTopActivityEligibleForUserAspectRatioButton() {
+        DisplayContent display = mAtm.mRootWindowContainer.getDefaultDisplay();
+        final Task rootTask = new TaskBuilder(mSupervisor).setCreateActivity(true)
+                .setWindowingMode(WINDOWING_MODE_FULLSCREEN).setDisplay(display).build();
+        final Task task = rootTask.getBottomMostTask();
+        final ActivityRecord root = task.getTopNonFinishingActivity();
+        spyOn(mWm.mLetterboxConfiguration);
+
+        // When device config flag is disabled the button is not enabled
+        doReturn(false).when(mWm.mLetterboxConfiguration)
+                .isUserAppAspectRatioSettingsEnabled();
+        doReturn(false).when(mWm.mLetterboxConfiguration)
+                .isTranslucentLetterboxingEnabled();
+        assertFalse(task.getTaskInfo().topActivityEligibleForUserAspectRatioButton);
+
+        // The flag is enabled
+        doReturn(true).when(mWm.mLetterboxConfiguration)
+                .isUserAppAspectRatioSettingsEnabled();
+        spyOn(root);
+        doReturn(task).when(root).getOrganizedTask();
+        // When the flag is enabled and the top activity is not in size compat mode.
+        doReturn(false).when(root).inSizeCompatMode();
+        assertTrue(task.getTaskInfo().topActivityEligibleForUserAspectRatioButton);
+
+        // When in size compat mode the button is not enabled
+        doReturn(true).when(root).inSizeCompatMode();
+        assertFalse(task.getTaskInfo().topActivityEligibleForUserAspectRatioButton);
+    }
+
     /**
      * Tests that a task with forced orientation has orientation-consistent bounds within the
      * parent.
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
index 7e4a9de..45ecc3f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
@@ -30,30 +30,18 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doAnswer;
-
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.content.Context;
 import android.content.res.Configuration;
-import android.content.res.Resources;
 import android.graphics.Insets;
 import android.graphics.Rect;
 import android.hardware.display.DisplayManagerGlobal;
-import android.util.DisplayMetrics;
-import android.util.TypedValue;
 import android.view.Display;
 import android.view.DisplayCutout;
 import android.view.DisplayInfo;
 
 import com.android.server.wm.DisplayWindowSettings.SettingsProvider.SettingsEntry;
 
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.stubbing.Answer;
-
 class TestDisplayContent extends DisplayContent {
 
     public static final int DEFAULT_LOGICAL_DISPLAY_DENSITY = 300;
@@ -89,6 +77,10 @@
         spyOn(inputMonitor);
         doNothing().when(inputMonitor).resumeDispatchingLw(any());
 
+        final InsetsPolicy insetsPolicy = getInsetsPolicy();
+        WindowTestsBase.suppressInsetsAnimation(insetsPolicy.getPermanentControlTarget());
+        WindowTestsBase.suppressInsetsAnimation(insetsPolicy.getTransientControlTarget());
+
         // For devices that set the sysprop ro.bootanim.set_orientation_<display_id>
         // See DisplayRotation#readDefaultDisplayRotation for context.
         // Without that, meaning of height and width in context of the tests can be swapped if
@@ -105,13 +97,8 @@
         private boolean mSystemDecorations = false;
         private int mStatusBarHeight = 0;
         private SettingsEntry mOverrideSettings;
-        private DisplayMetrics mDisplayMetrics;
         @NonNull
         private DeviceStateController mDeviceStateController = mock(DeviceStateController.class);
-        @Mock
-        Context mMockContext;
-        @Mock
-        Resources mResources;
 
         Builder(ActivityTaskManagerService service, int width, int height) {
             mService = service;
@@ -124,8 +111,6 @@
             // Set unique ID so physical display overrides are not inheritted from
             // DisplayWindowSettings.
             mInfo.uniqueId = generateUniqueId();
-            mDisplayMetrics = new DisplayMetrics();
-            updateDisplayMetrics();
         }
         Builder(ActivityTaskManagerService service, DisplayInfo info) {
             mService = service;
@@ -195,31 +180,7 @@
             mInfo.logicalDensityDpi = dpi;
             return this;
         }
-        Builder updateDisplayMetrics() {
-            mInfo.getAppMetrics(mDisplayMetrics);
-            return this;
-        }
-        Builder setDefaultMinTaskSizeDp(int valueDp) {
-            MockitoAnnotations.initMocks(this);
-            doReturn(mMockContext).when(mService.mContext).createConfigurationContext(any());
-            doReturn(mResources).when(mMockContext).getResources();
-            doAnswer(
-                    new Answer() {
-                        @Override
-                        public Object answer(InvocationOnMock i) {
-                            Object[] args = i.getArguments();
-                            TypedValue v = (TypedValue) args[1];
-                            v.type = TypedValue.TYPE_DIMENSION;
-                            v.data = TypedValue.createComplexDimension(valueDp,
-                                    TypedValue.COMPLEX_UNIT_DIP);
-                            return null;
-                        }
-                    }
-            ).when(mResources).getValue(
-                    eq(com.android.internal.R.dimen.default_minimal_size_resizable_task),
-                    any(TypedValue.class), eq(true));
-            return this;
-        }
+
         Builder setDeviceStateController(@NonNull DeviceStateController deviceStateController) {
             mDeviceStateController = deviceStateController;
             return this;
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestDisplayWindowSettingsProvider.java b/services/tests/wmtests/src/com/android/server/wm/TestDisplayWindowSettingsProvider.java
index b2e44b1..e11df98 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestDisplayWindowSettingsProvider.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestDisplayWindowSettingsProvider.java
@@ -52,6 +52,12 @@
         overrideSettings.setTo(overrides);
     }
 
+    @Override
+    public void onDisplayRemoved(@NonNull DisplayInfo info) {
+        final String identifier = getIdentifier(info);
+        mOverrideSettingsMap.remove(identifier);
+    }
+
     @NonNull
     private SettingsEntry getOrCreateOverrideSettingsEntry(DisplayInfo info) {
         final String identifier = getIdentifier(info);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index d4fabf4..ffecafb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -115,7 +115,11 @@
     private BLASTSyncEngine mSyncEngine;
 
     private Transition createTestTransition(int transitType, TransitionController controller) {
-        return new Transition(transitType, 0 /* flags */, controller, controller.mSyncEngine);
+        final Transition transition = new Transition(transitType, 0 /* flags */, controller,
+                controller.mSyncEngine);
+        spyOn(transition.mLogger);
+        doNothing().when(transition.mLogger).logOnSendAsync(any());
+        return transition;
     }
 
     private Transition createTestTransition(int transitType) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/TrustedPresentationCallbackTest.java b/services/tests/wmtests/src/com/android/server/wm/TrustedPresentationCallbackTest.java
new file mode 100644
index 0000000..df11a44
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/TrustedPresentationCallbackTest.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 com.android.server.wm;
+
+import static android.server.wm.ActivityManagerTestBase.createFullscreenActivityScenarioRule;
+import static android.server.wm.BuildUtils.HW_TIMEOUT_MULTIPLIER;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.app.Activity;
+import android.platform.test.annotations.Presubmit;
+import android.util.Log;
+import android.view.SurfaceControl;
+import android.view.SurfaceControl.TrustedPresentationThresholds;
+
+import androidx.test.ext.junit.rules.ActivityScenarioRule;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
+
+/**
+ * TODO (b/287076178): Move these tests to
+ * {@link android.view.surfacecontrol.cts.TrustedPresentationCallbackTest} when API is made public
+ */
+@Presubmit
+public class TrustedPresentationCallbackTest {
+    private static final String TAG = "TrustedPresentationCallbackTest";
+    private static final int STABILITY_REQUIREMENT_MS = 500;
+    private static final long WAIT_TIME_MS = HW_TIMEOUT_MULTIPLIER * 2000L;
+
+    private static final float FRACTION_VISIBLE = 0.1f;
+
+    @Rule
+    public ActivityScenarioRule<TestActivity> mActivityRule = createFullscreenActivityScenarioRule(
+            TestActivity.class);
+
+    private TestActivity mActivity;
+
+    @Before
+    public void setup() {
+        mActivityRule.getScenario().onActivity(activity -> mActivity = activity);
+    }
+
+    @Test
+    public void testAddTrustedPresentationListenerOnWindow() throws InterruptedException {
+        boolean[] results = new boolean[1];
+        CountDownLatch receivedResults = new CountDownLatch(1);
+        TrustedPresentationThresholds thresholds = new TrustedPresentationThresholds(
+                1 /* minAlpha */, FRACTION_VISIBLE, STABILITY_REQUIREMENT_MS);
+        SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+        mActivity.getWindow().getRootSurfaceControl().addTrustedPresentationCallback(t, thresholds,
+                Runnable::run, inTrustedPresentationState -> {
+                    Log.d(TAG, "onTrustedPresentationChanged " + inTrustedPresentationState);
+                    results[0] = inTrustedPresentationState;
+                    receivedResults.countDown();
+                });
+        t.apply();
+
+        assertTrue("Timed out waiting for results",
+                receivedResults.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+        assertTrue(results[0]);
+    }
+
+    @Test
+    public void testRemoveTrustedPresentationListenerOnWindow() throws InterruptedException {
+        final Object resultsLock = new Object();
+        boolean[] results = new boolean[1];
+        boolean[] receivedResults = new boolean[1];
+        TrustedPresentationThresholds thresholds = new TrustedPresentationThresholds(
+                1 /* minAlpha */, FRACTION_VISIBLE, STABILITY_REQUIREMENT_MS);
+        Consumer<Boolean> trustedPresentationCallback = inTrustedPresentationState -> {
+            synchronized (resultsLock) {
+                results[0] = inTrustedPresentationState;
+                receivedResults[0] = true;
+                resultsLock.notify();
+            }
+        };
+        SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+        mActivity.getWindow().getRootSurfaceControl().addTrustedPresentationCallback(t, thresholds,
+                Runnable::run, trustedPresentationCallback);
+        t.apply();
+
+        synchronized (resultsLock) {
+            if (!receivedResults[0]) {
+                resultsLock.wait(WAIT_TIME_MS);
+            }
+            // Make sure we received the results and not just timed out
+            assertTrue("Timed out waiting for results", receivedResults[0]);
+            assertTrue(results[0]);
+
+            // reset the state
+            receivedResults[0] = false;
+        }
+
+        mActivity.getWindow().getRootSurfaceControl().removeTrustedPresentationCallback(t,
+                trustedPresentationCallback);
+        t.apply();
+
+        synchronized (resultsLock) {
+            if (!receivedResults[0]) {
+                resultsLock.wait(WAIT_TIME_MS);
+            }
+            // Ensure we waited the full time and never received a notify on the result from the
+            // callback.
+            assertFalse("Should never have received a callback", receivedResults[0]);
+            // results shouldn't have changed.
+            assertTrue(results[0]);
+        }
+    }
+
+    public static class TestActivity extends Activity {
+    }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
index 2d8ddfa..a82459f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
@@ -64,10 +64,19 @@
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.clearInvocations;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.pm.ActivityInfo;
 import android.content.res.Configuration;
 import android.graphics.Rect;
+import android.os.Binder;
+import android.os.DeadObjectException;
+import android.os.IBinder;
+import android.os.IInterface;
+import android.os.Parcel;
 import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.os.ShellCallback;
 import android.platform.test.annotations.Presubmit;
 import android.view.IRemoteAnimationFinishedCallback;
 import android.view.IRemoteAnimationRunner;
@@ -87,8 +96,10 @@
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mockito;
 
+import java.io.FileDescriptor;
 import java.util.ArrayList;
 import java.util.Comparator;
+import java.util.NoSuchElementException;
 
 
 /**
@@ -1404,7 +1415,7 @@
     }
 
     @Test
-    public void testAddLocalInsetsSourceProvider() {
+    public void testAddLocalInsetsFrameProvider() {
          /*
                 ___ rootTask _______________________________________________
                |        |                |                                  |
@@ -1435,19 +1446,20 @@
                 TYPE_BASE_APPLICATION);
         attrs2.setTitle("AppWindow2");
         activity2.addWindow(createWindowState(attrs2, activity2));
+        final Binder owner = new Binder();
         Rect genericOverlayInsetsRect1 = new Rect(0, 200, 1080, 700);
         Rect genericOverlayInsetsRect2 = new Rect(0, 0, 1080, 200);
         final InsetsFrameProvider provider1 =
-                new InsetsFrameProvider(null, 1, WindowInsets.Type.systemOverlays())
+                new InsetsFrameProvider(owner, 1, WindowInsets.Type.systemOverlays())
                         .setArbitraryRectangle(genericOverlayInsetsRect1);
         final InsetsFrameProvider provider2 =
-                new InsetsFrameProvider(null, 2, WindowInsets.Type.systemOverlays())
+                new InsetsFrameProvider(owner, 2, WindowInsets.Type.systemOverlays())
                         .setArbitraryRectangle(genericOverlayInsetsRect2);
         final int sourceId1 = provider1.getId();
         final int sourceId2 = provider2.getId();
 
-        rootTask.addLocalInsetsFrameProvider(provider1);
-        container.addLocalInsetsFrameProvider(provider2);
+        rootTask.addLocalInsetsFrameProvider(provider1, owner);
+        container.addLocalInsetsFrameProvider(provider2, owner);
 
         InsetsSource genericOverlayInsetsProvider1Source = new InsetsSource(
                 sourceId1, systemOverlays());
@@ -1479,7 +1491,7 @@
     }
 
     @Test
-    public void testAddLocalInsetsSourceProvider_sameType_replacesInsets() {
+    public void testAddLocalInsetsFrameProvider_sameType_replacesInsets() {
          /*
                 ___ rootTask ________________________________________
                |                  |                                  |
@@ -1494,24 +1506,25 @@
         attrs.setTitle("AppWindow0");
         activity0.addWindow(createWindowState(attrs, activity0));
 
+        final Binder owner = new Binder();
         final Rect genericOverlayInsetsRect1 = new Rect(0, 200, 1080, 700);
         final Rect genericOverlayInsetsRect2 = new Rect(0, 0, 1080, 200);
         final InsetsFrameProvider provider1 =
-                new InsetsFrameProvider(null, 1, WindowInsets.Type.systemOverlays())
+                new InsetsFrameProvider(owner, 1, WindowInsets.Type.systemOverlays())
                         .setArbitraryRectangle(genericOverlayInsetsRect1);
         final InsetsFrameProvider provider2 =
-                new InsetsFrameProvider(null, 1, WindowInsets.Type.systemOverlays())
+                new InsetsFrameProvider(owner, 1, WindowInsets.Type.systemOverlays())
                         .setArbitraryRectangle(genericOverlayInsetsRect2);
         final int sourceId1 = provider1.getId();
         final int sourceId2 = provider2.getId();
 
-        rootTask.addLocalInsetsFrameProvider(provider1);
+        rootTask.addLocalInsetsFrameProvider(provider1, owner);
         activity0.forAllWindows(window -> {
             assertEquals(genericOverlayInsetsRect1,
                     window.getInsetsState().peekSource(sourceId1).getFrame());
         }, true);
 
-        rootTask.addLocalInsetsFrameProvider(provider2);
+        rootTask.addLocalInsetsFrameProvider(provider2, owner);
 
         activity0.forAllWindows(window -> {
             assertEquals(genericOverlayInsetsRect2,
@@ -1520,7 +1533,7 @@
     }
 
     @Test
-    public void testRemoveLocalInsetsSourceProvider() {
+    public void testRemoveLocalInsetsFrameProvider() {
          /*
                 ___ rootTask _______________________________________________
                |        |                |                                  |
@@ -1554,21 +1567,22 @@
 
         activity2.addWindow(createWindowState(attrs2, activity2));
 
+        final Binder owner = new Binder();
         final Rect navigationBarInsetsRect1 = new Rect(0, 200, 1080, 700);
         final Rect navigationBarInsetsRect2 = new Rect(0, 0, 1080, 200);
         final InsetsFrameProvider provider1 =
-                new InsetsFrameProvider(null, 1, WindowInsets.Type.systemOverlays())
+                new InsetsFrameProvider(owner, 1, WindowInsets.Type.systemOverlays())
                         .setArbitraryRectangle(navigationBarInsetsRect1);
         final InsetsFrameProvider provider2 =
-                new InsetsFrameProvider(null, 2, WindowInsets.Type.systemOverlays())
+                new InsetsFrameProvider(owner, 2, WindowInsets.Type.systemOverlays())
                         .setArbitraryRectangle(navigationBarInsetsRect2);
         final int sourceId1 = provider1.getId();
         final int sourceId2 = provider2.getId();
 
-        rootTask.addLocalInsetsFrameProvider(provider1);
-        container.addLocalInsetsFrameProvider(provider2);
+        rootTask.addLocalInsetsFrameProvider(provider1, owner);
+        container.addLocalInsetsFrameProvider(provider2, owner);
         mDisplayContent.getInsetsStateController().onPostLayout();
-        rootTask.removeLocalInsetsFrameProvider(provider1);
+        rootTask.removeLocalInsetsFrameProvider(provider1, owner);
         mDisplayContent.getInsetsStateController().onPostLayout();
 
         activity0.forAllWindows(window -> {
@@ -1593,6 +1607,67 @@
         }, true);
     }
 
+    @Test
+    public void testAddLocalInsetsFrameProvider_ownerDiesAfterAdding() {
+        final Task task = createTask(mDisplayContent);
+        final TestBinder owner = new TestBinder();
+        final InsetsFrameProvider provider =
+                new InsetsFrameProvider(owner, 0, WindowInsets.Type.systemOverlays())
+                        .setArbitraryRectangle(new Rect());
+        task.addLocalInsetsFrameProvider(provider, owner);
+
+        assertTrue("The death recipient must exist.", owner.hasDeathRecipient());
+        assertTrue("The source must be added.", hasLocalSource(task, provider.getId()));
+
+        // The owner dies after adding the source.
+        owner.die();
+
+        assertFalse("The death recipient must be removed.", owner.hasDeathRecipient());
+        assertFalse("The source must be removed.", hasLocalSource(task, provider.getId()));
+    }
+
+    @Test
+    public void testAddLocalInsetsFrameProvider_ownerDiesBeforeAdding() {
+        final Task task = createTask(mDisplayContent);
+        final TestBinder owner = new TestBinder();
+
+        // The owner dies before adding the source.
+        owner.die();
+
+        final InsetsFrameProvider provider =
+                new InsetsFrameProvider(owner, 0, WindowInsets.Type.systemOverlays())
+                        .setArbitraryRectangle(new Rect());
+        task.addLocalInsetsFrameProvider(provider, owner);
+
+        assertFalse("The death recipient must not exist.", owner.hasDeathRecipient());
+        assertFalse("The source must not be added.", hasLocalSource(task, provider.getId()));
+    }
+
+    @Test
+    public void testRemoveLocalInsetsFrameProvider_removeDeathRecipient() {
+        final Task task = createTask(mDisplayContent);
+        final TestBinder owner = new TestBinder();
+        final InsetsFrameProvider provider =
+                new InsetsFrameProvider(owner, 0, WindowInsets.Type.systemOverlays())
+                        .setArbitraryRectangle(new Rect());
+        task.addLocalInsetsFrameProvider(provider, owner);
+
+        assertTrue("The death recipient must exist.", owner.hasDeathRecipient());
+        assertTrue("The source must be added.", hasLocalSource(task, provider.getId()));
+
+        task.removeLocalInsetsFrameProvider(provider, owner);
+
+        assertFalse("The death recipient must be removed.", owner.hasDeathRecipient());
+        assertFalse("The source must be removed.", hasLocalSource(task, provider.getId()));
+    }
+
+    private static boolean hasLocalSource(WindowContainer container, int sourceId) {
+        if (container.mLocalInsetsSources == null) {
+            return false;
+        }
+        return container.mLocalInsetsSources.contains(sourceId);
+    }
+
     /* Used so we can gain access to some protected members of the {@link WindowContainer} class */
     private static class TestWindowContainer extends WindowContainer<TestWindowContainer> {
         private final int mLayer;
@@ -1797,4 +1872,82 @@
             mIsVisibleRequested = isVisibleRequested;
         }
     }
+
+    private static class TestBinder implements IBinder {
+
+        private boolean mDead;
+        private final ArrayList<IBinder.DeathRecipient> mDeathRecipients = new ArrayList<>();
+
+        public void die() {
+            mDead = true;
+            for (int i = mDeathRecipients.size() - 1; i >= 0; i--) {
+                final DeathRecipient recipient = mDeathRecipients.get(i);
+                recipient.binderDied(this);
+            }
+        }
+
+        public boolean hasDeathRecipient() {
+            return !mDeathRecipients.isEmpty();
+        }
+
+        @Override
+        public void linkToDeath(@NonNull DeathRecipient recipient, int flags)
+                throws RemoteException {
+            if (mDead) {
+                throw new DeadObjectException();
+            }
+            mDeathRecipients.add(recipient);
+        }
+
+        @Override
+        public boolean unlinkToDeath(@NonNull DeathRecipient recipient, int flags) {
+            final boolean successes = mDeathRecipients.remove(recipient);
+            if (successes || mDead) {
+                return successes;
+            }
+            throw new NoSuchElementException("Given recipient has not been registered.");
+        }
+
+        @Override
+        public boolean isBinderAlive() {
+            return !mDead;
+        }
+
+        @Override
+        public boolean pingBinder() {
+            return !mDead;
+        }
+
+        @Nullable
+        @Override
+        public String getInterfaceDescriptor() throws RemoteException {
+            return null;
+        }
+
+        @Nullable
+        @Override
+        public IInterface queryLocalInterface(@NonNull String descriptor) {
+            return null;
+        }
+
+        @Override
+        public void dump(@NonNull FileDescriptor fd, @Nullable String[] args)
+                throws RemoteException { }
+
+        @Override
+        public void dumpAsync(@NonNull FileDescriptor fd, @Nullable String[] args)
+                throws RemoteException { }
+
+        @Override
+        public void shellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out,
+                @Nullable FileDescriptor err, @NonNull String[] args,
+                @Nullable ShellCallback shellCallback,
+                @NonNull ResultReceiver resultReceiver) throws RemoteException { }
+
+        @Override
+        public boolean transact(int code, @NonNull Parcel data, @Nullable Parcel reply, int flags)
+                throws RemoteException {
+            return false;
+        }
+    }
 }
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 d3f6818..45331f7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -26,6 +26,7 @@
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.FLAG_OWN_FOCUS;
+import static android.view.Display.INVALID_DISPLAY;
 import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
 import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
 import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_SPY;
@@ -52,6 +53,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
@@ -80,6 +82,7 @@
 import android.platform.test.annotations.Presubmit;
 import android.util.DisplayMetrics;
 import android.util.MergedConfiguration;
+import android.view.ContentRecordingSession;
 import android.view.IWindow;
 import android.view.IWindowSessionCallback;
 import android.view.InputChannel;
@@ -99,6 +102,8 @@
 import androidx.test.platform.app.InstrumentationRegistry;
 
 import com.android.compatibility.common.util.AdoptShellPermissionsRule;
+import com.android.internal.os.IResultReceiver;
+import com.android.server.LocalServices;
 
 import org.junit.Rule;
 import org.junit.Test;
@@ -513,6 +518,13 @@
     }
 
     @Test
+    public void testIsInTouchMode_returnsDefaultInTouchModeForinexistingDisplay() {
+        assertThat(mWm.isInTouchMode(INVALID_DISPLAY)).isEqualTo(
+                mContext.getResources().getBoolean(
+                        com.android.internal.R.bool.config_defaultInTouchMode));
+    }
+
+    @Test
     public void testSetInTouchMode_instrumentedProcessGetPermissionToSwitchTouchMode() {
         // Enable global touch mode
         mWm.mPerDisplayFocusEnabled = true;
@@ -759,6 +771,63 @@
     }
 
     @Test
+    public void setContentRecordingSession_sessionNull_returnsTrue() {
+        WindowManagerInternal wmInternal = LocalServices.getService(WindowManagerInternal.class);
+
+        boolean result = wmInternal.setContentRecordingSession(/* incomingSession= */ null);
+
+        assertThat(result).isTrue();
+    }
+
+    @Test
+    public void setContentRecordingSession_sessionContentDisplay_returnsTrue() {
+        WindowManagerInternal wmInternal = LocalServices.getService(WindowManagerInternal.class);
+        ContentRecordingSession session = ContentRecordingSession.createDisplaySession(
+                DEFAULT_DISPLAY);
+
+        boolean result = wmInternal.setContentRecordingSession(session);
+
+        assertThat(result).isTrue();
+    }
+
+    @Test
+    public void setContentRecordingSession_sessionContentTask_noMatchingTask_returnsFalse() {
+        WindowManagerInternal wmInternal = LocalServices.getService(WindowManagerInternal.class);
+        IBinder launchCookie = new Binder();
+        ContentRecordingSession session = ContentRecordingSession.createTaskSession(launchCookie);
+
+        boolean result = wmInternal.setContentRecordingSession(session);
+
+        assertThat(result).isFalse();
+    }
+
+    @Test
+    public void setContentRecordingSession_sessionContentTask_matchingTask_returnsTrue() {
+        WindowManagerInternal wmInternal = LocalServices.getService(WindowManagerInternal.class);
+        ActivityRecord activityRecord = createActivityRecord(createTask(mDefaultDisplay));
+        ContentRecordingSession session = ContentRecordingSession.createTaskSession(
+                activityRecord.mLaunchCookie);
+
+        boolean result = wmInternal.setContentRecordingSession(session);
+
+        assertThat(result).isTrue();
+    }
+
+    @Test
+    public void setContentRecordingSession_matchingTask_mutatesSessionWithWindowContainerToken() {
+        WindowManagerInternal wmInternal = LocalServices.getService(WindowManagerInternal.class);
+        Task task = createTask(mDefaultDisplay);
+        ActivityRecord activityRecord = createActivityRecord(task);
+        ContentRecordingSession session = ContentRecordingSession.createTaskSession(
+                activityRecord.mLaunchCookie);
+
+        wmInternal.setContentRecordingSession(session);
+
+        assertThat(session.getTokenToRecord()).isEqualTo(
+                task.mRemoteToken.toWindowContainerToken().asBinder());
+    }
+
+    @Test
     public void testisLetterboxBackgroundMultiColored() {
         assertThat(setupLetterboxConfigurationWithBackgroundType(
                 LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING)).isTrue();
@@ -905,6 +974,56 @@
                 argThat(h -> (h.inputConfig & InputConfig.SPY) == InputConfig.SPY));
     }
 
+    @Test
+    public void testRequestKeyboardShortcuts_noWindow() {
+        doNothing().when(mWm.mContext).enforceCallingOrSelfPermission(anyString(), anyString());
+        doReturn(null).when(mWm).getFocusedWindowLocked();
+        doReturn(null).when(mWm.mRoot).getCurrentInputMethodWindow();
+
+        TestResultReceiver receiver = new TestResultReceiver();
+        mWm.requestAppKeyboardShortcuts(receiver, 0);
+        assertNotNull(receiver.resultData);
+        assertTrue(receiver.resultData.isEmpty());
+
+        receiver = new TestResultReceiver();
+        mWm.requestImeKeyboardShortcuts(receiver, 0);
+        assertNotNull(receiver.resultData);
+        assertTrue(receiver.resultData.isEmpty());
+    }
+
+    @Test
+    public void testRequestKeyboardShortcuts() throws RemoteException {
+        final IWindow window = mock(IWindow.class);
+        final IBinder binder = mock(IBinder.class);
+        doReturn(binder).when(window).asBinder();
+        final WindowState windowState =
+                createWindow(null, TYPE_BASE_APPLICATION, mDisplayContent, "appWin", window);
+        doNothing().when(mWm.mContext).enforceCallingOrSelfPermission(anyString(), anyString());
+        doReturn(windowState).when(mWm).getFocusedWindowLocked();
+        doReturn(windowState).when(mWm.mRoot).getCurrentInputMethodWindow();
+
+        TestResultReceiver receiver = new TestResultReceiver();
+        mWm.requestAppKeyboardShortcuts(receiver, 0);
+        mWm.requestImeKeyboardShortcuts(receiver, 0);
+        verify(window, times(2)).requestAppKeyboardShortcuts(receiver, 0);
+    }
+
+    class TestResultReceiver implements IResultReceiver {
+        public android.os.Bundle resultData;
+        private final IBinder mBinder = mock(IBinder.class);
+
+        @Override
+        public void send(int resultCode, android.os.Bundle resultData)
+                throws android.os.RemoteException {
+            this.resultData = resultData;
+        }
+
+        @Override
+        public android.os.IBinder asBinder() {
+            return mBinder;
+        }
+    }
+
     private void setupActivityWithLaunchCookie(IBinder launchCookie, WindowContainerToken wct) {
         final WindowContainer.RemoteToken remoteToken = mock(WindowContainer.RemoteToken.class);
         when(remoteToken.toWindowContainerToken()).thenReturn(wct);
@@ -917,7 +1036,7 @@
 
     private boolean setupLetterboxConfigurationWithBackgroundType(
             @LetterboxConfiguration.LetterboxBackgroundType int letterboxBackgroundType) {
-        mWm.mLetterboxConfiguration.setLetterboxBackgroundType(letterboxBackgroundType);
+        mWm.mLetterboxConfiguration.setLetterboxBackgroundTypeOverride(letterboxBackgroundType);
         return mWm.isLetterboxBackgroundMultiColored();
     }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index be8ee78..d5547ec 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -222,6 +222,10 @@
         displayPolicy.finishWindowsDrawn();
         displayPolicy.finishScreenTurningOn();
 
+        final InsetsPolicy insetsPolicy = mDefaultDisplay.getInsetsPolicy();
+        suppressInsetsAnimation(insetsPolicy.getTransientControlTarget());
+        suppressInsetsAnimation(insetsPolicy.getPermanentControlTarget());
+
         mTransaction = mSystemServicesTestRule.mTransaction;
         mMockSession = mock(Session.class);
 
@@ -278,6 +282,15 @@
         checkDeviceSpecificOverridesNotApplied();
     }
 
+    /**
+     * The test doesn't create real SurfaceControls, but mocked ones. This prevents the target from
+     * controlling them, or it will cause {@link NullPointerException}.
+     */
+    static void suppressInsetsAnimation(InsetsControlTarget target) {
+        spyOn(target);
+        Mockito.doNothing().when(target).notifyInsetsControlChanged();
+    }
+
     @After
     public void tearDown() throws Exception {
         if (mUseFakeSettingsProvider) {
diff --git a/services/texttospeech/java/com/android/server/texttospeech/TextToSpeechManagerService.java b/services/texttospeech/java/com/android/server/texttospeech/TextToSpeechManagerService.java
index 9015563..2411498 100644
--- a/services/texttospeech/java/com/android/server/texttospeech/TextToSpeechManagerService.java
+++ b/services/texttospeech/java/com/android/server/texttospeech/TextToSpeechManagerService.java
@@ -63,6 +63,12 @@
         public void createSession(String engine,
                 ITextToSpeechSessionCallback sessionCallback) {
             synchronized (mLock) {
+                if (engine == null) {
+                    runSessionCallbackMethod(
+                            () -> sessionCallback.onError("Engine cannot be null"));
+                    return;
+                }
+
                 TextToSpeechManagerPerUserService perUserService = getServiceForUserLocked(
                         UserHandle.getCallingUserId());
                 if (perUserService != null) {
diff --git a/services/usage/java/com/android/server/usage/BroadcastResponseStatsTracker.java b/services/usage/java/com/android/server/usage/BroadcastResponseStatsTracker.java
index 7d5be8e..c47d459 100644
--- a/services/usage/java/com/android/server/usage/BroadcastResponseStatsTracker.java
+++ b/services/usage/java/com/android/server/usage/BroadcastResponseStatsTracker.java
@@ -28,9 +28,9 @@
 import android.app.usage.BroadcastResponseStats;
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.os.Process;
 import android.os.SystemClock;
 import android.os.UserHandle;
-import android.permission.PermissionManager;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.LongArrayQueue;
@@ -94,9 +94,12 @@
     private AppStandbyInternal mAppStandby;
     private BroadcastResponseStatsLogger mLogger;
     private RoleManager mRoleManager;
+    private final Context mContext;
 
-    BroadcastResponseStatsTracker(@NonNull AppStandbyInternal appStandby) {
+    BroadcastResponseStatsTracker(@NonNull AppStandbyInternal appStandby,
+            @NonNull Context context) {
         mAppStandby = appStandby;
+        mContext = context;
         mLogger = new BroadcastResponseStatsLogger();
     }
 
@@ -305,12 +308,19 @@
 
     boolean doesPackageHoldExemptedPermission(@NonNull String packageName,
             @NonNull UserHandle user) {
-        final List<String> exemptedPermissions = mAppStandby
-                .getBroadcastResponseExemptedPermissions();
+        int uid;
+        try {
+            uid = mContext.getPackageManager().getPackageUidAsUser(
+                    packageName, user.getIdentifier());
+        } catch (PackageManager.NameNotFoundException e) {
+            return false;
+        }
+        final List<String> exemptedPermissions =
+                mAppStandby.getBroadcastResponseExemptedPermissions();
         for (int i = exemptedPermissions.size() - 1; i >= 0; --i) {
             final String permissionName = exemptedPermissions.get(i);
-            if (PermissionManager.checkPackageNamePermission(permissionName, packageName,
-                    user.getIdentifier()) == PackageManager.PERMISSION_GRANTED) {
+            if (mContext.checkPermission(permissionName, Process.INVALID_PID, uid)
+                    == PackageManager.PERMISSION_GRANTED) {
                 return true;
             }
         }
diff --git a/services/usage/java/com/android/server/usage/UsageStatsDatabase.java b/services/usage/java/com/android/server/usage/UsageStatsDatabase.java
index 361b1c0..f1c5865 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsDatabase.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsDatabase.java
@@ -26,9 +26,9 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.AtomicFile;
+import android.util.LongSparseArray;
 import android.util.Slog;
 import android.util.SparseArray;
-import android.util.TimeSparseArray;
 import android.util.TimeUtils;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -117,8 +117,8 @@
 
     private final Object mLock = new Object();
     private final File[] mIntervalDirs;
-    @VisibleForTesting
-    final TimeSparseArray<AtomicFile>[] mSortedStatFiles;
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    final LongSparseArray<AtomicFile>[] mSortedStatFiles;
     private final UnixCalendar mCal;
     private final File mVersionFile;
     private final File mBackupsDir;
@@ -155,7 +155,7 @@
         mVersionFile = new File(dir, "version");
         mBackupsDir = new File(dir, "backups");
         mUpdateBreadcrumb = new File(dir, "breadcrumb");
-        mSortedStatFiles = new TimeSparseArray[mIntervalDirs.length];
+        mSortedStatFiles = new LongSparseArray[mIntervalDirs.length];
         mPackageMappingsFile = new File(dir, "mappings");
         mCal = new UnixCalendar(0);
     }
@@ -178,11 +178,16 @@
             }
 
             checkVersionAndBuildLocked();
-            indexFilesLocked();
+            // Perform a pruning of older files if there was an upgrade, otherwise do indexing.
+            if (mUpgradePerformed) {
+                prune(currentTimeMillis); // prune performs an indexing when done
+            } else {
+                indexFilesLocked();
+            }
 
             // Delete files that are in the future.
-            for (TimeSparseArray<AtomicFile> files : mSortedStatFiles) {
-                final int startIndex = files.closestIndexOnOrAfter(currentTimeMillis);
+            for (LongSparseArray<AtomicFile> files : mSortedStatFiles) {
+                final int startIndex = files.firstIndexOnOrAfter(currentTimeMillis);
                 if (startIndex < 0) {
                     continue;
                 }
@@ -216,7 +221,7 @@
      */
     public boolean checkinDailyFiles(CheckinAction checkinAction) {
         synchronized (mLock) {
-            final TimeSparseArray<AtomicFile> files =
+            final LongSparseArray<AtomicFile> files =
                     mSortedStatFiles[UsageStatsManager.INTERVAL_DAILY];
             final int fileCount = files.size();
 
@@ -287,7 +292,7 @@
         // Index the available usage stat files on disk.
         for (int i = 0; i < mSortedStatFiles.length; i++) {
             if (mSortedStatFiles[i] == null) {
-                mSortedStatFiles[i] = new TimeSparseArray<>();
+                mSortedStatFiles[i] = new LongSparseArray<>();
             } else {
                 mSortedStatFiles[i].clear();
             }
@@ -695,7 +700,7 @@
             int filesDeleted = 0;
             int filesMoved = 0;
 
-            for (TimeSparseArray<AtomicFile> files : mSortedStatFiles) {
+            for (LongSparseArray<AtomicFile> files : mSortedStatFiles) {
                 final int fileCount = files.size();
                 for (int i = 0; i < fileCount; i++) {
                     final AtomicFile file = files.valueAt(i);
@@ -829,9 +834,9 @@
         }
 
         synchronized (mLock) {
-            final TimeSparseArray<AtomicFile> intervalStats = mSortedStatFiles[intervalType];
+            final LongSparseArray<AtomicFile> intervalStats = mSortedStatFiles[intervalType];
 
-            int endIndex = intervalStats.closestIndexOnOrBefore(endTime);
+            int endIndex = intervalStats.lastIndexOnOrBefore(endTime);
             if (endIndex < 0) {
                 // All the stats start after this range ends, so nothing matches.
                 if (DEBUG) {
@@ -852,7 +857,7 @@
                 }
             }
 
-            int startIndex = intervalStats.closestIndexOnOrBefore(beginTime);
+            int startIndex = intervalStats.lastIndexOnOrBefore(beginTime);
             if (startIndex < 0) {
                 // All the stats available have timestamps after beginTime, which means they all
                 // match.
@@ -894,7 +899,7 @@
             int bestBucket = -1;
             long smallestDiff = Long.MAX_VALUE;
             for (int i = mSortedStatFiles.length - 1; i >= 0; i--) {
-                final int index = mSortedStatFiles[i].closestIndexOnOrBefore(beginTimeStamp);
+                final int index = mSortedStatFiles[i].lastIndexOnOrBefore(beginTimeStamp);
                 int size = mSortedStatFiles[i].size();
                 if (index >= 0 && index < size) {
                     // We have some results here, check if they are better than our current match.
@@ -914,26 +919,31 @@
      */
     public void prune(final long currentTimeMillis) {
         synchronized (mLock) {
+            // prune all files older than 2 years in the yearly directory
             mCal.setTimeInMillis(currentTimeMillis);
-            mCal.addYears(-3);
+            mCal.addYears(-2);
             pruneFilesOlderThan(mIntervalDirs[UsageStatsManager.INTERVAL_YEARLY],
                     mCal.getTimeInMillis());
 
+            // prune all files older than 6 months in the monthly directory
             mCal.setTimeInMillis(currentTimeMillis);
             mCal.addMonths(-6);
             pruneFilesOlderThan(mIntervalDirs[UsageStatsManager.INTERVAL_MONTHLY],
                     mCal.getTimeInMillis());
 
+            // prune all files older than 4 weeks in the weekly directory
             mCal.setTimeInMillis(currentTimeMillis);
             mCal.addWeeks(-4);
             pruneFilesOlderThan(mIntervalDirs[UsageStatsManager.INTERVAL_WEEKLY],
                     mCal.getTimeInMillis());
 
+            // prune all files older than 10 days in the weekly directory
             mCal.setTimeInMillis(currentTimeMillis);
             mCal.addDays(-10);
             pruneFilesOlderThan(mIntervalDirs[UsageStatsManager.INTERVAL_DAILY],
                     mCal.getTimeInMillis());
 
+            // prune chooser counts for all usage stats older than the defined period
             mCal.setTimeInMillis(currentTimeMillis);
             mCal.addDays(-SELECTION_LOG_RETENTION_LEN);
             for (int i = 0; i < mIntervalDirs.length; ++i) {
@@ -1513,7 +1523,7 @@
             pw.println("Database Summary:");
             pw.increaseIndent();
             for (int i = 0; i < mSortedStatFiles.length; i++) {
-                final TimeSparseArray<AtomicFile> files = mSortedStatFiles[i];
+                final LongSparseArray<AtomicFile> files = mSortedStatFiles[i];
                 final int size = files.size();
                 pw.print(UserUsageStatsService.intervalToString(i));
                 pw.print(" stats files: ");
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index cf236dd..43cebe8 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -300,7 +300,7 @@
         mHandler = new H(BackgroundThread.get().getLooper());
 
         mAppStandby = mInjector.getAppStandbyController(getContext());
-        mResponseStatsTracker = new BroadcastResponseStatsTracker(mAppStandby);
+        mResponseStatsTracker = new BroadcastResponseStatsTracker(mAppStandby, getContext());
 
         mAppTimeLimit = new AppTimeLimitController(getContext(),
                 new AppTimeLimitController.TimeLimitCallbackListener() {
diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
index 9203208..fd56b6e 100644
--- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
@@ -46,10 +46,10 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.AtomicFile;
+import android.util.LongSparseArray;
 import android.util.Slog;
 import android.util.SparseArrayMap;
 import android.util.SparseIntArray;
-import android.util.TimeSparseArray;
 
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.CollectionUtils;
@@ -1024,7 +1024,7 @@
     }
 
     private void dumpFileDetailsForInterval(IndentingPrintWriter ipw, int interval) {
-        final TimeSparseArray<AtomicFile> files = mDatabase.mSortedStatFiles[interval];
+        final LongSparseArray<AtomicFile> files = mDatabase.mSortedStatFiles[interval];
         final int numFiles = files.size();
         for (int i = 0; i < numFiles; i++) {
             final long filename = files.keyAt(i);
diff --git a/services/usb/OWNERS b/services/usb/OWNERS
index 60172a3..d35dbb56 100644
--- a/services/usb/OWNERS
+++ b/services/usb/OWNERS
@@ -1,3 +1,7 @@
+aprasath@google.com
+kumarashishg@google.com
+sarup@google.com
+anothermark@google.com
 badhri@google.com
 elaurent@google.com
 albertccwang@google.com
diff --git a/services/usb/java/com/android/server/usb/UsbAlsaManager.java b/services/usb/java/com/android/server/usb/UsbAlsaManager.java
index fd0d540..5ef0fe3 100644
--- a/services/usb/java/com/android/server/usb/UsbAlsaManager.java
+++ b/services/usb/java/com/android/server/usb/UsbAlsaManager.java
@@ -304,7 +304,9 @@
         AlsaCardsParser.AlsaCardRecord cardRec =
                 mCardsParser.findCardNumFor(deviceAddress);
         if (cardRec == null) {
-            Slog.e(TAG, "usbDeviceAdded(): cannot find sound card for " + deviceAddress);
+            if (parser.hasAudioInterface()) {
+                Slog.e(TAG, "usbDeviceAdded(): cannot find sound card for " + deviceAddress);
+            }
             return;
         }
 
diff --git a/services/usb/java/com/android/server/usb/UsbService.java b/services/usb/java/com/android/server/usb/UsbService.java
index cad1f6f..5d2f27d 100644
--- a/services/usb/java/com/android/server/usb/UsbService.java
+++ b/services/usb/java/com/android/server/usb/UsbService.java
@@ -347,10 +347,11 @@
         return null;
     }
 
+    @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_MTP)
     /* Returns a dup of the control file descriptor for the given function. */
     @Override
     public ParcelFileDescriptor getControlFd(long function) {
-        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.ACCESS_MTP, null);
+        getControlFd_enforcePermission();
         return mDeviceManager.getControlFd(function);
     }
 
@@ -507,10 +508,11 @@
         }
     }
 
+    @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_USB)
     @Override
     public boolean hasDevicePermissionWithIdentity(UsbDevice device, String packageName,
             int pid, int uid) {
-        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
+        hasDevicePermissionWithIdentity_enforcePermission();
 
         final int userId = UserHandle.getUserId(uid);
         return getPermissionsForUser(userId).hasPermission(device, packageName, pid, uid);
@@ -530,9 +532,10 @@
         }
     }
 
+    @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_USB)
     @Override
     public boolean hasAccessoryPermissionWithIdentity(UsbAccessory accessory, int pid, int uid) {
-        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
+        hasAccessoryPermissionWithIdentity_enforcePermission();
 
         final int userId = UserHandle.getUserId(uid);
         return getPermissionsForUser(userId).hasPermission(accessory, pid, uid);
@@ -567,9 +570,10 @@
         }
     }
 
+    @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_USB)
     @Override
     public void grantDevicePermission(UsbDevice device, int uid) {
-        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
+        grantDevicePermission_enforcePermission();
         final int userId = UserHandle.getUserId(uid);
 
         final long token = Binder.clearCallingIdentity();
@@ -580,9 +584,10 @@
         }
     }
 
+    @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_USB)
     @Override
     public void grantAccessoryPermission(UsbAccessory accessory, int uid) {
-        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
+        grantAccessoryPermission_enforcePermission();
         final int userId = UserHandle.getUserId(uid);
 
         final long token = Binder.clearCallingIdentity();
@@ -625,9 +630,10 @@
         }
     }
 
+    @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_USB)
     @Override
     public void setCurrentFunctions(long functions, int operationId) {
-        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
+        setCurrentFunctions_enforcePermission();
         Preconditions.checkArgument(UsbManager.areSettableFunctions(functions));
         Preconditions.checkState(mDeviceManager != null);
         mDeviceManager.setCurrentFunctions(functions, operationId);
@@ -643,32 +649,36 @@
         return (getCurrentFunctions() & UsbManager.usbFunctionsFromString(function)) != 0;
     }
 
+    @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_USB)
     @Override
     public long getCurrentFunctions() {
-        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
+        getCurrentFunctions_enforcePermission();
         Preconditions.checkState(mDeviceManager != null);
         return mDeviceManager.getCurrentFunctions();
     }
 
+    @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_USB)
     @Override
     public void setScreenUnlockedFunctions(long functions) {
-        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
+        setScreenUnlockedFunctions_enforcePermission();
         Preconditions.checkArgument(UsbManager.areSettableFunctions(functions));
         Preconditions.checkState(mDeviceManager != null);
 
         mDeviceManager.setScreenUnlockedFunctions(functions);
     }
 
+    @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_USB)
     @Override
     public long getScreenUnlockedFunctions() {
-        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
+        getScreenUnlockedFunctions_enforcePermission();
         Preconditions.checkState(mDeviceManager != null);
         return mDeviceManager.getScreenUnlockedFunctions();
     }
 
+    @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_USB)
     @Override
     public int getCurrentUsbSpeed() {
-        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
+        getCurrentUsbSpeed_enforcePermission();
         Preconditions.checkNotNull(mDeviceManager, "DeviceManager must not be null");
 
         final long ident = Binder.clearCallingIdentity();
@@ -679,9 +689,10 @@
         }
     }
 
+    @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_USB)
     @Override
     public int getGadgetHalVersion() {
-        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
+        getGadgetHalVersion_enforcePermission();
         Preconditions.checkNotNull(mDeviceManager, "DeviceManager must not be null");
 
         final long ident = Binder.clearCallingIdentity();
@@ -692,9 +703,10 @@
         }
     }
 
+    @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_USB)
     @Override
     public void resetUsbGadget() {
-        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
+        resetUsbGadget_enforcePermission();
         Preconditions.checkNotNull(mDeviceManager, "DeviceManager must not be null");
 
         final long ident = Binder.clearCallingIdentity();
@@ -731,9 +743,10 @@
         }
     }
 
+    @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_USB)
     @Override
     public List<ParcelableUsbPort> getPorts() {
-        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
+        getPorts_enforcePermission();
 
         final long ident = Binder.clearCallingIdentity();
         try {
@@ -822,9 +835,10 @@
         }
     }
 
+    @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_USB)
     @Override
     public int getUsbHalVersion() {
-        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
+        getUsbHalVersion_enforcePermission();
 
         final long ident = Binder.clearCallingIdentity();
         try {
@@ -891,9 +905,10 @@
         }
     }
 
+    @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_USB)
     @Override
     public void setUsbDeviceConnectionHandler(ComponentName usbDeviceConnectionHandler) {
-        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
+        setUsbDeviceConnectionHandler_enforcePermission();
         synchronized (mLock) {
             if (mCurrentUserId == UserHandle.getCallingUserId()) {
                 if (mHostManager != null) {
diff --git a/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetAidl.java b/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetAidl.java
index d47ccc7..b7f7c43 100644
--- a/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetAidl.java
+++ b/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetAidl.java
@@ -67,10 +67,6 @@
         return GADGET_HAL_V2_0;
     }
 
-    @Override
-    public void systemReady() {
-    }
-
     public void serviceDied() {
         logAndPrint(Log.ERROR, mPw, "Usb Gadget AIDL hal service died");
         synchronized (mGadgetProxyLock) {
diff --git a/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetHal.java b/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetHal.java
index 7b52f46..eac796d 100644
--- a/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetHal.java
+++ b/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetHal.java
@@ -29,69 +29,6 @@
  */
 public interface UsbGadgetHal {
     /**
-     * Power role: This USB port can act as a source (provide power).
-     * @hide
-     */
-    public static final int HAL_POWER_ROLE_SOURCE = 1;
-
-    /**
-     * Power role: This USB port can act as a sink (receive power).
-     * @hide
-     */
-    public static final int HAL_POWER_ROLE_SINK = 2;
-
-    @IntDef(prefix = { "HAL_POWER_ROLE_" }, value = {
-            HAL_POWER_ROLE_SOURCE,
-            HAL_POWER_ROLE_SINK
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    @interface HalUsbPowerRole{}
-
-    /**
-     * Data role: This USB port can act as a host (access data services).
-     * @hide
-     */
-    public static final int HAL_DATA_ROLE_HOST = 1;
-
-    /**
-     * Data role: This USB port can act as a device (offer data services).
-     * @hide
-     */
-    public static final int HAL_DATA_ROLE_DEVICE = 2;
-
-    @IntDef(prefix = { "HAL_DATA_ROLE_" }, value = {
-            HAL_DATA_ROLE_HOST,
-            HAL_DATA_ROLE_DEVICE
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    @interface HalUsbDataRole{}
-
-    /**
-     * This USB port can act as a downstream facing port (host).
-     *
-     * @hide
-     */
-    public static final int HAL_MODE_DFP = 1;
-
-    /**
-     * This USB port can act as an upstream facing port (device).
-     *
-     * @hide
-     */
-    public static final int HAL_MODE_UFP = 2;
-    @IntDef(prefix = { "HAL_MODE_" }, value = {
-            HAL_MODE_DFP,
-            HAL_MODE_UFP,
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    @interface HalUsbPortMode{}
-
-    /**
-     * UsbPortManager would call this when the system is done booting.
-     */
-    public void systemReady();
-
-    /**
      * This function is used to query the USB functions included in the
      * current USB configuration.
      *
diff --git a/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetHidl.java b/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetHidl.java
index 68067d2..80a70dd 100644
--- a/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetHidl.java
+++ b/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetHidl.java
@@ -123,10 +123,6 @@
         }
     }
 
-    @Override
-    public void systemReady() {
-    }
-
     static boolean isServicePresent(IndentingPrintWriter pw) {
         try {
             IUsbGadget.getService(true);
diff --git a/services/usb/java/com/android/server/usb/hal/port/UsbPortHal.java b/services/usb/java/com/android/server/usb/hal/port/UsbPortHal.java
index f98c598..45de058 100644
--- a/services/usb/java/com/android/server/usb/hal/port/UsbPortHal.java
+++ b/services/usb/java/com/android/server/usb/hal/port/UsbPortHal.java
@@ -70,17 +70,17 @@
      *
      * @hide
      */
-    public static final int HAL_MODE_DFP = 1;
+    public static final int HAL_MODE_UFP = 1;
 
     /**
      * This USB port can act as an upstream facing port (device).
      *
      * @hide
      */
-    public static final int HAL_MODE_UFP = 2;
+    public static final int HAL_MODE_DFP = 2;
     @IntDef(prefix = { "HAL_MODE_" }, value = {
-            HAL_MODE_DFP,
             HAL_MODE_UFP,
+            HAL_MODE_DFP,
     })
     @Retention(RetentionPolicy.SOURCE)
     @interface HalUsbPortMode{}
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
index 13945a1..997015f 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
@@ -407,7 +407,9 @@
                 var eventLogger = new EventLogger(SESSION_MAX_EVENT_SIZE,
                         "SoundTriggerSessionLogs for package: "
                         + Objects.requireNonNull(originatorIdentity.packageName)
-                        + "#" + sessionId);
+                        + "#" + sessionId
+                        + " - " + originatorIdentity.uid
+                        + "|" + originatorIdentity.pid);
                 return new SoundTriggerSessionStub(client,
                         newSoundTriggerHelper(moduleProperties, eventLogger), eventLogger);
             }
@@ -428,7 +430,9 @@
                 var eventLogger = new EventLogger(SESSION_MAX_EVENT_SIZE,
                         "SoundTriggerSessionLogs for package: "
                         + Objects.requireNonNull(originatorIdentity.packageName) + "#"
-                        + sessionId);
+                        + sessionId
+                        + " - " + originatorIdentity.uid
+                        + "|" + originatorIdentity.pid);
                 return new SoundTriggerSessionStub(client,
                         newSoundTriggerHelper(moduleProperties, eventLogger), eventLogger);
             }
@@ -1801,7 +1805,9 @@
                         ServiceEvent.Type.ATTACH, identity.packageName + "#" + sessionId));
             var eventLogger = new EventLogger(SESSION_MAX_EVENT_SIZE,
                     "LocalSoundTriggerEventLogger for package: " +
-                    identity.packageName + "#" + sessionId);
+                    identity.packageName + "#" + sessionId
+                        + " - " + identity.uid
+                        + "|" + identity.pid);
 
             return new SessionImpl(newSoundTriggerHelper(underlyingModule, eventLogger, isTrusted),
                     client, eventLogger, identity);
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/DefaultHalFactory.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/DefaultHalFactory.java
index 2f2cb59..55cbf29 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/DefaultHalFactory.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/DefaultHalFactory.java
@@ -22,7 +22,7 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemProperties;
-import android.util.Log;
+import android.util.Slog;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -62,7 +62,7 @@
                         android.hardware.soundtrigger3.ISoundTriggerHw.class.getCanonicalName()
                                 + "/default";
                 if (ServiceManager.isDeclared(aidlServiceName)) {
-                    Log.i(TAG, "Connecting to default soundtrigger3.ISoundTriggerHw");
+                    Slog.i(TAG, "Connecting to default soundtrigger3.ISoundTriggerHw");
                     return new SoundTriggerHw3Compat(ServiceManager.waitForService(aidlServiceName),
                             () -> {
                                 // This property needs to be defined in an init.rc script and
@@ -72,7 +72,7 @@
                 }
 
                 // Fallback to soundtrigger-V2.x (HIDL).
-                Log.i(TAG, "Connecting to default soundtrigger-V2.x.ISoundTriggerHw");
+                Slog.i(TAG, "Connecting to default soundtrigger-V2.x.ISoundTriggerHw");
                 ISoundTriggerHw driver = ISoundTriggerHw.getService(true);
                 return SoundTriggerHw2Compat.create(driver, () -> {
                     // This property needs to be defined in an init.rc script and
@@ -81,7 +81,7 @@
                 }, mCaptureStateNotifier);
             } else if (mockHal == USE_MOCK_HAL_V2) {
                 // Use V2 mock.
-                Log.i(TAG, "Connecting to mock soundtrigger-V2.x.ISoundTriggerHw");
+                Slog.i(TAG, "Connecting to mock soundtrigger-V2.x.ISoundTriggerHw");
                 HwBinder.setTrebleTestingOverride(true);
                 try {
                     ISoundTriggerHw driver = ISoundTriggerHw.getService("mock", true);
@@ -89,7 +89,7 @@
                         try {
                             driver.debug(null, new ArrayList<>(Arrays.asList("reboot")));
                         } catch (Exception e) {
-                            Log.e(TAG, "Failed to reboot mock HAL", e);
+                            Slog.e(TAG, "Failed to reboot mock HAL", e);
                         }
                     }, mCaptureStateNotifier);
                 } finally {
@@ -100,14 +100,14 @@
                 final String aidlServiceName =
                         android.hardware.soundtrigger3.ISoundTriggerHw.class.getCanonicalName()
                                 + "/mock";
-                Log.i(TAG, "Connecting to mock soundtrigger3.ISoundTriggerHw");
+                Slog.i(TAG, "Connecting to mock soundtrigger3.ISoundTriggerHw");
                 return new SoundTriggerHw3Compat(ServiceManager.waitForService(aidlServiceName),
                         () -> {
                             try {
                                 ServiceManager.waitForService(aidlServiceName).shellCommand(null,
                                         null, null, new String[]{"reboot"}, null, null);
                             } catch (Exception e) {
-                                Log.e(TAG, "Failed to reboot mock HAL", e);
+                                Slog.e(TAG, "Failed to reboot mock HAL", e);
                             }
                         });
             } else {
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/ExternalCaptureStateTracker.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/ExternalCaptureStateTracker.java
index d195fbe..e3d64d4 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/ExternalCaptureStateTracker.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/ExternalCaptureStateTracker.java
@@ -17,7 +17,7 @@
 package com.android.server.soundtrigger_middleware;
 
 import android.annotation.NonNull;
-import android.util.Log;
+import android.util.Slog;
 
 import java.util.LinkedList;
 import java.util.List;
@@ -94,7 +94,7 @@
                 }
             }
         } catch (Exception e) {
-            Log.e(TAG, "Exception caught while setting capture state", e);
+            Slog.e(TAG, "Exception caught while setting capture state", e);
         }
     }
 
@@ -102,7 +102,7 @@
      * Called by native code when the remote service died.
      */
     private void binderDied() {
-        Log.w(TAG, "Audio policy service died");
+        Slog.w(TAG, "Audio policy service died");
         mNeedToConnect.release();
     }
 }
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHalEnforcer.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHalEnforcer.java
index c3e0a3c..0f63347 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHalEnforcer.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHalEnforcer.java
@@ -27,7 +27,7 @@
 import android.media.soundtrigger_middleware.RecognitionEventSys;
 import android.os.DeadObjectException;
 import android.os.IBinder;
-import android.util.Log;
+import android.util.Slog;
 
 import java.util.HashMap;
 import java.util.Map;
@@ -227,10 +227,10 @@
         }
         if (e.getCause() instanceof DeadObjectException) {
             // Server is dead, no need to reboot.
-            Log.e(TAG, "HAL died");
+            Slog.e(TAG, "HAL died");
             throw new RecoverableException(Status.DEAD_OBJECT);
         }
-        Log.e(TAG, "Exception caught from HAL, rebooting HAL");
+        Slog.e(TAG, "Exception caught from HAL, rebooting HAL");
         reboot();
         throw e;
     }
@@ -257,14 +257,14 @@
             synchronized (mModelStates) {
                 ModelState state = mModelStates.get(model);
                 if (state == null) {
-                    Log.wtfStack(TAG, "Unexpected recognition event for model: " + model);
+                    Slog.wtfStack(TAG, "Unexpected recognition event for model: " + model);
                     reboot();
                     return;
                 }
                 if (event.recognitionEvent.recognitionStillActive
                         && event.recognitionEvent.status != RecognitionStatus.SUCCESS
                         && event.recognitionEvent.status != RecognitionStatus.FORCED) {
-                    Log.wtfStack(TAG,
+                    Slog.wtfStack(TAG,
                             "recognitionStillActive is only allowed when the recognition status "
                                     + "is SUCCESS");
                     reboot();
@@ -283,14 +283,14 @@
             synchronized (mModelStates) {
                 ModelState state = mModelStates.get(model);
                 if (state == null) {
-                    Log.wtfStack(TAG, "Unexpected recognition event for model: " + model);
+                    Slog.wtfStack(TAG, "Unexpected recognition event for model: " + model);
                     reboot();
                     return;
                 }
                 if (event.phraseRecognitionEvent.common.recognitionStillActive
                         && event.phraseRecognitionEvent.common.status != RecognitionStatus.SUCCESS
                         && event.phraseRecognitionEvent.common.status != RecognitionStatus.FORCED) {
-                    Log.wtfStack(TAG,
+                    Slog.wtfStack(TAG,
                             "recognitionStillActive is only allowed when the recognition status "
                                     + "is SUCCESS");
                     reboot();
@@ -309,13 +309,13 @@
             synchronized (mModelStates) {
                 ModelState state = mModelStates.get(modelHandle);
                 if (state == null) {
-                    Log.wtfStack(TAG, "Unexpected unload event for model: " + modelHandle);
+                    Slog.wtfStack(TAG, "Unexpected unload event for model: " + modelHandle);
                     reboot();
                     return;
                 }
 
                 if (state == ModelState.ACTIVE) {
-                    Log.wtfStack(TAG, "Trying to unload an active model: " + modelHandle);
+                    Slog.wtfStack(TAG, "Trying to unload an active model: " + modelHandle);
                     reboot();
                     return;
                 }
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHalWatchdog.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHalWatchdog.java
index 0390f03..5e525e0 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHalWatchdog.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHalWatchdog.java
@@ -23,7 +23,7 @@
 import android.media.soundtrigger.RecognitionConfig;
 import android.media.soundtrigger.SoundModel;
 import android.os.IBinder;
-import android.util.Log;
+import android.util.Slog;
 
 import java.util.Objects;
 
@@ -172,7 +172,7 @@
 
         Watchdog() {
             mTask = mTimer.createTask(() -> {
-                Log.e(TAG, "HAL deadline expired. Rebooting.", mException);
+                Slog.e(TAG, "HAL deadline expired. Rebooting.", mException);
                 reboot();
             }, TIMEOUT_MS);
         }
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Compat.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Compat.java
index df2e9b4..730e92c 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Compat.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Compat.java
@@ -32,7 +32,7 @@
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.system.OsConstants;
-import android.util.Log;
+import android.util.Slog;
 
 import java.io.IOException;
 import java.util.HashMap;
@@ -240,7 +240,7 @@
                 try {
                     hidlModel.data.close();
                 } catch (IOException e) {
-                    Log.e(TAG, "Failed to close file", e);
+                    Slog.e(TAG, "Failed to close file", e);
                 }
             }
         }
@@ -276,7 +276,7 @@
                 try {
                     hidlModel.common.data.close();
                 } catch (IOException e) {
-                    Log.e(TAG, "Failed to close file", e);
+                    Slog.e(TAG, "Failed to close file", e);
                 }
             }
         }
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImpl.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImpl.java
index 3b800de..5a064da 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImpl.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImpl.java
@@ -20,7 +20,7 @@
 import android.media.soundtrigger_middleware.ISoundTriggerCallback;
 import android.media.soundtrigger_middleware.ISoundTriggerModule;
 import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor;
-import android.util.Log;
+import android.util.Slog;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -85,7 +85,7 @@
             try {
                 modules.add(new SoundTriggerModule(halFactory, audioSessionProvider));
             } catch (Exception e) {
-                Log.e(TAG, "Failed to add a SoundTriggerModule instance", e);
+                Slog.e(TAG, "Failed to add a SoundTriggerModule instance", e);
             }
         }
 
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java
index 7ec2d9f..0b9ed8c 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java
@@ -36,7 +36,7 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.ServiceSpecificException;
-import android.util.Log;
+import android.util.Slog;
 import android.util.SparseArray;
 
 import com.android.internal.util.Preconditions;
@@ -150,7 +150,7 @@
                     e.getMessage());
         }
 
-        Log.wtf(TAG, "Unexpected exception", e);
+        Slog.wtf(TAG, "Unexpected exception", e);
         throw new ServiceSpecificException(Status.INTERNAL_ERROR, e.getMessage());
     }
 
@@ -701,7 +701,7 @@
                 try {
                     mCallback.onRecognition(modelHandle, event, captureSession);
                 } catch (Exception e) {
-                    Log.w(TAG, "Client callback exception.", e);
+                    Slog.w(TAG, "Client callback exception.", e);
                }
             }
 
@@ -719,7 +719,7 @@
                 try {
                     mCallback.onPhraseRecognition(modelHandle, event, captureSession);
                 } catch (Exception e) {
-                    Log.w(TAG, "Client callback exception.", e);
+                    Slog.w(TAG, "Client callback exception.", e);
                }
             }
 
@@ -734,7 +734,7 @@
                 try {
                     mCallback.onModelUnloaded(modelHandle);
                 } catch (Exception e) {
-                    Log.w(TAG, "Client callback exception.", e);
+                    Slog.w(TAG, "Client callback exception.", e);
                 }
             }
 
@@ -746,7 +746,7 @@
                 } catch (RemoteException e) {
                     // Dead client will be handled by binderDied() - no need to handle here.
                     // In any case, client callbacks are considered best effort.
-                    Log.e(TAG, "Client callback exception.", e);
+                    Slog.e(TAG, "Client callback exception.", e);
                 }
             }
 
@@ -761,7 +761,7 @@
                 } catch (RemoteException e) {
                     // Dead client will be handled by binderDied() - no need to handle here.
                     // In any case, client callbacks are considered best effort.
-                    Log.e(TAG, "Client callback exception.", e);
+                    Slog.e(TAG, "Client callback exception.", e);
                 }
             }
 
@@ -795,11 +795,11 @@
                    // Check if state updated unexpectedly to log race conditions.
                     for (Map.Entry<Integer, ModelState> entry : mLoadedModels.entrySet()) {
                         if (cachedMap.get(entry.getKey()) != entry.getValue().activityState) {
-                            Log.e(TAG, "Unexpected state update in binderDied. Race occurred!");
+                            Slog.e(TAG, "Unexpected state update in binderDied. Race occurred!");
                         }
                     }
                     if (mLoadedModels.size() != cachedMap.size()) {
-                        Log.e(TAG, "Unexpected state update in binderDied. Race occurred!");
+                        Slog.e(TAG, "Unexpected state update in binderDied. Race occurred!");
                     }
                     try {
                         // Detach
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
index e793f31..45a7faf 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
@@ -31,7 +31,7 @@
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.RemoteException;
-import android.util.Log;
+import android.util.Slog;
 
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -136,7 +136,7 @@
 
     @Override
     public void binderDied() {
-        Log.w(TAG, "Underlying HAL driver died.");
+        Slog.w(TAG, "Underlying HAL driver died.");
         List<ISoundTriggerCallback> callbacks;
         synchronized (this) {
             callbacks = new ArrayList<>(mActiveSessions.size());
@@ -270,7 +270,7 @@
                     try {
                         mAudioSessionProvider.releaseSession(audioSession.mSessionHandle);
                     } catch (Exception ee) {
-                        Log.e(TAG, "Failed to release session.", ee);
+                        Slog.e(TAG, "Failed to release session.", ee);
                     }
                     throw e;
                 }
@@ -286,7 +286,7 @@
                     checkValid();
                     Model loadedModel = new Model();
                     int result = loadedModel.load(model, audioSession);
-                    Log.d(TAG, String.format("loadPhraseModel()->%d", result));
+                    Slog.d(TAG, String.format("loadPhraseModel()->%d", result));
                     return result;
                 } catch (Exception e) {
                     // We must do this outside the lock, to avoid possible deadlocks with the remote
@@ -294,7 +294,7 @@
                     try {
                         mAudioSessionProvider.releaseSession(audioSession.mSessionHandle);
                     } catch (Exception ee) {
-                        Log.e(TAG, "Failed to release session.", ee);
+                        Slog.e(TAG, "Failed to release session.", ee);
                     }
                     throw e;
                 }
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java
index 7598952..ffc7b8e 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java
@@ -233,7 +233,8 @@
 
         if (ENABLE_PROXIMITY_RESULT) {
             mAttentionManagerInternal = LocalServices.getService(AttentionManagerInternal.class);
-            if (mAttentionManagerInternal != null) {
+            if (mAttentionManagerInternal != null
+                    && mAttentionManagerInternal.isProximitySupported()) {
                 mAttentionManagerInternal.onStartProximityUpdates(mProximityCallbackInternal);
             }
         }
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index 423a81a..605af03 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -2343,8 +2343,7 @@
                     }
                 }
                 if (hitInt && doit) {
-                    // The user is force stopping our current interactor.
-                    // Clear the current settings and restore default state.
+                    // The user is force stopping our current interactor, restart the service.
                     synchronized (VoiceInteractionManagerServiceStub.this) {
                         Slog.i(TAG, "Force stopping current voice interactor: "
                                 + getCurInteractor(userHandle));
@@ -2353,28 +2352,7 @@
                             mImpl.shutdownLocked();
                             setImplLocked(null);
                         }
-
-                        setCurInteractor(null, userHandle);
-                        // TODO: should not reset null here. But even remove this line, the
-                        // initForUser() still reset it because the interactor will be null. Keep
-                        // it now but we should still need to fix it.
-                        setCurRecognizer(null, userHandle);
-                        resetCurAssistant(userHandle);
-                        initForUser(userHandle);
                         switchImplementationIfNeededLocked(true);
-
-                        // When resetting the interactor, the recognizer and the assistant settings
-                        // value, we also need to reset the assistant role to keep the values
-                        // consistent. Clear the assistant role will reset to the default value.
-                        Context context = getContext();
-                        context.getSystemService(RoleManager.class).clearRoleHoldersAsUser(
-                                RoleManager.ROLE_ASSISTANT, 0, UserHandle.of(userHandle),
-                                context.getMainExecutor(), successful -> {
-                                    if (!successful) {
-                                        Slog.e(TAG,
-                                                "Failed to clear default assistant for force stop");
-                                    }
-                                });
                     }
                 } else if (hitRec && doit) {
                     // We are just force-stopping the current recognizer, which is not
diff --git a/services/wallpapereffectsgeneration/OWNERS b/services/wallpapereffectsgeneration/OWNERS
index d2d3e2c0..a351032 100644
--- a/services/wallpapereffectsgeneration/OWNERS
+++ b/services/wallpapereffectsgeneration/OWNERS
@@ -1,4 +1,3 @@
 susharon@google.com
 shanh@google.com
-huiwu@google.com
 srazdan@google.com
diff --git a/startop/view_compiler/Android.bp b/startop/view_compiler/Android.bp
index 9023921..e172090 100644
--- a/startop/view_compiler/Android.bp
+++ b/startop/view_compiler/Android.bp
@@ -40,7 +40,7 @@
         "libziparchive",
         "libz",
     ],
-    cppflags: ["-std=c++17"],
+    cpp_std: "gnu++2b",
     target: {
         android: {
             shared_libs: [
@@ -80,7 +80,7 @@
         "libgflags",
         "libviewcompiler",
     ],
-    host_supported: true
+    host_supported: true,
 }
 
 cc_test_host {
diff --git a/startop/view_compiler/apk_layout_compiler.cc b/startop/view_compiler/apk_layout_compiler.cc
index 1d3b6481..5f5652c 100644
--- a/startop/view_compiler/apk_layout_compiler.cc
+++ b/startop/view_compiler/apk_layout_compiler.cc
@@ -80,10 +80,10 @@
 }
 
 namespace {
-void CompileApkAssetsLayouts(const std::unique_ptr<android::ApkAssets>& assets,
-                             CompilationTarget target, std::ostream& target_out) {
+void CompileApkAssetsLayouts(const android::ApkAssetsPtr& assets, CompilationTarget target,
+                             std::ostream& target_out) {
   android::AssetManager2 resources;
-  resources.SetApkAssets({assets.get()});
+  resources.SetApkAssets({assets});
 
   std::string package_name;
 
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index c152a41..1da4ea9 100644
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -24,6 +24,7 @@
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.pm.ServiceInfo;
 import android.net.Uri;
+import android.os.BadParcelableException;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
@@ -2951,21 +2952,27 @@
 
         for(String key : bundle.keySet()) {
             if (key != null) {
-                final Object value = bundle.get(key);
-                final Object newValue = newBundle.get(key);
                 if (!newBundle.containsKey(key)) {
                     return false;
                 }
-                if (value instanceof Bundle && newValue instanceof Bundle) {
-                    if (!areBundlesEqual((Bundle) value, (Bundle) newValue)) {
+                // In case new call extra contains non-framework class objects, return false to
+                // force update the call extra
+                try {
+                    final Object value = bundle.get(key);
+                    final Object newValue = newBundle.get(key);
+                    if (value instanceof Bundle && newValue instanceof Bundle) {
+                        if (!areBundlesEqual((Bundle) value, (Bundle) newValue)) {
+                            return false;
+                        }
+                    }
+                    if (value instanceof byte[] && newValue instanceof byte[]) {
+                        if (!Arrays.equals((byte[]) value, (byte[]) newValue)) {
+                            return false;
+                        }
+                    } else if (!Objects.equals(value, newValue)) {
                         return false;
                     }
-                }
-                if (value instanceof byte[] && newValue instanceof byte[]) {
-                    if (!Arrays.equals((byte[]) value, (byte[]) newValue)) {
-                        return false;
-                    }
-                } else if (!Objects.equals(value, newValue)) {
+                } catch (BadParcelableException e) {
                     return false;
                 }
             }
diff --git a/telecomm/java/android/telecom/CallEndpoint.java b/telecomm/java/android/telecom/CallEndpoint.java
index 0b2211d..ed4bf40 100644
--- a/telecomm/java/android/telecom/CallEndpoint.java
+++ b/telecomm/java/android/telecom/CallEndpoint.java
@@ -61,6 +61,13 @@
     /** Indicates that the type of endpoint through which call media flows is an external. */
     public static final int TYPE_STREAMING     = 5;
 
+    /**
+     * Error message attached to IllegalArgumentException when the endpoint name or id is null.
+     * @hide
+     */
+    private static final String CALLENDPOINT_NAME_ID_NULL_ERROR =
+            "CallEndpoint name cannot be null.";
+
     private final CharSequence mName;
     private final int mType;
     private final ParcelUuid mIdentifier;
@@ -81,6 +88,11 @@
      */
     public CallEndpoint(@NonNull CharSequence name, @EndpointType int type,
             @NonNull ParcelUuid id) {
+        // Ensure that endpoint name and id are non-null.
+        if (name == null || id == null) {
+            throw new IllegalArgumentException(CALLENDPOINT_NAME_ID_NULL_ERROR);
+        }
+
         this.mName = name;
         this.mType = type;
         this.mIdentifier = id;
@@ -93,6 +105,11 @@
 
     /** @hide */
     public CallEndpoint(CallEndpoint endpoint) {
+        // Ensure that endpoint name and id are non-null.
+        if (endpoint.getEndpointName() == null || endpoint.getIdentifier() == null) {
+            throw new IllegalArgumentException(CALLENDPOINT_NAME_ID_NULL_ERROR);
+        }
+
         mName = endpoint.getEndpointName();
         mType = endpoint.getEndpointType();
         mIdentifier = endpoint.getIdentifier();
diff --git a/telephony/java/android/telephony/AccessNetworkConstants.java b/telephony/java/android/telephony/AccessNetworkConstants.java
index 27ba676..35721f1 100644
--- a/telephony/java/android/telephony/AccessNetworkConstants.java
+++ b/telephony/java/android/telephony/AccessNetworkConstants.java
@@ -19,7 +19,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
-import android.hardware.radio.V1_5.AccessNetwork;
+import android.hardware.radio.AccessNetwork;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -134,20 +134,22 @@
      * http://www.etsi.org/deliver/etsi_ts/145000_145099/145005/14.00.00_60/ts_145005v140000p.pdf
      */
     public static final class GeranBand {
-        public static final int BAND_T380 = android.hardware.radio.V1_1.GeranBands.BAND_T380;
-        public static final int BAND_T410 = android.hardware.radio.V1_1.GeranBands.BAND_T410;
-        public static final int BAND_450 = android.hardware.radio.V1_1.GeranBands.BAND_450;
-        public static final int BAND_480 = android.hardware.radio.V1_1.GeranBands.BAND_480;
-        public static final int BAND_710 = android.hardware.radio.V1_1.GeranBands.BAND_710;
-        public static final int BAND_750 = android.hardware.radio.V1_1.GeranBands.BAND_750;
-        public static final int BAND_T810 = android.hardware.radio.V1_1.GeranBands.BAND_T810;
-        public static final int BAND_850 = android.hardware.radio.V1_1.GeranBands.BAND_850;
-        public static final int BAND_P900 = android.hardware.radio.V1_1.GeranBands.BAND_P900;
-        public static final int BAND_E900 = android.hardware.radio.V1_1.GeranBands.BAND_E900;
-        public static final int BAND_R900 = android.hardware.radio.V1_1.GeranBands.BAND_R900;
-        public static final int BAND_DCS1800 = android.hardware.radio.V1_1.GeranBands.BAND_DCS1800;
-        public static final int BAND_PCS1900 = android.hardware.radio.V1_1.GeranBands.BAND_PCS1900;
-        public static final int BAND_ER900 = android.hardware.radio.V1_1.GeranBands.BAND_ER900;
+        public static final int BAND_T380 = android.hardware.radio.network.GeranBands.BAND_T380;
+        public static final int BAND_T410 = android.hardware.radio.network.GeranBands.BAND_T410;
+        public static final int BAND_450 = android.hardware.radio.network.GeranBands.BAND_450;
+        public static final int BAND_480 = android.hardware.radio.network.GeranBands.BAND_480;
+        public static final int BAND_710 = android.hardware.radio.network.GeranBands.BAND_710;
+        public static final int BAND_750 = android.hardware.radio.network.GeranBands.BAND_750;
+        public static final int BAND_T810 = android.hardware.radio.network.GeranBands.BAND_T810;
+        public static final int BAND_850 = android.hardware.radio.network.GeranBands.BAND_850;
+        public static final int BAND_P900 = android.hardware.radio.network.GeranBands.BAND_P900;
+        public static final int BAND_E900 = android.hardware.radio.network.GeranBands.BAND_E900;
+        public static final int BAND_R900 = android.hardware.radio.network.GeranBands.BAND_R900;
+        public static final int BAND_DCS1800 =
+                android.hardware.radio.network.GeranBands.BAND_DCS1800;
+        public static final int BAND_PCS1900 =
+                android.hardware.radio.network.GeranBands.BAND_PCS1900;
+        public static final int BAND_ER900 = android.hardware.radio.network.GeranBands.BAND_ER900;
 
         /**
          * GeranBand
@@ -226,28 +228,28 @@
      * http://www.etsi.org/deliver/etsi_ts/125100_125199/125104/13.03.00_60/ts_125104v130p.pdf
      */
     public static final class UtranBand {
-        public static final int BAND_1 = android.hardware.radio.V1_5.UtranBands.BAND_1;
-        public static final int BAND_2 = android.hardware.radio.V1_5.UtranBands.BAND_2;
-        public static final int BAND_3 = android.hardware.radio.V1_5.UtranBands.BAND_3;
-        public static final int BAND_4 = android.hardware.radio.V1_5.UtranBands.BAND_4;
-        public static final int BAND_5 = android.hardware.radio.V1_5.UtranBands.BAND_5;
-        public static final int BAND_6 = android.hardware.radio.V1_5.UtranBands.BAND_6;
-        public static final int BAND_7 = android.hardware.radio.V1_5.UtranBands.BAND_7;
-        public static final int BAND_8 = android.hardware.radio.V1_5.UtranBands.BAND_8;
-        public static final int BAND_9 = android.hardware.radio.V1_5.UtranBands.BAND_9;
-        public static final int BAND_10 = android.hardware.radio.V1_5.UtranBands.BAND_10;
-        public static final int BAND_11 = android.hardware.radio.V1_5.UtranBands.BAND_11;
-        public static final int BAND_12 = android.hardware.radio.V1_5.UtranBands.BAND_12;
-        public static final int BAND_13 = android.hardware.radio.V1_5.UtranBands.BAND_13;
-        public static final int BAND_14 = android.hardware.radio.V1_5.UtranBands.BAND_14;
+        public static final int BAND_1 = android.hardware.radio.network.UtranBands.BAND_1;
+        public static final int BAND_2 = android.hardware.radio.network.UtranBands.BAND_2;
+        public static final int BAND_3 = android.hardware.radio.network.UtranBands.BAND_3;
+        public static final int BAND_4 = android.hardware.radio.network.UtranBands.BAND_4;
+        public static final int BAND_5 = android.hardware.radio.network.UtranBands.BAND_5;
+        public static final int BAND_6 = android.hardware.radio.network.UtranBands.BAND_6;
+        public static final int BAND_7 = android.hardware.radio.network.UtranBands.BAND_7;
+        public static final int BAND_8 = android.hardware.radio.network.UtranBands.BAND_8;
+        public static final int BAND_9 = android.hardware.radio.network.UtranBands.BAND_9;
+        public static final int BAND_10 = android.hardware.radio.network.UtranBands.BAND_10;
+        public static final int BAND_11 = android.hardware.radio.network.UtranBands.BAND_11;
+        public static final int BAND_12 = android.hardware.radio.network.UtranBands.BAND_12;
+        public static final int BAND_13 = android.hardware.radio.network.UtranBands.BAND_13;
+        public static final int BAND_14 = android.hardware.radio.network.UtranBands.BAND_14;
         // band 15, 16, 17, 18 are reserved
-        public static final int BAND_19 = android.hardware.radio.V1_5.UtranBands.BAND_19;
-        public static final int BAND_20 = android.hardware.radio.V1_5.UtranBands.BAND_20;
-        public static final int BAND_21 = android.hardware.radio.V1_5.UtranBands.BAND_21;
-        public static final int BAND_22 = android.hardware.radio.V1_5.UtranBands.BAND_22;
+        public static final int BAND_19 = android.hardware.radio.network.UtranBands.BAND_19;
+        public static final int BAND_20 = android.hardware.radio.network.UtranBands.BAND_20;
+        public static final int BAND_21 = android.hardware.radio.network.UtranBands.BAND_21;
+        public static final int BAND_22 = android.hardware.radio.network.UtranBands.BAND_22;
         // band 23, 24 are reserved
-        public static final int BAND_25 = android.hardware.radio.V1_5.UtranBands.BAND_25;
-        public static final int BAND_26 = android.hardware.radio.V1_5.UtranBands.BAND_26;
+        public static final int BAND_25 = android.hardware.radio.network.UtranBands.BAND_25;
+        public static final int BAND_26 = android.hardware.radio.network.UtranBands.BAND_26;
 
         // Frequency bands for TD-SCDMA. Defined in 3GPP TS 25.102, Table 5.2.
 
@@ -256,38 +258,38 @@
          * 1900 - 1920 MHz: Uplink and downlink transmission
          * 2010 - 2025 MHz: Uplink and downlink transmission
          */
-        public static final int BAND_A = android.hardware.radio.V1_5.UtranBands.BAND_A;
+        public static final int BAND_A = android.hardware.radio.network.UtranBands.BAND_A;
 
         /**
          * Band B
          * 1850 - 1910 MHz: Uplink and downlink transmission
          * 1930 - 1990 MHz: Uplink and downlink transmission
          */
-        public static final int BAND_B = android.hardware.radio.V1_5.UtranBands.BAND_B;
+        public static final int BAND_B = android.hardware.radio.network.UtranBands.BAND_B;
 
         /**
          * Band C
          * 1910 - 1930 MHz: Uplink and downlink transmission
          */
-        public static final int BAND_C = android.hardware.radio.V1_5.UtranBands.BAND_C;
+        public static final int BAND_C = android.hardware.radio.network.UtranBands.BAND_C;
 
         /**
          * Band D
          * 2570 - 2620 MHz: Uplink and downlink transmission
          */
-        public static final int BAND_D = android.hardware.radio.V1_5.UtranBands.BAND_D;
+        public static final int BAND_D = android.hardware.radio.network.UtranBands.BAND_D;
 
         /**
          * Band E
          * 2300—2400 MHz: Uplink and downlink transmission
          */
-        public static final int BAND_E = android.hardware.radio.V1_5.UtranBands.BAND_E;
+        public static final int BAND_E = android.hardware.radio.network.UtranBands.BAND_E;
 
         /**
          * Band F
          * 1880 - 1920 MHz: Uplink and downlink transmission
          */
-        public static final int BAND_F = android.hardware.radio.V1_5.UtranBands.BAND_F;
+        public static final int BAND_F = android.hardware.radio.network.UtranBands.BAND_F;
 
         /**
          * UtranBand
@@ -389,66 +391,66 @@
      * https://www.etsi.org/deliver/etsi_ts/136100_136199/136101/15.09.00_60/ts_136101v150900p.pdf
      */
     public static final class EutranBand {
-        public static final int BAND_1 = android.hardware.radio.V1_5.EutranBands.BAND_1;
-        public static final int BAND_2 = android.hardware.radio.V1_5.EutranBands.BAND_2;
-        public static final int BAND_3 = android.hardware.radio.V1_5.EutranBands.BAND_3;
-        public static final int BAND_4 = android.hardware.radio.V1_5.EutranBands.BAND_4;
-        public static final int BAND_5 = android.hardware.radio.V1_5.EutranBands.BAND_5;
-        public static final int BAND_6 = android.hardware.radio.V1_5.EutranBands.BAND_6;
-        public static final int BAND_7 = android.hardware.radio.V1_5.EutranBands.BAND_7;
-        public static final int BAND_8 = android.hardware.radio.V1_5.EutranBands.BAND_8;
-        public static final int BAND_9 = android.hardware.radio.V1_5.EutranBands.BAND_9;
-        public static final int BAND_10 = android.hardware.radio.V1_5.EutranBands.BAND_10;
-        public static final int BAND_11 = android.hardware.radio.V1_5.EutranBands.BAND_11;
-        public static final int BAND_12 = android.hardware.radio.V1_5.EutranBands.BAND_12;
-        public static final int BAND_13 = android.hardware.radio.V1_5.EutranBands.BAND_13;
-        public static final int BAND_14 = android.hardware.radio.V1_5.EutranBands.BAND_14;
-        public static final int BAND_17 = android.hardware.radio.V1_5.EutranBands.BAND_17;
-        public static final int BAND_18 = android.hardware.radio.V1_5.EutranBands.BAND_18;
-        public static final int BAND_19 = android.hardware.radio.V1_5.EutranBands.BAND_19;
-        public static final int BAND_20 = android.hardware.radio.V1_5.EutranBands.BAND_20;
-        public static final int BAND_21 = android.hardware.radio.V1_5.EutranBands.BAND_21;
-        public static final int BAND_22 = android.hardware.radio.V1_5.EutranBands.BAND_22;
-        public static final int BAND_23 = android.hardware.radio.V1_5.EutranBands.BAND_23;
-        public static final int BAND_24 = android.hardware.radio.V1_5.EutranBands.BAND_24;
-        public static final int BAND_25 = android.hardware.radio.V1_5.EutranBands.BAND_25;
-        public static final int BAND_26 = android.hardware.radio.V1_5.EutranBands.BAND_26;
-        public static final int BAND_27 = android.hardware.radio.V1_5.EutranBands.BAND_27;
-        public static final int BAND_28 = android.hardware.radio.V1_5.EutranBands.BAND_28;
-        public static final int BAND_30 = android.hardware.radio.V1_5.EutranBands.BAND_30;
-        public static final int BAND_31 = android.hardware.radio.V1_5.EutranBands.BAND_31;
-        public static final int BAND_33 = android.hardware.radio.V1_5.EutranBands.BAND_33;
-        public static final int BAND_34 = android.hardware.radio.V1_5.EutranBands.BAND_34;
-        public static final int BAND_35 = android.hardware.radio.V1_5.EutranBands.BAND_35;
-        public static final int BAND_36 = android.hardware.radio.V1_5.EutranBands.BAND_36;
-        public static final int BAND_37 = android.hardware.radio.V1_5.EutranBands.BAND_37;
-        public static final int BAND_38 = android.hardware.radio.V1_5.EutranBands.BAND_38;
-        public static final int BAND_39 = android.hardware.radio.V1_5.EutranBands.BAND_39;
-        public static final int BAND_40 = android.hardware.radio.V1_5.EutranBands.BAND_40;
-        public static final int BAND_41 = android.hardware.radio.V1_5.EutranBands.BAND_41;
-        public static final int BAND_42 = android.hardware.radio.V1_5.EutranBands.BAND_42;
-        public static final int BAND_43 = android.hardware.radio.V1_5.EutranBands.BAND_43;
-        public static final int BAND_44 = android.hardware.radio.V1_5.EutranBands.BAND_44;
-        public static final int BAND_45 = android.hardware.radio.V1_5.EutranBands.BAND_45;
-        public static final int BAND_46 = android.hardware.radio.V1_5.EutranBands.BAND_46;
-        public static final int BAND_47 = android.hardware.radio.V1_5.EutranBands.BAND_47;
-        public static final int BAND_48 = android.hardware.radio.V1_5.EutranBands.BAND_48;
-        public static final int BAND_49 = android.hardware.radio.V1_5.EutranBands.BAND_49;
-        public static final int BAND_50 = android.hardware.radio.V1_5.EutranBands.BAND_50;
-        public static final int BAND_51 = android.hardware.radio.V1_5.EutranBands.BAND_51;
-        public static final int BAND_52 = android.hardware.radio.V1_5.EutranBands.BAND_52;
-        public static final int BAND_53 = android.hardware.radio.V1_5.EutranBands.BAND_53;
-        public static final int BAND_65 = android.hardware.radio.V1_5.EutranBands.BAND_65;
-        public static final int BAND_66 = android.hardware.radio.V1_5.EutranBands.BAND_66;
-        public static final int BAND_68 = android.hardware.radio.V1_5.EutranBands.BAND_68;
-        public static final int BAND_70 = android.hardware.radio.V1_5.EutranBands.BAND_70;
-        public static final int BAND_71 = android.hardware.radio.V1_5.EutranBands.BAND_71;
-        public static final int BAND_72 = android.hardware.radio.V1_5.EutranBands.BAND_72;
-        public static final int BAND_73 = android.hardware.radio.V1_5.EutranBands.BAND_73;
-        public static final int BAND_74 = android.hardware.radio.V1_5.EutranBands.BAND_74;
-        public static final int BAND_85 = android.hardware.radio.V1_5.EutranBands.BAND_85;
-        public static final int BAND_87 = android.hardware.radio.V1_5.EutranBands.BAND_87;
-        public static final int BAND_88 = android.hardware.radio.V1_5.EutranBands.BAND_88;
+        public static final int BAND_1 = android.hardware.radio.network.EutranBands.BAND_1;
+        public static final int BAND_2 = android.hardware.radio.network.EutranBands.BAND_2;
+        public static final int BAND_3 = android.hardware.radio.network.EutranBands.BAND_3;
+        public static final int BAND_4 = android.hardware.radio.network.EutranBands.BAND_4;
+        public static final int BAND_5 = android.hardware.radio.network.EutranBands.BAND_5;
+        public static final int BAND_6 = android.hardware.radio.network.EutranBands.BAND_6;
+        public static final int BAND_7 = android.hardware.radio.network.EutranBands.BAND_7;
+        public static final int BAND_8 = android.hardware.radio.network.EutranBands.BAND_8;
+        public static final int BAND_9 = android.hardware.radio.network.EutranBands.BAND_9;
+        public static final int BAND_10 = android.hardware.radio.network.EutranBands.BAND_10;
+        public static final int BAND_11 = android.hardware.radio.network.EutranBands.BAND_11;
+        public static final int BAND_12 = android.hardware.radio.network.EutranBands.BAND_12;
+        public static final int BAND_13 = android.hardware.radio.network.EutranBands.BAND_13;
+        public static final int BAND_14 = android.hardware.radio.network.EutranBands.BAND_14;
+        public static final int BAND_17 = android.hardware.radio.network.EutranBands.BAND_17;
+        public static final int BAND_18 = android.hardware.radio.network.EutranBands.BAND_18;
+        public static final int BAND_19 = android.hardware.radio.network.EutranBands.BAND_19;
+        public static final int BAND_20 = android.hardware.radio.network.EutranBands.BAND_20;
+        public static final int BAND_21 = android.hardware.radio.network.EutranBands.BAND_21;
+        public static final int BAND_22 = android.hardware.radio.network.EutranBands.BAND_22;
+        public static final int BAND_23 = android.hardware.radio.network.EutranBands.BAND_23;
+        public static final int BAND_24 = android.hardware.radio.network.EutranBands.BAND_24;
+        public static final int BAND_25 = android.hardware.radio.network.EutranBands.BAND_25;
+        public static final int BAND_26 = android.hardware.radio.network.EutranBands.BAND_26;
+        public static final int BAND_27 = android.hardware.radio.network.EutranBands.BAND_27;
+        public static final int BAND_28 = android.hardware.radio.network.EutranBands.BAND_28;
+        public static final int BAND_30 = android.hardware.radio.network.EutranBands.BAND_30;
+        public static final int BAND_31 = android.hardware.radio.network.EutranBands.BAND_31;
+        public static final int BAND_33 = android.hardware.radio.network.EutranBands.BAND_33;
+        public static final int BAND_34 = android.hardware.radio.network.EutranBands.BAND_34;
+        public static final int BAND_35 = android.hardware.radio.network.EutranBands.BAND_35;
+        public static final int BAND_36 = android.hardware.radio.network.EutranBands.BAND_36;
+        public static final int BAND_37 = android.hardware.radio.network.EutranBands.BAND_37;
+        public static final int BAND_38 = android.hardware.radio.network.EutranBands.BAND_38;
+        public static final int BAND_39 = android.hardware.radio.network.EutranBands.BAND_39;
+        public static final int BAND_40 = android.hardware.radio.network.EutranBands.BAND_40;
+        public static final int BAND_41 = android.hardware.radio.network.EutranBands.BAND_41;
+        public static final int BAND_42 = android.hardware.radio.network.EutranBands.BAND_42;
+        public static final int BAND_43 = android.hardware.radio.network.EutranBands.BAND_43;
+        public static final int BAND_44 = android.hardware.radio.network.EutranBands.BAND_44;
+        public static final int BAND_45 = android.hardware.radio.network.EutranBands.BAND_45;
+        public static final int BAND_46 = android.hardware.radio.network.EutranBands.BAND_46;
+        public static final int BAND_47 = android.hardware.radio.network.EutranBands.BAND_47;
+        public static final int BAND_48 = android.hardware.radio.network.EutranBands.BAND_48;
+        public static final int BAND_49 = android.hardware.radio.network.EutranBands.BAND_49;
+        public static final int BAND_50 = android.hardware.radio.network.EutranBands.BAND_50;
+        public static final int BAND_51 = android.hardware.radio.network.EutranBands.BAND_51;
+        public static final int BAND_52 = android.hardware.radio.network.EutranBands.BAND_52;
+        public static final int BAND_53 = android.hardware.radio.network.EutranBands.BAND_53;
+        public static final int BAND_65 = android.hardware.radio.network.EutranBands.BAND_65;
+        public static final int BAND_66 = android.hardware.radio.network.EutranBands.BAND_66;
+        public static final int BAND_68 = android.hardware.radio.network.EutranBands.BAND_68;
+        public static final int BAND_70 = android.hardware.radio.network.EutranBands.BAND_70;
+        public static final int BAND_71 = android.hardware.radio.network.EutranBands.BAND_71;
+        public static final int BAND_72 = android.hardware.radio.network.EutranBands.BAND_72;
+        public static final int BAND_73 = android.hardware.radio.network.EutranBands.BAND_73;
+        public static final int BAND_74 = android.hardware.radio.network.EutranBands.BAND_74;
+        public static final int BAND_85 = android.hardware.radio.network.EutranBands.BAND_85;
+        public static final int BAND_87 = android.hardware.radio.network.EutranBands.BAND_87;
+        public static final int BAND_88 = android.hardware.radio.network.EutranBands.BAND_88;
 
         /**
          * EutranBands
@@ -714,61 +716,61 @@
      */
     public static final class NgranBands {
         /** 3GPP TS 38.101-1, Version 16.5.0, Table 5.2-1: FR1 bands */
-        public static final int BAND_1 = android.hardware.radio.V1_5.NgranBands.BAND_1;
-        public static final int BAND_2 = android.hardware.radio.V1_5.NgranBands.BAND_2;
-        public static final int BAND_3 = android.hardware.radio.V1_5.NgranBands.BAND_3;
-        public static final int BAND_5 = android.hardware.radio.V1_5.NgranBands.BAND_5;
-        public static final int BAND_7 = android.hardware.radio.V1_5.NgranBands.BAND_7;
-        public static final int BAND_8 = android.hardware.radio.V1_5.NgranBands.BAND_8;
-        public static final int BAND_12 = android.hardware.radio.V1_5.NgranBands.BAND_12;
-        public static final int BAND_14 = android.hardware.radio.V1_5.NgranBands.BAND_14;
-        public static final int BAND_18 = android.hardware.radio.V1_5.NgranBands.BAND_18;
-        public static final int BAND_20 = android.hardware.radio.V1_5.NgranBands.BAND_20;
-        public static final int BAND_25 = android.hardware.radio.V1_5.NgranBands.BAND_25;
-        public static final int BAND_26 = android.hardware.radio.V1_6.NgranBands.BAND_26;
-        public static final int BAND_28 = android.hardware.radio.V1_5.NgranBands.BAND_28;
-        public static final int BAND_29 = android.hardware.radio.V1_5.NgranBands.BAND_29;
-        public static final int BAND_30 = android.hardware.radio.V1_5.NgranBands.BAND_30;
-        public static final int BAND_34 = android.hardware.radio.V1_5.NgranBands.BAND_34;
-        public static final int BAND_38 = android.hardware.radio.V1_5.NgranBands.BAND_38;
-        public static final int BAND_39 = android.hardware.radio.V1_5.NgranBands.BAND_39;
-        public static final int BAND_40 = android.hardware.radio.V1_5.NgranBands.BAND_40;
-        public static final int BAND_41 = android.hardware.radio.V1_5.NgranBands.BAND_41;
-        public static final int BAND_46 = android.hardware.radio.V1_6.NgranBands.BAND_46;
-        public static final int BAND_48 = android.hardware.radio.V1_5.NgranBands.BAND_48;
-        public static final int BAND_50 = android.hardware.radio.V1_5.NgranBands.BAND_50;
-        public static final int BAND_51 = android.hardware.radio.V1_5.NgranBands.BAND_51;
-        public static final int BAND_53 = android.hardware.radio.V1_6.NgranBands.BAND_53;
-        public static final int BAND_65 = android.hardware.radio.V1_5.NgranBands.BAND_65;
-        public static final int BAND_66 = android.hardware.radio.V1_5.NgranBands.BAND_66;
-        public static final int BAND_70 = android.hardware.radio.V1_5.NgranBands.BAND_70;
-        public static final int BAND_71 = android.hardware.radio.V1_5.NgranBands.BAND_71;
-        public static final int BAND_74 = android.hardware.radio.V1_5.NgranBands.BAND_74;
-        public static final int BAND_75 = android.hardware.radio.V1_5.NgranBands.BAND_75;
-        public static final int BAND_76 = android.hardware.radio.V1_5.NgranBands.BAND_76;
-        public static final int BAND_77 = android.hardware.radio.V1_5.NgranBands.BAND_77;
-        public static final int BAND_78 = android.hardware.radio.V1_5.NgranBands.BAND_78;
-        public static final int BAND_79 = android.hardware.radio.V1_5.NgranBands.BAND_79;
-        public static final int BAND_80 = android.hardware.radio.V1_5.NgranBands.BAND_80;
-        public static final int BAND_81 = android.hardware.radio.V1_5.NgranBands.BAND_81;
-        public static final int BAND_82 = android.hardware.radio.V1_5.NgranBands.BAND_82;
-        public static final int BAND_83 = android.hardware.radio.V1_5.NgranBands.BAND_83;
-        public static final int BAND_84 = android.hardware.radio.V1_5.NgranBands.BAND_84;
-        public static final int BAND_86 = android.hardware.radio.V1_5.NgranBands.BAND_86;
-        public static final int BAND_89 = android.hardware.radio.V1_5.NgranBands.BAND_89;
-        public static final int BAND_90 = android.hardware.radio.V1_5.NgranBands.BAND_90;
-        public static final int BAND_91 = android.hardware.radio.V1_5.NgranBands.BAND_91;
-        public static final int BAND_92 = android.hardware.radio.V1_5.NgranBands.BAND_92;
-        public static final int BAND_93 = android.hardware.radio.V1_5.NgranBands.BAND_93;
-        public static final int BAND_94 = android.hardware.radio.V1_5.NgranBands.BAND_94;
-        public static final int BAND_95 = android.hardware.radio.V1_5.NgranBands.BAND_95;
-        public static final int BAND_96 = android.hardware.radio.V1_6.NgranBands.BAND_96;
+        public static final int BAND_1 = android.hardware.radio.network.NgranBands.BAND_1;
+        public static final int BAND_2 = android.hardware.radio.network.NgranBands.BAND_2;
+        public static final int BAND_3 = android.hardware.radio.network.NgranBands.BAND_3;
+        public static final int BAND_5 = android.hardware.radio.network.NgranBands.BAND_5;
+        public static final int BAND_7 = android.hardware.radio.network.NgranBands.BAND_7;
+        public static final int BAND_8 = android.hardware.radio.network.NgranBands.BAND_8;
+        public static final int BAND_12 = android.hardware.radio.network.NgranBands.BAND_12;
+        public static final int BAND_14 = android.hardware.radio.network.NgranBands.BAND_14;
+        public static final int BAND_18 = android.hardware.radio.network.NgranBands.BAND_18;
+        public static final int BAND_20 = android.hardware.radio.network.NgranBands.BAND_20;
+        public static final int BAND_25 = android.hardware.radio.network.NgranBands.BAND_25;
+        public static final int BAND_26 = android.hardware.radio.network.NgranBands.BAND_26;
+        public static final int BAND_28 = android.hardware.radio.network.NgranBands.BAND_28;
+        public static final int BAND_29 = android.hardware.radio.network.NgranBands.BAND_29;
+        public static final int BAND_30 = android.hardware.radio.network.NgranBands.BAND_30;
+        public static final int BAND_34 = android.hardware.radio.network.NgranBands.BAND_34;
+        public static final int BAND_38 = android.hardware.radio.network.NgranBands.BAND_38;
+        public static final int BAND_39 = android.hardware.radio.network.NgranBands.BAND_39;
+        public static final int BAND_40 = android.hardware.radio.network.NgranBands.BAND_40;
+        public static final int BAND_41 = android.hardware.radio.network.NgranBands.BAND_41;
+        public static final int BAND_46 = android.hardware.radio.network.NgranBands.BAND_46;
+        public static final int BAND_48 = android.hardware.radio.network.NgranBands.BAND_48;
+        public static final int BAND_50 = android.hardware.radio.network.NgranBands.BAND_50;
+        public static final int BAND_51 = android.hardware.radio.network.NgranBands.BAND_51;
+        public static final int BAND_53 = android.hardware.radio.network.NgranBands.BAND_53;
+        public static final int BAND_65 = android.hardware.radio.network.NgranBands.BAND_65;
+        public static final int BAND_66 = android.hardware.radio.network.NgranBands.BAND_66;
+        public static final int BAND_70 = android.hardware.radio.network.NgranBands.BAND_70;
+        public static final int BAND_71 = android.hardware.radio.network.NgranBands.BAND_71;
+        public static final int BAND_74 = android.hardware.radio.network.NgranBands.BAND_74;
+        public static final int BAND_75 = android.hardware.radio.network.NgranBands.BAND_75;
+        public static final int BAND_76 = android.hardware.radio.network.NgranBands.BAND_76;
+        public static final int BAND_77 = android.hardware.radio.network.NgranBands.BAND_77;
+        public static final int BAND_78 = android.hardware.radio.network.NgranBands.BAND_78;
+        public static final int BAND_79 = android.hardware.radio.network.NgranBands.BAND_79;
+        public static final int BAND_80 = android.hardware.radio.network.NgranBands.BAND_80;
+        public static final int BAND_81 = android.hardware.radio.network.NgranBands.BAND_81;
+        public static final int BAND_82 = android.hardware.radio.network.NgranBands.BAND_82;
+        public static final int BAND_83 = android.hardware.radio.network.NgranBands.BAND_83;
+        public static final int BAND_84 = android.hardware.radio.network.NgranBands.BAND_84;
+        public static final int BAND_86 = android.hardware.radio.network.NgranBands.BAND_86;
+        public static final int BAND_89 = android.hardware.radio.network.NgranBands.BAND_89;
+        public static final int BAND_90 = android.hardware.radio.network.NgranBands.BAND_90;
+        public static final int BAND_91 = android.hardware.radio.network.NgranBands.BAND_91;
+        public static final int BAND_92 = android.hardware.radio.network.NgranBands.BAND_92;
+        public static final int BAND_93 = android.hardware.radio.network.NgranBands.BAND_93;
+        public static final int BAND_94 = android.hardware.radio.network.NgranBands.BAND_94;
+        public static final int BAND_95 = android.hardware.radio.network.NgranBands.BAND_95;
+        public static final int BAND_96 = android.hardware.radio.network.NgranBands.BAND_96;
 
         /** 3GPP TS 38.101-2, Version 16.2.0, Table 5.2-1: FR2 bands */
-        public static final int BAND_257 = android.hardware.radio.V1_5.NgranBands.BAND_257;
-        public static final int BAND_258 = android.hardware.radio.V1_5.NgranBands.BAND_258;
-        public static final int BAND_260 = android.hardware.radio.V1_5.NgranBands.BAND_260;
-        public static final int BAND_261 = android.hardware.radio.V1_5.NgranBands.BAND_261;
+        public static final int BAND_257 = android.hardware.radio.network.NgranBands.BAND_257;
+        public static final int BAND_258 = android.hardware.radio.network.NgranBands.BAND_258;
+        public static final int BAND_260 = android.hardware.radio.network.NgranBands.BAND_260;
+        public static final int BAND_261 = android.hardware.radio.network.NgranBands.BAND_261;
 
         /**
          * NR Bands
diff --git a/telephony/java/android/telephony/BarringInfo.java b/telephony/java/android/telephony/BarringInfo.java
index 29152f1..971fc78 100644
--- a/telephony/java/android/telephony/BarringInfo.java
+++ b/telephony/java/android/telephony/BarringInfo.java
@@ -58,41 +58,41 @@
             BARRING_SERVICE_TYPE_SMS})
     public @interface BarringServiceType {}
 
-    /* Applicabe to UTRAN */
+    /* Applicable to UTRAN */
     /** Barring indicator for circuit-switched service; applicable to UTRAN */
     public static final int BARRING_SERVICE_TYPE_CS_SERVICE =
-            android.hardware.radio.V1_5.BarringInfo.ServiceType.CS_SERVICE;
+            android.hardware.radio.network.BarringInfo.SERVICE_TYPE_CS_SERVICE;
     /** Barring indicator for packet-switched service; applicable to UTRAN */
     public static final int BARRING_SERVICE_TYPE_PS_SERVICE =
-            android.hardware.radio.V1_5.BarringInfo.ServiceType.PS_SERVICE;
+            android.hardware.radio.network.BarringInfo.SERVICE_TYPE_PS_SERVICE;
     /** Barring indicator for circuit-switched voice service; applicable to UTRAN */
     public static final int BARRING_SERVICE_TYPE_CS_VOICE =
-            android.hardware.radio.V1_5.BarringInfo.ServiceType.CS_VOICE;
+            android.hardware.radio.network.BarringInfo.SERVICE_TYPE_CS_VOICE;
 
     /* Applicable to EUTRAN, NGRAN */
     /** Barring indicator for mobile-originated signalling; applicable to EUTRAN and NGRAN */
     public static final int BARRING_SERVICE_TYPE_MO_SIGNALLING =
-            android.hardware.radio.V1_5.BarringInfo.ServiceType.MO_SIGNALLING;
+            android.hardware.radio.network.BarringInfo.SERVICE_TYPE_MO_SIGNALLING;
     /** Barring indicator for mobile-originated data traffic; applicable to EUTRAN and NGRAN */
     public static final int BARRING_SERVICE_TYPE_MO_DATA =
-            android.hardware.radio.V1_5.BarringInfo.ServiceType.MO_DATA;
+            android.hardware.radio.network.BarringInfo.SERVICE_TYPE_MO_DATA;
     /** Barring indicator for circuit-switched fallback for voice; applicable to EUTRAN and NGRAN */
     public static final int BARRING_SERVICE_TYPE_CS_FALLBACK =
-            android.hardware.radio.V1_5.BarringInfo.ServiceType.CS_FALLBACK;
+            android.hardware.radio.network.BarringInfo.SERVICE_TYPE_CS_FALLBACK;
     /** Barring indicator for MMTEL (IMS) voice; applicable to EUTRAN and NGRAN */
     public static final int BARRING_SERVICE_TYPE_MMTEL_VOICE =
-            android.hardware.radio.V1_5.BarringInfo.ServiceType.MMTEL_VOICE;
+            android.hardware.radio.network.BarringInfo.SERVICE_TYPE_MMTEL_VOICE;
     /** Barring indicator for MMTEL (IMS) video; applicable to EUTRAN and NGRAN */
     public static final int BARRING_SERVICE_TYPE_MMTEL_VIDEO =
-            android.hardware.radio.V1_5.BarringInfo.ServiceType.MMTEL_VIDEO;
+            android.hardware.radio.network.BarringInfo.SERVICE_TYPE_MMTEL_VIDEO;
 
     /* Applicable to UTRAN, EUTRAN, NGRAN */
     /** Barring indicator for emergency services; applicable to UTRAN, EUTRAN, and NGRAN */
     public static final int BARRING_SERVICE_TYPE_EMERGENCY =
-            android.hardware.radio.V1_5.BarringInfo.ServiceType.EMERGENCY;
+            android.hardware.radio.network.BarringInfo.SERVICE_TYPE_EMERGENCY;
     /** Barring indicator for SMS sending; applicable to UTRAN, EUTRAN, and NGRAN */
     public static final int BARRING_SERVICE_TYPE_SMS =
-            android.hardware.radio.V1_5.BarringInfo.ServiceType.SMS;
+            android.hardware.radio.network.BarringInfo.SERVICE_TYPE_SMS;
 
     //TODO: add barring constants for Operator-Specific barring codes
 
@@ -112,13 +112,13 @@
 
         /** Barring is inactive */
         public static final int BARRING_TYPE_NONE =
-                android.hardware.radio.V1_5.BarringInfo.BarringType.NONE;
+                android.hardware.radio.network.BarringInfo.BARRING_TYPE_NONE;
         /** The service is barred */
         public static final int BARRING_TYPE_UNCONDITIONAL =
-                android.hardware.radio.V1_5.BarringInfo.BarringType.UNCONDITIONAL;
+                android.hardware.radio.network.BarringInfo.BARRING_TYPE_UNCONDITIONAL;
         /** The service may be barred based on additional factors */
         public static final int BARRING_TYPE_CONDITIONAL =
-                android.hardware.radio.V1_5.BarringInfo.BarringType.CONDITIONAL;
+                android.hardware.radio.network.BarringInfo.BARRING_TYPE_CONDITIONAL;
 
         /** If a modem does not report barring info, then the barring type will be UNKNOWN */
         public static final int BARRING_TYPE_UNKNOWN = -1;
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 5b1c6b1..ed1c41f 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -50,7 +50,6 @@
 import android.telephony.ims.RcsUceAdapter;
 import android.telephony.ims.feature.MmTelFeature;
 import android.telephony.ims.feature.RcsFeature;
-import android.telephony.ims.stub.ImsRegistrationImplBase;
 
 import com.android.internal.telephony.ICarrierConfigLoader;
 import com.android.telephony.Rlog;
@@ -570,6 +569,13 @@
             "editable_voicemail_number_bool";
 
     /**
+     * Determine whether the voicemail number in Settings is hidden.
+     * @hide
+     */
+    public static final String KEY_HIDE_VOICEMAIL_NUMBER_SETTING_BOOL =
+            "hide_voicemail_number_setting_bool";
+
+    /**
      * Determine whether the voicemail notification is persistent in the notification bar. If true,
      * the voicemail notifications cannot be dismissed from the notification bar.
      */
@@ -5813,6 +5819,21 @@
          */
         public static final int NR_SA_DISABLE_POLICY_VOWIFI_REGISTERED = 3;
 
+        /**
+         * This specifies whether the carrier support the global number format or not.
+         * {@link SubscriptionManager#getPhoneNumber(int)},
+         * {@link SubscriptionManager#getPhoneNumber(int, int)} with
+         * {@link SubscriptionManager#PHONE_NUMBER_SOURCE_IMS}
+         * In order to provide the phone number to the APIs, the framework extracts the phone
+         * number from the message received from the carrier server. If the carrier does not use
+         * global number format, the framework could not provide phone number.
+         * <p>
+         * If not set or set to false value, the framework handle only global number format URI.
+         * @hide
+         */
+        public static final String KEY_ALLOW_NON_GLOBAL_PHONE_NUMBER_FORMAT_BOOL =
+                KEY_PREFIX + "allow_non_global_phone_number_format_bool";
+
         private Ims() {}
 
         private static PersistableBundle getDefaults() {
@@ -5925,6 +5946,8 @@
             defaults.putString(KEY_IMS_USER_AGENT_STRING,
                                "#MANUFACTURER#_#MODEL#_Android#AV#_#BUILD#");
 
+            defaults.putBoolean(KEY_ALLOW_NON_GLOBAL_PHONE_NUMBER_FORMAT_BOOL, false);
+
             return defaults;
         }
     }
@@ -9384,6 +9407,37 @@
             "missed_incoming_call_sms_pattern_string_array";
 
     /**
+     * Indicate the satellite services supported per provider by a carrier.
+     *
+     * Key is the PLMN of a satellite provider. Value should be an integer array of supported
+     * services with the following value:
+     * <ul>
+     * <li>1 = {@link android.telephony.NetworkRegistrationInfo#SERVICE_TYPE_VOICE}</li>
+     * <li>2 = {@link android.telephony.NetworkRegistrationInfo#SERVICE_TYPE_DATA}</li>
+     * <li>3 = {@link android.telephony.NetworkRegistrationInfo#SERVICE_TYPE_SMS}</li>
+     * <li>4 = {@link android.telephony.NetworkRegistrationInfo#SERVICE_TYPE_VIDEO}</li>
+     * <li>5 = {@link android.telephony.NetworkRegistrationInfo#SERVICE_TYPE_EMERGENCY}</li>
+     * </ul>
+     * <p>
+     * If this carrier config is not present, the overlay config
+     * {@code config_satellite_services_supported_by_providers} will be used. If the carrier config
+     * is present, the supported satellite services will be identified as follows:
+     * <ul>
+     * <li>For the PLMN that exists in both provider supported satellite services and carrier
+     * supported satellite services, the supported services will be the intersection of the two
+     * sets.</li>
+     * <li>For the PLMN that is present in provider supported satellite services but not in carrier
+     * supported satellite services, the provider supported satellite services will be used.</li>
+     * <li>For the PLMN that is present in carrier supported satellite services but not in provider
+     * supported satellite services, the PLMN will be ignored.</li>
+     * </ul>
+     *
+     * This config is empty by default.
+     */
+    public static final String KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE =
+            "carrier_supported_satellite_services_per_provider_bundle";
+
+    /**
      * Indicating whether DUN APN should be disabled when the device is roaming. In that case,
      * the default APN (i.e. internet) will be used for tethering.
      *
@@ -9605,6 +9659,7 @@
      *
      * @see TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_CANCELED
      * @see TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_TIMEOUT
+     * @see TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_DISABLED
      */
     public static final String
             KEY_PREMIUM_CAPABILITY_NOTIFICATION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG =
@@ -9730,6 +9785,27 @@
     public static final String KEY_IWLAN_HANDOVER_POLICY_STRING_ARRAY =
             "iwlan_handover_policy_string_array";
 
+    /**
+     * Score table for {@link TelephonyManager#MOBILE_DATA_POLICY_AUTO_DATA_SWITCH}. The score is
+     * used in conjunction with a tolerance value defined in resource config
+     * {@code auto_data_switch_score_tolerance}, greater than which device will switch to the sub
+     * with higher score.
+     * Possible keys are network type name string(also see {@link #KEY_BANDWIDTH_STRING_ARRAY}).
+     * Value should be "score_level_0, score_level_1, score_level_2, score_level_3,score_level_4".
+     * Each network type must have 5 scores correspond to {@link CellSignalStrength}, where score is
+     * a non-negative integer. A score of 0 is treated the same as data out of service.
+     *
+     * For NR (5G), the following network names should be used:
+     * - NR_NSA: NR NSA, sub-6 frequencies
+     * - NR_NSA_MMWAVE: NR NSA, mmwave frequencies
+     * - NR_SA: NR SA, sub-6 frequencies
+     * - NR_SA_MMWAVE: NR SA, mmwave frequencies
+     *
+     * @hide
+     */
+    public static final String KEY_AUTO_DATA_SWITCH_RAT_SIGNAL_SCORE_BUNDLE =
+            "auto_data_switch_rat_signal_score_string_bundle";
+
     /** The default value for every variable. */
     private static final PersistableBundle sDefaults;
 
@@ -9834,6 +9910,7 @@
         sDefaults.putBoolean(KEY_USE_HFA_FOR_PROVISIONING_BOOL, false);
         sDefaults.putBoolean(KEY_EDITABLE_VOICEMAIL_NUMBER_SETTING_BOOL, true);
         sDefaults.putBoolean(KEY_EDITABLE_VOICEMAIL_NUMBER_BOOL, false);
+        sDefaults.putBoolean(KEY_HIDE_VOICEMAIL_NUMBER_SETTING_BOOL, false);
         sDefaults.putBoolean(KEY_USE_OTASP_FOR_PROVISIONING_BOOL, false);
         sDefaults.putBoolean(KEY_VOICEMAIL_NOTIFICATION_PERSISTENT_BOOL, false);
         sDefaults.putBoolean(KEY_VOICE_PRIVACY_DISABLE_UI_BOOL, false);
@@ -9876,9 +9953,9 @@
         sDefaults.putString(KEY_CARRIER_ERI_FILE_NAME_STRING, "eri.xml");
         sDefaults.putInt(KEY_DURATION_BLOCKING_DISABLED_AFTER_EMERGENCY_INT, 7200);
         sDefaults.putStringArray(KEY_CARRIER_METERED_APN_TYPES_STRINGS,
-                new String[]{"default", "mms", "dun", "supl"});
+                new String[]{"default", "mms", "dun", "supl", "enterprise"});
         sDefaults.putStringArray(KEY_CARRIER_METERED_ROAMING_APN_TYPES_STRINGS,
-                new String[]{"default", "mms", "dun", "supl"});
+                new String[]{"default", "mms", "dun", "supl", "enterprise"});
         sDefaults.putIntArray(KEY_ONLY_SINGLE_DC_ALLOWED_INT_ARRAY,
                 new int[] {TelephonyManager.NETWORK_TYPE_CDMA, TelephonyManager.NETWORK_TYPE_1xRTT,
                         TelephonyManager.NETWORK_TYPE_EVDO_0, TelephonyManager.NETWORK_TYPE_EVDO_A,
@@ -10367,6 +10444,9 @@
                 });
         sDefaults.putBoolean(KEY_DELAY_IMS_TEAR_DOWN_UNTIL_CALL_END_BOOL, false);
         sDefaults.putStringArray(KEY_MISSED_INCOMING_CALL_SMS_PATTERN_STRING_ARRAY, new String[0]);
+        sDefaults.putPersistableBundle(
+                KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE,
+                PersistableBundle.EMPTY);
         sDefaults.putBoolean(KEY_DISABLE_DUN_APN_WHILE_ROAMING_WITH_PRESET_APN_BOOL, false);
         sDefaults.putString(KEY_DEFAULT_PREFERRED_APN_NAME_STRING, "");
         sDefaults.putBoolean(KEY_SUPPORTS_CALL_COMPOSER_BOOL, false);
@@ -10404,6 +10484,51 @@
         sDefaults.putStringArray(KEY_IWLAN_HANDOVER_POLICY_STRING_ARRAY, new String[]{
                 "source=GERAN|UTRAN|EUTRAN|NGRAN|IWLAN, "
                         + "target=GERAN|UTRAN|EUTRAN|NGRAN|IWLAN, type=allowed"});
+        PersistableBundle auto_data_switch_rat_signal_score_string_bundle = new PersistableBundle();
+        auto_data_switch_rat_signal_score_string_bundle.putIntArray(
+                "NR_SA_MMWAVE", new int[]{10000, 13227, 16000, 18488, 20017});
+        auto_data_switch_rat_signal_score_string_bundle.putIntArray(
+                "NR_NSA_MMWAVE", new int[]{8000, 10227, 12488, 15017, 15278});
+        auto_data_switch_rat_signal_score_string_bundle.putIntArray(
+                "LTE", new int[]{3731, 5965, 8618, 11179, 13384});
+        auto_data_switch_rat_signal_score_string_bundle.putIntArray(
+                "NR_SA", new int[]{5288, 6795, 6955, 7562, 9713});
+        auto_data_switch_rat_signal_score_string_bundle.putIntArray(
+                "NR_NSA", new int[]{5463, 6827, 8029, 9007, 9428});
+        auto_data_switch_rat_signal_score_string_bundle.putIntArray(
+                "UMTS", new int[]{100, 169, 183, 192, 300});
+        auto_data_switch_rat_signal_score_string_bundle.putIntArray(
+                "eHRPD", new int[]{10, 400, 600, 800, 1000});
+        auto_data_switch_rat_signal_score_string_bundle.putIntArray(
+                "TD_SCDMA", new int[]{1, 100, 500, 1000});
+        auto_data_switch_rat_signal_score_string_bundle.putIntArray(
+                "iDEN", new int[]{1, 2, 10, 50, 100});
+        auto_data_switch_rat_signal_score_string_bundle.putIntArray(
+                "EvDo_B", new int[]{1000, 1495, 2186, 2532, 2600});
+        auto_data_switch_rat_signal_score_string_bundle.putIntArray(
+                "HSPA+", new int[]{1619, 2500, 3393, 4129, 4212});
+        auto_data_switch_rat_signal_score_string_bundle.putIntArray(
+                "HSPA", new int[]{1000, 1495, 2186, 2532, 2544});
+        auto_data_switch_rat_signal_score_string_bundle.putIntArray(
+                "HSUPA", new int[]{1500, 1919, 2132, 2362, 2704});
+        auto_data_switch_rat_signal_score_string_bundle.putIntArray(
+                "HSDPA", new int[]{1500, 1732, 4000, 7000, 8000});
+        auto_data_switch_rat_signal_score_string_bundle.putIntArray(
+                "EvDo_A", new int[]{600, 840, 1200, 1300, 1400});
+        auto_data_switch_rat_signal_score_string_bundle.putIntArray(
+                "EvDo_0", new int[]{300, 600, 1000, 1500, 2000});
+        auto_data_switch_rat_signal_score_string_bundle.putIntArray(
+                "1xRTT", new int[]{50, 60, 70, 80});
+        auto_data_switch_rat_signal_score_string_bundle.putIntArray(
+                "EDGE", new int[]{154, 169, 183, 192, 267});
+        auto_data_switch_rat_signal_score_string_bundle.putIntArray(
+                "GPRS", new int[]{15, 30, 40, 45, 50});
+        auto_data_switch_rat_signal_score_string_bundle.putIntArray(
+                "CDMA", new int[]{1, 50, 100, 300, 2000});
+        auto_data_switch_rat_signal_score_string_bundle.putIntArray(
+                "GSM", new int[]{1, 2, 10, 50, 100});
+        sDefaults.putPersistableBundle(KEY_AUTO_DATA_SWITCH_RAT_SIGNAL_SCORE_BUNDLE,
+                auto_data_switch_rat_signal_score_string_bundle);
         sDefaults.putInt(KEY_CELLULAR_USAGE_SETTING_INT,
                 SubscriptionManager.USAGE_SETTING_UNKNOWN);
         // Default data stall recovery configurations.
diff --git a/telephony/java/android/telephony/NetworkRegistrationInfo.java b/telephony/java/android/telephony/NetworkRegistrationInfo.java
index b0552b4..6258b9c 100644
--- a/telephony/java/android/telephony/NetworkRegistrationInfo.java
+++ b/telephony/java/android/telephony/NetworkRegistrationInfo.java
@@ -63,9 +63,9 @@
     /** Unknown / Unspecified domain */
     public static final int DOMAIN_UNKNOWN = 0;
     /** Circuit switched domain */
-    public static final int DOMAIN_CS = android.hardware.radio.V1_5.Domain.CS;
+    public static final int DOMAIN_CS = android.hardware.radio.network.Domain.CS;
     /** Packet switched domain */
-    public static final int DOMAIN_PS = android.hardware.radio.V1_5.Domain.PS;
+    public static final int DOMAIN_PS = android.hardware.radio.network.Domain.PS;
     /** Applicable to both CS and PS Domain */
     public static final int DOMAIN_CS_PS = DOMAIN_CS | DOMAIN_PS;
 
@@ -203,6 +203,12 @@
      */
     public static final int SERVICE_TYPE_EMERGENCY  = 5;
 
+    /** @hide  */
+    public static final int FIRST_SERVICE_TYPE = SERVICE_TYPE_VOICE;
+
+    /** @hide  */
+    public static final int LAST_SERVICE_TYPE = SERVICE_TYPE_EMERGENCY;
+
     @Domain
     private final int mDomain;
 
@@ -240,7 +246,7 @@
     private final boolean mEmergencyOnly;
 
     @ServiceType
-    private final ArrayList<Integer> mAvailableServices;
+    private ArrayList<Integer> mAvailableServices;
 
     @Nullable
     private CellIdentity mCellIdentity;
@@ -257,6 +263,9 @@
     // Updated based on the accessNetworkTechnology
     private boolean mIsUsingCarrierAggregation;
 
+    // Set to {@code true} when network is a non-terrestrial network.
+    private boolean mIsNonTerrestrialNetwork;
+
     /**
      * @param domain Network domain. Must be a {@link Domain}. For transport type
      * {@link AccessNetworkConstants#TRANSPORT_TYPE_WLAN}, this must set to {@link #DOMAIN_PS}.
@@ -280,6 +289,7 @@
      * @param rplmn the registered plmn or the last plmn for attempted registration if reg failed.
      * @param voiceSpecificInfo Voice specific registration information.
      * @param dataSpecificInfo Data specific registration information.
+     * @param isNonTerrestrialNetwork {@code true} if network is a non-terrestrial network.
      */
     private NetworkRegistrationInfo(@Domain int domain, @TransportType int transportType,
             @RegistrationState int registrationState,
@@ -287,7 +297,8 @@
             boolean emergencyOnly, @Nullable @ServiceType List<Integer> availableServices,
             @Nullable CellIdentity cellIdentity, @Nullable String rplmn,
             @Nullable VoiceSpecificRegistrationInfo voiceSpecificInfo,
-            @Nullable DataSpecificRegistrationInfo dataSpecificInfo) {
+            @Nullable DataSpecificRegistrationInfo dataSpecificInfo,
+            boolean isNonTerrestrialNetwork) {
         mDomain = domain;
         mTransportType = transportType;
         mRegistrationState = registrationState;
@@ -304,6 +315,7 @@
         mRplmn = rplmn;
         mVoiceSpecificInfo = voiceSpecificInfo;
         mDataSpecificInfo = dataSpecificInfo;
+        mIsNonTerrestrialNetwork = isNonTerrestrialNetwork;
 
         updateNrState();
     }
@@ -322,7 +334,7 @@
         this(domain, transportType, registrationState, accessNetworkTechnology, rejectCause,
                 emergencyOnly, availableServices, cellIdentity, rplmn,
                 new VoiceSpecificRegistrationInfo(cssSupported, roamingIndicator,
-                        systemIsInPrl, defaultRoamingIndicator), null);
+                        systemIsInPrl, defaultRoamingIndicator), null, false);
     }
 
     /**
@@ -344,7 +356,7 @@
                         .setNrAvailable(isNrAvailable)
                         .setEnDcAvailable(isEndcAvailable)
                         .setVopsSupportInfo(vopsSupportInfo)
-                        .build());
+                        .build(), false);
     }
 
     private NetworkRegistrationInfo(Parcel source) {
@@ -366,6 +378,7 @@
         mNrState = source.readInt();
         mRplmn = source.readString();
         mIsUsingCarrierAggregation = source.readBoolean();
+        mIsNonTerrestrialNetwork = source.readBoolean();
     }
 
     /**
@@ -382,6 +395,7 @@
         mRoamingType = nri.mRoamingType;
         mAccessNetworkTechnology = nri.mAccessNetworkTechnology;
         mIsUsingCarrierAggregation = nri.mIsUsingCarrierAggregation;
+        mIsNonTerrestrialNetwork = nri.mIsNonTerrestrialNetwork;
         mRejectCause = nri.mRejectCause;
         mEmergencyOnly = nri.mEmergencyOnly;
         mAvailableServices = new ArrayList<>(nri.mAvailableServices);
@@ -596,6 +610,16 @@
     }
 
     /**
+     * Set available service types.
+     *
+     * @param availableServices The list of available services for this network.
+     * @hide
+     */
+    public void setAvailableServices(@NonNull @ServiceType List<Integer> availableServices) {
+        mAvailableServices = new ArrayList<>(availableServices);
+    }
+
+    /**
      * @return The access network technology {@link NetworkType}.
      */
     public @NetworkType int getAccessNetworkTechnology() {
@@ -658,6 +682,26 @@
     }
 
     /**
+     * Set whether the network is a non-terrestrial network.
+     *
+     * @param isNonTerrestrialNetwork {@code true} if network is a non-terrestrial network
+     *                                            else {@code false}.
+     * @hide
+     */
+    public void setIsNonTerrestrialNetwork(boolean isNonTerrestrialNetwork) {
+        mIsNonTerrestrialNetwork = isNonTerrestrialNetwork;
+    }
+
+    /**
+     * Get whether the network is a non-terrestrial network.
+     *
+     * @return {@code true} if network is a non-terrestrial network else {@code false}.
+     */
+    public boolean isNonTerrestrialNetwork() {
+        return mIsNonTerrestrialNetwork;
+    }
+
+    /**
      * @hide
      */
     @Nullable
@@ -744,6 +788,18 @@
         }
     }
 
+    /**
+     * Convert isNonTerrestrialNetwork to string
+     *
+     * @param isNonTerrestrialNetwork boolean indicating whether network is a non-terrestrial
+     *                                network
+     * @return string format of isNonTerrestrialNetwork.
+     * @hide
+     */
+    public static String isNonTerrestrialNetworkToString(boolean isNonTerrestrialNetwork) {
+        return isNonTerrestrialNetwork ? "NON-TERRESTRIAL" : "TERRESTRIAL";
+    }
+
     @NonNull
     @Override
     public String toString() {
@@ -769,6 +825,8 @@
                         ? nrStateToString(mNrState) : "****")
                 .append(" rRplmn=").append(mRplmn)
                 .append(" isUsingCarrierAggregation=").append(mIsUsingCarrierAggregation)
+                .append(" isNonTerrestrialNetwork=").append(
+                        isNonTerrestrialNetworkToString(mIsNonTerrestrialNetwork))
                 .append("}").toString();
     }
 
@@ -777,7 +835,7 @@
         return Objects.hash(mDomain, mTransportType, mRegistrationState, mNetworkRegistrationState,
                 mRoamingType, mAccessNetworkTechnology, mRejectCause, mEmergencyOnly,
                 mAvailableServices, mCellIdentity, mVoiceSpecificInfo, mDataSpecificInfo, mNrState,
-                mRplmn, mIsUsingCarrierAggregation);
+                mRplmn, mIsUsingCarrierAggregation, mIsNonTerrestrialNetwork);
     }
 
     @Override
@@ -803,7 +861,8 @@
                 && Objects.equals(mVoiceSpecificInfo, other.mVoiceSpecificInfo)
                 && Objects.equals(mDataSpecificInfo, other.mDataSpecificInfo)
                 && TextUtils.equals(mRplmn, other.mRplmn)
-                && mNrState == other.mNrState;
+                && mNrState == other.mNrState
+                && mIsNonTerrestrialNetwork == other.mIsNonTerrestrialNetwork;
     }
 
     /**
@@ -827,6 +886,7 @@
         dest.writeInt(mNrState);
         dest.writeString(mRplmn);
         dest.writeBoolean(mIsUsingCarrierAggregation);
+        dest.writeBoolean(mIsNonTerrestrialNetwork);
     }
 
     /**
@@ -936,6 +996,8 @@
         @Nullable
         private VoiceSpecificRegistrationInfo mVoiceSpecificRegistrationInfo;
 
+        private boolean mIsNonTerrestrialNetwork;
+
         /**
          * Default constructor for Builder.
          */
@@ -964,6 +1026,7 @@
                 mVoiceSpecificRegistrationInfo = new VoiceSpecificRegistrationInfo(
                         nri.mVoiceSpecificInfo);
             }
+            mIsNonTerrestrialNetwork = nri.mIsNonTerrestrialNetwork;
         }
 
         /**
@@ -1111,6 +1174,18 @@
         }
 
         /**
+         * Set whether the network is a non-terrestrial network.
+         *
+         * @param isNonTerrestrialNetwork {@code true} if network is a non-terrestrial network
+         *                                            else {@code false}.
+         * @return The builder.
+         */
+        public @NonNull Builder setIsNonTerrestrialNetwork(boolean isNonTerrestrialNetwork) {
+            mIsNonTerrestrialNetwork = isNonTerrestrialNetwork;
+            return this;
+        }
+
+        /**
          * Build the NetworkRegistrationInfo.
          * @return the NetworkRegistrationInfo object.
          * @hide
@@ -1120,7 +1195,7 @@
             return new NetworkRegistrationInfo(mDomain, mTransportType, mNetworkRegistrationState,
                     mAccessNetworkTechnology, mRejectCause, mEmergencyOnly, mAvailableServices,
                     mCellIdentity, mRplmn, mVoiceSpecificRegistrationInfo,
-                    mDataSpecificRegistrationInfo);
+                    mDataSpecificRegistrationInfo, mIsNonTerrestrialNetwork);
         }
     }
 }
diff --git a/telephony/java/android/telephony/NetworkScanRequest.java b/telephony/java/android/telephony/NetworkScanRequest.java
index 65c2146..3285bc3 100644
--- a/telephony/java/android/telephony/NetworkScanRequest.java
+++ b/telephony/java/android/telephony/NetworkScanRequest.java
@@ -17,6 +17,8 @@
 package android.telephony;
 
 import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -24,6 +26,7 @@
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Objects;
 
 /**
  * Defines a request to perform a network scan.
@@ -108,6 +111,7 @@
     private int mIncrementalResultsPeriodicity;
 
     /** Describes the radio access technologies with bands or channels that need to be scanned. */
+    @Nullable
     private RadioAccessSpecifier[] mSpecifiers;
 
     /**
@@ -117,6 +121,7 @@
      * If list not sent, search to be completed till end and all PLMNs found to be reported.
      * Max size of array is MAX_MCC_MNC_LIST_SIZE
      */
+    @NonNull
     private ArrayList<String> mMccMncs;
 
     /**
@@ -240,27 +245,20 @@
     }
 
     @Override
-    public boolean equals (Object o) {
-        NetworkScanRequest nsr;
+    public boolean equals(Object other) {
+        if (this == other) return true;
 
-        try {
-            nsr = (NetworkScanRequest) o;
-        } catch (ClassCastException ex) {
-            return false;
-        }
+        if (!(other instanceof NetworkScanRequest)) return false;
 
-        if (o == null) {
-            return false;
-        }
+        NetworkScanRequest nsr = (NetworkScanRequest) other;
 
-        return (mScanType == nsr.mScanType
+        return mScanType == nsr.mScanType
                 && Arrays.equals(mSpecifiers, nsr.mSpecifiers)
                 && mSearchPeriodicity == nsr.mSearchPeriodicity
                 && mMaxSearchTime == nsr.mMaxSearchTime
                 && mIncrementalResults == nsr.mIncrementalResults
                 && mIncrementalResultsPeriodicity == nsr.mIncrementalResultsPeriodicity
-                && (((mMccMncs != null)
-                && mMccMncs.equals(nsr.mMccMncs))));
+                && Objects.equals(mMccMncs, nsr.mMccMncs);
     }
 
     @Override
diff --git a/telephony/java/android/telephony/PhoneNumberUtils.java b/telephony/java/android/telephony/PhoneNumberUtils.java
index cf0561d..cee2efb 100644
--- a/telephony/java/android/telephony/PhoneNumberUtils.java
+++ b/telephony/java/android/telephony/PhoneNumberUtils.java
@@ -111,6 +111,14 @@
     private static final String BCD_EF_ADN_EXTENDED = "*#,N;";
     private static final String BCD_CALLED_PARTY_EXTENDED = "*#abc";
 
+    private static final String PREFIX_WPS = "*272";
+
+    // WPS prefix when CLIR is being activated for the call.
+    private static final String PREFIX_WPS_CLIR_ACTIVATE = "*31#*272";
+
+    // WPS prefix when CLIR is being deactivated for the call.
+    private static final String PREFIX_WPS_CLIR_DEACTIVATE = "#31#*272";
+
     /*
      * global-phone-number = ["+"] 1*( DIGIT / written-sep )
      * written-sep         = ("-"/".")
@@ -2943,4 +2951,15 @@
             return false;
         }
     }
+
+    /**
+     * Check if the number is for Wireless Priority Service call.
+     * @param number  The phone number used for WPS call.
+     * @return {@code true} if number matches WPS pattern and {@code false} otherwise.
+     */
+    public static boolean isWpsCallNumber(@Nullable String number) {
+        return (number != null) && (number.startsWith(PREFIX_WPS)
+                || number.startsWith(PREFIX_WPS_CLIR_ACTIVATE)
+                || number.startsWith(PREFIX_WPS_CLIR_DEACTIVATE));
+    }
 }
diff --git a/telephony/java/android/telephony/PreciseDataConnectionState.java b/telephony/java/android/telephony/PreciseDataConnectionState.java
index 2670b03..7f1c14b 100644
--- a/telephony/java/android/telephony/PreciseDataConnectionState.java
+++ b/telephony/java/android/telephony/PreciseDataConnectionState.java
@@ -35,6 +35,7 @@
 import android.telephony.Annotation.NetworkType;
 import android.telephony.data.ApnSetting;
 import android.telephony.data.DataCallResponse;
+import android.telephony.data.Qos;
 
 import com.android.internal.telephony.util.TelephonyUtils;
 
@@ -64,6 +65,7 @@
     private final @DataFailureCause int mFailCause;
     private final LinkProperties mLinkProperties;
     private final ApnSetting mApnSetting;
+    private final Qos mDefaultQos;
 
     /**
      * Constructor
@@ -85,7 +87,7 @@
                         .setApnTypeBitmask(apnTypes)
                         .setApnName(apn)
                         .setEntryName(apn)
-                        .build());
+                        .build(), null);
     }
 
 
@@ -101,11 +103,13 @@
      *        code indicating the cause of the failure.
      * @param apnSetting If there is a valid APN for this Data Connection, then the APN Settings;
      *        if there is no valid APN setting for the specific type, then this will be null
+     * @param defaultQos If there is a valid QoS for the default bearer supporting this data call,
+     *        (supported for LTE and NR), then this is specified. Otherwise it should be null.
      */
     private PreciseDataConnectionState(@TransportType int transportType, int id,
             @DataState int state, @NetworkType int networkType,
             @Nullable LinkProperties linkProperties, @DataFailureCause int failCause,
-            @Nullable ApnSetting apnSetting) {
+            @Nullable ApnSetting apnSetting, @Nullable Qos defaultQos) {
         mTransportType = transportType;
         mId = id;
         mState = state;
@@ -113,6 +117,7 @@
         mLinkProperties = linkProperties;
         mFailCause = failCause;
         mApnSetting = apnSetting;
+        mDefaultQos = defaultQos;
     }
 
     /**
@@ -125,9 +130,16 @@
         mId = in.readInt();
         mState = in.readInt();
         mNetworkType = in.readInt();
-        mLinkProperties = in.readParcelable(LinkProperties.class.getClassLoader(), android.net.LinkProperties.class);
+        mLinkProperties = in.readParcelable(
+                LinkProperties.class.getClassLoader(),
+                android.net.LinkProperties.class);
         mFailCause = in.readInt();
-        mApnSetting = in.readParcelable(ApnSetting.class.getClassLoader(), android.telephony.data.ApnSetting.class);
+        mApnSetting = in.readParcelable(
+                ApnSetting.class.getClassLoader(),
+                android.telephony.data.ApnSetting.class);
+        mDefaultQos = in.readParcelable(
+                Qos.class.getClassLoader(),
+                android.telephony.data.Qos.class);
     }
 
     /**
@@ -263,6 +275,20 @@
         return mApnSetting;
     }
 
+    /**
+     * Return the QoS for the default bearer of this data connection.
+     *
+     * @return the default QoS if known or {@code null} if it is unknown. If the value is reported
+     * for LTE, then it will be an {@link android.telephony.data.EpsQos EpsQos}. If the value is
+     * reported for 5G, then it will be an {@link android.telehpony.data.NrQos NrQos}. Otherwise it
+     * shall always be {@code null}.
+     *
+     * @hide
+     */
+    public @Nullable Qos getDefaultQos() {
+        return mDefaultQos;
+    }
+
     @Override
     public int describeContents() {
         return 0;
@@ -277,6 +303,7 @@
         out.writeParcelable(mLinkProperties, flags);
         out.writeInt(mFailCause);
         out.writeParcelable(mApnSetting, flags);
+        out.writeParcelable(mDefaultQos, flags);
     }
 
     public static final @NonNull Parcelable.Creator<PreciseDataConnectionState> CREATOR
@@ -294,7 +321,7 @@
     @Override
     public int hashCode() {
         return Objects.hash(mTransportType, mId, mState, mNetworkType, mFailCause,
-                mLinkProperties, mApnSetting);
+                mLinkProperties, mApnSetting, mDefaultQos);
     }
 
 
@@ -309,7 +336,8 @@
                 && mNetworkType == that.mNetworkType
                 && mFailCause == that.mFailCause
                 && Objects.equals(mLinkProperties, that.mLinkProperties)
-                && Objects.equals(mApnSetting, that.mApnSetting);
+                && Objects.equals(mApnSetting, that.mApnSetting)
+                && Objects.equals(mDefaultQos, that.mDefaultQos);
     }
 
     @NonNull
@@ -324,6 +352,7 @@
         sb.append(", network type: " + TelephonyManager.getNetworkTypeName(mNetworkType));
         sb.append(", APN Setting: " + mApnSetting);
         sb.append(", link properties: " + mLinkProperties);
+        sb.append(", default QoS: " + mDefaultQos);
         sb.append(", fail cause: " + DataFailCause.toString(mFailCause));
 
         return sb.toString();
@@ -351,7 +380,7 @@
         private @NetworkType int mNetworkType = TelephonyManager.NETWORK_TYPE_UNKNOWN;
 
         /** If the data connection is connected, the properties of the connection */
-        private @Nullable LinkProperties mLinkProperties = null;
+        private @Nullable LinkProperties mLinkProperties;
 
         /**
          * In case a procedure related to this data connection fails, a non-zero error code
@@ -360,7 +389,10 @@
         private @DataFailureCause int mFailCause = DataFailCause.NONE;
 
         /** The APN Setting for this data connection */
-        private @Nullable ApnSetting mApnSetting = null;
+        private @Nullable ApnSetting mApnSetting;
+
+        /** The Default QoS for this EPS/5GS bearer or null otherwise */
+        private @Nullable Qos mDefaultQos;
 
         /**
          * Set the transport type of the data connection.
@@ -441,13 +473,26 @@
         }
 
         /**
+         * Set the default QoS for this data connection.
+         *
+         * @param qos The qos information, if any, associated with the default bearer of the
+         * data connection.
+         * @return The builder
+         * @hide
+         */
+        public @NonNull Builder setDefaultQos(@Nullable Qos qos) {
+            mDefaultQos = qos;
+            return this;
+        }
+
+        /**
          * Build the {@link PreciseDataConnectionState} instance.
          *
          * @return The {@link PreciseDataConnectionState} instance
          */
         public PreciseDataConnectionState build() {
             return new PreciseDataConnectionState(mTransportType, mId, mState, mNetworkType,
-                    mLinkProperties, mFailCause, mApnSetting);
+                    mLinkProperties, mFailCause, mApnSetting, mDefaultQos);
         }
     }
 }
diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java
index 523d0b0..5b8848c 100644
--- a/telephony/java/android/telephony/ServiceState.java
+++ b/telephony/java/android/telephony/ServiceState.java
@@ -2250,4 +2250,18 @@
             return false;
         }
     }
+
+    /**
+     * Get whether device is connected to a non-terrestrial network.
+     *
+     * @return {@code true} if device is connected to a non-terrestrial network else {@code false}.
+     */
+    public boolean isUsingNonTerrestrialNetwork() {
+        synchronized (mNetworkRegistrationInfos) {
+            for (NetworkRegistrationInfo nri : mNetworkRegistrationInfos) {
+                if (nri.isNonTerrestrialNetwork()) return true;
+            }
+        }
+        return false;
+    }
 }
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 64e4356..c0f4008 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -35,6 +35,9 @@
 import android.annotation.SystemService;
 import android.app.PendingIntent;
 import android.app.PropertyInvalidatedCache;
+import android.compat.Compatibility;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledAfter;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.Intent;
@@ -85,6 +88,7 @@
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
+import java.util.Objects;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.Executor;
 import java.util.function.Consumer;
@@ -1322,41 +1326,61 @@
      * for #onSubscriptionsChanged to be invoked.
      */
     public static class OnSubscriptionsChangedListener {
-        private class OnSubscriptionsChangedListenerHandler extends Handler {
-            OnSubscriptionsChangedListenerHandler() {
-                super();
-            }
-
-            OnSubscriptionsChangedListenerHandler(Looper looper) {
-                super(looper);
-            }
-        }
 
         /**
-         * Posted executor callback on the handler associated with a given looper.
-         * The looper can be the calling thread's looper or the looper passed from the
-         * constructor {@link #OnSubscriptionsChangedListener(Looper)}.
+         * After {@link Build.VERSION_CODES.Q}, it is no longer necessary to instantiate a
+         * Handler inside of the OnSubscriptionsChangedListener in all cases, so it will only
+         * be done for callers that do not supply an Executor.
          */
-        private final HandlerExecutor mExecutor;
+        @ChangeId
+        @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
+        private static final long LAZY_INITIALIZE_SUBSCRIPTIONS_CHANGED_HANDLER = 278814050L;
+
+        /**
+         * For backwards compatibility reasons, stashes the Looper associated with the thread
+         * context in which this listener was created.
+         */
+        private final Looper mCreatorLooper;
 
         /**
          * @hide
          */
-        public HandlerExecutor getHandlerExecutor() {
-            return mExecutor;
+        public Looper getCreatorLooper() {
+            return mCreatorLooper;
         }
 
+        /**
+         * Create an OnSubscriptionsChangedListener.
+         *
+         * For callers targeting {@link Build.VERSION_CODES.P} or earlier, this can only be called
+         * on a thread that already has a prepared Looper. Callers targeting Q or later should
+         * subsequently use {@link SubscriptionManager#addOnSubscriptionsChangedListener(
+         * Executor, OnSubscriptionsChangedListener)}.
+         *
+         * On OS versions prior to {@link Build.VERSION_CODES.V} callers should assume that this
+         * call will fail if invoked on a thread that does not already have a prepared looper.
+         */
         public OnSubscriptionsChangedListener() {
-            mExecutor = new HandlerExecutor(new OnSubscriptionsChangedListenerHandler());
+            mCreatorLooper = Looper.myLooper();
+            if (mCreatorLooper == null
+                    && !Compatibility.isChangeEnabled(
+                            LAZY_INITIALIZE_SUBSCRIPTIONS_CHANGED_HANDLER)) {
+                // matches the implementation of Handler
+                throw new RuntimeException(
+                        "Can't create handler inside thread "
+                        + Thread.currentThread()
+                        + " that has not called Looper.prepare()");
+            }
         }
 
         /**
          * Allow a listener to be created with a custom looper
-         * @param looper the looper that the underlining handler should run on
+         * @param looper the non-null Looper that the underlining handler should run on
          * @hide
          */
-        public OnSubscriptionsChangedListener(Looper looper) {
-            mExecutor = new HandlerExecutor(new OnSubscriptionsChangedListenerHandler(looper));
+        public OnSubscriptionsChangedListener(@NonNull Looper looper) {
+            Objects.requireNonNull(looper);
+            mCreatorLooper = looper;
         }
 
         /**
@@ -1423,7 +1447,9 @@
     @Deprecated
     public void addOnSubscriptionsChangedListener(OnSubscriptionsChangedListener listener) {
         if (listener == null) return;
-        addOnSubscriptionsChangedListener(listener.mExecutor, listener);
+
+        addOnSubscriptionsChangedListener(
+                new HandlerExecutor(new Handler(listener.getCreatorLooper())), listener);
     }
 
     /**
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 2a6099a..f1ee76e 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -5268,7 +5268,11 @@
         try {
             ITelephony telephony = getITelephony();
             if (telephony != null) {
-                return telephony.getMergedImsisFromGroup(getSubId(), getOpPackageName());
+                String[] mergedImsisFromGroup =
+                        telephony.getMergedImsisFromGroup(getSubId(), getOpPackageName());
+                if (mergedImsisFromGroup != null) {
+                    return mergedImsisFromGroup;
+                }
             }
         } catch (RemoteException ex) {
         }
@@ -10650,12 +10654,20 @@
      * no reason to power it off. When any of the voters want to power it off, it will be turned
      * off. In case of emergency, the radio will be turned on even if there are some reasons for
      * powering it off, and these radio off votes will be cleared.
-     * Multiple apps can vote for the same reason and the last vote will take effect. Each app is
-     * responsible for its vote. A powering-off vote of a reason will be maintained until it is
-     * cleared by calling {@link clearRadioPowerOffForReason} for that reason, or an emergency call
-     * is made, or the device is rebooted. When an app comes backup from a crash, it needs to make
-     * sure if its vote is as expected. An app can use the API {@link getRadioPowerOffReasons} to
-     * check its vote.
+     * <p>
+     * Each API call is for one reason. However, an app can call the API multiple times for multiple
+     * reasons. Multiple apps can vote for the same reason but the vote of one app does not affect
+     * the vote of another app.
+     * <p>
+     * Each app is responsible for its vote. A powering-off vote for a reason of an app will be
+     * maintained until it is cleared by calling {@link #clearRadioPowerOffForReason(int)} for that
+     * reason by the app, or an emergency call is made, or the device is rebooted. When an app
+     * comes backup from a crash, it needs to make sure if its vote is as expected. An app can use
+     * the API {@link #getRadioPowerOffReasons()} to check its votes. Votes won't be removed when
+     * an app crashes.
+     * <p>
+     * User setting for power state is persistent across device reboots. This applies to all users,
+     * callers must be careful to update the off reasons when the current user changes.
      *
      * @param reason The reason for powering off radio.
      * @throws SecurityException if the caller does not have MODIFY_PHONE_STATE permission.
@@ -10712,10 +10724,10 @@
     }
 
     /**
-     * Get reasons for powering off radio, as requested by {@link requestRadioPowerOffForReason}.
-     * If the reason set is empty, the radio is on in all cases.
+     * Get reasons for powering off radio of the calling app, as requested by
+     * {@link #requestRadioPowerOffForReason(int)}.
      *
-     * @return Set of reasons for powering off radio.
+     * @return Set of reasons for powering off radio of the calling app.
      * @throws SecurityException if the caller does not have READ_PRIVILEGED_PHONE_STATE permission.
      * @throws IllegalStateException if the Telephony service is not currently available.
      *
@@ -11279,29 +11291,6 @@
     }
 
     /**
-     * Returns the result and response from RIL for oem request
-     *
-     * @param oemReq the data is sent to ril.
-     * @param oemResp the respose data from RIL.
-     * @return negative value request was not handled or get error
-     *         0 request was handled succesfully, but no response data
-     *         positive value success, data length of response
-     * @hide
-     * @deprecated OEM needs a vendor-extension hal and their apps should use that instead
-     */
-    @Deprecated
-    public int invokeOemRilRequestRaw(byte[] oemReq, byte[] oemResp) {
-        try {
-            ITelephony telephony = getITelephony();
-            if (telephony != null)
-                return telephony.invokeOemRilRequestRaw(oemReq, oemResp);
-        } catch (RemoteException ex) {
-        } catch (NullPointerException ex) {
-        }
-        return -1;
-    }
-
-    /**
      * @deprecated Use {@link android.telephony.ims.ImsMmTelManager#setVtSettingEnabled(boolean)}
      * instead.
      * @hide
@@ -15163,6 +15152,14 @@
     @TestApi
     public static final int HAL_SERVICE_IMS = 7;
 
+    /**
+     * HAL service type that supports the HAL APIs implementation of IRadioSatellite
+     * {@link RadioSatelliteProxy}
+     * @hide
+     */
+    @TestApi
+    public static final int HAL_SERVICE_SATELLITE = 8;
+
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = {"HAL_SERVICE_"},
@@ -15175,6 +15172,7 @@
                     HAL_SERVICE_SIM,
                     HAL_SERVICE_VOICE,
                     HAL_SERVICE_IMS,
+                    HAL_SERVICE_SATELLITE
             })
     public @interface HalService {}
 
@@ -17608,6 +17606,15 @@
     public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_PENDING_NETWORK_SETUP = 15;
 
     /**
+     * Purchase premium capability failed because the user disabled the feature.
+     * Subsequent attempts will be throttled for the amount of time specified by
+     * {@link CarrierConfigManager
+     * #KEY_PREMIUM_CAPABILITY_NOTIFICATION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG}
+     * and return {@link #PURCHASE_PREMIUM_CAPABILITY_RESULT_THROTTLED}.
+     */
+    public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_DISABLED = 16;
+
+    /**
      * Results of the purchase premium capability request.
      * @hide
      */
@@ -17626,7 +17633,8 @@
             PURCHASE_PREMIUM_CAPABILITY_RESULT_NETWORK_NOT_AVAILABLE,
             PURCHASE_PREMIUM_CAPABILITY_RESULT_ENTITLEMENT_CHECK_FAILED,
             PURCHASE_PREMIUM_CAPABILITY_RESULT_NOT_DEFAULT_DATA_SUBSCRIPTION,
-            PURCHASE_PREMIUM_CAPABILITY_RESULT_PENDING_NETWORK_SETUP})
+            PURCHASE_PREMIUM_CAPABILITY_RESULT_PENDING_NETWORK_SETUP,
+            PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_DISABLED})
     public @interface PurchasePremiumCapabilityResult {}
 
     /**
diff --git a/telephony/java/android/telephony/data/ApnSetting.java b/telephony/java/android/telephony/data/ApnSetting.java
index 28ea5a6..1b5c537 100644
--- a/telephony/java/android/telephony/data/ApnSetting.java
+++ b/telephony/java/android/telephony/data/ApnSetting.java
@@ -22,7 +22,7 @@
 import android.annotation.SystemApi;
 import android.content.ContentValues;
 import android.database.Cursor;
-import android.hardware.radio.V1_5.ApnTypes;
+import android.hardware.radio.data.ApnTypes;
 import android.net.Uri;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -116,12 +116,11 @@
     /** APN type for XCAP. */
     public static final int TYPE_XCAP = ApnTypes.XCAP;
     /** APN type for VSIM. */
-    public static final int TYPE_VSIM = 1 << 12;  // TODO: Refer to ApnTypes.VSIM
+    public static final int TYPE_VSIM = ApnTypes.VSIM;
     /** APN type for BIP. */
-    public static final int TYPE_BIP = 1 << 13;   // TODO: Refer to ApnTypes.BIP
+    public static final int TYPE_BIP = ApnTypes.BIP;
     /** APN type for ENTERPRISE. */
-    public static final int TYPE_ENTERPRISE = 1 << 14; //TODO: In future should be referenced from
-    // hardware.interfaces.radio.data.ApnTypes
+    public static final int TYPE_ENTERPRISE = ApnTypes.ENTERPRISE;
 
     /** @hide */
     @IntDef(flag = true, prefix = {"TYPE_"}, value = {
diff --git a/telephony/java/android/telephony/data/DataCallResponse.java b/telephony/java/android/telephony/data/DataCallResponse.java
index a834e2bb..c7f0c5f 100644
--- a/telephony/java/android/telephony/data/DataCallResponse.java
+++ b/telephony/java/android/telephony/data/DataCallResponse.java
@@ -36,8 +36,10 @@
 import java.lang.annotation.RetentionPolicy;
 import java.net.InetAddress;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
+import java.util.Set;
 
 /**
  * Description of the response of a setup data call connection request.
@@ -172,63 +174,57 @@
                             @Nullable List<InetAddress> dnsAddresses,
                             @Nullable List<InetAddress> gatewayAddresses,
                             @Nullable List<InetAddress> pcscfAddresses, int mtu) {
-        mCause = cause;
-        mSuggestedRetryTime = suggestedRetryTime;
-        mId = id;
-        mLinkStatus = linkStatus;
-        mProtocolType = protocolType;
-        mInterfaceName = (interfaceName == null) ? "" : interfaceName;
-        mAddresses = (addresses == null)
-                ? new ArrayList<>() : new ArrayList<>(addresses);
-        mDnsAddresses = (dnsAddresses == null)
-                ? new ArrayList<>() : new ArrayList<>(dnsAddresses);
-        mGatewayAddresses = (gatewayAddresses == null)
-                ? new ArrayList<>() : new ArrayList<>(gatewayAddresses);
-        mPcscfAddresses = (pcscfAddresses == null)
-                ? new ArrayList<>() : new ArrayList<>(pcscfAddresses);
-        mMtu = mMtuV4 = mMtuV6 = mtu;
-        mHandoverFailureMode = HANDOVER_FAILURE_MODE_LEGACY;
-        mPduSessionId = PDU_SESSION_ID_NOT_SET;
-        mDefaultQos = null;
-        mQosBearerSessions = new ArrayList<>();
-        mSliceInfo = null;
-        mTrafficDescriptors = new ArrayList<>();
+        this(cause, suggestedRetryTime, id,
+                linkStatus, protocolType,
+                interfaceName == null ? "" : interfaceName,
+                addresses == null ? Collections.emptyList() : addresses,
+                dnsAddresses == null ? Collections.emptyList() : dnsAddresses,
+                gatewayAddresses == null ? Collections.emptyList() : gatewayAddresses,
+                pcscfAddresses == null ? Collections.emptyList() : pcscfAddresses,
+                mtu, mtu /* mtuV4 */, mtu /* mtuV6 */,
+                HANDOVER_FAILURE_MODE_LEGACY, PDU_SESSION_ID_NOT_SET,
+                null /* defaultQos */, Collections.emptyList() /* qosBearerSessions */,
+                null /* sliceInfo */,
+                Collections.emptyList() /* trafficDescriptors */);
     }
 
     private DataCallResponse(@DataFailureCause int cause, long suggestedRetryTime, int id,
             @LinkStatus int linkStatus, @ProtocolType int protocolType,
-            @Nullable String interfaceName, @Nullable List<LinkAddress> addresses,
-            @Nullable List<InetAddress> dnsAddresses, @Nullable List<InetAddress> gatewayAddresses,
-            @Nullable List<InetAddress> pcscfAddresses, int mtu, int mtuV4, int mtuV6,
+            @NonNull String interfaceName, @NonNull List<LinkAddress> addresses,
+            @NonNull List<InetAddress> dnsAddresses, @NonNull List<InetAddress> gatewayAddresses,
+            @NonNull List<InetAddress> pcscfAddresses, int mtu, int mtuV4, int mtuV6,
             @HandoverFailureMode int handoverFailureMode, int pduSessionId,
-            @Nullable Qos defaultQos, @Nullable List<QosBearerSession> qosBearerSessions,
+            @Nullable Qos defaultQos, @NonNull List<QosBearerSession> qosBearerSessions,
             @Nullable NetworkSliceInfo sliceInfo,
-            @Nullable List<TrafficDescriptor> trafficDescriptors) {
+            @NonNull List<TrafficDescriptor> trafficDescriptors) {
         mCause = cause;
         mSuggestedRetryTime = suggestedRetryTime;
         mId = id;
         mLinkStatus = linkStatus;
         mProtocolType = protocolType;
-        mInterfaceName = (interfaceName == null) ? "" : interfaceName;
-        mAddresses = (addresses == null)
-                ? new ArrayList<>() : new ArrayList<>(addresses);
-        mDnsAddresses = (dnsAddresses == null)
-                ? new ArrayList<>() : new ArrayList<>(dnsAddresses);
-        mGatewayAddresses = (gatewayAddresses == null)
-                ? new ArrayList<>() : new ArrayList<>(gatewayAddresses);
-        mPcscfAddresses = (pcscfAddresses == null)
-                ? new ArrayList<>() : new ArrayList<>(pcscfAddresses);
+        mInterfaceName = interfaceName;
+        mAddresses = new ArrayList<>(addresses);
+        mDnsAddresses = new ArrayList<>(dnsAddresses);
+        mGatewayAddresses = new ArrayList<>(gatewayAddresses);
+        mPcscfAddresses = new ArrayList<>(pcscfAddresses);
         mMtu = mtu;
         mMtuV4 = mtuV4;
         mMtuV6 = mtuV6;
         mHandoverFailureMode = handoverFailureMode;
         mPduSessionId = pduSessionId;
         mDefaultQos = defaultQos;
-        mQosBearerSessions = (qosBearerSessions == null)
-                ? new ArrayList<>() : new ArrayList<>(qosBearerSessions);
+        mQosBearerSessions = new ArrayList<>(qosBearerSessions);
         mSliceInfo = sliceInfo;
-        mTrafficDescriptors = (trafficDescriptors == null)
-                ? new ArrayList<>() : new ArrayList<>(trafficDescriptors);
+        mTrafficDescriptors = new ArrayList<>(trafficDescriptors);
+
+        if (mLinkStatus == LINK_STATUS_ACTIVE
+                || mLinkStatus == LINK_STATUS_DORMANT) {
+            Objects.requireNonNull(
+                    mInterfaceName, "Active data calls must be on a valid interface!");
+            if (mCause != DataFailCause.NONE) {
+                throw new IllegalStateException("Active data call must not have a failure!");
+            }
+        }
     }
 
     /** @hide */
@@ -241,24 +237,39 @@
         mProtocolType = source.readInt();
         mInterfaceName = source.readString();
         mAddresses = new ArrayList<>();
-        source.readList(mAddresses, LinkAddress.class.getClassLoader(), android.net.LinkAddress.class);
+        source.readList(mAddresses,
+                LinkAddress.class.getClassLoader(),
+                android.net.LinkAddress.class);
         mDnsAddresses = new ArrayList<>();
-        source.readList(mDnsAddresses, InetAddress.class.getClassLoader(), java.net.InetAddress.class);
+        source.readList(mDnsAddresses,
+                InetAddress.class.getClassLoader(),
+                java.net.InetAddress.class);
         mGatewayAddresses = new ArrayList<>();
-        source.readList(mGatewayAddresses, InetAddress.class.getClassLoader(), java.net.InetAddress.class);
+        source.readList(mGatewayAddresses,
+                InetAddress.class.getClassLoader(),
+                java.net.InetAddress.class);
         mPcscfAddresses = new ArrayList<>();
-        source.readList(mPcscfAddresses, InetAddress.class.getClassLoader(), java.net.InetAddress.class);
+        source.readList(mPcscfAddresses,
+                InetAddress.class.getClassLoader(),
+                java.net.InetAddress.class);
         mMtu = source.readInt();
         mMtuV4 = source.readInt();
         mMtuV6 = source.readInt();
         mHandoverFailureMode = source.readInt();
         mPduSessionId = source.readInt();
-        mDefaultQos = source.readParcelable(Qos.class.getClassLoader(), android.telephony.data.Qos.class);
+        mDefaultQos = source.readParcelable(Qos.class.getClassLoader(),
+                android.telephony.data.Qos.class);
         mQosBearerSessions = new ArrayList<>();
-        source.readList(mQosBearerSessions, QosBearerSession.class.getClassLoader(), android.telephony.data.QosBearerSession.class);
-        mSliceInfo = source.readParcelable(NetworkSliceInfo.class.getClassLoader(), android.telephony.data.NetworkSliceInfo.class);
+        source.readList(mQosBearerSessions,
+                QosBearerSession.class.getClassLoader(),
+                android.telephony.data.QosBearerSession.class);
+        mSliceInfo = source.readParcelable(
+                NetworkSliceInfo.class.getClassLoader(),
+                android.telephony.data.NetworkSliceInfo.class);
         mTrafficDescriptors = new ArrayList<>();
-        source.readList(mTrafficDescriptors, TrafficDescriptor.class.getClassLoader(), android.telephony.data.TrafficDescriptor.class);
+        source.readList(mTrafficDescriptors,
+                TrafficDescriptor.class.getClassLoader(),
+                android.telephony.data.TrafficDescriptor.class);
     }
 
     /**
@@ -322,28 +333,36 @@
      * @return A list of addresses of this data connection.
      */
     @NonNull
-    public List<LinkAddress> getAddresses() { return mAddresses; }
+    public List<LinkAddress> getAddresses() {
+        return Collections.unmodifiableList(mAddresses);
+    }
 
     /**
      * @return A list of DNS server addresses, e.g., "192.0.1.3" or
      * "192.0.1.11 2001:db8::1". Empty list if no dns server addresses returned.
      */
     @NonNull
-    public List<InetAddress> getDnsAddresses() { return mDnsAddresses; }
+    public List<InetAddress> getDnsAddresses() {
+        return Collections.unmodifiableList(mDnsAddresses);
+    }
 
     /**
      * @return A list of default gateway addresses, e.g., "192.0.1.3" or
      * "192.0.1.11 2001:db8::1". Empty list if the addresses represent point to point connections.
      */
     @NonNull
-    public List<InetAddress> getGatewayAddresses() { return mGatewayAddresses; }
+    public List<InetAddress> getGatewayAddresses() {
+        return Collections.unmodifiableList(mGatewayAddresses);
+    }
 
     /**
      * @return A list of Proxy Call State Control Function address via PCO (Protocol Configuration
      * Option) for IMS client.
      */
     @NonNull
-    public List<InetAddress> getPcscfAddresses() { return mPcscfAddresses; }
+    public List<InetAddress> getPcscfAddresses() {
+        return Collections.unmodifiableList(mPcscfAddresses);
+    }
 
     /**
      * @return MTU (maximum transmission unit) in bytes received from network. Zero or negative
@@ -404,7 +423,7 @@
      */
     @NonNull
     public List<QosBearerSession> getQosBearerSessions() {
-        return mQosBearerSessions;
+        return Collections.unmodifiableList(mQosBearerSessions);
     }
 
     /**
@@ -420,7 +439,7 @@
      */
     @NonNull
     public List<TrafficDescriptor> getTrafficDescriptors() {
-        return mTrafficDescriptors;
+        return Collections.unmodifiableList(mTrafficDescriptors);
     }
 
     @NonNull
@@ -461,22 +480,6 @@
 
         DataCallResponse other = (DataCallResponse) o;
 
-        final boolean isQosSame = (mDefaultQos == null || other.mDefaultQos == null)
-                ? mDefaultQos == other.mDefaultQos
-                : mDefaultQos.equals(other.mDefaultQos);
-
-        final boolean isQosBearerSessionsSame =
-                (mQosBearerSessions == null || other.mQosBearerSessions == null)
-                ? mQosBearerSessions == other.mQosBearerSessions
-                : (mQosBearerSessions.size() == other.mQosBearerSessions.size()
-                        && mQosBearerSessions.containsAll(other.mQosBearerSessions));
-
-        final boolean isTrafficDescriptorsSame =
-                (mTrafficDescriptors == null || other.mTrafficDescriptors == null)
-                ? mTrafficDescriptors == other.mTrafficDescriptors
-                : (mTrafficDescriptors.size() == other.mTrafficDescriptors.size()
-                        && mTrafficDescriptors.containsAll(other.mTrafficDescriptors));
-
         return mCause == other.mCause
                 && mSuggestedRetryTime == other.mSuggestedRetryTime
                 && mId == other.mId
@@ -496,43 +499,21 @@
                 && mMtuV6 == other.mMtuV6
                 && mHandoverFailureMode == other.mHandoverFailureMode
                 && mPduSessionId == other.mPduSessionId
-                && isQosSame
-                && isQosBearerSessionsSame
+                && Objects.equals(mDefaultQos, other.mDefaultQos)
+                && mQosBearerSessions.size() == other.mQosBearerSessions.size() // non-null
+                && mQosBearerSessions.containsAll(other.mQosBearerSessions) // non-null
                 && Objects.equals(mSliceInfo, other.mSliceInfo)
-                && isTrafficDescriptorsSame;
+                && mTrafficDescriptors.size() == other.mTrafficDescriptors.size() // non-null
+                && mTrafficDescriptors.containsAll(other.mTrafficDescriptors); // non-null
     }
 
     @Override
     public int hashCode() {
-        // Generate order-independent hashes for lists
-        int addressesHash = mAddresses.stream()
-                .map(LinkAddress::hashCode)
-                .mapToInt(Integer::intValue)
-                .sum();
-        int dnsAddressesHash = mDnsAddresses.stream()
-                .map(InetAddress::hashCode)
-                .mapToInt(Integer::intValue)
-                .sum();
-        int gatewayAddressesHash = mGatewayAddresses.stream()
-                .map(InetAddress::hashCode)
-                .mapToInt(Integer::intValue)
-                .sum();
-        int pcscfAddressesHash = mPcscfAddresses.stream()
-                .map(InetAddress::hashCode)
-                .mapToInt(Integer::intValue)
-                .sum();
-        int qosBearerSessionsHash = mQosBearerSessions.stream()
-                .map(QosBearerSession::hashCode)
-                .mapToInt(Integer::intValue)
-                .sum();
-        int trafficDescriptorsHash = mTrafficDescriptors.stream()
-                .map(TrafficDescriptor::hashCode)
-                .mapToInt(Integer::intValue)
-                .sum();
         return Objects.hash(mCause, mSuggestedRetryTime, mId, mLinkStatus, mProtocolType,
-                mInterfaceName, addressesHash, dnsAddressesHash, gatewayAddressesHash,
-                pcscfAddressesHash, mMtu, mMtuV4, mMtuV6, mHandoverFailureMode, mPduSessionId,
-                mDefaultQos, qosBearerSessionsHash, mSliceInfo, trafficDescriptorsHash);
+                mInterfaceName, Set.copyOf(mAddresses), Set.copyOf(mDnsAddresses),
+                Set.copyOf(mGatewayAddresses), Set.copyOf(mPcscfAddresses), mMtu, mMtuV4, mMtuV6,
+                mHandoverFailureMode, mPduSessionId, mDefaultQos, Set.copyOf(mQosBearerSessions),
+                mSliceInfo, Set.copyOf(mTrafficDescriptors));
     }
 
     @Override
@@ -557,15 +538,7 @@
         dest.writeInt(mMtuV6);
         dest.writeInt(mHandoverFailureMode);
         dest.writeInt(mPduSessionId);
-        if (mDefaultQos != null) {
-            if (mDefaultQos.getType() == Qos.QOS_TYPE_EPS) {
-                dest.writeParcelable((EpsQos) mDefaultQos, flags);
-            } else {
-                dest.writeParcelable((NrQos) mDefaultQos, flags);
-            }
-        } else {
-            dest.writeParcelable(null, flags);
-        }
+        dest.writeParcelable(mDefaultQos, flags);
         dest.writeList(mQosBearerSessions);
         dest.writeParcelable(mSliceInfo, flags);
         dest.writeList(mTrafficDescriptors);
@@ -628,15 +601,15 @@
 
         private @ProtocolType int mProtocolType;
 
-        private String mInterfaceName;
+        private String mInterfaceName = "";
 
-        private List<LinkAddress> mAddresses;
+        private List<LinkAddress> mAddresses = Collections.emptyList();
 
-        private List<InetAddress> mDnsAddresses;
+        private List<InetAddress> mDnsAddresses = Collections.emptyList();
 
-        private List<InetAddress> mGatewayAddresses;
+        private List<InetAddress> mGatewayAddresses = Collections.emptyList();
 
-        private List<InetAddress> mPcscfAddresses;
+        private List<InetAddress> mPcscfAddresses = Collections.emptyList();
 
         private int mMtu;
 
@@ -648,11 +621,11 @@
 
         private int mPduSessionId = PDU_SESSION_ID_NOT_SET;
 
-        private Qos mDefaultQos;
+        private @Nullable Qos mDefaultQos;
 
         private List<QosBearerSession> mQosBearerSessions = new ArrayList<>();
 
-        private NetworkSliceInfo mSliceInfo;
+        private @Nullable NetworkSliceInfo mSliceInfo;
 
         private List<TrafficDescriptor> mTrafficDescriptors = new ArrayList<>();
 
@@ -665,7 +638,9 @@
         /**
          * Set data call fail cause.
          *
-         * @param cause Data call fail cause. {@link DataFailCause#NONE} indicates no error.
+         * @param cause Data call fail cause. {@link DataFailCause#NONE} indicates no error, which
+         * is the only valid value for data calls that are {@link LINK_STATUS_ACTIVE} or
+         * {@link LINK_STATUS_DORMANT}.
          * @return The same instance of the builder.
          */
         public @NonNull Builder setCause(@DataFailureCause int cause) {
@@ -734,10 +709,13 @@
         /**
          * Set the network interface name.
          *
-         * @param interfaceName The network interface name (e.g. "rmnet_data1").
+         * @param interfaceName The network interface name (e.g. "rmnet_data1"). This value may not
+         * be null for valid data calls (those that are {@link LINK_STATUS_ACTIVE} or
+         * {@link LINK_STATUS_DORMANT}).
          * @return The same instance of the builder.
          */
-        public @NonNull Builder setInterfaceName(@NonNull String interfaceName) {
+        public @NonNull Builder setInterfaceName(@Nullable String interfaceName) {
+            if (interfaceName == null) interfaceName = "";
             mInterfaceName = interfaceName;
             return this;
         }
@@ -749,6 +727,7 @@
          * @return The same instance of the builder.
          */
         public @NonNull Builder setAddresses(@NonNull List<LinkAddress> addresses) {
+            Objects.requireNonNull(addresses);
             mAddresses = addresses;
             return this;
         }
@@ -760,6 +739,7 @@
          * @return The same instance of the builder.
          */
         public @NonNull Builder setDnsAddresses(@NonNull List<InetAddress> dnsAddresses) {
+            Objects.requireNonNull(dnsAddresses);
             mDnsAddresses = dnsAddresses;
             return this;
         }
@@ -771,6 +751,7 @@
          * @return The same instance of the builder.
          */
         public @NonNull Builder setGatewayAddresses(@NonNull List<InetAddress> gatewayAddresses) {
+            Objects.requireNonNull(gatewayAddresses);
             mGatewayAddresses = gatewayAddresses;
             return this;
         }
@@ -783,6 +764,7 @@
          * @return The same instance of the builder.
          */
         public @NonNull Builder setPcscfAddresses(@NonNull List<InetAddress> pcscfAddresses) {
+            Objects.requireNonNull(pcscfAddresses);
             mPcscfAddresses = pcscfAddresses;
             return this;
         }
diff --git a/telephony/java/android/telephony/data/Qos.java b/telephony/java/android/telephony/data/Qos.java
index 9c2a3bb..d7273b9 100644
--- a/telephony/java/android/telephony/data/Qos.java
+++ b/telephony/java/android/telephony/data/Qos.java
@@ -31,7 +31,7 @@
  *
  * @hide
  */
-public abstract class Qos {
+public abstract class Qos implements Parcelable {
 
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
@@ -136,8 +136,10 @@
 
     protected Qos(@NonNull Parcel source) {
         type = source.readInt();
-        downlink = source.readParcelable(QosBandwidth.class.getClassLoader(), android.telephony.data.Qos.QosBandwidth.class);
-        uplink = source.readParcelable(QosBandwidth.class.getClassLoader(), android.telephony.data.Qos.QosBandwidth.class);
+        downlink = source.readParcelable(
+                QosBandwidth.class.getClassLoader(), android.telephony.data.Qos.QosBandwidth.class);
+        uplink = source.readParcelable(
+                QosBandwidth.class.getClassLoader(), android.telephony.data.Qos.QosBandwidth.class);
     }
 
     /**
diff --git a/telephony/java/android/telephony/data/QosBearerFilter.java b/telephony/java/android/telephony/data/QosBearerFilter.java
index a0d9c1bd..baa160e 100644
--- a/telephony/java/android/telephony/data/QosBearerFilter.java
+++ b/telephony/java/android/telephony/data/QosBearerFilter.java
@@ -49,17 +49,13 @@
     public @interface QosProtocol {}
 
     public static final int QOS_PROTOCOL_UNSPECIFIED =
-            android.hardware.radio.V1_6.QosProtocol.UNSPECIFIED;
-    public static final int QOS_PROTOCOL_TCP = android.hardware.radio.V1_6.QosProtocol.TCP;
-    public static final int QOS_PROTOCOL_UDP = android.hardware.radio.V1_6.QosProtocol.UDP;
-    public static final int QOS_PROTOCOL_ESP = android.hardware.radio.V1_6.QosProtocol.ESP;
-    public static final int QOS_PROTOCOL_AH = android.hardware.radio.V1_6.QosProtocol.AH;
-    public static final int QOS_MIN_PORT = android.hardware.radio.V1_6.QosPortRange.MIN;
-    /**
-     * Hardcoded in place of android.hardware.radio.V1_6.QosPortRange.MAX as it
-     * returns -1 due to uint16_t to int conversion in java. (TODO: Fix the HAL)
-     */
-    public static final int QOS_MAX_PORT = 65535; // android.hardware.radio.V1_6.QosPortRange.MIN;
+            android.hardware.radio.data.QosFilter.PROTOCOL_UNSPECIFIED;
+    public static final int QOS_PROTOCOL_TCP = android.hardware.radio.data.QosFilter.PROTOCOL_TCP;
+    public static final int QOS_PROTOCOL_UDP = android.hardware.radio.data.QosFilter.PROTOCOL_UDP;
+    public static final int QOS_PROTOCOL_ESP = android.hardware.radio.data.QosFilter.PROTOCOL_ESP;
+    public static final int QOS_PROTOCOL_AH = android.hardware.radio.data.QosFilter.PROTOCOL_AH;
+    public static final int QOS_MIN_PORT = android.hardware.radio.data.PortRange.PORT_RANGE_MIN;
+    public static final int QOS_MAX_PORT = android.hardware.radio.data.PortRange.PORT_RANGE_MAX;
 
     private @QosProtocol int protocol;
 
@@ -78,11 +74,11 @@
     public @interface QosBearerFilterDirection {}
 
     public static final int QOS_FILTER_DIRECTION_DOWNLINK =
-            android.hardware.radio.V1_6.QosFilterDirection.DOWNLINK;
+            android.hardware.radio.data.QosFilter.DIRECTION_DOWNLINK;
     public static final int QOS_FILTER_DIRECTION_UPLINK =
-            android.hardware.radio.V1_6.QosFilterDirection.UPLINK;
+            android.hardware.radio.data.QosFilter.DIRECTION_UPLINK;
     public static final int QOS_FILTER_DIRECTION_BIDIRECTIONAL =
-            android.hardware.radio.V1_6.QosFilterDirection.BIDIRECTIONAL;
+            android.hardware.radio.data.QosFilter.DIRECTION_BIDIRECTIONAL;
 
     private @QosBearerFilterDirection int filterDirection;
 
diff --git a/telephony/java/android/telephony/emergency/EmergencyNumber.java b/telephony/java/android/telephony/emergency/EmergencyNumber.java
index 64bcf71..b429407 100644
--- a/telephony/java/android/telephony/emergency/EmergencyNumber.java
+++ b/telephony/java/android/telephony/emergency/EmergencyNumber.java
@@ -19,8 +19,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.TestApi;
-import android.hardware.radio.V1_4.EmergencyNumberSource;
-import android.hardware.radio.V1_4.EmergencyServiceCategory;
+import android.hardware.radio.voice.EmergencyServiceCategory;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.telephony.CarrierConfigManager;
@@ -172,13 +171,14 @@
      * Reference: 3gpp 22.101, Section 10 - Emergency Calls
      */
     public static final int EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING =
-            EmergencyNumberSource.NETWORK_SIGNALING;
+            android.hardware.radio.voice.EmergencyNumber.SOURCE_NETWORK_SIGNALING;
     /**
      * Bit-field which indicates the number is from the sim.
      *
      * Reference: 3gpp 22.101, Section 10 - Emergency Calls
      */
-    public static final int EMERGENCY_NUMBER_SOURCE_SIM = EmergencyNumberSource.SIM;
+    public static final int EMERGENCY_NUMBER_SOURCE_SIM =
+            android.hardware.radio.voice.EmergencyNumber.SOURCE_SIM;
     /**
      * Bit-field which indicates the number is from the platform-maintained database.
      */
@@ -192,7 +192,7 @@
     public static final int EMERGENCY_NUMBER_SOURCE_TEST =  1 << 5;
     /** Bit-field which indicates the number is from the modem config. */
     public static final int EMERGENCY_NUMBER_SOURCE_MODEM_CONFIG =
-            EmergencyNumberSource.MODEM_CONFIG;
+            android.hardware.radio.voice.EmergencyNumber.SOURCE_MODEM_CONFIG;
     /**
      * Bit-field which indicates the number is available as default.
      *
@@ -201,7 +201,8 @@
      *
      * Reference: 3gpp 22.101, Section 10 - Emergency Calls
      */
-    public static final int EMERGENCY_NUMBER_SOURCE_DEFAULT = EmergencyNumberSource.DEFAULT;
+    public static final int EMERGENCY_NUMBER_SOURCE_DEFAULT =
+            android.hardware.radio.voice.EmergencyNumber.SOURCE_DEFAULT;
 
     private static final Set<Integer> EMERGENCY_NUMBER_SOURCE_SET;
     static {
diff --git a/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java b/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
index d606f87..17d026c 100644
--- a/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
+++ b/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
@@ -421,7 +421,7 @@
      *
      * @param token The token to be used as a unique identifier for provisioning with satellite
      *              gateway.
-     * @param provisionData Data from the provisioning app that can be used by provisioning 
+     * @param provisionData Data from the provisioning app that can be used by provisioning
      *                      server
      * @param errorCallback The callback to receive the error code result of the operation.
      *
diff --git a/telephony/java/com/android/internal/telephony/ICarrierConfigLoader.aidl b/telephony/java/com/android/internal/telephony/ICarrierConfigLoader.aidl
index 1788bda..f6849de 100644
--- a/telephony/java/com/android/internal/telephony/ICarrierConfigLoader.aidl
+++ b/telephony/java/com/android/internal/telephony/ICarrierConfigLoader.aidl
@@ -30,12 +30,15 @@
     PersistableBundle getConfigForSubIdWithFeature(int subId, String callingPackage,
             String callingFeatureId);
 
+    @EnforcePermission("MODIFY_PHONE_STATE")
     void overrideConfig(int subId, in PersistableBundle overrides, boolean persistent);
 
     void notifyConfigChangedForSubId(int subId);
 
+    @EnforcePermission("MODIFY_PHONE_STATE")
     void updateConfigForPhoneId(int phoneId, String simState);
 
+    @EnforcePermission("READ_PRIVILEGED_PHONE_STATE")
     String getDefaultCarrierServicePackageName();
 
     PersistableBundle getConfigSubsetForSubIdWithFeature(int subId, String callingPackage,
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 23f4217..0c3991d 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -1192,17 +1192,6 @@
             in List<String> cdmaNonRoamingList);
 
     /**
-     * Returns the result and response from RIL for oem request
-     *
-     * @param oemReq the data is sent to ril.
-     * @param oemResp the respose data from RIL.
-     * @return negative value request was not handled or get error
-     *         0 request was handled succesfully, but no response data
-     *         positive value success, data length of response
-     */
-    int invokeOemRilRequestRaw(in byte[] oemReq, out byte[] oemResp);
-
-    /**
      * Check if any mobile Radios need to be shutdown.
      *
      * @return true is any mobile radio needs to be shutdown
diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java
index b273ba2..72e4389 100644
--- a/telephony/java/com/android/internal/telephony/RILConstants.java
+++ b/telephony/java/com/android/internal/telephony/RILConstants.java
@@ -323,11 +323,6 @@
     int SETUP_DATA_AUTH_CHAP      = 2;
     int SETUP_DATA_AUTH_PAP_CHAP  = 3;
 
-    /* LCE service related constants. */
-    int LCE_NOT_AVAILABLE = -1;
-    int LCE_STOPPED = 0;
-    int LCE_ACTIVE = 1;
-
     /**
      * No restriction at all including voice/SMS/USSD/SS/AV64
      * and packet data.
@@ -571,7 +566,24 @@
     int RIL_REQUEST_UPDATE_IMS_CALL_STATUS = 240;
     int RIL_REQUEST_SET_N1_MODE_ENABLED = 241;
     int RIL_REQUEST_IS_N1_MODE_ENABLED = 242;
+    int RIL_REQUEST_SET_LOCATION_PRIVACY_SETTING = 243;
+    int RIL_REQUEST_GET_LOCATION_PRIVACY_SETTING = 244;
+    int RIL_REQUEST_GET_SATELLITE_CAPABILITIES = 245;
+    int RIL_REQUEST_SET_SATELLITE_POWER = 246;
+    int RIL_REQUEST_GET_SATELLITE_POWER = 247;
+    int RIL_REQUEST_PROVISION_SATELLITE_SERVICE = 248;
+    int RIL_REQUEST_ADD_ALLOWED_SATELLITE_CONTACTS = 249;
+    int RIL_REQUEST_REMOVE_ALLOWED_SATELLITE_CONTACTS = 250;
+    int RIL_REQUEST_SEND_SATELLITE_MESSAGES = 251;
+    int RIL_REQUEST_GET_PENDING_SATELLITE_MESSAGES = 252;
+    int RIL_REQUEST_GET_SATELLITE_MODE = 253;
+    int RIL_REQUEST_SET_SATELLITE_INDICATION_FILTER = 254;
+    int RIL_REQUEST_START_SENDING_SATELLITE_POINTING_INFO = 255;
+    int RIL_REQUEST_STOP_SENDING_SATELLITE_POINTING_INFO = 256;
+    int RIL_REQUEST_GET_MAX_CHARACTERS_PER_SATELLITE_TEXT_MESSAGE = 257;
+    int RIL_REQUEST_GET_TIME_FOR_NEXT_SATELLITE_VISIBILITY = 258;
     int RIL_REQUEST_IS_NULL_CIPHER_AND_INTEGRITY_ENABLED = 259;
+    int RIL_REQUEST_SET_SATELLITE_PLMN = 260;
 
     /* Responses begin */
     int RIL_RESPONSE_ACKNOWLEDGEMENT = 800;
@@ -633,6 +645,13 @@
     int RIL_UNSOL_RESPONSE_SIM_PHONEBOOK_CHANGED = 1053;
     int RIL_UNSOL_RESPONSE_SIM_PHONEBOOK_RECORDS_RECEIVED = 1054;
     int RIL_UNSOL_SLICING_CONFIG_CHANGED = 1055;
+    int RIL_UNSOL_PENDING_SATELLITE_MESSAGE_COUNT = 1056;
+    int RIL_UNSOL_NEW_SATELLITE_MESSAGES = 1057;
+    int RIL_UNSOL_SATELLITE_MESSAGES_TRANSFER_COMPLETE = 1058;
+    int RIL_UNSOL_SATELLITE_POINTING_INFO_CHANGED = 1059;
+    int RIL_UNSOL_SATELLITE_MODE_CHANGED = 1060;
+    int RIL_UNSOL_SATELLITE_RADIO_TECHNOLOGY_CHANGED = 1061;
+    int RIL_UNSOL_SATELLITE_PROVISION_STATE_CHANGED = 1062;
 
     /* The following unsols are not defined in RIL.h */
     int RIL_UNSOL_HAL_NON_RIL_BASE = 1100;
diff --git a/telephony/java/com/android/telephony/Rlog.java b/telephony/java/com/android/telephony/Rlog.java
index 9d6c930..e3e6c60 100644
--- a/telephony/java/com/android/telephony/Rlog.java
+++ b/telephony/java/com/android/telephony/Rlog.java
@@ -15,6 +15,10 @@
  */
 package com.android.telephony;
 
+import android.net.Uri;
+import android.os.Build;
+import android.telecom.PhoneAccount;
+import android.telephony.PhoneNumberUtils;
 import android.text.TextUtils;
 import android.util.Base64;
 import android.util.Log;
@@ -128,6 +132,78 @@
     }
 
     /**
+     * Generates an obfuscated string for a calling handle in {@link Uri} format, or a raw phone
+     * phone number in {@link String} format.
+     * @param pii The information to obfuscate.
+     * @return The obfuscated string.
+     */
+    public static String piiHandle(Object pii) {
+        StringBuilder sb = new StringBuilder();
+        if (pii instanceof Uri) {
+            Uri uri = (Uri) pii;
+            String scheme = uri.getScheme();
+
+            if (!TextUtils.isEmpty(scheme)) {
+                sb.append(scheme).append(":");
+            }
+
+            String textToObfuscate = uri.getSchemeSpecificPart();
+            if (PhoneAccount.SCHEME_TEL.equals(scheme)) {
+                obfuscatePhoneNumber(sb, textToObfuscate);
+            } else if (PhoneAccount.SCHEME_SIP.equals(scheme)) {
+                for (int i = 0; i < textToObfuscate.length(); i++) {
+                    char c = textToObfuscate.charAt(i);
+                    if (c != '@' && c != '.') {
+                        c = '*';
+                    }
+                    sb.append(c);
+                }
+            } else {
+                sb.append("***");
+            }
+        } else if (pii instanceof String) {
+            String number = (String) pii;
+            obfuscatePhoneNumber(sb, number);
+        }
+
+        return sb.toString();
+    }
+
+    /**
+     * Obfuscates a phone number, allowing NUM_DIALABLE_DIGITS_TO_LOG digits to be exposed for the
+     * phone number.
+     * @param sb String buffer to write obfuscated number to.
+     * @param phoneNumber The number to obfuscate.
+     */
+    private static void obfuscatePhoneNumber(StringBuilder sb, String phoneNumber) {
+        int numDigitsToLog = USER_BUILD ? 0 : 2;
+        int numDigitsToObfuscate = getDialableCount(phoneNumber) - numDigitsToLog;
+        for (int i = 0; i < phoneNumber.length(); i++) {
+            char c = phoneNumber.charAt(i);
+            boolean isDialable = PhoneNumberUtils.isDialable(c);
+            if (isDialable) {
+                numDigitsToObfuscate--;
+            }
+            sb.append(isDialable && numDigitsToObfuscate >= 0 ? "*" : c);
+        }
+    }
+
+    /**
+     * Determines the number of dialable characters in a string.
+     * @param toCount The string to count dialable characters in.
+     * @return The count of dialable characters.
+     */
+    private static int getDialableCount(String toCount) {
+        int numDialable = 0;
+        for (char c : toCount.toCharArray()) {
+            if (PhoneNumberUtils.isDialable(c)) {
+                numDialable++;
+            }
+        }
+        return numDialable;
+    }
+
+    /**
      * Returns a secure hash (using the SHA1 algorithm) of the provided input.
      *
      * @return "****" if the build type is user, otherwise the hash
diff --git a/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java b/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java
index 591ffeb..482f633 100644
--- a/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java
+++ b/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java
@@ -408,8 +408,7 @@
         damageFileAgainstBlockDevice(apkPath, offsetFirstByte);
 
         // Expect actual read from disk to fail but only at damaged page.
-        BlockDeviceWriter.dropCaches(mDevice);
-        assertFalse(BlockDeviceWriter.canReadByte(mDevice, apkPath, offsetFirstByte));
+        expectReadFromBlockDeviceToFail(apkPath, offsetFirstByte);
         if (apkSize > offsetFirstByte + FSVERITY_PAGE_SIZE) {
             long lastByteOfTheSamePage =
                     offsetFirstByte % FSVERITY_PAGE_SIZE + FSVERITY_PAGE_SIZE - 1;
@@ -437,8 +436,7 @@
         damageFileAgainstBlockDevice(apkPath, offsetOfLastByte);
 
         // Expect actual read from disk to fail but only at damaged page.
-        BlockDeviceWriter.dropCaches(mDevice);
-        assertFalse(BlockDeviceWriter.canReadByte(mDevice, apkPath, offsetOfLastByte));
+        expectReadFromBlockDeviceToFail(apkPath, offsetOfLastByte);
         if (offsetOfLastByte - FSVERITY_PAGE_SIZE > 0) {
             long firstByteOfTheSamePage = offsetOfLastByte - offsetOfLastByte % FSVERITY_PAGE_SIZE;
             assertFalse(BlockDeviceWriter.canReadByte(mDevice, apkPath, firstByteOfTheSamePage));
@@ -456,29 +454,34 @@
             String path = appDir + "/" + basename;
             damageFileAgainstBlockDevice(path, kTargetOffset);
 
-            // Retry is sometimes needed to pass the test. Package manager may have FD leaks
-            // (see b/122744005 as example) that prevents the file in question to be evicted
-            // from filesystem cache. Forcing GC workarounds the problem.
-            int retry = 5;
-            for (; retry > 0; retry--) {
-                BlockDeviceWriter.dropCaches(mDevice);
-                if (!BlockDeviceWriter.canReadByte(mDevice, path, kTargetOffset)) {
-                    break;
-                }
-                try {
-                    String openFiles = expectRemoteCommandToSucceed("lsof " + apkPath);
-                    CLog.d("lsof: " + openFiles);
-                    Thread.sleep(1000);
-                    forceGCOnOpenFilesProcess(getOpenFilesPIDs(openFiles));
-                } catch (InterruptedException e) {
-                    Thread.currentThread().interrupt();
-                    return;
-                }
-            }
-            assertTrue("Read from " + path + " should fail", retry > 0);
+            expectReadFromBlockDeviceToFail(path, kTargetOffset);
         }
     }
 
+    private void expectReadFromBlockDeviceToFail(String readPath, long offset)
+            throws DeviceNotAvailableException {
+        // Retry is sometimes needed to pass the test. Package manager may have FD leaks
+        // (see b/122744005 as example) that prevents the file in question to be evicted
+        // from filesystem cache. Forcing GC workarounds the problem.
+        int retry = 5;
+        for (; retry > 0; retry--) {
+            BlockDeviceWriter.dropCaches(mDevice);
+            if (!BlockDeviceWriter.canReadByte(mDevice, readPath, offset)) {
+                break;
+            }
+            try {
+                String openFiles = expectRemoteCommandToSucceed("lsof " + readPath);
+                CLog.d("lsof: " + openFiles);
+                Thread.sleep(1000);
+                forceGCOnOpenFilesProcess(getOpenFilesPIDs(openFiles));
+            } catch (InterruptedException e) {
+                Thread.currentThread().interrupt();
+                return;
+            }
+        }
+        assertTrue("Read from " + readPath + " should fail", retry > 0);
+    }
+
     /**
      * This is a helper method that parses the lsof output to get PIDs of process holding FD.
      * Here is an example output of lsof. This method extracts the second columns(PID).
diff --git a/tests/ChoreographerTests/src/main/java/android/view/choreographertests/AttachedChoreographerTest.java b/tests/ChoreographerTests/src/main/java/android/view/choreographertests/AttachedChoreographerTest.java
index 48d050c..bb0d30a 100644
--- a/tests/ChoreographerTests/src/main/java/android/view/choreographertests/AttachedChoreographerTest.java
+++ b/tests/ChoreographerTests/src/main/java/android/view/choreographertests/AttachedChoreographerTest.java
@@ -22,9 +22,9 @@
 import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
 
 import android.Manifest;
-import android.app.compat.CompatChanges;
 import android.hardware.display.DisplayManager;
 import android.os.Looper;
 import android.support.test.uiautomator.UiDevice;
@@ -34,6 +34,7 @@
 import android.view.SurfaceControl;
 import android.view.SurfaceHolder;
 import android.view.SurfaceView;
+import android.view.WindowManager;
 
 import androidx.lifecycle.Lifecycle;
 import androidx.test.core.app.ActivityScenario;
@@ -52,7 +53,7 @@
 public class AttachedChoreographerTest {
     private static final String TAG = "AttachedChoreographerTest";
     private static final long DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE_CHANGEID = 170503758;
-    private static final long THRESHOLD_MS = 10;
+    private static final long THRESHOLD_MS = 4;
     private static final int FRAME_ITERATIONS = 21;
     private static final int CALLBACK_MISSED_THRESHOLD = 2;
 
@@ -66,7 +67,7 @@
     private SurfaceView mSurfaceView;
     private SurfaceHolder mSurfaceHolder;
     private Choreographer mChoreographer;
-    private boolean mIsFirstCallback = true;
+    private long mExpectedPresentTime;
     private int mCallbackMissedCounter = 0;
 
     @Before
@@ -74,6 +75,13 @@
         mScenario = ActivityScenario.launch(GraphicsActivity.class);
         mScenario.moveToState(Lifecycle.State.CREATED);
         mScenario.onActivity(activity -> {
+            // Lock the display frame rate. This will not allow SurfaceFlinger to use the frame rate
+            // override feature that throttles down the global choreographer for this test.
+            float refreshRate = activity.getDisplay().getMode().getRefreshRate();
+            WindowManager.LayoutParams attrs = activity.getWindow().getAttributes();
+            attrs.preferredRefreshRate = refreshRate;
+            activity.getWindow().setAttributes(attrs);
+
             mSurfaceView = activity.findViewById(R.id.surface);
             mSurfaceHolder = mSurfaceView.getHolder();
             mSurfaceHolder.addCallback(new SurfaceHolder.Callback() {
@@ -95,6 +103,12 @@
         });
 
         UiDevice uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+
+        // TODO(b/290634611): clean this up once SF new front end is enabled by default
+        boolean sfNewFeEnabled = uiDevice.executeShellCommand("dumpsys SurfaceFlinger")
+                .indexOf("SurfaceFlinger New Frontend Enabled:true") != -1;
+        assumeTrue(sfNewFeEnabled);
+
         uiDevice.wakeUp();
         uiDevice.executeShellCommand("wm dismiss-keyguard");
         mScenario.moveToState(Lifecycle.State.RESUMED);
@@ -112,18 +126,16 @@
             mDisplayManager.setRefreshRateSwitchingType(
                     DisplayManager.SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY);
             mDisplayManager.setShouldAlwaysRespectAppRequestedMode(true);
-            boolean changeIsEnabled =
-                    CompatChanges.isChangeEnabled(
-                            DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE_CHANGEID);
-            Log.i(TAG, "DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE_CHANGE_ID is "
-                    + (changeIsEnabled ? "enabled" : "disabled"));
         });
     }
 
     @After
     public void tearDown() {
-        mDisplayManager.setRefreshRateSwitchingType(mInitialMatchContentFrameRate);
-        mDisplayManager.setShouldAlwaysRespectAppRequestedMode(false);
+        if (mDisplayManager != null) {
+            mDisplayManager.setRefreshRateSwitchingType(mInitialMatchContentFrameRate);
+            mDisplayManager.setShouldAlwaysRespectAppRequestedMode(false);
+        }
+
         InstrumentationRegistry.getInstrumentation().getUiAutomation()
                 .dropShellPermissionIdentity();
     }
@@ -407,18 +419,141 @@
         }
     }
 
+    @Test
+    public void testChoreographerAttachedAfterSetFrameRate() {
+        Log.i(TAG, "adyabr: starting testChoreographerAttachedAfterSetFrameRate");
+
+        class TransactionGenerator implements SurfaceControl.TransactionCommittedListener {
+            private SurfaceControl mSc;
+            private int mFrame;
+            private static final int NUM_FRAMES = 50;
+            private final CountDownLatch mCompleteLatch = new CountDownLatch(1);
+
+            TransactionGenerator(SurfaceControl sc) {
+                mSc = sc;
+
+            }
+
+            @Override
+            public void onTransactionCommitted() {
+                mFrame++;
+                if (mFrame >= NUM_FRAMES) {
+                    mCompleteLatch.countDown();
+                    return;
+                }
+
+                SurfaceControl.Transaction transaction = new SurfaceControl.Transaction();
+                transaction.setAlpha(mSc, 1.0f / mFrame)
+                        .addTransactionCommittedListener(Runnable::run, this)
+                        .apply();
+
+            }
+
+            public void startAndWaitForCompletion() {
+                onTransactionCommitted();
+                if (waitForCountDown(mCompleteLatch, /* timeoutInSeconds */ 10L)) {
+                    fail("failed to send new transactions");
+                }
+            }
+        }
+
+
+        for (int divisor : new int[]{2, 3}) {
+            CountDownLatch choreographerLatch = new CountDownLatch(1);
+            mScenario.onActivity(activity -> {
+                if (waitForCountDown(mSurfaceCreationCountDown, /* timeoutInSeconds */ 1L)) {
+                    fail("Unable to create surface within 1 Second");
+                }
+
+                SurfaceControl sc = mSurfaceView.getSurfaceControl();
+                SurfaceControl.Transaction transaction = new SurfaceControl.Transaction();
+                float displayRefreshRate = activity.getDisplay().getMode().getRefreshRate();
+                float fps = displayRefreshRate / divisor;
+                long callbackDurationMs = Math.round(1000 / fps);
+                mCallbackMissedCounter = 0;
+                transaction.setFrameRate(sc, fps, Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE)
+                        .apply();
+
+
+                new TransactionGenerator(sc).startAndWaitForCompletion();
+
+                Choreographer choreographer = sc.getChoreographer();
+                verifyVsyncCallbacks(choreographer, callbackDurationMs,
+                        choreographerLatch, FRAME_ITERATIONS);
+            });
+            // wait for the previous callbacks to finish before moving to the next divisor
+            if (waitForCountDown(choreographerLatch, /* timeoutInSeconds */ 5L)) {
+                fail("Test not finished in 5 Seconds");
+            }
+        }
+    }
+
+    @Test
+    public void testChoreographerNonDivisorFixedSourceRefreshRate() {
+        CountDownLatch continueLatch = new CountDownLatch(1);
+        mScenario.onActivity(activity -> {
+            if (waitForCountDown(mSurfaceCreationCountDown, /* timeoutInSeconds */ 1L)) {
+                fail("Unable to create surface within 1 Second");
+            }
+            SurfaceControl sc = mSurfaceView.getSurfaceControl();
+            Choreographer choreographer = sc.getChoreographer();
+            SurfaceControl.Transaction transaction = new SurfaceControl.Transaction();
+            float displayRefreshRate = activity.getDisplay().getMode().getRefreshRate();
+            float fps = 61.7f; // hopefully not a divisor
+            long callbackDurationMs = Math.round(1000 / displayRefreshRate);
+            mCallbackMissedCounter = 0;
+            transaction.setFrameRate(sc, fps, Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE)
+                    .addTransactionCommittedListener(Runnable::run,
+                            () -> verifyVsyncCallbacks(choreographer,
+                                    callbackDurationMs, continueLatch, FRAME_ITERATIONS))
+                    .apply();
+        });
+        // wait for the previous callbacks to finish before moving to the next divisor
+        if (waitForCountDown(continueLatch, /* timeoutInSeconds */ 5L)) {
+            fail("Test not finished in 5 Seconds");
+        }
+    }
+
+    @Test
+    public void testChoreographerNonDivisorRefreshRate() {
+        CountDownLatch continueLatch = new CountDownLatch(1);
+        mScenario.onActivity(activity -> {
+            if (waitForCountDown(mSurfaceCreationCountDown, /* timeoutInSeconds */ 1L)) {
+                fail("Unable to create surface within 1 Second");
+            }
+            SurfaceControl sc = mSurfaceView.getSurfaceControl();
+            Choreographer choreographer = sc.getChoreographer();
+            SurfaceControl.Transaction transaction = new SurfaceControl.Transaction();
+            float displayRefreshRate = activity.getDisplay().getMode().getRefreshRate();
+            float fps = 61.7f; // hopefully not a divisor
+            float expectedFps = displayRefreshRate / Math.round(displayRefreshRate / fps);
+            long callbackDurationMs = Math.round(1000 / expectedFps);
+            mCallbackMissedCounter = 0;
+            transaction.setFrameRate(sc, fps, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT)
+                    .addTransactionCommittedListener(Runnable::run,
+                            () -> verifyVsyncCallbacks(choreographer,
+                                    callbackDurationMs, continueLatch, FRAME_ITERATIONS))
+                    .apply();
+        });
+        // wait for the previous callbacks to finish before moving to the next divisor
+        if (waitForCountDown(continueLatch, /* timeoutInSeconds */ 5L)) {
+            fail("Test not finished in 5 Seconds");
+        }
+    }
+
     private void verifyVsyncCallbacks(Choreographer choreographer, long callbackDurationMs,
             CountDownLatch continueLatch, int frameCount) {
-        long callbackRequestedTimeNs = System.nanoTime();
         choreographer.postVsyncCallback(frameData -> {
+            long expectedPresentTime =
+                    frameData.getPreferredFrameTimeline().getExpectedPresentationTimeNanos();
             if (frameCount > 0) {
-                if (!mIsFirstCallback) {
-                    // Skip the first callback as it takes 1 frame
-                    // to reflect the new refresh rate
-                    long callbackDurationDiffMs = getCallbackDurationDiffInMs(
-                            frameData.getFrameTimeNanos(),
-                            callbackRequestedTimeNs, callbackDurationMs);
-                    if (callbackDurationDiffMs < 0 || callbackDurationDiffMs > THRESHOLD_MS) {
+                if (mExpectedPresentTime > 0) {
+                    long callbackDurationDiffMs =
+                            TimeUnit.NANOSECONDS.toMillis(
+                                    expectedPresentTime - mExpectedPresentTime);
+                    if (callbackDurationDiffMs < 0
+                            || Math.abs(callbackDurationMs - callbackDurationDiffMs)
+                                > THRESHOLD_MS) {
                         mCallbackMissedCounter++;
                         Log.e(TAG, "Frame #" + Math.abs(frameCount - FRAME_ITERATIONS)
                                 + " vsync callback failed, expected callback in "
@@ -427,25 +562,19 @@
                                 + " but actual duration difference is " + callbackDurationDiffMs);
                     }
                 }
-                mIsFirstCallback = false;
+                mExpectedPresentTime = expectedPresentTime;
                 verifyVsyncCallbacks(choreographer, callbackDurationMs,
                         continueLatch, frameCount - 1);
             } else {
-                assertTrue("Missed timeline for " + mCallbackMissedCounter + " callbacks, while "
-                                + CALLBACK_MISSED_THRESHOLD + " missed callbacks are allowed",
+                assertTrue("Missed timeline for " + mCallbackMissedCounter
+                                + " callbacks, while " + CALLBACK_MISSED_THRESHOLD
+                                + " missed callbacks are allowed",
                         mCallbackMissedCounter <= CALLBACK_MISSED_THRESHOLD);
                 continueLatch.countDown();
             }
         });
     }
 
-    private long getCallbackDurationDiffInMs(long callbackTimeNs, long requestedTimeNs,
-            long expectedCallbackMs) {
-        long actualTimeMs = TimeUnit.NANOSECONDS.toMillis(callbackTimeNs)
-                - TimeUnit.NANOSECONDS.toMillis(requestedTimeNs);
-        return Math.abs(expectedCallbackMs - actualTimeMs);
-    }
-
     private boolean waitForCountDown(CountDownLatch countDownLatch, long timeoutInSeconds) {
         try {
             return !countDownLatch.await(timeoutInSeconds, TimeUnit.SECONDS);
diff --git a/tests/CompanionDeviceMultiDeviceTests/OWNERS b/tests/CompanionDeviceMultiDeviceTests/OWNERS
new file mode 100644
index 0000000..7517836
--- /dev/null
+++ b/tests/CompanionDeviceMultiDeviceTests/OWNERS
@@ -0,0 +1,6 @@
+# Bug component: 708992
+evanxinchen@google.com
+guojing@google.com
+jeremyns@google.com
+raphk@google.com
+yukl@google.com
diff --git a/tests/CompanionDeviceMultiDeviceTests/README.md b/tests/CompanionDeviceMultiDeviceTests/README.md
new file mode 100644
index 0000000..6cf735a
--- /dev/null
+++ b/tests/CompanionDeviceMultiDeviceTests/README.md
@@ -0,0 +1,17 @@
+## CDM Multi-device Tests
+
+### Device Setup
+To test on physical devices, connect _two_ devices locally and enable USB debugging setting on both devices.
+
+When running on a cloudtop or other remote setups, use pontis to connect the devices on remote set up by running `pontis start`.
+Verify that pontis client is connected via `pontis status` and confirm that both devices are in "connected" state via `adb devices`.
+
+See go/pontis for more details regarding this workflow.
+
+To test on virtual devices, follow instructions to [set up netsim on cuttlefish](https://g3doc.corp.google.com/ambient/d2di/sim/g3doc/guide/cuttlefish.md?cl=head).
+Launch _two_ instances of virtual devices by specifying `--num_instances=2` parameter.
+
+### Running the Test
+```
+atest CompanionDeviceManagerMultiDeviceTestCases
+```
diff --git a/tests/CompanionDeviceMultiDeviceTests/client/Android.bp b/tests/CompanionDeviceMultiDeviceTests/client/Android.bp
new file mode 100644
index 0000000..1e68c9d
--- /dev/null
+++ b/tests/CompanionDeviceMultiDeviceTests/client/Android.bp
@@ -0,0 +1,50 @@
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    // 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: "cdm_snippet",
+    srcs: ["src/**/*.kt"],
+    manifest: "AndroidManifest.xml",
+
+    platform_apis: true,
+    target_sdk_version: "current",
+
+    static_libs: [
+        "androidx.test.ext.junit",
+        "androidx.test.uiautomator_uiautomator",
+        "compatibility-device-util-axt",
+        "cts-companion-common",
+        "cts-companion-uicommon",
+        "kotlin-stdlib",
+        "mobly-snippet-lib",
+    ],
+    libs: [
+        "android.test.base",
+        "android.test.runner",
+    ],
+
+    optimize: {
+        proguard_compatibility: true,
+        proguard_flags_files: ["proguard.flags"],
+    },
+}
diff --git a/tests/CompanionDeviceMultiDeviceTests/client/AndroidManifest.xml b/tests/CompanionDeviceMultiDeviceTests/client/AndroidManifest.xml
new file mode 100644
index 0000000..11dc763
--- /dev/null
+++ b/tests/CompanionDeviceMultiDeviceTests/client/AndroidManifest.xml
@@ -0,0 +1,51 @@
+<?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.
+  -->
+
+<manifest
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.companion.multidevices">
+
+  <uses-permission android:name="android.permission.INTERNET" />
+  <uses-permission android:name="android.permission.REQUEST_COMPANION_SELF_MANAGED" />
+  <uses-permission android:name="android.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE" />
+  <uses-permission android:name="android.permission.REQUEST_COMPANION_PROFILE_WATCH" />
+  <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+  <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+  <uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
+  <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
+  <uses-permission android:name="android.permission.DELIVER_COMPANION_MESSAGES" />
+
+  <uses-feature android:name="android.hardware.bluetooth" android:required="true"/>
+  <uses-feature android:name="android.software.companion_device_setup" />
+
+  <application>
+    <!-- Add any classes that implement the Snippet interface as meta-data, whose
+         value is a comma-separated string, each section being the package path
+         of a snippet class -->
+    <meta-data
+        android:name="mobly-snippets"
+        android:value="android.companion.multidevices.CompanionDeviceManagerSnippet" />
+  </application>
+
+  <!-- Add an instrumentation tag so that the app can be launched through an
+       instrument command. The runner `com.google.android.mobly.snippet.SnippetRunner`
+       is derived from `AndroidJUnitRunner`, and is required to use the
+       Mobly Snippet Lib. -->
+  <instrumentation
+      android:name="com.google.android.mobly.snippet.SnippetRunner"
+      android:targetPackage="android.companion.multidevices" />
+</manifest>
diff --git a/tests/CompanionDeviceMultiDeviceTests/client/proguard.flags b/tests/CompanionDeviceMultiDeviceTests/client/proguard.flags
new file mode 100644
index 0000000..1c70253a
--- /dev/null
+++ b/tests/CompanionDeviceMultiDeviceTests/client/proguard.flags
@@ -0,0 +1,24 @@
+# Keep all companion classes.
+-keep class android.companion.** {
+    *;
+}
+
+# Do not touch Mobly.
+-keep class com.google.android.mobly.** {
+    *;
+}
+
+# Keep names for easy debugging.
+-dontobfuscate
+
+# Necessary to allow debugging.
+-keepattributes *
+
+# By default, proguard leaves all classes in their original package, which
+# needlessly repeats com.google.android.apps.etc.
+-repackageclasses ""
+
+# Allows proguard to make private and protected methods and fields public as
+# part of optimization. This lets proguard inline trivial getter/setter
+# methods.
+-allowaccessmodification
\ No newline at end of file
diff --git a/tests/CompanionDeviceMultiDeviceTests/client/src/android/companion/multidevices/CallbackUtils.kt b/tests/CompanionDeviceMultiDeviceTests/client/src/android/companion/multidevices/CallbackUtils.kt
new file mode 100644
index 0000000..3e4944a
--- /dev/null
+++ b/tests/CompanionDeviceMultiDeviceTests/client/src/android/companion/multidevices/CallbackUtils.kt
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.companion.multidevices
+
+import android.companion.AssociationInfo
+import android.companion.CompanionDeviceManager
+import android.companion.CompanionException
+import android.content.IntentSender
+import android.os.OutcomeReceiver
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit.SECONDS
+import java.util.concurrent.TimeoutException
+
+/** Blocking callbacks for Wi-Fi Aware and Connectivity Manager. */
+object CallbackUtils {
+    private const val TAG = "CDM_CallbackUtils"
+    private const val CALLBACK_TIMEOUT_SEC = 30L
+    private const val MESSAGE_CALLBACK_TIMEOUT_SEC = 5L
+
+    class AssociationCallback : CompanionDeviceManager.Callback() {
+        private val pending = CountDownLatch(1)
+        private val created = CountDownLatch(1)
+
+        private var pendingIntent: IntentSender? = null
+        private var associationInfo: AssociationInfo? = null
+        private var error: String? = null
+
+        override fun onAssociationPending(intentSender: IntentSender) {
+            this.pendingIntent = intentSender
+            pending.countDown()
+        }
+
+        override fun onAssociationCreated(associationInfo: AssociationInfo) {
+            this.associationInfo = associationInfo
+            created.countDown()
+        }
+
+        override fun onFailure(error: CharSequence?) {
+            this.error = error?.toString() ?: "There was an unexpected failure."
+            pending.countDown()
+            created.countDown()
+        }
+
+        fun waitForPendingIntent(): IntentSender? {
+            if (!pending.await(CALLBACK_TIMEOUT_SEC, SECONDS)) {
+                throw TimeoutException("Pending association request timed out.")
+            }
+
+            error?.let {
+                throw CompanionException(it)
+            }
+
+            return pendingIntent
+        }
+
+        fun waitForAssociation(): AssociationInfo? {
+            if (!created.await(CALLBACK_TIMEOUT_SEC, SECONDS)) {
+                throw TimeoutException("Association request timed out.")
+            }
+
+            error?.let {
+                throw CompanionException(it)
+            }
+
+            return associationInfo
+        }
+    }
+
+    class SystemDataTransferCallback : OutcomeReceiver<Void, CompanionException> {
+        private val completed = CountDownLatch(1)
+
+        private var error: CompanionException? = null
+
+        override fun onResult(result: Void?) {
+            completed.countDown()
+        }
+
+        override fun onError(error: CompanionException) {
+            this.error = error
+            completed.countDown()
+        }
+
+        fun waitForCompletion() {
+            if (!completed.await(CALLBACK_TIMEOUT_SEC, SECONDS)) {
+                throw TimeoutException("System data transfer timed out.")
+            }
+
+            error?.let {
+                throw it
+            }
+        }
+    }
+}
diff --git a/tests/CompanionDeviceMultiDeviceTests/client/src/android/companion/multidevices/CompanionDeviceManagerSnippet.kt b/tests/CompanionDeviceMultiDeviceTests/client/src/android/companion/multidevices/CompanionDeviceManagerSnippet.kt
new file mode 100644
index 0000000..ee587f5
--- /dev/null
+++ b/tests/CompanionDeviceMultiDeviceTests/client/src/android/companion/multidevices/CompanionDeviceManagerSnippet.kt
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.companion.multidevices
+
+import android.app.Instrumentation
+import android.bluetooth.BluetoothAdapter
+import android.bluetooth.BluetoothManager
+import android.companion.AssociationInfo
+import android.companion.AssociationRequest
+import android.companion.BluetoothDeviceFilter
+import android.companion.CompanionDeviceManager
+import android.companion.CompanionException
+import android.companion.cts.common.CompanionActivity
+import android.companion.multidevices.CallbackUtils.AssociationCallback
+import android.companion.multidevices.CallbackUtils.SystemDataTransferCallback
+import android.companion.multidevices.bluetooth.BluetoothConnector
+import android.companion.multidevices.bluetooth.BluetoothController
+import android.companion.cts.uicommon.CompanionDeviceManagerUi
+import android.content.Context
+import android.os.Handler
+import android.os.HandlerExecutor
+import android.os.HandlerThread
+import android.util.Log
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.google.android.mobly.snippet.Snippet
+import com.google.android.mobly.snippet.event.EventCache
+import com.google.android.mobly.snippet.rpc.Rpc
+import java.util.concurrent.Executor
+import java.util.regex.Pattern
+
+/**
+ * Snippet class that exposes Android APIs in CompanionDeviceManager.
+ */
+class CompanionDeviceManagerSnippet : Snippet {
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()!!
+    private val context: Context = instrumentation.targetContext
+
+    private val btAdapter: BluetoothAdapter by lazy {
+        (context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager).adapter
+    }
+    private val companionDeviceManager: CompanionDeviceManager by lazy {
+        context.getSystemService(Context.COMPANION_DEVICE_SERVICE) as CompanionDeviceManager
+    }
+    private val btConnector: BluetoothConnector by lazy {
+        BluetoothConnector(btAdapter, companionDeviceManager)
+    }
+
+    private val uiDevice by lazy { UiDevice.getInstance(instrumentation) }
+    private val confirmationUi by lazy { CompanionDeviceManagerUi(uiDevice) }
+    private val btController by lazy { BluetoothController(context, btAdapter, uiDevice) }
+
+    private val eventCache = EventCache.getInstance()
+    private val handlerThread = HandlerThread("Snippet-Aware")
+    private val handler: Handler
+    private val executor: Executor
+
+    init {
+        handlerThread.start()
+        handler = Handler(handlerThread.looper)
+        executor = HandlerExecutor(handler)
+    }
+
+    /**
+     * Make device discoverable to other devices via BLE and return device name.
+     */
+    @Rpc(description = "Start advertising device to be discoverable.")
+    fun becomeDiscoverable(): String {
+        btController.becomeDiscoverable()
+        return btAdapter.name
+    }
+
+    /**
+     * Associate with a nearby device with given name and return newly-created association ID.
+     */
+    @Rpc(description = "Start device association flow.")
+    @Throws(Exception::class)
+    fun associate(deviceName: String): Int {
+        val filter = BluetoothDeviceFilter.Builder()
+            .setNamePattern(Pattern.compile(deviceName))
+            .build()
+        val request = AssociationRequest.Builder()
+            .setSingleDevice(true)
+            .addDeviceFilter(filter)
+            .build()
+        val callback = AssociationCallback()
+        companionDeviceManager.associate(request, callback, handler)
+        val pendingConfirmation = callback.waitForPendingIntent()
+            ?: throw CompanionException("Association is pending but intent sender is null.")
+        CompanionActivity.launchAndWait(context)
+        CompanionActivity.startIntentSender(pendingConfirmation)
+        confirmationUi.waitUntilVisible()
+        confirmationUi.waitUntilPositiveButtonIsEnabledAndClick()
+        confirmationUi.waitUntilGone()
+
+        val (_, result) = CompanionActivity.waitForActivityResult()
+        if (result == null) {
+            throw CompanionException("Association result can't be null.")
+        }
+
+        val association = result.getParcelableExtra(
+            CompanionDeviceManager.EXTRA_ASSOCIATION,
+            AssociationInfo::class.java
+        )
+        val remoteDevice = association.associatedDevice?.getBluetoothDevice()!!
+
+        // Register associated device
+        btConnector.registerDevice(association.id, remoteDevice)
+
+        return association.id
+    }
+
+    /**
+     * Disassociate an association with given ID.
+     */
+    @Rpc(description = "Disassociate device.")
+    @Throws(Exception::class)
+    fun disassociate(associationId: Int) {
+        companionDeviceManager.disassociate(associationId)
+    }
+
+    /**
+     * Consent to system data transfer and carry it out using Bluetooth socket.
+     */
+    @Rpc(description = "Start permissions sync.")
+    fun startPermissionsSync(associationId: Int) {
+        val pendingIntent = companionDeviceManager
+            .buildPermissionTransferUserConsentIntent(associationId)
+        CompanionActivity.launchAndWait(context)
+        CompanionActivity.startIntentSender(pendingIntent)
+        confirmationUi.waitUntilSystemDataTransferConfirmationVisible()
+        confirmationUi.clickPositiveButton()
+        confirmationUi.waitUntilGone()
+
+        CompanionActivity.waitForActivityResult()
+
+        val callback = SystemDataTransferCallback()
+        companionDeviceManager.startSystemDataTransfer(associationId, executor, callback)
+        callback.waitForCompletion()
+    }
+
+    @Rpc(description = "Attach transport to the BT client socket.")
+    fun attachClientSocket(id: Int) {
+        btConnector.attachClientSocket(id)
+    }
+
+    @Rpc(description = "Attach transport to the BT server socket.")
+    fun attachServerSocket(id: Int) {
+        btConnector.attachServerSocket(id)
+    }
+
+    @Rpc(description = "Close all open sockets.")
+    fun closeAllSockets() {
+        // Close all open sockets
+        btConnector.closeAllSockets()
+    }
+
+    @Rpc(description = "Disassociate all associations.")
+    fun disassociateAll() {
+        companionDeviceManager.myAssociations.forEach {
+            Log.d(TAG, "Disassociating id=${it.id}.")
+            companionDeviceManager.disassociate(it.id)
+        }
+    }
+
+    companion object {
+        private const val TAG = "CDM_CompanionDeviceManagerSnippet"
+    }
+}
diff --git a/tests/CompanionDeviceMultiDeviceTests/client/src/android/companion/multidevices/bluetooth/BluetoothConnector.kt b/tests/CompanionDeviceMultiDeviceTests/client/src/android/companion/multidevices/bluetooth/BluetoothConnector.kt
new file mode 100644
index 0000000..c7312d2
--- /dev/null
+++ b/tests/CompanionDeviceMultiDeviceTests/client/src/android/companion/multidevices/bluetooth/BluetoothConnector.kt
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.companion.multidevices.bluetooth
+
+import android.bluetooth.BluetoothAdapter
+import android.bluetooth.BluetoothDevice
+import android.bluetooth.BluetoothServerSocket
+import android.bluetooth.BluetoothSocket
+import android.companion.CompanionDeviceManager
+import android.util.Log
+import java.io.IOException
+import java.util.UUID
+
+class BluetoothConnector(
+    private val adapter: BluetoothAdapter,
+    private val cdm: CompanionDeviceManager
+) {
+    companion object {
+        private const val TAG = "CDM_BluetoothServer"
+
+        private val SERVICE_NAME = "CDM_BluetoothChannel"
+        private val SERVICE_UUID = UUID.fromString("435fe1d9-56c5-455d-a516-d5e6b22c52f9")
+
+        // Registry of bluetooth server threads
+        private val serverThreads = mutableMapOf<Int, BluetoothServerThread>()
+
+        // Registry of remote bluetooth devices
+        private val remoteDevices = mutableMapOf<Int, BluetoothDevice>()
+
+        // Set of connected client sockets
+        private val clientSockets = mutableMapOf<Int, BluetoothSocket>()
+    }
+
+    fun attachClientSocket(associationId: Int) {
+        try {
+            val device = remoteDevices[associationId]!!
+            val socket = device.createRfcommSocketToServiceRecord(SERVICE_UUID)
+            if (clientSockets.containsKey(associationId)) {
+                detachClientSocket(associationId)
+                clientSockets[associationId] = socket
+            } else {
+                clientSockets += associationId to socket
+            }
+
+            socket.connect()
+            Log.d(TAG, "Attaching client socket $socket.")
+            cdm.attachSystemDataTransport(
+                    associationId,
+                    socket.inputStream,
+                    socket.outputStream
+            )
+        } catch (e: IOException) {
+            Log.e(TAG, "Failed to attach client socket.", e)
+            throw RuntimeException(e)
+        }
+    }
+
+    fun attachServerSocket(associationId: Int) {
+        val serverThread: BluetoothServerThread
+        if (serverThreads.containsKey(associationId)) {
+            serverThread = serverThreads[associationId]!!
+        } else {
+            serverThread = BluetoothServerThread(associationId)
+            serverThreads += associationId to serverThread
+        }
+
+        // Start thread
+        if (!serverThread.isOpen) {
+            serverThread.start()
+        }
+    }
+
+    fun closeAllSockets() {
+        val iter = clientSockets.keys.iterator()
+        while (iter.hasNext()) {
+            detachClientSocket(iter.next())
+        }
+        for (thread in serverThreads.values) {
+            thread.shutdown()
+        }
+        serverThreads.clear()
+    }
+
+    fun registerDevice(associationId: Int, remoteDevice: BluetoothDevice) {
+        remoteDevices[associationId] = remoteDevice
+    }
+
+    private fun detachClientSocket(associationId: Int) {
+        try {
+            Log.d(TAG, "Detaching client socket.")
+            cdm.detachSystemDataTransport(associationId)
+            clientSockets[associationId]?.close()
+        } catch (e: IOException) {
+            Log.e(TAG, "Failed to detach client socket.", e)
+            throw RuntimeException(e)
+        }
+    }
+
+    inner class BluetoothServerThread(
+        private val associationId: Int
+    ) : Thread() {
+        private lateinit var mServerSocket: BluetoothServerSocket
+
+        var isOpen = false
+
+        override fun run() {
+            try {
+                Log.d(TAG, "Listening for remote connections...")
+                mServerSocket = adapter.listenUsingRfcommWithServiceRecord(
+                        SERVICE_NAME,
+                        SERVICE_UUID
+                )
+                isOpen = true
+                do {
+                    val socket = mServerSocket.accept()
+                    Log.d(TAG, "Attaching server socket $socket.")
+                    cdm.attachSystemDataTransport(
+                            associationId,
+                            socket.inputStream,
+                            socket.outputStream
+                    )
+                } while (isOpen)
+            } catch (e: IOException) {
+                throw RuntimeException(e)
+            }
+        }
+
+        fun shutdown() {
+            if (!isOpen || !this::mServerSocket.isInitialized) return
+
+            try {
+                Log.d(TAG, "Closing server socket.")
+                cdm.detachSystemDataTransport(associationId)
+                mServerSocket.close()
+                isOpen = false
+            } catch (e: IOException) {
+                throw RuntimeException(e)
+            }
+        }
+    }
+}
diff --git a/tests/CompanionDeviceMultiDeviceTests/client/src/android/companion/multidevices/bluetooth/BluetoothController.kt b/tests/CompanionDeviceMultiDeviceTests/client/src/android/companion/multidevices/bluetooth/BluetoothController.kt
new file mode 100644
index 0000000..c4d2026
--- /dev/null
+++ b/tests/CompanionDeviceMultiDeviceTests/client/src/android/companion/multidevices/bluetooth/BluetoothController.kt
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.companion.multidevices.bluetooth
+
+import android.bluetooth.BluetoothAdapter
+import android.bluetooth.BluetoothDevice
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.os.SystemClock
+import android.util.Log
+import androidx.test.uiautomator.UiDevice
+import java.util.concurrent.TimeoutException
+import kotlin.time.Duration
+import kotlin.time.Duration.Companion.seconds
+
+/** Controls the local Bluetooth adapter for testing. */
+class BluetoothController(
+    private val context: Context,
+    private val adapter: BluetoothAdapter,
+    private val ui: UiDevice
+) {
+    companion object {
+        private const val TAG = "CDM_BluetoothController"
+    }
+
+    private val bluetoothUi by lazy { BluetoothUi(ui) }
+
+    init {
+        Log.d(TAG, "Registering pairing listener.")
+        context.registerReceiver(
+            PairingBroadcastReceiver(),
+            IntentFilter(BluetoothDevice.ACTION_PAIRING_REQUEST)
+        )
+    }
+
+    val isEnabled: Boolean
+        get() = adapter.isEnabled
+
+    /** Turns on the local Bluetooth adapter */
+    fun enableBluetooth() {
+        if (isEnabled) return
+
+        val intent = Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE)
+        intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
+        context.startActivity(intent)
+        bluetoothUi.clickAllowButton()
+        waitFor { adapter.state == BluetoothAdapter.STATE_ON }
+    }
+
+    /** Become discoverable for specified duration */
+    fun becomeDiscoverable(duration: Duration = 15.seconds) {
+        enableBluetooth()
+
+        val intent = Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE)
+        intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
+        intent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, duration.inWholeSeconds)
+        context.startActivity(intent)
+        bluetoothUi.clickAllowButton()
+    }
+
+    /** Unpair all devices for cleanup */
+    fun unpairAllDevices() {
+        for (device in adapter.bondedDevices) {
+            Log.d(TAG, "Unpairing $device.")
+            if (!device.removeBond()) continue
+            waitFor { device.bondState == BluetoothDevice.BOND_NONE }
+        }
+    }
+
+    private fun waitFor(
+        interval: Duration = 1.seconds,
+        timeout: Duration = 5.seconds,
+        condition: () -> Boolean
+    ) {
+        var elapsed = 0L
+        while (elapsed < timeout.inWholeMilliseconds) {
+            if (condition.invoke()) return
+            SystemClock.sleep(interval.inWholeMilliseconds)
+            elapsed += interval.inWholeMilliseconds
+        }
+        throw TimeoutException("Bluetooth did not become an expected state.")
+    }
+
+    inner class PairingBroadcastReceiver : BroadcastReceiver() {
+        override fun onReceive(context: Context, intent: Intent) {
+            Log.d(TAG, "Received broadcast for ${intent.action}")
+
+            // onReceive() somehow blocks pairing prompt from launching
+            Thread { bluetoothUi.confirmPairingRequest() }.start()
+            context.unregisterReceiver(this)
+        }
+    }
+}
diff --git a/tests/CompanionDeviceMultiDeviceTests/client/src/android/companion/multidevices/bluetooth/BluetoothUi.kt b/tests/CompanionDeviceMultiDeviceTests/client/src/android/companion/multidevices/bluetooth/BluetoothUi.kt
new file mode 100644
index 0000000..6983cb0
--- /dev/null
+++ b/tests/CompanionDeviceMultiDeviceTests/client/src/android/companion/multidevices/bluetooth/BluetoothUi.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.companion.multidevices.bluetooth
+
+import android.companion.cts.uicommon.CompanionDeviceManagerUi
+import android.util.Log
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.UiDevice
+import androidx.test.uiautomator.Until
+import java.util.regex.Pattern
+
+class BluetoothUi(private val ui: UiDevice) : CompanionDeviceManagerUi(ui) {
+    fun clickAllowButton() = click(ALLOW_BUTTON, "Allow button")
+
+    fun confirmPairingRequest(): Boolean {
+        if (ui.hasObject(PAIRING_PIN_ENTRY)) {
+            // It is prompting for a custom user pin entry
+            Log.d(TAG, "Is user entry prompt.")
+            ui.findObject(PAIRING_PIN_ENTRY).text = "0000"
+            click(OK_BUTTON, "Ok button")
+        } else {
+            // It just needs user consent
+            Log.d(TAG, "Looking for pair button.")
+            val button = ui.wait(Until.findObject(PAIR_BUTTON), 1_000)
+            if (button != null) {
+                Log.d(TAG, "Pair button found.")
+                button.click()
+                return true
+            }
+            Log.d(TAG, "Pair button not found.")
+        }
+        return false
+    }
+
+    companion object {
+        private const val TAG = "CDM_BluetoothUi"
+
+        private val ALLOW_TEXT_PATTERN = caseInsensitive("allow")
+        private val ALLOW_BUTTON = By.text(ALLOW_TEXT_PATTERN).clickable(true)
+
+        private val PAIRING_PIN_ENTRY = By.clazz(".EditText")
+
+        private val OK_TEXT_PATTERN = caseInsensitive("ok")
+        private val OK_BUTTON = By.text(OK_TEXT_PATTERN).clickable(true)
+
+        private val PAIR_TEXT_PATTERN = caseInsensitive("pair")
+        private val PAIR_BUTTON = By.text(PAIR_TEXT_PATTERN).clickable(true)
+
+        private fun caseInsensitive(text: String): Pattern =
+            Pattern.compile(text, Pattern.CASE_INSENSITIVE)
+    }
+}
diff --git a/tests/CompanionDeviceMultiDeviceTests/host/Android.bp b/tests/CompanionDeviceMultiDeviceTests/host/Android.bp
new file mode 100644
index 0000000..1167a3e
--- /dev/null
+++ b/tests/CompanionDeviceMultiDeviceTests/host/Android.bp
@@ -0,0 +1,51 @@
+// 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 {
+    // 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"],
+}
+
+python_test_host {
+    name: "CompanionDeviceManagerMultiDeviceTestCases",
+    main: "cdm_transport_test.py",
+    srcs: ["*.py"],
+    libs: [
+        "mobly",
+    ],
+    test_suites: [
+        "general-tests",
+    ],
+    test_options: {
+        unit_test: false,
+        tags: ["mobly"],
+    },
+    data: [
+        ":cdm_snippet",
+        "requirements.txt",
+    ],
+    version: {
+        py2: {
+            enabled: false,
+        },
+        py3: {
+            enabled: true,
+            embedded_launcher: true,
+        },
+    },
+}
diff --git a/tests/CompanionDeviceMultiDeviceTests/host/AndroidTest.xml b/tests/CompanionDeviceMultiDeviceTests/host/AndroidTest.xml
new file mode 100644
index 0000000..9d1813f
--- /dev/null
+++ b/tests/CompanionDeviceMultiDeviceTests/host/AndroidTest.xml
@@ -0,0 +1,42 @@
+<?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.
+-->
+<configuration description="Config for CDM multi-device test cases">
+    <option name="test-tag" value="CompanionDeviceMultiDeviceTests" />
+    <option name="config-descriptor:metadata" key="component" value="framework" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+
+    <object class="com.android.tradefed.testtype.suite.module.DeviceFeatureModuleController"
+        type="module_controller">
+        <option name="required-feature" value="android.software.companion_device_setup" />
+    </object>
+
+    <device name="device1">
+        <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+            <option name="test-file-name" value="cdm_snippet.apk" />
+        </target_preparer>
+    </device>
+    <device name="device2">
+        <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+            <option name="test-file-name" value="cdm_snippet.apk" />
+        </target_preparer>
+    </device>
+
+    <test class="com.android.tradefed.testtype.mobly.MoblyBinaryHostTest">
+      <!-- The mobly-par-file-name should match the module name -->
+      <option name="mobly-par-file-name" value="CompanionDeviceManagerMultiDeviceTestCases" />
+      <!-- Timeout limit in milliseconds for all test cases of the python binary -->
+      <option name="mobly-test-timeout" value="60000" />
+    </test>
+</configuration>
diff --git a/tests/CompanionDeviceMultiDeviceTests/host/cdm_base_test.py b/tests/CompanionDeviceMultiDeviceTests/host/cdm_base_test.py
new file mode 100644
index 0000000..bb10658
--- /dev/null
+++ b/tests/CompanionDeviceMultiDeviceTests/host/cdm_base_test.py
@@ -0,0 +1,63 @@
+# Lint as: python3
+"""
+Base class for setting up devices for CDM functionalities.
+"""
+
+from mobly import base_test
+from mobly import utils
+from mobly.controllers import android_device
+
+CDM_SNIPPET_PACKAGE = 'android.companion.multidevices'
+
+
+class BaseTestClass(base_test.BaseTestClass):
+
+    def setup_class(self):
+        # Declare that two Android devices are needed.
+        self.sender, self.receiver = self.register_controller(
+            android_device, min_number=2)
+        self.sender_id = None
+        self.receiver_id = None
+
+        def _setup_device(device):
+            device.load_snippet('cdm', CDM_SNIPPET_PACKAGE)
+            device.adb.shell('input keyevent KEYCODE_WAKEUP')
+            device.adb.shell('input keyevent KEYCODE_MENU')
+            device.adb.shell('input keyevent KEYCODE_HOME')
+
+            # Clean up existing associations
+            device.cdm.disassociateAll()
+
+        # Sets up devices in parallel to save time.
+        utils.concurrent_exec(
+            _setup_device,
+            ((self.sender,), (self.receiver,)),
+            max_workers=2,
+            raise_on_exception=True)
+
+    def associate_devices(self) -> tuple[int, int]:
+        """Associate devices with each other and return association IDs for both"""
+        # If association already exists, don't need another
+        if self.sender_id and self.receiver_id:
+            return (self.sender_id, self.receiver_id)
+
+        receiver_name = self.receiver.cdm.becomeDiscoverable()
+        self.receiver_id = self.sender.cdm.associate(receiver_name)
+
+        sender_name = self.sender.cdm.becomeDiscoverable()
+        self.sender_id = self.receiver.cdm.associate(sender_name)
+
+        return (self.sender_id, self.receiver_id)
+
+    def attach_transports(self):
+        """Attach transports to both devices"""
+        self.associate_devices()
+
+        self.receiver.cdm.attachServerSocket(self.sender_id)
+        self.sender.cdm.attachClientSocket(self.receiver_id)
+
+    def teardown_class(self):
+        """Clean up the opened sockets"""
+        self.sender.cdm.closeAllSockets()
+        self.receiver.cdm.closeAllSockets()
+
diff --git a/tests/CompanionDeviceMultiDeviceTests/host/cdm_transport_test.py b/tests/CompanionDeviceMultiDeviceTests/host/cdm_transport_test.py
new file mode 100644
index 0000000..9cb2d10
--- /dev/null
+++ b/tests/CompanionDeviceMultiDeviceTests/host/cdm_transport_test.py
@@ -0,0 +1,36 @@
+# Lint as: python3
+"""
+Test E2E CDM functions on mobly.
+"""
+
+import cdm_base_test
+import sys
+
+from mobly import asserts
+from mobly import test_runner
+
+CDM_SNIPPET_PACKAGE = 'android.companion.multidevices'
+
+
+class TransportTestClass(cdm_base_test.BaseTestClass):
+
+    def test_permissions_sync(self):
+        """This tests permissions sync from one device to another."""
+
+        # associate and attach transports
+        self.attach_transports()
+
+        # start permissions sync
+        self.sender.cdm.startPermissionsSync(self.receiver_id)
+
+
+if __name__ == '__main__':
+    try:
+        # Take test args and remove standalone '--' from the list
+        index = sys.argv.index('--')
+        sys.argv = sys.argv[:1] + sys.argv[index + 1:]
+    except ValueError:
+        # Ignore if '--' is not in args
+        pass
+
+    test_runner.main()
\ No newline at end of file
diff --git a/tests/CompanionDeviceMultiDeviceTests/host/requirements.txt b/tests/CompanionDeviceMultiDeviceTests/host/requirements.txt
new file mode 100644
index 0000000..86a11aa
--- /dev/null
+++ b/tests/CompanionDeviceMultiDeviceTests/host/requirements.txt
@@ -0,0 +1 @@
+mobly==1.12.1
diff --git a/tests/EnforcePermission/aidl/android/tests/enforcepermission/IProtected.aidl b/tests/EnforcePermission/aidl/android/tests/enforcepermission/IProtected.aidl
index 18e3aec..6e59b04 100644
--- a/tests/EnforcePermission/aidl/android/tests/enforcepermission/IProtected.aidl
+++ b/tests/EnforcePermission/aidl/android/tests/enforcepermission/IProtected.aidl
@@ -31,4 +31,31 @@
 
     @EnforcePermission("INTERNET")
     void ProtectedByInternetAndReadSyncSettingsImplicitly();
+
+    @EnforcePermission("TURN_SCREEN_ON")
+    void ProtectedByTurnScreenOn();
+
+    @EnforcePermission("READ_CONTACTS")
+    void ProtectedByReadContacts();
+
+    @EnforcePermission("READ_CALENDAR")
+    void ProtectedByReadCalendar();
+
+    @EnforcePermission(allOf={"INTERNET", "VIBRATE"})
+    void ProtectedByInternetAndVibrate();
+
+    @EnforcePermission(allOf={"INTERNET", "READ_SYNC_SETTINGS"})
+    void ProtectedByInternetAndReadSyncSettings();
+
+    @EnforcePermission(anyOf={"ACCESS_WIFI_STATE", "VIBRATE"})
+    void ProtectedByAccessWifiStateOrVibrate();
+
+    @EnforcePermission(anyOf={"INTERNET", "VIBRATE"})
+    void ProtectedByInternetOrVibrate();
+
+    @RequiresNoPermission
+    void NotProtected();
+
+    @PermissionManuallyEnforced
+    void ManuallyProtected();
 }
diff --git a/tests/EnforcePermission/perf-app/Android.bp b/tests/EnforcePermission/perf-app/Android.bp
new file mode 100644
index 0000000..b494bb7
--- /dev/null
+++ b/tests/EnforcePermission/perf-app/Android.bp
@@ -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 {
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test {
+    name: "EnforcePermissionPerfTests",
+    srcs: [
+        "src/**/*.java",
+        ":frameworks-enforce-permission-test-aidl",
+    ],
+    static_libs: [
+        "EnforcePermissionTestLib",
+        "androidx.benchmark_benchmark-common",
+        "androidx.benchmark_benchmark-junit4",
+        "apct-perftests-utils",
+        "collector-device-lib",
+        "androidx.test.rules",
+    ],
+    libs: [
+        "android.test.base",
+        "android.test.runner",
+    ],
+    data: [
+        ":EnforcePermissionTestHelper",
+        ":perfetto_artifacts",
+        "perfetto.textproto",
+    ],
+    platform_apis: true,
+    certificate: "platform",
+    test_suites: ["device-tests"],
+}
diff --git a/tests/EnforcePermission/perf-app/AndroidManifest.xml b/tests/EnforcePermission/perf-app/AndroidManifest.xml
new file mode 100644
index 0000000..900270d
--- /dev/null
+++ b/tests/EnforcePermission/perf-app/AndroidManifest.xml
@@ -0,0 +1,37 @@
+<?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="android.tests.enforcepermission.tests">
+
+    <uses-permission android:name="android.permission.INTERNET" />
+
+    <!-- Required by perfetto -->
+    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+
+    <queries>
+        <package android:name="android.tests.enforcepermission.service" />
+    </queries>
+
+    <application>
+      <uses-library android:name="android.test.runner" />
+      <profileable android:shell="true" />
+      <!-- Instance of the Service within the app. This is to test performance for same-process calls. -->
+      <service android:name=".TestService" />
+    </application>
+    <instrumentation android:name="androidx.benchmark.junit4.AndroidBenchmarkRunner"
+                     android:targetPackage="android.tests.enforcepermission.tests"/>
+</manifest>
diff --git a/tests/EnforcePermission/perf-app/AndroidTest.xml b/tests/EnforcePermission/perf-app/AndroidTest.xml
new file mode 100644
index 0000000..3bc1d2d
--- /dev/null
+++ b/tests/EnforcePermission/perf-app/AndroidTest.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Runs EnforcePermission Perf Tests">
+
+    <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
+    <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+        <option name="push-file" key="perfetto.textproto" value="/data/misc/perfetto-traces/trace_config.textproto" />
+    </target_preparer>
+    <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+      <option name="test-file-name" value="EnforcePermissionTestHelper.apk"/>
+      <option name="test-file-name" value="EnforcePermissionPerfTests.apk"/>
+      <option name="cleanup-apks" value="true" />
+    </target_preparer>
+
+    <option name="isolated-storage" value="false" />
+
+    <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+        <option name="pull-pattern-keys" value="perfetto_file_path" />
+        <option name="collect-on-run-ended-only" value="false" />
+    </metrics_collector>
+
+    <option name="test-tag" value="EnforcePermissionTests"/>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+        <option name="package" value="android.tests.enforcepermission.tests"/>
+        <option name="device-listeners" value="android.device.collectors.PerfettoListener" />
+        <!-- PerfettoListener related arguments -->
+        <option name="instrumentation-arg" key="perfetto_config_text_proto" value="true" />
+        <option name="instrumentation-arg" key="perfetto_config_file" value="trace_config.textproto" />
+    </test>
+</configuration>
diff --git a/tests/EnforcePermission/perf-app/perfetto.textproto b/tests/EnforcePermission/perf-app/perfetto.textproto
new file mode 100644
index 0000000..8a3eea4
--- /dev/null
+++ b/tests/EnforcePermission/perf-app/perfetto.textproto
@@ -0,0 +1,154 @@
+# Copyright (C) 2023 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Based on trace_config_detailed.textproto
+# proto-message: TraceConfig
+
+# Enable periodic flushing of the trace buffer into the output file.
+write_into_file: true
+
+# Writes the userspace buffer into the file every 1s.
+file_write_period_ms: 1000
+
+# See b/126487238 - we need to guarantee ordering of events.
+flush_period_ms: 10000
+
+# The trace buffers needs to be big enough to hold |file_write_period_ms| of
+# trace data. The trace buffer sizing depends on the number of trace categories
+# enabled and the device activity.
+
+# RSS events
+buffers {
+  size_kb: 32768
+  fill_policy: RING_BUFFER
+}
+
+# procfs polling
+buffers {
+  size_kb: 8192
+  fill_policy: RING_BUFFER
+}
+
+# perf memory
+buffers {
+  size_kb: 65536
+  fill_policy: RING_BUFFER
+}
+
+data_sources {
+  config {
+    name: "linux.ftrace"
+    target_buffer: 0
+    ftrace_config {
+      throttle_rss_stat: true
+      # These parameters affect only the kernel trace buffer size and how
+      # frequently it gets moved into the userspace buffer defined above.
+      buffer_size_kb: 16384
+      drain_period_ms: 250
+
+      # Store certain high-volume "sched" ftrace events in a denser format
+      # (falling back to the default format if not supported by the tracer).
+      compact_sched {
+        enabled: true
+      }
+
+      # Enables symbol name resolution against /proc/kallsyms
+      symbolize_ksyms: true
+      # Parse kallsyms before acknowledging that the ftrace data source has been started. In
+      # combination with "perfetto --background-wait" as the consumer, it lets us defer the
+      # test we're tracing until after the cpu has quieted down from the cpu-bound kallsyms parsing.
+      initialize_ksyms_synchronously_for_testing: true
+      # Avoid re-parsing kallsyms on every test run, as it takes 200-500ms per run. See b/239951079
+      ksyms_mem_policy: KSYMS_RETAIN
+
+      # We need to do process tracking to ensure kernel ftrace events targeted at short-lived
+      # threads are associated correctly
+      ftrace_events: "task/task_newtask"
+      ftrace_events: "task/task_rename"
+      ftrace_events: "sched/sched_process_exit"
+      ftrace_events: "sched/sched_process_free"
+
+      # Memory events
+      ftrace_events: "rss_stat"
+      ftrace_events: "ion_heap_shrink"
+      ftrace_events: "ion_heap_grow"
+      ftrace_events: "ion/ion_stat"
+      ftrace_events: "dmabuf_heap/dma_heap_stat"
+      ftrace_events: "oom_score_adj_update"
+      ftrace_events: "gpu_mem/gpu_mem_total"
+      ftrace_events: "fastrpc/fastrpc_dma_stat"
+
+      # Power events
+      ftrace_events: "power/suspend_resume"
+      ftrace_events: "power/cpu_frequency"
+      ftrace_events: "power/cpu_idle"
+      ftrace_events: "power/gpu_frequency"
+
+      # Old (kernel) LMK
+      ftrace_events: "lowmemorykiller/lowmemory_kill"
+
+      atrace_apps: "*"
+
+      atrace_categories: "am"
+      atrace_categories: "aidl"
+      atrace_categories: "bionic"
+      atrace_categories: "camera"
+      atrace_categories: "wm"
+      atrace_categories: "dalvik"
+      atrace_categories: "sched"
+      atrace_categories: "freq"
+      atrace_categories: "gfx"
+      atrace_categories: "view"
+      atrace_categories: "webview"
+      atrace_categories: "input"
+      atrace_categories: "hal"
+      atrace_categories: "binder_driver"
+      atrace_categories: "sync"
+      atrace_categories: "workq"
+      atrace_categories: "res"
+      atrace_categories: "power"
+
+    }
+  }
+}
+
+data_sources {
+  config {
+    name: "linux.process_stats"
+    target_buffer: 1
+    process_stats_config {
+      proc_stats_poll_ms: 10000
+    }
+  }
+}
+
+data_sources {
+  config {
+    name: "linux.perf"
+    target_buffer: 2
+    perf_event_config {
+      timebase {
+        frequency: 80
+      }
+      callstack_sampling {
+        scope {
+          target_cmdline: "android.tests.enforcepermission.tests"
+          target_cmdline: "android.tests.enforcepermission.service"
+          target_cmdline: "system_server"
+        }
+        kernel_frames: true
+      }
+    }
+  }
+}
diff --git a/tests/EnforcePermission/perf-app/src/android/tests/enforcepermission/tests/ServicePerfTest.java b/tests/EnforcePermission/perf-app/src/android/tests/enforcepermission/tests/ServicePerfTest.java
new file mode 100644
index 0000000..7cbf567
--- /dev/null
+++ b/tests/EnforcePermission/perf-app/src/android/tests/enforcepermission/tests/ServicePerfTest.java
@@ -0,0 +1,169 @@
+/**
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.tests.enforcepermission.tests;
+
+import static org.junit.Assert.assertTrue;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+import android.tests.enforcepermission.IProtected;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+/** Performance tests for EnforcePermission annotation.
+ *
+ * Permission check results are cached on the service side as it relies on
+ * PermissionManager. It means that only the first request will trigger a
+ * lookup to system_server. Subsequent requests will use the cached result. As
+ * this timing is similar to a permission check for a service hosted in
+ * system_server, we keep this cache active for the tests. The BenchmarkState
+ * used by PerfStatusReporter includes a warm-up stage. It means that the extra
+ * time taken by the first request will not be reflected in the outcome of the
+ * test.
+ */
+@RunWith(AndroidJUnit4.class)
+public class ServicePerfTest {
+
+    private static final String TAG = "EnforcePermission.PerfTests";
+    private static final String SERVICE_PACKAGE = "android.tests.enforcepermission.service";
+    private static final String LOCAL_SERVICE_PACKAGE = "android.tests.enforcepermission.tests";
+    private static final int SERVICE_TIMEOUT_SEC = 5;
+
+    @Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+    private Context mContext;
+    private volatile ServiceConnection mServiceConnection;
+
+    private void bindService(Intent intent) throws Exception {
+        mContext = InstrumentationRegistry.getTargetContext();
+        mServiceConnection = new ServiceConnection();
+        assertTrue(mContext.bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE));
+    }
+
+    public void bindRemoteService() throws Exception {
+        Log.d(TAG, "bindRemoteService");
+        Intent intent = new Intent();
+        intent.setClassName(SERVICE_PACKAGE, SERVICE_PACKAGE + ".TestService");
+        bindService(intent);
+    }
+
+    public void bindLocalService() throws Exception {
+        Log.d(TAG, "bindLocalService");
+        Intent intent = new Intent();
+        intent.setClassName(LOCAL_SERVICE_PACKAGE, SERVICE_PACKAGE + ".TestService");
+        bindService(intent);
+    }
+
+    @After
+    public void unbindTestService() throws Exception {
+        mContext.unbindService(mServiceConnection);
+    }
+
+    private static final class ServiceConnection implements android.content.ServiceConnection {
+        private volatile CompletableFuture<IProtected> mFuture = new CompletableFuture<>();
+
+        @Override
+        public void onServiceConnected(ComponentName className, IBinder service) {
+            mFuture.complete(IProtected.Stub.asInterface(service));
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName className) {
+            mFuture = new CompletableFuture<>();
+        }
+
+        public IProtected get() {
+            try {
+                return mFuture.get(SERVICE_TIMEOUT_SEC, TimeUnit.SECONDS);
+            } catch (ExecutionException | InterruptedException | TimeoutException e) {
+                throw new RuntimeException("Unable to reach TestService: " + e.toString());
+            }
+        }
+    }
+
+    @Test
+    public void testAnnotatedPermission() throws Exception {
+        bindRemoteService();
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            mServiceConnection.get().ProtectedByInternet();
+        }
+    }
+
+    @Test
+    public void testNoPermission() throws Exception {
+        bindRemoteService();
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            mServiceConnection.get().NotProtected();
+        }
+    }
+
+    @Test
+    public void testManuallyProtected() throws Exception {
+        bindRemoteService();
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            mServiceConnection.get().ManuallyProtected();
+        }
+    }
+
+    @Test
+    public void testAnnotatedPermissionLocal()
+            throws Exception {
+        bindLocalService();
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            mServiceConnection.get().ProtectedByInternet();
+        }
+    }
+
+    @Test
+    public void testNoPermissionLocal() throws Exception {
+        bindLocalService();
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            mServiceConnection.get().NotProtected();
+        }
+    }
+
+    @Test
+    public void testManuallyProtectedLocal() throws Exception {
+        bindLocalService();
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            mServiceConnection.get().ManuallyProtected();
+        }
+    }
+}
diff --git a/tests/EnforcePermission/service-app/Android.bp b/tests/EnforcePermission/service-app/Android.bp
index a4ac1d7..7878215 100644
--- a/tests/EnforcePermission/service-app/Android.bp
+++ b/tests/EnforcePermission/service-app/Android.bp
@@ -21,6 +21,14 @@
     default_applicable_licenses: ["frameworks_base_license"],
 }
 
+android_library {
+    name: "EnforcePermissionTestLib",
+    srcs: [
+        "src/**/*.java",
+        ":frameworks-enforce-permission-test-aidl",
+    ],
+}
+
 android_test_helper_app {
     name: "EnforcePermissionTestHelper",
     srcs: [
diff --git a/tests/EnforcePermission/service-app/AndroidManifest.xml b/tests/EnforcePermission/service-app/AndroidManifest.xml
index ddafe15..eba1230 100644
--- a/tests/EnforcePermission/service-app/AndroidManifest.xml
+++ b/tests/EnforcePermission/service-app/AndroidManifest.xml
@@ -15,6 +15,9 @@
 -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
   package="android.tests.enforcepermission.service">
+
+    <uses-permission android:name="android.permission.UPDATE_APP_OPS_STATS" />
+
     <application>
         <service
           android:name=".TestService"
diff --git a/tests/EnforcePermission/service-app/src/android/tests/enforcepermission/service/NestedTestService.java b/tests/EnforcePermission/service-app/src/android/tests/enforcepermission/service/NestedTestService.java
index 7879a12..0f083c9 100644
--- a/tests/EnforcePermission/service-app/src/android/tests/enforcepermission/service/NestedTestService.java
+++ b/tests/EnforcePermission/service-app/src/android/tests/enforcepermission/service/NestedTestService.java
@@ -18,13 +18,21 @@
 
 import android.annotation.EnforcePermission;
 import android.app.Service;
+import android.content.Context;
 import android.content.Intent;
 import android.os.IBinder;
+import android.os.PermissionEnforcer;
 import android.tests.enforcepermission.INested;
 import android.util.Log;
 
 public class NestedTestService extends Service {
     private static final String TAG = "EnforcePermission.NestedTestService";
+    private INested.Stub mBinder;
+
+    @Override
+    public void onCreate() {
+        mBinder = new Stub(this);
+    }
 
     @Override
     public IBinder onBind(Intent intent) {
@@ -32,7 +40,12 @@
         return mBinder;
     }
 
-    private final INested.Stub mBinder = new INested.Stub() {
+    private static class Stub extends INested.Stub {
+
+        Stub(Context context) {
+            super(PermissionEnforcer.fromContext(context));
+        }
+
         @Override
         @EnforcePermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
         public void ProtectedByAccessNetworkState() {
@@ -44,5 +57,5 @@
         public void ProtectedByReadSyncSettings() {
             ProtectedByReadSyncSettings_enforcePermission();
         }
-    };
+    }
 }
diff --git a/tests/EnforcePermission/service-app/src/android/tests/enforcepermission/service/TestService.java b/tests/EnforcePermission/service-app/src/android/tests/enforcepermission/service/TestService.java
index e9b897d..8b809cf 100644
--- a/tests/EnforcePermission/service-app/src/android/tests/enforcepermission/service/TestService.java
+++ b/tests/EnforcePermission/service-app/src/android/tests/enforcepermission/service/TestService.java
@@ -17,11 +17,13 @@
 package android.tests.enforcepermission.service;
 
 import android.annotation.EnforcePermission;
+import android.annotation.RequiresNoPermission;
 import android.app.Service;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.os.IBinder;
+import android.os.PermissionEnforcer;
 import android.os.RemoteException;
 import android.tests.enforcepermission.INested;
 import android.tests.enforcepermission.IProtected;
@@ -36,9 +38,11 @@
 
     private static final String TAG = "EnforcePermission.TestService";
     private volatile ServiceConnection mNestedServiceConnection;
+    private IProtected.Stub mBinder;
 
     @Override
     public void onCreate() {
+        mBinder = new Stub(this);
         mNestedServiceConnection = new ServiceConnection();
         Intent intent = new Intent(this, NestedTestService.class);
         boolean bound = bindService(intent, mNestedServiceConnection, Context.BIND_AUTO_CREATE);
@@ -78,7 +82,12 @@
         return mBinder;
     }
 
-    private final IProtected.Stub mBinder = new IProtected.Stub() {
+    private class Stub extends IProtected.Stub {
+
+        Stub(Context context) {
+            super(PermissionEnforcer.fromContext(context));
+        }
+
         @Override
         @EnforcePermission(android.Manifest.permission.INTERNET)
         public void ProtectedByInternet() {
@@ -105,7 +114,6 @@
             ProtectedByInternetAndAccessNetworkStateImplicitly_enforcePermission();
 
             mNestedServiceConnection.get().ProtectedByAccessNetworkState();
-
         }
 
         @Override
@@ -115,5 +123,65 @@
 
             mNestedServiceConnection.get().ProtectedByReadSyncSettings();
         }
-    };
+
+        @Override
+        @EnforcePermission(android.Manifest.permission.TURN_SCREEN_ON)
+        public void ProtectedByTurnScreenOn() {
+            ProtectedByTurnScreenOn_enforcePermission();
+        }
+
+        @Override
+        @EnforcePermission(android.Manifest.permission.READ_CONTACTS)
+        public void ProtectedByReadContacts() {
+            ProtectedByReadContacts_enforcePermission();
+        }
+
+        @Override
+        @EnforcePermission(android.Manifest.permission.READ_CALENDAR)
+        public void ProtectedByReadCalendar() {
+            ProtectedByReadCalendar_enforcePermission();
+        }
+
+        @Override
+        @EnforcePermission(allOf = {
+                android.Manifest.permission.INTERNET,
+                android.Manifest.permission.VIBRATE})
+        public void ProtectedByInternetAndVibrate() {
+            ProtectedByInternetAndVibrate_enforcePermission();
+        }
+
+        @Override
+        @EnforcePermission(allOf = {
+                android.Manifest.permission.INTERNET,
+                android.Manifest.permission.READ_SYNC_SETTINGS})
+        public void ProtectedByInternetAndReadSyncSettings() {
+            ProtectedByInternetAndReadSyncSettings_enforcePermission();
+        }
+
+        @Override
+        @EnforcePermission(anyOf = {
+                  android.Manifest.permission.ACCESS_WIFI_STATE,
+                  android.Manifest.permission.VIBRATE})
+        public void ProtectedByAccessWifiStateOrVibrate() {
+            ProtectedByAccessWifiStateOrVibrate_enforcePermission();
+        }
+
+        @Override
+        @EnforcePermission(anyOf = {
+                android.Manifest.permission.INTERNET,
+                android.Manifest.permission.VIBRATE})
+        public void ProtectedByInternetOrVibrate() {
+            ProtectedByInternetOrVibrate_enforcePermission();
+        }
+
+        @Override
+        @RequiresNoPermission
+        public void NotProtected() {
+        }
+
+        @Override
+        public void ManuallyProtected() {
+            enforceCallingOrSelfPermission(android.Manifest.permission.INTERNET, "access denied");
+        }
+    }
 }
diff --git a/tests/EnforcePermission/test-app/AndroidManifest.xml b/tests/EnforcePermission/test-app/AndroidManifest.xml
index 4a0c6a8..8bd05d7 100644
--- a/tests/EnforcePermission/test-app/AndroidManifest.xml
+++ b/tests/EnforcePermission/test-app/AndroidManifest.xml
@@ -16,9 +16,20 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
   package="android.tests.enforcepermission.tests">
 
-    <!-- Expected for the tests (not actually used) -->
+    <!-- Expected permissions for the tests (not actually used). These
+         are granted automatically at runtime by Tradefed (see
+         GrantPermissionPreparer). -->
+    <!-- normal -->
     <uses-permission android:name="android.permission.INTERNET" />
     <uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
+    <!-- normal|appops -->
+    <uses-permission android:name="android.permission.TURN_SCREEN_ON" />
+    <!-- dangerous -->
+    <uses-permission android:name="android.permission.READ_CONTACTS" />
+
+    <!-- Used by the tests to activate/deactivate AppOps -->
+    <uses-permission android:name="android.permission.MANAGE_APP_OPS_MODES" />
+    <uses-permission android:name="android.permission.MANAGE_APPOPS" />
 
     <queries>
         <package android:name="android.tests.enforcepermission.service" />
diff --git a/tests/EnforcePermission/test-app/src/android/tests/enforcepermission/tests/ServiceTest.java b/tests/EnforcePermission/test-app/src/android/tests/enforcepermission/tests/ServiceTest.java
index d2a4a03..e09097c 100644
--- a/tests/EnforcePermission/test-app/src/android/tests/enforcepermission/tests/ServiceTest.java
+++ b/tests/EnforcePermission/test-app/src/android/tests/enforcepermission/tests/ServiceTest.java
@@ -21,11 +21,13 @@
 import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
 
+import android.app.AppOpsManager;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.ServiceConnection;
 import android.os.IBinder;
+import android.os.Process;
 import android.os.RemoteException;
 import android.tests.enforcepermission.IProtected;
 import android.util.Log;
@@ -126,4 +128,61 @@
             throws RemoteException {
         mServiceConnection.get().ProtectedByInternetAndReadSyncSettingsImplicitly();
     }
+
+    @Test
+    public void testAppOpPermissionGranted_succeeds() throws RemoteException {
+        AppOpsManager appOpsManager = mContext.getSystemService(AppOpsManager.class);
+        appOpsManager.setUidMode(AppOpsManager.OP_TURN_SCREEN_ON,
+                Process.myUid(), AppOpsManager.MODE_ALLOWED);
+
+        mServiceConnection.get().ProtectedByTurnScreenOn();
+    }
+
+    @Test
+    public void testAppOpPermissionDenied_fails() throws RemoteException {
+        AppOpsManager appOpsManager = mContext.getSystemService(AppOpsManager.class);
+        appOpsManager.setUidMode(AppOpsManager.OP_TURN_SCREEN_ON,
+                Process.myUid(), AppOpsManager.MODE_ERRORED);
+
+        final Exception ex = assertThrows(SecurityException.class,
+                () -> mServiceConnection.get().ProtectedByTurnScreenOn());
+        assertThat(ex.getMessage(), containsString("TURN_SCREEN_ON"));
+    }
+
+    @Test
+    public void testRuntimePermissionGranted_succeeds() throws RemoteException {
+        mServiceConnection.get().ProtectedByReadContacts();
+    }
+
+    @Test
+    public void testRuntimePermissionDenied_fails() throws RemoteException {
+        final Exception ex = assertThrows(SecurityException.class,
+                () -> mServiceConnection.get().ProtectedByReadCalendar());
+        assertThat(ex.getMessage(), containsString("READ_CALENDAR"));
+    }
+
+    @Test
+    public void testAllOfPermissionGranted_succeeds() throws RemoteException {
+        mServiceConnection.get().ProtectedByInternetAndReadSyncSettings();
+    }
+
+    @Test
+    public void testAllOfPermissionDenied_fails() throws RemoteException {
+        final Exception ex = assertThrows(SecurityException.class,
+                () -> mServiceConnection.get().ProtectedByInternetAndVibrate());
+        assertThat(ex.getMessage(), containsString("VIBRATE"));
+    }
+
+    @Test
+    public void testAnyOfPermissionGranted_succeeds() throws RemoteException {
+        mServiceConnection.get().ProtectedByInternetOrVibrate();
+    }
+
+    @Test
+    public void testAnyOfPermissionDenied_fails() throws RemoteException {
+        final Exception ex = assertThrows(SecurityException.class,
+                () -> mServiceConnection.get().ProtectedByAccessWifiStateOrVibrate());
+        assertThat(ex.getMessage(), containsString("VIBRATE"));
+        assertThat(ex.getMessage(), containsString("ACCESS_WIFI_STATE"));
+    }
 }
diff --git a/tests/FixVibrateSetting/Android.bp b/tests/FixVibrateSetting/Android.bp
deleted file mode 100644
index bd7c701..0000000
--- a/tests/FixVibrateSetting/Android.bp
+++ /dev/null
@@ -1,15 +0,0 @@
-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_app {
-    name: "FixVibrateSetting",
-    srcs: ["**/*.java"],
-    sdk_version: "current",
-    certificate: "platform",
-}
diff --git a/tests/FixVibrateSetting/AndroidManifest.xml b/tests/FixVibrateSetting/AndroidManifest.xml
deleted file mode 100644
index c2d5918..0000000
--- a/tests/FixVibrateSetting/AndroidManifest.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-     package="com.android.fixvibratesetting">
-    <uses-permission android:name="android.permission.VIBRATE"/>
-
-    <application>
-        <activity android:name="FixVibrateSetting"
-             android:label="@string/app_label"
-             android:exported="true">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN"/>
-                <category android:name="android.intent.category.DEFAULT"/>
-                <category android:name="android.intent.category.LAUNCHER"/>
-            </intent-filter>
-        </activity>
-    </application>
-</manifest>
diff --git a/tests/FixVibrateSetting/OWNERS b/tests/FixVibrateSetting/OWNERS
deleted file mode 100644
index cc63ceb..0000000
--- a/tests/FixVibrateSetting/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-include /services/core/java/com/android/server/vibrator/OWNERS
diff --git a/tests/FixVibrateSetting/res/drawable-hdpi/stat_sys_warning.png b/tests/FixVibrateSetting/res/drawable-hdpi/stat_sys_warning.png
deleted file mode 100644
index 37c8853..0000000
--- a/tests/FixVibrateSetting/res/drawable-hdpi/stat_sys_warning.png
+++ /dev/null
Binary files differ
diff --git a/tests/FixVibrateSetting/res/drawable-mdpi/stat_sys_warning.png b/tests/FixVibrateSetting/res/drawable-mdpi/stat_sys_warning.png
deleted file mode 100644
index be00f47..0000000
--- a/tests/FixVibrateSetting/res/drawable-mdpi/stat_sys_warning.png
+++ /dev/null
Binary files differ
diff --git a/tests/FixVibrateSetting/res/layout/fix_vibrate.xml b/tests/FixVibrateSetting/res/layout/fix_vibrate.xml
deleted file mode 100644
index c505e65..0000000
--- a/tests/FixVibrateSetting/res/layout/fix_vibrate.xml
+++ /dev/null
@@ -1,46 +0,0 @@
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:orientation="vertical"
-    >
-
-    <TextView android:id="@+id/current_setting"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_gravity="left"
-        android:layout_marginTop="30dp"
-        android:layout_marginBottom="50dp"
-        android:paddingLeft="8dp"
-        android:paddingTop="4dp"
-        android:textSize="20sp"
-        android:textColor="#ffffffff"
-        />
-
-    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:gravity="center"
-        android:orientation="horizontal"
-        >
-        <Button android:id="@+id/fix"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:text="@string/fix"
-            />
-
-        <Button android:id="@+id/unfix"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:text="@string/unfix"
-            />
-    </LinearLayout>
-
-    <Button android:id="@+id/test"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_gravity="center"
-        android:text="@string/test"
-        />
-
-</LinearLayout>
-
diff --git a/tests/FixVibrateSetting/res/values/strings.xml b/tests/FixVibrateSetting/res/values/strings.xml
deleted file mode 100644
index 269cef3..0000000
--- a/tests/FixVibrateSetting/res/values/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2007 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-  
-          http://www.apache.org/licenses/LICENSE-2.0
-  
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT 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>
-    <string name="app_label">Fix Vibrate</string>
-    <string name="title">Fix vibrate setting</string>
-    <string name="current_setting">"Ringer: %1$s\nNotification: %2$s"</string>
-    <string name="fix">Fix the setting</string>
-    <string name="unfix">Break the setting</string>
-    <string name="test">Test the setting</string>
-</resources>
-
diff --git a/tests/FixVibrateSetting/src/com/android/fixvibratesetting/FixVibrateSetting.java b/tests/FixVibrateSetting/src/com/android/fixvibratesetting/FixVibrateSetting.java
deleted file mode 100644
index 761efe4..0000000
--- a/tests/FixVibrateSetting/src/com/android/fixvibratesetting/FixVibrateSetting.java
+++ /dev/null
@@ -1,129 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.fixvibratesetting;
-
-import android.app.Activity;
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.content.Intent;
-import android.media.AudioManager;
-import android.util.Log;
-import android.view.View;
-import android.widget.TextView;
-import android.os.Bundle;
-
-public class FixVibrateSetting extends Activity implements View.OnClickListener
-{
-    AudioManager mAudioManager;
-    NotificationManager mNotificationManager;
-    TextView mCurrentSetting;
-    View mFix;
-    View mUnfix;
-    View mTest;
-
-    @Override
-    public void onCreate(Bundle icicle) {
-        super.onCreate(icicle);
-
-        setContentView(R.layout.fix_vibrate);
-
-        mAudioManager = (AudioManager)getSystemService(AUDIO_SERVICE);
-        mNotificationManager = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
-
-        mCurrentSetting = (TextView)findViewById(R.id.current_setting);
-
-        mFix = findViewById(R.id.fix);
-        mFix.setOnClickListener(this);
-
-        mUnfix = findViewById(R.id.unfix);
-        mUnfix.setOnClickListener(this);
-
-        mTest = findViewById(R.id.test);
-        mTest.setOnClickListener(this);
-    }
-
-    @Override
-    public void onResume() {
-        super.onResume();
-
-        update();
-    }
-
-    private String getSettingValue(int vibrateType) {
-        int setting = mAudioManager.getVibrateSetting(vibrateType);
-        switch (setting) {
-            case AudioManager.VIBRATE_SETTING_OFF:
-                return "off";
-            case AudioManager.VIBRATE_SETTING_ON:
-                return "on";
-            case AudioManager.VIBRATE_SETTING_ONLY_SILENT:
-                return "silent-only";
-            default:
-                return "unknown";
-        }
-    }
-
-    public void onClick(View v) {
-        if (v == mFix) {
-            fix();
-            update();
-        } else if (v == mUnfix) {
-            unfix();
-            update();
-        } else if (v == mTest) {
-            test();
-            update();
-        }
-    }
-
-    private void update() {
-        String ringer = getSettingValue(AudioManager.VIBRATE_TYPE_RINGER);
-        String notification = getSettingValue(AudioManager.VIBRATE_TYPE_NOTIFICATION);
-        String text = getString(R.string.current_setting, ringer, notification);
-        mCurrentSetting.setText(text);
-    }
-
-    private void fix() {
-        mAudioManager.setVibrateSetting(AudioManager.VIBRATE_TYPE_NOTIFICATION,
-                AudioManager.VIBRATE_SETTING_ON);
-    }
-
-    private void unfix() {
-        mAudioManager.setVibrateSetting(AudioManager.VIBRATE_TYPE_NOTIFICATION,
-                AudioManager.VIBRATE_SETTING_OFF);
-    }
-
-    private void test() {
-        Intent intent = new Intent(this, FixVibrateSetting.class);
-        PendingIntent pending = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_IMMUTABLE);
-
-        Notification n = new Notification.Builder(this)
-                .setSmallIcon(R.drawable.stat_sys_warning)
-                .setTicker("Test notification")
-                .setWhen(System.currentTimeMillis())
-                .setContentTitle("Test notification")
-                .setContentText("Test notification")
-                .setContentIntent(pending)
-                .setVibrate(new long[] { 0, 700, 500, 1000 })
-                .setAutoCancel(true)
-                .build();
-
-        mNotificationManager.notify(1, n);
-    }
-}
-
diff --git a/tests/FlickerTests/Android.bp b/tests/FlickerTests/Android.bp
index 4ba538e..72b5159 100644
--- a/tests/FlickerTests/Android.bp
+++ b/tests/FlickerTests/Android.bp
@@ -23,14 +23,62 @@
     default_applicable_licenses: ["frameworks_base_license"],
 }
 
-android_test {
-    name: "FlickerTests",
+filegroup {
+    name: "FlickerTestsBase-src",
+    srcs: ["src/com/android/server/wm/flicker/*.kt"],
+}
+
+filegroup {
+    name: "FlickerTestsAppClose-src",
+    srcs: ["src/**/close/*.kt"],
+}
+
+filegroup {
+    name: "FlickerTestsActivityEmbedding-src",
     srcs: [
-        "src/**/*.java",
-        "src/**/*.kt",
+        "src/**/activityembedding/*.kt",
+        "src/**/activityembedding/open/*.kt",
+        "src/**/activityembedding/close/*.kt",
+        "src/**/activityembedding/rotation/*.kt",
     ],
-    manifest: "AndroidManifest.xml",
-    test_config: "AndroidTest.xml",
+}
+
+filegroup {
+    name: "FlickerTestsIme-src",
+    srcs: ["src/**/ime/*.kt"],
+}
+
+filegroup {
+    name: "FlickerTestsAppLaunch-src",
+    srcs: ["src/**/launch/*.kt"],
+}
+
+filegroup {
+    name: "FlickerTestsNotification-src",
+    srcs: ["src/**/notification/*.kt"],
+}
+
+filegroup {
+    name: "FlickerTestsQuickswitch-src",
+    srcs: ["src/**/quickswitch/*.kt"],
+}
+
+filegroup {
+    name: "FlickerTestsRotation-src",
+    srcs: ["src/**/rotation/*.kt"],
+}
+
+filegroup {
+    name: "FlickerServiceTests-src",
+    srcs: [
+        "src/com/android/server/wm/flicker/service/**/*.kt",
+    ],
+}
+
+java_defaults {
+    name: "FlickerTestsDefault",
+    manifest: "manifests/AndroidManifest.xml",
+    test_config_template: "AndroidTestTemplate.xml",
     platform_apis: true,
     certificate: "platform",
     optimize: {
@@ -42,16 +90,127 @@
         "androidx.test.ext.junit",
         "flickertestapplib",
         "flickerlib",
-        "flickerlib-apphelpers",
         "flickerlib-helpers",
-        "truth-prebuilt",
-        "launcher-helper-lib",
-        "launcher-aosp-tapl",
         "platform-test-annotations",
-        "wm-flicker-window-extensions",
+        "wm-flicker-common-app-helpers",
     ],
     data: [
         ":FlickerTestApp",
+        "trace_config/*",
+    ],
+}
+
+android_test {
+    name: "FlickerTestsOther",
+    defaults: ["FlickerTestsDefault"],
+    additional_manifests: ["manifests/AndroidManifestOther.xml"],
+    package_name: "com.android.server.wm.flicker",
+    instrumentation_target_package: "com.android.server.wm.flicker",
+    srcs: [
+        "src/**/*.java",
+        "src/**/*.kt",
+    ],
+    exclude_srcs: [
+        ":FlickerTestsAppClose-src",
+        ":FlickerTestsIme-src",
+        ":FlickerTestsAppLaunch-src",
+        ":FlickerTestsQuickswitch-src",
+        ":FlickerTestsRotation-src",
+        ":FlickerTestsNotification-src",
+        ":FlickerServiceTests-src",
+    ],
+}
+
+android_test {
+    name: "FlickerTestsAppClose",
+    defaults: ["FlickerTestsDefault"],
+    additional_manifests: ["manifests/AndroidManifestAppClose.xml"],
+    package_name: "com.android.server.wm.flicker.close",
+    instrumentation_target_package: "com.android.server.wm.flicker.close",
+    srcs: [
+        ":FlickerTestsBase-src",
+        ":FlickerTestsAppClose-src",
+    ],
+    exclude_srcs: [
+        ":FlickerTestsActivityEmbedding-src",
+    ],
+}
+
+android_test {
+    name: "FlickerTestsIme",
+    defaults: ["FlickerTestsDefault"],
+    additional_manifests: ["manifests/AndroidManifestIme.xml"],
+    package_name: "com.android.server.wm.flicker.ime",
+    instrumentation_target_package: "com.android.server.wm.flicker.ime",
+    srcs: [
+        ":FlickerTestsBase-src",
+        ":FlickerTestsIme-src",
+    ],
+}
+
+android_test {
+    name: "FlickerTestsAppLaunch",
+    defaults: ["FlickerTestsDefault"],
+    additional_manifests: ["manifests/AndroidManifestAppLaunch.xml"],
+    package_name: "com.android.server.wm.flicker.launch",
+    instrumentation_target_package: "com.android.server.wm.flicker.launch",
+    srcs: [
+        ":FlickerTestsBase-src",
+        ":FlickerTestsAppLaunch-src",
+    ],
+    exclude_srcs: [
+        ":FlickerTestsActivityEmbedding-src",
+    ],
+}
+
+android_test {
+    name: "FlickerTestsNotification",
+    defaults: ["FlickerTestsDefault"],
+    additional_manifests: ["manifests/AndroidManifestNotification.xml"],
+    package_name: "com.android.server.wm.flicker.notification",
+    instrumentation_target_package: "com.android.server.wm.flicker.notification",
+    srcs: [
+        ":FlickerTestsBase-src",
+        ":FlickerTestsNotification-src",
+    ],
+}
+
+android_test {
+    name: "FlickerTestsQuickswitch",
+    defaults: ["FlickerTestsDefault"],
+    additional_manifests: ["manifests/AndroidManifestQuickswitch.xml"],
+    package_name: "com.android.server.wm.flicker.quickswitch",
+    instrumentation_target_package: "com.android.server.wm.flicker.quickswitch",
+    srcs: [
+        ":FlickerTestsBase-src",
+        ":FlickerTestsQuickswitch-src",
+    ],
+}
+
+android_test {
+    name: "FlickerTestsRotation",
+    defaults: ["FlickerTestsDefault"],
+    additional_manifests: ["manifests/AndroidManifestRotation.xml"],
+    package_name: "com.android.server.wm.flicker.rotation",
+    instrumentation_target_package: "com.android.server.wm.flicker.rotation",
+    srcs: [
+        ":FlickerTestsBase-src",
+        ":FlickerTestsRotation-src",
+    ],
+    exclude_srcs: [
+        ":FlickerTestsActivityEmbedding-src",
+    ],
+}
+
+android_test {
+    name: "FlickerServiceTests",
+    defaults: ["FlickerTestsDefault"],
+    additional_manifests: ["manifests/AndroidManifestService.xml"],
+    package_name: "com.android.server.wm.flicker.service",
+    instrumentation_target_package: "com.android.server.wm.flicker.service",
+    srcs: [
+        ":FlickerTestsBase-src",
+        ":FlickerServiceTests-src",
     ],
 }
 
diff --git a/tests/FlickerTests/AndroidManifest.xml b/tests/FlickerTests/AndroidManifest.xml
deleted file mode 100644
index 462f91b..0000000
--- a/tests/FlickerTests/AndroidManifest.xml
+++ /dev/null
@@ -1,55 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2018 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="com.android.server.wm.flicker">
-
-    <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29"/>
-    <!-- Read and write traces from external storage -->
-    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
-    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
-    <!-- Allow the test to write directly to /sdcard/ -->
-    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
-    <!-- Write secure settings -->
-    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
-    <!-- Capture screen contents -->
-    <uses-permission android:name="android.permission.ACCESS_SURFACE_FLINGER" />
-    <!-- Enable / Disable tracing !-->
-    <uses-permission android:name="android.permission.DUMP" />
-    <!-- Force-stop test apps -->
-    <uses-permission android:name="android.permission.FORCE_STOP_PACKAGES"/>
-    <!-- Run layers trace -->
-    <uses-permission android:name="android.permission.HARDWARE_TEST"/>
-    <!-- Capture screen recording -->
-    <uses-permission android:name="android.permission.CAPTURE_VIDEO_OUTPUT"/>
-    <!-- Workaround grant runtime permission exception from b/152733071 -->
-    <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS"/>
-    <uses-permission android:name="android.permission.READ_LOGS"/>
-    <!-- ATM.removeRootTasksWithActivityTypes() -->
-    <uses-permission android:name="android.permission.MANAGE_ACTIVITY_TASKS" />
-    <!-- ActivityOptions.makeCustomTaskAnimation() -->
-    <uses-permission android:name="android.permission.START_TASKS_FROM_RECENTS" />
-    <!-- Allow the test to write directly to /sdcard/ -->
-    <application android:requestLegacyExternalStorage="true" android:largeHeap="true">
-        <uses-library android:name="android.test.runner"/>
-        <uses-library android:name="androidx.window.extensions" android:required="false"/>
-    </application>
-
-    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="com.android.server.wm.flicker"
-                     android:label="WindowManager Flicker Tests">
-    </instrumentation>
-</manifest>
diff --git a/tests/FlickerTests/AndroidTest.xml b/tests/FlickerTests/AndroidTest.xml
deleted file mode 100644
index 32ff243..0000000
--- a/tests/FlickerTests/AndroidTest.xml
+++ /dev/null
@@ -1,53 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- * Copyright 2018 Google Inc. All Rights Reserved.
- -->
-<configuration description="Runs WindowManager Flicker Tests">
-    <option name="test-tag" value="FlickerTests" />
-    <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
-        <!-- keeps the screen on during tests -->
-        <option name="screen-always-on" value="on" />
-        <!-- prevents the phone from restarting -->
-        <option name="force-skip-system-props" value="true" />
-        <!-- set WM tracing verbose level to all -->
-        <option name="run-command" value="cmd window tracing level all" />
-        <!-- set WM tracing to frame (avoid incomplete states) -->
-        <option name="run-command" value="cmd window tracing frame" />
-        <!-- ensure lock screen mode is swipe -->
-        <option name="run-command" value="locksettings set-disabled false" />
-        <!-- disable betterbug as it's log collection dialogues cause flakes in e2e tests -->
-        <option name="run-command" value="pm disable com.google.android.internal.betterbug" />
-        <!-- restart launcher to activate TAPL -->
-        <option name="run-command" value="setprop ro.test_harness 1 ; am force-stop com.google.android.apps.nexuslauncher" />
-        <!-- Ensure output directory is empty at the start -->
-        <option name="run-command" value="rm -rf /sdcard/flicker" />
-        <!-- Increase trace size: 20mb for WM and 80mb for SF -->
-        <option name="run-command" value="cmd window tracing size 20480" />
-        <option name="run-command" value="su root service call SurfaceFlinger 1029 i32 81920" />
-    </target_preparer>
-    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
-        <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="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" />
-    </target_preparer>
-    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
-        <option name="cleanup-apks" value="true"/>
-        <option name="test-file-name" value="FlickerTests.apk"/>
-        <option name="test-file-name" value="FlickerTestApp.apk" />
-    </target_preparer>
-    <test class="com.android.tradefed.testtype.AndroidJUnitTest">
-        <option name="package" value="com.android.server.wm.flicker"/>
-        <option name="shell-timeout" value="6600s" />
-        <option name="test-timeout" value="6600s" />
-        <option name="hidden-api-checks" value="false" />
-    </test>
-    <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
-        <option name="directory-keys" value="/sdcard/flicker" />
-        <option name="collect-on-run-ended-only" value="true" />
-        <option name="clean-up" value="true" />
-    </metrics_collector>
-</configuration>
diff --git a/tests/FlickerTests/AndroidTestTemplate.xml b/tests/FlickerTests/AndroidTestTemplate.xml
new file mode 100644
index 0000000..44a8245
--- /dev/null
+++ b/tests/FlickerTests/AndroidTestTemplate.xml
@@ -0,0 +1,95 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright 2018 Google Inc. All Rights Reserved.
+ -->
+<configuration description="Runs WindowManager {MODULE}">
+    <option name="test-tag" value="FlickerTests"/>
+    <!-- Needed for storing the perfetto trace files in the sdcard/test_results-->
+    <option name="isolated-storage" value="false"/>
+
+    <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+        <!-- keeps the screen on during tests -->
+        <option name="screen-always-on" value="on"/>
+        <!-- prevents the phone from restarting -->
+        <option name="force-skip-system-props" value="true"/>
+        <!-- set WM tracing verbose level to all -->
+        <option name="run-command" value="cmd window tracing level all"/>
+        <!-- set WM tracing to frame (avoid incomplete states) -->
+        <option name="run-command" value="cmd window tracing frame"/>
+        <!-- ensure lock screen mode is swipe -->
+        <option name="run-command" value="locksettings set-disabled false"/>
+        <!-- disable betterbug as it's log collection dialogues cause flakes in e2e tests -->
+        <option name="run-command" value="pm disable com.google.android.internal.betterbug"/>
+        <!-- restart launcher to activate TAPL -->
+        <option name="run-command"
+                value="setprop ro.test_harness 1 ; am force-stop com.google.android.apps.nexuslauncher"/>
+        <!-- Increase trace size: 20mb for WM and 80mb for SF -->
+        <option name="run-command" value="cmd window tracing size 20480"/>
+        <option name="run-command" value="su root service call SurfaceFlinger 1029 i32 81920"/>
+    </target_preparer>
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <option name="test-user-token" value="%TEST_USER%"/>
+        <option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/>
+        <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="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"/>
+    </target_preparer>
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true"/>
+        <option name="test-file-name" value="{MODULE}.apk"/>
+        <option name="test-file-name" value="FlickerTestApp.apk"/>
+    </target_preparer>
+    <!-- Needed for pushing the trace config file -->
+    <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
+    <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+        <option name="push-file"
+                key="trace_config.textproto"
+                value="/data/misc/perfetto-traces/trace_config.textproto"
+        />
+        <!--Install the content provider automatically when we push some file in sdcard folder.-->
+        <!--Needed to avoid the installation during the test suite.-->
+        <option name="push-file" key="trace_config.textproto" value="/sdcard/sample.textproto"/>
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+        <option name="package" value="{PACKAGE}"/>
+        <option name="shell-timeout" value="6600s"/>
+        <option name="test-timeout" value="6600s"/>
+        <option name="hidden-api-checks" value="false"/>
+        <option name="device-listeners" value="android.device.collectors.PerfettoListener"/>
+        <!-- PerfettoListener related arguments -->
+        <option name="instrumentation-arg" key="perfetto_config_text_proto" value="true"/>
+        <option name="instrumentation-arg"
+                key="perfetto_config_file"
+                value="trace_config.textproto"
+        />
+        <option name="instrumentation-arg" key="per_run" value="true"/>
+    </test>
+    <!-- Needed for pulling the collected trace config on to the host -->
+    <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+        <option name="pull-pattern-keys" value="perfetto_file_path"/>
+        <option name="directory-keys"
+                value="/data/user/0/com.android.server.wm.flicker/files"/>
+        <option name="directory-keys"
+                value="/data/user/0/com.android.server.wm.flicker.close/files"/>
+        <option name="directory-keys"
+                value="/data/user/0/com.android.server.wm.flicker.ime/files"/>
+        <option name="directory-keys"
+                value="/data/user/0/com.android.server.wm.flicker.launch/files"/>
+        <option name="directory-keys"
+                value="/data/user/0/com.android.server.wm.flicker.quickswitch/files"/>
+        <option name="directory-keys"
+                value="/data/user/0/com.android.server.wm.flicker.rotation/files"/>
+        <option name="directory-keys"
+                value="/data/user/0/com.android.server.wm.flicker.notification/files"/>
+        <option name="directory-keys"
+            value="/data/user/0/com.android.server.wm.flicker.service/files"/>
+        <option name="collect-on-run-ended-only" value="true"/>
+        <option name="clean-up" value="true"/>
+    </metrics_collector>
+</configuration>
diff --git a/tests/FlickerTests/manifests/AndroidManifest.xml b/tests/FlickerTests/manifests/AndroidManifest.xml
new file mode 100644
index 0000000..1a34d9e
--- /dev/null
+++ b/tests/FlickerTests/manifests/AndroidManifest.xml
@@ -0,0 +1,58 @@
+<?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"
+          xmlns:tools="http://schemas.android.com/tools"
+          package="com.android.server.wm.flicker">
+
+    <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29"/>
+    <!-- Read and write traces from external storage -->
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+    <!-- Allow the test to write directly to /sdcard/ -->
+    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
+    <!-- Write secure settings -->
+    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
+    <!-- Capture screen contents -->
+    <uses-permission android:name="android.permission.ACCESS_SURFACE_FLINGER" />
+    <!-- Enable / Disable tracing !-->
+    <uses-permission android:name="android.permission.DUMP" />
+    <!-- Force-stop test apps -->
+    <uses-permission android:name="android.permission.FORCE_STOP_PACKAGES"/>
+    <!-- Run layers trace -->
+    <uses-permission android:name="android.permission.HARDWARE_TEST"/>
+    <!-- Capture screen recording -->
+    <uses-permission android:name="android.permission.CAPTURE_VIDEO_OUTPUT"/>
+    <!-- Workaround grant runtime permission exception from b/152733071 -->
+    <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS"/>
+    <uses-permission android:name="android.permission.READ_LOGS"/>
+    <!-- ATM.removeRootTasksWithActivityTypes() -->
+    <uses-permission android:name="android.permission.MANAGE_ACTIVITY_TASKS" />
+    <!-- ActivityOptions.makeCustomTaskAnimation() -->
+    <uses-permission android:name="android.permission.START_TASKS_FROM_RECENTS" />
+    <!-- Allow the test to write directly to /sdcard/ -->
+    <application android:requestLegacyExternalStorage="true" android:largeHeap="true">
+        <uses-library android:name="android.test.runner"/>
+        <uses-library android:name="androidx.window.extensions" android:required="false"/>
+
+        <!-- (b/197936012) Remove startup provider due to test timeout issue -->
+        <provider
+            android:name="androidx.startup.InitializationProvider"
+            android:authorities="${applicationId}.androidx-startup"
+            tools:node="remove" />
+    </application>
+</manifest>
diff --git a/tests/FlickerTests/manifests/AndroidManifestAppClose.xml b/tests/FlickerTests/manifests/AndroidManifestAppClose.xml
new file mode 100644
index 0000000..4cdcb90
--- /dev/null
+++ b/tests/FlickerTests/manifests/AndroidManifestAppClose.xml
@@ -0,0 +1,24 @@
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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.flicker.close">
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.server.wm.flicker.close"
+                     android:label="WindowManager Flicker Tests">
+    </instrumentation>
+</manifest>
diff --git a/tests/FlickerTests/manifests/AndroidManifestAppLaunch.xml b/tests/FlickerTests/manifests/AndroidManifestAppLaunch.xml
new file mode 100644
index 0000000..659a745
--- /dev/null
+++ b/tests/FlickerTests/manifests/AndroidManifestAppLaunch.xml
@@ -0,0 +1,24 @@
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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.flicker.launch">
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.server.wm.flicker.launch"
+                     android:label="WindowManager Flicker Tests">
+    </instrumentation>
+</manifest>
diff --git a/tests/FlickerTests/manifests/AndroidManifestIme.xml b/tests/FlickerTests/manifests/AndroidManifestIme.xml
new file mode 100644
index 0000000..abd03af
--- /dev/null
+++ b/tests/FlickerTests/manifests/AndroidManifestIme.xml
@@ -0,0 +1,24 @@
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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.flicker.ime">
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.server.wm.flicker.ime"
+                     android:label="WindowManager Flicker Tests">
+    </instrumentation>
+</manifest>
diff --git a/tests/FlickerTests/manifests/AndroidManifestNotification.xml b/tests/FlickerTests/manifests/AndroidManifestNotification.xml
new file mode 100644
index 0000000..ad33dee
--- /dev/null
+++ b/tests/FlickerTests/manifests/AndroidManifestNotification.xml
@@ -0,0 +1,24 @@
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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.flicker.close">
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.server.wm.flicker.notification"
+                     android:label="WindowManager Flicker Tests">
+    </instrumentation>
+</manifest>
diff --git a/tests/FlickerTests/manifests/AndroidManifestOther.xml b/tests/FlickerTests/manifests/AndroidManifestOther.xml
new file mode 100644
index 0000000..47749b8
--- /dev/null
+++ b/tests/FlickerTests/manifests/AndroidManifestOther.xml
@@ -0,0 +1,24 @@
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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.flicker">
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.server.wm.flicker"
+                     android:label="WindowManager Flicker Tests">
+    </instrumentation>
+</manifest>
diff --git a/tests/FlickerTests/manifests/AndroidManifestQuickswitch.xml b/tests/FlickerTests/manifests/AndroidManifestQuickswitch.xml
new file mode 100644
index 0000000..203035d
--- /dev/null
+++ b/tests/FlickerTests/manifests/AndroidManifestQuickswitch.xml
@@ -0,0 +1,24 @@
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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.flicker.quickswitch">
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.server.wm.flicker.quickswitch"
+                     android:label="WindowManager Flicker Tests">
+    </instrumentation>
+</manifest>
diff --git a/tests/FlickerTests/manifests/AndroidManifestRotation.xml b/tests/FlickerTests/manifests/AndroidManifestRotation.xml
new file mode 100644
index 0000000..2852cf2
--- /dev/null
+++ b/tests/FlickerTests/manifests/AndroidManifestRotation.xml
@@ -0,0 +1,24 @@
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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.flicker.rotation">
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.server.wm.flicker.rotation"
+                     android:label="WindowManager Flicker Tests">
+    </instrumentation>
+</manifest>
diff --git a/tests/FlickerTests/manifests/AndroidManifestService.xml b/tests/FlickerTests/manifests/AndroidManifestService.xml
new file mode 100644
index 0000000..3a7bc509
--- /dev/null
+++ b/tests/FlickerTests/manifests/AndroidManifestService.xml
@@ -0,0 +1,24 @@
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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.flicker.service">
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.server.wm.flicker.service"
+                     android:label="WindowManager Flicker Service Tests">
+    </instrumentation>
+</manifest>
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 efd9b00..7c9c05d 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
@@ -21,7 +21,7 @@
 import android.tools.common.traces.component.ComponentNameMatcher
 import android.tools.device.flicker.junit.FlickerBuilderProvider
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.util.Log
 import androidx.test.platform.app.InstrumentationRegistry
 import com.android.launcher3.tapl.LauncherInstrumentation
@@ -37,7 +37,7 @@
 abstract class BaseTest
 @JvmOverloads
 constructor(
-    protected val flicker: FlickerTest,
+    protected val flicker: LegacyFlickerTest,
     protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation(),
     protected val tapl: LauncherInstrumentation = LauncherInstrumentation()
 ) {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
index ed9e14f..4d36111 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
@@ -19,18 +19,20 @@
 package com.android.server.wm.flicker
 
 import android.tools.common.PlatformConsts
+import android.tools.common.Position
+import android.tools.common.flicker.subject.layers.LayerTraceEntrySubject
 import android.tools.common.flicker.subject.region.RegionSubject
 import android.tools.common.traces.component.ComponentNameMatcher
 import android.tools.common.traces.component.IComponentNameMatcher
 import android.tools.common.traces.wm.WindowManagerTrace
-import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.helpers.WindowUtils
 
 /**
  * Checks that [ComponentNameMatcher.STATUS_BAR] window is visible and above the app windows in all
  * WM trace entries
  */
-fun FlickerTest.statusBarWindowIsAlwaysVisible() {
+fun LegacyFlickerTest.statusBarWindowIsAlwaysVisible() {
     assertWm { this.isAboveAppWindowVisible(ComponentNameMatcher.STATUS_BAR) }
 }
 
@@ -38,7 +40,7 @@
  * Checks that [ComponentNameMatcher.NAV_BAR] window is visible and above the app windows in all WM
  * trace entries
  */
-fun FlickerTest.navBarWindowIsAlwaysVisible() {
+fun LegacyFlickerTest.navBarWindowIsAlwaysVisible() {
     assertWm { this.isAboveAppWindowVisible(ComponentNameMatcher.NAV_BAR) }
 }
 
@@ -46,7 +48,7 @@
  * Checks that [ComponentNameMatcher.NAV_BAR] window is visible and above the app windows at the
  * start and end of the WM trace
  */
-fun FlickerTest.navBarWindowIsVisibleAtStartAndEnd() {
+fun LegacyFlickerTest.navBarWindowIsVisibleAtStartAndEnd() {
     this.navBarWindowIsVisibleAtStart()
     this.navBarWindowIsVisibleAtEnd()
 }
@@ -55,7 +57,7 @@
  * Checks that [ComponentNameMatcher.NAV_BAR] window is visible and above the app windows at the
  * start of the WM trace
  */
-fun FlickerTest.navBarWindowIsVisibleAtStart() {
+fun LegacyFlickerTest.navBarWindowIsVisibleAtStart() {
     assertWmStart { this.isAboveAppWindowVisible(ComponentNameMatcher.NAV_BAR) }
 }
 
@@ -63,7 +65,7 @@
  * Checks that [ComponentNameMatcher.NAV_BAR] window is visible and above the app windows at the end
  * of the WM trace
  */
-fun FlickerTest.navBarWindowIsVisibleAtEnd() {
+fun LegacyFlickerTest.navBarWindowIsVisibleAtEnd() {
     assertWmEnd { this.isAboveAppWindowVisible(ComponentNameMatcher.NAV_BAR) }
 }
 
@@ -71,7 +73,7 @@
  * Checks that [ComponentNameMatcher.TASK_BAR] window is visible and above the app windows in all WM
  * trace entries
  */
-fun FlickerTest.taskBarWindowIsAlwaysVisible() {
+fun LegacyFlickerTest.taskBarWindowIsAlwaysVisible() {
     assertWm { this.isAboveAppWindowVisible(ComponentNameMatcher.TASK_BAR) }
 }
 
@@ -79,7 +81,7 @@
  * Checks that [ComponentNameMatcher.TASK_BAR] window is visible and above the app windows in all WM
  * trace entries
  */
-fun FlickerTest.taskBarWindowIsVisibleAtEnd() {
+fun LegacyFlickerTest.taskBarWindowIsVisibleAtEnd() {
     assertWmEnd { this.isAboveAppWindowVisible(ComponentNameMatcher.TASK_BAR) }
 }
 
@@ -93,43 +95,45 @@
  * @param allStates if all states should be checked, othersie, just initial and final
  */
 @JvmOverloads
-fun FlickerTest.entireScreenCovered(allStates: Boolean = true) {
+fun LegacyFlickerTest.entireScreenCovered(allStates: Boolean = true) {
     if (allStates) {
         assertLayers {
             this.invoke("entireScreenCovered") { entry ->
-                entry.entry.displays.filter { it.isOn }.forEach { display ->
-                    entry.visibleRegion().coversAtLeast(display.layerStackSpace)
-                }
+                entry.entry.displays
+                    .filter { it.isOn }
+                    .forEach { display ->
+                        entry.visibleRegion().coversAtLeast(display.layerStackSpace)
+                    }
             }
         }
     } else {
         assertLayersStart {
-            this.entry.displays.filter { it.isOn }.forEach { display ->
-                this.visibleRegion().coversAtLeast(display.layerStackSpace)
-            }
+            this.entry.displays
+                .filter { it.isOn }
+                .forEach { display -> this.visibleRegion().coversAtLeast(display.layerStackSpace) }
         }
         assertLayersEnd {
-            this.entry.displays.filter { it.isOn }.forEach { display ->
-                this.visibleRegion().coversAtLeast(display.layerStackSpace)
-            }
+            this.entry.displays
+                .filter { it.isOn }
+                .forEach { display -> this.visibleRegion().coversAtLeast(display.layerStackSpace) }
         }
     }
 }
 
 /** Checks that [ComponentNameMatcher.NAV_BAR] layer is visible at the start of the SF trace */
-fun FlickerTest.navBarLayerIsVisibleAtStart() {
+fun LegacyFlickerTest.navBarLayerIsVisibleAtStart() {
     assertLayersStart { this.isVisible(ComponentNameMatcher.NAV_BAR) }
 }
 
 /** Checks that [ComponentNameMatcher.NAV_BAR] layer is visible at the end of the SF trace */
-fun FlickerTest.navBarLayerIsVisibleAtEnd() {
+fun LegacyFlickerTest.navBarLayerIsVisibleAtEnd() {
     assertLayersEnd { this.isVisible(ComponentNameMatcher.NAV_BAR) }
 }
 
 /**
  * Checks that [ComponentNameMatcher.NAV_BAR] layer is visible at the start and end of the SF trace
  */
-fun FlickerTest.navBarLayerIsVisibleAtStartAndEnd() {
+fun LegacyFlickerTest.navBarLayerIsVisibleAtStartAndEnd() {
     this.navBarLayerIsVisibleAtStart()
     this.navBarLayerIsVisibleAtEnd()
 }
@@ -137,18 +141,18 @@
 /**
  * Checks that [ComponentNameMatcher.TASK_BAR] layer is visible at the start and end of the SF trace
  */
-fun FlickerTest.taskBarLayerIsVisibleAtStartAndEnd() {
+fun LegacyFlickerTest.taskBarLayerIsVisibleAtStartAndEnd() {
     this.taskBarLayerIsVisibleAtStart()
     this.taskBarLayerIsVisibleAtEnd()
 }
 
 /** Checks that [ComponentNameMatcher.TASK_BAR] layer is visible at the start of the SF trace */
-fun FlickerTest.taskBarLayerIsVisibleAtStart() {
+fun LegacyFlickerTest.taskBarLayerIsVisibleAtStart() {
     assertLayersStart { this.isVisible(ComponentNameMatcher.TASK_BAR) }
 }
 
 /** Checks that [ComponentNameMatcher.TASK_BAR] layer is visible at the end of the SF trace */
-fun FlickerTest.taskBarLayerIsVisibleAtEnd() {
+fun LegacyFlickerTest.taskBarLayerIsVisibleAtEnd() {
     assertLayersEnd { this.isVisible(ComponentNameMatcher.TASK_BAR) }
 }
 
@@ -156,7 +160,7 @@
  * Checks that [ComponentNameMatcher.STATUS_BAR] layer is visible at the start and end of the SF
  * trace
  */
-fun FlickerTest.statusBarLayerIsVisibleAtStartAndEnd() {
+fun LegacyFlickerTest.statusBarLayerIsVisibleAtStartAndEnd() {
     assertLayersStart { this.isVisible(ComponentNameMatcher.STATUS_BAR) }
     assertLayersEnd { this.isVisible(ComponentNameMatcher.STATUS_BAR) }
 }
@@ -165,14 +169,9 @@
  * Asserts that the [ComponentNameMatcher.NAV_BAR] layer is at the correct position at the start of
  * the SF trace
  */
-fun FlickerTest.navBarLayerPositionAtStart() {
+fun LegacyFlickerTest.navBarLayerPositionAtStart() {
     assertLayersStart {
-        val display =
-            this.entry.displays.firstOrNull { !it.isVirtual } ?: error("There is no display!")
-        this.visibleRegion(ComponentNameMatcher.NAV_BAR)
-            .coversExactly(
-                WindowUtils.getNavigationBarPosition(display, scenario.isGesturalNavigation)
-            )
+        assertNavBarPosition(this, scenario.isGesturalNavigation)
     }
 }
 
@@ -180,15 +179,38 @@
  * Asserts that the [ComponentNameMatcher.NAV_BAR] layer is at the correct position at the end of
  * the SF trace
  */
-fun FlickerTest.navBarLayerPositionAtEnd() {
+fun LegacyFlickerTest.navBarLayerPositionAtEnd() {
     assertLayersEnd {
-        val display =
-            this.entry.displays.minByOrNull { it.id }
-                ?: throw RuntimeException("There is no display!")
-        this.visibleRegion(ComponentNameMatcher.NAV_BAR)
-            .coversExactly(
-                WindowUtils.getNavigationBarPosition(display, scenario.isGesturalNavigation)
-            )
+        assertNavBarPosition(this, scenario.isGesturalNavigation)
+    }
+}
+
+private fun assertNavBarPosition(sfState: LayerTraceEntrySubject, isGesturalNavigation: Boolean) {
+    val display =
+        sfState.entry.displays.filterNot { it.isOff }.minByOrNull { it.id }
+            ?: error("There is no display!")
+    val displayArea = display.layerStackSpace
+    val navBarPosition = display.navBarPosition(isGesturalNavigation)
+    val navBarRegion = sfState.visibleRegion(ComponentNameMatcher.NAV_BAR)
+
+    when (navBarPosition) {
+        Position.TOP ->
+            navBarRegion.hasSameTopPosition(displayArea)
+                .hasSameLeftPosition(displayArea)
+                .hasSameRightPosition(displayArea)
+        Position.BOTTOM ->
+            navBarRegion.hasSameBottomPosition(displayArea)
+                .hasSameLeftPosition(displayArea)
+                .hasSameRightPosition(displayArea)
+        Position.LEFT ->
+            navBarRegion.hasSameLeftPosition(displayArea)
+                .hasSameTopPosition(displayArea)
+                .hasSameBottomPosition(displayArea)
+        Position.RIGHT ->
+            navBarRegion.hasSameRightPosition(displayArea)
+                .hasSameTopPosition(displayArea)
+                .hasSameBottomPosition(displayArea)
+        else -> error("Unknown position $navBarPosition")
     }
 }
 
@@ -196,7 +218,7 @@
  * Asserts that the [ComponentNameMatcher.NAV_BAR] layer is at the correct position at the start and
  * end of the SF trace
  */
-fun FlickerTest.navBarLayerPositionAtStartAndEnd() {
+fun LegacyFlickerTest.navBarLayerPositionAtStartAndEnd() {
     navBarLayerPositionAtStart()
     navBarLayerPositionAtEnd()
 }
@@ -205,7 +227,7 @@
  * Asserts that the [ComponentNameMatcher.STATUS_BAR] layer is at the correct position at the start
  * of the SF trace
  */
-fun FlickerTest.statusBarLayerPositionAtStart(
+fun LegacyFlickerTest.statusBarLayerPositionAtStart(
     wmTrace: WindowManagerTrace? = this.reader.readWmTrace()
 ) {
     // collect navbar position for the equivalent WM state
@@ -221,7 +243,7 @@
  * Asserts that the [ComponentNameMatcher.STATUS_BAR] layer is at the correct position at the end of
  * the SF trace
  */
-fun FlickerTest.statusBarLayerPositionAtEnd(
+fun LegacyFlickerTest.statusBarLayerPositionAtEnd(
     wmTrace: WindowManagerTrace? = this.reader.readWmTrace()
 ) {
     // collect navbar position for the equivalent WM state
@@ -237,7 +259,7 @@
  * Asserts that the [ComponentNameMatcher.STATUS_BAR] layer is at the correct position at the start
  * and end of the SF trace
  */
-fun FlickerTest.statusBarLayerPositionAtStartAndEnd() {
+fun LegacyFlickerTest.statusBarLayerPositionAtStartAndEnd() {
     statusBarLayerPositionAtStart()
     statusBarLayerPositionAtEnd()
 }
@@ -246,25 +268,25 @@
  * Asserts that the visibleRegion of the [ComponentNameMatcher.SNAPSHOT] layer can cover the
  * visibleRegion of the given app component exactly
  */
-fun FlickerTest.snapshotStartingWindowLayerCoversExactlyOnApp(component: IComponentNameMatcher) {
+fun LegacyFlickerTest.snapshotStartingWindowLayerCoversExactlyOnApp(
+    component: IComponentNameMatcher
+) {
     assertLayers {
         invoke("snapshotStartingWindowLayerCoversExactlyOnApp") {
             val snapshotLayers =
                 it.subjects.filter { subject ->
-                    subject.name.contains(ComponentNameMatcher.SNAPSHOT.toLayerName()) &&
+                    ComponentNameMatcher.SNAPSHOT.layerMatchesAnyOf(subject.layer) &&
                         subject.isVisible
                 }
+            val visibleAreas =
+                snapshotLayers
+                    .mapNotNull { snapshotLayer -> snapshotLayer.layer.visibleRegion }
+                    .toTypedArray()
+            val snapshotRegion = RegionSubject(visibleAreas, timestamp)
             // Verify the size of snapshotRegion covers appVisibleRegion exactly in animation.
-            if (snapshotLayers.isNotEmpty()) {
-                val visibleAreas =
-                    snapshotLayers
-                        .mapNotNull { snapshotLayer -> snapshotLayer.layer.visibleRegion }
-                        .toTypedArray()
-                val snapshotRegion = RegionSubject(visibleAreas, timestamp)
+            if (snapshotRegion.region.isNotEmpty) {
                 val appVisibleRegion = it.visibleRegion(component)
-                if (snapshotRegion.region.isNotEmpty) {
-                    snapshotRegion.coversExactly(appVisibleRegion.region)
-                }
+                snapshotRegion.coversExactly(appVisibleRegion.region)
             }
         }
     }
@@ -307,7 +329,7 @@
  *      otherwise we won't and the layer must appear immediately.
  * ```
  */
-fun FlickerTest.replacesLayer(
+fun LegacyFlickerTest.replacesLayer(
     originalLayer: IComponentNameMatcher,
     newLayer: IComponentNameMatcher,
     ignoreEntriesWithRotationLayer: Boolean = false,
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/ActivityEmbeddingTestBase.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/ActivityEmbeddingTestBase.kt
index 7ef4d93..45cd65d 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/ActivityEmbeddingTestBase.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/ActivityEmbeddingTestBase.kt
@@ -16,12 +16,12 @@
 
 package com.android.server.wm.flicker.activityembedding
 
-import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTest
 import com.android.server.wm.flicker.BaseTest
 import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper
 import org.junit.Before
 
-abstract class ActivityEmbeddingTestBase(flicker: FlickerTest) : BaseTest(flicker) {
+abstract class ActivityEmbeddingTestBase(flicker: LegacyFlickerTest) : BaseTest(flicker) {
     val testApp = ActivityEmbeddingAppHelper(instrumentation)
 
     @Before
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/OpenActivityEmbeddingPlaceholderSplitTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/OpenActivityEmbeddingPlaceholderSplitTest.kt
deleted file mode 100644
index ed17059..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/OpenActivityEmbeddingPlaceholderSplitTest.kt
+++ /dev/null
@@ -1,129 +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.server.wm.flicker.activityembedding
-
-import android.platform.test.annotations.Presubmit
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test opening an activity that will launch another activity as ActivityEmbedding placeholder in
- * split.
- *
- * To run this test: `atest FlickerTests:OpenActivityEmbeddingPlaceholderSplitTest`
- */
-@RequiresDevice
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class OpenActivityEmbeddingPlaceholderSplitTest(flicker: FlickerTest) :
-    ActivityEmbeddingTestBase(flicker) {
-
-    /** {@inheritDoc} */
-    override val transition: FlickerBuilder.() -> Unit = {
-        setup {
-            tapl.setExpectedRotationCheckEnabled(false)
-            testApp.launchViaIntent(wmHelper)
-        }
-        transitions { testApp.launchPlaceholderSplit(wmHelper) }
-        teardown {
-            tapl.goHome()
-            testApp.exit(wmHelper)
-        }
-    }
-
-    /** Main activity should become invisible after launching the placeholder primary activity. */
-    @Presubmit
-    @Test
-    fun mainActivityWindowBecomesInvisible() {
-        flicker.assertWm {
-            isAppWindowVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
-                .then()
-                .isAppWindowInvisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
-        }
-    }
-
-    /** Main activity should become invisible after launching the placeholder primary activity. */
-    @Presubmit
-    @Test
-    fun mainActivityLayerBecomesInvisible() {
-        flicker.assertLayers {
-            isVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
-                .then()
-                .isInvisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
-        }
-    }
-
-    /**
-     * Placeholder primary and secondary should become visible after launch. The windows are not
-     * necessarily to become visible at the same time.
-     */
-    @Presubmit
-    @Test
-    fun placeholderSplitWindowsBecomeVisible() {
-        flicker.assertWm {
-            notContains(ActivityEmbeddingAppHelper.PLACEHOLDER_PRIMARY_COMPONENT)
-                .then()
-                .isAppWindowInvisible(ActivityEmbeddingAppHelper.PLACEHOLDER_PRIMARY_COMPONENT)
-                .then()
-                .isAppWindowVisible(ActivityEmbeddingAppHelper.PLACEHOLDER_PRIMARY_COMPONENT)
-        }
-        flicker.assertWm {
-            notContains(ActivityEmbeddingAppHelper.PLACEHOLDER_SECONDARY_COMPONENT)
-                .then()
-                .isAppWindowInvisible(ActivityEmbeddingAppHelper.PLACEHOLDER_SECONDARY_COMPONENT)
-                .then()
-                .isAppWindowVisible(ActivityEmbeddingAppHelper.PLACEHOLDER_SECONDARY_COMPONENT)
-        }
-    }
-
-    /** Placeholder primary and secondary should become visible together after launch. */
-    @Presubmit
-    @Test
-    fun placeholderSplitLayersBecomeVisible() {
-        flicker.assertLayers {
-            isInvisible(ActivityEmbeddingAppHelper.PLACEHOLDER_PRIMARY_COMPONENT)
-            isInvisible(ActivityEmbeddingAppHelper.PLACEHOLDER_SECONDARY_COMPONENT)
-                .then()
-                .isVisible(ActivityEmbeddingAppHelper.PLACEHOLDER_PRIMARY_COMPONENT)
-                .isVisible(ActivityEmbeddingAppHelper.PLACEHOLDER_SECONDARY_COMPONENT)
-        }
-    }
-
-    companion object {
-        /**
-         * Creates the test configurations.
-         *
-         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
-         * navigation modes.
-         */
-        @Parameterized.Parameters(name = "{0}")
-        @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests()
-        }
-    }
-}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/OpenActivityEmbeddingSecondaryToSplitTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/OpenActivityEmbeddingSecondaryToSplitTest.kt
deleted file mode 100644
index 8638288..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/OpenActivityEmbeddingSecondaryToSplitTest.kt
+++ /dev/null
@@ -1,122 +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.server.wm.flicker.activityembedding
-
-import android.platform.test.annotations.Presubmit
-import android.tools.common.traces.component.ComponentNameMatcher
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test opening a secondary activity that will split with the main activity.
- *
- * To run this test: `atest FlickerTests:OpenActivityEmbeddingSecondaryToSplitTest`
- */
-@RequiresDevice
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class OpenActivityEmbeddingSecondaryToSplitTest(flicker: FlickerTest) :
-    ActivityEmbeddingTestBase(flicker) {
-
-    /** {@inheritDoc} */
-    override val transition: FlickerBuilder.() -> Unit = {
-        setup {
-            tapl.setExpectedRotationCheckEnabled(false)
-            testApp.launchViaIntent(wmHelper)
-        }
-        transitions { testApp.launchSecondaryActivity(wmHelper) }
-        teardown {
-            tapl.goHome()
-            testApp.exit(wmHelper)
-        }
-    }
-
-    /** Main activity should remain visible when enter split from fullscreen. */
-    @Presubmit
-    @Test
-    fun mainActivityWindowIsAlwaysVisible() {
-        flicker.assertWm { isAppWindowVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT) }
-    }
-
-    /**
-     * Main activity surface is animated from fullscreen to ActivityEmbedding split. During the
-     * transition, there is a period of time that it is covered by a snapshot of itself.
-     */
-    @Presubmit
-    @Test
-    fun mainActivityLayerIsAlwaysVisible() {
-        flicker.assertLayers {
-            isVisible(
-                ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT.or(
-                    ComponentNameMatcher.TRANSITION_SNAPSHOT
-                )
-            )
-        }
-        flicker.assertLayersEnd {
-            isVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
-                .isInvisible(ComponentNameMatcher.TRANSITION_SNAPSHOT)
-        }
-    }
-
-    /** Secondary activity should become visible after launching into split. */
-    @Presubmit
-    @Test
-    fun secondaryActivityWindowBecomesVisible() {
-        flicker.assertWm {
-            notContains(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
-                .then()
-                .isAppWindowInvisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
-                .then()
-                .isAppWindowVisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
-        }
-    }
-
-    /** Secondary activity should become visible after launching into split. */
-    @Presubmit
-    @Test
-    fun secondaryActivityLayerBecomesVisible() {
-        flicker.assertLayers {
-            isInvisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
-                .then()
-                .isVisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
-        }
-    }
-
-    companion object {
-        /**
-         * Creates the test configurations.
-         *
-         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
-         * navigation modes.
-         */
-        @Parameterized.Parameters(name = "{0}")
-        @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests()
-        }
-    }
-}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/RTLStartSecondaryWithPlaceholderTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/RTLStartSecondaryWithPlaceholderTest.kt
new file mode 100644
index 0000000..4bc17ed
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/RTLStartSecondaryWithPlaceholderTest.kt
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.activityembedding
+
+import android.platform.test.annotations.Presubmit
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test launching a placeholder split over a normal split, both splits are configured in RTL.
+ *
+ * Setup: From A launch a split in RTL - resulting in B|A. Transitions: From A start
+ * PlaceholderPrimary, which is configured to launch with PlaceholderSecondary in RTL. Expect split
+ * PlaceholderSecondary|PlaceholderPrimary covering split B|A.
+ *
+ * To run this test: `atest FlickerTests:RTLStartSecondaryWithPlaceholderTest`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class RTLStartSecondaryWithPlaceholderTest(flicker: LegacyFlickerTest) :
+    ActivityEmbeddingTestBase(flicker) {
+
+    /** {@inheritDoc} */
+    override val transition: FlickerBuilder.() -> Unit = {
+        setup {
+            tapl.setExpectedRotationCheckEnabled(false)
+            testApp.launchViaIntent(wmHelper)
+            testApp.launchSecondaryActivityRTL(wmHelper)
+        }
+        transitions { testApp.launchPlaceholderSplitRTL(wmHelper) }
+        teardown {
+            tapl.goHome()
+            testApp.exit(wmHelper)
+        }
+    }
+
+    /**
+     * Main activity and Secondary activity will become invisible because they are covered by
+     * PlaceholderPrimary activity and PlaceholderSecondary activity.
+     */
+    @Presubmit
+    @Test
+    fun assertWindowVisibilities() {
+        flicker.assertWm {
+            isAppWindowVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+                .then()
+                .isAppWindowInvisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+        }
+        flicker.assertWm {
+            isAppWindowVisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+                .then()
+                .isAppWindowInvisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+        }
+        flicker.assertWm {
+            isAppWindowInvisible(ActivityEmbeddingAppHelper.PLACEHOLDER_PRIMARY_COMPONENT)
+                .then()
+                .isAppWindowVisible(ActivityEmbeddingAppHelper.PLACEHOLDER_PRIMARY_COMPONENT)
+        }
+        flicker.assertWm {
+            isAppWindowInvisible(ActivityEmbeddingAppHelper.PLACEHOLDER_SECONDARY_COMPONENT)
+                .then()
+                .isAppWindowVisible(ActivityEmbeddingAppHelper.PLACEHOLDER_SECONDARY_COMPONENT)
+        }
+    }
+
+    /**
+     * Main activity and Secondary activity will become invisible because they are covered by
+     * PlaceholderPrimary activity and PlaceholderSecondary activity.
+     */
+    @Presubmit
+    @Test
+    fun assertLayerVisibilities() {
+        flicker.assertLayers {
+            this.isVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+                .then()
+                .isInvisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+        }
+        flicker.assertLayers {
+            this.isVisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+                .then()
+                .isInvisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+        }
+        flicker.assertLayers {
+            isInvisible(ActivityEmbeddingAppHelper.PLACEHOLDER_PRIMARY_COMPONENT)
+                .then()
+                .isVisible(ActivityEmbeddingAppHelper.PLACEHOLDER_PRIMARY_COMPONENT)
+        }
+        flicker.assertLayers {
+            isInvisible(ActivityEmbeddingAppHelper.PLACEHOLDER_SECONDARY_COMPONENT)
+                .then()
+                .isVisible(ActivityEmbeddingAppHelper.PLACEHOLDER_SECONDARY_COMPONENT)
+        }
+    }
+
+    /** Main activity and Secondary activity split is in right-to-left layout direction. */
+    @Presubmit
+    @Test
+    fun assertWMRTLBeforeTransition() {
+        flicker.assertWmStart {
+            val mainActivityRegion =
+                this.visibleRegion(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+            val secondaryActivityRegion =
+                this.visibleRegion(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+            mainActivityRegion.notOverlaps(secondaryActivityRegion.region)
+            // secondary activity is on the left, main activity is on the right.
+            check { "isRTLBeforeTransition" }
+                .that(mainActivityRegion.region.bounds.left)
+                .isEqual(secondaryActivityRegion.region.bounds.right)
+        }
+    }
+
+    /** Main activity and Secondary activity split is in right-to-left layout direction. */
+    @Presubmit
+    @Test
+    fun assertLayerRTLBeforeTransition() {
+        flicker.assertLayersStart {
+            val mainActivityRegion =
+                this.visibleRegion(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+            val secondaryActivityRegion =
+                this.visibleRegion(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+            mainActivityRegion.notOverlaps(secondaryActivityRegion.region)
+            // secondary activity is on the left, main activity is on the right.
+            check { "isRTLBeforeTransition" }
+                .that(mainActivityRegion.region.bounds.left)
+                .isEqual(secondaryActivityRegion.region.bounds.right)
+        }
+    }
+
+    /**
+     * PlaceholderPrimary activity and PlaceholderSecondary activity split are in right-to-left
+     * layout direction.
+     */
+    @Presubmit
+    @Test
+    fun assertWMRTLAfterTransition() {
+        flicker.assertWmEnd {
+            val mainActivityRegion =
+                this.visibleRegion(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+            val secondaryActivityRegion =
+                this.visibleRegion(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+            mainActivityRegion.notOverlaps(secondaryActivityRegion.region)
+            // secondary activity is on the left, main activity is on the right.
+            check { "isRTLBeforeTransition" }
+                .that(mainActivityRegion.region.bounds.left)
+                .isEqual(secondaryActivityRegion.region.bounds.right)
+        }
+    }
+
+    /**
+     * PlaceholderPrimary activity and PlaceholderSecondary activity split are in right-to-left
+     * layout direction.
+     */
+    @Presubmit
+    @Test
+    fun assertLayerRTLAfterTransition() {
+        flicker.assertLayersEnd {
+            val mainActivityRegion =
+                this.visibleRegion(ActivityEmbeddingAppHelper.PLACEHOLDER_PRIMARY_COMPONENT)
+            val secondaryActivityRegion =
+                this.visibleRegion(ActivityEmbeddingAppHelper.PLACEHOLDER_SECONDARY_COMPONENT)
+            mainActivityRegion.notOverlaps(secondaryActivityRegion.region)
+            // Placeholder secondary activity is on the left, placeholder primary activity is on the
+            // right.
+            check { "isRTLAfterTransition" }
+                .that(mainActivityRegion.region.bounds.left)
+                .isEqual(secondaryActivityRegion.region.bounds.right)
+        }
+    }
+
+    companion object {
+        /**
+         * Creates the test configurations.
+         *
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * navigation modes.
+         */
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/close/CloseSecondaryActivityInSplitTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/close/CloseSecondaryActivityInSplitTest.kt
new file mode 100644
index 0000000..4530ef3
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/close/CloseSecondaryActivityInSplitTest.kt
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.flicker.activityembedding.close
+
+import android.platform.test.annotations.Presubmit
+import android.tools.common.datatypes.Rect
+import android.tools.common.traces.component.ComponentNameMatcher
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.activityembedding.ActivityEmbeddingTestBase
+import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test closing a secondary activity in a split.
+ *
+ * Setup: Launch A|B in split with B being the secondary activity. Transitions: Finish B and expect
+ * A to become fullscreen.
+ *
+ * To run this test: `atest FlickerTests:CloseSecondaryActivityInSplitTest`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class CloseSecondaryActivityInSplitTest(flicker: LegacyFlickerTest) :
+    ActivityEmbeddingTestBase(flicker) {
+
+    override val transition: FlickerBuilder.() -> Unit = {
+        setup {
+            tapl.setExpectedRotationCheckEnabled(false)
+            // Launches fullscreen A.
+            testApp.launchViaIntent(wmHelper)
+            // Launches a split A|B and waits for both activities to show.
+            testApp.launchSecondaryActivity(wmHelper)
+            // Get fullscreen bounds
+            startDisplayBounds =
+                wmHelper.currentState.layerState.physicalDisplayBounds
+                    ?: error("Can't get display bounds")
+        }
+        transitions {
+            // Finish secondary activity B.
+            testApp.finishSecondaryActivity(wmHelper)
+            // Expect the main activity A to expand into fullscreen.
+            wmHelper.StateSyncBuilder().withFullScreenApp(testApp).waitForAndVerify()
+        }
+        teardown {
+            tapl.goHome()
+            testApp.exit(wmHelper)
+        }
+    }
+
+    /** Main activity is always visible and becomes fullscreen in the end. */
+    @Presubmit
+    @Test
+    fun mainActivityWindowBecomesFullScreen() {
+        flicker.assertWm { isAppWindowVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT) }
+        flicker.assertWmEnd {
+            this.visibleRegion(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+                .coversExactly(startDisplayBounds)
+        }
+    }
+
+    /** Main activity surface is animated from split to fullscreen. */
+    @Presubmit
+    @Test
+    fun mainActivityLayerIsAlwaysVisible() {
+        flicker.assertLayers {
+            isVisible(
+                ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT.or(
+                    ComponentNameMatcher.TRANSITION_SNAPSHOT
+                )
+            )
+        }
+        flicker.assertLayersEnd {
+            isVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+                .isInvisible(ComponentNameMatcher.TRANSITION_SNAPSHOT)
+        }
+    }
+
+    /** Secondary activity should destroy and become invisible. */
+    @Presubmit
+    @Test
+    fun secondaryActivityWindowFinishes() {
+        flicker.assertWm {
+            contains(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+                .then()
+                .notContains(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+        }
+    }
+
+    @Presubmit
+    @Test
+    fun secondaryActivityLayerFinishes() {
+        flicker.assertLayers {
+            isVisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+                .then()
+                .isInvisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+        }
+    }
+
+    companion object {
+        /** {@inheritDoc} */
+        private var startDisplayBounds = Rect.EMPTY
+        /**
+         * Creates the test configurations.
+         *
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * navigation modes.
+         */
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/MainActivityStartsSecondaryWithAlwaysExpandTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/MainActivityStartsSecondaryWithAlwaysExpandTest.kt
new file mode 100644
index 0000000..0f406fd
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/MainActivityStartsSecondaryWithAlwaysExpandTest.kt
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.flicker.activityembedding.open
+
+import android.platform.test.annotations.FlakyTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.datatypes.Rect
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.activityembedding.ActivityEmbeddingTestBase
+import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper
+import org.junit.FixMethodOrder
+import org.junit.Ignore
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test launching an activity with AlwaysExpand rule.
+ *
+ * Setup: Launch A|B in split with B being the secondary activity. Transitions: A start C with
+ * alwaysExpand=true, expect C to launch in fullscreen and cover split A|B.
+ *
+ * To run this test: `atest FlickerTests:MainActivityStartsSecondaryWithAlwaysExpandTest`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class MainActivityStartsSecondaryWithAlwaysExpandTest(flicker: LegacyFlickerTest) :
+    ActivityEmbeddingTestBase(flicker) {
+
+    /** {@inheritDoc} */
+    override val transition: FlickerBuilder.() -> Unit = {
+        setup {
+            tapl.setExpectedRotationCheckEnabled(false)
+            // Launch a split
+            testApp.launchViaIntent(wmHelper)
+            testApp.launchSecondaryActivity(wmHelper)
+            startDisplayBounds =
+                wmHelper.currentState.layerState.physicalDisplayBounds ?: error("Display not found")
+        }
+        transitions {
+            // Launch C with alwaysExpand
+            testApp.launchAlwaysExpandActivity(wmHelper)
+        }
+        teardown {
+            tapl.goHome()
+            testApp.exit(wmHelper)
+        }
+    }
+
+    @Ignore("Not applicable to this CUJ.") override fun navBarWindowIsVisibleAtStartAndEnd() {}
+
+    @Ignore("Not applicable to this CUJ.") override fun statusBarWindowIsAlwaysVisible() {}
+
+    @Ignore("Not applicable to this CUJ.") override fun statusBarLayerPositionAtStartAndEnd() {}
+
+    /** Transition begins with a split. */
+    @FlakyTest(bugId = 286952194)
+    @Test
+    fun startsWithSplit() {
+        flicker.assertWmStart {
+            this.isAppWindowVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+        }
+        flicker.assertWmStart {
+            this.isAppWindowVisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+        }
+    }
+
+    /** Main activity should become invisible after being covered by always expand activity. */
+    @FlakyTest(bugId = 286952194)
+    @Test
+    fun mainActivityLayerBecomesInvisible() {
+        flicker.assertLayers {
+            isVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+                .then()
+                .isInvisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+        }
+    }
+
+    /** Secondary activity should become invisible after being covered by always expand activity. */
+    @FlakyTest(bugId = 286952194)
+    @Test
+    fun secondaryActivityLayerBecomesInvisible() {
+        flicker.assertLayers {
+            isVisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+                .then()
+                .isInvisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+        }
+    }
+
+    /** At the end of transition always expand activity is in fullscreen. */
+    @FlakyTest(bugId = 286952194)
+    @Test
+    fun endsWithAlwaysExpandActivityCoveringFullScreen() {
+        flicker.assertWmEnd {
+            this.visibleRegion(ActivityEmbeddingAppHelper.ALWAYS_EXPAND_ACTIVITY_COMPONENT)
+                .coversExactly(startDisplayBounds)
+        }
+    }
+
+    /** Always expand activity is on top of the split. */
+    @FlakyTest(bugId = 286952194)
+    @Presubmit
+    @Test
+    fun endsWithAlwaysExpandActivityOnTop() {
+        flicker.assertWmEnd {
+            this.isAppWindowOnTop(ActivityEmbeddingAppHelper.ALWAYS_EXPAND_ACTIVITY_COMPONENT)
+        }
+    }
+
+    companion object {
+        /** {@inheritDoc} */
+        private var startDisplayBounds = Rect.EMPTY
+        /**
+         * Creates the test configurations.
+         *
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * navigation modes.
+         */
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenActivityEmbeddingPlaceholderSplitTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenActivityEmbeddingPlaceholderSplitTest.kt
new file mode 100644
index 0000000..8a997dd
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenActivityEmbeddingPlaceholderSplitTest.kt
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.activityembedding.open
+
+import android.platform.test.annotations.Presubmit
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.activityembedding.ActivityEmbeddingTestBase
+import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test opening an activity that will launch another activity as ActivityEmbedding placeholder in
+ * split.
+ *
+ * To run this test: `atest FlickerTests:OpenActivityEmbeddingPlaceholderSplitTest`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class OpenActivityEmbeddingPlaceholderSplitTest(flicker: LegacyFlickerTest) :
+    ActivityEmbeddingTestBase(flicker) {
+
+    /** {@inheritDoc} */
+    override val transition: FlickerBuilder.() -> Unit = {
+        setup {
+            tapl.setExpectedRotationCheckEnabled(false)
+            testApp.launchViaIntent(wmHelper)
+        }
+        transitions { testApp.launchPlaceholderSplit(wmHelper) }
+        teardown {
+            tapl.goHome()
+            testApp.exit(wmHelper)
+        }
+    }
+
+    /** Main activity should become invisible after launching the placeholder primary activity. */
+    @Presubmit
+    @Test
+    fun mainActivityWindowBecomesInvisible() {
+        flicker.assertWm {
+            isAppWindowVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+                .then()
+                .isAppWindowInvisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+        }
+    }
+
+    /** Main activity should become invisible after launching the placeholder primary activity. */
+    @Presubmit
+    @Test
+    fun mainActivityLayerBecomesInvisible() {
+        flicker.assertLayers {
+            isVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+                .then()
+                .isInvisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+        }
+    }
+
+    /**
+     * Placeholder primary and secondary should become visible after launch. The windows are not
+     * necessarily to become visible at the same time.
+     */
+    @Presubmit
+    @Test
+    fun placeholderSplitWindowsBecomeVisible() {
+        flicker.assertWm {
+            notContains(ActivityEmbeddingAppHelper.PLACEHOLDER_PRIMARY_COMPONENT)
+                .then()
+                .isAppWindowInvisible(ActivityEmbeddingAppHelper.PLACEHOLDER_PRIMARY_COMPONENT)
+                .then()
+                .isAppWindowVisible(ActivityEmbeddingAppHelper.PLACEHOLDER_PRIMARY_COMPONENT)
+        }
+        flicker.assertWm {
+            notContains(ActivityEmbeddingAppHelper.PLACEHOLDER_SECONDARY_COMPONENT)
+                .then()
+                .isAppWindowInvisible(ActivityEmbeddingAppHelper.PLACEHOLDER_SECONDARY_COMPONENT)
+                .then()
+                .isAppWindowVisible(ActivityEmbeddingAppHelper.PLACEHOLDER_SECONDARY_COMPONENT)
+        }
+    }
+
+    /** Placeholder primary and secondary should become visible together after launch. */
+    @Presubmit
+    @Test
+    fun placeholderSplitLayersBecomeVisible() {
+        flicker.assertLayers {
+            isInvisible(ActivityEmbeddingAppHelper.PLACEHOLDER_PRIMARY_COMPONENT)
+            isInvisible(ActivityEmbeddingAppHelper.PLACEHOLDER_SECONDARY_COMPONENT)
+                .then()
+                .isVisible(ActivityEmbeddingAppHelper.PLACEHOLDER_PRIMARY_COMPONENT)
+                .isVisible(ActivityEmbeddingAppHelper.PLACEHOLDER_SECONDARY_COMPONENT)
+        }
+    }
+
+    companion object {
+        /**
+         * Creates the test configurations.
+         *
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * navigation modes.
+         */
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenActivityEmbeddingSecondaryToSplitTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenActivityEmbeddingSecondaryToSplitTest.kt
new file mode 100644
index 0000000..49aa84b
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenActivityEmbeddingSecondaryToSplitTest.kt
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.flicker.activityembedding.open
+
+import android.platform.test.annotations.Presubmit
+import android.tools.common.traces.component.ComponentNameMatcher
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.activityembedding.ActivityEmbeddingTestBase
+import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test opening a secondary activity that will split with the main activity.
+ *
+ * To run this test: `atest FlickerTests:OpenActivityEmbeddingSecondaryToSplitTest`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class OpenActivityEmbeddingSecondaryToSplitTest(flicker: LegacyFlickerTest) :
+    ActivityEmbeddingTestBase(flicker) {
+
+    /** {@inheritDoc} */
+    override val transition: FlickerBuilder.() -> Unit = {
+        setup {
+            tapl.setExpectedRotationCheckEnabled(false)
+            testApp.launchViaIntent(wmHelper)
+        }
+        transitions { testApp.launchSecondaryActivity(wmHelper) }
+        teardown {
+            tapl.goHome()
+            testApp.exit(wmHelper)
+        }
+    }
+
+    /** Main activity should remain visible when enter split from fullscreen. */
+    @Presubmit
+    @Test
+    fun mainActivityWindowIsAlwaysVisible() {
+        flicker.assertWm { isAppWindowVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT) }
+    }
+
+    /**
+     * Main activity surface is animated from fullscreen to ActivityEmbedding split. During the
+     * transition, there is a period of time that it is covered by a snapshot of itself.
+     */
+    @Presubmit
+    @Test
+    fun mainActivityLayerIsAlwaysVisible() {
+        flicker.assertLayers {
+            isVisible(
+                ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT.or(
+                    ComponentNameMatcher.TRANSITION_SNAPSHOT
+                )
+            )
+        }
+        flicker.assertLayersEnd {
+            isVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+                .isInvisible(ComponentNameMatcher.TRANSITION_SNAPSHOT)
+        }
+    }
+
+    /** Secondary activity should become visible after launching into split. */
+    @Presubmit
+    @Test
+    fun secondaryActivityWindowBecomesVisible() {
+        flicker.assertWm {
+            notContains(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+                .then()
+                .isAppWindowInvisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+                .then()
+                .isAppWindowVisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+        }
+    }
+
+    /** Secondary activity should become visible after launching into split. */
+    @Presubmit
+    @Test
+    fun secondaryActivityLayerBecomesVisible() {
+        flicker.assertLayers {
+            isInvisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+                .then()
+                .isVisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+        }
+    }
+
+    companion object {
+        /**
+         * Creates the test configurations.
+         *
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * navigation modes.
+         */
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenThirdActivityOverSplitTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenThirdActivityOverSplitTest.kt
new file mode 100644
index 0000000..27de12e
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenThirdActivityOverSplitTest.kt
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.flicker.activityembedding.open
+
+import android.platform.test.annotations.Presubmit
+import android.tools.common.datatypes.Rect
+import android.tools.common.traces.component.ComponentNameMatcher
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.activityembedding.ActivityEmbeddingTestBase
+import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test launching a secondary activity over an existing split. By default the new secondary activity
+ * will stack over the previous secondary activity.
+ *
+ * Setup: From Activity A launch a split A|B.
+ *
+ * Transitions: Let B start C, expect C to cover B and end up in split A|C.
+ *
+ * To run this test: `atest FlickerTests:OpenThirdActivityOverSplitTest`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class OpenThirdActivityOverSplitTest(flicker: LegacyFlickerTest) :
+    ActivityEmbeddingTestBase(flicker) {
+    /** {@inheritDoc} */
+    override val transition: FlickerBuilder.() -> Unit = {
+        setup {
+            tapl.setExpectedRotationCheckEnabled(false)
+            // Launch a split.
+            testApp.launchViaIntent(wmHelper)
+            testApp.launchSecondaryActivity(wmHelper)
+
+            startDisplayBounds =
+                wmHelper.currentState.layerState.physicalDisplayBounds
+                    ?: error("Can't get display bounds")
+        }
+        transitions { testApp.launchThirdActivity(wmHelper) }
+        teardown {
+            tapl.goHome()
+            testApp.exit(wmHelper)
+        }
+    }
+
+    /** Main activity remains visible throughout the transition. */
+    @Presubmit
+    @Test
+    fun mainActivityWindowAlwaysVisible() {
+        flicker.assertWm { isAppWindowVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT) }
+    }
+
+    /** Main activity remains visible throughout the transition and takes up half of the screen. */
+    @Presubmit
+    @Test
+    fun mainActivityLayersAlwaysVisible() {
+        flicker.assertLayers { isVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT) }
+
+        flicker.assertLayersStart {
+            val display =
+                this.entry.displays.firstOrNull { it.isOn && !it.isVirtual }
+                    ?: error("No non-virtual and on display found")
+            val mainActivityRegion =
+                this.visibleRegion(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+            val secondaryActivityRegion =
+                this.visibleRegion(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT).region
+            mainActivityRegion.plus(secondaryActivityRegion).coversExactly(display.layerStackSpace)
+        }
+
+        flicker.assertLayersEnd {
+            val display =
+                this.entry.displays.firstOrNull { it.isOn && !it.isVirtual }
+                    ?: error("No non-virtual and on display found")
+            val mainActivityRegion =
+                this.visibleRegion(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+            val secondaryActivityRegion =
+                this.visibleRegion(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+            secondaryActivityRegion.isEmpty()
+            val thirdActivityRegion =
+                this.visibleRegion(ActivityEmbeddingAppHelper.THIRD_ACTIVITY_COMPONENT)
+            mainActivityRegion
+                .plus(thirdActivityRegion.region)
+                .coversExactly(display.layerStackSpace)
+        }
+    }
+
+    /** Third activity launches during the transition and covers up secondary activity. */
+    @Presubmit
+    @Test
+    fun thirdActivityWindowLaunchesIntoSplit() {
+        flicker.assertWm {
+            isAppWindowVisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+                .isAppWindowInvisible(ActivityEmbeddingAppHelper.THIRD_ACTIVITY_COMPONENT)
+                .then()
+                .isAppWindowVisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+                .isAppWindowVisible(ActivityEmbeddingAppHelper.THIRD_ACTIVITY_COMPONENT)
+                .then()
+                .isAppWindowVisible(ActivityEmbeddingAppHelper.THIRD_ACTIVITY_COMPONENT)
+                .isAppWindowInvisible(
+                    ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT
+                ) // expectation
+        }
+    }
+
+    /** Third activity launches during the transition and covers up secondary activity. */
+    @Presubmit
+    @Test
+    fun thirdActivityLayerLaunchesIntoSplit() {
+        flicker.assertLayers {
+            isVisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+                .isInvisible(ActivityEmbeddingAppHelper.THIRD_ACTIVITY_COMPONENT)
+                .then()
+                .isVisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+                .isVisible(ActivityEmbeddingAppHelper.THIRD_ACTIVITY_COMPONENT)
+                .then()
+                .isVisible(ActivityEmbeddingAppHelper.THIRD_ACTIVITY_COMPONENT)
+                .isInvisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+        }
+    }
+
+    /** Assert the background animation layer is never visible during transition. */
+    @Presubmit
+    @Test
+    fun backgroundLayerNeverVisible() {
+        val backgroundColorLayer = ComponentNameMatcher("", "Animation Background")
+        flicker.assertLayers { isInvisible(backgroundColorLayer) }
+    }
+
+    companion object {
+        /** {@inheritDoc} */
+        private var startDisplayBounds = Rect.EMPTY
+        /**
+         * Creates the test configurations.
+         *
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * navigation modes.
+         */
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenTrampolineActivityTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenTrampolineActivityTest.kt
new file mode 100644
index 0000000..6722cc8
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenTrampolineActivityTest.kt
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.flicker.activityembedding
+
+import android.platform.test.annotations.Presubmit
+import android.tools.common.datatypes.Rect
+import android.tools.common.flicker.subject.region.RegionSubject
+import android.tools.common.traces.component.ComponentNameMatcher
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test launching a trampoline activity and resulting in a split state.
+ *
+ * Setup: Launch Activity A in fullscreen.
+ *
+ * Transitions: From A launch a trampoline Activity T, T launches secondary Activity B and finishes
+ * itself, end up in split A|B.
+ *
+ * To run this test: `atest FlickerTests:OpenTrampolineActivityTest`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class OpenTrampolineActivityTest(flicker: LegacyFlickerTest) : ActivityEmbeddingTestBase(flicker) {
+    override val transition: FlickerBuilder.() -> Unit = {
+        setup {
+            tapl.setExpectedRotationCheckEnabled(false)
+            testApp.launchViaIntent(wmHelper)
+            startDisplayBounds =
+                wmHelper.currentState.layerState.physicalDisplayBounds
+                    ?: error("Can't get display bounds")
+        }
+        transitions { testApp.launchTrampolineActivity(wmHelper) }
+        teardown {
+            tapl.goHome()
+            testApp.exit(wmHelper)
+        }
+    }
+
+    /** Trampoline activity should finish itself before the end of this test. */
+    @Presubmit
+    @Test
+    fun trampolineActivityFinishes() {
+        flicker.assertWmEnd {
+            notContains(ActivityEmbeddingAppHelper.TRAMPOLINE_ACTIVITY_COMPONENT)
+        }
+    }
+
+    @Presubmit
+    @Test
+    fun trampolineLayerNeverVisible() {
+        flicker.assertLayers {
+            isInvisible(ActivityEmbeddingAppHelper.TRAMPOLINE_ACTIVITY_COMPONENT)
+        }
+    }
+
+    /** Main activity is always visible throughout this test. */
+    @Presubmit
+    @Test
+    fun mainActivityWindowAlwaysVisible() {
+        flicker.assertWm { isAppWindowVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT) }
+    }
+
+    // TODO(b/289140963): After this is fixed, assert the main Activity window is visible
+    //  throughout the test instead.
+    /** Main activity layer is visible before and after the transition. */
+    @Presubmit
+    @Test
+    fun mainActivityLayerAlwaysVisible() {
+        flicker.assertLayersStart { isVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT) }
+        flicker.assertLayersEnd { isVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT) }
+    }
+
+    /** Secondary activity is launched from the trampoline activity. */
+    @Presubmit
+    @Test
+    fun secondaryActivityWindowLaunchedFromTrampoline() {
+        flicker.assertWm {
+            notContains(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+                .then()
+                .isAppWindowInvisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+                .then()
+                .isAppWindowVisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+        }
+    }
+
+    /** Secondary activity is launched from the trampoline activity. */
+    @Presubmit
+    @Test
+    fun secondaryActivityLayerLaunchedFromTrampoline() {
+        flicker.assertLayers {
+            isInvisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+                .then()
+                .isVisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+        }
+    }
+
+    /** Main activity should go from fullscreen to being a split with secondary activity. */
+    @Presubmit
+    @Test
+    fun mainActivityWindowGoesFromFullscreenToSplit() {
+        flicker.assertWm {
+            this.invoke("mainActivityStartsInFullscreen") {
+                    it.visibleRegion(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+                        .coversExactly(startDisplayBounds)
+                }
+                // Begin of transition.
+                .then()
+                .isAppWindowInvisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+                .then()
+                .invoke("mainAndSecondaryInSplit") {
+                    val mainActivityRegion =
+                        RegionSubject(
+                            it.visibleRegion(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+                                .region,
+                            it.timestamp
+                        )
+                    val secondaryActivityRegion =
+                        RegionSubject(
+                            it.visibleRegion(
+                                    ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT
+                                )
+                                .region,
+                            it.timestamp
+                        )
+                    check { "height" }
+                        .that(mainActivityRegion.region.height)
+                        .isEqual(secondaryActivityRegion.region.height)
+                    check { "width" }
+                        .that(mainActivityRegion.region.width)
+                        .isEqual(secondaryActivityRegion.region.width)
+                    mainActivityRegion
+                        .plus(secondaryActivityRegion.region)
+                        .coversExactly(startDisplayBounds)
+                }
+        }
+    }
+
+    /** Main activity should go from fullscreen to being a split with secondary activity. */
+    @Presubmit
+    @Test
+    fun mainActivityLayerGoesFromFullscreenToSplit() {
+        flicker.assertLayers {
+            this.invoke("mainActivityStartsInFullscreen") {
+                    it.visibleRegion(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+                        .coversExactly(startDisplayBounds)
+                }
+                .then()
+                .isInvisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+                .then()
+                .isVisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+        }
+        flicker.assertLayersEnd {
+            val leftLayerRegion = visibleRegion(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+            val rightLayerRegion =
+                visibleRegion(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+            // Compare dimensions of two splits, given we're using default split attributes,
+            // both activities take up the same visible size on the display.
+            check { "height" }
+                .that(leftLayerRegion.region.height)
+                .isEqual(rightLayerRegion.region.height)
+            check { "width" }
+                .that(leftLayerRegion.region.width)
+                .isEqual(rightLayerRegion.region.width)
+            leftLayerRegion.notOverlaps(rightLayerRegion.region)
+            // Layers of two activities sum to be fullscreen size on display.
+            leftLayerRegion.plus(rightLayerRegion.region).coversExactly(startDisplayBounds)
+        }
+    }
+
+    companion object {
+        /** {@inheritDoc} */
+        private var startDisplayBounds = Rect.EMPTY
+
+        /**
+         * Creates the test configurations.
+         *
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * navigation modes.
+         */
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt
new file mode 100644
index 0000000..0417f9d
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.flicker.activityembedding
+
+import android.platform.test.annotations.Presubmit
+import android.tools.common.datatypes.Rect
+import android.tools.common.traces.component.ComponentNameMatcher
+import android.tools.common.traces.component.ComponentNameMatcher.Companion.TRANSITION_SNAPSHOT
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test launching a secondary Activity into Picture-In-Picture mode.
+ *
+ * Setup: Start from a split A|B.
+ * Transition: B enters PIP, observe the window shrink to the bottom right corner on screen.
+ *
+ * To run this test: `atest FlickerTests:SecondaryActivityEnterPipTest`
+ *
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class SecondaryActivityEnterPipTest (flicker: LegacyFlickerTest) :
+        ActivityEmbeddingTestBase(flicker) {
+    override val transition: FlickerBuilder.() -> Unit = {
+        setup {
+            tapl.setExpectedRotationCheckEnabled(false)
+            testApp.launchViaIntent(wmHelper)
+            testApp.launchSecondaryActivity(wmHelper)
+            startDisplayBounds =
+                    wmHelper.currentState.layerState.physicalDisplayBounds
+                            ?: error("Can't get display bounds")
+        }
+        transitions {
+            testApp.secondaryActivityEnterPip(wmHelper)
+        }
+        teardown {
+            tapl.goHome()
+            testApp.exit(wmHelper)
+        }
+    }
+
+    /**
+     * Main and secondary activity start from a split each taking half of the screen.
+     */
+    @Presubmit
+    @Test
+    fun layersStartFromEqualSplit() {
+        flicker.assertLayersStart {
+            val leftLayerRegion =
+                    visibleRegion(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+            val rightLayerRegion =
+                    visibleRegion(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+            // Compare dimensions of two splits, given we're using default split attributes,
+            // both activities take up the same visible size on the display.
+            check { "height" }
+                    .that(leftLayerRegion.region.height).isEqual(rightLayerRegion.region.height)
+            check { "width" }
+                    .that(leftLayerRegion.region.width).isEqual(rightLayerRegion.region.width)
+            leftLayerRegion.notOverlaps(rightLayerRegion.region)
+            leftLayerRegion.plus(rightLayerRegion.region).coversExactly(startDisplayBounds)
+        }
+        flicker.assertLayersEnd {
+            visibleRegion(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+                    .coversExactly(startDisplayBounds)
+        }
+    }
+
+    /**
+     * Main Activity is visible throughout the transition and becomes fullscreen.
+     */
+    @Presubmit
+    @Test
+    fun mainActivityWindowBecomesFullScreen() {
+        flicker.assertWm { isAppWindowVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT) }
+        flicker.assertWmEnd {
+            visibleRegion(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+                    .coversExactly(startDisplayBounds)
+        }
+    }
+
+    /**
+     * Main Activity is visible throughout the transition and becomes fullscreen.
+     */
+    @Presubmit
+    @Test
+    fun mainActivityLayerBecomesFullScreen() {
+        flicker.assertLayers {
+            isVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+                    .then()
+                    .isVisible(TRANSITION_SNAPSHOT)
+                    .isInvisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+                    .then()
+                    .isVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+        }
+        flicker.assertLayersEnd {
+            visibleRegion(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+                    .coversExactly(startDisplayBounds)
+        }
+    }
+
+    /**
+     * Secondary Activity is visible throughout the transition and shrinks to the bottom right
+     * corner.
+     */
+    @Presubmit
+    @Test
+    fun secondaryWindowShrinks() {
+        flicker.assertWm {
+            isAppWindowVisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+        }
+        flicker.assertWmEnd {
+            val pipWindowRegion =
+                    visibleRegion(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+            check{"height"}
+                    .that(pipWindowRegion.region.height)
+                    .isLower(startDisplayBounds.height / 2)
+            check{"width"}
+                    .that(pipWindowRegion.region.width).isLower(startDisplayBounds.width)
+        }
+    }
+
+    /**
+     * During the transition Secondary Activity shrinks to the bottom right corner.
+     */
+    @Presubmit
+    @Test
+    fun secondaryLayerShrinks() {
+        flicker.assertLayers {
+            val pipLayerList = layers {
+                ComponentNameMatcher.PIP_CONTENT_OVERLAY.layerMatchesAnyOf(it) && it.isVisible
+            }
+            pipLayerList.zipWithNext { previous, current ->
+                // TODO(b/290987990): Add checks for visibleRegion.
+                current.screenBounds.isToTheRightBottom(previous.screenBounds.region, 3)
+                current.screenBounds.notBiggerThan(previous.screenBounds.region)
+            }
+        }
+        flicker.assertLayersEnd {
+            val pipRegion = visibleRegion(
+                    ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+            check { "height" }
+                    .that(pipRegion.region.height)
+                    .isLower(startDisplayBounds.height / 2)
+            check { "width" }
+                    .that(pipRegion.region.width).isLower(startDisplayBounds.width)
+        }
+    }
+
+    companion object {
+        /** {@inheritDoc} */
+        private var startDisplayBounds = Rect.EMPTY
+        /**
+         * Creates the test configurations.
+         *
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * navigation modes.
+         */
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
+    }
+}
\ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/rotation/RotateSplitNoChangeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/rotation/RotateSplitNoChangeTest.kt
new file mode 100644
index 0000000..da56500
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/rotation/RotateSplitNoChangeTest.kt
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.flicker.activityembedding.rotation
+
+import android.platform.test.annotations.Presubmit
+import android.tools.common.traces.component.ComponentNameMatcher
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper
+import com.android.server.wm.flicker.rotation.RotationTransition
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Tests rotating two activities in an Activity Embedding split.
+ *
+ * Setup: Launch A|B in split with B being the secondary activity. Transitions: Rotate display, and
+ * expect A and B to split evenly in new rotation.
+ *
+ * To run this test: `atest FlickerTests:RotateSplitNoChangeTest`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+open class RotateSplitNoChangeTest(flicker: LegacyFlickerTest) : RotationTransition(flicker) {
+
+    override val testApp = ActivityEmbeddingAppHelper(instrumentation)
+    override val transition: FlickerBuilder.() -> Unit
+        get() = {
+            super.transition(this)
+            setup {
+                testApp.launchViaIntent(wmHelper)
+                testApp.launchSecondaryActivity(wmHelper)
+            }
+        }
+
+    /**
+     * Checks that the [ComponentNameMatcher.ROTATION] layer appears during the transition, doesn't
+     * flicker, and disappears before the transition is complete
+     */
+    @Presubmit
+    @Test
+    fun rotationLayerAppearsAndVanishes() {
+        flicker.assertLayers {
+            this.isVisible(testApp)
+                .then()
+                .isVisible(ComponentNameMatcher.ROTATION)
+                .then()
+                .isVisible(testApp)
+                .isInvisible(ComponentNameMatcher.ROTATION)
+        }
+    }
+
+    /**
+     * Overrides inherited assertion because in AE Split, the main and secondary activity are
+     * separate layers, each covering up exactly half of the display.
+     */
+    @Presubmit
+    @Test
+    override fun appLayerRotates_StartingPos() {
+        flicker.assertLayersStart {
+            this.entry.displays.map { display ->
+                val leftLayerRegion =
+                    this.visibleRegion(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+                val rightLayerRegion =
+                    this.visibleRegion(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+                // Compare dimensions of two splits, given we're using default split attributes,
+                // both activities take up the same visible size on the display.
+                check { "height" }
+                    .that(leftLayerRegion.region.height)
+                    .isEqual(rightLayerRegion.region.height)
+                check { "width" }
+                    .that(leftLayerRegion.region.width)
+                    .isEqual(rightLayerRegion.region.width)
+                leftLayerRegion.notOverlaps(rightLayerRegion.region)
+                // Layers of two activities sum to be fullscreen size on display.
+                leftLayerRegion.plus(rightLayerRegion.region).coversExactly(display.layerStackSpace)
+            }
+        }
+    }
+
+    /** Verifies dimensions of both split activities hold their invariance after transition too. */
+    @Presubmit
+    @Test
+    override fun appLayerRotates_EndingPos() {
+        flicker.assertLayersEnd {
+            this.entry.displays.map { display ->
+                val leftLayerRegion =
+                    this.visibleRegion(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+                val rightLayerRegion =
+                    this.visibleRegion(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+                check { "height" }
+                    .that(leftLayerRegion.region.height)
+                    .isEqual(rightLayerRegion.region.height)
+                check { "width" }
+                    .that(leftLayerRegion.region.width)
+                    .isEqual(rightLayerRegion.region.width)
+                leftLayerRegion.notOverlaps(rightLayerRegion.region)
+                leftLayerRegion.plus(rightLayerRegion.region).coversExactly(display.layerStackSpace)
+            }
+        }
+    }
+
+    /** Both activities in split should remain visible during rotation. */
+    @Presubmit
+    @Test
+    fun bothActivitiesAreAlwaysVisible() {
+        flicker.assertWm { isAppWindowVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT) }
+        flicker.assertWm {
+            isAppWindowVisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+        }
+    }
+
+    companion object {
+        /**
+         * Creates the test configurations.
+         *
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * navigation modes.
+         */
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams() = LegacyFlickerTestFactory.rotationTests()
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt
index 10b71ff..71db76e 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt
@@ -17,12 +17,11 @@
 package com.android.server.wm.flicker.close
 
 import android.platform.test.annotations.FlakyTest
-import android.tools.device.flicker.annotation.FlickerServiceCompatible
+import android.tools.common.flicker.annotation.FlickerServiceCompatible
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
-import androidx.test.filters.RequiresDevice
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -71,12 +70,11 @@
  *        apps are running before setup
  * ```
  */
-@RequiresDevice
 @FlickerServiceCompatible
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class CloseAppBackButtonTest(flicker: FlickerTest) : CloseAppTransition(flicker) {
+class CloseAppBackButtonTest(flicker: LegacyFlickerTest) : CloseAppTransition(flicker) {
     /** {@inheritDoc} */
     override val transition: FlickerBuilder.() -> Unit
         get() = {
@@ -96,13 +94,11 @@
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
          * navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests()
-        }
+        fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTestCfArm.kt
deleted file mode 100644
index 9fa84019..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTestCfArm.kt
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm.flicker.close
-
-import android.tools.device.flicker.annotation.FlickerServiceCompatible
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
-import org.junit.FixMethodOrder
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-@FlickerServiceCompatible
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class CloseAppBackButtonTestCfArm(flicker: FlickerTest) : CloseAppBackButtonTest(flicker) {
-    companion object {
-        /**
-         * Creates the test configurations.
-         *
-         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
-         * navigation modes.
-         */
-        @Parameterized.Parameters(name = "{0}")
-        @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests()
-        }
-    }
-}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt
index e5bd350..8dd7e65 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt
@@ -17,12 +17,11 @@
 package com.android.server.wm.flicker.close
 
 import android.platform.test.annotations.FlakyTest
-import android.tools.device.flicker.annotation.FlickerServiceCompatible
+import android.tools.common.flicker.annotation.FlickerServiceCompatible
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
-import androidx.test.filters.RequiresDevice
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -71,12 +70,11 @@
  *        apps are running before setup
  * ```
  */
-@RequiresDevice
 @FlickerServiceCompatible
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class CloseAppHomeButtonTest(flicker: FlickerTest) : CloseAppTransition(flicker) {
+class CloseAppHomeButtonTest(flicker: LegacyFlickerTest) : CloseAppTransition(flicker) {
     /** {@inheritDoc} */
     override val transition: FlickerBuilder.() -> Unit
         get() = {
@@ -101,8 +99,6 @@
         /** Creates the test configurations. */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests()
-        }
+        fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTestCfArm.kt
deleted file mode 100644
index 136995a..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTestCfArm.kt
+++ /dev/null
@@ -1,41 +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.server.wm.flicker.close
-
-import android.tools.device.flicker.annotation.FlickerServiceCompatible
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
-import org.junit.FixMethodOrder
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-@FlickerServiceCompatible
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class CloseAppHomeButtonTestCfArm(flicker: FlickerTest) : CloseAppHomeButtonTest(flicker) {
-    companion object {
-        /** Creates the test configurations. */
-        @Parameterized.Parameters(name = "{0}")
-        @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests()
-        }
-    }
-}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt
index 4570fa2..8737edb 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt
@@ -16,11 +16,14 @@
 
 package com.android.server.wm.flicker.close
 
+import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Presubmit
+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.LAUNCHER
 import android.tools.device.apphelpers.StandardAppHelper
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTest
 import com.android.server.wm.flicker.BaseTest
 import com.android.server.wm.flicker.helpers.SimpleAppHelper
 import com.android.server.wm.flicker.helpers.setRotation
@@ -28,7 +31,7 @@
 import org.junit.Test
 
 /** Base test class for transitions that close an app back to the launcher screen */
-abstract class CloseAppTransition(flicker: FlickerTest) : BaseTest(flicker) {
+abstract class CloseAppTransition(flicker: LegacyFlickerTest) : BaseTest(flicker) {
     protected open val testApp: StandardAppHelper = SimpleAppHelper(instrumentation)
 
     /** {@inheritDoc} */
@@ -71,4 +74,21 @@
             ignoreEntriesWithRotationLayer = flicker.scenario.isLandscapeOrSeascapeAtStart
         )
     }
+
+    /** {@inheritDoc} */
+    @Presubmit
+    @Test
+    override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
+        flicker.assertLayers {
+            this.visibleLayersShownMoreThanOneConsecutiveEntry(
+                VISIBLE_FOR_MORE_THAN_ONE_ENTRY_IGNORE_LAYERS + listOf(ComponentNameMatcher.NAV_BAR)
+            )
+        }
+    }
+
+    @FlakyTest(bugId = 288369951)
+    @Test
+    fun visibleLayersShownMoreThanOneConsecutiveEntryWithNavBar() {
+        flicker.assertLayers { this.visibleLayersShownMoreThanOneConsecutiveEntry() }
+    }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt
index daecfe7..2d3bc2d 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt
@@ -59,6 +59,117 @@
             .waitForAndVerify()
     }
 
+    /** Clicks the button to launch a third activity over a secondary activity. */
+    fun launchThirdActivity(wmHelper: WindowManagerStateHelper) {
+        val launchButton =
+            uiDevice.wait(
+                Until.findObject(By.res(getPackage(), "launch_third_activity_button")),
+                FIND_TIMEOUT
+            )
+        require(launchButton != null) { "Can't find launch third activity button on screen." }
+        launchButton.click()
+        wmHelper
+            .StateSyncBuilder()
+            .withActivityState(MAIN_ACTIVITY_COMPONENT, PlatformConsts.STATE_RESUMED)
+            .withActivityState(SECONDARY_ACTIVITY_COMPONENT, PlatformConsts.STATE_STOPPED)
+            .withActivityState(THIRD_ACTIVITY_COMPONENT, PlatformConsts.STATE_RESUMED)
+            .waitForAndVerify()
+    }
+
+    /**
+     * Clicks the button to launch the trampoline activity, which should launch the secondary
+     * activity and finish itself.
+     */
+    fun launchTrampolineActivity(wmHelper: WindowManagerStateHelper) {
+        val launchButton =
+            uiDevice.wait(
+                Until.findObject(By.res(getPackage(), "launch_trampoline_button")),
+                FIND_TIMEOUT
+            )
+        require(launchButton != null) { "Can't find launch trampoline activity button on screen." }
+        launchButton.click()
+        wmHelper
+            .StateSyncBuilder()
+            .withActivityState(SECONDARY_ACTIVITY_COMPONENT, PlatformConsts.STATE_RESUMED)
+            .withActivityRemoved(TRAMPOLINE_ACTIVITY_COMPONENT)
+            .waitForAndVerify()
+    }
+
+    /**
+     * Clicks the button to finishes the secondary activity launched through
+     * [launchSecondaryActivity], waits for the main activity to resume.
+     */
+    fun finishSecondaryActivity(wmHelper: WindowManagerStateHelper) {
+        val finishButton =
+            uiDevice.wait(
+                Until.findObject(By.res(getPackage(), "finish_secondary_activity_button")),
+                FIND_TIMEOUT
+            )
+        require(finishButton != null) { "Can't find finish secondary activity button on screen." }
+        finishButton.click()
+        wmHelper
+            .StateSyncBuilder()
+            .withActivityRemoved(SECONDARY_ACTIVITY_COMPONENT)
+            .waitForAndVerify()
+    }
+
+    fun secondaryActivityEnterPip(wmHelper: WindowManagerStateHelper) {
+        val pipButton =
+                uiDevice.wait(
+                        Until.findObject(By.res(getPackage(), "secondary_enter_pip_button")),
+                        FIND_TIMEOUT
+                )
+        require(pipButton != null) { "Can't find enter pip button on screen." }
+        pipButton.click()
+        wmHelper
+                .StateSyncBuilder()
+                .withAppTransitionIdle()
+                .withPipShown()
+                .waitForAndVerify()
+    }
+
+    /**
+     * Clicks the button to launch a secondary activity with alwaysExpand enabled, which will launch
+     * a fullscreen window on top of the visible region.
+     */
+    fun launchAlwaysExpandActivity(wmHelper: WindowManagerStateHelper) {
+        val launchButton =
+            uiDevice.wait(
+                Until.findObject(By.res(getPackage(), "launch_always_expand_activity_button")),
+                FIND_TIMEOUT
+            )
+        require(launchButton != null) {
+            "Can't find launch always expand activity button on screen."
+        }
+        launchButton.click()
+        wmHelper
+            .StateSyncBuilder()
+            .withActivityState(ALWAYS_EXPAND_ACTIVITY_COMPONENT, PlatformConsts.STATE_RESUMED)
+            .withActivityState(MAIN_ACTIVITY_COMPONENT, PlatformConsts.STATE_PAUSED)
+            .waitForAndVerify()
+    }
+
+    /**
+     * Clicks the button to launch the secondary activity in RTL, which should split with the main
+     * activity based on the split pair rule.
+     */
+    fun launchSecondaryActivityRTL(wmHelper: WindowManagerStateHelper) {
+        val launchButton =
+            uiDevice.wait(
+                Until.findObject(By.res(getPackage(), "launch_secondary_activity_rtl_button")),
+                FIND_TIMEOUT
+            )
+        require(launchButton != null) {
+            "Can't find launch secondary activity rtl button on screen."
+        }
+        launchButton.click()
+        wmHelper
+            .StateSyncBuilder()
+            .withActivityState(SECONDARY_ACTIVITY_COMPONENT, PlatformConsts.STATE_RESUMED)
+            .withActivityState(MAIN_ACTIVITY_COMPONENT, PlatformConsts.STATE_RESUMED)
+            .waitForAndVerify()
+    }
+
     /**
      * Clicks the button to launch the placeholder primary activity, which should launch the
      * placeholder secondary activity based on the placeholder rule.
@@ -78,6 +189,25 @@
             .waitForAndVerify()
     }
 
+    /**
+     * Clicks the button to launch the placeholder primary activity in RTL, which should launch the
+     * placeholder secondary activity based on the placeholder rule.
+     */
+    fun launchPlaceholderSplitRTL(wmHelper: WindowManagerStateHelper) {
+        val launchButton =
+            uiDevice.wait(
+                Until.findObject(By.res(getPackage(), "launch_placeholder_split_rtl_button")),
+                FIND_TIMEOUT
+            )
+        require(launchButton != null) { "Can't find launch placeholder split button on screen." }
+        launchButton.click()
+        wmHelper
+            .StateSyncBuilder()
+            .withActivityState(PLACEHOLDER_PRIMARY_COMPONENT, PlatformConsts.STATE_RESUMED)
+            .withActivityState(PLACEHOLDER_SECONDARY_COMPONENT, PlatformConsts.STATE_RESUMED)
+            .waitForAndVerify()
+    }
+
     companion object {
         private const val TAG = "ActivityEmbeddingAppHelper"
 
@@ -87,6 +217,12 @@
         val SECONDARY_ACTIVITY_COMPONENT =
             ActivityOptions.ActivityEmbedding.SecondaryActivity.COMPONENT.toFlickerComponent()
 
+        val THIRD_ACTIVITY_COMPONENT =
+            ActivityOptions.ActivityEmbedding.ThirdActivity.COMPONENT.toFlickerComponent()
+
+        val ALWAYS_EXPAND_ACTIVITY_COMPONENT =
+            ActivityOptions.ActivityEmbedding.AlwaysExpandActivity.COMPONENT.toFlickerComponent()
+
         val PLACEHOLDER_PRIMARY_COMPONENT =
             ActivityOptions.ActivityEmbedding.PlaceholderPrimaryActivity.COMPONENT
                 .toFlickerComponent()
@@ -95,6 +231,9 @@
             ActivityOptions.ActivityEmbedding.PlaceholderSecondaryActivity.COMPONENT
                 .toFlickerComponent()
 
+        val TRAMPOLINE_ACTIVITY_COMPONENT =
+            ActivityOptions.ActivityEmbedding.TrampolineActivity.COMPONENT.toFlickerComponent()
+
         @JvmStatic
         fun getWindowExtensions(): WindowExtensions? {
             try {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FlickerExtensions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FlickerExtensions.kt
index 9227e07..5c8cbe4 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FlickerExtensions.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FlickerExtensions.kt
@@ -19,7 +19,7 @@
 package com.android.server.wm.flicker.helpers
 
 import android.tools.common.Rotation
-import android.tools.device.flicker.legacy.IFlickerTestData
+import android.tools.device.flicker.legacy.FlickerTestData
 import android.tools.device.flicker.rules.ChangeDisplayOrientationRule
 
 /**
@@ -27,7 +27,7 @@
  *
  * @param rotation New device rotation
  */
-fun IFlickerTestData.setRotation(rotation: Rotation) =
+fun FlickerTestData.setRotation(rotation: Rotation) =
     ChangeDisplayOrientationRule.setRotation(
         rotation,
         instrumentation,
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/GestureHelper.java b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/GestureHelper.java
index a8f1b3d..eeee7b4 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/GestureHelper.java
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/GestureHelper.java
@@ -62,6 +62,33 @@
     }
 
     /**
+     * Injects a series of {@link MotionEvent}s to simulate tapping.
+     *
+     * @param point coordinates of pointer to tap
+     * @param times the number of times to tap
+     */
+    public boolean tap(@NonNull Tuple point, int times) throws InterruptedException {
+        PointerProperties ptrProp = getPointerProp(0, MotionEvent.TOOL_TYPE_FINGER);
+        PointerCoords ptrCoord = getPointerCoord(point.x, point.y, 1, 1);
+
+        for (int i = 0; i <= times; i++) {
+            // If already tapped, inject delay in between movements
+            if (times > 0) {
+                SystemClock.sleep(50L);
+            }
+            if (!primaryPointerDown(ptrProp, ptrCoord, SystemClock.uptimeMillis())) {
+                return false;
+            }
+            // Delay before releasing tap
+            SystemClock.sleep(100L);
+            if (!primaryPointerUp(ptrProp, ptrCoord, SystemClock.uptimeMillis())) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
      * Injects a series of {@link MotionEvent}s to simulate a drag gesture without pointer release.
      *
      * Simulates a drag gesture without releasing the primary pointer. The primary pointer info
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/LetterboxAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/LetterboxAppHelper.kt
index a670d68..d83b6d3 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/LetterboxAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/LetterboxAppHelper.kt
@@ -17,6 +17,8 @@
 package com.android.server.wm.flicker.helpers
 
 import android.app.Instrumentation
+import android.tools.common.datatypes.Rect
+import android.tools.common.datatypes.Region
 import android.tools.common.traces.component.ComponentNameMatcher
 import android.tools.device.apphelpers.StandardAppHelper
 import android.tools.device.helpers.FIND_TIMEOUT
@@ -36,6 +38,8 @@
         ActivityOptions.NonResizeablePortraitActivity.COMPONENT.toFlickerComponent()
 ) : StandardAppHelper(instr, launcherName, component) {
 
+    private val gestureHelper: GestureHelper = GestureHelper(mInstrumentation)
+
     fun clickRestart(wmHelper: WindowManagerStateHelper) {
         val restartButton =
             uiDevice.wait(
@@ -56,4 +60,74 @@
             ?: error("Restart dialog button not found")
         wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify()
     }
+
+    fun repositionHorizontally(displayBounds: Rect, right: Boolean) {
+        val x = if (right) displayBounds.right - BOUNDS_OFFSET else BOUNDS_OFFSET
+        reposition(x.toFloat(), displayBounds.centerY().toFloat())
+    }
+
+    fun repositionVertically(displayBounds: Rect, bottom: Boolean) {
+        val y = if (bottom) displayBounds.bottom - BOUNDS_OFFSET else BOUNDS_OFFSET
+        reposition(displayBounds.centerX().toFloat(), y.toFloat())
+    }
+
+    private fun reposition(x: Float, y: Float) {
+        val coords = GestureHelper.Tuple(x, y)
+        require(gestureHelper.tap(coords, 2)) { "Failed to reposition letterbox app" }
+    }
+
+    fun waitForAppToMoveHorizontallyTo(
+        wmHelper: WindowManagerStateHelper,
+        displayBounds: Rect,
+        right: Boolean
+    ) {
+        wmHelper
+            .StateSyncBuilder()
+            .add("letterboxAppRepositioned") {
+                val letterboxAppWindow = getWindowRegion(wmHelper)
+                val appRegionBounds = letterboxAppWindow.bounds
+                val appWidth = appRegionBounds.width
+                return@add if (right)
+                    appRegionBounds.left == displayBounds.right - appWidth &&
+                        appRegionBounds.right == displayBounds.right
+                else
+                    appRegionBounds.left == displayBounds.left &&
+                        appRegionBounds.right == displayBounds.left + appWidth
+            }
+            .waitForAndVerify()
+    }
+
+    fun waitForAppToMoveVerticallyTo(
+        wmHelper: WindowManagerStateHelper,
+        displayBounds: Rect,
+        navBarHeight: Int,
+        bottom: Boolean
+    ) {
+        wmHelper
+            .StateSyncBuilder()
+            .add("letterboxAppRepositioned") {
+                val letterboxAppWindow = getWindowRegion(wmHelper)
+                val appRegionBounds = letterboxAppWindow.bounds
+                val appHeight = appRegionBounds.height
+                return@add if (bottom)
+                    appRegionBounds.bottom == displayBounds.bottom &&
+                        appRegionBounds.top == (displayBounds.bottom - appHeight + navBarHeight)
+                else
+                    appRegionBounds.top == displayBounds.top &&
+                        appRegionBounds.bottom == displayBounds.top + appHeight
+            }
+            .waitForAndVerify()
+    }
+
+    private fun getWindowRegion(wmHelper: WindowManagerStateHelper): Region {
+        val windowRegion = wmHelper.getWindowRegion(this)
+        require(!windowRegion.isEmpty) {
+            "Unable to find letterbox app window in the current state"
+        }
+        return windowRegion
+    }
+
+    companion object {
+        private const val BOUNDS_OFFSET: Int = 100
+    }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTest.kt
index 7e0632d..47a1619 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTest.kt
@@ -22,9 +22,8 @@
 import android.tools.common.traces.component.ComponentNameMatcher
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
-import androidx.test.filters.RequiresDevice
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import com.android.server.wm.flicker.BaseTest
 import com.android.server.wm.flicker.helpers.ImeEditorPopupDialogAppHelper
 import org.junit.FixMethodOrder
@@ -33,11 +32,10 @@
 import org.junit.runners.MethodSorters
 import org.junit.runners.Parameterized
 
-@RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class CloseImeOnDismissPopupDialogTest(flicker: FlickerTest) : BaseTest(flicker) {
+class CloseImeOnDismissPopupDialogTest(flicker: LegacyFlickerTest) : BaseTest(flicker) {
     private val imeTestApp = ImeEditorPopupDialogAppHelper(instrumentation)
 
     /** {@inheritDoc} */
@@ -101,10 +99,9 @@
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
                 supportedRotations = listOf(Rotation.ROTATION_0)
             )
-        }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTestCfArm.kt
deleted file mode 100644
index c355e27..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTestCfArm.kt
+++ /dev/null
@@ -1,42 +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.server.wm.flicker.ime
-
-import android.tools.common.Rotation
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
-import org.junit.FixMethodOrder
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class CloseImeOnDismissPopupDialogTestCfArm(flicker: FlickerTest) :
-    CloseImeOnDismissPopupDialogTest(flicker) {
-    companion object {
-        @Parameterized.Parameters(name = "{0}")
-        @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
-                supportedRotations = listOf(Rotation.ROTATION_0)
-            )
-        }
-    }
-}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnGoHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnGoHomeTest.kt
index 537238b..48c54ea 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnGoHomeTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnGoHomeTest.kt
@@ -22,9 +22,8 @@
 import android.tools.common.traces.component.ComponentNameMatcher
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
-import androidx.test.filters.RequiresDevice
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import com.android.server.wm.flicker.BaseTest
 import com.android.server.wm.flicker.helpers.ImeAppHelper
 import org.junit.FixMethodOrder
@@ -37,11 +36,10 @@
  * Test IME window closing to home transitions. To run this test: `atest
  * FlickerTests:CloseImeWindowToHomeTest`
  */
-@RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class CloseImeOnGoHomeTest(flicker: FlickerTest) : BaseTest(flicker) {
+class CloseImeOnGoHomeTest(flicker: LegacyFlickerTest) : BaseTest(flicker) {
     private val testApp = ImeAppHelper(instrumentation)
 
     /** {@inheritDoc} */
@@ -115,10 +113,9 @@
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
                 supportedRotations = listOf(Rotation.ROTATION_0)
             )
-        }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnGoHomeTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnGoHomeTestCfArm.kt
deleted file mode 100644
index 0fe52df..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnGoHomeTestCfArm.kt
+++ /dev/null
@@ -1,29 +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.server.wm.flicker.ime
-
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
-import org.junit.FixMethodOrder
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class CloseImeOnGoHomeTestCfArm(flicker: FlickerTest) : CloseImeOnGoHomeTest(flicker)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartOnGoHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartOnGoHomeTest.kt
index cbe03dc..31d5d7f 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartOnGoHomeTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartOnGoHomeTest.kt
@@ -21,9 +21,8 @@
 import android.tools.common.traces.component.ComponentNameMatcher
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
-import androidx.test.filters.RequiresDevice
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import com.android.server.wm.flicker.BaseTest
 import com.android.server.wm.flicker.helpers.ImeShownOnAppStartHelper
 import org.junit.FixMethodOrder
@@ -45,11 +44,10 @@
  *
  * To run this test: `atest FlickerTests:CloseImeAutoOpenWindowToHomeTest`
  */
-@RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class CloseImeShownOnAppStartOnGoHomeTest(flicker: FlickerTest) : BaseTest(flicker) {
+class CloseImeShownOnAppStartOnGoHomeTest(flicker: LegacyFlickerTest) : BaseTest(flicker) {
     private val testApp = ImeShownOnAppStartHelper(instrumentation, flicker.scenario.startRotation)
 
     /** {@inheritDoc} */
@@ -94,11 +92,10 @@
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
                 // b/190352379 (IME doesn't show on app launch in 90 degrees)
                 supportedRotations = listOf(Rotation.ROTATION_0)
             )
-        }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartOnGoHomeTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartOnGoHomeTestCfArm.kt
deleted file mode 100644
index 5aacb30..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartOnGoHomeTestCfArm.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.server.wm.flicker.ime
-
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
-import org.junit.FixMethodOrder
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class CloseImeShownOnAppStartOnGoHomeTestCfArm(flicker: FlickerTest) :
-    CloseImeShownOnAppStartOnGoHomeTest(flicker)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartToAppOnPressBackTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartToAppOnPressBackTest.kt
index 82c390b..180ae5d 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartToAppOnPressBackTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartToAppOnPressBackTest.kt
@@ -21,9 +21,8 @@
 import android.tools.common.traces.component.ComponentNameMatcher
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
-import androidx.test.filters.RequiresDevice
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import com.android.server.wm.flicker.BaseTest
 import com.android.server.wm.flicker.helpers.ImeShownOnAppStartHelper
 import org.junit.FixMethodOrder
@@ -45,11 +44,10 @@
  *
  * To run this test: `atest FlickerTests:CloseImeAutoOpenWindowToAppTest`
  */
-@RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class CloseImeShownOnAppStartToAppOnPressBackTest(flicker: FlickerTest) : BaseTest(flicker) {
+class CloseImeShownOnAppStartToAppOnPressBackTest(flicker: LegacyFlickerTest) : BaseTest(flicker) {
     private val testApp = ImeShownOnAppStartHelper(instrumentation, flicker.scenario.startRotation)
 
     /** {@inheritDoc} */
@@ -88,11 +86,10 @@
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
                 // b/190352379 (IME doesn't show on app launch in 90 degrees)
                 supportedRotations = listOf(Rotation.ROTATION_0)
             )
-        }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartToAppOnPressBackTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartToAppOnPressBackTestCfArm.kt
deleted file mode 100644
index eb81aed..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartToAppOnPressBackTestCfArm.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.server.wm.flicker.ime
-
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
-import org.junit.FixMethodOrder
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class CloseImeShownOnAppStartToAppOnPressBackTestCfArm(flicker: FlickerTest) :
-    CloseImeShownOnAppStartToAppOnPressBackTest(flicker)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToAppOnPressBackTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToAppOnPressBackTest.kt
index 8d80759..a0573b7 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToAppOnPressBackTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToAppOnPressBackTest.kt
@@ -21,9 +21,8 @@
 import android.tools.common.traces.component.ComponentNameMatcher
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
-import androidx.test.filters.RequiresDevice
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import com.android.server.wm.flicker.BaseTest
 import com.android.server.wm.flicker.helpers.ImeAppHelper
 import com.android.server.wm.flicker.navBarLayerPositionAtStartAndEnd
@@ -38,11 +37,10 @@
  * Test IME window closing back to app window transitions. To run this test: `atest
  * FlickerTests:CloseImeWindowToAppTest`
  */
-@RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class CloseImeToAppOnPressBackTest(flicker: FlickerTest) : BaseTest(flicker) {
+class CloseImeToAppOnPressBackTest(flicker: LegacyFlickerTest) : BaseTest(flicker) {
     private val testApp = ImeAppHelper(instrumentation)
 
     /** {@inheritDoc} */
@@ -115,8 +113,6 @@
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests()
-        }
+        fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToAppOnPressBackTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToAppOnPressBackTestCfArm.kt
deleted file mode 100644
index db1440b..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToAppOnPressBackTestCfArm.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.server.wm.flicker.ime
-
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
-import org.junit.FixMethodOrder
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class CloseImeToAppOnPressBackTestCfArm(flicker: FlickerTest) :
-    CloseImeToAppOnPressBackTest(flicker)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt
index 1526295..99858ce 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt
@@ -22,9 +22,8 @@
 import android.tools.common.Rotation
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
-import androidx.test.filters.RequiresDevice
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import com.android.server.wm.flicker.BaseTest
 import com.android.server.wm.flicker.helpers.ImeAppHelper
 import com.android.server.wm.flicker.helpers.SimpleAppHelper
@@ -40,11 +39,10 @@
  *
  * To run this test: `atest FlickerTests:OpenImeWindowAndCloseTest`
  */
-@RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class CloseImeToHomeOnFinishActivityTest(flicker: FlickerTest) : BaseTest(flicker) {
+class CloseImeToHomeOnFinishActivityTest(flicker: LegacyFlickerTest) : BaseTest(flicker) {
     private val simpleApp = SimpleAppHelper(instrumentation)
     private val testApp = ImeAppHelper(instrumentation)
 
@@ -90,10 +88,9 @@
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
                 supportedRotations = listOf(Rotation.ROTATION_0)
             )
-        }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTestCfArm.kt
deleted file mode 100644
index 405ab6b..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTestCfArm.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.server.wm.flicker.ime
-
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
-import org.junit.FixMethodOrder
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class CloseImeToHomeOnFinishActivityTestCfArm(flicker: FlickerTest) :
-    CloseImeToHomeOnFinishActivityTest(flicker)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CommonAssertions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CommonAssertions.kt
index 8e33719..777231e 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CommonAssertions.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CommonAssertions.kt
@@ -19,21 +19,21 @@
 package com.android.server.wm.flicker.ime
 
 import android.tools.common.traces.component.ComponentNameMatcher
-import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTest
 
-fun FlickerTest.imeLayerBecomesVisible() {
+fun LegacyFlickerTest.imeLayerBecomesVisible() {
     assertLayers {
         this.isInvisible(ComponentNameMatcher.IME).then().isVisible(ComponentNameMatcher.IME)
     }
 }
 
-fun FlickerTest.imeLayerBecomesInvisible() {
+fun LegacyFlickerTest.imeLayerBecomesInvisible() {
     assertLayers {
         this.isVisible(ComponentNameMatcher.IME).then().isInvisible(ComponentNameMatcher.IME)
     }
 }
 
-fun FlickerTest.imeWindowIsAlwaysVisible(rotatesScreen: Boolean = false) {
+fun LegacyFlickerTest.imeWindowIsAlwaysVisible(rotatesScreen: Boolean = false) {
     if (rotatesScreen) {
         assertWm {
             this.isNonAppWindowVisible(ComponentNameMatcher.IME)
@@ -47,7 +47,7 @@
     }
 }
 
-fun FlickerTest.imeWindowBecomesVisible() {
+fun LegacyFlickerTest.imeWindowBecomesVisible() {
     assertWm {
         this.isNonAppWindowInvisible(ComponentNameMatcher.IME)
             .then()
@@ -55,7 +55,7 @@
     }
 }
 
-fun FlickerTest.imeWindowBecomesInvisible() {
+fun LegacyFlickerTest.imeWindowBecomesInvisible() {
     assertWm {
         this.isNonAppWindowVisible(ComponentNameMatcher.IME)
             .then()
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToFixedPortraitAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToFixedPortraitAppTest.kt
index 19bbf0c..976ac82 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToFixedPortraitAppTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToFixedPortraitAppTest.kt
@@ -22,8 +22,8 @@
 import android.tools.common.traces.component.ComponentNameMatcher
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import android.tools.device.helpers.WindowUtils
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.BaseTest
@@ -42,7 +42,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class OpenImeWindowToFixedPortraitAppTest(flicker: FlickerTest) : BaseTest(flicker) {
+class OpenImeWindowToFixedPortraitAppTest(flicker: LegacyFlickerTest) : BaseTest(flicker) {
     private val testApp = ImeShownOnAppStartHelper(instrumentation, flicker.scenario.startRotation)
 
     /** {@inheritDoc} */
@@ -99,19 +99,18 @@
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
          * navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
                 supportedRotations =
                     listOf(
                         Rotation.ROTATION_90,
                     ),
                 supportedNavigationModes = listOf(NavBar.MODE_3BUTTON, NavBar.MODE_GESTURAL)
             )
-        }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppCfArmTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppCfArmTest.kt
deleted file mode 100644
index 03f21f9..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppCfArmTest.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.server.wm.flicker.ime
-
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
-import org.junit.FixMethodOrder
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class ShowImeOnAppStartWhenLaunchingAppCfArmTest(flicker: FlickerTest) :
-    ShowImeOnAppStartWhenLaunchingAppTest(flicker)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest.kt
index 496165a..ea7c7c4 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest.kt
@@ -16,18 +16,16 @@
 
 package com.android.server.wm.flicker.ime
 
+import android.platform.test.annotations.Postsubmit
 import android.platform.test.annotations.Presubmit
 import android.tools.common.Rotation
-import android.platform.test.annotations.Postsubmit
-import android.tools.common.Timestamp
+import android.tools.common.flicker.subject.layers.LayerTraceEntrySubject
 import android.tools.common.traces.component.ComponentNameMatcher
-import android.tools.common.flicker.subject.exceptions.ExceptionMessageBuilder
-import android.tools.common.flicker.subject.exceptions.InvalidPropertyException
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
-import androidx.test.filters.RequiresDevice
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.FlakyTest
 import com.android.server.wm.flicker.BaseTest
 import com.android.server.wm.flicker.helpers.ImeShownOnAppStartHelper
 import com.android.server.wm.flicker.helpers.setRotation
@@ -43,11 +41,10 @@
  * (e.g. Launcher activity). To run this test: `atest
  * FlickerTests:ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest`
  */
-@RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest(flicker: FlickerTest) :
+class ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest(flicker: LegacyFlickerTest) :
     BaseTest(flicker) {
     private val imeTestApp =
         ImeShownOnAppStartHelper(instrumentation, flicker.scenario.startRotation)
@@ -82,45 +79,43 @@
         flicker.snapshotStartingWindowLayerCoversExactlyOnApp(imeTestApp)
     }
 
+    @FlakyTest(bugId = 290767483)
     @Postsubmit
     @Test
     fun imeLayerAlphaOneAfterSnapshotStartingWindowRemoval() {
-        // Check if the snapshot appeared during the trace
-        var imeSnapshotRemovedTimestamp: Timestamp? = null
+        val layerTrace = flicker.reader.readLayersTrace() ?: error("Unable to read layers trace")
 
-        val layerTrace = flicker.reader.readLayersTrace()
-        val layerTraceEntries = layerTrace?.entries?.toList() ?: emptyList()
+        // Find the entries immediately after the IME snapshot has disappeared
+        val imeSnapshotRemovedEntries =
+            layerTrace.entries
+                .asSequence()
+                .zipWithNext { prev, next ->
+                    if (
+                        ComponentNameMatcher.SNAPSHOT.layerMatchesAnyOf(prev.visibleLayers) &&
+                            !ComponentNameMatcher.SNAPSHOT.layerMatchesAnyOf(next.visibleLayers)
+                    ) {
+                        next
+                    } else {
+                        null
+                    }
+                }
+                .filterNotNull()
 
-        layerTraceEntries.zipWithNext { prev, next ->
-            val prevSnapshotLayerVisible =
-                    ComponentNameMatcher.SNAPSHOT.layerMatchesAnyOf(prev.visibleLayers)
-            val nextSnapshotLayerVisible =
-                    ComponentNameMatcher.SNAPSHOT.layerMatchesAnyOf(next.visibleLayers)
+        // If we find it, make sure the IME is visible and fully animated in.
+        imeSnapshotRemovedEntries.forEach { entry ->
+            val entrySubject = LayerTraceEntrySubject(entry)
+            val imeLayerSubjects =
+                entrySubject.subjects.filter {
+                    ComponentNameMatcher.IME.layerMatchesAnyOf(it.layer) && it.isVisible
+                }
 
-            if (imeSnapshotRemovedTimestamp == null &&
-                    (prevSnapshotLayerVisible && !nextSnapshotLayerVisible)) {
-                imeSnapshotRemovedTimestamp = next.timestamp
-            }
-        }
+            entrySubject
+                .check { "InputMethod must exist and be visible" }
+                .that(imeLayerSubjects.isNotEmpty())
+                .isEqual(true)
 
-        // if so, make an assertion
-        imeSnapshotRemovedTimestamp?.let { timestamp ->
-            val stateAfterSnapshot = layerTrace?.getEntryAt(timestamp)
-                    ?: error("State not found for $timestamp")
-
-            val imeLayers = ComponentNameMatcher.IME
-                    .filterLayers(stateAfterSnapshot.visibleLayers.toList())
-
-            require(imeLayers.isNotEmpty()) { "IME layer not found" }
-            if (imeLayers.any { it.color.a != 1.0f }) {
-                val errorMsgBuilder = ExceptionMessageBuilder()
-                        .setTimestamp(timestamp)
-                        .forInvalidProperty("IME layer alpha")
-                        .setExpected("is 1.0")
-                        .setActual("not 1.0")
-                        .addExtraDescription("Filter",
-                                ComponentNameMatcher.IME.toLayerIdentifier())
-                throw InvalidPropertyException(errorMsgBuilder)
+            imeLayerSubjects.forEach { imeLayerSubject ->
+                imeLayerSubject.check { "alpha" }.that(imeLayerSubject.layer.color.a).isEqual(1.0f)
             }
         }
     }
@@ -129,15 +124,14 @@
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
          * navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
                 supportedRotations = listOf(Rotation.ROTATION_90)
             )
-        }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTestCfArm.kt
deleted file mode 100644
index 3aca2a0..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTestCfArm.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.server.wm.flicker.ime
-
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
-import org.junit.FixMethodOrder
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTestCfArm(flicker: FlickerTest) :
-    ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest(flicker)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromOverviewTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromOverviewTest.kt
index f64ad53..05babd67 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromOverviewTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromOverviewTest.kt
@@ -21,10 +21,9 @@
 import android.tools.common.traces.component.ComponentNameMatcher
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import android.tools.device.helpers.reopenAppFromOverview
-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.helpers.setRotation
@@ -37,11 +36,10 @@
 /**
  * Test IME window opening transitions. To run this test: `atest FlickerTests:ReOpenImeWindowTest`
  */
-@RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class ShowImeOnAppStartWhenLaunchingAppFromOverviewTest(flicker: FlickerTest) :
+class ShowImeOnAppStartWhenLaunchingAppFromOverviewTest(flicker: LegacyFlickerTest) :
     BaseTest(flicker) {
     private val testApp = ImeShownOnAppStartHelper(instrumentation, flicker.scenario.startRotation)
 
@@ -140,10 +138,9 @@
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
                 supportedRotations = listOf(Rotation.ROTATION_0)
             )
-        }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromOverviewTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromOverviewTestCfArm.kt
deleted file mode 100644
index e1aa418..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromOverviewTestCfArm.kt
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.flicker.ime
-
-import android.tools.common.Rotation
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
-import org.junit.FixMethodOrder
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class ShowImeOnAppStartWhenLaunchingAppFromOverviewTestCfArm(flicker: FlickerTest) :
-    ShowImeOnAppStartWhenLaunchingAppFromOverviewTest(flicker) {
-
-    companion object {
-        @Parameterized.Parameters(name = "{0}")
-        @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
-                supportedRotations = listOf(Rotation.ROTATION_0)
-            )
-        }
-    }
-}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest.kt
index 11bc7b9..aff8e65 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest.kt
@@ -22,9 +22,8 @@
 import android.tools.common.traces.component.ComponentNameMatcher
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
-import androidx.test.filters.RequiresDevice
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import com.android.server.wm.flicker.BaseTest
 import com.android.server.wm.flicker.helpers.ImeShownOnAppStartHelper
 import com.android.server.wm.flicker.helpers.SimpleAppHelper
@@ -39,12 +38,11 @@
  * Test IME windows switching with 2-Buttons or gestural navigation. To run this test: `atest
  * FlickerTests:SwitchImeWindowsFromGestureNavTest`
  */
-@RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 @Presubmit
-open class ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest(flicker: FlickerTest) :
+class ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest(flicker: LegacyFlickerTest) :
     BaseTest(flicker) {
     private val testApp = SimpleAppHelper(instrumentation)
     private val imeTestApp =
@@ -121,12 +119,11 @@
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
                 supportedNavigationModes = listOf(NavBar.MODE_GESTURAL),
                 supportedRotations = listOf(Rotation.ROTATION_0)
             )
-        }
 
         private const val TAG_IME_VISIBLE = "imeVisible"
         private const val TAG_IME_INVISIBLE = "imeInVisible"
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTestCfArm.kt
deleted file mode 100644
index 0f57467..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTestCfArm.kt
+++ /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.
- */
-
-package com.android.server.wm.flicker.ime
-
-import android.platform.test.annotations.Presubmit
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
-import org.junit.FixMethodOrder
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Presubmit
-open class ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTestCfArm(flicker: FlickerTest) :
-    ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest(flicker)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppTest.kt
index 46967db..4ffdcea 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppTest.kt
@@ -21,9 +21,8 @@
 import android.tools.common.traces.component.ComponentNameMatcher
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
-import androidx.test.filters.RequiresDevice
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import com.android.server.wm.flicker.BaseTest
 import com.android.server.wm.flicker.helpers.ImeShownOnAppStartHelper
 import com.android.server.wm.flicker.helpers.ImeStateInitializeHelper
@@ -75,11 +74,10 @@
  *        apps are running before setup
  * ```
  */
-@RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class ShowImeOnAppStartWhenLaunchingAppTest(flicker: FlickerTest) : BaseTest(flicker) {
+class ShowImeOnAppStartWhenLaunchingAppTest(flicker: LegacyFlickerTest) : BaseTest(flicker) {
     private val testApp = ImeShownOnAppStartHelper(instrumentation, flicker.scenario.startRotation)
     private val initializeApp = ImeStateInitializeHelper(instrumentation)
 
@@ -123,15 +121,14 @@
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
          * navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
                 supportedRotations = listOf(Rotation.ROTATION_0)
             )
-        }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhenFocusingOnInputFieldTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhenFocusingOnInputFieldTest.kt
index 827110d..918e1ea 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhenFocusingOnInputFieldTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhenFocusingOnInputFieldTest.kt
@@ -21,9 +21,8 @@
 import android.tools.common.Rotation
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
-import androidx.test.filters.RequiresDevice
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import com.android.server.wm.flicker.BaseTest
 import com.android.server.wm.flicker.helpers.ImeAppHelper
 import org.junit.FixMethodOrder
@@ -33,11 +32,10 @@
 import org.junit.runners.Parameterized
 
 /** Test IME window opening transitions. To run this test: `atest FlickerTests:OpenImeWindowTest` */
-@RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class ShowImeWhenFocusingOnInputFieldTest(flicker: FlickerTest) : BaseTest(flicker) {
+class ShowImeWhenFocusingOnInputFieldTest(flicker: LegacyFlickerTest) : BaseTest(flicker) {
     private val testApp = ImeAppHelper(instrumentation)
 
     /** {@inheritDoc} */
@@ -79,10 +77,9 @@
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
                 supportedRotations = listOf(Rotation.ROTATION_0)
             )
-        }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhenFocusingOnInputFieldTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhenFocusingOnInputFieldTestCfArm.kt
deleted file mode 100644
index f5b2294..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhenFocusingOnInputFieldTestCfArm.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.server.wm.flicker.ime
-
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
-import org.junit.FixMethodOrder
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class ShowImeWhenFocusingOnInputFieldTestCfArm(flicker: FlickerTest) :
-    ShowImeWhenFocusingOnInputFieldTest(flicker)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileDismissingThemedPopupDialogTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileDismissingThemedPopupDialogTest.kt
index 277b9155..6ad235c 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileDismissingThemedPopupDialogTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileDismissingThemedPopupDialogTest.kt
@@ -21,12 +21,11 @@
 import android.tools.common.traces.component.ComponentNameMatcher
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import android.view.WindowInsets.Type.ime
 import android.view.WindowInsets.Type.navigationBars
 import android.view.WindowInsets.Type.statusBars
-import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.BaseTest
 import com.android.server.wm.flicker.helpers.ImeShownOnAppStartHelper
 import org.junit.Assert.assertFalse
@@ -41,11 +40,10 @@
  * Test IME snapshot mechanism won't apply when transitioning from non-IME focused dialog activity.
  * To run this test: `atest FlickerTests:LaunchAppShowImeAndDialogThemeAppTest`
  */
-@RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class ShowImeWhileDismissingThemedPopupDialogTest(flicker: FlickerTest) : BaseTest(flicker) {
+class ShowImeWhileDismissingThemedPopupDialogTest(flicker: LegacyFlickerTest) : BaseTest(flicker) {
     private val testApp = ImeShownOnAppStartHelper(instrumentation, flicker.scenario.startRotation)
 
     /** {@inheritDoc} */
@@ -84,15 +82,14 @@
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
          * navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
                 supportedRotations = listOf(Rotation.ROTATION_0)
             )
-        }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileDismissingThemedPopupDialogTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileDismissingThemedPopupDialogTestCfArm.kt
deleted file mode 100644
index 8891d26..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileDismissingThemedPopupDialogTestCfArm.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.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.flicker.ime
-
-import android.tools.common.Rotation
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
-import org.junit.FixMethodOrder
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class ShowImeWhileDismissingThemedPopupDialogTestCfArm(flicker: FlickerTest) :
-    ShowImeWhileDismissingThemedPopupDialogTest(flicker) {
-    companion object {
-        /**
-         * Creates the test configurations.
-         *
-         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
-         * navigation modes.
-         */
-        @Parameterized.Parameters(name = "{0}")
-        @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
-                supportedRotations = listOf(Rotation.ROTATION_0)
-            )
-        }
-    }
-}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileEnteringOverviewTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileEnteringOverviewTest.kt
index 9275d6a..f1bc125 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileEnteringOverviewTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileEnteringOverviewTest.kt
@@ -21,10 +21,9 @@
 import android.tools.common.traces.component.ComponentNameMatcher
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import android.tools.device.traces.parsers.WindowManagerStateHelper
-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.navBarLayerIsVisibleAtStartAndEnd
@@ -41,11 +40,10 @@
  * Test IME window layer will be associated with the app task when going to the overview screen. To
  * run this test: `atest FlickerTests:OpenImeWindowToOverViewTest`
  */
-@RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class ShowImeWhileEnteringOverviewTest(flicker: FlickerTest) : BaseTest(flicker) {
+class ShowImeWhileEnteringOverviewTest(flicker: LegacyFlickerTest) : BaseTest(flicker) {
     private val imeTestApp =
         ImeShownOnAppStartHelper(instrumentation, flicker.scenario.startRotation)
 
@@ -205,13 +203,11 @@
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
          * navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests()
-        }
+        fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileEnteringOverviewTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileEnteringOverviewTestCfArm.kt
deleted file mode 100644
index fc39713..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileEnteringOverviewTestCfArm.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.server.wm.flicker.ime
-
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
-import org.junit.FixMethodOrder
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class ShowImeWhileEnteringOverviewTestCfArm(flicker: FlickerTest) :
-    ShowImeWhileEnteringOverviewTest(flicker)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt
deleted file mode 100644
index e6594c9..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.flicker.launch
-
-import android.platform.test.annotations.Presubmit
-import android.tools.common.traces.component.ComponentNameMatcher
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
-import android.tools.device.traces.parsers.toFlickerComponent
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.BaseTest
-import com.android.server.wm.flicker.helpers.TwoActivitiesAppHelper
-import com.android.server.wm.flicker.testapp.ActivityOptions
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test the back and forward transition between 2 activities.
- *
- * To run this test: `atest FlickerTests:ActivitiesTransitionTest`
- *
- * Actions:
- * ```
- *     Launch an app
- *     Launch a secondary activity within the app
- *     Close the secondary activity back to the initial one
- * ```
- *
- * Notes:
- * ```
- *     1. Part of the test setup occurs automatically via
- *        [com.android.server.wm.flicker.TransitionRunnerWithRules],
- *        including configuring navigation mode, initial orientation and ensuring no
- *        apps are running before setup
- * ```
- */
-@RequiresDevice
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class ActivitiesTransitionTest(flicker: FlickerTest) : BaseTest(flicker) {
-    private val testApp: TwoActivitiesAppHelper = TwoActivitiesAppHelper(instrumentation)
-
-    /** {@inheritDoc} */
-    override val transition: FlickerBuilder.() -> Unit = {
-        setup {
-            tapl.setExpectedRotation(flicker.scenario.startRotation.value)
-            testApp.launchViaIntent(wmHelper)
-        }
-        teardown { testApp.exit(wmHelper) }
-        transitions {
-            testApp.openSecondActivity(device, wmHelper)
-            tapl.pressBack()
-            wmHelper.StateSyncBuilder().withFullScreenApp(testApp).waitForAndVerify()
-        }
-    }
-
-    /**
-     * Checks that the [ActivityOptions.LaunchNewActivity] activity is visible at the start of the
-     * transition, that [ActivityOptions.SimpleActivity] becomes visible during the transition, and
-     * that [ActivityOptions.LaunchNewActivity] is again visible at the end
-     */
-    @Presubmit
-    @Test
-    fun finishSubActivity() {
-        val buttonActivityComponent =
-            ActivityOptions.LaunchNewActivity.COMPONENT.toFlickerComponent()
-        val imeAutoFocusActivityComponent =
-            ActivityOptions.SimpleActivity.COMPONENT.toFlickerComponent()
-        flicker.assertWm {
-            this.isAppWindowOnTop(buttonActivityComponent)
-                .then()
-                .isAppWindowOnTop(imeAutoFocusActivityComponent)
-                .then()
-                .isAppWindowOnTop(buttonActivityComponent)
-        }
-    }
-
-    /**
-     * Checks that the [ComponentNameMatcher.LAUNCHER] window is not on top. The launcher cannot be
-     * asserted with `isAppWindowVisible` because it contains 2 windows with the exact same name,
-     * and both are never simultaneously visible
-     */
-    @Presubmit
-    @Test
-    fun launcherWindowNotOnTop() {
-        flicker.assertWm { this.isAppWindowNotOnTop(ComponentNameMatcher.LAUNCHER) }
-    }
-
-    /**
-     * Checks that the [ComponentNameMatcher.LAUNCHER] layer is never visible during the transition
-     */
-    @Presubmit
-    @Test
-    fun launcherLayerNotVisible() {
-        flicker.assertLayers { this.isInvisible(ComponentNameMatcher.LAUNCHER) }
-    }
-
-    companion object {
-        /**
-         * Creates the test configurations.
-         *
-         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
-         * navigation modes.
-         */
-        @Parameterized.Parameters(name = "{0}")
-        @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests()
-        }
-    }
-}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTestCfArm.kt
deleted file mode 100644
index 8b89a8b..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTestCfArm.kt
+++ /dev/null
@@ -1,44 +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.server.wm.flicker.launch
-
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
-import org.junit.FixMethodOrder
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class ActivitiesTransitionTestCfArm(flicker: FlickerTest) : ActivitiesTransitionTest(flicker) {
-    companion object {
-        /**
-         * Creates the test configurations.
-         *
-         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
-         * navigation modes.
-         */
-        @Parameterized.Parameters(name = "{0}")
-        @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests()
-        }
-    }
-}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivityTransitionTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivityTransitionTest.kt
new file mode 100644
index 0000000..89ab41e
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivityTransitionTest.kt
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.flicker.launch
+
+import android.platform.test.annotations.Presubmit
+import android.tools.common.traces.component.ComponentNameMatcher
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+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 com.android.server.wm.flicker.BaseTest
+import com.android.server.wm.flicker.helpers.TwoActivitiesAppHelper
+import com.android.server.wm.flicker.testapp.ActivityOptions
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test the back and forward transition between 2 activities.
+ *
+ * To run this test: `atest FlickerTests:ActivitiesTransitionTest`
+ *
+ * Actions:
+ * ```
+ *     Launch an app
+ *     Launch a secondary activity within the app
+ *     Close the secondary activity back to the initial one
+ * ```
+ *
+ * Notes:
+ * ```
+ *     1. Part of the test setup occurs automatically via
+ *        [com.android.server.wm.flicker.TransitionRunnerWithRules],
+ *        including configuring navigation mode, initial orientation and ensuring no
+ *        apps are running before setup
+ * ```
+ */
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class ActivityTransitionTest(flicker: LegacyFlickerTest) : BaseTest(flicker) {
+    private val testApp: TwoActivitiesAppHelper = TwoActivitiesAppHelper(instrumentation)
+
+    /** {@inheritDoc} */
+    override val transition: FlickerBuilder.() -> Unit = {
+        setup {
+            tapl.setExpectedRotation(flicker.scenario.startRotation.value)
+            testApp.launchViaIntent(wmHelper)
+        }
+        teardown { testApp.exit(wmHelper) }
+        transitions {
+            testApp.openSecondActivity(device, wmHelper)
+            tapl.pressBack()
+            wmHelper.StateSyncBuilder().withFullScreenApp(testApp).waitForAndVerify()
+        }
+    }
+
+    /**
+     * Checks that the [ActivityOptions.LaunchNewActivity] activity is visible at the start of the
+     * transition, that [ActivityOptions.SimpleActivity] becomes visible during the transition, and
+     * that [ActivityOptions.LaunchNewActivity] is again visible at the end
+     */
+    @Presubmit
+    @Test
+    fun finishSubActivity() {
+        val buttonActivityComponent =
+            ActivityOptions.LaunchNewActivity.COMPONENT.toFlickerComponent()
+        val imeAutoFocusActivityComponent =
+            ActivityOptions.SimpleActivity.COMPONENT.toFlickerComponent()
+        flicker.assertWm {
+            this.isAppWindowOnTop(buttonActivityComponent)
+                .then()
+                .isAppWindowOnTop(imeAutoFocusActivityComponent)
+                .then()
+                .isAppWindowOnTop(buttonActivityComponent)
+        }
+    }
+
+    /**
+     * Checks that the [ComponentNameMatcher.LAUNCHER] window is not on top. The launcher cannot be
+     * asserted with `isAppWindowVisible` because it contains 2 windows with the exact same name,
+     * and both are never simultaneously visible
+     */
+    @Presubmit
+    @Test
+    fun launcherWindowNotOnTop() {
+        flicker.assertWm { this.isAppWindowNotOnTop(ComponentNameMatcher.LAUNCHER) }
+    }
+
+    /**
+     * Checks that the [ComponentNameMatcher.LAUNCHER] layer is never visible during the transition
+     */
+    @Presubmit
+    @Test
+    fun launcherLayerNotVisible() {
+        flicker.assertLayers { this.isInvisible(ComponentNameMatcher.LAUNCHER) }
+    }
+
+    companion object {
+        /**
+         * Creates the test configurations.
+         *
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * navigation modes.
+         */
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTest.kt
deleted file mode 100644
index 549183f..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTest.kt
+++ /dev/null
@@ -1,73 +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.server.wm.flicker.launch
-
-import android.tools.device.apphelpers.CameraAppHelper
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
-import androidx.test.filters.RequiresDevice
-import org.junit.FixMethodOrder
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test launching an app after cold opening camera
- *
- * To run this test: `atest FlickerTests:OpenAppAfterCameraTest`
- *
- * Notes: Some default assertions are inherited [OpenAppTransition]
- */
-@RequiresDevice
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class OpenAppAfterCameraTest(flicker: FlickerTest) : OpenAppFromLauncherTransition(flicker) {
-    private val cameraApp = CameraAppHelper(instrumentation)
-    /** {@inheritDoc} */
-    override val transition: FlickerBuilder.() -> Unit
-        get() = {
-            super.transition(this)
-            setup {
-                tapl.setExpectedRotationCheckEnabled(false)
-                // 1. Open camera - cold -> close it first
-                cameraApp.exit(wmHelper)
-                cameraApp.launchViaIntent(wmHelper)
-                // Can't use TAPL due to Recents not showing in 3 Button Nav in full screen mode
-                device.pressHome()
-                tapl.getWorkspace()
-            }
-            teardown { testApp.exit(wmHelper) }
-            transitions { testApp.launchViaIntent(wmHelper) }
-        }
-
-    companion object {
-        /**
-         * Creates the test configurations.
-         *
-         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
-         * navigation modes.
-         */
-        @Parameterized.Parameters(name = "{0}")
-        @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests()
-        }
-    }
-}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTestCfArm.kt
deleted file mode 100644
index ac05c76..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTestCfArm.kt
+++ /dev/null
@@ -1,44 +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.server.wm.flicker.launch
-
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
-import org.junit.FixMethodOrder
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class OpenAppAfterCameraTestCfArm(flicker: FlickerTest) : OpenAppAfterCameraTest(flicker) {
-    companion object {
-        /**
-         * Creates the test configurations.
-         *
-         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
-         * navigation modes.
-         */
-        @Parameterized.Parameters(name = "{0}")
-        @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests()
-        }
-    }
-}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIcon.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIcon.kt
deleted file mode 100644
index 3a80c66..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIcon.kt
+++ /dev/null
@@ -1,219 +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.server.wm.flicker.launch
-
-import android.platform.test.annotations.FlakyTest
-import android.tools.common.Rotation
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
-import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule
-import androidx.test.filters.RequiresDevice
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test cold launching an app from launcher
- *
- * To run this test: `atest FlickerTests:OpenAppColdFromIcon`
- *
- * Actions:
- * ```
- *     Make sure no apps are running on the device
- *     Launch an app [testApp] by clicking it's icon on all apps and wait animation to complete
- * ```
- *
- * Notes:
- * ```
- *     1. Some default assertions (e.g., nav bar, status bar and screen covered)
- *        are inherited [OpenAppTransition]
- *     2. Part of the test setup occurs automatically via
- *        [com.android.server.wm.flicker.TransitionRunnerWithRules],
- *        including configuring navigation mode, initial orientation and ensuring no
- *        apps are running before setup
- * ```
- */
-@RequiresDevice
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class OpenAppColdFromIcon(flicker: FlickerTest) : OpenAppFromLauncherTransition(flicker) {
-    /** {@inheritDoc} */
-    override val transition: FlickerBuilder.() -> Unit
-        get() = {
-            super.transition(this)
-            setup {
-                if (flicker.scenario.isTablet) {
-                    tapl.setExpectedRotation(flicker.scenario.startRotation.value)
-                } else {
-                    tapl.setExpectedRotation(Rotation.ROTATION_0.value)
-                }
-                RemoveAllTasksButHomeRule.removeAllTasksButHome()
-            }
-            transitions {
-                tapl
-                    .goHome()
-                    .switchToAllApps()
-                    .getAppIcon(testApp.launcherName)
-                    .launch(testApp.`package`)
-            }
-            teardown { testApp.exit(wmHelper) }
-        }
-
-    @FlakyTest(bugId = 240916028)
-    @Test
-    override fun focusChanges() {
-        super.focusChanges()
-    }
-
-    @FlakyTest(bugId = 240916028)
-    @Test
-    override fun appWindowReplacesLauncherAsTopWindow() {
-        super.appWindowReplacesLauncherAsTopWindow()
-    }
-    @FlakyTest(bugId = 240916028)
-    @Test
-    override fun appWindowAsTopWindowAtEnd() {
-        super.appWindowAsTopWindowAtEnd()
-    }
-
-    @FlakyTest(bugId = 240916028)
-    @Test
-    override fun appWindowBecomesTopWindow() {
-        super.appWindowBecomesTopWindow()
-    }
-
-    @FlakyTest(bugId = 240916028)
-    @Test
-    override fun appWindowBecomesVisible() {
-        super.appWindowBecomesVisible()
-    }
-
-    @FlakyTest(bugId = 240916028)
-    @Test
-    override fun appWindowIsTopWindowAtEnd() {
-        super.appWindowIsTopWindowAtEnd()
-    }
-
-    @FlakyTest(bugId = 240916028)
-    @Test
-    override fun appLayerBecomesVisible() {
-        super.appLayerBecomesVisible()
-    }
-
-    @FlakyTest(bugId = 240916028)
-    @Test
-    override fun appLayerReplacesLauncher() {
-        super.appLayerReplacesLauncher()
-    }
-
-    @FlakyTest(bugId = 240916028)
-    @Test
-    override fun cujCompleted() {
-        super.cujCompleted()
-    }
-
-    @FlakyTest(bugId = 240916028)
-    @Test
-    override fun entireScreenCovered() {
-        super.entireScreenCovered()
-    }
-
-    @FlakyTest(bugId = 240916028)
-    @Test
-    override fun navBarLayerIsVisibleAtStartAndEnd() {
-        super.navBarLayerIsVisibleAtStartAndEnd()
-    }
-
-    @FlakyTest(bugId = 240916028)
-    @Test
-    override fun navBarLayerPositionAtStartAndEnd() {
-        super.navBarLayerPositionAtStartAndEnd()
-    }
-
-    @FlakyTest(bugId = 240916028)
-    @Test
-    override fun navBarWindowIsAlwaysVisible() {
-        super.navBarWindowIsAlwaysVisible()
-    }
-
-    @FlakyTest(bugId = 240916028)
-    @Test
-    override fun navBarWindowIsVisibleAtStartAndEnd() {
-        super.navBarWindowIsVisibleAtStartAndEnd()
-    }
-
-    @FlakyTest(bugId = 240916028)
-    @Test
-    override fun statusBarLayerIsVisibleAtStartAndEnd() {
-        super.statusBarLayerIsVisibleAtStartAndEnd()
-    }
-
-    @FlakyTest(bugId = 240916028)
-    @Test
-    override fun statusBarLayerPositionAtStartAndEnd() {
-        super.statusBarLayerPositionAtStartAndEnd()
-    }
-
-    @FlakyTest(bugId = 240916028)
-    @Test
-    override fun statusBarWindowIsAlwaysVisible() {
-        super.statusBarWindowIsAlwaysVisible()
-    }
-
-    @FlakyTest(bugId = 240916028)
-    @Test
-    override fun taskBarLayerIsVisibleAtStartAndEnd() {
-        super.taskBarLayerIsVisibleAtStartAndEnd()
-    }
-
-    @FlakyTest(bugId = 240916028)
-    @Test
-    override fun taskBarWindowIsAlwaysVisible() {
-        super.taskBarWindowIsAlwaysVisible()
-    }
-
-    @FlakyTest(bugId = 240916028)
-    @Test
-    override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
-        super.visibleLayersShownMoreThanOneConsecutiveEntry()
-    }
-
-    @FlakyTest(bugId = 240916028)
-    @Test
-    override fun visibleWindowsShownMoreThanOneConsecutiveEntry() {
-        super.visibleWindowsShownMoreThanOneConsecutiveEntry()
-    }
-
-    companion object {
-        /**
-         * Creates the test configurations.
-         *
-         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
-         * navigation modes.
-         */
-        @Parameterized.Parameters(name = "{0}")
-        @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests()
-        }
-    }
-}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIconCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIconCfArm.kt
deleted file mode 100644
index d33a272..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIconCfArm.kt
+++ /dev/null
@@ -1,55 +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.server.wm.flicker.launch
-
-import android.platform.test.annotations.FlakyTest
-import android.tools.device.flicker.annotation.FlickerServiceCompatible
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/** Some assertions will fail because of b/264415996 */
-@FlickerServiceCompatible
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class OpenAppColdFromIconCfArm(flicker: FlickerTest) : OpenAppColdFromIcon(flicker) {
-    @Test
-    @FlakyTest
-    override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
-        super.visibleLayersShownMoreThanOneConsecutiveEntry()
-    }
-
-    companion object {
-        /**
-         * Creates the test configurations.
-         *
-         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
-         * navigation modes.
-         */
-        @Parameterized.Parameters(name = "{0}")
-        @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests()
-        }
-    }
-}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
deleted file mode 100644
index 26f88d2..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * 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.
- */
-
-package com.android.server.wm.flicker.launch
-
-import android.platform.test.annotations.Postsubmit
-import android.platform.test.annotations.Presubmit
-import android.tools.device.flicker.annotation.FlickerServiceCompatible
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
-import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule.Companion.removeAllTasksButHome
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.helpers.setRotation
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test cold launching an app from launcher
- *
- * To run this test: `atest FlickerTests:OpenAppColdTest`
- *
- * Actions:
- * ```
- *     Make sure no apps are running on the device
- *     Launch an app [testApp] and wait animation to complete
- * ```
- *
- * Notes:
- * ```
- *     1. Some default assertions (e.g., nav bar, status bar and screen covered)
- *        are inherited [OpenAppTransition]
- *     2. Part of the test setup occurs automatically via
- *        [com.android.server.wm.flicker.TransitionRunnerWithRules],
- *        including configuring navigation mode, initial orientation and ensuring no
- *        apps are running before setup
- * ```
- */
-@RequiresDevice
-@FlickerServiceCompatible
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class OpenAppColdTest(flicker: FlickerTest) : OpenAppFromLauncherTransition(flicker) {
-    /** {@inheritDoc} */
-    override val transition: FlickerBuilder.() -> Unit
-        get() = {
-            super.transition(this)
-            setup {
-                removeAllTasksButHome()
-                this.setRotation(flicker.scenario.startRotation)
-            }
-            teardown { testApp.exit(wmHelper) }
-            transitions { testApp.launchViaIntent(wmHelper) }
-        }
-
-    /** {@inheritDoc} */
-    @Presubmit @Test override fun appLayerReplacesLauncher() = super.appLayerReplacesLauncher()
-
-    @Postsubmit
-    @Test
-    override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
-
-    companion object {
-        /**
-         * Creates the test configurations.
-         *
-         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
-         * navigation modes.
-         */
-        @Parameterized.Parameters(name = "{0}")
-        @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests()
-        }
-    }
-}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTestCfArm.kt
deleted file mode 100644
index d9a99da..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTestCfArm.kt
+++ /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 com.android.server.wm.flicker.launch
-
-import android.platform.test.annotations.FlakyTest
-import android.tools.device.flicker.annotation.FlickerServiceCompatible
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-@FlickerServiceCompatible
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class OpenAppColdTestCfArm(flicker: FlickerTest) : OpenAppColdTest(flicker) {
-    @FlakyTest(bugId = 273696733)
-    @Test
-    override fun appLayerReplacesLauncher() = super.appLayerReplacesLauncher()
-
-    companion object {
-        /**
-         * Creates the test configurations.
-         *
-         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
-         * navigation modes.
-         */
-        @Parameterized.Parameters(name = "{0}")
-        @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests()
-        }
-    }
-}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIconColdTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIconColdTest.kt
new file mode 100644
index 0000000..57eb172
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIconColdTest.kt
@@ -0,0 +1,215 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.launch
+
+import android.platform.test.annotations.FlakyTest
+import android.tools.common.Rotation
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+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.flicker.rules.RemoveAllTasksButHomeRule
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test cold launching an app from launcher
+ *
+ * To run this test: `atest FlickerTests:OpenAppColdFromIcon`
+ *
+ * Actions:
+ * ```
+ *     Make sure no apps are running on the device
+ *     Launch an app [testApp] by clicking it's icon on all apps and wait animation to complete
+ * ```
+ *
+ * Notes:
+ * ```
+ *     1. Some default assertions (e.g., nav bar, status bar and screen covered)
+ *        are inherited [OpenAppTransition]
+ *     2. Part of the test setup occurs automatically via
+ *        [com.android.server.wm.flicker.TransitionRunnerWithRules],
+ *        including configuring navigation mode, initial orientation and ensuring no
+ *        apps are running before setup
+ * ```
+ */
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class OpenAppFromIconColdTest(flicker: LegacyFlickerTest) : OpenAppFromLauncherTransition(flicker) {
+    /** {@inheritDoc} */
+    override val transition: FlickerBuilder.() -> Unit
+        get() = {
+            super.transition(this)
+            setup {
+                if (flicker.scenario.isTablet) {
+                    tapl.setExpectedRotation(flicker.scenario.startRotation.value)
+                } else {
+                    tapl.setExpectedRotation(Rotation.ROTATION_0.value)
+                }
+                RemoveAllTasksButHomeRule.removeAllTasksButHome()
+            }
+            transitions {
+                tapl
+                    .goHome()
+                    .switchToAllApps()
+                    .getAppIcon(testApp.launcherName)
+                    .launch(testApp.`package`)
+            }
+            teardown { testApp.exit(wmHelper) }
+        }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun focusChanges() {
+        super.focusChanges()
+    }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun appWindowReplacesLauncherAsTopWindow() {
+        super.appWindowReplacesLauncherAsTopWindow()
+    }
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun appWindowAsTopWindowAtEnd() {
+        super.appWindowAsTopWindowAtEnd()
+    }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun appWindowBecomesTopWindow() {
+        super.appWindowBecomesTopWindow()
+    }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun appWindowBecomesVisible() {
+        super.appWindowBecomesVisible()
+    }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun appWindowIsTopWindowAtEnd() {
+        super.appWindowIsTopWindowAtEnd()
+    }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun appLayerBecomesVisible() {
+        super.appLayerBecomesVisible()
+    }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun appLayerReplacesLauncher() {
+        super.appLayerReplacesLauncher()
+    }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun cujCompleted() {
+        super.cujCompleted()
+    }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun entireScreenCovered() {
+        super.entireScreenCovered()
+    }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun navBarLayerIsVisibleAtStartAndEnd() {
+        super.navBarLayerIsVisibleAtStartAndEnd()
+    }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun navBarLayerPositionAtStartAndEnd() {
+        super.navBarLayerPositionAtStartAndEnd()
+    }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun navBarWindowIsAlwaysVisible() {
+        super.navBarWindowIsAlwaysVisible()
+    }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun navBarWindowIsVisibleAtStartAndEnd() {
+        super.navBarWindowIsVisibleAtStartAndEnd()
+    }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun statusBarLayerIsVisibleAtStartAndEnd() {
+        super.statusBarLayerIsVisibleAtStartAndEnd()
+    }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun statusBarLayerPositionAtStartAndEnd() {
+        super.statusBarLayerPositionAtStartAndEnd()
+    }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun statusBarWindowIsAlwaysVisible() {
+        super.statusBarWindowIsAlwaysVisible()
+    }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun taskBarLayerIsVisibleAtStartAndEnd() {
+        super.taskBarLayerIsVisibleAtStartAndEnd()
+    }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun taskBarWindowIsAlwaysVisible() {
+        super.taskBarWindowIsAlwaysVisible()
+    }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
+        super.visibleLayersShownMoreThanOneConsecutiveEntry()
+    }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun visibleWindowsShownMoreThanOneConsecutiveEntry() {
+        super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+    }
+
+    companion object {
+        /**
+         * Creates the test configurations.
+         *
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * navigation modes.
+         */
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdAfterCameraTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdAfterCameraTest.kt
new file mode 100644
index 0000000..f575fcc
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdAfterCameraTest.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.launch
+
+import android.tools.device.apphelpers.CameraAppHelper
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test launching an app after cold opening camera
+ *
+ * To run this test: `atest FlickerTests:OpenAppAfterCameraTest`
+ *
+ * Notes: Some default assertions are inherited [OpenAppTransition]
+ */
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class OpenAppFromIntentColdAfterCameraTest(flicker: LegacyFlickerTest) :
+    OpenAppFromLauncherTransition(flicker) {
+    private val cameraApp = CameraAppHelper(instrumentation)
+    /** {@inheritDoc} */
+    override val transition: FlickerBuilder.() -> Unit
+        get() = {
+            super.transition(this)
+            setup {
+                tapl.setExpectedRotationCheckEnabled(false)
+                // 1. Open camera - cold -> close it first
+                cameraApp.exit(wmHelper)
+                cameraApp.launchViaIntent(wmHelper)
+                // Can't use TAPL due to Recents not showing in 3 Button Nav in full screen mode
+                device.pressHome()
+                tapl.getWorkspace()
+            }
+            teardown { testApp.exit(wmHelper) }
+            transitions { testApp.launchViaIntent(wmHelper) }
+        }
+
+    companion object {
+        /**
+         * Creates the test configurations.
+         *
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * navigation modes.
+         */
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdTest.kt
new file mode 100644
index 0000000..93d0520
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdTest.kt
@@ -0,0 +1,91 @@
+/*
+ * 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.
+ */
+
+package com.android.server.wm.flicker.launch
+
+import android.platform.test.annotations.Postsubmit
+import android.platform.test.annotations.Presubmit
+import android.tools.common.flicker.annotation.FlickerServiceCompatible
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+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.flicker.rules.RemoveAllTasksButHomeRule.Companion.removeAllTasksButHome
+import com.android.server.wm.flicker.helpers.setRotation
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test cold launching an app from launcher
+ *
+ * To run this test: `atest FlickerTests:OpenAppColdTest`
+ *
+ * Actions:
+ * ```
+ *     Make sure no apps are running on the device
+ *     Launch an app [testApp] and wait animation to complete
+ * ```
+ *
+ * Notes:
+ * ```
+ *     1. Some default assertions (e.g., nav bar, status bar and screen covered)
+ *        are inherited [OpenAppTransition]
+ *     2. Part of the test setup occurs automatically via
+ *        [com.android.server.wm.flicker.TransitionRunnerWithRules],
+ *        including configuring navigation mode, initial orientation and ensuring no
+ *        apps are running before setup
+ * ```
+ */
+@FlickerServiceCompatible
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class OpenAppFromIntentColdTest(flicker: LegacyFlickerTest) :
+    OpenAppFromLauncherTransition(flicker) {
+    /** {@inheritDoc} */
+    override val transition: FlickerBuilder.() -> Unit
+        get() = {
+            super.transition(this)
+            setup {
+                removeAllTasksButHome()
+                this.setRotation(flicker.scenario.startRotation)
+            }
+            teardown { testApp.exit(wmHelper) }
+            transitions { testApp.launchViaIntent(wmHelper) }
+        }
+
+    /** {@inheritDoc} */
+    @Presubmit @Test override fun appLayerReplacesLauncher() = super.appLayerReplacesLauncher()
+
+    @Postsubmit
+    @Test
+    override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
+
+    companion object {
+        /**
+         * Creates the test configurations.
+         *
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * navigation modes.
+         */
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentWarmTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentWarmTest.kt
new file mode 100644
index 0000000..0197e66
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentWarmTest.kt
@@ -0,0 +1,102 @@
+/*
+ * 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.
+ */
+
+package com.android.server.wm.flicker.launch
+
+import android.platform.test.annotations.FlakyTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.flicker.annotation.FlickerServiceCompatible
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import com.android.server.wm.flicker.helpers.setRotation
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test warm launching an app from launcher
+ *
+ * To run this test: `atest FlickerTests:OpenAppWarmTest`
+ *
+ * Actions:
+ * ```
+ *     Launch [testApp]
+ *     Press home
+ *     Relaunch an app [testApp] and wait animation to complete (only this action is traced)
+ * ```
+ *
+ * Notes:
+ * ```
+ *     1. Some default assertions (e.g., nav bar, status bar and screen covered)
+ *        are inherited [OpenAppTransition]
+ *     2. Part of the test setup occurs automatically via
+ *        [com.android.server.wm.flicker.TransitionRunnerWithRules],
+ *        including configuring navigation mode, initial orientation and ensuring no
+ *        apps are running before setup
+ * ```
+ */
+@FlickerServiceCompatible
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class OpenAppFromIntentWarmTest(flicker: LegacyFlickerTest) :
+    OpenAppFromLauncherTransition(flicker) {
+    /** Defines the transition used to run the test */
+    override val transition: FlickerBuilder.() -> Unit
+        get() = {
+            super.transition(this)
+            setup {
+                tapl.setExpectedRotationCheckEnabled(false)
+                testApp.launchViaIntent(wmHelper)
+                tapl.goHome()
+                wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
+                this.setRotation(flicker.scenario.startRotation)
+            }
+            teardown { testApp.exit(wmHelper) }
+            transitions { testApp.launchViaIntent(wmHelper) }
+        }
+
+    /** {@inheritDoc} */
+    @FlakyTest(bugId = 206753786)
+    @Test
+    override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
+
+    /** {@inheritDoc} */
+    @Presubmit
+    @Test
+    override fun appLayerBecomesVisible() = super.appLayerBecomesVisible_warmStart()
+
+    /** {@inheritDoc} */
+    @Presubmit
+    @Test
+    override fun appWindowBecomesVisible() = super.appWindowBecomesVisible_warmStart()
+
+    companion object {
+        /**
+         * Creates the test configurations.
+         *
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * navigation modes.
+         */
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLauncherTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLauncherTransition.kt
index 3d5a8e3..62fb570 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLauncherTransition.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLauncherTransition.kt
@@ -18,12 +18,13 @@
 
 import android.platform.test.annotations.Presubmit
 import android.tools.common.traces.component.ComponentNameMatcher
-import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTest
 import com.android.server.wm.flicker.replacesLayer
 import org.junit.Test
 
 /** Base class for app launch tests */
-abstract class OpenAppFromLauncherTransition(flicker: FlickerTest) : OpenAppTransition(flicker) {
+abstract class OpenAppFromLauncherTransition(flicker: LegacyFlickerTest) :
+    OpenAppTransition(flicker) {
 
     /** Checks that the focus changes from the [ComponentNameMatcher.LAUNCHER] to [testApp] */
     @Presubmit
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationCold.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationCold.kt
deleted file mode 100644
index b21777b..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationCold.kt
+++ /dev/null
@@ -1,135 +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.server.wm.flicker.launch
-
-import android.platform.test.annotations.Postsubmit
-import android.platform.test.rule.SettingOverrideRule
-import android.provider.Settings
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
-import androidx.test.filters.RequiresDevice
-import org.junit.ClassRule
-import org.junit.FixMethodOrder
-import org.junit.Ignore
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test cold launching an app from a notification from the lock screen.
- *
- * This test assumes the device doesn't have AOD enabled
- *
- * To run this test: `atest FlickerTests:OpenAppFromLockNotificationCold`
- */
-@RequiresDevice
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Postsubmit
-open class OpenAppFromLockNotificationCold(flicker: FlickerTest) :
-    OpenAppFromNotificationCold(flicker) {
-
-    override val openingNotificationsFromLockScreen = true
-
-    override val transition: FlickerBuilder.() -> Unit
-        get() = {
-            // Needs to run at start of transition,
-            // so before the transition defined in super.transition
-            transitions { device.wakeUp() }
-
-            super.transition(this)
-
-            // Needs to run at the end of the setup, so after the setup defined in super.transition
-            setup {
-                device.sleep()
-                wmHelper.StateSyncBuilder().withoutTopVisibleAppWindows().waitForAndVerify()
-            }
-        }
-
-    /** {@inheritDoc} */
-    @Test @Ignore("Display is off at the start") override fun navBarLayerPositionAtStartAndEnd() {}
-
-    /** {@inheritDoc} */
-    @Test
-    @Ignore("Display is off at the start")
-    override fun statusBarLayerPositionAtStartAndEnd() {}
-
-    /** {@inheritDoc} */
-    @Test
-    @Ignore("Display is off at the start")
-    override fun taskBarLayerIsVisibleAtStartAndEnd() {}
-
-    /** {@inheritDoc} */
-    @Test @Ignore("Display is off at the start") override fun taskBarWindowIsAlwaysVisible() {}
-
-    /** {@inheritDoc} */
-    @Test
-    @Ignore("Display is off at the start")
-    override fun statusBarLayerIsVisibleAtStartAndEnd() =
-        super.statusBarLayerIsVisibleAtStartAndEnd()
-
-    /** {@inheritDoc} */
-    @Test
-    @Ignore("Not applicable to this CUJ. Display starts locked and app is full screen at the end")
-    override fun navBarWindowIsVisibleAtStartAndEnd() = super.navBarWindowIsVisibleAtStartAndEnd()
-
-    /** {@inheritDoc} */
-    @Test
-    @Ignore("Not applicable to this CUJ. Display starts locked and app is full screen at the end")
-    override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd()
-
-    /** {@inheritDoc} */
-    @Test
-    @Ignore("Not applicable to this CUJ. Display starts locked and app is full screen at the end")
-    override fun navBarWindowIsAlwaysVisible() {}
-
-    /** {@inheritDoc} */
-    @Postsubmit
-    @Test
-    override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
-        super.visibleLayersShownMoreThanOneConsecutiveEntry()
-
-    companion object {
-        /**
-         * Creates the test configurations.
-         *
-         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
-         * navigation modes.
-         */
-        @Parameterized.Parameters(name = "{0}")
-        @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests()
-        }
-
-        /**
-         * Ensures that posted notifications will be visible on the lockscreen and not suppressed
-         * due to being marked as seen.
-         */
-        @ClassRule
-        @JvmField
-        val disableUnseenNotifFilterRule =
-            SettingOverrideRule(
-                Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
-                /* value = */ "0",
-            )
-    }
-}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWarm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWarm.kt
deleted file mode 100644
index ec92ca6..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWarm.kt
+++ /dev/null
@@ -1,166 +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.server.wm.flicker.launch
-
-import android.platform.test.annotations.FlakyTest
-import android.platform.test.annotations.Presubmit
-import android.platform.test.rule.SettingOverrideRule
-import android.provider.Settings
-import android.tools.common.traces.component.ComponentNameMatcher
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.statusBarLayerPositionAtEnd
-import org.junit.ClassRule
-import org.junit.FixMethodOrder
-import org.junit.Ignore
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test warm launching an app from a notification from the lock screen.
- *
- * This test assumes the device doesn't have AOD enabled
- *
- * To run this test: `atest FlickerTests:OpenAppFromLockNotificationWarm`
- */
-@RequiresDevice
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class OpenAppFromLockNotificationWarm(flicker: FlickerTest) : OpenAppFromNotificationWarm(flicker) {
-
-    override val openingNotificationsFromLockScreen = true
-
-    override val transition: FlickerBuilder.() -> Unit
-        get() = {
-            // Needs to run at start of transition,
-            // so before the transition defined in super.transition
-            transitions { device.wakeUp() }
-
-            super.transition(this)
-
-            // Needs to run at the end of the setup, so after the setup defined in super.transition
-            setup {
-                device.sleep()
-                wmHelper.StateSyncBuilder().withoutTopVisibleAppWindows().waitForAndVerify()
-            }
-        }
-
-    /**
-     * Checks that we start of with no top windows and then [testApp] becomes the first and only top
-     * window of the transition, with snapshot or splash screen windows optionally showing first.
-     */
-    @Test
-    @Presubmit
-    fun appWindowBecomesFirstAndOnlyTopWindow() {
-        flicker.assertWm {
-            this.hasNoVisibleAppWindow()
-                .then()
-                .isAppWindowOnTop(ComponentNameMatcher.SNAPSHOT, isOptional = true)
-                .then()
-                .isAppWindowOnTop(ComponentNameMatcher.SPLASH_SCREEN, isOptional = true)
-                .then()
-                .isAppWindowOnTop(testApp)
-        }
-    }
-
-    /** Checks that the screen is locked at the start of the transition */
-    @Test
-    @Presubmit
-    fun screenLockedStart() {
-        flicker.assertWmStart { isKeyguardShowing() }
-    }
-
-    /** {@inheritDoc} */
-    @Test
-    @Ignore("Not applicable to this CUJ. Display starts locked and app is full screen at the end")
-    override fun navBarLayerPositionAtStartAndEnd() {}
-
-    /** {@inheritDoc} */
-    @Test
-    @Ignore("Not applicable to this CUJ. Display starts locked and app is full screen at the end")
-    override fun taskBarWindowIsAlwaysVisible() {}
-
-    /** {@inheritDoc} */
-    @Test
-    @Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end")
-    override fun statusBarLayerPositionAtStartAndEnd() {}
-
-    /** {@inheritDoc} */
-    @Test
-    @Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end")
-    override fun statusBarLayerIsVisibleAtStartAndEnd() =
-        super.statusBarLayerIsVisibleAtStartAndEnd()
-
-    /**
-     * Checks the position of the [ComponentNameMatcher.STATUS_BAR] at the start and end of the
-     * transition
-     */
-    @Presubmit @Test fun statusBarLayerPositionAtEnd() = flicker.statusBarLayerPositionAtEnd()
-
-    /** {@inheritDoc} */
-    @Test
-    @Ignore("Not applicable to this CUJ. Display starts locked and app is full screen at the end")
-    override fun navBarWindowIsVisibleAtStartAndEnd() = super.navBarWindowIsVisibleAtStartAndEnd()
-
-    /** {@inheritDoc} */
-    @Test
-    @Ignore("Not applicable to this CUJ. Display starts locked and app is full screen at the end")
-    override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd()
-
-    /** {@inheritDoc} */
-    @Test
-    @Ignore("Not applicable to this CUJ. Display starts locked and app is full screen at the end")
-    override fun navBarWindowIsAlwaysVisible() {}
-
-    /** {@inheritDoc} */
-    @FlakyTest(bugId = 246284526)
-    @Test
-    override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
-        super.visibleLayersShownMoreThanOneConsecutiveEntry()
-
-    companion object {
-        /**
-         * Creates the test configurations.
-         *
-         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
-         * navigation modes.
-         */
-        @Parameterized.Parameters(name = "{0}")
-        @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests()
-        }
-
-        /**
-         * Ensures that posted notifications will be visible on the lockscreen and not suppressed
-         * due to being marked as seen.
-         */
-        @ClassRule
-        @JvmField
-        val disableUnseenNotifFilterRule =
-            SettingOverrideRule(
-                Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
-                /* value= */ "0",
-            )
-    }
-}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWithLockOverlayApp.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWithLockOverlayApp.kt
deleted file mode 100644
index 009d617..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWithLockOverlayApp.kt
+++ /dev/null
@@ -1,136 +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.server.wm.flicker.launch
-
-import android.platform.test.annotations.FlakyTest
-import android.platform.test.annotations.Postsubmit
-import android.platform.test.annotations.Presubmit
-import android.tools.common.traces.component.ComponentNameMatcher
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
-import android.tools.device.helpers.wakeUpAndGoToHomeScreen
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.helpers.ShowWhenLockedAppHelper
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test cold launching an app from a notification from the lock screen when there is an app overlaid
- * on the lock screen.
- *
- * To run this test: `atest FlickerTests:OpenAppFromLockNotificationWithLockOverlayApp`
- */
-@RequiresDevice
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Postsubmit
-class OpenAppFromLockNotificationWithLockOverlayApp(flicker: FlickerTest) :
-    OpenAppFromLockNotificationCold(flicker) {
-    private val showWhenLockedApp = ShowWhenLockedAppHelper(instrumentation)
-
-    // Although we are technically still locked here, the overlay app means we should open the
-    // notification shade as if we were unlocked.
-    override val openingNotificationsFromLockScreen = false
-
-    override val transition: FlickerBuilder.() -> Unit
-        get() = {
-            super.transition(this)
-
-            setup {
-                device.wakeUpAndGoToHomeScreen()
-
-                // Launch an activity that is shown when the device is locked
-                showWhenLockedApp.launchViaIntent(wmHelper)
-                wmHelper.StateSyncBuilder().withFullScreenApp(showWhenLockedApp).waitForAndVerify()
-
-                device.sleep()
-                wmHelper.StateSyncBuilder().withoutTopVisibleAppWindows().waitForAndVerify()
-            }
-
-            teardown { showWhenLockedApp.exit(wmHelper) }
-        }
-
-    @Test
-    @FlakyTest(bugId = 227143265)
-    fun showWhenLockedAppWindowBecomesVisible() {
-        flicker.assertWm {
-            this.hasNoVisibleAppWindow()
-                .then()
-                .isAppWindowOnTop(ComponentNameMatcher.SNAPSHOT, isOptional = true)
-                .then()
-                .isAppWindowOnTop(showWhenLockedApp)
-        }
-    }
-
-    @Test
-    @FlakyTest(bugId = 227143265)
-    fun showWhenLockedAppLayerBecomesVisible() {
-        flicker.assertLayers {
-            this.isInvisible(showWhenLockedApp)
-                .then()
-                .isVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true)
-                .then()
-                .isVisible(showWhenLockedApp)
-        }
-    }
-
-    /** {@inheritDoc} */
-    @Presubmit @Test override fun appLayerBecomesVisible() = super.appLayerBecomesVisible()
-
-    /** {@inheritDoc} */
-    @FlakyTest(bugId = 227143265)
-    @Test
-    override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
-        super.visibleLayersShownMoreThanOneConsecutiveEntry()
-
-    /** {@inheritDoc} */
-    @FlakyTest(bugId = 209599395)
-    @Test
-    override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd()
-
-    /** {@inheritDoc} */
-    @Presubmit
-    @Test
-    override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
-
-    @Presubmit @Test override fun entireScreenCovered() = super.entireScreenCovered()
-
-    @FlakyTest(bugId = 278227468)
-    @Test
-    override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
-        super.visibleWindowsShownMoreThanOneConsecutiveEntry()
-
-    companion object {
-        /**
-         * Creates the test configurations.
-         *
-         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
-         * navigation modes.
-         */
-        @Parameterized.Parameters(name = "{0}")
-        @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests()
-        }
-    }
-}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockTransition.kt
deleted file mode 100644
index eae9ca1..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockTransition.kt
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.flicker.launch
-
-import android.platform.test.annotations.FlakyTest
-import android.platform.test.annotations.Presubmit
-import android.tools.common.traces.component.ComponentNameMatcher
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import com.android.server.wm.flicker.navBarLayerPositionAtEnd
-import com.android.server.wm.flicker.statusBarLayerPositionAtEnd
-import org.junit.Assume
-import org.junit.Ignore
-import org.junit.Test
-
-/** Base class for app launch tests from lock screen */
-abstract class OpenAppFromLockTransition(flicker: FlickerTest) : OpenAppTransition(flicker) {
-
-    /** Defines the transition used to run the test */
-    override val transition: FlickerBuilder.() -> Unit = {
-        super.transition(this)
-        setup {
-            device.sleep()
-            wmHelper.StateSyncBuilder().withoutTopVisibleAppWindows().waitForAndVerify()
-        }
-        teardown { testApp.exit(wmHelper) }
-        transitions { testApp.launchViaIntent(wmHelper) }
-    }
-
-    /** Check that we go from no focus to focus on the [testApp] */
-    @Presubmit
-    @Test
-    open fun focusChanges() {
-        flicker.assertEventLog { this.focusChanges("", testApp.`package`) }
-    }
-
-    /**
-     * Checks that we start of with no top windows and then [testApp] becomes the first and only top
-     * window of the transition, with snapshot or splash screen windows optionally showing first.
-     */
-    @FlakyTest(bugId = 203538234)
-    @Test
-    open fun appWindowBecomesFirstAndOnlyTopWindow() {
-        flicker.assertWm {
-            this.hasNoVisibleAppWindow()
-                .then()
-                .isAppWindowOnTop(ComponentNameMatcher.SNAPSHOT, isOptional = true)
-                .then()
-                .isAppWindowOnTop(ComponentNameMatcher.SPLASH_SCREEN, isOptional = true)
-                .then()
-                .isAppWindowOnTop(testApp)
-        }
-    }
-
-    /** Checks that the screen is locked at the start of the transition */
-    @Presubmit
-    @Test
-    fun screenLockedStart() {
-        flicker.assertLayersStart { isEmpty() }
-    }
-
-    /** {@inheritDoc} */
-    @FlakyTest(bugId = 203538234)
-    @Test
-    override fun appWindowBecomesVisible() = super.appWindowBecomesVisible()
-
-    /** {@inheritDoc} */
-    @Test
-    @Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end")
-    override fun navBarLayerPositionAtStartAndEnd() {}
-
-    /** {@inheritDoc} */
-    @Test
-    @Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end")
-    override fun statusBarLayerPositionAtStartAndEnd() {}
-
-    /** {@inheritDoc} */
-    @Test
-    @Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end")
-    override fun taskBarLayerIsVisibleAtStartAndEnd() {}
-
-    /** {@inheritDoc} */
-    @Test
-    @Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end")
-    override fun taskBarWindowIsAlwaysVisible() {}
-
-    /** Checks the position of the [ComponentNameMatcher.NAV_BAR] at the end of the transition */
-    @Presubmit
-    @Test
-    open fun navBarLayerPositionAtEnd() {
-        Assume.assumeFalse(flicker.scenario.isTablet)
-        flicker.navBarLayerPositionAtEnd()
-    }
-
-    /** Checks the position of the [ComponentNameMatcher.STATUS_BAR] at the end of the transition */
-    @Presubmit @Test fun statusBarLayerPositionAtEnd() = flicker.statusBarLayerPositionAtEnd()
-
-    /** {@inheritDoc} */
-    @Test
-    @Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end")
-    override fun statusBarLayerIsVisibleAtStartAndEnd() {}
-
-    /**
-     * Checks that the [ComponentNameMatcher.STATUS_BAR] layer is visible at the end of the trace
-     *
-     * It is not possible to check at the start because the screen is off
-     */
-    @Presubmit
-    @Test
-    fun statusBarLayerIsVisibleAtEnd() {
-        flicker.assertLayersEnd { this.isVisible(ComponentNameMatcher.STATUS_BAR) }
-    }
-}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenTransition.kt
new file mode 100644
index 0000000..687bc19
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenTransition.kt
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.flicker.launch
+
+import android.platform.test.annotations.FlakyTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.traces.component.ComponentNameMatcher
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import com.android.server.wm.flicker.navBarLayerPositionAtEnd
+import com.android.server.wm.flicker.statusBarLayerPositionAtEnd
+import org.junit.Assume
+import org.junit.Ignore
+import org.junit.Test
+
+/** Base class for app launch tests from lock screen */
+abstract class OpenAppFromLockscreenTransition(flicker: LegacyFlickerTest) :
+    OpenAppTransition(flicker) {
+
+    /** Defines the transition used to run the test */
+    override val transition: FlickerBuilder.() -> Unit = {
+        super.transition(this)
+        setup {
+            device.sleep()
+            wmHelper.StateSyncBuilder().withoutTopVisibleAppWindows().waitForAndVerify()
+        }
+        teardown { testApp.exit(wmHelper) }
+        transitions { testApp.launchViaIntent(wmHelper) }
+    }
+
+    /** Check that we go from no focus to focus on the [testApp] */
+    @Presubmit
+    @Test
+    open fun focusChanges() {
+        flicker.assertEventLog { this.focusChanges("", testApp.`package`) }
+    }
+
+    /**
+     * Checks that we start of with no top windows and then [testApp] becomes the first and only top
+     * window of the transition, with snapshot or splash screen windows optionally showing first.
+     */
+    @FlakyTest(bugId = 203538234)
+    @Test
+    open fun appWindowBecomesFirstAndOnlyTopWindow() {
+        flicker.assertWm {
+            this.hasNoVisibleAppWindow()
+                .then()
+                .isAppWindowOnTop(ComponentNameMatcher.SNAPSHOT, isOptional = true)
+                .then()
+                .isAppWindowOnTop(ComponentNameMatcher.SPLASH_SCREEN, isOptional = true)
+                .then()
+                .isAppWindowOnTop(testApp)
+        }
+    }
+
+    /** Checks that the screen is locked at the start of the transition */
+    @Presubmit
+    @Test
+    fun screenLockedStart() {
+        flicker.assertLayersStart { isEmpty() }
+    }
+
+    /** {@inheritDoc} */
+    @FlakyTest(bugId = 203538234)
+    @Test
+    override fun appWindowBecomesVisible() = super.appWindowBecomesVisible()
+
+    /** {@inheritDoc} */
+    @Test
+    @Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end")
+    override fun navBarLayerPositionAtStartAndEnd() {}
+
+    /** {@inheritDoc} */
+    @Test
+    @Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end")
+    override fun statusBarLayerPositionAtStartAndEnd() {}
+
+    /** {@inheritDoc} */
+    @Test
+    @Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end")
+    override fun taskBarLayerIsVisibleAtStartAndEnd() {}
+
+    /** {@inheritDoc} */
+    @Test
+    @Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end")
+    override fun taskBarWindowIsAlwaysVisible() {}
+
+    /** Checks the position of the [ComponentNameMatcher.NAV_BAR] at the end of the transition */
+    @Presubmit
+    @Test
+    open fun navBarLayerPositionAtEnd() {
+        Assume.assumeFalse(flicker.scenario.isTablet)
+        flicker.navBarLayerPositionAtEnd()
+    }
+
+    /** Checks the position of the [ComponentNameMatcher.STATUS_BAR] at the end of the transition */
+    @Presubmit @Test fun statusBarLayerPositionAtEnd() = flicker.statusBarLayerPositionAtEnd()
+
+    /** {@inheritDoc} */
+    @Test
+    @Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end")
+    override fun statusBarLayerIsVisibleAtStartAndEnd() {}
+
+    /**
+     * Checks that the [ComponentNameMatcher.STATUS_BAR] layer is visible at the end of the trace
+     *
+     * It is not possible to check at the start because the screen is off
+     */
+    @Presubmit
+    @Test
+    fun statusBarLayerIsVisibleAtEnd() {
+        flicker.assertLayersEnd { this.isVisible(ComponentNameMatcher.STATUS_BAR) }
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenViaIntentTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenViaIntentTest.kt
new file mode 100644
index 0000000..7a16060
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenViaIntentTest.kt
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.flicker.launch
+
+import android.platform.test.annotations.FlakyTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.common.flicker.annotation.FlickerServiceCompatible
+import android.tools.common.traces.component.ComponentNameMatcher
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
+import org.junit.Assume
+import org.junit.FixMethodOrder
+import org.junit.Ignore
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test launching an app while the device is locked
+ *
+ * This test assumes the device doesn't have AOD enabled
+ *
+ * To run this test: `atest FlickerTests:OpenAppNonResizeableTest`
+ *
+ * Actions:
+ * ```
+ *     Lock the device.
+ *     Launch an app on top of the lock screen [testApp] and wait animation to complete
+ * ```
+ *
+ * Notes:
+ * ```
+ *     1. Some default assertions (e.g., nav bar, status bar and screen covered)
+ *        are inherited [OpenAppTransition]
+ *     2. Part of the test setup occurs automatically via
+ *        [com.android.server.wm.flicker.TransitionRunnerWithRules],
+ *        including configuring navigation mode, initial orientation and ensuring no
+ *        apps are running before setup
+ * ```
+ */
+@RequiresDevice
+@FlickerServiceCompatible
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+open class OpenAppFromLockscreenViaIntentTest(flicker: LegacyFlickerTest) :
+    OpenAppFromLockscreenTransition(flicker) {
+    override val testApp = NonResizeableAppHelper(instrumentation)
+
+    /**
+     * Checks that the [ComponentNameMatcher.NAV_BAR] layer starts invisible, becomes visible during
+     * unlocking animation and remains visible at the end
+     */
+    @Presubmit
+    @Test
+    fun navBarLayerVisibilityChanges() {
+        Assume.assumeFalse(flicker.scenario.isTablet)
+        flicker.assertLayers {
+            this.isInvisible(ComponentNameMatcher.NAV_BAR)
+                .then()
+                .isVisible(ComponentNameMatcher.NAV_BAR)
+        }
+    }
+
+    /** Checks if [testApp] is visible at the end of the transition */
+    @Presubmit
+    @Test
+    fun appWindowBecomesVisibleAtEnd() {
+        flicker.assertWmEnd { this.isAppWindowVisible(testApp) }
+    }
+
+    /**
+     * Checks that the [ComponentNameMatcher.NAV_BAR] starts the transition invisible, then becomes
+     * visible during the unlocking animation and remains visible at the end of the transition
+     */
+    @Presubmit
+    @Test
+    fun navBarWindowsVisibilityChanges() {
+        Assume.assumeFalse(flicker.scenario.isTablet)
+        flicker.assertWm {
+            this.isNonAppWindowInvisible(ComponentNameMatcher.NAV_BAR)
+                .then()
+                .isAboveAppWindowVisible(ComponentNameMatcher.NAV_BAR)
+        }
+    }
+
+    /**
+     * Checks that the [ComponentNameMatcher.TASK_BAR] starts the transition invisible, then becomes
+     * visible during the unlocking animation and remains visible at the end of the transition
+     */
+    @Presubmit
+    @Test
+    fun taskBarLayerIsVisibleAtEnd() {
+        Assume.assumeTrue(flicker.scenario.isTablet)
+        flicker.assertLayersEnd { this.isVisible(ComponentNameMatcher.TASK_BAR) }
+    }
+
+    /**
+     * Checks that the [ComponentNameMatcher.STATUS_BAR] layer is visible at the end of the trace
+     *
+     * It is not possible to check at the start because the screen is off
+     */
+    @Presubmit
+    @Test
+    override fun statusBarLayerIsVisibleAtStartAndEnd() {
+        flicker.assertLayersEnd { this.isVisible(ComponentNameMatcher.STATUS_BAR) }
+    }
+
+    /** {@inheritDoc} */
+    @Test
+    @Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end")
+    override fun taskBarLayerIsVisibleAtStartAndEnd() {}
+
+    /** {@inheritDoc} */
+    @Test
+    @Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end")
+    override fun navBarLayerIsVisibleAtStartAndEnd() {}
+
+    /** {@inheritDoc} */
+    @Test
+    @Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end")
+    override fun taskBarWindowIsAlwaysVisible() {}
+
+    /** {@inheritDoc} */
+    @Test
+    @Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end")
+    override fun navBarWindowIsAlwaysVisible() {}
+
+    /** {@inheritDoc} */
+    @Test
+    @Ignore("Not applicable to this CUJ. Display starts locked and app is full screen at the end")
+    override fun navBarWindowIsVisibleAtStartAndEnd() = super.navBarWindowIsVisibleAtStartAndEnd()
+
+    /** {@inheritDoc} */
+    @Test
+    @Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end")
+    override fun statusBarWindowIsAlwaysVisible() {}
+
+    /** {@inheritDoc} */
+    @Presubmit
+    @Test
+    override fun appWindowBecomesFirstAndOnlyTopWindow() =
+        super.appWindowBecomesFirstAndOnlyTopWindow()
+
+    /** {@inheritDoc} */
+    @Presubmit @Test override fun appWindowBecomesVisible() = super.appWindowBecomesVisible()
+
+    /** Checks the [ComponentNameMatcher.NAV_BAR] is visible at the end of the transition */
+    @Presubmit
+    @Test
+    fun navBarLayerIsVisibleAtEnd() {
+        Assume.assumeFalse(flicker.scenario.isTablet)
+        flicker.assertLayersEnd { this.isVisible(ComponentNameMatcher.NAV_BAR) }
+    }
+
+    /** {@inheritDoc} */
+    @FlakyTest
+    @Test
+    override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+        super.visibleLayersShownMoreThanOneConsecutiveEntry()
+
+    /** {@inheritDoc} */
+    @Presubmit
+    @Test
+    override fun appLayerBecomesVisible() {
+        Assume.assumeFalse(flicker.scenario.isTablet)
+        super.appLayerBecomesVisible()
+    }
+
+    /** {@inheritDoc} */
+    @FlakyTest(bugId = 227143265)
+    @Test
+    fun appLayerBecomesVisibleTablet() {
+        Assume.assumeTrue(flicker.scenario.isTablet)
+        super.appLayerBecomesVisible()
+    }
+
+    @Presubmit
+    @Test
+    override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
+        super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+
+    @FlakyTest(bugId = 251217585)
+    @Test
+    override fun focusChanges() {
+        super.focusChanges()
+    }
+
+    companion object {
+        /**
+         * Creates the test configurations.
+         *
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * navigation modes.
+         */
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
+                supportedNavigationModes = listOf(NavBar.MODE_GESTURAL),
+                supportedRotations = listOf(Rotation.ROTATION_0)
+            )
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationCold.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationCold.kt
deleted file mode 100644
index 7bcb910..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationCold.kt
+++ /dev/null
@@ -1,113 +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.server.wm.flicker.launch
-
-import android.platform.test.annotations.Postsubmit
-import android.platform.test.annotations.Presubmit
-import android.tools.common.traces.component.ComponentNameMatcher
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.statusBarLayerPositionAtEnd
-import org.junit.FixMethodOrder
-import org.junit.Ignore
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test cold launching an app from a notification.
- *
- * This test assumes the device doesn't have AOD enabled
- *
- * To run this test: `atest FlickerTests:OpenAppFromNotificationCold`
- */
-@RequiresDevice
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Postsubmit
-open class OpenAppFromNotificationCold(flicker: FlickerTest) :
-    OpenAppFromNotificationWarm(flicker) {
-    /** {@inheritDoc} */
-    override val transition: FlickerBuilder.() -> Unit
-        get() = {
-            super.transition(this)
-
-            setup {
-                // Close the app that posted the notification to trigger a cold start next time
-                // it is open - can't just kill it because that would remove the notification.
-                tapl.setExpectedRotationCheckEnabled(false)
-                tapl.goHome()
-                tapl.workspace.switchToOverview()
-                tapl.overview.dismissAllTasks()
-            }
-        }
-
-    @Presubmit @Test override fun appWindowBecomesVisible() = appWindowBecomesVisible_coldStart()
-
-    @Presubmit @Test override fun appLayerBecomesVisible() = appLayerBecomesVisible_coldStart()
-
-    /** {@inheritDoc} */
-    @Test
-    @Ignore("Not applicable to this CUJ. Display starts locked and app is full screen at the end")
-    override fun navBarLayerPositionAtStartAndEnd() {}
-
-    /** {@inheritDoc} */
-    @Test
-    @Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end")
-    override fun statusBarLayerPositionAtStartAndEnd() {}
-
-    /** {@inheritDoc} */
-    @Test
-    @Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end")
-    override fun statusBarLayerIsVisibleAtStartAndEnd() =
-        super.statusBarLayerIsVisibleAtStartAndEnd()
-
-    /**
-     * Checks the position of the [ComponentNameMatcher.STATUS_BAR] at the start and end of the
-     * transition
-     */
-    @Presubmit @Test open fun statusBarLayerPositionAtEnd() = flicker.statusBarLayerPositionAtEnd()
-
-    /** {@inheritDoc} */
-    @Test
-    @Ignore("Not applicable to this CUJ. Display starts locked and app is full screen at the end")
-    override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd()
-
-    /** {@inheritDoc} */
-    @Test
-    @Ignore("Not applicable to this CUJ. Display starts locked and app is full screen at the end")
-    override fun navBarWindowIsAlwaysVisible() {}
-
-    companion object {
-        /**
-         * Creates the test configurations.
-         *
-         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
-         * navigation modes.
-         */
-        @Parameterized.Parameters(name = "{0}")
-        @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests()
-        }
-    }
-}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationColdCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationColdCfArm.kt
deleted file mode 100644
index 8b4a613..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationColdCfArm.kt
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm.flicker.launch
-
-import android.platform.test.annotations.Postsubmit
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
-import org.junit.FixMethodOrder
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Postsubmit
-class OpenAppFromNotificationColdCfArm(flicker: FlickerTest) :
-    OpenAppFromNotificationCold(flicker) {
-    companion object {
-        /**
-         * Creates the test configurations.
-         *
-         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
-         * navigation modes.
-         */
-        @Parameterized.Parameters(name = "{0}")
-        @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests()
-        }
-    }
-}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarm.kt
deleted file mode 100644
index 425e674..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarm.kt
+++ /dev/null
@@ -1,207 +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.server.wm.flicker.launch
-
-import android.platform.test.annotations.Postsubmit
-import android.platform.test.annotations.Presubmit
-import android.tools.common.traces.component.ComponentNameMatcher
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
-import android.tools.device.helpers.wakeUpAndGoToHomeScreen
-import android.view.WindowInsets
-import android.view.WindowManager
-import androidx.test.filters.RequiresDevice
-import androidx.test.uiautomator.By
-import androidx.test.uiautomator.Until
-import com.android.server.wm.flicker.helpers.NotificationAppHelper
-import com.android.server.wm.flicker.helpers.setRotation
-import com.android.server.wm.flicker.navBarLayerIsVisibleAtEnd
-import com.android.server.wm.flicker.navBarLayerPositionAtEnd
-import com.android.server.wm.flicker.navBarWindowIsVisibleAtEnd
-import com.android.server.wm.flicker.taskBarLayerIsVisibleAtEnd
-import com.android.server.wm.flicker.taskBarWindowIsVisibleAtEnd
-import org.junit.Assume
-import org.junit.FixMethodOrder
-import org.junit.Ignore
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test cold launching an app from a notification.
- *
- * This test assumes the device doesn't have AOD enabled
- *
- * To run this test: `atest FlickerTests:OpenAppFromNotificationWarm`
- */
-@RequiresDevice
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class OpenAppFromNotificationWarm(flicker: FlickerTest) : OpenAppTransition(flicker) {
-    override val testApp: NotificationAppHelper = NotificationAppHelper(instrumentation)
-
-    open val openingNotificationsFromLockScreen = false
-
-    /** {@inheritDoc} */
-    override val transition: FlickerBuilder.() -> Unit
-        get() = {
-            setup {
-                device.wakeUpAndGoToHomeScreen()
-                this.setRotation(flicker.scenario.startRotation)
-                testApp.launchViaIntent(wmHelper)
-                wmHelper.StateSyncBuilder().withFullScreenApp(testApp).waitForAndVerify()
-                testApp.postNotification(wmHelper)
-                device.pressHome()
-                wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
-            }
-
-            transitions {
-                var startY = 10
-                var endY = 3 * device.displayHeight / 4
-                var steps = 25
-                if (openingNotificationsFromLockScreen) {
-                    val wm: WindowManager =
-                        instrumentation.context.getSystemService(WindowManager::class.java)
-                            ?: error("Unable to connect to WindowManager service")
-                    val metricInsets = wm.currentWindowMetrics.windowInsets
-                    val insets =
-                        metricInsets.getInsetsIgnoringVisibility(
-                            WindowInsets.Type.statusBars() or WindowInsets.Type.displayCutout()
-                        )
-
-                    startY = insets.top + 100
-                    endY = device.displayHeight / 2
-                    steps = 4
-                }
-
-                // Swipe down to show the notification shade
-                val x = device.displayWidth / 2
-                device.swipe(x, startY, x, endY, steps)
-                device.waitForIdle(2000)
-                instrumentation.uiAutomation.syncInputTransactions()
-
-                // Launch the activity by clicking the notification
-                val notification =
-                    device.wait(Until.findObject(By.text("Flicker Test Notification")), 2000L)
-                notification?.click() ?: error("Notification not found")
-                instrumentation.uiAutomation.syncInputTransactions()
-
-                // Wait for the app to launch
-                wmHelper.StateSyncBuilder().withFullScreenApp(testApp).waitForAndVerify()
-            }
-
-            teardown { testApp.exit(wmHelper) }
-        }
-
-    @Presubmit @Test override fun appWindowBecomesVisible() = appWindowBecomesVisible_warmStart()
-
-    @Presubmit @Test override fun appLayerBecomesVisible() = appLayerBecomesVisible_warmStart()
-
-    @Presubmit
-    @Test
-    open fun notificationAppWindowVisibleAtEnd() {
-        flicker.assertWmEnd { this.isAppWindowVisible(testApp) }
-    }
-
-    @Presubmit
-    @Test
-    open fun notificationAppWindowOnTopAtEnd() {
-        flicker.assertWmEnd { this.isAppWindowOnTop(testApp) }
-    }
-
-    @Presubmit
-    @Test
-    open fun notificationAppLayerVisibleAtEnd() {
-        flicker.assertLayersEnd { this.isVisible(testApp) }
-    }
-
-    /**
-     * Checks that the [ComponentNameMatcher.TASK_BAR] window is visible at the end of the
-     * transition
-     *
-     * Note: Large screen only
-     */
-    @Presubmit
-    @Test
-    open fun taskBarWindowIsVisibleAtEnd() {
-        Assume.assumeTrue(flicker.scenario.isTablet)
-        flicker.taskBarWindowIsVisibleAtEnd()
-    }
-
-    /**
-     * Checks that the [ComponentNameMatcher.TASK_BAR] layer is visible at the end of the transition
-     *
-     * Note: Large screen only
-     */
-    @Presubmit
-    @Test
-    open fun taskBarLayerIsVisibleAtEnd() {
-        Assume.assumeTrue(flicker.scenario.isTablet)
-        flicker.taskBarLayerIsVisibleAtEnd()
-    }
-
-    /** Checks the position of the [ComponentNameMatcher.NAV_BAR] at the end of the transition */
-    @Presubmit
-    @Test
-    open fun navBarLayerPositionAtEnd() {
-        Assume.assumeFalse(flicker.scenario.isTablet)
-        flicker.navBarLayerPositionAtEnd()
-    }
-
-    /** {@inheritDoc} */
-    @Presubmit
-    @Test
-    open fun navBarLayerIsVisibleAtEnd() {
-        Assume.assumeFalse(flicker.scenario.isTablet)
-        flicker.navBarLayerIsVisibleAtEnd()
-    }
-
-    @Presubmit
-    @Test
-    open fun navBarWindowIsVisibleAtEnd() {
-        Assume.assumeFalse(flicker.scenario.isTablet)
-        flicker.navBarWindowIsVisibleAtEnd()
-    }
-
-    /** {@inheritDoc} */
-    @Test
-    @Ignore("Display is off at the start")
-    override fun taskBarLayerIsVisibleAtStartAndEnd() {}
-
-    /** {@inheritDoc} */
-    @Test
-    @Postsubmit
-    override fun taskBarWindowIsAlwaysVisible() = super.taskBarWindowIsAlwaysVisible()
-
-    companion object {
-        /**
-         * Creates the test configurations.
-         *
-         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
-         * navigation modes.
-         */
-        @Parameterized.Parameters(name = "{0}")
-        @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests()
-        }
-    }
-}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarmCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarmCfArm.kt
deleted file mode 100644
index 43d28fa..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarmCfArm.kt
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm.flicker.launch
-
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
-import org.junit.FixMethodOrder
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class OpenAppFromNotificationWarmCfArm(flicker: FlickerTest) :
-    OpenAppFromNotificationWarm(flicker) {
-    companion object {
-        /**
-         * Creates the test configurations.
-         *
-         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
-         * navigation modes.
-         */
-        @Parameterized.Parameters(name = "{0}")
-        @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests()
-        }
-    }
-}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
index 8e1b059..f1cd69a 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
@@ -19,12 +19,11 @@
 import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Presubmit
 import android.tools.common.Rotation
-import android.tools.device.flicker.annotation.FlickerServiceCompatible
+import android.tools.common.flicker.annotation.FlickerServiceCompatible
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
-import androidx.test.filters.RequiresDevice
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import com.android.server.wm.flicker.helpers.setRotation
 import org.junit.FixMethodOrder
 import org.junit.Test
@@ -55,12 +54,11 @@
  *        apps are running before setup
  * ```
  */
-@RequiresDevice
 @FlickerServiceCompatible
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class OpenAppFromOverviewTest(flicker: FlickerTest) : OpenAppFromLauncherTransition(flicker) {
+class OpenAppFromOverviewTest(flicker: LegacyFlickerTest) : OpenAppFromLauncherTransition(flicker) {
 
     /** Defines the transition used to run the test */
     override val transition: FlickerBuilder.() -> Unit
@@ -106,13 +104,11 @@
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
          * navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests()
-        }
+        fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTestCfArm.kt
deleted file mode 100644
index ff24190..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTestCfArm.kt
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm.flicker.launch
-
-import android.tools.device.flicker.annotation.FlickerServiceCompatible
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
-import org.junit.FixMethodOrder
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/** Some assertions will fail because of b/264415996 */
-@FlickerServiceCompatible
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class OpenAppFromOverviewTestCfArm(flicker: FlickerTest) : OpenAppFromOverviewTest(flicker) {
-    companion object {
-        /**
-         * Creates the test configurations.
-         *
-         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
-         * navigation modes.
-         */
-        @Parameterized.Parameters(name = "{0}")
-        @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests()
-        }
-    }
-}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt
deleted file mode 100644
index 1383ae3..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt
+++ /dev/null
@@ -1,224 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.flicker.launch
-
-import android.platform.test.annotations.FlakyTest
-import android.platform.test.annotations.Presubmit
-import android.tools.common.NavBar
-import android.tools.common.Rotation
-import android.tools.common.traces.component.ComponentNameMatcher
-import android.tools.device.flicker.annotation.FlickerServiceCompatible
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
-import org.junit.Assume
-import org.junit.FixMethodOrder
-import org.junit.Ignore
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test launching an app while the device is locked
- *
- * This test assumes the device doesn't have AOD enabled
- *
- * To run this test: `atest FlickerTests:OpenAppNonResizeableTest`
- *
- * Actions:
- * ```
- *     Lock the device.
- *     Launch an app on top of the lock screen [testApp] and wait animation to complete
- * ```
- *
- * Notes:
- * ```
- *     1. Some default assertions (e.g., nav bar, status bar and screen covered)
- *        are inherited [OpenAppTransition]
- *     2. Part of the test setup occurs automatically via
- *        [com.android.server.wm.flicker.TransitionRunnerWithRules],
- *        including configuring navigation mode, initial orientation and ensuring no
- *        apps are running before setup
- * ```
- */
-@RequiresDevice
-@FlickerServiceCompatible
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class OpenAppNonResizeableTest(flicker: FlickerTest) : OpenAppFromLockTransition(flicker) {
-    override val testApp = NonResizeableAppHelper(instrumentation)
-
-    /**
-     * Checks that the [ComponentNameMatcher.NAV_BAR] layer starts invisible, becomes visible during
-     * unlocking animation and remains visible at the end
-     */
-    @Presubmit
-    @Test
-    fun navBarLayerVisibilityChanges() {
-        Assume.assumeFalse(flicker.scenario.isTablet)
-        flicker.assertLayers {
-            this.isInvisible(ComponentNameMatcher.NAV_BAR)
-                .then()
-                .isVisible(ComponentNameMatcher.NAV_BAR)
-        }
-    }
-
-    /** Checks if [testApp] is visible at the end of the transition */
-    @Presubmit
-    @Test
-    fun appWindowBecomesVisibleAtEnd() {
-        flicker.assertWmEnd { this.isAppWindowVisible(testApp) }
-    }
-
-    /**
-     * Checks that the [ComponentNameMatcher.NAV_BAR] starts the transition invisible, then becomes
-     * visible during the unlocking animation and remains visible at the end of the transition
-     */
-    @Presubmit
-    @Test
-    fun navBarWindowsVisibilityChanges() {
-        Assume.assumeFalse(flicker.scenario.isTablet)
-        flicker.assertWm {
-            this.isNonAppWindowInvisible(ComponentNameMatcher.NAV_BAR)
-                .then()
-                .isAboveAppWindowVisible(ComponentNameMatcher.NAV_BAR)
-        }
-    }
-
-    /**
-     * Checks that the [ComponentNameMatcher.TASK_BAR] starts the transition invisible, then becomes
-     * visible during the unlocking animation and remains visible at the end of the transition
-     */
-    @Presubmit
-    @Test
-    fun taskBarLayerIsVisibleAtEnd() {
-        Assume.assumeTrue(flicker.scenario.isTablet)
-        flicker.assertLayersEnd { this.isVisible(ComponentNameMatcher.TASK_BAR) }
-    }
-
-    /**
-     * Checks that the [ComponentNameMatcher.STATUS_BAR] layer is visible at the end of the trace
-     *
-     * It is not possible to check at the start because the screen is off
-     */
-    @Presubmit
-    @Test
-    override fun statusBarLayerIsVisibleAtStartAndEnd() {
-        flicker.assertLayersEnd { this.isVisible(ComponentNameMatcher.STATUS_BAR) }
-    }
-
-    /** {@inheritDoc} */
-    @Test
-    @Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end")
-    override fun taskBarLayerIsVisibleAtStartAndEnd() {}
-
-    /** {@inheritDoc} */
-    @Test
-    @Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end")
-    override fun navBarLayerIsVisibleAtStartAndEnd() {}
-
-    /** {@inheritDoc} */
-    @Test
-    @Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end")
-    override fun taskBarWindowIsAlwaysVisible() {}
-
-    /** {@inheritDoc} */
-    @Test
-    @Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end")
-    override fun navBarWindowIsAlwaysVisible() {}
-
-    /** {@inheritDoc} */
-    @Test
-    @Ignore("Not applicable to this CUJ. Display starts locked and app is full screen at the end")
-    override fun navBarWindowIsVisibleAtStartAndEnd() = super.navBarWindowIsVisibleAtStartAndEnd()
-
-    /** {@inheritDoc} */
-    @Test
-    @Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end")
-    override fun statusBarWindowIsAlwaysVisible() {}
-
-    /** {@inheritDoc} */
-    @Presubmit
-    @Test
-    override fun appWindowBecomesFirstAndOnlyTopWindow() =
-        super.appWindowBecomesFirstAndOnlyTopWindow()
-
-    /** {@inheritDoc} */
-    @Presubmit @Test override fun appWindowBecomesVisible() = super.appWindowBecomesVisible()
-
-    /** Checks the [ComponentNameMatcher.NAV_BAR] is visible at the end of the transition */
-    @Presubmit
-    @Test
-    fun navBarLayerIsVisibleAtEnd() {
-        Assume.assumeFalse(flicker.scenario.isTablet)
-        flicker.assertLayersEnd { this.isVisible(ComponentNameMatcher.NAV_BAR) }
-    }
-
-    /** {@inheritDoc} */
-    @FlakyTest
-    @Test
-    override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
-        super.visibleLayersShownMoreThanOneConsecutiveEntry()
-
-    /** {@inheritDoc} */
-    @Presubmit
-    @Test
-    override fun appLayerBecomesVisible() {
-        Assume.assumeFalse(flicker.scenario.isTablet)
-        super.appLayerBecomesVisible()
-    }
-
-    /** {@inheritDoc} */
-    @FlakyTest(bugId = 227143265)
-    @Test
-    fun appLayerBecomesVisibleTablet() {
-        Assume.assumeTrue(flicker.scenario.isTablet)
-        super.appLayerBecomesVisible()
-    }
-
-    @Presubmit
-    @Test
-    override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
-        super.visibleWindowsShownMoreThanOneConsecutiveEntry()
-
-    @FlakyTest(bugId = 251217585)
-    @Test
-    override fun focusChanges() {
-        super.focusChanges()
-    }
-
-    companion object {
-        /**
-         * Creates the test configurations.
-         *
-         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
-         * navigation modes.
-         */
-        @Parameterized.Parameters(name = "{0}")
-        @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
-                supportedNavigationModes = listOf(NavBar.MODE_GESTURAL),
-                supportedRotations = listOf(Rotation.ROTATION_0)
-            )
-        }
-    }
-}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt
index 87a14c6..bb11be5 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt
@@ -20,7 +20,7 @@
 import android.tools.common.traces.component.ComponentNameMatcher
 import android.tools.device.apphelpers.StandardAppHelper
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.helpers.wakeUpAndGoToHomeScreen
 import com.android.server.wm.flicker.BaseTest
 import com.android.server.wm.flicker.helpers.SimpleAppHelper
@@ -28,7 +28,7 @@
 import org.junit.Test
 
 /** Base class for app launch tests */
-abstract class OpenAppTransition(flicker: FlickerTest) : BaseTest(flicker) {
+abstract class OpenAppTransition(flicker: LegacyFlickerTest) : BaseTest(flicker) {
     protected open val testApp: StandardAppHelper = SimpleAppHelper(instrumentation)
 
     /** {@inheritDoc} */
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt
deleted file mode 100644
index 3385830..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * 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.
- */
-
-package com.android.server.wm.flicker.launch
-
-import android.platform.test.annotations.FlakyTest
-import android.platform.test.annotations.Presubmit
-import android.tools.device.flicker.annotation.FlickerServiceCompatible
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.helpers.setRotation
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test warm launching an app from launcher
- *
- * To run this test: `atest FlickerTests:OpenAppWarmTest`
- *
- * Actions:
- * ```
- *     Launch [testApp]
- *     Press home
- *     Relaunch an app [testApp] and wait animation to complete (only this action is traced)
- * ```
- *
- * Notes:
- * ```
- *     1. Some default assertions (e.g., nav bar, status bar and screen covered)
- *        are inherited [OpenAppTransition]
- *     2. Part of the test setup occurs automatically via
- *        [com.android.server.wm.flicker.TransitionRunnerWithRules],
- *        including configuring navigation mode, initial orientation and ensuring no
- *        apps are running before setup
- * ```
- */
-@RequiresDevice
-@FlickerServiceCompatible
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class OpenAppWarmTest(flicker: FlickerTest) : OpenAppFromLauncherTransition(flicker) {
-    /** Defines the transition used to run the test */
-    override val transition: FlickerBuilder.() -> Unit
-        get() = {
-            super.transition(this)
-            setup {
-                tapl.setExpectedRotationCheckEnabled(false)
-                testApp.launchViaIntent(wmHelper)
-                tapl.goHome()
-                wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
-                this.setRotation(flicker.scenario.startRotation)
-            }
-            teardown { testApp.exit(wmHelper) }
-            transitions { testApp.launchViaIntent(wmHelper) }
-        }
-
-    /** {@inheritDoc} */
-    @FlakyTest(bugId = 206753786)
-    @Test
-    override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
-
-    /** {@inheritDoc} */
-    @Presubmit
-    @Test
-    override fun appLayerBecomesVisible() = super.appLayerBecomesVisible_warmStart()
-
-    /** {@inheritDoc} */
-    @Presubmit
-    @Test
-    override fun appWindowBecomesVisible() = super.appWindowBecomesVisible_warmStart()
-
-    companion object {
-        /**
-         * Creates the test configurations.
-         *
-         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
-         * navigation modes.
-         */
-        @Parameterized.Parameters(name = "{0}")
-        @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests()
-        }
-    }
-}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTestCfArm.kt
deleted file mode 100644
index d8b38b3..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTestCfArm.kt
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm.flicker.launch
-
-import android.tools.device.flicker.annotation.FlickerServiceCompatible
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
-import org.junit.FixMethodOrder
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-@FlickerServiceCompatible
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class OpenAppWarmTestCfArm(flicker: FlickerTest) : OpenAppWarmTest(flicker) {
-    companion object {
-        /**
-         * Creates the test configurations.
-         *
-         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
-         * navigation modes.
-         */
-        @Parameterized.Parameters(name = "{0}")
-        @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests()
-        }
-    }
-}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenCameraFromHomeOnDoubleClickPowerButtonTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenCameraFromHomeOnDoubleClickPowerButtonTest.kt
new file mode 100644
index 0000000..1bdb6e71
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenCameraFromHomeOnDoubleClickPowerButtonTest.kt
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.launch
+
+import android.os.SystemClock
+import android.platform.test.annotations.Postsubmit
+import android.tools.common.flicker.subject.layers.LayersTraceSubject
+import android.tools.common.traces.component.ComponentNameMatcher
+import android.tools.device.apphelpers.CameraAppHelper
+import android.tools.device.apphelpers.StandardAppHelper
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+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.flicker.rules.RemoveAllTasksButHomeRule
+import android.view.KeyEvent
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.helpers.setRotation
+import org.junit.FixMethodOrder
+import org.junit.Ignore
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test cold launching camera from launcher by double pressing power button
+ *
+ * To run this test: `atest FlickerTests:OpenCameraOnDoubleClickPowerButton`
+ *
+ * Actions:
+ * ```
+ *     Make sure no apps are running on the device
+ *     Launch an app [testApp] and wait animation to complete
+ * ```
+ *
+ * Notes:
+ * ```
+ *     1. Some default assertions (e.g., nav bar, status bar and screen covered)
+ *        are inherited [OpenAppTransition]
+ *     2. Part of the test setup occurs automatically via
+ *        [com.android.server.wm.flicker.TransitionRunnerWithRules],
+ *        including configuring navigation mode, initial orientation and ensuring no
+ *        apps are running before setup
+ * ```
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class OpenCameraFromHomeOnDoubleClickPowerButtonTest(flicker: LegacyFlickerTest) :
+    OpenAppFromLauncherTransition(flicker) {
+    private val cameraApp = CameraAppHelper(instrumentation)
+    override val testApp: StandardAppHelper
+        get() = cameraApp
+
+    override val transition: FlickerBuilder.() -> Unit
+        get() = {
+            setup {
+                RemoveAllTasksButHomeRule.removeAllTasksButHome()
+                this.setRotation(flicker.scenario.startRotation)
+            }
+            transitions {
+                device.pressKeyCode(KeyEvent.KEYCODE_POWER)
+                SystemClock.sleep(100)
+                device.pressKeyCode(KeyEvent.KEYCODE_POWER)
+                wmHelper.StateSyncBuilder().withWindowSurfaceAppeared(cameraApp).waitForAndVerify()
+            }
+            teardown { RemoveAllTasksButHomeRule.removeAllTasksButHome() }
+        }
+
+    @Postsubmit @Test override fun appLayerBecomesVisible() = super.appLayerBecomesVisible()
+
+    @Postsubmit @Test override fun appWindowAsTopWindowAtEnd() = super.appWindowAsTopWindowAtEnd()
+
+    @Postsubmit @Test override fun appWindowBecomesTopWindow() = super.appWindowBecomesTopWindow()
+
+    @Postsubmit @Test override fun appWindowBecomesVisible() = super.appWindowBecomesVisible()
+
+    @Postsubmit @Test override fun appLayerReplacesLauncher() = super.appLayerReplacesLauncher()
+
+    @Postsubmit @Test override fun appWindowIsTopWindowAtEnd() = super.appWindowIsTopWindowAtEnd()
+
+    @Postsubmit
+    @Test
+    override fun appWindowReplacesLauncherAsTopWindow() =
+        super.appWindowReplacesLauncherAsTopWindow()
+
+    @Postsubmit @Test override fun focusChanges() = super.focusChanges()
+
+    @Postsubmit @Test override fun entireScreenCovered() = super.entireScreenCovered()
+
+    @Ignore("Not applicable to this CUJ. App is full screen at the end")
+    @Test
+    override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd()
+
+    @Postsubmit
+    @Test
+    override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
+
+    @Postsubmit
+    @Test
+    override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
+
+    @Ignore("Status bar visibility depends on whether the permission dialog is displayed or not")
+    @Test
+    override fun statusBarLayerIsVisibleAtStartAndEnd() =
+        super.statusBarLayerIsVisibleAtStartAndEnd()
+
+    @Ignore("Status bar visibility depends on whether the permission dialog is displayed or not")
+    @Test
+    override fun statusBarLayerPositionAtStartAndEnd() = super.statusBarLayerPositionAtStartAndEnd()
+
+    @Ignore("Status bar visibility depends on whether the permission dialog is displayed or not")
+    @Test
+    override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible()
+
+    @Ignore("Not applicable to this CUJ. App is full screen at the end")
+    @Test
+    override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd()
+
+    @Ignore("Not applicable to this CUJ. App is full screen at the end")
+    @Test
+    override fun taskBarWindowIsAlwaysVisible() = super.taskBarWindowIsAlwaysVisible()
+
+    @Postsubmit
+    @Test
+    override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
+        flicker.assertLayers {
+            this.visibleLayersShownMoreThanOneConsecutiveEntry(
+                LayersTraceSubject.VISIBLE_FOR_MORE_THAN_ONE_ENTRY_IGNORE_LAYERS +
+                    listOf(CAMERA_BACKGROUND)
+            )
+        }
+    }
+
+    @Postsubmit
+    @Test
+    override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
+        super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+
+    @Ignore("Not applicable to this CUJ. App is full screen at the end")
+    @Test
+    override fun navBarWindowIsVisibleAtStartAndEnd() {
+        super.navBarWindowIsVisibleAtStartAndEnd()
+    }
+
+    companion object {
+        /**
+         * Creates the test configurations.
+         *
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * navigation modes.
+         */
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
+
+        private val CAMERA_BACKGROUND =
+            ComponentNameMatcher(
+                "Background for SurfaceView" +
+                    "[com.google.android.GoogleCamera/" +
+                    "com.google.android.apps.camera.legacy.app.activity.main.CameraActivity]"
+            )
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenCameraOnDoubleClickPowerButton.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenCameraOnDoubleClickPowerButton.kt
deleted file mode 100644
index ae9ca80..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenCameraOnDoubleClickPowerButton.kt
+++ /dev/null
@@ -1,167 +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.server.wm.flicker.launch
-
-import android.os.SystemClock
-import android.platform.test.annotations.Postsubmit
-import android.tools.device.apphelpers.CameraAppHelper
-import android.tools.device.apphelpers.StandardAppHelper
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
-import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule
-import android.view.KeyEvent
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.helpers.setRotation
-import org.junit.FixMethodOrder
-import org.junit.Ignore
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test cold launching camera from launcher by double pressing power button
- *
- * To run this test: `atest FlickerTests:OpenCameraOnDoubleClickPowerButton`
- *
- * Actions:
- * ```
- *     Make sure no apps are running on the device
- *     Launch an app [testApp] and wait animation to complete
- * ```
- *
- * Notes:
- * ```
- *     1. Some default assertions (e.g., nav bar, status bar and screen covered)
- *        are inherited [OpenAppTransition]
- *     2. Part of the test setup occurs automatically via
- *        [com.android.server.wm.flicker.TransitionRunnerWithRules],
- *        including configuring navigation mode, initial orientation and ensuring no
- *        apps are running before setup
- * ```
- */
-@RequiresDevice
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class OpenCameraOnDoubleClickPowerButton(flicker: FlickerTest) :
-    OpenAppFromLauncherTransition(flicker) {
-    private val cameraApp = CameraAppHelper(instrumentation)
-    override val testApp: StandardAppHelper
-        get() = cameraApp
-
-    override val transition: FlickerBuilder.() -> Unit
-        get() = {
-            setup {
-                RemoveAllTasksButHomeRule.removeAllTasksButHome()
-                this.setRotation(flicker.scenario.startRotation)
-            }
-            transitions {
-                device.pressKeyCode(KeyEvent.KEYCODE_POWER)
-                SystemClock.sleep(100)
-                device.pressKeyCode(KeyEvent.KEYCODE_POWER)
-                wmHelper.StateSyncBuilder().withWindowSurfaceAppeared(cameraApp).waitForAndVerify()
-            }
-            teardown { RemoveAllTasksButHomeRule.removeAllTasksButHome() }
-        }
-
-    @Postsubmit @Test override fun appLayerBecomesVisible() = super.appLayerBecomesVisible()
-
-    @Postsubmit @Test override fun appWindowAsTopWindowAtEnd() = super.appWindowAsTopWindowAtEnd()
-
-    @Postsubmit @Test override fun appWindowBecomesTopWindow() = super.appWindowBecomesTopWindow()
-
-    @Postsubmit @Test override fun appWindowBecomesVisible() = super.appWindowBecomesVisible()
-
-    @Postsubmit @Test override fun appLayerReplacesLauncher() = super.appLayerReplacesLauncher()
-
-    @Postsubmit @Test override fun appWindowIsTopWindowAtEnd() = super.appWindowIsTopWindowAtEnd()
-
-    @Postsubmit
-    @Test
-    override fun appWindowReplacesLauncherAsTopWindow() =
-        super.appWindowReplacesLauncherAsTopWindow()
-
-    @Postsubmit @Test override fun focusChanges() = super.focusChanges()
-
-    @Postsubmit @Test override fun entireScreenCovered() = super.entireScreenCovered()
-
-    @Ignore("Not applicable to this CUJ. App is full screen at the end")
-    @Test
-    override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd()
-
-    @Postsubmit
-    @Test
-    override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
-
-    @Postsubmit
-    @Test
-    override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
-
-    @Ignore("Status bar visibility depends on whether the permission dialog is displayed or not")
-    @Test
-    override fun statusBarLayerIsVisibleAtStartAndEnd() =
-        super.statusBarLayerIsVisibleAtStartAndEnd()
-
-    @Ignore("Status bar visibility depends on whether the permission dialog is displayed or not")
-    @Test
-    override fun statusBarLayerPositionAtStartAndEnd() = super.statusBarLayerPositionAtStartAndEnd()
-
-    @Ignore("Status bar visibility depends on whether the permission dialog is displayed or not")
-    @Test
-    override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible()
-
-    @Ignore("Not applicable to this CUJ. App is full screen at the end")
-    @Test
-    override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd()
-
-    @Ignore("Not applicable to this CUJ. App is full screen at the end")
-    @Test
-    override fun taskBarWindowIsAlwaysVisible() = super.taskBarWindowIsAlwaysVisible()
-
-    @Postsubmit
-    @Test
-    override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
-        super.visibleLayersShownMoreThanOneConsecutiveEntry()
-
-    @Postsubmit
-    @Test
-    override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
-        super.visibleWindowsShownMoreThanOneConsecutiveEntry()
-
-    @Ignore("Not applicable to this CUJ. App is full screen at the end")
-    @Test
-    override fun navBarWindowIsVisibleAtStartAndEnd() {
-        super.navBarWindowIsVisibleAtStartAndEnd()
-    }
-
-    companion object {
-        /**
-         * Creates the test configurations.
-         *
-         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
-         * navigation modes.
-         */
-        @Parameterized.Parameters(name = "{0}")
-        @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests()
-        }
-    }
-}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OverrideTaskTransitionTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OverrideTaskTransitionTest.kt
index b48611e..98e3646 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OverrideTaskTransitionTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OverrideTaskTransitionTest.kt
@@ -25,8 +25,8 @@
 import android.tools.device.flicker.junit.FlickerBuilderProvider
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule
 import android.tools.device.helpers.wakeUpAndGoToHomeScreen
 import androidx.test.filters.RequiresDevice
@@ -54,7 +54,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class OverrideTaskTransitionTest(val flicker: FlickerTest) {
+class OverrideTaskTransitionTest(val flicker: LegacyFlickerTest) {
 
     private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
     private val testApp = SimpleAppHelper(instrumentation)
@@ -121,8 +121,6 @@
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests()
-        }
+        fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt
index d0fd732..4164c0d 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt
@@ -28,8 +28,8 @@
 import android.tools.common.traces.component.IComponentMatcher
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import android.tools.device.helpers.WindowUtils
 import android.tools.device.traces.parsers.toFlickerComponent
 import androidx.test.filters.RequiresDevice
@@ -58,7 +58,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class TaskTransitionTest(flicker: FlickerTest) : BaseTest(flicker) {
+class TaskTransitionTest(flicker: LegacyFlickerTest) : BaseTest(flicker) {
     private val launchNewTaskApp = NewTasksAppHelper(instrumentation)
     private val simpleApp = SimpleAppHelper(instrumentation)
     private val wallpaper by lazy { getWallpaperPackage(instrumentation) }
@@ -121,7 +121,7 @@
     @FlakyTest(bugId = 265007895)
     @Test
     fun transitionHasColorBackground() {
-        val backgroundColorLayer = ComponentNameMatcher("", "Animation Background")
+        val backgroundColorLayer = ComponentNameMatcher("", "animation-background")
         val displayBounds = WindowUtils.getDisplayBounds(flicker.scenario.startRotation)
         flicker.assertLayers {
             this.invoke("LAUNCH_NEW_TASK_ACTIVITY coversExactly displayBounds") {
@@ -214,8 +214,6 @@
 
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests()
-        }
+        fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/notification/OWNERS b/tests/FlickerTests/src/com/android/server/wm/flicker/notification/OWNERS
new file mode 100644
index 0000000..68c37bd
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/notification/OWNERS
@@ -0,0 +1,2 @@
+# Android > Android OS & Apps > System UI > Internal Only > Notifications
+# https://b.corp.google.com/issues/new?component=181562&template=686802
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationColdTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationColdTest.kt
new file mode 100644
index 0000000..6f5daeb
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationColdTest.kt
@@ -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 com.android.server.wm.flicker.notification
+
+import android.platform.test.annotations.Postsubmit
+import android.platform.test.rule.SettingOverrideRule
+import android.provider.Settings
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.RequiresDevice
+import org.junit.ClassRule
+import org.junit.FixMethodOrder
+import org.junit.Ignore
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test cold launching an app from a notification from the lock screen.
+ *
+ * This test assumes the device doesn't have AOD enabled
+ *
+ * To run this test: `atest FlickerTests:OpenAppFromLockNotificationCold`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Postsubmit
+open class OpenAppFromLockscreenNotificationColdTest(flicker: LegacyFlickerTest) :
+    OpenAppFromNotificationColdTest(flicker) {
+
+    override val openingNotificationsFromLockScreen = true
+
+    override val transition: FlickerBuilder.() -> Unit
+        get() = {
+            // Needs to run at start of transition,
+            // so before the transition defined in super.transition
+            transitions { device.wakeUp() }
+
+            super.transition(this)
+
+            // Needs to run at the end of the setup, so after the setup defined in super.transition
+            setup {
+                device.sleep()
+                wmHelper.StateSyncBuilder().withoutTopVisibleAppWindows().waitForAndVerify()
+            }
+        }
+
+    /** {@inheritDoc} */
+    @Test @Ignore("Display is off at the start") override fun navBarLayerPositionAtStartAndEnd() {}
+
+    /** {@inheritDoc} */
+    @Test
+    @Ignore("Display is off at the start")
+    override fun statusBarLayerPositionAtStartAndEnd() {}
+
+    /** {@inheritDoc} */
+    @Test
+    @Ignore("Display is off at the start")
+    override fun taskBarLayerIsVisibleAtStartAndEnd() {}
+
+    /** {@inheritDoc} */
+    @Test @Ignore("Display is off at the start") override fun taskBarWindowIsAlwaysVisible() {}
+
+    /** {@inheritDoc} */
+    @Test
+    @Ignore("Display is off at the start")
+    override fun statusBarLayerIsVisibleAtStartAndEnd() =
+        super.statusBarLayerIsVisibleAtStartAndEnd()
+
+    /** {@inheritDoc} */
+    @Test
+    @Ignore("Not applicable to this CUJ. Display starts locked and app is full screen at the end")
+    override fun navBarWindowIsVisibleAtStartAndEnd() = super.navBarWindowIsVisibleAtStartAndEnd()
+
+    /** {@inheritDoc} */
+    @Test
+    @Ignore("Not applicable to this CUJ. Display starts locked and app is full screen at the end")
+    override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd()
+
+    /** {@inheritDoc} */
+    @Test
+    @Ignore("Not applicable to this CUJ. Display starts locked and app is full screen at the end")
+    override fun navBarWindowIsAlwaysVisible() {}
+
+    /** {@inheritDoc} */
+    @Postsubmit
+    @Test
+    override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+        super.visibleLayersShownMoreThanOneConsecutiveEntry()
+
+    companion object {
+        /**
+         * Creates the test configurations.
+         *
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * navigation modes.
+         */
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
+
+        /**
+         * Ensures that posted notifications will be visible on the lockscreen and not suppressed
+         * due to being marked as seen.
+         */
+        @ClassRule
+        @JvmField
+        val disableUnseenNotifFilterRule =
+            SettingOverrideRule(
+                Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
+                /* value = */ "0",
+            )
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationWarmTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationWarmTest.kt
new file mode 100644
index 0000000..483caa7
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationWarmTest.kt
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.flicker.notification
+
+import android.platform.test.annotations.FlakyTest
+import android.platform.test.annotations.Presubmit
+import android.platform.test.rule.SettingOverrideRule
+import android.provider.Settings
+import android.tools.common.traces.component.ComponentNameMatcher
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.statusBarLayerPositionAtEnd
+import org.junit.ClassRule
+import org.junit.FixMethodOrder
+import org.junit.Ignore
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test warm launching an app from a notification from the lock screen.
+ *
+ * This test assumes the device doesn't have AOD enabled
+ *
+ * To run this test: `atest FlickerTests:OpenAppFromLockNotificationWarm`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class OpenAppFromLockscreenNotificationWarmTest(flicker: LegacyFlickerTest) :
+    OpenAppFromNotificationWarmTest(flicker) {
+
+    override val openingNotificationsFromLockScreen = true
+
+    override val transition: FlickerBuilder.() -> Unit
+        get() = {
+            // Needs to run at start of transition,
+            // so before the transition defined in super.transition
+            transitions { device.wakeUp() }
+
+            super.transition(this)
+
+            // Needs to run at the end of the setup, so after the setup defined in super.transition
+            setup {
+                device.sleep()
+                wmHelper.StateSyncBuilder().withoutTopVisibleAppWindows().waitForAndVerify()
+            }
+        }
+
+    /**
+     * Checks that we start of with no top windows and then [testApp] becomes the first and only top
+     * window of the transition, with snapshot or splash screen windows optionally showing first.
+     */
+    @Test
+    @Presubmit
+    fun appWindowBecomesFirstAndOnlyTopWindow() {
+        flicker.assertWm {
+            this.hasNoVisibleAppWindow()
+                .then()
+                .isAppWindowOnTop(ComponentNameMatcher.SNAPSHOT, isOptional = true)
+                .then()
+                .isAppWindowOnTop(ComponentNameMatcher.SPLASH_SCREEN, isOptional = true)
+                .then()
+                .isAppWindowOnTop(testApp)
+        }
+    }
+
+    /** Checks that the screen is locked at the start of the transition */
+    @Test
+    @Presubmit
+    fun screenLockedStart() {
+        flicker.assertWmStart { isKeyguardShowing() }
+    }
+
+    /** {@inheritDoc} */
+    @Test
+    @Ignore("Not applicable to this CUJ. Display starts locked and app is full screen at the end")
+    override fun navBarLayerPositionAtStartAndEnd() {}
+
+    /** {@inheritDoc} */
+    @Test
+    @Ignore("Not applicable to this CUJ. Display starts locked and app is full screen at the end")
+    override fun taskBarWindowIsAlwaysVisible() {}
+
+    /** {@inheritDoc} */
+    @Test
+    @Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end")
+    override fun statusBarLayerPositionAtStartAndEnd() {}
+
+    /** {@inheritDoc} */
+    @Test
+    @Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end")
+    override fun statusBarLayerIsVisibleAtStartAndEnd() =
+        super.statusBarLayerIsVisibleAtStartAndEnd()
+
+    /**
+     * Checks the position of the [ComponentNameMatcher.STATUS_BAR] at the start and end of the
+     * transition
+     */
+    @Presubmit @Test fun statusBarLayerPositionAtEnd() = flicker.statusBarLayerPositionAtEnd()
+
+    /** {@inheritDoc} */
+    @Test
+    @Ignore("Not applicable to this CUJ. Display starts locked and app is full screen at the end")
+    override fun navBarWindowIsVisibleAtStartAndEnd() = super.navBarWindowIsVisibleAtStartAndEnd()
+
+    /** {@inheritDoc} */
+    @Test
+    @Ignore("Not applicable to this CUJ. Display starts locked and app is full screen at the end")
+    override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd()
+
+    /** {@inheritDoc} */
+    @Test
+    @Ignore("Not applicable to this CUJ. Display starts locked and app is full screen at the end")
+    override fun navBarWindowIsAlwaysVisible() {}
+
+    /** {@inheritDoc} */
+    @FlakyTest(bugId = 246284526)
+    @Test
+    override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+        super.visibleLayersShownMoreThanOneConsecutiveEntry()
+
+    companion object {
+        /**
+         * Creates the test configurations.
+         *
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * navigation modes.
+         */
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
+
+        /**
+         * Ensures that posted notifications will be visible on the lockscreen and not suppressed
+         * due to being marked as seen.
+         */
+        @ClassRule
+        @JvmField
+        val disableUnseenNotifFilterRule =
+            SettingOverrideRule(
+                Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
+                /* value= */ "0",
+            )
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationWithOverlayAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationWithOverlayAppTest.kt
new file mode 100644
index 0000000..3f3542d
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationWithOverlayAppTest.kt
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.flicker.notification
+
+import android.platform.test.annotations.FlakyTest
+import android.platform.test.annotations.Postsubmit
+import android.platform.test.annotations.Presubmit
+import android.tools.common.traces.component.ComponentNameMatcher
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+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.wakeUpAndGoToHomeScreen
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.helpers.ShowWhenLockedAppHelper
+import org.junit.FixMethodOrder
+import org.junit.Ignore
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test cold launching an app from a notification from the lock screen when there is an app overlaid
+ * on the lock screen.
+ *
+ * This test assumes the device doesn't have AOD enabled
+ *
+ * To run this test: `atest FlickerTests:OpenAppFromLockNotificationWithLockOverlayApp`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Postsubmit
+class OpenAppFromLockscreenNotificationWithOverlayAppTest(flicker: LegacyFlickerTest) :
+    OpenAppFromLockscreenNotificationColdTest(flicker) {
+    private val showWhenLockedApp = ShowWhenLockedAppHelper(instrumentation)
+
+    // Although we are technically still locked here, the overlay app means we should open the
+    // notification shade as if we were unlocked.
+    override val openingNotificationsFromLockScreen = false
+
+    override val transition: FlickerBuilder.() -> Unit
+        get() = {
+            super.transition(this)
+
+            transitions {
+                wmHelper.StateSyncBuilder().withFullScreenApp(testApp).waitForAndVerify()
+            }
+
+            setup {
+                device.wakeUpAndGoToHomeScreen()
+
+                // Launch an activity that is shown when the device is locked
+                showWhenLockedApp.launchViaIntent(wmHelper)
+                wmHelper.StateSyncBuilder().withFullScreenApp(showWhenLockedApp).waitForAndVerify()
+
+                device.sleep()
+                wmHelper.StateSyncBuilder().withoutTopVisibleAppWindows().waitForAndVerify()
+            }
+
+            teardown { showWhenLockedApp.exit(wmHelper) }
+        }
+
+    @Test
+    @FlakyTest(bugId = 227143265)
+    fun showWhenLockedAppWindowBecomesVisible() {
+        flicker.assertWm {
+            this.hasNoVisibleAppWindow()
+                .then()
+                .isAppWindowOnTop(ComponentNameMatcher.SNAPSHOT, isOptional = true)
+                .then()
+                .isAppWindowOnTop(showWhenLockedApp)
+        }
+    }
+
+    @Test
+    @FlakyTest(bugId = 227143265)
+    fun showWhenLockedAppLayerBecomesVisible() {
+        flicker.assertLayers {
+            this.isInvisible(showWhenLockedApp)
+                .then()
+                .isVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true)
+                .then()
+                .isVisible(showWhenLockedApp)
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Presubmit @Test override fun appLayerBecomesVisible() = super.appLayerBecomesVisible()
+
+    /** {@inheritDoc} */
+    @FlakyTest(bugId = 227143265)
+    @Test
+    override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+        super.visibleLayersShownMoreThanOneConsecutiveEntry()
+
+    /** {@inheritDoc} */
+    @Test
+    @Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end")
+    override fun navBarLayerIsVisibleAtStartAndEnd() {}
+
+    /** {@inheritDoc} */
+    @Presubmit
+    @Test
+    override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
+
+    @Presubmit @Test override fun entireScreenCovered() = super.entireScreenCovered()
+
+    @FlakyTest(bugId = 278227468)
+    @Test
+    override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
+        super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+
+    companion object {
+        /**
+         * Creates the test configurations.
+         *
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * navigation modes.
+         */
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/notification/OpenAppFromNotificationColdTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/notification/OpenAppFromNotificationColdTest.kt
new file mode 100644
index 0000000..fbdba7b
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/notification/OpenAppFromNotificationColdTest.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.
+ */
+
+package com.android.server.wm.flicker.notification
+
+import android.platform.test.annotations.Postsubmit
+import android.platform.test.annotations.Presubmit
+import android.tools.common.traces.component.ComponentNameMatcher
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import com.android.server.wm.flicker.statusBarLayerPositionAtEnd
+import org.junit.FixMethodOrder
+import org.junit.Ignore
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test cold launching an app from a notification.
+ *
+ * To run this test: `atest FlickerTests:OpenAppFromNotificationCold`
+ */
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Postsubmit
+open class OpenAppFromNotificationColdTest(flicker: LegacyFlickerTest) :
+    OpenAppFromNotificationWarmTest(flicker) {
+    /** {@inheritDoc} */
+    override val transition: FlickerBuilder.() -> Unit
+        get() = {
+            super.transition(this)
+
+            setup {
+                // Close the app that posted the notification to trigger a cold start next time
+                // it is open - can't just kill it because that would remove the notification.
+                tapl.setExpectedRotationCheckEnabled(false)
+                tapl.goHome()
+                tapl.workspace.switchToOverview()
+                tapl.overview.dismissAllTasks()
+            }
+        }
+
+    @Presubmit @Test override fun appWindowBecomesVisible() = appWindowBecomesVisible_coldStart()
+
+    @Presubmit @Test override fun appLayerBecomesVisible() = appLayerBecomesVisible_coldStart()
+
+    /** {@inheritDoc} */
+    @Test
+    @Ignore("Not applicable to this CUJ. Display starts locked and app is full screen at the end")
+    override fun navBarLayerPositionAtStartAndEnd() {}
+
+    /** {@inheritDoc} */
+    @Test
+    @Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end")
+    override fun statusBarLayerPositionAtStartAndEnd() {}
+
+    /** {@inheritDoc} */
+    @Test
+    @Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end")
+    override fun statusBarLayerIsVisibleAtStartAndEnd() =
+        super.statusBarLayerIsVisibleAtStartAndEnd()
+
+    /**
+     * Checks the position of the [ComponentNameMatcher.STATUS_BAR] at the start and end of the
+     * transition
+     */
+    @Presubmit @Test open fun statusBarLayerPositionAtEnd() = flicker.statusBarLayerPositionAtEnd()
+
+    /** {@inheritDoc} */
+    @Test
+    @Ignore("Not applicable to this CUJ. Display starts locked and app is full screen at the end")
+    override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd()
+
+    /** {@inheritDoc} */
+    @Test
+    @Ignore("Not applicable to this CUJ. Display starts locked and app is full screen at the end")
+    override fun navBarWindowIsAlwaysVisible() {}
+
+    companion object {
+        /**
+         * Creates the test configurations.
+         *
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * navigation modes.
+         */
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/notification/OpenAppFromNotificationWarmTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/notification/OpenAppFromNotificationWarmTest.kt
new file mode 100644
index 0000000..a6cf8ee
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/notification/OpenAppFromNotificationWarmTest.kt
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.flicker.notification
+
+import android.platform.test.annotations.Postsubmit
+import android.platform.test.annotations.Presubmit
+import android.tools.common.traces.component.ComponentNameMatcher
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+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.wakeUpAndGoToHomeScreen
+import android.view.WindowInsets
+import android.view.WindowManager
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.Until
+import com.android.server.wm.flicker.helpers.NotificationAppHelper
+import com.android.server.wm.flicker.helpers.setRotation
+import com.android.server.wm.flicker.navBarLayerIsVisibleAtEnd
+import com.android.server.wm.flicker.navBarLayerPositionAtEnd
+import com.android.server.wm.flicker.navBarWindowIsVisibleAtEnd
+import com.android.server.wm.flicker.taskBarLayerIsVisibleAtEnd
+import com.android.server.wm.flicker.taskBarWindowIsVisibleAtEnd
+import org.junit.Assume
+import org.junit.FixMethodOrder
+import org.junit.Ignore
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test cold launching an app from a notification.
+ *
+ * To run this test: `atest FlickerTests:OpenAppFromNotificationWarm`
+ */
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+open class OpenAppFromNotificationWarmTest(flicker: LegacyFlickerTest) :
+    OpenAppTransition(flicker) {
+    override val testApp: NotificationAppHelper = NotificationAppHelper(instrumentation)
+
+    open val openingNotificationsFromLockScreen = false
+
+    /** {@inheritDoc} */
+    override val transition: FlickerBuilder.() -> Unit
+        get() = {
+            setup {
+                device.wakeUpAndGoToHomeScreen()
+                this.setRotation(flicker.scenario.startRotation)
+                testApp.launchViaIntent(wmHelper)
+                wmHelper.StateSyncBuilder().withFullScreenApp(testApp).waitForAndVerify()
+                testApp.postNotification(wmHelper)
+                device.pressHome()
+                wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
+            }
+
+            transitions {
+                var startY = 10
+                var endY = 3 * device.displayHeight / 4
+                var steps = 25
+                if (openingNotificationsFromLockScreen) {
+                    val wm: WindowManager =
+                        instrumentation.context.getSystemService(WindowManager::class.java)
+                            ?: error("Unable to connect to WindowManager service")
+                    val metricInsets = wm.currentWindowMetrics.windowInsets
+                    val insets =
+                        metricInsets.getInsetsIgnoringVisibility(
+                            WindowInsets.Type.statusBars() or WindowInsets.Type.displayCutout()
+                        )
+
+                    startY = insets.top + 100
+                    endY = device.displayHeight / 2
+                    steps = 4
+                }
+
+                // Swipe down to show the notification shade
+                val x = device.displayWidth / 2
+                device.swipe(x, startY, x, endY, steps)
+                device.waitForIdle(2000)
+                instrumentation.uiAutomation.syncInputTransactions()
+
+                // Launch the activity by clicking the notification
+                val notification =
+                    device.wait(Until.findObject(By.text("Flicker Test Notification")), 2000L)
+                notification?.click() ?: error("Notification not found")
+                instrumentation.uiAutomation.syncInputTransactions()
+
+                // Wait for the app to launch
+                wmHelper.StateSyncBuilder().withFullScreenApp(testApp).waitForAndVerify()
+            }
+
+            teardown { testApp.exit(wmHelper) }
+        }
+
+    @Presubmit @Test override fun appWindowBecomesVisible() = appWindowBecomesVisible_warmStart()
+
+    @Presubmit @Test override fun appLayerBecomesVisible() = appLayerBecomesVisible_warmStart()
+
+    @Presubmit
+    @Test
+    open fun notificationAppWindowVisibleAtEnd() {
+        flicker.assertWmEnd { this.isAppWindowVisible(testApp) }
+    }
+
+    @Presubmit
+    @Test
+    open fun notificationAppWindowOnTopAtEnd() {
+        flicker.assertWmEnd { this.isAppWindowOnTop(testApp) }
+    }
+
+    @Presubmit
+    @Test
+    open fun notificationAppLayerVisibleAtEnd() {
+        flicker.assertLayersEnd { this.isVisible(testApp) }
+    }
+
+    /**
+     * Checks that the [ComponentNameMatcher.TASK_BAR] window is visible at the end of the
+     * transition
+     *
+     * Note: Large screen only
+     */
+    @Presubmit
+    @Test
+    open fun taskBarWindowIsVisibleAtEnd() {
+        Assume.assumeTrue(flicker.scenario.isTablet)
+        flicker.taskBarWindowIsVisibleAtEnd()
+    }
+
+    /**
+     * Checks that the [ComponentNameMatcher.TASK_BAR] layer is visible at the end of the transition
+     *
+     * Note: Large screen only
+     */
+    @Presubmit
+    @Test
+    open fun taskBarLayerIsVisibleAtEnd() {
+        Assume.assumeTrue(flicker.scenario.isTablet)
+        flicker.taskBarLayerIsVisibleAtEnd()
+    }
+
+    /** Checks the position of the [ComponentNameMatcher.NAV_BAR] at the end of the transition */
+    @Presubmit
+    @Test
+    open fun navBarLayerPositionAtEnd() {
+        Assume.assumeFalse(flicker.scenario.isTablet)
+        flicker.navBarLayerPositionAtEnd()
+    }
+
+    /** {@inheritDoc} */
+    @Presubmit
+    @Test
+    open fun navBarLayerIsVisibleAtEnd() {
+        Assume.assumeFalse(flicker.scenario.isTablet)
+        flicker.navBarLayerIsVisibleAtEnd()
+    }
+
+    @Presubmit
+    @Test
+    open fun navBarWindowIsVisibleAtEnd() {
+        Assume.assumeFalse(flicker.scenario.isTablet)
+        flicker.navBarWindowIsVisibleAtEnd()
+    }
+
+    /** {@inheritDoc} */
+    @Test
+    @Ignore("Display is off at the start")
+    override fun taskBarLayerIsVisibleAtStartAndEnd() {}
+
+    /** {@inheritDoc} */
+    @Test
+    @Postsubmit
+    override fun taskBarWindowIsAlwaysVisible() = super.taskBarWindowIsAlwaysVisible()
+
+    companion object {
+        /**
+         * Creates the test configurations.
+         *
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * navigation modes.
+         */
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/notification/OpenAppTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/notification/OpenAppTransition.kt
new file mode 100644
index 0000000..684b4b7
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/notification/OpenAppTransition.kt
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.flicker.notification
+
+import android.platform.test.annotations.Presubmit
+import android.tools.common.traces.component.ComponentNameMatcher
+import android.tools.device.apphelpers.StandardAppHelper
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.helpers.wakeUpAndGoToHomeScreen
+import com.android.server.wm.flicker.BaseTest
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.server.wm.flicker.helpers.setRotation
+import org.junit.Test
+
+/** Base class for app launch tests */
+abstract class OpenAppTransition(flicker: LegacyFlickerTest) : BaseTest(flicker) {
+    protected open val testApp: StandardAppHelper = SimpleAppHelper(instrumentation)
+
+    /** {@inheritDoc} */
+    override val transition: FlickerBuilder.() -> Unit = {
+        setup {
+            tapl.setExpectedRotation(flicker.scenario.startRotation.value)
+            device.wakeUpAndGoToHomeScreen()
+            this.setRotation(flicker.scenario.startRotation)
+        }
+        teardown { testApp.exit(wmHelper) }
+    }
+
+    /**
+     * Checks that the [testApp] layer doesn't exist or is invisible at the start of the transition,
+     * but is created and/or becomes visible during the transition.
+     */
+    @Presubmit
+    @Test
+    open fun appLayerBecomesVisible() {
+        appLayerBecomesVisible_coldStart()
+    }
+
+    protected fun appLayerBecomesVisible_coldStart() {
+        flicker.assertLayers {
+            this.notContains(testApp)
+                .then()
+                .isInvisible(testApp, isOptional = true)
+                .then()
+                .isVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true)
+                .then()
+                .isVisible(ComponentNameMatcher.SPLASH_SCREEN, isOptional = true)
+                .then()
+                .isVisible(testApp)
+        }
+    }
+
+    protected fun appLayerBecomesVisible_warmStart() {
+        flicker.assertLayers {
+            this.isInvisible(testApp)
+                .then()
+                .isVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true)
+                .then()
+                .isVisible(ComponentNameMatcher.SPLASH_SCREEN, isOptional = true)
+                .then()
+                .isVisible(testApp)
+        }
+    }
+
+    /**
+     * Checks that the [testApp] window doesn't exist at the start of the transition, that it is
+     * created (invisible - optional) and becomes visible during the transition
+     *
+     * The `isAppWindowInvisible` step is optional because we log once per frame, upon logging, the
+     * window may be visible or not depending on what was processed until that moment.
+     */
+    @Presubmit @Test open fun appWindowBecomesVisible() = appWindowBecomesVisible_coldStart()
+
+    protected fun appWindowBecomesVisible_coldStart() {
+        flicker.assertWm {
+            this.notContains(testApp)
+                .then()
+                .isAppWindowInvisible(testApp, isOptional = true)
+                .then()
+                .isAppWindowVisible(testApp)
+        }
+    }
+
+    protected fun appWindowBecomesVisible_warmStart() {
+        flicker.assertWm {
+            this.isAppWindowInvisible(testApp)
+                .then()
+                .isAppWindowVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true)
+                .then()
+                .isAppWindowVisible(ComponentNameMatcher.SPLASH_SCREEN, isOptional = true)
+                .then()
+                .isAppWindowVisible(testApp)
+        }
+    }
+
+    /**
+     * Checks that [testApp] window is not on top at the start of the transition, and then becomes
+     * the top visible window until the end of the transition.
+     */
+    @Presubmit
+    @Test
+    open fun appWindowBecomesTopWindow() {
+        flicker.assertWm {
+            this.isAppWindowNotOnTop(testApp)
+                .then()
+                .isAppWindowOnTop(
+                    testApp.or(ComponentNameMatcher.SNAPSHOT).or(ComponentNameMatcher.SPLASH_SCREEN)
+                )
+        }
+    }
+
+    /**
+     * Checks that [testApp] window is not on top at the start of the transition, and then becomes
+     * the top visible window until the end of the transition.
+     */
+    @Presubmit
+    @Test
+    open fun appWindowIsTopWindowAtEnd() {
+        flicker.assertWmEnd { this.isAppWindowOnTop(testApp) }
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt
index a8b80ad..50ff62b 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt
@@ -23,9 +23,8 @@
 import android.tools.common.traces.component.ComponentNameMatcher
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
-import androidx.test.filters.RequiresDevice
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import com.android.server.wm.flicker.BaseTest
 import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
 import com.android.server.wm.flicker.helpers.SimpleAppHelper
@@ -48,11 +47,10 @@
  *     Swipe right from the bottom of the screen to quick switch back to the first app [testApp1]
  * ```
  */
-@RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class QuickSwitchBetweenTwoAppsBackTest(flicker: FlickerTest) : BaseTest(flicker) {
+class QuickSwitchBetweenTwoAppsBackTest(flicker: LegacyFlickerTest) : BaseTest(flicker) {
     private val testApp1 = SimpleAppHelper(instrumentation)
     private val testApp2 = NonResizeableAppHelper(instrumentation)
 
@@ -243,10 +241,9 @@
 
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
                 supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
             )
-        }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTestCfArm.kt
deleted file mode 100644
index f970a79..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTestCfArm.kt
+++ /dev/null
@@ -1,42 +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.server.wm.flicker.quickswitch
-
-import android.tools.common.NavBar
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
-import org.junit.FixMethodOrder
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class QuickSwitchBetweenTwoAppsBackTestCfArm(flicker: FlickerTest) :
-    QuickSwitchBetweenTwoAppsBackTest(flicker) {
-    companion object {
-        @Parameterized.Parameters(name = "{0}")
-        @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
-                supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
-            )
-        }
-    }
-}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt
index 96cd8ff..aee9163 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt
@@ -23,9 +23,8 @@
 import android.tools.common.traces.component.ComponentNameMatcher
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
-import androidx.test.filters.RequiresDevice
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import com.android.server.wm.flicker.BaseTest
 import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
 import com.android.server.wm.flicker.helpers.SimpleAppHelper
@@ -49,11 +48,10 @@
  *     Swipe left from the bottom of the screen to quick switch forward to the second app [testApp2]
  * ```
  */
-@RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class QuickSwitchBetweenTwoAppsForwardTest(flicker: FlickerTest) : BaseTest(flicker) {
+class QuickSwitchBetweenTwoAppsForwardTest(flicker: LegacyFlickerTest) : BaseTest(flicker) {
     private val testApp1 = SimpleAppHelper(instrumentation)
     private val testApp2 = NonResizeableAppHelper(instrumentation)
 
@@ -261,10 +259,9 @@
 
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
                 supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
             )
-        }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTestCfArm.kt
deleted file mode 100644
index 9f48cda..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTestCfArm.kt
+++ /dev/null
@@ -1,42 +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.server.wm.flicker.quickswitch
-
-import android.tools.common.NavBar
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
-import org.junit.FixMethodOrder
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class QuickSwitchBetweenTwoAppsForwardTestCfArm(flicker: FlickerTest) :
-    QuickSwitchBetweenTwoAppsForwardTest(flicker) {
-    companion object {
-        @Parameterized.Parameters(name = "{0}")
-        @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
-                supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
-            )
-        }
-    }
-}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt
index 7e935f0..d6a951d 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt
@@ -24,9 +24,8 @@
 import android.tools.common.traces.component.ComponentNameMatcher
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
-import androidx.test.filters.RequiresDevice
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import com.android.server.wm.flicker.BaseTest
 import com.android.server.wm.flicker.helpers.SimpleAppHelper
 import org.junit.FixMethodOrder
@@ -48,11 +47,10 @@
  *     Swipe right from the bottom of the screen to quick switch back to the app
  * ```
  */
-@RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class QuickSwitchFromLauncherTest(flicker: FlickerTest) : BaseTest(flicker) {
+class QuickSwitchFromLauncherTest(flicker: LegacyFlickerTest) : BaseTest(flicker) {
     private val testApp = SimpleAppHelper(instrumentation)
 
     /** {@inheritDoc} */
@@ -272,12 +270,11 @@
 
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
                 supportedNavigationModes = listOf(NavBar.MODE_GESTURAL),
                 // TODO: Test with 90 rotation
                 supportedRotations = listOf(Rotation.ROTATION_0)
             )
-        }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTestCfArm.kt
deleted file mode 100644
index af671df..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTestCfArm.kt
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm.flicker.quickswitch
-
-import android.tools.common.NavBar
-import android.tools.common.Rotation
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
-import org.junit.FixMethodOrder
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class QuickSwitchFromLauncherTestCfArm(flicker: FlickerTest) :
-    QuickSwitchFromLauncherTest(flicker) {
-    companion object {
-        @Parameterized.Parameters(name = "{0}")
-        @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
-                supportedNavigationModes = listOf(NavBar.MODE_GESTURAL),
-                // TODO: Test with 90 rotation
-                supportedRotations = listOf(Rotation.ROTATION_0)
-            )
-        }
-    }
-}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
index ef75c61..1987a68 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
@@ -21,9 +21,8 @@
 import android.tools.common.traces.component.ComponentNameMatcher
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
-import androidx.test.filters.RequiresDevice
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import com.android.server.wm.flicker.helpers.SimpleAppHelper
 import org.junit.FixMethodOrder
 import org.junit.Test
@@ -34,7 +33,7 @@
 /**
  * Test opening an app and cycling through app rotations
  *
- * Currently runs:
+ * Currently, runs:
  * ```
  *      0 -> 90 degrees
  *      90 -> 0 degrees
@@ -81,11 +80,10 @@
  *        apps are running before setup
  * ```
  */
-@RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class ChangeAppRotationTest(flicker: FlickerTest) : RotationTransition(flicker) {
+class ChangeAppRotationTest(flicker: LegacyFlickerTest) : RotationTransition(flicker) {
     override val testApp = SimpleAppHelper(instrumentation)
     override val transition: FlickerBuilder.() -> Unit
         get() = {
@@ -140,13 +138,11 @@
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestFactory.rotationTests] for configuring screen orientation and navigation
-         * modes.
+         * See [LegacyFlickerTestFactory.rotationTests] for configuring screen orientation and
+         * navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.rotationTests()
-        }
+        fun getParams() = LegacyFlickerTestFactory.rotationTests()
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTestCfArm.kt
deleted file mode 100644
index 0e6b20f..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTestCfArm.kt
+++ /dev/null
@@ -1,44 +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.server.wm.flicker.rotation
-
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
-import org.junit.FixMethodOrder
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class ChangeAppRotationTestCfArm(flicker: FlickerTest) : ChangeAppRotationTest(flicker) {
-    companion object {
-        /**
-         * Creates the test configurations.
-         *
-         * See [FlickerTestFactory.rotationTests] for configuring screen orientation and navigation
-         * modes.
-         */
-        @Parameterized.Parameters(name = "{0}")
-        @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.rotationTests()
-        }
-    }
-}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt
index fe9da33..b0ca4d2 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt
@@ -20,13 +20,13 @@
 import android.tools.common.traces.component.ComponentNameMatcher
 import android.tools.device.apphelpers.StandardAppHelper
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTest
 import com.android.server.wm.flicker.BaseTest
 import com.android.server.wm.flicker.helpers.setRotation
 import org.junit.Test
 
 /** Base class for app rotation tests */
-abstract class RotationTransition(flicker: FlickerTest) : BaseTest(flicker) {
+abstract class RotationTransition(flicker: LegacyFlickerTest) : BaseTest(flicker) {
     protected abstract val testApp: StandardAppHelper
 
     /** {@inheritDoc} */
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
index f654bdd4..5b127c8 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
@@ -19,13 +19,13 @@
 import android.platform.test.annotations.PlatinumTest
 import android.platform.test.annotations.Presubmit
 import android.tools.common.ScenarioBuilder
+import android.tools.common.ScenarioImpl
 import android.tools.common.traces.component.ComponentNameMatcher
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import android.view.WindowManager
-import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.helpers.SeamlessRotationAppHelper
 import com.android.server.wm.flicker.testapp.ActivityOptions
 import org.junit.FixMethodOrder
@@ -87,11 +87,10 @@
  *        apps are running before setup
  * ```
  */
-@RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class SeamlessAppRotationTest(flicker: FlickerTest) : RotationTransition(flicker) {
+class SeamlessAppRotationTest(flicker: LegacyFlickerTest) : RotationTransition(flicker) {
     override val testApp = SeamlessRotationAppHelper(instrumentation)
 
     /** {@inheritDoc} */
@@ -236,43 +235,50 @@
     }
 
     companion object {
-        private val FlickerTest.starveUiThread
+        private val LegacyFlickerTest.starveUiThread
             get() =
-                getConfigValue<Boolean>(ActivityOptions.SeamlessRotation.EXTRA_STARVE_UI_THREAD)
+                scenario.getConfigValue<Boolean>(
+                    ActivityOptions.SeamlessRotation.EXTRA_STARVE_UI_THREAD
+                )
                     ?: false
 
         @JvmStatic
         protected fun createConfig(
-            sourceConfig: FlickerTest,
+            sourceConfig: LegacyFlickerTest,
             starveUiThread: Boolean
-        ): FlickerTest {
-            val originalScenario = sourceConfig.initialize("createConfig")
+        ): LegacyFlickerTest {
+            val originalScenario = sourceConfig.initialize("createConfig") as ScenarioImpl
             val nameExt = if (starveUiThread) "_BUSY_UI_THREAD" else ""
             val newConfig =
                 ScenarioBuilder()
-                    .fromScenario(originalScenario)
+                    .forClass(originalScenario.testClass)
+                    .withStartRotation(originalScenario.startRotation)
+                    .withEndRotation(originalScenario.endRotation)
+                    .withNavBarMode(originalScenario.navBarMode)
+                    .withExtraConfigs(originalScenario.extraConfig)
+                    .withDescriptionOverride(originalScenario.description)
                     .withExtraConfig(
                         ActivityOptions.SeamlessRotation.EXTRA_STARVE_UI_THREAD,
                         starveUiThread
                     )
                     .withDescriptionOverride("${originalScenario.description}$nameExt")
-            return FlickerTest(newConfig)
+            return LegacyFlickerTest(newConfig)
         }
 
         /**
          * Creates the test configurations for seamless rotation based on the default rotation tests
-         * from [FlickerTestFactory.rotationTests], but adding a flag (
+         * from [LegacyFlickerTestFactory.rotationTests], but adding a flag (
          * [ActivityOptions.SeamlessRotation.EXTRA_STARVE_UI_THREAD]) to indicate if the app should
          * starve the UI thread of not
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.rotationTests().flatMap { sourceConfig ->
-                val defaultRun = createConfig(sourceConfig, starveUiThread = false)
-                val busyUiRun = createConfig(sourceConfig, starveUiThread = true)
+        fun getParams() =
+            LegacyFlickerTestFactory.rotationTests().flatMap { sourceCfg ->
+                val legacyCfg = sourceCfg as LegacyFlickerTest
+                val defaultRun = createConfig(legacyCfg, starveUiThread = false)
+                val busyUiRun = createConfig(legacyCfg, starveUiThread = true)
                 listOf(defaultRun, busyUiRun)
             }
-        }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTestCfArm.kt
deleted file mode 100644
index b236d87..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTestCfArm.kt
+++ /dev/null
@@ -1,50 +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.server.wm.flicker.rotation
-
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
-import com.android.server.wm.flicker.testapp.ActivityOptions
-import org.junit.FixMethodOrder
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/** This test should fail because of b/264518826 */
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class SeamlessAppRotationTestCfArm(flicker: FlickerTest) : SeamlessAppRotationTest(flicker) {
-    companion object {
-        /**
-         * Creates the test configurations for seamless rotation based on the default rotation tests
-         * from [FlickerTestFactory.rotationTests], but adding a flag (
-         * [ActivityOptions.SeamlessRotation.EXTRA_STARVE_UI_THREAD]) to indicate if the app should
-         * starve the UI thread of not
-         */
-        @Parameterized.Parameters(name = "{0}")
-        @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.rotationTests().flatMap { sourceConfig ->
-                val defaultRun = createConfig(sourceConfig, starveUiThread = false)
-                val busyUiRun = createConfig(sourceConfig, starveUiThread = true)
-                listOf(defaultRun, busyUiRun)
-            }
-        }
-    }
-}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/Utils.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/Utils.kt
new file mode 100644
index 0000000..8242e9a
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/Utils.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.server.wm.flicker.service
+
+import android.app.Instrumentation
+import android.platform.test.rule.NavigationModeRule
+import android.platform.test.rule.PressHomeRule
+import android.platform.test.rule.UnlockScreenRule
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.apphelpers.MessagingAppHelper
+import android.tools.device.flicker.rules.ChangeDisplayOrientationRule
+import android.tools.device.flicker.rules.LaunchAppRule
+import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule
+import androidx.test.platform.app.InstrumentationRegistry
+import org.junit.rules.RuleChain
+
+object Utils {
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+
+    fun testSetupRule(navigationMode: NavBar, rotation: Rotation): RuleChain {
+        return RuleChain.outerRule(UnlockScreenRule())
+            .around(
+                NavigationModeRule(navigationMode.value, /* changeNavigationModeAfterTest */ false)
+            )
+            .around(
+                LaunchAppRule(MessagingAppHelper(instrumentation), clearCacheAfterParsing = false)
+            )
+            .around(RemoveAllTasksButHomeRule())
+            .around(
+                ChangeDisplayOrientationRule(
+                    rotation,
+                    resetOrientationAfterTest = false,
+                    clearCacheAfterParsing = false
+                )
+            )
+            .around(PressHomeRule())
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButton3ButtonLandscape.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButton3ButtonLandscape.kt
new file mode 100644
index 0000000..030b292
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButton3ButtonLandscape.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.close.flicker
+
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.close.scenarios.CloseAppBackButton
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class CloseAppBackButton3ButtonLandscape :
+    CloseAppBackButton(NavBar.MODE_3BUTTON, Rotation.ROTATION_90) {
+    @ExpectedScenarios(["APP_CLOSE_TO_HOME"])
+    @Test
+    override fun closeAppBackButtonTest() = super.closeAppBackButtonTest()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButton3ButtonPortrait.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButton3ButtonPortrait.kt
new file mode 100644
index 0000000..770da143
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButton3ButtonPortrait.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.close.flicker
+
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.close.scenarios.CloseAppBackButton
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class CloseAppBackButton3ButtonPortrait :
+    CloseAppBackButton(NavBar.MODE_3BUTTON, Rotation.ROTATION_0) {
+    @ExpectedScenarios(["APP_CLOSE_TO_HOME"])
+    @Test
+    override fun closeAppBackButtonTest() = super.closeAppBackButtonTest()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButtonGesturalNavLandscape.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButtonGesturalNavLandscape.kt
new file mode 100644
index 0000000..4b2206d7
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButtonGesturalNavLandscape.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.close.flicker
+
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.close.scenarios.CloseAppBackButton
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class CloseAppBackButtonGesturalNavLandscape :
+    CloseAppBackButton(NavBar.MODE_GESTURAL, Rotation.ROTATION_90) {
+    @ExpectedScenarios(["APP_CLOSE_TO_HOME"])
+    @Test
+    override fun closeAppBackButtonTest() = super.closeAppBackButtonTest()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButtonGesturalNavPortrait.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButtonGesturalNavPortrait.kt
new file mode 100644
index 0000000..54b471e
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButtonGesturalNavPortrait.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.close.flicker
+
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.close.scenarios.CloseAppBackButton
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class CloseAppBackButtonGesturalNavPortrait :
+    CloseAppBackButton(NavBar.MODE_GESTURAL, Rotation.ROTATION_0) {
+    @ExpectedScenarios(["APP_CLOSE_TO_HOME"])
+    @Test
+    override fun closeAppBackButtonTest() = super.closeAppBackButtonTest()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppHomeButton3ButtonLandscape.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppHomeButton3ButtonLandscape.kt
new file mode 100644
index 0000000..47c2529
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppHomeButton3ButtonLandscape.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.server.wm.flicker.service.close.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.close.scenarios.CloseAppHomeButton
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class CloseAppHomeButton3ButtonLandscape : CloseAppHomeButton(Rotation.ROTATION_90) {
+    @ExpectedScenarios(["APP_CLOSE_TO_HOME"])
+    @Test
+    override fun closeAppHomeButton() = super.closeAppHomeButton()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppHomeButton3ButtonPortrait.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppHomeButton3ButtonPortrait.kt
new file mode 100644
index 0000000..b18148f
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppHomeButton3ButtonPortrait.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.server.wm.flicker.service.close.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.close.scenarios.CloseAppHomeButton
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class CloseAppHomeButton3ButtonPortrait : CloseAppHomeButton(Rotation.ROTATION_0) {
+    @ExpectedScenarios(["APP_CLOSE_TO_HOME"])
+    @Test
+    override fun closeAppHomeButton() = super.closeAppHomeButton()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppSwipeToHomeGesturalNavLandscape.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppSwipeToHomeGesturalNavLandscape.kt
new file mode 100644
index 0000000..8543015
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppSwipeToHomeGesturalNavLandscape.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.server.wm.flicker.service.close.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.close.scenarios.CloseAppSwipeToHome
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class CloseAppSwipeToHomeGesturalNavLandscape : CloseAppSwipeToHome(Rotation.ROTATION_90) {
+    @ExpectedScenarios(["APP_CLOSE_TO_HOME"])
+    @Test
+    override fun closeAppSwipeToHome() = super.closeAppSwipeToHome()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppSwipeToHomeGesturalNavPortrait.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppSwipeToHomeGesturalNavPortrait.kt
new file mode 100644
index 0000000..f88963b
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppSwipeToHomeGesturalNavPortrait.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.server.wm.flicker.service.close.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.close.scenarios.CloseAppSwipeToHome
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class CloseAppSwipeToHomeGesturalNavPortrait : CloseAppSwipeToHome(Rotation.ROTATION_0) {
+    @ExpectedScenarios(["APP_CLOSE_TO_HOME"])
+    @Test
+    override fun closeAppSwipeToHome() = super.closeAppSwipeToHome()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/scenarios/CloseAppBackButton.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/scenarios/CloseAppBackButton.kt
new file mode 100644
index 0000000..2aaacde
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/scenarios/CloseAppBackButton.kt
@@ -0,0 +1,61 @@
+/*
+ * 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.
+ */
+
+package com.android.server.wm.flicker.service.close.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.server.wm.flicker.service.Utils
+import org.junit.After
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Base Test Class")
+abstract class CloseAppBackButton(
+    val gestureMode: NavBar = NavBar.MODE_GESTURAL,
+    val rotation: Rotation = Rotation.ROTATION_0
+) {
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val tapl = LauncherInstrumentation()
+    private val wmHelper = WindowManagerStateHelper(instrumentation)
+    private val testApp = SimpleAppHelper(instrumentation)
+
+    @Rule @JvmField val testSetupRule = Utils.testSetupRule(gestureMode, rotation)
+
+    @Before
+    fun setup() {
+        tapl.setExpectedRotation(rotation.value)
+        testApp.launchViaIntent(wmHelper)
+    }
+
+    @Test
+    open fun closeAppBackButtonTest() {
+        tapl.pressBack()
+        wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
+    }
+
+    @After
+    fun teardown() {
+        testApp.exit(wmHelper)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/scenarios/CloseAppHomeButton.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/scenarios/CloseAppHomeButton.kt
new file mode 100644
index 0000000..08683a3
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/scenarios/CloseAppHomeButton.kt
@@ -0,0 +1,59 @@
+/*
+ * 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.
+ */
+
+package com.android.server.wm.flicker.service.close.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.server.wm.flicker.service.Utils
+import org.junit.After
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Base Test Class")
+abstract class CloseAppHomeButton(val rotation: Rotation = Rotation.ROTATION_0) {
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val tapl = LauncherInstrumentation()
+    private val wmHelper = WindowManagerStateHelper(instrumentation)
+    private val testApp = SimpleAppHelper(instrumentation)
+
+    @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_3BUTTON, rotation)
+
+    @Before
+    fun setup() {
+        tapl.setExpectedRotation(rotation.value)
+        testApp.launchViaIntent(wmHelper)
+        tapl.setExpectedRotationCheckEnabled(false)
+    }
+
+    @Test
+    open fun closeAppHomeButton() {
+        tapl.goHome()
+        wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
+    }
+
+    @After
+    fun teardown() {
+        testApp.exit(wmHelper)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/scenarios/CloseAppSwipeToHome.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/scenarios/CloseAppSwipeToHome.kt
new file mode 100644
index 0000000..360e114
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/scenarios/CloseAppSwipeToHome.kt
@@ -0,0 +1,59 @@
+/*
+ * 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.
+ */
+
+package com.android.server.wm.flicker.service.close.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.server.wm.flicker.service.Utils
+import org.junit.After
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Base Test Class")
+abstract class CloseAppSwipeToHome(val rotation: Rotation = Rotation.ROTATION_0) {
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val tapl = LauncherInstrumentation()
+    private val wmHelper = WindowManagerStateHelper(instrumentation)
+    private val testApp = SimpleAppHelper(instrumentation)
+
+    @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+
+    @Before
+    fun setup() {
+        tapl.setExpectedRotation(rotation.value)
+        testApp.launchViaIntent(wmHelper)
+        tapl.setExpectedRotationCheckEnabled(false)
+    }
+
+    @Test
+    open fun closeAppSwipeToHome() {
+        tapl.goHome()
+        wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
+    }
+
+    @After
+    fun teardown() {
+        testApp.exit(wmHelper)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationCold3ButtonNavLandscape.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationCold3ButtonNavLandscape.kt
new file mode 100644
index 0000000..bb18770
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationCold3ButtonNavLandscape.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.server.wm.flicker.service.notification.flicker
+
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.notification.scenarios.OpenAppFromLockscreenNotificationCold
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class OpenAppFromLockscreenNotificationCold3ButtonNavLandscape :
+    OpenAppFromLockscreenNotificationCold(NavBar.MODE_3BUTTON, Rotation.ROTATION_90) {
+    @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"])
+    @Test
+    override fun openAppFromLockscreenNotificationCold() =
+        super.openAppFromLockscreenNotificationCold()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationCold3ButtonNavPortrait.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationCold3ButtonNavPortrait.kt
new file mode 100644
index 0000000..1c3cc21
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationCold3ButtonNavPortrait.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.server.wm.flicker.service.notification.flicker
+
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.notification.scenarios.OpenAppFromLockscreenNotificationCold
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class OpenAppFromLockscreenNotificationCold3ButtonNavPortrait :
+    OpenAppFromLockscreenNotificationCold(NavBar.MODE_3BUTTON, Rotation.ROTATION_0) {
+    @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"])
+    @Test
+    override fun openAppFromLockscreenNotificationCold() =
+        super.openAppFromLockscreenNotificationCold()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationColdGesturalNavLandscape.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationColdGesturalNavLandscape.kt
new file mode 100644
index 0000000..46d36db
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationColdGesturalNavLandscape.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.server.wm.flicker.service.notification.flicker
+
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.notification.scenarios.OpenAppFromLockscreenNotificationCold
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class OpenAppFromLockscreenNotificationColdGesturalNavLandscape :
+    OpenAppFromLockscreenNotificationCold(NavBar.MODE_GESTURAL, Rotation.ROTATION_90) {
+    @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"])
+    @Test
+    override fun openAppFromLockscreenNotificationCold() =
+        super.openAppFromLockscreenNotificationCold()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationColdGesturalNavPortrait.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationColdGesturalNavPortrait.kt
new file mode 100644
index 0000000..f6a668fe
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationColdGesturalNavPortrait.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.server.wm.flicker.service.notification.flicker
+
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.notification.scenarios.OpenAppFromLockscreenNotificationCold
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class OpenAppFromLockscreenNotificationColdGesturalNavPortrait :
+    OpenAppFromLockscreenNotificationCold(NavBar.MODE_GESTURAL, Rotation.ROTATION_0) {
+    @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"])
+    @Test
+    override fun openAppFromLockscreenNotificationCold() =
+        super.openAppFromLockscreenNotificationCold()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWarm3ButtonNavLandscape.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWarm3ButtonNavLandscape.kt
new file mode 100644
index 0000000..93200ba
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWarm3ButtonNavLandscape.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.server.wm.flicker.service.notification.flicker
+
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.notification.scenarios.OpenAppFromLockscreenNotificationWarm
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class OpenAppFromLockscreenNotificationWarm3ButtonNavLandscape :
+    OpenAppFromLockscreenNotificationWarm(NavBar.MODE_3BUTTON, Rotation.ROTATION_90) {
+    @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"])
+    @Test
+    override fun openAppFromLockscreenNotificationWarm() =
+        super.openAppFromLockscreenNotificationWarm()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWarm3ButtonNavPortrait.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWarm3ButtonNavPortrait.kt
new file mode 100644
index 0000000..f5d41d2
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWarm3ButtonNavPortrait.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.server.wm.flicker.service.notification.flicker
+
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.notification.scenarios.OpenAppFromLockscreenNotificationWarm
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class OpenAppFromLockscreenNotificationWarm3ButtonNavPortrait :
+    OpenAppFromLockscreenNotificationWarm(NavBar.MODE_3BUTTON, Rotation.ROTATION_0) {
+    @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"])
+    @Test
+    override fun openAppFromLockscreenNotificationWarm() =
+        super.openAppFromLockscreenNotificationWarm()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWarmGesturalNavLandscape.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWarmGesturalNavLandscape.kt
new file mode 100644
index 0000000..28f322b
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWarmGesturalNavLandscape.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.server.wm.flicker.service.notification.flicker
+
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.notification.scenarios.OpenAppFromLockscreenNotificationWarm
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class OpenAppFromLockscreenNotificationWarmGesturalNavLandscape :
+    OpenAppFromLockscreenNotificationWarm(NavBar.MODE_GESTURAL, Rotation.ROTATION_90) {
+    @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"])
+    @Test
+    override fun openAppFromLockscreenNotificationWarm() =
+        super.openAppFromLockscreenNotificationWarm()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWarmGesturalNavPortrait.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWarmGesturalNavPortrait.kt
new file mode 100644
index 0000000..beb3fae
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWarmGesturalNavPortrait.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.server.wm.flicker.service.notification.flicker
+
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.notification.scenarios.OpenAppFromLockscreenNotificationWarm
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class OpenAppFromLockscreenNotificationWarmGesturalNavPortrait :
+    OpenAppFromLockscreenNotificationWarm(NavBar.MODE_GESTURAL, Rotation.ROTATION_0) {
+    @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"])
+    @Test
+    override fun openAppFromLockscreenNotificationWarm() =
+        super.openAppFromLockscreenNotificationWarm()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWithOverlayApp3ButtonNavLandscape.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWithOverlayApp3ButtonNavLandscape.kt
new file mode 100644
index 0000000..b34da72
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWithOverlayApp3ButtonNavLandscape.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.server.wm.flicker.service.notification.flicker
+
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.notification.scenarios.OpenAppFromLockscreenNotificationWithOverlayApp
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class OpenAppFromLockscreenNotificationWithOverlayApp3ButtonNavLandscape :
+    OpenAppFromLockscreenNotificationWithOverlayApp(NavBar.MODE_3BUTTON, Rotation.ROTATION_90) {
+    @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"])
+    @Test
+    override fun openAppFromLockscreenNotificationWithOverlayApp() =
+        super.openAppFromLockscreenNotificationWithOverlayApp()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWithOverlayApp3ButtonNavPortrait.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWithOverlayApp3ButtonNavPortrait.kt
new file mode 100644
index 0000000..b163897
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWithOverlayApp3ButtonNavPortrait.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.server.wm.flicker.service.notification.flicker
+
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.notification.scenarios.OpenAppFromLockscreenNotificationWithOverlayApp
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class OpenAppFromLockscreenNotificationWithOverlayApp3ButtonNavPortrait :
+    OpenAppFromLockscreenNotificationWithOverlayApp(NavBar.MODE_3BUTTON, Rotation.ROTATION_0) {
+    @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"])
+    @Test
+    override fun openAppFromLockscreenNotificationWithOverlayApp() =
+        super.openAppFromLockscreenNotificationWithOverlayApp()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWithOverlayAppGesturalNavLandscape.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWithOverlayAppGesturalNavLandscape.kt
new file mode 100644
index 0000000..19b533e
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWithOverlayAppGesturalNavLandscape.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.server.wm.flicker.service.notification.flicker
+
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.notification.scenarios.OpenAppFromLockscreenNotificationWithOverlayApp
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class OpenAppFromLockscreenNotificationWithOverlayAppGesturalNavLandscape :
+    OpenAppFromLockscreenNotificationWithOverlayApp(NavBar.MODE_GESTURAL, Rotation.ROTATION_90) {
+    @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"])
+    @Test
+    override fun openAppFromLockscreenNotificationWithOverlayApp() =
+        super.openAppFromLockscreenNotificationWithOverlayApp()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWithOverlayAppGesturalNavPortrait.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWithOverlayAppGesturalNavPortrait.kt
new file mode 100644
index 0000000..c9ed4f4
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWithOverlayAppGesturalNavPortrait.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.server.wm.flicker.service.notification.flicker
+
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.notification.scenarios.OpenAppFromLockscreenNotificationWithOverlayApp
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class OpenAppFromLockscreenNotificationWithOverlayAppGesturalNavPortrait :
+    OpenAppFromLockscreenNotificationWithOverlayApp(NavBar.MODE_GESTURAL, Rotation.ROTATION_0) {
+    @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"])
+    @Test
+    override fun openAppFromLockscreenNotificationWithOverlayApp() =
+        super.openAppFromLockscreenNotificationWithOverlayApp()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationCold3ButtonNavLandscape.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationCold3ButtonNavLandscape.kt
new file mode 100644
index 0000000..866190f
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationCold3ButtonNavLandscape.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.notification.flicker
+
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.notification.scenarios.OpenAppFromNotificationCold
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class OpenAppFromNotificationCold3ButtonNavLandscape :
+    OpenAppFromNotificationCold(NavBar.MODE_3BUTTON, Rotation.ROTATION_90) {
+    @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"])
+    @Test
+    override fun openAppFromNotificationCold() = super.openAppFromNotificationCold()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationCold3ButtonNavPortrait.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationCold3ButtonNavPortrait.kt
new file mode 100644
index 0000000..a8d6283
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationCold3ButtonNavPortrait.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.notification.flicker
+
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.notification.scenarios.OpenAppFromNotificationCold
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class OpenAppFromNotificationCold3ButtonNavPortrait :
+    OpenAppFromNotificationCold(NavBar.MODE_3BUTTON, Rotation.ROTATION_0) {
+    @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"])
+    @Test
+    override fun openAppFromNotificationCold() = super.openAppFromNotificationCold()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationColdGesturalNavLandscape.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationColdGesturalNavLandscape.kt
new file mode 100644
index 0000000..ef84c3f
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationColdGesturalNavLandscape.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.notification.flicker
+
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.notification.scenarios.OpenAppFromNotificationCold
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class OpenAppFromNotificationColdGesturalNavLandscape :
+    OpenAppFromNotificationCold(NavBar.MODE_GESTURAL, Rotation.ROTATION_90) {
+    @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"])
+    @Test
+    override fun openAppFromNotificationCold() = super.openAppFromNotificationCold()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationColdGesturalNavPortrait.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationColdGesturalNavPortrait.kt
new file mode 100644
index 0000000..5bb6b37
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationColdGesturalNavPortrait.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.notification.flicker
+
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.notification.scenarios.OpenAppFromNotificationCold
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class OpenAppFromNotificationColdGesturalNavPortrait :
+    OpenAppFromNotificationCold(NavBar.MODE_GESTURAL, Rotation.ROTATION_0) {
+    @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"])
+    @Test
+    override fun openAppFromNotificationCold() = super.openAppFromNotificationCold()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationWarm3ButtonNavLandscape.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationWarm3ButtonNavLandscape.kt
new file mode 100644
index 0000000..39f25e9
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationWarm3ButtonNavLandscape.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.notification.flicker
+
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.notification.scenarios.OpenAppFromNotificationWarm
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class OpenAppFromNotificationWarm3ButtonNavLandscape :
+    OpenAppFromNotificationWarm(NavBar.MODE_3BUTTON, Rotation.ROTATION_90) {
+    @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"])
+    @Test
+    override fun openAppFromNotificationWarm() = super.openAppFromNotificationWarm()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationWarm3ButtonNavPortrait.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationWarm3ButtonNavPortrait.kt
new file mode 100644
index 0000000..28816bc
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationWarm3ButtonNavPortrait.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.notification.flicker
+
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.notification.scenarios.OpenAppFromNotificationWarm
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class OpenAppFromNotificationWarm3ButtonNavPortrait :
+    OpenAppFromNotificationWarm(NavBar.MODE_3BUTTON, Rotation.ROTATION_0) {
+    @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"])
+    @Test
+    override fun openAppFromNotificationWarm() = super.openAppFromNotificationWarm()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationWarmGesturalNavLandscape.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationWarmGesturalNavLandscape.kt
new file mode 100644
index 0000000..94ffe4e
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationWarmGesturalNavLandscape.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.notification.flicker
+
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.notification.scenarios.OpenAppFromNotificationWarm
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class OpenAppFromNotificationWarmGesturalNavLandscape :
+    OpenAppFromNotificationWarm(NavBar.MODE_GESTURAL, Rotation.ROTATION_90) {
+    @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"])
+    @Test
+    override fun openAppFromNotificationWarm() = super.openAppFromNotificationWarm()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationWarmGesturalNavPortrait.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationWarmGesturalNavPortrait.kt
new file mode 100644
index 0000000..e5f5ec4
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationWarmGesturalNavPortrait.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.notification.flicker
+
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.notification.scenarios.OpenAppFromNotificationWarm
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class OpenAppFromNotificationWarmGesturalNavPortrait :
+    OpenAppFromNotificationWarm(NavBar.MODE_GESTURAL, Rotation.ROTATION_0) {
+    @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"])
+    @Test
+    override fun openAppFromNotificationWarm() = super.openAppFromNotificationWarm()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/scenarios/NotificationUtils.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/scenarios/NotificationUtils.kt
new file mode 100644
index 0000000..ac4e169
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/scenarios/NotificationUtils.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.server.wm.flicker.service.notification.scenarios
+
+import android.app.Instrumentation
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import android.view.WindowInsets
+import android.view.WindowManager
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.UiDevice
+import androidx.test.uiautomator.Until
+import com.android.launcher3.tapl.LauncherInstrumentation
+
+object NotificationUtils {
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val tapl = LauncherInstrumentation()
+    private val wmHelper = WindowManagerStateHelper(instrumentation)
+    private val device = UiDevice.getInstance(instrumentation)
+
+    fun openNotification(openingNotificationsFromLockScreen: Boolean) {
+        var startY = 10
+        var endY = 3 * device.displayHeight / 4
+        var steps = 25
+        if (openingNotificationsFromLockScreen) {
+            val wm: WindowManager =
+                instrumentation.context.getSystemService(WindowManager::class.java)
+                    ?: error("Unable to connect to WindowManager service")
+            val metricInsets = wm.currentWindowMetrics.windowInsets
+            val insets =
+                metricInsets.getInsetsIgnoringVisibility(
+                    WindowInsets.Type.statusBars() or WindowInsets.Type.displayCutout()
+                )
+
+            startY = insets.top + 100
+            endY = device.displayHeight / 2
+            steps = 4
+        }
+
+        // Swipe down to show the notification shade
+        val x = device.displayWidth / 2
+        device.swipe(x, startY, x, endY, steps)
+        device.waitForIdle(2000)
+        instrumentation.uiAutomation.syncInputTransactions()
+
+        // Launch the activity by clicking the notification
+        val notification =
+            device.wait(Until.findObject(By.text("Flicker Test Notification")), 2000L)
+        notification?.click() ?: error("Notification not found")
+        instrumentation.uiAutomation.syncInputTransactions()
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/scenarios/OpenAppFromLockscreenNotificationCold.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/scenarios/OpenAppFromLockscreenNotificationCold.kt
new file mode 100644
index 0000000..9c53ab3
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/scenarios/OpenAppFromLockscreenNotificationCold.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.notification.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.helpers.wakeUpAndGoToHomeScreen
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.NotificationAppHelper
+import com.android.server.wm.flicker.service.Utils
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+
+abstract class OpenAppFromLockscreenNotificationCold(
+    val gestureMode: NavBar = NavBar.MODE_GESTURAL,
+    val rotation: Rotation = Rotation.ROTATION_0
+) {
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val tapl = LauncherInstrumentation()
+    private val wmHelper = WindowManagerStateHelper(instrumentation)
+    private val device = UiDevice.getInstance(instrumentation)
+    private val testApp: NotificationAppHelper = NotificationAppHelper(instrumentation)
+
+    val openingNotificationsFromLockScreen = true
+
+    @Rule @JvmField val testSetupRule = Utils.testSetupRule(gestureMode, rotation)
+
+    @Before
+    fun setup() {
+        device.wakeUpAndGoToHomeScreen()
+        testApp.launchViaIntent(wmHelper)
+        wmHelper.StateSyncBuilder().withFullScreenApp(testApp).waitForAndVerify()
+        testApp.postNotification(wmHelper)
+        device.pressHome()
+        wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
+
+        // Close the app that posted the notification to trigger a cold start next time
+        // it is open - can't just kill it because that would remove the notification.
+        tapl.setExpectedRotationCheckEnabled(false)
+        tapl.goHome()
+        tapl.workspace.switchToOverview()
+        tapl.overview.dismissAllTasks()
+
+        device.sleep()
+        wmHelper.StateSyncBuilder().withoutTopVisibleAppWindows().waitForAndVerify()
+    }
+
+    @Test
+    open fun openAppFromLockscreenNotificationCold() {
+        device.wakeUp()
+
+        NotificationUtils.openNotification(openingNotificationsFromLockScreen)
+        // Wait for the app to launch
+        wmHelper.StateSyncBuilder().withFullScreenApp(testApp).waitForAndVerify()
+    }
+
+    @After
+    fun teardown() {
+        testApp.exit(wmHelper)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/scenarios/OpenAppFromLockscreenNotificationWarm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/scenarios/OpenAppFromLockscreenNotificationWarm.kt
new file mode 100644
index 0000000..31f55d9
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/scenarios/OpenAppFromLockscreenNotificationWarm.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.notification.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.helpers.wakeUpAndGoToHomeScreen
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.NotificationAppHelper
+import com.android.server.wm.flicker.service.Utils
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+
+abstract class OpenAppFromLockscreenNotificationWarm(
+    val gestureMode: NavBar = NavBar.MODE_GESTURAL,
+    val rotation: Rotation = Rotation.ROTATION_0
+) {
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val tapl = LauncherInstrumentation()
+    private val wmHelper = WindowManagerStateHelper(instrumentation)
+    private val device = UiDevice.getInstance(instrumentation)
+    private val testApp: NotificationAppHelper = NotificationAppHelper(instrumentation)
+
+    private val openingNotificationsFromLockScreen = true
+
+    @Rule @JvmField val testSetupRule = Utils.testSetupRule(gestureMode, rotation)
+
+    @Before
+    fun setup() {
+        device.wakeUpAndGoToHomeScreen()
+        testApp.launchViaIntent(wmHelper)
+        wmHelper.StateSyncBuilder().withFullScreenApp(testApp).waitForAndVerify()
+        testApp.postNotification(wmHelper)
+        device.pressHome()
+        wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
+
+        device.sleep()
+        wmHelper.StateSyncBuilder().withoutTopVisibleAppWindows().waitForAndVerify()
+    }
+
+    @Test
+    open fun openAppFromLockscreenNotificationWarm() {
+        device.wakeUp()
+
+        NotificationUtils.openNotification(openingNotificationsFromLockScreen)
+        // Wait for the app to launch
+        wmHelper.StateSyncBuilder().withFullScreenApp(testApp).waitForAndVerify()
+    }
+
+    @After
+    fun teardown() {
+        testApp.exit(wmHelper)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/scenarios/OpenAppFromLockscreenNotificationWithOverlayApp.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/scenarios/OpenAppFromLockscreenNotificationWithOverlayApp.kt
new file mode 100644
index 0000000..b971555
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/scenarios/OpenAppFromLockscreenNotificationWithOverlayApp.kt
@@ -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.server.wm.flicker.service.notification.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.helpers.wakeUpAndGoToHomeScreen
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.NotificationAppHelper
+import com.android.server.wm.flicker.helpers.ShowWhenLockedAppHelper
+import com.android.server.wm.flicker.service.Utils
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+
+abstract class OpenAppFromLockscreenNotificationWithOverlayApp(
+    val gestureMode: NavBar = NavBar.MODE_GESTURAL,
+    val rotation: Rotation = Rotation.ROTATION_0
+) {
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val tapl = LauncherInstrumentation()
+    private val wmHelper = WindowManagerStateHelper(instrumentation)
+    private val device = UiDevice.getInstance(instrumentation)
+    private val showWhenLockedApp = ShowWhenLockedAppHelper(instrumentation)
+    private val testApp: NotificationAppHelper = NotificationAppHelper(instrumentation)
+
+    // Although we are technically still locked here, the overlay app means we should open the
+    // notification shade as if we were unlocked.
+    private val openingNotificationsFromLockScreen = false
+
+    @Rule @JvmField val testSetupRule = Utils.testSetupRule(gestureMode, rotation)
+
+    @Before
+    fun setup() {
+        device.wakeUpAndGoToHomeScreen()
+        testApp.launchViaIntent(wmHelper)
+        wmHelper.StateSyncBuilder().withFullScreenApp(testApp).waitForAndVerify()
+        testApp.postNotification(wmHelper)
+        device.pressHome()
+        wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
+
+        // Close the app that posted the notification to trigger a cold start next time
+        // it is open - can't just kill it because that would remove the notification.
+        tapl.setExpectedRotationCheckEnabled(false)
+        tapl.goHome()
+        tapl.workspace.switchToOverview()
+        tapl.overview.dismissAllTasks()
+
+        device.sleep()
+        wmHelper.StateSyncBuilder().withoutTopVisibleAppWindows().waitForAndVerify()
+
+        device.wakeUpAndGoToHomeScreen()
+
+        // Launch an activity that is shown when the device is locked
+        showWhenLockedApp.launchViaIntent(wmHelper)
+        wmHelper.StateSyncBuilder().withFullScreenApp(showWhenLockedApp).waitForAndVerify()
+
+        device.sleep()
+        wmHelper.StateSyncBuilder().withoutTopVisibleAppWindows().waitForAndVerify()
+    }
+
+    @Test
+    open fun openAppFromLockscreenNotificationWithOverlayApp() {
+        device.wakeUp()
+        NotificationUtils.openNotification(openingNotificationsFromLockScreen)
+        // Wait for the app to launch
+        wmHelper.StateSyncBuilder().withFullScreenApp(testApp).waitForAndVerify()
+    }
+
+    @After
+    fun teardown() {
+        testApp.exit(wmHelper)
+        showWhenLockedApp.exit(wmHelper)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/scenarios/OpenAppFromNotificationCold.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/scenarios/OpenAppFromNotificationCold.kt
new file mode 100644
index 0000000..fed077e
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/scenarios/OpenAppFromNotificationCold.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.server.wm.flicker.service.notification.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.helpers.wakeUpAndGoToHomeScreen
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.NotificationAppHelper
+import com.android.server.wm.flicker.service.Utils
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+
+abstract class OpenAppFromNotificationCold(
+    val gestureMode: NavBar = NavBar.MODE_GESTURAL,
+    val rotation: Rotation = Rotation.ROTATION_0
+) {
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val tapl = LauncherInstrumentation()
+    private val device = UiDevice.getInstance(instrumentation)
+    private val wmHelper = WindowManagerStateHelper(instrumentation)
+    private val testApp: NotificationAppHelper = NotificationAppHelper(instrumentation)
+
+    @Rule @JvmField val testSetupRule = Utils.testSetupRule(gestureMode, rotation)
+
+    private val openingNotificationsFromLockScreen = false
+
+    @Before
+    fun setup() {
+        device.wakeUpAndGoToHomeScreen()
+        testApp.launchViaIntent(wmHelper)
+        wmHelper.StateSyncBuilder().withFullScreenApp(testApp).waitForAndVerify()
+        testApp.postNotification(wmHelper)
+        device.pressHome()
+        wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
+
+        // Close the app that posted the notification to trigger a cold start next time
+        // it is open - can't just kill it because that would remove the notification.
+        tapl.setExpectedRotationCheckEnabled(false)
+        tapl.goHome()
+        tapl.workspace.switchToOverview()
+        tapl.overview.dismissAllTasks()
+    }
+
+    @Test
+    open fun openAppFromNotificationCold() {
+        NotificationUtils.openNotification(openingNotificationsFromLockScreen)
+        // Wait for the app to launch
+        wmHelper.StateSyncBuilder().withFullScreenApp(testApp).waitForAndVerify()
+    }
+
+    @After
+    fun teardown() {
+        testApp.exit(wmHelper)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/scenarios/OpenAppFromNotificationWarm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/scenarios/OpenAppFromNotificationWarm.kt
new file mode 100644
index 0000000..403790e
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/scenarios/OpenAppFromNotificationWarm.kt
@@ -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.server.wm.flicker.service.notification.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.helpers.wakeUpAndGoToHomeScreen
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.NotificationAppHelper
+import com.android.server.wm.flicker.service.Utils
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+
+abstract class OpenAppFromNotificationWarm(
+    val gestureMode: NavBar = NavBar.MODE_GESTURAL,
+    val rotation: Rotation = Rotation.ROTATION_0
+) {
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val tapl = LauncherInstrumentation()
+    private val device = UiDevice.getInstance(instrumentation)
+    private val wmHelper = WindowManagerStateHelper(instrumentation)
+    private val testApp: NotificationAppHelper = NotificationAppHelper(instrumentation)
+
+    private val openingNotificationsFromLockScreen = false
+
+    @Rule @JvmField val testSetupRule = Utils.testSetupRule(gestureMode, rotation)
+
+    @Before
+    fun setup() {
+        device.wakeUpAndGoToHomeScreen()
+        testApp.launchViaIntent(wmHelper)
+        wmHelper.StateSyncBuilder().withFullScreenApp(testApp).waitForAndVerify()
+        testApp.postNotification(wmHelper)
+        device.pressHome()
+        wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
+    }
+
+    @Test
+    open fun openAppFromNotificationWarm() {
+        NotificationUtils.openNotification(openingNotificationsFromLockScreen)
+        // Wait for the app to launch
+        wmHelper.StateSyncBuilder().withFullScreenApp(testApp).waitForAndVerify()
+    }
+
+    @After
+    fun teardown() {
+        testApp.exit(wmHelper)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/flicker/QuickSwitchBetweenTwoAppsBackGesturalNavLandscape.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/flicker/QuickSwitchBetweenTwoAppsBackGesturalNavLandscape.kt
new file mode 100644
index 0000000..d611a42
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/flicker/QuickSwitchBetweenTwoAppsBackGesturalNavLandscape.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.server.wm.flicker.service.quickswitch.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.quickswitch.scenarios.QuickSwitchBetweenTwoAppsBack
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class QuickSwitchBetweenTwoAppsBackGesturalNavLandscape :
+    QuickSwitchBetweenTwoAppsBack(Rotation.ROTATION_90) {
+    @ExpectedScenarios(["QUICKSWITCH"])
+    @Test
+    override fun quickSwitchBetweenTwoAppsBack() = super.quickSwitchBetweenTwoAppsBack()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/flicker/QuickSwitchBetweenTwoAppsBackGesturalNavPortrait.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/flicker/QuickSwitchBetweenTwoAppsBackGesturalNavPortrait.kt
new file mode 100644
index 0000000..e6bcbba
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/flicker/QuickSwitchBetweenTwoAppsBackGesturalNavPortrait.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.server.wm.flicker.service.quickswitch.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.quickswitch.scenarios.QuickSwitchBetweenTwoAppsBack
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class QuickSwitchBetweenTwoAppsBackGesturalNavPortrait :
+    QuickSwitchBetweenTwoAppsBack(Rotation.ROTATION_0) {
+    @ExpectedScenarios(["QUICKSWITCH"])
+    @Test
+    override fun quickSwitchBetweenTwoAppsBack() = super.quickSwitchBetweenTwoAppsBack()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/flicker/QuickSwitchBetweenTwoAppsForwardGesturalNavLandscape.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/flicker/QuickSwitchBetweenTwoAppsForwardGesturalNavLandscape.kt
new file mode 100644
index 0000000..2a26d63
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/flicker/QuickSwitchBetweenTwoAppsForwardGesturalNavLandscape.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.server.wm.flicker.service.quickswitch.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.quickswitch.scenarios.QuickSwitchBetweenTwoAppsForward
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class QuickSwitchBetweenTwoAppsForwardGesturalNavLandscape :
+    QuickSwitchBetweenTwoAppsForward(Rotation.ROTATION_90) {
+    @ExpectedScenarios(["QUICKSWITCH"])
+    @Test
+    override fun quickSwitchBetweenTwoAppsForward() = super.quickSwitchBetweenTwoAppsForward()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/flicker/QuickSwitchBetweenTwoAppsForwardGesturalNavPortrait.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/flicker/QuickSwitchBetweenTwoAppsForwardGesturalNavPortrait.kt
new file mode 100644
index 0000000..5ce7143
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/flicker/QuickSwitchBetweenTwoAppsForwardGesturalNavPortrait.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.server.wm.flicker.service.quickswitch.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.quickswitch.scenarios.QuickSwitchBetweenTwoAppsForward
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class QuickSwitchBetweenTwoAppsForwardGesturalNavPortrait :
+    QuickSwitchBetweenTwoAppsForward(Rotation.ROTATION_0) {
+    @ExpectedScenarios(["QUICKSWITCH"])
+    @Test
+    override fun quickSwitchBetweenTwoAppsForward() = super.quickSwitchBetweenTwoAppsForward()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/flicker/QuickSwitchFromLauncherGesturalNavLandscape.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/flicker/QuickSwitchFromLauncherGesturalNavLandscape.kt
new file mode 100644
index 0000000..cff47bb
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/flicker/QuickSwitchFromLauncherGesturalNavLandscape.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.server.wm.flicker.service.quickswitch.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.quickswitch.scenarios.QuickSwitchFromLauncher
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class QuickSwitchFromLauncherGesturalNavLandscape : QuickSwitchFromLauncher(Rotation.ROTATION_90) {
+    @ExpectedScenarios(["QUICKSWITCH"])
+    @Test
+    override fun quickSwitchFromLauncher() = super.quickSwitchFromLauncher()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/flicker/QuickSwitchFromLauncherGesturalNavPortrait.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/flicker/QuickSwitchFromLauncherGesturalNavPortrait.kt
new file mode 100644
index 0000000..33095a6
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/flicker/QuickSwitchFromLauncherGesturalNavPortrait.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.server.wm.flicker.service.quickswitch.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.quickswitch.scenarios.QuickSwitchFromLauncher
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class QuickSwitchFromLauncherGesturalNavPortrait : QuickSwitchFromLauncher(Rotation.ROTATION_0) {
+    @ExpectedScenarios(["QUICKSWITCH"])
+    @Test
+    override fun quickSwitchFromLauncher() = super.quickSwitchFromLauncher()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/scenarios/QuickSwitchBetweenTwoAppsBack.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/scenarios/QuickSwitchBetweenTwoAppsBack.kt
new file mode 100644
index 0000000..93130c4
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/scenarios/QuickSwitchBetweenTwoAppsBack.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.flicker.service.quickswitch.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.server.wm.flicker.service.Utils
+import org.junit.After
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Base Test Class")
+abstract class QuickSwitchBetweenTwoAppsBack(val rotation: Rotation = Rotation.ROTATION_0) {
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val tapl = LauncherInstrumentation()
+    private val wmHelper = WindowManagerStateHelper(instrumentation)
+    private val testApp1 = SimpleAppHelper(instrumentation)
+    private val testApp2 = NonResizeableAppHelper(instrumentation)
+
+    @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+
+    @Before
+    fun setup() {
+        tapl.setExpectedRotation(rotation.value)
+        tapl.setIgnoreTaskbarVisibility(true)
+        testApp1.launchViaIntent(wmHelper)
+        testApp2.launchViaIntent(wmHelper)
+    }
+
+    @Test
+    open fun quickSwitchBetweenTwoAppsBack() {
+        tapl.launchedAppState.quickSwitchToPreviousApp()
+        wmHelper
+            .StateSyncBuilder()
+            .withFullScreenApp(testApp1)
+            .withNavOrTaskBarVisible()
+            .withStatusBarVisible()
+            .waitForAndVerify()
+    }
+
+    @After
+    fun teardown() {
+        testApp1.exit(wmHelper)
+        testApp2.exit(wmHelper)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/scenarios/QuickSwitchBetweenTwoAppsForward.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/scenarios/QuickSwitchBetweenTwoAppsForward.kt
new file mode 100644
index 0000000..4941eea
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/scenarios/QuickSwitchBetweenTwoAppsForward.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.flicker.service.quickswitch.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.server.wm.flicker.service.Utils
+import org.junit.After
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Base Test Class")
+abstract class QuickSwitchBetweenTwoAppsForward(val rotation: Rotation = Rotation.ROTATION_0) {
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val tapl = LauncherInstrumentation()
+    private val wmHelper = WindowManagerStateHelper(instrumentation)
+    private val testApp1 = SimpleAppHelper(instrumentation)
+    private val testApp2 = NonResizeableAppHelper(instrumentation)
+
+    @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+
+    @Before
+    fun setup() {
+        tapl.setExpectedRotation(rotation.value)
+
+        testApp1.launchViaIntent(wmHelper)
+        testApp2.launchViaIntent(wmHelper)
+        tapl.launchedAppState.quickSwitchToPreviousApp()
+        wmHelper
+            .StateSyncBuilder()
+            .withFullScreenApp(testApp1)
+            .withNavOrTaskBarVisible()
+            .withStatusBarVisible()
+            .waitForAndVerify()
+    }
+
+    @Test
+    open fun quickSwitchBetweenTwoAppsForward() {
+        tapl.launchedAppState.quickSwitchToPreviousAppSwipeLeft()
+        wmHelper
+            .StateSyncBuilder()
+            .withFullScreenApp(testApp2)
+            .withNavOrTaskBarVisible()
+            .withStatusBarVisible()
+            .waitForAndVerify()
+    }
+
+    @After
+    fun teardown() {
+        testApp1.exit(wmHelper)
+        testApp2.exit(wmHelper)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/scenarios/QuickSwitchFromLauncher.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/scenarios/QuickSwitchFromLauncher.kt
new file mode 100644
index 0000000..7afe526
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/scenarios/QuickSwitchFromLauncher.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.flicker.service.quickswitch.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.server.wm.flicker.service.Utils
+import org.junit.After
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Base Test Class")
+abstract class QuickSwitchFromLauncher(val rotation: Rotation = Rotation.ROTATION_0) {
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val tapl = LauncherInstrumentation()
+    private val wmHelper = WindowManagerStateHelper(instrumentation)
+    private val testApp = SimpleAppHelper(instrumentation)
+
+    @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+
+    @Before
+    fun setup() {
+        tapl.setExpectedRotationCheckEnabled(false)
+
+        tapl.setExpectedRotation(rotation.value)
+
+        testApp.launchViaIntent(wmHelper)
+        tapl.goHome()
+        wmHelper
+            .StateSyncBuilder()
+            .withHomeActivityVisible()
+            .withWindowSurfaceDisappeared(testApp)
+            .waitForAndVerify()
+    }
+
+    @Test
+    open fun quickSwitchFromLauncher() {
+        tapl.workspace.quickSwitchToPreviousApp()
+        wmHelper
+            .StateSyncBuilder()
+            .withFullScreenApp(testApp)
+            .withNavOrTaskBarVisible()
+            .withStatusBarVisible()
+            .waitForAndVerify()
+    }
+
+    @After
+    fun teardown() {
+        testApp.exit(wmHelper)
+    }
+}
diff --git a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
index 1ec9ec9..ff9799a 100644
--- a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
@@ -22,7 +22,9 @@
     <application android:allowBackup="false"
                  android:supportsRtl="true">
         <uses-library android:name="androidx.window.extensions" android:required="false"/>
-
+        <property
+            android:name="android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED"
+            android:value="true" />
         <activity android:name=".SimpleActivity"
                   android:taskAffinity="com.android.server.wm.flicker.testapp.SimpleActivity"
                   android:theme="@style/CutoutShortEdges"
@@ -100,6 +102,24 @@
                 <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
+        <activity android:name=".LaunchTransparentActivity"
+                  android:resizeableActivity="false"
+                  android:screenOrientation="portrait"
+                  android:theme="@android:style/Theme"
+                  android:taskAffinity="com.android.server.wm.flicker.testapp.LaunchTransparentActivity"
+                  android:label="LaunchTransparentActivity"
+                  android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+        <activity android:name=".TransparentActivity"
+                  android:theme="@style/TransparentTheme"
+                  android:taskAffinity="com.android.server.wm.flicker.testapp.TransparentActivity"
+                  android:label="TransparentActivity"
+                  android:exported="false">
+        </activity>
         <activity android:name=".LaunchNewActivity"
                   android:taskAffinity="com.android.server.wm.flicker.testapp.LaunchNewActivity"
                   android:theme="@style/CutoutShortEdges"
@@ -191,11 +211,34 @@
             </intent-filter>
         </activity>
         <activity
+            android:name=".ActivityEmbeddingTrampolineActivity"
+            android:label="ActivityEmbedding Trampoline"
+            android:taskAffinity="com.android.server.wm.flicker.testapp.ActivityEmbedding"
+            android:theme="@style/CutoutShortEdges"
+            android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
+            android:exported="false">
+        </activity>
+        <activity
             android:name=".ActivityEmbeddingSecondaryActivity"
             android:label="ActivityEmbedding Secondary"
             android:taskAffinity="com.android.server.wm.flicker.testapp.ActivityEmbedding"
             android:theme="@style/CutoutShortEdges"
             android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
+            android:supportsPictureInPicture="true"
+            android:exported="false"/>
+        <activity
+            android:name=".ActivityEmbeddingThirdActivity"
+            android:label="ActivityEmbedding Third"
+            android:taskAffinity="com.android.server.wm.flicker.testapp.ActivityEmbedding"
+            android:theme="@style/CutoutShortEdges"
+            android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
+            android:exported="false"/>
+        <activity
+            android:name=".ActivityEmbeddingAlwaysExpandActivity"
+            android:label="ActivityEmbedding AlwaysExpand"
+            android:taskAffinity="com.android.server.wm.flicker.testapp.ActivityEmbedding"
+            android:theme="@style/CutoutShortEdges"
+            android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
             android:exported="false"/>
         <activity
             android:name=".ActivityEmbeddingPlaceholderPrimaryActivity"
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_base_layout.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_base_layout.xml
index 3a02cad..f0dfdfc 100644
--- a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_base_layout.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_base_layout.xml
@@ -20,5 +20,4 @@
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:orientation="vertical">
-
 </LinearLayout>
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_main_layout.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_main_layout.xml
index d78b9a8..e32a709 100644
--- a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_main_layout.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_main_layout.xml
@@ -25,16 +25,47 @@
         android:id="@+id/launch_secondary_activity_button"
         android:layout_width="wrap_content"
         android:layout_height="48dp"
-        android:layout_centerHorizontal="true"
         android:onClick="launchSecondaryActivity"
+        android:tag="LEFT_TO_RIGHT"
         android:text="Launch Secondary Activity" />
 
     <Button
+        android:id="@+id/launch_secondary_activity_rtl_button"
+        android:layout_width="wrap_content"
+        android:layout_height="48dp"
+        android:onClick="launchSecondaryActivity"
+        android:tag="RIGHT_TO_LEFT"
+        android:text="Launch Secondary Activity in RTL" />
+
+    <Button
         android:id="@+id/launch_placeholder_split_button"
         android:layout_width="wrap_content"
         android:layout_height="48dp"
-        android:layout_centerHorizontal="true"
         android:onClick="launchPlaceholderSplit"
+        android:tag="LEFT_TO_RIGHT"
         android:text="Launch Placeholder Split" />
 
+    <Button
+        android:id="@+id/launch_always_expand_activity_button"
+        android:layout_width="wrap_content"
+        android:layout_height="48dp"
+        android:onClick="launchAlwaysExpandActivity"
+        android:text="Launch Always Expand Activity" />
+
+    <Button
+        android:id="@+id/launch_placeholder_split_rtl_button"
+        android:layout_width="wrap_content"
+        android:layout_height="48dp"
+        android:onClick="launchPlaceholderSplit"
+        android:tag="RIGHT_TO_LEFT"
+        android:text="Launch Placeholder Split in RTL" />
+
+    <Button
+        android:id="@+id/launch_trampoline_button"
+        android:layout_width="wrap_content"
+        android:layout_height="48dp"
+        android:onClick="launchTrampolineActivity"
+        android:tag="LEFT_TO_RIGHT"
+        android:text="Launch Trampoline Activity" />
+
 </LinearLayout>
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_secondary_activity_layout.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_secondary_activity_layout.xml
new file mode 100644
index 0000000..135140a
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_secondary_activity_layout.xml
@@ -0,0 +1,44 @@
+<?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.
+-->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/secondary_activity_layout"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+
+  <Button
+      android:id="@+id/finish_secondary_activity_button"
+      android:layout_width="wrap_content"
+      android:layout_height="48dp"
+      android:text="Finish" />
+
+  <Button
+      android:id="@+id/launch_third_activity_button"
+      android:layout_width="wrap_content"
+      android:layout_height="48dp"
+      android:layout_centerHorizontal="true"
+      android:onClick="launchThirdActivity"
+      android:text="Launch a third activity" />
+
+  <Button
+      android:id="@+id/secondary_enter_pip_button"
+      android:layout_width="wrap_content"
+      android:layout_height="48dp"
+      android:text="Enter pip" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_transparent.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_transparent.xml
new file mode 100644
index 0000000..0730ded
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_transparent.xml
@@ -0,0 +1,21 @@
+<?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.
+  -->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+</FrameLayout>
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_transparent_launch.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_transparent_launch.xml
new file mode 100644
index 0000000..ff4ead9
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_transparent_launch.xml
@@ -0,0 +1,37 @@
+<?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.
+  -->
+<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/black">
+
+        <Button
+            android:id="@+id/button_launch_transparent"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_centerHorizontal="true"
+            android:layout_centerVertical="true"
+            android:text="Launch Transparent" />
+        <Button
+            android:id="@+id/button_request_permission"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_centerHorizontal="true"
+            android:layout_centerVertical="true"
+            android:text="Request Permission" />
+</LinearLayout>
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml b/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml
index 1d21fd5..e51ed29 100644
--- a/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml
@@ -43,6 +43,13 @@
         <item name="android:windowSoftInputMode">stateUnchanged</item>
     </style>
 
+    <style name="TransparentTheme" parent="@android:style/Theme.DeviceDefault">
+        <item name="android:windowIsTranslucent">true</item>
+        <item name="android:windowBackground">@android:color/transparent</item>
+        <item name="android:windowContentOverlay">@null</item>
+        <item name="android:backgroundDimEnabled">false</item>
+    </style>
+
     <style name="no_starting_window" parent="@android:style/Theme.DeviceDefault">
         <item name="android:windowDisablePreview">true</item>
     </style>
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingAlwaysExpandActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingAlwaysExpandActivity.java
new file mode 100644
index 0000000..d9b24ed
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingAlwaysExpandActivity.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 com.android.server.wm.flicker.testapp;
+
+import android.app.Activity;
+import android.graphics.Color;
+import android.os.Bundle;
+
+/**
+ * Activity with alwaysExpand=true (launched via R.id.launch_always_expand_activity_button)
+ */
+public class ActivityEmbeddingAlwaysExpandActivity extends Activity {
+  @Override
+  public void onCreate(Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+    setContentView(R.layout.activity_embedding_base_layout);
+    findViewById(R.id.root_activity_layout).setBackgroundColor(Color.GREEN);
+  }
+
+}
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingMainActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingMainActivity.java
index 6a7a2cc..3b1a859 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingMainActivity.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingMainActivity.java
@@ -16,87 +16,145 @@
 
 package com.android.server.wm.flicker.testapp;
 
+import androidx.annotation.NonNull;
 import android.app.Activity;
 import android.content.Intent;
 import android.os.Bundle;
-import android.util.ArraySet;
-import android.util.Log;
 import android.view.View;
 
-import androidx.window.extensions.embedding.ActivityEmbeddingComponent;
-import androidx.window.extensions.embedding.EmbeddingRule;
-import androidx.window.extensions.embedding.SplitPairRule;
-import androidx.window.extensions.embedding.SplitPlaceholderRule;
+import androidx.window.embedding.ActivityFilter;
+import androidx.window.embedding.ActivityRule;
+import androidx.window.embedding.EmbeddingAspectRatio;
+import androidx.window.embedding.RuleController;
+import androidx.window.embedding.SplitAttributes;
+import androidx.window.embedding.SplitAttributes.LayoutDirection;
+import androidx.window.embedding.SplitController;
+import androidx.window.embedding.SplitPairFilter;
+import androidx.window.embedding.SplitPairRule;
+import androidx.window.embedding.SplitPlaceholderRule;
+import androidx.window.embedding.SplitRule;
 
-import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper;
-
+import java.util.HashSet;
 import java.util.Set;
 
 /** Main activity of the ActivityEmbedding test app to launch other embedding activities. */
 public class ActivityEmbeddingMainActivity extends Activity {
     private static final String TAG = "ActivityEmbeddingMainActivity";
     private static final float DEFAULT_SPLIT_RATIO = 0.5f;
+    private RuleController mRuleController;
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         setContentView(R.layout.activity_embedding_main_layout);
+        final SplitController.SplitSupportStatus status = SplitController.getInstance(
+                this).getSplitSupportStatus();
+        if (status != SplitController.SplitSupportStatus.SPLIT_AVAILABLE) {
+            throw new RuntimeException(
+                    "Unable to initiate SplitController in ActivityEmbeddingMainActivity, "
+                            + "splitSupportStatus = " + status);
+        }
+        mRuleController = RuleController.getInstance(this);
+    }
+
+    /** R.id.launch_trampoline_button onClick */
+    public void launchTrampolineActivity(View view) {
+        final String layoutDirection = view.getTag().toString();
+        mRuleController.clearRules();
+        mRuleController.addRule(createSplitPairRules(layoutDirection));
+        startActivity(new Intent().setComponent(
+                ActivityOptions.ActivityEmbedding.TrampolineActivity.COMPONENT));
     }
 
     /** R.id.launch_secondary_activity_button onClick */
     public void launchSecondaryActivity(View view) {
-        initializeSplitRules(createSplitPairRules());
+        final String layoutDirection = view.getTag().toString();
+        mRuleController.clearRules();
+        mRuleController.addRule(createSplitPairRules(layoutDirection));
         startActivity(new Intent().setComponent(
                 ActivityOptions.ActivityEmbedding.SecondaryActivity.COMPONENT));
     }
 
+    /** R.id.launch_always_expand_activity_button onClick */
+    public void launchAlwaysExpandActivity(View view) {
+        final Set<ActivityFilter> activityFilters = new HashSet<>();
+        activityFilters.add(
+                new ActivityFilter(ActivityOptions.ActivityEmbedding.AlwaysExpandActivity.COMPONENT,
+                        null));
+        final ActivityRule activityRule = new ActivityRule.Builder(activityFilters)
+                .setAlwaysExpand(true)
+                .build();
+
+        RuleController rc = RuleController.getInstance(this);
+
+        rc.addRule(activityRule);
+        startActivity(new Intent().setComponent(
+                ActivityOptions.ActivityEmbedding.AlwaysExpandActivity.COMPONENT));
+    }
+
     /** R.id.launch_placeholder_split_button onClick */
     public void launchPlaceholderSplit(View view) {
-        initializeSplitRules(createSplitPlaceholderRules());
+        final String layoutDirection = view.getTag().toString();
+        mRuleController.clearRules();
+        mRuleController.addRule(createSplitPlaceholderRules(layoutDirection));
         startActivity(new Intent().setComponent(
                 ActivityOptions.ActivityEmbedding.PlaceholderPrimaryActivity.COMPONENT));
     }
 
-    private void initializeSplitRules(Set<EmbeddingRule> rules) {
-        ActivityEmbeddingComponent embeddingComponent =
-                ActivityEmbeddingAppHelper.getActivityEmbeddingComponent();
-        if (embeddingComponent == null) {
-            // Embedding not supported
-            Log.d(TAG, "ActivityEmbedding is not supported on this device");
-            finish();
-            return;
+    private static SplitPairRule createSplitPairRules(@NonNull String layoutDirection) {
+        final Set<SplitPairFilter> pairFilters = new HashSet<>();
+        final SplitPairFilter activitiesPair = new SplitPairFilter(
+                ActivityOptions.ActivityEmbedding.MainActivity.COMPONENT,
+                ActivityOptions.ActivityEmbedding.SecondaryActivity.COMPONENT,
+                null /* secondaryActivityIntentAction */);
+        pairFilters.add(activitiesPair);
+        final SplitAttributes splitAttributes = new SplitAttributes.Builder()
+                .setSplitType(SplitAttributes.SplitType.SPLIT_TYPE_EQUAL)
+                .setLayoutDirection(parseLayoutDirection(layoutDirection))
+                .build();
+        // Setting thresholds to ALWAYS_ALLOW values to make it easy for running on all devices.
+        final SplitPairRule rule = new SplitPairRule.Builder(pairFilters)
+                .setDefaultSplitAttributes(splitAttributes)
+                .setMinWidthDp(SplitRule.SPLIT_MIN_DIMENSION_ALWAYS_ALLOW)
+                .setMinHeightDp(SplitRule.SPLIT_MIN_DIMENSION_ALWAYS_ALLOW)
+                .setMinSmallestWidthDp(SplitRule.SPLIT_MIN_DIMENSION_ALWAYS_ALLOW)
+                .setMaxAspectRatioInPortrait(EmbeddingAspectRatio.ALWAYS_ALLOW)
+                .setMaxAspectRatioInLandscape(EmbeddingAspectRatio.ALWAYS_ALLOW)
+                .build();
+        return rule;
+    }
+
+    private static SplitPlaceholderRule createSplitPlaceholderRules(
+            @NonNull String layoutDirection) {
+        final Set<ActivityFilter> activityFilters = new HashSet<>();
+        activityFilters.add(new ActivityFilter(
+                ActivityOptions.ActivityEmbedding.PlaceholderPrimaryActivity.COMPONENT,
+                null /* intentAction */));
+        final Intent intent = new Intent();
+        intent.setComponent(
+                ActivityOptions.ActivityEmbedding.PlaceholderSecondaryActivity.COMPONENT);
+        final SplitAttributes splitAttributes = new SplitAttributes.Builder()
+                .setSplitType(SplitAttributes.SplitType.SPLIT_TYPE_EQUAL)
+                .setLayoutDirection(parseLayoutDirection(layoutDirection))
+                .build();
+        final SplitPlaceholderRule rule = new SplitPlaceholderRule.Builder(activityFilters, intent)
+                .setDefaultSplitAttributes(splitAttributes)
+                .setMinWidthDp(SplitRule.SPLIT_MIN_DIMENSION_ALWAYS_ALLOW)
+                .setMinHeightDp(SplitRule.SPLIT_MIN_DIMENSION_ALWAYS_ALLOW)
+                .setMinSmallestWidthDp(SplitRule.SPLIT_MIN_DIMENSION_ALWAYS_ALLOW)
+                .setMaxAspectRatioInPortrait(EmbeddingAspectRatio.ALWAYS_ALLOW)
+                .setMaxAspectRatioInLandscape(EmbeddingAspectRatio.ALWAYS_ALLOW)
+                .build();
+        return rule;
+    }
+
+    private static LayoutDirection parseLayoutDirection(@NonNull String layoutDirectionStr) {
+        if (layoutDirectionStr.equals(LayoutDirection.LEFT_TO_RIGHT.toString())) {
+            return LayoutDirection.LEFT_TO_RIGHT;
         }
-        embeddingComponent.setEmbeddingRules(rules);
-    }
-
-    private Set<EmbeddingRule> createSplitPairRules() {
-        final Set<EmbeddingRule> rules = new ArraySet<>();
-        final SplitPairRule rule = new SplitPairRule.Builder(
-                activitiesPair -> activitiesPair.first instanceof ActivityEmbeddingMainActivity
-                        && activitiesPair.second instanceof ActivityEmbeddingSecondaryActivity,
-                activityIntentPair ->
-                        activityIntentPair.first instanceof ActivityEmbeddingMainActivity
-                                && activityIntentPair.second.getComponent().equals(ActivityOptions
-                                .ActivityEmbedding.SecondaryActivity.COMPONENT),
-                windowMetrics -> true)
-                .setSplitRatio(DEFAULT_SPLIT_RATIO)
-                .build();
-        rules.add(rule);
-        return rules;
-    }
-
-    private Set<EmbeddingRule> createSplitPlaceholderRules() {
-        final Set<EmbeddingRule> rules = new ArraySet<>();
-        final SplitPlaceholderRule rule = new SplitPlaceholderRule.Builder(
-                new Intent().setComponent(
-                        ActivityOptions.ActivityEmbedding.PlaceholderSecondaryActivity.COMPONENT),
-                activity -> activity instanceof ActivityEmbeddingPlaceholderPrimaryActivity,
-                intent -> intent.getComponent().equals(
-                        ActivityOptions.ActivityEmbedding.PlaceholderPrimaryActivity.COMPONENT),
-                windowMetrics -> true)
-                .setSplitRatio(DEFAULT_SPLIT_RATIO)
-                .build();
-        rules.add(rule);
-        return rules;
+        if (layoutDirectionStr.equals(LayoutDirection.RIGHT_TO_LEFT.toString())) {
+            return LayoutDirection.RIGHT_TO_LEFT;
+        }
+        return LayoutDirection.LOCALE;
     }
 }
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingSecondaryActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingSecondaryActivity.java
index 00f4c25..ee087ef 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingSecondaryActivity.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingSecondaryActivity.java
@@ -16,15 +16,45 @@
 
 package com.android.server.wm.flicker.testapp;
 
+import android.app.Activity;
+import android.content.Intent;
+import android.app.PictureInPictureParams;
 import android.graphics.Color;
+import android.os.Bundle;
+import android.view.View;
 
 /**
  * Activity to be used as the secondary activity to split with
  * {@link ActivityEmbeddingMainActivity}.
  */
-public class ActivityEmbeddingSecondaryActivity extends ActivityEmbeddingBaseActivity {
+public class ActivityEmbeddingSecondaryActivity extends Activity {
+
     @Override
-    int getBackgroundColor() {
-        return Color.YELLOW;
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_embedding_secondary_activity_layout);
+        findViewById(R.id.secondary_activity_layout).setBackgroundColor(Color.YELLOW);
+        findViewById(R.id.finish_secondary_activity_button).setOnClickListener(
+              new View.OnClickListener() {
+                    @Override
+                    public void onClick(View v) {
+                        finish();
+                    }
+            });
+        findViewById(R.id.secondary_enter_pip_button).setOnClickListener(
+                new View.OnClickListener() {
+                    @Override
+                    public void onClick(View v) {
+                        PictureInPictureParams.Builder picInPicParamsBuilder =
+                                new PictureInPictureParams.Builder();
+                        enterPictureInPictureMode(picInPicParamsBuilder.build());
+                    }
+                }
+        );
+    }
+
+    public void launchThirdActivity(View view) {
+        startActivity(new Intent().setComponent(
+                ActivityOptions.ActivityEmbedding.ThirdActivity.COMPONENT));
     }
 }
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingThirdActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingThirdActivity.java
new file mode 100644
index 0000000..3bd7281
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingThirdActivity.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 com.android.server.wm.flicker.testapp;
+
+import android.app.Activity;
+import android.graphics.Color;
+import android.os.Bundle;
+
+/**
+ * Activity to be used also as a secondary activity to split with
+ * {@link ActivityEmbeddingMainActivity}.
+ */
+public class ActivityEmbeddingThirdActivity extends Activity {
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_embedding_base_layout);
+        findViewById(R.id.root_activity_layout).setBackgroundColor(Color.RED);
+    }
+}
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingTrampolineActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingTrampolineActivity.java
new file mode 100644
index 0000000..67eac2e
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingTrampolineActivity.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.flicker.testapp;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+
+/**
+ * A Trampoline Activity that launches {@link ActivityEmbeddingSecondaryActivity} and then
+ * finishes itself.
+ */
+public class ActivityEmbeddingTrampolineActivity extends Activity {
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        // Trampoline activity doesn't have a view.
+        startActivity(new Intent().setComponent(
+                ActivityOptions.ActivityEmbedding.SecondaryActivity.COMPONENT));
+        finish();
+    }
+}
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 9c3226b..2795a6c 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
@@ -73,6 +73,18 @@
                 FLICKER_APP_PACKAGE + ".NonResizeablePortraitActivity");
     }
 
+    public static class TransparentActivity {
+        public static final String LABEL = "TransparentActivity";
+        public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
+                FLICKER_APP_PACKAGE + ".TransparentActivity");
+    }
+
+    public static class LaunchTransparentActivity {
+        public static final String LABEL = "LaunchTransparentActivity";
+        public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
+                FLICKER_APP_PACKAGE + ".LaunchTransparentActivity");
+    }
+
     public static class DialogThemedActivity {
         public static final String LABEL = "DialogThemedActivity";
         public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
@@ -99,6 +111,18 @@
                     FLICKER_APP_PACKAGE + ".ActivityEmbeddingSecondaryActivity");
         }
 
+        public static class ThirdActivity {
+            public static final String LABEL = "ActivityEmbeddingThirdActivity";
+            public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
+                    FLICKER_APP_PACKAGE + ".ActivityEmbeddingThirdActivity");
+        }
+
+        public static class AlwaysExpandActivity {
+            public static final String LABEL = "ActivityEmbeddingAlwaysExpandActivity";
+            public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
+                FLICKER_APP_PACKAGE + ".ActivityEmbeddingAlwaysExpandActivity");
+        }
+
         public static class PlaceholderPrimaryActivity {
             public static final String LABEL = "ActivityEmbeddingPlaceholderPrimaryActivity";
             public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
@@ -110,6 +134,12 @@
             public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
                     FLICKER_APP_PACKAGE + ".ActivityEmbeddingPlaceholderSecondaryActivity");
         }
+
+        public static class TrampolineActivity {
+            public static final String LABEL = "ActivityEmbeddingTrampolineActivity";
+            public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
+                    FLICKER_APP_PACKAGE + ".ActivityEmbeddingTrampolineActivity");
+        }
     }
 
     public static class Notification {
@@ -180,6 +210,8 @@
             public static final String LABEL = "SplitScreenPrimaryActivity";
             public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
                     FLICKER_APP_PACKAGE + ".SplitScreenActivity");
+
+            public static final String EXTRA_LAUNCH_ADJACENT = "launch_adjacent";
         }
 
         public static class Secondary {
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchTransparentActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchTransparentActivity.java
new file mode 100644
index 0000000..7c161fd
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchTransparentActivity.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.flicker.testapp;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+
+public class LaunchTransparentActivity extends Activity {
+
+    @Override
+    protected void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        setContentView(R.layout.activity_transparent_launch);
+        findViewById(R.id.button_launch_transparent)
+                .setOnClickListener(v -> launchTransparentActivity());
+    }
+
+    private void launchTransparentActivity() {
+        startActivity(new Intent(this, TransparentActivity.class));
+    }
+}
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/SplitScreenActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/SplitScreenActivity.java
index 70196ae..8a27252 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/SplitScreenActivity.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/SplitScreenActivity.java
@@ -16,9 +16,14 @@
 
 package com.android.server.wm.flicker.testapp;
 
+import static com.android.server.wm.flicker.testapp.ActivityOptions.SplitScreen.Primary.EXTRA_LAUNCH_ADJACENT;
+
 import android.app.Activity;
+import android.content.Intent;
 import android.os.Bundle;
 
+import androidx.annotation.Nullable;
+
 public class SplitScreenActivity extends Activity {
 
     @Override
@@ -26,4 +31,17 @@
         super.onCreate(icicle);
         setContentView(R.layout.activity_splitscreen);
     }
+
+    @Override
+    protected void onPostCreate(@Nullable Bundle savedInstanceState) {
+        super.onPostCreate(savedInstanceState);
+        final boolean startSecondaryActivity = getIntent().hasExtra(EXTRA_LAUNCH_ADJACENT);
+        if (startSecondaryActivity) {
+            final Intent intent = new Intent(this, SplitScreenSecondaryActivity.class);
+            intent.addCategory(Intent.CATEGORY_LAUNCHER);
+            intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT | Intent.FLAG_ACTIVITY_NEW_TASK
+                    | Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
+            startActivity(intent);
+        }
+    }
 }
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/TransparentActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/TransparentActivity.java
new file mode 100644
index 0000000..1bac8bd
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/TransparentActivity.java
@@ -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.server.wm.flicker.testapp;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class TransparentActivity extends Activity {
+
+    @Override
+    protected void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        setContentView(R.layout.activity_transparent);
+    }
+}
diff --git a/tests/FlickerTests/trace_config/trace_config.textproto b/tests/FlickerTests/trace_config/trace_config.textproto
new file mode 100644
index 0000000..c9a35ac
--- /dev/null
+++ b/tests/FlickerTests/trace_config/trace_config.textproto
@@ -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.
+
+# proto-message: TraceConfig
+
+# Enable periodic flushing of the trace buffer into the output file.
+write_into_file: true
+
+# Writes the userspace buffer into the file every 1s.
+file_write_period_ms: 2500
+
+# See b/126487238 - we need to guarantee ordering of events.
+flush_period_ms: 30000
+
+# The trace buffers needs to be big enough to hold |file_write_period_ms| of
+# trace data. The trace buffer sizing depends on the number of trace categories
+# enabled and the device activity.
+
+# RSS events
+buffers: {
+  size_kb: 63488
+  fill_policy: RING_BUFFER
+}
+
+data_sources {
+  config {
+    name: "linux.process_stats"
+    target_buffer: 0
+    # polled per-process memory counters and process/thread names.
+    # If you don't want the polled counters, remove the "process_stats_config"
+    # section, but keep the data source itself as it still provides on-demand
+    # thread/process naming for ftrace data below.
+    process_stats_config {
+      scan_all_processes_on_start: true
+    }
+  }
+}
+
+data_sources: {
+  config {
+    name: "linux.ftrace"
+    ftrace_config {
+      ftrace_events: "ftrace/print"
+      ftrace_events: "task/task_newtask"
+      ftrace_events: "task/task_rename"
+      atrace_categories: "ss"
+      atrace_categories: "wm"
+      atrace_categories: "am"
+      atrace_categories: "aidl"
+      atrace_categories: "input"
+      atrace_categories: "binder_driver"
+      atrace_categories: "sched_process_exit"
+      atrace_apps: "com.android.server.wm.flicker"
+      atrace_apps: "com.android.server.wm.flicker.other"
+      atrace_apps: "com.android.server.wm.flicker.close"
+      atrace_apps: "com.android.server.wm.flicker.ime"
+      atrace_apps: "com.android.server.wm.flicker.launch"
+      atrace_apps: "com.android.server.wm.flicker.quickswitch"
+      atrace_apps: "com.android.server.wm.flicker.rotation"
+      atrace_apps: "com.android.server.wm.flicker.testapp"
+      atrace_apps: "com.android.systemui"
+      atrace_apps: "com.google.android.apps.nexuslauncher"
+    }
+  }
+}
+
diff --git a/tests/HandwritingIme/src/com/google/android/test/handwritingime/HandwritingIme.java b/tests/HandwritingIme/src/com/google/android/test/handwritingime/HandwritingIme.java
index 8380dcf..d939d91 100644
--- a/tests/HandwritingIme/src/com/google/android/test/handwritingime/HandwritingIme.java
+++ b/tests/HandwritingIme/src/com/google/android/test/handwritingime/HandwritingIme.java
@@ -20,6 +20,8 @@
 import android.graphics.PointF;
 import android.graphics.RectF;
 import android.inputmethodservice.InputMethodService;
+import android.os.CancellationSignal;
+import android.os.Handler;
 import android.util.Log;
 import android.view.MotionEvent;
 import android.view.View;
@@ -31,7 +33,9 @@
 import android.view.inputmethod.HandwritingGesture;
 import android.view.inputmethod.InputConnection;
 import android.view.inputmethod.InsertGesture;
+import android.view.inputmethod.InsertModeGesture;
 import android.view.inputmethod.JoinOrSplitGesture;
+import android.view.inputmethod.PreviewableHandwritingGesture;
 import android.view.inputmethod.RemoveSpaceGesture;
 import android.view.inputmethod.SelectGesture;
 import android.widget.AdapterView;
@@ -47,11 +51,14 @@
 
 public class HandwritingIme extends InputMethodService {
     private static final int OP_NONE = 0;
+    // ------- PreviewableHandwritingGesture BEGIN -----
     private static final int OP_SELECT = 1;
     private static final int OP_DELETE = 2;
+    // ------- PreviewableHandwritingGesture END -----
     private static final int OP_INSERT = 3;
     private static final int OP_REMOVE_SPACE = 4;
     private static final int OP_JOIN_OR_SPLIT = 5;
+    private static final int OP_INSERT_MODE = 6;
 
     private InkView mInk;
 
@@ -70,6 +77,10 @@
 
     private final IntConsumer mResultConsumer = value -> Log.d(TAG, "Gesture result: " + value);
 
+    private CancellationSignal mCancellationSignal = new CancellationSignal();
+    private boolean mUsePreview;
+    private CheckBox mGesturePreviewCheckbox;
+
     interface HandwritingFinisher {
         void finish();
     }
@@ -98,73 +109,107 @@
 
     private void onStylusEvent(@Nullable MotionEvent event) {
         // TODO Hookup recognizer here
+        HandwritingGesture gesture;
         switch (event.getAction()) {
-            case MotionEvent.ACTION_UP: {
-                if (areRichGesturesEnabled()) {
-                    HandwritingGesture gesture = null;
-                    switch (mRichGestureMode) {
-                        case OP_SELECT:
-                            gesture = new SelectGesture.Builder()
-                                    .setGranularity(mRichGestureGranularity)
-                                    .setSelectionArea(getSanitizedRectF(mRichGestureStartPoint.x,
-                                            mRichGestureStartPoint.y, event.getX(), event.getY()))
-                                    .setFallbackText("fallback text")
-                                    .build();
-                            break;
-                        case OP_DELETE:
-                            gesture = new DeleteGesture.Builder()
-                                    .setGranularity(mRichGestureGranularity)
-                                    .setDeletionArea(getSanitizedRectF(mRichGestureStartPoint.x,
-                                            mRichGestureStartPoint.y, event.getX(), event.getY()))
-                                    .setFallbackText("fallback text")
-                                    .build();
-                            break;
-                        case OP_INSERT:
-                            gesture = new InsertGesture.Builder()
-                                    .setInsertionPoint(new PointF(
-                                            mRichGestureStartPoint.x, mRichGestureStartPoint.y))
-                                    .setTextToInsert(" ")
-                                    .setFallbackText("fallback text")
-                                    .build();
-                            break;
-                        case OP_REMOVE_SPACE:
-                            gesture = new RemoveSpaceGesture.Builder()
-                                    .setPoints(
-                                            new PointF(mRichGestureStartPoint.x,
-                                                    mRichGestureStartPoint.y),
-                                            new PointF(event.getX(), event.getY()))
-                                    .setFallbackText("fallback text")
-                                    .build();
-                            break;
-                        case OP_JOIN_OR_SPLIT:
-                            gesture = new JoinOrSplitGesture.Builder()
-                                    .setJoinOrSplitPoint(new PointF(
-                                            mRichGestureStartPoint.x, mRichGestureStartPoint.y))
-                                    .setFallbackText("fallback text")
-                                    .build();
-                            break;
+            case MotionEvent.ACTION_MOVE:
+                if (mUsePreview && areRichGesturesEnabled()) {
+                    gesture = computeGesture(event, true /* isPreview */);
+                    if (gesture == null) {
+                        Log.e(TAG, "Preview not supported for gesture: " + mRichGestureMode);
+                        return;
                     }
+                    performGesture(gesture, true /* isPreview */);
+                }
+                break;
+            case MotionEvent.ACTION_UP:
+                if (areRichGesturesEnabled()) {
+                    gesture = computeGesture(event, false /* isPreview */);
                     if (gesture == null) {
                         // This shouldn't happen
                         Log.e(TAG, "Unrecognized gesture mode: " + mRichGestureMode);
                         return;
                     }
-                    performGesture(gesture);
+                    performGesture(gesture, false /* isPreview */);
                 } else {
                     // insert random ASCII char
                     sendKeyChar((char) (56 + new Random().nextInt(66)));
                 }
                 return;
-            }
             case MotionEvent.ACTION_DOWN: {
                 if (areRichGesturesEnabled()) {
                     mRichGestureStartPoint = new PointF(event.getX(), event.getY());
                 }
-                return;
             }
         }
     }
 
+    private HandwritingGesture computeGesture(MotionEvent event, boolean isPreview) {
+        HandwritingGesture gesture = null;
+        switch (mRichGestureMode) {
+            case OP_SELECT:
+                gesture = new SelectGesture.Builder()
+                        .setGranularity(mRichGestureGranularity)
+                        .setSelectionArea(getSanitizedRectF(mRichGestureStartPoint.x,
+                                mRichGestureStartPoint.y, event.getX(), event.getY()))
+                        .setFallbackText("fallback text")
+                        .build();
+                break;
+            case OP_DELETE:
+                gesture = new DeleteGesture.Builder()
+                        .setGranularity(mRichGestureGranularity)
+                        .setDeletionArea(getSanitizedRectF(mRichGestureStartPoint.x,
+                                mRichGestureStartPoint.y, event.getX(), event.getY()))
+                        .setFallbackText("fallback text")
+                        .build();
+                break;
+            case OP_INSERT:
+                gesture = new InsertGesture.Builder()
+                        .setInsertionPoint(new PointF(
+                                mRichGestureStartPoint.x, mRichGestureStartPoint.y))
+                        .setTextToInsert(" ")
+                        .setFallbackText("fallback text")
+                        .build();
+                break;
+            case OP_REMOVE_SPACE:
+                if (isPreview) {
+                    break;
+                }
+                gesture = new RemoveSpaceGesture.Builder()
+                        .setPoints(
+                                new PointF(mRichGestureStartPoint.x,
+                                        mRichGestureStartPoint.y),
+                                new PointF(event.getX(), event.getY()))
+                        .setFallbackText("fallback text")
+                        .build();
+                break;
+            case OP_JOIN_OR_SPLIT:
+                if (isPreview) {
+                    break;
+                }
+                gesture = new JoinOrSplitGesture.Builder()
+                        .setJoinOrSplitPoint(new PointF(
+                                mRichGestureStartPoint.x, mRichGestureStartPoint.y))
+                        .setFallbackText("fallback text")
+                        .build();
+                break;
+            case OP_INSERT_MODE:
+                if (isPreview) {
+                    break;
+                }
+                mCancellationSignal = new CancellationSignal();
+                InsertModeGesture img = new InsertModeGesture.Builder()
+                        .setInsertionPoint(new PointF(
+                                mRichGestureStartPoint.x, mRichGestureStartPoint.y))
+                        .setFallbackText("fallback text")
+                        .setCancellationSignal(mCancellationSignal)
+                        .build();
+                gesture = img;
+                new Handler().postDelayed(() -> img.getCancellationSignal().cancel(), 5000);
+                break;
+        }
+        return gesture;
+    }
+
     /**
      * sanitize values to support rectangles in all cases.
      */
@@ -193,10 +238,14 @@
         return rectF;
     }
 
-    private void performGesture(HandwritingGesture gesture) {
+    private void performGesture(HandwritingGesture gesture, boolean isPreview) {
         InputConnection ic = getCurrentInputConnection();
         if (getCurrentInputStarted() && ic != null) {
-            ic.performHandwritingGesture(gesture, Runnable::run, mResultConsumer);
+            if (isPreview) {
+                ic.previewHandwritingGesture((PreviewableHandwritingGesture) gesture, null);
+            } else {
+                ic.performHandwritingGesture(gesture, Runnable::run, mResultConsumer);
+            }
         } else {
             // This shouldn't happen
             Log.e(TAG, "No active InputConnection");
@@ -216,12 +265,21 @@
         layout.addView(getRichGestureActionsSpinner());
         layout.addView(getRichGestureGranularitySpinner());
         layout.addView(getBoundsInfoCheckBoxes());
+        layout.addView(getPreviewCheckBox());
         layout.setBackgroundColor(getColor(R.color.holo_green_light));
         view.addView(layout);
 
         return view;
     }
 
+    private View getPreviewCheckBox() {
+        mGesturePreviewCheckbox = new CheckBox(this);
+        mGesturePreviewCheckbox.setText("Use Gesture Previews (for Previewable Gestures)");
+        mGesturePreviewCheckbox.setOnCheckedChangeListener(
+                (buttonView, isChecked) -> mUsePreview = isChecked);
+        return mGesturePreviewCheckbox;
+    }
+
     private View getRichGestureActionsSpinner() {
         if (mRichGestureModeSpinner != null) {
             return mRichGestureModeSpinner;
@@ -236,6 +294,7 @@
                 "Rich gesture INSERT",
                 "Rich gesture REMOVE SPACE",
                 "Rich gesture JOIN OR SPLIT",
+                "Rich gesture INSERT MODE",
         };
         ArrayAdapter<String> adapter = new ArrayAdapter<>(this,
                 android.R.layout.simple_spinner_dropdown_item, items);
@@ -245,8 +304,13 @@
             @Override
             public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
                 mRichGestureMode = position;
-                mRichGestureGranularitySpinner.setEnabled(
-                        mRichGestureMode == OP_SELECT || mRichGestureMode == OP_DELETE);
+                boolean supportsGranularityAndPreview =
+                        mRichGestureMode == OP_SELECT || mRichGestureMode == OP_DELETE;
+                mRichGestureGranularitySpinner.setEnabled(supportsGranularityAndPreview);
+                mGesturePreviewCheckbox.setEnabled(supportsGranularityAndPreview);
+                if (!supportsGranularityAndPreview) {
+                    mUsePreview = false;
+                }
                 Log.d(TAG, "Setting RichGesture Mode " + mRichGestureMode);
             }
 
diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml
index 80c7a21..db3a992 100644
--- a/tests/HwAccelerationTest/AndroidManifest.xml
+++ b/tests/HwAccelerationTest/AndroidManifest.xml
@@ -789,6 +789,15 @@
             </intent-filter>
         </activity>
 
+        <activity android:name="BackdropBlurActivity"
+                  android:label="RenderEffect/BackdropBlur"
+                  android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="com.android.test.hwui.TEST"/>
+            </intent-filter>
+        </activity>
+
         <activity android:name="BlurActivity"
                   android:label="RenderEffect/Blur"
                   android:exported="true">
diff --git a/tests/HwAccelerationTest/res/drawable/robot_repeated.xml b/tests/HwAccelerationTest/res/drawable/robot_repeated.xml
new file mode 100644
index 0000000..bbb15b7
--- /dev/null
+++ b/tests/HwAccelerationTest/res/drawable/robot_repeated.xml
@@ -0,0 +1,18 @@
+<?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.
+-->
+<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
+        android:src="@drawable/robot"
+        android:tileMode="repeat" android:gravity="fill" />
\ No newline at end of file
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/BackdropBlurActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/BackdropBlurActivity.java
new file mode 100644
index 0000000..8086b29
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/BackdropBlurActivity.java
@@ -0,0 +1,119 @@
+/*
+ * 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.test.hwui;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.ColorMatrix;
+import android.graphics.ColorMatrixColorFilter;
+import android.graphics.Outline;
+import android.graphics.RenderEffect;
+import android.graphics.Shader;
+import android.os.Bundle;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewOutlineProvider;
+import android.view.animation.DecelerateInterpolator;
+import android.widget.FrameLayout;
+import android.widget.ScrollView;
+
+@SuppressWarnings({"UnusedDeclaration"})
+public class BackdropBlurActivity extends Activity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        final ScrollView scrollView = new ScrollView(this);
+        final FrameLayout innerFrame = new FrameLayout(this);
+        final View backgroundView = new View(this);
+        backgroundView.setBackgroundResource(R.drawable.robot_repeated);
+        innerFrame.addView(backgroundView, ViewGroup.LayoutParams.MATCH_PARENT, 10000);
+        scrollView.addView(innerFrame,
+                ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+
+        final FrameLayout contentView = new FrameLayout(this);
+        contentView.addView(scrollView,
+                ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
+        contentView.addView(new BackdropBlurView(this), 300, 300);
+        setContentView(contentView);
+    }
+
+    private static class BackdropBlurView extends View {
+        private final float mBlurRadius = 60f;
+        private final float mSaturation = 1.8f;
+
+        private float mDownOffsetX;
+        private float mDownOffsetY;
+
+        BackdropBlurView(Context c) {
+            super(c);
+
+            // init RenderEffect.
+            final RenderEffect blurEffect = RenderEffect.createBlurEffect(
+                    mBlurRadius, mBlurRadius,
+                    null, Shader.TileMode.MIRROR // TileMode.MIRROR is better for blur.
+            );
+
+            final ColorMatrix colorMatrix = new ColorMatrix();
+            colorMatrix.setSaturation(mSaturation);
+            final RenderEffect effect = RenderEffect.createColorFilterEffect(
+                    new ColorMatrixColorFilter(colorMatrix), blurEffect
+            );
+            setBackdropRenderEffect(effect);
+
+            // clip to a round outline.
+            setOutlineProvider(new ViewOutlineProvider() {
+                @Override
+                public void getOutline(View v, Outline outline) {
+                    outline.setOval(0, 0, v.getWidth(), v.getHeight());
+                }
+            });
+            setClipToOutline(true);
+
+            animate().setInterpolator(new DecelerateInterpolator(2.0f));
+        }
+
+        @Override
+        protected void onDraw(Canvas canvas) {
+            super.onDraw(canvas);
+
+            canvas.drawColor(0x99F0F0F0);
+        }
+
+        @Override
+        public boolean onTouchEvent(MotionEvent event) {
+            switch (event.getActionMasked()) {
+                case MotionEvent.ACTION_DOWN:
+                    mDownOffsetX = event.getRawX() - getTranslationX();
+                    mDownOffsetY = event.getRawY() - getTranslationY();
+                    animate().scaleX(1.5f).scaleY(1.5f).start();
+                    break;
+                case MotionEvent.ACTION_UP:
+                case MotionEvent.ACTION_CANCEL:
+                    animate().scaleX(1f).scaleY(1f).start();
+                    break;
+                case MotionEvent.ACTION_MOVE:
+                    setTranslationX(event.getRawX() - mDownOffsetX);
+                    setTranslationY(event.getRawY() - mDownOffsetY);
+                    break;
+            }
+            return true;
+        }
+    }
+}
diff --git a/tests/Input/Android.bp b/tests/Input/Android.bp
index 4fa6fbe..96b685d 100644
--- a/tests/Input/Android.bp
+++ b/tests/Input/Android.bp
@@ -19,13 +19,26 @@
     platform_apis: true,
     certificate: "platform",
     static_libs: [
+        "androidx.test.core",
         "androidx.test.ext.junit",
+        "androidx.test.ext.truth",
         "androidx.test.rules",
+        "androidx.test.runner",
+        "androidx.test.uiautomator_uiautomator",
+        "servicestests-utils",
+        "frameworks-base-testutils",
+        "hamcrest-library",
+        "kotlin-test",
         "mockito-target-minus-junit4",
+        "platform-test-annotations",
         "services.core.unboosted",
         "testables",
+        "testng",
         "truth-prebuilt",
-        "androidx.test.uiautomator_uiautomator",
+    ],
+    libs: [
+        "android.test.mock",
+        "android.test.base",
     ],
     test_suites: ["device-tests"],
 }
diff --git a/tests/Input/AndroidManifest.xml b/tests/Input/AndroidManifest.xml
index 20f564e..3b723dd 100644
--- a/tests/Input/AndroidManifest.xml
+++ b/tests/Input/AndroidManifest.xml
@@ -16,11 +16,14 @@
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="com.android.test.input">
-    <uses-permission android:name="android.permission.MONITOR_INPUT"/>
-    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/>
-    <uses-permission android:name="android.permission.INJECT_EVENTS"/>
 
-    <application android:label="InputTest">
+    <uses-permission android:name="android.permission.INJECT_EVENTS"/>
+    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL"/>
+    <uses-permission android:name="android.permission.MONITOR_INPUT"/>
+    <uses-permission android:name="android.permission.READ_DEVICE_CONFIG"/>
+    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/>
+
+    <application android:label="InputTest" android:debuggable="true">
 
         <activity android:name=".UnresponsiveGestureMonitorActivity"
              android:label="Unresponsive gesture monitor"
diff --git a/tests/Input/OWNERS b/tests/Input/OWNERS
index d701f23..3cffce9 100644
--- a/tests/Input/OWNERS
+++ b/tests/Input/OWNERS
@@ -1 +1,2 @@
+# Bug component: 136048
 include /core/java/android/hardware/input/OWNERS
diff --git a/services/tests/servicestests/res/raw/dummy_keyboard_layout.kcm b/tests/Input/res/raw/dummy_keyboard_layout.kcm
similarity index 100%
rename from services/tests/servicestests/res/raw/dummy_keyboard_layout.kcm
rename to tests/Input/res/raw/dummy_keyboard_layout.kcm
diff --git a/services/tests/servicestests/res/raw/input_port_associations.xml b/tests/Input/res/raw/input_port_associations.xml
similarity index 100%
rename from services/tests/servicestests/res/raw/input_port_associations.xml
rename to tests/Input/res/raw/input_port_associations.xml
diff --git a/services/tests/servicestests/res/raw/input_port_associations_bad_displayport.xml b/tests/Input/res/raw/input_port_associations_bad_displayport.xml
similarity index 100%
rename from services/tests/servicestests/res/raw/input_port_associations_bad_displayport.xml
rename to tests/Input/res/raw/input_port_associations_bad_displayport.xml
diff --git a/services/tests/servicestests/res/raw/input_port_associations_bad_xml.xml b/tests/Input/res/raw/input_port_associations_bad_xml.xml
similarity index 100%
rename from services/tests/servicestests/res/raw/input_port_associations_bad_xml.xml
rename to tests/Input/res/raw/input_port_associations_bad_xml.xml
diff --git a/services/tests/servicestests/res/xml/keyboard_layouts.xml b/tests/Input/res/xml/keyboard_layouts.xml
similarity index 100%
rename from services/tests/servicestests/res/xml/keyboard_layouts.xml
rename to tests/Input/res/xml/keyboard_layouts.xml
diff --git a/tests/Input/src/android/hardware/input/InputDeviceBatteryListenerTest.kt b/tests/Input/src/android/hardware/input/InputDeviceBatteryListenerTest.kt
new file mode 100644
index 0000000..90dff47
--- /dev/null
+++ b/tests/Input/src/android/hardware/input/InputDeviceBatteryListenerTest.kt
@@ -0,0 +1,246 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.hardware.input
+
+import android.content.Context
+import android.content.ContextWrapper
+import android.hardware.BatteryState
+import android.os.Handler
+import android.os.HandlerExecutor
+import android.os.test.TestLooper
+import android.platform.test.annotations.Presubmit
+import androidx.test.core.app.ApplicationProvider
+import com.android.server.testutils.any
+import java.util.concurrent.Executor
+import kotlin.test.assertEquals
+import kotlin.test.assertNotNull
+import kotlin.test.assertTrue
+import kotlin.test.fail
+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
+import org.mockito.Mockito
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.doAnswer
+import org.mockito.Mockito.`when`
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoJUnitRunner
+
+/**
+ * Tests for [InputManager.InputDeviceBatteryListener].
+ *
+ * Build/Install/Run:
+ * atest InputTests:InputDeviceBatteryListenerTest
+ */
+@Presubmit
+@RunWith(MockitoJUnitRunner::class)
+class InputDeviceBatteryListenerTest {
+    @get:Rule
+    val rule = MockitoJUnit.rule()!!
+
+    private lateinit var testLooper: TestLooper
+    private var registeredListener: IInputDeviceBatteryListener? = null
+    private val monitoredDevices = mutableListOf<Int>()
+    private lateinit var executor: Executor
+    private lateinit var context: Context
+    private lateinit var inputManager: InputManager
+
+    @Mock
+    private lateinit var iInputManagerMock: IInputManager
+    private lateinit var inputManagerGlobalSession: InputManagerGlobal.TestSession
+
+    @Before
+    fun setUp() {
+        context = Mockito.spy(ContextWrapper(ApplicationProvider.getApplicationContext()))
+        testLooper = TestLooper()
+        executor = HandlerExecutor(Handler(testLooper.looper))
+        registeredListener = null
+        monitoredDevices.clear()
+        inputManagerGlobalSession = InputManagerGlobal.createTestSession(iInputManagerMock)
+        inputManager = InputManager(context)
+        `when`(context.getSystemService(Mockito.eq(Context.INPUT_SERVICE)))
+                .thenReturn(inputManager)
+
+        // Handle battery listener registration.
+        doAnswer {
+            val deviceId = it.getArgument(0) as Int
+            val listener = it.getArgument(1) as IInputDeviceBatteryListener
+            if (registeredListener != null &&
+                    registeredListener!!.asBinder() != listener.asBinder()) {
+                // There can only be one registered battery listener per process.
+                fail("Trying to register a new listener when one already exists")
+            }
+            if (monitoredDevices.contains(deviceId)) {
+                fail("Trying to start monitoring a device that was already being monitored")
+            }
+            monitoredDevices.add(deviceId)
+            registeredListener = listener
+            null
+        }.`when`(iInputManagerMock).registerBatteryListener(anyInt(), any())
+
+        // Handle battery listener being unregistered.
+        doAnswer {
+            val deviceId = it.getArgument(0) as Int
+            val listener = it.getArgument(1) as IInputDeviceBatteryListener
+            if (registeredListener == null ||
+                    registeredListener!!.asBinder() != listener.asBinder()) {
+                fail("Trying to unregister a listener that is not registered")
+            }
+            if (!monitoredDevices.remove(deviceId)) {
+                fail("Trying to stop monitoring a device that is not being monitored")
+            }
+            if (monitoredDevices.isEmpty()) {
+                registeredListener = null
+            }
+        }.`when`(iInputManagerMock).unregisterBatteryListener(anyInt(), any())
+    }
+
+    @After
+    fun tearDown() {
+        if (this::inputManagerGlobalSession.isInitialized) {
+            inputManagerGlobalSession.close()
+        }
+    }
+
+    private fun notifyBatteryStateChanged(
+        deviceId: Int,
+        isPresent: Boolean = true,
+        status: Int = BatteryState.STATUS_FULL,
+        capacity: Float = 1.0f,
+        eventTime: Long = 12345L
+    ) {
+        registeredListener!!.onBatteryStateChanged(IInputDeviceBatteryState().apply {
+            this.deviceId = deviceId
+            this.updateTime = eventTime
+            this.isPresent = isPresent
+            this.status = status
+            this.capacity = capacity
+        })
+    }
+
+    @Test
+    fun testListenerIsNotifiedCorrectly() {
+        var callbackCount = 0
+
+        // Add a battery listener to monitor battery changes.
+        inputManager.addInputDeviceBatteryListener(1 /*deviceId*/, executor) {
+                deviceId: Int, eventTime: Long, batteryState: BatteryState ->
+            callbackCount++
+            assertEquals(1, deviceId)
+            assertEquals(true, batteryState.isPresent)
+            assertEquals(BatteryState.STATUS_DISCHARGING, batteryState.status)
+            assertEquals(0.5f, batteryState.capacity)
+            assertEquals(8675309L, eventTime)
+        }
+
+        // Adding the listener should register the callback with InputManagerService.
+        assertNotNull(registeredListener)
+        assertTrue(monitoredDevices.contains(1))
+
+        // Notifying battery change for a different device should not trigger the listener.
+        notifyBatteryStateChanged(deviceId = 2)
+        testLooper.dispatchAll()
+        assertEquals(0, callbackCount)
+
+        // Notifying battery change for the registered device will notify the listener.
+        notifyBatteryStateChanged(1 /*deviceId*/, true /*isPresent*/,
+            BatteryState.STATUS_DISCHARGING, 0.5f /*capacity*/, 8675309L /*eventTime*/)
+        testLooper.dispatchNext()
+        assertEquals(1, callbackCount)
+    }
+
+    @Test
+    fun testMultipleListeners() {
+        // Set up two callbacks.
+        var callbackCount1 = 0
+        var callbackCount2 = 0
+        val callback1 = InputManager.InputDeviceBatteryListener { _, _, _ -> callbackCount1++ }
+        val callback2 = InputManager.InputDeviceBatteryListener { _, _, _ -> callbackCount2++ }
+
+        // Monitor battery changes for three devices. The first callback monitors devices 1 and 3,
+        // while the second callback monitors devices 2 and 3.
+        inputManager.addInputDeviceBatteryListener(1 /*deviceId*/, executor, callback1)
+        assertEquals(1, monitoredDevices.size)
+        inputManager.addInputDeviceBatteryListener(2 /*deviceId*/, executor, callback2)
+        assertEquals(2, monitoredDevices.size)
+        inputManager.addInputDeviceBatteryListener(3 /*deviceId*/, executor, callback1)
+        assertEquals(3, monitoredDevices.size)
+        inputManager.addInputDeviceBatteryListener(3 /*deviceId*/, executor, callback2)
+        assertEquals(3, monitoredDevices.size)
+
+        // Notifying battery change for each of the devices should trigger the registered callbacks.
+        notifyBatteryStateChanged(deviceId = 1)
+        testLooper.dispatchNext()
+        assertEquals(1, callbackCount1)
+        assertEquals(0, callbackCount2)
+
+        notifyBatteryStateChanged(deviceId = 2)
+        testLooper.dispatchNext()
+        assertEquals(1, callbackCount1)
+        assertEquals(1, callbackCount2)
+
+        notifyBatteryStateChanged(deviceId = 3)
+        testLooper.dispatchNext()
+        testLooper.dispatchNext()
+        assertEquals(2, callbackCount1)
+        assertEquals(2, callbackCount2)
+
+        // Stop monitoring devices 1 and 2.
+        inputManager.removeInputDeviceBatteryListener(1 /*deviceId*/, callback1)
+        assertEquals(2, monitoredDevices.size)
+        inputManager.removeInputDeviceBatteryListener(2 /*deviceId*/, callback2)
+        assertEquals(1, monitoredDevices.size)
+
+        // Ensure device 3 continues to be monitored.
+        notifyBatteryStateChanged(deviceId = 3)
+        testLooper.dispatchNext()
+        testLooper.dispatchNext()
+        assertEquals(3, callbackCount1)
+        assertEquals(3, callbackCount2)
+
+        // Stop monitoring all devices.
+        inputManager.removeInputDeviceBatteryListener(3 /*deviceId*/, callback1)
+        assertEquals(1, monitoredDevices.size)
+        inputManager.removeInputDeviceBatteryListener(3 /*deviceId*/, callback2)
+        assertEquals(0, monitoredDevices.size)
+    }
+
+    @Test
+    fun testAdditionalListenersNotifiedImmediately() {
+        var callbackCount1 = 0
+        var callbackCount2 = 0
+        val callback1 = InputManager.InputDeviceBatteryListener { _, _, _ -> callbackCount1++ }
+        val callback2 = InputManager.InputDeviceBatteryListener { _, _, _ -> callbackCount2++ }
+
+        // Add a battery listener and send the latest battery state.
+        inputManager.addInputDeviceBatteryListener(1 /*deviceId*/, executor, callback1)
+        assertEquals(1, monitoredDevices.size)
+        notifyBatteryStateChanged(deviceId = 1)
+        testLooper.dispatchNext()
+        assertEquals(1, callbackCount1)
+
+        // Add a second listener for the same device that already has the latest battery state.
+        inputManager.addInputDeviceBatteryListener(1 /*deviceId*/, executor, callback2)
+        assertEquals(1, monitoredDevices.size)
+
+        // Ensure that this listener is notified immediately.
+        testLooper.dispatchNext()
+        assertEquals(1, callbackCount2)
+    }
+}
diff --git a/tests/Input/src/android/hardware/input/InputDeviceLightsManagerTest.java b/tests/Input/src/android/hardware/input/InputDeviceLightsManagerTest.java
new file mode 100644
index 0000000..080186e
--- /dev/null
+++ b/tests/Input/src/android/hardware/input/InputDeviceLightsManagerTest.java
@@ -0,0 +1,290 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.hardware.input;
+
+import static android.hardware.lights.LightsRequest.Builder;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static junit.framework.TestCase.assertEquals;
+import static junit.framework.TestCase.assertNotNull;
+
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.hardware.lights.Light;
+import android.hardware.lights.LightState;
+import android.hardware.lights.LightsManager;
+import android.hardware.lights.LightsRequest;
+import android.os.IBinder;
+import android.platform.test.annotations.Presubmit;
+import android.util.ArrayMap;
+import android.view.InputDevice;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+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;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.mockito.junit.MockitoRule;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Tests for {@link InputDeviceLightsManager}.
+ *
+ * Build/Install/Run:
+ * atest InputTests:InputDeviceLightsManagerTest
+ */
+@Presubmit
+@RunWith(MockitoJUnitRunner.class)
+public class InputDeviceLightsManagerTest {
+    private static final String TAG = "InputDeviceLightsManagerTest";
+
+    private static final int DEVICE_ID = 1000;
+    private static final int PLAYER_ID = 3;
+
+    @Rule public final MockitoRule mockito = MockitoJUnit.rule();
+
+    private InputManager mInputManager;
+
+    @Mock private IInputManager mIInputManagerMock;
+    private InputManagerGlobal.TestSession mInputManagerGlobalSession;
+
+    @Before
+    public void setUp() throws Exception {
+        final Context context = spy(
+                new ContextWrapper(InstrumentationRegistry.getInstrumentation().getContext()));
+        when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{DEVICE_ID});
+
+        when(mIInputManagerMock.getInputDevice(eq(DEVICE_ID))).thenReturn(
+                createInputDevice(DEVICE_ID));
+
+        mInputManagerGlobalSession = InputManagerGlobal.createTestSession(mIInputManagerMock);
+        mInputManager = new InputManager(context);
+        when(context.getSystemService(eq(Context.INPUT_SERVICE))).thenReturn(mInputManager);
+
+        ArrayMap<Integer, LightState> lightStatesById = new ArrayMap<>();
+        doAnswer(invocation -> {
+            final int[] lightIds = (int[]) invocation.getArguments()[1];
+            final LightState[] lightStates =
+                    (LightState[]) invocation.getArguments()[2];
+            for (int i = 0; i < lightIds.length; i++) {
+                lightStatesById.put(lightIds[i], lightStates[i]);
+            }
+            return null;
+        }).when(mIInputManagerMock).setLightStates(eq(DEVICE_ID),
+                any(int[].class), any(LightState[].class), any(IBinder.class));
+
+        doAnswer(invocation -> {
+            int lightId = (int) invocation.getArguments()[1];
+            if (lightStatesById.containsKey(lightId)) {
+                return lightStatesById.get(lightId);
+            }
+            return new LightState(0);
+        }).when(mIInputManagerMock).getLightState(eq(DEVICE_ID), anyInt());
+    }
+
+    @After
+    public void tearDown() {
+        if (mInputManagerGlobalSession != null) {
+            mInputManagerGlobalSession.close();
+        }
+    }
+
+    private InputDevice createInputDevice(int id) {
+        return new InputDevice.Builder()
+                .setId(id)
+                .setName("Test Device " + id)
+                .build();
+    }
+
+    private void mockLights(Light[] lights) throws Exception {
+        // Mock the Lights returned form InputManagerService
+        when(mIInputManagerMock.getLights(eq(DEVICE_ID))).thenReturn(
+                new ArrayList(Arrays.asList(lights)));
+    }
+
+    @Test
+    public void testGetInputDeviceLights() throws Exception {
+        InputDevice device = mInputManager.getInputDevice(DEVICE_ID);
+        assertNotNull(device);
+
+        Light[] mockedLights = {
+            new Light(1 /* id */, "Light1", 0 /* ordinal */, Light.LIGHT_TYPE_INPUT,
+                        Light.LIGHT_CAPABILITY_BRIGHTNESS),
+            new Light(2 /* id */, "Light2", 0 /* ordinal */, Light.LIGHT_TYPE_INPUT,
+                        Light.LIGHT_CAPABILITY_COLOR_RGB),
+            new Light(3 /* id */, "Light3", 0 /* ordinal */, Light.LIGHT_TYPE_INPUT,
+                        0 /* capabilities */)
+        };
+        mockLights(mockedLights);
+
+        LightsManager lightsManager = device.getLightsManager();
+        List<Light> lights = lightsManager.getLights();
+        verify(mIInputManagerMock).getLights(eq(DEVICE_ID));
+        assertEquals(lights, Arrays.asList(mockedLights));
+    }
+
+    @Test
+    public void testControlMultipleLights() throws Exception {
+        InputDevice device = mInputManager.getInputDevice(DEVICE_ID);
+        assertNotNull(device);
+
+        Light[] mockedLights = {
+            new Light(1 /* id */, "Light1", 0 /* ordinal */, Light.LIGHT_TYPE_INPUT,
+                        Light.LIGHT_CAPABILITY_COLOR_RGB),
+            new Light(2 /* id */, "Light2", 0 /* ordinal */, Light.LIGHT_TYPE_INPUT,
+                        Light.LIGHT_CAPABILITY_COLOR_RGB),
+            new Light(3 /* id */, "Light3", 0 /* ordinal */, Light.LIGHT_TYPE_INPUT,
+                        Light.LIGHT_CAPABILITY_COLOR_RGB),
+            new Light(4 /* id */, "Light4", 0 /* ordinal */, Light.LIGHT_TYPE_INPUT,
+                        Light.LIGHT_CAPABILITY_COLOR_RGB)
+        };
+        mockLights(mockedLights);
+
+        LightsManager lightsManager = device.getLightsManager();
+        List<Light> lightList = lightsManager.getLights();
+        LightState[] states = new LightState[]{new LightState(0xf1), new LightState(0xf2),
+                new LightState(0xf3)};
+        // Open a session to request turn 3/4 lights on:
+        LightsManager.LightsSession session = lightsManager.openSession();
+        session.requestLights(new Builder()
+                .addLight(lightsManager.getLights().get(0), states[0])
+                .addLight(lightsManager.getLights().get(1), states[1])
+                .addLight(lightsManager.getLights().get(2), states[2])
+                .build());
+        IBinder token = session.getToken();
+
+        verify(mIInputManagerMock).openLightSession(eq(DEVICE_ID),
+                any(String.class), eq(token));
+        verify(mIInputManagerMock).setLightStates(eq(DEVICE_ID), eq(new int[]{1, 2, 3}),
+                eq(states), eq(token));
+
+        // Then all 3 should turn on.
+        assertThat(lightsManager.getLightState(lightsManager.getLights().get(0)).getColor())
+                .isEqualTo(0xf1);
+        assertThat(lightsManager.getLightState(lightsManager.getLights().get(1)).getColor())
+                .isEqualTo(0xf2);
+        assertThat(lightsManager.getLightState(lightsManager.getLights().get(2)).getColor())
+                .isEqualTo(0xf3);
+
+        // And the 4th should remain off.
+        assertThat(lightsManager.getLightState(lightsManager.getLights().get(3)).getColor())
+                .isEqualTo(0x00);
+
+        // close session
+        session.close();
+        verify(mIInputManagerMock).closeLightSession(eq(DEVICE_ID), eq(token));
+    }
+
+    @Test
+    public void testControlPlayerIdLight() throws Exception {
+        InputDevice device = mInputManager.getInputDevice(DEVICE_ID);
+        assertNotNull(device);
+
+        Light[] mockedLights = {
+                new Light(1 /* id */, "Light1", 0 /* ordinal */, Light.LIGHT_TYPE_PLAYER_ID,
+                            0 /* capabilities */),
+                new Light(2 /* id */, "Light2", 0 /* ordinal */, Light.LIGHT_TYPE_INPUT,
+                            Light.LIGHT_CAPABILITY_COLOR_RGB | Light.LIGHT_CAPABILITY_BRIGHTNESS),
+                new Light(3 /* id */, "Light3", 0 /* ordinal */, Light.LIGHT_TYPE_INPUT,
+                            Light.LIGHT_CAPABILITY_BRIGHTNESS)
+        };
+        mockLights(mockedLights);
+
+        LightsManager lightsManager = device.getLightsManager();
+        List<Light> lightList = lightsManager.getLights();
+        LightState[] states = new LightState[]{new LightState(0xf1, PLAYER_ID)};
+        // Open a session to request set Player ID light:
+        LightsManager.LightsSession session = lightsManager.openSession();
+        session.requestLights(new Builder()
+                .addLight(lightsManager.getLights().get(0), states[0])
+                .build());
+        IBinder token = session.getToken();
+
+        verify(mIInputManagerMock).openLightSession(eq(DEVICE_ID),
+                any(String.class), eq(token));
+        verify(mIInputManagerMock).setLightStates(eq(DEVICE_ID), eq(new int[]{1}),
+                eq(states), eq(token));
+
+        // Verify the light state
+        assertThat(lightsManager.getLightState(lightsManager.getLights().get(0)).getColor())
+                .isEqualTo(0xf1);
+        assertThat(lightsManager.getLightState(lightsManager.getLights().get(0)).getPlayerId())
+                .isEqualTo(PLAYER_ID);
+
+        // close session
+        session.close();
+        verify(mIInputManagerMock).closeLightSession(eq(DEVICE_ID), eq(token));
+    }
+
+    @Test
+    public void testLightCapabilities() throws Exception {
+        Light light = new Light(1 /* id */, "Light1", 0 /* ordinal */, Light.LIGHT_TYPE_INPUT,
+                Light.LIGHT_CAPABILITY_COLOR_RGB | Light.LIGHT_CAPABILITY_BRIGHTNESS);
+        assertThat(light.getType()).isEqualTo(Light.LIGHT_TYPE_INPUT);
+        assertThat(light.getCapabilities()).isEqualTo(Light.LIGHT_CAPABILITY_COLOR_RGB
+                | Light.LIGHT_CAPABILITY_BRIGHTNESS);
+        assertTrue(light.hasBrightnessControl());
+        assertTrue(light.hasRgbControl());
+    }
+
+    @Test
+    public void testLightsRequest() throws Exception {
+        Light light1 = new Light(1 /* id */, "Light1", 0 /* ordinal */, Light.LIGHT_TYPE_INPUT,
+                0 /* capabilities */);
+        Light light2 = new Light(2 /* id */, "Light2", 0 /* ordinal */, Light.LIGHT_TYPE_PLAYER_ID,
+                0 /* capabilities */);
+        LightState state1 = new LightState(0xf1);
+        LightState state2 = new LightState(0xf2, PLAYER_ID);
+        LightsRequest request = new Builder().addLight(light1, state1)
+                .addLight(light2, state2).build();
+
+        // Covers the LightsRequest.getLights
+        assertThat(request.getLights().size()).isEqualTo(2);
+        assertThat(request.getLights().get(0)).isEqualTo(1);
+        assertThat(request.getLights().get(1)).isEqualTo(2);
+
+        // Covers the LightsRequest.getLightStates
+        assertThat(request.getLightStates().size()).isEqualTo(2);
+        assertThat(request.getLightStates().get(0)).isEqualTo(state1);
+        assertThat(request.getLightStates().get(1)).isEqualTo(state2);
+
+        // Covers the LightsRequest.getLightsAndStates
+        assertThat(request.getLightsAndStates().size()).isEqualTo(2);
+        assertThat(request.getLightsAndStates().containsKey(light1)).isTrue();
+        assertThat(request.getLightsAndStates().get(light1)).isEqualTo(state1);
+        assertThat(request.getLightsAndStates().get(light2)).isEqualTo(state2);
+    }
+
+}
diff --git a/tests/Input/src/android/hardware/input/InputDeviceSensorManagerTest.java b/tests/Input/src/android/hardware/input/InputDeviceSensorManagerTest.java
new file mode 100644
index 0000000..0e3c200
--- /dev/null
+++ b/tests/Input/src/android/hardware/input/InputDeviceSensorManagerTest.java
@@ -0,0 +1,246 @@
+/*
+ * 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.
+ */
+
+package android.hardware.input;
+
+import static junit.framework.TestCase.assertEquals;
+import static junit.framework.TestCase.assertNotNull;
+import static junit.framework.TestCase.assertTrue;
+import static junit.framework.TestCase.fail;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.platform.test.annotations.Presubmit;
+import android.view.InputDevice;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.internal.annotations.GuardedBy;
+
+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;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.mockito.junit.MockitoRule;
+
+import java.util.List;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Tests for {@link InputDeviceSensorManager}.
+ *
+ * Build/Install/Run:
+ * atest InputTests:InputDeviceSensorManagerTest
+ */
+@Presubmit
+@RunWith(MockitoJUnitRunner.class)
+public class InputDeviceSensorManagerTest {
+    private static final String TAG = "InputDeviceSensorManagerTest";
+
+    private static final int DEVICE_ID = 1000;
+
+    @Rule public final MockitoRule mockito = MockitoJUnit.rule();
+
+    private InputManager mInputManager;
+    private IInputSensorEventListener mIInputSensorEventListener;
+    private final Object mLock = new Object();
+
+    @Mock private IInputManager mIInputManagerMock;
+    private InputManagerGlobal.TestSession mInputManagerGlobalSession;
+
+    @Before
+    public void setUp() throws Exception {
+        final Context context = spy(
+                new ContextWrapper(InstrumentationRegistry.getInstrumentation().getContext()));
+        mInputManagerGlobalSession = InputManagerGlobal.createTestSession(mIInputManagerMock);
+        mInputManager = new InputManager(context);
+        when(context.getSystemService(eq(Context.INPUT_SERVICE))).thenReturn(mInputManager);
+
+        when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{DEVICE_ID});
+
+        when(mIInputManagerMock.getInputDevice(eq(DEVICE_ID))).thenReturn(
+                createInputDeviceWithSensor(DEVICE_ID));
+
+        when(mIInputManagerMock.getSensorList(eq(DEVICE_ID))).thenReturn(new InputSensorInfo[] {
+                createInputSensorInfo(DEVICE_ID, Sensor.TYPE_ACCELEROMETER),
+                createInputSensorInfo(DEVICE_ID, Sensor.TYPE_GYROSCOPE)});
+
+        when(mIInputManagerMock.enableSensor(eq(DEVICE_ID), anyInt(), anyInt(), anyInt()))
+                .thenReturn(true);
+
+        when(mIInputManagerMock.registerSensorListener(any())).thenReturn(true);
+    }
+
+    @After
+    public void tearDown() {
+        if (mInputManagerGlobalSession != null) {
+            mInputManagerGlobalSession.close();
+        }
+    }
+
+    private class InputTestSensorEventListener implements SensorEventListener {
+        @GuardedBy("mLock")
+        private final BlockingQueue<SensorEvent> mEvents = new LinkedBlockingQueue<>();
+        InputTestSensorEventListener() {
+            super();
+        }
+
+        public SensorEvent waitForSensorEvent() {
+            try {
+                return mEvents.poll(5, TimeUnit.SECONDS);
+            } catch (InterruptedException e) {
+                fail("unexpectedly interrupted while waiting for SensorEvent");
+                return null;
+            }
+        }
+
+        @Override
+        public void onSensorChanged(SensorEvent event) {
+            synchronized (mLock) {
+                try {
+                    mEvents.put(event);
+                } catch (InterruptedException ex) {
+                    fail("interrupted while adding a SensorEvent to the queue");
+                }
+            }
+        }
+
+        @Override
+        public void onAccuracyChanged(Sensor sensor, int accuracy) {
+        }
+    }
+
+    private InputDevice createInputDeviceWithSensor(int id) {
+        return new InputDevice.Builder()
+                .setId(id)
+                .setName("Test Device " + id)
+                .setHasSensor(true)
+                .build();
+    }
+
+    private InputSensorInfo createInputSensorInfo(int id, int type) {
+        InputSensorInfo info = new InputSensorInfo("name", "vendor", 0 /* version */,
+                0 /* handle */, type, 100.0f /*maxRange */, 0.02f /* resolution */,
+                0.8f /* power */, 1000 /* minDelay */, 0 /* fifoReservedEventCount */,
+                0 /* fifoMaxEventCount */, "" /* stringType */, "" /* requiredPermission */,
+                0 /* maxDelay */, 0 /* flags */, id);
+        return info;
+    }
+
+    private InputDevice getSensorDevice(int[] deviceIds) {
+        for (int deviceId : deviceIds) {
+            InputDevice device = mInputManager.getInputDevice(deviceId);
+            if (device.hasSensor()) {
+                return device;
+            }
+        }
+        return null;
+    }
+
+    @Test
+    public void getInputDeviceSensors_withExpectedType() throws Exception {
+        InputDevice device = getSensorDevice(mInputManager.getInputDeviceIds());
+        assertNotNull(device);
+
+        SensorManager sensorManager = device.getSensorManager();
+        List<Sensor> accelList = sensorManager.getSensorList(Sensor.TYPE_ACCELEROMETER);
+        verify(mIInputManagerMock).getSensorList(eq(DEVICE_ID));
+        assertEquals(1, accelList.size());
+        assertEquals(DEVICE_ID, accelList.get(0).getId());
+        assertEquals(Sensor.TYPE_ACCELEROMETER, accelList.get(0).getType());
+
+        List<Sensor> gyroList = sensorManager.getSensorList(Sensor.TYPE_GYROSCOPE);
+        verify(mIInputManagerMock).getSensorList(eq(DEVICE_ID));
+        assertEquals(1, gyroList.size());
+        assertEquals(DEVICE_ID, gyroList.get(0).getId());
+        assertEquals(Sensor.TYPE_GYROSCOPE, gyroList.get(0).getType());
+
+    }
+
+    @Test
+    public void getInputDeviceSensors_withUnexpectedType() throws Exception {
+        InputDevice device = getSensorDevice(mInputManager.getInputDeviceIds());
+
+        assertNotNull(device);
+        SensorManager sensorManager = device.getSensorManager();
+
+        List<Sensor> gameRotationList = sensorManager.getSensorList(
+                Sensor.TYPE_GAME_ROTATION_VECTOR);
+        verify(mIInputManagerMock).getSensorList(eq(DEVICE_ID));
+        assertEquals(0, gameRotationList.size());
+
+        List<Sensor> gravityList = sensorManager.getSensorList(Sensor.TYPE_GRAVITY);
+        verify(mIInputManagerMock).getSensorList(eq(DEVICE_ID));
+        assertEquals(0, gravityList.size());
+    }
+
+    @Test
+    public void testInputDeviceSensorListener() throws Exception {
+        InputDevice device = getSensorDevice(mInputManager.getInputDeviceIds());
+        assertNotNull(device);
+
+        SensorManager sensorManager = device.getSensorManager();
+        Sensor sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
+        assertEquals(Sensor.TYPE_ACCELEROMETER, sensor.getType());
+
+        doAnswer(invocation -> {
+            mIInputSensorEventListener = invocation.getArgument(0);
+            assertNotNull(mIInputSensorEventListener);
+            return true;
+        }).when(mIInputManagerMock).registerSensorListener(any());
+
+        InputTestSensorEventListener listener = new InputTestSensorEventListener();
+        assertTrue(sensorManager.registerListener(listener, sensor,
+                SensorManager.SENSOR_DELAY_NORMAL));
+        verify(mIInputManagerMock).registerSensorListener(any());
+        verify(mIInputManagerMock).enableSensor(eq(DEVICE_ID), eq(sensor.getType()),
+                anyInt(), anyInt());
+
+        float[] values = new float[] {0.12f, 9.8f, 0.2f};
+        mIInputSensorEventListener.onInputSensorChanged(DEVICE_ID, Sensor.TYPE_ACCELEROMETER,
+                SensorManager.SENSOR_STATUS_ACCURACY_HIGH,  /* timestamp */ 0x1234abcd, values);
+
+        SensorEvent event = listener.waitForSensorEvent();
+        assertNotNull(event);
+        assertEquals(0x1234abcd, event.timestamp);
+        assertEquals(values.length, event.values.length);
+        for (int i = 0; i < values.length; i++) {
+            assertEquals(values[i], event.values[i], 0.001f);
+        }
+
+        sensorManager.unregisterListener(listener);
+        verify(mIInputManagerMock).disableSensor(eq(DEVICE_ID), eq(sensor.getType()));
+    }
+
+}
diff --git a/tests/Input/src/android/hardware/input/InputManagerTest.kt b/tests/Input/src/android/hardware/input/InputManagerTest.kt
new file mode 100644
index 0000000..152dde9
--- /dev/null
+++ b/tests/Input/src/android/hardware/input/InputManagerTest.kt
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.hardware.input
+
+import android.content.Context
+import android.content.ContextWrapper
+import android.content.res.Resources
+import android.platform.test.annotations.Presubmit
+import android.view.Display
+import android.view.DisplayInfo
+import android.view.InputDevice
+import androidx.test.core.app.ApplicationProvider
+import org.junit.After
+import org.junit.Assert.assertNotNull
+import org.junit.Assert.assertEquals
+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.Mockito.eq
+import org.mockito.Mockito.`when`
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoJUnitRunner
+
+/**
+ * Tests for [InputManager].
+ *
+ * Build/Install/Run:
+ * atest InputTests:InputManagerTest
+ */
+@Presubmit
+@RunWith(MockitoJUnitRunner::class)
+class InputManagerTest {
+
+    companion object {
+        const val DEVICE_ID = 42
+        const val SECOND_DEVICE_ID = 96
+        const val THIRD_DEVICE_ID = 99
+    }
+
+    @get:Rule
+    val rule = MockitoJUnit.rule()!!
+
+    private lateinit var devicesChangedListener: IInputDevicesChangedListener
+    private val deviceGenerationMap = mutableMapOf<Int /*deviceId*/, Int /*generation*/>()
+    private lateinit var context: Context
+    private lateinit var inputManager: InputManager
+
+    @Mock
+    private lateinit var iInputManager: IInputManager
+    private lateinit var inputManagerGlobalSession: InputManagerGlobal.TestSession
+
+    @Before
+    fun setUp() {
+        context = Mockito.spy(ContextWrapper(ApplicationProvider.getApplicationContext()))
+        inputManagerGlobalSession = InputManagerGlobal.createTestSession(iInputManager)
+        inputManager = InputManager(context)
+        `when`(context.getSystemService(eq(Context.INPUT_SERVICE))).thenReturn(inputManager)
+        `when`(iInputManager.inputDeviceIds).then {
+            deviceGenerationMap.keys.toIntArray()
+        }
+    }
+
+    @After
+    fun tearDown() {
+        if (this::inputManagerGlobalSession.isInitialized) {
+            inputManagerGlobalSession.close()
+        }
+    }
+
+    private fun notifyDeviceChanged(
+        deviceId: Int,
+        associatedDisplayId: Int,
+        usiVersion: HostUsiVersion?,
+    ) {
+        val generation = deviceGenerationMap[deviceId]?.plus(1)
+            ?: throw IllegalArgumentException("Device $deviceId was never added!")
+        deviceGenerationMap[deviceId] = generation
+
+        `when`(iInputManager.getInputDevice(deviceId))
+            .thenReturn(createInputDevice(deviceId, associatedDisplayId, usiVersion, generation))
+        val list = deviceGenerationMap.flatMap { listOf(it.key, it.value) }
+        if (::devicesChangedListener.isInitialized) {
+            devicesChangedListener.onInputDevicesChanged(list.toIntArray())
+        }
+    }
+
+    private fun addInputDevice(
+        deviceId: Int,
+        associatedDisplayId: Int,
+        usiVersion: HostUsiVersion?,
+    ) {
+        deviceGenerationMap[deviceId] = 0
+        notifyDeviceChanged(deviceId, associatedDisplayId, usiVersion)
+    }
+
+    @Test
+    fun testUsiVersionDisplayAssociation() {
+        addInputDevice(DEVICE_ID, Display.DEFAULT_DISPLAY, null)
+        addInputDevice(SECOND_DEVICE_ID, Display.INVALID_DISPLAY, HostUsiVersion(9, 8))
+        addInputDevice(THIRD_DEVICE_ID, 42, HostUsiVersion(3, 1))
+
+        val usiVersion = inputManager.getHostUsiVersion(createDisplay(42))
+        assertNotNull(usiVersion)
+        assertEquals(3, usiVersion!!.majorVersion)
+        assertEquals(1, usiVersion.minorVersion)
+    }
+
+    @Test
+    fun testUsiVersionFallBackToDisplayConfig() {
+        addInputDevice(DEVICE_ID, Display.DEFAULT_DISPLAY, null)
+
+        `when`(iInputManager.getHostUsiVersionFromDisplayConfig(eq(42)))
+            .thenReturn(HostUsiVersion(9, 8))
+        val usiVersion = inputManager.getHostUsiVersion(createDisplay(42))
+        assertEquals(HostUsiVersion(9, 8), usiVersion)
+    }
+}
+
+private fun createInputDevice(
+    deviceId: Int,
+    associatedDisplayId: Int,
+    usiVersion: HostUsiVersion? = null,
+    generation: Int = -1,
+): InputDevice =
+    InputDevice.Builder()
+        .setId(deviceId)
+        .setName("Device $deviceId")
+        .setDescriptor("descriptor $deviceId")
+        .setAssociatedDisplayId(associatedDisplayId)
+        .setUsiVersion(usiVersion)
+        .setGeneration(generation)
+        .build()
+
+private fun createDisplay(displayId: Int): Display {
+    val res: Resources? = null
+    return Display(null /* global */, displayId, DisplayInfo(), res)
+}
diff --git a/tests/Input/src/android/hardware/input/KeyboardBacklightListenerTest.kt b/tests/Input/src/android/hardware/input/KeyboardBacklightListenerTest.kt
new file mode 100644
index 0000000..23135b2
--- /dev/null
+++ b/tests/Input/src/android/hardware/input/KeyboardBacklightListenerTest.kt
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.input
+
+import android.content.Context
+import android.content.ContextWrapper
+import android.os.Handler
+import android.os.HandlerExecutor
+import android.os.test.TestLooper
+import android.platform.test.annotations.Presubmit
+import androidx.test.core.app.ApplicationProvider
+import com.android.server.testutils.any
+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
+import org.mockito.Mockito
+import org.mockito.Mockito.doAnswer
+import org.mockito.Mockito.`when`
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoJUnitRunner
+import java.util.concurrent.Executor
+import kotlin.test.assertEquals
+import kotlin.test.assertNotNull
+import kotlin.test.assertNull
+import kotlin.test.fail
+
+/**
+ * Tests for [InputManager.KeyboardBacklightListener].
+ *
+ * Build/Install/Run:
+ * atest InputTests:KeyboardBacklightListenerTest
+ */
+@Presubmit
+@RunWith(MockitoJUnitRunner::class)
+class KeyboardBacklightListenerTest {
+    @get:Rule
+    val rule = MockitoJUnit.rule()!!
+
+    private lateinit var testLooper: TestLooper
+    private var registeredListener: IKeyboardBacklightListener? = null
+    private lateinit var executor: Executor
+    private lateinit var context: Context
+    private lateinit var inputManager: InputManager
+    private lateinit var inputManagerGlobalSession: InputManagerGlobal.TestSession
+
+    @Mock
+    private lateinit var iInputManagerMock: IInputManager
+
+    @Before
+    fun setUp() {
+        context = Mockito.spy(ContextWrapper(ApplicationProvider.getApplicationContext()))
+        inputManagerGlobalSession = InputManagerGlobal.createTestSession(iInputManagerMock)
+        testLooper = TestLooper()
+        executor = HandlerExecutor(Handler(testLooper.looper))
+        registeredListener = null
+        inputManager = InputManager(context)
+        `when`(context.getSystemService(Mockito.eq(Context.INPUT_SERVICE)))
+                .thenReturn(inputManager)
+
+        // Handle keyboard backlight listener registration.
+        doAnswer {
+            val listener = it.getArgument(0) as IKeyboardBacklightListener
+            if (registeredListener != null &&
+                    registeredListener!!.asBinder() != listener.asBinder()) {
+                // There can only be one registered keyboard backlight listener per process.
+                fail("Trying to register a new listener when one already exists")
+            }
+            registeredListener = listener
+            null
+        }.`when`(iInputManagerMock).registerKeyboardBacklightListener(any())
+
+        // Handle keyboard backlight listener being unregistered.
+        doAnswer {
+            val listener = it.getArgument(0) as IKeyboardBacklightListener
+            if (registeredListener == null ||
+                    registeredListener!!.asBinder() != listener.asBinder()) {
+                fail("Trying to unregister a listener that is not registered")
+            }
+            registeredListener = null
+            null
+        }.`when`(iInputManagerMock).unregisterKeyboardBacklightListener(any())
+    }
+
+    @After
+    fun tearDown() {
+        if (this::inputManagerGlobalSession.isInitialized) {
+            inputManagerGlobalSession.close()
+        }
+    }
+
+    private fun notifyKeyboardBacklightChanged(
+        deviceId: Int,
+        brightnessLevel: Int,
+        maxBrightnessLevel: Int = 10,
+        isTriggeredByKeyPress: Boolean = true
+    ) {
+        registeredListener!!.onBrightnessChanged(deviceId, IKeyboardBacklightState().apply {
+            this.brightnessLevel = brightnessLevel
+            this.maxBrightnessLevel = maxBrightnessLevel
+        }, isTriggeredByKeyPress)
+    }
+
+    @Test
+    fun testListenerIsNotifiedCorrectly() {
+        var callbackCount = 0
+
+        // Add a keyboard backlight listener
+        inputManager.registerKeyboardBacklightListener(executor) {
+                deviceId: Int,
+                keyboardBacklightState: KeyboardBacklightState,
+                isTriggeredByKeyPress: Boolean ->
+            callbackCount++
+            assertEquals(1, deviceId)
+            assertEquals(2, keyboardBacklightState.brightnessLevel)
+            assertEquals(10, keyboardBacklightState.maxBrightnessLevel)
+            assertEquals(true, isTriggeredByKeyPress)
+        }
+
+        // Adding the listener should register the callback with InputManagerService.
+        assertNotNull(registeredListener)
+
+        // Notifying keyboard backlight change will notify the listener.
+        notifyKeyboardBacklightChanged(1 /*deviceId*/, 2 /* brightnessLevel */)
+        testLooper.dispatchNext()
+        assertEquals(1, callbackCount)
+    }
+
+    @Test
+    fun testMultipleListeners() {
+        // Set up two callbacks.
+        var callbackCount1 = 0
+        var callbackCount2 = 0
+        val callback1 = InputManager.KeyboardBacklightListener { _, _, _ -> callbackCount1++ }
+        val callback2 = InputManager.KeyboardBacklightListener { _, _, _ -> callbackCount2++ }
+
+        // Add both keyboard backlight listeners
+        inputManager.registerKeyboardBacklightListener(executor, callback1)
+        inputManager.registerKeyboardBacklightListener(executor, callback2)
+
+        // Adding the listeners should register the callback with InputManagerService.
+        assertNotNull(registeredListener)
+
+        // Notifying keyboard backlight change trigger the both callbacks.
+        notifyKeyboardBacklightChanged(1 /*deviceId*/, 1 /* brightnessLevel */)
+        testLooper.dispatchAll()
+        assertEquals(1, callbackCount1)
+        assertEquals(1, callbackCount2)
+
+        inputManager.unregisterKeyboardBacklightListener(callback2)
+        // Notifying keyboard backlight change should still trigger callback1.
+        notifyKeyboardBacklightChanged(1 /*deviceId*/, 2 /* brightnessLevel */)
+        testLooper.dispatchAll()
+        assertEquals(2, callbackCount1)
+
+        // Unregister all listeners, should remove registered listener from InputManagerService
+        inputManager.unregisterKeyboardBacklightListener(callback1)
+        assertNull(registeredListener)
+    }
+}
diff --git a/core/tests/coretests/src/android/hardware/input/VirtualKeyEventTest.java b/tests/Input/src/android/hardware/input/VirtualKeyEventTest.java
similarity index 100%
rename from core/tests/coretests/src/android/hardware/input/VirtualKeyEventTest.java
rename to tests/Input/src/android/hardware/input/VirtualKeyEventTest.java
diff --git a/core/tests/coretests/src/android/hardware/input/VirtualMouseButtonEventTest.java b/tests/Input/src/android/hardware/input/VirtualMouseButtonEventTest.java
similarity index 100%
rename from core/tests/coretests/src/android/hardware/input/VirtualMouseButtonEventTest.java
rename to tests/Input/src/android/hardware/input/VirtualMouseButtonEventTest.java
diff --git a/core/tests/coretests/src/android/hardware/input/VirtualMouseRelativeEventTest.java b/tests/Input/src/android/hardware/input/VirtualMouseRelativeEventTest.java
similarity index 100%
rename from core/tests/coretests/src/android/hardware/input/VirtualMouseRelativeEventTest.java
rename to tests/Input/src/android/hardware/input/VirtualMouseRelativeEventTest.java
diff --git a/core/tests/coretests/src/android/hardware/input/VirtualMouseScrollEventTest.java b/tests/Input/src/android/hardware/input/VirtualMouseScrollEventTest.java
similarity index 100%
rename from core/tests/coretests/src/android/hardware/input/VirtualMouseScrollEventTest.java
rename to tests/Input/src/android/hardware/input/VirtualMouseScrollEventTest.java
diff --git a/core/tests/coretests/src/android/hardware/input/VirtualTouchEventTest.java b/tests/Input/src/android/hardware/input/VirtualTouchEventTest.java
similarity index 100%
rename from core/tests/coretests/src/android/hardware/input/VirtualTouchEventTest.java
rename to tests/Input/src/android/hardware/input/VirtualTouchEventTest.java
diff --git a/tests/Input/src/com/android/server/input/AmbientKeyboardBacklightControllerTests.kt b/tests/Input/src/com/android/server/input/AmbientKeyboardBacklightControllerTests.kt
new file mode 100644
index 0000000..ad481df
--- /dev/null
+++ b/tests/Input/src/com/android/server/input/AmbientKeyboardBacklightControllerTests.kt
@@ -0,0 +1,355 @@
+/*
+ * 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.input
+
+import android.content.Context
+import android.content.ContextWrapper
+import android.content.res.Resources
+import android.hardware.Sensor
+import android.hardware.SensorEvent
+import android.hardware.SensorEventListener
+import android.hardware.SensorManager
+import android.hardware.display.DisplayManagerInternal
+import android.hardware.input.InputSensorInfo
+import android.os.Handler
+import android.os.test.TestLooper
+import android.platform.test.annotations.Presubmit
+import android.util.TypedValue
+import android.view.Display
+import android.view.DisplayInfo
+import androidx.test.core.app.ApplicationProvider
+import com.android.internal.R
+import com.android.server.LocalServices
+import com.android.server.input.AmbientKeyboardBacklightController.HYSTERESIS_THRESHOLD
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertThrows
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito.any
+import org.mockito.Mockito.anyBoolean
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.eq
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.`when`
+import org.mockito.junit.MockitoJUnit
+
+/**
+ * Tests for {@link AmbientKeyboardBacklightController}.
+ *
+ * Build/Install/Run:
+ * atest InputTests:AmbientKeyboardBacklightControllerTests
+ */
+@Presubmit
+class AmbientKeyboardBacklightControllerTests {
+
+    companion object {
+        const val DEFAULT_DISPLAY_UNIQUE_ID = "uniqueId_1"
+        const val SENSOR_NAME = "test_sensor_name"
+        const val SENSOR_TYPE = "test_sensor_type"
+    }
+
+    @get:Rule
+    val rule = MockitoJUnit.rule()!!
+
+    private lateinit var context: Context
+    private lateinit var testLooper: TestLooper
+    private lateinit var ambientController: AmbientKeyboardBacklightController
+
+    @Mock
+    private lateinit var resources: Resources
+
+    @Mock
+    private lateinit var lightSensorInfo: InputSensorInfo
+
+    @Mock
+    private lateinit var sensorManager: SensorManager
+
+    @Mock
+    private lateinit var displayManagerInternal: DisplayManagerInternal
+    private lateinit var lightSensor: Sensor
+
+    private var currentDisplayInfo = DisplayInfo()
+    private var lastBrightnessCallback: Int = 0
+    private var listenerRegistered: Boolean = false
+    private var listenerRegistrationCount: Int = 0
+
+    @Before
+    fun setup() {
+        context = spy(ContextWrapper(ApplicationProvider.getApplicationContext()))
+        `when`(context.resources).thenReturn(resources)
+        setupBrightnessSteps()
+        setupSensor()
+        testLooper = TestLooper()
+        ambientController = AmbientKeyboardBacklightController(context, testLooper.looper)
+        ambientController.systemRunning()
+        testLooper.dispatchAll()
+    }
+
+    private fun setupBrightnessSteps() {
+        val brightnessValues = intArrayOf(100, 200, 0)
+        val decreaseThresholds = intArrayOf(-1, 900, 1900)
+        val increaseThresholds = intArrayOf(1000, 2000, -1)
+        `when`(resources.getIntArray(R.array.config_autoKeyboardBacklightBrightnessValues))
+            .thenReturn(brightnessValues)
+        `when`(resources.getIntArray(R.array.config_autoKeyboardBacklightDecreaseLuxThreshold))
+            .thenReturn(decreaseThresholds)
+        `when`(resources.getIntArray(R.array.config_autoKeyboardBacklightIncreaseLuxThreshold))
+            .thenReturn(increaseThresholds)
+        `when`(
+            resources.getValue(
+                eq(R.dimen.config_autoKeyboardBrightnessSmoothingConstant),
+                any(TypedValue::class.java),
+                anyBoolean()
+            )
+        ).then {
+            val args = it.arguments
+            val outValue = args[1] as TypedValue
+            outValue.data = java.lang.Float.floatToRawIntBits(1.0f)
+            Unit
+        }
+    }
+
+    private fun setupSensor() {
+        LocalServices.removeServiceForTest(DisplayManagerInternal::class.java)
+        LocalServices.addService(DisplayManagerInternal::class.java, displayManagerInternal)
+        currentDisplayInfo.uniqueId = DEFAULT_DISPLAY_UNIQUE_ID
+        `when`(displayManagerInternal.getDisplayInfo(Display.DEFAULT_DISPLAY)).thenReturn(
+            currentDisplayInfo
+        )
+        val sensorData = DisplayManagerInternal.AmbientLightSensorData(SENSOR_NAME, SENSOR_TYPE)
+        `when`(displayManagerInternal.getAmbientLightSensorData(Display.DEFAULT_DISPLAY))
+            .thenReturn(sensorData)
+
+        `when`(lightSensorInfo.name).thenReturn(SENSOR_NAME)
+        `when`(lightSensorInfo.stringType).thenReturn(SENSOR_TYPE)
+        lightSensor = Sensor(lightSensorInfo)
+        `when`(context.getSystemService(eq(Context.SENSOR_SERVICE))).thenReturn(sensorManager)
+        `when`(sensorManager.getSensorList(anyInt())).thenReturn(listOf(lightSensor))
+        `when`(
+            sensorManager.registerListener(
+                any(),
+                eq(lightSensor),
+                anyInt(),
+                any(Handler::class.java)
+            )
+        ).then {
+            listenerRegistered = true
+            listenerRegistrationCount++
+            true
+        }
+        `when`(
+            sensorManager.unregisterListener(
+                any(SensorEventListener::class.java),
+                eq(lightSensor)
+            )
+        ).then {
+            listenerRegistered = false
+            Unit
+        }
+    }
+
+    private fun setupSensorWithInitialLux(luxValue: Float) {
+        ambientController.registerAmbientBacklightListener { brightnessValue: Int ->
+            lastBrightnessCallback = brightnessValue
+        }
+        sendAmbientLuxValue(luxValue)
+        testLooper.dispatchAll()
+    }
+
+    @Test
+    fun testInitialAmbientLux_sendsCallbackImmediately() {
+        setupSensorWithInitialLux(500F)
+
+        assertEquals(
+            "Should receive immediate callback for first lux change",
+            100,
+            lastBrightnessCallback
+        )
+    }
+
+    @Test
+    fun testBrightnessIncrease_afterInitialLuxChanges() {
+        setupSensorWithInitialLux(500F)
+
+        // Current state: Step 1 [value = 100, increaseThreshold = 1000, decreaseThreshold = -1]
+        repeat(HYSTERESIS_THRESHOLD) {
+            sendAmbientLuxValue(1500F)
+        }
+        testLooper.dispatchAll()
+
+        assertEquals(
+            "Should receive brightness change callback for increasing lux change",
+            200,
+            lastBrightnessCallback
+        )
+    }
+
+    @Test
+    fun testBrightnessDecrease_afterInitialLuxChanges() {
+        setupSensorWithInitialLux(1500F)
+
+        // Current state: Step 2 [value = 200, increaseThreshold = 2000, decreaseThreshold = 900]
+        repeat(HYSTERESIS_THRESHOLD) {
+            sendAmbientLuxValue(500F)
+        }
+        testLooper.dispatchAll()
+
+        assertEquals(
+            "Should receive brightness change callback for decreasing lux change",
+            100,
+            lastBrightnessCallback
+        )
+    }
+
+    @Test
+    fun testRegisterAmbientListener_throwsExceptionOnRegisteringDuplicate() {
+        val ambientListener =
+            AmbientKeyboardBacklightController.AmbientKeyboardBacklightListener { }
+        ambientController.registerAmbientBacklightListener(ambientListener)
+
+        assertThrows(IllegalStateException::class.java) {
+            ambientController.registerAmbientBacklightListener(
+                ambientListener
+            )
+        }
+    }
+
+    @Test
+    fun testUnregisterAmbientListener_throwsExceptionOnUnregisteringNonExistent() {
+        val ambientListener =
+            AmbientKeyboardBacklightController.AmbientKeyboardBacklightListener { }
+        assertThrows(IllegalStateException::class.java) {
+            ambientController.unregisterAmbientBacklightListener(
+                ambientListener
+            )
+        }
+    }
+
+    @Test
+    fun testSensorListenerRegistered_onRegisterUnregisterAmbientListener() {
+        assertEquals(
+            "Should not have a sensor listener registered at init",
+            0,
+            listenerRegistrationCount
+        )
+        assertFalse("Should not have a sensor listener registered at init", listenerRegistered)
+
+        val ambientListener1 =
+            AmbientKeyboardBacklightController.AmbientKeyboardBacklightListener { }
+        ambientController.registerAmbientBacklightListener(ambientListener1)
+        assertEquals(
+            "Should register a new sensor listener", 1, listenerRegistrationCount
+        )
+        assertTrue("Should have sensor listener registered", listenerRegistered)
+
+        val ambientListener2 =
+            AmbientKeyboardBacklightController.AmbientKeyboardBacklightListener { }
+        ambientController.registerAmbientBacklightListener(ambientListener2)
+        assertEquals(
+            "Should not register a new sensor listener when adding a second ambient listener",
+            1,
+            listenerRegistrationCount
+        )
+        assertTrue("Should have sensor listener registered", listenerRegistered)
+
+        ambientController.unregisterAmbientBacklightListener(ambientListener1)
+        assertTrue("Should have sensor listener registered", listenerRegistered)
+
+        ambientController.unregisterAmbientBacklightListener(ambientListener2)
+        assertFalse(
+            "Should not have sensor listener registered if there are no ambient listeners",
+            listenerRegistered
+        )
+    }
+
+    @Test
+    fun testDisplayChange_shouldNotReRegisterListener_ifUniqueIdSame() {
+        setupSensorWithInitialLux(0F)
+
+        val count = listenerRegistrationCount
+        ambientController.onDisplayChanged(Display.DEFAULT_DISPLAY)
+        testLooper.dispatchAll()
+
+        assertEquals(
+            "Should not re-register listener on display change if unique is same",
+            count,
+            listenerRegistrationCount
+        )
+    }
+
+    @Test
+    fun testDisplayChange_shouldReRegisterListener_ifUniqueIdChanges() {
+        setupSensorWithInitialLux(0F)
+
+        val count = listenerRegistrationCount
+        currentDisplayInfo.uniqueId = "xyz"
+        ambientController.onDisplayChanged(Display.DEFAULT_DISPLAY)
+        testLooper.dispatchAll()
+
+        assertEquals(
+            "Should re-register listener on display change if unique id changed",
+            count + 1,
+            listenerRegistrationCount
+        )
+    }
+
+    @Test
+    fun testBrightnessDoesntChange_betweenIncreaseAndDecreaseThresholds() {
+        setupSensorWithInitialLux(1001F)
+
+        // Previous state: Step 1 [value = 100, increaseThreshold = 1000, decreaseThreshold = -1]
+        // Current state: Step 2 [value = 200, increaseThreshold = 2000, decreaseThreshold = 900]
+        lastBrightnessCallback = -1
+        repeat(HYSTERESIS_THRESHOLD) {
+            sendAmbientLuxValue(999F)
+        }
+        testLooper.dispatchAll()
+
+        assertEquals(
+            "Should not receive any callback for brightness change",
+            -1,
+            lastBrightnessCallback
+        )
+    }
+
+    @Test
+    fun testBrightnessDoesntChange_onChangeOccurringLessThanHysteresisThreshold() {
+        setupSensorWithInitialLux(1001F)
+
+        // Previous state: Step 1 [value = 100, increaseThreshold = 1000, decreaseThreshold = -1]
+        // Current state: Step 2 [value = 200, increaseThreshold = 2000, decreaseThreshold = 900]
+        lastBrightnessCallback = -1
+        repeat(HYSTERESIS_THRESHOLD - 1) {
+            sendAmbientLuxValue(2001F)
+        }
+        testLooper.dispatchAll()
+
+        assertEquals(
+            "Should not receive any callback for brightness change",
+            -1,
+            lastBrightnessCallback
+        )
+    }
+
+    private fun sendAmbientLuxValue(luxValue: Float) {
+        ambientController.onSensorChanged(SensorEvent(lightSensor, 0, 0, floatArrayOf(luxValue)))
+    }
+}
diff --git a/tests/Input/src/com/android/server/input/BatteryControllerTests.kt b/tests/Input/src/com/android/server/input/BatteryControllerTests.kt
new file mode 100644
index 0000000..f2724e6
--- /dev/null
+++ b/tests/Input/src/com/android/server/input/BatteryControllerTests.kt
@@ -0,0 +1,898 @@
+/*
+ * 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.input
+
+import android.bluetooth.BluetoothAdapter
+import android.bluetooth.BluetoothDevice
+import android.bluetooth.BluetoothManager
+import android.hardware.BatteryState.STATUS_CHARGING
+import android.hardware.BatteryState.STATUS_DISCHARGING
+import android.hardware.BatteryState.STATUS_FULL
+import android.hardware.BatteryState.STATUS_UNKNOWN
+import android.hardware.input.HostUsiVersion
+import android.hardware.input.IInputDeviceBatteryListener
+import android.hardware.input.IInputDeviceBatteryState
+import android.hardware.input.IInputDevicesChangedListener
+import android.hardware.input.IInputManager
+import android.hardware.input.InputManager
+import android.hardware.input.InputManagerGlobal
+import android.os.Binder
+import android.os.IBinder
+import android.os.test.TestLooper
+import android.platform.test.annotations.Presubmit
+import android.testing.TestableContext
+import android.view.InputDevice
+import androidx.test.core.app.ApplicationProvider
+import com.android.server.input.BatteryController.BluetoothBatteryManager
+import com.android.server.input.BatteryController.BluetoothBatteryManager.BluetoothBatteryListener
+import com.android.server.input.BatteryController.POLLING_PERIOD_MILLIS
+import com.android.server.input.BatteryController.UEventBatteryListener
+import com.android.server.input.BatteryController.USI_BATTERY_VALIDITY_DURATION_MILLIS
+import org.hamcrest.Description
+import org.hamcrest.Matcher
+import org.hamcrest.MatcherAssert.assertThat
+import org.hamcrest.Matchers
+import org.hamcrest.TypeSafeMatcher
+import org.hamcrest.core.IsEqual.equalTo
+import org.junit.After
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Assert.fail
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.notNull
+import org.mockito.Mock
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.eq
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.Mockito.`when`
+import org.mockito.hamcrest.MockitoHamcrest
+import org.mockito.junit.MockitoJUnit
+import org.mockito.verification.VerificationMode
+
+private fun createInputDevice(
+    deviceId: Int,
+    hasBattery: Boolean = true,
+    supportsUsi: Boolean = false,
+    generation: Int = -1,
+): InputDevice =
+    InputDevice.Builder()
+        .setId(deviceId)
+        .setName("Device $deviceId")
+        .setDescriptor("descriptor $deviceId")
+        .setExternal(true)
+        .setHasBattery(hasBattery)
+        .setUsiVersion(if (supportsUsi) HostUsiVersion(1, 0) else null)
+        .setGeneration(generation)
+        .build()
+
+// Returns a matcher that helps match member variables of a class.
+private fun <T, U> memberMatcher(
+    member: String,
+    memberProvider: (T) -> U,
+    match: Matcher<U>
+): TypeSafeMatcher<T> =
+    object : TypeSafeMatcher<T>() {
+
+        override fun matchesSafely(item: T?): Boolean {
+            return match.matches(memberProvider(item!!))
+        }
+
+        override fun describeMismatchSafely(item: T?, mismatchDescription: Description?) {
+            match.describeMismatch(item, mismatchDescription)
+        }
+
+        override fun describeTo(description: Description?) {
+            match.describeTo(description?.appendText("matches member $member"))
+        }
+    }
+
+// Returns a matcher for IInputDeviceBatteryState that optionally matches some arguments.
+private fun matchesState(
+    deviceId: Int,
+    isPresent: Boolean = true,
+    status: Int? = null,
+    capacity: Float? = null,
+    eventTime: Long? = null
+): Matcher<IInputDeviceBatteryState> {
+    val batteryStateMatchers = mutableListOf<Matcher<IInputDeviceBatteryState>>(
+        memberMatcher("deviceId", { it.deviceId }, equalTo(deviceId)),
+        memberMatcher("isPresent", { it.isPresent }, equalTo(isPresent))
+    )
+    if (eventTime != null) {
+        batteryStateMatchers.add(memberMatcher("updateTime", { it.updateTime }, equalTo(eventTime)))
+    }
+    if (status != null) {
+        batteryStateMatchers.add(memberMatcher("status", { it.status }, equalTo(status)))
+    }
+    if (capacity != null) {
+        batteryStateMatchers.add(memberMatcher("capacity", { it.capacity }, equalTo(capacity)))
+    }
+    return Matchers.allOf(batteryStateMatchers)
+}
+
+private fun isInvalidBatteryState(deviceId: Int): Matcher<IInputDeviceBatteryState> =
+    matchesState(deviceId, isPresent = false, status = STATUS_UNKNOWN, capacity = Float.NaN)
+
+// Helpers used to verify interactions with a mocked battery listener.
+private fun IInputDeviceBatteryListener.verifyNotified(
+    deviceId: Int,
+    mode: VerificationMode = times(1),
+    isPresent: Boolean = true,
+    status: Int? = null,
+    capacity: Float? = null,
+    eventTime: Long? = null
+) {
+    verifyNotified(matchesState(deviceId, isPresent, status, capacity, eventTime), mode)
+}
+
+private fun IInputDeviceBatteryListener.verifyNotified(
+    matcher: Matcher<IInputDeviceBatteryState>,
+    mode: VerificationMode = times(1)
+) {
+    verify(this, mode).onBatteryStateChanged(MockitoHamcrest.argThat(matcher))
+}
+
+private fun createMockListener(): IInputDeviceBatteryListener {
+    val listener = mock(IInputDeviceBatteryListener::class.java)
+    val binder = mock(Binder::class.java)
+    `when`(listener.asBinder()).thenReturn(binder)
+    return listener
+}
+
+/**
+ * Tests for {@link InputDeviceBatteryController}.
+ *
+ * Build/Install/Run:
+ * atest InputTests:InputDeviceBatteryControllerTests
+ */
+@Presubmit
+class BatteryControllerTests {
+    companion object {
+        const val PID = 42
+        const val DEVICE_ID = 13
+        const val SECOND_DEVICE_ID = 11
+        const val USI_DEVICE_ID = 101
+        const val SECOND_USI_DEVICE_ID = 102
+        const val BT_DEVICE_ID = 100001
+        const val SECOND_BT_DEVICE_ID = 100002
+        const val TIMESTAMP = 123456789L
+    }
+
+    @get:Rule
+    val rule = MockitoJUnit.rule()!!
+
+    @Mock
+    private lateinit var native: NativeInputManagerService
+    @Mock
+    private lateinit var iInputManager: IInputManager
+    @Mock
+    private lateinit var uEventManager: UEventManager
+    @Mock
+    private lateinit var bluetoothBatteryManager: BluetoothBatteryManager
+
+    private lateinit var batteryController: BatteryController
+    private lateinit var context: TestableContext
+    private lateinit var testLooper: TestLooper
+    private lateinit var devicesChangedListener: IInputDevicesChangedListener
+    private lateinit var inputManagerGlobalSession: InputManagerGlobal.TestSession
+    private val deviceGenerationMap = mutableMapOf<Int /*deviceId*/, Int /*generation*/>()
+
+    @Before
+    fun setup() {
+        context = TestableContext(ApplicationProvider.getApplicationContext())
+        testLooper = TestLooper()
+        inputManagerGlobalSession = InputManagerGlobal.createTestSession(iInputManager)
+        val inputManager = InputManager(context)
+        context.addMockSystemService(InputManager::class.java, inputManager)
+        `when`(iInputManager.inputDeviceIds).then {
+            deviceGenerationMap.keys.toIntArray()
+        }
+        addInputDevice(DEVICE_ID)
+        addInputDevice(SECOND_DEVICE_ID)
+
+        batteryController = BatteryController(context, native, testLooper.looper, uEventManager,
+            bluetoothBatteryManager)
+        batteryController.systemRunning()
+        val listenerCaptor = ArgumentCaptor.forClass(IInputDevicesChangedListener::class.java)
+        verify(iInputManager).registerInputDevicesChangedListener(listenerCaptor.capture())
+        devicesChangedListener = listenerCaptor.value
+        testLooper.dispatchAll()
+    }
+
+    @After
+    fun tearDown() {
+        if (this::inputManagerGlobalSession.isInitialized) {
+            inputManagerGlobalSession.close()
+        }
+    }
+
+    private fun notifyDeviceChanged(
+            deviceId: Int,
+        hasBattery: Boolean = true,
+        supportsUsi: Boolean = false
+    ) {
+        val generation = deviceGenerationMap[deviceId]?.plus(1)
+            ?: throw IllegalArgumentException("Device $deviceId was never added!")
+        deviceGenerationMap[deviceId] = generation
+
+        `when`(iInputManager.getInputDevice(deviceId))
+            .thenReturn(createInputDevice(deviceId, hasBattery, supportsUsi, generation))
+        val list = deviceGenerationMap.flatMap { listOf(it.key, it.value) }
+        if (::devicesChangedListener.isInitialized) {
+            devicesChangedListener.onInputDevicesChanged(list.toIntArray())
+        }
+    }
+
+    private fun addInputDevice(
+            deviceId: Int,
+        hasBattery: Boolean = true,
+        supportsUsi: Boolean = false,
+    ) {
+        deviceGenerationMap[deviceId] = 0
+        notifyDeviceChanged(deviceId, hasBattery, supportsUsi)
+    }
+
+    private fun createBluetoothDevice(address: String): BluetoothDevice {
+        return context.getSystemService(BluetoothManager::class.java)!!
+            .adapter.getRemoteDevice(address)
+    }
+
+    @Test
+    fun testRegisterAndUnregisterBinderLifecycle() {
+        val listener = createMockListener()
+        // Ensure the binder lifecycle is tracked when registering a listener.
+        batteryController.registerBatteryListener(DEVICE_ID, listener, PID)
+        verify(listener.asBinder()).linkToDeath(notNull(), anyInt())
+        batteryController.registerBatteryListener(SECOND_DEVICE_ID, listener, PID)
+        verify(listener.asBinder(), times(1)).linkToDeath(notNull(), anyInt())
+
+        // Ensure the binder lifecycle stops being tracked when all devices stopped being monitored.
+        batteryController.unregisterBatteryListener(SECOND_DEVICE_ID, listener, PID)
+        verify(listener.asBinder(), never()).unlinkToDeath(notNull(), anyInt())
+        batteryController.unregisterBatteryListener(DEVICE_ID, listener, PID)
+        verify(listener.asBinder()).unlinkToDeath(notNull(), anyInt())
+    }
+
+    @Test
+    fun testOneListenerPerProcess() {
+        val listener1 = createMockListener()
+        batteryController.registerBatteryListener(DEVICE_ID, listener1, PID)
+        verify(listener1.asBinder()).linkToDeath(notNull(), anyInt())
+
+        // If a second listener is added for the same process, a security exception is thrown.
+        val listener2 = createMockListener()
+        try {
+            batteryController.registerBatteryListener(DEVICE_ID, listener2, PID)
+            fail("Expected security exception when registering more than one listener per process")
+        } catch (ignored: SecurityException) {
+        }
+    }
+
+    @Test
+    fun testProcessDeathRemovesListener() {
+        val deathRecipient = ArgumentCaptor.forClass(IBinder.DeathRecipient::class.java)
+        val listener = createMockListener()
+        batteryController.registerBatteryListener(DEVICE_ID, listener, PID)
+        verify(listener.asBinder()).linkToDeath(deathRecipient.capture(), anyInt())
+
+        // When the binder dies, the callback is unregistered.
+        deathRecipient.value!!.binderDied(listener.asBinder())
+        verify(listener.asBinder()).unlinkToDeath(notNull(), anyInt())
+
+        // It is now possible to register the same listener again.
+        batteryController.registerBatteryListener(DEVICE_ID, listener, PID)
+        verify(listener.asBinder(), times(2)).linkToDeath(notNull(), anyInt())
+    }
+
+    @Test
+    fun testRegisteringListenerNotifiesStateImmediately() {
+        `when`(native.getBatteryStatus(DEVICE_ID)).thenReturn(STATUS_FULL)
+        `when`(native.getBatteryCapacity(DEVICE_ID)).thenReturn(100)
+        val listener = createMockListener()
+        batteryController.registerBatteryListener(DEVICE_ID, listener, PID)
+        listener.verifyNotified(DEVICE_ID, status = STATUS_FULL, capacity = 1.0f)
+
+        `when`(native.getBatteryStatus(SECOND_DEVICE_ID)).thenReturn(STATUS_CHARGING)
+        `when`(native.getBatteryCapacity(SECOND_DEVICE_ID)).thenReturn(78)
+        batteryController.registerBatteryListener(SECOND_DEVICE_ID, listener, PID)
+        listener.verifyNotified(SECOND_DEVICE_ID, status = STATUS_CHARGING, capacity = 0.78f)
+    }
+
+    @Test
+    fun testListenersNotifiedOnUEventNotification() {
+        `when`(native.getBatteryDevicePath(DEVICE_ID)).thenReturn("/sys/dev/test/device1")
+        `when`(native.getBatteryStatus(DEVICE_ID)).thenReturn(STATUS_CHARGING)
+        `when`(native.getBatteryCapacity(DEVICE_ID)).thenReturn(78)
+        val listener = createMockListener()
+        val uEventListener = ArgumentCaptor.forClass(UEventBatteryListener::class.java)
+        batteryController.registerBatteryListener(DEVICE_ID, listener, PID)
+        // The device paths for UEvent notifications do not include the "/sys" prefix, so verify
+        // that the added listener is configured to match the path without that prefix.
+        verify(uEventManager)
+            .addListener(uEventListener.capture(), eq("DEVPATH=/dev/test/device1"))
+        listener.verifyNotified(DEVICE_ID, status = STATUS_CHARGING, capacity = 0.78f)
+
+        // If the battery state has changed when an UEvent is sent, the listeners are notified.
+        `when`(native.getBatteryCapacity(DEVICE_ID)).thenReturn(80)
+        uEventListener.value!!.onBatteryUEvent(TIMESTAMP)
+        listener.verifyNotified(DEVICE_ID, status = STATUS_CHARGING, capacity = 0.80f,
+            eventTime = TIMESTAMP)
+
+        // If the battery state has not changed when an UEvent is sent, the listeners are not
+        // notified.
+        clearInvocations(listener)
+        uEventListener.value!!.onBatteryUEvent(TIMESTAMP + 1)
+        verifyNoMoreInteractions(listener)
+
+        batteryController.unregisterBatteryListener(DEVICE_ID, listener, PID)
+        verify(uEventManager).removeListener(uEventListener.capture())
+        assertEquals("The same observer must be registered and unregistered",
+            uEventListener.allValues[0], uEventListener.allValues[1])
+    }
+
+    @Test
+    fun testBatteryPresenceChanged() {
+        `when`(native.getBatteryDevicePath(DEVICE_ID)).thenReturn("/test/device1")
+        `when`(native.getBatteryStatus(DEVICE_ID)).thenReturn(STATUS_CHARGING)
+        `when`(native.getBatteryCapacity(DEVICE_ID)).thenReturn(78)
+        val listener = createMockListener()
+        val uEventListener = ArgumentCaptor.forClass(UEventBatteryListener::class.java)
+        batteryController.registerBatteryListener(DEVICE_ID, listener, PID)
+        verify(uEventManager).addListener(uEventListener.capture(), eq("DEVPATH=/test/device1"))
+        listener.verifyNotified(DEVICE_ID, status = STATUS_CHARGING, capacity = 0.78f)
+
+        // If the battery presence for the InputDevice changes, the listener is notified.
+        notifyDeviceChanged(DEVICE_ID, hasBattery = false)
+        testLooper.dispatchNext()
+        listener.verifyNotified(isInvalidBatteryState(DEVICE_ID))
+        // Since the battery is no longer present, the UEventListener should be removed.
+        verify(uEventManager).removeListener(uEventListener.value)
+
+        // If the battery becomes present again, the listener is notified.
+        notifyDeviceChanged(DEVICE_ID, hasBattery = true)
+        testLooper.dispatchNext()
+        listener.verifyNotified(DEVICE_ID, mode = times(2), status = STATUS_CHARGING,
+            capacity = 0.78f)
+        // Ensure that a new UEventListener was added.
+        verify(uEventManager, times(2))
+            .addListener(uEventListener.capture(), eq("DEVPATH=/test/device1"))
+    }
+
+    @Test
+    fun testStartPollingWhenListenerIsRegistered() {
+        val listener = createMockListener()
+        `when`(native.getBatteryCapacity(DEVICE_ID)).thenReturn(78)
+        batteryController.registerBatteryListener(DEVICE_ID, listener, PID)
+        listener.verifyNotified(DEVICE_ID, capacity = 0.78f)
+
+        // Assume there is a change in the battery state. Ensure the listener is not notified
+        // while the polling period has not elapsed.
+        `when`(native.getBatteryCapacity(DEVICE_ID)).thenReturn(80)
+        testLooper.moveTimeForward(1)
+        testLooper.dispatchAll()
+        listener.verifyNotified(DEVICE_ID, mode = never(), capacity = 0.80f)
+
+        // Move the time forward so that the polling period has elapsed.
+        // The listener should be notified.
+        testLooper.moveTimeForward(POLLING_PERIOD_MILLIS - 1)
+        assertTrue("There should be a polling callbacks posted to the handler", testLooper.isIdle)
+        testLooper.dispatchNext()
+        listener.verifyNotified(DEVICE_ID, capacity = 0.80f)
+
+        // Move the time forward so that another polling period has elapsed.
+        // The battery should still be polled, but there is no change so listeners are not notified.
+        testLooper.moveTimeForward(POLLING_PERIOD_MILLIS)
+        assertTrue("There should be a polling callbacks posted to the handler", testLooper.isIdle)
+        testLooper.dispatchNext()
+        listener.verifyNotified(DEVICE_ID, mode = times(1), capacity = 0.80f)
+    }
+
+    @Test
+    fun testNoPollingWhenTheDeviceIsNotInteractive() {
+        batteryController.onInteractiveChanged(false /*interactive*/)
+
+        val listener = createMockListener()
+        `when`(native.getBatteryCapacity(DEVICE_ID)).thenReturn(78)
+        batteryController.registerBatteryListener(DEVICE_ID, listener, PID)
+        listener.verifyNotified(DEVICE_ID, capacity = 0.78f)
+
+        // The battery state changed, but we should not be polling for battery changes when the
+        // device is not interactive.
+        `when`(native.getBatteryCapacity(DEVICE_ID)).thenReturn(80)
+        testLooper.moveTimeForward(POLLING_PERIOD_MILLIS)
+        assertFalse("There should be no polling callbacks posted to the handler", testLooper.isIdle)
+        testLooper.dispatchAll()
+        listener.verifyNotified(DEVICE_ID, mode = never(), capacity = 0.80f)
+
+        // The device is now interactive. Battery state polling begins immediately.
+        batteryController.onInteractiveChanged(true /*interactive*/)
+        testLooper.dispatchNext()
+        listener.verifyNotified(DEVICE_ID, capacity = 0.80f)
+
+        // Ensure that we continue to poll for battery changes.
+        `when`(native.getBatteryCapacity(DEVICE_ID)).thenReturn(90)
+        testLooper.moveTimeForward(POLLING_PERIOD_MILLIS)
+        assertTrue("There should be a polling callbacks posted to the handler", testLooper.isIdle)
+        testLooper.dispatchNext()
+        listener.verifyNotified(DEVICE_ID, capacity = 0.90f)
+    }
+
+    @Test
+    fun testGetBatteryState() {
+        `when`(native.getBatteryStatus(DEVICE_ID)).thenReturn(STATUS_CHARGING)
+        `when`(native.getBatteryCapacity(DEVICE_ID)).thenReturn(78)
+        val batteryState = batteryController.getBatteryState(DEVICE_ID)
+        assertThat("battery state matches", batteryState,
+            matchesState(DEVICE_ID, status = STATUS_CHARGING, capacity = 0.78f))
+    }
+
+    @Test
+    fun testGetBatteryStateWithListener() {
+        val listener = createMockListener()
+        `when`(native.getBatteryStatus(DEVICE_ID)).thenReturn(STATUS_CHARGING)
+        `when`(native.getBatteryCapacity(DEVICE_ID)).thenReturn(78)
+        batteryController.registerBatteryListener(DEVICE_ID, listener, PID)
+        listener.verifyNotified(DEVICE_ID, status = STATUS_CHARGING, capacity = 0.78f)
+
+        // If getBatteryState() is called when a listener is monitoring the device and there is a
+        // change in the battery state, the listener is also notified.
+        `when`(native.getBatteryCapacity(DEVICE_ID)).thenReturn(80)
+        val batteryState = batteryController.getBatteryState(DEVICE_ID)
+        assertThat("battery matches state", batteryState,
+            matchesState(DEVICE_ID, status = STATUS_CHARGING, capacity = 0.80f))
+        listener.verifyNotified(DEVICE_ID, status = STATUS_CHARGING, capacity = 0.80f)
+    }
+
+    @Test
+    fun testUsiDeviceIsMonitoredPersistently() {
+        `when`(native.getBatteryDevicePath(USI_DEVICE_ID)).thenReturn("/sys/dev/usi_device")
+        addInputDevice(USI_DEVICE_ID, supportsUsi = true)
+        testLooper.dispatchNext()
+
+        // Even though there is no listener added for this device, it is being monitored.
+        val uEventListener = ArgumentCaptor.forClass(UEventBatteryListener::class.java)
+        verify(uEventManager)
+            .addListener(uEventListener.capture(), eq("DEVPATH=/dev/usi_device"))
+
+        // Add and remove a listener for the device.
+        val listener = createMockListener()
+        batteryController.registerBatteryListener(USI_DEVICE_ID, listener, PID)
+        batteryController.unregisterBatteryListener(USI_DEVICE_ID, listener, PID)
+
+        // The device is still being monitored.
+        verify(uEventManager, never()).removeListener(uEventListener.value)
+    }
+
+    @Test
+    fun testNoPollingWhenUsiDevicesAreMonitored() {
+        `when`(native.getBatteryDevicePath(USI_DEVICE_ID)).thenReturn("/sys/dev/usi_device")
+        addInputDevice(USI_DEVICE_ID, supportsUsi = true)
+        testLooper.dispatchNext()
+        `when`(native.getBatteryDevicePath(SECOND_USI_DEVICE_ID)).thenReturn("/sys/dev/usi_device2")
+        addInputDevice(SECOND_USI_DEVICE_ID, supportsUsi = true)
+        testLooper.dispatchNext()
+
+        testLooper.moveTimeForward(POLLING_PERIOD_MILLIS)
+        assertFalse("There should be no polling callbacks posted to the handler", testLooper.isIdle)
+
+        // Add a listener.
+        val listener = createMockListener()
+        batteryController.registerBatteryListener(USI_DEVICE_ID, listener, PID)
+
+        testLooper.moveTimeForward(POLLING_PERIOD_MILLIS)
+        assertFalse("There should be no polling callbacks posted to the handler", testLooper.isIdle)
+    }
+
+    @Test
+    fun testExpectedFlowForUsiBattery() {
+        `when`(native.getBatteryDevicePath(USI_DEVICE_ID)).thenReturn("/sys/dev/usi_device")
+        `when`(native.getBatteryStatus(USI_DEVICE_ID)).thenReturn(STATUS_DISCHARGING)
+        `when`(native.getBatteryCapacity(USI_DEVICE_ID)).thenReturn(78)
+
+        addInputDevice(USI_DEVICE_ID, supportsUsi = true)
+        testLooper.dispatchNext()
+        val uEventListener = ArgumentCaptor.forClass(UEventBatteryListener::class.java)
+        verify(uEventManager)
+            .addListener(uEventListener.capture(), eq("DEVPATH=/dev/usi_device"))
+
+        // A USI device's battery state is not valid until the first UEvent notification.
+        // Add a listener, and ensure it is notified that the battery state is not present.
+        val listener = createMockListener()
+        batteryController.registerBatteryListener(USI_DEVICE_ID, listener, PID)
+        listener.verifyNotified(isInvalidBatteryState(USI_DEVICE_ID))
+
+        // Ensure that querying for battery state also returns the same invalid result.
+        assertThat("battery state matches", batteryController.getBatteryState(USI_DEVICE_ID),
+            isInvalidBatteryState(USI_DEVICE_ID))
+
+        // There is a UEvent signaling a battery change. The battery state is now valid.
+        uEventListener.value!!.onBatteryUEvent(TIMESTAMP)
+        listener.verifyNotified(USI_DEVICE_ID, status = STATUS_DISCHARGING, capacity = 0.78f)
+        assertThat("battery state matches", batteryController.getBatteryState(USI_DEVICE_ID),
+            matchesState(USI_DEVICE_ID, status = STATUS_DISCHARGING, capacity = 0.78f))
+
+        // There is another UEvent notification. The battery state is now updated.
+        `when`(native.getBatteryCapacity(USI_DEVICE_ID)).thenReturn(64)
+        uEventListener.value!!.onBatteryUEvent(TIMESTAMP + 1)
+        listener.verifyNotified(USI_DEVICE_ID, status = STATUS_DISCHARGING, capacity = 0.64f)
+        assertThat("battery state matches", batteryController.getBatteryState(USI_DEVICE_ID),
+            matchesState(USI_DEVICE_ID, status = STATUS_DISCHARGING, capacity = 0.64f))
+
+        // The battery state is still valid after a millisecond.
+        testLooper.moveTimeForward(1)
+        testLooper.dispatchAll()
+        assertThat("battery state matches", batteryController.getBatteryState(USI_DEVICE_ID),
+            matchesState(USI_DEVICE_ID, status = STATUS_DISCHARGING, capacity = 0.64f))
+
+        // The battery is no longer present after the timeout expires.
+        testLooper.moveTimeForward(USI_BATTERY_VALIDITY_DURATION_MILLIS - 1)
+        testLooper.dispatchNext()
+        listener.verifyNotified(isInvalidBatteryState(USI_DEVICE_ID), times(2))
+        assertThat("battery state matches", batteryController.getBatteryState(USI_DEVICE_ID),
+            isInvalidBatteryState(USI_DEVICE_ID))
+    }
+
+    @Test
+    fun testStylusPresenceExtendsValidUsiBatteryState() {
+        `when`(native.getBatteryDevicePath(USI_DEVICE_ID)).thenReturn("/sys/dev/usi_device")
+        `when`(native.getBatteryStatus(USI_DEVICE_ID)).thenReturn(STATUS_DISCHARGING)
+        `when`(native.getBatteryCapacity(USI_DEVICE_ID)).thenReturn(78)
+
+        addInputDevice(USI_DEVICE_ID, supportsUsi = true)
+        testLooper.dispatchNext()
+        val uEventListener = ArgumentCaptor.forClass(UEventBatteryListener::class.java)
+        verify(uEventManager)
+            .addListener(uEventListener.capture(), eq("DEVPATH=/dev/usi_device"))
+
+        // There is a UEvent signaling a battery change. The battery state is now valid.
+        uEventListener.value!!.onBatteryUEvent(TIMESTAMP)
+        val listener = createMockListener()
+        batteryController.registerBatteryListener(USI_DEVICE_ID, listener, PID)
+        listener.verifyNotified(USI_DEVICE_ID, status = STATUS_DISCHARGING, capacity = 0.78f)
+        assertThat("battery state matches", batteryController.getBatteryState(USI_DEVICE_ID),
+            matchesState(USI_DEVICE_ID, status = STATUS_DISCHARGING, capacity = 0.78f))
+
+        // Stylus presence is detected before the validity timeout expires.
+        testLooper.moveTimeForward(100)
+        testLooper.dispatchAll()
+        batteryController.notifyStylusGestureStarted(USI_DEVICE_ID, TIMESTAMP)
+
+        // Ensure that timeout was extended, and the battery state is now valid for longer.
+        testLooper.moveTimeForward(USI_BATTERY_VALIDITY_DURATION_MILLIS - 100)
+        testLooper.dispatchAll()
+        assertThat("battery state matches", batteryController.getBatteryState(USI_DEVICE_ID),
+            matchesState(USI_DEVICE_ID, status = STATUS_DISCHARGING, capacity = 0.78f))
+
+        // Ensure the validity period expires after the expected amount of time.
+        testLooper.moveTimeForward(100)
+        testLooper.dispatchNext()
+        listener.verifyNotified(isInvalidBatteryState(USI_DEVICE_ID))
+        assertThat("battery state matches", batteryController.getBatteryState(USI_DEVICE_ID),
+            isInvalidBatteryState(USI_DEVICE_ID))
+    }
+
+    @Test
+    fun testStylusPresenceMakesUsiBatteryStateValid() {
+        `when`(native.getBatteryDevicePath(USI_DEVICE_ID)).thenReturn("/sys/dev/usi_device")
+        `when`(native.getBatteryStatus(USI_DEVICE_ID)).thenReturn(STATUS_DISCHARGING)
+        `when`(native.getBatteryCapacity(USI_DEVICE_ID)).thenReturn(78)
+
+        addInputDevice(USI_DEVICE_ID, supportsUsi = true)
+        testLooper.dispatchNext()
+        val uEventListener = ArgumentCaptor.forClass(UEventBatteryListener::class.java)
+        verify(uEventManager)
+            .addListener(uEventListener.capture(), eq("DEVPATH=/dev/usi_device"))
+
+        // The USI battery state is initially invalid.
+        val listener = createMockListener()
+        batteryController.registerBatteryListener(USI_DEVICE_ID, listener, PID)
+        listener.verifyNotified(isInvalidBatteryState(USI_DEVICE_ID))
+        assertThat("battery state matches", batteryController.getBatteryState(USI_DEVICE_ID),
+            isInvalidBatteryState(USI_DEVICE_ID))
+
+        // A stylus presence is detected. This validates the battery state.
+        batteryController.notifyStylusGestureStarted(USI_DEVICE_ID, TIMESTAMP)
+
+        listener.verifyNotified(USI_DEVICE_ID, status = STATUS_DISCHARGING, capacity = 0.78f)
+        assertThat("battery state matches", batteryController.getBatteryState(USI_DEVICE_ID),
+            matchesState(USI_DEVICE_ID, status = STATUS_DISCHARGING, capacity = 0.78f))
+    }
+
+    @Test
+    fun testStylusPresenceDoesNotMakeUsiBatteryStateValidAtBoot() {
+        `when`(native.getBatteryDevicePath(USI_DEVICE_ID)).thenReturn("/sys/dev/usi_device")
+        // At boot, the USI device always reports a capacity value of 0.
+        `when`(native.getBatteryStatus(USI_DEVICE_ID)).thenReturn(STATUS_UNKNOWN)
+        `when`(native.getBatteryCapacity(USI_DEVICE_ID)).thenReturn(0)
+
+        addInputDevice(USI_DEVICE_ID, supportsUsi = true)
+        testLooper.dispatchNext()
+        val uEventListener = ArgumentCaptor.forClass(UEventBatteryListener::class.java)
+        verify(uEventManager)
+            .addListener(uEventListener.capture(), eq("DEVPATH=/dev/usi_device"))
+
+        // The USI battery state is initially invalid.
+        val listener = createMockListener()
+        batteryController.registerBatteryListener(USI_DEVICE_ID, listener, PID)
+        listener.verifyNotified(isInvalidBatteryState(USI_DEVICE_ID))
+        assertThat("battery state matches", batteryController.getBatteryState(USI_DEVICE_ID),
+            isInvalidBatteryState(USI_DEVICE_ID))
+
+        // Since the capacity reported is 0, stylus presence does not validate the battery state.
+        batteryController.notifyStylusGestureStarted(USI_DEVICE_ID, TIMESTAMP)
+
+        assertThat("battery state matches", batteryController.getBatteryState(USI_DEVICE_ID),
+            isInvalidBatteryState(USI_DEVICE_ID))
+
+        // However, if a UEvent reports a battery capacity of 0, the battery state is now valid.
+        uEventListener.value!!.onBatteryUEvent(TIMESTAMP)
+        listener.verifyNotified(USI_DEVICE_ID, status = STATUS_UNKNOWN, capacity = 0f)
+        assertThat("battery state matches", batteryController.getBatteryState(USI_DEVICE_ID),
+            matchesState(USI_DEVICE_ID, status = STATUS_UNKNOWN, capacity = 0f))
+    }
+
+    @Test
+    fun testRegisterBluetoothListenerForMonitoredBluetoothDevices() {
+        `when`(iInputManager.getInputDeviceBluetoothAddress(BT_DEVICE_ID))
+            .thenReturn("AA:BB:CC:DD:EE:FF")
+        `when`(iInputManager.getInputDeviceBluetoothAddress(SECOND_BT_DEVICE_ID))
+            .thenReturn("11:22:33:44:55:66")
+        addInputDevice(BT_DEVICE_ID)
+        testLooper.dispatchNext()
+        addInputDevice(SECOND_BT_DEVICE_ID)
+        testLooper.dispatchNext()
+
+        // Listen to a non-Bluetooth device and ensure that the BT battery listener is not added
+        // when there are no monitored BT devices.
+        val listener = createMockListener()
+        batteryController.registerBatteryListener(DEVICE_ID, listener, PID)
+        verify(bluetoothBatteryManager, never()).addBatteryListener(any())
+
+        val bluetoothListener = ArgumentCaptor.forClass(BluetoothBatteryListener::class.java)
+
+        // The BT battery listener is added when the first BT input device is monitored.
+        batteryController.registerBatteryListener(BT_DEVICE_ID, listener, PID)
+        verify(bluetoothBatteryManager).addBatteryListener(bluetoothListener.capture())
+
+        // The BT listener is only added once for all BT devices.
+        batteryController.registerBatteryListener(SECOND_BT_DEVICE_ID, listener, PID)
+        verify(bluetoothBatteryManager, times(1)).addBatteryListener(any())
+
+        // The BT listener is only removed when there are no monitored BT devices.
+        batteryController.unregisterBatteryListener(BT_DEVICE_ID, listener, PID)
+        verify(bluetoothBatteryManager, never()).removeBatteryListener(any())
+
+        `when`(iInputManager.getInputDeviceBluetoothAddress(SECOND_BT_DEVICE_ID))
+            .thenReturn(null)
+        notifyDeviceChanged(SECOND_BT_DEVICE_ID)
+        testLooper.dispatchNext()
+        verify(bluetoothBatteryManager).removeBatteryListener(bluetoothListener.value)
+    }
+
+    @Test
+    fun testNotifiesBluetoothBatteryChanges() {
+        `when`(iInputManager.getInputDeviceBluetoothAddress(BT_DEVICE_ID))
+            .thenReturn("AA:BB:CC:DD:EE:FF")
+        `when`(bluetoothBatteryManager.getBatteryLevel(eq("AA:BB:CC:DD:EE:FF"))).thenReturn(21)
+        addInputDevice(BT_DEVICE_ID)
+        val bluetoothListener = ArgumentCaptor.forClass(BluetoothBatteryListener::class.java)
+        val listener = createMockListener()
+        batteryController.registerBatteryListener(BT_DEVICE_ID, listener, PID)
+        verify(bluetoothBatteryManager).addBatteryListener(bluetoothListener.capture())
+        listener.verifyNotified(BT_DEVICE_ID, capacity = 0.21f)
+
+        // When the state has not changed, the listener is not notified again.
+        bluetoothListener.value!!.onBluetoothBatteryChanged(TIMESTAMP, "AA:BB:CC:DD:EE:FF", 21)
+        listener.verifyNotified(BT_DEVICE_ID, mode = times(1), capacity = 0.21f)
+
+        bluetoothListener.value!!.onBluetoothBatteryChanged(TIMESTAMP, "AA:BB:CC:DD:EE:FF", 25)
+        listener.verifyNotified(BT_DEVICE_ID, capacity = 0.25f)
+    }
+
+    @Test
+    fun testBluetoothBatteryIsPrioritized() {
+        `when`(native.getBatteryDevicePath(BT_DEVICE_ID)).thenReturn("/sys/dev/bt_device")
+        `when`(iInputManager.getInputDeviceBluetoothAddress(BT_DEVICE_ID))
+            .thenReturn("AA:BB:CC:DD:EE:FF")
+        `when`(bluetoothBatteryManager.getBatteryLevel(eq("AA:BB:CC:DD:EE:FF"))).thenReturn(21)
+        `when`(native.getBatteryCapacity(BT_DEVICE_ID)).thenReturn(98)
+        addInputDevice(BT_DEVICE_ID)
+        val bluetoothListener = ArgumentCaptor.forClass(BluetoothBatteryListener::class.java)
+        val listener = createMockListener()
+        val uEventListener = ArgumentCaptor.forClass(UEventBatteryListener::class.java)
+
+        // When the device is first monitored and both native and BT battery is available,
+        // the latter is used.
+        batteryController.registerBatteryListener(BT_DEVICE_ID, listener, PID)
+        verify(bluetoothBatteryManager).addBatteryListener(bluetoothListener.capture())
+        verify(uEventManager).addListener(uEventListener.capture(), any())
+        listener.verifyNotified(BT_DEVICE_ID, capacity = 0.21f)
+        assertThat("battery state matches", batteryController.getBatteryState(BT_DEVICE_ID),
+            matchesState(BT_DEVICE_ID, capacity = 0.21f))
+
+        // If only the native battery state changes the listener is not notified.
+        `when`(native.getBatteryCapacity(BT_DEVICE_ID)).thenReturn(97)
+        uEventListener.value!!.onBatteryUEvent(TIMESTAMP)
+        listener.verifyNotified(BT_DEVICE_ID, mode = times(1), capacity = 0.21f)
+        assertThat("battery state matches", batteryController.getBatteryState(BT_DEVICE_ID),
+            matchesState(BT_DEVICE_ID, capacity = 0.21f))
+    }
+
+    @Test
+    fun testFallBackToNativeBatteryStateWhenBluetoothStateInvalid() {
+        `when`(native.getBatteryDevicePath(BT_DEVICE_ID)).thenReturn("/sys/dev/bt_device")
+        `when`(iInputManager.getInputDeviceBluetoothAddress(BT_DEVICE_ID))
+            .thenReturn("AA:BB:CC:DD:EE:FF")
+        `when`(bluetoothBatteryManager.getBatteryLevel(eq("AA:BB:CC:DD:EE:FF"))).thenReturn(21)
+        `when`(native.getBatteryCapacity(BT_DEVICE_ID)).thenReturn(98)
+        addInputDevice(BT_DEVICE_ID)
+        val bluetoothListener = ArgumentCaptor.forClass(BluetoothBatteryListener::class.java)
+        val listener = createMockListener()
+        val uEventListener = ArgumentCaptor.forClass(UEventBatteryListener::class.java)
+
+        batteryController.registerBatteryListener(BT_DEVICE_ID, listener, PID)
+        verify(bluetoothBatteryManager).addBatteryListener(bluetoothListener.capture())
+        verify(uEventManager).addListener(uEventListener.capture(), any())
+        listener.verifyNotified(BT_DEVICE_ID, capacity = 0.21f)
+
+        // Fall back to the native state when BT is off.
+        bluetoothListener.value!!.onBluetoothBatteryChanged(TIMESTAMP, "AA:BB:CC:DD:EE:FF",
+            BluetoothDevice.BATTERY_LEVEL_BLUETOOTH_OFF)
+        listener.verifyNotified(BT_DEVICE_ID, capacity = 0.98f)
+
+        bluetoothListener.value!!.onBluetoothBatteryChanged(TIMESTAMP, "AA:BB:CC:DD:EE:FF", 22)
+        verify(bluetoothBatteryManager).addBatteryListener(bluetoothListener.capture())
+        listener.verifyNotified(BT_DEVICE_ID, capacity = 0.22f)
+
+        // Fall back to the native state when BT battery is unknown.
+        bluetoothListener.value!!.onBluetoothBatteryChanged(TIMESTAMP, "AA:BB:CC:DD:EE:FF",
+            BluetoothDevice.BATTERY_LEVEL_UNKNOWN)
+        listener.verifyNotified(BT_DEVICE_ID, mode = times(2), capacity = 0.98f)
+    }
+
+    @Test
+    fun testRegisterBluetoothMetadataListenerForMonitoredBluetoothDevices() {
+        `when`(iInputManager.getInputDeviceBluetoothAddress(BT_DEVICE_ID))
+            .thenReturn("AA:BB:CC:DD:EE:FF")
+        `when`(iInputManager.getInputDeviceBluetoothAddress(SECOND_BT_DEVICE_ID))
+            .thenReturn("11:22:33:44:55:66")
+        addInputDevice(BT_DEVICE_ID)
+        testLooper.dispatchNext()
+        addInputDevice(SECOND_BT_DEVICE_ID)
+        testLooper.dispatchNext()
+
+        // Listen to a non-Bluetooth device and ensure that the metadata listener is not added when
+        // there are no monitored BT devices.
+        val listener = createMockListener()
+        batteryController.registerBatteryListener(DEVICE_ID, listener, PID)
+        verify(bluetoothBatteryManager, never()).addMetadataListener(any(), any())
+
+        val metadataListener1 = ArgumentCaptor.forClass(
+            BluetoothAdapter.OnMetadataChangedListener::class.java)
+        val metadataListener2 = ArgumentCaptor.forClass(
+            BluetoothAdapter.OnMetadataChangedListener::class.java)
+
+        // The metadata listener is added when the first BT input device is monitored.
+        batteryController.registerBatteryListener(BT_DEVICE_ID, listener, PID)
+        verify(bluetoothBatteryManager)
+            .addMetadataListener(eq("AA:BB:CC:DD:EE:FF"), metadataListener1.capture())
+
+        // There is one metadata listener added for each BT device.
+        batteryController.registerBatteryListener(SECOND_BT_DEVICE_ID, listener, PID)
+        verify(bluetoothBatteryManager)
+            .addMetadataListener(eq("11:22:33:44:55:66"), metadataListener2.capture())
+
+        // The metadata listener is removed when the device is no longer monitored.
+        batteryController.unregisterBatteryListener(BT_DEVICE_ID, listener, PID)
+        verify(bluetoothBatteryManager)
+            .removeMetadataListener("AA:BB:CC:DD:EE:FF", metadataListener1.value)
+
+        `when`(iInputManager.getInputDeviceBluetoothAddress(SECOND_BT_DEVICE_ID))
+            .thenReturn(null)
+        notifyDeviceChanged(SECOND_BT_DEVICE_ID)
+        testLooper.dispatchNext()
+        verify(bluetoothBatteryManager)
+            .removeMetadataListener("11:22:33:44:55:66", metadataListener2.value)
+    }
+
+    @Test
+    fun testNotifiesBluetoothMetadataBatteryChanges() {
+        `when`(iInputManager.getInputDeviceBluetoothAddress(BT_DEVICE_ID))
+            .thenReturn("AA:BB:CC:DD:EE:FF")
+        `when`(bluetoothBatteryManager.getMetadata("AA:BB:CC:DD:EE:FF",
+                BluetoothDevice.METADATA_MAIN_BATTERY))
+            .thenReturn("21".toByteArray())
+        addInputDevice(BT_DEVICE_ID)
+        val metadataListener = ArgumentCaptor.forClass(
+            BluetoothAdapter.OnMetadataChangedListener::class.java)
+        val listener = createMockListener()
+        val bluetoothDevice = createBluetoothDevice("AA:BB:CC:DD:EE:FF")
+        batteryController.registerBatteryListener(BT_DEVICE_ID, listener, PID)
+        verify(bluetoothBatteryManager)
+            .addMetadataListener(eq("AA:BB:CC:DD:EE:FF"), metadataListener.capture())
+        listener.verifyNotified(BT_DEVICE_ID, capacity = 0.21f, status = STATUS_UNKNOWN)
+
+        // When the state has not changed, the listener is not notified again.
+        metadataListener.value!!.onMetadataChanged(
+            bluetoothDevice, BluetoothDevice.METADATA_MAIN_BATTERY, "21".toByteArray())
+        listener.verifyNotified(BT_DEVICE_ID, mode = times(1), capacity = 0.21f)
+
+        metadataListener.value!!.onMetadataChanged(
+            bluetoothDevice, BluetoothDevice.METADATA_MAIN_BATTERY, "25".toByteArray())
+        listener.verifyNotified(BT_DEVICE_ID, capacity = 0.25f, status = STATUS_UNKNOWN)
+
+        metadataListener.value!!.onMetadataChanged(
+            bluetoothDevice, BluetoothDevice.METADATA_MAIN_CHARGING, "true".toByteArray())
+        listener.verifyNotified(BT_DEVICE_ID, capacity = 0.25f, status = STATUS_CHARGING)
+
+        metadataListener.value!!.onMetadataChanged(
+            bluetoothDevice, BluetoothDevice.METADATA_MAIN_CHARGING, "false".toByteArray())
+        listener.verifyNotified(BT_DEVICE_ID, capacity = 0.25f, status = STATUS_DISCHARGING)
+
+        metadataListener.value!!.onMetadataChanged(
+            bluetoothDevice, BluetoothDevice.METADATA_MAIN_CHARGING, null)
+        listener.verifyNotified(BT_DEVICE_ID, mode = times(2), capacity = 0.25f,
+            status = STATUS_UNKNOWN)
+    }
+
+    @Test
+    fun testBluetoothMetadataBatteryIsPrioritized() {
+        `when`(iInputManager.getInputDeviceBluetoothAddress(BT_DEVICE_ID))
+            .thenReturn("AA:BB:CC:DD:EE:FF")
+        `when`(bluetoothBatteryManager.getBatteryLevel(eq("AA:BB:CC:DD:EE:FF"))).thenReturn(21)
+        `when`(bluetoothBatteryManager.getMetadata("AA:BB:CC:DD:EE:FF",
+                BluetoothDevice.METADATA_MAIN_BATTERY))
+            .thenReturn("22".toByteArray())
+        addInputDevice(BT_DEVICE_ID)
+        val bluetoothListener = ArgumentCaptor.forClass(BluetoothBatteryListener::class.java)
+        val metadataListener = ArgumentCaptor.forClass(
+            BluetoothAdapter.OnMetadataChangedListener::class.java)
+        val listener = createMockListener()
+        val bluetoothDevice = createBluetoothDevice("AA:BB:CC:DD:EE:FF")
+        batteryController.registerBatteryListener(BT_DEVICE_ID, listener, PID)
+
+        verify(bluetoothBatteryManager).addBatteryListener(bluetoothListener.capture())
+        verify(bluetoothBatteryManager)
+            .addMetadataListener(eq("AA:BB:CC:DD:EE:FF"), metadataListener.capture())
+        listener.verifyNotified(BT_DEVICE_ID, capacity = 0.22f)
+
+        // A change in the Bluetooth battery level has no effect while there is a valid battery
+        // level obtained through the metadata.
+        bluetoothListener.value!!.onBluetoothBatteryChanged(TIMESTAMP, "AA:BB:CC:DD:EE:FF", 23)
+        listener.verifyNotified(BT_DEVICE_ID, mode = never(), capacity = 0.23f)
+
+        metadataListener.value!!.onMetadataChanged(
+            bluetoothDevice, BluetoothDevice.METADATA_MAIN_BATTERY, "24".toByteArray())
+        listener.verifyNotified(BT_DEVICE_ID, capacity = 0.24f)
+
+        // When the battery level from the metadata is no longer valid, we fall back to using the
+        // Bluetooth battery level.
+        metadataListener.value!!.onMetadataChanged(
+            bluetoothDevice, BluetoothDevice.METADATA_MAIN_BATTERY, null)
+        listener.verifyNotified(BT_DEVICE_ID, capacity = 0.23f)
+    }
+}
diff --git a/tests/Input/src/com/android/server/input/ConfigurationProcessorTest.java b/tests/Input/src/com/android/server/input/ConfigurationProcessorTest.java
new file mode 100644
index 0000000..9a49d91
--- /dev/null
+++ b/tests/Input/src/com/android/server/input/ConfigurationProcessorTest.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.input;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+
+import android.content.Context;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.InputStream;
+import java.util.Map;
+
+/**
+ * Build/Install/Run:
+ * atest ConfigurationProcessorTest
+ */
+@RunWith(AndroidJUnit4.class)
+public class ConfigurationProcessorTest {
+
+    private Context mContext;
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getInstrumentation().getContext();
+    }
+
+    @Test
+    public void testGetInputPortAssociations() {
+        final int res = com.android.test.input.R.raw.input_port_associations;
+        InputStream xml = mContext.getResources().openRawResource(res);
+        Map<String, Integer> associations = null;
+        try {
+            associations = ConfigurationProcessor.processInputPortAssociations(xml);
+        } catch (Exception e) {
+            fail("Could not process xml file for input associations");
+        }
+        assertNotNull(associations);
+        assertEquals(2, associations.size());
+        assertEquals(0, associations.get("USB1").intValue());
+        assertEquals(1, associations.get("USB2").intValue());
+    }
+
+    @Test
+    public void testGetInputPortAssociationsBadDisplayport() {
+        final int res =
+                com.android.test.input.R.raw.input_port_associations_bad_displayport;
+        InputStream xml = mContext.getResources().openRawResource(res);
+        Map<String, Integer> associations = null;
+        try {
+            associations = ConfigurationProcessor.processInputPortAssociations(xml);
+        } catch (Exception e) {
+            fail("Could not process xml file for input associations");
+        }
+        assertNotNull(associations);
+        assertEquals(0, associations.size());
+    }
+
+    @Test
+    public void testGetInputPortAssociationsEmptyConfig() {
+        final int res = com.android.test.input.R.raw.input_port_associations_bad_xml;
+        InputStream xml = mContext.getResources().openRawResource(res);
+        try {
+            ConfigurationProcessor.processInputPortAssociations(xml);
+            fail("Parsing should fail, because xml contains bad data");
+        } catch (Exception e) {
+            // This is expected
+        }
+    }
+}
diff --git a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
new file mode 100644
index 0000000..93a5582
--- /dev/null
+++ b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
@@ -0,0 +1,402 @@
+/*
+ * 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.input
+
+
+import android.content.Context
+import android.content.ContextWrapper
+import android.hardware.display.DisplayViewport
+import android.hardware.input.InputManager
+import android.hardware.input.InputManagerGlobal
+import android.os.IInputConstants
+import android.os.test.TestLooper
+import android.platform.test.annotations.Presubmit
+import android.provider.Settings
+import android.test.mock.MockContentResolver
+import android.view.Display
+import android.view.PointerIcon
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.internal.util.test.FakeSettingsProvider
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.anyFloat
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mock
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.doAnswer
+import org.mockito.Mockito.never
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.Mockito.verifyZeroInteractions
+import org.mockito.junit.MockitoJUnit
+import org.mockito.stubbing.OngoingStubbing
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+
+/**
+ * Tests for {@link InputManagerService}.
+ *
+ * Build/Install/Run:
+ * atest InputTests:InputManagerServiceTests
+ */
+@Presubmit
+class InputManagerServiceTests {
+
+    @get:Rule
+    val mockitoRule = MockitoJUnit.rule()!!
+
+    @get:Rule
+    val fakeSettingsProviderRule = FakeSettingsProvider.rule()!!
+
+    @Mock
+    private lateinit var native: NativeInputManagerService
+
+    @Mock
+    private lateinit var wmCallbacks: InputManagerService.WindowManagerCallbacks
+
+    @Mock
+    private lateinit var uEventManager: UEventManager
+
+    private lateinit var service: InputManagerService
+    private lateinit var localService: InputManagerInternal
+    private lateinit var context: Context
+    private lateinit var testLooper: TestLooper
+    private lateinit var contentResolver: MockContentResolver
+    private lateinit var inputManagerGlobalSession: InputManagerGlobal.TestSession
+
+    @Before
+    fun setup() {
+        context = spy(ContextWrapper(InstrumentationRegistry.getInstrumentation().getContext()))
+        contentResolver = MockContentResolver(context)
+        contentResolver.addProvider(Settings.AUTHORITY, FakeSettingsProvider())
+        whenever(context.contentResolver).thenReturn(contentResolver)
+        testLooper = TestLooper()
+        service =
+            InputManagerService(object : InputManagerService.Injector(
+                    context, testLooper.looper, uEventManager) {
+                override fun getNativeService(
+                    service: InputManagerService?
+                ): NativeInputManagerService {
+                    return native
+                }
+
+                override fun registerLocalService(service: InputManagerInternal?) {
+                    localService = service!!
+                }
+            })
+        inputManagerGlobalSession = InputManagerGlobal.createTestSession(service)
+        val inputManager = InputManager(context)
+        whenever(context.getSystemService(InputManager::class.java)).thenReturn(inputManager)
+        whenever(context.getSystemService(Context.INPUT_SERVICE)).thenReturn(inputManager)
+
+        assertTrue("Local service must be registered", this::localService.isInitialized)
+        service.setWindowManagerCallbacks(wmCallbacks)
+    }
+
+    @After
+    fun tearDown() {
+        if (this::inputManagerGlobalSession.isInitialized) {
+            inputManagerGlobalSession.close()
+        }
+    }
+
+    @Test
+    fun testStart() {
+        verifyZeroInteractions(native)
+
+        service.start()
+        verify(native).start()
+    }
+
+    @Test
+    fun testInputSettingsUpdatedOnSystemRunning() {
+        verifyZeroInteractions(native)
+
+        service.systemRunning()
+
+        verify(native).setPointerSpeed(anyInt())
+        verify(native).setTouchpadPointerSpeed(anyInt())
+        verify(native).setTouchpadNaturalScrollingEnabled(anyBoolean())
+        verify(native).setTouchpadTapToClickEnabled(anyBoolean())
+        verify(native).setTouchpadRightClickZoneEnabled(anyBoolean())
+        verify(native).setShowTouches(anyBoolean())
+        verify(native).reloadPointerIcons()
+        verify(native).setMotionClassifierEnabled(anyBoolean())
+        verify(native).setMaximumObscuringOpacityForTouch(anyFloat())
+        verify(native).setStylusPointerIconEnabled(anyBoolean())
+        verify(native).setKeyRepeatConfiguration(anyInt(), anyInt())
+    }
+
+    @Test
+    fun testPointerDisplayUpdatesWhenDisplayViewportsChanged() {
+        val displayId = 123
+        whenever(wmCallbacks.pointerDisplayId).thenReturn(displayId)
+        val viewports = listOf<DisplayViewport>()
+        localService.setDisplayViewports(viewports)
+        verify(native).setDisplayViewports(any(Array<DisplayViewport>::class.java))
+        verify(native).setPointerDisplayId(displayId)
+
+        val x = 42f
+        val y = 314f
+        service.onPointerDisplayIdChanged(displayId, x, y)
+        testLooper.dispatchNext()
+        verify(wmCallbacks).notifyPointerDisplayIdChanged(displayId, x, y)
+    }
+
+    @Test
+    fun testSetVirtualMousePointerDisplayId() {
+        // Set the virtual mouse pointer displayId, and ensure that the calling thread is blocked
+        // until the native callback happens.
+        var countDownLatch = CountDownLatch(1)
+        val overrideDisplayId = 123
+        Thread {
+            assertTrue("Setting virtual pointer display should succeed",
+                localService.setVirtualMousePointerDisplayId(overrideDisplayId))
+            countDownLatch.countDown()
+        }.start()
+        assertFalse("Setting virtual pointer display should block",
+            countDownLatch.await(100, TimeUnit.MILLISECONDS))
+
+        val x = 42f
+        val y = 314f
+        service.onPointerDisplayIdChanged(overrideDisplayId, x, y)
+        testLooper.dispatchNext()
+        verify(wmCallbacks).notifyPointerDisplayIdChanged(overrideDisplayId, x, y)
+        assertTrue("Native callback unblocks calling thread",
+            countDownLatch.await(100, TimeUnit.MILLISECONDS))
+        verify(native).setPointerDisplayId(overrideDisplayId)
+
+        // Ensure that setting the same override again succeeds immediately.
+        assertTrue("Setting the same virtual mouse pointer displayId again should succeed",
+            localService.setVirtualMousePointerDisplayId(overrideDisplayId))
+
+        // Ensure that we did not query WM for the pointerDisplayId when setting the override
+        verify(wmCallbacks, never()).pointerDisplayId
+
+        // Unset the virtual mouse pointer displayId, and ensure that we query WM for the new
+        // pointer displayId and the calling thread is blocked until the native callback happens.
+        countDownLatch = CountDownLatch(1)
+        val pointerDisplayId = 42
+        `when`(wmCallbacks.pointerDisplayId).thenReturn(pointerDisplayId)
+        Thread {
+            assertTrue("Unsetting virtual mouse pointer displayId should succeed",
+                localService.setVirtualMousePointerDisplayId(Display.INVALID_DISPLAY))
+            countDownLatch.countDown()
+        }.start()
+        assertFalse("Unsetting virtual mouse pointer displayId should block",
+            countDownLatch.await(100, TimeUnit.MILLISECONDS))
+
+        service.onPointerDisplayIdChanged(pointerDisplayId, x, y)
+        testLooper.dispatchNext()
+        verify(wmCallbacks).notifyPointerDisplayIdChanged(pointerDisplayId, x, y)
+        assertTrue("Native callback unblocks calling thread",
+            countDownLatch.await(100, TimeUnit.MILLISECONDS))
+        verify(native).setPointerDisplayId(pointerDisplayId)
+    }
+
+    @Test
+    fun testSetVirtualMousePointerDisplayId_unsuccessfulUpdate() {
+        // Set the virtual mouse pointer displayId, and ensure that the calling thread is blocked
+        // until the native callback happens.
+        val countDownLatch = CountDownLatch(1)
+        val overrideDisplayId = 123
+        Thread {
+            assertFalse("Setting virtual pointer display should be unsuccessful",
+                localService.setVirtualMousePointerDisplayId(overrideDisplayId))
+            countDownLatch.countDown()
+        }.start()
+        assertFalse("Setting virtual pointer display should block",
+            countDownLatch.await(100, TimeUnit.MILLISECONDS))
+
+        val x = 42f
+        val y = 314f
+        // Assume the native callback updates the pointerDisplayId to the incorrect value.
+        service.onPointerDisplayIdChanged(Display.INVALID_DISPLAY, x, y)
+        testLooper.dispatchNext()
+        verify(wmCallbacks).notifyPointerDisplayIdChanged(Display.INVALID_DISPLAY, x, y)
+        assertTrue("Native callback unblocks calling thread",
+            countDownLatch.await(100, TimeUnit.MILLISECONDS))
+        verify(native).setPointerDisplayId(overrideDisplayId)
+    }
+
+    @Test
+    fun testSetVirtualMousePointerDisplayId_competingRequests() {
+        val firstRequestSyncLatch = CountDownLatch(1)
+        doAnswer {
+            firstRequestSyncLatch.countDown()
+        }.`when`(native).setPointerDisplayId(anyInt())
+
+        val firstRequestLatch = CountDownLatch(1)
+        val firstOverride = 123
+        Thread {
+            assertFalse("Setting virtual pointer display from thread 1 should be unsuccessful",
+                localService.setVirtualMousePointerDisplayId(firstOverride))
+            firstRequestLatch.countDown()
+        }.start()
+        assertFalse("Setting virtual pointer display should block",
+            firstRequestLatch.await(100, TimeUnit.MILLISECONDS))
+
+        assertTrue("Wait for first thread's request should succeed",
+            firstRequestSyncLatch.await(100, TimeUnit.MILLISECONDS))
+
+        val secondRequestLatch = CountDownLatch(1)
+        val secondOverride = 42
+        Thread {
+            assertTrue("Setting virtual mouse pointer from thread 2 should be successful",
+                localService.setVirtualMousePointerDisplayId(secondOverride))
+            secondRequestLatch.countDown()
+        }.start()
+        assertFalse("Setting virtual mouse pointer should block",
+            secondRequestLatch.await(100, TimeUnit.MILLISECONDS))
+
+        val x = 42f
+        val y = 314f
+        // Assume the native callback updates directly to the second request.
+        service.onPointerDisplayIdChanged(secondOverride, x, y)
+        testLooper.dispatchNext()
+        verify(wmCallbacks).notifyPointerDisplayIdChanged(secondOverride, x, y)
+        assertTrue("Native callback unblocks first thread",
+            firstRequestLatch.await(100, TimeUnit.MILLISECONDS))
+        assertTrue("Native callback unblocks second thread",
+            secondRequestLatch.await(100, TimeUnit.MILLISECONDS))
+        verify(native, times(2)).setPointerDisplayId(anyInt())
+    }
+
+    @Test
+    fun onDisplayRemoved_resetAllAdditionalInputProperties() {
+        setVirtualMousePointerDisplayIdAndVerify(10)
+
+        localService.setPointerIconVisible(false, 10)
+        verify(native).setPointerIconType(eq(PointerIcon.TYPE_NULL))
+        localService.setPointerAcceleration(5f, 10)
+        verify(native).setPointerAcceleration(eq(5f))
+
+        service.onDisplayRemoved(10)
+        verify(native).displayRemoved(eq(10))
+        verify(native).setPointerIconType(eq(PointerIcon.TYPE_NOT_SPECIFIED))
+        verify(native).setPointerAcceleration(
+            eq(IInputConstants.DEFAULT_POINTER_ACCELERATION.toFloat()))
+        verifyNoMoreInteractions(native)
+
+        // This call should not block because the virtual mouse pointer override was never removed.
+        localService.setVirtualMousePointerDisplayId(10)
+
+        verify(native).setPointerDisplayId(eq(10))
+        verifyNoMoreInteractions(native)
+    }
+
+    @Test
+    fun updateAdditionalInputPropertiesForOverrideDisplay() {
+        setVirtualMousePointerDisplayIdAndVerify(10)
+
+        localService.setPointerIconVisible(false, 10)
+        verify(native).setPointerIconType(eq(PointerIcon.TYPE_NULL))
+        localService.setPointerAcceleration(5f, 10)
+        verify(native).setPointerAcceleration(eq(5f))
+
+        localService.setPointerIconVisible(true, 10)
+        verify(native).setPointerIconType(eq(PointerIcon.TYPE_NOT_SPECIFIED))
+        localService.setPointerAcceleration(1f, 10)
+        verify(native).setPointerAcceleration(eq(1f))
+
+        // Verify that setting properties on a different display is not propagated until the
+        // pointer is moved to that display.
+        localService.setPointerIconVisible(false, 20)
+        localService.setPointerAcceleration(6f, 20)
+        verifyNoMoreInteractions(native)
+
+        clearInvocations(native)
+        setVirtualMousePointerDisplayIdAndVerify(20)
+
+        verify(native).setPointerIconType(eq(PointerIcon.TYPE_NULL))
+        verify(native).setPointerAcceleration(eq(6f))
+    }
+
+    @Test
+    fun setAdditionalInputPropertiesBeforeOverride() {
+        localService.setPointerIconVisible(false, 10)
+        localService.setPointerAcceleration(5f, 10)
+
+        verifyNoMoreInteractions(native)
+
+        setVirtualMousePointerDisplayIdAndVerify(10)
+
+        verify(native).setPointerIconType(eq(PointerIcon.TYPE_NULL))
+        verify(native).setPointerAcceleration(eq(5f))
+    }
+
+    @Test
+    fun setDeviceTypeAssociation_setsDeviceTypeAssociation() {
+        val inputPort = "inputPort"
+        val type = "type"
+
+        localService.setTypeAssociation(inputPort, type)
+
+        assertThat(service.getDeviceTypeAssociations()).asList().containsExactly(inputPort, type)
+            .inOrder()
+    }
+
+    @Test
+    fun setAndUnsetDeviceTypeAssociation_deviceTypeAssociationIsMissing() {
+        val inputPort = "inputPort"
+        val type = "type"
+
+        localService.setTypeAssociation(inputPort, type)
+        localService.unsetTypeAssociation(inputPort)
+
+        assertTrue(service.getDeviceTypeAssociations().isEmpty())
+    }
+
+    @Test
+    fun testAddAndRemoveVirtualmKeyboardLayoutAssociation() {
+        val inputPort = "input port"
+        val languageTag = "language"
+        val layoutType = "layoutType"
+        localService.addKeyboardLayoutAssociation(inputPort, languageTag, layoutType)
+        verify(native).changeKeyboardLayoutAssociation()
+
+        localService.removeKeyboardLayoutAssociation(inputPort)
+        verify(native, times(2)).changeKeyboardLayoutAssociation()
+    }
+
+    private fun setVirtualMousePointerDisplayIdAndVerify(overrideDisplayId: Int) {
+        val thread = Thread { localService.setVirtualMousePointerDisplayId(overrideDisplayId) }
+        thread.start()
+
+        // Allow some time for the set override call to park while waiting for the native callback.
+        Thread.sleep(100 /*millis*/)
+        verify(native).setPointerDisplayId(overrideDisplayId)
+
+        service.onPointerDisplayIdChanged(overrideDisplayId, 0f, 0f)
+        testLooper.dispatchNext()
+        verify(wmCallbacks).notifyPointerDisplayIdChanged(overrideDisplayId, 0f, 0f)
+        thread.join(100 /*millis*/)
+    }
+}
+
+private fun <T> whenever(methodCall: T): OngoingStubbing<T> = `when`(methodCall)
diff --git a/tests/Input/src/com/android/server/input/KeyRemapperTests.kt b/tests/Input/src/com/android/server/input/KeyRemapperTests.kt
new file mode 100644
index 0000000..f74fd72
--- /dev/null
+++ b/tests/Input/src/com/android/server/input/KeyRemapperTests.kt
@@ -0,0 +1,193 @@
+/*
+ * 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.input
+
+import android.content.Context
+import android.content.ContextWrapper
+import android.hardware.input.IInputManager
+import android.hardware.input.InputManager
+import android.hardware.input.InputManagerGlobal
+import android.os.test.TestLooper
+import android.platform.test.annotations.Presubmit
+import android.provider.Settings
+import android.view.InputDevice
+import android.view.KeyEvent
+import androidx.test.core.app.ApplicationProvider
+import org.junit.After
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.junit.MockitoJUnit
+import java.io.FileNotFoundException
+import java.io.FileOutputStream
+import java.io.IOException
+import java.io.InputStream
+
+private fun createKeyboard(deviceId: Int): InputDevice =
+    InputDevice.Builder()
+        .setId(deviceId)
+        .setName("Device $deviceId")
+        .setDescriptor("descriptor $deviceId")
+        .setSources(InputDevice.SOURCE_KEYBOARD)
+        .setKeyboardType(InputDevice.KEYBOARD_TYPE_ALPHABETIC)
+        .setExternal(true)
+        .build()
+
+/**
+ * Tests for {@link KeyRemapper}.
+ *
+ * Build/Install/Run:
+ * atest InputTests:KeyRemapperTests
+ */
+@Presubmit
+class KeyRemapperTests {
+
+    companion object {
+        const val DEVICE_ID = 1
+        val REMAPPABLE_KEYS = intArrayOf(
+            KeyEvent.KEYCODE_CTRL_LEFT, KeyEvent.KEYCODE_CTRL_RIGHT,
+            KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_META_RIGHT,
+            KeyEvent.KEYCODE_ALT_LEFT, KeyEvent.KEYCODE_ALT_RIGHT,
+            KeyEvent.KEYCODE_SHIFT_LEFT, KeyEvent.KEYCODE_SHIFT_RIGHT,
+            KeyEvent.KEYCODE_CAPS_LOCK
+        )
+    }
+
+    @get:Rule
+    val rule = MockitoJUnit.rule()!!
+
+    @Mock
+    private lateinit var iInputManager: IInputManager
+    @Mock
+    private lateinit var native: NativeInputManagerService
+    private lateinit var mKeyRemapper: KeyRemapper
+    private lateinit var context: Context
+    private lateinit var dataStore: PersistentDataStore
+    private lateinit var testLooper: TestLooper
+    private lateinit var inputManagerGlobalSession: InputManagerGlobal.TestSession
+
+    @Before
+    fun setup() {
+        context = Mockito.spy(ContextWrapper(ApplicationProvider.getApplicationContext()))
+        dataStore = PersistentDataStore(object : PersistentDataStore.Injector() {
+            override fun openRead(): InputStream? {
+                throw FileNotFoundException()
+            }
+
+            override fun startWrite(): FileOutputStream? {
+                throw IOException()
+            }
+
+            override fun finishWrite(fos: FileOutputStream?, success: Boolean) {}
+        })
+        testLooper = TestLooper()
+        mKeyRemapper = KeyRemapper(
+            context,
+            native,
+            dataStore,
+            testLooper.looper
+        )
+        inputManagerGlobalSession = InputManagerGlobal.createTestSession(iInputManager)
+        val inputManager = InputManager(context)
+        Mockito.`when`(context.getSystemService(Mockito.eq(Context.INPUT_SERVICE)))
+            .thenReturn(inputManager)
+        Mockito.`when`(iInputManager.inputDeviceIds).thenReturn(intArrayOf(DEVICE_ID))
+    }
+
+    @After
+    fun tearDown() {
+        if (this::inputManagerGlobalSession.isInitialized) {
+            inputManagerGlobalSession.close()
+        }
+    }
+
+    @Test
+    fun testKeyRemapping_whenRemappingEnabled() {
+        ModifierRemappingFlag(true).use {
+            val keyboard = createKeyboard(DEVICE_ID)
+            Mockito.`when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboard)
+
+            for (i in REMAPPABLE_KEYS.indices) {
+                val fromKeyCode = REMAPPABLE_KEYS[i]
+                val toKeyCode = REMAPPABLE_KEYS[(i + 1) % REMAPPABLE_KEYS.size]
+                mKeyRemapper.remapKey(fromKeyCode, toKeyCode)
+                testLooper.dispatchNext()
+            }
+
+            val remapping = mKeyRemapper.keyRemapping
+            val expectedSize = REMAPPABLE_KEYS.size
+            assertEquals("Remapping size should be $expectedSize", expectedSize, remapping.size)
+
+            for (i in REMAPPABLE_KEYS.indices) {
+                val fromKeyCode = REMAPPABLE_KEYS[i]
+                val toKeyCode = REMAPPABLE_KEYS[(i + 1) % REMAPPABLE_KEYS.size]
+                assertEquals(
+                    "Remapping should include mapping from $fromKeyCode to $toKeyCode",
+                    toKeyCode,
+                    remapping.getOrDefault(fromKeyCode, -1)
+                )
+            }
+
+            mKeyRemapper.clearAllKeyRemappings()
+            testLooper.dispatchNext()
+
+            assertEquals(
+                "Remapping size should be 0 after clearAllModifierKeyRemappings",
+                0,
+                mKeyRemapper.keyRemapping.size
+            )
+        }
+    }
+
+    @Test
+    fun testKeyRemapping_whenRemappingDisabled() {
+        ModifierRemappingFlag(false).use {
+            val keyboard = createKeyboard(DEVICE_ID)
+            Mockito.`when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboard)
+
+            mKeyRemapper.remapKey(REMAPPABLE_KEYS[0], REMAPPABLE_KEYS[1])
+            testLooper.dispatchAll()
+
+            val remapping = mKeyRemapper.keyRemapping
+            assertEquals(
+                "Remapping should not be done if modifier key remapping is disabled",
+                0,
+                remapping.size
+            )
+        }
+    }
+
+    private inner class ModifierRemappingFlag constructor(enabled: Boolean) : AutoCloseable {
+        init {
+            Settings.Global.putString(
+                context.contentResolver,
+                "settings_new_keyboard_modifier_key", enabled.toString()
+            )
+        }
+
+        override fun close() {
+            Settings.Global.putString(
+                context.contentResolver,
+                "settings_new_keyboard_modifier_key",
+                ""
+            )
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/Input/src/com/android/server/input/KeyboardBacklightControllerTests.kt b/tests/Input/src/com/android/server/input/KeyboardBacklightControllerTests.kt
new file mode 100644
index 0000000..59aa96c
--- /dev/null
+++ b/tests/Input/src/com/android/server/input/KeyboardBacklightControllerTests.kt
@@ -0,0 +1,849 @@
+/*
+ * 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.input
+
+import android.animation.ValueAnimator
+import android.content.Context
+import android.content.ContextWrapper
+import android.graphics.Color
+import android.hardware.input.IInputManager
+import android.hardware.input.IKeyboardBacklightListener
+import android.hardware.input.IKeyboardBacklightState
+import android.hardware.input.InputManager
+import android.hardware.input.InputManagerGlobal
+import android.hardware.lights.Light
+import android.os.UEventObserver
+import android.os.test.TestLooper
+import android.platform.test.annotations.Presubmit
+import android.view.InputDevice
+import androidx.test.annotation.UiThreadTest
+import androidx.test.core.app.ApplicationProvider
+import com.android.server.input.KeyboardBacklightController.DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL
+import com.android.server.input.KeyboardBacklightController.MAX_BRIGHTNESS_CHANGE_STEPS
+import com.android.server.input.KeyboardBacklightController.USER_INACTIVITY_THRESHOLD_MILLIS
+import org.junit.After
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertNotEquals
+import org.junit.Assert.assertNotNull
+import org.junit.Assert.assertNull
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito.any
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.eq
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.`when`
+import org.mockito.junit.MockitoJUnit
+import java.io.FileNotFoundException
+import java.io.FileOutputStream
+import java.io.IOException
+import java.io.InputStream
+
+private fun createKeyboard(deviceId: Int): InputDevice =
+    InputDevice.Builder()
+        .setId(deviceId)
+        .setName("Device $deviceId")
+        .setDescriptor("descriptor $deviceId")
+        .setSources(InputDevice.SOURCE_KEYBOARD)
+        .setKeyboardType(InputDevice.KEYBOARD_TYPE_ALPHABETIC)
+        .setExternal(true)
+        .build()
+
+private fun createLight(lightId: Int, lightType: Int): Light =
+    createLight(
+        lightId,
+        lightType,
+        null
+    )
+
+private fun createLight(lightId: Int, lightType: Int, suggestedBrightnessLevels: IntArray?): Light =
+    Light(
+        lightId,
+        "Light $lightId",
+        1,
+        lightType,
+        Light.LIGHT_CAPABILITY_BRIGHTNESS,
+        suggestedBrightnessLevels
+    )
+/**
+ * Tests for {@link KeyboardBacklightController}.
+ *
+ * Build/Install/Run:
+ * atest InputTests:KeyboardBacklightControllerTests
+ */
+@Presubmit
+class KeyboardBacklightControllerTests {
+    companion object {
+        const val DEVICE_ID = 1
+        const val LIGHT_ID = 2
+        const val SECOND_LIGHT_ID = 3
+        const val MAX_BRIGHTNESS = 255
+    }
+
+    @get:Rule
+    val rule = MockitoJUnit.rule()!!
+
+    @Mock
+    private lateinit var iInputManager: IInputManager
+    @Mock
+    private lateinit var native: NativeInputManagerService
+    @Mock
+    private lateinit var uEventManager: UEventManager
+    private lateinit var keyboardBacklightController: KeyboardBacklightController
+    private lateinit var context: Context
+    private lateinit var dataStore: PersistentDataStore
+    private lateinit var testLooper: TestLooper
+    private lateinit var inputManagerGlobalSession: InputManagerGlobal.TestSession
+    private var lightColorMap: HashMap<Int, Int> = HashMap()
+    private var lastBacklightState: KeyboardBacklightState? = null
+    private var sysfsNodeChanges = 0
+    private var lastAnimationValues = IntArray(2)
+
+    @Before
+    fun setup() {
+        context = spy(ContextWrapper(ApplicationProvider.getApplicationContext()))
+        dataStore = PersistentDataStore(object : PersistentDataStore.Injector() {
+            override fun openRead(): InputStream? {
+                throw FileNotFoundException()
+            }
+
+            override fun startWrite(): FileOutputStream? {
+                throw IOException()
+            }
+
+            override fun finishWrite(fos: FileOutputStream?, success: Boolean) {}
+        })
+        testLooper = TestLooper()
+        keyboardBacklightController = KeyboardBacklightController(context, native, dataStore,
+                testLooper.looper, FakeAnimatorFactory(), uEventManager)
+        inputManagerGlobalSession = InputManagerGlobal.createTestSession(iInputManager)
+        val inputManager = InputManager(context)
+        `when`(context.getSystemService(eq(Context.INPUT_SERVICE))).thenReturn(inputManager)
+        `when`(iInputManager.inputDeviceIds).thenReturn(intArrayOf(DEVICE_ID))
+        `when`(native.setLightColor(anyInt(), anyInt(), anyInt())).then {
+            val args = it.arguments
+            lightColorMap.put(args[1] as Int, args[2] as Int)
+        }
+        `when`(native.getLightColor(anyInt(), anyInt())).thenAnswer {
+            val args = it.arguments
+            lightColorMap.getOrDefault(args[1] as Int, 0)
+        }
+        lightColorMap.clear()
+        `when`(native.sysfsNodeChanged(any())).then {
+            sysfsNodeChanges++
+        }
+    }
+
+    @After
+    fun tearDown() {
+        if (this::inputManagerGlobalSession.isInitialized) {
+            inputManagerGlobalSession.close()
+        }
+    }
+
+    @Test
+    fun testKeyboardBacklightIncrementDecrement() {
+        KeyboardBacklightFlags(
+            animationEnabled = false,
+            customLevelsEnabled = false,
+            ambientControlEnabled = false
+        ).use {
+            val keyboardWithBacklight = createKeyboard(DEVICE_ID)
+            val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
+            `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
+            `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
+            keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
+
+            assertIncrementDecrementForLevels(keyboardWithBacklight, keyboardBacklight,
+                    DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL)
+        }
+    }
+
+    @Test
+    fun testKeyboardWithoutBacklight() {
+        KeyboardBacklightFlags(
+            animationEnabled = false,
+            customLevelsEnabled = false,
+            ambientControlEnabled = false
+        ).use {
+            val keyboardWithoutBacklight = createKeyboard(DEVICE_ID)
+            val keyboardInputLight = createLight(LIGHT_ID, Light.LIGHT_TYPE_INPUT)
+            `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithoutBacklight)
+            `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardInputLight))
+            keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
+
+            incrementKeyboardBacklight(DEVICE_ID)
+            assertTrue("Non Keyboard backlights should not change", lightColorMap.isEmpty())
+        }
+    }
+
+    @Test
+    fun testKeyboardWithMultipleLight() {
+        KeyboardBacklightFlags(
+            animationEnabled = false,
+            customLevelsEnabled = false,
+            ambientControlEnabled = false
+        ).use {
+            val keyboardWithBacklight = createKeyboard(DEVICE_ID)
+            val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
+            val keyboardInputLight = createLight(SECOND_LIGHT_ID, Light.LIGHT_TYPE_INPUT)
+            `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
+            `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(
+                listOf(
+                    keyboardBacklight,
+                    keyboardInputLight
+                )
+            )
+            keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
+
+            incrementKeyboardBacklight(DEVICE_ID)
+            assertEquals("Only keyboard backlights should change", 1, lightColorMap.size)
+            assertNotNull("Keyboard backlight should change", lightColorMap[LIGHT_ID])
+            assertNull("Input lights should not change", lightColorMap[SECOND_LIGHT_ID])
+        }
+    }
+
+    @Test
+    fun testRestoreBacklightOnInputDeviceAdded() {
+        KeyboardBacklightFlags(
+            animationEnabled = false,
+            customLevelsEnabled = false,
+            ambientControlEnabled = false
+        ).use {
+            val keyboardWithBacklight = createKeyboard(DEVICE_ID)
+            val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
+            `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
+            `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
+
+            for (level in 1 until DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL.size) {
+                dataStore.setKeyboardBacklightBrightness(
+                    keyboardWithBacklight.descriptor,
+                    LIGHT_ID,
+                    DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL[level] - 1
+                )
+
+                keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
+                keyboardBacklightController.notifyUserActivity()
+                testLooper.dispatchNext()
+                assertEquals(
+                    "Keyboard backlight level should be restored to the level saved in the " +
+                            "data store",
+                    Color.argb(DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL[level], 0, 0, 0),
+                    lightColorMap[LIGHT_ID]
+                )
+                keyboardBacklightController.onInputDeviceRemoved(DEVICE_ID)
+            }
+        }
+    }
+
+    @Test
+    fun testRestoreBacklightOnInputDeviceChanged() {
+        KeyboardBacklightFlags(
+            animationEnabled = false,
+            customLevelsEnabled = false,
+            ambientControlEnabled = false
+        ).use {
+            val keyboardWithBacklight = createKeyboard(DEVICE_ID)
+            val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
+            `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
+            dataStore.setKeyboardBacklightBrightness(
+                keyboardWithBacklight.descriptor,
+                LIGHT_ID,
+                MAX_BRIGHTNESS
+            )
+
+            keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
+            keyboardBacklightController.notifyUserActivity()
+            testLooper.dispatchNext()
+            assertTrue(
+                "Keyboard backlight should not be changed until its added",
+                lightColorMap.isEmpty()
+            )
+
+            `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
+            keyboardBacklightController.onInputDeviceChanged(DEVICE_ID)
+            keyboardBacklightController.notifyUserActivity()
+            testLooper.dispatchNext()
+            assertEquals(
+                "Keyboard backlight level should be restored to the level saved in the data store",
+                Color.argb(MAX_BRIGHTNESS, 0, 0, 0),
+                lightColorMap[LIGHT_ID]
+            )
+        }
+    }
+
+    @Test
+    fun testKeyboardBacklight_registerUnregisterListener() {
+        KeyboardBacklightFlags(
+            animationEnabled = false,
+            customLevelsEnabled = false,
+            ambientControlEnabled = false
+        ).use {
+            val keyboardWithBacklight = createKeyboard(DEVICE_ID)
+            val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
+            val maxLevel = DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL.size - 1
+            `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
+            `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
+            keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
+
+            // Register backlight listener
+            val listener = KeyboardBacklightListener()
+            keyboardBacklightController.registerKeyboardBacklightListener(listener, 0)
+
+            lastBacklightState = null
+            keyboardBacklightController.incrementKeyboardBacklight(DEVICE_ID)
+            testLooper.dispatchNext()
+
+            assertEquals(
+                "Backlight state device Id should be $DEVICE_ID",
+                DEVICE_ID,
+                lastBacklightState!!.deviceId
+            )
+            assertEquals(
+                "Backlight state brightnessLevel should be 1",
+                1,
+                lastBacklightState!!.brightnessLevel
+            )
+            assertEquals(
+                "Backlight state maxBrightnessLevel should be $maxLevel",
+                maxLevel,
+                lastBacklightState!!.maxBrightnessLevel
+            )
+            assertEquals(
+                "Backlight state isTriggeredByKeyPress should be true",
+                true,
+                lastBacklightState!!.isTriggeredByKeyPress
+            )
+
+            // Unregister listener
+            keyboardBacklightController.unregisterKeyboardBacklightListener(listener, 0)
+
+            lastBacklightState = null
+            incrementKeyboardBacklight(DEVICE_ID)
+
+            assertNull("Listener should not receive any updates", lastBacklightState)
+        }
+    }
+
+    @Test
+    fun testKeyboardBacklight_userActivity() {
+        KeyboardBacklightFlags(
+            animationEnabled = false,
+            customLevelsEnabled = false,
+            ambientControlEnabled = false
+        ).use {
+            val keyboardWithBacklight = createKeyboard(DEVICE_ID)
+            val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
+            `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
+            `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
+            dataStore.setKeyboardBacklightBrightness(
+                keyboardWithBacklight.descriptor,
+                LIGHT_ID,
+                MAX_BRIGHTNESS
+            )
+
+            keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
+            keyboardBacklightController.notifyUserActivity()
+            testLooper.dispatchNext()
+            assertEquals(
+                "Keyboard backlight level should be restored to the level saved in the data store",
+                Color.argb(MAX_BRIGHTNESS, 0, 0, 0),
+                lightColorMap[LIGHT_ID]
+            )
+
+            testLooper.moveTimeForward(USER_INACTIVITY_THRESHOLD_MILLIS + 1000)
+            testLooper.dispatchNext()
+            assertEquals(
+                "Keyboard backlight level should be turned off after inactivity",
+                0,
+                lightColorMap[LIGHT_ID]
+            )
+        }
+    }
+
+    @Test
+    fun testKeyboardBacklight_displayOnOff() {
+        KeyboardBacklightFlags(
+            animationEnabled = false,
+            customLevelsEnabled = false,
+            ambientControlEnabled = false
+        ).use {
+            val keyboardWithBacklight = createKeyboard(DEVICE_ID)
+            val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
+            `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
+            `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
+            dataStore.setKeyboardBacklightBrightness(
+                keyboardWithBacklight.descriptor,
+                LIGHT_ID,
+                MAX_BRIGHTNESS
+            )
+
+            keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
+            keyboardBacklightController.handleInteractiveStateChange(true /* isDisplayOn */)
+            assertEquals(
+                "Keyboard backlight level should be restored to the level saved in the data " +
+                        "store when display turned on",
+                Color.argb(MAX_BRIGHTNESS, 0, 0, 0),
+                lightColorMap[LIGHT_ID]
+            )
+
+            keyboardBacklightController.handleInteractiveStateChange(false /* isDisplayOn */)
+            assertEquals(
+                "Keyboard backlight level should be turned off after display is turned off",
+                0,
+                lightColorMap[LIGHT_ID]
+            )
+        }
+    }
+
+    @Test
+    fun testKeyboardBacklightSysfsNodeAdded_AfterInputDeviceAdded() {
+        var counter = sysfsNodeChanges
+        keyboardBacklightController.onKeyboardBacklightUEvent(UEventObserver.UEvent(
+            "ACTION=add\u0000SUBSYSTEM=leds\u0000DEVPATH=/xyz/leds/abc::no_backlight\u0000"
+        ))
+        assertEquals(
+            "Should not reload sysfs node if UEvent path doesn't contain kbd_backlight",
+            counter,
+            sysfsNodeChanges
+        )
+
+        keyboardBacklightController.onKeyboardBacklightUEvent(UEventObserver.UEvent(
+            "ACTION=add\u0000SUBSYSTEM=power\u0000DEVPATH=/xyz/leds/abc::kbd_backlight\u0000"
+        ))
+        assertEquals(
+            "Should not reload sysfs node if UEvent doesn't belong to subsystem LED",
+            counter,
+            sysfsNodeChanges
+        )
+
+        keyboardBacklightController.onKeyboardBacklightUEvent(UEventObserver.UEvent(
+            "ACTION=remove\u0000SUBSYSTEM=leds\u0000DEVPATH=/xyz/leds/abc::kbd_backlight\u0000"
+        ))
+        assertEquals(
+            "Should not reload sysfs node if UEvent doesn't have ACTION(add)",
+            counter,
+            sysfsNodeChanges
+        )
+
+        keyboardBacklightController.onKeyboardBacklightUEvent(UEventObserver.UEvent(
+            "ACTION=add\u0000SUBSYSTEM=leds\u0000DEVPATH=/xyz/pqr/abc::kbd_backlight\u0000"
+        ))
+        assertEquals(
+            "Should not reload sysfs node if UEvent path doesn't belong to leds/ directory",
+            counter,
+            sysfsNodeChanges
+        )
+
+        keyboardBacklightController.onKeyboardBacklightUEvent(UEventObserver.UEvent(
+            "ACTION=add\u0000SUBSYSTEM=leds\u0000DEVPATH=/xyz/leds/abc::kbd_backlight\u0000"
+        ))
+        assertEquals(
+            "Should reload sysfs node if a valid Keyboard backlight LED UEvent occurs",
+            ++counter,
+            sysfsNodeChanges
+        )
+
+        keyboardBacklightController.onKeyboardBacklightUEvent(UEventObserver.UEvent(
+            "ACTION=add\u0000SUBSYSTEM=leds\u0000DEVPATH=/xyz/leds/abc:kbd_backlight:red\u0000"
+        ))
+        assertEquals(
+            "Should reload sysfs node if a valid Keyboard backlight LED UEvent occurs",
+            ++counter,
+            sysfsNodeChanges
+        )
+    }
+
+    @Test
+    @UiThreadTest
+    fun testKeyboardBacklightAnimation_onChangeLevels() {
+        KeyboardBacklightFlags(
+            animationEnabled = true,
+            customLevelsEnabled = false,
+            ambientControlEnabled = false
+        ).use {
+            val keyboardWithBacklight = createKeyboard(DEVICE_ID)
+            val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
+            `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
+            `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
+            keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
+
+            incrementKeyboardBacklight(DEVICE_ID)
+            assertEquals(
+                "Should start animation from level 0",
+                DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL[0],
+                lastAnimationValues[0]
+            )
+            assertEquals(
+                "Should start animation to level 1",
+                DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL[1],
+                lastAnimationValues[1]
+            )
+        }
+    }
+
+    @Test
+    fun testKeyboardBacklightPreferredLevels() {
+        KeyboardBacklightFlags(
+            animationEnabled = false,
+            customLevelsEnabled = true,
+            ambientControlEnabled = false
+        ).use {
+            val keyboardWithBacklight = createKeyboard(DEVICE_ID)
+            val suggestedLevels = intArrayOf(0, 22, 63, 135, 196, 255)
+            val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT,
+                    suggestedLevels)
+            `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
+            `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
+            keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
+
+            assertIncrementDecrementForLevels(keyboardWithBacklight, keyboardBacklight,
+                    suggestedLevels)
+        }
+    }
+
+    @Test
+    fun testKeyboardBacklightPreferredLevels_moreThanMax_shouldUseDefault() {
+        KeyboardBacklightFlags(
+            animationEnabled = false,
+            customLevelsEnabled = true,
+            ambientControlEnabled = false
+        ).use {
+            val keyboardWithBacklight = createKeyboard(DEVICE_ID)
+            val suggestedLevels = IntArray(MAX_BRIGHTNESS_CHANGE_STEPS + 1) { 10 * (it + 1) }
+            val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT,
+                    suggestedLevels)
+            `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
+            `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
+            keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
+
+            assertIncrementDecrementForLevels(keyboardWithBacklight, keyboardBacklight,
+                    DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL)
+        }
+    }
+
+    @Test
+    fun testKeyboardBacklightPreferredLevels_mustHaveZeroAndMaxBrightnessAsBounds() {
+        KeyboardBacklightFlags(
+            animationEnabled = false,
+            customLevelsEnabled = true,
+            ambientControlEnabled = false
+        ).use {
+            val keyboardWithBacklight = createKeyboard(DEVICE_ID)
+            val suggestedLevels = intArrayOf(22, 63, 135, 196)
+            val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT,
+                    suggestedLevels)
+            `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
+            `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
+            keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
+
+            // Framework will add the lowest and maximum levels if not provided via config
+            assertIncrementDecrementForLevels(keyboardWithBacklight, keyboardBacklight,
+                    intArrayOf(0, 22, 63, 135, 196, 255))
+        }
+    }
+
+    @Test
+    fun testKeyboardBacklightPreferredLevels_dropsOutOfBoundsLevels() {
+        KeyboardBacklightFlags(
+            animationEnabled = false,
+            customLevelsEnabled = true,
+            ambientControlEnabled = false
+        ).use {
+            val keyboardWithBacklight = createKeyboard(DEVICE_ID)
+            val suggestedLevels = intArrayOf(22, 63, 135, 400, 196, 1000)
+            val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT,
+                    suggestedLevels)
+            `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
+            `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
+            keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
+
+            // Framework will drop out of bound levels in the config
+            assertIncrementDecrementForLevels(keyboardWithBacklight, keyboardBacklight,
+                    intArrayOf(0, 22, 63, 135, 196, 255))
+        }
+    }
+
+    @Test
+    fun testAmbientBacklightControl_doesntRestoreBacklightLevel() {
+        KeyboardBacklightFlags(
+            animationEnabled = false,
+            customLevelsEnabled = false,
+            ambientControlEnabled = true
+        ).use {
+            val keyboardWithBacklight = createKeyboard(DEVICE_ID)
+            val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
+            `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
+            `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
+
+            dataStore.setKeyboardBacklightBrightness(
+                keyboardWithBacklight.descriptor,
+                LIGHT_ID,
+                DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL[1]
+            )
+
+            keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
+            keyboardBacklightController.notifyUserActivity()
+            testLooper.dispatchNext()
+            assertNull(
+                "Keyboard backlight level should not be restored to the saved level",
+                lightColorMap[LIGHT_ID]
+            )
+        }
+    }
+
+    @Test
+    fun testAmbientBacklightControl_doesntBackupBacklightLevel() {
+        KeyboardBacklightFlags(
+            animationEnabled = false,
+            customLevelsEnabled = false,
+            ambientControlEnabled = true
+        ).use {
+            val keyboardWithBacklight = createKeyboard(DEVICE_ID)
+            val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
+            `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
+            `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
+
+            keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
+            incrementKeyboardBacklight(DEVICE_ID)
+            assertFalse(
+                "Light value should not be backed up if ambient control is enabled",
+                dataStore.getKeyboardBacklightBrightness(
+                    keyboardWithBacklight.descriptor, LIGHT_ID
+                ).isPresent
+            )
+        }
+    }
+
+    @Test
+    fun testAmbientBacklightControl_incrementLevel_afterAmbientChange() {
+        KeyboardBacklightFlags(
+            animationEnabled = false,
+            customLevelsEnabled = false,
+            ambientControlEnabled = true
+        ).use {
+            val keyboardWithBacklight = createKeyboard(DEVICE_ID)
+            val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
+            `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
+            `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
+            keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
+            sendAmbientBacklightValue(1)
+            assertEquals(
+                "Light value should be changed to ambient provided value",
+                Color.argb(1, 0, 0, 0),
+                lightColorMap[LIGHT_ID]
+            )
+
+            incrementKeyboardBacklight(DEVICE_ID)
+
+            assertEquals(
+                "Light value for level after increment post Ambient change is mismatched",
+                Color.argb(DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL[1], 0, 0, 0),
+                lightColorMap[LIGHT_ID]
+            )
+        }
+    }
+
+    @Test
+    fun testAmbientBacklightControl_decrementLevel_afterAmbientChange() {
+        KeyboardBacklightFlags(
+            animationEnabled = false,
+            customLevelsEnabled = false,
+            ambientControlEnabled = true
+        ).use {
+            val keyboardWithBacklight = createKeyboard(DEVICE_ID)
+            val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
+            `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
+            `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
+            keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
+            sendAmbientBacklightValue(254)
+            assertEquals(
+                "Light value should be changed to ambient provided value",
+                Color.argb(254, 0, 0, 0),
+                lightColorMap[LIGHT_ID]
+            )
+
+            decrementKeyboardBacklight(DEVICE_ID)
+
+            val numLevels = DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL.size
+            assertEquals(
+                "Light value for level after decrement post Ambient change is mismatched",
+                Color.argb(DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL[numLevels - 2], 0, 0, 0),
+                lightColorMap[LIGHT_ID]
+            )
+        }
+    }
+
+    @Test
+    fun testAmbientBacklightControl_ambientChanges_afterManualChange() {
+        KeyboardBacklightFlags(
+            animationEnabled = false,
+            customLevelsEnabled = false,
+            ambientControlEnabled = true
+        ).use {
+            val keyboardWithBacklight = createKeyboard(DEVICE_ID)
+            val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
+            `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
+            `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
+            keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
+            incrementKeyboardBacklight(DEVICE_ID)
+            assertEquals(
+                "Light value should be changed to the first level",
+                Color.argb(DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL[1], 0, 0, 0),
+                lightColorMap[LIGHT_ID]
+            )
+
+            sendAmbientBacklightValue(100)
+            assertNotEquals(
+                "Light value should not change based on ambient changes after manual changes",
+                Color.argb(100, 0, 0, 0),
+                lightColorMap[LIGHT_ID]
+            )
+        }
+    }
+
+    private fun assertIncrementDecrementForLevels(
+        device: InputDevice,
+        light: Light,
+        expectedLevels: IntArray
+    ) {
+        val deviceId = device.id
+        val lightId = light.id
+        for (level in 1 until expectedLevels.size) {
+            incrementKeyboardBacklight(deviceId)
+            assertEquals(
+                "Light value for level $level mismatched",
+                Color.argb(expectedLevels[level], 0, 0, 0),
+                lightColorMap[lightId]
+            )
+            assertEquals(
+                "Light value for level $level must be correctly stored in the datastore",
+                expectedLevels[level],
+                dataStore.getKeyboardBacklightBrightness(device.descriptor, lightId).asInt
+            )
+        }
+
+        // Increment above max level
+        incrementKeyboardBacklight(deviceId)
+        assertEquals(
+            "Light value for max level mismatched",
+            Color.argb(MAX_BRIGHTNESS, 0, 0, 0),
+            lightColorMap[lightId]
+        )
+        assertEquals(
+            "Light value for max level must be correctly stored in the datastore",
+            MAX_BRIGHTNESS,
+            dataStore.getKeyboardBacklightBrightness(device.descriptor, lightId).asInt
+        )
+
+        for (level in expectedLevels.size - 2 downTo 0) {
+            decrementKeyboardBacklight(deviceId)
+            assertEquals(
+                "Light value for level $level mismatched",
+                Color.argb(expectedLevels[level], 0, 0, 0),
+                lightColorMap[lightId]
+            )
+            assertEquals(
+                "Light value for level $level must be correctly stored in the datastore",
+                expectedLevels[level],
+                dataStore.getKeyboardBacklightBrightness(device.descriptor, lightId).asInt
+            )
+        }
+
+        // Decrement below min level
+        decrementKeyboardBacklight(deviceId)
+        assertEquals(
+            "Light value for min level mismatched",
+            Color.argb(0, 0, 0, 0),
+            lightColorMap[lightId]
+        )
+        assertEquals(
+            "Light value for min level must be correctly stored in the datastore",
+            0,
+            dataStore.getKeyboardBacklightBrightness(device.descriptor, lightId).asInt
+        )
+    }
+
+    inner class KeyboardBacklightListener : IKeyboardBacklightListener.Stub() {
+        override fun onBrightnessChanged(
+            deviceId: Int,
+            state: IKeyboardBacklightState,
+            isTriggeredByKeyPress: Boolean
+        ) {
+            lastBacklightState = KeyboardBacklightState(
+                deviceId,
+                state.brightnessLevel,
+                state.maxBrightnessLevel,
+                isTriggeredByKeyPress
+            )
+        }
+    }
+
+    private fun incrementKeyboardBacklight(deviceId: Int) {
+        keyboardBacklightController.incrementKeyboardBacklight(deviceId)
+        keyboardBacklightController.notifyUserActivity()
+        testLooper.dispatchAll()
+    }
+
+    private fun decrementKeyboardBacklight(deviceId: Int) {
+        keyboardBacklightController.decrementKeyboardBacklight(deviceId)
+        keyboardBacklightController.notifyUserActivity()
+        testLooper.dispatchAll()
+    }
+
+    private fun sendAmbientBacklightValue(brightnessValue: Int) {
+        keyboardBacklightController.handleAmbientLightValueChanged(brightnessValue)
+        keyboardBacklightController.notifyUserActivity()
+        testLooper.dispatchAll()
+    }
+
+    class KeyboardBacklightState(
+        val deviceId: Int,
+        val brightnessLevel: Int,
+        val maxBrightnessLevel: Int,
+        val isTriggeredByKeyPress: Boolean
+    )
+
+    private inner class KeyboardBacklightFlags constructor(
+        animationEnabled: Boolean,
+        customLevelsEnabled: Boolean,
+        ambientControlEnabled: Boolean
+    ) : AutoCloseable {
+        init {
+            InputFeatureFlagProvider.setKeyboardBacklightAnimationEnabled(animationEnabled)
+            InputFeatureFlagProvider.setKeyboardBacklightCustomLevelsEnabled(customLevelsEnabled)
+            InputFeatureFlagProvider
+                .setAmbientKeyboardBacklightControlEnabled(ambientControlEnabled)
+        }
+
+        override fun close() {
+            InputFeatureFlagProvider.clearOverrides()
+        }
+    }
+
+    private inner class FakeAnimatorFactory : KeyboardBacklightController.AnimatorFactory {
+        override fun makeIntAnimator(from: Int, to: Int): ValueAnimator {
+            lastAnimationValues[0] = from
+            lastAnimationValues[1] = to
+            return ValueAnimator.ofInt(from, to)
+        }
+    }
+}
diff --git a/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt b/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt
new file mode 100644
index 0000000..b6477510
--- /dev/null
+++ b/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt
@@ -0,0 +1,912 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.input
+
+import android.content.Context
+import android.content.ContextWrapper
+import android.content.pm.ActivityInfo
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
+import android.content.pm.ResolveInfo
+import android.content.pm.ServiceInfo
+import android.hardware.input.IInputManager
+import android.hardware.input.InputManager
+import android.hardware.input.InputManagerGlobal
+import android.hardware.input.KeyboardLayout
+import android.icu.util.ULocale
+import android.os.Bundle
+import android.os.test.TestLooper
+import android.platform.test.annotations.Presubmit
+import android.provider.Settings
+import android.view.InputDevice
+import android.view.inputmethod.InputMethodInfo
+import android.view.inputmethod.InputMethodSubtype
+import androidx.test.core.R
+import androidx.test.core.app.ApplicationProvider
+import org.junit.After
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNotEquals
+import org.junit.Assert.assertNull
+import org.junit.Assert.assertTrue
+import org.junit.Assert.assertThrows
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.junit.MockitoJUnit
+import java.io.FileNotFoundException
+import java.io.FileOutputStream
+import java.io.IOException
+import java.io.InputStream
+
+private fun createKeyboard(
+    deviceId: Int,
+    vendorId: Int,
+    productId: Int,
+    languageTag: String,
+    layoutType: String
+): InputDevice =
+    InputDevice.Builder()
+        .setId(deviceId)
+        .setName("Device $deviceId")
+        .setDescriptor("descriptor $deviceId")
+        .setSources(InputDevice.SOURCE_KEYBOARD)
+        .setKeyboardType(InputDevice.KEYBOARD_TYPE_ALPHABETIC)
+        .setExternal(true)
+        .setVendorId(vendorId)
+        .setProductId(productId)
+        .setKeyboardLanguageTag(languageTag)
+        .setKeyboardLayoutType(layoutType)
+        .build()
+
+/**
+ * Tests for {@link Default UI} and {@link New UI}.
+ *
+ * Build/Install/Run:
+ * atest InputTests:KeyboardLayoutManagerTests
+ */
+@Presubmit
+class KeyboardLayoutManagerTests {
+    companion object {
+        const val DEVICE_ID = 1
+        const val VENDOR_SPECIFIC_DEVICE_ID = 2
+        const val ENGLISH_DVORAK_DEVICE_ID = 3
+        const val ENGLISH_QWERTY_DEVICE_ID = 4
+        const val DEFAULT_VENDOR_ID = 123
+        const val DEFAULT_PRODUCT_ID = 456
+        const val USER_ID = 4
+        const val IME_ID = "ime_id"
+        const val PACKAGE_NAME = "KeyboardLayoutManagerTests"
+        const val RECEIVER_NAME = "DummyReceiver"
+        private const val ENGLISH_US_LAYOUT_NAME = "keyboard_layout_english_us"
+        private const val ENGLISH_UK_LAYOUT_NAME = "keyboard_layout_english_uk"
+        private const val VENDOR_SPECIFIC_LAYOUT_NAME = "keyboard_layout_vendorId:1,productId:1"
+    }
+
+    private val ENGLISH_US_LAYOUT_DESCRIPTOR = createLayoutDescriptor(ENGLISH_US_LAYOUT_NAME)
+    private val ENGLISH_UK_LAYOUT_DESCRIPTOR = createLayoutDescriptor(ENGLISH_UK_LAYOUT_NAME)
+    private val VENDOR_SPECIFIC_LAYOUT_DESCRIPTOR =
+        createLayoutDescriptor(VENDOR_SPECIFIC_LAYOUT_NAME)
+
+    @get:Rule
+    val rule = MockitoJUnit.rule()!!
+
+    @Mock
+    private lateinit var iInputManager: IInputManager
+
+    @Mock
+    private lateinit var native: NativeInputManagerService
+
+    @Mock
+    private lateinit var packageManager: PackageManager
+    private lateinit var keyboardLayoutManager: KeyboardLayoutManager
+
+    private lateinit var imeInfo: InputMethodInfo
+    private var nextImeSubtypeId = 0
+    private lateinit var context: Context
+    private lateinit var dataStore: PersistentDataStore
+    private lateinit var testLooper: TestLooper
+    private lateinit var inputManagerGlobalSession: InputManagerGlobal.TestSession
+
+    // Devices
+    private lateinit var keyboardDevice: InputDevice
+    private lateinit var vendorSpecificKeyboardDevice: InputDevice
+    private lateinit var englishDvorakKeyboardDevice: InputDevice
+    private lateinit var englishQwertyKeyboardDevice: InputDevice
+
+    @Before
+    fun setup() {
+        context = Mockito.spy(ContextWrapper(ApplicationProvider.getApplicationContext()))
+        inputManagerGlobalSession = InputManagerGlobal.createTestSession(iInputManager)
+        dataStore = PersistentDataStore(object : PersistentDataStore.Injector() {
+            override fun openRead(): InputStream? {
+                throw FileNotFoundException()
+            }
+
+            override fun startWrite(): FileOutputStream? {
+                throw IOException()
+            }
+
+            override fun finishWrite(fos: FileOutputStream?, success: Boolean) {}
+        })
+        testLooper = TestLooper()
+        keyboardLayoutManager = KeyboardLayoutManager(context, native, dataStore, testLooper.looper)
+        setupInputDevices()
+        setupBroadcastReceiver()
+        setupIme()
+    }
+
+    @After
+    fun tearDown() {
+        if (this::inputManagerGlobalSession.isInitialized) {
+            inputManagerGlobalSession.close()
+        }
+    }
+
+    private fun setupInputDevices() {
+        val inputManager = InputManager(context)
+        Mockito.`when`(context.getSystemService(Mockito.eq(Context.INPUT_SERVICE)))
+            .thenReturn(inputManager)
+
+        keyboardDevice = createKeyboard(DEVICE_ID, DEFAULT_VENDOR_ID, DEFAULT_PRODUCT_ID, "", "")
+        vendorSpecificKeyboardDevice = createKeyboard(VENDOR_SPECIFIC_DEVICE_ID, 1, 1, "", "")
+        englishDvorakKeyboardDevice = createKeyboard(ENGLISH_DVORAK_DEVICE_ID, DEFAULT_VENDOR_ID,
+                DEFAULT_PRODUCT_ID, "en", "dvorak")
+        englishQwertyKeyboardDevice = createKeyboard(ENGLISH_QWERTY_DEVICE_ID, DEFAULT_VENDOR_ID,
+                DEFAULT_PRODUCT_ID, "en", "qwerty")
+        Mockito.`when`(iInputManager.inputDeviceIds)
+            .thenReturn(intArrayOf(
+                DEVICE_ID,
+                VENDOR_SPECIFIC_DEVICE_ID,
+                ENGLISH_DVORAK_DEVICE_ID,
+                ENGLISH_QWERTY_DEVICE_ID
+            ))
+        Mockito.`when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardDevice)
+        Mockito.`when`(iInputManager.getInputDevice(VENDOR_SPECIFIC_DEVICE_ID))
+            .thenReturn(vendorSpecificKeyboardDevice)
+        Mockito.`when`(iInputManager.getInputDevice(ENGLISH_DVORAK_DEVICE_ID))
+            .thenReturn(englishDvorakKeyboardDevice)
+        Mockito.`when`(iInputManager.getInputDevice(ENGLISH_QWERTY_DEVICE_ID))
+                .thenReturn(englishQwertyKeyboardDevice)
+    }
+
+    private fun setupBroadcastReceiver() {
+        Mockito.`when`(context.packageManager).thenReturn(packageManager)
+
+        val info = createMockReceiver()
+        Mockito.`when`(packageManager.queryBroadcastReceiversAsUser(Mockito.any(), Mockito.anyInt(),
+                Mockito.anyInt())).thenReturn(listOf(info))
+        Mockito.`when`(packageManager.getReceiverInfo(Mockito.any(), Mockito.anyInt()))
+            .thenReturn(info.activityInfo)
+
+        val resources = context.resources
+        Mockito.`when`(
+            packageManager.getResourcesForApplication(
+                Mockito.any(
+                    ApplicationInfo::class.java
+                )
+            )
+        ).thenReturn(resources)
+    }
+
+    private fun setupIme() {
+        imeInfo = InputMethodInfo(PACKAGE_NAME, RECEIVER_NAME, "", "", 0)
+    }
+
+    @Test
+    fun testDefaultUi_getKeyboardLayouts() {
+        NewSettingsApiFlag(false).use {
+            val keyboardLayouts = keyboardLayoutManager.keyboardLayouts
+            assertNotEquals(
+                "Default UI: Keyboard layout API should not return empty array",
+                0,
+                keyboardLayouts.size
+            )
+            assertTrue(
+                "Default UI: Keyboard layout API should provide English(US) layout",
+                hasLayout(keyboardLayouts, ENGLISH_US_LAYOUT_DESCRIPTOR)
+            )
+        }
+    }
+
+    @Test
+    fun testNewUi_getKeyboardLayouts() {
+        NewSettingsApiFlag(true).use {
+            val keyboardLayouts = keyboardLayoutManager.keyboardLayouts
+            assertNotEquals(
+                "New UI: Keyboard layout API should not return empty array",
+                0,
+                keyboardLayouts.size
+            )
+            assertTrue(
+                "New UI: Keyboard layout API should provide English(US) layout",
+                hasLayout(keyboardLayouts, ENGLISH_US_LAYOUT_DESCRIPTOR)
+            )
+        }
+    }
+
+    @Test
+    fun testDefaultUi_getKeyboardLayoutsForInputDevice() {
+        NewSettingsApiFlag(false).use {
+            val keyboardLayouts =
+                keyboardLayoutManager.getKeyboardLayoutsForInputDevice(keyboardDevice.identifier)
+            assertNotEquals(
+                "Default UI: getKeyboardLayoutsForInputDevice API should not return empty array",
+                0,
+                keyboardLayouts.size
+            )
+            assertTrue(
+                "Default UI: getKeyboardLayoutsForInputDevice API should provide English(US) " +
+                        "layout",
+                hasLayout(keyboardLayouts, ENGLISH_US_LAYOUT_DESCRIPTOR)
+            )
+
+            val vendorSpecificKeyboardLayouts =
+                keyboardLayoutManager.getKeyboardLayoutsForInputDevice(
+                    vendorSpecificKeyboardDevice.identifier
+                )
+            assertEquals(
+                "Default UI: getKeyboardLayoutsForInputDevice API should return only vendor " +
+                        "specific layout",
+                1,
+                vendorSpecificKeyboardLayouts.size
+            )
+            assertEquals(
+                "Default UI: getKeyboardLayoutsForInputDevice API should return vendor specific " +
+                        "layout",
+                VENDOR_SPECIFIC_LAYOUT_DESCRIPTOR,
+                vendorSpecificKeyboardLayouts[0].descriptor
+            )
+        }
+    }
+
+    @Test
+    fun testNewUi_getKeyboardLayoutsForInputDevice() {
+        NewSettingsApiFlag(true).use {
+            val keyboardLayouts = keyboardLayoutManager.keyboardLayouts
+            assertNotEquals(
+                    "New UI: getKeyboardLayoutsForInputDevice API should not return empty array",
+                    0,
+                    keyboardLayouts.size
+            )
+            assertTrue(
+                    "New UI: getKeyboardLayoutsForInputDevice API should provide English(US) " +
+                            "layout",
+                    hasLayout(keyboardLayouts, ENGLISH_US_LAYOUT_DESCRIPTOR)
+            )
+        }
+    }
+
+    @Test
+    fun testDefaultUi_getSetCurrentKeyboardLayoutForInputDevice() {
+        NewSettingsApiFlag(false).use {
+            assertNull(
+                "Default UI: getCurrentKeyboardLayoutForInputDevice API should return null if " +
+                        "nothing was set",
+                keyboardLayoutManager.getCurrentKeyboardLayoutForInputDevice(
+                    keyboardDevice.identifier
+                )
+            )
+
+            keyboardLayoutManager.setCurrentKeyboardLayoutForInputDevice(
+                keyboardDevice.identifier,
+                ENGLISH_US_LAYOUT_DESCRIPTOR
+            )
+            val keyboardLayout =
+                keyboardLayoutManager.getCurrentKeyboardLayoutForInputDevice(
+                    keyboardDevice.identifier
+                )
+            assertEquals(
+                "Default UI: getCurrentKeyboardLayoutForInputDevice API should return the set " +
+                        "layout",
+                ENGLISH_US_LAYOUT_DESCRIPTOR,
+                keyboardLayout
+            )
+        }
+    }
+
+    @Test
+    fun testNewUi_getSetCurrentKeyboardLayoutForInputDevice() {
+        NewSettingsApiFlag(true).use {
+            keyboardLayoutManager.setCurrentKeyboardLayoutForInputDevice(
+                keyboardDevice.identifier,
+                ENGLISH_US_LAYOUT_DESCRIPTOR
+            )
+            assertNull(
+                "New UI: getCurrentKeyboardLayoutForInputDevice API should always return null " +
+                        "even after setCurrentKeyboardLayoutForInputDevice",
+                keyboardLayoutManager.getCurrentKeyboardLayoutForInputDevice(
+                    keyboardDevice.identifier
+                )
+            )
+        }
+    }
+
+    @Test
+    fun testDefaultUi_getEnabledKeyboardLayoutsForInputDevice() {
+        NewSettingsApiFlag(false).use {
+            keyboardLayoutManager.addKeyboardLayoutForInputDevice(
+                keyboardDevice.identifier, ENGLISH_US_LAYOUT_DESCRIPTOR
+            )
+
+            val keyboardLayouts =
+                keyboardLayoutManager.getEnabledKeyboardLayoutsForInputDevice(
+                    keyboardDevice.identifier
+                )
+            assertEquals(
+                "Default UI: getEnabledKeyboardLayoutsForInputDevice API should return added " +
+                        "layout",
+                1,
+                keyboardLayouts.size
+            )
+            assertEquals(
+                "Default UI: getEnabledKeyboardLayoutsForInputDevice API should return " +
+                        "English(US) layout",
+                ENGLISH_US_LAYOUT_DESCRIPTOR,
+                keyboardLayouts[0]
+            )
+            assertEquals(
+                "Default UI: getCurrentKeyboardLayoutForInputDevice API should return " +
+                        "English(US) layout (Auto select the first enabled layout)",
+                ENGLISH_US_LAYOUT_DESCRIPTOR,
+                keyboardLayoutManager.getCurrentKeyboardLayoutForInputDevice(
+                    keyboardDevice.identifier
+                )
+            )
+
+            keyboardLayoutManager.removeKeyboardLayoutForInputDevice(
+                keyboardDevice.identifier, ENGLISH_US_LAYOUT_DESCRIPTOR
+            )
+            assertEquals(
+                "Default UI: getKeyboardLayoutsForInputDevice API should return 0 layouts",
+                0,
+                keyboardLayoutManager.getEnabledKeyboardLayoutsForInputDevice(
+                    keyboardDevice.identifier
+                ).size
+            )
+            assertNull(
+                "Default UI: getCurrentKeyboardLayoutForInputDevice API should return null after " +
+                        "the enabled layout is removed",
+                keyboardLayoutManager.getCurrentKeyboardLayoutForInputDevice(
+                    keyboardDevice.identifier
+                )
+            )
+        }
+    }
+
+    @Test
+    fun testNewUi_getEnabledKeyboardLayoutsForInputDevice() {
+        NewSettingsApiFlag(true).use {
+            keyboardLayoutManager.addKeyboardLayoutForInputDevice(
+                keyboardDevice.identifier, ENGLISH_US_LAYOUT_DESCRIPTOR
+            )
+
+            assertEquals(
+                "New UI: getEnabledKeyboardLayoutsForInputDevice API should return always return " +
+                        "an empty array",
+                0,
+                keyboardLayoutManager.getEnabledKeyboardLayoutsForInputDevice(
+                    keyboardDevice.identifier
+                ).size
+            )
+            assertNull(
+                "New UI: getCurrentKeyboardLayoutForInputDevice API should always return null",
+                keyboardLayoutManager.getCurrentKeyboardLayoutForInputDevice(
+                    keyboardDevice.identifier
+                )
+            )
+        }
+    }
+
+    @Test
+    fun testDefaultUi_switchKeyboardLayout() {
+        NewSettingsApiFlag(false).use {
+            keyboardLayoutManager.addKeyboardLayoutForInputDevice(
+                keyboardDevice.identifier, ENGLISH_US_LAYOUT_DESCRIPTOR
+            )
+            keyboardLayoutManager.addKeyboardLayoutForInputDevice(
+                keyboardDevice.identifier, ENGLISH_UK_LAYOUT_DESCRIPTOR
+            )
+            assertEquals(
+                "Default UI: getCurrentKeyboardLayoutForInputDevice API should return " +
+                        "English(US) layout",
+                ENGLISH_US_LAYOUT_DESCRIPTOR,
+                keyboardLayoutManager.getCurrentKeyboardLayoutForInputDevice(
+                    keyboardDevice.identifier
+                )
+            )
+
+            keyboardLayoutManager.switchKeyboardLayout(DEVICE_ID, 1)
+
+            // Throws null pointer because trying to show toast using TestLooper
+            assertThrows(NullPointerException::class.java) { testLooper.dispatchAll() }
+            assertEquals("Default UI: getCurrentKeyboardLayoutForInputDevice API should return " +
+                    "English(UK) layout",
+                ENGLISH_UK_LAYOUT_DESCRIPTOR,
+                keyboardLayoutManager.getCurrentKeyboardLayoutForInputDevice(
+                    keyboardDevice.identifier
+                )
+            )
+        }
+    }
+
+    @Test
+    fun testNewUi_switchKeyboardLayout() {
+        NewSettingsApiFlag(true).use {
+            keyboardLayoutManager.addKeyboardLayoutForInputDevice(
+                keyboardDevice.identifier, ENGLISH_US_LAYOUT_DESCRIPTOR
+            )
+            keyboardLayoutManager.addKeyboardLayoutForInputDevice(
+                keyboardDevice.identifier, ENGLISH_UK_LAYOUT_DESCRIPTOR
+            )
+
+            keyboardLayoutManager.switchKeyboardLayout(DEVICE_ID, 1)
+            testLooper.dispatchAll()
+
+            assertNull("New UI: getCurrentKeyboardLayoutForInputDevice API should always return " +
+                    "null",
+                keyboardLayoutManager.getCurrentKeyboardLayoutForInputDevice(
+                    keyboardDevice.identifier
+                )
+            )
+        }
+    }
+
+    @Test
+    fun testDefaultUi_getKeyboardLayout() {
+        NewSettingsApiFlag(false).use {
+            val keyboardLayout =
+                keyboardLayoutManager.getKeyboardLayout(ENGLISH_US_LAYOUT_DESCRIPTOR)
+            assertEquals("Default UI: getKeyboardLayout API should return correct Layout from " +
+                    "available layouts",
+                ENGLISH_US_LAYOUT_DESCRIPTOR,
+                keyboardLayout!!.descriptor
+            )
+        }
+    }
+
+    @Test
+    fun testNewUi_getKeyboardLayout() {
+        NewSettingsApiFlag(true).use {
+            val keyboardLayout =
+                keyboardLayoutManager.getKeyboardLayout(ENGLISH_US_LAYOUT_DESCRIPTOR)
+            assertEquals("New UI: getKeyboardLayout API should return correct Layout from " +
+                    "available layouts",
+                ENGLISH_US_LAYOUT_DESCRIPTOR,
+                keyboardLayout!!.descriptor
+            )
+        }
+    }
+
+    @Test
+    fun testDefaultUi_getSetKeyboardLayoutForInputDevice_WithImeInfo() {
+        NewSettingsApiFlag(false).use {
+            val imeSubtype = createImeSubtype()
+            keyboardLayoutManager.setKeyboardLayoutForInputDevice(
+                keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype,
+                ENGLISH_UK_LAYOUT_DESCRIPTOR
+            )
+            val keyboardLayout =
+                keyboardLayoutManager.getKeyboardLayoutForInputDevice(
+                    keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype
+                )
+            assertNull(
+                "Default UI: getKeyboardLayoutForInputDevice API should always return null",
+                keyboardLayout
+            )
+        }
+    }
+
+    @Test
+    fun testNewUi_getSetKeyboardLayoutForInputDevice_withImeInfo() {
+        NewSettingsApiFlag(true).use {
+            val imeSubtype = createImeSubtype()
+
+            keyboardLayoutManager.setKeyboardLayoutForInputDevice(
+                keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype,
+                ENGLISH_UK_LAYOUT_DESCRIPTOR
+            )
+            assertEquals(
+                "New UI: getKeyboardLayoutForInputDevice API should return the set layout",
+                ENGLISH_UK_LAYOUT_DESCRIPTOR,
+                keyboardLayoutManager.getKeyboardLayoutForInputDevice(
+                    keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype
+                )
+            )
+
+            // This should replace previously set layout
+            keyboardLayoutManager.setKeyboardLayoutForInputDevice(
+                keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype,
+                ENGLISH_US_LAYOUT_DESCRIPTOR
+            )
+            assertEquals(
+                "New UI: getKeyboardLayoutForInputDevice API should return the last set layout",
+                ENGLISH_US_LAYOUT_DESCRIPTOR,
+                keyboardLayoutManager.getKeyboardLayoutForInputDevice(
+                    keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype
+                )
+            )
+        }
+    }
+
+    @Test
+    fun testDefaultUi_getKeyboardLayoutListForInputDevice() {
+        NewSettingsApiFlag(false).use {
+            val keyboardLayouts =
+                keyboardLayoutManager.getKeyboardLayoutListForInputDevice(
+                    keyboardDevice.identifier, USER_ID, imeInfo,
+                    createImeSubtype()
+                )
+            assertEquals("Default UI: getKeyboardLayoutListForInputDevice API should always " +
+                    "return empty array",
+                0,
+                keyboardLayouts.size
+            )
+        }
+    }
+
+    @Test
+    fun testNewUi_getKeyboardLayoutListForInputDevice() {
+        NewSettingsApiFlag(true).use {
+            // Check Layouts for "hi-Latn". It should return all 'Latn' keyboard layouts
+            var keyboardLayouts =
+                keyboardLayoutManager.getKeyboardLayoutListForInputDevice(
+                    keyboardDevice.identifier, USER_ID, imeInfo,
+                    createImeSubtypeForLanguageTag("hi-Latn")
+                )
+            assertNotEquals(
+                "New UI: getKeyboardLayoutListForInputDevice API should return the list of " +
+                        "supported layouts with matching script code",
+                0,
+                keyboardLayouts.size
+            )
+            assertTrue("New UI: getKeyboardLayoutListForInputDevice API should return a list " +
+                    "containing English(US) layout for hi-Latn",
+                containsLayout(keyboardLayouts, ENGLISH_US_LAYOUT_DESCRIPTOR)
+            )
+            assertTrue("New UI: getKeyboardLayoutListForInputDevice API should return a list " +
+                    "containing English(No script code) layout for hi-Latn",
+                containsLayout(
+                    keyboardLayouts,
+                    createLayoutDescriptor("keyboard_layout_english_without_script_code")
+                )
+            )
+
+            // Check Layouts for "hi" which by default uses 'Deva' script.
+            keyboardLayouts =
+                keyboardLayoutManager.getKeyboardLayoutListForInputDevice(
+                    keyboardDevice.identifier, USER_ID, imeInfo,
+                    createImeSubtypeForLanguageTag("hi")
+                )
+            assertEquals("New UI: getKeyboardLayoutListForInputDevice API should return empty " +
+                    "list if no supported layouts available",
+                0,
+                keyboardLayouts.size
+            )
+
+            // If user manually selected some layout, always provide it in the layout list
+            val imeSubtype = createImeSubtypeForLanguageTag("hi")
+            keyboardLayoutManager.setKeyboardLayoutForInputDevice(
+                keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype,
+                ENGLISH_US_LAYOUT_DESCRIPTOR
+            )
+            keyboardLayouts =
+                keyboardLayoutManager.getKeyboardLayoutListForInputDevice(
+                    keyboardDevice.identifier, USER_ID, imeInfo,
+                    imeSubtype
+                )
+            assertEquals("New UI: getKeyboardLayoutListForInputDevice API should return user " +
+                    "selected layout even if the script is incompatible with IME",
+                    1,
+                keyboardLayouts.size
+            )
+
+            // Special case Japanese: UScript ignores provided script code for certain language tags
+            // Should manually match provided script codes and then rely on Uscript to derive
+            // script from language tags and match those.
+            keyboardLayouts =
+                keyboardLayoutManager.getKeyboardLayoutListForInputDevice(
+                        keyboardDevice.identifier, USER_ID, imeInfo,
+                        createImeSubtypeForLanguageTag("ja-Latn-JP")
+                )
+            assertNotEquals(
+                "New UI: getKeyboardLayoutListForInputDevice API should return the list of " +
+                        "supported layouts with matching script code for ja-Latn-JP",
+                0,
+                keyboardLayouts.size
+            )
+            assertTrue("New UI: getKeyboardLayoutListForInputDevice API should return a list " +
+                    "containing English(US) layout for ja-Latn-JP",
+                containsLayout(keyboardLayouts, ENGLISH_US_LAYOUT_DESCRIPTOR)
+            )
+            assertTrue("New UI: getKeyboardLayoutListForInputDevice API should return a list " +
+                    "containing English(No script code) layout for ja-Latn-JP",
+                containsLayout(
+                    keyboardLayouts,
+                    createLayoutDescriptor("keyboard_layout_english_without_script_code")
+                )
+            )
+
+            // If script code not explicitly provided for Japanese should rely on Uscript to find
+            // derived script code and hence no suitable layout will be found.
+            keyboardLayouts =
+                keyboardLayoutManager.getKeyboardLayoutListForInputDevice(
+                        keyboardDevice.identifier, USER_ID, imeInfo,
+                        createImeSubtypeForLanguageTag("ja-JP")
+                )
+            assertEquals(
+                "New UI: getKeyboardLayoutListForInputDevice API should return empty list of " +
+                        "supported layouts with matching script code for ja-JP",
+                0,
+                keyboardLayouts.size
+            )
+
+            // If IME doesn't have a corresponding language tag, then should show all available
+            // layouts no matter the script code.
+            keyboardLayouts =
+                keyboardLayoutManager.getKeyboardLayoutListForInputDevice(
+                    keyboardDevice.identifier, USER_ID, imeInfo, null
+                )
+            assertNotEquals(
+                "New UI: getKeyboardLayoutListForInputDevice API should return all layouts if" +
+                    "language tag or subtype not provided",
+                0,
+                keyboardLayouts.size
+            )
+            assertTrue("New UI: getKeyboardLayoutListForInputDevice API should contain Latin " +
+                "layouts if language tag or subtype not provided",
+                containsLayout(keyboardLayouts, ENGLISH_US_LAYOUT_DESCRIPTOR)
+            )
+            assertTrue("New UI: getKeyboardLayoutListForInputDevice API should contain Cyrillic " +
+                "layouts if language tag or subtype not provided",
+                containsLayout(
+                    keyboardLayouts,
+                    createLayoutDescriptor("keyboard_layout_russian")
+                )
+            )
+        }
+    }
+
+    @Test
+    fun testNewUi_getDefaultKeyboardLayoutForInputDevice_withImeLanguageTag() {
+        NewSettingsApiFlag(true).use {
+            assertCorrectLayout(
+                keyboardDevice,
+                createImeSubtypeForLanguageTag("en-US"),
+                ENGLISH_US_LAYOUT_DESCRIPTOR
+            )
+            assertCorrectLayout(
+                keyboardDevice,
+                createImeSubtypeForLanguageTag("en-GB"),
+                ENGLISH_UK_LAYOUT_DESCRIPTOR
+            )
+            assertCorrectLayout(
+                keyboardDevice,
+                createImeSubtypeForLanguageTag("de"),
+                createLayoutDescriptor("keyboard_layout_german")
+            )
+            assertCorrectLayout(
+                keyboardDevice,
+                createImeSubtypeForLanguageTag("fr-FR"),
+                createLayoutDescriptor("keyboard_layout_french")
+            )
+            assertCorrectLayout(
+                keyboardDevice,
+                createImeSubtypeForLanguageTag("ru"),
+                createLayoutDescriptor("keyboard_layout_russian")
+            )
+            assertNull(
+                "New UI: getDefaultKeyboardLayoutForInputDevice should return null when no " +
+                        "layout available",
+                keyboardLayoutManager.getKeyboardLayoutForInputDevice(
+                    keyboardDevice.identifier, USER_ID, imeInfo,
+                    createImeSubtypeForLanguageTag("it")
+                )
+            )
+            assertNull(
+                "New UI: getDefaultKeyboardLayoutForInputDevice should return null when no " +
+                        "layout for script code is available",
+                keyboardLayoutManager.getKeyboardLayoutForInputDevice(
+                    keyboardDevice.identifier, USER_ID, imeInfo,
+                    createImeSubtypeForLanguageTag("en-Deva")
+                )
+            )
+        }
+    }
+
+    @Test
+    fun testNewUi_getDefaultKeyboardLayoutForInputDevice_withImeLanguageTagAndLayoutType() {
+        NewSettingsApiFlag(true).use {
+            assertCorrectLayout(
+                keyboardDevice,
+                createImeSubtypeForLanguageTagAndLayoutType("en-US", "qwerty"),
+                ENGLISH_US_LAYOUT_DESCRIPTOR
+            )
+            assertCorrectLayout(
+                keyboardDevice,
+                createImeSubtypeForLanguageTagAndLayoutType("en-US", "dvorak"),
+                createLayoutDescriptor("keyboard_layout_english_us_dvorak")
+            )
+            // Try to match layout type even if country doesn't match
+            assertCorrectLayout(
+                keyboardDevice,
+                createImeSubtypeForLanguageTagAndLayoutType("en-GB", "dvorak"),
+                createLayoutDescriptor("keyboard_layout_english_us_dvorak")
+            )
+            // Choose layout based on layout type priority, if layout type is not provided by IME
+            // (Qwerty > Dvorak > Extended)
+            assertCorrectLayout(
+                keyboardDevice,
+                createImeSubtypeForLanguageTagAndLayoutType("en-US", ""),
+                ENGLISH_US_LAYOUT_DESCRIPTOR
+            )
+            assertCorrectLayout(
+                keyboardDevice,
+                createImeSubtypeForLanguageTagAndLayoutType("en-GB", "qwerty"),
+                ENGLISH_UK_LAYOUT_DESCRIPTOR
+            )
+            assertCorrectLayout(
+                keyboardDevice,
+                createImeSubtypeForLanguageTagAndLayoutType("de", "qwertz"),
+                createLayoutDescriptor("keyboard_layout_german")
+            )
+            // Wrong layout type should match with language if provided layout type not available
+            assertCorrectLayout(
+                keyboardDevice,
+                createImeSubtypeForLanguageTagAndLayoutType("de", "qwerty"),
+                createLayoutDescriptor("keyboard_layout_german")
+            )
+            assertCorrectLayout(
+                keyboardDevice,
+                createImeSubtypeForLanguageTagAndLayoutType("fr-FR", "azerty"),
+                createLayoutDescriptor("keyboard_layout_french")
+            )
+            assertCorrectLayout(
+                keyboardDevice,
+                createImeSubtypeForLanguageTagAndLayoutType("ru", "qwerty"),
+                createLayoutDescriptor("keyboard_layout_russian_qwerty")
+            )
+            // If layout type is empty then prioritize KCM with empty layout type
+            assertCorrectLayout(
+                keyboardDevice,
+                createImeSubtypeForLanguageTagAndLayoutType("ru", ""),
+                createLayoutDescriptor("keyboard_layout_russian")
+            )
+            assertNull("New UI: getDefaultKeyboardLayoutForInputDevice should return null when " +
+                    "no layout for script code is available",
+                keyboardLayoutManager.getKeyboardLayoutForInputDevice(
+                    keyboardDevice.identifier, USER_ID, imeInfo,
+                    createImeSubtypeForLanguageTagAndLayoutType("en-Deva-US", "")
+                )
+            )
+        }
+    }
+
+    @Test
+    fun testNewUi_getDefaultKeyboardLayoutForInputDevice_withHwLanguageTagAndLayoutType() {
+        NewSettingsApiFlag(true).use {
+            val frenchSubtype = createImeSubtypeForLanguageTagAndLayoutType("fr", "azerty")
+            // Should return English dvorak even if IME current layout is French, since HW says the
+            // keyboard is a Dvorak keyboard
+            assertCorrectLayout(
+                englishDvorakKeyboardDevice,
+                frenchSubtype,
+                createLayoutDescriptor("keyboard_layout_english_us_dvorak")
+            )
+
+            // Back to back changing HW keyboards with same product and vendor ID but different
+            // language and layout type should configure the layouts correctly.
+            assertCorrectLayout(
+                englishQwertyKeyboardDevice,
+                frenchSubtype,
+                createLayoutDescriptor("keyboard_layout_english_us")
+            )
+
+            // Fallback to IME information if the HW provided layout script is incompatible with the
+            // provided IME subtype
+            assertCorrectLayout(
+                englishDvorakKeyboardDevice,
+                createImeSubtypeForLanguageTagAndLayoutType("ru", ""),
+                createLayoutDescriptor("keyboard_layout_russian")
+            )
+        }
+    }
+
+    private fun assertCorrectLayout(
+        device: InputDevice,
+        imeSubtype: InputMethodSubtype,
+        expectedLayout: String
+    ) {
+        assertEquals(
+            "New UI: getDefaultKeyboardLayoutForInputDevice should return $expectedLayout",
+            expectedLayout,
+            keyboardLayoutManager.getKeyboardLayoutForInputDevice(
+                device.identifier, USER_ID, imeInfo, imeSubtype
+            )
+        )
+    }
+
+    private fun createImeSubtype(): InputMethodSubtype =
+        InputMethodSubtype.InputMethodSubtypeBuilder().setSubtypeId(nextImeSubtypeId++).build()
+
+    private fun createImeSubtypeForLanguageTag(languageTag: String): InputMethodSubtype =
+        InputMethodSubtype.InputMethodSubtypeBuilder().setSubtypeId(nextImeSubtypeId++)
+            .setLanguageTag(languageTag).build()
+
+    private fun createImeSubtypeForLanguageTagAndLayoutType(
+        languageTag: String,
+        layoutType: String
+    ): InputMethodSubtype =
+        InputMethodSubtype.InputMethodSubtypeBuilder().setSubtypeId(nextImeSubtypeId++)
+            .setPhysicalKeyboardHint(ULocale.forLanguageTag(languageTag), layoutType).build()
+
+    private fun hasLayout(layoutList: Array<KeyboardLayout>, layoutDesc: String): Boolean {
+        for (kl in layoutList) {
+            if (kl.descriptor == layoutDesc) {
+                return true
+            }
+        }
+        return false
+    }
+
+    private fun createLayoutDescriptor(keyboardName: String): String =
+        "$PACKAGE_NAME/$RECEIVER_NAME/$keyboardName"
+
+    private fun containsLayout(layoutList: Array<KeyboardLayout>, layoutDesc: String): Boolean {
+        for (kl in layoutList) {
+            if (kl.descriptor.equals(layoutDesc)) {
+                return true
+            }
+        }
+        return false
+    }
+
+    private fun createMockReceiver(): ResolveInfo {
+        val info = ResolveInfo()
+        info.activityInfo = ActivityInfo()
+        info.activityInfo.packageName = PACKAGE_NAME
+        info.activityInfo.name = RECEIVER_NAME
+        info.activityInfo.applicationInfo = ApplicationInfo()
+        info.activityInfo.metaData = Bundle()
+        info.activityInfo.metaData.putInt(
+            InputManager.META_DATA_KEYBOARD_LAYOUTS,
+            R.xml.keyboard_layouts
+        )
+        info.serviceInfo = ServiceInfo()
+        info.serviceInfo.packageName = PACKAGE_NAME
+        info.serviceInfo.name = RECEIVER_NAME
+        return info
+    }
+
+    private inner class NewSettingsApiFlag constructor(enabled: Boolean) : AutoCloseable {
+        init {
+            Settings.Global.putString(
+                context.contentResolver,
+                "settings_new_keyboard_ui", enabled.toString()
+            )
+        }
+
+        override fun close() {
+            Settings.Global.putString(
+                context.contentResolver,
+                "settings_new_keyboard_ui",
+                ""
+            )
+        }
+    }
+}
diff --git a/tests/Input/src/com/android/server/input/KeyboardMetricsCollectorTests.kt b/tests/Input/src/com/android/server/input/KeyboardMetricsCollectorTests.kt
new file mode 100644
index 0000000..56b0b9a
--- /dev/null
+++ b/tests/Input/src/com/android/server/input/KeyboardMetricsCollectorTests.kt
@@ -0,0 +1,203 @@
+/*
+ * 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.input
+
+import android.hardware.input.KeyboardLayout
+import android.icu.util.ULocale
+import android.platform.test.annotations.Presubmit
+import android.view.InputDevice
+import android.view.inputmethod.InputMethodSubtype
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertThrows
+import org.junit.Assert.assertTrue
+import org.junit.Test
+
+private fun createKeyboard(
+    deviceId: Int,
+    vendorId: Int,
+    productId: Int,
+    languageTag: String?,
+    layoutType: String?
+): InputDevice =
+    InputDevice.Builder()
+        .setId(deviceId)
+        .setName("Device $deviceId")
+        .setDescriptor("descriptor $deviceId")
+        .setSources(InputDevice.SOURCE_KEYBOARD)
+        .setKeyboardType(InputDevice.KEYBOARD_TYPE_ALPHABETIC)
+        .setExternal(true)
+        .setVendorId(vendorId)
+        .setProductId(productId)
+        .setKeyboardLanguageTag(languageTag)
+        .setKeyboardLayoutType(layoutType)
+        .build()
+
+private fun createImeSubtype(
+    imeSubtypeId: Int,
+    languageTag: ULocale?,
+    layoutType: String
+): InputMethodSubtype =
+    InputMethodSubtype.InputMethodSubtypeBuilder().setSubtypeId(imeSubtypeId)
+        .setPhysicalKeyboardHint(languageTag, layoutType).build()
+
+/**
+ * Tests for {@link KeyboardMetricsCollector}.
+ *
+ * Build/Install/Run:
+ * atest InputTests:KeyboardMetricsCollectorTests
+ */
+@Presubmit
+class KeyboardMetricsCollectorTests {
+
+    companion object {
+        const val DEVICE_ID = 1
+        const val DEFAULT_VENDOR_ID = 123
+        const val DEFAULT_PRODUCT_ID = 456
+    }
+
+    @Test
+    fun testCreateKeyboardConfigurationEvent_throwsExceptionWithoutAnyLayoutConfiguration() {
+        assertThrows(IllegalStateException::class.java) {
+            KeyboardMetricsCollector.KeyboardConfigurationEvent.Builder(
+                createKeyboard(
+                    DEVICE_ID,
+                    DEFAULT_VENDOR_ID,
+                    DEFAULT_PRODUCT_ID,
+                    null,
+                    null
+                )
+            ).build()
+        }
+    }
+
+    @Test
+    fun testCreateKeyboardConfigurationEvent_throwsExceptionWithInvalidLayoutSelectionCriteria() {
+        assertThrows(IllegalStateException::class.java) {
+            KeyboardMetricsCollector.KeyboardConfigurationEvent.Builder(
+                createKeyboard(
+                    DEVICE_ID,
+                    DEFAULT_VENDOR_ID,
+                    DEFAULT_PRODUCT_ID,
+                    null,
+                    null
+                )
+            ).addLayoutSelection(createImeSubtype(1, ULocale.forLanguageTag("en-US"), "qwerty"),
+             null, 123).build()
+        }
+    }
+
+    @Test
+    fun testCreateKeyboardConfigurationEvent_withMultipleConfigurations() {
+        val builder = KeyboardMetricsCollector.KeyboardConfigurationEvent.Builder(
+            createKeyboard(
+                DEVICE_ID,
+                DEFAULT_VENDOR_ID,
+                DEFAULT_PRODUCT_ID,
+                "de-CH",
+                "qwertz"
+            )
+        )
+        val event = builder.addLayoutSelection(
+            createImeSubtype(1, ULocale.forLanguageTag("en-US"), "qwerty"),
+            KeyboardLayout(null, "English(US)(Qwerty)", null, 0, null, 0, 0, 0),
+            KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD
+        ).addLayoutSelection(
+            createImeSubtype(2, ULocale.forLanguageTag("en-US"), "azerty"),
+            null, // Default layout type
+            KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_USER
+        ).addLayoutSelection(
+            createImeSubtype(3, ULocale.forLanguageTag("en-US"), "qwerty"),
+            KeyboardLayout(null, "German", null, 0, null, 0, 0, 0),
+            KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEVICE
+        ).addLayoutSelection(
+            createImeSubtype(4, null, "qwerty"), // Default language tag
+            KeyboardLayout(null, "German", null, 0, null, 0, 0, 0),
+            KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEVICE
+        ).setIsFirstTimeConfiguration(true).build()
+
+        assertEquals(
+            "KeyboardConfigurationEvent should pick vendor ID from provided InputDevice",
+            DEFAULT_VENDOR_ID,
+            event.vendorId
+        )
+        assertEquals(
+            "KeyboardConfigurationEvent should pick product ID from provided InputDevice",
+            DEFAULT_PRODUCT_ID,
+            event.productId
+        )
+        assertTrue(event.isFirstConfiguration)
+
+        assertEquals(
+            "KeyboardConfigurationEvent should contain 4 configurations provided",
+            4,
+            event.layoutConfigurations.size
+        )
+        assertExpectedLayoutConfiguration(
+            event.layoutConfigurations[0],
+            "de-CH",
+            KeyboardLayout.LayoutType.getLayoutTypeEnumValue("qwertz"),
+            "English(US)(Qwerty)",
+            KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD,
+            "en-US",
+            KeyboardLayout.LayoutType.getLayoutTypeEnumValue("qwerty"),
+        )
+        assertExpectedLayoutConfiguration(
+            event.layoutConfigurations[1],
+            "de-CH",
+            KeyboardLayout.LayoutType.getLayoutTypeEnumValue("qwertz"),
+            KeyboardMetricsCollector.DEFAULT_LAYOUT,
+            KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_USER,
+            "en-US",
+            KeyboardLayout.LayoutType.getLayoutTypeEnumValue("azerty"),
+        )
+        assertExpectedLayoutConfiguration(
+            event.layoutConfigurations[2],
+            "de-CH",
+            KeyboardLayout.LayoutType.getLayoutTypeEnumValue("qwertz"),
+            "German",
+            KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEVICE,
+            "en-US",
+            KeyboardLayout.LayoutType.getLayoutTypeEnumValue("qwerty"),
+        )
+        assertExpectedLayoutConfiguration(
+            event.layoutConfigurations[3],
+            "de-CH",
+            KeyboardLayout.LayoutType.getLayoutTypeEnumValue("qwertz"),
+            "German",
+            KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEVICE,
+            KeyboardMetricsCollector.DEFAULT_LANGUAGE_TAG,
+            KeyboardLayout.LayoutType.getLayoutTypeEnumValue("qwerty"),
+        )
+    }
+
+    private fun assertExpectedLayoutConfiguration(
+        configuration: KeyboardMetricsCollector.LayoutConfiguration,
+        expectedKeyboardLanguageTag: String,
+        expectedKeyboardLayoutType: Int,
+        expectedSelectedLayout: String,
+        expectedLayoutSelectionCriteria: Int,
+        expectedImeLanguageTag: String,
+        expectedImeLayoutType: Int
+    ) {
+        assertEquals(expectedKeyboardLanguageTag, configuration.keyboardLanguageTag)
+        assertEquals(expectedKeyboardLayoutType, configuration.keyboardLayoutType)
+        assertEquals(expectedSelectedLayout, configuration.keyboardLayoutName)
+        assertEquals(expectedLayoutSelectionCriteria, configuration.layoutSelectionCriteria)
+        assertEquals(expectedImeLanguageTag, configuration.imeLanguageTag)
+        assertEquals(expectedImeLayoutType, configuration.imeLayoutType)
+    }
+}
diff --git a/tests/Input/src/com/android/test/input/AnrTest.kt b/tests/Input/src/com/android/test/input/AnrTest.kt
index d185ee6..44da69c 100644
--- a/tests/Input/src/com/android/test/input/AnrTest.kt
+++ b/tests/Input/src/com/android/test/input/AnrTest.kt
@@ -152,8 +152,7 @@
     private fun triggerAnr() {
         startUnresponsiveActivity()
         val uiDevice: UiDevice = UiDevice.getInstance(instrumentation)
-        val obj: UiObject2? = uiDevice.wait(Until.findObject(
-                By.text("Unresponsive gesture monitor")), 10000)
+        val obj: UiObject2? = uiDevice.wait(Until.findObject(By.pkg(PACKAGE_NAME)), 10000)
 
         if (obj == null) {
             fail("Could not find unresponsive activity")
diff --git a/tests/InputMethodStressTest/TEST_MAPPING b/tests/InputMethodStressTest/TEST_MAPPING
index 06e2ce8..d982dd8 100644
--- a/tests/InputMethodStressTest/TEST_MAPPING
+++ b/tests/InputMethodStressTest/TEST_MAPPING
@@ -1,5 +1,5 @@
 {
-  "presubmit-large": [
+  "postsubmit": [
     {
       "name": "InputMethodStressTest"
     }
diff --git a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/NotificationTest.java b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/NotificationTest.java
index f4a04a1..b6b9924 100644
--- a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/NotificationTest.java
+++ b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/NotificationTest.java
@@ -97,6 +97,8 @@
         assumeFalse(pm.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE));
         // Do not run on TV. Direct Reply isn't supported on TV.
         assumeFalse(pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK_ONLY));
+        // Do not run on Wear. Direct Reply isn't supported on Wear.
+        assumeFalse(pm.hasSystemFeature(PackageManager.FEATURE_WATCH));
     }
 
     @After
diff --git a/tests/Internal/src/com/android/internal/os/TimeoutRecordTest.java b/tests/Internal/src/com/android/internal/os/TimeoutRecordTest.java
index 7419ee1..00085f8 100644
--- a/tests/Internal/src/com/android/internal/os/TimeoutRecordTest.java
+++ b/tests/Internal/src/com/android/internal/os/TimeoutRecordTest.java
@@ -104,11 +104,11 @@
 
     @Test
     public void forServiceExec_returnsCorrectTimeoutRecord() {
-        TimeoutRecord record = TimeoutRecord.forServiceExec("Test ANR reason");
+        TimeoutRecord record = TimeoutRecord.forServiceExec("com.app.MyService", 1000L);
 
         assertNotNull(record);
         assertEquals(record.mKind, TimeoutRecord.TimeoutKind.SERVICE_EXEC);
-        assertEquals(record.mReason, "Test ANR reason");
+        assertEquals(record.mReason, "executing service com.app.MyService, waited 1000ms");
         assertTrue(record.mEndTakenBeforeLocks);
     }
 
diff --git a/tests/StagedInstallTest/OWNERS b/tests/StagedInstallTest/OWNERS
index aac68e9..d7301dc 100644
--- a/tests/StagedInstallTest/OWNERS
+++ b/tests/StagedInstallTest/OWNERS
@@ -1,3 +1,5 @@
+# Bug component: 36137
+
 include /services/core/java/com/android/server/pm/OWNERS
 
 dariofreni@google.com
diff --git a/tests/SurfaceViewBufferTests/AndroidManifest.xml b/tests/SurfaceViewBufferTests/AndroidManifest.xml
index c910ecd..78415e8 100644
--- a/tests/SurfaceViewBufferTests/AndroidManifest.xml
+++ b/tests/SurfaceViewBufferTests/AndroidManifest.xml
@@ -43,7 +43,7 @@
                 <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
-        <service android:name="android.view.cts.surfacevalidator.LocalMediaProjectionService"
+        <service android:name="com.android.test.LocalMediaProjectionService"
                  android:foregroundServiceType="mediaProjection"
                  android:enabled="true">
         </service>
diff --git a/tests/SurfaceViewBufferTests/src/com/android/test/InverseDisplayTransformTests.kt b/tests/SurfaceViewBufferTests/src/com/android/test/InverseDisplayTransformTests.kt
index e722ba5..1de965e 100644
--- a/tests/SurfaceViewBufferTests/src/com/android/test/InverseDisplayTransformTests.kt
+++ b/tests/SurfaceViewBufferTests/src/com/android/test/InverseDisplayTransformTests.kt
@@ -76,4 +76,4 @@
         }
         LayersTraceSubject(trace).layer("SurfaceView", 3).hasBufferSize(rotatedBufferSize)
     }
-}
+}
\ No newline at end of file
diff --git a/tests/SurfaceViewBufferTests/src/com/android/test/LocalMediaProjectionService.java b/tests/SurfaceViewBufferTests/src/com/android/test/LocalMediaProjectionService.java
new file mode 100644
index 0000000..7339a6b8
--- /dev/null
+++ b/tests/SurfaceViewBufferTests/src/com/android/test/LocalMediaProjectionService.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.test;
+
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.drawable.Icon;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+
+public class LocalMediaProjectionService extends Service {
+
+    private Bitmap mTestBitmap;
+
+    private static final String NOTIFICATION_CHANNEL_ID = "Surfacevalidator";
+    private static final String CHANNEL_NAME = "ProjectionService";
+
+    static final int MSG_START_FOREGROUND_DONE = 1;
+    static final String EXTRA_MESSENGER = "messenger";
+
+    @Override
+    public int onStartCommand(Intent intent, int flags, int startId) {
+        startForeground(intent);
+        return super.onStartCommand(intent, flags, startId);
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return null;
+    }
+
+    @Override
+    public void onDestroy() {
+        if (mTestBitmap != null) {
+            mTestBitmap.recycle();
+            mTestBitmap = null;
+        }
+        super.onDestroy();
+    }
+
+    private Icon createNotificationIcon() {
+        mTestBitmap = Bitmap.createBitmap(50, 50, Bitmap.Config.ARGB_8888);
+        final Canvas canvas = new Canvas(mTestBitmap);
+        canvas.drawColor(Color.BLUE);
+        return Icon.createWithBitmap(mTestBitmap);
+    }
+
+    private void startForeground(Intent intent) {
+        final NotificationChannel channel = new NotificationChannel(NOTIFICATION_CHANNEL_ID,
+                CHANNEL_NAME, NotificationManager.IMPORTANCE_NONE);
+        channel.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
+
+        final NotificationManager notificationManager =
+                (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+        notificationManager.createNotificationChannel(channel);
+
+        final Notification.Builder notificationBuilder =
+                new Notification.Builder(this, NOTIFICATION_CHANNEL_ID);
+
+        final Notification notification = notificationBuilder.setOngoing(true)
+                .setContentTitle("App is running")
+                .setSmallIcon(createNotificationIcon())
+                .setCategory(Notification.CATEGORY_SERVICE)
+                .setContentText("Context")
+                .build();
+
+        startForeground(2, notification);
+
+        final Messenger messenger = intent.getParcelableExtra(EXTRA_MESSENGER);
+        final Message msg = Message.obtain();
+        msg.what = MSG_START_FOREGROUND_DONE;
+        try {
+            messenger.send(msg);
+        } catch (RemoteException e) {
+        }
+    }
+
+}
diff --git a/tests/SurfaceViewBufferTests/src/com/android/test/ScreenRecordTestBase.kt b/tests/SurfaceViewBufferTests/src/com/android/test/ScreenRecordTestBase.kt
index df3d30e..e80dd8e 100644
--- a/tests/SurfaceViewBufferTests/src/com/android/test/ScreenRecordTestBase.kt
+++ b/tests/SurfaceViewBufferTests/src/com/android/test/ScreenRecordTestBase.kt
@@ -19,7 +19,6 @@
 import android.content.Context
 import android.content.Intent
 import android.graphics.Rect
-import android.server.wm.WindowManagerState.getLogicalDisplaySize
 import android.view.cts.surfacevalidator.CapturedActivity
 import android.view.cts.surfacevalidator.ISurfaceValidatorTestCase
 import android.view.cts.surfacevalidator.PixelChecker
@@ -44,8 +43,6 @@
         mActivity = mActivityRule.launchActivity(Intent())
         lateinit var surfaceReadyLatch: CountDownLatch
         runOnUiThread {
-            it.dismissPermissionDialog()
-            it.setLogicalDisplaySize(getLogicalDisplaySize())
             surfaceReadyLatch = it.addSurfaceView(defaultBufferSize)
         }
         surfaceReadyLatch.await()
diff --git a/tests/SurfaceViewBufferTests/src/com/android/test/SharedBufferModeTests.kt b/tests/SurfaceViewBufferTests/src/com/android/test/SharedBufferModeTests.kt
index be3ed71..4c5224a 100644
--- a/tests/SurfaceViewBufferTests/src/com/android/test/SharedBufferModeTests.kt
+++ b/tests/SurfaceViewBufferTests/src/com/android/test/SharedBufferModeTests.kt
@@ -87,4 +87,4 @@
             checkPixels(svBounds, Color.BLUE)
         }
     }
-}
+}
\ No newline at end of file
diff --git a/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceTracingTestBase.kt b/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceTracingTestBase.kt
index cf4cb8c..a38019d 100644
--- a/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceTracingTestBase.kt
+++ b/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceTracingTestBase.kt
@@ -116,4 +116,4 @@
         private const val TRACE_FLAGS =
                 (1 shl 0) or (1 shl 5) or (1 shl 6) // TRACE_CRITICAL | TRACE_BUFFERS | TRACE_SYNC
     }
-}
+}
\ No newline at end of file
diff --git a/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceViewBufferTestBase.kt b/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceViewBufferTestBase.kt
index bba9678..1770e32 100644
--- a/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceViewBufferTestBase.kt
+++ b/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceViewBufferTestBase.kt
@@ -100,4 +100,4 @@
             INVERSE_DISPLAY(0x08)
         }
     }
-}
+}
\ No newline at end of file
diff --git a/tests/UiBench/Android.bp b/tests/UiBench/Android.bp
index 90e61c5..0d2f2ef 100644
--- a/tests/UiBench/Android.bp
+++ b/tests/UiBench/Android.bp
@@ -24,6 +24,5 @@
         "androidx.recyclerview_recyclerview",
         "androidx.leanback_leanback",
     ],
-    certificate: "platform",
     test_suites: ["device-tests"],
 }
diff --git a/tests/UiBench/AndroidManifest.xml b/tests/UiBench/AndroidManifest.xml
index 47211c5..4fc6ec7 100644
--- a/tests/UiBench/AndroidManifest.xml
+++ b/tests/UiBench/AndroidManifest.xml
@@ -18,7 +18,6 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
      xmlns:tools="http://schemas.android.com/tools"
      package="com.android.test.uibench">
-    <uses-permission android:name="android.permission.INJECT_EVENTS" />
 
     <application android:allowBackup="false"
          android:theme="@style/Theme.AppCompat.Light.DarkActionBar"
diff --git a/tests/UiBench/src/com/android/test/uibench/EditTextTypeActivity.java b/tests/UiBench/src/com/android/test/uibench/EditTextTypeActivity.java
index 1b2c3c6..06b65a7 100644
--- a/tests/UiBench/src/com/android/test/uibench/EditTextTypeActivity.java
+++ b/tests/UiBench/src/com/android/test/uibench/EditTextTypeActivity.java
@@ -15,15 +15,11 @@
  */
 package com.android.test.uibench;
 
-import android.app.Instrumentation;
+import android.content.Intent;
 import android.os.Bundle;
-import android.os.Looper;
-import android.os.MessageQueue;
-import androidx.appcompat.app.AppCompatActivity;
-import android.view.KeyEvent;
 import android.widget.EditText;
 
-import java.util.concurrent.Semaphore;
+import androidx.appcompat.app.AppCompatActivity;
 
 /**
  * Note: currently incomplete, complexity of input continuously grows, instead of looping
@@ -32,7 +28,13 @@
  * Simulates typing continuously into an EditText.
  */
 public class EditTextTypeActivity extends AppCompatActivity {
-    Thread mThread;
+
+    /**
+     * Broadcast action: Used to notify UiBenchEditTextTypingMicrobenchmark test when the
+     * test activity was paused.
+     */
+    private static final String ACTION_CANCEL_TYPING_CALLBACK =
+            "com.android.uibench.action.CANCEL_TYPING_CALLBACK";
 
     private static String sSeedText = "";
     static {
@@ -46,9 +48,6 @@
         sSeedText = builder.toString();
     }
 
-    final Object mLock = new Object();
-    boolean mShouldStop = false;
-
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -56,55 +55,13 @@
         EditText editText = new EditText(this);
         editText.setText(sSeedText);
         setContentView(editText);
-
-        final Instrumentation instrumentation = new Instrumentation();
-        final Semaphore sem = new Semaphore(0);
-        MessageQueue.IdleHandler handler = new MessageQueue.IdleHandler() {
-            @Override
-            public boolean queueIdle() {
-                // TODO: consider other signaling approaches
-                sem.release();
-                return true;
-            }
-        };
-        Looper.myQueue().addIdleHandler(handler);
-        synchronized (mLock) {
-            mShouldStop = false;
-        }
-        mThread = new Thread(new Runnable() {
-            int codes[] = { KeyEvent.KEYCODE_H, KeyEvent.KEYCODE_E, KeyEvent.KEYCODE_L,
-                    KeyEvent.KEYCODE_L, KeyEvent.KEYCODE_O, KeyEvent.KEYCODE_SPACE };
-            int i = 0;
-            @Override
-            public void run() {
-                while (true) {
-                    try {
-                        sem.acquire();
-                    } catch (InterruptedException e) {
-                        // TODO, maybe
-                    }
-                    int code = codes[i % codes.length];
-                    if (i % 100 == 99) code = KeyEvent.KEYCODE_ENTER;
-
-                    synchronized (mLock) {
-                        if (mShouldStop) break;
-                    }
-
-                    // TODO: bit of a race here, since the event can arrive after pause/stop.
-                    // (Can't synchronize on key send, since it's synchronous.)
-                    instrumentation.sendKeyDownUpSync(code);
-                    i++;
-                }
-            }
-        });
-        mThread.start();
     }
 
     @Override
     protected void onPause() {
-        synchronized (mLock) {
-            mShouldStop = true;
-        }
+        // Cancel the typing when the test activity was paused.
+        sendBroadcast(new Intent(ACTION_CANCEL_TYPING_CALLBACK).addFlags(
+                Intent.FLAG_RECEIVER_FOREGROUND | Intent.FLAG_RECEIVER_REGISTERED_ONLY));
         super.onPause();
     }
 }
diff --git a/tests/UsbTests/Android.bp b/tests/UsbTests/Android.bp
index 9328b67..c60c519 100644
--- a/tests/UsbTests/Android.bp
+++ b/tests/UsbTests/Android.bp
@@ -29,7 +29,7 @@
     static_libs: [
         "frameworks-base-testutils",
         "androidx.test.rules",
-        "mockito-target-inline-minus-junit4",
+        "mockito-target-extended-minus-junit4",
         "platform-test-annotations",
         "services.core",
         "services.net",
@@ -37,7 +37,12 @@
         "truth-prebuilt",
         "UsbManagerTestLib",
     ],
-    jni_libs: ["libdexmakerjvmtiagent"],
+    jni_libs: [
+        // Required for ExtendedMockito
+        "libdexmakerjvmtiagent",
+        "libmultiplejvmtiagentsinterferenceagent",
+        "libstaticjvmtiagent",
+    ],
     certificate: "platform",
     platform_apis: true,
     test_suites: ["device-tests"],
diff --git a/tests/UsbTests/src/com/android/server/usb/UsbHandlerTest.java b/tests/UsbTests/src/com/android/server/usb/UsbHandlerTest.java
index 210e3ea..c2d0f7c 100644
--- a/tests/UsbTests/src/com/android/server/usb/UsbHandlerTest.java
+++ b/tests/UsbTests/src/com/android/server/usb/UsbHandlerTest.java
@@ -19,6 +19,7 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -27,23 +28,29 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.SharedPreferences;
+import android.debug.AdbManagerInternal;
+import android.debug.AdbTransportType;
 import android.hardware.usb.UsbManager;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
-import android.provider.Settings;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
 import com.android.server.FgThread;
+import com.android.server.LocalServices;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
 
 import java.util.HashMap;
 import java.util.Locale;
@@ -68,6 +75,8 @@
     private SharedPreferences mSharedPreferences;
     @Mock
     private SharedPreferences.Editor mEditor;
+    @Mock
+    private AdbManagerInternal mAdbManagerInternal;
 
     private MockUsbHandler mUsbHandler;
 
@@ -83,6 +92,7 @@
 
     private Map<String, String> mMockProperties;
     private Map<String, Integer> mMockGlobalSettings;
+    private MockitoSession mStaticMockSession;
 
     private class MockUsbHandler extends UsbDeviceManager.UsbHandler {
         boolean mIsUsbTransferAllowed;
@@ -157,6 +167,10 @@
     @Before
     public void before() {
         MockitoAnnotations.initMocks(this);
+        mStaticMockSession = ExtendedMockito.mockitoSession()
+                .mockStatic(LocalServices.class)
+                .strictness(Strictness.WARN)
+                .startMocking();
         mMockProperties = new HashMap<>();
         mMockGlobalSettings = new HashMap<>();
         when(mSharedPreferences.edit()).thenReturn(mEditor);
@@ -164,6 +178,16 @@
         mUsbHandler = new MockUsbHandler(FgThread.get().getLooper(),
                 InstrumentationRegistry.getContext(), mUsbDeviceManager, mUsbAlsaManager,
                 mUsbSettingsManager, mUsbPermissionManager);
+
+        when(LocalServices.getService(eq(AdbManagerInternal.class)))
+                .thenReturn(mAdbManagerInternal);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        if (mStaticMockSession != null) {
+            mStaticMockSession.finishMocking();
+        }
     }
 
     @SmallTest
@@ -234,8 +258,8 @@
         assertEquals(mUsbHandler.getEnabledFunctions(), UsbManager.FUNCTION_NONE);
         assertEquals(mMockProperties.get(UsbDeviceManager.UsbHandler
                 .USB_PERSISTENT_CONFIG_PROPERTY), UsbManager.USB_FUNCTION_ADB);
-        assertTrue(mUsbHandler.isAdbEnabled());
 
+        when(mAdbManagerInternal.isAdbEnabled(eq(AdbTransportType.USB))).thenReturn(true);
         mUsbHandler.handleMessage(mUsbHandler.obtainMessage(MSG_UPDATE_STATE, 1, 1));
 
         assertTrue(mUsbHandler.mBroadcastedIntent.getBooleanExtra(UsbManager.USB_CONNECTED, false));
@@ -271,20 +295,6 @@
 
     @SmallTest
     @Test
-    public void bootCompletedAdbEnabled() {
-        mMockProperties.put(UsbDeviceManager.UsbHandler.USB_PERSISTENT_CONFIG_PROPERTY, "adb");
-        mUsbHandler = new MockUsbHandler(FgThread.get().getLooper(),
-                InstrumentationRegistry.getContext(), mUsbDeviceManager, mUsbAlsaManager,
-                mUsbSettingsManager, mUsbPermissionManager);
-
-        sendBootCompleteMessages(mUsbHandler);
-        assertEquals(mUsbHandler.getEnabledFunctions(), UsbManager.FUNCTION_NONE);
-        assertEquals(mMockGlobalSettings.get(Settings.Global.ADB_ENABLED).intValue(), 1);
-        assertTrue(mUsbHandler.isAdbEnabled());
-    }
-
-    @SmallTest
-    @Test
     public void userSwitchedDisablesMtp() {
         mUsbHandler.handleMessage(mUsbHandler.obtainMessage(MSG_SET_CURRENT_FUNCTIONS,
                 UsbManager.FUNCTION_MTP));
diff --git a/tests/componentalias/Android.bp b/tests/componentalias/Android.bp
index 7af76e1..01d34e4 100644
--- a/tests/componentalias/Android.bp
+++ b/tests/componentalias/Android.bp
@@ -26,7 +26,6 @@
         "compatibility-device-util-axt",
         "mockito-target-extended-minus-junit4",
         "truth-prebuilt",
-        "ub-uiautomator",
     ],
     libs: ["android.test.base"],
     srcs: [
diff --git a/tests/permission/OWNERS b/tests/permission/OWNERS
index 999ea0e..2fe78c5 100644
--- a/tests/permission/OWNERS
+++ b/tests/permission/OWNERS
@@ -1 +1,2 @@
+#Bug component: 137825
 include /core/java/android/permission/OWNERS
diff --git a/tests/testables/src/android/testing/TestableLooper.java b/tests/testables/src/android/testing/TestableLooper.java
index edd6dd3..82e40b1 100644
--- a/tests/testables/src/android/testing/TestableLooper.java
+++ b/tests/testables/src/android/testing/TestableLooper.java
@@ -32,6 +32,7 @@
 import java.lang.annotation.Target;
 import java.lang.reflect.Field;
 import java.util.Map;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
  * This is a wrapper around {@link TestLooperManager} to make it easier to manage
@@ -55,7 +56,6 @@
     private MessageHandler mMessageHandler;
 
     private Handler mHandler;
-    private Runnable mEmptyMessage;
     private TestLooperManager mQueueWrapper;
 
     static {
@@ -121,8 +121,12 @@
      * @param num Number of messages to parse
      */
     public int processMessages(int num) {
+        return processMessagesInternal(num, null);
+    }
+
+    private int processMessagesInternal(int num, Runnable barrierRunnable) {
         for (int i = 0; i < num; i++) {
-            if (!parseMessageInt()) {
+            if (!processSingleMessage(barrierRunnable)) {
                 return i + 1;
             }
         }
@@ -130,6 +134,27 @@
     }
 
     /**
+     * Process up to a certain number of messages, not blocking if the queue has less messages than
+     * that
+     * @param num the maximum number of messages to process
+     * @return the number of messages processed. This will be at most {@code num}.
+     */
+
+    public int processMessagesNonBlocking(int num) {
+        final AtomicBoolean reachedBarrier = new AtomicBoolean(false);
+        Runnable barrierRunnable = () -> {
+            reachedBarrier.set(true);
+        };
+        mHandler.post(barrierRunnable);
+        waitForMessage(mQueueWrapper, mHandler, barrierRunnable);
+        try {
+            return processMessagesInternal(num, barrierRunnable) + (reachedBarrier.get() ? -1 : 0);
+        } finally {
+            mHandler.removeCallbacks(barrierRunnable);
+        }
+    }
+
+    /**
      * Process messages in the queue until no more are found.
      */
     public void processAllMessages() {
@@ -165,19 +190,20 @@
 
     private int processQueuedMessages() {
         int count = 0;
-        mEmptyMessage = () -> { };
-        mHandler.post(mEmptyMessage);
-        waitForMessage(mQueueWrapper, mHandler, mEmptyMessage);
-        while (parseMessageInt()) count++;
+        Runnable barrierRunnable = () -> { };
+        mHandler.post(barrierRunnable);
+        waitForMessage(mQueueWrapper, mHandler, barrierRunnable);
+        while (processSingleMessage(barrierRunnable)) count++;
         return count;
     }
 
-    private boolean parseMessageInt() {
+    private boolean processSingleMessage(Runnable barrierRunnable) {
         try {
             Message result = mQueueWrapper.next();
             if (result != null) {
                 // This is a break message.
-                if (result.getCallback() == mEmptyMessage) {
+                if (result.getCallback() == barrierRunnable) {
+                    mQueueWrapper.execute(result);
                     mQueueWrapper.recycle(result);
                     return false;
                 }
diff --git a/tests/testables/src/com/android/internal/config/sysui/TestableFlagResolver.java b/tests/testables/src/com/android/internal/config/sysui/TestableFlagResolver.java
index a8815dc..6a6ab00 100644
--- a/tests/testables/src/com/android/internal/config/sysui/TestableFlagResolver.java
+++ b/tests/testables/src/com/android/internal/config/sysui/TestableFlagResolver.java
@@ -27,7 +27,9 @@
         return mOverrides.getOrDefault(flag.mSysPropKey, flag.mDefaultValue);
     }
 
-    public void setFlagOverride(SystemUiSystemPropertiesFlags.Flag flag, boolean isEnabled) {
+    public TestableFlagResolver setFlagOverride(SystemUiSystemPropertiesFlags.Flag flag,
+            boolean isEnabled) {
         mOverrides.put(flag.mSysPropKey, isEnabled);
+        return this;
     }
 }
diff --git a/tests/testables/tests/src/android/testing/TestableLooperTest.java b/tests/testables/tests/src/android/testing/TestableLooperTest.java
index 0f491b8..a02eb6b 100644
--- a/tests/testables/tests/src/android/testing/TestableLooperTest.java
+++ b/tests/testables/tests/src/android/testing/TestableLooperTest.java
@@ -27,12 +27,6 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.InOrder;
-
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
@@ -40,6 +34,11 @@
 import android.testing.TestableLooper.MessageHandler;
 import android.testing.TestableLooper.RunWithLooper;
 
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InOrder;
+
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @RunWithLooper
@@ -240,4 +239,33 @@
         inOrder.verify(handler).dispatchMessage(messageC);
     }
 
+    @Test
+    public void testProcessMessagesNonBlocking_onlyArgNumber() {
+        Handler h = new Handler(mTestableLooper.getLooper());
+        Runnable r = mock(Runnable.class);
+
+        h.post(r);
+        h.post(r);
+        h.post(r);
+
+        int processed = mTestableLooper.processMessagesNonBlocking(2);
+
+        verify(r, times(2)).run();
+        assertEquals(2, processed);
+    }
+
+    @Test
+    public void testProcessMessagesNonBlocking_lessMessagesThanArg() {
+        Handler h = new Handler(mTestableLooper.getLooper());
+        Runnable r = mock(Runnable.class);
+
+        h.post(r);
+        h.post(r);
+        h.post(r);
+
+        int processed = mTestableLooper.processMessagesNonBlocking(5);
+
+        verify(r, times(3)).run();
+        assertEquals(3, processed);
+    }
 }
diff --git a/tests/utils/testutils/java/android/os/test/FakePermissionEnforcer.java b/tests/utils/testutils/java/android/os/test/FakePermissionEnforcer.java
index b94bb41..d10ae30 100644
--- a/tests/utils/testutils/java/android/os/test/FakePermissionEnforcer.java
+++ b/tests/utils/testutils/java/android/os/test/FakePermissionEnforcer.java
@@ -47,6 +47,10 @@
         mGranted.remove(permission);
     }
 
+    public void revokeAll() {
+        mGranted.clear();
+    }
+
     private boolean granted(String permission) {
         return mGranted.contains(permission);
     }
diff --git a/tests/utils/testutils/java/com/android/server/accessibility/TEST_MAPPING b/tests/utils/testutils/java/com/android/server/accessibility/TEST_MAPPING
new file mode 100644
index 0000000..1c67399
--- /dev/null
+++ b/tests/utils/testutils/java/com/android/server/accessibility/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "imports": [
+    {
+      "path": "frameworks/base/services/accessibility/TEST_MAPPING"
+    }
+  ]
+}
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
index 89271e1..302af52 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
@@ -42,6 +42,7 @@
 import static org.mockito.Matchers.argThat;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
@@ -345,6 +346,33 @@
         verify(mConnMgr).reportNetworkConnectivity(eq(mNetworkAgent.getNetwork()), eq(false));
     }
 
+    @Test
+    public void testMigrationHandleFailure() throws Exception {
+        triggerChildOpened();
+        mTestLooper.dispatchAll();
+        assertEquals(mIkeConnectionInfo, mGatewayConnection.getIkeConnectionInfo());
+
+        mGatewayConnection
+                .getUnderlyingNetworkControllerCallback()
+                .onSelectedUnderlyingNetworkChanged(TEST_UNDERLYING_NETWORK_RECORD_2);
+
+        final IkeSessionConnectionInfo newIkeConnectionInfo =
+                new IkeSessionConnectionInfo(
+                        TEST_ADDR_V4, TEST_ADDR_V4_2, TEST_UNDERLYING_NETWORK_RECORD_2.network);
+        getIkeSessionCallback().onIkeSessionConnectionInfoChanged(newIkeConnectionInfo);
+        getChildSessionCallback()
+                .onIpSecTransformsMigrated(makeDummyIpSecTransform(), makeDummyIpSecTransform());
+
+        doThrow(new IllegalArgumentException("testMigrationHandleFailure"))
+                .when(mIpSecSvc)
+                .setNetworkForTunnelInterface(anyInt(), any(), any());
+
+        mTestLooper.dispatchAll();
+
+        assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState());
+        verify(mIkeSession).close();
+    }
+
     private void triggerChildOpened() {
         triggerChildOpened(Collections.singletonList(TEST_INTERNAL_ADDR), TEST_DNS_ADDR);
     }
diff --git a/tools/aapt2/Android.bp b/tools/aapt2/Android.bp
index 0d6dc35..7323b0f 100644
--- a/tools/aapt2/Android.bp
+++ b/tools/aapt2/Android.bp
@@ -47,6 +47,7 @@
         "-Wno-missing-field-initializers",
         "-fno-exceptions",
         "-fno-rtti",
+        "-Wno-deprecated-declarations",
     ],
     target: {
         windows: {
diff --git a/tools/aapt2/Debug.cpp b/tools/aapt2/Debug.cpp
index df87889..cac4edd 100644
--- a/tools/aapt2/Debug.cpp
+++ b/tools/aapt2/Debug.cpp
@@ -265,6 +265,16 @@
     ValueHeadlinePrinter headline_printer(package.name, printer);
     ValueBodyPrinter body_printer(package.name, printer);
 
+    auto& dynamicRefTable = table.GetReferencedPackages();
+    if (!dynamicRefTable.empty()) {
+      printer->Println(StringPrintf("DynamicRefTable entryCount=%d", int(dynamicRefTable.size())));
+      printer->Indent();
+      for (auto&& [id, name] : dynamicRefTable) {
+        printer->Println(StringPrintf("0x%02x -> %s", id, name.c_str()));
+      }
+      printer->Undent();
+    }
+
     printer->Print("Package name=");
     printer->Print(package.name);
     if (package.id) {
diff --git a/tools/aapt2/LoadedApk.h b/tools/aapt2/LoadedApk.h
index 4cd7eae..27c354a 100644
--- a/tools/aapt2/LoadedApk.h
+++ b/tools/aapt2/LoadedApk.h
@@ -40,10 +40,8 @@
 };
 
 // Info about an APK loaded in memory.
-class LoadedApk {
+class LoadedApk final {
  public:
-  virtual ~LoadedApk() = default;
-
   // Loads both binary and proto APKs from disk.
   static std::unique_ptr<LoadedApk> LoadApkFromPath(android::StringPiece path,
                                                     android::IDiagnostics* diag);
@@ -96,8 +94,8 @@
    * Writes the APK on disk at the given path, while also removing the resource
    * files that are not referenced in the resource table.
    */
-  virtual bool WriteToArchive(IAaptContext* context, const TableFlattenerOptions& options,
-                              IArchiveWriter* writer);
+  bool WriteToArchive(IAaptContext* context, const TableFlattenerOptions& options,
+                      IArchiveWriter* writer);
 
   /**
    * Writes the APK on disk at the given path, while also removing the resource files that are not
@@ -108,9 +106,9 @@
    * original manifest will be written. The manifest is only required if the contents of the new APK
    * have been modified in a way that require the AndroidManifest.xml to also be modified.
    */
-  virtual bool WriteToArchive(IAaptContext* context, ResourceTable* split_table,
-                              const TableFlattenerOptions& options, FilterChain* filters,
-                              IArchiveWriter* writer, xml::XmlResource* manifest = nullptr);
+  bool WriteToArchive(IAaptContext* context, ResourceTable* split_table,
+                      const TableFlattenerOptions& options, FilterChain* filters,
+                      IArchiveWriter* writer, xml::XmlResource* manifest = nullptr);
 
   /** Loads the file as an xml document. */
   std::unique_ptr<xml::XmlResource> LoadXml(const std::string& file_path,
diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp
index fa9a98f..6af39b7 100644
--- a/tools/aapt2/ResourceParser.cpp
+++ b/tools/aapt2/ResourceParser.cpp
@@ -800,7 +800,7 @@
 
   // Process the raw value.
   std::unique_ptr<Item> processed_item = ResourceUtils::TryParseItemForAttribute(
-      xmlsub_tree.raw_value, type_mask, on_create_reference);
+      &diag, xmlsub_tree.raw_value, type_mask, on_create_reference);
   if (processed_item) {
     // Fix up the reference.
     if (auto ref = ValueCast<Reference>(processed_item.get())) {
diff --git a/tools/aapt2/ResourceTable.h b/tools/aapt2/ResourceTable.h
index bb286a8..61e399c 100644
--- a/tools/aapt2/ResourceTable.h
+++ b/tools/aapt2/ResourceTable.h
@@ -307,6 +307,11 @@
   // order.
   ResourceTableView GetPartitionedView(const ResourceTableViewOptions& options = {}) const;
 
+  using ReferencedPackages = std::map<uint8_t, std::string>;
+  const ReferencedPackages& GetReferencedPackages() const {
+    return included_packages_;
+  }
+
   struct SearchResult {
     ResourceTablePackage* package;
     ResourceTableType* type;
@@ -342,7 +347,7 @@
 
   // Set of dynamic packages that this table may reference. Their package names get encoded
   // into the resources.arsc along with their compile-time assigned IDs.
-  std::map<size_t, std::string> included_packages_;
+  ReferencedPackages included_packages_;
 
  private:
   DISALLOW_COPY_AND_ASSIGN(ResourceTable);
diff --git a/tools/aapt2/ResourceUtils.cpp b/tools/aapt2/ResourceUtils.cpp
index 5a118a9..d358df9 100644
--- a/tools/aapt2/ResourceUtils.cpp
+++ b/tools/aapt2/ResourceUtils.cpp
@@ -619,7 +619,7 @@
 }
 
 std::unique_ptr<Item> TryParseItemForAttribute(
-    StringPiece value, uint32_t type_mask,
+    android::IDiagnostics* diag, StringPiece value, uint32_t type_mask,
     const std::function<bool(const ResourceName&)>& on_create_reference) {
   using android::ResTable_map;
 
@@ -670,8 +670,32 @@
     // Try parsing this as a float.
     auto floating_point = TryParseFloat(value);
     if (floating_point) {
+      // Only check if the parsed result lost precision when the parsed item is
+      // android::Res_value::TYPE_FLOAT and there is other possible types saved in type_mask, like
+      // ResTable_map::TYPE_INTEGER.
       if (type_mask & AndroidTypeToAttributeTypeMask(floating_point->value.dataType)) {
-        return std::move(floating_point);
+        const bool mayOnlyBeFloat = (type_mask & ~float_mask) == 0;
+        const bool parsedAsFloat = floating_point->value.dataType == android::Res_value::TYPE_FLOAT;
+        if (!mayOnlyBeFloat && parsedAsFloat) {
+          float f = reinterpret_cast<float&>(floating_point->value.data);
+          std::u16string str16 = android::util::Utf8ToUtf16(util::TrimWhitespace(value));
+          double d;
+          if (android::ResTable::stringToDouble(str16.data(), str16.size(), d)) {
+            // Parse as a float only if the difference between float and double parsed from the
+            // same string is smaller than 1, otherwise return as raw string.
+            if (fabs(f - d) < 1) {
+              return std::move(floating_point);
+            } else {
+              if (diag->IsVerbose()) {
+                diag->Note(android::DiagMessage()
+                           << "precision lost greater than 1 while parsing float " << value
+                           << ", return a raw string");
+              }
+            }
+          }
+        } else {
+          return std::move(floating_point);
+        }
       }
     }
   }
@@ -683,12 +707,12 @@
  * allows.
  */
 std::unique_ptr<Item> TryParseItemForAttribute(
-    StringPiece str, const Attribute* attr,
+    android::IDiagnostics* diag, StringPiece str, const Attribute* attr,
     const std::function<bool(const ResourceName&)>& on_create_reference) {
   using android::ResTable_map;
 
   const uint32_t type_mask = attr->type_mask;
-  auto value = TryParseItemForAttribute(str, type_mask, on_create_reference);
+  auto value = TryParseItemForAttribute(diag, str, type_mask, on_create_reference);
   if (value) {
     return value;
   }
diff --git a/tools/aapt2/ResourceUtils.h b/tools/aapt2/ResourceUtils.h
index f30f4ac..50fc879 100644
--- a/tools/aapt2/ResourceUtils.h
+++ b/tools/aapt2/ResourceUtils.h
@@ -200,11 +200,11 @@
  * reference to an ID that must be created (@+id/foo).
  */
 std::unique_ptr<Item> TryParseItemForAttribute(
-    android::StringPiece value, const Attribute* attr,
+    android::IDiagnostics* diag, android::StringPiece value, const Attribute* attr,
     const std::function<bool(const ResourceName&)>& on_create_reference = {});
 
 std::unique_ptr<Item> TryParseItemForAttribute(
-    android::StringPiece value, uint32_t type_mask,
+    android::IDiagnostics* diag, android::StringPiece value, uint32_t type_mask,
     const std::function<bool(const ResourceName&)>& on_create_reference = {});
 
 uint32_t AndroidTypeToAttributeTypeMask(uint16_t type);
diff --git a/tools/aapt2/ResourceUtils_test.cpp b/tools/aapt2/ResourceUtils_test.cpp
index 568871a..4cba04d 100644
--- a/tools/aapt2/ResourceUtils_test.cpp
+++ b/tools/aapt2/ResourceUtils_test.cpp
@@ -217,17 +217,46 @@
 }
 
 TEST(ResourceUtilsTest, ItemsWithWhitespaceAreParsedCorrectly) {
-  EXPECT_THAT(ResourceUtils::TryParseItemForAttribute(" 12\n   ", ResTable_map::TYPE_INTEGER),
+  std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+  EXPECT_THAT(ResourceUtils::TryParseItemForAttribute(context->GetDiagnostics(), " 12\n   ",
+                                                      ResTable_map::TYPE_INTEGER),
               Pointee(ValueEq(BinaryPrimitive(Res_value::TYPE_INT_DEC, 12u))));
-  EXPECT_THAT(ResourceUtils::TryParseItemForAttribute(" true\n   ", ResTable_map::TYPE_BOOLEAN),
+  EXPECT_THAT(ResourceUtils::TryParseItemForAttribute(context->GetDiagnostics(), " true\n   ",
+                                                      ResTable_map::TYPE_BOOLEAN),
               Pointee(ValueEq(BinaryPrimitive(Res_value::TYPE_INT_BOOLEAN, 0xffffffffu))));
 
   const float expected_float = 12.0f;
   const uint32_t expected_float_flattened = *(uint32_t*)&expected_float;
-  EXPECT_THAT(ResourceUtils::TryParseItemForAttribute(" 12.0\n   ", ResTable_map::TYPE_FLOAT),
+  EXPECT_THAT(ResourceUtils::TryParseItemForAttribute(context->GetDiagnostics(), " 12.0\n   ",
+                                                      ResTable_map::TYPE_FLOAT),
               Pointee(ValueEq(BinaryPrimitive(Res_value::TYPE_FLOAT, expected_float_flattened))));
 }
 
+TEST(ResourceUtilsTest, FloatAndBigIntegerParsedCorrectly) {
+  std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+  const float expected_float = 0.125f;
+  const uint32_t expected_float_flattened = *(uint32_t*)&expected_float;
+  EXPECT_THAT(ResourceUtils::TryParseItemForAttribute(context->GetDiagnostics(), "0.125",
+                                                      ResTable_map::TYPE_FLOAT),
+              Pointee(ValueEq(BinaryPrimitive(Res_value::TYPE_FLOAT, expected_float_flattened))));
+
+  const float special_float = 1.0f;
+  const uint32_t special_float_flattened = *(uint32_t*)&special_float;
+  EXPECT_THAT(ResourceUtils::TryParseItemForAttribute(context->GetDiagnostics(), "1.0",
+                                                      ResTable_map::TYPE_FLOAT),
+              Pointee(ValueEq(BinaryPrimitive(Res_value::TYPE_FLOAT, special_float_flattened))));
+
+  EXPECT_EQ(ResourceUtils::TryParseItemForAttribute(context->GetDiagnostics(), "1099511627776",
+                                                    ResTable_map::TYPE_INTEGER),
+            std::unique_ptr<Item>(nullptr));
+
+  const float big_float = 1099511627776.0f;
+  const uint32_t big_flattened = *(uint32_t*)&big_float;
+  EXPECT_THAT(ResourceUtils::TryParseItemForAttribute(context->GetDiagnostics(), "1099511627776",
+                                                      ResTable_map::TYPE_FLOAT),
+              Pointee(ValueEq(BinaryPrimitive(Res_value::TYPE_FLOAT, big_flattened))));
+}
+
 TEST(ResourceUtilsTest, ParseSdkVersionWithCodename) {
   EXPECT_THAT(ResourceUtils::ParseSdkVersion("Q"), Eq(std::optional<int>(10000)));
   EXPECT_THAT(ResourceUtils::ParseSdkVersion("Q.fingerprint"), Eq(std::optional<int>(10000)));
diff --git a/tools/aapt2/ResourceValues.cpp b/tools/aapt2/ResourceValues.cpp
index a5754e0..166b01b 100644
--- a/tools/aapt2/ResourceValues.cpp
+++ b/tools/aapt2/ResourceValues.cpp
@@ -439,6 +439,21 @@
   return str;
 }
 
+// This function is designed to using different specifier to print different floats,
+// which can print more accurate format rather than using %g only.
+const char* BinaryPrimitive::DecideFormat(float f) {
+  // if the float is either too big or too tiny, print it in scientific notation.
+  // eg: "10995116277760000000000" to 1.099512e+22, "0.00000000001" to 1.000000e-11
+  if (fabs(f) > std::numeric_limits<int64_t>::max() || fabs(f) < 1e-10) {
+    return "%e";
+    // Else if the number is an integer exactly, print it without trailing zeros.
+    // eg: "1099511627776" to 1099511627776
+  } else if (int64_t(f) == f) {
+    return "%.0f";
+  }
+  return "%g";
+}
+
 void BinaryPrimitive::PrettyPrint(Printer* printer) const {
   using ::android::Res_value;
   switch (value.dataType) {
@@ -470,7 +485,9 @@
       break;
 
     case Res_value::TYPE_FLOAT:
-      printer->Print(StringPrintf("%g", *reinterpret_cast<const float*>(&value.data)));
+      float f;
+      f = *reinterpret_cast<const float*>(&value.data);
+      printer->Print(StringPrintf(DecideFormat(f), f));
       break;
 
     case Res_value::TYPE_DIMENSION:
diff --git a/tools/aapt2/ResourceValues.h b/tools/aapt2/ResourceValues.h
index 6f9dccb..5192c2b 100644
--- a/tools/aapt2/ResourceValues.h
+++ b/tools/aapt2/ResourceValues.h
@@ -284,6 +284,7 @@
   bool Equals(const Value* value) const override;
   bool Flatten(android::Res_value* out_value) const override;
   void Print(std::ostream* out) const override;
+  static const char* DecideFormat(float f);
   void PrettyPrint(text::Printer* printer) const override;
 };
 
diff --git a/tools/aapt2/SdkConstants.cpp b/tools/aapt2/SdkConstants.cpp
index a7c5479..83f2eb3 100644
--- a/tools/aapt2/SdkConstants.cpp
+++ b/tools/aapt2/SdkConstants.cpp
@@ -16,20 +16,24 @@
 
 #include "SdkConstants.h"
 
+#include <stdint.h>
+
 #include <algorithm>
 #include <string>
-#include <unordered_set>
-#include <vector>
+#include <string_view>
 
 using android::StringPiece;
+using namespace std::literals;
 
 namespace aapt {
 
-static ApiVersion sDevelopmentSdkLevel = 10000;
-static const auto sDevelopmentSdkCodeNames =
-    std::unordered_set<StringPiece>({"Q", "R", "S", "Sv2", "Tiramisu", "UpsideDownCake"});
+static constexpr ApiVersion sDevelopmentSdkLevel = 10000;
+static constexpr StringPiece sDevelopmentSdkCodeNames[] = {
+    "Q"sv, "R"sv, "S"sv, "Sv2"sv, "Tiramisu"sv, "UpsideDownCake"sv, "VanillaIceCream"sv};
 
-static const std::vector<std::pair<uint16_t, ApiVersion>> sAttrIdMap = {
+static constexpr auto sPrivacySandboxSuffix = "PrivacySandbox"sv;
+
+static constexpr std::pair<uint16_t, ApiVersion> sAttrIdMap[] = {
     {0x021c, 1},
     {0x021d, 2},
     {0x0269, SDK_CUPCAKE},
@@ -62,25 +66,37 @@
     {0x064c, SDK_S_V2},
 };
 
-static bool less_entry_id(const std::pair<uint16_t, ApiVersion>& p, uint16_t entryId) {
-  return p.first < entryId;
-}
+static_assert(std::is_sorted(std::begin(sAttrIdMap), std::end(sAttrIdMap),
+                             [](auto&& l, auto&& r) { return l.first < r.first; }));
 
 ApiVersion FindAttributeSdkLevel(const ResourceId& id) {
   if (id.package_id() != 0x01 || id.type_id() != 0x01) {
     return 0;
   }
-  auto iter = std::lower_bound(sAttrIdMap.begin(), sAttrIdMap.end(), id.entry_id(), less_entry_id);
-  if (iter == sAttrIdMap.end()) {
+  const auto it =
+      std::lower_bound(std::begin(sAttrIdMap), std::end(sAttrIdMap), id.entry_id(),
+                       [](const auto& pair, uint16_t entryId) { return pair.first < entryId; });
+  if (it == std::end(sAttrIdMap)) {
     return SDK_LOLLIPOP_MR1;
   }
-  return iter->second;
+  return it->second;
 }
 
 std::optional<ApiVersion> GetDevelopmentSdkCodeNameVersion(StringPiece code_name) {
-  return (sDevelopmentSdkCodeNames.find(code_name) == sDevelopmentSdkCodeNames.end())
-             ? std::optional<ApiVersion>()
-             : sDevelopmentSdkLevel;
+  const auto it =
+      std::find_if(std::begin(sDevelopmentSdkCodeNames), std::end(sDevelopmentSdkCodeNames),
+                   [code_name](const auto& item) { return code_name.starts_with(item); });
+  if (it == std::end(sDevelopmentSdkCodeNames)) {
+    return {};
+  }
+  if (code_name.size() == it->size()) {
+    return sDevelopmentSdkLevel;
+  }
+  if (code_name.size() == it->size() + sPrivacySandboxSuffix.size() &&
+      code_name.ends_with(sPrivacySandboxSuffix)) {
+    return sDevelopmentSdkLevel;
+  }
+  return {};
 }
 
 }  // namespace aapt
diff --git a/tools/aapt2/SdkConstants_test.cpp b/tools/aapt2/SdkConstants_test.cpp
index 61f4d71..0f645ff 100644
--- a/tools/aapt2/SdkConstants_test.cpp
+++ b/tools/aapt2/SdkConstants_test.cpp
@@ -28,4 +28,24 @@
   EXPECT_EQ(0, FindAttributeSdkLevel(ResourceId(0x7f010345)));
 }
 
+TEST(SdkConstantsTest, GetDevelopmentSdkCodeNameVersionValid) {
+  EXPECT_EQ(std::optional<ApiVersion>(10000), GetDevelopmentSdkCodeNameVersion("Q"));
+  EXPECT_EQ(std::optional<ApiVersion>(10000), GetDevelopmentSdkCodeNameVersion("VanillaIceCream"));
+}
+
+TEST(SdkConstantsTest, GetDevelopmentSdkCodeNameVersionPrivacySandbox) {
+  EXPECT_EQ(std::optional<ApiVersion>(10000), GetDevelopmentSdkCodeNameVersion("QPrivacySandbox"));
+  EXPECT_EQ(std::optional<ApiVersion>(10000),
+            GetDevelopmentSdkCodeNameVersion("VanillaIceCreamPrivacySandbox"));
+}
+
+TEST(SdkConstantsTest, GetDevelopmentSdkCodeNameVersionInvalid) {
+  EXPECT_EQ(std::optional<ApiVersion>(), GetDevelopmentSdkCodeNameVersion("A"));
+  EXPECT_EQ(std::optional<ApiVersion>(), GetDevelopmentSdkCodeNameVersion("Sv3"));
+  EXPECT_EQ(std::optional<ApiVersion>(),
+            GetDevelopmentSdkCodeNameVersion("VanillaIceCream_PrivacySandbox"));
+  EXPECT_EQ(std::optional<ApiVersion>(), GetDevelopmentSdkCodeNameVersion("PrivacySandbox"));
+  EXPECT_EQ(std::optional<ApiVersion>(), GetDevelopmentSdkCodeNameVersion("QQQQQQQQQQQQQQQ"));
+}
+
 }  // namespace aapt
diff --git a/tools/aapt2/cmd/Compile.cpp b/tools/aapt2/cmd/Compile.cpp
index 03f9715..d2ea599 100644
--- a/tools/aapt2/cmd/Compile.cpp
+++ b/tools/aapt2/cmd/Compile.cpp
@@ -597,6 +597,7 @@
 
   void SetVerbose(bool val) {
     verbose_ = val;
+    diagnostics_->SetVerbose(val);
   }
 
   bool IsVerbose() override {
diff --git a/tools/aapt2/cmd/Dump.cpp b/tools/aapt2/cmd/Dump.cpp
index 71b0802..864af06 100644
--- a/tools/aapt2/cmd/Dump.cpp
+++ b/tools/aapt2/cmd/Dump.cpp
@@ -112,6 +112,7 @@
 
   void SetVerbose(bool val) {
     verbose_ = val;
+    diagnostics_.SetVerbose(val);
   }
 
   int GetMinSdkVersion() override {
diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp
index 97404fc..eb4e38c 100644
--- a/tools/aapt2/cmd/Link.cpp
+++ b/tools/aapt2/cmd/Link.cpp
@@ -148,6 +148,7 @@
 
   void SetVerbose(bool val) {
     verbose_ = val;
+    diagnostics_->SetVerbose(val);
   }
 
   int GetMinSdkVersion() override {
diff --git a/tools/aapt2/cmd/Link_test.cpp b/tools/aapt2/cmd/Link_test.cpp
index 28fcc1a..7096f5c 100644
--- a/tools/aapt2/cmd/Link_test.cpp
+++ b/tools/aapt2/cmd/Link_test.cpp
@@ -441,8 +441,8 @@
       R"(<resources>
           <public type="attr" name="finalized_res" id="0x01010001"/>
 
-          <!-- S staged attributes (support staged resources in the same type id) -->
-          <staging-public-group type="attr" first-id="0x01010050">
+          <!-- S staged attributes (Not support staged resources in the same type id) -->
+          <staging-public-group type="attr" first-id="0x01fc0050">
             <public name="staged_s_res" />
           </staging-public-group>
 
@@ -480,8 +480,8 @@
           <public type="attr" name="staged_s2_res" id="0x01010003"/>
           <public type="string" name="staged_s_string" id="0x01020000"/>
 
-          <!-- S staged attributes (support staged resources in the same type id) -->
-          <staging-public-group-final type="attr" first-id="0x01010050">
+          <!-- S staged attributes (Not support staged resources in the same type id) -->
+          <staging-public-group-final type="attr" first-id="0x01fc0050">
             <public name="staged_s_res" />
           </staging-public-group-final>
 
@@ -551,7 +551,7 @@
   EXPECT_THAT(android_r_contents, HasSubstr("public static final int finalized_res=0x01010001;"));
   EXPECT_THAT(
       android_r_contents,
-      HasSubstr("public static final int staged_s_res; static { staged_s_res=0x01010050; }"));
+      HasSubstr("public static final int staged_s_res; static { staged_s_res=0x01fc0050; }"));
   EXPECT_THAT(
       android_r_contents,
       HasSubstr("public static final int staged_s_string; static { staged_s_string=0x01fd0080; }"));
@@ -575,7 +575,7 @@
   android::AssetManager2 am;
   auto android_asset = android::ApkAssets::Load(android_apk);
   ASSERT_THAT(android_asset, NotNull());
-  ASSERT_TRUE(am.SetApkAssets({android_asset.get()}));
+  ASSERT_TRUE(am.SetApkAssets({android_asset}));
 
   auto result = am.GetResourceId("android:attr/finalized_res");
   ASSERT_TRUE(result.has_value());
@@ -583,7 +583,7 @@
 
   result = am.GetResourceId("android:attr/staged_s_res");
   ASSERT_TRUE(result.has_value());
-  EXPECT_THAT(*result, Eq(0x01010050));
+  EXPECT_THAT(*result, Eq(0x01fc0050));
 
   result = am.GetResourceId("android:string/staged_s_string");
   ASSERT_TRUE(result.has_value());
@@ -631,7 +631,7 @@
   auto app_against_non_final = android::ApkAssets::Load(app_apk);
   ASSERT_THAT(android_asset, NotNull());
   ASSERT_THAT(app_against_non_final, NotNull());
-  ASSERT_TRUE(am.SetApkAssets({android_asset.get(), app_against_non_final.get()}));
+  ASSERT_TRUE(am.SetApkAssets({android_asset, app_against_non_final}));
 
   auto result = am.GetResourceId("android:attr/finalized_res");
   ASSERT_TRUE(result.has_value());
@@ -667,7 +667,7 @@
 
   auto app_against_final = android::ApkAssets::Load(app_apk_respin);
   ASSERT_THAT(app_against_final, NotNull());
-  ASSERT_TRUE(am.SetApkAssets({android_asset.get(), app_against_final.get()}));
+  ASSERT_TRUE(am.SetApkAssets({android_asset, app_against_final}));
 
   {
     auto style = am.GetBag(0x7f020000);
diff --git a/tools/aapt2/cmd/Optimize.cpp b/tools/aapt2/cmd/Optimize.cpp
index dbe7970..f045dad 100644
--- a/tools/aapt2/cmd/Optimize.cpp
+++ b/tools/aapt2/cmd/Optimize.cpp
@@ -101,6 +101,7 @@
 
   void SetVerbose(bool val) {
     verbose_ = val;
+    diagnostics_.SetVerbose(val);
   }
 
   void SetMinSdkVersion(int sdk_version) {
diff --git a/tools/aapt2/compile/IdAssigner.cpp b/tools/aapt2/compile/IdAssigner.cpp
index b3f98a9..5421abd 100644
--- a/tools/aapt2/compile/IdAssigner.cpp
+++ b/tools/aapt2/compile/IdAssigner.cpp
@@ -37,6 +37,7 @@
 
 template <typename Id, typename Key>
 struct NextIdFinder {
+  std::map<Id, Key> pre_assigned_ids_;
   explicit NextIdFinder(Id start_id = 0u) : next_id_(start_id){};
 
   // Attempts to reserve an identifier for the specified key.
@@ -55,7 +56,6 @@
   Id next_id_;
   bool next_id_called_ = false;
   bool exhausted_ = false;
-  std::map<Id, Key> pre_assigned_ids_;
   typename std::map<Id, Key>::iterator next_preassigned_id_;
 };
 
@@ -158,7 +158,7 @@
   }
 
   if (assigned_id_map_) {
-    // Reserve all the IDs mentioned in the stable ID map. That way we won't assig IDs that were
+    // Reserve all the IDs mentioned in the stable ID map. That way we won't assign IDs that were
     // listed in the map if they don't exist in the table.
     for (const auto& stable_id_entry : *assigned_id_map_) {
       const ResourceName& pre_assigned_name = stable_id_entry.first;
@@ -191,6 +191,11 @@
 }
 
 namespace {
+static const std::string_view staged_type_overlap_error =
+    "Staged public resource type IDs have conflict with non staged public resources type "
+    "IDs, please restart staged resource type ID assignment at 0xff in public-staging.xml "
+    "and also delete all the overlapping groups in public-final.xml";
+
 template <typename Id, typename Key>
 Result<Id> NextIdFinder<Id, Key>::ReserveId(Key key, Id id) {
   CHECK(!next_id_called_) << "ReserveId cannot be called after NextId";
@@ -282,8 +287,20 @@
     // another type.
     auto assign_result = type_id_finder_.ReserveId(key, id.type_id());
     if (!assign_result.has_value()) {
-      diag->Error(android::DiagMessage() << "can't assign ID " << id << " to resource " << name
-                                         << " because type " << assign_result.error());
+      auto pre_assigned_type = type_id_finder_.pre_assigned_ids_[id.type_id()].type;
+      bool pre_assigned_type_staged =
+          non_staged_type_ids_.find(pre_assigned_type) == non_staged_type_ids_.end();
+      auto hex_type_id = fmt::format("{:#04x}", (int)id.type_id());
+      bool current_type_staged = visibility.staged_api;
+      diag->Error(android::DiagMessage()
+                  << "can't assign type ID " << hex_type_id << " to "
+                  << (current_type_staged ? "staged type " : "non staged type ") << name.type.type
+                  << " because this type ID have been assigned to "
+                  << (pre_assigned_type_staged ? "staged type " : "non staged type ")
+                  << pre_assigned_type);
+      if (pre_assigned_type_staged || current_type_staged) {
+        diag->Error(android::DiagMessage() << staged_type_overlap_error);
+      }
       return false;
     }
     type = types_.emplace(key, TypeGroup(package_id_, id.type_id())).first;
@@ -298,6 +315,20 @@
                   << " because type already has ID " << std::hex << (int)id.type_id());
       return false;
     }
+  } else {
+    // Ensure that staged public resources cannot have the same type name and type id with
+    // non staged public resources.
+    auto non_staged_type = non_staged_type_ids_.find(name.type.type);
+    if (non_staged_type != non_staged_type_ids_.end() && non_staged_type->second == id.type_id()) {
+      diag->Error(
+          android::DiagMessage()
+          << "can`t assign type ID " << fmt::format("{:#04x}", (int)id.type_id())
+          << " to staged type " << name.type.type << " because type ID "
+          << fmt::format("{:#04x}", (int)id.type_id())
+          << " already has been assigned to a non staged resource type with the same type name");
+      diag->Error(android::DiagMessage() << staged_type_overlap_error);
+      return false;
+    }
   }
 
   auto assign_result = type->second.ReserveId(name, id);
diff --git a/tools/aapt2/compile/IdAssigner_test.cpp b/tools/aapt2/compile/IdAssigner_test.cpp
index 8911dad..ce45b7c 100644
--- a/tools/aapt2/compile/IdAssigner_test.cpp
+++ b/tools/aapt2/compile/IdAssigner_test.cpp
@@ -117,14 +117,28 @@
 }
 
 TEST_F(IdAssignerTests, FailWhenTypeHasTwoNonStagedIdsRegardlessOfStagedId) {
-  auto table = test::ResourceTableBuilder()
-                   .AddSimple("android:attr/foo", ResourceId(0x01050000))
-                   .AddSimple("android:attr/bar", ResourceId(0x01ff0006))
-                   .Add(NewResourceBuilder("android:attr/staged_baz")
-                            .SetId(0x01ff0000)
-                            .SetVisibility({.staged_api = true})
-                            .Build())
-                   .Build();
+  auto table =
+      test::ResourceTableBuilder()
+          .AddSimple("android:attr/foo", ResourceId(0x01050000))
+          .AddSimple("android:attr/bar", ResourceId(0x01ff0006))
+          .Add(NewResourceBuilder("android:attr/staged_baz")
+                   .SetId(0x01ff0000)
+                   .SetVisibility({.staged_api = true, .level = Visibility::Level::kPublic})
+                   .Build())
+          .Build();
+  IdAssigner assigner;
+  ASSERT_FALSE(assigner.Consume(context.get(), table.get()));
+}
+
+TEST_F(IdAssignerTests, FailWhenTypeHaveBothStagedAndNonStagedIds) {
+  auto table =
+      test::ResourceTableBuilder()
+          .AddSimple("android:attr/foo", ResourceId(0x01010000))
+          .Add(NewResourceBuilder("android:bool/staged_baz")
+                   .SetId(0x01010001)
+                   .SetVisibility({.staged_api = true, .level = Visibility::Level::kPublic})
+                   .Build())
+          .Build();
   IdAssigner assigner;
   ASSERT_FALSE(assigner.Consume(context.get(), table.get()));
 }
diff --git a/tools/aapt2/format/binary/TableFlattener.cpp b/tools/aapt2/format/binary/TableFlattener.cpp
index 8c594ba..f056110 100644
--- a/tools/aapt2/format/binary/TableFlattener.cpp
+++ b/tools/aapt2/format/binary/TableFlattener.cpp
@@ -68,9 +68,8 @@
 class PackageFlattener {
  public:
   PackageFlattener(IAaptContext* context, const ResourceTablePackageView& package,
-                   const std::map<size_t, std::string>* shared_libs,
-                   SparseEntriesMode sparse_entries,
-                   bool compact_entries,
+                   const ResourceTable::ReferencedPackages* shared_libs,
+                   SparseEntriesMode sparse_entries, bool compact_entries,
                    bool collapse_key_stringpool,
                    const std::set<ResourceName>& name_collapse_exemptions,
                    bool deduplicate_entry_values)
@@ -145,10 +144,9 @@
   // 2) the entries will be accessed on platforms U+, and
   // 3) all entry keys can be encoded in 16 bits
   bool UseCompactEntries(const ConfigDescription& config, std::vector<FlatEntry>* entries) const {
-    return compact_entries_ &&
-        (context_->GetMinSdkVersion() > SDK_TIRAMISU || config.sdkVersion > SDK_TIRAMISU) &&
-        std::none_of(entries->cbegin(), entries->cend(),
-          [](const auto& e) { return e.entry_key >= std::numeric_limits<uint16_t>::max(); });
+    return compact_entries_ && context_->GetMinSdkVersion() > SDK_TIRAMISU &&
+      std::none_of(entries->cbegin(), entries->cend(),
+        [](const auto& e) { return e.entry_key >= std::numeric_limits<uint16_t>::max(); });
   }
 
   std::unique_ptr<ResEntryWriter> GetResEntryWriter(bool dedup, bool compact, BigBuffer* buffer) {
@@ -548,7 +546,7 @@
   IAaptContext* context_;
   android::IDiagnostics* diag_;
   const ResourceTablePackageView package_;
-  const std::map<size_t, std::string>* shared_libs_;
+  const ResourceTable::ReferencedPackages* shared_libs_;
   SparseEntriesMode sparse_entries_;
   bool compact_entries_;
   android::StringPool type_pool_;
diff --git a/tools/aapt2/format/proto/ProtoDeserialize.cpp b/tools/aapt2/format/proto/ProtoDeserialize.cpp
index 09ef9bd..e1a3013 100644
--- a/tools/aapt2/format/proto/ProtoDeserialize.cpp
+++ b/tools/aapt2/format/proto/ProtoDeserialize.cpp
@@ -916,7 +916,7 @@
         } break;
         case pb::Primitive::kIntDecimalValue: {
           val.dataType = android::Res_value::TYPE_INT_DEC;
-          val.data = static_cast<uint32_t>(pb_prim.int_decimal_value());
+          val.data = static_cast<int32_t>(pb_prim.int_decimal_value());
         } break;
         case pb::Primitive::kIntHexadecimalValue: {
           val.dataType = android::Res_value::TYPE_INT_HEX;
diff --git a/tools/aapt2/format/proto/ProtoSerialize_test.cpp b/tools/aapt2/format/proto/ProtoSerialize_test.cpp
index afb8356..fa8860f 100644
--- a/tools/aapt2/format/proto/ProtoSerialize_test.cpp
+++ b/tools/aapt2/format/proto/ProtoSerialize_test.cpp
@@ -250,6 +250,7 @@
 }
 
 TEST(ProtoSerializeTest, SerializeAndDeserializeXml) {
+  std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
   xml::Element element;
   element.line_number = 22;
   element.column_number = 23;
@@ -269,8 +270,8 @@
   attr.namespace_uri = xml::kSchemaAndroid;
   attr.value = "23dp";
   attr.compiled_attribute = xml::AaptAttribute(Attribute{}, ResourceId(0x01010000));
-  attr.compiled_value =
-      ResourceUtils::TryParseItemForAttribute(attr.value, android::ResTable_map::TYPE_DIMENSION);
+  attr.compiled_value = ResourceUtils::TryParseItemForAttribute(
+      context->GetDiagnostics(), attr.value, android::ResTable_map::TYPE_DIMENSION);
   attr.compiled_value->SetSource(android::Source().WithLine(25));
   element.attributes.push_back(std::move(attr));
 
diff --git a/tools/aapt2/link/ReferenceLinker.cpp b/tools/aapt2/link/ReferenceLinker.cpp
index 9dadfb2..c69b325 100644
--- a/tools/aapt2/link/ReferenceLinker.cpp
+++ b/tools/aapt2/link/ReferenceLinker.cpp
@@ -164,8 +164,8 @@
 std::unique_ptr<Item> ReferenceLinkerTransformer::ParseValueWithAttribute(
     std::unique_ptr<Item> value, const Attribute* attr) {
   if (RawString* raw_string = ValueCast<RawString>(value.get())) {
-    std::unique_ptr<Item> transformed =
-        ResourceUtils::TryParseItemForAttribute(*raw_string->value, attr);
+    std::unique_ptr<Item> transformed = ResourceUtils::TryParseItemForAttribute(
+        context_->GetDiagnostics(), *raw_string->value, attr);
 
     // If we could not parse as any specific type, try a basic STRING.
     if (!transformed && (attr->type_mask & android::ResTable_map::TYPE_STRING)) {
diff --git a/tools/aapt2/link/XmlReferenceLinker.cpp b/tools/aapt2/link/XmlReferenceLinker.cpp
index d2e9bd7..aec7ceb 100644
--- a/tools/aapt2/link/XmlReferenceLinker.cpp
+++ b/tools/aapt2/link/XmlReferenceLinker.cpp
@@ -90,7 +90,8 @@
         attribute = &attr.compiled_attribute.value().attribute;
       }
 
-      attr.compiled_value = ResourceUtils::TryParseItemForAttribute(attr.value, attribute);
+      attr.compiled_value = ResourceUtils::TryParseItemForAttribute(context_->GetDiagnostics(),
+                                                                    attr.value, attribute);
       if (attr.compiled_value) {
         // With a compiledValue, we must resolve the reference and assign it an ID.
         attr.compiled_value->SetSource(source);
diff --git a/tools/aapt2/optimize/Obfuscator.cpp b/tools/aapt2/optimize/Obfuscator.cpp
index 8f12f735..903cdf8 100644
--- a/tools/aapt2/optimize/Obfuscator.cpp
+++ b/tools/aapt2/optimize/Obfuscator.cpp
@@ -40,9 +40,9 @@
       collapse_key_stringpool_(optimizeOptions.table_flattener_options.collapse_key_stringpool) {
 }
 
-std::string ShortenFileName(android::StringPiece file_path, int output_length) {
+std::string Obfuscator::ShortenFileName(android::StringPiece file_path, int output_length) {
   std::size_t hash_num = std::hash<android::StringPiece>{}(file_path);
-  std::string result = "";
+  std::string result;
   // Convert to (modified) base64 so that it is a proper file path.
   for (int i = 0; i < output_length; i++) {
     uint8_t sextet = hash_num & 0x3f;
@@ -52,10 +52,33 @@
   return result;
 }
 
+static std::string RenameDisallowedFileNames(const std::string& file_name) {
+  // We are renaming shortened file names to make sure they not a reserved file name in Windows.
+  // See: https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file. We are renaming
+  // "COM" and "LPT" too because we are appending a number in case of hash collisions; "COM1",
+  // "COM2", etc. are reserved names.
+  static const char* const reserved_windows_names[] = {"CON", "PRN", "AUX", "NUL", "COM", "LPT"};
+  if (file_name.length() == 3) {
+    // Need to convert the file name to uppercase as Windows is case insensitive. E.g., "NuL",
+    // "nul", and "NUl" are also reserved.
+    std::string result_upper_cased(3, 0);
+    std::transform(file_name.begin(), file_name.end(), result_upper_cased.begin(),
+                   [](unsigned char c) { return std::toupper(c); });
+    for (auto reserved_windows_name : reserved_windows_names) {
+      if (result_upper_cased == reserved_windows_name) {
+        // Simple solution to make it a non-reserved name is to add an underscore
+        return "_" + file_name;
+      }
+    }
+  }
+
+  return file_name;
+}
+
 // Return the optimal hash length such that at most 10% of resources collide in
 // their shortened path.
 // Reference: http://matt.might.net/articles/counting-hash-collisions/
-int OptimalShortenedLength(int num_resources) {
+static int OptimalShortenedLength(int num_resources) {
   if (num_resources > 4000) {
     return 3;
   } else {
@@ -63,8 +86,8 @@
   }
 }
 
-std::string GetShortenedPath(android::StringPiece shortened_filename,
-                             android::StringPiece extension, int collision_count) {
+static std::string GetShortenedPath(android::StringPiece shortened_filename,
+                                    android::StringPiece extension, int collision_count) {
   std::string shortened_path = std::string("res/") += shortened_filename;
   if (collision_count > 0) {
     shortened_path += std::to_string(collision_count);
@@ -82,9 +105,9 @@
   }
 };
 
-static bool HandleShortenFilePaths(ResourceTable* table,
-                                   std::map<std::string, std::string>& shortened_path_map,
-                                   const std::set<ResourceName>& path_shorten_exemptions) {
+bool Obfuscator::HandleShortenFilePaths(ResourceTable* table,
+                                        std::map<std::string, std::string>& shortened_path_map,
+                                        const std::set<ResourceName>& path_shorten_exemptions) {
   // used to detect collisions
   std::unordered_set<std::string> shortened_paths;
   std::set<FileReference*, PathComparator> file_refs;
@@ -112,7 +135,8 @@
     // Android detects ColorStateLists via pathname, skip res/color*
     if (util::StartsWith(res_subdir, "res/color")) continue;
 
-    std::string shortened_filename = ShortenFileName(*file_ref->path, num_chars);
+    std::string shortened_filename =
+        RenameDisallowedFileNames(ShortenFileName(*file_ref->path, num_chars));
     int collision_count = 0;
     std::string shortened_path = GetShortenedPath(shortened_filename, extension, collision_count);
     while (shortened_paths.find(shortened_path) != shortened_paths.end()) {
diff --git a/tools/aapt2/optimize/Obfuscator.h b/tools/aapt2/optimize/Obfuscator.h
index 5ccf5438..79d7e08 100644
--- a/tools/aapt2/optimize/Obfuscator.h
+++ b/tools/aapt2/optimize/Obfuscator.h
@@ -53,7 +53,14 @@
       const ResourceNamedType& type_name, const ResourceTableEntryView& entry,
       const android::base::function_ref<void(Result, const ResourceName&)> onObfuscate);
 
+ protected:
+  virtual std::string ShortenFileName(android::StringPiece file_path, int output_length);
+
  private:
+  bool HandleShortenFilePaths(ResourceTable* table,
+                              std::map<std::string, std::string>& shortened_path_map,
+                              const std::set<ResourceName>& path_shorten_exemptions);
+
   TableFlattenerOptions& options_;
   const bool shorten_resource_paths_;
   const bool collapse_key_stringpool_;
diff --git a/tools/aapt2/optimize/Obfuscator_test.cpp b/tools/aapt2/optimize/Obfuscator_test.cpp
index 940cf10..c3429e0 100644
--- a/tools/aapt2/optimize/Obfuscator_test.cpp
+++ b/tools/aapt2/optimize/Obfuscator_test.cpp
@@ -19,6 +19,7 @@
 #include <map>
 #include <memory>
 #include <string>
+#include <utility>
 
 #include "ResourceTable.h"
 #include "android-base/file.h"
@@ -26,6 +27,7 @@
 
 using ::aapt::test::GetValue;
 using ::testing::AnyOf;
+using ::testing::Contains;
 using ::testing::Eq;
 using ::testing::HasSubstr;
 using ::testing::IsFalse;
@@ -33,6 +35,10 @@
 using ::testing::Not;
 using ::testing::NotNull;
 
+namespace aapt {
+
+namespace {
+
 android::StringPiece GetExtension(android::StringPiece path) {
   auto iter = std::find(path.begin(), path.end(), '.');
   return android::StringPiece(iter, path.end() - iter);
@@ -45,7 +51,22 @@
   }
 }
 
-namespace aapt {
+class FakeObfuscator : public Obfuscator {
+ public:
+  explicit FakeObfuscator(OptimizeOptions& optimize_options,
+                          const std::unordered_map<std::string, std::string>& shortened_name_map)
+      : Obfuscator(optimize_options), shortened_name_map_(shortened_name_map) {
+  }
+
+ protected:
+  std::string ShortenFileName(android::StringPiece file_path, int output_length) override {
+    return shortened_name_map_[std::string(file_path)];
+  }
+
+ private:
+  std::unordered_map<std::string, std::string> shortened_name_map_;
+  DISALLOW_COPY_AND_ASSIGN(FakeObfuscator);
+};
 
 TEST(ObfuscatorTest, FileRefPathsChangedInResourceTable) {
   std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
@@ -127,7 +148,7 @@
   EXPECT_THAT(path_map.find("res/drawables/xmlfile2.xml"), Not(Eq(path_map.end())));
 
   FileReference* ref = GetValue<FileReference>(table.get(), "android:drawable/xmlfile");
-  EXPECT_THAT(ref, NotNull());
+  ASSERT_THAT(ref, NotNull());
   ASSERT_THAT(HasFailure(), IsFalse());
   // The path of first drawable in exemption was not changed
   EXPECT_THAT("res/drawables/xmlfile.xml", Eq(*ref->path));
@@ -161,13 +182,78 @@
   ASSERT_THAT(path_map.find("res/drawable/xmlfile.xml"), Not(Eq(path_map.end())));
   ASSERT_THAT(path_map.find("res/drawable/pngfile.png"), Not(Eq(path_map.end())));
 
-  auto shortend_xml_path = path_map[original_xml_path];
-  auto shortend_png_path = path_map[original_png_path];
-
   EXPECT_THAT(GetExtension(path_map[original_xml_path]), Eq(android::StringPiece(".xml")));
   EXPECT_THAT(GetExtension(path_map[original_png_path]), Eq(android::StringPiece(".png")));
 }
 
+TEST(ObfuscatorTest, ShortenedToReservedWindowsNames) {
+  std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+
+  std::string original_path_1 = "res/drawable/pngfile_1.png";
+  std::string original_path_2 = "res/drawable/pngfile_2.png";
+  std::string original_path_3 = "res/drawable/pngfile_3.png";
+  std::string original_path_4 = "res/drawable/pngfile_4.png";
+  std::string original_path_5 = "res/drawable/pngfile_5.png";
+  std::string original_path_6 = "res/drawable/pngfile_6.png";
+  std::string original_path_7 = "res/drawable/pngfile_7.png";
+  std::string original_path_8 = "res/drawable/pngfile_8.png";
+  std::string original_path_9 = "res/drawable/pngfile_9.png";
+
+  std::unique_ptr<ResourceTable> table =
+      test::ResourceTableBuilder()
+          .AddFileReference("android:drawable/pngfile_1", original_path_1)
+          .AddFileReference("android:drawable/pngfile_2", original_path_2)
+          .AddFileReference("android:drawable/pngfile_3", original_path_3)
+          .AddFileReference("android:drawable/pngfile_4", original_path_4)
+          .AddFileReference("android:drawable/pngfile_5", original_path_5)
+          .AddFileReference("android:drawable/pngfile_6", original_path_6)
+          .AddFileReference("android:drawable/pngfile_7", original_path_7)
+          .AddFileReference("android:drawable/pngfile_8", original_path_8)
+          .AddFileReference("android:drawable/pngfile_9", original_path_9)
+          .Build();
+
+  OptimizeOptions options{.shorten_resource_paths = true};
+  std::map<std::string, std::string>& path_map = options.table_flattener_options.shortened_path_map;
+  auto obfuscator = FakeObfuscator(
+      options,
+      {
+          {original_path_1, "CON"},
+          {original_path_2, "Prn"},
+          {original_path_3, "AuX"},
+          {original_path_4, "nul"},
+          {original_path_5, "cOM"},
+          {original_path_6, "lPt"},
+          {original_path_7, "lPt"},
+          {original_path_8, "lPt"},  // 6, 7, and 8 will be appended with a number to disambiguate
+          {original_path_9, "F0o"},  // This one is not reserved
+      });
+  ASSERT_TRUE(obfuscator.Consume(context.get(), table.get()));
+
+  // Expect that the path map is populated
+  ASSERT_THAT(path_map.find(original_path_1), Not(Eq(path_map.end())));
+  ASSERT_THAT(path_map.find(original_path_2), Not(Eq(path_map.end())));
+  ASSERT_THAT(path_map.find(original_path_3), Not(Eq(path_map.end())));
+  ASSERT_THAT(path_map.find(original_path_4), Not(Eq(path_map.end())));
+  ASSERT_THAT(path_map.find(original_path_5), Not(Eq(path_map.end())));
+  ASSERT_THAT(path_map.find(original_path_6), Not(Eq(path_map.end())));
+  ASSERT_THAT(path_map.find(original_path_7), Not(Eq(path_map.end())));
+  ASSERT_THAT(path_map.find(original_path_8), Not(Eq(path_map.end())));
+  ASSERT_THAT(path_map.find(original_path_9), Not(Eq(path_map.end())));
+
+  EXPECT_THAT(path_map[original_path_1], Eq("res/_CON.png"));
+  EXPECT_THAT(path_map[original_path_2], Eq("res/_Prn.png"));
+  EXPECT_THAT(path_map[original_path_3], Eq("res/_AuX.png"));
+  EXPECT_THAT(path_map[original_path_4], Eq("res/_nul.png"));
+  EXPECT_THAT(path_map[original_path_5], Eq("res/_cOM.png"));
+  EXPECT_THAT(path_map[original_path_9], Eq("res/F0o.png"));
+
+  std::set<std::string> lpt_shortened_names{path_map[original_path_6], path_map[original_path_7],
+                                            path_map[original_path_8]};
+  EXPECT_THAT(lpt_shortened_names, Contains("res/_lPt.png"));
+  EXPECT_THAT(lpt_shortened_names, Contains("res/_lPt1.png"));
+  EXPECT_THAT(lpt_shortened_names, Contains("res/_lPt2.png"));
+}
+
 TEST(ObfuscatorTest, DeterministicallyHandleCollisions) {
   std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
 
@@ -247,7 +333,9 @@
   ASSERT_TRUE(Obfuscator(options).Consume(context.get(), table.get()));
 
   // Expect that the id resource name map is populated
+  ASSERT_THAT(id_resource_map.find(0x7f020000), Not(Eq(id_resource_map.end())));
   EXPECT_THAT(id_resource_map.at(0x7f020000), Eq("mycolor"));
+  ASSERT_THAT(id_resource_map.find(0x7f030000), Not(Eq(id_resource_map.end())));
   EXPECT_THAT(id_resource_map.at(0x7f030000), Eq("mystring"));
   EXPECT_THAT(id_resource_map.find(0x7f030001), Eq(id_resource_map.end()));
   EXPECT_THAT(id_resource_map.find(0x7f030002), Eq(id_resource_map.end()));
@@ -300,17 +388,18 @@
   ASSERT_TRUE(obfuscator.Consume(test::ContextBuilder().Build().get(),
                                  getProtocolBufferTableUnderTest().get()));
 
-  obfuscator.WriteObfuscationMap("obfuscated_map.pb");
+  const auto map_path = testing::TempDir() + "/obfuscated_map.pb";
+  ASSERT_TRUE(obfuscator.WriteObfuscationMap(map_path));
 
   std::string pbOut;
-  android::base::ReadFileToString("obfuscated_map.pb", &pbOut, false /* follow_symlinks */);
+  ASSERT_TRUE(android::base::ReadFileToString(map_path, &pbOut, false /* follow_symlinks */));
   EXPECT_THAT(pbOut, HasSubstr("drawable/xmlfile.xml"));
   EXPECT_THAT(pbOut, HasSubstr("drawable/pngfile.png"));
   EXPECT_THAT(pbOut, HasSubstr("mycolor"));
   EXPECT_THAT(pbOut, HasSubstr("mystring"));
   pb::ResourceMappings resourceMappings;
-  EXPECT_THAT(resourceMappings.ParseFromString(pbOut), IsTrue());
-  EXPECT_THAT(resourceMappings.collapsed_names().resource_names_size(), Eq(2));
+  ASSERT_THAT(resourceMappings.ParseFromString(pbOut), IsTrue());
+  ASSERT_THAT(resourceMappings.collapsed_names().resource_names_size(), Eq(2));
   auto& resource_names = resourceMappings.collapsed_names().resource_names();
   EXPECT_THAT(resource_names.at(0).name(), AnyOf(Eq("mycolor"), Eq("mystring")));
   EXPECT_THAT(resource_names.at(1).name(), AnyOf(Eq("mycolor"), Eq("mystring")));
@@ -328,11 +417,14 @@
   ASSERT_TRUE(obfuscator.Consume(test::ContextBuilder().Build().get(),
                                  getProtocolBufferTableUnderTest().get()));
 
-  obfuscator.WriteObfuscationMap("obfuscated_map.pb");
+  const auto map_path = testing::TempDir() + "/obfuscated_map.pb";
+  ASSERT_TRUE(obfuscator.WriteObfuscationMap(map_path));
 
   std::string pbOut;
-  android::base::ReadFileToString("obfuscated_map.pb", &pbOut, false /* follow_symlinks */);
+  ASSERT_TRUE(android::base::ReadFileToString(map_path, &pbOut, false /* follow_symlinks */));
   ASSERT_THAT(pbOut, Eq(""));
 }
 
+}  // namespace
+
 }  // namespace aapt
diff --git a/tools/aapt2/process/SymbolTable.cpp b/tools/aapt2/process/SymbolTable.cpp
index bca62da..d78baf9f 100644
--- a/tools/aapt2/process/SymbolTable.cpp
+++ b/tools/aapt2/process/SymbolTable.cpp
@@ -220,15 +220,9 @@
 
 bool AssetManagerSymbolSource::AddAssetPath(StringPiece path) {
   TRACE_CALL();
-  if (std::unique_ptr<const ApkAssets> apk = ApkAssets::Load(path.data())) {
+  if (auto apk = ApkAssets::Load(path.data())) {
     apk_assets_.push_back(std::move(apk));
-
-    std::vector<const ApkAssets*> apk_assets;
-    for (const std::unique_ptr<const ApkAssets>& apk_asset : apk_assets_) {
-      apk_assets.push_back(apk_asset.get());
-    }
-
-    asset_manager_.SetApkAssets(apk_assets);
+    asset_manager_.SetApkAssets(apk_assets_);
     return true;
   }
   return false;
@@ -251,7 +245,7 @@
     return true;
   }
 
-  for (const std::unique_ptr<const ApkAssets>& assets : apk_assets_) {
+  for (auto&& assets : apk_assets_) {
     for (const std::unique_ptr<const android::LoadedPackage>& loaded_package
          : assets->GetLoadedArsc()->GetPackages()) {
       if (package_name == loaded_package->GetPackageName() && loaded_package->IsDynamic()) {
@@ -266,10 +260,11 @@
 static std::unique_ptr<SymbolTable::Symbol> LookupAttributeInTable(
     android::AssetManager2& am, ResourceId id) {
   using namespace android;
-  if (am.GetApkAssets().empty()) {
+  if (am.GetApkAssetsCount() == 0) {
     return {};
   }
 
+  auto op = am.StartOperation();
   auto bag_result = am.GetBag(id.id);
   if (!bag_result.has_value()) {
     return nullptr;
diff --git a/tools/aapt2/process/SymbolTable.h b/tools/aapt2/process/SymbolTable.h
index b09ff70..36eb0ba 100644
--- a/tools/aapt2/process/SymbolTable.h
+++ b/tools/aapt2/process/SymbolTable.h
@@ -207,8 +207,8 @@
   }
 
  private:
+  std::vector<android::AssetManager2::ApkAssetsPtr> apk_assets_;
   android::AssetManager2 asset_manager_;
-  std::vector<std::unique_ptr<const android::ApkAssets>> apk_assets_;
 
   DISALLOW_COPY_AND_ASSIGN(AssetManagerSymbolSource);
 };
diff --git a/tools/incident_section_gen/main.cpp b/tools/incident_section_gen/main.cpp
index dd266ff..a2c1d6b 100644
--- a/tools/incident_section_gen/main.cpp
+++ b/tools/incident_section_gen/main.cpp
@@ -383,13 +383,11 @@
     if (allDefaults) return false;
 
     emptyline();
-    int policyCount = 0;
     printf("Privacy* %s[] = {\n", messageName.c_str());
     for (size_t i=0; i<fieldsInOrder.size(); i++) {
         const FieldDescriptor* field = fieldsInOrder[i];
         if (hasDefaultFlags[i]) continue; // NOLINT(clang-analyzer-core.uninitialized.Branch)
         printf("    &%s,\n", getFieldName(field).c_str());
-        policyCount++;
     }
     printf("    NULL };\n");
     emptyline();
diff --git a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/Constants.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/Constants.kt
index dcfbe95..f1727b7 100644
--- a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/Constants.kt
+++ b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/Constants.kt
@@ -30,6 +30,7 @@
 const val IINTERFACE_INTERFACE = "android.os.IInterface"
 
 const val AIDL_PERMISSION_HELPER_SUFFIX = "_enforcePermission"
+const val PERMISSION_PREFIX_LITERAL = "android.permission."
 
 /**
  * If a non java (e.g. c++) backend is enabled, the @EnforcePermission
@@ -72,5 +73,78 @@
         "Status",
         "IThermalService",
         "IPowerManager",
-        "ITunerResourceManager"
+        "ITunerResourceManager",
+        // b/278147400
+        "IActivityManager",
+        "IUidObserver",
+        "IDrm",
+        "IVsyncCallback",
+        "IVsyncService",
+        "ICallback",
+        "IIPCTest",
+        "ISafeInterfaceTest",
+        "IGpuService",
+        "IConsumerListener",
+        "IGraphicBufferConsumer",
+        "ITransactionComposerListener",
+        "SensorEventConnection",
+        "SensorServer",
+        "ICamera",
+        "ICameraClient",
+        "ICameraRecordingProxy",
+        "ICameraRecordingProxyListener",
+        "ICrypto",
+        "IOMXObserver",
+        "IStreamListener",
+        "IStreamSource",
+        "IAudioService",
+        "IDataSource",
+        "IDrmClient",
+        "IMediaCodecList",
+        "IMediaDrmService",
+        "IMediaExtractor",
+        "IMediaExtractorService",
+        "IMediaHTTPConnection",
+        "IMediaHTTPService",
+        "IMediaLogService",
+        "IMediaMetadataRetriever",
+        "IMediaMetricsService",
+        "IMediaPlayer",
+        "IMediaPlayerClient",
+        "IMediaPlayerService",
+        "IMediaRecorder",
+        "IMediaRecorderClient",
+        "IMediaResourceMonitor",
+        "IMediaSource",
+        "IRemoteDisplay",
+        "IRemoteDisplayClient",
+        "IResourceManagerClient",
+        "IResourceManagerService",
+        "IComplexTypeInterface",
+        "IPermissionController",
+        "IPingResponder",
+        "IProcessInfoService",
+        "ISchedulingPolicyService",
+        "IStringConstants",
+        "IObbActionListener",
+        "IStorageEventListener",
+        "IStorageManager",
+        "IStorageShutdownObserver",
+        "IPersistentVrStateCallbacks",
+        "IVrManager",
+        "IVrStateCallbacks",
+        "ISurfaceComposer",
+        "IMemory",
+        "IMemoryHeap",
+        "IProcfsInspector",
+        "IAppOpsCallback",
+        "IAppOpsService",
+        "IBatteryStats",
+        "IResultReceiver",
+        "IShellCallback",
+        "IDrmManagerService",
+        "IDrmServiceListener",
+        "IAAudioClient",
+        "IAAudioService",
+        "VtsFuzzer",
 )
diff --git a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt
index 0baac2c..3a95df9 100644
--- a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt
+++ b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt
@@ -40,6 +40,8 @@
 import org.jetbrains.uast.UMethod
 import org.jetbrains.uast.toUElement
 
+import java.util.EnumSet
+
 /**
  * Lint Detector that ensures that any method overriding a method annotated
  * with @EnforcePermission is also annotated with the exact same annotation.
@@ -93,7 +95,7 @@
             val v1 = ConstantEvaluator.evaluate(context, value1)
             val v2 = ConstantEvaluator.evaluate(context, value2)
             if (v1 != null && v2 != null) {
-                if (v1 != v2) {
+                if (v1 != v2 && !isOneShortPermissionOfOther(v1, v2)) {
                     return false
                 }
             } else {
@@ -105,7 +107,7 @@
                 for (j in children1.indices) {
                     val c1 = ConstantEvaluator.evaluate(context, children1[j])
                     val c2 = ConstantEvaluator.evaluate(context, children2[j])
-                    if (c1 != c2) {
+                    if (c1 != c2 && !isOneShortPermissionOfOther(c1, c2)) {
                         return false
                     }
                 }
@@ -114,6 +116,12 @@
         return true
     }
 
+    private fun isOneShortPermissionOfOther(
+        permission1: Any?,
+        permission2: Any?
+    ): Boolean = permission1 == (permission2 as? String)?.removePrefix(PERMISSION_PREFIX_LITERAL) ||
+            permission2 == (permission1 as? String)?.removePrefix(PERMISSION_PREFIX_LITERAL)
+
     private fun compareMethods(
         context: JavaContext,
         element: UElement,
@@ -206,7 +214,7 @@
             severity = Severity.ERROR,
             implementation = Implementation(
                     EnforcePermissionDetector::class.java,
-                    Scope.JAVA_FILE_SCOPE
+                    EnumSet.of(Scope.JAVA_FILE, Scope.TEST_SOURCES)
             )
         )
 
@@ -219,7 +227,7 @@
             severity = Severity.ERROR,
             implementation = Implementation(
                     EnforcePermissionDetector::class.java,
-                    Scope.JAVA_FILE_SCOPE
+                    EnumSet.of(Scope.JAVA_FILE, Scope.TEST_SOURCES)
             )
         )
     }
diff --git a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionHelperDetector.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionHelperDetector.kt
index df13af5..cbbf91b4 100644
--- a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionHelperDetector.kt
+++ b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionHelperDetector.kt
@@ -34,6 +34,8 @@
 import org.jetbrains.uast.UMethod
 import org.jetbrains.uast.skipParenthesizedExprDown
 
+import java.util.EnumSet
+
 class EnforcePermissionHelperDetector : Detector(), SourceCodeScanner {
     override fun getApplicableUastTypes(): List<Class<out UElement?>> =
             listOf(UMethod::class.java)
@@ -117,7 +119,7 @@
                 severity = Severity.ERROR,
                 implementation = Implementation(
                         EnforcePermissionHelperDetector::class.java,
-                        Scope.JAVA_FILE_SCOPE
+                        EnumSet.of(Scope.JAVA_FILE, Scope.TEST_SOURCES)
                 )
         )
 
@@ -130,7 +132,7 @@
                 severity = Severity.ERROR,
                 implementation = Implementation(
                         EnforcePermissionDetector::class.java,
-                        Scope.JAVA_FILE_SCOPE
+                        EnumSet.of(Scope.JAVA_FILE, Scope.TEST_SOURCES)
                 )
         )
 
diff --git a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/SimpleManualPermissionEnforcementDetector.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/SimpleManualPermissionEnforcementDetector.kt
index c7be36e..22f749e 100644
--- a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/SimpleManualPermissionEnforcementDetector.kt
+++ b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/SimpleManualPermissionEnforcementDetector.kt
@@ -53,10 +53,9 @@
                 lintFix
         )
 
-        // TODO(b/265014041): turn on errors once all code that would cause one is fixed
-        // if (enforcePermissionFix.errorLevel) {
-        //     incident.overrideSeverity(Severity.ERROR)
-        // }
+        if (enforcePermissionFix.errorLevel) {
+            incident.overrideSeverity(Severity.ERROR)
+        }
 
         context.report(incident)
     }
diff --git a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorTest.kt b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorTest.kt
index 75b0073..b3dacbd 100644
--- a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorTest.kt
+++ b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorTest.kt
@@ -316,20 +316,55 @@
                 overrides the method Stub.testMethod which is annotated with @EnforcePermission. The same annotation must be used on Default.testMethod [MissingEnforcePermissionAnnotation]
                     public void testMethod() {}
                                 ~~~~~~~~~~
-                1 errors, 0 warnings 
+                1 errors, 0 warnings
                 """.addLineContinuation()
             )
     }
 
-    fun testDoesDetectIssuesShortStringsNotAllowed() {
+    fun testDoesNotDetectIssuesShortStringsAllowedInChildAndParent() {
         lint().files(java(
             """
             package test.pkg;
             import android.annotation.EnforcePermission;
             public class TestClass121 extends IFooMethod.Stub {
                 @Override
+                @EnforcePermission("READ_PHONE_STATE")
+                public void testMethod() {}
+                @Override
+                @EnforcePermission(android.Manifest.permission.READ_PHONE_STATE)
+                public void testMethodParentShortPermission() {}
+                @Override
                 @EnforcePermission(anyOf={"INTERNET", "READ_PHONE_STATE"})
                 public void testMethodAnyLiteral() {}
+                @Override
+                @EnforcePermission(anyOf={android.Manifest.permission.INTERNET, android.Manifest.permission.READ_PHONE_STATE})
+                public void testMethodAnyLiteralParentsShortPermission() {}
+            }
+            """).indented(),
+            *stubs
+        )
+            .run()
+            .expectClean()
+    }
+
+    fun testDoesDetectIssuesWrongShortStringsInChildAndParent() {
+        lint().files(java(
+            """
+            package test.pkg;
+            import android.annotation.EnforcePermission;
+            public class TestClass121 extends IFooMethod.Stub {
+                @Override
+                @EnforcePermission("READ_WRONG_PHONE_STATE")
+                public void testMethod() {}
+                @Override
+                @EnforcePermission(android.Manifest.permission.READ_WRONG_PHONE_STATE)
+                public void testMethodParentShortPermission() {}
+                @Override
+                @EnforcePermission(anyOf={"WRONG_INTERNET", "READ_PHONE_STATE"})
+                public void testMethodAnyLiteral() {}
+                @Override
+                @EnforcePermission(anyOf={android.Manifest.permission.INTERNET, android.Manifest.permission.READ_WRONG_PHONE_STATE})
+                public void testMethodAnyLiteralParentsShortPermission() {}
             }
             """).indented(),
             *stubs
@@ -337,14 +372,19 @@
             .run()
             .expect(
                 """
-                src/test/pkg/TestClass121.java:6: Error: The method \
-                TestClass121.testMethodAnyLiteral is annotated with @EnforcePermission(anyOf={"INTERNET", "READ_PHONE_STATE"}) \
-                which differs from the overridden method Stub.testMethodAnyLiteral: \
-                @android.annotation.EnforcePermission(anyOf={android.Manifest.permission.INTERNET, "android.permission.READ_PHONE_STATE"}). \
-                The same annotation must be used for both methods. [MismatchingEnforcePermissionAnnotation]
-                    public void testMethodAnyLiteral() {}
-                                ~~~~~~~~~~~~~~~~~~~~
-                1 errors, 0 warnings
+            src/test/pkg/TestClass121.java:6: Error: The method TestClass121.testMethod is annotated with @EnforcePermission("READ_WRONG_PHONE_STATE") which differs from the overridden method Stub.testMethod: @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE). The same annotation must be used for both methods. [MismatchingEnforcePermissionAnnotation]
+                public void testMethod() {}
+                            ~~~~~~~~~~
+            src/test/pkg/TestClass121.java:9: Error: The method TestClass121.testMethodParentShortPermission is annotated with @EnforcePermission(android.Manifest.permission.READ_WRONG_PHONE_STATE) which differs from the overridden method Stub.testMethodParentShortPermission: @android.annotation.EnforcePermission("READ_PHONE_STATE"). The same annotation must be used for both methods. [MismatchingEnforcePermissionAnnotation]
+                public void testMethodParentShortPermission() {}
+                            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+            src/test/pkg/TestClass121.java:12: Error: The method TestClass121.testMethodAnyLiteral is annotated with @EnforcePermission(anyOf={"WRONG_INTERNET", "READ_PHONE_STATE"}) which differs from the overridden method Stub.testMethodAnyLiteral: @android.annotation.EnforcePermission(anyOf={android.Manifest.permission.INTERNET, "android.permission.READ_PHONE_STATE"}). The same annotation must be used for both methods. [MismatchingEnforcePermissionAnnotation]
+                public void testMethodAnyLiteral() {}
+                            ~~~~~~~~~~~~~~~~~~~~
+            src/test/pkg/TestClass121.java:15: Error: The method TestClass121.testMethodAnyLiteralParentsShortPermission is annotated with @EnforcePermission(anyOf={android.Manifest.permission.INTERNET, android.Manifest.permission.READ_WRONG_PHONE_STATE}) which differs from the overridden method Stub.testMethodAnyLiteralParentsShortPermission: @android.annotation.EnforcePermission(anyOf={"INTERNET", "READ_PHONE_STATE"}). The same annotation must be used for both methods. [MismatchingEnforcePermissionAnnotation]
+                public void testMethodAnyLiteralParentsShortPermission() {}
+                            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+            4 errors, 0 warnings
                 """.addLineContinuation()
             )
     }
@@ -360,12 +400,18 @@
             @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE)
             public void testMethod() {}
             @Override
+            @android.annotation.EnforcePermission("READ_PHONE_STATE")
+            public void testMethodParentShortPermission() {}
+            @Override
             @android.annotation.EnforcePermission(anyOf={android.Manifest.permission.INTERNET, android.Manifest.permission.READ_PHONE_STATE})
             public void testMethodAny() {}
             @Override
             @android.annotation.EnforcePermission(anyOf={android.Manifest.permission.INTERNET, "android.permission.READ_PHONE_STATE"})
             public void testMethodAnyLiteral() {}
             @Override
+            @android.annotation.EnforcePermission(anyOf={"INTERNET", "READ_PHONE_STATE"})
+            public void testMethodAnyLiteralParentsShortPermission() {}
+            @Override
             @android.annotation.EnforcePermission(allOf={android.Manifest.permission.INTERNET, android.Manifest.permission.READ_PHONE_STATE})
             public void testMethodAll() {}
             @Override
@@ -374,10 +420,14 @@
           }
           @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE)
           public void testMethod();
+          @android.annotation.EnforcePermission("READ_PHONE_STATE")
+          public void testMethodParentShortPermission();
           @android.annotation.EnforcePermission(anyOf={android.Manifest.permission.INTERNET, android.Manifest.permission.READ_PHONE_STATE})
           public void testMethodAny() {}
           @android.annotation.EnforcePermission(anyOf={android.Manifest.permission.INTERNET, "android.permission.READ_PHONE_STATE"})
           public void testMethodAnyLiteral() {}
+          @android.annotation.EnforcePermission(anyOf={"INTERNET", "READ_PHONE_STATE"})
+          public void testMethodAnyLiteralParentsShortPermission() {}
           @android.annotation.EnforcePermission(allOf={android.Manifest.permission.INTERNET, android.Manifest.permission.READ_PHONE_STATE})
           public void testMethodAll() {}
           @android.annotation.EnforcePermission(allOf={android.Manifest.permission.INTERNET, "android.permission.READ_PHONE_STATE"})
@@ -404,6 +454,7 @@
         package android.Manifest;
         class permission {
           public static final String READ_PHONE_STATE = "android.permission.READ_PHONE_STATE";
+          public static final String READ_WRONG_PHONE_STATE = "android.permission.READ_WRONG_PHONE_STATE";
           public static final String NFC = "android.permission.NFC";
           public static final String INTERNET = "android.permission.INTERNET";
         }
diff --git a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/SimpleManualPermissionEnforcementDetectorTest.kt b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/SimpleManualPermissionEnforcementDetectorTest.kt
index 6b8e72cf..9170e75 100644
--- a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/SimpleManualPermissionEnforcementDetectorTest.kt
+++ b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/SimpleManualPermissionEnforcementDetectorTest.kt
@@ -51,10 +51,10 @@
             .run()
             .expect(
                 """
-                src/Foo.java:7: Warning: ITest permission check should be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement]
+                src/Foo.java:7: Error: ITest permission check should be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement]
                         mContext.enforceCallingOrSelfPermission("android.permission.READ_CONTACTS", "foo");
                         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-                0 errors, 1 warnings
+                1 errors, 0 warnings
                 """
             )
             .expectFixDiffs(
@@ -168,10 +168,10 @@
             .run()
             .expect(
                 """
-                src/Foo.java:8: Warning: ITest permission check should be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement]
+                src/Foo.java:8: Error: ITest permission check should be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement]
                             mContext.enforceCallingOrSelfPermission(
                             ^
-                0 errors, 1 warnings
+                1 errors, 0 warnings
                 """
             )
             .expectFixDiffs(
@@ -209,10 +209,10 @@
             .run()
             .expect(
                 """
-                src/Foo.java:8: Warning: ITest permission check should be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement]
+                src/Foo.java:8: Error: ITest permission check should be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement]
                         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.READ_CONTACTS, "foo");
                         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-                0 errors, 1 warnings
+                1 errors, 0 warnings
                 """
             )
             .expectFixDiffs(
@@ -252,10 +252,10 @@
             .run()
             .expect(
                 """
-                src/Foo.java:10: Warning: ITest permission check should be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement]
+                src/Foo.java:10: Error: ITest permission check should be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement]
                             mContext.enforceCallingOrSelfPermission(
                             ^
-                0 errors, 1 warnings
+                1 errors, 0 warnings
                 """
             )
             .expectFixDiffs(
@@ -414,10 +414,10 @@
             .run()
             .expect(
                 """
-                src/Foo.java:14: Warning: ITest permission check should be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement]
+                src/Foo.java:14: Error: ITest permission check should be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement]
                         helper();
                         ~~~~~~~~~
-                0 errors, 1 warnings
+                1 errors, 0 warnings
                 """
             )
             .expectFixDiffs(
@@ -506,10 +506,10 @@
             .run()
             .expect(
                 """
-                src/Foo.java:16: Warning: ITest permission check should be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement]
+                src/Foo.java:16: Error: ITest permission check should be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement]
                         mContext.enforceCallingOrSelfPermission("FOO", "foo");
                         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-                0 errors, 1 warnings
+                1 errors, 0 warnings
                 """
             )
             .expectFixDiffs(
@@ -558,10 +558,10 @@
             .run()
             .expect(
                 """
-                src/Foo.java:19: Warning: ITest permission check should be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement]
+                src/Foo.java:19: Error: ITest permission check should be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement]
                         helperHelper();
                         ~~~~~~~~~~~~~~~
-                0 errors, 1 warnings
+                1 errors, 0 warnings
                 """
             )
             .expectFixDiffs(
@@ -599,10 +599,10 @@
                 .run()
                 .expect(
                     """
-                    src/Foo.java:7: Warning: ITest permission check should be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement]
+                    src/Foo.java:7: Error: ITest permission check should be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement]
                             if (mContext.checkCallingOrSelfPermission("android.permission.READ_CONTACTS", "foo")
                             ^
-                    0 errors, 1 warnings
+                    1 errors, 0 warnings
                     """
                 )
                 .expectFixDiffs(
diff --git a/tools/protologtool/Android.bp b/tools/protologtool/Android.bp
index 039bb4e..46745e9 100644
--- a/tools/protologtool/Android.bp
+++ b/tools/protologtool/Android.bp
@@ -11,7 +11,7 @@
     name: "protologtool-lib",
     srcs: [
         "src/com/android/protolog/tool/**/*.kt",
-        ":protolog-common-src",
+        ":protolog-common-no-android-src",
     ],
     static_libs: [
         "javaparser",
diff --git a/tools/xmlpersistence/Android.bp b/tools/xmlpersistence/Android.bp
deleted file mode 100644
index 0b6dba6..0000000
--- a/tools/xmlpersistence/Android.bp
+++ /dev/null
@@ -1,20 +0,0 @@
-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"],
-}
-
-java_binary_host {
-    name: "xmlpersistence_cli",
-    manifest: "manifest.txt",
-    srcs: [
-        "src/**/*.kt",
-    ],
-    static_libs: [
-        "javaparser-symbol-solver",
-        "javapoet",
-    ],
-}
diff --git a/tools/xmlpersistence/OWNERS b/tools/xmlpersistence/OWNERS
deleted file mode 100644
index 4f4d06a..0000000
--- a/tools/xmlpersistence/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-zhanghai@google.com
diff --git a/tools/xmlpersistence/manifest.txt b/tools/xmlpersistence/manifest.txt
deleted file mode 100644
index 6d97719..0000000
--- a/tools/xmlpersistence/manifest.txt
+++ /dev/null
@@ -1 +0,0 @@
-Main-class: MainKt
diff --git a/tools/xmlpersistence/src/main/kotlin/Generator.kt b/tools/xmlpersistence/src/main/kotlin/Generator.kt
deleted file mode 100644
index 8e62388..0000000
--- a/tools/xmlpersistence/src/main/kotlin/Generator.kt
+++ /dev/null
@@ -1,577 +0,0 @@
-/*
- * 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.
- */
-
-import com.squareup.javapoet.ClassName
-import com.squareup.javapoet.FieldSpec
-import com.squareup.javapoet.JavaFile
-import com.squareup.javapoet.MethodSpec
-import com.squareup.javapoet.NameAllocator
-import com.squareup.javapoet.ParameterSpec
-import com.squareup.javapoet.TypeSpec
-import java.io.File
-import java.io.FileInputStream
-import java.io.FileNotFoundException
-import java.io.FileOutputStream
-import java.io.IOException
-import java.nio.charset.StandardCharsets
-import java.time.Year
-import java.util.Objects
-import javax.lang.model.element.Modifier
-
-// JavaPoet only supports line comments, and can't add a newline after file level comments.
-val FILE_HEADER = """
-    /*
-     * Copyright (C) ${Year.now().value} The Android Open Source Project
-     *
-     * Licensed under the Apache License, Version 2.0 (the "License");
-     * you may not use this file except in compliance with the License.
-     * You may obtain a copy of the License at
-     *
-     *      http://www.apache.org/licenses/LICENSE-2.0
-     *
-     * Unless required by applicable law or agreed to in writing, software
-     * distributed under the License is distributed on an "AS IS" BASIS,
-     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     * See the License for the specific language governing permissions and
-     * limitations under the License.
-     */
-
-    // Generated by xmlpersistence. DO NOT MODIFY!
-    // CHECKSTYLE:OFF Generated code
-    // @formatter:off
-""".trimIndent() + "\n\n"
-
-private val atomicFileType = ClassName.get("android.util", "AtomicFile")
-
-fun generate(persistence: PersistenceInfo): JavaFile {
-    val distinctClassFields = persistence.root.allClassFields.distinctBy { it.type }
-    val type = TypeSpec.classBuilder(persistence.name)
-        .addJavadoc(
-            """
-                Generated class implementing XML persistence for${'$'}W{@link $1T}.
-                <p>
-                This class provides atomicity for persistence via {@link $2T}, however it does not provide
-                thread safety, so please bring your own synchronization mechanism.
-            """.trimIndent(), persistence.root.type, atomicFileType
-        )
-        .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
-        .addField(generateFileField())
-        .addMethod(generateConstructor())
-        .addMethod(generateReadMethod(persistence.root))
-        .addMethod(generateParseMethod(persistence.root))
-        .addMethods(distinctClassFields.map { generateParseClassMethod(it) })
-        .addMethod(generateWriteMethod(persistence.root))
-        .addMethod(generateSerializeMethod(persistence.root))
-        .addMethods(distinctClassFields.map { generateSerializeClassMethod(it) })
-        .addMethod(generateDeleteMethod())
-        .build()
-    return JavaFile.builder(persistence.root.type.packageName(), type)
-        .skipJavaLangImports(true)
-        .indent("    ")
-        .build()
-}
-
-private val nonNullType = ClassName.get("android.annotation", "NonNull")
-
-private fun generateFileField(): FieldSpec =
-    FieldSpec.builder(atomicFileType, "mFile", Modifier.PRIVATE, Modifier.FINAL)
-        .addAnnotation(nonNullType)
-        .build()
-
-private fun generateConstructor(): MethodSpec =
-    MethodSpec.constructorBuilder()
-        .addJavadoc(
-            """
-                Create an instance of this class.
-
-                @param file the XML file for persistence
-            """.trimIndent()
-        )
-        .addModifiers(Modifier.PUBLIC)
-        .addParameter(
-            ParameterSpec.builder(File::class.java, "file").addAnnotation(nonNullType).build()
-        )
-        .addStatement("mFile = new \$1T(file)", atomicFileType)
-        .build()
-
-private val nullableType = ClassName.get("android.annotation", "Nullable")
-
-private val xmlPullParserType = ClassName.get("org.xmlpull.v1", "XmlPullParser")
-
-private val xmlType = ClassName.get("android.util", "Xml")
-
-private val xmlPullParserExceptionType = ClassName.get("org.xmlpull.v1", "XmlPullParserException")
-
-private fun generateReadMethod(rootField: ClassFieldInfo): MethodSpec =
-    MethodSpec.methodBuilder("read")
-        .addJavadoc(
-            """
-                Read${'$'}W{@link $1T}${'$'}Wfrom${'$'}Wthe${'$'}WXML${'$'}Wfile.
-
-                @return the persisted${'$'}W{@link $1T},${'$'}Wor${'$'}W{@code null}${'$'}Wif${'$'}Wthe${'$'}WXML${'$'}Wfile${'$'}Wdoesn't${'$'}Wexist
-                @throws IllegalArgumentException if an error occurred while reading
-            """.trimIndent(), rootField.type
-        )
-        .addAnnotation(nullableType)
-        .addModifiers(Modifier.PUBLIC)
-        .returns(rootField.type)
-        .addControlFlow("try (\$1T inputStream = mFile.openRead())", FileInputStream::class.java) {
-            addStatement("final \$1T parser = \$2T.newPullParser()", xmlPullParserType, xmlType)
-            addStatement("parser.setInput(inputStream, null)")
-            addStatement("return parse(parser)")
-            nextControlFlow("catch (\$1T e)", FileNotFoundException::class.java)
-            addStatement("return null")
-            nextControlFlow(
-                "catch (\$1T | \$2T e)", IOException::class.java, xmlPullParserExceptionType
-            )
-            addStatement("throw new IllegalArgumentException(e)")
-        }
-        .build()
-
-private val ClassFieldInfo.allClassFields: List<ClassFieldInfo>
-    get() =
-        mutableListOf<ClassFieldInfo>().apply {
-            this += this@allClassFields
-            for (field in fields) {
-                when (field) {
-                    is ClassFieldInfo -> this += field.allClassFields
-                    is ListFieldInfo -> this += field.element.allClassFields
-                    else -> {}
-                }
-            }
-        }
-
-private fun generateParseMethod(rootField: ClassFieldInfo): MethodSpec =
-    MethodSpec.methodBuilder("parse")
-        .addAnnotation(nonNullType)
-        .addModifiers(Modifier.PRIVATE, Modifier.STATIC)
-        .returns(rootField.type)
-        .addParameter(
-            ParameterSpec.builder(xmlPullParserType, "parser").addAnnotation(nonNullType).build()
-        )
-        .addExceptions(listOf(ClassName.get(IOException::class.java), xmlPullParserExceptionType))
-        .apply {
-            addStatement("int type")
-            addStatement("int depth")
-            addStatement("int innerDepth = parser.getDepth() + 1")
-            addControlFlow(
-                "while ((type = parser.next()) != \$1T.END_DOCUMENT\$W"
-                    + "&& ((depth = parser.getDepth()) >= innerDepth || type != \$1T.END_TAG))",
-                xmlPullParserType
-            ) {
-                addControlFlow(
-                    "if (depth > innerDepth || type != \$1T.START_TAG)", xmlPullParserType
-                ) {
-                    addStatement("continue")
-                }
-                addControlFlow(
-                    "if (\$1T.equals(parser.getName(),\$W\$2S))", Objects::class.java,
-                    rootField.tagName
-                ) {
-                    addStatement("return \$1L(parser)", rootField.parseMethodName)
-                }
-            }
-            addStatement(
-                "throw new IllegalArgumentException(\$1S)",
-                "Missing root tag <${rootField.tagName}>"
-            )
-        }
-        .build()
-
-private fun generateParseClassMethod(classField: ClassFieldInfo): MethodSpec =
-    MethodSpec.methodBuilder(classField.parseMethodName)
-        .addAnnotation(nonNullType)
-        .addModifiers(Modifier.PRIVATE, Modifier.STATIC)
-        .returns(classField.type)
-        .addParameter(
-            ParameterSpec.builder(xmlPullParserType, "parser").addAnnotation(nonNullType).build()
-        )
-        .apply {
-            val (attributeFields, tagFields) = classField.fields
-                .partition { it is PrimitiveFieldInfo || it is StringFieldInfo }
-            if (tagFields.isNotEmpty()) {
-                addExceptions(
-                    listOf(ClassName.get(IOException::class.java), xmlPullParserExceptionType)
-                )
-            }
-            val nameAllocator = NameAllocator().apply {
-                newName("parser")
-                newName("type")
-                newName("depth")
-                newName("innerDepth")
-            }
-            for (field in attributeFields) {
-                val variableName = nameAllocator.newName(field.variableName, field)
-                when (field) {
-                    is PrimitiveFieldInfo -> {
-                        val stringVariableName =
-                            nameAllocator.newName("${field.variableName}String")
-                        addStatement(
-                            "final String \$1L =\$Wparser.getAttributeValue(null,\$W\$2S)",
-                            stringVariableName, field.attributeName
-                        )
-                        if (field.isRequired) {
-                            addControlFlow("if (\$1L == null)", stringVariableName) {
-                                addStatement(
-                                    "throw new IllegalArgumentException(\$1S)",
-                                    "Missing attribute \"${field.attributeName}\""
-                                )
-                            }
-                        }
-                        val boxedType = field.type.box()
-                        val parseTypeMethodName = if (field.type.isPrimitive) {
-                            "parse${field.type.toString().capitalize()}"
-                        } else {
-                            "valueOf"
-                        }
-                        if (field.isRequired) {
-                            addStatement(
-                                "final \$1T \$2L =\$W\$3T.\$4L($5L)", field.type, variableName,
-                                boxedType, parseTypeMethodName, stringVariableName
-                            )
-                        } else {
-                            addStatement(
-                                "final \$1T \$2L =\$W$3L != null ?\$W\$4T.\$5L($3L)\$W: null",
-                                field.type, variableName, stringVariableName, boxedType,
-                                parseTypeMethodName
-                            )
-                        }
-                    }
-                    is StringFieldInfo ->
-                        addStatement(
-                            "final String \$1L =\$Wparser.getAttributeValue(null,\$W\$2S)",
-                            variableName, field.attributeName
-                        )
-                    else -> error(field)
-                }
-            }
-            if (tagFields.isNotEmpty()) {
-                for (field in tagFields) {
-                    val variableName = nameAllocator.newName(field.variableName, field)
-                    when (field) {
-                        is ClassFieldInfo ->
-                            addStatement("\$1T \$2L =\$Wnull", field.type, variableName)
-                        is ListFieldInfo ->
-                            addStatement(
-                                "final \$1T \$2L =\$Wnew \$3T<>()", field.type, variableName,
-                                ArrayList::class.java
-                            )
-                        else -> error(field)
-                    }
-                }
-                addStatement("int type")
-                addStatement("int depth")
-                addStatement("int innerDepth = parser.getDepth() + 1")
-                addControlFlow(
-                    "while ((type = parser.next()) != \$1T.END_DOCUMENT\$W"
-                        + "&& ((depth = parser.getDepth()) >= innerDepth || type != \$1T.END_TAG))",
-                    xmlPullParserType
-                ) {
-                    addControlFlow(
-                        "if (depth > innerDepth || type != \$1T.START_TAG)", xmlPullParserType
-                    ) {
-                        addStatement("continue")
-                    }
-                    addControlFlow("switch (parser.getName())") {
-                        for (field in tagFields) {
-                            addControlFlow("case \$1S:", field.tagName) {
-                                val variableName = nameAllocator.get(field)
-                                when (field) {
-                                    is ClassFieldInfo -> {
-                                        addControlFlow("if (\$1L != null)", variableName) {
-                                            addStatement(
-                                                "throw new IllegalArgumentException(\$1S)",
-                                                "Duplicate tag \"${field.tagName}\""
-                                            )
-                                        }
-                                        addStatement(
-                                            "\$1L =\$W\$2L(parser)", variableName,
-                                            field.parseMethodName
-                                        )
-                                        addStatement("break")
-                                    }
-                                    is ListFieldInfo -> {
-                                        val elementNameAllocator = nameAllocator.clone()
-                                        val elementVariableName = elementNameAllocator.newName(
-                                            field.element.xmlName!!.toLowerCamelCase()
-                                        )
-                                        addStatement(
-                                            "final \$1T \$2L =\$W\$3L(parser)", field.element.type,
-                                            elementVariableName, field.element.parseMethodName
-                                        )
-                                        addStatement(
-                                            "\$1L.add(\$2L)", variableName, elementVariableName
-                                        )
-                                        addStatement("break")
-                                    }
-                                    else -> error(field)
-                                }
-                            }
-                        }
-                    }
-                }
-            }
-            for (field in tagFields.filter { it is ClassFieldInfo && it.isRequired }) {
-                addControlFlow("if ($1L == null)", nameAllocator.get(field)) {
-                    addStatement(
-                        "throw new IllegalArgumentException(\$1S)", "Missing tag <${field.tagName}>"
-                    )
-                }
-            }
-            addStatement(
-                classField.fields.joinToString(",\$W", "return new \$1T(", ")") {
-                    nameAllocator.get(it)
-                }, classField.type
-            )
-        }
-        .build()
-
-private val ClassFieldInfo.parseMethodName: String
-    get() = "parse${type.simpleName().toUpperCamelCase()}"
-
-private val xmlSerializerType = ClassName.get("org.xmlpull.v1", "XmlSerializer")
-
-private fun generateWriteMethod(rootField: ClassFieldInfo): MethodSpec =
-    MethodSpec.methodBuilder("write")
-        .apply {
-            val nameAllocator = NameAllocator().apply {
-                newName("outputStream")
-                newName("serializer")
-            }
-            val parameterName = nameAllocator.newName(rootField.variableName)
-            addJavadoc(
-                """
-                    Write${'$'}W{@link $1T}${'$'}Wto${'$'}Wthe${'$'}WXML${'$'}Wfile.
-
-                    @param $2L the${'$'}W{@link ${'$'}1T}${'$'}Wto${'$'}Wpersist
-                """.trimIndent(), rootField.type, parameterName
-            )
-            addAnnotation(nullableType)
-            addModifiers(Modifier.PUBLIC)
-            addParameter(
-                ParameterSpec.builder(rootField.type, parameterName)
-                    .addAnnotation(nonNullType)
-                    .build()
-            )
-            addStatement("\$1T outputStream = null", FileOutputStream::class.java)
-            addControlFlow("try") {
-                addStatement("outputStream = mFile.startWrite()")
-                addStatement(
-                    "final \$1T serializer =\$W\$2T.newSerializer()", xmlSerializerType, xmlType
-                )
-                addStatement(
-                    "serializer.setOutput(outputStream, \$1T.UTF_8.name())",
-                    StandardCharsets::class.java
-                )
-                addStatement(
-                    "serializer.setFeature(\$1S, true)",
-                    "http://xmlpull.org/v1/doc/features.html#indent-output"
-                )
-                addStatement("serializer.startDocument(null, true)")
-                addStatement("serialize(serializer,\$W\$1L)", parameterName)
-                addStatement("serializer.endDocument()")
-                addStatement("mFile.finishWrite(outputStream)")
-                nextControlFlow("catch (Exception e)")
-                addStatement("e.printStackTrace()")
-                addStatement("mFile.failWrite(outputStream)")
-            }
-        }
-        .build()
-
-private fun generateSerializeMethod(rootField: ClassFieldInfo): MethodSpec =
-    MethodSpec.methodBuilder("serialize")
-        .addModifiers(Modifier.PRIVATE, Modifier.STATIC)
-        .addParameter(
-            ParameterSpec.builder(xmlSerializerType, "serializer")
-                .addAnnotation(nonNullType)
-                .build()
-        )
-        .apply {
-            val nameAllocator = NameAllocator().apply { newName("serializer") }
-            val parameterName = nameAllocator.newName(rootField.variableName)
-            addParameter(
-                ParameterSpec.builder(rootField.type, parameterName)
-                    .addAnnotation(nonNullType)
-                    .build()
-            )
-            addException(IOException::class.java)
-            addStatement("serializer.startTag(null, \$1S)", rootField.tagName)
-            addStatement("\$1L(serializer, \$2L)", rootField.serializeMethodName, parameterName)
-            addStatement("serializer.endTag(null, \$1S)", rootField.tagName)
-        }
-        .build()
-
-private fun generateSerializeClassMethod(classField: ClassFieldInfo): MethodSpec =
-    MethodSpec.methodBuilder(classField.serializeMethodName)
-        .addModifiers(Modifier.PRIVATE, Modifier.STATIC)
-        .addParameter(
-            ParameterSpec.builder(xmlSerializerType, "serializer")
-                .addAnnotation(nonNullType)
-                .build()
-        )
-        .apply {
-            val nameAllocator = NameAllocator().apply {
-                newName("serializer")
-                newName("i")
-            }
-            val parameterName = nameAllocator.newName(classField.serializeParameterName)
-            addParameter(
-                ParameterSpec.builder(classField.type, parameterName)
-                    .addAnnotation(nonNullType)
-                    .build()
-            )
-            addException(IOException::class.java)
-            val (attributeFields, tagFields) = classField.fields
-                .partition { it is PrimitiveFieldInfo || it is StringFieldInfo }
-            for (field in attributeFields) {
-                val variableName = "$parameterName.${field.name}"
-                if (!field.isRequired) {
-                    beginControlFlow("if (\$1L != null)", variableName)
-                }
-                when (field) {
-                    is PrimitiveFieldInfo -> {
-                        if (field.isRequired && !field.type.isPrimitive) {
-                            addControlFlow("if (\$1L == null)", variableName) {
-                                addStatement(
-                                    "throw new IllegalArgumentException(\$1S)",
-                                    "Field \"${field.name}\" is null"
-                                )
-                            }
-                        }
-                        val stringVariableName =
-                            nameAllocator.newName("${field.variableName}String")
-                        addStatement(
-                            "final String \$1L =\$WString.valueOf(\$2L)", stringVariableName,
-                            variableName
-                        )
-                        addStatement(
-                            "serializer.attribute(null, \$1S, \$2L)", field.attributeName,
-                            stringVariableName
-                        )
-                    }
-                    is StringFieldInfo -> {
-                        if (field.isRequired) {
-                            addControlFlow("if (\$1L == null)", variableName) {
-                                addStatement(
-                                    "throw new IllegalArgumentException(\$1S)",
-                                    "Field \"${field.name}\" is null"
-                                )
-                            }
-                        }
-                        addStatement(
-                            "serializer.attribute(null, \$1S, \$2L)", field.attributeName,
-                            variableName
-                        )
-                    }
-                    else -> error(field)
-                }
-                if (!field.isRequired) {
-                    endControlFlow()
-                }
-            }
-            for (field in tagFields) {
-                val variableName = "$parameterName.${field.name}"
-                if (field.isRequired) {
-                    addControlFlow("if (\$1L == null)", variableName) {
-                        addStatement(
-                            "throw new IllegalArgumentException(\$1S)",
-                            "Field \"${field.name}\" is null"
-                        )
-                    }
-                }
-                when (field) {
-                    is ClassFieldInfo -> {
-                        addStatement("serializer.startTag(null, \$1S)", field.tagName)
-                        addStatement(
-                            "\$1L(serializer, \$2L)", field.serializeMethodName, variableName
-                        )
-                        addStatement("serializer.endTag(null, \$1S)", field.tagName)
-                    }
-                    is ListFieldInfo -> {
-                        val sizeVariableName = nameAllocator.newName("${field.variableName}Size")
-                        addStatement(
-                            "final int \$1L =\$W\$2L.size()", sizeVariableName, variableName
-                        )
-                        addControlFlow("for (int i = 0;\$Wi < \$1L;\$Wi++)", sizeVariableName) {
-                            val elementNameAllocator = nameAllocator.clone()
-                            val elementVariableName = elementNameAllocator.newName(
-                                field.element.xmlName!!.toLowerCamelCase()
-                            )
-                            addStatement(
-                                "final \$1T \$2L =\$W\$3L.get(i)", field.element.type,
-                                elementVariableName, variableName
-                            )
-                            addControlFlow("if (\$1L == null)", elementVariableName) {
-                                addStatement(
-                                    "throw new IllegalArgumentException(\$1S\$W+ i\$W+ \$2S)",
-                                    "Field element \"${field.name}[", "]\" is null"
-                                )
-                            }
-                            addStatement("serializer.startTag(null, \$1S)", field.element.tagName)
-                            addStatement(
-                                "\$1L(serializer,\$W\$2L)", field.element.serializeMethodName,
-                                elementVariableName
-                            )
-                            addStatement("serializer.endTag(null, \$1S)", field.element.tagName)
-                        }
-                    }
-                    else -> error(field)
-                }
-            }
-        }
-        .build()
-
-private val ClassFieldInfo.serializeMethodName: String
-    get() = "serialize${type.simpleName().toUpperCamelCase()}"
-
-private val ClassFieldInfo.serializeParameterName: String
-    get() = type.simpleName().toLowerCamelCase()
-
-private val FieldInfo.variableName: String
-    get() = name.toLowerCamelCase()
-
-private val FieldInfo.attributeName: String
-    get() {
-        check(this is PrimitiveFieldInfo || this is StringFieldInfo)
-        return xmlNameOrName.toLowerCamelCase()
-    }
-
-private val FieldInfo.tagName: String
-    get() {
-        check(this is ClassFieldInfo || this is ListFieldInfo)
-        return xmlNameOrName.toLowerKebabCase()
-    }
-
-private val FieldInfo.xmlNameOrName: String
-    get() = xmlName ?: name
-
-private fun generateDeleteMethod(): MethodSpec =
-    MethodSpec.methodBuilder("delete")
-        .addJavadoc("Delete the XML file, if any.")
-        .addModifiers(Modifier.PUBLIC)
-        .addStatement("mFile.delete()")
-        .build()
-
-private inline fun MethodSpec.Builder.addControlFlow(
-    controlFlow: String,
-    vararg args: Any,
-    block: MethodSpec.Builder.() -> Unit
-): MethodSpec.Builder {
-    beginControlFlow(controlFlow, *args)
-    block()
-    endControlFlow()
-    return this
-}
diff --git a/tools/xmlpersistence/src/main/kotlin/Main.kt b/tools/xmlpersistence/src/main/kotlin/Main.kt
deleted file mode 100644
index e271f8c..0000000
--- a/tools/xmlpersistence/src/main/kotlin/Main.kt
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * 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.
- */
-
-import java.io.File
-import java.nio.file.Files
-
-fun main(args: Array<String>) {
-    val showUsage = args.isEmpty() || when (args.singleOrNull()) {
-        "-h", "--help" -> true
-        else -> false
-    }
-    if (showUsage) {
-        usage()
-        return
-    }
-
-    val files = args.flatMap {
-        File(it).walk().filter { it.isFile && it.extension == "java" }.map { it.toPath() }
-    }
-    val persistences = parse(files)
-    for (persistence in persistences) {
-        val file = generate(persistence)
-        Files.newBufferedWriter(persistence.path).use {
-            it.write(FILE_HEADER)
-            file.writeTo(it)
-        }
-    }
-}
-
-private fun usage() {
-    println("Usage: xmlpersistence <FILES>")
-}
diff --git a/tools/xmlpersistence/src/main/kotlin/Parser.kt b/tools/xmlpersistence/src/main/kotlin/Parser.kt
deleted file mode 100644
index 3ea12a9..0000000
--- a/tools/xmlpersistence/src/main/kotlin/Parser.kt
+++ /dev/null
@@ -1,248 +0,0 @@
-/*
- * 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.
- */
-
-import com.github.javaparser.JavaParser
-import com.github.javaparser.ParseProblemException
-import com.github.javaparser.ParseResult
-import com.github.javaparser.ParserConfiguration
-import com.github.javaparser.ast.Node
-import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration
-import com.github.javaparser.ast.body.FieldDeclaration
-import com.github.javaparser.ast.body.TypeDeclaration
-import com.github.javaparser.ast.expr.AnnotationExpr
-import com.github.javaparser.ast.expr.Expression
-import com.github.javaparser.ast.expr.NormalAnnotationExpr
-import com.github.javaparser.ast.expr.SingleMemberAnnotationExpr
-import com.github.javaparser.ast.expr.StringLiteralExpr
-import com.github.javaparser.resolution.declarations.ResolvedReferenceTypeDeclaration
-import com.github.javaparser.resolution.types.ResolvedPrimitiveType
-import com.github.javaparser.resolution.types.ResolvedReferenceType
-import com.github.javaparser.symbolsolver.JavaSymbolSolver
-import com.github.javaparser.symbolsolver.javaparsermodel.declarations.JavaParserClassDeclaration
-import com.github.javaparser.symbolsolver.resolution.typesolvers.CombinedTypeSolver
-import com.github.javaparser.symbolsolver.resolution.typesolvers.MemoryTypeSolver
-import com.github.javaparser.symbolsolver.resolution.typesolvers.ReflectionTypeSolver
-import com.squareup.javapoet.ClassName
-import com.squareup.javapoet.ParameterizedTypeName
-import com.squareup.javapoet.TypeName
-import java.nio.file.Path
-import java.util.Optional
-
-class PersistenceInfo(
-    val name: String,
-    val root: ClassFieldInfo,
-    val path: Path
-)
-
-sealed class FieldInfo {
-    abstract val name: String
-    abstract val xmlName: String?
-    abstract val type: TypeName
-    abstract val isRequired: Boolean
-}
-
-class PrimitiveFieldInfo(
-    override val name: String,
-    override val xmlName: String?,
-    override val type: TypeName,
-    override val isRequired: Boolean
-) : FieldInfo()
-
-class StringFieldInfo(
-    override val name: String,
-    override val xmlName: String?,
-    override val isRequired: Boolean
-) : FieldInfo() {
-    override val type: TypeName = ClassName.get(String::class.java)
-}
-
-class ClassFieldInfo(
-    override val name: String,
-    override val xmlName: String?,
-    override val type: ClassName,
-    override val isRequired: Boolean,
-    val fields: List<FieldInfo>
-) : FieldInfo()
-
-class ListFieldInfo(
-    override val name: String,
-    override val xmlName: String?,
-    override val type: ParameterizedTypeName,
-    val element: ClassFieldInfo
-) : FieldInfo() {
-    override val isRequired: Boolean = true
-}
-
-fun parse(files: List<Path>): List<PersistenceInfo> {
-    val typeSolver = CombinedTypeSolver().apply { add(ReflectionTypeSolver()) }
-    val javaParser = JavaParser(ParserConfiguration()
-        .setSymbolResolver(JavaSymbolSolver(typeSolver)))
-    val compilationUnits = files.map { javaParser.parse(it).getOrThrow() }
-    val memoryTypeSolver = MemoryTypeSolver().apply {
-        for (compilationUnit in compilationUnits) {
-            for (typeDeclaration in compilationUnit.getNodesByClass<TypeDeclaration<*>>()) {
-                val name = typeDeclaration.fullyQualifiedName.getOrNull() ?: continue
-                addDeclaration(name, typeDeclaration.resolve())
-            }
-        }
-    }
-    typeSolver.add(memoryTypeSolver)
-    return mutableListOf<PersistenceInfo>().apply {
-        for (compilationUnit in compilationUnits) {
-            val classDeclarations = compilationUnit
-                .getNodesByClass<ClassOrInterfaceDeclaration>()
-                .filter { !it.isInterface && (!it.isNestedType || it.isStatic) }
-            this += classDeclarations.mapNotNull { parsePersistenceInfo(it) }
-        }
-    }
-}
-
-private fun parsePersistenceInfo(classDeclaration: ClassOrInterfaceDeclaration): PersistenceInfo? {
-    val annotation = classDeclaration.getAnnotationByName("XmlPersistence").getOrNull()
-        ?: return null
-    val rootClassName = classDeclaration.nameAsString
-    val name = annotation.getMemberValue("value")?.stringLiteralValue
-        ?: "${rootClassName}Persistence"
-    val rootXmlName = classDeclaration.getAnnotationByName("XmlName").getOrNull()
-        ?.getMemberValue("value")?.stringLiteralValue
-    val root = parseClassFieldInfo(
-        rootXmlName ?: rootClassName, rootXmlName, true, classDeclaration
-    )
-    val path = classDeclaration.findCompilationUnit().get().storage.get().path
-        .resolveSibling("$name.java")
-    return PersistenceInfo(name, root, path)
-}
-
-private fun parseClassFieldInfo(
-    name: String,
-    xmlName: String?,
-    isRequired: Boolean,
-    classDeclaration: ClassOrInterfaceDeclaration
-): ClassFieldInfo {
-    val fields = classDeclaration.fields.filterNot { it.isStatic }.map { parseFieldInfo(it) }
-    val type = classDeclaration.resolve().typeName
-    return ClassFieldInfo(name, xmlName, type, isRequired, fields)
-}
-
-private fun parseFieldInfo(field: FieldDeclaration): FieldInfo {
-    require(field.isPublic && field.isFinal)
-    val variable = field.variables.single()
-    val name = variable.nameAsString
-    val annotations = field.annotations + variable.type.annotations
-    val annotation = annotations.getByName("XmlName")
-    val xmlName = annotation?.getMemberValue("value")?.stringLiteralValue
-    val isRequired = annotations.getByName("NonNull") != null
-    return when (val type = variable.type.resolve()) {
-        is ResolvedPrimitiveType -> {
-            val primitiveType = type.typeName
-            PrimitiveFieldInfo(name, xmlName, primitiveType, true)
-        }
-        is ResolvedReferenceType -> {
-            when (type.qualifiedName) {
-                Boolean::class.javaObjectType.name, Byte::class.javaObjectType.name,
-                Short::class.javaObjectType.name, Char::class.javaObjectType.name,
-                Integer::class.javaObjectType.name, Long::class.javaObjectType.name,
-                Float::class.javaObjectType.name, Double::class.javaObjectType.name ->
-                    PrimitiveFieldInfo(name, xmlName, type.typeName, isRequired)
-                String::class.java.name -> StringFieldInfo(name, xmlName, isRequired)
-                List::class.java.name -> {
-                    requireNotNull(xmlName)
-                    val elementType = type.typeParametersValues().single()
-                    require(elementType is ResolvedReferenceType)
-                    val listType = ParameterizedTypeName.get(
-                        ClassName.get(List::class.java), elementType.typeName
-                    )
-                    val element = parseClassFieldInfo(
-                        "(element)", xmlName, true, elementType.classDeclaration
-                    )
-                    ListFieldInfo(name, xmlName, listType, element)
-                }
-                else -> parseClassFieldInfo(name, xmlName, isRequired, type.classDeclaration)
-            }
-        }
-        else -> error(type)
-    }
-}
-
-private fun <T> ParseResult<T>.getOrThrow(): T =
-    if (isSuccessful) {
-        result.get()
-    } else {
-        throw ParseProblemException(problems)
-    }
-
-private inline fun <reified T : Node> Node.getNodesByClass(): List<T> =
-    getNodesByClass(T::class.java)
-
-private fun <T : Node> Node.getNodesByClass(klass: Class<T>): List<T> = mutableListOf<T>().apply {
-    if (klass.isInstance(this@getNodesByClass)) {
-        this += klass.cast(this@getNodesByClass)
-    }
-    for (childNode in childNodes) {
-        this += childNode.getNodesByClass(klass)
-    }
-}
-
-private fun <T> Optional<T>.getOrNull(): T? = orElse(null)
-
-private fun List<AnnotationExpr>.getByName(name: String): AnnotationExpr? =
-    find { it.name.identifier == name }
-
-private fun AnnotationExpr.getMemberValue(name: String): Expression? =
-    when (this) {
-        is NormalAnnotationExpr -> pairs.find { it.nameAsString == name }?.value
-        is SingleMemberAnnotationExpr -> if (name == "value") memberValue else null
-        else -> null
-    }
-
-private val Expression.stringLiteralValue: String
-    get() {
-        require(this is StringLiteralExpr)
-        return value
-    }
-
-private val ResolvedReferenceType.classDeclaration: ClassOrInterfaceDeclaration
-    get() {
-        val resolvedClassDeclaration = typeDeclaration
-        require(resolvedClassDeclaration is JavaParserClassDeclaration)
-        return resolvedClassDeclaration.wrappedNode
-    }
-
-private val ResolvedPrimitiveType.typeName: TypeName
-    get() =
-        when (this) {
-            ResolvedPrimitiveType.BOOLEAN -> TypeName.BOOLEAN
-            ResolvedPrimitiveType.BYTE -> TypeName.BYTE
-            ResolvedPrimitiveType.SHORT -> TypeName.SHORT
-            ResolvedPrimitiveType.CHAR -> TypeName.CHAR
-            ResolvedPrimitiveType.INT -> TypeName.INT
-            ResolvedPrimitiveType.LONG -> TypeName.LONG
-            ResolvedPrimitiveType.FLOAT -> TypeName.FLOAT
-            ResolvedPrimitiveType.DOUBLE -> TypeName.DOUBLE
-        }
-
-// This doesn't support type parameters.
-private val ResolvedReferenceType.typeName: TypeName
-    get() = typeDeclaration.typeName
-
-private val ResolvedReferenceTypeDeclaration.typeName: ClassName
-    get() {
-        val packageName = packageName
-        val classNames = className.split(".")
-        val topLevelClassName = classNames.first()
-        val nestedClassNames = classNames.drop(1)
-        return ClassName.get(packageName, topLevelClassName, *nestedClassNames.toTypedArray())
-    }
diff --git a/tools/xmlpersistence/src/main/kotlin/StringCaseExtensions.kt b/tools/xmlpersistence/src/main/kotlin/StringCaseExtensions.kt
deleted file mode 100644
index b4bdbba..0000000
--- a/tools/xmlpersistence/src/main/kotlin/StringCaseExtensions.kt
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * 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.
- */
-
-import java.util.Locale
-
-private val camelHumpBoundary = Regex(
-    "-"
-    + "|_"
-    + "|(?<=[0-9])(?=[^0-9])"
-    + "|(?<=[A-Z])(?=[^A-Za-z]|[A-Z][a-z])"
-    + "|(?<=[a-z])(?=[^a-z])"
-)
-
-private fun String.toCamelHumps(): List<String> = split(camelHumpBoundary)
-
-fun String.toUpperCamelCase(): String =
-    toCamelHumps().joinToString("") { it.toLowerCase(Locale.ROOT).capitalize(Locale.ROOT) }
-
-fun String.toLowerCamelCase(): String = toUpperCamelCase().decapitalize(Locale.ROOT)
-
-fun String.toUpperKebabCase(): String =
-    toCamelHumps().joinToString("-") { it.toUpperCase(Locale.ROOT) }
-
-fun String.toLowerKebabCase(): String =
-    toCamelHumps().joinToString("-") { it.toLowerCase(Locale.ROOT) }
-
-fun String.toUpperSnakeCase(): String =
-    toCamelHumps().joinToString("_") { it.toUpperCase(Locale.ROOT) }
-
-fun String.toLowerSnakeCase(): String =
-    toCamelHumps().joinToString("_") { it.toLowerCase(Locale.ROOT) }
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java
index e207b01..01f1591 100644
--- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java
@@ -84,17 +84,12 @@
     public @interface DeviceType {
     }
 
-    /**
-     * Key in extras bundle indicating that the device battery is charging.
-     * @hide
-     */
-    public static final String EXTRA_KEY_IS_BATTERY_CHARGING = "is_battery_charging";
-
     @DeviceType
     private final int mDeviceType;
     private final String mDeviceName;
     private final String mModelName;
     private final int mBatteryPercentage;
+    private final boolean mIsBatteryCharging;
     private final int mConnectionStrength;
     private final Bundle mExtras;
 
@@ -106,6 +101,7 @@
         private String mDeviceName;
         private String mModelName;
         private int mBatteryPercentage;
+        private boolean mIsBatteryCharging;
         private int mConnectionStrength;
         private Bundle mExtras = Bundle.EMPTY;
 
@@ -167,6 +163,18 @@
         }
 
         /**
+         * Sets if the battery of the remote device is charging.
+         *
+         * @param isBatteryCharging True if battery is charging.
+         * @return Returns the Builder object.
+         */
+        @NonNull
+        public Builder setBatteryCharging(boolean isBatteryCharging) {
+            mIsBatteryCharging = isBatteryCharging;
+            return this;
+        }
+
+        /**
          * Sets the displayed connection strength of the remote device to the internet.
          *
          * @param connectionStrength Connection strength in range 0 to 4.
@@ -198,7 +206,7 @@
         @NonNull
         public NetworkProviderInfo build() {
             return new NetworkProviderInfo(mDeviceType, mDeviceName, mModelName, mBatteryPercentage,
-                    mConnectionStrength, mExtras);
+                    mIsBatteryCharging, mConnectionStrength, mExtras);
         }
     }
 
@@ -218,13 +226,14 @@
     }
 
     private NetworkProviderInfo(@DeviceType int deviceType, @NonNull String deviceName,
-            @NonNull String modelName, int batteryPercentage, int connectionStrength,
-            @NonNull Bundle extras) {
+            @NonNull String modelName, int batteryPercentage, boolean isBatteryCharging,
+            int connectionStrength, @NonNull Bundle extras) {
         validate(deviceType, deviceName, modelName, batteryPercentage, connectionStrength);
         mDeviceType = deviceType;
         mDeviceName = deviceName;
         mModelName = modelName;
         mBatteryPercentage = batteryPercentage;
+        mIsBatteryCharging = isBatteryCharging;
         mConnectionStrength = connectionStrength;
         mExtras = extras;
     }
@@ -270,6 +279,15 @@
     }
 
     /**
+     * Gets the charging state of the battery on the remote device.
+     *
+     * @return Returns true if the battery of the remote device is charging.
+     */
+    public boolean isBatteryCharging() {
+        return mIsBatteryCharging;
+    }
+
+    /**
      * Gets the displayed connection strength of the remote device to the internet.
      *
      * @return Returns the connection strength in range 0 to 4.
@@ -297,13 +315,14 @@
                 && Objects.equals(mDeviceName, other.mDeviceName)
                 && Objects.equals(mModelName, other.mModelName)
                 && mBatteryPercentage == other.mBatteryPercentage
+                && mIsBatteryCharging == other.mIsBatteryCharging
                 && mConnectionStrength == other.mConnectionStrength;
     }
 
     @Override
     public int hashCode() {
         return Objects.hash(mDeviceType, mDeviceName, mModelName, mBatteryPercentage,
-                mConnectionStrength);
+                mIsBatteryCharging, mConnectionStrength);
     }
 
     @Override
@@ -312,6 +331,7 @@
         dest.writeString(mDeviceName);
         dest.writeString(mModelName);
         dest.writeInt(mBatteryPercentage);
+        dest.writeBoolean(mIsBatteryCharging);
         dest.writeInt(mConnectionStrength);
         dest.writeBundle(mExtras);
     }
@@ -329,7 +349,7 @@
     @NonNull
     public static NetworkProviderInfo readFromParcel(@NonNull Parcel in) {
         return new NetworkProviderInfo(in.readInt(), in.readString(), in.readString(), in.readInt(),
-                in.readInt(), in.readBundle());
+                in.readBoolean(), in.readInt(), in.readBundle());
     }
 
     @NonNull
@@ -352,6 +372,7 @@
                 .append(", deviceName=").append(mDeviceName)
                 .append(", modelName=").append(mModelName)
                 .append(", batteryPercentage=").append(mBatteryPercentage)
+                .append(", isBatteryCharging=").append(mIsBatteryCharging)
                 .append(", connectionStrength=").append(mConnectionStrength)
                 .append(", extras=").append(mExtras.toString())
                 .append("]").toString();